我一直在用一些奇怪的代码做一些代码考古学,我遇到了类似的东西:
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>
?
我可以使用这两种类型的版本成功地编译和运行测试,但我对幕后发生的事情感到困惑。
最佳答案
- 虽然叫
Inner
在这个例子中,它实际上不是一个内部类,而是一个静态嵌套类。内部类是非静态嵌套类(参见 https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html )。 - 它是静态嵌套类而不是顶级类这一事实在这里无关紧要。
- 代码使用类
Inner
可以使用原始类型Inner
(其中绕过所有类型检查 - 此处不感兴趣),或为类型参数U
指定实际类型.在后一种情况下,上限将该类型限制为泛型类型的子类型Outer<T>
其中T
可以是任何类型,无论是否Inner
声明为Inner<U extends Outer>
或Inner<U extends Outer<?>>
.
在类签名中使用原始类型仍然可以在声明变量或参数时使用强类型检查。例如,以下将编译(假设 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
不使用原始类型作为上限的参数,Inner
必须声明为 Inner<U extends Outer<? super String>>
, 在 PECS 之后原则,因为U
在这种情况下是消费者的类型。
关于java - 了解静态嵌套类类型定义中的原始通配符与无限通配符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35029134/