public class Collections {
    ....
    public static <T> void copy(List<? super T> dest, List<? extends T> src){}
    ....
}

 

Collections 클래스는 List와 관련된 여러 유용한 기능을 제공합니다. 그 중 copy API를 통해서 이번절 내용을 살펴보겠습니다. 

copy는 src 변수가 가르키는 List의 데이터 값을 dest 변수가 가르키는 List에 저장하는 기능을 제공합니다.

 

여기서 우리는 지금까지 보지 못한 구문을 또 발견하게됩니다.

 

  1. ? super T
  2. <T>

 

2번째 내용은 다음 포스팅에서 다루기로하며, 이번 절에서는 ? super T 구문에 대해서 살펴보겠습니다. 

 

? super T는 무엇을 의미할까요?

이는 T 타입 뿐만 아니라 T 타입의 모든 Supertype까지 허용하겠다는 의미입니다.

즉 T 타입이 Integer라면 List<Integer> 외에 List<Number> 및 List<Object> 형태 할당을 허용하겠다는 의미입니다.

이러한 특성을 Contravariant 라고 합니다.

 

또한, Parameter Type을 T 타입 이상으로 제한한다고해서 하위 경계(Lower bound)라고 말합니다.

 

예제를 통해서 개념을 살펴보겠습니다.

 

public class Main {
    public static void main(String[] args)  {
        List<Object> objs = Arrays.asList(2, 3.14, "Penguin");
        List<Integer> ints = Arrays.asList(1, 2);
        Collections.copy(objs, ints);
        System.out.println(objs);
    }
}

 

위 코드는 Object List에 Integer List 값을 추가하는 코드입니다.

여기에서 Collections.copy 메소드는 objs 객체, ints 객체에도 속하지 않습니다. 따라서 메소드 호출 시에 어떠한 타입으로 호출될지 컴파일러가 추론하거나 개발자가 명시적로 호출시 타입을 지정해야합니다.

 

이때 지정될 수 있는 타입의 종류는 Integer, Number(Integer의 Supertype 이므로), Object입니다.

 

  1. Collections.copy(objs, ints);
  2. Collections.<Integer>copy(objs, ints);
  3. Collections.<Number>copy(objs,ints);
  4. Collections.<Object>copy(objs,ints);

 

public static <T> void copy(List<T> dest, List<? extends T> src){}

 

이러한 상황에서 만약 copy 메소드의 dst 변수 타입이 하위 경계가 아닌 T 타입으로 설계되어 있었다면 어떠한 타입으로 추론될까요?

 

public static <Object> void copy(List<Object> dest, List<? extends Object> src){}

 

위 코드를 만족시키기 위해서는 추론되는 타입이 Object 타입만 허용 가능합니다. 그 이유는 dest에 할당되는 Parameterized Type이 List<Object>이기 때문입니다.

 

따라서 위의 4가지 경우 중 1번과 4번 메소드 호출만 성공하고, 나머지 2개의 타입은 컴파일 오류가 발생합니다.

 

public static <T> void copy(List<? super T> dest, List<? extends T> src){}

 

이번에는 원래의 copy 메소드처럼 상위 경계와 하위 경계를 같이 쓰게되는 경우에는 어떻게 될까요? 결과는 위 4가지 어떠한 타입으로 추론되더라도 문제가 없습니다.

 

타입을 직접 명시하는 경우를 기준으로 하나하나씩 살펴볼까요?

 

 

1.  Collections.<Integer>copy()

public static <Integer> void copy(List<? super Integer> dest, List<? extends Integer> src){}

 

Object는 Integer의 SuperType이고, Integer는 자기 자신에 대한 Subtype이므로 위와 같이 추론될 경우 할당 가능합니다.


2. Collections.<Number>copy()

public static <Number> void copy(List<? super Number> dest, List<? extends Number> src){}

 Object는 Number Class의 SuperType이고, Integer <: Number 이므로 할당 가능합니다.


3. Collections.<Object>copy()

public static <Object> void copy(List<? super Object> dest, List<? extends Object> src){}

Object는 모든 Class의 SuperType이므로 할당 가능합니다.

 

 

위 사실들을 기반으로 사용될 수 있는 타입 파라미터 종류를 조합해보면 다음과 같은 결과를 얻을 수 있습니다.

 

1. copy(List<T> dst, List<T> src) 
   - 동일 타입만이 가능하므로, 위 예제에서는 해당 메소드를 사용할 수 없습니다.


2. copy(List<T> dst, List<? extends T> src) 
  - dst가 Object 타입이므로 Object 타입 추론될 경우에만 허용 가능합니다. 

3. copy(List<? super T> dst, List<T> src) 
  - src가 Integer 이므로 Integer 타입 추론될 경우에만 허용 가능합니다. 

4. copy(List<? suepr T> dst, List<? extends T> src) 
  - Object, Number, Integer 타입 어떤 것이 오든지 허용 가능합니다.

 

 

이를 통해 알 수 있는 사실은 적절한 경계를 사용함으로서 메소드의 타입을 추론하는데 있어서 한정된 타입만 사용되지 않도록하여 유연한 API 설계를 가능하도록 지원합니다.

 

