Stream 是 Java 8 引入的一种新的抽象,用于处理集合类 (Collection) 的数据。Stream 并不存储数据,而是按需计算数据。Stream 操作有两个重要特性:
- 流水线操作 (Pipelining):Stream 操作可以链式调用,形成一个流水线,这些操作既可以是中间操作(intermediate operation),也可以是终端操作(terminal operation)。
- 内部迭代 (Internal Iteration):不同于集合类的外部迭代 (external iteration),Stream 使用内部迭代,通过底层的迭代器实现。
Stream 的优点
- 简洁性:Stream API 提供了声明性的方法链来处理数据,这使得代码更简洁、更易读。
- 易于并行化:Stream API 提供了简单的并行化处理数据的方式,通过 parallelStream 可以轻松实现并行计算,提高处理性能。
- 函数式编程风格:Stream API 支持函数式编程,允许使用 Lambda 表达式和方法引用,减少了样板代码。
- 延迟执行:中间操作是惰性求值的,只有在终端操作执行时才会计算,优化了性能。
更高的抽象层次:Stream API 提供了一种更高层次的数据处理抽象,使得代码更具表达力和可维护性。
用途
Stream API 主要用于对集合数据进行操作,比如过滤、排序、映射、归约等。它提供了一种函数式编程的风格,使代码更简洁、易读、可维护。常见用途包括:
- 过滤 (Filtering):从集合中筛选出符合条件的元素。
- 映射 (Mapping):将集合中的每个元素映射成另一种形式。
- 归约 (Reduction):将集合中的元素组合成一个值。
- 收集 (Collecting):将处理后的数据转换为其他集合形式。
- 统计 (Statistics):对数据进行统计计算,如计数、求和、平均值等。
常用的 Stream 操作示例:
1. 创建 Stream
List<String> list = Arrays.asList("a", "b", "c", "d");
// 从集合创建 Stream
Stream<String> stream = list.stream();
// 从数组创建 Stream
Stream<String> stream = Stream.of("a", "b", "c", "d");
2. 中间操作 (Intermediate Operations)
中间操作会返回一个新的 Stream,它们是惰性求值的,只有在终端操作执行时才会执行。
// 过滤
Stream<String> filteredStream = stream.filter(s -> s.startsWith("a"));
// 映射
Stream<String> mappedStream = stream.map(String::toUpperCase);
// 排序
Stream<String> sortedStream = stream.sorted();
// 去重
Stream<String> distinctStream = stream.distinct();
// 排序
Stream<String> sortedStream = stream.sorted();
Stream<String> sortedByComparator = stream.sorted((s1, s2) -> s2.compareTo(s1));
3. 终端操作 (Terminal Operations)
终端操作会触发 Stream 的计算,并生成一个结果或副作用。
// 收集
List<String> collectedList = stream.collect(Collectors.toList());
// 统计
long count = stream.count();
// 查找
Optional<String> firstElement = stream.findFirst();
//reduce:归约操作,将元素组合成一个值
Optional<String> concatenated = stream.reduce((s1, s2) -> s1 + s2);
代码案例
案例 1:过滤和映射
List<String> strings = Arrays.asList("apple", "banana", "orange", "apple", "mango");
List<String> result = strings.stream() //获取stream对象
.filter(s -> s.startsWith("a")) //过滤,检查列表中是否以“a”开头,满足条件保留到流中
.map(String::toUpperCase) //映射,将流中每个字符串转换为大写
.collect(Collectors.toList()); //收集,将流中的元素收集到一个list中
System.out.println(result); // 输出:[APPLE, APPLE]
案例 2:排序和去重
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> result = numbers.stream()
.distinct() //去重
.sorted() //排序
.collect(Collectors.toList());
System.out.println(result); // 输出:[2, 3, 5, 7]
案例 3:归约操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum); //reduce:归约操作,将元素组合成一个值
System.out.println(sum); // 输出:15
案例 4:分组操作
List<String> strings = Arrays.asList("apple", "banana", "orange", "apple", "mango");
Map<String, Long> result = strings.stream()
.collect(Collectors.groupingBy(s -> s, Collectors.counting()));
//终端操作,收集 使用Collectors.groupingBy()方法来收集流中的元素。
// 这个方法接受两个参数:一个是分类函数(在这里是s -> s,表示使用字符串本身作为键)
// 另一个是下游的收集器(在这里是Collectors.counting(),用于计算每个键的出现次数)。
System.out.println(result); // 输出:{orange=1, banana=1, apple=2, mango=1}
案例 5:并行流
List<String> strings = Arrays.asList("apple", "banana", "orange", "apple", "mango");
List<String> result = strings.parallelStream() //并行处理集合中的数据,适用与大规模的计算、数据转换、过滤等操作
.filter(s -> s.startsWith("a"))
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
System.out.println(result); // 输出:[APPLE, APPLE]
案例 6:生成数列
List<Integer> numbers = IntStream.range(1, 10) //创建了一个从1到9的整数序列的IntStream
.boxed() //将IntStream中的原始类型int转换成Integer对象的Stream
.collect(Collectors.toList());
System.out.println(numbers); // 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]
案例 7:字符串拼接
List<String> strings = Arrays.asList("apple", "banana", "orange");
String result = strings.stream()
.collect(Collectors.joining(", ")); //字符串拼接
System.out.println(result); // 输出:apple, banana, orange
注意事项
1.排序 stream.sorted()
Stream<String> sortedStream = stream.sorted();
Stream<String> sortedByComparator = stream.sorted((s1, s2) -> s2.compareTo(s1));
- 无参的 sorted():元素按照它们的自然顺序排序。如果你的元素是数字或字符串等基本类型,它们将按照自然数值或字典顺序排序。如果你的元素是对象,它们将按照 Comparable 接口中定义的顺序排序。
- 带Comparator的 sorted():元素按照你提供的 Comparator 排序。这允许你定制排序逻辑,例如,你可以按字符串长度排序、按某个属性的逆序排序等。
2.中间操作 (Intermediate Operations) 和 终端操作 (Terminal Operations) 的区别
中间操作:
- 返回一个新的 Stream。
- 惰性求值:中间操作本身不会触发实际的计算,只有在终端操作执行时才会计算。
- 例子:filter, map, flatMap, distinct, sorted, limit, skip, peek。
终端操作:
- 触发 Stream 的计算,并生成结果。
- 不是惰性求值,一旦调用终端操作,整个 Stream 的计算就会执行。
- 终端操作会关闭 Stream,之后不能再对其进行操作。
- 例子:forEach, toArray, reduce, collect, min, max, count, anyMatch, allMatch, noneMatch, findFirst, findAny。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。