로 타입은 사용하지 말라
로 타입을 쓰게 되면 컴파일 타임에 오류를 알기 힘들다. 문법적으로 막아놓지 않았지만 절대로 쓰면 안된다. 로 타입을 쓰면 제네릭이 안겨주는 안전성과 표현력을 모두 잃게 된다. 로 타입은 안되지만 List<Object>처럼 임의 객체를 허용하는 매개변수화 타입은 괜찮다. List에 List<String>을 넘길 수 있지만 List<Object>는 안된다. List<String>은 List의 하위타입이지만, List<Object>는 아니기 때문이다. 그렇다면 제네릭 타입을 쓰고 싶지만 매개변수 타입을 신경쓰고 싶지 않다면 어떻게 해야 할까? class<?>를 사용하는 것이다. Collection<?>에는 null이외에는 어떤 원소도 넣을 수 없다.
하지만, class 리터럴에는 로 타입을 사용해야 한다. List<String>.class는 허용되지 않는다. 또, instanceof에는 로타입을 사용해도 된다.
용어 정리
한글 용어 | 영문 용어 | 예 | 아이템 |
매개변수화 타입 | parameterized type | List<String> | 아이템 26 |
실제 타입 매개변수 | actual type parameter | String | 아이템 26 |
제네릭 타입 | generic type | List<E> | 아이템 26, 29 |
정규 타입 매개변수 | formal type parameter | E | 아이템 26 |
비한정적 와일드카드 타입 | unbounded wildcard type | List<?> | 아이템 26 |
로 타입 | raw type | List | 아이템 26 |
한정적 타입 매개변수 | bounded type parameter | <T extends Number> | 아이템 29 |
재귀적 타입 한정 | recursice type type | List<T extends Comparable<T>> | 아이템 30 |
한정적 와일드카드 타입 | bounded wildcard type | List<? extends Number> | 아이템 31 |
제네릭 메서드 | generic method | static <E> List<E> asList(E[] a) | 아이템 30 |
타입 토큰 | type token | String.class | 아이템 33 |
비검사 경고를 제거하라
제네릭을 사용하게 되면 수많은 컴파일러 결과를 보게 된다. 비검사변환경고, 비검사 메서드 호출 경고, 비검새 매개변수화 가변수이 타입 경고, 비검사 경고 등이 있다.
// unchecked error가 나온다
Set<Lark> exaltation = new HashSet();
위 코드에서는 빌드하면 컴파일에서 set<Lark>가 필요하다고 나온다.
Set<Lark> exaltation = new HashSet<>();
처리하기 어려운 경고도 있지만 가능한 한 모든 비검사 경고를 제거하라. 런타임에 ClassCastException이 발생할 일이 없고, 여러분이 의도한 대로 잘 동작하리라 확신할 수 있다.
경고를 제거할 수는 없지만 타입 안전하다고 확신 할 수 있다면 @SuppressWarnings("unchecked") 어노테이션을 달아 경고를 숨기자. 경고만 숨기는 것이고 실제로 런타임 에러는 나올 수 있으니 조심해야 한다. 또, 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야 한다.
배열보다는 리스트를 사용하라.
배열과 제네릭에 차이점은 2가지가 있다.
첫 번째, 배열은 공변이다. Sub가 Super의 하위타입이라면 Sub[]도 Super[]의 하위타입이 된다. 따라서 Super가 변하면 Sub도 변하게 된다. 반면, 제네릭은 불공변이다. List<Sub>과 List<Super>서로에게 하위타입도 상위타입도 아니다.
Object[] objectArray = new Long[1];
objectArray[0] = "타입일 달라 넣을 수 없다";
아래 코드에서 objectArray에 String타입을 넣으면 런타임에서야 알 수 있다.(작성하는 시점에서는 런타임에러 였던 것으로 보이나 현재는 컴파일 타임에 에러를 알 수 있다.)
두 번째, 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 하지만 제네릭은 타입정보가 런타임에는 소거된다. 그래서 배열과 제네릭은 잘 어울리지 못한다. 즉, 코드를 제네릭 타입(new List<E>[]), 매개변수화 타입(new List<String>[]), 타입 매개변수(new E[])로 사용 할 수 없다. E, List<E>, List<String>같은 타입을 실체화 불가 타입이라한다. 실체화 되지 않아서 런타임에는 컴파일 타임보다 타입 정보를 적게 가지는 타입이다. 소거 메커니즘 때문에 매개변수화 타입 가운데 실체화 될 수 있는 타입은 List<?>와 Map<?,?> 같은 비한정적 와일드카드 타입뿐이다.
제네릭 적용해보기.
public class Chooser {
private final Object[] choiceArray;
public Chooser(Collection choices) {
choiceArray = choices.toArray();
}
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
T[] 을 사용한다면 타입이 무엇인지 알수 없으니 안전을 보장 할 수 없다는 경고 에러가 뜬다.
public class Chooser<T> {
private final T[] choiceArray;
public Chooser(Collection choices) {
choiceArray = (T[]) choices.toArray();
}
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
제네릭으로 바꿔주면 안전성이 높이지고 오류나 경고없이 컴파일이 된다.
public class Chooser<T> {
private final List<T> choiceArray;
public Chooser(Collection choices) {
choiceArray = new ArrayList<>(choices);
}
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray.get(rnd.nextInt(choiceArray.size()));
}
}
'Language > Effective java' 카테고리의 다른 글
제네릭 (0) | 2022.06.26 |
---|---|
제네릭 (0) | 2022.06.19 |
클래스와 인터페이스 (0) | 2022.06.17 |
클래스와 인터페이스 (0) | 2022.06.17 |
클래스와 인터페이스 (0) | 2022.06.14 |
댓글