📚 本系列系统梳理了 Java 开发的详细知识点,从基础语法到工程实践层层递进,内容详实成体系,建议先收藏再慢慢阅读,方便日后随时回顾查阅。

前言

Java 8 是 Java 历史上变化最大的一个版本,Lambda 表达式和 Stream API 彻底改变了 Java 的代码风格——从冗长的匿名内部类和 for 循环,变成了简洁的声明式写法。这篇文章把 Lambda、函数式接口、方法引用、Stream、Optional 一次讲完。

1. 从匿名内部类到 Lambda

1.1 问题:匿名内部类太啰嗦

Java 8 之前,想把"一段行为"传给方法,只能用匿名内部类。

什么是匿名内部类? 就是在创建对象的同时定义一个没有名字的子类。new 接口/父类() { 实现方法 } 相当于三步合一:定义一个类 → 实现接口方法 → 创建实例:

// 正常写法:先定义一个实现类,再实例化
class LengthComparator implements Comparator<String> {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
}
list.sort(new LengthComparator());

// 匿名内部类:省掉类名,直接在 new 的时候写实现
list.sort(new Comparator<String>() {   // new 接口() { 实现 }
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

但这样还是很啰嗦——5 行代码,真正有用的只有 a.length() - b.length() 一行,其余全是模板代码。

1.2 Lambda 表达式

Lambda 本质就是匿名函数——一段可以传递的代码:

// 上面的匿名内部类,用 Lambda 一行搞定
list.sort((a, b) -> a.length() - b.length());

1.3 Lambda 语法

// 完整形式
(String a, String b) -> { return a.length() - b.length(); }

// 参数类型可省略(编译器推断)
(a, b) -> { return a.length() - b.length(); }

// 方法体只有一行时,可以省略大括号和 return
(a, b) -> a.length() - b.length()

// 只有一个参数时,可以省略括号
x -> x * 2

// 无参数
() -> System.out.println("hello")

1.4 Lambda 的本质

Lambda 不是语法糖那么简单。它的底层不是匿名内部类,而是通过 invokedynamic 字节码指令实现,性能更好(不会每次创建新的 class 文件)。

但从使用角度理解,你可以把 Lambda 看作一个只有一个抽象方法的接口的实例。这就引出了下一个概念。

2. 函数式接口(Functional Interface)

2.1 定义

只有一个抽象方法的接口就是函数式接口,Lambda 只能赋值给函数式接口:

@FunctionalInterface  // 可选注解,编译器会校验是否只有一个抽象方法
public interface MyFunction {
    int apply(int x);
    // 如果再加一个抽象方法,编译报错
}

MyFunction doubler = x -> x * 2;
System.out.println(doubler.apply(5));  // 10

2.2 Java 内置的四大函数式接口

Java 在 java.util.function 包中预定义了大量函数式接口,最常用的四个:

接口 方法签名 用途 示例
Function<T, R> R apply(T t) 转换:T → R s -> s.length()
Predicate<T> boolean test(T t) 判断:T → boolean s -> s.isEmpty()
Consumer<T> void accept(T t) 消费:T → void s -> System.out.println(s)
Supplier<T> T get() 生产:() → T () -> new ArrayList<>()
// Function:转换
Function<String, Integer> strLen = s -> s.length();
strLen.apply("hello");  // 5

// Predicate:判断
Predicate<String> isLong = s -> s.length() > 5;
isLong.test("hello");   // false

// Consumer:消费
Consumer<String> printer = s -> System.out.println(s);
printer.accept("hello");  // 输出 hello

// Supplier:生产
Supplier<List<String>> listFactory = () -> new ArrayList<>();
List<String> list = listFactory.get();

2.3 其他常用变体

双参数版本:在四大接口前加 Bi 前缀,表示接受两个参数:

// BiFunction:两个入参,一个返回值
BiFunction<String, String, Integer> sumLen = (a, b) -> a.length() + b.length();
sumLen.apply("hello", "world");  // 10

// BiPredicate:两个入参,返回 boolean
BiPredicate<String, Integer> longerThan = (s, n) -> s.length() > n;
longerThan.test("hello", 3);  // true

// BiConsumer:两个入参,无返回值
BiConsumer<String, Integer> printPair = (k, v) -> System.out.println(k + "=" + v);
printPair.accept("age", 18);  // age=18

基本类型特化:泛型只能用包装类(IntegerDouble),每次调用都要装箱/拆箱有性能开销。Java 提供了直接操作基本类型的版本:

// ToIntFunction:入参任意类型,返回 int(省去 Integer 装箱)
ToIntFunction<String> strToInt = s -> s.length();
strToInt.applyAsInt("hello");  // 5

// IntFunction:入参 int,返回任意类型
IntFunction<String> intToStr = i -> "num:" + i;
intToStr.apply(42);  // "num:42"

// IntPredicate:入参 int,返回 boolean
IntPredicate isPositive = n -> n > 0;
isPositive.test(5);   // true

// IntConsumer:入参 int,无返回值
IntConsumer printInt = n -> System.out.println(n);
printInt.accept(42);  // 42

// IntSupplier:无入参,返回 int
IntSupplier randomInt = () -> (int)(Math.random() * 100);
randomInt.getAsInt();  // 0~99 的随机整数

类似的还有 LongFunctionDoubleFunction 等,命名规律一样,实际开发中用到再查即可。

同类型输入输出的简化版

// UnaryOperator<T>:入参和返回值同类型,是 Function<T,T> 的简化
UnaryOperator<String> toUpper = s -> s.toUpperCase();
toUpper.apply("hello");  // "HELLO"

// BinaryOperator<T>:两个同类型入参,返回同类型,是 BiFunction<T,T,T> 的简化
BinaryOperator<Integer> add = (a, b) -> a + b;
add.apply(3, 4);  // 7

实际开发中最常用的就是四大核心接口(FunctionPredicateConsumerSupplier),其余变体在 Stream 和框架源码中会遇到,用到时对照命名规律就能猜出用途。

2.4 函数式接口的组合

函数式接口可以通过内置方法组合成更复杂的逻辑,避免写嵌套 Lambda。

Predicate 组合

方法 含义 示例
and(other) 两个条件都满足 isLong.and(startsWithA)
or(other) 任一条件满足 isLong.or(startsWithA)
negate() 取反 isLong.negate()
Predicate<String> isLong = s -> s.length() > 5;
Predicate<String> startsWithA = s -> s.startsWith("A");

isLong.and(startsWithA).test("Alexander");   // true(长度>5 且 以A开头)
isLong.or(startsWithA).test("Alice");        // true(长度不>5,但以A开头)
isLong.negate().test("hi");                  // true(长度不>5)

Function 组合

方法 含义 执行顺序
andThen(after) 先执行自己,再执行 after this → after
compose(before) 先执行 before,再执行自己 before → this
Function<String, String> trim  = s -> s.trim();
Function<String, String> upper = s -> s.toUpperCase();

// andThen:先 trim 再 upper
trim.andThen(upper).apply("  hello  ");  // "HELLO"

// compose:先 upper 再 trim(和 andThen 顺序相反)
trim.compose(upper).apply("  hello  ");  // "  HELLO  "(先大写再trim,trim没效果)

记忆技巧:andThen 就是"然后再做",顺序和代码书写顺序一致;compose 是数学里的函数复合 f∘g,先执行括号里的。实际开发中 andThen 更常用。

3. 方法引用(Method Reference)

当 Lambda 只是调用一个已有方法时,可以用方法引用进一步简化:

// Lambda                           →  方法引用
s -> System.out.println(s)System.out::println
s -> s.toUpperCase()String::toUpperCase
s -> Integer.parseInt(s)Integer::parseInt
() -> new ArrayList<>()ArrayList::new

3.1 四种形式

类型 语法 等价 Lambda 示例
静态方法引用 类名::静态方法 x -> 类名.方法(x) Integer::parseInt
实例方法引用(对象) 对象::实例方法 x -> 对象.方法(x) System.out::println
实例方法引用(类) 类名::实例方法 (obj, x) -> obj.方法(x) String::compareTo
构造方法引用 类名::new x -> new 类名(x) ArrayList::new
List<String> list = List.of("banana", "apple", "cherry");

// 静态方法引用
list.stream().map(Integer::parseInt);  // 如果 list 是数字字符串

// 实例方法引用(通过类名)
list.sort(String::compareToIgnoreCase);
// 等价于 list.sort((a, b) -> a.compareToIgnoreCase(b))

// 构造方法引用
list.stream().map(StringBuilder::new);
// 等价于 list.stream().map(s -> new StringBuilder(s))

4. Stream API

4.1 Stream 是什么?

Stream 是对集合数据的声明式处理管道——你描述"要做什么",而不是"怎么做":

// 命令式:手动循环
List<String> result = new ArrayList<>();
for (String s : list) {
    if (s.length() > 4) {
        result.add(s.toUpperCase());
    }
}

// 声明式:Stream
List<String> result = list.stream()
    .filter(s -> s.length() > 4)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

4.2 Stream 的三个阶段

数据源 → 中间操作(lazy) → 终端操作(触发执行)

中间操作返回新的 Stream,可以链式调用,不会立即执行(惰性求值):

操作 说明 示例
filter 过滤 .filter(s -> s.length() > 3)
map 转换 .map(String::toUpperCase)
flatMap 一对多展开 .flatMap(line -> Arrays.stream(line.split(" ")))
distinct 去重 .distinct()
sorted 排序 .sorted().sorted(Comparator)
peek 查看(调试用) .peek(System.out::println)
limit 截取前 n 个 .limit(5)
skip 跳过前 n 个 .skip(2)

终端操作触发整个管道执行,返回结果:

操作 说明 示例
collect 收集为集合 .collect(Collectors.toList())
forEach 遍历 .forEach(System.out::println)
count 计数 .count()
reduce 归约 .reduce(0, Integer::sum)
findFirst 第一个元素 .findFirst()
findAny 任意一个 .findAny()
anyMatch 任一匹配 .anyMatch(s -> s.isEmpty())
allMatch 全部匹配 .allMatch(s -> s.length() > 0)
noneMatch 全不匹配 .noneMatch(s -> s.isEmpty())
min / max 最值 .min(Comparator.naturalOrder())
toArray 转数组 .toArray(String[]::new)

4.3 stream 的 Collectors 收集器详解

collect() 是 Stream 最重要的终端操作,需要传入一个 Collector 告诉它怎么收集结果。Collectors 工具类提供了所有常用的收集器:

基本收集

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

// toList:最常用
List<String> list = names.stream().collect(Collectors.toList());

// toSet:自动去重,顺序不保证
Set<String> set = names.stream().collect(Collectors.toSet());
// {Alice, Bob, Charlie}

// toCollection:指定具体实现类
LinkedList<String> linked = names.stream()
    .collect(Collectors.toCollection(LinkedList::new));

字符串拼接 joining

// joining() 只能用于 Stream<String>
List<String> words = List.of("hello", "world", "java");

words.stream().collect(Collectors.joining());
// "helloworldjava"

words.stream().collect(Collectors.joining(", "));
// "hello, world, java"

words.stream().collect(Collectors.joining(", ", "[", "]"));
// "[hello, world, java]"
//    ↑分隔符  ↑前缀  ↑后缀

toMap:收集为 Map

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

// toMap(key提取函数, value提取函数)
Map<String, Integer> nameToLength = names.stream()
    .collect(Collectors.toMap(
        name -> name,       // key:名字本身
        String::length      // value:名字长度
    ));
// {Alice=5, Bob=3, Charlie=7}

// key 冲突时需要提供合并函数,否则抛异常
List<String> withDup = List.of("Alice", "Bob", "Alice");
Map<String, Integer> map = withDup.stream()
    .collect(Collectors.toMap(
        name -> name,
        String::length,
        (old, newVal) -> old  // key 重复时保留旧值
    ));

groupingBy:分组

按某个条件把元素分成多组,返回 Map<分组key, List<元素>>

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

// 按字符串长度分组
Map<Integer, List<String>> byLength = names.stream()
    .collect(Collectors.groupingBy(String::length));
// {3=[Bob, Amy], 5=[Alice, David], 7=[Charlie]}

// 按首字母分组
Map<Character, List<String>> byFirstChar = names.stream()
    .collect(Collectors.groupingBy(s -> s.charAt(0)));
// {A=[Alice, Amy], B=[Bob], C=[Charlie], D=[David]}

// 分组后再统计数量(downstream collector)
Map<Integer, Long> countByLength = names.stream()
    .collect(Collectors.groupingBy(
        String::length,
        Collectors.counting()   // 每组的数量
    ));
// {3=2, 5=2, 7=1}

// 分组后再收集为 Set(去重)
Map<Integer, Set<String>> setByLength = names.stream()
    .collect(Collectors.groupingBy(
        String::length,
        Collectors.toSet()
    ));

partitioningBy:分区

按条件把元素分成恰好两组,返回 Map<Boolean, List<元素>>,key 只有 truefalse

List<String> names = List.of("Alice", "Bob", "Charlie", "Amy");

// 按长度是否大于 4 分区
Map<Boolean, List<String>> partition = names.stream()
    .collect(Collectors.partitioningBy(s -> s.length() > 4));
// {false=[Bob, Amy], true=[Alice, Charlie]}

List<String> longNames  = partition.get(true);   // [Alice, Charlie]
List<String> shortNames = partition.get(false);  // [Bob, Amy]

groupingBy vs partitioningBy 的选择:

groupingBy partitioningBy
分组数量 任意多组 恰好两组
key 类型 任意类型 Boolean
适用场景 按类别分组 按条件过滤出两组

统计类收集器

List<Integer> nums = List.of(1, 2, 3, 4, 5);

// counting:计数
long count = nums.stream().collect(Collectors.counting());  // 5

// summingInt / averagingInt:求和 / 求平均
int sum = nums.stream().collect(Collectors.summingInt(n -> n));  // 15
double avg = nums.stream().collect(Collectors.averagingInt(n -> n));  // 3.0

// summarizingInt:一次性获取所有统计信息
IntSummaryStatistics stats = nums.stream()
    .collect(Collectors.summarizingInt(n -> n));
stats.getCount();    // 5
stats.getSum();      // 15
stats.getMin();      // 1
stats.getMax();      // 5
stats.getAverage();  // 3.0

常用收集器速查表

收集器 返回类型 用途
toList() List<T> 收集为列表
toSet() Set<T> 收集为集合(去重)
toMap(k, v) Map<K,V> 收集为映射
joining(sep) String 字符串拼接
groupingBy(fn) Map<K, List<T>> 按条件分组
partitioningBy(pred) Map<Boolean, List<T>> 按条件分成两组
counting() Long 计数
summingInt(fn) Integer 求和
averagingInt(fn) Double 求平均
summarizingInt(fn) IntSummaryStatistics 全部统计信息

4.4 Stream 的 map 操作详解

map 是最常用的中间操作,作用是把每个元素转换成另一种类型

// 基本用法:String → Integer
List<String> names = List.of("Alice", "Bob", "Charlie");
List<Integer> lengths = names.stream()
    .map(String::length)        // 每个字符串转成长度
    .collect(Collectors.toList());
// [5, 3, 7]

// 对象字段提取
List<User> users = List.of(new User("Alice", 20), new User("Bob", 25));
List<String> nameList = users.stream()
    .map(User::getName)         // 提取每个 User 的 name 字段
    .collect(Collectors.toList());
// ["Alice", "Bob"]

// 链式转换
List<String> result = names.stream()
    .map(String::toLowerCase)   // 先转小写
    .map(s -> s + "!")          // 再加感叹号
    .collect(Collectors.toList());
// ["alice!", "bob!", "charlie!"]

基本类型特化版本(避免装箱开销):

方法 返回类型 用途
mapToInt(fn) IntStream 转换为 int 流
mapToLong(fn) LongStream 转换为 long 流
mapToDouble(fn) DoubleStream 转换为 double 流
mapToObj(fn) Stream<T> 基本类型流转回对象流
List<String> names = List.of("Alice", "Bob", "Charlie");

// mapToInt:转成 IntStream,可以直接调用 sum/avg/min/max
int totalLength = names.stream()
    .mapToInt(String::length)
    .sum();   // 15

double avgLength = names.stream()
    .mapToInt(String::length)
    .average()
    .orElse(0);  // 5.0

// IntStream 转回 Stream<T>
IntStream.of(1, 2, 3)
    .mapToObj(i -> "num" + i)   // IntStream → Stream<String>
    .collect(Collectors.toList());
// ["num1", "num2", "num3"]

flatMap:一对多展开

map 是一对一转换,flatMap 是一对多转换,会把嵌套结构展平一层:

// map 的结果是嵌套的 Stream<Stream<String>>
// flatMap 把它展平成 Stream<String>

// 场景:每行文本拆成单词
List<String> lines = List.of("hello world", "foo bar baz");

// map:结果是 [[hello, world], [foo, bar, baz]](嵌套)
List<List<String>> nested = lines.stream()
    .map(line -> Arrays.asList(line.split(" ")))
    .collect(Collectors.toList());

// flatMap:结果是 [hello, world, foo, bar, baz](展平)
List<String> words = lines.stream()
    .flatMap(line -> Arrays.stream(line.split(" ")))
    .collect(Collectors.toList());

// 场景:展平嵌套列表
List<List<Integer>> nums = List.of(
    List.of(1, 2, 3),
    List.of(4, 5),
    List.of(6)
);
List<Integer> flat = nums.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());
// [1, 2, 3, 4, 5, 6]

map vs flatMap 一句话总结:

输入 输出 结果形状
map(fn) 一个元素 一个元素 不变
flatMap(fn) 一个元素 多个元素(Stream) 展平一层

4.5 其他常用中间操作

filter:过滤

List<String> names = List.of("Alice", "Bob", "Charlie", "Amy");

// 保留满足条件的元素
List<String> longNames = names.stream()
    .filter(s -> s.length() > 3)
    .collect(Collectors.toList());
// ["Alice", "Charlie"]

// 多个 filter 可以链式叠加(等价于 and)
List<String> result = names.stream()
    .filter(s -> s.length() > 3)
    .filter(s -> s.startsWith("A"))
    .collect(Collectors.toList());
// ["Alice"]

sorted:排序

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

// 自然排序(字典序)
names.stream().sorted().collect(Collectors.toList());
// ["Alice", "Bob", "Charlie"]

// 自定义排序
names.stream()
    .sorted(Comparator.comparingInt(String::length))  // 按长度升序
    .collect(Collectors.toList());
// ["Bob", "Alice", "Charlie"]

// 多字段排序
names.stream()
    .sorted(Comparator.comparingInt(String::length)
                      .thenComparing(Comparator.naturalOrder()))  // 长度相同再按字典序
    .collect(Collectors.toList());

// 降序
names.stream()
    .sorted(Comparator.comparingInt(String::length).reversed())
    .collect(Collectors.toList());
// ["Charlie", "Alice", "Bob"]

distinct / limit / skip:去重和截取

List<Integer> nums = List.of(1, 2, 2, 3, 3, 3, 4, 5);

nums.stream().distinct().collect(Collectors.toList());
// [1, 2, 3, 4, 5](去重,保持顺序)

nums.stream().limit(3).collect(Collectors.toList());
// [1, 2, 2](只取前3个)

nums.stream().skip(5).collect(Collectors.toList());
// [3, 4, 5](跳过前5个)

// 组合使用:分页效果
int page = 2, pageSize = 3;
nums.stream()
    .skip((page - 1) * pageSize)   // 跳过前一页
    .limit(pageSize)                // 取一页
    .collect(Collectors.toList());
// [3, 3, 4](第2页)

peek:调试用

peek 不改变元素,只是"偷看"一下,常用于调试时打印中间结果:

List<String> result = names.stream()
    .peek(s -> System.out.println("过滤前: " + s))
    .filter(s -> s.length() > 3)
    .peek(s -> System.out.println("过滤后: " + s))
    .map(String::toUpperCase)
    .collect(Collectors.toList());
// 过滤前: Charlie
// 过滤前: Alice
// 过滤前: Bob
// 过滤后: Charlie
// 过滤后: Alice

注意 peek 只用于调试,不要在里面做修改元素内容等有副作用的操作。

4.6 终端操作详解

match 系列:匹配判断

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

names.stream().anyMatch(s -> s.startsWith("A"));   // true(任意一个满足)
names.stream().allMatch(s -> s.length() > 2);      // true(全部满足)
names.stream().noneMatch(s -> s.startsWith("Z"));  // true(全不满足)

find 系列:查找元素

// findFirst:返回第一个元素(有序流用这个)
Optional<String> first = names.stream()
    .filter(s -> s.length() > 3)
    .findFirst();
first.orElse("none");  // "Alice"

// findAny:返回任意一个(并行流下更快)
Optional<String> any = names.stream()
    .filter(s -> s.length() > 3)
    .findAny();

reduce:归约

把所有元素合并成一个结果:

List<Integer> nums = List.of(1, 2, 3, 4, 5);

// 有初始值:不会返回 Optional
int sum = nums.stream().reduce(0, (a, b) -> a + b);   // 15
int sum2 = nums.stream().reduce(0, Integer::sum);      // 15(方法引用简化)

// 无初始值:可能流为空,返回 Optional(将会在下一节介绍)
Optional<Integer> max = nums.stream().reduce(Integer::max);
max.orElse(0);  // 5

// 字符串拼接(实际更推荐用 Collectors.joining)
String joined = Stream.of("a", "b", "c")
    .reduce("", (a, b) -> a + b);  // "abc"

min / max:最值

Optional<String> shortest = names.stream()
    .min(Comparator.comparingInt(String::length));
shortest.orElse("");  // "Bob"

Optional<String> longest = names.stream()
    .max(Comparator.comparingInt(String::length));
longest.orElse("");  // "Charlie"

count / toArray

long count = names.stream().filter(s -> s.length() > 3).count();  // 2

// toArray:转成数组
String[] arr = names.stream().toArray(String[]::new);

5. Optional

5.1 为什么需要 Optional?

Optional 是一个容器,表示"值可能存在,也可能不存在",用来替代 null 减少 NPE(NullPointerException):

// 传统写法:满屏的 null 检查
String city = null;
if (user != null) {
    Address addr = user.getAddress();
    if (addr != null) {
        city = addr.getCity();
    }
}
if (city == null) city = "Unknown";

5.2 基本用法

// 创建
Optional<String> opt1 = Optional.of("hello");       // 值不能为 null
Optional<String> opt2 = Optional.ofNullable(null);   // 值可以为 null
Optional<String> opt3 = Optional.empty();             // 空的 Optional

// 判断和获取
opt1.isPresent();   // true
opt2.isPresent();   // false
opt1.isEmpty();     // false(Java 11+)

opt1.get();         // "hello"(有值时获取)
// opt2.get();      // 抛 NoSuchElementException!空的不能 get

// 安全获取
opt2.orElse("default");                    // "default"
opt2.orElseGet(() -> computeDefault());    // 惰性计算默认值
opt2.orElseThrow(() -> new RuntimeException("no value"));  // 抛自定义异常

5.3 链式操作

// 用 Optional 改写上面的嵌套 null 检查
String city = Optional.ofNullable(user)
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("Unknown");

// filter:条件不满足时变成空 Optional
Optional<String> longName = Optional.of("Alice")
    .filter(s -> s.length() > 10);  // Optional.empty()

// flatMap:当映射函数本身返回 Optional 时
Optional<String> city = Optional.ofNullable(user)
    .flatMap(User::getOptionalAddress)   // 返回 Optional<Address>
    .flatMap(Address::getOptionalCity);  // 返回 Optional<String>

5.4 Optional 的使用原则

// ✅ 用作方法返回值,表示结果可能不存在
public Optional<User> findById(String id) {
    User user = db.query(id);
    return Optional.ofNullable(user);
}

// ❌ 不要用作方法参数(调用者还是要判断传 Optional 还是传 null)
// ❌ 不要用作类的字段(Optional 没有实现 Serializable)
// ❌ 不要用 Optional 包装集合(空集合比 Optional<List> 更好)
// ❌ 不要用 isPresent() + get() 替代 null 检查(这和直接判 null 一样丑)

// 反面教材
if (opt.isPresent()) {
    doSomething(opt.get());
}

// 正确姿势
opt.ifPresent(value -> doSomething(value));
// 或
opt.ifPresentOrElse(
    value -> doSomething(value),
    () -> handleEmpty()
);  // Java 9+

6. 小结

主题 关键要点
Lambda 匿名函数,替代匿名内部类;底层用 invokedynamic 而非生成类
函数式接口 只有一个抽象方法;四大核心:Function、Predicate、Consumer、Supplier
方法引用 Lambda 的简写;四种形式:静态、实例(对象)、实例(类)、构造
Stream 三阶段 数据源 → 中间操作(lazy) → 终端操作(触发执行)
常用终端操作 collect、forEach、count、reduce、findFirst、anyMatch
Collectors toList、toSet、toMap、groupingBy、joining、partitioningBy
flatMap 展平嵌套结构,一对多映射
Optional 替代 null;用作返回值,不用作参数和字段;链式 map/flatMap/orElse

下一篇预告:I/O 与文件操作——BIO、NIO 与 Files 工具类


🎯 如果这篇文章对你有帮助,别忘了点赞、收藏、关注三连!关注我,让你在 Java 学习的道路上不迷路,持续为你带来成体系的 Java 干货~

更多推荐