关于泛型:Java 7中的菱形运算符(<>)有什么意义?

What is the point of the diamond operator (<>) in Java 7?

Java 7中的菱形操作符允许如下代码:

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

然而,在Java 5/6中,我可以简单地写:

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

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

为什么还要麻烦钻石呢?它允许什么新的功能/类型安全?如果它没有产生任何新的功能,为什么他们把它作为一个功能来提到呢?我对这个概念的理解有缺陷吗?


问题与

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

在左侧,您使用的是通用类型List,在右侧,您使用的是原始类型LinkedList。Java中的原始类型只与前泛型代码兼容,不应用于新代码中,除非你绝对得这么做。

现在,如果Java从一开始就有泛型,并且没有类型,比如EDCX1(6),它最初是在有泛型之前创建的,它可能是这样做的,以便泛型类型的构造函数在可能的情况下从赋值的左手边自动推断出它的类型参数。但事实并非如此,为了向后兼容,它必须对原始类型和泛型类型进行不同的处理。这使得它们需要做一个稍微不同但同样方便的方法来声明一个通用对象的新实例,而不必重复它的类型参数…钻石操作员。

对于您最初的List list = new LinkedList()示例,编译器会为该分配生成一个警告,因为它必须这样做。考虑一下:

1
2
3
4
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);

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

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

但是,diamond运算符允许将赋值的右侧定义为一个真正的泛型实例,该实例与左侧具有相同的类型参数…不必再输入这些参数。它允许您以几乎与使用原始类型相同的努力来保持泛型的安全性。

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


你的理解有点缺陷。钻石接线员是一个很好的功能,因为你不必重复你自己。在声明类型时定义一次类型是有意义的,但是在右侧再次定义类型却没有意义。干燥原理。

现在来解释一下关于定义类型的所有模糊性。在运行时删除该类型是正确的,但如果希望从具有类型定义的列表中检索某个内容,则在声明该列表时会将其恢复为所定义的类型,否则它将丢失所有特定功能,并且只有对象功能,除非将检索到的对象强制转换为其原始类型,否则它可以有时很棘手,会导致类异常。

使用List list = new LinkedList()将获得RAWTYPE警告。


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

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

因此,问题转换为:为什么[未选中]警告不会仅在创建新集合时自动取消?

我认为,这比添加<>特性要困难得多。

厄普:我还认为,如果法律上只为一些事情使用原始类型,那将是一团糟。


理论上,Diamond运算符允许您通过保存重复的类型参数来编写更紧凑(和可读)的代码。实际上,只不过是两个让人困惑的字符而已。为什么?

  • 没有理智的程序员在新代码中使用原始类型。所以编译器可以简单地假设,通过不编写类型参数,您希望它推断它们。
  • Diamond运算符不提供类型信息,它只是说编译器"会很好的"。所以省略它,你就不会有任何伤害。在菱形运算符合法的任何地方,编译器都可以"推断"它。
  • IMHO,有一个清晰而简单的方法来标记一个源作为Java 7将比发明这些奇怪的东西更有用。在如此标记的代码中,可以禁止原始类型,而不会丢失任何内容。

    顺便说一句,我不认为应该使用编译开关来完成。程序文件的Java版本是文件的属性,根本没有选择。使用一些像

    1
    package 7 com.example;

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


    当您编写List list = new LinkedList();时,编译器会生成一个"unchecked"警告。您可以忽略它,但是如果您曾经忽略这些警告,您也可能会错过一个通知您实际类型安全问题的警告。

    因此,最好编写一个不会产生额外警告的代码,Diamond操作符允许您以方便的方式执行,而不需要重复。


    其他响应中所说的都是有效的,但是用例不是完全有效的imho。如果您检查了guava,特别是与集合相关的内容,那么静态方法也会这样做。例如,lists.newarraylist(),它允许您编写

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

    或静态导入

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

    番石榴还有其他非常强大的功能,像这样,我真的想不出它有什么用途。

    如果将菱形运算符行为设为默认行为,即从表达式的左侧推断类型,或者从右侧推断左侧的类型,则会更有用。后者是发生在斯卡拉。


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

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

    1
    List<String> list = new ArrayList();

    必须将@SuppressWarnings("unchecked")指定为list(否则将收到未选中的强制转换警告)。我的理解是钻石运营商正在努力使开发变得更容易。它与泛型的运行时执行完全无关。