第8章 クラス

目次

8.1 クラス宣言
8.1 クラス修飾子
8.1.1.1. abstractクラス
8.1.1.2。sealednon-sealedおよびfinalクラス
8.1.1.3. strictfpクラス
8.1.1.4. staticクラス
8.1 汎用クラスおよび型パラメータ
8.1 内部クラスおよび包含インスタンス
8.1 スーパークラスとサブクラス
8.1 スーパーインタフェース
8.1 許可された直接サブクラス
8.1 クラス本体およびメンバーの宣言
8.1 暗黙的に宣言されたクラス
8.2 クラス・メンバー
8.3 フィールド宣言
8.3 フィールド修飾子
8.3.1.1. staticフィールド
8.3.1.2. finalフィールド
8.3.1.3. transientフィールド
8.3.1.4. volatileフィールド
8.3 フィールド初期化
8.3 初期化子でのフィールド参照の制限
8.4 メソッド宣言
8.4 正式なパラメータ
8.4 メソッド署名
8.4 メソッド修飾子
8.4.3.1. abstractメソッド
8.4.3.2. staticメソッド
8.4.3.3. finalメソッド
8.4.3.4. nativeメソッド
8.4.3.5. strictfpメソッド
8.4.3.6. synchronizedメソッド
8.4 汎用メソッド
8.4 メソッド結果
8.4 メソッド・スロー
8.4 メソッド本体
8.4 継承、オーバーライドおよび非表示
8.4.8.1. (インスタンス・メソッドによる)オーバーライド
8.4.8.2. 非表示(クラス・メソッド別)
8.4.8.3. オーバーライドおよび非表示の要件
8.4.8.4. Override-Equivalentシグネチャを使用したメソッドの継承
8.4 オーバーロード
8.5 メンバー・クラスおよびインタフェース宣言
8.6 インスタンス初期化子
8.7 静的イニシャライザ
8.8 コンストラクタ宣言
8.8 正式なパラメータ
8.8 コンストラクタの署名
8.8 コンストラクタ修飾子
8.8 汎用コンストラクタ
8.8 コンストラクタのスロー
8.8 コンストラクタのタイプ
8.8 コンストラクタ本体
8.8.7.1. コンストラクタ呼出し
8.8 コンストラクタのオーバーロード
8.8 デフォルト・コンストラクタ
8.8 クラスのインスタンス化の防止
8.9 列挙クラス
8.9 列挙定数
8.9 列挙ボディ宣言
8.9 列挙メンバー
8.1 レコード・クラス
8.1 レコード・コンポーネント
8.1 レコード本文宣言
8.1 レコード・メンバー
8.1 コンストラクタ宣言の記録
8.10.4.1. 通常の正規コンストラクタ
8.10.4.2. コンパクトな正規コンストラクタ

クラス宣言は、新しいクラスを定義し、その実装方法を記述します(§8.1)。

最上位クラス(§7.6)は、コンパイル単位で直接宣言されたクラスです。

ネストされたクラスは、別のクラスまたはインタフェース宣言の本体内で宣言が発生する任意のクラスです。 ネストされたクラスは、メンバー・クラス(§8.5§9.5)、ローカル・クラス(§14.3)、または匿名クラス(§15.9.5)です。

ネストされたクラスは、内部 (§8.1.3)である可能性があり、その場合、(宣言の出現場所に応じて)包含クラスインスタンス、ローカル変数、および型変数を参照できる場合があります。

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

レコード・クラス(§8.10)は、値の単純な集計を定義する省略構文で宣言されたクラスです。

この章では、すべてのクラスの共通セマンティクスについて説明します。 特定の種類のクラスに固有の詳細については、それらの構造専用の項で説明します。

クラスはpublic (§8.1.1)と宣言され、そのモジュールの任意のパッケージ内のコードから参照でき、他のモジュールのコードから参照できる可能性があります。

クラスはabstract (§8.1.1.1)として宣言され、完全に実装されていない場合はabstractを宣言する必要があります。このようなクラスはインスタンス化できませんが、サブクラスによって拡張できます。 クラスを拡張できる程度は、明示的に制御できます(§8.1.1.2)。つまり、サブクラスを制限するためにsealedを宣言することも、サブクラスがないようにfinalを宣言することもできます。 Objectを除く各クラスは、単一の既存のクラス(§8.1.4)の拡張(つまり、サブクラス)であり、インタフェース(§8.1.5)を実装できます。

クラスは generic (§8.1.2)である可能性があります。つまり、その宣言では、クラスの異なるインスタンス間でバインディングが異なる型変数を導入できます。

クラス宣言は、他の種類の宣言と同様に、注釈(§9.7)で装飾できます。

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

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

メンバー・クラス宣言(§8.5)は、周囲のクラスのメンバーであるネストされたクラスを記述します。 メンバー・クラスは、static (その場合、周囲のクラスのインスタンス変数にアクセスできない)または内部クラスである場合があります。

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

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

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

インスタンス・イニシャライザ(§8.6)は、インスタンスの作成時にインスタンスの初期化に役立つ実行可能コードのブロックです(§15.9)。

静的イニシャライザ(§8.7)は、クラスの初期化に役立つ実行可能コードのブロックです。

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

8.1.  クラス宣言

クラス宣言は、クラスを指定します。

クラス宣言には、通常のクラス宣言列挙宣言(§8.9)、およびレコード宣言(§8.10)の3種類があります。

一部のクラスは、他の構文によって暗黙的に宣言されます(§8.1.8)。

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

これは、クラスがそれを囲むクラスまたはインタフェースと同じ単純名を持つ場合のコンパイル時のエラーです。

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

8.1.1.  クラス修飾子

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

ClassModifier:
(いずれか)
注釈 public protected private
abstract static final sealed non-sealed strictfp

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

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

アクセス修飾子protectedおよびprivateは、メンバー・クラスにのみ関係します。

修飾子staticは、メンバー・クラスおよびローカル・クラスにのみ関係します。

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

クラス宣言に複数の修飾子sealednon-sealedおよびfinalがある場合、コンパイル時にエラーが発生します。

2つ以上の(個別)クラス修飾子がクラス宣言に表示される場合、ClassModifierの本番で前述したものと一致する順序で表示されることは、必須ではありませんが慣例です。

8.1.1.1. abstractクラス

abstractクラスは、不完全または不完全とみなされるクラスです。

クラス・インスタンス作成式(§15.9.1)を使用してabstractクラスのインスタンスを作成しようとすると、コンパイル時にエラーが発生します。

abstractではないabstractクラスのサブクラスをインスタンス化すると、abstractクラスのコンストラクタが実行されるため、そのクラスのインスタンス変数に対してフィールド・イニシャライザが実行されます。

通常のクラスには、abstractクラスの場合のみ、abstractメソッド、つまり宣言されているがまだ実装されていないメソッド(§8.4.3.1)を含めることができます。 abstractではない通常のクラスにabstractメソッドがある場合、コンパイル時にエラーが発生します。

次のいずれかに該当する場合、クラスCにはabstractメソッドがあります。

  • Cのメンバー・メソッド(§8.2) (宣言または継承)のいずれかは、abstractです。

  • Cのスーパークラスには、パッケージ・アクセスで宣言されたabstractメソッドがあり、CまたはCのスーパークラスからabstractメソッドをオーバーライドするメソッドはありません。

すべてのabstractメソッドを実装するサブクラスを作成できないように、abstractクラス型を宣言するのはコンパイル時エラーです。 この状況は、クラスが同じメソッド・シグネチャ(§8.4.2)を持つ2つのabstractメソッドをメンバーとして持つが、どちらの型も戻り型置換可能でない戻り型(§8.4.5)を持つ場合に発生することがあります。

例 8.1.1.1-1. 抽象クラス宣言

abstract class Point {
    int x = 1, y = 1;
    void move(int dx, int dy) {
        x += dx;
        y += dy;
        alert();
    }
    abstract void alert();
}
abstract class ColoredPoint extends Point {
    int color;
}
class SimplePoint extends Point {
    void alert() { }
}

ここでは、alertという名前のabstractメソッドの宣言が含まれているため、abstractを宣言する必要があるクラスPointが宣言されています。 ColoredPointという名前のPointのサブクラスは、abstractメソッドalertを継承するため、abstractも宣言する必要があります。 一方、SimplePointという名前のPointのサブクラスは、alertの実装を提供するため、abstractである必要はありません。

次の文を発行した場合:

Point p = new Point();

コンパイル時にエラーが発生します。クラスPointabstractであるため、インスタンス化できません。 ただし、Point変数は、Pointのサブクラスへの参照を使用して正しく初期化でき、クラスSimplePointabstractではないため、文は次のようになります。

Point p = new SimplePoint();

正しいでしょう。 SimplePointをインスタンス化すると、Pointxおよびyのデフォルトのコンストラクタおよびフィールド・イニシャライザが実行されます。


例 8.1.1.1-2. サブクラスを禁止する抽象クラス宣言

interface Colorable {
    void setColor(int color);
}
abstract class Colored implements Colorable {
    public abstract int setColor(int color);
}

これらの宣言はコンパイル時にエラーが発生します。クラスColoredのサブクラスが、int型の引数を1つ取るsetColorという名前のメソッドの実装を提供することは不可能です。abstractメソッドの指定。これは、インタフェースColorable内のメソッドが値を返さないために同じメソッドを必要とし、クラスColored内のメソッドがint型の値を返すのに同じメソッドを必要とするためです(§8.4)。


クラス型は、実装を完了するためにサブクラスを作成できる場合にのみ、abstractを宣言する必要があります。 単にクラスのインスタンス化を防止する目的の場合、これを表現する適切な方法は、引数のないコンストラクタ(§8.8.10)を宣言し、privateにし、そのコンストラクタを呼び出さないようにし、他のコンストラクタを宣言することです。 通常、この形式のクラスには、クラス・メソッドおよび変数が含まれます。

クラスMathは、インスタンス化できないクラスの例です。その宣言は次のようになります。


public final class Math {
    private Math() { }  // never instantiate this class
    . . . declarations of class variables and methods . . .
}

8.1.1.2。sealednon-sealedおよびfinalクラス

クラスが宣言されるときにすべての直接サブクラスが認識され(§8.1.6)、他の直接サブクラスが望まれないか要求されない場合は、クラスをsealedとして宣言できます。

クラスの直接サブクラスに対する明示的かつ徹底的な制御は、コード継承および再利用のメカニズムとしてではなく、クラス階層を使用してドメイン内の値の種類をモデル化する場合に便利です。 直接サブクラス自体は、クラス階層をさらに制御するためにsealedとして宣言できます。

クラスは、その定義が完全であり、サブクラスが望ましくない場合や必要ない場合にfinalを宣言できます。

クラスがfinalabstractの両方で宣言されている場合、コンパイル時にエラーが発生します。これは、このようなクラスの実装を完了できないためです(§8.1.1.1)。

finalクラスにはサブクラスがないため、finalクラスのメソッドはオーバーライドされません(§8.4.8.1)。

直接スーパークラスがsealed (§8.1.4)でなく、その直接スーパーインタフェースがsealed (§8.1.5)でなく、sealedでもfinal自体でもない場合、クラスは完全に拡張可能です。

sealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェースを持つクラスは、non-sealedと宣言されている場合にのみ自由に拡張できます。

クラスにsealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェースがあり、finalsealedまたはnon-sealedが明示的にまたは暗黙的に宣言されていない場合、コンパイル時にエラーが発生します。

したがって、sealedキーワードの効果は、すべての直接サブクラスに、finalsealedまたはnon-sealedのいずれであるかが明示的に宣言されるように強制することです。 これにより、シール済クラス階層を不要なサブクラス化に誤って公開することを回避できます。

enumクラスは、暗黙的にfinalまたは暗黙的にsealedであるため、sealedインタフェースを実装できます。 同様に、レコード・クラスは暗黙的にfinalであるため、シール済インタフェースを実装することもできます。

クラスがnon-sealedとして宣言されているが、sealedダイレクト・スーパークラスもsealedダイレクト・スーパーインタフェースもない場合、コンパイル時にエラーが発生します。

したがって、non-sealedクラスのサブクラス自体をnon-sealedとして宣言することはできません。

8.1.1.3. strictfpクラス

クラス宣言のstrictfp修飾子は廃止されており、新しいコードでは使用しないでください。 その存在または不在は、コンパイル時または実行時に影響しません。

8.1.1.4. staticクラス

static修飾子は、ネストされたクラスが内部クラスでないことを指定します(§8.1.3)。 クラスのstaticメソッドが、その本体内にクラスの現在のインスタンスを持たないのと同様に、ネストされたstaticクラスには、その本体内に即時に包含するインスタンスはありません。

staticネストされたクラスから型パラメータ、インスタンス変数、ローカル変数、仮パラメータ、例外パラメータ、または字句的に包含するクラス、インタフェースまたはメソッド宣言のインスタンス・メソッドへの参照は許可されません(§6.5.5.1§6.5.6.1および§15.12.3)。

static修飾子は、すべてのネストされたクラスに関係するわけではありません。 これは、宣言でstatic修飾子を使用できるメンバー・クラスにのみ関係し、宣言でstatic修飾子を使用できないローカル・クラスまたは匿名クラスには関係しません(§14.3§15.9.5)。 ただし、すべてのネストされた列挙クラスおよびネストされたレコード・クラスは暗黙的にstaticであるため、一部のローカル・クラスは暗黙的にstatic、つまりローカル列挙クラスおよびローカル・レコード・クラスです(§8.9§8.10)。

8.1.2.  汎用クラスおよび型パラメータ

クラス宣言で1つ以上の型変数が宣言されている場合、クラスは汎用です(§4.4)。

これらの型変数は、クラスの型パラメータと呼ばれます。 タイプ・パラメータ・セクションは、クラス名の後に山カッコで区切られます。

TypeParameters:
TypeParameterList:

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

TypeParameterModifier:
TypeBound:
AdditionalBound:

型パラメータ宣言の注釈修飾子に関する規則は、§9.7.4および§9.7.5に記載されています。

クラスの型パラメータ・セクションで、型変数T直接依存は型変数S(STの境界である場合)に依存し、TS(TSに直接依存している場合)またはTのいずれかがSに直接依存している場合(この定義を再帰的に使用する場合)は、Sに依存する型変数Uに依存します。

クラス型パラメータ・セクションの型変数がそれ自体に依存している場合は、コンパイル時エラーです。

クラスの型パラメータのスコープとシャドウ化は、§6.3および §6.4.1で規定されています。

静的コンテキストまたはネストされたクラスまたはインタフェースからのクラスの型パラメータへの参照は、§6.5.5.1で指定されているように制限されます。

汎用クラス宣言は、パラメータ化された型のセット(§4.5)を定義し、型引数による型パラメータ・セクションの可能なパラメータ化ごとに1つずつ定義します。 これらのパラメータ化された型はすべて、実行時に同じクラスを共有します。

たとえば、次のコードを実行します:


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 Virtual Machineのキャッチ・メカニズムは非汎用クラスでのみ機能するため、この制限が必要です。

