このドキュメントでは、Java SE 14のプレビュー機能であるinstanceof
式のパターン・マッチングをサポートするためのJava言語仕様の変更について説明します。 この機能の概要については、JEP 305を参照してください。
変更は、JLSの既存のセクションについて説明しています。 新しいテキストはこのように示され、削除されたテキストはこのように示されます。 必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。
第1章: 概要
1.1 仕様の編成
...
第14章では、CとC++に基づくブロックと文について、およびデータの形状を表すための手段を提供するパターンについて説明します。 この言語にはgoto
文はありませんが、ラベル付きのbreak
およびcontinue
文が含まれます。 Cとは異なり、Javaプログラミング言語では、制御フロー文にboolean
(またはBoolean
)式が必要であり、コンパイル時により多くのエラーを捕捉することを期待して、(ボックス化解除を介す場合を除き)型を暗黙的にboolean
には変換しません。 synchronized
文は、基本的なオブジェクトレベルのモニター・ロックを提供します。 try
には、ローカルでない制御の転送を防ぐためにcatch
およびfinally
句を含めることができます。
...
第4章: 型、値および変数
4.11 型の使用場所
型は、ほとんどの種類の宣言内および特定の種類の式内で使用されます。 特に、型を使用する1617の型コンテキストがあります。
宣言内:
汎用クラス、インタフェース、メソッドまたはコンストラクタの型パラメータ宣言の
extends
句内の型(8.1.2、9.1.2、8.4.4、8.8.4)メソッドのレシーバ・パラメータの型(8.4)
例外パラメータ宣言内の型(14.20)
- パターン変数の宣言内の型(14.30.1)
式内:
明示的なコンストラクタ呼出し文、クラス・インスタンス作成式またはメソッド呼出し式の明示的な型引数リスト内の型(8.8.7.1、15.9、15.12)
インスタンス化対象のクラス型として(15.9)またはインスタンス化対象の無名クラスの直接スーパークラスまたは直接スーパーインタフェースとしての非修飾クラス・インスタンス作成式内(15.9.5)
配列作成式内の要素型(15.10.1)
キャスト式のキャスト演算子内の型(15.16)
type
instanceof
関係演算子の後ろの型(15.20.2)メンバー・メソッドを検索するための参照型として、またはコンストラクトに対するクラス型または配列型としてのメソッド参照式内(15.13)。
また、型は次としても使用されます。
前述の任意のコンテキスト内の配列型の要素型
前述の任意のコンテキスト内でパラメータ化された型の非ワイルドカード型引数またはワイルドカード型引数の境界。
最後に、Javaプログラミング言語には、型の使用方法を示す3つの特別な語句があります。
...
4.12 変数
4.12.3 変数の種類
変数の種類は8つ9つあります。
クラス変数は、クラス宣言内でキーワード
static
を使用して宣言されたフィールド(8.3.1.1)、またはインタフェース宣言内でキーワードstatic
ありまたはなしで宣言されたフィールド(9.3)です。クラス変数は、そのクラスまたはインタフェースが準備され(12.3.2)、デフォルト値に初期化された(4.12.5)ときに作成されます。 クラス変数は、そのクラスまたはインタフェースがアンロード(12.7)されたときに実質的に存在しなくなります。
インスタンス変数は、クラス宣言内でキーワード
static
を使用せずに宣言されたフィールドです(8.3.1.1)。インスタンス変数であるフィールド
a
がクラスTに含まれる場合、クラスTまたはTのサブクラス(8.1.4)である任意のクラスの、新しい作成されたオブジェクトの一部として、新しいインスタンス変数a
が作成されてデフォルト値(4.12.5)に初期化されます。 インスタンス変数は、オブジェクトに必要なファイナライズ(12.6)が完了した後、この変数がフィールドであるオブジェクトが参照されなくなったときに実質的に存在しなくなります。配列コンポーネントは、配列である新しいオブジェクトが作成されるたびに作成されてデフォルト値(4.12.5)に初期化される名前のない変数です(10、15.10.2)。 配列コンポーネントは、配列が参照されなくなったときに実質的に存在しなくなります。
メソッドに渡されるメソッド・パラメータ (8.4.1)の名前引数値。
メソッド宣言で宣言されるすべてのパラメータについて、そのメソッドが呼び出されるたびに新しいパラメータ変数が作成されます(15.12)。 新しい変数は、メソッド呼出し内の対応する引数値を使用して初期化されます。 メソッド・パラメータは、メソッドの本体の実行が完了したときに実質的に存在しなくなります。
コンストラクタに渡されるコンストラクタ・パラメータ (8.8.1)の名前引数値。
コンストラクタ宣言で宣言されるすべてのパラメータについて、クラス・インスタンス作成式(15.9)または明示的なコンストラクタ呼出し(8.8.7)がそのコンストラクタを呼び出すたびに新しいパラメータ変数が作成されます。 新しい変数は、作成式、コンストラクタまたは呼出し内の対応する引数値を使用して初期化されます。 コンストラクタ・パラメータは、コンストラクタの本体の実行が完了したときに実質的に存在しなくなります。
ラムダ式の本体(15.27.2)に渡されるラムダ・パラメータ (15.27.1)の名前引数値。
ラムダ式で宣言されるすべてのパラメータについて、ラムダの本体によって実装されたメソッドが呼び出されるたびに新しいパラメータ変数が作成されます(15.12)。 新しい変数は、メソッド呼出し内の対応する引数値を使用して初期化されます。 ラムダ・パラメータは、ラムダ式の本体の実行が完了したときに実質的に存在しなくなります。
例外パラメータは、
try
文のcatch
句によって例外が捕捉されるたびに作成されます(14.20)。新しい変数は、例外に関連付けられた実際のオブジェクトを使用して初期化されます(11.3、14.18)。 例外パラメータは、
catch
句に関連付けられたブロックの実行が完了したときに実質的に存在しなくなります。ローカル変数は、ローカル変数宣言文によって宣言されます(14.4)。
制御のフローがブロック(14.2)または
for
文に入るたびに(14.14)、そのブロックまたはfor
文内に直接含まれるローカル変数宣言文によって宣言されたローカル変数ごとに新しい変数が作成されます。ローカル変数宣言文には、その変数を初期化する式が含まれる場合があります。 ただし、初期化する式を持つローカル変数は、それを宣言するローカル変数宣言文が実行されるまでは初期化されません。 (明確な割当てのルール(16)により、ローカル変数が初期化されるか、それ以外の場合は値が割り当てられる前にローカル変数の値が使用されないようにします。) ローカル変数は、ブロックまたはfor文の実行が完了したときに実質的に存在しなくなります。
1つの例外的な状況を除けば、ローカル変数は常に、ローカル変数宣言文が実行されるときに作成されるとみなすことができます。 この例外的状況には、
switch
文(14.11)が含まれます。この場合、制御はブロックに入ることが可能になりますがローカル変数宣言文の実行をバイパスします。 ただし、明確な割当て(16)のルールによって課される制約のため、このようにバイパスされたローカル変数宣言文で宣言されたローカル変数は、割当て式(15.26)によって明確に値が割り当てられる前には使用できません。
例4.12.3-1. 様々な変数の種類
class Point {
static int numPoints; // numPoints is a class variable
int x, y; // x and y are instance variables
int[] w = new int[10]; // w[0] is an array component
int setX(int x) { // x is a method parameter
int oldx = this.x; // oldx is a local variable
this.x = x;
return oldx;
}
boolean equalAtX(Object o) {
if (o instanceof Point p) // p is a pattern variable
return this.x == p.x;
else
return false;
}
}
4.12.4 final
変数
変数はfinal
として宣言できます。 final
変数は1回のみ割り当てることができます。 final
変数が割当て(16)の直前に明確に割当て解除されないかぎり、この変数を割り当てると、コンパイル時にエラーが発生します。
final
変数はいったん割り当てられると、常に同じ値をとります。 オブジェクトへの参照がfinal
変数内に保持されている場合、オブジェクトの状態はオブジェクトの演算子によって変更される可能性がありますが、この変数は常に同じオブジェクトを参照します。 これはオブジェクトである配列にも当てはまります。配列への参照がfinal
変数内に保持されている場合、配列のコンポーネントは配列の演算子によって変更される可能性がありますが、この変数は常に同じ配列を参照します。
空のfinal
は、宣言にイニシャライザが欠落しているfinal
変数です。
定数変数は、定数式を使用して初期化される型String
またはプリミティブ型のfinal
変数です(15.28)。 変数が定数変数であるかどうかは、クラス初期化(12.4.1)、バイナリ互換性(13.1)、到達可能性(14.21)および明確な割当て(16.1.1)と密接な関係を持つ場合があります。
34種類の変数、インタフェースのフィールド(9.3)、try
-with-resources文のリソースとして宣言されたローカル変数(14.20.3)、およびmulti-catch
句の例外パラメータ(14.20)およびパターン変数(14.30.1)が暗黙的にfinal
として宣言されます。 uni-catch
句の例外パラメータは、final
と暗黙的に宣言されることはありませんが、実質的にfinalである場合があります。
例4.12.4-1. final変数
変数final
を宣言すると、値が変更されないことを示す有用な記述として機能し、プログラミングのエラーを回避するのに役立ちます。 次のプログラムでは、
class Point {
int x, y;
int useCount;
Point(int x, int y) { this.x = x; this.y = y; }
static final Point origin = new Point(0, 0);
}
クラスPoint
は、final
クラスの変数origin
を宣言します。 origin
変数には、座標が(0, 0)であるクラスPoint
のインスタンスであるオブジェクトへの参照が保持されています。 変数Point.origin
の値は変更できないため、常に、イニシャライザによって作成されたオブジェクトである同じPoint
オブジェクトを参照します。 ただし、このPoint
オブジェクトに対する操作によってその状態が変更される可能性があります。たとえば、useCount
を変更したり、さらには誤ってx
またはy
座標を変更する場合などです。
final
として宣言されていない特定の変数は、かわりに実質的にfinalであるとみなされます。
次がすべて当てはまる場合、宣言子がイニシャライザを持つローカル変数(14.4.2)は実質的にfinalです。
次がすべて当てはまる場合、宣言子にイニシャライザが欠落しているローカル変数は実質的にfinalです。
final
としては宣言されません。割当て式の左側として出現する場合は常に、明確に未割当てであり、その割当ての前では明確に割り当てられていません。つまり、明確に未割当てであり、割当て式の右側の後では明確に割り当てられていません(16)。
接頭辞または接尾辞の増加または減少演算子のオペランドとして使用されることはありません。
宣言子がイニシャライザであるローカル変数として、実質的にfinalであるかどうかを確認することを目的として、メソッド、コンストラクタ、ラムダまたは例外パラメータ(8.4.1、8.8.1、9.4、15.27.1、14.20)が処理されます。
変数が実質的にfinalである場合、final
修飾子を宣言に追加しても、コンパイル時にエラーは発生しません。 反対に、有効なプログラム内でfinal
として宣言されているローカル変数またはパラメータは、final
修飾子が削除されていても、実質的にfinalになります。
4.12.5 変数の初期値
プログラム内の変数はすべて、その値の使用前に値を持つ必要があります。
各クラス変数、インスタンス変数または配列コンポーネントは、作成時にデフォルト値を使用して初期化されます(15.9, 15.10.2)。
型
byte
の場合、デフォルト値はゼロ、つまり、(byte)0
の値です。型
short
の場合、デフォルト値はゼロ、つまり、(short)0
の値です。型
int
の場合、デフォルト値はゼロ、つまり、0
です。型
long
の場合、デフォルト値はゼロ、つまり、0L
です。型
float
の場合、デフォルト値は正のゼロ、つまり、0.0f
です。型
double
の場合、デフォルト値は正のゼロ、つまり、0.0d
です。型
char
の場合、デフォルト値はnull文字、つまり、'\u0000'
です。型
boolean
の場合、デフォルト値はfalse
です。すべての参照型(4.3)の場合、デフォルト値は
null
です。
各メソッド・パラメータ(8.4.1)は、メソッドのインボーカ(15.12)によって提供された対応する引数値に初期化されます。
各コンストラクタ・パラメータ(8.8.1)は、クラス・インスタンス作成式(15.9)または明示的なコンストラクタ呼出し(8.8.7)によって提供された、対応する引数値に初期化されます。
各パターン変数(14.30.1)には、パターン・マッチング・プロセスが成功(14.30.3)した後に値が割り当てられます。
ローカル変数(14.4、14.14)には、初期化(14.4)または割当て(15.26)により、明確な割当て(16)のルールを使用して検証できる方法で、使用前に値を明示的に与える必要があります。
例4.12.5-1. 変数の初期値
class Point {
static int npoints;
int x, y;
Point root;
}
class Test {
public static void main(String[] args) {
System.out.println("npoints=" + Point.npoints);
Point p = new Point();
System.out.println("p.x=" + p.x + ", p.y=" + p.y);
System.out.println("p.root=" + p.root);
}
}
このプログラムは次を出力します。
npoints=0
p.x=0, p.y=0
p.root=null
これは、クラスPoint
の準備(12.3.2)が整ったときに行われるnpoints
のデフォルトの初期化、および新しいPoint
がインスタンス化されたときに行われるx
、y
およびroot
のデフォルトの初期化を示しています。 クラスとインタフェースのロード、リンクおよび初期化のあらゆる側面の完全な説明、および新しいクラス・インスタンスを作成するためのクラスのインスタンス化の説明については、12を参照してください。
第5章: 変換およびコンテキスト
5.5 キャスト・コンテキスト
キャスト・コンテキストを使用すると、キャスト式(15.16)のオペランドを、キャスト演算子によって明示的に名前が付けられた型に変換し、instanceof
演算子(15.20.2)の最初のオペランドを、2番目のオペランドによって示される型に変換できます。 割当てコンテキストおよび呼出しコンテキストとは対照的に、キャスト・コンテキストでは、5.1で定義されている変換をさらに多用できるとともに、これらの変換の組合せをより多く使用できます。
...
第6章: 名前
6.1 宣言
宣言により、エンティティがプログラムに導入され、このエンティティを参照するために名前内で使用できる識別子(3.8)が組み込まれます。 この識別子には、導入されるエンティティがクラス、インタフェースまたは型パラメータである場合は型識別子であるという制約があります。
宣言されたエンティティは、次のいずれかです。
module
宣言で宣言されたモジュール(7.7)package
宣言で宣言されたパッケージ(7.4)単一静的インポート宣言またはオンデマンド静的インポート宣言で宣言された、インポートされた
static
メンバー(7.5.3、7.5.4)クラス型宣言で宣言されたクラス(8.1)
インタフェース型宣言で宣言されたインタフェース(9.1)
汎用クラス、インタフェース、メソッドまたはコンストラクタの宣言の一部として宣言された型パラメータ(8.1.2、9.1.2、8.4.4、8.8.4)
パラメータで、次のいずれか:
ローカル変数で、次のいずれか:
パターンで宣言されたパターン変数(14.30)。
コンストラクタ(8.8)も宣言によって導入されますが、新しい名前が導入されるのではなく、そのコンストラクタが宣言されたクラスの名前が使用されます。
6.3 宣言のスコープ
宣言のスコープは、単純名を使用して、その宣言によって宣言されたエンティティを参照できるプログラム内の領域です(ただし、シャドウ化されていることが条件です) (6.4.1)。
宣言がプログラム内の特定のポイントでスコープ内にあるとされるのは、宣言のスコープにそのポイントが含まれる場合のみです。
観察可能な最上位パッケージ(7.4.3)の宣言のスコープは、パッケージが一意として表示されるモジュールに関連付けられたすべての観察可能なコンパイル・ユニットです(7.4.3)。
観察可能でないパッケージの宣言はスコープ内ではありません。
サブパッケージの宣言はスコープ内ではありません。
パッケージjava
は常にスコープ内にあります。
単一型インポート宣言(7.5.1)またはオンデマンド型インポート宣言(7.5.2)によってインポートされる型のスコープは、モジュール宣言(7.7)、import
宣言が出現するコンパイル・ユニットのすべてのクラスおよびインタフェース型宣言(7.6)、およびコンパイル・ユニットのモジュール宣言またはパッケージ宣言の注釈です。
単一静的インポート宣言(7.5.3)またはオンデマンド静的インポート宣言(7.5.4)によってインポートされるメンバーのスコープは、モジュール宣言、import
宣言が出現するコンパイル・ユニットのすべてのクラスおよびインタフェース型宣言、およびコンパイル・ユニットのモジュール宣言またはパッケージ宣言の注釈です。
最上位型(7.6)のスコープは、最上位型が宣言されるパッケージ内のすべての型宣言です。
クラス型C (8.1.6)で宣言または継承されるメンバーmの宣言のスコープは、Cの本体全体(ネストした型宣言を含む)です。
インタフェース型I (9.1.4)で宣言または継承されるメンバーmの宣言のスコープは、Iの本体全体(ネストした型宣言を含む)です。
enum型Tで宣言されるenum定数Cのスコープは、Tの本体、および式がenum型Tであるswitch
文(14.11)のcase
ラベルです。
メソッド(8.4.1)、コンストラクタ(8.8.1)またはラムダ式(15.27)の仮パラメータのスコープは、メソッド、コンストラクタまたはラムダ式の本体全体です。
クラスの型パラメータ(8.1.2)のスコープは、クラス宣言の型パラメータ・セクション、クラス宣言のスーパークラスまたはスーパーインタフェースの型パラメータ・セクション、およびクラス本体です。
インタフェースの型パラメータ(9.1.2)のスコープは、インタフェース宣言の型パラメータ・セクション、インタフェース宣言のスーパーインタフェースの型パラメータ・セクション、およびインタフェース本体です。
メソッドの型パラメータ(8.4.4)のスコープは、メソッドの宣言全体で、型パラメータ・セクションを含みますが、メソッド修飾子は除きます。
コンストラクタの型パラメータ(8.8.4)のスコープは、コンストラクタの宣言全体で、型パラメータ・セクションを含みますが、コンストラクタ修飾子は除きます。
ブロック(14.2)で直接囲まれたローカル・クラス宣言のスコープは、直接囲んでいるブロックの残りの部分であり、それ自体のクラス宣言を含みます。
switchブロック文グループ(14.11)で直接囲まれたローカル・クラス宣言のスコープは、直接囲んでいるswitchブロック文グループの残りの部分であり、それ自体のクラス宣言を含みます。
ブロックのローカル変数宣言(14.4)のスコープは、宣言が出現するブロックの残りの部分であり、それ自体のイニシャライザから始まり、ローカル変数宣言文の右側にある別の宣言子を含みます。
基本的なfor
文(14.14.1)のForInit部分で宣言されたローカル変数のスコープには、次がすべて含まれます。
それ自体のイニシャライザ
for
文のForInit部分の右側にある別の宣言子for
文のExpressionおよびForUpdate部分含まれている文
拡張されたfor
文(14.14.2)のFormalParameter部分で宣言されたローカル変数のスコープが、含まれている文です。
try
文(14.20)のcatch
句で宣言された例外ハンドラのパラメータのスコープは、catch
に関連付けられたブロック全体です。
try
-with-resources文(14.20.3)のResourceSpecificationで宣言された変数のスコープは、ResourceSpecificationの残りの部分の右側にある宣言、およびtry
-with-resources文に関連付けられたtry
ブロック全体です。
try
-with-resources文の翻訳は、前述のルールを示します。
例6.3-1. 型宣言のスコープ
これらのルールは、クラスおよびインタフェースの宣言が型の使用の前に出現する必要がないことを示します。 次のプログラムでは、クラス宣言PointList
のスコープには、クラスPoint
とクラスPointList
の両方、およびパッケージpoints
の他のコンポーネント・ユニット内の他の型宣言が含まれるため、クラスPoint
内のPointList
の使用は有効です。
package points;
class Point {
int x, y;
PointList list;
Point next;
}
class PointList {
Point first;
}
例6.3-2. ローカル変数宣言のスコープ
ローカル変数x
の初期化がローカル変数x
の宣言のスコープ内にありますが、ローカル変数x
はまだ値を持たず、使用できないため、次のプログラムを実行すると、コンパイル時にエラーが発生します。 フィールドx
は値0
(Test1
が初期化されたときに割り当てられたもの)を持ちますが、ローカル変数x
によってシャドウ化(6.4.1)されているため、注意がそらされることがあります。
class Test1 {
static int x;
public static void main(String[] args) {
int x = x;
}
}
次のプログラムは正常にコンパイルされます。
class Test2 {
static int x;
public static void main(String[] args) {
int x = (x=2)*2;
System.out.println(x);
}
}
これは、ローカル変数x
が使用される前に明確に割り当てられている(16)ためです。 次のように出力されます。
4
次のプログラムでは、three
のイニシャライザは、前の宣言子で宣言された変数two
を正確に参照でき、次の行のメソッド呼出しは、ブロック内で前に宣言された変数three
を正確に参照できます。
class Test3 {
public static void main(String[] args) {
System.out.print("2+1=");
int two = 2, three = two + 1;
System.out.println(three);
}
}
このプログラムでは、次の出力が生成されます。
2+1=3
パターン内で宣言されたされた変数は、パターン変数(14.30)と呼ばれます。 パターン変数が他のローカル変数と異なる点は、パターン変数には、パターン・マッチングによってのみ値を割り当てることができる点です(14.30.3)。 このプロセスは条件付きです。パターン変数に値が割り当てられるのは、パターン・マッチングが成功した場合のみです。
パターン変数のスコープを慎重に定義し、パターン・マッチングが成功し、パターン変数に明確に値が割り当てられるプログラム上のポイントのスコープ内にのみパターン変数が含まれるようにします。 つまり、パターン・マッチングの成功が保証されない場所ではパターン変数にアクセスできず、コンパイル時にエラーが発生します。
この意味では、パターン変数のスコープは、明確な割当て(第16章)と似たフロー依存の概念です。 この章の残りの部分で定義するルールは、第16章で使用されているルールと意図的に同じ形式にされています。
パターン変数のスコープは、パターン変数宣言を含む最も内側の包含文Sを考慮して決定されます。 パターン変数Vの全体的なスコープは、(i) Vが明確に一致しているSに含まれる式および文、(ii) Sが文Qに直接含まれる場合にVが明確に一致しているQに含まれるSに続く文、および(iii) Sがブロックに直接含まれる場合にVが明確に一致しているブロックに含まれるSに続く文であると定義されます。
このセクションの残りの部分では、「明確に一致している場所」という用語の正確な説明に注力します。これについては、次の3つの補助的な技術用語を定義しています。
- パターン変数は、式がtrueであれば導入されます。
- パターン変数は、式がfalseである場合に導入されます。
- パターン変数は、文によって導入されます。
この分析では、文と式の構造を考慮しますが、その際、ブール式の演算子と特定の文の形式を特別に扱います。
最も簡単な例では、式
a instanceof String s
がtrueであればパターン変数s
が導入されます。 つまり、式の値がtrue
である場合、パターン・マッチングは成功しており、その結果、パターン変数に値が割り当てられています。対照的に、式
!(b instanceof Integer t)
がfalseである場合はパターン変数t
が導入されます。 これは、パターン・マッチングが成功するのは、式の値がfalse
である場合のみであるためです。特定の状況下では、パターン変数を文によって導入できます。 詳細は、6.3.2を参照してください。
6.3.1 パターン宣言のスコープおよび式
特定のブール式のみが新しいパターン変数をスコープに導入できます。 式が論理補数式、条件付きAND式、条件付きOR式、条件式、またはinstanceof
演算子でない場合、パターン変数の導入に関してルールは適用されません。
6.3.1.1 条件付きAND演算子&&
条件付きAND式(15.23)には、次のルールが適用されます。
- Vは、次のいずれかの場合に限り、
a && b
がtrueであれば導入されます- Vは
a
がtrueであれば導入されます - Vは
b
がtrueであれば導入されます。
a
がtrueであればVが導入されると同時に、b
がtrueであればVが導入される場合、コンパイル時にエラーが発生します。 - Vは
a
がtrueである場合に導入されたパターン変数は明確にb
で照合されます。a
がtrueである場合に導入されたパターン変数がすでにb
でスコープ内にある場合、コンパイル時にエラーが発生します。
最初のルールにより、パターン・マッチングによってパターン変数に値が暗黙的に複数回割り当てられる可能性が除外されます。 パターン変数は設計上、暗黙的にfinalです。
if ((a instanceof String s) && (b instanceof String s)) { System.out.println(s); // Not allowed }
2番目のルールは、条件付きANDおよび演算子の左側のオペランドによって導入されたパターン変数がスコープ内にあるため、右側のオペランドで使用できることを意味します。 これにより、
x instanceof String s && s.length()>0
などの式が可能になります。
a
がfalseであると同時にb
がfalseであるときにパターン変数が導入される場合、コンパイル時にエラーが発生します。この最後のケースでは、1つのパターン変数が数箇所で宣言されても、これらの宣言がコンテキストによって結合される次のような例は除外されます。
if (!(a instanceof T t) && !(b instanceof T t)) { } else { }
現状ではパターン変数
t
は2番目に含まれる文(else
の後ろの文)のスコープ内にありませんが、これはこの言語の将来のバージョンで緩和される可能性があります(これらの型が同一であることを前提とします)。
6.3.1.2 条件付きOR演算子||
条件付きOR式(15.24)には、次のルールが適用されます。
Vは、次のいずれかで
a || b
がfalseである場合に導入されます- Vは
a
がfalseである場合に導入されます - Vは
b
がfalseである場合に導入されます。
a
がfalseである場合にVが導入されると同時に、b
がfalseである場合にVが導入されると、コンパイル時にエラーが発生します。- Vは
a
がfalseである場合に導入されたパターン変数は、明確にb
で照合されます。a
がfalseである場合に導入されたパターン変数がすでにb
でスコープ内にある場合、コンパイル時にエラーが発生します。
a
がtrueであると同時にb
がtrueである場合にパターン変数が導入されると、コンパイル時にエラーが発生します。この最後のケースでは、次のような例は除外されます。
if ((a instanceof T t) || (b instanceof T t)) { }
現状ではパターン変数
t
は最初に含まれる文のスコープ内にありませんが、これはこの言語の将来のバージョンで緩和される可能性があります。
6.3.1.3 論理補数演算子!
論理補数式(15.15.6)には、次のルールが適用されます。
a
がfalseであればVが導入されるときに限り、!a
がtrueであればVが導入されます。a
がtrueであればVが導入されるときに限り、!a
がfalseであればVが導入されます。
6.3.1.4 条件演算子? :
条件式a ? b : c
(15.25)には、次のルールが適用されます。
a
がtrueである場合に導入されたパターン変数は明確にb
で照合されます。a
がtrueである場合に導入されたパターン変数がすでにb
でスコープ内にある場合、コンパイル時にエラーが発生します。a
がfalseである場合に導入されたパターン変数が明確にc
で照合されます。a
がfalseである場合に導入されたパターン変数がすでにc
でスコープ内にある場合、コンパイル時にエラーが発生します。次の条件のいずれかが満たされる場合、コンパイル時にエラーが発生します。
a
がtrueであると同時にc
がtrueであればパターン変数が導入されます。a
がfalseであると同時にb
がtrueであればパターン変数が導入されます。b
がtrueであると同時にc
がtrueであればパターン変数が導入されます。a
がtrueであると同時にc
がfalseであればパターン変数が導入されます。a
がfalseであると同時にb
がfalseであるときにパターン変数が導入されます。b
がfalseであると同時にc
がfalseであるときにパターン変数が導入されます。
これらのfinalのケースは、この言語の将来のバージョンでサポートされる可能性があるパターン変数の導入のケースを除外することを目的としています。
6.3.1.5 instanceof
演算子
instanceof
式(15.20.2)には、次のルールが適用されます。
Vがパターン
p
によって宣言されるときに限り、a instanceof p
がtrueであればVが導入されます。 (どのパターン変数がパターンによって宣言されるかを決定するルールは、14.30.1で規定しています。)パターン
p
によって導入されたパターン変数がすでにinstanceof
式でスコープ内にある場合、コンパイル時にエラーが発生します。
式
a instanceof p
がfalseである場合に導入されるパターン変数はありません。
JEP 361では、switch
式を追加することが提案されています。 この結果、パターン変数のスコープを決定する際に(switch
式を指定するために必要な変更とあわせて)次の追加ルールが適用されます。
6.3.1.6 switch
式
次のルールは、switch
式(15.28)を対象としています。
- switchのラベルが付いた文グループ(14.11.1)に含まれる文Sによって導入されるパターン変数は、このswitchのラベルが付いた文グループ内でSに続くすべての文(存在する場合)で明確に照合されます。
6.3.2 パターン宣言のスコープおよび文
パターン変数のスコープを決定する上で重要な役割を果たす文はわずかしかありません。
if
、while
、do
およびfor
文に含まれるサブ式で宣言されたパターン変数のスコープには、特定の状況下では他のサブ文が含まれる場合があります。 例:
if (x instance String s) {
// String s in scope for this block
// No explicit cast needed here!
...
System.out.println("The string value was: " + s);
} else {
// String s not in scope here
System.out.println(s); // Compile-time error!
}
特定の制約付きの状況下では、パターン変数が文によって導入される場合があります。 この場合、パターン変数は、囲んでいるブロック内の次の文のスコープ内にあります。 例:
public void RequiresAString(Object o) {
if (!(o instanceof String s)) {
throw new IllegalArgumentException();
}
// Only reachable if the pattern match succeeded
// String s is thus in scope for the rest of the block
System.out.println("The parameter string was: " + s);
...
}
6.3.2.1 ブロック
switchブロックではないブロックに含まれるブロック文Sには、次のルールが適用されます。
- Sによって導入されるパターン変数は、ブロック内でSに続くすべてのブロック文(存在する場合)で明確に照合されます。
6.3.2.2 if
文
文if (e) S
(14.9.1)には、次のルールが適用されます。
e
がtrueである場合に導入されたパターン変数が明確にS
で照合されます。e
がtrueである場合に導入されたパターン変数がすでにS
でスコープ内にある場合、コンパイル時にエラーが発生します。e
がfalseである場合にVが導入され、S
が正常に完了できない場合に限り、if (e) S
によってVが導入されます。if
文によって導入されたパターン変数がすでにスコープ内にある場合、コンパイル時にエラーが発生します。
2番目のルールでは、「正常に完了できない」という表現(14.21)を使用します。この表記自体は、定数式(15.29)の概念を使用します。 つまり、パターン変数のスコープを計算するには、単純名、またはTypeName
.
識別子形式の修飾名が定数変数を参照しているかどうかの確認が必要になる場合があることを意味します。 パターン変数は定数変数を参照することはないため、循環性はありません。
文if (e) S else T
(14.9.2)には、次のルールが適用されます。
e
がtrueである場合に導入されたパターン変数が明確にS
で照合されます。e
がtrueである場合に導入されたパターン変数がすでにS
でスコープ内にある場合、コンパイル時にエラーが発生します。e
がfalseである場合に導入されたパターン変数が明確にT
で照合されます。e
がfalseである場合に導入されたパターン変数がすでにT
でスコープ内にある場合、コンパイル時にエラーが発生します。- Vは、次のいずれかであるときに限り、
if (e) S else T
によって導入されます。- Vは
e
がtrueであれば導入され、S
は正常に完了でき、T
は正常に完了できません - Vは
e
がfalseである場合に導入され、S
は正常に完了できず、T
は正常に完了できます。
if
文によって導入されたパターン変数がすでにスコープ内にある場合、コンパイル時にエラーが発生します。 - Vは
これらのルールでは、パターン変数のスコープのフローのような性質に注目しています。 文の例:
if (e instanceof String s) { counter += s.length(); } else { ... // s not in scope }
パターン変数
s
はinstanceof
演算子によって導入され、最初に含まれる文(else
キーワードの前の文)のスコープ内にありますが、2番目に含まれる含まれる文(else
キーワードの後ろの文)のスコープ内にはありません。
また、ブール式の処理と組み合されることにより、パターン変数のスコープは、ブールの論理的な等価性を活用するコードのリファクタリングに対して堅牢になります。 たとえば、前述のコードは次のように書き直すことができます。
if (!(e instanceof String s)) { ... // s not in scope } else { counter += s.length(); }
さらに、次のように書き直すこともできます。
if (!!(e instanceof String s)) { counter += s.length(); } else { ... // s not in scope }
6.3.2.3 while
文
文while (e) S
(14.12)には、次のルールが適用されます。
e
がtrueである場合に導入されたパターン変数が明確にS
で照合されます。e
がtrueである場合に導入されたパターン変数がすでにS
でスコープ内にある場合、コンパイル時にエラーが発生します。e
がfalseである場合にVが導入され、breakターゲットにSが含まれる到達可能なbreak
文がSに含まれないときに限り、while (e) S
によってVが導入されます。while
文によって導入されたパターン変数がすでにスコープ内にある場合、コンパイル時にエラーが発生します。
6.3.2.4 do
文
文do S while (e)
(14.13)には、次のルールが適用されます。
e
がfalseである場合にVが導入され、breakターゲットにS
が含まれる到達可能なbreak
文がS
に含まれないときに限り、do S while (e)
によってVが導入されます。do
文によって導入されたパターン変数がすでにスコープ内にある場合、コンパイル時にエラーが発生します。
6.3.2.5 for
文
次のルールは、for
文(14.14.1)を対象としています。 拡張されたfor
文(14.14.2)は基本的なfor
文の翻訳によって定義されるため、これに対して特別なルールを提供する必要はありません。
条件式がtrueである場合に導入されたパターン変数は、増分部分および包含文の両方で明確に照合されます。
条件式によって導入されたパターン変数がすでに増分部分または包含文でスコープ内にある場合、コンパイル時にエラーが発生します。
条件式がfalseである場合にVが導入され、breakターゲットにSが含まれる到達可能な
break
文が包含文Sに含まれないときに限り、for
文によってVが導入されます。for
文によって導入されたパターン変数がすでにスコープ内にある場合、コンパイル時にエラーが発生します。
6.3.2.6 switch
文
次のルールは、switch
文(14.11)を対象としています。
- switchブロック文グループ(14.11)に含まれる文Sによって導入されるパターン変数は、このswitchブロック文グループ内でSに続くすべての文(存在する場合)で明確に照合されます。
6.4 シャドウ化および不明瞭化
ローカル変数(14.4)、仮パラメータ(8.4.1、15.27.1)、例外パラメータ(14.20)、およびローカル・クラス(14.3)およびパターン変数(14.30)は、修飾名ではなく単純名(6.2)を使用してのみ参照できます。
一部の宣言は、単純名のみを使用して宣言されたエンティティを区別できないため、ローカル変数、パターン変数、仮パラメータ、例外パラメータまたはローカル・クラス宣言のスコープ内では許可されません。
たとえば、メソッドの仮パラメータの名前をメソッド本体内のローカル変数の名前として再宣言できる場合、ローカル変数によって仮パラメータがシャドウ化され、仮パラメータを参照する手段がなくなる、という望ましくない結果となります。
仮パラメータの名前を使用してメソッド、コンストラクタまたはラムダ式の本体内で新しい変数が宣言される場合、コンパイル時にエラーが発生します。ただし、この新しい変数が、メソッド、コンストラクタまたはラムダ式に含まれるクラス宣言内で宣言される場合は除きます。
ローカル変数vの名前を使用してvのスコープ内で新しい変数が宣言される場合、コンパイル時にエラーが発生します。ただし、この新しい変数が、宣言がvのスコープ内にあるクラス内で宣言される場合は除きます。
例外パラメータの名前を使用してcatch
句のBlock内で新しい変数が宣言される場合、コンパイル時にエラーが発生します。ただし、この新しい変数が、catch
句のBlockに含まれるクラス宣言内で宣言される場合は除きます。
ローカル・クラスCの名前を使用してCのスコープ内で新しいローカル・クラスが宣言される場合、コンパイル時にエラーが発生します。ただし、この新しいローカル・クラスが、宣言がCのスコープ内にある別のクラス内で宣言される場合は除きます。
これらのルールにより、変数またはローカル・クラスのスコープ内で行われるネストしたクラス宣言内でこの変数またはローカル・クラスを再宣言できるようになります。このようなネストしたクラス宣言は、ローカル・クラス(14.3)または無名クラス(15.9)である場合があります。 このため、仮パラメータ、ローカル変数、パターン変数またはローカル・クラスの宣言は、メソッド、コンストラクタまたはラムダ式内でネストしたクラス宣言内でシャドウ化される場合があります。また、例外パラメータの宣言は、
catch
句のブロック内でネストしたクラス宣言内でシャドウ化される場合があります。
ラムダ・パラメータとラムダ式内で宣言された他の変数によって生じる名前の競合を処理するために、2つの設計上の選択肢が用意されています。 1つは、クラス宣言を模倣する方法です。ローカル・クラスの場合と同様、ラムダ式は名前に新しい「レベル」を導入し、式の外部にあるすべての変数名を再宣言できます。 もう1つは、「ローカル」戦略です。
catch
句、for
ループ、ブロックの場合と同様、ラムダ式は、それを囲むコンテキストと同じ「レベル」で動作します。また、式の外部のローカル変数をシャドウ化することはできません。 前述のルールでは、ローカル戦略を使用しています。ラムダ式で宣言された変数が、それを囲むメソッドで宣言された変数をシャドウ化することを可能にする特別な方法はありません。
ローカル・クラスのルールでは、ローカル・クラス自体で宣言された同じ名前のクラスに例外が認められることはありません。 ただし、このケースは別のルールでは止されています。つまり、クラスがそれを囲むクラスと同じ名前を持つことはできません(8.1)。
例6.4-1. ローカル変数のシャドウ化試行
メソッド、コンストラクタまたはイニシャライザ・ブロックのローカル変数としての識別子の宣言が同じ名前のパラメータ、またはローカル変数またはパターン変数のスコープ内に出現してはならないため、次のプログラムの場合、コンパイル時にエラーが発生します。
class Test1 {
public static void main(String[] args) {
int i;
for (int i = 0; i < 10; i++)
System.out.println(i);
}
}
この制約は、これがなければ非常にわかりにくいバグを検出する上で役に立ちます。 スーパークラスにメンバーを追加すると、サブクラスがローカル変数の名前を変更する必要が生じる可能性があるため、ローカル変数によるメンバーのシャドウ化に関する同様の制約は実用的でないと判断されています。 これに関連する考慮事項により、ネストしたクラスのメンバーによるローカル変数のシャドウ化、またはネストしたクラス内で宣言されたローカル変数によるローカル変数のシャドウ化もそれほど有用ではなくなります。
したがって、次のプログラムはエラーなしでコンパイルされます。
class Test2 {
public static void main(String[] args) {
int i;
class Local {
{
for (int i = 0; i < 10; i++)
System.out.println(i);
}
}
new Local();
}
}
一方、同じ名前を持つローカル変数は、2つの別個のブロックまたはfor
文(どちらも他方を含まない)内で宣言できます。
class Test3 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++)
System.out.print(i + " ");
for (int i = 10; i > 0; i--)
System.out.print(i + " ");
System.out.println();
}
}
このプログラムはエラーなしでコンパイルされ、実行されると、次の出力を生成します。
0 1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1
このスタイルは、繰返し型テスト・パターンによって同じパターン名が採用されることの多いパターン・マッチングでも一般的です。
class Test4 {
class Point {
int x, y;
}
public static void test(Object a, Object b, Object c) {
if (a instanceof Point p) {
System.out.print("a is a point (" + p.x + ", " + p.y);
}
if (b instanceof Point p){
System.out.print("b is a point (" + p.x + ", " + p.y);
} else if (c instanceof Point p) {
System.out.print("c is a point (" + p.x + ", " + p.y);
}
System.out.println();
}
}
ただし、パターン変数が他のパターン変数をシャドウ化することは許可されていません。次のプログラムの場合、コンパイル時にエラーが発生します。
class Test5 {
public static void test(Object a, Object b, Object c) {
if (a instanceof Point p) {
System.out.print("a is a point (" + p.x + ", " + p.y);
if (b instanceof Point p){
System.out.print("b is a point (" + p.x + ", " + p.y);
}
}
System.out.println();
}
}
ローカル変数の場合と同様、パターン変数がローカル変数をシャドウ化することは許可されていません。次のプログラムの場合、コンパイル時にエラーが発生します。
class Test6 {
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public static void test(Object o) {
Point p = new Point(0,0);
if (o instanceof Point p)
System.out.println("I get your point");
}
}
6.4.1 シャドウ化
一部の宣言は、同じ名前を持つ他の宣言によってそのスコープ内の一部でシャドウ化できます。この場合、単純名を使用して、宣言されたエンティティを参照することはできません。
シャドウ化は、非表示(8.3、8.4.8.2、8.5、9.3、9.5)とは異なります。非表示は、サブクラス内で宣言されているために継承されておらず、そうでなければ継承されていたメンバーにのみ適用されます。 また、シャドウ化は不明瞭化(6.4.2)とも異なります。
nという名前の型の宣言dは、dのスコープ全体にわたって、dが発生するポイントで、スコープ内にあるnという名前の他の型の宣言をシャドウ化します。
nという名前のフィールドまたは仮パラメータの宣言dは、dのスコープ全体にわたって、dが発生するポイントでスコープ内にあるnという名前の他の変数の宣言をシャドウ化します。
nという名前のローカル変数、パターン変数または例外パラメータの宣言dは、dのスコープ全体にわたって、(a) dが発生するポイントでスコープ内にあるnという名前の他のフィールドの宣言、および(b) dが発生するポイントでスコープ内にあるが、dが宣言された最も内側のクラス内で宣言されていないnという名前の他の変数の宣言をシャドウ化します。
nという名前のメソッドの宣言dは、dのスコープ全体にわたって、dが発生するポイントでそれを囲むスコープ内にあるnという名前の他のメソッドの宣言をシャドウ化します。
パッケージ宣言によって他の宣言がシャドウ化されることはありません。
オンデマンド型インポート宣言によって他の宣言がシャドウ化されることはありません。
オンデマンド静的インポート宣言によって他の宣言がシャドウ化されることはありません。
nという名前の型をインポートするパッケージpのコンパイル・ユニットc内の単一型インポート宣言dは、c全体にわたって、次の宣言をシャドウ化します。
pの別のコンパイル・ユニットで宣言されたnという名前の最上位型
cのオンデマンド型インポート宣言によってインポートされたnという名前の型
cのオンデマンド静的インポート宣言によってインポートされたnという名前の型
nという名前のフィールドをインポートするパッケージpのコンパイル・ユニットc内の単一静的インポート宣言dは、c全体にわたって、cのオンデマンド静的インポート宣言によってインポートされたnという名前の静的フィールドの宣言をシャドウ化します。
シグネチャsを持つnという名前のメソッドをインポートするパッケージpのコンパイル・ユニットc内の単一静的インポート宣言dは、c全体にわたって、cのオンデマンド静的インポート宣言によってインポートされたシグネチャsを持つnという名前の静的メソッドの宣言をシャドウ化します。
nという名前の型をインポートするパッケージpのコンパイル・ユニットc内の単一静的インポート宣言dは、c全体にわたって、次の宣言をシャドウ化します。
cのオンデマンド静的インポート宣言によってインポートされたnという名前の静的型
cのオンデマンド型インポート宣言(7.5.2)によってインポートされたnという名前の型
例6.4.1-1. ローカル変数宣言によるフィールド宣言のシャドウ化
class Test {
static int x = 1;
public static void main(String[] args) {
int x = 0;
System.out.print("x=" + x);
System.out.println(", Test.x=" + Test.x);
}
}
このプログラムでは、次の出力が生成されます。
x=0, Test.x=1
このプログラムは次を宣言します。
クラス
Test
クラス
Test
のメンバーであるクラス(static
)変数x
クラス
Test
のメンバーであるクラス・メソッドmain
main
メソッドのパラメータargs
main
メソッドのローカル変数x
クラス変数のスコープにはクラス(8.2)の本体全体が含まれるため、クラス変数x
は通常、メソッドmain
の本体全体にわたって使用可能です。 ただし、この例では、クラス変数x
は、ローカル変数x
の宣言によってメソッドmain
の本体内にシャドウ化されます。
ローカル変数は、それが宣言されたブロックの残りの部分をスコープとして持ちます(6.3)。この場合、これはmain
メソッドの本体の残りの部分、すなわち、イニシャライザ0
、およびSystem.out.print
とSystem.out.println
の呼出しです。
これは、次のことを意味します。
print
の呼出し内の式x
は、ローカル変数x
の値を参照します(示します)。println
の呼出しは、修飾名(6.6)Test.x
を使用し、この修飾名は、クラス型名Test
を使用してクラス変数x
にアクセスします。これは、Test.x
の宣言はこのポイントでシャドウ化され、その単純名によっては参照できないためです。
また、キーワードthis
を使用して、形式this.x
でシャドウ化されたフィールドx
にアクセスすることもできます。 実際に、この語句は通常、コンストラクタ内に出現します(8.8)。
class Pair {
Object first, second;
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
}
ここでは、コンストラクタは、初期化対象のフィールドと同じ名前を持つパラメータをとります。 この方法は、パラメータに異なる名前を考える必要がある方法より簡単であり、このようにスタイル設定されたコンテキストなら複雑になることはありません。 ただし、一般的には、フィールドと同じ名前を持つローカル変数を採用するので未熟なスタイルであるとみなされます。
例6.4.1-2. 別の型宣言による型宣言のシャドウ化
import java.util.*;
class Vector {
int val[] = { 1 , 2 };
}
class Test {
public static void main(String[] args) {
Vector v = new Vector();
System.out.println(v.val[0]);
}
}
このプログラムはコンパイルされ、次を出力します。
1
この場合、オンデマンドでインポートされる可能性がある汎用クラスjava.util.Vector
(8.1.2)より、ここで宣言されるクラスVector
を優先して使用しています。
6.5 名前の意味の確認
6.5.1 コンテキストに応じた名前の構文的分類
...
次のコンテキストでは名前がExpressionNameとして構文的に分類されています。
修飾されたスーパークラス・コンストラクタ呼び出し内で修飾する式として(8.8.7.1)
修飾されたクラス・インスタンス作成式内で修飾する式として(15.9)
配列アクセス式内で配列参照式として(15.10.3)
PostfixExpressionとして(15.14)
割当て演算子の左側のオペランドとして(15.26)
パターン内のPatternVariableとして(14.30)
try
-with-resources文内のVariableAccessとして(14.20.3)
...
6.5.2 コンテキスト的にあいまいな名前の再分類
この場合、AmbiguousNameは次のように再分類されます。
AmbiguousNameが単純名であるときに単一の識別子を構成する場合:
識別子が、その名前を持つローカル変数宣言(14.4)、パターン変数宣言(14.30.1)、
またはパラメータ宣言(8.4.1、8.8.1、14.20)またはフィールド宣言(8.3)のスコープ(6.3)内に出現する場合、AmbiguousNameはExpressionNameとして再分類されます。そうでない場合、その名前のフィールドが、単一静的インポート宣言(7.5.3)またはオンデマンド静的インポート宣言(7.5.4)により、識別子を含むコンパイル・ユニット(7.3)内で宣言された場合、AmbiguousNameはExpressionNameとして再分類されます。
そうでない場合、識別子が有効なTypeIdentifierであり、その名前を持つ最上位クラス(8)、またはインタフェース型宣言(9)、ローカル・クラス宣言(14.3)またはメンバー型宣言(8.5、9.5)のスコープ(6.3)内に出現する場合、AmbiguousNameはTypeNameとして再分類されます。
そうでない場合、識別子が有効なTypeIdentifierであり、その名前の型が、単一型インポート宣言(7.5.1)、オンデマンド型インポート宣言(7.5.2)、単一静的インポート宣言(7.5.3)またはオンデマンド静的インポート宣言(7.5.4)により、識別子を含むコンパイル・ユニット(7.3)内で宣言された場合、AmbiguousNameはTypeNameとして再分類されます。
そうでない場合、AmbiguousNameはPackageNameとして再分類されます。 後のステップでは、その名前を持つパッケージが実際に存在するかどうかを確認します。
...
6.5.6 式名の意味
6.5.6.1 単純な式名
式名が単一の識別子で構成されている場合、識別子が使用されているポイントでスコープ内のローカル変数、仮パラメータまたはフィールドを示す正確に1つの宣言が存在する必要があります。 そうでない場合、コンパイル時にエラーが発生します。
宣言がインスタンス変数(8.3.1.1)を示している場合、式名は、インスタンス・メソッド(8.4.3.2)、インスタンス変数イニシャライザ(8.3.2)、インスタンス・イニシャライザ(8.6)またはコンストラクタ(8.8)内に出現する必要があります。 式名がクラス・メソッド、クラス変数イニシャライザ、または静的イニシャライザ(8.7)内に出現する場合、コンパイル時にエラーが発生します。
宣言により、単純な式の前に明確に割り当てられるfinal
変数が宣言される場合、この名前はその変数の値を意味します。 そうでない場合、式名は、その宣言によって宣言された変数を意味します。
式名が割当てコンテキスト、呼出しコンテキストまたはキャスト・コンテキスト内に出現する場合、式名の型は、キャプチャ変換(5.1.10)の後に宣言されたフィールド、ローカル変数、パターン変数またはパラメータの型です。
そうでない場合、式名の型は、宣言されたフィールド、ローカル変数、パターン変数またはパラメータの型です。
つまり、式名が「右側」に出現する場合、その型はキャプチャ変換の対象になります。 式名が「左側」に出現する変数である場合、その型はキャプチャ変換の対象にはなりません。
例6.5.6.1-1. 単純な式名
class Test {
static int v;
static final int f = 3;
public static void main(String[] args) {
int i;
i = 1;
v = 2;
f = 33; // compile-time error
System.out.println(i + " " + v + " " + f);
}
}
このプログラムでは、i
、v
およびf
への割当てで左側として使用されている名前は、ローカル変数i
、フィールドv
およびf
の値(f
は変数final
であるため、変数f
ではありません)を示しています。 したがって、この例では、最後の割当ての左側に変数がないため、コンパイル時にエラーが発生します。 間違った割当てを削除すると、修正したコードをコンパイルできるようになり、次の出力が生成されます。
1 2 3
第9章 インタフェース
9.6 注釈型
9.6.4 事前定義済注釈型
9.6.4.1 @Target
型java.lang.annotation.Target
の注釈を注釈型Tの宣言で使用して、Tが適用可能であるコンテキストを指定します。java.lang.annotation.Target
には、コンテキストを指定するために型java.lang.annotation.ElementType[]
を持つ単一要素value
が用意されています。
注釈型は、注釈が宣言に適用される宣言コンテキスト内、または宣言および式で使用される型に注釈が適用される型コンテキスト内に適用可能である場合があります。
宣言コンテキストは9つあり、それぞれがjava.lang.annotation.ElementType
のenum定数に対応しています。
モジュール宣言(7.7)
java.lang.annotation.ElementType.MODULE
に対応していますパッケージ宣言(7.4.1)
java.lang.annotation.ElementType.PACKAGE
に対応しています型宣言: クラス、インタフェース、enumおよび注釈型宣言(8.1.1、9.1.1、8.5、9.5、8.9、9.6)
java.lang.annotation.ElementType.TYPE
に対応していますまた、注釈型宣言は
java.lang.annotation.ElementType.ANNOTATION_TYPE
に対応していますメソッド宣言(注釈型の要素を含む) (8.4.3、9.4、9.6.1)
java.lang.annotation.ElementType.METHOD
に対応していますコンストラクタ宣言(8.8.3)
java.lang.annotation.ElementType.CONSTRUCTOR
に対応しています汎用クラス、インタフェース、メソッドおよびコンストラクタの型パラメータ宣言(8.1.2、9.1.2、8.4.4、8.8.4)
java.lang.annotation.ElementType.TYPE_PARAMETER
に対応していますフィールド宣言(enum定数を含む) (8.3.1、9.3、8.9.1)
java.lang.annotation.ElementType.FIELD
に対応しています仮パラメータおよび例外パラメータ宣言(8.4.1、9.4、14.20)
java.lang.annotation.ElementType.PARAMETER
に対応していますローカル変数宣言(
for
文のループ変数、およびtry
-with-resources文のリソース変数およびパターン変数を含む) (14.4、14.14.1、14.14.2、14.20.3、15.20.2)java.lang.annotation.ElementType.LOCAL_VARIABLE
に対応しています
型コンテキストは16あり(4.11)、これらはすべて、java.lang.annotation.ElementType
のenum定数TYPE_USE
によって表されます。
型java.lang.annotation.Target
の注釈のvalue
要素内に同じenum定数が複数回出現すると、コンパイル時にエラーが発生します。
型java.lang.annotation.Target
の注釈が注釈型Tの宣言内に存在しない場合、Tは、型パラメータ宣言を除くすべての宣言コンテキスト内に適用されますが、型コンテキストには適用されません。
これらのコンテキストは、Java SE 7で注釈が許可されていた構文的な場所です。
第14章: ブロックと文およびパターン
セクション14.22~14.29は、将来的な言語の進化に備えて意図的に未使用のまま残されています。
14.30 パターン
パターンは、データの形状を示します。 パターン・マッチングは、値をパターンと比較し、値がパターンと一致するかどうかを確認するプロセスです。 また、パターンにより、 パターン変数を宣言し、 形状のコンポーネントを指定する場合があります。 値がパターンと一致する場合、 この変数には、パターン・マッチングのプロセスによって値が割り当てられます。 6.3.1でのパターン変数のスコープの取扱いにより、マッチングが成功することが保証されているスコープ内にのみパターン変数が収まるよう徹底されるため、パターン変数は実行時に値と密接に関連付けられます。
14.30.1 パターンの種類
- Pattern:
- TypeTestPattern
14.30.1.1 型テスト・パターン
- TypeTestPattern:
- ReferenceType Identifier
型テスト・パターンは、型とパターン変数で構成されています。 型が参照型(4.3)を示していない場合、コンパイル時にエラーが発生します。
型テスト・パターンの型は、ReferenceTypeです。
型テスト・パターンは、パターン変数識別子を宣言するとされています。 このパターン変数識別子のスコープは、6.3に定義されているとおり、コンテキストでは条件付きです。 パターン変数識別子の型は、ReferenceTypeとして定義されています。
14.30.2 式とパターンの互換性
式は、次のようにパターンと互換性があります。
null
リテラルは、型テスト・パターンと互換性があります。
null
リテラルでない式がTの型テスト・パターンと互換性を持つのは、(i)この式をキャスト変換(5.5)によって型Tに変換できる場合、および(ii)チェックされない絞り込み参照変換(5.1.6.2)がキャスト変換で使用されない場合です。
コンパイル時には、instanceof
演算子(15.20.2)により、最初のオペランドである式とその2番目のオペランドの型との互換性がチェックされます。
14.30.3 パターン・マッチングの実行
実行時には、パターンに対して値が照合されます。 値がパターンと一致する場合、値はさらに、パターン内で宣言されたパターン変数に割り当てられる場合があります。 値がパターンと一致するかどうかを確認するルールは、次のとおりです。
- null参照値は、型テスト・パターンと一致しません。
- null参照値ではない参照型の値が型テスト・パターン
T t
と一致するのは、ClassCastException
を呼び出さずにこの値をT
にキャストできる可能性がある場合です。この場合、この値はパターン変数t
に割り当てられ、そうでない場合は一致しません。
他の可能性はすべて、コンパイル時のパターン・マッチングの型チェックによって除外されます。
第15章: 式
15.20 関係演算子
数値比較演算子<
、>
、<=
、>=
とinstanceof
演算子は、関係演算子と呼ばれます。
- RelationalExpression:
- ShiftExpression
- RelationalExpression
<
ShiftExpression - RelationalExpression
>
ShiftExpression - RelationalExpression
<=
ShiftExpression - RelationalExpression
>=
ShiftExpression - RelationalExpression
instanceof
ReferenceTypeReferenceTypeOrPattern
パターンは、14.30に定義されています。
関係演算子は、構文的には左結合です(左から右へグループ化されます)。
ただし、この事実は役に立ちません。 たとえば、
a<b<c
は(a<b)<c
と解析されますが、a<b
の型は常にboolean
であり、<はboolean
値の演算子ではないため、この場合は常にコンパイル時にエラーが発生します。
関係式の型は常にboolean
です。
15.20.2 型比較演算子instanceof
instanceof
演算子
instanceof
instanceof
演算子のRelationalExpressionオペランドの型は、参照型またはnull型である必要があります。そうでない場合、コンパイル時にエラーが発生します。
instanceof
演算子の後に指定されているReferenceTypeが、具象化可能型(4.7)である参照型を示していない場合、コンパイル時にエラーが発生します。
ReferenceTypeに対するRelationalExpressionのキャストがコンパイル時のエラーとして拒否される場合(15.16)、instanceof
関係式の場合も同様に、コンパイル時にエラーが発生します。 このような状況下では、instanceof
式の結果がtrueになることはありません。
実行時に、instanceof
演算子の結果がtrue
になるのは、RelationalExpressionの値がnull
ではなく、ClassCastException
を呼び出さずに参照をReferenceTypeにキャストできる場合です。 それ以外の場合、結果はfalse
です。
- ReferenceTypeOrPattern:
- ReferenceType
- Pattern
instanceof
演算子の形式には、(i) ReferenceTypeOrPatternオペランドがReferenceTypeである型instanceof
、または(ii) ReferenceTypeOrPatternオペランドがPatternであるパターン instanceof
の2種類があります。
型instanceof
演算子には次が適用されます。
型
instanceof
演算子のRelationalExpressionオペランドの型は、参照型またはnull型である必要があります。そうでない場合、コンパイル時にエラーが発生します。式RelationalExpressionが型ReferenceTypeと互換性を持つのは、(i)キャスト変換(5.5)によってRelationalExpressionを型ReferenceTypeに変換できる場合、および(ii)チェックされない絞り込み参照変換(5.1.6.2)がキャスト変換で使用されない場合です。 ReferenceTypeがReferenceTypeと互換性がない場合、コンパイル時にエラーが発生します。
実行時に、
instanceof
演算子の結果がtrue
になるのは、RelationalExpressionの値がnull
ではなく、ClassCastException
を呼び出さずに参照をReferenceTypeにキャストできる場合です。 それ以外の場合、結果はfalse
です。
パターンinstanceof
演算子には次が適用されます。
パターン
instanceof
演算子のRelationalExpressionオペランドの型は、参照型またはnull型である必要があります。そうでない場合、コンパイル時にエラーが発生します。パターン
instanceof
演算子のRelationalExpressionオペランドは、14.30.2に定義されているとおりにPatternオペランドと互換性を持つ必要があります。そうでない場合、コンパイル時にエラーが発生します。実行時に、14.30.3に詳しく説明されているとおりに、RelationalExpressionの値がPatternと照合されます。 一致する場合、パターン
instanceof
演算子の結果はtrue
です。そうでない場合、パターンinstanceof
演算子の結果はfalse
です
例15.20.2-1. 型instanceof
演算子
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
に正確にキャストできない場合には実行されないため、これによって例外が呼び出されることはありません。