본문 바로가기
프로그래밍/JAVA

Java 제네릭 와일드카드 사용법 | ?, ? extends, ? super 차이점 한눈에!

by 곰 옥수수 2025. 3. 4.
728x90
반응형

제네릭 와일드카드(Generics Wildcards)

제네릭 와일드카드(Generics Wildcards)는 제네릭 타입을 좀 더 유연하게 사용하기 위해 제공되는 기능이다. 주로 메서드에서 제네릭 타입을 사용할 때 타입의 불확실성을 처리하거나, 특정한 범위의 타입만 허용하도록 할 때 사용한다.


1. 와일드카드의 종류

제네릭 와일드카드는 ?(물음표) 기호를 사용하며, 크게 세 가지 종류가 있다.

와일드카드 설명
? (Unbounded Wildcard) 제한이 없는 와일드카드
? extends T (Upper Bounded Wildcard) T 또는 T의 하위 클래스만 허용
? super T (Lower Bounded Wildcard) T 또는 T의 상위 클래스만 허용

2. 와일드카드 상세 설명

(1) 제한 없는 와일드카드 (?)

  • ?는 어떤 타입이든 올 수 있다는 의미다.
  • 특정한 타입을 명시하지 않고, 어떤 타입이라도 허용할 때 사용된다.

예제

import java.util.*;

public class WildcardExample {
    public static void printList(List<?> list) { // 어떤 타입이든 허용
        for (Object obj : list) {
            System.out.println(obj);
        }
    }

    public static void main(String[] args) {
        List<String> strList = Arrays.asList("Apple", "Banana", "Orange");
        List<Integer> intList = Arrays.asList(1, 2, 3, 4);

        printList(strList);  // 가능
        printList(intList);  // 가능
    }
}
  • List<?> list는 모든 타입의 리스트를 받을 수 있다는 뜻이다.
  • 단, list.add()와 같은 요소 추가는 불가능하다. (제네릭 타입 안정성 문제)

(2) 상한 제한 와일드카드 (? extends T)

  • ? extends T는 T와 T의 하위 클래스들만 허용한다.
  • 주로 읽기 전용으로 사용하며, 리스트에서 값을 가져올 때 유용하다.

예제

import java.util.*;

class Animal {
    void sound() {
        System.out.println("Some animal sound");
    }
}

class Dog extends Animal {
    void sound() {
        System.out.println("Woof Woof!");
    }
}

class Cat extends Animal {
    void sound() {
        System.out.println("Meow!");
    }
}

public class WildcardExample {
    // 상한 제한: Animal 또는 Animal의 하위 클래스만 가능
    public static void printAnimalSounds(List<? extends Animal> animals) {
        for (Animal animal : animals) {
            animal.sound(); // 안전하게 호출 가능
        }
    }

    public static void main(String[] args) {
        List<Dog> dogs = Arrays.asList(new Dog(), new Dog());
        List<Cat> cats = Arrays.asList(new Cat(), new Cat());

        printAnimalSounds(dogs); // 가능
        printAnimalSounds(cats); // 가능
    }
}
  • List<? extends Animal>는 Animal과 그 하위 타입(Dog, Cat 등)만 허용
  • 그러나 animals.add(new Dog()); 와 같은 요소 추가는 불가능하다.

   ➡ "읽기(Read)는 가능하지만, 쓰기(Write)는 불가능"


(3) 하한 제한 와일드카드 (? super T)

  • ? super T는 T와 T의 상위 클래스만 허용한다.
  • 주로 쓰기(Write) 연산이 필요할 때 사용된다.

예제

import java.util.*;

public class WildcardExample {
    public static void addNumbers(List<? super Integer> list) {
        list.add(10);
        list.add(20);
    }

    public static void main(String[] args) {
        List<Number> numbers = new ArrayList<>();
        addNumbers(numbers); // 가능

        List<Object> objects = new ArrayList<>();
        addNumbers(objects); // 가능

        // List<Double> doubles = new ArrayList<>();
        // addNumbers(doubles);  // 오류 발생 (Integer의 상위 클래스가 아님)
    }
}
  • List<? super Integer>는 Integer와 그 상위 클래스(Number, Object 등)만 허용
  • list.add(10); 같은 쓰기(Write)는 가능하지만, list.get(0)을 하면 Object 타입으로 반환됨

    ➡ "쓰기(Write)는 가능하지만, 읽기(Read)는 제한됨(Object로 반환)"


3. 와일드카드 사용 원칙

와일드카드특징주요 사용 예

? (Unbounded) 모든 타입 허용 (제한 없음) 제네릭 타입을 신경 쓰지 않고 리스트를 읽을 때
? extends T (상한 제한) T 및 그 하위 클래스만 허용 (읽기 전용) 리스트에서 값을 읽을 때
? super T (하한 제한) T 및 그 상위 클래스만 허용 (쓰기 전용) 리스트에 값을 추가할 때

4. 와일드카드와 제네릭 타입의 차이

(1) 제네릭 타입 사용

public static <T> void printListGeneric(List<T> list) {
    for (T item : list) {
        System.out.println(item);
    }
}

➡ printListGeneric(List<T> list)는 고정된 타입을 사용

(2) 와일드카드 사용

public static void printListWildcard(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

➡ printListWildcard(List<?> list)는 모든 타입을 받을 수 있음

  • 제네릭 메서드는 동일한 타입을 유지하면서 사용해야 할 때 유용
  • 와일드카드는 여러 타입을 허용하지만, 읽기/쓰기 제한이 있을 수 있음

5. 요약

  • ? → 제한 없음 (모든 타입 가능)
  • ? extends T → T 및 하위 클래스 (읽기 가능, 쓰기 불가능)
  • ? super T → T 및 상위 클래스 (쓰기 가능, 읽기 시 Object로 반환)
728x90
반응형

댓글