例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>(
                Integer.valueOf(1),
                new Seq<Number>(Double.valueOf(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)

次のネストされたクラスは暗黙的にstaticであるため、内部クラスではありません。

  • メンバー列挙クラス(§8.9)

  • ローカル列挙クラス(§14.3)

  • メンバー・レコード・クラス(§8.10)

  • ローカル・レコード・クラス(§14.3)

  • インタフェースのメンバー・クラス(§9.5)

ネストされたクラスに適用されるルールはすべて、内部クラスに適用されます。 特に、内部クラスは、内部クラス自体がstaticでない場合でも、staticメンバー(§8.2)を宣言および継承し、静的イニシャライザ(§8.7)を宣言できます。

すべてのネストされたインタフェースは暗黙的にstatic (§9.1.1.3)であるため、「内部インタフェース」はありません。

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

class HasStatic {
    static int j = 100;
}

class Outer {
    class Inner extends HasStatic {
        static {
            System.out.println("Hello from Outer.Inner");
        }

        static       int x = 3;
        static final int y = 4;

        static void hello() {
            System.out.println("Hello from Outer.Inner.hello");
        }

        static class VeryNestedButNotInner
            extends NestedButNotInner {}
    }

    static class NestedButNotInner {
        int z = Inner.x;
    }

    interface NeverInner {}  // Implicitly static, so never inner
}

Java SE 16より前は、内部クラスは静的イニシャライザを宣言できず、定数変数であるstaticメンバーのみを宣言できました(§4.12.4)。


構成(文、ローカル変数宣言文、ローカル・クラス宣言、ローカル・インタフェース宣言または式)は、静的コンテキストで発生します(最も内側の場合)。

  • メソッド宣言、

  • フィールド宣言、

  • コンストラクタ宣言、

  • インスタンス・イニシャライザ、または

  • static初期化子

この構成要素を含むのは、次のいずれかです。

なお、コンストラクタ宣言またはインスタンス・イニシャライザにおいて出現する構成要素は、静的コンテキストにおいては出現しません。

静的コンテキストの目的は、宣言が静的コンテキストを字句的に包含するクラスの現在のインスタンスが定義されていないコードを境界指定することです。 したがって、静的コンテキストにおいて出現するコードには、次のような制約があります。

  • this式(非修飾と修飾の両方)は許可されません(§15.8.3§15.8.4)。

  • フィールド・アクセス、メソッド呼出しおよびメソッド参照は、superによって修飾されない場合があります(§15.11.2§15.12.3§15.13.1)。

  • 字句的に包含するクラスまたはインタフェース宣言のインスタンス変数への非修飾参照は許可されません(§6.5.6.1)。

  • 字句的に包含するクラスまたはインタフェース宣言のインスタンス・メソッドの非修飾呼出しは許可されません(§15.12.3)。

  • 字句的に包含するクラスまたはインタフェース宣言の型パラメータへの参照は許可されません(§6.5.5.1)。

  • 直近のクラスまたはインタフェース宣言の外部にある字句的に包含するクラスまたはインタフェース宣言のメソッドまたはコンストラクタによって宣言された型パラメータ、ローカル変数、仮パラメータおよび例外パラメータへの参照は許可されません(§6.5.5.1§6.5.6.1)。

  • ローカル正規クラス(ローカル列挙クラスとは対照的に)の宣言と無名クラスの宣言は、どちらも内部クラスを指定しますが、インスタンス化時に直ちに包含するインスタンスはありません(§15.9.2)。

  • 内部メンバー・クラスをインスタンス化するクラス・インスタンス作成式は、修飾される必要があります(§15.9)。

内部クラスCクラスまたはインタフェースOの直接内部クラスです。OCの直近のクラスまたはインタフェース宣言で、Cの宣言が静的コンテキストで発生しない場合です。

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

クラス Cは、クラスまたはインタフェース Oの内部クラス(Oの直接内部クラス、または Oの内部クラスの内部クラス)です。

内部クラスのクラスまたはインタフェース宣言をすぐに囲むことは異常ですが、インタフェースになる可能性があります。 これは、クラスがdefaultまたはstaticメソッド本体(§9.4)で宣言されたローカル・クラスまたは匿名クラスである場合にのみ発生します。

クラスまたはインタフェース Oは、ゼロの字句で囲まれたクラスまたはそれ自体のインタフェース宣言です。

クラスOは、クラスCn-1の字句的に包含するクラス宣言のCn番目の字句で包含するクラス宣言である場合、そのクラスCのクラス宣言です。

クラスまたはインタフェースOの直接内部クラスCのインスタンスiは、Oのインスタンス(iの即時包含インスタンス)に関連付けることができます。 オブジェクト(存在する場合)の直近の包含インスタンスは、オブジェクトの作成時に決定されます(§15.9.2)。

オブジェクトoは、それ自体を字句的に包含するインスタンスです。

オブジェクトoが、in-1の字句的に囲まれたiインスタンスの直近の包含インスタンスである場合、オブジェクトon番目の字句で囲まれているインスタンスです。

宣言が静的コンテキストにおいて出現する内部ローカル・クラスまたは無名クラスのインスタンスには、直接包含インスタンスはありません。 また、staticネスト・クラスのインスタンス(§8.1.1.4)には、すぐに包含されるインスタンスはありません。

CのすべてのスーパークラスS(それ自体がクラスまたはインタフェースSOの直接内部クラス)について、iに関連付けられたSOのインスタンスがあります。これは、Sに関してiの即時包含インスタンスと呼ばれます。 クラスの直接スーパークラス(存在する場合)に関するオブジェクトの直近の包含インスタンスは、スーパークラス・コンストラクタがコンストラクタ呼出し(§8.8.7.1)を介して呼び出されたときに決定されます。

内部クラスに、字句として包含するクラスまたはインタフェースの宣言のメンバーであるインスタンス変数への有効な参照が含まれている場合は、対応する字句的に包含するインスタンスの変数が使用されます。

内部クラスで使用されているが宣言されていないローカル変数、仮パラメータまたは例外パラメータは、§6.5.6.1で指定されているとおり、finalまたは実質的にfinal (§4.12.4)である必要があります。

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

ラムダ式の本体(§15.27.2)には、変数の使用に関する同様の規則が適用されます。

字句的に囲まれているクラスまたはインタフェース宣言の空白のfinalフィールド(§4.12.4)は、内部クラス内で割り当てられないか、コンパイル時にエラーが発生します。

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

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

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

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

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

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

ここで、WithDeepNesting.Nested.DeeplyNestedのすべてのインスタンスには、クラスWithDeepNesting.Nestedの包含インスタンス(即時に包含するインスタンス)とクラスWithDeepNestingの包含インスタンス(2番目の字句的に包含するインスタンス)があります。


8.1.4.  スーパークラスおよびサブクラス

通常のクラス宣言のオプションのextends句では、宣言するクラスのダイレクト・スーパークラス型を指定します。

ClassExtends:
extends ClassType

extends句は、Objectクラスの定義内に指定しないでください。指定しないと、コンパイル時にエラーが発生します。これは、この句が初期クラスであり、直接スーパークラス型を持たないためです。

ClassTypeは、アクセス可能なクラス(§6.6)に名前を付ける必要があります。そうしないと、コンパイル時にエラーが発生します。

ClassTypesealed (§8.1.1.2)のクラスに名前を付け、宣言されているクラスが指定されたクラスの許可された直接サブクラスでない場合(§8.1.6)、コンパイル時にエラーが発生します。

ClassTypefinalのクラスに名前を付けると、コンパイル時にエラーが発生します。これは、finalクラスがサブクラスを持つことが許可されないためです(§8.1.1.2)。

ClassTypeがenumクラス(§8.9)によってのみ拡張できるクラスEnumに名前を付ける場合、またはレコード・クラス(§8.10)によってのみ拡張できるクラスRecordに名前を付ける場合、コンパイル時にエラーが発生します。

ClassTypeに型引数がある場合、整形式のパラメータ化された型(§4.5)を示す必要があり、型引数にワイルドカード型引数を指定できないか、コンパイル時にエラーが発生します。

宣言にextends句がないクラスの直接スーパークラス型は、次のとおりです。

  • クラスObjectには、直接スーパークラス型はありません。

  • 通常のクラス宣言を持つObject以外のクラスの場合、直接スーパークラス型はObjectです。

  • 列挙クラスEの場合、直接スーパークラス型はEnum<E>です。

  • レコード・クラスRの場合、直接スーパークラス・タイプはRecordです。

  • 匿名クラスの場合、直接スーパークラス型は§15.9.5で定義されています。

クラスのダイレクト・スーパークラスは、そのダイレクト・スーパークラス・タイプによって命名されたクラスです。 直接スーパークラスは、宣言されているクラスの実装を導出するためにその実装が使用されるため、重要です。

スーパークラス関係は、直接スーパークラス関係の推移的クローズです。 次のいずれかが当てはまる場合、クラス Aはクラス Cのスーパークラスです。

  • ACの直接スーパークラスです

  • クラス BCの直接スーパークラスである場合、ABのスーパークラスであり、この定義を再帰的に適用します。

クラスは、その直接スーパークラスの直接サブクラスであり、各スーパークラスのサブクラスであると言われています。

例8.1.4-1.  直接スーパークラスおよびサブクラス

class Point { int x, y; }
final class ColoredPoint extends Point { int color; }
class Colored3DPoint extends ColoredPoint { int z; }  // error

この場合、関係は次のようになります:

  • クラスPointは、Objectの直接サブクラスです。

  • クラスObjectは、クラスPointの直接スーパークラスです。

  • クラスColoredPointは、クラスPointの直接サブクラスです。

  • クラスPointは、クラスColoredPointの直接スーパークラスです。

クラスColored3dPointの宣言により、最終クラスColoredPointの拡張が試行されるため、コンパイル時にエラーが発生します。


例8.1.4-2.  スーパークラスおよびサブクラス

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
final class Colored3dPoint extends ColoredPoint { int z; }

この場合、関係は次のようになります:

  • クラスPointは、クラスColoredPointのスーパークラスです。

  • クラスPointは、クラスColored3dPointのスーパークラスです。

  • クラスColoredPointは、クラスPointのサブクラスです。

  • クラスColoredPointは、クラスColored3dPointのスーパークラスです。

  • クラスColored3dPointは、クラスColoredPointのサブクラスです。

  • クラスColored3dPointは、クラスPointのサブクラスです。


クラスCは、クラスまたはインタフェースAに直接依存します。Aが、スーパークラスまたはスーパーインタフェースとして、あるいはスーパークラスまたはスーパーインタフェース名の完全修飾形式の修飾子としてCextends句またはimplements句で指定されている場合です。

次のいずれかに該当する場合、クラスCはクラスまたはインタフェースAに依存します。

  • CAに直接依存する。

  • Cは、Aに依存するインタフェース I (§9.1.3)に直接依存します。

  • Cが、Aに依存するクラスBに直接依存する(この定義を再帰的に適用)。

クラスがそれ自体に依存する場合は、コンパイル時にエラーが発生します。

循環宣言されたクラスが実行時に検出され、クラスがロードされると、ClassCircularityErrorがスローされます(§12.2.1)。

例8.1.4-3.  それ自体に依存するクラス

class Point extends ColoredPoint { int x, y; }
class ColoredPoint extends Point { int color; }

このプログラムでは、クラスPointがそれ自体に依存するため、コンパイル時にエラーが発生します。


8.1.5.  スーパーインタフェース

クラス宣言のオプションのimplements句では、宣言するクラスのダイレクト・スーパーインタフェース型を指定します。

ClassImplements:
implements InterfaceTypeList
InterfaceTypeList:

InterfaceTypeはアクセス可能なインタフェースに名前を付ける必要があります(§6.6)。そうしないと、コンパイル時にエラーが発生します。

いずれかのInterfaceTypesealed (§9.1.1.4)のインタフェースに名前を付け、宣言されているクラスが指定されたインタフェースの許可された直接サブクラスでない場合(§9.1.4)、コンパイル時にエラーが発生します。

InterfaceTypeに型引数がある場合、整形式のパラメータ化された型(§4.5)を示す必要があり、どの型引数もワイルドカード型引数にできないか、コンパイル時にエラーが発生します。

1つのimplements句で同じインタフェースが直接スーパーインタフェース・タイプによって複数回指定されている場合、コンパイル時にエラーが発生します。 これは、インタフェースに異なる方法で名前が付けられている場合でも当てはまります。

例8.1.5-1.  不正なスーパーインタフェース

class Redundant implements java.lang.Cloneable, Cloneable {
    int x;
}

java.lang.CloneableおよびCloneableという名前が同じインタフェースを参照しているため、このプログラムではコンパイル時にエラーが発生します。


宣言にimplements句がないクラスには、直接スーパーインタフェース型はありません。ただし、匿名クラスにはスーパーインタフェース型がある場合があります(§15.9.5)。

インタフェースがクラスの直接スーパーインタフェース・タイプの1つによって指定されている場合、インタフェースはクラスの直接スーパーインタフェースです。

インタフェースIは、次のいずれかが当てはまる場合に、クラスCスーパーインタフェースです。

  • ICの直接スーパーインタフェースである。

  • Cには、Iがスーパーインタフェースである直接スーパーインタフェース Jがいくつかあり、§9.1.3で示されている「インタフェースのスーパーインタフェース」の定義を使用します。

  • ICの直接スーパークラスのスーパーインタフェースである。

クラスは、複数の方法でスーパー・インタフェースを持つことができます。

クラスは、その直接スーパーインタフェースを直接実装し、そのすべてのスーパーインタフェースを実装します。

クラスは、その直接スーパーインタフェースの直接サブクラスであり、そのすべてのスーパーインタフェースのサブクラスです。

クラスは、直接スーパークラス型と直接スーパーインタフェース型、または同じ汎用インタフェース(§9.1.2)の異なるパラメータ化であるスーパータイプ(§4.10.2)を持つ2つの直接スーパーインタフェース型を宣言したり、同じ汎用インタフェースである汎用インタフェースとRAW型ネーミングのパラメータ化を宣言することはできません。 このような競合の場合は、コンパイル時にエラーが発生します。

この要件は、型消去による翻訳をサポートするために導入されました(§4.6)。

例8.1.5-2.  スーパーインタフェース

interface Colorable {
    void setColor(int color);
    int getColor();
}
enum Finish { MATTE, GLOSSY }
interface Paintable extends Colorable {
    void setFinish(Finish finish);
    Finish getFinish();
}

class Point { int x, y; }
class ColoredPoint extends Point implements Colorable {
    int color;
    public void setColor(int color) { this.color = color; }
    public int getColor() { return color; }
}
class PaintedPoint extends ColoredPoint implements Paintable {
    Finish finish;
    public void setFinish(Finish finish) {
        this.finish = finish;
    }
    public Finish getFinish() { return finish; }
}

この場合、関係は次のようになります:

  • インタフェースPaintableは、クラスPaintedPointのスーパーインタフェースです。

  • インタフェースColorableは、クラスColoredPointおよびクラスPaintedPointのスーパーインタフェースです。

  • インタフェースPaintableは、インタフェースColorableのサブインタフェースであり、Colorableは、§9.1.3で定義されているPaintableのスーパーインタフェースです。

クラスPaintedPointは、ColoredPointのスーパーインタフェースであり、Paintableのスーパーインタフェースであるため、どちらもスーパーインタフェースとしてColorableを持ちます。


例8.1.5-3.  不正なインタフェースの複数継承

interface I<T> {}
class B implements I<Integer> {}
class C extends B implements I<String> {}

クラスCは、I<Integer>とI<String>の両方のサブタイプになるため、コンパイル時にエラーが発生します。


宣言されているクラスがabstractでないかぎり、各直接スーパーインタフェースのすべてのabstractメンバー・メソッドは、このクラスの宣言、または直接スーパークラスまたは直接スーパーインタフェースから継承された既存のメソッド宣言のいずれかによって実装する必要があります(§8.4.8.1)。これは、abstractでないクラスがabstractメソッドを持つことが許可されないためです(§8.1.1.1)。

クラスのスーパーインタフェースの各デフォルト・メソッド(§9.4.3)は、オプションでクラス内のメソッドによってオーバーライドできます。そうでない場合は、通常、デフォルト・メソッドが継承され、その動作はデフォルト本文で指定されます。

クラス内の単一のメソッド宣言では、複数のスーパー・インタフェースのメソッドを実装できます。

例8.1.5-4.  スーパーインタフェースのメソッドの実装

interface Colorable {
    void setColor(int color);
    int getColor();
}
class Point { int x, y; };
class ColoredPoint extends Point implements Colorable {
    int color;
}

このプログラムでは、ColoredPointabstractクラスではなく、インタフェースColorableのメソッドsetColorおよびgetColorの実装を提供できないため、コンパイル時にエラーが発生します。

次のプログラムでは:

interface Fish  { int getNumberOfScales(); }
interface Piano { int getNumberOfScales(); }
class Tuna implements Fish, Piano {
    // You can tune a piano, but can you tuna fish?
    public int getNumberOfScales() { return 91; }
}

クラスTunaのメソッドgetNumberOfScalesには、インタフェースFishで宣言されたメソッドと一致し、インタフェースPianoで宣言されたメソッドと一致する名前、シグネチャおよび戻り型があり、両方を実装するとみなされます。

一方、次のような状況では:

interface Fish       { int    getNumberOfScales(); }
interface StringBass { double getNumberOfScales(); }
class Bass implements Fish, StringBass {
    // This declaration cannot be correct,
    // no matter what type is used.
    public ?? getNumberOfScales() { return 91; }
}

シグネチャおよび戻り型がインタフェースFishおよびインタフェースStringBassで宣言された両方のメソッドと互換性があるgetNumberOfScalesという名前のメソッドを宣言することはできません。これは、クラスが同じシグネチャおよび異なるプリミティブ戻り型を持つ複数のメソッドを持つことができないためです(§8.4)。 したがって、単一のクラスがインタフェースFishとインタフェースStringBassの両方を実装することは不可能です(§8.4.8)。


8.1.6.  許容された直接サブクラス

通常のクラス宣言のオプションのpermits句では、宣言されるクラスの直接サブクラス(§8.1.1.2)として意図されるすべてのクラスを指定します。

ClassPermits:
permits TypeName {, TypeName}

クラス宣言にpermits句があり、sealed修飾子がない場合、コンパイル時にエラーが発生します。

すべてのTypeNameはアクセス可能なクラスに名前を付ける必要があります(§6.6)。そうしないと、コンパイル時にエラーが発生します。

permits句で同じクラスが複数回指定されている場合、コンパイル時にエラーが発生します。 これは、クラスに異なる方法で名前が付けられている場合でも当てはまります。

クラスの正規名はpermits句で使用する必要はありませんが、permits句で指定できるのは1回のみです。 たとえば、次のプログラムはコンパイルできません。

package p;

sealed class A     permits B, C, p.B {}  // error

non-sealed class B extends A {}
non-sealed class C extends A {}

sealedクラスCが名前付きモジュール(§7.3)に関連付けられている場合、Cの宣言のpermits句で指定されたすべてのクラスをCと同じモジュールに関連付ける必要があります。そうしないと、コンパイル時にエラーが発生します。

sealedクラスCが無名モジュール(§7.7.5)に関連付けられている場合、Cの宣言のpermits句で指定されるすべてのクラスは、Cと同じパッケージに属している必要があります。そうでないと、コンパイル時にエラーが発生します。

sealedクラスとその直接サブクラスは、それぞれpermits句とextends句で、循環方式で相互を参照する必要があります。 したがって、モジュラー・コードベースでは、異なるモジュール内のクラスが相互を循環方式で参照できないため、同じモジュール内に配置する必要があります。 シール済クラス階層は常に単一のメンテナンス・ドメイン内で宣言する必要があり、同じ開発者または開発者のグループが階層の維持を担当するため、いずれの場合もコロケーションが望ましいです。 名前付きモジュールは通常、モジュラーコードベースの保守ドメインを表します。

sealedクラスCの宣言にpermits句がある場合、C許可された直接サブクラスは、permits句で指定されたクラスです。

permits句で指定されるすべての許可される直接サブクラスは、C (§8.1.4)の直接サブクラスである必要があります。そうしないと、コンパイル時にエラーが発生します。

sealedクラスCの宣言にpermits句がない場合、Cで許可される直接サブクラスは次のようになります。

  • Cがenumクラスでない場合、許可される直接サブクラスは、正規名(§6.7)を持ち、直接スーパークラスがCであるC (§7.3)と同じコンパイル・ユニットで宣言されるクラスです。

    つまり、許可される直接サブクラスは、Cを直接スーパークラスとして指定する同じコンパイルユニット内のクラスと見なされます。 正規名の要件は、ローカル・クラスまたは匿名クラスは考慮されないことを意味します。

    sealedクラスCの宣言にpermits句がなく、Cに許可された直接サブクラスがない場合、コンパイル時にエラーが発生します。

  • Cがenumクラスである場合、許可される直接サブクラス(ある場合)は§8.9で指定されます。

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

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

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

クラスCで宣言または継承されたメンバーmの宣言のスコープおよびシャドウ化は、§6.3および§6.4.1で指定されます。

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

8.1.8.  暗黙的に宣言されたクラス

すべてのクラスがクラス宣言によって示されるわけではありません。 次の構成要素により、クラスが暗黙的に宣言されます。

  • クラス本体で終わるクラス・インスタンス作成式(§15.9.5)

  • クラス本体で終わるenum定数(§8.9.1)

  • コンパクト・コンパイル・ユニット(§7.3)

どの場合も、暗黙的に宣言されたクラスのメンバー(暗黙的に宣言されたメンバーなど)は、クラス内のメンバー宣言に関する通常のルールの対象になります。

ここでは、便宜上、§7.3の次の本番環境を示します。

コンパクト・コンパイル・ユニットによって暗黙的に宣言されたクラスは、次のプロパティを満たします。

  • 最上位クラス(§7.6)です。

  • その名前は、ホスト・システムによって決定される有効な識別子(§3.8)です。

    コンパイル・ユニットがファイルに格納されるJava SE Platformの単純な実装では、この暗黙的に宣言されたクラスの名前は、通常、コンパクト・コンパイル・ユニットを含むファイルの名前から任意の拡張子(.java.javなど)を引いた名前になります。

  • abstract(§8.1.1.1)ではありません。

  • final (§8.1.1.2)です。

  • 名前のないパッケージ(§7.4.2)のメンバーであり、パッケージにアクセスできます。

  • その直接スーパークラスはObject (§8.1.4)です。

  • 直接スーパーインタフェースはありません(§8.1.5)。

  • クラスの本体には、コンパクト・コンパイル・ユニットで宣言されたすべてのクラス・メンバーが含まれます(これらはフィールド(§8.3)、メソッド(§8.4)、メンバー・クラス(§8.5)、およびメンバー・インタフェース(§9.1.1.3)の宣言です)。 コンパクト・コンパイル・ユニットには、インスタンス・イニシャライザ(§8.6)、静的イニシャライザ(§8.7)、またはコンストラクタ(§8.8)の宣言を含めることはできません。

  • 暗黙的に宣言されたデフォルト・コンストラクタ(§8.8.9)があります。

このクラスが少なくとも1つの候補mainメソッド(§12.1.4)を宣言しない場合、コンパイル時にエラーが発生します。

なお、名前のないパッケージには、複数の暗黙的に宣言されたクラスがメンバーとして含まれている場合があります。

8.2. クラス・メンバー

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

  • 直接スーパークラス型を持たないクラスObjectを除き、直接スーパークラス型(§8.1.4)から継承されたメンバー

  • 任意の直接スーパーインタフェース型から継承されたメンバー(§8.1.5)

  • クラスの本体で宣言されたメンバー(§8.1.7)

privateが宣言されているクラスのメンバーは、そのクラスのサブクラスに継承されません。

protectedまたはpublicが宣言されているクラスのメンバーのみが、クラスが宣言されているパッケージ以外のパッケージで宣言されているサブクラスによって継承されます。

コンストラクタ、静的イニシャライザおよびインスタンス・イニシャライザはメンバーではないため、継承されません。

メンバーの型という語句を使用して、次のことを表します。

  • フィールドの場合は、その型です。

  • メソッドの場合、次のもので構成される順序付けられた4タプル(メソッド・タイプと呼ばれます)

    • 型パラメータ: メソッド・メンバーの任意の型パラメータの宣言(§8.4.4)。

    • パラメータ型: メソッド・メンバーの仮パラメータの型のリスト(§8.4.1)。

    • 戻り型: メソッド・メンバーの戻り型(§8.4.5)。

    • throws句: メソッド・メンバーのthrows句で宣言された例外型(§8.4.6)。

クラスのフィールド、メソッド、メンバー・クラスおよびメンバー・インタフェースは、異なるコンテキストで使用され、異なる参照プロシージャ(§6.5)によって曖昧化されるため、同じ名前を持つ場合があります。 ただし、スタイルの面からお薦めできません。

例8.2-1. クラス・メンバーの使用

class Point {
    int x, y;
    private Point() { reset(); }
    Point(int x, int y) { this.x = x; this.y = y; }
    private void reset() { this.x = 0; this.y = 0; }
}
class ColoredPoint extends Point {
    int color;
    void clear() { reset(); }  // error
}
class Test {
    public static void main(String[] args) {
        ColoredPoint c = new ColoredPoint(0, 0);  // error
        c.reset();  // error
    }
}

このプログラムでは、コンパイル時に4つのエラーが発生します。

1つのエラーが発生するのは、mainでの使用によってリクエストされたように、ColoredPointに2つのintパラメータで宣言されたコンストラクタがないためです。 これは、ColoredPointがそのスーパークラスPointのコンストラクタを継承しないという事実を示しています。

ColoredPointはコンストラクタを宣言しないため、そのデフォルト・コンストラクタは暗黙的に宣言され(§8.8.9)、このデフォルト・コンストラクタは次のものと同等であるため、別のエラーが発生します。

ColoredPoint() { super(); }

クラスColoredPointの直接スーパークラスのコンストラクタを引数なしで呼び出します。 エラーは、引数をとらないPointのコンストラクタがprivateであるため、スーパークラス・コンストラクタ呼出し(§8.8.7)を介しても、クラスPointの外ではアクセスできないことです。

さらに2つのエラーが発生するのは、クラスPointのメソッドresetprivateであるため、クラスColoredPointによって継承されないためです。 したがって、クラスColoredPointのメソッドclearおよびクラスTestのメソッドmainでのメソッド呼出しは正しくありません。


例8.2-2. パッケージ・アクセスを持つクラス・メンバーの継承

pointsパッケージが2つのコンパイル・ユニットを宣言する例を考えてみます。

package points;
public class Point {
    int x, y;
    public void move(int dx, int dy) { x += dx; y += dy; }
}

および

package points;
public class Point3d extends Point {
    int z;
    public void move(int dx, int dy, int dz) {
        x += dx; y += dy; z += dz;
    }
}

3つ目のコンパイル・ユニットは、別のパッケージにあります。

import points.Point3d;
class Point4d extends Point3d {
    int w;
    public void move(int dx, int dy, int dz, int dw) {
        x += dx; y += dy; z += dz; w += dw; // compile-time errors
    }
}

ここでは、pointsパッケージの両方のクラスがコンパイルされます。 クラスPoint3dは、Pointと同じパッケージ内にあるため、クラスPointのフィールドxおよびyを継承します。 異なるパッケージ内にあるクラスPoint4dは、クラスPointのフィールドxおよびy、またはクラスPoint3dのフィールドzを継承しないため、コンパイルに失敗します。

3番目のコンパイルユニットを記述するより良い方法は、次のとおりです。


import points.Point3d;
class Point4d extends Point3d {
    int w;
    public void move(int dx, int dy, int dz, int dw) {
        super.move(dx, dy, dz); w += dw;
    }
}

スーパークラスPoint3dmoveメソッドを使用して、dxdyおよびdzを処理します。 この方法でPoint4dを記述すると、エラーなしでコンパイルされます。


例8.2-3. publicおよびprotectedクラス・メンバーの継承

次のクラスがPointであるとします。

package points;
public class Point {
    public int x, y;
    protected int useCount = 0;
    static protected int totalUseCount = 0;
    public void move(int dx, int dy) {
        x += dx; y += dy; useCount++; totalUseCount++;
    }
}

publicおよびprotectedフィールドxyuseCountおよびtotalUseCountは、Pointのすべてのサブクラスで継承されます。

したがって、このテストプログラムは、別のパッケージで正常にコンパイルできます。

class Test extends points.Point {
    public void moveBack(int dx, int dy) {
        x -= dx; y -= dy; useCount++; totalUseCount++;
    }
}

例8.2-4. privateクラス・メンバーの継承

class Point {
    int x, y;
    void move(int dx, int dy) {
        x += dx; y += dy; totalMoves++;
    }
    private static int totalMoves;
    void printMoves() { System.out.println(totalMoves); }
}
class Point3d extends Point {
    int z;
    void move(int dx, int dy, int dz) {
        super.move(dx, dy); z += dz; totalMoves++; // error
    }
}

ここで、クラス変数totalMovesはクラスPoint内でのみ使用でき、サブクラスPoint3dによって継承されません。 コンパイル時エラーが発生するのは、クラスPoint3dのメソッド移動がtotalMovesを増分しようとするためです。


例8.2-5. アクセスできないクラスのメンバーへのアクセス

クラスがpublicとして宣言されていない場合でも、クラスのインスタンスは実行時に、publicスーパークラスまたはスーパーインタフェースによって宣言されるパッケージ外のコードとして使用できます。 クラスのインスタンスは、このようなpublic型の変数に割り当てることができます。 このような変数によって参照されるオブジェクトのpublicメソッドを呼び出すと、クラスのメソッドがpublicスーパークラスまたはスーパーインタフェースのメソッドを実装またはオーバーライドする場合に、そのメソッドを呼び出すことができます。 (この状況では、public以外のクラスで宣言されていても、メソッドは必ずpublicと宣言されます。)

コンパイル・ユニットについて考えてみます。

package points;
public class Point {
    public int x, y;
    public void move(int dx, int dy) {
        x += dx; y += dy;
    }
}

別のパッケージの別のコンパイル・ユニット:

package morePoints;
class Point3d extends points.Point {
    public int z;
    public void move(int dx, int dy, int dz) {
        super.move(dx, dy); z += dz;
    }
    public void move(int dx, int dy) {
        move(dx, dy, 0);
    }
}
public class OnePoint {
    public static points.Point getOne() {
        return new Point3d();
    }
}

まだ3番目のパッケージでmorePoints.OnePoint.getOne()を起動すると、Pointとして使用できるPoint3dが返されます。ただし、Point3d型はパッケージmorePointsの外部では使用できません。 その後、そのオブジェクトに対してメソッドmoveの2つの引数バージョンを呼び出すことができます。これは、Point3dのメソッドmovepublicであるため許容されます(publicメソッドをオーバーライドするメソッド自体は、正確にはpublicである必要があり、このような状況が正しく機能するため)。 そのオブジェクトのフィールドxおよびyには、このような3番目のパッケージからもアクセスできます。

クラスPoint3dのフィールドzpublicですが、Point型の変数p内のクラスPoint3dのインスタンスへの参照のみを指定した場合、パッケージmorePoints以外のコードからこのフィールドにアクセスすることはできません。 これは、pPoint型を持ち、クラスPointzという名前のフィールドがないため、式p.zが正しくないためです。また、クラス型Point3dを外部パッケージmorePointsで参照できないため、式((Point3d)p).zも正しくありません。

ただし、フィールドzpublicとして宣言しても意味がありません。 morePointsパッケージに、クラスPoint3dpublicサブクラスPoint4dが存在する場合:

package morePoints;
public class Point4d extends Point3d {
    public int w;
    public void move(int dx, int dy, int dz, int dw) {
        super.move(dx, dy, dz); w += dw;
    }
}

その後、クラスPoint4dはフィールドzを継承します。このフィールドpublicは、morePoints以外のパッケージのコードによって、publicPoint4dの変数および式を介してアクセスできます。


8.3.  フィールド宣言

クラスの変数は、フィールド宣言によって導入されます。

VariableDeclaratorList:
VariableDeclarator:
VariableDeclaratorId:
VariableInitializer:

ここでは、便宜上、§4.3の次の本番環境を示します。

ディメンション:
{注釈} [ ] {{注釈} [ ]}

FieldDeclarationの各宣言子は、1つのフィールドを宣言します。 宣言子に識別子が含まれている必要があります。そうしないと、コンパイル時にエラーが発生します。 「識別子」は、フィールドを参照するために名前で使用できます。

複数の宣言子を使用して、複数のフィールドを1つのFieldDeclarationで宣言できます。FieldModifierおよびUnannTypeは、宣言内のすべての宣言子に適用されます。

FieldModifier句については §8.3.1で説明されています。

宣言されたフィールドの型は、UnannTypeVariableDeclaratorIdにカッコのペアが表示されない場合、UnannTypeで示され、それ以外の場合は§10.2で指定されます。

フィールド宣言のスコープとシャドウ化は、§6.3および §6.4.1で規定されています。

クラス宣言の本体で、同じ名前のフィールドを複数宣言すると、コンパイル時にエラーが発生します。

クラスが特定の名前を持つフィールドを宣言すると、そのフィールドの宣言は、スーパークラス内の同じ名前を持つフィールドのすべてのアクセス可能な宣言およびクラスのスーパーインタフェースを非表示にします。

この点において、フィールドの非表示はメソッドの非表示(§8.4.8.3)とは異なります。フィールド非表示ではstaticフィールドと非staticフィールドとの間に区別が描かれず、メソッド非表示ではstaticメソッドと非staticメソッドとの間に区別が描かれます。

非表示フィールドは、修飾名(§6.5.6.2)がstaticの場合、またはキーワードsuper (§15.11.2)を含むフィールド・アクセス式またはスーパークラス型へのキャストを使用してアクセスできます。

この点に関しては、フィールドの非表示はメソッドの非表示と同様です。

フィールド宣言によって別のフィールドの宣言が非表示になる場合、2つのフィールドの型が同じである必要はありません。

クラスは、その直接スーパークラスおよび直接スーパークラスから継承し、スーパークラスおよびスーパークラスのすべての非privateフィールドを継承します。これらは両方ともクラス内のコードにアクセスでき、クラスの宣言では非表示になりません(§6.6)。

スーパークラスのprivateフィールドには、サブクラスからアクセスできる場合があります。たとえば、両方のクラスが同じクラスのメンバーである場合です。 ただし、privateフィールドは、サブクラスによって継承されることはありません。

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

インタフェースから同じフィールドの宣言が継承されるパスが複数ある場合があります。 このような状況下では、フィールドは1回のみ継承されたとみなされ、あいまいさなしに単純名で参照できます。

例8.3-1.  継承フィールドの乗算

クラスは、スーパークラスおよびスーパーインタフェースから、または2つのスーパーインタフェースから、同じ名前の2つ以上のフィールドを継承できます。 単純名によってあいまいな継承フィールドを参照しようとすると、コンパイル時エラーが発生します。 キーワードsuper (§15.11.2)を含む修飾名またはフィールド・アクセス式を使用して、そのようなフィールドに明確にアクセスすることができます。 プログラム内では次のようになります。

interface Frob  { float v = 2.0f; }
class SuperTest { int   v = 3; }
class Test extends SuperTest implements Frob {
    public static void main(String[] args) {
        new Test().printV();
    }
    void printV() { System.out.println(v); }
}

クラスTestは、vという名前の2つのフィールドを、そのスーパークラスSuperTestから1つ、スーパーインタフェースFrobから1つ継承します。 これ自体は許可されますが、メソッドprintVで単純名vが使用されているため、コンパイル時にエラーが発生します。つまり、どのvが意図されているかを判断できません。

次のバリエーションでは、フィールド・アクセス式super.vを使用して、クラスSuperTestで宣言されたvという名前のフィールドを参照し、修飾名Frob.vを使用して、インタフェースFrobで宣言されたvという名前のフィールドを参照します。

interface Frob  { float v = 2.0f; }
class SuperTest { int   v = 3; }
class Test extends SuperTest implements Frob {
    public static void main(String[] args) {
        new Test().printV();
    }
    void printV() {
        System.out.println((super.v + Frob.v)/2);
    }
}

次のようにコンパイルおよび出力されます。

2.5

2つの異なる継承フィールドの型が同じで、値が同じで、両方ともfinalの場合でも、単純名によるいずれかのフィールドへの参照はあいまいであるとみなされ、コンパイル時にエラーが発生します。 プログラム内では次のようになります。

interface Color        { int RED=0, GREEN=1,  BLUE=2;  }
interface TrafficLight { int RED=0, YELLOW=1, GREEN=2; }
class Test implements Color, TrafficLight {
    public static void main(String[] args) {
        System.out.println(GREEN);  // compile-time error
        System.out.println(RED);    // compile-time error
    }
}

クラスTestは、異なる値を持つGREENに対して2つの異なる宣言を継承するため、GREENへの参照があいまいであるとみなされるのは驚くべきことではありません。 この例のポイントは、2つの異なる宣言が継承されるため、REDへの参照もあいまいであるとみなされることです。 REDという名前の2つのフィールドが同じ型で、変更されない値が同じであるという事実は、この判断には影響しません。


例8.3-2.  フィールドの再継承

同じフィールド宣言が複数のパスを経由してインタフェースから継承される場合、フィールドは1回のみ継承されるとみなされます。 これは、あいまいさのない単純な名前で参照できます。 たとえば、次のコードのようになります。

interface Colorable {
    int RED = 0xff0000, GREEN = 0x00ff00, BLUE = 0x0000ff;
}
interface Paintable extends Colorable {
    int MATTE = 0, GLOSSY = 1;
}
class Point { int x, y; }
class ColoredPoint extends Point implements Colorable {}
class PaintedPoint extends ColoredPoint implements Paintable {
    int p = RED;
}

フィールドREDGREENおよびBLUEは、直接スーパークラスColoredPointおよびその直接スーパーインタフェースPaintableを介して、クラスPaintedPointによって継承されます。 ただし、単純名REDGREENおよびBLUEは、インタフェースColorableで宣言されたフィールドを参照するために、クラスPaintedPoint内であいまいさなく使用できます。


8.3.1.  フィールド修飾子

FieldModifier:
(いずれか)
Annotation public protected private
static final transient volatile

フィールド宣言の注釈修飾子に関する規則は、§9.7.4および§9.7.5に記載されています。

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

2つ以上の(個別)フィールド修飾子がフィールド宣言に表示される場合、FieldModifierの本番で前述したものと一致する順序で表示されることは、必須ではありませんが慣例です。

8.3.1.1. staticフィールド

フィールドがstaticと宣言されている場合、最終的に作成されるクラスのインスタンスの数(おそらくゼロ)に関係なく、フィールドのインカネーションが1つのみ存在します。 staticフィールドは、クラス変数と呼ばれることもあり、クラスが初期化されるとインカネーションされます(§12.4)。

staticが宣言されていないフィールドは、インスタンス変数と呼ばれ、static以外のフィールドと呼ばれることもあります。 クラスの新しいインスタンス(§12.5)が作成されるたびに、そのインスタンスに関連付けられた新しい変数が、そのクラスまたはそのスーパークラスで宣言されたすべてのインスタンス変数に対して作成されます。

クラス変数の宣言では、静的コンテキスト(§8.1.3)が導入され、現在のオブジェクトを参照するコンストラクトの使用が制限されます。 特に、キーワードthisおよびsuperは、静的コンテキスト(§15.8.3§15.11.2)では禁止されており、字句的に包含する宣言のインスタンス変数、インスタンス・メソッドおよび型パラメータへの修飾されていない参照です(§6.5.5.1§6.5.6.1§15.12.3)。

静的コンテキストまたはネストされたクラスまたはインタフェースからのインスタンス変数への参照は、§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のフィールドxyおよびuseCountを変更しても、これらのフィールドは個別のオブジェクトのインスタンス変数であるため、qのフィールドは影響を受けません。 この例では、クラスPointのクラス変数originは、Point.originで修飾子としてクラス名を使用し、p.originおよびq.originのようにフィールド・アクセス式(§15.11)でクラス型の変数を使用して参照されます。 originクラス変数にアクセスする次の2つの方法は、参照等価式の値(§15.21.3)によって証明された同じオブジェクトにアクセスします。

q.origin==Point.origin

がtrueであることで証明されます。 さらに、増分は次のようになります:

p.origin.useCount++;

これにより、q.origin.useCountの値が1になります。これは、p.originq.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

クラスTestxの宣言は、クラスPointxの定義を非表示にするため、クラスTestは、スーパークラスPointからフィールドxを継承しません。 クラスTestの宣言内では、単純名xはクラスTest内で宣言されたフィールドを参照します。 クラスTestのコードは、クラスPointのフィールドxsuper.xと呼びます(または、xstaticで、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

クラスTestxの宣言は、クラスPointxの定義を非表示にするため、クラスTestは、スーパークラスPointからフィールドxを継承しません。 ただし、クラスPointのフィールドxはクラスTestに継承されませんが、クラスTestのインスタンスによって実装されることに注意してください。 つまり、クラスTestのすべてのインスタンスには、int型の1つとdouble型の1つの2つのフィールドが含まれます。 どちらのフィールドもxという名前になりますが、クラスTestの宣言内では、単純名xは常にクラスTest内で宣言されたフィールドを参照します。 クラスTestのインスタンス・メソッドのコードは、クラスPointのインスタンス変数xsuper.xと呼びます。

フィールド・アクセス式を使用してフィールドxにアクセスするコードは、参照式の型によって示されるクラスのxという名前のフィールドにアクセスします。 したがって、式sample.xは、Testクラスで宣言されたインスタンス変数であるdouble値にアクセスします。これは、変数sampleの型がTestであるが、式((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.1.2. finalフィールド

フィールドはfinal(§4.12.4)と宣言できます。 クラス変数とインスタンス変数(staticおよびstatic以外のフィールド)の両方をfinalとして宣言できます。

空のfinalクラス変数は、宣言されているクラスの静的イニシャライザによって確実に割り当てられる必要があります。そうしないと、コンパイル時にエラーが発生します(§8.7§16.8)。

空白のfinalインスタンス変数は、必ず割り当てる必要があり、その変数が宣言されているクラスのすべてのコンストラクタの最後に必ず割り当てられていないか、コンパイル時にエラーが発生します(§8.8§16.9)。

8.3.1.3. transientフィールド

変数は、オブジェクトの永続状態の一部ではないことを示すために、transientとマークできます。

例 8.3.1.3-1. transientフィールドの永続性

クラスPointのインスタンスの場合:


class Point {
    int x, y;
    transient float rho, theta;
}

システム・サービスによって永続ストレージに保存され、フィールドxおよびyのみが保存されます。 この仕様では、このようなサービスの詳細は指定しません。このようなサービスの例は、java.io.Serializableの仕様を参照してください。


8.3.1.4. volatileフィールド

Javaプログラミング言語を使用すると、スレッドは共有変数にアクセスできます(§17.1)。 原則として、共有変数が一貫して確実に更新されるようにするために、スレッドは、これらの共有変数に対して相互に除外されるロックを取得することによって、そのような変数を排他的に使用することを保証する必要があります。

Javaプログラミング言語には、いくつかの目的でロックするよりも便利な2番目のメカニズムであるvolatileフィールドが用意されています。

フィールドをvolatileとして宣言できます。この場合、Javaメモリー・モデルにより、すべてのスレッドで変数の一貫した値が確認されます(§17.4)。

final変数もvolatileとして宣言されている場合、コンパイル時にエラーが発生します。

例8.3.1.4-1. volatileフィールド

次の例では、あるスレッドがメソッドoneを繰り返しコールし(すべてのスレッドでInteger.MAX_VALUE回以下)、別のスレッドがメソッドtwoを繰り返しコールする場合:

class Test {
    static int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

この場合、メソッドtwoは、iの値より大きいjの値を出力することがあります。これは、この例には同期が含まれておらず、§17.4で説明されているルールの下で、iおよびjの共有値が順番どおりに更新されない可能性があるためです。

この順不同の動作を回避する1つの方法は、メソッドoneおよびtwosynchronized (§8.4.3.6)として宣言することです。

class Test {
    static int i = 0, j = 0;
    static synchronized void one() { i++; j++; }
    static synchronized void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

これにより、メソッドoneとメソッドtwoが同時に実行されなくなり、さらに、メソッドoneが戻される前にijの共有値が両方とも更新されることが保証されます。 したがって、メソッドtwoは、iの値よりも大きいjの値を観察することはありません。実際には、常にijの同じ値を観察します。

別の方法として、iおよびjvolatileとして宣言します。

class Test {
    static volatile int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

これにより、メソッドoneおよびメソッドtwoを同時に実行できますが、iおよびjの共有値へのアクセスは、各スレッドによるプログラム・テキストの実行中に発生しているように見えるのと同じ回数、まったく同じ順序で行われます。 したがって、jの共有値は、iの共有値より大きくなることはありません。これは、jへの更新が発生する前に、iへの各更新がiの共有値に反映される必要があるためです。 ただし、メソッドtwoの任意の呼出しによって、iで観測された値よりもはるかに大きいjの値が監視される場合があります。これは、メソッドoneが、メソッドtwoiの値をフェッチする瞬間から、メソッド2がjの値をフェッチする瞬間まで何度も実行される可能性があるためです。

詳細および例は、§17.4を参照してください。


8.3.2.  フィールドの初期化

フィールド宣言の宣言子に変数イニシャライザがある場合、宣言子は宣言された変数への代入(§15.26)のセマンティクスを持ちます。

宣言子がクラス変数(つまり、staticフィールド) (§8.3.1.1)用である場合、次のルールがその初期化子に適用されます。

  • イニシャライザは、§15.8.3および§15.11.2で指定されているキーワードthisまたはキーワードsuperを使用して現在のオブジェクトを参照したり、§6.5.6.1および§15.12.3で指定されているインスタンス変数またはインスタンス・メソッドを単純名で参照したりすることはできません。

  • 実行時に、初期化子が評価され、クラスが初期化されるときに割当てが1回のみ実行されます(§12.4.2)。

    定数変数であるstaticフィールド(§4.12.4)は、他のstaticフィールド(§12.4.2、ステップ6)より前に初期化されることに注意してください。 これはインタフェース(§9.3.1)にも当てはまります。 このようなフィールドが単純名で参照される場合、デフォルトの初期値を持つことは決してありません(§4.12.5)。

宣言子がインスタンス変数用(つまり、staticではないフィールド)の場合、次のルールがその初期化子に適用されます。

  • イニシャライザは、キーワードthisまたはキーワードsuperを使用して現在のオブジェクトを参照できます。また、イニシャライザの右側に宣言があるクラス変数(§3.5)であっても、クラスで宣言または継承されたクラス変数に対して単純な名前で参照できます。

  • 実行時に、イニシャライザが評価され、クラスのインスタンスが作成されるたびに割当てが実行されます(§12.5)。

変数イニシャライザからまだ初期化されていないフィールドへの参照は、§8.3.3および§16 (Definite Assignment)で指定されているように制限されています。

フィールド宣言内の変数イニシャライザの例外チェックは、§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

xおよびyへの割当ては、新しいPointが作成されるたびに発生するためです。


例8.3.2-2.  クラス変数への前方参照

class Test {
    float f = j;
    static int j = 1;
}

このプログラムはエラーなしでコンパイルされ、クラスTestが初期化されるときにj1に初期化し、クラスTestのインスタンスが作成されるたびにfjの現在の値に初期化します。


8.3.3. 初期化子でのフィールド参照の制限

フィールドへの参照は、そのフィールドを介してスコープ内にある場合でも、制限されることがあります。 次のルールでは、フィールドへの前方参照(フィールド宣言の前にテキストを使用して使用)および自己参照(フィールドが独自のイニシャライザで使用される)を制約します。

クラスまたはインタフェースCで宣言されたクラス変数fへの単純名による参照の場合、次の場合にコンパイル時にエラーが発生します。

  • 参照は、Cのクラス変数イニシャライザまたはCの静的イニシャライザ(§8.7)に表示されます。

  • 参照は、f独自の宣言子のイニシャライザまたはfの宣言子の左側のポイントに表示されます。

  • 参照は、代入式の左側ではない(§15.26)。

  • 参照を囲む最も内側のクラスまたはインタフェースは Cです。

クラスCで宣言されたインスタンス変数fへの単純名による参照の場合、次の場合にコンパイル時にエラーが発生します。

  • 参照は、Cのインスタンス変数イニシャライザまたはCのインスタンス・イニシャライザ(§8.6)に表示されます。

  • 参照は、f独自の宣言子のイニシャライザ、またはfの宣言子の左側のポイントに表示されます。

  • 参照は、代入式の左側ではない(§15.26)。

  • 参照を囲む最も内側のクラスはCです。

例8.3.3-1. フィールド参照の制限

このプログラムでコンパイル時エラーが発生します。

class Test1 {
    int i = j;  // compile-time error:
                // incorrect forward reference
    int j = 1;
}

ただし、次のプログラムはエラーなしでコンパイルされます。

class Test2 {
    Test2() { k = 2; }
    int j = 1;
    int i = j;
    int k;
}

Test2のコンストラクタ(§8.8)が、3行後に宣言されたフィールドkを参照している場合でも。

前述の制限は、コンパイル時に循環または不正な形式の初期化を捕捉するように設計されています。 したがって、両方:

class Z {
    static int i = j + 2;
    static int j = 4;
}

および

class Z {
    static { i = j + 2; }
    static int i, j;
    static { j = 4; }
}

コンパイル時にエラーが発生します。 メソッドによるアクセスは、この方法でチェックされないため、次のようになります。

class Z {
    static int peek() { return j; }
    static int i = peek();
    static int j = 1;
}
class Test {
    public static void main(String[] args) {
        System.out.println(Z.i);
    }
}

出力の生成:

0

iの変数イニシャライザは、jが変数イニシャライザによって初期化される前に、クラス・メソッドpeekを使用して変数jの値にアクセスするため、その時点でデフォルト値が保持されます(§4.12.5)。

より詳細な例を次に示します。

class UseBeforeDeclaration {
    static {
        x = 100;
          // ok - assignment
        int y = x + 1;
          // error - read before declaration
        int v = x = 3;
          // ok - x at left hand side of assignment
        int z = UseBeforeDeclaration.x * 2;
          // ok - not accessed via simple name

        Object o = new Object() {
            void foo() { x++; }
              // ok - occurs in a different class
            { x++; }
              // ok - occurs in a different class
        };
    }

    {
        j = 200;
          // ok - assignment
        j = j + 1;
          // error - right hand side reads before declaration
        int k = j = j + 1;
          // error - illegal forward reference to j
        int n = j = 300;
          // ok - j at left hand side of assignment
        int h = j++;
          // error - read before declaration
        int l = this.j * 3;
          // ok - not accessed via simple name

        Object o = new Object() {
            void foo(){ j++; }
              // ok - occurs in a different class
            { j = j + 1; }
              // ok - occurs in a different class
        };
    }

    int w = x = 3;
      // ok - x at left hand side of assignment
    int p = x;
      // ok - instance initializers may access static fields

    static int u =
        (new Object() { int bar() { return x; } }).bar();
        // ok - occurs in a different class

    static int x;

    int m = j = 4;
      // ok - j at left hand side of assignment
    int o =
        (new Object() { int bar() { return j; } }).bar();
        // ok - occurs in a different class
    int j;
}

8.4.  メソッド宣言

メソッドは、起動可能な実行可能コードを宣言し、固定数の値を引数として渡します。

ここでは、便宜上、§4.3の次の本番環境を示します。

ディメンション:
{注釈} [ ] {{注釈} [ ]}

FormalParameterList句については、§8.4.1§8.4.3MethodModifier句、§8.4.4TypeParameters句、§8.4.4Result句、§8.4.5Throws句、§8.4.6MethodBody、および§8.4.7を参照してください。

MethodDeclaratorIdentifierは、メソッドを参照するために名前で使用できます(§6.5.7.1§15.12)。

メソッド宣言のスコープとシャドウ化は、§6.3および §6.4.1で規定されています。

受信者パラメータは、インスタンス・メソッドまたは内部クラスのコンストラクタのオプションの構文デバイスです。 インスタンス・メソッドの場合、receiverパラメータは、メソッドが呼び出されるオブジェクトを表します。 内部クラスのコンストラクタの場合、受信側パラメータは、新しく構築されたオブジェクトの直後のインスタンスを表します。 どちらの場合も、レシーバ・パラメータは、表現されたオブジェクトの型をソース・コードで示せるようにするためだけに存在し、型に注釈を付けることができます(§9.7.4)。 受信側パラメータは仮パラメータではありません。より正確には、どのような種類の変数(§4.12.3)の宣言でもなく、メソッド呼出し式またはクラス・インスタンス作成式で引数として渡される値にはバインドされず、実行時に何の影響もありません。

受信者パラメータは、インスタンス・メソッドのMethodDeclaratorまたは内部クラスが静的コンテキストで宣言されていない内部クラスのコンストラクタのConstructorDeclarator (§8.1.3)に表示されます。 他の種類のメソッドまたはコンストラクタに受信者パラメータが存在する場合は、コンパイル時にエラーが発生します。

受信者パラメータのタイプと名前は、次のように制約されます。

  • インスタンス・メソッドでは、レシーバ・パラメータの型はメソッドが宣言されるクラスまたはインタフェースである必要があり、レシーバ・パラメータの名前はthisである必要があります。そうでない場合は、コンパイル時にエラーが発生します。

  • 内部クラスのコンストラクタでは、受信側パラメータの型は内部クラスの即時包含型宣言であるクラスまたはインタフェースである必要があり、受信側パラメータの名前はIdentifier . this。ここで、Identifierは、内部クラスの即時包含型宣言であるクラスまたはインタフェースの単純名です。それ以外の場合は、コンパイル時にエラーが発生します。

クラス宣言の本体で、オーバーライド等価のシグネチャを持つ2つのメソッドをメンバーとして宣言するのはコンパイル時エラーです(§8.4.2)。

配列を返すメソッドの宣言は、仮パラメータ・リストの後に配列型を示す大カッコ・ペアの一部またはすべてを配置できます。 この構文は、Javaプログラミング言語の初期バージョンとの互換性のためにサポートされています。 この構文は新しいコードでは使用しないことを強くお薦めします。

8.4.1.  仮パラメータ

メソッドまたはコンストラクタのフォーマル・パラメータは、カンマ区切りパラメータ指定子のリストで指定されます(ある場合)。 各パラメータ指定子は、型(オプションとしてfinal修飾子または1つ以上の注釈、あるいはその両方)と、パラメータの名前を指定する識別子(オプションとしてカッコが続く)で構成されます。

メソッドまたはコンストラクタに仮パラメータがなく、レシーバ・パラメータもない場合は、メソッドまたはコンストラクタの宣言に空のカッコのペアが表示されます。

FormalParameterList:
VariableArityParameter:
VariableModifier:
注釈
final

ここでは、便宜上、§8.3および §4.3からの次のプロダクションを示します。

VariableDeclaratorId:
ディメンション:
{注釈} [ ] {{注釈} [ ]}

メソッドまたはコンストラクタの仮パラメータは、型に続く省略記号で示される変数引数パラメータです。 メソッドまたはコンストラクタには、最大1つの可変引数パラメータを指定できます。 可変引数パラメータが最後の位置を除くパラメータ指定子のリストの任意の場所にある場合、コンパイル時エラーになります。

VariableArityParameterの文法では、省略記号(...)はそれ自体に対するトークン(§3.11)であることに注意してください。 これと型の間には空白を入れることができますが、スタイルに関してはお薦めしません。

メソッドの最後の仮パラメータが可変アリティ・パラメータである場合、メソッドは変数アリティ・メソッドです。 それ以外の場合は、固定アリティ・メソッドです。

仮パラメータ宣言およびレシーバ・パラメータの注釈修飾子に関する規則は、§9.7.4および§9.7.5で指定されています。

仮パラメータ宣言の修飾子としてfinalが複数回出現した場合、コンパイル時にエラーが発生します。

仮パラメータの範囲とシャドウ化は、§6.3および §6.4で規定されています。

ネストされたクラスまたはインタフェース、またはラムダ式からの仮パラメータへの参照は、§6.5.6.1で指定されているように制限されます。

メソッドまたはコンストラクタの仮パラメータの宣言にはすべて識別子を含める必要があります。そうしないと、コンパイル時にエラーが発生します。

メソッドまたはコンストラクタが同じ名前で2つの仮パラメータを宣言すると、コンパイル時にエラーが発生します。 (つまり、その宣言には同じ識別子が記述されています。)

finalと宣言された仮パラメータがメソッドまたはコンストラクタの本体内で割り当てられている場合、コンパイル時にエラーが発生します。

仮パラメータの宣言型は、可変引数パラメータであるかどうかによって異なります:

  • 仮パラメータが可変引数パラメータでない場合、宣言された型は、UnannTypeVariableDeclaratorIdにカッコのペアが表示されない場合はUnannTypeで示され、それ以外の場合は§10.2で指定されます。

  • 仮パラメータが可変引数パラメータの場合、宣言された型は§10.2で指定された配列型です。

変数Arityパラメータの宣言された型に、変更不可能な要素型(§4.7)がある場合、メソッドに@SafeVarargs (§9.6.4.7)の注釈が付けられているか、@SuppressWarnings (§9.6.4.5)によって警告が抑制されていないかぎり、変数Arityメソッドの宣言に対してコンパイル時の未チェックの警告が発生します。

メソッドまたはコンストラクタが呼び出されると(§15.12)、実際の引数式の値は、メソッドまたはコンストラクタの本体を実行する前に、新しく作成された各パラメータ変数(宣言された型)を初期化します。 FormalParameterに表示される識別子は、仮パラメータを参照するためにメソッドまたはコンストラクタの本体で単純名として使用できます。

可変引数メソッドの呼出しには、仮パラメータよりも多くの実引数式を含めることができます。 変数Arityパラメータの前の仮パラメータに対応しない実際の引数式がすべて評価され、メソッド呼出しに渡される配列に格納される結果(§15.12.4.2)が評価されます。

次に、インスタンス・メソッドおよび内部クラス・コンストラクタのレシーバ・パラメータの例をいくつか示します:


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.2.  メソッド・シグネチャ

2つのメソッドまたはコンストラクタMNは、同じ名前、同じ型パラメータ(存在する場合) (§8.4.4)、およびNの仮パラメータ型をMの型パラメータ(同じ仮パラメータ型)に適応させた後、同じ仮パラメータ型を持つ同じシグネチャを持ちます。

次のいずれかの場合、メソッドm1のシグネチャは、メソッドm2のシグネチャのサブシグネチャです。

  • m2は、m1と同じシグネチャを持ちます。

  • m1のシグネチャは、m2のシグネチャの消去(§4.6)と同じです。

m1m2のサブシグネチャであるか、m2m1のサブシグネチャである場合、m1およびm2の2つのメソッド・シグネチャはオーバーライド等価です。

クラスでオーバーライド等価のシグネチャを持つ2つのメソッドを宣言すると、コンパイル時にエラーが発生します。

例8.4.2-1. オーバーライド- 同等の署名

class Point {
    int x, y;
    abstract void move(int dx, int dy);
    void move(int dx, int dy) { x += dx; y += dy; }
}

このプログラムは、同じ(したがって、オーバーライド等価)シグネチャを持つ2つのmoveメソッドを宣言しているため、コンパイル時にエラーが発生します。 これは、宣言の1つがabstractであってもエラーです。


副署名の概念は、署名が同一ではなく、一方が他方を上書きできる2つの方法間の関係を表現するように設計されています。 具体的には、汎用型を使用しないシグネチャを持つメソッドで、そのメソッドの生成済バージョンをオーバーライドできます。 これは、ライブラリ・デザイナが、ライブラリのサブクラスまたはサブインタフェースを定義するクライアントとは独立してメソッドを自由に生成できるようにするために重要です。

次の例を考えてみます。


class CollectionConverter {
    List toList(Collection c) {...}
}
class Overrider extends CollectionConverter {
    List toList(Collection c) {...}
}

ここで、このコードがジェネリックスの導入前に記述されていると仮定します。これで、クラスCollectionConverterの作成者がコードを生成することを決定します。


class CollectionConverter {
    <T> List<T> toList(Collection<T> c) {...}
}

特別な交付がない場合、Overrider.toListCollectionConverter.toListをオーバーライドしなくなります。 かわりに、コードは違法になります。 ライブラリライターは既存のコードの移行を躊躇するため、これによってジェネリクスの使用が大幅に抑制されます。

8.4.3.  メソッド修飾子

MethodModifier:
(いずれか)
注釈 public protected private
abstract static final synchronized native strictfp

メソッド宣言の注釈修飾子に関する規則は、§9.7.4および§9.7.5に記載されています。

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

キーワードabstractを含むメソッド宣言に、キーワードprivatestaticfinalnativestrictfpまたはsynchronizedのいずれかが含まれている場合、コンパイル時にエラーが発生します。

キーワードnativeを含むメソッド宣言にstrictfpも含まれている場合、コンパイル時にエラーが発生します。

2つ以上の(個別)メソッド修飾子がメソッド宣言に表示される場合、MethodModifierの本番で前述したものと一致する順序で表示されることは、必須ではありませんが慣例です。

8.4.3.1. abstractメソッド

abstractメソッド宣言では、メソッドをメンバーとして導入し、そのシグネチャ(§8.4.2)、結果(§8.4.5)およびthrows句(存在する場合)(§8.4.6)、実装を提供しません(§8.4.7)。 abstract以外のメソッドは、コンクリート・メソッドと呼びます。

abstractメソッドmの宣言は、enum宣言(§8.9)内で発生しないかぎり、abstractクラス(これをAと呼びます)内で直接出現する必要があります。そうでない場合、コンパイル時にエラーが発生します。

abstractでないAのすべてのサブクラス(§8.1.1.1)は、mの実装を提供する必要があります。そうしないと、コンパイル時にエラーが発生します。

abstractクラスは、別のabstractメソッド宣言を指定することで、abstractメソッドをオーバーライドできます。

これにより、ドキュメンテーション・コメントを配置したり、戻り型を絞り込んだり、そのメソッドがサブクラスによって実装されるときにスローできるチェック済例外のセットをより限定できる場所を提供できます。

abstractではないインスタンス・メソッドは、abstractメソッドでオーバーライドできます。

例 8.4.3.1-1. 抽象メソッドまたは抽象メソッドのオーバーライド

class BufferEmpty extends Exception {
    BufferEmpty() { super(); }
    BufferEmpty(String s) { super(s); }
}
class BufferError extends Exception {
    BufferError() { super(); }
    BufferError(String s) { super(s); }
}
interface Buffer {
    char get() throws BufferEmpty, BufferError;
}
abstract class InfiniteBuffer implements Buffer {
    public abstract char get() throws BufferError;
}

クラスInfiniteBufferのメソッドgetのオーバーライド宣言は、InfiniteBufferのサブクラスのメソッドgetBufferEmpty例外をスローすることはないと述べています。これは、バッファにデータを生成するためです。したがって、データが不足することはありません。


例 8.4.3.1-2. 抽象/非抽象オーバーライド

完全でインスタンス化可能なクラスである場合は、サブクラスがtoStringを実装する必要があるabstractクラスPointを宣言できます。

abstract class Point {
    int x, y;
    public abstract String toString();
}

このtoStringabstract宣言は、クラスObjectabstract以外のtoStringメソッドをオーバーライドします。 (Objectは、クラスPointの暗黙的な直接スーパークラスです。) コードの追加:

class ColoredPoint extends Point {
    int color;
    public String toString() {
        return super.toString() + ": color " + color;  // error
    }
}

呼出しsuper.toString()がクラスPointのメソッドtoStringを参照しているため、コンパイル時にエラーが発生します。これは、abstractであるため起動できません。 クラスObjectのメソッドtoStringをクラスColoredPointで使用可能にできるのは、クラスPointが他のメソッドを介して明示的に使用可能にする場合のみです。次に例を示します。

abstract class Point {
    int x, y;
    public abstract String toString();
    protected String objString() { return super.toString(); }
}
class ColoredPoint extends Point {
    int color;
    public String toString() {
        return objString() + ": color " + color;  // correct
    }
}

8.4.3.2. staticメソッド

staticと宣言されたメソッドは、クラス・メソッドと呼ばれます。

クラス・メソッドは、常に特定のオブジェクトへの参照なしで呼び出されます。 クラス・メソッドの宣言では、静的コンテキスト(§8.1.3)が導入され、現在のオブジェクトを参照する構造体の使用が制限されます。 特に、キーワードthisおよびsuperは、静的コンテキスト(§15.8.3§15.11.2)では禁止されており、字句的に包含する宣言のインスタンス変数、インスタンス・メソッドおよび型パラメータへの修飾されていない参照です(§6.5.5.1§6.5.6.1§15.12.3)。

staticが宣言されていないメソッドは、インスタンス・メソッドと呼ばれ、static以外のメソッドと呼ばれることもあります。

インスタンス・メソッドは、メソッド本体の実行中にキーワードthisおよびsuperが参照する現在のオブジェクトとなるオブジェクトに関して常に呼び出されます。

静的コンテキストまたはネストされたクラスまたはインタフェースからのインスタンス・メソッドへの参照は、§15.12.3で指定されているように制限されています。

8.4.3.3. finalメソッド

メソッドをfinalとして宣言して、サブクラスがメソッドをオーバーライドまたは非表示にしないようにできます。

finalメソッドをオーバーライドまたは非表示にしようとすると、コンパイル時にエラーが発生します。

privateメソッドおよびfinalクラス(§8.1.1.2)内で即時に宣言されたすべてのメソッドは、オーバーライドできないため、finalであるかのように動作します。

実行時に、マシン・コード・ジェネレータまたはオプティマイザは、finalメソッドの本体をインライン化して、メソッドの呼出しをその本体内のコードに置き換えることができます。 インライン化プロセスでは、メソッド呼出しのセマンティクスを保持する必要があります。 特に、インスタンス・メソッド呼出しのターゲットがnullの場合、メソッドがインライン化されていても、NullPointerExceptionをスローする必要があります。 Javaコンパイラは、メソッドの実際の引数がメソッド呼出しの前に正しい順序で評価されるように、例外が正しい時点でスローされることを確認する必要があります。

次の例を考えてみます。

final class Point {
    int x, y;
    void move(int dx, int dy) { x += dx; y += dy; }
}
class Test {
    public static void main(String[] args) {
        Point[] p = new Point[100];
        for (int i = 0; i < p.length; i++) {
            p[i] = new Point();
            p[i].move(i, p.length-1-i);
        }
    }
}

メソッドmain内のクラスPointのメソッドmoveをインライン化すると、forループが次の形式に変換されます。


    for (int i = 0; i < p.length; i++) {
        p[i] = new Point();
        Point pi = p[i];
        int j = p.length-1-i;
        pi.x += i;
        pi.y += j;
    }

その後、ループはさらに最適化される可能性があります。

このようなインライン化は、TestおよびPointが常に一緒に再コンパイルされることが保証されないかぎり、コンパイル時に実行できません。これにより、Pointおよび特にそのmoveメソッドが変更されるたびに、Test.mainのコードも更新されます。

8.4.3.4. nativeメソッド

nativeのメソッドは、プラットフォームに依存するコードで実装され、通常はCなどの別のプログラミング言語で記述されます。 nativeメソッドの本体はセミコロンとしてのみ指定され、ブロック(§8.4.7)ではなく実装が省略されていることを示します。

たとえば、パッケージjava.ioのクラスRandomAccessFileは、次のnativeメソッドを宣言できます。


package java.io;
public class RandomAccessFile
    implements DataOutput, DataInput {
    . . .
    public native void open(String name, boolean writeable)
        throws IOException;
    public native int readBytes(byte[] b, int off, int len)
        throws IOException;
    public native void writeBytes(byte[] b, int off, int len)
        throws IOException;
    public native long getFilePointer() throws IOException;
    public native void seek(long pos) throws IOException;
    public native long length() throws IOException;
    public native void close() throws IOException;
}

8.4.3.5. strictfpメソッド

メソッド宣言のstrictfp修飾子は廃止されているため、新しいコードでは使用しないでください。 その有無は、実行時に影響しません。

8.4.3.6. synchronizedメソッド

synchronizedメソッドは、モニター(§17.1)が実行される前に取得します。

クラス(static)メソッドの場合は、そのメソッドのクラスのClassオブジェクトに関連付けられたモニターが使用されます。

インスタンス・メソッドの場合、thisに関連付けられたモニター(メソッドが呼び出されたオブジェクト)が使用されます。

例 8.4.3.6-1. synchronizedモニター

これらは、synchronized文(§14.19)で使用できるモニターと同じです。

したがって、コードは次のようになります。

class Test {
    int count;
    synchronized void bump() {
        count++;
    }
    static int classCount;
    static synchronized void classBump() {
        classCount++;
    }
}

以下とまったく同じ効果があります。

class BumpTest {
    int count;
    void bump() {
        synchronized (this) { count++; }
    }
    static int classCount;
    static void classBump() {
        try {
            synchronized (Class.forName("BumpTest")) {
                classCount++;
            }
        } catch (ClassNotFoundException e) {}
    }
}

例8.4.3.6-2. synchronizedメソッド

public class Box {
    private Object boxContents;
    public synchronized Object get() {
        Object contents = boxContents;
        boxContents = null;
        return contents;
    }
    public synchronized boolean put(Object contents) {
        if (boxContents != null) return false;
        boxContents = contents;
        return true;
    }
}

このプログラムは、同時使用のために設計されたクラスを定義します。 クラスBoxの各インスタンスには、任意のオブジェクトへの参照を保持できるインスタンス変数boxContentsがあります。 Boxにオブジェクトを配置するには、putを呼び出します。このfalseは、ボックスがすでにいっぱいの場合に返されます。 Boxから何かを取得するには、getを呼び出します。このgetは、ボックスが空の場合にnull参照を返します。

putgetsynchronizedではなく、2つのスレッドがBoxの同じインスタンスに対して同時にメソッドを実行していた場合、コードが誤動作する可能性があります。 たとえば、putへの2回の呼出しが同時に実行されたため、オブジェクトの追跡が失われる場合があります。


8.4.4.  汎用メソッド

メソッドが1つ以上の型変数(§4.4)を宣言している場合、メソッドは汎用です。

これらの型変数は、メソッドの型パラメータと呼ばれます。 汎用メソッドの型パラメータ・セクションの形式は、汎用クラスの型パラメータ・セクション(§8.1.2)と同じです。

汎用メソッド宣言では、型引数による型パラメータ・セクションの呼出しごとに、一連のメソッドを定義します。 型引数は、一般メソッドが呼び出されたときに明示的に指定する必要がない場合があります。これは、一般メソッドが推測されることが多いためです(§18 (Type Inference))。

メソッドの型パラメータのスコープとシャドウは、§6.3および §6.4.1で指定されています。

ネストされたクラスまたはインタフェースからのメソッドの型パラメータへの参照は、§6.5.5.1で指定されているように制限されています。

次の両方に該当する場合は、2つのメソッドまたはコンストラクタMN同じ型パラメータがあります。

  • MNの型パラメータは同じ数です(ゼロの場合もあります)。

  • ここで、A1、 ...、 AnMの型パラメータで、B1、 ...、 BnNの型パラメータで、θ=[B1:=A1、 ...、 Bn:=An]とします。 したがって、すべての i (1 i n)の場合、Aiの境界は、Biの境界に適用される θと同じ型になります。

2つのメソッドまたはコンストラクタMNが同じ型パラメータを持つ場合、前述のように型にθを適用することで、Nに記載されている型をM型パラメータに適用できます。

8.4.5.  メソッド結果

メソッド宣言の結果は、メソッドが返す値の型を宣言するか(戻り型)、キーワードvoidを使用してメソッドが値を返さないことを示します。

結果:
UnannType
void

結果がvoidでない場合、メソッドの戻り型は、仮パラメータ・リストの後にカッコのペアが存在しない場合はUnannTypeで示され、それ以外の場合は§10.2で指定されます。

戻り型が参照型の場合、戻り型は相互にオーバーライドするメソッド間で異なる場合があります。 return-type-substitutabilityの概念では、共変戻り値(つまり、戻り型をサブタイプに特殊化)がサポートされます。

次のいずれかに該当する場合、戻り型がR1のメソッド宣言d1は、戻り型がR2の別のメソッドd2return-type-substitutableです。

  • R1voidの場合、R2voidです。

  • R1がプリミティブ型の場合、R2R1と同じです。

  • R1が参照型の場合、次のいずれかが当てはまります。

    • R1は、d2 (§8.4.4)の型パラメータに適合しており、R2のサブタイプです。

    • R1は、未チェックの変換(§5.1.9)によってR2のサブタイプに変換できます。

    • d1は、d2 (§8.4.2)と同じシグネチャを持たず、R1 = |R2|です。

非汎用コードから汎用コードへのスムーズな移行を可能にする特別な手当として、未チェックの変換が定義で許可されます。 未チェックの変換を使用して、R1R2の戻り型置換可能であると判断した場合、R1は必ずしもR2のサブタイプではなく、オーバーライドするルール(§8.4.8.3§9.4.1)には、コンパイル時の未チェックの警告が必要です。

8.4.6. メソッド・スロー

throws句は、メソッドまたはコンストラクタ本体の文がスローできるチェックされた例外クラス(§11.1.1)を示すために使用されます(§11.2.2)。

スロー:
ExceptionTypeList:
ExceptionType:

throws句に記述されているExceptionTypeThrowableのサブタイプ(§4.10)ではない場合、コンパイル時にエラーが発生します。

型変数は、catch句(§14.20)で許可されていない場合でも、throws句で許可されます。

throws句では、未チェックの例外クラス(§11.1.1)を指定することは許可されていますが、必須ではありません。

throws句とメソッドまたはコンストラクタ本体の例外チェックの関係は、§11.2.3に記載されています。

基本的に、メソッドまたはコンストラクタの本体の実行によって生じる可能性のあるチェックされた例外ごとに、その例外タイプまたは例外タイプのスーパータイプがメソッドまたはコンストラクタの宣言のthrows句で指定されていないかぎり、コンパイル時にエラーが発生します。

チェック例外を宣言する要件により、Javaコンパイラは、このようなエラー条件を処理するためのコードが含まれていることを確認できます。 本体でチェックされた例外としてスローされた例外条件を処理できないメソッドまたはコンストラクタは、通常、throws句に適切な例外型がない場合にコンパイル時エラーが発生します。 したがって、Javaプログラミング言語では、この方法で、まれで、そうでなければ真に例外的な条件が文書化されるプログラミング・スタイルが推奨されます。

メソッドのthrows句とオーバーライド・メソッドまたは非表示メソッドのthrows句の関係は、§8.4.8.3で指定します。

例8.4.6-1. スローされた例外タイプとしての型変数

import java.io.FileNotFoundException;

interface PrivilegedExceptionAction<E extends Exception> {
    void run() throws E;
}
class AccessController {
    public static <E extends Exception>
    Object doPrivileged(PrivilegedExceptionAction<E> action) throws E {
        action.run();
        return "success";
    }
}
class Test {
    public static void main(String[] args) {
        try {
            AccessController.doPrivileged(
              new PrivilegedExceptionAction<FileNotFoundException>() {
                  public void run() throws FileNotFoundException {
                      // ... delete a file ...
                  }
              });
        } catch (FileNotFoundException f) { /* Do something */ }
    }
}


8.4.7.  メソッド本体

メソッド本体は、メソッドを実装するコードのブロックか、実装の欠如を示す単なるセミコロンです。

MethodBody:

メソッドがabstractまたはnative (§8.4.3.1§8.4.3.4)の場合、メソッドの本体はセミコロンである必要があります。 より正確には、次のようになります。

  • メソッド宣言がabstractまたはnativeで、その本体のブロックがある場合、コンパイル時にエラーが発生します。

  • メソッド宣言がabstractでもnativeでもなく、本体にセミコロンがある場合、コンパイル時にエラーが発生します。

voidと宣言されたメソッドに対して実装が提供されるが、実装に実行可能コードが必要ない場合、メソッド本体は文を含まないブロックとして記述する必要があります: "{ }"。

メソッド本体内のreturn文のルールは、§14.17で指定します。

メソッドが戻り型(§8.4.5)を持つように宣言されている場合、メソッドの本文が正常に完了できるとコンパイル時エラーが発生します(§14.1)。

つまり、戻り値の型を持つメソッドは、値の戻り値を提供するreturn文を使用してのみ返す必要があります。このメソッドは、本体の終わりをドロップ・オフすることはできません。 メソッド本体のreturn文に関する正確な規則については、§14.17を参照してください。

メソッドが戻り型を持つことは可能ですが、return文は含まれません。 次に例を示します。

class DizzyDean {
    int pitch() { throw new RuntimeException("90 mph?!"); }
}

8.4.8.  継承、オーバーライドおよび非表示

直接スーパークラス型Dから継承されるクラスCは、次のすべてに当てはまるすべての具象メソッドm (staticとインスタンスの両方)を継承します。

  • mは、Dのメンバーです。

  • mは、publicprotected、またはCと同じパッケージ内のパッケージ・アクセスで宣言されます。

  • Cで宣言されたメソッドには、Dのメンバーとしてのmのシグネチャのサブシグネチャ(§8.4.2)を持つシグネチャはありません。

クラスCは、その直接スーパークラス型から継承し、直接スーパーインタフェース型はすべてabstractおよびデフォルト(§9.4)メソッドmで、次のすべてに該当します。

  • mは、ダイレクト・スーパークラス型またはCの直接スーパーインタフェース型のメンバーで、いずれの場合もDと呼ばれます。

  • mは、publicprotected、またはCと同じパッケージ内のパッケージ・アクセスで宣言されます。

  • Cで宣言されたメソッドには、Dのメンバーとしてのmのシグネチャのサブシグネチャ(§8.4.2)を持つシグネチャはありません。

  • Cが直接スーパークラス型から継承する具体的なメソッドには、Dのメンバーとしてのmのシグネチャのサブシグネチャを持つシグネチャはありません。

  • CD'の直接スーパークラス型または直接スーパーインタフェース型のメンバーであるメソッドm' (m'と異なるmDD'と異なる)が存在せず、D'のクラスまたはインタフェースからのm'がメソッドmの宣言(§8.4.8.1§9.4.1.1)をオーバーライドします。

インタフェースの継承は、§9.1.3で定義されています。

クラスは、そのスーパーインタフェース型からprivateまたはstaticメソッドを継承しません。

メソッドは、シグネチャごとにオーバーライドされるか、隠されます。 たとえば、クラスが同じ名前を持つ2つのpublicメソッド(§8.4.9)を宣言し、サブクラスがそのいずれかをオーバーライドした場合でも、サブクラスはもう一方のメソッドを継承します。

例8.4.8-1.  継承

interface I1 {
    int foo();
}

interface I2 {
    int foo();
}

abstract class Test implements I1, I2 {}

ここで、abstractクラスTestは、abstractメソッドfooをインタフェースI1から継承し、abstractメソッドfooをインタフェースI2から継承します。 I1からのfooの継承を決定する際の重要な質問は、I2のメソッドfooI1のメソッドfooを"from I2" (§9.4.1.1)でオーバーライドするかどうかです。 いいえ。I1I2は相互のサブインタフェースではないためです。 したがって、クラスTestのビューポイントから、I1からのfooの継承は変更されません。同様に、I2からのfooの継承についても同様です。 §8.4.8.4に従って、クラスTestは両方のfooメソッドを継承できます。明らかに、abstractを宣言するか、または両方のabstract fooメソッドを具象メソッドでオーバーライドする必要があります。


継承された具象メソッドは、abstractまたはデフォルト・メソッドの継承を防ぐことができます。 (具体的なメソッドは、§8.4.8.1および§9.4.1.1に従って、Cからのabstractまたはデフォルト・メソッドをオーバーライドします。) また、あるスーパータイプ・メソッドが別のスーパータイプ・メソッドの継承を防ぐことができるのは、前のスーパータイプ・メソッドが後者をオーバーライドしている場合です。これは、インタフェース(§9.4.1)のルールと同じであり、複数のデフォルト・メソッドが継承され、一方の実装が他方のスーパータイプ・メソッドより優先される競合を回避するためです。

8.4.8.1.  オーバーライド(インスタンス・メソッドを使用)

クラスCで宣言または継承されたインスタンス・メソッドmCクラスAで宣言された別のメソッドmACからオーバーライドします(次のすべてに該当する場合)。

  • Cは、Aのサブクラスである。

  • CmAを継承しません。

  • mCのシグネチャ(§8.4.2)は、Aという名前のCのスーパータイプのメンバーとしてのmAのシグネチャのサブシグネチャ(§8.4.2)です。

  • 次のいずれかがtrue:

    • mApublicです。

    • mAprotectedです。

    • mAは、Cと同じパッケージ内のパッケージ・アクセスで宣言され、CmCを宣言するか、mAが直接スーパークラス型Cのメンバーであることを宣言します。

    • mAはパッケージ・アクセスで宣言され、mCCの一部のスーパークラスのmAをオーバーライドします。

    • mAはパッケージ・アクセスで宣言され、mCCのメソッドm'をオーバーライドします(mCおよびmAとは異なるm')。これにより、m'CのスーパークラスのmAをオーバーライドします。

mCabstract以外で、CからabstractメソッドmAをオーバーライドする場合、mCCからmA実装します。

オーバーライドされたメソッドmAstaticメソッドである場合、コンパイル時にエラーが発生します。

この点において、メソッドのオーバーライドは、フィールド(§8.3)の非表示とは異なります。これは、インスタンス変数がstatic変数を非表示にすることが許可されるためです。

クラスCで宣言または継承されたインスタンス・メソッドmCインタフェースIで宣言された別のメソッドmICからオーバーライドします(次のすべてに該当する場合)。

  • Iは、Cのスーパーインタフェースである。

  • mIstaticではありません。

  • CmIを継承しません。

  • mCのシグネチャ(§8.4.2)は、Iという名前のCのスーパータイプのメンバーとしてのmIのシグネチャのサブシグネチャ(§8.4.2)です。

  • mIpublicです。

いずれかのメソッドの仮パラメータがRAW型を持ち、他のメソッドの対応するパラメータがパラメータ化された型を持つ場合、オーバーライドするメソッドのシグネチャは、オーバーライドされるメソッドのシグネチャとは異なる場合があります。 これにより、既存のコードの移行に対応し、汎用を活用できます。

オーバーライドの概念には、その宣言クラスのなんらかのサブクラスから別のメソッドをオーバーライドするメソッドが含まれます。 これは、2つの方法で考えられます。

  • 汎用スーパークラスの具象メソッドは、特定のパラメータ化の下で、そのクラスのabstractメソッドと同じシグネチャを持つことができます。 この場合、具象メソッドが継承され、abstractメソッドは継承されません(前述)。 継承されたメソッドは、その抽象ピアをCからオーバーライドするとみなされます。 (このシナリオは、パッケージ・アクセスによって複雑になります。Cが別のパッケージ内にある場合、mAはいずれも継承されておらず、オーバーライドされているとはみなされません。)

  • クラスから継承されるメソッドにより、スーパーインタフェース・メソッドをオーバーライドできます。 (幸い、パッケージ・アクセス権はここでは懸念事項ではありません。)

オーバーライドされたメソッドには、キーワードsuperを含むメソッド呼出し式(§15.12)を使用してアクセスできます。 修飾名またはスーパークラス型へのキャストは、オーバーライドされるメソッドへのアクセスの試行では、効果的ではありません。

この点に関しては、メソッドのオーバーライドはフィールドの非表示とは異なります。

strictfp修飾子の有無は、メソッドをオーバーライドし、abstractメソッドを実装するためのルールにはまったく影響しません。 たとえば、strictfp以外のメソッドがstrictfpメソッドをオーバーライドすることは許可され、strictfpメソッドがstrictfp以外のメソッドをオーバーライドすることは許可されます。

例 8.4.8.1-1. 上書き

class Point {
    int x = 0, y = 0;
    void move(int dx, int dy) { x += dx; y += dy; }
}
class SlowPoint extends Point {
    int xLimit, yLimit;
    void move(int dx, int dy) {
        super.move(limit(dx, xLimit), limit(dy, yLimit));
    }
    static int limit(int d, int limit) {
        return d > limit ? limit : d < -limit ? -limit : d;
    }
}

ここで、クラスSlowPointは、クラスPointのメソッドmoveの宣言を独自のmoveメソッドでオーバーライドします。これにより、メソッドの呼出しごとにポイントが移動できる距離が制限されます。 クラスSlowPointのインスタンスに対してmoveメソッドが呼び出されると、SlowPointオブジェクトへの参照がPoint型の変数から取得される場合でも、クラスSlowPointのオーバーライド定義が常に呼び出されます。


例 8.4.8.1-2. 上書き

オーバーライドにより、次の例に示すように、サブクラスで既存のクラスの動作を簡単に拡張できます。

import java.io.IOException;
import java.io.OutputStream;

class BufferOutput {
    private OutputStream o;
    BufferOutput(OutputStream o) { this.o = o; }
    protected byte[] buf = new byte[512];
    protected int pos = 0;
    public void putchar(char c) throws IOException {
        if (pos == buf.length) flush();
        buf[pos++] = (byte)c;
    }
    public void putstr(String s) throws IOException {
        for (int i = 0; i < s.length(); i++)
            putchar(s.charAt(i));
    }
    public void flush() throws IOException {
        o.write(buf, 0, pos);
        pos = 0;
    }
}
class LineBufferOutput extends BufferOutput {
    LineBufferOutput(OutputStream o) { super(o); }
    public void putchar(char c) throws IOException {
        super.putchar(c);
        if (c == '\n') flush();
    }
}
class Test {
    public static void main(String[] args) throws IOException {
        LineBufferOutput lbo = new LineBufferOutput(System.out);
        lbo.putstr("lbo\nlbo");
        System.out.print("print\n");
        lbo.putstr("\n");
    }
}

このプログラムは出力を生成します:

lbo
print
lbo

クラスBufferOutputは、非常に単純なバッファ・バージョンのOutputStreamを実装し、バッファがいっぱいになったとき、またはflushが呼び出されたときに出力をフラッシュします。 サブクラスLineBufferOutputは、コンストラクタと、BufferOutputのメソッドputcharをオーバーライドする単一のメソッドputcharのみを宣言します。 クラスBufferOutputからメソッドputstrおよびflushを継承します。

LineBufferOutputオブジェクトのputcharメソッドで、文字引数が改行の場合、flushメソッドを呼び出します。 この例でのオーバーライドに関する重要な点は、クラスBufferOutputで宣言されているメソッドputstrが、現在のオブジェクトthisで定義されたputcharメソッドを呼び出すことです。これは、必ずしもクラスBufferOutputで宣言されているputcharメソッドではありません。

したがって、LineBufferOutputオブジェクトlboを使用してputstrmainで呼び出された場合、putstrメソッドの本体でのputcharの呼出しは、改行をチェックするputcharのオーバーライド宣言であるオブジェクトlboputcharの呼出しです。 これにより、BufferOutputのサブクラスは、再定義せずにputstrメソッドの動作を変更できます。

拡張するように設計されたBufferOutputなどのクラスのドキュメントは、クラスとそのサブクラスの間の規約を明確に示し、この方法でサブクラスがputcharメソッドをオーバーライドできることを明確に示す必要があります。 したがって、BufferOutputクラスの実装者は、メソッドputcharを使用しないように、BufferOutputの将来の実装でputstrの実装を変更したくありません。これは、サブクラスとの既存の契約を破るためです。 §13 (Binary Compatibility) (特に §13.2)のバイナリ互換性の説明を参照してください。


8.4.8.2.  非表示(クラス・メソッドを使用)

クラスCstaticメソッドmを宣言または継承する場合、mは、クラスまたはインタフェースAで宣言されたメソッドm'非表示にし、次のすべてに該当するとみなされます。

  • Aは、Cのスーパークラスまたはスーパーインタフェースである。

  • Aがインタフェースの場合、m'はインスタンス・メソッドです。

  • m'には、C (§6.6)からアクセスできます。

  • mのシグネチャ(§8.4.2)は、Aという名前のCのスーパータイプのメンバーとしてのm'のシグネチャのサブシグネチャ(§8.4.2)です。

staticメソッドがインスタンス・メソッドを非表示にすると、コンパイル時にエラーが発生します。

この点において、メソッドの非表示はフィールド(§8.3)の非表示とは異なります。これは、static変数でインスタンス変数を非表示にできるためです。 非表示は、シャドウイング(§6.4.1)および不明瞭化(§6.4.2)とも区別されます。

非表示のメソッドには、修飾名を使用するか、キーワードsuperを含むメソッド呼出し式(§15.12)またはスーパークラス型へのキャストを使用してアクセスできます。

この点に関しては、メソッドの非表示はフィールドの非表示と同様です。

例 8.4.8.2-1.  隠されたクラス・メソッドの呼出し

非表示のクラス(static)メソッドは、メソッドの宣言を実際に含むクラスの型である参照を使用して起動できます。 この点において、staticメソッドの非表示は、インスタンス・メソッドのオーバーライドとは異なります。 次に例を示します。

class Super {
    static String greeting() { return "Goodnight"; }
    String name() { return "Richard"; }
}
class Sub extends Super {
    static String greeting() { return "Hello"; }
    String name() { return "Dick"; }
}
class Test {
    public static void main(String[] args) {
        Super s = new Sub();
        System.out.println(s.greeting() + ", " + s.name());
    }
}

出力の生成:

Goodnight, Dick

greetingの呼出しではsの型(Super)を使用して、どのクラス・メソッドを起動するかをコンパイル時に判断しますが、nameの呼出しでは、sのクラス(Sub)を使用して、実行時にどのインスタンス・メソッドを起動するかを判断します。


8.4.8.3.  オーバーライドおよび非表示の要件

戻り型R1のメソッド宣言d1が、戻り型R2の別のメソッドd2の宣言をオーバーライドまたは非表示にした場合、d1d2の戻り型置換可能(§8.4.5)である必要があります。そうでない場合、コンパイル時にエラーが発生します。

このルールにより、オーバーライド時にメソッドの戻り型を改良する共変の戻り型が可能になります。

R1R2のサブタイプでない場合、@SuppressWarnings (§9.6.4.5)によって抑制されないかぎり、コンパイル時の未チェックの警告が発生します。

インタフェースで定義されたabstractメソッドを実装するメソッドなど、別のメソッドをオーバーライドまたは非表示にするメソッドは、オーバーライドまたは非表示のメソッドよりも多くのチェック済例外をスローするように宣言することはできません。

この点において、メソッドのオーバーライドは、フィールドの非表示(§8.3)とは異なります。これは、あるフィールドが別の型のフィールドを非表示にできるためです。

具体的には、Bがクラスまたはインタフェースで、ABのスーパークラスまたはスーパーインタフェースで、Bのメソッド宣言m2Aのメソッド宣言m1をオーバーライドまたは非表示にするとします。 次に、

  • m2に、チェックされた例外タイプを示すthrows句がある場合は、m1throws句を指定するか、コンパイル時にエラーが発生します。

  • m2throws句にリストされているすべてのチェック済例外タイプについて、同じ例外クラスまたはそのスーパータイプの1つが、m1throws句の消去(§4.6)で発生する必要があります。それ以外の場合、コンパイル時にエラーが発生します。

  • m1の未消去のthrows句に、m2throws句(必要に応じて、m1の型パラメータに適用)の各例外型のスーパータイプが含まれていない場合、@SuppressWarnings (§9.6.4.5)で抑制されないかぎり、コンパイル時の未チェックの警告が発生します。

クラスまたはインタフェースCにメンバー・メソッドm1があり、Cで宣言されたメソッドm2が存在する場合、またはCAのスーパークラスまたはスーパーインタフェースが存在する場合、次のすべてに当てはまるようにコンパイル時にエラーが発生します。

  • m1m2は同じ名前です。

  • m2は、Cからアクセス可能(§6.6)です。

  • m1のシグネチャ(§8.4.2)は、Aという名前のCのスーパータイプのメンバーとしてのm2のシグネチャのサブシグネチャ(§8.4.2)ではありません。

  • m1または一部のメソッドのm1オーバーライドの宣言されたシグネチャ(直接的または間接的)は、m2の宣言されたシグネチャまたは一部のメソッドのm2オーバーライド(直接的または間接的)と同じ消去を持ちます。

イレイジャを介して汎用が実装されるため、これらの制限が必要となります。 前述のルールは、同じクラスで宣言された同じ名前のメソッドは異なるイレイジャを持つ必要があることを示しています。 また、同じ汎用インタフェースの2つの異なるパラメータ化を実装または拡張できないことも意味します。

オーバーライドするメソッドまたは隠すメソッドのアクセス修飾子は、次のように、少なくともオーバーライドされるメソッドまたは隠されるメソッドと同程度のアクセスを提供する必要があります。

  • オーバーライドまたは非表示のメソッドがpublicの場合、オーバーライドまたは非表示のメソッドはpublicである必要があります。それ以外の場合、コンパイル時にエラーが発生します。

  • オーバーライドまたは非表示のメソッドがprotectedの場合、オーバーライドまたは非表示のメソッドはprotectedまたはpublicである必要があります。それ以外の場合、コンパイル時にエラーが発生します。

  • オーバーライド・メソッドまたは非表示メソッドにパッケージ・アクセスがある場合、オーバーライド・メソッドまたは非表示メソッドをprivateしない必要があります。そうしないと、コンパイル時にエラーが発生します。

privateメソッドは、これらの用語の技術的な意味ではオーバーライドまたは非表示にできないことに注意してください。 これは、サブクラスがそのスーパークラスの1つでprivateメソッドと同じシグネチャを持つメソッドを宣言できることを意味し、このようなメソッドの戻り型またはthrows句がスーパークラスのprivateメソッドのそれと関係を持つという要件はありません。

例 8.4.8.3-1. 共変戻り型

Java SE 5.0以降のJavaプログラミング言語では、次の宣言を使用できます。

class C implements Cloneable {
    C copy() throws CloneNotSupportedException {
        return (C)clone();
    }
}
class D extends C implements Cloneable {
    D copy() throws CloneNotSupportedException {
        return (D)clone();
    }
}

オーバーライドの緩和されたルールでは、インタフェースを実装する抽象クラスの条件を緩和することもできます。


例 8.4.8.3-2. 戻り型からの未チェックの警告

次の点を考慮してください。


class StringSorter {
    // turns a collection of strings into a sorted list
    List toList(Collection c) {...}
}

また、誰かがStringSorterをサブクラス化するとします。


class Overrider extends StringSorter {
    List toList(Collection c) {...}
}

これで、ある時点で、StringSorterの作成者がコードを生成することを決定しました。


class StringSorter {
    // turns a collection of strings into a sorted list
    List<String> toList(Collection<String> c) {...}
}

Overrider.toListの戻り型はListで、オーバーライドされたメソッドList<String>の戻り型のサブタイプではないため、StringSorterの新しい定義に対してOverriderをコンパイルすると、未チェックの警告が表示されます。


例 8.4.8.3-3. throwsによる誤ったオーバーライド

このプログラムは、クラスBadPointExceptionの宣言で新しい例外型を宣言するために、通常形式と従来形式を使用します。

class BadPointException extends Exception {
    BadPointException() { super(); }
    BadPointException(String s) { super(s); }
}
class Point {
    int x, y;
    void move(int dx, int dy) { x += dx; y += dy; }
}
class CheckedPoint extends Point {
    void move(int dx, int dy) throws BadPointException {
        if ((x + dx) < 0 || (y + dy) < 0)
            throw new BadPointException();
        x += dx; y += dy;
    }
}

クラスCheckedPointのメソッドmoveをオーバーライドすると、クラスPointmoveが宣言されていないというチェック例外がスローされると宣言されるため、プログラムはコンパイル時にエラーが発生します。 これがエラーとみなされなかった場合、Point型の参照に対するメソッドmoveの起動側は、この例外がスローされた場合に、そのメソッドとPointとの間の契約が破損していることを検出できます。

throws句を削除しても効果はありません。

class CheckedPoint extends Point {
    void move(int dx, int dy) {
        if ((x + dx) < 0 || (y + dy) < 0)
            throw new BadPointException();
        x += dx; y += dy;
    }
}

別のコンパイル時エラーが発生するようになりました。これは、メソッドmoveの本体が、movethrows句にないチェック済例外(BadPointException)をスローできないためです。


例 8.4.8.3-4. 消去による上書きの影響

クラスには、同じ名前と型消去を持つ2つのメンバー・メソッドを含めることはできません。


class C<T> {
    T id (T x) {...}
}
class D extends C<String> {
    Object id(Object x) {...}
}

これは、D.id(Object)Dのメンバーであり、C<String>.id(String)Dのスーパータイプで宣言され、次の処理が行われるため、無効です。

  • 2つのメソッドの名前は同じです(id)。

  • C<String>.id(String)にはDからアクセスできます。

  • D.id(Object)のシグネチャは、C<String>.id(String)のシグネチャではありません

  • 2つのメソッドは同じ消去を持ちます。

クラスの2つの異なるメソッドで、同じイレイジャを持つメソッドをオーバーライドすることはできません。


class C<T> {
    T id(T x) {...}
}
interface I<T> {
    T id(T x);
}
class D extends C<String> implements I<Integer> {
   public String  id(String x)  {...}
   public Integer id(Integer x) {...}
}

これは、D.id(String)Dのメンバーであるため、D.id(Integer)Dで宣言され、次の場合も違法です。

  • 2つのメソッドの名前は同じです(id)。

  • D.id(Integer)にはDからアクセスできます。

  • 2つの方法には異なる署名があります(どちらも他方のサブシグネチャではありません)。

  • D.id(String)C<String>.id(String)をオーバーライドし、D.id(Integer)I.id(Integer)をオーバーライドしますが、オーバーライドされた2つのメソッドは同じ消去を持ちます


8.4.8.4. Override-Equivalentシグネチャを使用したメソッドの継承

クラスは、オーバーライド等価のシグネチャを持つ複数のメソッドを継承できます(§8.4.2)。

クラスCが、署名がCによって継承された別のメソッドとオーバーライド等価である具象メソッドを継承する場合、コンパイル時にエラーが発生します。

クラスCが、Cが継承する別のメソッドとオーバーライド等価のシグネチャを持つデフォルト・メソッドを継承する場合、コンパイル時にエラーが発生します。ただし、Cのスーパークラスで宣言され、2つのメソッドとオーバーライド等価のCが継承するabstractメソッドが存在する場合を除きます。

厳密なdefault-abstractおよびdefault-default競合ルールに対するこの例外は、スーパークラスでabstractメソッドが宣言されている場合に発生します。つまり、スーパークラス階層からの抽象性のアサーションは、基本的にデフォルト・メソッドを切り捨て、デフォルト・メソッドはabstractであるかのように動作します。 ただし、クラス階層からのabstractメソッドのシグネチャをインタフェースで改良できるため、クラスからのabstractメソッドはデフォルトのメソッドをオーバーライドしません。

Cによって継承されたすべてのオーバーライド等価のabstractメソッドがインタフェースで宣言されている場合、例外は適用されません。

それ以外の場合、オーバーライド等価メソッドのセットは、少なくとも1つのabstractメソッドと0個以上のデフォルト・メソッドで構成されます。その場合、クラスは必ずしもabstractクラスであり、すべてのメソッドを継承するとみなされます。

継承されたメソッドの1つは、他のすべての継承されたメソッドに対してreturn-type-substitutableである必要があります。そうでない場合は、コンパイル時にエラーが発生します。 (この場合、throws句によってエラーは発生しません。)

同じメソッド宣言がインタフェースから継承される複数のパスが存在する場合があります。 この事実は難しくなく、それ自体ではコンパイル時にエラーが発生することはありません。

例 8.4.8.4-1. 同等のオーバーライド・メソッドの継承

前述の、具象メソッドを継承するクラスCに関する最初のコンパイル時エラーは、Cのスーパークラスが汎用であり、スーパークラスに2つのメソッドがあり、汎用宣言では区別されていても、Cで使用されるパラメータ化(§4.5)で同じシグネチャを持つ場合に発生する可能性があります。 たとえば:


class A<T> {
    void m(String s) {} // 1
    void m(T t) {} // 2
}
class C extends A<String> {}

Cは、直接スーパークラス型A<String>から2つのメソッドを継承します。1でマークされたメソッドm(String)と、2でマークされたメソッドm(String) (CAのパラメータ化による)です。 これらのメソッドには同じシグネチャがあるため、互いにオーバーライド等価です。


8.4.9. オーバーロード

クラスの2つのメソッド(同じクラスで宣言されたメソッド、またはクラスによって継承されたメソッド、または宣言されたメソッドと継承されたメソッドの両方)が同じ名前で、オーバーライド等価ではないシグネチャを持つ場合、メソッド名はオーバーロードされます。

この事実は難しくなく、それ自体ではコンパイル時にエラーが発生することはありません。 戻り型間、または同じ名前の2つのメソッドのthrows句の間には、シグネチャがオーバーライド等価でないかぎり、必要な関係はありません。

メソッドが呼び出されると(§15.12)、実際の引数の数(および明示的な型引数)、および引数のコンパイル時型がコンパイル時に、呼び出されるメソッドのシグネチャを決定するために使用されます(§15.12.2)。 呼び出されるメソッドがインスタンス・メソッドである場合、呼び出される実際のメソッドは、動的メソッド参照(§15.12.4)を使用して実行時に決定されます。

例8.4.9-1. オーバーロード

class Point {
    float x, y;
    void move(int dx, int dy) { x += dx; y += dy; }
    void move(float dx, float dy) { x += dx; y += dy; }
    public String toString() { return "("+x+","+y+")"; }
}

ここで、クラスPointには、moveという同じ名前のメソッドである2つのメンバーがあります。 特定のメソッド呼出しに選択されたクラスPointのオーバーロードされたmoveメソッドは、§15.12に示されているオーバーロード解決プロシージャによってコンパイル時に決定されます。

合計で、クラスPointのメンバーは、Pointで宣言されたfloatインスタンス変数xおよびy、宣言された2つのmoveメソッド、宣言されたtoStringメソッド、およびPointが暗黙的な直接スーパークラスObject (§4.3.2)から継承するメンバー(メソッドhashCodeなど)です。 クラスObjecttoStringメソッドは、クラスPointtoStringメソッドの宣言によってオーバーライドされるため、PointはクラスObjecttoStringメソッドを継承しません。


例8.4.9-2. オーバーロード、オーバーライドおよび非表示

class Point {
    int x = 0, y = 0;
    void move(int dx, int dy) { x += dx; y += dy; }
    int color;
}
class RealPoint extends Point {
    float x = 0.0f, y = 0.0f;
    void move(int dx, int dy) { move((float)dx, (float)dy); }
    void move(float dx, float dy) { x += dx; y += dy; }
}

ここで、クラスRealPointは、クラスPointintインスタンス変数xおよびyの宣言を、独自のfloatインスタンス変数xおよびyで非表示にし、クラスPointのメソッドmoveを独自のmoveメソッドでオーバーライドします。 また、moveという名前を別のシグネチャを持つ別のメソッド(§8.4.2)でオーバーロードします。

この例では、クラスRealPointのメンバーには、クラスPointから継承されたインスタンス変数colorRealPointで宣言されたfloatインスタンス変数xおよびy、およびRealPointで宣言された2つのmoveメソッドが含まれます。

RealPointクラスのこれらのオーバーロードされたmoveメソッドのうち、特定のメソッド呼出しに対して選択されるものは、§15.12で説明されているオーバーロード解決プロシージャによってコンパイル時に決定されます。

次のプログラムは、前述のプログラムの拡張されたバリエーションです。

class Point {
    int x = 0, y = 0, color;
    void move(int dx, int dy) { x += dx; y += dy; }
    int getX() { return x; }
    int getY() { return y; }
}
class RealPoint extends Point {
    float x = 0.0f, y = 0.0f;
    void move(int dx, int dy) { move((float)dx, (float)dy); }
    void move(float dx, float dy) { x += dx; y += dy; }
    float getX() { return x; }
    float getY() { return y; }
}

ここで、クラスPointは、フィールドxおよびyの値を返すメソッドgetXおよびgetYを提供します。その後、クラスRealPointは、同じシグネチャを持つメソッドを宣言して、これらのメソッドをオーバーライドします。 戻り型が一致しないため、コンパイル時に各メソッドに1つずつ2つのエラーが発生します。クラスPointのメソッドはint型の値を返しますが、float型の値を返すクラスRealPointのメソッドをオーバーライドします。

このプログラムは、前述のプログラムのエラーを修正します。

class Point {
    int x = 0, y = 0;
    void move(int dx, int dy) { x += dx; y += dy; }
    int getX() { return x; }
    int getY() { return y; }
    int color;
}
class RealPoint extends Point {
    float x = 0.0f, y = 0.0f;
    void move(int dx, int dy) { move((float)dx, (float)dy); }
    void move(float dx, float dy) { x += dx; y += dy; }
    int getX() { return (int)Math.floor(x); }
    int getY() { return (int)Math.floor(y); }
}

ここで、クラスRealPointのオーバーライド・メソッドgetXおよびgetYの戻り型は、オーバーライドするクラスPointのメソッドと同じであるため、このコードを正常にコンパイルできます。

次に、このテスト・プログラムについて考えてみます。

class Test {
    public static void main(String[] args) {
        RealPoint rp = new RealPoint();
        Point p = rp;
        rp.move(1.71828f, 4.14159f);
        p.move(1, -1);
        show(p.x, p.y);
        show(rp.x, rp.y);
        show(p.getX(), p.getY());
        show(rp.getX(), rp.getY());
    }
    static void show(int x, int y) {
        System.out.println("(" + x + ", " + y + ")");
    }
    static void show(float x, float y) {
        System.out.println("(" + x + ", " + y + ")");
    }
}

このプログラムからの出力は次のとおりです。

(0, 0)
(2.7182798, 3.14159)
(2, 3)
(2, 3)

出力の最初の行は、RealPointのインスタンスが実際にクラスPointで宣言された2つの整数フィールドを含んでいるという事実を示しています。その名前は、クラスRealPointの宣言(およびそのサブクラスが持つ可能性のあるもの)内に存在するコードからは隠されています。 Point型の変数内のクラスRealPointのインスタンスへの参照を使用してフィールドxにアクセスすると、クラスPointで宣言された整数フィールドxがアクセスされます。 その値がゼロであるという事実は、メソッド呼出しp.move(1, -1)がクラスPointのメソッドmoveを呼び出さず、かわりにクラスRealPointのオーバーライド・メソッドmoveを呼び出したことを示しています。

出力の2行目は、フィールド・アクセスrp.xが、クラスRealPointで宣言されたフィールドxを参照していることを示しています。 このフィールドはfloat型で、この2行目の出力ではそれに応じて浮動小数点値が表示されます。 ちなみに、これは、メソッド名showがオーバーロードされているという事実も示しています。メソッド呼出しの引数の型によって、起動される2つの定義が決まります。

出力の最後の2行は、メソッドがp.getX()およびrp.getX()を起動すると、それぞれクラスRealPointで宣言されたgetXメソッドが呼び出されることを示しています。 実際、オブジェクトへの参照を保持するために使用できる変数の型に関係なく、クラスRealPointのインスタンスのクラスPointgetXメソッドをRealPointの本体の外から呼び出す方法はありません。 したがって、フィールドとメソッドの動作が異なります。非表示はオーバーライドとは異なります。


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

メンバー・クラスは、宣言が別のクラスまたはインタフェース宣言の本体に直接囲まれているクラスです(§8.1.7§9.1.5)。

メンバー・インタフェースは、宣言が別のクラスまたはインタフェース宣言の本体に直接囲まれているインタフェースです。

メンバー・クラスは、標準クラス(§8.1)、列挙クラス(§8.9)、またはレコード・クラス(§8.10)です。

メンバー・インタフェースは、標準インタフェース(§9.1)または注釈インタフェース(§9.6)です。

クラス宣言の本体におけるメンバー・クラスまたはインタフェース宣言のアクセシビリティは、そのアクセス修飾子によって、またはアクセス修飾子がなければ§6.6によって指定されます。

クラス宣言の本体におけるメンバー・クラス宣言の修飾子のルールは、§8.1.1で指定されています。

クラス宣言の本体におけるメンバー・インタフェース宣言の修飾子に関する規則は、§9.1.1に規定されています。

メンバー・クラスまたはインタフェースのスコープとシャドウは、§6.3および§6.4.1で指定されています。

クラスが特定の名前を持つメンバー・クラスまたはインタフェースを宣言する場合、メンバー・クラスまたはインタフェースの宣言は、クラスのスーパークラスおよびスーパーインタフェースで同じ名前を持つメンバー・クラスおよびインタフェースのすべてのアクセス可能な宣言を非表示にします。

この点において、メンバー・クラスおよびインタフェースを非表示にすることは、フィールドの非表示に類似しています(§8.3)。

クラスは、そのダイレクト・スーパークラスおよびダイレクト・スーパーインタフェースから継承します。スーパークラスおよびスーパークラスのすべての非privateメンバー・クラスおよびインタフェースは、クラス内のコードにアクセスでき、クラス内の宣言では非表示になりません。

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

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

8.6.  インスタンス初期化子

クラスで宣言されたインスタンス・イニシャライザは、クラスのインスタンスが作成されたときに実行されます(§12.5§15.9§8.8.7.1)。

InstanceInitializer:

インスタンス・イニシャライザが正常に完了できない場合、コンパイル時にエラーが発生します(§14.22)。

return文(§14.17)がインスタンス・イニシャライザ内の任意の場所にある場合、コンパイル時にエラーが発生します。

インスタンス・イニシャライザは、キーワードthis (§15.8.3)またはキーワードsuper (§15.11.2§15.12)を使用して現在のオブジェクトを参照し、スコープ内の任意の型変数を使用することを許可されます。

インスタンス変数がスコープ内にある場合でも、インスタンス・イニシャライザがインスタンス変数を参照する方法に関する制限は、§8.3.3に記載されています。

インスタンス・イニシャライザの例外チェックは、§11.2.3で指定されています。

8.7.  静的イニシャライザ

クラスで宣言された静的イニシャライザは、クラスが初期化されるときに実行されます(§12.4.2)。 クラス変数のフィールド・イニシャライザ(§8.3.2)とともに、静的イニシャライザを使用してクラスのクラス変数を初期化できます。

StaticInitializer:
static ブロック

静的イニシャライザが正常に完了できない場合、コンパイル時にエラーが発生します(§14.22)。

return文(§14.17)が静的イニシャライザ内の任意の場所にある場合、コンパイル時にエラーが発生します。

静的イニシャライザは、現在のオブジェクトを参照するコンストラクトの使用を制限する静的コンテキスト(§8.1.3)を導入します。 特に、キーワードthisおよびsuperは、静的コンテキスト(§15.8.3§15.11.2)では禁止されており、字句的に包含する宣言のインスタンス変数、インスタンス・メソッドおよび型パラメータへの修飾されていない参照です(§6.5.5.1§6.5.6.1§15.12.3)。

静的イニシャライザがクラス変数を参照する方法に関する制限は、クラス変数がスコープ内にある場合でも、§8.3.3に規定されています。

静的イニシャライザの例外チェックは、§11.2.3で規定されています。

8.8.  コンストラクタ宣言

コンストラクタは、クラスのインスタンスであるオブジェクトの作成に使用されます(§12.5§15.9)。

このセクションのルールは、enum宣言およびレコード宣言を含むすべてのクラス宣言内のコンストラクタに適用されます。 ただし、コンストラクタ修飾子、コンストラクタ本体およびデフォルト・コンストラクタに関して、列挙宣言には特別なルールが適用されます。これらのルールは、§8.9.2に記載されています。 コンストラクタに関するレコード宣言には、§8.10.4に記載されている特別なルールも適用されます。

ConstructorDeclaratorSimpleTypeNameは、コンストラクタ宣言を含むクラスの単純名である必要があります。そうしないと、コンパイル時にエラーが発生します。

その他の点では、コンストラクタ宣言は、結果を持たないメソッド宣言に似ています(§8.4.5)。

コンストラクタの宣言はメンバーではありません。 これらは継承されないため、非表示または上書きの対象になりません。

コンストラクタは、クラス・インスタンス作成式(§15.9)、文字列連結演算子+(§15.18.1)に起因する変換と連結、および他のコンストラクタからの明示的なコンストラクタ呼出し(§8.8.7)によって呼び出されます。 コンストラクタへのアクセスはアクセス修飾子(§6.6)によって制御されるため、アクセスできないコンストラクタ(§8.8.10)を宣言することで、クラスのインスタンス化を防ぐことができます。

コンストラクタは、メソッド呼出し式によって呼び出されることはありません(§15.12)。

例8.8-1.  コンストラクタ宣言

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}

8.8.1.  仮パラメータ

コンストラクタの仮パラメータは、構文およびセマンティクスにおいてメソッド(§8.4.1)の仮パラメータと同一です。

コンストラクタの最後の仮パラメータが可変引数パラメータの場合、コンストラクタは可変引数コンストラクタです。 それ以外の場合は、固定アリティ・コンストラクタです。

private内部メンバー・クラスのコンストラクタは、最初の仮パラメータとして、クラスの直近のインスタンスを表す変数を暗黙的に宣言します(§15.9.2§15.9.3)。

暗黙的に宣言されたコンストラクタ・パラメータを持つのは、この種のクラスのみである理由の根拠です。 次の説明が役立つ場合があります:

  1. private以外の内部メンバー・クラスのクラス・インスタンス作成式では、§15.9.2によって、メンバー・クラスの直前に包含されるインスタンスが指定されます。 メンバー・クラスは、クラス・インスタンス作成式のコンパイラとは異なるコンパイラによって発行された可能性があります。 したがって、作成式のコンパイラが参照(すぐに囲んでいるインスタンスを表す)をメンバー・クラス・コンストラクタに渡すには、標準的な方法が必要です。 したがって、Javaプログラミング言語は、この項で、private以外の内部メンバー・クラスのコンストラクタが、直近のインスタンスの初期パラメータを暗黙的に宣言していると見なします。§15.9.3では、インスタンスがコンストラクタに渡されることを指定します。

  2. 内部ローカル・クラスまたは無名クラス(静的コンテキストにはない)のクラス・インスタンス作成式では、§15.9.2は、ローカル/無名クラスの直近のインスタンスを指定します。 ローカル/匿名クラスは、クラス・インスタンスの作成式と同じコンパイラによって発行される必要があります。 そのコンパイラは、必要なインスタンスをすぐに囲むことができます。 Javaプログラミング言語でローカル/匿名クラス・コンストラクタのパラメータを暗黙的に宣言する必要はありません。

  3. 無名クラスのクラス・インスタンス作成式で、無名クラスのスーパークラスが(静的コンテキストではなく)内部クラスである場合、§15.9.2は、スーパークラスに関して無名クラスの直近のインスタンスを指定します。 このインスタンスは、匿名クラスからスーパークラスに転送する必要があります。スーパークラスでは、このインスタンスはすぐに囲まれたインスタンスとして機能します。 スーパークラスはクラス・インスタンス作成式のコンパイラとは異なるコンパイラによって発行された可能性があるため、インスタンスを最初の引数としてスーパークラス・コンストラクタに渡すことで、標準的な方法で転送する必要があります。 匿名クラス自体は、クラス・インスタンス作成式と同じコンパイラによって発行される必要があるため、匿名クラスがスーパークラス・コンストラクタにインスタンスを渡す前に、スーパークラスに関して直接包含するインスタンスを匿名クラスに転送できます。 ただし、一貫性を保つために、Javaプログラミング言語は§15.9.5.1で、状況によっては、匿名クラスのコンストラクタが、スーパークラスに関して即座に包含するインスタンスの初期パラメータを暗黙的に宣言していると判断します。

private内部メンバー・クラスには、コンパイルされたものとは異なるコンパイラからアクセスできますが、内部ローカル・クラスまたは匿名クラスは、常に同じコンパイラからアクセスされます。非private内部メンバー・クラスのバイナリ名が予測可能であると定義されているが、内部ローカル・クラスまたは無名クラスのバイナリ名が予測可能ではない理由を説明する(§13.1)。

8.8.2. コンストラクタの署名

オーバーライド等価のシグネチャ(§8.4.2)を持つ2つのコンストラクタをクラスで宣言すると、コンパイル時にエラーが発生します。

クラス内で同じ消去(§4.6)を持つ署名を持つ2つのコンストラクタを宣言すると、コンパイル時にエラーが発生します。

8.8.3.  コンストラクタ修飾子

ConstructorModifier:
(いずれか)
Annotation public protected private

コンストラクタ宣言の注釈修飾子に関する規則は、§9.7.4および§9.7.5で指定されています。

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

通常のクラス宣言では、アクセス修飾子のないコンストラクタ宣言はパッケージ・アクセスを持ちます。

2つ以上の(個別)メソッド修飾子がメソッド宣言に表示される場合、MethodModifierの本番で前述したものと一致する順序で表示されることは、必須ではありませんが慣例です。

メソッドとは異なり、コンストラクタはabstractstaticfinalnativestrictfpまたはsynchronizedにできません。

  • コンストラクタは継承されないため、finalを宣言する必要はありません。

  • abstractコンストラクタは実装できませんでした。

  • コンストラクタは、常にオブジェクトに関して呼び出されるため、コンストラクタがstaticであることは意味がありません。

  • コンストラクタは、構築中のオブジェクトをロックするため、実際にはsynchronizedである必要はありません。これは、通常、オブジェクトのすべてのコンストラクタが作業を完了するまで、他のスレッドでは使用できないためです。

  • nativeコンストラクタの欠如は、Java Virtual Machineの実装がオブジェクトの作成時にスーパークラス・コンストラクタが常に適切に呼び出されることを簡単に検証できるようにする任意の言語設計選択です。

  • コンストラクタをstrictfpとして宣言できないこと(メソッド(§8.4.3)とは対照的に)は、意図的な言語設計の選択であり、クラスをstrictfpとして宣言する(現在は廃止されている)機能から生じます。

8.8.4.  汎用コンストラクタ

コンストラクタは、1つ以上の型変数(§4.4)を宣言する場合、汎用です。

これらの型変数は、コンストラクタの型パラメータと呼ばれます。 汎用コンストラクタの型パラメータ・セクションの形式は、汎用クラスの型パラメータ・セクション(§8.1.2)と同じです。

コンストラクタが宣言されているクラス自体がジェネリックであるかどうかに関係なく、コンストラクタをジェネリックにすることができます。

ジェネリック・コンストラクタ宣言は、型引数によって型パラメータ・セクションが呼び出されるたびに、コンストラクタのセットを定義します。 型引数は、一般コンストラクタが呼び出されたときに明示的に指定する必要がない場合があります。これは、推測されることが多いためです(§18 (Type Inference))。

コンストラクタの型パラメータのスコープとシャドウは、§6.3および§6.4.1で指定されています。

コンストラクタの呼出しまたはネストされたクラスまたはインタフェースからのコンストラクタの型パラメータへの参照は、§6.5.5.1で指定されているように制限されます。

8.8.5. コンストラクタのスロー

コンストラクタのthrows句の構造と動作は、メソッドのthrows句(§8.4.6)と同じです。

8.8.6. コンストラクタのタイプ

コンストラクタの型は、そのシグネチャと、そのthrows句で指定された例外型で構成されます。

8.8.7.  コンストラクタ本体

コンストラクタ本体は、クラスの新しいインスタンスを作成するプロセスの一部として実行されるコードのブロックです(§12.5)。 コンストラクタ本体には、同じクラスまたは直接スーパークラス(§8.8.7.1)の別のコンストラクタの明示的な呼出しを含めることができます。

コンストラクタ本体に明示的なコンストラクタ呼出しが含まれている場合、コンストラクタ呼出しの前のBlockStatementsは、コンストラクタ本体のprologueと呼ばれます。 コンストラクタ本体のプロローグは空の場合があります。 明示的なコンストラクタ呼出しのないコンストラクタのBlockStatementsと、コンストラクタ本体でのコンストラクタ呼出しに続くBlockStatementsは、epilogueと呼ばれます。 コンストラクタ本体のエピローグも空の場合があります。

コンストラクト(文、ローカル変数宣言文、ローカル・クラス宣言、ローカル・インタフェース宣言または式)Cは、Cのコンストラクタ本体のプロローグに含まれている場合、またはCのコンストラクタ本体のコンストラクタ呼出し(§8.8.7.1)にネストされている場合に、クラスの初期構成コンテキストで発生します。

コンストラクタ本体に明示的なコンストラクタ呼出しが含まれておらず、宣言されているコンストラクタが原始クラスObjectの一部ではない場合、コンストラクタ本体(i)には空のprologueがあり、(ii)暗黙的にスーパークラスで始まりますコンストラクタ呼出し"super();"は、引数をとらない直接スーパークラスのコンストラクタの暗黙的な呼出しであり、(iii)コンストラクタ本体に指定された文(ある場合)は、コンストラクタ本体のエピローグを形成するために取得されます。

明示的または暗黙的なコンストラクタ呼出しの可能性、およびreturn文の禁止(§14.17)を除き、コンストラクタの本体はメソッドの本体と似ています(§8.4.7)。

コンストラクタ本体には、最大1つのコンストラクタ呼出しが含まれていることに注意してください。 文法では、たとえば、コンストラクタ呼出しをif文の異なるブランチに配置することは不可能です。

例8.8.7-1.  コンストラクタ本体

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, WHITE);
    }
    ColoredPoint(int x, int y, int color) {
        super(x, y);
        this.color = color;
    }
}

ここで、ColoredPointの最初のコンストラクタは、追加の引数を指定して2番目のコンストラクタを呼び出します。ColoredPointの2番目のコンストラクタは、そのスーパークラスPointのコンストラクタを呼び出して、座標を渡します。


8.8.7.1. コンストラクタ呼出し

ConstructorInvocation:

便宜上、§4.5.1および §15.12からの次のプロダクションがここに示されています。

TypeArguments:
ArgumentList:
{, }

コンストラクタの呼出しは、次の2種類に分けられます。

  • 代替コンストラクタ呼出しは、キーワードthisで始まります(明示的な型引数が先頭に付く場合もあります)。 同じクラスの代替コンストラクタを呼び出すために使用されます。

  • スーパークラスのコンストラクタ呼出しは、キーワードsuper (明示的な型引数で先頭に置かれる場合もあります)またはPrimary式またはExpressionNameのいずれかで始まります。 直接スーパークラスのコンストラクタを呼び出すために使用されます。 さらに次のように分割されます:

    • 修飾されていないスーパークラス・コンストラクタ呼出しは、キーワードsuperで始まります(明示的な型引数で先頭に置かれる場合もあります)。

    • 修飾スーパークラスのコンストラクタ呼出しは、Primary式またはExpressionNameで始まります。 これにより、サブクラス・コンストラクタは、直接スーパークラス(§8.1.3)に関して、新しく作成されたオブジェクトの直近の包含インスタンスを明示的に指定できます。 これは、スーパークラスが内部クラスの場合に必要になることがあります。

コンストラクタで一連の1つ以上の代替コンストラクタ呼出しによって直接的または間接的にそれ自体を呼び出すと、コンパイル時にエラーが発生します。

コンストラクタ呼出しでは、初期のコンストラクション・コンテキスト(§8.8.7)が導入され、現在のオブジェクトを参照するコンストラクトの使用が制限されます。 特に、thisおよびsuperを使用した現在のオブジェクトへの参照は、初期構成コンテキスト(§15.8.3§15.11.2)において、インスタンス変数(§6.5.6.1)およびインスタンス・メソッド(§6.5.7.1)への参照として制限されています。

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

Cをインスタンス化するクラスにし、SCの直接スーパークラスにします。

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

  • Sが内部メンバー・クラスで、SCを囲むクラスのメンバーではない場合、コンパイル時にエラーが発生します。

    そうでない場合は、OSがメンバーである最も内側の包括クラスCにします。 CO (§8.1.3)の内部クラスでなければなりません。そうでなければ、コンパイル時にエラーが発生します。 スーパークラス・コンストラクタ呼出しがクラスOの早期構築コンテキストに出現すると、コンパイル時にエラーが発生します。

  • Sが内側のローカルクラスで、Sが静的コンテキストで発生しない場合は、OSの直前に包含されるクラスまたはインタフェース宣言にしてください。 COの内部クラスである必要があり、そうでない場合はコンパイル時にエラーが発生します。

  • Sが静的コンテキストで宣言が発生する内部ローカル・クラスである場合、Nを最も近いstaticメソッド宣言、staticフィールド宣言またはSの宣言を包含する静的イニシャライザにします。 Nが、最も近いstaticメソッド宣言、staticフィールド宣言、またはスーパークラスのコンストラクタ呼出しを囲む静的イニシャライザでない場合、コンパイル時にエラーが発生します。

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

  • Sが内部クラスでない場合、またはSの宣言が静的コンテキストで発生した場合は、コンパイル時にエラーが発生します。

  • それ以外の場合は、pPrimary式または.superの直前のExpressionNameにし、OSの直近のクラスにします。 pの型がOまたはOのサブクラスでない場合、またはpの型にアクセスできない場合(§6.6)は、コンパイル時にエラーが発生します。

明示的なコンストラクタ呼出しがスローできる例外型は、§11.2.2で指定します。

代替コンストラクタ呼出しの評価は、通常のメソッド呼出しと同様に、最初にコンストラクタの引数を左から右に評価し、次にコンストラクタを呼び出して行われます。

iを、スーパークラスのコンストラクタ呼出しによって作成されるインスタンスにします。 S(存在する場合)に関してiの直近のインスタンスは、次のように決定されます。

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

  • そうでない場合、スーパークラス・コンストラクタ呼出しが非修飾であれば、Sは内部ローカル・クラスまたは内部メンバー・クラスである必要があります。

    Sが内側のローカルクラスである場合は、OSの直近のクラスまたはインタフェース宣言にしてください。

    Sが内部メンバー・クラスである場合、OSがメンバーであるCの最も内側の包含クラスにします。 スーパークラス・コンストラクタ呼出しがクラスOの早期構築コンテキストに出現すると、コンパイル時にエラーが発生します。

    nを整数(n 1)とし、Onの字句で囲まれたクラスまたはCのインタフェース宣言となるようにします。

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

    継承が原因でSCのメンバーである場合もありますが、thisのゼロの字句で囲んでいるインスタンス(つまり、this自体)は、Sに関してiの直近のインスタンスとして使用されません。

  • それ以外の場合、スーパークラスのコンストラクタ呼出しが修飾されている場合、Sに関してiの直近のインスタンスは、Primary式またはExpressionNameの値であるオブジェクトです。

スーパークラス・コンストラクタ呼出しに即時包含インスタンスがある場合、このインスタンスはコンストラクタ呼出しの最初の実際の引数とみなされ、コンストラクタ呼出しに対する後続の実際の引数は、スーパークラス・コンストラクタ呼出しの引数リスト(ある場合)内の引数とみなされます(出現する順序で)。 それ以外の場合、コンストラクタ呼出しの実際の引数は、スーパークラス・コンストラクタ呼出しの引数(ある場合)として、出現順に受け取られます。

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

  1. 最初に、スーパークラスのコンストラクタ呼出しが修飾されている場合は、Primary式またはExpressionNameが評価されます。 nullと評価されると、NullPointerExceptionが生成され、スーパークラスのコンストラクタ呼出しが突然完了します。 この評価が突然完了すると、スーパークラスのコンストラクタ呼出しも同じ理由で突然完了します。

  2. コンストラクタ呼出しの実際の引数は、通常のメソッド呼出しと同様に左から右に評価され、コンストラクタが呼び出されます。

    コンストラクタ呼出しの実際の最初の引数は、包含インスタンスである可能性があることに注意してください。

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

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

例 8.8.7.1-1. コンストラクタ呼出しの制限

§8.8.7の例のColoredPointの最初のコンストラクタが次のように変更された場合:

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

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


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

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

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

驚くことに、Outerの同じインスタンスが、ChildOfInnerの複数のインスタンスについてInnerに関して、ChildOfInnerの直近のインスタンスとして機能する場合があります。 ChildOfInnerのこれらのインスタンスは、Outerの同じインスタンスに暗黙的にリンクされます。 次のプログラムは、OuterのインスタンスをChildOfInnerのコンストラクタに渡すことでこれを実現します。このコンストラクタでは、修飾されたスーパークラスのコンストラクタ呼出しでインスタンスが使用されます。 コンストラクタ呼出しのルールは、呼出しを含むコンストラクタの仮パラメータを使用することを禁止しません。

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

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

このプログラムは出力を生成します:

5
6

この効果は、Outerの共通インスタンス内のインスタンス変数の操作は、従来の意味では別名ではない場合でも、ChildOfInnerの異なるインスタンスへの参照を介して参照できることです。


8.8.8. コンストラクタのオーバーロード

コンストラクタのオーバーロードは、メソッドのオーバーロードと同じ動作です(§8.4.9)。 オーバーロードは、各クラス・インスタンス作成式(§15.9)によってコンパイル時に解決されます。

8.8.9. デフォルト・コンストラクタ

クラスにコンストラクタ宣言が含まれていない場合は、デフォルト・コンストラクタが暗黙的に宣言されます。 トップ・レベル・クラス、メンバー・クラスまたはローカル・クラスのデフォルト・コンストラクタの形式は次のとおりです。

  • デフォルト・コンストラクタは、クラスにアクセス修飾子がないかぎり、クラスと同じアクセス修飾子を持ちます。この場合、デフォルト・コンストラクタはパッケージ・アクセスを持ちます(§6.6)。

  • デフォルト・コンストラクタには、private以外の内部メンバー・クラスを除き、仮パラメータはありません。デフォルト・コンストラクタでは、クラスの直近のインスタンスを表す1つの仮パラメータを暗黙的に宣言します(§8.8.1§15.9.2§15.9.3)。

  • デフォルト・コンストラクタには、throws句はありません。

  • 宣言されるクラスが原始クラスObjectの場合、デフォルト・コンストラクタは空の本体を持ちます。 それ以外の場合、デフォルト・コンストラクタは単に引数を指定せずにスーパークラス・コンストラクタを呼び出します。 このスーパークラス・コンストラクタ呼出しは、修飾されていないスーパークラス・コンストラクタ呼出し(§8.8.7.1)のすべてのルールを満たす必要があります。

匿名クラスのデフォルト・コンストラクタの形式は、§15.9.5.1で指定します。

デフォルト・コンストラクタが暗黙的に宣言されているが、スーパークラスに引数を取らず、throws句を持たないアクセス可能なコンストラクタがない場合、コンパイル時にエラーが発生します。

例8.8.9-1. デフォルト・コンストラクタ

宣言:


public class Point {
    int x, y;
}

は宣言と同等です。


public class Point {
    int x, y;
    public Point() { super(); }
}

ここで、クラスPointpublicであるため、デフォルト・コンストラクタはpublicです。


例8.8.9-2. コンストラクタvのアクセシビリティ クラス

クラスのデフォルト・コンストラクタのアクセシビリティがクラス自体と同じであるというルールは、単純で直感的です。 ただし、これは、クラスがアクセス可能なときにコンストラクタにアクセスできることを意味しません。 次の点を考慮してください。

package p1;
public class Outer {
    protected class Inner {}
}
package p2;
class SonOfOuter extends p1.Outer {
    void foo() {
        new Inner();  // compile-time access error
    }
}

Innerのデフォルト・コンストラクタはprotectedです。 ただし、コンストラクタはInnerに対して相対的なprotectedで、InnerOuterに対して相対的なprotectedです。 したがって、InnerOuterのサブクラスであるため、SonOfOuterでアクセスできます。 SonOfOuterクラスがInnerのサブクラスではないため、InnerのコンストラクタはSonOfOuterでアクセスできません。 したがって、Innerはアクセス可能ですが、デフォルトのコンストラクタはアクセスできません。


8.8.10. クラスのインスタンス化の防止

クラスは、少なくとも1つのコンストラクタを宣言し、デフォルト・コンストラクタが作成されないようにし、すべてのコンストラクタをprivate (§6.6.1)と宣言することによって、クラス宣言外のコードがクラスのインスタンスを作成できないように設計できます。

同様に、publicクラスでは、少なくとも1つのコンストラクタを宣言し、publicアクセスを持つデフォルト・コンストラクタが作成されないようにし、publicまたはprotected (§6.6.2)のコンストラクタを宣言することによって、パッケージ外でのインスタンスの作成を防止できます。

例8.8.10-1. コンストラクタのアクセシビリティによるインスタンス化の防止


class ClassOnly {
    private ClassOnly() { }
    static String just = "only the lonely";
}

ここでは、次のコードでは、クラスClassOnlyをインスタンス化できません。


package just;
public class PackageOnly {
    PackageOnly() { }
    String[] justDesserts = { "cheesecake", "ice cream" };
}

publicクラスPackageOnlyは、宣言されているパッケージjust内でのみインスタンス化できます。 この制限は、PackageOnlyのコンストラクタがprotectedの場合にも適用されますが、その場合は、他のパッケージのコードでPackageOnlyのサブクラスをインスタンス化できます。


8.9.  列挙クラス

enum宣言は、新しいenumクラスを指定します。これは、名前付きクラス・インスタンスの小さいセットを定義する制限された種類のクラスです。

列挙宣言では、最上位の列挙クラス(§7.6)、メンバー列挙クラス(§8.5§9.5)、またはローカル列挙クラス(§14.3)を指定できます。

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

列挙宣言に修飾子abstractfinalsealedまたはnon-sealedがある場合、コンパイル時にエラーが発生します。

enumクラスは、次のように暗黙的にfinalまたは暗黙的にsealedです。

  • enumクラスは、宣言にクラス本体を持つenum定数が含まれていない場合(§8.9.1)、暗黙的にfinalになります。

  • enumクラスEは、クラス本体を持つenum定数を少なくとも1つ宣言に含まれている場合、暗黙的にsealedです。 Eの許可される直接サブクラス(§8.1.6)は、クラス本体を持つenum定数によって暗黙的に宣言される匿名クラスです。

ネストされたenumクラスは暗黙的にstaticです。 つまり、すべてのメンバー列挙クラスとローカル列挙クラスはstaticです。 メンバー列挙クラスの宣言でstatic修飾子を重複して指定することは許可されていますが、ローカル列挙クラスの宣言には許可されていません(§14.3)。

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

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

列挙宣言にextends句がないため、Enum<E>でも直接スーパークラス型を明示的に宣言することはできません。

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

コンパイル時エラーに加えて、次の3つのメカニズムにより、列挙型クラスのインスタンスが列挙定数で定義されたインスタンスを超えて存在しないことが保証されます:

  • Enumfinal cloneメソッドにより、enum定数がクローニングされないことが保証されます。

  • 列挙型クラスのリフレクション・インスタンス化は禁止されています。

  • 直列化メカニズムによる特別な処理により、直列化復元の結果として重複するインスタンスが作成されることがなくなります。

8.9.1.  enum定数

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

EnumBody:
EnumConstantList:
EnumConstantModifier:

便宜上、§15.12の次の本番を示します。

ArgumentList:
{, }

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

EnumConstantIdentifierは、enum定数を参照するために使用できるenumクラス(§8.9.3)の暗黙的なフィールドの名前を提供します。

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

enum定数のオプションのクラス本体は、匿名クラス(§15.9.5)を暗黙的に宣言します。i)は、直近のenumクラス(§8.1.4)の直接サブクラスであり、(ii)はfinal(§8.1.1.2)です。 クラス本文は、匿名クラスの通常のルールによって管理されます。特に、コンストラクタを含めることはできません。 これらのクラス本体で宣言されたインスタンス・メソッドは、包含するenumクラス(§8.4.8)内のアクセス可能なメソッドをオーバーライドする場合にのみ、包含するenumクラスの外部で呼び出すことができます。

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

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

Enumequalsメソッドは、単に引数に対してsuper.equalsを呼び出して結果を返すfinalメソッドで、アイデンティティ比較を実行します。

8.9.2.  enum本体宣言

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

EnumBodyDeclarations:

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

enum宣言の本体内のコンストラクタまたはメンバー宣言は、特に明記されていないかぎり、通常のクラス宣言の本体に存在していたかのようにenumクラスに適用されます。

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

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

フィールドが定数変数(§4.12.4)でないかぎり、クラスのenum宣言でコンストラクタ、インスタンス・イニシャライザまたはインスタンス変数イニシャライザからenumクラスのstaticフィールドを参照するのはコンパイル時エラーです。

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

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

実際には、コンパイラは、enumクラスのデフォルト・コンストラクタでStringおよびintパラメータを宣言することで、Enumクラスをミラー化します。 ただし、異なるコンパイラがデフォルト・コンストラクタの形式に同意する必要がないため、これらのパラメータは"暗黙のうちに宣言された"として指定されません。 enum宣言のコンパイラのみがenum定数のインスタンス化方法を認識します。他のコンパイラでは、これらのフィールドの初期化方法に関係なく、暗黙的に宣言されたenumクラス(§8.9.3)のpublic staticフィールドにのみ依存できます。

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

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

例8.9.2-1.  enum本体宣言

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

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

各enum定数は、コンストラクタを介して渡されるフィールドvalue内の異なる値に対して配置されます。 このフィールドは、アメリカン・コインの値をセント単位で表します。 enumクラスのコンストラクタで宣言できるパラメータには制限がないことに注意してください。


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

staticフィールド・アクセスのルールがないと、enumクラスに固有の初期化循環が原因で、妥当なコードが実行時に失敗する可能性があります。 (循環性は、自己型型のstaticフィールドを持つ任意のクラスに存在します。) 次に、失敗するコードの種類の例を示します:

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

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

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

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

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

enum Color {
    RED, GREEN, BLUE;

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

静的な初期化は上から下に行われるため、リファクタされたバージョンは明確に正しいです。


8.9.3.  enumメンバー

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

  • Eの宣言の本体で宣言されたメンバー。

  • Enum<E>から継承されたメンバー。

  • E宣言の本体で宣言されたenum定数cごとに、Eには、cと同じ名前を持つ型Eの暗黙的に宣言されたpublic static finalフィールドがあります。 このフィールドには、Eをインスタンス化し、cの引数をEに選択したコンストラクタに渡す変数イニシャライザがあります。 フィールドの注釈は、c (ある場合)と同じです。

    これらのフィールドは、E宣言の本体で明示的に宣言されたstaticフィールドの前に、対応するenum定数と同じ順序で暗黙的に宣言されます。

    enum定数は、対応する暗黙的に宣言されたフィールドが初期化されるときに作成されます。

  • 暗黙的に宣言されたメソッドpublic static E[] values()Eのenum定数を含む配列を、E宣言の本体にあるものと同じ順序で戻します。

  • 暗黙的に宣言されたメソッドpublic static E valueOf(String name)。指定した名前でEの列挙定数を返します。

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

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

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

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

このプログラムは出力を生成します:

WINTER
SPRING
SUMMER
FALL

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

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

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

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

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

このプログラムは出力を生成します:

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

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

switch文を使用して外部からenumクラスに動作を「追加」するのではなく、クラス本体を使用して動作をenum定数に直接アタッチできます。

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

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

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

次のような出力が生成されます:

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

このパターンは、新しい定数に対する動作の追加を忘れる可能性が排除されるため、switch文を使用するよりもはるかに安全です(enum宣言によってコンパイル時エラーが発生するため)。


例8.9.3-4. 複数の列挙クラス

次のプログラムでは、2つの簡単な列挙の上に再生カード・クラスが構築されます。

import java.util.ArrayList;
import java.util.List;

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

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

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

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

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

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

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

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

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

次のプログラムは、Cardクラスを実行します。 これは、処理する手の数と各手のカードの数を表す2つの整数パラメータをコマンドラインで取ります:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

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

次のような出力が生成されます:

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

8.10.  レコード・クラス

レコード宣言では、新しいレコード・クラス(値の単純な集計を定義する制限された種類のクラス)を指定します。

レコード宣言では、最上位レコード・クラス(§7.6)、メンバー・レコード・クラス(§8.5§9.5)、またはローカル・レコード・クラス(§14.3)を指定できます。

レコード宣言のTypeIdentifierは、レコード・クラスの名前を指定します。

レコード宣言に修飾子abstractsealedまたはnon-sealedがある場合、コンパイル時にエラーが発生します。

レコード・クラスは暗黙的にfinalです。 レコード・クラスの宣言でfinal修飾子を重複して指定することが許可されます。

ネストされたレコード・クラスは暗黙的にstaticです。 つまり、すべてのメンバー・レコード・クラスとローカル・レコード・クラスはstaticです。 メンバー・レコード・クラスの宣言でstatic修飾子を重複して指定することは許可されていますが、ローカル・レコード・クラスの宣言には許可されていません(§14.3)。

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

レコード・クラスの直接スーパークラス型はRecordです(§8.1.4)。

レコード宣言にextends句がないため、直接スーパークラス型(Record)を明示的に宣言することはできません。

直列化メカニズムは、通常の直列化可能オブジェクトまたは外部化可能オブジェクトとは異なる方法でレコード・クラスのインスタンスを処理します。 特に、レコード・オブジェクトは、正規コンストラクタ(§8.10.4)を使用してデシリアライズされます。

8.10.1.  レコード・コンポーネント

レコード・クラスのレコード・コンポーネント(存在する場合)は、レコード宣言のヘッダーで指定されます。 各レコード・コンポーネントは、(オプションで、1つ以上の注釈を前に付けます。)型と、レコード・コンポーネントの名前を指定する識別子で構成されます。 レコード・コンポーネントは、暗黙的に宣言されたprivateフィールドと、明示的にまたは暗黙的に宣言されたpublicアクセッサ・メソッド(§8.10.3)の2つのレコード・クラスのメンバーに対応します。

レコード・クラスにレコード・コンポーネントがない場合、レコード宣言のヘッダーに空のカッコのペアが表示されます。

RecordHeader:
RecordComponentList:
VariableArityRecordComponent:
RecordComponentModifier:

レコード・コンポーネントは、変数アリティ・レコード・コンポーネントであり、タイプに続く省略記号で示されます。 1つのレコード・クラスに対して最大で1つの可変個引数レコード・コンポーネントが許可されています。 可変引数のレコード・コンポーネントが、最後の位置を除くレコード・コンポーネントのリストのどこかに出現した場合、コンパイル時エラーになります。

レコード・コンポーネントの注釈修飾子に関する規則は、§9.7.4および§9.7.5で指定されています。

レコード・コンポーネント上の注釈は、その注釈インタフェースがレコード・コンポーネント・コンテキスト(§9.6.4.1)に適用可能な場合、リフレクションを介して使用できます。 レコード・コンポーネントの注釈は、他のコンテキスト(§8.10.3§8.10.4)で注釈インタフェースが適用可能な場合、レコード・クラスのメンバーおよびコンストラクタの宣言に個別に伝播されます。

レコード・コンポーネントのすべての宣言に識別子が含まれている必要があります。そうしないと、コンパイル時にエラーが発生します。

clonefinalizegetClasshashCodenotifynotifyAlltoStringまたはwaitという名前のレコード・コンポーネントを持つレコード宣言では、コンパイル時にエラーが発生します。

これらは、Objectの引数なしのpublicメソッドおよびprotectedメソッドの名前です。 これらをレコード・コンポーネントの名前に禁止すると、様々な形で混乱を回避できます。 まず、すべてのレコード・クラスは、レコード・オブジェクト全体の表現を返すhashCodeおよびtoStringの実装を提供し、hashCodeまたはtoStringと呼ばれるレコード・コンポーネントのアクセッサ・メソッド(§8.10.3)として機能できず、レコード・クラスの外部からそのようなレコード・コンポーネントにアクセスする方法はありません。 同様に、一部のレコード・クラスではcloneおよび(残念ながら)finalizeの実装が提供されるため、cloneまたはfinalizeというレコード・コンポーネントはアクセッサ・メソッドを介してアクセスできませんでした。 最後に、ObjectgetClassnotifynotifyAllおよびwaitメソッドはfinalであるため、同じ名前のレコード・コンポーネントにアクセッサ・メソッドを使用できませんでした。 (アクセッサ・メソッドは、finalメソッドと同じシグネチャを持つため、オーバーライドは失敗します。)

レコード宣言に同じ名前の2つのレコード・コンポーネントを含めると、コンパイル時にエラーが発生します。

レコード・コンポーネントの宣言された型は、それが可変引数レコード・コンポーネントであるかどうかに応じて異なります:

  • レコード・コンポーネントが可変アリティ・レコード・コンポーネントではない場合、宣言された型はUnannTypeで示されます。

  • レコード・コンポーネントが可変アリティ・レコード・コンポーネントの場合、宣言された型は§10.2で指定された配列型です。

変数Arityレコード・コンポーネントの宣言された型に、変更不可能な要素型(§4.7)がある場合、正規コンストラクタ(§8.10.4)に@SafeVarargs (§9.6.4.7)の注釈が付けられているか、または@SuppressWarnings (§9.6.4.5)によって警告が抑制されていないかぎり、変数Arityレコード・コンポーネントの宣言に対してコンパイル時の未チェックの警告が発生します。

8.10.2. レコード本文宣言

レコード宣言の本文には、コンストラクタ、メンバー宣言および静的初期化子を含めることができます。

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

CompactConstructorDeclaration句については、§8.10.4.2で説明します。

レコード宣言の本体にstatic以外のフィールド宣言(§8.3.1.1)を含めると、コンパイル時にエラーが発生します。

レコード宣言の本体に、abstractまたはnative (§8.4.3.1§8.4.3.4)のメソッド宣言が含まれている場合は、コンパイル時にエラーが発生します。

レコード宣言の本体にインスタンス・イニシャライザ(§8.6)を含めると、コンパイル時にエラーが発生します。

8.10.3.  レコード・メンバー

レコード コンポーネントごとに、レコード クラスには、レコード コンポーネントと同じ名前のフィールドがあり、レコード コンポーネントの宣言タイプと同じタイプを持ちます。 このフィールドは暗黙的に宣言され、コンポーネント・フィールドと呼ばれます。

コンポーネント・フィールドは、privatefinalおよびstatic以外です。

コンポーネント・フィールドには、対応するレコード・コンポーネントに表示され、その注釈インタフェースがフィールド宣言コンテキストまたはタイプ・コンテキスト(§9.7.4)に適用可能な注釈(ある場合)が付けられます。

さらに、レコード・コンポーネントごとに、レコード・クラスには、レコード・コンポーネントと同じ名前のメソッドと空の仮パラメータ・リストがあります。 このメソッドは、明示的または暗黙的に宣言され、アクセッサ・メソッドと呼ばれます。

レコード・コンポーネントのアクセッサ・メソッドが明示的に宣言されている場合、次のすべてがtrueである必要があり、そうでない場合はコンパイル時にエラーが発生します。

  • アクセッサ・メソッドの戻り型(§8.4.5)は、レコード・コンポーネントの宣言された型と同じである必要があります。

  • アクセッサ・メソッドは汎用にできません(§8.4.4)。

  • アクセッサ・メソッドは、仮パラメータがなく、throws句もないpublicインスタンス・メソッドである必要があります。

レコード・クラスに、アクセッサ・メソッドが明示的に宣言されていないレコード・コンポーネントがある場合、そのレコード・コンポーネントのアクセッサ・メソッドは、次のプロパティを使用して暗黙的に宣言されます。

  • この名前は、レコード・コンポーネントの名前と同じです。

  • その戻り型は、レコード・コンポーネントの宣言された型と同じです。

  • 汎用ではありません。

  • 仮パラメータがなく、throws句もないpublicインスタンス・メソッドです。

  • 対応するレコード・コンポーネントに表示され、その注釈インタフェースがメソッド宣言コンテキストまたはタイプ・コンテキスト(あるいはその両方)(§9.7.4)に適用可能な注釈(ある場合)が付けられます。

  • その本体は、対応するコンポーネント・フィールドの値を返します。

レコード・コンポーネント名(§8.10.1)の制限は、暗黙的に宣言されたアクセッサ・メソッドに、クラスObjectprivate以外のメソッドとオーバーライド等価のシグネチャがないことを意味します。 public void wait() {...}など、制限された名前の1つを取る明示的なメソッド宣言は、waitがレコード・コンポーネント名ではないため、アクセッサ・メソッドではありません。

レコード・コンポーネントに表示される注釈は、そのレコード・コンポーネントに対して明示的に宣言されたアクセッサ・メソッドには伝播されません。 状況によっては、プログラマは明示的に宣言されたアクセッサ・メソッドでレコード・コンポーネントの注釈を複製する必要がある場合がありますが、これは一般的には必要ありません。

暗黙的に宣言されたアクセッサ・メソッドに伝播される注釈は、法的に注釈が付けられたメソッドになる必要があります。 たとえば、次のレコード宣言では、暗黙的に宣言されたアクセッサ・メソッドx()@SafeVarargsの注釈が付けられますが、このような注釈は固定アリティ・メソッド(§9.6.4.7)では無効です。

record BadRecord(@SafeVarargs int x) {}  // Error

コンポーネント・フィールドとアクセッサ・メソッドのスコープとシャドウは、§6.3および§6.4.1で指定されています。 (対応するレコード・コンポーネントは宣言ではないため、独自のスコープはありません。)

レコード・クラスは、アクセッサ・メソッド以外のインスタンス・メソッドを明示的に宣言できますが、インスタンス変数を明示的に宣言することはできません(§8.10.2)。 クラス・メソッドおよびクラス変数の明示的な宣言が許可されます。

レコード・クラスのすべてのメンバー(暗黙的に宣言されたメンバーを含む)は、クラス内のメンバー宣言の通常のルールに従います(§8.3§8.4§8.5)。

通常のクラスに適用される継承に関するすべてのルールは、レコード・クラスに適用されます。 特に、レコード・クラスはスーパーインタフェースからメンバーを継承する場合がありますが、スーパーインタフェース・メソッドはアクセッサ・メソッドとして継承されることはありません。これは、レコード・クラスは、スーパーインタフェース・メソッドをオーバーライドするアクセッサ・メソッドを常に明示的または暗黙的に宣言するためです。

たとえば、レコード・クラスは直接スーパーインタフェースからデフォルト・メソッドを継承できますが、デフォルトのメソッド本体にはレコード・クラスのコンポーネント・フィールドは認識されません。 次のプログラムは、Loggedを出力します。

public class Test {
    interface Logging {
        default void logAction() {
            System.out.println("Logged");
        }
    }

    record Point(int i, int j) implements Logging {}

    public static void main(String[] args) {
        Point p = new Point(10, 20);
        p.logAction();
    }
}

レコード・クラスは、クラスRecordで宣言されたすべてのabstractメソッドの実装を提供します。 次の各メソッドについて、レコード・クラスRが同じ修飾子、名前およびシグネチャを持つメソッドを明示的に宣言しない場合(§8.4.2)、メソッドは次のように暗黙的に宣言されます。

  • 引数がRのインスタンスであり、現在のインスタンスがRの各レコード・コンポーネントの引数インスタンスと等しい場合にのみtrueを返すメソッドpublic final boolean equals(Object)。それ以外の場合はfalseが返されます。

    レコード・クラスRのインスタンスaと、レコード・コンポーネントcの同じレコード・クラスの別のインスタンスbとの等価性は、次のように決定されます。

    • レコード・コンポーネントcの型が参照型の場合、等価は次のように決定されます。abの両方のコンポーネント・フィールドcの値がNULL参照の場合、trueが返されます。コンポーネント・フィールドcの値がaまたはbのいずれか(両方ではない)がNULL参照であるため、falseが返されます。それ以外の場合の等価は、aのコンポーネント・フィールドcの値に対してequalsメソッドを呼び出し、引数がbのコンポーネント・フィールドcの値であることによって決定されます。

    • レコード・コンポーネントcの型がプリミティブ型Tの場合、T (§5.1.7)に対応するラッパー・クラスのstaticメソッドcompareを最初の引数とともに呼び出すことによって、等価性が判断されます。aのコンポーネント・フィールドcの値、およびbのコンポーネント・フィールドcの値で指定された2番目の引数によって指定されます。メソッドが0を返す場合はtrueが戻され、それ以外の場合はfalseが戻されます。

    ラッパー・クラスでcompareを使用すると、暗黙的に宣言されたequalsメソッドが再帰的であり、浮動小数点コンポーネントを持つレコード・クラスの暗黙的に宣言されたhashCodeメソッドと一貫して動作します。

  • Rのすべてのレコード・コンポーネントのハッシュ・コード値から導出されたハッシュ・コード値を戻すメソッドpublic final int hashCode()

    レコード・コンポーネントcのレコード・クラスのインスタンスaのハッシュ・コード値は、次のとおりです。

    • レコード・コンポーネントcの型が参照型の場合、ハッシュ・コード値は、aのコンポーネント・フィールドcの値に対してhashCodeメソッドを呼び出すかのように決定されます。

    • レコード・コンポーネントcの型がプリミティブ型Tの場合、ハッシュ・コード値は、aのコンポーネント・フィールドcの値をボクシング変換(§5.1.7)に適用し、結果のオブジェクトでTに対応するラッパー・クラスのメソッドhashCodeを呼び出すことによって、どのように決定されます。

  • レコード・クラスの名前から導出された文字列、およびRのすべてのレコード・コンポーネントの名前と文字列表現を返すメソッドpublic final String toString()

    レコード・クラスのインスタンスaのレコード・コンポーネントcの文字列表現は次のとおりです。

    • レコード・コンポーネントcの型が参照型の場合、文字列表現は、aのコンポーネント・フィールドcの値に対してtoStringメソッドを呼び出したかのように決定されます。

    • レコード・コンポーネントcの型がプリミティブ型Tの場合、文字列表現は、aのコンポーネント・フィールドcの値をボクシング変換(§5.1.7)に適用し、結果のオブジェクトでTに対応するラッパー・クラスのメソッドtoStringメソッドを呼び出すかのように決定されます。

等価、ハッシュ・コード値および文字列表現は、アクセッサ・メソッドを呼び出すのではなく、コンポーネント・フィールドの値を直接調べることによって決定されることに注意してください。

コンポーネントc1、...、cn、およびすべてのコンポーネントに対して暗黙的に宣言されたアクセッサ・メソッド、および暗黙的に宣言されたequalsメソッドを持つレコード・クラスRについて考えてみます。 Rのインスタンスr1が次のようにコピーされた場合:

R r2 = new R(r1.c1(), r1.c2(), ..., r1.cn());

この場合、r1がNULL参照でないと仮定すると、式r1.equals(r2)trueと評価されるのは常に同じです。 明示的に宣言されたアクセッサ・メソッドおよびequalsメソッドは、この不変条件を考慮する必要があります。 一般に、明示的に宣言されたメソッドが不変条件を尊重するかどうかをコンパイラでチェックすることはできません。 次のレコード宣言は、そのアクセッサ・メソッドがxおよびyコンポーネントをクリップするため、p3equalsからp1にならないようにするため、不適切なスタイルです。

record SmallPoint(int x, int y) {
    public int x() { return this.x < 100 ? this.x : 100; }
    public int y() { return this.y < 100 ? this.y : 100; }

    public static void main(String[] args) {
        SmallPoint p1 = new SmallPoint(200,300);
        SmallPoint p2 = new SmallPoint(200,300);
        System.out.println(p1.equals(p2));  // prints true

        SmallPoint p3 = new SmallPoint(p1.x(), p1.y());
        System.out.println(p1.equals(p3));  // prints false
    }
}

8.10.4.  レコード・コンストラクタ宣言

レコード・コンポーネントを適切に初期化するために、レコード・クラスはデフォルト・コンストラクタ(§8.8.9)を暗黙的に宣言しません。 かわりに、レコード・クラスには、レコード・クラスのすべてのコンポーネント・フィールドを初期化する正規コンストラクタが明示的にまたは暗黙的に宣言されています。

レコード宣言で標準コンストラクタを明示的に宣言するには、2つの方法があります。1つは、適切なシグネチャ(§8.10.4.1)で標準コンストラクタを宣言するか、またはコンパクト・コンストラクタ(§8.10.4.2)を宣言する方法です。

標準的なコンストラクタのシグネチャと、コンパクト・コンストラクタ用に導出されたシグネチャを考慮すると、コンストラクタ・シグネチャのルール(§8.8.2)は、レコード宣言に正規およびコンパクト・コンストラクタとして修飾される通常のコンストラクタの両方がある場合、コンパイル時エラーであることを意味します。

どちらにしても、明示的に宣言された標準コンストラクタでは、次のように、少なくともレコード・クラスと同じくらいのアクセスを提供する必要があります。

  • レコード・クラスがpublicの場合、正規コンストラクタはpublicである必要があります。そうでない場合は、コンパイル時にエラーが発生します。

  • レコード・クラスがprotectedの場合、正規コンストラクタはprotectedまたはpublicである必要があります。それ以外の場合、コンパイル時にエラーが発生します。

  • レコード・クラスにパッケージ・アクセス権がある場合、標準コンストラクタはprivateではない必要があります。そうしないと、コンパイル時にエラーが発生します。

  • レコード・クラスがprivateの場合、正規コンストラクタを任意のアクセシビリティで宣言できます。

明示的に宣言された正規コンストラクタは、固定アリティ・コンストラクタまたは可変アリティ・コンストラクタ(§8.8.1)です。

レコード・クラスRの宣言で正規コンストラクタが明示的に宣言されていない場合、正規コンストラクタrは、次のプロパティを使用してRで暗黙的に宣言されます。

  • rのシグネチャには型パラメータがなく、次に定義するRの導出仮パラメータ・リストで指定される仮パラメータがあります。

  • rは、Rにアクセス修飾子がないかぎり、Rと同じアクセス修飾子を持ちます(この場合、rはパッケージ・アクセスを持ちます)。

  • rには、throws句はありません。

  • rの本体は、(コンポーネント・フィールドに対応する)レコード・コンポーネントがレコード・ヘッダーに表示される順序で、レコード・クラスの各コンポーネント・フィールドをrの対応する仮パラメータで初期化します。

レコード・クラスの派生仮パラメータ・リストは、次のようにレコード・ヘッダーの各レコード・コンポーネントから仮パラメータを順番に導出することによって形成されます。

  • レコード・コンポーネントが可変個引数レコード・コンポーネントでない場合、導出された仮パラメータの名前および宣言型は、レコード・コンポーネントと同じになります。

    レコード・コンポーネントが可変アリティ・レコード・コンポーネントの場合、導出された仮パラメータは、レコード・コンポーネントと同じ名前および宣言された型を持つ可変アリティ・パラメータ(§8.4.1)です。

  • 導出された仮パラメータには、レコード・コンポーネントに表示され、その注釈インタフェースが仮パラメータ・コンテキストまたは型コンテキスト(§9.7.4)に適用可能な注釈(ある場合)が付けられます。

レコード宣言には、標準コンストラクタではないコンストラクタの宣言が含まれている場合があります。 レコード宣言内のすべての非正規コンストラクタの本体には、代替コンストラクタ呼出し(§8.8.7.1)が含まれている必要があります。そうでない場合、コンパイル時にエラーが発生します。

8.10.4.1. 通常の正規コンストラクタ

レコード・クラスRの宣言における(非コンパクトな)コンストラクタは、そのシグネチャがRの導出されたコンストラクタ・シグネチャとオーバーライド等価(§8.4.2)である場合、R正規コンストラクタです。

レコード・クラスR派生コンストラクタ・シグネチャは、Rという名前、型パラメータなし、および各レコード・コンポーネントの宣言型を順番に取得することによってRのレコード・ヘッダーから導出された仮パラメータ・タイプで構成されるシグネチャです。

標準コンストラクタには、レコード・クラスの派生コンストラクタ・シグネチャとオーバーライド等価のシグネチャがあるため、レコード・クラスで明示的に宣言される正規コンストラクタは1つのみです。

(非コンパクト)正規コンストラクタの宣言は、次の条件をすべて満たす必要があります。そうしないと、コンパイル時にエラーが発生します。

  • 仮パラメータ・リスト内の各仮パラメータは、対応するレコード・コンポーネントと同じ名前および宣言型を持つ必要があります。

    仮パラメータは、対応するレコード・コンポーネントが可変アリティ・レコード・コンポーネントである場合のみ、可変アリティ・パラメータである必要があります。

  • コンストラクタは汎用であってはなりません(§8.8.4)。

  • コンストラクタにthrows句を指定することはできません。

  • コンストラクタ本体に明示的なコンストラクタ呼出し(§8.8.7.1)を含めることはできません。

  • 通常のクラス宣言のコンストラクタ宣言に関する他のすべての規則は満たされなければならない(§8.8)。

これらのルールの結果、レコード・コンポーネント上の注釈は、明示的に宣言された正規コンストラクタの対応する仮パラメータ上の注釈と異なる場合があります。 たとえば、次のレコード宣言は有効です。

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

@interface Foo {}
@interface Bar {}

record Person(@Foo String name) {
    Person(@Bar String name) {
        this.name = name;
    }
}

8.10.4.2. コンパクトな正規コンストラクタ

コンパクト・コンストラクタ宣言は、簡潔な形式のコンストラクタ宣言であり、レコード宣言でのみ使用できます。 クラスのレコード・コンポーネントをコンストラクタの仮パラメータとして手動で繰り返す必要なく、レコード・クラスの正規コンストラクタを宣言します。

CompactConstructorDeclaration:

ここでは、便宜上、§8.8§8.8.3、および §8.8.7からの次のプロダクションを示します。

ConstructorModifier:
(いずれか)
Annotation public protected private
SimpleTypeName:

レコード宣言に複数のコンパクト・コンストラクタ宣言があると、コンパイル時にエラーが発生します。

レコード・クラスのコンパクト・コンストラクタの仮パラメータが暗黙的に宣言されます。 これらは、レコード・クラスの導出された仮パラメータ・リスト(§8.10.4)によって指定されます。

レコード・クラスに可変アリティ・レコード・コンポーネントがある場合、レコード・クラスのコンパクト・コンストラクタは可変アリティ・コンストラクタ(§8.8.1)です。

コンパクト・コンストラクタ宣言のシグネチャは、レコード・クラスの派生コンストラクタ・シグネチャ(§8.10.4.1)と同じです。

コンパクト・コンストラクタ宣言の本体は、次の条件をすべて満たす必要があります。そうしないと、コンパイル時にエラーが発生します。

  • 本文にreturn文を含めることはできません(§14.17)。

  • 本体に明示的なコンストラクタ呼出し(§8.8.7.1)を含めることはできません。

  • 本文にレコード・クラスのコンポーネント・フィールドへの割当を含めることはできません。

  • 通常のクラス宣言のコンストラクタのその他のルールはすべて満たす必要があります(§8.8)。ただし、レコード・クラスのコンポーネント・フィールドは必ず割り当てる必要があり、さらにコンパクト・コンストラクタ(§8.3.1.2)の最後に割当てが解除されないという要件は除きます。

レコード宣言にcという名前のレコード・コンポーネントがある場合、コンパクト・コンストラクタの本体にある単純名cは、cという名前のコンポーネント・フィールドではなく、cという名前の暗黙的な仮パラメータを示します。

コンパクト・コンストラクタの本体の最後の文(ある場合)が正常に完了した後(§14.1)、レコード・クラスのすべてのコンポーネント・フィールドは、対応する仮パラメータの値に暗黙的に初期化されます。 コンポーネント・フィールドは、対応するレコード・コンポーネントがレコード・ヘッダーで宣言される順序で初期化されます。

コンパクト・コンストラクタ宣言の目的は、パラメータを検証または正規化するためのコードのみをコンストラクタ本体に指定する必要があることであり、残りの初期化コードはコンパイラによって提供されます。 たとえば、次のレコード・クラスには、合理的な数値を簡略化するコンパクトなコンストラクタがあります。

record Rational(int num, int denom) {
    private static int gcd(int a, int b) {
        if (b == 0) return Math.abs(a);
        else return gcd(b, a % b);
    }

    Rational {
        int gcd = gcd(num, denom);
        num    /= gcd;
        denom  /= gcd;
    }
}

コンパクト・コンストラクタRational {...}の動作は、次の通常のコンストラクタと同じです。

Rational(int num, int denom) {
    int gcd = gcd(num, denom);
    num    /= gcd;
    denom  /= gcd;
    this.num   = num;
    this.denom = denom;
}