下面的代碼無法在 OpenjDK 11 上編譯。在我看來,B 中的 test1 應該覆寫 A 中的 test1,因為:
- 這些方法具有相同的名稱。
- 這些方法具有相同的引數串列。
- 這些方法具有相同的可見性。
- 這些方法實際上不會拋出可能不兼容的檢查例外。
- 它們的回傳型別是協變的。
我拿了一個反編譯器,分別反編譯了每個類。編譯后的代碼實際上像我預期的那樣作業。它用 Number 替換了 U extends Number,用 Integer 替換了 T extends Integer 等等。但是當我嘗試將這兩個類一起編譯時,我在第二個類上出現錯誤,說第二個類中的測驗不會覆寫第一個類中的方法。
我在這里遺漏了一些大大小小的東西。它可能與5有關。也許型別不是協變的。你能幫助我嗎?
class A {
<U extends Number, T extends Number> U test(T test) {
System.out.println("In A.test(T test)");
return null;
}
//Decompiler shows that the above method erases to
// Number test(Number test)
// Just like the method below.
Number test2(Number test) {
return null;
}
}
class B extends A {
//Unsuccessful override. Compiler error.
@Override
<U extends Integer, T extends Number> U test(T test) {
System.out.println("In B.test(T tesT)");
return null;
}
//Decompiler shows that the above method erases to
//Integer test(Number test)
//Just like the method bellow.
//Successful override
@Override
Integer test2(Number test) {
return null;
}
}
uj5u.com熱心網友回復:
您的test方法產生錯誤的原因是它們具有完全不相關的不同簽名。請注意,方法的簽名由其名稱、引數串列和型別引陣列成。
參考 Java 語言規范:
如果兩個方法或建構式 M 和 N 具有相同的名稱、相同的型別引數(如果有)(第 8.4.4 節),并且在將 N 的形式引數型別調整為M,形參型別相同。
最重要的是,你的兩個test方法不具有相同型別的引數,因為U在A.test有不同的約束來自U于B.test。
如果以下兩個都為真,則兩個方法或建構式 M 和 N 具有相同的型別引數:
M 和 N 具有相同數量的型別引數(可能為零)。
其中A1, ..., An是M的型別引數,B1, ..., Bn是N的型別引數,設θ=[B1:=A1, ..., Bn:=An]。然后,對于所有 i (1 ≤ i ≤ n),Ai 的邊界與應用于 Bi 的邊界的 θ 型別相同。
想想如果B.test真的被覆寫會發生什么A.test。您可以將型別傳遞給U超出其范圍的型別引數!
A a = new B();
// This will call B.test, and U is Double, T is Integer
// but U should extends Integer!
Double x = a.test((Integer)0);
欲了解更多資訊,這里是壓倒一切的時候發生的精確的規則。請注意,您的串列中的標準#4 和#5 實際上并未被考慮。它們只是附加要求,如果您破壞它們,會使您的代碼無法編譯。即使您違反了這些要求,一種方法仍被“定義”以覆寫另一種方法。它們列在這里的JLS。
uj5u.com熱心網友回復:
鑒于我發現相關的 JLS 規則(在上述 Sweeper 的回答中參考)非常令人困惑,因此提及由 Enthuware ( https://enthuware.com/ )的偉人制定的規則很有用。他們沒有提到可見性和例外規則,但他們詳細介紹了泛型。他們似乎解釋了我在上面評論中提到的兩個案例。我將在這里再次陳述案例:
class A {
public <T,U> U test(T test) {
return null;
}
}
class B extends A {
//Successful override even though type parameters do not match
//This method even has none.
@Override
public Number test(Object test) {
return null;
}
}
class C {
public Object test(Object test) {
return null;
}
}
class D extends C {
//Same situation as A and B, but with places being exchanged.
//Now the generic method overrides non-generic.
//And we have error.
@Override
public <T,U> U test(T test) {
return null;
}
}
現在,由 Enthuware 制定的步驟:
檢查有效覆寫的步驟:
首先,檢查方法簽名(即方法名稱和引數串列)。如果子類中方法的簽名與超類中方法的簽名匹配,那么它可能是一個有效的覆寫,否則它只是一個多載的方法。請注意,簽名不包括引數名稱和引數的泛型型別規范。
注意:我認為他們的意思是我們用它的邊界替換型別引數,然后比較這兩種方法。
其次,如果是潛在的覆寫,請檢查引數的泛型型別規范。如果重寫方法不使用引數型別的泛型型別規范,那么它是有效的。反過來是無效的,即允許覆寫方法洗掉泛型型別規范,但如果被覆寫的方法沒有泛型型別規范,則不允許添加泛型型別規范。如果這兩種方法都具有泛型型別規范,則規范必須完全匹配。例如,如果重寫方法具有 Set<Integer>,則重寫方法可以使用 Set 或 Set<Integer>。但如果被覆寫的方法具有 Set,則覆寫方法也必須具有 Set 才能有效覆寫。
第三,如果它是潛在的覆寫,請檢查回傳型別。Java 允許“協變”回傳,這意味著覆寫方法的回傳型別必須相同或者是被覆寫方法中提到的回傳型別的子型別。檢查沒有泛型型別規范的兩個回傳型別。如果覆寫方法的回傳型別相對于覆寫方法的回傳型別是協變的(例如,ArrayList 與 List 是協變的),則執行相同的檢查,包括泛型型別規范(例如,ArrayList<CharSequence> 是協變的與 List<? 擴展 CharSequence>)。不要對代碼中存在的 <T> 感到困惑。相同的覆寫規則仍然適用。<T> 中的 T 稱為“型別”引數。它用作在呼叫方法時實際使用的任何型別的占位符。例如,如果您使用 List<String> 呼叫方法 <T> List<T> transform(List<T> list),則 T 將被鍵入為 String。因此,它將回傳 List<String>。如果在另一個地方,您使用 Integer 呼叫相同的方法,則 T 將被鍵入為 Integer,因此,該呼叫的方法的回傳型別將為 List<Integer>
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/407139.html
標籤:
上一篇:使用泛型在java中實作工廠模式
下一篇:如何處理回傳雙指標的函式的輸出
