NHN Cloud NHN Cloud Meetup!

Big things in JDK 11

2018/9/25に最終リリースされた新バージョンのjdkです。新しい機能は以下のJEPSでご確認ください。
http://openjdk.java.net/projects/jdk/11/
jdk 11バージョンは、約90個の新機能が公開されました。そのうちのいくつかをサンプルとともに見てみましょう。

注目機能

(JEP 323)Local-Variable Syntax for Lambda Parameters

list.stream()
    .map((var s) -> s.toLowerCase())
    .collect(Collectors.toList());

jdk 10バージョンでローカル変数として使用できるvarが追加されました。jdk 11バージョンではvarをラムダ式で使用すると、配信されるパラメーターのタイプを推論することができます。

list.stream()
    .map(s -> s.toLowerCase())
    .collect(Collectors.toList());

もちろん、既存のラムダ式では、より簡単にタイプを推論することができました。上記のようにタイプを省略すると、コンパイラがコンパイル時にsのタイプをStringに推論します。jdk 8の推論方法がより簡単なのに、なぜこのような機能を追加したのでしょうか?

Align the syntax of a formal parameter declaration in an implicitly typed lambda expression with the syntax of a local variable declaration.

JEP 323のゴールを見ると、上のように書かれています。jdk 10で明示的に宣言された構文をvaで宣言できるようになり、この機能をラムダ式にも適用して、暗示的に宣言された形態をvarで宣言し、式を統一できるようにしました。

list.stream()
    .map((@NotNull var s) -> s.toLowerCase())
    .collect(Collectors.toList());

また、このように明示的に宣言すると、ラムダ式でアノテーションを使用する場合に、より簡単にコードを作成できるようになります。

(var x, y) -> x.process(y)  //not allowed
(var x, int y) -> x.process(y) //not allowed

ちなみに、ラムダ式でvarを使用する場合は、すべてのパラメータが同じ形で宣言する必要があります。上記のように複数のパラメータがラムダ式として提供されている場合、varを使用するには、すべてのローカル変数をvarで推論できるコードを作成する必要があります。

(JEP 321)HTTP Client(Standard)

jdk 9で追加されjdk 10で更新されたjava.incubator.httpパッケージがインキュベーターからリリースされ、java.net.httpパッケージに標準化されました。このパッケージが開発された理由は以下の通りです。

  • ベースとなるURLConnection APIは、現在はほとんど使用されないプロトコルを念頭に置いて設計されている
  • HTTP / 1.1よりも抽象的である
  • 文書化がうまくいっていない、使用の難しさ
  • ブロッキング形態のみ動作
  • メンテナンスの難しさ

java.net.httpに移されたパッケージの機能は以下の通りです。

  • Non-Blocking request and responseサポート(将来的に)
  • Backpressureサポート(java.util.concurrent.FlowパッケージからRX Flowを実装適用)
  • HTTP / 2サポート
  • Factory method形態でサポート

例:

Httpクライアント
public void get(String uri) throws Exception {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create(uri))
          .build();

    HttpResponse<String> response = client.send(request, BodyHandlers.ofString());

    System.out.println(response.body());
}

インスタンスを生成するためにファクトリーメソッド形態を提供しています。したがってHttpClient.newHttpClient();のような形態で、一般的な形態のHttpクライアントを提供することもできます。また以下のようにHttpクライアントビルダーからHTTPのバージョンとリダイレクトの実行可否、Proxy、Authenticatorなどを設定して、インスタンスを生成することができます。

HttpClient client = HttpClient.newBuilder()
      .version(Version.HTTP_2)
      .followRedirects(Redirect.SAME_PROTOCOL)
      .proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
      .authenticator(Authenticator.getDefault())
      .build();
Httpリクエスト

リクエストを送信するためのHttpリクエストもビルダーによって提供されます。ビルダーで以下を設定できます。

  • URI
  • Method(GET、POST、PUT …)
  • Body
  • timeout
  • headers
HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("http://openjdk.java.net/"))
      .timeout(Duration.ofMinutes(1))
      .header("Content-Type", "application/json")
      .POST(BodyPublishers.ofFile(Paths.get("file.json")))
      .build()

このように生成されたHttpリクエストは不変で、一度作成して複数回使用することができます。

同期/非同期
//Synchronous
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());

//Asynchronous
client.sendAsync(request, BodyHandlers.ofString())
    .thenApply(response -> { 
        System.out.println(response.statusCode());
        return response; 
    })
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println);

send関数からSynchronous(同期)に要求を送信し、応答を受け取ることができます。sendAsync関数によって返されるCompletableFutureを合成したり、操作してAsynchronous(非同期)にリクエストを送信し、応答を受け取ることができます。
その他のサンプルは以下のリンクをご確認ください。
https://openjdk.java.net/groups/net/httpclient/recipes.html

(JEP 333)ZGC:A Scalable Low-Latency Garbage Collector(Experimental)

jdk 11で登場したガベージコレクターです。ZGCとも呼ばれるこのガベージコレクターは、以下のいくつかの目標を持って開発されました。

  • GCの一時停止時間は10msを超過しない
  • 小さいサイズ(数百メガバイト)〜非常に大きなサイズ(数テラ)の範囲のヒップを処理する
  • G1に比べてアプリケーション処理量が15%以上減少しない
  • 今後GC最適化のための基盤作り
  • 最初は、Linux / x64をサポート(今後追加のプラットフォームもサポート可能)

ご存知のように、JVMで駆動されるアプリケーションの場合、GCが動作する時アプリケーションが停止する現象(Stop-The-World)が、パフォーマンスに大きな影響を与えてきました。このような停止時間を減らしたり、なくすことがアプリケーションの性能向上に寄与します。
ZGCの主な原理は、Load barrierとColored object pointerを使用することです。これにより、Javaのアプリケーションスレッドの動作中に、ZGCがオブジェクト再配置のようなタスクを実行することができます。

(JEP 330)Launch Single-File Source-Code Programs

java HelloWorld.java

単一の実行ファイルで提供されるプログラムを実行できるように、Java Launcherを改善する機能です。shebangファイル(Unix系列OSで、プログラムで実行されるスクリプトファイルであることを示す構文)を認識できるようにするものですが、このファイルを認識するためにJLS(Java Launguage Specification)またはjavacを変更したり、Javaを汎用スクリプト言語で開発することが目標ではありません。
単一ファイルプログラムは、一般的には、最初にJavaを学習する過程や小規模ユーティリティが必要な場合に使用されます。単一のソースファイルが複数のクラスファイルにコンパイルされることがあるので「run this program」という単純な目標にパッケージングというオーバーヘッドが追加されることがあります。
Java Launcherはclass file、main class of jar fileそしてmain class of moduleを実行する3つのモードを持っています。ここで、以下2つの項目によって決定される第四のモードが追加されます。

  • コマンド行の最初の項目がクラス名の場合
  • –source version オプションがある場合(versionはjavaのバージョンを意味する)

もし、ファイルに.java拡張子がない場合は、–sourceオプションを使用して、ソースファイルのモードを強制的に適用する必要があります。対象のソースファイルが、 “スクリプト”でファイル名が一般的な命名規則に従わない場合に該当します。(ソースファイルモードを適用すべきファイルが「スクリプト」であるというのは、拡張子が.shという意味ではありません。拡張子が存在しないファイルもソースファイルモード適用時、実行可能です。)

実行

java HelloWorld.java 3 4 5

ソースファイルモードで実行させる場合に、最初のパラメータはファイル名を、二回目からはクラスに渡すパラメータを意味します。上記のコマンドラインは、下記のような動作をします。

javac -d <memory> HelloWorld.java
java -cp <memory> HelloWorld 3 4 5

実行時には下記のような、追加オプションが存在します。

  • 実行するファイル名の前に–class-path, –module-path, –add-exports, –limit-module, –upgrade-module-path, –enable-previewなどの実行オプションを与えることができる
  • 伝達する引数があまりにも多い場合は、@filenameに引数リストを渡すことができる

二番目のオプションの場合、引数を渡すファイルを作成し、java @{filename}のような形式でファイルの名前を渡すと、1つのコマンドラインにすべての引数を同様に実行できます。非常に簡単な下の例を参照してください。

$ cat HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
        for (int i=0; i<args.length; i++) {
                System.out.print(args[i] + " ");
        }
    }
}
$ cat args
HelloWorld.java 3 4 5
$ java @args
Hello, World!
3 4 5

詳しい内容は、文書をご参照ください。

shebangファイル

#!/path/to/java --source 11

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

Shebangファイルを作成する方法は、他のスクリプトファイルに記述する方法と似ています。このように最初の行を作成した後の下に実行内容をJavaで作成すればよいです。作成したファイルは以下の方法で実行できます。

$ ./helloWorld
Hello World

参照

NHN Cloud Meetup 編集部

NHN Cloudの技術ナレッジやお得なイベント情報を発信していきます
pagetop