Java Stream介绍

&白帝& 2024-10-17 16:05:02 阅读 91

Java Stream API 是 Java 8 引入的一项强大功能,旨在简化集合数据的处理。它允许开发者以更简洁和声明性的方式执行复杂的数据操作。以下是对 Java Stream API 的详细介绍,包括其核心概念、常见操作、性能优化以及最佳实践。

一 核心概念

1.1 流(Stream):

流是一种对数据序列的抽象,可以从集合、数组、I/O 通道等源创建。流并不存储数据,而是按需计算。

1.2 操作:

流操作分为中间操作和终端操作:

中间操作:返回一个新的流,如 filter()、map()、sorted(),可以链式调用。终端操作:产生一个结果或副作用,并结束流的处理,如 forEach()、collect()、reduce()。

1.3 惰性求值:

中间操作是惰性执行的,只有在终端操作被调用时才会进行计算。这意味着你可以构建一个操作链,但流不会执行任何计算直到需要结果。

1.4 无状态和有状态操作:

无状态操作:不依赖于流中元素的顺序,如 filter() 和 map()。有状态操作:需要整个流的上下文信息,如 distinct() 和 sorted()。

1.5 并行流:

通过调用 parallelStream(),可以轻松实现并行处理,提高性能,尤其是在处理大规模数据时。

<code>import java.util.Arrays;

import java.util.List;

public class ParallelStreamExample {

public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 使用并行流计算总和

int sum = numbers.parallelStream()

.mapToInt(Integer::intValue)

.sum();

System.out.println("总和: " + sum); // 输出: 总和: 15

}

}

二 常见操作

2.1. 创建 Stream

从集合创建:

List<String> list = Arrays.asList("a", "b", "c");

Stream<String> streamFromList = list.stream();

从数组创建:

String[] array = { "a", "b", "c"};

Stream<String> streamFromArray = Arrays.stream(array);

从文件创建(使用 Files 类):

Stream<String> lines = Files.lines(Paths.get("file.txt"));

2.2. 中间操作

方法 描述
filter(Predicate<? super T> predicate) 根据给定的条件过滤流中的元素。
map(Function<? super T, ? extends R> mapper) 将流中的每个元素映射为另一个元素,返回一个新的流。
distinct() 返回一个只包含不同元素的新流,去除重复项。
sorted() 返回一个按照自然顺序排序的新流。
sorted(Comparator<? super T> comparator) 使用指定的比较器对流中的元素进行排序。
limit(long maxSize) 返回一个包含流中前 maxSize 个元素的新流。
skip(long n) 跳过流中的前 n 个元素,返回其余部分的新流。

1. filter:

filter 是 Java Stream API 中的一个中间操作,用于根据给定的条件过滤流中的元素。它接受一个 Predicate

函数作为参数,该函数返回 true 或 false,以决定是否保留某个元素。

import java.util.Arrays;

import java.util.List;

import java.util.stream.Collectors;

public class FilterExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

// 过滤出长度大于3的名字

List<String> filteredNames = names.stream()

.filter(name -> name.length() > 3)

.collect(Collectors.toList());

System.out.println(filteredNames); // 输出: [Alice, Charlie, David]

}

}

参数:filter 方法接受一个 Predicate<? super T> 类型的参数,T 是流中元素的类型。返回值:返回一个新的流,其中包含通过 Predicate 测试的所有元素。惰性求值:filter 是惰性操作,这意味着它不会立即执行,而是等到终结操作(如 collect())被调用时才会计算。链式调用:可以与其他流操作方法(如 map、sorted)链式结合使用。

2. map

map 是 Java Stream API 中的一个中间操作,用于将流中的每个元素应用一个函数,并返回一个包含转换后元素的新流。它通常用于从流中提取或计算新值。

import java.util.Arrays;

import java.util.List;

import java.util.stream.Collectors;

public class MapExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// 将名字转换为大写字母

List<String> upperCaseNames = names.stream()

.map(String::toUpperCase)

.collect(Collectors.toList());

System.out.println(upperCaseNames); // 输出: [ALICE, BOB, CHARLIE, DAVID]

}

}

详细说明

参数:map 方法接受一个 Function<? super T, ? extends R> 类型的参数,其中 T 是流中元素的类型,R

