Java 11 教程

wangwei_hz發表於2018-09-30

Java 11 教程

原文連結:wangwei.one/posts/921ad…

Java11 已於 2018/09/25 成功釋出,不過目前 絕大多數人 在生產環境仍舊使用的是Java 8。這篇以案例為主的教程涵蓋了從 Java 9 到 Java 11的絕大多數重要的語法與API特性。讓我們開始吧!

區域性變數型別推斷

Java 10引入了一個新的語言關鍵字var,它可以在宣告 區域性變數 時替換型別資訊( 區域性 意味著方法體內的變數宣告)。

Java 10之前,變數的宣告形式如下:

String text = "Hello Java 9";
複製程式碼

現在,你可以使用 var 替換 String 。編譯器將會從變數的賦值中推斷出它的正確型別。在這個例子裡 變數text 即為 String 型別:

var text = "Hello Java 10";
複製程式碼

不同於 Javascript 中的 var 關鍵字,Java中的 var 宣告的變數仍舊是靜態型別。你不能再次賦予另一個與原型別不符的變數值。

var text = "Hello Java 11";
text = 23;  // ERROR: Incompatible types(型別錯誤)
複製程式碼

var 關鍵字還可以與 final 一起使用,意義同之前的版本一樣,表示不可修改。

final var text = "Hello Java 10";
text = "Hello Java 11"; // Cannot assign a value to final variable 'text'
複製程式碼

當編譯器不能正確識別出變數的數值型別時,var將不被允許使用。下面這些程式碼都是沒法編譯的程式碼:

// Cannot infer type:
var a;
var nothing = null;
var lambda = () -> System.out.println("Pity!");
var method = this::someMethod;
複製程式碼

區域性變數型別推斷在與複雜的泛型型別結合時,能放大它的價值。在下面這個例子中,current 是有著一個冗長的資料型別 Map<String, List<Integer>> ,不過它的型別宣告可以被 var 這個關鍵字簡單地替換掉,讓你避免了寫一大竄的型別麻煩事。

var myList = new ArrayList<Map<String, List<Integer>>>();

for (var current : myList) {
    // current is infered to type: Map<String, List<Integer>>
    System.out.println(current);
}
複製程式碼

從Java 11開始,lambda表示式的引數也允許使用var關鍵字,這樣使得你可以為這些引數新增註解標識:

Predicate<String> predicate = (@Nullable var a) -> true;
複製程式碼

Tip:在Intellij IDEA中,你可以在按住CMD / CTRL的同時將滑鼠懸停在變數上,以顯示變數的推斷型別。

HTTP Client

Java 9引入了一個新的孵化HttpClient API來處理HTTP請求。從Java 11開始,這個API已經可以在標準庫 java.net 中使用了。讓我們來探索一下通過這個API我們可以做些什麼。

這個新的 HttpClient 既可以被同步使用,也可以被非同步使用。同步請求將會阻塞當前的執行緒,直到返回響應訊息。BodyHandlers 定義了響應訊息體的型別(e.g string,byte-array 或 file):

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://wangwei.one"))
    .GET()
    .build();
var client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
複製程式碼

同樣的請求也可以被非同步執行。呼叫 sendAsync 方法不會阻塞當前執行緒,並且會返回 CompletableFuture 物件,用來構建非同步執行結果的操作流。

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://wangwei.one"))
    .build();
var client = HttpClient.newHttpClient();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println);
複製程式碼

我們可以省略 .GET() 的呼叫,因為它預設的請求方式。

下面這個列子,我們以POST的方法向指定的URL傳送資料。類似於 BodyHandlers ,你可以使用 BodyPublishers 去定義請求訊息體中你想要傳送的資料型別,例如 strings, byte-arrays,files 或 input-streams:

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://postman-echo.com/post"))
    .header("Content-Type", "text/plain")
    .POST(HttpRequest.BodyPublishers.ofString("Hi there!"))
    .build();
