はじめに
こんにちは、もんすんです。
私の実務経験として、最近Javaが一番長くなってきました。
特に保守業務が多いので、Javaのバージョンは最新ではなく、LTSであるJava8を常用しています。
さて、先日JJUG CCC Fall 2024というJavaのカンファレンスに参加してきました。
そのカンファレンスで、とある会社のブースが「Java23クイズ」を出題しており、意気揚々と参加したのですが…悉く間違えてしまいました。
(唯一正解したのは、Effective Java 第2版
の表紙の絵柄だけ……)
レベル0扱いされてもおかしくない状況でした。
そこで、大恥をかいた私は思いました。
レベルアップしたい!!と。
Java8は素敵なバージョンで、Lambda式やStream APIといった要素が追加されましたが、それ以降もJavaの進化は止まりません。
未来の現場で時代遅れの開発者とならないためにも、最新の情報を頭に入れ、日々の鍛錬を怠らないようにしたいです。
某ラノベにおける、音声ファイルでの脳波へ干渉云々とかがあれば一瞬で解決するかもしれませんが、現実はそう甘くありません。
地道な努力が必要です。
今回は、Java9をJava8と比較しながら、新しく追加された機能やその活用方法を紹介していきます。
この記事を通じて、だれかの超能力ではなく、技術力を高める助けとなれば幸いです。
※今回の記事は、以下の記事を参考にさせていただきました。
環境
今回、Java8とJava9の環境をそれぞれDockerで構築し、その中で作業をしていきます。
環境差異はありますが、とりあえずJavaが使えればOKと思って使うことにしました。
Java8
FROM amazoncorretto:8u432-al2023-jre
# 作業ディレクトリを設定
WORKDIR /app
# コンテナを常時起動状態にする
CMD ["tail", "-f", "/dev/null"]
Java9
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へのレベルアップ!
てれれ、てってって〜ん
1. Jshell
コマンドラインからJavaを実行できるもののようです。
コマンドラインでjshell
コマンドを入力するだけで、実行ができます。
Java8では当然存在しない機能なので、以下のようにコマンドを実行しても、command not found
になります。
Java9ではどうでしょうか?
このようにコマンドを実行するとjshell >
と入力受付待ちの状態になりました。
終了する際は、
/exit
を実行することで、通常のコマンドラインに戻ることが可能です。
2. 不変なコレクション
外部ライブラリなしで、Java8で、不変なコレクションを作成するには、Collections
クラスの unmodifiableList
などを利用します。ただ、これは既存のコレクションをラップする方法であり、元のコレクションが変更されると不変性が保たれません。
List<String> mutableList = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<String> immutableList = Collections.unmodifiableList(mutableList);
もしくは、Stream API
を利用して作成することも可能です。
List<String> immutableList = Stream.of("a", "b", "c")
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
それをJava9では、
List<String> immutableList = List.of("a", "b", "c");
と作成することが可能になっています。
実行してみると、実行時エラーになることも確認できました。
どうようにMap/Setも不変にすることができるみたいでした。
3. try-with-resources文の変更
みんな大好きtry-with-resources文ですね。
Java8では、当然以下のように実装しますね。
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節の中でクラスを宣言する必要がなくなりました。
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
メソッド
先頭から連続して、指定した条件を満たす値を取得するメソッドです。
条件が不一致になるまでの値を全て取得してくれます。
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
メソッド
先頭から連続して、指定した条件を満たす値を除去し、残りを取得するメソッドです。
条件が不一致になった時点以降の値を全て返却します。
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を返すメソッドです。
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も返却される。
// 上の例から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メソッドから呼び出すことが可能です。
その他
以下は、詳しく調べてみましたが、あまり自分の中で使い所もなさそうだったので、割愛しました。
- 匿名クラスでのダイヤモンド演算子
- リアクティブストリーム
- Optionalクラスでのメソッドの追加
- Deprecatedアノテーションの強化
- モジュールシステム(Project Jigsaw)
- ... エトセトラエトセトラ
終わりに
Java9の新機能をざっと触れてみましたが、いかがだったでしょうか?
個人的には、不変コレクションの作成が簡単になった点が印象的でした。
現場ではまだJava8を使うことが多いですが、時代の先を行く新しい機能にはやはり惹かれるものがありますね。
次回は、Java9に引き続き、Java10以降の新機能(未元物質)についても触れていこうと思っています。今回同様、実際のコード例を交えながら解説していこうと思います。
一緒にレベルアップを目指しましょう!