//source
class Container<E> {
private E element;
public E getElement() {
return element;
}
}
public static void main(String[] args) {
Container<Integer> container = new Container<Integer>();
Integer integer = container.getElement();
}
//runtime
class Container {
private Object element;
public Object getElement() {
return element;
}
}
public static void main(String[] args) {
Container container = new Container();
Integer integer = (Integer) container.getElement();
}
JDK1.5 이전까지 자바에서 자료구조를 저장할 때 Object Type으로 저장했고,
그렇기 때문에 사용 시 각 타입으로 캐스팅하여 사용해야 했다.
List list = new ArrayList();
list.add(new MyObject());
MyObject myObject = (MyObject) list.get(0);
myObject.printName(); //name 출력
이런 런타임 시 사용자가 지정한 캐스팅의 단점은 컴파일 시 오류를 알 수 없다는 것이다.
다음 코드는 문제 없이 컴파일되지만 런타임에 예외가 발생한다.
List list = new ArrayList();
list.add(new MyObject());
list.add(new Integer(1));
MyObject myObject = (MyObject) list.get(0);
MyObject intObject = (MyObject) list.get(1); // ClassCastException 발생
하지만 제네릭을 이용한다면 클래스에서 사용할 데이터 타입을 지정할 수 있다.
지정한 타입과 다른 타입을 사용하면 컴파일 시 오류가 발생해 좀 더 빠르게 대응이 가능하다.
제네릭 클래스, 인터페이스에서 사용되는 파라미터
public class MyGenericClass<T>{...} // T가 제네릭 타입
public class MyGenericClassWithMyTypeName<MyTypeName123>{...}
특수문자를 제외한 어떤 문자가 들어가도 되지만 대표적으로 사용되는 몇 가지 이름이 있다.
: Element. (Collection의 Element를 표현할 때 사용됨)
: Type
: Key
: Value
: Number
, , : more Types
제네릭 타입 파라미터의 범위는 기본적으로 Unbounded(모든 참조 타입 사용 가능)이다.
다음 방법으로 제네릭 타입의 범위를 지정할 수 있다.
Upper Bounded Type Parameter
제네릭 타입의 범위를 클래스의 서브 타입으로 제한
Class<[TypeParameter] extends [SuperClass]>
(인터페이스 및 추상클래스도 extends로 사용)
public class UpperBoundedList<T extends Number>{...}
이와 같이 사용하면 제네릭 타입이 상위 타입을 상속하거나 구현한 타입임을 명시할 수 있으며 Number의 제네릭 타입에서 상속한 타입의 메서드를 사용할 수 있다.
public class MyObject<T extends List>{
private T myObject;
public T get(){
return myObject.stream()
.collect(Collectors.toList());
}
}
제네릭 타입을 매개변수나 리턴타입, 필드 등으로 지정할 때 타입 파라미터를 제한할 목적으로 사용된다.
제네릭 클래스의 타입 파라미터 이외의 타입을 인자로 받고, 반환할 때 사용할 수 있다.
와일드카드는 제네릭 클래스의 형식, 제네릭 클래스 인스턴스 생성, 슈퍼타입으로 사용될 수 없다.
public boolean containsAll(Collection<?> collection){}
와일드카드도 기본적으로 Unbounded이다.
(List<?> -> List<? extends Object>)
제네릭 타입 파라미터처럼 UpperBound를 제한할 수 있고,
추가적으로 LowerBound 설정도 가능하다.
Lower Bounded Type Parameter
제네릭 타입의 범위를 지정한 클래스의 상위 타입으로 제한
Class<[TypeParameter] super [SubClass]>
public void removeAll(List<? super Integer>){...}
와일드카드는 정확한 데이터 타입을 알고, 조작하기보다는 행위에 초점을 두고 사용한다.
정확한 타입이 필요한 곳에서 와일드카드를 사용한다면 캡처 컴파일 에러가 발생한다.
public class MyObject<T>{
private List<T> myList = new ArrayList<>();
public void addAll(List<?> wildCardList){
this.myList.add(wildCardList.get(0));// 에러 발생
}
...
}