오늘의하루

[Java]제한된 Generic 클래스 본문

JAVA

[Java]제한된 Generic 클래스

오늘의하루_master 2022. 8. 25. 10:24

extends를 이용해 타입을 제한 하기

class Test<T>{}

이 경우에는 모든 타입이 저장가능하다.

class Fruit{}
class Apple extends Fruit{}
class FruitBox<T extends Fruit>{}

FruitBox<Fruit> Test = new FruitBox<Fruit>(); // OK
FruitBox<Apple> Test = new FruitBox<Apple>(); // OK
FruitBox<Toy> Test = new FruitBox<Toy>(); // Error!

이 경우에는 Fruit를 포함해서 Fruit의 자식 타입만 사용할수 있게 된다.

인터페이스의 경우에도 extends를 사용한다.
interface Eatable{ }
class FruitBox<T extends Eatable>{ }
class FruitBox<T extends Fruit & Eatable> { } 
  - Fruit를 포함한 자식 타입이거나 Eatable를 구현한 클래스 타입만 가능하다.

Generic의 제약

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

Box<Apple> appleBox = new Box<Apple>(); // OK

제약사항

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

class Box<T>{
    static T item; // Error!
    static int compare(T t1, T t2); // Error!
}

예제

public class MyClass {
    public static void main(String args[]) {
        FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
        FruitBox<Apple> appleBox = new FruitBox<Apple>();
        FruitBox<Grape> grapeBox = new FruitBox<Grape>();
        Box<Toy> toyBox = new Box<Toy>();
        
        fruitBox.add(new Fruit());
        fruitBox.add(new Apple());
        fruitBox.add(new Grape());
        appleBox.add(new Apple());
        grapeBox.add(new Grape());
        System.out.println(fruitBox);
    }
}

interface Eatable{}
class Fruit implements Eatable{public String toString(){return "Fruit";}}
class Apple extends Fruit{public String toString(){return "Apple";}}
class Grape extends Fruit{public String toString(){return "Grape";}}

class Toy{public String toString(){return "Toy";}}

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

배열을 생성할 때 타입 변수 사용 불가하지만 타입 변수로 배열 선언은 가능하다.

class Box<T>{
    T[] itemArr; // OK 타입 변수로 배열을 선언할 수 있다.
    T[] toArray(){
        T[] tmpArr = new T[itemArr.length]; // Error! 타입 변수로 배열을 생성할 수 없다.
    }
}
  • 간단하게 new 연산자 뒤에는 확정된 타입만 올수 있다.

와일드 카드 <?>

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

  • 단, 와일드 카드에서는 <? enxtends T & E>와 같이 "&"를 사용할 수 없다.
class Product{}
class Tv extends Product{}
class Audio extends Product{}

ArrayList<? extends Product> list = new ArrayList<Tv>(); // OK
ArrayList<? extends Product> list = new ArrayList<Audio>(); // OK

ArrayList<Product> list = new ArrayList<Tv>(); // Error! 타입 불일치

와일드 카드에 대해 좀더 알아보기

구분 내용
<? extends T> 상한 제한(T를 포함해서 T의 자식 타입들만 가능)
<? super T> 하한 제한(T를 포함해서 T의 부모 타입들만 가능)
<?> 모든 타입 가능 == < ? extneds Object >

매개변수로 와일드 카드 사용

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

예제

public class MyClass {
    public static void main(String args[]) {
        // FruitBox<? extends Fruit> appleBox = new FruitBox<Apple>(); // OK Fruit를 포함한 자식 타입 가능
        FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
        FruitBox<Apple> appleBox = new FruitBox<Apple>();
        
        fruitBox.add(new Apple());
        fruitBox.add(new Grape());
        appleBox.add(new Apple());
        appleBox.add(new Apple());
        
        System.out.println(Juicer.makeJuice(fruitBox));
        System.out.println(Juicer.makeJuice(appleBox));
    }
}
class Fruit{public String toString(){return "Fruit";}}
class Apple extends Fruit{public String toString(){return "Apple";}}
class Grape extends Fruit{public String toString(){return "Grape";}}

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();}
}

class FruitBox<T extends Fruit> extends Box<T>{}

class Juice{
    String name;
    Juice(String name){
        this.name = name + "Juice";
    }
    public String toString(){return name;}
}

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

Generic 메서드

제네릭 타입이 선언된 메서드이며 타입 변수는 메서드 내에서만 유효하다.

static <T> void sort(List<T> list, Comparator<? super T> c)
  • 클래스의 타입 매개변수 <T>와 타입 매개변수 <T>는 별개이다.
class Test<T>{
    static <T> void sort(List <T> list, Comparator<? super T> c){
        ....
    }
}

메서드를 호출할 때 마다 타입을 대입해야한다.

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

System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Fruit>makeJuice(appleBox));

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

System.out.println(<Fruit>makeJuice(fruitBox)); // Error! 클래스 이름 생략 불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); // OK
System.out.println(Juicer.makeJuice(fruitBox)); // OK
Comments