Java 제네릭 와일드카드에 대하여
Java 제네릭(Generic)은 코드의 재사용성을 높이고, 타입 안전성을 보장하며, 런타임 시 타입 캐스팅을 줄이기 위해 도입된 기능입니다. 제네릭은 컬렉션이나 메서드에 타입을 정의할 수 있는 강력한 기능을 제공하지만, 때로는 제네릭 타입을 유연하게 다루어야 하는 경우가 생깁니다. 이러한 상황에서 사용되는 것이 바로 와일드카드(Wildcard) 입니다.
이 글에서는 Java 제네릭 와일드카드의 개념, 종류, 사용법, 그리고 장단점에 대해 자세히 살펴보겠습니다.
1. 제네릭 와일드카드란?
와일드카드는 ?
기호로 표현되며, 제네릭 타입의 상한 또는 하한을 정의하거나 제네릭 타입을 보다 유연하게 다룰 수 있도록 설계되었습니다.
와일드카드는 크게 세 가지로 나뉩니다:
- Unbounded Wildcard: 제한이 없는 와일드카드 (
?
) - Upper Bounded Wildcard: 상한이 지정된 와일드카드 (
<? extends Type>
) - Lower Bounded Wildcard: 하한이 지정된 와일드카드 (
<? super Type>
)
2. 와일드카드의 종류와 사용법
2.1 Unbounded Wildcard (?
)
Unbounded Wildcard는 특정 타입에 제한을 두지 않고, "아무 타입이나 허용"하는 경우에 사용됩니다. 주로 제네릭 타입에 대해서 타입에 의존하지 않는 코드를 작성할 때 유용합니다.
사용 예시
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
동작 설명
List<?>
는 어떤 타입의 리스트라도 전달받을 수 있습니다.- 내부적으로는
Object
타입으로 처리합니다.
주요 특징
- 읽기 전용으로 사용하는 것이 일반적입니다.
- 타입이 명확하지 않으므로 데이터를 추가(insert)하는 동작에는 적합하지 않습니다.
2.2 Upper Bounded Wildcard (<? extends Type>
)
상한이 지정된 와일드카드는 특정 타입과 그 서브타입만 허용합니다. 주로 읽기 전용의 데이터를 다룰 때 사용됩니다.
사용 예시
public double sum(List<? extends Number> numbers) {
double sum = 0.0;
for (Number number : numbers) {
sum += number.doubleValue();
}
return sum;
}
동작 설명
List<? extends Number>
는Number
와 그 서브타입(Integer
,Double
,Float
등)의 리스트만 받을 수 있습니다.- 메서드 내부에서 읽기 작업은 가능하지만, 타입이 불확실하므로 추가(insert) 작업은 불가능합니다.
주요 특징
- "읽기 전용(Read-Only)" 작업에 적합합니다.
- 데이터를 안전하게 읽기 위해 사용하는 경우가 많습니다.
2.3 Lower Bounded Wildcard (<? super Type>
)
하한이 지정된 와일드카드는 특정 타입과 그 슈퍼타입만 허용합니다. 주로 쓰기(insert) 작업에 유용합니다.
사용 예시
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
동작 설명
List<? super Integer>
는Integer
와 그 슈퍼타입(Number
,Object
)의 리스트를 받을 수 있습니다.- 메서드 내부에서 안전하게
Integer
값을 추가할 수 있습니다. - 반대로, 읽기 작업 시에는 타입이
Object
로 처리됩니다.
주요 특징
- "쓰기 전용(Write-Only)" 작업에 적합합니다.
- 데이터를 삽입(insert)하기 위한 작업에서 주로 사용됩니다.
3. 와일드카드가 필요한 이유
3.1 제네릭 타입의 유연성 확보
제네릭은 타입 안정성을 보장하지만, 특정 상황에서는 너무 엄격하게 작동하여 불편함을 초래할 수 있습니다. 와일드카드는 이러한 엄격함을 완화하고, 유연한 코드를 작성할 수 있게 합니다.
3.2 코드의 재사용성 증가
와일드카드는 다양한 타입을 처리할 수 있어, 코드의 재사용성을 높이는 데 큰 역할을 합니다. 예를 들어, 상한 바운드를 사용하면 특정 타입 계층에 대한 로직을 하나의 메서드로 처리할 수 있습니다.
4. 와일드카드 사용 시 주의점
4.1 읽기와 쓰기의 제한
<? extends T>
: 읽기는 가능하지만, 쓰기는 제한됩니다.<? super T>
: 쓰기는 가능하지만, 읽기는 제한됩니다.<?>
: 읽기는 가능하지만, 쓰기는 거의 불가능합니다.
4.2 타입 안전성 보장
와일드카드는 타입 안정성을 일부 포기하는 대신 유연성을 제공합니다. 따라서 잘못된 타입 캐스팅이나 타입 불일치로 인한 런타임 오류를 방지하려면, 설계 시 신중하게 고려해야 합니다.
5. 와일드카드와 제네릭 메서드 비교
와일드카드와 제네릭 메서드는 비슷해 보이지만, 용도가 다릅니다.
와일드카드 예시
public void print(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
제네릭 메서드 예시
public <T> void print(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}
차이점
- 와일드카드는 불특정 타입(
?
)을 처리하는 데 사용되며, 구체적인 타입 명시 없이 유연성을 제공합니다. - 제네릭 메서드는 특정 타입(
T
)을 정의하고, 타입에 의존적인 로직을 작성하는 데 적합합니다.
6. 와일드카드와 PECS 원칙
Java에서 와일드카드를 사용할 때 PECS 원칙을 기억하는 것이 좋습니다.
- Producer Extends, Consumer Super
- 데이터를 제공(Produce)할 때는
<? extends Type>
를 사용합니다. - 데이터를 소비(Consume)할 때는
<? super Type>
를 사용합니다.
PECS 적용 예시
// Producer - 데이터 제공
public void readData(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num);
}
}
// Consumer - 데이터 소비
public void writeData(List<? super Integer> numbers) {
numbers.add(10);
numbers.add(20);
}
7. 실무에서의 활용 사례
7.1 타입 계층 구조에서의 데이터 처리
public void processShapes(List<? extends Shape> shapes) {
for (Shape shape : shapes) {
shape.draw();
}
}
7.2 컬렉션의 유연한 데이터 추가
public void addToCollection(List<? super String> collection) {
collection.add("Hello");
collection.add("World");
}
8. 장단점 요약
장점
- 코드의 재사용성을 높이고, 유연성을 제공합니다.
- 다양한 타입 계층을 안전하게 처리할 수 있습니다.
- 읽기와 쓰기 작업을 명확하게 구분할 수 있습니다.
단점
- 읽기와 쓰기 작업의 제약 조건을 이해하고 사용해야 하므로 학습 곡선이 있습니다.
- 타입 안정성을 일부 포기하는 경우가 있습니다.
'Java' 카테고리의 다른 글
명품자바 프로그래밍의 기초: 15장 (0) | 2024.09.18 |
---|---|
명품자바 프로그래밍의 기초: 14장 (0) | 2024.09.18 |
명품자바 프로그래밍의 기초: 13장 (0) | 2024.09.17 |
명품자바 프로그래밍의 기초: 12장 (0) | 2024.09.15 |
명품자바 프로그래밍의 기초: 11장 (0) | 2024.09.13 |