每个Java开发者都有过这样的时刻:面对一段复杂业务逻辑,写了几十行嵌套if-else后陷入迷茫;或是调试NullPointerException到深夜,怀疑人生。但当我们第一次看到JDK源码里的Stream流水线、Optional链式判空、CompletableFuture异步组合时,总会忍不住惊叹——"原来Java代码还能这么写!"
本文不只是简单罗列"优雅代码案例",更会深入每段代码背后的设计思想、底层原理和适用场景,从"知其然"到"知其所以然",帮你理解Java语言演进的核心逻辑,真正掌握写出"让人哇塞"的代码的能力。
一、Lambda表达式:不止是语法糖,更是思维方式的革命
Java 8之前,匿名内部类是实现"传递行为"的唯一方式,但冗长的模板代码让逻辑淹没在语法细节中。Lambda表达式的出现,不仅简化了代码,更推动Java从"命令式编程"向"声明式编程"转变。
1. 先搞懂Lambda的本质:函数式接口的实例
很多人以为Lambda是"匿名内部类的简写",但本质上,Lambda表达式是"函数式接口"的实例——它没有类名、没有方法名,直接传递方法体,是一种"行为即数据"的体现。
(1)函数式接口的定义
函数式接口需满足两个条件:
- 仅包含一个抽象方法(可以包含多个默认方法、静态方法);
- 标注
@FunctionalInterface注解(非强制,但能让编译器校验)。
JDK内置的核心函数式接口:
| 接口名称 | 抽象方法 | 用途 | 示例场景 |
|---|---|---|---|
Runnable |
void run() |
无参数无返回值的行为 | 线程任务 |
Consumer<T> |
void accept(T t) |
接收T类型参数,无返回值 | 集合遍历、数据消费 |
Predicate<T> |
boolean test(T t) |
接收T类型参数,返回布尔值 | 数据过滤 |
Function<T,R> |
R apply(T t) |
接收T类型参数,返回R类型结果 | 数据转换(映射) |
Supplier<T> |
T get() |
无参数,返回T类型结果 | 数据生成(如获取配置) |
(2)Lambda的语法结构
Lambda表达式的语法可拆解为三部分,用"箭头"分隔:
(参数列表) -> { 方法体 }
- 参数列表:与函数式接口的抽象方法参数一致,可省略参数类型(编译器自动推断);若只有一个参数,可省略括号;
- 箭头
->:分隔参数与方法体,固定语法; - 方法体:若只有一行代码,可省略大括号和
return(若有返回值);若多行代码,需用大括号包裹并显式return。
(3)从匿名内部类到Lambda的蜕变
以Comparator排序为例,看Lambda如何简化代码:
// Java 8之前:匿名内部类(23行代码)
List<User> users = Arrays.asList(new User("Alice", 25), new User("Bob", 20));
Collections.sort(users, new Comparator<User>() {
@Override
public int compare(User u1, User u2) {
// 按年龄升序排序
if (u1.getAge() > u2.getAge()) {
return 1;
} else if (u1.getAge() < u2.getAge()) {
return -1;
}
return 0;
}
});
// Java 8之后:Lambda表达式(3行代码)
users.sort((u1, u2) -> u1.getAge() - u2.getAge());
// 更简洁:方法引用(1行代码)
users.sort(Comparator.comparingInt(User::getAge));
这段代码的进化,本质是从"描述排序的每一步操作"(命令式),到"描述排序的规则"(声明式)——我们只关心"按年龄排序",不关心排序的具体实现。
2. Lambda的核心应用场景
(1)简化线程与异步任务
除了原文章中的Thread示例,更常见的是ExecutorService提交任务:
// 传统写法:匿名内部类
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "任务完成";
}
});
// Lambda写法
executor.submit(() -> {
Thread.sleep(1000);
return "任务完成";
});
(2)集合操作的简化
结合List的forEach方法(接收Consumer),遍历集合无需手动写for循环:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 传统for循环
for (String name : names) {
System.out.println(name);
}
// Lambda + forEach
names.forEach(name -> System.out.println(name));
// 方法引用简化
names.forEach(System.out::println);
3. 避坑指南:Lambda的常见误区
-
变量捕获必须是final或effectively final:Lambda中引用的外部变量,不能被修改(否则编译报错),因为Lambda可能在另一个线程执行,变量修改会导致线程安全问题;
// 错误:变量count被Lambda引用后又修改 int count = 0; names.forEach(name -> { System.out.println(name); count++; // 编译报错:Variable used in lambda expression should be final or effectively final }); - 不要用Lambda实现复杂逻辑:Lambda的优势是简洁,若方法体超过3行,建议提取为单独方法,用方法引用调用,避免Lambda变成"臃肿的匿名方法";
- 注意异常处理:Lambda中抛出的受检异常,需在Lambda内部捕获,或函数式接口的抽象方法声明该异常(如
Callable声明throws Exception)。
二、Stream API:数据处理的"工业流水线"
如果说Lambda是"传递行为的工具",那Stream API就是"基于行为处理数据的流水线"。它将数据处理抽象为"创建→中间操作→终端操作"的流程,让复杂数据处理逻辑变得清晰优雅。
1. Stream的核心原理:惰性求值与流水线
(1)Stream的生命周期
Stream的处理过程分为三个阶段,缺一不可:
- 创建阶段:从集合、数组、生成器等数据源创建Stream(如
list.stream()); - 中间操作阶段:对Stream中的元素进行过滤、映射、排序等操作(如
filter、map),中间操作是惰性的——仅记录操作链,不实际遍历元素; - 终端操作阶段:触发Stream的遍历,执行所有中间操作,并返回结果(如
collect、count、forEach),终端操作后Stream不可再使用。
(2)惰性求值的底层逻辑
为什么中间操作是惰性的?因为Stream会优化操作链,避免不必要的计算。例如:
// 案例:查找年龄大于18的第一个用户的名字
String userName = users.stream()
.filter(user -> {
System.out.println("过滤用户:" + user.getName()); // 中间操作
return user.getAge() > 18;
})
.map(User::getName) // 中间操作
.findFirst() // 终端操作
.orElse("未知");
执行结果只会打印"过滤用户:Alice"(假设第一个用户年龄>18),后续用户不会被过滤——因为findFirst是短路终端操作,找到第一个符合条件的元素后就停止遍历。这种优化在大数据量场景下能大幅提升性能。
2. 复杂业务场景实战:从20行到5行
原文章展示了订单分组统计,我们再看一个更贴近电商的场景:计算"未取消的订单中,每个用户的平均订单金额,只保留平均金额>100的用户,按平均金额降序排序"。
(1)传统写法(约25行)
// 1. 过滤未取消的订单
List<Order> validOrders = new ArrayList<>();
for (Order order : allOrders) {
if (order.getStatus() != OrderStatus.CANCELLED) {
validOrders.add(order);
}
}
// 2. 按用户分组,计算每个用户的订单总金额和数量
Map<Long, Double> userTotalAmount = new HashMap<>();
Map<Long, Integer> userOrderCount = new HashMap<>();
for (Order order : validOrders) {
Long userId = order.getUserId();
// 累加总金额
userTotalAmount.put(userId, userTotalAmount.getOrDefault(userId, 0.0) + order.getTotalAmount());
// 累加订单数量
userOrderCount.put(userId, userOrderCount.getOrDefault(userId, 0) + 1);
}
// 3. 计算平均金额,过滤并排序
List<Map.Entry<Long, Double>> resultList = new ArrayList<>();
for (Map.Entry<Long, Double> entry : userTotalAmount.entrySet()) {
Long userId = entry.getKey();
Double total = entry.getValue();
Integer count = userOrderCount.get(userId);
Double avg = total / count;
if (avg > 100) {
resultList.add(new AbstractMap.SimpleEntry<>(userId, avg));
}
}
// 4. 按平均金额降序排序
Collections.sort(resultList, new Comparator<Map.Entry<Long, Double>>() {
@Override
public int compare(Map.Entry<Long, Double> e1, Map.Entry<Long, Double> e2) {
return e2.getValue().compareTo(e1.getValue());
}
});
(2)Stream API写法(5行)
List<Map.Entry<Long, Double>> result = allOrders.stream()
// 1. 过滤未取消的订单
.filter(order -> order.getStatus() != OrderStatus.CANCELLED)
// 2. 按用户分组,计算总金额和数量(用简单对象存储)
.collect(Collectors.groupingBy(
Order::getUserId,
Collectors.mapping(
Order::getTotalAmount,
Collectors.summarizingDouble(Double::doubleValue) // 返回DoubleSummaryStatistics,包含总和、数量、平均等
)
))
// 3. 转换为Entry流,过滤平均金额>100的用户
.entrySet().stream()
.filter(entry -> entry.getValue().getAverage() > 100)
// 4. 按平均金额降序排序
.sorted((e1, e2) -> Double.compare(e2.getValue().getAverage(), e1.getValue().getAverage()))
.collect(Collectors.toList());
这段代码的优势不仅是行数减少,更重要的是逻辑与代码结构完全对齐——每个中间操作对应一个业务步骤,可读性远超嵌套循环。
3. 进阶技巧:并行Stream与性能优化
Stream支持并行处理(parallelStream()),底层使用Fork/Join框架,能利用多核CPU提升大数据量处理性能:
// 并行处理:注意线程安全问题
double totalAmount = allOrders.parallelStream()
.filter(order -> order.getStatus() != OrderStatus.CANCELLED)
.mapToDouble(Order::getTotalAmount)
.sum();
并行Stream的避坑点:
- 线程安全:若中间操作修改外部非线程安全集合(如
ArrayList),会导致数据错乱,需使用线程安全集合或collect而非forEach; - 性能阈值:数据量小时(如<1000元素),并行Stream的线程调度开销可能超过性能提升,反而更慢;
- 避免状态依赖:中间操作不能依赖前一个元素的处理结果(如
map中修改外部变量),并行时元素处理顺序不确定。
三、Optional:从"层层判空"到"链式调用",彻底告别NPE
每个Java开发者都经历过NullPointerException的折磨——为了获取"订单→用户→地址→城市",写了三层嵌套if-else。Optional的出现,不是为了"消灭null",而是强迫开发者"正视null",用更优雅的方式处理null场景。
1. Optional的核心设计:封装"可能为null的值"
Optional本质是一个"容器",它包含两种状态:
- 存在值(Present):容器内有非null的值;
- 空(Empty):容器内没有值(对应原有的null)。
通过这种封装,开发者必须显式处理"值不存在"的场景,避免无意识忽略null导致NPE。
2. Optional的正确使用:从创建到链式调用
(1)创建Optional的三种方式
| 方法 | 用途 | 注意事项 |
|---|---|---|
Optional.of(T t) |
封装非null的值 | 若t为null,直接抛NullPointerException,适合确定值非null的场景 |
Optional.ofNullable(T t) |
封装可能为null的值 | 若t为null,返回Optional.empty(),最常用 |
Optional.empty() |
创建空的Optional | 不直接传入值,适合返回"无结果"的场景 |
错误示例:用Optional.of(null)创建,直接抛NPE:
// 错误:user可能为null,用of会抛NPE
Optional<User> optionalUser = Optional.of(getUserById(1L)); // 若getUserById返回null,报错
// 正确:用ofNullable
Optional<User> optionalUser = Optional.ofNullable(getUserById(1L));
(2)常用方法与链式调用
Optional的核心价值在于链式调用,通过map、flatMap等方法避免嵌套判空,常用方法如下:
| 方法 | 用途 | 返回值类型 |
|---|---|---|
map(Function<T,R>) |
若值存在,对值执行映射操作 | Optional<R> |
flatMap(Function<T,Optional<R>>) |
若值存在,对值执行映射(返回Optional) | Optional<R> |
orElse(T other) |
若值不存在,返回other(other需提前创建) | T |
orElseGet(Supplier<T>) |
若值不存在,执行Supplier获取值 | T |
orElseThrow(Supplier<Exception>) |
若值不存在,抛指定异常 | T |
ifPresent(Consumer<T>) |
若值存在,执行Consumer操作 | void |
实战案例:获取"订单中的用户的收货地址的城市",处理所有null场景:
// 传统写法:三层if-else(12行)
public String getOrderUserCity(Order order) {
if (order != null) {
User user = order.getUser();
if (user != null) {
Address address = user.getShippingAddress();
if (address != null) {
String city = address.getCity();
if (city != null) {
return city;
}
}
}
}
return "未知城市";
}
// Optional写法:链式调用(5行)
public String getOrderUserCity(Order order) {
return Optional.ofNullable(order)
.map(Order::getUser) // 映射到User,返回Optional<User>
.map(User::getShippingAddress) // 映射到Address,返回Optional<Address>
.map(Address::getCity) // 映射到City,返回Optional<String>
.orElse("未知城市"); // 无值时返回默认值
}
(3)map与flatMap的区别
当映射操作的返回值本身是Optional时,需用flatMap避免嵌套Optional(如Optional<Optional<String>>):
// 示例:用户的邮箱可能为null,getEmail返回Optional<String>
public Optional<String> getUserEmail(User user) {
return Optional.ofNullable(user.getEmail());
}
// 若用map,会得到Optional<Optional<String>>
Optional<Optional<String>> nestedEmail = Optional.ofNullable(user)
.map(this::getUserEmail);
// 用flatMap,会"扁平化"为Optional<String>
Optional<String> email = Optional.ofNullable(user)
.flatMap(this::getUserEmail);
3. 避坑指南:Optional的反模式
-
不要用
get()直接获取值:get()在值不存在时会抛NoSuchElementException,相当于回到了NPE的问题,应优先用orElse、orElseGet等方法;// 错误:直接get(),可能抛异常 User user = optionalUser.get(); // 正确:用orElseGet User user = optionalUser.orElseGet(() -> new User("默认用户", 0)); - 不要用Optional封装集合:
Optional<List<User>>是反模式,集合本身可以为空(new ArrayList<>()),无需用Optional封装,应直接返回空集合而非Optional.empty(); - 不要在方法参数中使用Optional:方法参数用Optional会增加调用者的复杂度(需手动创建Optional),应直接接收参数,在方法内部用
ofNullable处理。
四、设计模式:从"if-else地狱"到"优雅扩展"
设计模式的核心是"解耦",让代码符合开闭原则(对扩展开放,对修改关闭)。原文章提到的策略模式和建造者模式,是Java开发中最常用的两种模式,我们深入其底层逻辑和实际应用。
1. 策略模式:彻底消灭if-else地狱
(1)为什么需要策略模式?
当业务逻辑中存在多个"分支判断"(如不同用户类型的折扣计算、不同支付方式的处理),用if-else会导致:
- 代码冗长,可读性差;
- 新增分支需修改原有代码,违反开闭原则;
- 测试困难,需覆盖所有分支。
(2)策略模式的核心结构
策略模式包含三个角色:
- 策略接口(Strategy):定义统一的行为方法(如折扣计算);
- 具体策略(Concrete Strategy):实现策略接口,封装具体逻辑(如VIP折扣、新用户折扣);
- 上下文(Context):持有策略接口的引用,负责选择并执行具体策略。
(3)实战:电商折扣计算系统
以"根据用户类型计算商品折扣"为例,对比if-else和策略模式:
① 传统if-else写法(15行)
public double calculateDiscountPrice(String userType, double originalPrice) {
if ("VIP".equals(userType)) {
// VIP用户8折
return originalPrice * 0.8;
} else if ("MEMBER".equals(userType)) {
// 会员用户9折
return originalPrice * 0.9;
} else if ("NEW".equals(userType)) {
// 新用户95折
return originalPrice * 0.95;
} else if ("EMPLOYEE".equals(userType)) {
// 员工7折
return originalPrice * 0.7;
}
// 默认无折扣
return originalPrice;
}
问题:新增"老用户折扣"时,需新增else if分支,修改原有方法。
② 策略模式写法(Spring环境)
在Spring中,可通过自动注入收集所有策略,实现"零配置扩展":
- 定义策略接口:
@FunctionalInterface // 函数式接口,简化实现
public interface DiscountStrategy {
/**
* 计算折扣后价格
* @param originalPrice 原价
* @return 折扣价
*/
double calculate(double originalPrice);
/**
* 获取策略对应的用户类型(如"VIP")
*/
String getUserType();
}
- 实现具体策略:
// VIP用户策略
@Component
public class VipDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(double originalPrice) {
return originalPrice * 0.8;
}
@Override
public String getUserType() {
return "VIP";
}
}
// 新用户策略
@Component
public class NewUserDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(double originalPrice) {
return originalPrice * 0.95;
}
@Override
public String getUserType() {
return "NEW";
}
}
// 更多策略...
- 上下文类(自动注入所有策略):
@Service
public class DiscountContext {
// 自动注入所有实现DiscountStrategy的Bean,按用户类型分组
private final Map<String, DiscountStrategy> strategyMap;
// Spring构造函数注入:自动收集所有DiscountStrategy实例
public DiscountContext(List<DiscountStrategy> strategyList) {
this.strategyMap = strategyList.stream()
.collect(Collectors.toMap(
DiscountStrategy::getUserType, // Key:用户类型
Function.identity() // Value:策略实例
));
}
// 计算折扣价
public double calculateDiscountPrice(String userType, double originalPrice) {
// 查找对应策略,无则返回原价
return Optional.ofNullable(strategyMap.get(userType))
.map(strategy -> strategy.calculate(originalPrice))
.orElse(originalPrice);
}
}
- 使用方式:
@Autowired
private DiscountContext discountContext;
public void test() {
double vipPrice = discountContext.calculateDiscountPrice("VIP", 1000); // 800.0
double newUserPrice = discountContext.calculateDiscountPrice("NEW", 1000); // 950.0
}
③ 策略模式的优势
- 开闭原则:新增"老用户折扣"时,只需新增
OldUserDiscountStrategy类,无需修改DiscountContext; - 单一职责:每个策略类只负责一种折扣逻辑,代码清晰,易于测试;
- 灵活扩展:支持动态切换策略(如根据活动临时调整折扣)。
2. 建造者模式:解决"对象属性爆炸"问题
当一个类有多个属性(尤其是可选属性)时,传统构造函数会出现"参数爆炸",建造者模式通过链式调用,让对象创建更优雅。
(1)传统构造函数的问题
以Order类为例,包含订单号(必填)、用户ID(必填)、金额(必填)、备注(可选)、支付方式(可选)、收货地址(可选)等属性:
// 传统写法:重载构造函数(冗余)
public class Order {
private String orderNo; // 必填
private Long userId; // 必填
private double amount; // 必填
private String remark; // 可选
private String payType; // 可选
private String address; // 可选
// 全参构造函数(参数太多,顺序易混淆)
public Order(String orderNo, Long userId, double amount, String remark, String payType, String address) {
this.orderNo = orderNo;
this.userId = userId;
this.amount = amount;
this.remark = remark;
this.payType = payType;
this.address = address;
}
// 省略其他重载构造函数...
}
// 使用时:参数顺序易写错,可选参数需传null
Order order = new Order("20240520001", 1L, 99.99, null, "ALIPAY", "北京市朝阳区");
(2)建造者模式的实现(手动编写)
public class Order {
// 成员变量(不变,用final修饰必填参数)
private final String orderNo; // 必填
private final Long userId; // 必填
private final double amount; // 必填
private String remark; // 可选
private String payType; // 可选
private String address; // 可选
// 私有构造函数:仅允许Builder调用
private Order(Builder builder) {
// 校验必填参数
if (builder.orderNo == null || builder.orderNo.isEmpty()) {
throw new IllegalArgumentException("订单号不能为空");
}
if (builder.userId == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
if (builder.amount <= 0) {
throw new IllegalArgumentException("金额必须大于0");
}
this.orderNo = builder.orderNo;
this.userId = builder.userId;
this.amount = builder.amount;
this.remark = builder.remark;
this.payType = builder.payType;
this.address = builder.address;
}
// 静态内部Builder类
public static class Builder {
// 复制Order的成员变量
private String orderNo;
private Long userId;
private double amount;
private String remark;
private String payType;
private String address;
// Builder构造函数:传入必填参数
public Builder(String orderNo, Long userId, double amount) {
this.orderNo = orderNo;
this.userId = userId;
this.amount = amount;
}
// 可选参数的setter方法,返回Builder自身(链式调用)
public Builder remark(String remark) {
this.remark = remark;
return this;
}
public Builder payType(String payType) {
this.payType = payType;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
// 构建Order对象
public Order build() {
return new Order(this);
}
}
// Getter方法(无Setter,保证对象不可变)
// ...省略...
}
// 使用方式:链式调用,清晰优雅
Order order = new Order.Builder("20240520001", 1L, 99.99)
.remark("加急订单")
.payType("ALIPAY")
.address("北京市朝阳区")
.build();
(3)Lombok简化建造者模式
手动编写Builder类繁琐,Lombok的@Builder注解可自动生成Builder,大幅简化代码:
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
@Getter // 自动生成Getter
@Builder // 自动生成Builder类
public class Order {
@NonNull // 标记必填参数,Builder会自动校验非null
private String orderNo;
@NonNull
private Long userId;
@NonNull
private double amount;
// 可选参数(无@NonNull)
private String remark;
private String payType;
private String address;
}
// 使用方式:与手动编写一致
Order order = Order.builder()
.orderNo("20240520001") // 必填参数,不填编译报错
.userId(1L)
.amount(99.99)
.remark("加急订单")
.build();
(4)建造者模式的适用场景
- 多属性对象:当类的属性超过4个,尤其是存在多个可选属性时;
- 不可变对象:通过私有构造函数和Builder,保证对象创建后不可修改(线程安全);
- 复杂对象创建:对象创建需多步逻辑(如校验、默认值设置),可在Builder中封装。
五、并发编程:从"线程安全"到"性能优化"的艺术
Java并发编程一直是难点,但JDK提供的CompletableFuture、ConcurrentHashMap等工具,让异步和线程安全变得优雅。
1. CompletableFuture:异步编程的"瑞士军刀"
传统Future的get()方法会阻塞线程,而CompletableFuture支持异步链式调用,能组合多个异步任务,实现真正的非阻塞编程。
(1)CompletableFuture的核心能力
- 异步执行任务:通过
supplyAsync(有返回值)、runAsync(无返回值)提交异步任务; - 任务组合:支持
thenApply(同步处理结果)、thenCompose(异步处理结果)、thenCombine(合并两个任务结果); - 异常处理:通过
exceptionally、handle优雅处理异步任务的异常; - 多任务协调:支持
allOf(等待所有任务完成)、anyOf(等待任意任务完成)。
(2)实战:电商下单后的异步流程
电商下单后需执行三个异步任务:
- 扣减库存;
- 生成支付单;
- 发送下单成功短信。
传统Future需要手动管理线程和阻塞等待,CompletableFuture可优雅实现:
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Autowired
private SmsService smsService;
// 自定义线程池,避免使用默认线程池
private final ExecutorService executor = Executors.newFixedThreadPool(5);
public void createOrder(Order order) {
// 1. 异步扣减库存(有返回值:是否扣减成功)
CompletableFuture<Boolean> inventoryFuture = CompletableFuture.supplyAsync(() ->
inventoryService.deductStock(order.getProductId(), order.getQuantity()), executor
);
// 2. 异步生成支付单(有返回值:支付单号)
CompletableFuture<String> paymentFuture = CompletableFuture.supplyAsync(() ->
paymentService.createPayment(order.getOrderNo(), order.getAmount()), executor
);
// 3. 库存扣减成功后,异步发送短信(无返回值)
CompletableFuture<Void> smsFuture = inventoryFuture.thenAcceptAsync((deductSuccess) -> {
if (deductSuccess) {
smsService.sendOrderSuccessSms(order.getUserId(), order.getOrderNo());
}
}, executor);
// 4. 等待所有任务完成,处理结果(非阻塞)
CompletableFuture.allOf(inventoryFuture, paymentFuture, smsFuture)
.whenComplete((unused, throwable) -> {
if (throwable != null) {
// 处理异常(如回滚库存)
log.error("下单异步任务失败", throwable);
inventoryService.restoreStock(order.getProductId(), order.getQuantity());
} else {
// 所有任务成功,更新订单状态
try {
Boolean deductSuccess = inventoryFuture.get();
String paymentNo = paymentFuture.get();
order.setStatus(deductSuccess ? OrderStatus.PENDING_PAY : OrderStatus.FAILED);
order.setPaymentNo(paymentNo);
orderRepository.save(order);
} catch (Exception e) {
log.error("获取异步任务结果失败", e);
}
}
});
}
}
(3)CompletableFuture的避坑点
- 自定义线程池:避免使用
ForkJoinPool.commonPool()(默认线程池),高并发下会导致线程耗尽,应创建自定义线程池并指定拒绝策略; - 异常处理:每个异步任务都可能抛异常,需通过
exceptionally或whenComplete处理,避免异常被吞噬; - 结果获取:尽量避免用
get()阻塞获取结果,优先用whenComplete、thenAccept等非阻塞方式。
2. 并发集合:线程安全与性能的平衡
JDK并发包中的集合(ConcurrentHashMap、CopyOnWriteArrayList等),通过精妙的设计实现了"线程安全"与"高性能"的平衡。
(1)ConcurrentHashMap:从分段锁到CAS+synchronized
ConcurrentHashMap是线程安全的HashMap,底层实现经历了两次重大演进:
- Java 7:采用"分段锁(Segment)",将数组分为16个Segment,每个Segment是一个独立的锁,支持16个线程同时写操作;
- Java 8:放弃分段锁,采用"CAS+synchronized",对数组的每个节点(Node)加锁,粒度更细,性能更高。
核心优势:支持并发读写,无需外部同步,常用方法如下:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 1. 原子性put(不存在则添加,存在则不操作)
map.putIfAbsent("key1", 1);
// 2. 原子性计算(避免先get再put的线程安全问题)
map.compute("key1", (k, v) -> v == null ? 1 : v + 1); // 结果:2
// 3. 原子性删除(仅当值匹配时删除)
map.remove("key1", 2); // 删除成功,返回true
(2)CopyOnWriteArrayList:读多写少场景的神器
CopyOnWriteArrayList的核心思想是"写时复制"——写操作(add、remove)会复制底层数组,读操作(get)直接访问原数组,因此读操作无锁,性能极高。
适用场景:读操作远多于写操作(如配置列表、日志列表),代码示例:
CopyOnWriteArrayList<String> configs = new CopyOnWriteArrayList<>();
// 写操作:复制数组(线程安全)
configs.add("timeout=3000");
configs.remove("oldConfig");
// 读操作:无锁,直接访问(性能极高)
for (String config : configs) {
System.out.println(config);
}
// 迭代器:基于快照,不抛出ConcurrentModificationException
Iterator<String> iterator = configs.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
configs.add("newConfig"); // 迭代时修改,不会报错
}
注意事项:写操作会复制数组,因此写多的场景性能差,且内存占用高(复制期间存在两个数组)。
六、结语:代码的修行,是"优雅"与"实用"的平衡
看完这些代码案例,你可能会惊叹"原来Java还能这么写",但真正的代码艺术,不是追求"最炫的语法",而是在"可读性、可维护性、性能"之间找到平衡。
最后,分享几个写出优雅Java代码的习惯:
- 多读源码:JDK源码(如
ArrayList、ConcurrentHashMap)、Spring源码中的优秀实现,是最好的老师; - 善用工具:Lambda、Stream、Optional、Lombok等,让代码更简洁,但不要为了"炫技"而过度使用;
- 关注设计:遇到复杂逻辑时,先思考设计模式(如策略模式解耦if-else),再动手写代码;
- 持续重构:代码写完不是结束,而是开始,定期重构冗余代码,让它"越用越顺手"。
记住:代码是写给人看的,顺便能在机器上运行。十年后再看自己今天写的代码,如果还能轻松理解,甚至觉得"当时考虑得真周到",那你就真正掌握了代码的艺术。
除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接
文章评论