본문 바로가기

프로그래밍/JAVA

[ JAVA ] 지네릭스(Generics) - ③

1. 와일드 카드

지네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않음.
이럴 때 사용하기 위해 고안된 것이 '와일드 카드'이다. 
와일드 카드는 기호 '?'로 표현. 와일드 카드는 어떠한 타입도 될 수 있다.
<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T>     : 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?>                    : 제한 없음. 모든 타입이 가능 <? extends Object>와 동일
※ 지네릭 클래스와 달리 와일드 카드에는 '&'를 사용할 수 없다. 즉 <? extends T & E> 처럼 사용할 수 없다.

2. 지네릭 메서드

지네릭 메서드 : 메서드의 선언부에 지네릭 타입이 선언된 메서드. 지네릭 타입의 선언 위치는 반환 타입 바로 앞이다.
▶ 지네릭 클래스에 정의된 타입 매개변수 != 지네릭 메서드에 정의된 타입 매개변수
▶ static메서드에 지네릭 타입을 선언하고 사용하는 것은 가능하다. (static멤버에는 타입 매개 변수를 사용할 수 없다.)
※ 지네릭 메서드는 지네릭 클래스가 아닌 클래스에도 정의될 수 있다.
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
    Sring tmp = "";
    for(Fruit f : box.getList()) tmp += f + " ";
    return new Juice(tmp);
}

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

// makeJuice 메서드를 호출할 때는 타입 변수에 타입을 대입해야한다.
Juicer.<Fruit>makeJuice(fruitBox);
Juicer.<Apple>makeJuice(appleBox);

// 컴파일러가 타입을 추정할 수 있기 때문에 대입된 타입을 생략할 수 있다.
Juicer.makeJuice(fruitBox);
Juicer.makeJuice(appleBox);

// 대입된 타입이 있을 때는 참조변수나 클래스 이름을 생략할 수 없다.
<Fruit>makeJuice(fruitBox);        // 에러. 클래스 이름 생략불가
this.<Fruit>makeJuice(fruitBox);   // OK
Juicer.<Fruit>makeJuice(fruitBox); // OK

3. 지네릭 타입의 형변환

① 지네릭 타입과 넌지네릭(non-generic) 타입간의 형변환

// 가능하지만 경고가 발생한다.
Box box = null;
Box<Object> objBox = null; 

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

② 대입된 타입이 다른 지네릭 타입 간의 형변환

// 불가능하다.
Box<Object> objBox = null;
Box<String> strBox = null;

objBox = (Box<Object>)strBox; // 에러. Box<String> -> Box<Object>
strBox = (Box<String>)objBox; // 에러. Box<Object> -> Box<String>

③ <? extends T> 의 형변환

// 매개변수로 FruitBox<Fruit>, FruitBox<Apple>, FruitBox<Grape> 등이 가능
static Juice makeJuice(FruitBox<? extends Fruit> box) { ... }

FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); // OK
FruitBox<? extends Fruit> box = new FruitBox<Apple>(); // OK
FruitBox<? extends Fruit> box = new FruitBox<Grape>(); // OK
FruitBox<? extends Fruit> box = null;
// Ok. 미확인 타입으로 형변환 경고
// FruitBox<? extends Fruit>에 대입될 수 있는 타입이 여러 개이고, FruitBox<Apple>를 제외한
// 다른 타입은 FruitBox<Apple>로 형변환될 수 없기 때문
FruitBox<Apple> appleBox = (FruitBox<Apple>)box;

4. 지네릭 타입의 제거

▶ 컴파일러는 지네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 지네릭 타입을 제거한다.
    즉, 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없다.
   - 이렇게 하는 주된 이유 : 지네릭이 도입되기 이전의 소스 코드와의 호환성을 유지하기 위함

※ 기본적인 제거과정

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

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

   - List의 get()은 Object 타입을 반환하므로 형변환이 필요하다.

③ 와일드 카드가 포함되어 있는 경우에는 적절한 타입으로의 형변환이 추가된다.

'프로그래밍 > JAVA' 카테고리의 다른 글

[ JAVA ] 스트림(stream) - ①  (0) 2023.03.19
[ JAVA ] 쓰레드의 실행제어  (0) 2023.02.26
[ JAVA ] 지네릭스(Generics) - ②  (0) 2023.02.06
[ JAVA ] 지네릭스(Generics) - ①  (0) 2023.02.06
[ JAVA ] Arrays  (0) 2023.01.08