Upper Bounded 와일드카드
upper bounded 와일드카드를 사용하여 변수에 대한 제한을 완화할 수 있습니다. 예를 들어 List<Integer>, List<Double>, List<Number>에서 작동하는 메서드를 작성하고 싶다고 가정할 때, upper bounded 와일드카드를 사용하여 이를 달성할 수 있습니다.
상한 와일드카드를 선언하려면 와일드카드 문자(‘?’) 뒤에 extends 키워드를 사용하고 그 뒤에 상한을 지정합니다. 이 문맥에서 extends는 일반적인 의미로 “extends”(클래스에서처럼) 또는 “implements”(인터페이스에서처럼)을 의미한다는 점에 유의하세요.
Number의 리스트와 Number의 하위 타입인 Integer, Double, Float에 대해 작동하는 메서드를 작성하려면 List<? extends Number>를 지정하면 됩니다. 전자는 Number 타입의 리스트만 일치시키는 반면, 후자는 Number 타입의 리스트 또는 그 서브클래스와 일치시키기 때문에 List<Number>라는 용어가 List<? extends Number>보다 더 제한적이라고 할 수 있습니다.
다음 처리 방법을 고려해 보세요:
public static void process(List<? extends Foo> list) { /* ... */ }upper bounded 와일드카드인 <? extends Foo>는 Foo가 임의의 타입인 경우 Foo 및 Foo의 모든 하위 타입과 일치합니다. 프로세스 메서드는 Foo 타입으로 목록 요소에 액세스할 수 있습니다:
public static void process(List<? extends Foo> list) {
for (Foo elem : list) {
// ...
}
}foreach 절에서 elem 변수는 목록의 각 요소를 반복합니다. 이제 Foo 클래스에 정의된 모든 메서드를 elem에서 사용할 수 있습니다.
sumOfList() 메서드는 목록에 있는 숫자의 합을 반환합니다:
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}다음 코드는 Integer 객체 목록을 사용하여 sum = 6.0을 출력합니다:
List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));Double 값의 목록은 동일한 sumOfList() 메서드를 사용할 수 있습니다. 다음 코드는 sum = 7.0을 출력합니다:
List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));
바인딩되지 않은 와일드카드
바인딩되지 않은 와일드카드 타입은 와일드카드 문자(?)를 사용하여 지정합니다(예: List<?>). 이를 알 수 없는 타입의 목록이라고 합니다. 다음과 같은 두 가지 시나리오에서 바인딩되지 않은 와일드카드가 유용한 접근 방식이 될 수 있습니다:
Object클래스에서 제공하는 기능을 사용하여 구현할 수 있는 메서드를 작성하는 경우.- 코드가 타입 매개변수에 의존하지 않는 제네릭 클래스의 메서드를 사용하는 경우. 예를 들어,
List.size()또는List.clear(). 사실Class<?>가 자주 사용되는 이유는Class<T>의 메서드 대부분이T에 의존하지 않기 때문입니다.
다음 메서드인 printList()를 살펴봅시다:
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}printList()의 목표는 모든 타입의 목록을 인쇄하는 것이지만, Object 인스턴스 목록만 인쇄하고 List<Integer>, List<String>, List<Double> 등은 List<Object>의 하위 유형이 아니므로 인쇄하지 못하기 때문에 그 목표를 달성하지 못합니다. 제네릭 printList() 메서드를 작성하려면 List<?>를 사용합니다:
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}구체적인 타입 A의 경우 List<A>는 List<?>의 하위 타입이므로 printList()를 사용하여 모든 타입의 목록을 인쇄할 수 있습니다:
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);Note: 이 섹션의 예제에서는
Arrays.asList()메서드가 사용됩니다. 이 정적 팩토리 메서드는 지정된 배열을 변환하여 고정된 크기의 리스트를 반환합니다.
List<Object>와 List<?>는 동일하지 않다는 점에 유의하세요. Object 또는 Object의 하위 타입을 List<Object>에 삽입할 수 있습니다. 그러나 List<?>에는 null만 삽입할 수 있습니다. 이 섹션 끝에 있는 와일드카드 사용 가이드라인 단락에 주어진 상황에서 어떤 종류의 와일드카드를 사용해야 하는지 결정하는 방법에 대한 자세한 정보가 나와 있습니다.
Lower Bounded Wildcards
Upper Bounded 와일드카드 섹션에서는 알 수 없는 타입을 특정 타입 또는 해당 타입의 하위 타입으로 제한하며 extends 키워드를 사용하여 표시합니다. 비슷한 방식으로 lower bounded 와일드카드는 알 수 없는 타입을 특정 타입 또는 해당 타입의 상위 타입으로 제한합니다.
lower bounded 와일드카드는 와일드카드 문자(?) 뒤에 super 키워드가 오고 그 뒤에 하한값인 <? super A>를 사용하여 표현합니다.
Note: 와일드카드의 상한을 지정하거나 하한을 지정할 수 있지만 둘 다 지정할 수는 없습니다.
Integer 객체를 목록에 넣는 메서드를 작성한다고 가정해 보겠습니다. 유연성을 극대화하기 위해 메서드가 Integer 값을 담을 수 있는 List<Integer>, List<Number>, List<Object>에서 작동하기를 원할 것입니다.
Integer의 리스트와 Integer의 슈퍼타입인 Integer, Number, Object에 대해 작동하는 메서드를 작성하려면 List<? super Integer>를 지정하면 됩니다. 전자는 Integer 타입의 목록만 일치시키는 반면, 후자는 Integer의 상위 타입인 모든 타입의 목록을 일치시키기 때문에 List<Integer>가 List<? super Integer>보다 더 제한적인 용어입니다.
다음 코드는 목록 끝에 숫자 1부터 10까지를 추가하는 코드입니다:
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}이 섹션 끝에 있는 와일드카드 사용 가이드라인 단락에서는 upper bounded 와일드카드를 사용하는 경우와 lower bounded 와일드카드를 사용하는 경우에 대한 지침을 제공합니다.
와일드카드 및 하위 유형화
이전 섹션에서 설명한 것처럼 제네릭 클래스나 인터페이스는 단지 타입 사이에 관계가 있다는 이유만으로 서로 연관되지 않습니다. 그러나 와일드카드를 사용하여 제네릭 클래스 또는 인터페이스 간의 관계를 만들 수 있습니다.
다음 두 개의 일반(제네릭이 아닌) 클래스가 있다고 가정해 보겠습니다:
class A { /* ... */ }
class B extends A { /* ... */ }다음과 같은 코드를 작성하는 것이 합리적일 것입니다:
B b = new B();
A a = b;이 예는 일반 클래스의 상속이 이러한 서브타입 규칙을 따른다는 것을 보여줍니다: 클래스 B가 A를 확장하면 B는 클래스 A의 서브타입이 됩니다. 이 규칙은 제네릭 타입에는 적용되지 않습니다:
List<B> lb = new ArrayList<>();
List<A> la = lb; // compile-time errorInteger가 Number의 하위 유형이라고 가정할 때, List<Integer>와 List<Number>의 관계는 무엇인가요?

