簡體   English   中英

這兩種通用方法有什么區別?

[英]What's difference between these two generic methods?

抱歉,標題太抽象了,但我不知道如何將這個問題壓縮成一個句子。

我不明白為什么call1編譯失敗而call2運行良好。 call1call2邏輯上不是一樣的嗎?

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是不變的。 對於任何兩個不同的類型TU ,類List<T>List<U>既不是彼此的子類也不是彼此的超類,即使原始類型TU之間存在這種關系。 如您所知,您可以使用通配符將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.

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