Java SE 9 新增特性

Java SE 9 新增特性

作者:Grey

原文地址:

Java SE 9 新增特性

源碼

源倉庫: Github:java_new_features

鏡像倉庫: GitCode:java_new_features

JShell

JShellJava SE 9新增的一個交互式的編程環境工具。它允許你無需使用類或者方法包裝來執行Java語句。它與Python的解釋器類似,可以直接輸入表達式並查看其執行結果。

在控制檯輸入jshell命令並回車,注:需要配置jdk的環境變量,jdk版本要大於或等於9

基本用法

C:\Users\Young>jshell
|  歡迎使用 JShell -- 版本 17.0.4
|  要大致瞭解該版本, 請鍵入: /help intro

jshell> System.out.println("hello shell");
hello shell

jshell> 1 + 2
$2 ==> 3

jshell> Math.pow(3,2)
$3 ==> 9.0

jshell> void p(String s){System.out.println(s);}
|  已創建 方法 p(String)

jshell> p("hello shell");
hello shell

更多介紹參考:Introduction to JShell

try-with-resources增強

try-with-resourcesJDK 7中一個新的異常處理機制,它能夠很容易地關閉在try-catch語句塊中使用的資源(所有實現了java.lang.AutoCloseable接口和java.io.Closeable的對象都可以是資源)。

try-with-resources聲明在JDK 9已得到改進。如果你已經有一個資源是final或等效於final變量,可以在try-with-resources語句中使用該變量,而無需在try-with-resources語句中聲明一個新變量。

實例

package git.snippets.jdk9;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

/**
 * try-with-resources增強
 *
 * @since 1.9
 */
public class TryWithResourceDemo {
    public static void main(String[] args) throws IOException {
        System.out.println(readDataPreJDK9("test"));
        System.out.println(readDataInJDK9("test"));
    }

    static String readDataPreJDK9(String message) throws IOException {
        Reader inputString = new StringReader(message);
        BufferedReader br = new BufferedReader(inputString);
        try (BufferedReader br1 = br) {
            return br1.readLine();
        }
    }

    static String readDataInJDK9(String message) throws IOException {
        Reader inputString = new StringReader(message);
        BufferedReader br = new BufferedReader(inputString);
        try (br) {
            return br.readLine();
        }
    }
}

readDataPreJDK9方法是Java 9之前的做法,需要在try語句塊中聲明資源br1,然後才能使用它。

Java 9中,我們不需要聲明資源br1就可以使用它,並得到相同的結果。見readDataInJDK9方法。

創建不可變集合

package git.snippets.jdk9;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 創建不可變集合
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2021/11/28
 * @since 9
 */
public class ImmutableListTest {
    static void test() {
        List<String> list1 = List.of("a", "b");
        // 創建了不可變的List,不可以執行add操作
        System.out.println(list1); // true

        // 創建不可變的Set,不可以執行add操作
        Set<String> set = Set.of("ab", "bc");
        System.out.println(set.size());

        // 創建了不可變Map,無法put元素
        Map<String, Integer> map1 = Map.of("a", 2, "b", 3, "c", 4);
        System.out.println(map1);
        Map<String, Integer> map2 = Map.ofEntries(Map.entry("a", 1), Map.entry("b", 2));
        System.out.println(map2);
    }
}

Stream API

API 使用方法
dropWhile 從頭開始,遇到不滿足就結束,收集剩餘的
takeWhile 從頭開始收集,遇到不滿足就結束
iterate 將某個值使用某個方法,直到不滿足給定的條件
ofNullable 如果指定元素爲非 null,則獲取一個元素並生成單個元素流,元素爲 null 則返回一個空流。

示例代碼

package git.snippets.jdk9;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * stream增強
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2021/11/29
 * @since 9
 */
public class StreamEnhanceTest {
    public static void main(String[] args) {
        // dropWhile方法,從頭開始刪除,遇到不滿足的就結束,返回:[3,4,4,0]
        System.out.println(List.of(1, 2, 3, 4, 3, 0).stream().dropWhile(x -> x < 3).collect(Collectors.toList()));
        // takeWhile方法,從頭開始篩選,遇到不滿足的就結束,返回:[1,2]
        System.out.println(List.of(1, 2, 3, 4, 3, 0).stream().takeWhile(x -> x < 3).collect(Collectors.toList()));
        // iterate方法,收集[0,9] 十個數字,然後打印出來
        Stream.iterate(0, x -> x < 10, x -> x + 1).forEach(System.out::println);
//        ofNullable 用法
//        ofNullable 方法可以預防 NullPointerExceptions 異常, 可以通過檢查流來避免 null 值。
//        如果指定元素爲非 null,則獲取一個元素並生成單個元素流,元素爲 null 則返回一個空流。
        long count = Stream.ofNullable(100).count();
        // 非空,返回1
        System.out.println(count);
        count = Stream.ofNullable(null).count();
        // null,返回0
        System.out.println(count);
    }
}

Optional增強

Java SE 9中,可以將Optional轉爲一個Stream,如果該Optional中包含值,那麼就返回包含這個值的Stream,否則返回一個空的StreamStream.empty())。

實例

package git.snippets.jdk9;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Optional增強
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/8/13
 * @since 9
 */
public class OptionalDemo {
    public static void main(String[] args) {
        // Optional的stream方法
        List<Optional<String>> list = Arrays.asList(Optional.empty(), Optional.of("A"), Optional.empty(), Optional.of("B"), Optional.ofNullable(null));
        // jdk 9 之前
        list.stream().flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty()).forEach(System.out::println);
        // jdk 9 優化後
        list.stream().flatMap(Optional::stream).forEach(System.out::println);
    }
}

執行輸出結果爲:

[A, B]
[A, B]

Java SE 9中新增了ifPresentOrElse()方法,如果一個Optional包含值,則對其包含的值調用函數action,即action.accept(value)ifPresentOrElse還有第二個參數emptyAction,如果Optional不包含值,那麼ifPresentOrElse便會調用emptyAction,即emptyAction.run()

示例代碼

package git.snippets.jdk9;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Optional增強
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/8/13
 * @since 9
 */
public class OptionalDemo {
    public static void main(String[] args) {
        Optional<Integer> optional = Optional.of(1);
        optional.ifPresentOrElse(x -> System.out.println("Value: " + x), () -> System.out.println("Not Present."));

        optional = Optional.empty();
        optional.ifPresentOrElse(x -> System.out.println("Value: " + x), () -> System.out.println("Not Present."));

        optional = Optional.ofNullable(null);
        optional.ifPresentOrElse(x -> System.out.println("Value: " + x), () -> System.out.println("Not Present."));
    }
}

執行輸出結果爲

Value: 1
Not Present.
Not Present.

Optional中新增了or(),如果值存在,返回Optional指定的值,否則返回一個預設的值。

示例代碼

package git.snippets.jdk9;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Optional增強
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/8/13
 * @since 9
 */
public class OptionalDemo {
    public static void main(String[] args) {
        // Optional的or方法
        Optional.empty().or(() -> Optional.of("Not Present")).ifPresent(x -> System.out.println("value:" + x));
        Optional.of("hello").or(() -> Optional.of("Not Present")).ifPresent(x -> System.out.println("value:" + x));
    }
}

輸出結果爲

value:Not Present
value:hello

接口私有方法

Java SE 9開始,接口支持private方法,接口中的private無法被子類重寫和調用,但是可以用於內部default方法或者static方法調用。

示例如下

package git.snippets.jdk9;

/**
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2021/11/29
 * @since
 */
public class InterfacePrivateTest {
    public static void main(String[] args) {
        X x = new X();
        x.sleep();
        x.eat();
        x.doXxx();
        A.x();
    }
}

class X implements A {
    @Override
    public void sleep() {
        System.out.println("sleep");
    }
}

interface A {
    void sleep();

    default void eat() {
        sleep();
    }

    default void doXxx() {
        drink();
        x();
    }

    static void x() {
        System.out.println("x");
    }

    private void drink() {
        System.out.println("drink");
        x();
    }
}

Java SE 8中,接口可以有靜態方法的默認實現,例:

public interface Test {
    public static void print() {
        System.out.println("interface print");
    }

    default void pout() {
        System.out.println();
    }
}

Java SE 9中,可以支持private的靜態方法實現,例:

public interface Test {
    private static void print() {
        System.out.println("interface print");
    }

    static void pout() {
        print();
    }
}

自帶HttpClient客戶端(孵化階段)

package git.snippets.jdk9;

import jdk.incubator.http.HttpClient;
import jdk.incubator.http.HttpRequest;
import jdk.incubator.http.HttpResponse;

import java.io.IOException;
import java.net.URI;

