为什么Java 7的菱形操作符不能与匿名类一起使用?

41

考虑这段试图实例化一些List的 Java 代码:

List<String> list1 = new ArrayList<String>();
List<String> list2 = new ArrayList<>();
List<String> list3 = new ArrayList<String>() { };
List<String> list4 = new ArrayList<>() { };
List<String> list5 = new ArrayList<Integer>() { };
list1list2很简单;list2使用Java 7中的钻石操作符来减少类型参数不必要的重复。 list3是对list1的变体,使用匿名类,可能会覆盖一些ArrayList的方法。 list4试图使用钻石操作符,类似于list2,但这是一个编译错误,消息为“'<'>'不能与匿名类一起使用”。 list5产生了一个错误,证明编译器知道实际需要的类型。错误消息是“类型不匹配:无法将new ArrayList<Integer>(){}转换为List<String>”。
因此,在list4的声明中,为什么不能在匿名类中使用钻石操作符?有一个类似的问题,其中有一个被接受的答案,包含来自JSR-334的以下解释:
“在匿名内部类中使用钻石是不支持的,因为通常情况下,这样做需要扩展类文件签名属性以表示不可表示的类型,这是一种事实上的JVM更改。”
我需要一些帮助理解这个论据。为什么显式类型与相同且明显容易推断出的类型需要在生成的类文件中有任何差异?“通常情况下这样做”的困难用例将涵盖什么?

1
这不是那个特定问题的重复。我参考了一个类似的问题,但需要对答案进行解释(或更好的答案)。 - Dave Hartnoll
你链接的答案对你引用的段落给出了一个“最佳猜测”。你不明白什么?你想要一个更加确定的答案吗? - Radiodef
Java 7 钻石操作符为什么难以实现?这个问题的答案在于 Java 编译器需要能够推断出钻石操作符中泛型类型参数的类型。然而,在某些情况下,编译器无法准确地推断出类型参数的类型,因此必须进行额外的工作来解决这个问题。这是 Java 7 钻石操作符实现过程中遇到的主要挑战之一。 - RMachnik
谢谢@Rafik991,我也看了那个答案,但它并没有真正帮助我理解任何奇怪的用例。 - Dave Hartnoll
@Radiodef,链接答案中的最佳猜测并没有真正解释编译器为什么不能(或选择不)推断正确的类型。一旦类型已知,生成的类应该是相同的。或者也许不同-在这种情况下,任何额外的解释都会有所帮助。 - Dave Hartnoll
https://dev59.com/q2Yr5IYBdhLWcg3wQYFa - ZhongYu
5个回答

24

这个问题在"Project Coin"邮件列表上进行了讨论。以下是内容(重点是我的):

在Java编译器内部,它操作的类型集合比Java程序中明确写下的类型集合要丰富得多。无法在Java程序中明确写出的编译器内部类型称为非指示类型。使用钻石语法可能会产生非指示类型。因此,不支持在匿名内部类中使用钻石语法,因为这通常需要扩展类文件签名属性以表示非指示类型,这是一种实际上的JVM更改。未来的平台版本可能允许在创建匿名内部类时使用钻石语法,只要推断出的类型是可指示的。

请注意,Java 8也不支持该功能,但将作为Java 9的新功能包括在内("Milling Project Coin"第3项)。


2
非常感谢您的回答。您提供的邮件列表还指向了http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6894753,其中包含了更深入的分析以及一个“困难”类的示例。简而言之,似乎推断不适用于匿名类的原因是支持它的工作量将是不成比例的。 - Dave Hartnoll
2
JDK 9 的解决方案就像人们可以想象的那样简单:只允许它用于可表示类型。最大的讽刺是,他们想要避免的问题已经存在。考虑 public class Foo<T> { Foo(T t) {} public static void main(String... arg) { System.out.println( new Foo<>("".getClass()).new Inner() {} .getClass().getGenericSuperclass()); } class Inner {} }。这捕获了一个不能在字节码中表达的非可表示类型。在 JDK 11 之前,它将生成无效的字节码;从 JDK 11 开始,它会产生编译器错误“非法签名属性”,但没有解释。 - Holger

2
你可以在Java9中使用钻石操作符。
MyHandler<Integer> intHandler = new MyHandler<>(1) {

        @Override
        public void handle() {
            // handling code...
        }
    };

    MyHandler<? extends Integer> intHandler1 = new MyHandler<>(10) {

        @Override
        void handle() {
            // handling code...
        }
    };

    MyHandler<?> handler = new MyHandler<>("One hundred") {

        @Override
        void handle() {
            // handling code...
        }
    };
}

2

您可以在Java 9中使用它。 示例钻石运算符

 MyHandler<Integer> intHandler = new MyHandler<>(1) {

        @Override
        public void handle() {
            // handling code...
        }
 };

1
Java 10 开始,你可以轻松使用 var 来进行类型推断,编译器会自动处理。
var list1 = new ArrayList();
var list2 = new ArrayList<String>();
var list3 = new ArrayList<String>() { };
var list4 = new ArrayList<>() { };
var list5 = new ArrayList<Integer>() { };

0
从Java 9开始,菱形操作符<>可以与匿名类一起使用。请参见这里

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