본문 바로가기
Programming/JAVA

Upper Bounded Wildcards

by 한 땀; 한 땀; 2020. 11. 29.
class Something<T> {
    private T obj;
    public void in(T something) { obj = something; }
    public T out() { return obj; }
}

public class UpperBoundedWildcards{

    public static <T> void outBox(Something<? extends T> obj1,T obj2) {
        obj1.in(obj2);
        System.out.println(obj1.out());
    }
    public static <T> void inBox(Something<T> obj1,T obj2) {
        obj1.in(obj2);
    }
}

 

만약 다음과 같이 실수로 outBox 메서드에 obj1.in(obj2); 와 같은 코드를 삽입했다고 생각하면 와일드카드 상한 제한에 의해 컴파일 과정에서 에러가 발견된다.

 

에러가 생기는 이유는 obj1의 인자로 올 수 있는 인스턴스는 제네릭 T type의 Something 클래스 또는 T를 상속하는 Something 클래스 이기 때문이다. 

 

예를 들어 public <T> voidtest(Something <? extends Number> obj1, T obj2)에서 obj1로는 Number를 상속하는 모든 인스턴스 참조 변수가 올 수 있다. 즉 참조변수의 상속관계 개념을 생각해서 허용 범위를 생각하면 된다.

 

그렇다면, 다음과 같이 코드를 작성한다면 하위 클래스의 참조 변수가 상위 클래스의 참조 변수를 받는 꼴이 되기 때문에 에러가 발생하게 된다. 정말 간단하게 말하면 모든 자료형을 받을 수 있는 메소드에 자료형을 제한한 것이다.

 

public static void main(String[] args) {
        Something <Integer> intObj= new Something <>(); 
        Number n;
        outBox(intObj, n)
    }

 

그런데 한 번 더 생각해보면 와일드카드 상한 제한을 제네릭 T로 받을 때 제네릭 T의 값은 전달되는 매개변수화 타입에 맞춰서 바뀌는데 두 번째 인자인 T obj2T 자료형이 첫 번째 인자인 obj1T보다 상위 클래스일 수가 없게 된다.

 

그렇다면 와일드 카드 상한제한을 제네릭으로 받으면 굳이 컴파일 에러를 작동시킬 필요가 없지 않을까? 라고 생각할 수 있다.

 

이때 알아야 하는 개념이 "Type Erasure"이다.

 

컴파일러는 outBox(Something <? extends T> obj1, T obj2) "Type inference" 또는 "Target Type"에 의해 T를 결정한 후, "Type Erasure"에 의해서 바인딩 되어 만약 Number가 전달 되었다면 

outBox(Something <? extends Number> obj1, T obj2)가 최종적으로 만들어지게 되는 것이다. 인스턴스가 만들어지는게 아니다.

따라서 우리가 보는 코드는 <? extends T> 이지만 컴파일러 입장에선 Type Parameter T가 정해진 상태로 생성이 되게 된다.

 

또한, 제네릭 메서드가 Unbounded Wildcards 라면 즉, outBox(Something <?> obj1, T obj2) 이런 형태의 메서드라면 "Type Erasure"에 의해 ?Object로 대체하게 된다. 따라서 obj1.in(obj2) 코드는 에러를 발생시키지 않는다.

 

즉, 와일드카드 상한 제한은 저러한 에러를 방지하기에 앞서, 어떤 인스턴스가 전달될 때 그 인스턴스를 상속하는 인스턴스도 허용하기 위해 만들어졌다고 볼 수 있다. 근데 extends 로 제한을 하다보니 저러한 에러를 방지 할 수 있게 된 것이다.

 

그래서 "어떤 인스턴스가 전달될 때 그 인스턴스를 상속하는 인스턴스 허용하기 위해" 가 아닌

          "어떤 인스턴스가 전달될 때 그 인스턴스를 상속하는 인스턴스 허용하기 위해" 라고 해석하면

컴파일 에러가 뜨게 되는 것이다.

 

참고 

 

"와일드카드 상한 제한" 이란?

 

public <T> void test(Something <Number> obj) 메서드는 매개변수 obj로 올 수 있는 인자는 반드시

Something <Number> 라는 '매개변수화 타입'(또는 '제네릭 타입')만 올 수 있다. 만약 Number를 상속하는 클래스가 올 수 있지 않냐는 생각이 든다면 제네릭에 대한 이해가 부족하므로 기본 개념을 다시 익혀야 한다.

 

그런데 이때 제네릭 타입 Something<Number> 를 상속하는 인스턴스도 받고 싶다면 extends 선언이 필요하다.

 

즉, public void test(Something <? extends Number> obj)라는 메서드를 선언하면 obj로 올 수 있는 인자는

Number 인스턴스 또는 Number 인스턴스를 상속하는 Byte, Integer, Double 등이 올 수 있다는 뜻이다.

 

그런데.. Number 인스턴스 또는 하위 인스턴스가 아닌 모든 인스턴스를 받기 위해선 어떻게 해야 할까?

 

오버로딩을 생각해서 Something <? extends UserObject> 같은 코드를 생각해볼 수 있지만 이는 "Type Erasure" 에 의해 오버로딩이 되지 않기 때문에 제네릭을 사용하여 <? extends T> 형식의 코드를 사용할 수 있다. 

 

public static <T> void test(Something <? extends T>

T를 상한 제한 하여 해당 인자(Type Argument)의 인스턴스 또는 해당 인자를 상속하는 하위 인스턴스만 받는다."

 

"Type Erasure" 란?

 

Java언어는 Genric의 등장으로 자바의 문법은 많이 발전하게 되었으며 <>(이하 다이아몬드기호)는 존재하지 않는 문법이였다. 즉 Java는 Generic을 구현하면서 기존 코드와 상호호환 하기 위해서 컴파일러는 < > 를 내부적으로 지우면서 다음과 같은 형태로 변형 시킨다.

 

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.  
  • 참고 : Java Documentation

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

Lambda Expression  (0) 2020.12.10
Nested Class, Inner Class  (0) 2020.12.08
Set<E>  (0) 2020.12.02

댓글