Java 11 新特性

java 11 是繼 java8 之後的第一個LTS版本。因此有必要針對它進行一些深入的學習,雖然短時間內java8 還是主流版本。當然,如果從java8基礎上升級,幾乎可以確定目標就是java11。

同時也要明確一個問題,現在java的版本升級週期與前些年相比速度快了太多,對於應用開發者來說沒必要每一個小版本都去花時間研究,比如這些過渡版本:java9、java10、java12、java13(至少目前還不是LTS版本),瞭解即可。

下面梳理一下 java11 的新特性。

181: Nest-Based Access Control(基於嵌套的訪問控制)

http://openjdk.java.net/jeps/181

規範中提到了一個例子:

We will adjust the JVM's access rules by adding something like the following clause to JVMS 5.4.4:

A field or method R is accessible to a class or interface D if and only if any of the following conditions are true:

  ...
  R is private and is declared in a different class or interface C, and C and D, are nestmates.

For types C and D to be nestmates they must have the same nest host. A type C claims to be a member of the nest hosted by D, if it lists D in its NestHost attribute. The membership is validated if D also lists C in its NestMembers attribute. D is implicitly a member of the nest that it hosts.

A class with no NestHost or NestMembers attribute, implicitly forms a nest with itself as the nest host, and sole nest member.

簡單的翻譯一下重點部分:

當且僅當以下條件爲真時,一個成員字段或方法R是可以被類或接口D訪問的:

  ...
  R 是私有的,並且是聲明在另一個類或接口C中,同時 C 和 D 是嵌套夥伴。

由於C和D是嵌套同伴,那麼他們一定有同一個嵌套宿主類,在這裏是Test。如果C在自己的嵌套宿主的屬性中可以列舉出D,那麼C類就會被D 斷言爲自己的嵌套成員。如果D也可以在自己的NestMembers 屬性中列舉出C,那麼這個嵌套同伴關係就是有效的。D是自己的隱性嵌套成員。

一個類如果沒有 NestHost 或 NestMembers 屬性,就會隱性地把自己做爲嵌套宿主,以及唯一的嵌套成員。(這和上一句是一致的)

巴拉了一堆,看一個例子:

import java.lang.reflect.Field;

public class Test {
	public static void main(String[] args) throws Exception {
    System.out.println("---D 的嵌套宿主");
    System.out.println(D.class.getNestHost());
		System.out.println("---D 的嵌套成員");
		for(Class cls:D.class.getNestMembers()) System.out.println(cls);
    System.out.println("---C 的嵌套宿主");
    System.out.println(C.class.getNestHost());
		System.out.println("---C 的嵌套成員");
		for(Class cls:C.class.getNestMembers()) System.out.println(cls);
		System.out.println("---");
	}

	static class C{
	}
	static class D{
	}
}

執行結果:

---D 的嵌套宿主
class Test
---D 的嵌套成員
class Test
class Test$D
class Test$C

---C 的嵌套宿主
class Test
---C 的嵌套成員
class Test
class Test$D
class Test$C
---

從該實例中可以看出:

  • C 和 D 是 Test 的嵌套成員類,而Test是 前兩者的嵌套宿主,這是顯而易見的。
  • C 可以在其NestHost(即Test)中列舉出D成員,因此他會被加入到D的嵌套成員列表中。
  • D 也同上。

繼續來研究這個話題,java 11 解決了什麼問題?

import java.lang.reflect.Field;

public class Test {
	public static void main(String[] args) throws Exception {
    D d=new D();
    d.R();
	}

	static class C{
		private int flag=0;
	}
	static class D{
		public void R() throws Exception {
      C c=new C();
      c.flag=1;
      System.out.println(c.flag);// ① 這裏沒有問題

      Field f = C.class.getDeclaredField("flag");
      f.setInt(c, 2);
      System.out.println(c.flag);//② jdk8 拋出 IllegalAccessException,jdk11 正常
		}
	}
}

在java8中,註釋① 處的代碼是沒有問題的,因爲嵌套類是可以訪問別的嵌套類的私有屬性的。

但註釋② 處的代碼會拋出異常,這是一個令人看困惑的問題。java11 修復了這個問題,以上代碼在java11中執行正常。

309: Dynamic Class-File Constants(動態類文件常量)

http://openjdk.java.net/jeps/309

這是針對類加載機制的修改,增加了一個名爲 CONSTANT_Dynamic 的常量池實體。CONSTANT_Dynamic常量池條目對bootstrap方法進行編碼,以執行解析(一個方法句柄)、常量的類型(一個類)和任何靜態bootstrap參數(常量的任意序列,在動態常量之間的常量池中排除週期)。

