Java 26 正式發布!這 3 個新特性,讓程式碼量直接減半
寫了 9 年 Java,我以為自己已經見慣了「版本更新」,直到 Java 26 發布——這次,我真的被驚豔到了。
大家好,我是捲毛。
3 月份 Java 26 正式發布的時候,我正在重構一個老專案。隨手升級了 JDK 試了試新特性,結果一個下午,就幹掉了專案裡將近 40% 的樣板程式碼。
今天這篇,不講虛的,只聊我實際用下來真正提升效率的 3 個新特性。每個都附帶程式碼對比,看完你就能直接用。
之前用 CompletableFuture 寫並發,程式碼是這樣的:
java 代碼解讀 複製代碼// Java 21 之前的寫法 —— 巢狀地獄
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> userService.findById(userId));
CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(() -> orderService.findByUserId(userId));
userFuture.thenCombine(orderFuture, (user, order) -> {
CompletableFuture<List<Coupon>> couponFuture = CompletableFuture.supplyAsync(
() -> couponService.findByUser(user.getId())
);
couponFuture.thenAccept(coupons -> {
// 終於拿到所有資料了...但已經巢狀 3 層了
System.out.println(buildResult(user, order, coupons));
});
});
Java 26 結構化並發正式轉正後:
java 代碼解讀 複製代碼// Java 26 —— 優雅到想哭
try (var scope = StructuredTaskScope.open()) {
var userTask = scope.fork(() -> userService.findById(userId));
var orderTask = scope.fork(() -> orderService.findByUserId(userId));
var couponTask = scope.fork(() -> couponService.findByUser(userId));
scope.join(); // 等所有任務完成
// 直接拿結果,沒有巢狀,沒有回呼
return buildResult(userTask.get(), orderTask.get(), couponTask.get());
}
實際收益:我們專案裡有個聚合介面,之前用 CompletableFuture 寫了 60 多行,改完之後 28 行。更重要的是,例外處理終於清楚了——任何一個子任務失敗,整個 scope 自動關閉,不用再手動寫一堆 exceptionally。
| 特性 | CompletableFuture | StructuredTaskScope |
|---|---|---|
| 錯誤傳播 | 手動處理 | 自動傳播,父任務感知子任務失敗 |
| 任務取消 | 手動管理 | 父任務取消,子任務自動取消 |
| 執行緒洩漏 | 可能洩漏 | scope 關閉時自動清理 |
| 程式碼可讀性 | 巢狀回呼 | 扁平化,順序閱讀 |
捲毛 tips:如果你專案裡有「聚合多個 RPC 呼叫」的場景,結構化並發是首選。配合虛擬執行緒,效能直接起飛。
9 年 Java 開發,ThreadLocal 用了無數次,也踩了無數次坑。記憶體洩漏、執行緒池重用導致資料串台、InheritableThreadLocal 在線程池下不生效……
Java 26 終於把 Scoped Values 扶正了:
java 代碼解讀 複製代碼// 以前 —— ThreadLocal 的痛
private static final ThreadLocal<UserContext> CONTEXT = new ThreadLocal<>();
public void handleRequest(Request req) {
CONTEXT.set(buildContext(req));
try {
doBusiness();
} finally {
CONTEXT.remove(); // 忘了寫就是記憶體洩漏
}
}
// Java 26 —— Scoped Values 的優雅
private static final ScopedValue<UserContext> CONTEXT = ScopedValue.newInstance();
public void handleRequest(Request req) {
ScopedValue.where(CONTEXT, buildContext(req)).run(() -> {
doBusiness();
// scope 結束自動清理,不需要 remove
// 虛擬執行緒中也能正確傳遞
});
}
我踩過的坑分享給你:
去年我們有個線上事故,就是 ThreadLocal + 執行緒池導致的。使用者 A 的請求處理完後,ThreadLocal 沒清理乾淨,執行緒被重用給使用者 B,結果使用者 B 看到了使用者 A 的資料。用 Scoped Values 後,這種問題從根本上就不存在了——值的作用域是程式碼區塊級別,出了 scope 就沒了。
java 代碼解讀 複製代碼// ❌ ThreadLocal:不可變、有洩漏風險、執行緒池下傳遞不可靠
ThreadLocal<String> userId = new ThreadLocal<>();
userId.set("user_001");
executor.submit(() -> {
// 執行緒池重用,可能拿到上一個任務的殘留值
System.out.println(userId.get()); // 可能不是 user_001!
});
// ✅ ScopedValue:不可變、無洩漏、虛擬執行緒友好
static final ScopedValue<String> USER_ID = ScopedValue.newInstance();
ScopedValue.where(USER_ID, "user_001").run(() -> {
executor.submit(() -> {
// 虛擬執行緒中也能正確讀取
System.out.println(USER_ID.get()); // 一定是 user_001
});
});
Java 26 對模式比對做了進一步增強,switch 表達式現在支援 guard 條件和巢狀模式:
java 代碼解讀 複製代碼// 以前 —— 一堆 if-else
public String processShape(Shape shape) {
if (shape instanceof Circle c) {
if (c.radius() > 100) {
return "大圓";
} else {
return "小圓";
}
} else if (shape instanceof Rectangle r) {
if (r.width() == r.height()) {
return "正方形";
} else {
return "長方形";
}
} else if (shape instanceof Triangle t) {
if (t.angle() == 90) {
return "直角三角形";
}
return "普通三角形";
}
return "未知形狀";
}
// Java 26 —— 模式比對 + guard 條件
public String processShape(Shape shape) {
return switch (shape) {
case Circle(double r) when r > 100 -> "大圓";
case Circle(_) -> "小圓";
case Rectangle(double w, double h) when w == h -> "正方形";
case Rectangle(_, _) -> "長方形";
case Triangle(_, _, double angle) when angle == 90 -> "直角三角形";
case Triangle(_, _, _) -> "普通三角形";
};
}
程式碼量直接減半,而且可讀性提升了好幾個檔次。
java 代碼解讀 複製代碼// 支付回呼處理 —— Java 26 寫法
public PaymentResult handleCallback(Callback callback) {
return switch (callback) {
case WechatCallback(var orderId, var amount, var status)
when status == Status.SUCCESS ->
paymentService.confirm(orderId, amount);
case WechatCallback(var orderId, _, var status)
when status == Status.FAILED ->
paymentService.fail(orderId, "微信支付失敗");
case AlipayCallback(var orderId, var amount, var tradeNo)
when tradeNo != null ->
paymentService.confirm(orderId, amount);
case AlipayCallback(var orderId, _, null) ->
paymentService.pending(orderId);
case RefundCallback(var orderId, var refundAmount) ->
refundService.process(orderId, refundAmount);
};
}
如果你的專案還在 Java 17 甚至 Java 8,我的建議是:
升級過程中最容易踩的坑:
sun.misc.Unsafe 相關 API 被移除(很多老套件依賴)--add-opensJava 26 這次更新,說實話,讓我這個寫了 9 年 Java 的老兵重新燃起了熱情。結構化並發解決了非同步程式設計的可讀性,Scoped Values 解決了執行緒上下文傳遞的頑疾,模式比對讓程式碼真正變得優雅。
Java 沒有死,它只是在不緊不慢地變好。
📌 我是捲毛,9 年 Java 開發,持續分享 Java 技術乾貨。
如果覺得這篇文章對你有幫助,點個關注不迷路,後續會持續更新 Java 新特性實戰系列。
有問題歡迎在留言區交流,我會一一回覆!
關注《捲毛的技術筆記》,一起捲出技術力。 🔥