제네릭은 자바 5부터 나왔는데 이전에는 컬렉션에서 객체 꺼낼 때 마다 형변환을 해야했다. 그래서 런타임 형변환 오류 나곤 했다. 반면 제네릭은 컬렉션이 담을 수 있는 타입을 컴파일러에 알려주게됨. 그래서 컴파일러가 알아서 형변환 코드 추가, 엉뚱타입 객체 넣으려는 시도를 컴파일 과정에서 차단.
List<E>
같이 클래스와 인터페이스 선언에 타입 매개변수가 쓰이면 제네릭 클래스/제네릭 인터페이스임.
그런 걸 로 타입(실제 매개변수 타입 지정 안하고)으로 쓰지 말라. 제네릭의 이점인 안정성, 표현력 모두 잃는다.
컴파일에서 오류 발견 못하고 한참 뒤 런타임에 알아채게 된다.
타입 지정해서 타입 안정성 확보하라. 만약에 Object로 지정한다면, 그것은 컴파일러에게 모든 타입을 허용한다는 것을 정확히 전달한 것이라서 로 타입과 다르다.
원소 타입 몰라도 되는 걸 작성하고 싶으면 비한정적 와일드카드 타입unbounded wildcard type을 써라.
List<?>
로타입과 달리 안전하다. 로 타입에는 아무 원소나 넣을 수 있지만 ?에는 null외에 어떤 타입도 넣을 수 없다.
소소한 예외
: class 리터럴에는 로 타입 , instanceof연산자에는 로 타입
예)List.class -> OK
List<String>.class -> NG
if(o instanceof Set) { -> OK
Set<?> s = (Set<?>) o;
}
제네릭 쓰면 컴파일러 경고 많이 뜰 것이다. 타입 안전 확신하면 @SuppressWarnings("unchecked") 달라.
근데 왜 안전한지 이유를 항상 주석으로 남겨라.
배열과 제네릭의 중요한 차이 두 가지
그래서 배열 쓰면 런타임 오류가 날 걸 리스트 쓰면 컴파일때 알 수 있다.
제네릭 배열은 생성 안되게 막혀있다. 타입 안전하지 않기 때문. (비한정적 와일드 카드 타입은 만들 수 있다)
클래스를 제네릭으로 만들 때 만약에 배열 사용하려면 발목을 잡네.
두 가지 해결책(예시는 배열로 stack 클래스 만든 것임)
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
위에
Object[] elements = new Object[DEFAULT_INITIAL_CAPACITY];
...
public E pop() {
if (size == 0) new EmptyStackException();
@SupressWarnings("unchecked") E result = (E) elements[--size];
elements[size] = null;
return result;
}
클래스처럼 메서드도 제네릭으로 만들 수 있다.
예)
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
} -> 로 타입 사용 NG
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
} -> 굿 굿
Collection<? super E> collection
유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개 변수에 와일드 카드 타입을 사용하라.
반환 타입에는 한정적 와일드 카드 타입을 사용하면 안 된다. 유연성 높이는게 아니라 클라이언트 코드에서도 와일드 카드 타입을 써야하기 때문.
varargs 매개변수에 제네릭이나 매개변수화 타입이 포함되면 힙 폴루션 경고 난다.