該方案是爲了降低創建新形式類文件常量的代價和干擾,爲語言設計者和編譯器開發者提供更廣泛的表現形式以及性能。

315: Improve Aarch64 Intrinsics(改進 Aarch64 內聯函數 )

http://openjdk.java.net/jeps/315

AArch64是ARMv8 架構的一種執行狀態。

在AArch64 CPU指令集中,改進了存在的string和array的內聯函數,包括:String::compareTo, String::indexOf, StringCoding::hasNegatives, Arrays::equals, StringUTF16::compress, StringLatin1::inflate 以及多樣的 checksum 計算方法。

實現了 java.lang.Mathsin, cos 以及 log() 等函數。

318: Epsilon: A No-Op Garbage Collector(Epsilon — 一個無操作的垃圾收集器)

http://openjdk.java.net/jeps/318

開發了一個GC,它處理內存分配,但不實現任何實際的內存回收機制。一旦可用的Java堆耗盡,JVM就會關閉。

Epsilon GC 通過 -XX:+UseEpsilonGC 開關啓用。

320: Remove the Java EE and CORBA Modules(刪除 Java EE 和 CORBA 模塊)

http://openjdk.java.net/jeps/320

從Java SE平臺和jdk中移除了 Java EECORBA 模塊,他們在java9中已經標記爲 @Deprecated 了。

模塊包括:JAX-WS、JAXB、JAF 和 Common Annotation.

具體的技術組件:

java.xml.ws (JAX-WS, plus the related technologies SAAJ and Web Services Metadata)
java.xml.bind (JAXB)
java.activation (JAF)
java.xml.ws.annotation (Common Annotations)
java.corba (CORBA)
java.transaction (JTA)

相關的包:

java.se.ee (Aggregator module for the six modules above)
jdk.xml.ws (Tools for JAX-WS)
jdk.xml.bind (Tools for JAXB)

共計九個jmods:

Their source code will be deleted from the OpenJDK repository.
Their classes will not exist in the JDK runtime image.
Their tools will no longer be available:
wsgen and wsimport (from jdk.xml.ws)
schemagen and xjc (from jdk.xml.bind)
idlj, orbd, servertool, and tnamesrv (from java.corba)
The JNDI CosNaming provider (from java.corba) will no longer be available.
No command line flag will be capable of enabling them, as --add-modules does on JDK 9.

321: HTTP Client (Standard)

http://openjdk.java.net/jeps/321

HTTPClient 是在java9引入孵化的項目,在java10中通過JEP110進行了更新,最終在java11通過JEP321 形成標準。

該API通過 CompletableFutures 實現了非阻塞的request和response功能。

背壓和流控制是在 java.util.concurrent.Flow API中通過 reactive-streams 平臺實現的。

JEP 321 幾乎完全重寫了以前的孵化API,實現了完全的異步(以前的HTTP/1.1的實現是阻塞的)。

模塊名和包名是: java.net.http

同步請求用例

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.time.Duration;
import java.nio.file.Path;
import java.nio.file.Paths;

public class HttpSyncDemo{
    public static void main(String[] args) throws Exception {
       HttpClient httpClient = HttpClient.newHttpClient();
       HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://www.baidu.com"))
            .timeout(Duration.ofSeconds(20))
            .header("Content-Type", "text/html")
            .build();
       HttpResponse<Path> response =
            httpClient.send(request, HttpResponse.BodyHandlers.ofFile(Paths.get("baidu.txt")));
       System.out.println("Response status code: " + response.statusCode());
       System.out.println("Response headers: " + response.headers());
       System.out.println("Response body: " + response.body());
    }
}

異步請求用例

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.util.concurrent.CompletableFuture;
import java.net.http.HttpResponse;
import java.net.URI;
import java.time.Duration;
import java.nio.file.Path;
import java.nio.file.Paths;

public class HttpAsyncDemo{
    public static void main(String[] args) throws Exception {
       HttpClient httpClient = HttpClient.newHttpClient();
       HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://www.baidu.com"))
            .timeout(Duration.ofSeconds(20))
            .header("Content-Type", "text/html")
            .build();
       CompletableFuture<String> response =
            httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body);
       response.whenComplete((resp, t) -> {
            if (t != null) {
                System.out.println("response message:" + t.getMessage());
            } else {
                System.out.println("response:" + resp);
            }
        }).join();
    }
}

323: Local-Variable Syntax for Lambda Parameters(用於 Lambda 參數的局部變量語法)

在java10 中已經引入了本地變量的類型推斷:

