「Java8からJava11」で何が起きたのか、どう環境構築すればいいのか

# この記事の目的

- 「Javaの環境構築」で絶対にハマったり、錯綜する情報にいつも惑わされる人に向けた記事です。
- 2017年以降のJavaは、移り変わりが激しい世界になりました。このことを認識し、「軸となる考え方」や「重要な動向」を把握できるように、調べた情報をまとめました。
- **「5年ぶりに(Java|JVM言語)触るんだけど環境周りが全然わからん」**とか、**「CorrettoとかAdoptOpenJDKとか、みんな何を言っているんだ」**という人(つまりちょっと前の筆者)が、「今後に渡って２度とハマらないための基礎知識を得られること」を目的にしています。以下の３本立てです:
  - ざっくりとした歴史
  - 2020年におけるローカル開発環境構築
  - 情報源と参考URL
- APIや言語仕様の変更点には触れません(他の[良い記事](https://qiita.com/nowokay/items/1ce24079f4daafc73b4a)があります)。

---------

# I. ざっくりとした歴史

- 2017年9月、OracleはJavaのリリース・モデルの変更を発表し、同時に「Oracle JDKの有償機能をOpenJDKで公開」することを発表した。すなわち**Javaはオープン化されることになった(有償化ではなく)。**
- 以前から有償契約でサポートを受けていたクライアントは、引き続き有償サポートを受けられる。この場合に限り、JDKは"Open"ではない「Oracle JDK」として提供され、プロダクション環境での利用が有償となる(ここが誤解されがち)。

## リリースサイクルについて

- 毎年3月と9月にフィーチャー・リリースが提供され、メジャーバージョンが上がっていく。Ex: Java9 => 2017年9月、Java10 => 2018年3月、Java11 => 2018年9月、...
- 全フィーチャー・リリースがOracle JDKのLTS(Long Term Support)**というわけではない**。Java9以降では、Java11, Java17がLTS対象。それ以外はnon-LTS。

- 詳しくは、[Oracle Java SE サポート・ロードマップ](https://www.oracle.com/technetwork/jp/java/eol-135779-ja.html)を参照。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183715/0d481312-4589-2930-d4c9-85778542c8e4.png)

## 「8とそれ以降」

- Java8は「リリース・モデル変更前の最後のバージョン」という位置にいる。Java8自体もオープン化され、かつOracle JDKのLTS対象になっている。
- Java8からJava9に上がる際、Project Jigsawと呼ばれるモジュール化が採用され、設計に比較的大きな変更が入った。
- つまり、「Java8以前とJava9以降」ではリリース・モデルの面でも、互換性の面でも大きな隔たりがある。移行に際しては両面を(混同することなく)意識したほうが良い。


## ディストリビューションの分化

- オープンソース化したことで、複数ベンダーがJDKをビルド、発表できるようになった。OpenJDKのソースを取り巻き、**Linuxのように各社のディストリビューションが複数存在する**状況が生まれた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183715/e93f792c-4b5d-426c-a532-ee076927d56a.png)
（出典: [最適なOpenJDKディストリビューションの選び方](https://www.slideshare.net/TakahiroYamada3/how-to-choose-the-best-openjdk-distribution-201905)）
- 2020年現在、主要なディストリビューションは以下:
  - Oracle OpenJDK (by Oracle)
  - Red Hat OpenJDK (by Red Hat)
  - Azul Zulu (by Azul Systems)
  - SapMachine (by SAP)
  - Liberica JDK (by BellSoft)
  - Amazon Corretto (by Amazon)
  - AdoptOpenJDK (Community Based)

- 各社のOpenJDKディストリビューションは、「latestだけ」「OracleのLTS対象だけ」などバージョンを絞って公開・サポートすることがある。
  - 例えば2020年1月現在、Oracle OpenJDKは最新の13をGA(General-Availability Release)として公開。他バージョンもダウンロードは可能だが、使用は非推奨。
  - 同じく2020年1月現在、Amazon Correttoは8と11を公開。

- ディストリビューションにお墨付きを与えるため、Technology Compatibility Kit(TCK)という一連のテストがOpenJDKコミュニティ(Oracleと要契約)によって用意されている。TCKをパスしたディストリビューションは、“Java SE compatible”と名乗ることができる
- AdoptOpenJDKのように、TCKをpassしていないが比較的普及しているディストリビューションもある

## どう選べばいいの？

- サポート状況や、各ディストリビューションを取り巻くエコシステムで判断するのが現実的。
  - 例えば、Zuluのような有償ライセンス / サポートがあるか？
  - 例えば、インストーラはきちんと準備されていて、使いやすいか？
  - 例えば、公式Dockerイメージは用意されているか？

- とはいえ、現状目立った差は少なく、選びづらい面もある。これから特徴が出てくるかもしれない。

# II. 2020年におけるローカル開発環境構築

~~Iに書いたような状況なので、定まったベストはなく、しかもすぐに陳腐化する傾向にある。~~

=> [SDKMAN!](https://sdkman.io/)が現状のベストプラクティス。(2020.1.10 追記)

ここでは、「コマンドラインのみでインストールが終わること」「複数バージョンを切り替え可能な状態にすること」を重視し、以下2パターンの構築手順を例示する:

- パターン1: SDKMAN!(推奨)
- パターン2: Homebrew & jEnv

## パターン1: SDKMAN!によるインストール

https://sdkman.io/

SDKMAN!は、Groovyの複数バージョン管理ツールであるGVMを前身とする。JVM言語やそのビルドツールを中心に多種多様なツールの複数バージョンの管理を実現してくれる。

SDKMAN!自体のインストールは公式を参照。導入後、Javaのインストールは以下のコマンドで完結する:

```
sdk install java      
```

このときインストールされるディストリビューションは、2020年1月現在はAdoptOpenJDKの11。

その他のディストリビューションに関しても、`list`, `install`, `default`の３つのサブコマンドで自由に確認・切替えできる。バージョンやディストリビューションの指定はリスト中のIdentifierを使用する。

```
sdk list java

================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
 AdoptOpenJDK  |     | 13.0.1.j9    | adpt    |            | 13.0.1.j9-adpt      
               |     | 13.0.1.hs    | adpt    |            | 13.0.1.hs-adpt      
               |     | 12.0.2.j9    | adpt    |            | 12.0.2.j9-adpt      
               |     | 12.0.2.hs    | adpt    |            | 12.0.2.hs-adpt      
               |     | 11.0.5.j9    | adpt    |            | 11.0.5.j9-adpt      
               | >>> | 11.0.5.hs    | adpt    | installed  | 11.0.5.hs-adpt      
               |     | 8.0.232.j9   | adpt    |            | 8.0.232.j9-adpt     
               |     | 8.0.232.hs   | adpt    |            | 8.0.232.hs-adpt     
 Amazon        |     | 11.0.5       | amzn    |            | 11.0.5-amzn         
               |     | 8.0.232      | amzn    |            | 8.0.232-amzn        
               |     | 8.0.202      | amzn    |            | 8.0.202-amzn        
 Azul Zulu     |     | 13.0.1       | zulu    |            | 13.0.1-zulu         
               |     | 12.0.2       | zulu    |            | 12.0.2-zulu  
       
...(中略)...         

 SAP           |     | 12.0.2       | sapmchn |            | 12.0.2-sapmchn      
               |     | 11.0.4       | sapmchn |            | 11.0.4-sapmchn      
================================================================================
```

なお、２つ目以降のJDKインストール時に、そのディストリビューションをデフォルトにするかどうかプロンプトで尋ねられる。

```
sdk install java 13.0.1-open # Oracle OpenJDK 13

Downloading: java 13.0.1-open

...(中略)...

Installing: java 13.0.1-open
Done installing!

Do you want java 13.0.1-open to be set as default? (Y/n): Y

Setting java 13.0.1-open as default.
```

デフォルトに設定すれば、その時点でJavaのバージョンは切り替わる。パスの設定等は一切不要。試しにjshellの起動確認をしてみる。

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

jshellはJava9から追加されたJavaの[REPL](https://ja.wikipedia.org/wiki/REPL)である。他の言語でお馴染みかもしれないが、標準APIのちょっとした動作確認などに重宝する。`/exit`で終了できるので、バージョンを戻して再実行してみる。

```
sdk default java 11.0.5.hs-adpt

Default java version set to 11.0.5.hs-adpt
```

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

バージョンが切り替ることを確認できた。

<details><summary>パターン2: Homebrew & jEnvによるインストール</summary><div>

## パターン2: Homebrew & jEnvによるインストール

*SDKMAN!に比べて煩雑で、お勧めできない。裏側でどのようにパスが通されているかなどの参考にはなるかも*

**HomebrewでのJDKインストール**

https://brew.sh/index_ja

- 比較的最近(恐らく2019年11月)、Formulaeに`openjdk`が追加された。したがって、`brew install openjdk`でインストール可能。
- この時インストールされるディストリビューションは、Oracle OpenJDK。
- Java8やそれ以前をインストールする必要がある場合、または他のディストリビューションを利用したい場合は、現状Homebrew Caskを使うことになる(この記事では割愛する)。
- サポートされているFormulaeはここで検索するのが早い => https://formulae.brew.sh/formula/

インストールに成功するとCaveats(警告)が出ていることに気づく。以下はCatalina+zshで試したときのメッセージ。

```
==> Pouring openjdk-13.0.1+9.catalina.bottle.tar.gz
==> Caveats
For the system Java wrappers to find this JDK, symlink it with
  sudo ln -sfn /usr/local/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk

openjdk is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have openjdk first in your PATH run:
  echo 'export PATH="/usr/local/opt/openjdk/bin:$PATH"' >> ~/.zshrc
```

さらっと書いてあるが、どのパラグラフも重要。環境によってはメッセージの細部や取るべき対応が違うこともある。

> For the system Java wrappers to find this JDK, symlink it with...

"system Java wrappers"が何を指すか明確でないが、後述する`java_home`コマンドなどを動作させるために、シンボリックリンクの作成が必要。

> openjdk is keg-only, which means it was not symlinked into /usr/local,...

`/usr/local`以下にsymlinkされていないということなので、つまりこの時点でターミナルで`java`と打ってもここでインストールしたOpenJDKが直ちに動作するわけではないと言っている。

> If you need to have openjdk first in your PATH run:...

ここでインストールしたOpenJDKがパスとして最初に検索されるようにするためには、シェルの起動時にパスを追加する必要がある。

ただ、今回は複数バージョンのJDKを別ツールで管理する方針なので、Javaのパスを直接`~/.zshrc`に書くことは避ける。

以上を踏まえて、最初のメッセージが推奨する、シンボリックリンクの作成だけを実行する:

```
sudo ln -sfn /usr/local/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk
```

こうすれば、以下のコマンドが動作するようになってくれるはずだ:

```
java --version

openjdk 13.0.1 2019-10-15
OpenJDK Runtime Environment (build 13.0.1+9)
OpenJDK 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)
```

```
/usr/libexec/java_home

/Library/Java/JavaVirtualMachines/openjdk.jdk/Contents/Home
```

**jEnvへの登録**

http://www.jenv.be/

[pyenv](https://github.com/pyenv/pyenv)のように、複数バージョンのJDKの管理を楽にしてくれるツール。インストール手順は公式に譲る。`brew install`の実行後、パスを通す必要がある。

例えばpyEnvだと`pyenv install 3.5.0`などのコマンドでPythonをインストールできる。だがここまでの操作からもわかるように、jEnv自体にJDKをダウンロードしてくる機能はない。`jenv add`コマンドが引数として要求するのは、「ダウンロード済みのJDKの`JAVA_HOME`へのフルパス」だ。このパスは先述の`java_home`コマンドで参照できる。

```
/usr/libexec/java_home

/Library/Java/JavaVirtualMachines/openjdk.jdk/Contents/Home
```

あとはこれを利用してJDKをjEnvに追加してやれば良い:

```
jenv add `/usr/libexec/java_home`

openjdk64-13.0.1 added
13.0.1 added
13.0 added
```

**複数バージョンのインストール**

上記の状態から、例えば、Oracle OpenJDKの11を追加でインストールする場合は以下のような手順になる:

```
# 1. JDKをインストールする
brew install openjdk@11

# 2. JavaVirtualMachines配下にsymlinkを作成する
# (brew install時にプロンプトに出てくるコマンドをコピペする)
sudo ln -sfn /usr/local/opt/openjdk@11/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-11.jdk

# 3. jEnvの管理対象に加える
jenv add `/usr/libexec/java_home -v 11`
```

jEnvが複数バージョンを管理できるようになったことの確認と、jshellの起動確認をしてみる。

```
jenv versions

* system
  11.0
  11.0.5
  13.0
  13.0.1
  openjdk64-11.0.5
  openjdk64-13.0.1
```

バージョンを13に設定する。

```
jenv global 13.0 

jenv versions

  system
  11.0
  11.0.5
* 13.0
  13.0.1
  openjdk64-11.0.5
  openjdk64-13.0.1
```

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

続いてバージョンを切り替えて同じことをしてみる。

```
jenv global 11.0

jenv versions

  system
* 11.0
  11.0.5
  13.0
  13.0.1
  openjdk64-11.0.5
  openjdk64-13.0.1
```

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

バージョンが切り替ることを確認できた。

</div></details>

ここまでやっておけば、ある程度ディストリビューションやバージョンの追加にも対応できるローカル開発環境と言えるはず。

# III. 情報源と参考URL

以上述べてきたような経緯があるので、選択したディストリビューションによって開発者が参照すべき情報は異なってくる。

横断的に現状を把握できるページが見つかれば望ましいが、現状は、まずOpenJDKの開発をリードするOracle公式のロードマップを参照するのが良いだろう。

Oracle Java SE サポート・ロードマップ
https://www.oracle.com/technetwork/jp/java/eol-135779-ja.html

Red Hatなど、ベンダーが自社ディストリビューションのライフサイクルやポリシーを公開している場合もある。

OpenJDK Life Cycle and Support Policy
https://access.redhat.com/articles/1299013

## その他 参考URL

（本記事はほとんど以下の記事からの抜粋と要約なので、より深く知りたい方はぜひ！）

[JDKの新しいリリース・モデルおよび提供ライセンスについて]
(https://www.oracle.com/technetwork/jp/articles/java/ja-topics/jdk-release-model-4487660-ja.html)

[Project Jigsaw]
(https://openjdk.java.net/projects/jigsaw/)

[これからのJDK 何を選ぶ？どう選ぶ？ (v1.2)]
(https://www.slideshare.net/TakahiroYamada3/how-to-choose-jdk-20191101)[^1]

[^1]: 2020-03-09追加。 @yamadamn さん、ありがとうございます

[最適なOpenJDKディストリビューションの選び方]
(https://www.slideshare.net/TakahiroYamada3/how-to-choose-the-best-openjdk-distribution-201905)

[「Java 有償化」で誤解する人になるべく分かりやすく説明するためのまとめ]
(https://togetter.com/li/1343743)

[OpenJDK - Wikipedia]
(https://ja.wikipedia.org/wiki/OpenJDK)

[Oracle JDK vs. OpenJDK builds comparison]
(https://devexperts.com/blog/oracle-jdk-vs-openjdk-builds-comparison/)


#### 関連Qiita記事[^2]

[^2]: 2020-12-17追加。 @yamadamn さん、ありがとうございます

[OpenJDKと各種JDKディストリビューションの情報源まとめ #minjava](https://qiita.com/yamadamn/items/2dd26a014791b9557199)

[Java のリリース日とEOLのまとめ](https://qiita.com/YujiSoftware/items/2f912af3c857e827ff95)