抱歉標題太抽象,但我不知道如何將這個問題壓縮成一個句子。
我無法弄清楚為什么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
}
}
uj5u.com熱心網友回復:
這可能確實是一個令人驚訝的行為,因為兩種方法都產生完全相同的位元組碼(方法的簽名除外)。為什么第一次呼叫會產生編譯時錯誤
incompatible types: List<List<Parent>> cannot be converted to List<List<? extends Parent>>
首先,當您呼叫一個方法時,您必須能夠將實際引數分配給該方法的引數。所以,如果你有一個方法
void method(SomeClass arg) {}
你稱它為
method(someArgument);
那么以下分配必須是有效的:
SomeClass arg = someArgument;
someArgument僅當 的型別是 的子類時,才可能進行此分配SomeClass。
現在,Java 中的陣列是協變的,如果 somesubClass是 some 的子類superClass,那么subClass[]是 的子類superClass[]。這意味著以下方法
void method(superClass[] arg) {}
可以使用 class 的引數呼叫subClass[]。
但是,Java 中的泛型是不變的。對于任何兩個不同的型別T和U類List<T>,List<U>它們既不是彼此的子類也不是超類,即使原始型別T和之間存在這種關系U。如您所知,您可以List使用通配符將引數傳遞給方法:
void method(List<?> arg) {}
但是,這是什么意思?正如我們所見,為了使呼叫生效,以下分配必須有效:
List<?> arg = ListOfAnyType;
That means, that List<?> becomes a superclass for all possible lists. 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. Compare the following two types: List<?> and 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:
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. Because of that we can pass any list of lists to method2. The argument arg1 of the first method, however, has a more complex type. 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>>. 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:
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>:
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:
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.
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):
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>>:
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. However, the following declaration:
void method4(List<List<? extends Parent>> arg4)
allows only the last type to be the argument for 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. 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.
To summarize: while looking similarly, declarations <T extends SomeClass> List<T> and List<? extends SomeClass> are working very differently. 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.
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/426100.html
