とあるJavaエンジニアの幻想御手(レベルアッパー) Java8→9編

# はじめに

こんにちは、もんすんです。

私の実務経験として、最近Javaが一番長くなってきました。  
特に保守業務が多いので、Javaのバージョンは最新ではなく、LTSであるJava8を常用しています。

さて、先日[JJUG CCC Fall 2024](https://ccc2024fall.java-users.jp/)というJavaのカンファレンスに参加してきました。  
そのカンファレンスで、とある会社のブースが「Java23クイズ」を出題しており、意気揚々と参加したのですが…悉く間違えてしまいました。  

（唯一正解したのは、**`Effective Java 第2版`の表紙の絵柄**だけ……）

**レベル0扱い**されてもおかしくない状況でした。  
そこで、大恥をかいた私は思いました。

<font color="red">**レベルアップしたい！！**</font>と。

Java8は素敵なバージョンで、Lambda式やStream APIといった要素が追加されましたが、それ以降もJavaの進化は止まりません。  
未来の現場で時代遅れの開発者とならないためにも、最新の情報を頭に入れ、日々の鍛錬を怠らないようにしたいです。

:::note warn
某ラノベにおける、**音声ファイルでの脳波へ干渉云々**とかがあれば一瞬で解決するかもしれませんが、現実はそう甘くありません。
地道な努力が必要です。
:::

今回は、Java9をJava8と比較しながら、新しく追加された機能やその活用方法を紹介していきます。  
この記事を通じて、だれかの**超能力**ではなく、**技術力**を高める助けとなれば幸いです。

※今回の記事は、以下の記事を参考にさせていただきました。

https://qiita.com/sizer/items/76d5f7dfe925640b3766

# 環境
今回、Java8とJava9の環境をそれぞれDockerで構築し、その中で作業をしていきます。
環境差異はありますが、とりあえずJavaが使えればOKと思って使うことにしました。

## Java8

```dockerfile
FROM amazoncorretto:8u432-al2023-jre

# 作業ディレクトリを設定
WORKDIR /app

# コンテナを常時起動状態にする
CMD ["tail", "-f", "/dev/null"]

```

## Java9

```dockerfile
FROM ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive

# 必要なツールをインストール
RUN apt-get update && \
  apt-get install -y --no-install-recommends \
  curl \
  unzip \
  && rm -rf /var/lib/apt/lists/*

# Java 9のバイナリをダウンロードしてインストール
RUN curl -k -L -o openjdk9.tar.gz https://download.java.net/java/GA/jdk9/9.0.4/binaries/openjdk-9.0.4_linux-x64_bin.tar.gz && \
  mkdir -p /usr/lib/jvm && \
  tar -xzf openjdk9.tar.gz -C /usr/lib/jvm && \
  rm openjdk9.tar.gz

# Javaの環境変数を設定
ENV JAVA_HOME=/usr/lib/jvm/jdk-9.0.4/
ENV PATH="$JAVA_HOME/bin:${PATH}"

# 作業ディレクトリを設定
WORKDIR /app

# コンテナを常時起動状態にする
CMD ["tail", "-f", "/dev/null"]
```

# Java8からJava9へのレベルアップ！

てれれ、てってって〜ん:musical_note:

## 1. Jshell
コマンドラインからJavaを実行できるもののようです。
コマンドラインで`jshell`コマンドを入力するだけで、実行ができます。

Java8では当然存在しない機能なので、以下のようにコマンドを実行しても、`command not found`になります。
![スクリーンショット 2024-11-06 21.35.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1655179/41154686-c534-ab4a-a951-38bf786f73fb.png)

Java9ではどうでしょうか？
このようにコマンドを実行すると`jshell >`と入力受付待ちの状態になりました。
![スクリーンショット 2024-11-06 21.34.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1655179/717ec3db-5457-d72c-23c1-9185eb2ad751.png)

簡単な処理ですが、変数を持たせたり、出力したりできました！
![スクリーンショット 2024-11-06 22.03.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1655179/3e8d5aed-b05a-fe73-1595-b58e4dd43e8a.png)

終了する際は、

```shell
/exit
```

を実行することで、通常のコマンドラインに戻ることが可能です。

## 2. 不変なコレクション
外部ライブラリなしで、Java8で、不変なコレクションを作成するには、`Collections`クラスの `unmodifiableList`などを利用します。ただ、これは既存のコレクションをラップする方法であり、元のコレクションが変更されると不変性が保たれません。

```java
List<String> mutableList = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<String> immutableList = Collections.unmodifiableList(mutableList);
```

もしくは、`Stream API`を利用して作成することも可能です。

```java
List<String> immutableList = Stream.of("a", "b", "c")
    .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
```

それをJava9では、

```java
List<String> immutableList = List.of("a", "b", "c");
```

と作成することが可能になっています。
実行してみると、実行時エラーになることも確認できました。
![スクリーンショット 2024-11-06 22.42.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1655179/34ed052c-25a4-bc7b-5a53-6e14875f54f4.png)

どうようにMap/Setも不変にすることができるみたいでした。

## 3. try-with-resources文の変更

みんな大好きtry-with-resources文ですね。
Java8では、当然以下のように実装しますね。

```java
import java.io.BufferedReader;
import java.io.FileReader;

public class TryWithResources {
  public void useTryWithResources() {
    try (BufferedReader br = new BufferedReader(new FileReader("tryWithResources.txt"))) {
      String line;
      while ((line = br.readLine()) != null) {
        System.out.println(line);
      }
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }
}
```

Java9では、try節の中でクラスを宣言する必要がなくなりました。

```java
import java.io.BufferedReader;
import java.io.FileReader;

public class TryWithResources {
  public void useTryWithResources() throws Exception {

    // このように実装が可能
    BufferedReader br = new BufferedReader(new FileReader("tryWithResources.txt"));

    try (br) {
      String line;
      while ((line = br.readLine()) != null) {
        System.out.println(line);
      }
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }
}
```

## 4. Stream APIの変更

Java9で、Stream APIに`takeWhile`、`dropWhile`、`ofNullable`メソッドが追加されました。

### `takeWhile`メソッド

先頭から連続して、指定した条件を満たす値を取得するメソッドです。
条件が不一致になるまでの値を全て取得してくれます。

```java
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1);
list.stream()
    .takeWhile(i -> i < 5)
    .forEach(System.out::println);

/*
* 実行結果:
* 1
* 2
* 3
* 4
*/
```

上記の例では、先頭から5より小さい数を連続で取得するため、`1, 2, 3, 4`までが出力されます。リストの`5`が判定された時点で、それ以降の値は取得されません。
そのため、例えば、上記例におけるリストの先頭が`5以上の値`の場合、`takeWhile`が返却するStreamは空になります。

### `dropWhile`メソッド

先頭から連続して、指定した条件を満たす値を除去し、残りを取得するメソッドです。
条件が不一致になった時点以降の値を全て返却します。

```java
List<Integer> list = List.of(1, 2, 3, 4, 5, 5, 4, 3, 2, 1);
list.stream()
    .dropWhile(i -> i < 5)
    .forEach(System.out::println);
/*
* 実行結果:
* 5
* 5
* 4
* 3
* 2
* 1
*/
```

上記の例では、先頭から5より小さい数を連続でスキップ(除去)するため、初めて`5以上`の値がチェックされた時点で、それ以降の値が表示されます。

### `ofNullable`メソッド

引数に指定する値がnullでない場合にStreamを返すメソッドです。

```java
List<Integer> list = List.of(1, 2, 3);
Map<Integer, String> map = new HashMap<>() {
  {
    put(1, "one");
    put(2, "two");
    put(4, "four");
  }
};
list.stream()
    .map(map::get)
    .flatMap(Stream::ofNullable)
    .forEach(System.out::println);
/*
* 実行結果:
* one
* two
*/
```

上記の例では、listに1,2,3が入っており、mapのkeyには、1,2,4が入っている。
もし仮に以下のように`ofNullable`の処理を記述していない場合、listの`3`がmapのkeyに存在しないため、nullも返却される。

```diff_java
// 上の例からofNullableの行を削除した場合、
list.stream()
    .map(map::get)
-    .flatMap(Stream::ofNullable)
    .forEach(System.out::println);
/*
* 実行結果:
* one
* two
* null
*/
```

`ofNullable`メソッドを利用することで、nullをStreamから弾くことができるということになります。
 

## 5. Interface内のprivateメソッド

Java8では、`Interface`に**defaultメソッド**と**staticメソッド**が定義できるになったらしいです。
defaultメソッドとは、`Interface`にも関わらず、共通処理を定義したときに実装できるメソッドですね。

Java9では、さらに**privateメソッド**を実装できるようになりました。

このprivateメソッドは、default/staticメソッドから呼び出すことが可能です。

Java8では正しくエラーになりますね。
![スクリーンショット 2024-11-16 22.05.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1655179/5be5dc5b-5767-d3ec-d793-95031d768904.png)

## その他

以下は、詳しく調べてみましたが、あまり自分の中で使い所もなさそうだったので、割愛しました。

- 匿名クラスでのダイヤモンド演算子
- リアクティブストリーム
- Optionalクラスでのメソッドの追加
- Deprecatedアノテーションの強化
- モジュールシステム(Project Jigsaw)
- ... エトセトラエトセトラ

# 終わりに
Java9の新機能をざっと触れてみましたが、いかがだったでしょうか？
個人的には、不変コレクションの作成が簡単になった点が印象的でした。

現場ではまだJava8を使うことが多いですが、時代の先を行く新しい機能にはやはり惹かれるものがありますね。

次回は、Java9に引き続き、Java10以降の新機能(未元物質)についても触れていこうと思っています。今回同様、実際のコード例を交えながら解説していこうと思います。
一緒にレベルアップを目指しましょう！