Java 8 findFirst并遇到订单

时间:2017-01-27 12:37:12

标签: java java-8

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

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

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

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

每次跑步时,我都会将a作为第一个字符串。然后我尝试在Collections.shuffle上添加List,然后将其添加到Set,但这并没有改变任何内容。

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

我每次都会回复a这个词。

然后我尝试使用unordered中的BaseStream方法,该方法声称返回没有遇到订单的流,但没有区别:

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

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

4 个答案:

答案 0 :(得分:22)

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

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

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

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

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

List<String> equal=IntStream.range(0, 100)
    .mapToObj(i->new String("test")) // don't do this in normal code
    .collect(Collectors.toList());
Map<String, Integer> 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);

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

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

答案 1 :(得分:10)

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

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

我们创建另一组单词,添加一堆东西(并不重要),然后删除它:

    Set<String> 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中,新的不可变集(和映射)实际上是随机的,因此它们的迭代次序将在不同的运行之间发生变化,即使它们每次都以相同的方式初始化。

答案 2 :(得分:9)

通过将您的流标记为无序,您实际上并不是制作它(您没有在您的Set中订单任何不同),而是您正在删除任何其他订购的限制流可能强加。

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

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

运行几次,显示:

  Optional[strings] and then Optional[this]

将您的设置更改为列表并并行运行将保留订单:

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

绝对必须在此处阅读Holger great answer

答案 3 :(得分:3)

正如@Eugene已经提到的,调用unordered并不一定会改变元素的实际物理排序。不要忘记unordered是一个中间操作,在调用终端操作之前什么都不做。

因此我倾向于这样想:

  1. 创建包含元素Set的{​​{1}}时,"this", "is", "a", "stream", "of", "strings"上迭代它的第一个元素是Set,所以{ {1}}只返回该值。

  2. 使用"a"创建流时,它会返回一个具有排序限制的流,findFirst将遵守该流。调用Stream.of("this", "is", "stream", "of", "strings")会删除该限制,但元素findFirst仍然是第一个元素,因为unordered不一定会更改源数组中的顺序。

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

    "this"

    注意unordered方法如何更改结果,因为它强制执行排序限制,不像Set<String> words = new HashSet<>(); words.addAll(Arrays.asList("this", "is", "stream", "of", "strings")); Optional<String> firstString1 = words.stream().findFirst(); // Optional[strings] System.out.println(firstString1); Optional<String> firstString2 = words.stream() .sorted().findFirst(); // Optional[is] System.out.println(firstString2); Optional<String> firstString3 = Stream.of("this", "is", "stream", "of", "strings") .findFirst(); // Optional[this] System.out.println(firstString3); Optional<String> firstString4 = Stream.of("this", "is", "stream", "of", "strings") .unordered().findFirst(); // Optional[this] System.out.println(firstString4); 方法没有效果。