为什么在Java 7中使用钻石操作符进行类型推断?

13

List<String> list = new ArrayList();会导致编译器警告。

然而,以下示例可以编译而没有任何警告:List<String> list = new ArrayList<>();

我很好奇为什么需要引入钻石操作符。如果构造函数中缺少类型参数,为什么不能像Java中的静态方法一样进行类型推断(这已经是Java中的做法,并被集合库如Google Guava所利用)?

编辑:以millimoose的答案为起点,我查看了类型擦除实际上是什么,它不仅仅是删除所有类型信息。编译器实际上还做了更多的工作(摘自官方文档):

  • 将泛型类型中的所有类型参数替换为其边界或Object(如果类型参数未加限定)。因此,生成的字节码只包含普通类、接口和方法。
  • 必要时插入类型转换以维护类型安全性。
  • 生成桥接方法以保留扩展泛型类型中的多态性。

7
我认为这是为了区分使用原始类型和泛型。使用原始类型会让编译器出于兼容考虑采取完全不同的处理方式。(涉及原始类型的表达式与涉及泛型的表达式的处理方式不同。) - millimoose
我可能会同意millimoose的观点,但是考虑到泛型在运行时被擦除,为了向后兼容编译器警告引入新的操作符(only)并不合适。 - Petro Semeniuk
向后兼容性并不是为了编译器警告而存在的。例如,Object s = new ArrayList().get() 这样的语句,其结果类型会使用预泛型算法来解析,与 String s = new ArrayList<String>().get() 不同。如果将 ArrayList 存储在中间变量中,也会出现这种情况。 - millimoose
6个回答

7
Java开发人员非常努力地避免更改现有程序的行为。 List<String> list = new ArrayList(); 虽然可以编译,但会创建一个未指定类型的ArrayList。如果应用类型推断,结果将是一个ArrayList<String>,这会改变其行为并可能导致程序中其他地方出现运行时错误。
=================================================================================
经过进一步考虑,并参考@millimoose的评论,我发现行为变化将局限于初始化程序并在编译时检测到。请考虑以下程序:
import java.util.ArrayList;
import java.util.List;


public class Test {
  public static void main(String[] args) throws Exception {
    List<Integer> integers = new ArrayList<Integer>();
    integers.add(Integer.valueOf(3));
    integers.add(Integer.valueOf(4));
    List<String> list = new ArrayList(integers);
    System.out.println(list);
  }
}

没有类型推断的情况下,它会运行并打印出[3, 4],尽管这是一个包含整数引用的List<String>不良情况。
有了类型推断,它将无法编译,因为ArrayList(Collection<? extends E> c)不允许在创建ArrayList<String>时使用List<Integer>作为参数。

@millimoose 经过更深思熟虑,我同意了。行为的变化将局限于初始化程序,并在编译时检测到。我会编辑我的答案以展示一个例子。 - Patricia Shanahan

7

关于这个特性的具体答案要从设计该特性的人员那里得到,但我认为这是为了区分它与使用原始类型的情况,因为出于兼容性的考虑,编译器会处理包含原始类型的表达式有着微妙的不同。一个包含原始类型的表达式与包含泛型的表达式在处理方式上有所不同,例如在这个 SO 问题中就可以看到: Generic screws up non-related collection


+1 是为了示例泛型和原始类型之间的区别。 - Philipp Wendler
@PhilippWendler 最近记得有这个问题,这也是我找到这个答案的原因之一,所以部分功劳也归于它的作者。 - millimoose

5
Java 5和6编译器要求的完整语法如下: List<String> list = new ArrayList<String>(); 为了简化语法,他们允许我们不在赋值操作符两侧写相同的类型参数。但是,仍需要使用<>运算符来确保您理解自己在做什么。通过编写new ArrayList<>(),您表明“我了解我正在创建泛型类型实例,并且泛型参数与赋值符号左侧声明的一样”。

3
这是Java 7中改进Java泛型的一部分。
之前,您需要编写:
final List<String> list = new ArrayList<String>();

现在您可以编写代码。
final List<String> list = new ArrayList<>();

这是等价的 - 编译器会自动处理。这与

final List list = new ArrayList();

这是一个未指定类型的List


1
有趣的情况是,当使用钻石操作符并作为原始类型调用构造函数时,编译成功但生成不同的代码。这种情况可能与方法重载特性混合使用。我记得在OpenJDK coin邮件列表的某个地方有一个例子(不,我不打算去找它)。
让完全相同的代码在Java SE 6和Java SE 7上都能成功编译,但产生不同的结果是不可接受的。
对我来说,如果选择了7中的推断算法(基本上与J2SE 5.0的方法泛型类型推断相同),我会省略钻石操作符并发出警告(将其视为错误),如果推断算法会产生不同的代码。如果您编写了这样的代码,很可能无法轻易地确定它是否可编译。

0
如果您的项目是基于Maven构建的,请在pom.xml文件中添加以下内容,放在标签下面。它可以完美地工作。 <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins>

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