中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

剛剛 Java 25 炸裂發布!讓 Java 再次偉大

大家好,我是程序員魚皮。

剛剛,Java 25 正式發布!這是繼 Java 21 之后,又一個 LTS 長期支持版本,也是 Java 開發者們最期待的版本之一。其中有個特性可以說是顛覆了我對 Java 的認知,讓 Java 再次偉大!

那么 Java 25 都發布了哪些新特性?有沒有必要升級?

一篇文章,帶你速通 Java 新特性,學會后又能愉快地和面試官吹牛皮了~

推薦觀看視頻版:

?? 正式特性

這些特性在 Java 25 中正式穩定,可以在生產環境中放心使用。

【實用】Scoped Values 作用域值

如果我問你:怎么在同一個線程內共享數據?

估計你的答案是 ThreadLocal。

但是你有沒有想過,ThreadLocal 存在什么問題?

舉一個典型的 ThreadLocal 使用場景,在同一個請求內獲取用戶信息:

public class UserService {
   private static final ThreadLocal<String> USER_ID = new ThreadLocal<>();
   
   public void processRequest(String userId) {
       USER_ID.set(userId);  // 寫入
       doWork();
       USER_ID.remove();  // 問題:必須手動清理,容易忘記
  }
   
   public void doWork() {
       String userId = USER_ID.get();  // 讀取
       System.out.println("處理用戶: " + userId);
       // 問題:其他代碼可以隨意修改
       USER_ID.set("被篡改的值");
  }
}

這段簡單的代碼其實暗藏玄雞,可以看出 ThreadLocal 的痛點:

  1. 容易內存泄漏:必須手動調用 remove()

  2. 可以隨意修改數據,可能導致不可預期的結果

此外,如果想讓子線程也共享數據,每個子線程都要復制一份數據。如果你使用的是 Java 21 的虛擬線程,1000 個虛擬線程就要復制1000 次,性能很差。

InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("用戶數據");
for (int i = 0; i < 1000; i++) {
   Thread.ofVirtual().start(() -> {
       String data = threadLocal.get(); // 每個線程都有自己的副本
  });
}

現在 Java 25 的 Scoped Values 特性轉正了,能解決 ThreadLocal 的這些問題。

 

什么是 Scoped Values?

Scoped Values 允許方法 在線程內以及子線程間安全高效地共享不可變數據

和傳統的 ThreadLocal 相比,它不僅更安全,而且在虛擬線程環境下的內存開銷要小很多。

Scoped Values 和 ThreadLocal 的寫法很像:

import static java.lang.ScopedValue.where;
?
public class UserService {
   private static final ScopedValue<String> USER_ID = ScopedValue.newInstance();
   
   public void processRequest(String userId) {
       where(USER_ID, userId)  // 寫入并綁定作用域
          .run(this::doWork);
       // 自動清理,無需 remove()
  }
   
   public void doWork() {
       String userId = USER_ID.get();  // 讀取
       System.out.println("處理用戶: " + userId);
  }
}

這段代碼中,我們使用 where().run() 自動管理作用域,出了作用域就自動清理,更安全。

而且作用域一旦綁定,值就不能被修改,避免意外的狀態變更。

和虛擬線程配合使用時,所有虛擬線程共享同一份數據,內存占用更小:

ScopedValue<String> scopedValue = ScopedValue.newInstance();
where(scopedValue, "用戶數據").run(() -> {
   for (int i = 0; i < 1000; i++) {
       Thread.ofVirtual().start(() -> {
           String data = scopedValue.get(); // 所有線程共享同一份數據
      });
  }
});
 

使用方法

1)支持返回值

除了 run() 方法,還可以使用 call() 方法來處理有返回值的場景:

public String processWithResult(String input) {
   return where(CONTEXT, input)
      .call(() -> {
           String processed = doSomeWork();
           return "結果: " + processed;
      });
}

 

2)嵌套作用域

支持在已有作用域內建立新的嵌套作用域:

void outerMethod() {
   where(X, "hello").run(() -> {
       System.out.println(X.get());  // 輸出 "hello"
       where(X, "goodbye").run(() -> {
           System.out.println(X.get());  // 輸出 "goodbye"
      });
       System.out.println(X.get());  // 輸出 "hello"
  });
}

 

3)多值綁定

可以在一個調用中綁定多個 Scoped Values,或者直接用類封裝多個值:

where(USER_ID, userId)
  .where(REQUEST_ID, requestId)
  .where(TENANT_ID, tenantId)
  .run(() -> {
       processRequest();
  });
 

4)和結構化并發配合

Scoped Values 和 Java 結構化并發 API 可以打個配合,子線程自動繼承父線程的作用域值:

void handleRequest() {
   where(USER_ID, getCurrentUserId())
      .run(() -> {
           try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
               var userTask = scope.fork(() -> loadUser());      // 子線程可以訪問 USER_ID
               var ordersTask = scope.fork(() -> loadOrders());  // 子線程可以訪問 USER_ID
               scope.join().throwIfFailed();
               return new Response(userTask.get(), ordersTask.get());
          }
      });
}

不過結構化并發這哥們也挺慘的,過了這么多個版本還沒轉正。等它轉正了,感覺 Java 并發編程的模式也要改變了。

 

使用場景

雖然 Scoped Values 聽起來比 ThreadLocal 更高級,但它不能 100% 替代 ThreadLocal。

如果你要在線程中共享不可變數據、尤其是使用了虛擬線程的場景,建議使用 Scoped Values;但如果線程中共享的數據可能需要更新,那么還是使用 ThreadLocal,要根據實際場景選擇。

 

【實用】模塊導入聲明

模塊導入聲明特性(Module Import Declarations)雖然是首次亮相,但它的設計理念可以追溯到 Java 9 的模塊系統。

模塊系統允許我們將代碼組織成模塊,每個模塊都有明確的依賴關系和導出接口,讓大型應用的架構變得更加清晰和可維護。

模塊導入聲明是在這個基礎上進一步簡化開發體驗。

以前我們使用多個 Java 標準庫的包需要大量的導入語句:

import java.util.Map;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.function.Function;
import java.nio.file.Path;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// ... 還有更多

 

現在可以一行導入整個模塊:

import module java.base;

 

對于聚合模塊(比如 java.se),一次導入可以使用大量包:

import module java.se;  // 導入 100 多個包
?
public class FullFeatureApp {
   // 可以使用整個 Java SE 平臺的所有公開 API
}
 

不過我感覺這個特性會比較有爭議,我記得大廠的 Java 規約中是禁止使用通配符 * 方式導入所有類的,可讀性差、有命名沖突風險、依賴不明確。

而模塊導入的范圍更大,類名沖突可能更嚴重。如果導入多個模塊時遇到了同名類,還要再通過具體導入來解決:

import module java.base;      // 包括 java.util.List 接口
import module java.desktop;   // 包括 java.awt.List 類
?
import java.util.List;        // 明確指定使用 util 包的 List

所以我可能不會用這個特性,純個人偏好。

 

【必備】緊湊源文件和實例主方法

這是我最喜歡的特性,直接打破了外界對于 Java 的刻板印象!

之前不是都說 Java 入門比 Python 難么?一個簡單的 Hello World 程序就要包含類、public、static、方法參數等概念。

傳統的 Hello World 程序:

public class HelloWorld {
   public static void main(String[] args) {
       System.out.println("Hello, World!");
  }
}

用 Python 或者 JavaScript 直接寫一行代碼就完成了:

print("Hello World")

在 Java 25 中,Hello World 程序可以直接簡寫為 3 行代碼!

void main() {
   IO.println("Hello, World!");
}

這么一看和好多語言都有點像啊。。。安能辨我是 Java?

但是你知道,這 3 含代碼的含金量么?你知道 Java 為了支持簡寫成這 3 行代碼付出了多少努力么?

 

新的 IO 類

首先是 Java 25 在 java.lang 包中新增了 IO 類,提供更簡單的控制臺 I/O 操作:

void main() {
   String name = IO.readln("請輸入您的姓名: ");
   IO.print("很高興認識您,");
   IO.println(name);
}

 

IO 類的主要方法包括:

public static void print(Object obj);
public static void println(Object obj);
public static void println();
public static String readln(String prompt);
public static String readln();

 

自動導入 java.base 模塊

在緊湊源文件中,所有 java.base 模塊導出的包都會自動導入,就像有這樣一行代碼:

import module java.base;

也就是說,你可以直接使用 List、Map、Stream 等常用類:

void main() {
   var fruits = List.of("蘋果", "香蕉", "橙子");
   var lengths = fruits.stream()
      .collect(Collectors.toMap(
           fruit -> fruit,
           String::length
      ));
   IO.println("水果及其長度: " + lengths);
}

這樣一來,Java 對初學者更友好,同時也讓有經驗的開發者能快速編寫腳本和小工具。

我覺得這個特性是讓 Java 再次偉大的關鍵,為什么呢?

Java 的核心競爭優勢在于它成熟完善的生態系統,但語法不夠簡潔;現在 Java 只要持續借鑒其他新興編程語言的優秀設計和語法特性、給開發者提供平滑的技術升級路徑,就還是會有很多開發者繼續使用 Java,就不會被別的語言取代。

 

【實用】靈活的構造函數體

這個特性解決的是 Java 自誕生以來就存在的一個限制:構造函數中的 super()this() 調用必須是第一條語句。

這個限制雖然保證了對象初始化的安全性,但可能也會影響我們的編碼。

舉個例子:

class Employee extends Person {
   Employee(String name, int age) {
       super(name, age);  // 必須是第一條語句
       if (age < 18 || age > 67) {
           throw new IllegalArgumentException("員工年齡不符要求");
      }
  }
}

這種寫法的問題是,即使參數不符合要求,也會先調用父類構造函數,做一些可能不必要的工作。

Java 25 打破了這個限制,引入了新的構造函數執行模型,分為兩個階段:

  1. 序言階段:構造函數調用之前的代碼

  2. 尾聲階段:構造函數調用之后的代碼

簡單來說,允許在構造函數調用之前添加語句!

class Employee extends Person {
   String department;
   
   Employee(String name, int age, String department) {
       // 序言階段,可以校驗參數
       if (age < 18 || age > 67) {
           throw new IllegalArgumentException("員工年齡必須在 18-67 之間");
      }        
       // 可以初始化字段
       this.department = department;
       // 驗證通過后,再調用父類構造函數
       super(name, age);
       // 尾聲階段,可以干點兒別的事
       IO.println("新員工 " + name + " 已加入 " + department + " 部門");
  }
}

怎么樣,感覺是不是一直以來背的八股文、做的選擇題崩塌了!

此外,這個特性還能防止父類構造函數調用子類未初始化的方法:

class Base {
   Base() {
       this.show();  // 調用可能被重寫的方法
  }
   void show() {
       IO.println("Base.show()");
  }
}
?
class Yupi extends Base {
   private String message;
   
   Yupi(String message) {
       this.message = message;  // 在 super() 之前初始化
       super();
  }
   
   @Override
   void show() {
       IO.println("消息: " + message);  // message 已經正確初始化了
  }
}

現在,當創建 Yupi 對象時,message 字段會在父類構造函數運行之前就被正確初始化,避免了打印 null 值的問題。

 

限制條件

但是要注意,在序言階段有一些限制:

  1. 不能使用 this 引用(除了字段賦值)

  2. 不能調用實例方法

  3. 只能對未初始化的字段進行賦值

class Example {
   int value;
   String name = "default";  // 有初始化器
   
   Example(int val, String nm) {
       // ? 允許:驗證參數
       if (val < 0) throw new IllegalArgumentException();
       
       // ? 允許:初始化未初始化的字段
       this.value = val;
       
       // ? 不允許:字段已有初始化器
       // this.name = nm;
       
       // ? 不允許:調用實例方法
       // this.helper();
       
       super();
       
       // ? 現在可以正常使用 this 了
       this.name = nm;
       this.helper();
  }
   
   void helper() { /* ... */ }
}

總之,這個特性讓構造函數變得更加靈活和安全,特別適合需要復雜初始化邏輯的場景。

 

【了解】密鑰派生函數 API

隨著量子計算技術的發展,傳統的密碼學算法面臨威脅,后量子密碼學成為必然趨勢。

因此 Java 也順應時代,推出了密鑰派生函數(KDF),這是一種從初始密鑰材料、鹽值等輸入生成新密鑰的加密算法。

簡單來說,你理解為 Java 出了一個新的加密工具類就好了,適用于對密碼進行加強、從主密鑰派生多個子密鑰的場景。

核心是 javax.crypto.KDF 類,提供了兩個主要方法:

  1. deriveKey() 生成 SecretKey 對象

  2. deriveData() 生成字節數組

比如使用 HKDF(HMAC-based Key Derivation Function)算法:

// 創建 HKDF 實例
KDF hkdf = KDF.getInstance("HKDF-SHA256");
?
// 準備初始密鑰材料和鹽值
byte[] initialKeyMaterial = "my-secret-key".getBytes();
byte[] salt = "random-salt".getBytes();
byte[] info = "application-context".getBytes();
?
// 創建 HKDF 參數
AlgorithmParameterSpec params = HKDFParameterSpec.ofExtract()
  .addIKM(initialKeyMaterial)  // 添加初始密鑰材料
  .addSalt(salt)               // 添加鹽值
  .thenExpand(info, 32);       // 擴展為 32 字節
?
// 派生 AES 密鑰
SecretKey aesKey = hkdf.deriveKey("AES", params);
?
// 或者直接獲取字節數據
byte[] derivedData = hkdf.deriveData(params);

 

【了解】緊湊對象頭

了解過 Java 對象結構的同學應該知道,Java 對象除了存儲數據外,還要通過 對象頭 存儲很多額外的信息,比如類型信息、GC 標記、鎖狀態等元數據。

 

如果程序中要創建大量小對象,可能對象頭本身占用的空間都比實際要存儲的數據多了!

比如下面這段代碼:

class Point {
   int x, y;  // 實際數據只有 8 字節
}
?
// 創建一堆 Point 對象
List<Point> points = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
   points.add(new Point(i, i));  // 每個對象實際占用 24 字節!
}

用來存儲傳統的對象頭在 64 位系統上占 16 字節,對于只有 8 字節數據的 Point 來說,開銷確實有點大。

Java 25 將緊湊對象頭特性轉正,把對象頭從 16 字節壓縮到 8 字節,減少了小對象的內存開銷。

但是,緊湊對象頭并不是默認開啟的,需要手動指定。畢竟少存了一些信息,必須考慮到兼容性,比如下面這幾種情況可能會有問題:

  • 使用了 JNI 直接操作對象頭的代碼

  • 依賴特定對象頭布局的調試工具

  • 某些第三方性能分析工具

 

【了解】Shenandoah 分代收集

對于 Java 這種自動垃圾收集的語言,我們經常遇到這種問題:GC 一運行,應用就卡頓。特別是占用大堆內存的應用,傳統的垃圾收集器一跑起來,停頓時間可能多達幾百毫秒甚至幾秒。

Shenandoah 作為 Java 中延遲最低的垃圾收集器,在 Java 25 中 Shenandoah 的分代模式轉為正式特性。

什么是分代垃圾回收呢?

大部分對象都是 “朝生夕死” 的。將堆內存劃分為年輕代和老年代兩個區域,年輕代的垃圾收集可以更加頻繁和高效,因為大部分年輕對象很快就會死亡,收集器可以快速清理掉這些垃圾;而老年代的收集頻率相對較低,減少了對長期存活對象的不必要掃描。

經過大量測試,分代 Shenandoah 在保持超低延遲的同時,還能獲得更好的吞吐量。對于延遲敏感的應用來說,這個改進還是很實用的。

但是 Shenandoah 并不是默認的垃圾收集器,需要手動指定。而且分代模式也不是 Shenandoah 的默認模式,默認情況下 Shenandoah 還是使用單代模式。

 

預覽特性

這些特性仍在預覽階段,可以體驗 但不建議在生產環境使用

【了解】結構化并發

目前我們經常使用 ExecutorService 實現并發,可能會寫下面這種代碼:

