public class Person<T> { }
public interface Animal<T> { }
이에 대하여 타입 파라미터를 사용하지 않을 경우와 비교하며 알아보자
1) 타입파라미터를 사용하지 않을 경우
모든 자바 클래스의 최상위 조상 클래스인 Object 타입으로 필드를 선언해, 다양한 타입의 객체를 set하고 get 하는 클래스가 있다.
public class Person {
private Object object;
public void set(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
이를 인스턴스로 생성하여 다음과 같이 String 타입으로 저장한다고 하자.
자바는 자식 타입이 부모 타입으로 자동 타입 변환이 가능하므로 set메소드를 실행하면 Object 필드에 "My name is"가 저장될 것이다.
이후 get() 메소드로 저장된 객체를 가져올 때, 필드에 저장된 원래 타입으로 객체를 얻으려면 강제 타입 변환을 해야한다.
Person person = new Person();
person.set("My name is");
String str = (String)person.get(); //casting
이러한 타입 변환이 빈번해지면 전체 프로그램 성능에 좋지 않다.
따라서 이를 개선할 수 있는 방법이 제네릭이다.
2) 타입파라미터를 사용(제네릭 사용)
위의 Person 클래스를 제네릭을 사용한다면 다음과 같이 수정될 것이다.
public class Person<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
그리고 이후 String이라는 구체적인 타입으로 객체를 생성하면
Person<String> person = new Person<String>();
타입 파라미터 T가 String 타입으로 변경돼 자동으로 클래스 내부가 재구성된다.
public class Person<String> {
private String t;
public void set(String t) {
this.t = t;
}
public String get() {
return t;
}
}
따라서 set() 이후 get()으로 저장된 필드를 읽어올 때 타입 변환이 필요없다.
Person<String> person = new Person<String>();
person.set("My name is");
String str = person.get(); //casting 필요 없음.
제네릭 타입은 두 개 이상의 멀티 타입 파라미터를 사용할 수 있다.
public class Person<T, M> { }
public interface Animal<T, M, K> { }
Person<String, Int> person = new Person<String, Int>(); //~Java 6
Person<String, Int> person = new Person<>(); //Java 7~
public <T> Person<T> naming(T t) { }
//첫번째 방법 : 제네릭메소드의리턴타입 변수명 = <구체적인타입> 제네릭메소드명(파라미터)
Person<String> resultMethod = <String>naming("My Name is..");
//두번째 방법 : 제네릭메소드의리턴타입 변수명 = 제네릭메소드명(파라미터)
Person<String> resultMethod = naming("My Name is...");
public <T extends Number> int ageCompare(T t1, T t2) {
}
위와 같이 선언할 경우 메소드의 파라미터로 상위타입 또는 상위타입의 하위 클래스 타입의 인스턴스만 가질 수 있다. 상위 타입은 클래스와 인터페이스 모두 가능하다.
즉, Number 타입으로 상위 타입을 선언했을 경우, 메소드 사용 시 t1/t2에 Number, Byte, Short, Integer, Long, Double의 인스턴스를 사용할 수 있다. 그리고 메소드 중괄호 블럭{} 안에서 t1, t2가 사용할 수 있는 메소드로는 Number의 메소드인 doubleValue()가 있을 것이다.
public <T extends Number> int ageCompare(T t1, T t2) {
double v1 = t1.doubleValue(); //Number의 doubleValue() 메소드
double v2 = t2.doubleValue(); //Number의 doubleValue() 메소드
if(v1 > v2) {
return 1;
} else {
return -1;
}
}
1. Unbounded Wildcards(제한 없음)
제네릭타입 <?>
2. Upper Bounded Wildcards(상위 클래스 제한)
제네릭타입 <? extends 상위타입>
3. Lower Bounded Wildcards(하위 클래스 제한)
제네릭타입 <? super 하위타입>
1) 상속
제네릭 타입도 부모 클래스가 될 수 있고, 자식 제네릭 타입은 추가적으로 타입 파라미터를 가질 수 있다.
public class ChildAnimal<T, M, C> extends Animal<T, M> { }
2) 구현
제네릭 인터페이스를 구현한 클래스도 제네릭 타입이 된다.
public interface Service<T> { }
public class ServiceImpl<T> implements Service<T> { }