자바 기초

자바 기초 04 - 제네릭(고급)/스레드/예외처리/소켓통신

H-V 2021. 11. 8. 13:02

유투버 '데어프로그래밍'님 강의 참조.

 

 

01 제네릭

  • 제네릭은 간단히 말해 클래스 내부에서 타입을 지정하는것이 아닌 외부에서 받아서 쓸때 사용자에 의해 지정되는 것을 의미 한다.

 

  • 제네릭을 이해하기전에 Object를 예로 보자
    //경우에 따라 문자열/숫자등 담는게 다름
    class Basket {
    	Object data;
    }
    
    public class GenericEx01 {
    	public static void main(String[] args) {
    		Basket b = new Basket();
    		b.data = 1;
    		System.out.println(b.data);
    		
    		Basket b2 = new Basket();
    		b2.data = "String";
    		System.out.println(b2.data);
    	}
    }
    
    1
    String​
    오브젝트 타입도 굉장히 편하게 외부에서 지정이 가능 하다. 하지만 오브젝트는 단점이 있다. 
  • 아래 코드를 보자
    class Tiger {
    	String name = "Tiger";
    }
    
    class Lion {
    	String name = "Lion";
    }
    
    // 다른 클래스를 담는 클래스
    class BigBasket {
    	Object data;
    }
    
    public class GenericEx02 {
    	public static void main(String[] args) {
    		BigBasket b = new BigBasket();
    		b.data = new Tiger();
    		*System.out.println(b.data.name);
    	}
    }​
    별표가 쳐진 부분에서 오류가 걸린다. 오브젝트를 써서 다른 클래스를 변수에 담을 수 있지만 그 클래스가 들고 있는것을 불러 올 수 없다. 그 이유는 현재 'b' 객체 타입이 다른 클래스가 아닌 'BigBasket'이므로 이 기능만 쓸 수 있다. 

  • 방법은 있다. 다운 캐스팅 하여 쓸 수는 있지만 되게 불편 하다. 
    Tiger t = (Tiger) b.data;
    System.out.println(t.name);
    
    Tiger​
    오브젝트의 한계가 여기서 들어난다. 다른 클래스의 기능을 받아 쓰려고 하니 다운캐스팅을 거쳐야하고 나중에 기능이 많아지면 더욱 더 복잡해 질 것이다

 

  • 그렇다면 제네릭으로 변경해 보자
    class Tiger {
    	String name = "Tiger";
    }
    
    class Lion {
    	String name = "Lion";
    }
    
    // 다른 클래스를 담는 클래스
    class BigBasket<T> {
    	T data;
    }
    
    public class GenericEx02 {
    	public static void main(String[] args) {
    		BigBasket<Lion> b = new BigBasket<>();
    		b.data = new Lion();
    		System.out.println(b.data.name);
    		
    		BigBasket<Tiger> b2 = new BigBasket<>();
    		b2.data = new Tiger();
    		System.out.println(b2.data.name);
    	}
    }​
     다운 캐스팅없이 제네릭으로 선언 후 제네릭에 원하는 클래스와 타입을 넣으면 외부에서 자유자재로 변경하면서 쓸 수가 있다!

 

 

 

 

