體驗 Java 9(1):從 Hello World 到 Lombok

jywhltj發表於2017-09-22

本文原發於我的個人部落格:https://hltj.me/java/2017/09/22/experience-java9-lombok.html 。本副本只用於圖靈社群,禁止第三方轉載。

Java 9 正式版已於當地時間的 9 月 21 日(北京時間大約是今天凌晨)如期釋出。可在 Oracle 官網下載

Java 9 沒有像 Java 5/Java 8 那樣引入新的程式設計正規化而給語言本身帶來革命性的改進,不過 Java 9 的改動還是很大的,尤其是引入模組化對 JDK 與執行時的改動都很大。 現在網上能找到很多介紹 Java 9 新特性的文章,這裡不再贅述,只簡要列舉如下:

  1. 模組化(Jigsaw 專案)
  2. G1 成為預設垃圾收集器
  3. JDK 自帶 REPL 即 JShell
  4. 支援 HTTP/2 與 WebSocket 的新版 HTTP 客戶端
  5. 多版本 Jar 包
  6. 語言、JDK、執行時與工具的其他優化與改動

如何切換到 Java 9,Oracle 官網上也有詳細的遷移文件

而如何獲得最直觀的感受,就不妨跟我一起來親身體驗下了。

Hello World

千萬不要覺得用體驗一門新語言的方式來體驗一個新版本是小兒科或者小題大做。 因為 Hello World 能執行至少意味著基本環境沒問題,如果不能就說明我們發現了問題。

Hello JShell

JShell 是 JDK 9 自帶的互動式程式設計命令列,即 REPL(Read-Eval-Print Loop 的簡寫,直譯為 “讀取-求值-輸出”迴圈),非常適合快速實驗一些程式碼片段。 它遵循通用的命令列操作,如 Tab 自動補全、Ctrl-D 退出、Ctrl-R 反向搜尋、Ctrl-S 正向搜尋等等。 執行 jshell --help 可以檢視其命令列選項說明,直接執行 jshell 進入互動式模式後,可以通過 /help 檢視互動式模式幫助。 更詳細的用法說明可參見其官方文件

互動式 Hello World

執行 jshell 進入互動式模式:

$ jshell
|  歡迎使用 JShell -- 版本 9
|  要大致瞭解該版本, 請鍵入: /help intro

jshell> 

jshell> 是 JShell 的命令提示符,可在其後直接鍵入程式碼。在 JShell 中直接輸入可執行的程式碼就可以,無需定義額外的類與 main 函式,還可以省略單行語句的分號。因此執行 Hello World 只需鍵入 System.out.println("Hello, World!") 即可,並且在輸入過程中還可以通過 Tab 鍵補全:

jshell> System.out.println("Hello, World!")
Hello, World!

大功告成。當然,如果只是想在 JShell 中檢視一個表示式的求值結果,並不需要呼叫 System.out.println() 這麼麻煩,而只需直接鍵入表示式然後回車即可:

jshell> "Hello, World!"
$2 ==> "Hello, World!"

jshell> Math.PI * 1.5 * 1.5
$3 ==> 7.0685834705770345

這實際上是 JShell 的“反饋”,如果希望反饋中包含表示式的型別,可以在啟動時加命令列選項 -v 或者通過互動模式的命令 /set feedback verbose 切換到詳細反饋模式:

jshell> /set feedback verbose
|  反饋模式: verbose

jshell> "Hello, World!"
$4 ==> "Hello, World!"
|  已建立暫存變數 $4 : String

jshell> double areaOfCircle(double radius) {
   ...>     return Math.PI * radius * radius;
   ...> }
|  已建立 方法 areaOfCircle(double)

jshell> areaOfCircle(1.5)
$6 ==> 7.0685834705770345
|  已建立暫存變數 $6 : double

更多互動式模式的用法可以查閱文件或自行發掘,現在通過快捷鍵 Ctrl-D 或者互動式命令 /exit 退出 JShell,來看下非互動式 JShell 版 Hello World。

Hello JShell 指令碼

除了互動式執行,JShell 還可以用作指令碼執行。 現在我們將輸出 Hello World 的語句寫入一個檔案:

$ echo 'System.out.println("Hello, World!")' > hello.jsh

然後用 JShell 執行之:

$ jshell hello.jsh
Hello, World!
|  歡迎使用 JShell -- 版本 9
|  要大致瞭解該版本, 請鍵入: /help intro

