简体   繁体   English

这两种通用方法有什么区别?

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

Sorry for too abstract title, but I have no idea how to compress this question into a single sentence.抱歉,标题太抽象了,但我不知道如何将这个问题压缩成一个句子。

I cannot figure out why call1 is failed to be compiled while call2 works well.我不明白为什么call1编译失败而call2运行良好。 Aren't call1 and call2 logically same? 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
    }
}

This might be indeed a surprising behavior, because both methods produce exactly the same bytecode (except the signature of the methods).这可能确实是一个令人惊讶的行为,因为这两种方法都产生完全相同的字节码(方法的签名除外)。 Why then the first call produces a compile-time error为什么第一次调用会产生编译时错误

incompatible types: List<List<Parent>> cannot be converted to List<List<? extends Parent>>

First of all, when you call a method you must be able to assign the actual parameters to the arguments of the method.首先,当你调用一个方法时,你必须能够将实际参数赋值给方法的arguments。 So, if you have a method所以,如果你有一个方法

void method(SomeClass arg) {}

and you call it as你称之为

method(someArgument);

then the following assignment must be valid:那么下面的赋值必须是有效的:

SomeClass arg = someArgument;

This assignment is possible only if the type of someArgument is a subclass of SomeClass .仅当someArgument的类型是SomeClass的子类时,此分配才有可能。

Now, arrays in Java are covariant, if some subClass is a subclass of some superClass , then subClass[] is a subclass of superClass[] .现在,Java 中的 arrays 是协变的,如果某个subClass是某个superClass的子类,则subClass[]superClass[]的子类。 That means that the following method这意味着下面的方法

void method(superClass[] arg) {}

can be called with argument of class subClass[] .可以使用 class subClass[]的参数调用。

However, generics in Java are invariant.但是Java中的generics是不变的。 For any two different types T and U the classes List<T> and List<U> are neither subclasses nor superclasses of each other, even if such relation exists between the original types T and U .对于任何两个不同的类型TU ,类List<T>List<U>既不是彼此的子类也不是彼此的超类,即使原始类型TU之间存在这种关系。 As you know you can pass a List argument to a method by using a wildcard:如您所知,您可以使用通配符将List参数传递给方法:

void method(List<?> arg) {}

But what does this mean?但是,这是什么意思? As we have seen, in order for the call to work the following assignment must be valid:正如我们所见,为了使调用工作,以下分配必须有效:

List<?> arg = ListOfAnyType;

That means, that List<?> becomes a superclass for all possible lists.这意味着List<?>成为所有可能列表的超类。 This is an important point.这是很重要的一点。 The type of List<?> is not any type of any list, because none of them can be assigned to each other, it's a special type , which is assignable from any list. List<?>的类型不是任何列表的任何类型,因为它们都不能相互赋值,它是一种特殊类型,可以从任何列表赋值。 Compare the following two types: List<?> and List<Object> .比较以下两种类型: List<?>List<Object> The first one can contain any type of list, while the second one can contain list of any type of objects.第一个可以包含任何类型的列表,而第二个可以包含任何类型的对象列表。 So, if you have two methods:所以,如果你有两种方法:

void method1(List<?> arg) {}
void <T> method2(List<T> arg) {}

then the first one will accept any list as an argument, while the second one will accept as an argument the list of any type of objects.那么第一个将接受任何列表作为参数,而第二个将接受任何类型对象的列表作为参数。 Well, both methods work in the same way, but the difference becomes important when we add a new level of generics:好吧,这两种方法的工作方式相同,但是当我们添加新级别 generics 时,区别就变得很重要了:

void method1(List<List<?>> arg1) {}
void <T> method2(List<List<T>> arg2) {}

The second case is simple.第二种情况很简单。 The type of arg2 is a list, which contains elements, whose type is a list of some (unknown) type. arg2的类型是一个列表,其中包含元素,其类型是某种(未知)类型的列表。 Because of that we can pass any list of lists to method2.因此,我们可以将任何列表列表传递给 method2。 The argument arg1 of the first method, however, has a more complex type.但是,第一个方法的参数arg1具有更复杂的类型。 It's a list of elements, which have a very special type which is a superclass of any list.它是一个元素列表,具有非常特殊的类型,它是任何列表的超类。 Again, while superclass of any list can be assigned from any list, it is not a list itself.同样,虽然可以从任何列表中分配任何列表的超类,但它本身并不是列表。 And, because the generics in Java is invariant, it means that since List<?> differs from any type of a list, the type of List<List<?>> differs from any List<List<of_any_type>> .并且,因为 Java 中的 generics 是不变的,这意味着由于List<?>不同于任何类型的列表,因此List<List<?>>的类型不同于任何List<List<of_any_type>> That's the meaning of the compile error这就是编译错误的意思

