クラスおよびインタフェースに関する一貫した用語

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

このドキュメントでは、クラスおよびインタフェースに関連する用語の使用方法を明らかにし、それらをより明確にと区別するためのJava言語仕様の変更点について説明します。

次の用語が推奨されます。クラスまたはインタフェース宣言は、クラスまたはインタフェースを導入する構文構造です。個々のクラスおよびインタフェース宣言は構文形式が異なり、文法によって、異なるコンテキストに出現することがあります。クラス型またはインタフェース型は変数または式の型で、クラスまたはインタフェースから導出されます。宣言について述べる際には、これらの用語を使用しないでください。enum宣言はクラス宣言の一種で、特別な種類のクラスであるenumクラスを導入します。注釈宣言はインタフェース宣言の一種で、特別な種類のインタフェースである注釈インタフェースを導入します。

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

第1章: 概要

1.1 仕様の編成

第2章では、言語の字句および構文文法を表すために使用する文法および表記法について説明します。

第3章では、CおよびC++に基づくJavaプログラミング言語の字句構造について説明します。この言語は、Unicode文字セットで書かれています。これは、ASCIIのみをサポートするシステム上のUnicode文字の書込みをサポートしています。

第4章では、型、値および変数について説明します。型は、プリミティブ型および参照型に細分されます。

プリミティブ型は、すべてのマシン上およびすべての実装内で同じであるよう定義されており、2の補数整数、単精度および倍精度のIEEE 754標準浮動小数点数、boolean型、およびUnicode文字のchar型の様々なサイズがあります。プリミティブ型の値は、状態を共有しません。

参照型には、クラス型、インタフェース型および配列型があります。参照型は、クラスまたは配列のインスタンスである、動的に作成されたオブジェクトによって実装されます。各オブジェクトに対する複数の参照が存在する場合があります。すべてのオブジェクト(配列を含む)が、クラス階層の(単一)ルートであるクラスObjectのメソッドをサポートしています。事前定義されたStringクラスは、Unicode文字列をサポートしています。オブジェクト内のプリミティブ値をラップするためのクラスが存在します。多くの場合、ラップおよびラップ解除は、コンパイラによって自動的に実行されます(この場合、ラップはボックス化と呼ばれ、ラップ解除はボックス化解除と呼ばれます)。クラスおよびインタフェース宣言は汎用的です。つまり、これらは他の参照型によってパラメータ化される場合があります。この場合、このような宣言は特定の型引数によって呼び出される場合があります。

変数は、型付きの記憶域の場所です。プリミティブ型の変数には、そのプリミティブ型の値が格納されます。クラス型の変数には、null参照、または型がそのクラス型またはそのクラス型のサブクラスであるオブジェクトへの参照を格納できます。インタフェース型の変数には、null参照、またはそのインタフェースを実装する任意のクラスのインスタンスへの参照を格納できます。配列型の変数には、null参照、または配列への参照を格納できます。Objectクラス型の変数には、null参照、または(クラス・インスタンスと配列のどちらであるかとは関係なく)任意のオブジェクトへの参照を格納できます。

第5章では、変換および数値昇格について説明します。変換により、コンパイル時の型、および場合によっては式の値が変更されます。これらの変換には、プリミティブ型と参照型の間のボックス化およびボックス化解除変換が含まれます。数値昇格を使用して、数値演算子のオペランドを、演算が実行可能な共通型に変換します。この言語にはループホールがありません。参照型でのキャストは、型の安全性を確保するために実行時にチェックされます。

第6章では、宣言および名前について説明するとともに、名前が意味するもの(つまり、名前がどの宣言を表しているか)を確認する方法について説明します。Javaプログラミング言語では、クラス、インタフェースまたはメンバーを使用する前にこれらを宣言する必要はありません。宣言順序が重要なのは、ローカル変数、ローカル・クラスの場合、およびクラスまたはインタフェース内のフィールド・イニシャライザの順序の場合のみです。ここでは、読みやすいプログラムの作成に役立つ、推奨される命名規則について説明します。

第7章では、パッケージに組み込まれるプログラムの構造について説明します。パッケージのメンバーは、クラス、インタフェースおよびサブパッケージです。パッケージ(およびその結果としてメンバー)は、階層ネームスペース内に名前があります。通常、パッケージ名はインターネット・ドメイン名システムを使用して一意に構成できます。コンパイル・ユニットは、特定のパッケージのメンバーであるクラスおよびインタフェースの宣言を含み、他のパッケージからクラスおよびインタフェースをインポートしてそれらに短い名前を付けることができます。

パッケージは、非常に大規模なプログラムの構築時にビルディング・ブロックとして機能するモジュールにグループ化できます。モジュールの宣言により、独自のパッケージ内でコードをコンパイルおよび実行するために必要な他のモジュール(およびその結果としてのパッケージと、その結果としてのクラスおよびインタフェース)を指定します。

Javaプログラミング言語は、パッケージのメンバー、クラスおよびインタフェースに対する外部アクセスに関する制限をサポートしています。パッケージのメンバーは、同じパッケージ内の他のメンバー、同じモジュール内の他のパッケージのメンバー、または異なるモジュール内のパッケージのメンバーからのみアクセスできます。クラスおよびインタフェースのメンバーにも同様の制約が適用されます。

第8章では、クラスについて説明します。クラスのメンバーは、クラス、インタフェース、フィールド(変数)およびメソッドです。クラス変数はクラスごとに1つ存在します。クラス・メソッドは、特定のオブジェクトを参照せずに動作します。インスタンス変数は、クラスのインスタンスであるオブジェクト内に動的に作成されます。インスタンス・メソッドは、クラスのインスタンス上で呼び出されます。このようなインスタンスは、実行時に現在のオブジェクトthisになり、オブジェクト指向のプログラミング・スタイルをサポートします。

クラスは単一継承をサポートしており、この場合、各クラスが単一のスーパークラスを持ちます。各クラスは、スーパークラスからメンバーを継承し、最終的にはクラスObjectから継承します。クラス型の変数は、そのクラスまたはそのクラスのサブクラスのインスタンスを参照できるため、多相的に既存のメソッドとともに新しい型を使用できます。

クラスは、synchronizedメソッドを使用した同時プログラミングをサポートします。メソッドは、実行によって発生する可能性があるチェック済例外を宣言します。これにより、コンパイル時のチェックが可能になり、例外条件を確実に処理できるようになります。オブジェクトは、オブジェクトがガベージ・コレクタによって破棄される前に呼び出されるfinalizeメソッドを宣言します。これにより、オブジェクトが状態をクリーン・アップできるようになります。

簡潔にするために、この言語には、クラスの実装とは別個の宣言「ヘッダー」や、別個の型およびクラス階層はありません。

特別な形式のクラスであるenum enumクラスは、小規模な値セットの定義およびその操作を型安全な方法でサポートします。他の言語の列挙とは異なり、enum enum定数はオブジェクトであり、独自のメソッドを持つ場合があります。

第9章では、インタフェースについて説明します。インタフェースのメンバーは、クラス、インタフェース、定数フィールドおよびメソッドです。それ以外には無関係なクラスも、同じインタフェースを実装できます。インタフェース型の変数には、そのインタフェースを実装する任意のオブジェクトへの参照を含めることができます。

クラスおよびインタフェースは、インタフェースからの複数の継承をサポートします。1つ以上のインタフェースを実装するクラスは、スーパークラスとスーパーインタフェースの両方からインスタンス・メソッドを継承できます。

注釈インタフェースは、宣言に注釈を付けるために使用される特別なインタフェースです。このような注釈は、いかなる方法であれJavaプログラミング言語内のプログラムのセマンティクスに影響を及ぼすことが許可されません。ただし、これらは、様々なツールに対して有用な入力を提供します。

第10章では、配列について説明します。配列アクセスには、境界のチェックが含まれます。配列は、動的に作成されたオブジェクトであり、Object型の変数に割り当てることができます。この言語は、多次元の配列ではなく、配列の配列をサポートします。

第11章では、例外について説明します。例外は再開されるものではなく、言語のセマンティクスおよび同時メカニズムと完全に統合されます。例外には、チェック例外、ランタイム例外およびエラーの3種類があります。コンパイラでは、メソッドまたはコンストラクタの結果としてチェック例外が発生するのはメソッドまたはコンストラクタがチェック例外を宣言する場合のみとするよう求めることにより、チェック例外が適切に処理されるよう徹底します。これにより、例外ハンドラが存在するかどうかをコンパイル時にチェックできるようにし、大規模なプログラミングを支援します。ほとんどのユーザー定義の例外はチェック例外にする必要があります。Java仮想マシンによってプログラム内で無効な例外が検出されると、NullPointerExceptionなどのランタイム例外が発生します。Java仮想マシンによって失敗が検出されると、OutOfMemoryErrorなどのエラーが発生します。ほとんどの単純なプログラムでは、エラー処理が試行されません。

第12章では、プログラムの実行時に発生するアクティビティについて説明します。プログラムは通常、コンパイルされたクラスおよびインタフェースを表すバイナリ・ファイルとして格納されます。これらのバイナリ・ファイルをJava仮想マシンにロードし、他のクラスおよびインタフェースとリンクし、初期化できます。

初期化後は、クラス・メソッドおよびクラス変数を使用できます。一部のクラスはインスタンス化し、クラス型の新しいオブジェクトを作成できます。クラス・インスタンスであるオブジェクトには、クラスの各スーパークラスのインスタンスも含まれます。オブジェクトの作成には、これらのスーパークラス・インスタンスの再帰的な作成が含まれます。

オブジェクトが作成されなくなると、ガベージ・コレクタによって回収される場合があります。オブジェクトがファイナライザを宣言すると、このファイナライザがオブジェクトの再利用前に実行され、ファイナライザがなければリリースされなかったはずのリソースをクリーン・アップする機会がオブジェクトに与えられます。クラスが必要なくなると、クラスはアンロードされる場合があります。

第13章では、バイナリの互換性について説明します。ここでは、型を変更すると、変更された型を使用しても再コンパイルはされていない他の型にどのような影響が及ぶのかを明確に示します。これらの考慮事項は、たいていはインターネットを介して連続的なバージョンのシリーズとして広範に配布する型の開発者に関連するものです。優れたプログラム開発環境では、型が変更されるたびに依存コードが自動的に再コンパイルされるため、ほとんどのプログラマはこれらの詳細を意識する必要はありません。

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

第15章では、式について説明します。このドキュメントでは、決定論と移植性を高めるために、式の評価の(明白な)順序を完全に規定します。オーバーロードのメソッドおよびコンストラクタは、適用可能なものの中から最も的確なメソッドまたはコンストラクタを選択することにより、コンパイル時に解決されます。

第16章では、この言語でローカル変数が使用される前に確実に定義されるようにする明確な方法について説明します。他のすべての変数は自動的にデフォルト値に初期化されますが、Javaプログラミング言語では、プログラミング・エラーのマスキングを回避するためにローカル変数は自動的には初期化されません。

第17章では、スレッドおよびロックのセマンティクスについて説明します。これらは、当初はMesaプログラミング言語に導入されたモニターベースの同時実行性に基づいています。Javaプログラミング言語では、高パフォーマンスの実装をサポートする共有メモリーのマルチプロセッサのメモリー・モデルを規定します。

第18章では、汎用メソッドの適用性をテストし、汎用メソッドの呼出し時に型を推測するために使用される様々な型インタフェース・アルゴリズムについて説明します。

第19章では、この言語の構文文法を示します。

第6章: 名前

名前を使用して、プログラム内で宣言されるエンティティを参照します。

宣言されるエンティティ(6.1)は、パッケージ、クラス型(標準またはenum)、インタフェース型(標準または注釈型)、参照型のメンバー(クラス、インタフェース、フィールドまたはメソッド)、(クラス、インタフェース、メソッドまたはコンストラクタの)型パラメータ、(メソッド、コンストラクタまたは例外ハンドラの)パラメータ仮パラメータ、例外パラメータまたはローカル変数です。

この後に、すべてのケースの列挙に特化したセクション(6.1)があります。この章の導入文でその列挙全体を繰り返すことはしません。たとえば、様々な種類のクラス宣言について後ほど説明できます。

プログラム内の名前は、単一の識別子で構成された単純名、または「.」トークンによって区切られた識別子のシーケンスで構成された修飾名のどちらかです(6.2)。

名前を取り入れるすべての宣言にはscope (6.3)があります。これは、プログラム・テキストの一部であり、その中で、宣言されたエンティティを単純名によって参照できます。

修飾名N.xを使用して、パッケージまたは参照型のメンバーを参照できます。この場合、Nは単純名または修飾名で、xは識別子です。Nがパッケージを示す場合、xはそのパッケージのメンバーです。これは、クラスまたはインタフェース型またはサブパッケージです。Nが参照型または参照型の変数を示す場合、xはその型のメンバーを示します。これは、クラス、インタフェース、フィールドまたはメソッドです。

名前の意味を確認する場合(6.5)、発生のコンテキストを使用して、同じ名前を持つパッケージ、型、変数およびメソッド間で区別します。

アクセス制御(6.6)をクラス、インタフェース、メソッドまたはフィールド宣言内で指定して、メンバーへのアクセスを許可するタイミングを制御します。アクセスはスコープとは異なる概念です。アクセスにより、プログラム・テキストの一部を指定し、その中で、宣言されたエンティティを修飾名によって参照できます。宣言されたエンティティへのアクセスは、フィールド・アクセス式(15.11)、メソッドが単純名では指定されないメソッド呼出し式(15.12)、メソッド参照式(15.13)、または修飾クラス・インスタンス作成式(15.9)にも関連します。アクセス修飾子がない場合、ほとんどの宣言はパッケージ・アクセス権を持つため、その宣言が含まれていれば、パッケージ内のどの場所でもアクセスできます。その他の選択肢は、publicprotectedおよびprivateです。

この章では、完全修飾名および正規名(6.7)についても説明します。

6.1 宣言

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

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

仮パラメータと例外パラメータは通常、2つの別個のエンティティとして扱います。一方、クラス・メソッドとインタフェース・メソッドの仮パラメータは1箇所のみで指定し、別個のものとして扱う必要はありません。

