Jenerics 3
Wildcards
일반 코드에서 와일드카드라고 하는 물음표(?)는 알 수 없는 타입을 나타냅니다. 와일드카드는 다양한 상황에서 사용할 수 있습니다. 때로는 리턴 타입으로 사용됩니다(좀 더 구체적으로 프로그래밍하는 것이 더 나은 방법이긴 하지만).
와일드카드는 제네릭 메서드 호출, 제네릭 클래스 인스턴스 생성 또는 상위 타입에 대한 타입 아규먼트로 사용되지 않습니다.
다음 섹션에서는
- 상한 와일드카드(Upper Bounded Wildcards)
- 하한 와일드카드(Lower Bounded Wildcards)
- 와일드카드 캡처(Wildcards Capture)
를 포함하여 와일드카드에 대해 자세히 설명합니다.
Upper Bounded Wildcards
Upper Bounded Wildcards를 사용하여 변수에 대한 제한을 완화할 수 있습니다.
예를 들어 List<Integer>, List<Double> 및 List<Number>에서 작동하는 메서드를 작성한다고 가정합니다.
상한 와일드카드를 사용하여 이를 달성할 수 있습니다.
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) { /* ... */ }
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 sumOfList 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을 콘솔에 출력합니다.
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));
Unbounded Wildcards
unbounded 와일드카드 타입은 와일드카드 문자(?)를 사용하여 지정됩니다(예: List<?>). 이를 알 수 없는 타입의 list라고 합니다. unbounded 와일드카드는 유용한 접근 방식인 두 가지 시나리오가 있습니다.
- Object 클래스에서 제공하는 기능을 사용하는, 구현될 수 있는 메서드를 작성하는 경우.
- 코드가 타입 파라미터에 의존하지 않는 제네릭 클래스의 메서드를 사용하는 경우.
- 예를 들어 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<?>는 동일하지 않다는 점에 유의해야 합니다.
List<Object>에 Object 또는 Object의 하위 타입을 삽입할 수 있습니다.
그러나 List<?>에는 null만 삽입할 수 있습니다.
Guidelines for Wildcard Use 에는 주어진 상황에서 어떤 종류의 와일드카드를 사용해야 하는지 결정하는 방법에 대한 자세한 정보가 있습니다.
Lower Bounded Wildcards
상한 와일드카드 섹션에서는 상한 와일드카드가 알 수 없는 타입을 특정 타입 또는 해당 타입의 하위 타입으로 제한하고 extends 키워드를 사용하여 표시됨을 보여줍니다.
비슷한 방식으로 Lower Bounded Wildcards는 알 수 없는 타입을 특정 타입 또는 해당 타입의 상위 타입으로 제한합니다.
Lower Bounded Wildcards는 와일드카드 문자('?'), super 키워드, lower bound을 차례로 사용하여 표현됩니다. <? super A>.
참고: 와일드카드의 upper bound을 지정하거나 lower bound을 지정할 수 있지만 둘 다 지정할 수는 없습니다.
// 잘못된 코드 예시
public void invalidMethod(List<? extends Number super Integer> list) {
// ...
}
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);
}
}
Wildcards and Subtyping
Generics, Inheritance, and Subtypes에서 설명한 것처럼 제네릭 클래스 또는 인터페이스는 단순히 타입간에 관계가 있기 때문에 서로 관련이 없습니다.
그러나 와일드카드를 사용하여 제너릭 클래스 또는 인터페이스 간의 관계를 만들 수 있습니다.
다음 두 개의 정규(제네릭이 아닌) 클래스가 주어집니다.
class A { /* ... */ }
class B extends A { /* ... */ }
다음 코드를 작성하는 것은 합리적입니다.
B b = new B();
A a = b;
이 예제는 일반 클래스의 상속이 subtype 지정 규칙을 따른다는 것을 보여줍니다. B가 A를 확장하는 경우, 클래스 B는 클래스 A의 subtype입니다. 이 규칙은 제네릭 유형에는 적용되지 않습니다.
List<B> lb = new ArrayList<>();
List<A> la = lb; // compile-time error
Integer가 Number의 subtype인 경우 List<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 클래스 간의 관계를 보여줍니다.

