今のうちに知っておきたい、かゆい所に手が届くJava9の新機能7選

More than 1 year has passed since last update.

サイバー・バズ@kashira2339です。

Java9の正式公開が迫っていますね!(何度か延期されていますが。。。)

サイバー・バズでは、Javaを使ったプロダクトがいくつか開発されています。

なので、社員一同Java9へのアップデートにウキウキしています!

今回は先取りして、

Java8でムズムズしていた部分を解決するJava9の新機能の数々を

同じくJava9の新機能であるJShellを使って試していきたいと思います。

今回はMac OSXでの実践です。


ことはじめ


1. JDK9のインストール

手順
図 

1

JDK9のダウンロードページから Early Access Releasesをダウンロード
jdk9ea.png

2
アイコンをダブルクリック
スクリーンショット 0028-09-08 午前8.10.43.png

3
続ける
スクリーンショット 0028-09-08 午前8.11.28.png

4
インストール
スクリーンショット 0028-09-08 午前8.11.39.png

5
完了! 閉じる。
スクリーンショット 0028-09-08 午前8.12.07.png


2. jenvを使ってJava9を使用可能にする

jEnvは、PythonのpyenvやRubyのrbenvのような、

Javaのバージョン管理ツールです。

jenvをご存知でない方は、以下の記事を見ながら導入するのがオススメです。

jenv使ってJDKのバージョン管理

今回はJava9を使う必要があるので、以下のコマンドでJDK9をjenv管理下に追加。

(Mac OSXで同様の手順を踏んだ方は以下のままで問題ないと思います)

jenv add /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home

jenv versions
# ここで9-eaが表示されていれば問題無し

Java9用のディレクトリを作成して、

その配下でJava9を有効にしましょう。

mkdir ~/java9 # ホームディレクトリ配下にJava9用のディレクトリを作成

cd java9
jenv local 9-ea # Java9を有効に
java -version
# java version "9-ea"
# Java(TM) SE Runtime Environment (build 9-ea+134)
# Java HotSpot(TM) 64-Bit Server VM (build 9-ea+134, mixed mode)


JShellの起動

上記手順でインストールを進めた方は、おそらく jshell コマンドが使えるようになっているはず。

jshell

| JShellへようこそ -- バージョン9-ea
| 概要については、次を入力してください: /help intro

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

Hello World が出力できましたね。

これで準備完了です。


新機能


1. JShell

JShellについては、以下の記事がオススメです。

[Java9 新機能] jshell入門

おおよそのことはこの記事に書いてありますが、

/exit でJShellから抜け出せるということはおさえておきましょう。

以下はJShellで /? とタイプした結果です。

jshell> /?

| Java言語の式、文または宣言を入力します。
| または、次のコマンドのいずれかを入力します:

| /list [<name or id>|-all|-start] -- 入力したソースをリストします
| /edit <name or id> -- 名前またはIDで参照されるソース・エントリを編集します
| /drop <name or id> -- 名前またはIDで参照されるソース・エントリを削除します
| /save [-all|-history|-start] <file> -- ファイルにスニペット・ソースを保存します。
| /open <file> -- ソースの入力としてファイルを開きます
| /vars [<name or id>|-all|-start] -- 宣言された変数およびその値をリストします
| /methods [<name or id>|-all|-start] -- 宣言されたメソッドおよびその署名をリストします
| /types [<name or id>|-all|-start] -- 宣言された型をリストします
| /imports -- インポートされたアイテムをリストします
| /exit -- exit jshell
| /reset -- reset jshell
| /reload [-restore] [-quiet] -- リセットして関連する履歴をリプレイします -- 現在または以前(-restore)
| /classpath <path> -- クラスパスにパスを追加します
| /history -- 入力した内容の履歴
| /help [<command>|<subject>] -- jshellに関する情報を取得します
| /set editor|start|feedback|mode|prompt|truncation|format ... -- jshell構成情報を設定します
| /retain editor|start|feedback|mode -- 後続のセッションに対してjshell構成情報を保持します
| /? [<command>|<subject>] -- jshellに関する情報を取得します
| /! -- 最後のスニペットを再実行します
| /<id> -- IDでスニペットを再実行します
| /-<n> -- n回前のスニペットを再実行します
|
| 詳細は、'/help'の後にコマンドまたはサブジェクトの名前を続けて入力してください。
| たとえば、'/help /list'または'/help intro'などです。サブジェクト:

