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);
}
}
'자바 > 지네릭스' 카테고리의 다른 글
지네릭 메서드 (0) | 2022.05.13 |
---|---|
와일드 카드 (0) | 2022.05.12 |
지네릭스의 전반적 개념(2) (0) | 2022.05.11 |
지네릭스의 전반적 개념(1) (0) | 2022.05.11 |