와일드 카드에 대한 정리

와일드카드는 Java의 제네릭 타입에서 유연성을 높이기 위해 도입된 기능으로, 제네릭 타입 매개변수를 특정하지 않고 사용할 수 있게 해줍니다. 주로 세 가지 형태로 사용되며, 각각의 의미와 사용법이 조금씩 다릅니다.

1. 와일드카드의 종류

Java에서는 다음 세 가지 형태의 와일드카드를 사용할 수 있습니다.

  1. 비한정적 와일드카드 (?)

  2. 상한 경계 와일드카드 (? extends T)

  3. 하한 경계 와일드카드 (? super T)

이 세 가지 와일드카드는 서로 다른 상황에서 제네릭 타입의 유연성을 높이기 위해 사용됩니다.

2. 와일드카드의 종류와 사용법

상한 제한인 한정적 와일드카드가 읽기 전용인 건 타입 안정성 때문인데 읽을 때는 상위 타입으로 처리하면 되지만 ?는 실제로 어떤 타입이 들어가는지 알 수 없다는 거라서 그렇다고 함 List라고 해도 실제로 Double이 들어갈지 어떤 타입이 들어가는지 모르니까 타입 불일치가 있을수 있고 이건 컴파일타임에 체크할 수 없어서 막아두었다고 함

2.1. 비한정적 와일드카드 (?)

  • 표기법: <?>

  • 의미: "아무 타입이나" 허용한다는 의미로, 타입 매개변수가 무엇이든 관계없이 사용할 수 있습니다.

  • 사용 사례: 컬렉션의 원소 타입이 무엇인지 신경 쓰지 않고, 모든 타입의 컬렉션을 처리하고자 할 때 사용합니다. 예를 들어, List<?>는 어떤 타입의 리스트든 받을 수 있습니다.

  • 제약사항: 비한정적 와일드카드를 사용하는 컬렉션에는 null 외의 다른 값을 추가할 수 없습니다.

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

위 코드에서는 Collection<?> 타입의 인자를 받기 때문에, 어떤 타입의 컬렉션이든 인자로 전달할 수 있습니다.

2.2. 상한 경계 와일드카드 (? extends T)

  • 표기법: <? extends T>

  • 의미: 특정 타입 T와 그 하위 타입만을 허용합니다.

  • 사용 사례: 주로 읽기 전용 작업에 사용됩니다. 예를 들어, 특정 클래스의 하위 클래스 타입을 다룰 때 유용합니다.

  • 제약사항: 와일드카드 타입으로 컬렉션에 추가할 수 있는 요소의 타입이 불명확하므로, 컬렉션에 요소를 추가할 수 없습니다.

void printNumbers(List<? extends Number> list) {
    for (Number n : list) {
        System.out.println(n);
    }
}

위 코드에서 List<? extends Number>List<Integer>, List<Double>Number의 하위 타입을 인자로 받을 수 있습니다.

public void read(Collection<?> list) {
        list.add(null);
    }

읽기 전용이라는 게 파라미터로 와일드카드 타입인 컬렉션 받으면 add 가 Null 밖에 안되고 읽기만 가능해서 그렇다는 것 ?가 들어가면 결국 타입을 알수 없다는 건데 List<? extends Number> list = new ArrayList();라는 정의가 있을 때 컴파일러는 list가 적어도 Number라는 건 알지만 실제로 어떤 타입인지 모르니까 그 다음에 list.add(1.0); 으로 Integer 아닌 값을 넣어도 타입을 몰라서 이부분까지 체크를 못해준다고 함 타입이 다를 수 있는데 컴파일러가 체크해줄수 없어서 막아둔듯 합니다 반대로 하한제한은 컴파일러가 타입 체크를 해줄 수 있어서 쓰기 작업도 허용해준다고 함

2.3. 하한 경계 와일드카드 (? super T)

  • 표기법: <? super T>

  • 의미: 특정 타입 T와 그 상위 타입만을 허용합니다.

  • 사용 사례: 컬렉션에 안전하게 요소를 추가할 수 있도록 보장합니다. T와 그 상위 타입만을 허용하므로, 예를 들어 List<? super Integer>Integer, Number, Object 타입의 요소를 추가할 수 있습니다.

  • 제약사항: 요소를 읽을 때는 Object 타입으로 반환됩니다.