コンストラクタ(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で宣言または継承されるメンバーm (8.1.6 8.29.2)の宣言のスコープは、Cの本体全体(ネストしたクラスまたはインタフェース宣言を含む)です。

インタフェース型I (9.1.4)で宣言または継承されるメンバーmの宣言のスコープは、Iの本体全体(ネストした型宣言を含む)です。

enum型Tで宣言されるenum定数Cのスコープは、Tの本体、および式がenum型Tであるswitch文(14.11)のcaseラベルです。

名前は、enum定数に解決されるのではなく、enum型の暗黙的なフィールドに解決されます。switch文は一定の特別な処理を必要とするものの、通常のスコープ/名前解決メカニズムには依存しません。

メソッド(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

6.5 名前の意味の確認

6.5.4 PackageOrTypeNameの意味

6.5.4.1 単純なPackageOrTypeName

PackageOrTypeNameQが有効なTypeIdentifierで、かつQという名前のクラス、インタフェースまたは型パラメータのスコープに出現する場合、PackageOrTypeNameTypeNameとして再分類されます。

そうでない場合、PackageOrTypeNamePackageNameとして再分類されます。PackageOrTypeNameの意味は、再分類された名前の意味です。

6.5.4.2 修飾されたPackageOrTypeName

修飾されたPackageOrTypeNameQ.Idという形式であるとすると、Idが有効なTypeIdentifierで、かつQが示すクラス、インタフェース、型パラメータまたはパッケージにIdという名前のメンバー・クラスまたはインタフェースがある場合、修飾されたPackageOrTypeName名はTypeNameとして再分類されます。

そうでない場合はPackageNameとして再分類されます。修飾されたPackageOrTypeNameの意味は、再分類された名前の意味です。

6.5.5 型名の意味

TypeNameとして分類された名前の意味は、次のように判別されます。

6.5.5.1 単純な型名

型名が単一のIdentifierで構成される場合、その識別子は、この名前を持つクラス、インタフェースまたは型パラメータの正確に1つの宣言のスコープ(6.3)に出現する必要があり、そうでない場合はコンパイル時にエラーが発生します。

型名の意味は、そのクラス、インタフェースまたは型パラメータです。

6.5.5.2 修飾された型名

型名がQ.Idという形式である場合、Qは、現在のモジュールから一意に見えるパッケージ内のクラス、インタフェースまたは型パラメータの名前であるか、現在のモジュールから一意に見えるパッケージの名前である必要があります(7.4.3)。

To-Do: これは正しくないようです。たとえば、ローカル・クラスや型パラメータがパッケージのメンバーになることはありません。import文をチェックして外部パッケージを処理する必要はありませんか。

Idが、Qが示すクラス、インタフェース、型パラメータまたはパッケージのメンバーである、アクセス可能な正確に1つのクラスまたはインタフェース(6.6)の名前である場合、修飾された型名はそのクラスまたはインタフェースを示します。

Idが、Q内のメンバー・クラスまたはインタフェースの名前でない場合(8.59.5)、Q内のIdという名前のメンバー・クラスまたはインタフェースがアクセス可能でない場合(6.6)、あるいはIdQ内の複数のメンバー・クラスまたはインタフェースの名前である場合は、コンパイル時にエラーが発生します。

例6.5.5.2-1.修飾された型名

class Test {
    public static void main(String[] args) {
        java.util.Date date =
            new java.util.Date(System.currentTimeMillis());
        System.out.println(date.toLocaleString());
    }
}

このプログラムを最初に実行したときには、次の出力が生成されました。

Sun Jan 21 22:56:29 1996

この例では、名前java.util.Dateは型を示す必要があるため、最初にこの手順を再帰的に使用して、java.utilがアクセス可能なクラス、インタフェース、型パラメータまたはパッケージのいずれであるかを特定した後、型Dateがこのパッケージ内でアクセス可能であるかどうかを確認します。

第7章: パッケージおよびモジュール

7.3 コンパイル・ユニット

CompilationUnitは、Javaプログラムの構文文法(2.3)の目標シンボル(2.1)です。これは、次のプロダクションによって定義されます。

CompilationUnit:
OrdinaryCompilationUnit
ModularCompilationUnit
OrdinaryCompilationUnit:
[PackageDeclaration] {ImportDeclaration} {TypeDeclaration TopLevelClassOrInterfaceDeclaration}
ModularCompilationUnit:
{ImportDeclaration} ModuleDeclaration

通常コンパイル・ユニットは3つの部分で構成され、それぞれがオプションです。

モジュラ・コンパイル・ユニットmodule宣言(7.7)で構成され、オプションで、その前にimport宣言が含められます。import宣言を使用すると、このモジュールおよび他のモジュールのパッケージ内のクラスおよびインタフェースクラスおよびインタフェースstaticメンバーを、module宣言内でそれらの単純名を使用して参照できます。

package宣言の直後の各コンパイル・ユニットの先頭に宣言import java.lang.*;が出現する場合と同様に、それぞれのコンパイル・ユニットによって、事前定義済のパッケージjava.langで宣言されたそれぞれのpublicクラスまたはインタフェース名が暗黙的にインポートされます。その結果、それらのクラスおよびインタフェースすべての名前をそれぞれのコンパイル・ユニットで単純名として使用できます。

ホスト・システムによって、観察可能なコンパイル・ユニットが特定されます。ただし、事前定義済のパッケージjavaとそのサブパッケージlangおよびio内のコンパイル・ユニットは除きます(これらはすべて、常に観察可能です)。

それぞれの観察可能なコンパイル・ユニットは、次のようにモジュールに関連付けることができます。

コンパイル・ユニットの観察可能性はそのパッケージの観察可能性(7.4.3)に影響し、観察可能なコンパイル・ユニットとモジュールとの関連付けはそのモジュールの観察可能性(7.7.6)に影響します。

モジュールMに関連付けられたモジュラ・コンパイル・ユニットおよび通常コンパイル・ユニットをコンパイルする場合、ホスト・システムは、Mの宣言で指定された依存を順守する必要があります。具体的には、ホスト・システムは、本来なら観察可能な通常コンパイル・ユニットを、Mから見えるもののみに制限する必要があります。Mから見える通常コンパイル・ユニットは、Mによって読み取られるモジュールに関連付けられた、観察可能な通常コンパイル・ユニットです。Mによって読み取られるモジュールは、java.lang.moduleパッケージ仕様に記載されているように、Mを唯一のルート・モジュールとする解決の結果によって指定されます。ホスト・システムは、解決を実行して、Mによって読み取られるモジュールを特定する必要があります。java.lang.moduleパッケージ仕様に記載されているいずれかの理由で解決が失敗すると、コンパイル時にエラーが発生します。

読取り可能性の関係は再帰的であるため、Mはそれ自体を読み取り、Mに関連付けられたモジュラ・コンパイル・ユニットおよび通常コンパイル・ユニットはすべて、Mから見えます。

Mによって読み取られるモジュールによって、Mから一意に見えるパッケージが決まり(7.4.3)、さらに、それによって、スコープ内の最上位パッケージと、Mに関連付けられたモジュラ・コンパイル・ユニットおよび通常コンパイル・ユニット内のコードのパッケージ名の意味の両方が決まります(6.36.5.36.5.5)。

前述のルールによって、モジュラ・コンパイル・ユニット内の注釈(特に、モジュール宣言に適用される注釈)で使用されるパッケージ/型の名前が、モジュールに関連付けられた通常コンパイル・ユニットにそれらが出現する場合と同様に解釈されることが確実になります。

異なる通常コンパイル・ユニットで宣言されたクラスおよびインタフェースは、相互を循環的に参照することがあります。Javaコンパイラは、そのようなすべてのクラスおよびインタフェースを同時にコンパイルするように準備する必要があります。

7.5 インポート宣言

インポート宣言を使用すると、名前付き型またはクラス、インタフェースまたはstaticメンバーを、単一の識別子で構成される単純名(6.2)で参照できます。

適切なインポート宣言を使用しない場合、別のパッケージで宣言された型、または別の型のstaticメンバーを参照する唯一の方法は、完全修飾名(6.7)を使用することです別のパッケージで宣言されたクラスまたはインタフェース、あるいは別のクラスまたはインタフェースのstaticメンバーへの参照では、通常、修飾名(6.7)を使用する必要があります

元の記載に関する問題:

ImportDeclaration:
SingleTypeImportDeclaration
TypeImportOnDemandDeclaration
SingleStaticImportDeclaration
StaticImportOnDemandDeclaration

これらの宣言によってインポートされるクラス、インタフェースまたはメンバーのスコープおよびシャドウ化については、6.3および6.4に規定されています。

import宣言によって、実際にそのimport宣言を含むコンパイル・ユニット内でのみ、クラス、インタフェースまたはメンバーを単純名で使用できるようになります。import宣言によって導入されるクラス、インタフェースまたはメンバーのスコープには、具体的には、同じパッケージ内の他のコンパイル・ユニット、現在のコンパイル・ユニット内の他のimport宣言、または現在のコンパイル・ユニット内のpackage宣言(package宣言の注釈は除く)は含まれません。

7.5.1 単一型インポート宣言

単一型インポート宣言では、単一のクラスまたはインタフェースを、その正規名を指定することによってインポートし、その単一型インポート宣言が出現するコンパイル・ユニットのモジュール、クラスおよびインタフェース宣言内で単純名で使用できるようにします。

SingleTypeImportDeclaration:
import TypeName ;

TypeNameは、クラス型、インタフェース型、enum型または注釈型またはインタフェースの正規名(6.7)である必要があります。

クラスまたはインタフェースは、名前付きパッケージのメンバーであるか、最も外側にある字句的な包含クラスまたはインタフェース宣言(8.1.3)が名前付きパッケージのメンバーであるクラスまたはインタフェースのメンバーである必要があり、そうでない場合はコンパイル時にエラーが発生します。

名前付きクラスまたはインタフェースがアクセス可能でない場合(6.6)、コンパイル時にエラーが発生します。

同じコンパイル・ユニット内の2つの単一型インポート宣言が同じ単純名のクラスまたはインタフェースをインポートしようとすると、コンパイル時にエラーが発生します。ただし、2つのクラスまたはインタフェースが同じである場合は除きます(この場合、重複する宣言は無視されます)。

単一型インポート宣言によってインポートされるクラスまたはインタフェースが、import宣言を含むコンパイル・ユニット内で宣言される場合、import宣言は無視されます。

単純名がnであるクラスまたはインタフェースが単一型インポート宣言によってインポートされ、単純名がnである最上位クラスまたはインタフェース(7.6)もコンパイル・ユニットで宣言される場合、コンパイル時にエラーが発生します。

単純名がnであるクラスまたはインタフェースをインポートする単一型インポート宣言と、単純名がnであるクラスまたはインタフェースをインポートする単一静的インポート宣言(7.5.3)の両方がコンパイル・ユニットに含まれる場合、コンパイル時にエラーが発生します。ただし、2つのクラスまたはインタフェースが同じである場合は除きます(この場合、重複する宣言は無視されます)。

例7.5.1-1.単一型インポート

import java.util.Vector;

これにより、単純名Vectorがコンパイル・ユニット内のクラスおよびインタフェース宣言内で使用可能になります。つまり、単純名Vectorは、フィールド、パラメータまたはローカル変数の宣言や、同じ名前を持つネストしたクラスまたはインタフェース宣言によってシャドウ化(6.4.1)または不明瞭化(6.4.2)されていないすべての場所で、パッケージjava.util内のクラス宣言Vectorを参照します。

java.util.Vectorの実際の宣言は汎用です(8.1.2)。インポートされた後、名前Vectorは、Vector<String>などのパラメータ化された型内の修飾なしで、またはRAW型Vectorとして使用できます。import宣言に関連する制限として、汎用クラスまたはインタフェース宣言内で宣言されたネストした型メンバー・クラスまたはインタフェースはインポートできますが、その外側の型は常に消去されます。

例7.5.1-2.クラスまたはインタフェース宣言の重複

次のプログラムを実行すると、

import java.util.Vector;
class Vector { Object[] vec; }

次の場合と同様に、Vectorの宣言が重複するため、コンパイル時にエラーが発生します。

import java.util.Vector;
import myVector.Vector;

この場合、myVectorは、コンパイル・ユニットが含まれるパッケージです。

package myVector;
public class Vector { Object[] vec; }

例7.5.1-3.サブパッケージのインポートなし

import宣言でサブパッケージをインポートすることはできません。インポートできるのは、クラスまたはインタフェースのみです。

たとえば、java.utilをインポートし、名前util.Randomを使用して型java.util.Randomを参照しようとしても動作しません。

import java.util;
class Test { util.Random generator; }
  // incorrect: compile-time error

例7.5.1-4.パッケージ名でもある型名のインポート

パッケージ名および型名は通常、6.1で説明されている命名規則では異なるものです。ただし、人為的な例として次を想定します。ここで使用されるのは、Mosquitoという名前のパブリック・クラスを宣言する、慣例に従わない名前が付けられたパッケージVector:

package Vector;
public class Mosquito { int capacity; }

および次のコンパイル・ユニットです。

package strange;
import java.util.Vector;
import Vector.Mosquito;
class Test {
    public static void main(String[] args) {
        System.out.println(new Vector().getClass());
        System.out.println(new Mosquito().getClass());
    }
}

クラスVectorをパッケージjava.utilからインポートする単一型インポート宣言では、パッケージ名Vectorが後続のimport宣言に出現して適切に認識されることが阻止されることはありません。この例では、次の出力をコンパイルして生成しています。

class java.util.Vector
class Vector.Mosquito

7.5.2 オンデマンド型インポート宣言

オンデマンド型インポート宣言を使用すると、名前付きパッケージ、クラスまたはインタフェースまたは型のアクセス可能なすべてのクラスおよびインタフェースを必要に応じてインポートできます。

TypeImportOnDemandDeclaration:
import PackageOrTypeName . * ;

PackageOrTypeNameは、パッケージ、クラスまたはインタフェース型、enum型または注釈型の正規名(6.7)である必要があります。

PackageOrTypeNameクラスまたはインタフェースを示す場合(6.5.4)、そのクラスまたはインタフェースは、名前付きパッケージのメンバーであるか、最も外側にある字句的な包含クラスまたはインタフェース宣言(8.1.3)が名前付きパッケージのメンバーであるクラスまたはインタフェースのメンバーである必要があり、そうでない場合はコンパイル時にエラーが発生します。

名前付きパッケージが現在のモジュールから一意に見えない場合(7.4.3)、または名前付きクラスまたはインタフェースがアクセス可能でない場合(6.6)、コンパイル時にエラーが発生します。

オンデマンド型インポート宣言で現在のコンパイル・ユニットの名前付きパッケージまたはjava.langのいずれかに名前を付ける場合、コンパイル時エラーにはなりません。このような場合、オンデマンド型インポート宣言は無視されます。

同じコンパイル・ユニット内の2つ以上のオンデマンド型インポート宣言によって、同じ型またはパッケージ、クラスまたはインタフェースの名前が指定されることがあります。これらの宣言の1つを除いてすべてが重複しているとみなされます。この結果、その型は1回のみインポートされたものとして扱われます。

同じクラスまたはインタフェースの名前を指定するオンデマンド型インポート宣言とオンデマンド静的インポート宣言(7.5.4)の両方がコンパイル・ユニットに含まれる場合、そのクラスまたはインタフェースstaticメンバー・クラスおよびインタフェース(8.59.5)が1回のみインポートされた場合と同様の結果になります。

例7.5.2-1.オンデマンド型インポート

import java.util.*;

これにより、パッケージjava.util内で宣言されたすべてのpublicクラスおよびインタフェースの単純名がコンパイル・ユニットのクラスおよびインタフェース宣言内で使用可能になります。つまり、単純名Vectorは、クラスVectorの宣言がシャドウ化(6.4.1)または不明瞭化(6.4.2)されていないコンパイル・ユニット内のすべての場所でパッケージjava.util内のそのクラスを参照します。

この宣言は、単純名がVectorであるクラスまたはインタフェースの単一型インポート宣言によって、コンパイル・ユニットが属するパッケージ内で宣言されたVectorという名前のクラスまたはインタフェースによって、あるいはネストしたクラスまたはインタフェースによってシャドウ化される場合があります。

この宣言は、フィールド、パラメータ、またはVectorという名前のローカル変数の宣言によって不明瞭化される場合があります。

(これらのいずれかの状況が発生するのは異常です。)

7.5.3 単一静的インポート宣言

単一静的インポート宣言では、特定の単純名を持つアクセス可能なすべてのstaticメンバーをクラスまたはインタフェースからインポートします。これにより、単一静的インポート宣言が出現するコンパイル・ユニットのモジュール、クラスおよびインタフェース宣言内でstaticメンバーを単一名で使用できるようになります。

SingleStaticImportDeclaration:
import static TypeName . Identifier ;

TypeNameは、クラス型、インタフェース型、enum型または注釈型またはインタフェースの正規名(6.7)である必要があります。

クラスまたはインタフェースは、名前付きパッケージのメンバーであるか、最も外側にある字句的な包含クラスまたはインタフェース宣言(8.1.3)が名前付きパッケージのメンバーであるクラスまたはインタフェースのメンバーである必要があり、そうでない場合はコンパイル時にエラーが発生します。

名前付きクラスまたはインタフェースがアクセス可能でない場合(6.6)、コンパイル時にエラーが発生します。

Identifierは、名前付きクラスまたはインタフェースの少なくとも1つのstaticメンバーの名前である必要があります。その名前を持つstaticメンバーがないか、名前付きメンバーがすべてアクセス不可能である場合は、コンパイル時にエラーが発生します。

1つの単一静的インポート宣言で、同じ名前を持つ複数のフィールド、クラスまたはインタフェースまたは型、あるいは同じ名前とシグネチャを持つ複数のメソッドをインポートできます。このことが発生するのは、名前付きクラスまたはインタフェースが、すべて同じ名前を持つ複数のフィールド、メンバー・クラス、メンバー・インタフェースまたはメソッドをそれ自体のスーパー・タイプから継承する場合です。

同じコンパイル・ユニット内の2つの単一静的インポート宣言が同じ単純名のクラスまたはインタフェースをインポートしようとすると、コンパイル時にエラーが発生します。ただし、2つのクラスまたはインタフェースが同じである場合は除きます(この場合、重複する宣言は無視されます)。

単純名がnであるクラスまたはインタフェースが単一静的インポート宣言によってインポートされ、単純名がnである最上位クラスまたはインタフェース(7.6)もコンパイル・ユニットで宣言される場合、コンパイル時にエラーが発生します。

単純名がnであるクラスまたはインタフェースをインポートする単一静的インポート宣言と、単純名がnであるクラスまたはインタフェースをインポートする単一型インポート宣言(7.5.1)の両方がコンパイル・ユニットに含まれる場合、コンパイル時にエラーが発生します。ただし、2つのクラスまたはインタフェースが同じである場合は除きます(この場合、重複する宣言は無視されます)。

7.5.4 オンデマンド静的インポート宣言

オンデマンド静的インポート宣言を使用すると、名前付きクラスまたはインタフェースのアクセス可能なすべてのstaticメンバーを必要に応じてインポートできます。

StaticImportOnDemandDeclaration:
import static TypeName . * ;

TypeNameは、クラス型、インタフェース型、enum型または注釈型またはインタフェースの正規名(6.7)である必要があります。

クラスまたはインタフェースは、名前付きパッケージのメンバーであるか、最も外側にある字句的な包含クラスまたはインタフェース宣言(8.1.3)が名前付きパッケージのメンバーであるクラスまたはインタフェースのメンバーである必要があり、そうでない場合はコンパイル時にエラーが発生します。

名前付きクラスまたはインタフェースがアクセス可能でない場合(6.6)、コンパイル時にエラーが発生します。

同じコンパイル・ユニット内の2つ以上のオンデマンド静的インポート宣言によって、同じクラスまたはインタフェースの名前が指定されることがあります。この場合、そのような宣言が1つのみ存在する場合と同様の結果になります。

同じコンパイル・ユニット内に複数のオンデマンド静的インポート宣言がある場合、同じメンバーの名前が付けられる場合があります。この結果、メンバーが正確に1回インポートされたものとして扱われます。

1つのオンデマンド静的インポート宣言で、同じ名前を持つ複数のフィールド、クラスまたはインタフェースまたは型、あるいは同じ名前とシグネチャを持つ複数のメソッドをインポートできます。このことが発生するのは、名前付きクラスまたはインタフェースが、すべて同じ名前を持つ複数のフィールド、メンバー・クラス、メンバー・インタフェースまたはメソッドをそれ自体のスーパー・タイプから継承する場合です。

同じクラスまたはインタフェースの名前を指定するオンデマンド静的インポート宣言とオンデマンド型インポート宣言(7.5.2)の両方がコンパイル・ユニットに含まれる場合、そのクラスまたはインタフェースstaticメンバー・クラスおよびインタフェース(8.59.5)が1回のみインポートされた場合と同様の結果になります。

7.6 最上位クラスおよびインタフェース宣言

最上位クラスまたはインタフェース宣言では、enumクラス(8.9)である可能性がある最上位クラス(8 8.1)、または注釈インタフェース(9.6)である可能性がある最上位インタフェース(9 9.1)を宣言します。

TypeDeclaration: TopLevelClassOrInterfaceDeclaration:
ClassDeclaration
InterfaceDeclaration
ClassOrInterfaceDeclaration
;
ClassOrInterfaceDeclaration:
ClassDeclaration
EnumDeclaration
InterfaceDeclaration
AnnotationDeclaration

コンパイル・ユニット内でクラスまたはインタフェース宣言のレベルに出現する余分な;トークンは、コンパイル・ユニットの意味に影響しません。Javaプログラミング言語では、不要なセミコロンは、クラス宣言の後に;を配置することに慣れているC++プログラマのための便宜的措置としてのみ許容されています。新しいJavaコードでは使用しないでください。

アクセス修飾子がない場合、最上位クラスまたはインタフェースはパッケージ・アクセス権を持ちます。それが宣言されたパッケージの通常コンパイル・ユニット内でのみアクセス可能です(6.6.1)。クラスまたはインタフェースpublicとして宣言すると、同じモジュールの他のパッケージのコードから、また場合によっては他のモジュールのパッケージのコードからクラスまたはインタフェースへのアクセスを許可できます。

protectedprivateまたはstaticのいずれかのアクセス修飾子が最上位クラスまたはインタフェース宣言に含まれている場合は、コンパイル時にエラーが発生します。

最上位クラスまたはインタフェースの名前が、同じパッケージで宣言された他の最上位クラスまたはインタフェースの名前として出現する場合は、コンパイル時にエラーが発生します。

最上位クラスまたはインタフェースのスコープおよびシャドウ化については、6.3および6.4に規定されています。

最上位クラスまたはインタフェースの完全修飾名については、6.7に規定されています。

例7.6-1.最上位クラスまたはインタフェース宣言の競合

package test;
import java.util.Vector;
class Point {
    int x, y;
}
interface Point {  // compile-time error #1
    int getR();
    int getTheta();
}
class Vector { Point[] pts; }  // compile-time error #2

ここでは、最初のコンパイル時エラーの原因は、名前Pointを同じパッケージ内でクラスとインタフェースの両方として重複して宣言していることです。2つ目のコンパイル時エラーでは、クラス宣言と単一型インポート宣言の両方によって名前Vectorを宣言しようとしています。

ただし、クラスの名前が、そのクラス宣言を含むコンパイル・ユニット(7.3)内のオンデマンド型インポート宣言(7.5.2)によって別にインポートされる可能性があるクラスまたはインタフェースの名前でもある場合には、エラーにはなりません。つまり、次のプログラムでは、

package test;
import java.util.*;
class Vector {}  // not a compile-time error

クラスVectorの宣言は、クラスjava.util.Vectorも存在するとしても許可されます。このコンパイル・ユニット内では、単純名Vectorはクラスtest.Vectorを参照し、java.util.Vectorを参照するわけではありません(コンパイル・ユニット内のコードによってこれを参照することはできますが、完全修飾名によってのみ可能です)。

例7.6-2.最上位クラスおよびインタフェースのスコープ

package points;
class Point {
    int x, y;           // coordinates
    PointColor color;   // color of this point
    Point next;         // next point with this color
    static int nPoints;
}
class PointColor {
    Point first;        // first point with this color
    PointColor(int color) { this.color = color; }
    private int color;  // color components
}

このプログラムでは、クラス・メンバーの宣言で相互を使用する2つのクラスが定義されます。クラス型クラスPointおよびPointColorは、パッケージpoints内のすべてのクラス宣言(現在のコンパイル・ユニット内のものすべてを含む)をスコープとして持つため、このプログラムは正しくコンパイルされます。つまり、前方参照は問題ではありません。

例7.6-3.完全修飾名

class Point { int x, y; }

このコードでは、package宣言がないコンパイル・ユニット内でクラスPointが宣言されているため、Pointはその完全修飾名です。一方、次のコードでは、

package vista;
class Point { int x, y; }

クラスPointの完全修飾名はvista.Pointです。(パッケージ名vistaは、ローカルまたは個人での使用には適しています。パッケージを広範に配布する予定がある場合は、一意のパッケージ名を付けることをお薦めします(6.1)。)

Java SEプラットフォームの実装では、パッケージ内のクラスおよびインタフェースを、それらの包含モジュール名とバイナリ名(13.1)の組合せによって追跡する必要があります。クラスまたはインタフェースの複数の命名方法をバイナリ名に拡張して、そのような名前が同じクラスまたはインタフェースを参照するものとして確実に理解されるようにする必要があります。

たとえば、コンパイル・ユニットに次の単一型インポート宣言(7.5.1)が含まれているとします。

import java.util.Vector;

この場合、そのコンパイル・ユニット内では、単純名Vectorと完全修飾名java.util.Vectorは同じクラスを参照します。

パッケージがファイル・システムに格納されている場合(7.2)にのみ、ホスト・システムは、次のいずれかに該当し、かつ、クラスまたはインタフェースが、そのクラスまたはインタフェース名と拡張子(.java.javなど)で構成される名前のファイルに見つからない場合はコンパイル時にエラーが発生するという制限を強制することを選択できます。

この制限は、1つのコンパイル・ユニットについてそのようなクラスまたはインタフェースを1つまでにする必要があることを意味します。この制限によって、Javaコンパイラはパッケージ内で名前付きクラスまたはインタフェースを容易に見つけることができます。実際には、多くのプログラマは、各クラスまたはインタフェースpublicであるかどうかや、他のコンパイル・ユニットのコードによって参照されているかどうかに関係なく、クラスまたはインタフェースをその独自のコンパイル・ユニットに配置します。

たとえば、publicwet.sprocket.Toadのソース・コードはディレクトリwet/sprocketのファイルToad.java内にあり、対応するオブジェクト・コードは同じディレクトリのファイルToad.class内にあります。

7.7 モジュール宣言

7.7.4 サービスのプロビジョニング

providesディレクティブでサービスを指定し、with句でjava.util.ServiceLoaderに対する1つ以上のサービス・プロバイダを指定します。

サービスは、クラス型、インタフェース型または注釈型またはインタフェース(enumクラス以外)である必要があります。providesディレクティブでenumクラス(8.9)をサービスとして指定した場合は、コンパイル時にエラーが発生します。

サービスは、現在のモジュール内でも別のモジュール内でも宣言できます。サービスが現在のモジュール内で宣言されていない場合、現在のモジュール内のコードからサービスにアクセスできる必要があり(6.6)、そうでない場合はコンパイル時にエラーが発生します。

それぞれのサービス・プロバイダは、publicで、かつ最上位であるか、ネストしたstaticであるpublicクラスまたはインタフェースである必要があり、そうでない場合はコンパイル時にエラーが発生します。

「ネストしたstatic」が意味するものが後であいまいになります。「ネストした」という単語を単に取り除くことによって、それを解決できます。正常に参照できるstaticクラスまたはインタフェースで問題ありません。

それぞれのサービス・プロバイダは現在のモジュール内で宣言する必要があります。そうしないとコンパイル時にエラーが発生します。

サービス・プロバイダが、仮パラメータがないpublicコンストラクタを明示的に宣言するか、publicなデフォルト・コンストラクタ(8.8.9)を暗黙的に宣言する場合、そのコンストラクタはプロバイダ・コンストラクタと呼ばれます。

サービス・プロバイダが、仮パラメータがないproviderというpublicstaticメソッドを明示的に宣言する場合、そのメソッドはプロバイダ・メソッドと呼ばれます。

サービス・プロバイダにプロバイダ・メソッドがある場合、その戻り型は、(i)現在のモジュール内で宣言されているか、別のモジュール内で宣言されていて、現在のモジュール内のコードからアクセス可能であり、かつ(ii) providesディレクティブで指定されたサービスのサブタイプである必要があります。そうでない場合はコンパイル時にエラーが発生します。

providesディレクティブによって指定されるサービス・プロバイダは現在のモジュール内で宣言する必要がありますが、そのプロバイダ・メソッドは、別のモジュール内で宣言された戻り型を持つことができます。また、サービス・プロバイダがプロバイダ・メソッドを宣言する場合、サービス・プロバイダ自体がサービスのサブタイプである必要はありません。

サービス・プロバイダにプロバイダ・メソッドがない場合、そのサービス・プロバイダはプロバイダ・コンストラクタを持つ必要があるとともに、providesディレクティブで指定されたサービスのサブタイプである必要があります。そうでない場合はコンパイル時にエラーが発生します。

モジュール宣言内の複数のprovidesディレクティブで同じサービスを指定した場合、コンパイル時にエラーが発生します。

特定のprovidesディレクティブのwith句で同じサービス・プロバイダを複数回指定した場合、コンパイル時にエラーが発生します。

第8章: クラス

クラス宣言では、新しい参照型を定義し、これらの実装方法について記述します(8.1)。

最上位クラス(7.6)は、ネストしたクラスではないコンパイル・ユニットの最上位で宣言されるクラスです。

ネストしたクラスは、宣言が別のクラスまたはインタフェースの本体内で行われるクラスです。ネストしたクラスは、メンバー・クラス(8.59.5)、ローカル・クラス(14.3)または無名クラス(15.9.5)である場合があります。

内部クラス(8.1.3)は、包含クラス・インスタンス、ローカル変数および型変数を参照できる、ネストしたクラスです。

enumクラス(8.9)は、名前付きクラス・インスタンスの小規模なセットを定義する特別な構文で宣言されるクラスです。

この章では、すべてのクラス、つまり、最上位クラス(7.6)とネストしたクラス(メンバー・クラス(8.59.5)、ローカル・クラス(14.3)および無名クラス(15.9.5)を含む)の共通セマンティクスについて説明します。特定の種類のクラスに固有の詳細は、これらのコンストラクトに特化したセクションで説明します。

名前付きクラスはabstract (8.1.1.1)として宣言でき、これが不完全に実装されている場合は抽象であると宣言する必要があります。このようなクラスはインスタンス化できませんが、サブクラスによって拡張できます。クラスはfinal (8.1.1.2)として宣言される場合があります。この場合、このクラスはサブクラスを持つことができません。クラスがpublicとして宣言されている場合、そのモジュールの任意のパッケージ内のコードから、および場合によっては他のモジュール内のコードから参照できます。Objectを除く各クラスは、既存の単一のクラスの拡張(つまり、サブクラス) (8.1.4)であり、インタフェースを実装する場合があります(8.1.5)。クラスは汎用(8.1.2)である場合があります。つまり、クラスは、クラスの様々なインスタンス間でバインディングが異なる可能性のある型変数を宣言できます。

クラスは、他の種類の宣言と同じように、注釈(9.7)を使用して修飾できます。

クラスの本体では、メンバー(フィールドおよびメソッドおよびネストしたクラスおよびインタフェース)、インスタンス・イニシャライザ、静的イニシャライザおよびコンストラクタ(8.1.6)を宣言します。メンバー(8.2)のスコープ(6.3)は、メンバーが属するクラスの宣言の本体全体です。フィールド、メソッド、メンバー・クラス、メンバー・インタフェースおよびコンストラクタ宣言には、アクセス修飾子(6.6) publicprotectedまたはprivateが含まれる場合があります。クラスのメンバーには、宣言されたメンバーと継承されたメンバーの両方が含まれます(8.2)。新しく宣言されたフィールドは、スーパークラスまたはスーパーインタフェース内で宣言されたフィールドを隠すことができます。新しく宣言されたクラス・メンバーメンバー・クラスおよびインタフェース・メンバーメンバー・インタフェースは、スーパークラスまたはスーパーインタフェース内で宣言されたクラス・メンバーまたはインタフェース・メンバーメンバー・クラスおよびメンバー・インタフェースを隠すことができます。新しく宣言されたメソッドは、スーパークラスまたはスーパーインタフェース内で宣言されたメソッドを隠す、実装する、またはオーバーライドすることができます。

フィールド宣言(8.3)では、1回インカネーションされるクラス変数、およびクラスのインスタンスごとに新しくインカネーションされるインスタンス変数を記述します。フィールドはfinal (8.3.1.2)として宣言できます。この場合、このフィールドは1回のみ割り当てることができます。任意のフィールド宣言にイニシャライザを含めることができます。

メンバー・クラス宣言(8.5)では、前後のクラスのメンバーであるネストしたクラスを記述します。メンバー・クラスはstaticである場合があります。この場合、メンバー・クラスは、前後のクラスのインスタンス変数にアクセスできません。または、内部クラス(8.1.3)である場合もあります。

メンバー・インタフェース宣言(8.5)では、前後のクラスのメンバーであるネストしたインタフェースを記述します。

メソッド宣言(8.4)では、メソッド呼出し式(15.12)によって呼び出すことのできるコードを記述します。クラス・メソッドは、クラスに関連して呼び出されます。インスタンス・メソッドは、クラスのインスタンスである特定のオブジェクトに関連して呼び出されます。宣言が実装方法を示していないメソッドは、abstractとして宣言する必要があります。メソッドはfinal (8.4.3.3)として宣言される場合があります。この場合、このメソッドは隠したりオーバーライドすることができません。メソッドは、プラットフォーム依存のnativeコード(8.4.3.4)によって実装される場合があります。synchronizedメソッド(8.4.3.6)は、synchronized文(14.19)で使用されるように、本体を実行する前にオブジェクトを自動的にロックし、戻り時にオブジェクトを自動的にロック解除します。これにより、そのアクティビティを他のスレッド(17)のアクティビティと同期できるようになります。

メソッド名はオーバーロードする場合があります(8.4.9)。

インスタンス・イニシャライザ(8.6)は、作成時にインスタンスの初期化をサポートするために使用できる実行可能コードのブロックです(15.9)。

静的イニシャライザ(8.7)は、クラスの初期化をサポートするために使用できる実行可能コードのブロックです。

コンストラクタ(8.8)はメソッドと似ていますが、メソッド呼出しによって直接呼び出すことはできません。コンストラクタは、新しいクラス・インスタンスを初期化するために使用されます。メソッドと同様、これはオーバーロードする場合があります(8.8.8)。

8.1 クラス宣言

クラス宣言では、新しい名前付き参照型新しいクラスを指定します。

標準クラス宣言enum宣言という2種類のクラス宣言があります。

ClassDeclaration:
NormalClassDeclaration
EnumDeclaration
NormalClassDeclaration: ClassDeclaration:
{ClassModifier} class TypeIdentifier [TypeParameters]
[Superclass] [Superinterfaces] ClassBody

このセクションのルールは、enum宣言を含め、すべてのクラス宣言に適用されます。ただし、クラス修飾子、内部クラスおよびスーパークラスに関してはenum宣言に特別なルールが適用されます。これらのルールについては、8.9を参照してください。

enumクラスはクラスの一種ですが、enum宣言は別個の構文コンストラクトです。ここにそれらをまとめる特別な理由はありません。

クラスに関する個々の宣言形式(enum宣言および無名クラス宣言を含む)が第8章全体で規定されているルールにどのように関連するかについての説明は、すでに8に記載されています。ここでそれを繰り返すと混乱が生じます。

クラス宣言内のTypeIdentifierは、クラスの名前を指定します。

クラスの単純名が前後のクラスまたはインタフェースの単純名と同じである場合は、コンパイル時にエラーが発生します。

クラス宣言のスコープおよびシャドウ化については、6.3および6.4に規定されています。

8.1.1 クラス修飾子

クラス宣言には、クラス修飾子を含めることができます。

ClassModifier:
(次のうちの1つ)
Annotation public protected private
abstract static final strictfp

クラス宣言の注釈修飾子に関するルールについては、9.7.4および9.7.5に規定されています。

アクセス修飾子public (6.6)は、最上位クラス(7.6)とメンバー・クラス(8.59.5)にのみ関連し、ローカル・クラス(14.3)または無名クラス(15.9.5)には関連しません。

アクセス修飾子protectedおよびprivateは、直接包含するクラス宣言内のメンバー・クラス(8.5)にのみ関連します。

修飾子staticは、メンバー・クラス(8.5.1)にのみ関連し、最上位またはローカル・または無名クラスには関連しません。

ここでは、ClassDeclaration構文に出現するClassModifierプロダクションについて説明しています。これは、最上位クラス、メンバー・クラスおよびローカル・クラスによって共有されます。無名クラス宣言にClassModifierプロダクションはないため、そのようなクラスについてここで述べても意味がありません。

クラス宣言について同じキーワードが修飾子として複数回出現する場合、またはアクセス修飾子publicprotectedおよびprivate (6.6)のうちの複数がクラス宣言に含まれる場合、コンパイル時にエラーが発生します。

2つ以上の(別個の)クラス修飾子が1つのクラス宣言に出現する場合は、ClassModifierのプロダクションでの出現順序が前述の内容と一致することが慣例ですが、必須ではありません。

8.1.1.3 strictfpクラス

strictfp修飾子には、クラス宣言内(変数イニシャライザ、インスタンス・イニシャライザ、静的イニシャライザおよびコンストラクタ内を含む)のすべてのfloatまたはdouble式を明示的にFP-strict (15.4)にするという効果があります。

これは、そのクラスで宣言されるすべてのメソッドと、そのクラスで宣言されるすべてのネストしたクラスおよびインタフェースは暗黙的にstrictfpであることを意味します。

8.1.3 内部クラスと包含インスタンス

内部クラスは、明示的にも暗黙的にもstaticとして宣言されていない、ネストしたクラスです。

内部クラスは、非staticメンバー・クラス(8.5)、ローカル・クラス(14.3)または無名クラス(15.9.5)である場合があります。インタフェースのメンバー・クラスは暗黙的にstaticであるため(9.5)、内部クラスとみなされることはありません。

内部クラスで静的イニシャライザ(8.7)を宣言した場合、コンパイル時にエラーが発生します。

明示的または暗黙的にstaticであるメンバーを内部クラスで宣言した場合、コンパイル時にエラーが発生します。ただし、そのメンバーが定数変数(4.12.4)である場合は除きます。

内部クラスは、定数変数でないstaticメンバーを宣言することはできませんが、それらを継承することがあります。

内部クラスでないネストしたクラスでは、Javaプログラミング言語の通常のルールに従って、staticメンバーを自由に宣言できます。

例8.1.3-1.内部クラス宣言とstaticメンバー

class HasStatic {
    static int j = 100;
}
class Outer {
    class Inner extends HasStatic {
        static final int x = 3;  // OK: constant variable
        static int y = 4;  // Compile-time error: an inner class
    }
    static class NestedButNotInner{
        static int z = 5;    // OK: not an inner class
    }
    interface NeverInner {}  // Interfaces are never inner
}

文または式が静的コンテキストに出現するのは、その文または式を包含する最も内側のメソッド、コンストラクタ、インスタンス・イニシャライザ、静的イニシャライザ、フィールド・イニシャライザまたは明示的なコンストラクタ呼出し文が静的メソッド、静的イニシャライザ、静的変数の変数イニシャライザ、または明示的なコンストラクタ呼出し文(8.8.7.1)である場合のみです。

内部クラスCクラスまたはインタフェースOの直接内部クラスとなるのは、Oが、Cを直接包含するクラスまたはインタフェース宣言で、かつCの宣言が静的コンテキストに出現しない場合です。

内部クラスがローカル・クラスまたは無名クラスである場合は静的コンテキストで宣言でき、その場合、包含クラスまたはインタフェースの内部クラスとみなされることはありません。

クラスCクラスまたはインタフェースOの内部クラスとなるのは、それがOの直接内部クラスであるか、Oの内部クラスの内部クラスである場合です。

内部クラスを直接包含するクラスまたはインタフェース宣言がインタフェースであることはまれですが、その可能性はあります。このような状況が発生するのは、そのクラスが、デフォルトまたは静的メソッド本体(9.4)で宣言されたローカル・クラスまたは無名クラスである場合のみです。具体的には、デフォルト・メソッド本体で無名クラスまたはローカル・クラスが宣言されるか、デフォルト・メソッド本体で宣言された無名クラスの本体でメンバー・クラスが宣言されると、このような状況が発生します。

その2つ目の例では、直接包含する型宣言はインタフェースではなく、無名クラスです。

クラスまたはインタフェースOは、それ自体のゼロ番目の字句的な包含型宣言です。

クラスOクラスCのn番目の字句的な包含型宣言となるのは、それがCn-1番目の字句的な包含型宣言を直接包含する型宣言である場合です。

クラスまたはインタフェースOの直接内部クラスCのインスタンスiは、iを直接包含するインスタンスと呼ばれる、Oのインスタンスに関連付けられます。オブジェクトを直接包含するインスタンス(ある場合)は、オブジェクトの作成時に特定されます(15.9.2)。

オブジェクトoは、それ自体のゼロ番目の字句的な包含インスタンスです。

オブジェクトoインスタンスiのn番目の字句的な包含インスタンスとなるのは、それがin-1番目の字句的な包含インスタンスを直接包含するインスタンスである場合です。

宣言が静的コンテキストに出現する内部クラスIローカル・クラスまたは無名クラスのインスタンスには、字句的な包含インスタンスはありません。ただし、Iが静的メソッドまたは静的イニシャライザ内で直接宣言されている場合、Iには、Iの宣言を字句的に包含する最も内側のブロック文である包含ブロックがあります。

「包含ブロック」という用語は、この段落外では使用されていません。このセクションにはすでに圧倒的な量の新しい用語が含まれているため、この追加定義はない方がよいです。

それ自体がクラスまたはインタフェースSOの直接内部クラスであるCのそれぞれのスーパークラスSについては、Sに関してiを直接包含するインスタンスと呼ばれる、iに関連付けられたSOのインスタンスがあります。そのクラス直接スーパークラス(ある場合)に関してオブジェクトを直接包含するインスタンスは、明示的なコンストラクタ呼出し文(8.8.7.1)を介してスーパークラス・コンストラクタが呼び出されるときに特定されます。

内部クラス(宣言が静的コンテキストに出現しないもの)が、字句的な包含クラスまたはインタフェース宣言のメンバーであるインスタンス変数を参照する場合、対応する字句的な包含インスタンスの変数が使用されます。

内部クラスで使用されるが、宣言されていないローカル変数、仮パラメータまたは例外パラメータは、finalとして宣言するか、実質的にfinalである(4.12.4)必要があります。そうでない場合は、その使用が試行されると、コンパイル時にエラーが発生します。

内部クラスで使用されるが、宣言されていないローカル変数は、内部クラスの本体より前で明確に割り当てる必要があります(16)。そうしないとコンパイル時にエラーが発生します。

変数の使用に関する同様のルールがラムダ式(15.27.2)の本体にも適用されます。

字句的な包含クラスまたはインタフェース宣言の空白のfinalフィールド(4.12.4)を内部クラス内で割り当てることはできません。そのようにすると、コンパイル時にエラーが発生します。

例8.1.3-2.内部クラス宣言

class Outer {
    int i = 100;
    static void classMethod() {
        final int l = 200;
        class LocalInStaticContext {
            int k = i;  // Compile-time error
            int m = l;  // OK
        }
    }
    void foo() {
        class Local {  // A local class
            int j = i;
        }
    }
}

クラスLocalInStaticContextの宣言は、静的メソッドclassMethod内にあるため、静的コンテキストに出現します。クラスOuterのインスタンス変数は、静的メソッドの本体内では使用できません。具体的には、Outerのインスタンス変数をLocalInStaticContextの本体内で使用することはできません。ただし、前後のメソッドからのローカル変数はエラーなしで参照できます(それらがfinalとして宣言されているか、実質的にfinalである場合)。

宣言が静的コンテキストに出現しない内部クラスは、それらの包含クラスまたはインタフェース宣言のインスタンス変数を自由に参照できます。インスタンス変数は常にインスタンスに関して定義されます。包含クラスまたはインタフェース宣言のインスタンス変数については、その宣言された型クラスまたはインタフェースの包含インスタンスに関してインスタンス変数を定義する必要があります。たとえば、前述のクラスLocalには、クラスOuterの包含インスタンスがあります。さらに、次のような例があるとします。

class WithDeepNesting {
    boolean toBe;
    WithDeepNesting(boolean b) { toBe = b; }

    class Nested {
        boolean theQuestion;
        class DeeplyNested {
            DeeplyNested(){
                theQuestion = toBe || !toBe;
            }
        }
    }
}

ここでは、WithDeepNesting.Nested.DeeplyNestedのそれぞれのインスタンスに、クラスWithDeepNesting.Nestedの包含インスタンス(それを直接包含するインスタンス)とクラスWithDeepNestingの包含インスタンス(その2番目の字句的な包含インスタンス)があります。

8.1.6 クラス本体およびメンバー宣言

クラス本体には、クラスのメンバー、つまり、フィールド(8.3)、メソッド(8.4)、およびクラス(8.5)、とインタフェース(8.5)の宣言を含めることができます。

クラス本体に、クラスのインスタンス・イニシャライザ(8.6)、静的イニシャライザ(8.7)、およびコンストラクタの宣言(8.8)を含めることもできます。

ClassBody:
{ {ClassBodyDeclaration} }
ClassBodyDeclaration:
ClassMemberDeclaration
InstanceInitializer
StaticInitializer
ConstructorDeclaration
ClassMemberDeclaration:
FieldDeclaration
MethodDeclaration
ClassDeclaration
InterfaceDeclaration
ClassOrInterfaceDeclaration
;

クラス型Cで宣言または継承されるメンバーmの宣言のスコープおよびシャドウ化については、6.3および6.4に規定されています。

C自体がネストしたクラスである場合は、包含スコープにmと同じ種類(変数、メソッドまたは型)および名前の定義がある可能性があります。(このスコープは、ブロック、クラスまたはパッケージである場合があります。)そのようなすべての場合において、Cで宣言または継承されたメンバーmによって、同じ種類および名前の他の定義がシャドウ化されます(6.4.1)。

8.5 メンバー・クラスおよびインタフェース宣言

メンバー・クラスは、宣言が別のクラスまたはインタフェース宣言の本体(8.1.69.1.4)に直接包含されているクラスです。メンバー・クラスはenumクラス(8.9)である場合があります。

メンバー・インタフェースは、宣言が別のクラスまたはインタフェース宣言の本体(8.1.69.1.4)に直接包含されているインタフェースです。メンバー・インタフェースは注釈インタフェース(9.6)である場合があります。

クラス内のメンバー・クラスまたはインタフェース宣言のアクセス可能性は、そのアクセス修飾子によって指定され、アクセス修飾子がない場合は6.6によって指定されます。

クラス内のメンバー型宣言について同じキーワードが修飾子として複数回出現する場合、またはアクセス修飾子publicprotectedおよびprivate (6.6)のうちの複数がメンバー型宣言に含まれる場合、コンパイル時にエラーが発生します。

このことについては、すでに8.1.1および9.1.1に記載されています。

メンバー型のスコープおよびシャドウ化については、6.3および6.4に規定されています。

このことについては、すでに8.1および9.1に記載されています。

特定の名前を持つメンバー・クラスまたはインタフェースをクラスで宣言した場合、そのクラスまたはインタフェースの宣言は、クラスのスーパークラスおよびスーパーインタフェース内の同じ名前を持つメンバー・クラスおよびインタフェースのアクセス可能なすべての宣言を隠すとされています。

この点に関しては、メンバー・クラスおよびインタフェースを隠すことは、フィールド(8.3)を隠すことと同様です。

クラスは、その直接スーパークラスおよび直接スーパーインタフェースから、スーパークラスおよびスーパーインタフェースの非privateメンバー・クラスおよびインタフェースのうち、クラス内のコードからアクセス可能で、かつクラス内の宣言によって隠されていないものをすべて継承します。

クラスは、そのスーパークラスとスーパーインタフェースから、またはそのスーパーインタフェースのみから、同じ名前を持つ複数のメンバー・クラスまたはインタフェースを継承することがあります。そのような状況自体によってコンパイル時にエラーが発生することはありません。ただし、クラスの本体内でそのようなメンバー・クラスまたはインタフェースをその単純名で参照しようとする場合、参照があいまいであるため、コンパイル時にエラーが発生します。

複数のパスによって、同じメンバー・クラスまたはインタフェース宣言が1つのインタフェースから継承されることがあります。そのような状況では、そのメンバー・クラスまたはインタフェースは1回のみ継承されるとみなされ、あいまいになることなく、その単純名で参照できます。

8.5.1 staticメンバー・クラスおよびインタフェース宣言

staticキーワードは、非内部クラスまたはインタフェースTの本体内のメンバー・クラスCの宣言を変更する場合があります。この結果、Cが内部クラスではないことを宣言します。Tstaticメソッドの本体内にTの現在のインスタンスがないように、CにもTの現在のインスタンスはなく、字句的に囲むインスタンスもありません。

staticクラスに包含クラスの非staticメンバーの使用が含まれる場合、コンパイル時にエラーが発生します。

メンバー・インタフェースは暗黙的にstaticです(9.1.1)。メンバー・インタフェースの宣言でstatic修飾子の重複指定が許可されています。

8.8 コンストラクタ宣言

8.8.7 コンストラクタ本体

8.8.7.1 明示的なコンストラクタ呼出し
ExplicitConstructorInvocation:
[TypeArguments] this ( [ArgumentList] ) ;
[TypeArguments] super ( [ArgumentList] ) ;
ExpressionName . [TypeArguments] super ( [ArgumentList] ) ;
Primary . [TypeArguments] super ( [ArgumentList] ) ;

便宜上、ここでは4.5.1および15.12の次のプロダクションを示します。

TypeArguments:
< TypeArgumentList >
ArgumentList:
Expression {, Expression}

明示的なコンストラクタ呼出し文は、次の2種類に分類されます。

コンストラクタ本体内の明示的なコンストラクタ呼出し文では、このクラスやスーパークラスで宣言されたインスタンス変数、インスタンス・メソッドまたは内部クラスを参照したり、式でthisまたはsuperを使用することはできません。そのようにすると、コンパイル時にエラーが発生します。

このように現在のインスタンスの使用が禁止されていることが、明示的なコンストラクタ呼出し文が静的コンテキストに出現する(8.1.3)とみなされる理由となっています。

TypeArgumentsthisまたはsuperの左側にある場合、いずれかの型引数がワイルドカードであれば(4.5.1)、コンパイル時にエラーが発生します。

Cはインスタンス化されるクラスであり、SCの直接スーパークラスであるとします。

スーパークラス・コンストラクタ呼出し文が修飾されていない場合、次のようになります。

スーパークラス・コンストラクタ呼出し文が修飾されている場合、次のようになります。

明示的なコンストラクタ呼出し文がスローできる例外型については、11.2.2に規定されています。

代替コンストラクタ呼出し文の評価では、まず、通常のメソッド呼出しと同様に、コンストラクタの引数が左から右に評価され、続いてコンストラクタが呼び出されます。

スーパークラス・コンストラクタ呼出し文の評価は、次のように進められます。

  1. iは、作成されるインスタンスであるとします。S (ある場合)に関してiを直接包含するインスタンスを特定する必要があります。

    • Sが内部クラスでない場合、またはSの宣言が静的コンテキストに出現する場合、Sに関してiを直接包含するインスタンスは存在しません。

    • スーパークラス・コンストラクタ呼出しが修飾されていない場合、Sは必然的にローカル・クラスまたは内部メンバー・クラスです。

      Sがローカル・クラスである場合、Oは、Sを直接包含するクラスまたはインタフェース宣言であるとします。

      Sが内部メンバー・クラスである場合、Oは、SがメンバーであるCの最も内側の包含クラスであるとします。

      nは、OCn番目の字句的な包含クラスまたはインタフェース宣言であるような整数であるとします(n 1)。

      Sに関してiを直接包含するインスタンスは、thisn番目の字句的な包含インスタンスです。

      継承によってSCのメンバーである場合もありますが、thisのゼロ番目の字句的な包含インスタンス(つまり、this自体)が、Sに関してiを直接包含するインスタンスとして使用されることはありません。

    • スーパークラス・コンストラクタ呼出しが修飾されている場合、.superの直前のPrimary式またはExpressionNamepが評価されます。

      pnullと評価されると、NullPointerExceptionが発生し、スーパークラス・コンストラクタ呼出しは突然完了します。

      それ以外の場合は、この評価の結果が、Sに関してiを直接包含するインスタンスです。

  2. S (ある場合)に関してiを直接包含するインスタンスが特定されたら、スーパークラス・コンストラクタ呼出し文の評価では、通常のメソッド呼出しと同様に、コンストラクタの引数が左から右に評価され、続いてコンストラクタが呼び出されます。

  3. 最後に、スーパークラス・コンストラクタ呼出し文が正常に完了すると、Cのすべてのインスタンス変数イニシャライザとCのすべてのインスタンス・イニシャライザが実行されます。インスタンス・イニシャライザまたはインスタンス変数イニシャライザIのテキストが別のインスタンス・イニシャライザまたはインスタンス変数イニシャライザJの前にある場合、IJより先に実行されます。

    スーパークラス・コンストラクタ呼出しが実際に明示的なコンストラクタ呼出し文として出現するか、暗黙的に提供されるかに関係なく、インスタンス変数イニシャライザおよびインスタンス・イニシャライザの実行は実施されます。(代替コンストラクタ呼出しでは、こうした追加の暗黙的な実行は実施されません。)

例8.8.7.1-1.明示的なコンストラクタ呼出し文に関する制限

8.8.7の例にあるColoredPointという最初のコンストラクタが次のように変更されたとします。

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}
class ColoredPoint extends Point {
    static final int WHITE = 0, BLACK = 1;
    int color;
    ColoredPoint(int x, int y) {
        this(x, y, color);  // Changed to color from WHITE
    }
    ColoredPoint(int x, int y, int color) {
        super(x, y);
        this.color = color;
    }
}

この場合、インスタンス変数colorを明示的なコンストラクタ呼出し文で使用することはできないため、コンパイル時にエラーが発生します。

例8.8.7.1-2.修飾されたスーパークラス・コンストラクタ呼出し

次のコードで、ChildOfInnerには字句的な包含クラスまたはインタフェース宣言がないため、ChildOfInnerのインスタンスには包含インスタンスがありません。ただし、ChildOfInnerのスーパークラス(Inner)には字句的な包含クラス宣言(Outer)があるため、InnerのインスタンスにはOuterの包含インスタンスが必要です。Outerの包含インスタンスは、Innerのインスタンスの作成時に設定されます。したがって、暗黙的にInnerのインスタンスであるChildOfInnerのインスタンスを作成する際に、ChildOfInnerのコンストラクタ内で修飾されたスーパークラス呼出し文を使用してOuterの包含インスタンスを提供する必要があります。Outerのインスタンスは、Innerに関してChildOfInnerを直接包含するインスタンスと呼ばれます。

class Outer {
    class Inner {}
}
class ChildOfInner extends Outer.Inner {
    ChildOfInner() { (new Outer()).super(); }
}

意外に感じられますが、Outerの同じインスタンスが、ChildOfInnerの複数のインスタンスについてInnerに関してChildOfInnerを直接包含するインスタンスとして機能することもあります。ChildOfInnerのこれらのインスタンスは、Outerの同じインスタンスに暗黙的にリンクされます。次のプログラムでは、Outerのインスタンスを、修飾されたスーパークラス・コンストラクタ呼出し文でそのインスタンスを使用するChildOfInnerのコンストラクタに渡すことによって、このことを実現します。明示的なコンストラクタ呼出し文のルールでは、その文を含む、コンストラクタの仮パラメータを使用することは禁止されていません。

class Outer {
    int secret = 5;
    class Inner {
        int  getSecret()      { return secret; }
        void setSecret(int s) { secret = s; }
    }
}
class ChildOfInner extends Outer.Inner {
    ChildOfInner(Outer x) { x.super(); }
}

public class Test {
    public static void main(String[] args) {
        Outer x = new Outer();
        ChildOfInner a = new ChildOfInner(x);
        ChildOfInner b = new ChildOfInner(x);
        System.out.println(b.getSecret());
        a.setSecret(6);
        System.out.println(b.getSecret());
    }
}

このプログラムでは、次の出力が生成されます。

5
6

その結果、ChildOfInnerの異なるインスタンスへの参照が従来の意味で別名でなくても、そのような参照を通じて、Outerの共通インスタンス内のインスタンス変数の操作を見ることができます。

8.9 enumクラス

enum宣言では、名前付きクラス・インスタンスの小規模なセットを定義する特別な種類のクラスである、新しいenumクラスを指定します。

EnumDeclaration:
{ClassModifier} enum TypeIdentifier [Superinterfaces] EnumBody

enum宣言では、最上位enumクラス(7.6)またはメンバーenumクラス(8.59.5)を指定できます。

enum宣言に修飾子abstractまたはfinalが含まれている場合、コンパイル時にエラーが発生します。

enum宣言は、クラス本体を持つenum定数(8.9.1)を1つ以上含んでいる場合を除き、暗黙的にfinalです。

ネストしたメンバーenum宣言は、暗黙的にstaticです。ネストしたメンバーenumクラスの宣言ではstatic修飾子を重複して指定することが許可されています。

これは、内部クラス(8.1.3)は、定数変数を除いてstaticメンバーを持つことができないため、内部クラスの本体でのメンバーとしてenumクラスを宣言できないことを意味します。

enum宣言について同じキーワードが修飾子として複数回出現する場合、またはアクセス修飾子publicprotectedおよびprivate (6.6)のうちの複数がenum宣言に含まれる場合、コンパイル時にエラーが発生します。

enumクラスEの直接スーパークラスは、Enum<E>です(8.1.4)。

enumクラスには、そのenum定数によって定義されたもの以外のインスタンスはありません。enumクラスを明示的にインスタンス化しようとすると(15.9.1)、コンパイル時にエラーが発生します。

enumクラスのインスタンスが、そのenum定数によって定義されたもの以外に存在することがないようにするために、コンパイル時エラーに加えて、さらに次の3つのメカニズムが用意されています。

8.9.1 enum定数

enum宣言の本体には、enum定数を含めることができます。enum定数では、enumクラスのインスタンスを定義します。

EnumBody:
{ [EnumConstantList] [,] [EnumBodyDeclarations] }
EnumConstantList:
EnumConstant {, EnumConstant}
EnumConstant:
{EnumConstantModifier} Identifier [( [ArgumentList] )] [ClassBody]
EnumConstantModifier:
Annotation

便宜上、ここでは15.12の次のプロダクションを示します。

ArgumentList:
Expression {, Expression}

enum定数宣言の注釈修飾子に関するルールについては、9.7.4および9.7.5に規定されています。

EnumConstantIdentifierは、enum定数を参照するために名前で使用できます。

enum定数のスコープおよびシャドウ化については、6.3および6.4に規定されています。

enum定数の後に、引数(このセクションで後述するように、クラスの初期化中に定数が作成されるときにenumのコンストラクタに渡される)を指定できます。呼び出されるコンストラクタは、オーバーロード解決(15.12.2)の通常のルールを使用して選択されます。引数を省略すると、引数リストは空であるとみなされます。

enum定数のオプションのクラス本体では、直接包含するenumクラスを拡張する無名クラス宣言(15.9.5)を暗黙的に定義します。クラス本体は、無名クラスの通常のルールに準拠します。たとえば、コンストラクタを含めることはできません。これらのクラス本体で宣言されたインスタンス・メソッドを包含enumクラスの外部で呼び出すことができるのは、包含enumクラス内のアクセス可能なメソッドをそれらがオーバーライドする場合のみです(8.4.8)。

enum定数のクラス本体でabstractメソッドを宣言した場合、コンパイル時にエラーが発生します。

それぞれのenum定数のインスタンスは1つのみであるため、2つのオブジェクト参照を比較するときに、それらの少なくとも一方がenum定数を参照することがわかっていれば、equalsメソッドのかわりに==演算子を使用することが許可されています。

Enum内のequalsメソッドは、単にその引数でsuper.equalsを呼び出してその結果を返すことにより、アイデンティティ比較を実行するfinalメソッドです。

8.9.2 enum本体宣言

enum定数に加えて、enum宣言の本体には、コンストラクタとメンバーの宣言、インスタンス・イニシャライザおよび静的イニシャライザを含めることができます。

EnumBodyDeclarations:
; {ClassBodyDeclaration}

便宜上、ここでは8.1.6の次のプロダクションを示します。

ClassBodyDeclaration:
ClassMemberDeclaration
InstanceInitializer
StaticInitializer
ConstructorDeclaration
ClassMemberDeclaration:
FieldDeclaration
MethodDeclaration
ClassDeclaration
InterfaceDeclaration
ClassOrInterfaceDeclaration
;

enum宣言の本体内のコンストラクタ宣言またはメンバー宣言は、他に明示的に示されている場合を除き、それらが標準クラス宣言の本体内にある場合とまったく同様にenumクラスに適用されます。

enum宣言内のコンストラクタ宣言がpublicまたはprotected (6.6)である場合、コンパイル時にエラーが発生します。

enum宣言内のコンストラクタ宣言にスーパークラス・コンストラクタ呼出し文(8.8.7.1)が含まれている場合、コンパイル時にエラーが発生します。

enum宣言のコンストラクタ、インスタンス・イニシャライザまたはインスタンス変数イニシャライザからenumクラスstaticフィールドを参照すると、コンパイル時にエラーが発生します。ただし、フィールドが定数変数(4.12.4)である場合は除きます。

enum宣言で、アクセス修飾子がないコンストラクタ宣言はprivateです。

コンストラクタ宣言がないenum宣言では、デフォルト・コンストラクタが暗黙的に宣言されます。デフォルト・コンストラクタはprivateであり、仮パラメータもthrows句も持ちません。

実際には、コンパイラは、enum型のデフォルト・コンストラクタでStringおよびintパラメータを宣言することによって、Enumクラスをミラーリングする可能性があります。ただし、個々のコンパイラがデフォルト・コンストラクタの形式について合意する必要はないため、これらのパラメータは「暗黙的に宣言される」ものとして規定されていません。enum宣言のコンパイラのみが、enum定数をインスタンス化する方法を認識しています。他のコンパイラは、enumクラスの暗黙的に宣言されたpublic staticフィールド(8.9.3)がどのように初期化されたかを考慮することなく、単にそれらのフィールドを利用できます。

enum宣言Eabstractメソッドmがメンバーとして含まれている場合、コンパイル時にエラーが発生します。ただし、Eにenum定数が少なくとも1つあり、かつ、mの具体的な実装を提供するクラス本体がEのenum定数すべてにある場合は除きます。

enum宣言でファイナライザ(12.6)を宣言すると、コンパイル時にエラーが発生します。enumクラスのインスタンスをファイナライズすることはできません。

例8.9.2-1.enum本体宣言

enum Coin {
    PENNY(1), NICKEL(5), DIME(10), QUARTER(25);
    Coin(int value) { this.value = value; }

    private final int value;
    public int value() { return value; }
}

それぞれのenum定数は、コンストラクタを介して渡される、フィールドvalueの異なる値を準備します。このフィールドは、アメリカの硬貨の値をセントで表します。enum型のクラスのコンストラクタで宣言できるパラメータに制限はありません。

例8.9.2-2.enum定数の自己参照に関する制限

staticフィールド・アクセスに関するルールがないと、enumクラスに固有の初期化の循環性が原因で、一見妥当なコードであっても実行時に失敗します。(循環性は、自己型付けされるstaticフィールドがあるクラスに存在します。)失敗するコードの例を次に示します。

import java.util.Map;
import java.util.HashMap;

enum Color {
    RED, GREEN, BLUE;
    Color() { colorMap.put(toString(), this); }

    static final Map<String,Color> colorMap =
        new HashMap<String,Color>();
}

enum定数のコンストラクタが実行されるときにstatic変数colorMapが初期化されていないため、このenumの静的初期化はNullPointerExceptionをスローします。前述の制限によって、このようなコードをコンパイルすることはできません。ただし、正しく機能するように、このコードを次のように簡単にリファクタできます。

import java.util.Map;
import java.util.HashMap;

enum Color {
    RED, GREEN, BLUE;

    static final Map<String,Color> colorMap =
        new HashMap<String,Color>();
    static {
        for (Color c : Color.values())
            colorMap.put(c.toString(), c);
    }
}

静的初期化が上から下に発生するため、リファクタされたバージョンは明らかに適切です。

8.9.3 enumメンバー

enumクラスEのメンバーは、次のすべてです。

つまり、enumクラスEの宣言には、Eのenum定数に対応する暗黙的に宣言されたフィールドと競合するフィールドを含めることも、暗黙的に宣言されたメソッドと競合したり、クラスEnum<E>finalメソッドをオーバーライドするメソッドを含めることもできないということになります。

例8.9.3-1.拡張されたforループを使用したenum定数の反復

public class Test {
    enum Season { WINTER, SPRING, SUMMER, FALL }

    public static void main(String[] args) {
        for (Season s : Season.values())
            System.out.println(s);
    }
}

このプログラムでは、次の出力が生成されます。

WINTER
SPRING
SUMMER
FALL

例8.9.3-2.enum定数の切替え

switch文(14.11)は、enumクラスの外部からクラスへのメソッドの追加をシミュレートする場合に便利です。この例では、8.9.2Coinクラスcolorメソッドを追加し、硬貨、それらの値およびそれらの色の表を出力します。

class Test {
    enum CoinColor { COPPER, NICKEL, SILVER }

    static CoinColor color(Coin c) {
        switch (c) {
            case PENNY:
                return CoinColor.COPPER;
            case NICKEL:
                return CoinColor.NICKEL;
            case DIME: case QUARTER:
                return CoinColor.SILVER;
            default:
                throw new AssertionError("Unknown coin: " + c);
        }
    }

    public static void main(String[] args) {
        for (Coin c : Coin.values())
            System.out.println(c + "\t\t" +
                               c.value() + "\t" + color(c));
    }
}

このプログラムでは、次の出力が生成されます。

PENNY           1       COPPER
NICKEL          5       NICKEL
DIME            10      SILVER
QUARTER         25      SILVER

例8.9.3-3.クラス本体を含むenum定数

enum Operation {
    PLUS {
        double eval(double x, double y) { return x + y; }
    },
    MINUS {
        double eval(double x, double y) { return x - y; }
    },
    TIMES {
        double eval(double x, double y) { return x * y; }
    },
    DIVIDED_BY {
        double eval(double x, double y) { return x / y; }
    };

    // Each constant supports an arithmetic operation
    abstract double eval(double x, double y);

    public static void main(String args[]) {
        double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        for (Operation op : Operation.values())
            System.out.println(x + " " + op + " " + y +
                               " = " + op.eval(x, y));
    }
}

クラス本体は、enum定数に動作を追加します。このプログラムでは次の出力が生成されます。

java Operation 2.0 4.0
2.0 PLUS 4.0 = 6.0
2.0 MINUS 4.0 = -2.0
2.0 TIMES 4.0 = 8.0
2.0 DIVIDED_BY 4.0 = 0.5

このパターンでは、新しい定数の動作を追加するのを忘れる可能性がなくなるので(enum宣言が原因でコンパイル時にエラーが発生するため)、基底クラス(Operation)でswitch文を使用するよりも、このパターンの方がはるかに安全です。

例8.9.3-4.複数のenumクラス

次のプログラムでは、2つの単純なenumに基づいてトランプのクラスが作成されています。

import java.util.List;
import java.util.ArrayList;
class Card implements Comparable<Card>,
                      java.io.Serializable {
    public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX, SEVEN,
                       EIGHT, NINE, TEN,JACK, QUEEN, KING, ACE }

    public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }

    private final Rank rank;
    private final Suit suit;
    public Rank rank() { return rank; }
    public Suit suit() { return suit; }

    private Card(Rank rank, Suit suit) {
        if (rank == null || suit == null)
            throw new NullPointerException(rank + ", " + suit);
        this.rank = rank;
        this.suit = suit;
    }

    public String toString() { return rank + " of " + suit; }

    // Primary sort on suit, secondary sort on rank
    public int compareTo(Card c) {
        int suitCompare = suit.compareTo(c.suit);
        return (suitCompare != 0 ?
                    suitCompare :
                    rank.compareTo(c.rank));
    }

    private static final List<Card> prototypeDeck =
        new ArrayList<Card>(52);

    static {
        for (Suit suit : Suit.values())
            for (Rank rank : Rank.values())
                prototypeDeck.add(new Card(rank, suit));
    }

    // Returns a new deck
    public static List<Card> newDeck() {
        return new ArrayList<Card>(prototypeDeck);
    }
}

