Java 7中的菱形运算符(<>)有什么意义?


445

Java 7中的菱形运算符允许如下代码:

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

但是,在Java 5/6中,我可以简单地编写:

List<String> list = new LinkedList();

我对类型擦除的理解是这些完全相同。(无论如何,泛型都会在运行时删除)。

为什么要打扰钻石呢?它允许哪些新功能/类型安全?如果它没有产生任何新功能,为什么他们将其称为功能?我对这个概念的理解有缺陷吗?


4
请注意,如果您使用静态工厂方法,这不是问题,因为Java确实在方法调用上键入了推断。
Brian Gordon

当您禁用警告时,它实际上是无用的... :)对我来说
Renetik

3
它已经回答了,但它也是在Java教程(周围的页面中间):docs.oracle.com/javase/tutorial/java/generics/...
安德烈亚斯Tasoulas

关于dZone的不错的文章。
R. Oosterholt 2015年

2
我的看法是,它是List <String> list = new LinkedList <的语法糖,无论左侧的类型是>(); 即保持它的通用
尤Mellgren

Answers:


496

与问题

List<String> list = new LinkedList();

是在左侧,您使用的是泛型类型List<String>,而在右侧,您使用的是原始类型LinkedList。Java中的原始类型实际上仅是为了与前泛型代码兼容而存在,除非绝对必要,否则绝对不能在新代码中使用它们。

现在,如果Java从一开始就具有泛型,并且没有LinkedList最初在具有泛型之前创建的类型(例如),则它可能已经做到了,这样泛型类型的构造函数会自动从左侧推断出其类型参数-尽可能在作业的另一侧。但事实并非如此,为了向后兼容,必须对原始类型和泛型类型进行不同的处理。这使得他们需要采取一种稍微不同但同样方便的方式来声明泛型对象的新实例,而不必重复其类型参数……菱形运算符。

就您的原始示例而言List<String> list = new LinkedList(),编译器会为该分配生成警告,因为它必须这样做。考虑一下:

List<String> strings = ... // some list that contains some strings

// Totally legal since you used the raw type and lost all type checking!
List<Integer> integers = new LinkedList(strings);

存在泛型以提供编译时保护以防止做错事。在上面的示例中,使用原始类型意味着您没有获得此保护,并且在运行时会收到错误消息。这就是为什么您不应该使用原始类型的原因。

// Not legal since the right side is actually generic!
List<Integer> integers = new LinkedList<>(strings);

但是,菱形运算符允许将赋值的右侧定义为真正的泛型实例,并具有与左侧相同的类型参数...而无需再次键入这些参数。它使您可以与使用原始类型几乎一样的工作来保持泛型的安全。

我认为关键要理解的是,原始类型(不带<>)不能与泛型类型相同。声明原始类型时,不会获得任何好处和泛型的类型检查。您还必须记住,泛型是Java语言的通用组成部分 ……它们不仅仅适用于Collections 的无参数构造函数!


31
向后兼容性很好,但是请不要牺牲复杂性。为什么Java 7不能只引入-compatibility编译器切换,而如果不提供,javac则将禁止所有原始类型并仅严格执行通用类型?这将使我们的代码不再那么冗长。
Rosdi Kasim 2011年

3
@Rosdi:同意,在新代码中不需要原始类型。但是,我强烈希望在源文件中包含Java版本号(而不是(使用命令行)错误),请参阅我的答案。
maaartinus 2011年

37
我个人不喜欢使用菱形,除非您正在同一行上定义和实例化。List<String> strings = new List<>()是可以的,但是如果您定义private List<String> my list;,然后在实例化页面的中途下进行my_list = new List<>(),那就太酷了!我的清单又包含什么?哦,让我四处寻找定义。突然,钻石捷径的好处就再见了。
rmir​​abelle 2013年

11
@rmirabelle与:有my_list = getTheList()什么不同?有几种更好的方法来解决此类问题:1.使用IDE,在鼠标悬停时向您显示变量的类型。2.使用更有意义的变量名,例如private List<String> strings3.除非确实需要,否则不要拆分变量的声明和初始化。
Natix

1
@Morfidon:是的,它仍然是有效的Java 8.我敢肯定,你应该会得到警告。
ColinD

36

您的理解有些瑕疵。Diamond运算符是一个不错的功能,因为您不必重复自己的操作。在声明类型时一次定义类型是有意义的,但是在右侧再次定义它是没有意义的。DRY原理。

现在来解释有关定义类型的所有模糊性。您是对的,该类型在运行时已删除,但是一旦您想从具有类型定义的列表中检索某些内容,就可以在声明该列表时将其恢复为您定义的类型,否则它将丢失所有特定功能并且仅具有对象功能,除非您将检索到的对象转换为原始类型,否则有时会非常棘手,并导致ClassCastException。

使用List<String> list = new LinkedList()将获得原始类型警告。


8
List<String> list = new LinkedList()是正确的代码。你知道这一点,我也知道。问题(据我了解)是:为什么只有Java编译器不理解该代码是相当安全的?
罗曼(Roman)2010年

