目次
&&||? :switch式プログラムでの作業の多くは、式を評価することによって行われます。これは、変数への代入などの副作用のため、またはそれらの値(より大きな式の引数またはオペランドとして使用可能)、または文の実行順序(あるいはその両方)に影響するためです。
この章では、式の意味と評価のルールを指定します。
プログラム内の式が評価済(実行済)の場合、結果は次の3つのうちのいずれかを示します。
式が変数を示し、さらに評価するために値が必要な場合は、その変数の値が使用されます。 このコンテキストでは、式が変数または値を示す場合、式の値を単純に表すことができます。
式は、値を返さないメソッド、つまりvoid (§8.4)を宣言したメソッドを呼び出すメソッド呼出し(§15.12)の場合にのみ、何も示しません。 このような式は、式文(§14.8)またはラムダ本体の単一式(§15.27.2)としてのみ使用できます。これは、式が出現する他のすべてのコンテキストでは、式が何かを示す必要があるためです。 メソッド呼出しである式文またはラムダ本体でも、結果を生成するメソッドを呼び出すことができます。この場合、メソッドによって戻される値は静かに破棄されます。
式には埋込み割当て、増分演算子、減分演算子、メソッド呼出し、およびswitch式では任意の文が含まれる場合があるため、式を評価すると副作用が発生する可能性があります。
式は次のいずれかで発生します。
宣言されているクラスまたはインタフェースの宣言: フィールド・イニシャライザ、静的イニシャライザ、インスタンス・イニシャライザ、コンストラクタ宣言、メソッド宣言または注釈内。
モジュール、パッケージ、または最上位のクラスまたはインタフェースの宣言に関する注釈。
式は、次の構文形式に広く分類できます。
演算子間の優先順位は、文法プロダクションの階層によって管理されます。 最下位の優先順位演算子は、ラムダ式の矢印(->)の後に代入演算子が続きます。 したがって、すべての式は、LambdaExpressionおよびAssignmentExpression非ターミナルに構文的に含まれます。
一部の式が特定のコンテキストに表示される場合、多式とみなされます。 次の形式の式を多式にすることができます。
これらのフォームのいずれかの式がポリ式であるかどうかを判断するルールは、これらの式の形式を指定する個々のセクションで指定されます。
ポリ式ではない式は、スタンドアロン式です。 スタンドアロン式は、ポリ式ではないと判断された場合の前述の形式の式であり、他のすべての形式のすべての式です。 他のすべての形式の式には、スタンドアロン・フォームがあると言われています。
一部の式には、コンパイル時に決定できる値があります。 これらは定数式(§15.29)です。
式が変数または値を示す場合、式にはコンパイル時に認識される型があります。 スタンドアロン式のタイプは、式の内容から完全に決定できます。一方、ポリ式のタイプは、式のターゲット・タイプ(§5 (Conversions and Contexts))の影響を受ける場合があります。 式のタイプを決定するルールは、式のタイプごとに個別に後述します。
式の値は、ヒープ汚染が発生しないかぎり、式の型と代入互換(§5.2)です(§4.12.2)。
同様に、変数に格納される値は、ヒープ汚染が発生しないかぎり、常に変数の型と互換性があります。
つまり、型が Tの式の値は、T型の変数への代入に常に適しています。
式の型がクラスCを指定するクラス型である場合、クラスCをfinalまたはsealed (§8.1.1.2)として宣言すると、式の値に意味があることに注意してください。
Cがfinalの場合、式は、(i) NULL参照、または(ii)クラスがC自体であるオブジェクトのいずれかの値を持つことが保証されます。これは、finalクラスにはサブクラスがないためです。
Cがsealedの場合、式は、(i) NULL参照、(ii)クラスがC自体であるオブジェクト、または(iii) C (§8.1.6)の許可された直接サブクラスのいずれかと互換性のある代入のいずれかの値を持つことが保証されます。
Cが自由に拡張可能な場合、式には(i) NULL参照、(ii)クラスが C自体であるオブジェクト、または(iii) Cと互換性のある代入のいずれかの値が指定されていることが保証されます。
浮動小数点式は、型がfloatまたはdouble (§4.2.3)の式です。 float型の浮動小数点式は、32ビットIEEE 754 binary32形式で表される値に正確に対応する値を示します。 double型の浮動小数点式は、64ビットIEEE 754 binary64形式で表される値に正確に対応する値を示します。
浮動小数点式を形成するために使用できるJavaプログラミング言語の比較演算子および数値演算子の多くは、浮動小数点値に作用する変換と同様に、IEEE 754操作に対応しています(表15.4-A)。
表15.4-A. IEEE 754操作への対応
| オペレータ/変換 | IEEE 754操作 |
|---|---|
数値比較演算子<、<=、>および>= (§15.20.1)
|
compareQuietLess, compareQuietLessEqual, compareQuietGreater, compareQuietGreaterEqual |
数値等価演算子==および!= (§15.21.1)
|
compareQuietEqual、compareQuietNotEqual |
単項マイナス演算子- (§15.15.4)
|
negate |
乗法演算子*および/(§15.17.1、§15.17.2)
|
乗算、除算 |
加算演算子+および- (§15.18.2)
|
加算、減算 |
| 整数型からのプリミティブ変換の拡大(§5.1.2) | convertFromInt |
| 整数型へのプリミティブ変換の絞込み(§5.1.3) | convertToIntegerTowardZero |
floatとdoubleの間の変換 |
convertFormat |
浮動小数点剰余演算子 % (§15.17.3)は、IEEE 754剰余演算に対応していません。
Javaプログラミング言語の対応する演算子のないIEEE 754操作の中には、IEEE 754 squareRoot操作のsqrtメソッド、IEEE 754 fusedMultiplyAdd操作のfmaメソッド、IEEE 754剰余操作のIEEEremainderメソッドなど、MathおよびStrictMathクラスのメソッドを介して提供されるものがあります。
Javaプログラミング言語では、IEEE 754の非正規浮動小数点数と段階的アンダーフローのサポートが必要です。これにより、特定の数値アルゴリズムの望ましいプロパティを簡単に証明できます。 計算結果が非正規数である場合、浮動小数点演算は「ゼロにフラッシュ」されません。
Javaプログラミング言語の浮動小数点演算子の結果は、同じオペランドに対する対応するIEEE 754操作の結果と一致する必要があります。 有限の結果の場合、これは浮動小数点結果の符号、重要度、および指数がすべてIEEE 754で指定されているものである必要があることを意味します。
符号、重要度、および指数の一致の要件により、浮動小数点の動作の精度が低い場合に許容される変換が除外されます。 たとえば、xが-0.0の場合、結果の符号は異なるため、通常、-xを(0.0 - x)に置き換えることはできません。 また、(a * b + c)を融合された乗算累計ライブラリ・メソッドへのコールに置き換えるなど、場合によっては値を変更する他の変換は、結果が同一であることが証明されないかぎり許可されません。
浮動小数点式の評価で、式の型で示される精度または指数範囲が大きい中間結果を使用できる状況はありません。
オーバーフローする浮動小数点演算は、符号付き無限大を生成します。
アンダーフローする浮動小数点演算は、非正規値または符号付きゼロを生成します。
一意の数学的に定義された結果を持たない浮動小数点演算は NaNを生成します。
NaNをオペランドとするすべての数値演算は、結果として NaNを生成します。
NaNは順序付けられていないため、1つまたは2つのNaNを含む数値比較操作ではfalseが戻され、NaNを含む==比較ではfalseが戻され、NaNを含む!=比較ではtrueが返されます。
浮動小数点演算は、実際の演算への近似です。 実数には無限数がありますが、特定の浮動小数点形式には有限数の値しかありません。 Javaプログラミング言語では、端数処理ポリシーは、特定の形式の実数から浮動小数点値へのマッピングに使用される関数です。 浮動小数点形式の表現可能な範囲の実数の場合、実数行の連続セグメントは単一の浮動小数点値にマップされます。 値が数値的に浮動小数点値に等しい実数は、その浮動小数点値にマップされます。たとえば、実数1.5は、指定された形式の浮動小数点値1.5にマップされます。 Javaプログラミング言語では、次のように2つの丸めポリシーを定義します。
四捨五入の丸めポリシーは、(i)整数値への変換、(ii)浮動小数点の残りを除くすべての浮動小数点演算子に適用されます。 四捨五入から最も近い端数処理ポリシーでは、正確な結果を無限に正確な結果に最も近い表現可能な値に丸める必要があります。最も近い2つの表現可能な値が等しく近い場合は、最下位ビットがゼロの値が選択されます。
最も近い丸めポリシーへの丸めは、IEEE 754、roundTiesToEvenのバイナリ演算のデフォルトの丸め方向属性に対応します。
roundTiesToEvenの丸め方向属性は、1985バージョンのIEEE 754 Standardでは「最も近い丸め」モードと呼ばれていました。 Javaプログラミング言語の丸めポリシーは、この丸めモードにちなんで名前が付けられます。
ゼロに丸める丸めポリシーは、(i)浮動小数点値から整数値への変換(§5.1.3)および(ii)浮動小数点残り(§15.17.3)に適用されます。 ゼロの端数処理ポリシーへの丸めの下では、正確な結果は、無限に正確な結果よりも大きくない最も近い表現可能な値に丸められます。 整数に変換する場合、ゼロへの丸めポリシーは、小数桁ビットが破棄される切り捨てと同等です。
ゼロに向けた丸めポリシーは、IEEE 754でのバイナリ演算の roundTowardZero丸め方向属性に対応します。
roundTowardZeroの丸め方向属性は、IEEE 754 Standardの1985バージョンでは「ゼロに向かって丸める」丸めモードと呼ばれていました。 Javaプログラミング言語の丸めポリシーは、この丸めモードにちなんで名前が付けられます。
Javaプログラミング言語では、すべての浮動小数点演算子が浮動小数点結果を結果の精度に丸める必要があります。 各浮動小数点演算子に使用される丸めポリシーは、前述のように四捨五入するか、ゼロに向かって四捨五入します。
Java 1.0および1.1では、浮動小数点式の厳密な評価が必要でした。 厳密な評価は、各floatオペランドがIEEE 754 binary32形式で表現可能な値に対応し、各doubleオペランドがIEEE 754 binary64形式で表現可能な値に対応し、対応するIEEE 754操作を持つ各浮動小数点演算子が同じオペランドのIEEE 754結果と一致することを意味します。
厳密な評価では予測可能な結果が得られますが、Java 1.0/1.1時代に共通するプロセッサ・ファミリのJava Virtual Machine実装でパフォーマンスの問題が発生しました。 その結果、Java 1.2からJava SE 16では、Java SE Platformにより、Java Virtual Machine実装で、各浮動小数点型に関連付けられた1つまたは2つの値セットを持つことができました。 float型はfloat値セットおよびfloat-extended-exponent値セットに関連付けられ、double型はダブル値セットおよびダブル拡張指数値セットに関連付けられました。 IEEE 754 binary32形式で表現可能な値に対応する浮動小数点値セット。浮動小数点拡張指数値セットの精度ビット数は同じですが、指数範囲が大きくなっています。 同様に、double値セットはIEEE 754 binary64形式で表現可能な値に対応しており、double-extended-exponent値セットの精度ビット数は同じですが、指数範囲は大きくなっています。 拡張指数値セットの使用をデフォルトで許可すると、一部のプロセッサファミリのパフォーマンスの問題が改善されました。
互換性のために、Java 1.2ではプログラマが拡張構成要素値セットの使用による実装の禁止を許可しました。 プログラマは、strictfp修飾子をクラス、インタフェースまたはメソッドの宣言に配置することによってこれを表現しました。strictfpは、float式に設定されたfloat値およびdouble式に設定されたdouble値を使用するように、囲まれた式の浮動小数点セマンティクスを制約し、このような式の結果が完全に予測可能であることを保証しました。 したがって、strictfpによって変更されたコードは、Java 1.0および1.1で指定したものと同じ浮動小数点セマンティクスを持っていました。
Java SE 17以降では、Java SEプラットフォームは常に浮動小数点式の厳密な評価を必要とします。 厳密な評価を実装するパフォーマンスの問題が発生したプロセッサ・ファミリの新規メンバーは、これほど困難ではありません。 この指定では、前述の4つの値セットにfloatおよびdoubleが関連付けられなくなり、strictfp修飾子は浮動小数点式の評価に影響しなくなりました。 互換性のために、strictfpはJava SE 26 (§3.8)のキーワードのままであり、その使用に関する制限(§8.4.3、§9.4)を引き続き持っていますが、Javaコンパイラでは、プログラマに廃止されたステータスについて警告することをお薦めします。 Javaプログラミング言語の将来のバージョンでは、strictfpキーワードを再定義または削除できます。
式の型がプリミティブ型の場合、式の値は同じプリミティブ型になります。
式の型が参照型の場合、参照オブジェクトのクラス、または値がnullではなくオブジェクトへの参照であるかどうかは、コンパイル時に必ずしもわかりません。 Javaプログラミング言語には、参照オブジェクトの実際のクラスが、式の型から推測できない方法でプログラムの実行に影響を与える場所がいくつかあります。 パラメータは次のとおりです。
メソッド呼出し(§15.12)。 呼出しo.m(...)に使用される特定のメソッドは、oの型であるクラスまたはインタフェースの一部であるメソッドに基づいて選択されます。 インスタンス・メソッドの場合、oのランタイム値によって参照されるオブジェクトのクラスが参加します。これは、サブクラスが親クラスですでに宣言されている特定のメソッドをオーバーライドして、このオーバーライド・メソッドが呼び出されるようにできるためです。 (オーバーライド・メソッドは、オーバーライドされた元のmメソッドをさらに呼び出すことを選択することも選択しないこともあります。)
instanceof演算子(§15.20.2)。 型が参照型である式は、instanceofを使用してテストし、式の実行時値によって参照されるオブジェクトのクラスを他の参照型に変換できるかどうかを確認できます。
キャスティング(§15.16)。 オペランド式の実行時の値によって参照されるオブジェクトのクラスは、キャスト演算子で指定された型と互換性がないことがあります。 参照型の場合、実行時に決定された参照オブジェクトのクラスをターゲット型に変換できない場合に、例外をスローする実行時のチェックが必要になることがあります。
参照型の配列コンポーネントへの割当て(§10.5、§15.13、§15.26.1)。 型チェック・ルールでは、SがTのサブタイプである場合に、配列型S[]をT[]のサブタイプとして処理できますが、キャストで実行されるチェックと同様に、配列コンポーネントへの割当てのランタイム・チェックが必要です。
例外処理(§14.20)。 catch句によって例外が捕捉されるのは、スローされた例外オブジェクトのクラスが、catch句の仮パラメータの型であるinstanceofである場合のみです。
オブジェクトのクラスが静的に認識されていない状況では、実行時の型エラーが発生する可能性があります。
また、実行時に静的に認識されいる型が正確でない場合もあります。 そのような状況は、コンパイル時に無検査の警告が発生するプログラムで発生する可能性があります。 このような警告は、静的に安全であると保証できない操作に対応して与えられ、それらには非対応型(§4.7)が含まれるため、直ちに動的チェックの対象にすることはできません。 その結果として、プログラムの実行の後半で動的チェックを行うと、不整合が検出されて実行時の型エラーが発生する可能性があります。
実行時の型エラーは、次の状況でのみ発生する可能性があります:
キャストでは、オペランド式の値によって参照されるオブジェクトの実際のクラスが、キャスト演算子によって指定されたターゲット・タイプ(§5.5、§15.16)と互換性がない場合、この場合、ClassCastExceptionがスローされます。
自動生成されたキャストで、再可能でない型(§4.7)に対する操作の妥当性を保証するために導入されました。
参照型の配列コンポーネントへの代入において、割り当てられる値によって参照されるオブジェクトの実際のクラスが、配列の実際の実行時コンポーネント・タイプ(§10.5、§15.13、§15.26.1)と互換性がない場合、この場合、ArrayStoreExceptionがスローされます。
try文(§14.20)のcatch句で例外が捕捉されない場合、この場合、例外を検出した制御のスレッドは、最初に捕捉されない例外ハンドラ(§11.3)を呼び出して終了します。
すべての式には、特定の計算ステップが実行される通常の評価モードがあります。 次の項では、各種類の式の通常の評価モードについて説明します。
すべてのステップが例外をスローせずに実行された場合、式は正常に完了と呼ばれます。
ただし、式の評価で例外がスローされた場合、その式は突然完了と呼ばれます。 突然の完了には常に関連する理由があります。これは常に、指定された値を持つthrowです。
ランタイム例外は、事前定義された演算子によって次のようにスローされます。
クラス・インスタンス作成式(§15.9.4)、配列作成式(§15.10.2)、メソッド参照式(§15.13.3)、配列イニシャライザ式(§10.6)、文字列連結演算子式(§15.18.1)、またはラムダ式(§15.27.4)は、使用可能なメモリーが不足するとOutOfMemoryErrorをスローします。
配列作成式(§15.10.2)は、ディメンション式の値がゼロより小さい場合にNegativeArraySizeExceptionをスローします。
配列参照式の値がnullの場合、配列アクセス式(§15.10.4)はNullPointerExceptionをスローします。
配列索引式の値が負または配列のlength以上である場合、配列アクセス式(§15.10.4)はArrayIndexOutOfBoundsExceptionをスローします。
フィールド・アクセス式(§15.11)は、オブジェクト参照式の値がnullの場合にNullPointerExceptionをスローします。
インスタンス・メソッドを呼び出すメソッド呼出し式(§15.12)は、ターゲット参照がnullの場合にNullPointerExceptionをスローします。
キャスト式(§15.16)は、実行時にキャストが許容されないことが判明した場合、ClassCastExceptionをスローします。
整数の除算(§15.17.2)または整数の剰余(§15.17.3)演算子は、右側のオペランド式の値がゼロの場合にArithmeticExceptionをスローします。
参照型(§15.26.1)、メソッド呼出し式(§15.12)、接頭辞または接頭辞増分(§15.14.2、§15.15.1)または減分演算子(§15.14.3、§15.15.2)の配列コンポーネントへの代入は、すべてボクシング変換の結果としてOutOfMemoryErrorをスローできます(§5.1.7)。
参照型の配列コンポーネントへの代入(§15.26.1)は、代入される値が配列のコンポーネント・タイプ(§10.5)と互換性がない場合にArrayStoreExceptionをスローします。
セレクタ式の値に適用されるswitchラベルがない場合、switch式(§15.28)または拡張switch文(§14.11.2)はMatchExceptionをスローします。
メソッド起動式では、メソッド本体の実行が突然完了する原因となる例外が発生した場合に、例外がスローされることもあります。
また、コンストラクタの実行が突然完了する例外が発生した場合、クラス・インスタンス作成式によって例外がスローされることもあります。
式の評価中に、様々なリンケージおよび仮想マシン・エラーが発生する場合もあります。 その性質上、このようなエラーは予測が困難であり、処理が困難です。
例外が発生した場合、1つ以上の式の評価は、その通常の評価モードのすべてのステップが完了する前に終了することがあります。このような式は突然完了すると言われます。
式の評価で副式の評価が必要な場合、副式の突然の完了によって、常に同じ理由で式自体が即座に完了し、評価の通常モードでの後続のステップはすべて実行されません。
「完全通常」と「完全突然」という用語は、文の実行にも適用されます(§14.1)。 例外がスローされたためだけでなく、様々な理由で文が突然完了することがあります。
Javaプログラミング言語では、演算子のオペランドが特定の評価順序(つまり、左から右)で評価されるように見えます。
コードはこの仕様に決定的に依存しないことをお薦めします。 コードは通常、各式に最大で1つの副作用が含まれる場合、最も外側の操作として、およびコードが式の左から右への評価の結果として発生する例外に正確に依存しない場合に明確になります。
バイナリ演算子の左側のオペランドは、右側のオペランドのどの部分も評価される前に完全に評価されるようである。
演算子が複合代入演算子(§15.26.2)の場合、左側のオペランドの評価には、左側のオペランドが示す変数を記憶することと、暗黙のバイナリ演算で使用する変数の値をフェッチおよび保存することの両方が含まれます。
バイナリ演算子の左側のオペランドの評価が突然完了した場合、右側のオペランドの一部は評価されていないように見えます。
例15.7.1-1 左側のオペランドが最初に評価される
次のプログラムでは、*演算子には、変数への代入を含む左側のオペランドと、同じ変数への参照を含む右側のオペランドがあります。 参照によって生成される値は、最初に割当てが発生したという事実を反映します。
class Test1 {
public static void main(String[] args) {
int i = 2;
int j = (i=3) * i;
System.out.println(j);
}
}
このプログラムは出力を生成します:
9
*演算子の評価では、9ではなく6を生成することは許可されていません。
例15.7.1-2 複合割当の暗黙的な左辺オペランド
次のプログラムでは、2つの代入文によって、左側のオペランド(9)の値がフェッチされ、加算演算子の右側のオペランドが評価される前に記憶されます。この時点で変数は3に設定されます。
class Test2 {
public static void main(String[] args) {
int a = 9;
a += (a = 3); // first example
System.out.println(a);
int b = 9;
b = b + (b = 3); // second example
System.out.println(b);
}
}
このプログラムは出力を生成します:
12 12
結果6を生成する代入(aの場合は複合、bの場合は単純)には許可されません。
§15.26.2の例も参照してください。
例15.7.1-3 左側のオペランドの評価の突然完了
class Test3 {
public static void main(String[] args) {
int j = 1;
try {
int i = forgetIt() / (j = 2);
} catch (Exception e) {
System.out.println(e);
System.out.println("Now j = " + j);
}
}
static int forgetIt() throws Exception {
throw new Exception("I'm outta here!");
}
}
このプログラムは出力を生成します:
java.lang.Exception: I'm outta here! Now j = 1
つまり、演算子/の左側のオペランドforgetIt()は、右側のオペランドが評価される前に例外をスローし、jへの2の埋込み割当てが発生します。
Javaプログラミング言語では、演算子のすべてのオペランド(条件演算子&&、||および? :を除く)が、操作自体の任意の部分が実行される前に完全に評価されるように見えます。
2項演算子が整数の除算/ (§15.17.2)または整数の剰余% (§15.17.3)である場合、その実行はArithmeticExceptionを発生させる可能性がありますが、この例外は、2項演算子の両方のオペランドが評価され、これらの評価が正常に完了した場合にのみスローされます。
例15.7.2-1 操作前のオペランドの評価
class Test {
public static void main(String[] args) {
int divisor = 0;
try {
int i = 1 / (divisor * loseBig());
} catch (Exception e) {
System.out.println(e);
}
}
static int loseBig() throws Exception {
throw new Exception("Shuffle off to Buffalo!");
}
}
このプログラムは出力を生成します:
java.lang.Exception: Shuffle off to Buffalo!
次のようには指定しません。
java.lang.ArithmeticException: / by zero
分割操作の一部(ゼロ除算例外のシグナリングを含む)は、loseBigの起動が完了する前に発生しない可能性があります。ただし、実装によって除算操作が確実にゼロ除算例外になることが検出または推測できる場合もあります。
Javaプログラミング言語は、カッコで明示的に示され、演算子の優先順位によって暗黙的に示される評価の順序を尊重します。
Javaプログラミング言語の実装では、代入法などの代数的アイデンティティを利用して式をより便利な計算順序に書き換えることはできません。ただし、置換式が次と同等であることが証明される場合を除きます。複数のスレッド(§17 (Threads and Locks)のスレッド実行モデルを使用)が存在する場合でも、関連する可能性のあるすべての計算値について、値とその観察可能な副作用。
浮動小数点計算の場合、このルールは無限大および非数(NaN)値にも適用されます。
たとえば、xまたはyのいずれかがNaNの場合、または両方がNaNの場合、これらの式は異なる値を持つため、!(x<y)をx>=yとしてリライトすることはできません。
具体的には、数学的に連想的であるように見える浮動小数点計算は、計算的に連想的である可能性はほとんどありません。 このような計算は、素直に並べ替えることはできません。
たとえば、Javaコンパイラで4.0*x*0.5を2.0*xとしてリライトするのは正しくありません。ここで丸めが発生しないときは、最初の式が無限大(オーバーフローのため)を生成するが、2番目の式が有限の結果を生成するxの値が大きくなります。
たとえば、テスト・プログラムは次のようになります。
class Test {
public static void main(String[] args) {
double d = 8E307;
System.out.println(4.0 * d * 0.5);
System.out.println(2.0 * d);
}
}
出力:
Infinity 1.6E308
なぜなら、最初の式はオーバーフローし、2番目の式はオーバーフローしないからです。
対照的に、整数の加算と乗算は、Javaプログラミング言語では実質的に連想的です。
たとえば、a、bおよびcがローカル変数であるa+b+c(この簡略化の前提により、複数のスレッドおよびvolatile変数に関する問題が回避されます)は、(a+b)+cまたはa+(b+c)として評価されるかどうかに関係なく、常に同じ回答を生成します。コードの近くに式b+cが存在する場合、スマートJavaコンパイラは、この共通部分正規表現を使用できます。
メソッドまたはコンストラクタの呼出しまたはクラス・インスタンス作成式では、引数式はカンマで区切ってカッコ内に記述できます。 各引数式は、右側の引数式の任意の部分の前に完全に評価されます。
引数式の評価が突然完了した場合、その右側の引数式のどの部分も評価されていないように見えます。
例15.7.4-1 メソッド起動時の評価順序
class Test1 {
public static void main(String[] args) {
String s = "going, ";
print3(s, s, s = "gone");
}
static void print3(String a, String b, String c) {
System.out.println(a + b + c);
}
}
このプログラムは出力を生成します:
going, going, gone
sへの文字列"gone"の代入は、print3への最初の2つの引数の評価後に行われるためです。
例15.7.4-2 引数式の突然の補完
class Test2 {
static int id;
public static void main(String[] args) {
try {
test(id = 1, oops(), id = 3);
} catch (Exception e) {
System.out.println(e + ", id=" + id);
}
}
static int test(int a, int b, int c) {
return a + b + c;
}
static int oops() throws Exception {
throw new Exception("oops");
}
}
このプログラムは出力を生成します:
java.lang.Exception: oops, id=1
idへの3の割当ては実行されないためです。
1次式には、リテラル、オブジェクト作成、フィールド・アクセス、メソッド呼出し、メソッド参照および配列アクセスなど、他のすべての式を構成する最も単純な種類の式の大部分が含まれます。 カッコ付きの式も、構文的にはPrimary式として扱われます。
Javaプログラミング言語の文法のこの部分は、2つの点が異例です。 まず、ローカル変数やメソッド・パラメータの名前などの単純な名前はPrimary式と想定されます。 技術的な理由から、ポストフィックス式が導入されると、名前がプライマリ式とともに少し後でグループ化されます(§15.14)。
この技術的な理由は、トークン・ルックアヘッドが1つのみのJavaプログラムの左から右への解析が可能なことに関係します。 式(z[3])および(z[])について考えてみます。 1つ目はカッコで囲まれた配列アクセス(§15.10.3)で、2つ目はキャストの開始(§15.16)です。 先読み記号が[の時点で、左から右の解析によってzが非端末名前に縮小されます。 キャストのコンテキストでは、名前を「プライマリ」に減らす必要はありませんが、「名前」が「プライマリ」の代替の1つであった場合は、[に続くトークンに対して、2つのトークンを先取りせずに、削減を実行するかどうか(つまり、現在の状況がカッコで囲まれた配列アクセスまたはキャストになるかどうかを確認できませんでした)。 ここに示した文法では、「名前」と「プライマリ」を分離して、他の特定の構文ルール(識別子を直接使用するため、ClassInstanceCreationExpression、MethodInvocation、「ArrayAccess」および「PostfixExpression」のどちらでも使用できるため、この問題を回避できます。FieldAccessは識別子を直接使用するためです)。 この戦略は、より多くのコンテキストを調べるまで、名前をプライマリとして処理する必要があるかどうかという問題を効果的に防ぎます。
2つ目の異常な機能では、式"new int[3][3]"の文法上のあいまいさが回避されます。これは、Javaでは常に多次元配列を1つ作成することを意味しますが、適切な文法的なあいまいさがないと、(new int[3])[3]と同じ意味として解釈されることもあります。
このあいまいさは、Primaryの予期される定義をPrimaryおよびPrimaryNoNewArrayに分割することで解消されます。 (これは、StatementをStatementおよびStatementNoShortIf (§14.5)に分割して、dangling elseの問題を回避するために比較できます。)
リテラル(§3.10)は、変化のない固定値を示します。
ここでは、便宜上、§3.10の次の本番環境を示します。
リテラルの型は次のように決定されます。
字句リテラルの評価は、常に正常に完了します。
クラス・リテラルは、クラス、インタフェース、配列型またはプリミティブ型の名前、または擬似型void、その後に.およびトークンclassが続く式です。
[ ]} . class [ ]} . class boolean {[ ]} . class void . class
TypeNameは、アクセス可能なクラスまたはインタフェース(§6.6)を示す必要があります。 TypeNameがアクセスできないクラスまたはインタフェースを示している場合、または型変数を示している場合は、コンパイル時にエラーが発生します。
C.classの型(Cはクラス、インタフェースまたは配列型の名前(§4.3)は、Class<C>です。
p.classの型(pはプリミティブ型の名前(§4.2))、Class<B>(Bはボクシング変換後の型pの式のタイプ(§5.1.7)です。
void.class (§8.4.5)の型は、Class<Void>です。
クラス・リテラルは、現在のインスタンスのクラスの定義クラス・ローダー(§12.2)で定義されているように、名前付きクラス、インタフェース、配列型またはプリミティブ型(またはvoid)のClassオブジェクトに評価されます。
this
キーワードthisは、次のコンテキストで式として使用できます。
式として使用する場合、キーワードthisは、インスタンス・メソッドが呼び出されたオブジェクト(§15.12)または構築されるオブジェクトへの参照である値を示します。 ラムダ本体でthisで示される値(§15.27.2)は、周囲のコンテキストでthisで示される値と同じです。
キーワードthisは、コンストラクタ呼出し(§8.8.7.1)でも使用され、メソッドまたはコンストラクタの受信者パラメータ(§8.4)を示します。
this式が静的コンテキスト(§8.1.3)で発生した場合、コンパイル時にエラーが発生します。
Cを、this式の最も内側の包含クラスまたはインタフェース宣言にします。
this式がCの早期構成コンテキスト(§8.8.7)で発生した場合、フィールド・アクセス式の修飾子(§15.11)として表示されないかぎり、コンパイル時にエラーが発生します。単純代入式の左側のオペランド(§15.26)および単純代入式は、Cの初期構成コンテキストに含まれるラムダ式または内部クラス宣言には含まれません。
Cが汎用で、型パラメータF1、...Fnの場合、thisの型はC<F1、...、Fn>です。 それ以外の場合、thisの型はCです。
実行時に、参照される実際のオブジェクトのクラスは、CまたはCのサブクラス(§8.1.5)です。
例15.8.3-1. this式
class IntVector {
int[] v;
boolean equals(IntVector other) {
if (this == other)
return true;
if (v.length != other.v.length)
return false;
for (int i = 0; i < v.length; i++) {
if (v[i] != other.v[i]) return false;
}
return true;
}
}
ここで、クラスIntVectorは、2つのベクトルを比較するメソッドequalsを実装します。 もう一方のベクトルが、equalsメソッドが呼び出されたベクトル・オブジェクトと同じ場合は、長さと値の比較をスキップできます。 equalsメソッドは、他のオブジェクトへの参照をthisと比較することで、このチェックを実装します。
this
字句的に囲むインスタンス(§8.1.3)は、キーワードthisを明示的に修飾することで参照できます。
nは、TypeNameがnの字句で囲まれたクラスまたはインタフェース宣言を示す整数で、その宣言が修飾されたthis式をすぐに包含します。
修飾されたthis式TypeName.thisの値は、thisのn番目の字句で囲まれたインスタンスです。
TypeNameが、型パラメータF1、...Fnを持つ汎用クラスを示している場合、修飾されたthis式の型はTypeName<F1、...、Fn>です。 それ以外の場合、修飾されたthis式の型はTypeNameです。
修飾this式が静的コンテキスト(§8.1.3)で発生した場合、コンパイル時にエラーが発生します。
TypeNameで指定されたクラスの初期構成コンテキスト(§8.8.7)で修飾されたthis式が発生した場合、その式がフィールド・アクセス式の修飾子(§15.11)として表示されないかぎり、コンパイル時にエラーが発生します。単純代入式の左側のオペランド(§15.26)であり、単純代入式は、TypeNameで指定されたクラスの初期構成コンテキストに含まれるラムダ式または内部クラス宣言には含まれません。
修飾されたthis式をすぐに包含する宣言を持つクラスまたはインタフェースが、TypeNameまたはTypeName自体の内部クラスでない場合、コンパイル時にエラーが発生します。
カッコ化された式は、含まれる式の型であり、実行時にその値が含まれる式の値であるプライマリ式です。 含まれる式が変数を示す場合、カッコで囲まれた式もその変数を示します。
カッコの使用は評価の順序にのみ影響します。ただし、(-2147483648)および(-9223372036854775808L)は有効ですが、-(2147483648)および-(9223372036854775808L)は不正です。
これは、小数点リテラル2147483648および9223372036854775808Lが単項マイナス演算子(§3.10.1)のオペランドとしてのみ許可されるためです。
特に、式を囲むカッコの有無は、変数が確実に割当てられるかどうか、trueのときに確実に割り当てられるかどうか、falseのときに確実に割り当てられるか、trueのときは絶対に割り当てられないか、falseのときは絶対に割り当てられないか(§16 (Definite Assignment))に影響しません。
ターゲット・タイプT (§5 (Conversions and Contexts))の特定の種類のコンテキストにカッコで囲まれた式が含まれている場合、その含まれる式は、ターゲット・タイプTと同じ種類のコンテキストに類似して表示されます。
含まれる式が多式(§15.2)の場合、カッコで囲まれた式も多式です。 それ以外の場合はスタンドアロン式です。
ポリカッコで囲まれた式は、含まれる式がTと互換性がある場合、ターゲット・タイプTと互換性があります。
クラス・インスタンス作成式は、クラスのインスタンスである新しいオブジェクトを作成するために使用されます。
new [TypeArguments] ClassOrInterfaceTypeToInstantiate ( [ArgumentList] ) [ClassBody]
便宜上、§15.12の次の本番を示します。
クラス・インスタンス作成式は、インスタンス化されるクラスを指定します。インスタンス化されるクラスが汎用(§8.1.2)の場合、その後に型引数(§4.5.1)またはダイヤモンド(<>)が続く場合、コンストラクタに対する実際の値引数のリスト(空の場合もあります)が続きます。
クラスの型引数リストが空の場合- ダイヤモンド形式<> - クラスの型引数が推測されます。 スタイルの問題として強くお勧めしませんが、ダイヤモンドの「<」と「>」の間に空白を入れることは合法です。
コンストラクタが汎用(§8.8.4)の場合、コンストラクタへの型引数も同様に推測されるか、明示的に渡されます。 明示的に渡された場合、コンストラクタへの型引数は、キーワードnewの直後に続きます。
クラス・インスタンス作成式がコンストラクタに型引数を指定し、クラスへの型引数にダイヤモンド形式を使用する場合、コンパイル時にエラーが発生します。
このルールは、汎用クラスの型引数を推論すると、汎用コンストラクタの型引数の制約に影響を与える可能性があるため導入されています。
TypeArgumentsがnewの直後または(の直前に存在する場合、いずれかの型引数がワイルドカード(§4.5.1)である場合、コンパイル時にエラーが発生します。
クラス・インスタンス作成式がスローできる例外タイプは、§11.2.1で指定します。
クラス・インスタンス作成式には、次の2つの形式があります。
修飾されていないクラス・インスタンス作成式と修飾されたクラス・インスタンス作成式の両方が、オプションでクラス本体で終わる場合があります。 このようなクラス・インスタンス作成式は、匿名クラス(§15.9.5)を宣言し、そのインスタンスを作成します。
クラス・インスタンス作成式は、クラスへの型引数にダイヤモンド・フォームを使用し、代入コンテキストまたは呼出しコンテキスト(§5.2、§5.3)に表示される場合、多式(§15.2)です。 それ以外の場合はスタンドアロン式です。
クラスのインスタンスがクラス・インスタンス作成式によって作成されると、クラスはインスタンス化されます。 クラスのインスタンス化では、インスタンス化するクラス(§15.9.1)、新しく作成されたインスタンスの包含インスタンス(§15.9.2)、および新しいインスタンスを作成するために呼び出されるコンストラクタ(§15.9.3)を決定します。
ClassOrInterfaceTypeToInstantiateが(<>ではなく)TypeArgumentsで終わる場合、ClassOrInterfaceTypeToInstantiateは整形式のパラメータ化された型(§4.5)を示す必要があります。そうしないと、コンパイル時にエラーが発生します。
ClassOrInterfaceTypeToInstantiateが<>で終了するが、ClassOrInterfaceTypeToInstantiateのIdentifierで示されるクラスまたはインタフェースが汎用でない場合は、コンパイル時にエラーが発生します。
クラス・インスタンス作成式がクラス本文で終了する場合、インスタンス化されるクラスは匿名クラスです。 次に、
クラス・インスタンス作成式が修飾されていない場合:
ClassOrInterfaceTypeToInstantiateのIdentifierは、アクセス可能で自由に拡張可能なクラス(§8.1.1.2)であり、enumクラスではないクラス、またはアクセス可能で自由に拡張可能なインタフェース(§9.1.1.4)を示す必要があります。 そうでない場合、コンパイル時にエラーが発生します。
ClassOrInterfaceTypeToInstantiateのIdentifierがクラスCを示す場合、Cの匿名ダイレクト・サブクラスが宣言されます。 TypeArgumentsが存在する場合、CはTypeArgumentsによって指定された型引数を持ちます。<>が存在する場合、Cは§15.9.3で推測される型引数を持ちます。存在しない場合、Cには型引数がありません。 サブクラスの本体は、クラス・インスタンス作成式で指定されるClassBodyです。 インスタンス化されるクラスは匿名サブクラスです。
ClassOrInterfaceTypeToInstantiateのIdentifierがインタフェースIを示している場合、Iを実装するObjectの匿名ダイレクト・サブクラスが宣言されます。 TypeArgumentsが存在する場合、IはTypeArgumentsによって指定された型引数を持ちます。<>が存在する場合、Iは§15.9.3で推測される型引数を持ちます。存在しない場合、Iには型引数がありません。 サブクラスの本体は、クラス・インスタンス作成式で指定されるClassBodyです。 インスタンス化されるクラスは匿名サブクラスです。
クラス・インスタンス作成式が修飾されている場合は、次のようになります:
ClassOrInterfaceTypeToInstantiateのIdentifierは、enumクラスではなく、アクセス可能で自由に拡張可能な内部クラスと、Primary式またはExpressionNameのコンパイル時タイプのメンバーを明確に表す必要があります。 そうでない場合、コンパイル時にエラーが発生します。
ClassOrInterfaceTypeToInstantiateのIdentifierにクラスCを示します。 Cの匿名直接サブクラスが宣言されます。 TypeArgumentsが存在する場合、CはTypeArgumentsによって指定された型引数を持ちます。<>が存在する場合、Cは§15.9.3で推測される型引数を持ちます。存在しない場合、Cには型引数がありません。 サブクラスの本体は、クラス・インスタンス作成式で指定されるClassBodyです。 インスタンス化されるクラスは匿名サブクラスです。
クラス・インスタンス作成式で匿名クラスが宣言されていない場合は、次のようになります:
クラス・インスタンス作成式が修飾されていない場合:
ClassOrInterfaceTypeToInstantiateのIdentifierは、enumクラスではなく、アクセス可能なabstract以外のクラスを示す必要があります。 そうでない場合、コンパイル時にエラーが発生します。
インスタンス化されるクラスは、ClassOrInterfaceTypeToInstantiateのIdentifierによって指定されます。 TypeArgumentsが存在する場合、クラスにはTypeArgumentsによって指定された型引数があります。<>が存在する場合、クラスには§15.9.3で推測される型引数があります。存在しない場合、クラスには型引数がありません。
クラス・インスタンス作成式が修飾されている場合は、次のようになります:
ClassOrInterfaceTypeToInstantiateは、アクセス可能な内部クラス(enumクラスではなくabstract以外)と、Primary式またはExpressionNameのコンパイル時タイプのメンバーを明確に表す必要があります。
インスタンス化されるクラスは、ClassOrInterfaceTypeToInstantiateのIdentifierによって指定されます。 TypeArgumentsが存在する場合、クラスにはTypeArgumentsによって指定された型引数があります。<>が存在する場合、クラスには§15.9.3で推測される型引数があります。存在しない場合、クラスには型引数がありません。
Cをインスタンス化するクラスにし、iを作成中のインスタンスにします。 Cが内部クラスである場合、iは、次のように決定される即時包含インスタンス(§8.1.3)を持つことができます。
Cが無名クラスである場合は、次のようになります。
クラス・インスタンス作成式が静的コンテキストで発生する場合、iにはすぐに包含されるインスタンスはありません。
Cの直接スーパークラスが、静的コンテキストで発生する内部ローカル・クラスLである場合、Sを最も近いstaticメソッド宣言、staticフィールド宣言またはLの宣言を囲む静的イニシャライザにしてください。 最も近いstaticメソッド宣言、staticフィールド宣言、またはクラス・インスタンス作成式を囲む静的イニシャライザがSでない場合、コンパイル時にエラーが発生します。
それ以外の場合、iの直近のインスタンスはthisです。
Cが内部ローカル・クラスの場合は、次のようになります。
Cが静的コンテキストで発生する場合、iにはすぐに包含されるインスタンスはありません。
Sを、Cの宣言を囲む最も近いstaticメソッド宣言、staticフィールド宣言または静的イニシャライザにします。 最も近いstaticメソッド宣言、staticフィールド宣言、またはクラス・インスタンス作成式を囲む静的イニシャライザがSでない場合、コンパイル時にエラーが発生します。
そうでない場合、クラス・インスタンス作成式が静的コンテキストで発生すると、コンパイル時エラーが発生します。
そうでない場合、Oは直前と直後のCのクラスまたはインタフェース宣言、Uは直前と直後のクラス・インスタンス作成式のクラスまたはインタフェース宣言とします。
Uが Oまたは O自体の内部クラスでない場合、コンパイル時にエラーが発生します。
nは、Oがn番目の字句で囲まれたクラスまたはUのインタフェース宣言となるような整数になります。
iの即時包含インスタンスは、thisのn番目の字句的に包含されるインスタンスです。
Cが内部メンバー・クラスである場合は、次のようになります。
クラス・インスタンス作成式が修飾されていない場合:
クラス・インスタンス作成式が静的コンテキストで発生すると、コンパイル時エラーが発生します。
そうでない場合、Cが、クラス・インスタンス作成式が宣言で字句として包含されているクラスのメンバーでなければ、コンパイル時にエラーが発生します。
そうでない場合、OをCがメンバーである最も内側の包含クラス宣言にし、Uを直前と直後のクラス・インスタンス作成式のクラスまたはインタフェース宣言にします。
Uが Oまたは O自体の内部クラスでない場合、コンパイル時にエラーが発生します。
クラス・インスタンス作成式がOの初期の構築コンテキスト(§8.8.7)で発生すると、コンパイル時にエラーが発生します。
nは、Oがn番目の字句で囲まれたクラスまたはUのインタフェース宣言となるような整数になります。
iの即時包含インスタンスは、thisのn番目の字句的に包含されるインスタンスです。
クラス・インスタンス作成式が修飾されている場合、iの直近のインスタンスは、Primary式またはExpressionNameの値であるオブジェクトです。
Cが匿名クラスで、その直接スーパークラスSが内部クラスである場合、iはSに関してすぐに包含するインスタンスを持つことができ、次のように決定されます。
Sが内部ローカル・クラスの場合は、次のようになります。
Sが静的コンテキストで発生する場合、iはSに関してすぐに包含するインスタンスを持ちません。
そうでない場合、クラス・インスタンス作成式が静的コンテキストで発生すると、コンパイル時エラーが発生します。
そうでない場合、Oを直前と直後のSのクラスまたはインタフェース宣言にし、Uを直前と直後のクラス・インスタンス宣言式のクラスまたはインタフェース宣言にします。
Uが Oまたは O自体の内部クラスでない場合、コンパイル時にエラーが発生します。
nは、Oがn番目の字句で囲まれたクラスまたはUのインタフェース宣言となるような整数になります。
Sに関してiの直近のインスタンスは、thisのn番目の字句的に包含されるインスタンスです。
Sが内部メンバー・クラスである場合は、次のようになります。
クラス・インスタンス作成式が修飾されていない場合:
クラス・インスタンス作成式が静的コンテキストで発生すると、コンパイル時エラーが発生します。
そうでない場合、Sはその宣言がクラス・インスタンス作成式を囲んでいるクラスのメンバーではなく、コンパイル時にエラーが発生します。
そうでない場合、OをSがメンバーである、最も内側の包含クラス宣言にし、Uを直前と直後のクラス・インスタンス作成式のクラスまたはインタフェース宣言にします。
Uが Oまたは O自体の内部クラスでない場合、コンパイル時にエラーが発生します。
クラス・インスタンス作成式がOの早期構築コンテキストに示されると、コンパイル時にエラーが発生します。
nは、Oがn番目の字句で囲まれたクラスまたはUのインタフェース宣言となるような整数になります。
Sに関してiの直近のインスタンスは、thisのn番目の字句的に包含されるインスタンスです。
そうでない場合、コンパイル時にエラーが発生します。
クラス・インスタンス作成式が修飾されている場合、Sに関してiの直近のインスタンスは、Primary式またはExpressionNameの値であるオブジェクトです。
Cは、インスタンス化対象のクラスであるとします。 Cのインスタンスを作成するには、次のルールによってコンパイル時にCのコンストラクタiが選択されます。
まず、コンストラクタ呼出しの実際の引数が決定します。
Cが直接スーパークラスSを持つ匿名クラスである場合、次のようになります。
Sが内部クラスでない場合、またはSが静的コンテキストで発生するローカル・クラスである場合、コンストラクタの引数は、クラス・インスタンス作成式の引数リストにある引数(存在する場合)で、式に出現する順序になります。
それ以外の場合、コンストラクタの最初の引数は、S (§15.9.2)に関してiの直前に包含されるインスタンスであり、コンストラクタに対する後続の引数は、クラス・インスタンス作成式の引数リストにある引数(存在する場合)で、クラス・インスタンス作成式に出現する順序になります。
Cがローカル・クラスまたはprivate内部メンバー・クラスの場合、コンストラクタの引数は、クラス・インスタンス作成式の引数リストにある引数(存在する場合)で、クラス・インスタンス作成式に表示される順序になります。
Cがprivate以外の内部メンバー・クラスである場合、コンストラクタの最初の引数はi (§8.8.1、§15.9.2)の直近の包含インスタンスであり、そのコンストラクタへの後続の引数は、クラス・インスタンス作成式の引数リストにある引数(存在する場合)で、クラス・インスタンス作成式に出現する順序です。
それ以外の場合、コンストラクタの引数はクラス・インスタンス作成式の引数リストの引数(ある場合)であり、式に出現する順序となります。
次に、Cのコンストラクタと、対応するthrows句および戻り型が決定されます。
クラス・インスタンス作成式で<>を使用しない場合、次のようになります。
Cが匿名クラスではない場合、次のようになります。
Tは、式のクラス型引数が後に続く、Cで示される型であるとします。 コンストラクタを処理するように変更された§15.12.2で指定されたプロセスは、Tのコンストラクタの1つを選択し、そのthrows句を決定するために使用されます。
Tに、適用可能かつアクセス可能(§6.6)な一意のもっとも固有なコンストラクタがない場合、(メソッド呼出しと同様に)コンパイル時エラーが発生します。
それ以外の場合、選択されたコンストラクタに対応する戻り型はTです。
Cが無名クラスである場合は、次のようになります。
コンストラクタを処理するように変更された§15.12.2で指定されたプロセスは、Cの直接スーパークラス型のコンストラクタの1つを選択し、そのthrows句を決定するために使用されます。
Cの直接スーパークラス型に、適用可能かつアクセス可能な一意の最も的確なコンストラクタがない場合、コンパイル時にエラーが発生します(メソッド呼出しと同様)。
それ以外の場合、Cの匿名コンストラクタがCのコンストラクタとして選択されます(§15.9.5.1)。 その本体は、直接スーパークラス型Cで選択されたコンストラクタの明示的なコンストラクタ呼出し(§8.8.7.1)で構成されます。
選択したコンストラクタのthrows句には、Cの直接スーパークラス型で選択されたコンストラクタのthrows句の例外が含まれます。
選択されたコンストラクタに対応する戻り型は匿名クラス型です。
クラス・インスタンス作成式で<>が使用されている場合は、次のようになります。
Cが匿名クラスでない場合は、Dを Cと同じにしてください。 Cが匿名クラスである場合は、Dをクラス・インスタンス作成式で指定されたCのスーパークラスまたはスーパーインタフェースにします。
Dがクラスである場合は、c1、...、cnをクラスDのコンストラクタにします。 Dがインタフェースの場合は、c1、...、cnを、クラスObjectのゼロ引数コンストラクタを含むシングルトン・リスト(n = 1)にします。
メソッドm1、...、mnのリストは、オーバーロード解決および型引数の推論のために定義されています。 すべてのj (1 ≤ j ≤ n)について、mjはcjに関して次のように定義されます。
置換θjは、cjの型をインスタンス化するために最初に定義されます。
F1、 ...、 FpをDの型パラメータとし、G1、 ...、 Gqをcjの型パラメータ(ある場合)にします。 X1、 ...、 Xpおよび Y1、 ...、 Yqを、Dの本体内でスコープ内にない個別の名前を持つ型変数とします。
θjは[F1:=X1, ..., Fp:=Xp, G1:=Y1, ..., Gq:=Yq]です。
mjの型パラメータは、X1、...、Xp、Y1、...、Yqです。 各型パラメータの境界(ある場合)は、Dまたはcjでバインドされた対応する型パラメータに適用されるθjです。
mjの戻り型は、D<F1、...、Fp>に適用されるθjです。
mjの引数型のリストは、cjの引数型に適用されるθjです。
mjのスローされた型のリストは、cjのスローされた型に適用されるθjです。
mjの修飾子は、cjの修飾子です。
mjの名前は#mで、D内のすべてのコンストラクタおよびメソッド名と区別され、m1、...、mnによって共有される、自動的に生成される名前です。
mjの本体は無関係です。
コンストラクタを選択するために、一時的にm1、...、mnをDのメンバーとみなします。 §15.12.2で指定されたプロセスを使用して、クラス・インスタンス作成式の引数式によって決定されるm1、...、mnのいずれかが選択されます。
適用可能かつアクセス可能な一意の最も的確なメソッドがない場合、コンパイル時にエラーが発生します。
それ以外の場合は、mjが選択されたメソッドです。
Cが匿名クラスでない場合、cjがCのコンストラクタとして選択されます。
選択したコンストラクタのthrows句は、mjに対して決定されたthrows句と同じです。
選択したコンストラクタに対応する戻り型は、mj (§15.12.2.6)に対して決定される戻り型です。
Cが匿名クラスである場合、Cの匿名コンストラクタがCのコンストラクタとして選択されます。 本体は、cjの明示的なコンストラクタ呼出し(§8.8.7.1)で構成されます。
選択したコンストラクタのthrows句には、mjに対して決定されたthrows句の例外が含まれます。
選択されたコンストラクタに対応する戻り型は匿名クラス型です。
クラス・インスタンス作成式が多式の場合、ターゲット・タイプとの互換性は、選択したメソッドmとしてmjを使用し、§18.5.2.1によって決定されます。
ターゲット型との互換性のテストは、クラス・インスタンス作成式のターゲット型と、選択したコンストラクタに対応する戻り型の最終的な判定の前に、複数回発生する場合があります。 たとえば、包含メソッド呼出し式では、異なるメソッドの仮パラメータ型との互換性についての、クラス・インスタンス作成式のテストが必要な場合があります。
Cが匿名クラスである場合、その直接スーパークラス型または直接スーパーインタフェース型は、mj (§15.12.2.6)に対して決定される戻り型です。
直接スーパークラス型または直接スーパーインタフェース型あるいはそこの副次式(「副次式」ではパラメータ化された型の型引数、ワイルドカード型引数の境界および配列型の要素型が含まれますが、型変数の境界は除外されます)の形式が次のいずれかである場合、コンパイル時にエラーが発生します。
型パラメータとして宣言されていない型変数(キャプチャ変換によって生成される型変数など)。
交差型。
クラスまたはインタフェース宣言が、クラス・インスタンス作成式が出現するクラスまたはインタフェースからアクセス不可能である、クラスまたはインタフェース型。
クラス・インスタンス作成式の引数が、呼出しタイプ(§15.12.2.6)から導出されたターゲット・タイプと互換性がない場合、コンパイル時にエラーが発生します。
コンパイル時宣言が変数引数の呼出し(§15.12.2.4)によって適用される場合、コンストラクタの呼出しタイプの最後の仮パラメータ型がFn[]の場合、Fnの消去である型が呼出しの時点でアクセスできない場合、コンパイル時にエラーが発生します。
クラス・インスタンス作成式の型は、前述の定義のとおり、選択されたコンストラクタに対応する戻り型です。
実行時の、クラス・インスタンス作成式の評価は、次のようになります。
まず、クラス・インスタンス作成式が、修飾されたクラス・インスタンス作成式である場合、修飾する1次式が評価されます。 修飾式がnullと評価されると、NullPointerExceptionが生成され、クラス・インスタンス作成式が突然完了します。 修飾する式が突然完了する場合、クラス・インスタンス作成式が同じ理由で突然完了します。
次に、新しいクラス・インスタンスに領域が割り当てられます。 オブジェクトを割り当てるための領域が不足している場合は、OutOfMemoryErrorをスローして、クラス・インスタンス作成式の評価が突然完了します。
新しいオブジェクトには、指定されたクラスとそのすべてのスーパークラスで宣言されたすべてのフィールドの新しいインスタンスが含まれます。 新しいフィールド・インスタンスが作成されるたびに、そのデフォルト値(§4.12.5)に初期化されます。
次に、コンストラクタの実際の引数が左から右に評価されます。 いずれかの引数の評価が突然完了する場合、その右の引数式は評価されず、クラス・インスタンス作成式が同じ理由で突然完了します。
次に、指定されたクラスの選択されたコンストラクタが呼び出されます。 これにより、クラスのスーパークラスごとに少なくとも1つのコンストラクタが呼び出されます。 このプロセスは、明示的なコンストラクタ呼出し(§8.8.7.1)によって指示され、§12.5で詳細に規定されています。
クラス・インスタンス作成式の値は、指定されたクラスの新しく作成されたオブジェクトへの参照です。 式が評価されるたびに、新しいオブジェクトが作成されます。
例15.9.4-1 評価順序およびメモリー不足の検出
クラス・インスタンス作成式の評価で、作成操作を実行するのに十分なメモリーがないことがわかった場合は、OutOfMemoryErrorがスローされます。 このチェックは、引数式が評価される前に行われます。
たとえば、テスト・プログラムは次のようになります。
class List {
int value;
List next;
static List head = new List(0);
List(int n) { value = n; next = head; head = this; }
}
class Test {
public static void main(String[] args) {
int id = 0, oldid = 0;
try {
for (;;) {
++id;
new List(oldid = id);
}
} catch (Error e) {
List.head = null;
System.out.println(e.getClass() + ", " + (oldid==id));
}
}
}
出力:
class java.lang.OutOfMemoryError, false
引数式oldid = idが評価される前にメモリー不足条件が検出されるためです。
これを、ディメンション式の評価後にメモリー不足状態が検出される配列作成式の処理と比較します(§15.10.2)。
匿名クラスは、クラス・インスタンス作成式またはクラス本体で終わるenum定数(§8.9.1)によって暗黙的に宣言されます。
匿名クラスはabstractではありません(§8.1.1.1)。
匿名クラスはsealed (§8.1.1.2)ではないため、直接サブクラスが許可されていません(§8.1.6)。
クラス・インスタンス作成式によって宣言された匿名クラスは、final (§8.1.1.2)ではありません。
enum定数によって宣言される匿名クラスは、常にfinalです。
final以外である匿名クラスは、キャスト、特にキャスト演算子(§5.5)で許可される絞込み参照変換に関連します。 一方、匿名クラスが非finalであるにもかかわらず、匿名クラスのサブクラスを宣言することは不可能であるため(匿名クラスはextends句で指定できません)、サブクラス化には関係ありません。
無名クラスは、常に内部クラス(§8.1.3)です。
ローカル・クラスまたはインタフェース(§14.3)と同様に、匿名クラスはパッケージ、クラスまたはインタフェースのメンバーではありません(§7.1、§8.5)。
クラス・インスタンス作成式によって宣言された匿名クラスの直接スーパークラス型または直接スーパーインタフェース型は、式(§15.9.1)によって指定され、コンストラクタの選択中に型引数が必要に応じて推測されます(§15.9.3)。 直接スーパーインタフェース型が指定されている場合、直接スーパークラス型はObjectです。
enum定数によって宣言される匿名クラスの直接スーパークラス型は、宣言するenumクラスの型です。
クラス・インスタンス作成式または列挙定数ClassBodyは、匿名クラスのフィールド(§8.3)、メソッド(§8.4)、メンバー・クラス(§8.5)、メンバー・インタフェース(§9.1.1.3)、インスタンス・イニシャライザ(§8.6)および静的イニシャライザ(§8.7)を宣言します。 匿名クラスのコンストラクタは、常に暗黙的です(§15.9.5.1)。
ClassBodyを持つクラス・インスタンス作成式で、インスタンス化されるクラスの型引数にダイヤモンド(<>)を使用する場合、ClassBodyで宣言されたすべての非privateメソッドに対して、メソッド宣言に@Override (§9.6.4.4)の注釈が付けられているかのようになります。
<>を使用する場合、推測型引数はプログラマが予想するほどでない可能性があります。 したがって、匿名クラスのスーパータイプは予想どおりではなく、匿名クラスで宣言されたメソッドが意図したとおりにスーパータイプ・メソッドをオーバーライドしない場合があります。 @Overrideで注釈が付けられているようなメソッド(@Overrideで明示的に注釈が付けられていない場合)を処理すると、サイレントに誤ったプログラムを回避できます。
匿名クラスは、明示的に宣言されたコンストラクタを持つことができません。 かわりに、無名コンストラクタが、匿名クラスに対して暗黙的に宣言されます。 直接スーパークラスSを持つ匿名クラスCの匿名コンストラクタの形式は次のとおりです。
Sが内部クラスでない場合、またはSが静的コンテキストで発生するローカル・クラスである場合、匿名コンストラクタは、Cを宣言するクラス・インスタンス作成式または列挙定数に対する実際の引数ごとに1つの仮パラメータを持ちます。
クラス・インスタンス作成式または列挙定数に対する実際の引数は、§15.9.3で指定されているSのコンストラクタxを決定するために使用されます。 匿名コンストラクタの各仮パラメータの型は、xの対応する仮パラメータと同一になります。
無名コンストラクタ本体は、super(...)形式の明示的なコンストラクタ呼出し(§8.8.7.1)から構成され、実際の引数は、宣言された順序で無名コンストラクタの仮パラメータです。 呼び出されるスーパークラス・コンストラクタはxです。
それ以外の場合、匿名コンストラクタの最初の仮パラメータは、S (§15.9.2)に関してiの直前に包含されるインスタンスの値を表します。 このパラメータの型は、Sの宣言をすぐに囲むクラス型です。
匿名コンストラクタには、匿名クラスを宣言したクラス・インスタンス作成式への実際の引数ごとに、追加の仮パラメータがあります。 n番目の仮パラメータは、n-1の実引数に対応します。
クラス・インスタンス作成式の実際の引数は、§15.9.3で指定されているSのコンストラクタxを決定するために使用されます。 匿名コンストラクタの各仮パラメータの型は、xの対応する仮パラメータと同一になります。
匿名コンストラクタ本体は、o.super(...)形式の明示的なコンストラクタ呼出しで構成されます。ここで、oは匿名コンストラクタの最初の仮パラメータであり、実際の引数は、宣言された順序で、コンストラクタの後続の仮パラメータです。 呼び出されるスーパークラス・コンストラクタはxです。
いずれの場合も、匿名コンストラクタのthrows句には、§15.9.3で指定されているように、匿名コンストラクタに含まれる明示的なコンストラクタ呼出しによってスローされるすべてのチェック済例外と、匿名クラスのインスタンス・イニシャライザまたはインスタンス変数イニシャライザによってスローされるすべてのチェック済例外がリストされます。
匿名コンストラクタのシグネチャは、アクセスできない型を参照できます(たとえば、スーパークラス・コンストラクタxのシグネチャでそのような型が発生した場合)。 これ自体では、コンパイル時にも実行時にもエラーが発生することはありません。
配列作成式は、新しい配列を作成するために使用されます(§10 (Arrays))。
new PrimitiveType DimExprs [Dims] new ClassOrInterfaceType DimExprs [Dims]
[ 式 ]
ここでは、便宜上、§4.3の次の本番環境を示します。
配列作成式は、PrimitiveTypeまたはClassOrInterfaceTypeで指定された型の要素を持つ新しい配列であるオブジェクトを作成します。
ClassOrInterfaceTypeが再可能な型(§4.7)を示さない場合、コンパイル時にエラーが発生します。 それ以外の場合、ClassOrInterfaceTypeは、abstractクラス型(§8.1.1.1)またはインタフェース型であっても、任意の名前付き参照型に名前を付けることができます。
前述のルールは、パラメータ化された型に対するすべての型引数がバインドされていないワイルドカードでないかぎり、配列作成式の要素型をパラメータ化された型にできないことを意味します。
DimExpr内の各ディメンション式の型は、整数型に変換可能な型(§5.1.8)である必要があります。そうでない場合、コンパイル時にエラーが発生します。
各ディメンション式は、単項数値プロモーション(§5.6)の対象となります。 プロモートされた型はintである必要があります。そうでないと、コンパイル時にエラーが発生します。
配列作成式の型は、newキーワードとすべてのDimExpr式および配列イニシャライザが削除された配列作成式のコピーで示すことができる配列型です。
たとえば、作成式のタイプは次のとおりです。
new double[3][3][]
が:
double[][][]
実行時には、配列作成式の評価は次のように動作します。
ディメンション式がない場合は、配列イニシャライザが必要です。 新しく割り当てられた配列は、§10.6で説明されているように、配列イニシャライザによって提供される値で初期化されます。 配列イニシャライザの値は、配列作成式の値になります。
それ以外の場合は、配列イニシャライザがなく、次の処理が行われます。
まず、ディメンション式が左から右に評価されます。 いずれかの式の評価が突然完了した場合、その右側の式は評価されません。
次に、ディメンション式の値がチェックされます。 DimExpr式の値が0より小さい場合は、NegativeArraySizeExceptionがスローされます。
次に、新しい配列に領域が割り当てられます。 配列を割り当てるための領域が不足している場合は、OutOfMemoryErrorをスローして、配列作成式の評価が突然完了します。
次に、単一のDimExprが出現すると、指定された長さの1次元配列が作成され、配列の各コンポーネントがデフォルト値(§4.12.5)に初期化されます。
それ以外の場合、n DimExpr式が出現すると、配列作成では、深さn-1のネストしたループのセットが効果的に実行され、配列の暗黙の配列が作成されます。
多次元配列は、各レベルで同じ長さの配列を持つ必要はありません。
例15.10.2-1 アレイの作成評価
1つ以上のディメンション式を含む配列作成式では、各ディメンション式は、右側のディメンション式の任意の部分の前に完全に評価されます。 このため、:
class Test1 {
public static void main(String[] args) {
int i = 4;
int[][] ia = new int[i][i=3];
System.out.println(
"[" + ia.length + "," + ia[0].length + "]");
}
}
出力:
[4,3]
2番目のディメンション式がiを3に設定する前に、最初のディメンションが4として計算されるためです。
ディメンション式の評価が突然完了した場合、その右側のディメンション式のどの部分も評価されていないように見えます。 このため、:
class Test2 {
public static void main(String[] args) {
int[][] a = { { 00, 01 }, { 10, 11 } };
int i = 99;
try {
a[val()][i = 1]++;
} catch (Exception e) {
System.out.println(e + ", i=" + i);
}
}
static int val() throws Exception {
throw new Exception("unimplemented");
}
}
出力:
java.lang.Exception: unimplemented, i=99
iを1に設定する埋込み割当ては実行されないためです。
例15.10.2-2 多次元配列の作成
宣言:
float[][] matrix = new float[3][3];
次の動作と同等です。
float[][] matrix = new float[3][]; for (intd= 0;d< matrix.length;d++) matrix[d] = new float[3];
および
Age[][][][][] Aquarius = new Age[6][10][8][12][];
は次と同等です。
Age[][][][][] Aquarius = new Age[6][][][][]; for (intd1= 0;d1< Aquarius.length;d1++) { Aquarius[d1] = new Age[10][][][]; for (intd2= 0;d2< Aquarius[d1].length;d2++) { Aquarius[d1][d2] = new Age[8][][]; for (intd3= 0;d3< Aquarius[d1][d2].length;d3++) { Aquarius[d1][d2][d3] = new Age[12][]; } } }
d、d1、d2およびd3をローカルで宣言されていない名前に置き換えます。 したがって、単一のnew式では、実際には長さ6の1つの配列、長さ10の6つの配列、長さ8の6x10 = 60の配列、長さ12の6x10x8 = 480の配列が作成されます。 この例では、5番目のディメンションを残します。5番目のディメンションは、実際の配列要素(Ageオブジェクトへの参照)を含む配列であり、NULL参照にのみ初期化されます。 これらの配列は、後で次のような他のコードによって入力できます。
Age[] Hair = { new Age("quartz"), new Age("topaz") };
Aquarius[1][9][6][9] = Hair;
次の方法で三角行列を作成できます。
float[][] triang = new float[100][];
for (int i = 0; i < triang.length; i++)
triang[i] = new float[i+1];
配列作成式の評価で、作成操作を実行するのに十分なメモリーがないことがわかった場合は、OutOfMemoryErrorがスローされます。 配列作成式に配列イニシャライザがない場合、このチェックは、すべてのディメンション式の評価が正常に完了した後にのみ行われます。 配列作成式に配列イニシャライザが含まれている場合、変数イニシャライザ式の評価中に参照型のオブジェクトが割り当てられるとき、または(ネストされた)配列イニシャライザの値を保持するために配列に領域が割り当てられるときに、OutOfMemoryErrorが発生する可能性があります。
例15.10.2-3. OutOfMemoryErrorおよびディメンション式の評価
class Test3 {
public static void main(String[] args) {
int len = 0, oldlen = 0;
Object[] a = new Object[0];
try {
for (;;) {
++len;
Object[] temp = new Object[oldlen = len];
temp[0] = a;
a = temp;
}
} catch (Error e) {
System.out.println(e + ", " + (oldlen==len));
}
}
}
このプログラムは出力を生成します:
java.lang.OutOfMemoryError, true
ディメンション式oldlen = lenが評価された後にメモリー不足状態が検出されるためです。
これを、引数式を評価する前にメモリー不足状態を検出するクラス・インスタンス作成式(§15.9)と比較します(§15.9.4)。
配列アクセス式は、配列のコンポーネントである変数を参照します。
配列アクセス式には、配列参照式(左カッコの前)と索引式(カッコの内側)の2つの副式が含まれています。
配列参照式は、配列作成式に配列イニシャライザ(§15.10.1)が含まれていないかぎり、名前または配列作成式ではない任意のプライマリ式にすることができます。
配列参照式の型は、配列型(T[]、コンポーネントがT型である配列)である必要があります。そうでないと、コンパイル時にエラーが発生します。
索引式には、単項数値プロモーション(§5.6)が適用されます。 プロモートされた型はintである必要があります。そうでないと、コンパイル時にエラーが発生します。
配列アクセス式の型は、取得変換(§5.1.10)をTに適用した結果です。
配列アクセス式の結果は、T型の変数、つまり索引式の値によって選択された配列内の変数です。
この結果生成される変数は、配列参照式がfinal変数を示している場合でも、finalとはみなされません。
実行時には、配列アクセス式の評価は次のように動作します。
まず、配列参照式が評価されます。 この評価が突然完了した場合、配列アクセスは同じ理由で突然完了し、索引式は評価されません。
それ以外の場合は、索引式が評価されます。 この評価が突然完了すると、同じ理由でアレイのアクセスが突然完了します。
それ以外の場合、配列参照式の値がnullの場合は、NullPointerExceptionがスローされます。
それ以外の場合、配列参照式の値は実際に配列を参照します。 索引式の値が0より小さいか、配列のlength以上である場合、ArrayIndexOutOfBoundsExceptionがスローされます。
それ以外の場合、配列アクセスの結果は、インデックス式の値によって選択された、配列内の T型の変数になります。
例15.10.4-1 配列参照が最初に評価されます
配列アクセスでは、カッコの左側の式は、カッコ内の式のどの部分も評価される前に完全に評価されているように見えます。 たとえば、(明らかに大きな)式a[(a=b)[3]]では、式aは式(a=b)[3]の前に完全に評価されます。つまり、式(a=b)[3]が評価されるときに、aの元の値がフェッチされて記憶されます。 aの元の値によって参照されるこの配列は、bによって参照され、現在はaによって参照されている別の配列(おそらく同じ配列)の要素3の値によって記述されます。
したがって、プログラムは次のようになります。
class Test1 {
public static void main(String[] args) {
int[] a = { 11, 12, 13, 14 };
int[] b = { 0, 1, 2, 3 };
System.out.println(a[(a=b)[3]]);
}
}
出力:
14
monstrous式の値はa[b[3]]、a[3]または14と等価であるためです。
例15.10.4-2 配列参照評価の突然完了
括弧の左側の式の評価が突然完了した場合、括弧内の式の一部は評価されていないように見えます。 したがって、プログラムは次のようになります。
class Test2 {
public static void main(String[] args) {
int index = 1;
try {
skedaddle()[index=2]++;
} catch (Exception e) {
System.out.println(e + ", index=" + index);
}
}
static int[] skedaddle() throws Exception {
throw new Exception("Ciao");
}
}
出力:
java.lang.Exception: Ciao, index=1
2からindexへの埋込み割当ては発生しないためです。
例15.10.4-3. null配列のリファレンス
配列参照式が配列への参照ではなくnullを生成する場合、NullPointerExceptionは実行時にスローされますが、配列アクセス式のすべての部分が評価され、これらの評価が正常に完了した場合にのみスローされます。 したがって、プログラムは次のようになります。
class Test3 {
public static void main(String[] args) {
int index = 1;
try {
nada()[index=2]++;
} catch (Exception e) {
System.out.println(e + ", index=" + index);
}
}
static int[] nada() { return null; }
}
出力:
java.lang.NullPointerException, index=2
2からindexへの埋込み割当ては、null配列参照式のチェックの前に行われるためです。 関連する例として、プログラムは次のことを行います。
class Test4 {
public static void main(String[] args) {
int[] a = null;
try {
int i = a[vamoose()];
System.out.println(i);
} catch (Exception e) {
System.out.println(e);
}
}
static int vamoose() throws Exception {
throw new Exception("Twenty-three skidoo!");
}
}
常に出力:
java.lang.Exception: Twenty-three skidoo!
NullPointerExceptionは発生しません。これは、配列アクセスのこれ以上の部分が発生する前に索引式を完全に評価する必要があり、配列参照式の値がnullかどうかのチェックが含まれるためです。
フィールド・アクセス式は、オブジェクトまたは配列のフィールド、つまり式または特殊キーワードsuperの値への参照にアクセスできます。
フィールド・アクセス式の意味は、修飾名(§6.5.6.2)と同じルールを使用して決定されます。ただし、式がパッケージ、クラス・タイプまたはインタフェース・タイプを示せないという事実によって制限されます。
単純名(§6.5.6.1)を使用して、現在のインスタンスまたは現在のクラスのフィールドを参照することもできます。
「プライマリ」の型は、参照型Tである必要があります。そうしないと、コンパイル時にエラーが発生します。
フィールドアクセス式の意味は、次のように決定されます。
実行時に、フィールド・アクセス式の結果は次のように計算されます: (プログラムが明確な割当て分析に関して正しいと仮定します。つまり、すべての空白のfinal変数がアクセス前に必ず割り当てられます)
フィールドがstaticの場合:
フィールドがstaticでない場合:
プライマリ式が評価されます。 「プライマリ」式の評価が突然完了した場合、フィールド・アクセス式は同じ理由で突然完了します。
「プライマリ」の値がnullの場合は、NullPointerExceptionがスローされます。
フィールドが空白でないfinalの場合、結果は、「プライマリ」の値によって参照されるオブジェクト内で検出されたT型の名前付きメンバー・フィールドの値になります。
フィールドがfinalでないか、空白のfinalで、フィールド・アクセスがインスタンス変数イニシャライザ(§8.3.2)、インスタンス・イニシャライザ(§8.6)またはコンストラクタ(§8.8)で発生する場合、結果は変数、つまりPrimaryの値によって参照されるオブジェクト内にあるT型の名前付きメンバー・フィールドになります。
実行時に参照される実際のオブジェクトのクラスではなく、プライマリ式の型のみが、使用するフィールドの決定に使用されます。
例15.11.1-1 フィールド・アクセスの静的バインディング
class S { int x = 0; }
class T extends S { int x = 1; }
class Test1 {
public static void main(String[] args) {
T t = new T();
System.out.println("t.x=" + t.x + when("t", t));
S s = new S();
System.out.println("s.x=" + s.x + when("s", s));
s = t;
System.out.println("s.x=" + s.x + when("s", s));
}
static String when(String name, Object t) {
return " when " + name + " holds a "
+ t.getClass() + " at run time.";
}
}
このプログラムは出力を生成します:
t.x=1 when t holds a class T at run time. s.x=0 when s holds a class S at run time. s.x=0 when s holds a class T at run time.
最後の行は、実際にアクセスされるフィールドが参照オブジェクトの実行時クラスに依存しないことを示しています。sがクラスTのオブジェクトへの参照を保持している場合でも、式s.xはクラスSのxフィールドを参照します。これは、式sのタイプがSであるためです。 クラスTのオブジェクトには、xという名前の2つのフィールドが含まれています。1つはクラスT用、もう1つはスーパークラスS用です。
このようにフィールド・アクセスの動的参照がないと、プログラムを簡単に実装して効率的に実行できます。 遅延バインドおよびオーバーライドのパワーは、インスタンス・メソッドが使用されている場合にのみ使用できます。 インスタンス・メソッドを使用してフィールドにアクセスする同じ例を考えてみます。
class S { int x = 0; int z() { return x; } }
class T extends S { int x = 1; int z() { return x; } }
class Test2 {
public static void main(String[] args) {
T t = new T();
System.out.println("t.z()=" + t.z() + when("t", t));
S s = new S();
System.out.println("s.z()=" + s.z() + when("s", s));
s = t;
System.out.println("s.z()=" + s.z() + when("s", s));
}
static String when(String name, Object t) {
return " when " + name + " holds a "
+ t.getClass() + " at run time.";
}
}
これで、出力は次のようになります。
t.z()=1 when t holds a class T at run time. s.z()=0 when s holds a class S at run time. s.z()=1 when s holds a class T at run time.
最後の行は、実際には、アクセスされるメソッドが参照されるオブジェクトの実行時クラスに依存することを示しています。sがクラスTのオブジェクトへの参照を保持している場合、式sの型がSであるにもかかわらず、式s.z()はクラスTのzメソッドを参照します。 クラスTのメソッドzは、クラスSのメソッドzをオーバーライドします。
例15.11.1-2 受信者変数は、staticフィールド・アクセスには関係ありません
次のプログラムは、例外を発生させずにクラス(static)変数にアクセスするためにnull参照を使用できることを示しています。
class Test3 {
static String mountain = "Chocorua";
static Test3 favorite(){
System.out.print("Mount ");
return null;
}
public static void main(String[] args) {
System.out.println(favorite().mountain);
}
}
次のものをコンパイル、実行および出力します。
Mount Chocorua
favorite()の結果がnullであっても、NullPointerExceptionはスローされません。 「Mount 」が出力されることは、Primary式が実行時に完全に評価されることを示しています。ただし、その型のみが値ではなく、アクセスするフィールドを決定するために使用されます(フィールドmountainがstaticであるため)。
superを使用したスーパークラス・メンバーへのアクセス
super.Identifierという形式は、現行オブジェクトのIdentifierというフィールドを指しますが、現行オブジェクトは現行クラスのスーパークラスのインスタンスとして表示されます。
T.super.Identifierという形式は、Tに対応する字句で囲んでいるインスタンスのIdentifierという名前のフィールドを指しますが、そのインスタンスはTのスーパークラスのインスタンスとして表示されます。
キーワードsuperを使用するフォームは、キーワードthisを式として許可するクラス宣言内の場所で使用できます(§15.8.3)。
キーワードsuperを使用したフィールド・アクセス式が静的コンテキスト(§8.1.3)または現在のクラスの初期構成コンテキスト(§8.8.7)で発生した場合、コンパイル時にエラーが発生します。
super.Identifier形式のフィールド・アクセス式の場合:
フィールド・アクセス式の即時包含クラスまたはインタフェース宣言がクラスObjectまたはインタフェースである場合、コンパイル時にエラーが発生します。
T.super.Identifierという形式のフィールド・アクセス式の場合:
TがクラスObjectまたはインタフェースの場合、コンパイル時にエラーが発生します。
Uは、フィールド・アクセス式の、直接包含するクラスまたはインタフェースの宣言であるとします。 Uが Tまたは T自体の内部クラスでない場合、コンパイル時にエラーが発生します。
フィールド・アクセス式super.fがクラスC内にあり、Cの直接スーパークラスがクラスSであるとします。 SのfにクラスC (§6.6)からアクセスできる場合、super.fは、クラスSの本体で式this.fであったかのように扱われます。 そうでない場合、コンパイル時にエラーが発生します。
したがって、super.fは、クラスSでアクセス可能なフィールドfにアクセスできます。これは、そのフィールドがクラスCのフィールドfの宣言によって非表示になっている場合でも同様です。
フィールド・アクセス式T.super.fがクラスC内にあり、Tで示されるクラスの即時スーパークラスが完全修飾名がSのクラスであるとします。 SのfにCからアクセスできる場合、T.super.fは、クラスSの本体で式this.fであったかのように処理されます。 そうでない場合、コンパイル時にエラーが発生します。
したがって、T.super.fは、クラスTのフィールドfの宣言によってそのフィールドが非表示になっている場合でも、クラスSでアクセス可能なフィールドfにアクセスできます。
例15.11.2-1 super式
interface I { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1 { int x = 2; }
class T3 extends T2 {
int x = 3;
void test() {
System.out.println("x=\t\t" + x);
System.out.println("super.x=\t\t" + super.x);
System.out.println("((T2)this).x=\t" + ((T2)this).x);
System.out.println("((T1)this).x=\t" + ((T1)this).x);
System.out.println("((I)this).x=\t" + ((I)this).x);
}
}
class Test {
public static void main(String[] args) {
new T3().test();
}
}
このプログラムは出力を生成します:
x= 3 super.x= 2 ((T2)this).x= 2 ((T1)this).x= 1 ((I)this).x= 0
クラスT3内で、式super.xは、xにパッケージ・アクセス権がある場合、((T2)this).xと同じ効果を持ちます。 スーパークラスのprotectedメンバーへのアクセスが困難であるため、super.xはキャストに関して指定されません。
メソッド呼出し式は、クラスまたはインスタンス・メソッドを呼び出すために使用されます。
( [ArgumentList] ) . [TypeArguments] Identifier ( [ArgumentList] ) . [TypeArguments] Identifier ( [ArgumentList] ) . [TypeArguments] Identifier ( [ArgumentList] ) super . [TypeArguments] Identifier ( [ArgumentList] ) . super . [TypeArguments] Identifier ( [ArgumentList] )
コンパイル時のメソッド名の解決は、メソッドがオーバーロードされる可能性があるため、フィールド名の解決よりも複雑です。 インスタンス・メソッドがオーバーライドされる可能性があるため、実行時にメソッドを呼び出すことも、フィールドにアクセスするよりも複雑になります。
メソッド呼出し式によって呼び出されるメソッドの決定には、いくつかのステップが含まれます。 次の3つの項では、メソッド呼出しのコンパイル時処理について説明します。 メソッド呼出し式の型の決定は、§15.12.3で指定されています。
メソッド呼出し式がスローできる例外型は、§11.2.1で指定します。
MethodInvocationの(の前に出現する右端の"."の左側の名前をTypeNameまたはExpressionName (§6.5.2)として分類できない場合、コンパイル時にエラーが発生します。
識別子の左側にTypeArgumentsが存在する場合、いずれかの型引数がワイルドカード(§4.5.1)である場合、コンパイル時にエラーが発生します。
メソッド呼出し式は、次のすべてに該当する場合の多式です。
それ以外の場合、メソッド呼出し式はスタンドアロン式です。
コンパイル時のメソッド呼出しの処理での最初のステップは、呼び出されるメソッドの名前およびその名前のメソッドの定義の検索対象の型を把握することです。
メソッドの名前は、MethodNameまたはIdentifierで指定します。この識別子は、MethodInvocationの左カッコの直前にあります。
検索する型については、MethodInvocationの左カッコの前にあるフォームに応じて、次の6つのケースを考慮する必要があります。
フォームがMethodName (つまり、識別子のみ)の場合は、次のようになります。
Identifierがその名前のメソッド宣言のスコープ(§6.3、§6.4.1)に表示される場合、次のようになります。
そのメソッドがメンバーである包含クラスまたはインタフェース宣言がある場合は、Eを最も内側のそのようなクラスまたはインタフェース宣言にします。 検索する型は、E.this (§15.8.4)の型です。
この検索ポリシーは、"組合せルール"と呼ばれます。 包含するクラスおよびそのスーパークラス階層内のメソッドを検索する前に、ネストされたクラス・スーパークラス階層内のメソッドを効率的に検索します。 例については、§6.5.7.1を参照してください。
それ以外の場合、メソッド宣言は、single-static-importまたはstatic-import-on-demand宣言のためにスコープ内にある可能性があります。 呼び出されるメソッドは後で決定されるため、検索する型はありません(§15.12.2.1)。
フォームがTypeName . [TypeArguments] Identifierの場合、検索する型はTypeNameで示される(おそらくRAW)型です。
フォームがExpressionName . [TypeArguments] Identifierの場合、検索する型は、Tがクラスまたはインタフェース型の場合はExpressionNameで示される変数の宣言された型T、Tが型変数の場合はTの上限です。
フォームが「プライマリ」 . 「[TypeArguments]」 「識別子」の場合は、「T」を「プライマリ」式のタイプにします。 検索する型は、T (Tがクラスまたはインタフェース型の場合)または T (Tが型変数の場合)の上限です。
Tが参照型でない場合は、コンパイル時にエラーが発生します。
フォームがsuper . [TypeArguments] 識別子の場合、検索する型は、メソッド呼出しを含む宣言を持つクラスの直接スーパークラス型です。
Eは、メソッド呼出しを直接包含するクラスまたはインタフェース宣言であるとします。 EがクラスObjectまたはインタフェースの場合、コンパイル時にエラーが発生します。
フォームがTypeName . super . [TypeArguments] Identifierの場合:
TypeNameがクラスもインタフェースも示さない場合、コンパイル時にエラーが発生します。
TypeNameがクラスCを示している場合、検索する型は直接スーパークラス型Cです。
Cがメソッド呼出しの字句で囲まれているクラス宣言でない場合、またはCがクラスObjectである場合、コンパイル時にエラーが発生します。
Eは、メソッド呼出しを直接包含するクラスまたはインタフェース宣言であるとします。 EがクラスObjectの場合、コンパイル時にエラーが発生します。
それ以外の場合、TypeNameはインタフェースIを示します。
Eは、メソッド呼出しを直接包含するクラスまたはインタフェース宣言であるとします。 IがEの直接スーパーインタフェースでない場合、またはJがIのサブクラスまたはサブインタフェースとなるように、E、Jの他の直接スーパークラスまたは直接スーパーインタフェースが存在する場合は、コンパイル時にエラーが発生します。
検索対象の型は、Eの直接スーパーインタフェース型であるIの型です。
TypeName . super構文はオーバーロードされます。従来、TypeNameは字句的に包含するクラス宣言を指し、ターゲットはこのクラスのスーパークラスです。これは、呼出しが字句的に包含するクラス宣言の修飾されていないsuperであるかのようになります。
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
}
};
}
回避策は、privateメソッドを、インタフェースsuperコールを実行する字句で包含するクラス宣言に導入することです。
2番目のステップでは、前のステップで決定した型でメンバー・メソッドを検索します。 このステップでは、メソッドの名前と引数式を使用して、accessibleとapplicableの両方、つまり、指定された引数で正しく起動できる宣言を検索します。
このようなメソッドが複数存在する場合があり、その場合は最も具体的なメソッドが選択されます。 最も具体的なメソッドの記述子(シグネチャと戻り値の型)は、メソッドのディスパッチを実行するために実行時に使われる記述子です。
暗黙的に型指定されたラムダ式(§15.27.1)または不正確なメソッド参照(§15.13.1)を含む特定の引数式は、呼出しのターゲット型が選択されるまで意味を決定できないため、適用性テストでは無視されます。 一方、メソッド呼出し式が多式であっても、適用性テストに影響を与えるのは、呼出しのターゲット・タイプではない引数式のみです。
適用性を決定するプロセスは、潜在的に適用可能なメソッド(§15.12.2.1)を決定することから始まります。 その後、Java SE 5.0より前のJavaプログラミング言語との互換性を確保するために、プロセスは3つのフェーズで続行されます。
最初のフェーズでは、ボクシングまたはアンボクシング変換、または可変アリティ・メソッド呼出しの使用を許可せずに、オーバーロード解決を実行します。 このフェーズで該当するメソッドが見つからない場合、処理は第2フェーズに進みます。
これにより、Java SE 5.0より前のJavaプログラミング言語で有効なコールは、可変アリティ・メソッドの導入、暗黙的なボクシングまたはアンボクシング(あるいはその両方)の結果としてあいまいとはみなされません。 ただし、可変アリティ・メソッド(§8.4.1)は、最初のフェーズで固定アリティ・メソッドとして扱われるため、特定のメソッド呼出し式に選択されたメソッドを変更できます。 たとえば、すでにm(Object)を宣言しているクラスでm(Object...)を宣言すると、m(Object[])がより具体的であるため、m(Object)が一部の呼出し式(m(null)など)に対して選択されなくなります。
2番目のフェーズでは、ボクシングおよびアンボクシングを許可しながらオーバーロード解決を実行しますが、可変アリティ・メソッド呼出しの使用は除外されます。 このフェーズで該当するメソッドが見つからない場合、処理は第3フェーズに進みます。
これにより、メソッドが固定のアリティ・メソッド呼出しによって適用される場合、可変のアリティ・メソッド呼出しによってメソッドが選択されることはありません。
第3フェーズでは、オーバーロードを可変アリティ・メソッド、ボクシングおよびアンボクシングと組み合せることができます。
メソッドが厳密な呼出し(第1フェーズ、§15.12.2.2)、ルース呼出し(第2フェーズ、§15.12.2.3)または可変アリティ呼出し(第3フェーズ、§15.12.2.4)のいずれかによって適用される場合、メソッドは適用可能です。 汎用メソッド(§8.4.4)の場合、メソッドが適用可能かどうかを判断するには、型引数の分析が必要です。 型引数は、明示的または暗黙的に渡すことができます。暗黙的に渡される場合は、型引数の境界を引数式(§18 (Type Inference))から推測する必要があります。
適用性テストの3つのフェーズのいずれかでいくつかの適用可能な方法が特定された場合、§15.12.2.5で指定されているように、最も具体的な方法が選択されます。
適用性をチェックするために、呼出しの引数の型は、通常、分析への入力にはできません。 この理由は次のとおりです。
メソッド呼出しの引数は、多式の場合があります。
多式は、ターゲット・タイプがないと入力できません。
引数のターゲット型がわかる前に、オーバーロードの解決を完了する必要があります。
かわりに、適用性チェックへの入力は、引数自体のリストです。 引数の最終的な型が不明な場合でも、潜在的なターゲット型との互換性を確認できます。
オーバーロード解決は、ターゲット・タイプとは無関係です。 これは、次の2つの理由によります。
まず、ユーザー・モデルにアクセスしやすくなり、エラーが発生しやすくなります。 メソッド名の意味(つまり、名前に対応する宣言)は、プログラムの意味にとってあまりに基本的なものであり、微妙なコンテキスト・ヒントに依存しません。 (対照的に、他の多式には、ターゲット型に応じて異なる動作がある場合がありますが、動作の変動は常に制限され、基本的に同等ですが、名前およびアリティを共有するメソッドの任意のセットの動作については、そのような保証はできません。)
次に、メソッドが多式であるかどうか(§15.12)、条件式を分類する方法(§15.25)などの他のプロパティが、ターゲット・タイプがわかる前であっても、メソッド名の意味に依存することを許可します。
例15.12.2-1 方法の適用
class Doubler {
static int two() { return two(1); }
private static int two(int i) { return 2*i; }
}
class Test extends Doubler {
static long two(long j) { return j+j; }
public static void main(String[] args) {
System.out.println(two(3));
System.out.println(Doubler.two(3)); // compile-time error
}
}
クラスDoubler内のメソッド呼出しtwo(1)には、twoという名前の2つのアクセス可能なメソッドがありますが、2番目のメソッドのみが適用可能であるため、実行時に呼び出されます。
クラスTest内のメソッド呼出しtwo(3)には2つの適用可能なメソッドがありますが、クラスTest内のメソッドのみがアクセス可能であるため、実行時に呼び出されるメソッドです(引数3はlong型に変換されます)。
メソッド呼出しDoubler.two(3)の場合、クラスTestではなくクラスDoublerでtwoという名前のメソッドが検索され、適用可能な唯一のメソッドにアクセスできないため、このメソッド呼出しによってコンパイル時エラーが発生します。
次に、別の例を示します。
class ColoredPoint {
int x, y;
byte color;
void setColor(byte color) { this.color = color; }
}
class Test {
public static void main(String[] args) {
ColoredPoint cp = new ColoredPoint();
byte color = 37;
cp.setColor(color);
cp.setColor(37); // compile-time error
}
}
ここでは、コンパイル時に適用可能なメソッドが見つからないため、setColorの2回目の呼出しでコンパイル時エラーが発生します。 リテラル37の型はintで、intは呼出し変換によってbyteに変換できません。 割当て変換は、変数colorの初期化で使用されるもので、int型からbyte型への定数の暗黙的な変換を実行します。これは、値37がbyte型で表現できるほど小さいため許可されますが、このような変換は呼出し変換では許可されません。
ただし、メソッドsetColorがbyteではなくintを取るように宣言されている場合、両方のメソッド呼出しは正しくなります。呼出し変換ではbyteからintへの拡張変換が許可されるため、最初の呼出しが許可されます。 ただし、setColorの本体には縮小キャストが必要です。
void setColor(int color) { this.color = (byte)color; }
次に、あいまいさのオーバーロードの例を示します。 プログラムについて考えてみます。
class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
static void test(ColoredPoint p, Point q) {
System.out.println("(ColoredPoint, Point)");
}
static void test(Point p, ColoredPoint q) {
System.out.println("(Point, ColoredPoint)");
}
public static void main(String[] args) {
ColoredPoint cp = new ColoredPoint();
test(cp, cp); // compile-time error
}
}
この例では、コンパイル時にエラーが生成されます。 問題は、適用可能でアクセス可能なtestの宣言が2つあり、どちらも他方より限定的ではないことです。 したがって、メソッドの呼出しはあいまいです。
testの3番目の定義が追加された場合:
static void test(ColoredPoint p, ColoredPoint q) {
System.out.println("(ColoredPoint, ColoredPoint)");
}
その場合、他の2つより具体的になり、メソッド呼出しがあいまいになることはなくなります。
例15.12.2-2. メソッドの選択時に考慮されない戻り型
class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
static int test(ColoredPoint p) {
return p.color;
}
static String test(Point p) {
return "Point";
}
public static void main(String[] args) {
ColoredPoint cp = new ColoredPoint();
String s = test(cp); // compile-time error
}
}
ここでは、メソッドtestの最も具体的な宣言は、ColoredPoint型のパラメータをとる宣言です。 メソッドの結果の型はintであるため、割当て変換によってintをStringに変換できないため、コンパイル時にエラーが発生します。 この例は、メソッドの結果型がオーバーロード・メソッドの解決に関与しないため、Stringを返す2番目のtestメソッドが選択されないことを示しています。ただし、このメソッドには、サンプル・プログラムがエラーなしでコンパイルできる結果型があります。
例15.12.2-3. 最も具体的な方法の選択
最も具体的なメソッドはコンパイル時に選択され、その記述子は実行時に実際に実行されるメソッドを決定します。 新しいメソッドがクラスに追加されると、クラスの古い定義を使用してコンパイルされたソース・コードでは、再コンパイルによってこのメソッドが選択される場合でも、新しいメソッドが使用されないことがあります。
したがって、たとえば、クラスPoint用に1つずつ、2つのコンパイル単位を考えてみます。
package points;
public class Point {
public int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public String toString() { return toString(""); }
public String toString(String s) {
return "(" + x + "," + y + s + ")";
}
}
1つはクラスColoredPoint用です。
package points;
public class ColoredPoint extends Point {
public static final int
RED = 0, GREEN = 1, BLUE = 2;
public static String[] COLORS =
{ "red", "green", "blue" };
public byte color;
public ColoredPoint(int x, int y, int color) {
super(x, y);
this.color = (byte)color;
}
/** Copy all relevant fields of the argument into
this ColoredPoint object. */
public void adopt(Point p) { x = p.x; y = p.y; }
public String toString() {
String s = "," + COLORS[color];
return super.toString(s);
}
}
次に、ColoredPointを使用する3番目のコンパイル・ユニットについて考えてみます。
import points.*;
class Test {
public static void main(String[] args) {
ColoredPoint cp =
new ColoredPoint(6, 6, ColoredPoint.RED);
ColoredPoint cp2 =
new ColoredPoint(3, 3, ColoredPoint.GREEN);
cp.adopt(cp2);
System.out.println("cp: " + cp);
}
}
出力は次のとおりです。
cp: (3,3,red)
クラスTestをコーディングしたプログラマは、実際の引数ColoredPointにcolorフィールドがあり、colorが関連フィールドであるように見えるため、greenという語を予期していました。 (もちろん、パッケージpointsのドキュメントは、はるかに正確であるべきです!)
ちなみに、adoptのメソッド呼出しの最も具体的なメソッド(実際に適用可能な唯一のメソッド)には、1つのパラメータのメソッドを示すシグネチャがあり、パラメータがPoint型であることに注意してください。 このシグネチャは、Javaコンパイラによって生成されるクラスTestのバイナリ表現の一部となり、実行時にメソッド呼出しによって使用されます。
プログラマがこのソフトウェア・エラーを報告し、pointsパッケージのメンテナンス担当者が、メソッドをクラスColoredPointに追加して修正することを意図的に決定したとします。
public void adopt(ColoredPoint p) {
adopt((Point)p);
color = p.color;
}
その後、プログラマがColoredPointの新しいバイナリ・ファイルを使用してTestの古いバイナリ・ファイルを実行した場合でも、出力は次のようになります。
cp: (3,3,red)
Testの古いバイナリ・ファイルには記述子「1つのパラメータ(型はPoint)、voidはメソッド・コールcp.adopt(cp2)に関連付けられています。 Testのソース・コードが再コンパイルされると、Javaコンパイラは、適用可能なadoptメソッドが2つあり、より具体的なメソッドのシグネチャが「1つのパラメータ(型はColoredPoint、void)」であることを検出します。プログラムを実行すると、必要な出力が生成されます。
cp: (3,3,green)
このような問題を事前に考えてみると、pointsパッケージのメンテナは、ColoredPointクラスを修正して、新しくコンパイルされたコードと古いコードの両方を処理できます。そのためには、古いadoptメソッドに、ColoredPoint引数でまだ起動する古いコードのために防御コードを追加します。
public void adopt(Point p) {
if (p instanceof ColoredPoint)
color = ((ColoredPoint)p).color;
x = p.x; y = p.y;
}
ソース・コードが依存するコードが変更されるたびに、ソース・コードを再コンパイルすることが理想的です。 ただし、異なるクラスが異なる組織によって保守される環境では、これは必ずしも実現可能ではありません。 クラス進化の問題に注意を払った防御プログラミングは、アップグレードされたコードをはるかに堅牢にすることができます。 バイナリ互換性および型進化の詳細は、§13 (Binary Compatibility)を参照してください。
コンパイル時ステップ1 (§15.12.1)で決定された型は、このメソッド呼出しに適用可能なすべてのメンバー・メソッドを検索します。スーパークラスおよびスーパーインタフェースから継承されたメンバーは、この検索に含まれます。
また、メソッド呼出し式の形式がMethodName (つまり、単一のIdentifier)の場合、適用可能な可能性のあるメソッドの検索では、インポートされるすべてのメンバー・メソッドも検査されますメソッド呼出しが発生し(§7.5.3、§7.5.4)、メソッド呼出しが表示される時点でシャドウ化されていないコンパイル・ユニットの単一静的インポート宣言および静的インポート・オン・デマンド宣言による。
メンバー・メソッドは、次のすべてに該当する場合にのみ、メソッド呼出しに潜在的に適用可能です。
メンバーの名前は、メソッド呼出しのメソッドの名前と同じです。
メンバーは、メソッド呼出しが出現するクラスまたはインタフェースからアクセス可能(§6.6)。
メソッド呼出しでメンバー・メソッドにアクセスできるかどうかは、メンバーの宣言内のアクセス修飾子(public、protected、修飾子なし(パッケージ・アクセス)、またはprivate)、およびコンパイル時ステップ1で決定されたクラスまたはインタフェースによるメンバーの継承、およびメソッド呼出しが表示される場所によって異なります。
メンバーがアリティnの固定アリティ・メソッドの場合、メソッド呼出しのアリティはnと等しく、すべてのi (1 ≤ i ≤ n)に対して、メソッド呼出しのi番目の引数は、メソッドのi番目のパラメータの型で、次に定義するように、i番目のパラメータの型と等しくなります。
メンバーが引数のnを持つ可変引数のメソッドである場合、すべてのi (1 ≤ i ≤ n-1)について、メソッド呼出しのi番目の引数が、メソッドのi番目のパラメータの型を持つ潜在的に互換性がある場合、およびメソッドのn番目のパラメータのタイプがT[]の場合、次のいずれかが当てはまります。
メソッド呼出しの引数は、n-1と同じです。
メソッド呼出しの引数はnと等しく、メソッド呼出しのn番目の引数は、TまたはT[]と互換性がある可能性があります。
メソッド呼出しの引数はmで、m > n、およびすべてのi (n ≤ i ≤ m)、メソッド呼出しのi番目の引数はTと互換性がある可能性があります。
メソッド呼出しに明示的な型引数が含まれ、メンバーが汎用メソッドである場合、型引数の数はメソッドの型パラメータの数と等しくなります。
この句は、非汎用メソッドが、明示的な型引数を提供する呼出しに適用できる可能性があることを意味します。 確かに適用可能になるかもしれません。 このような場合、型引数は単に無視されます。
このルールは、互換性の問題と代替性の原則から生じます。 インタフェースまたはスーパークラスはサブタイプとは無関係に生成できるため、汎用メソッドを非汎用メソッドでオーバーライドできます。 ただし、オーバーライド(非汎用)メソッドは、型引数を明示的に渡すコールを含め、汎用メソッドへのコールに適用できる必要があります。 それ以外の場合、サブタイプは生成済スーパータイプに代入できません。
検索で適用可能なメソッドが少なくとも1つ生成されない場合、コンパイル時にエラーが発生します。
式は、次のルールに従ってターゲット・タイプと潜在的に互換性があります。
メソッド参照式(§15.13)は、関数型Tの引数がnで、メソッド参照式が引数n(§15.13.1)の関数型をターゲットとする場合に、関数型Tと潜在的に互換性があり、次のいずれかが当てはまる場合、少なくとも1つの適用可能なメソッドが存在する可能性があります。
メソッド参照式の形式はReferenceType :: [TypeArguments] 識別子で、少なくとも1つの適用可能なメソッドが(i) staticで、アリティnをサポートしているか、(ii) staticではなく、アリティn-1をサポートしています。
メソッド参照式には別の形式があり、少なくとも1つの適用可能なメソッドがstaticではありません。
型変数が候補メソッドの型パラメータである場合、ラムダ式またはメソッド参照式は、型変数と互換性がある可能性があります。
カッコで囲まれた式(§15.8.5)は、含まれている式がその型と互換性がある可能性がある場合、型と互換性がある可能性があります。
条件式(§15.25)は、2番目と3番目の各オペランド式がその型と互換性がある可能性がある場合に、型と互換性がある可能性があります。
switch式(§15.28)は、すべての結果式がその型と互換性がある可能性がある場合に、型と互換性がある可能性があります。
クラス・インスタンス作成式、メソッド呼出し式、またはスタンドアロン形式(§15.2)の式には、任意の型との互換性がある可能性があります。
潜在的な適用の定義は、基本的な妥当性チェックを超えて、機能インタフェース・ターゲット・タイプの存在と「形状」も考慮します。 型引数の推論が含まれる場合、メソッド呼出し引数として出現するラムダ式は、オーバーロード解決後まで正しく入力できません。 これらのルールによって、ラムダ式の形式が引き続き考慮され、明らかに間違ったターゲット・タイプが破棄されるため、あいまいなエラーが発生する可能性があります。
引数式は、次の形式のいずれかでないかぎり、適用可能な可能性のあるメソッドmに対して適用性に関連があるとみなされます。
暗黙的に型指定されたラムダ式(§15.27.1)。
不正確なメソッド参照式(§15.13.1)。
mが汎用メソッドで、メソッド呼出しが明示的な型引数を提供しない場合、明示的に型指定されたラムダ式、または対応するターゲット型(mのシグネチャから導出)がmの型パラメータである正確なメソッド参照式。
本文が適用性に関連しない式である、明示的に型指定されたラムダ式。
ボディがブロックである明示的に型指定されたラムダ式で、少なくとも1つの結果式が適用可能とは関係ありません。
カッコで囲まれた式(§15.8.5)は適用性には関係ありません。
条件式(§15.25)で、第2または第3のオペランドが適用対象とは関係ありません。
mを、アリティnおよび仮パラメータ型F1、...、Fnを持つ適用可能なメソッド(§15.12.2.1)にし、e1、...、enをメソッド呼出しの実際の引数式にします。 次に、
mが汎用メソッドで、メソッド呼出しが明示的な型引数を提供しない場合、メソッドの適用性は§18.5.1で指定されているとおりに推測されます。
mが汎用メソッドで、メソッド呼出しが明示的な型引数を提供する場合、R1、...、Rp (p ≥ 1)をmの型パラメータにし、BlをRl (1 ≤ l ≤ p)の宣言された境界にし、U1、...、Upをメソッド呼出しで指定された明示的な型引数にします。 次の両方に該当する場合、mは厳密な呼出しによって適用可能です。
1 ≤ i ≤ nの場合、eiが適用性に関連する場合、eiはFi[R1:=U1、 ...、 Rp:=Up] (§5.3)との厳密な呼出しコンテキストで互換性があります。
≤ l ≤ pの場合、Ul <: Bl[R1:=U1、...、Rp:=Up]。
mが汎用メソッドでない場合、mは厳密な呼出しによって適用可能であり、1 ≤ i ≤ nの場合、eiはFi (§5.3)またはeiと厳密な呼出しコンテキストで互換性がありません。
厳密な呼出しで適用可能なメソッドが見つからない場合、適用可能なメソッドの検索はフェーズ2 (§15.12.2.3)で続行されます。
それ以外の場合、厳密な呼び出しによって適用可能なメソッドの中から、もっとも具体的なメソッド(§15.12.2.5)が選択されます。
暗黙的に型指定されたラムダ式または不正確なメソッド参照式の意味は、これらの式を含む引数が適用性に関連しているとはみなされないターゲット型を解決する前に十分に曖昧です。オーバーロード解決が終了するまで、単純に無視されます(期待されるアリティを除く)。
mを、アリティnおよび仮パラメータ型F1、...、Fnを持つ適用可能なメソッド(§15.12.2.1)にし、e1、...、enをメソッド呼出しの実際の引数式にします。 次に、
mが汎用メソッドで、メソッド呼出しが明示的な型引数を提供しない場合、メソッドの適用性は§18.5.1で指定されているとおりに推測されます。
mが汎用メソッドで、メソッド呼出しが明示的な型引数を提供する場合、R1、...、Rp (p ≥ 1)をmの型パラメータにし、BlをRl (1 ≤ l ≤ p)の宣言された境界にし、U1、...、Upをメソッド呼出しで指定された明示的な型引数にします。 次の両方に該当する場合、mはルース呼出しで適用可能です。
1 ≤ i ≤ nの場合、eiが適用性に関連している(§15.12.2.2)場合、eiは Fi[R1:=U1、 ...、 Rp:=Up] (§5.3)との緩やかな呼び出しコンテキストで互換性があります。
≤ l ≤ pの場合、Ul <: Bl[R1:=U1、...、Rp:=Up]。
mが汎用メソッドでない場合、mはルース呼出しによって適用可能であり、1 ≤ i ≤ nの場合、eiはFi (§5.3)とのルース呼出しコンテキストで互換性があるか、eiは適用性に関連していません。
ルーズ呼出しで適用可能なメソッドが見つからない場合、適用可能なメソッドの検索はフェーズ3 (§15.12.2.4)で続行されます。
それ以外の場合、最も具体的なメソッド(§15.12.2.5)は、ルーズ呼出しによって適用可能なメソッドの中から選択されます。
変数arityメソッドに仮パラメータ型F1、...、Fn-1、Fn[]がある場合、メソッドのi番目の変数arityパラメータ型を次のように定義します。
i ≤ n-1の場合、i番目の変数arityパラメータ型は Fiです。
i ≥ nの場合、i番目の可変引数の型はFnです。
mを、可変引数を持つ潜在的に適用可能なメソッド(§15.12.2.1)にし、T1、...、Tkをmの最初のk変数引数の型にし、e1、...、ekをメソッド呼出しの実際の引数式にします。 次に、
mが汎用メソッドで、メソッド呼出しが明示的な型引数を提供しない場合、メソッドの適用性は§18.5.1で指定されているとおりに推測されます。
mが汎用メソッドで、メソッド呼出しが明示的な型引数を提供する場合、R1、...、Rp (p ≥ 1)をmの型パラメータにし、BlをRl (1 ≤ l ≤ p)の宣言された境界にし、U1、...、Upをメソッド呼出しで指定された明示的な型引数にします。 次の場合、mは可変アリティ呼出しによって適用可能です。
1 ≤ i ≤ kの場合、eiが適用性に関連している(§15.12.2.2)場合、eiは Ti[R1:=U1、 ...、 Rp:=Up] (§5.3)との緩やかな呼び出しコンテキストで互換性があります。
≤ l ≤ pの場合、Ul <: Bl[R1:=U1、...、Rp:=Up]。
mが汎用メソッドでない場合、mは、1≤ i ≤ kの場合、Ti (§5.3)またはeiが適用性に関連しない場合に、Ti (§5.3)との緩やかな呼出しコンテキストでeiに互換性があります。
変数引数の呼出しで適用可能なメソッドが見つからない場合、コンパイル時にエラーが発生します。
それ以外の場合、最も具体的なメソッド(§15.12.2.5)は、可変アリティ呼出しで適用可能なメソッドの中から選択されます。
複数のメンバー・メソッドがアクセス可能で、1つのメソッド呼出しに適用可能な場合は、1つのメンバー・メソッドを選択して、ランタイム・メソッド・ディスパッチのディスクリプタを提供する必要があります。 Javaプログラミング言語では、最も具体的なメソッドが選択されているルールが使用されます。
非公式な直観は、最初のメソッドで処理された呼出しがコンパイル時エラーなしで別のメソッドに渡される可能性がある場合に、あるメソッドが別のメソッドより具体的であることです。 明示的に型指定されたラムダ式引数(§15.27.1)や可変引数の呼出し(§15.12.2.4)などの場合、あるシグネチャをもう一方のシグネチャに適応させる柔軟性があります。
適用可能なメソッドm1の1つが、適用可能な別のメソッドm2より限定的です。引数式e1、...、ekを使用した呼出しの場合、次のいずれかが当てはまります。
m2は汎用であり、m1は、§18.5.4によって、引数式e1、...、ekのm2より具体的であると推測されます。
m2は汎用ではなく、m1およびm2は厳密な呼出しまたは緩い呼出しによって適用され、m1にはすべてのiの引数eiの仮パラメータ・タイプS1およびm2の仮パラメータ・タイプT1、...、Tnの仮パラメータ・タイプSiはより具体的なであり、m1にはすべてのiの仮パラメータ・タイプ≤ i ≤ n、nにはk)の仮パラメータ・タイプeiTiより限定的です。
m2は汎用ではなく、m1およびm2は可変引数の呼出しによって適用され、m1の最初のk変数引数の型はS1、...、Skおよび最初のk変数引数の型はT1、...、Tk、型Siは、すべてのi (1 ≤ i221 ≤ k)の引数eiに対するTiよりもより具体的なです。 また、m2にk+1パラメータがある場合、k+1番目の可変アリティ・パラメータ・タイプm1は、k+1番目の可変アリティ・パラメータ・タイプm2のサブタイプです。
上記の条件は、ある方法が別の方法より具体的である可能性がある唯一の状況です。
S <: T (§4.10)の場合、any式の型Sは型Tより具体的です。
次のすべてに当てはまる場合、ファンクション・インタフェース・タイプSは、式eのファンクション・インタフェース・タイプTより固有です。
Sのインタフェースは、Tのインタフェースのスーパーインタフェースでもサブインタフェースでもありません。
Sまたは Tが交差型の場合、Sのどのインタフェースも Tの任意のインタフェースのスーパーインタフェースまたはサブインタフェースであるとはかぎりません。 (交差タイプの「インタフェース」は、この交差で(パラメータ化された)インタフェース・タイプとして表示される一連のインタフェースを参照します。)
MTSをSの取得のファンクション・タイプにし、MTTをTのファンクション・タイプにします。 MTSおよびMTTは、同じ型パラメータ(ある場合)を持つ必要があります(§8.4.4)。
P1、 ...、 PnをMTTの型パラメータに適合したMTSの仮パラメータ型にします。 P1'、 ...、 Pn'は、MTTの型パラメータに適合した、S (取得なし)の関数型の仮パラメータ型になります。 Q1、 ...、 QnをMTTの仮パラメータ型にします。 次に、すべての i (1 ≤ i ≤ n)について、Qi <: Piおよび Qi = Pi'。
通常、このルールは、SおよびTから導出された仮パラメータ・タイプが同じであることを宣言します。 ただし、Sがワイルドカード・パラメータ型の場合、取得変数を仮パラメータ型(最初に、各仮パラメータ型T)で発生させるには、チェックがより複雑になります。Sのキャプチャの対応する仮パラメータ型のサブタイプである必要があります。2つ目は、ワイルドカードをそれらの境界にマップした後(§9.9)、結果の関数型の仮パラメータ型は同じです。
RSをMTSの戻り型にし、MTTの型パラメータに適応させ、RTをMTTの戻り型にします。 次の条件の1つに該当している必要があります。
eは、明示的に型指定されたラムダ式(§15.27.1)であり、次のいずれかが当てはまります。
eは正確なメソッド参照式(§15.13.1)であり、次のいずれかが当てはまります。
Rはvoidです。
RS <: RT。
RSはプリミティブ型で、RTは参照型で、メソッド参照のコンパイル時宣言にはプリミティブ型である戻り型があります。
RSは参照型で、RTはプリミティブ型で、メソッド参照のコンパイル時宣言には参照型である戻り型があります。
eはカッコで囲まれた式で、これらの条件の1つが、含まれている式に再帰的に適用されます。
eは条件式であり、2番目と3番目の各オペランドについて、これらの条件の1つが再帰的に適用されます。
eはswitch式で、各結果式に対して、これらの条件の1つが再帰的に適用されます。
メソッドm1は、m1がm2より具体的で、m2がm1より限定的でない場合にのみ、別のメソッドm2より厳密には具体的です。
メソッドがアクセス可能かつ適用可能であり、より厳密に適用されるアクセス可能かつ適用可能なメソッドが他にない場合、メソッドはメソッド呼出しに対して最大固有であるとみなされます。
最大固有メソッドが1つしかない場合、そのメソッドは実際には最も固有なメソッドであり、適用可能な他のアクセス可能なメソッドよりも固有であることが必要です。 その後、§15.12.3で指定されたコンパイル時チェックがさらに実施されます。
最大限固有のメソッドが2つ以上あるため、最も具体的なメソッドがない可能性があります。 この場合、次のようになります。
最大固有のすべてのメソッドに、オーバーライド等価のシグネチャ(§8.4.2)があり、最大固有メソッドの正確に1つが具象(つまり、abstractもデフォルトも)である場合、最も具体的なメソッドです。
それ以外の場合、最大固有のすべてのメソッドにオーバーライド等価のシグネチャがあり、すべてで最大固有のメソッドがabstractまたはdefaultであり、これらのメソッドの宣言に同じ消去されたパラメータ・タイプがある場合、そして、次のルールに従って、少なくとも1つの最大固有メソッドが優先であり、最も具体的なメソッドは、優先される最大固有メソッドのサブセットの中から任意に選択されます。 最も具体的なメソッドは、abstractと見なされます。
最大固有メソッドは、次のものがある場合に優先されます:
すべての最大固有メソッドのシグネチャのサブシグネチャであるシグネチャ。
戻り型R(voidなど)で、Rは最大固有のすべてのメソッドの戻り型と同じか、Rは参照型であり、2つのメソッドが同じシグネチャを持つ場合、最大固有のすべてのメソッドの戻り型のサブタイプです(任意の型パラメータ(§8.4.4)に適応した後)。
前述のルールに従って優先メソッドが存在しない場合、最大固有メソッドは次の場合に優先になります。
すべての最大固有メソッドのシグネチャのサブシグネチャであるシグネチャを持ちます。
は、最大限固有のすべてのメソッドのreturn-type-substitutable (§8.4.5)です。
最も具体的なメソッドのスローされた例外タイプは、次のように、最大固有メソッドのthrows句から導出されます。
最も具体的なメソッドが汎用である場合、throws句は、最も具体的なメソッド(§8.4.4)の型パラメータに最初に適応されます。
最も具体的なメソッドが汎用的ではなく、少なくとも1つの最大固有メソッドが汎用的である場合、throws句は最初に消去されます。
次に、スローされた例外タイプには、次の制約を満たすすべてのタイプEが含まれます。
Eは、throws句のいずれかで記述されています。
throws句ごとに、Eは、その句で指定されたタイプのサブタイプです。
オーバーロードされたメソッドのグループから単一のメソッド型を導出するためのこれらのルールは、関数型インタフェース(§9.9)の関数型を識別するためにも使用されます。
それ以外の場合、メソッドの呼出しはあいまいであり、コンパイル時にエラーが発生します。
最もアクセス可能で適用可能なメソッドの呼出しタイプは、呼出し引数のターゲット・タイプ、呼出しの結果(戻り型またはvoid)、および呼出しの例外タイプを表すメソッド・タイプ(§8.2)です。 次のように決定されます。
選択したメソッドが汎用で、メソッド呼出しが明示的な型引数を提供しない場合、呼出しタイプは§18.5.2で指定されているように推測されます。
この場合、メソッド呼出し式が多式の場合、ターゲット・タイプとの互換性は§18.5.2.1によって決定されます。
ターゲット・タイプとの互換性のテストは、メソッド呼出し式のターゲット・タイプおよび呼出しタイプの最終的な決定を行う前に複数回行われる場合があります。 たとえば、包含メソッド呼出し式では、様々なメソッドの仮パラメータ型との互換性のために、より深いメソッド呼出し式のテストが必要になる場合があります。
選択したメソッドが汎用で、メソッド呼出しが明示的な型引数を提供する場合、Piをメソッドの型パラメータとし、Tiをメソッド呼出しに提供される明示的な型引数にします(1 ≤ i ≤ p)。 次に、
メソッドを適用するために未チェックの変換が必要であった場合、メソッドの型のパラメータ型に置換[P1:=T1、...、Pp:=Tp]を適用することによって、呼出しタイプのパラメータ型が取得され、メソッドの型の戻り型およびスローされた型の消去によって呼出しタイプの戻り型およびスローされた型が取得されます。
メソッドが適用可能であるために、チェックされていない変換が不要な場合は、置換[P1:=T1、 ...、 Pp:=Tp]をメソッドの型に適用することによって、起動タイプが取得されます。
選択したメソッドが汎用でない場合は、次のようになります。
メソッド呼出しに対して最も具体的なメソッド宣言がある場合は、メソッド呼出しに対してコンパイル時宣言と呼ばれます。
メソッド呼出しの引数が、コンパイル時宣言の呼出しタイプから導出されたターゲット・タイプと互換性がない場合は、コンパイル時エラーです。
コンパイル時宣言が可変アリティ呼出しによって適用される場合、メソッドの呼出しタイプの最後の仮パラメータ型がFn[]の場合、Fnの消去である型が呼出しの時点でアクセスできない(§6.6)ときは、コンパイル時にエラーが発生します。
コンパイル時宣言がvoidの場合、メソッド呼出しは最上位の式(つまり、式文の式、またはfor文のForInitまたはForUpdate部分)である必要があります。そうしないと、コンパイル時にエラーが発生します。 このようなメソッド呼出しでは値が生成されないため、値が不要な状況でのみ使用する必要があります。
また、コンパイル時の宣言が適切かどうかは、次のように、左カッコの前のメソッド呼出し式の形式によって異なります:
フォームがMethodName (つまり、識別子のみ)であり、コンパイル時宣言がインスタンス・メソッドである場合、次のようになります。
フォームがTypeName . [TypeArguments] Identifierの場合、コンパイル時宣言はstaticである必要があります。そうでないと、コンパイル時にエラーが発生します。
フォームがExpressionName . [TypeArguments] IdentifierまたはPrimary . [TypeArguments] Identifierの場合、コンパイル時宣言はインタフェースで宣言されたstaticメソッドであってはなりません。そうしないと、コンパイル時にエラーが発生します。
フォームがsuper . [TypeArguments] 識別子の場合:
コンパイル時の宣言がabstractの場合、コンパイル時にエラーが発生します。
メソッド呼出しが静的コンテキストで発生した場合は、コンパイル時エラーです。
メソッドの呼出しが現在のクラスの初期構成コンテキストで発生した場合、コンパイル時にエラーが発生します。
フォームがTypeName . super . [TypeArguments] Identifierの場合:
コンパイル時の宣言がabstractの場合、コンパイル時にエラーが発生します。
メソッド呼出しが静的コンテキストで発生した場合は、コンパイル時エラーです。
TypeNameで指定されたクラスの初期構成コンテキストでメソッド呼出しが発生した場合、コンパイル時にエラーが発生します。
TypeNameがクラスCを示している場合、メソッド呼出しを直ちに囲むクラスまたはインタフェース宣言がCまたは内部クラスCでないと、コンパイル時にエラーが発生します。
TypeNameがインタフェースを示す場合、Eをメソッド呼出しを直ちに囲むクラスまたはインタフェース宣言にします。 コンパイル時宣言とは異なるメソッドが存在し、Eの直接スーパークラスまたは直接スーパーインタフェースからのコンパイル時宣言をオーバーライドする(§9.4.1)場合、コンパイル時エラーが発生します。
スーパー・インタフェースが祖父母インタフェースで宣言されたメソッドをオーバーライドする場合、このルールでは、祖父母を直接のスーパー・インタフェースのリストに追加するだけで、子インタフェースが"スキップ"からオーバーライドされないようにします。 祖父母の機能にアクセスする適切な方法は、直接のスーパー・インタフェースを介して、そのインタフェースが目的の動作を公開することを選択した場合のみです。 (または、プログラマは、superメソッド呼出しで必要な動作を公開する追加のスーパーインタフェースを自由に定義できます。)
compile-timeパラメータ・タイプおよびcompile-time resultは次のように決定されます。
メソッド呼出しのコンパイル時宣言がシグネチャの多相メソッドでない場合、次のようになります。
コンパイル時パラメータ型は、コンパイル時宣言の仮パラメータの型です。
コンパイル時の結果は、コンパイル時宣言の呼出しタイプ(§15.12.2.6)の結果です。
メソッド呼出しのコンパイル時宣言がシグネチャ多相メソッドの場合は、次のようになります:
コンパイル時のパラメータ型は、実際の引数式の型です。 nullリテラルnullである引数式(§3.10.8)は、Void型を持つものとして扱われます。
コンパイル時の結果は次のように決定されます:
署名多相メソッドがvoidであるか、Object以外の戻り型を持つ場合、コンパイル時の結果はコンパイル時宣言の呼出し型(§15.12.2.6)の結果です。
それ以外の場合、メソッド呼出し式が式文の場合、コンパイル時の結果はvoidになります。
それ以外の場合、メソッド呼出し式がキャスト式のオペランド(§15.16)である場合、コンパイル時の結果はキャスト式の型の消去になります(§4.6)。
それ以外の場合、コンパイル時の結果は、シグネチャ多相メソッドの戻り型Objectになります。
次のすべてに当てはまる場合、メソッドは署名多相です。
これは、java.lang.invoke.MethodHandleクラスまたはjava.lang.invoke.VarHandleクラスで宣言されます。
これには、宣言された型がObject[]である単一の可変引数パラメータ(§8.4.1)があります。
nativeです。
次のコンパイル時情報は、実行時に使用するメソッド呼出しに関連付けられます:
メソッドの名前。
メソッド呼出しの修飾クラスまたはインタフェース(§13.1)。
パラメータの数とコンパイル時のパラメータの型。
コンパイル時の結果。
呼出しモード。次のように計算されます:
コンパイル時宣言にstatic修飾子がある場合、起動モードはstaticです。
それ以外の場合、左カッコの前のメソッド呼出しの部分がsuper .の形式の場合 識別子またはTypeName . super .の形式 識別子。起動モードはsuperです。
それ以外の場合、メソッド呼出しの修飾クラスまたはインタフェースが実際はインタフェースである場合、呼出しモードはinterfaceです。
それ以外の場合、呼出しモードはvirtualです。
コンパイル時宣言の呼出し型の結果がvoidでない場合、取得変換(§5.1.10)をコンパイル時宣言の呼出し型の戻り型に適用することで、メソッド呼出し式の型が取得されます。
実行時に、メソッドの起動には5つのステップが必要です。 まず、ターゲット参照を計算できます。 次に、引数式が評価されます。 次に、呼び出されるメソッドのアクセシビリティをチェックします。 4つ目は、実行するメソッドの実際のコードです。 5番目には、新しいアクティブ化フレームが作成され、必要に応じて同期が実行され、制御がメソッド・コードに転送されます。
メソッド呼出しの形式に応じて、次の6つのケースを考慮する必要があります。
フォームがMethodName (識別子のみ)の場合:
起動モードがstaticの場合、ターゲット参照はありません。
それ以外の場合は、Tをメソッドがメンバーである包含型宣言にし、nを整数にし、Tがメソッド呼出しをすぐに含むクラスのn字句的に包含する型宣言になるようにします。 ターゲット参照は、thisのn番目の字句エンクロージャ・インスタンスです。
nの字句で囲んでいるthisインスタンスが存在しない場合、コンパイル時にエラーが発生します。
フォームがTypeName . [TypeArguments] Identifierの場合、ターゲット参照はありません。
フォームがExpressionName . [TypeArguments] Identifierの場合:
起動モードがstaticの場合、ターゲット参照はありません。 ExpressionNameは評価されますが、結果は破棄されます。
それ以外の場合、ターゲット参照はExpressionNameで示される値です。
フォームが「プライマリ」 . [TypeArguments] 「識別子」に含まれている場合は、次のようにします。
起動モードがstaticの場合、ターゲット参照はありません。 プライマリ式は評価されますが、結果は破棄されます。
それ以外の場合は、プライマリ式が評価され、結果がターゲット参照として使用されます。
いずれの場合も、プライマリ式の評価が突然完了した場合、どの引数式も評価されていないように見え、同じ理由でメソッド呼出しが突然完了します。
フォームがsuper . [TypeArguments] 識別子の場合、ターゲット参照はthisの値になります。
フォームがTypeName . super . [TypeArguments] Identifierの場合、TypeNameがクラスを示すと、ターゲット参照はTypeName.thisの値になります。それ以外の場合、ターゲット参照はthisの値になります。
例 15.12.4.1-1. ターゲット参照およびstaticメソッド
呼出しモードがstaticであるため、ターゲット参照が計算されて破棄された場合、参照がnullかどうかは調べられません。
class Test1 {
static void mountain() {
System.out.println("Monadnock");
}
static Test1 favorite(){
System.out.print("Mount ");
return null;
}
public static void main(String[] args) {
favorite().mountain();
}
}
次のように出力されます:
Mount Monadnock
ここで、favorite()はnullを返しますが、NullPointerExceptionはスローされません。
例 15.12.4.1-2. メソッド起動時の評価順序
インスタンス・メソッド呼出し(§15.12)の一部として、呼び出されるオブジェクトを示す式があります。 この式は、メソッド呼出しに対する引数式のどの部分も評価される前に完全に評価されたように見えます。
たとえば、次の場合:
class Test2 {
public static void main(String[] args) {
String s = "one";
if (s.startsWith(s = "two"))
System.out.println("oops");
}
}
引数式s = "two"の前に、.startsWithの前にsが最初に評価されます。 したがって、文字列"one"への参照は、ローカル変数sが文字列"two"を参照するように変更される前に、ターゲット参照として記憶されます。 その結果、引数"two"を持つターゲット・オブジェクト"one"に対してstartsWithメソッドが呼び出されるため、文字列"one"が"two"で始まらないため、呼出しの結果はfalseになります。 この場合、テスト・プログラムは「oops」を出力しません。
引数リストを評価するプロセスは、呼び出されるメソッドが固定アリティ・メソッドか可変アリティ・メソッド(§8.4.1)かによって異なります。
呼び出されるメソッドが可変のアリティ・メソッドmの場合、必ずしもn > 0の仮パラメータと、mの呼出しタイプ(§15.12.2.6)のn番目のパラメータ・タイプには、T[]の型が必ずTになります。 kは、メソッド呼出しの実際の引数式の数です。k ≠ nの場合、またはk = nで、k番目の引数式がT[]との代入互換でない場合、mは可変アリティ呼出し(§15.12.2.4)によって適用可能であることがわかります。 この場合、引数リスト(e1、 ...、 en-1、 en、 ...、 ek)は、(e1、 ...、 en-1、 new |T[]| { en、 ...、 ek })として記述されたかのように評価されます。ここで、|T[]|はT[]の消去(§4.6)を表します。
前述の段落は、Java Virtual Machineで発生するパラメータ化された型と配列型と消去されたジェネリックスとの相互作用を処理するために作成されています。 つまり、変数配列パラメータの要素タイプTがList<String>などの変更不可能な場合、作成された配列の要素タイプを再有効化する必要があるため、配列作成式(§15.10)で特別な注意を払う必要があります。 引数リストの最後の式の配列型を消去することで、再利用可能な要素型が得られることが保証されます。 次に、配列作成式が呼出しコンテキスト(§5.3)に表示されるため、再可能な要素型を持つ配列型から、非再可能な要素型を持つ配列型(特に変数arityパラメータの型)への未チェックの変換が可能です。 Javaコンパイラは、この変換時にコンパイル時にチェックされていない警告を出すために必要です。 OracleのJavaコンパイラのリファレンス実装では、この未チェックの警告は、より有益な未チェックの汎用配列作成として識別されます。
引数式(前述のようにリライトされる可能性がある)が評価され、引数値が得られます。 各引数値は、メソッドのn仮パラメータの1つのみに対応します。
引数式がある場合は、左から右の順に評価されます。 引数式の評価が突然完了した場合、右側の引数式の一部が評価されていないように見え、同じ理由でメソッドの呼出しが突然完了します。 j番目の引数式を評価した結果は、j番目の引数値であり、1 ≤ j ≤ nになります。 次に、次に説明するように、引数値を使用して評価を続行します。
このセクションでは:
Javaプログラミング言語の実装では、リンクの一部として、クラスまたはインタフェース Qがアクセス可能であることを確認する必要があります。
Qが Dと同じパッケージにある場合は、Qにアクセスできます。
Qが Dとは異なるパッケージ内にあり、それらのパッケージが同じモジュール内にあり、Qが publicまたは protectedの場合、Qにアクセスできます。
Qが Dとは異なるパッケージ内にあり、それらのパッケージが異なるモジュール内にあり、Qのモジュールが Qのパッケージを Dのモジュールにエクスポートし、Qが publicまたは protectedの場合、Qにアクセスできます。
Qがprotectedの場合、必ずしもネストされたクラスまたはインタフェースであるため、コンパイル時に、そのアクセシビリティは、宣言を囲むクラスおよびインタフェースのアクセシビリティの影響を受けます。 ただし、リンク中、そのアクセシビリティは、宣言を囲むクラスおよびインタフェースのアクセシビリティの影響を受けません。 さらに、リンク中、protected Qにはpublic Qとしてアクセスできます。 コンパイル時におけるアクセス制御(§6.6)と実行時におけるアクセス制御の相違点は、Java Virtual Machineの制限によるものです。
実装では、リンケージ中に、メソッドmがQまたはQのスーパークラスまたはスーパーインタフェースで引き続き見つかることも確認する必要があります。 mが見つからない場合、NoSuchMethodError (IncompatibleClassChangeErrorのサブクラス)が発生します。 mが見つかった場合は、Cをmを宣言するクラスまたはインタフェースにします。 実装では、リンク時にCのm宣言にDからアクセスできることを確認する必要があります。
mがpublicの場合、mにアクセスできます。
mがprotectedの場合、mは、(i) DがCと同じパッケージ内にあるか、DがCまたはC自体のサブクラスである場合、および(ii) mがprotectedインスタンス・メソッドである場合、QはDまたはD自体のサブクラスである必要があります。
protectedインスタンス・メソッドは、実行者の型と一致する修飾クラスまたはインタフェースを介してのみ起動できるため、Qがmのチェックに関与する唯一の場所です。
mにパッケージ・アクセス権がある場合、DがCと同じパッケージにある場合は、mにアクセスできます。
mがprivateの場合、DがCの場合、またはDがCを囲んでいる場合、またはCがDを囲んでいる場合、またはCとDの両方が3番目のクラスまたはインタフェースで囲まれている場合は、mにアクセスできます。
Qまたはmにアクセスできない場合、IllegalAccessErrorが発生します(§12.3)。
起動モードがinterfaceの場合、実装では、ターゲット参照クラスが指定されたインタフェースを実装していることを確認する必要があります。 ターゲット参照クラスがまだインタフェースを実装していない場合、IncompatibleClassChangeErrorが発生します。
前の項(§15.12.4.3):
Qをメソッド呼出しの修飾クラスまたはインタフェースにします(§13.1)。
mを、Qにあるメソッド、またはQのスーパークラスまたはスーパーインタフェースにします。 (mは、前の項のメソッドの名前にすぎません。ここでは実際の宣言です。)
Cをmを宣言するクラスまたはインタフェースにします。
呼び出すメソッドを特定するための戦略は、呼出しモードに応じて異なります。
起動モードがstaticの場合、ターゲット参照は不要で、オーバーライドは許可されません。 クラスまたはインタフェースCのメソッドmは、呼び出されるメソッドです。
それ以外の場合、インスタンス・メソッドが呼び出され、ターゲット参照が存在します。 ターゲット参照がnullの場合は、この時点でNullPointerExceptionがスローされます。 それ以外の場合、ターゲット参照はターゲット・オブジェクトを参照し、呼び出されたメソッドでキーワードthisの値として使用されます。 その後、呼出しモードの他の3つの可能性が検討されます。
起動モードがsuperの場合、オーバーライドは許可されません。 クラスまたはインタフェースCのメソッドmは、呼び出されるメソッドです。 mがabstractの場合は、AbstractMethodErrorがスローされます。
それ以外の場合、呼出しモードがvirtualで、Qとmが署名多相メソッド(§15.12.3)を共同で示している場合、ターゲット・オブジェクトはjava.lang.invoke.MethodHandleまたはjava.lang.invoke.VarHandleのインスタンスです。 ターゲット・オブジェクトは、コンパイル時のメソッド呼出しに関連付けられた情報に対して一致する状態をカプセル化します。 この一致の詳細は、Java仮想マシン仕様、Java SE 26 EditionとJava SEプラットフォームAPIに示されています。 一致が成功した場合、java.lang.invoke.MethodHandleインスタンスによって参照されるメソッドが直接かつ即時に呼び出されるか、またはjava.lang.invoke.VarHandleインスタンスによって表される変数が直接かつ即時にアクセスされ、いずれの場合も§15.12.4.5のプロシージャは実行されません。 一致が失敗すると、java.lang.invoke.WrongMethodTypeExceptionがスローされます。
それ以外の場合、呼出しモードはinterfaceまたはvirtualです。
クラスまたはインタフェースCのメソッドmがprivateの場合は、呼び出されるメソッドです。
そうでない場合、オーバーライドが発生する可能性があります。 次に示す動的メソッド・ルックアップは、呼び出すメソッドの検索に使用されます。 参照手順は、ターゲット・オブジェクトの実際の実行時クラスであるクラスRから開始します。
呼出しモードinterfaceの場合、Rは必ずQを実装します。呼出しモードvirtualの場合、Rは必ずしもQまたはQのサブクラスのいずれかです。 ターゲット・オブジェクトが配列の場合、Rは、配列型を表す「クラス」です。
動的メソッド検索の手順は、次のとおりです。 Sを Rで始まる検索対象のクラスにします。 次に、
クラスSに、R(§8.4.8.1)のクラスまたはインタフェースCのメソッドmをオーバーライドするメソッドの宣言が含まれている場合、オーバーライドするメソッドが呼び出されるメソッドであり、プロシージャが終了します。
それ以外の場合、Sにスーパークラスがある場合、この参照プロシージャのステップ1および2は、SのかわりにSの直接スーパークラスを使用して再帰的に実行されます。呼び出されるメソッド(ある場合)は、この参照プロシージャの再帰的呼出しの結果です。
前の2つの手順でメソッドが見つからない場合は、Sのスーパーインタフェースで適切なメソッドが検索されます。
候補メソッドのセットは、次のプロパティで考慮されます。(i)各メソッドは、Rの(直接または間接)スーパーインタフェースで宣言されます。(ii)各メソッドには、メソッド呼出しに必要な名前と記述子があります。(iii)各メソッドは、非staticおよび非privateです。(iv)メソッドの宣言インタフェースがIの場合、Iのサブインタフェースで宣言されている、(i)から(iii)を満たす他のメソッドはありません。
このセットにデフォルト・メソッドが含まれている場合、このようなメソッドの1つが呼び出されるメソッドです。 それ以外の場合は、セット内のabstractメソッドが呼び出されるメソッドとして選択されます。
動的メソッド・ルックアップにより、次のエラーが発生する可能性があります。
呼び出されるメソッドがabstractの場合は、AbstractMethodErrorがスローされます。
起動するメソッドがdefaultで、前述のステップ3の候補のセットに複数のデフォルト・メソッドが存在する場合は、IncompatibleClassChangeErrorがスローされます。
起動モードがinterfaceで、呼び出されるメソッドがpublicでもprivateでもない場合、IllegalAccessErrorがスローされます。
前述のプロシージャ(エラーなしで終了した場合)では、プログラム内のすべてのクラスおよびインタフェースが一貫してコンパイルされていれば、呼び出すアクセス可能なabstract以外のメソッドが検索されます。 ただし、そうでない場合は、前述のように様々なエラーが発生する可能性があります。このような状況でのJava Virtual Machineの動作の詳細は、Java Virtual Machine仕様、Java SE 26 Editionを参照してください。
動的参照プロセスは、ここで説明したものの、多くの場合、暗黙的に実装されます。たとえば、クラスごとのメソッド・ディスパッチ表の構築と使用、または効率的なディスパッチに使用される他のクラスごとの構造の構築の副作用として。
例 15.12.4.4-1. オーバーライドおよびメソッド呼出し
class Point {
final int EDGE = 20;
int x, y;
void move(int dx, int dy) {
x += dx; y += dy;
if (Math.abs(x) >= EDGE || Math.abs(y) >= EDGE)
clear();
}
void clear() {
System.out.println("\tPoint clear");
x = 0; y = 0;
}
}
class ColoredPoint extends Point {
int color;
void clear() {
System.out.println("\tColoredPoint clear");
super.clear();
color = 0;
}
}
ここでは、サブクラスColoredPointは、そのスーパークラスPointで定義されたclear抽象化を拡張します。 これを行うには、clearメソッドを独自のメソッドでオーバーライドします。このメソッドは、super.clear()という形式を使用してスーパークラスのclearメソッドを呼び出します。
このメソッドは、clearの起動のターゲット・オブジェクトがColoredPointであるたびに呼び出されます。 Pointのメソッドmoveでも、thisのクラスがColoredPointの場合、このテスト・プログラムの出力で示すように、クラスColoredPointのclearメソッドを呼び出します。
class Test1 {
public static void main(String[] args) {
Point p = new Point();
System.out.println("p.move(20,20):");
p.move(20, 20);
ColoredPoint cp = new ColoredPoint();
System.out.println("cp.move(20,20):");
cp.move(20, 20);
p = new ColoredPoint();
System.out.println("p.move(20,20), p colored:");
p.move(20, 20);
}
}
つまり、次のようになります。
p.move(20,20):
Point clear
cp.move(20,20):
ColoredPoint clear
Point clear
p.move(20,20), p colored:
ColoredPoint clear
Point clear
オーバーライドは、遅延バインドされた自己参照と呼ばれることもあります。この例では、Point.moveの本体内のclearへの参照(実際にはthis.clearの構文の短縮形)によって、選択されたメソッドが呼び出されることを意味します"late" (thisによって参照されるオブジェクトの実行時クラスに基づくランタイム時)は、thisの型のみに基づくコンパイル時ではなく、"early" (コンパイル時)。 これは、プログラマに抽象化を拡張する強力な方法を提供し、オブジェクト指向プログラミングの重要なアイデアです。
例 15.12.4.4-2. superを使用したメソッド呼出し
スーパークラスのオーバーライドされたインスタンス・メソッドにアクセスするには、キーワードsuperを使用して即時スーパークラスのメンバーにアクセスし、メソッド呼出しを含むクラスのオーバーライド宣言をバイパスします。
インスタンス変数にアクセスする場合、superはthisのキャスト(§15.11.2)と同じ意味ですが、この等価はメソッド呼出しに対してtrueを保持しません。 これは、次の例で示されています。
class T1 {
String s() { return "1"; }
}
class T2 extends T1 {
String s() { return "2"; }
}
class T3 extends T2 {
String s() { return "3"; }
void test() {
System.out.println("s()=\t\t" + s());
System.out.println("super.s()=\t" + super.s());
System.out.println("((T2)this).s()=\t" + ((T2)this).s());
System.out.println("((T1)this).s()=\t" + ((T1)this).s());
}
}
class Test2 {
public static void main(String[] args) {
T3 t3 = new T3();
t3.test();
}
}
出力を生成します。
s()= 3 super.s()= 2 ((T2)this).s()= 3 ((T1)this).s()= 3
T1型およびT2型へのキャストでは、呼び出されるメソッドが変更されません。これは、呼び出されるインスタンス・メソッドが、thisによって参照されるオブジェクトの実行時クラスに従って選択されるためです。 キャストはオブジェクトのクラスを変更せず、そのクラスが指定された型と互換性があることをチェックするだけです。
一部のクラスSのメソッドmは、呼び出されるメソッドとして識別されています。
新しいアクティブ化フレームが作成され、ターゲット参照(存在する場合)、引数値(存在する場合)、およびローカル変数用の十分な領域が含まれます。呼び出されるメソッドと、実装で必要となる可能性のあるその他の経理情報(スタックポインタ、プログラムカウンタ、前のアクティベーションフレームへの参照など)のスタック。 このようなアクティブ化フレームを作成するために使用できる十分なメモリーがない場合は、StackOverflowErrorがスローされます。
新しく作成されたアクティブ化フレームが現在のアクティブ化フレームになります。 この効果は、メソッドの新しく作成された対応するパラメータ変数に引数値を代入し、ターゲット参照がthis (ターゲット参照がある場合)として使用できるようにすることです。 各引数値が対応するパラメータ変数に代入される前に、呼出し変換の対象となります(§5.3)。
呼び出されるメソッドの型の消去(§4.6)が、メソッド呼出しのコンパイル時宣言の型の消去(§15.12.3)と署名で異なる場合、もしあれば、引数値のうち、メソッド呼出しのコンパイル時宣言における対応する仮パラメータ型の消去のサブクラスまたはサブインタフェースのインスタンスではないオブジェクトです。その後、ClassCastExceptionがスローされます。
メソッドmがnativeメソッドであるが、必要なネイティブで実装に依存するバイナリ・コードがロードされていないか、または動的にリンクできない場合、UnsatisfiedLinkErrorがスローされます。
メソッドmがsynchronizedでない場合、制御は呼び出されるメソッドmの本体に転送されます。
メソッドmがsynchronizedの場合、制御の転送前にオブジェクトをロックする必要があります。 現在のスレッドがロックを取得するまで、これ以上の進行はできません。 ターゲット参照がある場合は、ターゲット・オブジェクトをロックする必要があります。そうでない場合は、メソッドmのクラスであるクラスSのClassオブジェクトをロックする必要があります。 その後、呼び出されるメソッドmの本体に制御が転送されます。 オブジェクトは、メソッドの本体の実行が完了すると、通常どおりに、または突然にロック解除されます。 ロックおよびロック解除の動作は、メソッドの本体がsynchronized文(§14.19)に埋め込まれている場合とまったく同じです。
例 15.12.4.5-1. 起動されたメソッド・シグネチャの消去がコンパイル時メソッド・シグネチャと異なる
次の宣言について考えてみます。
abstract class C<T> {
abstract T id(T x);
}
class D extends C<String> {
String id(String x) { return x; }
}
次に、呼出しを示します。
C c = new D(); c.id(new Object()); // fails with a ClassCastException
呼び出される実際のメソッドの消去(D.id())は、コンパイル時メソッド宣言(C.id())とシグネチャが異なります。 前者はString型の引数を取り、後者はObject型の引数を取ります。 メソッドの本体が実行される前に、呼出しがClassCastExceptionで失敗します。
このような状況は、プログラムがコンパイル時に未チェックの警告(§4.8、§5.1.6、§5.1.9、§8.4.1、§8.4.8.3、§15.13.2、§15.12.4.2、§15.27.3)を引き起こす場合にのみ発生します。
実装では、ブリッジ・メソッドを作成することで、これらのセマンティクスを適用できます。 前述の例では、次のブリッジ・メソッドがクラスDに作成されます。
Object id(Object x) { return id((String) x); }
これは、前述のc.id(new Object())のコールに応答してJava Virtual Machineによって実際に呼び出されるメソッドであり、必要に応じてキャストが実行され、失敗します。
呼出しを実際に実行することなく、メソッドの呼出しを参照するために、メソッド参照式が使用されます。 メソッド参照式の特定の形式では、クラス・インスタンスの作成(§15.9)または配列の作成(§15.10)も、メソッド呼出しであるかのように処理できます。
:: [TypeArguments] 識別子 :: [TypeArguments] 識別子 :: [TypeArguments] 識別子 super :: [TypeArguments] 識別子 . super :: [TypeArguments] 識別子 :: [TypeArguments] new :: new
TypeArgumentsが::の右側に存在する場合、いずれかの型引数がワイルドカード(§4.5.1)である場合、コンパイル時にエラーが発生します。
メソッド参照式の形式がExpressionName :: [TypeArguments] IdentifierまたはPrimary :: [TypeArguments] Identifierの場合、ExpressionNameまたはPrimaryの型が参照型でないと、コンパイル時にエラーが発生します。
メソッド参照式の形式がsuper :: [TypeArguments] Identifierの場合、Eをメソッド参照式をすぐに囲むクラスまたはインタフェース宣言にします。 EがクラスObjectの場合、またはEがインタフェースの場合、コンパイル時にエラーが発生します。
メソッド参照式の形式がTypeName . super :: [TypeArguments] Identifierの場合:
TypeNameがクラスCを示している場合、Cが現在のクラスの字句で囲まれているクラスでない場合、またはCがクラスObjectである場合、コンパイル時にエラーが発生します。
TypeNameがインタフェースIを示す場合、Eをメソッド参照式をすぐに囲むクラスまたはインタフェース宣言にします。 IがEの直接スーパーインタフェースでない場合、またはJがIのサブクラスまたはサブインタフェースとなるように、E、Jの他の直接スーパークラスまたは直接スーパーインタフェースが存在する場合は、コンパイル時にエラーが発生します。
TypeNameが型変数を示している場合、コンパイル時にエラーが発生します。
メソッド参照式の形式がsuper :: [TypeArguments] IdentifierまたはTypeName . super :: [TypeArguments] Identifierの場合、式が現在のクラスの静的コンテキスト(§8.1.3)または初期構成コンテキスト(§8.8.7)で発生すると、コンパイル時にエラーが発生します。
メソッド参照式の形式がClassType :: [TypeArguments] newの場合:
メソッド参照式の形式がArrayType :: newの場合、ArrayTypeは再可能な型を示す必要があります(§4.7)。そうしないと、コンパイル時にエラーが発生します。
インスタンス・メソッドのターゲット参照(§15.12.4.1)は、ExpressionName、Primaryまたはsuperを使用してメソッド参照式で指定することも、後でメソッドの起動時に指定することもできます。 新しい内部クラス・インスタンスの即時包含インスタンス(§15.9.2)は、thisの字句的に包含されるインスタンス(§8.1.3)によって提供されます。
型の複数のメンバー・メソッドに同じ名前がある場合、またはクラスに複数のコンストラクタがある場合、§15.13.1で指定されているように、メソッド参照式によってターゲット指定された関数型に基づいて適切なメソッドまたはコンストラクタが選択されます。
メソッドまたはコンストラクタが汎用である場合は、適切な型引数を推測するか明示的に指定することができます。 同様に、メソッド参照式で示される汎用型の型引数は、明示的に指定するか推測することができます。
メソッド参照式は、常にポリ式です(§15.2)。
代入コンテキスト(§5.2)、呼出しコンテキスト(§5.3)、またはキャスト・コンテキスト(§5.5)以外のプログラムでメソッド参照式が発生した場合、コンパイル時にエラーが発生します。
メソッド参照式を評価すると、関数型(§9.8)のインスタンスが生成されます。 これによって、対応するメソッドが実行されるわけではありません。かわりに、ファンクション・インタフェースの適切なメソッドが呼び出されると、後で実行が発生する可能性があります。
いくつかのメソッド参照式を次に示します。まずはターゲット参照なし、次にターゲット参照ありです。
String::length // instance method System::currentTimeMillis // static method List<String>::size // explicit type arguments for generic type List::size // inferred type arguments for generic type int[]::clone T::tvarMember System.out::println "abc"::length foo[x]::bar (test ? list.replaceAll(String::trim) : list) :: iterator super::toString
さらにいくつかのメソッド参照式を次に示します。
String::valueOf // overload resolution needed Arrays::sort // type arguments inferred from context Arrays::<String>sort // explicit type arguments
オブジェクトまたは配列が遅れて作成されるメソッド参照式を次に示します。
ArrayList<String>::new // constructor for parameterized type
ArrayList::new // inferred type arguments
// for generic class
Foo::<Integer>new // explicit type arguments
// for generic constructor
Bar<String>::<Integer>new // generic class, generic constructor
Outer.Inner::new // inner class constructor
int[]::new // array creation
照合する特定のシグネチャ(Arrays::sort(int[])など)を指定することはできません。 かわりに、関数型インタフェースは、オーバーロード解決アルゴリズム(§15.12.2)への入力として使用される引数型を提供します。 これで大部分のユースケースに対応できます。まれですが、より細かい制御が必要になった場合は、ラムダ式を使用できます。
デリミタ(List<String>::size)の前にクラス名で型引数構文を使用すると、<を型引数カッコとして、<をより小さい演算子として区別するという解析の問題が発生します。 理論的には、これはキャスト式で型引数を許可するよりも悪いことではありませんが、違いは、キャスト・ケースが(トークンが検出された場合にのみ出現することであり、メソッド参照式を追加すると、すべての式の開始がパラメータ化された型になる可能性があります。
メソッド参照式のコンパイル時宣言は、式が参照するメソッドです。 特別なケースでは、コンパイル時の宣言は実際には存在しませんが、クラス・インスタンスの作成または配列の作成を表すノーショナル・メソッドです。 コンパイル時宣言の選択は、メソッド呼出しのコンパイル時宣言が呼出しの引数(§15.12.3)に依存するのと同様に、式がターゲットとする関数型に依存します。
コンパイル時宣言の検索では、次のように§15.12.1および§15.12.2のメソッド呼出しのプロセスがミラー化されます。
最初に、検索するタイプを決定します:
メソッド参照式の形式がExpressionName :: [TypeArguments] IdentifierまたはPrimary :: [TypeArguments] Identifierの場合、検索する型は::トークンの前の式のタイプです。
メソッド参照式の形式がReferenceType :: [TypeArguments] 識別子の場合、検索する型は、ReferenceTypeに適用された取得変換(§5.1.10)の結果です。
メソッド参照式の形式がsuper :: [TypeArguments] Identifierの場合、検索する型は、メソッド参照式の直前に包含されるクラスまたはインタフェース宣言のスーパークラス型です。
Tを、メソッド参照式を直接囲んでいるクラスまたはインタフェース宣言にします。 TがクラスObjectまたはインタフェースの場合、コンパイル時にエラーが発生します。
メソッド参照式の形式がTypeName . super :: [TypeArguments] Identifierの場合、TypeNameがクラスを示すと、検索する型は名前付きクラスのスーパークラス型になり、それ以外の場合、TypeNameは検索するインタフェースを示します。
TypeNameが、メソッド参照式の字句的に包含するクラスまたはインタフェース宣言でもなく、メソッド参照式の直近のクラスまたはインタフェース宣言の直接スーパーインタフェースでもない場合、コンパイル時にエラーが発生します。
TypeNameがObjectクラスの場合、コンパイル時にエラーが発生します。
TypeNameがインタフェースで、JがTypeNameのサブクラスまたはサブインタフェースとなるように、メソッド参照式Jの直近のクラスまたはインタフェース宣言に他の直接スーパークラスまたは直接スーパーインタフェースが存在する場合は、コンパイル時にエラーが発生します。
他の2つの形式(:: newを含む)では、参照されるメソッドは概念的で、検索する型はありません。
次に、nパラメータを持つターゲット関数型を指定すると、適用可能な一連のメソッドが識別されます。
メソッド参照式の形式がReferenceType :: [TypeArguments] Identifierの場合、適用可能なメソッドは次のとおりです。
検索する型のメンバー・メソッドで、Identifierという名前で、n型の引数TypeArgumentsを持ち、メソッド参照式と同じクラスに出現するメソッド呼出しに適用可能(§15.12.2.1)である可能性があります。さらに
Identifierという名前で、n-1型の引数を持ち、TypeArguments型の引数を持ち、メソッド参照式と同じクラスに出現する、検索する型のメンバー・メソッド。
このフォームがstaticメソッドまたはインスタンス・メソッドを参照する可能性を考慮するために、nとn-1の2つの異なるアリティが考慮されます。
メソッド参照式の形式がClassType :: [TypeArguments] newの場合、適用可能な可能性のあるメソッドは、ClassTypeのコンストラクタに対応する一連の名前付きメソッドです。
ClassTypeがRAW型で、RAW型のstatic以外のメンバー型ではない場合、候補のノーショナル・メンバー・メソッドは、<>を使用してクラスへの型引数を消去するクラス・インスタンス作成式に対して§15.9.3で指定されているメソッドです。 それ以外の場合、候補のノーショナル・メンバー・メソッドはClassTypeのコンストラクタであり、戻り型がClassTypeのメソッドであるかのように処理されます。
これらの候補の中で、適用可能な可能性のあるメソッドは、アリティnを持ち、型引数TypeArgumentsを持ち、メソッド参照式と同じクラスに出現するメソッド呼出しに適用できる可能性のある概念メソッドです。
メソッド参照式の形式がArrayType :: newの場合、単一のノーショナル・メソッドが考慮されます。 このメソッドには、int型の1つのパラメータがあり、ArrayTypeを返し、throws句はありません。 n = 1の場合、これが唯一適用可能なメソッドです。それ以外の場合、適用可能なメソッドはありません。
他のすべてのフォームでは、適用可能な可能性があるメソッドは、検索するタイプのメンバー・メソッドであり、Identifierという名前で、nというアリティを持ち、TypeArgumentsという型引数を持ち、メソッド参照式と同じクラスに表示されます。
最後に、適用可能なメソッドが存在しない可能性がある場合、コンパイル時の宣言はありません。
そうでない場合、パラメータ型P1, ..., Pnのターゲットの関数型および適用可能なメソッドのセットに対して、コンパイル時宣言が次のように選択されます。
メソッド参照式の形式がReferenceType :: [TypeArguments] 識別子の場合は、最も具体的な適用可能なメソッドを2回検索します。 各検索は、次の説明とともに、§15.12.2.2から§15.12.2.5で指定されています。 各検索では、適用可能なメソッドのセットが生成され、場合によってはセットの最も具体的なメソッドが指定されます。 §15.12.2.4で指定されたエラーの場合、適用可能なメソッドのセットは空です。 §15.12.2.5で指定されたエラーの場合、最も具体的なメソッドはありません。
最初の検索では、メソッド参照は型P1, ..., Pnの引数式で呼び出されたものとして扱われます。 型引数がある場合は、メソッド参照式によって指定されます。
2番目の検索では、P1、...、Pnが空でなく、P1がReferenceTypeのサブタイプである場合、メソッド参照式は、P2、...、Pn型の引数式を持つメソッド呼出し式であるかのように扱われます。 ReferenceTypeがRAW型で、この型G<...>(P1のスーパータイプ)のパラメータ化が存在する場合、検索する型は、G<...>に適用された取得変換(§5.1.10)の結果です。それ以外の場合、検索する型は最初の検索のタイプと同じです。 型引数がある場合は、メソッド参照式によって指定されます。
最初の検索でstaticという最も具体的なメソッドが生成され、2番目の検索で生成される適用可能なメソッドのセットにstatic以外のメソッドが含まれていない場合、コンパイル時宣言は最初の検索の最も具体的なメソッドです。
それ以外の場合、最初の検索で生成される適用可能なメソッドのセットにstaticメソッドが含まれず、2番目の検索でstatic以外の最も具体的なメソッドが生成されると、コンパイル時宣言が2番目の検索の最も具体的なメソッドになります。
それ以外の場合、コンパイル時の宣言はありません。
他のすべての形式のメソッド参照式では、最も具体的な適用可能なメソッドの検索が実行されます。 検索は、§15.12.2.2から§15.12.2.5で指定されているとおりで、次の説明があります。
メソッド参照は、型P1, ..., Pnの引数式の呼出しとして扱われ、型引数があればメソッド参照式で指定されます。
§15.12.2.2から§15.12.2.5で指定されているとおりに検索結果がエラーになった場合、または最も具体的な適用可能なメソッドがstaticの場合、コンパイル時宣言はありません。
それ以外の場合は、コンパイル時の宣言が適用可能な最も具体的なメソッドです。
メソッド参照式の形式がReferenceType :: [TypeArguments] Identifierで、コンパイル時宣言がstaticで、ReferenceTypeが単純名または修飾名(§6.2)でない場合、コンパイル時にエラーが発生します。
メソッド参照式の形式がsuper :: [TypeArguments] IdentifierまたはTypeName . super :: [TypeArguments] Identifierで、コンパイル時宣言の形式がabstractの場合、コンパイル時にエラーが発生します。
メソッド参照式の形式がsuper :: [TypeArguments] IdentifierまたはTypeName . super :: [TypeArguments] Identifierで、メソッド参照式が現在のクラスの静的コンテキスト(§8.1.3)または初期構成コンテキスト(§8.8.7)で発生した場合、コンパイル時にエラーが発生します。
メソッド参照式の形式がTypeName . super :: [TypeArguments] Identifierで、TypeNameがクラスCを示し、メソッド参照式の即時包含クラスまたはインタフェース宣言がCまたは内部クラスCではない場合、コンパイル時にエラーが発生します。
メソッド参照式の形式がTypeName . super :: [TypeArguments] Identifierで、TypeNameがインタフェースを示し、コンパイル時宣言とは異なるメソッドが存在する場合、コンパイル時宣言はメソッド参照式をすぐに囲むクラスまたはインタフェースの直接スーパークラスまたは直接スーパーインタフェースからのコンパイル時宣言をオーバーライドします(§8.4.8、§9.4.1)。
メソッド参照式がClassType :: [TypeArguments] newの形式であり、§15.9.2で指定されているClassTypeの包含インスタンスを決定するときにコンパイル時エラーが発生した場合(メソッド参照式を不適格クラス・インスタンス作成式であるかのように処理する場合)、コンパイル時エラーです。
ReferenceType :: [TypeArguments] Identifierという形式のメソッド参照式は、様々な方法で解釈できます。 Identifierがインスタンス・メソッドを参照する場合、Identifierがstaticメソッドを参照する場合と比較して、暗黙的なラムダ式に余分なパラメータがあります。 ReferenceTypeは、両方の種類の適用可能なメソッドを持つことができるため、前述の検索アルゴリズムは、各ケースに異なるパラメータ・タイプがあるため、それらを個別に識別します。
あいまいさの例を次に示します:
interface Fun<T,R> { R apply(T arg); }
class C {
int size() { return 0; }
static int size(Object arg) { return 0; }
void test() {
Fun<C, Integer> f1 = C::size;
// Error: instance method size()
// or static method size(Object)?
}
}
このあいまいさを解決するには、適用可能なstaticメソッドよりも具体的な適用可能なインスタンス・メソッドを指定します。
interface Fun<T,R> { R apply(T arg); }
class C {
int size() { return 0; }
static int size(Object arg) { return 0; }
int size(C arg) { return 0; }
void test() {
Fun<C, Integer> f1 = C::size;
// Error: instance method size()
// or static method size(Object)?
}
}
検索は、(両方の検索から)で適用可能なすべてのメソッドがインスタンス・メソッドであるあいまいさを無視するのに十分なスマートです:
interface Fun<T,R> { R apply(T arg); }
class C {
int size() { return 0; }
int size(Object arg) { return 0; }
int size(C arg) { return 0; }
void test() {
Fun<C, Integer> f1 = C::size;
// OK: reference is to instance method size()
}
}
便宜上、汎用型の名前を使用してインスタンス・メソッド(レシーバが最初のパラメータになる場所)を参照する場合、ターゲット型を使用して型引数が決定されます。 これにより、Pair<String,Integer>::firstのかわりにPair::firstなどの使用が容易になります。 同様に、Pair::newのようなメソッド参照は、ダイヤモンド・インスタンス作成(new Pair<>())のように扱われます。 "diamond"は暗黙的であるため、この形式はRAW型をインスタンス化しません。実際、RAW型のコンストラクタへの参照を表現する方法はありません。
一部のメソッド参照式では、対象となる関数型に関係なく、可能な呼出し型(§15.12.2.6)が1つのみのコンパイル時宣言が1つのみ存在できます。 このようなメソッド参照式は、完全です。 完全ではないメソッド参照式は、inexactと呼ばれます。
Identifierで終わるメソッド参照式は、次のすべてを満たす場合に正確です。
ClassType :: [TypeArguments] newという形式のメソッド参照式は、次のすべてを満たす場合に正確です。
ClassTypeで示される型はRAWではないか、RAW型のstatic以外のメンバー型です。
ClassTypeで示される型には、メソッド参照式が出現するクラスまたはインタフェースからアクセスできるコンストラクタが1つのみあります。
このコンストラクタは可変引数ではありません。
このコンストラクタが汎用の場合、メソッド参照式はTypeArgumentsを提供します。
ArrayType :: new形式のメソッド参照式は、常に正確です。
メソッド参照式は、Tがファンクション・インタフェース・タイプ(§9.8)で、式がTから導出されたグラウンド・ターゲット・タイプのファンクション・タイプを持つcongruentの場合、割当てコンテキスト、呼出しコンテキストまたはターゲット・タイプTとのキャスト・コンテキストで互換性があります。
グラウンド・ターゲット・タイプは、次のようにTから導出されます。
Tがワイルドカードでパラメータ化された機能インタフェース型の場合、接地ターゲット型は Tのワイルドカード以外のパラメータ化(§9.9)です。
それ以外の場合、接地ターゲット・タイプはTです。
次の両方に当てはまる場合、メソッド参照式はファンクション・タイプと一致します。
関数型は、参照に対応する単一のコンパイル時宣言を識別します。
次のいずれかがtrue:
関数型の結果はvoidです。
関数型の結果はRで、選択したコンパイル時宣言の呼出し型(§15.12.2.6)の戻り型に取得変換(§5.1.10)を適用した結果はRです。(RはRを推測するために使用できるターゲット・タイプ)、RもRもvoidでもなく、Rも割当てコンテキストのRと互換性があります。
コンパイル時宣言を適用するために未チェックの変換が必要で、この変換によって起動コンテキストで未チェックの警告が発生した場合、@SuppressWarnings (§9.6.4.5)によって抑制されないかぎり、コンパイル時の未チェックの警告が発生します。
前述の戻り型Rで、ファンクション型の戻り型Rと互換性があるために、未チェックの変換が必要だった場合、この変換によって割当てコンテキストで未チェックの警告が発生し、@SuppressWarningsで抑制されないかぎり、コンパイル時の未チェックの警告が発生します。
メソッド参照式がターゲット・タイプTと互換性がある場合、式の型Uは、Tから導出された接地ターゲット型です。
Uまたは Uの関数型が、メソッド参照式が出現するクラスまたはインタフェースからアクセスできない(§6.6)場合は、コンパイル時にエラーが発生します。
Uのstatic以外のメンバー・メソッドmごとに、Uのファンクション・タイプにmのシグネチャのサブシグネチャがある場合、メソッド・タイプがUのファンクション・タイプであるノーショナル・メソッドはmをオーバーライドすると言われ、§8.4.8.3で指定されたコンパイル時エラーまたはチェックされていない警告が発生する可能性があります。
コンパイル時宣言の起動タイプのthrows句にリストされているチェックされた例外タイプXごとに、XまたはXのスーパークラスがUファンクション・タイプのthrows句で指定されている必要があり、そうでない場合はコンパイル時にエラーが発生します。
互換性定義を駆動する主な考え方は、同等のラムダ式(x, y, z) に互換性がある場合にのみ、メソッド参照に互換性があることです。 (これは非公式であり、このようなリライトの観点からセマンティクスを正式に定義することが困難または不可能になる問題があります。)
-> exp.<T1, T2>method(x, y, z)
これらの互換性ルールは、ある機能インタフェースから別の機能インタフェースに変換するための便利な機能を提供します。
Task t = () -> System.out.println("hi");
Runnable r = t::invoke;
ラムダ派生オブジェクトが渡されて様々な型に変換される場合に、コア・ラムダ本体の周りに多くのレベルの適応ロジックが生成されないように、実装を最適化できます。
ラムダ式とは異なり、メソッド参照は汎用関数型(型パラメータを持つ関数型)に準拠できます。 これは、ラムダ式で型パラメータを宣言できる必要があり、構文でこれがサポートされないためです。メソッド参照の場合、そのような宣言は必要ありません。 たとえば、次のプログラムは有効です。
interface ListFactory {
<T> List<T> make();
}
ListFactory lf = ArrayList::new;
List<String> ls = lf.make();
List<Number> ln = lf.make();
実行時、メソッド参照式の評価はクラス・インスタンス作成式の評価と似ていますが、通常の完了によってオブジェクトへの参照が生成されるかぎりです。 メソッド参照式の評価は、メソッド自体の呼出しとは異なります。
まず、メソッド参照式がExpressionNameまたはPrimaryで始まる場合、このサブ式が評価されます。 サブ式がnullと評価されると、NullPointerExceptionが生成され、メソッド参照式が突然完了します。 サブ式が突然完了すると、メソッド参照式も同じ理由で突然完了します。
次に、次のプロパティを持つクラスの新規インスタンスが割り当てられて初期化されるか、または次のプロパティを持つクラスの既存のインスタンスが参照されます。 新規インスタンスを作成するが、オブジェクトを割り当てるための領域が不足している場合、メソッド参照式の評価はOutOfMemoryErrorをスローして突然完了します。
メソッド参照式の値は、次のプロパティを持つクラスのインスタンスへの参照です。
このクラスは、ターゲットとなる機能インタフェース・タイプを実装し、ターゲット・タイプが交差タイプである場合は、交差に記述されている他のすべてのインタフェース・タイプを実装します。
メソッド参照式のタイプがUの場合、Uのstatic以外のメンバー・メソッドmごとに、次のようになります。
ファンクション・タイプUにmのシグネチャのサブシグネチャがある場合、クラスはmをオーバーライドする呼出しメソッドを宣言します。 起動メソッドの本体は、次に説明するように、参照メソッドを呼び出したり、クラス・インスタンスを作成したり、配列を作成します。 呼出しメソッドの結果がvoidでない場合、本体は、必要な代入変換(§5.2)の後に、メソッド呼出しまたはオブジェクト作成の結果を返します。
オーバーライドされるメソッドの型の消去が、そのシグネチャでUのファンクション・タイプの消去と異なる場合、メソッドの呼出しまたはオブジェクトの作成の前に、呼出しメソッドの本体は、各引数値がUファンクション・タイプ内の対応するパラメータ型の消去のサブクラスまたはサブインタフェースのインスタンスであることをチェックします。そうでない場合は、ClassCastExceptionがスローされます。
このクラスは、Objectクラスのメソッドをオーバーライドする場合もありますが、前述の機能インタフェース・タイプやその他のインタフェース・タイプの他のメソッドをオーバーライドしません。
呼出しメソッドの本体は、次のようにメソッド参照式の形式によって異なります。
フォームがExpressionName :: [TypeArguments] IdentifierまたはPrimary :: [TypeArguments] Identifierの場合、呼出しメソッドの本体は、メソッド参照式のコンパイル時宣言であるコンパイル時宣言に対するメソッド呼出し式の影響を受けます。 メソッド呼出し式の実行時評価は、§15.12.4.3、§15.12.4.4および§15.12.4.5で指定されています。ここでは:
呼出しモードは、§15.12.3で指定されたコンパイル時宣言から導出されます。
ターゲット参照は、メソッド参照式の評価時に決定されるExpressionNameまたはPrimaryの値です。
メソッド呼出し式の引数は、呼出しメソッドの仮パラメータです。
フォームがReferenceType :: [TypeArguments] 識別子の場合、呼出しメソッドの本体も同様に、メソッド参照式のコンパイル時宣言であるコンパイル時宣言のメソッド呼出し式の影響を受けます。 メソッド呼出し式の実行時評価は、§15.12.4.3、§15.12.4.4および§15.12.4.5で指定されています。ここでは:
呼出しモードは、§15.12.3で指定されたコンパイル時宣言から導出されます。
コンパイル時宣言がインスタンス・メソッドの場合、ターゲット参照は呼出しメソッドの最初の仮パラメータです。 それ以外の場合、ターゲット参照はありません。
コンパイル時宣言がインスタンス・メソッドの場合、メソッド呼出し式への引数(存在する場合)は、呼出しメソッドの2番目以降の仮パラメータです。 それ以外の場合、メソッド呼出し式の引数は、呼出しメソッドの仮パラメータです。
形式がsuper :: [TypeArguments] IdentifierまたはTypeName . super :: [TypeArguments] Identifierの場合、呼出しメソッドの本体は、メソッド参照式のコンパイル時宣言であるコンパイル時宣言に対してメソッド呼出し式の影響を受けます。 メソッド呼出し式の実行時評価は、§15.12.4.3、§15.12.4.4および§15.12.4.5で指定されています。ここでは:
起動モードはsuperです。
メソッド参照式がクラスを指定するTypeNameで始まる場合、ターゲット参照は、メソッド参照が評価される時点でのTypeName . thisの値です。 それ以外の場合、ターゲット参照は、メソッド参照が評価される時点でのthisの値です。
メソッド呼出し式の引数は、呼出しメソッドの仮パラメータです。
フォームがClassType :: [TypeArguments] newの場合、呼出しメソッドの本体は、new [TypeArguments] ClassType(A1、 ...、 Anという形式のクラス・インスタンス作成式の影響を受けます。ここで、引数A1、 ...、 Anは呼出しメソッドの仮パラメータであり、ここでは:
フォームがType[]k :: new (k ≥ 1)の場合、起動メソッドの本体は、new Type [ size ] []k-1という形式の配列作成式と同じ効果を持ちます。ここで、sizeは起動メソッドの単一パラメータです。 ([]kという表記は、kカッコのペアのシーケンスを示します。)
呼出しメソッドの本体にメソッド呼出し式の効果がある場合、コンパイル時パラメータ型とメソッド呼出しのコンパイル時の結果は、§15.12.3で指定されているとおりに決定されます。 コンパイル時の結果を判別するために、メソッド呼出し式は、呼出しメソッドの結果がvoidの場合は式文であり、呼出しメソッドの結果がvoid以外の場合はreturn文の式です。
メソッド参照式の評価のタイミングは、ラムダ式の評価よりも複雑です(§15.27.4)。 メソッド参照式に::セパレータの前の式(型ではなく)がある場合、その部分正規表現は即時に評価されます。 評価の結果は、対応する関数型インタフェース・タイプのメソッドが呼び出されるまで格納されます。その時点で、結果は呼出しのターゲット参照として使用されます。 つまり、::セパレータの前の式は、プログラムがメソッド参照式を検出した場合にのみ評価され、ファンクション・インタフェース型での後続の呼出しでは再評価されません。
ここでのnullの処理と、メソッド呼出し中のその処理との対比が興味深いです。 メソッド呼出し式が評価されると、nullに評価する呼出しを修飾するプライマリがNullPointerExceptionを生成しないことが可能になります。 これは、呼び出されたメソッドがstaticの場合に発生します(インスタンス・メソッドを示唆する呼出しの構文にかかわらず)。 Primaryで修飾されたメソッド参照式に適用可能なメソッドはstatic (§15.13.1)にはならないため、メソッド参照式の評価は単純です。つまり、null Primaryでは常にNullPointerExceptionが呼び出されます。
Postfix式には、接頭辞++および--演算子の使用が含まれます。 名前は一次式(§15.8)とはみなされませんが、特定の曖昧さを避けるために文法では別々に扱われます。 これらは、ポストフィックス式の優先順位レベルでのみここで交換可能になります。
式名を評価するための規則は、§6.5.6に記載されています。
++
++演算子が続くポストフィックス式は、ポストフィックス増分式です。
postfix式の結果は、数値型に変換可能な型(§5.1.8)の変数である必要があります。そうでない場合、コンパイル時にエラーが発生します。
postfix増分式の型は、変数の型です。 postfix増分式の結果は変数ではなく、値です。
実行時に、オペランド式の評価が突然完了した場合、ポストフィックス増分式は、同じ理由で突然完了し、増分は発生しません。 それ以外の場合は、値1が変数の値に追加され、合計が変数に戻されます。 加算の前に、バイナリ数値プロモーション(§5.6)は、値1および変数の値に対して実行されます。 必要な場合、合計は、狭いプリミティブ変換(§5.1.3)またはボクシング変換(§5.1.7)の対象となる変数の型(あるいはその両方)によって、格納前に絞り込まれます。 接頭辞増分式の値は、新しい値が格納される前の変数の値です。
上記の二進数プロモーションには、ボックス化解除変換(§5.1.8)が含まれる場合があることに注意してください。
このようなfinal変数のアクセスが式として使用される場合、結果は変数ではなく値であるため、finalと宣言された変数は増分できません。 したがって、ポストフィックス増分演算子のオペランドとして使用することはできません。
--
--演算子が続くポストフィックス式は、ポストフィックスデクリメント式です。
postfix式の結果は、数値型に変換可能な型(§5.1.8)の変数である必要があります。そうでない場合、コンパイル時にエラーが発生します。
postfixデクリメント式の型は、変数の型です。 postfix減分式の結果は変数ではなく、値です。
実行時に、オペランド式の評価が突然完了した場合、ポストフィックス減分式は、同じ理由で突然完了し、減分は発生しません。 それ以外の場合、値1は変数の値から減算され、差は変数に戻されます。 減算の前に、値1および変数の値に対して2進数昇格(§5.6)が実行されます。 必要に応じて、その差は、格納される前に、狭いプリミティブ変換(§5.1.3)またはボクシング変換(§5.1.7)の対象となる変数の型(あるいはその両方)によって絞り込まれます。 postfixデクリメント式の値は、新しい値が格納される前の変数の値です。
上記の二進数プロモーションには、ボックス化解除変換(§5.1.8)が含まれる場合があることに注意してください。
このようなfinal変数のアクセスが式として使用される場合、結果は変数ではなく値であるため、finalと宣言された変数は減少できません。 したがって、ポストフィックス減分演算子のオペランドとして使用することはできません。
演算子+、-、++、--、~、!およびキャスト演算子(§15.16)は、単項演算子と呼ばれます。 単項式は、オペランドに適用される単項演算子またはswitch式(§15.28)のいずれかです。
単項演算子を持つ式は、右から左にグループ化されるため、-~xは-(~x)と同じ意味になります。
文法のこの部分には、潜在的な二つの構文上の曖昧さを避けるためのいくつかのトリックが含まれています。
最初の潜在的なあいまいさは、CまたはC++プログラマに見える(p)+qなどの式で発生します。これは、qで動作する単項+のp型へのキャスト、または2つの数量pおよびqの2進追加のいずれかであるかのようになります。 CおよびC++では、パーサーはこの問題を、解析時に限定された量のセマンティック分析を実行することで処理するため、pが型の名前か変数の名前かを知ることができます。
Javaは別のアプローチを採用しています。 +演算子の結果は数値である必要があり、数値のキャストに関係するすべての型名は既知のキーワードです。 したがって、pがプリミティブ型を指定するキーワードである場合、(p)+qは単項式のキャストとしてのみ意味を持ちます。 ただし、pがプリミティブ型を指定するキーワードではない場合、(p)+qはバイナリ算術演算としてのみ意味を持ちます。 同様の備考が-演算子に適用されます。 文法では、CastExpressionを複数のケースに分割して、次の区別を行います。
非ターミナルUnaryExpressionにはすべての単項演算子が含まれますが、非ターミナルUnaryExpressionNotPlusMinusでは、Javaでは+および-であるバイナリ演算子になる可能性のあるすべての単項演算子の使用が除外されます。
2つ目の考えられる曖昧さは、式(p)++がCまたはC++プログラマに対して、カッコで囲まれた式のポストフィックス増分またはキャストの先頭(たとえば、(p)++q)のように見えることです。 前述のように、CおよびC++のパーサーは、pが型の名前か変数の名前かを認識します。 ただし、解析中に1トークン先読みのみを使用し、セマンティック分析を行わないパーサーは、++が先読みトークンである場合に、(p)をプライマリ式とみなすか、CastExpressionの一部として後で検討するために単独で考慮するかを判断できません。
Javaでは、++演算子の結果は数値である必要があり、数値のキャストに含まれるすべての型名は既知のキーワードです。 したがって、pがプリミティブ型を指定するキーワードである場合、(p)++は接頭辞増分式のキャストとしてのみ意味があり、++の後にqなどのオペランドがある方が適切です。 ただし、pがプリミティブ型に名前を付けるキーワードではない場合、(p)++はpのポストフィクス増分としてのみ意味を持ちます。 同様の備考が--演算子に適用されます。 したがって、非ターミナルUnaryExpressionNotPlusMinusでは、接頭辞演算子++および--の使用も除外されます。
++
++演算子の前にある単項式は、接頭辞増分式です。
単項式の結果は、数値型に変換可能な型(§5.1.8)の変数である必要があります。そうでないと、コンパイル時にエラーが発生します。
接頭辞増分式の型は、変数の型です。 接頭辞増分式の結果は変数ではなく、値です。
実行時に、オペランド式の評価が突然完了した場合、接頭辞増分式は同じ理由で突然完了し、増分は発生しません。 それ以外の場合は、値1が変数の値に追加され、合計が変数に戻されます。 加算の前に、バイナリ数値プロモーション(§5.6)は、値1および変数の値に対して実行されます。 必要な場合、合計は、狭いプリミティブ変換(§5.1.3)またはボクシング変換(§5.1.7)の対象となる変数の型(あるいはその両方)によって、格納前に絞り込まれます。 接頭辞増分式の値は、新しい値が格納された後の変数の値です。
上記の二進数プロモーションには、ボックス化解除変換(§5.1.8)が含まれる場合があることに注意してください。
このようなfinal変数のアクセスが式として使用される場合、結果は変数ではなく値であるため、finalと宣言された変数は増分できません。 したがって、接頭辞増分演算子のオペランドとして使用することはできません。
--
--演算子の前に付く単項式は、接頭辞減分式です。
単項式の結果は、数値型に変換可能な型(§5.1.8)の変数である必要があります。そうでないと、コンパイル時にエラーが発生します。
接頭辞減分式の型は、変数の型です。 接頭辞減分式の結果は変数ではなく、値です。
実行時に、オペランド式の評価が突然完了した場合、接頭辞減分式は同じ理由で突然完了し、減分は発生しません。 それ以外の場合、値1は変数の値から減算され、差は変数に戻されます。 減算の前に、値1および変数の値に対して2進数昇格(§5.6)が実行されます。 必要に応じて、その差は、格納される前に、狭いプリミティブ変換(§5.1.3)またはボクシング変換(§5.1.7)の対象となる変数の型(あるいはその両方)によって絞り込まれます。 接頭辞減分式の値は、新しい値が格納された後の変数の値です。
上記の二進数プロモーションには、ボックス化解除変換(§5.1.8)が含まれる場合があることに注意してください。
このようなfinal変数のアクセスが式として使用される場合、結果は変数ではなく値であるため、finalと宣言された変数は減少できません。 したがって、接頭辞減分演算子のオペランドとして使用することはできません。
+
単項+演算子のオペランド式の型は、プリミティブ数値型に変換可能な型(§5.1.8)である必要があります。そうでない場合、コンパイル時にエラーが発生します。
単項数値プロモーション(§5.6)はオペランド上で実行されます。 単項プラス式の型は、オペランドの昇格された型です。 単項プラス式の結果は、オペランド式の結果が変数であっても、変数ではなく値になります。
実行時、単項プラス式の値は、オペランドの昇格された値です。
-
単項-演算子のオペランド式の型は、プリミティブ数値型に変換可能な型(§5.1.8)である必要があります。そうでない場合、コンパイル時にエラーが発生します。
単項数値プロモーション(§5.6)はオペランド上で実行されます。
単項マイナス式の型は、オペランドの昇格された型です。
実行時、単項マイナス式の値は、オペランドの昇格された値の算術否定です。
整数値の場合、否定はゼロからの減算と同じです。 Javaプログラミング言語では整数に2の補数表現が使用され、2の補数値の範囲は対称ではないため、負の最大値intまたはlongを否定すると、同じ最大負数になります。 この場合、オーバーフローが発生しますが、例外はスローされません。 すべての整数値xの場合、-xは(~x)+1と等しくなります。
浮動小数点値の場合、否定はゼロからの減算と同じではありません。xが+0.0の場合、0.0-xは+0.0ですが、-xは-0.0であるためです。 単項マイナスは、浮動小数点数の符号を反転するだけです。 特別な関心事:
オペランドが NaNの場合、結果は NaNです。 (NaNには符号がないことを思い出してください(§4.2.3)。
Javaプログラミング言語は、NaNを含むすべての入力の符号ビットを反転させる2019年版のIEEE 754 Standardのより強力な要件を採用していません。
オペランドが無限大の場合、結果は反対符号の無限大になります。
オペランドがゼロの場合、結果は反対符号のゼロになります。
~ 単項~演算子のオペランド式の型は、プリミティブ整数型に変換可能な型(§5.1.8)である必要があります。そうでない場合、コンパイル時にエラーが発生します。
単項数値プロモーション(§5.6)はオペランド上で実行されます。 単項ビット単位補数式の型は、オペランドの昇格型です。
実行時、単項ビット単位の補数式の値は、オペランドの昇格された値のビット単位の補数です。 いずれの場合も、~xは(-x)-1です。
! 単項!演算子のオペランド式の型は、booleanまたはBooleanである必要があります。そうでない場合、コンパイル時にエラーが発生します。
単項論理補数式の型はbooleanです。
実行時には、オペランドは必要に応じてボックス化解除変換(§5.1.8)の対象となります。 単項論理補数式の値は、オペランド値がfalseの場合はtrue、オペランド値がtrueの場合はfalseです。
キャスト式は、実行時にある数値型の値を別の数値型の同様の値に変換したり、コンパイル時に式の型がbooleanであることを確認したりします。または、実行時に、クラスが指定された参照型または参照型のリストと互換性があるか、プリミティブ型の値を体現するオブジェクトを参照することをチェックします。
ここでは、便宜上、§4.4の次の本番環境を示します。
カッコおよびカッコに含まれる型または型のリストは、キャスト演算子と呼ばれることもあります。
キャスト演算子に型のリストが含まれている場合、つまり、ReferenceTypeの後に1つ以上のAdditionalBound語が続く場合、次のすべてがtrueである必要があり、そうでない場合はコンパイル時にエラーが発生します。
ReferenceTypeは、クラスまたはインタフェース・タイプを示す必要があります。
リストされたすべての型の消去(§4.6)は、ペア単位で異なる必要があります。
リストされた2つの型は、同じ汎用インタフェースの異なるパラメータ化のサブタイプにすることはできません。
キャスト式によって導入されたキャスト・コンテキストのターゲット・タイプ(§5.5)は、キャスト演算子に表示されるPrimitiveTypeまたはReferenceType (その後にAdditionalBound語が続かない場合)か、キャスト演算子に表示されるReferenceTypeおよびAdditionalBound語で示される交差タイプです。
キャスト式の型は、取得変換(§5.1.10)をこのターゲット型に適用した結果です。
キャストは、ラムダ式またはメソッド参照式に特定のターゲット型を明示的に"タグ付け"するために使用できます。 適切な柔軟性を提供するために、交差によって機能インタフェース(§9.8)が誘発される場合、ターゲット・タイプは交差タイプを示すタイプのリストになることがあります。
キャスト式の結果は、オペランド式を評価した結果が変数であっても、変数ではなく値になります。
キャスト演算子によって指定されたターゲット型への変換(§5.5)をキャストすることによってオペランドのコンパイル時型を変換できない場合、コンパイル時にエラーが発生します。
それ以外の場合は、キャスト演算子で指定されたターゲット型へのキャスト変換によって、実行時にオペランド値が変換されます(必要な場合)。
ClassCastExceptionは、実行時にキャストが見つからないことが検出された場合にスローされます。
一部のキャストでは、コンパイル時にエラーが発生します。 一部のキャストは、実行時に常に正しいことをコンパイル時に証明できます。 たとえば、クラス型の値をそのスーパークラスの型に変換することは常に正しく、このようなキャストでは実行時に特別な処置が不要になります。 最後に、一部のキャストは、コンパイル時に常に正しいか、常に正しくないかを証明できません。 そのようなキャストでは、実行時にテストが必要になります。 詳細は §5.5を参照してください。
演算子*、/および%は、多重演算子と呼ばれます。
乗算演算子の優先順位は同じで、構文的に左連想(グループ左から右)です。
乗算演算子の各オペランドの型は、プリミティブ数値型に変換可能な型(§5.1.8)である必要があります。そうでない場合、コンパイル時にエラーが発生します。
2進数昇格はオペランド(§5.6)で行われる。
バイナリ数値プロモーションには、ボックス化解除変換が含まれる場合があることに注意してください(§5.1.8)。
乗法式の型は、そのオペランドの昇格型です。
プロモートされた型がintまたはlongの場合は、整数の算術が実行されます。
プロモートされた型がfloatまたはdoubleの場合は、浮動小数点演算が実行されます。
*
バイナリ*演算子は乗算を実行し、オペランドの積を生成します。
乗算は、オペランド式に副作用がない場合の可換演算です。
整数乗算は、オペランドがすべて同じ型の場合に関連付けられます。
浮動小数点乗算は連想的ではありません。
整数の乗算がオーバーフローした場合、結果は数学積の下位ビットであり、十分な大きさの2の補数形式で表されます。 結果として、オーバーフローが発生した場合、結果の符号は、2つのオペランド値の数学的積の符号と同じでない可能性があります。
浮動小数点乗算の結果は、IEEE 754演算の規則によって決まります。
どちらかのオペランドが NaNの場合、結果は NaNになります。
結果が NaNでない場合、結果の符号は、両方のオペランドが同じ符号を持つ場合は正、オペランドが異なる符号を持つ場合は負になります。
無限大をゼロで乗算すると、NaNになります。
無限大を有限値で乗算すると、符号付き無限大になります。 標識は上記の規則によって決定される。
無限大もNaNも関係しない残りのケースでは、正確な数学的積が計算される。
製品の大きさが大きすぎて表現できない場合、操作オーバーフローと言います。その結果は適切な符号の無限大になります。
それ以外の場合、製品は最も近い端数処理ポリシー(§15.4)を使用して最も近い表現可能な値に丸められます。 Javaプログラミング言語では、IEEE 754で定義されている段階的アンダーフローのサポートが必要です。
情報のオーバーフロー、アンダーフローまたは損失が発生する可能性があるにもかかわらず、乗算演算子*の評価では実行時例外はスローされません。
/
バイナリ/演算子は除算を実行し、そのオペランドの商を生成します。 左側のオペランドはdividend、右側のオペランドはdivisorです。
整数除算は、0に丸められます。 つまり、オペランド nおよび dに対して生成される、2進数昇格後の整数(§5.6)は整数値 qであり、その大きさは|d ⋅ q| ≤ |n|を満たしている間はできるだけ大きくなります。 また、qは、|n| ≥ |d|とnとdが同じ符号を持つ場合に正ですが、|n| ≥ |d|とnとdに逆の符号がある場合、負の値になります。
このルールを満たさない特殊なケースが1つあります。つまり、除数が-1の場合、整数のオーバーフローが発生し、結果は除算と等しくなります。 オーバーフローにもかかわらず、この場合は例外はスローされません。 一方、整数除数の除数の値が0の場合は、ArithmeticExceptionがスローされます。
浮動小数点除算の結果は、IEEE 754演算の規則によって決まります。
どちらかのオペランドが NaNの場合、結果は NaNになります。
結果が NaNでない場合、結果の符号は、両方のオペランドが同じ符号を持つ場合は正、オペランドが異なる符号を持つ場合は負になります。
無限大による無限大の除算はNaNになります。
有限値で無限を分割すると、符号付き無限大になります。 標識は上記の規則によって決定される。
無限大による有限値を除算すると、符号付きゼロになります。 標識は上記の規則によって決定される。
ゼロをゼロで除算すると NaNになります。ゼロを他の有限値で除算すると、符号付きゼロになります。 標識は上記の規則によって決定される。
ゼロ以外の有限値をゼロで除算すると、符号付き無限大になります。 標識は上記の規則によって決定される。
無限大もNaNも関係しない残りのケースでは、正確な数学的指数を計算します。
商の大きさが大きすぎて表せない場合、演算はオーバーフローし、その結果は適切な符号の無限大になります。
それ以外の場合、四捨五入は最も近い端数処理ポリシー(§15.4)を使用して、最も近い表現可能な値に丸められます。 Javaプログラミング言語では、IEEE 754で定義されている段階的アンダーフローのサポートが必要です。
オーバーフロー、アンダーフロー、ゼロによる除算、または情報の損失が発生する可能性があるにもかかわらず、浮動小数点除算演算子/の評価では、実行時例外はスローされません。
%
バイナリ%演算子は、オペランドの残りの部分を暗黙の除算から生成するといわれ、左側のオペランドはdividend、右側のオペランドはdivisorです。
CおよびC++では、剰余演算子は整数オペランドのみを受け入れますが、Javaプログラミング言語では浮動小数点オペランドも受け入れます。
2進数昇格後の整数であるオペランドの剰余演算(§5.6)では、(a/b)*b+(a%b)がaと等しくなるような結果値が生成されます。
このアイデンティティは、特別な場合でも、除数がその型で可能な最大の大きさの負の整数であり、除数が-1 (残りは0)であることを保持します。
このルールから、剰余演算の結果は、配当がマイナスの場合のみマイナスにでき、配当がプラスの場合のみプラスにできることがわかります。 さらに、結果の大きさは、常に除数の大きさより小さくなります。
整数の剰余演算子の除数の値が0の場合は、ArithmeticExceptionがスローされます。
例15.17.3-1 整数剰余演算子
class Test1 {
public static void main(String[] args) {
int a = 5%3; // 2
int b = 5/3; // 1
System.out.println("5%3 produces " + a +
" (note that 5/3 produces " + b + ")");
int c = 5%(-3); // 2
int d = 5/(-3); // -1
System.out.println("5%(-3) produces " + c +
" (note that 5/(-3) produces " + d + ")");
int e = (-5)%3; // -2
int f = (-5)/3; // -1
System.out.println("(-5)%3 produces " + e +
" (note that (-5)/3 produces " + f + ")");
int g = (-5)%(-3); // -2
int h = (-5)/(-3); // 1
System.out.println("(-5)%(-3) produces " + g +
" (note that (-5)/(-3) produces " + h + ")");
}
}
このプログラムは出力を生成します:
5%3 produces 2 (note that 5/3 produces 1) 5%(-3) produces 2 (note that 5/(-3) produces -1) (-5)%3 produces -2 (note that (-5)/3 produces -1) (-5)%(-3) produces -2 (note that (-5)/(-3) produces 1)
Javaプログラミング言語(§15.4)で端数処理ポリシーを選択したため、%演算子で計算された浮動小数点余剰演算の結果は、IEEE 754での余剰演算で計算されたものと同じではありません。 IEEE 754剰余演算は、切り捨て除算ではなく端数処理除算からの剰余を計算するため、その動作は通常の整数剰余演算子の動作と似ていません。 かわりに、Javaプログラミング言語では、整数余剰演算子と同様の方法で動作するように浮動小数点オペランドに%を定義し、丸めゼロの丸めポリシーを使用する暗黙の除算を使用します。これは、Cライブラリ関数fmodと比較できます。 IEEE 754残り操作は、ライブラリルーチン Math.IEEEremainderまたは StrictMath.IEEEremainderによって計算できます。
浮動小数点剰余演算の結果は、暗黙の除算の計算方法を除き、IEEE 754算術と一致する次の規則によって決定されます。
どちらかのオペランドが NaNの場合、結果は NaNになります。
結果がNaNでない場合、結果の符号は配当の符号に等しくなります。
配当が無限大であるか、除数がゼロまたはその両方の場合、結果はNaNになります。
配当が有限で、除数が無限である場合、結果は配当と等しくなります。
配当がゼロで、除数が有限の場合、結果は配当と等しくなります。
無限大もゼロもNaNも関係しない残りのケースでは、除数dによる除算nからの浮動小数点残りrは、数学的な関係r = n - (d ⋅)で定義されます。q) qが負の整数で、n/dが負および正の場合にのみ、n/dが正で、その大きさは、nおよびdの真の数学的商の大きさを超えずに可能なかぎり大きくなります。
浮動小数点余剰演算子%の評価では、右側のオペランドがゼロの場合でも、実行時例外はスローされません。 オーバーフロー、アンダーフロー、または精度の損失は発生しません。
例15.17.3-2 浮動小数点余剰演算子
class Test2 {
public static void main(String[] args) {
double a = 5.0%3.0; // 2.0
System.out.println("5.0%3.0 produces " + a);
double b = 5.0%(-3.0); // 2.0
System.out.println("5.0%(-3.0) produces " + b);
double c = (-5.0)%3.0; // -2.0
System.out.println("(-5.0)%3.0 produces " + c);
double d = (-5.0)%(-3.0); // -2.0
System.out.println("(-5.0)%(-3.0) produces " + d);
}
}
このプログラムは出力を生成します:
5.0%3.0 produces 2.0 5.0%(-3.0) produces 2.0 (-5.0)%3.0 produces -2.0 (-5.0)%(-3.0) produces -2.0
演算子+および-は、加算演算子と呼ばれます。
加算演算子の優先順位は同じで、構文的に左連想(左から右にグループ)です。
+演算子のいずれかのオペランドの型がStringの場合、操作は文字列連結です。
それ以外の場合、+演算子の各オペランドの型は、プリミティブ数値型に変換可能な型(§5.1.8)である必要があります。そうでない場合、コンパイル時にエラーが発生します。
いずれの場合も、バイナリ-演算子の各オペランドの型は、プリミティブ数値型に変換可能な型(§5.1.8)である必要があります。そうでない場合、コンパイル時にエラーが発生します。
+ String型のオペランド式が1つのみの場合、文字列変換(§5.1.11)がもう一方のオペランドで実行され、実行時に文字列が生成されます。
文字列連結の結果は、2つのオペランド文字列を連結したStringオブジェクトへの参照です。 左側のオペランドの文字は、新しく作成された文字列の右側のオペランドの文字の前にあります。
Stringオブジェクトは、式が定数式(§15.29)でないかぎり、新しく作成されます(§12.5)。
実装では、中間Stringオブジェクトの作成および破棄を回避するために、変換と連結を1つのステップで実行できます。 繰り返される文字列連結のパフォーマンスを向上させるために、Javaコンパイラは、StringBufferクラスまたは同様の手法を使用して、式の評価によって作成された中間Stringオブジェクトの数を減らすことができます。
プリミティブ型の場合、実装は、プリミティブ型から文字列に直接変換することでラッパー・オブジェクトの作成を最適化することもできます。
例15.18.1-1 文字列の連結
式の例:
"The square root of 2 is " + Math.sqrt(2)
結果は次のようになります。
"The square root of 2 is 1.4142135623730952"
+演算子は、文字列連結または数値加算を表す型分析によって決定されるかどうかに関係なく、構文的に左相関です。 場合によっては、目的の結果を得るために注意が必要です。 次のような式があるとします。
a + b + c
は常に意味とみなされます。
(a + b) + c
したがって、式の結果は次のようになります。
1 + 2 + " fiddlers"
が:
"3 fiddlers"
しかし、その結果:
"fiddlers " + 1 + 2
が:
"fiddlers 12"
例15.18.1-2 文字列の連結と条件
このjocularの小さい例:
class Bottles {
static void printSong(Object stuff, int n) {
String plural = (n == 1) ? "" : "s";
loop: while (true) {
System.out.println(n + " bottle" + plural
+ " of " + stuff + " on the wall,");
System.out.println(n + " bottle" + plural
+ " of " + stuff + ";");
System.out.println("You take one down "
+ "and pass it around:");
--n;
plural = (n == 1) ? "" : "s";
if (n == 0)
break loop;
System.out.println(n + " bottle" + plural
+ " of " + stuff + " on the wall!");
System.out.println();
}
System.out.println("No bottles of " +
stuff + " on the wall!");
}
public static void main(String[] args) {
printSong("slime", 3);
}
}
メソッドprintSongは、子曲のバージョンを出力します。 stuffの一般的な値には、popおよびbeerが含まれます。nの最も一般的な値は100です。 次に、プログラムを実行した結果の出力を示します。
3 bottles of slime on the wall, 3 bottles of slime; You take one down and pass it around: 2 bottles of slime on the wall! 2 bottles of slime on the wall, 2 bottles of slime; You take one down and pass it around: 1 bottle of slime on the wall! 1 bottle of slime on the wall, 1 bottle of slime; You take one down and pass it around: No bottles of slime on the wall!
コードでは、複数形の"bottles"ではなく、適切な場合は単数形の"bottle"の慎重な条件付き生成に注意してください。また、文字列連結演算子を使用して長い定数文字列を分割する方法にも注意してください。
"You take one down and pass it around:"
ソースコード内の不便な長い行を避けるために2つの部分に。
+および-)
バイナリ+演算子は、数値型の2つのオペランドに適用されたときに加算を実行し、オペランドの合計を生成します。
バイナリ-演算子は減算を実行し、2つの数値オペランドの差を生成します。
2進数昇格はオペランド(§5.6)で行われる。
バイナリ数値プロモーションには、ボックス化解除変換が含まれる場合があることに注意してください(§5.1.8)。
数値オペランドに対する加算式の型は、そのオペランドの昇格された型です。
この昇格された型がintまたはlongの場合は、整数の算術が実行されます。
この昇格された型がfloatまたはdoubleの場合は、浮動小数点演算が実行されます。
オペランド式に副作用がない場合、加算は可換演算です。
整数加算は、オペランドがすべて同じ型の場合に関連付けられます。
浮動小数点追加は連想的ではありません。
整数の加算がオーバーフローした場合、結果は、十分な大きさの2の補足形式で表される数学合計の下位ビットになります。 オーバーフローが発生した場合、結果の符号は、2つのオペランド値の数学的合計の符号と同じではありません。
浮動小数点加算の結果は、IEEE 754演算の規則によって決まります。
どちらかのオペランドが NaNの場合、結果は NaNになります。
反対符号の2つの無限度の合計は NaNです。
同じ符号の2つの無限度の合計は、その符号の無限大です。
無限大と有限値の合計は無限オペランドと等しくなります。
反対符号の2つのゼロの合計は正のゼロです。
同じ符号の2つのゼロの合計は、その符号のゼロです。
ゼロとゼロ以外の有限値の合計は、ゼロ以外のオペランドと等しくなります。
同じ大きさおよび反対符号の2つのゼロ以外の有限値の合計は正のゼロです。
無限大もゼロもNaNも関与せず、オペランドが同じ符号を持っているか、異なる大きさを持つ残りのケースでは、正確な算術合計が計算されます。
合計の大きさが大きすぎて表せない場合、演算はオーバーフローし、その結果は適切な符号の無限大になります。
それ以外の場合、この合計は、最も近い端数処理ポリシー(§15.4)を使用して、最も近い表現可能な値に丸められます。 Javaプログラミング言語では、段階的なアンダーフローのサポートが必要です。
バイナリ-演算子は、数値型の2つのオペランドに適用されると減算を実行し、そのオペランドの差異を生成します。左側のオペランドはminuendで、右側のオペランドはsubtrahendです。
整数と浮動小数点のどちらの減算の場合も、a-bがa+(-b)と同じ結果を生成するのは常に同じです。
整数値の場合、ゼロからの減算は否定と同じであることに注意してください。 ただし、浮動小数点オペランドの場合、ゼロからの減算は否定と同じではありません。xが+0.0の場合、0.0-xは+0.0ですが、-xは-0.0であるためです。
情報のオーバーフロー、アンダーフロー、または損失が発生する可能性があるにもかかわらず、数値加算演算子の評価は実行時例外をスローしません。
演算子<< (左シフト)、>> (符号付き右シフト)および>>> (符号なし右シフト)は、シフト演算子と呼ばれます。 シフト演算子の左側のオペランドはシフトする値です。右側のオペランドはシフト距離を指定します。
シフト演算子は、構文的に左連想(左から右にグループ)です。
単項数値プロモーション(§5.6)は、各オペランドで個別に実行されます。 (2進数昇格はオペランドでは実行されません。)
シフト演算子の各オペランドの型が、単項数値プロモーションの後にプリミティブ整数型でない場合、コンパイル時にエラーが発生します。
シフト式のタイプは、左側のオペランドの昇格されたタイプです。
左側のオペランドの昇格された型がintの場合、右側のオペランドの最下位5ビットのみがシフト距離として使用されます。 これは、右側のオペランドが、マスク値0x1f (0b11111)を持つビット単位の論理AND演算子& (§15.22.1)の対象であるかのようになります。 したがって、実際に使用されるシフト距離は常に0から31の範囲内です。
左側のオペランドの昇格された型がlongの場合、右側のオペランドの最下位6ビットのみがシフト距離として使用されます。 これは、右側のオペランドが、マスク値0x3f (0b111111)を持つビット単位の論理AND演算子& (§15.22.1)の対象であるかのようになります。 したがって、実際に使用されるシフト距離は常に0から63の範囲内です。
実行時に、シフト操作は、左側のオペランドの値の2の補数整数表現に対して実行されます。
n << sの値は、左シフトn個のsビット位置です。これは、2を乗算してsを乗算する場合と同等です(オーバーフローが発生した場合でも)。
n >> sの値は、n個の右シフトsビット位置と符号拡張です。 結果の値は、floor(n / 2s)です。 nの負でない値の場合、これは整数除算の切捨てと同等です。これは、整数除算演算子/で計算され、2乗のsです。
n >>> sの値は、n個の右シフトsビット位置で、拡張が0です。ここでは:
nが正の場合、結果はn >> sの結果と同じです。
nが負で、左側のオペランドの型がintの場合、結果は式(n >> s) + (2 << ~s)と等しくなります。
nが負で、左側のオペランドの型がlongの場合、結果は式(n >> s) + (2L << ~s)と等しくなります。
追加された用語(2 << ~s)または(2L << ~s)は、伝播された符号ビットを取り消します。
シフト演算子の右オペランドの暗黙的なマスキングのため、シフト距離としての~sは、int値をシフトする場合の31-sと、long値をシフトする場合の63-sと同等です。
数値比較演算子<、>、<=および>=、およびinstanceof演算子は、リレーショナル演算子と呼ばれます。
リレーショナル演算子は、構文的に左結合の(左から右へグループ化)です。
しかし、この事実は役に立ちません。 たとえば、a<bの型は常にbooleanで、<はboolean値の演算子ではないため、a<b<cは(a<b)<cとして解析され、これは常にコンパイル時エラーです。
リレーショナル式の型は、常にbooleanです。
<、<=、>および>= 数値比較演算子の各オペランドの型は、プリミティブ数値型に変換可能な型(§5.1.8)である必要があります。そうでない場合、コンパイル時にエラーが発生します。
2進数昇格はオペランド(§5.6)で行われる。
バイナリ数値プロモーションには、ボックス化解除変換が含まれる場合があることに注意してください(§5.1.8)。
プロモートされたオペランドの型がintまたはlongの場合は、符号付き整数比較が実行されます。
プロモートされた型がfloatまたはdoubleの場合は、浮動小数点比較が実行されます。
IEEE 754規格の仕様によって決定される浮動小数点比較の結果は、次のとおりです。
どちらかのオペランドがNaNの場合、結果はfalseになります。
NaN以外のすべての値が順序付けされ、負の無限大がすべての有限値より小さく、正の無限大がすべての有限値よりも大きくなります。
正のゼロと負のゼロは等しいとみなされます。
たとえば、-0.0<0.0はfalseですが、-0.0<=0.0はtrueです。
ただし、メソッドMath.minおよびMath.maxでは、負のゼロは正のゼロより厳密に小さいものとして扱われます。
浮動小数点数に関するこれらの考慮事項に従って、次の規則は整数オペランドまたは NaN以外の浮動小数点オペランドを保持します。
<演算子によって生成される値は、左側のオペランドの値が右側のオペランドの値より小さい場合はtrue、それ以外の場合はfalseです。
<=演算子によって生成される値は、左側のオペランドの値が右側のオペランドの値以下である場合はtrue、それ以外の場合はfalseです。
左側のオペランドの値が右側のオペランドの値より大きい場合、>演算子によって生成される値はtrueで、それ以外の場合はfalseです。
>=演算子によって生成される値は、左側のオペランドの値が右側のオペランドの値以上である場合はtrue、それ以外の場合はfalseです。
instanceof演算子
instanceof式は、型比較またはパターン一致のいずれかを実行できます。
instanceofReferenceTypeinstanceofパターン
instanceofキーワードの右側のオペランドがReferenceTypeの場合、instanceofキーワードは型比較演算子です。
instanceofキーワードの右側のオペランドがパターンの場合、instanceofキーワードはパターン一致演算子です。
instanceofが型比較演算子の場合、次のルールが適用されます。
式RelationalExpressionの型は、参照型またはnull型である必要があります。そうしないと、コンパイル時にエラーが発生します。
RelationalExpressionは、ReferenceType (§5.5)と互換性のあるキャストをチェックする必要があります。チェックしないと、コンパイル時にエラーが発生します。
実行時に型比較演算子の結果は次のように決定されます。
RelationalExpressionの値がNULL参照(§4.1)である場合、結果はfalseになります。
RelationalExpressionの値がNULL参照でない場合、ClassCastExceptionを発生させずに値をReferenceTypeにキャストできる場合はtrue、それ以外の場合はfalseになります。
instanceofがパターン一致演算子の場合、次のルールが適用されます。
式RelationalExpressionの型は、参照型またはnull型である必要があります。そうしないと、コンパイル時にエラーが発生します。
パターンは、式RelationalExpression (§14.30.3)のタイプで適用可能である必要があります。そうでない場合、コンパイル時にエラーが発生します。
実行時に、パターン一致演算子の結果は次のように決定されます:
RelationalExpressionの値がNULL参照の場合、結果はfalseになります。
RelationalExpressionの値がNULL参照ではない場合、値がパターン(§14.30.2)と一致する場合、結果はtrueになり、一致しない場合はfalseになります。
true結果の副作用は、パターンで宣言されたすべてのパターン変数(ある場合)が初期化されることです。
例15.20.2-1. 型比較演算子
class Point { int x, y; }
class Element { int atomicNumber; }
class Test {
public static void main(String[] args) {
Point p = new Point();
Element e = new Element();
if (e instanceof Point) { // compile-time error
System.out.println("I get your point!");
p = (Point)e; // compile-time error
}
}
}
このプログラムのコンパイル時には、2つのエラーが発生します。 キャストされた(Point)eは、Elementのインスタンスがないか、その可能なサブクラス(ここでは何も示されていません)のいずれかが、Pointのサブクラスのインスタンスである可能性があるため、正しくありません。 instanceof式は、まったく同じ理由で正しくありません。 一方、クラスPointがElementのサブクラス(この例では明らかに奇妙な概念)である場合:
class Point extends Element { int x, y; }
この場合、キャストは可能ですが、ランタイム・チェックが必要になり、instanceof式は妥当で有効になります。 キャストされた(Point)eは、eの値をPoint型に正しくキャストできなかった場合には実行されないため、例外は発生しません。
Java SE 16より前は、型比較演算子のReferenceTypeオペランドを再有効化する必要がありました(§4.7)。 これにより、そのすべての型引数がワイルドカードでないかぎり、パラメータ化型を使用できませんでした。 多くのパラメータ化型の使用を許可するために、その要件はJava SE 16で取り除かれました。 たとえば、次のプログラムでは、メソッド・パラメータx (静的型List<Integer>)が実行時にパラメータ化された型ArrayList<Integer>をより細かく持つかどうかをテストすることが有効です。
import java.util.ArrayList;
import java.util.List;
class Test2 {
public static void main(String[] args) {
List<Integer> x = new ArrayList<Integer>();
if (x instanceof ArrayList<Integer>) { // OK
System.out.println("ArrayList of Integers");
}
if (x instanceof ArrayList<String>) { // error
System.out.println("ArrayList of Strings");
}
if (x instanceof ArrayList<Object>) { // error
System.out.println("ArrayList of Objects");
}
}
}
List<Integer>からArrayList<Integer>へのキャスト変換があるため、最初のinstanceof式は有効です。 ただし、List<Integer>からArrayList<String>またはArrayList<Object>への変換がキャストされないため、2番目と3番目のinstanceof式の両方でコンパイル時にエラーが発生します。
演算子== (等しい)および!= (等しくない)は、等価演算子と呼ばれます。
等価演算子は、構文的に左連想(グループ左から右)です。
しかし、この事実は本質的に決して役に立たない。 たとえば、a==b==cは(a==b)==cとして解析されます。 a==bの結果型は常にbooleanであるため、cはboolean型である必要があります。そうでないと、コンパイル時にエラーが発生します。 したがって、a==b==cでは、a、bおよびcがすべて等しいかどうかはテストされません。
等価演算子は、オペランド式に副作用がない場合、可換です。
等価演算子は、優先順位が低いことを除き、関係演算子に似ています。 したがって、a<bとc<dが同じ真理値を持つ場合、a<b==c<dはtrueになります。
等価演算子は、変換可能な2つのオペランド(§5.1.8)と数値型、booleanまたはBoolean型の2つのオペランド、または参照型またはNULL型のそれぞれである2つのオペランドを比較するために使用できます。 その他の場合、コンパイル時にエラーが発生します。
等価式の型は常にbooleanです。
いずれの場合も、a!=bは!(a==b)と同じ結果を生成します。
==および!= 等価演算子のオペランドが両方とも数値型であるか、一方が数値型であり、もう一方が数値型に変換可能(§5.1.8)である場合、オペランドに対して2進数昇格(§5.6)が行われます。
バイナリ数値プロモーションには、ボックス化解除変換が含まれる場合があることに注意してください(§5.1.8)。
プロモートされたオペランドの型がintまたはlongの場合は、整数の等価テストが実行されます。
プロモートされた型がfloatまたはdoubleの場合は、浮動小数点等価テストが実行されます。
浮動小数点等価テストは、IEEE 754規格の規則に従って実行されます。
いずれかのオペランドがNaNの場合、==の結果はfalseですが、!=の結果はtrueです。
実際、テストx!=xは、xの値がNaNの場合にのみtrueです。
Float.isNaNおよびDouble.isNaNメソッドは、値がNaNかどうかをテストするためにも使用できます。
正のゼロと負のゼロは等しいとみなされます。
たとえば、-0.0==0.0はtrueです。
それ以外の場合、2つの異なる浮動小数点値は等価演算子によって等しくないと見なされます。
特に、正の無限大を表す1つの値、負の無限大を表す1つの値があります。それぞれがそれ自体と等しいのみを比較し、それぞれが他のすべての値と等しくないことを比較します。
浮動小数点数に関するこれらの考慮事項に従って、次の規則は整数オペランドまたは NaN以外の浮動小数点オペランドを保持します。
左側のオペランドの値が右側のオペランドの値と等しい場合、==演算子によって生成される値はtrueで、それ以外の場合、結果はfalseです。
左側のオペランドの値が右側のオペランドの値と等しくない場合、!=演算子によって生成される値はtrueです。それ以外の場合、結果はfalseです。
==および!= 等価演算子のオペランドが両方ともboolean型であるか、一方のオペランドがboolean型で他方のオペランドがBoolean型である場合、その操作はブール等価です。
ブール等価演算子は連想です。
オペランドの1つがBoolean型の場合、ボックス化解除変換の対象となります(§5.1.8)。
オペランド(必要なアンボクシング変換後)が両方ともtrueまたは両方ともfalseの場合、==の結果はtrueになり、それ以外の場合、結果はfalseになります。
オペランドが両方ともtrueまたは両方ともfalseの場合、!=の結果はfalseになり、それ以外の場合、結果はtrueになります。
したがって、!=は、booleanオペランドに適用された場合、^ (§15.22.2)と同じ動作をします。
==および!= 等価演算子のオペランドが参照型またはnull型の両方である場合、操作はオブジェクト等価です。
どちらかのオペランドの型をキャスト変換(§5.5)によって他方の型に変換できない場合、コンパイル時にエラーが発生します。 2つのオペランドの実行時値は、必ず等しくない(両方の値がnullの場合を無視)。
実行時に、オペランド値が両方ともnullの場合、または両方が同じオブジェクトまたは配列を参照する場合、==の結果はtrueになり、それ以外の場合、結果はfalseになります。
オペランド値が両方ともnullの場合、または両方が同じオブジェクトまたは配列を参照する場合、!=の結果はfalseになり、それ以外の場合、結果はtrueになります。
==はString型の参照を比較するために使用できますが、このような等価性テストでは、2つのオペランドが同じStringオブジェクトを参照するかどうかが判断されます。 オペランドが同じ文字シーケンス(§3.10.5、§3.10.6)を含む場合でも、オペランドが個別のStringオブジェクトである場合、結果はfalseになります。 2つの文字列sおよびtの内容は、メソッド呼出しs.equals(t)によって等価性をテストできます。
ビット演算子および論理演算子には、AND演算子&、排他OR演算子^および包含OR演算子|が含まれます。
これらの演算子の優先順位は異なり、&の優先順位は最も高く、|の優先順位は最も低くなります。
これらの各演算子は、構文的に左相関(各グループは左から右)です。
オペランド式に副作用がない場合、各演算子は可換的です。
各演算子は連想的です。
ビット単位演算子と論理演算子を使用して、数値型の2つのオペランドまたはboolean型の2つのオペランドを比較できます。 その他の場合、コンパイル時にエラーが発生します。
&、^および| 演算子&、^、または |の両方のオペランドが、プリミティブ整数型に変換可能な型(§5.1.8)の場合、最初にオペランド(§5.6)で2進数昇格が実行されます。
ビット単位の演算子式の型は、オペランドの昇格型です。
&の場合、結果値はオペランド値のビット単位ANDです。
^の場合、結果値はオペランド値のビット単位の排他ORです。
|の場合、結果値はオペランド値のビット単位のORです。
たとえば、式の結果は次のようになります。
0xff00 & 0xf0f0
が:
0xf000
式の結果:
0xff00 ^ 0xf0f0
が:
0x0ff0
式の結果:
0xff00 | 0xf0f0
が:
0xfff0
&、^および| &、^または|演算子のオペランドが両方ともbooleanまたはBoolean型の場合、ビット単位の演算子式の型はbooleanです。 いずれの場合も、オペランドは必要に応じてボックス化解除変換(§5.1.8)の対象となります。
&の場合、両方のオペランド値がtrueの場合、結果値はtrueになり、それ以外の場合、結果はfalseになります。
^の場合、オペランド値が異なる場合、結果値はtrueになり、それ以外の場合、結果はfalseになります。
|の場合、両方のオペランド値がfalseの場合、結果値はfalseになり、それ以外の場合、結果はtrueになります。
&& 条件演算子&&は& (§15.22.2)と似ていますが、左側のオペランドの値がtrueの場合にのみ、右側のオペランドを評価します。
条件演算子と条件演算子は、構文的に左相関(左から右にグループ化)です。
条件演算子と条件演算子は、副作用と結果値の両方に対して完全に関連付けられます。 つまり、式a、bおよびcの場合、式((の評価は、式a) && (b)) && (c)(の評価と同じ順序で同じ副作用が発生する同じ結果を生成します。
a) && ((b) && (c))
条件演算子の各オペランドは、booleanまたはBoolean型である必要があります。そうでないと、コンパイル時にエラーが発生します。
条件式と式の型は常にbooleanです。
実行時には、左側のオペランド式が最初に評価されます。結果の型がBooleanの場合は、ボックス化解除変換(§5.1.8)の対象となります。
結果の値がfalseの場合、条件式および式の値はfalseで、右側のオペランド式は評価されません。
左側のオペランドの値がtrueの場合、右側の式が評価されます。結果がBoolean型の場合、ボックス化解除変換の対象となります(§5.1.8)。 結果の値は、条件式と式の値になります。
したがって、&&は、booleanオペランドの&と同じ結果を計算します。 これは、右側のオペランド式が常にではなく条件付きで評価されるという点でのみ異なります。
|| 条件演算子||演算子は、| (§15.22.2)と似ていますが、左側のオペランドの値がfalseの場合にのみ、右側のオペランドを評価します。
|| ConditionalAndExpression
条件演算子は、構文的に左相関(左から右にグループ化)です。
条件演算子または条件演算子は、副作用と結果値の両方に対して完全に関連付けられます。 つまり、式a、bおよびcの場合、式((の評価は、式a) || (b)) || (c)(の評価と同じ順序で同じ副作用が発生する同じ結果を生成します。
a) || ((b) || (c))
条件演算子の各オペランドは、booleanまたはBoolean型である必要があります。そうでないと、コンパイル時にエラーが発生します。
条件式または式の型は常にbooleanです。
実行時には、左側のオペランド式が最初に評価されます。結果の型がBooleanの場合は、ボックス化解除変換(§5.1.8)の対象となります。
結果の値がtrueの場合、条件式または式の値はtrueで、右側のオペランド式は評価されません。
左側のオペランドの値がfalseの場合、右側の式が評価されます。結果がBoolean型の場合、ボックス化解除変換の対象となります(§5.1.8)。 結果の値は、条件式または条件式の値になります。
したがって、||は、booleanまたはBooleanオペランドの|と同じ結果を計算します。 これは、右側のオペランド式が常にではなく条件付きで評価されるという点でのみ異なります。
? : 条件演算子? :は、1つの式のブール値を使用して、評価する他の2つの式を決定します。
条件演算子は、構文的に右相関(右から左にグループ化)です。 したがって、a?b:c?d:e?f:gはa?b:(c?d:(e?f:g))と同じことを意味します。
条件演算子には3つのオペランド式があります。?は最初の式と2番目の式の間にあり、:は2番目の式と3番目の式の間にあります。
最初の式はboolean型またはBoolean型である必要があります。そうでない場合、コンパイル時にエラーが発生します。
2番目または3番目のオペランド式のいずれかがvoidメソッドの呼出しである場合は、コンパイル時にエラーが発生します。
実際、式文の文法(§14.8)では、voidメソッドの呼出しが出現する可能性があるコンテキストに条件式を表示することは許可されていません。
2番目と3番目のオペランド式に従って分類される条件式には、ブール条件式、数値条件式および参照条件式の3種類があります。 分類ルールは次のとおりです。
2番目と3番目のオペランド式の両方がブール式の場合、条件式はブール条件式です。
条件を分類するために、次の式はブール式です。
2番目と3番目のオペランド式の両方が数値式の場合、条件式は数値条件式です。
条件を分類するために、次の式は数値式です。
それ以外の場合、条件式は参照条件式です。
条件式のタイプを決定するプロセスは、次の項で説明するように、条件式の種類によって異なります。
次の表は、2番目と3番目のオペランドの可能なすべてのタイプの条件式のタイプを指定することで、前述のルールをまとめたものです。bnp(..)は、2進数プロモーションを適用することを意味します。 「T | bnp(..)」という形式は、1つのオペランドが int型の定数式であり、T型で表現できる場合に使用されます。ここで、オペランドが T型で表現できない場合、2進数昇格が使用されます。 オペランド型Objectは、null型以外のすべての参照型と、Boolean、Byte、Short、Character、Integer、Long、Float、Doubleという8つのラッパー・クラスを意味します。
表15.25-A. 条件式タイプ(プリミティブ3番目のオペランド、パートI)
| 3rd → | byte |
short |
char |
int |
|---|---|---|---|---|
| 2nd ↓ | ||||
byte |
byte |
short |
bnp(byte、char)
|
byte | bnp(byte,int)
|
Byte |
byte |
short |
bnp(Byte、char)
|
byte | bnp(Byte,int)
|
short |
short |
short |
bnp(short、char)
|
short | bnp(short,int)
|
Short |
short |
short |
bnp(Short、char)
|
short | bnp(Short,int)
|
char |
bnp(char、byte)
|
bnp(char、short)
|
char |
char | bnp(char,int)
|
Character |
bnp(Character、byte)
|
bnp(Character、short)
|
char |
char | bnp(Character,int)
|
int |
byte | bnp(int,byte)
|
short | bnp(int,short)
|
char | bnp(int,char)
|
int |
Integer |
bnp(Integer、byte)
|
bnp(Integer、short)
|
bnp(Integer、char)
|
int |
long |
bnp(long、byte)
|
bnp(long、short)
|
bnp(long、char)
|
bnp(long、int)
|
Long |
bnp(Long、byte)
|
bnp(Long、short)
|
bnp(Long、char)
|
bnp(Long、int)
|
float |
bnp(float、byte)
|
bnp(float、short)
|
bnp(float、char)
|
bnp(float、int)
|
Float |
bnp(Float、byte)
|
bnp(Float、short)
|
bnp(Float、char)
|
bnp(Float、int)
|
double |
bnp(double、byte)
|
bnp(double、short)
|
bnp(double、char)
|
bnp(double、int)
|
Double |
bnp(Double、byte)
|
bnp(Double、short)
|
bnp(Double、char)
|
bnp(Double、int)
|
boolean |
lub(Boolean、Byte)
|
lub(Boolean、Short)
|
lub(Boolean、Character)
|
lub(Boolean、Integer)
|
Boolean |
lub(Boolean、Byte)
|
lub(Boolean、Short)
|
lub(Boolean、Character)
|
lub(Boolean、Integer)
|
null |
lub(null、Byte)
|
lub(null、Short)
|
lub(null、Character)
|
lub(null、Integer)
|
Object |
lub(Object、Byte)
|
lub(Object、Short)
|
lub(Object、Character)
|
lub(Object、Integer)
|
表15.25-B. 条件式タイプ(プリミティブ3番目のオペランド、パートII)
| 3rd → | long |
float |
double |
boolean |
|---|---|---|---|---|
| 2nd ↓ | ||||
byte |
bnp(byte、long)
|
bnp(byte、float)
|
bnp(byte、double)
|
lub(Byte、Boolean)
|
Byte |
bnp(Byte、long)
|
bnp(Byte、float)
|
bnp(Byte、double)
|
lub(Byte、Boolean)
|
short |
bnp(short、long)
|
bnp(short、float)
|
bnp(short、double)
|
lub(Short、Boolean)
|
Short |
bnp(Short、long)
|
bnp(Short、float)
|
bnp(Short、double)
|
lub(Short、Boolean)
|
char |
bnp(char、long)
|
bnp(char、float)
|
bnp(char、double)
|
lub(Character、Boolean)
|
Character |
bnp(Character、long)
|
bnp(Character、float)
|
bnp(Character、double)
|
lub(Character、Boolean)
|
int |
bnp(int、long)
|
bnp(int、float)
|
bnp(int、double)
|
lub(Integer、Boolean)
|
Integer |
bnp(Integer、long)
|
bnp(Integer、float)
|
bnp(Integer、double)
|
lub(Integer、Boolean)
|
long |
long |
bnp(long、float)
|
bnp(long、double)
|
lub(Long、Boolean)
|
Long |
long |
bnp(Long、float)
|
bnp(Long、double)
|
lub(Long、Boolean)
|
float |
bnp(float、long)
|
float |
bnp(float、double)
|
lub(Float、Boolean)
|
Float |
bnp(Float、long)
|
float |
bnp(Float、double)
|
lub(Float、Boolean)
|
double |
bnp(double、long)
|
bnp(double、float)
|
double |
lub(Double、Boolean)
|
Double |
bnp(Double、long)
|
bnp(Double、float)
|
double |
lub(Double、Boolean)
|
boolean |
lub(Boolean、Long)
|
lub(Boolean、Float)
|
lub(Boolean、Double)
|
boolean |
Boolean |
lub(Boolean、Long)
|
lub(Boolean、Float)
|
lub(Boolean、Double)
|
boolean |
null |
lub(null、Long)
|
lub(null、Float)
|
lub(null、Double)
|
lub(null、Boolean)
|
Object |
lub(Object、Long)
|
lub(Object、Float)
|
lub(Object、Double)
|
lub(Object、Boolean)
|
表15.25-C. 条件式タイプ(第3オペランド、第I部を参照)
| 3rd → | Byte |
Short |
Character |
Integer |
|---|---|---|---|---|
| 2nd ↓ | ||||
byte |
byte |
short |
bnp(byte、Character)
|
bnp(byte、Integer)
|
Byte |
Byte |
short |
bnp(Byte、Character)
|
bnp(Byte、Integer)
|
short |
short |
short |
bnp(short、Character)
|
bnp(short、Integer)
|
Short |
short |
Short |
bnp(Short、Character)
|
bnp(Short、Integer)
|
char |
bnp(char、Byte)
|
bnp(char、Short)
|
char |
bnp(char、Integer)
|
Character |
bnp(Character、Byte)
|
bnp(Character、Short)
|
Character |
bnp(Character、Integer)
|
int |
byte | bnp(int,Byte)
|
short | bnp(int,Short)
|
char | bnp(int,Character)
|
int |
Integer |
bnp(Integer、Byte)
|
bnp(Integer、Short)
|
bnp(Integer、Character)
|
Integer |
long |
bnp(long、Byte)
|
bnp(long、Short)
|
bnp(long、Character)
|
bnp(long、Integer)
|
Long |
bnp(Long、Byte)
|
bnp(Long、Short)
|
bnp(Long、Character)
|
bnp(Long、Integer)
|
float |
bnp(float、Byte)
|
bnp(float、Short)
|
bnp(float、Character)
|
bnp(float、Integer)
|
Float |
bnp(Float、Byte)
|
bnp(Float、Short)
|
bnp(Float、Character)
|
bnp(Float、Integer)
|
double |
bnp(double、Byte)
|
bnp(double、Short)
|
bnp(double、Character)
|
bnp(double、Integer)
|
Double |
bnp(Double、Byte)
|
bnp(Double、Short)
|
bnp(Double、Character)
|
bnp(Double、Integer)
|
boolean |
lub(Boolean、Byte)
|
lub(Boolean、Short)
|
lub(Boolean、Character)
|
lub(Boolean、Integer)
|
Boolean |
lub(Boolean、Byte)
|
lub(Boolean、Short)
|
lub(Boolean、Character)
|
lub(Boolean、Integer)
|
null |
Byte |
Short |
Character |
Integer |
Object |
lub(Object、Byte)
|
lub(Object、Short)
|
lub(Object、Character)
|
lub(Object、Integer)
|
表15.25-D. 条件式タイプ(参照3番目のオペランド、パートII)
| 3rd → | Long |
Float |
Double |
Boolean |
|---|---|---|---|---|
| 2nd ↓ | ||||
byte |
bnp(byte、Long)
|
bnp(byte、Float)
|
bnp(byte、Double)
|
lub(Byte、Boolean)
|
Byte |
bnp(Byte、Long)
|
bnp(Byte、Float)
|
bnp(Byte、Double)
|
lub(Byte、Boolean)
|
short |
bnp(short、Long)
|
bnp(short、Float)
|
bnp(short、Double)
|
lub(Short、Boolean)
|
Short |
bnp(Short、Long)
|
bnp(Short、Float)
|
bnp(Short、Double)
|
lub(Short、Boolean)
|
char |
bnp(char、Long)
|
bnp(char、Float)
|
bnp(char、Double)
|
lub(Character、Boolean)
|
Character |
bnp(Character、Long)
|
bnp(Character、Float)
|
bnp(Character、Double)
|
lub(Character、Boolean)
|
int |
bnp(int、Long)
|
bnp(int、Float)
|
bnp(int、Double)
|
lub(Integer、Boolean)
|
Integer |
bnp(Integer、Long)
|
bnp(Integer、Float)
|
bnp(Integer、Double)
|
lub(Integer、Boolean)
|
long |
long |
bnp(long、Float)
|
bnp(long、Double)
|
lub(Long、Boolean)
|
Long |
Long |
bnp(Long、Float)
|
bnp(Long、Double)
|
lub(Long、Boolean)
|
float |
bnp(float、Long)
|
float |
bnp(float、Double)
|
lub(Float、Boolean)
|
Float |
bnp(Float、Long)
|
Float |
bnp(Float、Double)
|
lub(Float、Boolean)
|
double |
bnp(double、Long)
|
bnp(double、Float)
|
double |
lub(Double、Boolean)
|
Double |
bnp(Double、Long)
|
bnp(Double、Float)
|
Double |
lub(Double、Boolean)
|
boolean |
lub(Boolean、Long)
|
lub(Boolean、Float)
|
lub(Boolean、Double)
|
boolean |
Boolean |
lub(Boolean、Long)
|
lub(Boolean、Float)
|
lub(Boolean、Double)
|
Boolean |
null |
Long |
Float |
Double |
Boolean |
Object |
lub(Object、Long)
|
lub(Object、Float)
|
lub(Object、Double)
|
lub(Object、Boolean)
|
表15.25-E. 条件式タイプ(第3オペランド、第III部を参照)
| 3rd → | null |
Object |
|---|---|---|
| 2nd ↓ | ||
byte |
lub(Byte、null)
|
lub(Byte、Object)
|
Byte |
Byte |
lub(Byte、Object)
|
short |
lub(Short、null)
|
lub(Short、Object)
|
Short |
Short |
lub(Short、Object)
|
char |
lub(Character、null)
|
lub(Character、Object)
|
Character |
Character |
lub(Character、Object)
|
int |
lub(Integer、null)
|
lub(Integer、Object)
|
Integer |
Integer |
lub(Integer、Object)
|
long |
lub(Long、null)
|
lub(Long、Object)
|
Long |
Long |
lub(Long、Object)
|
float |
lub(Float、null)
|
lub(Float、Object)
|
Float |
Float |
lub(Float、Object)
|
double |
lub(Double、null)
|
lub(Double、Object)
|
Double |
Double |
lub(Double、Object)
|
boolean |
lub(Boolean、null)
|
lub(Boolean、Object)
|
Boolean |
Boolean |
lub(Boolean、Object)
|
null |
null |
lub(null、Object)
|
Object |
Object |
Object |
実行時に、条件式の最初のオペランド式が最初に評価されます。 必要に応じて、結果に対してボックス化解除変換が実行されます。
結果のboolean値は、2番目または3番目のオペランド式の選択に使用されます。
最初のオペランドの値がtrueの場合は、2番目のオペランド式が選択されます。
最初のオペランドの値がfalseの場合は、3番目のオペランド式が選択されます。
選択したオペランド式が評価され、結果の値は、次に示すルールによって決定される条件式のタイプに変換されます。
この変換には、ボクシング変換またはボクシング解除変換(§5.1.7、§5.1.8)が含まれる場合があります。
選択されていないオペランド式は、条件式のその特定の評価に対して評価されません。
ブール条件式は、スタンドアロン式です(§15.2)。
ブール条件式の型は、次のように決定されます。
2番目と3番目のオペランドが両方ともBoolean型の場合、条件式の型はBooleanです。
それ以外の場合、条件式の型はbooleanです。
数値条件式は、スタンドアロン式です(§15.2)。
数値条件式の型は、次のように決定されます。
2番目と3番目のオペランドが同じ型の場合、それは条件式の型です。
2番目と3番目のオペランドの1つがプリミティブ型 Tで、もう一方の型がボクシング変換(§5.1.7)を Tに適用した結果である場合、条件式の型は Tです。
オペランドの1つがbyte型またはByte型で、もう1つがshort型またはShort型の場合、条件式の型はshortです。
オペランドの1つがT型で、Tがbyte、shortまたはcharで、もう1つのオペランドがint型の定数式(§15.29)で、その値がT型で表現可能な場合、条件式の型はTです。
オペランドの1つがT型で、TがByte、ShortまたはCharacterで、もう1つのオペランドがint型の定数式で、Tへのボックス化解除変換を適用した結果であるU型で値が表現可能な場合、条件式の型はUです。
それ以外の場合は、一般的な数値昇格(§5.6)が2番目と3番目のオペランドに適用され、条件式の型が2番目と3番目のオペランドの昇格された型になります。
数値プロモーションには、ボックス化解除変換が含まれる場合があることに注意してください(§5.1.8)。
参照条件式は、代入コンテキストまたは呼出しコンテキスト(§5.2. §5.3)に表示される場合、多式です。 それ以外の場合はスタンドアロン式です。
ターゲット型がTの特定の種類のコンテキストに多参照条件式が表示される場合、その2番目と3番目のオペランド式は、ターゲット型がTの同じ種類のコンテキストにも同様に表示されます。
ポリ参照条件式は、2番目と3番目のオペランド式が Tと互換性がある場合に、ターゲット型 Tと互換性があります。
ポリ参照条件式の型は、そのターゲット型と同じです。
スタンドアロン参照条件式の型は、次のように決定されます。
2番目と3番目のオペランドが同じ型(null型の場合もある)の場合、これは条件式の型です。
2番目と3番目のオペランドのいずれかの型がnull型で、もう一方のオペランドの型が参照型である場合、条件式の型はその参照型になります。
それ以外の場合、2番目と3番目のオペランドはそれぞれ S1型と S2型になります。 T1を S1へのボクシング変換の適用によって生じる型にし、T2を S2へのボクシング変換の適用によって生じる型にしましょう。 条件式の型は、取得変換(§5.1.10)をlub(T1、 T2)に適用した結果です。
参照条件式は多式にできるため、オペランドにコンテキストを渡すことができます。 これにより、ラムダ式およびメソッド参照式をオペランドとして表示できます。
return ... ? (x->x) : (x->-x);
また、一般的なメソッド呼出しの型チェックを改善するために、追加の情報を使用することもできます。 Java SE 8より前は、この割当ては適切に型付けされていました。
List<String> ls = Arrays.asList();
しかし、これは:
List<String> ls = ... ? Arrays.asList() : Arrays.asList("a","b");
前述のルールにより、両方の割当てを適切に型付けされたものとみなすことができます。
参照条件式は、ポリ式を含むためにオペランドとしてポリ式を含む必要はありません。 これは、その文脈が出現する文脈によって単純に多重表現である。 たとえば、次のコードでは、条件式は多式であり、各オペランドはClass<? super Integer>をターゲットとする代入コンテキスト内にあるとみなされます。
Class<? super Integer> choose(boolean b,
Class<Integer> c1,
Class<Number> c2) {
return b ? c1 : c2;
}
条件式が多式でない場合、その型はlub(Class<Integer>、 Class<Number>) = Class<? extends Number>で、chooseの戻り型と互換性がないため、コンパイル時にエラーが発生します。
割当て演算子は12個あり、すべて構文的に右相関(右から左のグループ)です。 したがって、a=b=cはa=(b=c)を意味し、cの値をbに割り当て、bの値をaに割り当てます。
=*=/=%=+=-=<<=>>=>>>=&=^=|=
代入演算子の最初のオペランドの結果は変数である必要があります。そうでない場合、コンパイル時にエラーが発生します。
このオペランドは、ローカル変数、現在のオブジェクトまたはクラスのフィールドなどの名前付き変数、またはフィールド・アクセス(§15.11)または配列アクセス(§15.10.3)の結果として計算された変数です。
代入式の型は、取得変換後の変数の型です(§5.1.10)。
実行時に、代入式の結果は代入が発生した後の変数の値です。 代入式の結果自体は変数ではありません。
finalが宣言された変数は、(確実に未割当てでないかぎり(§16 (Definite Assignment))には割り当てられません。このようなfinal変数のアクセスが式として使用される場合、結果は変数ではなく値であるため、代入演算子の最初のオペランドとして使用できません。
= 右側のオペランドの型が変数の型と互換性がない場合(§5.2)、コンパイル時エラーが発生します。
それ以外の場合は、実行時に式が3つの方法のいずれかで評価されます。
左側のオペランド式がフィールド・アクセス式e.f (§15.11)で、1つ以上のカッコで囲まれている場合があると、次のようになります。
まず、式eが評価されます。 eの評価が突然完了した場合、代入式も同じ理由で突然完了します。
次に、右側のオペランドが評価されます。 右手式の評価が突然完了した場合、代入式は同じ理由で突然完了します。
次に、e.fで示されるフィールドがstaticでなく、前述のeの評価結果がnullの場合、NullPointerExceptionがスローされます。
それ以外の場合、e.fで示される変数には、前述のとおりに右側のオペランドの値が割り当てられます。
左側のオペランドが配列アクセス式(§15.10.3)で、1つ以上のカッコで囲まれている可能性がある場合、次のようになります。
まず、左側のオペランド配列アクセス式の配列参照部分式が評価されます。 この評価が突然完了すると、代入式が同じ理由で突然完了します。つまり、(左側のオペランド配列アクセス式の)インデックス副式と右側のオペランドは評価されず、代入は行われません。
それ以外の場合は、左側のオペランド配列アクセス式の索引副式が評価されます。 この評価が突然完了すると、代入式が同じ理由で突然完了し、右側のオペランドは評価されず、代入は行われません。
それ以外の場合は、右側のオペランドが評価されます。 この評価が突然完了した場合、代入式は同じ理由で突然完了し、代入は行われません。
それ以外の場合は、配列参照の副式の値がnullの場合、代入は行われず、NullPointerExceptionがスローされます。
それ以外の場合、配列参照のsubexpressionの値は、実際には配列を参照します。 索引の副式の値が0より小さいか、配列のlength以上である場合、代入は行われず、ArrayIndexOutOfBoundsExceptionがスローされます。
それ以外の場合は、索引の副式の値を使用して、配列参照副式の値によって参照される配列のコンポーネントが選択されます。
このコンポーネントは変数であり、SC型を呼び出します。 また、コンパイル時に決定される代入演算子の左側のオペランドの型を TCにします。 次に、次の2つの可能性があります。
TCがプリミティブ型の場合、SCは必ず TCと同じになります。
右側のオペランドの値は、選択した配列コンポーネントのタイプに変換され、変換の結果が配列コンポーネントに格納されます。
TCが参照型の場合、SCは TCと同じではなく、TCを拡張または実装する型になる可能性があります。
RCは、実行時に右側のオペランドの値によって参照されるオブジェクトのクラスになります。
Javaコンパイラは、配列コンポーネントの型が正確にTCであることをコンパイル時に証明できる場合があります(たとえば、TCはfinalです)。 ただし、Javaコンパイラが、配列コンポーネントが正確にTC型であることをコンパイル時に証明できない場合は、クラスRCが配列コンポーネントの実際の型SCと代入互換性があることを確認するために、実行時にチェックを実行する必要があります(§5.2)。
このチェックは、絞込みキャスト(§5.5、§15.16)と似ていますが、チェックが失敗すると、ClassCastExceptionではなくArrayStoreExceptionがスローされます。
クラスRCがタイプSCに割り当てられない場合、割当ては行われず、ArrayStoreExceptionがスローされます。
それ以外の場合、右側のオペランドの参照値は、選択した配列コンポーネントに格納されます。
それ以外の場合は、次の3つのステップが必要です。
最初に、左側のオペランドが評価され、変数が生成されます。 この評価が突然完了すると、代入式が同じ理由で突然完了します。右側のオペランドは評価されず、代入は発生しません。
それ以外の場合は、右側のオペランドが評価されます。 この評価が突然完了した場合、代入式は同じ理由で突然完了し、代入は行われません。
それ以外の場合は、右側のオペランドの値が左側の変数の型に変換され、変換の結果が変数に格納されます。
例15.26.1-1 配列コンポーネントへの単純な割り当て
class ArrayReferenceThrow extends RuntimeException { }
class IndexThrow extends RuntimeException { }
class RightHandSideThrow extends RuntimeException { }
class IllustrateSimpleArrayAssignment {
static Object[] objects = { new Object(), new Object() };
static Thread[] threads = { new Thread(), new Thread() };
static Object[] arrayThrow() {
throw new ArrayReferenceThrow();
}
static int indexThrow() {
throw new IndexThrow();
}
static Thread rightThrow() {
throw new RightHandSideThrow();
}
static String name(Object q) {
String sq = q.getClass().getName();
int k = sq.lastIndexOf('.');
return (k < 0) ? sq : sq.substring(k+1);
}
static void testFour(Object[] x, int j, Object y) {
String sx = x == null ? "null" : name(x[0]) + "s";
String sy = name(y);
System.out.println();
try {
System.out.print(sx + "[throw]=throw => ");
x[indexThrow()] = rightThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[throw]=" + sy + " => ");
x[indexThrow()] = y;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[" + j + "]=throw => ");
x[j] = rightThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[" + j + "]=" + sy + " => ");
x[j] = y;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
}
public static void main(String[] args) {
try {
System.out.print("throw[throw]=throw => ");
arrayThrow()[indexThrow()] = rightThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[throw]=Thread => ");
arrayThrow()[indexThrow()] = new Thread();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]=throw => ");
arrayThrow()[1] = rightThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]=Thread => ");
arrayThrow()[1] = new Thread();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
testFour(null, 1, new StringBuffer());
testFour(null, 9, new Thread());
testFour(objects, 1, new StringBuffer());
testFour(objects, 1, new Thread());
testFour(objects, 9, new StringBuffer());
testFour(objects, 9, new Thread());
testFour(threads, 1, new StringBuffer());
testFour(threads, 1, new Thread());
testFour(threads, 9, new StringBuffer());
testFour(threads, 9, new Thread());
}
}
このプログラムは出力を生成します:
throw[throw]=throw => ArrayReferenceThrow throw[throw]=Thread => ArrayReferenceThrow throw[1]=throw => ArrayReferenceThrow throw[1]=Thread => ArrayReferenceThrow null[throw]=throw => IndexThrow null[throw]=StringBuffer => IndexThrow null[1]=throw => RightHandSideThrow null[1]=StringBuffer => NullPointerException null[throw]=throw => IndexThrow null[throw]=Thread => IndexThrow null[9]=throw => RightHandSideThrow null[9]=Thread => NullPointerException Objects[throw]=throw => IndexThrow Objects[throw]=StringBuffer => IndexThrow Objects[1]=throw => RightHandSideThrow Objects[1]=StringBuffer => Okay! Objects[throw]=throw => IndexThrow Objects[throw]=Thread => IndexThrow Objects[1]=throw => RightHandSideThrow Objects[1]=Thread => Okay! Objects[throw]=throw => IndexThrow Objects[throw]=StringBuffer => IndexThrow Objects[9]=throw => RightHandSideThrow Objects[9]=StringBuffer => ArrayIndexOutOfBoundsException Objects[throw]=throw => IndexThrow Objects[throw]=Thread => IndexThrow Objects[9]=throw => RightHandSideThrow Objects[9]=Thread => ArrayIndexOutOfBoundsException Threads[throw]=throw => IndexThrow Threads[throw]=StringBuffer => IndexThrow Threads[1]=throw => RightHandSideThrow Threads[1]=StringBuffer => ArrayStoreException Threads[throw]=throw => IndexThrow Threads[throw]=Thread => IndexThrow Threads[1]=throw => RightHandSideThrow Threads[1]=Thread => Okay! Threads[throw]=throw => IndexThrow Threads[throw]=StringBuffer => IndexThrow Threads[9]=throw => RightHandSideThrow Threads[9]=StringBuffer => ArrayIndexOutOfBoundsException Threads[throw]=throw => IndexThrow Threads[throw]=Thread => IndexThrow Threads[9]=throw => RightHandSideThrow Threads[9]=Thread => ArrayIndexOutOfBoundsException
ロットの最も興味深いケースは、最後から13番目です:
Threads[1]=StringBuffer => ArrayStoreException
これは、Thread型のコンポーネントの配列にStringBufferへの参照を格納しようとすると、ArrayStoreExceptionがスローされることを示します。 このコードは、コンパイル時に型が修正されます。割当てには、Object[]型の左側とObject型の右側があります。 実行時、メソッドtestFourに対する最初の実引数は"array of Thread"のインスタンスへの参照であり、3番目の実引数はクラスStringBufferのインスタンスへの参照です。
E1 op= E2形式の複合代入式は、E1 = (T) ((E1) op (E2))と同等です。ここで、TはE1の型です。ただし、E1は1回のみ評価されます。
たとえば、次のコードは正しいです。
short x = 3; x += 4.6;
結果として、xの値は7になります。これは、次と同等であるためです。
short x = 3; x = (short)(x + 4.6);
実行時に、式は2つの方法のいずれかで評価されます。
左側のオペランド式が配列アクセス式でない場合、次のようになります。
最初に、左側のオペランドが評価され、変数が生成されます。 この評価が突然完了すると、代入式が同じ理由で突然完了します。右側のオペランドは評価されず、代入は発生しません。
それ以外の場合は、左側のオペランドの値が保存され、右側のオペランドが評価されます。 この評価が突然完了した場合、代入式は同じ理由で突然完了し、代入は行われません。
それ以外の場合は、左側の変数の保存値と右側のオペランドの値を使用して、複合代入演算子によって示されるバイナリ操作が実行されます。 この操作が突然完了した場合、代入式は同じ理由で突然完了し、代入は発生しません。
それ以外の場合、バイナリ操作の結果は左側の変数の型に変換され、変換の結果は変数に格納されます。
左側のオペランド式が配列アクセス式(§15.10.3)である場合、次のようになります。
まず、左側のオペランド配列アクセス式の配列参照部分式が評価されます。 この評価が突然完了すると、代入式が同じ理由で突然完了します。つまり、(左側のオペランド配列アクセス式の)インデックス副式と右側のオペランドは評価されず、代入は行われません。
それ以外の場合は、左側のオペランド配列アクセス式の索引副式が評価されます。 この評価が突然完了すると、代入式が同じ理由で突然完了し、右側のオペランドは評価されず、代入は行われません。
それ以外の場合は、配列参照の副式の値がnullの場合、代入は行われず、NullPointerExceptionがスローされます。
それ以外の場合、配列参照のsubexpressionの値は、実際には配列を参照します。 索引の副式の値が0より小さいか、配列のlength以上である場合、代入は行われず、ArrayIndexOutOfBoundsExceptionがスローされます。
それ以外の場合は、索引の副式の値を使用して、配列参照副式の値によって参照される配列のコンポーネントが選択されます。 このコンポーネントの値が保存され、右側のオペランドが評価されます。 この評価が突然完了した場合、代入式は同じ理由で突然完了し、代入は行われません。
単純代入演算子の場合、右側のオペランドの評価は配列参照の副式および索引の副次式のチェックの前に行われますが、複合代入演算子の場合、これらのチェックの後に右側のオペランドの評価が発生します。
それ以外の場合は、前のステップで選択されたアレイ コンポーネントについて考えてみます。アレイ コンポーネントの値は保存されています。 このコンポーネントは変数で、S型を呼び出します。 また、Tは、コンパイル時に決定される代入演算子の左側のオペランドの型になります。
Tがプリミティブ型の場合、Sは必ず Tと同じになります。
配列コンポーネントの保存された値および右側のオペランドの値は、複合代入演算子によって示されるバイナリ操作の実行に使用されます。
この操作が突然完了した場合(唯一の可能性はゼロによる整数除算です- §15.17.2を参照)、代入式は同じ理由で突然完了し、代入は発生しません。
それ以外の場合、バイナリ操作の結果は、選択した配列コンポーネントのタイプに変換され、変換の結果は配列コンポーネントに格納されます。
Tが参照型の場合は、Stringである必要があります。 クラスStringはfinalクラスであるため、SもStringである必要があります。
したがって、単純代入演算子に必要となる場合がある実行時チェックは、複合代入演算子には必要ありません。
配列コンポーネントの保存された値と右側のオペランドの値は、複合代入演算子(必ず+=)によって示されるバイナリ操作(文字列連結)を実行するために使用されます。 この操作が突然完了した場合、代入式は同じ理由で突然完了し、代入は発生しません。
それ以外の場合は、バイナリ操作のString結果が配列コンポーネントに格納されます。
例15.26.2-1. 配列コンポーネントへの複合割当て
class ArrayReferenceThrow extends RuntimeException { }
class IndexThrow extends RuntimeException { }
class RightHandSideThrow extends RuntimeException { }
class IllustrateCompoundArrayAssignment {
static String[] strings = { "Simon", "Garfunkel" };
static double[] doubles = { Math.E, Math.PI };
static String[] stringsThrow() {
throw new ArrayReferenceThrow();
}
static double[] doublesThrow() {
throw new ArrayReferenceThrow();
}
static int indexThrow() {
throw new IndexThrow();
}
static String stringThrow() {
throw new RightHandSideThrow();
}
static double doubleThrow() {
throw new RightHandSideThrow();
}
static String name(Object q) {
String sq = q.getClass().getName();
int k = sq.lastIndexOf('.');
return (k < 0) ? sq : sq.substring(k+1);
}
static void testEight(String[] x, double[] z, int j) {
String sx = (x == null) ? "null" : "Strings";
String sz = (z == null) ? "null" : "doubles";
System.out.println();
try {
System.out.print(sx + "[throw]+=throw => ");
x[indexThrow()] += stringThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sz + "[throw]+=throw => ");
z[indexThrow()] += doubleThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[throw]+=\"heh\" => ");
x[indexThrow()] += "heh";
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sz + "[throw]+=12345 => ");
z[indexThrow()] += 12345;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[" + j + "]+=throw => ");
x[j] += stringThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sz + "[" + j + "]+=throw => ");
z[j] += doubleThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sx + "[" + j + "]+=\"heh\" => ");
x[j] += "heh";
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print(sz + "[" + j + "]+=12345 => ");
z[j] += 12345;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
}
public static void main(String[] args) {
try {
System.out.print("throw[throw]+=throw => ");
stringsThrow()[indexThrow()] += stringThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[throw]+=throw => ");
doublesThrow()[indexThrow()] += doubleThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[throw]+=\"heh\" => ");
stringsThrow()[indexThrow()] += "heh";
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[throw]+=12345 => ");
doublesThrow()[indexThrow()] += 12345;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]+=throw => ");
stringsThrow()[1] += stringThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]+=throw => ");
doublesThrow()[1] += doubleThrow();
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]+=\"heh\" => ");
stringsThrow()[1] += "heh";
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
try {
System.out.print("throw[1]+=12345 => ");
doublesThrow()[1] += 12345;
System.out.println("Okay!");
} catch (Throwable e) { System.out.println(name(e)); }
testEight(null, null, 1);
testEight(null, null, 9);
testEight(strings, doubles, 1);
testEight(strings, doubles, 9);
}
}
このプログラムは出力を生成します:
throw[throw]+=throw => ArrayReferenceThrow throw[throw]+=throw => ArrayReferenceThrow throw[throw]+="heh" => ArrayReferenceThrow throw[throw]+=12345 => ArrayReferenceThrow throw[1]+=throw => ArrayReferenceThrow throw[1]+=throw => ArrayReferenceThrow throw[1]+="heh" => ArrayReferenceThrow throw[1]+=12345 => ArrayReferenceThrow null[throw]+=throw => IndexThrow null[throw]+=throw => IndexThrow null[throw]+="heh" => IndexThrow null[throw]+=12345 => IndexThrow null[1]+=throw => NullPointerException null[1]+=throw => NullPointerException null[1]+="heh" => NullPointerException null[1]+=12345 => NullPointerException null[throw]+=throw => IndexThrow null[throw]+=throw => IndexThrow null[throw]+="heh" => IndexThrow null[throw]+=12345 => IndexThrow null[9]+=throw => NullPointerException null[9]+=throw => NullPointerException null[9]+="heh" => NullPointerException null[9]+=12345 => NullPointerException Strings[throw]+=throw => IndexThrow doubles[throw]+=throw => IndexThrow Strings[throw]+="heh" => IndexThrow doubles[throw]+=12345 => IndexThrow Strings[1]+=throw => RightHandSideThrow doubles[1]+=throw => RightHandSideThrow Strings[1]+="heh" => Okay! doubles[1]+=12345 => Okay! Strings[throw]+=throw => IndexThrow doubles[throw]+=throw => IndexThrow Strings[throw]+="heh" => IndexThrow doubles[throw]+=12345 => IndexThrow Strings[9]+=throw => ArrayIndexOutOfBoundsException doubles[9]+=throw => ArrayIndexOutOfBoundsException Strings[9]+="heh" => ArrayIndexOutOfBoundsException doubles[9]+=12345 => ArrayIndexOutOfBoundsException
ロットの最も興味深いケースは、最後から11番目と12番目です:
Strings[1]+=throw => RightHandSideThrow doubles[1]+=throw => RightHandSideThrow
これらは、例外をスローする右側が実際に例外をスローするケースです。さらに、ロット内のそのようなケースはそれらのみです。 これは、NULL配列参照値と範囲外索引値のチェック後に、右側のオペランドの評価が実際に発生することを示しています。
例15.26.2-2. 右側の評価の前に、複合割当の左側の値が保存されます
class Test {
public static void main(String[] args) {
int k = 1;
int[] a = { 1 };
k += (k = 4) * (k + 2);
a[0] += (a[0] = 4) * (a[0] + 2);
System.out.println("k==" + k + " and a[0]==" + a[0]);
}
}
このプログラムは出力を生成します:
k==25 and a[0]==25
kの値1は、右側のオペランド(k = 4) * (k + 2)が評価される前に、複合代入演算子+=によって保存されます。 この右側のオペランドを評価すると、4がkに割り当てられ、k + 2の値6が計算され、4に6が乗算されて24が取得されます。 これは、保存された値1に追加され、25が取得され、+=演算子によってkに格納されます。 a[0]を使用するケースには、同一の分析が適用されます。
つまり、次の文があります。
k += (k = 4) * (k + 2); a[0] += (a[0] = 4) * (a[0] + 2);
次の文とまったく同じように動作します。
k = k + (k = 4) * (k + 2); a[0] = a[0] + (a[0] = 4) * (a[0] + 2);
ラムダ式はメソッドのようなもので、仮パラメータと本体(式またはブロック)のリストをそれらのパラメータで表します。
ラムダ式は、常にポリ式です(§15.2)。
ラムダ式が代入コンテキスト(§5.2)、呼出しコンテキスト(§5.3)、またはキャスト・コンテキスト(§5.5)以外のプログラムで発生した場合、コンパイル時にエラーが発生します。
ラムダ式を評価すると、関数型インタフェースのインスタンスが生成されます(§9.8)。 ラムダ式の評価では、式のボディは実行されません。かわりに、ファンクション・インタフェースの適切なメソッドが呼び出されたときに、後で発生する可能性があります。
次に、ラムダ式の例を示します。
() -> {} // No parameters; result is void
() -> 42 // No parameters, expression body
() -> null // No parameters, expression body
() -> { return 42; } // No parameters, block body with return
() -> { System.gc(); } // No parameters, void block body
() -> { // Complex block body with returns
if (true) return 12;
else {
int result = 15;
for (int i = 1; i < 10; i++)
result *= i;
return result;
}
}
(int x) -> x+1 // Single declared-type parameter
(int x) -> { return x+1; } // Single declared-type parameter
(x) -> x+1 // Single inferred-type parameter
x -> x+1 // Parentheses optional for
// single inferred-type parameter
(String s) -> s.length() // Single declared-type parameter
(Thread t) -> { t.start(); } // Single declared-type parameter
s -> s.length() // Single inferred-type parameter
t -> { t.start(); } // Single inferred-type parameter
(int x, int y) -> x+y // Multiple declared-type parameters
(x, y) -> x+y // Multiple inferred-type parameters
(x, int y) -> x+y // Illegal: can't mix inferred and declared types
(x, final y) -> x+y // Illegal: no modifiers with inferred types
この構文には、単純なラムダ式でカッコ・ノイズを最小限に抑えるという利点があります。これは、ラムダ式がメソッドの引数である場合や、本体が別のラムダ式である場合に特に役立ちます。 また、式と文の形式も明確に区別されるため、あいまいさや';'トークンへの過剰な依存を回避できます。 完全なラムダ式またはそのボディ式を視覚的に区別するために余分なカッコが必要な場合、カッコは自然にサポートされます(演算子の優先順位が不明瞭な場合と同様)。
構文にはいくつかの解析の課題があります。 Javaプログラミング言語では、型と式を'('トークンの後に区別するために、常に任意の先読みが必要でした。これは、キャスト式またはカッコで囲まれた式です。 これは、ジェネリックスが型でバイナリ演算子'<'および'>'を再利用したときに悪化しました。 ラムダ式では、'('に続くトークンによって、型、式またはラムダ・パラメータ・リストが記述される可能性があります。 一部のトークンはすぐにパラメータ・リスト(注釈、final)を示します。その他の場合、パラメータ・リストとして解釈する必要がある特定のパターンがあります(1行に2つの名前、'<'と'>'の内部にネストされていない',')。また、')'の後に'->'が検出されるまで、決定できない場合があります。 これを効率的に解析する方法について考える最も簡単な方法は、ステート・マシンを使用することです。各状態は、可能な解釈のサブセット(タイプ、式またはパラメータ)を表し、マシンがシングルトンである状態に遷移する場合、パーサーはそれがどのケースであるかを認識します。 ただし、これは固定先読み文法にはあまり優雅にマッピングされません。
特別なnull形式はありません。引数がゼロのラムダ式は、() として表されます。 明らかな特殊ケース構文-> ...は、引数リストとキャスト(-> ...(x) )の間に曖昧さが生じているため、機能しません。
-> ...
ラムダ式は型パラメータを宣言できません。 これを行うことは意味的に意味がありますが、自然構文(パラメータ・リストの前に型パラメータ・リストを指定)では、あいまいな曖昧さが生じます。 たとえば、次のような文があるとします:
foo( (x) < y , z > (w) -> v )これは、1つの引数を持つfooの呼出し(型xにキャストされる汎用ラムダ)の場合もあれば、2つの引数を持つfooの呼出し(比較の結果の両方)の場合もあり、2つ目はzとラムダ式を比較します。 (厳密に言えば、ラムダ式は関係演算子>のオペランドとして無意味ですが、これは文法を構築するための本質的な仮定です。)
非プリミティブ・キャスト(§15.15)の後に-および+を使用することを基本的に禁止する、キャストに関するあいまいさ解決の前例がありますが、そのアプローチを汎用ラムダに拡張するには、文法に侵襲的な変更が必要になります。
ラムダ式の仮パラメータは、カンマ区切りの標準パラメータ指定子のカッコ付きリスト、カンマ区切りの簡潔なパラメータ指定子のカッコ付きリスト、またはカッコなしの1つの簡潔なパラメータ指定子によって指定されます(ある場合)。
したがって、ラムダ式が1つの仮パラメータを正確に持つ場合は、シングルトン・リスト(int x)または(x)で指定するか、カッコをxとして完全に除去することによって指定できます。
通常のパラメータ指定子は、オプションの修飾子、型(またはvar)、および識別子またはキーワード_ (アンダースコア)で構成されます。 識別子が存在する場合は、仮パラメータの名前を指定します。 キーワード_が存在する場合、仮パラメータを名前で参照することはできません。
簡潔なパラメータ指定子は、識別子またはキーワード_で構成されます。 識別子が存在する場合は、仮パラメータの名前を指定します。 キーワード_が存在する場合、仮パラメータを名前で参照することはできません。
名前で参照できないラムダ式の仮パラメータは、名前のないラムダ・パラメータと呼ばれます。
ラムダ式に仮パラメータがない場合、->およびラムダ本体の前に空のカッコのペアが表示されます。
var
_
ここでは、便宜上、§8.4.1、§8.3、および §4.3からの次のプロダクションを示します。
ラムダ式の仮パラメータは、通常のパラメータ指定子で指定されている場合にのみ、finalを宣言するか、注釈を付けることができます。 仮パラメータが簡潔な仮指定子によって指定されている場合、仮パラメータはfinalではなく、注釈はありません。
ラムダ式の仮パラメータは、通常のパラメータ指定子内の型の後に省略記号で示される変数引数パラメータです。 ラムダ式には、最大1つの可変引数パラメータを使用できます。 変数の引数パラメータが、最後の位置を除く通常のパラメータ指定子のリストのどこかにある場合、コンパイル時にエラーが発生します。
ラムダ式の各仮パラメータには、推測型または宣言型があります。
仮パラメータが、varを使用する通常のパラメータ指定子または簡潔なパラメータ指定子によって指定されている場合、仮パラメータは推論型を持ちます。 この型は、ラムダ式(§15.27.3)をターゲットとする関数型インタフェース型から推測されます。
仮パラメータがvarを使用しない通常のパラメータ指定子で指定されている場合、仮パラメータは宣言された型を持ちます。 宣言された型は次のように決定されます:
次のラムダ・パラメータ・リストは区別されません:
(int... x)->BODY (int[] x)->BODY
機能インタフェースのabstractメソッドが固定アリティであるか可変アリティであるかに関係なく、いずれかを使用できます。 (これは、メソッドのオーバーライドのルールと一貫性があります。) ラムダ式は直接呼び出されないため、関数インタフェースがint[]を使用する仮パラメータにint...を使用すると、周囲のプログラムに影響を与えません。 ラムダ本文では、可変引数パラメータは配列型パラメータと同様に扱われます。
すべての仮パラメータに宣言された型があるラムダ式は、明示的に型指定されます。 すべての仮パラメータに推測型があるラムダ式は、暗黙的に型指定されます。 仮パラメータのないラムダ式は、明示的に型指定されます。
ラムダ式が暗黙的に型指定されている場合、そのラムダ本文は、それが出現するコンテキストに従って解釈されます。 具体的には、本文内の式の型、本文によってスローされるチェック例外、および本文内のコードの型の正確性はすべて、仮パラメータに対して推測される型に依存します。 これは、ラムダ本文の型チェックを試行する前に仮パラメータ型の推論が必要であることを意味します。
ラムダ式が宣言された型および推測型を持つ仮パラメータを持つ仮パラメータを宣言する場合、コンパイル時にエラーが発生します。
このルールでは、仮パラメータ((x, int y) -> BODYや(var x, int y) -> BODYなど)に推測型と宣言型が混在しないようにします。 すべての仮パラメータに推測型がある場合、文法は識別子とvarパラメータ指定子((x, var y) -> BODYや(var x, y) -> BODYなど)の混在を防ぎます。
仮パラメータ宣言の注釈修飾子に関する規則は、§9.7.4および§9.7.5で指定されています。
仮パラメータ宣言の修飾子としてfinalが複数回出現した場合、コンパイル時にエラーが発生します。
仮パラメータのLambdaParameterTypeがvarで、同じ仮パラメータのVariableDeclaratorIdに1つ以上のカッコのペアが含まれている場合、コンパイル時にエラーが発生します。
仮パラメータの範囲とシャドウ化は、§6.3および §6.4で規定されています。
ネストされたクラスまたはインタフェース、またはネストされたラムダ式からの仮パラメータへの参照は、§6.5.6.1で指定されているように制限されます。
ラムダ式で同じ名前の2つの仮パラメータを宣言すると、コンパイル時にエラーが発生します。 (つまり、その宣言には同じ識別子が記述されています。)
一方、ラムダ式では、(_, _) -> BODYや(String _, int _) -> BODYなど、複数の名前のないラムダ・パラメータを宣言できます。
finalと宣言された仮パラメータがラムダ式の本体内に割り当てられている場合、コンパイル時にエラーが発生します。
ラムダ式が(メソッド呼出し式(§15.12)を介して)呼び出されると、実際の引数式の値は、ラムダ本体の実行前に、新しく作成された各パラメータ変数(宣言型または推測型)を初期化します。 NormalLambdaParameterまたはConciseLambdaParameterに表示されるIdentifierは、仮パラメータを参照するためにラムダ本体で単純名として使用できます。
ラムダ本体は、単一の式またはブロック(§14.2)のいずれかです。 メソッド本文と同様に、ラムダ本文は、呼出しが発生するたびに実行されるコードを記述します。
匿名クラス宣言に表示されるコードとは異なり、ラムダ本体に表示される名前およびthisおよびsuperキーワードの意味は、参照される宣言のアクセシビリティとともに、周囲のコンテキストと同じです(ラムダ・パラメータによって新しい名前が導入される場合を除く)。
ラムダ式の本体内のthis (明示的および暗黙的の両方)の透過性(つまり、周囲のコンテキストと同じように処理)により、実装の柔軟性が向上し、本体内の修飾されていない名前の意味が過負荷解決に依存しないようになります。
実質的に言えば、ラムダ式で自身について話す必要があることは珍しくありません(それ自体を再帰的に呼び出すか、他のメソッドを呼び出すかのいずれか)。ただし、囲んでいるクラス(this、toString())内のものを参照するために名前を使用する方が一般的です。 ラムダ式がそれ自体を参照する必要がある場合(thisを使用する場合など)、かわりにメソッド参照または無名内部クラスを使用する必要があります。
ブロック内のすべてのreturn文の形式がreturn;の場合、ブロック・ラムダ本体はvoid-compatibleです。
ブロック・ラムダ本体は、正常に完了できず(§14.22)、ブロック内のすべてのreturn文の形式がreturn Expression;の場合、value-compatibleです。
ブロック・ラムダ本文がvoid互換でもvalue互換でもない場合、コンパイル時エラーです。
値互換のブロック・ラムダ本体では、結果式は、呼出しの値を生成する任意の式です。 具体的には、本文に含まれるreturn 式 ;という形式の各文について、式は結果式です。
次のラムダ本体は無効化互換です:
()->{} ()->{ System.out.println("done"); }
値と互換性があります:
()->{ return "done"; } ()->{ if (...) return 1; else return 0; }
これらは両方とも次のとおりです:
()->{ throw new RuntimeException(); } ()->{ while (true); }
これは次のいずれでもありません:
() -> { if (...) return "done"; System.out.println("done"); }
void/value-compatibleの処理と本文内の名前の意味は、指定されたコンテキスト内の特定のターゲット・タイプへの依存性を最小限に抑えるために結合され、実装とプログラマの理解の両方に役立ちます。 ターゲット・タイプに応じてオーバーロードの解決時に式に異なるタイプを割り当てることができますが、ラムダ本文の非修飾名と基本構造の意味は変わりません。
void/value互換の定義は厳密な構造プロパティではないことに注意してください: "正常に完了できます"は定数式の値に依存し、定数変数を参照する名前が含まれる場合があります。
ラムダ式で使用されているが宣言されていないローカル変数、仮パラメータまたは例外パラメータは、§6.5.6.1で指定されているとおり、finalまたは実質的にfinal (§4.12.4)である必要があります。
ラムダ本体で使用されているが宣言されていない局所変数は、ラムダ本体の前に必ず割り当てる必要があります(§16 (Definite Assignment))。そうしないと、コンパイル時にエラーが発生します。
変数の使用に関する同様の規則は、内部クラスの本体(§8.1.3)にも適用される。 final変数を効果的に制限すると、動的に変更されるローカル変数へのアクセスが禁止されます。このローカル変数の取得では、同時実行性の問題が発生する可能性があります。 final制限と比較して、プログラマの事務的負担を軽減します。
final変数を効果的に制限するには、標準ループ変数が含まれますが、拡張forループ変数は含まれません。このループ変数は、ループの反復ごとに個別として扱われます(§14.14.2)。
次のラムダ本体は、効果的なfinal変数の使用方法を示しています。
void m1(int x) {
int y = 1;
foo(() -> x+y);
// Legal: x and y are both effectively final.
}
void m2(int x) {
int y;
y = 1;
foo(() -> x+y);
// Legal: x and y are both effectively final.
}
void m3(int x) {
int y;
if (...) y = 1;
foo(() -> x+y);
// Illegal: y is effectively final, but not definitely assigned.
}
void m4(int x) {
int y;
if (...) y = 1; else y = 2;
foo(() -> x+y);
// Legal: x and y are both effectively final.
}
void m5(int x) {
int y;
if (...) y = 1;
y = 2;
foo(() -> x+y);
// Illegal: y is not effectively final.
}
void m6(int x) {
foo(() -> x+1);
x++;
// Illegal: x is not effectively final.
}
void m7(int x) {
foo(() -> x=1);
// Illegal: x is not effectively final.
}
void m8() {
int y;
foo(() -> y=1);
// Illegal: y is not definitely assigned before the lambda.
}
void m9(String[] arr) {
for (String s : arr) {
foo(() -> s);
// Legal: s is effectively final
// (it is a new variable on each iteration)
}
}
void m10(String[] arr) {
for (int i = 0; i < arr.length; i++) {
foo(() -> arr[i]);
// Illegal: i is not effectively final
// (it is not final, and is incremented)
}
}
Tが関数型(§9.8)で、式がTから派生したグラウンド・ターゲット型の関数型を持つcongruentである場合、ラムダ式は代入コンテキスト、呼出しコンテキストまたはターゲット型Tとのキャスト・コンテキストで互換性があります。
グラウンド・ターゲット・タイプは、次のようにTから導出されます。
次のすべてに該当する場合、ラムダ式は関数型を持つ一致です。
関数型に型パラメータがありません。
ラムダ・パラメータの数は、ファンクション・タイプのパラメータ・タイプの数と同じです。
ラムダ式が明示的に入力されている場合、その仮パラメータ型は関数型のパラメータ型と同じです。
ラムダ・パラメータがファンクション・タイプのパラメータ・タイプと同じ型であると想定される場合、次のようになります。
ラムダ式がターゲット・タイプTと互換性がある場合、式の型Uは、Tから導出された接地ターゲット型です。
Uまたは Uの関数型が、ラムダ式が出現するクラスまたはインタフェースからアクセスできない(§6.6)場合は、コンパイル時にエラーが発生します。
Uのstatic以外のメンバー・メソッドmごとに、Uのファンクション・タイプにmのシグネチャのサブシグネチャがある場合、メソッド・タイプがUのファンクション・タイプであるノーショナル・メソッドはmをオーバーライドするとみなされ、§8.4.8.3で指定されたコンパイル時エラーまたは未チェックの警告が発生する可能性があります。
ラムダ式の本体でスローされる可能性があるチェック例外は、§11.2.3に規定されているコンパイル時エラーを引き起こす可能性があります。
明示的に型指定されたラムダのパラメータ型は、関数型のパラメータ型と完全に一致する必要があります。 たとえば、ボクシングや収縮を可能にする、より柔軟であることは可能ですが、このような寛大さは必要ではなく、クラス宣言でのオーバーライドの動作と矛盾しています。 プログラマは、ラムダ式を記述するときにターゲットとなる関数型を正確に認識する必要があるため、プログラマはどの署名をオーバーライドする必要があるかを正確に認識する必要があります。 (これはメソッド参照の場合ではないため、メソッド参照を使用すると柔軟性が向上します。) さらに、パラメータ・タイプの柔軟性が向上すると、型の推論およびオーバーロード解決の複雑さが増します。
ボクシングは厳密な呼出しコンテキストでは許可されませんが、ラムダ結果式のボクシングは常に許可されます。つまり、ラムダ式を囲むコンテキストに関係なく、結果式は割当てコンテキストに表示されます。 ただし、明示的に型指定されたラムダ式がオーバーロード・メソッドの引数である場合、ラムダ結果のボクシングまたはアンボックス化を回避するメソッド・シグネチャは、最も具体的なチェック(§15.12.2.5)によって優先されます。
ラムダの本体が文式(つまり、文として単独で立つことができる式)である場合、void生成関数型と互換性があり、結果は単に破棄されます。 したがって、たとえば、次の両方が合法です。
// Predicate has abooleanresult java.util.function.Predicate<String> p = s->list.add(s); // Consumer has avoidresult java.util.function.Consumer<String> c = s->list.add(s);
一般的に、() -> exprという形式のラムダ(exprは文式)は、ターゲット・タイプに応じて、() -> { return expr; }または() -> { expr; }と解釈されます。
実行時、ラムダ式の評価はクラス・インスタンス作成式の評価と似ていますが、通常の完了によってオブジェクトへの参照が生成されるかぎりです。 ラムダ式の評価は、ラムダ本体の実行とは異なります。
次のプロパティを持つクラスの新規インスタンスが割り当てられて初期化されるか、または次のプロパティを持つクラスの既存のインスタンスが参照されます。 新しいインスタンスを作成するが、オブジェクトを割り当てるための領域が不足している場合は、OutOfMemoryErrorをスローしてラムダ式の評価が突然完了します。
これは、ラムダ式を評価した結果(または、ラムダ式をシリアライズおよびデシリアライズした結果)のアイデンティティが予測不可能であり、したがってアイデンティティに依存する操作(参照等価性など)であることを意味します(§)15.21.3)、オブジェクト・ロック(§14.19)、およびSystem.identityHashCodeメソッド)は、Javaプログラミング言語の異なる実装、または同じ実装の異なるラムダ式の評価でさえ、異なる結果を生成することがあります。
ラムダ式の値は、次のプロパティを持つクラスのインスタンスへの参照です。
このクラスは、ターゲットとなる機能インタフェース・タイプを実装し、ターゲット・タイプが交差タイプである場合は、交差に記述されている他のすべてのインタフェース・タイプを実装します。
ラムダ式のタイプがUの場合、Uのstatic以外のメンバー・メソッドmごとに、次のようになります。
Uのファンクション・タイプにmのシグネチャのサブシグネチャがある場合、クラスはmをオーバーライドするメソッドを宣言します。 メソッドの本体は、ラムダ本体を評価するか、式であるか、ラムダ本体を実行するか(ブロックの場合)、その結果が期待される場合はメソッドから返されます。
オーバーライドされるメソッドの型の消去が、そのシグネチャでUのファンクション・タイプの消去と異なる場合、ラムダ本体を評価または実行する前に、メソッドbodyは、各引数値が、Uファンクション・タイプ内の対応するパラメータ・タイプの消去のサブクラスまたはサブインタフェースのインスタンスであることを確認します。そうでない場合は、ClassCastExceptionがスローされます。
このクラスは、Objectクラスのメソッドをオーバーライドしてもかまいませんが、前述のターゲット関数インタフェース・タイプまたは他のインタフェース・タイプのメソッドをオーバーライドしません。
これらのルールは、次の点でJavaプログラミング言語の実装に柔軟性を提供することを目的としています。
新しいオブジェクトを評価ごとに割り当てる必要はありません。
異なるラムダ式によって生成されるオブジェクトは、異なるクラスに属している必要はありません(たとえば、本体が同一である場合)。
評価によって生成されるすべてのオブジェクトは、同じクラスに属している必要はありません(たとえば、取得されたローカル変数はインライン化されます)。
「既存のインスタンス」が使用可能な場合は、前のラムダ評価で作成する必要はありません(たとえば、包含クラスの初期化中に割り当てられている可能性があります)。
ターゲット関数インタフェース・タイプがjava.io.Serializableのサブタイプである場合、結果のオブジェクトは自動的にシリアライズ可能クラスのインスタンスになります。 ラムダ式から導出されたオブジェクトをシリアライズ可能にすると、追加のランタイム・オーバーヘッドおよびセキュリティへの影響が発生する可能性があるため、ラムダ導出オブジェクトはデフォルトでシリアライズ可能である必要はありません。
switch式
switch式は、式の値に応じて、制御を複数の文または式のいずれかに転送します。その式の可能な値はすべて処理する必要があり、いくつかの文および式はすべて、switch式の結果の値を生成する必要があります。
switch ( 式 ) SwitchBlock
式は、セレクタ式と呼ばれます。 セレクタ式の型は、char、byte、short、intまたは参照型である必要があります。そうしないと、コンパイル時にエラーが発生します。
switch式とswitch文(§14.11)の両方の本体は、スイッチ・ブロックと呼ばれます。 すべてのスイッチ・ブロックに適用される一般的な規則は、switch式またはswitch文のいずれでも、§14.11.1に示されています。 ここでは、便宜上、§14.11.1からの次のプロダクションを示します。
{ SwitchRule {SwitchRule} } { {SwitchBlockStatementGroup} {SwitchLabel :} }
-> 式 ; -> ブロック -> ThrowStatement
case CaseConstant {, CaseConstant} case null [, default] case CasePattern {, CasePattern} [Guard] default
when 式
switch式のSwitchブロック
switchブロックの一般的なルール(§14.11.1)に加えて、switch式にはswitchブロックに関するその他のルールがあります。
switch式のswitchブロックがswitchルールで構成されていても、1つ以上のswitchルール・ブロックが正常に完了できる場合は、コンパイル時にエラーが発生します(§14.22)。
switch式のswitchブロックがswitchラベル付き文グループで構成されていても、switchブロックの最後の文が正常に完了できる場合、またはswitchブロックに最後のswitchラベル付き文グループの後に1つ以上のswitchラベルがある場合、コンパイル時にエラーが発生します。
switch式が完全でない場合(§14.11.1.1)、コンパイル時にエラーが発生します。
switch式は、switchブロックの矢印(->)の右側に表示される式、つまりswitchルール式として使用できる式に関して、switch文とは異なります。 switch式では、任意の式をswitchルール式として使用できますが、switch文では、文式のみを使用できます(§14.11.1)。
switch式の結果式は、次のように決定されます。
switchブロックがswitchルールで構成される場合、各switchルールが順番に考慮されます。
switchルールの形式が... -> 式 ;の場合、式はswitch式の結果式です。
switchルールの形式が... -> Blockの場合、Blockのyield文にすぐに含まれるすべての式(yieldターゲットが指定されたswitch式)は、switch式の結果式です。
switchブロックがswitchラベル付き文グループで構成されている場合、switchブロック内のyield文にすぐに含まれるすべての式(yieldターゲットが指定されたswitch式)は、switch式の結果式です。
switch式に結果式がない場合、コンパイル時にエラーが発生します。
switch式は、代入コンテキストまたは呼出しコンテキスト(§5.2、§5.3)に表示される場合、多式です。 それ以外の場合はスタンドアロン式です。
ターゲット型がTの特定の種類のコンテキストにポリswitch式が出現する場合、その結果式は、ターゲット型がTの同じ種類のコンテキストにも同様に表示されます。
ポリswitch式は、各結果式がTと互換性がある場合、ターゲット型Tと互換性があります。
ポリswitch式の型は、そのターゲット型と同じです。
スタンドアロンのswitch式のタイプは、次のように決定されます。
結果式がすべて同じ型(null型(§4.1)の場合)の場合、これはswitch式の型です。
それ以外の場合、各結果式の型がbooleanまたはBooleanの場合、ボックス化解除変換(§5.1.8)がBoolean型の各結果式に適用され、switch式のタイプがbooleanになります。
それ以外の場合、各結果式の型が数値型(§5.1.8)に変換可能な場合、switch式の型は、結果式に適用される一般的な数値プロモーション(§5.6)の結果です。
それ以外の場合、ボクシング変換(§5.1.7)はプリミティブ型を持つ各結果式に適用され、その後、switch式の型は、取得変換(§5.1.10)を結果式の型の最下位の上限(§4.10.4)に適用した結果になります。
switch式の実行時評価
switch式は、最初にセレクタ式を評価することで評価されます。 セレクタ式の評価が突然完了すると、switch式全体の評価も同じ理由で突然完了します。
セレクタ式の評価が正常に完了した場合、switchブロックに関連付けられたswitchラベルがセレクタ式の値に適用されるかどうかを判断することで、switch式の評価が続行されます(§14.11.1.2)。 次に、
適用するスイッチ・ラベルを決定するプロセスが突然完了すると、switch式全体が同じ理由で突然完了します。
スイッチラベルが適用されない場合は、次のいずれかが保持されます。
セレクタ式の値がnullの場合、NullPointerExceptionがスローされ、その理由によりswitch式の評価が突然完了します。
それ以外の場合は、MatchExceptionがスローされ、その理由でswitch式の評価が突然完了します。
スイッチラベルが適用される場合、次のいずれかが保持されます。
switchルール式のswitchラベルである場合、式が評価されます。 評価の結果が値の場合、switch式は通常同じ値で完了します。
switchルール・ブロックのswitchラベルである場合、ブロックが実行されます。 このブロックが正常に完了すると、switch式は正常に完了します。
スイッチ・ルールthrow文のスイッチ・ラベルである場合、throw文が実行されます。
それ以外の場合は、適用されるswitchラベルの後のswitchブロック内のすべての文が順番に実行されます。 これらの文が正常に完了すると、switch式は正常に完了します。
switchブロックの文または式の実行が突然完了する場合、次のように処理されます。
式の評価が突然完了した場合、switch式の評価も同じ理由で突然完了します。
値Vのyieldが原因で文の実行が突然完了した場合、switch式の評価は正常に完了し、switch式の値はVになります。
値を持つyield以外の理由で文の実行が突然完了した場合、switch式の評価も同じ理由で突然完了します。
定数式は、プリミティブ型の値を示す式、または突然完了せず、次のもののみを使用して構成されるStringです。
プリミティブ型のリテラル(§3.10.1、§3.10.2、§3.10.3、§3.10.4)、文字列リテラル(§3.10.5)、およびテキスト・ブロック(§3.10.6)
プリミティブ型へのキャストおよびString型へのキャスト(§15.16)
単項演算子+、-、~および! (ただし++または--は除く) (§15.15.3、§15.15.4、§15.15.5、§15.15.6)
乗算演算子*、/および% (§15.17)
加算演算子+および- (§15.18)
シフト演算子<<、>>および>>> (§15.19)
関係演算子<、<=、>および>= (ただしinstanceofではない)(§15.20)
等価演算子==および!= (§15.21)
ビット単位および論理演算子&、^および| (§15.22)
3項条件演算子? : (§15.25)
含まれる式が定数式であるカッコ付きの式(§15.8.5)。
String型の定数式は、String.internメソッドを使用して一意のインスタンスを共有するために常に「内部」です。
定数式は、switch文およびswitch式(§14.11、§15.28)でcaseラベルとして使用され、代入コンテキスト(§5.2)およびクラスまたはインタフェースの初期化(§12.4.2)において特別な意味を持ちます。 また、while文、do文またはfor文が正常に完了する機能(§14.22)、および数値オペランドを使用した条件演算子? :のタイプも制御できます。
例15.29-1. 定数式
true (short)(1*2*3*4*5*6) Integer.MAX_VALUE / 2 2.0 * Math.PI "The integer " + Long.MAX_VALUE + " is mighty big."