次のプログラムでは、Cardクラスが実行されます。これは、カードを配るプレーヤの数とそれぞれのプレーヤのカードの数を表す2つの整数パラメータをコマンドラインで取得します。

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
class Deal {
    public static void main(String args[]) {
        int numHands     = Integer.parseInt(args[0]);
        int cardsPerHand = Integer.parseInt(args[1]);
        List<Card> deck  = Card.newDeck();
        Collections.shuffle(deck);
        for (int i=0; i < numHands; i++)
            System.out.println(dealHand(deck, cardsPerHand));
    }

    /**
     * Returns a new ArrayList consisting of the last n
     * elements of deck, which are removed from deck.
     * The returned list is sorted using the elements'
     * natural ordering.
     */
    public static <E extends Comparable<E>>
    ArrayList<E> dealHand(List<E> deck, int n) {
        int deckSize = deck.size();
        List<E> handView = deck.subList(deckSize - n, deckSize);
        ArrayList<E> hand = new ArrayList<E>(handView);
        handView.clear();
        Collections.sort(hand);
        return hand;
    }
}

このプログラムでは次の出力が生成されます。

java Deal 4 3
[DEUCE of CLUBS, SEVEN of CLUBS, QUEEN of DIAMONDS]
[NINE of HEARTS, FIVE of SPADES, ACE of SPADES]
[THREE of HEARTS, SIX of HEARTS, TEN of SPADES]
[TEN of CLUBS, NINE of DIAMONDS, THREE of SPADES]

第9章 インタフェース

インタフェース宣言は、メンバーがクラス、インタフェース、定数およびメソッドである1つ以上のクラスによって実装できる新しいabstract参照型を導入します。プログラムでは、インタフェースを使用して、本来なら無関係なクラスに共通のスーパー・タイプを提供できます。

この型はインタフェースはインスタンス変数を持たず、通常は、1つ以上のabstractメソッドを宣言します宣言します。本来なら無関係なクラスにインタフェースを実装するには、そのabstractメソッドの実装を提供します。インタフェースを直接インスタンス化することはできません。

ネストしたインタフェースは、宣言が別のクラスまたはインタフェースの本体内に出現するインタフェースです。

最上位インタフェースは、ネストしたインタフェースではないインタフェースです。

最上位インタフェース(7.6)は、コンパイル・ユニットの最上位で宣言されるインタフェースです。

ネストしたインタフェースは、宣言が別のクラスまたはインタフェースのメンバー・インタフェース(8.59.5)として出現するインタフェースです。

標準インタフェースと注釈型という2種類のインタフェースを区別します。

注釈インタフェース(9.6)は、注釈(9.7)の反射型の表現によって実装することを目的とした、特別な構文で宣言されるインタフェースです。

この章では、すべてのインタフェース、つまり、標準インタフェース(最上位インタフェース(7.6)とネストしたインタフェース(8.59.5)の両方)および注釈型(9.6)の共通セマンティクスについて説明します。特定の種類のインタフェースに固有の詳細は、これらのコンストラクトに特化したセクションで説明します。

