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
, Set
和 Map
這樣的集合,它們的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
例項的副本,因此 list
和 copy
是相同的例項。 但是,如果你複製一個可變列表,那麼 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
複製程式碼
dropWhile
與 takeWhile
方法可以用來決定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版本中包含了更多內容:
- Flow API for reactive programming
- Java Module System
- Application Class Data Sharing
- Dynamic Class-File Constants
- Java REPL (JShell)
- Flight Recorder
- Unicode 10
- G1: Full Parallel Garbage Collector
- ZGC: Scalable Low-Latency Garbage Collector
- Epsilon: No-Op Garbage Collector
- Deprecate the Nashorn JavaScript Engine