Java11相對Java8的新特性

1、背景

本文旨在介紹Java11相對Java8的新特性,包括Java9與Java10引入的特性。主要是新的語法特性,模塊化開發,以及其餘方面的一些新特性。html

本文涉及到的代碼位於: https://github.com/zhaochuninhefei/study-czhao/tree/master/jdk11-test
或: https://gitee.com/XiaTangShaoBing/study/tree/master/jdk11-test

2、新的語法特性

Java11相對Java8,在語法上的新特性並很少。主要有:java

  • 本地變量類型推斷
  • HttpClient
  • Collection加強
  • Stream加強
  • Optional加強
  • String加強
  • InputStream加強

2.1 本地變量類型推斷

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<>());
    }
}
  • lambda參數
// 從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.";
    }
}

2.2 HttpClient

Java 9開始引入HttpClient API來處理HTTP請求。 從Java 11開始,這個API正式進入標準庫包。參考網址:http://openjdk.java.net/groups/net/httpclient/intro.html程序員

HttpClient具備如下特性:github

  1. 同時支持 HTTP1.1 和 HTTP2 協議,並支持 websocket
  2. 同時支持同步和異步編程模型
  3. 將請求和響應主體做爲響應式流(reactive-streams)處理,並使用構建器模式

HttpClient

要發送http請求,首先要使用其構建器建立一個HttpClient。這個構建器可以配置每一個客戶端的狀態:web

  • 首選協議版本 ( HTTP/1.1 或 HTTP/2 )
  • 是否跟隨重定向
  • 代理
  • 身份驗證

一旦構建完成,就可使用HttpClient發送多個請求。算法

HttpRequest

HttpRequest是由它的構建器建立的。請求的構建器可用於設置:

  • 請求URI
  • 請求Method ( GET, PUT, POST )
  • 請求主體(若是有)
  • 超時時間
  • 請求頭

HttpRequest構建以後是不可變的,但能夠發送屢次。

Synchronous or Asynchronous

請求既能夠同步發送,也能夠異步發送。固然同步的API會致使線程阻塞直到HttpResponse可用。異步API當即返回一個CompletableFuture,當HttpResponse可用時,它將獲取HttpResponse並執行後續處理。

CompletableFuture是Java 8添加的新特性,用於可組合的異步編程。

Data as reactive-streams

請求和響應的主體做爲響應式流(具備非阻塞背壓的異步數據流)供外部使用。HttpClient其實是請求正文的訂閱者和響應正文字節的發佈者。BodyHandler接口容許在接收實際響應體以前檢查響應代碼和報頭,並負責建立響應BodySubscriber。

HttpRequest和HttpResponse類型提供了許多便利的工廠方法,用於建立請求發佈者和響應訂閱者,以處理常見的主體類型,如文件、字符串和字節。這些便利的實現要麼累積數據,直到能夠建立更高級別的Java類型(如String),要麼就文件流傳輸數據。BodySubscriber和BodyPublisher接口能夠實現爲自定義反應流處理數據。

HttpRequest和HttpResponse還提供了轉換器,用於將 java.util.concurrent.Flow 的 Publisher/Subscriber 類型轉換爲 HTTP Client的 BodyPublisher/BodySubscriber 類型。

HTTP/2

Java HTTP Client支持 HTTP/1.1 和 HTTP/2。默認狀況下,客戶端將使用 HTTP/2 發送請求。發送到尚不支持 HTTP/2 的服務器的請求將自動降級爲 HTTP/1.1。如下是HTTP/2帶來的主要改進:

  • 標頭壓縮。 HTTP/2 使用 HPACK 壓縮,從而減小了開銷。
  • 與服務器的單一鏈接減小了創建多個TCP鏈接所需的往返次數。
  • 多路複用。 在同一鏈接上,同時容許多個請求。
  • 服務器推送。 能夠將其餘未來須要的資源發送給客戶端。
  • 二進制格式。 更緊湊。

因爲HTTP/2是默認的首選協議,而且在須要的地方無縫地實現回退到HTTP/1.1,那麼當HTTP/2被更普遍地部署時,Java HTTP客戶端就無需修正它的應用代碼。

API文檔

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.");
    }
}

2.3 Collection加強

List,Set,Map有了新的加強方法:ofcopyOf

List的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

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

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();
        }
    }
}

2.4 Stream加強

