Java新特性(8-21)¶
🎯 学习目标¶
完成本章学习后,你将能够: - 熟练使用Java 8 Lambda表达式与Stream API - 掌握Java 9-11的模块化、var推断等特性 - 理解Java 14-17的Records、Sealed Classes、Pattern Matching - 深入掌握Java 21虚拟线程(Virtual Threads)及结构化并发 - 能在新旧写法之间灵活切换,写出现代化的Java代码
1. Java 8 核心特性(最重要版本)¶
1.1 Lambda表达式¶
Lambda是匿名函数的简洁表示,使Java支持函数式编程风格。
Java
// ============ 旧写法:匿名内部类 ============
Runnable oldWay = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// ============ 新写法:Lambda ============
Runnable newWay = () -> System.out.println("Hello"); // Lambda表达式,简洁的匿名函数
// 排序对比
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
// 旧写法
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
// Lambda写法
names.sort((a, b) -> a.compareTo(b));
// 方法引用写法
names.sort(String::compareTo);
1.2 函数式接口¶
只有一个抽象方法的接口,可以用Lambda实例化。
Java
// JDK内置核心函数式接口
// Function<T, R> — T → R(转换)
// Predicate<T> — T → boolean(判断)
// Consumer<T> — T → void(消费)
// Supplier<T> — () → T(生产)
// UnaryOperator<T> — T → T(一元操作)
// BiFunction<T,U,R> — (T, U) → R(双参数转换)
import java.util.function.*;
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// Function:转换
Function<String, Integer> strLen = String::length;
System.out.println(strLen.apply("Hello")); // 5
// 链式组合
Function<String, String> trim = String::trim;
Function<String, String> upper = String::toUpperCase;
Function<String, String> pipeline = trim.andThen(upper);
System.out.println(pipeline.apply(" hello ")); // "HELLO"
// Predicate:过滤
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositiveEven = isPositive.and(isEven);
System.out.println(isPositiveEven.test(4)); // true
// Consumer:消费
Consumer<String> print = System.out::println;
Consumer<String> printUpper = s -> System.out.println(s.toUpperCase());
print.andThen(printUpper).accept("hello");
// hello
// HELLO
// Supplier:延迟创建
Supplier<List<String>> listFactory = ArrayList::new;
List<String> list = listFactory.get();
// 自定义函数式接口
// @FunctionalInterface // 编译期校验
// public interface Transformer<T> {
// T transform(T input);
// }
}
}
1.3 Stream API(完整教程)¶
Java
import java.util.*;
import java.util.stream.*;
public class StreamDemo {
// 注意:record是Java 16+特性,此处用于简化示例代码
// Java 8环境请改用传统class定义(含构造器、getter等)
record Employee(String name, String dept, double salary, int age) {}
public static void main(String[] args) {
List<Employee> employees = List.of(
new Employee("Alice", "Engineering", 120000, 30),
new Employee("Bob", "Engineering", 95000, 25),
new Employee("Charlie", "Marketing", 80000, 28),
new Employee("Diana", "Engineering", 150000, 35),
new Employee("Eve", "Marketing", 70000, 23),
new Employee("Frank", "HR", 60000, 40)
);
// ===== 中间操作(惰性求值,返回Stream) =====
// filter:过滤
List<Employee> engineers = employees.stream() // Stream API 声明式数据处理流水线
.filter(e -> e.dept().equals("Engineering"))
.toList();
// map:映射/转换
List<String> names = employees.stream()
.map(Employee::name)
.toList();
// flatMap:一对多展平
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> flat = nested.stream()
.flatMap(Collection::stream) // Collection::stream 等价于 list -> list.stream(),对每个子列表调用stream()后合并为一个流
.toList(); // [1, 2, 3, 4]
// sorted:排序
List<Employee> sortedBySalary = employees.stream()
.sorted(Comparator.comparingDouble(Employee::salary).reversed())
.toList();
// distinct:去重
// peek:调试(中间操作)
// limit / skip:截取 / 跳过
// ===== 终端操作(触发流水线执行) =====
// collect:收集为集合
Map<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::dept));
// 统计:各部门平均工资
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::dept,
Collectors.averagingDouble(Employee::salary)
));
// reduce:聚合
double totalSalary = employees.stream()
.mapToDouble(Employee::salary)
.sum();
Optional<Employee> highestPaid = employees.stream()
.max(Comparator.comparingDouble(Employee::salary));
// count / anyMatch / allMatch / noneMatch
long count = employees.stream()
.filter(e -> e.salary() > 100000)
.count();
boolean anyHighSalary = employees.stream()
.anyMatch(e -> e.salary() > 140000);
// forEach:遍历
employees.stream()
.filter(e -> e.age() < 30)
.forEach(e -> System.out.println(e.name()));
// toMap
Map<String, Double> salaryMap = employees.stream()
.collect(Collectors.toMap(Employee::name, Employee::salary));
// joining
String nameStr = employees.stream()
.map(Employee::name)
.collect(Collectors.joining(", "));
// ===== 并行流 =====
// 大数据量(> 10万)+ CPU密集型计算时考虑使用
double total = employees.parallelStream()
.mapToDouble(Employee::salary)
.sum();
System.out.println("总薪资: " + total);
// ⚠️ parallelStream 安全使用要点:
// ✅ 无状态操作(filter/map/reduce)是安全的
// ❌ 不要在并行流中修改共享可变状态:
// List<String> results = new ArrayList<>();
// employees.parallelStream().forEach(e -> results.add(e.name())); // 竞态!
// 正确写法:.collect(Collectors.toList())
// ❌ 不要对小数据集使用(线程调度开销 > 计算收益)
// ❌ 不要在并行流中执行IO操作(共享ForkJoinPool会被阻塞)
// ❌ 不要对有序性敏感的操作使用(如 forEach 不保证顺序,用 forEachOrdered)
}
}
1.4 Optional¶
Java
// ============ 旧写法:容易NPE ============
public String getCity(User user) {
if (user != null) {
Address addr = user.getAddress();
if (addr != null) {
return addr.getCity();
}
}
return "Unknown";
}
// ============ 新写法:Optional链 ============
public String getCity(User user) {
return Optional.ofNullable(user) // Optional 安全处理可能为null的值
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");
}
// Optional常用API
Optional<String> opt = Optional.of("Hello");
opt.isPresent(); // true
opt.ifPresent(System.out::println); // 输出Hello
opt.orElse("default"); // "Hello"
opt.orElseGet(() -> "computed"); // "Hello"
opt.orElseThrow(() -> new RuntimeException("空值"));
opt.map(String::toUpperCase); // Optional<"HELLO">
opt.filter(s -> s.length() > 3); // Optional<"Hello">
// ⚠️ Optional 最佳实践:
// ✅ 用作方法返回值(表示"可能为空"的语义)
// ❌ 不要用作方法参数类型(增加调用复杂度,用@Nullable或重载代替)
// ❌ 不要用作字段类型(Optional不可序列化,增加内存开销)
// ❌ 不要用 Optional.get() 不检查(等于回到NPE)
// ❌ 不要用 Optional 包装集合(返回空集合 Collections.emptyList() 更好)
// Java 9+ 增强
opt.ifPresentOrElse(
System.out::println,
() -> System.out.println("空值")
);
opt.or(() -> Optional.of("backup")); // Optional链
opt.stream(); // 转为Stream
1.5 新日期时间API(java.time)¶
Java
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
// 旧: Date/Calendar(线程不安全、API混乱)
// 新: java.time包(不可变、线程安全、清晰API)
LocalDate date = LocalDate.now(); // 2026-02-07
LocalDate birthday = LocalDate.of(1998, 5, 15);
LocalTime time = LocalTime.now(); // 14:30:45.123
LocalDateTime dateTime = LocalDateTime.now(); // 日期+时间
ZonedDateTime zonedDT = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
Instant instant = Instant.now(); // UTC时间戳
// 操作
LocalDate nextWeek = date.plusWeeks(1);
LocalDate lastMonth = date.minusMonths(1);
long daysBetween = ChronoUnit.DAYS.between(birthday, date);
// 格式化
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = dateTime.format(fmt);
LocalDateTime parsed = LocalDateTime.parse("2026-02-07 14:30:00", fmt);
// Duration(时间段)和 Period(日期段)
Duration duration = Duration.between(LocalTime.of(9, 0), LocalTime.of(17, 30));
System.out.println(duration.toHours()); // 8
Period period = Period.between(birthday, date);
System.out.println(period.getYears() + "年");
2. Java 9-11¶
2.1 模块系统(JPMS,Java 9)¶
Java
// module-info.java — 模块描述符
module com.example.myapp {
requires java.sql; // 依赖其他模块
requires transitive java.logging; // 传递依赖
exports com.example.myapp.api; // 导出包(对外可见)
opens com.example.myapp.model to com.fasterxml.jackson.databind; // 对反射开放
}
2.2 局部变量类型推断 var(Java 10)¶
Java
// 旧写法
Map<String, List<Employee>> groupedMap = employees.stream()
.collect(Collectors.groupingBy(Employee::dept));
// 新写法:var推断
var groupedMap = employees.stream() // var 局部变量类型推断(Java 10+)
.collect(Collectors.groupingBy(Employee::dept));
var list = new ArrayList<String>(); // 推断为 ArrayList<String>
var stream = list.stream(); // 推断为 Stream<String>
// 注意:var只能用于局部变量,不能用于方法参数、字段、返回值
// var不是关键字,仍可用作变量名(但不推荐)
2.3 HTTP Client(Java 11)¶
Java
// 旧: HttpURLConnection(繁琐)
// 新: java.net.http.HttpClient(支持HTTP/2, 异步, WebSocket)
import java.net.http.*;
import java.net.URI;
// 同步请求
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
// 异步请求
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
// POST请求
HttpRequest postReq = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"Alice\",\"age\":25}"))
.build();
2.4 String增强(Java 11)¶
Java
// Java 11
" hello ".strip(); // "hello"(比trim更完善,支持Unicode空白)
" hello ".stripLeading(); // "hello "
" hello ".stripTrailing(); // " hello"
"".isBlank(); // true(空字符串或纯空白)
"hello\nworld".lines().toList(); // ["hello", "world"]
"ha".repeat(3); // "hahaha"
3. Java 14-17 LTS¶
3.1 Records(Java 16正式)¶
Java
// ============ 旧写法:POJO太冗长 ============
public class Point {
private final int x;
private final int y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int getX() { return x; }
public int getY() { return y; }
@Override
public boolean equals(Object o) { /* ... */ }
@Override
public int hashCode() { return Objects.hash(x, y); }
@Override
public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }
}
// ============ 新写法:Record(一行搞定) ============
public record Point(int x, int y) {} // record 不可变数据类,自动生成构造器/getter/equals/hashCode/toString
// 自动生成:构造器、getter(x()、y()而非getX())、equals、hashCode、toString
// Record是不可变的(final字段),不能继承其他类(隐式继承java.lang.Record)
// 可自定义构造器和方法
public record User(String name, String email, int age) {
// 紧凑构造器(Compact Constructor)— 参数校验
public User {
if (age < 0) throw new IllegalArgumentException("年龄不能为负");
name = name.strip(); // 可修改参数
email = email.toLowerCase();
}
// 自定义方法
public String displayName() {
return name + " <" + email + ">";
}
}
// 使用
var user = new User("Alice", "ALICE@example.com", 25);
System.out.println(user.name()); // "Alice"
System.out.println(user.displayName()); // "Alice <alice@example.com>"
3.2 Sealed Classes(Java 17正式)¶
Java
// 限制类的继承层级 — 只有permits指定的类可以继承
public sealed interface Shape permits Circle, Rectangle, Triangle {
double area();
}
public record Circle(double radius) implements Shape {
public double area() { return Math.PI * radius * radius; }
}
public record Rectangle(double width, double height) implements Shape {
public double area() { return width * height; }
}
// final:不可再被继承
public final class Triangle implements Shape {
private final double base, height;
public Triangle(double base, double height) {
this.base = base; this.height = height;
}
public double area() { return 0.5 * base * height; }
}
// 好处:编译器可以穷举检查(配合Pattern Matching switch极佳)
3.3 Pattern Matching(模式匹配)¶
Java
// ============ instanceof模式匹配 (Java 16+) ============
// 旧写法
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// 新写法:直接绑定变量
if (obj instanceof String s) {
System.out.println(s.length());
}
// 还支持在条件中使用
if (obj instanceof String s && s.length() > 5) {
System.out.println("长字符串: " + s);
}
// ============ switch模式匹配 (Java 21正式) ============
// 配合sealed classes实现穷举
public static String describe(Shape shape) {
return switch (shape) {
case Circle c -> "圆形,半径=" + c.radius();
case Rectangle r -> "矩形,面积=" + r.area();
case Triangle t -> "三角形,面积=" + t.area();
// 无需default,因为Shape是sealed的,编译器知道所有子类
};
}
// Guarded Pattern(带条件)
public static String classify(Shape shape) {
return switch (shape) {
case Circle c when c.radius() > 10 -> "大圆";
case Circle c -> "小圆";
case Rectangle r when r.area() > 100 -> "大矩形";
case Rectangle r -> "小矩形";
case Triangle t -> "三角形";
};
}
3.4 Switch表达式(Java 14正式)¶
Java
// 旧写法:传统switch
String result;
switch (day) {
case MONDAY:
case FRIDAY:
result = "工作日";
break;
case SATURDAY:
case SUNDAY:
result = "周末";
break;
default:
result = "其他";
}
// 新写法:switch表达式(箭头语法 + 直接返回值)
String result = switch (day) { // switch表达式,箭头语法直接返回值(Java 14+)
case MONDAY, FRIDAY -> "工作日";
case SATURDAY, SUNDAY -> "周末";
default -> "其他";
};
// 多行用yield返回
int value = switch (status) {
case "active" -> 1;
case "inactive" -> {
log("处理inactive");
yield 0;
}
default -> -1;
};
3.5 Text Blocks(Java 15正式)¶
Java
// 旧写法:字符串拼接地狱
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 25\n" +
"}";
// 新写法:文本块(三引号)
String json = """
{
"name": "Alice",
"age": 25
}
""";
String sql = """
SELECT u.name, u.email, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
ORDER BY o.created_at DESC
""";
String html = """
<html>
<body>
<h1>Hello, %s!</h1>
</body>
</html>
""".formatted("Alice"); // 支持formatted()
4. Java 19-21 LTS(重要突破版本)¶
4.1 虚拟线程 Virtual Threads(Java 21正式)¶
这是Java 21最重要的特性,彻底改变了Java并发编程模型。
Java
// ============ 传统平台线程(OS线程,重量级) ============
// 每个线程约占1MB栈内存,创建/切换开销大
// 典型Web服务器能支撑的并发线程数:数千
// ============ 虚拟线程(用户态线程,超轻量) ============
// 由JVM管理,不直接对应OS线程
// 每个虚拟线程仅占几KB,可轻松创建百万个
// 特别适合IO密集型任务(Web请求、数据库查询、RPC调用)
import java.util.concurrent.*;
public class VirtualThreadDemo {
public static void main(String[] args) throws Exception {
// 方式1:直接创建
Thread vt = Thread.ofVirtual().name("my-vt").start(() -> {
System.out.println("虚拟线程: " + Thread.currentThread());
});
vt.join();
// 方式2:工厂创建
ThreadFactory factory = Thread.ofVirtual().name("worker-", 0).factory();
Thread t = factory.newThread(() -> System.out.println("Hello from VT"));
t.start();
// 方式3:ExecutorService(推荐,生产环境)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 提交100万个任务 — 平台线程不可能做到!
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
final int taskId = i;
futures.add(executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1)); // 模拟IO
return "Task-" + taskId + " done";
}));
}
// 等待所有完成
int count = 0;
for (var future : futures) {
future.get();
count++;
}
System.out.println("完成任务数: " + count);
}
}
}
// 对比:处理10000个HTTP请求
// 平台线程:需要线程池(200个线程),请求排队等待
// 虚拟线程:每个请求一个虚拟线程,IO阻塞时自动让出载体线程
// Spring Boot 3.2+ 启用虚拟线程
// application.yml:
// spring:
// threads:
// virtual:
// enabled: true
4.2 结构化并发 Structured Concurrency(Java 21预览)¶
Java
// 将并发任务视为一个整体(类似try-with-resources管理线程生命周期)
import java.util.concurrent.StructuredTaskScope;
public class StructuredConcurrencyDemo {
record UserProfile(String user, String orders, String recommendations) {}
// 并行获取用户数据:用户信息 + 订单 + 推荐
static UserProfile fetchUserProfile(long userId) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 同时发起三个子任务
var userTask = scope.fork(() -> fetchUser(userId));
var ordersTask = scope.fork(() -> fetchOrders(userId));
var recsTask = scope.fork(() -> fetchRecommendations(userId));
scope.join(); // 等待所有子任务完成
scope.throwIfFailed(); // 任何一个失败则抛异常
return new UserProfile(
userTask.get(), ordersTask.get(), recsTask.get()
);
}
// scope关闭时,未完成的子任务会被自动取消
}
static String fetchUser(long id) throws InterruptedException {
Thread.sleep(100); return "User-" + id;
}
static String fetchOrders(long id) throws InterruptedException {
Thread.sleep(200); return "Orders for " + id;
}
static String fetchRecommendations(long id) throws InterruptedException {
Thread.sleep(150); return "Recs for " + id;
}
}
4.3 Record Patterns(Java 21正式)¶
Java
// 解构Record组件
record Point(int x, int y) {}
record Line(Point start, Point end) {}
// 嵌套解构
static String describeLine(Object obj) {
return switch (obj) {
// 嵌套解构: 匹配Line记录并同时解构其内部两个Point,提取所有坐标分量; .formatted()等价于String.format()
case Line(Point(var x1, var y1), Point(var x2, var y2)) ->
"线段: (%d,%d) → (%d,%d)".formatted(x1, y1, x2, y2);
case Point(var x, var y) ->
"点: (%d,%d)".formatted(x, y);
default -> "未知";
};
}
// if语句中使用
if (obj instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
double length = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
System.out.println("线段长度: " + length);
}
4.4 Sequenced Collections(Java 21正式)¶
Java
// 旧:链表取首尾元素方式不统一
// List: list.get(0) / list.get(list.size()-1)
// Deque: deque.getFirst() / deque.getLast()
// SortedSet: set.first() / set.last()
// 新:统一的SequencedCollection接口
// interface SequencedCollection<E> extends Collection<E> {
// E getFirst();
// E getLast();
// void addFirst(E);
// void addLast(E);
// E removeFirst();
// E removeLast();
// SequencedCollection<E> reversed();
// }
var list = new ArrayList<>(List.of("A", "B", "C"));
System.out.println(list.getFirst()); // "A"
System.out.println(list.getLast()); // "C"
list.addFirst("Z"); // ["Z", "A", "B", "C"]
list.reversed().forEach(System.out::println); // C, B, A, Z
var map = new LinkedHashMap<String, Integer>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
System.out.println(map.firstEntry()); // one=1
System.out.println(map.lastEntry()); // three=3
5. 版本特性速查表¶
| 版本 | LTS | 关键特性 |
|---|---|---|
| 8 | ✅ | Lambda, Stream, Optional, java.time, 函数式接口, 默认方法 |
| 9 | 模块系统(JPMS), JShell, 集合工厂方法(List.of), 接口private方法 | |
| 10 | var局部变量推断, G1并行Full GC | |
| 11 | ✅ | HTTP Client, String新方法, 单文件运行(java Xxx.java), ZGC实验 |
| 12 | Switch表达式(预览), String新方法(indent, transform) | |
| 13 | Text Blocks(预览) | |
| 14 | Records(预览), Pattern Matching instanceof(预览), 有用的NullPointerException | |
| 15 | Sealed Classes(预览), Text Blocks(正式), ZGC/Shenandoah正式 | |
| 16 | Records(正式), Pattern Matching instanceof(正式), Vector API(孵化) | |
| 17 | ✅ | Sealed Classes(正式), switch模式匹配(预览), 移除AOT/Applet |
| 18 | UTF-8为默认字符集, 简单Web服务器 | |
| 19 | 虚拟线程(预览), 结构化并发(孵化), Record Patterns(预览) | |
| 20 | Scoped Values(孵化) | |
| 21 | ✅ | 虚拟线程(正式), Record Patterns(正式), switch模式匹配(正式), Sequenced Collections, 分代ZGC |
✏️ 练习¶
- Java 8:使用Stream API对一组学生数据进行分组统计(按班级分组计算平均分,筛选出TOP3高分学生,拼接所有学生姓名为逗号分隔字符串)
- Java 17:定义sealed interface
Expression(子类:Number、Add、Multiply),使用switch模式匹配实现表达式求值器 - Java 21:使用Virtual Threads + Structured Concurrency并行请求3个模拟API,汇总结果;对比平台线程和虚拟线程分别创建10000个线程的内存占用和耗时
📋 面试要点¶
- Stream vs for循环? — Stream声明式、链式、支持并行,适合集合变换;for命令式,性能更可控,适合简单遍历和需要break/continue的场景
- Lambda捕获变量为什么要effectively final? — Lambda可能延迟执行或在另一个线程执行,可变变量会导致数据竞争和不确定行为
- Record和普通类的区别? — Record不可变(final字段)、自动生成equals/hashCode/toString、不能继承其他类(隐式继承Record)、可实现接口
- 虚拟线程vs平台线程? — 虚拟线程由JVM调度、轻量(KB vs MB)、适合IO密集型、可创建百万级;平台线程对应OS线程、适合CPU密集型
- Sealed Classes的作用? — 限制继承层级,使类型系统更精确;配合模式匹配可编译期穷举检查,防止遗漏分支
- var是动态类型吗? — 不是!Java仍是强类型,var只是编译期类型推断(语法糖),编译后有明确类型
📌 下一步学习:15-Java并发编程进阶 — 深入理解JMM和并发工具