Lambda 表达式与 Stream API
一、引言
Java 8 引入了 Lambda 表达式和 Stream API,这是 Java 语言历史上最具革命性的更新之一。Lambda 表达式让 Java 具备了函数式编程的能力,而 Stream API 则提供了一种声明式的方式来处理集合数据。本文将深入讲解这两个核心特性,并通过大量代码示例帮助你快速掌握。
二、Lambda 表达式
2.1 什么是 Lambda 表达式
Lambda 表达式是一种匿名函数( Anonymous function ),它可以作为参数传递给方法,或者作为函数式接口的实例。Lambda 表达式让代码更加简洁、易读。
Lambda 表达式的语法格式:
(参数列表) -> { 方法体 }- 左侧:参数列表(可以省略参数类型)
- 右侧:Lambda 体(表达式或代码块)
2.2 Lambda 的基本语法
语法形式一:无参数,无返回值
// 传统写法 - 匿名内部类
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello from anonymous class");
}
};
// Lambda 写法
Runnable r2 = () -> System.out.println("Hello from lambda");
// 调用
r1.run();
r2.run();语法形式二:一个参数,无返回值
// 传统写法
Consumer<String> c1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
// Lambda 写法(参数括号可以省略)
Consumer<String> c2 = s -> System.out.println(s);
c2.accept("Hello Lambda");语法形式三:多个参数,有返回值
// 传统写法
Comparator<Integer> comp1 = new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return Integer.compare(a, b);
}
};
// Lambda 写法
Comparator<Integer> comp2 = (a, b) -> Integer.compare(a, b);
// 带花括号的完整写法
Comparator<Integer> comp3 = (a, b) -> {
System.out.println("比较: " + a + " 和 " + b);
return Integer.compare(a, b);
};
System.out.println(comp2.compare(3, 5)); // -1多行语句体
当 Lambda 体包含多条语句时,必须使用花括号 {} 包裹,并且如果有返回值,需要显式使用 return:
// 多行 Lambda 体
BinaryOperator<Integer> calculator = (x, y) -> {
int result = x * y + x - y;
System.out.println("计算结果: " + result);
return result;
};
System.out.println(calculator.apply(10, 5)); // 552.3 变量捕获
Lambda 表达式可以访问外部的局部变量,这些变量必须是 final 或 effectively final(即初始化后不再改变):
String prefix = "值: "; // effectively final
int factor = 10; // effectively final
Consumer<Integer> printer = value -> {
// prefix = "新值"; // 编译错误!不能修改捕获的变量
System.out.println(prefix + value * factor);
};
printer.accept(5); // 值: 50注意:Lambda 内部不能修改被捕获的局部变量,这是为了避免线程安全问题。
三、函数式接口
3.1 @FunctionalInterface 注解
函数式接口( Functional Interface )是指有且仅有一个抽象方法的接口。@FunctionalInterface 是可选注解,用于编译器校验:
@FunctionalInterface
public interface MyFunction {
void execute(String msg);
// 默认方法不算抽象方法
default void log(String msg) {
System.out.println("日志: " + msg);
}
// 静态方法也不算
static void info() {
System.out.println("静态方法");
}
}
// 使用 Lambda 实现
MyFunction f = msg -> System.out.println("执行: " + msg);
f.execute("任务");
f.log("完成");如果接口中定义了多个抽象方法,
@FunctionalInterface会触发编译错误。
3.2 四大核心函数式接口
Java 8 在 java.util.function 包中提供了大量内置函数式接口,最核心的四个如下:
(1)Function<T, R> — 转换型接口
接受一个参数,返回一个结果,用于类型转换或映射操作。
| 接口 | 抽象方法 | 用途 |
|---|---|---|
Function<T, R> | R apply(T t) | 将 T 转换为 R |
// 字符串转整数
Function<String, Integer> strToInt = s -> Integer.parseInt(s);
System.out.println(strToInt.apply("123") + 1); // 124
// 链式组合:andThen 先执行当前,再执行参数
Function<String, String> trimAndUpper = s -> s.trim();
trimAndUpper = trimAndUpper.andThen(s -> s.toUpperCase());
System.out.println(trimAndUpper.apply(" hello ")); // "HELLO"
// 链式组合:compose 先执行参数,再执行当前
Function<Integer, Integer> square = x -> x * x;
Function<Integer, Integer> addOne = x -> x + 1;
// 先加1,再平方:(3+1)^2 = 16
Function<Integer, Integer> composed = square.compose(addOne);
System.out.println(composed.apply(3)); // 16(2)Consumer<T> — 消费型接口
接受一个参数,没有返回值,用于消费数据(如打印、发送等)。
| 接口 | 抽象方法 | 用途 |
|---|---|---|
Consumer<T> | void accept(T t) | 消费 T 类型数据 |
// 打印列表元素
Consumer<String> print = s -> System.out.print(s + " ");
List<String> list = Arrays.asList("A", "B", "C");
list.forEach(print); // A B C
// andThen 链式消费
Consumer<String> saveToDb = s -> System.out.println("保存到数据库: " + s);
Consumer<String> sendEmail = s -> System.out.println("发送邮件: " + s);
Consumer<String> pipeline = saveToDb.andThen(sendEmail);
pipeline.accept("订单 #1001");
// 保存到数据库: 订单 #1001
// 发送邮件: 订单 #1001(3)Predicate<T> — 断言型接口
接受一个参数,返回 boolean,用于条件判断和过滤。
| 接口 | 抽象方法 | 用途 |
|---|---|---|
Predicate<T> | boolean test(T t) | 判断 T 是否满足条件 |
// 判断字符串是否为空
Predicate<String> isEmpty = s -> s == null || s.trim().isEmpty();
System.out.println(isEmpty.test("")); // true
System.out.println(isEmpty.test("Hi")); // false
// 谓词组合:and / or / negate
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven = n -> n % 2 == 0;
// 正偶数
Predicate<Integer> positiveEven = isPositive.and(isEven);
System.out.println(positiveEven.test(4)); // true
System.out.println(positiveEven.test(-4)); // false
System.out.println(positiveEven.test(3)); // false
// 负数或奇数
Predicate<Integer> negativeOrOdd = isPositive.negate().or(isEven.negate());
System.out.println(negativeOrOdd.test(-3)); // true(4)Supplier<T> — 供给型接口
不接受参数,返回一个结果,用于延迟计算或数据生成。
| 接口 | 抽象方法 | 用途 |
|---|---|---|
Supplier<T> | T get() | 提供一个 T 类型数据 |
// 生成当前时间
Supplier<String> timeSupplier = () -> LocalDateTime.now().toString();
System.out.println("当前时间: " + timeSupplier.get());
// 延迟加载
public class LazyLoader<T> {
private Supplier<T> supplier;
private T cached;
public LazyLoader(Supplier<T> supplier) {
this.supplier = supplier;
}
public T get() {
if (cached == null) {
cached = supplier.get();
}
return cached;
}
}
// 只有第一次调用时才执行耗时操作
LazyLoader<String> config = new LazyLoader<>(() -> {
System.out.println("加载配置...");
return "db_url=localhost";
});
System.out.println(config.get()); // 加载配置... db_url=localhost
System.out.println(config.get()); // (直接从缓存读取,不打印加载)3.3 其他常用函数式接口
| 接口 | 抽象方法 | 用途 |
|---|---|---|
BiFunction<T, U, R> | R apply(T t, U u) | 接受两个参数,返回一个结果 |
BiConsumer<T, U> | void accept(T t, U u) | 接受两个参数,无返回值 |
BiPredicate<T, U> | boolean test(T t, U u) | 接受两个参数,返回 boolean |
UnaryOperator<T> | T apply(T t) | 参数和返回值类型相同(继承自 Function) |
BinaryOperator<T> | T apply(T t1, T t2) | 两个参数和返回值类型相同(继承自 BiFunction) |
// BiFunction 示例
BiFunction<String, String, String> mergeName = (first, last) -> first + "·" + last;
System.out.println(mergeName.apply("张三", "三")); // 张三·三
// BinaryOperator 示例
BinaryOperator<Integer> max = BinaryOperator.maxBy(Integer::compareTo);
System.out.println(max.apply(10, 20)); // 20
// UnaryOperator 示例
UnaryOperator<String> repeat = s -> s + s;
System.out.println(repeat.apply("哈")); // 哈哈四、方法引用
方法引用是 Lambda 表达式的语法糖,当 Lambda 体仅仅是调用某个已有的方法时,可以用方法引用进一步简化代码。
四种方法引用形式:
| 类型 | 语法 | 示例 |
|---|---|---|
| 静态方法引用 | 类名::静态方法名 | Integer::parseInt |
| 对象方法引用 | 对象::实例方法名 | System.out::println |
| 类方法引用 | 类名::实例方法名 | String::length |
| 构造方法引用 | 类名::new | ArrayList::new |
4.1 静态方法引用
// Lambda 写法
Function<String, Integer> lambda = s -> Integer.parseInt(s);
// 方法引用
Function<String, Integer> ref = Integer::parseInt;
System.out.println(ref.apply("456") + 1); // 457
// 另一个示例:Comparator
Comparator<Integer> comp = Integer::compareTo;4.2 对象方法引用
List<String> cities = Arrays.asList("北京", "上海", "广州", "深圳");
// Lambda 写法
cities.forEach(s -> System.out.println(s));
// 方法引用
cities.forEach(System.out::println);4.3 类方法引用(实例方法引用)
当 Lambda 的参数列表中,第一个参数作为方法的调用者,其余参数作为方法的参数时,可以使用这种形式:
// Lambda 写法
BiPredicate<String, String> lambda = (s1, s2) -> s1.equals(s2);
// 方法引用:第一个参数 s1 调用 equals,第二个参数 s2 作为参数传入
BiPredicate<String, String> ref = String::equals;
System.out.println(ref.test("hello", "hello")); // true
// 另一个示例:排序
List<String> names = Arrays.asList("Banana", "Apple", "Cherry");
names.sort(String::compareToIgnoreCase);
System.out.println(names); // [Apple, Banana, Cherry]4.4 构造方法引用
// Lambda 写法
Supplier<List<String>> lambda1 = () -> new ArrayList<>();
Function<Integer, String[]> lambda2 = len -> new String[len];
// 构造方法引用
Supplier<List<String>> ref1 = ArrayList::new;
Function<Integer, String[]> ref2 = String[]::new;
List<String> list = ref1.get();
String[] array = ref2.apply(5);
System.out.println(array.length); // 5
// 带参数构造方法引用
Function<String, StringBuilder> sbBuilder = StringBuilder::new;
StringBuilder sb = sbBuilder.apply("Hello");
sb.append(" World");
System.out.println(sb); // Hello World4.5 综合对比
public class MethodReferenceDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
// 匿名内部类
numbers.sort(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return Integer.compare(a, b);
}
});
// Lambda
numbers.sort((a, b) -> Integer.compare(a, b));
// 方法引用
numbers.sort(Integer::compareTo);
System.out.println(numbers); // [1, 1, 3, 4, 5, 9]
}
}五、Stream API
Stream 是 Java 8 引入的一套新的数据抽象,它代表一个支持顺序和并行聚合操作的元素序列。Stream 不是数据结构,它不存储数据,而是从数据源(集合、数组等)获取数据并进行管道化操作。
Stream 操作三步骤:
- 创建 Stream — 从数据源获取一个流
- 中间操作 — 对流进行一系列转换(零个或多个)
- 终止操作 — 产生结果,关闭流
关键特性:中间操作是惰性的(lazy),只有在执行终止操作时才会触发实际计算。
5.1 创建 Stream
从集合创建
// Collection 接口提供了 stream() 和 parallelStream() 方法
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));
Stream<Integer> setStream = set.stream();
// Map 需要先获取 keySet、values 或 entrySet
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();从数组创建
// 使用 Arrays.stream()
String[] arr = {"Java", "Python", "Go"};
Stream<String> arrStream = Arrays.stream(arr);
// 可以指定范围
Stream<String> partialStream = Arrays.stream(arr, 0, 2); // Java, Python
partialStream.forEach(System.out::println);使用 Stream.of()
// 直接传入元素
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Stream<String> words = Stream.of("Lambda", "Stream", "Optional");
// 可变参数
String[] array = {"a", "b", "c"};
Stream<String> fromArray = Stream.of(array);使用 Stream.iterate() — 迭代生成
// 无限流,需要配合 limit 使用
// 生成: 0, 2, 4, 6, 8, ...
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2);
evenNumbers.limit(5).forEach(n -> System.out.print(n + " ")); // 0 2 4 6 8
System.out.println();
// Java 9+ 带条件的 iterate
Stream.iterate(1, n -> n <= 10, n -> n + 1)
.forEach(n -> System.out.print(n + " ")); // 1 2 3 4 5 6 7 8 9 10使用 Stream.generate() — 生成器
// 生成 5 个随机数
Stream<Double> randomStream = Stream.generate(Math::random);
randomStream.limit(5).forEach(n -> System.out.printf("%.2f ", n));
System.out.println();
// 生成常量流
Stream<String> constantStream = Stream.generate(() -> "Hello");
constantStream.limit(3).forEach(s -> System.out.print(s + " ")); // Hello Hello Hello其他创建方式
// 从文件行读取
try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
// 字符串分割
Stream<String> chars = "Hello".chars()
.mapToObj(c -> (char) c + "");
chars.forEach(System.out::print); // Hello5.2 中间操作
中间操作返回一个新的 Stream,多个中间操作可以组成一个操作流水线( pipeline )。
filter — 过滤
根据 Predicate 条件筛选元素:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evens); // [2, 4, 6, 8, 10]
// 过滤并统计
List<String> words = Arrays.asList("", "Hello", " ", "World", "");
long count = words.stream()
.filter(s -> !s.trim().isEmpty())
.count();
System.out.println(count); // 2map — 映射
将元素转换成另一种形式,一对一转换:
List<String> words = Arrays.asList("java", "stream", "lambda");
// 转大写
List<String> upper = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upper); // [JAVA, STREAM, LAMBDA]
// 获取字符串长度
List<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths); // [4, 6, 6]
// 对象属性提取
List<User> users = Arrays.asList(
new User("张三", 25),
new User("李四", 30),
new User("王五", 28)
);
List<String> names = users.stream()
.map(User::getName)
.collect(Collectors.toList());
System.out.println(names); // [张三, 李四, 王五]flatMap — 扁平化映射
将每个元素映射为一个 Stream,然后将所有 Stream 合并为一个:
// 案例一:拆分句子为单词
List<String> sentences = Arrays.asList("Hello World", "Java Stream", "Flat Map");
List<String> words = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.collect(Collectors.toList());
System.out.println(words); // [Hello, World, Java, Stream, Flat, Map]
// 案例二:合并多个集合
List<List<Integer>> nestedList = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4, 5),
Arrays.asList(6)
);
List<Integer> flatList = nestedList.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(flatList); // [1, 2, 3, 4, 5, 6]map vs flatMap 对比:
// map:结果嵌套
List<Stream<String>> mapResult = sentences.stream()
.map(s -> Arrays.stream(s.split(" ")))
.collect(Collectors.toList()); // List<Stream<String>>
// flatMap:结果扁平化
List<String> flatResult = sentences.stream()
.flatMap(s -> Arrays.stream(s.split(" ")))
.collect(Collectors.toList()); // List<String>peek — 查看中间结果
主要用于调试,可以查看流中间操作的结果,不改变流中的数据:
List<String> result = Stream.of("one", "two", "three", "four")
.filter(s -> s.length() > 3)
.peek(s -> System.out.println("过滤后: " + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("转换后: " + s))
.collect(Collectors.toList());
System.out.println("最终结果: " + result);
// 输出:
// 过滤后: three
// 转换后: THREE
// 过滤后: four
// 转换后: FOUR
// 最终结果: [THREE, FOUR]distinct — 去重
List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 3, 5, 1);
List<Integer> distinct = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinct); // [1, 2, 3, 4, 5]
// 复杂对象的去重要重写 equals 和 hashCodesorted — 排序
List<Integer> numbers = Arrays.asList(5, 3, 1, 4, 2);
// 自然排序
List<Integer> asc = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(asc); // [1, 2, 3, 4, 5]
// 自定义排序(降序)
List<Integer> desc = numbers.stream()
.sorted((a, b) -> b - a)
.collect(Collectors.toList());
System.out.println(desc); // [5, 4, 3, 2, 1]
// 对象排序
List<User> sortedUsers = users.stream()
.sorted(Comparator.comparingInt(User::getAge))
.collect(Collectors.toList());
// 先按姓名排,再按年龄排
List<User> sorted = users.stream()
.sorted(Comparator.comparing(User::getName)
.thenComparingInt(User::getAge))
.collect(Collectors.toList());skip 和 limit — 截取
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// limit: 取前 N 个
List<Integer> first3 = numbers.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(first3); // [1, 2, 3]
// skip: 跳过前 N 个
List<Integer> after4 = numbers.stream()
.skip(4)
.collect(Collectors.toList());
System.out.println(after4); // [5, 6, 7, 8, 9, 10]
// 组合使用:实现分页
int page = 2;
int size = 3;
List<Integer> pageResult = numbers.stream()
.skip((page - 1) * size) // 跳过第1页的3个
.limit(size)
.collect(Collectors.toList());
System.out.println("第2页: " + pageResult); // 第2页: [4, 5, 6]5.3 终止操作
终止操作是 Stream 管道的最后一个操作,执行后会关闭 Stream。
collect — 收集
collect 是最常用的终止操作,它将流中的元素收集到各种容器中。
收集到 List / Set:
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
// 收集到特定类型的集合
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
TreeSet<String> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));收集到 Map:
// 将用户列表转成 Map<姓名, 用户>
Map<String, User> userMap = users.stream()
.collect(Collectors.toMap(
User::getName, // key 映射
user -> user, // value 映射
(oldVal, newVal) -> oldVal // 如果有重复 key 的处理策略
));
System.out.println(userMap.get("张三")); // User{name='张三', age=25}groupingBy — 分组:
public class Student {
private String name;
private String grade; // 年级
private int score;
// getter/setter/constructor 略
}
List<Student> students = Arrays.asList(
new Student("小明", "A", 85),
new Student("小红", "A", 92),
new Student("小刚", "B", 78),
new Student("小丽", "B", 88),
new Student("小华", "C", 95)
);
// 按年级分组
Map<String, List<Student>> byGrade = students.stream()
.collect(Collectors.groupingBy(Student::getGrade));
System.out.println(byGrade);
// {A=[小明(85), 小红(92)], B=[小刚(78), 小丽(88)], C=[小华(95)]}
// 多级分组
Map<String, Map<String, List<Student>>> multiGroup = students.stream()
.collect(Collectors.groupingBy(
Student::getGrade,
Collectors.groupingBy(s -> s.getScore() >= 90 ? "优秀" : "普通")
));
// 分组后统计每组的平均分
Map<String, Double> avgScore = students.stream()
.collect(Collectors.groupingBy(
Student::getGrade,
Collectors.averagingInt(Student::getScore)
));
// {A=88.5, B=83.0, C=95.0}partitioningBy — 分区(按 boolean 条件分为两组):
// 按是否及格分区(>=60 及格)
Map<Boolean, List<Student>> passFail = students.stream()
.collect(Collectors.partitioningBy(s -> s.getScore() >= 60));
System.out.println("及格人数: " + passFail.get(true).size());
System.out.println("不及格人数: " + passFail.get(false).size());
// 分区后统计
Map<Boolean, Long> countByPass = students.stream()
.collect(Collectors.partitioningBy(
s -> s.getScore() >= 60,
Collectors.counting()
));joining — 连接字符串:
List<String> words = Arrays.asList("Lambda", "Stream", "API");
// 直接连接
String joined = words.stream().collect(Collectors.joining());
System.out.println(joined); // LambdaStreamAPI
// 带分隔符
String withDelimiter = words.stream()
.collect(Collectors.joining(", "));
System.out.println(withDelimiter); // Lambda, Stream, API
// 带前后缀
String withPrefixSuffix = words.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(withPrefixSuffix); // [Lambda, Stream, API]reduce — 归约
将流中的元素反复组合起来,得到一个值:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和:初始值为 0,反复相加
Optional<Integer> sum1 = numbers.stream()
.reduce((a, b) -> a + b);
System.out.println(sum1.get()); // 15
// 带初始值的 reduce
Integer sum2 = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println(sum2); // 15
// 求最大值
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
System.out.println(max.get()); // 5
// 字符串拼接
List<String> letters = Arrays.asList("a", "b", "c", "d");
String concat = letters.stream()
.reduce("", (a, b) -> a + "|" + b);
System.out.println(concat); // |a|b|c|dcount — 计数
long count = Stream.of("a", "b", "c").count();
System.out.println(count); // 3
long longCount = Stream.generate(Math::random)
.limit(1000)
.count();
System.out.println(longCount); // 1000forEach — 遍历
Stream.of("A", "B", "C")
.forEach(System.out::println);
// 带索引的遍历(使用 IntStream)
String[] colors = {"Red", "Green", "Blue"};
IntStream.range(0, colors.length)
.forEach(i -> System.out.println(i + ": " + colors[i]));
// 0: Red
// 1: Green
// 2: Blue注意:
forEach不能保证并行流中的顺序。如果需要保证顺序,使用forEachOrdered。
anyMatch / allMatch / noneMatch — 匹配检查
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 9, 10);
// anyMatch:是否存在任意一个满足条件
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
System.out.println("是否存在偶数: " + hasEven); // true
// allMatch:是否所有元素都满足条件
boolean allPositive = numbers.stream().allMatch(n -> n > 0);
System.out.println("是否全部正数: " + allPositive); // true
// noneMatch:是否没有元素满足条件
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);
System.out.println("是否没有负数: " + noneNegative); // truefindFirst / findAny — 查找元素
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// findFirst:返回第一个元素
Optional<Integer> first = numbers.stream()
.filter(n -> n > 3)
.findFirst();
System.out.println(first.get()); // 4
// findAny:返回任意一个元素(在并行流中更高效)
Optional<Integer> any = numbers.parallelStream()
.filter(n -> n > 3)
.findAny();
System.out.println(any.get()); // 可能是 4 或 5
// 空流时安全处理
Optional<Integer> empty = Stream.<Integer>empty()
.findFirst();
System.out.println(empty.isPresent()); // false
System.out.println(empty.orElse(-1)); // -15.4 数值流
对于数值操作,IntStream、LongStream、DoubleStream 提供了更高效的专用方法:
// 创建数值流
IntStream intStream = IntStream.range(1, 10); // [1, 9]
IntStream intStreamClosed = IntStream.rangeClosed(1, 10); // [1, 10]
// 数值计算
int sum = IntStream.rangeClosed(1, 100).sum();
OptionalDouble avg = IntStream.rangeClosed(1, 100).average();
OptionalInt max = IntStream.of(3, 5, 1, 9, 7).max();
OptionalInt min = IntStream.of(3, 5, 1, 9, 7).min();
System.out.println("1-100 求和: " + sum); // 5050
// 对象流 ↔ 数值流转换
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
int listSum = list.stream()
.mapToInt(Integer::intValue)
.sum();
// 统计信息
IntSummaryStatistics stats = list.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("总数: " + stats.getCount());
System.out.println("平均: " + stats.getAverage());
System.out.println("最大: " + stats.getMax());
System.out.println("最小: " + stats.getMin());5.5 综合案例
将前面的知识点整合为一个完整的业务场景:
public class StreamDemo {
static class Order {
private String id;
private String category;
private double amount;
private String status;
// constructor, getters
public Order(String id, String category, double amount, String status) {
this.id = id;
this.category = category;
this.amount = amount;
this.status = status;
}
public String getId() { return id; }
public String getCategory() { return category; }
public double getAmount() { return amount; }
public String getStatus() { return status; }
@Override
public String toString() {
return String.format("Order{id='%s', cat='%s', amount=%.1f, status='%s'}",
id, category, amount, status);
}
}
public static void main(String[] args) {
List<Order> orders = Arrays.asList(
new Order("001", "电子产品", 5999.0, "已完成"),
new Order("002", "食品", 89.9, "已完成"),
new Order("003", "电子产品", 1299.0, "处理中"),
new Order("004", "服装", 299.0, "已完成"),
new Order("005", "食品", 15.5, "已取消"),
new Order("006", "电子产品", 7999.0, "已完成"),
new Order("007", "服装", 199.0, "处理中")
);
// 需求:统计"电子产品"类别中已完成订单的总金额
double electronicsCompleted = orders.stream()
.filter(o -> "电子产品".equals(o.getCategory()))
.filter(o -> "已完成".equals(o.getStatus()))
.mapToDouble(Order::getAmount)
.sum();
System.out.printf("电子产品已完成订单总金额: %.2f%n", electronicsCompleted);
// 需求:按类别分组,统计每个类别的订单数量和平均金额
Map<String, Map<String, Object>> categoryStats = orders.stream()
.collect(Collectors.groupingBy(
Order::getCategory,
Collectors.collectingAndThen(
Collectors.toList(),
list -> {
Map<String, Object> stats = new HashMap<>();
stats.put("count", list.size());
stats.put("total", list.stream().mapToDouble(Order::getAmount).sum());
stats.put("avg", list.stream().mapToDouble(Order::getAmount).average().orElse(0));
stats.put("max", list.stream().mapToDouble(Order::getAmount).max().orElse(0));
return stats;
}
)
));
categoryStats.forEach((cat, stats) -> {
System.out.printf("类别: %s, 统计: %s%n", cat, stats);
});
// 需求:获取金额最高的前3个已完成订单
List<Order> top3Completed = orders.stream()
.filter(o -> "已完成".equals(o.getStatus()))
.sorted(Comparator.comparingDouble(Order::getAmount).reversed())
.limit(3)
.collect(Collectors.toList());
System.out.println("已完成订单金额Top3: " + top3Completed);
}
}六、并行流
6.1 什么是并行流
并行流将数据切分成多个段,每个段由不同的线程处理,最后合并结果。底层使用 ForkJoinPool 线程池。
6.2 创建并行流
// 方式一:从集合创建
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> parallelStream = list.parallelStream();
// 方式二:将串行流转为并行
Stream<Integer> parallel = list.stream().parallel();
// 方式三:从并行流转串行(不常用)
Stream<Integer> sequential = parallelStream.sequential();6.3 性能对比示例
// 一个较大的数据集
List<Integer> data = new ArrayList<>();
for (int i = 0; i < 10_000_000; i++) {
data.add(i);
}
// 串行流
long start = System.currentTimeMillis();
long serialSum = data.stream()
.mapToLong(Integer::longValue)
.sum();
long serialTime = System.currentTimeMillis() - start;
System.out.println("串行流: " + serialSum + ", 耗时: " + serialTime + "ms");
// 并行流
start = System.currentTimeMillis();
long parallelSum = data.parallelStream()
.mapToLong(Integer::longValue)
.sum();
long parallelTime = System.currentTimeMillis() - start;
System.out.println("并行流: " + parallelSum + ", 耗时: " + parallelTime + "ms");
System.out.println("加速比: " + (double) serialTime / parallelTime);6.4 ForkJoinPool 原理
并行流底层使用 ForkJoinPool(默认线程数为 CPU 核心数):
// 查看默认线程数
System.out.println("CPU 核心数: " + Runtime.getRuntime().availableProcessors());
// ForkJoinPool 默认线程数 = CPU 核心数 - 1(主线程也算一个)
// 指定并行度(全局设置)
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "8");
// 或者通过自定义 ForkJoinPool
ForkJoinPool customPool = new ForkJoinPool(4);
try {
customPool.submit(() -> {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
int sum = list.parallelStream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("自定义线程池结果: " + sum);
}).get();
} catch (Exception e) {
e.printStackTrace();
} finally {
customPool.shutdown();
}Fork/Join 工作窃取(Work-Stealing)原理:
- 将大任务拆分成小任务(Fork)
- 每个线程处理自己的双端队列中的任务
- 空闲线程会从其他线程的队列尾部"窃取"任务
- 所有子任务完成后合并结果(Join)
6.5 并行流使用注意事项
适用场景(适合并行化):
- 数据量大(通常 > 10,000 元素)
- 元素处理耗时较长
- 无状态、无依赖的操作
- 操作可分解、结果可合并
不适用场景(不适合并行化):
- 数据量小(并行化开销 > 收益)
- 有共享可变状态
- 操作顺序敏感(如
findFirst) - I/O 密集型操作
// 错误示例:共享可变状态
List<Integer> unsafeResult = new ArrayList<>(); // 线程不安全!
IntStream.range(0, 100).parallel()
.forEach(unsafeResult::add); // 可能丢失元素或抛异常
System.out.println(unsafeResult.size()); // 可能 < 100
// 正确做法:使用线程安全的收集器
List<Integer> safeResult = IntStream.range(0, 100)
.parallel()
.boxed()
.collect(Collectors.toList());
System.out.println(safeResult.size()); // 100
// 注意:limit 在并行流中性能较差
// 注意:forEach 不保证顺序,使用 forEachOrdered 但会降低性能七、小结
本文全面介绍了 Java Lambda 表达式和 Stream API 的核心知识点,总结如下:
| 知识点 | 要点 |
|---|---|
| Lambda 语法 | (参数) -> 表达式 或 (参数) -> { 语句; },支持变量捕获 |
| 函数式接口 | @FunctionalInterface 确保只有一个抽象方法,四大核心接口:Function(转换)、Consumer(消费)、Predicate(判断)、Supplier(供给) |
| 方法引用 | 四种形式:类::静态方法、对象::实例方法、类::实例方法、类::new |
| Stream 创建 | 从集合、数组、Stream.of、iterate、generate 创建流 |
| 中间操作 | filter(过滤)、map/flatMap(映射)、peek(调试)、distinct(去重)、sorted(排序)、skip/limit(截取) |
| 终止操作 | collect(收集,含 groupingBy/partitioningBy/joining)、reduce(归约)、count(计数)、forEach(遍历)、anyMatch/allMatch/noneMatch(匹配)、findFirst/findAny(查找) |
| 并行流 | parallelStream() / parallel() 方法,基于 ForkJoinPool 工作窃取机制 |
实践建议:
- 优先使用方法引用替代 Lambda 表达式,代码更简洁
- 警惕并行流的滥用,小数据量或存在竞争条件时使用串行流
- 充分利用
Optional处理空值,避免NullPointerException - 中间操作是惰性的,务必以终止操作结尾
- 理解并善用
Collectors工具类,减少手动收集代码
掌握 Lambda 和 Stream API 是写好现代 Java 代码的基础,希望本文能帮助你快速上手并在实际项目中灵活运用。