SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

【最新Javaアップデート解説】変更点と過去バージョンからのおさらい

【Java 23リリース】Project Amberの新しい機能でより自然なコーディングがしやすくなる

第2回:Javaのバージョンアップの大きな流れと主なツールの変更点

  • X ポスト
  • このエントリーをはてなブックマークに追加

コンストラクタにおける定義記述に関する改善(JEP482 - Second Preview)

 コンストラクタで、親クラスのコンストラクタであるsuper()を実行する前に何らかの処理をしたい場合があります。例えば、リスト5のように引数の型などを変更したい場合です。

[リスト5]コンストラクタ前の処理(src/java/jp/enbind/jep482/Jep482.javaの抜粋)
public class Jep482 extends ParentClass{
    //  省略
    //  (1) コンストラクタ内で使用する為のstaticメソッドを定義
    private static InputStream fileToStream(File file) throws FileNotFoundException {
            FileInputStream fis = new FileInputStream(file);
            return new BufferedInputStream(fis);
    }
    public Jep482(File file) throws FileNotFoundException {
        super(fileToStream(file)); // (2) コンストラクタでの引数で利用する
    }
    // 省略
}

 これまでのJavaでは、superを実行する前に処理ができなかったために、(1)(2)のようにメソッドをわけるなどのテクニックが必要でした。しかし、この処理をsuperの前に実行しても本来は何も問題ないはずで、むしろ、この制約により直感的なコード記述を阻害していました。今回の修正では、リスト6のように開発者にとってよりわかりやすい記述が可能になります。

[リスト6]より直感的なコンストラクタの記述例(src/java/jp/enbind/jep482/Jep482.javaの抜粋)
public class Jep482 extends ParentClass{
    public Jep482(File file) throws FileNotFoundException {
        if(!file.exists()){
            throw new FileNotFoundException();
        }
        FileInputStream fis = new FileInputStream(file);
        BufferedInputStream bos = new BufferedInputStream(fis);
        super(bos);
    }
}

 この例では、superを呼び出す前にthisに一切アクセスしていないので何も問題が生じないことは明白ですが、場合によってはthisにアクセスしたい場合もあります。例えば、リスト7のように自分自身のクラスのメンバー変数に値を設定したい場合です。

[リスト7]クラスフィールドへのアクセス(src/java/jp/enbind/jep482/Jep482.javaの抜粋)
public class Jep482 extends ParentClass{
    private String childName;
    public Jep482(String name) {
        this.childName = name;
        super(name);
    }
}

 このような使い方であれば、実際にはsuperの前に処理を記述しても、後に記述しても実質的な違いはほとんどありません。同じ意味なら、どちらの書き方も許してほしいと思う方もいるはずです(Javaに慣れた開発者であればsuperの後に書くのが制限と覚えてしまっている方も多いと思いますが)。このような無意味な制限がなくなるのは、一般的には望ましいことです。

 もちろん、superを最初に記述する意味があるのは、このクラスのインスタンスを安全に作成するためです。例えば、インスタンスができあがっていないのに、親クラスで定義している変数や、各インスタンスメソッドなどが使えないなどの制限は、これまでと同様に課されます(エラーとなります)。

プリミティブ型におけるswitchやinstanceofなどを使った記述形式の改善(JEP455 - Preview)

 Javaではプリミティブ型とオブジェクト型でさまざまな違いがあり、プリミティブ型では記述可能であるのに、オブジェクト型だと記述できない場合があります。

switchの条件分岐処理におけるプリミティブ型への対応

 Java21からはリスト8のようにswitchにおいてより柔軟な条件分岐ルールが記述できます。

[リスト8]オブジェクト型でのswitchを使った条件処理(src/java/jp/enbind/jep455/Jep455.javaの抜粋)
public void sample1_java21(Integer value){
    switch (value){
        case Integer i when i > 10 -> System.out.println("value is greater than 10");
        case Integer i when i < 10 -> System.out.println("value is less than 10");
        default -> System.out.println("default case");
    }
}

 しかし、これとほぼ同じ意味にもかかわらず、リスト9のように記述するとコンパイルエラーになります。

[リスト9]プリミティブ型でのswitchを使った条件処理(src/java/jp/enbind/jep455/Jep455.javaの抜粋)
public void sample1_after_java21(int value){
    switch (value){
        case int i when i > 10 -> System.out.println("value is greater than 10");
        case int i when i < 10 ->System.out.println("value is less than 10");
        default -> System.out.println("default case");
    }
}

//  表示されるエラー内容
//   error: unexpected type
//            case int i when i > 10 -> System.out.println("value is greater than 10");
//                 ^
//  required: class or array
//  found:    int

 分かりにくいエラーですが、Java21までは条件分岐においてプリミティブ型に対応していなかったわけです。しかし、今回の対応によってどちらでも使えるようになるので、こういったわかりにくさが解消されます。

instanceofにおけるプリミティブ型への対応

 同じく、プリミティブ型ではinstanceofの記述ができませんでした。個人的には、"instance of"という名称から、インスタンスという概念ではないはずのプリミティブ型では使わないのでは、とも思います。しかし、オブジェクト型と同じような意味で、安全なcastのために使いたいといった動機もわかります。

 例えば、int型をbyte型や、double型をint型に安全にcastしたい場合などです。その場合、リスト10のように利用できます。

