Java 26 正式發布!這 3 個新特性,讓程式碼量直接減半

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。

結構化並發的 3 個核心優勢

特性 CompletableFuture StructuredTaskScope
錯誤傳播 手動處理 自動傳播,父任務感知子任務失敗
任務取消 手動管理 父任務取消,子任務自動取消
執行緒洩漏 可能洩漏 scope 關閉時自動清理
程式碼可讀性 巢狀回呼 扁平化,順序閱讀

捲毛 tips:如果你專案裡有「聚合多個 RPC 呼叫」的場景,結構化並發是首選。配合虛擬執行緒,效能直接起飛。


二、Scoped Values 正式上線:ThreadLocal 的終結者

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 就沒了

Scoped Values vs ThreadLocal 對比

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
    });
});

三、模式比對增強:switch 終於像現代語言了

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,我的建議是:

  1. 先升級到 Java 21(LTS),這是目前最穩定的 LTS 版本,虛擬執行緒、模式比對初版都有了
  2. 在 Java 21 上跑穩之後,再評估是否升級到 Java 26,主要看你是否需要結構化並發和 Scoped Values
  3. 不要直接從 Java 8 跳到 Java 26,中間的 API 變更太多,建議分步走

升級過程中最容易踩的坑:

  • sun.misc.Unsafe 相關 API 被移除(很多老套件依賴)
  • 反射存取內部 API 需要額外參數 --add-opens
  • 序列化相關行為有變化

寫在最後

Java 26 這次更新,說實話,讓我這個寫了 9 年 Java 的老兵重新燃起了熱情。結構化並發解決了非同步程式設計的可讀性,Scoped Values 解決了執行緒上下文傳遞的頑疾,模式比對讓程式碼真正變得優雅。

Java 沒有死,它只是在不緊不慢地變好。


📌 我是捲毛,9 年 Java 開發,持續分享 Java 技術乾貨。

如果覺得這篇文章對你有幫助,點個關注不迷路,後續會持續更新 Java 新特性實戰系列。

有問題歡迎在留言區交流,我會一一回覆!

關注《捲毛的技術筆記》,一起捲出技術力。 🔥


原文出處:https://juejin.cn/post/7655226776235065344


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝13   💬2   ❤️1
571
🥈
我愛JS
📝1   ❤️1
71
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登