ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java Note] Generics(제네릭스) 선언 정의 제한 형 변환 와일드 카드
    JAVA 2022. 8. 31. 16:55
    728x90
    반응형

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

    제네릭 클래스 선언

    클래스를 작성할 때 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);
    }
    728x90
    반응형
Designed by Tistory.