오늘의하루

[Java Note] Generics(제네릭스) 선언 정의 제한 형 변환 와일드 카드 본문

JAVA

[Java Note] Generics(제네릭스) 선언 정의 제한 형 변환 와일드 카드

오늘의하루_master 2022. 8. 31. 16:55

제네릭스는 컴파일 시 타입을 체크해주는 기능으로 객체의 타입 안정성을 높이고 형 변환의 번거로움을 줄여줌으로써 코드가 간결해질 수 있다.

제네릭 클래스 선언

클래스를 작성할 때 Object 타입 대신 T와 같은 타입 변수를 사용한다.

class TEST{
    Object item;
    void setItem(Object item){this.item = item;}
    Object getItem(){return item;}
}

// Object 대신 T와 같은 타입변수 사용

class Test<T>{
    T item;
    void setItem(T item){this.item = item;}
    T getItem(){return item;}
}
  • 참조 변수, 생성자에 T 대신 실제 타입을 지정하면 형 변환 생략이 가능하다.
Test<String> b = new Test<String>();
b.setItem(new Object()); // Error! String타입만 지정 가능하다.
b.setItem("ABC");
String item = b.getItem(); // 형변환 필요없음
// String item = (String)b.getItem(); 생략가능

제네릭스 용어

class Test<T> Box  = 원시타입 (raw type)
Box<T> = 제네릭 클래스
Test<String> b = new Test<String>(); String = 대입된 타입
Test<String> = 제네릭 타입 호출

제네릭스의 제한

1. static 멤버에는 타입 변수 T를 사용할 수 없다.

class Test<T>{
    static T item; // Error!
    static T getItem(){...} // Error!
}

2. 제네릭 타입의 배열 T []를 선언할 수는 있지만 생성할 수 없다.

class Test<T>{
    T[] itemArr; // OK 선언은 가능하다.
    
    T[] toArray(){
        T[] tmpArr = new T[itemArr.length]; // Error! 생성은 불가능하다.
        return tmpArr;
    }
}

제네릭 클래스의 객체 생성과 사용

제네릭 클래스 Box <T> 선언

class Box<T>{
    ArrayList<T> list = new ArrayList<T>();
    
    void add(T item){list.add(item);}
    T get(int i){return list.get(i);}
    ArrayList<T> getList(){return list;}
    int size(){return list.size();}
    public String toString(){return list.toString();}
}

1. 참조 변수와 생성자에 대입된 타입이 일치해야 한다.

Box<Apple> apple = new Box<Apple>(); // OK
Box<Apple> apple = new Box<Grape>(); // Error! 타입 불일치
Box<Fruit> apple = new Box<Apple>(); // Error! 타입 불일치

2. 제네릭 클래스 간 상속관계이고 대입된 타입이 일치한다면 생성 가능

Box{}
FruitBox extends Box{}

Box<Apple> appleBox = new FruitBox<Apple>(); // OK 다형성
Box<Apple> appleBox = new Box<>(); // OK JDK1.7부터 생성자에 대입되는 타입 생략 가능

3. 대입된 타입과 다른 타입의 객체는 추가할 수 없다.

Box<Apple> appleBox = new Box<Apple>();
appleBox.add(new Apple()); // OK
appleBox.add(new Grape()); // Error! Apple타입만 가능하다.

제한된 제네릭 클래스

1. 제네릭 타입에 "extends"를 사용하면 특정 타입을 포함한 자식들만 대입할 수 있다.

class FruitBox<T extends Fruit>{ // Fruit와 Fruit자식만 타입으로 올수 있다.
    ArrayList<T> list = new ArrayList<T>();
    void add(T item){list.add(item);}
}

2. 매개변수 타입 T도 Fruit와 Fruit의 자식 타입이 될 수 있다.

// 1번의 코드에서 이어진다.

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
fruitBox.add(new Apple()); // OK Apple은 Fruit의 자식
fruitBox.add(new Grape()); // OK Grape은 Fruit의 자식

3. 인터페이스의 경우에도 제네릭 타입으로 지정할 때는 "extends" 사용한다.

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

4. 상속받는 타입과 인터페이스 구현한 타입을 동시에 만족시키는 제네릭 타입

class FruitBox <T extends Fruit & Eatable>{....}

와일드카드 "? "

제네릭 타입에 와일드카드를 쓰면 여러 타입의 대입이 가능하다.

  • 단 와일드카드에서는 <? extends Fruit & Eatable> 같은 " & "를 사용하지 못한다.
구분 특징
< ? extends T > T와 그의 자식 타입만 가능
< ? super T > T와 그의 부모 타입만 가능
< ? > 모든 타입 가능
class Juicer{
    staitc Juice makeJuice(FruitBox<? extends Fruit> box){
    String tmp = "";
    for(Fruit f : box.getList()){
        tmp = tmp + f + " ";
    }
    return new Juice(tmp);
    }
}
  • makeJuice의 매개변수로는 FruitBox <Apple>, FruitBox <Grape> 가능하다.
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

Juicer.makeJuice(fruitBox); // OK
Juicer.makeJuice(appleBox); // OK

제네릭 메서드

1. 반환 타입 앞에 제네릭 타입이 선언된 메서드

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

2. 클래스의 타입 매개변수 <T>와 메서드의 타입 변수 <T>는 별개이다.

clas FruitBox<T>{
    static <T> void sort(List<T> list, CompareTo(? super T> c){}
}

3. 제네릭 메서드를 호출할 때 타입 변수에 타입을 대입해야 한다.

  • 대부분의 경우 추정이 가능하므로 생략할 수 있다.
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

Juicer.<Fruit>makeJuice(fruitBox);
Juicer.<Apple>makeJuice(appleBox);
Juicer.makeJuice(appleBox); // 생략가능

제네릭 타입의 형 변환

1. 제네릭 타입과 원시 타입 간의 형 변환은 불가능하다.

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

box = (Box)objbox; // OK But 경고 발생 (제네릭 타입을 원시 타입으로 변환)
objbox = (Box<Object>)box // OK But 경고 발생 (원시 타입을 제네릭 타입으로 변환)

2. 와일드카드가 사용된 제네릭 타입으로 형 변환 가능

Box<? extends Object> wBox = new Box<String>();

FruitBox<? extends Fruit> box = null;
FruitBox<Apple> appleBox = (FruitBox<Apple>)box; // OK But 미확인 타입으로 형변환 경고

3. <? extends Object >를 줄여서 <? >로 쓸 수 있다.

Optional<?> EMPTY = new Optional<?>(); // Error! 미확인 타입의 객체는 생성 불가
Optional<?> EMPTY = new Optional<Object>(); // OK
Optional<?> EMPTY = new Optional<>(); // OK <Object>와 같은 문장이다.

제네릭 타입의 제거

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

1. 제네릭 타입의 경계를 제거

class Box<T extends Fruit>{
    void add(T t){}
}

// 제네릭 타입의 경계를 제거

class Box{
    void add(Fruit t){}
}

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

T get(int i){
    return list.get(i);
}

// 형변환 추가

Fruit get(int i){
    return (Fruit)list.get(i);
}

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

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

// 와일드 카드이기 때문에 적절한 타입으로 형변환

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