Java8開始引入stream,Java11提供了一些擴展:

  • 單個元素直接構造爲Stream對象
  • dropWhile與takeWhile
  • 重載iterate方法用於限制無限流範圍

單個元素直接構造爲Stream對象

注意null與""的區別:

long size1 = Stream.ofNullable(null).count();
System.out.println(size1); // 0
long size2 = Stream.ofNullable("").count();
System.out.println(size2); // 1

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); // [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);

重載iterate方法用於限制無限流範圍

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(",")));
    }
}

2.5 Optional加強

能夠將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());

完整示例代碼

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());
    }

}

2.6 String加強

String方面,針對空白字符(空格,製表符,回車,換行等),提供了一些新的方法。

isBlank

判斷目標字符串是不是空白字符。如下結果所有爲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());

strip,stripLeading與stripTrailing

去除首尾的空白字符:

// 全角空格 + 製表符 + 回車 + 換行 + 半角空格 + <內容> + 全角空格 + 製表符 + 回車 + 換行 + 半角空格
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() + "]");

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);

lines

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());
    }
}

2.7 InputStream加強

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();
        }
    }
}

3、模塊化開發簡介

Java9引入了模塊化,Java Platform Module System,java平臺模塊系統,簡稱JPMS。

這裏使用IDEA說明如何基於module進行開發。

3.1 idea新建java工程

使用idea新建工程module-test-jdk11,並刪除根目錄下的src目錄。

3.2 新建Module

選擇工程根目錄,右鍵,選擇 new -> module:
2020-05-14-14-36-56.png

next,輸入module名稱:
2020-05-14-14-37-28.png

這裏新建了兩個module,一個叫core,一個叫main
2020-05-14-14-37-57.png

3.3 開發module並exports能夠被外部訪問的包

這裏選擇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暴露出去。

3.4 開發其餘須要依賴core的module

首先,在idea的工程配置中,選擇須要依賴core的module,在其依賴中添加modulecore:
2020-05-14-14-44-35.png

而後,在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的。

3.5 依賴其餘module

java9以後,部分類庫想在module中使用的話,也須要在module-info.java引入它們,好比jdbc的相關類庫。

mainmodule-info.java:

module main {
    requires core;
    requires java.sql;
}

在modulemain的依賴中添加jdbc驅動包:
2020-05-14-14-51-02.png

而後能夠在代碼中使用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();
        }
    }
}

4、新工具或新功能

從Java9到Java11,陸續提供了一些新的工具或功能。

4.1 REPL交互式編程

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>

4.2 單文件源代碼程序的直接執行

一個單文件源代碼,即,單獨的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文件。

4.3 徹底支持Linux容器(包括docker)

在Docker容器中運行Java應用程序一直存在一個問題,那就是在容器中運行的JVM程序在設置內存大小和CPU使用率後,會致使應用程序的性能降低。這是由於Java應用程序沒有意識到它正在容器中運行。隨着Java10的發佈,這個問題總算得以解訣,JVM如今能夠識別由容器控制組(cgroups) 設置的約束,能夠在容器中使用內存和CPU約束來直接管理Java應用程序,其中包括:

  • 遵照容器中設置的內存限制
  • 在容器中設置可用的CPU
  • 在容器中設置CPU約束

4.4 支持Unicode 10

Unicode 10新增了8518個字符,總計達到了136690個字符。包括56個新的emoji表情符號。

JDK11在java.lang下增長了4個類來處理:

  • CharacterData00.class
  • CharacterData01.class
  • CharacterData02.class
  • CharacterData0E.class

4.5 新支持的加密算法

Java實現了RFC7539中指定的ChaCha20和Poly1305兩種加密算法,代替RC4。
RFC7748定義的密鑰協商方案更高效,更安全,JDK增長了兩個新的接口XECPublicKeyXECPrivateKey

4.6 Low-Overhead Heap Profiling

免費的低耗能飛行記錄儀和堆分析儀。

經過JVMTI的SampledObjectAlloc回調提供了一個開銷低的heap分析方式提供一個低開銷的,爲了排錯java應用問題,以及JVM問題的數據收集框架。

但願達到的目標以下:

  • 提供用於生產和消費數據做爲事件的API
  • 提供緩存機制和二進制數據格式
  • 容許事件配置和事件過濾
  • 提供OS,JVM和JDK庫的事件

4.7 Flight Recorder

Flight Recorder 源自飛機的黑盒子。 Flight Recorder 之前是商業版的特性,在java11當中開源出來,它能夠導出事件到文件中,以後能夠用Java Mission Control 來分析。