プログラムでは、インタフェースを使用して、関連するクラスが共通のabstractスーパークラスを共有したり、メソッドをObjectに追加しなくても済むようにすることができます。

インタフェースは、1つ以上の他のインタフェースの直接拡張として宣言できます。これは、それがオーバーライドしたり、隠す可能性があるメンバーを除き、それが拡張するインタフェースのすべてのメンバー・クラスおよびインタフェース、インスタンス・メソッドおよび定数staticフィールドを継承することを意味します。

クラスは、1つ以上のインタフェースを直接実装するように宣言できます。これは、インタフェースによって指定されたすべてのabstractメソッドをそのクラスのインスタンスが実装することを意味します。クラスは必然的に、その直接スーパークラスおよび直接スーパーインタフェースが実装するすべてのインタフェースを実装します。こうした(複数の)インタフェース継承によって、オブジェクトは、スーパークラスを共有することなく、(複数の)共通動作をサポートできます。

宣言された型がインタフェース型である変数は、指定されたインタフェースを実装するクラスのインスタンスへの参照をその値として持つことができます。インタフェースのすべてのabstractメソッドがクラスに実装されていても十分ではありません。クラス、またはそのスーパークラスのいずれかが、実際に、インタフェースを実装するように宣言されている必要があります。そうでない場合、クラスはインタフェースを実装するとみなされません。