[リスト10]プリミティブ型でのinstanceofの動作(src/java/jp/enbind/jep455/Jep455.javaの抜粋)
int val = 120;

// 値が変わってしまう値の場合には、 instanceof でチェックが出来るようになった
// -128 〜 127は trueになる
if(val instanceof byte b1){
    System.out.println("value is " + b1);
}
else{
    System.out.println("different value: " + val + " -> " + (byte)val);
}

double val2 = 5.1;
// 5.0 はtrue , 5.1 はfalseなど、値が変わってしまうケースなど
if(val2 instanceof int f1){
    System.out.println("value is " + f1);
}
else{
    System.out.println("different value: " + val2 + " -> " + (int)val2);
}

 byte型は-128~127の範囲まで扱えるので、この範囲のint型であればbyte型に変換しても値の意味は変わりません。この場合にはinstanceofでの値はtrueになります。しかし、それ以外の範囲ではfalseになります。

 同様の考え方として、doubleでの5.1をintに変換しようとすると5のように小数点情報が抜け落ち、値の意味が変わってしまいます。この場合にはfalseになります。一方、5であれば、doubleからintに安全に同じ値としてcastが可能なのでtrueになります。

 また、Java16から使えるようになったinstanceofの中での変数定義を使えば、castする必要もないため、より安全なコードが書けます。一方、double型から小数点以下の情報を取り除くためにint型へcastする形の使い方をしている場合には、instanceofを使った型変換はできません。

無名変数と無名パターン(JEP456)

 この機能は「無名」と訳されることが多いですが、より意味的に近い日本語に訳すなら「使用しないの変数のための特別変数名」としたほうが分かりやすいと思います。

 例えば、リスト11のような記述では、同じ変数名を使いがちになる場合があります。

[リスト11]catchでの変数名が重複した書き方
try{
    //  処理の実行
}
catch(Exception ex){ // (1) exと命名
    try{
        Thread.sleep(2000);
        // 時間をおいて再度処理をする
    }
    catch(Exception ex){ // (2) ここで変数名の重複によりコンパイルエラーになる
        //  エラー処理
    }
}

 最初の処理では何らかの想定できないエラーがあり得ます。エラーが生じた場合には、時間をおいてから再度処理したいケースがあるでしょう。

 この場合、最初のエラーはどのような理由であれcatchするのですが、特に原因までは考慮しない場合があります。このとき、処理の記述はしないため変数名は何でもよいのですが、(1)のようによく使われる変数名であるexとつけてしまいがちです。

 当然この後ではexという変数名は使えなくなるため、このままでは、(2)でコンパイルエラーが生じてしまいます。こういった意味のない処理であっても便宜上、変数を定義しなくてはならない場合として「_(アンダースコア)」のみの変数が使えます。これを使った場合、リスト12のように書き換えることができます。

[リスト12]無名変数を用いた書換コード例
catch(Exception _){
    try{
        Thread.sleep(2000);
        // 時間をおいて再度処理をする
    }
    catch(Exception ex){
        //  エラー処理
    }
}

 このように、使わない変数のために考慮する手間が少なくなります。

戻り値を利用しない場合の無名変数

 通常の変数は、有効なスコープに1つしか定義できませんが、無名変数はリスト13のように同じスコープ内でも何度も利用することが可能です。

[リスト13]繰り返し利用できる無名変数の例(src/java/jp/enbind/jep456/Jep456.javaの抜粋)
Queue words = new ArrayDeque<>(List.of("dummy1","dummy2","apple","banana","cherry"));

//  (1) 最初の2つは必ず意味がないので、取得しても棄てる
var _ = words.poll();
var _ = words.poll();

var item = words.poll();

 (1)では、キューにある意味のない最初の2つの値を取り除く処理をしています。このような処理はプロトコル処理や特定のフォーマット処理などではよくあります。その場合には、リスト14のようにこれまで記述しているケースがありました。

[リスト14]無名変数が使えない場合の書き方
words.poll();
words.poll();

 例では、Queueクラスのpoll()なので、コメントがなくても想像しやすいです。しかし、これが独自に定義したクラスの場合、このような値を返すメソッドを使っていながら値を使わないのは、書き間違いなのか意図したものなのかわかりにくくなります。

 もちろん、その旨のコメントを書けばよいのですが、この無名変数を使えば、例えコメントがなくても「値を取得しているが使わない」といった意味が明確にわかるようになります。

無名パターン

 続いて、instanceofやswitchなどのパターン定義内で利用しない変数を明示的にする場合にも利用できます。

[リスト15]パターン定義の中で使用する無名変数の例(src/java/jp/enbind/jep456/Jep456.javaの抜粋)
record Dish(String name,int calories){}
record Dinner(String name,Dish dish1,Dish dish2){};

Object todayDinner = new Dinner("スペシャルディナー",new Dish("メイン",600),new Dish("前菜",300));

//  使わない引数は "_"にする
if( todayDinner instanceof Dinner(String name,Dish(String d1name,int _),Dish(String d2name,int _))){
   System.out.println("今日のメニューは「" + name + "(" + d1name + "," + d2name + ")」" );
}

 このようにパターン内で使う場合、使う定義と使わない定義が明確になるため、意図がわかりやすいだけではなく、コーディングのむだも減らせます。

次のページ
文字列テンプレート(JEP459 - JDK23で廃止)

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
【最新Javaアップデート解説】変更点と過去バージョンからのおさらい連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/20334 2024/10/24 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング