このドキュメントでは、クラスおよびインタフェースに関連する用語の使用方法を明らかにし、それらをより明確に型と区別するための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)にも関連します。アクセス修飾子がない場合、ほとんどの宣言はパッケージ・アクセス権を持つため、その宣言が含まれていれば、パッケージ内のどの場所でもアクセスできます。その他の選択肢は、public
、protected
およびprivate
です。
この章では、完全修飾名および正規名(6.7)についても説明します。
6.1 宣言
宣言により、エンティティがプログラムに導入され、このエンティティを参照するために名前内で使用できる識別子(3.8)が組み込まれます。この識別子には、導入されるエンティティがクラス、インタフェースまたは型パラメータである場合は型識別子であるという制約があります。
宣言されたエンティティは、次のいずれかです。
module
宣言で宣言されたモジュール(7.7)package
宣言で宣言されたパッケージ(7.4)単一型インポート宣言またはオンデマンド型インポート宣言で宣言される、インポートされる
型クラスまたはインタフェース(7.5.1、7.5.2)単一静的インポート宣言またはオンデマンド静的インポート宣言で宣言された、インポートされた
static
メンバー(7.5.3、7.5.4)汎用クラス、インタフェース、メソッドまたはコンストラクタの宣言の一部として宣言された型パラメータ(8.1.2、9.1.2、8.4.4、8.8.4)
参照型のメンバー(8.2、9.2、8.9.3、9.6、10.7)で、次のいずれか:
enum定数(8.9)enum定数はメンバーではありません。これらは、メンバーである暗黙的なフィールドを導入します。
フィールドで、次のいずれか:
メソッドで、次のいずれか:
enum定数(8.9.1)
パラメータで、次のいずれか:クラスまたはインタフェースのメソッド(8.4.1)、クラスのコンストラクタ(8.8.1)またはラムダ式(15.27.1)の仮パラメータ
try
文のcatch
句で宣言された例外ハンドラの例外パラメータ(14.20)
仮パラメータと例外パラメータは通常、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.2、9.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
文のForInit部分の右側にある別の宣言子for
文のExpressionおよびForUpdate部分含まれている文
拡張されたfor
文(14.14.2)のFormalParameter部分で宣言されたローカル変数のスコープが、含まれている文です。
try
文(14.20)のcatch
句で宣言された例外ハンドラのパラメータのスコープは、catch
に関連付けられたブロック全体です。
try
-with-resources文(14.20.3)のResourceSpecificationで宣言された変数のスコープは、ResourceSpecificationの残りの部分の右側にある宣言、およびtry
-with-resources文に関連付けられたtry
ブロック全体です。
try
-with-resources文の翻訳は、前述のルールを示します。
例6.3-1.型クラス宣言のスコープ
これらのルールは、クラスおよびインタフェースの宣言が型の使用の前に出現する必要がないことを示します。次のプログラムでは、クラス宣言PointList
のスコープにクラスPoint
とクラスPointList
の両方、およびパッケージpoints
の他のコンパイル・ユニット内の他の型クラスまたはインタフェース宣言が含まれるため、クラスPoint
内のPointList
の使用は有効です。
package points;
class Point {
int x, y;
PointList list;
Point next;
}
class PointList {
Point first;
}
例6.3-2.ローカル変数宣言のスコープ
ローカル変数x
の初期化がローカル変数x
の宣言のスコープ内にありますが、ローカル変数x
はまだ値を持たず、使用できないため、次のプログラムを実行すると、コンパイル時にエラーが発生します。フィールドx
は値0
(Test1
が初期化されたときに割り当てられたもの)を持ちますが、ローカル変数x
によってシャドウ化(6.4.1)されているため、注意がそらされることがあります。
class Test1 {
static int x;
public static void main(String[] args) {
int x = x;
}
}
次のプログラムは正常にコンパイルされます。
class Test2 {
static int x;
public static void main(String[] args) {
int x = (x=2)*2;
System.out.println(x);
}
}
これは、ローカル変数x
が使用される前に明確に割り当てられている(16)ためです。次のように出力されます。
4
次のプログラムでは、three
のイニシャライザは、前の宣言子で宣言された変数two
を正確に参照でき、次の行のメソッド呼出しは、ブロック内で前に宣言された変数three
を正確に参照できます。
class Test3 {
public static void main(String[] args) {
System.out.print("2+1=");
int two = 2, three = two + 1;
System.out.println(three);
}
}
このプログラムでは、次の出力が生成されます。
2+1=3
6.5 名前の意味の確認
6.5.4 PackageOrTypeNameの意味
6.5.4.1 単純なPackageOrTypeName
PackageOrTypeName、Qが有効なTypeIdentifierで、かつQという名前の型クラス、インタフェースまたは型パラメータのスコープに出現する場合、PackageOrTypeNameはTypeNameとして再分類されます。
そうでない場合、PackageOrTypeNameはPackageNameとして再分類されます。PackageOrTypeNameの意味は、再分類された名前の意味です。
6.5.4.2 修飾されたPackageOrTypeName
修飾されたPackageOrTypeNameがQ.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.5、9.5)、Q内のIdという名前のメンバー型・クラスまたはインタフェースがアクセス可能でない場合(6.6)、あるいはIdがQ内の複数のメンバー型・クラスまたはインタフェースの名前である場合は、コンパイル時にエラーが発生します。
例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} {
TypeDeclarationTopLevelClassOrInterfaceDeclaration} - ModularCompilationUnit:
- {ImportDeclaration} ModuleDeclaration
通常コンパイル・ユニットは3つの部分で構成され、それぞれがオプションです。
コンパイル・ユニットが属するパッケージの完全修飾名(6.7)を指定する、
package
宣言(7.4)。package
宣言がないコンパイル・ユニットは、名前のないパッケージ(7.4.2)に属します。他のパッケージ内の
型クラスおよびインタフェースや型クラスおよびインタフェースのstatic
メンバーを、それらの単純名を使用して参照できるようにするimport
宣言(7.5)。
モジュラ・コンパイル・ユニットはmodule
宣言(7.7)で構成され、オプションで、その前にimport
宣言が含められます。import
宣言を使用すると、このモジュールおよび他のモジュールのパッケージ内の型クラスおよびインタフェースや型クラスおよびインタフェースのstatic
メンバーを、module
宣言内でそれらの単純名を使用して参照できます。
package
宣言の直後の各コンパイル・ユニットの先頭に宣言import java.lang.*;
が出現する場合と同様に、それぞれのコンパイル・ユニットによって、事前定義済のパッケージjava.lang
で宣言されたそれぞれのpublic
型クラスまたはインタフェース名が暗黙的にインポートされます。その結果、それらの型クラスおよびインタフェースすべての名前をそれぞれのコンパイル・ユニットで単純名として使用できます。
ホスト・システムによって、観察可能なコンパイル・ユニットが特定されます。ただし、事前定義済のパッケージjava
とそのサブパッケージlang
およびio
内のコンパイル・ユニットは除きます(これらはすべて、常に観察可能です)。
それぞれの観察可能なコンパイル・ユニットは、次のようにモジュールに関連付けることができます。
ホスト・システムは、観察可能な通常コンパイル・ユニットが、ホスト・システムによって選択されたモジュールに関連付けられていることを判断できます。ただし、(i)事前定義済のパッケージ
java
とそのサブパッケージlang
およびio
内の通常コンパイル・ユニット(これらはすべて、java.base
モジュールに関連付けられる)、および(ii)名前のないパッケージ内の通常コンパイル・ユニット(これは、7.4.2で指定されたモジュールに関連付けられる)は除きます。ホスト・システムは、観察可能なモジュラ・コンパイル・ユニットがモジュラ・コンパイル・ユニットで宣言されたモジュールに関連付けられていることを判断する必要があります。
コンパイル・ユニットの観察可能性はそのパッケージの観察可能性(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.3、6.5.3、6.5.5)。
前述のルールによって、モジュラ・コンパイル・ユニット内の注釈(特に、モジュール宣言に適用される注釈)で使用される
パッケージ/型の名前が、モジュールに関連付けられた通常コンパイル・ユニットにそれらが出現する場合と同様に解釈されることが確実になります。
異なる通常コンパイル・ユニットで宣言された型クラスおよびインタフェースは、相互を循環的に参照することがあります。Javaコンパイラは、そのようなすべての型クラスおよびインタフェースを同時にコンパイルするように準備する必要があります。
7.5 インポート宣言
インポート宣言を使用すると、名前付き型またはクラス、インタフェースまたはstatic
メンバーを、単一の識別子で構成される単純名(6.2)で参照できます。
適切なインポート宣言を使用しない場合、別のパッケージで宣言された型、または別の型の別のパッケージで宣言されたクラスまたはインタフェース、あるいは別のクラスまたはインタフェースのstatic
メンバーを参照する唯一の方法は、完全修飾名(6.7)を使用することですstatic
メンバーへの参照では、通常、修飾名(6.7)を使用する必要があります。
元の記載に関する問題:
メンバー・クラスには、その宣言クラスをインポートすることによって、部分修飾名でアクセスできました。
スーパークラスのメンバーは、インポートする必要なく、継承を介してスコープ内になります。
- ImportDeclaration:
- SingleTypeImportDeclaration
- TypeImportOnDemandDeclaration
- SingleStaticImportDeclaration
- StaticImportOnDemandDeclaration
- 単一型インポート宣言(7.5.1)では、単一の名前付き
型 クラスまたはインタフェースを、その正規名(6.7)を指定することによってインポートします。
オンデマンド型インポート宣言(7.5.2)では、
型またはパッケージ、クラスまたはインタフェースの正規名を指定することによって、必要に応じて、名前付き型または名前付きパッケージ、クラスまたはインタフェースのアクセス可能なすべての型クラスおよびインタフェースをインポートします。単一静的インポート宣言(7.5.3)では、特定の名前を持つアクセス可能なすべての
static
メンバーを、型クラスまたはインタフェースの正規名を指定することによってそこからインポートします。オンデマンド静的インポート宣言(7.5.4)では、
型クラスまたはインタフェースの正規名を指定することによって、必要に応じて、名前付き型クラスまたはインタフェースのアクセス可能なすべてのstatic
メンバーをインポートします。
これらの宣言によってインポートされる型クラス、インタフェースまたはメンバーのスコープおよびシャドウ化については、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.5、9.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.5、9.5)が1回のみインポートされた場合と同様の結果になります。
7.6 最上位型クラスおよびインタフェース宣言
最上位型クラスまたはインタフェース宣言では、enumクラス(8.9)である可能性がある最上位クラス型(8 8.1)、または注釈インタフェース(9.6)である可能性がある最上位インタフェース型(9 9.1)を宣言します。
TypeDeclaration:TopLevelClassOrInterfaceDeclaration:ClassDeclarationInterfaceDeclaration- ClassOrInterfaceDeclaration
;
- ClassOrInterfaceDeclaration:
- ClassDeclaration
- EnumDeclaration
- InterfaceDeclaration
- AnnotationDeclaration
コンパイル・ユニット内で
型クラスまたはインタフェース宣言のレベルに出現する余分な;
トークンは、コンパイル・ユニットの意味に影響しません。Javaプログラミング言語では、不要なセミコロンは、クラス宣言の後に;
を配置することに慣れているC++プログラマのための便宜的措置としてのみ許容されています。新しいJavaコードでは使用しないでください。
アクセス修飾子がない場合、最上位型クラスまたはインタフェースはパッケージ・アクセス権を持ちます。それが宣言されたパッケージの通常コンパイル・ユニット内でのみアクセス可能です(6.6.1)。型クラスまたはインタフェースをpublic
として宣言すると、同じモジュールの他のパッケージのコードから、また場合によっては他のモジュールのパッケージのコードから型クラスまたはインタフェースへのアクセスを許可できます。
protected
、private
または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
など)で構成される名前のファイルに見つからない場合はコンパイル時にエラーが発生するという制限を強制することを選択できます。
型クラスまたはインタフェースが、その型クラスまたはインタフェースが宣言されているパッケージの他の通常コンパイル・ユニット内のコードによって参照されています。型クラスまたはインタフェースがpublic
として宣言されています(そのため、他のパッケージ内のコードからアクセスできる可能性があります)。
この制限は、1つのコンパイル・ユニットについてそのような
型クラスまたはインタフェースを1つまでにする必要があることを意味します。この制限によって、Javaコンパイラはパッケージ内で名前付きクラスまたはインタフェースを容易に見つけることができます。実際には、多くのプログラマは、各クラスまたはインタフェース型がpublic
であるかどうかや、他のコンパイル・ユニットのコードによって参照されているかどうかに関係なく、クラスまたはインタフェースをその独自のコンパイル・ユニットに配置します。
たとえば、
public
型wet.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
というpublic
なstatic
メソッドを明示的に宣言する場合、そのメソッドはプロバイダ・メソッドと呼ばれます。
サービス・プロバイダにプロバイダ・メソッドがある場合、その戻り型は、(i)現在のモジュール内で宣言されているか、別のモジュール内で宣言されていて、現在のモジュール内のコードからアクセス可能であり、かつ(ii) provides
ディレクティブで指定されたサービスのサブタイプである必要があります。そうでない場合はコンパイル時にエラーが発生します。
provides
ディレクティブによって指定されるサービス・プロバイダは現在のモジュール内で宣言する必要がありますが、そのプロバイダ・メソッドは、別のモジュール内で宣言された戻り型を持つことができます。また、サービス・プロバイダがプロバイダ・メソッドを宣言する場合、サービス・プロバイダ自体がサービスのサブタイプである必要はありません。
サービス・プロバイダにプロバイダ・メソッドがない場合、そのサービス・プロバイダはプロバイダ・コンストラクタを持つ必要があるとともに、provides
ディレクティブで指定されたサービスのサブタイプである必要があります。そうでない場合はコンパイル時にエラーが発生します。
モジュール宣言内の複数のprovides
ディレクティブで同じサービスを指定した場合、コンパイル時にエラーが発生します。
特定のprovides
ディレクティブのwith
句で同じサービス・プロバイダを複数回指定した場合、コンパイル時にエラーが発生します。
第8章: クラス
クラス宣言では、新しい参照型を定義し、これらの実装方法について記述します(8.1)。
最上位クラス(7.6)は、ネストしたクラスではないコンパイル・ユニットの最上位で宣言されるクラスです。
ネストしたクラスは、宣言が別のクラスまたはインタフェースの本体内で行われるクラスです。ネストしたクラスは、メンバー・クラス(8.5、9.5)、ローカル・クラス(14.3)または無名クラス(15.9.5)である場合があります。
内部クラス(8.1.3)は、包含クラス・インスタンス、ローカル変数および型変数を参照できる、ネストしたクラスです。
enumクラス(8.9)は、名前付きクラス・インスタンスの小規模なセットを定義する特別な構文で宣言されるクラスです。
この章では、すべてのクラス、つまり、最上位クラス(7.6)とネストしたクラス(メンバー・クラス(8.5、9.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) public
、protected
または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.5、9.5)にのみ関連し、ローカル・クラス(14.3)または無名クラス(15.9.5)には関連しません。
アクセス修飾子protected
およびprivate
は、直接包含するクラス宣言内のメンバー・クラス(8.5)にのみ関連します。
修飾子static
は、メンバー・クラス(8.5.1)にのみ関連し、最上位またはローカル・または無名クラスには関連しません。
ここでは、ClassDeclaration構文に出現するClassModifierプロダクションについて説明しています。これは、最上位クラス、メンバー・クラスおよびローカル・クラスによって共有されます。無名クラス宣言にClassModifierプロダクションはないため、そのようなクラスについてここで述べても意味がありません。
クラス宣言について同じキーワードが修飾子として複数回出現する場合、またはアクセス修飾子public
、protected
および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番目の字句的な包含型宣言となるのは、それがCのn-1番目の字句的な包含型宣言を直接包含する型宣言である場合です。
クラスまたはインタフェースOの直接内部クラスCのインスタンスiは、iを直接包含するインスタンスと呼ばれる、Oのインスタンスに関連付けられます。オブジェクトを直接包含するインスタンス(ある場合)は、オブジェクトの作成時に特定されます(15.9.2)。
オブジェクトoは、それ自体のゼロ番目の字句的な包含インスタンスです。
オブジェクトoがインスタンスiのn番目の字句的な包含インスタンスとなるのは、それがiのn-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
ClassDeclarationInterfaceDeclaration- ClassOrInterfaceDeclaration
;
クラス型Cで宣言または継承されるメンバーmの宣言のスコープおよびシャドウ化については、6.3および6.4に規定されています。
C自体がネストしたクラスである場合は、包含スコープにmと同じ種類(変数、メソッドまたは型)および名前の定義がある可能性があります。(このスコープは、ブロック、クラスまたはパッケージである場合があります。)そのようなすべての場合において、Cで宣言または継承されたメンバーmによって、同じ種類および名前の他の定義がシャドウ化されます(6.4.1)。
8.5 メンバー型・クラスおよびインタフェース宣言
メンバー・クラスは、宣言が別のクラスまたはインタフェース宣言の本体(8.1.6、9.1.4)に直接包含されているクラスです。メンバー・クラスはenumクラス(8.9)である場合があります。
メンバー・インタフェースは、宣言が別のクラスまたはインタフェース宣言の本体(8.1.6、9.1.4)に直接包含されているインタフェースです。メンバー・インタフェースは注釈インタフェース(9.6)である場合があります。
クラス内のメンバー型・クラスまたはインタフェース宣言のアクセス可能性は、そのアクセス修飾子によって指定され、アクセス修飾子がない場合は6.6によって指定されます。
クラス内のメンバー型宣言について同じキーワードが修飾子として複数回出現する場合、またはアクセス修飾子public
、protected
およびprivate
(6.6)のうちの複数がメンバー型宣言に含まれる場合、コンパイル時にエラーが発生します。
メンバー型のスコープおよびシャドウ化については、6.3および6.4に規定されています。
特定の名前を持つメンバー型・クラスまたはインタフェースをクラスで宣言した場合、その型クラスまたはインタフェースの宣言は、クラスのスーパークラスおよびスーパーインタフェース内の同じ名前を持つメンバー型・クラスおよびインタフェースのアクセス可能なすべての宣言を隠すとされています。
この点に関しては、メンバー
型・クラスおよびインタフェースを隠すことは、フィールド(8.3)を隠すことと同様です。
クラスは、その直接スーパークラスおよび直接スーパーインタフェースから、スーパークラスおよびスーパーインタフェースの非private
メンバー型・クラスおよびインタフェースのうち、クラス内のコードからアクセス可能で、かつクラス内の宣言によって隠されていないものをすべて継承します。
クラスは、そのスーパークラスとスーパーインタフェースから、またはそのスーパーインタフェースのみから、同じ名前を持つ複数のメンバー型・クラスまたはインタフェースを継承することがあります。そのような状況自体によってコンパイル時にエラーが発生することはありません。ただし、クラスの本体内でそのようなメンバー型・クラスまたはインタフェースをその単純名で参照しようとする場合、参照があいまいであるため、コンパイル時にエラーが発生します。
複数のパスによって、同じメンバー型・クラスまたはインタフェース宣言が1つのインタフェースから継承されることがあります。そのような状況では、そのメンバー型・クラスまたはインタフェースは1回のみ継承されるとみなされ、あいまいになることなく、その単純名で参照できます。
8.5.1 staticメンバー型・クラスおよびインタフェース宣言
static
キーワードは、非内部クラスまたはインタフェースTの本体内のメンバー型・クラスCの宣言を変更する場合があります。この結果、Cが内部クラスではないことを宣言します。Tのstatic
メソッドの本体内に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])
;
- TypeArguments:
<
TypeArgumentList>
- ArgumentList:
- Expression {
,
Expression}
明示的なコンストラクタ呼出し文は、次の2種類に分類されます。
代替コンストラクタ呼出しは、キーワード
this
で始まります(明示的な型引数が先頭に付くこともあります)。これらは、同じクラスの代替コンストラクタを呼び出す場合に使用します。スーパークラス・コンストラクタ呼出しは、キーワード
super
で始まるか(明示的な型引数が先頭に付くこともあります)、Primary式またはExpressionNameで始まります。これらは、直接スーパークラスのコンストラクタを呼び出す場合に使用します。これらは、さらに次のように分類されます。修飾されていないスーパークラス・コンストラクタ呼出しは、キーワード
super
で始まります(明示的な型引数が先頭に付くこともあります)。修飾されたスーパークラス・コンストラクタ呼出しは、Primary式またはExpressionNameで始まります。これらを使用すると、直接スーパークラスに関して、新しく作成されるオブジェクトを直接包含するインスタンス(8.1.3)をサブクラス・コンストラクタで明示的に指定できます。これは、スーパークラスが内部クラスである場合に必要になることがあります。
コンストラクタ本体内の明示的なコンストラクタ呼出し文では、このクラスやスーパークラスで宣言されたインスタンス変数、インスタンス・メソッドまたは内部クラスを参照したり、式でthis
またはsuper
を使用することはできません。そのようにすると、コンパイル時にエラーが発生します。
このように現在のインスタンスの使用が禁止されていることが、明示的なコンストラクタ呼出し文が静的コンテキストに出現する(8.1.3)とみなされる理由となっています。
TypeArgumentsがthis
またはsuper
の左側にある場合、いずれかの型引数がワイルドカードであれば(4.5.1)、コンパイル時にエラーが発生します。
Cはインスタンス化されるクラスであり、SはCの直接スーパークラスであるとします。
スーパークラス・コンストラクタ呼出し文が修飾されていない場合、次のようになります。
- Sが内部メンバー・クラスであるが、SがCを包含するクラスのメンバーでない場合は、コンパイル時にエラーが発生します。
スーパークラス・コンストラクタ呼出し文が修飾されている場合、次のようになります。
Sが内部クラスでない場合、またはSの宣言が静的コンテキストに出現する場合は、コンパイル時にエラーが発生します。
また、pは
.super
の直前のPrimary式またはExpressionNameであり、OはSを直接包含するクラスであるとします。pの型がOまたはOのサブクラスでない場合、あるいはpの型にアクセスできない場合は(6.6)、コンパイル時にエラーが発生します。
明示的なコンストラクタ呼出し文がスローできる例外型については、11.2.2に規定されています。
代替コンストラクタ呼出し文の評価では、まず、通常のメソッド呼出しと同様に、コンストラクタの引数が左から右に評価され、続いてコンストラクタが呼び出されます。
スーパークラス・コンストラクタ呼出し文の評価は、次のように進められます。
iは、作成されるインスタンスであるとします。S (ある場合)に関してiを直接包含するインスタンスを特定する必要があります。
Sが内部クラスでない場合、またはSの宣言が静的コンテキストに出現する場合、Sに関してiを直接包含するインスタンスは存在しません。
スーパークラス・コンストラクタ呼出しが修飾されていない場合、Sは必然的にローカル・クラスまたは内部メンバー・クラスです。
Sがローカル・クラスである場合、Oは、Sを直接包含する
型クラスまたはインタフェース宣言であるとします。Sが内部メンバー・クラスである場合、Oは、SがメンバーであるCの最も内側の包含クラスであるとします。
nは、OがCのn番目の字句的な包含
型クラスまたはインタフェース宣言であるような整数であるとします(n ≥ 1)。Sに関してiを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。継承によってSがCのメンバーである場合もありますが、
this
のゼロ番目の字句的な包含インスタンス(つまり、this
自体)が、Sに関してiを直接包含するインスタンスとして使用されることはありません。スーパークラス・コンストラクタ呼出しが修飾されている場合、
.super
の直前のPrimary式またはExpressionName、pが評価されます。pが
null
と評価されると、NullPointerException
が発生し、スーパークラス・コンストラクタ呼出しは突然完了します。それ以外の場合は、この評価の結果が、Sに関してiを直接包含するインスタンスです。
S (ある場合)に関してiを直接包含するインスタンスが特定されたら、スーパークラス・コンストラクタ呼出し文の評価では、通常のメソッド呼出しと同様に、コンストラクタの引数が左から右に評価され、続いてコンストラクタが呼び出されます。
最後に、スーパークラス・コンストラクタ呼出し文が正常に完了すると、Cのすべてのインスタンス変数イニシャライザとCのすべてのインスタンス・イニシャライザが実行されます。インスタンス・イニシャライザまたはインスタンス変数イニシャライザIのテキストが別のインスタンス・イニシャライザまたはインスタンス変数イニシャライザJの前にある場合、IはJより先に実行されます。
スーパークラス・コンストラクタ呼出しが実際に明示的なコンストラクタ呼出し文として出現するか、暗黙的に提供されるかに関係なく、インスタンス変数イニシャライザおよびインスタンス・イニシャライザの実行は実施されます。(代替コンストラクタ呼出しでは、こうした追加の暗黙的な実行は実施されません。)
例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.5、9.5)を指定できます。
enum宣言に修飾子abstract
またはfinal
が含まれている場合、コンパイル時にエラーが発生します。
enum宣言は、クラス本体を持つenum定数(8.9.1)を1つ以上含んでいる場合を除き、暗黙的にfinal
です。
ネストしたメンバーenum型宣言は、暗黙的にstatic
です。ネストしたメンバーenum型クラスの宣言ではstatic
修飾子を重複して指定することが許可されています。
これは、内部クラス(8.1.3)は、定数変数を除いて
static
メンバーを持つことができないため、内部クラスの本体でのメンバーとしてenum型クラスを宣言できないことを意味します。
enum宣言について同じキーワードが修飾子として複数回出現する場合、またはアクセス修飾子public
、protected
およびprivate
(6.6)のうちの複数がenum宣言に含まれる場合、コンパイル時にエラーが発生します。
enum型クラスEの直接スーパークラス型は、Enum<
E>
です(8.1.4)。
enum型クラスには、そのenum定数によって定義されたもの以外のインスタンスはありません。enum型クラスを明示的にインスタンス化しようとすると(15.9.1)、コンパイル時にエラーが発生します。
enum
型クラスのインスタンスが、そのenum定数によって定義されたもの以外に存在することがないようにするために、コンパイル時エラーに加えて、さらに次の3つのメカニズムが用意されています。
Enum
のfinal
clone
メソッドによって、enum定数を一切クローニングできないことが確実化されます。enum
型クラスの反射型のインスタンス化は禁止されています。直列化メカニズムによる特別な処理によって、直列化復元の結果として重複するインスタンスが作成されることがなくなります。
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に規定されています。
EnumConstantのIdentifierは、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
ClassDeclarationInterfaceDeclaration- 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宣言Eにabstract
メソッド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のメンバーは、次のすべてです。
Eの宣言の本体で宣言されたメンバー。
Enum<
E>
から継承されたメンバー。Eの宣言の本体で宣言されたそれぞれのenum定数cについて、Eには、cと同じ名前を持つ型Eの暗黙的に宣言された
public
static
final
フィールドがあります。このフィールドは、Eをインスタンス化し、Eについて選択されたコンストラクタにcの引数を渡す変数イニシャライザを持ちます。このフィールドは、cと同じ注釈を持ちます(ある場合)。これらのフィールドは、Eの宣言の本体で
static
フィールドが明示的に宣言される前に、対応するenum定数と同じ順序で暗黙的に宣言されます。enum定数は、対応する暗黙的に宣言されたフィールドが初期化されるときに作成されるとされています。
Eの宣言の本体に出現するのと同じ順序でEのenum定数を含む配列を返す、暗黙的に宣言されたメソッド
public
static
E[]
values()
。指定された名前を持つEのenum定数を返す、暗黙的に宣言されたメソッド
public
static
EvalueOf(String name)
。暗黙的に宣言された次のメソッド。
/** * Returns an array containing the constants of this enum
* type, in the order they're declared. This method may be
* class, in the order they're declared. This method may be
* used to iterate over the constants as follows: * * for(E c : E.values()) * System.out.println(c); * * @return an array containing the constants of this enum
* type, in the order they're declared
* class, in the order they're declared
*/ public static E[] values(); /**
* Returns the enum constant of this type with the specified
* Returns the enum constant of this class with the specified
* name. * The string must match exactly an identifier used to declare
* an enum constant in this type. (Extraneous whitespace
* an enum constant in this class. (Extraneous whitespace
* characters are not permitted.) * * @return the enum constant with the specified name
* @throws IllegalArgumentException if this enum type has no
* @throws IllegalArgumentException if this enum class has no
* constant with the specified name */ public static E valueOf(String name);
つまり、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.2のCoin
型クラスに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.5、9.5)として出現するインタフェースです。
標準インタフェースと注釈型という2種類のインタフェースを区別します。
注釈インタフェース(9.6)は、注釈(9.7)の反射型の表現によって実装することを目的とした、特別な構文で宣言されるインタフェースです。
この章では、すべてのインタフェース、つまり、標準インタフェース(最上位インタフェース(7.6)とネストしたインタフェース(8.5、9.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
ClassDeclarationInterfaceDeclaration- ClassOrInterfaceDeclaration
;
インタフェース型Iで宣言または継承されるメンバーmの宣言のスコープについては、6.3に規定されています。
9.5 メンバー型・クラスおよびインタフェース宣言
インタフェースには、メンバー型・クラスおよびインタフェース宣言(8.5)を含めることができます。
インタフェースの本体内のそれぞれのメンバー型・クラスまたはインタフェース宣言は、暗黙的にpublic
かつstatic
です。これらの修飾子のいずれかまたは両方を重複して指定することが許可されています。
インタフェースのメンバー型・クラスまたはインタフェース宣言に修飾子protected
またはprivate
が含まれている場合、コンパイル時にエラーが発生します。
インタフェースのメンバー型宣言について同じキーワードが修飾子として複数回出現する場合、コンパイル時にエラーが発生します。
特定の名前を持つメンバー型・クラスまたはインタフェースをインタフェースで宣言した場合、その型クラスまたはインタフェースの宣言は、インタフェースのスーパーインタフェース内の同じ名前を持つメンバー型・クラスおよびインタフェースのアクセス可能なすべての宣言を隠すとされています。
インタフェースは、その直接スーパーインタフェースから、スーパーインタフェースの非メンバーprivate
型・クラスおよびインタフェースのうち、インタフェース内のコードからアクセス可能で、かつインタフェース内の宣言によって隠されていないものをすべて継承します。
スーパーインタフェースのメンバー・クラスおよびインタフェースはすべてpublic
であるため、それらのアクセス可能性をここで考慮する必要はありません。
インタフェースは、同じ名前を持つ複数のメンバー型・クラスまたはインタフェースを継承することがあります。そのような状況自体によってコンパイル時にエラーが発生することはありません。ただし、インタフェースの本体内でそのようなメンバー型・クラスまたはインタフェースをその単純名で参照しようとする場合、参照があいまいであるため、コンパイル時にエラーが発生します。
複数のパスによって、同じメンバー型・クラスまたはインタフェース宣言が1つのインタフェースから継承されることがあります。そのような状況では、そのメンバー型・クラスまたはインタフェースは1回のみ継承されるとみなされ、あいまいになることなく、その単純名で参照できます。
9.6 注釈型インタフェース
注釈型宣言では、特別な種類のインタフェース型である、新しい注釈型インタフェースを指定します。注釈型宣言を標準インタフェース宣言と区別するために、キーワードinterface
の前にアットマークアットマーク(@
)が付けられます。
AnnotationTypeDeclaration:AnnotationDeclaration:- {InterfaceModifier}
@
interface
TypeIdentifierAnnotationTypeBodyAnnotationInterfaceBody
アットマークアットマーク(@
)とキーワードinterface
は、別個のトークンです。それらを空白で区切ることもできますが、スタイルとしてはお薦めしません。
注釈型宣言の注釈修飾子に関するルールについては、9.7.4および9.7.5に規定されています。
注釈型宣言のTypeIdentifierでは、注釈型インタフェースの名前を指定します。
注釈型インタフェースの単純名がその包含クラスまたはインタフェースのいずれかと同じ場合、コンパイル時にエラーが発生します。
それぞれの注釈型インタフェースの直接スーパーインタフェースはjava.lang.annotation.Annotation
です。
AnnotationTypeDeclarationAnnotationInterfaceDeclaration構文により、注釈型インタフェース宣言を汎用にすることはできず、extends
句も許可されていません。
注釈
型インタフェースではスーパークラスまたはスーパーインタフェースを明示的に宣言できないため、注釈型インタフェースのサブクラスまたはサブインタフェース自体が注釈型インタフェースになることはありません。同様に、java.lang.annotation.Annotation
自体も注釈型インタフェースにはなりません。
注釈型インタフェースは、様々なメンバーをjava.lang.annotation.Annotation
から継承します。これには、Object
のインスタンス・メソッドに対応する暗黙的に宣言されたメソッドも含まれますが、これらのメソッドでは、注釈型インタフェースの要素(9.6.1)は定義されません。
これらのメソッドでは注釈
型インタフェースの要素が定義されないため、その型の注釈(9.7)でそれらを使用することは不正です。このルールがなければ、要素が注釈で表現できる型であることや、それらのアクセサ・メソッドを使用できることを確認できません。
ここで明示的に変更されていないかぎり、標準インタフェース宣言に適用されるすべてのルールが注釈型宣言にも適用されます。
たとえば、注釈
型インタフェースは、標準クラスおよびインタフェース型クラスおよびインタフェースと同じネームスペースを共有します。また、注釈型宣言は、インタフェース宣言が正当である場合は常に正当であり、インタフェース宣言と同じスコープおよびアクセス可能性を持ちます。
9.6.1 注釈型要素
注釈型宣言の本体にはメソッド宣言を含めることができ、それぞれの宣言で、注釈型インタフェースの要素を定義します。注釈型インタフェースは、それが明示的に宣言するメソッドで定義されたもの以外の要素を持ちません。
AnnotationTypeBody:AnnotationInterfaceBody:{
{AnnotationTypeMemberDeclarationAnnotationMemberDeclaration}}
AnnotationTypeMemberDeclaration:AnnotationMemberDeclaration:AnnotationTypeElementDeclarationAnnotationElementDeclaration- ConstantDeclaration
ClassDeclarationInterfaceDeclaration- ClassOrInterfaceDeclaration
;
AnnotationTypeElementDeclaration:AnnotationElementDeclaration:- {
AnnotationTypeElementModifierAnnotationElementModifier} UnannType Identifier(
)
[Dims]
[DefaultValue];
AnnotationTypeElementModifier:AnnotationElementModifier:- (次のうちの1つ)
- Annotation
public
abstract
AnnotationTypeElementDeclarationAnnotationElementDeclarationプロダクションにより、注釈型宣言内のメソッド宣言は、仮パラメータ、型パラメータまたはthrows
区を持つことができません。便宜上、ここでは4.3の次のプロダクションを示します。
- Dims:
- {Annotation}
[
]
{{Annotation}[
]
}
AnnotationTypeElementModifierAnnotationElementModifierプロダクションにより、注釈型宣言内のメソッド宣言をdefault
またはstatic
にすることはできません。したがって、注釈型インタフェースでは、標準インタフェース型と同じ種類のメソッドを宣言できません。注釈型インタフェースがその暗黙的なスーパーインタフェースjava.lang.annotation.Annotation
からデフォルト・メソッドを継承することも考えられますが、Java SE 14の時点ではそのようなデフォルト・メソッドは存在しません。
規則により、注釈
型要素宣言に存在する必要がある唯一のAnnotationTypeElementModifierAnnotationElementModifierは注釈です。
注釈型インタフェースで宣言されるメソッドの戻り型は、次のいずれかである必要があります。そうでない場合はコンパイル時にエラーが発生します。
このルールによって、次のようなネストした配列型を持つ要素を防ぐことができます。
@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 注釈型要素のデフォルト
注釈型要素宣言には、キーワードdefault
とElementValueを含む要素の(空の)パラメータ・リストに従って指定されるデフォルト値を含めることができます(9.7.1)。
- DefaultValue:
default
ElementValue
要素の型が、指定されたデフォルト値に対応していない(9.7)場合、コンパイル時にエラーが発生します。
デフォルト値は注釈にコンパイルされず、注釈が読み取られるときに動的に適用されます。したがって、デフォルト値を変更すると、変更が加えられる前にコンパイルされたクラスの注釈にも影響します(これらの注釈に、デフォルトが指定された要素の明示的な値がないものと仮定します)。
例9.6.2-1.デフォルト値を含む注釈型宣言
9.6.1のRequestForEnhancement
注釈型インタフェースを改良したものを次に示します。
@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の包含注釈型インタフェースです。
TCによって、戻り型がT
[]
であるvalue()
メソッド要素が宣言されます。TCによって宣言された、
value()
以外のメソッド要素にデフォルト値があります。少なくとも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.SOURCE
、java.lang.annotation.RetentionPolicy.CLASS
またはjava.lang.annotation.RetentionPolicy.RUNTIME
です。
少なくともTCと同じ種類のプログラム要素にTを適用できます(9.6.4.1)。具体的には、Tを適用できるプログラム要素の種類がセットm1によって示され、TCを適用できるプログラム要素の種類がセットm2によって示される場合、m2内のそれぞれの種類がm1に出現する必要があります。ただし、次の場合は除きます。
m2内の種類が
java.lang.annotation.ElementType.ANNOTATION_TYPE
である場合は、java.lang.annotation.ElementType.ANNOTATION_TYPE
、java.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に出現する必要があります。
この条項によって、注釈
型インタフェースは、それを適用できるプログラム要素の種類の一部でのみ繰返し可能にすることができるというポリシーが実装されます。java.lang.annotation.Documented
に対応する(メタ)注釈がTの宣言に含まれている場合、java.lang.annotation.Documented
に対応する(メタ)注釈がTCの宣言に含まれている必要があります。Tが
@Documented
でないときにTCが@Documented
であることは許容されます。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
をその包含注釈型インタフェースとして指定しようとしていますが、実際は、FooContainer
はFoo
の包含注釈型インタフェースではないため、Foo
宣言をコンパイルすると、コンパイル時にエラーが生成されます。(FooContainer.value()
の戻り型はFoo[]
ではありません。)
@Repeatable
注釈を繰り返すことはできないため、繰返し可能な注釈型インタフェースで指定できる包含注釈型インタフェースは1つのみです。
複数の包含注釈
型インタフェースの指定を許可すると、繰返し可能な注釈型インタフェースの複数の注釈が論理的にコンテナ注釈(9.7.5)に置き換えられるときに、コンパイル時に望ましくない選択が行われます。
注釈型インタフェースは、最大1つの注釈型インタフェースの包含注釈型インタフェースにすることができます。
このことは、注釈
型インタフェースTの宣言でTCという包含注釈型インタフェースを指定する場合、TCのvalue()
メソッドの戻り型はTを含む、具体的にはT[]
であるという要件によって暗黙的に規定されています。
注釈型インタフェースでそれ自体をその包含注釈型インタフェースとして指定することはできません。
このことは、包含注釈
型インタフェースのvalue()
メソッドに関する要件によって暗黙的に規定されています。具体的には、注釈型インタフェースAでそれ自体を(@Repeatable
によって)その包含注釈型インタフェースとして指定する場合、Aのvalue()
メソッドの戻り型は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
がその包含注釈型インタフェースである場合を考えてみましょう。
Foo
に@Target
メタ注釈がなく、FooContainer
にも@Target
メタ注釈がない場合、@Foo
は、注釈をサポートするすべてのプログラム要素で繰り返すことができます。Foo
には@Target
メタ注釈がないが、FooContainer
には@Target
メタ注釈がある場合、@Foo
は、@FooContainer
が出現できるプログラム要素でのみ繰り返すことができます。Foo
に@Target
メタ注釈がある場合、Javaプログラミング言語の設計者の判断で、Foo
の適用可能性を理解したうえでFooContainer
を宣言する必要があります。具体的には、FooContainer
が出現できるプログラム要素の種類は、論理的にFoo
の種類と同じであるか、そのサブセットである必要があります。たとえば、
Foo
がフィールド宣言とメソッド宣言に適用可能である場合、FooContainer
がフィールド宣言にのみ適用可能である(@Foo
をメソッド宣言で繰り返すことができない)とすると、FooContainer
はFoo
の包含注釈型インタフェースとして正当に機能できます。しかし、FooContainer
が仮パラメータ宣言にのみ適用可能であるとすると、@Foo
が繰り返される一部のプログラム要素で@FooContainer
を暗黙的に宣言できないため、FooContainer
は、Foo
による包含注釈型インタフェースの選択肢として適切ではありません。同様に、
Foo
がフィールド宣言とメソッド宣言に適用可能である場合、FooContainer
がフィールド宣言とパラメータ宣言に適用可能であるとすると、FooContainer
はFoo
の包含注釈型インタフェースとして正当に機能できません。プログラム要素の交差を使用して、フィールド宣言でのみFoo
を繰返し可能にすることもできますが、FooContainer
に他のプログラム要素が存在することは、FooContainer
がFoo
の包含注釈型インタフェースとして設計されていないことを示します。したがって、Foo
がそれに依存することは危険です。
例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定数に対応しています。
モジュール宣言(7.7)
java.lang.annotation.ElementType.MODULE
に対応していますパッケージ宣言(7.4.1)
java.lang.annotation.ElementType.PACKAGE
に対応しています型宣言: クラス、インタフェース、enumおよび注釈
型宣言(8.1.1、9.1.1、8.5、9.5、8.9、9.6)java.lang.annotation.ElementType.TYPE
に対応していますまた、注釈
型宣言はjava.lang.annotation.ElementType.ANNOTATION_TYPE
に対応していますメソッド宣言(注釈
型インタフェースの要素を含む) (8.4.3、9.4、9.6.1)java.lang.annotation.ElementType.METHOD
に対応していますコンストラクタ宣言(8.8.3)
java.lang.annotation.ElementType.CONSTRUCTOR
に対応しています汎用クラス、インタフェース、メソッドおよびコンストラクタの型パラメータ宣言(8.1.2、9.1.2、8.4.4、8.8.4)
java.lang.annotation.ElementType.TYPE_PARAMETER
に対応していますフィールド宣言(enum定数を含む) (8.3.1、9.3、8.9.1)
java.lang.annotation.ElementType.FIELD
に対応しています仮パラメータおよび例外パラメータ宣言(8.4.1、9.4、14.20)
java.lang.annotation.ElementType.PARAMETER
に対応していますローカル変数宣言(
for
文のループ変数およびtry
-with-resources文のリソース変数を含む) (14.4、14.14.1、14.14.2、14.20.3)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
に対応する(メタ)注釈mがTに含まれている場合、次のようになります。
値が
java.lang.annotation.RetentionPolicy.SOURCE
である要素をmが持つ場合、Javaコンパイラは、aが出現するクラスまたはインタフェースのバイナリ表現にaが存在しないことを確認する必要があります。値が
java.lang.annotation.RetentionPolicy.CLASS
またはjava.lang.annotation.RetentionPolicy.RUNTIME
である要素をmが持つ場合、Javaコンパイラは、aが出現するクラスまたはインタフェースのバイナリ表現でaが表されていることを確認する必要があります。ただし、aがローカル変数宣言に注釈を付ける場合、またはaがラムダ式の仮パラメータ宣言に注釈を付ける場合は除きます。ローカル変数の宣言、またはラムダ式の仮パラメータの宣言にある注釈がバイナリ表現で保持されることはありません。一方、注釈
型インタフェースで適切な保持ポリシーが指定されている場合、ローカル変数の型、またはラムダ式の仮パラメータの型にある注釈はバイナリ表現で保持されます。注釈
型インタフェースに@Target(java.lang.annotation.ElementType.LOCAL_VARIABLE)
および@Retention(java.lang.annotation.RetentionPolicy.CLASS)
または@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
をメタ注釈として付けることは不正ではありません。値が
java.lang.annotation.RetentionPolicy.RUNTIME
である要素をmが持つ場合、Java SEプラットフォームのリフレクション・ライブラリは、aを実行時に使用できるようにする必要があります。
java.lang.annotation.Retention
に対応する(メタ)注釈mがTに含まれていない場合、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) { ... }
これは完全に正当ですが、クラス
Foo
はObject
からequals
実装を継承するため、微妙なバグの原因となる可能性があります。
型クラスまたはインタフェースTのメソッド宣言に@Override
の注釈が付けられているが、そのメソッドが、Tのスーパー・タイプで宣言されたメソッドをTからオーバーライドしない場合(8.4.8.1、9.4.1.1)、またはObject
のpublic
メソッドとオーバーライド等価でない場合(4.3.2、8.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.hashCode
をFoo
からオーバーライドするため、クラス宣言での@Override
の使用は、最初の条項によって正当です。インタフェース宣言については、インタフェースが
Object
をスーパー・タイプとして持たず、Object
のpublic
メンバーに対応するpublic
abstract
メンバーをインタフェースが持つとします(9.2)。インタフェースでそれらを明示的に宣言する(つまり、Object
のpublic
メソッドとオーバーライド等価であるメンバーを宣言する)ことを選択した場合、インタフェースはそれらをオーバーライドするとみなされ、@Override
の使用は許可されます。一方、
clone
メソッドで@Override
を使用しようとするインタフェースを考えてみましょう。(この例では、finalize
を使用することもできます)interface Quux { @Override Object clone(); }
Object.clone
はpublic
でないため、Quux
で暗黙的に宣言されたclone
というメンバーはありません。したがって、Quux
内のclone
の明示的な宣言は他のメソッドを実装するとみなされず、@Override
の使用は誤りです。(Quux.clone
はpublic
であるという事実は関係ありません。)対照的に、
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種類の警告が定義されています。
未チェック警告(4.8、5.1.6、5.1.9、8.4.1、8.4.8.3、15.12.4.2、15.13.2、15.27.3)は、文字列"
unchecked
"によって指定されます。非推奨警告(9.6.4.6)は、文字列"
deprecation
"によって指定されます。削除警告(9.6.4.6)は、文字列"
removal
"によって指定されます。プレビュー警告(1.5)は、文字列"
preview
"によって指定されます。
他の文字列は、標準以外の警告を指定します。Javaコンパイラは、認識しないこのような文字列を無視する必要があります。
コンパイラ・ベンダーは、
@SuppressWarnings
についてサポートする文字列を文書化することと、同じ文字列が複数のコンパイラにわたって認識されるようにするために協力し合うことを奨励されています。
9.6.4.6 @Deprecated
プログラマは、特定のプログラム要素(モジュール、型クラス、インタフェース、フィールド、メソッドおよびコンストラクタ)について、それらが危険であったり、より効果的な代替手段が存在するために使用を推奨されないことがあります。注釈型インタフェースDeprecated
を使用すると、そうしたプログラム要素の使用についてコンパイラで警告できます。
非推奨のプログラム要素は、宣言に@Deprecated
の注釈が付けられているモジュール、型クラス、インタフェース、フィールド、メソッドまたはコンストラクタです。プログラム要素の非推奨の形態は、注釈のforRemoval
要素の値によって異なります。
forRemoval=false
(デフォルト)の場合、プログラム要素は通常の非推奨です。通常の非推奨プログラム要素は、将来のリリースで削除されるものではありませんが、それでも、プログラマはその使用から移行する必要があります。
forRemoval=true
の場合、プログラム要素は廃止予定の非推奨です。廃止予定の非推奨プログラム要素は、将来のリリースで削除されるものです。プログラマは、その使用を中止する必要があります。そうしないと、新しいリリースにアップグレードするときに、ソースおよびバイナリの非互換性(13.2)のリスクがあります。
プログラム要素の宣言で(明示的に宣言されているか、暗黙的に宣言されているかに関係なく)通常の非推奨プログラム要素が使用されている(オーバーライドされている、呼び出されている、または名前で参照されている)場合、Javaコンパイラは非推奨警告を生成する必要があります。ただし、次の場合は除きます。
それ自体が非推奨(通常または廃止予定)である宣言内で使用されています。あるいは
非推奨警告を抑制するように注釈が付けられている(9.6.4.5)宣言内で使用されています。あるいは
使用が出現する宣言と通常の非推奨プログラム要素の宣言の両方が同じ最も外側のクラス内にあります。あるいは
通常の非推奨型またはメンバーをインポートする
import
宣言内で使用されています。あるいはexports
またはopens
ディレクティブ(7.7.2)内で使用されています。
プログラム要素の宣言で(明示的に宣言されているか、暗黙的に宣言されているかに関係なく)廃止予定の非推奨プログラム要素が使用されている(オーバーライドされている、呼び出されている、または名前で参照されている)場合、Javaコンパイラは削除警告を生成する必要があります。ただし、次の場合は除きます。
削除警告を抑制するように注釈が付けられている(9.6.4.5)宣言内で使用されています。あるいは
使用が出現する宣言と廃止予定の非推奨プログラム要素の宣言の両方が同じ最も外側のクラス内にあります。あるいは
廃止予定の非推奨
型クラス、インタフェースまたはメンバーをインポートするimport
宣言内で使用されています。あるいはexports
またはopens
ディレクティブ内で使用されています。
廃止予定の非推奨には十分な緊急性があるため、廃止予定の非推奨要素が使用されていると、使用している側の要素自体が非推奨であっても、削除警告が生成されます。両方の要素が同時に削除されるという保証がないためです。警告を閉じて要素の使用を続行するには、プログラマは、
@SuppressWarnings
注釈を使用して、そのリスクを手動で確認する必要があります。
次の場合、非推奨警告または削除警告は生成されません。
ローカル変数または仮パラメータが使用されています(名前で参照されています)。ローカル変数または仮パラメータの宣言に
@Deprecated
の注釈が付けられている場合も含みます。パッケージの名前が使用されています(修飾された型名、
import
宣言、あるいはexports
またはopens
ディレクティブによって参照されています)。パッケージの宣言に@Deprecated
の注釈が付けられている場合も含みます。モジュールの名前が、修飾された
exports
またはopens
ディレクティブによって使用されています。フレンド・モジュールの宣言に@Deprecated
の注釈が付けられている場合も含みます。
パッケージをエクスポートする、または開くモジュール宣言は、通常、パッケージの宣言を制御するのと同じプログラマまたはチームによって制御されます。そのため、パッケージがモジュール宣言によってエクスポートされたり、開かれたときに、パッケージ宣言に
@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
メタ注釈は、TCをTの包含注釈型インタフェースにするには十分ではありません。TCがTの包含注釈型インタフェースとみなされるための様々な整形式ルールがあります。
9.6.4.9 @FunctionalInterface
注釈型インタフェースFunctionalInterface
は、インタフェースが機能インタフェース(9.8)として想定されていることを示す場合に使用します。これにより、機能すると想定されているインタフェースに出現したり、継承される不適切なメソッド宣言の早期検出が容易になります。
インタフェース宣言に@FunctionalInterface
の注釈が付けられているが、実際は機能インタフェースでない場合、コンパイル時にエラーが発生します。
インタフェースの中には付随的に機能するものもあるため、機能インタフェースのすべての宣言に@FunctionalInterface
の注釈を付けることは必要ないか、望ましくありません。
第13章: バイナリ互換性
13.1 バイナリの形式
プログラムは、Java仮想マシン仕様、Java SE 14 Editionによって規定されているclass
ファイル形式に、またはJavaプログラミング言語で作成されたクラス・ローダーによってこの形式にマップできる表現にコンパイルする必要があります。
クラスまたはインタフェース宣言に対応するclass
ファイルには、特定の特性が必要です。これらの特性の多くは、バイナリの互換性を確保するソース・コードの変換をサポートするように明確に選択されています。必須特性は次のとおりです。
クラスまたはインタフェースはバイナリ名によって名前を付ける必要があり、これは次の制約を満たす必要があります。
メンバー
型・クラスまたはインタフェース(8.5、9.5)のバイナリ名は、それを直接包含する型クラスまたはインタフェースのバイナリ名、その後に続く$
、その後に続くメンバーの単純名で構成されます。ローカル・クラス(14.3)のバイナリ名は、それを直接包含する
型クラスまたはインタフェースのバイナリ名、その後に続く$
、その後に続く空でない一連の数字、その後に続くローカル・クラスの単純名で構成されます。無名クラス(15.9.5)のバイナリ名は、それを直接包含する
型クラスまたはインタフェースのバイナリ名、その後に続く$
、その後に続く空でない一連の数字で構成されます。汎用クラスまたはインタフェース(8.1.2、9.1.2)によって宣言される型変数のバイナリ名は、それを直接包含する
型クラスまたはインタフェースのバイナリ名、その後に続く$
、その後に続く型変数の単純名で構成されます。汎用メソッド(8.4.4)によって宣言される型変数のバイナリ名は、そのメソッドを宣言する
型クラスまたはインタフェースのバイナリ名、その後に続く$
、その後に続くメソッドの記述子(JVMS §4.3.3)、その後に続く$
、その後に続く型変数の単純名で構成されます。汎用コンストラクタ(8.8.4)によって宣言される型変数のバイナリ名は、そのコンストラクタを宣言する
型クラスのバイナリ名、その後に続く$
、その後に続くコンストラクタの記述子(JVMS §4.3.3)、その後に続く$
、その後に続く型変数の単純名で構成されます。
別のクラスまたはインタフェース
型への参照は、型クラスまたはインタフェースのバイナリ名を使用するシンボリックである必要があります。定数変数(4.12.4)であるフィールドへの参照は、コンパイル時に定数変数のイニシャライザが示す値Vに解決される必要があります。
このようなフィールドが
static
である場合、バイナリ・ファイルのコード内にフィールドへの参照が存在しない必要があります。これには、このフィールドを宣言したクラスまたはインタフェースも含まれます。このようなフィールドは常に、初期化(12.4.2)されているものとして示される必要があり、このフィールドのデフォルトの初期値は(Vとは異なる場合)、表示されないようにします。このようなフィールドが非
static
である場合、バイナリ・ファイルのコード内にフィールドへの参照が存在しない必要があります。ただし、このフィールドが含まれるクラス内は除きます。(インタフェースにはstatic
フィールドしかないため、これはインタフェースではなくクラスです。)このクラスには、インスタンス作成時(12.5)にフィールドの値をVに設定するためのコードが必要です。クラスC内でのフィールド・アクセスを示す正当な式が与えられ、定数値ではなく、(場合によっては別個の)クラスまたはインタフェースD内で宣言されているfという名前のフィールドを参照している場合、Oracleではフィールド参照の修飾型を次のように定義します。
式が単純名によって参照されているときに、fが現在のクラスまたはインタフェースCのメンバーである場合、TをCにします。それ以外の場合、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へのシンボリック参照にコンパイルする必要があります。また、型が想定どおりであるかどうかを検証者がチェックできるように、この参照には、宣言されたフィールドの型のイレイジャへのシンボリック参照も含まれている必要があります。
クラスまたはインタフェースC内のメソッド呼出し式またはメソッド参照式が与えられ、(場合によっては別個の)クラスまたはインタフェースD内で宣言されている(または暗黙的な宣言されている(9.2)) mという名前のメソッドを参照している場合、Oracleではメソッド呼出しの修飾型を次のように定義します。
Dが
Object
である場合、式の修飾型はObject
です。そうでない場合は、次のようになります。
メソッドが単純名によって参照されているときに、mが現在のクラスまたはインタフェースCのメンバーである場合、TをCとします。それ以外の場合、Tは、mがメンバーである最も内側の字句的な包含
型クラスまたはインタフェース宣言であるとします。どちらの場合も、Tがメソッド呼出しの修飾型です。式の形式がTypeName
.
mまたはReferenceType::
mである場合、TypeNameまたはReferenceTypeが示す型がメソッド呼出しの修飾型です。式の形式がExpressionName
.
m、Primary.
m、ExpressionName::
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
を宣言され、値を戻さないことを示すものが含まれている必要があります。クラス・インスタンス作成式(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)へのシンボリック参照に解決する必要があります。コンストラクタのシグネチャには、次の両方が含まれている必要があります。
コンストラクタのパラメータ数
各仮パラメータの型へのシンボリック参照
クラスまたはインタフェースのバイナリ表現には、次もすべて含まれる必要があります。
これがクラスであり、
Object
ではない場合、このクラスの直接スーパークラスのイレイジャへのシンボリック参照。それぞれの直接スーパーインタフェースのイレイジャへのシンボリック参照(存在する場合)。
フィールドの単純名およびフィールドの型のイレイジャへのシンボリック参照として与えられた、クラスまたはインタフェース内で宣言された各フィールドの仕様。
これがクラスである場合、各コンストラクタの消去されたシグネチャ(前述を参照)。
クラスまたはインタフェース内で宣言された各メソッド(インタフェースについては、暗黙的に宣言されたメソッドを除く(9.2))ごとに、消去されたシグネチャおよび戻り型(前述を参照)。
クラスまたはインタフェースを実装するために必要なコード。
それぞれの
型クラスまたはインタフェースに、その正規名(6.7)を回復するための十分な情報が含まれている必要があります。それぞれのメンバー
型・クラスまたはインタフェースに、そのソースレベルのアクセス修飾子を回復するための十分な情報が含まれている必要があります。それぞれのネストしたクラス
およびネストしたまたはインタフェースに、それを直接包含する型クラスまたはインタフェース(8.1.3)へのシンボリック参照が含まれている必要があります。それぞれのクラスまたはインタフェースに、そのメンバー
型・クラスおよびインタフェース(8.5、9.5)すべてへのシンボリック参照と、そのメソッド、コンストラクタ、静的イニシャライザ、インスタンス・イニシャライザおよびフィールド・イニシャライザに出現するローカル・クラスと無名クラスその本体内で宣言された他のネストしたクラスおよびインタフェースすべてへのシンボリック参照が含まれている必要があります。すべてのインタフェースに、すべてのメンバー型(9.5)へのシンボリック参照、およびデフォルトのメソッドとフィールド・イニシャライザ内に出現するローカル・クラスと無名クラスへのシンボリック参照が含まれている必要があります。Javaコンパイラによって発行されたコンストラクトは、発行されたコンストラクトがクラス初期化メソッドでないかぎり、ソース・コードで明示的または暗黙的に宣言されたコンストラクトに対応していない場合、合成としてマークする必要があります(JVMS §2.9)。
Javaコンパイラによって発行されたコンストラクトは、ソース・コードで暗黙的に宣言された仮パラメータに対応している場合、必須としてマークする必要があります(8.8.1、8.8.9、8.9.3、15.9.5.1)。
次の仮パラメータは、ソース・コードで暗黙的に宣言されます。
参考までに、次のコンストラクタは、ソース・コードでは暗黙的に宣言されますが、
class
ファイル内で必須としてマークできるのは仮パラメータのみであるため、必須としてはマークされません(JVMS §4.7.24)。
モジュール宣言に対応するclass
ファイルには、バイナリ名がmodule-info
であり、スーパークラス、スーパーインタフェース、フィールドおよびメソッドを持たないクラスのclass
ファイルのプロパティが必要です。また、このモジュールのバイナリ表現には次のものがすべて含まれている必要があります。
module
の後ろに示す名前へのシンボリック参照として与えられたモジュールの名前の仕様。また、仕様では、モジュールが標準とオープンのどちらであるかも示す必要があります(7.7)。requires
ディレクティブが示すモジュールの名前へのシンボリック参照として与えられ、このディレクティブによって示される各依存の仕様(7.7.1)。また、仕様では、依存がtransitive
であるかどうか、および依存がstatic
であるかどうかも示す必要があります。exports
またはopens
ディレクティブが示すパッケージの名前へのシンボリック参照として与えられ、これらのディレクティブが示す各パッケージの仕様(7.7.2)。また、ディレクティブが修飾されていた場合、仕様では、ディレクティブのto
句が示すモジュールの名前へのシンボリック参照を与える必要があります。uses
ディレクティブによって示される型クラスまたはインタフェースの名前へのシンボリック参照として指定される、このディレクティブが示す各サービスの仕様(7.7.3)。provides
ディレクティブのwith
句によって示される型クラスおよびインタフェースの名前へのシンボリック参照として指定される、このディレクティブが示すサービス・プロバイダの仕様(7.7.4)。また、この仕様では、このディレクティブによってサービスとして示される型クラスまたはインタフェースの名前へのシンボリック参照を指定する必要があります。
次の各セクションでは、既存のバイナリとの互換性を損なわずにクラスおよびインタフェース型宣言に加えることができる変更について説明します。前述の翻訳要件に基づき、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
という形式の式の値は、this
のn番目の字句的な包含インスタンスです。
式の型はTです。
クラスTの内部クラスまたはT自体でないクラスまたはインタフェースにこの式が出現する場合、コンパイル時にエラーが発生します。
15.9 クラス・インスタンス作成式
15.9.2 包含インスタンスの特定
Cはインスタンス化されるクラス、iは作成されるインスタンスであるとします。Cが内部クラスである場合、iには、直接包含するインスタンス(8.1.3)がある可能性があり、次のように特定されます。
Cが無名クラスである場合は、次のようになります。
クラス・インスタンス作成式が静的コンテキストに出現する場合、iには、直接包含するインスタンスはありません。
そうでない場合、iを直接包含するインスタンスは
this
です。
Cがローカル・クラスである場合は、次のようになります。
Cが静的コンテキストに出現する場合、iには、直接包含するインスタンスはありません。
そうでない場合、クラス・インスタンス作成式が静的コンテキストに出現すると、コンパイル時にエラーが発生します。
そうでない場合、Oは、Cを直接包含するクラスまたはインタフェース宣言であるとします。nは、Oが、クラス・インスタンス作成式が出現するクラスまたはインタフェースのn番目の字句的な包含
型クラスまたはインタフェース宣言であるような整数であるとします。iを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。
Cが内部メンバー・クラスである場合は、次のようになります。
クラス・インスタンス作成式が修飾されていない場合、次のようになります。
クラス・インスタンス作成式が静的コンテキストに出現する場合、コンパイル時にエラーが発生します。
そうでない場合、Cが、クラス・インスタンス作成式が出現するクラスまたはインタフェースを包含するクラスのメンバーである場合、Oは、Cがメンバーである、直接包含するクラスであるとします。nは、Oが、クラス・インスタンス作成式が出現するクラスまたはインタフェースのn番目の字句的な包含
型クラスまたはインタフェース宣言であるような整数であるとします。iを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。そうでない場合、コンパイル時にエラーが発生します。
クラス・インスタンス作成式が修飾されている場合、iを直接包含するインスタンスは、Primary式またはExpressionNameの値であるオブジェクトです。
Cが無名クラスで、かつその直接スーパークラスSが内部クラスである場合、iは、Sに関して直接包含するインスタンスを持つ可能性があり、次のように判別されます。
Sがローカル・クラスである場合は、次のようになります。
Sが静的コンテキストに出現する場合、iには、Sに関して直接包含するインスタンスはありません。
そうでない場合、クラス・インスタンス作成式が静的コンテキストに出現すると、コンパイル時にエラーが発生します。
そうでない場合、Oは、Sを直接包含する
型クラスまたはインタフェース宣言であるとします。nは、Oが、クラス・インスタンス作成式が出現するクラスまたはインタフェースのn番目の字句的な包含型クラスまたはインタフェース宣言であるような整数であるとします。Sに関してiを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。
Sが内部メンバー・クラスである場合は、次のようになります。
クラス・インスタンス作成式が修飾されていない場合、次のようになります。
クラス・インスタンス作成式が静的コンテキストに出現する場合、コンパイル時にエラーが発生します。
そうでない場合、Sが、クラス・インスタンス作成式が出現するクラスまたはインタフェースを包含するクラスのメンバーである場合、Oは、Sがメンバーである、直接包含するクラスであるとします。nは、Oが、クラス・インスタンス作成式が出現するクラスまたはインタフェースのn番目の字句的な包含
型クラスまたはインタフェース宣言であるような整数であるとします。Sに関してiを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。そうでない場合、コンパイル時にエラーが発生します。
クラス・インスタンス作成式が修飾されている場合、Sに関してiを直接包含するインスタンスは、Primary式またはExpressionNameの値であるオブジェクトです。
15.12 メソッド呼出し式
15.12.1 コンパイル時のステップ1: 検索するクラスまたはインタフェースの特定
コンパイル時にメソッド呼出しを処理するときの最初のステップは、呼び出されるメソッドの名前と、その名前のメソッドの定義を検索するクラスまたはインタフェースを特定することです。
メソッドの名前は、MethodInvocationの左カッコの直前にあるMethodNameまたはIdentifierによって指定されます。
検索するクラスまたはインタフェースについては、MethodInvocationの左カッコの前にある形式に応じて、次の6つのケースを検討します。
形式がMethodName、つまり単にIdentifierである場合は、次のようになります。
Identifierが、その名前を持つメソッド宣言のスコープ内に出現する場合(6.3、6.4.1)、次のようになります。
そのメソッドがメンバーである包含
型クラスまたはインタフェース宣言がある場合、Tは、最も内側のそのような型クラスまたはインタフェース宣言であるとします。検索するクラスまたはインタフェースは、Tです。この検索ポリシーは、コーム・ルールと呼ばれます。実質的には、ネストしたクラスのスーパークラス階層内でメソッドを検索してから、包含クラスおよびそのスーパークラス階層内でメソッドを検索します。例については、6.5.7.1を参照してください。
それ以外の場合、1つ以上の単一静的インポートまたはオンデマンド静的インポート宣言により、メソッド宣言はスコープ内にある可能性があります。呼び出されるメソッドは後で特定されるため(15.12.2.1)、検索するクラスまたはインタフェースはありません。
形式がTypeName
.
[TypeArguments] Identifierである場合、検索する型は、TypeNameが示す型です。形式がExpressionName
.
[TypeArguments] Identifierである場合、検索するクラスまたはインタフェースは、ExpressionNameが示す変数の宣言された型Tであるか(Tがクラスまたはインタフェース型である場合)、Tの上限です(Tが型変数である場合)。形式がPrimary
.
[TypeArguments] Identifierである場合、Tは、Primary式の型であるとします。検索するクラスまたはインタフェースは、Tであるか(Tがクラスまたはインタフェース型である場合)、Tの上限です(Tが型変数である場合)。Tが参照型でない場合は、コンパイル時にエラーが発生します。
形式が
super
.
[TypeArguments] Identifierである場合、検索するクラスは、宣言にメソッド呼出しが含まれているクラスのスーパークラスです。Tは、メソッド呼出しを直接包含する
型クラスまたはインタフェース宣言であるとします。TがクラスObject
であるか、Tがインタフェースである場合は、コンパイル時にエラーが発生します。形式がTypeName
.
super
.
[TypeArguments] Identifierである場合は、次のようになります。TypeNameがクラスもインタフェースも示さない場合、コンパイル時にエラーが発生します。
TypeNameがクラスCを示す場合、検索するクラスはCのスーパークラスです。
Cが現在のクラスまたはインタフェースの字句的な包含
型クラスまたはインタフェース宣言でない場合、あるいはCがクラスObject
である場合は、コンパイル時にエラーが発生します。Tは、メソッド呼出しを直接包含する
型クラスまたはインタフェース宣言であるとします。TがクラスObject
である場合は、コンパイル時にエラーが発生します。それ以外の場合、TypeNameは、検索するインタフェースIを示します。
Tは、メソッド呼出しを直接包含する
型クラスまたはインタフェース宣言であるとします。IがTの直接スーパーインタフェースでない場合、またはTに他の直接スーパークラスまたは直接スーパーインタフェースJが存在し、JがIのサブタイプであるような場合は、コンパイル時にエラーが発生します。
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)である必要があります。そうでない場合はコンパイル時にエラーが発生します。このようなメソッド呼出しは値を生成しないため、値が不要な状況でのみ使用する必要があります。
さらに、コンパイル時宣言が適切であるかどうかは、次のように、左カッコの前にあるメソッド呼出し式の形式によって異なることがあります。
形式がMethodName、つまり単にIdentifierであり、かつコンパイル時宣言がインスタンス・メソッドである場合は、次のようになります。
メソッド呼出しが静的コンテキストに出現する場合(8.1.3)、コンパイル時にエラーが発生します。
それ以外の場合、Cは、コンパイル時宣言がメンバーである、直接包含するクラスであるとします。メソッド呼出しがCまたはCの内部クラスによって直接包含されていない場合は、コンパイル時にエラーが発生します。
形式がTypeName
.
[TypeArguments] Identifierである場合、コンパイル時宣言はstatic
である必要があります。そうでない場合はコンパイル時にエラーが発生します。形式がExpressionName
.
[TypeArguments] IdentifierまたはPrimary.
[TypeArguments] Identifierである場合、コンパイル時宣言は、インタフェースで宣言されたstatic
メソッドでないことが必要です。そうである場合はコンパイル時にエラーが発生します。形式が
super
.
[TypeArguments] Identifierである場合は、次のようになります。コンパイル時宣言が
abstract
である場合、コンパイル時にエラーが発生します。メソッド呼出しが静的コンテキストに出現する場合、コンパイル時にエラーが発生します。
形式がTypeName
.
super
.
[TypeArguments] Identifierである場合は、次のようになります。コンパイル時宣言が
abstract
である場合、コンパイル時にエラーが発生します。メソッド呼出しが静的コンテキストに出現する場合、コンパイル時にエラーが発生します。
TypeNameがクラスCを示す場合、メソッド呼出しがCまたはCの内部クラスによって直接包含されていない場合は、コンパイル時にエラーが発生します。
TypeNameがインタフェースを示す場合、Tは、メソッド呼出しを直接包含する
型クラスまたはインタフェース宣言であるとします。コンパイル時宣言をTの直接スーパークラスまたは直接スーパーインタフェースからオーバーライドする(9.4.1)、コンパイル時宣言とは異なるメソッドが存在する場合、コンパイル時にエラーが発生します。親インタフェースの親で宣言されたメソッドをスーパーインタフェースがオーバーライドする場合、このルールでは、親の親を直接スーパーインタフェースのそのリストに単に追加することによって、子インタフェースがオーバーライドをスキップすることが回避されます。親の親の機能にアクセスするための適切な方法は、直接スーパーインタフェースを介して、そのインタフェースが目的の動作を公開することを選択する場合にのみ、アクセスすることです。(また、プログラマは、
super
メソッド呼出しで目的の動作を公開する追加のスーパーインタフェースを自由に定義できます。)
...