【问题标题】:Why does Set.of() throw an IllegalArgumentException if the elements are duplicates?如果元素重复,为什么 Set.of() 会抛出 IllegalArgumentException?
【发布时间】:2018-05-17 16:42:22
【问题描述】:

在 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() 方法的问题的重复,但明显不同。并非所有静态工厂 of() 方法的行为都相同。换句话说,当我询问有关 Set.of() 方法的问题时,我不会点击处理 Map.of() 方法的问题。

【问题讨论】:

  • 我不确定为什么会这样,但作为替代方案,我可以建议使用以下 Java 8 语句为您执行重复数据删除:list.stream().collect(Collectors.toSet())
  • 或者直接用古单new HashSet&lt;&gt;(list);

标签: java collections set java-9


【解决方案1】:

Set.of() 是一种手动创建小型Set 的捷径。在这种情况下,如果你给它重复的值,那将是一个严重的编程错误,因为你应该自己写出元素。 IE。 Set.of("foo", "bar", "baz", "foo"); 显然是程序员的错误。

你的 cool 方式实际上是一个非常糟糕的方式。如果您想将List 转换为Set,您可以使用Set&lt;Foo&gt; foo = new HashSet&lt;&gt;(myList); 或您希望的任何其他方式(例如使用流和收集toSet())来完成。优点包括不做无用的toArray(),选择您自己的Set(您可能需要LinkedHashSet 来保持顺序)等。缺点包括必须输入更多字符的代码。

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

【讨论】:

  • @Kayaman 虽然这是正确的,例如 List.of... 可能会扩展到其他一些“更大”的集合...stackoverflow.com/questions/47461196/…
  • @Eugene 抱歉,我不确定你的意思?
  • @Kayaman 我的错。我的意思是,您的答案集中在由Set.ofList.of 创建的“手工小元素”集合上。我链接到的 Stuart 的评论说,这可能会扩展到更大的收藏。我希望我说得通
  • @Eugene 是的,我的意思是我假设设计决策是基于此做出的,但这只是Set.of() 的专长。有了List.of() 和朋友,您没有理由不得不假设小型手动创建的集合。虽然,我确实相信 of() 方法背后的最初想法更多是关于有效地创建小型集合(这就是所有重载的原因)。
  • @Kayaman nitpick:他们都还没有,从他们仍然委托给 var-args 的 3 个元素开始
【解决方案2】:

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

在支持固定数量参数的变体中(static &lt;E&gt; Set&lt;E&gt; of​()static &lt;E&gt; Set&lt;E&gt; of​(E e1)static &lt;E&gt; Set&lt;E&gt; of​(E e1,E e2) 等),没有重复的要求更容易理解 - 当您调用方法 Set.of(a,b,c) ,您声明您希望创建一个 恰好 3 个元素的不可变 Set,因此如果参数包含重复项,则拒绝您的输入而不是生成较小的 Set 是有意义的。

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

您仍然可以使用以下方法从List(可能存在重复)创建Set

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

在 Java 9 之前就已经可用了。

【讨论】:

    【解决方案3】:

    您期望这是“最后的胜利”,就像我猜的 HashSet 一样,但这是一个深思熟虑的决定(正如 Stuart Marks - 这些的创建者所解释的那样)。他甚至有一个这样的例子:

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

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

    还要注意Set.of() 返回一个不可变的Set,因此您可以将Set 包装成:

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

    【讨论】:

    • 你有The choice is that since this could be error-prone, they should prohibit it.的参考吗?
    • 谢谢你的链接,我去看看 :)
    【解决方案4】:

    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&lt;String&gt;Set&lt;Object&gt;。你想让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。作为奖励,如果源集合已经是正确类型的不可修改集合,这些方法会跳过创建副本。

    【讨论】:

    • 感谢您提供的最新见解!重复数据删除时选择任意副本正是我所期望的。对我来说,在第一胜还是最后胜之间犹豫是不可能的。
    【解决方案5】:

    Set.of​(E... elements)

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

    投掷:

    IllegalArgumentException - if there are any duplicate elements
    

    很明显,这并没有做任何重复测试,因为Set 的大小将是数组的长度。

    该方法只是为了能够在一行中获得填充的Set

    Set.of("A","B","C");
    

    但你必须自己小心复制。 (这将简单地迭代可变参数并将它们添加到新的 Set 中。

    【讨论】:

      【解决方案6】:

      正确答案:

      Set.copyOf(list);
      

      此代码将创建包含来自源集合的唯一值的集合,而不会引发重复异常。

      原始问题没有一个答案。只有很多词解释了为什么 Set.of 抛出异常是正确的(这是有争议的)。 还要记住,以这种方式创建的集合是不可变的,你应该在 99% 的时间里使用它。这就是我总是使用番石榴的原因,但最近 sonarqube 告诉我应该切换到 Java 9 不可变对象并且没有提到行为实际上是不同的,ImmutableSet.of 静默过滤重复项,Set.of 抛出重复异常。

      当我有 3 个变量并想要构建一个只包含唯一值的不可变集合时,我遇到了类似的问题。番石榴开箱即用:

      MyType one = ...;
      MyType two = ...;
      MyType three = ...;
      ImmutableSet.of(one, two, three);
      

      使用 Java 9,您需要过多的代码:

      MyType one = ...;
      MyType two = ...;
      MyType three = ...;
      Set.copyOf(List.of(one, two, three));
      

      而 Java 9 甚至没有方法

      【讨论】:

      • 嗯,最初的问题是它为什么会抛出异常。您正在解释如何创建一组唯一值,但这不是问题的答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-27
      • 1970-01-01
      • 1970-01-01
      • 2015-12-04
      • 1970-01-01
      • 2015-08-08
      • 2017-12-26
      相关资源
      最近更新 更多