第4章 タイプ、値および変数

目次

4.1 タイプと値の種類
4.2 プリミティブ型と値
4.2 整数型と値
4.2 整数演算
4.2 浮動小数点型と値
4.2 浮動小数点演算
4.2 boolean型およびブール値
4.3 参照タイプおよび値
4.3 オブジェクト
4.3 クラスObject
4.3 クラスString
4.3 参照タイプが同じ場合
4.4 型変数
4.5 パラメータ化されたタイプ
4.5 パラメータ化された型の型引数
4.5 パラメータ化された型のメンバーおよびコンストラクタ
4.6 タイプ消去
4.7 再可能タイプ
4.8 RAWタイプ
4.9 交差タイプ
4.1 サブタイピング
4.1 プリミティブ型間のサブタイピング
4.1 クラスおよびインタフェース・タイプ間のサブタイピング
4.1 配列型間のサブタイピング
4.1 最小上限
4.1 タイプ予測
4.11 タイプが使用される場所
4.12 変数
4.12 プリミティブ型の変数
4.12 参照タイプの変数
4.12 変数の種類
4.12.4. final変数
4.12 変数の初期値
4.12 タイプ、クラスおよびインタフェース

Javaプログラミング言語は静的型付き言語です。つまり、すべての変数とすべての式には、コンパイル時に認識される型があります。

Javaプログラミング言語は厳密に型付けされた言語でもあります。これは、型によって変数(§4.12)が保持できる値が制限されるか、式が生成できるか、それらの値でサポートされる操作を制限し、操作の意味を決定するためです。 強い静的型指定は、コンパイル時にエラーを検出するのに役立ちます。

Javaプログラミング言語の型は、プリミティブ型と参照型の2種類に分けられます。 プリミティブ型(§4.2)は、boolean型と数値型です。 数値型は、整数型byteshortintlongおよびchar、浮動小数点型floatおよびdoubleです。 参照型(§4.3)は、クラス型、インタフェース型および配列型です。 特別なnull型もあります。 オブジェクト(§4.3.1)は、クラス型または動的に作成された配列の動的に作成されたインスタンスです。 参照型の値はオブジェクトへの参照です。 配列を含むすべてのオブジェクトは、Objectクラスのメソッドをサポートします(§4.3.2)。 文字列リテラルは、Stringオブジェクト(§4.3.3)で表されます。

4.1. タイプと値の種類

Javaプログラミング言語には、プリミティブ型(§4.2)と参照型(§4.3)の2種類の型があります。 それに応じて、変数に格納し、引数として渡され、メソッドによって返され、プリミティブ値(§4.2)および参照値(§4.3)に対して操作できる2種類のデータ値があります。

次のように入力します。

名前のない式null (§3.10.8§15.8.1)の型である特殊なnull型もあります。

null型には名前がないため、null型の変数を宣言したり、null型にキャストしたりすることはできません。

null参照は、null型の式の唯一可能な値です。

null参照は、いつでも任意の参照型(§5.2§5.3§5.5)に代入またはキャストできます。

実際には、プログラマはnull型を無視し、nullが単に任意の参照型にできる特別なリテラルであるように振る舞うことができます。

4.2. プリミティブ型と値

プリミティブ型は、Javaプログラミング言語によって事前定義され、その予約キーワード(§3.9)によって命名されます。

PrimitiveType:
IntegralType:
(いずれか)
byte short int long char
FloatingPointType:
(いずれか)
float double

プリミティブ値は、他のプリミティブ値と状態を共有しません。

数値型は、整数型と浮動小数点型です。

整数型は、byteshortintおよびlongで、値はそれぞれ8ビット、16ビット、32ビットおよび64ビットの符号付き2の補完整数で、値はUTF-16コード単位を表す16ビットの符号なし整数(§3.1)です。

浮動小数点型floatで、その値は32ビットのIEEE 754 binary32浮動小数点数に正確に対応し、doubleは値が64ビットのIEEE 754 binary64浮動小数点数に正確に対応しています。

boolean型には、truefalseの2つの値があります。

4.2.1. 整数型と値

整数型の値は、次の範囲の整数です。

  • byteの場合は、-128から127まで(両端を含む)

  • shortの場合は、-32768から32767まで(両端を含む)

  • intの場合は、-2147483648から2147483647まで(両端を含む)

  • longの場合、-9223372036854775808から9223372036854775807まで(両端を含む)

  • charの場合、'\\u0000'から'\\uffff' (0から65535まで)

4.2.2. 整数演算

Javaプログラミング言語には、整数値を処理する演算子が多数用意されています。

  • 比較演算子は、boolean型の値になります。

    • 数値比較演算子<<=>および>= (§15.20.1)

    • 数値等価演算子==および!= (§15.21.1)

  • int型またはlong型の値になる数値演算子:

    • 単項プラス演算子およびマイナス演算子+および- (§15.15.3§15.15.4)

    • 乗算演算子*/および% (§15.17)

    • 加算演算子+および- (§15.18)

    • 増分演算子++、接頭辞(§15.15.1)と接頭辞(§15.14.2)の両方

    • 減分演算子--、接頭辞(§15.15.2)と接頭辞(§15.14.3)の両方

    • 符号付きシフト演算子および符号なしシフト演算子<<>>および>>> (§15.19)

    • ビット単位の補数演算子~ (§15.15.5)

    • ビット単位の整数演算子&^および| (§15.22.1)

  • 条件演算子? : (§15.25)

  • 整数値から指定された数値型の値に変換できるキャスト演算子(§15.16)。

  • 文字列連結演算子+ (§15.18.1)は、Stringオペランドと整数オペランドを指定すると、整数オペランドをString (byteshortintまたはlongオペランドの10進形式、またはcharオペランドの文字)に変換し、2つの文字列を連結した、新しく作成されたStringを生成します。

その他の便利なコンストラクタ、メソッドおよび定数は、クラスByteShortIntegerLongおよびCharacterに事前定義されています。

シフト演算子以外の整数演算子にlong型のオペランドが少なくとも1つある場合、この操作は64ビットの精度を使用して実行され、数値演算子の結果はlong型になります。 もう一方のオペランドがlongでない場合、最初に拡張され(§5.1.5)、数値プロモーションによってlongが入力されます(§5.6)。

それ以外の場合、操作は32ビットの精度を使用して実行され、数値演算子の結果はint型になります。 いずれかのオペランドがintでない場合は、最初に数値昇格によってint型に拡張されます。

整数演算子は、オーバーフローまたはアンダーフローを示しません。

任意の整数型の値は、任意の数値型に対して、または任意の数値型からキャストできます。 整数型とboolean型の間にキャストはありません。

整数式をbooleanに変換する慣例については、§4.2.5を参照してください。

整数演算子は、次の理由で例外(§11 (例外))をスローできます。

  • NULL参照のボックス化解除変換(§5.1.8)が必要な場合、任意の整数演算子でNullPointerExceptionをスローできます。

  • 整数除算演算子/ (§15.17.2)および整数余剰演算子% (§15.17.3)は、右オペランドがゼロの場合にArithmeticExceptionをスローできます。

  • 増分演算子および減分演算子++ (§15.14.2§15.15.1)および-- (§15.14.3§15.15.2)は、ボクシング変換(§5.1.7)が必要で、変換を実行するのに十分なメモリーがない場合、OutOfMemoryErrorをスローできます。

例4.2.2-1. 整数演算

class Test {
    public static void main(String[] args) {
        int i = 1000000;
        System.out.println(i * i);
        long l = i;
        System.out.println(l * l);
        System.out.println(20296 / (l - i));
    }
}

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

-727379968
1000000000000

次に、l - iがゼロであるため、l - iによる除算でArithmeticExceptionが発生します。 最初の乗算は32ビット精度で実行されますが、2番目の乗算はlong乗算です。 -727379968は、数学的結果の下位32ビットの10進値1000000000000です。これは、int型には大きすぎる値です。


4.2.3. 浮動小数点型と値

浮動小数点型はfloatおよびdoubleで、IEEE 754標準(§1.7)で指定されているように、IEEE 754の値および操作の32ビットbinary32および64ビットbinary64浮動小数点形式と概念的に関連付けられています。

Java SE 15以降では、Javaプログラミング言語はIEEE 754 Standardの2019バージョンを使用します。 Java SE 15より前のJavaプログラミング言語では、IEEE 754 Standardの1985バージョンが使用されていましたが、ここで、binary32形式は単一形式と呼ばれ、binary64形式はdouble形式と呼ばれていました。

IEEE 754には、符号と大きさで構成される正と負の数だけでなく、正と負のゼロ、正と負の 無限、および特殊な Not-a-Number値(以降は NaN)も含まれています。 NaN値は、ゼロで除算するなど、特定の無効な操作の結果を表すために使用されます。 float型とdouble型の両方のNaN定数は、Float.NaNおよびDouble.NaNとして事前定義されています。

浮動小数点型の有限非ゼロ値は、すべて s m 2(e - N + 1)の形式で表すことができます。ここでは:

  • sは+1または-1です。

  • mは、2N未満の正の整数です。

  • eは、Emin = -(2K-1-2)からEmax = 2K-1-1までの整数です。

  • NおよびKは、型に依存するパラメータです。

一部の値は、この形式で複数の方法で表すことができます。 たとえば、浮動小数点型の値vが、smおよびeの特定の値を使用してこの形式で表される場合、mが偶数であり、eが2K-1未満であった場合は、mを停止し、eを1増やして、同じ値vの2番目の表現を生成できます。

この形式での表現は、正規化(m 2N-1の場合)と呼ばれ、それ以外の場合、表現は非正規と呼ばれます。 浮動小数点型の値がm 2N-1のように表現できない場合、その大きさは最小正規化値の大きさを下回るため、その値は非正規値であるとみなされます。

floatおよびdoubleのパラメータNおよびK (および導出パラメータEminおよびEmax)に対する制約を、表4.2.3-Aにまとめます。

表4.2.3-A. 浮動小数点パラメータ

Parameter float double
N 24 53
K 8 11
Emax +127 +1023
Emin -126 -1022

NaNを除き、浮動小数点値は順序付けされます。 最小から最大までの範囲では、負の無限大、負の有限非ゼロ値、負と正のゼロ、正の有限非ゼロ値、正の無限大です。

IEEE 754では、binary32およびbinary64浮動小数点形式ごとに複数の異なる NaN値を使用できます。 ただし、Java SE Platformでは、通常、特定の浮動小数点型のNaN値が単一の正規値に縮小されたかのように扱われるため、この仕様は通常、任意のNaNを正規値として参照します。

IEEE 754では、非 NaN引数を持つ浮動小数点演算が NaN結果を生成する可能性があります。 IEEE 754は一連の NaNビットパターンを指定しますが、NaN結果を表すためにどの特定の NaNビットパターンが使用されるかは指定しません。これはハードウェアアーキテクチャーに任されます。 プログラマは、遡及診断情報など、エンコードする様々なビット・パターンを持つNaNを作成できます。 これらのNaN値は、floatおよびdoubleFloat.intBitsToFloatおよびDouble.longBitsToDoubleメソッドでそれぞれ作成できます。 逆に、NaN値のビット・パターンを検査するために、Float.floatToRawIntBitsおよびDouble.doubleToRawLongBitsメソッドをそれぞれfloatおよびdoubleに使用できます。

正のゼロと負のゼロの比較は等しくなるため、式0.0==-0.0の結果はtrueで、0.0>-0.0の結果はfalseになります。 その他の操作では、正と負のゼロを区別できます。たとえば、1.0/0.0の値は正の無限大で、1.0/-0.0の値は負の無限大です。

NaNは順序付けられていないため、次のようになります。

  • いずれかまたは両方のオペランドがNaN (§15.20.1)の場合、数値比較演算子<<=>および>=falseを返します。

    特に、xまたはyがNaNの場合、(x<y) == !(x>=y)falseになります。

  • いずれかのオペランドがNaNの場合、等価演算子==falseを返します。

  • いずれかのオペランドがNaN (§15.21.1)の場合、不等式演算子!=trueを返します。

    特に、xがNaNの場合のみ、x!=xtrueです。

4.2.4.  浮動小数点操作

Javaプログラミング言語には、浮動小数点値に作用する演算子が多数用意されています。

  • 比較演算子は、boolean型の値になります。

    • 数値比較演算子<<=>および>= (§15.20.1)

    • 数値等価演算子==および!= (§15.21.1)

  • float型またはdouble型の値になる数値演算子:

  • 条件演算子? : (§15.25)

  • 浮動小数点値から指定された数値型の値に変換できるキャスト演算子(§15.16)

  • 文字列連結演算子+ (§15.18.1)は、Stringオペランドと浮動小数点オペランドを指定すると、浮動小数点オペランドを10進数形式(情報損失なし)の値を表すStringに変換し、2つの文字列を連結して新しく作成されたStringを生成します。

