Java 8 findFirst并遇到订单

findFirst的JavaDocs表示如果流具有遭遇顺序,则将始终返回第一个元素,但如果流没有遇到顺序,则可能返回任何元素。

我试图演示它如何在没有遭遇顺序的流上工作,但我不能让它返回除了实际的第一个元素之外的任何东西。

我尝试将元素添加到Set ,它没有定义的遭遇顺序:

  Set words = new HashSet(); words.addAll(Arrays.asList("this", "is", "a", "stream", "of", "strings")); Optional firstString = words.stream() .findFirst(); System.out.println(firstString); 

每次我跑,我得到a作为第一个字符串。 然后我尝试在List上执行Collections.shuffle ,然后将其添加到Set ,但这并没有改变任何东西。

  List wordList = Arrays.asList("this", "is", "a", "stream", "of", "strings"); words = new HashSet(); words.addAll(wordList); firstString = words.stream() .findFirst(); System.out.println(firstString); 

我每次都会回复这个词。

然后我尝试使用BaseStreamunordered方法,该方法声称返回没有遇到顺序的流,但没有区别:

  firstString = Stream.of("this", "is", "a", "stream", "of", "strings") .unordered() .findFirst(); System.out.println(firstString); 

现在我每次都得到这个词。 我错过了什么吗? 有没有办法certificate无序流上的findFirst返回不同的值?

好吧,“任何”包括“第一”的可能性。 当然,Stream实现不会浪费随机化数据的工作,因此对于很多情况,特别是顺序执行,它仍然是第一个元素,如果我们可以这样调用它(因为没有命令,有没有尊贵的第一元素)。

findFirst展示不同结果的最佳机会是使用并行Streams。 但即便如此,并非所有操作组合都适合展示无序性。

有一点是,在当前实现中 ,当Stream无序时, findFirst() 操作不会改变它的行为,即它不会主动尝试像findAny() 。 由于Stream的来源 ,它仍然可能表现出不可预测的行为,但如果您的源是Stream.of("this", "is", "a", "stream", "of", "strings") ,即已知大小的不可变序列,它已经具有可能的最佳并行性能,因此根本无法获得链式unordered()的好处,因此,当前实现不会改变其行为。

这可能会让人感到惊讶,但这在某种程度上甚至适用于HashSet 。 虽然它有一个未指定的顺序,但在某个时间点它的后备数组中会有一个实际的顺序,只要你不修改Set ,就没有理由将这些条目随机改变,所以对于一个特定的HashSet例如,您可能会重复获取相同的“第一个”元素,尽管未指定哪个元素,甚至在单个运行时内,另一个表示相同内容但具有不同历史记录的HashSet实例可能具有不同的顺序。


已知从无序特征中获益的操作的一个示例是distinct 。 虽然它必须整理重复,但它必须保持第一次遇到相同的元素,如果它产生显着的差异。 这会显着降低性能,因此,如果流是无序的,实现将立即尝试获得好处。 例如

 List equal=IntStream.range(0, 100) .mapToObj(i->new String("test")) // don't do this in normal code .collect(Collectors.toList()); Map map = IntStream.range(0, equal.size()) .collect(IdentityHashMap::new, (m,i)->m.put(equal.get(i),i), Map::putAll); equal.parallelStream().distinct().map(map::get) .findFirst().ifPresent(System.out::println); 

这会创建一堆equal但可区分的String实例(您通常不应该这样做),在IdentityHashMap使用它们的位置编号注册它们,这样我们就可以找出, distinct保留了哪个实例。 由于上面的代码使用由List创建的有序流,因此无论您执行它的频率如何,它都会始终打印0

相反,

 equal.parallelStream().unordered().distinct().map(map::get) .findFirst().ifPresent(System.out::println); 

将打印任意数量的范围,因为我们已经发布了有序合同并允许选择任何相同的字符串。


如前所述,这是所有特定于实现的 。 你永远不应该假设一个操作是否可以实际获得一个好处,从而改变它对无序流的行为。 上面的解释仅仅是为了说明为什么有时特定实现的行为可能不会因无序流而改变。 但是,它仍然可能在下一个版本或不同的JRE实现中。

