akios @ ウィキ

4.12.2. 参照型の変数

最終更新:

akios

- view
管理者のみ編集可

4. 型と値と変数

4.1. 型と変数の種類

4.2. プリミティブ型と値

4.3. 参照型と値

4.4. 型変数

4.5. 引数付き型

4.6. 型の抹消

4.7. 具象可能型

4.8. 未加工型

4.9. 交差型

4.10. サブタイプ化

4.11. 型の使用箇所

4.12. 変数

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

4.12.2. 参照型の変数

クラス型Tの変数はヌル参照またはクラスTかTの任意のサブクラスのインスタンスへの参照を保持することができます。

インタフェース型の変数はヌル参照またはそのインタフェースを実装する任意のクラスのインスタンスへの参照を保持することができます。

変数は常にその宣言された型のサブタイプを参照できるとは保障されていません。保障されているのはその宣言された型のサブクラスやサブインタフェースのみです。これは以下に記述するヒープ汚染の可能性があるためです。

Tをプリミティブ型とすると、"Tの配列"型の変数はヌル参照または"Tの配列"型の任意の配列への参照を保持することができます。

Tを参照型とすると、"Tの配列"型の変数はヌル参照または"Sの配列"型の任意の配列への参照を保持することができます。ここでSは型Tのサブクラスまたはサブインタフェースです。

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

Object型の変数はヌル参照または、クラスや配列のインスタンスである任意のオブジェクトへの参照を保持することができます。

引数付き型の変数は引数付き型のものではないオブジェクトを参照することができます。この状況はヒープ汚染として知られています。

ヒープ汚染(heap pollution)とはプログラムがコンパイル時に未検査警告(4.8.5.1.9.5.5.2.8.4.1.8.4.8.3.8.4.8.4.9.4.1.2.15.12.4.2.)が発生する未加工型に対する演算を行った場合に、もしくは、プログラムが未加工または非ジェネリックなスーパータイプの配列変数を通して非具象可能要素型の配列変数に別名をつける場合にのみ起こりえます。

例えば、以下のコード:

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)の時点でstringLists配列はList<string>List<String>を参照するべきなのにList<Integer>を参照したためヒープ汚染が起きます。万能的なスーパータイプ(Object[])と非具象可能型(仮引数の宣言された型、List<string>[])の両方が存在するため、この汚染を検出する方法はありません。(1)の時点での未検査警告を無視すると、実行時には(2)の時点でClassCastExceptionがスローされます。

Javaプログラミング言語の静的型システムが要素の型、List<String>、が非具象可能である配列を作成する呼び出しと判断するため、コンパイル時に未検査警告を上記のようなメソッド呼び出し毎に出力します。メソッドの本文が変数の項数引数の面から型安全である場合のみ、プログラマーはSafeVarargs注釈を呼び出しにおける警告を止めるために使用することができます。メソッドの本文が上記のように書かれていればヒープ汚染は発生するため、呼び出しに対する警告を止める注釈は全くの不適切です。

最後に、stringLists配列はObject[]以外の型の変数を通して名前が変更されているので、ヒープ汚染はまだ起きます。例えば、配列変数の型がjava.util.Collection[]、未加工要素型、であれば上記のメソッドの本文では警告もエラーも出力されませんが依然としてヒープ汚染は起きます。そしてJava SEプラットフォームでSequenceList<T>の非ジェネリックスーパータイプとして定義されていて、配列の型としてSequenceを使用したとしてもヒープ汚染となります。

変数は引数付き型を表すクラスのインスタンスであるオブジェクトを常に参照します。

上記例のlsの値は常にListで表されるクラスのインスタンスです。

未加工型の式から引数付き型の変数への代入は引数付き型を使用するコードと引数付き型を使用しないレガシーコードとの接合が必要な際にのみ使用すべきです。

コンパイル時に未検査警告が発生するような演算や非具象可能要素型の配列変数の名前変更のような安全ではない演算がない場合、ヒープ汚染は起きません。実際に非検査警告が発生する場合にだけヒープ汚染が起きるわけではありません。Javaプログラミング言語の古いバージョン用のコンパイラーによって生成されたバイナリ―や未検査警告を明示的に抑制したソースを実行する際に起きます。この実行は良くても健康なものではありません。

反対に、コンパイル時の未検査警告が起きる(起きた)ようなコードを実行したとしても、ヒープ汚染は起きないこともあります。実際、良いプログラミングの実践としてプログラマーは、未検査警告が起きたとしても、コードが正しくヒープ汚染が起きないようにします。

4.12.3. 変数の種類

4.12.4. final変数

4.12.5. 変数の初期値

4.12.6. 型とクラスとインタフェース

目安箱バナー