var greeting="helloworld!";
System.out.println(greeting);

在java11中類型推斷得到了增強,開發者可以使用 var 聲明 lambda 參數。

// Inference von Lambda-Parametern
Consumer<String> printer = (var s) -> System.out.println(s); // statt s -> System.out.println(s);

324: Key Agreement with Curve25519 and Curve448(Curve25519 和 Curve448 算法的密鑰協議)

http://openjdk.java.net/jeps/324

密鑰交換協議規範 RFC 7748 定義了比 elliptic curve Diffie-Hellman (ECDH) 更高效、更安全的密鑰協議模式,JEP324是該協議的具體實現。

KeyPairGenerator kpg = KeyPairGenerator.getInstance("XDH");
NamedParameterSpec paramSpec = new NamedParameterSpec("X25519");
kpg.initialize(paramSpec); // equivalent to kpg.initialize(255)
// alternatively: kpg = KeyPairGenerator.getInstance("X25519")
KeyPair kp = kpg.generateKeyPair();

KeyFactory kf = KeyFactory.getInstance("XDH");
BigInteger u = ...
XECPublicKeySpec pubSpec = new XECPublicKeySpec(paramSpec, u);
PublicKey pubKey = kf.generatePublic(pubSpec);

KeyAgreement ka = KeyAgreement.getInstance("XDH");
ka.init(kp.getPrivate());
ka.doPhase(pubKey, true);
byte[] secret = ka.generateSecret();

什麼是密碼交換協議?爲了數據交互的安全,當客戶端和服務端進行交互前,二者需要和對方約定一個密碼用來加密解密,那麼這個交換密鑰(握手)的過程中如何保證密鑰不被攔截?這就是密碼交換協議解決的範疇。

327: Unicode 10

http://openjdk.java.net/jeps/327

升級平臺API,以支持 unicode規範 的 10.0 版本。

設計的類有:java.lang.Characterjava.lang.Stringjava.awt.font.NumericShaperjava.text.Bidijava.text.BreakIterator 以及 java.text.Normalizer

該實現增加了 16,018 個字符,和10個新的腳本。

328: Flight Recorder (飛行記錄器)

http://openjdk.java.net/jeps/328

爲java應用程序和 HotSpot JVM 提供了一個低開銷的排錯框架。

發送事件:

import jdk.jfr.*;

public class Hello{
@Label("Hello World")
@Description("Helps the programmer getting started")
static class HelloWorld extends Event {
   @Label("Message")
   String message;
}

public static void main(String... args) throws Exception {
    HelloWorld event = new HelloWorld();
    event.message = "hello, world!";
    event.commit();
    Thread.sleep(30*60*1000);
}
}

執行如下命令在啓動應用程序時記錄:

$ java -XX:StartFlightRecording Hello
Started recording 1. No limit specified, using maxsize=250MB as default.

Use jcmd 6120 JFR.dump name=1 filename=FILEPATH to copy recording data to file.

編碼方式訂閱事件:

import java.nio.file.*;
import jdk.jfr.consumer.*;

public class Consumer{
  public static void main(String... args) throws Exception{
    Path p = Paths.get("recording.jfr");
    for (RecordedEvent e : RecordingFile.readAllEvents(p)) {
      try{
        System.out.println(e.getStartTime() + " : " + e.getValue("message"));
      }catch(Exception ee){
      //ee.printStackTrace();
      }
    }
  }
}

輸出:

2019-10-18T08:20:37.755218322Z : hello, world!

根據提示執行jcmd 完成記錄。

$ jcmd <pid> JFR.start
$ jcmd <pid> JFR.dump filename=recording.jfr
$ jcmd <pid> JFR.stop

329: ChaCha20 and Poly1305 Cryptographic Algorithms(ChaCha20 和 Poly1305 加密算法)

http://openjdk.java.net/jeps/329

實現了 RFC 7539 規範中的 ChaCha20 和 ChaCha20-Poly1305 兩種加密算法。

ChaCha20 是新式的流加密算法,可以代替陳舊的,不太安全的 RC4 流加密算法。

ChaCha20 測試:

// Get a Cipher instance and set up the parameters
// Assume SecretKey "key", 12-byte nonce "nonceBytes" and plaintext "pText"
// are coming from outside this code snippet
Cipher mambo = Cipher.getInstance("ChaCha20");
ChaCha20ParameterSpec mamboSpec
    = new ChaCha20ParameterSpec(nonceBytes, 7);   // Use a starting counter value of "7"
// Encrypt our input
mambo.init(Cipher.ENCRYPT_MODE, key, mamboSpec);
byte[] encryptedResult = mambo.doFinal(pText);

