와일드카드는 제네릭 메서드 호출, 제네릭 클래스 인스턴스 생성 또는 상위 타입에 대한 타입 아규먼트로 사용되지 않습니다.
다음 섹션에서는 상한 와일드카드(Upper Bounded Wildcards), 하한 와일드카드(Lower Bounded Wildcards) 및 와일드카드 캡처(Wildcards Capture)를 포함하여 와일드카드에 대해 자세히 설명합니다.
Upper Bounded Wildcards를 선언하려면 와일드카드 문자('?'), extends 키워드, upper bound을 차례로 사용합니다. 이 컨텍스트에서 extends은 일반적인 의미에서 "extends"(클래스에서와 같이) 또는 "implements"(인터페이스에서와 같이)을 의미하는 데 사용됩니다.
Number과 Integer, Double 및 Float와 같은 Number의 하위 타입의 List에서 작동하는 메서드를 작성하려면 List<? extends Number> 코드를 정의합니다.
List<Number>라는 용어는 List<? extneds Number>보다 더 제한적입니다. 전자가 Number 타입의 List에만 일치하는 반면 후자는 Number 타입의 List 또는 해당 하위 클래스와 일치하기 때문입니다.
(그래서 섹션 시작시 언급한 "Upper Bounded Wildcards를 사용하여 변수에 대한 제한을 완화할 수 있습니다" 내용을 이해할 수 있음)
Consider the following process method.
public static void process(List<? extends Foo> list) { /* ... */ }
// foo를 상속한 애들이 List의 element.
upper bounded wildcard, <? extends Foo>, 여기서 Foo는, Foo 및 Foo의 모든 하위 타입과 일치합니다. process 메소드는 Foo 타입으로 List 요소에 액세스할 수 있습니다.
public static void process(List<? extends Foo> list) {
for (Foo elem : list) {
// ...
}
}
foreach 절에서 elem 변수는 list의 각 요소를 반복합니다. 이제 Foo 클래스에 정의된 모든 메서드들은 elem에서 사용할 수 있습니다.
The method returns the sum of the numbers in a list.
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
다음 코드는 Integer 객체 list을 사용하여 sum = 6.0을 print합니다.
List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));
Double value을 요소로 가지는 list은 동일한 sumOfList 메서드를 사용할 수 있습니다.
다음 코드는 sum = 7.0을 print합니다.
List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));
Object 클래스에서 제공하는 기능(method)을 사용하는, 구현될 수 있는 메서드를 작성하는 경우.
코드가 타입 파라미터에 의존하지 않는 제네릭 클래스의 메서드를 사용하는 경우. 예를 들어 List.size 또는 List.clear입니다. 실제로 Class<?>는 Class<T>의 대부분의 메서드가 T에 의존하지 않기 때문에 자주 사용됩니다.
Consider the following method, printList.
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
printList의 목표는 모든 타입의 list을 print 하는 것이지만 목표를 달성하는 데 실패했습니다. Object 인스턴스의 list만 print 합니다.
List<Integer>, List<String>, List<Double> 등은 List<Object>의 하위 타입이 아니기 때문에 print할 수 없습니다.
일반 printList 메소드를 작성하려면 List<?>를 사용하십시오. 와일드카드 사용하면 가능.
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
구체적인 타입 A의 경우, List는 List의 하위 타입이므로 printList를 사용하여 모든 타입의 list를 인쇄할 수 있습니다.
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
Note: Arrays.asList 메서드는 이 단원 전체의 예제에서 사용됩니다. 이 정적 팩터리 메서드는 지정된 배열을 변환하고 고정 크기 list를 반환합니다.
List<Object>와 List<?>는 동일하지 않다는 점에 유의해야 합니다. Object 또는 Object의 하위 타입을 List<Object>에 삽입할 수 있습니다.
그러나 List<?>에는 null만 삽입할 수 있습니다. Guidelines for Wildcard Use 에는 주어진 상황에서 어떤 종류의 와일드카드를 사용해야 하는지 결정하는 방법에 대한 자세한 정보가 있습니다.
Lower Bounded Wildcards는 와일드카드 문자('?'), super 키워드, lower bound을 차례로 사용하여 표현됩니다. <? super A>.
참고: 와일드카드의 upper bound을 지정하거나 lower bound을 지정할 수 있지만 둘 다 지정할 수는 없습니다.
Integer 객체를 List에 넣는 메서드를 작성하고 싶다고 가정해 보겠습니다. 유연성을 극대화하기 위해 메서드가List<Integer>, List<Number> 및 List<Object>— Integer 값을 보유할 수 있는 모든 요소에서 작동하기를 원합니다.
Integer, Number 및 Object와 같은 Integer의 상위 타입 및 Integer list에서 작동하는 메서드를 작성하려면 List<? super Integer>을 지정하면 됩니다.
List<Integer>라는 용어는 List<? super Integer> 보다 더 제한적입니다.
전자는 Integer 타입의 list에만 일치하지만 후자는 Integer의 상위 타입인 모든 타입의 list과 일치하기 때문입니다.
다음 코드는 1에서 10까지의 숫자를 list 끝에 추가합니다.
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
} // i : autoboxing
그러나 와일드카드를 사용하여 제너릭 클래스 또는 인터페이스 간의 관계를 만들 수 있습니다.
다음 두 개의 정규(제네릭이 아닌) 클래스가 주어집니다.
class A { /* ... */ }
class B extends A { /* ... */ }
다음 코드를 작성하는 것은 합리적입니다.
B b = new B();
A a = b;
이 예제는 일반 클래스의 상속이 subtype 지정 규칙을 따른다는 것을 보여줍니다. B가 A를 확장하는 경우, 클래스 B는 클래스 A의 subtype입니다. 이 규칙은 제네릭 유형에는 적용되지 않습니다.
List<B> lb = new ArrayList<>();
ist<A> la = lb; // compile-time error
Integer가 Number의 subtype인 경우 ist<Integer>와 List<Number>사이의 관계는 어떻게 될까요?

