LESSON 08 · Java 8+ 新特性

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 个关键点:
  1. Stream 不存数据:它只是一系列"操作计划",只有终结操作才真正执行(lazy)
  2. Stream 只能用一次:用完就废了,要再处理就重新创建
  3. 不要修改原集合: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

你的作业(分步走)

  1. Lambda 三连:用 Lambda 实现:(a)对 List<Integer> 排序(b)筛选长度 > 3 的字符串(c)自定义 Function<Integer, String> 把数字转中文"一/二/三"
  2. Stream 链:给 List<String> names = ["Alice", "Bob", "Charlie", "David", "Eve"],用一条 Stream 链完成:过滤长度 ≥ 4 → 转大写 → 按字母倒序 → 收集成 List,打印
  3. 方法引用:把作业 2 改写成方法引用版本(String::lengthString::toUpperCase 等),对比哪个更简洁
  4. Optional 链:写个 User → Address → City 三层对象模型,用 Optional 链式取出 city 并转大写,city 为空时返回 "UNKNOWN"
  5. 新时间 API:写代码计算"下个月的第一天是星期几"、以及"从今天起 100 天后的日期"
  6. 改写老代码:找一个之前用 for 循环的练习(比如 Lesson 05 的 ArrayList 操作),用 Stream 改写,对比行数