Java中的钻石操作符(<>)有什么作用?

496

Java 7中的菱形操作符允许编写以下代码:

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

然而在Java 5/6中,我只需简单地编写:

List<String> list = new LinkedList();

我对类型擦除的理解是它们完全相同。(泛型在运行时被移除了)。

那为什么还要使用钻石语法呢?它有什么新功能/类型安全性吗?如果没有带来任何新功能,为什么会将其作为一个特性进行提及呢?我的这个概念理解有误吗?


4
请注意,如果您使用静态工厂方法,则这不是问题,因为Java会对方法调用进行类型推断。 - Rag
当你禁用警告时,它实际上是无用的... :) 就像对我来说。 - Renetik
4
已有答案,但在Java教程中也有(在页面中部左右):http://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html - Andreas Tasoulas
dZone上有一篇关于这个问题的好文章。 - R. Oosterholt
2
我的看法是,它是List<String> list = new LinkedList<左侧类型无论是什么>();的语法糖,即保持它的泛型。 - Viktor Mellgren
7个回答

541

问题在于

List<String> list = new LinkedList();

你的代码左边使用了泛型类型 List<String> ,而右边使用了原始类型 LinkedList 。在Java中,原始类型仅为与泛型前代码兼容而存在。除非必须这样做,否则不应在新代码中使用它们。

如果Java一开始就有泛型,并且没有像 LinkedList 这样在泛型出现之前创建的类型,它可能会使泛型类型的构造函数根据赋值语句左侧自动推断其类型参数。但事实并非如此,为了向后兼容,Java必须对待原始类型和泛型类型有所区别。这导致需要采用一个稍微不同但同样方便的方式来声明泛型对象的新实例,而无需重复其类型参数...钻石操作符。

至于你最初的代码示例 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 语言的通用部分...它们不仅适用于 Collection 的无参构造函数!


36
向后兼容性很好,但不应以复杂性为代价。为什么Java 7不能引入“-compatibility”编译器开关,如果没有它,那么javac将禁止所有原始类型并严格强制使用通用类型?这将使我们的代码更加简洁。 - Rosdi Kasim
3
@Rosdi:同意,新代码中没有必要使用原始类型。然而,我强烈建议在源文件中包含Java版本号(而不是(误)使用命令行),请参阅我的回答。 - maaartinus
43
个人不喜欢使用 diamond(<>)符号,除非你在同一行上定义和实例化。例如 List<String> strings = new List<>() 是可以的,但如果你先定义了 private List<String> my list;,然后在页面的中间位置再使用 my_list = new List<>() 进行实例化,那么就不太好了!我得再找一下这个 my list 到底包含了什么内容。这时候,diamond 的快捷方式所带来的好处就消失了。 - rmirabelle
16
这与 my_list = getTheList() 有何不同?有几种更好的方法来处理这种问题:1. 使用可以在鼠标悬停时显示变量类型的 IDE。2. 使用更有意义的变量名称,例如 private List<String> strings。3. 除非确实需要,否则不要将变量的声明和初始化分开。 - Natix
1
@Morfidon:是的,它对于Java 8仍然有效。我相当确定你应该会收到警告。 - ColinD
显示剩余9条评论

40

你的理解有些错误。钻石操作符是一项好功能,因为你不必重复自己。当你声明类型时只需要定义一次类型,但在右边再次定义就没有意义了。这是DRY原则。

现在来解释一下定义类型的所有模糊问题。你说得对,在运行时类型被移除,但是一旦你想要从具有类型定义的列表中检索出某些东西,你会将其作为你在声明列表时定义的类型返回,否则它会失去所有特定的特征,并且只有Object的特征,除非你将检索到的对象强制转换为其原始类型,这有时可能非常棘手,并导致ClassCastException。

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


8
“List<String> list = new LinkedList()” 是正确的代码。你知道这一点,我也知道。问题(据我理解)是:为什么只有 Java 编译器不理解这段代码是相当安全的呢? - Roman
25
@Roman:“List<String> list = new LinkedList()”不是正确的代码。当然,如果它是正确的代码那就太好了!如果Java一开始就有泛型并且不必处理以前非泛型的通用类型的向后兼容性问题,那么它可能本来可以这样写。但事实上,并不是这样的。 - ColinD
7
Java在每一行代码中都不需要处理向后兼容性。在任何使用泛型的Java源文件中,旧的非泛型类型应该被禁止(如果需要与旧的代码进行交互,可以始终使用<?>)。此外,无用的尖括号语法应该不存在。 - maaartinus

15

这行代码会产生[unchecked]警告:

