Java 8 到 Java 21 核心特性实战:10 个高频场景 + 可运行代码
Java 版本迭代频繁,从 Java 8 到 Java 21 新增了数十个实用特性(如 Lambda、Stream、虚拟线程等),但多数开发者仅停留在 "听过" 层面,未真正落地到项目中。本文聚焦10 个工作高频场景,用 "特性说明 + 问题场景 + 实战代码" 的形式,让你快速掌握核心用法,代码直接复制到 IDE 即可运行,同时适配面试 / 项目双需求。
一、场景 1:集合遍历(告别 for 循环,用 Lambda+Stream 简化)
特性说明
Java 8 引入的 Lambda 表达式 + Stream API,彻底简化集合遍历、过滤、排序操作,代码量减少 50%+,可读性更高。
问题场景
遍历列表筛选出 "年龄> 18 的用户",并按年龄升序排序,传统 for 循环代码冗余。
实战代码
java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
// 用户实体类
class User {
private String name;
private int age;
// 构造器+getter
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
public class StreamTraversalDemo {
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User("张三", 22));
userList.add(new User("李四", 17));
userList.add(new User("王五", 25));
userList.add(new User("赵六", 19));
// 1. 筛选年龄>18的用户 + 按年龄升序排序 + 提取姓名列表
List<String> adultNames = userList.stream()
.filter(user -> user.getAge() > 18) // 过滤条件
.sorted(Comparator.comparingInt(User::getAge)) // 排序(方法引用简化)
.map(User::getName) // 提取姓名
.collect(Collectors.toList()); // 收集结果
// 2. 打印结果
System.out.println("成年用户姓名(按年龄排序):" + adultNames);
// 输出:成年用户姓名(按年龄排序):[赵六, 张三, 王五]
}
}
核心亮点
filter:按条件筛选元素,替代传统 if 判断;sorted:支持方法引用(User::getAge),比匿名内部类更简洁;map:提取集合中指定字段,避免循环中手动取值。
二、场景 2:空值处理(用 Optional 杜绝空指针异常)
特性说明
Java 8 引入的 Optional 类,专门解决 "空指针异常(NullPointerException)",通过链式调用替代多层 if (obj != null) 判断。
问题场景
获取用户的地址信息(用户可能为 null,地址也可能为 null),传统写法需嵌套 2 层空判断。
实战代码
java
import java.util.Optional;
// 地址实体类
class Address {
private String city;
public Address(String city) { this.city = city; }
public String getCity() { return city; }
}
public class OptionalDemo {
public static void main(String[] args) {
// 模拟:用户为null / 地址为null / 正常情况
User nullUser = null;
User userWithNullAddr = new User("孙七", 23);
User userWithAddr = new User("周八", 24);
userWithAddr.setAddress(new Address("上海")); // 给用户设置地址
// 1. 安全获取用户地址(无空指针风险)
String city1 = Optional.ofNullable(nullUser) // 允许用户为null
.map(User::getAddress) // 提取地址(用户为null则跳过)
.map(Address::getCity) // 提取城市(地址为null则跳过)
.orElse("默认城市"); // 所有环节为null时返回默认值
String city2 = Optional.ofNullable(userWithNullAddr)
.map(User::getAddress)
.map(Address::getCity)
.orElse("默认城市");
String city3 = Optional.ofNullable(userWithAddr)
.map(User::getAddress)
.map(Address::getCity)
.orElse("默认城市");
System.out.println("城市1:" + city1); // 输出:城市1:默认城市
System.out.println("城市2:" + city2); // 输出:城市2:默认城市
System.out.println("城市3:" + city3); // 输出:城市3:上海
}
}
// 补充 User 类的 address 字段和 getter/setter
class User {
private String name;
private int age;
private Address address;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// getter/setter
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
public String getName() { return name; }
public int getAge() { return age; }
}
核心亮点
ofNullable:支持传入 null 值,避免of方法的空指针风险;- 链式
map:连续提取嵌套字段,无需手动判断每层是否为 null; orElse:设置默认值,替代else分支。
三、场景 3:日期时间处理(用 LocalDateTime 替代 Date)
特性说明
Java 8 引入的 java.time 包(JSR 310),解决了传统 Date 类 "线程不安全、API 混乱" 的问题,提供 LocalDate(日期)、LocalTime(时间)、LocalDateTime(日期时间)等核心类。
问题场景
计算 "当前时间 3 天后的日期""两个日期相差的天数""日期格式化",传统 Date+SimpleDateFormat 需处理时区和线程安全问题。
实战代码
java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
public class DateTimeDemo {
public static void main(String[] args) {
// 1. 获取当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("当前日期时间:" + now); // 输出:2025-10-20T15:30:45.123
// 2. 日期计算:当前时间 +3 天
LocalDateTime threeDaysLater = now.plusDays(3);
System.out.println("3天后日期时间:" + threeDaysLater); // 输出:2025-10-23T15:30:45.123
// 3. 日期格式化(线程安全,无需手动同步)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedNow = now.format(formatter);
System.out.println("格式化后当前时间:" + formattedNow); // 输出:2025-10-20 15:30:45
// 4. 两个日期相差天数
LocalDate date1 = LocalDate.of(2025, 1, 1);
LocalDate date2 = LocalDate.of(2025, 12, 31);
Period period = Period.between(date1, date2);
System.out.println("两个日期相差天数:" + period.getDays()); // 输出:304(实际差值按年/月/日拆分)
}
}
核心亮点
- 线程安全:
DateTimeFormatter替代SimpleDateFormat,无需担心多线程问题; - 链式 API:
plusDays/minusMonths等方法直接计算,无需手动处理毫秒值; - 清晰区分日期 / 时间:
LocalDate仅存日期,LocalTime仅存时间,避免Date类的混淆。
四、场景 4:接口增强(用 default 方法避免接口升级兼容问题)
特性说明
Java 8 允许接口中定义 default 方法(带实现体)和 static 方法,解决了 "接口升级导致所有实现类必须修改" 的痛点。
问题场景
原有接口 Payment 只有 pay() 方法,现在需要新增 refund() 方法,若直接添加抽象方法,所有实现类(如 WechatPay、Alipay)都会报错。
实战代码
java
// 支付接口(含 default 方法,支持升级)
interface Payment {
// 抽象方法(必须由实现类实现)
void pay(double amount);
// default 方法(带默认实现,实现类可选择性重写)
default void refund(double amount) {
System.out.println("默认退款逻辑:退款金额" + amount + "元(未指定支付渠道)");
}
// static 方法(接口工具方法,直接通过接口调用)
static void validateAmount(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("金额必须大于0!");
}
}
}
// 微信支付实现类
class WechatPay implements Payment {
@Override
public void pay(double amount) {
Payment.validateAmount(amount); // 调用接口静态方法校验
System.out.println("微信支付:" + amount + "元");
}
// 可选:重写 default 方法,自定义微信退款逻辑
@Override
public void refund(double amount) {
Payment.validateAmount(amount);
System.out.println("微信退款:" + amount + "元(原路退回微信余额)");
}
}
// 支付宝实现类(不重写 refund 方法,使用默认实现)
class Alipay implements Payment {
@Override
public void pay(double amount) {
Payment.validateAmount(amount);
System.out.println("支付宝支付:" + amount + "元");
}
}
public class DefaultMethodDemo {
public static void main(String[] args) {
Payment wechatPay = new WechatPay();
wechatPay.pay(100); // 输出:微信支付:100.0元
wechatPay.refund(100); // 输出:微信退款:100.0元(原路退回微信余额)
Payment alipay = new Alipay();
alipay.pay(200); // 输出:支付宝支付:200.0元
alipay.refund(200); // 输出:默认退款逻辑:退款金额200.0元(未指定支付渠道)
}
}
核心亮点
- 兼容性:接口新增
default方法,原有实现类无需修改,直接使用默认逻辑; - 灵活性:实现类可根据自身需求重写
default方法; - 工具化:
static方法可作为接口通用工具,避免创建额外工具类。
五、场景 5:虚拟线程(Java 21 新特性,轻量级并发编程)
特性说明
Java 21 正式引入虚拟线程(Virtual Thread),属于 "用户态线程",无需绑定操作系统内核线程,创建成本极低(可创建百万级线程),彻底解决传统线程 "资源占用高、切换成本高" 的问题。
问题场景
需要并发处理 1000 个 HTTP 请求(或 IO 密集型任务),传统线程池(ThreadPoolExecutor)受限于核心线程数,并发效率低;虚拟线程可轻松应对百万级 IO 密集型任务。
实战代码
java
import java.util.concurrent.Executors;
public class VirtualThreadDemo {
public static void main(String[] args) {
// 1. 创建虚拟线程池(Java 21 新增 Executors.newVirtualThreadPerTaskExecutor())
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 2. 提交 1000 个任务(每个任务模拟 1 秒 IO 等待)
for (int i = 0; i < 1000; i++) {
int taskId = i;
executor.submit(() -> {
// 模拟 IO 任务(如 HTTP 请求、数据库查询)
try {
Thread.sleep(1000); // 睡眠 1 秒,模拟 IO 等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
System.out.println("虚拟线程完成任务:" + taskId + ",线程名:" + Thread.currentThread().getName());
});
}
} // 自动关闭线程池,无需手动 shutdown()
System.out.println("所有任务提交完成,等待执行...");
// 输出:1 秒后批量打印 1000 个任务完成信息(虚拟线程并发执行,无阻塞)
}
}
核心亮点
- 低资源消耗:虚拟线程创建成本仅为传统线程的千分之一,支持百万级并发;
- 简化 API:
newVirtualThreadPerTaskExecutor()一键创建虚拟线程池,无需配置核心线程数; - 自动管理:try-with-resources 语法自动关闭线程池,避免资源泄漏;
- 适配 IO 密集型:虚拟线程在 IO 等待时会自动 "挂起",释放资源给其他线程,大幅提升并发效率。