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

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

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

Set set = new HashSet(); set.addAll(list); 

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

 Set.of(list.toArray()) 

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

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

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

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

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

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

您仍然可以使用以下方法在一行中从List (具有潜在重复项)创建Set

 Set set = new HashSet<>(list); 

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

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

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

这里解释了Set.of()List.of()Map.of()方法(以及它们的众多重载)背后的原始设计思想。在Java 9中重载的方便工厂方法的重点是什么 , 在这里 ,其中提到的重点是小型集合 ,这是内部API的常见问题,因此可以获得性能优势。 虽然目前这些方法委托varargs方法没有提供任何性能优势,但这可以很容易地改变(虽然不知道阻塞是什么)。

你希望这是一个“最后的胜利”,就像我想的HashSet一样,但这是一个刻意的决定(正如斯图尔特马克斯 – 这些解释的创造者)。 他甚至有一个这样的例子:

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

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

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

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

List.ofSet.ofMap.ofMap.ofEntries静态工厂方法的主要设计目标是使程序员能够通过在源代码中明确列出元素来创建这些集合。 当然,存在对少量元素或条目的偏见,因为它们更常见,但这里的相关特征是元素在源代码中列出。

如果向Set.of提供重复元素或向Map.ofMap.ofEntries提供重复键,那么行为应该是什么? 假设元素在源代码中明确列出,这可能是编程错误。 诸如首胜或最后胜利之类的替代方案似乎可能会默默地掩盖错误,因此我们认为将重复作为错误是最好的行动方案。 如果明确列出了元素,那么如果这是一个编译时错误就会很好。 但是,直到运行时才检测到重复项的检测,因此在那时抛出exception是我们能做的最好的事情。

*将来,如果所有参数都是常量表达式或者是常量可折叠的,那么Set或Map创建也可以在编译时进行评估,也可以进行常量折叠。 这可能会在编译时检测到重复项。

如果您有一组元素并且想要对它们进行重复数据删除,那么用例怎么样? 这是一个不同的用例,并且Set.ofMap.ofEntries没有很好地处理它。 你必须先创建一个中间数组,这非常麻烦:

 Set set = Set.of(list.toArray()); 

这不会编译,因为list.toArray()返回一个Object[] 。 这将生成Set ,无法将其分配给Set 。 你希望toArray给你一个String[]代替:

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

这个类型检查,但它仍然会引发重复的exception! 提出了另一种选择:

 Set set = new HashSet<>(list); 

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

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

 Set set = Set.copyOf(list); 

这会从源集合的元素创建一个不可修改的集合,并且重复项不会引发exception。 相反,使用任意一个重复项。 List.copyOfMap.copyOf有类似的方法。 作为奖励,如果源集合已经是正确类型的不可修改集合,则这些方法会跳过创建副本。

Set.of(E …元素)

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

抛出:

 IllegalArgumentException - if there are any duplicate elements 

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

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

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

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