1. 와일드카드 (Generics Wildcards)란?
와일드카드(Wildcards)는 제네릭 타입을 더욱 유연하게 처리하기 위한 기능입니다.
?
(물음표) 기호를 사용하여 타입을 특정하지 않고 여러 타입을 다룰 수 있도록 합니다.- 주로 제네릭 메서드의 매개변수에서 사용되며, 타입의 상속 관계를 활용하는 데 유용합니다.
- 와일드카드를 사용하면 다양한 타입을 처리하는 메서드를 더욱 유연하게 작성할 수 있습니다.
1.1 와일드카드가 필요한 이유
제네릭을 사용하다 보면 단일한 타입이 아니라, 여러 타입을 동시에 처리해야 할 경우가 종종 발생합니다. 예를 들어, 컬렉션 같은 자료구조에서 다양한 타입의 데이터를 처리하고자 할 때, 구체적인 타입을 지정하지 않고 타입 간의 상속 관계를 이용하고 싶을 때가 있습니다.
제네릭을 사용할 때, 타입 간의 상속 관계가 제네릭 타입에는 직접 적용되지 않는다는 점에서 문제가 발생합니다.
즉, 제네릭 타입은 불공변이므로, 다음과 같은 코드가 컴파일되지 않습니다.
List<Object> objectList = new ArrayList<Integer>(); // 컴파일 에러 발생!
Integer
는Object
의 하위 타입이지만,List<Integer>
는List<Object>
의 하위 타입이 아닙니다.- 제네릭은 불공변(invariant)이기 때문에, 타입 간의 직접적인 상속 관계가 적용되지 않습니다.
이를 해결하기 위해 와일드카드를 사용할 수 있습니다. 와일드카드를 사용하면 어떤 타입이든 허용할 수 있고, 타입 간의 상속 관계를 통해 상위 타입 또는 하위 타입만 제한할 수 있습니다.
2. 와일드카드의 종류
와일드카드는 크게 세 가지 종류가 있습니다.
와일드카드 종류 | 문법 | 설명 | 사용 예제 |
---|---|---|---|
무제한 와일드카드 | <?> |
모든 타입을 허용 (읽기 전용) | List<?> list |
상한 제한 와일드카드 | <? extends T> |
T와 그 하위 타입만 허용 (읽기) | List<? extends Number> |
하한 제한 와일드카드 | <? super T> |
T와 그 상위 타입만 허용 (쓰기) | List<? super Integer> |
2.1 무제한 와일드카드 (<?>
)
<?>
는 어떤 타입이든 허용하는 무제한 와일드카드입니다. 이는 특정 타입에 제한을 두지 않고, 어떤 제네릭 타입이든 처리하고 싶을 때 사용됩니다. 주로 읽기 전용의 경우에 적합합니다.
import java.util.List;
public class UnboundedWildcardExample {
// 어떤 타입의 리스트든 받아서 출력하는 메서드
public static void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3);
List<String> strList = List.of("A", "B", "C");
printList(intList); // Integer 타입 리스트 전달 가능
printList(strList); // String 타입 리스트 전달 가능
}
}
<?>
는 모든 타입을 허용하는 와일드카드입니다.- 위 예제에서
List<?>
를 사용하여List<Integer>
,List<String>
모두 전달할 수 있습니다. - 주로 읽기 작업에서 유용합니다. 하지만 쓰기 작업에서는 타입 안정성을 보장하지 않기 때문에 사용이 제한됩니다. 즉,
null
외의 값을 추가할 수 없습니다
list.add(100); // 컴파일 에러 발생!
2.2 상한 제한 와일드카드 (<? extends T>
)
<? extends T>
는 T와 그 하위 타입만 허용하는 상한 제한 와일드카드입니다. 이는 T 타입의 하위 클래스만 허용하고, 상위 클래스는 허용하지 않습니다. 주로 읽기 작업에서 사용되며, 쓰기는 제한됩니다.
import java.util.List;
public class UpperBoundedWildcardExample {
// Number의 하위 타입 리스트만 허용하는 메서드 (Integer, Double 등)
public static double sumOfNumbers(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3);
List<Double> doubleList = List.of(1.1, 2.2, 3.3);
System.out.println("Sum of integers: " + sumOfNumbers(intList)); // 허용
System.out.println("Sum of doubles: " + sumOfNumbers(doubleList)); // 허용
}
}
<? extends Number>
는Number
클래스와 그 하위 클래스만 허용합니다.- 이 메서드에서 정수나 실수 값을 모두 처리할 수 있지만, 리스트에 값을 추가하는 작업은 불가능합니다. 즉, 읽기 작업은 가능하지만 쓰기 작업은 불가능합니다.
list.add(10); // 컴파일 에러 발생!
2.3 하한 제한 와일드카드 (<? super T>
)
<? super T>
는 T와 그 상위 타입만 허용하는 하한 제한 와일드카드입니다. 이는 T 타입의 상위 클래스만 허용하고, 하위 클래스는 허용하지 않습니다. 주로 쓰기 작업에서 사용되며, 읽기 작업은 제한됩니다.
import java.util.List;
import java.util.ArrayList;
public class LowerBoundedWildcardExample {
// Integer의 상위 타입 리스트에 값을 추가하는 메서드
public static void addNumbers(List<? super Integer> list) {
list.add(10); // Integer 타입 값 추가 가능
list.add(20);
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>(); // Number는 Integer의 상위 타입
addNumbers(numberList); // Number 리스트에 Integer 추가 가능
System.out.println(numberList);
List<Object> objectList = new ArrayList<>(); // Object도 Integer의 상위 타입
addNumbers(objectList); // Object 리스트에도 Integer 추가 가능
System.out.println(objectList);
}
}
<? super Integer>
는Integer
와 그 상위 클래스(예:Number
,Object
)만 허용합니다.- 이 방식은 객체를 추가하는 작업에서 유용하며,
Integer
값은Number
나Object
타입 리스트에도 안전하게 추가할 수 있습니다. - 반대로 읽기 작업은 제한적입니다. 즉,
Object
이상의 타입으로만 읽어올 수 있습니다.
Integer num = list.get(0); // 컴파일 에러 발생!
3. PECS 공식 (Producer-Extends, Consumer-Super)
PECS 공식은 이펙티브 자바에서 제시한 개념으로, 제네릭의 상한 제한과 하한 제한을 언제 사용해야 하는지를 설명하는 가이드라인입니다. PECS는 다음과 같이 해석됩니다.
- Producer-Extends: 데이터를 생산하는 경우(읽어오는 경우)에는 상한 제한(
<? extends T>
)을 사용합니다. 데이터를 읽어올 때, 하위 타입을 포함한 여러 타입의 데이터를 읽을 수 있어야 하므로, 상한 제한을 두어 유연하게 처리합니다. - Consumer-Super: 데이터를 소비하는 경우(값을 추가하는 경우)에는 하한 제한(
<? super T>
)을 사용합니다. 데이터를 추가할 때, 해당 데이터의 상위 타입도 안전하게 받을 수 있어야 하므로 하한 제한을 사용합니다.
이 공식을 간단히 정리하면 다음과 같습니다.
- 생산자(Producer) 역할을 하는 경우, 즉 읽기 작업을 할 때는 상한 제한을 사용합니다. (
<? extends T>
) - 소비자(Consumer) 역할을 하는 경우, 즉 쓰기 작업을 할 때는 하한 제한을 사용합니다. (
<? super T>
)
import java.util.List;
import java.util.ArrayList;
public class PECSDemo {
// Producer-Extends: 데이터를 읽기 위해 사용
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num); // 데이터를 읽어오는 작업 (생산자 역할)
}
}
// Consumer-Super: 데이터를 추가하기 위해 사용
public static void addNumbers(List<? super Integer> list) {
list.add(100); // 데이터를 추가하는 작업 (소비자 역할)
list.add(200);
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
addNumbers(intList); // 값 추가 가능
List<Number> numberList = new ArrayList<>();
addNumbers(numberList); // 값 추가 가능
printNumbers(numberList); // 값 읽기 가능
}
}
이 예시에서 addNumbers
메서드는 소비자(Consumer)로 동작하여 하한 제한(<? super Integer>
)을 사용하며, printNumbers
메서드는 생산자(Producer)로 동작하여 상한 제한(<? extends Number>
)을 사용합니다.
3.1 (참고) PECS와 카프카에서의 용어 차이
참고로 카프카(Kafka)에서의 Producer와 Consumer는 메시징 시스템에서 사용되는 개념으로, 메시지를 생산(쓰기)하거나 소비(읽기)하는 주체를 말합니다. Producer는 메시지를 생산해서 보내는 역할을 하고, Consumer는 그 메시지를 가져와서 사용하는 역할을 합니다. 이는 데이터 흐름에 중점을 둔 시스템 관점에서의 정의입니다.
하지만, PECS 공식에서의 Producer와 Consumer는 조금 다른 의미를 가지고 있습니다. 자바 제네릭에서 PECS(Producer-Extends, Consumer-Super)는 메서드에서 데이터의 흐름 방향에 따라 제네릭 타입을 어떻게 처리해야 할지 결정하는 가이드입니다. 여기에서의 Producer는 "데이터를 제공(생산)하는 측", 즉 읽기 작업을 하는 측을 의미하고, Consumer는 "데이터를 소비하는 측", 즉 쓰기 작업을 의미합니다.
정리하자면
- 카프카(Kafka)에서 Producer는 데이터를 생산해서 보낸다는 의미로, 쓰기 작업을 하는 주체입니다. Consumer는 메시지를 소비(가져온다)는 의미로 읽기 작업을 하는 주체입니다.
- 반면, PECS 공식에서는 Producer(extends)는 데이터를 읽어오는 작업을 의미합니다. 이는 데이터를 제공하는 측으로 읽기 작업에 해당합니다. Consumer(super)는 데이터를 추가하는 작업을 의미합니다. 이는 데이터를 사용하는(소비하는) 측으로 쓰기 작업에 해당합니다.
따라서, 카프카의 용어와 PECS 공식에서의 의미는 다르지만, 이 둘은 서로 다른 문맥에서 정의된 개념입니다.
4. 와일드카드 사용 시 주의사항
- 와일드카드를 사용할 때는 읽기/쓰기 여부를 고려해야 합니다.
<? extends T>
는 읽기 전용입니다.<? super T>
는 쓰기 전용입니다.
- 무제한 와일드카드(
<?>
)는 읽기 전용으로 사용해야 합니다.- 데이터를 추가할 수 없습니다.
- 제네릭 메서드와 와일드카드의 차이를 이해해야 합니다.
- 제네릭 메서드는 타입 매개변수를 직접 지정할 수 있습니다.
- 와일드카드는 타입을 지정하지 않고 유연하게 처리할 수 있습니다.
- PECS 원칙을 기억
- Producer(extends):
<? extends T>
→ 읽기 - Consumer(super):
<? super T>
→ 쓰기
- Producer(extends):
5. 정리
- 와일드카드 종류
와일드카드 | 문법 | 사용 예제 |
---|---|---|
무제한 와일드카드 | <?> |
List<?> list |
상한 제한 와일드카드 | <? extends T> |
List<? extends Number> |
하한 제한 와일드카드 | <? super T> |
List<? super Integer> |
- PECS 공식
- 생산자(Producer) →
<? extends T>
(읽기) - 소비자(Consumer) →
<? super T>
(쓰기)
- 생산자(Producer) →
'BackEnd > JAVA' 카테고리의 다른 글
[JAVA] Lombok의 @UtilityClass 어노테이션 정리 (1) | 2025.02.13 |
---|---|
[JAVA] 얕은 복사 (Shallow Copy) VS 깊은 복사 (Deep Copy) (2) | 2025.02.06 |
[JAVA] 제네릭 (Generic) 정리 (1) | 2025.01.26 |
[JAVA] 스레드 (Thread) 정리 (2) | 2025.01.25 |
[JAVA] 인터페이스 심화: 다중 상속, 다중 구현 (3) | 2025.01.20 |