JAVA/자바의 정석

[자바의 정석 - 기초편] 12. (2) 제한된 지네릭 클래스 / 지네릭스의 제약 / 와일드 카드 <?> / 지네릭 메서드 / 지네릭 타입 ( 형변환, 제거 )

서영22 2023. 12. 14. 00:30

 제한된 지네릭 클래스 

- extends로 대입할 수 있는 타입을 제한

class FruitBox<T extends Fruit> {   // Fruit의 자손만 타입으로 지정가능
        ArrayList<T> list = new ArrayList<T>();
        ...
}

FruitBox<Apple> appleBox = new FruitBox<Apple>();  // OK
FruitBox<Toy> toyBox = new FruitBox<Toy>();    // 에러. Toy는 Fruit의 자손이 아님

 

 

- 인터페이스인 경우에도 extends 사용

interface Eatable { }
class FruitBox<T extends Eatable> { ... }

 

 

 

< 예제 >

 

 

 

 

 지네릭스의 제약 

- 타입 변수에 대입은 인스턴스 별로 다르게 가능

Box<Apple>  appleBox = new Box<Apple>();   //  OK. Apple 객체만 저장 가능
Box<Grape> grapeBox = new Box<Grape>();  //  OK. Grape 객체만 저장 가능

 

 

제약

 

1. static 멤버에 타입 변수 사용 불가

class Box<T> {
        static T item;  // 에러
        static int compare(T t1, T t2) { ... }  // 에러

 

 

2. 배열 생성할 때 타입 변수 사용 불가. 타입 변수로 배열 선언은 가능

class Box<T> {
        T[ ] itemArr;  // OK. T타입의 배열을 위한 참조 변수
             ...
        T[ ] toArray() {
                T[ ] tmpArr = new T[itemArr.length];  // 에러. 지네릭 배열 생성 불가
             ...

➔ T : 객체 생성 불가 , 배열 생성 불가 ( new 연산자 다음에 T 사용할 수 없음)

 

 

 

 와일드 카드 <?> 

- 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능

<? extends T>  : 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T>      : 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?>                    : 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
ArrayList<? extends Product> list = new ArrayList<Tv>();   // OK (불일치 OK)
ArrayList<? extends Product> list = new ArrayList<Audio>();   // OK
ArrayList<Product> list = new ArrayList<Tv>();   // 에러. 대입된 타입 불일치

 

 

- 메서드의 매개변수에 와일드 카드를 사용

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

 

 

 

< 예제 >

 

 

 

 

 지네릭 메서드 

지네릭 타입이 선언된 메서드 (타입 변수는 메서드 내에서만 유효)

static <T> void sort(List<T> list, Comparator<? super T> c)

 

 

- 클래스 타입 매개변수 <T>와 메서드의 타입 매개변수 <T>는 별개

class FruitBox<T> {
            ...
       static <T> void sort(List<T> list, Comparator<? super T> c) {
            ...
       }
}

 

 

- 메서드를 호출할 때마다 타입을 대입해야 함 (대부분 생략 가능)

 

 

- 메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가

Sysytem.out.println(<Fruit>makeJuice(fruitBox));  // 에러. 클래스 이름 생략 불가
Sysytem.out.println(this.<Fruit>makeJuice(fruitBox));  // OK
Sysytem.out.println(Juicer.<Fruit>makeJuice(fruitBox));  // OK

 

 

 

 와일드 카드 VS 지네릭 메서드 

와일드 카드 : 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것

지네릭 메서드 : 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것, 와일드 카드 안될 때도 사용

 

 

 

 지네릭 타입의 형변환 

- 지네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않음 (경고 발생)

 

 

 

 

- 와일드 카드가 사용된 지네릭 타입으로는 형변환 가능

 

 

 

 

 

 지네릭 타입의 제거 

컴파일러는 지네릭 타입을 제거하고 필요한 곳에 형변환을 넣는다

( 지네릭스를 도입하면서 Object ➔ <T>로 바꿨는데 컴파일 후에는 다시 <T> ➔ Object 가 됨 )

 

 

1. 지네릭 타입의 경계(bound)를 제거

 

2. 지네릭 타입 제거 후에 타입이 불일치하면 형변환을 추가

 

3. 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가