자바 제네릭의 와일드카드를 사용하는 방법과 제네릭 타입의 상위/하위타입에 관해 설명합니다.

지금까지 제네릭 타입제네릭 메소드를 선언하고 사용하는 방법에 대해 알아보았습니다. 하지만 제네릭은 생각처럼 만만한 개념이 아닙니다. 제네릭을 조금 더 깊이 이해하기 위해서는 주의해야 할 것들과 꼭 알아야 할 것들이 많습니다.

지금부터 제네릭 클래스의 상위/하위 타입에 대해서 알아보도록 하겠습니다.

서브 타입 일랑가 아닐랑가

String 타입의 값을 상위 타입인 Object 타입 변수에 넣을 수 있을까요?

String string = "yaho";
Object object = string;

당연히 넣을 수 있습니다. 그렇다면 이건 어떤가요?

String 타입 리스트 변수를 Object 타입 리스트 변수에 할당하는 구문입니다.

List<String> stringList = new ArrayList<>();
List<Object> objectList = stringList;

만약 이게 가능하다면 어떤 일이 일어날까요?

먼저 objectListInteger 123을 넣어봅니다. Object이니까 당연히 가능하겠지요.

List<String> stringList = new ArrayList<>();
List<Object> objectList = stringList;

objectList.add(123); // 추가된 구문

그러고 나서 stringList에서 방금 넣은 첫 번째 값을 가져옵니다. 이게 왜 가능하냐면 objectList = stringList 구분에서 서로 같은 참조 값을 가지기 때문에 stringList로도 objectList로 넣은 값을 가져올 수 있겠지요. 그리고 그걸 String 타입 변수에 넣어 봅니다.

List<String> stringList = new ArrayList<>();
List<Object> objectList = stringList;
objectList.add(123);

String s = stringList.get(0);  // 추가된 구문

어라? 우리의 전제 List<Object> objectList = stringList; 가 수행이 된다면, String 타입 변수에 Integer 타입 값을 넣는 게 가능해지는군요.

이건 말이 안 되지요? 그래서 List<Object> 타입 변수에 List<String> 타입 변수를 대입할 수 없다는 결론을 낼 수 있습니다.

다시 말하면 List<String> 변수의 상위 타입이 List<Object>가 아니라는 것이지요!

타입 파라미터 간에는 상/하위 관계가 없고 raw-type 간에만 상/하위 관계가 존재합니다.

이와 유사하게 컬렉션에 들어 있는 것들을 하나씩 꺼내서 출력하는 코드를 보겠습니다. 나름 재사용성에 신경 쓴다고 컬렉션의 어떤 타입이든 받아서 출력할 수 있도록 메소드 인자를 Collection<Object> 타입으로 한 메소드 printCollection을 만들었습니다.

static void printCollection(Collection<Object> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

public static void main(String[] args) {
    Collection<String> c = new ArrayList<>();
    c.add("hi");
    printCollection(c);
}

그러나… 이 메소드가 의도대로 동작할까요? 컴파일조차 안 됩니다. 왜요?

조금 전에 List<Object>List<String>상위타입이 아니라는 것과 같은 이유입니다. 인자로 넘긴 컬렉션 타입은 Collection<String>이고 메소드 파라미터 타입은 Collection<Object>입니다. 당연히 하위 호환이 안 됩니다.

? 등장

그러면 어떤 컬렉션이든지 받아서 출력하는 메소드를 만들면 어떻게 해야 할까요? 이걸 해결하기 위해 wildcard가 등장합니다. 간단히 <Object><?>로 바꾸면 언제 에러 났냐는 듯 해결이 됩니다.

static void printCollection(Collection<?> c) {

그럼 Collection<Object>의 상위타입이 Collection<?> 인가요? 그렇게 말하기는 좀 애매합니다. 왜냐하면 ?은 타입은 아니니 까요^^; 그냥 ?그냥 임의의 어떤 것으로 해석하는 게 쉬울 듯합니다. 상하 관계가 아닌 그냥 어떤 것!

자 그러면 여기서 어떤 것이기는 하는데 Number를 상속한 어떤 것! 이라고 하고 싶을 때는 어떻게 할까요? 지지난번 포스트에서 한정자 이야기를 했었는데 바로 그놈을 쓰면 됩니다. extends

위 코드를 아래처럼 고쳐 보겠습니다.

static void printCollection(Collection<? extends Number> c) {
    for (Number e : c) {
        System.out.println(e);
    }
}

public static void main(String[] args) {
    Collection<Integer> c = new ArrayList<>();
    c.add(123);
    printCollection(c);
}

제네릭과 유사하게 printCollection의 인자 cNumber 타입 컬렉션 중 어떤 것이기 때문에 Number가 할 수 있는 일을 할 수 있습니다.

목표 다시 해석해보기

이쯤에서 우리의 목표 메소드를 보면

public <T extends Comparable<? super T>> T max(Collection<? extends T> col)

이제 저 ? 가 어떤 존재인지 알게 되었습니다.

Collection<? extends T> col에서 T 타입보다 하위 타입인 어떤 것을 저장하는 컬렉션을 인자로 받는 메소드인 것을 알 수 있습니다.

그럼 T 타입은 무엇이냐? 그러기 위해서는 조금 더 알아야 할 키워드가 있습니다. super.

이어지는 포스트는 이렇게나 복잡한 와일드카드에 대해 더 자세한 내용으로 이야기해 보겠습니다.

고고!