| intro -- jshellツールの概要
| shortcuts -- ショートカットの説明


2. Immutableコレクション生成API(List.of, Set.of, Map.of)

従来、List,Map,Setなどの不変コレクションを作るには一手間必要でした。


javaでの不変Set生成

Set<String> set = new HashSet<>();

set.add("one");
set.add("two");
set.add("three");
set = Collections.unmodifiableSet(set)
set.add("four")
// java.lang.UnsupportedOperationException thrown:
// at Collections$UnmodifiableCollection.add (Collections.java:1056)
// at (#9:1)

これが、Java9では以下のように書くことができます。

# Setでは

jshell> Set<String> set = Set.of("one", "two", "three")
set ==> [three, two, one]

jshell> set.add("four")
| java.lang.UnsupportedOperationException thrown:
| at AbstractCollection.add (AbstractCollection.java:267)
| at (#3:1)

# listでも
jshell> List<String> list = List.of("one", "two", "three")
list ==> [one, two, three]

# Mapでも!
jshell> Map<String, Integer> map = Map.of("one", 1, "two", 2, "three", 3)
map ==> {three=3, two=2, one=1}

と、不変コレクションは簡単に標準APIで作ることができるようになりました。

Guavaなどを普段使ってる人には、馴染み深い形でもありますね。

それにしても、さっそくJShellの便利さが実感できますね。


3. HTTP 2 Client(HttpRequest, HttpResponse)

Javaが標準APIとしてHTTP/2クライアントを搭載します。

今までちょっとしたリクエストのために

別途HTTPクライアントを依存関係に追加していましたが、

その手間がなくなりますね!

WebSocketも対応しています。

QiitaにGETリクエストを送ってみましょう。

jshell> java.net.http.HttpResponse response = java.net.http.HttpRequest.create(new URI("https://qiita.com/")).GET().response();

response ==> java.net.http.HttpResponseImpl@2cd76f31

jshell> response.statusCode();
$13 ==> 200

jshell> response.body(java.net.http.HttpResponse.asString(java.nio.charset.Charset.forName("UTF-8")));
$17 ==> "<!DOCTYPE html><html xmlns:og="http://ogp.me/ns#"><head><meta charset="UTF-8" /><title>Qiita - プログラマの技術情報共有サービス</title>..."

まちがいなくQiitaのHTMLがかえってきてますね。

ちょっとしたHTTPリクエストなら、十分このAPIでカバーすることができそうです。


4. Optionalの強化(ifPresentOrElse, stream)

Java8でOptionalが追加され、それ自体は大変嬉しかったのですが

まだわずかに歯がゆい部分がありました。


ifPresentOrElse

1つは、ifPresent メソッドのように値が存在する時にラムダを実行するメソッドはあるが、値が存在しない時の処理は別途書く必要がある、というところです。

これの救済となる、

ifPresentOrElse というメソッドが追加されました。

以下のように使えます。

jshell> Optional.ofNullable("Not Null").ifPresentOrElse(System.out::println, () -> System.out.println("Null"))

Not Null

jshell> Optional.ofNullable(null).ifPresentOrElse(System.out::println, () -> System.out.println("Null"))
Null

より直感的に値のある時とない時の処理を切り分けられますね。


Stream

OptionalをStreamに変換することができます。

例えば、 Optionalで構成されるListがある時、

このメソッドと Stream#flatMap() を併用すると簡単にnullをスキップしつつ操作を加えられます。

jshell> List<Optional<String>> list = List.of(Optional.ofNullable("hoge"), Optional.ofNullable(null), Optional.ofNullable("fuga"))

list ==> [Optional[hoge], Optional.empty, Optional[fuga]]

jshell> list.stream().flatMap(Optional::stream).forEach(System.out::println)
hoge
fuga


5. Objectsの増強(requireNonNullElse,requireNonNullElseGet)

NotNullであるべきオブジェクトを検査し、

そのオブジェクトがNullなら新しい値を使う...という操作は、

誰もが使ったことがあるかと思います。

本来はOptionalを有効活用する場面でも

使えない、使うまでもないといったケースは存在します。

Java9からは、Objectsクラスに追加された

requireNonNullElse,requireNonNullElseGetというメソッドを使うと

以下のように書くことができます。


jshell> String hoge = "CyberBuzz"
hoge ==> "CyberBuzz"

# java8
jshell> String hogeNew = hoge != null ? hoge : "empty"
# String hogeNew = hoge;
# if (hogeNew == null) {
# hogeNew = "empty";
# }
hogeNew ==> "CyberBuzz"

# java9
jshell> String hogeNew = Objects.requireNonNullElse(hoge, "empty")
hogeNew ==> "CyberBuzz"
# あるいは
jshell> String hogeNew = Objects.requireNonNullElseGet(hoge, () -> "empty")
hogeNew ==> "CyberBuzz"

Java8以前では上のように三項演算子、あるいはif文を用いて実装するでしょう。

ただ、ある変数がNullであったばあいに別の値を採用するというケースにおいて

同じ変数を2回以上登場させることや、変数がNullであった場合のみ実行される

複雑な処理を書くのは、あまり直感的とは言えません。

Java9以降では、そういった場合に

Objects.requireNonNullElse または Objects.requireNonNullElseGet

を使うのが有効な手段になるでしょう。


6. Streamで指定の条件の要素以前or以降を処理する(takeWhile,dropWhile)

以下の記事でも解説してくださってます。

Java 9 Streamに追加された3つの新機能

Streamで、ある条件に合致する要素以前の要素に対する処理や、

逆にある条件に合致する要素以降の要素に対する処理を書きたい時に

Java8まではわざわざ要素のIndexを取得してから Stream#skip() を用いることなどで

それを実現していました。

Java9では、それらをStream#takeWhileやStream#dropWhileを使って解決することができます。

# takeWhile 条件に合致しない要素が現れるまで処理

jshell> List.of(0, 1, 2, 1, 3, 2).stream().takeWhile(n -> n <= 2).collect(java.util.stream.Collectors.toList())
# ==> [0, 1, 2, 1]

# dropWhile 条件に合致しない要素のIndexまでスキップ、それ以降の要素を処理
jshell> List.of(0, 1, 2, 1, 3, 2).stream().dropWhile(n -> n <= 2).collect(java.util.stream.Collectors.toList())
# ==> [3, 2]


7. Module System

主にjarでライブラリを作る時、

外部から使用可能なクラスやその依存パッケージなどの明示的な指定はできませんでした。

そのため、本来公開したくないクラスでもアクセス修飾子がpublicであれば

外部から使うことができてしまう、などの歯がゆい部分がありました。

Java9で実装されるModule Systemは、それらの問題を解決し

PythonやECMAScript6以降のJavaScriptなどに見られる

import/exportのような仕組みを提供します。

以下の記事がとても詳しい。

JDK9 Jigsaw を試してみた

java 9のmodule機能を試してみる

今回は、

Project Jigsaw: Module System Quick-Start Guide

から引用してみましょう。


モジュールのexport


src/org.astro/module-info.java

module org.astro {

exports org.astro;
// requires xxx;
}

これは、モジュールを提供する側の記述です。

src/org.astro パッケージ直下にmodule-info.javaというファイルを配置し、

exports org.astro という部分で外部から利用可能なパッケージを明記します。

必要に応じて、org.astroパッケージが依存するモジュールを requires という書き方で

表すことができます。


モジュールのimport


src/com.greetings/module-info.java

module com.greetings {

requires org.astro;
}

上でexportしたモジュールをimportする記述がこちらです。

src/com.greetings パッケージで利用する依存パッケージを

requires の部分で指定しています。

Module Systemを利用すると、

依存解決が正しく行われない場合にコンパイルエラーを検出してくれます。

Module SystemはJava9の目玉機能!

今後の実装の動きもウォッチしていきたいところですね。


まとめ

Java9は、Java7からJava8のような派手な変化はないものの、

Java8の時点で「あとこれができればいいんだけどな」という悩みを解決する、

まさに痒いところに手がとどくような機能が実装されています。

とくに、今までのJavaに馴染んだ開発者にとっては

Module Systemはすごく注目すべきものになるのではないでしょうか。

今後の動向に期待です。


おわりに

サイバー・バズでは一緒にサービスを創れるエンジニアを募集しています :)