本文旨在介紹Java11相對Java8的新特性,包括Java9與Java10引入的特性。主要是新的語法特性,模塊化開發,以及其餘方面的一些新特性。html
本文涉及到的代碼位於:https://github.com/zhaochuninhefei/study-czhao/tree/master/jdk11-test
或:https://gitee.com/XiaTangShaoBing/study/tree/master/jdk11-test
Java11相對Java8,在語法上的新特性並很少。主要有:java
Java10之後能夠用var定義一個局部變量,不用顯式寫出它的類型。但要注意,被var定義的變量仍然是靜態類型,編譯器會試圖去推斷其類型。python
String strBeforeJava10 = "strBeforeJava10"; var strFromJava10 = "strFromJava10"; System.out.println(strBeforeJava10); System.out.println(strFromJava10);
所以,要注意:mysql
// 例以下面的語句編譯會失敗,"InCompatible types." strFromJava10 = 10;
// 例以下面這些都沒法經過編譯: var testVarWithoutInitial; var testNull = null; var testLamda = () -> System.out.println("test"); var testMethodByLamda = () -> giveMeString(); var testMethod2 = this::giveMeString;
而推薦使用類型推斷的場景有:react
// 以下所示,Map <String,List <Integer >>類型,能夠被簡化爲單個var關鍵字 var testList = new ArrayList<Map<String, List<Integer>>>(); for (var curEle : testList) { // curEle可以被推斷出類型是 Map<String, List<Integer>> if (curEle != null) { curEle.put("test", new ArrayList<>()); } }
// 從Java 11開始,lambda參數也容許使用var關鍵字: Predicate<String> predNotNull = (var a) -> a != null && a.trim().length() > 0; String strAfterFilter = Arrays.stream((new String[]{"a", "", null, "x"})) .filter(predNotNull) .collect(Collectors.joining(",")); System.out.println(strAfterFilter);
完整的演示代碼:git
package jdk11; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; /** * 本地變量類型推斷 * * @author zhaochun */ public class TestCase01TypeInference { public static void main(String[] args) { TestCase01TypeInference me = new TestCase01TypeInference(); me.testVar(); } private void testVar() { // Java10之後能夠用var定義一個局部變量,不用顯式寫出它的類型。 String strBeforeJava10 = "strBeforeJava10"; var strFromJava10 = "strFromJava10"; System.out.println(strBeforeJava10); System.out.println(strFromJava10); // 但要注意,被var定義的變量仍然是靜態類型,編譯器會試圖去推斷其類型。 // 所以不兼容的類型是不能從新賦值的。 // 例以下面的語句編譯會失敗,"InCompatible types." // strFromJava10 = 10; // 只要編譯器沒法推斷出變量類型,就會編譯錯誤! // 例以下面這些都沒法經過編譯: // var testVarWithoutInitial; // var testNull = null; // var testLamda = () -> System.out.println("test"); // var testMethodByLamda = () -> giveMeString(); // var testMethod2 = this::giveMeString; // 局部變量類型推斷能夠用於簡化泛型聲明。以下所示,Map <String,List <Integer >>類型,能夠被簡化爲單個var關鍵字,從而避免大量樣板代碼: var testList = new ArrayList<Map<String, List<Integer>>>(); for (var curEle : testList) { // curEle可以被推斷出類型是 Map<String, List<Integer>> if (curEle != null) { curEle.put("test", new ArrayList<>()); } } // 從Java 11開始,lambda參數也容許使用var關鍵字: Predicate<String> predNotNull = (var a) -> a != null && a.trim().length() > 0; String strAfterFilter = Arrays.stream((new String[]{"a", "", null, "x"})) .filter(predNotNull) .collect(Collectors.joining(",")); System.out.println(strAfterFilter); } private String giveMeString() { return "a string."; } }
Java 9開始引入HttpClient API來處理HTTP請求。 從Java 11開始,這個API正式進入標準庫包。參考網址:http://openjdk.java.net/groups/net/httpclient/intro.html
程序員
HttpClient具備如下特性:github
要發送http請求,首先要使用其構建器建立一個HttpClient。這個構建器可以配置每一個客戶端的狀態:web
一旦構建完成,就可使用HttpClient發送多個請求。算法
HttpRequest是由它的構建器建立的。請求的構建器可用於設置:
HttpRequest構建以後是不可變的,但能夠發送屢次。
請求既能夠同步發送,也能夠異步發送。固然同步的API會致使線程阻塞直到HttpResponse可用。異步API當即返回一個CompletableFuture,當HttpResponse可用時,它將獲取HttpResponse並執行後續處理。
CompletableFuture是Java 8添加的新特性,用於可組合的異步編程。
請求和響應的主體做爲響應式流(具備非阻塞背壓的異步數據流)供外部使用。HttpClient其實是請求正文的訂閱者和響應正文字節的發佈者。BodyHandler接口容許在接收實際響應體以前檢查響應代碼和報頭,並負責建立響應BodySubscriber。
HttpRequest和HttpResponse類型提供了許多便利的工廠方法,用於建立請求發佈者和響應訂閱者,以處理常見的主體類型,如文件、字符串和字節。這些便利的實現要麼累積數據,直到能夠建立更高級別的Java類型(如String),要麼就文件流傳輸數據。BodySubscriber和BodyPublisher接口能夠實現爲自定義反應流處理數據。
HttpRequest和HttpResponse還提供了轉換器,用於將 java.util.concurrent.Flow 的 Publisher/Subscriber 類型轉換爲 HTTP Client的 BodyPublisher/BodySubscriber 類型。
Java HTTP Client支持 HTTP/1.1 和 HTTP/2。默認狀況下,客戶端將使用 HTTP/2 發送請求。發送到尚不支持 HTTP/2 的服務器的請求將自動降級爲 HTTP/1.1。如下是HTTP/2帶來的主要改進:
因爲HTTP/2是默認的首選協議,而且在須要的地方無縫地實現回退到HTTP/1.1,那麼當HTTP/2被更普遍地部署時,Java HTTP客戶端就無需修正它的應用代碼。
https://docs.oracle.com/en/ja...
代碼中請求的網址中,localhost:30001
的相關uri來自工程https://github.com/zhaochuninhefei/study-czhao/tree/master/jdk11-test
。
package jdk11; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.WebSocket; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; /** * HttpClient * * @author zhaochun */ public class TestCase02HttpClient { public static void main(String[] args) throws Exception { TestCase02HttpClient me = new TestCase02HttpClient(); me.testHttpClientGetSync(); me.testHttpClientGetAsync(); me.testHttpClientPost(); // 同一個HttpClient先登陸網站獲取token,再請求受限制資源,從而爬取須要認證的資源 me.testLogin(); // HttpClient支持websocket me.testWebsocket(); } private void testHttpClientGetSync() { var url = "https://openjdk.java.net/"; var request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() .build(); var client = HttpClient.newHttpClient(); try { System.out.println(String.format("send begin at %s", LocalDateTime.now())); // 同步請求 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(String.format("send end at %s", LocalDateTime.now())); System.out.println(String.format("receive response : %s", response.body().substring(0, 10))); } catch (Exception e) { e.printStackTrace(); } } private void testHttpClientGetAsync() { var url = "https://openjdk.java.net/"; var request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() .build(); var client = HttpClient.newHttpClient(); try { System.out.println(String.format("sendAsync begin at %s", LocalDateTime.now())); // 異步請求 client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(stringHttpResponse -> { System.out.println(String.format("receive response at %s", LocalDateTime.now())); return stringHttpResponse.body(); }) .thenAccept(s -> System.out.println(String.format("receive response : %s at %s", s.substring(0, 10), LocalDateTime.now()))); System.out.println(String.format("sendAsync end at %s", LocalDateTime.now())); // 爲了防止異步請求還沒有返回主線程就結束(jvm會退出),這裏讓主線程sleep 10秒 System.out.println("Main Thread sleep 10 seconds start..."); Thread.sleep(10000); System.out.println("Main Thread sleep 10 seconds stop..."); } catch (Exception e) { e.printStackTrace(); } } private void testHttpClientPost() { var url = "http://localhost:30001/jdk11/test/helloByPost"; var request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "text/plain") .POST(HttpRequest.BodyPublishers.ofString("zhangsan")) .build(); var client = HttpClient.newHttpClient(); try { HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.body()); } catch (Exception e) { e.printStackTrace(); } } private void testLogin() throws Exception { var client = HttpClient.newHttpClient(); // 某測試環境用戶登陸URL var urlLogin = "http://x.x.x.x:xxxx/xxx/login"; var requestObj = new HashMap<String, Object>(); requestObj.put("username", "xxxxxx"); requestObj.put("password", "xxxxxxxxxxxxxxxx"); var objectMapper = new ObjectMapper(); var requestBodyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(requestObj); var requestLogin = HttpRequest.newBuilder() .uri(URI.create(urlLogin)) .header("Content-Type", "application/json;charset=UTF-8") .POST(HttpRequest.BodyPublishers.ofString(requestBodyJson)) .build(); HttpResponse<String> responseLogin = client.send(requestLogin, HttpResponse.BodyHandlers.ofString()); // 這裏的登陸網站使用token,而沒有使用session,所以咱們須要從返回的報文主體中查找token信息; // 若是是使用session的網站,這裏須要從響應的headers中查找"set-cookie"從而獲取session id,並在後續請求中,將sid設置到header的Cookie中。 // 如: responseLogin.headers().map().get("set-cookie")獲取cookies,再從中查找sid。 var loginResponse = responseLogin.body(); var mpLoginResponse = objectMapper.readValue(loginResponse, Map.class); var dataLogin = (Map<String, Object>) mpLoginResponse.get("data"); var token = dataLogin.get("token").toString(); // 測試環境獲取某資源的URL var urlGetResource = "http://xxxx:xxxx/xxx/resource"; var requestRes = HttpRequest.newBuilder() .uri(URI.create(urlGetResource)) .header("Content-Type", "application/json;charset=UTF-8") // 注意,token並不是必定設置到header的Authorization中,這取決於網站驗證的方式,也有可能token也放到cookie裏。 // 但對於使用session的網站,sid都是設置在cookie裏的。如: .header("Cookie", "JSESSIONID=" + sid) .header("Authorization", token) .GET() .build(); HttpResponse<String> responseResource = client.send(requestRes, HttpResponse.BodyHandlers.ofString()); var response = responseResource.body(); System.out.println(response); } private void testWebsocket() { var wsUrl = "ws://localhost:30001/ws/test"; var httpClient = HttpClient.newHttpClient(); WebSocket websocketClient = httpClient.newWebSocketBuilder() .buildAsync(URI.create(wsUrl), new WebSocket.Listener() { @Override public void onOpen(WebSocket webSocket) { System.out.println("onOpen : webSocket opened."); webSocket.request(1); } @Override public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) { System.out.println("onText"); webSocket.request(1); return CompletableFuture.completedFuture(data) .thenAccept(System.out::println); } @Override public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) { System.out.println("ws closed with status(" + statusCode + "). cause:" + reason); webSocket.sendClose(statusCode, reason); return null; } @Override public void onError(WebSocket webSocket, Throwable error) { System.out.println("error: " + error.getLocalizedMessage()); webSocket.abort(); } }).join(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } // last參數用於指示websocketClient,本次發送的數據是不是完整消息的最後部分。 // 若是是false,則websocketClient不會把消息發送給websocket後臺的listener,只會把數據緩存起來; // 當傳入true時,會將以前緩存的數據和此次的數據拼接起來一塊兒發送給websocket後臺的listener。 websocketClient.sendText("test1", false); websocketClient.sendText("test2", true); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } websocketClient.sendText("org_all_request", true); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } websocketClient.sendText("employee_all_request", true); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } websocketClient.sendClose(WebSocket.NORMAL_CLOSURE, "Happy ending."); } }
List,Set,Map有了新的加強方法:of
與copyOf
。
List.of根據傳入的參數列表建立一個新的不可變List集合;List.copyOf根據傳入的list對象建立一個不可變副本。
var listImmutable = List.of("a", "b", "c"); var listImmutableCopy = List.copyOf(listImmutable);
因爲拷貝的集合自己就是一個不可變對象,所以拷貝實際上並無建立新的對象,直接使用了原來的不可變對象。
// 結果爲true System.out.println(listImmutable == listImmutableCopy); // 不可變對象不能進行修改 try { listImmutable.add("d"); } catch (Throwable t) { System.out.println("listImmutable can not be modified!"); } try { listImmutableCopy.add("d"); } catch (Throwable t) { System.out.println("listImmutableCopy can not be modified!"); }
若是想快速新建一個可變的集合對象,能夠直接使用以前的不可變集合做爲構造參數,建立一個新的可變集合。
var listVariable = new ArrayList<>(listImmutable); var listVariableCopy = List.copyOf(listVariable);
新建立的可變集合固然是一個新的對象,從這個新對象拷貝出來的不可變副本也是一個新的對象,並非以前的不可變集合。
System.out.println(listVariable == listImmutable); // false System.out.println(listVariable == listVariableCopy); // false System.out.println(listImmutable == listVariableCopy); // false // 新的可變集合固然是能夠修改的 try { listVariable.add("d"); } catch (Throwable t) { System.out.println("listVariable can not be modified!"); } // 可變集合拷貝出來的副本依然是不可變的 try { listVariableCopy.add("d"); } catch (Throwable t) { System.out.println("listVariableCopy can not be modified!"); }
Set的of和copyOf與List相似。
var set = Set.of("a", "c", "r", "e"); var setCopy = Set.copyOf(set); System.out.println(set == setCopy);
但要注意,用of建立不可變Set時,要確保元素不重複,不然運行時會拋出異常: "java.lang.IllegalArgumentException: duplicate element"
try { var setErr = Set.of("a", "b", "a"); } catch (Throwable t) { t.printStackTrace(); }
固然建立可變set後添加劇復元素不會拋出異常,但會被去重
var setNew = new HashSet<>(set); setNew.add("c"); System.out.println(setNew.toString());
Map的of和copyOf與list,set相似,注意of方法的參數列表是依次傳入key和value:
var map = Map.of("a", 1, "b", 2); var mapCopy = Map.copyOf(map); System.out.println(map == mapCopy);
固然也要注意建立不可變Map時,key不能重複
try { var mapErr = Map.of("a", 1, "b", 2, "a", 3); } catch (Throwable t) { t.printStackTrace(); }
package jdk11; import java.util.*; /** * Collection加強 * * @author zhaochun */ public class TestCase03Collection { public static void main(String[] args) { TestCase03Collection me = new TestCase03Collection(); me.test01_of_copyOf(); } private void test01_of_copyOf() { // List,Set,Map有了新的加強方法。 // List.of根據傳入的參數列表建立一個新的不可變List集合; // List.copyOf根據傳入的list對象建立一個不可變副本。 var listImmutable = List.of("a", "b", "c"); var listImmutableCopy = List.copyOf(listImmutable); // 因爲拷貝的集合自己就是一個不可變對象,所以拷貝實際上並無建立新的對象,直接使用了原來的不可變對象。 System.out.println(listImmutable == listImmutableCopy); // 不可變對象不能進行修改 try { listImmutable.add("d"); } catch (Throwable t) { System.out.println("listImmutable can not be modified!"); } try { listImmutableCopy.add("d"); } catch (Throwable t) { System.out.println("listImmutableCopy can not be modified!"); } // 若是想快速新建一個可變的集合對象,能夠直接使用以前的不可變集合做爲構造參數,建立一個新的可變集合。 var listVariable = new ArrayList<>(listImmutable); var listVariableCopy = List.copyOf(listVariable); // 新建立的可變集合固然是一個新的對象,從這個新對象拷貝出來的不可變副本也是一個新的對象,並非以前的不可變集合。 System.out.println(listVariable == listImmutable); System.out.println(listVariable == listVariableCopy); System.out.println(listImmutable == listVariableCopy); // 新的可變集合固然是能夠修改的 try { listVariable.add("d"); } catch (Throwable t) { System.out.println("listVariable can not be modified!"); } // 可變集合拷貝出來的副本依然是不可變的 try { listVariableCopy.add("d"); } catch (Throwable t) { System.out.println("listVariableCopy can not be modified!"); } // Set的of和copyOf與List相似。 var set = Set.of("a", "c", "r", "e"); var setCopy = Set.copyOf(set); System.out.println(set == setCopy); // 但要注意,用of建立不可變Set時,要確保元素不重複,不然運行時會拋出異常: "java.lang.IllegalArgumentException: duplicate element" try { var setErr = Set.of("a", "b", "a"); } catch (Throwable t) { t.printStackTrace(); } // 固然建立可變set後添加劇復元素不會拋出異常,但會被去重 var setNew = new HashSet<>(set); setNew.add("c"); System.out.println(setNew.toString()); // Map的of和copyOf與list,set相似 var map = Map.of("a", 1, "b", 2); var mapCopy = Map.copyOf(map); System.out.println(map == mapCopy); // 固然也要注意建立不可變Map時,key不能重複 try { var mapErr = Map.of("a", 1, "b", 2, "a", 3); } catch (Throwable t) { t.printStackTrace(); } } }
Java8開始引入stream,Java11提供了一些擴展:
注意null與""的區別:
long size1 = Stream.ofNullable(null).count(); System.out.println(size1); // 0 long size2 = Stream.ofNullable("").count(); System.out.println(size2); // 1
dropWhile,對於有序的stream,從頭開始去掉知足條件的元素,一旦遇到不知足元素的就結束
List lst1 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1) .dropWhile(e -> e < 3) .collect(Collectors.toList()); System.out.println(lst1); // [3, 4, 5, 4, 3, 2, 1]
takeWhile,對於有序的stream,從頭開始保留知足條件的元素,一旦遇到不知足的元素就結束
List lst2 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1) .takeWhile(e -> e < 3) .collect(Collectors.toList()); System.out.println(lst2); // [1, 2]
即便把剩下的元素都收集到了無序的set中,但在此以前,stream對象是有序的,所以結果包含了原來stream中最後的[a2]和[a1]:
Set set1 = Stream.of("a1", "a2", "a3", "a4", "a5", "a4", "a3", "a2", "a1") .dropWhile(e -> "a3".compareTo(e) > 0) .collect(Collectors.toSet()); System.out.println(set1); // [a1, a2, a3, a4, a5]
若是先建立一個無序不重複的set集合,set無序更準確的說法是不保證順序不變,事實上是有順序的。
所以這裏會發現,dropWhile仍是按set當前的元素順序斷定的,一旦不知足條件就結束。
Set<String> set = new HashSet<>(); for (int i = 1; i <= 100 ; i++) { set.add("test" + i); } System.out.println(set); Set setNew = set.stream() .dropWhile(s -> "test60".compareTo(s) > 0) .collect(Collectors.toSet()); System.out.println(setNew);
java8裏能夠建立一個無限流,好比下面這個數列,起始值是1,後面每一項都在前一項的基礎上 * 2 + 1,經過limit限制這個流的長度:
Stream<Integer> streamInJava8 = Stream.iterate(1, t -> 2 * t + 1); // 打印出該數列的前十個: 1,3,7,15,31,63,127,255,511,1023 System.out.println(streamInJava8.limit(10).map(Object::toString).collect(Collectors.joining(",")));
從Java9開始,iterate方法能夠添加一個斷定器,例如,限制數的大小不超過1000
Stream<Integer> streamFromJava9 = Stream.iterate(1, t -> t < 1000, t -> 2 * t + 1); // 這裏打印的結果是 1,3,7,15,31,63,127,255,511 System.out.println(streamFromJava9.map(Objects::toString).collect(Collectors.joining(",")));
package jdk11; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Stream加強 * * @author zhaochun */ public class TestCase04Stream { public static void main(String[] args) { TestCase04Stream me = new TestCase04Stream(); me.test01_ofNullable(); me.test02_dropWhile_takeWhile(); me.test03_iterate(); } private void test01_ofNullable() { // 單個參數的Stream構造方法 long size1 = Stream.ofNullable(null).count(); System.out.println(size1); long size2 = Stream.ofNullable("").count(); System.out.println(size2); } private void test02_dropWhile_takeWhile() { // dropWhile 對於有序的stream,從頭開始去掉知足條件的元素,一旦遇到不知足元素的就結束 List lst1 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1) .dropWhile(e -> e < 3) .collect(Collectors.toList()); System.out.println(lst1); // takeWhile 對於有序的stream,從頭開始保留知足條件的元素,一旦遇到不知足的元素就結束 List lst2 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1) .takeWhile(e -> e < 3) .collect(Collectors.toList()); System.out.println(lst2); // 雖然這裏最後把剩下的元素都收集到了無序的set中,但在此以前,stream對象是有序的,所以結果包含了原來stream中最後的[a2]和[a1] Set set1 = Stream.of("a1", "a2", "a3", "a4", "a5", "a4", "a3", "a2", "a1") .dropWhile(e -> "a3".compareTo(e) > 0) .collect(Collectors.toSet()); System.out.println(set1); // 這裏先建立一個無序不重複的set集合,set無序更準確的說法是不保證順序不變,事實上是有順序的。 // 所以這裏會發現,dropWhile仍是按set當前的元素順序斷定的,一旦不知足條件就結束。 Set<String> set = new HashSet<>(); for (int i = 1; i <= 100 ; i++) { set.add("test" + i); } System.out.println(set); Set setNew = set.stream() .dropWhile(s -> "test60".compareTo(s) > 0) .collect(Collectors.toSet()); System.out.println(setNew); } private void test03_iterate() { // java8裏能夠建立一個無限流,好比下面這個數列,起始值是1,後面每一項都在前一項的基礎上 * 2 + 1 Stream<Integer> streamInJava8 = Stream.iterate(1, t -> 2 * t + 1); // 打印出該數列的前十個: 1,3,7,15,31,63,127,255,511,1023 System.out.println(streamInJava8.limit(10).map(Object::toString).collect(Collectors.joining(","))); // 從Java9開始,iterate方法能夠添加一個斷定器,能夠用於限制數列範圍不超過1000 Stream<Integer> streamFromJava9 = Stream.iterate(1, t -> t < 1000, t -> 2 * t + 1); // 這裏打印的結果是 1,3,7,15,31,63,127,255,511 System.out.println(streamFromJava9.map(Objects::toString).collect(Collectors.joining(","))); } }
Optional.of("Hello openJDK11").stream() .flatMap(s -> Arrays.stream(s.split(" "))) .forEach(System.out::println);
System.out.println(Optional.empty() .or(() -> Optional.of("default")) .get());
package jdk11; import java.util.Arrays; import java.util.Optional; /** * Optional加強 * * @author zhaochun */ public class TestCase05Optional { public static void main(String[] args) { TestCase05Optional me = new TestCase05Optional(); me.test01_2stream_default(); } private void test01_2stream_default() { // 能夠將Optional對象直接轉爲stream Optional.of("Hello openJDK11").stream() .flatMap(s -> Arrays.stream(s.split(" "))) .forEach(System.out::println); // 能夠爲Optional對象提供一個默認的Optional對象 System.out.println(Optional.empty() .or(() -> Optional.of("default")) .get()); } }
String方面,針對空白字符(空格,製表符,回車,換行等),提供了一些新的方法。
判斷目標字符串是不是空白字符。如下結果所有爲true
:
// 半角空格 System.out.println(" ".isBlank()); // 全角空格 System.out.println(" ".isBlank()); // 半角空格的unicode字符值 System.out.println("\u0020".isBlank()); // 全角空格的unicode字符值 System.out.println("\u3000".isBlank()); // 製表符 System.out.println("\t".isBlank()); // 回車 System.out.println("\r".isBlank()); // 換行 System.out.println("\n".isBlank()); // 各類空白字符拼接 System.out.println(" \t\r\n ".isBlank());
去除首尾的空白字符:
// 全角空格 + 製表符 + 回車 + 換行 + 半角空格 + <內容> + 全角空格 + 製表符 + 回車 + 換行 + 半角空格 var strTest = " \t\r\n 你好 jdk11 \t\r\n "; // strip 去除兩邊空白字符 System.out.println("[" + strTest.strip() + "]"); // stripLeading 去除開頭的空白字符 System.out.println("[" + strTest.stripLeading() + "]"); // stripTrailing 去除結尾的空白字符 System.out.println("[" + strTest.stripTrailing() + "]");
重複字符串內容,拼接新的字符串:
var strOri = "jdk11"; var str1 = strOri.repeat(1); var str2 = strOri.repeat(3); System.out.println(str1); System.out.println(str2); // repeat傳入參數爲1時,不會建立一個新的String對象,而是直接返回原來的String對象。 System.out.println(str1 == strOri);
lines方法用 r 或 n 或 rn 對字符串切割並返回stream對象:
var strContent = "hello java\rhello jdk11\nhello world\r\nhello everyone"; // lines方法用 \r 或 \n 或 \r\n 對字符串切割並返回stream對象 strContent.lines().forEach(System.out::println); System.out.println(strContent.lines().count());
package jdk11; /** * String加強 * * @author zhaochun */ public class TestCase06String { public static void main(String[] args) { TestCase06String me = new TestCase06String(); me.test01_blank(); me.test02_strip(); me.test03_repeat(); me.test04_lines(); } private void test01_blank() { // 半角空格 System.out.println(" ".isBlank()); // 全角空格 System.out.println(" ".isBlank()); // 半角空格的unicode字符值 System.out.println("\u0020".isBlank()); // 全角空格的unicode字符值 System.out.println("\u3000".isBlank()); // 製表符 System.out.println("\t".isBlank()); // 回車 System.out.println("\r".isBlank()); // 換行 System.out.println("\n".isBlank()); // 各類空白字符拼接 System.out.println(" \t\r\n ".isBlank()); } private void test02_strip() { // 全角空格 + 製表符 + 回車 + 換行 + 半角空格 + <內容> + 全角空格 + 製表符 + 回車 + 換行 + 半角空格 var strTest = " \t\r\n 你好 jdk11 \t\r\n "; // strip 去除兩邊空白字符 System.out.println("[" + strTest.strip() + "]"); // stripLeading 去除開頭的空白字符 System.out.println("[" + strTest.stripLeading() + "]"); // stripTrailing 去除結尾的空白字符 System.out.println("[" + strTest.stripTrailing() + "]"); } private void test03_repeat() { var strOri = "jdk11"; var str1 = strOri.repeat(1); var str2 = strOri.repeat(3); System.out.println(str1); System.out.println(str2); // repeat傳入參數爲1時,不會建立一個新的String對象,而是直接返回原來的String對象。 System.out.println(str1 == strOri); } private void test04_lines() { var strContent = "hello java\rhello jdk11\nhello world\r\nhello everyone"; // lines方法用 \r 或 \n 或 \r\n 對字符串切割並返回stream對象 strContent.lines().forEach(System.out::println); System.out.println(strContent.lines().count()); } }
InputStream提供了一個新的方法transferTo
,將輸入流直接傳輸到輸出流:
inputStream.transferTo(outputStream);
package jdk11; import java.io.*; /** * InputStream加強 * * @author zhaochun */ public class TestCase07InputStream { public static void main(String[] args) { TestCase07InputStream me = new TestCase07InputStream(); me.test01_transferTo(); } private void test01_transferTo() { var filePath = "/home/work/sources/test/jdk11-test/src/main/resources/application.yml"; var tmpFilePath = "/home/work/sources/test/jdk11-test/src/main/resources/application.yml.bk"; File tmpFile = new File(tmpFilePath); if (tmpFile.exists() && tmpFile.isFile()) { tmpFile.delete(); } try(InputStream inputStream = new FileInputStream(filePath); OutputStream outputStream = new FileOutputStream(tmpFilePath)) { // transferTo將 InputStream 的數據直接傳輸給 OutputStream inputStream.transferTo(outputStream); } catch (IOException e) { e.printStackTrace(); } } }
Java9引入了模塊化,Java Platform Module System,java平臺模塊系統,簡稱JPMS。
這裏使用IDEA說明如何基於module進行開發。
使用idea新建工程module-test-jdk11
,並刪除根目錄下的src
目錄。
選擇工程根目錄,右鍵,選擇 new -> module:
next,輸入module名稱:
這裏新建了兩個module,一個叫core
,一個叫main
。
這裏選擇core,在其src目錄下,新建包com.czhao.test.module.core
,並新建classEmployee
:
package com.czhao.test.module.core; import java.util.Objects; /** * @author zhaochun */ public class Employee { private String name; private int level; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return level == employee.level && name.equals(employee.name); } @Override public int hashCode() { return Objects.hash(name, level); } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", level=" + level + '}'; } }
而後在src目錄下,新建module-info.java
(idea支持選擇src -> 右鍵 -> new -> module-info.java):
module core { exports com.czhao.test.module.core; }
這裏表示經過exports把包com.czhao.test.module.core
暴露出去。
core
的module首先,在idea的工程配置中,選擇須要依賴core
的module,在其依賴中添加modulecore
:
而後,在modulemain
的src目錄下新建module-info.java
:
module main { requires core; }
這裏使用requires表示引入對modulecore
的依賴。
而後能夠在main
的src目錄下新建包和對應的類,並使用core
暴露出來的類:
package com.czhao.test.module.main; import com.czhao.test.module.core.Employee; /** * @author zhaochun */ public class AppMain { public static void main(String[] args) { Employee employee = new Employee(); employee.setName("kobe"); employee.setLevel(99); System.out.println(employee); } }
若是沒有依賴core
,這裏是沒法使用core
裏的類Employee
的。
java9以後,部分類庫想在module中使用的話,也須要在module-info.java
引入它們,好比jdbc的相關類庫。
main
的module-info.java
:
module main { requires core; requires java.sql; }
在modulemain
的依賴中添加jdbc驅動包:
而後能夠在代碼中使用JDBC開發:
package com.czhao.test.module.main; import com.czhao.test.module.core.Employee; import java.sql.*; /** * @author zhaochun */ public class AppMain { public static void main(String[] args) { Employee employee = new Employee(); employee.setName("kobe"); employee.setLevel(99); System.out.println(employee); try (Connection connection = DriverManager.getConnection( "jdbc:mysql://localhost:3306/db_jdk11_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false", "root", "xxxxxx"); PreparedStatement ps = connection.prepareStatement("select * from tb_employee")) { ResultSet rs = ps.executeQuery(); while (rs.next()) { System.out.println(rs.getString("employee_name")); } } catch (SQLException e) { e.printStackTrace(); } } }
從Java9到Java11,陸續提供了一些新的工具或功能。
Java提供了一個新的工具jshell
,Java終於能夠像python,scala等語言那樣,交互式演示語法了。
$ /usr/java/jdk-11.0.7+10/bin/jshell | 歡迎使用 JShell -- 版本 11.0.7 | 要大體瞭解該版本, 請鍵入: /help intro jshell> var str1 = "hello world"; str1 ==> "hello world" jshell> System.out.println(str1); hello world jshell>
一個單文件源代碼,即,單獨的java文件,有main方法,且只依賴jdk類庫以及本身文件內部定義的類,能夠直接用java
執行而無需先編譯再執行編譯後的class文件了。
這對於一些簡單的腳本開發是個利好。
zhaochun@zhaochun-T480:/home/work/sources/test/jdk11-test/src/test/java/jdk11$ ll 總用量 44 drwxrwxr-x 2 zhaochun zhaochun 4096 5月 14 15:27 ./ drwxrwxr-x 3 zhaochun zhaochun 4096 5月 12 14:25 ../ -rw-rw-r-- 1 zhaochun zhaochun 2323 5月 14 14:48 TestCase01TypeInference.java -rw-rw-r-- 1 zhaochun zhaochun 9803 5月 14 10:56 TestCase02HttpClient.java -rw-rw-r-- 1 zhaochun zhaochun 3384 5月 14 10:55 TestCase03Collection.java -rw-rw-r-- 1 zhaochun zhaochun 2896 5月 14 15:27 TestCase04Stream.java -rw-rw-r-- 1 zhaochun zhaochun 717 5月 14 11:04 TestCase05Optional.java -rw-rw-r-- 1 zhaochun zhaochun 2220 5月 14 12:54 TestCase06String.java -rw-rw-r-- 1 zhaochun zhaochun 1009 5月 14 13:23 TestCase07InputStream.java zhaochun@zhaochun-T480:/home/work/sources/test/jdk11-test/src/test/java/jdk11$ /usr/java/jdk-11.0.7+10/bin/java TestCase01TypeInference.java strBeforeJava10 strFromJava10 a,x zhaochun@zhaochun-T480:/home/work/sources/test/jdk11-test/src/test/java/jdk11$ ll 總用量 44 drwxrwxr-x 2 zhaochun zhaochun 4096 5月 14 15:27 ./ drwxrwxr-x 3 zhaochun zhaochun 4096 5月 12 14:25 ../ -rw-rw-r-- 1 zhaochun zhaochun 2323 5月 14 14:48 TestCase01TypeInference.java -rw-rw-r-- 1 zhaochun zhaochun 9803 5月 14 10:56 TestCase02HttpClient.java -rw-rw-r-- 1 zhaochun zhaochun 3384 5月 14 10:55 TestCase03Collection.java -rw-rw-r-- 1 zhaochun zhaochun 2896 5月 14 15:27 TestCase04Stream.java -rw-rw-r-- 1 zhaochun zhaochun 717 5月 14 11:04 TestCase05Optional.java -rw-rw-r-- 1 zhaochun zhaochun 2220 5月 14 12:54 TestCase06String.java -rw-rw-r-- 1 zhaochun zhaochun 1009 5月 14 13:23 TestCase07InputStream.java zhaochun@zhaochun-T480:/home/work/sources/test/jdk11-test/src/test/java/jdk11$
能夠看到,該目錄下並未生成class文件。
在Docker容器中運行Java應用程序一直存在一個問題,那就是在容器中運行的JVM程序在設置內存大小和CPU使用率後,會致使應用程序的性能降低。這是由於Java應用程序沒有意識到它正在容器中運行。隨着Java10的發佈,這個問題總算得以解訣,JVM如今能夠識別由容器控制組(cgroups) 設置的約束,能夠在容器中使用內存和CPU約束來直接管理Java應用程序,其中包括:
Unicode 10新增了8518個字符,總計達到了136690個字符。包括56個新的emoji表情符號。
JDK11在java.lang
下增長了4個類來處理:
Java實現了RFC7539中指定的ChaCha20和Poly1305兩種加密算法,代替RC4。
RFC7748定義的密鑰協商方案更高效,更安全,JDK增長了兩個新的接口XECPublicKey
和XECPrivateKey
。
免費的低耗能飛行記錄儀和堆分析儀。
經過JVMTI的SampledObjectAlloc回調提供了一個開銷低的heap分析方式提供一個低開銷的,爲了排錯java應用問題,以及JVM問題的數據收集框架。
但願達到的目標以下:
Flight Recorder 源自飛機的黑盒子。 Flight Recorder 之前是商業版的特性,在java11當中開源出來,它能夠導出事件到文件中,以後能夠用Java Mission Control 來分析。
兩種啓動方式:
java -XX:StartFlightRecording
$ jcmd <pid> JFR.start # 啓動記錄儀 $ jcmd <pid> JFR.dump.filename=recording.jfr # 將記錄內容保存到文件裏 $ jcmd <pid> JFR.stop # 中止記錄儀
查看jfr文件在java11裏沒有辦法,不過在java12裏已經加入了jfr命令,能夠查看jfr文件
JFR是一套集成進入JDK、JVM內部的事件機制框架,經過良好架構和設計的框架,硬件層面的極致優化,生產環境的普遍驗證,它能夠作到極致的可靠和低開銷。在SPECjbb2015等基準測試中,JFR的性能開銷最大不超過1%,因此,工程師能夠基本沒有心理負擔地在大規模分佈式的生產系統使用,這意味着,咱們既能夠隨時主動開啓JFR進行特定診斷,也可讓系統長期運行JFR,用以在複雜環境中進行"After-the-fact"分析。還須要苦惱重現隨機問題嗎? JFR讓問題簡化了不少在保證低開銷的基礎上, JFR提供的能力也使人眼前一亮,例如:咱們無需BCI就能夠進行Object Allocation Profiling, 終於不用擔憂BTrace 之類把進程搞掛了。對鎖競爭、阻塞、延遲,JVM GC、SafePoint 等領域,進行很是細粒度分析。甚至深刻JIT Compiler 內部,全面把握熱點方法、內聯、逆優化等等。JFR提供了標準的Java,C++ 等擴展API,能夠與各類層面的應用進行定製、集成,爲複雜的企業應用棧或者複雜的分佈式應用,提供All-in-One 解決方案。而這一切都是內建在JDK和JVM內部的,並不須要額外的依賴,開箱即用。
Java11新增了兩種垃圾回收器,並改善了Java8開始提供的G1垃圾回收器。
關於Java8到Java11的垃圾回收器,將在後續其餘文章中詳細介紹。
Experimental(實驗性質),生產環境不建議使用
ZGC是Java11最引人矚目的新特性。
啓用方法:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
說明:ZGC, A Scalable Low-Latency Garbage collector( Experimental) ,一個可伸縮的低延時的垃圾回收器。GC暫停時間不會超過10ms,既能處理幾百兆的小堆,也能處理幾個T的大堆。和G1相比,應用吞吐能力不會降低超過15%,爲將來的GC功能和利用colord指針以及Load barriers 優化奠基了基礎。初始只支持64位系統。
ZGC的設計目標是:支持TB級內存容量,暫停時間低(<10ms),對整個程序吞吐量的影響小於15%。未來還能夠擴 展實現機制,以支持很多使人興奮的功能,例如多層堆(即熱對象置於DRAM和冷對象置於NVMe閃存),或壓縮堆。
GC是java主要優點之一。然而,當GC停頓太長,就會開始影響應用的響應時間。消除或者減小GC停頓時長,java將有可能在更普遍的應用場景中成長爲一個更有吸引力的平臺。此外,現代系統中可用內存不斷增加,用戶和程序員但願JVM可以以高效的方式充分利用這些內存,而且無需長時間的GC暫停時間。
ZGC是一個併發,基於region,壓縮型的垃圾收集器,只有root掃描階段會STW,所以GC停頓時間不會隨着堆的增加和存活對象的增加而變長。
垃圾回收器 | 平均等待時間(ms) | 最大等待時間(ms) |
---|---|---|
ZGC | 1.091 | 1.681 |
G1 | 156.806 | 543.846 |
Experimental(實驗性質),生產環境不建議使用
啓用方法:-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
說明:開發一個處理內存分配但不實現任何實際內存回收機制的GC,一旦可用堆內存用完,JVM就會退出,若是有System.gc()
調用,實際上什麼也不會發生(這種場景下和-XX:+DisableExplicitGC
效果同樣), 由於沒有內存回收,這個實現可能會警告用戶嘗試強制GC是徒勞的。
主要用途以下:
-Xmx1g -XX:+UseEpsilonGC
,若是程序有問題,則程序會崩潰。對於G1 GC,相比於JDK8,升級到JDK 11便可免費享受到:並行的Full GC,快速的CardTable掃描,自適應的堆佔用比例調整(IHOP),在併發標記階段的類型卸載等等。這些都是針對G1的不斷加強,其中串行FullGC等甚至是曾經被普遍詬病的短板,你會發現GC配置和調優在JDK11中愈來愈方便。
Java9到Java11,陸續移除了一些類庫或功能。
在java11中移除了不太使用的JavaEE模塊和CORBA技術。
CORBA來自於二十世紀九十年代,Oracle認爲,如今用CORBA開發現代Java應用程序已經沒有意義了,維護CORBA的成本已經超過了保留它帶來的好處。
可是刪除CORBA將使得那些依賴於JDK提供部分CORBAAPI的CORBA實現沒法運行。目前尚未第三方CORBA版本,也不肯定是否會有第三方願意接手CORBA API的維護工做。
在java11中將java9標記廢棄的Java EE及CORBA模塊移除掉,具體以下:
只剩下java.xml, java.xml.crypto.jdk.xml.dom 這幾個模塊。
其中,使用java.lang.invoke.MethodHandles.Lookup.defineClass
來替代移除的sun.miss.Unsafe.defineClass
。
將Javascript引擎標記爲Deprecate,後續版本會移除,有須要的能夠考慮使用開源的GraalVM。
java11中將pack200以及unpack200工具以及java.tiljar中的Pack200 API標記爲Deprecate。由於Pack200主要是用來壓縮jar包的工具,因爲網絡下載速度的提高以及java9引入模塊化系統以後再也不依賴Pack200,所以這個版本將其標記爲Deprecate。