[Java] 와일드카드 타입(Wildcard type)

이 글의 목적

  • 와일드카드 타입과 로 타입을 어떨 때 주로 사용하는지, 그 차이를 명확히 알 수 있다.

와일드카드 <?>

  • 하나의 참조 변수로 대입된 타입이 다른 객체를 참조할 수 있다.
    ArrayList<? extends Product> list = new ArrayList<TV>(); //OK
    ArrayList<? extends Product> list = new ArrayList<Audio>(); //OK
    ArrayList<Product> list = new ArrayList<TV>(); //에러. 대입된 타입 불일치
    

    일반적으로 Generic 클래스는 참조변수와 생성자에 대입된 타입이 일치해야한다.
    그런데 위 세번째 라인의 경우 참조변수는 Product, 생성자에 대입된 타입은 TV로 일치하지 않아 에러가 발생한다.

그래서 이 답답함을 없애기 위해 등장한 것이 바로 와일드카드이다.
와일드카드를 사용하면 참조변수와 생성자에 대입된 타입이 일치하지 않아도 된다.
총 3가지 용법이 있으며 주로 <? extends T>를 많이 쓴다.

  • <? extends T>: Upper Bounded Wildcards. 상위 클래스 제한. T와 그 자손들만 가능
  • <? super T>: Lower Bounded Wildcards. 하위 클래스 제한. T와 그 조상들만 가능
  • <?>: Unbounded Wildcards, 비한정적 와일드카드 타입. 제한 없음. 모든 타입이 가능. <? extends Object>와 동일

메서드의 매개변수에도 와일드 카드를 사용한다.

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

Fruit의 자손인 Apple과 자기 자신이 메서드의 매개변수로 사용될 수 있다.

와일드카드 타입을 사용한 소스코드 예시

  • Juice.java
package chapter5.item28.Juice;

public class Juice {
    String name;

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

    public String toString(){
        return name;
    }
}

class Juicer {
    //? extends Fruit: Fruit 자기 자신과 그 자손들인 Apple, Grape
    static Juice makeJuice(FruitBox<? extends Fruit> box){
        String tmp = "";
        for (Fruit f : box.getList()) // 향상된 for문
            tmp += f + " ";

        return new Juice(tmp);
    }
}

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";
    }
}
  • FruitBox.java
package chapter5.item28.Juice;

public class FruitBox<T extends Fruit> extends Box<T> {
}
  • Box.java
package chapter5.item28.Juice;

import java.util.ArrayList;

public class Box<T>{
    private 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();
    }
}
  • Client.java
package chapter5.item28.Juice;

public class Client {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
        FruitBox<Apple> appleBox = new FruitBox<Apple>();
        // ? extends Fruit: Fruit과 그 자손들 Apple, Grape
        //FruitBox<? extends Fruit> 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(appleBox));
        System.out.println(Juicer.makeJuice(fruitBox));
    }
}

참고