9.1 インタフェース宣言

インタフェース宣言では、新しい名前付き参照型を指定します。標準インタフェース宣言注釈型宣言(9.6)という2種類のインタフェース宣言があります。

InterfaceDeclaration:
NormalInterfaceDeclaration
AnnotationTypeDeclaration
NormalInterfaceDeclaration: InterfaceDeclaration:
{InterfaceModifier} interface TypeIdentifier [TypeParameters]
[ExtendsInterfaces] InterfaceBody

インタフェース宣言のTypeIdentifierでは、インタフェースの名前を指定します。

インタフェースの単純名がその包含クラスまたはインタフェースのいずれかと同じ場合、コンパイル時にエラーが発生します。

インタフェース宣言のスコープおよびシャドウ化については、6.3および6.4に規定されています。

9.1.4 インタフェース本体およびメンバー宣言

インタフェースの本体では、インタフェースのメンバー、つまり、フィールド(9.3)、メソッド(9.4)、およびクラス(9.5)、とインタフェース(9.5)を宣言できます。

InterfaceBody:
{ {InterfaceMemberDeclaration} }
InterfaceMemberDeclaration:
ConstantDeclaration
InterfaceMethodDeclaration
ClassDeclaration
InterfaceDeclaration
ClassOrInterfaceDeclaration
;

インタフェースIで宣言または継承されるメンバーmの宣言のスコープについては、6.3に規定されています。

9.5 メンバー・クラスおよびインタフェース宣言

インタフェースには、メンバー・クラスおよびインタフェース宣言(8.5)を含めることができます。

インタフェースの本体内のそれぞれのメンバー・クラスまたはインタフェース宣言は、暗黙的にpublicかつstaticです。これらの修飾子のいずれかまたは両方を重複して指定することが許可されています。

インタフェースのメンバー・クラスまたはインタフェース宣言に修飾子protectedまたはprivateが含まれている場合、コンパイル時にエラーが発生します。

インタフェースのメンバー型宣言について同じキーワードが修飾子として複数回出現する場合、コンパイル時にエラーが発生します。

このことについては、すでに8.1.1および9.1.1に記載されています。

特定の名前を持つメンバー・クラスまたはインタフェースをインタフェースで宣言した場合、そのクラスまたはインタフェースの宣言は、インタフェースのスーパーインタフェース内の同じ名前を持つメンバー・クラスおよびインタフェースのアクセス可能なすべての宣言を隠すとされています。

インタフェースは、その直接スーパーインタフェースから、スーパーインタフェースのprivateメンバー・クラスおよびインタフェースのうち、インタフェース内のコードからアクセス可能で、かつインタフェース内の宣言によって隠されていないものをすべて継承します。

スーパーインタフェースのメンバー・クラスおよびインタフェースはすべてpublicであるため、それらのアクセス可能性をここで考慮する必要はありません。

インタフェースは、同じ名前を持つ複数のメンバー・クラスまたはインタフェースを継承することがあります。そのような状況自体によってコンパイル時にエラーが発生することはありません。ただし、インタフェースの本体内でそのようなメンバー・クラスまたはインタフェースをその単純名で参照しようとする場合、参照があいまいであるため、コンパイル時にエラーが発生します。

複数のパスによって、同じメンバー・クラスまたはインタフェース宣言が1つのインタフェースから継承されることがあります。そのような状況では、そのメンバー・クラスまたはインタフェースは1回のみ継承されるとみなされ、あいまいになることなく、その単純名で参照できます。

9.6 注釈インタフェース

注釈宣言では、特別な種類のインタフェースである、新しい注釈インタフェースを指定します。注釈宣言を標準インタフェース宣言と区別するために、キーワードinterfaceの前にアットマークアットマーク(@)が付けられます。

AnnotationTypeDeclaration: AnnotationDeclaration:
{InterfaceModifier} @ interface TypeIdentifier AnnotationTypeBody AnnotationInterfaceBody

アットマークアットマーク(@)とキーワードinterfaceは、別個のトークンです。それらを空白で区切ることもできますが、スタイルとしてはお薦めしません。

注釈宣言の注釈修飾子に関するルールについては、9.7.4および9.7.5に規定されています。

注釈宣言のTypeIdentifierでは、注釈インタフェースの名前を指定します。

注釈インタフェースの単純名がその包含クラスまたはインタフェースのいずれかと同じ場合、コンパイル時にエラーが発生します。

それぞれの注釈インタフェースの直接スーパーインタフェースはjava.lang.annotation.Annotationです。

AnnotationTypeDeclaration AnnotationInterfaceDeclaration構文により、注釈インタフェース宣言を汎用にすることはできず、extends句も許可されていません。

注釈インタフェースではスーパークラスまたはスーパーインタフェースを明示的に宣言できないため、注釈インタフェースのサブクラスまたはサブインタフェース自体が注釈インタフェースになることはありません。同様に、java.lang.annotation.Annotation自体も注釈インタフェースにはなりません。

注釈インタフェースは、様々なメンバーをjava.lang.annotation.Annotationから継承します。これには、Objectのインスタンス・メソッドに対応する暗黙的に宣言されたメソッドも含まれますが、これらのメソッドでは、注釈インタフェースの要素(9.6.1)は定義されません。

これらのメソッドでは注釈インタフェースの要素が定義されないため、その型の注釈(9.7)でそれらを使用することは不正です。このルールがなければ、要素が注釈で表現できる型であることや、それらのアクセサ・メソッドを使用できることを確認できません。

ここで明示的に変更されていないかぎり、標準インタフェース宣言に適用されるすべてのルールが注釈宣言にも適用されます。

たとえば、注釈インタフェースは、標準クラスおよびインタフェース型クラスおよびインタフェースと同じネームスペースを共有します。また、注釈宣言は、インタフェース宣言が正当である場合は常に正当であり、インタフェース宣言と同じスコープおよびアクセス可能性を持ちます。

9.6.1 注釈要素

注釈宣言の本体にはメソッド宣言を含めることができ、それぞれの宣言で、注釈インタフェース要素を定義します。注釈インタフェースは、それが明示的に宣言するメソッドで定義されたもの以外の要素を持ちません。

AnnotationTypeBody: AnnotationInterfaceBody:
{ {AnnotationTypeMemberDeclaration AnnotationMemberDeclaration} }
AnnotationTypeMemberDeclaration: AnnotationMemberDeclaration:
AnnotationTypeElementDeclaration AnnotationElementDeclaration
ConstantDeclaration
ClassDeclaration
InterfaceDeclaration
ClassOrInterfaceDeclaration
;
AnnotationTypeElementDeclaration: AnnotationElementDeclaration:
{AnnotationTypeElementModifier AnnotationElementModifier} UnannType Identifier ( ) [Dims]
[DefaultValue] ;
AnnotationTypeElementModifier: AnnotationElementModifier:
(次のうちの1つ)
Annotation public
abstract

AnnotationTypeElementDeclaration AnnotationElementDeclarationプロダクションにより、注釈宣言内のメソッド宣言は、仮パラメータ、型パラメータまたはthrows区を持つことができません。便宜上、ここでは4.3の次のプロダクションを示します。

Dims:
{Annotation} [ ] {{Annotation} [ ]}

AnnotationTypeElementModifier AnnotationElementModifierプロダクションにより、注釈宣言内のメソッド宣言をdefaultまたはstaticにすることはできません。したがって、注釈インタフェースでは、標準インタフェースと同じ種類のメソッドを宣言できません。注釈インタフェースがその暗黙的なスーパーインタフェースjava.lang.annotation.Annotationからデフォルト・メソッドを継承することも考えられますが、Java SE 14の時点ではそのようなデフォルト・メソッドは存在しません。

規則により、注釈要素宣言に存在する必要がある唯一のAnnotationTypeElementModifier AnnotationElementModifierは注釈です。

注釈インタフェースで宣言されるメソッドの戻り型は、次のいずれかである必要があります。そうでない場合はコンパイル時にエラーが発生します。

このルールによって、次のようなネストした配列型を持つ要素を防ぐことができます。

@interface Verboten {
    String[][] value();
}

配列を返すメソッドの宣言では、空の仮パラメータ・リストの後ろに配列型を示すカッコのペアを配置することが許可されています。この構文は、以前のバージョンのJavaプログラミング言語との互換性のためにサポートされています。新しいコードではこの構文を使用しないことをお薦めします。

注釈インタフェースで宣言されたメソッドに、クラスObjectまたはインタフェースjava.lang.annotation.Annotationで宣言されたpublicまたはprotectedメソッドのものとオーバーライド等価であるシグネチャがある場合、コンパイル時にエラーが発生します。

注釈型宣言注釈インタフェースの宣言Tに型Tの要素が直接的または間接的に含まれている場合、コンパイル時にエラーが発生します。

たとえば、次は不正です。

@interface SelfRef { SelfRef value(); }

次も同様です。

@interface Ping { Pong value(); }
@interface Pong { Ping value(); }

要素がない注釈インタフェースは、マーカー注釈インタフェースと呼ばれます。

要素が1つの注釈インタフェースは、単一要素注釈インタフェースと呼ばれます。

規則により、単一要素注釈インタフェース内の唯一の要素の名前はvalueです。この規則の言語サポートは、単一要素注釈(9.7.3)によって提供されます。

例9.6.1-1.注釈宣言

次の注釈宣言では、要素が複数ある注釈インタフェースが定義されています。

/**
 * Describes the "request-for-enhancement" (RFE)
 * that led to the presence of the annotated API element.
 */
@interface RequestForEnhancement {
    int    id();        // Unique ID number associated with RFE
    String synopsis();  // Synopsis of RFE
    String engineer();  // Name of engineer who implemented RFE
    String date();      // Date RFE was implemented
}

例9.6.1-2.マーカー注釈宣言

次の注釈宣言では、マーカー注釈インタフェースが定義されています。

/**
 * An annotation with this type indicates that the 
 * specification of the annotated API element is 
 * preliminary and subject to change.
 */
@interface Preliminary {}

例9.6.1-3.単一要素注釈宣言

次の注釈宣言では、単一要素注釈インタフェースではvalueという要素が定義されるという規則を示しています。

/**
 * Associates a copyright notice with the annotated API element.
 */
@interface Copyright {
    String value();
}

次の注釈宣言では、唯一の要素が配列型を持つ単一要素注釈インタフェースが定義されています。

/**
 * Associates a list of endorsers with the annotated class.
 */
@interface Endorsers {
    String[] value();
}

次の注釈宣言は、制限付きのワイルドカードによって値が制約されているClass型の要素を示しています。

interface Formatter {}

// Designates a formatter to pretty-print the annotated class
@interface PrettyPrinter {
    Class<? extends Formatter> value();
}

次の注釈宣言には、型が同様に注釈インタフェース型である要素が含まれています。

/**
 * Indicates the author of the annotated program element.
 */
@interface Author {
    Name value();
}
/**
 * A person's name.  This annotation type is not designed
 * to be used directly to annotate program elements, but to
 * define elements of other annotation types.
 * A person's name.  This annotation interface is not designed
 * to be used directly to annotate program elements, but to
 * define elements of other annotation interfaces.
*/
@interface Name {
   String first();
   String last();
}

注釈宣言の文法では、メソッド要素宣言に加えて、他の要素メンバー宣言も許可されています。たとえば、注釈インタフェースとともに使用するネストしたenumを宣言することを選択できます。

@interface Quality {
    enum Level { BAD, INDIFFERENT, GOOD }
    Level value();
}

9.6.2 注釈要素のデフォルト

注釈要素宣言には、キーワードdefaultElementValueを含む要素の(空の)パラメータ・リストに従って指定されるデフォルト値を含めることができます(9.7.1)。

DefaultValue:
default ElementValue

要素の型が、指定されたデフォルト値に対応していない(9.7)場合、コンパイル時にエラーが発生します。

デフォルト値は注釈にコンパイルされず、注釈が読み取られるときに動的に適用されます。したがって、デフォルト値を変更すると、変更が加えられる前にコンパイルされたクラスの注釈にも影響します(これらの注釈に、デフォルトが指定された要素の明示的な値がないものと仮定します)。

例9.6.2-1.デフォルト値を含む注釈宣言

9.6.1RequestForEnhancement注釈インタフェースを改良したものを次に示します。

@interface RequestForEnhancementDefault {
    int    id();       // No default - must be specified in 
                       // each annotation
    String synopsis(); // No default - must be specified in 
                       // each annotation
    String engineer()  default "[unassigned]";
    String date()      default "[unimplemented]";
}

9.6.3 繰返し可能な注釈インタフェース

注釈インタフェースTは、value要素がTの包含注釈インタフェースを示す@Repeatable注釈(9.6.4.8)を使用してその宣言に(メタ)注釈が付けられている場合、繰返し可能です。

次のすべてに該当する場合、注釈インタフェースTCは、Tの包含注釈インタフェースです。

  1. TCによって、戻り型がT[]であるvalue()メソッド要素が宣言されます。

  2. TCによって宣言された、value()以外のメソッド要素にデフォルト値があります。

  3. 少なくともTと同じ期間、TCが保持されます(保持期間は、@Retention注釈(9.6.4.2)を使用して明示的または暗黙的に表されます)。具体的には、次のようになります。

    • TCの保持期間がjava.lang.annotation.RetentionPolicy.SOURCEである場合、Tの保持期間はjava.lang.annotation.RetentionPolicy.SOURCEです。

    • TCの保持期間がjava.lang.annotation.RetentionPolicy.CLASSである場合、Tの保持期間はjava.lang.annotation.RetentionPolicy.CLASSまたはjava.lang.annotation.RetentionPolicy.SOURCEです。

    • TCの保持期間がjava.lang.annotation.RetentionPolicy.RUNTIMEである場合、Tの保持期間はjava.lang.annotation.RetentionPolicy.SOURCEjava.lang.annotation.RetentionPolicy.CLASSまたはjava.lang.annotation.RetentionPolicy.RUNTIMEです。

  4. 少なくともTCと同じ種類のプログラム要素にTを適用できます(9.6.4.1)。具体的には、Tを適用できるプログラム要素の種類がセットm1によって示され、TCを適用できるプログラム要素の種類がセットm2によって示される場合、m2内のそれぞれの種類がm1に出現する必要があります。ただし、次の場合は除きます。

    • m2内の種類がjava.lang.annotation.ElementType.ANNOTATION_TYPEである場合は、java.lang.annotation.ElementType.ANNOTATION_TYPEjava.lang.annotation.ElementType.TYPEまたはjava.lang.annotation.ElementType.TYPE_USEのうちの少なくとも1つがm1に出現する必要があります。

    • m2内の種類がjava.lang.annotation.ElementType.TYPEである場合は、java.lang.annotation.ElementType.TYPEまたはjava.lang.annotation.ElementType.TYPE_USEのうちの少なくとも1つがm1に出現する必要があります。

    • m2内の種類がjava.lang.annotation.ElementType.TYPE_PARAMETERである場合は、java.lang.annotation.ElementType.TYPE_PARAMETERまたはjava.lang.annotation.ElementType.TYPE_USEのうちの少なくとも1つがm1に出現する必要があります。

    この条項によって、注釈インタフェースは、それを適用できるプログラム要素の種類の一部でのみ繰返し可能にすることができるというポリシーが実装されます。

  5. java.lang.annotation.Documentedに対応する(メタ)注釈がTの宣言に含まれている場合、java.lang.annotation.Documentedに対応する(メタ)注釈がTCの宣言に含まれている必要があります。

    T@DocumentedでないときにTC@Documentedであることは許容されます。

  6. java.lang.annotation.Inheritedに対応する(メタ)注釈がTの宣言に含まれている場合、java.lang.annotation.Inheritedに対応する(メタ)注釈がTCの宣言に含まれている必要があります。

    T@InheritedでないときにTC@Inheritedであることは許容されます。

注釈インタフェースTに、Tの包含注釈インタフェースでない型をvalue要素が示す@Repeatable注釈を使用して(メタ)注釈が付けられている場合、コンパイル時にエラーが発生します。

例9.6.3-1.形式が正しくない包含注釈インタフェース

次の宣言を考えてみましょう。

import java.lang.annotation.Repeatable;

@Repeatable(FooContainer.class)
@interface Foo {}

@interface FooContainer { Object[] value(); }

