在Java8之前,如果我们要处理集合数据通一般都是使用for循环、while循环和迭代器。
这种方式我们可以叫他命令式编程,要明确的告诉程序怎么做,每一步都要自己实现。
这是Java8之前的代码:
java
package com.lazy.snail.day32;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @ClassName Day32Demo
* @Description TODO
* @Author lazysnail
* @Date 2025/7/14 11:30
* @Version 1.0
*/
public class Day32Demo {
public static void main(String[] args) {
List<Transaction> transactions = getTransactions();
List<Transaction> filteredTransactions = new ArrayList<>();
for (Transaction t : transactions) {
if (t.getValue() > 500) {
filteredTransactions.add(t);
}
}
Collections.sort(filteredTransactions, new Comparator<Transaction>() {
@Override
public int compare(Transaction t1, Transaction t2) {
return Double.compare(t2.getValue(), t1.getValue());
}
});
}
public static List<Transaction> getTransactions() {
List<Transaction> transactions = new ArrayList<>();
transactions.add(new Transaction(450.0));
transactions.add(new Transaction(1200.50));
transactions.add(new Transaction(300.0));
transactions.add(new Transaction(890.75));
transactions.add(new Transaction(500.0));
transactions.add(new Transaction(620.0));
return transactions;
}
}
class Transaction {
double value;
public Transaction(double value) {
this.value = value;
}
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
}
}
从交易数据列表中筛选出价格大于500的交易,并按金额排序。
筛选和排序的逻辑都要一步步的写出来。
java
List<Transaction> sortedTransactions = transactions.stream()
.filter(t -> t.getValue() > 500)
.sorted(Comparator.comparing(Transaction::getValue).reversed())
.toList();
现在用Stream方式直接过滤排序。
Stream用起来就像处理数据的工厂流水线,数据在流水线上经过一道道工序(过滤、映射、排序等),最终产出我们想要的结果。
一、什么是Stream?
首先,要明确Stream不是一种数据结构。它更像一个高级迭代器,从一个数据源(如集合、数组)获取元素,并支持复杂的聚合操作。
他有几个核心的特性:
不存储数据:流本身不存储任何元素。元素存储在底层的数据源里。
函数式本质:对流的操作会生成一个新的流,而不会修改它原始数据源。比如filter操作会生成一个只包含符合条件元素的新流。
惰性求值:流的中间操作(比如如filter, map)并不会马上执行。它们只有在遇到终端操作(像forEach, collect)的时候,才会一次性地、从头到尾地执行整个操作链。
只能消费一次:一个流一旦被终端操作消费过,就不能再被使用。如果需要再次处理,必须从数据源重新创建一个新的流。
比如:
java
package com.lazy.snail.day32;
import java.util.List;
import java.util.stream.Stream;
/**
* @ClassName Day32Demo2
* @Description TODO
* @Author lazysnail
* @Date 2025/7/14 14:02
* @Version 1.0
*/
public class Day32Demo2 {
public static void main(String[] args) {
List<String> list = List.of("懒惰蜗牛", "lazysnail", "lazysnailstudio.com");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);
}
}
再次消费的时候就抛出异常了。

