为什么Diamond无法推断匿名内部类的类型?

24
在Java 7及以后的版本中,可以使用钻石语法(infer types on normally like so without an issue)来推断类型,而不会出现问题。
List<String> list = new ArrayList<>();

然而,对于像这样的匿名内部类,它是无法使用的:
List<String> st = new List<>() { //Doesn't compile

    //Implementation here

}

为什么会这样呢?从逻辑上讲,在这种情况下,我可以确定类型是 String。是否有逻辑原因导致无法在匿名内部类中推断类型,或者是由于其他原因而被省略了?


2
@Philipp 我不同意 - 那个问题是在问为什么某段代码无法编译(事实上答案就是您不能在匿名内部类中使用钻石语法),而这个问题则是在询问为什么Java开发人员选择设置那种特定的限制,虽然相关但完全不同。 - Michael Berry
5
JDK 9 在这方面有了极大的改进:https://bugs.openjdk.java.net/browse/JDK-8062373 - jcsahnwaldt Reinstate Monica
@MichaelBerry 如果您认为重新开放此问题是有道理的,请删除重复链接,否则请重新关闭该问题。 - Cœur
3个回答

15
JSR-334 中:

由于这样做通常需要扩展类文件签名属性来表示不可指定类型,即实际上是 JVM 更改,因此不支持使用匿名内部类的 diamond 语法。

我猜大家都知道,匿名类会生成自己的类文件。
我想,在这些文件中不存在泛型类型,而是由有效(静态)类型替换(因此在声明对象时通过显式类型如 <String> 声明)。
事实上,与内部类对应的文件从未在其多个不同实例之间共享,因此为什么要在其中使用泛型呢?!:)
编译器强制在这种类文件中添加一个特殊属性来支持泛型会更难以实现(并且肯定是无用的)。

6

跳过stackoverflow的帖子后,谷歌搜索结果如下:http://mail.openjdk.java.net/pipermail/coin-dev/2011-June/003283.html

我的猜测是,通常匿名类是显式类型的一个具体子类。

    interface Foo<N extends Number>
    {
        void foo(N n);
    }

    Foo<Integer> foo = new Foo<Integer>(){ ... }

被实现于

    class AnonFoo_1 implements Foo<Integer>{ ... }

    Foo<Integer> foo = new AnonFoo_1();

假设我们允许在匿名类中进行钻石推断,可能会出现复杂的情况,例如:
    Foo<? extends Runnable> foo = new Foo<>(){ ... }

推理规则产生N=Number&Runnable;根据之前的实现技巧,我们需要:
    class AnonFoo_2 implements Foo<Number&Runnable>{ ... }

目前不允许这样做;super类型的type参数Foo必须是“正常”的类型。


然而,这样做的理由并不十分充分。我们可以发明其他实现技巧来使其工作。

    class AnonFoo<N extends Number&Runnable> implements Foo<N>
    {
        @Override public void foo(N n)
        {
            n.intValue();
            n.run();
        }
    }

    Foo<? extends Runnable> foo = new AnonFoo<>();

编译器应该能够做同样的技巧。
无论如何,至少编译器应该允许大多数不涉及“无法标注类型”的用例,例如Foo<Integer> foo = new Foo<>(){...}。可惜这些常见/简单的情况也被不必要地禁止了。

我想说的是,即使是这样一个简单的具体类实例化的代码行,在编译时也无法通过:Foo<? extends Runnable> foo = new Foo<???What I put in there???>();。这里的"???我应该放什么???"与Runnable不相关的数字。因此,你所展示的"辛苦努力"(声明接口并多重扩展类)显然也需要应用于非匿名具体类。 - Mik378
如果您没有输入任何内容,那么代码会编译通过 :) - irreputable
好的,我明白了 :) 可怜的编译器 ^^ - Mik378
实际上,简单情况会编译,因为它通过编译器进行类型检查(对两种类型进行交集),而不是创建一个新类(如匿名类所做的那样),限制了物理上声明多个 extends,从而违反了 Foo 泛型类型允许的规则。 - Mik378
3
new Foo<>()new Foo<>() {} 的区别在于前者是泛型实例化,受类型擦除的影响,而后者创建了一个非泛型类,该类扩展了一个参数化类型,在字节码中将被捕获(您可以在运行时调用getClass().getGenericSuperclass()并查看实际的类型参数)。这将会产生与不可指定类型相关的问题。实际上,这些问题已经存在,例如:public class Foo<T> { Foo(T t) {} public static void main(String... arg) { new Foo<>((Runnable&CharSequence)null).new Inner() {}; } class Inner {} }. - Holger
显示剩余3条评论

1
简而言之,<> 对于类型推断没有太大的作用,它只是关闭了没有它时会出现的警告。
编辑:正如@Natix所指出的那样,它确实进行了一些检查。
List<Integer> ints = new ArrayList<>();
List<String> copy = new ArrayList<>(ints);

产生编译错误

Error:Error:line (42)error: incompatible types
required: List<String>
found:    ArrayList<Integer>

正如您所看到的,<>采用了参数的类型,而不是从copy的类型推断出类型。


3
不完全正确,diamond仍然执行类型检查。对于简单的集合初始化可能不太明显,但是以使用复制构造函数为例:List<String> copy = new ArrayList<>(original); 这可以确保original列表也是一个字符串列表(或更准确地说是Collection<? extends String>)。 - Natix
虽然这是正确的,但它并不从用法中推断类型。它是从参数中获取类型。 - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接