在静态嵌套类类型定义中理解原始与无界通配符

Ale*_*ore 6 java generics

我在一些奇怪的代码中一直在做一些代码考古学,我遇到了类似的东西:

public abstract class Outer<T>
{
    protected Outer(Inner<?> inner)
    {
        // ...
    }

    public static abstract class Inner<U extends Outer>
    {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

让我印象深刻Inner的是,在Outer类型(<U extends Outer>位)的使用上没有无界的通配符类型.

使用Inner<U extends Outer<?>>vs. 的含义是Inner<U extends Outer>什么?

我可以使用两种类型的版本成功编译和运行测试,但是我很难理解幕后发生的事情.

her*_*man 2

  1. 虽然Inner例子中调用了,但实际上并不是内部类,而是静态嵌套类。内部类是非静态嵌套类(请参阅https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html)。
  2. 事实上,它是静态嵌套类而不是顶级类在这里并不重要。
  3. 使用类的代码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>>();
Run Code Online (Sandbox Code Playgroud)

但是Outer<String>在任一侧(而不是另一侧)替换Outer都会产生编译器错误。如果使用无界通配符而不是原始类型,此行为将完全相同,因此到目前为止没有区别

实际的区别在于类如何Inner使用类型的变量U。假设您在构造函数中传递这样一个变量:

public Inner(U u) { this.u = u; }
Run Code Online (Sandbox Code Playgroud)

还假设Outer有一个采用类型参数T(其自己的类型参数)的方法,例如:

void add(T) { ...}
Run Code Online (Sandbox Code Playgroud)

现在,在原始上限 ( ) 的情况下,类中的代码使用任何对象(例如字符串)调用此方法U extends Outer都是合法的:Inner

this.u.add("anything")
Run Code Online (Sandbox Code Playgroud)

尽管会发出编译器警告(除非被抑制),并且如果实际运行时类型T与 不同StringClassCastException则会在依赖于不同类型的对象的代码中抛出 a 。

然而,在无界通配符 ( ) 的情况下U extends Outer<?>,由于T它是特定但未知的类型,因此add无论您提供哪个参数,调用该方法都会导致编译器错误。

由于您提到代码在这两种情况下都编译得很好,因此使用的方法T要么不存在于 中Outer,要么没有从 中调用Inner。但是通过添加无界通配符,您可以向类的用户证明这种情况不会发生(因为否则代码将无法编译)。

为了允许this.u.add(s)使用参数进行调用而不使用原始类型s作为String上限,Inner必须将其声明为Inner<U extends Outer<? super String>>,遵循PECS原则,因为U是本例中消费者的类型。