このドキュメントでは、次の2つの新しい位置でのstatic
宣言をサポートするためにクラスおよびインタフェースに関する一貫した用語で修正された、Java言語仕様の変更について説明します。
ネストされた静的宣言は、包含インスタンス、ローカル変数または型パラメータにアクセスできません。これは、新しいコンテキストからの参照を処理するために、変数(6.5.6.1)、型(6.5.5.1)、メソッド(15.12.3)およびthis
(15.8.3、15.9.2、15.11.2など)の参照のルールで対応されています。
これらの拡張はレコード機能の一部です。詳細は、JEPドラフトを参照してください。
変更は、Java言語仕様の既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。
第1章: 概要
1.1 仕様の編成
...
第6章では、宣言および名前について説明するとともに、名前が意味するもの(つまり、名前がどの宣言を表しているか)を確認する方法について説明します。Javaプログラミング言語では、クラス、インタフェースまたはメンバーを使用する前にこれらを宣言する必要はありません。宣言順序が重要なのは、ローカル変数、ローカル・クラス、およびインタフェースの場合、およびクラスまたはインタフェース内のフィールド・イニシャライザの順序の場合のみです。ここでは、読みやすいプログラムの作成に役立つ、推奨される命名規則について説明します。
...
第6章: 名前
6.1 宣言
宣言により、エンティティがプログラムに導入され、このエンティティを参照するために名前内で使用できる識別子(3.8)が組み込まれます。この識別子には、導入されるエンティティがクラス、インタフェースまたは型パラメータである場合は型識別子であるという制約があります。
宣言されたエンティティは、次のいずれかです。
module
宣言で宣言されたモジュール(7.7)package
宣言で宣言されたパッケージ(7.4)単一型インポート宣言またはオンデマンド型インポート宣言(7.5.1、7.5.2)で宣言された、インポートされたクラスまたはインタフェース
単一静的インポート宣言またはオンデマンド静的インポート宣言で宣言された、インポートされた
static
メンバー(7.5.3、7.5.4)汎用クラス、インタフェース、メソッドまたはコンストラクタの宣言の一部として宣言された型パラメータ(8.1.2、9.1.2、8.4.4、8.8.4)
enum定数(8.9.1)
クラスまたはインタフェースのメソッド(8.4.1)、クラスのコンストラクタ(8.8.1)またはラムダ式(15.27.1)の仮パラメータ
try
文のcatch
句で宣言された例外ハンドラの例外パラメータ(14.20)ローカル変数で、次のいずれか:
次のいずれかによって宣言された、ローカル・クラスまたはインタフェース(14.3):
標準クラス宣言
enum宣言
インタフェース宣言
コンストラクタ(8.8)も宣言によって導入されますが、新しい名前が導入されるのではなく、そのコンストラクタが宣言されたクラスの名前が使用されます。
...
6.3 宣言のスコープ
宣言のスコープは、単純名を使用して、その宣言によって宣言されたエンティティを参照できるプログラム内の領域です(ただし、シャドウ化されていることが条件です) (6.4.1)。
宣言がプログラム内の特定のポイントでスコープ内にあるとされるのは、宣言のスコープにそのポイントが含まれる場合のみです。
...
ブロック(14.2)で直接囲まれたローカル・クラスまたはインタフェース宣言のスコープは、直前と直後のブロックの残りの部分であり、それ自体のクラス宣言クラスまたはインタフェース宣言自体を含みます。
switchブロック文グループ(14.11)で直接囲まれたローカル・クラスまたはインタフェース宣言のスコープは、直前と直後のswitchブロック文グループの残りの部分であり、それ自体のクラス宣言クラスまたはインタフェース宣言自体を含みます。
ブロックのローカル変数宣言(14.4)のスコープは、宣言が出現するブロックの残りの部分であり、それ自体のイニシャライザから始まり、ローカル変数宣言文の右側にある別の宣言子を含みます。
基本的なfor
文(14.14.1)のForInit部分で宣言されたローカル変数のスコープには、次がすべて含まれます。
それ自体のイニシャライザ
for
文のForInit部分の右側にある別の宣言子for
文のExpressionおよびForUpdate部分含まれている文
拡張されたfor
文(14.14.2)のFormalParameter部分で宣言されたローカル変数のスコープが、含まれている文です。
try
文(14.20)のcatch
句で宣言された例外ハンドラのパラメータのスコープは、catch
に関連付けられたブロック全体です。
try
-with-resources文(14.20.3)のResourceSpecificationで宣言された変数のスコープは、ResourceSpecificationの残りの部分の右側にある宣言、およびtry
-with-resources文に関連付けられたtry
ブロック全体です。
try
-with-resources文の翻訳は、前述のルールを示します。
...
6.4 シャドウ化および不明瞭化
ローカル変数(14.4)、仮パラメータ(8.4.1, 15.27.1)、例外パラメータ(14.20)、およびまたはローカル・クラスまたはインタフェース(14.3)は、修飾名ではなく単純名(6.2)を使用してのみ参照できます。
一部の宣言は、単純名のみを使用して宣言されたエンティティを区別できないため、ローカル変数、仮パラメータ、例外パラメータまたはローカル・クラスまたはインタフェース宣言のスコープ内では許可されません。
たとえば、メソッドの仮パラメータの名前をメソッド本体内のローカル変数の名前として再宣言できる場合、ローカル変数によって仮パラメータがシャドウ化され、仮パラメータを参照する手段がなくなる、という望ましくない結果となります。
仮パラメータの名前を使用してメソッド、コンストラクタまたはラムダ式の本体内で新しい変数が宣言される場合、コンパイル時にエラーが発生します。ただし、この新しい変数が、メソッド、コンストラクタまたはラムダ式に含まれるクラスまたはインタフェース宣言で宣言される場合は除きます。
ローカル変数vの名前を使用してvのスコープ内で新しい変数が宣言される場合、コンパイル時にエラーが発生します。ただし、この新しい変数が、宣言がvのスコープ内にあるクラスまたはインタフェース内で宣言される場合は除きます。
例外パラメータの名前を使用してcatch
句のBlock内で新しい変数が宣言される場合、コンパイル時にエラーが発生します。ただし、この新しい変数が、catch
句のBlockに含まれるクラスまたはインタフェース宣言内で宣言される場合は除きます。
ローカル・クラスまたはインタフェースCの名前を使用してCのスコープ内で新しいローカル・クラスまたはインタフェースが宣言される場合、コンパイル時にエラーが発生します。ただし、この新しいローカル・クラスまたはインタフェースが、Cのスコープ内にある宣言がある別のクラスクラスまたはインタフェース宣言で宣言される場合は除きます。
これらのルールにより、変数
、またはローカル・クラス、またはローカル・インタフェースのスコープ内で行われるネストしたクラスまたはインタフェース宣言内でこの変数、またはローカル・クラスまたはローカル・インタフェースを再宣言できるようになります。このようなネストしたクラスまたはインタフェース宣言は、ローカル・クラスクラスまたはインタフェース(14.3)または匿名クラスクラス宣言(15.915.9.5)である場合があります。このため、仮パラメータ、ローカル変数、またはローカル・クラスまたはローカル・インタフェースの宣言は、メソッド、コンストラクタまたはラムダ式内でネストしたクラスまたはインタフェース宣言内でシャドウ化される場合があります。また、例外パラメータの宣言は、catch
句のブロック内でネストしたクラスまたはインタフェース宣言内でシャドウ化される場合があります。
ラムダ・パラメータとラムダ式内で宣言された他の変数によって生じる名前の競合を処理するために、2つの設計上の選択肢が用意されています。1つは、クラス宣言を模倣する方法です。ローカル・クラスの場合と同様、ラムダ式は名前に新しい「レベル」を導入し、式の外部にあるすべての変数名を再宣言できます。もう1つは、「ローカル」戦略です。
catch
句、for
ループ、ブロックの場合と同様、ラムダ式は、それを囲むコンテキストと同じ「レベル」で動作します。また、式の外部のローカル変数をシャドウ化することはできません。前述のルールでは、ローカル戦略を使用しています。ラムダ式で宣言された変数が、それを囲むメソッドで宣言された変数をシャドウ化することを可能にする特別な方法はありません。
ローカル・クラスのルールでは、ローカル・クラス自体で宣言された同じ名前のクラスに例外が認められることはありません。ただし、このケースは別のルールでは禁止されています。つまり、クラスがそれを囲むクラスと同じ名前を持つことはできません(8.1)。
ルールを言い換え、「別の」世界の広範な説明を不要にしました。(つまり、ここで述べる内容が何であれ、この名前の選択がエラーになると8.1で判断しています)
例6.4-1.ローカル変数のシャドウ化試行
メソッド、コンストラクタまたはイニシャライザ・ブロックのローカル変数としての識別子の宣言が同じ名前のパラメータまたはローカル変数のスコープ内には出現できないため、次のプログラムの場合、コンパイル時にエラーが発生します。
class Test1 {
public static void main(String[] args) {
int i;
for (int i = 0; i < 10; i++)
System.out.println(i);
}
}
この制約は、これがなければ非常にわかりにくいバグを検出する上で役に立ちます。スーパークラスにメンバーを追加すると、サブクラスがローカル変数の名前を変更する必要が生じる可能性があるため、ローカル変数によるメンバーのシャドウ化に関する同様の制約は実用的でないと判断されています。これに関連する考慮事項により、ネストしたクラスのメンバーによるローカル変数のシャドウ化、またはネストしたクラス内で宣言されたローカル変数によるローカル変数のシャドウ化もそれほど有用ではなくなります。
したがって、次のプログラムはエラーなしでコンパイルされます。
class Test2 {
public static void main(String[] args) {
int i;
class Local {
{
for (int i = 0; i < 10; i++)
System.out.println(i);
}
}
new Local();
}
}
一方、同じ名前を持つローカル変数は、2つの別個のブロックまたはfor
文(どちらも他方を含まない)内で宣言できます。
class Test3 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++)
System.out.print(i + " ");
for (int i = 10; i > 0; i--)
System.out.print(i + " ");
System.out.println();
}
}
このプログラムはエラーなしでコンパイルされ、実行されると、次の出力を生成します。
0 1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1
6.4.2 不明瞭化
...
フィールド名に不明瞭化が関連することはまれですが、
...
6.5 名前の意味の確認
6.5.2 コンテキスト的にあいまいな名前の再分類
この場合、AmbiguousNameは次のように再分類されます。
AmbiguousNameが単純名であるときに単一の識別子を構成する場合:
識別子が
ローカル変数宣言(14.4)またはパラメータ宣言(8.4.1、8.8.1、14.20)またはフィールド宣言(8.3)その名前の変数のスコープ(6.3)内に出現する場合、AmbiguousNameはExpressionNameとして再分類されます。そうでない場合、その名前のフィールドが、単一静的インポート宣言(7.5.3)またはオンデマンド静的インポート宣言(7.5.4)により、識別子を含むコンパイル・ユニット(7.3)内で宣言された場合、AmbiguousNameはExpressionNameとして再分類されます。インポートされたフィールドはスコープ内になるよう指定されます。追加ルールは必要ありません。
そうでない場合、識別子が有効なTypeIdentifierであり、その名前を持つ
最上位クラス(8)、またはインタフェース型宣言(9)、ローカル・クラス宣言(14.3)またはメンバー型宣言(8.5、9.5)クラス、インタフェース、または型パラメータのスコープ(6.3)内に出現する場合、AmbiguousNameはTypeNameとして再分類されます。バグ修正: このルールは、型パラメータのために更新されることはありません。
そうでない場合、識別子が有効なTypeIdentifierであり、その名前の型が、単一型インポート宣言(7.5.1)、オンデマンド型インポート宣言(7.5.2)、単一静的インポート宣言(7.5.3)またはオンデマンド静的インポート宣言(7.5.4)により、識別子を含むコンパイル・ユニット(7.3)内で宣言された場合、AmbiguousNameはTypeNameとして再分類されます。インポートされた型はスコープ内になるよう指定されます。追加ルールは必要ありません。
そうでない場合、AmbiguousNameはPackageNameとして再分類されます。後のステップでは、その名前を持つパッケージが実際に存在するかどうかを確認します。
AmbiguousNameが指定された名前の場合、名前、「.
」、識別子で構成されており、「.
」の左側の名前自体はAmbiguousNameであるため、これが最初に再分類されます。これで、次を選択できます:
「
.
」の左側の名前がPackageNameとして再分類される場合:識別子が有効なTypeIdentifierであり、その名前が「
.
」の左側の名前であるパッケージがあり、そのパッケージに識別子と同じ名前の型宣言が含まれている場合、AmbiguousNameはTypeNameとして再分類されます。そうでない場合、このAmbiguousNameはPackageNameとして再分類されます。後のステップでは、その名前を持つパッケージが実際に存在するかどうかを確認します。
「
.
」の左側の名前がTypeNameとして再分類される場合:識別子がTypeNameの示す型のメソッドまたはフィールドの名前の場合、このAmbiguousNameはExpressionNameとして再分類されます。
そうでない場合、識別子が有効なTypeIdentifierであり、TypeNameが示す型のメンバー型の名前である場合、このAmbiguousNameはTypeNameとして再分類されます。
そうでない場合、コンパイル時にエラーが発生します。
「
.
」の左側の名前がExpressionNameとして再分類される場合、このAmbiguousNameはExpressionNameとして再分類されます。後のステップでは、その名前の識別子のメンバーが実際に存在するかどうかを確認します。
潜在的な型名は「有効なTypeIdentifier」であるという要件のために、
var
およびyield
は型名として扱えません。すでに宣言のルールによってvar
およびyield
という名前の型を導入できないため、これは通常は重複になります。ただし、場合によってはコンパイラがvar
またはyield
という名前のバイナリ・クラスを検出するため、このようなクラスの名前は付けられないことを明確にしておきます。最も簡単な解決策は、有効なTypeIdentifierの整合性をチェックすることです。
例 6.5.2-1. コンテキスト的にあいまいな名前の再分類
以下の人為的な「ライブラリ・コード」を考えてみます。
package org.rpgpoet;
import java.util.Random;
public interface Music { Random[] wizards = new Random[4]; }
次に別のパッケージでこのコードの例を考えてみます。
package bazola;
class Gabriel {
static int n = org.rpgpoet.Music.wizards.length;
}
最初に、org.rpgpoet.Music.wizards.length
という名前はPostfixExpressionとして機能するため、ExpressionNameに分類されます。そのため、それぞれの名前:
org.rpgpoet.Music.wizards
org.rpgpoet.Music
org.rpgpoet
org
は最初にAmbiguousNameとして分類されます。その後、次のように再分類されます:
単純名
org
は(スコープ内にorgという名前の変数または型がないため)PackageNameとして再分類されます。次に、パッケージ
org
のコンパイル・ユニットにrpgpoet
という名前のクラスまたはインタフェースがないと仮定すると(パッケージorg
にはrpgpoet
というサブパッケージがあるためそのようなクラスまたはインタフェースがないことは判明しています)、修飾名org.rpgpoet
はPackageNameとして再分類されます。次に、パッケージ
org.rpgpoet
にはMusic
という名前のアクセス可能な(6.6)インタフェース型があるため、修飾名org.rpgpoet.Music
はTypeNameとして再分類されます。最終的に、名前
org.rpgpoet.Music
がTypeNameであるため、修飾名org.rpgpoet.Music.wizards
はExpressionNameとして再分類されます。
6.5.5 型名の意味
6.5.5.1 単純な型名
型名が単一の識別子で構成される場合、識別子はこの名前の(6.3)クラス、インタフェース、または型パラメータの1つだけの宣言のスコープにのみ出現する必要があり、そうでない場合はコンパイル時にエラーが発生します。
宣言がクラスまたはインタフェースCの型パラメータ(8.1.2、9.1.2)を示す場合、次の両方が当てはまる必要があります。
型名が静的コンテキストに出現しません(8.1.3)。
たとえば、型名はCによって宣言された静的メソッドの本体に出現できません。
型名がCのネストされたクラスまたはインタフェース宣言に出現した場合、型名の直接包含するクラスまたはインタフェース宣言によってCの内部クラス(8.1.3)が指定されます。
たとえば、型名はC内にネストされた静的クラスのインスタンス・メソッドの本体に出現できません。
そうでない場合、コンパイル時にエラーが発生します。
宣言がクラスまたはインタフェースCによって宣言されたメソッドまたはコンストラクタmの型パラメータ(8.4.4、8.8.4)を示す場合、次の両方が当てはまる必要があります。
mによって包含されている場合を除き、型名が静的コンテキストに出現しません。
たとえば、型名はmの本体で宣言されたローカル・クラスの静的メソッドの本体に出現できません。
型名がCのネストされたクラスまたはインタフェース宣言に出現した場合、型名の直接包含するクラスまたはインタフェース宣言によって、mの本体で宣言された内部(8.1.3)ローカルまたは匿名クラスか、mの本体で宣言された内部ローカルまたは匿名クラスの内部クラスが指定されます。
たとえば、型名はmの本体で宣言されたローカル・インタフェースのデフォルト・メソッドの本体に出現できません。
そうでない場合、コンパイル時にエラーが発生します。
型名の意味はそのスコープ内クラス、インタフェース、または型パラメータです。
例6.5.5.1-1.型パラメータへの参照
class Box<T> {
T val;
T get() {
return val;
}
static Box<T> empty() { // compile-time error
return new Box<>(null);
}
static <U> Box<U> make(U val) {
interface Checker {
void check(U val); // compile-time error
}
class NullChecker implements Checker {
void check(U val) {
if (val == null) {
throw new IllegalArgumentException();
}
}
}
new NullChecker().check(val);
return new Box<U>(val);
}
}
クラス型パラメータT
はクラスBox
の宣言全体にわたってスコープ内ですが、静的メソッド宣言empty
での名前T
の使用は不正になります。
同様に、メソッド型パラメータU
はメソッドmake
の宣言全体にわたってスコープ内ですが、(暗黙的に静的な)ローカル・インタフェース宣言Checker
での名前U
の使用は不正になります。
6.5.6 式名の意味
6.5.6.1 単純な式名
式名が単一の識別子で構成される場合、その識別子が出現するポイントでスコープ内のローカル変数、仮パラメータ例外パラメータ、またはフィールドのいずれかを示す1つの宣言が存在する必要があります。そうでない場合、コンパイル時にエラーが発生します。
宣言がクラスCのインスタンス変数(8.3.1.1)を示している場合、式名はインスタンス・メソッド(8.4.3.2)、インスタンス変数イニシャライザ(8.3.2)、インスタンス・イニシャライザ(8.6)、またはコンストラクタ(8.8)内に出現する必要があります。式名が次の両方が当てはまる必要があります。static
型宣言、クラス・メソッド、クラス変数イニシャライザまたは静的イニシャライザ(8.7)内に出現する場合、
そうでない場合、コンパイル時にエラーが発生します。
宣言がローカル変数を示している場合、仮パラメータまたは例外パラメータによってXが変数宣言を囲む最も内側のメソッド宣言、コンストラクタ宣言、インスタンス・イニシャライザ、静的イニシャライザ、フィールド宣言、または明示的なコンストラクタ呼出し文になり、Cが直前と直後のXの型宣言になります。次の両方が当てはまる必要があります。
Xによって包含されている場合を除き、式名が静的コンテキストに出現しません。
たとえば、式名はmの本体で宣言されたローカル・クラスの静的メソッドの本体に出現できません。
式名がCのネストされたクラスまたはインタフェース宣言に出現した場合、式名の直接包含するクラスまたはインタフェース宣言によって、Xで宣言された内部(8.1.3)ローカルまたは匿名クラスか、Xで宣言された内部ローカルまたは匿名クラスの内部クラスが指定されます。
たとえば、式名はmの本体で宣言されたローカル・インタフェースのデフォルト・メソッドの本体に出現できません。
そうでない場合、コンパイル時にエラーが発生します。
また、宣言がfinal
でもなく、実質的にfinalでもない(4.12.4),ローカル変数、仮パラメータ、または例外パラメータを示している場合、式名がXに含まれる内部クラスまたはラムダ式(15.27)に出現するとコンパイル時にエラーが発生します。
宣言により、単純な式の前に明確に割り当てられるfinal
変数が宣言される場合、この名前はその変数の値を意味します。そうでない場合、式名は、その宣言によって宣言された変数を意味します。
式名が割当てコンテキスト、呼出しコンテキスト、またはキャスト・コンテキストに出現する場合、式名の型はキャプチャ変換後のフィールド、ローカル変数、またはパラメータになります(5.1.10)。
そうでない場合、式名の型はフィールド、ローカル変数、またはパラメータの宣言された型になります。
つまり、式名が「右側」に出現する場合、その型はキャプチャ変換の対象になります。式名が「左側」に出現する変数である場合、その型はキャプチャ変換の対象にはなりません。
例6.5.6.1-1.単純な式名
class Test {
static int v;
static final int f = 3;
public static void main(String[] args) {
int i;
i = 1;
v = 2;
f = 33; // compile-time error
System.out.println(i + " " + v + " " + f);
}
}
このプログラムでは、i
、v
およびf
への割当てで左側として使用されている名前は、ローカル変数i
、フィールドv
およびf
の値(f
は変数final
であるため、変数f
ではありません)を示しています。したがって、この例では、最後の割当ての左側に変数がないため、コンパイル時にエラーが発生します。間違った割当てを削除すると、修正したコードをコンパイルできるようになり、次の出力が生成されます。
1 2 3
例6.5.6.1-2.インスタンス変数への参照
class InstanceVariableTest {
static String a;
String b;
String concat() {
return a + b;
}
static String staticConcat() {
return a + b; // compile-time error
}
int index() {
interface I {
class Matcher {
void check() {
if (a == null ||
b == null) { // compile-time error
throw new IllegalArgumentException();
}
}
int match(String s, String t) {
return s.indexOf(t);
}
}
}
I.Matcher matcher = new I.Matcher();
matcher.check();
matcher.match(a, b);
}
}
フィールドa
およびb
はクラスInstanceVariableTest
の本体全体でスコープ内にあります。ただし、staticConcat
メソッドの静的コンテキスト内、またはInstanceVariableTest
の内部クラスではないネストされたクラス宣言Matcher
内でのb
という名前の使用は不正になります。
例6.5.6.1-3.ローカル変数および仮パラメータへの参照
class LocalVariableTest {
public static void main(String[] args) {
String first = args[0];
class Checker {
void checkWhitespace(int x) {
String arg = args[x];
if (!arg.trim().equals(arg)) {
throw new IllegalArgumentException();
}
}
static void checkFlag(int x) {
String arg = args[x]; // compile-time error
if (!arg.startsWith("-")) {
throw new IllegalArgumentException();
}
}
static void checkFirst() {
Runnable r = new Runnable() {
public void run() {
if (first == null) { // compile-time error
throw new IllegalArgumentException();
}
}
};
r.run();
}
}
final Checker c = new Checker();
c.checkFirst();
for (int i = 1; i < args.length; i++) {
Runnable r = () -> {
c.checkWhitespace(i); // compile-time error
c.checkFlag(i); // compile-time error
}
}
}
}
仮パラメータargs
は、メソッドmain
の本体全体でスコープ内にあります。args
は実質的にfinalであるため、名前args
をローカル・クラスChecker
のインスタンス・メソッドcheckWhitespace
で使用できます。ただし、名前args
をローカル・クラスChecker
のcheckFlag
メソッドの静的コンテキストで使用することは不正です。
ローカル変数first
は、メソッドmain
の本体の残りの部分のスコープ内にあります。first
も実質的にfinalです。ただし、checkFirst
内の匿名クラスはChecker
の内部クラスではないため、名前first
を匿名クラス本体で使用することは不正です。
ローカル変数c
は、メソッドmain
の本体の最後の数行のスコープ内にあり、final
と宣言されているため、名前c
をラムダ式の本体で使用できます。
ローカル変数i
はfor
ループ全体でスコープ内にあります。ただし、i
は実質的にfinalではないため、ラムダ式の本体でi
という名前を使用することは不正になります。
6.5.7 メソッド名の意味
6.5.7.1 単純なメソッド名
単純なメソッド名はメソッド呼出し式(15.12)のコンテキストに出現します。単純なメソッド名は、呼び出されるメソッドの名前を示す単一の識別子で構成されます。メソッド呼出しのルール(15.12)では、識別子またはがメソッド呼出しの時点でメソッドがスコープ内であることを示しているまたは単一静的インポート宣言かオンデマンド静的インポート宣言(7.5.3、7.5.4)によってインポートされたメソッドであることを示している必要があります。このルール(15.12.3)では、静的コンテキスト(8.1.3)で発生した、またはmを宣言しているクラスまたはインタフェースの内部クラス以外のネストされたクラスまたはインタフェースからのインスタンス・メソッドへの参照が禁じられています。
インスタンス・メソッドへの参照に関する制限の詳細は15.12.3で触れていますが、6.5.5.1および6.5.6.1との一貫性を図るためにここでも述べておきます。
インポートに関する句は必要ありません。スコープ・ルール(6.3)ではすでにこのようにメソッドがスコープ内に構築されています。
例6.5.7.1-1.単純なメソッド名
次のプログラムは、呼び出すメソッドを決定する場合にスコープが果たす役割を示しています。
class Super {
void f2(String s) {}
void f3(String s) {}
void f3(int i1, int i2) {}
}
class Test {
void f1(int i) {}
void f2(int i) {}
void f3(int i) {}
void m() {
new Super() {
{
f1(0); // OK, resolves to Test.f1(int)
f2(0); // compile-time error
f3(0); // compile-time error
}
};
}
}
f1(0)
の呼出しでは、f1
という名前のメソッドのみがスコープ内になります。これはメソッドTest.f1(int)
であり、その宣言は匿名クラス宣言を含め、Test
本体全体でスコープ内になります。15.12.1では匿名クラス宣言にf1
という名前のメンバーがないため、クラスTest
での検索が選択されます。最終的にTest.f1(int)
が解決されます。
f2(0)
の呼出しでは、f2
という名前の2つのメソッドがスコープ内になります。まず、メソッドSuper.f2(String)
の宣言が匿名クラス宣言全体でスコープ内になります。次に、メソッドTest.f2(int)
の宣言が匿名クラス宣言を含め、Test
本体全体でスコープ内になります。(それぞれが宣言された時点では、もう一方はスコープ内にはないため、どちらの宣言ももう一方をシャドウ化することはありません。) 15.12.1ではf2
という名前のメンバーがあるためクラスSuper
での検索が選択されます。ただし、Super.f2(String)
はf2(0)
には適用できないため、コンパイル時にエラーが発生します。クラスTest
は検索されないことに注意してください。
f3(0)
の呼出しでは、f3
という名前の3つのメソッドがスコープ内になります。最初と2番目は、メソッドSuper.f3(String)
およびSuper.f3(int,int)
の宣言が匿名クラス宣言全体でスコープ内になります。3番目に、メソッドTest.f3(int)
の宣言が匿名クラス宣言を含め、Test
本体全体でスコープ内になります。15.12.1では、f3
という名前のメンバーがあるためクラスSuper
での検索が選択されます。ただし、Super.f3(String)
およびSuper.f3(int,int)
はf3(0)
には適用できないため、コンパイル時にエラーが発生します。クラスTest
は検索されないことに注意してください。
語彙的な包含スコープの前でネストされたクラスのスーパークラス階層の検索を選択することは、「コーム・ルール」と呼ばれます(15.12.1)。
6.7 完全修飾名および正規名
プリミティブ型、名前付きパッケージ、最上位クラス、最上位インタフェースにはすべて次のような完全修飾名があります。
プリミティブ型の完全修飾名は、そのプリミティブ型のキーワード、つまり
byte
、short
、char
、int
、long
、float
、double
、またはboolean
です。ある名前付きパッケージのサブパッケージではない名前付きパッケージの完全修飾名は、その単純名です。
別の名前付きパッケージのサブパッケージである名前付きパッケージの完全修飾名は、それを含むパッケージの完全修飾名と、その後に続く「
.
」とサブパッケージの単純(メンバー)名で構成されます。名前のないパッケージで宣言されている最上位クラスまたは最上位インタフェースの完全修飾名は、クラスまたはインタフェースの単純名です。
名前付きパッケージで宣言されている最上位クラスまたは最上位インタフェースの完全修飾名は、そのパッケージの完全修飾名と、その後に続く「
.
」とそのクラスまたはインタフェースの単純名で構成されます。
各メンバー・クラス、メンバー・インタフェース、および配列型には次のように完全修飾名を付けることができます。
別のクラスまたはインタフェースCのメンバー・クラスまたはメンバー・インタフェースMには、Cに完全修飾名がある場合のみ完全修飾名が付けられます。
その場合、Mの完全修飾名は、Cの完全修飾名と、その後に続く「
.
」とMの単純名で構成されます。配列型には、その要素型に完全修飾名がある場合のみ、完全修飾名が付けられます。
その場合、配列型の完全修飾名は、その配列型の要素型の完全修飾名と、その後に続く「
[]
」で構成されます。
ローカル・クラス、ローカル・インタフェース、または匿名クラスに完全修飾名は付けられません。
すべてのプリミティブ型、名前付きパッケージ、最上位クラス、および最上位インタフェースに正規名があります。
- すべてのプリミティブ型、名前付きパッケージ、最上位クラス、および最上位インタフェースの正規名は完全修飾名と同じです。
メンバー・クラス、メンバー・インタフェース、および配列型にはそれぞれ正規名を持たせることができます。
別のクラスまたはインタフェースCで宣言されたメンバー・クラスまたはメンバー・インタフェースMには、Cに正規名がある場合のみ正規名が付けられます。
その場合、Mの正規名はCと、その後に続く「
.
」およびMの単純名で構成されます。配列型には、コンポーネント型に正規名がある場合のみ正規名が付けられます。
その場合、配列型の正規名は、その配列型のコンポーネント型の正規名と、その後に続く「
[]
」で構成されます。
ローカル・クラス、ローカル・インタフェース、または匿名クラスに正規名はありません。
例6.7-1.完全修飾名
型
long
の完全修飾名は「long
」です。パッケージ
java.lang
の完全修飾名は、これがパッケージjava
のサブパッケージlang
であるため「java.lang
」です。パッケージ
java.lang
内で定義されているクラスObject
の完全修飾名は「java.lang.Object
」です。パッケージ
java.util
内で定義されているインタフェースEnumeration
の完全修飾名は「java.util.Enumeration
」です。「
double
の配列」の型の完全修飾名は「double[]
」です。「
String
の配列の配列の配列の配列」の型の完全修飾名は「java.lang.String[][][][]
」です。
コードでは、次のようになります。
package points;
class Point { int x, y; }
class PointVec { Point[] vec; }
型Point
の完全修飾名は「points.Point
」、型PointVec
の完全修飾名は「points.PointVec
」、クラスPointVec
のフィールドvec
の完全修飾名は「points.Point[]
」です。
例6.7-2.完全修飾名および正規名
完全修飾名と正規名の違いは、コードでは次のように示されます。
package p;
class O1 { class I {} }
class O2 extends O1 {}
p.O1.I
とp.O2.I
はどちらもメンバー・クラスI
を示す完全修飾名ですが、p.O1.I
のみがその正規名です。
第8章: クラス
8.1 クラス宣言
8.1.1 クラス修飾子
クラス宣言には、クラス修飾子を含めることができます。
- ClassModifier:
- (次のうちの1つ)
- Annotation
public
protected
private
abstract
static
final
strictfp
クラス宣言の注釈修飾子に関するルールについては、9.7.4および9.7.5に規定されています。
アクセス修飾子public
(6.6)は、最上位クラス(7.6)およびメンバー・クラス(8.5、9.5)のみに関連します。
アクセス修飾子protected
、およびprivate
およびは、メンバー・クラスのみに関連します。static
修飾子static
は、メンバー・クラスおよびローカル・クラス(14.3)のみに関連します。
クラス宣言について同じキーワードが修飾子として複数回出現する場合、またはアクセス修飾子public
、protected
およびprivate
(6.6)のうちの複数がクラス宣言に含まれる場合、コンパイル時にエラーが発生します。
2つ以上の(別個の)クラス修飾子が1つのクラス宣言に出現する場合は、ClassModifierのプロダクションでの出現順序が前述の内容と一致することが慣例ですが、必須ではありません。
8.1.1.1 abstract
クラス
abstract
クラスとは不完全、または不完全とみなされるクラスです。
クラス・インスタンス作成式を使用してabstract
クラスのインスタンスを作成しようとすると、コンパイル時にエラーが発生します(15.9.1)。
...
8.1.1.2 final
クラス
クラスはその定義が完全であり、サブクラスを目的としない、または必要としない場合にfinal
と宣言されます。
final
クラスの名前が別のクラス宣言のextends
句に出現する場合はコンパイル時にエラーになり(8.1.4)、final
クラスがサブクラスを持つことができないことを示します。
クラスがfinal
およびabstract
を宣言する場合はコンパイル時にエラーが発生します。これは、このようなクラスの実装が完了しないことを示しています(8.1.1.1)。
final
クラスはサブクラスを持つことができないため、final
クラスのメソッドがオーバーライドされることはありません(8.4.8.1)。
8.1.1.3 strictfp
クラス
strictfp
修飾子には、クラス宣言内(変数イニシャライザ、インスタンス・イニシャライザ、静的イニシャライザおよびコンストラクタ内を含む)のすべてのfloat
またはdouble
式を明示的にFP-strict (15.4)にするという効果があります。
これは、このクラスで宣言されるすべてのメソッド、およびこのクラスで宣言されるすべてのネストされたクラスおよびインタフェースが暗黙的にstrictfp
であることを示しています。
8.1.1.4 static
クラス
static
キーワードは、ネストされたクラスが内部クラスではないことを示しています(8.1.3)。クラスには直前と直後のインスタンスがなく、前後の型変数(6.5.5.1)、前後のインスタンス変数、ローカル変数、仮パラメータ、例外パラメータ(6.5.6.1)、また前後のインスタンス・メソッド(15.12.3)を直接参照できません。
ローカル・クラス宣言では、static
キーワードを使用できない場合があります(14.3)。
ネストされたenumクラスは暗黙的にstatic
と宣言されます。メンバーenumクラスはstatic
修飾子を重複して指定することがありますが、ローカルenumクラスは指定しません(8.9)。
8.1.2 汎用クラスおよび型パラメータ
クラスは型変数を1つ以上宣言する場合はgenericです(4.4)。
このような型変数はクラスの型パラメータと呼ばれます。型パラメータのセクションの後にはクラス名が続き、山カッコで区切られます。
- TypeParameters:
<
TypeParameterList>
- TypeParameterList:
- TypeParameter {
,
TypeParameter}
便宜上、ここでは4.4の次のプロダクションを示します。
- TypeParameter:
- {TypeParameterModifier} TypeIdentifier [TypeBound]
- TypeParameterModifier:
- Annotation
- TypeBound:
extends
TypeVariableextends
ClassOrInterfaceType {AdditionalBound}- AdditionalBound:
&
InterfaceType
型パラメータ宣言での注釈修飾子のルールは、9.7.4および9.7.5に規定されています。
クラスの型パラメータ・セクションでは、SがTの境界の場合はTが型変数Sに直接依存し、TがSに直接依存する場合、またはTがSに依存する型変数Uに直接依存する場合はTがSに依存します(この定義は再帰的に使用します)。クラスの型パラメータ・セクションで型変数がそれ自体に依存する場合は、コンパイル時にエラーが発生します。
クラスの型パラメータのスコープとシャドウ化は、6.3および6.4に規定されています。
静的コンテキストまたはネストされたクラスからクラスの型パラメータへの参照は、6.5.5.1に示すとおり制限されています。
汎用クラス宣言では、型引数による型パラメータ・セクションに考えられるそれぞれのパラメータ化について、パラメータ化された型セットを定義します(4.5)。これらのパラメータ化された型はすべて、実行時に同じクラスを共有します。
たとえば、次のコードを実行すると、
Vector<String> x = new Vector<String>(); Vector<Integer> y = new Vector<Integer>(); boolean b = x.getClass() == y.getClass();
変数
b
に値true
が格納されます。
汎用クラスがThrowable
の直接または間接サブクラスの場合、コンパイル時にエラーになります(11.1.1)。
この制限は、Java仮装マシンの捕捉メカニズムが汎用クラス以外でのみ機能するために必要とされます。
次のいずれかで汎用クラスCの型パラメータを参照すると、コンパイル時にエラーが発生します。
このような制限は現在、参照が発生した時点で6.5.5.1で規定されています。
例8.1.2-1.相互に再帰的な型変数の境界
interface ConvertibleTo<T> {
T convert();
}
class ReprChange<T extends ConvertibleTo<S>,
S extends ConvertibleTo<T>> {
T t;
void set(S s) { t = s.convert(); }
S get() { return t.convert(); }
}
例8.1.2-2.ネストされた汎用クラス
class Seq<T> {
T head;
Seq<T> tail;
Seq() { this(null, null); }
Seq(T head, Seq<T> tail) {
this.head = head;
this.tail = tail;
}
boolean isEmpty() { return tail == null; }
class Zipper<S> {
Seq<Pair<T,S>> zip(Seq<S> that) {
if (isEmpty() || that.isEmpty()) {
return new Seq<Pair<T,S>>();
} else {
Seq<T>.Zipper<S> tailZipper =
tail.new Zipper<S>();
return new Seq<Pair<T,S>>(
new Pair<T,S>(head, that.head),
tailZipper.zip(that.tail));
}
}
}
}
class Pair<T, S> {
T fst; S snd;
Pair(T f, S s) { fst = f; snd = s; }
}
class Test {
public static void main(String[] args) {
Seq<String> strs =
new Seq<String>(
"a",
new Seq<String>("b",
new Seq<String>()));
Seq<Number> nums =
new Seq<Number>(
new Integer(1),
new Seq<Number>(new Double(1.5),
new Seq<Number>()));
Seq<String>.Zipper<Number> zipper =
strs.new Zipper<Number>();
Seq<Pair<String,Number>> combined =
zipper.zip(nums);
}
}
8.1.3 内部クラスと包含インスタンス
内部クラスは、明示的にも暗黙的にもstatic
として宣言されていない、ネストしたクラスです。
内部クラスは非static
メンバー・クラス(8.5)、非static
ローカル・クラス(14.3)、または匿名クラス(15.9.5)にできます。インタフェースのメンバー・クラスは暗黙的にネストされたインタフェース(9.1)、ネストされたenumクラス(8.9)、メンバー注釈インタフェース(9.6)、およびインタフェースのメンバー・クラス(9.5)は暗黙的にstatic
(9.5)のため、内部クラスとはみなされません。static
なため、内部クラスとみなされることはありません。
内部クラスで静的イニシャライザ(8.7)を宣言した場合、コンパイル時にエラーが発生します。
明示的または暗黙的にstatic
であるメンバーを内部クラスで宣言した場合、コンパイル時にエラーが発生します。ただし、そのメンバーが定数変数(4.12.4)である場合は除きます。
内部クラスは、定数変数でないstatic
メンバーを宣言することはできませんが、それらを継承することがあります。
内部クラスでないネストしたクラスでは、Javaプログラミング言語の通常のルールに従って、static
メンバーを自由に宣言できます。
例8.1.3-1.内部クラス宣言とstaticメンバー
class HasStatic {
static int j = 100;
}
class Outer {
class Inner extends HasStatic {
static final int x = 3; // OK: constant variable
static int y = 4; // Compile-time error: an inner class
}
static class NestedButNotInner{
static int z = 5; // OK: not an inner class
}
interface NeverInner {} // Interfaces are never inner
}
これらのルールを取り除くことによって、内部クラス内の任意のstatic
メンバー宣言を許可します。
最も内側のメソッド宣言、コンストラクタ宣言、インスタンス・イニシャライザ、静的イニシャライザ、フィールド・イニシャライザ宣言、または文または式を囲む明示的なコンストラクタ呼出し文が静的メソッド宣言、静的イニシャライザ、静的変数の変数イニシャライザstatic
フィールド宣言、または明示的なコンストラクタ呼出し文の場合のみ、文または式が静的コンテキストで発生します(8.8.7.1)。
Oが直前と直後のCのクラスまたはインタフェース宣言で、Cの宣言が静的コンテキストに出現しない場合、内部クラスCはクラスまたはインタフェースOの直接の内部クラスです。
内部クラスがローカル・クラスまたは無名クラスである場合は静的コンテキストで宣言でき、その場合、包含クラスまたはインタフェースの内部クラスとみなされることはありません。
クラスCがクラスまたはインタフェースOの内部クラスとなるのは、それがOの直接内部クラスであるか、Oの内部クラスの内部クラスである場合です。
これは正常な状況ではありませんが、内部クラスの直前と直後のクラスまたはインタフェース宣言がインタフェースの場合に該当する場合があります。これはデフォルトまたは静的メソッド本体で、クラスがローカルまたは匿名クラスとして宣言された場合にのみ発生します(9.4)。
クラスまたはインタフェースOは、それ自体のゼロ番目の字句的な包含型宣言です。
クラスOがクラスCのn番目の字句的な包含型宣言となるのは、それがCのn-1番目の字句的な包含型宣言を直接包含する型宣言である場合です。
クラスまたはインタフェースOの直接内部クラスCのインスタンスiは、iを直接包含するインスタンスと呼ばれる、Oのインスタンスに関連付けられます。オブジェクトを直接包含するインスタンス(ある場合)は、オブジェクトの作成時に特定されます(15.9.2)。
オブジェクトoは、それ自体のゼロ番目の字句的な包含インスタンスです。
オブジェクトoがインスタンスiのn番目の字句的な包含インスタンスとなるのは、それがiのn-1番目の字句的な包含インスタンスを直接包含するインスタンスである場合です。
静的コンテキストで宣言される内部ローカル・クラスまたは匿名クラスのインスタンスには、字句的な包含インスタンスはありません。
それ自体がクラスまたはインタフェースSOの直接内部クラスであるCのそれぞれのスーパークラスSについては、Sに関してiを直接包含するインスタンスと呼ばれる、iに関連付けられたSOのインスタンスがあります。クラスの直接スーパークラスに関連して、直前と直後のオブジェクトのインスタンスがある場合、これはスーパークラス・コンストラクタが明示的なコンストラクタ呼出し文で呼び出される場合に決まります(8.8.7.1)。
(宣言が静的コンテキストには出現しない)内部クラスが語彙的な包括クラスまたはインタフェース宣言のメンバーであるインスタンス変数を参照すると、対応する語彙的な包括インスタンスの変数が使用されます。
使用されていても、内部クラスで宣言されていないローカル変数、仮パラメータ、例外パラメータはすべて、final
として宣言されるか、実質的にfinal (4.12.4)である必要があり、そうでない場合は使用しようとするとコンパイル時にエラーが発生します。
内部クラスで使用されるが、宣言されていないローカル変数は、内部クラスの本体より前で明確に割り当てる必要があります(16)。そうしないとコンパイル時にエラーが発生します。
変数の使用でも同様のルールがラムダ式の本体で適用されます(15.27.2)。
語彙的な包含クラスまたはインタフェース宣言の空のfinal
フィールド(4.12.4)は内部クラス内では割り当てることはできず、そうでない場合はコンパイル時にエラーが発生します。
final
変数以外への参照の制限については、現在、参照が発生するポイント6.5.6.1で直接指定しています。
明確な割当てに関するルールは重複している可能性がありますが、これは変更のスコープ外です。
例8.1.3-2.内部クラス宣言
class Outer {
int i = 100;
static void classMethod() {
final int l = 200;
class LocalInStaticContext {
int k = i; // Compile-time error
int m = l; // OK
}
}
void foo() {
class Local { // A local class
int j = i;
}
}
}
クラスLocalInStaticContext
の宣言は、静的メソッドclassMethod
内にあるため、静的コンテキストに出現します。クラスOuter
のインスタンス変数は、静的メソッドの本体内では使用できません。具体的には、Outer
のインスタンス変数をLocalInStaticContext
の本体内で使用することはできません。ただし、前後のメソッドからのローカル変数はエラーなしで参照できます(それらがfinal
として宣言されているか、実質的にfinalである場合)。
宣言が静的コンテキストには出現しない内部クラスは、包含クラス宣言のインスタンス変数を自由に参照できます。インスタンス変数は常にインスタンスに関して定義されます。包含クラス宣言のインスタンス変数の場合、インスタンス変数は内部クラスの包含インスタンスに関して定義される必要があります。たとえば、前述のクラスLocal
には、クラスOuter
の包含インスタンスがあります。さらに、次のような例があるとします。
class WithDeepNesting {
boolean toBe;
WithDeepNesting(boolean b) { toBe = b; }
class Nested {
boolean theQuestion;
class DeeplyNested {
DeeplyNested(){
theQuestion = toBe || !toBe;
}
}
}
}
ここでは、WithDeepNesting.Nested.DeeplyNested
のそれぞれのインスタンスに、クラスWithDeepNesting.Nested
の包含インスタンス(それを直接包含するインスタンス)とクラスWithDeepNesting
の包含インスタンス(その2番目の字句的な包含インスタンス)があります。
8.3 フィールド宣言
8.3.1 フィールド修飾子
8.3.1.1 static
フィールド
フィールドがstatic
として宣言されている場合、最終的に作成されるクラスの(ゼロの場合がある)インスタンス数に関係なく、そのフィールドのインカネーションは1つのみです。static
フィールドはクラス変数と呼ばれることもあり、クラスが初期化されるとインカネーションされます(12.4)。
静的フィールド宣言は静的コンテキストを導入します(8.1.3)。
static
として宣言されないフィールド(非static
フィールドとも呼ばれます)はインスタンス変数と呼ばれます。クラスの新しいインスタンスが作成されると(12.5)、常にそのインスタンスに関連する新しい変数が、そのクラスまたはそのスーパークラスで宣言されたインスタンス変数ごとに作成されます。
静的コンテキストまたはネストされたクラスからのインスタンス変数への参照は、6.5.6.1の規定のとおり制限されています。
例8.3.1.1-1. static
フィールド
class Point {
int x, y, useCount;
Point(int x, int y) { this.x = x; this.y = y; }
static final Point origin = new Point(0, 0);
}
class Test {
public static void main(String[] args) {
Point p = new Point(1,1);
Point q = new Point(2,2);
p.x = 3;
p.y = 3;
p.useCount++;
p.origin.useCount++;
System.out.println("(" + q.x + "," + q.y + ")");
System.out.println(q.useCount);
System.out.println(q.origin == Point.origin);
System.out.println(q.origin.useCount);
}
}
このプログラムは次を出力します。
(2,2)
0
true
1
p
のフィールドx
、y
、およびuseCount
は別のオブジェクトのインスタンス変数であるため、q
のフィールドには影響しません。この例では、クラスPoint
のクラス変数origin
は、p.origin
およびq.origin
のようにPoint.origin
での修飾子としてクラス名を使用、およびフィールド・アクセス式(15.11)のクラス型の変数を使用して参照されます。origin
クラス変数へのこの2つのアクセス方法は、同じオブジェクトにアクセスし、これは参照等価式(15.21.3)の値:
q.origin==Point.origin
がtrueであることで証明されます。その他の証拠は増分:
p.origin.useCount++;
によってq.origin.useCount
の値が1
になることです。これはp.origin
およびq.origin
が同じ変数を参照するためです。
例8.3.1.1-2.クラス変数の非表示
class Point {
static int x = 2;
}
class Test extends Point {
static double x = 4.7;
public static void main(String[] args) {
new Test().printX();
}
void printX() {
System.out.println(x + " " + super.x);
}
}
このプログラムでは、次の出力が生成されます。
4.7 2
クラスTest
のx
の宣言によってクラスPoint
のx
の定義が非表示になるため、クラスTest
はそのスーパークラスPoint
からフィールドx
を継承しません。クラスTest
の宣言内では、単純名x
がクラスTest
内で宣言されたフィールドを参照します。クラスTest
内のコードはクラスPoint
のフィールドx
をsuper.x
として参照する(またはx
がstatic
のため、Point.x
とする)ことがあります。Test.x
の宣言が削除された場合:
class Point {
static int x = 2;
}
class Test extends Point {
public static void main(String[] args) {
new Test().printX();
}
void printX() {
System.out.println(x + " " + super.x);
}
}
クラスPoint
のフィールドx
はクラスTest
内では非表示ではなくなり、かわりに単純名x
がフィールドPoint.x
を参照するようになりました。クラスTest
のコードは、引き続きsuper.x
と同じフィールドを参照することがあります。そのため、このバリアント・プログラムからの出力は次のようになります。
2 2
例8.3.1.1-3.インスタンス変数の非表示
class Point {
int x = 2;
}
class Test extends Point {
double x = 4.7;
void printBoth() {
System.out.println(x + " " + super.x);
}
public static void main(String[] args) {
Test sample = new Test();
sample.printBoth();
System.out.println(sample.x + " " + ((Point)sample).x);
}
}
このプログラムでは、次の出力が生成されます。
4.7 2
4.7 2
クラスTest
のx
の宣言によってクラスPoint
のx
の定義が非表示になるため、クラスTest
はそのスーパークラスPoint
からフィールドx
を継承しません。これは注意する必要がありますが、クラスPoint
のフィールドx
がクラスTest
では継承されなくても、クラスTest
のインスタンスによって実装されます。つまり、クラスTest
のすべてのインスタンスには、型int
と型double
の2つのフィールドが含まれています。どちらのフィールドも名前x
を持ちますが、クラスTest
の宣言内では、単純名x
は常にクラスTest
で宣言されたフィールドを参照します。クラスTest
のインスタンス・メソッド内のコードは、クラスPoint
のインスタンス変数x
をsuper.x
として参照することがあります。
フィールドx
へのアクセスにフィールド・アクセス式を使用するコードは、参照式の型が示すクラスのx
という名前のフィールドにアクセスします。このため、式sample.x
は変数sample
の型がTest
のため、クラスTest
で宣言されたインスタンス変数であるdouble
値にアクセスしますが、式((Point)sample).x
は型Point
へのキャストのため、クラスPoint
で宣言されたインスタンス変数であるint
値にアクセスします。
プログラムでx
の宣言がクラスTest
から削除された場合:
class Point {
static int x = 2;
}
class Test extends Point {
void printBoth() {
System.out.println(x + " " + super.x);
}
public static void main(String[] args) {
Test sample = new Test();
sample.printBoth();
System.out.println(sample.x + " " + ((Point)sample).x);
}
}
クラスPoint
のフィールドx
はクラスTest
内で非表示ではなくなります。クラスTest
の宣言のインスタンス・メソッド内では、単純名x
がクラスPoint
で宣言されたフィールドを参照するようになりました。クラスTest
のコードは、引き続きsuper.x
と同じフィールドを参照することがあります。式sample.x
は引き続き型Test
のフィールドx
を参照しますが、現在、そのフィールドは継承したフィールドのため、クラスPoint
で宣言されたフィールドx
を参照します。このバリアント・プログラムの出力は次のとおりです。
2 2
2 2
8.3.2 フィールドの初期化
フィールド宣言の宣言子に変数イニシャライザがある場合、その宣言子には宣言された変数への割当てのセマンティクス(15.26)が含まれます。
宣言子がクラス変数(つまりstatic
フィールド)の場合、そのイニシャライザに次のルールが適用されます:実行時にクラスが初期化されたとき(12.4.2)、イニシャライザが評価され、割当てが1回のみ実行されます。
いずれかのインスタンス変数への単純名による参照がイニシャライザ内に出現すると、コンパイル時にエラーが発生します。
キーワード
this
(15.8.3)またはキーワードsuper
(15.11.2、15.12)がイニシャライザ内に出現すると、コンパイル時にエラーが発生します。クラスが初期化されると(12.4.2)、実行時にイニシャライザが評価され、割当てが1回のみ実行されます。
定数変数である
static
フィールド(4.12.4)は、他のstatic
フィールドの前に初期化されることに注意してください(12.4.2)。これはインタフェースでも同様です(9.3.1)。このようなフィールドが単純名で参照される場合、そのデフォルトの初期値が表示されることはありません(4.12.5)。
定数変数である
static
フィールド(4.12.4)は、他のstatic
フィールドの前に初期化されることに注意してください(12.4.2)。これはインタフェースでも同様です(9.3.1)。このようなフィールドが単純名で参照される場合、そのデフォルトの初期値が表示されることはありません(4.12.5)。
宣言子がインスタンス変数(つまり、static
ではないフィールド)のための場合、そのイニシャライザに次のルールが適用されます:実行時に、クラスのインスタンス作成ごとにイニシャライザが評価され、割当てが実行されます(12.5)。
変数イニシャライザから、初期化されていない可能性のあるフィールドの参照は、8.3.3および16の規定のとおりその他の制限対象になります制限されています。
フィールド宣言での変数イニシャライザの例外チェックについては、11.2.3に規定されています。
変数イニシャライザはローカル変数宣言文でも使用されており(14.4)、ローカル変数宣言文が実行されるたびにイニシャライザが評価され、割当てが実行されます。
例8.3.2-1.フィールドの初期化
class Point {
int x = 1, y = 5;
}
class Test {
public static void main(String[] args) {
Point p = new Point();
System.out.println(p.x + ", " + p.y);
}
}
このプログラムでは、次の出力が生成されます。
1, 5
これは、新しいPoint
の作成ごとにx
およびy
への割当てが行われるためです。
例8.3.2-2.クラス変数への前方参照
class Test {
float f = j;
static int j = 1;
}
このプログラムはエラーなしでコンパイルし、クラスTest
を初期化したときはj
を1
に初期化し、クラスTest
のインスタンスが作成されるたびにf
を現在の値j
に初期化します。
8.4 メソッド宣言
8.4.1 仮パラメータ
メソッドまたはコンストラクタの仮パラメータがある場合、カンマ区切りパラメータ指定子のリストで指定されます。パラメータ指定子はそれぞれ、型(オプションでその前にfinal
修飾子および/または1つ以上の注釈が付きます)、およびパラメータの名前を指定する識別子(オプションで角カッコが続きます)で構成されます。
メソッドまたはコンストラクタに仮パラメータもレシーバ・パラメータもない場合、メソッドまたコンストラクタの宣言に空のカッコのペアが出現します。
- FormalParameterList:
- FormalParameter {
,
FormalParameter} - FormalParameter:
- {VariableModifier} UnannType VariableDeclaratorId
- VariableArityParameter
- VariableArityParameter:
- {VariableModifier} UnannType {Annotation}
...
Identifier - VariableModifier:
- Annotation
final
- VariableDeclaratorId:
- Identifier [Dims]
- Dims:
- {Annotation}
[
]
{{Annotation}[
]
}
メソッドまたはコンストラクタの仮パラメータは、型に続く省略記号が示す可変個引数パラメータである場合があります。1つのメソッドまたはコンストラクタに対して最大で1つの可変個引数パラメータが許可されています。可変個引数パラメータがパラメータ指定子のリスト内で最後の位置以外の場所に出現する場合は、コンパイル時にエラーが発生します。
VariableArityParameterの文法について、省略記号(
...
)はそれ自体へのトークンであることに注意してください(3.11)。それと型の間に空白を入れることは可能ですが、スタイルの面からお薦めできません。
最後の仮パラメータが可変個引数パラメータの場合、メソッドは可変個引数メソッドになります。そうでない場合は固定個引数メソッドです。
仮パラメータ宣言およびレシーバ・パラメータに対する注釈修飾子のルールは、9.7.4および9.7.5に規定されています。
final
が仮パラメータ宣言の修飾子に複数回出現する場合、コンパイル時にエラーが発生します。
仮パラメータのスコープおよびシャドウ化については、6.3および6.4に規定されています。
ネストされたクラスまたはラムダ式から仮パラメータへの参照は、6.5.6.1に規定のとおり制限されています。
メソッドまたはコンストラクタで同じ名前を持つ2つの仮パラメータを宣言する場合は、コンパイル時にエラーが発生します。(つまり、その宣言は同じ識別子を指定しています。)
final
として宣言された仮パラメータがメソッドまたはコンストラクタの本体内に割り当てられると、コンパイル時にエラーが発生します。
仮パラメータの宣言型は、それが可変個引数パラメータかどうかによって決まります。
仮パラメータが可変個引数パラメータでない場合、宣言される型はUnannTypeとVariableDeclaratorIdに角カッコのペアがなければUnannTypeで示され、そうでない場合は10.2で規定されています。
仮パラメータが可変個引数パラメータの場合、宣言される型は10.2で規定されている配列型です。
可変個引数パラメータの宣言される型に非具象化可能な要素型(4.7)がある場合、可変個引数メソッドに@SafeVarargs
(9.6.4.7)で注釈が付けられていないかぎりはそのメソッドの宣言にコンパイル時に警告がチェックされないか、警告が@SuppressWarnings
(9.6.4.5)によって非表示にされます。
メソッドまたはコンストラクタが呼び出されると(15.12)、メソッドまたはコンストラクタの本体の実行前に、それぞれ宣言された型である、新しく作成されたパラメータ変数が実引数式の値によって初期化されます。FormalParameterに出現する識別子は、メソッドまたはコンストラクタの本体で、仮パラメータへの参照のための単純名として使用できます。
可変個引数メソッドの呼出しには、仮パラメータより多くの実引数式が含まれることがあります。可変個引数パラメータの前の仮パラメータに対応しない実引数式はすべて評価され、その結果が配列に格納され、メソッド呼出しに渡されます(15.12.4.2)。
メソッドまたはコンストラクタのfloat
型のパラメータには、常にfloat値セットの要素が含まれ(4.2.3)、同様に、メソッドまたはコンストラクタの型double
の仮パラメータには常にdoubleの値セットの要素が含まれます。メソッドまたはコンストラクタの型float
の仮パラメータに、float値セットの要素ではないfloat-extended-exponent値セットの要素を含めたり、メソッドまたはコンストラクタの型double
の仮パラメータにdouble値セットの要素ではないdouble-extended-exponent値セットの要素を含めることはできません。
パラメータ変数に対応している実引数式がFP-strict (15.4)ではない場合、実引数式の評価には、該当するextended-exponent値セットから取得した中間での値を使用できます。このような式の結果は、パラメータ変数に保存する前に、呼出し変換の対象として対応する標準の値セットに最も近い値にマップされます(5.3)。
インスタンス・メソッド内および内部クラスのコンストラクタ内のレシーバ・パラメータの例の一部を次に示します。
class Test { Test(/* ?? ?? */) {} // No receiver parameter is permitted in the constructor of // a top level class, as there is no conceivable type or name. void m(Test this) {} // OK: receiver parameter in an instance method static void n(Test this) {} // Illegal: receiver parameter in a static method class A { A(Test Test.this) {} // OK: the receiver parameter represents the instance // of Test which immediately encloses the instance // of A being constructed. void m(A this) {} // OK: the receiver parameter represents the instance // of A for which A.m() is invoked. class B { B(Test.A A.this) {} // OK: the receiver parameter represents the instance // of A which immediately encloses the instance of B // being constructed. void m(Test.A.B this) {} // OK: the receiver parameter represents the instance // of B for which B.m() is invoked. } } }
B
のコンストラクタとインスタンス・メソッドは、レシーバ・パラメータの型が他の型と同様に、修飾子TypeNameで示されていますが、内部クラスのコンストラクタ内のレシーバ・パラメータの名前は、包含クラスの単純名を使用する必要があることを示しています。
8.4.3 メソッド修飾子
8.4.3.2 static
メソッド
static
として宣言されたメソッドはクラス・メソッドと呼ばれます。
クラス・メソッドのヘッダーまたは本体内で、前後の宣言の型パラメータの名前を使用すると、コンパイル時にエラーが発生します。
クラス・メソッドは常に特定のオブジェクトを参照せずに呼び出されます。キーワードthis
(15.8.3)またはキーワードsuper
(15.11.2)を使用して現在のオブジェクトを参照しようとすると、コンパイル時にエラーが発生します。
静的メソッド宣言によって、静的コンテキストが導入されます(8.1.3)。
static
として宣言されていないメソッドはインスタンス・メソッドと呼ばれ、非static
メソッドと呼ばれることもあります。
インスタンス・メソッドは常にオブジェクトに関連して呼び出されるため、そのオブジェクトは、メソッド本体の実行中にキーワードthis
およびsuper
が参照する最新のオブジェクトになります(15.8.3、15.11.2)。
静的コンテキストまたはネストされたクラスからのインスタンス・メソッドへの参照は、15.12.3の規定のとおり制限されています。
8.4.4 汎用メソッド
メソッドは、1つ以上の型変数を宣言している場合は汎用です(4.4)。
このような型変数はメソッドの型パラメータと呼ばれます。汎用メソッドの型パラメータ・セクションの形式は、汎用クラスの型パラメータ・セクションと同じです(8.1.2)。
汎用メソッド宣言では、型引数による型パラメータ・セクションの考えられる呼出しについて、メソッドのセットを定義します。型引数は推測されることが多いため、汎用メソッドが呼び出されるときに明示的に指定する必要はありません(18)。
メソッドの型パラメータのスコープとシャドウ化は、6.3および6.4に規定されています。
ネストされたクラスからメソッドの型パラメータへの参照は、6.5.5.1の規定のとおり制限されています。
次のどちらもtrueの場合、2つのメソッドまたはコンストラクタ、MおよびNは、同じ型パラメータを持ちます。
MおよびNの型パラメータの数(ゼロの場合もあります)は同じです。
A1, ..., AnはMの型パラメータ、B1, ..., BnはNの型パラメータであり、θ=[B1:=A1, ..., Bn:=An]とすると、すべてのi (1 ≤ i ≤ n)で、Aiの境界はBiの境界に適用されるθと同じ型になります。
2つのメソッドまたはコンストラクタMとNで同じ型パラメータを使用している場合、Nで示す型は、前述の定義のとおり、θをその型に適用することによって、Mの型パラメータに適合させることができます。
8.5 メンバー・クラスおよびインタフェース宣言
8.5.1 静的メンバー・クラスおよびインタフェース宣言
static
キーワードは、非内部クラスまたはインタフェースTの本体内で、メンバー・クラスCの宣言を変更することがあります。この結果、Cが内部クラスではないことを宣言します。Tのstatic
メソッドの本体内にTの現在のインスタンスがないように、CにもTの現在のインスタンスはなく、字句的に囲むインスタンスもありません。
static
クラスに包含クラスの非static
メンバーの使用が含まれる場合、コンパイル時にエラーが発生します。
メンバー・インタフェースは暗黙的にstatic
です(9.1.1)。メンバー・インタフェースの宣言でstatic
修飾子の重複指定が許可されています。
このセクションは、メンバー・クラスとローカルenumクラスの両方に該当する8.1.1.4に置き換えられました。
8.7 静的イニシャライザ
クラス内で宣言されている静的イニシャライザはクラスが初期化されたときに実行されます(12.4.2)。静的イニシャライザは、クラス変数のフィールド・イニシャライザ(8.3.2)とともにクラスのクラス変数の初期化に使用できます。
- StaticInitializer:
static
Block
静的イニシャライザが正常に終了できない場合、コンパイル時にエラーが発生します(14.21)。
return
文(14.17)が静的イニシャライザ内に出現する場合、コンパイル時にエラーが発生します。
キーワードthis
(15.8.3)またはキーワードsuper
(15.11、15.12)や、静的イニシャライザの外部で宣言された他の型変数が、静的イニシャライザ内に出現する場合、コンパイル時にエラーが発生します。
静的イニシャライザによって静的コンテキストが導入されます(8.1.3)。
クラス変数がスコープ内の場合でも、静的イニシャライザがクラス変数を参照する方法に関する制限は、8.3.3で規定されています。
静的イニシャライザの例外チェックは11.2.3で規定されています。
8.8 コンストラクタ宣言
8.8.1 仮パラメータ
コンストラクタの仮パラメータは構文内で同一であり、メソッドのパラメータに対するセマンティクスです(8.4.1)。
非private
内部メンバー・クラスのコンストラクタは、そのクラスの直前と直後のインスタンス(15.9.2、15.9.3)を表す変数である、最初の仮パラメータとして暗黙的に宣言されます。
この種のクラスのみ、コンストラクタ・パラメータが暗黙的に宣言される理由は明らかではありません。次の説明が役立つ場合があります。
非
private
内部メンバー・クラスのクラス・インスタンス作成式では、15.9.2で直前と直後のメンバー・クラスのインスタンスが規定されています。このメンバー・クラスは、クラス・インスタンス作成式のコンパイラとは別のコンパイラで生成されている可能性があります。そのため、作成式のコンパイラの標準として、(直前と直後のインスタンスを表す)参照をメンバー・クラスのコンストラクタに渡す方法があります。その結果、Javaプログラミング言語では、このセクションで非private
内部メンバー・クラスのコンストラクタが、暗黙的に直前と直後のインスタンスの初期パラメータを宣言するとみなされます。15.9.3では、そのインスタンスがコンストラクタに渡されることを規定しています。
(静的コンテキスト内にない)内部ローカル・クラスまたは(静的コンテキスト内にはない)匿名クラス、15.9.2のクラス・インスタンス作成式では、ローカル/匿名クラスの直前と直後のインスタンスを規定しています。ローカル/匿名クラスはクラス・インスタンス作成式と同じコンパイラで生成される必要があります。このコンパイラは直前と直後のインスタンスを、その想定どおり表すことができます。Javaプログラミング言語では、ローカル/匿名クラス・コンストラクタで暗黙的にパラメータを宣言する必要はありません。匿名クラスのクラス・インスタンス作成式内、および匿名クラスのスーパークラスが
内部またはローカル(静的コンテキスト内ではない)内部クラスである場合、15.9.2で匿名クラスがスーパークラスに関するインスタンスを直接囲むことを規定しています。このインスタンスは匿名クラスからそのスーパークラスに転送され、そこで直前と直後のインスタンスとして使用されます。スーパークラスは、クラス・インスタンス作成式のコンパイラとは別のコンパイラで生成されている可能性があるため、そのインスタンスを最初の引数としてスーパークラスのコンストラクタに渡す標準の方法で転送する必要があります。匿名クラス自体はクラス・インスタンス作成式のコンパイラと同じコンパイラで生成されている必要があるため、匿名クラスがインスタンスをスーパークラスのコンストラクタに渡す前に、そのコンパイラでスーパークラスに関して直前と直後のインスタンスをその想定どおり匿名クラスに転送できます。ただし、整合性を保つため、Javaプログラミング言語では、15.9.5.1で、特定の状況では、匿名クラスのコンストラクタが暗黙的にスーパークラスに関連して直前と直後のインスタンスの初期パラメータを宣言するとみなしています。
非
private
内部メンバー・クラスはそれをコンパイルしたものではなく、別のコンパイラからアクセスできる一方、内部ローカルまたは匿名クラスは常にそれをコンパイルしたものと同じコンパイラからアクセスされるという事実は、非private
内部メンバー・クラスのバイナリ名は推測可能なものとして定義されるが、内部ローカルまたは匿名クラスはそうではない理由を示しています(13.1)。
8.8.4 汎用コンストラクタ
コンストラクタは、1つ以上の型変数を宣言する場合は汎用です(4.4)。
これらの型変数は、コンストラクタの型パラメータと呼ばれます。汎用コンストラクタの型パラメータ・セクションの形式は、汎用クラスの型パラメータ・セクションと同じです(8.1.2)。
コンストラクタは、そのコンストラクタが宣言されているクラス自体が汎用であるかどうかに関係なく、汎用にできます。
汎用コンストラクタ宣言では、型引数による型パラメータ・セクションの考えられる呼出しについて、コンストラクタのセットを定義します。型引数は一般に推測されるため、汎用コンストラクタの呼出し時に明示的に指定する必要はありません(18)。
コンストラクタの型パラメータのスコープとシャドウ化は、6.3および6.4に規定されています。
明示的なコンストラクタ呼出し文、またはネストされたクラスからコンストラクタの型パラメータへの参照は、6.5.5.1の規定のとおり制限されています。
8.8.7 コンストラクタ本体
8.8.7.1 明示的なコンストラクタ呼出し
- ExplicitConstructorInvocation:
- [TypeArguments]
this
(
[ArgumentList])
;
- [TypeArguments]
super
(
[ArgumentList])
;
- ExpressionName
.
[TypeArguments]super
(
[ArgumentList])
;
- Primary
.
[TypeArguments]super
(
[ArgumentList])
;
- TypeArguments:
<
TypeArgumentList>
- ArgumentList:
- Expression {
,
Expression}
明示的なコンストラクタ呼出し文は、次の2種類に分類されます。
代替コンストラクタ呼出しは、キーワード
this
で始まります(明示的な型引数が先頭に付くこともあります)。これらは、同じクラスの代替コンストラクタを呼び出す場合に使用します。スーパークラス・コンストラクタ呼出しは、キーワード
super
で始まるか(明示的な型引数が先頭に付くこともあります)、Primary式またはExpressionNameで始まります。これらは、直接スーパークラスのコンストラクタを呼び出す場合に使用します。これらは、さらに次のように分類されます。修飾されていないスーパークラス・コンストラクタ呼出しは、キーワード
super
で始まります(明示的な型引数が先頭に付くこともあります)。修飾されたスーパークラス・コンストラクタ呼出しは、Primary式またはExpressionNameで始まります。これらを使用すると、直接スーパークラスに関して、新しく作成されるオブジェクトを直接包含するインスタンス(8.1.3)をサブクラス・コンストラクタで明示的に指定できます。これは、スーパークラスが内部クラスである場合に必要になることがあります。
コンストラクタ本体での明示的なコンストラクタ呼出し文は、インスタンス変数またはインスタンス・メソッド、またはこのクラスや任意のスーパークラスで宣言されている内部クラスは参照せず、どの式でもthis
またはsuper
を使用しません。そうでない場合は、コンパイル時にエラーが発生します。
現在のインスタンス使用に関するこのような禁止は、明示的なコンストラクタ呼出し文が静的コンテキストに出現するとみなされる理由を示しています(8.1.3)。
明示的なコンストラクタ呼出し文は、静的コンテキストを導入します(8.1.3)。
参照に関する制限については、参照が発生するポイント6.5.6.1、15.8.3、15.9.2、15.11.2、および15.12.3で直接触れています。
実際の制限は、this
の暗黙的または明示的な使用に関するものであり、たとえば、パラメータとして渡された別のクラス・インスタンスのインスタンス変数の参照や、内部クラス型へのキャストなどは可能です。
ここで、ルールが明示されていないため、javac
がどのような役割を果たすかを見てみます。
TypeArgumentsがthis
またはsuper
の左側にある場合、いずれかの型引数がワイルドカードであれば(4.5.1)、コンパイル時にエラーが発生します。
Cはインスタンス化されるクラスであり、SはCの直接スーパークラスであるとします。
スーパークラス・コンストラクタ呼出し文が修飾されていない場合、次のようになります。
Sが内部メンバー・クラスであるが、SがCを包含するクラスのメンバーでない場合は、コンパイル時にエラーが発生します。
そうでない場合は、OをSがメンバーである最も内側の包括クラスCにします。CはOの内部クラスである必要があり(8.1.3)、そうでない場合はコンパイル時にエラーが発生します。
Sが内部ローカル・クラスであり、Sが静的コンテキストに出現しない場合、Oは直前と直後のSのクラスまたはインタフェース宣言にします。CはOの内部クラスである必要があり(8.1.3)、そうでない場合はコンパイル時にエラーが発生します。
スーパークラス・コンストラクタ呼出し文が修飾されている場合、次のようになります。
Sが内部クラスでない場合、またはSの宣言が静的コンテキストに出現する場合は、コンパイル時にエラーが発生します。
また、pは
.super
の直前のPrimary式またはExpressionNameであり、OはSを直接包含するクラスであるとします。pの型がOまたはOのサブクラスでない場合、あるいはpの型にアクセスできない場合は(6.6)、コンパイル時にエラーが発生します。
明示的なコンストラクタ呼出し文がスローできる例外型については、11.2.2に規定されています。
代替コンストラクタ呼出し文の評価では、まず、通常のメソッド呼出しと同様に、コンストラクタの引数が左から右に評価され、続いてコンストラクタが呼び出されます。
スーパークラス・コンストラクタ呼出し文の評価は、次のように進められます。
iは、作成されるインスタンスであるとします。S (ある場合)に関してiを直接包含するインスタンスを特定する必要があります。
Sが内部クラスでない場合、またはSの宣言が静的コンテキストに出現する場合、Sに関してiを直接包含するインスタンスは存在しません。
そうでない場合、スーパークラス・コンストラクタ呼出しが非修飾の場合、Sは内部ローカル・クラスまたは内部メンバー・クラスである必要があります。Sが
内部ローカル・クラスの場合、Oは直前と直後のSのクラスまたはインタフェース宣言にする必要があります。Sが内部メンバー・クラスである場合、Oは、SがメンバーであるCの最も内側の包含クラスであるとします。
Oがn番目の語彙的なCの包含クラスまたはインタフェース宣言になるように、nを整数(n ≥ 1)にします。
Sに関してiを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。継承によってSがCのメンバーである場合もありますが、
this
のゼロ番目の字句的な包含インスタンス(つまり、this
自体)が、Sに関してiを直接包含するインスタンスとして使用されることはありません。そうでない場合、スーパークラス・コンストラクタ呼出しが修飾されている場合、「.super
」、pの直前にあるPrimary式またはExpressionNameが評価されます。pが
null
と評価されると、NullPointerException
が発生し、スーパークラス・コンストラクタ呼出しは突然完了します。それ以外の場合は、この評価の結果が、Sに関してiを直接包含するインスタンスです。
S (ある場合)に関してiを直接包含するインスタンスが特定されたら、スーパークラス・コンストラクタ呼出し文の評価では、通常のメソッド呼出しと同様に、コンストラクタの引数が左から右に評価され、続いてコンストラクタが呼び出されます。
最後に、スーパークラス・コンストラクタ呼出し文が正常に完了すると、Cのすべてのインスタンス変数イニシャライザとCのすべてのインスタンス・イニシャライザが実行されます。インスタンス・イニシャライザまたはインスタンス変数イニシャライザIのテキストが別のインスタンス・イニシャライザまたはインスタンス変数イニシャライザJの前にある場合、IはJより先に実行されます。
スーパークラス・コンストラクタ呼出しが実際に明示的なコンストラクタ呼出し文として出現するか、暗黙的に提供されるかに関係なく、インスタンス変数イニシャライザおよびインスタンス・イニシャライザの実行は実施されます。(代替コンストラクタ呼出しでは、こうした追加の暗黙的な実行は実施されません。)
例8.8.7.1-1.明示的なコンストラクタ呼出し文に関する制限
8.8.7の例にあるColoredPoint
という最初のコンストラクタが次のように変更されたとします。
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
class ColoredPoint extends Point {
static final int WHITE = 0, BLACK = 1;
int color;
ColoredPoint(int x, int y) {
this(x, y, color); // Changed to color from WHITE
}
ColoredPoint(int x, int y, int color) {
super(x, y);
this.color = color;
}
}
この場合、インスタンス変数color
を明示的なコンストラクタ呼出し文で使用することはできないため、コンパイル時にエラーが発生します。
例8.8.7.1-2.修飾されたスーパークラス・コンストラクタ呼出し
次のコードでは、ChildOfInner
に語彙的な包含クラスまたはインタフェース宣言がないため、ChildOfInner
のインスタンスに包含インスタンスはありません。ただし、ChildOfInner
のスーパークラス(Inner
)には語彙的な包含クラス宣言(Outer
)があるため、Inner
のインスタンスにはOuter
の包含インスタンスが必要です。Outer
の包含インスタンスは、Inner
のインスタンスの作成時に設定されます。したがって、暗黙的にInner
のインスタンスであるChildOfInner
のインスタンスを作成する際に、ChildOfInner
のコンストラクタ内で修飾されたスーパークラス呼出し文を使用してOuter
の包含インスタンスを提供する必要があります。Outer
のインスタンスは、Inner
に関してChildOfInner
を直接包含するインスタンスと呼ばれます。
class Outer {
class Inner {}
}
class ChildOfInner extends Outer.Inner {
ChildOfInner() { (new Outer()).super(); }
}
意外に感じられますが、Outer
の同じインスタンスが、ChildOfInner
の複数のインスタンスについて、Inner
に関してChildOfInner
を直接包含するインスタンスとして機能することもあります。ChildOfInner
のこれらのインスタンスは、Outer
の同じインスタンスに暗黙的にリンクされます。次のプログラムでは、Outer
のインスタンスを、修飾されたスーパークラス・コンストラクタ呼出し文でそのインスタンスを使用するChildOfInner
のコンストラクタに渡すことによって、このことを実現します。明示的なコンストラクタ呼出し文のルールでは、その文を含む、コンストラクタの仮パラメータを使用することは禁止されていません。
class Outer {
int secret = 5;
class Inner {
int getSecret() { return secret; }
void setSecret(int s) { secret = s; }
}
}
class ChildOfInner extends Outer.Inner {
ChildOfInner(Outer x) { x.super(); }
}
public class Test {
public static void main(String[] args) {
Outer x = new Outer();
ChildOfInner a = new ChildOfInner(x);
ChildOfInner b = new ChildOfInner(x);
System.out.println(b.getSecret());
a.setSecret(6);
System.out.println(b.getSecret());
}
}
このプログラムでは、次の出力が生成されます。
5
6
その結果、ChildOfInner
の異なるインスタンスへの参照が従来の意味で別名でなくても、そのような参照を通じて、Outer
の共通インスタンス内のインスタンス変数の操作を見ることができます。
8.9 enumクラス
enum宣言では、少数の名前付きクラス・インスタンスのセットを定義する特別な種類のクラスである新しいenumクラスを指定します。
- EnumDeclaration:
- {ClassModifier}
enum
TypeIdentifier [ClassImplements] EnumBody
enum宣言では、最上位enumクラス(7.6)、またはメンバーenumクラス(8.5、9.5)またはローカルenumクラス(14.3)を指定できます。
enum宣言に修飾子abstract
またはfinal
が含まれている場合、コンパイル時にエラーが発生します。
enum宣言は、クラス本体を持つenum定数(8.9.1)を1つ以上含んでいる場合を除き、暗黙的にfinal
です。
メンバーネストされたenumクラスは暗黙的にstatic
です。メンバーenumクラスの宣言をstatic
修飾子で重複して指定することが許可されています。ローカルenum宣言では、static
修飾子を重複して指定できません(14.3)。
これは、内部クラスには定数変数を除き、
static
を含めることができないため、enumクラスを内部クラスのメンバーとして定義できないことを示しています(8.1.3)。
enum宣言について同じキーワードが修飾子として複数回出現する場合、またはアクセス修飾子public
、protected
およびprivate
(6.6)のうちの複数がenum宣言に含まれる場合、コンパイル時にエラーが発生します。
enum宣言にはextends
句はありません。enumクラスEの直接スーパークラス型はEnum<
E>
です(8.1.4)。
enumクラスには、そのenum定数で定義されたもの以外のインスタンスはありません。enumクラスを明示的にインスタンス化しようとすると、コンパイル時にエラーが発生します(15.9.1)。
コンパイル時のエラーに加え、さらに3つのメカニズムにより、enumクラスにはそのenum定数で定義されている以上のインスタンスがないことが確認されます。
Enum
のfinal
clone
メソッドによって、enum定数を一切クローニングできないことが確実化されます。enumクラスの反射型のインスタンス化は禁じられています。
直列化メカニズムによる特別な処理によって、直列化復元の結果として重複するインスタンスが作成されることがなくなります。
第9章 インタフェース
インタフェース宣言により、1つ以上のクラスによって実装できる新しいインタフェースが導入されます。プログラムでは、インタフェースを使用して、それ以外に無関係なクラスの一般的なスーパータイプを指定できます。
インタフェースにはインスタンス変数がなく、通常は1つ以上のabstract
メソッドを宣言し、そうでない場合、そのabstract
メソッドの実装を指定すると、関連しないクラスによってインタフェースを実装できます。インタフェースを直接インスタンス化することはできません。
最上位インタフェース (7.6)とは、コンパイル・ユニットの最上位で宣言されるインタフェースです。
ネストされたインタフェースとは、宣言がメンバー・インタフェース(8.5、9.5)として別のクラスまたなインタフェースの本体内に出現するインタフェースです。ネストされたインタフェースはメンバー・インタフェース(8.5、9.5)またはローカル・インタフェース(14.3)にできます。
注釈インタフェース(9.6)とは、特別な構文で宣言されたインタフェースであり、注釈の反射型の表現で実装するためのものです(9.7)。
この章では、すべてのインタフェースに共通のセマンティクスについて説明します。特定の種類のインタフェースに固有の詳細は、これらのコンストラクトに特化したセクションで説明します。
インタフェースは他の1つ以上のインタフェースの直接拡張になるように宣言できます。つまり、それがオーバーライドまたは非表示にできるメンバーを除き、拡張するインタフェースのすべてのメンバー・クラスおよびインタフェース、インスタンス・メソッド、およびstatic
フィールドを継承します。
クラスは、1つ以上のインタフェースを直接実装するように宣言できます(8.1.5)。これは、インタフェースによって指定されたすべてのabstract
メソッドをそのクラスのインスタンスが実装することを意味します。クラスは必然的に、その直接スーパークラスおよび直接スーパーインタフェースが実装するすべてのインタフェースを実装します。こうした(複数の)インタフェース継承によって、オブジェクトは、スーパークラスを共有することなく、(複数の)共通動作をサポートできます。
宣言された型がインタフェース型である変数は、指定されたインタフェースを実装するクラスのインスタンスへの参照をその値として持つことができます。インタフェースのすべてのabstract
メソッドがクラスに実装されていても十分ではありません。クラス、またはそのスーパークラスのいずれかが、実際に、インタフェースを実装するように宣言されている必要があります。そうでない場合、クラスはインタフェースを実装するとみなされません。
9.1 インタフェース宣言
9.1.1 インタフェース修飾子
インタフェース宣言にはインタフェース修飾子が含まれることがあります。
- InterfaceModifier:
- (次のうちの1つ)
- Annotation
public
protected
private
abstract
static
strictfp
インタフェース宣言での注釈修飾子のルールは、9.7.4および9.7.5に規定されています。
アクセス修飾子public
(6.6)は、最上位インタフェース(7.6)およびメンバー・インタフェース(8.5、9.5)のみに関連します。
アクセス修飾子protected
、およびprivate
およびは、メンバー・インタフェースstatic
(8.5、9.5)のみに関連します。
修飾子static
は、メンバー・インタフェースおよびローカル・インタフェース(14.3)のみに関連します。
同一のキーワードがあるインタフェース宣言の1つの修飾子として複数回出現する、またはインタフェース宣言にアクセス修飾子public
、protected
、およびprivate
のいずれかが複数出現する場合は、コンパイル時にエラーが発生します(6.6)。
複数の(別個の)インタフェース修飾子が1つのインタフェース宣言に出現すると、InterfaceModifierのプロダクションでは、必須ではありませんが、慣行として前述に従った順序で表示されます。
9.1.1.1 abstract
インタフェース
すべてのインタフェースは暗黙的にabstract
です。
この修飾子は廃止されたため、新しいプログラムでは使用できません。
9.1.1.2 strictfp
インタフェース
strictfp
修飾子の結果、インタフェース宣言内のすべてのfloat
またはdouble
式が明示的にFP-strictになります(15.4)。
これは、そのインタフェース内で宣言されたすべてのメソッド、およびそのインタフェース内で宣言されたすべてのネストされた型が、暗黙的にstrictfp
であることを示しています。
9.1.1.3 static
インタフェース
ネストされたインタフェース宣言は暗黙的にstatic
です。メンバー・インタフェースの宣言でstatic
修飾子の重複指定が許可されています。ローカル・インタフェース宣言はstatic
修飾子を重複して指定できません(14.3)。
ネストされたインタフェースはstatic
であるため、直前と直後のインスタンスがなく、包含型変数(6.5.5.1)、包含インスタンス変数、ローカル変数、仮パラメータ、例外パラメータ(6.5.6.1)、または包含インスタンス・メソッド(15.12.3)を直接参照できません。
9.1.2 汎用インタフェースおよび型パラメータ
1つ以上の型変数を宣言する場合、そのインタフェースは汎用です(4.4)。
これらの型変数は、インタフェースの型パラメータと呼ばれます。型パラメータ・セクションはインタフェース名の後に続き、山カッコで区切られます。
- TypeParameters:
<
TypeParameterList>
- TypeParameterList:
- TypeParameter {
,
TypeParameter}- TypeParameter:
- {TypeParameterModifier} TypeIdentifier [TypeBound]
- TypeParameterModifier:
- Annotation
- TypeBound:
extends
TypeVariableextends
ClassOrInterfaceType {AdditionalBound}- AdditionalBound:
&
InterfaceType
型パラメータ宣言での注釈修飾子のルールは、9.7.4および9.7.5に規定されています。
インタフェースの型パラメータ・セクションでは、SがTの境界の場合、型変数Tは型変数Sに直接依存し、TがSに直接依存する場合、またはSに依存する型変数UにTが直接依存する場合、TがSに依存します(この定義は再帰的に使用します)。インタフェースの型パラメータ・セクションで型変数がそれ自体に依存する場合は、コンパイル時にエラーが発生します。
インタフェースの型パラメータのスコープおよびシャドウ化は、6.3および6.4に規定されています。
Iのstatic
メンバーの宣言内のどの場所でも、汎用インタフェースIの型パラメータを参照しようとすると、コンパイル時にエラーが発生します(9.3、9.4、9.5)。
静的コンテキストまたはネストされたクラスからのインタフェースの型パラメータの参照は、6.5.5.1の規定のとおり制限されています。
汎用インタフェース宣言では、型引数による型パラメータ・セクションの考えられるパラメータ化について、パラメータ化された型のセットを定義します(4.5)。これらのパラメータ化された型はすべて、実行時に同一のインタフェースを共有します。
9.4 メソッド宣言
- InterfaceMethodDeclaration:
- {InterfaceMethodModifier} MethodHeader MethodBody
- InterfaceMethodModifier:
- (次のうちの1つ)
- Annotation
public
private
abstract
default
static
strictfp
- MethodHeader:
- Result MethodDeclarator [Throws]
- TypeParameters {Annotation} Result MethodDeclarator [Throws]
- Result:
- UnannType
void
- MethodDeclarator:
- Identifier
(
[ReceiverParameter,
] [FormalParameterList])
[Dims]- MethodBody:
- Block
;
インタフェース・メソッド宣言上での注釈修飾子のルールは、9.7.4および9.7.5で規定されています。
インタフェース本体内のメソッドは、public
またはprivate
として宣言できます(6.6)。アクセス修飾子が指定されていない場合、メソッドは暗黙的にpublic
です。インタフェース内でメソッド宣言にpublic
修飾子を重複して指定することは禁止されていませんが、スタイルの観点からお薦めしません。
デフォルトのメソッドは、default
修飾子を付けた、インタフェース内で宣言されたインスタンス・メソッドです。その本体は常にブロックで表示され、どのクラスにも、メソッドをオーバーライドせずにインタフェースを実装するデフォルト実装を指定します。デフォルトのメソッドは具体的なメソッドとは異なり(8.4.3.1)、クラス内で、および継承またはオーバーライドされないprivate
インタフェース・メソッドから宣言されます。
インタフェースはstatic
メソッドを宣言できますが、これは特定のオブジェクトを参照せずに呼び出されます。static
インタフェース・メソッドは、インスタンス・メソッドであるデフォルト、abstract
、およびprivate
メソッドとは異なります。
インタフェースのstatic
メソッドのヘッダーまたは本体内で、前後の宣言の型パラメータの名前を使用すると、コンパイル時にエラーが発生します。
この制限は、6.5.5.1の参照が出現するポイントで直接規定されています。
静的メソッド宣言によって、静的コンテキストが導入されます(8.1.3)。
静的コンテキストまたはネストされたクラスからのインスタンス・メソッドへの参照は、15.12.3の規定のとおり制限されています。
strictfp
修飾子の結果、デフォルトまたはstatic
メソッドの本体内で、すべてのfloat
またはdouble
式が明示的にFP-strictになります(15.4)。
private
、default
、またはstatic
修飾子がないインタフェース・メソッドは、暗黙的にabstract
になります。その本体はブロックではなく、セミコロンで表されます。このようなメソッド宣言にabstract
修飾子を重複して指定することは禁止されていませんが、スタイルの観点からお薦めしません。
インタフェース・メソッドは
protected
またはパッケージ・アクセス、または修飾子final
、synchronized
、native
を使用して宣言できません。
1つのインタフェース・メソッド宣言に修飾子として同一のキーワードが複数回出現する、またはインタフェース・メソッド宣言にアクセス修飾子public
およびprivate
のいずれかが複数ある場合、コンパイル時にエラーが発生します(6.6)。
インタフェース・メソッド宣言にキーワードabstract
、default
、static
のいずれかが複数ある場合、コンパイル時にエラーが発生します。
キーワードprivate
を含むインタフェース・メソッド宣言にキーワードabstract
またはdefault
も含まれる場合、コンパイル時にエラーが発生します。インタフェース・メソッド宣言には、private
とstatic
の両方を含めることができます。
キーワードabstract
を含むインタフェース・メソッド宣言にキーワードstrictfp
も含まれる場合、コンパイル時にエラーが発生します。
インタフェースの本体で、オーバーライドと同等のシグネチャのある2つのメソッドを明示的または暗黙的に宣言すると、コンパイル時にエラーが発生します(8.4.2)。ただし、1つのインタフェースでは、そのようなシグネチャのあるabstract
メソッドを複数継承できます(9.4.1)。
インタフェース内で宣言されるメソッドは汎用にできます。インタフェース内の汎用メソッドの型パラメータのルールは、クラスの汎用メソッドのルールと同じものです(8.4.4)。
9.4.3 インタフェース・メソッド本体
デフォルトのメソッドにはブロック本体があります。このコードのブロックは、クラスがインタフェースを実装してもメソッドの実装を独自に指定しない場合、メソッドの実装を指定します。
private
またはstatic
メソッドにもブロック本体があり、メソッドの実装を指定します。
インタフェース・メソッド宣言がabstract
(明示的または暗黙的)であり、その本体にブロックがある場合、コンパイル時にエラーが発生します。
インタフェース・メソッド宣言がdefault
、private
、またはstatic
であり、その本体にセミコロンがある場合、コンパイル時にエラーが発生します。
static
メソッド本体がキーワードthis
またはキーワードsuper
を使用して現在のオブジェクトを参照しようとすると、コンパイル時にエラーが発生します。
メソッド本体内のreturn
文のルールは14.17で規定されています。
メソッドが戻り型(8.4.5)を持つよう宣言されている場合、メソッドの本体が正常に完了できるとコンパイル時にエラーが発生します(14.1)。
9.6 注釈インタフェース
注釈宣言は、特殊な種類のインタフェースである新しい注釈インタフェースを指定します。注釈宣言は標準インタフェース宣言と区別するため、キーワードinterface
の前にアットマーク(@
)が付いています。
- AnnotationDeclaration:
- {InterfaceModifier}
@
interface
TypeIdentifier AnnotationInterfaceBody
アットマーク(
@
)とキーワードinterface
は個別のトークンです。それらを空白で区切ることもできますが、スタイルとしてはお薦めしません。
注釈宣言がローカル・クラスまたはインタフェース宣言(14.3)の本体内、または匿名クラス(15.9.5)の本体内にネストされている場合は、コンパイル時にエラーが発生します。
このルールのため、注釈インタフェースには常に完全修飾名があります(6.7)。
注釈宣言の注釈修飾子のルールは、9.7.4および9.7.5に規定されています。
注釈宣言のTypeIdentifierは、注釈インタフェースの名前を指定します。
注釈インタフェースにその包含クラスまたはインタフェースのいずれかと同じ単純名がある場合、コンパイル時にエラーが発生します。
すべての注釈インタフェースの直接スーパーインタフェースはjava.lang.annotation.Annotation
です([9.1.3])。
AnnotationInterfaceDeclaration構文のため、注釈インタフェース宣言は汎用にはできず、
extends
句は許可されません。
注釈インタフェースではスーパークラス型またはスーパーインタフェース型を明示的に宣言できないため、注釈インタフェースのサブインタフェース自体が注釈インタフェースになることはありません。同様に、
java.lang.annotation.Annotation
はそれ自体が注釈インタフェースではありません。
注釈インタフェースは、Object
のインスタンス・メソッドに対応して暗黙的に宣言されたメソッドを含め、java.lang.annotation.Annotation
から複数のメンバーを継承しますが、これらのメソッドは注釈インタフェースの要素を定義しません(9.6.1)。
これらのメソッドは注釈インタフェースの要素を定義するものではないため、この型の注釈で使用することは不正になります(9.7)。このルールがなければ、要素が注釈で表現できる型であることや、それらのアクセサ・メソッドを使用できることを確認できません。
ここで明示的に修正していないかぎり、標準インタフェース宣言に適用されるルールはすべて、注釈宣言に適用されます。
たとえば、注釈インタフェースは標準のクラスおよびインタフェースと同じ名前空間を共有し、注釈宣言はインタフェース宣言と同じスコープおよびアクセス可能性を持ちます。
第13章: バイナリ互換性
13.1 バイナリの形式
プログラムは、Java仮想マシン仕様、Java SE 14 Editionによって規定されているclass
ファイル形式に、またはJavaプログラミング言語で作成されたクラス・ローダーによってこの形式にマップできる表現にコンパイルする必要があります。
クラスまたはインタフェース宣言に対応するclass
ファイルには、特定の特性が必要です。これらの特性の多くは、バイナリの互換性を確保するソース・コードの変換をサポートするように明確に選択されています。必須特性は次のとおりです。
クラスまたはインタフェースはバイナリ名によって名前を付ける必要があり、これは次の制約を満たす必要があります。
メンバー・クラスまたはインタフェース(8.5、9.5)のバイナリ名は、直前と直後のクラスまたはインタフェースのバイナリ名と、その後に続く
$
とメンバーの単純名で構成されます。ローカル・クラスまたはインタフェース(14.3)のバイナリ名は、それを直接囲んでいるクラスまたはインタフェースのバイナリ名と、その後に続く
$
、空でない数字のシーケンス、およびローカル・クラスの単純名で構成されます。匿名クラス(15.9.5)のバイナリ名は、それを直接囲んでいるクラスまたはインタフェースのバイナリ名と、その後に続く
$
、および空でない数字のシーケンスで構成されます。汎用クラスまたはインタフェースによって宣言される型変数のバイナリ名(8.1.2、9.1.2)は、それを直接囲んでいるクラスまたはインタフェースのバイナリ名、その後ろに順に続く
$
、型変数の単純名で構成されます。汎用メソッド(8.4.4)で宣言された型変数のバイナリ名は、そのメソッドを宣言するクラスまたはインタフェースのバイナリ名と、その後に続く
$
、メソッドの記述子(JVMS §4.3.3)、$
、および型変数の単純名で構成されます。汎用コンストラクタ(8.8.4)で宣言された型変数のバイナリ名は、そのコンストラクタを宣言するクラスのバイナリ名と、その後に続く
$
、コンストラクタの記述子(JVMS §4.3.3)、$
、および型変数の単純名で構成されます。
...
第14章: ブロックおよび文
プログラムの実行順序は、その結果のために実行され、値を持たない文によって制御されます。
一部の文にはその構造として、その文のサブ文など他の文を含むものがあります。SがTを含み、TがUを含む場合、SおよびUとは別の文Tがない場合、文Sは文Uを直接含みます。同様に、その構造の一部として、式(15)を含む文もあります。
この章の最初のセクションでは、文の正常な終了と突然の終了の違いについて説明します(14.1)。残りのセクションの大部分では、様々な種類の文について、正常な動作と、突然の終了に対する特別な措置の両方について詳しく説明します。
ブロックについては最初に説明し(14.2)、次にローカル・クラスおよびインタフェース宣言(14.3)、およびローカル変数宣言文(14.4)について説明します。
その後、よく知られている「dangling else」問題(14.5)を回避するための文法上の演習について説明します。
この章の最後のセクション(14.22)では、特定の技術的な意味においてすべての文を到達可能にするための要件を扱います。
14.2 ブロック
ブロックとは、文、ローカル・クラスおよびインタフェース宣言、およびカッコで囲まれたローカル変数宣言文のシーケンスです。
- Block:
{
[BlockStatements]}
- BlockStatements:
- BlockStatement {BlockStatement}
- BlockStatement:
- LocalVariableDeclarationStatement
ClassDeclaration- LocalClassOrInterfaceDeclaration
- Statement
ブロックは、ローカル変数宣言文および他の文のそれぞれを最初から最後(左から右)の順に実行することによって実行されます。これらすべてのブロック文が正常に完了すると、ブロックは正常に完了します。これらすべてのブロック文がなんらかの理由により突如完了すると、同じ理由でブロックも突如完了します。
14.3 ローカル・クラスおよびインタフェース宣言
ローカル・クラスまたはローカル・インタフェースはネストされたクラスまたはインタフェース(8、9)であり、クラスのメンバーではなく、名前があります(6.2、6.7)その宣言はブロックに直接含まれています(14.2)。
- LocalClassOrInterfaceDeclaration:
- ClassDeclaration
- NormalInterfaceDeclaration
ローカル・クラスはenumクラス(8.9)である場合があります。ローカル・インタフェースが注釈インタフェース(9.6)であることはありません。
ローカル・クラスはすべて内部クラスです(8.1.3)。
ローカルenumクラスおよびローカル・インタフェースは暗黙的にstatic
です(8.1.1.4、9.1.1.3)。暗黙的にstatic
でないローカル・クラスは内部クラスです(8.1.3)。
ローカル・クラスまたはインタフェースはパッケージ、クラス、またはインタフェースのメンバーではありません。匿名クラス(15.9.5)とは異なり、ローカル・クラスまたはインタフェースには単純名があります(6.2、6.7)。
ローカル・クラス宣言文はすべて、ただちにブロックに含まれます(14.2)。ローカル・クラス型宣言文およびインタフェース宣言は、包含ブロック内の他の種類の文と自由に混在できます。
ローカル・クラスまたはインタフェース宣言にがアクセス修飾子public
、protected
、またはprivate
のいずれか(6.6)、または修飾子static
(8.1.1)で宣言された場合、コンパイル時にエラーが発生します。
ローカル・クラスまたはインタフェース宣言のスコープおよびシャドウ化は、6.3および6.4で規定しています。
例14.3-1.ローカル・クラスおよびインタフェース宣言
ここでは、前述のルールのいくつかの項目の例を示します。
class Global {
class Cyclic {}
void foo() {
new Cyclic(); // create a Global.Cyclic
class Cyclic extends Cyclic {} // circular definition
{
class Local {}
{
class Local {} // compile-time error
}
class Local {} // compile-time error
class AnotherLocal {
void bar() {
class Local {} // ok
}
}
}
class Local {} // ok, not in scope of prior Local
}
}
メソッドfoo
の最初の文は、ローカル・クラス宣言のスコープの前に出現しているため、ローカル・クラスCyclic
のインスタンスではなく、メンバー・クラスGlobal.Cyclic
のインスタンスを作成しています。
ローカル・クラス宣言のスコープが(本体のみではなく)宣言全体を含んでいることは、ローカル・クラスCyclic
の定義がGlobal.Cyclic
ではなくそれ自体を拡張しているため、真に周期的であることを意味します。その結果、ローカル・クラスCyclic
の宣言はコンパイル時に拒否されます。
ローカル・クラス名は同じメソッド(または場合によってはコンストラクタまたはイニシャライザ)内で再宣言することはできないため、Local
の2回目と3回目の宣言の結果、コンパイル時にエラーが発生します。ただし、Local
は、AnotherLocal
など、より深くネストした別のクラス内のコンテキスト内で再宣言できます。
Local
の最後の宣言は、Local
の前の宣言のスコープ外で行われているため正当です。
14.4 ローカル変数宣言文
14.4.1 ローカル変数宣言子および型
ローカル変数宣言内のそれぞれの宣言子は、1つのローカル変数を宣言し、その名前は宣言子に出現する識別子です。
オプションのキーワードfinal
が宣言の初めに出現する場合、宣言する変数はfinal変数です(4.12.4)。
ローカル変数の宣言型は次のように決定されます。
LocalVariableTypeがUnannTypeであり、UnannTypeまたはVariableDeclaratorIdにカッコが出現していない場合、UnannTypeはローカル変数の型を示しています。
LocalVariableTypeがUnannTypeであり、UnannTypeまたはVariableDeclaratorIdにカッコが出現している場合、そのローカル変数の型は10.2で規定されます。
LocalVariableTypeが
var
の場合、Tは、割当てコンテキストに出現していなかったように扱う場合はイニシャライザ式の型、つまりスタンドアロン式にします(15.2)。ローカル変数のタイプはTで指定しているすべての合成型変数について、Tの上方投影です(4.10.5)。Tがnull型の場合はコンパイル時にエラーが発生します。
イニシャライザは割当てコンテキストに出現しないものとして扱われるため、ラムダ式(15.27)またはメソッド参照式(15.13)の場合はエラーが発生します。
例14.4.1-1.var
で宣言されるローカル変数の型
次のコードは、var
で宣言される変数の型の決定を示しています。
var a = 1; // a has type 'int'
var b = java.util.List.of(1, 2); // b has type 'List<Integer>'
var c = "x".getClass(); // c has type 'Class<? extends String>'
// (see JLS 15.12.2.6)
var d = new Object() {}; // d has the type of the anonymous class
var e = (CharSequence & Comparable<String>) "x";
// e has type CharSequence & Comparable<String>
var f = () -> "hello"; // Illegal: lambda not in an assignment context
var g = null; // Illegal: null type
var
で宣言される一部の変数は、明示的な型ではこの変数の型を示せないため宣言できません。
上方投影は、変数の型を決定するときのイニシャライザの型に適用されます。イニシャライザの型にキャプチャ変数が含まれている場合、この投影法は、キャプチャ変数を含まないサブタイプをイニシャライザの型で表します。
変数の型でキャプチャ変数は記録できますが、それを投影しないように指定してキャプチャ変数の不変を有効にし、キャプチャ変数のスコープが、型をキャプチャされる式を含む文より大きくならないようにします。非公式ですが、キャプチャ変数は後続の文に「リーク」できません。
型float
のローカル変数には常にfloat値セットの要素が含まれ(4.2.3)、同様に、型double
のローカル変数には常にdouble値セットの要素が含まれます。型float
のローカル変数にfloat値セットの要素ではないfloat-extended-exponent値セットを含めることはできず、型double
のローカル変数にdouble値セットの要素ではないdouble-extended-exponent値セットの要素を含めることはできません。
ローカル変数宣言のスコープとシャドウ化は、6.3および6.4で規定されています。
ネストされたクラスまたはラムダ式からのローカル変数の参照は、6.5.6.1の規定のとおり制限されています。
14.14 The for
文
14.14.1 基本のfor
文
基本のfor
文は一部の初期化コードを実行し、その後Expression、Statement、および一部の更新コードをExpressionの値がfalse
になるまで繰り返し実行します。
- BasicForStatement:
for
(
[ForInit];
[Expression];
[ForUpdate])
Statement- BasicForStatementNoShortIf:
for
(
[ForInit];
[Expression];
[ForUpdate])
StatementNoShortIf- ForInit:
- StatementExpressionList
- LocalVariableDeclaration
- ForUpdate:
- StatementExpressionList
- StatementExpressionList:
- StatementExpression {
,
StatementExpression}
Expressionには型boolean
またはBoolean
を持つ必要があり、そうでない場合はコンパイル時にエラーが発生します。
基本のfor
文のForInit部分で宣言されているローカル変数のスコープとシャドウ化は、6.3および6.4で規定されています。
ネストされたクラスまたはラムダ式からのローカル変数の参照は、6.5.6.1の規定のとおり制限されています。
14.14.2 拡張されたfor
文
拡張されたfor
文の形式は次のようになります。
- EnhancedForStatement:
for
(
{VariableModifier} LocalVariableType VariableDeclaratorId
:
Expression)
Statement- EnhancedForStatementNoShortIf:
for
(
{VariableModifier} LocalVariableType VariableDeclaratorId
:
Expression)
StatementNoShortIf
- VariableModifier:
- Annotation
final
- LocalVariableType:
- UnannType
var
- VariableDeclaratorId:
- Identifier [Dims]
- Dims:
- {Annotation}
[
]
{{Annotation}[
]
}
拡張されたfor
文のヘッダーはローカル変数を宣言し、その名前はVariableDeclaratorIdが指定する識別子です。
キーワードfinal
がその宣言の初めに出現する場合、宣言される変数はfinal
変数です(4.12.4)。
LocalVariableTypeがvar
であり、VariableDeclaratorIdにカッコのペアが1つ以上ある場合、コンパイル時にエラーが発生します。
Expressionの型はRAW型Iterable
のサブタイプ、または配列型(10.1)である必要があり、そうでない場合はコンパイル時にエラーが発生します。
ローカル変数の型は次のように決定されます。
LocalVariableTypeがUnannTypeであり、UnannTypeまたはVariableDeclaratorIdにカッコが出現していない場合、UnannTypeはローカル変数の型を示しています。
LocalVariableTypeがUnannTypeであり、UnannTypeまたはVariableDeclaratorIdにカッコが出現している場合、そのローカル変数の型は10.2で規定されます。
LocalVariableTypeが
var
の場合、Tは次のようにExpressionの型から導出されます。Expressionに配列型がある場合、Tは配列型のコンポーネント型です。
そうでない場合、Expressionに一部の型Xについて
Iterable<
X>
のサブタイプである型がある場合、TはXになります。そうでない場合、ExpressionにはRAW型
Iterable
のサブタイプである型があり、TはObject
です。
ローカル変数のタイプはTで指定しているすべての合成型変数について、Tの上方投影です(4.10.5)。
ローカル変数のスコープとシャドウ化については、6.3および6.4で規定されています。
ネストされたクラスまたはラムダ式からのローカル変数への参照は、6.5.6.1の規定のとおり制限されています。
拡張されたfor
文が実行されると、ローカル変数がそれぞれのループの反復で、連続する配列の要素、または式によって生成されたIterable
に初期化されます。拡張されたfor
文の明確な意味は、次のように基本のfor
文への変換によって与えられます。
...
14.20 try
文
try
文はブロックを実行します。値がスローされ、try
文にそれを捕捉する1つ以上のcatch
句がある場合、制御はその最初のcatch
句に移動します。try
文にfinally
句がある場合、try
ブロックが正常に終了したか突如終了したかに関係なく、またcatch
句に最初に制御が渡されるかどうかに関係なく、別のコード・ブロックが実行されます。
- TryStatement:
try
Block Catchestry
Block [Catches] Finally- TryWithResourcesStatement
- Catches:
- CatchClause {CatchClause}
- CatchClause:
catch
(
CatchFormalParameter)
Block- CatchFormalParameter:
- {VariableModifier} CatchType VariableDeclaratorId
- CatchType:
- UnannClassType {
|
ClassType} - Finally:
finally
Block
UnannClassTypeについては8.3を参照してください。便宜上、4.3、8.3および8.4.1からの次のプロダクションをここに示します。
- VariableModifier:
- Annotation
final
- VariableDeclaratorId:
- Identifier [Dims]
- Dims:
- {Annotation}
[
]
{{Annotation}[
]
}
try
キーワードの直後のBlockは、try
文のtry
ブロックと呼ばれます。
キーワードfinally
の直後のBlockは、try
文のfinally
ブロックと呼ばれます。
try
文には例外ハンドラとも呼ばれるcatch
句が含まれることがあります。
catch
句は例外パラメータと呼ばれるパラメータを1つのみ宣言します。
final
が例外パラメータ宣言の修飾子として複数回出現すると、コンパイル時にエラーが発生します。
例外パラメータのスコープとシャドウ化は、6.3および6.4で規定されています。
ネストされたクラスまたはラムダ式からの例外パラメータへの参照は、6.5.6.1の規定のとおり制限されています。
例外パラメータは、その型を単一のクラス型、または複数のクラス型の結合(代替)として宣言することがあります。結合の代替は構文的に|
で区切られます。
例外パラメータを単一のクラス型として示しているcatch
句は、uni-catch
句と呼ばれます。
例外パラメータを型の結合として示しているcatch
句は、multi-catch
句と呼ばれます。
...
14.20.3 try
-with-resources
try
-with-resources文は、try
ブロックの実行後に初期化された順序とは逆に、ローカル変数(resourcesと呼ばれます)でパラメータ化され、try
ブロックの実行前に初期化され、自動的に終了します。catch
句とfinally
句は通常、リソースが自動的に終了すると不要になります。
- TryWithResourcesStatement:
try
ResourceSpecification Block [Catches] [Finally]- ResourceSpecification:
(
ResourceList [;
])
- ResourceList:
- Resource {
;
Resource} - Resource:
- {VariableModifier} LocalVariableType Identifier
=
Expression - VariableAccess
- VariableAccess:
- ExpressionName
- FieldAccess
- VariableModifier:
- Annotation
final
- LocalVariableType:
- UnannType
var
リソースの指定では変数を使用して、イニシャライザ式でローカル変数を宣言、または適切な既存の変数を参照してtry
文にresourcesを示します。既存の変数は式名(6.5.6)またはフィールド・アクセス式(15.11)のいずれかによって参照されます。
リソースの指定で、同じ名前の変数を複数宣言すると、コンパイル時にエラーが発生します。
リソース指定に宣言されるそれぞれの変数に対してfinal
が修飾子として複数回出現すると、コンパイル時にエラーが発生します。
リソース指定で宣言される変数は、明示的にfinal
として宣言されない場合は暗黙的にfinal
として宣言されます(4.12.4)。
式名またはフィールド・アクセス式に示すリソースは、final
、またはtry
-with-resources文の前に明確に割り当てられた実質的にfinal
変数である必要があり、(16)そうでない場合はコンパイル時にエラーが発生します。
リソース指定で宣言された変数のLocalVariableTypeがvar
であり、イニシャライザ式に変数への参照が含まれている場合、コンパイル時にエラーが発生します。
リソース指定で宣言された変数の型は、次のように決定されます。
LocalVariableTypeがUnannTypeの場合、UnannTypeはローカル変数の型を示します。
LocalVariableTypeが
var
の場合、Tは、割当てコンテキストに出現していなかったように扱う場合はイニシャライザ式の型、つまりスタンドアロン式にします(15.2)。ローカル変数のタイプはTで指定しているすべての合成型変数について、Tの上方投影です(4.10.5)。Tがnull型の場合はコンパイル時にエラーが発生します。
リソース指定でリソースとして宣言または参照された変数の型はAutoCloseable
のサブタイプである必要があり、そうでない場合はコンパイル時にエラーが発生します。
リソース指定で宣言された変数のスコープとシャドウ化は、6.3および6.4で規定されています。
ネストされたクラスからリソース指定で宣言された変数への参照は、6.5.6.1の規定のとおり制限されています。
リソースは左から右に初期化されます。リソースの初期化に失敗(つまり、イニシャライザ式が例外をスロー)する場合、それまでtry
-with-resources文で初期化されたリソースはすべてクローズされます。すべてのリソースの初期化が完了すると、try
ブロックが正常に実行され、その後、try
-with-resources文のnullでないリソースがクローズされます。
リソースは、初期化された位置から逆方向にクローズします。リソースは、nullではない値に初期化された場合のみクローズします。1つのリソースのクローズからの例外が他のリソースのクローズを妨げることはありません。イニシャライザ、try
ブロック、またはリソースのクローズによってすでに例外がスローされている場合、例外は非表示になります。
リソース指定が複数のリソースを示すtry
-with-resources文は、複数のtry
-with-resources文であり、そのそれぞれが単一のリソースを示すリソース指定を持つものとして扱われます。nリソース(n > 1)を持つtry
-with-resources文が変換されると、その結果はn-1リソースを持つtry
-with-resources文になります。nが変換されると、nがtry
-catch
-finally
文にネストされ、全体の変換が完了します。
第15章: 式
15.8 Primary式
15.8.3 this
キーワードthis
は次のコンテキストでのみ使用できます。
他の場所に出現する場合は、コンパイル時にエラーが発生します。
キーワードthis
は、ラムダ式が出現するコンテキストで許可される場合のみ、ラムダ式に使用できます。そうでない場合、コンパイル時にエラーが発生します。
1次式として使用する場合、キーワードthis
はインスタンス・メソッドまたはデフォルト・メソッドを呼び出すオブジェクト(15.12)、または構成されるオブジェクトへの参照である値を示します。ラムダ本体でthis
が示す値は、それを囲むコンテキストのthis
が示す値と同じものです。
キーワード
this
は明示的なコンストラクタ呼出し文でも使用されます(8.8.7.1)。
Cはthis
式の最も内側の包括クラスまたはインタフェース宣言であるとします。Cが汎用であり、型パラメータF1, ..., Fnを持つ場合、this
の型はC<F1, ..., Fn>です。そうでない場合、this
の型はCです。
this
式が静的コンテキストに出現すると、コンパイル時にエラーが発生します(8.1.3)。
静的コンテキストのコンセプトをすでに定義してある場合は、this
を使用できる場所をすべて列挙する必要はありません。
実行時に、参照される実際のオブジェクトのクラスはC (Cがクラスの場合)またはCのサブクラスである場合があります。
例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
と比較してこのチェックを実装します。
15.8.4 修飾されたthis
字句的な包含インスタンス(8.1.3)は、キーワードthis
を明示的に修飾することによって参照できます。
nは、TypeNameが修飾されたthis
式が出現するを直接包含するクラスまたはインタフェースのn番目の字句的な包含クラスまたはインタフェース宣言であるような整数とします。
TypeName.this
という形式の式の値は、this
のn番目の字句的な包含インスタンスです。
TypeNameが汎用の場合(型パラメータF1、...、Fn)、式の型はTypeName<F1、...、Fn>です。そうでない場合、式の型はTypeNameです。
TypeNameが式の字句的な包含クラスまたはインタフェース宣言ではない場合、または式がTypeNameの内部クラスまたはTypeName自体ではないクラスまたはインタフェース内に出現する場合は、コンパイル時にエラーが発生します。
修飾されたthis
式が静的コンテキストに出現すると、コンパイル時にエラーが発生します(8.1.3)。
修飾されたthis
式の直接包含するクラスまたはインタフェース宣言がTypeNameの内部クラスまたはTypeName自体ではない場合は、コンパイル時にエラーが発生します。
15.9 クラス・インスタンス作成式
15.9.2 包含インスタンスの特定
Cはインスタンス化されるクラス、iは作成されるインスタンスであるとします。Cが内部クラスである場合、iには、直接包含するインスタンス(8.1.3)がある可能性があり、次のように特定されます。
Cが無名クラスである場合は、次のようになります。
クラス・インスタンス作成式が静的コンテキストに出現する場合、iには、直接包含するインスタンスはありません。
そうでない場合、iを直接包含するインスタンスは
this
です。
Cが
内部ローカル・クラスの場合は次のようになります。Cが静的コンテキストに出現する場合、iには、直接包含するインスタンスはありません。
そうでない場合、クラス・インスタンス作成式が静的コンテキストに出現すると、コンパイル時にエラーが発生します。
そうでない場合、Oは直前と直後のCのクラスまたはインタフェース宣言、Uは直前と直後のクラス・インスタンス作成式のクラスまたはインタフェース宣言とします。UがOの内部クラスまたはO自体ではない場合、コンパイル時にエラーが発生します。
そうでない場合、
Oは直前と直後のCのクラスまたはインタフェース宣言とします。nを整数とすると、OはUのクラス・インスタンス作成式が出現するクラスまたはインタフェースのn番目の語彙的な包含クラスまたはインタフェース宣言になります。iを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。
Cが内部メンバー・クラスである場合は、次のようになります。
クラス・インスタンス作成式が修飾されていない場合、次のようになります。
クラス・インスタンス作成式が静的コンテキストに出現する場合、コンパイル時にエラーが発生します。
そうでない場合、Cがその宣言でクラス・インスタンス作成式を囲んでいるクラスのメンバーでなければコンパイル時にエラーが発生します。
そうでない場合、OをCがメンバーである最も内側の包含クラス宣言にし、Uを直前と直後のクラス・インスタンス作成式のクラスまたはインタフェース宣言にします。UがOの内部クラスまたはO自体ではない場合、コンパイル時にエラーが発生します。
そうでない場合、
Cがクラス・インスタンス作成式が出現する、クラスまたはインタフェースを囲むクラスのメンバーである場合、OをCがメンバーである、直前と直後のクラスにします。nを整数とすると、OはUのクラス・インスタンス作成式が出現するクラスまたはインタフェースのn番目の語彙的な包含クラスまたはインタフェース宣言になります。iを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。そうでない場合、コンパイル時にエラーが発生します。
クラス・インスタンス作成式が修飾されている場合、iを直接包含するインスタンスは、Primary式またはExpressionNameの値であるオブジェクトです。
Cが無名クラスで、かつその直接スーパークラスSが内部クラスである場合、iは、Sに関して直接包含するインスタンスを持つ可能性があり、次のように判別されます。
Sが
内部ローカル・クラスの場合、次のようになります。Sが静的コンテキストに出現する場合、iには、Sに関して直接包含するインスタンスはありません。
そうでない場合、クラス・インスタンス作成式が静的コンテキストに出現すると、コンパイル時にエラーが発生します。
そうでない場合、Oを直前と直後のSのクラスまたはインタフェース宣言にし、Uを直前と直後のクラス・インスタンス宣言式のクラスまたはインタフェース宣言にします。UがOの内部クラスまたはO自体ではない場合、コンパイル時にエラーが発生します。
そうでない場合、
Oを直前と直後のSのクラスまたはインタフェース宣言にします。nを整数とすると、OはUのクラス・インスタンス作成式が出現するクラスまたはインタフェースのn番目の語彙的な包含クラスまたはインタフェース宣言になります。Sに関してiを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。
Sが内部メンバー・クラスである場合は、次のようになります。
クラス・インスタンス作成式が修飾されていない場合、次のようになります。
クラス・インスタンス作成式が静的コンテキストに出現する場合、コンパイル時にエラーが発生します。
そうでない場合、Sはその宣言がクラス・インスタンス作成式を囲んでいるクラスのメンバーではなく、コンパイル時にエラーが発生します。
そうでない場合、OをSがメンバーである、最も内側の包含クラス宣言にし、Uを直前と直後のクラス・インスタンス作成式のクラスまたはインタフェース宣言にします。UがOの内部クラスまたはO自体ではない場合、コンパイル時にエラーが発生します。
そうでない場合、
Sがクラス・インスタンス作成式が出現する、クラスまたはインタフェースを囲むクラスのメンバーであれば、OをSがメンバーである、直前と直後のクラスにします。nを整数とすると、OはUのクラス・インスタンス作成式が出現するクラスまたはインタフェースのn番目の語彙的な包含クラスまたはインタフェース宣言になります。Sに関してiを直接包含するインスタンスは、
this
のn番目の字句的な包含インスタンスです。そうでない場合、コンパイル時にエラーが発生します。
クラス・インスタンス作成式が修飾されている場合、Sに関してiを直接包含するインスタンスは、Primary式またはExpressionNameの値であるオブジェクトです。
15.11 フィールド・アクセス式
15.11.2 super
を使用したスーパークラス・メンバーへのアクセス
super.
Identifierの形式は、現在のオブジェクトのIdentifierというフィールドを参照していますが、その現在のオブジェクトは、現在のクラスのスーパークラスのインスタンスとして表示されます。
T.super.
Identifierという形式は、Tに対応する語彙的な包含インスタンスのIdentifierというフィールドを参照しますが、そのインスタンスはTのスーパークラスのインスタンスとして表示されます。
キーワードsuper
を使用した形式は、クラスのインスタンス・メソッド、インスタンス・イニシャライザ、コンストラクタ、またはクラスのインスタンス変数のイニシャライザでのみ有効です。他の場所で出現する場合、コンパイル時にエラーが発生します。
これは、キーワード
this
をクラス宣言で使用する場合とまったく同じ状況です(15.8.3)。
キーワードsuper
を使用するフィールド・アクセス式が静的コンテキストに出現する場合、コンパイル時にエラーが発生します(8.1.3)。
キーワードsuper
を使用する形式がクラスObject
の宣言に出現する場合、Object
にはスーパークラスがないためコンパイル時にエラーが発生します。
形式super.
Identifierのフィールド・アクセス式の、直前と直後のクラスまたはインタフェース宣言が、クラスObject
またはインタフェースである場合、コンパイル時にエラーが発生します。
Uを形式T.super.
Identifierの直前と直後のフィールド・アクセス式のクラスまたはインタフェース宣言にします。UがTの内部クラスまたはT自体ではない場合、コンパイル時にエラーが発生します。TがクラスObject
またはインタフェースの場合、コンパイル時にエラーが発生します。
インタフェースに関する制限はこれまで規定されていませんが、デフォルト・メソッドが導入されると必要になります。15.12.1と比較します。
フィールド・アクセス式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は、クラスS内でアクセス可能なフィールドfがクラスT内のフィールドfの宣言によって非表示にされていても、そのフィールドにアクセスできます。
現在のクラスがクラスTの内部クラスまたはT自体ではない場合、コンパイル時にエラーが発生します。
例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
と同じです。super.x
はスーパークラスのprotected
メンバーへのアクセスが困難なため、キャストについては指定されていません。
15.12 メソッド呼出し式
15.12.1 コンパイル時のステップ1: 検索対象の型の決定
コンパイル時のメソッド呼出しの処理での最初のステップは、呼び出されるメソッドの名前およびその名前のメソッドの定義の検索対象の型を把握することです。
メソッドの名前は、MethodInvocationの左カッコの直前にあるMethodNameまたはIdentifierによって指定されます。
検索対象の型については、MethodInvocationの左カッコの前の形式に応じて6つのケースを考慮します。
形式がMethodName、つまり単にIdentifierである場合は、次のようになります。
Identifierが、その名前を持つメソッド宣言のスコープ内に出現する場合(6.3、6.4.1)、次のようになります。
そのメソッドがメンバーである包含クラスまたはインタフェース宣言がある場合は、Eを最も内側のそのようなクラスまたはインタフェース宣言にします。検索対象の型は、E.
this
(15.8.4)の型です。この検索ポリシーは、コーム・ルールと呼ばれます。実質的には、ネストしたクラスのスーパークラス階層内でメソッドを検索してから、包含クラスおよびそのスーパークラス階層内でメソッドを検索します。例については、6.5.7.1を参照してください。
それ以外の場合、1つ以上の単一静的インポートまたはオンデマンド静的インポート宣言により、メソッド宣言はスコープ内にある可能性があります。呼び出されるメソッドは後で決定されるため(15.12.2.1)、検索対象の型はありません。
形式がTypeName
.
[TypeArguments] Identifierである場合、検索対象の型はTypeNameが示す(場合によってはRAW)型です。形式がExpressionName
.
[TypeArguments] Identifierである場合、検索対象の型は、ExpressionNameが示す変数の宣言された型Tであるか(Tがクラスまたはインタフェース型である場合)、Tの上限です(Tが型変数である場合)。形式がPrimary
.
[TypeArguments] Identifierである場合、Tは、Primary式の型であるとします。検索対象の型は、Tであるか(Tがクラスまたはインタフェース型である場合)、Tの上限です(Tが型変数である場合)。Tが参照型でない場合は、コンパイル時にエラーが発生します。
形式が
super
.
[TypeArguments] Identifierである場合、検索対象の型は宣言にメソッド呼出しが含まれるクラスの直接スーパークラス型です。Eは、メソッド呼出しを直接包含するクラスまたはインタフェース宣言であるとします。Eがクラス
Object
であるかEがインタフェースである場合、コンパイル時にエラーが発生します。形式がTypeName
.
super
.
[TypeArguments] Identifierである場合は、次のようになります。TypeNameがクラスもインタフェースも示さない場合、コンパイル時にエラーが発生します。
TypeNameがクラスCを示す場合、検索対象の型はCの直接スーパークラス型です。
Cが
現在のクラスまたはインタフェースメソッド呼出し式の語彙的な包含クラスまたはインタフェース宣言ではない場合、またはCがクラスObject
の場合、コンパイル時にエラーが発生します。Cは現在のクラスである場合があります。
Eは、メソッド呼出しを直接包含するクラスまたはインタフェース宣言であるとします。EがクラスObject
である場合、コンパイル時にエラーが発生します。これは重要ではありませんが、
Object
には包含の型宣言はなく、あった場合にそのクラスのsuper
メソッドを参照しても問題はありません。そうでない場合、TypeNameはインタフェースIを示します。
Eは、メソッド呼出しを直接包含するクラスまたはインタフェース宣言であるとします。IがEの直接スーパーインタフェースではない場合、またはEに他の直接スーパークラスか直接スーパーインタフェースJが存在し、JがIのサブクラスまたはサブインタフェースであるような場合は、コンパイル時にエラーが発生します。
検索対象の型は、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 } }; }
回避策としては、インタフェース
super
呼出しを実行する、語彙的な包含クラス宣言にprivate
メソッドを導入します。
15.12.3 コンパイル時のステップ3: 選択したメソッドが適切かどうか
メソッド呼出しについて最も的確なメソッド宣言がある場合、メソッド呼出しのコンパイル時宣言と呼ばれます。
メソッド呼出しの引数が、コンパイル時宣言の呼出しタイプから導出されたそのターゲット型と互換性がない場合は、コンパイル時にエラーが発生します。
コンパイル時宣言が可変引数呼出しによって適用可能である場合、メソッドの呼出しタイプの最後の仮パラメータ型がFn[]
であるとすると、Fnのイレイジャである型が呼出しの時点でアクセス可能でない場合(6.6)、コンパイル時にエラーが発生します。
コンパイル時宣言がvoid
である場合、メソッド呼出しは、最上位式(つまり、式の文にあるか、for
文のForInitまたはForUpdate部分にあるExpression)である必要があります。そうでない場合はコンパイル時にエラーが発生します。このようなメソッド呼出しは値を生成しないため、値が不要な状況でのみ使用する必要があります。
さらに、コンパイル時宣言が適切であるかどうかは、次のように、左カッコの前にあるメソッド呼出し式の形式によって異なることがあります。
形式がMethodName、つまり単にIdentifierであり、かつコンパイル時宣言がインスタンス・メソッドである場合は、次のようになります。
形式がTypeName
.
[TypeArguments] Identifierである場合、コンパイル時宣言はstatic
である必要があります。そうでない場合はコンパイル時にエラーが発生します。形式がExpressionName
.
[TypeArguments] IdentifierまたはPrimary.
[TypeArguments] Identifierである場合、コンパイル時宣言は、インタフェースで宣言されたstatic
メソッドでないことが必要です。そうである場合はコンパイル時にエラーが発生します。形式が
super
.
[TypeArguments] Identifierである場合は、次のようになります。コンパイル時宣言が
abstract
である場合、コンパイル時にエラーが発生します。メソッド呼出しが静的コンテキストに出現する場合、コンパイル時にエラーが発生します。
形式がTypeName
.
super
.
[TypeArguments] Identifierである場合は、次のようになります。コンパイル時宣言が
abstract
である場合、コンパイル時にエラーが発生します。メソッド呼出しが静的コンテキストに出現する場合、コンパイル時にエラーが発生します。
TypeNameがクラスCを示している場合、直前と直後のメソッド呼出しのクラスまたはインタフェース宣言がC
に直接囲まれていないではない場合、またはCの内部クラスの場合、コンパイル時にエラーが発生します。TypeNameがインタフェースを示している場合、Eをメソッド呼出しを直接包含するクラスまたはインタフェース宣言とします。Eの直接スーパークラスまたは直接スーパーインタフェースからのコンパイル時宣言をオーバーライド(9.4.1)する、コンパイル時宣言とは異なるメソッドが存在する場合、コンパイル時にエラーが発生します。
親インタフェースの親で宣言されたメソッドをスーパーインタフェースがオーバーライドする場合、このルールでは、親の親を直接スーパーインタフェースのそのリストに単に追加することによって、子インタフェースがオーバーライドをスキップすることが回避されます。親の親の機能にアクセスするための適切な方法は、直接スーパーインタフェースを介して、そのインタフェースが目的の動作を公開することを選択する場合にのみ、アクセスすることです。(また、プログラマは、
super
メソッド呼出しで目的の動作を公開する追加のスーパーインタフェースを自由に定義できます。)
compile-timeパラメータ型とcompile-time結果は次のように決定されます。
メソッド呼出しのコンパイル時宣言がシグネチャ多相メソッドではない場合は次のようになります。
コンパイル時のパラメータ型は、コンパイル時宣言の仮パラメータの型です。
コンパイル時の結果は、コンパイル時宣言の呼出し型の結果です(15.12.2.6)。
メソッド呼出しのコンパイル時宣言がシグネチャ多相メソッドの場合、次のようになります。
コンパイル時のパラメータ型は、実引数式の型です。nullリテラル
null
(3.10.7)の引数式は、型Void
を持つものとして扱われます。コンパイル時の結果は次のように決定されます。
メソッドは、次がすべてtrueの場合にシグネチャ多相になります。
java.lang.invoke.MethodHandle
クラスまたはjava.lang.invoke.VarHandle
クラスで宣言されている。宣言型が
Object[]
の単一の可変個引数パラメータ(8.4.1)がある。native
である。
次のコンパイル時の情報は、実行時に使用するメソッド呼出しに関連するものです。
メソッドの名前。
メソッド呼出しの修飾型(13.1)。
順に並べたパラメータの数、およびコンパイル時のパラメータ型。
コンパイル時の結果。
次のように計算された呼出しモード。
コンパイル時宣言に
static
修飾子がある場合、呼出しモードはstatic
です。そうでない場合、左側のカッコの前のメソッド呼出しの部分が
super
.
IdentifierまたはTypeName.
super
.
Identifierの形式であれば、呼出しモードはsuper
です。そうでない場合、メソッド呼出しの修飾型がインタフェースであれば、呼出しモードは
interface
です。そうでない場合、呼出しモードは
virtual
です。
コンパイル時宣言の呼出し型の結果がvoid
の場合、メソッド呼出し式の型は、コンパイル時宣言の呼出し型の戻り型へのキャプチャ変換を適用して取得します(5.1.10)。
15.13 メソッド参照式
15.13.1 メソッド参照のコンパイル時宣言
メソッド参照式のコンパイル時宣言は、式の参照先のメソッドです。特殊なケースでは、コンパイル時宣言は実際に存在しませんが、クラス・インスタンス作成または配列作成を表す概念上のメソッドが存在します。コンパイル時宣言の選択は、メソッド呼出しのコンパイル時宣言が呼出しの引数に依存するように、式がターゲットとする関数型に依存します(15.12.3)。
コンパイル時宣言の検索では、15.12.1および15.12.2のメソッド呼出しのプロセスを次のようにミラー化します。
最初に、検索する型を決定します。
メソッド参照式の形式がExpressionName
::
[TypeArguments] IdentifierまたはPrimary::
[TypeArguments] Identifierの場合、検索対象の型は::
トークンの前の式の型です。メソッド参照式の形式がReferenceType
::
[TypeArguments] Identifierの場合、検索対象の型は、ReferenceTypeに適用されるキャプチャ変換(5.1.10)の結果です。メソッド参照式の形式が
super
::
[TypeArguments] Identifierの場合、検索対象の型は、宣言に含まれるメソッド参照の直前と直後のクラスまたはインタフェース宣言のスーパークラス型です。Tを、メソッド参照式を直接囲んでいるクラスまたはインタフェース宣言にします。Tがクラス
Object
、またはTがインタフェースの場合、コンパイル時にエラーが発生します。メソッド参照式の形式がTypeName
.
super
::
[TypeArguments] Identifierの場合、TypeNameがクラスを示すのであれば、検索対象の型は名前付きクラスのスーパークラス型であり、そうでない場合はTypeNameが検索対象のインタフェース、および宣言にメソッド参照が含まれる、該当するクラスまたはインタフェースのスーパーインタフェース型を示します。TypeNameがメソッド参照式の語彙的な包含クラスまたはインタフェース宣言、または直前と直後のメソッド参照式のクラスまたはインタフェース宣言の直接スーパーインタフェースではない場合、コンパイル時にエラーが発生します。
TypeNameがクラス
Object
の場合、コンパイル時にエラーが発生します。TypeNameがインタフェース型であり、直前と直後のメソッド参照式Jのクラスまたはインタフェース宣言に他の直接スーパークラスまたは直接スーパーインタフェースがある場合、そのJはTypeNameのサブタイプです。
15.12.1と比較します。メソッド参照式に対するこれらのルールが記載されている場所はないようです。
他の2つの形式(
::
new
の呼出し)については、参照されているメソッドが概念的なものであり、検索対象の型はありません。
次に、ターゲットの関数型をnパラメータで指定すると、適用可能なメソッドのセットが特定されます。
メソッド参照式の形式がReferenceType
::
[TypeArguments] Identifierの場合、適用可能なメソッドは次のとおりです。名前がIdentifier、引数の数がnであり、型引数TypeArgumentsがあり、メソッド参照式と同じクラスに出現する、メソッド呼出しに適用可能な検索対象のメンバー・メソッド(15.12.2.1)、および
名前がIdentifier、引数の数がn-1であり、型引数TypeArgumentsがあり、メソッド参照式と同じクラスに出現する、メソッド呼出しに適用可能な検索対象の型のメンバー・メソッド。
2つの異なる引数の数、nとn-1で、この形式が
static
メソッドとインスタンス・メソッドのどちらを参照している可能性があるかを判断できます。メソッド参照式の形式がClassType
::
[TypeArguments]new
の場合、適用可能なメソッドは、ClassTypeのコンストラクタに対応する概念的なメソッドのセットです。ClassTypeがRAW型でも、RAW型の非
static
メンバー型ではない場合、概念的なメンバー・メソッドの候補は、<>
を使用してクラスへの型引数を除外するクラス・インスタンス作成式について15.9.3で規定されているメソッドです。そうでない場合、概念的なメンバー・メソッドの候補はClassTypeのコンストラクタであり、戻り型ClassTypeのメソッドとして扱われます。これらの候補のうち適用可能なメソッドは、引数の数がnであり、型引数TypeArgumentsがあり、メソッド参照式として同じクラスに出現するメソッド呼出しに適用可能になる概念的なメソッドです。
メソッド参照式の形式がArrayType
::
new
の場合、単一の概念的なメソッドが考慮されます。メソッドには型int
の単一のパラメータがあり、ArrayTypeを返し、throws
句はありません。n = 1の場合はこれが唯一適用可能なメソッドであり、そうでない場合は適用可能なメソッドがありません。他のすべての形式については、適用可能なメソッドは、名前がIdentifier、引数の数がnであり、型引数TypeArgumentsがあり、メソッド参照式と同じクラスに出現するメソッド呼出しに適用できる、検索対象の型のメンバー・メソッドです。
最終的に、適用可能なメソッドがなければ、コンパイル時宣言もありません。
そうでない場合、パラメータ型P1, ..., Pnのターゲットの関数型および適用可能なメソッドのセットに対して、コンパイル時宣言が次のように選択されます。
メソッド参照式の形式がReferenceType
::
[TypeArguments] Identifierの場合、最も的確で適用可能なメソッドの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型であり、P1のスーパータイプであるこの型G
<
...>
のパラメータ化がある場合、検索対象の型はG<
...>
に適用されるキャプチャ変換の結果(5.1.10)になり、そうでない場合、検索対象の型は最初の検索の型と同じものになります。型引数があれば、メソッド参照式で指定されます。最初の検索で
static
である最も的確なメソッドが生成され、2番目の検索で生成された適用可能なメソッドのセットに非static
メソッドが含まれていない場合、コンパイル時宣言は最初の検索の最も的確なメソッドになります。そうでない場合、最初の検索で生成された適用可能なメソッドのセットに
static
メソッドが含まれておらず、2番目の検索で非static
の最も的確なメソッドが生成されると、コンパイル時宣言が2番目の検索の最も的確なメソッドになります。そうでない場合、コンパイル時宣言はありません。
メソッド参照式のその他すべての形式については、最も的確で適用可能なメソッドの検索が1回実行されます。検索については、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で、メソッド参照式が静的コンテキストに出現する場合、コンパイル時にエラーが発生します。
メソッド参照式の形式がTypeName .
super
::
[TypeArguments] Identifierでメソッド参照式が静的コンテキストに出現する場合、またはTypeNameがクラスCを示し、直前と直後のメソッド参照式のクラスまたはインタフェース宣言がCまたはCの内部クラスではない場合、コンパイル時にエラーが発生します。
15.12.3と比較します。
メソッド参照式の形式が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<>()
)として扱われます。「ダイヤモンド」は暗黙的のため、この形式はRAW型をインスタンス化しません。事実、RAW型のコンストラクタへの参照を表現する方法はありません。
一部のメソッド参照式では、ターゲットの関数型に関係なく、使用できるコンパイル時宣言は1つのみであり、その呼出し型も1つのみです(15.12.2.6)。このようなメソッド参照式はexactと呼ばれます。exactではないメソッド参照式はinexactと呼ばれます。
次をすべて満たす場合、Identifierで終了するメソッド参照式はexactです。
メソッド参照式の形式がReferenceType
::
[TypeArguments] Identifierの場合、ReferenceTypeがRAW型を示さない。検索対象の型に、メソッド参照式が出現するクラスまたはインタフェースにアクセスできる、Identifierというメンバー・メソッドが1つだけある。
このメソッドが可変個引数でない(8.4.1)。
このメソッドが汎用(8.4.4)の場合、メソッド参照式でTypeArgumentsが指定される。
次をすべて満たす場合、形式ClassType ::
[TypeArguments] new
のメソッド参照式はexactです。
ClassTypeが示す型がRAW型ではないか、RAW型の非
static
メンバー型である。ClassTypeが示す型に、メソッド参照式が出現するクラスまたはインタフェースにアクセスできるコンストラクタが1つだけある。
このコンストラクタが可変個引数ではない。
このコンストラクタが汎用の場合、メソッド参照式でTypeArgumentsが指定される。
形式ArrayType ::
new
のメソッド参照式は常にexactです。
15.27 ラムダ式
15.27.1 ラムダ・パラメータ
ラムダ式の仮パラメータがある場合は、カンマ区切りのパラメータ指定子のカッコで囲まれたリスト、またはカンマ区切りの識別子のカッコで囲まれたリストで指定されています。パラメータ指定子のリストでは、各パラメータ指定子はオプションの修飾子とその後に続く型(またはvar
)、パラメータの名前を指定する識別子で構成されます。識別子のリストでは、各識別子がパラメータの名前を指定します。
ラムダ式に仮パラメータがない場合、->
とラムダ本体の前に空のカッコのペアが出現します。
ラムダ式に仮パラメータが1つだけあり、そのパラメータがパラメータ指定子ではなく識別子で指定されている場合、識別子を囲むカッコを省略できます。
- LambdaParameters:
(
[LambdaParameterList])
- Identifier
- LambdaParameterList:
- LambdaParameter {
,
LambdaParameter} - Identifier {
,
Identifier} - LambdaParameter:
- {VariableModifier} LambdaParameterType VariableDeclaratorId
- VariableArityParameter
- LambdaParameterType:
- UnannType
var
- VariableArityParameter:
- {VariableModifier} UnannType {Annotation}
...
Identifier- VariableModifier:
- Annotation
final
- VariableDeclaratorId:
- Identifier [Dims]
- Dims:
- {Annotation}
[
]
{{Annotation}[
]
}
ラムダ式の仮パラメータは、パラメータ指定子で指定されている場合のみfinal
として宣言または注釈が付けられます。かわりに仮パラメータが指定子で指定されている場合、その仮パラメータはfinal
ではなく、注釈は付けられません。
ラムダ式の仮パラメータは、省略記号とそれに続くパラメータ指定子の型で示す可変個引数パラメータの場合があります。ラムダ式では、最大で1つの可変個引数パラメータが許可されます。可変個引数パラメータがパラメータ指定子のリスト内で最後の位置以外の場所に出現する場合は、コンパイル時にエラーが発生します。
ラムダ式のそれぞれの仮パラメータには、推測型または宣言型があります。
仮パラメータが
var
を使用するパラメータ指定子、またはパラメータ指定子のかわりの識別子で指定される場合、その仮パラメータは推測型です。この型はラムダ式がターゲットとする機能インタフェース型から推測されます(15.27.3)。仮パラメータが
var
を使用しないパラメータ指定子で指定される場合、その仮パラメータは宣言型です。宣言型は次のように決定されます。
次のラムダ式のリストは区別されていません。
(int... x) `->` BODY (int[] x) `->` BODY
機能インタフェースの抽象メソッドが固定個引数と可変個引数のどちらでも使用できます。(これはメソッドのオーバーライドのルールに従っています。)ラムダ式は直接呼び出されないため、機能インタフェースが
int[]
を使用する仮パラメータにint...
を使用しても、周辺のプログラムに影響はありません。ラムダ本体では、可変個引数パラメータは、配列型パラメータとして扱われます。
すべての仮パラメータに宣言型があるラムダ式は、明示的な型指定と呼ばれます。すべての仮パラメータに推測型があるラムダ式は、暗黙的な型指定と呼ばれます。仮パラメータのないラムダ式は、明示的に型指定されます。
ラムダ式が暗黙的に型指定されている場合、そのラムダ本体はそれが出現するコンテキストに応じて解釈されます。具体的には、本体の式の型、本体からスローされたチェック済の例外、および本体のコードの型の正確性はすべて、仮パラメータに推測される型によって決まります。これは、仮パラメータ型の推測が、ラムダ本体の型チェックの試行「前」に行われていることを示しています。
ラムダ式で宣言型の仮パラメータおよび推測型の仮パラメータを宣言すると、コンパイル時にエラーが発生します。
このルールによって、
(x, int y) -> BODY
や(var x, int y) -> BODY
など、仮パラメータで推測型と宣言型の混在を回避できます。すべての仮パラメータに推測型がある場合、(x, var y) -> BODY
や(var x, y) -> BODY
など、識別子とvar
パラメータ指定子の混在が文法によって回避されます。
仮パラメータ宣言の注釈修飾子のルールは、9.7.4および9.7.5に規定されています。
final
が仮パラメータ宣言の修飾子に複数回出現する場合、コンパイル時にエラーが発生します。
仮パラメータのLambdaParameterTypeがvar
であり、同じ仮パラメータのVariableDeclaratorIdに1つ以上のカッコのペアがある場合、コンパイル時にエラーが発生します。
仮パラメータ宣言のスコープとシャドウ化は、6.3および6.4で規定されています。
ネストされたクラスまたはネストされたラムダ式から仮パラメータへの参照は、6.5.6.1に規定のとおり制限されています。
ラムダ式で2つの仮パラメータを同じ名前で制限すると、コンパイル時にエラーが発生します。(つまり、その宣言は同じ識別子を指定しています。)
Java SE 8では
_
をラムダ・パラメータ名に使用することは禁じられており、他の種類の変数の名前に使用することもお薦めできません(4.12.3)。Java SE 9以降、_
はキーワードになるため(3.9)、どのコンテキストでも変数名としては使用できません。
final
として宣言された仮パラメータがラムダ式の本体内に割り当てられると、コンパイル時にエラーが発生します。
ラムダ式が(メソッド呼出し式(15.12)によって)呼び出されると、実引数式の値によって、それぞれ宣言型または推測型で新たに作成されたパラメータ変数がラムダ本体の実行前に初期化されます。LambdaParameterに出現する、あるいはLambdaParameterListまたはLambdaParametersに出現するIdentifierはラムダ本体の単純名として、仮パラメータの参照に使用できます。
型float
のラムダ式の仮パラメータには常にfloat値セットの要素が含まれており(4.2.3)、同様に型double
のラムダ式の仮パラメータには常にdouble値セットの要素が含まれています。型float
のラムダ式の仮パラメータにfloat値セットの要素ではないfloat-extended-exponent値セットの要素を含めることはできず、型double
のラムダ式の仮パラメータにdouble値セットの要素ではないdouble-extended-exponent値セットの要素を含めることはできません。
15.27.2 ラムダ本体
ラムダ本体は単一式またはブロック(14.2)です。メソッド本体と同様に、ラムダ本体は呼出しが発生したときに実行されるコードを記述します。
- LambdaBody:
- Expression
- Block
匿名クラス宣言に出現するコードとは異なり、ラムダ本体に出現する名前とthis
およびsuper
キーワードの意味、および参照される宣言のアクセシビリティは、(新しい名前を導入するラムダ・パラメータを除き)その周辺のコンテキストと同じです。
ラムダ式の本体の
this
も、その透過性(明示的でも暗黙的でも)によって周辺のコンテキストと同じように扱い、柔軟な実装を可能にし、本体内の未修飾名の意味がオーバーロードの解決に依存しないようにできます。
実質的に、ラムダ式が(それ自体を再帰的に呼び出す、またはその別のメソッドを呼び出すためにも)そのラムダ式自体に指示を出すことは正常ではなく、包含クラス内のものを参照するための名前(
this
,toString()
)を使用し、そうでなければシャドウ化されることが一般的です。ラムダ式でそれ自体を(this
などによって)参照する必要がある場合は、メソッド参照または匿名内部クラスをかわりに使用します。
ブロック内のすべての戻り文の形式がreturn;
の場合、ブロックのラムダ本体はvoid-compatibleです。
正常に終了できず(14.21)、ブロック内のすべての戻り文の形式がreturn
Expression;
の場合、ブロックのラムダ本体はvalue-compatibleです。
ブロックのラムダ本体がvoid互換またはvalue互換ではない場合、コンパイル時にエラーが発生します。
value互換のブロックのラムダ本体では、result expressionsは呼出しの値を生成する任意の式です。具体的には、本体に含まれる形式return
Expression ;
のそれぞれの文では、Expressionが結果の式になります。
次のラムダ本体はvoid互換です。
() `->` {} () `->` { System.out.println("done"); }
これらはvalue互換です。
() `->` { return "done"; } () `->` { if (...) return 1; else return 0; }
これらは両方です。
() `->` { throw new RuntimeException(); } () `->` { while (true); }
これはいずれでもありません。
() `->` { if (...) return "done"; System.out.println("done"); }
void/value互換と本体内の名前の意味を同時に扱うことによって、一定のコンテキストでの特定のターゲット型への依存を抑えることができ、実装とプログラマの理解に役立てることができます。ターゲット型に応じて、オーバーロード解決中に式を様々な型に割り当てることができますが、未修飾名の意味とラムダ本体の基本の構造は変わりません。
void/value互換の定義は厳密に構築されたプロパティではなく、定数式の値によっては「正常に完了できる」ものであり、定数変数を参照する名前を含む場合があります。
ラムダ式で使用され、宣言されていないローカル変数、仮パラメータ、または例外パラメータはすべて、final
として宣言するか、実質的にfinal (4.12.4)である必要があります。そうでない場合に使用すると、コンパイル時にエラーが発生します。
ラムダ式で使用されており、宣言されていないローカル変数はすべて、ラムダ本体の前に明確に割り当てます(16)。そうでない場合、コンパイル時にエラーが発生します。
同様の変数のルールも、内部クラスの本体で適用されます(8.1.3)。実質的にfinalの変数に対する制限によって、動的に変化するローカル変数へのアクセスが禁じられています。このキャプチャによって同時実効性の問題が生じることがあります。
final
の制限と比較すると、プログラマの事務的な負担が減少します。
実質的にfinalの変数の制限には、標準loop変数が含まれますが、拡張
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) } }
この一部は、6.5.6.1に移動して使用できます。
第16章: 明確な割当て
16.2 明確な割当ておよび文
16.2.3 ローカル・クラス宣言文およびインタフェース宣言
- Vがローカル・クラスまたはインタフェース宣言
文の前に割り当てられている[いない]場合、Vはローカル・クラス変数またはインタフェース宣言文(14.3)の後に割り当てられています[いません]。