Java 8中findAny()和findFirst()的区别

127

我有点困惑于Java 8中Stream API的Stream#findAny()Stream#findFirst()之间的区别。

我的理解是,它们都将返回与过滤器匹配的第一个元素。那么为什么要有两种方法来完成相同的任务呢?我是否遗漏了什么?

8个回答

114
我理解的是两个方法都会返回流中第一个匹配的元素,例如与过滤器(filter)一起使用时。
这并不正确。根据Javadoc文件, Stream#findAny():
返回描述流中某个元素的Optional,如果流为空,则返回一个空的Optional。 此操作的行为明确是非确定性的;它可以自由选择流中的任何元素。这是为了在并行操作中实现最大的性能;

当使用Stream.findFirst()时,它将返回一个Optional<T>,描述了流的严格第一个元素。 Stream类没有.findOne()方法,所以我想你的意思是.findFirst()


我还是不太明白,你的意思是即使应用了 filterfindAny 仍然可以返回任何元素,包括不符合所应用的过滤器的元素? - Koray Tugay
2
@KorayTugay - 不,经过过滤后,无论剩下哪些元素,findAny 可以从中返回任何一个元素(在并行流操作中可能是随机的)。 - KrishPrabakar

65
不,两者都不会返回流的第一个元素。
来自Stream.findAny()(重点在于“some element”):
返回描述流中某个元素的Optional,如果流为空,则返回空的Optional。
这是一项短路终端操作。
此操作的行为明确是不确定的;它可以自由选择流中的任何元素。这是为了在并行操作中实现最大性能;代价是对同一源的多次调用可能不会返回相同的结果。(如果需要稳定的结果,请改用findFirst()。)
因此,简单地说,它可能会选择流的第一个元素,也可能不会。
根据当前的Oracle特定实现,我认为它将返回非并行管道中的第一个元素。但在并行管道中,它不总是会执行,例如:
System.out.println(IntStream.range(0, 100).parallel().findAny());

当我运行它时,它返回了OptionalInt [50] 。但是,无论如何,您都不能依赖它。

26

findFirst选择流的第一个元素,但findAny可以任意选择流中的任何元素。

List<String> lst1 = Arrays.asList("Jhonny", "David", "Jack", "Duke", "Jill","Dany","Julia","Jenish","Divya");
List<String> lst2 = Arrays.asList("Jhonny", "David", "Jack", "Duke", "Jill","Dany","Julia","Jenish","Divya");

Optional<String> findFirst = lst1.parallelStream().filter(s -> s.startsWith("D")).findFirst();
Optional<String> fidnAny = lst2.parallelStream().filter(s -> s.startsWith("J")).findAny();

System.out.println(findFirst.get()); //Always print David
System.out.println(fidnAny.get()); //Print Jack/Jill/Julia :behavior of this operation is explicitly nondeterministic

5

在并行模式下,findAny不能保证顺序,但是findFirst可以。

我写了一些代码片段来展示这种差异,请访问


2

[1]我已经开发了一小段代码来测试流的findAny和findFirst。我创建了1000万个虚拟数据,并使用并行流和顺序流过滤这些数据,然后应用findAny和findFirst。

从这个小实验中,我发现findAny和findFirst给出相同的结果,并且在顺序流中提供第一个结果。但是我发现,在parallelStream中,findAny不能保证每次产生相同的结果,而findFirst则保证它无论流是并行还是顺序,始终产生相同的结果。

public class TestFilter {
static class Employee {
    String name;
    double salary;
    long id;

    public Employee(String name, long id, double salary) {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }

    String genNextName() {
        return "Emp-" + (this.id + 1);
    }

    double genNextSalary() {
        return new Random().nextDouble() * 1000000;
    }

    long genNextId() {
        return this.id + 1;
    }