Wildcard Capture and Helper Methods
어떤 경우에는 컴파일러가 와일드카드 타입을 유추합니다.
예를 들어 List은 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 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
이 샘플에서 코드는 안전한 작업을 수행하려고 시도하므로 컴파일러 오류를 어떻게 해결할 수 있을까요? 와일드카드를 캡쳐하는 private 도우미 메서드를 작성하여 문제를 해결할 수 있습니다. 이 경우 WildcardFixed에 표시된 것처럼 private 헬퍼 메서드인 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));
}
}
규칙에 따라 도우미 메서드는 일반적으로 originalMethodNameHelper로 이름이 지정됩니다.
도우미 메서드 덕분에 컴파일러는 추론을 사용하여 T가 호출에서 캡쳐 변수인 CAP#1인지 확인합니다. 이제 예제가 성공적으로 컴파일됩니다.
CAP#1은 자바 컴파일러가 제너릭 메서드를 호출할 때 유추한 캡쳐 변수입니다.
캡쳐 변수는 컴파일러가 파라미터화된 타입의 실제 타입을 추론할 때 사용되는 내부적인 변수입니다.
제너릭 메서드를 호출할 때 컴파일러는 유추 과정을 통해 캡쳐 변수를 생성합니다.
이 캡쳐 변수는 제너릭 메서드 내부에서 실제 타입으로 대체되어 사용됩니다.
이를 통해 컴파일러는 호출된 메서드의 타입 아규먼트를 추론하고, 메서드를 올바르게 호출할 수 있도록 합니다. CAP#1은 일반적으로 컴파일러 내부에서 사용되는 임시적인 이름으로, 프로그래머가 직접 사용하거나 참조할 수 없습니다. 컴파일러는 유추한 캡처 변수를 내부적으로 처리하여 제너릭 메서드를 호출하는 데 사용합니다.
capture#는 자바 컴파일러가 내부적으로 사용하는 메커니즘으로, 와일드카드 타입(?)을 다룰 때 발생하는 타입 불확정성을 처리하기 위해 사용됩니다. 이것은 와일드카드에 의해 소개된 "알 수 없는 타입"을 일시적으로 "캡쳐"하고, 각각의 와일드카드 사용에 대해 고유한 참조 타입을 생성하는 과정을 나타냅니다.
capture#의 이해
1. 와일드카드 사용시 타입 불확정성: 와일드카드 ?를 사용하는 제네릭 타입은 구체적인 타입을 지정하지 않습니다. 이 때문에, 해당 타입이 무엇인지 컴파일러는 알 수 없습니다.
2. 타입 캡쳐 (Type Capture): 컴파일러는 각 와일드카드 사용 시점에 대해 일시적인 참조 타입을 생성합니다.
이를 타입 캡쳐라고 하며, 이렇게 생성된 타입을, capture#1-of ?, capture#2-of ? 등으로 표현합니다. 각 capture#는 독립적인 타입을 나타내며, 서로 다른 와일드카드 사용 사례에 대해 서로 다른 타입을 나타냅니다.
3. 타입 안전성 보장: 이러한 메커니즘은 프로그램의 타입 안전성을 보장하기 위해 필요합니다. 와일드카드를 사용하는 제네릭 타입에 대해 너무 자유롭게 작업을 허용하면, 타입 불일치로 인한 런타임 오류의 위험이 증가합니다. capture# 메커니즘은 각 와일드카드 사용을 별개의 타입으로 취급하여, 타입 안전성을 위반할 수 있는 연산을 컴파일 시점에 차단합니다.
규칙에 따라 도우미 메서드는 일반적으로 originalMethodNameHelper로 이름이 지정됩니다.
위의 WildcardFixed 클래스에서 사용된 해결 방법은 자바의 제네릭 타입 추론과 와일드카드 캡처를 활용한 것입니다.
이 방법은 와일드카드를 사용하는 리스트에서 특정 작업을 수행할 때 자주 사용되는 패턴입니다.
<해결 방법의 이유>
1. 와일드카드의 제한:
List<?> 타입의 리스트는 "알 수 없는 타입의 엘리먼트를 가지는 리스트"를 의미합니다.
직접적으로 이 리스트에 대해 set 같은 수정 작업을 수행할 수 없습니다.
이는 와일드카드를 사용한 타입의 불확정성 때문입니다.
2. 타입 추론을 이용한 Helper 메소드:
fooHelper 메소드는 제네릭 타입 T를 사용하는 List<T>를 매개변수로 받습니다.
이 메소드를 호출할 때,
자바 컴파일러는 전달된 실제 리스트의 타입 아규먼트를 기반으로 T의 구체적인 타입을 추론할 수 있습니다.
foo 메소드에서 fooHelper(i)를 호출하면, 컴파일러는 i의 실제 타입에 대응하는 T를 추론합니다.
이로 인해 와일드카드가 사용된 리스트에 대한 타입 불확정성 문제를 우회할 수 있습니다.
3. 타입 안전성 유지:
fooHelper 내에서는 T 타입의 명확한 참조를 가지고 있기 때문에,
리스트의 요소를 안전하게 다룰 수 있습니다. l.set(0, l.get(0)) 구문은
이제 타입 안전성을 위반하지 않으며,컴파일러 에러가 발생하지 않습니다.
결론
WildcardFixed 클래스에서의 해결 방법은 와일드카드를 사용하는 리스트에 대해 안전하게 작업을 수행하기 위한
일반적인 패턴입니다.
이는 제네릭 타입 추론과 함께 와일드카드 캡처를 효과적으로 사용하여,
리스트의 요소를 수정하는 작업을 타입 안전하게 만듭니다.
이 패턴은 와일드카드와 제네릭을 사용하는 복잡한 상황에서 타입 안전성을 유지하는 데 매우 유용합니다.
이제 더 복잡한 예인 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 값 리스트에 배치하려고 시도하는 것은 분명히 올바르지 않습니다.
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
}
}
Wildcards
컬렉션의 모든 요소를 출력하는 루틴을 작성하는 문제를 생각해 보십시오. 이전 버전의 언어(즉, 5.0 이전 릴리스)에서 작성하는 방법은 다음과 같습니다.
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 타입이 예상되는 파라미터로 전달하는 것은 안전합니다.
Guidelines for Wildcard Use
제네릭으로 프로그래밍하는 방법을 배울 때 더 혼란스러운 측면 중 하나는 upper bounded 와일드카드를 사용할 시기와 lower bounded 와일드카드를 사용할 시기를 결정하는 것입니다. 이 페이지에서는 코드를 디자인할 때 따라야 할 몇 가지 지침을 제공합니다.
이 토론의 목적을 위해 변수를 다음 두 기능 중 하나를 제공하는 것으로 생각하는 것이 좋습니다.
An In Variable
in 변수는 코드에 데이터를 제공합니다. copy(src, dest)라는 두 개의 아규먼트가 있는 복사 메서드를 상상해 보십시오. src 아규먼트는 복사할 데이터를 제공하므로 in 파라미터입니다.
An Out Variable
out 변수는 다른 곳에서 사용할 데이터를 보유합니다. 복사 예제에서 copy(src, dest), dest 아규먼트는 데이터를 허용하므로 out 파라미터입니다.
물론 일부 변수는 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을 사용하여 짝수 List에 NaturalNumber를 추가할 수는 없습니다. 이 List에서 다음 작업이 가능합니다.
- null을 추가할 수 있습니다.
- clear를 호출할 수 있습니다.
- iterator을 얻을 수 있고 remove를 호출할 수 있습니다.
- wildcard을 캡쳐할 수 있고 write elements that you've read from the list.
List<? extends NaturalNumber>에 의해 정의된 List는 엄밀한 의미에서 읽기 전용이 아니지만 List에서 새 요소를 저장하거나 기존 요소를 변경할 수 없기 때문에 그렇게 생각할 수 있습니다.