본문 바로가기
Language/Effective java

제네릭

by y.j 2022. 6. 18.
728x90

로 타입은 사용하지 말라

로 타입을 쓰게 되면 컴파일 타임에 오류를 알기 힘들다. 문법적으로 막아놓지 않았지만 절대로 쓰면 안된다. 로 타입을 쓰면 제네릭이 안겨주는 안전성과 표현력을 모두 잃게 된다. 로 타입은 안되지만 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()));
    }
}

 

728x90

'Language > Effective java' 카테고리의 다른 글

제네릭  (0) 2022.06.26
제네릭  (0) 2022.06.19
클래스와 인터페이스  (0) 2022.06.17
클래스와 인터페이스  (0) 2022.06.17
클래스와 인터페이스  (0) 2022.06.14

댓글