22
@Roman:List<String> list = new LinkedList()正确的代码。当然可以,如果这样的话会很好!如果Java从一开始就具有泛型,并且不必处理以前是非泛型的泛型类型的向后兼容性,则可能是这样,但是确实如此。
ColinD

6
@ColinD Java实际上不需要处理每一行中的向后兼容性。在任何使用泛型的Java源文件中,都应禁止使用旧的非泛型类型(<?>如果与传统代码接口,则可以始终使用),并且不应该使用无用的菱形运算符。
maaartinus 2011年

16

此行导致[unchecked]警告:

List<String> list = new LinkedList();

因此,问题发生了变化:为什么仅在创建新集合时才自动取消[unchecked]警告?

我认为,添加<>功能会比以前困难得多。

UPD:我还认为,如果合法地使用原始类型“仅用于几件事”会造成混乱。


13

从理论上讲,菱形运算符允许您通过保存重复的类型参数来编写更紧凑(更易读)的代码。实际上,仅仅是两个混乱的字符就什么都不给您。为什么?

  1. 没有理智的程序员在新代码中使用原始类型。因此,编译器可以简单地假设,通过不编写任何类型参数,就可以让其推断它们。
  2. Diamond运算符不提供类型信息,只是对编译器说:“会没事的”。因此,省略它不会造成任何伤害。在钻石操作员合法的任何地方,编译器都可以“推断”出来。

恕我直言,以清晰简单的方式将源标记为Java 7会比发明这种奇怪的东西有用。在这样标记的代码中,原始类型可以被禁止而不丢失任何内容。

顺便说一句,我不认为应该使用编译开关来完成它。程序文件的Java版本是文件的属性,完全没有选项。使用一些琐碎的东西

package 7 com.example;

可以使您清楚(您可能更喜欢一些复杂的内容,包括一个或多个奇特的关键字)。它甚至可以一起编译针对不同Java版本编写的源代码,而不会出现任何问题。它将允许引入新的关键字(例如“模块”)或删除一些过时的功能(在单个文件中或多个文件中存在多个非公共的非嵌套类)而不会失去任何兼容性。


2
您是否考虑了new ArrayList(anotherList)和之间的区别new ArrayList<>(anotherList)(特别是如果将其分配给List<String>anotherListList<Integer>)?
Paul Bellora

@Paul Bellora:不。对我来说,两者都令人惊讶。甚至连钻石都没有警告。但是,我对此没有任何意义,您能详细说明一下吗?
maaartinus

抱歉,我讲得不好。请看这两个示例之间的区别:ideone.com/uyHaghideone.com/ANkg3T我只是指出,至少在传递带有通用范围的参数时,使用菱形运算符代替原始类型确实很重要英寸
保罗Bellora

实际上,我没有花时间阅读ColinD的答案-他引用了几乎相同的内容。
Paul Bellora 2013年

2
因此,如果我们要为原始类型引入一种新的语法,那么在真正需要它的几个地方,为什么不使用new @RawType List()。这已经是有效的Java 8语法,并且类型注释允许在需要的任何地方使用它,例如@RawType List = (@RawType List) genericMethod();。考虑到原始类型当前会创建一个编译器警告,除非@SuppressWarnings放置了适当的警告,@RawType这将是一个合理的替代,并且不需要更微妙的语法。
霍尔格2015年

8

当您编写时List<String> list = new LinkedList();,编译器会产生“未经检查”的警告。您可能会忽略它,但是如果您过去曾经忽略这些警告,则可能还会错过一条警告消息,该警告通知您有关实型安全问题。

因此,最好编写一个不会生成额外警告的代码,并且菱形运算符允许您以便捷的方式进行操作,而无需不必要的重复。


4

其他回复中的所有内容都是有效的,但用例不是完全有效的恕我直言。如果检查番石榴 ,尤其是与收藏有关的东西,那么静态方法也可以做到这一点。例如,Lists.newArrayList()允许您编写

List<String> names = Lists.newArrayList();

或使用静态导入

import static com.google.common.collect.Lists.*;
...
List<String> names = newArrayList();
List<String> names = newArrayList("one", "two", "three");

番石榴还具有其他非常强大的功能,而我实际上想不到<>的许多用途。

如果它们使菱形运算符的行为成为默认值,即从表达式的左侧推断类型,或者从右侧推断左侧的类型,则将更加有用。后者就是Scala中发生的情况。


3

Diamond运算符的目的只是在声明泛型类型时减少代码的类型。它对运行时没有任何影响。

如果您在Java 5和6中指定,则唯一的区别是

List<String> list = new ArrayList();

是,你必须指定@SuppressWarnings("unchecked")list(否则你将得到一个unchecked投警告)。我的理解是,钻石运营商正在努力使开发变得更容易。与泛型的运行时执行完全无关。


您甚至不必使用该注释。至少在Eclipse中,您可以告诉编译器不要对此发出警告,并且您还可以...
Xerus

最好有注释。并非每个开发人员都在这里使用Eclipse。
Buhake Sindi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.