Foo@Repeatableを使用して、FooContainerをその包含注釈インタフェースとして指定しようとしていますが、実際は、FooContainerFooの包含注釈インタフェースではないため、Foo宣言をコンパイルすると、コンパイル時にエラーが生成されます。(FooContainer.value()の戻り型はFoo[]ではありません。)

@Repeatable注釈を繰り返すことはできないため、繰返し可能な注釈インタフェースで指定できる包含注釈インタフェースは1つのみです。

複数の包含注釈インタフェースの指定を許可すると、繰返し可能な注釈インタフェースの複数の注釈が論理的にコンテナ注釈(9.7.5)に置き換えられるときに、コンパイル時に望ましくない選択が行われます。

注釈インタフェースは、最大1つの注釈インタフェースの包含注釈インタフェースにすることができます。

このことは、注釈インタフェースTの宣言でTCという包含注釈インタフェースを指定する場合、TCvalue()メソッドの戻り型はTを含む、具体的にはT[]であるという要件によって暗黙的に規定されています。

注釈インタフェースでそれ自体をその包含注釈インタフェースとして指定することはできません。

このことは、包含注釈インタフェースvalue()メソッドに関する要件によって暗黙的に規定されています。具体的には、注釈インタフェースAでそれ自体を(@Repeatableによって)その包含注釈インタフェースとして指定する場合、Avalue()メソッドの戻り型はA[]である必要があります。しかし、注釈インタフェースはその要素(9.6.1)内でそれ自体を参照できないため、これによってコンパイル時にエラーが発生します。より一般的には、注釈インタフェースの循環宣言は不正であるため、2つの注釈インタフェースで相互をその包含注釈インタフェースとして指定することはできません。

注釈インタフェースTCは、注釈インタフェースTの包含注釈インタフェースであると同時に、独自の包含注釈インタフェースTCを持つこともできます。つまり、包含注釈インタフェース自体を繰返し可能な注釈インタフェースにすることができます。

例9.6.3-2.注釈を繰り返すことができる場所の制限

宣言がjava.lang.annotation.ElementType.TYPEというターゲットを示す注釈は、宣言がjava.lang.annotation.ElementType.ANNOTATION_TYPEというターゲットを示す注釈と少なくとも同じ数の場所に出現できます。たとえば、繰返し可能な注釈および包含注釈インタフェースの次のような宣言があるとします。

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;

@Target(ElementType.TYPE)
@Repeatable(FooContainer.class)
@interface Foo {}

@Target(ElementType.ANNOTATION_TYPE)
@interface FooContainer {
    Foo[] value();
}

@Fooはすべてのクラスまたはインタフェース宣言に出現できますが、@FooContainerは注釈インタフェース宣言にのみ出現できます。したがって、次の注釈インタフェース宣言は正当です。

@Foo @Foo
@interface Anno {}

一方、次のインタフェース宣言は不正です。

@Foo @Foo
interface Intf {}

さらに広げて、Fooが繰返し可能な注釈インタフェースで、FooContainerがその包含注釈インタフェースである場合を考えてみましょう。

例9.6.3-3.繰返し可能な包含注釈インタフェース

次の宣言は正当です。

import java.lang.annotation.Repeatable;
// Foo: Repeatable annotation type
// Foo: Repeatable annotation interface
@Repeatable(FooContainer.class)
@interface Foo { int value(); }
// FooContainer: Containing annotation type of Foo
//               Also a repeatable annotation type itself
// FooContainer: Containing annotation interface of Foo
//               Also a repeatable annotation interface itself
@Repeatable(FooContainerContainer.class)
@interface FooContainer { Foo[] value(); }
// FooContainerContainer: Containing annotation type of FooContainer
// FooContainerContainer: Containing annotation interface of FooContainer
@interface FooContainerContainer { FooContainer[] value(); }

つまり、型が包含注釈インタフェースである注釈自体を繰り返すことができます。

@FooContainer({@Foo(1)}) @FooContainer({@Foo(2)})
class Test {}

繰返し可能と包含の両方である注釈インタフェースには、繰返し可能な注釈型の注釈と包含注釈型の注釈の混在に関するルールが適用されます(9.7.5)。たとえば、複数の@Foo注釈を複数の@FooContainer注釈と一緒に記述することはできません。また、複数の@FooContainer注釈を複数の@FooContainerContainer注釈と一緒に記述することもできません。ただし、FooContainerContainer注釈インタフェース自体が繰返し可能である場合は、複数の@Foo注釈を複数の@FooContainerContainer注釈と一緒に記述できます。

9.6.4 事前定義済注釈インタフェース

Java SEプラットフォームのライブラリには、いくつかの注釈インタフェースが事前定義されています。これらの事前定義済注釈インタフェースの中には、特別なセマンティクスを持つものがあります。このセクションでは、これらのセマンティクスについて規定します。このセクションは、ここに含まれている事前定義済注釈の完全な仕様を示すものではありません。それは該当するAPI仕様の役割となります。ここでは、JavaコンパイラまたはJava仮想マシンの実装側で特別な動作を必要とするセマンティクスのみを規定します。

9.6.4.1 @Target

java.lang.annotation.Targetの注釈を注釈インタフェースTの宣言で使用して、T適用できるコンテキストを指定します。java.lang.annotation.Targetは、コンテキストを指定するための、型java.lang.annotation.ElementType[]の1つの要素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.3)

    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個の宣言コンテキストすべてと16個の型コンテキストすべてで適用可能です。

9.6.4.2 @Retention

注釈はソース・コードにのみ存在できます。あるいは、クラスまたはインタフェースのバイナリ形式に存在することもあります。バイナリ形式に存在する注釈は、Java SEプラットフォームのリフレクション・ライブラリを介して実行時に使用できることもあれば、使用できないこともあります。注釈インタフェースjava.lang.annotation.Retentionは、これらの可能性から選択するために使用されます。

注釈a注釈インタフェースTに対応し、java.lang.annotation.Retentionに対応する(メタ)注釈mTに含まれている場合、次のようになります。

java.lang.annotation.Retentionに対応する(メタ)注釈mTに含まれていない場合、Javaコンパイラは、値がjava.lang.annotation.RetentionPolicy.CLASSである要素を持つそのようなメタ注釈mが含まれている場合と同様にTを処理する必要があります。

9.6.4.3 @Inherited

注釈インタフェースjava.lang.annotation.Inheritedは、指定した注釈インタフェースに対応するクラスCの注釈がCのサブクラスによって継承されることを示す場合に使用します。

9.6.4.4 @Override

プログラマは、メソッド宣言をオーバーライドするときにそれをオーバーロードすることがあり、微妙な問題につながります。注釈インタフェースOverrideは、こうした問題の早期検出をサポートします。

従来の例は、equalsメソッドに関するものです。プログラマは、クラスFooで次のように記述します。

public boolean equals(Foo that) { ... }

実際に記述しようとした内容は次のとおりです。

public boolean equals(Object that) { ... }

これは完全に正当ですが、クラスFooObjectからequals実装を継承するため、微妙なバグの原因となる可能性があります。

クラスまたはインタフェースTのメソッド宣言に@Overrideの注釈が付けられているが、そのメソッドが、Tのスーパー・タイプで宣言されたメソッドをTからオーバーライドしない場合(8.4.8.19.4.1.1)、またはObjectpublicメソッドとオーバーライド等価でない場合(4.3.28.4.2)、コンパイル時にエラーが発生します。

この動作は、Java SE 5.0とは異なります。Java SE 5.0では、@Overrideによってコンパイル時にエラーが発生するのは、スーパーインタフェースからメソッドを実装し、スーパークラスにも存在しないメソッドに適用された場合のみでした。

publicメソッドのオーバーライドに関する条項の背景には、インタフェースでの@Overrideの使用があります。次の宣言を考えてみましょう。

class Foo     { @Override public int hashCode() {..} }
interface Bar { @Override int hashCode(); }

Foo.hashCodeはメソッドObject.hashCodeFooからオーバーライドするため、クラス宣言での@Overrideの使用は、最初の条項によって正当です。

インタフェース宣言については、インタフェースがObjectをスーパー・タイプとして持たず、Objectpublicメンバーに対応するpublic abstractメンバーをインタフェースが持つとします(9.2)。インタフェースでそれらを明示的に宣言する(つまり、Objectpublicメソッドとオーバーライド等価であるメンバーを宣言する)ことを選択した場合、インタフェースはそれらをオーバーライドするとみなされ、@Overrideの使用は許可されます。

一方、cloneメソッドで@Overrideを使用しようとするインタフェースを考えてみましょう。(この例では、finalizeを使用することもできます)

interface Quux { @Override Object clone(); }

Object.clonepublicでないため、Quuxで暗黙的に宣言されたcloneというメンバーはありません。したがって、Quux内のcloneの明示的な宣言は他のメソッドを実装するとみなされず、@Overrideの使用は誤りです。(Quux.clonepublicであるという事実は関係ありません。)

対照的に、cloneを宣言するクラス宣言は単純にObject.cloneをオーバーライドするため、@Overrideを使用できます。

class Beep { @Override protected Object clone() {..} }
9.6.4.5 @SuppressWarnings

Javaコンパイラは、有用な「lintのような」警告を徐々に発行できるようになってきています。こうした警告の使用を促進するためには、プログラマが警告が不適切であることがわかっているときに、プログラムの一部で警告を無効にするためのなんらかの方法が必要です。

注釈インタフェースSuppressWarningsは、本来ならJavaコンパイラによって発行される警告のプログラマによる制御をサポートします。これは、Stringの配列である1つの要素を定義します。

宣言に@SuppressWarnings(value = {S1, ..., Sk})の注釈が付けられている場合、注釈が付けられた宣言またはその一部の結果として、S1 ... Skのいずれかによって指定された警告が生成されると、Javaコンパイラはその警告を抑制する(つまり、報告しないようにする)必要があります。

Javaプログラミング言語では、@SuppressWarningsで指定できる4種類の警告が定義されています。

他の文字列は、標準以外の警告を指定します。Javaコンパイラは、認識しないこのような文字列を無視する必要があります。

コンパイラ・ベンダーは、@SuppressWarningsについてサポートする文字列を文書化することと、同じ文字列が複数のコンパイラにわたって認識されるようにするために協力し合うことを奨励されています。

9.6.4.6 @Deprecated

プログラマは、特定のプログラム要素(モジュール、クラス、インタフェース、フィールド、メソッドおよびコンストラクタ)について、それらが危険であったり、より効果的な代替手段が存在するために使用を推奨されないことがあります。注釈インタフェースDeprecatedを使用すると、そうしたプログラム要素の使用についてコンパイラで警告できます。

非推奨のプログラム要素は、宣言に@Deprecatedの注釈が付けられているモジュール、クラス、インタフェース、フィールド、メソッドまたはコンストラクタです。プログラム要素の非推奨の形態は、注釈のforRemoval要素の値によって異なります。

プログラム要素の宣言で(明示的に宣言されているか、暗黙的に宣言されているかに関係なく)通常の非推奨プログラム要素が使用されている(オーバーライドされている、呼び出されている、または名前で参照されている)場合、Javaコンパイラは非推奨警告を生成する必要があります。ただし、次の場合は除きます。

プログラム要素の宣言で(明示的に宣言されているか、暗黙的に宣言されているかに関係なく)廃止予定の非推奨プログラム要素が使用されている(オーバーライドされている、呼び出されている、または名前で参照されている)場合、Javaコンパイラは削除警告を生成する必要があります。ただし、次の場合は除きます。

廃止予定の非推奨には十分な緊急性があるため、廃止予定の非推奨要素が使用されていると、使用している側の要素自体が非推奨であっても、削除警告が生成されます。両方の要素が同時に削除されるという保証がないためです。警告を閉じて要素の使用を続行するには、プログラマは、@SuppressWarnings注釈を使用して、そのリスクを手動で確認する必要があります。

次の場合、非推奨警告または削除警告は生成されません。

パッケージをエクスポートする、または開くモジュール宣言は、通常、パッケージの宣言を制御するのと同じプログラマまたはチームによって制御されます。そのため、パッケージがモジュール宣言によってエクスポートされたり、開かれたときに、パッケージ宣言に@Deprecatedの注釈が付けられていると警告することにほとんど利点はありません。一方、パッケージをフレンド・モジュールに対してエクスポートする、または開くモジュール宣言は、通常、フレンド・モジュールを制御するのと同じプログラマまたはチームによって制御されるわけではありません。単にパッケージをエクスポートしたり、開いても、モジュール宣言がフレンド・モジュールに依存するようになるわけではないため、フレンド・モジュールが非推奨である場合に警告することにほとんど価値はありません。モジュール宣言のプログラマは、ほとんどの場合、このような警告を抑制しようと考えます。

非推奨警告または削除警告が発生する可能性がある唯一の暗黙的な宣言は、コンテナ注釈(9.7.5)です。つまり、Tが繰返し可能な注釈インタフェースで、TCがその包含注釈インタフェースであり、かつTCが非推奨である場合、@T注釈を繰り返すと、警告が発生します。この警告は、暗黙的な@TCコンテナ注釈によるものです。対応する繰返し可能な注釈インタフェースを非推奨にすることなく、包含注釈インタフェースを非推奨にすることはお薦めしません。

9.6.4.7 @SafeVarargs

非reifiable要素型(4.7)の可変引数パラメータは、ヒープ汚染(4.12.2)の原因となり、コンパイル時に未チェック警告(5.1.9)を生成する可能性があります。可変引数メソッドの本体が可変引数パラメータに関して正常に動作する場合、このような警告は有益ではありません。

注釈インタフェースSafeVarargsを使用して、メソッドまたはコンストラクタ宣言に注釈を付けると、プログラマは、Javaコンパイラが可変引数メソッドまたはコンストラクタの宣言や呼出しについて未チェック警告を報告しないようにすることができます。そうしない場合、可変引数パラメータが非reifiable要素型を持つことが原因で、コンパイラはそのような警告を報告します。

注釈@SafeVarargsは、可変引数メソッド自体の宣言に関係する未チェック警告(8.4.1)に加え、メソッド呼出し式でも未チェック警告を抑制するため、その影響は局所的ではありません。対照的に、注釈@SuppressWarnings("unchecked")は、メソッドの宣言に関係する未チェック警告のみを抑制するため、その影響は局所的です。

@SafeVarargsの正規のターゲットは、宣言が次のもので始まる、java.util.Collections.addAllのようなメソッドです。

public static <T> boolean
  addAll(Collection<? super T> c, T... elements)

この可変引数パラメータには、非reifiableである宣言された型T[]があります。ただし、このメソッドは基本的に、単に入力配列から読み取って、要素をコレクションに追加します。これはどちらも、配列に関して安全な操作です。したがって、java.util.Collections.addAllのメソッド呼出し式でのコンパイル時の未チェック警告は、ほぼ間違いなく誤りであり、有益ではありません。メソッド宣言に@SafeVarargsを適用すると、メソッド呼出し式でのこうした未チェック警告の生成を回避できます。

注釈@SafeVarargsを使用して固定引数メソッドまたはコンストラクタ宣言に注釈が付けられている場合、コンパイル時にエラーが発生します。

注釈@SafeVarargsを使用して、staticでもfinalでもprivateでもない可変引数メソッド宣言に注釈が付けられている場合、コンパイル時にエラーが発生します。

@SafeVarargsは、staticメソッド、finalまたはprivate (あるいはその両方)であるインスタンス・メソッド、およびコンストラクタにのみ適用可能であるため、メソッドのオーバーライドが発生する場合、この注釈は使用できません。注釈の継承はクラスの注釈についてのみ機能する(メソッド、インタフェースまたはコンストラクタの注釈については機能しない)ため、クラス内のインスタンス・メソッドを介して、あるいはインタフェースを介して@SafeVarargsスタイルの注釈を渡すことはできません。

9.6.4.8 @Repeatable

注釈インタフェースjava.lang.annotation.Repeatable繰返し可能な注釈インタフェースの宣言で使用して、その包含注釈インタフェース(9.6.3)を指定します。

TCを示す、Tの宣言の@Repeatableメタ注釈は、TCTの包含注釈インタフェースにするには十分ではありませんTCTの包含注釈インタフェースとみなされるための様々な整形式ルールがあります。

9.6.4.9 @FunctionalInterface

注釈インタフェースFunctionalInterfaceは、インタフェースが機能インタフェース(9.8)として想定されていることを示す場合に使用します。これにより、機能すると想定されているインタフェースに出現したり、継承される不適切なメソッド宣言の早期検出が容易になります。

インタフェース宣言に@FunctionalInterfaceの注釈が付けられているが、実際は機能インタフェースでない場合、コンパイル時にエラーが発生します。

インタフェースの中には付随的に機能するものもあるため、機能インタフェースのすべての宣言に@FunctionalInterfaceの注釈を付けることは必要ないか、望ましくありません。

第13章: バイナリ互換性

13.1 バイナリの形式

プログラムは、Java仮想マシン仕様、Java SE 14 Editionによって規定されているclassファイル形式に、またはJavaプログラミング言語で作成されたクラス・ローダーによってこの形式にマップできる表現にコンパイルする必要があります。

