为什么在 Outer<String>.Inner obj2 = new Outer<>().new Inner() 中使用菱形运算符失败?虽然与 Inner 相同可以吗?

Why using diamond operator fails in Outer<String>.Inner obj2 = new Outer<>().new Inner() ? While same with Inner is OK?

为什么在实例化通用外部 class(与内部 class 一起)时使用菱形运算符会在代码段 2 中产生错误,而代码段 1 完全正常?

我知道稀有类型是被禁止的,但我的情况不是稀有类型 - 在稀有类型中,Outer 和 Inner 都是通用的,但其中一个(任一)用作原始类型,另一个用作通用类型。

片段 1:

class Outer {
    class Inner<T> {

    }
}

class Driver {
    public static void main(String[] args) {

        Outer.Inner obj1 = new Outer().new Inner<>(); // fine !
        Outer.Inner<String> obj2 = new Outer().new Inner<>(); // fine !

    }
}

片段 2:

class Outer<T> {
    class Inner {

    }    
}

class Driver {    
    public static void main(String[] args) {

        Outer.Inner obj1 = new Outer<>().new Inner();  // fine !
        Outer<String>.Inner obj2 = new Outer<>().new Inner(); // error !

    }
}

P.S。在 Eclipse 编译器上测试。

编译错误:

Error:(11, 50) java: incompatible types: Outer<java.lang.Object>.Inner cannot be converted to Outer<java.lang.String>.Inner

这意味着你应该改变:

Outer<String>.Inner obj2 = new Outer<>().new Inner()

至:

Outer<String>.Inner obj2 = new Outer<String>().new Inner()

编译器将 new Outer<>() 视为原始类型(默认为 Object),而它期望 Outer<String>,因此要解决它,我们需要更具体地说明我们在作业。

您正在尝试使用菱形运算符来推断类型参数:

Outer<String>.Inner obj2 = new Outer<>().new Inner(); // error !

JLS, section 15.9,关于钻石是这样说的:

A class instance creation expression specifies a class to be instantiated, possibly followed by type arguments (§4.5.1) or a diamond (<>) if the class being instantiated is generic

你有两个 class 实例化表达式:

new Outer<>()

new Inner()  // or more precisely, new Outer<>().new Inner()

在 15.9 节的末尾,它区分了这两个表达式:

A class instance creation expression is a poly expression (§15.2) if it uses the diamond form for type arguments to the class, and it appears in an assignment context or an invocation context (§5.2, §5.3). Otherwise, it is a standalone expression.

因此,第二个表达式是一个多边形表达式,这意味着它的类型取决于赋值上下文;但是第一个表达式 new Outer<>() 是一个独立的表达式:它的类型不受任何上下文的影响。

你的其他陈述

Outer.Inner obj2 = new Outer<>().new Inner();

很好,因为您使用 Outer 作为原始类型。

alfasin 的回答中介绍了编译代码的解决方案。

但是,原因在于 Java 推断类型的方式。 Java 只能在某些情况下推断类型(有关详细信息,请参阅文档 here,特别是 Target Types 部分)。

引用自这些文档:

Note: It is important to note that the inference algorithm uses only invocation arguments, target types, and possibly an obvious expected return type to infer types. The inference algorithm does not use results from later in the program.

您的代码片段的问题归结为目标类型推断所推断的类型以及使用点运算符链接方法。使用点运算符时,第一个方法的结果首先用于第二个方法(通过管道传输)。因此,这会更改目标类型,使其成为点后面的部分所期望的类型。但是,只有方法链中最后一个方法的结果被分配给变量类型,因此可以推断出您的案例中最后一个方法的泛型类型。

因此在第一个片段中,需要推断类型的部分是点运算符之后的最后一个方法,因此该方法的目标类型是要分配结果的变量,因此可以推断。 即从您的第一个代码段开始:

Outer.Inner<String> obj2 = new Outer().new Inner<>(); // fine !

因为 'new Outer()' returns 一个没有通用类型的对象,不需要进行推理。第二种方法new Inner<>()可以继续。这里new Inner<>()方法的结果将被赋值给变量obj2,变量obj2被声明为Outer.Inner<String>类型,因此可以从它的目标推断出TString.

在第二个片段中,需要推断类型的部分是点运算符之前的方法,这意味着第一个方法的结果是第二个方法的应用。

因此从您的第二个片段中可以看到:

Outer<String>.Inner obj2 = new Outer<>().new Inner();

new Outer<>()的目标类型是new Inner()期望的,即Outer<T>.newInner();由于类型擦除和 Java 泛型的工作方式,这个 T,因为它没有被指定为某种特定类型,所以被视为 Object。现在第二部分可以继续了,但是第二种方法的结果现在变成了 Outer<Object>.Inner 类型,无法转换为变量的赋值类型。

因此您需要为该方法提供类型见证,即 new Outer<SomeMethod>(),因为它在第二个片段中没有正确的目标类型,无法按照您想要的方式进行类型推断.