11
import java.util.function.*; 

class Test { 
    void test(int foo, Consumer<Integer> bar) { } 
    void test(long foo, Consumer<Long> bar) { } 
    void test(float foo, Consumer<Float> bar) { } 
    void test(double foo, Consumer<Double> bar) { } 
} 

可能模糊當我編譯這與javac -Xlint Test.java我得到了一些警告:警告:重載]方法M1與M2的方法

Test.java:4: warning: [overloads] test(int,Consumer<Integer>) in Test is potentially ambiguous with test(long,Consumer<Long>) in Test 
    void test(int foo, Consumer<Integer> bar) { } 
     ^
Test.java:6: warning: [overloads] test(float,Consumer<Float>) in Test is potentially ambiguous with test(double,Consumer<Double>) in Test 
    void test(float foo, Consumer<Float> bar) { } 
     ^
2 warnings 

如果我改變ConsumerSupplier警告消失。這個程序是免費的警告:

import java.util.function.*; 

class Test { 
    void test(int foo, Supplier<Integer> bar) { } 
    void test(long foo, Supplier<Long> bar) { } 
    void test(float foo, Supplier<Float> bar) { } 
    void test(double foo, Supplier<Double> bar) { } 
} 

這是爲什麼?這個警告是什麼意思?這些方法如何模糊?壓制警告是否安全?

+0

當您嘗試調用警告功能時會發生什麼? – 2015-03-19 02:46:29

+1

看來只有在以下情況下才會發出警告:a。它是一個功能界面(即消費者,供應商),以及b。任何接口的方法都包含任何參數......作爲一項測試,我製作了我自己的消費者/供應商接口副本,並與他們一起分享。可悲的是我對Java功能知之甚少,不知道爲什麼會爲此產生警告。 – Kai 2015-03-19 02:57:41

+0

更正:A點不正確,它不需要是功能接口,只有任何接口(類不產生警告),只定義了一個(非默認)方法。此外,我已將test(int,Consumer)的參數更改爲不可能像測試(對象,消費者)v.s一樣不明確的東西。測試(地圖,消費者),但警告仍然拋出。因此,除非你使用了一些非常古怪的編程,否則我真的不認爲你需要擔心這一點。 – Kai 2015-03-19 03:15:42

回答

16

這些警告是由於重載分辨率,目標鍵入和類型推斷之間的有趣交集而發生的。編譯器正在爲您提前想,並警告您,因爲大多數lambda表達式都是在沒有明確聲明的類型的情況下編寫的。例如,請考慮以下呼叫:

test(1, i -> { }); 

i是什麼類型的?在完成重載解析之前,編譯器無法推斷它......但值1匹配所有四個重載。無論選擇哪種超載都會影響第二個參數的目標類型,這反過來會影響爲i推斷的類型。是不是真的有足夠的信息,這裏編譯器來決定調用哪個方法,所以這條線實際上會導致編譯時錯誤:

error: reference to test is ambiguous 
      both method test(float,Consumer<Float>) in Test and 
      method test(double,Consumer<Double>) in Test match 

(有趣的是,它提到的floatdouble過載,但如果你對此有何評論其中的一個了,你得到同樣的錯誤關於long過載。)

可以想象當編譯器完成使用最特定的規則,從而選擇超載與int重載解析政策ARG。然後它會有一個明確的目標類型來應用於lambda。編譯器設計人員認爲這太微妙了,並且會出現程序員會驚訝於哪個超載最終被調用的情況。他們並沒有以可能意想不到的方式編譯程序,而是認爲讓這個錯誤變得更爲安全,並迫使程序員消除歧義。

編譯器在方法聲明處發出警告以指示程序員編寫的調用其中一個方法(如上所示)的可能代碼將導致編譯時錯誤。

消除歧義的電話,一會而不是必須寫

test(1, (Integer i) -> { }); 

或宣佈爲i參數其他一些明確的類型。另一種方法是在拉姆達前添加演員表:

test(1, (Consumer<Integer>)i -> { }); 

但是這可以說是更糟。您可能不希望API的調用者必須在每個通話點都與這種事情搏鬥。

這些警告不會發生在Supplier的情況下,因爲可以通過本地推理確定供應商的類型,而無需任何類型推斷。

您可能想重新考慮將此API放在一起的方式。如果你真的想要這些參數類型的方法,你可能很好地重命名方法testInttestLong等,並避免完全重載。請注意,Java SE API已在類似情況下完成此操作,例如Comparator上的comparingInt,comparingLongcomparingDouble;還有mapToInt,mapToLongmapToDoubleStream

+0

非常好的解釋!閱讀後沒有問題。 – TWiStErRob 2017-02-25 16:59:50