/**
 * 注意:添加module-info信息
 * jdk11已經把包移入:java.net.http
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/8/14
 * @since 9
 */
public class HttpClientTestJDK9 {
    public static void main(String[] args) throws IOException, InterruptedException {
        HttpClient client = HttpClient.newHttpClient();
        URI uri = URI.create("http://httpbin.org/get");
        HttpRequest req = HttpRequest.newBuilder(uri).header("accept", "application/json").GET().build();
        HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());
        String body = resp.body();
        System.out.println(body);
    }
}

注:執行上述代碼時候,jdk.incubator.httpclient需要通過module-info.java引入進來

module git.snippets.jdk9 {
    requires jdk.incubator.httpclient;
}

或者在javac的時候,通過

--add-modules jdk.incubator.httpclient 

引入這個模塊。

jdk9中的HttpClient還在jdk.incubator.httpclient包中,jdk11已移動到java.net.http包中

ProcessHandle

Java SE 9ProcessHandle接口的實例標識一個本地進程,它允許查詢進程狀態並管理進程,onExit()方法可用於在某個進程終止時觸發某些操作。

package git.snippets.jdk9;

import java.io.IOException;
import java.util.stream.Collectors;


/**
 * 獲取進程相關信息
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/8/14
 * @since 9
 */
public class ProcessHandlerDemo {
    public static void main(String[] args) throws IOException {
        // 獲取所有未結束的進程信息並打印
        ProcessHandle.allProcesses().filter(ProcessHandle::isAlive).collect(Collectors.toSet()).forEach(s -> System.out.println(s.info().command().get()));

        Runtime rt = Runtime.getRuntime();
        // FIXME 可以替換成你本地的一個進程名稱
        Process p = rt.exec("java.exe");
        ProcessHandle pro = p.toHandle();
        p.onExit().thenRunAsync(() -> System.out.println("程序退出之後執行"));
        pro.supportsNormalTermination();
        if (pro.destroyForcibly()) {
            System.out.println("摧毀進程:" + pro.pid());
        }
        System.out.println(pro.isAlive());
    }
}

VarHandle

Varhandle是對變量或參數定義的變量系列的動態強類型引用,包括靜態字段,非靜態字段,數組元素或堆外數據結構的組件。在各種訪問模式下都支持訪問這些變量,包括簡單的讀/寫訪問,volatile的讀/寫訪問以及CAS (compare-and-set)訪問。簡單來說Variable就是對這些變量進行綁定,通過Varhandle直接對這些變量進行操作。Java SE 9之後,官方推薦使用java.lang.invoke.Varhandle來替代Unsafe大部分功能,對比UnsafeVarhandle有着相似的功能,但會更加安全,並且,在併發方面也提高了不少性能。

代碼示例

package git.snippets.jdk9;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;

/**
 * VarHandle使用
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/8/14
 * @since 9
 */
public class VarHandleDemo {
    public static void main(String[] args) throws Exception {
        Data instance = new Data();
        System.out.println(instance);
        MethodHandles.privateLookupIn(Data.class, MethodHandles.lookup()).findVarHandle(Data.class, "privateVar", int.class).set(instance, 11);
        MethodHandles.privateLookupIn(Data.class, MethodHandles.lookup()).findVarHandle(Data.class, "publicVar", int.class).set(instance, 22);
        MethodHandles.privateLookupIn(Data.class, MethodHandles.lookup()).findVarHandle(Data.class, "protectedVar", int.class).set(instance, 33);
        VarHandle arrayVarHandle = MethodHandles.arrayElementVarHandle(int[].class);
        arrayVarHandle.compareAndSet(instance.arrayData, 0, 1, 111);
        arrayVarHandle.compareAndSet(instance.arrayData, 1, 2, 222);
        arrayVarHandle.compareAndSet(instance.arrayData, 2, 3, 333);
        System.out.println(instance);
    }
}

class Data {
    public int publicVar = 1;
    protected int protectedVar = 2;
    private int privateVar = 3;
    public int[] arrayData = new int[]{1, 2, 3};

    @Override
    public String toString() {
        return "Data{" + "publicVar=" + publicVar + ", protectedVar=" + protectedVar + ", privateVar=" + privateVar + ", arrayData=" + Arrays.toString(arrayData) + '}';
    }
}

輸出結果

Data{publicVar=1, protectedVar=2, privateVar=3, arrayData=[1, 2, 3]}
Data{publicVar=22, protectedVar=33, privateVar=11, arrayData=[111, 222, 333]}

