泛型的 super


繼續 泛型的 extends 的內容,如果BA的子類別,如果Node<A>視為一種Node<B>,則稱Node具有逆變性(Contravariance)。也就是說,如果以下程式碼片段沒有發生錯誤,則Node具有逆變性:

Node<Fruit> fruit = new Node<>(new Fruit(), null);
Node<Banana> node = fruit;  // 實際上會編譯錯誤

Java泛型並不支援逆變性,所以實際上第二行會發生編譯錯誤。可以使用型態通配字元?super來宣告,以達到類似逆變性的效果,例如:

Node<Fruit> fruit = new Node<>(new Fruit(), null);
Node<? super Banana> node = fruit;
Node<? super Apple> node = fruit;

這樣的語法支援看似奇怪,可以從實際範例來瞭解如何支援逆變性。假設你想設計了一個籃子,可以指定籃中置放的物品,放置的物品會是同一種類(例如都是一種Fruit),並有一個排序方法,可指定java.util.Comparator針對籃中物品進行排序,那麼請問以下泛型未填寫部份該如何宣告?

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

宣告為<? extends T>嗎?如果是這樣的話,籃中裝Apple時,在呼叫sort()方法時,就必須傳入實作Comparator<Apple>的物件。例如針對price比較時:

Basket<Apple> apples = new Basket<>(
    new Apple(20, 100), new Apple(25, 150));

// 以下使用 JDK8 Lambda 可寫為
// apples.sort((apple1, apple2) -> apple1.price - apple2.price);

apples.sort(new Comparator<Apple>() {
    @Override
    public int compare(Apple apple1, Apple apple2) {
        return apple1.price – apple2.price;
    }
});

因為宣告為Basket<Apple>sort()方法就只接受Comparator<? extends Apple>的物件。同樣地,籃中裝Banana時,在呼叫sort()方法時,就必須傳入實作Comparator<Banana>的物件。例如:

Basket<Banana> bananas = new Basket<>(
    new Banana(30, 200), new Banana(50, 300));


// 以下使用 JDK8 Lambda 可寫為
// bananas.sort((banana1, banana2) -> banana1.price - banana2.price);

bananas.sort(new Comparator<Banana>() {
    @Override
    public int compare(Banana banana1, Banana banana2) {
        return banana1.price - banana2.price;
    }
});

然而就以上兩段程式範例來看,AppleBanana都是針對price比較,也就是都是針對水果價格比較,也就是都是針對Fruit繼承而來的price比較,並不需要個別指定Comparator<Apple>Comparator<Banana>兩個比較器,你希望可以有以下的操作:

// 以下使用 JDK8 Lambda 可寫為
// Comparator<Fruit> priceComparato = (fruit1, fruit2) -> fruit1.price - fruit2.price;

Comparator<Fruit> priceComparator = new Comparator<Fruit>() {
    @Override
    public int compare(Fruit fruit1, Fruit fruit2) {
        return fruit1.price – fruit2.price;
    }
};

Basket<Apple> apples = new Basket<>(
    new Apple(20, 100), new Apple(25, 150));
apples.sort(priceComparator);

Basket<Banana> bananas = new Basket<>(
    new Banana(30, 200), new Banana(50, 300));
bananas.sort(priceComparator);

如果是如此,那麼sort()方法的Comparator泛型應該宣告為<? super T>。例如:

package cc.openhome;

import java.util.Comparator;

public class Basket<T> {
    public T[] things;

    public Basket(T... things) {
        this.things = things;
    }

    public void sort(Comparator<? super T> comparator) {
        Arrays.sort(things, comparator);
    }
}

範例中使用了java.util.Arrayssort()方法進行排序,你可以查看API文件,可以發現sort()方法的第二個參數,正是宣告為Comparator<? super T>

<? super T>實際應用


可以用以下範例看看是否能正確排序:

package cc.openhome;

import java.util.*;

public class ContravarianceDemo {
    public static void main(String[] args) {
        // 以下使用 JDK8 Lambda 可寫為
// Comparator<Fruit> byPrice = (fruit1, fruit2) -> fruit1.price - fruit2.price;

Comparator<Fruit> byPrice = new Comparator<Fruit>() { @Override public int compare(Fruit fruit1, Fruit fruit2) { return fruit1.price - fruit2.price; } }; Basket<Apple> apples = new Basket<>( new Apple(25, 150), new Apple(20, 100)); apples.sort(byPrice); printlnForEach(apples); Basket<Banana> bananas = new Basket<>( new Banana(50, 300), new Banana(30, 200)); bananas.sort(byPrice); printlnForEach(bananas); } private static void printlnForEach(Basket<? extends Fruit> basket) { for(Fruit fruit : basket.things) { System.out.printf("%s(%d, %d) ", fruit.getClass().getName(), fruit.price, fruit.weight); } System.out.println(); } }

執行結果如下:

cc.openhome.Apple(20, 100) cc.openhome.Apple(25, 150)
cc.openhome.Banana(30, 200) cc.openhome.Banana(50, 300)

如果泛型類別或介面不具共變性或逆變性,則稱為不可變的(Nonvariant)或嚴謹的(Rigid),Java屬於此類。