如果元素是重复的,为什么Set.of()会抛出IllegalArgumentException?

时间:2017-12-04 08:52:58

标签: java collections set java-9

在Java 9中,在Set接口上引入了新的静态工厂方法,称为of(),它接受多个元素,甚至是元素数组。

我想将一个列表转换为一个集合,以删除集合中的任何重复条目,这可以通过以下方式完成(在Java 9之前):

Set<String> set = new HashSet<>();
set.addAll(list);

但我认为使用这个新的Java 9静态工厂方法会很酷:

Set.of(list.toArray())

其中list是以前定义的字符串列表。

但是,当元素重复时,java抛出了IllegalArgumentException,也在方法的Javadoc中说明了。这是为什么?

编辑 :这个问题与另一个关于概念上等效的主题Map.of()方法的问题不重复,但截然不同。并非所有()方法的静态工厂都表现相同。换句话说,当我询问Set.of()方法时,我不会点击处理Map.of()方法的问题。

5 个答案:

答案 0 :(得分:25)

Set是一种创建手动Set.of("foo", "bar", "baz", "foo");的简短方法。在这种情况下,如果你给它重复值,那将是一个明显的编程错误,因为你应该自己写出这些元素。即List显然是程序员的错误。

你的方式实际上是一种非常糟糕的方式。如果您要将Set转换为Set<Foo> foo = new HashSet<>(myList);,可以使用toSet()或您希望的任何其他方式(例如使用流和收集toArray())。优点包括不做无用的Set,选择自己的LinkedHashSet(您可能希望Set.of()保留订单)等。缺点包括必须输入更多的代码字符

List.of()Map.of()tiny_mce_before_init方法(及其众多重载)背后的原始设计理念在What is the point of overloaded Convenience Factory Methods for Collections in Java 9here进行了解释,其中& #39;提到重点是小型集合,这是内部API的常见问题,因此可以获得性能优势。虽然目前这些方法委托varargs方法没有提供任何性能优势,但是这很容易改变(虽然不确定阻止是什么)。

答案 1 :(得分:17)

Set.of()工厂方法为给定数量的元素生成不可变Set个。

在支持固定数量参数(static <E> Set<E> of​()static <E> Set<E> of​(E e1)static <E> Set<E> of​(E e1,E e2)等等)的变体中,不重复的要求更容易理解 - 当你调用方法Set.of(a,b,c),表示你希望创建一个完全 3个元素的不可变Set,所以如果参数包含重复项,那么拒绝输入是有意义的产生较小的Set

虽然Set<E> of​(E... elements)变体不同(如果允许创建任意数量元素的Set,但它遵循其他变体的相同逻辑。如果您将n元素传递给该方法,则表明您希望创建一个完全 Set元素的不可变n,因此不允许重复。< / p>

您仍然可以使用以下内容从Set(具有潜在重复项)创建List

Set<String> set = new HashSet<>(list);

在Java 9之前已经可用。

答案 2 :(得分:6)

你希望这是&#34;最后胜利&#34;,就像HashSet我猜,但这是故意的决定(作为Stuart Marks - 这些解释的创造者)。他甚至有一个这样的例子:

Map.ofEntries(
   "!", "Eclamation"
   .... // lots of other entries
   ""
   "|", "VERTICAL_BAR"
);

选择是因为这可能容易出错,所以应该禁止它。

另请注意,Set.of()会返回不可变Set,因此您可以将Set打包成:

Collections.unmodifiableCollection(new HashSet<>(list))

答案 3 :(得分:2)

List.ofSet.ofMap.ofMap.ofEntries静态工厂方法的主要设计目标是使程序员能够通过在列表中显式列出元素来创建这些集合。源代码。自然,偏向于少量元素或条目,因为它们比较常见,但是此处的相关特征是元素在源代码中列出。

如果向Set.of提供了重复的元素,或者向Map.ofMap.ofEntries提供了重复的键,该怎么办?假设在源代码中明确列出了这些元素,则可能是编程错误。诸如首奖或最后奖之类的选择似乎可能会默默地掩盖错误,因此我们决定将重复副本作为错误是最佳的做法。如果显式列出了元素,那么如果这是编译时错误,那就更好了。但是,只有在运行时才检测到重复项,*因此,此时最好抛出异常。

  

*将来,如果所有参数都是常量表达式或常量可折叠的,则还可以在编译时评估Set或Map的创建并对其进行常量折叠。这样可以使在编译时检测到重复项。

如果您有一个元素集合并且想要对它们进行重复数据删除,该用例又如何呢?这是一个不同的用例,Set.ofMap.ofEntries处理得不好。您必须先创建一个中间数组,这很麻烦:

Set<String> set = Set.of(list.toArray());

这不会编译,因为list.toArray()返回了Object[]。这将产生一个Set<Object>,无法将其分配给Set<String>。您希望toArray给您一个String[]

Set<String> set = Set.of(list.toArray(new String[0]));

此类型检查,但仍会引发重复异常!建议另一种选择:

Set<String> set = new HashSet<>(list);

这有效,但是您会得到一个HashSet,它是可变的,并且比Set.of返回的集合占用更多的空间。您可以通过HashSet对元素进行重复数据删除,从中获取一个数组,然后将其传递给Set.of。那行得通,但真是令人沮丧。

幸运的是,这在Java 10中已修复。您现在可以编写:

Set<String> set = Set.copyOf(list);

这会从源集合的元素中创建一个不可修改的集合,并且重复项不会抛出异常。而是使用重复项中的任意一个。有类似的方法List.copyOfMap.copyOf。另外,如果源集合已经是正确类型的不可修改集合,则这些方法会跳过创建副本的操作。

答案 4 :(得分:1)

Set.of​(E... elements)

  

结果集的元素类型将是数组的组件类型,集的大小将等于数组的长度。

     

<强>抛出:

Set

很明显,这不会进行任何重复测试,因为Set的大小将是数组的长度。

此方法只是为了能够在一行中填充Set.of("A","B","C");

Set

但你必须小心自己的副本。 (这将简单地迭代varargs并将它们添加到新的{{1}}。