List<String> list = new LinkedList();
所以,问题转化为:为什么只有在创建新集合的情况下未经检查的警告才不会自动抑制? 我认为,这比添加<>功能要困难得多。 更新:我也认为,如果可以使用原始类型“仅用于一些事情”,那么可能会出现混乱。

12
理论上,钻石操作符允许你通过保存重复的类型参数来编写更紧凑(且易读)的代码。但实际上,它只是多加了两个令人困惑的字符,没有带来任何好处。为什么呢?
1. 没有理智的程序员在新代码中使用原始类型。因此,编译器可以简单地假设您不想推断类型,而是通过不写类型参数来推断类型。 2. 钻石操作符不提供任何类型信息,它只告诉编译器“没问题”。因此,省略它不会有任何损害。在任何可以使用钻石操作符的地方,编译器都可以“推断”它。
依我看,比起发明这样奇怪的东西,有一个清晰简单的方式来标记一个源代码为Java 7更有用。在这样标记的代码中,可以禁止使用原始类型而不失去任何功能。
顺便说一句,我认为这不应该使用编译开关来完成。程序文件的Java版本是文件的属性,根本不需要选项。使用像这样微不足道的东西:
package 7 com.example;

你可以更加清晰地阐述(可能更喜欢使用一个或多个花哨的关键词)。这甚至可以让不同Java版本编写的源代码一起编译而没有任何问题。它将允许引入新的关键字(例如,“module”)或删除某些已过时的特性(例如单个文件中的多个非公共非嵌套类等),而不会失去任何兼容性。


2
您是否考虑过 new ArrayList(anotherList)new ArrayList<>(anotherList) 之间的区别(特别是当它被分配给 List<String> ,而 anotherList 是一个 List<Integer> 时)? - Paul Bellora
@Paul Bellora:不。出乎我的意料,两者都可以编译通过。甚至带有钻石符号的那个没有警告。然而,我无法理解其中的任何意义,你能详细说明一下吗? - maaartinus
我在博客中进行了评论,其中<>被过滤掉了,难以理解其含义。我想说的是,如果我写下这样一行代码 List<Foo> list=new ArrayList();,编译器假定我想要一个裸类型,而事实上变量的类型List<Foo>明确说明它不是旧的代码(并明确表示我想要一个裸类型,否则我会写List list=…)。这没有任何合理的理由去支持先前的泛型代码。 - Holger
@Holger 是的,这很有道理。使用 f(new List()) 可能会更加复杂,但我猜想你实际上从来不需要行类型,除非你明确要求它(例如使用你的 List list=)。我不确定所有的边缘情况,但我和你一样。+++ 尽管如此,假设需要一个特殊的语法,并且是我发明原始类型构造函数的语法,我会使用 new List<>()。:D - maaartinus
2
因此,如果我们要为原始类型引入新的语法,只在真正需要的少数地方使用,为什么不使用类似于 new @RawType List() 的东西呢?这已经是有效的Java 8语法,类型注释允许在每个需要的地方使用它,例如 @RawType List = (@RawType List) genericMethod();。考虑到原始类型目前会创建编译器警告,除非放置了适当的 @SuppressWarnings@RawType 将是一个合理的替代选择,而不需要更微妙的语法。 - Holger
显示剩余3条评论

8
当你写下List<String> list = new LinkedList();时,编译器会产生一个“unchecked”警告。你可以忽略它,但如果你习惯忽略这些警告,你也可能会错过一个通知你真正类型安全问题的警告。
因此,最好编写不会生成额外警告的代码,钻石操作符允许你以方便的方式完成这个操作,而不需要重复冗余的代码。

4

其他回答中提到的都是正确的,但在我看来使用情况并不完全有效。如果查看Guava,特别是与集合相关的内容,同样可以使用静态方法实现。例如,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");

Guava拥有许多其他非常强大的功能,就像这样的 <>,我实际上想不到它的许多用途。

如果他们采用菱形操作符默认行为会更有用,也就是说,类型会从表达式左侧进行推断,或者左侧的类型从右侧进行推断。后一种情况发生在Scala中。


2

钻石操作符的作用是在声明泛型类型时减少代码输入,对运行时没有任何影响。

在Java 5和6中,唯一的区别是您是否指定了类型参数:

List<String> list = new ArrayList();

问题在于您必须为list指定@SuppressWarnings("unchecked")(否则会收到未经检查的转换警告)。我理解钻石操作符试图使开发更加容易。它与泛型的运行时执行完全无关。


在编程中,你甚至不必使用那个注解。至少在 Eclipse 中,你可以告诉编译器不要警告你,并且你就可以安心了... - xeruf
最好有注释。并不是每个开发者都在使用Eclipse。 - Buhake Sindi

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