일반적인 부모 매개변수화된 리스트입니다.
Integer는 Number의 서브타입이지만, List<Integer>는 List<Number>의 서브타입이 아니며 실제로 이 두 타입은 서로 관련이 없습니다. List<Number>와 List<Integer>의 공통 부모는 List<? >입니다.
코드가 Number의 요소를 통해 List<Integer>의 메서드에 액세스할 수 있도록 이러한 클래스 간의 관계를 만들려면 상위 한정 와일드카드를 사용합니다:
List<? extends Integer> intList = new ArrayList<>();
// This is OK because List<? extends Integer> is a subtype of List<? extends Number>
List<? extends Number> numList = intList; Integer는 Number의 서브타입이고 numList는 Number 객체의 리스트이므로 이제 intList(Integer 객체의 리스트)와 numList 사이에 관계가 존재합니다. 다음 다이어그램은 상한 및 하한 와일드카드를 모두 사용하여 선언된 여러 List 클래스 간의 관계를 보여줍니다.

여러 제네릭 List 클래스 선언의 계층 구조.
동일한 규칙에 따라 List<? extends Number>는 다음 다이어그램과 같이 Number 자체를 포함하여 Number의 확장인 모든 타입의 목록으로 확장될 수 있습니다.

Number의 목록은 ?의 목록을 확장합니다.
그리고 List<? super Integer>와 List<Integer> 사이의 관계도 마찬가지입니다.