incompatible types: List<List<Parent>> cannot be converted to List<List<? extends Parent>>

The type list of lists of some type is different from list of superclass of all lists , because list of some type is different from superclass of all lists .某种类型的列表的类型列表不同于所有列表的超类列表,因为某种类型的列表不同于所有列表的超类

Now, if you understood all of this you can find a way to make the call to the first method work.现在,如果您理解了所有这些,您就可以找到一种方法来调用第一个方法。 What you need is another wildcard:您需要的是另一个通配符:

private void call1(final List<? extends List<? extends Parent>> testList)

Now the calls to this method will compile.现在对该方法的调用将编译。

UPDATE更新

Maybe a graphical representation of types' dependences will help to understand it easier.也许类型依赖关系的图形表示将有助于更容易地理解它。 Consider two classes: Parent, and Child which extends Parent.考虑两个类:父类和扩展父类的子类。

Parent <--- Child

You can have a list of Parent, and a list of Child, but because of generics invariance these classes are not related to each other:您可以有一个 Parent 列表和一个 Child 列表,但是由于 generics 不变性,这些类彼此不相关:

List<Parent>
List<Child>

When you create a parameterized method创建参数化方法时

<T extends Parent> void method1(List<T> arg1) 

you tell Java that the argument arg1 will be either List<Parent> or List<Child> :您告诉 Java 参数arg1将是List<Parent>List<Child>

List<Parent> - possible type for arg1
List<Child>  - possible type for arg1

However, when you create a method with a wildcard argument但是,当您创建带有通配符参数的方法时

void method2(List<? extends Parent> arg2) 

you tell Java to create a new special type, which is a supertype for both List<Parent> and List<Child> , and make arg2 a variable of this type:你告诉 Java 创建一个新的特殊类型,它是List<Parent>List<Child>的超类型,并使arg2成为这种类型的变量:

                        List<Parent>
                       /
                      /
List<? extends Parent>
          ^           \
          |            \
          |             List<Child>
          |
 the exact type of arg2

Because the type of arg2 is a supertype of both List<Parent> and List<Child> , you can pass either of these lists to method2.因为arg2的类型是List<Parent>List<Child>的超类型,所以您可以将这些列表中的任何一个传递给 method2。

Now let's introduce the next level of generics. When we wrap the above diagram into a new List, we break all the superclass-subclass dependencies (remember the invariance of generics):下面介绍下一层generics,当我们将上图包裹成一个新的List时,我们打破了所有的超类-子类依赖(记住泛型的不变性):

List<List<Parent>>                   all of these three classes
List<List<Child>>                    are independent 
List<List<? extends Parent>>         of each other

When we create the following method:当我们创建以下方法时:

<T extends Parent> void method3(List<List<T>> arg3)

we tell Java, that arg3 can be either List<List<Parent>> or List<List<Child>> :我们告诉 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>>

Thus, we can pass either of the list of lists to method3.因此,我们可以将列表列表中的任何一个传递给 method3。 However, the following declaration:但是,以下声明:

void method4(List<List<? extends Parent>> arg4)

allows only the last type to be the argument for method4:只允许最后一个类型作为 method4 的参数:

List<List<Parent>>              
List<List<Child>>               
List<List<? extends Parent>>    - possible type for arg4

Consequently, we cannot pass either of the list of lists to method4.因此,我们不能将列表列表中的任何一个传递给 method4。 We can use another wildcard to create a new superclass for all three types:我们可以使用另一个通配符为所有三种类型创建一个新的超类:

                                        List<List<Parent>>
                                       /
                                      /
List<? extends List<? extends Parent>> --- List<List<Child>>
                                      \
                                       \
                                        List<List<? extends Parent>>

Now if we use this type as an argument to our method现在如果我们使用这个类型作为我们方法的参数

void method5(List<? extends List<? extends Parent>> arg5)

we'll be able to pass either of the lists of lists to method5.我们将能够将列表列表中的任何一个传递给 method5。

To summarize: while looking similarly, declarations <T extends SomeClass> List<T> and List<? extends SomeClass>总结:虽然看起来相似,但声明<T extends SomeClass> List<T>List<? extends SomeClass> List<? extends SomeClass> are working very differently. List<? extends SomeClass>的工作方式非常不同。 The first declaration says that the variable can have one of possible types List<SubClass1 of SomeClass> , or List<SubClass2 of SomeClass> , or List<SubClass3 of SomeClass> , etc. The second declaration creates a single special type, which becomes a superclass of all of List<SubClass1 of SomeClass> , and List<SubClass2 of SomeClass> , and List<SubClass3 of SomeClass> , etc.第一个声明说变量可以有一个可能的类型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