霍尔格已经巧妙地解释了这种情况。 (+1)我想提供具有相同内容但具有不同迭代顺序的HashSet实例的演示。 首先我们像以前一样创建一个集合:

  List wordList = Arrays.asList("this", "is", "a", "stream", "of", "strings"); Set words = new HashSet<>(wordList); 

我们创建另一组单词,添加一堆东西(无论它究竟是什么),然后将其删除:

  Set words2 = new HashSet<>(wordList); IntStream.range(0, 50).forEachOrdered(i -> words2.add(String.valueOf(i))); words2.retainAll(wordList); 

如果我们检查结果如下:

  System.out.println(words.equals(words2)); System.out.println(words); System.out.println(words2); 

我们可以从输出中看到集合相等但以不同的顺序迭代:

 true [a, strings, stream, of, this, is] [this, is, strings, stream, of, a] 

如其他地方所述,如果从这些中获取流并调用findFirst() ,则结果是迭代顺序中的第一个元素,这些元素在这些集合之间明显不同。

发生了什么,通过添加和删除一堆元素,我们已经导致集合增加其内部表大小,需要重新元素。 即使在删除了新元素之后,原始元素也会在新表中的不同相对位置结束。

虽然HashSets没有指定的迭代顺序,但如果每次以相同的方式使用相同的内容初始化集合,则顺序可能是可重复的(甚至可预测的)。 因此,我们说来自集合的流没有定义的遭遇顺序,即使每次顺序通常相同。

请注意,在JDK 9中,新的不可变集(和映射)实际上是随机的,因此它们的迭代顺序将在不同的运行之间发生变化,即使它们每次都以相同的方式初始化。

通过将您的流标记为无序,您实际上没有这样做(您没有在您的Set中使订单有任何不同),而是取消了有序流可能强加的任何限制。

certificate这将返回不同结果的方法是使用并行流。

  Set words = new HashSet<>(); words.addAll(Arrays.asList("this", "is", "a", "stream", "of", "strings")); Optional firstString = words.stream().parallel() .findFirst(); System.out.println(firstString); 

运行几次,显示:

  Optional[strings] and then Optional[this] 

将您的Set更改为List并并行运行将保留订单:

  List words = new ArrayList<>(); words.addAll(Arrays.asList("this", "is", "a", "stream", "of", "strings")); Optional firstString = words.stream().parallel() .findFirst(); System.out.println(firstString); // always Optional[this] 

这里绝对必读的是霍尔格很棒的答案

正如@Eugene已经提到的那样, unordered调用并不一定会改变元素的实际物理顺序。 不要忘记, unordered是一个中间操作,在调用终端操作之前不执行任何操作。

因此我倾向于这样想:

  1. 当创建一个包含元素"this", "is", "a", "stream", "of", "strings"Set时,那么当迭代它时Set中的第一个元素是"a" ,所以findFirst只返回那个值。

  2. 当您使用Stream.of("this", "is", "stream", "of", "strings")创建流时,它将返回具有排序限制的流,该流将受到findFirst尊重。 调用unordered会删除该限制,但元素"this"仍然是物理上的第一个元素,因为unordered并不一定会改变源数组中的顺序。

一个更好的例子可能如下:

 Set words = new HashSet<>(); words.addAll(Arrays.asList("this", "is", "stream", "of", "strings")); Optional firstString1 = words.stream().findFirst(); // Optional[strings] System.out.println(firstString1); Optional firstString2 = words.stream() .sorted().findFirst(); // Optional[is] System.out.println(firstString2); Optional firstString3 = Stream.of("this", "is", "stream", "of", "strings") .findFirst(); // Optional[this] System.out.println(firstString3); Optional firstString4 = Stream.of("this", "is", "stream", "of", "strings") .unordered().findFirst(); // Optional[this] System.out.println(firstString4); 

注意sorted()方法如何更改结果,因为它强制执行排序限制,这与无效的无效方法不同。