jshell> 

在執行完 hello.jsh 中的語句後進入了互動式模式,這並不是預期的結果,我們希望它執行完就退出而不是進入互動式模式。 當然,這隻需在檔案末尾追加 /exit 命令即可:

$ echo '/exit' >> hello.jsh
$ jshell hello.jsh
Hello, World!

又大功告成了!(奇怪,我為什麼要說又 :P)。比較遺憾的是 JShell 目前還不支援 shbang,不能將 .jsh 檔案直接作為可執行指令碼來用。

構建執行 Hello World

對於 Hello World 這樣簡單的程式碼片段,確實只用 JShell 演練就可以了。只是這並不能驗證構建執行也一切正常,因此還要繼續來看使用 JDK 9 構建執行 Hello World 程式的情形。

直接編譯執行

直接編譯執行與 JDK 8 及以前版本沒有任何區別:

$ cat > Hello.java << END
> public class Hello {
>     public static void main(String[] args) {
>         System.out.println("Hello, World!");
>     }
> }
> END
$ javac Hello.java
$ java Hello
Hello, World!

而我們在實際專案中通常並不能如此簡單地編譯執行,而需使用 Maven 或者 Gradle 來構建專案。

注:本文中測過的 Maven 版本為 3.3.9、3.5.0;Gradle 版本為 3.5-RC-2 到 4.0.2。

Hello Maven

現在我們將 Hello.java 的開頭加上包宣告 package me.hltj.java9demo.hello;,並將其放到 src/main/java/me/hltj/java9demo/hello 目錄下,然後建立 pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>me.hltj</groupId>
    <artifactId>java9demo-hello</artifactId>
    <version>1.0-SNAPSHOT</version>

</project>

這樣就建立了一個簡單的 maven 專案。 如果使用 JDK 8,此時就可以通過 mvn package 生成 jar 包,並通過以下命令來執行:

$ java -cp target/java9demo-hello-1.0-SNAPSHOT.jar me.hltj.java9demo.hello.Hello

但是使用 JDK 9 編譯會報錯:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project java9demo-hello: Compilation failure: Compilation failure:
[ERROR] Source option 1.5 is no longer supported. Use 1.6 or later.
[ERROR] Target option 1.5 is no longer supported. Use 1.6 or later.
[ERROR] -> [Help 1]

這是因為 Maven 預設的原始碼和目標版本都是 1.5,而 JDK 9 支援的最低版本是 1.6,因此必須在 pom.xml 中顯式指定這兩個版本號:

    <properties>
        <maven.compiler.source>1.9</maven.compiler.source>
        <maven.compiler.target>1.9</maven.compiler.target>
    </properties>

之後就可以像使用 JDK 8 一樣正常構建和執行了。

Hello Gradle

首先為專案建立 gradle wrapper 並基於 maven 專案初始化:

$ gradle wrapper --gradle-version=4.0.2 --distribution-type=bin
$ ./gradlew init

然後將 build.gradle 簡化為:

apply plugin: 'java'

group = 'me.hltj'
version = '1.0-SNAPSHOT'

無需指定原始碼與目標版本就可以正常構建執行:

$ ./gradlew build
BUILD SUCCESSFUL in 1s
$ java -cp build/libs/java9demo-hello-1.0-SNAPSHOT.jar me.hltj.java9demo.hello.Hello
Hello, World!

除了構建工具外,在實際專案開發中也離不開 IDE,接下來我們就看下在不同 IDE 中使用 Java 9 構建與執行的情況。

Hello IDEA

最新穩定版的 IDEA(2017.2.4)已支援 Java 9。 在其中使用 Java 9 構建與執行 Hello World 與 Java 8 無異,只需選用 JDK 9 即可:

如果尚未新增點選上圖所示的對話方塊右上方的“New”按鈕來新增。 之後可以照常構建與執行 Hello World,無論建立專案時選 Java 專案、Maven 專案還是 Gradle 專案。

Hello Eclipse

最新穩定版的 Eclipse Oxygen(4.7.0)並沒有內建 Java 9 支援,需要安裝一個 Beta 版的 Java 9 支援外掛才行:

之後就可以新增 JDK 9 了:

新增完成後就可以通過 JDK 9 照常構建與執行 Hello World,無論建立專案時選 Java 專案、Maven 專案還是 Gradle 專案。

Hello NetBeans