02 제네릭 2

  • 일반 제네릭 같은 경우는 <T>를 써서 표현 하지만 와일드 카드(?)의 개념으로 <?> 물음표를 써서 진행이 가능 하다
  • 리턴 타입이 '?(물음표)'가 된다는 것인데 이 뜻은 <? extends Object>으로 오브젝트를 상속하고있는 모든 클래스중 하나가 온다라는 뜻

 

  • 예제를 하나 보자
    class BasketBall {
    	private String name = "BasketBall";
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    }
    
    class SoccerBall {
    	private String name = "SoccerBall";
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    }
    
    class Bag<T> {
    	private T data;
    	
    	public T getData() {
    		return data;
    	}
    	
    	public void setData(T data) {
    		this.data = data;
    	}
    }
    public class GenericEx03 {
    	
    	//9시 = 축구 , 12시 = 농구
    	static Bag<SoccerBall> takeOut(int time) {
    		if(time == 9) {
    			SoccerBall cb = new SoccerBall();
    			Bag<SoccerBall> b2 = new Bag<>();
    			b2.setData(cb);
    			return b2;
    		} else {
    			BasketBall bb = new BasketBall();
    			Bag<BasketBall> b1 = new Bag<>();
    			b1.setData(bb);
    			return b1;
    		}
    	}
    	public static void main(String[] args) {
    
    	}
    }​
    'takeOut()' 함수의 else 부분에서 오류가 걸리는데 그 이유는 현재 이 함수의 리턴타입이 오로지 축구공이기 때문이다. 즉 타입을 하나로 선언해버리면 그 타입으로 맞춰줘야만 실행이 된다. 이때 필요한것이 '?'.
  • 다시 예제를 보자
    public class GenericEx03 {
    	//9시 = 축구 , 12시 = 농구
    	static Bag<?> takeOut(int time) {
    		if(time == 9) {
    			System.out.println("A soccerball is in the bag");
    			SoccerBall cb = new SoccerBall();
    			Bag<SoccerBall> b2 = new Bag<>();
    			b2.setData(cb);
    			return b2;
    		} else {
    			System.out.println("A basketball is in the bag");
    			BasketBall bb = new BasketBall();
    			Bag<BasketBall> b1 = new Bag<>();
    			b1.setData(bb);
    			return b1;
    		}
    	}
    	public static void main(String[] args) {
    		Bag<?> r1 = takeOut(9);
    		Bag<?> r2 = takeOut(12);
    	}
    }
    
    A soccerball is in the bag
    A basketball is in the bag

    잘 출력이 되지만 또 하나의 문제가 있다. 물음표로 선언을 했고 제네릭에 담기는 모든 원하는 클래스가 오브젝트 형태로 되어 불러와서 쓰지만 외부에서 다른 클래스가 가지고 있는 변수 혹은 함수를 쓰지 못한다. 위에서 언급한 오브젝트의 문제점을 그대로 가지고 있다. 


  • 이럴때 해결방법이 추상클래스를 만드는 것
    abstract class Ball {
    	abstract String getName();
    }
    
    class BasketBall extends Ball {
    	...
    }
    
    class SoccerBall extends Ball {
    	...
    }
    
    ...
    
    public class GenericEx03 {
    	...
        	public static void main(String[] args) {
    		Bag<? extends Ball> r1 = takeOut(9);
    		Bag<? extends Ball> r2 = takeOut(12);
    		System.out.println(r1.getData().getName());
    		System.out.println(r2.getData().getName());
    		
    	}
    }​
    
    A soccerball is in the bag
    A basketball is in the bag
    SoccerBall
    BasketBall
    부모를 추상클래스로 선언하고 자식들이 그것들을 그대로 받아서 부모 기능 + 자기 기능들을 제네릭을 통해 다 넘기는 형식 (오버라이딩 + 다형성)
  • <?> 타입의 제네릭은 반드시 타입의 일치 (다형성), Object 클래스, 추상화, 오버라이딩(무효화)에 대한 이해가 필요하며 물음표의 제네릭 타입 + 4가지를 혼합하면 동적 바인딩이 가능해 진다. 

 

 

03 스레드

  • 스레드(thread)란 프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미. 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행
  • 다시 말해 하나의 CPU가 두가지 일을 동시에 수행을 하는데 이것을 멀티 스레드 라고 부른다. 자바는 기본적으로 하나의 스레드 (메인 스레드)를 들고 있다.
    public static void main(String[] args) {
    	...
    } # 메인 스레드 종료
  • 메인 스레드에서 멀티 스레드가 파생이 된다. 즉 자바에서는 메인스레드에서 도중에 필요한 작업들에 따라 다른 멀티 스레드들을 만들어서 병렬로 코드실행이 가능 하다. (멀티 태스킹이 가능하다는 뜻)
    public class Task implements Runnable {
    
        @Override
        public void run() {
            int sum = 0;
            for (int index = 0; index < 10; index++) {
                sum += index;
                System.out.println(sum);
            }
            System.out.println( Thread.currentThread() + "최종 합 : " + sum);
        }
    
    }
    
    public static void main(String args[]){
        Runnable task = new Task();
        Thread subTread1 = new Thread(task);
        Thread subTread2 = new Thread(task);
        subTread1.start();
        subTread2.start();
    }​
    main() 메소드에서 하나의 메인 스레드로 안의 코드가 실행되지만 중간 중간에 필요한 상황에 따라 다른 스레드들을 만들어서 왔다 갔다 하는 것을 코드로 볼 수 가 있다.

 

 

 

