2015-09-06 64 views
6

我很努力地理解Java中的差異是如何工作的。Java類型差異,泛型類型的消費者

在以下示例中,我定義了一個函數test,它需要Consumer。該函數被定義爲沒有反轉,因此我預計Consumer<Object>不是Consumer<Pair<Animal, Animal>>的子類型。然而,代碼編譯和測試接受lambda Variance:::superAction

我錯過了什麼?

import org.apache.commons.lang3.tuple.ImmutablePair; 
import org.apache.commons.lang3.tuple.Pair; 

import java.util.function.Consumer; 

public class Variance { 

    public static void main(String[] args) { 
    test(Variance::exactMatchAction); 
    test(Variance::superAction); 
    } 

    private static void exactMatchAction(Pair<Animal, Animal> pair) { 
    System.out.println(pair.getLeft().getClass().getName()); 
    } 

    private static void superAction(Object obj) { 
    System.out.println(obj.getClass().getName()); 
    } 

    private static void test(Consumer<Pair<Animal, Animal>> action) { 
    action.accept(ImmutablePair.of(new Animal(), new Animal())); 
    action.accept(ImmutablePair.of(new Dog(), new Dog())); 
    } 

    static class Animal { } 

    static class Dog extends Animal { } 
} 

編輯:每@ Thielo的評論,參考superAction被脫到Consumer<Pair<Animal, Animal>>不是一個Consumer<Object>

正確的類型給予test方法是一樣的東西:

void test(Consumer<? super Pair<? extends Animal, ? extends Animal>>) 

這種類型將使我們能夠通過一個Consumer<Object>test,也使我們能夠調用帶參數的消費者喜歡Pair<Dog, Dog>,而不是隻Pair<Animal, Animal>

作爲後續問題,使用此更新的測試類型,它不再接受像void exactMatchAction<Pair<Animal, Animal>>這樣的方法參考,只有void exactMatchAction<Pair<? extends Animal, ? extends Animal>>。爲什麼是這樣?

+0

據我所知,沒有警告。 – asp

+0

不知道這是如何實現的,但它確實有道理。對象的消費者也可以消費對。如果你改變這個參數來表示一個字符串,你會得到一個錯誤,對嗎? – Thilo

+0

真的,我不知道。但我的猜測是這與'@ FunctionalInterface'的處理方式有關。它可能不關心接口本身的類型參數,只是它們在方法中被引用的方式。所以'Object - > void'方法可以用作'Pair <> - > void',因爲如果它可以消耗*任何對象*,那麼當然可以消耗一對。 – ryachza

回答

0

方法參考表達式(如您Variance::superAction)是聚表達式(JLS8,15.13)。表達式的目標類型(JLS8,15.3)可能會影響poly表達式的類型,在您的情況下,該類型是該上下文中預期的類型(JLS8,5),即Consumer<Pair<Animal, Animal>>

細節在JLS8,15.13.2中詳細說明。基本思想是對功能接口類型進行特殊處理,例如Consumer。具體來說,方法類型只需要全等到函數類型(即Pair<Animal, Animal> -> void - 注意Consumer已經從這裏的類型考慮中消失了),這是通過「識別單個編譯時聲明參考「(並且具有void作爲返回類型)。這裏,「識別」一個聲明的概念可以追溯到15.12.2,並且基本上描述了方法重載解析過程。換句話說,語言現在採用Consumer<Pair<Animal, Animal>>.accept()(即Pair<Animal, Animal>)預期的函數參數,並檢查方法引用是否可以用該方法調用(如果存在多個具有相同名稱的靜態方法,則解析重載)。