クラスまたはインタフェース宣言に対応するclassファイルには、特定の特性が必要です。これらの特性の多くは、バイナリの互換性を確保するソース・コードの変換をサポートするように明確に選択されています。必須特性は次のとおりです。

  1. クラスまたはインタフェースはバイナリ名によって名前を付ける必要があり、これは次の制約を満たす必要があります。

    • 最上位クラスまたはインタフェース(7.6)のバイナリ名は、その正規名(6.7)です。

    • メンバー・クラスまたはインタフェース(8.59.5)のバイナリ名は、それを直接包含するクラスまたはインタフェースのバイナリ名、その後に続く$、その後に続くメンバーの単純名で構成されます。

    • ローカル・クラス(14.3)のバイナリ名は、それを直接包含するクラスまたはインタフェースのバイナリ名、その後に続く$、その後に続く空でない一連の数字、その後に続くローカル・クラスの単純名で構成されます。

    • 無名クラス(15.9.5)のバイナリ名は、それを直接包含するクラスまたはインタフェースのバイナリ名、その後に続く$、その後に続く空でない一連の数字で構成されます。

    • 汎用クラスまたはインタフェース(8.1.29.1.2)によって宣言される型変数のバイナリ名は、それを直接包含するクラスまたはインタフェースのバイナリ名、その後に続く$、その後に続く型変数の単純名で構成されます。

    • 汎用メソッド(8.4.4)によって宣言される型変数のバイナリ名は、そのメソッドを宣言するクラスまたはインタフェースのバイナリ名、その後に続く$、その後に続くメソッドの記述子(JVMS §4.3.3)、その後に続く$、その後に続く型変数の単純名で構成されます。

    • 汎用コンストラクタ(8.8.4)によって宣言される型変数のバイナリ名は、そのコンストラクタを宣言するクラスのバイナリ名、その後に続く$、その後に続くコンストラクタの記述子(JVMS §4.3.3)、その後に続く$、その後に続く型変数の単純名で構成されます。

  2. 別のクラスまたはインタフェースへの参照は、クラスまたはインタフェースのバイナリ名を使用するシンボリックである必要があります。

  3. 定数変数(4.12.4)であるフィールドへの参照は、コンパイル時に定数変数のイニシャライザが示す値Vに解決される必要があります。

    このようなフィールドがstaticである場合、バイナリ・ファイルのコード内にフィールドへの参照が存在しない必要があります。これには、このフィールドを宣言したクラスまたはインタフェースも含まれます。このようなフィールドは常に、初期化(12.4.2)されているものとして示される必要があり、このフィールドのデフォルトの初期値は(Vとは異なる場合)、表示されないようにします。

    このようなフィールドが非staticである場合、バイナリ・ファイルのコード内にフィールドへの参照が存在しない必要があります。ただし、このフィールドが含まれるクラス内は除きます。(インタフェースにはstaticフィールドしかないため、これはインタフェースではなくクラスです。)このクラスには、インスタンス作成時(12.5)にフィールドの値をVに設定するためのコードが必要です。

  4. クラスC内でのフィールド・アクセスを示す正当な式が与えられ、定数値ではなく、(場合によっては別個の)クラスまたはインタフェースD内で宣言されているfという名前のフィールドを参照している場合、Oracleではフィールド参照の修飾型を次のように定義します。

    • 式が単純名によって参照されているときに、fが現在のクラスまたはインタフェースCのメンバーである場合、TCにします。それ以外の場合、Tは、fがメンバーである最も内側の字句的な包含クラスまたはインタフェース宣言であるとします。どちらの場合も、Tが参照の修飾型です。

    • 参照の形式がTypeName.fであり、TypeNameがクラスまたはインタフェースを示している場合、TypeNameが示すクラスまたはインタフェースが参照の修飾型です。

    • 式の形式がExpressionName.fまたはPrimary.fである場合は次のようになります。

      • ExpressionNameまたはPrimaryのコンパイル時の型が交差型V1 & ... & Vn (4.9)である場合、参照の修飾型はV1です。

      • そうでない場合、ExpressionNameまたはPrimaryのコンパイル時の型が参照の修飾型です。

    • 式の形式がsuper.fである場合、Cのスーパークラスが参照の修飾型です。

    • 式の形式がTypeName.super.fである場合、TypeNameが示すクラスのスーパークラスが参照の修飾型です。

    fへの参照は、参照の修飾型のイレイジャ(4.6)およびフィールドの単純名fへのシンボリック参照にコンパイルする必要があります。また、型が想定どおりであるかどうかを検証者がチェックできるように、この参照には、宣言されたフィールドの型のイレイジャへのシンボリック参照も含まれている必要があります。

  5. クラスまたはインタフェースC内のメソッド呼出し式またはメソッド参照式が与えられ、(場合によっては別個の)クラスまたはインタフェースD内で宣言されている(または暗黙的な宣言されている(9.2)) mという名前のメソッドを参照している場合、Oracleではメソッド呼出しの修飾型を次のように定義します。

    • DObjectである場合、式の修飾型はObjectです。

    • そうでない場合は、次のようになります。

      • メソッドが単純名によって参照されているときに、mが現在のクラスまたはインタフェースCのメンバーである場合、TCとします。それ以外の場合、Tは、mがメンバーである最も内側の字句的な包含クラスまたはインタフェース宣言であるとします。どちらの場合も、Tがメソッド呼出しの修飾型です。

      • 式の形式がTypeName.mまたはReferenceType::mである場合、TypeNameまたはReferenceTypeが示す型がメソッド呼出しの修飾型です。

      • 式の形式がExpressionName.mPrimary.mExpressionName::mまたはPrimary::mである場合、次のようになります。

        • ExpressionNameまたはPrimaryのコンパイル時の型が交差型V1 & ... & Vn (4.9)である場合、メソッド呼出しの修飾型はV1です。

        • そうでない場合、ExpressionNameまたはPrimaryのコンパイル時の型がメソッド呼出しの修飾型です。

      • 式の形式がsuper.mまたはsuper::mである場合、Cのスーパークラスがメソッド呼出しの修飾型です。

      • 式の形式がTypeName.super.mまたはTypeName.super::mであり、TypeNameがクラスXを示している場合、Xがメソッド呼出しの修飾型です。また、TypeNameがインタフェースXを示している場合、Xがメソッド呼出しの修飾型です。

    メソッドへの参照はコンパイル時に、呼出しの修飾型のイレイジャ(4.6)およびメソッドのシグネチャ(8.4.2)のイレイジャへのシンボリック参照に解決する必要があります。メソッドのシグネチャには、15.12.3で確認されたとおりに次のすべてが含まれている必要があります。

    • メソッドの単純名

    • メソッドに対するパラメータ数

    • 各パラメータの型へのシンボリック参照

    また、メソッドへの参照には、示されるメソッドの戻り型のイレイジャへのシンボリック参照が含まれているか、示されるメソッドがvoidを宣言され、値を戻さないことを示すものが含まれている必要があります。

  6. クラス・インスタンス作成式(15.9)、明示的なコンストラクタ呼出し文(8.8.7.1)、またはクラスまたはインタフェースC内のClassType :: new (15.13)形式のメソッド参照式が与えられ、(場合によっては別個の)クラスまたはインタフェースD内で宣言されているコンストラクタmを参照している場合、Oracleでは、コンストラクタ呼出しの修飾型を次のように定義します。

    • 式の形式がnew D(...)ExpressionName.new D(...)Primary.new D(...)またはD :: newである場合、呼出しの修飾型はDです。

    • 式の形式がnew D(...){...}ExpressionName.new D(...){...}またはPrimary.new D(...){...}である場合、式の修飾型は式のコンパイル時の型です。

    • 式の形式がsuper(...)ExpressionName.super(...)またはPrimary.super(...)である場合、式の修飾型はCの直接スーパークラスです。

    • 式の形式がthis(...)である場合、式の修飾型はCです。

    コンストラクタへの参照はコンパイル時に、呼出しの修飾型のイレイジャ(4.6)およびコンストラクタのシグネチャ(8.8.2)へのシンボリック参照に解決する必要があります。コンストラクタのシグネチャには、次の両方が含まれている必要があります。

    • コンストラクタのパラメータ数

    • 各仮パラメータの型へのシンボリック参照

クラスまたはインタフェースのバイナリ表現には、次もすべて含まれる必要があります。

  1. これがクラスであり、Objectではない場合、このクラスの直接スーパークラスのイレイジャへのシンボリック参照。

  2. それぞれの直接スーパーインタフェースのイレイジャへのシンボリック参照(存在する場合)。

  3. フィールドの単純名およびフィールドの型のイレイジャへのシンボリック参照として与えられた、クラスまたはインタフェース内で宣言された各フィールドの仕様。

  4. これがクラスである場合、各コンストラクタの消去されたシグネチャ(前述を参照)。

  5. クラスまたはインタフェース内で宣言された各メソッド(インタフェースについては、暗黙的に宣言されたメソッドを除く(9.2))ごとに、消去されたシグネチャおよび戻り型(前述を参照)。

  6. クラスまたはインタフェースを実装するために必要なコード。

    • インタフェースについては、フィールド・イニシャライザのコード、およびブロック本体を持つ各メソッドの実装(9.4.3)。

    • クラスについては、フィールド・イニシャライザ、インスタンス・イニシャライザおよび静的イニシャライザのコード、ブロック本体を持つ各メソッドの実装(8.4.7)、および各コンストラクタの実装。

  7. それぞれのクラスまたはインタフェースに、その正規名(6.7)を回復するための十分な情報が含まれている必要があります。

  8. それぞれのメンバー・クラスまたはインタフェースに、そのソースレベルのアクセス修飾子を回復するための十分な情報が含まれている必要があります。

  9. それぞれのネストしたクラスおよびネストしたまたはインタフェースに、それを直接包含するクラスまたはインタフェース(8.1.3)へのシンボリック参照が含まれている必要があります。

  10. それぞれのクラスまたはインタフェースに、そのメンバー・クラスおよびインタフェース(8.59.5)すべてへのシンボリック参照と、そのメソッド、コンストラクタ、静的イニシャライザ、インスタンス・イニシャライザおよびフィールド・イニシャライザに出現するローカル・クラスと無名クラスその本体内で宣言された他のネストしたクラスおよびインタフェースすべてへのシンボリック参照が含まれている必要があります。

    すべてのインタフェースに、すべてのメンバー型(9.5)へのシンボリック参照、およびデフォルトのメソッドとフィールド・イニシャライザ内に出現するローカル・クラスと無名クラスへのシンボリック参照が含まれている必要があります。

  11. Javaコンパイラによって発行されたコンストラクトは、発行されたコンストラクトがクラス初期化メソッドでないかぎり、ソース・コードで明示的または暗黙的に宣言されたコンストラクトに対応していない場合、合成としてマークする必要があります(JVMS §2.9)。

  12. Javaコンパイラによって発行されたコンストラクトは、ソース・コードで暗黙的に宣言された仮パラメータに対応している場合、必須としてマークする必要があります(8.8.18.8.98.9.315.9.5.1)。

次の仮パラメータは、ソース・コードで暗黙的に宣言されます。

参考までに、次のコンストラクタは、ソース・コードでは暗黙的に宣言されますが、classファイル内で必須としてマークできるのは仮パラメータのみであるため、必須としてはマークされません(JVMS §4.7.24)。

モジュール宣言に対応するclassファイルには、バイナリ名がmodule-infoであり、スーパークラス、スーパーインタフェース、フィールドおよびメソッドを持たないクラスのclassファイルのプロパティが必要です。また、このモジュールのバイナリ表現には次のものがすべて含まれている必要があります。

次の各セクションでは、既存のバイナリとの互換性を損なわずにクラスおよびインタフェース宣言に加えることができる変更について説明します。前述の翻訳要件に基づき、Java仮想マシンおよびそのclassファイル形式はこれらの変更をサポートしています。前述の要件に基づくクラス・ローダーによってclassファイルにマップし戻される圧縮または暗号化された表現など、他の有効なバイナリ形式もすべて、これらの変更を必然的にサポートします。

13.4 クラスの展開

13.4.26 enum enumクラスの展開

enum宣言の定数を追加したり、順序変更しても、既存のバイナリとの互換性が損なわれることはありません。

存在しなくなったenum定数に既存のバイナリがアクセスしようとすると、クライアントは実行時に失敗し、NoSuchFieldErrorが発生します。したがって、広範に配布されるenumについては、このような変更はお薦めしません。

enum定数を削除すると、対応する暗黙的なフィールド宣言が削除され、13.4.8に記載されているような結果になります。

他のあらゆる点において、enum enumクラスに関するバイナリ互換性のルールは、クラス標準クラスに関するものと同じです。

13.5 インタフェースの展開

13.5.7 注釈インタフェースの展開

注釈インタフェースは、他のインタフェースとまったく同じように動作します。注釈インタフェースに対する要素の追加または削除は、メソッドの追加または削除に似ています。注釈インタフェースを繰返し可能にするなど(9.6.3)、注釈インタフェースに対する他の変更を左右する重要な考慮事項がありますが、これらは、Java仮想マシンによるバイナリのリンケージには影響しません。かわりに、こうした変更は、注釈を操作する反射型APIの動作に影響を及ぼします。これらのAPIのドキュメントには、基になる注釈インタフェースに対して様々な変更が加えられたときのそれらの動作が規定されています。

注釈を追加または削除しても、Javaプログラミング言語によるプログラムのバイナリ表現の正しいリンケージには影響しません。

第15章: 式

15.8 Primary式

15.8.4 修飾されたthis

字句的な包含インスタンス(8.1.3)は、キーワードthisを明示的に修飾することによって参照できます。

Tは、TypeNameが示す型であるとします。nは、Tが、修飾されたthis式が出現するクラスまたはインタフェースのn番目の字句的な包含クラスまたはインタフェース宣言であるような整数であるとします。

TypeName.thisという形式の式の値は、thisn番目の字句的な包含インスタンスです。

式の型はTです。

クラスTの内部クラスまたはT自体でないクラスまたはインタフェースにこの式が出現する場合、コンパイル時にエラーが発生します。

15.9 クラス・インスタンス作成式

15.9.2 包含インスタンスの特定

Cはインスタンス化されるクラス、iは作成されるインスタンスであるとします。Cが内部クラスである場合、iには、直接包含するインスタンス(8.1.3)がある可能性があり、次のように特定されます。

Cが無名クラスで、かつその直接スーパークラスSが内部クラスである場合、iは、Sに関して直接包含するインスタンスを持つ可能性があり、次のように判別されます。

15.12 メソッド呼出し式

15.12.1 コンパイル時のステップ1: 検索するクラスまたはインタフェースの特定

コンパイル時にメソッド呼出しを処理するときの最初のステップは、呼び出されるメソッドの名前と、その名前のメソッドの定義を検索するクラスまたはインタフェースを特定することです。

メソッドの名前は、MethodInvocationの左カッコの直前にあるMethodNameまたはIdentifierによって指定されます。

検索するクラスまたはインタフェースについては、MethodInvocationの左カッコの前にある形式に応じて、次の6つのケースを検討します。

TypeName . super構文はオーバーロードされます。呼出しが字句的な包含クラス宣言内の修飾されていないsuperである場合のように、従来、TypeNameは、クラスである字句的な包含クラス宣言を参照し、ターゲットはこのクラスのスーパークラスです。

class Superclass {
    void foo() { System.out.println("Hi"); }
}

class Subclass1 extends Superclass {
    void foo() { throw new UnsupportedOperationException(); }

    Runnable tweak = new Runnable() {
        void run() {
            Subclass1.super.foo();  // Gets the 'println' behavior
        }
    };
}

スーパーインタフェースのデフォルト・メソッドの呼出しをサポートするために、TypeNameは、現在のクラスまたはインタフェースの直接スーパーインタフェースを参照することもできます。ターゲットはそのスーパーインタフェースです。

interface Superinterface {
    default void foo() { System.out.println("Hi"); }
}

class Subclass2 implements Superinterface {
    void foo() { throw new UnsupportedOperationException(); }

    void tweak() {
        Superinterface.super.foo();  // Gets the 'println' behavior
    }
}

これらの形式の組合せ、つまり、呼出しの形式が字句的な包含クラス宣言内のInterfaceName . superである場合のように、クラスである字句的な包含クラス宣言のスーパーインタフェース・メソッドを呼び出すことをサポートする構文はありません。

class Subclass3 implements Superinterface {
    void foo() { throw new UnsupportedOperationException(); }

    Runnable tweak = new Runnable() {
        void run() {
            Subclass3.Superinterface.super.foo();  // Illegal
        }
    };
}

次善策は、インタフェースのsuperコールを実行する字句的な包含クラス宣言でprivateメソッドを導入することです。

15.12.3 コンパイル時のステップ3: 選択したメソッドが適切かどうか

メソッド呼出しについて最も的確なメソッド宣言がある場合、メソッド呼出しのコンパイル時宣言と呼ばれます。

メソッド呼出しの引数が、コンパイル時宣言の呼出しタイプから導出されたそのターゲット型と互換性がない場合は、コンパイル時にエラーが発生します。

コンパイル時宣言が可変引数呼出しによって適用可能である場合、メソッドの呼出しタイプの最後の仮パラメータ型がFn[]であるとすると、Fnのイレイジャである型が呼出しの時点でアクセス可能でない場合(6.6)、コンパイル時にエラーが発生します。

コンパイル時宣言がvoidである場合、メソッド呼出しは、最上位式(つまり、式の文にあるか、for文のForInitまたはForUpdate部分にあるExpression)である必要があります。そうでない場合はコンパイル時にエラーが発生します。このようなメソッド呼出しは値を生成しないため、値が不要な状況でのみ使用する必要があります。

さらに、コンパイル時宣言が適切であるかどうかは、次のように、左カッコの前にあるメソッド呼出し式の形式によって異なることがあります。

...