void addIntegers(List<? super Integer> list) {
    list.add(10);
    list.add(20);
}

위 코드에서 List<? super Integer>List<Integer>, List<Number>, List<Object>와 같은 상위 타입의 리스트를 인자로 받을 수 있으며, Integer 타입의 값을 추가할 수 있습니다.

3. 와일드카드 사용 시 주의사항

  1. 비한정적 와일드카드 (?)는 컬렉션의 타입 매개변수를 알 수 없으므로, 안전한 타입을 보장하기 위해 null 외의 값을 추가할 수 없습니다.

  2. 상한 경계 와일드카드 (? extends T)는 주로 읽기 전용 작업에 사용되며, 컬렉션에 요소를 추가하는 것은 불가능합니다.

  3. 하한 경계 와일드카드 (? super T)는 안전하게 요소를 추가할 수 있지만, 요소를 읽을 때는 Object로 반환되므로 타입 캐스팅이 필요할 수 있습니다.

4. 로 타입과의 차이

  • 로 타입 (List)은 타입 안전성이 보장되지 않으며, 제네릭의 장점을 활용할 수 없습니다. 타입을 지정하지 않으면 컴파일 시점에 타입 오류를 검출할 수 없고, 런타임 오류가 발생할 수 있습니다.

  • 와일드카드 (List<?>, List<? extends T>, List<? super T>)를 사용하면 제네릭의 타입 안전성을 유지하면서도, 유연한 타입 처리가 가능합니다.

5. 와일드카드 사용 시 유의점

  • 와일드카드는 제네릭 메서드 작성 시 유용하며, 특히 라이브러리 설계에서 API의 유연성을 높이는 데 큰 도움이 됩니다.

  • 클래스 리터럴에서는 로 타입을 사용해야 하며, List<?>.classList<String>.class는 허용되지 않습니다.

  • instanceof 연산자와 함께 사용할 때도 로 타입을 사용해야 합니다. 제네릭 타입 정보는 런타임에 지워지기 때문입니다.

// 로 타입을 사용하여 instanceof 연산자 적용
if (o instanceof Set) {
    Set<?> set = (Set<?>) o;
}

6. PECS(Producer-Extends, Consumer-Super) 공식

그렇다면 도대체 언제 super를 사용해야 하고, 언제 extends를 사용해야 하는지 헷갈릴 수 있다. 그래서 이펙티브 자바에서는 PECS라는 공식을 만들었는데, 이는 Producer-Extends, Consumer-Super의 줄임말이다. 즉, 컬렉션으로부터 와일드카드 타입의 객체를 꺼내서 생성하면(produce) extends를, 갖고 있는 객체를 컬렉션에 사용(consumer)하여 더하면 super를 사용하라는 것이다.

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

void addElement(Collection<? super MyParent> c) {
    c.add(new MyParent());
}

printCollection 같은 경우에는 컬렉션으로부터 원소들을 꺼내면서 와일드카드 타입 객체를 생성(produce)하고 있다. 반대로 addElement의 경우에는 컬렉션에 해당 타입의 원소를 추가함으로써 객체를 사용(consume)하고 있다. 그러므로 와일드카드 타입의 객체를 produce하는 printCollection은 extends가, 객체를 consume하는 addElement에는 super가 적합한 것이다.

7. 결론

  • 비한정적 와일드카드 (?): 모든 타입을 허용하지만, 읽기 전용 작업에 적합하며 null 외에는 값을 추가할 수 없습니다.

  • 상한 경계 와일드카드 (? extends T): 특정 타입의 하위 타입만 허용하며, 주로 읽기 전용 작업에 사용됩니다.

  • 하한 경계 와일드카드 (? super T): 특정 타입의 상위 타입만 허용하며, 컬렉션에 안전하게 요소를 추가할 때 사용됩니다.

와일드카드는 제네릭 타입의 유연성을 높이기 위한 중요한 도구이며, 각각의 와일드카드 타입을 적절히 사용하면 코드의 재사용성과 안전성을 동시에 확보할 수 있습니다.

출처 : https://mangkyu.tistory.com/241

Last updated