その他の便利なコンストラクタ、メソッドおよび定数は、クラスFloatDoubleおよびMathに事前定義されています。

2項演算子に対するオペランドの少なくとも1つが浮動小数点型の場合、もう一方のオペランドが整数であっても、その操作は浮動小数点演算になります。

数値演算子に対するオペランドの少なくとも1つがdouble型の場合、この操作は64ビットの浮動小数点演算を使用して実行され、数値演算子の結果はdouble型の値になります。 もう一方のオペランドがdoubleでない場合、最初に拡張され(§5.1.5)、数値プロモーションによってdoubleが入力されます(§5.6)。

それ以外の場合、オペランドの少なくとも1つがfloat型で、この操作は32ビットの浮動小数点演算を使用して実行され、数値演算子の結果はfloat型の値になります。 もう一方のオペランドがfloatでない場合は、最初に数値昇格によってfloat型に拡張されます。

浮動小数点演算は、オーバーフローおよびアンダーフロー(§15.4)を含むIEEE 754規格の規則に従って実行されますが、余剰演算子% (§15.17.3)は例外です。

浮動小数点型の値は、任意の数値型に対して、または任意の数値型からキャストできます。 浮動小数点型とboolean型の間にキャストはありません。

浮動小数点式をbooleanに変換する慣例については、§4.2.5を参照してください。

浮動小数点演算子は、次の理由で例外(§11 (例外))をスローできます。

  • NULL参照のボックス化解除変換(§5.1.8)が必要な場合、浮動小数点演算子はNullPointerExceptionをスローできます。

  • 増分演算子および減分演算子++ (§15.14.2§15.15.1)および-- (§15.14.3§15.15.2)は、ボクシング変換(§5.1.7)が必要で、変換を実行するのに十分なメモリーがない場合、OutOfMemoryErrorをスローできます。

例4.2.4-1. 浮動小数点演算

class Test {
    public static void main(String[] args) {
        // An example of overflow:
        double d = 1e308;
        System.out.print("overflow produces infinity: ");
        System.out.println(d + "*10==" + d*10);
        // An example of gradual underflow:
        d = 1e-305 * Math.PI;
        System.out.print("gradual underflow: " + d + "\n   ");
        for (int i = 0; i < 4; i++)
            System.out.print(" " + (d /= 100000));
        System.out.println();
        // An example of NaN:
        System.out.print("0.0/0.0 is Not-a-Number: ");
        d = 0.0/0.0;
        System.out.println(d);
        // An example of inexact results and rounding:
        System.out.print("inexact results with float:");
        for (int i = 0; i < 100; i++) {
            float z = 1.0f / i;
            if (z * i != 1.0f)
                System.out.print(" " + i);
        }
        System.out.println();
        // Another example of inexact results and rounding:
        System.out.print("inexact results with double:");
        for (int i = 0; i < 100; i++) {
            double z = 1.0 / i;
            if (z * i != 1.0)
                System.out.print(" " + i);
        }
        System.out.println();
        // An example of cast to integer rounding:
        System.out.print("cast to int rounds toward 0: ");
        d = 12345.6;
        System.out.println((int)d + " " + (int)(-d));
    }
}

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

overflow produces infinity: 1.0E308*10==Infinity
gradual underflow: 3.141592653589793E-305
    3.1415926535898E-310 3.141592653E-315 3.142E-320 0.0
0.0/0.0 is Not-a-Number: NaN
inexact results with float: 0 41 47 55 61 82 83 94 97
inexact results with double: 0 49 98
cast to int rounds toward 0: 12345 -12345

この例は、特に段階的なアンダーフローによって精度が徐々に低下することがあることを示しています。

i0の場合の結果は、zが正の無限大になり、z * 01.0と等しくないNaNになるように、ゼロによる除算を伴います。


4.2.5. boolean型およびブール値

boolean型は、trueおよびfalseリテラル(§3.10.3)で示される、2つの可能な値を持つ論理量を表します。

ブール演算子は次のとおりです。

  • 関係演算子==および!= (§15.21.2)

  • 論理補数演算子! (§15.15.6)

  • 論理演算子&^および| (§15.22.2)

  • 条件演算子および条件演算子&& (§15.23)および|| (§15.24)

  • 条件演算子? : (§15.25)

  • 文字列連結演算子+ (§15.18.1)は、Stringオペランドとbooleanオペランドを指定すると、booleanオペランドをString ("true"または"false")に変換し、2つの文字列を連結した、新しく作成されたStringを生成します。

ブール式は、次のいくつかの文で制御フローを決定します。

boolean式は、条件付き? :演算子(§15.25)で評価される副式も決定します。

制御フロー文および条件演算子? :の最初のオペランドとして使用できるのは、boolean式およびBoolean式のみです。

整数式または浮動小数点式xは、式x!=0によってゼロ以外の値がtrueであるというC言語規則に従って、boolean値に変換できます。

オブジェクト参照objは、式obj!=nullによってnull以外の参照がtrueであるというC言語規則に従って、boolean値に変換できます。

boolean値は、文字列変換によってStringに変換できます(§5.4)。

boolean値は、boolean型、Boolean型またはObject型(§5.5)にキャストできます。 boolean型に対する他のキャストは許可されません。

4.3. 参照タイプおよび値

参照型には、クラス型(§8.1)、インタフェース型(§9.1)、型変数(§4.4)、および配列型(§10.1)の4種類があります。

サンプル・コード:

class Point { int[] metrics; }
interface Move { void move(int deltax, int deltay); }

クラス型Point(インタフェース型Move)を宣言し、配列型int[](intの配列)を使用して、クラスPointのフィールドmetricsを宣言します。

クラスまたはインタフェース・タイプは、識別子またはドット区切りの識別子シーケンスで構成され、各識別子の後にはオプションで型引数が続きます(§4.5.1)。 型引数がクラスまたはインタフェース型の任意の場所にある場合、パラメータ化された型です(§4.5)。

クラスまたはインタフェース型の各識別子は、パッケージ名または型名(§6.5.1)として分類されます。 型名として分類される識別子には注釈を付けることができます。 クラスまたはインタフェース型の形式がT.id (オプションで型引数が続く)の場合、idは、アクセス可能なメンバー型の単純名であるT (§6.6§8.5§9.5)である必要があります。そうでない場合、コンパイル時にエラーが発生します。 クラスまたはインタフェース・タイプは、そのメンバー・タイプを示します。

4.3.1.  オブジェクト

オブジェクトは、クラス・インスタンスまたは配列です。

参照値(参照のみ)は、これらのオブジェクトへのポインタであり、オブジェクトを参照しない特殊なNULL参照です。

クラス・インスタンスは、クラス・インスタンス作成式(§15.9)によって明示的に作成されます。

配列は、配列作成式(§15.10.1)によって明示的に作成されます。

他の式では、暗黙的にクラス・インスタンス(§12.5)または配列(§10.6)を作成できます。

例4.3.1-1.  オブジェクトの作成

class Point {
    int x, y;
    Point() { System.out.println("default"); }
    Point(int x, int y) { this.x = x; this.y = y; }

    /* A Point instance is explicitly created at
       class initialization time: */
    static Point origin = new Point(0,0);

    /* A String can be implicitly created
       by a + operator: */
    public String toString() { return "(" + x + "," + y + ")"; }
}

class Test {
    public static void main(String[] args) {
        /* A Point is explicitly created
           using newInstance: */
        Point p = null;
        try {
            p = (Point)Class.forName("Point").newInstance();
        } catch (Exception e) {
            System.out.println(e);
        }

        /* An array is implicitly created
           by an array initializer: */
        Point[] a = { new Point(0,0), new Point(1,1) };

        /* Strings are implicitly created
           by + operators: */
        System.out.println("p: " + p);
        System.out.println("a: { " + a[0] + ", " + a[1] + " }");

        /* An array is explicitly created
           by an array creation expression: */
        String[] sa = new String[2];
        sa[0] = "he"; sa[1] = "llo";
        System.out.println(sa[0] + sa[1]);
    }
}

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

default
p: (0,0)
a: { (0,0), (1,1) }
hello

オブジェクトへの参照の演算子は次のとおりです。

  • 修飾名(§6.6)またはフィールド・アクセス式(§15.11)を使用したフィールド・アクセス

  • メソッド呼出し(§15.12)

  • キャスト演算子(§5.5§15.16)

  • 文字列連結演算子+ (§15.18.1)は、Stringオペランドと参照を指定すると、参照オブジェクトのtoStringメソッドを呼び出して参照をStringに変換します(参照またはtoStringの結果がnull参照の場合は"null"を使用します)。その後、2つの文字列を連結した、新しく作成されたStringを生成します。

  • instanceof演算子(§15.20.2)

  • 参照等価演算子==および!= (§15.21.3)

  • 条件演算子? : (§15.25)。

同じオブジェクトに対する参照が多数存在する場合があります。 ほとんどのオブジェクトには状態があり、クラスのインスタンスであるオブジェクトのフィールド、または配列オブジェクトのコンポーネントである変数に格納されます。 2つの変数に同じオブジェクトへの参照が含まれている場合、オブジェクトの状態は、そのオブジェクトに対する1つの変数の参照を使用して変更でき、変更後の状態は、他の変数の参照を介して確認できます。

例4.3.1-2. プリミティブ・アイデンティティと参照アイデンティティ

class Value { int val; }

class Test {
    public static void main(String[] args) {
        int i1 = 3;
        int i2 = i1;
        i2 = 4;
        System.out.print("i1==" + i1);
        System.out.println(" but i2==" + i2);
        Value v1 = new Value();
        v1.val = 5;
        Value v2 = v1;
        v2.val = 6;
        System.out.print("v1.val==" + v1.val);
        System.out.println(" and v2.val==" + v2.val);
    }
}

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

i1==3 but i2==4
v1.val==6 and v2.val==6

v1.valv2.valは、唯一のnew式によって作成された1つのValueオブジェクトで同じインスタンス変数(§4.12.3)を参照し、i1i2は異なる変数であるためです。


各オブジェクトはモニター(§17.1)に関連付けられており、モニターはsynchronizedメソッド(§8.4.3)およびsynchronized文(§14.19)によって使用され、複数のスレッド(§17 (スレッドおよびロック))による状態への同時アクセスを制御できます。

4.3.2. クラスObject

クラスObjectは、他のすべてのクラスのスーパークラス(§8.1.4)です。

すべてのクラスおよび配列型は、クラスObjectのメソッドを継承(§8.4.8)します。これらのメソッドは、次のように要約されています。

  • メソッドcloneを使用して、オブジェクトを複製します。

  • メソッドequalsは、参照比較ではなく値に基づくオブジェクト等価の概念を定義します。

  • メソッドfinalizeは、オブジェクトが破棄される直前に実行されます(§12.6)。

  • メソッドgetClassは、オブジェクトのクラスを表すClassオブジェクトを返します。

    Classオブジェクトは、参照タイプごとに存在します。 たとえば、クラスの完全修飾名、そのメンバー、その即時スーパークラスおよび実装するすべてのインタフェースを検出するために使用できます。

    getClassのメソッド呼出し式の型はClass<? extends |T|>で、TgetClass (§15.12.1)で検索されたクラスまたはインタフェースで、|T|はT (§4.6)の消去を示します。

    synchronized (§8.4.3.6)と宣言されたクラス・メソッドは、クラスのClassオブジェクトに関連付けられたモニターで同期されます。

  • メソッドhashCodeは、メソッドequalsとともに、java.util.HashMapなどのハッシュ表で非常に役立ちます。

  • メソッドwaitnotifyおよびnotifyAllは、スレッドを使用した同時プログラミングで使用されます(§17.2)。

  • メソッドtoStringは、オブジェクトのString表現を返します。

4.3.3. クラスString

クラスStringのインスタンスは、Unicodeコード・ポイントのシーケンスを表します。

Stringオブジェクトには、定数(変更されていない)値があります。

文字列リテラル(§3.10.5)およびテキスト・ブロック(§3.10.6)は、クラスStringのインスタンスへの参照です。

文字列連結演算子+(§15.18.1)は、結果が定数式でない場合(§15.29)に、新しいStringオブジェクトを暗黙的に作成します。

4.3.4. 参照タイプが同じ場合

2つの参照型は、同じモジュール(§7.3)に関連付けられたコンパイル単位で宣言され、それらが同じバイナリ名(§13.1)を持ち、その型引数(存在する場合)が同じで、この定義を再帰的に適用する場合、同じコンパイル時型です。

