Java 7中的钻石运算符有什么意义?

tof*_*arr 438 java generics diamond-operator java-7

java 7中的菱形运算符允许以下代码:

List<String> list = new LinkedList<>();
Run Code Online (Sandbox Code Playgroud)

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

List<String> list = new LinkedList();
Run Code Online (Sandbox Code Playgroud)

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

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

Col*_*inD 488

这个问题

List<String> list = new LinkedList();
Run Code Online (Sandbox Code Playgroud)

在左侧,您使用的是泛型类型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);
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  • 我个人不喜欢钻石的使用,除非你在同一条线上定义和实例化.`List <String> strings = new List <>()`没关系,但如果你定义`private List <String>我的列表;`,然后在页面的中间用`my_list = new List <>()实例化`,那就不酷了!我的清单又包含了什么?哦,让我来寻找定义.突然间,钻石捷径的好处再见. (37认同)
  • 向后兼容性很好,但请不要以牺牲复杂性为代价.为什么Java 7只能引入`-compatibility`编译器开关,而如果不存在那么`javac`将禁止所有原始类型并仅强制执行严格的泛型类型?这将使我们的代码不那么冗长. (31认同)
  • @rmirabelle有什么不同:`my_list = getTheList()`?有几种更好的方法可以解决这类问题:1.使用IDE在鼠标悬停时显示变量类型.2.使用更有意义的变量名,例如`private List <String> strings`.3.除非你真的需要,否则不要拆分变量的声明和初始化. (10认同)
  • @Rosdi:同意,新代码中不需要原始类型.但是,我非常希望在源文件中包含Java版本号(而不是使用命令行(mis)),请参阅我的回答. (3认同)

Oct*_*ean 35

你的理解有点缺陷.钻石操作员是一个很好的功能,因为你不必重复自己.在声明类型时定义类型一次是有意义的,但在右侧再次定义它是没有意义的.DRY原则.

现在解释有关定义类型的所有模糊.你是正确的,在运行时删除了类型,但是一旦你想要从具有类型定义的List中检索某些东西,你就会将它作为你在声明列表时定义的类型返回,否则会丢失所有特定的功能并且只有对象功能,除非您将检索到的对象强制转换为原始类型,这有时会非常棘手并导致ClassCastException.

使用List<String> list = new LinkedList()将获得rawtype警告.

  • @Roman:`List <String> list = new LinkedList()`是_not_正确的代码.当然,如果是这样的话会很好!如果Java从一开始就具有泛型,并且不必处理过去非泛型的泛型类型的向后兼容性,那可能就是这样,但确实如此. (22认同)
  • `List <String> list = new LinkedList()`是正确的代码.你知道这一点,我也知道这一点.问题(据我所知)是:为什么只有java编译器不理解这段代码是非常安全的? (8认同)
  • @ColinD Java真的不需要处理*每一行*的向后兼容性.在使用泛型的任何Java源文件中,应禁止使用旧的非泛型类型(如果与遗留代码接口,则始终可以使用`<?>`),并且不应存在无用的菱形运算符. (6认同)

Rom*_*man 16

此行导致[未选中]警告:

List<String> list = new LinkedList();
Run Code Online (Sandbox Code Playgroud)

因此,问题转换:为什么[unchecked]警告不会仅在创建新集合时自动被抑制?

我认为,添加<>功能将是一项艰巨的任务.

UPD:我还认为,如果合法地使用原始类型"只是为了一些事情",那将会是一团糟.


maa*_*nus 13

理论上,菱形运算符允许您通过保存重复的类型参数来编写更紧凑(和可读)的代码.在实践中,它只是两个令人困惑的角色更多地给你什么.为什么?

  1. 没有理智的程序员在新代码中使用原始类型.所以编译器可以简单地假设通过编写没有类型参数,你希望它推断它们.
  2. 钻石运算符不提供类型信息,它只是说编译器,"它会没事".因此,通过省略它,你不会有任何伤害.在钻石操作符合法的任何地方,编译器都可以"推断"它.

恕我直言,有一个清晰简单的方法将源标记为Java 7比发明这些奇怪的东西更有用.在如此标记的代码中,可以禁止原始类型而不丢失任何内容.

顺便说一句,我认为不应该使用编译开关来完成.程序文件的Java版本是文件的属性,根本没有选项.使用像微不足道的东西

package 7 com.example;
Run Code Online (Sandbox Code Playgroud)

可以说清楚(你可能更喜欢更复杂的东西,包括一个或多个花哨的关键词).它甚至允许编译为不同Java版本编写的源代码而没有任何问题.它将允许引入新的关键字(例如,"模块")或删除一些过时的功能(多个非公共非嵌套类在单个文件中或任何其他),而不会失去任何兼容性.

  • 您是否考虑过`new ArrayList(anotherList)`和`new ArrayList <>(anotherList)`之间的区别(特别是如果它被分配给`List <String>`和`anotherList`是`List <Integer>`) ? (2认同)
  • 因此,如果我们要为原始类型引入一种新的语法,那么在真正需要它的几个地方,为什么不使用`new @RawType List()之类的东西。这已经是有效的Java 8语法,并且类型注释允许在需要的任何地方使用它,例如,@@ RawType List =(@RawType List)genericMethod();。考虑到原始类型当前会产生编译器警告,除非已放置适当的@SuppressWarnings,否则@RawType将是合理的替代,并且不需要更细微的语法。 (2认同)

axt*_*avt 8

编写时List<String> list = new LinkedList();,编译器会生成"未选中"警告.您可以忽略它,但如果您曾经忽略这些警告,您可能还会错过警告,通知您有关真正的类型安全问题.

因此,编写一个不会产生额外警告的代码会更好,而菱形运算符允许您以方便的方式执行此操作而无需不必要的重复.