Java 中菱形运算符 (<>) 的意义何在?
Posted
技术标签:
【中文标题】Java 中菱形运算符 (<>) 的意义何在?【英文标题】:What is the point of the diamond operator (<>) in Java? 【发布时间】:2011-05-09 04:38:54 【问题描述】:java 7 中的菱形运算符允许如下代码:
List<String> list = new LinkedList<>();
但是在 Java 5/6 中,我可以简单地写:
List<String> list = new LinkedList();
我对类型擦除的理解是这些是完全一样的。 (无论如何,泛型在运行时都会被删除)。
为什么要操心钻石呢?它允许哪些新功能/类型安全?如果它没有产生任何新功能,为什么他们会提到它作为一个特性?我对这个概念的理解有问题吗?
【问题讨论】:
请注意,如果您使用静态工厂方法,这不是问题,因为 Java 会对方法调用进行类型推断。 当你禁用警告它实际上没用...... :) 就像我一样 已回答但也在Java教程中(页面中间):docs.oracle.com/javase/tutorial/java/generics/… dZone 上关于此的好文章。 我的观点是它是 List问题
List<String> list = new LinkedList();
是在左侧,您使用的是 generic 类型 List<String>
,而在右侧您使用的是 raw 类型 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 语言的通用部分...它们不仅仅适用于 Collection
s 的无参数构造函数!
【讨论】:
向后兼容性很好,但请不要以牺牲复杂性为代价。为什么 Java 7 不能只引入-compatibility
编译器开关,而如果没有,那么 javac
将禁止所有原始类型并仅强制执行严格的泛型类型?这将使我们的代码不再那么冗长。
@Rosdi:同意,新代码中不需要原始类型。但是,我强烈希望在源文件中包含 Java 版本号(而不是(错误)使用命令行),请参阅我的答案。
我个人不喜欢使用菱形,除非您在同一行上定义和实例化。 List<String> strings = new List<>()
是可以的,但是如果你定义了private List<String> my list;
,然后在页面的中途用my_list = new List<>()
实例化,那就不酷了!我的清单又包含什么?哦,让我四处寻找定义。突然间,钻石捷径的好处就消失了。
@rmirabelle 这与my_list = getTheList()
有何不同?有几种更好的方法可以处理这类问题: 1. 使用 IDE,在鼠标悬停时显示变量类型。 2. 使用更有意义的变量名,例如private List<String> strings
3. 不要拆分变量的声明和初始化,除非你真的必须这样做。
@Morfidon:是的,它对 Java 8 仍然有效。我很确定您应该收到警告。【参考方案2】:
您的理解略有缺陷。钻石运算符是一个很好的功能,因为您不必重复自己。在声明类型时定义一次类型是有意义的,但在右侧再次定义它是没有意义的。 DRY 原则。
现在解释关于定义类型的所有模糊问题。您是对的,该类型在运行时被删除,但是一旦您想从具有类型定义的列表中检索某些内容,您就可以将其恢复为您在声明列表时定义的类型,否则它将丢失所有特定功能并且只有对象功能,除非您将检索到的对象转换为其原始类型,这有时会非常棘手并导致 ClassCastException。
使用List<String> list = new LinkedList()
会收到原始类型警告。
【讨论】:
List<String> list = new LinkedList()
是正确的代码。你知道这一点,我也知道这一点。问题(据我了解)是:为什么只有 java 编译器不明白这段代码非常安全?
@Roman: List<String> list = new LinkedList()
是不是正确的代码。当然,如果是这样就好了!如果 Java 从一开始就有泛型并且不必处理过去是非泛型的泛型类型的向后兼容性,那么它可能是这样的,但它确实如此。
@ColinD Java 真的不需要处理每一行的向后兼容性。在任何使用泛型的 Java 源文件中,应禁止旧的非泛型类型(如果与遗留代码接口,您始终可以使用 <?>
)并且不应存在无用的菱形运算符。【参考方案3】:
此行导致 [unchecked] 警告:
List<String> list = new LinkedList();
那么,问题转变了:为什么只有在创建新集合的情况下才会自动抑制 [unchecked] 警告?
我认为,添加<>
功能会更加困难。
UPD:我还认为,如果合法地使用原始类型“只是为了一些事情”,那将会是一团糟。
【讨论】:
【参考方案4】:理论上,菱形运算符允许您通过保存重复的类型参数来编写更紧凑(和可读)的代码。在实践中,这只是两个令人困惑的字符,更多的是给你什么。为什么?
-
没有理智的程序员在新代码中使用原始类型。因此,编译器可以简单地假设通过不编写类型参数来推断它们。
菱形运算符不提供类型信息,它只是告诉编译器,“它会没事的”。因此,通过省略它,您不会造成任何伤害。在菱形运算符合法的任何地方,编译器都可以“推断”出来。
恕我直言,用一种清晰简单的方法将源标记为 Java 7 比发明这些奇怪的东西更有用。在如此标记的代码中,原始类型可以被禁止而不会丢失任何东西。
顺便说一句,我认为不应该使用编译开关来完成。程序文件的 Java 版本是文件的一个属性,根本没有选项。使用像
这样微不足道的东西package 7 com.example;
可以说清楚(您可能更喜欢更复杂的东西,包括一个或多个花哨的关键字)。它甚至可以毫无问题地编译为不同 Java 版本编写的源代码。它将允许引入新的关键字(例如,“模块”)或删除一些过时的功能(单个文件中的多个非公共非嵌套类或其他),而不会失去任何兼容性。
【讨论】:
您考虑过new ArrayList(anotherList)
和new ArrayList<>(anotherList)
之间的区别吗(尤其是分配给List<String>
和anotherList
是List<Integer>
时)?
@Paul Bellora:不。令我惊讶的是,两者都可以编译。那个有钻石的人甚至没有发出任何警告。但是,我看不出这有什么意义,您能详细说明一下吗?
对不起,我解释的不是很好。查看这两个示例之间的区别:ideone.com/uyHagh 和 ideone.com/ANkg3T 我只是指出使用菱形运算符而不是原始类型确实很重要,至少在传入具有通用边界的参数时。跨度>
实际上我没有花时间阅读 ColinD 的答案——他引用的内容几乎相同。
所以,如果我们要为原始类型引入一种新语法,对于少数真正需要它的地方,为什么不使用像new @RawType List()
这样的东西。这已经是有效的 Java 8 语法,并且类型注释允许在任何需要的地方使用它,例如@RawType List = (@RawType List) genericMethod();
。考虑到原始类型当前会产生编译器警告,除非已放置适当的 @SuppressWarnings
,@RawType
将是一个合理的替代品,并且不需要更微妙的语法。【参考方案5】:
当您编写List<String> list = new LinkedList();
时,编译器会产生“未经检查”的警告。你可以忽略它,但如果你过去忽略了这些警告,你也可能会错过一个警告,该警告会通知你真正的类型安全问题。
因此,最好编写一个不会产生额外警告的代码,而菱形运算符允许您以方便的方式执行此操作,而无需不必要的重复。
【讨论】:
【参考方案6】:其他回复中所说的所有内容都是有效的,但用例并不完全有效,恕我直言。如果检查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 中发生的事情。
【讨论】:
【参考方案7】:菱形运算符的目的只是为了在声明泛型类型时减少代码类型。它对运行时没有任何影响。
如果您在 Java 5 和 6 中指定,唯一的区别是,
List<String> list = new ArrayList();
是您必须将@SuppressWarnings("unchecked")
指定为list
(否则您将收到未经检查的强制转换警告)。我的理解是,钻石运营商正试图让开发变得更容易。它与泛型的运行时执行完全无关。
【讨论】:
您甚至不必使用该注释。至少在 Eclipse 中,你可以告诉编译器不要警告你,你很好...... 最好有注解。并非每个开发人员都在这里使用 Eclipse。以上是关于Java 中菱形运算符 (<>) 的意义何在?的主要内容,如果未能解决你的问题,请参考以下文章