Java

와일드카드

voider 2020. 9. 9. 10:57

참고 : 자바의 정석

와일드 카드

 

기호 '?' 표현하는데, 와일드카드는 어떤 타입도 될 수 있다.

아래 코드를 보자.

package com.javaex.generics;

import java.util.ArrayList;
import java.util.List;

public class FruitsBoxEx3 {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<>();
        FruitBox<Apple> appleBox = new FruitBox<>();

        fruitBox.add(new Apple());
        fruitBox.add(new Grape());

        appleBox.add(new Apple());

        System.out.println(Juicer.makeJuice(fruitBox));
        System.out.println(Juicer.makeJuice(appleBox));
    }

}

class Juice {
    String name;

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

    @Override
    public String toString() {
        return this.name;
    }
}

class Juicer {
    static Juice makeJuice(FruitBox<? extends Fruit> box) {
        String tmp = "";

        for(Fruit fruit: box.getList()) {
            tmp += fruit + " ";
        }

        return new Juice(tmp);
    }
}

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

class Box<T> {
    List<T> list = new ArrayList<>();

    List<T> getList() {
        return list;
    }

    void add(T item) {
        list.add(item);
    }

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

    int size() {
        return list.size();
    }
    @Override
    public String toString(){
        return list.toString();
    }
}

Fruit, Grape, Apple 클래스는 제외했다. 이전 글에서 볼 수 있다.

핵심은 이 부분이다.

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

만약, 와일드카드를 사용할 수 없다고 가정하자.

static Juice makeJuice(FruitBox<Fruit> box)
static Juice makeJuice(FruitBox<Apple> box)
static Juice makeJuice(FruitBox<Group> box)

이런 식으로 중복 정의했을 것이다. 그러나 이 코드는 컴파일 에러가 발생한다. 지네릭 타입이 다른 것만으로 오버로딩이 성립하지 않는다. 이 문제를 해결하는 것이 와일드카드'?'이다.

'?'자체는 Object와 같다. 'extends'와 'supper'로 상한과 하한을 제한한다.

<? extends T> // 와일드카드 상한 제한. T와 그 자손만 가능
<? super T>    //와일드카드 하한제한. T와 그 조상들만 가능
<?>    // == 제한 없음. 모든 타입 가능.(==<? extends Object>)

위 코드에서 와일드카드를 적용한 static메서드를 호출하는 부분

System.out.println(Juicer.makeJuice(fruitBox);
System.out.println(Juicer.makeJuice(appleBox);

와일드카드를 사용함으로써 모든 종류의 FruitBox를 매개변수의 인자로 받을 수 있다. 그러나 box의 요소가 Fruit의 자손이라는 보장이 없으므로 for문에서 box에 저장된 요소를 Fruit타입 참조변수로 받을 수 없다.

for(Fruit fruit : box.getList()) {
    ...
}

위 코드에서 box.getList()가 Fruit가 아닐 수도 있으므로 에러가 발생해야 맞지만,

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

지네릭 클래스 FruitBox선언부에 이미 Fruit를 상속하는 타입만을 받도록 제한했기 때문에 아무 문제 없이 컴파일된다.

    package com.javaex.generics;


import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

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

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

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

        System.out.println("===========정렬 전===========");
        System.out.println("appleBox : " + appleBox);
        System.out.println("grapeBox : " + grapeBox);
        System.out.println();

        Collections.sort(appleBox.getList(), new FruitComp());
        Collections.sort(grapeBox.getList(), new FruitComp());

        System.out.println("===========정렬 후===========");
        System.out.println("appleBox : " + appleBox);
        System.out.println("grapeBox : " + grapeBox);
    }
}

class Fruit {
    String name;
    int weight;

    public Fruit(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return name + "("+weight+")";
    }
}   //end - Fruit

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

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

class FruitComp implements Comparator<Fruit> {  //내림차순

    @Override
    public int compare(Fruit o1, Fruit o2) {
        return o2.weight - o1.weight;
    }
}

class AppleComp implements Comparator<Apple> {

    @Override
    public int compare(Apple o1, Apple o2) {
        return o2.weight - o1.weight;
    }
}

class GrapeComp implements Comparator<Grape> {
    @Override
    public int compare(Grape o1, Grape o2) {
        return o2.weight - o1.weight;
    }
}

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

class Box<T> {
    List<T> list = new ArrayList<>();

    void add(T item) {
        list.add(item);
    }

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

    List<T> getList() {
        return list;
    }

    int size() {
        return list.size();
    }

    @Override
    public String toString() {
        return list.toString();
    }

}

Collections의 sort()를 이용해서 무게별로 정렬하는 예제다.

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

sort()의 선언부다.

만약 와일드 카드를 사용하지 않았다고 가정한다면

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

선언부는 이렇게 된다. <T>에 Apple을 대입하면 이렇게 바뀐다.

    static <Apple> void sort(List<Apple> list, Comparator<Apple> c)

List<Apple>를 정렬하기 위해 Comparator<Apple>가 필요하다. 그래서 Comparator<Apple>를 구현한 클래스를 만들어야 한다.

class AppleComp implements Comparator<Apple> {
    @Overrride
    public int compare(Apple o1, Apple o2) {
        return o2.weight - o1.weight;
    }
}

만약 Apple이 아니고 Grape가 대입된다면?
똑같이 Comparator<Grape>를 구현한 클래스를 하나 더 정의해야 한다.
타입만 다를 뿐 완전히 똑같은 클래스를 만들어야 한다는 것은 낭비다. 이 문제를 해결하기 위해 타입 매개변수에 하한 제한(super) 와일드카드를 적용해둔 것이다.

그럼 Apple이 들어오든, Grape이 들어오든 상관 없이 정렬할 수 있다.


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

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

Comparator <? super Apple>이라는 의미는 Apple과 그 조상이 가능하다는 뜻이다.
Comparator <Apple>, Comparator<Fruit>, Comparator<Object>

그래서

class FruitComp implements Comparator<Fruit> { 
    @Override
    public int compare(Fruit o1, Fruit o2) {
        return o2.weight - o1.weight;
    }
}```
이렇게 정의해두면 Apple과 Grape을 모두 정렬할 수 있다.
### 질문
- 왜 와일드카드 대신 <T&gt;로 받지 못하나?

'Java' 카테고리의 다른 글

람다Lambda  (0) 2020.09.09
제네릭 메서드Generic Method  (0) 2020.09.09
제네릭 클래스Generics Class  (0) 2020.09.09
TreeMap  (0) 2020.09.09
HashMap  (0) 2020.09.09