最新穩定版的 NetBeans(8.2)並不支援 Java 9,也沒有用於 Java 9 支援的外掛。想要支援 Java 9 只能用每日構建版,可在這裡下載: http://bits.netbeans.org/download/trunk/nightly/latest/

在安裝時選 JDK 9。之後可以照常構建與執行 Hello World,無論建立專案時選 Java 專案還是 Maven 專案。

**注:**NetBeans 雖然有 Gradle 外掛,但是在每日構建版中安裝之後也基本無法使用。

Hello World 小結

以任何方式通過 JRE 9 執行 Hello World 都如預期般順利。使用 Gradle 或者 IDEA 通過 JDK 9 構建 Hello World 示例專案也一如既往地順利。而對於 Maven 需要注意顯式指定原始碼與目標版本,對於 Eclipse 需要安裝外掛,對於 NetBeans 目前只能用每日構建版。

注:示例程式碼已放到 Github 上: https://github.com/hltj/java9demo/tree/master/hello

Hello World 總體上看還比較順利,接下來我們看下更具挑戰的 Lombok 支援情況。

Java 9 中使用 Lombok

程式碼冗長是 Java 語言廣為病詬的缺點之一。而其中很多場景都可以使用 Lombok 來簡化,這在實際專案中廣泛應用。因此驗證 JDK 9 以及使用 JDK 9 的構建工具與 IDE 對 Lombok 的支援情況尤為重要,會關係到很多既有專案能否遷移到 Java 9。

這裡以一段簡單程式碼為例,驗證 Maven、Gradle、IDEA、Eclipse、NetBeans 在使用 JDK 9 時對 Lombok 的支援情況。程式碼如下:

package me.hltj.java9demo.lombok;

import lombok.*;
import lombok.extern.slf4j.Slf4j;

@Value
@ToString
class Language {
    @NonNull
    private String name;
    private int version;
}

@Slf4j
public class LombokDemo {
    public static void main(String[] args) {
        val version = Integer.parseInt(System.getProperty("java.version"));

        val java = new Language("Java", version);

        log.info("Hello Lombok in {}.", java);
    }
}

使用 Maven 構建

我們只需建立相應程式碼目錄結構,然後在 pom.xml 中配置以下依賴即可在 Java 8 下通過編譯:

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>

當然為了便於執行還引入了 assembly 外掛,另外設定原始碼與目標級別為 1.8,完整的 pom.xml 參見 https://github.com/hltj/java9demo/blob/7a704a1b4a60cab74e454ab8d2d7edda30af430f/lombok/pom.xml。此時使用 JDK 8、JDK 9 都能正常編譯,但只有在 Java 9 環境中才能正常執行,因為 Java 8 的版本號如“1.8.0_144”不能解析為整數,執行時會拋異常。

但是當我們把 pom.xml 中的原始碼與目標的版本號升級為 1.9 時,卻編譯失敗了:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project java9demo-lombok: Compilation failure: Compilation failure: 
[ERROR] /home/hltj/java9demo/lombok/src/main/java/me/hltj/java9demo/lombok/LombokDemo.java:[6,1] 程式包 javax.annotation 不可見
[ERROR]   (程式包 javax.annotation 已在模組 java.xml.ws.annotation 中宣告, 但該模組不在模組圖中)

這是因為 Lombok 預設在每個生成的方法上新增了註解 @javax.annotation.Generated("lombok"),而在 Java 9 引入模組化系統後不再預設提供這一註解。 解決方案有:

  1. 在 lombok.config 中新增以下配置來禁用這一行為:

    lombok.addJavaxGeneratedAnnotation = false

  2. 給 javac 增加引數 --add-modules=java.xml.ws.annotation

  3. 採用安卓專案針對此問題的解決方案,新增一個提供該註解的依賴: org.glassfish:javax.annotation:10.0-b28

例如採用第三種方案,只需在 pom.xml 中追加以下配置:

        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.annotation</artifactId>
            <version>10.0-b28</version>
            <scope>provided</scope>
        </dependency>

之後就可以使用 Java 9 順利構建執行了:

$ java -jar target/java9demo-lombok-1.0-SNAPSHOT-jar-with-dependencies.jar

17:24:57.723 [main] INFO me.hltj.java9demo.lombok.LombokDemo - Hello Lombok in Language(name=Java, version=9).

使用 Gradle 構建