2つの参照型が同じ場合、同じクラスまたは同じインタフェースと言われることがあります。

実行時に、同じバイナリ名を持つ複数の参照型を異なるクラス・ローダーによって同時にロードできます。 これらの型は、同じ型宣言を表す場合と表さない場合があります。 このような2つの型が同じ型宣言を表している場合でも、それらは個別とみなされます。

次の場合は、2つの参照タイプが同じランタイム・タイプです:

  • これらは、クラスまたは両方のインタフェース・タイプであり、同じクラス・ローダーによって定義され、同じバイナリ名(§13.1)を持ちます。この場合、同じランタイム・クラスまたは同じランタイム・インタフェースと呼ばれることもあります。

  • これらはどちらも配列型であり、そのコンポーネント型は同じ実行時型です(§10 (Arrays))。

4.4. 型変数

型変数は、クラス、インタフェース、メソッドおよびコンストラクタ本体の型として使用される修飾されていない識別子です。

型変数は、汎用クラス、インタフェース、メソッドまたはコンストラクタの型パラメータの宣言によって導入されます(§8.1.2§9.1.2§8.4.4§8.8.4)。

TypeParameterModifier:
TypeBound:
AdditionalBound:

型パラメータとして宣言された型変数のスコープは、§6.3に規定されています。

型パラメータとして宣言されたすべての型変数には、バインドがあります。 型変数に対してバインドが宣言されていない場合は、Objectとみなされます。 バインドが宣言されている場合は、次のいずれかで構成されます。

  • 単一の型変数 T、または

  • クラスまたはインタフェース型 Tのあとに、インタフェース型 I1 & ... & Inが続く可能性があります。

I1、...、Inのいずれかの型がクラス型または型変数である場合、コンパイル時にエラーが発生します。

バインドのすべての構成要素タイプの消去(§4.6)は、ペア単位で異なる必要があります。そうしないと、コンパイル時にエラーが発生します。

型変数は、同じ汎用インタフェースのパラメータ化が異なる2つのインタフェース・タイプのサブタイプを同時に指定することはできません。そうしないと、コンパイル時にエラーが発生します。

バインド内の型の順序は、型変数の消去がバインド内の最初の型によって決定され、クラス型または型変数が最初の位置にのみ表示されるという点で、重要になります。

バインドされたT & I1 & ... & Inを持つ型変数Xのメンバーは、型変数が宣言されているポイントに出現する交差型(§4.9) T1 & ... & Inのメンバーです。

例4.4-1. 型変数のメンバー

package TypeVarMembers;

class C {
    public    void mCPublic()    {}
    protected void mCProtected() {}
              void mCPackage()   {}
    private   void mCPrivate()   {}
}

interface I {
    void mI();
}

class CT extends C implements I {
    public void mI() {}
}

class Test {
    <T extends C & I> void test(T t) {
        t.mI();           // OK
        t.mCPublic();     // OK
        t.mCProtected();  // OK
        t.mCPackage();    // OK
        t.mCPrivate();    // Compile-time error
    }
}

型変数Tは、交差型C & Iと同じメンバーを持ちます。このメンバーは、同等のスーパータイプを持つ同じスコープで定義された空のクラスCTと同じメンバーを持ちます。 インタフェースのメンバーは常にpublicであるため、常に継承されます(オーバーライドされないかぎり)。 したがって、mICTおよびTのメンバーです。 Cのメンバーの中では、mCPrivate以外はすべてCTによって継承されるため、CTTの両方のメンバーです。

CTとは異なるパッケージで宣言されている場合、Tが宣言された時点ではそのメンバーにアクセスできないため、mCPackageの呼出しによってコンパイル時にエラーが発生します。


4.5.  パラメータ化された型

汎用的なクラスまたはインタフェース(§8.1.2§9.1.2)は、パラメータ化された型のセットを定義します。

パラメータ化された型は、C<T1、...、Tn>という形式のクラスまたはインタフェース型です。Cは汎用クラスまたはインタフェースの名前で、<T1、...、Tn>は、汎用クラスまたはインタフェースの特定のパラメータ化を示す型引数のリストです。

汎用クラスまたはインタフェースには、型パラメータ F1、...、Fnと対応する境界 B1、...、Bnがあります。 パラメータ化された型のそれぞれの型引数Tiの対象は、対応する境界にリストされたすべての型のサブタイプであるすべての型です。 つまり、Biの各バインド・タイプSについて、TiS[F1:=T1,...,Fn:=Tn]のサブタイプです(§4.10)。

パラメータ化された型C<T1、...、Tn>は、次のすべてに該当する場合、整形式です。

  • Cは、汎用クラスまたは汎用インタフェースの名前です。

  • 型引数の数が、Cの汎用宣言の型パラメータの数と同じである。

  • C<X1、...、Xn>型になる変換(§5.1.10)を取得する場合、各型引数Xiは、Biの各バインド型Sのサブ型Sです。

パラメータ化された型が整形式ではない場合は、コンパイル時にエラーが発生します。

この仕様では、クラスまたはインタフェース型を話すたびに、明示的に除外されないかぎり、パラメータ化された型も含まれます。

次のいずれかが当てはまる場合、パラメータ化された2つの型は明確に区別されます

  • これらが、別個の汎用型宣言のパラメータ化である。

  • その型引数のいずれかが明確に異なる。

§8.1.2の例のジェネリッククラスを考えると、整形式のパラメータ化された型がいくつかある:

  • Seq<String>

  • Seq<Seq<String>>

  • Seq<String>.Zipper<Integer>

  • Pair<String,Integer>

これらの汎用クラスの誤ったパラメータ化を次に示します。

  • Seq<int>は、プリミティブ型を型引数にできないため、無効です。

  • Pair<String>は、型引数が不足しているため無効です。

  • 型引数が多すぎるため、Pair<String,String,String>は不正です。

パラメータ化された型は、ネストされた汎用クラスまたはインタフェースのパラメータ化です。 たとえば、非汎用クラスCに1つの型パラメータを持つ汎用メンバー・クラスDがある場合、C.D<Object>はパラメータ化された型です。 一方、1つの型パラメータを持つ汎用クラスCに非汎用メンバー・クラスDがある場合、クラスDが汎用でない場合でも、メンバー・クラス・タイプC<String>.Dはパラメータ化された型です。

4.5.1. パラメータ化された型の型引数

型引数は、参照型またはワイルドカードのいずれかです。 ワイルドカードは、型パラメータに関する部分的な知識のみが必要な場合に役立ちます。

TypeArguments:
TypeArgumentList:
ワイルドカード:
WildcardBounds:

ワイルドカードには、通常の型の変数宣言と同様に、明示的な境界を指定できます。 上限は、次の構文で示されます。ここで、Bは境界です。

? extends B

メソッド・シグネチャで宣言される通常の型変数とは異なり、ワイルドカードを使用する場合、型推論は必要ありません。 したがって、Bは下限である次の構文を使用して、ワイルドカードで下限を宣言できます。

? super B

ワイルドカード? extends Objectは、バインドなしワイルドカード?と同等です。

次のいずれかが当てはまる場合、2つの型引数は明確に区別されます

  • 引数が型変数でもワイルドカードでもなく、2つの引数が同じ型ではありません。

  • 1つの型引数は、Sのバウンド(型変数の場合)または上限(必要に応じて取得変換(§5.1.10)を使用したワイルドカードの場合)を持つ型変数またはワイルドカードであり、もう1つの型引数Tは型変数またはワイルドカードではなく、|S| <: |T|も|T| <: |S|も(§4.8§4.10)でもありません。

  • 各型引数は、Sおよび Tの上限(必要に応じて取得変換から)を持つ型変数またはワイルドカードであり、|S| <: |T|も|T| <: |S|もありません。

型引数 T1は、Tが示す別の型引数 T2T2 <= T1に記述し、T2が示す型セットが、次の規則(<:はサブタイピング(§4.10)を表す)の下で、T1が示す型セットのサブセットである可能性があります。

  • ? extends T <= ? extends S (T <: Sの場合)

  • ? extendsT <= ?

  • ? super T <= ? super S (S <: Tの場合)

  • ? superT <= ?

  • ? super T <= ? extends Object

  • <= T

  • <= ? extends

  • <= ? super

ワイルドカードと確立された型理論の関係は興味深いものであり、ここでは簡単に理解できます。 ワイルドカードは、存在タイプの制限された形式です。 汎用型宣言G<T extends B>の場合、G<?>は、一部のX <: Bとほぼ同じです。 G<X>

歴史的に、ワイルドカードは、伊賀らし厚寿とミルコ・ヴィロリの作品の直接の子孫です。 より包括的なディスカッションに関心のある読者は、Proceedings of the 16th European Conference on Object Oriented Programming (ECOOP 2002)On Variance-Based Subtyping for Parametric Typesを参照してください。 この作業自体は、Kresten ThorupとMads Torgersen(Unifying Genericity、ECOOP 99)による以前の作業と、Pierre AmericaのPOOL(OOPSLA 89)に関する作業にさかのぼる宣言ベースの差異に関する長い伝統に基づいて構築されています。

ワイルドカードは、特にIgarashiおよびViroliによって記述されたclose操作ではなく、取得変換(§5.1.10)の使用において、前述の論文で説明されている構造物とは特定の詳細が異なります。 ワイルドカードの正式な説明については、Mads Torgersen、Erik Ernst、Christian Plesner HansenによるWild FJ(オブジェクト指向プログラミングの基礎に関する第12回ワークショップ)を参照してください(FOOL 2005)。

例4.5.1-1. バインドなしワイルドカード

import java.util.ArrayList;
import java.util.Collection;

class Test {
    static void printCollection(Collection<?> c) {
                                // a wildcard collection
        for (Object o : c) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        Collection<String> cs = new ArrayList<String>();
        cs.add("hello");
        cs.add("world");
        printCollection(cs);
    }
}

受信パラメータcの型としてCollection<Object>を使用すると、あまり役に立たないことに注意してください。このメソッドは、Collection<Object>型の引数式でのみ使用でき、これは非常にまれです。 対照的に、無制限のワイルドカードを使用すると、任意の種類のコレクションを引数として渡すことができます。

配列の要素タイプがワイルドカードによってパラメータ化される例を次に示します。


public Method getMethod(Class<?>[] parameterTypes) { ... }


例4.5.1-2. バインドされたワイルドカード

boolean addAll(Collection<? extends E> c)

ここでは、メソッドはインタフェースCollection<E>内で宣言され、その着信引数のすべての要素を、そのメソッドが呼び出されるコレクションに追加するように設計されています。 通常、cの型としてCollection<E>を使用する傾向がありますが、これは不必要に制限されます。 別の方法として、メソッド自体を汎用として宣言する方法があります。

<T> boolean addAll(Collection<T> c)

このバージョンは十分に柔軟ですが、typeパラメータはシグネチャで1回のみ使用されることに注意してください。 これは、引数の型、戻り型またはスロー型(あるいはその両方)間の相互依存性を表現するために、型パラメータが使用されていないという事実を反映しています。 このような相互依存関係がない場合、一般的なメソッドは不正なスタイルとみなされ、ワイルドカードが推奨されます。

Reference(T referent, ReferenceQueue<? super T> queue)

ここで、参照は、要素タイプが参照のT型のスーパータイプである任意のキューに挿入できます。Tは、ワイルドカードの下限です。


4.5.2.  パラメータ化された型のメンバーおよびコンストラクタ

CA1、...、Anという型パラメータを持つ汎用クラスまたはインタフェースとし、C<T1、...、Tn>Cのパラメータ化にします。ここで、1 i nは、Tiは(ワイルドカードではなく)型です。 次に、

  • mCのメンバー宣言またはコンストラクタ宣言とし、宣言された型はT (§8.2§8.8.6)とします。

    C<T1、...、Tn>mの型はT[A1:=T1,...,An:=Tn]です。

  • mDのメンバーまたはコンストラクタ宣言にします。ここで、Dは、Cによって拡張されたクラス、またはCによって実装されたインタフェースです。 D<U1、...、Uk>C<T1、...、Tn>のスーパータイプ(Dに対応する§4.10.2)にします。

    C<T1、...、Tn>mのタイプは、D<U1、...、Uk>mのタイプです。

Cのパラメータ化の型引数のいずれかがワイルドカードである場合、次のようになります。

  • C<T1、...、Tn>のフィールド、メソッドおよびコンストラクタのタイプは、C<T1、...、Tn> (§5.1.10)の取得変換におけるフィールド、メソッドおよびコンストラクタのタイプです。

  • Dは、Cの(場合によっては汎用の)クラスまたはインタフェース宣言であるとします。 次に、C<T1、...、Tn>D型がDで、Dが汎用の場合、すべての型引数がバインドされていないワイルドカードになります。

