Box에 넣을 내용물로 content 필드를 선언하려고 할 때 타입은 무엇으로 설정해야 하는가?
public class Box{
public Object content;
}
Box에 들어가는 내용물은 다양하다. 그래서 어떠한 타입도 가능해야 한다면 Object 타입으로 선언한다. 그러면 모든 객체는 부모 타입인 Object로 자동 타입 변환이 되기 때문에 content 필드에는 어떤 객체든 대입이 가능하다.
Box box = new Box();
box.content = 모든 객체;
그런데, Box 안의 내용물을 얻을 때는? 강제 타입 변환을 통해서 내가 얻고자 하는 타입으로 가져와야 한다. 하지만 다운캐스팅은 실패할 수 있다. 이 오류를 방지하기 위해 Box를 생성할 때 저장할 내용물의 타입을 미리 알려주면 Box는 content에 무엇이 대입되고, 읽을 때 어떤 타입으로 제공되는지 확인할 수 있다.
이 개념이 제네릭이다.
결정되지 않은 타입을 파라미터로 처리하고 실제 사용할 때 파라미터를 구체적인 타입으로 대체시키는 기능
public class Box <T>{
public T content;
}
<T>: T가 타입 파라미터임을 뜻하는 기호이다. 타입이 필요한 자리에 T를 사용할 수 있음을 명시한다.
Box클래스는 T가 무엇인지 모르지만, Box 객체가 생성될 시점에 다른 타입으로 대체된다.
Box<Integer> box = new Box<>();
box.content = 100;
int content = box.content; //강제 타입 변환 없이 바로 정수값을 얻을 수 있다.
결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스
제네릭 타입은 선언부에 <> 부호가 붙고, 그 사이에 타입 파라미터들이 위치한다.
public class 클래스명<A,B,...>{...}
public interface 인터페이스명<A,B,...>{...}
타입 파라미터는 일반적으로 변수명의 대문자 알파벳 한글자로 표현한다. 외부에서 제네릭 타입을 사용하려면 타입 파라미터에 구체적인 타입을 지정해야 한다. 만약 지정하지 않으면 Object타입이 암묵적으로 사용된다.
package ch13.sec02.exam01;
//제네릭 타입
public class Product<K, M> { // 타입 파라미터로 K와 M 정의
//타입 파라미터를 필드 타입으로 사용
private K kind;
private M model;
//타입 파라미터를 리턴 타입과 매개 변수 타입으로 사용
public K getKind() {
return this.kind;
}
public M getModel() {
return this.model;
}
public void setKind(K kind) {
this.kind = kind;
}
public void setModel(M model) {
this.model = model;
}
}
Product 클래스는 다양한 종류(kind)와 모델(model)의 제품 정보를 저장하기 위해, 타입 파라미터 K와 M을 각각 필드, Getter, Setter의 타입으로 사용한다.
Product 클래스 내에서 K,M에 대해서 어떤 연산을 사용하고, method를 사용할 수 있는가?
=(대입) 연산자만 가능하다.public interface Rentable<P>{ //타입 파라미터 P 정의
P rent();
}
public class HomeAgency implements Retable<Home>
타입 파라미터 P를 Home으로 대체한다. 재정의하는 method rent의 리턴타입은 반드시 Home이어야 한다.
타입 파라미터 P의 실제 타입은 인터페이스를 구현할 때 결정된다.
타입 파라미터를 가지고 있는 method
타입 파라미터가 method 선언부에서 정의 된다. 한개의 method에서만 쓰이는 제네릭, method의 매개변수, 지역변수, 리턴타입에서만 쓰인다.
public <A,B,...> 리턴타입 method명(매개변수, ...){...}
public <T> Box<T> boxing(T t){...}
타입 파라미터로 <T>를 정의하고 매개변수 타입과 리턴 타입에서 T를 사용한다. 정확한 리턴타입은 T를 내용물로 갖는 Box 객체이다.
타입 T는 해당 클래스 내에서만 살아있게 된다.
Box<Integer> box1 = boxing(100);
Box<String> box2 = boxing("Hello world");
Integer면 정수타입 형태로 변환해서 리턴된다.
모든 타입으로 대체할 수 없고, 특정 타입과 자식 또는 구현 관계에 있는 타입만 대체할 수 있는 타입 파라미터
경우에 따라서는 타입 파라미터를 대체하는 구체적인 타입을 제한할 필요가 있다.
public <T extends 상위타입> 리턴타입 method(매개변수,...){...}
상위타입을 선언하면서 해당 타입의 자식 클래스까지 모두 허용하게 된다. 이를 통해 부모 클래스의 method를 호출 가능하게 된다.
public <T extends Number> boolean compare(T t1,T t2){...}
//main method
compare(10,20);
compare(4.5,4.5);
각 타입을 자동으로 Integer, Double 타입으로 대체하여 반환한다.
이를 통해서 타입 안정성 확보와 제네릭 타입에서도 다형성 활용이 가능하다.
Object.class(정적 접근)
Class<String> clazz = String.class;
컴파일 타입에 이미 클래스가 존재하고 로드돼있어야 한다. 정적 접근 방식이다. .class를 사용하면(Integer.class) 클래스 객체를 직접 얻을 수 있다.
getClass()(인스턴스를 통해 얻기)
String str = "Hello";
Class<?> clazz = str.getClass();
이미 인스턴스가 존재할 때, 해당 인스턴스로부터 클래스 정보를 얻을 수 있다. 런타임 시점에서 실제 객체의 타입 정보를 확인할 수 있다.
Class.forName(”패키지명.클래스명”)
Class<?> clazz = Class.forName("java.lang.String");
클래스 이름을 문자열로 받아 해당 클래스를 동적으로 로드한다. 예외처리를 필수적으로 해줘야 한다.
와일드카드 타입 파라미터를 직접 사용하는 경우는 많지 않지만 라이브러리 도큐먼트를 볼 때 이해할줄 알아야 한다.
Student와 해당 클래스의 자식클래스를 타입 파라미터의 대체 타입으로 사용 가능하다.
리턴타입 method명(제네릭타입<? extends Student> 변수){...}
해당 클래스와 부모 클래스만 사용 가능하도록 매개변수를 다음과 같이 선언할 수 있다.
리턴타입 method명(제네릭타입<? super Worker> 변수){...}
어떤 타입이든 가능하도록 매개변수를 선언할 수도 있다.
리턴타입 method명(제네릭타입<?> 변수){...}