자바/지네릭스

지네릭 타입의 형변환과 제거

백_곰 2022. 5. 16. 15:23

1. 지네릭 타입과 원시 타입의 형변환

- 아래의 코드처럼 지네릭 타입과 원시 타입의 형변환은 항상 가능하지만, 경고가 발생한다.

Box box = null;
Box<Object> objBox = null;

box = (Box) objBox;         // 지네릭 타입 -> 원시 타입 (경고 발생)
objBox = (Box<Object>) box; // 원시 타입 -> 지네릭 타입 (경고 발생)

 

 

 

- 단, 아래의 코드처럼 대입된 타입이 다른 지네릭 타입 간에는 형변환이 불가능하다.

Box<Object> objBox = null;
Box<String> strBox = null;

objBox = (Box<Object>) strBox;
// 에러 발생

strBox = (Box<String>) objBox;     
// 에러 발생

 

 

- 그러므로, 아래의 코드처럼 <Object> 지네릭으로는 다른 지네릭으로 형변환할 수 없다.

Box<Object> objBox = new Box<String>();
// 에러 발생. 형변환 불가능

 

 

- 그러나, 아래의 코드처럼 <? extends Object>를 사용하여 <String> 지네릭으로 변경할 수 있다.

Box <? extends Object> wBox = new Box<String>();
// 형변환 가능.

 

 

- 만약 아래의 코드처럼 반대로 형변환을 한다면, 확인되지 않는 형변환이라는 경고가 발생한다.

FruitBox <? extends Fruit> box = null;
FruitBox <Apple> appleBox = (FruitBox<Apple>) box;
// 형변환 OK. but 경고 발생

 

 

- 아래의 코드 java.util.Optional 클래스의 실제 소스이다.

public final class Optional<T>{
    private static final Optional<?> EMPTY = new Optional<>();
    private final T value;
    ...
    
    public static<T> Optional<T> empty(){
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
    
        ...
}

 

( 상수 EMPTY 변수는 아래의 코드와 동일하다. 접근 제어자는 생략했다. )

( (1) Optional<?> EMPTY = new Optional<Object>(); )

( (2) Optional<? extends Object> EMPTY = new Optional<>(); )

( (3) Optional<? extends Object> EMPTY = new Optional<Object>(); )

 

( 사실 <?>는 <? extends Object>를 생략하였고, (1)의 <> 안에 생략된 타입은 "?"가 아닌 "Object"이다. )

 

( 단, class Box<T extends Fruit>일 경우, Box<?> b = new Box<>;는 Box<?> b = new Box<Fruit>; 이다. )

 

( 그러므로, Optional<?> Empty = new Optional<?>(); 는 에러를 발생하게 된다. )

( 그 이유는 미확인 타입의 객체로 간주하기 때문이다. )

 

 

( 또한 상수 EMPTY 타입을 Optional<Object>가 아닌 Optional<?>로 한 이유는 아래의 코드처럼 Optional<T>로

형변환이 가능하기 때문이다. ) 

Optional<?>      wopt = new Optional<Object>();
Optional<Object> oopt = new Optional<Object>();

Option<String> sopt = (Optional<String>) wopt;
// 컴파일 OK. 형변환 가능

Option<String> sopt = (Optional<String>) oopt;
// 컴파일 에러. 형변환 불가능

 

 

( 그러므로, Optional<Object>를 Optional<String>으로 직접 형변환하는 것은 불가능하지만, 와일드 카드가

포함된 지네릭 타입으로 형변환하면 가능하다. )

( Optional 클래스의 empty() 메서드만 봐도 형변환을 수행하고 있다. )

Optional<T> t = (Optional<T>) EMPTY;
// Optional<?> -> Optional<T>

 

 

( 대신 확인되지 않는 타입으로의 형변환이라는 경고가 발생한다. )

( 아래의 코드를 통해 이해하자. )

Optional<Object> -> Optional<T>
// 형변환 불가능

Optional<Object> -> Optional<?> -> Optional<T>
// 형변환 가능. 경고 발생

 

 

( 마지막으로, 와일드 카드를 사용한 지네릭 타입끼리도 아래의 코드처럼 형변환이 가능하다. )

FruitBox<? extends Object> objBox = null;
FruitBox<? extends String> strBox = null;

strBox = (FruitBox<? extends String>) objBox;
objBox = (FruitBox<? extends Object>) strBox;
// 둘 다 형변환 가능 but 미확정 타입으로 경고

 

 

 

 

2. 지네릭 타입의 제거

- 컴파일러는 지네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 지네릭 타입을

제거한다. 그러므로, (*.class)에는 지네릭 타입에 대한 정보가 없는 것이다. 

 

- 이렇게 하는 주된 이유는 지네릭이 도입되기 이전의 소스 코드와의 호환성을 유지하기 위해서이다.

 

 

- 아래의 순서는 지네릭 타입을 제거하는 컴파일러의 과정을 보여준다.

 

(1) 지네릭 타입의 경계(Bound)를 제거한다.

적용 전 적용 후
class Box<T extends Fruit>{
   void add(T t) {
          ...
   }
}
class Box{
   void add(Fruit t) {
          ...
   }
}

( <T extends Fruit>라면 T는 Fruit로 치환한다. )

( 또한 <T>인 경우는 T가 Object로 치환된다. )

 

 

 

(2) 지네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형변환을 추가한다.

적용 전 적용 후
T get(int i){
     return list.get(i);
}
Fruit get(int i){
      return (Fruit) list.get(i);
}

( 여기서는 컴파일러가 T를 치환했을 뿐만 아니라 형변환을 적용했다. )

 

 

( 만약 와일드 카드가 포함된 경우에는 아래와 같이 적절한 타입으로의 형변환이 추가된다. )

 

< 적용 전 >

class Juicer{
    static Juice makeJuice(FruitBox<? extends Fruit> box) {
         String tmp = "";
         for(Fruit f : box.getList()) tmp += f + " ";
         return new Juice(tmp);
    }
}

 

< 적용 후 >

class Juicer{
    static Juice makeJuice(FruitBox  box) {
         String tmp = "";
         Iterator it = box.getList().iterator();
         while(it.hasNext()){
               tmp += (Fruit) it.next() + " ";
         }
         return new Juice(tmp);
    }
}