switch
文を使用しないコードベースは存在しません。パフォーマンス面からも,if/else
文より多く使用する傾向があります。昔ながらのJavaのswitch
文はJavaが生まれた時から存在しているので,私たちはそれに -- 特にその特異さに慣れています。
Javaのswitch文の現在のデザインは,C++などの言語に厳密に従っており,既定ではフォールスルーセマンティクスをサポートします。この制御フローは,低レベルのコードを書く場合には便利ですが,レベルの高いコンテキストで使用されるようになると,柔軟性よりもエラーの起こしやすさが上回り始めます。
Java言語でパターンマッチングがサポートされるようになるにつれて,既存のswitch文の不規則さが障害になってきています。switchブロックの制御フロー,switchブロックが既定では全体でひとつのスコープとして扱われること,switchが文としてのみ機能すること,などが問題視されているのです。
Java 12ではswitch
が拡張されて,新たな機能が加えられました。これまでswitch文であったものを,通常の拡張switch文または"switch式"として使用することが可能になり,コーディングが簡単になります。
これによって"traditional"または"simplified"という,2つのスコープと制御フロー動作が可能になります。これらの変更は,日々のコーディングを簡略化すると同時に,将来的にswitch文/式でパターンマッチングを使用するための準備段階にもなっています。
新たな機能
JDK 9からは新たに,従来よりも短期間のリリース計画が導入されました。新しい6ヶ月のリリースカデンツは,Javaをより生産的で革新的なものにするために,機能をより早く,高い頻度で提供することを目標としたものです。
最近のJDK 9, 10, 11,そして今後登場するJDK 12(2019年3月19日予定)や次期リリースではすでにこれが採用されており,多くのAPI拡張とともにJava言語の新機能が毎回提供されています。
新しいプロジェクト
こうした拡張や新機能を可能にするためには,言語の特定の側面に集中するプロジェクトが必要になります。そうすることによって,Java言語エンジニアのグループが,巨大なJDKのすべての機能や開発ではなく,新たな機能強化に集中することが可能になるのです。
そのために、多くのプロジェクトが立ち上げられ、それぞれが言語の異なる面に注目しています。これらのプロジェクトには、OpenJDKのネットのWebサイトにあるプロジェクトメニューのセクションからアクセスできます。少し挙げただけでも、PanamaやValhalla、Amber、Jigsaw、Loomなど、数多くのプロジェクトがあります。今回私たちが注目するのはProject Amberです。
Project Amber
Project AmberはCompiler Groupがスポンサです。この名前を見ただけでも、プロジェクトの目標が分かるでしょう。そのとおり -- このプロジェクトの目標は、Oracle JEPプロセス下でJEP候補として受け入れられるような、小さく、生産性を重視したJava言語機能を探求することにあります。
以下に示したすべてのJEPと関連JDK(もしあれば)の一覧で分かるように、Project Amberは現在、インキュベーションないし提供済の状態にあります。
現在進行中のJEO
- JEP 302 Lambda残件
- JEP 305 パターンマッチの例 (プレビュー)
- JEP 325 Switch式 (プレビュー、JDK 12)
- JEP 326 生文字列リテラル (プレビュー)
- JEPドラフト8209434 簡潔化一般
すでに提供済のJEP
パターンマッチングによるJavaの拡張
パターンマッチングは1960年代から、SNOBOL4やAWKのようなテキスト指向言語,HaskellやMLなどの関数型言語、さらに最近ではScalaなどのオブジェクト指向言語にまで(最も近くではMicrosoftのC#言語があります)、多種多様なスタイルでプログラミング言語に取り入れられてきました。
Project Amberは、Java言語にパターンマッチングを追加します。パターンマッチングは、オブジェクトから特定条件のコンポーネントを取り出すための共通ロジックを実現にすると同時に、それをより簡潔かつ安全に実行可能にします。例えばJEP 305ではinstanceof
演算子(対象とするJDKはまだ決まっていません)が、JEP 325ではswitch
式(JDK 12で提供予定)が、これによって拡張されます。
新しいswitch文を使用する
Java SE 9のリリース以来、対話型環境ツール(REPL)のJShellで新しいJava言語やAPI機能を試すことが慣例になりました。IDEなど他のソフトウェアをインストールする必要はありません – JDKだけで十分です。
この機能はすべてのJDKに添付されているので、新たに拡張されたswitch文や、あるいはswitch式など、Java SE 12の新しい言語機能を手軽に試すことができます。必要なのは,JDK 12早期アクセスビルドをダウンロードしてインストールすることだけです。インストールはこちらのリンクから可能です。
ダウンロードが成功したならば,好きな場所にバイナリを解凍して,JDKのbinフォルダを作り,システムのどこからでもアクセスできるようにします。
なぜJShellなのか
Java言語の構文を手早く勉強したり,新しいJava APIやその機能を試したり,複雑なコードのプロトタイプしたりする場合,私はいつも,現代的な言語ならばほとんど用意されている,インタラクティブなプログラミング環境ツールを使うようにしています。
一般的に次のようなプロセスを含む,コードの編集,コンパイル,実行という面倒なサイクルに入らなくていいからです。
- プログラム全体を記述する。
- コンパイルしてエラーを取り除く。
- プログラムを実行する。
- どこが悪いのかを見つける。
- 編集する。
- このプロセスを繰り返す。
JShellとは何か?
インタラクティブなプログラミング環境として,Java Shellの意味を持つJShellツールが用意されたことで,JavaはリッチなREPL(Read-Evaluate-Print-Loop)の実装を持つようになりました。なぜこれが重要なのでしょう?簡単です。JShellが,Java言語や拡張ライブラリの機能を手軽に試し,見つけ,経験するための,高性能で親しみやすい環境を提供してくれるからです。
JShellを使えば,プログラム要素をひとつずつ入力して,その結果をすぐに確認し,必要ならば修正することができます。プログラム全体をコンパイルする代わりに,このREPLでJShellコマンドやJavaコードのスニペットを記述するのです。
JShellの紹介としてはこれで十分ですが,InfoQでは先日,このツールの詳細な解説記事を公開しています。JShellの機能をより詳しく学ぶために,"Hands-on Java 10 Programming with JShell [Video]"と題したビデオトレーニングを用意しました。JShellをマスタするための一助になると思います。このビデオはPackt Webサイトからも入手可能です。
JShellセッションを開始する
ツールとの対話を始めるために最初にすることは,次の手順で新しいJShellセッションを開始することです。
- Microsoft Windowsでは,コマンドプロンプトを開いてjshellと入力し,
Enter
を押すだけです。 - Linuxならば,shellウィンドウを開いてjshellと入力し,
Enter
を押します。 - macOS(以前のOS X)を使用しているならば,Terminalウィンドウを開いた上でコマンド"jshell"を入力し,最後に
Enter
を押します。
さあ! 新しいJShellセッションが起動しました。次のようなメッセージに続いて,jshell>
プロンプトが表示されるはずです。
mohamed_taman:?$ jshell --enable-preview
| Welcome to JShell -- Version 12-ea
| For an introduction type: /help intro
jshell>
最初の行の"Version 12-ea"は,Java SE JDK 12早期アクセス版(early access)を使用していることを示しています。JShellでは情報メッセージの前に縦棒(|)が付けられています。これでJavaコード,あるいはJShellコマンドとスニペットを入力する準備が整いました。
ここで--enable-preview
というオプションが指定されていることに気付いたと思います。これは何でしょう?このオプションを使用すると,公式にはJDKの一部になっていない,現時点では"プレビュー"状態の新しい言語機能のロックを解除することができるのです。これらの機能はまだ実験とフィードバックの段階であるため,既定では無効になっています。
このフラグを使うことで,開発者としてプレビュー機能を試して,改善のためのフィードバックを提供したり,あるいは気に入らない部分をフィードバックすることが可能になります。今回の記事では,新しいswitch式の機能を試しますので,JShellセッションの起動時にこのオプションを含めておく必要があります。
注記: これはプレビュー機能ですので,まだAmberメーリングリストにフィードバックを送ることが可能です。
理屈はこのくらいにして,早速新しいswitch文と式のスニペットを使って概念を理解し,この素晴らしい機能の使い方を学ぶことにしましょう。
switch文と式をハッキングする
まず最初に,現在の状況について知っておく必要があります。また,switch
文の現行エディションの状況や,さらには日常のコーディングで私たちが行っている方法についても確認しておきましょう。
Day
を変数として持つ単純なメソッドを考えてみましょう。このメソッドは通常の日(Sat, San, Mon...
など)を含むDay
列挙をスイッチし,それに基づいて"週末"か"週日"かを返すものです。
先に起動したJshellセッションで,次のようなDayt列挙
を定義しましょう。
jshell> enum Day{
...> NONE,
...> SAT,
...> SUN,
...> MON,
...> TUS,
...> WED,
...> THU,
...> FRI;
...> }
| created enum Day
次にメソッド"String wahllsToday (Day day)
"を定義して,day
の値でスイッチして(通常のswitchを使用します),次のように週日(working)または週末を返すようにします。
jshell> String whatIsToday(Day day){
...> var today = "";
...> switch(day){
...> case SAT:
...> case SUN: today = "Weekend day";
...> break;
...> case MON:
...> case TUS:
...> case WED:
...> case THU:
...> case FRI: today = "Working day";
...> break;
...> default: today = "N/A";
...> }
...> return today;
...> }
| created method whatIsToday(Day)
jshell> whatIsToday(Day.SUN)
$3 ==> "Weekend day"
Although this function works perfectly, and if we used the original version with the many break statements for each case, it introduces visual noise which often makes it hard to debug the errors. 同時に,コードが必要以上に冗長になっていて,break文を誤って忘れると,思いがけないフォールスルーが発生することになります。
上の例ではフォールスルー機構を使うことで,通常同じ処理を行うよりもbreakの使用を減らしていますが,それでもまだ非常に長くなっています。
従来のswitch文を拡張する
新たに"矢印"("switchラベル付きルール")形式を提案したJEP 325では,"case L->"と書くことで,ラベルにマッチした場合にラベルの右のコードのみを実行することを示すことができます。この記法は,switch
文でもswitch
式でも使用可能です。
同じように,従来の"コロン"構文("switchラベル付きステートメントグループ")も両方のcaseで使用できます。
コロンも新しい矢印の構文も,どちらのcaseでも使用できるので,コロン(:)が使用されているからswitch文である,あるいは"矢印"(->)が使用されているからswitch式である,という意味にはなりません。
それでは,従来の論理文の書き換えをしてみましょう。例えば,前述の"String whatIsToday(Day day)
"メソッドは,次のようにすれば,もっと簡略化したものに書き換えることができます。
jshell> /edit whatIsToday
JShellの"/edit
"コマンドを実行すると,JShell edit padが開いて,今回のメソッドのような,大きなコードスニペットを簡単に編集することができます。次に,JShell edit padで,以下のようにメソッドを修正してください。
String whatIsToday(Day day){
var today = "";
switch(day){
case SAT, SUN -> today = "Weekend day";
case MON, TUS, WED, THU, FRI -> today = "Working day";
default -> throw new IllegalArgumentException("Invalid day: " + day.name());
}
return today;
}
修正が終わったらExit
ボタンをクリックして変更を登録し,現在の"JShell>
"セッションに戻ります。値を指定して,もう一度メソッドを実行してみましょう。
jshell> /edit whatIsToday
| modified method whatIsToday(Day)
jshell> whatIsToday(Day.SUN)
$5 ==> "Weekend day"
jshell> whatIsToday(Day.MON)
$6 ==> "Working day"
jshell> whatIsToday(Day.NONE)
| Exception java.lang.IllegalArgumentException: Invalid day: NONE
| at whatIsToday (#11:6)
| at (#12:1)
jshell>
上記の新しいswicth文の構造を詳しく見ると,多くの点で違っていることが分かると思います。まず最初に,"break"がなくなって明確かつ簡潔になっています。
第2に,switch文で新しい"矢印"構文("ラベルルール")形式を使うことによって,明示的に"break
"を指定する必要がなくなり,時に忌まわしいswitchの"フォールスルー"caseから私たちを救ってくれています。
"case L->
"swicthラベルの右のコードが式,ブロック,または(便宜上)throw
文に制限されている点にも注目してください。先程のメソッドでは,switch文のdefault
ケースで,無効な日付を識別するために"IllegalArgumentException
"をスローしています。
ひとつのcaseに複数のコンマ区切りラベルを指定する
従来の古いswitch
文では、複数のcaseで同じステートメントセットを実行する必要がある場合には、 フォールスルーのメカニズムを使用していました。
しかし,ここで第3の注意点として,先程の例では"単一のswitch case内にコンマで区切られた複数のラベル"という構造を使用しています。これは単に,同じ文を実行する共通のcaseをコンマで区切ったものです。
switchラベル付きステートメントグループ
JEP325では,従来の"コロン"構文("switchラベル付きステートメントグループ")も普通に使うことができるので,先程のメソッドをもう一度,このコロン形式で次のように書き換えることが可能です。
String whatIsToday(Day day){
var today = "";
switch(day){
case SAT, SUN: today = "Weekend day"; break;
case MON, TUS, WED, THU, FRI: today = "Working day"; break;
default: throw new IllegalArgumentException("Invalid day: " + day.name());
}
return today;
}
これはちょっとした練習として残しておきましょう。JShellコマンド"/edit whatIsToday
"を実行して,矢印トークン構文を使用しているこのメソッドを以前のようなコロン/break構文に書き直した上で,いくつかの値でうまく動くことを確かめてください。
新しいスイッチ式
この話題に入る前に,式として機能するようになる前のswitch
がどのようなものだったのか,疑問に思う人もいるでしょう。Java SE 12以前のswitchは常に文でした。つまり,制御フローを指示する命令形の構文であり,指示先になることはできなかったのです。一方で,式は常に,明確な値として評価されます。計算の最終的な目標は結果ですから,指示先に値を与えることが可能なのです。
文と式のち外について確認したので,新しいswitch式がどのように動作するのかを見てみましょう。実際に,日々のコードで使用している既存のswitch
文の多くは,そして前述のコードも,それぞれのアームが共通の変数に値を設定する,あるいは値を返すという意味で,基本的にはswitch
式のシミュレーションなのです。
新たにswitch
式が使えるようになったことで,ターゲットとなる変数に値を直接返すことが可能になったので,"StringwhatIsToday(Day day)
"メソッドを変更してみましょう。
String whatIsToday(Day day){
var today = switch(day){
case SAT, SUN -> "Weekend day";
case MON, TUS, WED, THU, FRI -> "Working day";
default -> throw new IllegalArgumentException("Invalid day: " + day.name());
};
return today;
}
修正したメソッドを詳しく見ると,switch文が式として書かれていることに気付くはずです。第1に,switchが等号の後にかかれています。第2に,文の一部であるため,これまでのswitch文とは違って,最後にセミコロンが必要です。第3に,矢印に続くものが戻り値になっています。
前述したように,新しいswitch式でも従来の"コロン"構文"case L:
"を使用することができるので,breakを使っていた先程のメソッドを次のように書き換えることが可能です。
String whatIsToday(Day day){
var today = switch(day){
case SAT, SUN: break "Weekend day";
case MON, TUS, WED, THU, FRI: break "Working day";
default: throw new IllegalArgumentException("Invalid day: " + day.name());
};
return today;
}
break
の2つの記法(値ありと値なし)は,メソッドでのreturn
の2形式に似ています。
ステートメントブロック
新しいswitch
文/式を"コロン"あるいは"矢印"構文のどちらかを使って日々の作業で使用する時は,形式がどちらであっても,右辺には単一の式を使用する場合がほとんどでしょう。
しかしながら,複数の式を同時に評価することが必要な場合もあります。ブロック{}を使えば,これを簡単に行うことができます。また,次の例のように,ひとつのswitch文/式の中で同じ変数名を複数回生成して使用できる点も非常に便利です。
String whatIsToday(Day day){
var today = switch(day){
case SAT, SUN: break "Weekend day";
case MON, TUS, WED, THU, FRI:{
var kind = "Working day";
break kind;
}
default: {
var kind = day.name();
System.out.println(kind);
throw new IllegalArgumentException("Invalid day: " + kind);
}
};
return today;
}
複合式
switch
式は"複合式(poly expression)"です。ターゲット型が分かっていれば,その型が各caseアームにプッシュダウンされますが,不明な場合は,各caseアームを組み合わせた標準型が計算されます。
次のswitch式代入について考えてみましょう。
jshell> var day = Day.SUN
day ==> SUN
jshell> var today = switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> default -> {
...> var len = day.name().length();
...> break len;
...> }
...> };
today ==> "Weekend day"
jshell> var day = Day.NONE
day ==> NONE
jshell> var today = switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> default -> {
...> var len = day.name().length();
...> break len;
...> }
...> };
4
today ==> 4
上のswitch式をもう少し詳しく見ると,2つのアームはString
を,defaultアームはint
変数のlen
を返していることが分かります。ターゲット変数がvar
なので,どちらも完全に動作するのです。
それでは,代入対象がvarではなく
int
であることを明示的に宣言してみましょう。これによってコンパイラは,"代入対象の型が既知である場合,これが各caseのアームにプッシュダウンされる"という条件を満足するようになります。
jshell> int today = switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> default -> {
...> var len = day.name().length();
...> System.out.println(len);
...> break len;
...> }
...> };
| Error:
| incompatible types: bad type in switch expression
| java.lang.String cannot be converted to int
| case SAT, SUN -> "Weekend day";
| ^-----------^
| Error:
| incompatible types: bad type in switch expression
| java.lang.String cannot be converted to int
| case MON, TUS, WED, THU, FRI -> "Working day";
| ^-----------^
新しいswitch機能ではできないこと
私たちの記述したswitch文/式に対して,コンパイラが怒って不満を言うような場合を検討してみましょう。そうすることで,このような状態を回避して,コンパイラを幸福にする方法を学ぶことができます :)。
switch文中のreturnでは値を返すことはできない
switch
文に関連するbreak
から値を返そうとすると,コンパイル時エラーになります。
jshell> String whatIsToday(Day day){
...> var today = "";
...> switch(day){
...> case SAT, SUN: break "Weekend day";
...> case MON, TUS, WED, THU, FRI: break "Working day";
...> default: throw new IllegalArgumentException("Invalid day: " + day.name());
...> }
...> return today;
...> }
| Error:
| unexpected value break
| case SAT, SUN: break "Weekend day";
| ^------------------^
| Error:
| unexpected value break
| case MON, TUS, WED, THU, FRI: break "Working day";
| ^------------------^
| Error:
| unreachable statement
| return today;
| ^-----------^
jshell>
矢印構文が示すのはswitch文内の文のみである
値を返すswitch文で矢印構文を使おうとすると,同じくコンパイル時エラーになります。この場合の矢印構文は値を返さずに,文を示すだけでなくてはなりません。
jshell> String whatIsToday(Day day){
...> var today = "";
...> switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> default -> throw new IllegalArgumentException("Invalid day: " + day.name());
...> }
...> return today;
...> }
| Error:
| not a statement
| case SAT, SUN -> "Weekend day";
| ^-----------^
| Error:
| not a statement
| case MON, TUS, WED, THU, FRI -> "Working day";
矢印構文とコロン/break構文を混在することはできない
"矢印"と従来のコロン/break構文を混在しようとすると,コンパイラは非常に腹を立ててエラーを出すでしょう。
jshell> String whatIsToday(Day day){
...> var today = "";
...> switch(day){
...> case SAT, SUN -> today = "Weekend day";
...> case MON, TUS, WED, THU, FRI: today = "Working day";break;
...> default -> throw new IllegalArgumentException("Invalid day: " + day.name());
...> }
...> return today;
...> }
| Error:
| different case kinds used in the switch
| case MON, TUS, WED, THU, FRI: today = "Working day";break;
| ^--------------------------------------------------------^
switch式ではすべてのケースを指定しなければならない
そして最後に,switch
式ですべてのswitchケースが指定されていない場合にも,同じくコンパイルエラーになります。指定されていないものがあると,次のようなエラーが発生します。
jshell> String whatIsToday(Day day){
...> var today = switch(day){
...> case SAT, SUN -> "Weekend day";
...> case MON, TUS, WED, THU, FRI -> "Working day";
...> };
...> return today;
...> }
| Error:
| the switch expression does not cover all possible input values
| var today = switch(day){
| ^-----------...
switch式ですべてのケースを指定するのは容易ではありませんし,いらいらするのは確かですが,修正は簡単です -- 単にdefault
を追加するだけで,問題なくコンパイルすることができます。上記のコードを正しくコンパイルする方法は,読者にお任せしたいと思います。
switchの今後
古いswitch
と同じく,新しく拡張されたswitch文とswith式のどちらも,列挙型で問題なく動作します。では,他の型ではどうでしょうか?拡張された"従来型"も"式"形式も同じで,String,int,short,byte,char
やそれぞれのラッパ型でも動作します。現時点では何も変わっていません。
将来のJavaバージョンでのswitchの拡張では,例えば,これまでは許可されていないfloat,double
やlong
(およびそれらのボックスタイプ)でのswitchを可能にすることが目標になるはずです。
まとめ
今回の記事では,Project Amberとパターンマッチング,JEP 325がswitch
文を拡張して文と式のいずれにも使えるようにすること,どちらの形式でも"従来型"および"簡略型"のスコープと制御構造が可能であることを学びました。
これらの変更は日々のコーディングを簡単にするとともに,適切なbreak文を付け忘れた場合の"フォールスルー"問題によって生じるバグからあなたを救ってくれます。さらに,新しいswitch文/式を使い始める時に開発者が犯しがちな,最も一般的な間違いについても挙げました。
さらに今回の言語変更は,switch文/式へのパターンマッチング(JEP 305)導入の準備であると同時に,現在のswitchがfloatやdouble,long,さらにはそれらのラッパクラスによるスイッチのサポートへと拡張されるようになります。
終わりになりましたが,今回の記事を執筆したことをとてもうれしく思うとともに,読者の皆さんが記事を楽しんで頂けたらと願っています!もしそうでしたら,"like"ボタンをクリックして,この言葉を友人やソーシャルメディアに広げてください!
リソース
- Project Amber
- Javaのパターンマッチング
- JEP 325:スイッチ式
- JEP 305:instanceofのパターンマッチング
- JShellを使った実践的なJava 10プログラミング
- Clean Code Java SE 9を始めるには
著者について
Mohamed TamanContradeのディジタルサービスのシニアエンタープライズアーキテクトであり,Java Champion,Oracle Groundbraker Ambassador,Java SE.next()とJakartaEE.next()のAdopt,JCPメンバ,JCP Executive Committee元メンバ,JSR 354/363/373 Expert Groupメンバ,EGJUGリーダ,Oracle Egypt Architects Clubボートメンバです。氏はJavaを語り,モバイル,ビッグデータ,クラウド,ブロックチェーン,DevOpsを愛しています。国際的な講演者で,"JavaFX essentials"や"Getting Started with Clean Code, Java SE 9","Hands-On Java 10 Programming with JShell",および新刊"Secrets of a Java Champions"といった書籍やビデオの著者であり,Duke’s choice 2015と2014,JCP outstanding adopt-a-jar participant 2013といった賞を受賞しています。