04 예외 처리

  • 자바에서 예외는 Exception이라고 불리는데 하나의 기능을 수행할때 나올 수 있는 에러 혹은 생각지도 못한 에러등에 대해서 처리가 필요할때 쓰는 기능
  • 자바는 크게 기능을 시작할때 기능 시작 전에 잡는 컴파일 예외처리, 기능 실행 후에 잡는 런타임 예외처리가 있다

 

  • 컴파일 에러는 보통 자바 프로그램상에서 띄워 준다
    Thread.sleep(1000); //메인 스레드가 1초 후 작동​
    이런 코드를 메인에 적어보면 오류를 일으킨다. 컴파일 에러는 보통 미연에 방지가 가능 하다. 

 

  • 런타임 에러는 보통 자바 프로그램 실행 시에 나온다. 즉 자바 프로그램이 아닌 개발자가 알 수 있다.
    public class ExceptionEx01 {
    	public static void main(String[] args) {
    		//컴파일 예외처리
    		try {
    			Thread.sleep(1000); //메인 스레드가 1초 후 작동
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		// 런타임 예외
    		int [] nums = {1,2,3};
    		System.out.println(nums[3]);
    		System.out.println("Main 스레드 종료");
    	}
    }​
    현재 이렇게 코드를 짜게 되면 아래와 같은 오류가 뜬다


  • 이런 런타임 오류를 개발자가 직접 처리를 할 수 있다.
    public class ExceptionEx01 {
    	public static void main(String[] args) {
    		//컴파일 예외처리
    		try {
    			Thread.sleep(1000); //메인 스레드가 1초 후 작동
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		// 런타임 예외
    		int [] nums = {1,2,3};
    		try {
    			System.out.println(nums[3]);
    		} catch (ArrayIndexOutOfBoundsException e) {
    			System.out.println("계속해서 진행!");
    		}
    		System.out.println("Main 스레드 종료");
    	}
    }​
  • 여기서 catch 구문에서 2가지의 좋은 예외처리 방법이 있다.
    		int [] nums = {1,2,3};
    		try {
    			System.out.println(nums[3]);
    		} catch (ArrayIndexOutOfBoundsException e) {
    			System.out.println("계속해서 진행!");
    			System.out.println(e.getMessage());
    			e.printStackTrace();
    		}
    		System.out.println("Main 스레드 종료");
            
            
    계속해서 진행!
    Index 3 out of bounds for length 3
    java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
    Main 스레드 종료
    	at ch07.ExceptionEx01.main(ExceptionEx01.java:15)​
    1) e.getMessage() - 어떤 오류가 터졌는지 직접 메세지로 확인 가능 하다
    2) e.printStackTrace() - 어디에 오류가 터졌는지 정확히 볼 수 있다. 

    ※ 나중에 개발을 하게 되면 이러한 오류들을 로그파일로 남겨놔야 확인 및 수정에 도움이 된다.

  • 에러의 종류는 엄청나게 많다. 그래서 하나하나 정확하게 잡아주기에는 어렵다 그럴때 컴파일/런타임 에러를 통합해서 부모의 것을 쓰면 편하다. 결과도 똑같이 나온다. 
    컴파일 = Exception / 런타임 = RuntimeException


 

 

 

05 소켓 통신

  • 소켓 통신에 있어 중요한 개념인 '포트(Port)'를 알고 넘어 가야 한다.
  • 한대의 컴퓨터에 여러 프로그램이 동시에 돌아가게되는데 이때 이 한대의 컴퓨터는 하나의 스트림을 가지고 이 스트림을 통해 외부와 통신을 하게 된다. 이 스트림이 각각의 프로그램에 도달을 하게 되는데 도달하게 될때 포트라는 주소를 가지고 찾게 된다. 
  • 보통 포트는 0~65535번지까지 있고 0~1023 포트는 건드릴 수 없고, 그 이상부터는 정해서 사용 가능 하다.
  • 소켓통신을 다시 정리하자면 양끝단에 각각의 포트를 달고 바이트 스트림을 통해서 데이터를 주고 받는것을 일컫는다.