このドキュメントでは、switch
のパターン・マッチングとレコード・パターン(どちらもJava SE 19のプレビュー機能)をサポートするためにJava言語仕様に加えられた変更について説明します。この機能の概要は、それぞれJEP 427およびJEP 405を参照してください。
変更は、JLSの既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。
変更ログ:
2022-06-01: - 14.11.1.2 実行時にnull caseラベルが解決され判断される方法の取扱いを修正しました。- 14.30.2 anyパターンに対するパターン・マッチングのセマンティクスを修正しました。
2022-05-17: 15.20.2 パターンの解決に関して欠落していたテキストを追加しました(これについては、14.30.2で明確に説明されていました)。
2022-05-10: 14.30.2 レコード・アクセサが突然完了することで生じるMatchException
について、この理由を原因として明示的に記録する例が必要です。
2022-04-26: JEP 427に更新しました(switch
のパターン・マッチング(第3プレビュー))。
2022-04-25: 一部のコード例を修正して、「洗練されていない」パターン・ラベルを「ガードなし」に名前変更しました。default switchラベルの周囲のテキストを明確化しました。
2022-04-20: amber-spec-experts
メーリング・リストに対するフィードバックに従った修正を含む2番目の草稿。
2022-04-07: 初稿をリリースしました。様々な不具合の修正に加えて、JEP 420プレビューからの主な変更点は次のとおりです。
第3章: 字句構造
3.9 キーワード
ASCII文字で構成された51個の文字シーケンスは、キーワードとして使用するために予約されており、識別子(3.8)として使用することはできません。その他の同じくASCII文字で構成された16個の文字シーケンスは、出現するコンテキストによっては、キーワードや別のトークンとして解釈される可能性があります。
- Keyword:
- ReservedKeyword
- ContextualKeyword
- ReservedKeyword:
- (次のいずれか)
abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while
_
(アンダースコア)- ContextualKeyword:
- (次のいずれか)
exports permits to with
module provides transitive yield
non-sealed record uses
open requires var
opens sealed
when
キーワード
const
およびgoto
は、現在使用されていませんが、予約されています。これにより、これらのC++のキーワードがプログラムで誤って出現した場合にJavaコンパイラがより適切なエラー・メッセージを作成できるようになります。
キーワード
strictfp
は廃止されています。新しいコードには使用しないでください。
キーワード
_
(アンダースコア)は、将来パラメータ宣言で使用できるように予約されています。
true
およびfalse
はキーワードではなく、booleanリテラルです(3.10.3)。
null
はキーワードではなく、nullリテラルです(3.10.8)。
入力文字から入力要素への還元(3.5)の際に、コンテキスト・キーワードと概念的に一致する入力文字のシーケンスは、次の両方の条件が満たされる場合にのみ、コンテキスト・キーワードに還元されます。
シーケンスは、次のように、構文文法(2.3)の適切なコンテキストで指定された終端として認識されます。
module
およびopen
については、ModuleDeclaration (7.7)で終端として認識される場合。exports
、opens
、provides
、requires
、to
、uses
およびwith
については、ModuleDirectiveで終端として認識される場合。transitive
については、RequiresModifierで終端として認識される場合。たとえば、シーケンス
requires
transitive
;
の認識では、RequiresModifierが使用されていないため、このtransitive
という用語はコンテキスト・キーワードではなく識別子に還元されます。var
については、LocalVariableType (14.4)またはLambdaParameterType (15.27.1)で終端として認識される場合。その他のコンテキストでは、識別子として
var
を使用しようとすると、var
はTypeIdentifier (3.8)ではないためにエラーが発生します。yield
については、YieldStatement (14.21)で終端として認識される場合。その他のコンテキストでは、識別子として
yield
を使用しようとすると、yield
はTypeIdentifierとUnqualifiedMethodIdentifierのどちらでもないためにエラーが発生します。record
については、RecordDeclaration (8.10)で終端として認識される場合。non-sealed
、permits
およびsealed
については、NormalClassDeclaration (8.1)またはNormalInterfaceDeclaration (9.1)で終端として認識される場合。when
については、SwitchBlock (14.11.1)で終端として認識される場合。
シーケンスの直前または直後に、JavaLetterOrDigitと一致する入力文字がない。
一般に、ソースコードで誤って空白を省略すると、「可能なかぎり長い変換」ルール(3.2)によって、入力文字のシーケンスが識別子としてトークン化されます。たとえば、12文字の入力文字シーケンス
p u b l i c s t a t i c
は、予約キーワードのpublic
およびstatic
としてではなく、常に識別子のpublicstatic
としてトークン化されます。2つのトークンを使用する場合は、それらを空白またはコメントで区切る必要があります。
前述のルールは、「可能なかぎり長い変換」ルールと連動して、コンテキスト・キーワードが現れるコンテキスト内で直感的な結果を生成します。たとえば、11文字の入力文字シーケンス
v a r f i l e n a m e
は、通常、識別子varfilename
としてトークン化されますが、ローカル変数宣言では、最初の3文字の入力文字は、前述のルールの最初の条件によって暫定的にコンテキスト・キーワードvar
として認識されます。ただし、シーケンス内の空白の不足を見落とすと、その次の8文字の入力文字を識別子filename
として認識することによる混乱が生じます。(これは、シーケンスが異なるコンテキストで異なるトークン化を経ることを意味します。ほとんどのコンテキストでは識別子ですが、ローカル変数宣言ではコンテキスト・キーワードと識別子です)。したがって、2番目の条件により、直後の入力文字のf
がJavaLetterOrDigitであるという理由で、コンテキスト・キーワードvar
の認識を防止します。そのため、シーケンスv a r f i l e n a m e
は、ローカル変数宣言の識別子varfilename
としてトークン化されます。
コンテキスト・キーワードの慎重な認識の別の例として、15文字の入力文字のシーケンス
n o n - s e a l e d c l a s s
について考えてみます。このシーケンスは、通常、識別子non
、演算子-
および識別子sealedclass
の3つのトークンに変換されますが、最初の条件が成立する通常のクラス宣言では、最初の10の入力文字は暫定的にコンテキスト・キーワードnon-sealed
として認識されます。シーケンスを3つの非キーワード・トークンではなく2つのキーワード・トークン(non-sealed
とclass
)に解釈されることを回避し、class
の前の空白を省略するプログラマに報酬を与えないようにするために、2番目の条件によってコンテキスト・キーワードの認識を防止します。そのため、シーケンスn o n - s e a l e d c l a s s
は、クラス宣言では3つのトークンとしてトークン化されます。
前述のルールでは、最初の条件は構文文法の詳細に依存しますが、Javaプログラミング言語のコンパイラは、入力プログラムを完全に解析することなくルールを実装できます。たとえば、コンテキスト・キーワードの有効な使用がキーワードとしてトークン化され、識別子の有効な使用が識別子としてトークン化されることがヒューリスティックで保証されているかぎり、ヒューリスティックを使用してトークナイザのコンテキスト状態を追跡できます。また、コンパイラでは常にコンテキスト・キーワードを識別子としてトークン化し、そうした識別子の特別な使用の認識は、それより後のフェーズに任せることもできます。
第5章: 変換およびコンテキスト
5.5 キャスト・コンテキスト
キャスト・コンテキストを使用すると、キャスト式(15.16)のオペランドは、キャスト演算子によって明示的に指定された型に変換されるようになります。割当てコンテキストおよび呼出しコンテキストとは対照的に、キャスト・コンテキストでは、5.1で定義されている変換をさらに多用できるとともに、これらの変換の組合せをより多く使用できます。
式がプリミティブ型の場合は、キャスト・コンテキストに次のいずれかを使用できます。
恒等変換(5.1.1)
拡張プリミティブ変換(5.1.2)
縮小プリミティブ変換(5.1.3)
拡張および縮小プリミティブ変換(5.1.4)
ボックス化変換(5.1.7)
ボックス化変換後の拡張参照変換(5.1.5)
式が参照型の場合は、キャスト・コンテキストに次のいずれかを使用できます。
恒等変換(5.1.1)
拡張参照変換(5.1.5)
拡張参照変換後のボックス化解除変換
拡張参照変換後のボックス化解除変換に続く拡張プリミティブ変換
縮小参照変換(5.1.6)
縮小参照変換後のボックス化解除変換
ボックス化解除変換(5.1.8)
ボックス化解除変換後の拡張プリミティブ変換
式にNULL型がある場合は、式は任意の参照型にキャストされることがあります。
キャスト・コンテキストで、チェックされているか部分的にチェックされていない縮小参照変換(5.1.6.2、5.1.6.3)が使用されると、式の値のクラスに対して実行時チェックが実行され、ClassCastException
が発生する可能性があります。それ以外の場合は、実行時チェックは実行されません。
チェックされていない縮小参照変換以外のキャスト変換によって式を参照型に変換できる場合、その式(またはその値)は、参照型とのダウンキャスト互換性があると表現されます。
参照型Sの式が、別の参照型Tとのダウンキャスト互換性がある場合、型Sは型Tにダウンキャスト変換可能であると表現します。
次の表に、特定のキャスト・コンテキストで使用される変換を列挙します。それぞれの変換は、次の記号で示されます。
- は、変換が許可されていないことを示します
≈は、恒等変換(5.1.1)を示します
ωは、拡張プリミティブ変換を示します(5.1.2)
ηは、縮小プリミティブ変換を示します(5.1.3)
ωηは、拡張および縮小プリミティブ変換を示します(5.1.4)
⇑は、拡張参照変換を示します(5.1.5)
⇓は、縮小参照変換を示します(5.1.6)
⊕は、ボックス化変換を示します(5.1.7)
⊗は、ボックス化解除変換を示します(5.1.8)
この表では、記号間のカンマは、キャスト・コンテキストがある変換とそれに続く別の変換を使用することを示します。型Object
は、8つのラッパー・クラスBoolean
、Byte
、Short
、Character
、Integer
、Long
、Float
、Double
以外の任意の参照型を意味します。
表5.5-A.プリミティブ型へのキャスト
キャスト先→ キャスト元↓ |
byte |
short |
char |
int |
long |
float |
double |
boolean |
---|---|---|---|---|---|---|---|---|
byte |
≈ | ω | ωη | ω | ω | ω | ω | - |
short |
η | ≈ | η | ω | ω | ω | ω | - |
char |
η | η | ≈ | ω | ω | ω | ω | - |
int |
η | η | η | ≈ | ω | ω | ω | - |
long |
η | η | η | η | ≈ | ω | ω | - |
float |
η | η | η | η | η | ≈ | ω | - |
double |
η | η | η | η | η | η | ≈ | - |
boolean |
- | - | - | - | - | - | - | ≈ |
Byte |
⊗ | ⊗,ω | - | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Short |
- | ⊗ | - | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Character |
- | - | ⊗ | ⊗,ω | ⊗,ω | ⊗,ω | ⊗,ω | - |
Integer |
- | - | - | ⊗ | ⊗,ω | ⊗,ω | ⊗,ω | - |
Long |
- | - | - | - | ⊗ | ⊗,ω | ⊗,ω | - |
Float |
- | - | - | - | - | ⊗ | ⊗,ω | - |
Double |
- | - | - | - | - | - | ⊗ | - |
Boolean |
- | - | - | - | - | - | - | ⊗ |
Object |
⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ | ⇓,⊗ |
表5.5-B.参照型へのキャスト
キャスト先→ キャスト元↓ |
Byte |
Short |
Character |
Integer |
Long |
Float |
Double |
Boolean |
Object |
---|---|---|---|---|---|---|---|---|---|
byte |
⊕ | - | - | - | - | - | - | - | ⊕,⇑ |
short |
- | ⊕ | - | - | - | - | - | - | ⊕,⇑ |
char |
- | - | ⊕ | - | - | - | - | - | ⊕,⇑ |
int |
- | - | - | ⊕ | - | - | - | - | ⊕,⇑ |
long |
- | - | - | - | ⊕ | - | - | - | ⊕,⇑ |
float |
- | - | - | - | - | ⊕ | - | - | ⊕,⇑ |
double |
- | - | - | - | - | - | ⊕ | - | ⊕,⇑ |
boolean |
- | - | - | - | - | - | - | ⊕ | ⊕,⇑ |
Byte |
≈ | - | - | - | - | - | - | - | ⇑ |
Short |
- | ≈ | - | - | - | - | - | - | ⇑ |
Character |
- | - | ≈ | - | - | - | - | - | ⇑ |
Integer |
- | - | - | ≈ | - | - | - | - | ⇑ |
Long |
- | - | - | - | ≈ | - | - | - | ⇑ |
Float |
- | - | - | - | - | ≈ | - | - | ⇑ |
Double |
- | - | - | - | - | - | ≈ | - | ⇑ |
Boolean |
- | - | - | - | - | - | - | ≈ | ⇑ |
Object |
⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ⇓ | ≈ |
例5.5-1.参照型のキャスト
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
final class EndPoint extends Point {}
class Test {
public static void main(String[] args) {
Point p = new Point();
ColoredPoint cp = new ColoredPoint();
Colorable c;
// The following may cause errors at run time because
// we cannot be sure they will succeed; this possibility
// is suggested by the casts:
cp = (ColoredPoint)p; // p might not reference an
// object which is a ColoredPoint
// or a subclass of ColoredPoint
c = (Colorable)p; // p might not be Colorable
// The following are incorrect at compile time because
// they can never succeed as explained in the text:
Long l = (Long)p; // compile-time error #1
EndPoint e = new EndPoint();
c = (Colorable)e; // compile-time error #2
}
}
ここで、最初のコンパイル時エラーが発生する理由は、クラス型のLong
とPoint
が関連していない(これらは同じではなく、どちらも他方のサブクラスではない)ためです。これらの間のキャストは常に失敗します。
2つ目のコンパイル時エラーは、EndPoint
型の変数が、インタフェースColorable
を実装する値を参照できないために発生します。これは、EndPoint
はfinal
型であり、final
型の変数は常にコンパイル時の型と同じ実行時の型の値を保持するためです。そのため、変数e
の実行時の型は正確にEndPoint
型になっている必要があり、EndPoint
型はColorable
を実装しません。
例5.5-2.配列型のキャスト
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
public String toString() { return "("+x+","+y+")"; }
}
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
ColoredPoint(int x, int y, int color) {
super(x, y); setColor(color);
}
public void setColor(int color) { this.color = color; }
public String toString() {
return super.toString() + "@" + color;
}
}
class Test {
public static void main(String[] args) {
Point[] pa = new ColoredPoint[4];
pa[0] = new ColoredPoint(2, 2, 12);
pa[1] = new ColoredPoint(4, 5, 24);
ColoredPoint[] cpa = (ColoredPoint[])pa;
System.out.print("cpa: {");
for (int i = 0; i < cpa.length; i++)
System.out.print((i == 0 ? " " : ", ") + cpa[i]);
System.out.println(" }");
}
}
このプログラムはエラーなしでコンパイルされ、次の出力を生成します。
cpa: { (2,2)@12, (4,5)@24, null, null }
例5.5-3.実行時の互換性のない型のキャスト
class Point { int x, y; }
interface Colorable { void setColor(int color); }
class ColoredPoint extends Point implements Colorable {
int color;
public void setColor(int color) { this.color = color; }
}
class Test {
public static void main(String[] args) {
Point[] pa = new Point[100];
// The following line will throw a ClassCastException:
ColoredPoint[] cpa = (ColoredPoint[])pa;
System.out.println(cpa[0]);
int[] shortvec = new int[2];
Object o = shortvec;
// The following line will throw a ClassCastException:
Colorable c = (Colorable)o;
c.setColor(0);
}
}
このプログラムはコンパイルにキャストを使用しますが、型に互換性がないため、実行時に例外をスローします。
第6章: 名前
6.3 宣言のスコープ
6.3.1 式のパターン変数のスコープ
6.3.1.6 switch
式
次のルール各ルールは、switchルールで構成されたswitchブロック(14.11.1)があるswitch
式(15.28)に適用されます。
switchラベルによって導入されるパターン変数は、関連付けられたswitchルール式、switchルール・ブロックまたはswitchルール
throw
文で明確に照合されます。switchラベルによって導入されるanyパターン変数が、関連付けられたswitchルール式、switchルール・ブロックまたはswitchルール
throw
文でスコープ内にすでにある場合は、コンパイル時にエラーになります。
次のルールは、switchラベルが付いた文グループで構成されたswitchブロック(14.11.1)があるswitch式に適用されます。
switchラベルによって導入されるパターン変数は、switchラベルが付いた文グループのすべての文で明確に照合されます。
switchラベルによって導入されるいずれかのパターン変数が、switchラベルが付いた文グループの文でスコープ内にすでにある場合、コンパイル時にエラーになります。
- switchラベルが付いた文グループ
(14.11.1)に含まれる文Sによって導入されるパターン変数は、このswitchラベルが付いた文グループ内のSに続くすべての文(存在する場合)で明確に照合されます。
6.3.2 文のパターン変数のスコープ
6.3.2.6 switch
文
次のルール各ルールは、switchルールで構成されたswitchブロック(14.11.1)があるswitch
文(14.11)に適用されます。
switchラベルによって導入されるパターン変数は、関連付けられたswitchルール式、switchルール・ブロックまたはswitchルール
throw
文で明確に照合されます。switchラベルによって導入されるanyパターン変数が、関連付けられたswitchルール式、switchルール・ブロックまたはswitchルール
throw
文でスコープ内にすでにある場合は、コンパイル時にエラーになります。
次のルールは、switchラベルが付いた文グループで構成されたswitchブロック(14.11.1)があるswitch式に適用されます。
switchラベルによって導入されるパターン変数は、switchラベルが付いた文グループのすべての文で明確に照合されます。
switchラベルによって導入されるいずれかのパターン変数が、switchラベルが付いた文グループの文でスコープ内にすでにある場合、コンパイル時にエラーになります。
- switchブロック文グループ
(14.11.1)に含まれるラベルが付いた文Sによって導入されるパターン変数は、switchブロック文グループ内のSに続くすべての文(存在する場合)で明確に照合されます。
6.3.3 パターンのパターン変数のスコープ
6.3.3.1 レコード・パターン
次のルールは、レコード・パターンpに適用されます。
pのレコード・コンポーネント・パターン・リスト内のパターンqごとに、qで宣言されたパターン変数は、リスト内でその後に続くすべてのレコード・パターン・コンポーネントで確実に照合されます。
qで宣言されたパターン変数が、すでにリスト内で後続のレコード・パターン・コンポーネントのスコープ内にあると、コンパイル時エラーになります。
このルールは、パターン変数は単一のレコード・コンポーネント・パターン・リストで最大1回しか宣言できないという線形性制約を強制します。2つのレコード・コンポーネントの値が等しくなるように指定することは、パターンで直接エンコードできませんが、後続のコードで処理する必要があります。
Object o = ... if (o instanceof Point(int x, int y) && (x == y)) { // Not the pattern Point(int x, int x)! System.out.println("Point on the diagonal"); }
レコード・パターンpが名前付きレコード・パターンである場合は、レコード・パターン変数が、すでにリスト内で後続のレコード・パターン・コンポーネントのスコープ内にあると、コンパイル時エラーになります。
6.3.4 Switchラベルのパターン変数のスコープ
パターン変数は、switchラベルおよびパターンに関連付けられた任意のwhen
式でサポートされるパターンによって導入でき、関連付けられたswitch
式(6.3.1.6)またはswitch
文(6.3.2.6)の関連部分のスコープ内にあります。
次の各ルールは、switchラベルに適用されます。
パターン変数は、pによって宣言される場合、パターンpをサポートするswitchラベルによって導入されます。
switchラベルでサポートされるパターンによって宣言されたパターン変数は、関連するすべての
when
式で確実に一致します。switchラベルでサポートされるパターンによって宣言されたanyパターン変数が、関連付けられた
when
式でスコープ内にすでにある場合、コンパイル時エラーになります。パターン変数は、関連付けられた
when
式eを持つパターンがtrueのときにeよって導入された場合、そのパターンをサポートするswitchラベルによって導入されます(6.3.1)。
第13章: バイナリ互換性
13.4 クラスの展開
13.4.2 sealed
、non-sealed
およびfinal
クラス
13.4.2.1 sealed
クラス
自由に拡張可能であったクラス(8.1.1.2)がsealed
として宣言されるように変更された場合、このクラスの既存のサブクラスのバイナリがロードされ、そのサブクラスがこのクラスの許容された直接サブクラス(8.1.6)でない場合には、IncompatibleClassChangeError
がスローされます。広く配布されるクラスについては、このような変更はお薦めしません。
final
として宣言されたクラスをsealed
として宣言されるよう変更しても、既存のバイナリとの互換性が失われることはありません。
sealed
クラスの許容された直接サブクラスのセットにクラスを追加しても、既存のバイナリとの互換性が失われることはありません。
許容された直接サブクラスを追加して
sealed
クラスを展開することは、バイナリ互換性がある変更とみなされます。以前にエラーなしでリンクされていた既存のバイナリ(網羅的なswitch
(14.11.1)を含むクラス・ファイルなど)は引き続きエラーなしでリンクされるためです。網羅的なswitch
を含むクラス・ファイルは、それが切り替えたsealed
クラスが新しい許容された直接サブクラスを持つように階層の所有者によって拡張されても、リンクに失敗することはありません。JVMは、網羅的なswitch
を含むクラス・ファイルをリンクするときに網羅性チェックを実行する必要はありません。
網羅的な
switch
の実行は、コンパイル時に認識されなかった許容されている直接サブクラスのインスタンスが検出された場合に、エラーで失敗する(MatchException
がスローされる)ことがあります(14.11.3、15.28.2)。厳密に言えば、このエラーは、sealed
クラスのバイナリ互換性がない変更を示しているのではなく、より正確にはsealed
クラスの移行互換性がない変更を示しています。
sealed
クラスの許容された直接サブクラスのセットからクラスが削除された場合、削除されたクラスの既存のバイナリがロードされると、IncompatibleClassChangeError
がスローされます。
sealed
直接スーパークラスまたはsealed
直接スーパーインタフェースを持たないクラスからsealed
修飾子を削除しても、既存のバイナリとの互換性が失われることはありません。
sealed
直接スーパークラスまたはsealed
直接スーパーインタフェースを持つすべてのクラスはfinal
、sealed
またはnon-sealed
である必要があるため、sealedクラスCがsealed
直接スーパークラスまたはsealed
直接スーパーインタフェースを持っていた場合、sealed
修飾子を削除すると、Cを再コンパイルできなくなります。
13.4.26 enumクラスの展開
enumクラス内でenum定数を追加したり、並べ替えても、既存のバイナリとの互換性が失われることはありません。
sealed
クラス(13.4.2.1)と同様に、enumクラスにenum定数を追加することは、バイナリ互換性がある変更とみなされますが、コンパイル時に認識されていなかった新しいenum定数がswitch
によって検出された場合、網羅的なswitch
(14.11.1)の実行はリンケージ・エラーで失敗する(IncompatibleClassChangeError
がスローされる)ことがあります(14.11.3、15.28.2)。
enumクラスからenum定数を削除すると、そのenum定数に対応するpublic
フィールド(8.9.3)が削除されます。この結果は、13.4.8に明示されています。広く配布されるenumクラスについては、このような変更はお薦めしません。
他のあらゆる点において、enumクラスに関するバイナリ互換性のルールは標準クラスに関するものと同じです。
13.5 インタフェースの展開
13.5.2 sealed
およびnon-sealed
インタフェース
自由に拡張可能であったインタフェース(9.1.1.4)がsealed
として宣言されるように変更された場合、このインタフェースの既存のサブクラスまたはサブインタフェースのバイナリがロードされ、そのサブクラスまたはサブインタフェースがこのインタフェースの許容された直接サブクラスまたはサブインタフェース(9.1.4)でない場合には、IncompatibleClassChangeError
がスローされます。広く配布されるクラスについては、このような変更はお薦めしません。
sealed
インタフェースの許容された直接サブクラスまたはサブインタフェースのセットにそれぞれクラスまたはインタフェースを追加しても、既存のバイナリとの互換性が失われることはありません。
sealed
クラス(13.4.2.1)と同様に、sealed
インタフェースの許容された直接サブクラスまたはサブインタフェースを追加することは、バイナリ互換性がある変更とみなされますが、コンパイル時に認識されていなかった新しい許容された直接サブクラスおよびサブインタフェースのインスタンスがswitch
によって検出された場合、網羅的なswitch
(14.11.1)の実行はリンケージ・エラーで失敗する(MatchException
がスローされる)ことがあります(14.11.3、15.28.2)。
sealed
インタフェースの許容された直接サブクラスまたはサブインタフェースのセットからクラスまたはインタフェースが削除された場合、削除されたクラスまたはインタフェースの既存のバイナリがロードされると、IncompatibleClassChangeError
がスローされます。
sealed
として宣言されたインタフェースをnon-sealed
として宣言されるように変更しても、既存のバイナリとの互換性が失われることはありません。
non-sealed
インタフェースIには、sealed
直接スーパーインタフェースが必要です。sealed
直接スーパーインタフェースを持つすべてのインタフェースはsealed
またはnon-sealed
である必要があるため、non-sealed
修飾子を削除すると、Iを再コンパイルできなくなります。
sealed
直接スーパーインタフェースを持たないインタフェースからsealed
修飾子を削除しても、既存のバイナリとの互換性が失われることはありません。
sealed
直接スーパーインタフェースを持つすべてのインタフェースはsealed
またはnon-sealed
である必要があるため、sealedインタフェースIがsealed
直接スーパーインタフェースを持っていた場合、sealed
修飾子を削除すると、Iを再コンパイルできなくなります。
第14章: ブロックと文およびパターン
14.11 switch
文
switch
文は、式の値に応じて、いくつかの文または式の1つに制御を転送します。
- SwitchStatement:
switch
(
Expression)
SwitchBlock
Expressionはセレクタ式と呼ばれます。セレクタ式の型はchar
、byte
、short
、int
、Character
、Byte
、Short
、Integer
、String
またはenum型(8.9)である必要があり、そうでない場合、コンパイル時にエラーが発生します。
セレクタ式の型のこれらの制限は、次のセクションに定義されている、セレクタ式と互換性があるというswitchブロックの概念に含まれるようになりました。
14.11.1 Switchブロック
switch
文およびswitch
式(15.28)の両方の本体は、switchブロックと呼ばれます。このサブセクションでは、switch
文またはswitch
式のどちらに出現するかに関係なく、すべてのswitchブロックに適用される一般的なルールを示します。他のサブセクションでは、switch
文のswitchブロック(14.11.2)またはswitch
式のswitchブロック(15.28.1)のいずれかに適用される追加ルールを示します。
- SwitchBlock:
{
SwitchRule {SwitchRule}}
{
{SwitchBlockStatementGroup} {SwitchLabel:
}}
- SwitchRule:
- SwitchLabel
->
Expression;
- SwitchLabel
->
Block - SwitchLabel
->
ThrowStatement - SwitchBlockStatementGroup:
- SwitchLabel
:
{SwitchLabelBlockStatements:
}
- SwitchLabel:
case
CaseConstant {,
CaseConstant}default
- SwitchLabel:
- CaseOrDefaultLabel {
:
CaseOrDefaultLabel } - CaseOrDefaultLabel:
case
CaseElement {,
CaseElement }default
- CaseElement:
- CaseConstant
- Pattern [ Guard ]
null
default
- CaseConstant:
- ConditionalExpression
- Guard:
when
Expression
switchブロックは次のいずれかで構成できます。
Switchルール。これは、
->
を使用して、switchルール式、switchルール・ブロックまたはswitchルールthrow
文を導入します。またはSwitchラベルが付いた文グループ。これは
:
を使用してswitchラベルが付いたブロック文を導入します。
すべてのswitchルールおよびswitchラベルの付いた文グループは、switchラベル (case
ラベルまたはdefault
ラベルのいずれか)で始まります。複数のswitchラベルが、1つのswitchラベルが付いた文グループに許可されます。
case
ラベルには、1つ以上のcase
定数があります。すべてのcase
定数は、定数式(15.29)またはenum定数の名前(8.9.1)のいずれかである必要があり、そうでない場合、コンパイル時にエラーが発生します。
Switchラベルおよびそのcase
定数は、switchブロックに関連付けられていると言い表されます。switchブロックに関連付けられている2つのcase
定数が同じ値を持つことはなく、そうでない場合、コンパイル時にエラーが発生します。
すべてのswitchルールおよびswitchラベル付き文グループは、switchラベルで始まります。switchラベルは、1つ以上のcase
ラベルおよびdefault
ラベルで構成されます。case
ラベルには、1つ以上のcaseラベル要素があります。switchラベルは、case要素を含むcase
ラベルがある場合、case要素をサポートすると表現します。
パターン・ケース要素には、when
式が含まれていることがあります。パターンcase要素は、(i)その要素にwhen
式が関連付けられていない場合、または(ii)値がtrue
の定数式であるwhen
式がその要素に関連付けられている(15.29)場合に、ガードなしと表現されます。case要素は、パターンcase要素でない場合や、またはガードなしパターンcase要素である場合にはガードされません。
switchブロックのすべてのswitchラベルについて、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。
switchラベルでサポートされるすべての
case
定数は、定数式(15.29)またはenum定数の名前(8.9.1)のいずれかです。switchラベルでは、同じ値の複数の
case
定数をサポートしません。switchラベルでは、
case
定数とパターンの両方をサポートしません。switchラベルでは、複数の
default
をサポートしません。switchラベルでは、
default
サポートしません。また、default
ラベルは付けられません。switchラベルは、複数の
default
ラベルを使用できません。switchラベルでは、複数の
null
をサポートしません。switchラベルでは、複数のパターンをサポートしません。
switchラベルでは、パターンと
default
の両方をサポートしません。switchラベルでは、パターンと
default
ラベルの両方をサポートしません。switchラベルでは、レコード・パターンと
null
の両方をサポートしません。
これらのルールはswitchラベルの形式を制限します。複雑さの多くは、文グループのswitchラベル内でcaseラベル要素を結合する2つの方法が歴史的にサポートされていることが原因です(たとえば、
case 1: case 2
とcase 1,2
)。
パターンのcase要素に関連付けられたwhen
式には、boolean
型またはBoolean
型が必要です。when
式で使用されているが宣言されていない変数は、finalであるかeffectively finalであることが必要です(4.12.4)。when
式がfalse
の値を持つ定数式(15.29)の場合は、コンパイル時エラーになります。
switchルールのswitchラベルが複数のcase
またはdefault
ラベルで構成されている場合、コンパイル時にエラーになります。
つまり、
case 1: case 2 -> ...
は有効なswitchルールではありませんが、case 1, 2 -> ...
と書くことができます。
switchラベルは、default
ラベルがある場合や、default
case要素をサポートする場合に、default switchラベルであると表現されます。switchブロックに複数のdefault switchラベルがある場合、コンパイル時エラーになります。
次の両方に該当する場合、switch
文またはswitch
式のswitchブロックは、セレクタ式T型との互換性があります。
Tがenum型でない場合は、switchブロックに関連付けられているすべての
case
定数にTとの代入互換性がある(5.2)。- Tがenum型の場合は、switchブロックに関連付けられているすべての
case
定数がT型のenum定数である。
switch
文またはswitch
式のswitchブロックは、switchブロック内のすべてのswitchラベルがTとのswitch互換性がある場合、セレクタ式T型とのswitch互換性があります。switchラベルは、そのラベルがサポートするすべてのcase要素(存在する場合)が次のように定義されているTとのswitch互換性がある場合、Tとのswitch互換性があります。
case
定数cはT型とのswitch互換性があります。このTは、cにTとの代入互換性がある場合(5.2)、char
、byte
、short
、int
、Character
、Byte
、Short
、Integer
またはString
のいずれかになります。case
定数cは、cがT型のenum定数である場合、enum型Tとのswitch互換性があります。パターンcase要素pは、pがT型で適用可能な場合(14.30.3)、Tとのswitch互換性があります。
null
case要素は、Tが参照タイプの場合、Tとのswitch互換性があります。default
case要素は、すべての型とのswitch互換性があります。
特に、
default
ラベルのみを含むswitchブロックは、すべてのセレクタ式との互換性があります。
switch
文またはswitch
式のswitchブロックは、セレクタ式の型とのswitch互換性が必要であり、そうでない場合はコンパイル時にエラーが発生します。
switchラベルは、前者が後者に適用されるすべての値に適用される場合、別のswitchラベルより優先されると表現します。switchブロック内のswitchラベルが、それに続くswitchラベルより優先される場合は、コンパイル時にエラーが発生します。優先を決定するルールは次のとおりです。
case
定数をサポートするswitchラベルは、同じcase
定数をサポートする別のswitchラベルより優先されます。これにより、次に示すように、2番目に現れる
case
定数2
が決して一致しない例が除外されます。int i = ...; switch (i) { case 1, 2 -> System.out.println("1 or 2"); case 2, 3 -> System.out.println("2 or 3"); // Error! }
ガードなしパターンpをサポートするswitchラベルは、pがqより優先される場合、パターンqをサポートする別のswitchラベルよりも優先されます(14.30.3)。
when
式でパターンをサポートするswitchラベルは、同じパターンをサポートする別のswitchラベルより優先されるとはみなされません。これにより、次の一般的なパターン・プログラミング・スタイルが可能になります。Object o = ...; switch (o) { case Integer i when i <= 0 -> ... case Integer i when i > 0 -> ... case Integer i -> ... ... }
唯一の例外は、
when
式の値がtrue
の場合です。次に例を示します。Object o = ...; switch (o) { case Integer i when true -> ... // Allowed but ... why? case Integer i -> ... // Error - dominated switch label ... }
ただし、
when
式でパターンをサポートするswitchラベルは、パターンのみをサポートするswitchラベルよりも優先されます。たとえば、次はコンパイル時にエラーになります。Object o = ...; switch (o) { case List<?> l -> ... case List<?> l when l.size() == 2 -> ... // Error - dominated by previous switch label ... }
パターンが別のパターンより優先される定義(14.30.3)は型に基づきます。たとえば、次はコンパイル時にエラーになります。
Object o = ... switch (o) { case Object obj -> System.out.println("Object"); case String s -> System.out.println("String"); // Error - dominated switch label }
正確に言うと、優先は型のイレイジャに関して定義されます。たとえば、型パターン
ArrayList<? extends Number> al
は、型パターンArrayList<Number> aln
より優位であり、その逆も同様です。つまり、たとえば、次のswitchブロックはコンパイル時にエラーになります。List<Number> l = ...; switch (l) { case ArrayList<? extends Number> al -> ... case ArrayList<Number> aln -> ... // Error - dominated switch label ... }
パターンpをサポートするswitchラベルは、pが
case
定数cよりも優先される場合、cをサポートする別のswitchラベルよりも優先されます。これは、次のように定義されます。型Tのパターン変数を宣言する型パターンは、プリミティブ型Pのラッパー・クラス(5.1.7)がTのイレイジャのサブタイプである場合、Pの定数cよりも優先されます。
T型のパターン変数を宣言する型パターンは、EがT型のイレイジャのサブタイプである場合、E型のenum定数cよりも優先されます。
カッコ付きパターンは、cよりも優先されるパターンが含まれている場合、定数cよりも優先されます。
最初のルールは、プリミティブ
case
定数が優先されるcaseに対応します。Integer i = ...; switch (i) { case Integer j -> ... case 42 -> ... // Error - dominated! }
2番目のルールは、enum
case
定数が優先されるcaseに対応します。enum E { A, B, C } E value = ...; switch (value) { case E e -> ... case A -> ... // Error - dominated! }
レコード・パターンについてのルールはありません。
when
式の分析(通常は決定不能)は試行されません。たとえば、セレクタ式の値が42
の場合、最初のswitchラベルが一致しない場合でも、次の結果はコンパイル時エラーになります。Integer i = ...; switch (i) { case Integer j when j != 42 -> ... case 42 -> ... // Error - dominated! ... }
case
定数をサポートするswitchラベルは、パターンをサポートするラベルの前に現れる必要があります。次に例を示します。Integer i = ...; switch (i) { case 42 -> ... case Integer j when j < 50 -> ... // All integers less than 50 // *except* 42 case Integer j -> ... // All other integers }
次の両方が当てはまるswitchラベルが付いた文グループで構成されるswitchブロックに文がある場合、コンパイル時にエラーになります。
パターン変数を導入するswitchラベルでラベル付けされている。
switchブロック内で前に文があり、その文を正常に完了できる(14.22)。
この条件は、switchラベルで宣言されたパターン変数がスコープ内にあるが、成功したパターン・マッチングがない、switchラベルが付いた文に到達する可能性を除外するために必要です。たとえば、型パターン
Integer i
をサポートするswitchラベルでラベル付けされた文には、先行する文グループから到達できるため、パターン変数i
は初期化されません。Object o = "Hello"; switch (o) { case String s: System.out.println("String: " + s ); case Integer i: System.out.println(i + 1); // Error! Can be reached // without matching switch label ... }
switch
式またはswitch
文に次の両方が当てはまる場合、コンパイル時にエラーになります。
switchブロックにデフォルトswitchラベルがあり、
switchブロック内にパターンpをサポートするswitchラベルがあり、そのpはセレクタ式の型で無条件である(14.30.3)。
セレクタ式のタイプで無条件であるパターンは、その名前が示すとおり、すべての値に一致するため、default switchラベルのように動作します。
14.11.1.1 網羅的なSwitchブロック
switch
式またはswitch
文のswitchブロックは、(i) switchブロックにdefault switchラベルが含まれている場合、または(ii) switchラベルでサポートされているすべてのガードなしcase要素が含まれているセットがTに対して網羅的である(次のように定義されている)場合、T型のセレクタ式に対して網羅的です。
case要素のセットは、Eのすべてのenum定数が含まれている場合、enumクラス型Eに対して網羅的です。
デフォルトswitchラベルは許可されますが、すべてのenum定数がswitchラベルに出現する場合は必須ではないことに注意してください。次に例を示します。
enum E { F, G, H } static int testEnumExhaustive(E e) { return switch(e) { case F -> 0; case G -> 1; case H -> 2; // No default required! }; }
型Tで無条件であるパターン(14.30.3)が含まれている場合、型Tに対するcase要素のセットは網羅的です。
パターンは、所定の型のすべての値と一致することがわかっている場合は、その型で無条件であると表現されます。たとえば、型パターン
Object o
は、型String
で無条件です。case要素のセットは、Tのすべての適用可能な許可された直接サブタイプに対して網羅的である場合、
abstract
およびsealed
クラスまたはCという名前のインタフェースが含まれている型Tに対して網羅的です。型Tは、次のいずれかが成立する場合にのみ、Cという名前のインタフェースまたは
abstract
およびsealed
クラスが含まれます。TはCの名前を付けるクラス型であり、クラスCは
sealed
およびabstract
の両方です。TはCの名前を付けるインタフェース型であり、インタフェースCは
sealed
です。Tは型変数であり、その上限はCを含んでいます。
Tは交差型T1
&
...&
Tnであり、型TiはC (1 ≤ i ≤ n)を含んでいます。
クラスまたはインタフェース型Tは、次のいずれかが成立する場合、
abstract
およびsealed
クラスまたはインタフェースのCを指定するクラスまたはインタフェース型Sの適用可能な許可された直接サブタイプと表現されます。Tはクラスまたはインタフェース型Dであり、(i)DはCの許可された直接サブクラスまたはサブインタフェースであり、(ii) Dは汎用クラスまたはインタフェースではなく、(iii) SはDにダウンキャスト変換可能です(5.5)。
TはD<
?
, ...,?
>型(つまり、すべてがワイルドカードであるn個の型引数を持つDのパラメータ化)であり、(i) DはCの許可された直接サブクラスまたはサブインタフェースであり、(ii) Dはn個のパラメータを持つ汎用クラスまたはサブインタフェースであり、(iii) SはD<?
, ...,?
>にダウンキャスト可能です。
default switchラベルは許可されますが、
abstract
およびsealed
クラスまたはインタフェースの許容された直接サブクラスおよびサブインタフェースのすべてに対してswitchブロックが網羅的である場合は不要です。次に例を示します。sealed interface I permits A, B, C {} final class A implements I {} final class B implements I {} record C(int j) implements I {} // Implicitly final static int testExhaustive1(I i) { return switch(i) { case A a -> 0; case B b -> 1; case C c -> 2; // No default required! }; }
switchブロックには、型
A
、B
およびC
の値と一致するパターンをサポートするスイッチ・ラベルが含まれていて、それ以外の型I
のインスタンスは許可されないため、このswitchブロックは網羅的です。許容された直接サブクラスまたはサブインタフェースは、汎用
sealed
スーパークラスまたはスーパーインタフェースの特定のパラメータ化のみを拡張できるという事実は、switchブロックが網羅的であるかどうかを判断するときには考慮が不要な場合があることを意味します。次に例を示します。sealed interface J<X> permits D, E {} final class D<Y> implements J<String> {} final class E<X> implements J<X> {} static int testExhaustive2(J<Integer> ji) { return switch(ji) { // Exhaustive! case E<Integer> e -> 42; }; }
このセレクタ式には型
J<Integer>
があり、ji
の値がD
のインスタンスである可能性はないため、許可された直接サブクラスD
について考慮する必要はありません。case要素のセットPは、(i) QがRという名前の型を持つレコード・パターンのみが含まれたPの空ではないサブセットであり、(ii) レコード・クラスRのレコード・コンポーネントが0個またはQが型Uでコンポーネントcから網羅的である場合に、レコード・クラスRを指定する型Tに対して網羅的です。このcはRのレコード・コンポーネント・リスト内の最初のコンポーネントであり、UはTの対応するコンポーネント・フィールドの型です。
レコード・クラスRを指定するレコード・タイプTがあるとすると、次の場合に、レコード・パターンPのセットは型Uでレコード・コンポーネントcから網羅的になります。- P内のすべてのレコード・パターンからのcに対応するコンポーネント・パターンを含むパターンのセットはUに対して網羅的であり、(ii) cがレコード・クラスRのレコード・コンポーネント・リストの最終コンポーネントではなく、次のいずれかが該当する場合。
セットQは、型Vでコンポーネントdから網羅的です。このdはcの後のコンポーネント、VはTの対応するコンポーネント・フィールドの型、QはU対して網羅的であるcに対応するコンポーネント・パターンを持つP内のすべてのレコード・パターンが含まれたパターンのセットです。
型Uは、
abstract
およびsealed
クラスまたはインタフェースDを指定するクラスまたはインタフェース型で、すべての適用可能な許可された直接サブタイプUに対して、その型でセットPはコンポーネントcから網羅的です。
switch
文または式は、そのswitchブロックがセレクタ式の型について網羅的である場合、網羅的です。
14.11.1.2 実行可能なSwitchブロックおよび実行時に適用されるSwitchラベルの決定
一部のパターンの意味は照合対象の式の型によって決まるため、パターン・マッチング(14.30.2)の実行前にパターンを解決しておく必要があります。そのため、switch
式またはswitch
文のswitchラベルでサポートされるanyパターンも解決する必要があります。
実行可能なswitch
式またはswitch
文は、次のように、セレクタ式Tの型に関してすべてのdefault
およびcase
ラベルが変換されているものです。
default
ラベルは、default
ラベルに変換されます。case
ラベルLは、まず、すべてのcase要素が次のように変換されたcase
ラベルに変換されます。case
定数cは、case
定数cに変換されます。default
case要素は、default
case要素に変換されます。null
case要素は、null
case要素に変換されます。パターンcase要素pは、パターンcase要素ラベルqに変換されます。このqは、型Tでパターンpを解決したときの結果です(14.30.2)。pに
when
式が関連付けられている場合、qは同じ式に関連付けられます。
結果として変換された
case
ラベルにnull
case要素とパターンcase要素pの両方がある場合(pは型Uのパターン変数xを宣言するパターン)、pは、型Uのxを宣言するanyパターン(14.30.1)に置き換えられます。前述したように、switchラベルがパターンと
null
の両方をサポートしている場合は、パターンをレコード・パターンにすることはできません(14.11.1)。この最後のステップでは、
switch
のパターン・マッチングの特殊なケースに対応します。たとえば、ラベルcase null, String s
はNULL参照値に適用され、さらに、この場合のパターン変数s
はNULL参照値で初期化されます。これは、型パターンを型String
のanyパターンに置き換えることで実現されます。
実行可能なswitch
文の実行(14.11.3)と実行可能なswitch
式の評価(15.28.2)の両方で、switchラベルがセレクタ式の値と一致するに適用されるかどうかを判断する必要があります。switchブロック内のswitchラベルが所定の値と一致するに適用されるかどうかを判断するために、の判断は、その値がswitchブロックに関連付けられたcase
定数と比較されます次のように進められます。次のようになります。
値がnull参照の場合、値に適用されるswitchブロックの最初の(ある場合) switchラベルを次のように決定します。
null
をサポートするswitchラベルは、そのswitchラベルもサポートするすべてのパターン(ある場合)と値が一致する場合に適用されます。パターン・マッチングが突然完了した場合は、適用されるswitchラベルの判断についても同じ理由で突然完了します。具体的には、
null
をサポートしていてanyパターンをサポートしていないswitchラベルは、null参照値に適用されることを意味します。
値がnull参照ではない場合、値に適用されるswitchブロックの最初の(ある場合) switchラベルを次のように決定します。
case
定数cをサポートするswitchラベルは、最初に値がボックス化解除変換(5.1.8)の対象になり、定数cがボックス化解除した値と等しくなる場合、Character
、Byte
、Short
またはInteger
型の値に適用されます。case
定数の1つが値と等しい場合、case
定数を含むcase
ラベルが一致します。ボックス化解除変換は、ボックス化解除される値がNULL参照にならないことが保証されるため、正常に完了する必要があります。
等価性は、
==
演算子で定義されます(15.21)。case
定数cをサポートするswitchラベルは、Character
、Byte
、Short
またはInteger
型でない値に適用されます(その値と定数cが等しい場合)。等価性は
==
演算子(15.21)によって定義されますが、値がString
の場合はString
クラスのequals
メソッドによって定義されます。パターンpをサポートするswitchラベルが値に適用されるかどうかの判断は、その値がパターンpと一致するかどうかをチェックする(14.30.2)ことから始まります。
パターン・マッチングが突然完了した場合は、適用されるswitchラベルを判断するプロセスについても同じ理由で突然完了します。
パターン・マッチングが成功して、パターンに
when
式が関連付けられていない場合は、このswitchラベルが適用されます。パターン・マッチングが成功して、パターンに
when
式が関連付けられている場合は、そのwhen
式が評価されます。その結果がBoolean
型の場合は、ボックス化解除変換(5.1.8)の対象になります。when
式または後続のボックス化解除変換(ある場合)の評価がなんらかの理由で突然完了した場合、適用するswitchラベルを判断するプロセスは同じ理由で突然完了します。それ以外の場合は、その結果の値が
true
のときに、switchラベルが適用されます。一致する前述のルールがswitchブロック内のいずれのswitchラベルにも適用されない場合は、default switchラベルが適用されます。case
ラベルがない場合は、default
ラベルが存在していれば、そのdefault
ラベルが一致します。switchブロックは最大で1つのデフォルトswitchラベルを持つことができます。
switchラベルは、複数のcase
case
定数を含めることがサポートできます。ラベルは、その定数のいずれかがセレクタ式の値と一致等しい場合、セレクタ式の値と一致しますに適用されます。たとえば、次のコードで、enum変数day
が表示されているenum定数のいずれかである場合、switchラベルは一致します。case
switch (day) { ... case SATURDAY, SUNDAY : System.out.println("It's the weekend!"); break; ... }
パターンをサポートするswitchラベルが適用される場合、これは、パターンに対する値のパターン・マッチング・プロセスが成功したことによるものです(14.30.2)。値がパターンと正常に一致すると、パターン・マッチングのプロセスは、パターンによって宣言されたパターン変数を初期化します。
null
は、定数式ではないためcase
定数として使用できません。case
null
が許可されている場合でも、そのcase
内のコードを決して実行できないため、好ましくありません。つまり、セレクタ式が参照型(すなわち、String
またはボックス化プリミティブ型またはenum型)である場合、セレクタ式が実行時にnull
に評価されると例外が発生します。Javaプログラミング言語の設計者の判断では、case
ラベル一致がない、またはdefault
ラベル一致がないより、例外を伝播するほうがよい結果になります。
CおよびC++で、
switch
文の本体は文(単数および複数)にすることができ、case
ラベルをその文で直接含む必要はありません。単純なループを考えてみましょう。for (i = 0; i < n; ++i) foo();
ここで
n
は正であると知られています。ダフスデバイスとして知られているトリックは、ループをアンロールするためにCまたはC++で使用できますが、これはJavaプログラミング言語では有効なコードではありません。int q = (n+7)/8; switch (n%8) { case 0: do { foo(); // Great C hack, Tom, case 7: foo(); // but it's not valid here. case 6: foo(); case 5: foo(); case 4: foo(); case 3: foo(); case 2: foo(); case 1: foo(); } while (--q > 0); }
幸いにも、このトリックは広く認識または使用されてはいないようです。さらに、今日ではあまり必要ではなくなりました。この種のコード変換は、適切に最新式の最適化コンパイラの領域にあります。
14.11.2 switch
文のSwitchブロック
switchブロック(14.11.1)の一般的なルールに加えて、switch
文のswitchブロックにはさらにルールがあります。
拡張switch
文とは、(i) セレクタ式の型がchar
、byte
、short
、int
、Character
、Byte
、Short
、Integer
、String
またはenum型でない文か、(ii) switchラベルの少なくとも1つがパターンまたはnull
をサポートする文かのいずれかです。
つまり、すべてのswitch
文のswitchブロックに対して次のすべてが当てはまる必要があり、そうでない場合はコンパイル時にエラーが発生します。
1つのみのdefault
ラベルがswitch
ブロックに関連付けられています。switchブロックのすべてのswitchルール式は、文の式です(14.8)。
switch
文は、switchブロックの矢印(->
)の右にどの式が出現するか、つまり、どの式をswitchルール式として使用できるかという観点で、switch
式と異なります。switch
文では、文の式のみをswitchルール式として使用できますが、switch
式では、任意の式を使用できます(15.28.1)。switch
文が拡張switch
文である場合、それは網羅的である必要があります。
Java SE 19より前の
switch
文(およびswitch
式)は、(i) セレクタ式の型が整数型(long
を除く)、列挙型またはString
に制限されていて、(ii)case
定数要素のみがサポートされていたという2つの制限がありました。さらに、switch
式とは異なり、switch
文は網羅的であることを必要としませんでした。これは多くの場合、バグの検出を困難にする原因になり、switchラベルは適用されず、switch
文は何もしません。次に例を示します。enum E { A, B, C} E e = ...; switch (e) { case A -> System.out.println("A"); case B -> System.out.println("B"); // No case for C! }
Java SE 19では、前述の2つの制限を取り除くという意味で
switch
文が拡張されました。Javaプログラミング言語の設計者が、拡張switch
文はswitch
式と揃える必要があり、網羅的である必要があると決めました。多くの場合、これは自明的なdefault
ラベルの追加によって達成されます。たとえば、次の拡張switch
文は網羅的ではありません。Object o = ...; switch (o) { // Error - non-exhaustive switch! case String s -> System.out.println("A string!"); }
しかし、それは簡単に網羅的にすることができます。
Object o = ...; switch (o) { case String s -> System.out.println("A string!"); default -> {} }
互換性の理由で、拡張
switch
文ではないswitch
文は網羅的である必要はありません。
14.11.3 switch
文の実行
実行可能なswitch
文(14.11.1.2)は、セレクタ式の最初の評価によって実行されます。その後、次のように処理されます。
セレクタ式の評価が突然完了する場合、
switch
文全体が同じ理由で突然完了します。それ以外の場合、セレクタ式の評価の結果が
null
であり、解決されたswitchブロックのswitchラベルが適用されないときは、NullPointerException
がスローされてswitch
文全体がその理由で突然完了します。適用するswitchラベルを決定するプロセスが突然完了する場合は、同じ理由でswitch
文全体が突然完了します。
- それ以外の場合、セレクタ式の評価の結果が型
Character
、Byte
、Short
またはInteger
のとき、ボックス化解除変換の対象になります(5.1.8)。この変換が突然完了する場合、switch
文全体が同じ理由で突然完了します。
セレクタ式の評価が正常に完了する場合、結果が非null
で後続のボックス化解除変換(ある場合)が正常に完了するとき、switch
文の実行は、解決されたswitchブロックに関連付けられた内のswitchラベルがセレクタ式の値と一致するに適用されるかどうかを決定することで続行されます(14.11.1.2)。その後、次のように処理されます。
適用するswitchラベルを決定するプロセスが突然完了する場合は、同じ理由で
switch
文全体が突然完了します。switchラベルが
一致しない場合は、適用されない場合は、次のいずれかが成立します。switch
文全体が正常に完了します。switch
文が拡張switch
文であり、セレクタ式の評価結果が列挙型の場合は、IncompatibleClassChangeError
がスローされ、その理由からswitch
文全体が突然完了します。switch
文が拡張switch
文であり、セレクタ式の評価結果が列挙型ではない場合は、MatchException
がスローされ、その理由からswitch
文全体が突然完了します。switch
文が拡張switch
文でない場合は、switch
文全体が正常に完了します。
switchラベルが
一致する適用される場合、次のいずれかが適用保持されます。switchルール式のswitchラベルである場合、switchルール式は必然的に文の式です(14.11.2)。文の式が評価されます。評価が正常に完了する場合、
switch
文は正常に完了します。評価の結果が値の場合、それは破棄されます。switchルール・ブロックのswitchラベルである場合、ブロックが実行されます。このブロックが正常に完了する場合、
switch
文は正常に完了します。switchルール
throw
文のswitchラベルである場合、throw
文が実行されます。switchラベルが付いた文グループのswitchラベルである場合、switchラベルに続くswitchブロックのすべての文が順番に実行されます。これらの文が正常に完了する場合、
switch
文は正常に完了します。それ以外の場合、
一致する適用されるswitchラベルに続く文がswitchブロック内になく、switch
文が正常に完了します。
switchブロックの文または式の実行が突然完了する場合、次のように処理されます。
ラベルなしの
break
のために文の実行が突然完了する場合、それ以上のアクションは行われず、switch
文は正常に完了します。ラベルありの
break
による突然の完了は、ラベルが付いた文の一般的なルールによって処理されます(14.7)。文または式の実行が他の理由のために突然完了する場合、
switch
文は同じ理由で突然完了します。yield
文による突然の完了は、switch式の一般的なルールによって処理されます(15.28.2)。
例14.11.3-1.switch
文のフォールスルー
セレクタ式がswitchラベルに一致しswitchラベルが適用され、そのswitchラベルがswitchルールに対するものである場合、switchラベルによって導入されたswitchルール式または文が実行されるのみです。文グループ用のswitchラベルの場合、そのswitchラベルに続くswitchブロックのすべてのブロック文が、後続のswitchラベルの後に出現するものを含めて実行されます。その効果は、CおよびC++でのように、文の実行が「ラベルをフォールスルー」できることです。
たとえば、次のプログラムの場合:
class TooMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.print("one ");
case 2: System.out.print("too ");
case 3: System.out.println("many");
}
}
public static void main(String[] args) {
howMany(3);
howMany(2);
howMany(1);
}
}
各case
のコードが次のcase
のコードにフォールスルーするswitch
ブロックを含みます。結果として、プログラムの出力は次のようになります。
many
too many
one too many
フォールスルーは判断しにくいバグの原因になる場合があります。コードがこのようにcase
からcase
にフォールスルーする予定ではない場合、break
文を使用して、いつ制御を転送する必要があるかを示すことができます。または、switchルールを次のプログラムのように使用できます。
class TwoMany {
static void howMany(int k) {
switch (k) {
case 1: System.out.println("one");
break; // exit the switch
case 2: System.out.println("two");
break; // exit the switch
case 3: System.out.println("many");
break; // not needed, but good style
}
}
static void howManyAgain(int k) {
switch (k) {
case 1 -> System.out.println("one");
case 2 -> System.out.println("two");
case 3 -> System.out.println("many");
}
}
public static void main(String[] args) {
howMany(1);
howMany(2);
howMany(3);
howManyAgain(1);
howManyAgain(2);
howManyAgain(3);
}
}
このプログラムは次を出力します。
one
two
many
one
two
many
14.30 パターン
パターンは、値に対して実行できるテストを示します。パターンは文および式のオペランドとして出現し、テストする値を提供します。パターンでは、パターン変数と呼ばれるローカル変数を宣言できます。
パターンに対して値をテストするプロセスはパターン・マッチングと呼ばれます。値がパターンに正常に一致する場合、パターン・マッチング・プロセスが、そのパターンによって宣言されたパターン変数変数(存在する場合)を初期化します。
パターン変数は、パターン・マッチングが成功するスコープ(6.3)内にのみあるため、パターン変数が初期化されています。初期化されていないパターン変数を使用することはできません。
14.30.1 パターンの種類
型パターンは、値がパターンに出現する型のインスタンスであるかどうかをテストするために使用されます。パターンは、カッコで囲むと読みやすくなります。レコード・パターンは、値がレコード・クラス型のインスタンスであるかどうかをテストし、そうである場合は、レコード・コンポーネント値に対してパターン・マッチングを再帰的に実行するために使用されます。
- Pattern:
- TypePattern
- ParenthesizedPattern
- RecordPattern
- TypePattern:
- LocalVariableDeclaration
- ParenthesizedPattern:
(
Pattern)
- RecordPattern:
- ReferenceType RecordStructurePattern [ Identifier ]
- RecordStructurePattern:
(
[ RecordComponentPatternList ])
- RecordComponentPatternList :
- Pattern {
,
Pattern }
- LocalVariableDeclaration:
- {VariableModifier} LocalVariableType VariableDeclaratorList
- VariableModifier:
- Annotation
final
- LocalVariableType:
- UnannType
var
- VariableDeclaratorList:
- VariableDeclarator {
,
VariableDeclarator}- VariableDeclarator:
- VariableDeclaratorId [
=
VariableInitializer]- VariableDeclaratorId:
- Identifier [Dims]
- Dims:
- {Annotation}
[
]
{{Annotation}[
]
}
UnannTypeについては、8.3を参照してください。
型パターンは、パターン変数として知られる1つのローカル変数を宣言します。ローカル変数宣言の識別子は、パターン変数の名前を指定します。
レコード・コンポーネント・パターン・リストに要素として現れない型パターンは、トップレベル型パターンと呼ばれています。
型パターンで宣言されたローカル変数のルールについては、14.4に規定されています。さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。
トップレベル型パターンのLocalVariableTypeは、参照型(また、
var
でないこと)を示します。VariableDeclaratorListは単一のVariableDeclaratorで構成されています。
VariableDeclaratorにはイニシャライザがありません。
VariableDeclaratorIdにカッコのペアがありません。
トップレベル型パターンで宣言されたパターン変数の宣言型は、LocalVariableTypeによって示される参照型です。
トップレベル型パターンではない型パターンで宣言されたパターン変数の宣言型は、次のように決定されます。
LocalVariableTypeがUnannTypeの場合、パターン変数の型はUnannTypeで示されます
LocalVariableTypeが
var
の場合、パターン変数は、型Rのレコード・パターンのレコード・コンポーネント・パターン・リストに存在する必要があります。パターン変数の宣言型は、Rの対応するコンポーネント・フィールドの型です。次のレコード宣言について考えてみます。
record R<T>(ArrayList<T> r){}
パターン
R<String>(var r)
の場合、パターン変数r
の宣言型はArrayList<String>
です。
型パターンの型は、そのパターン変数の型です。
カッコ付きのパターンは、含まれたパターンによって宣言されたローカル変数を宣言します。
レコード・パターンは、ReferenceType、レコード・コンポーネント・パターン・リストおよびオプションの識別子で構成されます。ReferenceTypeがレコード・クラス・タイプ(8.10)でない場合、コンパイル時エラーが発生します。
識別子を持つレコード・パターンは、名前付きレコード・パターンと呼ばれます。
レコード・コンポーネント・パターン・リストの長さは、ReferenceTypeで指定されたレコード・クラスの宣言内のレコード・コンポーネント・リストと同じ長さである必要があります。そうでない場合は、コンパイル時エラーが発生します。
現時点では、可変個引数レコード・パターンはサポートされていません。これは、Javaプログラミング言語の将来のバージョンでサポートされる可能性があります。
レコード・パターンでは、レコード・コンポーネント・パターン・リスト内のパターンで宣言されているローカル変数(ある場合)を宣言します。
名前付きレコード・パターンでは、レコード・パターン変数と呼ばれるローカル変数も宣言します。この変数は識別子と呼ばれ、その宣言型はReferenceTypeで示される型です。
特殊なanyパターンもあります。これは、パターンの解決のプロセスから発生するパターンです(14.30.2)。
現時点では、anyパターンの構文は存在しないため、パターン
instanceof
式のパターンとして使用することも、switch
式またはswitch
文のswitchラベルのパターンとして使用することもできません。Javaプログラミング言語の将来のバージョンで、この制限が緩和される可能性があります。
anyパターンは、パターン変数として知られる1つのローカル変数を宣言します。
anyパターンのパターン変数は、参照型の型を持ちます。
14.30.2 パターン・マッチング
パターン・マッチングは、実行時にパターンに対して値をテストするプロセスです。パターン・マッチングは、文実行(14.1)および式評価(15.1)とは異なります。値がパターンに正常に一致する場合、パターン・マッチング・プロセスが、パターンによって宣言されたすべてのパターン変数(ある場合)を初期化します。
パターン・マッチングの実行前に、すべてのパターンが、まず、照合される式のタイプ(switch
文のセレクタ式またはswitch
文、あるいはinstanceof
式のRelationalExpressionのいずれか)に関して解決されます。その結果として、パターンが修正される可能性があります。
型Uでパターンを解決するには次のように指定します。
型Tのパターン変数xを宣言する型パターンpは、pがUで無条件の場合、型Tのxを宣言するanyパターンに解決されます。それ以外の場合は、pに解決されます。
含まれたパターンpがあるカッコ付きのパターンは、含まれたパターンが型Uでのpの解決の結果であるカッコ付きのパターンに解決されます。
- 型Rおよびレコード・コンポーネント・パターン・リストLのレコード・パターンpは、型Rのレコードパターンと、Rの対応するコンポーネント・フィールドの型でLのパターン(ある場合)ごとに順に解決した結果のレコード・コンポーネント・パターン・リストに解決されます。
このパターンを解決するプロセスは、レコード・パターンの正しいセマンティクスを取得することです。次の例について考えてみます。
class Super {} class Sub extends Super {} record R(Super s) {}
型Rのすべてのnull以外の値は、式
new R(null)
の評価結果の値を含め、パターンR(Super s)
に一致すると期待します。(null値がパターンSuper s
と一致しないとしても)。ただし、レコード・コンポーネントのnull値がパターンSub s
と一致しないため、この値がパターンR(Sub s)
と一致することは期待しません。次に、レコード・コンポーネント・パターン・リスト内で出現するパターンの意味は、レコード宣言に関して決定されます。解決では、nullを含むすべての値と一致する必要があるレコード・コンポーネント・パターン・リストに現れるany型のパターンを特別なanyパターンのインスタンスに置き換えます。前述の例では、パターン
R(Sub s)
はパターンR(Sub s)
に解決されますが、パターンR(Super s)
は型R
のレコード・パターンと、anyパターンを含むレコード・コンポーネント・パターン・リストに解決されます。
パターン・マッチングのプロセスには、式の評価または文の実行が関与する場合があります。したがって、式の評価または文の実行が突然完了する場合は、パターン・マッチングが突然完了すると表現されます。突然の完了には常に理由が関連付けられており、それは常に、指定された値を持つthrow
です。パターン・マッチングは、突然完了しない場合、正常に完了すると言い表されます。
値がパターンと一致するかどうかを決定するルールおよびパターン変数を初期化するルールは、次のとおりです。
NULL参照は、anyパターンに一致します。
anyパターンによって宣言されるパターン変数は、null参照に初期化されます。
null参照ではない値vは、
ClassCastException
の発生なしにvをTにキャストできる場合、型Tのanyパターンと一致します。そうでない場合は、一致しません。vが一致する場合、anyパターンによって宣言されたパターン変数はvに初期化されます。
vが一致しない場合、anyパターンによって宣言されたパターン変数は初期化されません。
null参照は、型パターンと一致しません。
この場合、型パターンによって宣言されたパターン変数は初期化されません。
null参照ではない値vは、
ClassCastException
を発生させずにvをTにキャストできる場合、型Tの型パターンと一致します。そうでない場合、一致しません。vが一致する場合、型パターンによって宣言されたパターン変数はvに初期化されます。
vが一致しない場合、型パターンによって宣言されたパターン変数は初期化されません。
値は、含まれているパターンと一致する場合、カッコ付きのパターンと一致します。そうでない場合、一致しません。
null参照は、レコード・パターンと一致しません。
この場合、レコード・パターンによって宣言されたanyパターンの変数は初期化されません。
NULL参照ではない値vは、(i)
ClassCastException
の発生なしにvをRにキャストでき、(ii) vがLと一致する場合、型Rのレコードパターンおよびレコード・コンポーネント・パターン・リストLと一致します。それ以外の場合は一致しません。vが一致し、レコード・パターンが名前付きレコード・パターンである場合、レコード・パターン変数はvで初期化されます。
値は、対応するレコード・コンポーネントでリスト内のすべてのパターン(ある場合)と一致する場合、レコード・コンポーネント・パターン・リストと一致します。レコード・コンポーネントで値を照合するパターンが突然完了すると、同じ理由でパターン・マッチングが突然完了します。
null参照でない値vは、その値vのcに対応するアクセサ・メソッドの呼出し結果がパターンpと一致する場合、レコード・コンポーネントcでパターンpと一致します。アクセサ・メソッドの呼出しの実行が理由Sのために突然完了した場合、パターン・マッチングは原因Sによる
MatchException
をスローすることで突然完了します。レコード・コンポーネント・パターン・リストに現れるパターンで宣言されたanyパターン変数は、リスト内のすべてのパターンが一致する場合にのみ初期化されます。
null参照である値を対象とするルールはありません。これは、パターン・マッチングを実行する単独のコンストラクトである
instanceof
パターン一致演算子(15.20.2)が、値がnull参照ではない場合にのみパターン・マッチングを実行するためです。Javaプログラミング言語の将来のバージョンで、他の式および文でのパターン・マッチングが許可される可能性があります。
14.30.3 パターンのプロパティ
次のいずれかの規則が適用される場合、パターンpは型Tで適用可能であると表現されます。
参照型Uのパターン変数を宣言する型パターンは、TがUにダウンキャスト変換可能(5.5)な場合に、別の参照型Tで適用可能です。
プリミティブ型Pのパターン変数を宣言する型パターンは、型Pで適用可能です。
カッコ付きパターンは、それに含まれているパターンが型Tで適用可能な場合、型Tで適用可能です。
型Rおよびレコード・コンポーネント・パターン・リストLのレコード・パターンは、(i) TがRにダウンキャスト変換可能であり、(ii) L内に現れるすべてのパターンp (ある場合)について、pがRの対応するコンポーネント・フィールドの型で適用可能になっている場合、型Tで適用可能です(5.5)。
型Rで汎用レコード・クラスが指定されている場合、Rがパラメータ化型でない場合はコンパイル時エラーになります。
汎用レコード・クラスを指定する型によるレコード・パターンでは、このクラスのパラメータ化を使用する必要があります。現時点では、レコード・パターンの型引数の推定はサポートされていません。これは、Javaプログラミング言語の将来のバージョンでサポートされる可能性があります。
パターンpは、型Tのすべての値がp (型Tでpが解決(14.30.2)された後)に一致する場合、型Tで無条件であると表現されます。これは、次のように定義されます。
参照タイプSのパターン変数を宣言する型パターンは、TのイレイジャがSのイレイジャのサブタイプである場合、別の参照タイプTで無条件です。
プリミティブ型Pのパターン変数を宣言する型パターンは、型Pで無条件です。
カッコ付きパターンは、それに含まれているパターンが型Tで無条件な場合、型Tで無条件です。
null参照はanyレコード・パターンと一致しないため、レコード・パターンはanyタイプで無条件ではありません。
qと一致するすべての値がpとも一致する場合、パターンpは別のパターンqよりも優先されると表現され、次のように定義されます。
パターンpは、pがTで無条件である場合、型Tのパターン変数を宣言する型パターンよりも優先されます。
pがqより優先される場合、パターンpは、含まれたパターンqがあるカッコ付きのパターンより優先されます。
パターンpは、pがRで無条件である場合、型Rのレコード・パターンよりも優先されます。
型Rおよびレコード・コンポーネント・パターン・リストLのレコード・パターンは、(i) SのイレイジャがRのイレイジャのサブタイプであり、Lのすべてのパターン(ある場合)がMの対応するパターンよりも優先される場合、型Sおよびレコード・コンポーネント・パターン・リストMの別のレコード・パターンよりも優先されます。
第15章: 式
15.20 関係演算子
15.20.2 instanceof
演算子
instanceof
式は型比較またはパターン・マッチングのいずれかを実行できます。
- InstanceofExpression:
- RelationalExpression
instanceof
ReferenceType - RelationalExpression
instanceof
Pattern
instanceof
キーワードの右側のオペランドがReferenceTypeである場合、instanceof
キーワードは型比較演算子です。
instanceof
キーワードの右側のオペランドがPatternである場合、instanceof
キーワードはパターン一致演算子です。
次のルールは、instanceof
が型比較演算子の場合に適用されます。
式RelationalExpressionの型は、参照型またはnull型である必要があり、そうでない場合、コンパイル時にエラーが発生します。
RelationalExpressionは、ReferenceTypeとダウンキャスト互換性がある必要があり(5.5)、そうでない場合、コンパイル時にエラーが発生します。
実行時に型比較演算子の結果は次のように決定されます。
RelationalExpressionの値がnull参照(4.1)の場合、結果は
false
になります。RelationalExpressionの値がnull参照ではない場合、
ClassCastException
を発生させずに値をReferenceTypeにキャストできるときは、結果はtrue
です。そうでない場合、false
です。
次のルールは、instanceof
がパターン一致演算子の場合に適用されます。
式RelationalExpressionの型は、参照型またはnull型である必要があり、そうでない場合、コンパイル時にエラーが発生します。
RelationalExpressionは、パターンと互換性がある必要があり(14.30.1)、そうでない場合、コンパイル時にエラーが発生します。パターンは、式RelationalExpressionの型(14.30.3)で適用可能である必要があります。それ以外の場合は、コンパイル時にエラーが発生します。RelationalExpressionの型がPattern,の型のサブタイプである場合、コンパイル時にエラーが発生します。パターン・マッチングの実行前に、すべてのパターンが最初に解決されます(14.30.2)。実行可能なパターン一致演算子は、パターンがRelationalExpressionの型で解決されている演算子です。
実行時に、実行可能なパターン一致演算子の結果は次のように決定されます。
RelationalExpressionの値がnull参照の場合、結果は
false
になります。RelationalExpressionの値がnull参照ではないときに、値がパターンと一致する場合(14.30.2)、結果は
true
です。それ以外の場合は、false
です。true
の結果の悪影響は、パターンで宣言されたすべてのパターン変数(ある場合)が初期化されることです。
例15.20.2-1.型比較演算子
class Point { int x, y; }
class Element { int atomicNumber; }
class Test {
public static void main(String[] args) {
Point p = new Point();
Element e = new Element();
if (e instanceof Point) { // compile-time error
System.out.println("I get your point!");
p = (Point)e; // compile-time error
}
}
}
このプログラムの結果、コンパイル時に2つのエラーが発生します。Element
または使用可能なそのサブクラス(ここには示されていません)のインスタンスがPoint
のサブクラスのインスタンスではない可能性があるため、キャスト(Point)e
は正しくありません。まったく同じ理由により、instanceof
式も正しくありません。一方、クラスPoint
がElement
のサブクラスであった場合(この例では明らかに奇妙な表記です):
class Point extends Element { int x, y; }
キャストは可能になりますが、実行時のチェックが必要になるため、instanceof
式が実用的かつ有効です。キャスト(Point)e
は、e
の値を型Point
に正確にキャストできない場合には実行されないため、これによって例外が呼び出されることはありません。
Java SE 16より前は、reifiableにするには型比較演算子のReferenceTypeオペランドが必要でした(4.7)。これにより、そのすべての型引数がワイルドカードでないかぎり、パラメータ化型を使用できませんでした。多くのパラメータ化型の使用を許可するために、その要件はJava SE 16で取り除かれました。たとえば、次のプログラムで、静的型List<Integer>
のあるメソッド・パラメータx
に、さらに詳細なパラメータ化型ArrayList<Integer>
があるかどうかを実行時にテストすることは有効です。
import java.util.ArrayList;
import java.util.List;
class Test2 {
public static void main(String[] args) {
List<Integer> x = new ArrayList<Integer>();
if (x instanceof ArrayList<Integer>) { // OK
System.out.println("ArrayList of Integers");
}
if (x instanceof ArrayList<String>) { // error
System.out.println("ArrayList of Strings");
}
if (x instanceof ArrayList<Object>) { // error
System.out.println("ArrayList of Objects");
}
}
}
最初のinstanceof
式は、List<Integer>
からArrayList<Integer>
へのキャスト変換があるため有効です。ただし、2番目および3番目のinstanceof
式では、List<Integer>
からArrayList<String>
またはArrayList<Object>
へのキャスティング変換がないため、どちらもコンパイル時にエラーが発生します。
15.28 switch
式
switch
式は、式の値に応じて、いくつかの文または式の1つに制御を転送します。その式のすべての可能な値が処理される必要があり、いくつかの文または式のすべてがswitch
式の結果の値を生成する必要があります。
- SwitchExpression:
switch
(
Expression)
SwitchBlock
Expressionはセレクタ式と呼ばれます。セレクタ式の型はchar
、byte
、short
、int
、Character
、Byte
、Short
、Integer
、String
またはenum型(8.9)である必要があり、そうでない場合、コンパイル時にエラーが発生します。
switch
式およびswitch
文(14.11)の両方の本体は、switchブロックと呼ばれます。switch
式またはswitch
文のどちらに出現するかに関係なく、すべてのswitchブロックに適用される一般的なルールは、14.11.1に示されています。便宜上、ここでは14.11.1の次のプロダクションを示します。
- SwitchBlock:
{
SwitchRule {SwitchRule}}
{
{SwitchBlockStatementGroup} {SwitchLabel:
}}
- SwitchRule:
- SwitchLabel
->
Expression;
- SwitchLabel
->
Block- SwitchLabel
->
ThrowStatement- SwitchBlockStatementGroup:
- SwitchLabel
:
{SwitchLabelBlockStatements:
}
- SwitchLabel:
case
CaseConstant {,
CaseConstant}default
- SwitchLabel:
- CaseOrDefaultLabel {
:
CaseOrDefaultLabel }- CaseOrDefaultLabel:
case
CaseElement {,
CaseElement }default
- CaseElement:
- CaseConstant
- Pattern { Guard }
null
default
- CaseConstant:
- ConditionalExpression
- Guard:
when
Expression
15.28.1 switch
式のSwitchブロック
switchブロック(14.11.1)の一般的なルールに加えて、switch
式のswitchブロックにはさらにルールがあります。つまり、switch
式のswitchブロックに対して次のすべてが当てはまる必要があり、そうでない場合はコンパイル時にエラーが発生します。
セレクタ式の型がenum型ではない場合、switchブロックに関連付けられた
default
ラベルが1つのみあります。セレクタ式の型がenum型の場合、(i) switchブロックに関連付けられた
case
定数のセットが、enum型のすべてのenum定数を含み、(ii)最大で1つのdefault
ラベルがswitchブロックに関連付けられます。default
ラベルは許可されますが、case
ラベルがすべてのenum定数を対象とする場合、必須ではありません。
switchブロックがswitchルールで構成される場合、switchルール・ブロックは正常に完了できません(14.22)。
switchブロックがswitchラベルが付いた文グループで構成される場合、switchブロックの最後の文は正常に完了できず、switchブロックの最後のswitchラベルが付いた文グループの後にはswitchラベルがありません。
switch
式は、switch
文とは異なり、空のswitchブロックを持つことができません。さらに、switch
式は、switchブロックの矢印(->
)の右にどの式が出現するか、つまり、どの式をswitchルール式として使用できるかという観点で、switch
文と異なります。switch
式では、任意の式をswitchルール式として使用できますが、switch
文では、文の式のみを使用できます(14.11.1)。
switch
式は網羅的である必要があり(14.11.1.1)、そうでない場合、コンパイル時にエラーが発生します。
switch
式の結果式は、次のように決定されます。
switchブロックがswitchルールで構成される場合、各switchルールが順番に考慮されます。
switchルールが
...
->
式の形式の場合、式はswitch
式の結果式です。switchルールが
...
->
ブロックの形式の場合、yieldターゲットが指定されたswitch
式であるブロックのyield
文に直接含まれているすべての式は、switch
式の結果式です。
switchブロックがswitchラベルが付いた文グループで構成される場合、yieldターゲットが指定された
switch
式であるswitchブロックのyield
文に直接含まれるすべての式がswitch
式の結果式です。
switch
式に結果式がない場合、コンパイル時にエラーになります。
switch
式が割当てコンテキストまたは呼出しコンテキスト(5.2、5.3)に出現する場合、それは複合式です。それ以外の場合はスタンドアロン式です。
複合switch
式がターゲット型Tの特定の種類のコンテキストに出現する場合、その結果式も同様にターゲット型Tの同じ種類のコンテキストに出現します。
複合switch
式は、その結果式のそれぞれがTと互換性がある場合、ターゲット型Tと互換性があります。
複合switch
式の型は、そのターゲット型と同じです。
スタンドアロンswitch
式の型は、次のように決定されます。
結果式がすべて同じ型を持つ場合(null型も可能(4.1))、それは
switch
式の型です。それ以外の場合、各結果式の型が
boolean
またはBoolean
のときは、ボックス化解除変換(5.1.8)が型Boolean
の各結果式に適用され、switch
式は型boolean
を持ちます。それ以外の場合、各結果式の型が数値型(5.1.8)に変換可能なときは、
switch
式の型は、結果式に適用された一般的な数値プロモーション(5.6)の結果です。それ以外の場合、ボックス化変換(5.1.7)がプリミティブ型を持つ各結果式に適用され、その後、
switch
式の型は、結果式の型の最小上限(4.10.4)への取得変換(5.1.10)の適用の結果になります。
15.28.2 switch
式の実行時評価
実行可能なswitch
式(14.11.1.2)は、セレクタ式の最初の評価によって評価されます。その後、次のように処理されます。
セレクタ式の評価が突然完了する場合、
switch
式が同じ理由で突然完了します。それ以外の場合、セレクタ式の評価の結果が
null
であり、解決されたswitchブロックのswitchラベルが適用されないときは、NullPointerException
がスローされてswitch
式全体がその理由で突然完了します。適用するswitchラベルを決定するプロセスが突然完了する場合は、同じ理由でswitch
式全体が突然完了します。
- それ以外の場合、セレクタ式の評価の結果が非
null
で、型Character
、Byte
、Short
またはInteger
のとき、ボックス化解除変換の対象になります(5.1.8)。この変換が突然完了する場合、switch
式全体が同じ理由で突然完了します。
セレクタ式の評価が正常に完了する場合、結果が非null
で後続のボックス化解除変換(ある場合)が正常に完了するとき、switch
式の評価は、解決されたswitchブロックに関連付けられた内のswitchラベルがセレクタ式の値と一致するに適用されるかどうかを決定することで続行されます(14.11.1.2)。その後、次のように処理されます。
適用するswitchラベルを決定するプロセスが突然完了する場合は、同じ理由で
switch
式全体が突然完了します。一致する適用されるswitchラベルがなく、セレクタ式の評価結果がenum型の場合は、IncompatibleClassChangeError
がスローされ、その理由からswitch
式全体が突然完了します。適用されるswitchラベルがなく、セレクタ式の評価結果がenum型でない場合は、
MatchException
がスローされ、その理由のためにswitch
式全体が突然完了します。switchラベルが
一致する適用される場合、次のいずれかが適用保持されます。switchルール式のswitchラベルである場合、式が評価されます。評価の結果が値である場合、
switch
式は同じ値で正常に完了します。switchルール・ブロックのswitchラベルである場合、ブロックが実行されます。このブロックが正常に完了する場合、
switch
式は正常に完了します。switchルール
throw
文のswitchラベルである場合、throw
文が実行されます。それ以外の場合、switchブロック内の適用される
一致するswitchラベルの後のすべての文が順番に実行されます。これらの文が正常に完了する場合、switch
式は正常に完了します。
switchブロックの文または式の実行が突然完了する場合、次のように処理されます。
式の実行が突然完了する場合、
switch
式が同じ理由で突然完了します。値がVの
yield
のために文の実行が突然完了する場合、switch
式は正常に完了し、switch
式の値はVです。文の実行が値のある
yield
以外の理由のために突然完了する場合、switch
文は同じ理由で突然完了します。
第16章: 明確な割当て
16.2 明確な割当ておよび文
16.2.9 switch
文
Vは、次のすべてが当てはまる場合のみ、
switch
文(14.11)の後で割当て[解除]されます。Vは、
switch
文を終了できるすべてのbreak
文(14.15)の前で割当て[解除]されます。switchブロックの各switchルール(14.11.1)に対して、Vは、switchルール式、switchルール・ブロック、またはswitchルールによって導入されたswitchルール
throw
文の後で割当て[解除]されます。switchブロックにswitchラベルが付いた文グループがある場合、Vは、最後のswitchラベルが付いた文グループの最後のブロック文の後で割当て[解除]されます。
switchブロックにswitch文が網羅的でない場合や、switchブロックがswitchラベルの後のdefault
ラベルが存在しない}
セパレータで終了している場合は、Vはセレクタ式の後で割当て[解除]されます。
Vが
switch
文の前で割当て[解除]される場合のみ、Vは、switch
文のセレクタ式の前で割当て[解除]されます。Vが
switch
文のセレクタ式の後で割当て[解除]される場合のみ、Vは、switchルール式、switchルール・ブロック、またはswitchブロックのswitchルールによって導入されたswitchルールthrow
文の前で割当て[解除]されます。次の両方が当てはまる場合のみ、Vは、switchブロックのswitchラベルが付いた文グループの最初のブロック文の前で割当て[解除]されます。
Vは、
switch
文のセレクタ式の後で割当て[解除]されます。switchラベルが付いた文グループがswitchブロックで初めてではない場合、Vは、前述のswitchラベルが付いた文グループの最後のブロック文の後で割当て[解除]されます。
Vが前述のブロック文の後で割当て[解除]される場合のみ、Vは、switchブロックで最初のswitchラベルが付いた文グループではないブロック文の前で割当て[解除]されます。