class Box<T> { }
로 타입은 타입 매개변수를 사용하지 않은 경우인데, 제네릭이 도입되기 전 코드와 호환되기 위해 있따.
자바 9에서도 여전히 동작하지만 좋은 예가 아니다.
// Stamp 인스턴스만 취급할 거다.
private final Collection stamps = ...;
// 실수로 동전 넣는다.
stamps.add(new Coin(...)); // 'unchecked call' 경고를 뱉지만 실행이 된다.
제네릭을 활용하면 타입 선언 자체에서 알 수 있다.
private final Collection<Stamp> stamps = ... ;
타입 안전성이 확보되어서 컴파일 시에 다른 클래스를 넣으려하면 오류가 발생한다.
로 타입을 쓰면 제네릭이 주는 안전성과 표현력을 잃는다. 쓰지말자.
그럼 왜 만들어 놨을까? 호환성 때문이다. 제네릭이 자바 1.5에 나오면서 이전 코드와 호환을 해야하기 때문이다. 그래서 로 타입을 지원하고 제네릭 구현에는 소거(아이템 28)을 사용하기로 했다.
List
와 같은 로 타입은 안되나, List<Object>
와 같이 임의 객체를 허용하는 매개변수화 타입은 괜찮다.
List
는 제네릭 타입에서 완전히 발을 뺀 것이고, List<Object>
는 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달한 것이다.
매개변수로 List
를 받는 메서드에 List<String>
은 넘길 수 있지만, List<Object>
는 넘길 수 없다. 제네릭의 하위 타입 규칙 때문인데 List<String>
은 로 타입인 List
의 하위 타입이지만, List<Object>
의 하위 타입은 아니다 (불공변)
그래서 List<Object>
같은 매개변수화 타입을 사용할 때와 달리 List 같은 로 타입을 사용하면 타입 안정성을 잃게 된다.
제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 물음표(?)를 사용하자.
예컨데 제네릭 타입인 Set<E>
의 비한정적 와일드카드 타입은 Set<?>
이다. 이것은 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 Set 타입이다.
static int numElementsInCommon(Set<?> s1, Set<?> s2) {...}
와일드카드 타입인 Set<?>
와 로 타입인 Set의 차이는, 와일드카드 타입은 안전하고 로 타입은 안전하지 않다는 것이다. 로타입 컬렉션에는 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉽다. 반면, Collection<?>에는 (null 외에는) 어떤 원소도 넣을 수 없다. 다른 원소를 넣을려하면 컴파일 할 때 다음의 오류 메시지를 보게된다.
보충 설명
public class test {
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5);
printList(list);
List<String> listString = List.of("a", "b", "c");
printList(listString);
List<?> wildCardList1 = List.of("1", "2", "3");
printList(wildCardList1);
List<?> wildCardList2 = new ArrayList<>();
wildCardList2.add(null);
wildCardList2.add(1); // 오류 남
printList(wildCardList2);
}
public static void printList(List<?> list) {
for (Object o : list) {
System.out.println(o + " ");
}
}
}
비한정적 와일드카드를 쓰는 파라미터에는 문제가 없지만, 비한정적 와일드카드로 선언한 Collection에 add를 하게되면 에러 발생.
Object와 비한정적 와일드카드(
?
)의 차이점.
Object는 형변환, 검사가 매번 필요하고 ?는 컴파일타임에 타입을 지정해준다.
컴파일러는 제 역할을 한 것이다. 즉 컬렉션의 타입 불변식을 훼손하지 못하게 막았다. 구체적으로는 (null외의) 어떤 원소도 Collection<?>
에 넣지 못하게 했으며 컬렉션에서 꺼낼 수 있는 객체의 타입도 알 수 없게 했다. 만약 이 제약을 받아들일 수 없다면 제네릭 메서드(아이템 30)나 한정적 와일드 카드(아이템 31)를 쓰면 된다.
Object와 비한정적 와일드카드(
?
)의 차이점.
https://snoop-study.tistory.com/113
로 타입을 쓰지 말라는 규칙에도 소소한 예외가 있는데, class 리터럴에는 로 타입을 써야 한다.
자바 명세는 class 리터럴에 매개변수화 타입을 사용하지 못하겠다.
두 번째 예외는 instanceof 연산자와 관련이 있다. 런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다. 다음은 제네릭타입에 instanceof를 사용하는 올바른 예다.
if (o instanceof Set) { // 로 타입
Set<?> s = (Set<?>) o; // 와일드카드 타입
}