是转换后元素的类型。返回值:返回一个新的流,其中每个元素都是通过应用提供的函数对原始元素进行转换后的结果。惰性求值:map 是惰性操作,它不会立即执行,而是在遇到终结操作(如 collect())时才执行。链式调用:可以与其他流操作方法(如 filter、sorted)一起链式调用。

3 distinct:

distinct 是 Java Stream API 中的一个中间操作,用于去除流中的重复元素。它返回一个包含唯一元素的新流。

import java.util.Arrays;

import java.util.List;

import java.util.stream.Collectors;

public class DistinctExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Alice", "David", "Bob");

// 去除重复的名字

List<String> distinctNames = names.stream()

.distinct()

.collect(Collectors.toList());

System.out.println(distinctNames); // 输出: [Alice, Bob, Charlie, David]

}

}

详细说明

行为:distinct 方法通过使用 Object.equals() 方法来判断元素是否相同,因此对于自定义对象,需要重写

equals() 和 hashCode() 方法,以确保正确判断重复元素。返回值:返回一个新的流,其中只包含唯一的元素。惰性求值:distinct 是惰性操作,只有在遇到终结操作(例如 collect())时才会执行。

4 sorted:

sorted 是 Java Stream API中的一个中间操作,用于对流中的元素进行排序。它可以按自然顺序排序,或者你可以提供一个自定义的比较器。

按自然顺序排序

import java.util.Arrays;

import java.util.List;

import java.util.stream.Collectors;

public class SortedExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Charlie", "Alice", "Bob", "David");

// 按自然顺序排序

List<String> sortedNames = names.stream()

.sorted()

.collect(Collectors.toList());

System.out.println(sortedNames); // 输出: [Alice, Bob, Charlie, David]

}

}

使用自定义比较器

如果你想按特定的顺序进行排序,可以传递一个比较器。例如,按字符串长度排序:

import java.util.Arrays;

import java.util.Comparator;

import java.util.List;

import java.util.stream.Collectors;

public class SortedWithComparatorExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Charlie", "Alice", "Bob", "David");

// 按字符串长度排序

List<String> sortedByLength = names.stream()

.sorted(Comparator.comparingInt(String::length))

.collect(Collectors.toList());

System.out.println(sortedByLength); // 输出: [Bob, Alice, David, Charlie]

}

}

详细说明

返回值:sorted 返回一个新的流,其中元素按指定的顺序排列。惰性求值:sorted 是惰性操作,只有在遇到终结操作(例如 collect())时才会执行。稳定性:sorted 操作是稳定的,这意味着相等的元素在排序后的顺序与原始顺序相同。

5 limit

limit 是 Java Stream API 中的一个中间操作,用于限制流中元素的数量。它返回一个包含最多指定数量元素的新流。

import java.util.Arrays;

import java.util.List;

import java.util.stream.Collectors;

public class LimitExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

// 限制流中最多只保留前3个元素

List<String> limitedNames = names.stream()

.limit(3)

.collect(Collectors.toList());

System.out.println(limitedNames); // 输出: [Alice, Bob, Charlie]

}

}

详细说明

行为:limit(n) 方法返回一个新的流,其中包含最多 n 个元素。如果流中的元素少于 n,则返回所有元素。惰性求值:limit 是惰性操作,只有在遇到终结操作(例如 collect())时才会执行。适用场景:常用于分页或从大数据集中提取前几个元素。

6 skip

skip 是 Java Stream API 中的一个中间操作,用于跳过流中的前若干个元素。它返回一个包含剩余元素的新流。

import java.util.Arrays;

import java.util.List;

import java.util.stream.Collectors;

public class SkipExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

// 跳过前2个元素

List<String> skippedNames = names.stream()

.skip(2)

.collect(Collectors.toList());

System.out.println(skippedNames); // 输出: [Charlie, David, Eve]

}

}

详细说明

行为:skip(n) 方法返回一个新的流,其中跳过了前 n 个元素。如果流中的元素少于或等于 n,则返回一个空流。惰性求值:skip 是惰性操作,只有在遇到终结操作(如 collect())时才会执行。适用场景:常用于分页或在处理数据时跳过不需要的前几个元素。

2.3. 终端操作

方法 描述
forEach(Consumer<? super T> action) 对流中的每个元素执行给定的操作。
collect(Collector<? super T, A, R> collector) 将流的元素收集到集合、数组或其他形式,常用于数据汇总。
reduce(T identity, BinaryOperator accumulator) 通过反复结合流中的元素来生成单个值,使用初始值。
reduce(BinaryOperator accumulator) 类似于上面的 reduce,但不需要初始值。
count() 返回流中元素的数量。
min(Comparator<? super T> comparator) 返回流中的最小值,基于给定的比较器。
max(Comparator<? super T> comparator) 返回流中的最大值,基于给定的比较器。
findFirst() 返回流中的第一个元素,可能为空。
findAny() 返回流中的任意元素,通常用于并行流。
allMatch(Predicate<? super T> predicate) 检查流中的所有元素是否符合给定条件。
anyMatch(Predicate<? super T> predicate) 检查流中是否有任意元素符合给定条件。
noneMatch(Predicate<? super T> predicate) 检查流中是否没有元素符合给定条件。
toArray() 将流的元素收集到一个数组中。
sum() 对于数值流(如 IntStream, DoubleStream, LongStream),返回所有元素的总和。
average() 对于数值流,返回所有元素的平均值。

1. forEach:

forEach 是 Java Stream API中的一个终结操作,用于对流中的每个元素执行指定的操作。它通常用于遍历流中的所有元素并执行某些副作用,比如打印、修改状态等。

import java.util.Arrays;

import java.util.List;

public class ForEachExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

// 遍历并打印每个名字

names.stream()

.forEach(name -> System.out.println(name));

}

}

详细说明

行为:forEach 接受一个 Consumer 函数式接口作为参数,定义对每个元素的处理逻辑。终结操作:forEach 是一种终结操作,意味着在调用后流无法再被使用。副作用:由于 forEach 用于执行操作,因此通常带有副作用,比如输出到控制台或更新外部状态。

2 collect:

collect 是 Java Stream API 中的一个终结操作,它用于将流中的元素收集到容器中,例如列表、集合或映射。collect方法通常与 Collectors 类一起使用,提供了多种常用的收集方式。

import java.util.Arrays;

import java.util.List;

import java.util.stream.Collectors;

public class CollectExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

// 收集到一个列表

List<String> collectedNames = names.stream()

.collect(Collectors.toList());

System.out.println(collectedNames); // 输出: [Alice, Bob, Charlie, David, Eve]

}

}

详细说明

行为:collect 方法接受一个 Collector,该 Collector 定义了如何将流中的元素收集到目标容器。终结操作:collect 是一种终结操作,意味着在调用后流无法再被使用。

常用的 Collectors

toList(): 收集到一个 List。

toSet(): 收集到一个 Set,去除重复元素。

toMap(): 收集到一个 Map,需要提供键和值的映射函数。

joining(): 将流中的字符串连接成一个字符串。

收集到一个 Map

import java.util.Arrays;

import java.util.List;

import java.util.Map;

import java.util.stream.Collectors;

public class CollectToMapExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 收集到一个 Map,键为名字,值为名字的长度

Map<String, Integer> namesMap = names.stream()

.collect(Collectors.toMap(name -> name, String::length));

System.out.println(namesMap); // 输出: {Alice=5, Bob=3, Charlie=7}

}

}

将名字连接成一个字符串

import java.util.Arrays;

import java.util.List;

import java.util.stream.Collectors;

public class JoiningExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 将名字连接成一个字符串

String result = names.stream()

.collect(Collectors.joining(", "));

System.out.println(result); // 输出: Alice, Bob, Charlie

}

}

3 reduce:

reduce 是 Java Stream API 中的一个终结操作,主要用于将流中的元素进行归约(即通过某种逻辑将流中的所有元素合并成一个单一的结果)。它可以用于多种情况,比如求和、求最大值、连接字符串等。

求和

import java.util.Arrays;

import java.util.List;

public class ReduceExample {

public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 使用 reduce 求和

int sum = numbers.stream()

.reduce(0, Integer::sum);

System.out.println(sum); // 输出: 15

}

}

求最大值

import java.util.Arrays;

import java.util.List;

public class ReduceMaxExample {

public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(3, 5, 1, 8, 2);

// 使用 reduce 求最大值

int max = numbers.stream()

.reduce(Integer::max)

.orElseThrow(); // 或者提供默认值

System.out.println(max); // 输出: 8

}

}

字符串连接

import java.util.Arrays;

import java.util.List;

public class ReduceStringExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 使用 reduce 连接字符串

String result = names.stream()

.reduce("", (s1, s2) -> s1 + s2 + ", ");

System.out.println(result); // 输出: Alice, Bob, Charlie,

}

}

4. count:

count 是 Java Stream API 中的一个终结操作,用于计算流中元素的数量。它返回一个 long类型的值,表示流中元素的总数。

import java.util.Arrays;

import java.util.List;

public class CountExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// 计算流中的元素数量

long count = names.stream().count();

System.out.println(count); // 输出: 4

}

}

详细说明

返回值:count 返回流中元素的数量,类型为 long。性能:count 是一个高效的操作,因为它只需遍历流一次即可获得结果。

5 min

min 是 Java Stream API 中的一个终结操作,用于找出流中最小的元素。它返回一个Optional,因为流可能为空,因此无法保证总是有最小值。

import java.util.Arrays;

import java.util.List;

import java.util.Optional;

public class MinExample {

public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(3, 5, 1, 8, 2);

// 使用 min 找到最小值

Optional<Integer> min = numbers.stream()

.min(Integer::compareTo);

min.ifPresent(System.out::println); // 输出: 1

}

}

详细说明

参数:min 方法接受一个比较器(Comparator)作为参数,用于定义元素之间的顺序。返回值:返回一个 Optional,如果流为空,则返回 Optional.empty();否则返回包含最小值的 Optional。

6 max

max 是 Java Stream API 中的一个终结操作,用于找出流中最大的元素。它返回一个Optional,因为流可能为空,因此无法保证总是有最大值。

import java.util.Arrays;

import java.util.List;

import java.util.Optional;

public class MaxExample {

public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(3, 5, 1, 8, 2);

// 使用 max 找到最大值

Optional<Integer> max = numbers.stream()

.max(Integer::compareTo);

max.ifPresent(System.out::println); // 输出: 8

}

}

详细说明

参数:max 方法接受一个比较器(Comparator)作为参数,用于定义元素之间的顺序。返回值:返回一个 Optional,如果流为空,则返回 Optional.empty();否则返回包含最大值的 Optional。

7 findFirst

findFirst 是 Java Stream API 中的一个终结操作,主要用于从流中返回第一个元素。这个方法非常有用,当你需要获取流中的第一个元素时,它能够提供一种简洁有效的方法。

import java.util.Arrays;

import java.util.List;

import java.util.Optional;

public class FindFirstExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 获取第一个元素

Optional<String> firstName = names.stream()

.findFirst();

firstName.ifPresent(System.out::println); // 输出: Alice

RetailOrderItem retailOrderItem = retailOrderDTO.getRetailOrderItemList().stream()

.filter(o -> o.getRetailSpuId().equals(retailSpu.getId()))

.findFirst()

.orElse(null);

}

}

详细说明

返回值:findFirst 返回一个 Optional,如果流不为空,则返回包含第一个元素的 Optional;否则返回

Optional.empty()。顺序:对于有序流(如列表),findFirst返回的是第一个元素;对于无序流(如集合),结果不一定是添加的第一个元素,因为无序流的处理可能是并行的。

8 findAny

findAny 是 Java Stream API 中的一个终结操作,用于从流中返回任意元素。这个方法的主要用途是在需要快速找到流中的某个元素时,尤其是在并行流处理时。

import java.util.Arrays;

import java.util.List;

import java.util.Optional;

public class FindAnyFilteredExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Brian");

// 找到任意一个以 'B' 开头的名字

Optional<String> anyWithB = names.stream()

.filter(name -> name.startsWith("B"))

.findAny();

anyWithB.ifPresent(System.out::println); // 可能输出: Bob 或 Brian

}

}

9 toArray

toArray 是 Java Stream API中的一个终结操作,用于将流中的元素收集到一个数组中。这个方法非常灵活,可以根据需要返回不同类型的数组。

import java.util.Arrays;

import java.util.List;

public class ToArrayExample {

public static void main(String[] args) {

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 将流转换为字符串数组

String[] namesArray = names.stream()

.toArray(String[]::new);

// 输出数组内容

System.out.println(Arrays.toString(namesArray)); // 输出: [Alice, Bob, Charlie]

}

}

10 sum

sum 是 Java Stream API 中的一个终结操作,主要用于对流中的数值元素进行求和。这个方法适用于IntStream、LongStream 和 DoubleStream,可以方便地计算这些基本数据类型的总和。

import java.util.Arrays;

import java.util.List;

public class SumExample {

public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 使用 Stream 计算总和

int sum = numbers.stream()

.mapToInt(Integer::intValue) // 转换为 IntStream

.sum();

System.out.println("总和: " + sum); // 输出: 总和: 15

}

}

详细说明

返回值:sum 方法返回流中所有元素的总和。适用类型:sum 仅适用于基本数值流,如 IntStream、LongStream 和 DoubleStream。如果流包含对象类型的元素,需先进行转换。

11 average

average 是 Java Stream API 中的一个终结操作,用于计算流中数值元素的平均值。它适用于IntStream、LongStream 和 DoubleStream,可以非常方便地获取这些基本数据类型的平均值

import java.util.Arrays;

import java.util.List;

import java.util.OptionalDouble;

public class AverageExample {

public static void main(String[] args) {

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 使用 Stream 计算平均值

OptionalDouble average = numbers.stream()

.mapToInt(Integer::intValue) // 转换为 IntStream

.average();

// 输出平均值

average.ifPresent(avg -> System.out.println("平均值: " + avg)); // 输出: 平均值: 3.0

}

}

详细说明

返回值:average 方法返回一个 OptionalDouble,表示流中所有元素的平均值。如果流为空,则返回一个空的

OptionalDouble。适用类型:average 仅适用于基本数值流,如 IntStream、LongStream 和

DoubleStream。如果流包含对象类型的元素,需先进行转换。

三 性能优化

避免不必要的操作:尽量减少链中不必要的中间操作,避免重复计算。使用并行流:对于大数据集,使用 parallelStream() 可以利用多核处理器提高性能。预先估算大小:在创建流之前,估算数据源的大小有助于优化性能(例如,使用 StreamSupport.stream())。

四 最佳实践

保持链的简单性:避免过长的操作链,保持代码的可读性。对原始数据进行操作:尽量对原始数据进行操作,而不是在已处理的数据上继续操作。异常处理:在流操作中处理异常时,要确保逻辑清晰,可以考虑使用 try-catch 块。使用收集器:利用 Collectors 类提供的丰富功能,如 toMap()、joining() 进行复杂的收集操作。

五 示例代码

以下是一个综合示例,展示了如何使用 Java Stream API 处理一组学生对象,筛选出成绩优秀的学生并排序。

class Student {

String name;

int score;

Student(String name, int score) {

this.name = name;

this.score = score;

}

public String getName() {

return name;

}

public int getScore() {

return score;

}

}

import java.util.*;

import java.util.stream.Collectors;

public class StudentManagement {

public static void main(String[] args) {

List<Student> students = Arrays.asList(

new Student("Alice", 85),

new Student("Bob", 90),

new Student("Charlie", 70),

new Student("David", 95),

new Student("Eva", 78)

);

// 1. 筛选出成绩在 80 分以上的学生

List<String> topStudents = students.stream()

.filter(s -> s.getScore() > 80) // 过滤条件

.sorted(Comparator.comparing(Student::getScore).reversed()) // 降序排序

.map(Student::getName) // 提取姓名

.collect(Collectors.toList()); // 收集结果

// 输出成绩优秀的学生

System.out.println("成绩在80分以上的学生: " + topStudents);

// 2. 计算所有学生的平均分

double averageScore = students.stream()

.mapToInt(Student::getScore) // 提取分数

.average() // 计算平均值

.orElse(0); // 如果没有学生,则返回0

// 输出平均分

System.out.printf("所有学生的平均分: %.2f%n", averageScore);

}

}

成绩在80分以上的学生: [David, Bob, Alice]

所有学生的平均分: 83.60

六 总结

Java Stream API 显著提升了 Java 数据处理的能力,通过其简单易用的接口和强大的功能,使得集合操作更加高效和优雅。无论是在日常开发中还是在处理复杂数据流时,掌握 Stream API 都将极大地提高工作效率。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。