https://sundaland.tistory.com/162
[ ▶ 와일드카드 (Wildcards) ]
일반 코드에서 와일드카드라고 하는 물음표(?)는 알 수 없는 타입을 나타낸다. 와일드카드는 다양한 상황에서 사용할 수 있다. 때로는 리턴 타입으로 사용된다.
와일드카드는 제너릭 메서드 호출, 제너릭 클래스 인스턴스 생성 또는 상위 타입에 대한 타입 아규먼트로 사용되지 않는다.
[ ▷ 상한 와일드카드 (Upper Bounded) ]
상한 와일드카드를 사용하여 변수에 대한 제한을 완화할 수 있다. 예를 들어 List<Integer>, List<Double> 및 List<Number>에서 작동하는 메서드를 작성한다고 가정한다.
상한 와일드카드를 선언하려면 와일드카드 문자 (?), extends 키워드, upper bound를 차례로 사용한다. 이 컨텍스트에서 extends는 일반적인 의미에서 extends 또는 implements를 의미하는데 사용된다.
Number과 Integer, Double 및 Float와 같은 Number의 하위 타입의 List에서 작동하는 메서드를 작성하려면 List<? extends Number> 보다 더 제한적이다.
전자가 Number 타입의 List에만 일치하는 반면 후자는 Number 타입의 List 또는 해당 하위 클래스와 일치하기 때문이다.
<T extends Number> 같은 경우에는 T는 전자의 타입 파라미터 제한에 적합한 타입 아규먼트가 제공되면, T는 특정 타입으로 특정된다.
- 제너릭 타입 파라미터 T가 Number 클래스 또는 그 하위 클래스여야 한다는 것을 의미한다.
- 타입 파라미터 선언은 제너릭 클래스나 메서드에서 사용된다.
- T는 특정 타입으로 결정된다. Number을 상속한 클래스 중 하나로 결정된다.
- 클래스나 메서드 내부에서 T를 특정 타입으로 안전하게 사용할 수 있다.
class Box<T extends Number> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
Box<Integer>, Box<Double> 등으로 사용할 수 있으며, T는 Integer 또는 Double로 특정된다.
<? extends Number> 같은 경우에는 Number를 상속한 서브클래스들 중, 하나로 특정되지 않고, 타입 아규먼트로 Number, Byte, Integer, Long, Short, Double, Float 클래스와 같은 서브클래스들이 될 수 있는 것을 의마한다.
- 와일드카드 타입으로 Number 클래스 또는 그 하위 클래스여야 한다는 것을 의미한다.
- ? 는 특정되지 않은 타입을 나타내며, Number의 하위 클래스들 중 하나일 수 있다.
- 와일드카드 타입은 유연하지만, 읽기 전용으로 사용되는 경우가 많다. 값을 추가하는 등의 작업에 제한이 있다.
public void processNumbers(List<? extends Number> numbers) {
for (Number number : numbers) {
System.out.println(number);
}
}
타입 파라미터
- 제너릭 타입 파라미터를 선언할 때 사용되며, T는 특정 타입으로 결정된다.
- T는 특정 타입이므로, 타입 안정성을 제공한다.
- 제너릭 클래스나 메서드의 정의에서 사용된다.
와일드카드
- 특정되지 않은 Number의 서브클래스 중 하나를 의미한다.
- 특정되지 않은 타입이기에 유연성을 제공하지만 일반적으론 읽기 전용으로 사용된다.
- 메서드 파라미터나 컬랙션 타입에서 사용된다.
따라서 전자는 제너릭 타입 파라미터를 특정 타입으로 제한하고, 후자는 특정되지 않은 Number의 하위 타입을 유연하게 받을 수 있다는 차이가 있다.
public static void process(List<? extends Foo> list) { /* ... */ }
상한 와일드카드 <? extends Foo>, 여기서 Foo는 Foo 및 Foo의 모든 하위 타입과 일치한다.
즉, process 메서드는 Foo 타입으로 List의 엘리먼트에 액세스할 수 있다.
import java.util.ArrayList;
import java.util.List;
class Foo {
private String name;
public Foo(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Bar extends Foo {
public Bar(String name) {
super(name);
}
}
public class Main {
public static void process(List<? extends Foo> list) {
for (Foo elem : list) {
System.out.println("Processing: " + elem.getName());
}
}
public static void main(String[] args) {
List<Bar> barList = new ArrayList<>();
barList.add(new Bar("Bar1"));
barList.add(new Bar("Bar2"));
barList.add(new Bar("Bar3"));
process(barList); // Using the process method with a List of Bar
}
}
foreach 절에서 elem 변수는 list의 각 엘리먼트를 반복한다. 이제 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 객체 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을 출력한다.
List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));
[ ▷ Unbounded 와일드카드 ]
와일드카드 문자 (?)를 사용하여 지정된다. 이를 알 수 없는 타입의 list 라고 한다. 언바운드 와일드카느는 유용한 접근 방식인 두 가지 시나리오가 있다.
- Object 클래스에서 제공하는 기능을 사용하여 구현할 수 있는 메서드를 작성하는 경우.
- 코드가 타입 파라미터에 의존하지 않는 제너릭 클래스의 메서드를 사용하는 경우.
예를 들어가 List.size 또는 List.clear 가 있다.
실제로 Class<?>는 Class<T>의 대부분의 메서드가 T에 의존하지 않기 때문에 자주 사용된다.
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);
List<Object> 와 List<?> 는 동일하지 않다.
List<Object> 에 Object 또는 Object의 하위 타입을 삽입할 수 있다.
그러나 List<?> 에는 null만 삽입할 수 있다.
import java.util.ArrayList;
import java.util.List;
public class WildcardExample {
public static void main(String[] args) {
List<?> wildcardList = new ArrayList<Integer>();
// null 삽입은 허용됩니다.
wildcardList.add(null);
// 다른 객체 삽입 시도 (컴파일 오류 발생)
// wildcardList.add("Hello"); // 컴파일 오류
// wildcardList.add(123); // 컴파일 오류
System.out.println("List contains: " + wildcardList);
}
}
List는 동일한 데이터 타입의 엘리먼트들을 그룹화하기 때문이다.
[ ▷ 하한 와일드카드 (Lower) ]
알 수 없는 타입을 특정 타입 또는 해당 타입의 상위 타입으로 제한한다.
와일드카드 (?), super 키워드, lower bound를 차례로 사용하여 표현된다.
<? super A>
위 코드는 A 클래스 (A 클래스 포함)의 상속 계층의 클래스들이 ? 에 해당한다.
# 상한 와일드카드와 하한 와일드카드는 동시에 사용할 수 없다.
Integer 객체를 List에 넣는 메서드를 작성할때 유연성을 극대화하기 위해 메서드가 List<Integer>,List<Number> 및 List<Object> ... Integer 값을 보유할 수 있는 모든 엘리터먼트에서 작동하게 만드려고 한다,
List<? super Integer> 를 사용하면 된다.
Integer, Number, Object와 같은 Integer의 상위 타입 및 Integer list에서 작동하는 메서드를 작성하려면
List<Integer> 는 List<? super Integer> 보다 더 제한적이다. 전자는 Integer 타입의 list에만 일치하지만 후자는 Integer의 상위 타입인 모든 타입의 list와 일치하기 때문이다.
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
public static void main(String[] args) {
// Example with List<Integer>
List<Integer> integerList = new ArrayList<>();
addNumbers(integerList);
System.out.println("Integer list: " + integerList);
// Example with List<Number>
List<Number> numberList = new ArrayList<>();
addNumbers(numberList);
System.out.println("Number list: " + numberList);
// Example with List<Object>
List<Object> objectList = new ArrayList<>();
addNumbers(objectList);
System.out.println("Object list: " + objectList);
// Example with List<Comparable>
List<Comparable> comparableList = new ArrayList<>();
addNumbers(comparableList);
System.out.println("Comparable list: " + comparableList);
// Example with LinkedList<Object>
List<Object> linkedObjectList = new LinkedList<>();
addNumbers(linkedObjectList);
System.out.println("Linked Object list: " + linkedObjectList);
}
}
와일드카드와 서브타입
일반적으로 클래스의 상속은 subtype 지정 규칙을 따른다. 클래스 B가 클래스 A를 상속하는 경우 클래스 B는 클래스 A의 서브타입이다.
class A { /* ... */ }
class B extends A { /* ... */ }
B b = new B();
A a = b;
하지만 이 규칙은 제너릭 유형에는 적용되지 않는다.
List<B> lb = new ArrayList<>();
List<A> la = lb; // compile-time error
Integer는 Number의 서브 타입이자만 List<Integer>는 List<Number>의 서브타입이 아니며 실제로는 이 두 타입은 관련이 없다.
이 둘의 공통 부모는 List<?>이다.
코드가 List<Integer>의 엘리먼트를 통해 Number의 메서드에 액세스할 수 있도록 이러한 클래스가 간의 관계를 만드려면 상한 와일드카드를 사용한다.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
// Create a list of Integer
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
integerList.add(4);
integerList.add(5);
// Assign integerList to List<? extends Integer>
List<? extends Integer> intList = integerList;
// Assign intList to List<? extends Number>
List<? extends Number> numList = intList;
// OK. List<? extends Integer> is a subtype of List<? extends Number>
// Read elements from numList
for (Number num : numList) {
System.out.println("Number: " + num);
}
// Demonstrating that we cannot add elements to intList or numList
// intList.add(6); // Compile error: cannot add element to List<? extends Integer>
// numList.add(6); // Compile error: cannot add element to List<? extends Number>
// However, we can still read elements as Number
Number firstNumber = numList.get(0);
System.out.println("First number: " + firstNumber);
// Creating a list of Double to demonstrate more flexibility with Number
List<Double> doubleList = new ArrayList<>();
doubleList.add(1.1);
doubleList.add(2.2);
doubleList.add(3.3);
// Assign doubleList to List<? extends Number>
List<? extends Number> anotherNumList = doubleList;
// Read elements from anotherNumList
for (Number num : anotherNumList) {
System.out.println("Number from anotherNumList: " + num);
}
}
}
[ ▷ 와일드카드 캡쳐와 헬퍼 메서드 (Wildcard Capture and Helper Methods) ]
어떤 경우에는 컴파일러가 와일드카드 타입을 유추한다.
예를 들어 List는 List<?>로 정의될 수 있지만, 표현식을 실행할 때 컴파일러는 코드 특정 타입을 유추한다.
이 시나리오를 와일드카드 캡쳐라고 한다.
대부분의 경우 capture of라는 문구가 포함된 오류 메시지가 표시되는 경우를 제외하고는 걱정할 필요가 없다.
다음 예제 코드는 와일드카드 캡처 오류를 생성한다.
import java.util.List;
public class WildcardError {
void foo(List<?> i) {
i.set(0, i.get(0));
}
}
컴파일러는 i 입력 파라미터를 타입 소거를 적용하여 Object 타입으로 처리한다.
foo 메서드가 List.set(int, E)를 호출하면 컴파일러는 리스트에 삽입되는 객체의 타입을 확인할 수 없으며 오류가 발생한다.
컴파일러가 프로그래머가 잘못된 타입을 변수에 지정하고 있다고 믿는다는 의미이다.
즉, 와일드카드 캡쳐는 컴파일시 타입 안정성을 강화하기 위해서다.
코드는 안전한 작업을 수행히려고 시도하므로 컴파일러 오류는 와일드카드를 캡쳐하는 private 헬퍼 메서드를 작성하여 문제를 해결할 수 있다.
아래 예제는 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(1)); // 테스트를 위해 0번째 엘리먼트를 1번째 엘리먼트로 set하는 코드로 변경함
}
public static void main(String[] args) {
WildcardError wildcardError = new WildcardError();
// Integer 타입의 리스트 생성 및 초기화
List<Integer> intList = new ArrayList<>();
intList.add(42); // 첫 번째 요소
intList.add(15); // 두 번째 요소
System.out.println("Before foo: " + intList);
wildcardError.foo(intList);
System.out.println("After foo: " + intList);
// String 타입의 리스트 생성 및 초기화
List<String> strList = new ArrayList<>();
strList.add("Hello"); // 첫 번째 요소
strList.add("World"); // 두 번째 요소
System.out.println("Before foo: " + strList);
wildcardError.foo(strList);
System.out.println("After foo: " + strList);
}
}
규칙에 따라 헬퍼 메스드는 일반적으로 [메서드 이름]Helper로 이름이 지정된다.
헬퍼 메서드 덕에 컴파일러는 추론을 사용하여 T가 호출에서 캡쳐 변수인 CAP#1 인지 확인한다.
위의 WildcarFixed 클래스에서 사용된 해결 방법은 자바의 제너릭 타입 추론과 와일드카드 캡쳐를 활용한 것이다.
이 방법은 주로 와일드 카드를 사용하는 리스트에서 특정 작업을 수행할 때 자주 사용되는 패턴이다.
- 와일드 카드의 제한 : List<?> 타입의 리스트는 알 수 없는 타입의 앨리먼트를 가지는 리스트를 의미한다. 직접적으로 이 리스트에 대해 set 같은 수정 작업을 수행할 수 없다. 이는 와일드카드를 사용한 타입의 불확정성 때문이다.
- 타입 추론을 이용한 헬퍼 메서드 : fooHelpler 메서드는 제너릭 타입 T를 사용하는 List<T>를 매게변수로 받는다. 이 메서드를 호출할 때, 자바 컴파일러는 전달된 실제 리스트의 타입 아규먼트를 기반으로 T의 구체적인 타입을 추론한다. foo 메서드에서 fooHelper(i)를 호출하면 컴퍼일러는 i의 실제 타입에 대응하는 T를 추론한다. 이로 인해 와일드카드가 사용된 리스트에 대한 타입 불확정성 문제를 우회할 수 있다.
- 타입 안정성 유지 : fooHelper 내에서는 T 타입의 명확한 참조를 가지고 있기 때문에, 리스트의 요소를 안전하게 다룰 수 있다. l.set(0, l.get(0)) 구문은 이제 타입 안정성을 위반하지 않으며, 컴파일 에러가 발생하지 않는다.
결론 : 해당 해결 방법은 와일드카드를 사용하는 리스트에 대해 안전하게 작업을 수행하기 위한 일반적인 패턴이다.
이제 제너릭 타입 추론과 함께 와일드카드 캡쳐를 효과적으로 사용하여, 리스트의 요소를 수정하는 작업을 타입을 안전하게 만든다.
이 패턴은 와일드카드와 제너릭을 사용하는 복잡한 상황에서 타입 안전성을 유지하는데 매우 유용하다.
CAP#1은 자바 컴파일러가 제너릭 메서드를 호출할 때 유추한 캡쳐 변수이다.
캡쳐 변수는 컴파일러가 파라미터화된 타입의 실제 타입을 추론할 때 사용되는 내부적인 변수이다.
제너릭 메서드를 호출할 때 컴파일러는 유추과정을 통해 캡쳐 변수를 생성한다.
이 캡쳐 변수는 제너릭 메서드 내부에서 실제 타입으로 대체되어서 사용된다.
이를 통해 컴파일러는 호출된 메서드의 타입 아규먼트를 추론하고, 메서드를 올바르게 호출할 수 있도록 한다.
CAP#1은 일반적으로 컴파일러 내부에서 사용되는 임시적인 이름으로, 프로그래머가 직접 사용하거나 참조할 수 없다.
컴파일러는 유츄한 캡쳐 변수를 내부적으로 처리하여 제너릭 메서드를 호출하는데 사용한다.
capture#는 자바 컴파일러가 내부적으로 사용하는 메커니즘으로, 와일드카드 타입 (?)을 다룰때 발생하는 타입 불확정성을 처리하기 위해 사용된다. 이것은 와일드카드에 의해 소개된 "알 수 없는 타입"을 일시적으로 캡쳐하고, 각각의 와일드카드 사용에 대해 고유한 참조 타임을 생성하는 과정을 나타낸다.
List<?>와 같은 와일드카드 타입은 어떤 타입의 요소가 들어갈지 알 수 없다. 컴파일러는 이 알 수 없는 타입이 사용될 때마다 고유한 참조 타입을 만들어내고, 이를 통해 타입 검사를 수행한다.
- 와일드카드 사용시 타입 불확정성 : 와일드카드 (?)를 사용하는 제너릭 타입은 구체적인 타입을 지정하지 않는다. 그렇기에 해당 타입이 무엇인지 컴파이러는 알 수 없다.
- 타임 캡쳐 : 컴파일러는 각 와일드카드 사용 시점에 대해 일시적인 참조 타입을 생성한다, 이렇게 생성된 타입을, catpure#1-of, capture#2-of 등으로 표현한다. 각 capture#는 독립적인 타입을 나타내며, 서로 다른 와일드카드 사용 사레에 대해 서로 다른 타입을 나타낸다.
- 타입 안정성 보장 : 이러한 매커니즘은 프로그램의 타입 안정성을 보장하기 위해 필요하다. 와일드카드를 사용하는 제너릭 타입에 대해 너무 자유롭게 작업을 허용하면, 타입 불일치로 인한 런타임 오류의 위험이 증가한다. capture# 메커니즘은 각 와일드카드 사용을 별개의 타입으로 취급하여, 타입 안정성을 위반할 수 있는 연산을 컴파일 시점에서 차단한다.
다음 코드는 더 복잡한 예시이다.
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
}
}
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 Numbers> 에서 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
}
}
[ ▷ 와일드카드 ]
다음 코드는 자바 5.0 이전 버전에 작성된 컬렉션의 모든 엘리먼트를 출력하는 코드이다.
void printCollection(Collection c) {
Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
아래 코드는 제너릭을 사용하여 작성한 코드의 초기 버전이다.
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
제너릭을 사용하였지만 이전 버전보다 훨씬 유용하지 않다. 이전 코드는 어떤 종류의 컬렉션도 파라미터로 사용할 수 있었지만, 새로운 코드는 Collection<Object>만 받아들이기 때문에 우리가 방금 증명한 대로 이는 모든 종류의 컬렉션의 상위 타입이 아니다.
모든 종류의 컬렉션의 상위 타입은 Colletcion<?> (알 수 없는 엘리먼트의 컬랙션)이다.
이것은 와일드카드 타입이라고 부르며 다음과 같이 작성이 가능하다.
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은 가능하다. 모든 타입의 멤버이기 때문이다.
null의 어떤 특정 타입의 객체가 아니며, 모든 참조 타입에 대한 디폴트 값이기 때문에 설명하였다.
List<?>에서는 get() 메서드를 호출하고 그 결과를 사용할 수 있다. 결과 타입은 알 수 없는 타입이지만 항상 객체임을 알수 있기 때문이다. 따라서 get()의 결과를 Object 타입의 변수이 할당하거나, Object 타입이 예상되는 파라미터로 전달하는 것은 안전하다.
[ ▷ 와일드카드 사용 가이드 ]
An In Variable
in 변수는 코드에 데이터를 제공한다. copy(src, dest)라는 두 개의 아규먼트가 있는 복사 메서드에서 src 아규먼트는 복사할 데이터를 제공하므로 in 파라미터이다.
An Out Variable
out 변수는 다른 곳에서 사용할 데이터를 보유한다. 복사 메서드 copy(src, dest)에서 dest 아규먼트는 데이터를 허용하므로 out 파라미터이다.
# 일부 변수는 in 및 out 목적으로 둘다 사용된다.
와일드 카드 사용 여부와 적절한 와일드카드 타입을 결정할 때 in 및 out 원칙을 사용할 수 있다.
- in 변수는 extends 키워드를 사용하여 상한 와일드카드로 정의된다.
- out 변수는 super 키워드를 사용하여 하한 와일드카드로 정의된다.
- Object 클래스에 정의된 메서드를 사용하여 in 변수에 액세스할 수 있는 경우 unbounded 와일드카드를 사용한다.
- 코드가 in 및 out 변수로 변수에 액세서해야 하는 경우 와일드카드를 사용하면 안된다.
위 지침은 메서드의 리턴 타입에 적용되지 않는다. 리턴 타입으로 와일드카드를 사용하는 것은 해당 메서드를 사용하는 프로그래머가 와일드카를 처리하도록 강제하므로 피해야 한다.
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 error
List<EvenNumber>는 List<? extends NaturalNumber>의 서브 타입이기 때문에, ln에 le를 할당할 수 있다.
그러나 ln을 사용해 짝수 NaturalNumber를 추가할 수는 없다.
해당 리스트에서는 다음 작업이 가능하다.
- null을 추가할 수 있다.
- clear를 호출할 수 있다.
- iterator를 얻을 수 있고 remove를 호출할 수 있다.
- wildcard를 캡쳐할 수 있고 리스트에서 읽은 엘리먼트를 쓸 수 있다.
해당 List는 읽기 전용이 아니지만, List에서 새 요소를 저장하거나 기존 요소를 변경할 수 없기 때문에 그렇게 생각할 수 있다.
'자바 튜토리얼' 카테고리의 다른 글
Collections [1] (0) | 2024.07.19 |
---|---|
제너릭 (Generics) [4] (0) | 2024.07.18 |
제너릭 (Generics) [2] (0) | 2024.07.16 |
제너릭 (Generics) [1] (0) | 2024.07.16 |
인터페이스와 상속 (Interface and Inheritance) [2] (0) | 2024.07.12 |