Java 8 Stream API的常见误用与正确姿势

后端思维 2019-05-01 ⋅ 21 阅读

Java 8 引入了 Stream API,该 API 提供了一种函数式的方式来处理集合数据。它可以帮助我们更加简洁、高效地处理数据,但是在使用 Stream API 时也有一些常见的误用情况。本文将介绍一些常见的误用情况,并提供正确的姿势来使用 Stream API。

误用1:频繁创建新的 Stream

经常看到有人这样使用 Stream API:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream().filter(n -> n % 2 == 0).forEach(System.out::println);
numbers.stream().filter(n -> n % 2 != 0).forEach(System.out::println);

这样的写法会频繁地创建新的 Stream 对象,影响性能,正确的做法是使用流的终端操作来完成一系列处理:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
       .filter(n -> n % 2 == 0)
       .forEach(System.out::println);
numbers.stream()
       .filter(n -> n % 2 != 0)
       .forEach(System.out::println);

误用2:不恰当的短路操作

Stream API 中提供了一系列的短路操作,比如 findFirstfindAnyanyMatchallMatchnoneMatch,这些操作在满足条件时可以提前结束流的处理。但是在一些情况下,我们可能会错误地使用这些操作。

例如,我们需要找到列表中的第一个满足条件的元素:

List<String> fruits = Arrays.asList("apple", "banana", "orange");
Optional<String> firstFruit = fruits.stream()
                                   .filter(f -> f.startsWith("a"))
                                   .findFirst();

这种写法是正确的姿势,但是如果我们错误地使用了 findAny 来替代 findFirst

List<String> fruits = Arrays.asList("apple", "banana", "orange");
Optional<String> firstFruit = fruits.stream()
                                   .filter(f -> f.startsWith("a"))
                                   .findAny();

这样的写法在逻辑上是有问题的,因为 findAny 并不保证返回的是列表中的第一个匹配元素,而是任意匹配的元素。

误用3:过度使用并行流

Java 8 的 Stream API 还提供了并行流的功能,可以自动利用多个线程来并行执行流的处理操作。但是,并不是所有的场景都适合使用并行流,过度使用并行流可能会导致性能下降。

并行流适合于计算密集型的任务,但是对于 IO 密集型的任务,开启多个线程可能会导致更多的线程切换和额外的开销。

因此,在使用并行流时需要谨慎评估场景,确保使用并行流真的能够提升性能。

误用4:不适当使用无状态的操作

Stream API 中提供了无状态和有状态的操作,无状态操作是指操作的结果不依赖于之前的元素,例如 filtermap;有状态操作是指操作的结果依赖于之前的元素,例如 distinctsorted

在一些情况下,我们可能会错误地使用有状态操作,导致性能下降。比如,我们需要对一个大列表进行排序,但是我们可能会在列表已经是排序好的情况下再次调用 sorted 操作:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> sortedNumbers = numbers.stream()
                                     .sorted()
                                     .collect(Collectors.toList());

这样的写法在排序好的列表上再次调用 sorted 操作是没有意义的,正确的姿势是避免不必要的有状态操作,例如使用 sorted 操作针对未排序的列表,而不是已经排序好的列表。

误用5:忽略异常处理

在 Stream API 中,许多操作可以抛出异常,比如 flatMapreduce 等。如果我们忽略了异常处理,可能会导致异常被吞掉而不容易发现问题。

List<String> words = Arrays.asList("java", "stream", "api");
String result = words.stream()
                     .flatMap(word -> Arrays.stream(word.split("")))
                     .reduce("", String::concat);

在上面的代码中,如果列表中的某个单词为空,则会抛出 NullPointerException 异常。为了避免这种情况,我们应该在适当的位置处理异常:

List<String> words = Arrays.asList("java", "stream", "api");
try {
    String result = words.stream()
                         .flatMap(word -> Arrays.stream(word.split("")))
                         .reduce("", String::concat);
    System.out.println(result);
} catch (NullPointerException e) {
    // 异常处理逻辑
}

通过正确地处理异常,我们可以更好地发现潜在的问题并进行相应的处理。

结论

Stream API 是 Java 8 引入的一个强大工具,可以帮助我们更加简洁、高效地处理集合数据。但是,我们需要注意避免常见的误用情况,从而更好地发挥 Stream API 的优势。本文介绍了一些常见的误用情况,并提供了正确的姿势来使用 Stream API。希望对你使用 Stream API 时有所帮助!


全部评论: 0

    我有话说: