Java20的新特性

codecraft發表於2023-03-26
Java語言特性系列

本文主要講述一下Java20的新特性

版本號

java -version
openjdk version "20" 2023-03-21
OpenJDK Runtime Environment (build 20+36-2344)
OpenJDK 64-Bit Server VM (build 20+36-2344, mixed mode, sharing)
從version資訊可以看出是build 20+36

特性列表

JEP 429: Scoped Values (Incubator)

ScopedValue是一種類似ThreadLocal的執行緒內/父子執行緒傳遞變數的更優方案。ThreadLocal提供了一種無需在方法引數上傳遞通用變數的方法,InheritableThreadLocal使得子執行緒可以複製繼承父執行緒的變數。但是ThreadLocal提供了set方法,變數是可變的,另外remove方法很容易被忽略,導致線上程池場景下很容易造成記憶體洩露。ScopedValue則提供了一種不可變、不複製的方案,即不提供set方法,子執行緒不需要複製就可以訪問父執行緒的變數。具體使用如下:

class Server {
  public final static ScopedValue<User> LOGGED_IN_USER = ScopedValue.newInstance();
 
  private void serve(Request request) {
    // ...
    User loggedInUser = authenticateUser(request);
    ScopedValue.where(LOGGED_IN_USER, loggedInUser)
               .run(() -> restAdapter.processRequest(request));
    // ...
  }
}
透過ScopedValue.where可以繫結ScopedValue的值,然後在run方法裡可以使用,方法執行完畢自行釋放,可以被垃圾收集器回收

JEP 432: Record Patterns (Second Preview)

JDK19的JEP 405: Record Patterns (Preview)將Record的模式匹配作為第一次preview
JDK20則作為第二次preview
  • 針對巢狀record的推斷,可以這樣

    record Point(int x, int y) {}
    enum Color { RED, GREEN, BLUE }
    record ColoredPoint(Point p, Color c) {}
    record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
    
    static void printColorOfUpperLeftPoint(Rectangle r) {
      if (r instanceof Rectangle(ColoredPoint(Point p, Color c),
                                 ColoredPoint lr)) {
          System.out.println(c);
      }
    }
  • 整體而言,模式匹配有如下幾種:

    Pattern:
    TypePattern
    ParenthesizedPattern
    RecordPattern
    
    TypePattern:
    LocalVariableDeclaration
    
    ParenthesizedPattern:
    ( Pattern )
    
    RecordPattern:
    ReferenceType RecordStructurePattern
    
    RecordStructurePattern:
    ( [ RecordComponentPatternList ] )
    
    RecordComponentPatternList : 
    Pattern { , Pattern }
  • 針對泛型推斷

    record Box<T>(T t) {}
    
    static void test1(Box<String> bo) {
      if (bo instanceof Box<String>(var s)) {
          System.out.println("String " + s);
      }
    }

    也支援巢狀

    static void test3(Box<Box<String>> bo) {
      if (bo instanceof Box<Box<String>>(Box(var s))) {    
          System.out.println("String " + s);
      }
    }

JEP 433: Pattern Matching for switch (Fourth Preview)

在JDK14JEP 305: Pattern Matching for instanceof (Preview)作為preview
在JDK15JEP 375: Pattern Matching for instanceof (Second Preview)作為第二輪的preview
在JDK16JEP 394: Pattern Matching for instanceof轉正
JDK17引入JEP 406: Pattern Matching for switch (Preview)
JDK18的JEP 420: Pattern Matching for switch (Second Preview)則作為第二輪preview
JDK19的JEP 427: Pattern Matching for switch (Third Preview)作為第三輪preview
JDK20作為第四輪preview
自第三次預覽以來的主要變化是:
  • 針對列舉型別出現無法匹配的時候丟擲MatchException而不是IncompatibleClassChangeError
  • 開關標籤的語法更簡單
  • switch現在支援record泛型的推斷

以前針對null值switch會丟擲異常,需要特殊處理

static void testFooBar(String s) {
    if (s == null) {
        System.out.println("Oops!");
        return;
    }
    switch (s) {
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}

現在可以直接switch

static void testFooBar(String s) {
    switch (s) {
        case null         -> System.out.println("Oops");
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}

case when的支援,以前這麼寫

class Shape {}
class Rectangle extends Shape {}
class Triangle  extends Shape { int calculateArea() { ... } }

static void testTriangle(Shape s) {
    switch (s) {
        case null:
            break;
        case Triangle t:
            if (t.calculateArea() > 100) {
                System.out.println("Large triangle");
                break;
            }
        default:
            System.out.println("A shape, possibly a small triangle");
    }
}

現在可以這麼寫

static void testTriangle(Shape s) {
    switch (s) {
        case null -> 
            { break; }
        case Triangle t
        when t.calculateArea() > 100 ->
            System.out.println("Large triangle");
        case Triangle t ->
            System.out.println("Small triangle");
        default ->
            System.out.println("Non-triangle");
    }
}

針對record泛型的型別推斷:

record MyPair<S,T>(S fst, T snd){};

static void recordInference(MyPair<String, Integer> pair){
    switch (pair) {
        case MyPair(var f, var s) -> 
            ... // Inferred record Pattern MyPair<String,Integer>(var f, var s)
        ...
    }
}

JEP 434: Foreign Function & Memory API (Second Preview)

Foreign Function & Memory (FFM) API包含了兩個incubating API
JDK14的JEP 370: Foreign-Memory Access API (Incubator)引入了Foreign-Memory Access API作為incubator
JDK15的JEP 383: Foreign-Memory Access API (Second Incubator)Foreign-Memory Access API作為第二輪incubator
JDK16的JEP 393: Foreign-Memory Access API (Third Incubator)作為第三輪,它引入了Foreign Linker API (JEP 389)
FFM API在JDK 17的JEP 412: Foreign Function & Memory API (Incubator)作為incubator引入
FFM API在JDK 18的JEP 419: Foreign Function & Memory API (Second Incubator)作為第二輪incubator
JDK19的JEP 424: Foreign Function & Memory API (Preview)則將FFM API作為preview API
JDK20作為第二輪preview

使用示例

 javac --release 20 --enable-preview ... and java --enable-preview ....

// 1. Find foreign function on the C library path
Linker linker          = Linker.nativeLinker();
SymbolLookup stdlib    = linker.defaultLookup();
MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort"), ...);
// 2. Allocate on-heap memory to store four strings
String[] javaStrings = { "mouse", "cat", "dog", "car" };
// 3. Use try-with-resources to manage the lifetime of off-heap memory
try (Arena offHeap = Arena.openConfined()) {
    // 4. Allocate a region of off-heap memory to store four pointers
    MemorySegment pointers = offHeap.allocateArray(ValueLayout.ADDRESS, javaStrings.length);
    // 5. Copy the strings from on-heap to off-heap
    for (int i = 0; i < javaStrings.length; i++) {
        MemorySegment cString = offHeap.allocateUtf8String(javaStrings[i]);
        pointers.setAtIndex(ValueLayout.ADDRESS, i, cString);
    }
    // 6. Sort the off-heap data by calling the foreign function
    radixsort.invoke(pointers, javaStrings.length, MemorySegment.NULL, '\0');
    // 7. Copy the (reordered) strings from off-heap to on-heap
    for (int i = 0; i < javaStrings.length; i++) {
        MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);
        javaStrings[i] = cString.getUtf8String(0);
    }
} // 8. All off-heap memory is deallocated here
assert Arrays.equals(javaStrings, new String[] {"car", "cat", "dog", "mouse"});  // true

JEP 436: Virtual Threads (Second Preview)

在JDK19https://openjdk.org/jeps/425)作為第一次preview
在JDK20作為第二次preview,此版本java.lang.ThreadGroup被永久廢棄

使用示例

void handle(Request request, Response response) {
    var url1 = ...
    var url2 = ...
 
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        var future1 = executor.submit(() -> fetchURL(url1));
        var future2 = executor.submit(() -> fetchURL(url2));
        response.send(future1.get() + future2.get());
    } catch (ExecutionException | InterruptedException e) {
        response.fail(e);
    }
}
 
String fetchURL(URL url) throws IOException {
    try (var in = url.openStream()) {
        return new String(in.readAllBytes(), StandardCharsets.UTF_8);
    }
}

JEP 437: Structured Concurrency (Second Incubator)

在JDK19JEP 428: Structured Concurrency (Incubator)作為第一次incubator
在JDK20作為第二次incubator

JEP 438: Vector API (Fifth Incubator)

JDK16引入了JEP 338: Vector API (Incubator)提供了jdk.incubator.vector來用於向量計算
JDK17進行改進並作為第二輪的incubatorJEP 414: Vector API (Second Incubator)
JDK18的JEP 417: Vector API (Third Incubator)進行改進並作為第三輪的incubator
JDK19JEP 426:Vector API (Fourth Incubator)作為第四輪的incubator
JDK20作為第五輪的incubator

細項解讀

上面列出的是大方面的特性,除此之外還有一些api的更新及廢棄,主要見JDK 20 Release Notes,這裡舉幾個例子。

新增項

移除項

  • Thread.suspend/resume Changed to Throw UnsupportedOperationException JDK-8249627
  • Thread.Stop Changed to Throw UnsupportedOperationException JDK-8289610
  • Improved Control of G1 Concurrent Refinement Threads JDK-8137022

    以下這些引數未來版本移除
    -XX:-G1UseAdaptiveConcRefinement
    
    -XX:G1ConcRefinementGreenZone=buffer-count
    
    -XX:G1ConcRefinementYellowZone=buffer-count
    
    -XX:G1ConcRefinementRedZone=buffer-count
    
    -XX:G1ConcRefinementThresholdStep=buffer-count
    
    -XX:G1ConcRefinementServiceIntervalMillis=msec

廢棄項

完整列表見Java SE 20 deprecated-list

  • java.net.URL Constructors Are Deprecated JDK-8294241

    URL的構造器被廢棄,可以使用URL::of(URI, URLStreamHandler)工廠方法替代

已知問題

  • java.lang.Float.floatToFloat16 and java.lang.Float.float16ToFloat May Return Different NaN Results when Optimized by the JIT Compiler (JDK-8302976, JDK-8289551, JDK-8289552)

    JDK20引入了java.lang.Float.floatToFloat16及java.lang.Float.float16ToFloat方法,在JIT編譯最佳化時可能會返回不同的Nan結果,可以使用如下引數禁用JIT對此的最佳化
    -XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_floatToFloat16,_float16ToFloat

其他事項

  • Disabled TLS_ECDH_* Cipher Suites (JDK-8279164)

    TLS_ECDH_* cipher suites預設被禁用了
  • HTTP Response Input Streams Will Throw an IOException on Interrupt (JDK-8294047)
  • HttpClient Default Keep Alive Time is 30 Seconds (JDK-8297030)

    http1.1及http2的空閒連線的超時時間從1200秒改為30秒

小結

Java20主要有如下幾個特性

doc