更多VarHandle見:Class VarHandle

StackWalker

Java SE 9以前堆棧遍歷

StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

拋出異常並從中提取堆棧跟蹤信息。

new Exception().printStackTrace();

Java SE 9提供了一種新的API使用遍歷堆棧。

StackWalker stack = StackWalker.getInstance();

如果我們想要遍歷整個堆棧,那隻需要調用forEach()方法:

stack.forEach(System.out::println);

使用示例

package git.snippets.jdk9;

import java.util.Scanner;

/**
 * StackWalker使用
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/8/14
 * @since 9
 */
public class StackWalkerDemo {
    /**
     * Computes the factorial of a number
     *
     * @param n a non-negative integer
     * @return n! = 1 * 2 * . . . * n
     */
    public static int factorial(int n) {
        System.out.println("factorial(" + n + "):");
        StackWalker walker = StackWalker.getInstance();
        walker.forEach(System.out::println);
        int r;
        if (n <= 1) {
            r = 1;
        } else {
            r = n * factorial(n - 1);
        }
        System.out.println("return " + r);
        return r;
    }

    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in)) {
            System.out.print("Enter n: ");
            int n = in.nextInt();
            int result = factorial(n);
            System.out.println(result);
        }
    }
}

運行,輸入n=4,可以看到控制檯打印了堆棧信息

Enter n: 4
factorial(4):
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:22)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.main(StackWalkerDemo.java:37)
factorial(3):
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:22)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:27)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.main(StackWalkerDemo.java:37)
factorial(2):
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:22)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:27)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:27)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.main(StackWalkerDemo.java:37)
factorial(1):
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:22)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:27)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:27)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.factorial(StackWalkerDemo.java:27)
git.snippets.jdk9/git.snippets.jdk9.StackWalkerDemo.main(StackWalkerDemo.java:37)
return 1
return 2
return 6
return 24
24

模塊系統

Java SE 9引入了模塊系統,模塊就是代碼和數據的封裝體。模塊的代碼被組織成多個包,每個包中包含Java類和接口;模塊的數據則包括資源文件和其他靜態信息。

module-info.java文件中,我們可以用新的關鍵詞module來聲明一個模塊。

如上示例中的HttpClient客戶端代碼,

執行代碼時候,jdk.incubator.httpclient需要通過module-info.java引入進來

module git.snippets.jdk9 {
    requires jdk.incubator.httpclient;
}

或者在javac的時候,通過

--add-modules jdk.incubator.httpclient 

更多關於模塊系統的說明見

Understanding Java 9 Modules

Java 9 模塊系統

Multi-Release JAR Files

多版本兼容JAR功能能讓你創建僅在特定版本的Java環境中運行庫程序時選擇使用的class版本。通過--release參數指定編譯版本。

Java SE 9 多版本兼容 JAR 包示例

匿名的內部類支持鑽石操作符

具體參考如下代碼

package git.snippets.jdk9;

/**
 * 匿名類支持鑽石操作符
 *
 * @author <a href="mailto:[email protected]">Grey</a>
 * @date 2022/8/14
 * @since 9
 */
public class DiamondEnhanceDemo {
    public static void main(String[] args) {
        // jdk9之前
        Handler<Integer> preJdk9 = new Handler<Integer>(1) {
            @Override
            public void handle() {
                System.out.println(content);
            }
        };
        // jdk9及以上版本
        Handler<Integer> jdk9Above = new Handler<>(1) {
            @Override
            public void handle() {
                System.out.println(content);
            }
        };
    }


}

abstract class Handler<T> {
    public T content;

    public Handler(T content) {
        this.content = content;
    }

    abstract void handle();
}

其他更新

Java SE 9開始,無法用單個下劃線作爲變量名稱

int _ = 3; // java9 or above , error

Objects.requireNonNullElse方法

String a = Objects.requireNonNullElse(m,"Bc"); // 若m不爲null,則a = m,若m爲null,則a = "Bc"

在命令行參數中,Java SE 9之前指定classpath通過如下參數

-cp

或者

-classpath

Java SE 9新增了

--class-path

更多命令參數,見jeps

更多

Java SE 7及以後各版本新增特性,持續更新中...

參考資料

Java Language Updates

Java Platform, Standard Edition What’s New in Oracle JDK 9

Java 新特性教程

Java 9 新特性

Creating Multi-Release JAR Files in IntelliJ IDEA

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章