兩種啓動方式:

  1. 能夠在應用啓動時配置java -XX:StartFlightRecording
  2. 應用啓動以後,使用jcmd來錄製,以下代碼:
$ 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內部的,並不須要額外的依賴,開箱即用。

5、垃圾回收器

Java11新增了兩種垃圾回收器,並改善了Java8開始提供的G1垃圾回收器。

關於Java8到Java11的垃圾回收器,將在後續其餘文章中詳細介紹。

5.1 ZGC

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

5.2 Epsilon

Experimental(實驗性質),生產環境不建議使用

啓用方法:-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC

說明:開發一個處理內存分配但不實現任何實際內存回收機制的GC,一旦可用堆內存用完,JVM就會退出,若是有System.gc()調用,實際上什麼也不會發生(這種場景下和-XX:+DisableExplicitGC效果同樣), 由於沒有內存回收,這個實現可能會警告用戶嘗試強制GC是徒勞的。

主要用途以下:

  • 性能測試(它能夠幫助過濾掉GC引發的性能假象)
  • 內存壓力測試(例如,知道測試用例應該分配不超過1GB的內存,咱們可使用-Xmx1g -XX:+UseEpsilonGC,若是程序有問題,則程序會崩潰。
  • 很是短的JOB任務(對於這種任務,GC是在浪費資源)
  • VM接口測試
  • Last-drop延遲&吞吐改進

5.3 更好的G1

對於G1 GC,相比於JDK8,升級到JDK 11便可免費享受到:並行的Full GC,快速的CardTable掃描,自適應的堆佔用比例調整(IHOP),在併發標記階段的類型卸載等等。這些都是針對G1的不斷加強,其中串行FullGC等甚至是曾經被普遍詬病的短板,你會發現GC配置和調優在JDK11中愈來愈方便。

6、移除與再也不推薦使用的類庫或功能

Java9到Java11,陸續移除了一些類庫或功能。

6.1 移除了Java EE和CORBA Moudles

在java11中移除了不太使用的JavaEE模塊和CORBA技術。

CORBA來自於二十世紀九十年代,Oracle認爲,如今用CORBA開發現代Java應用程序已經沒有意義了,維護CORBA的成本已經超過了保留它帶來的好處。

可是刪除CORBA將使得那些依賴於JDK提供部分CORBAAPI的CORBA實現沒法運行。目前尚未第三方CORBA版本,也不肯定是否會有第三方願意接手CORBA API的維護工做。

在java11中將java9標記廢棄的Java EE及CORBA模塊移除掉,具體以下:

xml 相關被移除的:

  • java.xml.ws
  • java.xml.bind
  • java.xml.ws
  • java.xml.ws.annotation
  • jdk.xml.bind
  • jdk.xml.ws

只剩下java.xml, java.xml.crypto.jdk.xml.dom 這幾個模塊。

其它被移除的Java EE和CORBA相關類庫:

  • java.corba
  • java.se.ee
  • java.activation
  • java.transaction(可是java11新增了一個java.transaction.xa模塊)

6.2 其餘移除的類庫

  • com.sun.awt.AWTUtilities
  • sun.miss.Unsafe.defineClass
  • Thread.destroy() 以及 Thread.stop(Throwable) 方法
  • sun.nio.ch.disableSystemWideOverlappingFileLockCheck 屬性
  • sun.locale.formatasdefault 屬性
  • jdk snmp 模塊
  • javafx
  • java Mission Control
  • Root Certificates: 一些根證書被移除:Baltimore Cybertrust Code Signing CA, SECOM Root Certificate, AOL and Swisscom Root Certificates

其中,使用java.lang.invoke.MethodHandles.Lookup.defineClass來替代移除的sun.miss.Unsafe.defineClass

6.3 將Nashorn Javascript標記爲不推薦

將Javascript引擎標記爲Deprecate,後續版本會移除,有須要的能夠考慮使用開源的GraalVM。

6.4 將Pack200 Tools and API標記爲不推薦

java11中將pack200以及unpack200工具以及java.tiljar中的Pack200 API標記爲Deprecate。由於Pack200主要是用來壓縮jar包的工具,因爲網絡下載速度的提高以及java9引入模塊化系統以後再也不依賴Pack200,所以這個版本將其標記爲Deprecate。

相關文章
相關標籤/搜索