有很多複雜的在交叉路口重載分辨率和類型推斷。 lambda規範的current draft具有所有的細節。 F節和G節分別介紹重載分辨率和類型推斷。我不會假裝理解這一切。然而,引言中的總結部分是可以理解的,而且我建議人們閱讀它們,特別是F和G部分的摘要,以便了解這方面的情況。
爲了簡要回顧這些問題,請考慮在存在重載方法的情況下調用某些參數的方法。重載解析必須選擇正確的方法來調用。該方法的「形狀」(參數或參數數量)是最重要的;很顯然,帶有一個參數的方法調用無法解析爲帶有兩個參數的方法。但重載的方法通常具有相同數量的不同類型的參數。在這種情況下,類型開始變得重要。
假設有兩個重載方法:
void foo(int i);
void foo(String s);
和一些代碼具有下列方法調用:
foo("hello");
顯然,這解決了第二方法,基於所述參數的是所述類型通過。但是如果我們正在做重載解析,而且參數是lambda? (尤其是那些類型是隱式的,依賴於類型推斷來建立類型的類型。)回想一下,lambda表達式的類型是從目標類型中推斷出來的,也就是這個上下文中預期的類型。不幸的是,如果我們有重載的方法,我們沒有一個目標類型,直到我們解決了我們要調用的重載方法。但是由於我們還沒有lambda表達式的類型,所以在重載解析期間我們不能使用它的類型來幫助我們。
讓我們來看看這裏的例子。考慮示例中定義的接口A
和抽象類B
。我們有一個包含兩個重載類C
,然後是一些代碼調用apply
方法,並傳遞一個lambda:
public void apply(A a)
public B apply(B b)
c.apply(x -> System.out.println(x));
兩個apply
重載具有相同數量的參數。參數是一個lambda,它必須匹配一個功能接口。 A
和B
是實際類型,因此它表明A
是一個功能接口,而B
不是,因此重載解析的結果是apply(A)
。在這一點上,我們現在有一個針對lambda的目標類型A
,並且類型推斷爲x
。
現在的變化:
public void apply(A a)
public <T extends B> T apply(T t)
c.apply(x -> System.out.println(x));
相反的實際類型,的apply
第二過載是一個通用類型的變量T
。我們還沒有進行類型推斷,因此我們不考慮T
,至少在重載解析完成之後纔會這樣做。因此,兩種重載仍然適用,也不是最具體的,並且編譯器發出呼叫不明確的錯誤。
你可能會認爲,既然我們知道是有T
結合的B
一個類型,它是一類,而不是一個功能接口,在lambda不可能適用於這個過載,因此應該被排除在重載解析期間,消除歧義。我不是那個有爭論的人。 :-)這可能確實是編譯器或甚至規範中的錯誤。
我知道這個領域在Java 8的設計過程中經歷了一系列的變化。早期的變化確實試圖將更多的類型檢查和推理信息帶入重載解決階段,但是它們很難實現,並理解。 (是的,甚至比現在更難理解。)不幸的是問題不斷出現。決定通過減少可能超載的範圍來簡化事情。
類型推理和超載是反對的;從第1天開始,許多類型推斷的語言都禁止重載(除了可能在arity上。)因此,對於需要推理的隱式lambdas這樣的構造,似乎合理的是放棄一些重載力的東西來增加可以使用隱式lambdas的情況的範圍。
- Brian Goetz, Lambda Expert Group, 9 Aug 2013
(這是一個相當有爭議的決定需要注意的是有在這個線程116點的消息,並且還有其他一些討論這個問題的線程。)
之一這個決定的後果是某些API必須改變以避免超載,例如the Comparator API。此前,Comparator.comparing
方法有四個重載:
comparing(Function)
comparing(ToDoubleFunction)
comparing(ToIntFunction)
comparing(ToLongFunction)
的問題是,這些重載由拉姆達返回類型只能有區別的,我們其實從來沒有完全得到了類型推斷與隱式類型的lambda表達式在這裏工作。爲了使用這些,總是必須爲lambda轉換或提供顯式類型參數。這些API後來改爲:
comparing(Function)
comparingDouble(ToDoubleFunction)
comparingInt(ToIntFunction)
comparingLong(ToLongFunction)
這是有點笨拙,但它是完全明確的。類似的情況Stream.map
,mapToDouble
,mapToInt
和mapToLong
,並在周圍的API其他一些地方發生。
底線是在存在類型推斷的情況下獲得重載分辨率是非常困難的,並且語言和編譯器設計人員爲了使類型推斷更好地工作,而將語言和編譯器設計人員從重載分辨率中分離出來。出於這個原因,Java 8 API避免了預計將使用隱式類型lambda的重載方法。
您可以顯式調用與'C第一種方法。 apply(x - > System.out.println(x));'。但它看起來像它應該沒有它工作... – assylias
我猜的答案是B的子類型牛逼可能實現A. –
@ user2580516是的,這可能是問題,我沒有想到這種可能性。 – schenka7