Java的8方法签名如下:
public static <E extends CharSequence> List<? super E> m(List<E> list);
在代码的某处,方法调用:
result = m(list);
假设我创建了参数list
,如下所示:
List<String> list = new ArrayList<>();
我的问题是,为什么result
可能只是原始List
,甚至不能是List<Object>
?
答案 0 :(得分:3)
使用List<String>
作为参数调用方法就像调用以下方法一样:
public static List<? super String> m(List<String> list) { ... }
考虑以下起始代码:
List<String> list = new ArrayList<>();
List<? super String> result = m(list);
这将调用上面提到的方法并将返回值存储到变量result
中 - 该变量使用方法的返回类型完全输入。
现在的问题是:您可以为这些变量分配哪些变量 - 或更好的类型?所以我们谈论的是赋值兼容性。
考虑这些作业:
List<String> result1 = result; // compiler error: type mismatch (not assignable)
List<Object> result2 = result; // compiler error: type mismatch (not assignable)
List result3 = result; // ok
List<?> result4 = result; // ok
List<? super String> result5 = result; // ok
List<? extends Object> result6 = result; // ok
要了解此错误的性质,您必须知道泛型不变。这意味着,类型List<String>
不是 List<Object>
的子类型 - 尽管类型String
和Object
具有这样的子类型层次结构。
所以,我们在这里尝试的是:
List<? super String>
分配给List<String>
=&gt;失败,没有子类型List<? super String>
分配给List<Object>
=&gt;失败,没有子类型List<? super String>
分配给List
=&gt;成功,因为使用原始类型通常会从类型检查中选择List<? super String>
分配给List<?>
=&gt;成功,因为List<?>
是所有List<...>
List<? super String>
分配给List<? super String>
=&gt;成功,因为......好吧...... List<? super String>
分配给List<? ectends Object>
=&gt;成功,因为List<? ectends Object>
与List<?>
基本相同。注意,尝试将List<? super String>
分配给List<? super CharSequence>
或List<? extends CharSequence>
也会失败。它们不在子类型层次结构中。
为什么会这样?编译器无法保证实际列表是使用与约束? super/extends CharSequence
匹配的类型实例化的。
答案 1 :(得分:1)
您无法做到这一点的原因是因为Java泛型不是协变的。也就是说,List<Object>
不是List<String>
的超类。然而,方法m
的方法签名允许返回List<String>
,然后您会有一个List<Object>
类型的引用指向List<String>
。由于泛型类型信息在运行时不可用,因此不会立即引发异常,但可能会导致不相关位置出现类型错误。
例如,假设m
实现如下:
private static List<CharSequence> theResult = new ArrayList<>();
public static <E extends CharSequence> List<? super E> m(List<E> list) {
theResult.addAll(list);
return theResult;
}
然后使用您的方法,将非CharSequence
对象添加到List<CharSequence>
:
List<Object> os = m(someList);
os.add(new Object());
幸运的是,以上内容无法编译。