    @Override
    public String toString() {
        return this.id + " " + this.name + " " + this.salary + "\n";
    }
}

public static void main(String[] args) {
    List<Employee> employees = createDummyEmployee(10000000l);
    List<Employee> emps = null;
    long time = 0;
    for (int i = 0; i < 50; i++) {
        
        Optional<Employee> e1 = employees.stream()
                .filter(e -> e.name.endsWith("999"))
                .filter(e -> e.salary > 10000)
                .filter(e -> e.id % 2 == 1)
                .findAny();
        
        Optional<Employee> e2 = employees.stream()
                .filter(e -> e.name.endsWith("999"))
                .filter(e -> e.salary > 10000)
                .filter(e -> e.id % 2 == 1)
                .findFirst();
        
        Optional<Employee> pe1 = employees.parallelStream()
                .filter(e -> e.name.endsWith("999"))
                .filter(e -> e.salary > 10000).filter(e -> e.id % 2 == 1)
                .findAny();
        
        Optional<Employee> pe2 = employees.parallelStream()
                .filter(e -> e.name.endsWith("999"))
                .filter(e -> e.salary > 10000)
                .filter(e -> e.id % 2 == 1)
                .findFirst();

        System.out.print("FindAny without parallel : " + (e1.isPresent() ? e1.get().id +"": "null"));
        System.out.print(" | FindFirst without parallel : " + (e2.isPresent() ? e2.get().id +"": "null"));
        System.out.print(" | FindAny by Parallel : " + (pe1.isPresent() ? pe1.get().id +"": "null"));
        System.out.print(" | FindFirst by Parallel : " + (pe2.isPresent() ? pe2.get().id +"": "null"));
        System.out.println();
    }
}

public static List<Employee> createDummyEmployee(long n) {
    Employee e1 = new Employee("Emp-1", 1l, 1.0);
    return Stream.iterate(e1, (Employee e) -> new Employee(e.genNextName(), e.genNextId(), e.genNextSalary()))
            .limit(n).collect(Collectors.toList());
}

}

[实验结果] [1]: https://istack.dev59.com/HOZjA.webp


1
在流中,findFirst和findAny返回第一个元素,并且不执行其余部分,但在parallelStream中,无法确定顺序,并且parallelStream会执行集合的其余部分。 参考 时间 1:25:00

1
我只想提醒使用时要小心 findFirst()findAny()
根据它们的 Javadoc(这里这里),两种方法都会从流中返回任意一个元素 - 除非该流具有遇到顺序,在这种情况下,findFirst() 返回第一个元素,而 findAny() 则返回任意一个元素。
假设我们有自定义的list,包含 ISBN 和书名。对于一个场景,请看以下示例:
public class Solution {
   private Integer ISBN;
   private String BookName;

public Solution(int i, String string) {
    ISBN =i;
    BookName = string;
}
//getters and setters
}

public static void main(String[] args) {
        List<Solution> Library = Arrays.asList(new Solution(12,"Java in Action"),new Solution(13,"Java 8"),new Solution(15,"Java 8 Features"),new Solution(16,"Java in Action"));
 System.out.println(Library.stream()
        .map(p->p.getBookName())
        .sorted(Comparator.reverseOrder())
        .findFirst());
    }

输出: 可选[Java in Action]

有时候书名相同但ISBN号码不同,这种情况下排序和查找图书可以与findAny()非常相似,并且会给出错误的结果。想象一下,有5本书的名称都是“Java Reference”,但有不同的ISBN号码,按名称findFirst()查找书籍将与findAny()相同。

想象一种场景:

 ISBN    Name Of book
+-----+------------------+
| 100 | Java-8 in Action |
+-----+------------------+
| 101 | Java-8 in Action |
+-----+------------------+
| 102 | Java-8 in Action |
+-----+------------------+
| 103 | Java-8 in Action |
+-----+------------------+
| 104 | Java-8 in Action |
+-----+------------------+

在按书名排序的情况下,findFirst()和findAny()将给出相同的结果。

详细文章:


-1

Stream无序时,findFirst()findAny()是相同的。但是当Stream有序时,findAny()会更好。


这是不正确的。这两种方法都不是“更好”的,因为它们的行为和用例完全不同。另外,你所说的“有序”的Stream是什么意思?它总是有序的(当它没有并行化时,在给定的Stream上执行操作的顺序每次都相同),但可能不是由用户排序的。 - Jezor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接