このドキュメントでは、switch
のパターン・マッチングとレコード・パターン(どちらもJava SE 20のプレビュー機能)をサポートするためにJava言語仕様に加えられた変更について説明します。この機能の概要は、それぞれJEP 433およびJEP 432を参照してください。
変更は、JLSの既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。
変更ログ:
2022-11-15:
実行時にswitchラベルが適用されない場合、enumクラスに対する
switch
式は、IncompatibleClassChangeError
ではなくMatchException
をスローするようになりました。レコード・パターンの型引数の推論例の改善。
2022-10-28: レコード・パターンの型引数の推論の詳細を追加しました。
2022-10-18: 初稿をリリースしました。様々なバグ修正に加えて、3番目のプレビュー仕様からの主な変更点は次のとおりです:
- switchラベル用の簡略化された文法。
- 拡張
for
文のヘッダーに表示されるレコード・パターンの新しいサポート。 - 名前付きレコード・パターンが削除されました。
- switchブロックが網羅的かどうかを確認するテストが強化されました。
第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
については、Guard (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.5 for
文
基本のfor
文(14.14.1)には、次のルールが適用されます:
条件式がtrueである場合に導入されたパターン変数は、増分部分および包含文の両方で明確に照合されます。
条件式がtrueである場合に導入されたパターン変数がすでに包含文の増分部分でスコープ内にある場合、コンパイル時にエラーが発生します。
(i)条件式がfalseである場合にパターン変数が導入され、(ii) breakターゲットにSが含まれる到達可能な
break
文が包含文Sに含まれないときにかぎり、基本的なfor
文によってパターン変数が導入されます(14.15)。基本の
for
文によって導入されたパターン変数がすでにfor
文でスコープ内にある場合、コンパイル時にエラーが発生します。
拡張されたfor
文(14.14.2)は基本のfor
文の変換によって定義されるため、これに対して特別なルールを提供する必要はありません。
拡張された
for
文for (p : e) S
の変換のプロパティ(p
はレコード・パターン)は、パターンp
によって導入されたパターン変数がS
文で明確に照合されるということです。
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"); }
6.3.4 Switchラベルのパターン変数のスコープ
パターン変数は、パターン自体または関連するwhen
式によって、case
パターンを持つcase
ラベルによって導入でき、関連するswitch
式(6.3.1.6)またはswitch
文(6.3.2.6)の関連部分のスコープ内にあります。
case
ラベルには、次のルールが適用されます:
パターン変数は、
case
パターンpがpで宣言されている場合、これを持つcase
ラベルによって導入されます。ガード付き
case
パターンのパターンで宣言されたパターン変数は、関連するwhen
式で確実に照合されます。ガード付き
case
パターンのパターンによって宣言されたanyパターン変数が、関連付けられたwhen
式でスコープ内にすでにある場合、コンパイル時にエラーが発生します。パターン変数がガード付き
case
パターンを持つcase
ラベルによって導入されるのは、関連付けられているwhen
式がtrue (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)の実行は失敗することがあります(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
:
{ SwitchLabel:
} BlockStatements - SwitchLabel:
-
case
CaseConstant {,
CaseConstant} -
case null
[, default
] -
case
CasePattern -
default
- CaseConstant:
- ConditionalExpression
- CasePattern:
- Pattern [ Guard ]
- 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
定数が同じ値を持つことはなく、そうでない場合、コンパイル時にエラーが発生します。
case
ラベルは、case
定数のリストまたは単一のcase
パターンのいずれかで構成されます。
すべてのcase
定数は、(1) null
リテラル、(2)定数式(15.29)または(3) enum定数の名前(8.9.1)のいずれかである必要があり、そうでない場合、コンパイル時にエラーが発生します。単一のnull
のcase定数をdefault
キーワードとペアにすることもできます。
case
パターンには、パターンに一致する値に対する追加のテストを表すオプションのwhen
式を含めることができます。case
パターンは、(i)その要素にwhen
式がない場合、または(ii)値がtrue
の定数式であるwhen
式がその要素にある(15.29)場合に、ガードなしと表現されます。それ以外の場合、ガード付きです。
switchラベルおよびそのcase
定数およびcase
パターンは、switchブロックに関連付けられていると言い表されます。
特定のswitchブロックの場合、次の両方が当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します:
- switchブロックに関連付けられている2つの
case
定数が同じ値を持つことがない。
default
ラベルが1つのみ、switchブロックに関連付けられている。
switchブロックに関連付けられたwhen
式には、boolean
型またはBoolean
型が必要です。when
式で使用されているが宣言されていない変数は、finalであるかeffectively finalであることが必要です(4.12.4)。when
式がfalse
の値を持つ定数式(15.29)の場合は、コンパイル時エラーになります。
次の両方に該当する場合、switch
文またはswitch
式のswitchブロックは、セレクタ式T型との互換性があります。
Tがenum型でない場合は、switchブロックに関連付けられているすべての
case
定数にTとの代入互換性がある(5.2)。Tがenum型の場合は、switchブロックに関連付けられているすべての
case
定数がT型のenum定数である。
次のすべてに該当する場合、switch
文またはswitch
式のswitchブロックは、セレクタ式T型とのswitch互換性があります:
null
リテラルは、Tが参照型の場合にのみswitchブロックに関連付けられている。Tが
char
、byte
、short
、int
、Character
、Byte
、Short
、Integer
またはString
のいずれか場合のみ、cがTと代入互換性がある場合のみ、定数式cがswitchブロックに関連付けられている(5.2)。Tがenum型のEである場合にのみ、enum定数eがswitchブロックに関連付けられている。
pが型Tで適用可能な場合にのみ、パターンpがswitchブロックに関連付けられている(14.30.3)。
case
ラベルは、boolean
、long
、float
またはdouble
型のセレクタ式をサポートしていません。switchブロックは、これらの型と連携するように設計されていません。
switch
文またはswitch
式のswitchブロックは、セレクタ式の型とのswitch互換性が必要であり、そうでない場合はコンパイル時にエラーが発生します。
switchラベルは、前者が後者に適用されるすべての値に適用される場合、別のswitchラベルより優先されると表現します。switchブロック内のswitchラベルが、それに続くswitchラベルより優先される場合は、コンパイル時にエラーが発生します。優先を決定するルールは次のとおりです。
case
パターンpがガード付きでないcase
ラベルは、pがqより優先されている場合、case
パターンqを持つ別のcase
ラベルより優先されます(14.30.3)。パターンが別のパターンより優先される定義(14.30.3)は型に基づきます。たとえば、次はコンパイル時にエラーになります。
Object obj = ... switch (obj) { case Object o -> System.out.println("An object"); case String s -> // Error - dominated case label System.out.println("A string"); }
正確に言うと、パターンの優先は型のイレイジャに関して定義されます。たとえば、型パターン
ArrayList<? extends Number> al
は、型パターンArrayList<Number> aln
より優位であり、その逆も同様です。次はコンパイル時にエラーが発生します:List<Number> l = ...; switch (l) { case ArrayList<Number> al -> System.out.println("An ArrayList of Number"); case ArrayList<? extends Number> aln -> // Error - dominated case label System.out.println("An ArrayList of Number"); default -> System.out.println("A List"); }
ガード付き
case
パターンを持つcase
ラベルよりも、ガード付きでない同じパターンを持つcase
ラベルが優先されます。たとえば、次はコンパイル時にエラーになります。String str = ...; switch (str) { case String s -> System.out.println("A string"); case String s when s.length() == 2 -> // Error - dominated case label System.out.println("Two character string"); ... }
一方で、ガード付き
case
パターンpを持つcase
ラベルは、ガード付きでないcase
パターンpを持つcase
ラベルより優先されるとみなされません。これにより、次の一般的なパターン・プログラミング・スタイルが可能になります。Integer j = ...; switch (j) { case Integer i when i <= 0 -> System.out.println("Less than or equal to zero"); case Integer i -> System.out.println("An integer"); }
唯一の例外は、
when
式が値true
を含む定数式の場合です。次に例を示します:Integer j = ...; switch (j) { case Integer i when true -> // Allowed but why write this? System.out.println("An integer"); case Integer i -> // Error - dominated case label System.out.println("An integer"); }
case
パターンp (ガード付きまたはガード付きでない)を持つcase
ラベルは、次のいずれかが真の場合、case
定数cを持つ別のcase
ラベルより優先されます:pは型Tのパターン変数を宣言する型パターン(場合によってはカッコ内にネストされる)であり、cはプリミティブ型Sの定数式、およびSのラッパー・クラス(5.1.7)はTのイレイジャのサブタイプです。
pは型Tのパターン変数を宣言する型パターン(場合によってはカッコ内にネストされる)であり、cは定数式または参照型Sのenum定数であり、SはTのイレイジャのサブタイプです。
たとえば、
Integer
型パターンを持つラベルは、int
リテラルを持つラベルより優先されます:Integer j = ...; switch (j) { case Integer i -> System.out.println("An integer"); case 42 -> // Error - dominated! System.out.println("42!"); }
一般的に決定不能な
when
式の分析は試行されません。たとえば、セレクタ式の値が42
の場合、最初のswitchラベルが一致しない場合でも、次の結果はコンパイル時エラーになります。Integer j = ...; switch (j) { case Integer i when i != 42 -> System.out.println("An integer that isn't 42"); case 42 -> // Error - dominated! System.out.println("42!"); }
case
定数を持つすべてのcase
ラベルは、case
パターンを持つラベルの前に出現します。次に例を示します:Integer j = ...; switch (j) { case 42 -> System.out.println("42"); case Integer i when i < 50 -> System.out.println("An integer less than 50"); case Integer i -> System.out.println("An integer"); }
default
ラベルは、case
パターンを持つcase
ラベルより優先され、null
のcase
定数を持つcase
ラベルよりも優先されます。使用する場合、
default
ラベルはswitch
ブロックの最後に配置されます。過去の理由から、
default
ラベルは、null
以外のcase
定数を持つcase
ラベルの前に出現します。int i = ...; switch(i) { default -> System.out.println("Some other integer"); case 42 -> // allowed System.out.println("42"); }
このスタイルは新しいコードではお薦めしません。
case null, default
ラベルは、他のすべてのswitchラベルより優先されます。使用する場合、
case null, default
ラベルは常にswitch
ブロックの最後に配置されます。
ガード付きでない
case
パターンp (pはセレクタ式(14.30.3)の型で無条件である)を持つcase
ラベルは、default
ラベルおよびcase null, default
ラベルより優先されます。セレクタ式の型で無条件である
case
パターンを持つcase
ラベルは、名前が示すように、すべての値に一致するため、default
ラベルのように動作します。switchブロックは、default
のように動作する複数のswitchラベルを持つことはできません。
switchラベルが付いた文グループで構成されるswitchブロックで、1つ以上のパターン変数を宣言するcase
パターンで文がラベル付けされていて、次のいずれかの場合、コンパイル時にエラーが発生します:
switchブロックの前述の文が正常に完了する可能性がある(14.22)、または
文に複数のswitchラベルが付けられている。
最初の条件により、文グループがパターン変数を初期化せずに別の文グループにフォール・スルーすることがなくなります。たとえば、前述の文グループから到達可能な
case Integer i
というラベルが付けられた文では、パターン変数i
は初期化されていません:Object o = "Hello"; switch (o) { case String s: System.out.println("String: " + s ); // No break! case Integer i: System.out.println(i + 1); // Error! Can be reached // without matching the // pattern `Integer i` default: }
switchラベル文グループで構成されるswitchブロックでは、複数のラベルを文グループに適用できます。2番目の条件は、別のラベルのパターン変数を初期化せずに、あるラベルに基づいて文グループが実行されないようにします。たとえば:
Object o = "Hello World"; switch (o) { case String s: case Integer i: System.out.println(i + 1); // Error! Can be reached // without matching the // pattern `Integer i` default: } Object obj = null; switch (obj) { case null: case String s: System.out.println(s); // Error! Can be reached // without matching the // pattern `String s` default: }
これらの条件は両方とも、
case
パターンがパターン変数を宣言している場合にのみ適用されます。一方、次の例は問題ありません:record R() {} record S() {} Object o = "Hello World"; switch (o) { case String s: System.out.println(s); // No break! case R(): System.out.println("It's either an R or a string"); break; default: } Object ob = new R(); switch (ob) { case R(): case S(): // Multiple case labels! System.out.println("Either R or an S"); break; default: } Object obj = null; switch (obj) { case null: case R(): // Multiple case labels! System.out.println("Either null or an R"); break; default: }
14.11.1.1 網羅的なSwitchブロック
switch
式またはswitch
文のswitchブロックは、(i) switchブロックに関連付けられているdefault
ラベルがある場合、または(ii) switchブロックに関連付けられたdefault
を持つcase
ラベルがある場合、または(iii) switchブロックに関連付けられたすべてのcase
定数およびガード付きでないcase
パターン(総称してcase
要素)を含むセットが空ではなく、Tを網羅する場合、型Tのセレクタ式に対して網羅的です。次のように指定されます:
case
要素のセットは、Eのすべてのenum定数が含まれている場合、enumクラス型Eを網羅しています。つまり、
default
ラベルは許可されますが、すべてのenum定数がcase
定数として出現する場合は必須ではありません。たとえば: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! }; }
型Sを網羅し、SがTにダウンキャスト変換可能である場合、
case
要素のセットは型Tを網羅しています(5.5)。case
要素のセットが型を網羅している場合、他の型の範囲(特に、ダウンキャスト変換可能な型)を網羅しています。たとえば、セットが型Object
を網羅すると、型String
も網羅します。ただし、セットが型List<Object>
を網羅しても、型List<String>
は網羅しません。これに対して、型List<? extends Object>
を網羅するセットも型List<String>
を網羅します。型T (14.30.3)の型パターンが含まれている場合、
case
要素のセットは型Tを網羅しています。case
要素のセットは、abstract
およびsealed
クラスまたはインタフェースCを指定する型Tを網羅します。許可されるすべての直接サブクラスまたはCのサブインタフェースDについて、次の2つの条件のうちの1つが保持されます:Dは汎用クラスまたはインタフェースではなく、TはDにダウンキャスト変換可能であり、
case
要素のセットがD型を網羅します。Dが汎用クラスまたはインタフェースであり、(i) Tにダウンキャスト変換可能であるDを指定するパラメータ化された型Sがないか、(ii) case要素のセットが、SがTにダウンキャスト変換可能なDを指定する型Sを網羅します。
つまり、
default
ラベルは許可されますが、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が型Tを持つレコード・パターンのみが含まれたPの空ではないサブセットであり、(ii) レコード・クラスRのレコード・コンポーネントが0個またはQが型Uでコンポーネントcから網羅的である場合に、レコード・クラスRを指定する型Tを網羅します。このcはRのレコード・コンポーネント・リスト内の最初のコンポーネントであり、UはTの対応するコンポーネント・フィールドの型です(8.10.3)。レコード・クラス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を指定するクラスまたはインタフェース型であり、Dの許可されるすべての直接サブクラスまたはサブインタフェースEに対して、次の2つの条件のいずれかが保持されます:Eが汎用クラスまたはインタフェースではなく、UがEにダウンキャスト変換可能であり、Pが型Eのコンポーネントcから網羅的である。
Eが汎用クラスまたはインタフェースであり、(i) Uにダウンキャスト変換可能なEを指定するパラメータ化された型Vがないか、(ii) Pが、Eを指定する型Vのコンポーネントcから網羅的である(ここで、VはUにダウンキャスト変換可能)。
switch
文またはswitch式は、そのswitchブロックがセレクタ式の型を網羅している場合、網羅的です。
14.11.1.2 実行可能なSwitchブロックおよび実行時に適用されるSwitchラベルの決定
一部のパターンの意味は照合対象の式の型によって決まるため、パターン・マッチング(14.30.2)の実行前にパターンを解決しておく必要があります。したがって、switch
式またはswitch
文のswitchブロックに関連付けられたcase
パターンも解決する必要があります。
実行可能なswitch
式またはswitch
文は、次のように、switchブロックに関連付けられたすべてのcase
パターンがセレクタ式Tの型に関して解決される文です:
case
パターンpを持つcase
ラベルは、case
パターンqを持つcase
ラベルに解決されます。ここで、qは、型T (14.30.2)でパターンpを解決した結果です。pがガード付きである場合、qは同じwhen
式でガードされます。
switch
文の実行(14.11.3)とswitch
式の評価(15.28.2)の両方では、switchラベルがセレクタ式の値と一致するかどうかを判断する必要があります。switchブロック内のswitchラベルが特定の値と一致するかどうかを決定するために、値はswitchブロックに関連付けられたcase
定数と比較されます。その後、次のように処理されます。
case
定数の1つが値と等しい場合、case
定数を含むcase
ラベルが一致します。等価性は
==
演算子(15.21)によって定義されますが、値がString
の場合はString
クラスのequals
メソッドによって定義されます。一致する
case
ラベルがない場合は、default
ラベルが存在していれば、そのdefault
ラベルが一致します。
実行可能なswitch
文(14.11.3)の実行と実行可能なswitch
式(15.28.2)の評価の両方では、次のように、switchブロックに関連付けられたswitchラベルがセレクタ式の値に適用されるかどうかを判断する必要があります:
値がnull参照の場合、
null
のcase
定数を含むcase
ラベルが適用されます。値がnull参照ではない場合、値に適用されるswitchブロックの最初の(ある場合)
case
ラベルを次のように決定します:null以外の
case
定数cを持つcase
ラベルは、最初に値がボックス化解除変換(5.1.8)の対象になり、定数cがボックス化解除した値と等しくなる場合、Character
、Byte
、Short
またはInteger
型の値に適用されます。ボックス化解除変換は、ボックス化解除される値がNULL参照にならないことが保証されるため、正常に完了する必要があります。
等価性は、
==
演算子で定義されます(15.21)。null以外の
case
定数cを持つcase
ラベルは、定数cが値と等しい場合、Character
、Byte
、Short
またはInteger
型ではない値に適用されます。等価性は
==
演算子によって定義されますが、値がString
の場合はString
クラスのequals
メソッドによって定義されます。case
パターンpを持つcase
ラベルが値に適用されることを確認するには、最初に、値がパターンp (14.30.2)に一致するかどうかをチェックします。パターン・マッチングが突然完了した場合は、適用されるswitchラベルを判断するプロセスについても同じ理由で突然完了します。
パターン・マッチングが成功し、
case
パターンがガードされていない場合、このcase
ラベルが適用されます。パターン・マッチングが成功し、
case
パターンがガードされている場合、when
式が評価されます。その結果がBoolean
型の場合は、ボックス化解除変換(5.1.8)の対象になります。when
式または後続のボックス化解除変換(ある場合)の評価がなんらかの理由で突然完了した場合、適用するswitchラベルを判断するプロセスは同じ理由で突然完了します。それ以外の場合は、その結果の値が
true
のときに、case
ラベルが適用されます。case null, default
ラベルはすべての値に適用されます
値がnull参照ではなく、ステップ2のルールに従って
case
ラベルが適用されない場合、default
ラベルがswitchブロックに関連付けられている場合は、そのラベルが適用されます。
単一の
case
ラベルには、複数のcase
定数を含めることができます。ラベルは、その定数のいずれかがセレクタ式の値と一致等しい場合、セレクタ式の値と一致しますに適用されます。たとえば、次のコードで、enum変数day
が表示されているenum定数のいずれかである場合、case
ラベルは一致します:switch (day) { ... case SATURDAY, SUNDAY : System.out.println("It's the weekend!"); break; ... }
パターンをサポートするswitchラベルが適用される場合、これは、パターンに対する値のパターン・マッチング・プロセスが成功したことによるものです(14.30.2)。値がパターンと正常に一致すると、パターン・マッチングのプロセスは、パターンによって宣言されたパターン変数を初期化します。
過去の理由から、
default
ラベルは、default
ラベルの後に一部のラベルが出現する場合でも、すべてのcase
ラベルが一致しなかった場合にのみ考慮されます。ただし、後続のラベルでは、null
以外のcase定数(14.11.1)のみを使用でき、スタイルによっては、プログラマはdefault
ラベルを最後に配置することをお薦めします。
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ブロックと関連付けられたcase
パターンまたはnull
のcase
定数がある文です。
つまり、すべてのswitch
文のswitchブロックに対して次のすべてが当てはまる必要があり、そうでない場合はコンパイル時にエラーが発生します。
1つのみのdefault
ラベルがswitch
ブロックに関連付けられています。switchブロックのすべてのswitchルール式は、文の式です(14.8)。
switch
文は、switchブロックの矢印(->
)の右にどの式が出現するか、つまり、どの式をswitchルール式として使用できるかという観点で、switch
式と異なります。switch
文では、文の式のみをswitchルール式として使用できますが、switch
式では、任意の式を使用できます(15.28.1)。switch
文が拡張switch
文である場合、それは網羅的である必要があります。
Java SE 20より前の
switch
文(およびswitch
式)は、(i) セレクタ式の型が整数型(long
を除く)、enum型またはString
に制限されていて、(ii) null以外の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 20では、
switch
文は、case
パターンのサポートとともに前述の2つの制限も解除されているという点で拡張されています。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
の場合、NullPointerException
がスローされ、その理由でswitch
文全体が突然完了します。それ以外の場合、セレクタ式の評価の結果が型
Character
、Byte
、Short
またはInteger
のとき、ボックス化解除変換の対象になります(5.1.8)。この変換が突然完了する場合、switch
文全体が同じ理由で突然完了します。
セレクタ式の評価が正常に完了する場合、結果が非null
で後続のボックス化解除変換(ある場合)が正常に完了するとき、switch
文の実行は、解決されたswitchブロックに関連付けられたswitchラベルがセレクタ式の値と一致するに適用されるかどうかを決定することで続行されます(14.11.1.2)。その後、次のように処理されます。
適用するswitchラベルを決定するプロセスが突然完了する場合は、同じ理由で
switch
文全体が突然完了します。switchラベルが
一致しない場合は、適用されない場合は、次のいずれかが成立します:switch
文全体が正常に完了します。セレクタ式の値が
null
の場合、NullPointerException
がスローされ、その理由でswitch
文全体が突然完了します。switch
文が拡張されたswitch
文の場合、MatchException
がスローされ、switch
文全体がその理由で突然完了します。セレクタ式の値が
null
ではなく、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.14 for
文
14.14.2 拡張されたfor
文
拡張されたfor
文の形式は次のようになります。
- EnhancedForStatement:
-
for
(
LocalVariableDeclarationEnhancedForDeclaration:
式)
文 - EnhancedForStatementNoShortIf:
-
for
(
LocalVariableDeclarationEnhancedForDeclaration:
式)
StatementNoShortIf
- EnhancedForDeclaration:
- LocalVariableDeclaration
- RecordPattern
- LocalVariableDeclaration:
- {VariableModifier} LocalVariableType VariableDeclaratorList
- VariableModifier:
- Annotation
final
- LocalVariableType:
- UnannType
var
- VariableDeclaratorList:
- VariableDeclarator {
,
VariableDeclarator}- VariableDeclarator:
- VariableDeclaratorId [
=
VariableInitializer]- VariableDeclaratorId:
- Identifier [Dims]
- Dims:
- {Annotation}
[
]
{{Annotation}[
]
}
- RecordPattern:
- ReferenceType
(
[ PatternList ])
- PatternList:
- Pattern {
,
Pattern }
式の型は、配列型(10.1)またはRAW型Iterable
のサブタイプである必要があります。そうしないと、コンパイル時にエラーが発生します。
拡張されたEnhancedForDeclarationは、(i) VariableDeclaratorIdで指定された識別子の名前を持つローカル変数を宣言するローカル変数宣言、または(ii)レコード・パターン(14.30.1)のいずれかです。拡張されたfor
文のヘッダーfor
文が実行されると、次に、ローカル変数がループの反復ごとに、Iterable
の連続する要素、または式によって生成された配列に初期化されるか、ループの反復ごとに、Iterable
の連続する要素または式によって生成された配列は、レコード・パターンと照合されます。
式の反復型は次のように定義されます:
Expressionに配列型がある場合、反復型は配列型のコンポーネント型です。
そうでない場合、Expressionに一部の型Xについて
Iterable<
X>
のサブタイプである型がある場合、反復型はXになります。そうでない場合、ExpressionにはRAW型
Iterable
のサブタイプである型があり、反復型はObject
です。
拡張されたfor
文のヘッダーで宣言されたローカル変数のルールは、14.4で規定され、LocalVariableTypeがvar
の場合に適用されるそのセクション内のルールが無視されます。
さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。
拡張されたfor
文のEnhancedForDeclarationがLocalVariableDeclarationの場合、次の両方がtrueであるか、コンパイル時にエラーが発生します。
VariableDeclaratorListは単一のVariableDeclaratorで構成されています。
VariableDeclaratorにはイニシャライザがありません。
LocalVariableTypeがvar
の場合、VariableDeclaratorIdにはカッコのペアがありません。
14.4で指定されたコンパイル時のルールのために、拡張されたfor
文のEnhancedForDeclarationがLocalVariableDeclarationの場合、型がfor
文の反復型であるイニシャライザがあるかのように処理されます。
これらの変更により、ローカル変数宣言(特にvar
の処理)の詳細な処理が14.4に移行されます。ローカル変数宣言を使用した拡張されたfor
の解釈方法には、大幅な変更はありません。
拡張されたfor
文のEnhancedForDeclarationがレコード・パターンである場合、次の両方がtrueであるか、コンパイル時にエラーが発生します:
拡張されたfor
文のヘッダーEnhancedForDeclarationで宣言されたすべてのローカル変数のスコープとシャドウイングは、6.3および6.4で規定されています。
ネストされたクラスまたはインタフェースまたはラムダ式からのこのようなローカル変数への参照は、6.5.6.1で規定されているように制限されます。
拡張されたfor
文のヘッダーで宣言されたローカル変数の型Tは、次のように決定されます:
LocalVariableTypeがUnannTypeで、UnannTypeまたはVariableDeclaratorIdにカッコのペアが出現しない場合、TはUnannTypeで示される型です。
LocalVariableTypeがUnannTypeで、カッコのペアがUnannTypeまたはVariableDeclaratorIdに出現する場合、Tは10.2によって規定されています。
LocalVariableTypeが
var
の場合、Rは次のようにExpressionの型から導出されます:Expressionに配列型がある場合、Rは配列型のコンポーネント型です。
そうでない場合、Expressionに一部の型Xについて
Iterable<
X>
のサブタイプである型がある場合、RはXになります。そうでない場合、ExpressionにはRAW型
Iterable
のサブタイプである型があり、RはObject
です。
Tは、R (4.10.5)で記述されているすべての合成型変数に関するRの上方投影です。
これらのルールは、「反復型」の定義と、ローカル変数宣言が反復型のイニシャライザがあるかのように処理されるというアサーションによって組み込まれます。
拡張されたfor
文の明確な意味は、次のように基本のfor
文への変換によって与えられます。
式の型が
Iterable
のサブタイプである場合、基本のfor
文の形式は次のとおりです:for (I #i = Expression.iterator(); #i.hasNext(); ) {
{VariableModifier} T Identifier = (TargetType) #i.next();Statement#Body }説明:
式の型が
Iterable<
X>
のサブタイプで、一部の型引数Xの場合、Iはjava.util.Iterator<
X>
型です。それ以外の場合、IはRAW型java.util.Iterator
です。#i
は自動生成される識別子となり、拡張されたfor
文が出現するポイントでスコープ(6.3)内にある、他のいかなる識別子(自動生成またはその他)とも異なります。
{VariableModifier}は、拡張された
for
文のヘッダーで指定されています。Tは、前述のローカル変数の型です。
Tが参照型の場合、TargetTypeはTです。それ以外の場合、TargetTypeはIの型引数の取得変換(5.1.10)の上限、またはIがRAWの場合は
Object
です。
拡張された
for
文のEnhancedForDeclarationがLocalVariableDeclarationの場合、#Body
は次のようになります:LocalVariableDeclaration = #i.next(); Statement
これにより、TargetTypeへのキャストが削除されます。JDK-6690688で説明されているように、キャストはわかりやすくなりましたが、正式に必要ではありませんでした。どのイレイジャ関連キャストも、代入変換の結果としてすでに指定されています(5.2)。レコード・パターンを追加すると、複雑さが増し、有用でなくなります。
それ以外の場合、拡張された
for
文のEnhancedForDeclarationはRecordPatternで、#Body
は次のようになります:switch(#i.next()) { case null -> throw new MatchException(new NullPointerException()); case RecordPattern -> Statement }
それ以外の場合、式には必ず配列型S
[]
があり、基本のfor
文の形式は次のとおりです:S[] #a = Expression; L1: L2: ... Lm: for (int #i = 0; #i < #a.length; #i++) {
{VariableModifier} T Identifier = #a[#i];Statement#Body }説明:
L1 ... Lmは、拡張された
for
文の直前の(空の場合もある)ラベルのシーケンスです。#a
および#i
は自動生成される識別子となり、拡張されたfor
文が出現するポイントでスコープ内にある、他のいかなる識別子(自動生成またはその他)とも異なります。
{VariableModifier}は、拡張された
for
文のヘッダーで指定されています。Tは、前述のローカル変数の型です。
拡張された
for
文のEnhancedForDeclarationがLocalVariableDeclarationの場合、#Body
は次のようになります:LocalVariableDeclaration = #a[#i]; Statement
それ以外の場合、拡張された
for
文のEnhancedForDeclarationはRecordPatternで、#Body
は次のようになります:switch(#a[#i]) { case null -> throw new MatchException(new NullPointerException()); case RecordPattern -> Statement }
たとえば、次のコードがあるとします。
List<? extends Integer> l = ... for (float i : l) ...
次のように変換されます。
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) { float #i0 = (Integer)#i.next(); ...
for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) { float #i0 = #i.next(); ... }
例14.14-1.拡張されたfor
および配列
整数配列の合計を計算する次のプログラムは、拡張されたfor
が配列に対してどのように機能するかを示しています。
int sum(int[] a) {
int sum = 0;
for (int i : a) sum += i;
return sum;
}
例14.14-2.拡張されたfor
およびボックス化解除変換
次のプログラムは、拡張されたfor
文と自動ボックス化解除を組み合せて、ヒストグラムを度数分布表に変換します。
Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
System.out.println(e.getKey() + " " + e.getValue() / total);
}
14.30 パターン
パターンは、値に対して実行できるテストを示します。パターンは文および式のオペランドとして出現し、テストする値を提供します。パターンでは、パターン変数と呼ばれるローカル変数を宣言できます。
パターンに対して値をテストするプロセスはパターン・マッチングと呼ばれます。値がパターンに正常に一致する場合、パターン・マッチング・プロセスが、そのパターンによって宣言されたパターン変数変数(存在する場合)を初期化します。
パターン変数は、パターン・マッチングが成功するスコープ(6.3)内にのみあるため、パターン変数が初期化されています。初期化されていないパターン変数を使用することはできません。
14.30.1 パターンの種類
型パターンは、値がパターンに出現する型のインスタンスであるかどうかをテストするために使用されます。レコード・パターンは、値がレコード・クラス型のインスタンスであるかどうかをテストし、そうである場合は、レコード・コンポーネント値に対してパターン・マッチングを再帰的に実行するために使用されます。パターンは、カッコで囲むと読みやすくなります。
- Pattern:
- TypePattern
- RecordPattern
- ParenthesizedPattern
- TypePattern:
- LocalVariableDeclaration
- RecordPattern:
-
ReferenceType
(
[ PatternList ])
- PatternList :
-
Pattern {
,
Pattern } - ParenthesizedPattern:
-
(
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のレコード・パターンのパターン・リストに存在する必要があります。TをRの対応するコンポーネント・フィールドの型にします。パターン変数の型はTで指定しているすべての合成型変数に関するTの上方投影です。次のレコード宣言について考えてみます。
record R<T>(ArrayList<T> r){}
パターン
R<String>(var r)
の場合、パターン変数r
の型はArrayList<String>
です。
型パターンの型は、そのパターン変数の型です。
レコード・パターンは、ReferenceTypeとネストされたパターン・リストで構成されます。ReferenceTypeがレコード・クラス・タイプ(8.10)でない場合、コンパイル時エラーが発生します。
ReferenceTypeがRAW型である場合、18.5.5で説明されているように、レコード・パターンの型が推論されます。レコード・パターンに対して型を推論できない場合、コンパイル時にエラーが発生します。
それ以外の場合、レコード・パターンの型はReferenceTypeです。
レコード・パターンのネストされたパターン・リストの長さは、ReferenceTypeで指定されたレコード・クラスの宣言内のレコード・コンポーネント・リストと同じ長さである必要があります。そうでない場合は、コンパイル時にエラーが発生します。
現時点では、可変個引数レコード・パターンはサポートされていません。これは、Javaプログラミング言語の将来のバージョンでサポートされる可能性があります。
レコード・パターンでは、ネストされたパターン・リスト内のパターンで宣言されているローカル変数(ある場合)を宣言します。
カッコ付きのパターンは、含まれたパターンによって宣言されたローカル変数を宣言します。
特殊な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に解決されます。
型がRで、ネストされたパターン・リストがLのレコード・パターンpは、型Rのレコード・パターンと、Rの対応するコンポーネント・フィールドの型でL内のパターン(ある場合)ごとに順に解決した結果の、ネストされたパターン・リストに解決されます。
含まれたパターンpがあるカッコ付きのパターンは、含まれたパターンが型Uでのpの解決の結果であるカッコ付きのパターンに解決されます。
このパターンを解決するプロセスは、レコード・パターンの正しいセマンティクスを取得することです。次の例について考えてみます。
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のアクセサ・メソッドを呼び出すことによって決定されます。アクセサ・メソッドの呼出しの実行が理由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)。
パターン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のイレイジャのサブタイプであり、(ii) 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
:
{ SwitchLabel:
} BlockStatements
- SwitchLabel:
case
CaseConstant {,
CaseConstant}default
- SwitchLabel:
case
CaseConstant {,
CaseConstant }case
CasePatterncase null
[, default
]default
- CaseConstant:
- ConditionalExpression
- CasePattern:
- Pattern [ Guard ]
- 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ルールで構成されている場合、コンパイル時にエラーが発生します。すべてのswitchルール・ブロックは完了できません。ただし、1つ以上のswitchルール・ブロックは正常に完了できます(14.22)。
switch
式のswitchブロックがswitchラベル付き文グループで構成されている場合、コンパイル時にエラーが発生します。次に、ただし、switchブロックの最後の文は正常に完了できません。できます。およびまたはswitchブロックは、最後のswitchラベル付き文グループの後にラベルがありません1つ以上のswitchラベルがあります。
switch
式が網羅的でない場合(14.11.1.1)、コンパイル時にエラーが発生します。
switch
式は、switch
文とは異なり、空のswitchブロックを持つことができません。
これは、次の結果式の要件で説明されています。
さらに、switch
式は、switchブロックの矢印(->
)の右にどの式が出現するか、つまり、どの式をswitchルール式として使用できるかという観点で、switch
文と異なります。switch
式では、任意の式をswitchルール式として使用できますが、switch
文では、文の式のみを使用できます(14.11.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
の場合、NullPointerException
がスローされ、その理由でswitch
式全体が突然完了します。それ以外の場合、セレクタ式の評価の結果が型
Character
、Byte
、Short
またはInteger
のとき、ボックス化解除変換の対象になります(5.1.8)。この変換が突然完了する場合、switch
式全体が同じ理由で突然完了します。
セレクタ式の評価が正常に完了する場合、結果が非null
で後続のボックス化解除変換(ある場合)が正常に完了するとき、switch
式の評価は、解決されたswitchブロックに関連付けられたswitchラベルがセレクタ式の値と一致するに適用されるかどうかを決定することで続行されます(14.11.1.2)。その後、次のように処理されます。
適用するswitchラベルを決定するプロセスが突然完了する場合は、同じ理由で
switch
式全体が突然完了します。一致する適用されるswitchラベルがない場合、かつセレクタ式の評価結果がenum型の場合は、次のいずれかになります:IncompatibleClassChangeError
がスローされ、その理由からswitch
式全体が突然完了します。セレクタ式の値が
null
の場合、NullPointerException
がスローされ、その理由でswitch
式の評価が突然完了します。それ以外の場合は、
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ラベルが付いた文グループではないブロック文の前で割当て[解除]されます。
第18章: 型推論
18.5 推論の使用
18.5.5 レコード・パターンの型推論
汎用レコード・クラスRのレコード・パターン(14.30.1)が、型Tの値がそれと照合され、パターンがRの型引数を提供しないコンテキストで出現する場合、次に説明するように型引数が推論されます。
TがRAW型Rへのダウンキャスト変換可能(5.5)でない場合、推論は失敗します。
それ以外の場合、P1, ..., Pn (n ≥ 1)はRの型パラメータで、α1, ..., αnは推論変数にします。初期範囲セットB0は、18.1.3で説明されているように、P1, ..., Pnの宣言された範囲から生成されます。
型T'は、次のようにTから導出されます:
Tがワイルドカードでパラメータ化された型である場合、β1, ..., βk (k ≥ 1)を推論変数にします。ここで、kはT内のワイルドカード型引数の数です。T'は、Tの各ワイルドカード型引数をβi (1≤ i≤ k)に置き換えた結果です。
β1, ..., βkの追加範囲は、次のように、範囲セットB1を形成するためにB0に組み込まれています:
βi (1≤ i≤ k)がTのワイルドカードを上限Uに置き換えた場合、範囲βi
<:
Uが範囲セットに出現しますβi (1≤ i≤ k)がTのワイルドカードを下限Lに置き換えた場合、範囲L
<:
βiが範囲セットに出現しますQ1, ..., Qm (m ≥ 1)をT'で指定されたクラスまたはインタフェースの型パラメータにし、A1, ..., AmをT'の型引数にします。
βi (1 ≤ i ≤ k)ごとに、およびβi (1 ≤ i ≤ m)に対応する型パラメータのTypeBoundの型U (
&
で区切られている)ごとに、範囲βi<:
U[
Q1:=A1, ..., Qm:=Am]
が範囲セットに存在します。βiに対応する型パラメータのTypeBoundがない場合、またはTypeBound (依存関係のみ)から適切な上限が導出されない場合、範囲βi<:
Object
はセットに存在します。
Tが他のクラスまたはインタフェース型である場合、T'はTと同じであり、B1はB0と同じです。
Tが型変数または交差型の場合は、交差型の型変数または要素の上限ごとに、このステップとステップ4が再帰的に繰り返されます。ステップ3と4で作成したすべての範囲は、単一の範囲セットに組み込まれています。
T'が汎用クラスGのパラメータ化であり、Gのパラメータ化でもあるR
<
α1, ..., αn>
のスーパータイプが存在する場合、R'をそのスーパータイプにします。制約式‹T' = R'›が削減され(18.2)、結果として得られた範囲がB1に組み込まれ、新しい範囲セットB2が生成されます。それ以外の場合、B2はB1と同じです。
B2に範囲falseが含まれている場合、推論は失敗します。
それ以外の場合、推論変数α1, ..., αnはB2 (18.4)で解決されます。通常の解決とは異なり、このケースの解決では、推論変数のインスタンス化を適切な下限または適切な上限から生成しようとするステップがスキップされます。かわりに、新しいインスタンス化が、新しい型変数を導入するステップに直接スキップして作成されます。
解決に失敗すると、推論は失敗します。
それ以外の場合、A1, ..., Anをα1, ..., αnの解決されたインスタンス化にし、Y1, ..., Yp (p ≥ 0)を解決で導入された新しい型変数にします。
レコード・パターンの型は、Y1, ..., Yp (4.10.5)に関するR
<
A1, ..., An>
の上方投影です。
例18.5.5-1.レコード・パターンの型推論
次のプログラムは、レコード・クラスのパラメータ化を推論します:
record Mapper<T>(T in, T out) implements UnaryOperator<T> {
public T apply(T arg) { return in.equals(arg) ? out : null; }
}
void test(UnaryOperator<? extends CharSequence> op) {
if (op instanceof Mapper(var in, var out)) {
boolean shorter = out.length() < in.length();
}
}
この場合、Rはレコード・クラスMapper
であり、Tは型UnaryOperator<? extends CharSequence>
です。TはRAWのMapper
にダウンキャスト変換可能であるため、Mapper<
α>
内のαのインスタンス化を推論します。
T'はUnaryOperator<
β>
型であり、βは上限CharSequence
です。
Mapper<
α>
にはスーパータイプUnaryOperator<
α>
があるため、制約式UnaryOperator<
β>
= UnaryOperator<
α>
を減らします。これにより、範囲はα = βになります。組み込むと、さらにα <:
CharSequence
を推論します。
ここで、αを解決し、上限がCharSequence
の新しい型変数であるα = Y
を生成します。
最後に、Y
に関するMapper<Y>
の上方投影を見つけ、レコード・パターンの型がMapper<? extends CharSequence>
であると推論します。
レコード・パターンの型がわかると、ネストされたパターンと照合されるコンポーネント型を確認できます。パターン変数in
およびout
は両方ともCharSequence
型を持ちます。