我们能区分钻石操作员和原始构造函数的结果吗?

Mic*_*son 26 java generics language-lawyer

我有一些代码可以写

GenericClass<Foo> foos = new GenericClass<>();
Run Code Online (Sandbox Code Playgroud)

虽然有同事会写

GenericClass<Foo> foos = new GenericClass();
Run Code Online (Sandbox Code Playgroud)

认为在这种情况下,钻石运营商不会增加任何东西.

我知道实际使用与泛型类型相关的参数的构造函数可能会导致编译时错误,<>而不是原始情况下的运行时错误.并且编译时错误要好得多.(如本问题所述)

我也非常清楚编译器(和IDE)可以生成警告,以便将原始类型分配给泛型.

问题是,对于没有参数的情况,或者没有与泛型类型相关的参数.在这种情况下,构造对象是否有任何方式GenericClass<Foo> foos可以根据使用的构造函数而有所不同,或者Javas类型擦除是否保证它们是相同的?

Mak*_*oto 11

对于两个ArrayLists的实例化,一个是钻石操作员,一个是没有...

List<Integer> fooList = new ArrayList<>();
List<Integer> barList = new ArrayList();
Run Code Online (Sandbox Code Playgroud)

...生成的字节码是相同的.

LOCALVARIABLE fooList Ljava/util/List; L1 L4 1
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: java.util.List<java.lang.Integer>
LOCALVARIABLE barList Ljava/util/List; L2 L4 2
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: java.util.List<java.lang.Integer>
Run Code Online (Sandbox Code Playgroud)

因此,根据字节码,两者之间没有任何区别.

但是,如果使用第二种方法,编译器将生成未经检查的警告.因此,第二种方法确实没有价值; 你正在做的就是用编译器生成一个误报未经检查的警告,这会增加项目的噪音.


我已经成功地展示了这样做的场景,其中这样做是非常有害的.正式名称是堆污染. 这不是您希望在代码库中出现的内容,只要您看到此类调用,就应该将其删除.

考虑这个扩展了一些功能的类ArrayList.

class Echo<T extends Number> extends ArrayList<T> {
    public Echo() {

    }

    public Echo(Class<T> clazz)  {
        try {
            this.add(clazz.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            System.out.println("YOU WON'T SEE ME THROWN");
            System.exit(-127);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

似乎无害; 您可以添加任何类型绑定的实例.

但是,如果我们正在玩原始类型 ......这样做可能会产生一些不幸的副作用.

final Echo<? super Number> oops = new Echo(ArrayList.class);
oops.add(2);
oops.add(3);

System.out.println(oops);
Run Code Online (Sandbox Code Playgroud)

这打印[[], 2, 3]而不是抛出任何异常.如果我们想对Integer这个列表中的所有s 进行操作,那么我们会遇到一个ClassCastException令人愉快的ArrayList.class调用.

当然,如果添加钻石操作员,所有这些都可以避免,这将保证我们不会有这样的情况.

现在,因为我们已经在混合中引入了原始类型,所以Java无法按照JLS 4.12.2执行类型检查:

例如,代码:

List l = new ArrayList<Number>();
List<String> ls = l;  // Unchecked warning
Run Code Online (Sandbox Code Playgroud)

产生编译时未经检查的警告,因为无法在编译时(在编译时类型检查规则的限制内)或在运行时确定变量l是否确实引用了a List<String>.

上面的情况非常相似; 如果我们看一下我们使用的第一个例子,我们所做的就是不在这个问题中添加额外的变量.堆污染完全相同.

List rawFooList = new ArrayList();
List<Integer> fooList = rawFooList;
Run Code Online (Sandbox Code Playgroud)

因此,虽然字节代码是相同的(可能是由于擦除),但事实仍然是这样的声明会产生不同或异常的行为.

不要使用原始类型,mmkay?