[英]What's difference between these two generic methods?
抱歉,標題太抽象了,但我不知道如何將這個問題壓縮成一個句子。
我不明白為什么call1
編譯失敗而call2
運行良好。 call1
和call2
邏輯上不是一樣的嗎?
public class Test {
@AllArgsConstructor
@Getter
private static class Parent {
private String name;
}
private static class Child extends Parent {
Child(final String name) {
super(name);
}
}
private void call1(final List<List<? extends Parent>> testList) {
System.out.println(testList.get(0).get(0).getName());
}
private <T extends Parent> void call2(final List<List<T>> testList) {
System.out.println(testList.get(0).get(0).getName());
}
public void show() {
final List<List<Parent>> nestedList1 = List.of(List.of(new Parent("P1")));
final List<List<Child>> nestedList2 = List.of(List.of(new Child("C1")));
call1(nestedList1); // compile error
call1(nestedList2); // compile error
call2(nestedList1); // okay
call2(nestedList2); // okay
}
}
這可能確實是一個令人驚訝的行為,因為這兩種方法都產生完全相同的字節碼(方法的簽名除外)。 為什么第一次調用會產生編譯時錯誤
incompatible types: List<List<Parent>> cannot be converted to List<List<? extends Parent>>
首先,當你調用一個方法時,你必須能夠將實際參數賦值給方法的arguments。 所以,如果你有一個方法
void method(SomeClass arg) {}
你稱之為
method(someArgument);
那么下面的賦值必須是有效的:
SomeClass arg = someArgument;
僅當someArgument
的類型是SomeClass
的子類時,此分配才有可能。
現在,Java 中的 arrays 是協變的,如果某個subClass
是某個superClass
的子類,則subClass[]
是superClass[]
的子類。 這意味着下面的方法
void method(superClass[] arg) {}
可以使用 class subClass[]
的參數調用。
但是Java中的generics是不變的。 對於任何兩個不同的類型T
和U
,類List<T>
和List<U>
既不是彼此的子類也不是彼此的超類,即使原始類型T
和U
之間存在這種關系。 如您所知,您可以使用通配符將List
參數傳遞給方法:
void method(List<?> arg) {}
但是,這是什么意思? 正如我們所見,為了使調用工作,以下分配必須有效:
List<?> arg = ListOfAnyType;
這意味着List<?>
成為所有可能列表的超類。 這是很重要的一點。 List<?>
的類型不是任何列表的任何類型,因為它們都不能相互賦值,它是一種特殊類型,可以從任何列表賦值。 比較以下兩種類型: List<?>
和List<Object>
。 第一個可以包含任何類型的列表,而第二個可以包含任何類型的對象列表。 所以,如果你有兩種方法:
void method1(List<?> arg) {}
void <T> method2(List<T> arg) {}
那么第一個將接受任何列表作為參數,而第二個將接受任何類型對象的列表作為參數。 好吧,這兩種方法的工作方式相同,但是當我們添加新級別 generics 時,區別就變得很重要了:
void method1(List<List<?>> arg1) {}
void <T> method2(List<List<T>> arg2) {}
第二種情況很簡單。 arg2
的類型是一個列表,其中包含元素,其類型是某種(未知)類型的列表。 因此,我們可以將任何列表列表傳遞給 method2。 但是,第一個方法的參數arg1
具有更復雜的類型。 它是一個元素列表,具有非常特殊的類型,它是任何列表的超類。 同樣,雖然可以從任何列表中分配任何列表的超類,但它本身並不是列表。 並且,因為 Java 中的 generics 是不變的,這意味着由於List<?>
不同於任何類型的列表,因此List<List<?>>
的類型不同於任何List<List<of_any_type>>
。 這就是編譯錯誤的意思
incompatible types: List<List<Parent>> cannot be converted to List<List<? extends Parent>>
某種類型的列表的類型列表不同於所有列表的超類列表,因為某種類型的列表不同於所有列表的超類。
現在,如果您理解了所有這些,您就可以找到一種方法來調用第一個方法。 您需要的是另一個通配符:
private void call1(final List<? extends List<? extends Parent>> testList)
現在對該方法的調用將編譯。
更新
也許類型依賴關系的圖形表示將有助於更容易地理解它。 考慮兩個類:父類和擴展父類的子類。
Parent <--- Child
您可以有一個 Parent 列表和一個 Child 列表,但是由於 generics 不變性,這些類彼此不相關:
List<Parent>
List<Child>
創建參數化方法時
<T extends Parent> void method1(List<T> arg1)
您告訴 Java 參數arg1
將是List<Parent>
或List<Child>
:
List<Parent> - possible type for arg1
List<Child> - possible type for arg1
但是,當您創建帶有通配符參數的方法時
void method2(List<? extends Parent> arg2)
你告訴 Java 創建一個新的特殊類型,它是List<Parent>
和List<Child>
的超類型,並使arg2
成為這種類型的變量:
List<Parent>
/
/
List<? extends Parent>
^ \
| \
| List<Child>
|
the exact type of arg2
因為arg2
的類型是List<Parent>
和List<Child>
的超類型,所以您可以將這些列表中的任何一個傳遞給 method2。
下面介紹下一層generics,當我們將上圖包裹成一個新的List時,我們打破了所有的超類-子類依賴(記住泛型的不變性):
List<List<Parent>> all of these three classes
List<List<Child>> are independent
List<List<? extends Parent>> of each other
當我們創建以下方法時:
<T extends Parent> void method3(List<List<T>> arg3)
我們告訴 Java, arg3
可以是List<List<Parent>>
或List<List<Child>>
:
List<List<Parent>> - possible type for arg3
List<List<Child>> - possible type for arg3
List<List<? extends Parent>>
因此,我們可以將列表列表中的任何一個傳遞給 method3。 但是,以下聲明:
void method4(List<List<? extends Parent>> arg4)
只允許最后一個類型作為 method4 的參數:
List<List<Parent>>
List<List<Child>>
List<List<? extends Parent>> - possible type for arg4
因此,我們不能將列表列表中的任何一個傳遞給 method4。 我們可以使用另一個通配符為所有三種類型創建一個新的超類:
List<List<Parent>>
/
/
List<? extends List<? extends Parent>> --- List<List<Child>>
\
\
List<List<? extends Parent>>
現在如果我們使用這個類型作為我們方法的參數
void method5(List<? extends List<? extends Parent>> arg5)
我們將能夠將列表列表中的任何一個傳遞給 method5。
總結:雖然看起來相似,但聲明<T extends SomeClass> List<T>
和List<? extends SomeClass>
List<? extends SomeClass>
的工作方式非常不同。 第一個聲明說變量可以有一個可能的類型List<SubClass1 of SomeClass>
或List<SubClass2 of SomeClass>
或List<SubClass3 of SomeClass>
等。第二個聲明創建一個單一的特殊類型,成為所有List<SubClass1 of SomeClass>
和List<SubClass2 of SomeClass>
以及List<SubClass3 of SomeClass>
等的超類。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.