instanceofのパターン・マッチング

Java®言語仕様バージョン15+36-1562への変更

このドキュメントでは、Java SE 15のプレビュー機能であるinstanceof式のパターン・マッチングをサポートするためのJava言語仕様の変更について説明します。この機能の概要については、JEP 375を参照してください。

これらの変更は、Java SE 14内のinstanceofのパターン・マッチングの最初のプレビュー内の変更と同じ内容ですが、この2番目のプレビューに2つの欠落サブセクションが追加された点を除きます。

変更は、JLSの既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。

第1章: 概要

1.1 仕様の編成

...

第14章では、CとC++に基づくブロックと文について、およびデータの形状を表すための手段を提供するパターンについて説明します。この言語にはgoto文はありませんが、ラベル付きのbreakおよびcontinue文が含まれます。Cとは異なり、Javaプログラミング言語では、制御フロー文にboolean (またはBoolean)式が必要であり、コンパイル時により多くのエラーを捕捉することを期待して、(ボックス化解除を介す場合を除き)型を暗黙的にbooleanには変換しません。synchronized文は、基本的なオブジェクトレベルのモニター・ロックを提供します。tryには、ローカルでない制御の転送を防ぐためにcatchおよびfinally句を含めることができます。

...

第4章: 型、値および変数

4.11 型の使用場所

型は、ほとんどの種類の宣言内および特定の種類の式内で使用されます。特に、型を使用する1617型コンテキストがあります。

また、型は次としても使用されます。

最後に、Javaプログラミング言語には、型の使用方法を示す3つの特別な語句があります。

...

4.12 変数

4.12.3 変数の種類

変数の種類は8つ9つあります。

  1. クラス変数は、クラス宣言内でキーワードstaticを使用して宣言されたフィールド(8.3.1.1)、またはインタフェース宣言内でキーワードstaticありまたはなしで宣言されたフィールド(9.3)です。

    クラス変数は、そのクラスまたはインタフェースが準備され(12.3.2)、デフォルト値に初期化された(4.12.5)ときに作成されます。クラス変数は、そのクラスまたはインタフェースがアンロード(12.7)されたときに実質的に存在しなくなります。

  2. インスタンス変数は、クラス宣言内でキーワードstaticを使用せずに宣言されたフィールドです(8.3.1.1)。

    インスタンス変数であるフィールドaがクラスTに含まれる場合、クラスTまたはTのサブクラス(8.1.4)である任意のクラスの、新しい作成されたオブジェクトの一部として、新しいインスタンス変数aが作成されてデフォルト値(4.12.5)に初期化されます。インスタンス変数は、オブジェクトに必要なファイナライズ(12.6)が完了した後、この変数がフィールドであるオブジェクトが参照されなくなったときに実質的に存在しなくなります。

  3. 配列コンポーネントは、配列である新しいオブジェクトが作成されるたびに作成されてデフォルト値(4.12.5)に初期化される名前のない変数です(1015.10.2)。配列コンポーネントは、配列が参照されなくなったときに実質的に存在しなくなります。

  4. メソッドに渡されるメソッド・パラメータ (8.4.1)の名前引数値。

    メソッド宣言で宣言されるすべてのパラメータについて、そのメソッドが呼び出されるたびに新しいパラメータ変数が作成されます(15.12)。新しい変数は、メソッド呼出し内の対応する引数値を使用して初期化されます。メソッド・パラメータは、メソッドの本体の実行が完了したときに実質的に存在しなくなります。

  5. コンストラクタに渡されるコンストラクタ・パラメータ (8.8.1)の名前引数値。

    コンストラクタ宣言で宣言されるすべてのパラメータについて、クラス・インスタンス作成式(15.9)または明示的なコンストラクタ呼出し(8.8.7)がそのコンストラクタを呼び出すたびに新しいパラメータ変数が作成されます。新しい変数は、作成式、コンストラクタまたは呼出し内の対応する引数値を使用して初期化されます。コンストラクタ・パラメータは、コンストラクタの本体の実行が完了したときに実質的に存在しなくなります。

  6. ラムダ式の本体(15.27.2)に渡されるラムダ・パラメータ (15.27.1)の名前引数値。

    ラムダ式で宣言されるすべてのパラメータについて、ラムダの本体によって実装されたメソッドが呼び出されるたびに新しいパラメータ変数が作成されます(15.12)。新しい変数は、メソッド呼出し内の対応する引数値を使用して初期化されます。ラムダ・パラメータは、ラムダ式の本体の実行が完了したときに実質的に存在しなくなります。

  7. 例外パラメータは、try文のcatch句によって例外が捕捉されるたびに作成されます(14.20)。

    新しい変数は、例外に関連付けられた実際のオブジェクトを使用して初期化されます(11.314.18)。例外パラメータは、catch句に関連付けられたブロックの実行が完了したときに実質的に存在しなくなります。

  8. ローカル変数は、ローカル変数宣言文によって宣言されます(14.4)。

    制御のフローがブロック(14.2)またはfor文に入るたびに(14.14)、そのブロックまたはfor文内に直接含まれるローカル変数宣言文によって宣言されたローカル変数ごとに新しい変数が作成されます。

    ローカル変数宣言文には、その変数を初期化する式が含まれる場合があります。ただし、初期化する式を持つローカル変数は、それを宣言するローカル変数宣言文が実行されるまでは初期化されません。(明確な割当てのルール(16)により、ローカル変数が初期化されるか、それ以外の場合は値が割り当てられる前にローカル変数の値が使用されないようにします。)ローカル変数は、ブロックまたはfor文の実行が完了したときに実質的に存在しなくなります。

    1つの例外的な状況を除けば、ローカル変数は常に、ローカル変数宣言文が実行されるときに作成されるとみなすことができます。この例外的状況には、switch文(14.11)が含まれます。この場合、制御はブロックに入ることが可能になりますがローカル変数宣言文の実行をバイパスします。ただし、明確な割当て(16)のルールによって課される制約のため、このようにバイパスされたローカル変数宣言文で宣言されたローカル変数は、割当て式(15.26)によって明確に値が割り当てられる前には使用できません。

  1. パターン変数はパターン内で宣言されます。パターン変数には、パターン・マッチングのプロセスによって値が割り当てられます(14.30.3)。このプロセスは条件付きです。パターン変数に値が割り当てられるのは、パターン・マッチングが成功した場合のみです。この理由により、パターン変数には、その使用を制限する特別なルールが必要です(6.3)。

