2015-02-26

[轉載] Java 型態通配字元

轉載自:Java Essence: 我只收這種東西

如果你定義了以下的類別:
class Node<T> {
    T value;
    Node<T> next;

    Node(T value, Node<T> next) {
        this.value = value;
        this.next = next;
    }
}


如果在以下的例子中:
class Fruit {}
class Apple extends Fruit {
    @Override
    public String toString() {
        return "Apple";
    }
}

class Banana extends Fruit {
    @Override
    public String toString() {
        return "Banana";
    }
}


public class Main {
    public static void main(String[] args) {
        Node<Apple> apple = new Node<Apple>(new Apple(), null);
        Node<Fruit> fruit = apple;  // 編譯錯誤,incompatible types
    }
}



在範例中,apple 的型態是 Node<Apple>,而 fruit 的型態為 Node<Fruit>,你將 apple 所參考的物件 給 fruit 參考,那麼 Node<Apple> 該是一種 Node<Fruit> 呢?在上例中編譯器給你的答案為「不是」!

如 果 B 是 A 的子型態,而 Node<B> 被視為一種 Node<A> 型態,則稱 Node 具有共變性(Covariance)或有彈性的(flexible)。如 果 Node<A> 被視為一種 Node<B> 型態,則稱 Node 具有逆變性(Contravariance)。如果不具共變性或逆變性,則 Node 是不可變 的(nonvariant)嚴謹的(rigid)

Java 的泛型不支援共變性,不過可以使用型態通配字元 ?extends 來宣告變數,使其達到類似共變性,例如:
public class Main {
    public static void main(String[] args) {
        Node<Apple> apple = new Node<Apple>(new Apple(), null);
        Node<? extends Fruit> fruit = apple; // 類似共變性效果
    }
}


一個實際應用的例子是:
public class Main {
    public static void main(String[] args) {
        Node<Apple> apple1 = new Node<Apple>(new Apple(), null);
        Node<Apple> apple2 = new Node<Apple>(new Apple(), apple1);
        Node<Apple> apple3 = new Node<Apple>(new Apple(), apple2);

        Node<Banana> banana1 = new Node<Banana>(new Banana(), null);
        Node<Banana> banana2 = new Node<Banana>(new Banana(), banana1);

        show(apple3);
        show(banana2);
    }

    static void show(Node<? extends Fruit> n) {
        Node<? extends Fruit> node = n;
        do {
            System.out.println(node.value);
            node = node.next;
        } while(node != null);
    }
}



你的目的是可以顯示所有的水果節點,由於 show() 方法使用型態通配字元宣告參數,使得 n 具備類似共變性的效果,因此 show() 方法就可以顯示 Node<Apple> 也可以顯示 Node<Banana>

Java 的泛型亦不支援逆變性,不過可以使用型態通配字元 ?super 來宣告變數,使其達到類似逆變性,例如:
public class Main {
    public static void main(String[] args) {
        Node<Fruit> fruit = new Node<Fruit>(new Fruit(), null);
        Node<? super Apple> apple = fruit;
        Node<? super Banana> banana = fruit;
    }
}


一個實際應用的例子如下:
class Fruit {
    int price;
    int weight;
    Fruit(int price, int weight) {
        this.price = price;
        this.weight = weight;
    }
}

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

class Banana extends Fruit {
     Banana(int price, int weight) {
         super(price, weight);
     }
}

interface Comparator<T> {
    int compare(T t1, T t2);
}

class Basket<T> {
    private T[] things;
    Basket(T... things) {
        this.things = things;
    }
    void sort(Comparator<? super T> comparator) {
        // 作一些排序
    }
}


籃子(Basket)中可以置放各種物品,並可以傳入一個比較器(Comparator)進行排序。假設你分別在兩個籃子中放置了蘋果(Apple)與香蕉(Banana):
public class Main {
    public static void main(String[] args) {
        Comparator<Fruit> comparator = new Comparator<Fruit>() {
            public int compare(Fruit f1, Fruit f2) {
                return f1.price - f2.price;
            }
        };
        Basket<Apple> b1 = new Basket<Apple>(
            new Apple(20, 100), new Apple(25, 150)
        );
        Basket<Banana> b2 = new Basket<Banana>(
            new Banana(30, 200), new Banana(25, 250)
        );
        b1.sort(comparator);
        b2.sort(comparator);
    }
}


現在 b1 的型態為 Basket<Apple>,而 b2 的型態為 Basket<Banana>,你可以如上實作一個水果(Fruit)比較器,比較水果的價格進行排序,這可以同時適用於 Basket<Apple>Basket<Banana>

0 回應: