🏠 로 타입은 사용하지 말자.
제네릭 클래스란 클래스 선언에 타입 매개변수가 사용된 클래스이다.
각각의 제네릭 타입은 일련의 매개변수화 타입을 정의한다. 클래스 이름이 나온 후, 꺽쇠괄호 안에 실제 타입 매개변수들을 나열한다.
List<String> 은 원소의 타입이 String 인 리스트를 뜻하는 매개변수화 타입이다.
여기서 String이 정규타입 매개변수 E에 해당하는 실제 타입 매개변수이다.
제네릭 타입을 하나 정의하면 그에 해당하는 로 타입도 함께 정의된다.
로 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 정의한다.
List<String> 에서 타입 매개변수 String 을 사용하지 않는 List 가 바로 로 타입이다.
로 타입은 타입 선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작한다.
로 타입을 사용하면 디버깅하기 어려울 수 있다.
//Stamp 인스턴스만 취급한다.
private final Collection stamps = ...;
stamps.add(new Coin(...));
위 예제에서 stamps 에는 Stamp 인스턴스만 취급하지만, 해당 코드에서는 Stamp 대신 Coin 을 넣어도 아무런 오류 없이 컴파일되고 실행된다. 컬렉션에서 이 동전을 다시 꺼내 형변환하기 전까지는 오류를 알아채지 못한다.
오류는 가능한 컴파일 할 때 발견하는 것이 좋다.
제네릭을 활용하면 이를 해결할 수 있다.
private final Collection<Stamp> stamps = ...;
stamps.add(new Coin()); -> error
stamps 에는 Stamp 의 인스턴스만 넣어야함을 컴파일러가 인지하게 된다.
컴파일러는 컬렉션에서 원소를 꺼내는 모든 곳에 보이지 않는 형변환을 추가하여 절대 실패하지 않음을 보장한다.
로 타입을 쓰면 제네릭이 안겨주는 안정성과 표현력을 모두 잃게 된다.
그저 로 타입은 호환성 때문에 만들어둔 것이다.
제네릭 없이 짜여졌던 이전 코드들을 수용해서 맞물려 돌아가게 해야만 했던 장치일 뿐이다.
List 같은 로 타입은 사용해서는 안되지만, List<Object> 처럼 임의 객체를 하용하는 매개변수화 타입은 괜찮다.
List<Object> 는 모든 타입을 허용한다는 의미를 컴파일러에 명확히 전달한 것이다.
List 에는 List<String> 을 넘길 수 있지만 List<Object> 에는 List<String> 을 넘길 수 없다.
String 은 Object 의 하위 객체이지만, List<String> 이 List<Object> 의 하위 객체가 아니기 때문이다.
때문에 List<Object> 같은 매개변수화 타입을 사용할 때와는 달리 List 로 타입을 사용하면 타입 안정성을 잃게 된다.
public static void main() {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0);
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
해당 코드는 컴파일 되지만 List 로 타입을 사용하여 경고가 발생한다.
strings.get(0) 결과를 형 변환하려할 때 ClssCastException 이 발생한다.
Integer 값을 String 으로 변환하려 시도했기 때문이다.
이 형변환은 보통 컴파일러가 자동으로 만들었준 것이기에 실패하지 않지만, 경고를 무시했기에 대가가 발생한 것이다.
public static void main() {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0);
}
private static void unsafeAdd(List<Object> list, Object o) {
list.add(o);
}
List<Object> 코드로 바꾸면, 해당 코드는 컴파일조차 실행되지 않는다.
🔥 제네릭 타입을 사용하고 싶지만, 실제 타입 매개변수가 무엇인지 신경쓰고 싶지 않을 땐 ?을 사용하자.
제네릭 타입인 Set<E> 의 비한정적 와일드카드 타입은 Set<?>이다.
어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 Set타입인 것이다.
로 타입과 비한정적 와일드카드 타입의 큰 차이점은 안정성이다.
로 타입은 아무 원소나 넣을 수 있기 때문에 타입 불변식을 훼손하기 쉽다.
반면 비한정적 와일드카드에는 어떤 원소도 넣을 수 없다. 넣게되면 컴파일 에러가 발생한다.
🔥 제레닉 로 타입 사용 예외 규칙
1. class 리터럴에는 로 타입을 써야한다.
class 리터럴에 매개변수화 타입을 자바는 금지했기 때문에 List.class, String[].class 와 같이 사용해야한다.
2. instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.
런타임에는 제네릭 타입 정보가 지워지기 때문이다. 그리고 instanceof 연산자에서는 비한정적 와일드카드와 로 타입이 완전히 똑같이 동작하기 때문에 가독성을 위해 로 타입으로 적용하는게 좋다.
'개인 공부 > 스터디' 카테고리의 다른 글
[이펙티브 자바] 열거(enum) 타입 (0) | 2023.07.04 |
---|---|
[이펙티브 자바] 제네릭 메서드 작성법 (0) | 2023.06.26 |
[이펙티브 자바] 인터페이스의 용도 (0) | 2023.06.20 |
[이펙티브 자바] equals 재정의 (0) | 2023.06.13 |
[이펙티브 자바] 상속보다는 컴포지션을 사용하라 (1) | 2023.06.13 |