java - 我们可以将菱形运算符的结果与原始构造函数区分开来吗?

标签 java generics language-lawyer

我有一些代码要写

GenericClass<Foo> foos = new GenericClass<>();

虽然是同事写的

GenericClass<Foo> foos = new GenericClass();

认为在这种情况下菱形运算符没有添加任何内容。

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

我也很清楚,编译器(和 IDE)可以为将原始类型分配给泛型生成警告。

问题是针对没有参数或没有与泛型类型相关的参数的情况。在那种情况下,有什么办法构造对象GenericClass<Foo> foos可以根据使用的构造函数而有所不同,还是 Java 类型删除保证它们是相同的?

最佳答案

对于两个 ArrayList 的实例化s,一个在末尾带有菱形运算符,一个没有...

List<Integer> fooList = new ArrayList<>();
List<Integer> barList = new ArrayList();

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

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>

所以根据字节码,两者之间没有任何区别。

但是,如果您使用第二种方法,编译器 将生成未经检查的警告。因此,第二种方法确实没有值(value)。您所做的只是使用编译器生成一个误报未经检查的警告,这会增加项目的噪音。


我已经成功地演示了一个场景,在该场景中这样做是积极的有害。它的正式名称是heap pollution . This is not something that you want to occur在你的代码库中,任何时候你看到这种调用,它都应该被删除。

考虑这个类,它扩展了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);
        }
    }
}

看起来足够无害;您可以添加任何类型绑定(bind)的实例。

但是,如果我们在玩 raw types ...这样做可能会有一些不幸的副作用。

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

System.out.println(oops);

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

当然,如果添加菱形运算符,所有这些都可以避免,这将保证我们不会遇到这种情况。

现在,由于我们在混合中引入了原始类型,Java 无法根据 JLS 4.12.2 执行类型检查:

For example, the code:

List l = new ArrayList<Number>();
List<String> ls = l;  // Unchecked warning

gives rise to a compile-time unchecked warning, because it is not possible to ascertain, either at compile time (within the limits of the compile-time type checking rules) or at run time, whether the variable l does indeed refer to a List<String>.

上面的情况很相似;如果我们看一下我们使用的第一个示例,我们所做的只是没有在问题中添加额外的变量。堆污染同样发生。

List rawFooList = new ArrayList();
List<Integer> fooList = rawFooList;

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

Don't use raw types ,嗯?

关于java - 我们可以将菱形运算符的结果与原始构造函数区分开来吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28934615/

相关文章:

java - HashMap<String, boolean> 将所有键复制到 HashMap<String, Integer> 并将值初始化为零

java - 使用 Retrofit2 在 Recycleview 中得到空响应?

java - Admob 测试不工作

c# - 为什么成员(member)提供者不是通用的?

当存在移动和复制构造函数时,C++ 默认构造函数未使用 "using"继承

java - 解析数据 Spring MVC

c# - 如何将通用对象作为方法参数传递

arrays - Kotlin:泛型数组的泛型集合

c - 是否可以通过成员地址访问超过结构的大小,并分配足够的空间?

c++ - 试图访问一个类的私有(private)成员以解决重载是否是不正确的?