Integer는 Number의 subtype이지만 List<Integer>는 List<Number>의 subtype이 아니며 실제로 이 두 type은 관련이 없습니다. List<Number> 및 List<Integer>의 공통 부모는 List<?>입니다.
List<Integer>의 요소를 통해 Number의 메서드에 액세스할 수 있도록 이러한 클래스 간의 관계를 만들려면 upper bounded wildcard를 사용합니다.List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList;
// OK. List<? extends Integer> "is a" subtype of List<? extends Number>
Integer는 Number의 subtype이고 numList는 Number 객체 리스트이므로 이제 intList(Integer 객체 목록)와 numList 간에 관계가 존재합니다.
다음 다이어그램은 upper 및 lower bounded 와일드카드로 선언된 여러 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 예제는 Oracle의 JDK 7 java로 컴파일할 때 다음 오류를 생성합니다.
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
이 샘플에서 코드는 안전한 작업을 수행하려고 시도하므로 컴파일러 오류를 어떻게 해결할 수 있을까요? 와일드카드를 캡쳐하는 private 도우미 메서드를 작성하여 문제를 해결할 수 있습니다.
이 경우 WildcardFixed에 표시된 것처럼 private 헬퍼 메서드인 fooHelper를 생성하여 문제를 해결할 수 있습니다.
public class WildcardFixed {
void foo(List<?> i) {
fooHelper(i);
// fooHelper(i) : generic method.
}
// 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));
}
}
규칙에 따라 도우미 메서드는 일반적으로 originalMethodNameHelper로 이름이 지정됩니다.
도우미 메서드 덕분에 컴파일러는 추론을 사용하여 T가 호출에서 캡쳐 변수인 CAP#1인지 확인합니다. 이제 예제가 성공적으로 컴파일됩니다.
CAP#1은 자바 컴파일러가 제너릭 메서드를 호출할 때 유추한 캡쳐 변수입니다. 캡쳐 변수는 컴파일러가 파라미터화된 타입의 실제 타입을 추론할 때 사용되는 내부적인 변수입니다.
제너릭 메서드를 호출할 때 컴파일러는 유추 과정을 통해 캡쳐 변수를 생성합니다. 이 캡쳐 변수는 제너릭 메서드 내부에서 실제 타입으로 대체되어 사용됩니다.
이를 통해 컴파일러는 호출된 메서드의 타입 아규먼트를 추론하고, 메서드를 올바르게 호출할 수 있도록 합니다. 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>은 모두 ist<? extends Number>에서 Integer 값 리스트에서 항목을 가져와서 Double 값 리스트에 배치하려고 시도하는 것은 분명히 올바르지 않습니다.
Oracle의 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 값 리스트에 배치하려고 시도하는 것은 분명히 올바르지 않습니다.
다음 와일드 카드 샘플 코드를 확인해 보겠습니다.
package com.kitec.wildcard1;
import java.util.Arrays;
import java.util.List;
public class ComparisonUtil {
// void foo(List<?> i) {
// i.set(0, i.get(0)); // 컴파일 에러:
// // List의 element가 와일드 카드<?>(알 수 없는 타입 변수)로
// // 설정되었음.
// // 그래서 컴파일 타임시, 당연히 에러가 발생함.
// // E set(int index, E element);
// // ->2nd Para:E(타입 변수) element
// // : 그러므로 '알 수 없는 타입 변수(?)'와
// // '요소의 타입을 지정하는 타입 변수(E)' 간에 //
// // 불일치.
// // 와일드 카드 캡처 변수?
// }
public static <T> void foo(List<T> list) {
list.set(0, list.get(0));
}
public static void printList(List<?> list) {
// printList(intList); 코드로 인해 printList가 호출되었다면,
// 실제 디버깅하면 element는 Integer 타입임.
// : 이는 `와일드 타입 캡처 변수`(실제 컴파일러가 내부적으로 생성하는 임시 변수)에
// Object를 Integer로 캡처함
// (Integer 타입의 캡처 변수에 element 변수가 캐스팅됨!!!)
for (Object element : list) {
System.out.println(element.getClass());
}
}
// public static <T> void printList(List<T> list) {
// for (T element : list) {
// System.out.println(element.getClass());
// }
// }
// 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
// }
//public static <T> void compare(List<T> list1, List<T> list2) {
//public static <T, X> void compare(List<T> list1, List<X> list2) {
public static void compare(List<?> list1, List<?> list2) {
if (list1.equals(list2)) {
System.out.println("Equal");
} else {
System.out.println("Not Equal");
}
}
public static void main(String[] args) {
List<String> strList = Arrays.asList("Hello", "World");
List<Integer> intList = Arrays.asList(1, 2, 3);
//printList(strList);
printList(intList);
compare(strList, intList); // Not Equal
// List<Double> dblList1 = Arrays.asList(3.14, 2.718);
// List<Double> dblList2 = Arrays.asList(3.14, 2.718);
// compare(dblList1, dblList2); // Equal
}
}
void printCollection(Collection c) {
Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
다음은 제너릭을 사용하여 작성한 코드의 초기 버전입니다. (그리고 새로운 for 루프 구문을 사용했습니다)
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
문제는 이 새로운 버전이 이전 버전보다 훨씬 유용하지 않다는 것입니다. 이전 코드는 어떤 종류의 컬렉션도 파라미터로 사용할 수 있었지만, 새로운 코드는 Collection<Object>만 받아들이기 때문에 우리가 방금 증명한 대로 모든 종류의 컬렉션의 상위 타입이 아닙니다!
모든 종류의 컬렉션의 상위 타입은 Collection<?> (읽는 방식은 "알 수 없는 요소의 컬렉션")입니다. 이는 요소의 타입이 어떤 것이든 상관없이 일치하는 컬렉션을 의미합니다. 이것은 당연한 이유로 와일드카드 타입이라고 불립니다. 다음과 같이 작성할 수 있습니다:
void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}
}
이제 우리는 어떤 타입의 컬렉션에 대해서도 해당 메서드를 호출할 수 있습니다.
printCollection() 내부에서는 여전히 컬렉션 c에서 요소를 읽고 해당 요소를 Object 타입으로 간주할 수 있습니다.
이는 항상 안전하며, 실제로 컬렉션의 유형에 관계없이 객체를 포함하고 있기 때문입니다. 그러나 임의의 객체를 추가하는 것은 안전하지 않습니다.
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
우리는 c의 요소 타입이 무엇인지 알 수 없기 때문에 객체를 추가할 수 없습니다. add() 메서드는 컬렉션의 요소 유형인 E 타입의 인자를 받습니다.
실제 타입 매개변수가 ?인 경우, 이는 알 수 없는 타입을 나타냅니다. add에 전달하는 파라미터는 이 알 수 없는 타입의 하위 타입이어야 합니다. 그러나 어떤 타입인지 모르기 때문에 아무 것도 전달할 수 없습니다.
유일한 예외는 null이며, 이는 모든 타입의 멤버입니다.
반면에 List<?>에서는 get() 메서드를 호출하고 그 결과를 사용할 수 있습니다. 결과 타입은 알 수 없는 타입이지만 항상 객체임을 알 수 있습니다.
따라서 get()의 결과를 Object 타입의 변수에 할당하거나, Object 타입이 예상되는 파라미터로 전달하는 것은 안전합니다.
이 토론의 목적을 위해 변수를 다음 두 기능 중 하나를 제공하는 것으로 생각하는 것이 좋습니다.
- An "In" Variable
"in" 변수는 코드에 데이터를 제공합니다. copy(src, dest)라는 두 개의 아규먼트가 있는 복사 메서드를 상상해 보십시오.
src 아규먼트는 복사할 데이터를 제공하므로 "in" 파라미터입니다.
- An "Out" Variable
"out" 변수는 다른 곳에서 사용할 데이터를 보유합니다. 복사 예제에서 copy(src, dest), dest 아규먼트는 데이터를 허용하므로 "out" 파라미터입니다. (dest는 쓰기전용)
물론 일부 변수는 "in" 및 "out" 목적으로 모두 사용됩니다. 이 시나리오도 지침에서 다룹니다.
와일드카드 사용 여부와 적절한 와일드카드 타입을 결정할 때 "in" 및 "out" 원칙을 사용할 수 있습니다.
다음 목록은 따라야 할 지침을 제공합니다.
Wildcard Guidelines
- "in" 변수는 extends 키워드를 사용하여 upper bounded 와일드카드로 정의됩니다.
- "out" 변수는 super 키워드를 사용하여 lower bounded 와일드카드로 정의됩니다.
- Object 클래스에 정의된 메서드를 사용하여 "in" 변수에 액세스할 수 있는 경우 unbounded 와일드카드를 사용합니다.
- 코드가 "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); }
// ...
}
The following code
List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35)); // compile-time error
List<EvenNumber>는 List<? extends NaturalNumber>의 subtype이기 때문에, ln에 le를 할당할 수 있습니다.
그러나 ln을 사용하여 짝수 리스트에 NaturalNumber를 추가할 수는 없습니다.
이 리스트에서 다음 작업이 가능합니다.
null을 추가할 수 있습니다.
clear를 호출할 수 있습니다.
iterator을 얻을 수 있고 remove를 호출할 수 있습니다.
wildcard을 캡쳐할 수 있고 write elements that you've read from the list.
List<? extends NaturalNumber>에 의해 정의된 list는 엄밀한 의미에서 읽기 전용이 아니지만 목록에서 새 요소를 저장하거나 기존 요소를 변경할 수 없기 때문에 그렇게 생각할 수 있습니다.