二、Stream的生命周期
一个完整的Stream操作通常分为三个步骤:创建 -> 中间操作 -> 终端操作。
2.1创建流
可以从多种数据源创建流:
从集合创建: list.stream(), set.parallelStream() (并行流)
从数组创建: Arrays.stream(array)
由值创建: Stream.of("a", "b", "c")
由函数创建(无限流):
Stream.iterate(T seed, UnaryOperator<T> f): 从一个种子值开始,不断应用函数f。
Stream.generate(Supplier<T> s): 不断调用get()方法来生成元素。
java
package com.lazy.snail.day32;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
* @ClassName Day32Demo3
* @Description TODO
* @Author lazysnail
* @Date 2025/7/14 14:08
* @Version 1.0
*/
public class Day32Demo3 {
public static void main(String[] args) {
// 从集合创建
List<String> names = List.of("懒惰蜗牛", "lazysnail");
Stream<String> nameStream = names.stream();
// 从数组创建
String[] array = {"懒惰蜗牛", "lazysnail", "lazysnailstudio.com"};
Stream<String> arrayStream = Arrays.stream(array);
// 由值创建
Stream<Integer> numberStream = Stream.of(1, 2, 3);
// iterate 创建无限流: 0, 2, 4, 6... (需要配合limit使用)
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2);
}
}
2.2中间操作
中间操作是流水线上的加工站,每个操作都会返回一个新的流,可以链式调用。
常见的中间操作:
这个作为被操作数据源
java
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 2, 4, 6, 8);
筛选与切片
filter(Predicate<T> p): 过滤出满足条件的元素。
筛选出所有的偶数
java
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.toList();
distinct(): 去除重复元素(基于equals()方法)。
取出重复数字
java
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.toList();
limit(long n): 截断流,使其元素不超过给定数量。
skip(long n): 跳过前n个元素。
java
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0)
.distinct()
.skip(1)
.limit(2)
.toList();
结合上面四个操作,筛选偶数 -> 去重 -> 跳过第1个 -> 取前2个
映射
map(Function<T, R> f): 将每个元素转换为另一个元素。
把每个单词都转换成大写,或者获取长度。
java
package com.lazy.snail.day32;
import java.util.List;
/**
* @ClassName Day32Demo4
* @Description TODO
* @Author lazysnail
* @Date 2025/7/14 14:19
* @Version 1.0
*/
public class Day32Demo4 {
public static void main(String[] args) {
List<String> words = List.of("lazy", "snail", "lazysnailstudio.com");
List<String> upperCaseWords = words.stream()
.map(String::toUpperCase)
.toList();
System.out.println("大写单词: " + upperCaseWords);
List<Integer> wordLengths = words.stream()
.map(String::length)
.toList();
System.out.println("单词长度: " + wordLengths);
}
}
flatMap(Function<T, Stream<R>> f): 将每个元素转换为一个流,然后将所有流连接成一个流(扁平化)。
把一个包含多个列表的列表,压平成一个单一的列表。
java
package com.lazy.snail.day32;
import java.util.List;
/**
* @ClassName Day32Demo5
* @Description TODO
* @Author lazysnail
* @Date 2025/7/14 14:22
* @Version 1.0
*/
public class Day32Demo5 {
public static void main(String[] args) {
List<List<Integer>> listOfLists = List.of(
List.of(1, 2, 3),
List.of(4, 5)
);
List<Integer> flatList = listOfLists.stream()
.flatMap(list -> list.stream())
.toList();
System.out.println("扁平化后的列表: " + flatList);
}
}
listOfLists是两个列表的列表,每个list元素被转换成一个新的stream,flatMap把这些stream连接成一个。
mapMulti(BiConsumer<T, Consumer<R>> mapper): (JDK 16+) flatMap的替代方案,在某些场景下更高效。它将每个元素替换为零个或多个元素。
某些场景下,要使用到mapMulti,要稍微比flatMap更灵活一点。
比如,把每个单词同时转换成大写和小写,然后放到同一个输出流里。
java
package com.lazy.snail.day32;
import java.util.List;
/**
* @ClassName Day32Demo6
* @Description TODO
* @Author lazysnail
* @Date 2025/7/14 14:27
* @Version 1.0
*/
public class Day32Demo6 {
public static void main(String[] args) {
List<String> words = List.of("lazy", "snail", "lazysnail");
List<String> result = words.stream()
.<String>mapMulti((word, consumer) -> {
consumer.accept(word.toUpperCase());
consumer.accept(word.toLowerCase());
})
.toList();
System.out.println("结果: " + result);
}
}
对每个单词,生成一个大写和一个小写版本。
排序
以这个作为数据源:
java
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio", "lazysnailstudio.com");
sorted(): 按自然顺序排序。
java
List<String> sortedNames = names.stream()
.sorted()
.toList();
System.out.println("按自然顺序排序: " + sortedNames);
sorted(Comparator<T> c): 按自定义比较器排序。
java
List<String> sortedByLength = names.stream()
.sorted(Comparator.comparingInt(String::length))
.toList();
System.out.println("按长度排序: " + sortedByLength);
List<String> sortedByLengthDesc = names.stream()
.sorted(Comparator.comparingInt(String::length).reversed())
.toList();
System.out.println("按长度降序排序: " + sortedByLengthDesc);
2.3终端操作
终端操作是流水线的打包站,它会触发所有惰性操作的执行,并关闭流。
常用的终端操作:
循环:
forEach(Consumer<T> c)
java
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
names.stream().forEach(System.out::println);
匹配:
anyMatch(Predicate<T> p): 是否有至少一个元素匹配。
java
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
System.out.println(names.stream().anyMatch("lazysnail"::equals));
allMatch(Predicate<T> p): 是否所有元素都匹配。
java
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
System.out.println(names.stream().allMatch("lazysnail"::equals));
noneMatch(Predicate<T> p): 是否没有元素匹配。
java
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
System.out.println(names.stream().noneMatch("lazysnail"::equals));
查找:
findFirst(): 返回第一个元素(Optional<T>)。
java
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
Optional<String> firstMatch = names.stream()
.filter(name -> name.contains("snail"))
.findFirst();
firstMatch.ifPresentOrElse(
foundName -> System.out.println("找到了第一个匹配项: " + foundName),
() -> System.out.println("没有找到任何匹配项。")
);
通过findFirst方法查找第一个包含snail的字符串。
用Optional的ifPresentOrElse来处理结果,如果找到了,就执行第一个Lambda;如果没找到,就执行第二个。
findAny(): 返回任意一个元素(Optional<T>),在并行流中更高效。
java
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
Optional<String> anyMatch = names.stream()
.filter(name -> name.contains("snail"))
.findAny();
String result = anyMatch.orElse("默认值:没找到");
System.out.println("查找结果: " + result);
查找任意一个包含 snail的字符串,使用Optional的orElse来处理结果。如果找到了,就返回找到的值;如果没找到,就返回一个默认值。
归约:
count(): 返回流中元素个数。
java
long totalCount = names.stream().count();
System.out.println("列表中的元素总数是: " + totalCount);
reduce(T identity, BinaryOperator<T> accumulator): 将流中元素反复结合起来,得到一个值。
java
Optional<String> combinedString = names.stream()
.reduce((accumulator, element) -> accumulator + ", " + element);
combinedString.ifPresent(
result -> System.out.println("拼接后的字符串: " + result)
);
用reduce把所有字符串拼接起来,reduce的这个重载版本不接受初始值,因此返回一个Optional,防止流为空。
collect(Collector c): 将流转换为其他形式(如List, Set, Map)。这是最重要、最强大的终端操作。
java
Map<String, Integer> nameLengthMap = names.stream()
.collect(Collectors.toMap(
name -> name,
String::length
));
System.out.println("转换后的Map: " + nameLengthMap);
把流转换成Map<String, Integer>。
三、Collectors
Collectors是一个工具类,预制了很多收集器,能满足绝大部分数据处理需求。
3.1收集到集合
java
public static void main(String[] args) {
List<String> names = List.of("lazysnail", "懒惰蜗牛", "lazysnailstudio.com");
List<String> nameList = names.stream().toList();
Set<String> nameSet = names.stream().collect(Collectors.toSet());
LinkedList<String> nameLinkedList = names.stream()
.collect(Collectors.toCollection(LinkedList::new));
}
3.2聚合计算
java
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(300, 420, 180, 600, 210);
int total = intList.stream()
.collect(Collectors.summingInt(Integer::intValue));
double avg = intList.stream()
.collect(Collectors.averagingInt(Integer::intValue));
IntSummaryStatistics statistics = intList.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("和: " + total);
System.out.println("平均值: " + avg);
System.out.println("完整统计:");
System.out.println("记录数: " + statistics.getCount());
System.out.println("最大: " + statistics.getMax());
System.out.println("最小: " + statistics.getMin());
System.out.println("总和: " + statistics.getSum());
System.out.println("平均值: " + statistics.getAverage());
}
3.3分组与分区
groupingBy(Function classifier): 分组,类似于SQL的GROUP BY。
partitioningBy(Predicate predicate): 分区,是groupingBy的一种特殊情况,键是布尔值。
java
package com.lazy.snail.day32;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @ClassName Day32Demo8
* @Description TODO
* @Author lazysnail
* @Date 2025/7/14 15:47
* @Version 1.0
*/
public class Day32Demo8 {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("懒惰蜗牛", "数学", 85),
new Student("二蛋", "数学", 72),
new Student("小二狗", "物理", 92),
new Student("二狗子", "物理", 68),
new Student("狗蛋", "数学", 93),
new Student("小阿三", "化学", 78)
);
Map<String, List<Student>> bySubject = students.stream()
.collect(Collectors.groupingBy(Student::getSubject));
bySubject.forEach((subject, list) -> {
System.out.println(subject + ": " +
list.stream().map(Student::getName).collect(Collectors.joining(", ")));
});
Map<String, List<Student>> byGrade = students.stream()
.collect(Collectors.groupingBy(student -> {
int score = student.getScore();
if (score >= 90) return "A";
else if (score >= 80) return "B";
else if (score >= 70) return "C";
else return "D";
}));
byGrade.forEach((grade, list) -> {
System.out.println(grade + ": " +
list.stream().map(Student::getName).collect(Collectors.joining(", ")));
});
Map<Boolean, List<Student>> passOrFail = students.stream()
.collect(Collectors.partitioningBy(student -> student.getScore() >= 60));
System.out.println("及格学生: " +
passOrFail.get(true).stream().map(Student::getName).collect(Collectors.joining(", ")));
System.out.println("不及格学生: " +
passOrFail.get(false).stream().map(Student::getName).collect(Collectors.joining(", ")));
}
}
class Student {
private String name;
private String subject;
private int score;
public Student(String name, String subject, int score) {
this.name = name;
this.subject = subject;
this.score = score;
}
public String getName() { return name; }
public String getSubject() { return subject; }
public int getScore() { return score; }
}
用groupingBy分别进行了按照学科和成绩等级(自定义)进行了分组。
用partitioningBy按照是否及格(60分)进行了分区。
案例中使用到的Collectors.joining方法就是把流中的元素拼接成一个字符串。
结语
Stream API提供了丰富的流操作,非常适合处理集合数据,让处理过程更加的简洁方便。
虽然看着内容比较多,但是多实操一下,上手非常快。
之前集合中的案例都可以尝试用流操作重构下,感受一下流操作。
下一篇预告
Day33 | Java中的Optional
如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!
更多文章请关注我的公众号《懒惰蜗牛工坊》