반응형

ITEM 26 "로 타입은 사용하지 말라"

 

같은 기능을 하는 코드이지만 자료형에 따라 오버로딩 함수를 여러 개 만들어야 한다고 생각해보자.

자연스레 타입에 상관없이 제네릭하게 코드를 구성하고 싶을 것이다. 자바에서도 타입을 매개변수로 하여 클래스와 인터페이스를 작성할 수 있으며 제네릭 클래스 혹은 제네릭 인터페이스라고 한다. 두 정의를 통틀어 제네릭 타입이라 한다.

 

꺽쇠괄호 안에 실제 타입 매개변수들을 나열해서 매개변수화 타입을 정의한다. 예를 들어 List<String> 과 같은 식이다.

이 제네릭 타입을 정의하면 로 타입도 함께 정의되는데 이 로 타입을 주의해야 한다. List<String> 의 로 타입은 List 이다. 선언 시에 타입에 대한 정보가 지워진 것처럼 동작하는데 하위 호환성을 위해 자바 9 까지 계속 허용하고 있다.

 

public class EffectiveJavaTest {
    static class Stamp {
        public Stamp () {}
        public void cancel() {}
    }
    static class Coin {
        public Coin () {}
    }
    private final static Collection stamps = new ArrayList<>();

    public static void main(String[] args) {
        stamps.add(new Coin());
        for (Iterator i = stamps.iterator(); i.hasNext();) {
            Stamp stamp = (Stamp) i.next();
            stamp.cancel();
        }
    }
}

 

선언 시에 타입에 대한 정보가 없으니 아무런 원소를 넣어도 컴파일 오류가 발생하지 않는다. 위 코드처럼 반복자로 원소를 비교하거나 꺼낼 때 런타임 오류가 발생한다. 원소 추가는 Coin 클래스로, 원소 활용은 Stamp 클래스로 할 수 있는 것을 볼 수 있다. 항상 느끼지만 자유도가 높으면 프로그래머의 실수를 유발하는 것 같다.

 

Collection<Stamp> 로 타입을 지정해준다면, 컴파일 타임 때 오류를 발견할 수 있다. 런타임 오류의 경우, 문제 발생 코드와 원인을 제공하는 코드가 물리적으로 상당히 떨어지면 어디서 오류가 발생했는지 모든 소스코드를 다 뒤져야 할 수도 있다. 그런데 컴파일 타임 때 오류를 발생하면 타입 안전하게 코드를 작성할 수 있다.

 

정말 로 타입을 쓰고 싶다면, List<Object> 처럼 임의의 객체를 허용하는 매개변수화 타입을 권장하고 있다.

매개변수로 List 를 받는 메서드에 List<String> 을 넘길 수 있지만, List<Object> 를 받는 메서드에는 넘길 수 없다.

 

더 나아가, 원소의 타입을 몰라도 되는 로 타입을 쓰고 싶을 때 <?> 비한정적 와일드카드를 쓸 것을 권장하고 있다.

<?> 는 < ? extends Object > 의 줄임 표현으로 어떤 자료형의 객체도 매개변수로 받겠다는 의미이다. 로 타입하고 같아보이지만, 처음에 넣은 타입 외에는 다른 원소를 넣을 수 없다는 특징이 있다.

 

public class EffectiveJavaTest {
    private static int test(Set<?> s1, Set<?> s2) {
        s1.add("test");
    }
    public static void main(String[] args) {
        Set<Integer> s1 = new HashSet<>();
        s1.add(Integer.valueOf(10));
        Set<Integer> s2 = new HashSet<>();
        s2.add(Integer.valueOf(20));
        test(s1, s2);
    }
}

 

< ? extends Object > 라고 많은 블로그에서 정의하고 있지만 사실 상 <? extends 입력된 첫번째 자료형> 이지 않을까 싶다. 그대로 컴파일하면 아래와 같은 에러를 내뿜는다. Integer 용 Set 에 String 을 add 하기 때문에 타입 컨벤션 오류가 발생한 것이다.

 

error: incompatible types: String cannot be converted to CAP#1
        s1.add("test");
               ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?

 

컴파일 단계 때, 타입 검사를 할 수 있어 로 타입보다 더 좋아보이지만 extends 와 super 키워드 없이 (상한제한자, 하한제한자) 단독으로 사용하지 않을 것을 권장하고 있다. 자세한 내용은 아래 링크를 참조하는 것을 추천한다. 

사용법 - Java에서 클래스 <?>은 무엇을 의미합니까? - 스택 오버플로 (stackoverflow.com)

 

그렇다고 로 타입을 아예 안 쓰지는 않는다. 로 타입을 써야되는 경우는 두 가지이다.

class 리터럴에는 로 타입을 써야 한다. 어떤 클래스를 표현하고자 할 때 xxx.class 로 표기하는데 여기에 제네릭 타입을 쓰면 안 된다. 두 번째는 instanceof 연산자이다. instanceof 연산자의 비연산자도 굳이 제네릭 타입을 쓸 필요가 없다.

 

"로 타입은 지양하자."

반응형

+ Recent posts