ChaCha20-Poly1305 測試:

// Get a Cipher instance and set up the parameters
// Assume SecretKey "key", 12-byte nonce "nonceBytes" and plaintext "pText"
// are coming from outside this code snippet
Cipher mambo = Cipher.getInstance("ChaCha20-Poly1305");
AlgorithmParameterSpec mamboSpec = new IvParameterSpec(nonceBytes);

// Encrypt our input
mambo.init(Cipher.ENCRYPT_MODE, key, mamboSpec);
byte[] encryptedResult = new byte[mambo.getOutputSize(pText.length)];
mambo.doFinal(pText, 0, pText.length, encryptedResult);

330: Launch Single-File Source-Code Programs(啓動單一文件的源代碼程序)

可以啓動尚未編譯的類文件。

java HelloWorld.java

它相當於:

javac -d <memory> HelloWorld.java
java -cp <memory> HelloWorld

同時,java11也引入了 Shebang(#!)機制可以直接編寫java可執行腳本,如:以下是 demo.sh 的腳本:

#!/usr/local/jdk/bin/java --source 11

public class HelloWorld{
  public static void main(String[] args){
    System.out.println("helloworld");
  }
}

執行:

# chmod +x demo.sh
# ./demo.sh
helloworld
  • 如果你對shell編程比較熟悉,你可能希望使用env來避免使用java路徑的硬編碼,但這是錯誤的。

env的語法:

#!/usr/bin/env 腳本解釋器名稱

如果編寫如下腳本:

#!/usr/bin/env java --source 11

執行會出現錯誤,因爲 java --source 11 被當做了一個整體傳遞給了 env.

331: Low-Overhead Heap Profiling(低開銷的 Heap Profiling)

http://openjdk.java.net/jeps/331

提供了一個低開銷的堆分配採樣方法,可以使用JVMTI 進行訪問。

332: Transport Layer Security (TLS) 1.3(支持 TLS 1.3)

http://openjdk.java.net/jeps/332

瞭解 https的開發者都應該知道TLS,這是 TLS1.3 的實現。

333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental) (可伸縮低延遲垃圾收集器)

http://openjdk.java.net/jeps/333

一個新的低延遲的垃圾回收期,該版本提供的是實驗性特性。

335: Deprecate the Nashorn JavaScript Engine(棄用 Nashorn JavaScript 引擎)

http://openjdk.java.net/jeps/335

將 Nashorn javascript 引擎標記爲廢棄,以後的版本會被刪除。世事難料,它是java8才被引入的,這就廢棄了,至於原因,規範中說:

With the rapid pace at which ECMAScript language constructs, along with APIs, are adapted and modified, we have found Nashorn challenging to maintain.

大致意思是:隨着ECMAScript語言結構和api的快速調整和修改,我們發現很難維護Nashorn。

當版本帝遇到版本帝,一山不容二帝,byebye.

336: Deprecate the Pack200 Tools and API (棄用 Pack200 工具和 API)

http://openjdk.java.net/jeps/336

將pack200 和 unpack200 工具標記爲棄用,同時棄用的還有 java.util.jar 中的API。

pack200 是什麼?它在java的生命中很重要。pack200 是jar文件的壓縮模式,它當初的目標是"在java應用程序打包、傳輸、交付過程中減少磁盤存儲和帶寬需求"。

這麼重要的工具爲什麼要廢棄,官方給出了三個原因:

  • 過去,由於網絡使用mordem撥號的56k 帶寬阻礙了java的接受程度。隨着java功能的增強,下載數據的大小瘋狂增長進一步阻礙了java的接受程度。使用Pack200壓縮jdk可以很好的解決這個問題。然而隨着技術的發展網速大幅度提升,同時java9提供了jmod工具用以爲jre瘦身。java8是最後一個用Pack200打包的正式版本,之後,我們不再需要它了。
  • 除了JDK之外,使用Pack200壓縮客戶端應用程序(尤其是applet)也很有吸引力。然而隨着客戶端瀏覽器UI設計的改變,大部分瀏覽器不在接受java插件。因此 Pack200的一個主要使用者——applet也不會再幫助它穩固地位。
  • Pack200 是一個複雜的技術,緊緊地與 class文件格式及jar 文件格式相關聯。隨着JEP 200(模塊化jdk)的採用,二者已經發生了不可預料的變化。這個實現將java代碼和本地C代碼拆分開來,這使它更難維護。Pack200 不利於java se 的模塊化。綜上所述,維護pack200的成本是巨大的,超過了把它包含在java se和jdk中所帶來的好處。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章