Wildcards
제네릭 코드에서는 wildcard로 불리는 question mark ?로 알 수 없는 타입(unKnown Type)을 표현한다.
사용이 되는 부분과 안되는 부분은 실제 사용을 해보면서 익혀야한다. 다양한 곳에서 사용가능하다.
(
In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.
)
Upper Bounded Wildcards
어떤 타입을 사용할 지는 모르겠지만 그 타입에 대한 상한선을 지정하는 방법이 있다.
예를 들어 List< T > 를 정하는데 T가 Integer, Dobule, Number으로 사용을 제한하고 싶다면 List < ? extends Number>
아래와 같이 쓸 수 있다.
public static void process(List<? extends Foo> list) {
/* ... */
}
아래 예제를 참고하라.
public static double sumOfList(List<? extends Number> list)
{
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
sumOfList는 Number타입의 어떤 것이라도 받아서 double로 처리하고 멤버 s에 누적하여 double을 리턴하는 함수다.
아래와같이 사용하면 double을 출력한다.
Unbounded Wildcards
타입에 상관하지 않은 unbounded wild card type List는 List<?>로 쓸 수 있다.
1> Object Class에 제공되있는 기능을 사용하여 메소드를 구현하고자한다.
2> 타입 파라미터에 대해 의존하지않는 제네릭 클래스에서 메소드를 사용할 때. 예를 들어 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();
}
위 예제는 목적에 맞게 동작하지않는다. List<Object>는 오직 Object만 인스턴스 해서 리스트를 만들 수 있기 때문이다. 따라서 List<String>, List<Integer>와 같은 것을 프린트 하지못한다. 이것들은 List<Object>의 하위타입이 아니기때문이다(이전 generic-1 설명 참고)
따라서 제네릭한 printList를 만들기위해서는 List<?>를 사용하여야한다.
public static void printList(List<?> list)
{
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
위의 코드를 아래와 같이 호출하면 우리가 원하던 대로 동작함을 확인할 수 있다.
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li); printList(ls);
(
It's important to note that List<Object> and List<?> are not the same. You can insert an Object, or any subtype of Object, into a List<Object>. But you can only insert null into a List<?>. The Guidelines for Wildcard Use section has more information on how to determine what kind of wildcard, if any, should be used in a given situation
)
Lower Bounded Wildcards
upperBound wildcard (ex : List<? extends Foo> list)는 알 수 없는 타입을 어떤 타입의 하위타입으로 두거나 특정한 타입으로 두는 것으로 제한하는 것을 위에서 보았다. 마찬가지 방법으로 lower bound wildcard는 해당 타입의 super 타입이나 특정한 타입으로 알 수 없는 타입을 제한한다.
lower bound는 <? super A>로 쓸 수 있다
upper bound와 lower bound는 동시에 특정할 수는 없다
아래와 같이 list를 lower bound화 하면 list에 들어가는 타입은 적어도 integer가 된다는 것을 알 수 있다.
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
(
To write the method that works on lists of Integer and the supertypes of Integer, such as Integer, Number, and Object, you would specify List<? super Integer>. The term List<Integer> is more restrictive than List<? super Integer> because the former matches a list of type Integer only, whereas the latter matches a list of any type that is a supertype of Integer.
)
Wildcards and Subtyping
와일드카드 *를 사용하여 제네릭 클래스와 인터페이스 사이에서의 relationship에 대해 설정해줄 수 있다는 것에 대해 이전 장에서 설명하였다.
class A { /* ... */ }
class B extends A { /* ... */ }
제네릭이 아닌 클래스(위)를 가지고 아래와 같이 reasonable하게 구성할 수 있다.
B b = new B();
A a = b;
위는, 하위타이핑에 대한 규칙을 따르는 일반적인 클래스의 상속관계를 보여준다.
B의 부모가 A이고 b를 a에 대입할 수 있다.
그런데 제네릭 타입은 이것이 허용되지 않는다.
List<B> lb = new ArrayList<>();
List<A> la = lb; // compile-time error
Integer는 Number의 하위 타입이지만.. List<Integer>와 List<Number>의 관계는 "아무 관계 없다"는 것을 위에서 보았다.
사실, List<Integer>와 List<Number>의 공통 부모는 List<?>이다.
List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>
위의 코드와 같이 upper bound wildcild를 사용하여 List<Integer>의 element를 통해 List<Number>로 접근 할 수 있다.
위의 포함 관계에 대해 생각해보라
Wildcard Capture and Helper Methods
[생략]
Guidelines for Wildcard Use
이 제네릭을 사용하는 것은 무척이나 혼란스러우므로 아래의 가이드를 이용하라.
"In", "Out"
"In"은 코드에 데이터를 올리는 역할을 하는 변수이다.
"Out"은 다른 곳에서 데이터를 쓰기위해 데이터를 hold한다.
(
One of the more confusing aspects when learning to program with generics is determining when to use an upper bounded wildcard and when to use a lower bounded wildcard. This page provides some guidelines to follow when designing your code.
For purposes of this discussion, it is helpful to think of variables as providing one of two functions:
An "In" VariableAn "in" variable serves up data to the code. Imagine a copy method with two arguments: copy(src, dest). The src argument provides the data to be copied, so it is the "in" parameter.
An "Out" VariableAn "out" variable holds data for use elsewhere. In the copy example, copy(src, dest), the dest argument accepts data, so it is the "out" parameter.
Of course, some variables are used both for "in" and "out" purposes — this scenario is also addressed in the guidelines.
)
와일드 카드 사용 여부와 적합한 와일드 카드 유형을 결정할 때 "in"및 "out" 룰을 사용하라.
가이드라인은 다음과 같다.
Wildcard Guidelines:
1>An "in" variable is defined with an upper bounded wildcard, using the extends keyword.
2>An "out" variable is defined with a lower bounded wildcard, using the super keyword.
3>In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard.
4>In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard.
3> : Object Class에 정의된 메소드를 사용하여 접근하기위해서 in 변수에 접근해야하는 경우 unbounded wild 카드를 사용하라.
4> in 과 out 변수로 접근이 필요한 경우 wildcard를 쓰지마라.
return type을 만들기위해 와일드카드를 사용하는 것은 피해야한다.
(
These guidelines do not apply to a method's return type. Using a wildcard as a return type should be avoided because it forces programmers using the code to deal with wildcards.
)
List<? extends ...>로 정의 된 리스트는 비공식적으로 read-only로 생각할 수 있다, 하지만 이는 strictly 보장되지는 않는다.
아래의 예제를 보라.
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>의 하위타입이기때문에 le를 In에 넣을 수 있다.
하지만, even number의 리스트에 자연수를 추가하기 위해 In을 사용할 수 없다.(짝수 타입만 받는 le로 In이 정의 되었기 때문이다)
아래 operation은 사용할 수 있다.
1. null을 추가할 수 있음
2. clear를 invoke할 수 있음
3. remove를 invoke하거나 iterator를 얻을 수 있음
4. 와일드 카드를 캡처하고 목록에서 읽은 요소를 쓸 수 있다.
(
You can see that the list defined by List<? extends NaturalNumber> is not read-only in the strictest sense of the word, but you might think of it that way because you cannot store a new element or change an existing element in the list
)
'Programming Theory > Generics' 카테고리의 다른 글
Generics -scala(1) (0) | 2020.12.05 |
---|---|
Generics -java(1) (0) | 2020.12.02 |