정수 목록은 ? 슈퍼 정수 목록을 확장합니다.
이 섹션 끝에 있는 와일드카드 사용 가이드라인 단락에 상한 및 하한 와일드카드 사용의 영향에 대한 자세한 정보가 나와 있습니다.
와일드카드 캡처 및 헬퍼 메서드
어떤 경우에는 컴파일러가 와일드카드의 타입을 유추합니다. 예를 들어, 목록은 List<?>로 정의될 수 있지만 표현식을 평가할 때 컴파일러는 코드에서 특정 타입을 유추합니다. 이 시나리오를 와일드카드 캡처라고 합니다.
대부분의 경우 와일드카드 캡처에 대해 걱정할 필요는 없지만, “capture of”라는 문구가 포함된 오류 메시지가 표시되는 경우는 예외입니다.
WildcardError 예제는 컴파일 시 캡처 오류를 생성합니다:
import java.util.List;
public class WildcardError {
void foo(List<?> i) {
i.set(0, i.get(0));
}
}이 예제에서 컴파일러는 i 입력 매개변수를 Object 타입인 것으로 처리합니다. foo 메서드가 List.set(int, E)를 호출하면 컴파일러가 목록에 삽입되는 객체의 타입을 확인할 수 없어 에러가 발생합니다. 이러한 유형의 오류가 발생하면 일반적으로 컴파일러는 사용자가 변수에 잘못된 타입을 할당하고 있다고 판단합니다. 이러한 이유로 Java 언어에 제네릭이 추가되어 컴파일 시 타입 안전성을 강화했습니다.
WildcardError 예제는 오라클의 JDK 7 javac 구현으로 컴파일할 때 다음과 같은 오류를 생성합니다:
WildcardError.java:6: error: method set in interface List<E> cannot be applied to given types;
i.set(0, i.get(0));
^
required: int,CAP#1
found: int,Object
reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error이 예제에서는 코드가 안전한 작업을 수행하려고 시도하고 있는데 컴파일러 오류를 어떻게 해결할 수 있을까요? 와일드카드를 캡처하는 비공개 헬퍼 메서드를 작성하여 문제를 해결할 수 있습니다. 이 경우 WildcardFixed에 표시된 것처럼 비공개 헬퍼 메서드인 fooHelper()를 작성하여 문제를 해결할 수 있습니다:
public class WildcardFixed {
void foo(List<?> i) {
fooHelper(i);
}
// Helper method created so that the wildcard can be captured
// through type inference.
private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}
}헬퍼 메서드 덕분에 컴파일러는 추론을 통해 호출에서 T가 캡처 변수인 CAP#1인지 확인합니다. 이제 예제가 성공적으로 컴파일됩니다.
일반적으로 헬퍼 메서드의 이름은 originalMethodNameHelper()입니다.
이제 좀 더 복잡한 예제인 WildcardErrorBad를 살펴봅시다:
import java.util.List;
public class WildcardErrorBad {
void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
Number temp = l1.get(0);
l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
// got a CAP#2 extends Number;
// same bound, but different types
l2.set(0, temp); // expected a CAP#1 extends Number,
// got a Number
}
}이 예시에서는 코드가 안전하지 않은 작업을 시도하고 있습니다. 예를 들어 다음과 같은 swapFirst() 메서드 호출을 생각해 보겠습니다:
List<Integer> li = Arrays.asList(1, 2, 3);
List<Double> ld = Arrays.asList(10.10, 20.20, 30.30);
swapFirst(li, ld);List<Integer>와 List<Double>는 모두 List<? extends Number>의 조건을 충족하지만, Integer 값 목록에서 항목을 가져와 Double 값 목록에 배치하려고 시도하는 것은 분명히 올바르지 않습니다.
오라클의 JDK javac 컴파일러로 코드를 컴파일하면 다음과 같은 오류가 발생합니다:
WildcardErrorBad.java:7: error: method set in interface List<E> cannot be applied to given types;
l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
^
required: int,CAP#1
found: int,Number
reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:10: error: method set in interface List<E> cannot be applied to given types;
l2.set(0, temp); // expected a CAP#1 extends Number,
^
required: int,CAP#1
found: int,Number
reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:15: error: method set in interface List<E> cannot be applied to given types;
i.set(0, i.get(0));
^
required: int,CAP#1
found: int,Object
reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
3 errors코드가 근본적으로 잘못되었기 때문에 이 문제를 해결할 수 있는 헬퍼 메서드는 없습니다. Integer 값 목록에서 항목을 가져와서 Double 값 목록에 넣으려는 것은 분명히 잘못된 것입니다.
와일드카드 사용 가이드라인
제네릭을 사용하여 프로그래밍을 배울 때 가장 혼란스러운 측면 중 하나는 상위 와일드카드를 언제 사용할지, 하위 와일드카드를 언제 사용할지 결정하는 것입니다. 이 페이지에서는 코드를 디자인할 때 따라야 할 몇 가지 지침을 제공합니다.
이 논의의 목적을 위해 변수를 두 가지 기능 중 하나를 제공하는 것으로 생각하면 도움이 됩니다:
- “In” 변수. “in” 변수는 코드에 데이터를 제공합니다. 두 개의 인수가 있는 복사 메서드를 상상해 봅시다:
copy(src, dest). 인자src는 복사할 데이터를 제공하므로 “in” 매개변수입니다. - “Out” 변수. “out” 변수는 다른 곳에서 사용할 데이터를 저장합니다. 복사 예제인
copy(src, dest)에서 dest 인수는 데이터를 받으므로 “out” 매개변수입니다.
물론 일부 변수는 “in” 및 “out” 목적으로 모두 사용되며, 이 시나리오도 가이드라인에서 다루고 있습니다.
와일드카드 사용 여부와 적절한 와일드카드 타입을 결정할 때 “in” 및 “out” 원칙을 사용할 수 있습니다. 다음 목록은 따라야 할 가이드라인을 제공합니다:
- “in” 변수는
extends키워드를 사용하여 상한 와일드카드로 정의됩니다. - “out” 변수는
super키워드를 사용하여 하한 와일드카드로 정의됩니다. - “in” 변수가
Object클래스에 정의된 메서드를 사용하여 액세스할 수 있는 경우, 제한되지 않은 와일드카드를 사용합니다. - 코드가 변수를 “in” 및 “out” 변수로 모두 액세스해야 하는 경우에는 와일드카드를 사용하지 마세요.
이 가이드라인은 메서드의 리턴 타입에는 적용되지 않습니다. 와일드카드를 리턴 타입으로 사용하면 코드를 사용하는 프로그래머가 와일드카드를 처리해야 하므로 피해야 합니다.
List<? extends ...>로 정의된 리스트는 비공식적으로 읽기 전용으로 생각할 수 있지만, 이는 엄격하게 보장되지 않습니다. 다음과 같은 두 개의 클래스가 있다고 가정해 봅시다:
class NaturalNumber {
private int i;
public NaturalNumber(int i) { this.i = i; }
// ...
}
class EvenNumber extends NaturalNumber {
public EvenNumber(int i) { super(i); }
// ...
}다음 코드를 살펴보세요:
List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35)); // compile-time errorList<EvenNumber>는 List<? extends NaturalNumber>의 하위 유형이므로 le를 ln에 할당할 수 있습니다. 그러나 ln을 사용하여 짝수 목록에 자연수를 추가할 수는 없습니다. 목록에 대해 다음과 같은 연산이 가능합니다:
null을 추가할 수 있습니다.clear()를 호출할 수 있습니다.- 이터레이터를 가져와
remove()를 호출할 수 있습니다. - 와일드카드를 캡처하고 목록에서 읽은 요소를 쓸 수 있습니다.
List<? extends NaturalNumber>로 정의된 목록은 엄밀한 의미에서 읽기 전용이 아니라는 것을 알 수 있지만, 목록에 새 요소를 저장하거나 기존 요소를 변경할 수 없기 때문에 그렇게 생각할 수도 있습니다.