// 傳統寫法
Response handle() throws ExecutionException, InterruptedException {
   Future<String> user = executor.submit(() -> findUser());
   Future<Integer> order = executor.submit(() -> fetchOrder());
   
   String theUser = user.get();   // 如果這里異常了
   int theOrder = order.get();    // 這個任務還在跑,造成線程泄漏!
   
   return new Response(theUser, theOrder);
}

上述代碼存在一個問題,如果 findUser() 方法失敗了,fetchOrder() 還在后臺運行,浪費資源。而且如果當前線程被中斷,子任務不會自動取消。

 

什么是結構化并發?

結構化并發就是來解決并發編程中 線程泄露和資源管理 問題的。它在 Java 25 中第 5 次預覽,API 已經成熟,盲猜下個版本就要轉正了。

結構化并發的基本使用方法:

Response handle() throws InterruptedException {
   // 打開新作用域,打開作用域的線程是作用域的所有者。
   try (var scope = StructuredTaskScope.open()) {
       // 使用 fork 方法在作用域中開啟子任務
       var userTask = scope.fork(() -> findUser());
       var orderTask = scope.fork(() -> fetchOrder());
       // 等待所有子任務完成或失敗
       scope.join();
       return new Response(userTask.get(), orderTask.get());
  }
   // 作用域結束時,所有子任務自動清理,不會泄漏
}

這樣就解決了幾個問題:

  • 自動清理:任一任務失敗,其他任務自動取消

  • 異常傳播:主線程被中斷,子任務也會取消

  • 資源管理:可以配合 try-with-resources 保證資源釋放

 

更多用法

Java 25 提供了多種執行策略:

// 1. 默認策略:所有任務都要成功
try (var scope = StructuredTaskScope.open()) {
   // 任一失敗就全部取消
}
?
// 2. 競速策略:任一成功即可
try (var scope = StructuredTaskScope.open(Joiner.anySuccessfulResultOrThrow())) {
   var task1 = scope.fork(() -> callService1());
   var task2 = scope.fork(() -> callService2());
   var task3 = scope.fork(() -> callService3());
   
   // 誰先成功就用誰的結果,其他任務取消
   return scope.join();
}
?
// 3. 收集所有結果(忽略失敗)
try (var scope = StructuredTaskScope.open(Joiner.awaitAll())) {
   // 等待所有任務完成,不管成功失敗
}

結構化并發搭配虛擬線程,可以輕松處理大量并發:

void processLotsOfRequests() throws InterruptedException {
   try (var scope = StructuredTaskScope.open()) {
       // 創建上萬個虛擬線程都沒問題
       for (int i = 0; i < 10000; i++) {
           int requestId = i;
           scope.fork(() -> processRequest(requestId));
      }
       scope.join();  // 等待所有請求處理完成
  }
   // 自動清理,不用擔心線程泄漏
}
 

【了解】基本類型模式匹配

之前 Java 優化過很多次模式匹配,可以利用 switch 和 instanceof 快速進行類型檢查和轉換。

比如:

public String processMessage(Object message) {
   return switch (message) {
       case String text -> "文本消息:" + text;
       case Integer number -> "數字消息:" + number;
       case List<?> list -> "列表消息,包含 " + list.size() + " 個元素";
       case  -> "空消息";
       default -> "未知消息類型";
  };
}

但是目前模式匹配只能用于引用類型,如果你想在 switch 中匹配基本類型,只能用常量,不能綁定變量。

支持基本類型模式匹配后,switch 中可以使用基本類型:

switch (value) {
   case int i when i > 100 -> "大整數: " + i;
   case int i -> "小整數: " + i;
   case float f -> "浮點數: " + f;
   case double d -> "雙精度: " + d;
}

注意這里的 int i,變量 i 綁定了匹配的值,可以直接使用。

而且基本類型模式會檢查轉換是否安全,這比手動寫范圍檢查方便多了!

int largeInt = 1000000;
?
// 檢查能否安全轉換為 byte
if (largeInt instanceof byte b) {
   IO.println("可以安全轉換為 byte: " + b);
} else {
   IO.println("轉換為 byte 會丟失精度");  // 這個會執行
}
?
// 檢查 int 轉 float 是否會丟失精度
int preciseInt = 16777217;  // 2^24 + 1
if (preciseInt instanceof float f) {
   IO.println("轉換為 float 不會丟失精度");
} else {
   IO.println("轉換為 float 會丟失精度");  // 這個會執行
}
 

