关于java:通过谓词查找第一个元素

Find first element by predicate

我刚刚开始使用Java 8 lambdas,我正在尝试实现我在函数式语言中习惯的一些东西。

例如,大多数函数式语言都有某种类型的查找函数,这些函数对序列进行操作,或者返回第一个元素的列表,谓词为true。 我在Java 8中实现这一目标的唯一方法是:

1
2
3
lst.stream()
    .filter(x -> x > 5)
    .findFirst()

然而这对我来说似乎效率低下,因为过滤器会扫描整个列表,至少根据我的理解(这可能是错误的)。 有没有更好的办法?


不,过滤器不会扫描整个流。这是一个中间操作,它返回一个惰性流(实际上所有中间操作都返回一个惰性流)。为了说服你,你可以简单地做以下测试:

1
2
3
4
5
6
7
List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
            .peek(num -> System.out.println("will filter" + num))
            .filter(x -> x > 5)
            .findFirst()
            .get();
System.out.println(a);

哪个输出:

1
2
3
will filter 1
will filter 10
10

您会看到实际只处理了流的两个第一个元素。

所以你可以采用你的方法,这是非常好的。


However this seems inefficient to me, as the filter will scan the whole list

不,它不会 - 一旦找到满足谓词的第一个元素,它就会"中断"。你可以在流包javadoc中阅读更多关于懒惰的内容,特别是(强调我的):

Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example,"find the first String with three consecutive vowels" need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.


1
return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().orElse(null);

我不得不从对象列表中过滤出一个对象。所以我用这个,希望它有所帮助。


除了Alexis C的答案之外,如果您正在使用数组列表,在该列表中您不确定您要搜索的元素是否存在,请使用此列表。

1
2
3
4
5
Integer a = list.stream()
                .peek(num -> System.out.println("will filter" + num))
                .filter(x -> x > 5)
                .findFirst()
                .orElse(null);

然后你可以简单地检查a是否是null


如果您正在寻找布尔返回值,我们可以通过添加null检查来做得更好:

1
return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().orElse(null) != null;

除非你的列表非常庞大(数千个元素),否则在这里使用流只是昂贵的,甚至使代码更难理解。

注意:java不是一种函数式语言(jvm并不特别适合有效地实现函数式语言)。

更简单,更有效率(在所有Iterable上):

1
2
for (MyType walk : lst)
    if (walk > 5) { do_whatever; break; }

或者如果你想跳过迭代器:

1
2
for (int x=0; x<list.size(); x++)
    if (list.get(x) > 5 { do_whatever; break; }

实际上,我真的很想知道为什么有些人会建议使用这种复杂而昂贵的流程机器,即使对于获得阵列的第一个元素等微不足道的事情也是如此。 (是的:Java8中仍然支持数组)。