var client = HttpClient.newHttpClient();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());      // 200
複製程式碼

最後這個列子來演示如何通過 BASIC-AUTH 來執行身份認證。

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://postman-echo.com/basic-auth"))
    .build();
var client = HttpClient.newBuilder()
    .authenticator(new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication("postman", "password".toCharArray());
        }
    })
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());      // 200
複製程式碼

Collections

List, SetMap 這樣的集合,它們的API也得到了擴充套件。List.of 可以從給定的引數中建立一個不可變的list。List.copyOf 可以建立一個 list 的不可變副本。

var list = List.of("A", "B", "C");
var copy = List.copyOf(list);
System.out.println(list == copy);   // true
複製程式碼

因為 list 已經是不可變的,所以實際上不需要實際地地去建立 list 例項的副本,因此 listcopy 是相同的例項。 但是,如果你複製一個可變列表,那麼 copy 確定就是一個新例項,因此在改變原始 list 時,要保證沒有副作用產生:

var list = new ArrayList<String>();
var copy = List.copyOf(list);
System.out.println(list == copy);   // false
複製程式碼

當建立不可變的 maps 時,你不需要親自去建立一個完整的 map 集合,你可以直接通過 Map.of 來進行建立:

var map = Map.of("A", 1, "B", 2);
System.out.println(map);    // {B=2, A=1}
複製程式碼

Java 11中的不可變集合仍然使用舊Collection API中的相同介面。 但是,如果嘗試通過新增或刪除元素來修改不可變集合,則會丟擲java.lang.UnsupportedOperationException。 幸運的是,如果你嘗試改變不可變集合,Intellij IDEA會通過檢查發出警告。

Streams

Java8中介紹的Stream也新增了方法。Stream.ofNullable 可以從單個元素中構造一個Stream:

Stream.ofNullable(null).count(); // 0
複製程式碼

dropWhiletakeWhile 方法可以用來決定stream中的哪些元素可以被拋棄:

Stream.of(1, 2, 3, 2, 1)
    .dropWhile(n -> n < 3)
    .collect(Collectors.toList());  // [3, 2, 1]

Stream.of(1, 2, 3, 2, 1)
    .takeWhile(n -> n < 3)
    .collect(Collectors.toList());  // [1, 2]
複製程式碼

如果你對Streams不是很熟悉,你可以看看這篇文章 Java 8 Streams Tutorial.

Optionals

Optionals 也新增了一些非常好用的方法。例如,現在你可以簡單地將 Optionals 轉換為 Streams,或者使用另一個optional作為一個空optional的fallback。

Optional.of("foo").orElseThrow();     // foo
Optional.of("foo").stream().count();  // 1
Optional.ofNullable(null)
    .or(() -> Optional.of("fallback"))
    .get();                           // fallback
複製程式碼

Strings

最基本的類之一 String 新增了一些輔助方法,用以修剪或檢查空格以及對字串進行流化處理:

" ".isBlank();                // true
" Foo Bar ".strip();          // "Foo Bar"
" Foo Bar ".stripTrailing();  // " Foo Bar"
" Foo Bar ".stripLeading();   // "Foo Bar "
"Java".repeat(3);             // "JavaJavaJava"
"A\nB\nC".lines().count();    // 3
複製程式碼

InputStreams

最後但並非最不重要的是,InputStream最終獲得了一個非常有用的方法來將資料傳輸到OutputStream,這是一個在處理原始資料流時非常常見的用例。

var classLoader = ClassLoader.getSystemClassLoader();
var inputStream = classLoader.getResourceAsStream("myFile.txt");
var tempFile = File.createTempFile("myFileCopy", "txt");
try (var outputStream = new FileOutputStream(tempFile)) {
    inputStream.transferTo(outputStream);
}
複製程式碼

其他JVM特性

這些是 - 在我看來 - 從Java 8遷移到11時最有趣的語言新API功能。但是功能列表並沒有在這裡結束。 最新的Java版本中包含了更多內容:

相關文章