例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.29)。変数が定数変数であるかどうかは、クラス初期化(12.4.1)、バイナリ互換性(13.1)、到達可能性(14.22)および明確な割当て(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であるとみなされます。

変数が実質的にfinalである場合、final修飾子を宣言に追加しても、コンパイル時にエラーは発生しません。反対に、有効なプログラム内でfinalとして宣言されているローカル変数またはパラメータは、final修飾子が削除されていても、実質的にfinalになります。

4.12.5 変数の初期値

プログラム内の変数はすべて、その値の使用前に値を持つ必要があります。

例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がインスタンス化されたときに行われるxyおよびrootのデフォルトの初期化を示しています。クラスとインタフェースのロード、リンクおよび初期化のあらゆる側面の完全な説明、および新しいクラス・インスタンスを作成するためのクラスのインスタンス化の説明については、12を参照してください。

第5章: 変換およびコンテキスト

5.5 キャスト・コンテキスト

キャスト・コンテキストを使用すると、キャスト式(15.16)のオペランドを、キャスト演算子によって明示的に名前が付けられた型に変換し、instanceof演算子(15.20.2)の最初のオペランドを、2番目のオペランドによって示される型に変換できます。割当てコンテキストおよび呼出しコンテキストとは対照的に、キャスト・コンテキストでは、5.1で定義されている変換をさらに多用できるとともに、これらの変換の組合せをより多く使用できます。

...

第6章: 名前

6.1 宣言

宣言により、エンティティがプログラムに導入され、このエンティティを参照するために名前内で使用できる識別子(3.8)が組み込まれます。この識別子には、導入されるエンティティがクラス、インタフェースまたは型パラメータである場合は型識別子であるという制約があります。

宣言されたエンティティは、次のいずれかです。

コンストラクタ(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文(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つの補助的な技術用語を定義しています。

この分析では、文と式の構造を考慮しますが、その際、ブール式の演算子と特定の文の形式を特別に扱います。

最も簡単な例では、式a instanceof String strueであればパターン変数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)には、次のルールが適用されます。

最初のルールにより、パターン・マッチングによってパターン変数に値が暗黙的に複数回割り当てられる可能性が除外されます。パターン変数は設計上、暗黙的に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などの式が可能になります。

6.3.1.2 条件付きOR演算子||

条件付きOR式(15.24)には、次のルールが適用されます。

6.3.1.3 論理補数演算子!

論理補数式(15.15.6)には、次のルールが適用されます。

6.3.1.4 条件演算子? :

条件式a ? b : c (15.25)には、次のルールが適用されます。

6.3.1.5 instanceof演算子

instanceof式(15.20.2)には、次のルールが適用されます。

a instanceof pがfalseである場合に導入されるパターン変数はありません。

6.3.1.6 switch

次のルールは、switch式(15.28)を対象としています。

6.3.1.7 カッコで囲まれた式

次のルールは、カッコで囲まれた式(15.8.5)を対象としています。

6.3.2 パターン宣言のスコープおよび文

パターン変数のスコープを決定する上で重要な役割を果たす文はわずかしかありません。

ifwhiledoおよびfor文に含まれるサブ式で宣言されたパターン変数のスコープには、特定の状況下では他のサブ文が含まれる場合があります。例:

if (x instanceof 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には、次のルールが適用されます。

6.3.2.2 if

if (e) S (14.9.1)には、次のルールが適用されます。

2番目のルールでは、「正常に完了できない」という表現([14.21])を使用します。この表記自体は、定数式(15.29)の概念を使用します。つまり、パターン変数のスコープを計算するには、単純名、またはTypeName.識別子形式の修飾名が定数変数を参照しているかどうかの確認が必要になる場合があることを意味します。パターン変数は定数変数を参照することはないため、循環性はありません。

if (e) S else T (14.9.2)には、次のルールが適用されます。

これらのルールでは、パターン変数のスコープのフローのような性質に注目しています。文の例:

if (e instanceof String s) {
  counter += s.length();
} else {
  ...   // s not in scope
}

パターン変数sinstanceof演算子によって導入され、最初に含まれる文(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)には、次のルールが適用されます。

6.3.2.4 do

do S while (e) (14.13)には、次のルールが適用されます。

6.3.2.5 for

次のルールは、for文(14.14.1)を対象としています。拡張されたfor文(14.14.2)は基本的なfor文の翻訳によって定義されるため、これに対して特別なルールを提供する必要はありません。

6.3.2.6 switch

次のルールは、switch文(14.11)を対象としています。

6.3.2.7 ラベルが付いた文

次のルールは、ラベルが付いた文([14.7])を対象としています。

6.4 シャドウ化および不明瞭化

ローカル変数(14.4)、仮パラメータ(8.4.115.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.38.4.8.28.59.39.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全体にわたって、次の宣言をシャドウ化します。

nという名前のフィールドをインポートするパッケージpのコンパイル・ユニットc内の単一静的インポート宣言dは、c全体にわたって、cのオンデマンド静的インポート宣言によってインポートされたnという名前の静的フィールドの宣言をシャドウ化します。

シグネチャsを持つnという名前のメソッドをインポートするパッケージpのコンパイル・ユニットc内の単一静的インポート宣言dは、c全体にわたって、cのオンデマンド静的インポート宣言によってインポートされたシグネチャsを持つnという名前の静的メソッドの宣言をシャドウ化します。

nという名前の型をインポートするパッケージpのコンパイル・ユニットc内の単一静的インポート宣言dは、c全体にわたって、次の宣言をシャドウ化します。

例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

このプログラムは次を宣言します。

クラス変数のスコープにはクラス(8.2)の本体全体が含まれるため、クラス変数xは通常、メソッドmainの本体全体にわたって使用可能です。ただし、この例では、クラス変数xは、ローカル変数xの宣言によってメソッドmainの本体内にシャドウ化されます。

ローカル変数は、それが宣言されたブロックの残りの部分をスコープとして持ちます(6.3)。この場合、これはmainメソッドの本体の残りの部分、すなわち、イニシャライザ0、およびSystem.out.printSystem.out.printlnの呼出しです。

これは、次のことを意味します。

また、キーワード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として構文的に分類されています。

...

6.5.2 コンテキスト的にあいまいな名前の再分類

この場合、AmbiguousNameは次のように再分類されます。

AmbiguousNameが単純名であるときに単一の識別子を構成する場合:

...

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);
    }
}

このプログラムでは、ivおよび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定数に対応しています。

  1. モジュール宣言(7.7)

    java.lang.annotation.ElementType.MODULEに対応しています

  2. パッケージ宣言(7.4.1)

    java.lang.annotation.ElementType.PACKAGEに対応しています

  3. 型宣言: クラス、インタフェース、enumおよび注釈型宣言(8.1.19.1.18.59.58.99.6)

    java.lang.annotation.ElementType.TYPEに対応しています

    また、注釈型宣言はjava.lang.annotation.ElementType.ANNOTATION_TYPEに対応しています

  4. メソッド宣言(注釈型の要素を含む) (8.4.39.49.6.1)

    java.lang.annotation.ElementType.METHODに対応しています

  5. コンストラクタ宣言(8.8.3)

    java.lang.annotation.ElementType.CONSTRUCTORに対応しています

  6. 汎用クラス、インタフェース、メソッドおよびコンストラクタの型パラメータ宣言(8.1.29.1.28.4.48.8.4)

    java.lang.annotation.ElementType.TYPE_PARAMETERに対応しています

  7. フィールド宣言(enum定数を含む) (8.3.19.38.9.1)

    java.lang.annotation.ElementType.FIELDに対応しています

  8. 仮パラメータおよび例外パラメータ宣言(8.4.19.414.20)

    java.lang.annotation.ElementType.PARAMETERに対応しています

  9. ローカル変数宣言(for文のループ変数およびtry-with-resources文のリソース変数およびパターン変数を含む) (14.414.14.114.14.214.20.315.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は9個すべての宣言コンテキストおよび1617個すべての型コンテキストに適用できます。

第14章: ブロックとおよびパターン

セクション14.22~14.29は、将来的な言語の進化に備えて意図的に未使用のまま残されています。

14.30 パターン

パターンは、データの形状を示します。パターン・マッチングは、値をパターンと比較し、値がパターンと一致するかどうかを確認するプロセスです。また、1つのパターンが、様々なコンポーネントに名前を付けるために複数のパターン変数を宣言できます。値がパターンと一致する場合、これらの変数はパターン・マッチングのプロセスによって割り当てられた値です。6.3.1でのパターン変数のスコープの取扱いにより、マッチングが成功することが保証されているスコープ内にのみパターン変数が収まるよう徹底されるため、パターン変数は実行時に値と密接に関連付けられます。

14.30.1 パターンの種類

Pattern:
TypeTestPattern
14.30.1.1 型テスト・パターン
TypeTestPattern:
ReferenceType Identifier

型テスト・パターンは、型とパターン変数で構成されています。型が参照型(4.3)を示していない場合、コンパイル時にエラーが発生します。

型テスト・パターンの型は、ReferenceTypeです。

型テスト・パターンは、パターン変数識別子宣言するとされています。このパターン変数識別子のスコープは、6.3に定義されているとおり、コンテキストでは条件付きです。パターン変数識別子の型は、ReferenceTypeとして定義されています。

14.30.2 式とパターンの互換性

コンパイル時には、パターンinstanceof演算子(15.20.2)が、最初のオペランド(式)が2番目のオペランド(パターン)と互換性を持つかどうかをチェックします。

式は、次のようにパターンと互換性があります。

14.30.3 パターン・マッチングの実行

実行時には、パターンに対して値が照合されます。値がパターンと一致する場合、値はさらに、パターン内で宣言されたパターン変数に割り当てられる場合があります。値がパターンと一致し(または一致せず)、値をパターン変数に割り当てている可能性があるかどうかを確認するためのルールは、次のとおりです。

第15章: 式

15.20 関係演算子

数値比較演算子<><=>=instanceof演算子は、関係演算子と呼ばれます。

RelationalExpression:
ShiftExpression
RelationalExpression < ShiftExpression
RelationalExpression > ShiftExpression
RelationalExpression <= ShiftExpression
RelationalExpression >= ShiftExpression
RelationalExpression instanceof ReferenceType ReferenceTypeOrPattern

パターンは、14.30に定義されています。

関係演算子は、構文的には左結合です(左から右へグループ化されます)。

ただし、この事実は役に立ちません。たとえば、a<b<c(a<b)<cと解析されますが、a<bの型は常にbooleanであり、<はboolean値の演算子ではないため、この場合は常にコンパイル時にエラーが発生します。

関係式の型は常にbooleanです。

15.20.2 型比較演算子instanceofinstanceof演算子

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演算子には次が適用されます。

例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式も正しくありません。一方、クラスPointElementのサブクラスであった場合(この例では明らかに奇妙な表記です):

class Point extends Element { int x, y; }

キャストは可能になりますが、実行時のチェックが必要になるため、instanceof式が実用的かつ有効です。キャスト(Point)eは、eの値を型Pointに正確にキャストできない場合には実行されないため、これによって例外が呼び出されることはありません。