はじめに
こんにちは。 マイクロアドでWebエンジニアをしている木田です。
今回は普段Java8を使用しているエンジニアがJava19を触ってみて便利だなと思った点をいくつかご紹介しようとおもいます。
マイクロアドではWebアプリ開発のサーバサイド言語としては以下を使用しています。
- Java
- Kotlin
Kotlinについては比較的新しい言語ということもあり、Java8にはない便利な組込APIがいくつも存在しています。
今までは、「Kotlinのほうがシンプルに書けていいな〜」と思ってましたが、
Java19を触ってみて、「あれ、Javaも負けてないぞ!」と思ったので両者を比較しながら紹介します。
※Java19での新機能ではなく、Java19で使用できるJava8にはなかった機能(Java9以降で追加された機能)の紹介になります。
目次
- はじめに
- 目次
- その1 Record Class 【JDK14〜】
- その2 テキストブロック 【JDK13〜】
- その3 switch式 【JDK12〜】
- その4 ローカル変数の型推論 【JDK10〜】
- その5 StreamのtoList() 【JDK16〜】
- その6 jshell 【JDK9〜】
- その7 sealedクラス 【JDK15〜】
- その8 instanceofのパターンマッチング 【JDK14〜】
- その9 単一ソース・ファイルの起動 【JDK11〜】
- その10 仮想スレッド 【JDK19〜】
- おわりに
その1 Record Class 【JDK14〜】
概要
- データを保持するだけのクラス(Java Beans)を簡単に実装できます
- Kotlinでいうdata classに当たります
- Kotlinのdata classと違いイミュータブルになります(=setterはない)
使用例
User.java
public record User (
Integer userId,
String userName
){}
Main.java
public class Main { public static void main(String[] args) { User taro = new User(1, "taro"); taro.userId() // 1 taro.userName() // "taro" } }
説明
- 以下は自動生成されます
- アクセサ(getter)
- コンストラクタ
- equals
- hashCode
- インスタンスの作成方法はこれまでと同じです
- getterはgetId(), getName()ではなくid(), name()のように使用します
- ブロックの中で定義するフィールドは必ずstaticにする必要があります
- Recordクラスの継承はできません
- 以下のことは可能です
- インターフェースの実装
- ジェネリクスの使用
- アノテーションの使用
参考
https://docs.oracle.com/en/java/javase/19/language/records.html
その2 テキストブロック 【JDK13〜】
概要
- 複数行にまたがる文章を簡単に読みやすく記述できます
- Kotlinにも同じ機能があり、書き方も同じです
使用例
String query = """ SELECT name, age FROM EMP WHERE name = 'John' AND age > 20"""; System.out.println(query); // 下記のようにインデントを保ったまま出力される /** * SELECT name, age * FROM EMP * WHERE name = 'John' * AND age > 20 */ String abc = """ aaa\ bbb\ ccc """; System.out.println(abc); // aaabbbccc
説明
- ほとんどの記号のエスケープが不要になります
- エスケープシーケンスは使用可能です
- インデントは以下のいずれかの位置が基準となります
- 一番左側にある非空白文字
- 一番左側にある終了デリミタ(
"""
)
- 開始のデリミタ(
"""
)のあとは必ず改行が必要です - 改行コードはLFです
- 末尾の空白は除去されます
- 「\」を入れることであえて改行させないこともできます
参考
https://blogs.oracle.com/otnjp/post/text-blocks-come-to-java-ja
その3 switch式 【JDK12〜】
概要
- switchを式で書けます
- 文と式の大きな違いは、文は結果を返さないのに対し、式は結果を返します。つまりswitchの結果を変数に代入できます。
- Kotlinでいうwhen式に近いです
- 「->」で書くことができます
使用例
enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; } Day day = Day.WEDNESDAY; // case A -> パターン int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> { ... もろもろの処理 ... yield 9; } }; System.out.println(numLetters); // 9 // case A: yield ◯◯ パターン int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY : yield 6; case TUESDAY : yield 7; case THURSDAY, SATURDAY : yield 8; case WEDNESDAY : ... もろもろの処理 ... yield 9; }; System.out.println(numLetters); // 9
説明
- 評価結果を返すようになりました
- case A, B, C, ...と複数のパターンを書くことができます
- 2通りの書き方ができます
- case A -> hoge;と書くことができます
- 「:」と「break」が不要!
- case A: yield ◯◯
- case A -> hoge;と書くことができます
- switch式は
yield
を使用して値を返すことができます
参考
https://docs.oracle.com/en/java/javase/13/language/switch-expressions.html
その4 ローカル変数の型推論 【JDK10〜】
概要
- ローカル変数としてのみ利用できます
- nullは入れることができません
- Kotlinには「val(再代入不可)」、「var(再代入可能)」があります
使用例
var url = new URL("http://www.oracle.com/"); var conn = url.openConnection(); var reader = new BufferedReader( new InputStreamReader(conn.getInputStream())); /** * 今までの書き方 * URL url = new URL("http://www.oracle.com/"); * URLConnection conn = url.openConnection(); * Reader reader = new BufferedReader( * new InputStreamReader(conn.getInputStream())); */ // for文で使用することもできる var list = List.of(1,2,3); for (var num : list) { System.out.println(num); } // try-with-resourcesで使用することもできる try (var input = new FileInputStream("validation.txt")) {...}
説明
- varを使用することでコードをスッキリさせることができます
- しかし、型が明記されていたほうが見やすい場合も多々あるので、考えて使用する必要があります
参考
https://docs.oracle.com/javase/jp/13/language/local-variable-type-inference.html
その5 StreamのtoList() 【JDK16〜】
概要
- streamの終端処理でList型に変換するとき、今までは
.collect(Collectors.toList())
を使用していたが、.toList()
で済むようになりました - ここでは紹介しませんが、Kotlinのコレクションは組込みAPIがすごい豊富です
使用例
var list = List.of(1,2,3,4,5,6,7,8,9,10); var evens = list.stream() .filter(e -> e % 2 == 0) .toList(); System.out.println(evens); // [2, 4, 6, 8, 10]
説明
.collect(Collectors.toList())
は長くて直感的ではないと思っていたのですが、今後は短くシンプルな書き方ができますCollectors.toList()
をstatic importすれば少し短く書くことはできますが、それよりも.toList()
のほうが短くて直感的です
参考
https://docs.oracle.com/javase/jp/16/docs/api/java.base/java/util/stream/Stream.html
その6 jshell 【JDK9〜】
概要
- JavaのREPL環境です
- Kotlinでも同様の機能があります
使用例
% jshell | JShellへようこそ -- バージョン19.0.1 | 概要については、次を入力してください: /help intro jshell> 1 + 1 $1 ==> 2 jshell> "test".toUpperCase() $2 ==> "TEST" jshell> import java.util.List; jshell> List. # ここでtabキーを押しています class copyOf( of( jshell> List.of(1,2,3) $4 ==> [1, 2, 3] jshell>
説明
- Javaをちょっと試したいというときに便利です
- importもできます
- ピリオドを入力した後にtabキーを押すと、メソッドの候補が表示されます
- 終了するときは
/exit
と入力する
参考
https://docs.oracle.com/javase/jp/9/jshell/introduction-jshell.htm
その7 sealedクラス 【JDK15〜】
概要
- 継承先・インターフェースの実装先を限定できます
- Kotlinにもあります
使用例
- sealedクラス Shape.java
public sealed class Shape permits Circle, Square, Rectangle { }
- sealedクラスのサブクラス
Circle.java
public final class Circle extends Shape { public float radius; }
Square.java
public non-sealed class Square extends Shape { public double side; }
Rectangle.java
public sealed class Rectangle extends Shape { public double length, width; }
説明
- サブクラスは同じファイルにも別のファイルにも書くことができます
- 同じファイルに書く場合、
permits
句を省略できます
- 同じファイルに書く場合、
- サブクラスには以下の制約があります
- コンパイル時にsealedクラスからアクセスできる必要があります
- sealedクラスを直接継承する必要があります
- サブクラスは
final
,sealed
,non-sealed
のいずれかの修飾子を付ける必要があります - 同じモジュールまたは同じパッケージ配下にある必要があります
- インターフェースにsealedをつけることもできます
参考
https://docs.oracle.com/javase/jp/15/language/sealed-classes-and-interfaces.html
その8 instanceofのパターンマッチング 【JDK14〜】
概要
- instanceofで検証とキャストを同時にできる
- Kotlinも「is」で同じことができる
使用例
Object obj = "abc"; if (obj instanceof String str) { System.out.println(str.toUpperCase()); // ABC } if (obj instanceof String str && str.length() == 3) { System.out.println(str.toUpperCase() + "は3文字です"); // ABCは3文字です }
説明
- 今までは
instanceof
で検証した後、if文の中で明示的にキャストする必要があったが、それをまとめて書くことができる instanceof
の右側の変数(バインディング変数と言います)のスコープは、instanceof
の結果がtrueとなる場所です
参考
https://docs.oracle.com/javase/jp/14/language/pattern-matching-instanceof-operator.html
その9 単一ソース・ファイルの起動 【JDK11〜】
概要
- 単一ファイルのみの実行の場合、
javac
コマンドを実行する必要はありません - mainメソッドだけを持つ
Main.java
を作ってちょっとした動作確認をするときには、java Main.java
とするだけでコードを実行できます - 2023年5月現在、最新バージョンのKotlin1.8.21にはこのような機能はありません
参考
https://docs.oracle.com/javase/jp/13/docs/specs/man/java.html
その10 仮想スレッド 【JDK19〜】
概要
- JVMの中でスレッドを作成・管理することで従来のスレッドよりスループットを向上させることができます
使用例
※下記サイトのサンプルを拝借して動かしてみました https://gihyo.jp/article/2022/08/tfc003-java19
import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; public class Main { public static void main(String[] args) { Main main = new Main(); // 仮想スレッドを使用 Thread.Builder builder = Thread.ofVirtual(); long startTime = System.currentTimeMillis(); main.createThreadPerTask(builder); long endTime = System.currentTimeMillis(); System.out.println((endTime - startTime) / 1000 + "秒かかりました"); } /** * 100万個スレッドを作成して並列処理 * @param builder */ public void createThreadPerTask(Thread.Builder builder) { try (var exec = Executors.newThreadPerTaskExecutor(builder.factory())) { IntStream.range(0, 1_000_000).forEach(i -> { exec.submit(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { throw new IllegalStateException(e); } System.out.println(Thread.currentThread().getName() + "タスク終了 for " + i); }); }); } } }
説明
- JVMの中でスレッドを作成・管理することで従来のスレッドよりスループットが向上します
- OS側のスレッドを効率よく使用できるのが仮想スレッドの特徴です
- 今まではJava側のスレッドとOS側のスレッドを1対1でつないでいたが、仮想スレッドは複数のJava側のスレッドが1つのOS側のスレッドを共有できます
- 待ちが多くスレッドの切り替えが多い処理に有効
参考
https://blogs.oracle.com/oracle4engineer/post/java-loom-virtual-threads-platform-threads-ja
おわりに
Java20が2023年3月にリリースされました。 LTS版のJava21は2023年9月にリリース予定です。 今後のJavaのさらなる発展に期待です。