Java 8 新特性
Lambda / Stream / Optional
Java 8(2014 年发布)是 Java 历史上最重要的一次升级。Lambda、Stream、Optional 改变了 Java 的写法,现在 80% 的 Java 代码都在用这套。如果只学一个版本,就 Java 8。
学完这节:你能用 Lambda 替代匿名内部类、能把集合操作用
stream().filter().map().collect() 一行搞定、能用 Optional 优雅处理 null、能读懂 Spring Boot / 现代 Java 代码。
1
为什么 Java 8 这么重要?Lambda 之前的痛
💡 动机:Lambda 出现前,写个"按钮点击"都要 5 行
痛点 1:匿名内部类太啰嗦
// Java 8 之前:写个比较器要这么多代码
List<String> list = Arrays.asList("banana", "apple", "cherry");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
// Java 8+:一行搞定
list.sort((a, b) -> a.compareTo(b));
// 或者更简单
list.sort(String::compareTo); // 方法引用
痛点 2:集合操作繁琐
// 找出所有长度 > 3 的,转大写,排序,去重
// 之前要写 4-5 个循环
// Java 8+:一行
List<String> result = list.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.sorted()
.distinct()
.collect(Collectors.toList());
Java 8 的 4 大杀器:
- Lambda 表达式 → 函数式编程基础
- Stream API → 链式数据处理(filter / map / reduce)
- Optional → 优雅处理 null
- 新时间 API(java.time)→ 替代难用的 Date / Calendar
2
Lambda 表达式:把"行为"当参数
💡 动机:Java 终于可以"把一段代码传给方法"了
Lambda 语法:(参数) -> { 方法体 }
5 种最常见的写法
// 1. 无参 + 单行
Runnable r = () -> System.out.println("hi");
// 2. 单参 + 单行(括号可省)
Consumer<String> c = s -> System.out.println(s);
// 3. 多参 + 单行
Comparator<Integer> cmp = (a, b) -> a - b;
// 4. 多行(带 return)
Comparator<String> cmp2 = (a, b) -> {
System.out.println("比较: " + a + " vs " + b);
return a.compareTo(b);
};
// 5. 类型可省略(编译器自动推断)
BinaryOperator<Integer> add = (a, b) -> a + b; // a, b 自动推断为 Integer
小白记法:Lambda = "省略了方法名的匿名内部类"。比如:
// 完整写法
new Comparator<Integer>() {
public int compare(Integer a, Integer b) { return a - b; }
}
// Lambda 写法(省略了 new Comparator + 方法名 + 返回值类型 + return)
(a, b) -> a - b
3
函数式接口 + 方法引用
💡 动机:Lambda 不是"万能函数",它必须实现某个"函数式接口"
JDK 自带的 4 大核心函数式接口
| 接口 | 签名 | 用途 |
|---|---|---|
Function<T, R> |
T -> R |
"转换":把 T 变成 R |
Consumer<T> |
T -> void |
"消费":处理 T,不返回 |
Supplier<T> |
() -> T |
"提供":不输入,只返回 T |
Predicate<T> |
T -> boolean |
"判断":T 是否满足条件 |
方法引用(Lambda 的简化版)
// 4 种语法
// 1. 静态方法:Class::staticMethod
Integer::parseInt // 等价于 s -> Integer.parseInt(s)
// 2. 实例方法:instance::method
System.out::println // 等价于 x -> System.out.println(x)
// 3. 任意对象的实例方法:Class::method
String::compareTo // 等价于 (a, b) -> a.compareTo(b)
// 4. 构造器:Class::new
ArrayList::new // 等价于 () -> new ArrayList<>()
4
Stream API:链式数据处理 ⭐ 面试常问
💡 动机:把集合操作从"多个循环"变成"一条链"
Stream 不是数据结构,是对数据的一系列操作。三步走:创建 → 中间操作 → 终结操作。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "avocado");
// 找出以 'a' 开头的,长度 > 5,按字母排序
List<String> result = fruits.stream() // 1. 创建流
.filter(s -> s.startsWith("a")) // 2. 中间操作:过滤
.filter(s -> s.length() > 5) // 2. 中间操作:再过滤
.sorted() // 2. 中间操作:排序
.collect(Collectors.toList()); // 3. 终结操作:收集为 List
// → ["avocado"]
9 个最常用的 Stream 操作
| 操作 | 类型 | 作用 |
|---|---|---|
filter | 中间 | 按条件过滤 |
map | 中间 | 元素转换 |
sorted | 中间 | 排序 |
distinct | 中间 | 去重 |
limit / skip | 中间 | 截取前 n / 跳过 n |
forEach | 终结 | 遍历 |
collect | 终结 | 收集到集合 |
count | 终结 | 计数 |
findFirst / anyMatch | 终结 | 找第一个 / 是否存在 |
⭐ 3 个关键点:
- Stream 不存数据:它只是一系列"操作计划",只有终结操作才真正执行(lazy)
- Stream 只能用一次:用完就废了,要再处理就重新创建
- 不要修改原集合:filter/map 都是返回新 Stream,原集合不变
5
Optional:优雅地处理 null ⭐ 面试加分
💡 动机:每个 Java 程序都有一堆 NPE,能不能让 API 强制处理 null?
没有 Optional 时的痛
// 经典 NPE 链
String city = user.getAddress().getCity().toUpperCase();
// 任何一环返回 null 就炸
// 防御性写法(一坨 if)
String city = null;
if (user != null) {
Address addr = user.getAddress();
if (addr != null) {
city = addr.getCity();
}
}
if (city != null) {
city = city.toUpperCase();
}
用 Optional 改写
// 把可能为 null 的返回值包成 Optional
public Optional<String> getCity() {
return Optional.ofNullable(this.city); // null 也接受
}
// 链式处理,null 安全
String city = user.getAddress()
.flatMap(Address::getCity) // 如果 address 是空,跳过
.map(String::toUpperCase) // 非空就转大写
.orElse("未知"); // 最终为空就给默认值
5 个核心方法
Optional<String> opt = Optional.ofNullable(getValue());
// 1. 拿值(空就抛异常)
String s = opt.get(); // ⚠️ 空时抛 NoSuchElementException
// 2. 拿值(空就给默认值)
String s = opt.orElse("default");
// 3. 拿值(空就执行 supplier 计算)
String s = opt.orElseGet(() -> computeDefault());
// 4. 拿值(空就抛指定异常)
String s = opt.orElseThrow(() -> new MyException("没值"));
// 5. 有值才做某事
opt.ifPresent(s -> System.out.println(s));
⭐ 实战建议:
- Optional 不要当参数用(违反设计意图)
- Optional 不要序列化(很多 JSON 库不支持)
- DAO / Repository 的 find 方法推荐返回
Optional<T>而不是null
6
新时间 API + 其他新特性
💡 动机:Date / Calendar 难用到哭,新 API 救你
java.time 包的 5 个核心类
| 类 | 表示 | 例子 |
|---|---|---|
LocalDate | 日期(年月日) | 2026-09-04 |
LocalTime | 时间(时分秒) | 14:30:00 |
LocalDateTime | 日期 + 时间 | 2026-09-04T14:30 |
Instant | 时间戳(机器视角) | 1725436200 |
Duration | 时间间隔 | PT2H30M |
// 获取 + 运算 + 格式化
LocalDate today = LocalDate.now(); // 今天
LocalDate tomorrow = today.plusDays(1); // 明天
LocalDate nextMonth = today.plusMonths(1);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String formatted = LocalDateTime.now().format(fmt); // "2026-09-04 14:30"
// 算两个日期相差几天
long days = ChronoUnit.DAYS.between(today, tomorrow); // 1
⭐⭐ 面试必问:SimpleDateFormat 线程安全问题
// ❌ 危险!SimpleDateFormat 不是线程安全的
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 多个线程同时用 → 出现奇怪的日期 / NumberFormatException
// ✅ 改用 java.time 包的 DateTimeFormatter(线程安全)
private static DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 或者用 ThreadLocal 包装 SimpleDateFormat
private static ThreadLocal<SimpleDateFormat> sdf = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd")
);
其他 Java 8+ 新特性速览
| 特性 | 用途 |
|---|---|
default 方法 | 接口可以有默认实现(不破坏现有实现类) |
static 接口方法 | 接口里可以有静态方法(如 List.of()) |
| 方法引用 | Lambda 的语法糖(String::length) |
| 重复注解 | 同一个注解可以贴多次(@Repeatable) |
| 类型注解 | 注解能贴到任何地方(TYPE_USE) |
你的作业(分步走)
- Lambda 三连:用 Lambda 实现:(a)对
List<Integer>排序(b)筛选长度 > 3 的字符串(c)自定义Function<Integer, String>把数字转中文"一/二/三" - Stream 链:给
List<String> names = ["Alice", "Bob", "Charlie", "David", "Eve"],用一条 Stream 链完成:过滤长度 ≥ 4 → 转大写 → 按字母倒序 → 收集成 List,打印 - 方法引用:把作业 2 改写成方法引用版本(
String::length、String::toUpperCase等),对比哪个更简洁 - Optional 链:写个
User → Address → City三层对象模型,用 Optional 链式取出 city 并转大写,city 为空时返回 "UNKNOWN" - 新时间 API:写代码计算"下个月的第一天是星期几"、以及"从今天起 100 天后的日期"
- 改写老代码:找一个之前用 for 循环的练习(比如 Lesson 05 的 ArrayList 操作),用 Stream 改写,对比行数