取得変換を実行せずにパラメータ化された型のメンバーにアクセスすることは不可能であり、クラス・インスタンス作成式(§15.9)でキーワードnewの後にワイルドカードを使用することは不可能であるため、これは結果ではありません。

前の段落の唯一の例外は、ネストされたパラメータ化された型がinstanceof演算子(§15.20.2)の式として使用される場合で、取得変換は適用されません。

汎用クラスまたはインタフェースで宣言されたstaticメンバーは、汎用クラスまたは汎用インタフェースの名前(§6.1§6.5.5.2§6.5.6.2)を使用して参照する必要があります。そうしないと、コンパイル時にエラーが発生します。

つまり、パラメータ化された型を使用して、汎用型宣言で宣言されたstaticメンバーを参照することは不正です。

4.6.  型の消去

型イレイジャは、型(パラメータ化された型および型変数を含む)から型(パラメータ化された型または型変数ではない)へのマッピングです。 タイプ Tの消去について|T|を記述します。 消去マッピングは、次のように定義されます。

  • パラメータ化された型(§4.5)G<T1、...、Tn>の消去は|G|です。

  • ネストされた型T.Cの消去は|T|です。C

  • 配列型T[]の消去は、|T|[]です。

  • 型変数の消去(§4.4)は、その左端の境界の消去です。

  • 他のすべてのタイプの消去は、タイプそのものです。

型消去は、コンストラクタまたはメソッドのシグネチャ(§8.4.2)も、パラメータ化された型または型変数を持たないシグネチャにマップします。 コンストラクタまたはメソッド・シグネチャsの消去は、sと同じ名前と、sで指定されたすべての仮パラメータ・タイプの消去で構成されるシグネチャです。

メソッドの戻り型(§8.4.5)および汎用メソッドまたはコンストラクタの型パラメータ(§8.4.4§8.8.4)も、メソッドまたはコンストラクタの署名が消去されると消去されます。

汎用メソッドのシグネチャの消去には型パラメータがありません。

4.7. 再可能タイプ

一部のタイプの情報はコンパイル中に消去されるため、実行時にすべてのタイプを使用できるわけではありません。 実行時に完全に使用可能なタイプは、変更可能なタイプと呼ばれます。

次のいずれかが保持されている場合のみ、型は再可能です。

  • 非汎用クラスまたはインタフェース型の宣言を参照します。

  • これはパラメータ化された型で、すべての型引数は無制限のワイルドカードです(§4.5.1)。

  • これはRAW型です(§4.8)。

  • これはプリミティブ型です(§4.2)。

  • 要素型が再可能である配列型(§10.1)です。

  • これは、.で区切られたT型ごとに、T自体を再有効化するネストされた型です。

    たとえば、汎用クラスX<T>に汎用メンバー・クラスY<U>がある場合、X<?>.Y<?>は、X<?>が再可能であり、Y<?>が再可能であるため、タイプX?>は再可能です。 タイプX<?>.Y<Object>は、Y<Object>が再可能でないため再可能ではありません。

交差タイプは再利用できません。

すべての汎用型を再利用可能にしないという決定は、Javaプログラミング言語の型システムに関する最も重要で論争の的になる設計上の決定の1つです。

最終的に、この決定の最も重要な動機は、既存のコードとの互換性です。 素朴な意味では、ジェネリックスなどの新しい構造体の追加は、既存のコードには影響しません。 Javaプログラミング言語は、以前のバージョンで記述されたすべてのプログラムが新しいバージョンでその意味を保持しているかぎり、以前のバージョンと互換性があります。 しかし、言語互換性と呼ばれるこの概念は、純粋に理論的な関心事です。 実際のプログラム(「Hello World」などの簡単なプログラム)は、複数のコンパイル・ユニットで構成され、その一部はJava SEプラットフォームによって提供されます(java.langjava.utilの要素など)。 実際には、最小要件はプラットフォーム互換性です。つまり、以前のバージョンのJava SEプラットフォーム用に記述されたプログラムは、新しいバージョンでは変更されずに機能し続けます。

プラットフォームの互換性を提供する1つの方法は、既存のプラットフォーム機能を変更せず、新しい機能を追加することです。 たとえば、java.utilの既存のCollections階層を変更するのではなく、ジェネリックスを利用した新しいライブラリを導入できます。

このようなスキームのデメリットは、コレクション・ライブラリの既存のクライアントが新しいライブラリに移行することが非常に困難であることです。 コレクションは、独立して開発されたモジュール間でデータを交換するために使用されます。ベンダーが新しい汎用ライブラリに切り替えることを決定した場合、そのベンダーは、クライアントとの互換性を確保するために、2つのバージョンのコードを配布する必要もあります。 他のベンダー・コードに依存するライブラリは、サプライヤのライブラリが更新されるまで、ジェネリックスを使用するように変更できません。 2つのモジュールが相互に依存する場合は、変更を同時に行う必要があります。

明らかに、プラットフォーム互換性は、前述のように、ジェネリックスなどの広範囲な新機能を採用するための現実的な道筋を提供していません。 したがって、汎用型システムの設計では、移行の互換性がサポートされます。 移行の互換性により、既存のコードの進化は、独立して開発されたソフトウェア・モジュール間の依存関係を課すことなく、ジェネリックスを利用できます。

移行互換性の価格は、少なくとも移行が行われている間は、汎用型システムの完全かつ健全な再検証が不可能であるということです。

4.8.  RAW型

非汎用レガシー・コードとのインタフェースを容易にするために、パラメータ化された型(§4.5)の消去(§4.6)または要素型がパラメータ化された型である配列型(§10.1)の消去(§4.6)のタイプとして使用できます。 このような型は、RAWタイプと呼ばれます。

より正確に言うと、RAW型は次のいずれかとして定義できます。

  • 付随する型引数リストなしで、汎用クラスまたはインタフェース宣言の名前を取得することによって形成される参照型。

  • 要素型がRAW型である配列型。

  • Rのスーパークラスまたはスーパーインタフェースから継承されていないRAW型Rの内部メンバー・クラスの名前。

非汎用クラスまたはインタフェースの型がraw型ではありません。

raw型の内部メンバー・クラスの名前がRAWとみなされる理由を確認するには、次の例を考えてみます。

class Outer<T>{
    T t;
    class Inner {
        T setOuterT(T t1) { t = t1; return t; }
    }
}

Innerのメンバーの型は、Outerの型パラメータによって異なります。 OuterがRAWの場合、Tに有効なバインディングがないため、InnerもRAWとして扱われる必要があります。

このルールは、継承されていない内部メンバー・クラスにのみ適用されます。 型変数に依存する継承された内部メンバー・クラスは、この項の後半で説明するように、RAW型のスーパータイプが消去されるルールの結果としてRAW型として継承されます。

前述のルールには、RAW型の汎用内部クラスは、それ自体のみをRAW型として使用できるという意味もあります。

class Outer<T>{
    class Inner<S> {
        S s;
    }
}

Innerに部分的にRAW型(まれな型)としてアクセスすることはできません。

Outer.Inner<Double> x = null;  // illegal
Double d = x.s;

Outer自体はRAWであるため、Innerを含むすべての内部クラスであるため、どの型引数も内部に渡すことはできません。

raw型のスーパークラス型(それぞれスーパーインタフェース型)は、名前付きクラスまたはインタフェースのスーパークラス型(スーパーインタフェース型)の消去です。

スーパークラスまたはスーパーインタフェースから継承されていないRAW型Cのコンストラクタ(§8.8)、インスタンス・メソッド(§8.4§9.4)または非staticフィールド(§8.3)のタイプは、汎用クラスまたはインタフェースCでのそのタイプの消去です。

メンバーがクラスまたはインタフェースDで宣言されたRAW型Cの継承されたインスタンス・メソッドまたは非staticフィールドの型は、Dという名前のCのスーパータイプのメンバーの型です。

RAW型Cstaticメソッドまたはstaticフィールドの型は、汎用クラスまたはインタフェースCの型と同じです。

スーパークラスまたはスーパーインタフェースから継承されていないRAW型の非staticメンバー・クラスまたはインタフェースに型引数を渡すのは、コンパイル時エラーです。

パラメータ化された型のメンバー・クラスまたはインタフェースをRAW型として使用しようとすると、コンパイル時にエラーが発生します。

これは、「未加工の」型についての禁止事項が、修飾型がパラメータ化されるが内部クラスをRAW型として使用しようとする場合にまで及ぶことを意味します。

Outer<Integer>.Inner x = null; // illegal

これは、前述のものとは反対のケースです。 この不完全な型が妥当であることを示す実質的な根拠はありません。 レガシー・コードでは、型引数は使用されません。 非レガシー・コードでは、汎用型を適切に使用して、すべての必要な型引数を渡す必要があります。

RAW型の使用は、レガシー・コードの互換性の便宜的措置としてのみ許可されています。 Javaプログラミング言語に汎用が導入された後に作成するコードでRAW型を使用することはお薦めしません。 今後のバージョンのJavaプログラミング言語でRAW型を使用できなくなる可能性があります。

型指定ルールの潜在的な違反が常に確実に通知されるように、RAW型のメンバーがアクセスされると、特定の場合に、コンパイル時に未チェック警告が発生します。 RAW型のメンバーまたはコンストラクタにアクセスしたときのコンパイル時の未チェック警告に関するルールは、次のとおりです。

  • フィールドへの割当て時: フィールド・アクセス式(§15.11)のPrimaryのタイプがRAW型の場合、消去によってフィールドのタイプが変更されると、コンパイル時の未チェックの警告が発生します。

  • メソッドまたはコンストラクタの呼出し時: 検索するクラスまたはインタフェースの型(§15.12.1)がRAW型の場合、消去によってメソッドまたはコンストラクタの仮パラメータ型が変更されると、コンパイル時の未チェックの警告が発生します。

  • 仮パラメータ型が消去(戻り型またはthrows句、あるいはその両方が変更された場合でも)、フィールドからの読取り、またはRAW型のクラス・インスタンス作成で変更されない場合、メソッド・コールに対してコンパイル時の未チェックの警告は発生しません。

上記の未チェックの警告は、参照変換の絞り込み(§5.1.6)、未チェックの変換(§5.1.9)、メソッド宣言(§8.4.1§8.4.8.3)、および特定の式(§15.12.4.2§15.13.2§15.27.3)から発生する可能性のある未チェックの警告とは異なります。

ここでの警告は、レガシー・コンシューマが生成されたライブラリを使用する場合を示しています。 たとえば、ライブラリはVector<T>型のフィールドfを持つ汎用クラスFoo<T extends String>を宣言しますが、コンシューマはe.fに整数のベクトルを割り当てます(eはRAW型Foo)。 レガシー・コンシューマは、生成済ライブラリの生成済コンシューマに対するヒープ汚染(§4.12.2)を引き起こした可能性があるため、警告を受け取ります。