初始化 Gradle 之後將 build.gradle 改成這樣即可使用 JDK 8 編譯:

apply plugin: 'java'
apply plugin: 'application'

group = 'me.hltj'
version = '1.0-SNAPSHOT'

mainClassName = 'me.hltj.java9demo.lombok.LombokDemo'

repositories {
    mavenCentral()
}

dependencies {
    compileOnly "org.projectlombok:lombok:1.16.18"
    compile "ch.qos.logback:logback-classic:1.2.3"
}

但是使用 JDK 9 編譯會遇到與 Maven 構建時相同的問題,這裡採用第二種方案,在 build.gradle 中追加以下配置即可:

compileJava {
    options.compilerArgs += ['--add-modules', 'java.xml.ws.annotation']
}

注意:目前 Gradle 4.1 到 4.2-RC-2 版無法使用 JDK 9 編譯這個專案,這也是本文只驗證了 Gradle 3.5-RC-2 到 4.0.2 版的原因。

在 IDEA 中使用

如果 IDEA 中已安裝 Lombok 外掛,並且在專案中開啟了註解處理,就能夠正常解析 Lombok 註解。但是很遺憾的是,無法直接在 IDEA 中使用 JDK 9 構建:

在 IDEA 中要使用 JDK 9 編譯這個專案,需採用將構建與執行委託給 Gradle 的方式:

這樣能夠通過 Gradle 來構建,而執行還需要再做幾步。首先點選第 15/16 行左側的執行按鈕:

在彈出選單中選擇“Run”,此時“Run”視窗中預設顯示的是各個 Gradle 任務的執行情況,需要點選側欄的按鈕將其切換到文字模式才能看到輸出:

在 Eclipse 中使用

如果已執行過 lombok.jar 為 Eclipse 新增支援,並且在專案配置中啟用了註解處理,那麼 Eclipse 能夠正常解析程式碼中的大多數 Lombok 註解,只有 @Value@ToString 這兩行有問題。

在 Eclipse 中要使用 JDK 9 編譯這個專案,需要通過 Maven 方式來構建執行,而且還要去除程式碼中 @Value@ToString 註解並修改相關聯程式碼:

在 NetBeans 中使用

在 Netbeans 每日構建版中即便開啟註解處理,仍然只能識別出注解本身,而不能進行相應語義處理,可認為無法解析 Lombok 註解。

在 Netbeans 中要使用 JDK 9 編譯這個專案,需要通過 Maven 方式來構建執行:

使用 Lombok 小結

在 Java 9 環境中使用 Lombok 需要確保編譯期有能夠提供 javax.annotation 的模組可用。 Maven/Gradle 專案使用 JDK 9 編譯都不成問題,使用 Gradle 4.1 到 4.2-RC-2 版除外。 三款 IDE 中只有 IDEA 能夠良好解析 Lombok 註解,Maven 專案只有每日構建版的 NetBeans 能夠順利構建執行,Gradle 專案只有 IDEA 能夠構建執行。

注:示例程式碼已放到 Github 上: https://github.com/hltj/java9demo/tree/master/lombok

如此看來工具支援還有待提升,那麼對於既有專案來說,切換到 Java 9 有收益嗎? 當然有!Java 9 不僅提供了新功能,也有很多優化的地方。讓我們看一個簡單、直觀的示例。

效能提升的示例

以之前在由“千萬字母表問題”看多正規化程式語言中的 Java 程式碼為例,使用 Java 9 執行的效率相對 Java 8 有顯著提升。以下是我分別在不同環境中執行三次取中位數的結果:

 

Java 8

Java 9

某雲主機

6.495s

3.706s

某 Dell 本

10.580s

6.829s

某 Thinkpad

8.206s

5.889s

當然這只是說明 Java 9 的 JVM/JRE 在一些場景中相對於 Java 8 有顯著的效能提升。而新版 JDK、新的 API、新的工具同樣能夠帶來很多收益,這些會在本系列後續文章中展開。

結論

JShell 很適合簡短程式碼的演練。 JDK 9 的 IDE 支援還有待改善,短期內並不適合使用 JDK 9 進行專案開發。 但是無論 JRE 9 的效能提升還是 Java 9 引入的新功能都很值得進一步關注。

本系列後續文章會更多關注 Java 9 的新特性,同時如果發現 IDE 支援有改善也會一併提及,以便大家能夠儘早用上。

相關文章