簡體   English   中英

為什么我會在這段代碼中收到歧義錯誤?

[英]Why do I get an ambiguity error in this code?

假設我們有這 3 個類:

class A { }
class B extends A { }

public class App {
    static void f(int i, A a) { }
    static void f(float j, B b) { }
   
    static public void main() {
        int i = 0;
        B b = new B();
        App.f(i, b);
    }
}

這會產生錯誤:

App.java:11: error: reference to f is ambiguous
        App.f(i, b);
           ^
  both method f(int,A) in App and method f(float,B) in App match
1 error

為什么它不選擇f(int, A)類型,因為i是 integer?

之所以模棱兩可,原因有二:

  • 兩種重載都適用,並且;
  • 兩個重載都不比另一個更具體

請注意, f(int, A)重載和f(float, B)重載都可以使用參數(i, b)調用,因為存在從intfloat的隱式轉換,以及從B到的隱式轉換A

當有不止一種適用的方法時會發生什么? Java應該選擇最具體的方法。 這在語言規范的§15.12.2.5中有描述。 事實證明,這些重載中的一個並非比另一個更具體。

一個適用的方法 m1 比另一個適用的方法 m2 更具體,用於使用參數表達式 e1、...、ek 的調用,如果以下任何一項為真:

  • m2 是通用的 [...]

  • m2 不是泛型的,m1 和 m2 可以通過嚴格或松散調用來應用,其中 m1 有形參類型 S1,...,Sn,m2 有形參類型 T1,...,Tn,類型 Si 更多對於所有 i (1 ≤ i ≤ n, n = k),參數 ei 比 Ti 更具體。

  • m2 不是通用的,並且 m1 和 m2 可通過變量 arity 調用應用 [...]

只有第二點適用於f的兩個重載。 為了使其中一個重載比另一個更具體,一個重載的每個參數類型都必須比另一個重載中的相應參數類型更具體。

對於任何表達式,如果 S <: T ( §4.10 ),類型 S 比類型 T 更具體。

注意“<:”是子類型關系。 B顯然是A的子類型。 float實際上是int的超類型(不是子類型!)。 這可以從§4.10.1中列出的直接子類型關系推導出來。 因此,沒有一個重載比另一個更具體。

語言規范繼續討論最大限度地具體的方法,這並不真正適用於f這里。 最后,它說:

否則,方法調用不明確,出現編譯時錯誤。

更多示例

static void f(int x) {}
static void f(float x) {}

當用int調用時並不模棱兩可,因為int重載更具體。

static void f(int x, B a) {}
static void f(float x, A a) {}

當使用參數類型(int, A)調用時,它並沒有歧義,因為(int, B)重載更具體。

static void f(int x, A a) {}
static void f(float x, A a) {}

當使用參數類型(int, A)調用時,它並沒有歧義,因為(int, A)重載更具體。 請注意,子類型關系是自反的(即AA的子類型)。

請注意,在 java 中調用重載方法:

原始類型的優先順序:

相同類型 > 自動加寬 > 拳擊 > 向上投射(父類) > Super Class

參考類型的優先順序:

相同類型 > Upcasting(父類) > Super Class > 拆箱

解釋:

    // you have here a method who accept an int = same Type 
    //  f(int i, A a) { } and this method can accept the other parameter 
    //  because B is a subclass of A
    int i = 0;
    // But You have a method who accept a B reference = same Type 
    // f(float j, B b) { } and this method can accept the other parameter 
    // because float is actually a supertype of int as mentionned by Sweeper
    B b = new B();

 // So calling the method with an int and a B reference 
 // will confuse the compiler because both of the two methods
 // can accept the other parameter
    App.f(i, b);

兩件事情:

  • 類型擴展(第一個參數)
  • Inheritance(第二個參數)

類型擴大float是比int更“寬”的類型,因此傳遞的int值可以很容易地打包到更大的float框中。 如果我們想將float “打包”到int中,它將不起作用,因為我們可能會丟失浮點。 在提供的情況下,擁有一個int我們可以潛在地將兩個函數都傳遞給它。

fFloat(float f){}
int intValue = 221;
fFloat(intValue); // inside f, intValue is treated as 221.0

fInt(int i){}
float floatVal = 221.221;
fInt(floatVal); // what should compiler do with remaining .221 part?

Inheritance : A is a base class, while B is one of subclasses of A. So, when we declare method parameter as a base class, we can provide there both base class and its instance (but we will be able to use B b instance在f(int i, A a)內部,類似於A類型。因此,我們也可以將B b = new B();傳遞給這兩種方法。

class A {
public void f() {}
}

class B extends A {
public void g() {}
}

fA(A a){}
B bInst = new B();
fA(bInst); // you can refer only to f() method, not g()

fB(B b){}
A aInst = new A();
fB(aInst); // you cannot do this, as subclass can have some additional
// stuff, that superclass does not have

//but even
A aInstBImpl = new B();
fB(aInstBImpl); // will not work without explicit casting as it is
// kind of A instance with B implementation - here polymorphism comes into action

由於所有 2 個參數(從 2 個可能)都可以適用於這兩種方法,而無需任何明確的“采取行動”,因此會出現歧義錯誤。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM