Java Stream API:集合的高效处理,让你的代码像诗一样流畅 🚀
各位程序猿、攻城狮、代码艺术家们,大家好!👋 今天,咱们要聊聊Java世界里的一件神器,一个能让你的代码告别“面条式”🍝,变得像诗一样流畅优雅的利器——Java Stream API!
想象一下,你面前堆满了各种各样的数据,就像一座小山,你需要从这座小山里筛选出符合特定条件的金子,然后把这些金子打磨成闪闪发光的宝石。 如果你用传统的循环遍历方式,那就像拿着小铲子一点一点地挖,不仅效率低,而且代码也显得臃肿不堪,让人看了就头疼 😫。
但是,有了Stream API,一切就变得不一样了!它就像一台挖掘机,能快速地筛选出你需要的金子,然后通过一系列精密的加工流水线,自动地把它们打磨成宝石。整个过程简洁高效,代码也变得赏心悦目。
一、什么是Stream API?它为什么这么牛? 🐂
Stream API,顾名思义,就是处理数据流的一套API。它不是一种数据结构,而是一种处理数据的方式。你可以把它想象成一条流水线,数据从流水线的一端进入,经过一系列的加工处理,最终从另一端流出。
为什么Stream API如此强大?
-
声明式编程: Stream API采用声明式编程风格,你只需要告诉它“你要什么”,而不需要告诉它“怎么做”。这样可以大大简化代码,提高可读性。
-
链式调用: Stream API支持链式调用,你可以将多个操作像链条一样连接起来,形成一个流畅的处理流程。
-
延迟执行: Stream API采用延迟执行策略,只有在需要结果的时候才会真正执行。这意味着它可以避免不必要的计算,提高效率。
-
并行处理: Stream API可以轻松地进行并行处理,充分利用多核CPU的优势,大幅提高处理速度。
-
代码简洁: 使用Stream API可以减少大量的循环代码,使代码更加简洁易懂。
二、Stream API的基本概念:掌握这些,你就能玩转Stream! 🕹️
要玩转Stream API,我们需要掌握几个核心概念:
-
Stream(流): Stream是元素的序列,它可以来自集合、数组、I/O管道等等。你可以把Stream想象成一条河流,数据就像水一样在河流中流动。
-
中间操作(Intermediate Operations): 中间操作是对Stream进行转换的操作,例如
filter、map、sorted等等。中间操作会返回一个新的Stream,所以可以进行链式调用。你可以把中间操作想象成河流中的一个个加工站,数据流经过这些加工站,会被筛选、转换、排序等等。 -
终端操作(Terminal Operations): 终端操作是触发Stream处理的操作,例如
forEach、collect、count等等。终端操作会返回一个结果,或者产生副作用。你可以把终端操作想象成河流的终点,数据流到达终点后,会被收集起来或者进行其他的处理。 -
管道(Pipeline): 由一个源(source)、零个或多个中间操作和一个终端操作组成的流程,称为Stream管道。
用表格来总结一下:
| 概念 | 解释 | 示例 |
|---|---|---|
| Stream(流) | 元素的序列,来自集合、数组、I/O管道等。 | List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Stream<String> stream = names.stream(); |
| 中间操作(Intermediate Operations) | 对Stream进行转换的操作,返回一个新的Stream。例如:filter(过滤)、map(映射)、sorted(排序)、distinct(去重)、limit(限制数量)、skip(跳过)等。 |
stream.filter(name -> name.startsWith("A")).map(String::toUpperCase).sorted(); |
| 终端操作(Terminal Operations) | 触发Stream处理的操作,返回一个结果或产生副作用。例如:forEach(遍历)、collect(收集)、count(计数)、reduce(规约)、toArray(转换为数组)、min(最小值)、max(最大值)、anyMatch(任意匹配)、allMatch(全部匹配)、noneMatch(全部不匹配)、findFirst(查找第一个)、findAny(查找任意一个)等。 |
stream.forEach(System.out::println); List<String> result = stream.collect(Collectors.toList()); |
| 管道(Pipeline) | 由一个源、零个或多个中间操作和一个终端操作组成的流程。 | names.stream().filter(name -> name.length() > 3).map(String::toUpperCase).collect(Collectors.toList()); |
三、Stream API的常用操作:让你轻松驾驭数据! 🏎️
接下来,我们来详细讲解一些常用的Stream API操作,并通过示例代码来演示它们的用法。
1. 创建Stream:
-
从集合创建:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Stream<String> stream = names.stream(); // 顺序流 Stream<String> parallelStream = names.parallelStream(); // 并行流 -
从数组创建:
String[] names = {"Alice", "Bob", "Charlie"}; Stream<String> stream = Arrays.stream(names); -
使用
Stream.of()创建:Stream<String> stream = Stream.of("Alice", "Bob", "Charlie"); -
使用
Stream.iterate()创建无限流:Stream<Integer> stream = Stream.iterate(0, n -> n + 2); // 创建一个无限的偶数流 stream.limit(10).forEach(System.out::println); // 打印前10个偶数 -
使用
Stream.generate()创建无限流:Stream<Double> stream = Stream.generate(Math::random); // 创建一个无限的随机数流 stream.limit(5).forEach(System.out::println); // 打印前5个随机数
2. 中间操作:
-
filter(Predicate<T> predicate):过滤根据指定的条件过滤元素,只保留符合条件的元素。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Anna"); names.stream() .filter(name -> name.startsWith("A")) // 过滤以"A"开头的名字 .forEach(System.out::println); // 输出:Alice, Anna -
map(Function<T, R> mapper):映射将Stream中的每个元素映射成一个新的元素。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream() .map(String::toUpperCase) // 将名字转换为大写 .forEach(System.out::println); // 输出:ALICE, BOB, CHARLIE -
flatMap(Function<T, Stream<R>> mapper):扁平化映射将Stream中的每个元素映射成一个Stream,然后将所有Stream合并成一个Stream。
List<List<String>> names = Arrays.asList( Arrays.asList("Alice", "Bob"), Arrays.asList("Charlie", "David") ); names.stream() .flatMap(List::stream) // 将List<List<String>>转换为Stream<String> .forEach(System.out::println); // 输出:Alice, Bob, Charlie, David -
sorted():排序对Stream中的元素进行排序。
List<String> names = Arrays.asList("Charlie", "Alice", "Bob"); names.stream() .sorted() // 默认按字母顺序排序 .forEach(System.out::println); // 输出:Alice, Bob, Charlie可以使用
Comparator自定义排序规则:List<String> names = Arrays.asList("Charlie", "Alice", "Bob"); names.stream() .sorted(Comparator.comparingInt(String::length)) // 按字符串长度排序 .forEach(System.out::println); // 输出:Bob, Alice, Charlie -
distinct():去重去除Stream中重复的元素。
List<String> names = Arrays.asList("Alice", "Bob", "Alice", "Charlie"); names.stream() .distinct() // 去除重复的元素 .forEach(System.out::println); // 输出:Alice, Bob, Charlie -
peek(Consumer<T> action):窥视对Stream中的每个元素执行一些操作,但不改变Stream本身。通常用于调试。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream() .peek(name -> System.out.println("Processing: " + name)) // 打印正在处理的元素 .map(String::toUpperCase) .forEach(System.out::println); -
limit(long maxSize):限制数量限制Stream中元素的数量。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream() .limit(2) // 只保留前两个元素 .forEach(System.out::println); // 输出:Alice, Bob -
skip(long n):跳过跳过Stream中前n个元素。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream() .skip(1) // 跳过第一个元素 .forEach(System.out::println); // 输出:Bob, Charlie
3. 终端操作:
-
forEach(Consumer<T> action):遍历对Stream中的每个元素执行一些操作。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream() .forEach(System.out::println); // 输出:Alice, Bob, Charlie -
toArray():转换为数组将Stream中的元素转换为数组。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); String[] array = names.stream().toArray(String[]::new); System.out.println(Arrays.toString(array)); // 输出:[Alice, Bob, Charlie] -
collect(Collector<T, A, R> collector):收集将Stream中的元素收集到一个集合中。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // 收集到List List<String> list = names.stream().collect(Collectors.toList()); System.out.println(list); // 输出:[Alice, Bob, Charlie] // 收集到Set Set<String> set = names.stream().collect(Collectors.toSet()); System.out.println(set); // 输出:[Alice, Bob, Charlie] (顺序可能不同) // 收集到Map (需要指定key和value的映射规则) Map<String, Integer> map = names.stream().collect(Collectors.toMap( name -> name, // key为name String::length // value为name的长度 )); System.out.println(map); // 输出:{Alice=5, Bob=3, Charlie=7} // 连接字符串 String joinedString = names.stream().collect(Collectors.joining(", ")); System.out.println(joinedString); // 输出:Alice, Bob, Charlie -
reduce(BinaryOperator<T> accumulator):规约将Stream中的元素规约为一个值。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> sum = numbers.stream().reduce(Integer::sum); // 计算所有元素的和 System.out.println(sum.orElse(0)); // 输出:15 // 带初始值的reduce int product = numbers.stream().reduce(1, (a, b) -> a * b); // 计算所有元素的乘积 System.out.println(product); // 输出:120 -
count():计数统计Stream中元素的数量。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); long count = names.stream().count(); System.out.println(count); // 输出:3 -
min(Comparator<T> comparator):最小值查找Stream中的最小值。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> min = numbers.stream().min(Integer::compare); System.out.println(min.orElse(0)); // 输出:1 -
max(Comparator<T> comparator):最大值查找Stream中的最大值。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> max = numbers.stream().max(Integer::compare); System.out.println(max.orElse(0)); // 输出:5 -
anyMatch(Predicate<T> predicate):任意匹配判断Stream中是否至少有一个元素满足指定的条件。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); boolean anyMatch = names.stream().anyMatch(name -> name.startsWith("A")); System.out.println(anyMatch); // 输出:true -
allMatch(Predicate<T> predicate):全部匹配判断Stream中是否所有元素都满足指定的条件。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); boolean allMatch = names.stream().allMatch(name -> name.length() > 2); System.out.println(allMatch); // 输出:true -
noneMatch(Predicate<T> predicate):全部不匹配判断Stream中是否所有元素都不满足指定的条件。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); boolean noneMatch = names.stream().noneMatch(name -> name.length() > 10); System.out.println(noneMatch); // 输出:true -
findFirst():查找第一个查找Stream中的第一个元素。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Optional<String> first = names.stream().findFirst(); System.out.println(first.orElse("")); // 输出:Alice -
findAny():查找任意一个查找Stream中的任意一个元素。 在并行流中,效率更高,因为只需要找到一个即可返回。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Optional<String> any = names.stream().findAny(); System.out.println(any.orElse("")); // 输出:Alice (顺序可能不同)
四、Stream API的并行处理:让你的代码飞起来! 🚀
Stream API支持并行处理,可以充分利用多核CPU的优势,大幅提高处理速度。 使用parallelStream()方法可以创建一个并行流。
List<Integer> numbers = IntStream.rangeClosed(1, 1000000).boxed().collect(Collectors.toList());
// 顺序流
long startTime = System.nanoTime();
int sumSequential = numbers.stream().reduce(0, Integer::sum);
long endTime = System.nanoTime();
System.out.println("Sequential Stream Sum: " + sumSequential + ", Time: " + (endTime - startTime) / 1_000_000 + " ms");
// 并行流
startTime = System.nanoTime();
int sumParallel = numbers.parallelStream().reduce(0, Integer::sum);
endTime = System.nanoTime();
System.out.println("Parallel Stream Sum: " + sumParallel + ", Time: " + (endTime - startTime) / 1_000_000 + " ms");
注意:
- 并行流并不是在所有情况下都比顺序流快。 当数据量较小或者操作比较简单时,并行流可能会因为线程切换的开销而变得更慢。
- 并行流可能会导致一些意想不到的问题,例如线程安全问题。 在使用并行流时,需要确保你的代码是线程安全的。
五、Stream API的实际应用:让你看到它的强大! 🌟
Stream API在实际开发中有很多应用场景,例如:
-
数据过滤: 从大量数据中筛选出符合特定条件的数据。
-
数据转换: 将数据从一种格式转换为另一种格式。
-
数据排序: 对数据进行排序。
-
数据聚合: 对数据进行统计、求和、求平均值等操作。
-
数据分组: 将数据按照一定的规则进行分组。
举个例子:
假设我们有一个User类,包含name、age和city三个属性。
class User {
private String name;
private int age;
private String city;
public User(String name, int age, String city) {
this.name = name;
this.age = age;
this.city = city;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getCity() {
return city;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
", city='" + city + ''' +
'}';
}
}
现在我们有一个User列表,我们需要筛选出年龄大于18岁的用户,并将他们的名字转换为大写,然后按照城市进行分组。
List<User> users = Arrays.asList(
new User("Alice", 20, "New York"),
new User("Bob", 17, "London"),
new User("Charlie", 25, "New York"),
new User("David", 30, "London"),
new User("Eve", 22, "Paris")
);
Map<String, List<String>> result = users.stream()
.filter(user -> user.getAge() > 18) // 筛选出年龄大于18岁的用户
.map(User::getName) // 提取用户的名字
.map(String::toUpperCase) // 将名字转换为大写
.collect(Collectors.groupingBy(name -> {
// 根据名字查找对应的城市
for (User user : users) {
if (user.getName().toUpperCase().equals(name)) {
return user.getCity();
}
}
return "Unknown"; // 如果找不到对应的城市,返回"Unknown"
})); // 按照城市进行分组
System.out.println(result); // 输出:{New York=[ALICE, CHARLIE], Paris=[EVE], London=[DAVID]}
可以看到,使用Stream API可以很方便地实现复杂的数据处理逻辑。
六、总结:Stream API,让你的代码更上一层楼! 🚀
Java Stream API是一个强大的工具,可以让你以一种简洁、高效的方式处理集合数据。 掌握Stream API,可以大大提高你的编程效率,让你的代码更加优雅易懂。
希望今天的分享能帮助大家更好地理解和使用Stream API。 记住,学习是一个循序渐进的过程,多练习、多实践,才能真正掌握Stream API的精髓。
祝大家编码愉快! 🎉