我在一些奇怪的代码中一直在做一些代码考古,我遇到了类似的东西:
public abstract class Outer<T>
{
protected Outer(Inner<?> inner)
{
// ...
}
public static abstract class Inner<U extends Outer>
{
// ...
}
}
让我印象深刻的是,在Inner
类型Outer
类型(<U extends Outer>
位)的使用情况下,没有无界通配符类型。
使用Inner<U extends Outer<?>>
与Inner<U extends Outer>
的含义是什么?
我可以使用两种类型的版本成功编译和运行测试,但是我很难理解幕后发生的事情。
答案 0 :(得分:2)
Inner
,但它实际上不是内部类,而是静态嵌套类。内部类是非静态嵌套类(请参阅https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html)。Inner
的代码可以使用原始类型Inner
(其中绕过所有类型检查 - 此处不感兴趣),或者为类型参数U
指定实际类型。在后一种情况下,上限将该类型限制为泛型类型Outer<T>
的子类型,其中T
可以是任何类型,无论Inner
是否声明为Inner<U extends Outer>
或{ {1}}。在类签名中使用原始类型仍然可以在声明变量或参数时使用强类型检查。例如,以下将编译(假设Inner<U extends Outer<?>>
具有no-args构造函数):
Inner
但是用Outer.Inner<Outer<String>> x = new Outer.Inner<Outer<String>>();
替换任何一方(但不是另一方)的Outer<String>
将产生编译器错误。如果使用无界通配符而不是原始类型,则此行为将完全相同,因此目前没有差异。
实际差异在于如何允许类Outer
使用Inner
类型的变量。假设您在构造函数中传递了这样的变量:
U
假设public Inner(U u) { this.u = u; }
有一个方法接受类型Outer
的参数(它自己的类型参数),例如:
T
现在,在原始上限(void add(T) { ...}
)的情况下,类U extends Outer
中的代码用任何对象调用此方法是合法的,例如一个字符串:
Inner
虽然会发出编译器警告(除非被禁止),并且如果实际运行时类型this.u.add("anything")
与T
不同,则会在代码中抛出String
对象是另一种类型。
对于无界通配符(ClassCastException
),由于U extends Outer<?>
是特定但未知的类型,因此调用T
方法将导致编译器错误,无论哪个参数你给它。
由于您在两种情况下都提到代码编译正常,因此add
中不存在使用T
的此类方法,或者未从Outer
调用此方法。但是通过添加无界通配符,您可以证明给类没有发生的类用户(因为否则代码将无法编译)。
为了允许调用Inner
this.u.add(s)
为s
参数而不使用上限的原始类型,String
必须声明为{{ 1}},遵循PECS原则,因为在这种情况下Inner
是消费者的类型。