【了解】Stable Values 穩定值

這個特性對大多數開發者來說應該是沒什么用的。

不信我先考考大家:final 字段有什么問題?

答案是必須在構造時初始化。

舉個例子:

class OrderController {
   private final Logger logger = Logger.create(OrderController.class);
}

這段代碼中,logger 必須立刻初始化,如果創建 logger 很耗時,所有實例都要等待,可能影響啟動性能。

特別是在對象很多、但不是每個都會用到某個字段的場景下,這種強制初始化就很浪費。

但我又想保證不可變性,怎么辦呢?

Stable Values 可以解決上述問題,提供 延遲初始化的不可變性

class OrderController {
   private final StableValue<Logger> logger = StableValue.of();
   
   Logger getLogger() {
       // 只在首次使用時初始化,之后直接返回同一個實例
       return logger.orElseSet(() -> Logger.create(OrderController.class));
  }
}

說白了其實就是包一層。。。

還有更簡潔的寫法,可以在聲明時指定初始化邏輯:

class OrderController {
   private final Supplier<Logger> logger = 
       StableValue.supplier(() -> Logger.create(OrderController.class));
   
   void logOrder() {
       logger.get().info("處理訂單");  // 自動延遲初始化
  }
}

還支持集合的延遲初始化:

class ConnectionPool {
   // 延遲創建連接池,每個連接按需創建
   private static final List<Connection> connections = 
       StableValue.list(POOL_SIZE, index -> createConnection(index));
   
   public static Connection getConnection(int index) {
       return connections.get(index);  // 第一次訪問才創建這個連接
  }
}

重點是,Stable Values 底層使用 JVM 的 @Stable 注解,享受和 final 字段一樣的優化(比如常量折疊),所以不用擔心性能。

這個特性特別適合:

  • 創建成本高的對象

  • 不是每個實例都會用到的字段

  • 需要延遲初始化但又要保證不可變的場景

沒記錯的話這個特性應該是首次亮相,對于追求性能的開發者來說還是挺實用的。

 

其他特性

【了解】移除 32 位 x86 支持

Java 25 正式移除了對 32 位 x86 架構的支持。

簡單來說就是:32 位系統現在用不了 Java 25 了。

不過對絕大多數朋友來說,這個變化應該沒啥影響。

 

【了解】JFR 性能分析增強

JFR(Java Flight Recorder)飛行記錄器是 JDK 內置的低開銷性能監控工具,可以記錄程序運行時的詳細數據。

它在這個版本獲得了幾個重要增強,包括更精確地測量 CPU 使用情況、通過協作式采樣保證了 JVM 安全性、可以更安全地在生產環境開啟分析等。

 

【了解】Vector API(第 10 次孵化)

Vector API 繼續孵化,主要改進:

  1. 更好的數學函數支持:現在通過 FFM API 調用本地數學庫,提高可維護性

  2. Float16 支持增強:在支持的 CPU 上可以自動向量化 16 位浮點運算

  3. VectorShuffle 增強:支持與 MemorySegment 交互

這個 API 對高性能計算、機器學習等領域很重要,尤其是現在 AI 的發展帶火了向量運算。但是我想說真的別再孵化了,等你轉正了,黃花菜都涼了。

 


 

以上就是 Java 25 的新特性了,不知道大家有什么想法,可以在評論區留言分享。

我的評價是:雖然 Java 25 在開發體驗、性能優化和并發編程方面都有進步,雨露均沾,但是目前對于新項目選擇 Java 21 就夠了,老項目還是可以繼續用 Java 8,真沒必要升級到 Java 25。

但是建議大家還是把 Java 8 之后的 Java 新特性都了解一下,拓寬下知識面,面試的時候也能跟面試官多聊幾句。我也把免費 Java 教程和 Java 新特性大全都整理到編程導航上了:

免費 Java 教程 + 新特性大全:

如果本期內容有收獲,記得點贊關注三連支持,我們下期見。

更多編程學習資源

posted @ 2025-09-17 11:00  程序員魚皮  閱讀(2604)  評論(4)    收藏  舉報