(レガシー・コンシューマは、警告を受信せずに、ライブラリから独自のVector変数にVector<String>を割り当てることができます。 つまり、Javaプログラミング言語のサブタイピング・ルール(§4.10.2)により、RAW型の変数に、その型のパラメータ化されたインスタンスの値を割り当てることが可能になります。

未チェックの変換からの警告は、生成済コンシューマがレガシー・ライブラリを使用するデュアル・ケースをカバーしています。 たとえば、ライブラリのメソッドはRAW戻り型Vectorを持ちますが、コンシューマはメソッド呼出しの結果をVector<String>型の変数に割り当てます。 rawベクトルの要素型がStringと異なる可能性があるため、これは安全ではありません。ただし、レガシー・コードとのインタフェースを有効にするために、未チェックの変換を使用することは許可されています。 未チェックの変換からの警告は、生成済コンシューマがプログラム内の他のポイントでヒープ汚染によって問題が発生する可能性があることを示しています。

例4.8-1.  RAW型

class Cell<E> {
    E value;

    Cell(E v)     { value = v; }
    E get()       { return value; }
    void set(E v) { value = v; }

    public static void main(String[] args) {
        Cell x = new Cell<String>("abc");
        System.out.println(x.value);  // OK, has type Object
        System.out.println(x.get());  // OK, has type Object
        x.set("def");                 // unchecked warning
    }
}

例4.8-2. RAW型と継承

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

class NonGeneric {
    Collection<Number> myNumbers() { return null; }
}

abstract class RawMembers<T> extends NonGeneric
                             implements Collection<String> {
    static Collection<NonGeneric> cng =
        new ArrayList<NonGeneric>();

    public static void main(String[] args) {
        RawMembers rw = null;

        Collection<Number> cn = rw.myNumbers();
                                 // OK

        Iterator<String> is   = rw.iterator();
                                 // Unchecked warning

        Collection<NonGeneric> cnn = rw.cng;
                                      // OK, static member
    }
}

このプログラム(これは実行されない)では、RawMembers<T>は次のメソッドを継承します。

Iterator<String> iterator()

Collection<String>スーパーインタフェースから。 RAW型RawMembersは、Collectionからiterator()を継承し、Collection<String>を消去します。これは、RawMembersiterator()の戻り型がIteratorであることを意味します。 その結果、rw.iterator()Iterator<String>に割り当てようとすると、未チェックの変換が必要になるため、コンパイル時に未チェックの警告が発行されます。

一方、RawMembersは、消去もNonGenericであるNonGenericクラスからmyNumbers()を継承します。 したがって、RawMembersmyNumbers()の戻り型は消去されず、rw.myNumbers()Collection<Number>に割り当てようとすると、未チェックの変換は必要ないため、コンパイル時の未チェックの警告は発行されません。

同様に、staticメンバーcngは、RAW型のオブジェクトを介してアクセスされた場合でも、パラメータ化された型を保持します。 インスタンスを介したstaticメンバーへのアクセスは不適切なスタイルとみなされるため、お薦めしません。

この例では、RAW型の特定のメンバー、つまり型がパラメータ化されたstaticメンバー、および非汎用スーパータイプから継承されたメンバーが消去されないことが示されています。


RAW型はワイルドカードと密接に関連しています。 どちらも実在型に基づいています。 Raw型は、レガシー・コードとの相互作用に対応するために、型ルールが意図的に不明瞭なワイルドカードと考えることができます。 従来、RAW型はワイルドカードの前にあり、GJで最初に導入され、1998年10月、Gilad Bracha、Martin Odersky、David StoutamireおよびPhilip Wadlerによる「Making the future safe for the past: Adding Genericity to the Java Programming Language」「Proceedings of the ACM Conference on Object-Oriented Programming、 Systems、 Languages and Applications (OOPSLA 98)」の論文で説明されています。

4.9.  交差型

交差型は、T1 & ... & Tn (n > 0)という形式をとります。ここで、Ti (1 i n)は型です。

交差型は、型パラメータ境界(§4.4)およびキャスト式(§15.16)から導出できます。また、取得変換(§5.1.10)および最小上限計算(§4.10.4)のプロセスでも発生します。

交差型の値は、1 i nのすべての型 Tiの値であるオブジェクトです。

すべての交差タイプT1 & ... & Tn インダクションは、次のように交差タイプのメンバーを識別する目的で、概念クラスまたはインタフェースを誘導します。

  • Ti (1 i n)ごとに、CiTi <: Ciのようなもっとも具体的なクラスまたは配列型にします。 次に、iCk <: Ci (1 i n)またはコンパイル時エラーが発生するように、いくつかの kが必要です。

  • 1 j nの場合、Tjが型変数である場合、TjTjpublicメンバーと同じメンバーを持つインタフェースにし、そうでない場合は、Tjがインタフェースである場合は、TjTjにしてください。

  • CkObjectの場合、概念インタフェースが誘発されます。それ以外の場合、概念クラスは直接スーパークラス型Ckで誘発されます。 このクラスまたはインタフェースは、直接スーパーインタフェース型 T1'、 ...、 Tn'を持ち、交差型が出現するパッケージ内で宣言されます。

交差型のメンバーは、それによって帰納されるクラスまたはインタフェースのメンバーになります。

交差型と型変数の境界の違いは、深く検討する価値があります。 すべての型変数の境界は、交差型を帰納します。 この交差型は、一般的に、単一の型で構成される些細なものです。 境界の形式は、特定の不自然な状況が発生しなくなるように制限(クラスまたは型変数にできるのは最初の要素のみ、かつ境界に出現できる型変数は1つのみ)されます。 ただし、キャプチャ変換によって、配列型などのより一般的な境界を持つ型変数が作成される可能性があります。

4.10. サブタイピング

サブタイプとスーパータイプの関係は、型に対する2項関係です。

型のスーパータイプは、S >1 Tと記述された直接スーパータイプ・リレーションに対する反射および推移的なクロージャによって取得されます。この項の後半で述べるルールによって定義されます。 S :> Tを記述して、スーパータイプ関係がSTの間に保持されることを示します。

S適切なスーパータイプであり、S :> TおよびS Tの場合、S > Tが書き込まれます。

タイプTのサブタイプはすべてUタイプであり、TUのスーパータイプであり、null型です。 T <: Sを記述して、サブタイプ関係がT型とS型の間で保持されることを示します。

TS適切なサブタイプで、T < Sが書き込まれます(T <: SおよびS Tの場合)。

TS直接サブタイプで、S >1 Tの場合はT <1 Sと記述されます。

サブタイピングは、パラメータ化された型を介して拡張されません。T <: Sは、C<T> <: C<S>を意味しません。

4.10.1. プリミティブ型間のサブタイピング

次のルールは、プリミティブ型間のスーパータイプの直接関係を定義します。

  • double >1 float

  • float >1 long

  • long >1 int

  • int >1 char

  • int >1 short

  • short >1 byte

4.10.2.  クラスおよびインタフェース型間のサブタイプ

非汎用クラスまたはインタフェース Cが指定された場合、Cタイプの直接スーパータイプは次のすべてになります。

  • Cがクラスである場合、Cの直接スーパークラス型(§8.1.4)。

  • Cの直接スーパーインタフェース型(§8.1.5§9.1.3)。

  • Cが直接スーパーインタフェース型を持たないインタフェースの場合、Object型(§9.1.3)。

型パラメータF1、...Fn (n > 0)を持つ汎用クラスまたはインタフェースCの場合、RAW型C (§4.8)の直接スーパータイプは次のすべてです。

  • Cがクラスである場合、直接スーパークラス型Cの消去(§4.6)。

  • Cの直接スーパーインタフェースタイプの消去。

  • Cが直接スーパーインタフェース型を持たないインタフェースの場合、Object型。

型パラメータ F1、...、Fn (n > 0)を持つ汎用クラスまたはインタフェース Cが指定された場合、パラメータ化された型 C<T1Tn>Ti (1 i n)の direct supertypesは型であり、次のすべてです。

  • Cがクラスである場合、Cの直接スーパークラス型に適用される置換[F1:=T1,...,Fn:=Tn]

  • Cの直接スーパーインタフェース型に適用される置換[F1:=T1,...,Fn:=Tn]

  • C<S1、...、Sn>。ここで、SiTi(1 i n)(§4.5.1)を含みます。

  • Cが直接スーパーインタフェース型を持たないインタフェースの場合、Object型。

  • RAW型C

Given a generic class or interface C with type parameters F1,...,Fn (n > 0), the direct supertypes of the parameterized type C<R1,...,Rn> where at least one of the Ri (1 i n) is a wildcard type argument, are the direct supertypes of the parameterized type C<X1,...,Xn> which is the result of applying capture conversion to C<R1,...,Rn> (§5.1.10).

交差型 T1 & ... & Tnの直接スーパータイプは Ti (1 i n)です。

型変数の直接スーパータイプは、その境界にリストされる型です。

型変数は、その下限の直接スーパータイプです。

null型の直接スーパータイプは、null型自体以外のすべての参照型です。

4.10.3. 配列型間のサブタイピング

次のルールは、配列タイプ間のスーパータイプの直接関係を定義します。

  • STの両方が参照型の場合、S [] >1 T[] iff S >1 T

  • Object >1 Object[]

  • Cloneable >1 Object[]

  • java.io.Serializable >1 Object[]

  • Pがプリミティブ型の場合は、次のようになります。

    • Object >1P[]

    • Cloneable >1P[]

    • java.io.Serializable >1P[]

4.10.4. 最小上限

一連の参照タイプの最小上限(lub)は、他の共有スーパータイプより具体的な共有スーパータイプです(つまり、他の共有スーパータイプは、最小の上限のサブタイプではありません)。 この型lub(U1、 ...、 Uk)は、次のように決定されます。

k = 1の場合、lubは型自体です: lub(U) = U

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

  • Ui (1 i k)ごとに次を実行します。

    ST(Ui)をUiのスーパータイプのセットにします。

    Uiの消去されたスーパータイプのセットであるEST(Ui)を次のようにします。

    EST(Ui) = { |W| W in ST(Ui) }。ここで、|W|はWの消去です。

    消去されたスーパータイプのセットを計算する理由は、一連の型に汎用型の個別パラメータ化がいくつか含まれている状況に対処するためです。

    たとえば、List<String>List<Object>の場合、ST(List<String>) = { List<String>Collection<String>Object }とST(List<Object>) = { List<Object>Collection<Object>Object }を交差させると、セット{ Object }が生成され、上限を安全にListと見なすことができるという事実が失われます。

    一方、EST(List<String>) = { ListCollectionObject }およびEST(List<Object>) = { ListCollectionObject }を交差させると、{ ListCollectionObject }が生成され、最終的にList<?>を生成できるようになります。

  • EC (U1、 ...、 Ukに設定された消去された候補)を、すべてのセットEST(Ui) (1 i k)の交差にします。

  • U1、 ...、 Uk用に設定された最小消去候補であるMECは、次のようにします。

    MEC = { V | V in EC、およびECのすべての W Vの場合、W <: V }とは異なります

    より正確なタイプを推測しようとしているので、他の候補者のスーパータイプである候補者を除外したいと考えています。 これはMECが実現するものです。 実行例では、EC = { ListCollectionObject }であるため、MEC = { List }でした。 次のステップは、MECで消去された型の型引数を回復することです。

  • 汎用型であるMECの任意の要素 Gについて:

    G、Relevant(G)の"関連"パラメータ化は次のようにします。

    Relevant(G) = { V | 1 i k: ST(Ui)のVおよびV = G<...> }

    実行中の例では、MECの唯一の汎用要素はListで、Relevant(List) = { List<String>List<Object> }です。 次に、StringObjectの両方を含む(§4.5.1)Listの型引数を検索します。

    これは、次に定義するパラメータ化(lcp)の最小格納操作によって行われます。 最初の行は、Relevant(List)などのセットに対するlcp()を、セットの要素で構成されるリストに対する操作として定義します。 次の行は、リストの要素に対するペア単位の削減として、このようなリストに対する操作を定義します。 3行目は、パラメータ化された型のペアに対するlcp()の定義です。この定義は、最小包含型引数(lcta)の概念に依存します。lcta()は、考えられるすべてのケースに対して定義されます。

    G、Candidate(G)の「候補」パラメータ化を、Gのすべての関連パラメータ化を含む汎用型Gの最も具体的なパラメータ化にします。

    Candidate(G) = lcp(Relevant(G))

    lcp()は、パラメータ化を含まないものですが、

    • lcp(S) = lcp(e1、 ...、 en) Sの ei (1 i n)

    • lcp(e1, ..., en) = lcp(lcp(e1, e2), e3, ..., en)

    • lcp(G<X1, ..., Xn>, G<Y1, ..., Yn>) = G<lcta(X1, Y1), ..., lcta(Xn, Yn)>

    • lcp(G<X1, ..., Xn>) = G<lcta(X1), ..., lcta(Xn)>

    lcta()は、型引数を最も多く含んでいる引数です(UVは型であると仮定します)

    • lcta(UV) = U (UVの場合)、? extends lub(UV)

    • lcta(U, ? extends V) = ? extends lub(U, V)

    • lcta(U, ? super V) = ? super glb(U, V)

    • lcta(? extends U, ? extends V) = ? extends lub(U, V)

    • lcta(? extends U? super V) = ?

    • lcta(? super U, ? super V) = ? super glb(U, V)

    • Uの上限がObjectの場合はlcta(U) = ?、それ以外の場合は? extends lub(UObject)

    glb()が§5.1.10で定義されている場所。

  • lub(U1、 ...、 Uk)を次のようにします。

    Best(W1) & ... & Best(Wr)

    ここで、Wi (1 i r)はMECの要素であり、U1、...、Ukの最小消去候補セットです。

    これらの要素のいずれかが汎用である場合は、(型引数を回復するために)候補のパラメータ化を使用します。

    Best(X) = Xが汎用の場合はCandidate(X)、それ以外の場合はX

厳密に言えば、このlub()関数は最小の上限に近いだけです。 正式には、U1、...、Ukのすべてが Tのサブタイプであり、Tがlub(U1、...、Uk)のサブタイプになるように、ほかのタイプの Tが存在する可能性があります。 ただし、Javaプログラミング言語のコンパイラは、前述のようにlub()を実装する必要があります。

lub()関数が無限型を生成する可能性があります。 これは許容され、Javaプログラミング言語のコンパイラはこのような状況を認識し、循環データ構造を使用して適切に表現する必要があります。

無限型の可能性は、lub()への再帰呼び出しから生じます。 再帰型に精通している読者は、無限型が再帰型と同じではないことに注意する必要があります。

4.10.5. タイプ予測

合成型変数は、取得変換(§5.1.10)または推論変数解決(§18.4)中にコンパイラによって導入された型変数です。

場合によっては、スーパータイプが特定の合成型変数に言及していないタイプの近いスーパータイプを見つける必要があります。 これを実現するには、上方投影をタイプに適用します。

同様に、下方投影を適用して、ある型のクローズ・サブタイプを検索できます。この場合、そのサブタイプには特定の合成型変数は記述されません。 このような型は必ずしも存在するわけではないため、下方投影は部分関数です。

これらの操作は、参照する必要がなくなった一連の型変数を入力として取ります(制限型変数と呼ばれます)。 操作が繰り返されると、制限された型の変数のセットが暗黙的に再帰アプリケーションに渡されます。

制限付き型の変数セットに対するタイプ Tの上向き投影は、次のように定義されます。

  • Tに制限型変数が指定されていない場合、結果は Tになります。

  • Tが制限型変数である場合、結果は Tの上限の上方投影になります。

  • Tがパラメータ化されたクラス・タイプまたはパラメータ化されたインタフェース・タイプであるG<A1、...、An>の場合、結果はG<A1'、...、An'>になります。ここで、1 i nAi'は、次のようにAiから導出されます。

    • Aiに制限型変数が指定されていない場合、Ai'は Aiになります。

    • Aiが制限された型変数を示す型である場合は、UAiの上向き投影にしてください。 Aiは、次の3つのケースで定義されるワイルドカードです。

      • UObjectではなく、GBiGBii番目のパラメータの宣言された境界、またはBiUのサブタイプでない場合、Ai'は上限ワイルドカード、? extends Uのいずれかになります。

      • それ以外の場合、Aiの下方投影がLの場合、Ai'は下限ワイルドカード? super Lです。

      • それ以外の場合、Aiの下方投影は未定義で、Ai'は無制限のワイルドカード?です。

    • Aiが制限された型変数を指す上限ワイルドカードである場合は、Uをワイルドカード境界の上方投影にします。 Ai'は、上限ワイルドカード? extends Uです。

    • Aiが制限された型変数を示す下限ワイルドカードの場合、ワイルドカード境界の下方投影がLの場合、Aiは下限ワイルドカード? super Lです。ワイルドカード境界の下方投影が定義されていない場合、Ai'は無制限ワイルドカード?です。

  • Tが配列型S[]の場合、結果は、コンポーネント型がSの上方投影である配列型になります。

  • Tが交差タイプの場合、結果は交差タイプになります。 Tの各要素 Sについて、結果は Sの上方投影を要素として持ちます。

制限付き型変数のセットに対する型 Tの下方投影は部分関数であり、次のように定義されます。

  • Tに制限型変数が指定されていない場合、結果は Tになります。

  • Tが制限型変数の場合、Tに下限があり、その境界の下方投影がLの場合、結果はLになります。Tに下限がない場合、またはその境界の下方投影が未定義の場合、結果は未定義になります。

  • Tがパラメータ化されたクラス・タイプまたはパラメータ化されたインタフェース・タイプであるG<A1、...、An>の場合、結果はG<A1'、...、An'>で、1 i nで、型引数AiAiから次のように導出できます。そうでない場合、結果は未定義です。

    • Aiが制限型変数を示していない場合、Ai'は Aiです。

    • Aiが制限型変数を示す型である場合、Aiは未定義です。

    • Aiが制限された型変数を示す上限ワイルドカードである場合、ワイルドカード境界の下方投影がUの場合、Aiは上限ワイルドカード? extends Uです。ワイルドカード境界の下方投影が未定義の場合、Aiは未定義です。

    • Aiが制限された型変数を示す下限ワイルドカードである場合は、Lをワイルドカード境界の上方投影にします。 Ai'は、下限ワイルドカード? super Lです。

  • Tが配列型S[]の場合、Sの下方投影がSの場合、結果はS'[]になり、Sの下方投影が未定義の場合、結果は未定義になります。

  • Tが交差タイプの場合、T要素に対して下方向投影が定義されている場合、結果は交差タイプになり、その要素はTの要素の下方向投影になります。T任意の要素に対して下方向投影が定義されていない場合、結果は定義されません。

lub (§4.10.4)と同様、上向きの投影と下向きの投影は、型変数境界の再帰のために無限のタイプを生成することがあります。

4.11.  型の使用場所

型は、宣言のほとんどおよび特定のタイプの式で使用されます。 具体的には、型が使用される17の型コンテキストがあります:

  • 宣言の内容は次のとおりです:

    1. クラス宣言のextends句またはimplements句の型(§8.1.4§8.1.5)

    2. インタフェース宣言のextends句の型(§9.1.3)

    3. メソッドの戻り型(§8.4.5§9.4)。注釈インタフェースの要素の型を含みます(§9.6.1)。

    4. メソッドまたはコンストラクタのthrows句内の型(§8.4.6§8.8.5§9.4)

    5. 汎用クラス、インタフェース、メソッドまたはコンストラクタの型パラメータ宣言のextends句内の型(§8.1.2§9.1.2§8.4.4§8.8.4)

    6. 列挙定数を含むクラスまたはインタフェースのフィールド宣言内の型(§8.3§9.3) (§8.9.1)

    7. メソッド、コンストラクタまたはラムダ式の仮パラメータ宣言の型(§8.4.1§8.8.1§9.4§15.27.1)

    8. メソッドの受信側パラメータのタイプ(§8.4)

    9. 文(§14.4.2§14.14.1§14.14.2§14.20.3)またはパターン(§14.30.1)のいずれかのローカル変数宣言内の型

    10. 例外パラメータ宣言の型(§14.20)

    11. レコード・クラスのレコード・コンポーネント宣言の型(§8.10.1)

  • 式の場合:

    1. コンストラクタ呼出し、クラス・インスタンス作成式、メソッド呼出し式またはメソッド参照式への明示的な型引数リストの型(§8.8.7.1§15.9§15.12§15.13)

    2. インスタンス化するクラス・タイプ(§15.9)、またはインスタンス化する匿名クラスの直接スーパークラス・タイプまたは直接スーパーインタフェース・タイプ(§15.9.5)としての、不適格なクラス・インスタンス作成式

    3. 配列作成式の要素型(§15.10.1)

    4. キャスト式のキャスト演算子の型(§15.16)

    5. instanceof型比較演算子に続く型(§15.20.2)

    6. メソッド参照式(§15.13)では、メンバー・メソッドを検索するための参照型として、または構築するクラス型または配列型として。

また、タイプは次のように使用します:

  • 前述のコンテキストの配列型の要素型。

  • 前述のコンテキストのいずれかで、パラメータ化された型の非ワイルドカード型引数、またはワイルドカード型引数のバインド。

最後に、型の使用を示す3つの特別な用語がJavaプログラミング言語にあります:

  • 無制限ワイルドカード(§4.5.1)

  • 配列型を示す可変引数パラメータ(§8.4.1)の型の...

  • 構築されたオブジェクトのクラスを示すコンストラクタ宣言(§8.8)内の型の単純名

タイプ・コンテキストのタイプの意味は、次のとおりです:

  • §4.2 (プリミティブ型の場合)

  • §4.4、型パラメータ

  • §4.5、パラメータ化されたクラス型およびインタフェース型の場合、またはパラメータ化された型の型引数またはパラメータ化された型のワイルドカード型引数の境界として出現する場合

  • §4.8 (RAWのクラス・タイプおよびインタフェース・タイプ)

  • §4.9、型パラメータの境界内の交差型

  • §6.5、非汎用クラス、インタフェースおよび型変数の型

  • §10.1、配列型

一部のタイプ・コンテキストでは、参照タイプのパラメータ化方法が制限されます:

  • 次のタイプのコンテキストでは、タイプがパラメータ化された参照タイプの場合、ワイルドカード型引数がないことが必要です:

    • クラス宣言のextendsまたはimplements句内(§8.1.4§8.1.5)

    • インタフェース宣言のextends句内(§9.1.3)

    • インスタンス化するクラス・タイプ(§15.9)、またはインスタンス化する匿名クラスの直接スーパークラス・タイプまたは直接スーパーインタフェース・タイプ(§15.9.5)としての、不適格なクラス・インスタンス作成式

    • メソッド参照式(§15.13)では、メンバー・メソッドを検索するための参照型として、または構築するクラス型または配列型として。

    また、コンストラクタ呼出し、クラス・インスタンス作成式、メソッド呼出し式またはメソッド参照式(§8.8.7.1§15.9§15.12§15.13)への明示的な型引数リストでは、ワイルドカード型引数は許可されません。

  • 次のタイプのコンテキストでは、型がパラメータ化された参照型である場合、(つまり、これは繰返し可能なタイプです)にバインドされていないワイルドカード型引数のみが含まれる必要があります :

    • 配列作成式の要素型として(§15.10.1)

    • instanceof関係演算子に続く型として(§15.20.2)

  • 次の型コンテキストでは、パラメータ化された参照型は例外を伴い、例外の型は非汎用(§6.1)であるため、完全に許可されません。

    • メソッドまたはコンストラクタによってスローできる例外のタイプとして(§8.4.6§8.8.5§9.4)

    • 例外パラメータ宣言内(§14.20)

型が使用されている型コンテキストでは、プリミティブ型を示すキーワードに注釈を付けるか、参照型の単純名を示す識別子に注釈を付けることができます。 配列型のネストに必要なレベルで[の左側に注釈を書き込むことで、配列型に注釈を付けることもできます。 これらの位置の注釈は型注釈と呼ばれ、§9.7.4で指定されます。 いくつか例を挙げます。

  • @Foo int[] f;は、プリミティブ型intに注釈を付けます。

  • int @Foo [] f;は、配列型int[]に注釈を付けます。

  • int @Foo [][] f;は、配列型int[][]に注釈を付けます。

  • int[] @Foo [] f;は、配列型int[][]のコンポーネント型である配列型int[]に注釈を付けます。

宣言に含まれる型コンテキストの一部は、多数の宣言コンテキスト(§9.6.4.1)と同じ構文不動産を占有します。

  • メソッドの戻り型(注釈インタフェースの要素の型を含む)

  • クラスまたはインタフェース(enum定数を含む)のフィールド宣言での型。

  • メソッド、コンストラクタ、またはラムダ式の仮パラメータ宣言における型です。

  • ローカル変数宣言の型。

  • 例外パラメータ宣言のタイプ

  • レコード・クラスのレコード・コンポーネント宣言の型

プログラム内の同じ構文位置が型コンテキストと宣言コンテキストの両方であるという事実は、宣言の修飾子が宣言エンティティの型の直前にあるため発生します。§9.7.4では、このような場所の注釈が型コンテキストまたは宣言コンテキスト、あるいはその両方でどのように表示されるかを説明しています。

例4.11-1.  型の使用方法

import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;

class MiscMath<T extends Number> {
    int divisor;
    MiscMath(int divisor) { this.divisor = divisor; }
    float ratio(long l) {
        try {
            l /= divisor;
        } catch (Exception e) {
            if (e instanceof ArithmeticException)
                l = Long.MAX_VALUE;
            else
                l = 0;
        }
        return (float)l;
    }
    double gausser() {
        Random r = new Random();
        double[] val = new double[2];
        val[0] = r.nextGaussian();
        val[1] = r.nextGaussian();
        return (val[0] + val[1]) / 2;
    }
    Collection<Number> fromArray(Number[] na) {
        Collection<Number> cn = new ArrayList<Number>();
        for (Number n : na) cn.add(n);
        return cn;
    }
    <S> void loop(S s) { this.<S>loop(s); }
}

この例では、次の宣言で型を使用しています:

  • クラスのクラス変数とインスタンス変数(§8.3)、およびインタフェースの定数(§9.3)であるフィールド。ここでは、クラスMiscMathのフィールドdivisorは、int型であると宣言されます。

  • メソッド・パラメータ(§8.4.1)。ここでは、メソッドratioのパラメータlは、long型であると宣言されています。

  • メソッドの結果(§8.4)。ここでは、メソッドratioの結果はfloat型であると宣言され、メソッドgausserの結果はdouble型であると宣言されます。

  • コンストラクタ・パラメータ(§8.8.1)。ここでは、MiscMathのコンストラクタのパラメータがint型であると宣言されています。

  • ローカル変数(§14.4§14.14)。メソッドgausserのローカル変数rおよびvalは、Randomおよびdouble[](doubleの配列)の型であると宣言されます。

  • 例外パラメータ(§14.20)。ここでは、catch句の例外パラメータeException型であると宣言されています。

  • 型パラメータ(§4.4)。ここでは、MiscMathの型パラメータは、宣言されたバインドとしてNumber型を持つ型変数Tです。

  • パラメータ化された型を使用する宣言では、Number型がパラメータ化された型Collection<Number>の型引数(§4.5.1)として使用されます。

および次の種類の式で:

  • クラス・インスタンスの作成(§15.9)。ここでは、gausserメソッドのローカル変数rは、Random型を使用するクラス・インスタンス作成式によって初期化されます。

  • 汎用クラス(§8.1.2)のインスタンス作成(§15.9)。ここでは、Numberを式new ArrayList<Number>()の型引数として使用します。

  • 配列の作成(§15.10.1)。ここでは、メソッドgausserのローカル変数valは、サイズ2のdoubleの配列を作成する配列作成式によって初期化されます。

  • 汎用メソッド(§8.4.4)またはコンストラクタ(§8.8.4)の呼出し(§15.12)。ここでは、メソッドloopは、明示的な型引数Sを使用して自身をコールします。

  • キャスト(§15.16)。ここでは、メソッドratioreturn文がキャストでfloat型を使用します。

  • instanceof演算子(§15.20.2)。ここでは、instanceof演算子は、eArithmeticException型と代入互換性があるかどうかをテストします。


4.12. 変数

変数は格納場所であり、コンパイル時型と呼ばれることがあり、これはプリミティブ型(§4.2)または参照型(§4.3)のいずれかです。

変数の値は、代入(§15.26)または接頭辞または接頭辞++(増分)または--(減分)演算子(§15.14.2§15.14.3§15.15.1§15.15.2)によって変更されます。

プログラムがコンパイル時の未チェックの警告(§4.12.2)を発生させないかぎり、変数の値とその型との互換性は、Javaプログラミング言語の設計によって保証されます。 デフォルト値(§4.12.5)は互換性があり、変数へのすべての代入は代入互換性(§5.2)のためにチェックされ、通常はコンパイル時に行われますが、配列を含む単一のケースではランタイム・チェックが行われます(§10.5)。

4.12.1. プリミティブ型の変数

プリミティブ型の変数は、常にその正確なプリミティブ型のプリミティブ値を保持します。

4.12.2. 参照タイプの変数

クラス型Tの変数は、NULL参照またはクラスTのインスタンスへの参照、またはTのサブクラスであるクラスの参照を保持できます。

インタフェース・タイプの変数は、null参照またはインタフェースを実装するクラスの任意のインスタンスへの参照を保持できます。

変数が常に宣言された型のサブタイプを参照することは保証されず、宣言された型のサブクラスまたはサブインタフェースのみを参照することに注意してください。 これは、次で説明するヒープ汚染の可能性によるものです。

Tがプリミティブ型の場合、「Tの配列」型の変数は、NULL参照または「Tの配列」型の任意の配列への参照を保持できます。

Tが参照型の場合、Tの配列型の変数は、null参照またはS型がT型のサブクラスまたはサブインタフェースとなるようにSの配列型の任意の配列への参照を保持できます。

Object[]型の変数は、任意の参照型の配列への参照を保持できます。

Object型の変数は、クラスまたは配列のインスタンスであるかどうかに関係なく、null参照または任意のオブジェクトへの参照を保持できます。

パラメータ化された型の変数は、そのパラメータ化された型でないオブジェクトを参照する可能性があります。 この状況は、ヒープ汚染と呼ばれます。

ヒープ汚染は、プログラムが、コンパイル時の未チェックの警告(§4.8§5.1.6§5.1.9§8.4.1§8.4.8.3§8.4.8.4§9.4.1.2§15.12.4.2)、またはプログラムがRAWまたは非汎用のいずれかのスーパータイプの配列変数を介して、非対応要素タイプの配列変数を別名設定する場合にのみ発生します。

たとえば、次のコードは:

List l = new ArrayList<Number>();
List<String> ls = l;  // Unchecked warning

コンパイル時(コンパイル時型チェック・ルールの制限内)または実行時に、変数lが実際にList<String>を参照しているかどうかは確認できないため、コンパイル時の未チェックの警告が発生します。

前述のコードが実行されると、List<String>として宣言された変数lsが、実際にはList<String>ではない値を参照しているため、ヒープ汚染が発生します。

型変数は修正されていないため、実行時に問題を識別できません。したがって、インスタンスの作成に使用された型引数に関して、実行時にインスタンスは情報を持ちません。

前述の単純な例では、コンパイル時に状況を特定し、エラーを出すことは簡単であると考えられます。 ただし、一般的(および一般的な)ケースでは、変数lの値は、個別にコンパイルされたメソッドの呼出しの結果であるか、その値が任意の制御フローに依存する場合があります。 したがって、上記のコードは非定型であり、実際には非常に悪いスタイルです。

さらに、Object[]がすべての配列型のスーパータイプであるという事実は、安全でないエイリアシングが発生し、ヒープが汚染される可能性があることを意味します。 たとえば、次のコードは静的に型修正されているため、コンパイルされます。


static void m(List<String>... stringLists) {
    Object[] array = stringLists;
    List<Integer> tmpList = Arrays.asList(42);
    array[0] = tmpList;                // (1)
    String s = stringLists[0].get(0);  // (2)
}

ヒープの汚染は、(1)で発生します。これは、List<String>を参照する必要があるstringLists配列内のコンポーネントがList<Integer>を参照するようになったためです。 ユニバーサル・スーパータイプ(Object[])と非更新可能型(仮パラメータの宣言型、List<String>[])の両方が存在する場合に、この汚染を検出する方法はありません。 チェックされていない警告は(1)に正当化されません。ただし、実行時に(2)にClassCastExceptionが発生します。

Javaプログラミング言語の静的型システムで、要素型List<String>が再可能でない配列(§15.12.4.2)を作成するために起動が考慮されるため、前述のメソッドの起動時にコンパイル時の未チェックの警告が表示されます。 メソッドの本体が可変引数パラメータに関して型安全であった場合のみ、プログラマはSafeVarargs注釈を使用して、呼出し時に警告をサイレントにできます(§9.6.4.7)。 前述のようにメソッドの本体によってヒープ汚染が発生するため、呼出し側の警告を無効にするために注釈を使用することは完全に不適切です。

最後に、stringLists配列は、Object[]以外の型の変数によって別名設定でき、ヒープ汚染が発生する可能性があります。 たとえば、array変数の型は、java.util.Collection[] - raw要素型- であり、前述のメソッドの本体は、警告やエラーなしでコンパイルされ、ヒープ汚染を引き起こします。 また、Java SEプラットフォームが定義されている場合(たとえば、SequenceList<T>の非汎用スーパータイプとして定義した場合、Sequencearrayのタイプとして使用すると、ヒープ汚染が発生します)。

変数は、パラメータ化された型を表すクラスのインスタンスであるオブジェクトを常に参照します。

前述の例のlsの値は、常にListの表現を提供するクラスのインスタンスです。

RAW型の式からパラメータ化された型の変数への割当ては、パラメータ化された型を使用しないレガシー・コードと、より最新のコードとを組み合せる場合にのみ使用してください。

コンパイル時の未チェックの警告を発行する必要のある操作が実行されず、かつ、変更不可能な要素型を持つ配列変数に安全でないエイリアスが発生しない場合、ヒープ汚染は発生しません。 これは、コンパイル時の未チェックの警告が実際に発生した場合にのみヒープの汚染が発生するという意味ではありません。 一部のバイナリが古いバージョンのJavaプログラミング言語用のコンパイラによって生成されたプログラム、または未チェックの警告を明示的に抑制したソースから生成されたプログラムを実行できます。 この習慣はよくても不健康だ。

逆に、コンパイル時に未チェックの警告が発生する可能性のあるコードを実行したにもかかわらず、ヒープ汚染は発生しない可能性があります。 実際、優れたプログラミング手法では、プログラマは、未チェックの警告にもかかわらず、コードが正しく、ヒープ汚染が発生しないことを満足させる必要があります。

4.12.3.  変数の種類

次の8種類の変数があります:

  1. クラス変数は、クラス宣言(§8.3.1.1)内でキーワードstaticを使用して宣言されたフィールド、またはインタフェース宣言(§9.3)内でキーワードstaticの有無にかかわらず宣言されたフィールドです。

    クラス変数は、そのクラスまたはインタフェースの準備(§12.3.2)時に作成され、デフォルト値(§4.12.5)に初期化されます。 クラス変数は、そのクラスまたはインタフェースがアンロードされると実質的に存在しなくなります(§12.7)。

  2. インスタンス変数は、キーワードstatic (§8.3.1.1)を使用せずにクラス宣言内で宣言されたフィールドです。

    クラスTにインスタンス変数であるフィールドaがある場合、新しいインスタンス変数aが作成され、デフォルト値(§4.12.5)に初期化されます。これは、クラスTの新しく作成された各オブジェクト、またはTのサブクラスであるクラスの一部です(§8.1.4)。 インスタンス変数は、オブジェクト(§12.6)の必要なファイナライズが完了した後、フィールドであるオブジェクトが参照されなくなると、事実上存在しなくなります。

  3. 配列コンポーネントは、配列である新しいオブジェクトが作成されるたびに、デフォルト値(§4.12.5)に作成および初期化される名前のない変数です(§10 (配列)§15.10.2)。 配列が参照されなくなると、配列コンポーネントは事実上存在しなくなります。

  4. Method parameters (§8.4.1)メソッドに渡されるname引数値。

    メソッド宣言で宣言されるすべてのパラメータについて、メソッドが呼び出されるたびに新しいパラメータ変数が作成されます(§15.12)。 新しい変数は、メソッドの起動時に対応する引数値を使用して初期化されます。 メソッド本文の実行が完了すると、メソッド・パラメータは事実上終了します。

  5. コンストラクタに渡されるコンストラクタ・パラメータ(§8.8.1)の名前引数値。

    コンストラクタ宣言で宣言されたすべてのパラメータについて、クラス・インスタンス作成式(§15.9)またはコンストラクタ呼出し(§8.8.7)がそのコンストラクタを呼び出すたびに、新しいパラメータ変数が作成されます。 新規変数は、作成式またはコンストラクタのコールからの対応する引数値で初期化されます。 コンストラクタの本文の実行が完了すると、コンストラクタ・パラメータは事実上終了します。

  6. ラムダパラメータ (§15.27.1)ラムダ式本体(§15.27.2)に渡されるname引数値。

    ラムダ式で宣言されたパラメータごとに、ラムダ本体で実装されたメソッドが呼び出されるたびに、新しいパラメータ変数が作成されます(§15.12)。 新しい変数は、メソッドの起動時に対応する引数値を使用して初期化されます。 ラムダ式本文の実行が完了すると、ラムダ・パラメータは効率的に存在しなくなります。

  7. 例外パラメータは、try文のcatch句によって例外が捕捉されるたびに作成されます(§14.20)。

    新しい変数は、例外に関連付けられた実際のオブジェクト(§11.3§14.18)で初期化されます。 catch句に関連付けられたブロックの実行が完了すると、例外パラメータは事実上存在しなくなります。

  8. ローカル変数(§14.4)は、文(§14.4.2§14.14.1§14.14.2§14.20.3)およびパターン(§14.30)によって宣言されます。 パターンで宣言されたローカル変数は、パターン変数と呼ばれます。

    文によって宣言されたローカル変数は、制御のフローがもっとも近い囲みブロック(§14.2)、for文またはtry-with-resources文に入ると作成されます。

    文の宣言子にイニシャライザがある場合は、文によって宣言されたローカル変数が文の実行の一部として初期化されます。 確定割当てのルール(§16 (Definite Assignment))は、文によって宣言されたローカル変数の値が初期化されるか、または値が割り当てられる前に使用されないようにします。

    パターンによって宣言されたローカル変数は、パターンが一致したときに作成および初期化されます(§14.30.2)。 スコープの規則(§6.3)は、パターンが一致しないかぎり、パターンによって宣言されたローカル変数の値が使用されないようにします。

    ローカル変数は、その宣言がスコープ内に存在しなくなったときに存在しなくなります。

    例外的な状況が1つでない場合、文によって宣言されたローカル変数は常に、文の実行時に作成されているとみなすことができます。 例外的な状況には、switch文(§14.11)が含まれます。この文では、制御でブロックを入力することはできますが、ローカル変数宣言文の実行はバイパスできます。 ただし、明確な代入のルールによって課される制限(§16 (Definite Assignment))のため、そのようなバイパスされたローカル変数宣言文によって宣言されたローカル変数は、代入式(§15.26)によって値が確実に代入される前に使用できません。

例4.12.3-1  様々な変数の種類

class Point {
    static int numPoints;   // numPoints is a class variable
    int x, y;               // x and y are instance variables
    int[] w = new int[10];  // w[0] is an array component
    int setX(int x) {       // x is a method parameter
        int oldx = this.x;  // oldx is a local variable
        this.x = x;
        return oldx;
    }
    boolean equalAtX(Object o) {
        if (o instanceof Point p)  // p is a pattern variable
            return this.x == p.x;
        else
            return false;
    }
}


4.12.4. final変数

変数はfinalとして宣言できます。 final変数は1回のみ割り当てることができます。 final変数が代入の直前に確実に割当て解除されないかぎり(§16 (Definite Assignment))に割り当てられている場合、コンパイル時にエラーが発生します。

final変数が割り当てられると、常に同じ値が含まれます。 final変数がオブジェクトへの参照を保持している場合、オブジェクトの状態はオブジェクトに対する操作によって変更される場合がありますが、変数は常に同じオブジェクトを参照します。 配列はオブジェクトであるため、これは配列にも当てはまります。final変数が配列への参照を保持している場合、配列のコンポーネントは配列に対する操作によって変更できますが、変数は常に同じ配列を参照します。

空白のfinalは、宣言にイニシャライザがないfinal変数です。

定数変数は、定数式(§15.29)で初期化されるプリミティブ型または型Stringfinal変数です。 変数が定数変数であるかどうかは、クラスの初期化(§12.4.1)、バイナリ互換性(§13.1)、到達可能性(§14.22)、および明確な代入(§16.1.1)に関して影響を与える可能性があります。

インタフェースのフィールド(§9.3)、try-with-resources文のリソースとして宣言されたローカル変数(§14.20.3)、およびmulti-catch句の例外パラメータ(§14.20)の3種類の変数が暗黙的にfinalとして宣言されます。 uni-catch句の例外パラメータは、暗黙的にfinalを宣言することはありませんが、事実上finalになる場合があります。

例4.12.4-1  final変数

変数finalの宣言は、その値が変更されず、プログラミング・エラーを回避するのに役立つ有用なドキュメントとして機能します。 このプログラムでは:

class Point {
    int x, y;
    int useCount;
    Point(int x, int y) { this.x = x; this.y = y; }
    static final Point origin = new Point(0, 0);
}

クラスPointは、finalクラス変数originを宣言します。 origin変数は、座標が(0、 0)であるクラスPointのインスタンスであるオブジェクトへの参照を保持します。 変数Point.originの値は変更できないため、常に同じPointオブジェクト(イニシャライザによって作成されたオブジェクト)を参照します。 ただし、このPointオブジェクトに対する操作は、その状態(たとえば、useCountを変更したり、誤解を招くx座標またはy座標)を変更したりすることがあります。


finalが宣言されていない特定の変数は、かわりに実質的にfinalとみなされます。

  • 文によって宣言され、宣言子にイニシャライザ(§14.4)があるローカル変数、またはパターンによって宣言されたローカル変数(§14.30.1)は、次のすべてに該当する場合、実質的にfinalになります。

    • finalは宣言されません。

    • これは、代入式(§15.26)で左側として出現することはありません。 (イニシャライザを含むローカル変数宣言子は、代入式ではありません。)

    • プリフィクスまたはポストフィクスの増分または減分演算子(§15.14§15.15)のオペランドとして出現することはありません。

  • 文によって宣言され、宣言子にイニシャライザがないローカル変数は、次のすべてに該当する場合、実質的にfinalです。

    • finalは宣言されません。

    • 代入式の左側として出現する場合、それは絶対に割当て解除され、代入の前に確実に割り当てられません。つまり、代入式の右側(§16 (Definite Assignment))の後に絶対に割り当てられず、確実に割り当てられません。

    • プレフィクスまたはポストフィクスの値の増減演算子のオペランドとしては使用されません。

  • メソッド、コンストラクタ、ラムダまたは例外パラメータ(§8.4.1§8.8.1§9.4§15.27.1§14.20)は、宣言子にイニシャライザがあるローカル変数として実質的にfinalであるかどうかを判断する目的で処理されます。

変数が実質的にfinalである場合、宣言にfinal修飾子を追加しても、コンパイル時のエラーは発生しません。 逆に、有効なプログラムでfinalと宣言されているローカル変数またはパラメータは、final修飾子を削除すると、実質的にfinalになります。

4.12.5.  変数の初期値

プログラムのすべての変数には、値を使用する前に値が必要です:

  • 各クラス変数、インスタンス変数または配列コンポーネントは、作成時にデフォルト値で初期化されます(§15.9§15.10.2)。

    • byte型の場合、デフォルト値はゼロ、つまり(byte)0の値です。

    • short型の場合、デフォルト値はゼロ、つまり(short)0の値です。

    • int型の場合、デフォルト値はゼロ(0)です。

    • long型の場合、デフォルト値はゼロ(0L)です。

    • float型の場合、デフォルト値は正のゼロ、つまり0.0fです。

    • double型の場合、デフォルト値は正のゼロ、つまり0.0dです。

    • タイプがcharの場合、デフォルト値はNULL文字、つまり'\\u0000'です。

    • タイプがbooleanの場合、デフォルト値はfalseです。

    • すべての参照型(§4.3)のデフォルト値はnullです。

  • 各メソッド・パラメータ(§8.4.1)は、メソッドの呼出し元(§15.12)によって提供される対応する引数値に初期化されます。

  • 各コンストラクタ・パラメータ(§8.8.1)は、クラス・インスタンス作成式(§15.9)またはコンストラクタ呼出し(§8.8.7)によって提供される対応する引数値に初期化されます。

  • 例外パラメータ(§14.20)は、例外を表すスローされたオブジェクト(§11.3§14.18)に初期化されます。

  • 文(§14.4.2§14.14.1§14.14.2§14.20.3)によって宣言されたローカル変数は、初期化(§14.4)または代入(§15.26)のいずれかによって、明確な代入(§16 (無期限代入))のルールを使用して検証できる方法で、明示的に値を与えられる必要があります。

    パターン(§14.30.1)で宣言された局所変数は、パターンマッチング(§14.30.2)のプロセスによって暗黙的に初期化される。

例4.12.5-1.  変数の初期値

class Point {
    static int npoints;
    int x, y;
    Point root;
}

class Test {
    public static void main(String[] args) {
        System.out.println("npoints=" + Point.npoints);
        Point p = new Point();
        System.out.println("p.x=" + p.x + ", p.y=" + p.y);
        System.out.println("p.root=" + p.root);
    }
}

このプログラムは次の内容を出力します:

npoints=0
p.x=0, p.y=0
p.root=null

クラスPointの準備時に発生するnpointsのデフォルトの初期化(§12.3.2)、および新しいPointがインスタンス化されたときに発生するxyおよびrootのデフォルトの初期化を示しています。 クラスおよびインタフェースのロード、リンクおよび初期化のすべての側面の完全な説明と、新しいクラス・インスタンスを作成するクラスのインスタンス化の説明は、§12 (Execution)を参照してください。


4.12.6. タイプ、クラスおよびインタフェース

Javaプログラミング言語では、すべての変数とすべての式に、コンパイル時に決定できる型があります。 型はプリミティブ型または参照型です。 参照タイプには、クラス・タイプとインタフェース・タイプがあります。 参照型は、クラス宣言(§8.1)およびインタフェース宣言(§9.1)を含む型宣言によって導入されます。 typeという用語は、クラスまたはインタフェースを指す場合によく使用されます。

Java Virtual Machineでは、すべてのオブジェクトが特定のクラスに属しています。つまり、オブジェクトを生成した作成式(§15.9)で言及されたクラス、またはClassオブジェクトを使用してオブジェクトを生成するリフレクティブ・メソッドを呼び出すクラス、文字列連結演算子+ (§15.18.1)によって暗黙的に作成されたオブジェクトのStringクラスです。 このクラスは、オブジェクトのクラスと呼ばれます。 オブジェクトは、そのクラスのインスタンスであり、そのクラスのすべてのスーパークラスであるとみなされます。

すべての配列にもクラスがあります。 メソッドgetClassは、配列オブジェクトに対して呼び出されると、配列のクラス(§10.8)を表すクラス・オブジェクト(クラスClass)を返します。

変数のコンパイル時型は常に宣言され、式のコンパイル時型はコンパイル時に推定できます。 コンパイル時型は、実行時に変数が保持できる値、または実行時に式が生成できる値を制限します。 ランタイム値がnull以外の参照である場合、そのランタイム値はクラスを持つオブジェクトまたは配列を参照し、そのクラスはコンパイル時型と互換性がある必要があります。

変数または式にインタフェース型であるコンパイル時型がある場合でも、インタフェースのインスタンスはありません。 型がインタフェース型である変数または式は、そのインタフェースを実装するクラス(§8.1.5)を持つ任意のオブジェクトを参照できます。

変数または式が「実行時型」であると言われる場合があります。 これは、値がnullでないと仮定して、実行時に変数または式の値によって参照されるオブジェクトのクラスを参照します。

コンパイル時型とランタイム型との対応は、次の2つの理由で不完全です。

  1. 実行時に、クラスおよびインタフェースは、クラス・ローダーを使用してJava Virtual Machineによってロードされます。 各クラス・ローダーは、独自のクラスおよびインタフェースのセットを定義します。 その結果、2つのローダーで同じクラスまたはインタフェース定義をロードできますが、実行時に異なるクラスまたはインタフェースを生成できます。 したがって、正しくコンパイルされたコードは、ロードするクラス・ローダーに一貫性がないと、リンク時に失敗する可能性があります。

    詳細は、ACM SIGPLAN Notices、Volume 33、Number 10、1998年10月、36-44ページ、および The Java Virtual Machine Specification、 Java SE 26 Editionとして公開されている Proceedings of OOPSLA '98のSheng LiangおよびGilad Brachaによる Dynamic Class Loading in the Java Virtual Machineを参照してください。

  2. 型変数(§4.4)と型引数(§4.5.1)は、実行時に改定されません。 その結果、実行時に同じクラスまたはインタフェースが、コンパイル時から複数のパラメータ化された型(§4.5)を表します。 具体的には、特定の汎用型(§8.1.2§9.1.2)のすべてのコンパイル時パラメータ化は、単一のランタイム表現を共有します。

    特定の条件下では、パラメータ化された型の変数が、そのパラメータ化された型でないオブジェクトを参照している可能性があります。 この状況はヒープ汚染(§4.12.2)と呼ばれています。 変数は、パラメータ化された型を表すクラスのインスタンスであるオブジェクトを常に参照します。

例4.12.6-1 変数のタイプとオブジェクトのクラスのタイプ

interface Colorable {
    void setColor(byte r, byte g, byte b);
}

class Point { int x, y; }

class ColoredPoint extends Point implements Colorable {
    byte r, g, b;
    public void setColor(byte rv, byte gv, byte bv) {
        r = rv; g = gv; b = bv;
    }
}

class Test {
    public static void main(String[] args) {
        Point p = new Point();
        ColoredPoint cp = new ColoredPoint();
        p = cp;
        Colorable c = cp;
    }
}

この例では、次のようになります。

  • クラスTestのメソッドmainのローカル変数pは、Point型を持ち、最初はクラスPointの新しいインスタンスへの参照が割り当てられます。

  • ローカル変数cpも同様にColoredPoint型であり、最初はクラスColoredPointの新しいインスタンスへの参照が割り当てられます。

  • cpの値を変数pに割り当てると、pColoredPointオブジェクトへの参照を保持します。 これは、ColoredPointPointのサブクラスであるため許可されるため、クラスColoredPointは、Point型の代入互換(§5.2)です。 ColoredPointオブジェクトには、Pointのすべてのメソッドのサポートが含まれます。 特定のフィールドrgおよびbに加えて、クラスPointのフィールドxおよびyがあります。

  • ローカル変数cの型はインタフェース型Colorableであるため、Colorableを実装するクラスを持つ任意のオブジェクトへの参照を保持できます。具体的には、ColoredPointへの参照を保持できます。

new Colorable()などの式は、クラスのみでインタフェースのインスタンスを作成できないため、有効ではありません。 ただし、式new Colorable() { public void setColor... }は、Colorableインタフェースを実装する匿名クラス(§15.9.5)を宣言しているため有効です。