[기본 개념] 7 | (1.2) 와일드 카드, 지네릭 메서드, 지네릭 타입 제거

728x90

[기본 개념] 7 | (1.2) 와일드 카드, 지네릭 메서드, 지네릭 타입 제거

1 지네릭스란?

2 지네릭 클래스의 선언

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

4 제한된 지네릭 클래스

5> 와일드 카드

6> 지네릭 메서드

7> 지네릭 타입의 제거

5. 와일드 카드

 매개변수에 과일박스를 대입하면 주스를 만들어서 반환하는 Juicer라는 클래스가 있다고 하자.

 

 그러면 Juicer클래스는 지네릭 클래스가 아니고, static메서드는 타입 매개변수 T를 사용할 수 없으므로 'FruitBox<Fruit>'과 같이 특정 타입을 지정해야 한다. 이렇게 고정하면 'FruitBox<Apple>'타입의 객체는 매개변수가 될 수 없으므로 다음과 같이 여러 타입의 매개변수를 갖는 makeJuice( )를 만들어야 한다.

 

static Juice makeJuice(FruitBox<Fruit> box) {

    String tmp = "" ;

    for (Fruit f : box.getList( )) tmp += f + " " ;

    return new Juice(tmp) ;

}

static Juice makeJuice(FruitBox<Apple> box) {

    String tmp = "" ;

    for (Fruit f : box.getList( )) tmp += f + " " ;

    return new Juice(tmp) ;

}

 

 하지만 위와 같이 오버로딩하면 컴파일 에러가 발생한다. 지네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않기 때문이다. 지네릭 타입은 컴파일 시에만 사용되며 컴파일 후 제거한다. 따라서 '메서드 중복 정의'가 되는 것이다. 

 

 이럴 때, 기호 '?'로 표현하여 '와일드 카드'라는 것을 사용할 수 있다.

 

<? extends T>        와일드 카드의 상한 제한. T와 그 자손들만 가능

<? super T>             와일드 카드의 하한 제한. T와 그 조상들만 가능

<?>                              제한없음. 모든 타입이 가능. <? extends Object>와 동일

 

예제/FruitBoxEx4.java

import java.util.*;

class Fruit {
    String name;
    int weight;

    Fruit(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }
    public String toString() { return name + "(" + weight + ")"; }
}

class Apple extends Fruit {
    Apple(String name, int weight) {
        super(name, weight);
    }
}

class Grape extends Fruit {
    Grape(String name, int weight) {
        super(name, weight);
    }
}

class AppleComp implements Comparator<Apple> {
    public int compare(Apple t1, Apple t2) {
        return t2.weight - t1.weight;
    }
}

class GrapeComp implements Comparator<Grape> {
    public int compare(Grape t1, Grape t2) {
        return t2.weight - t1.weight;
    }
}

class FruitComp implements Comparator<Fruit> {
    public int compare(Fruit t1, Fruit t2) {
        return t1.weight - t2.weight;
    }
}

public class FruitBoxEx4 {
    public static void main(String[] args) {
        FruitBox<Apple> appleBox = new FruitBox<Apple>();
        FruitBox<Grape> grapeBox = new FruitBox<Grape>();

        appleBox.add(new Apple("GreenApple", 300));
        appleBox.add(new Apple("GreenApple", 100));
        appleBox.add(new Apple("GreenApple", 200));

        grapeBox.add(new Grape("GreenGrape", 400));
        grapeBox.add(new Grape("GreenGrape", 300));
        grapeBox.add(new Grape("GreenGrape", 200));

        Collections.sort(appleBox.getList(), new AppleComp());
        Collections.sort(grapeBox.getList(), new GrapeComp());
        System.out.println(appleBox);
        System.out.println(grapeBox);
        System.out.println();
        Collections.sort(appleBox.getList(), new FruitComp());
        Collections.sort(grapeBox.getList(), new FruitComp());
        System.out.println(appleBox);
        System.out.println(grapeBox);
    }
}

class FruitBox<T extends Fruit> extends 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(); }
실행결과

[GreenApple(300), GreenApple(200), GreenApple(100)]
[GreenGrape(400), GreenGrape(300), GreenGrape(200)]

[GreenApple(100), GreenApple(200), GreenApple(300)]
[GreenGrape(200), GreenGrape(300), GreenGrape(400)]

 이 예제는 Collections.sort( )를 이용해서 appleBox와 grapeBox에 담긴 과일을 무게별 정렬한다. 메서드의 선언부는 다음과 같다.

 

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

 

 첫 번째 매개변수는 정렬할 대상이고, 두 번째 매개변수는 정렬할 방법이 정의된 Comparator이다.

 

 와일드 카드가 사용되지 않았다면, Apple이 대입됐을 때와 Grape가 대입됐을 때 각각 같은 코드를 적어야 한다. 또한 새로운 자손이 생길 때마다 추가해야 한다. 따라서 하한 제한의 와일드 카드를 사용하여 문제를 해결할 수 있다.

 

Comparator<? super Apple> : Comparator<Apple>, Comparator<Fruit>, Comparator<Object>

Comparator<? super Grape> : Comparator<Grape>, Comparator<Fruit>, Comparator<Object>

6. 지네릭 메서드

 메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다. Collections.sort( )가 지네릭 메서드이며, 지네릭 타입의 선언 위치는 반환타입 바로 앞이다.

 

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

 

 그리고 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 같은 타입 문자 T를 사용해도 같은 것이 아니다.

 

 또한, static멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에 지네릭 타입을 선언하면 사용할 수 있다.

 

public static <T extends Comparable<? super T>> void sort (List<T> list)

 

-> 'T'또는 그 조상의 타입(<? super T>)을 비교하는 Comparable을 구현하는(T extends Comparable) 타입 T를 요소로 하는 List(List<T>)를 매개변수로 허용한다.

7. 지네릭 타입의 제거

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

지네릭 타입이 <T extends Fruit>라면 T는 Fruit로 치환된다. <T>인 경우는 T는 Object로 치환되며 클래스 옆의 선언은 제거된다.

2 지네릭 타입을 제거한 후 타입이 일치하지 않으면 형변환을 한다.

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

 


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

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

 

와일드 카드가 포함되어 있는 경우

 


    static Juice makeJuice(FruitBox<? extends Fruit> box) {

        String tmp = "" ;
        for (Fruit f : box.getList( )) tmp += f + " " ;
        return new Juice(tmp) ;
    }





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

 

 

 

 

 

 

출처 | Java의 정석 (남궁 성)

728x90