그렇다면 하위 경계는 언제 주로 활용될까요?

전달받은 구조 내부로 T나 T의 Subtype으로 값을 입력하는 경우 주로 사용됩니다.

 

public class Main {
    public static void main(String[] args)  {
        List<Integer> ints = new ArrayList<>();
        ints.add(1);
        ints.add(3);
        List<? super Integer> ints2 = ints;
        ints.add(4);
        
        for(Integer i : ints2){   //Compile 에러 발생
           System.out.println(i);
        }
    }
}

 

위 예제와 같이 ints2 객체에서는 Integer Subtype 값 입력이 가능합니다.

반면, 해당 구조체로부터 값을 가져오려는 경우에는 컴파일 에러가 발생합니다.

그 이유는, 컴파일러 입장에서 해당 객체는 Integer의 상위 타입은 모두 올 수 있는데, 그것을 Integer 타입 변수로 할당 받는 것은 적합하지 않기 때문입니다.

 

하지만 하위 경계에도 예외가 존재합니다. 즉 하위 경계는 주로 값 입력을 위해 사용되나, 특정 타입에 경우에는 값을 가져올 수 있습니다. 그것은 바로 Object 타입입니다.

 

public class Main {
    public static void main(String[] args)  {
        List<Integer> ints = new ArrayList<>();
        ints.add(1);
        ints.add(3);
        List<? super Integer> ints2 = ints;
        ints.add(4);
        
        for(Object i : ints2){  
           System.out.println(i);
        }
    }
}

 

그 이유는 Object는 모든 클래스의 부모 클래스이기 때문입니다.

아래 예시를 통해서 자세히 알아보겠습니다.

 

List<? super Integer> 변수 사용시, 어떠한 타입이 올지 모르므로 해당 타입을 임의의 타입 변수 X라고 가정해봅시다.

이때 Integer <: X이며, X <: Object 입니다.

 

public interface List<X> extends Collection<X>{
    ...
    X get(int index);
    void add(X element);
    ...
}

 

따라서 X의 Supertype인 Object의 반환은 허용됩니다.


Get and Put Principle

 

지금까지 상위, 하위 경계를 학습하였습니다.

이전의 예시들에서 확인하였듯이 Wildcard는 사용할 수 있을 때 가능하면 사용하는 것이 좋은 습관입니다.

그 이유는 WIldcard 사용을 통해서 해당 API 호출하는 입장에서 전달하는 파라미터 내용이 내부적으로 어떻게 사용될지 유추가 가능할 뿐더러 유연한 타입 제공이 가능하기 때문입니다.

 

그렇다면 언제, 어떤 Wildcard를 사용하는 것이 좋을까요?

이를 결정하기 위한 간단한 원칙이 있습니다.(Get and Put Principle)

 

  • 하위 경계(? extends T) : 구조 바깥으로 T나 T의 Supertype으로 값을 읽어야 하는 경우 사용하자
  • 상위 경계(? super T) : 구조 내부로 T나 T의 Subtype으로 값을 입력해야하는 경우 사용하자

 

이전에 살펴본 copy 메소드를 통해 해당 원칙이 어떻게 적용되었는지 확인해봅시다.

 

public static <T> void copy(List<? super T> dst, List<? extends T> src){}

 

Get and Put 원칙을 인지한 상태에서 위와 같은 API를 보면 다음과 같은 생각을 할 수 있습니다.

 

'src 변수에 전달하는 List 객체에서는 내부에서 값을 추출하기 위한 용도로 사용되며, dst 변수에 전달하는 객체에서는 값을 입력하는 용도로 사용되겠구나'

 

이번에는 Number 타입을 지닌 Collection의 합을 구하는 메소드를 직접 디자인한다면 어떻게 타입을 설계하는것이 좋을지 살펴보도록 하겠습니다.

 

public static double sum(Collection<Number> cols){
    double s = 0.0;
    for(Number col : cols)
    	s = s + col.doubleValue();
    return s;
}    

 

먼저 위와 같이 Number 타입 지정을 고려해볼 수 있습니다. 하지만 이는 이전에 줄곧 얘기한 List<Number> 만 허용 가능한 메소드이기에 사용의 제약이 있습니다. 또한 Get and Put 원리에 따르면 해당 Collection에 별도 값 입력을 하지 않기 때문에 상위 경계(? extends Number)를 사용하는 것이 적합해보입니다.

 

이번에는 n을 입력하면 0부터 n-1까지 값을 채워주는 함수를 만들어 보겠습니다.

 

public static void assign(Collection<? super Integer> ints, int n){
    for(int i = 0 ; i < n; i++) ints.add(i);
}    

 

위 경우에는 해당 Collection 안에 값을 채워넣는 작업이 수행되므로 하위 경계를 입력하는 것이 적합해보입니다.

'JAVA > Generic' 카테고리의 다른 글

6. Generic 메소드  (0) 2020.03.10
5. Wildcard  (0) 2020.03.08
3. Wildcard with extends(상위 경계)  (2) 2020.03.06
2. Subtyping, Substitution 원리  (0) 2020.03.05
1. Generic 기초  (3) 2020.03.04

+ Recent posts