跳转至

深入理解 Java Streams

简介

Java Streams 是 Java 8 引入的一项强大功能,它为处理集合数据提供了一种简洁、高效且函数式的方式。通过 Streams,开发者可以以声明式的风格对数据进行过滤、映射、归约等操作,极大地提高了代码的可读性和可维护性。本文将深入探讨 Java Streams 的基础概念、使用方法、常见实践以及最佳实践。

目录

  1. 基础概念
  2. 使用方法
    • 创建 Stream
    • 中间操作
    • 终端操作
  3. 常见实践
    • 过滤数据
    • 映射数据
    • 归约数据
  4. 最佳实践
    • 避免不必要的中间操作
    • 并行流的合理使用
    • 与传统集合操作的结合
  5. 小结
  6. 参考资料

基础概念

Java Stream 是一系列支持串行和并行聚合操作的元素。它不是数据结构,不存储数据,而是从数据源(如集合、数组)获取数据,并在其上进行各种操作。Stream 操作具有以下特点: - 函数式编程风格:Stream 操作鼓励使用函数式编程风格,将计算视为函数的求值,避免可变状态和副作用。 - 延迟执行:Stream 的中间操作(如过滤、映射)是延迟执行的,只有在终端操作(如遍历、归约)被调用时,才会开始实际的计算。 - 并行处理:Stream 支持并行处理,可以利用多核处理器的优势,提高处理大规模数据的效率。

使用方法

创建 Stream

  1. 从集合创建: ```java import java.util.Arrays; import java.util.List; import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); Stream streamFromList = numbers.stream(); Stream parallelStreamFromList = numbers.parallelStream(); } } 2. **从数组创建**:java import java.util.Arrays; import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { int[] array = {1, 2, 3, 4, 5}; IntStream streamFromArray = Arrays.stream(array); } } 3. **使用 `Stream.of` 方法**:java import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { Stream streamFromString = Stream.of("apple", "banana", "cherry"); } } ```

中间操作

中间操作返回一个新的 Stream,并且是延迟执行的。常见的中间操作有: 1. 过滤(filter): ```java import java.util.Arrays; import java.util.List; import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Stream<Integer> filteredStream = numbers.stream()
              .filter(n -> n % 2 == 0);
    }
}
```
  1. 映射(map): ```java import java.util.Arrays; import java.util.List; import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); Stream mappedStream = numbers.stream() .map(n -> n * 2); } } 3. **排序(sorted)**:java import java.util.Arrays; import java.util.List; import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(5, 2, 4, 1, 3); Stream sortedStream = numbers.stream() .sorted(); } } ```

终端操作

终端操作会触发 Stream 的计算,并返回一个结果。常见的终端操作有: 1. 遍历(forEach): ```java import java.util.Arrays; import java.util.List; import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        numbers.stream()
              .forEach(System.out::println);
    }
}
```
  1. 归约(reduce): ```java import java.util.Arrays; import java.util.List; import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream() .reduce(0, (a, b) -> a + b); } } 3. **收集(collect)**:java import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); List evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); } } ```

常见实践

过滤数据

过滤数据是 Stream 最常见的应用之一。例如,从一个字符串列表中过滤出长度大于 5 的字符串:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "fig");
        List<String> longWords = words.stream()
              .filter(word -> word.length() > 5)
              .collect(Collectors.toList());
    }
}

映射数据

映射操作可以将一个 Stream 中的元素转换为另一种类型的元素。例如,将一个整数列表中的每个元素平方:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> squaredNumbers = numbers.stream()
              .map(n -> n * n)
              .collect(Collectors.toList());
    }
}

归约数据

归约操作可以将 Stream 中的元素合并为一个结果。例如,计算一个整数列表的乘积:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        int product = numbers.stream()
              .reduce(1, (a, b) -> a * b);
    }
}

最佳实践

避免不必要的中间操作

过多的中间操作会增加 Stream 的复杂性和执行时间。尽量将多个操作合并为一个操作,以提高效率。例如:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        // 推荐:将过滤和映射合并为一个操作
        List<Integer> result1 = numbers.stream()
              .filter(n -> n % 2 == 0)
              .map(n -> n * 2)
              .collect(Collectors.toList());

        // 不推荐:分开进行过滤和映射操作
        Stream<Integer> filteredStream = numbers.stream().filter(n -> n % 2 == 0);
        List<Integer> result2 = filteredStream.map(n -> n * 2).collect(Collectors.toList());
    }
}

并行流的合理使用

并行流可以利用多核处理器提高处理效率,但并不是在所有情况下都适用。并行流的创建和管理会带来一定的开销,对于小规模数据,串行流可能更快。在使用并行流时,要确保数据量足够大,并且操作可以有效并行化。例如:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> largeNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
        // 并行流
        List<Integer> parallelResult = largeNumbers.parallelStream()
              .filter(n -> n % 2 == 0)
              .map(n -> n * 2)
              .collect(Collectors.toList());

        // 串行流
        List<Integer> sequentialResult = largeNumbers.stream()
              .filter(n -> n % 2 == 0)
              .map(n -> n * 2)
              .collect(Collectors.toList());
    }
}

与传统集合操作的结合

虽然 Stream 提供了强大的功能,但在某些情况下,传统的集合操作可能更合适。例如,在需要频繁修改集合元素时,使用传统的循环和集合方法可能更高效。合理结合 Stream 和传统集合操作,可以充分发挥两者的优势。

小结

Java Streams 为处理集合数据提供了一种简洁、高效且函数式的方式。通过理解基础概念、掌握使用方法、熟悉常见实践以及遵循最佳实践,开发者可以利用 Streams 提高代码的可读性和可维护性,同时提升性能。在实际应用中,要根据具体需求选择合适的 Stream 操作和策略,以达到最佳效果。

参考资料