【问题标题】:Why can't diamond infer types on anonymous inner classes?为什么 diamond 不能推断匿名内部类的类型?
【发布时间】:2012-12-11 13:35:07
【问题描述】:

在 Java 7 及更高版本中,diamond 可用于正常推断类型,如下所示:

List<String> list = new ArrayList<>();

但是,它不能用于这样的匿名内部类:

List<String> st = new List<>() { //Doesn't compile

    //Implementation here

}

这是为什么?从逻辑上讲,在这种情况下,我绝对可以将类型推断为String。这个决定是否存在逻辑上的原因,即类型实际上不能在匿名内部类上推断出来,还是因为其他原因而被省略?

【问题讨论】:

  • @Philipp 我不同意 - 这个问题是问为什么某段代码无法编译(实际上答案只是你不能使用带有匿名内部类的菱形),这个问题是在问为什么 Java 开发人员选择实施该特定限制的技术/逻辑原因。相关,但几乎不一样。
  • 这在 JDK 9 中得到了极大的改进:bugs.openjdk.java.net/browse/JDK-8062373
  • @MichaelBerry 如果您的重新打开是合理的,请删除指向副本的链接,否则请重新关闭问题。

标签: java java-7 type-inference


【解决方案1】:

在JSR-334:

不支持使用带有匿名内部类的菱形,因为 这样做通常需要对类文件进行扩展 表示不可表示类型的签名属性,事实上的 JVM 改变。

我的猜测是,众所周知,匿名类会导致生成自己的类文件。

我想这些文件中不存在泛型类型,而是由有效(静态)类型代替(因此在声明对象时由 &lt;String&gt; 等显式类型声明)。

确实,对应于内部类的文件永远不会在它的多个不同实例之间共享,那么为什么还要在其中使用泛型呢?! :)。

编译器强制扩展(通过为泛型添加特殊属性)到这些类文件将更难以实现(并且肯定是无用的)。

【讨论】:

    【解决方案2】:

    在从 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&amp;Runnable;按照上一个实现技巧,我们需要

        class AnonFoo_2 implements Foo<Number&Runnable>{ ... }
    

    目前不允许这样做;类型 arg 到超类型 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&lt;Integer&gt; foo = new Foo&lt;&gt;(){...} 遗憾的是,这些常见/简单的情况也被不必要地禁止了。

    【讨论】:

    • 您的示例严格适用于简单(非匿名)具体类。您的示例中与匿名类对应的具体功能是什么?的确,如果您的理论是正确的,那么无论哪种情况,钻石都不可能适用。
    • 在字节码层面,我认为匿名类没有什么特别之处。
    • 因此,如果您拥有的不是接口Foo,那么您的示例将以完全相同的方式表现: class Foo { void foo(N n){} } Foo foo = new Foo();
    • 我想说的是,即使这一行(带有简单的具体类实例化)也永远不会编译:Foo&lt;? extends Runnable&gt; foo = new Foo&lt;???What I put in there???&gt;(); => Number 与Runnable 无关。因此,您所展示的“HARD WORK”(声明具有多个扩展类的接口)显然也需要为非匿名具体类实现。
    • @Mik378 new Foo&lt;&gt;() 和 new Foo&lt;&gt;() {} 之间的区别在于前者是泛型实例化,受类型擦除影响,而后者创建非泛型类扩展将被捕获的参数化类型字节码(您可以在运行时调用getClass().getGenericSuperclass() 并查看实际的类型参数)。这将产生不可表示类型的问题。事实上,这些问题确实已经存在,例如public class Foo&lt;T&gt; { Foo(T t) {} public static void main(String... arg) { new Foo&lt;&gt;((Runnable&amp;CharSequence)null).new Inner() {}; } class Inner {} }.
    【解决方案3】:

    简而言之,&lt;&gt; 对推断类型几乎没有作用,它会关闭没有它的警告。

    编辑:正如@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>
    

    如您所见,&lt;&gt; 采用参数的类型,而不是从 copy 的类型推断类型

    【讨论】:

    • 不完全是,diamond 仍然执行类型检查。对于简单的集合初始化可能并不明显,但以使用复制构造函数为例:List&lt;String&gt; copy = new ArrayList&lt;&gt;(original); 这确保了original 列表也是字符串列表(或更准确地说是Collection&lt;? extends String&gt;)。
    • 虽然这是真的,但它并没有从它的使用方式推断出类型。它从参数中获取类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-11
    • 1970-01-01
    相关资源
    最近更新 更多