このページでは、次のトピックについて説明します。
ArrayList<Number>
やList<String>
など、パラメータ化された型のほとんどは、非具象化可能型です。非具象化可能型は、実行時に完全には使用可能にならない型です。非具象化可能型は、コンパイル時に型消去と呼ばれる処理を受けます。この処理では、型パラメータと型引数に関する情報がコンパイラによって削除されます。これにより、ジェネリクスより前に作成されたJavaライブラリおよびアプリケーションとのバイナリ互換性が保証されます。パラメータ化された型は、その情報が型消去によってコンパイル時に削除されるため、非具象化可能です。
ヒープ汚染は、パラメータ化された型の変数が、そのパラメータ化された型ではないオブジェクトを参照するときに発生します。この状態が発生する可能性があるのは、コンパイル時に非チェック警告を発生させるような操作がプログラムで実行された場合だけです。非チェック警告は、コンパイル時(コンパイル時の型チェック規則の範囲内で)または実行時に、パラメータ化された型を使用する操作(キャストやメソッド呼び出しなど)が正しいかどうかを確認できない場合に生成されます。
次に例を示します。
List l = new ArrayList<Number>(); List<String> ls = l; // unchecked warning l.add(0, new Integer(42)); // another unchecked warning String s = ls.get(0); // ClassCastException is thrown
型の消去中に、型ArrayList<Number>
およびList<String>
はそれぞれArrayList
およびList
になります。
変数ls
はパラメータ化された型List<String>
を持ちます。l
によって参照されるList
がls
に代入されると、コンパイラは非チェック警告を生成します。コンパイラは、l
がList<String>
型を参照するかどうか(実際には参照しない)をコンパイル時に判断できません。また、JVMが実行時にそれを判断できないこともわかっています。その結果、ヒープ汚染が発生します。
結果として、コンパイル時に、別の非チェック警告がadd
文で生成されます。コンパイラは、変数l
がList<String>
型またはList<Integer>
型を参照するかどうかを判断できません(そして別のヒープ汚染状態が発生します)。ただし、コンパイラはget
文では警告やエラーを生成しません。これはList<String>.get
メソッドを呼び出してString
オブジェクトを取得している、有効な文です。その代わり、このget
文は実行時にClassCastException
をスローします。
詳しく説明すると、ヒープ汚染状態が発生するのは、List
オブジェクトl
(そのstatic型はList<Number>
)が別のList
オブジェクトls
(異なるstatic型List<String>
を持つ)に代入される場合です。しかし、コンパイラはこの代入をいまだに許可しています。この代入を許可する必要があるのは、ジェネリックスをサポートしないJava SEのバージョンとの下位互換性を確保するためです。型消去のために、List<Number>
とList<String>
はList
になります。その結果、コンパイラはオブジェクトl
(List
というraw型を持つ)をオブジェクトls
に代入することを許可します。
さらに、l.add
メソッドが呼び出されたときにヒープ汚染状態が発生します。add
メソッドの2番目のstatic型仮パラメータはString
ですが、このメソッドは異なる型(Integer
)の実パラメータで呼び出されています。しかし、コンパイラはこのメソッド呼出しをいまだに許可しています。型消去のために、add
メソッドの2番目の仮パラメータの型(List<E>.add(int,E)
として定義されている)はObject
になります。その結果、型消去の後でl.add
メソッドはObject
型の任意のオブジェクトを追加でき、Object
のサブ型であるInteger
型のオブジェクトもこれに含まれます。そのため、コンパイラはこのメソッド呼出しを許可します。
次の例のメソッドArrayBuilder.addToList
について考えます。これは、elements
という可変長引数仮パラメータに格納されたT
型のオブジェクトをList
listArg
に追加する、可変長引数(varargs)メソッドです。
import java.util.*; public class ArrayBuilder { public static <T> void addToList (List<T> listArg, T... elements) { for (T x : elements) { listArg.add(x); } } public static void faultyMethod(List<String>... l) { Object[] objectArray = l; // Valid objectArray[0] = Arrays.asList(new Integer(42)); String s = l[0].get(0); // ClassCastException thrown here } }
import java.util.*; public class HeapPollutionExample { public static void main(String[] args) { List<String> stringListA = new ArrayList<String>(); List<String> stringListB = new ArrayList<String>(); ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine"); ArrayBuilder.addToList(stringListA, "Ten", "Eleven", "Twelve"); List<List<String>> listOfStringLists = new ArrayList<List<String>>(); ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB); ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!")); } }
Java SE 7のコンパイラは、メソッドArrayBuilder.addToList
の定義に関する次の警告を生成します。
warning: [varargs] Possible heap pollution from parameterized vararg type T
コンパイラが可変引数メソッド検出すると、可変引数仮パラメータを配列に変換します。しかし、Javaプログラミング言語では、パラメータ化された型の配列の作成は許可されません。メソッドArrayBuilder.addToList
では、コンパイラはvarargsの仮パラメータT... elements
を仮パラメータT[] elements
に(配列)変換します。しかし、型消去のために、コンパイラはvarargsの仮パラメータをObject[] elements
に変換します。その結果、ヒープ汚染が発生する可能性があります。詳細については、次のセクション「非具象化可能仮パラメータを持つ可変長引数メソッドの潜在的な脆弱性」を参照してください。
注: Java SE 5および6のコンパイラは、ArrayBuilder.addToList
が呼び出されたときにこの警告を生成します。この例では、警告はクラスHeapPollutionExample
に対して生成されます。これらのコンパイラは、宣言箇所ではこの警告を生成しません。ただし、Java SE 7は、宣言箇所と呼出し箇所の両方でこの警告を生成します(警告が注釈によって阻止されている場合は除く。詳細は、「非具象化可能仮パラメータを持つ可変長引数メソッドからの警告の抑制」を参照)。非具象化可能可変長引数仮パラメータを持つ可変長引数メソッドが(呼出し箇所ではなく)宣言箇所で見つかったときにコンパイラで警告を生成する利点は、呼出し箇所は多数存在する可能性があるのに対し、宣言箇所は1つしかないことです。
メソッドArrayBuilder.faultyMethod
を見れば、このようなメソッドについてコンパイラが警告する理由がわかります。このメソッドの最初の文は、可変長引数仮パラメータl
をObject
配列objectArgs
に代入しています。
Object[] objectArray = l;
この文はヒープ汚染を招く可能性があります。可変長引数仮パラメータl
のパラメータ化された型に一致する値は、変数objectArray
に代入でき、したがってl
に代入できます。ただし、コンパイラはこの文では非チェック警告を生成しません。コンパイラは、可変長引数仮パラメータList<String>... l
を仮パラメータList[] l
に変換したときに、すでに警告を生成しています。変数l
の型はList[]
で、これはObject[]
のサブ型なので、この文は有効です。
その結果、次の文のように、任意の型のList
オブジェクトをobjectArray
配列のどの配列要素に代入しても、コンパイラは警告やエラーを生成しません。
objectArray[0] = Arrays.asList(new Integer(42));
この文は、Integer
型のオブジェクトを1つ含むList
オブジェクトを、objectArray
配列の最初の配列要素に代入しています。
ArrayBuilder.makeArray
メソッドを次の文で呼び出すとします。
ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
実行時に、JVMは次の文でClassCastException
をスローします。
String s = l[0].get(0); // ClassCastException thrown here
変数l
の最初の配列要素に格納されているオブジェクトの型はList<Integer>
ですが、この文はList<String>
型のオブジェクトを想定しています。
パラメータ化されたパラメータを持つ可変長引数メソッドを宣言する場合、メソッドの本体で可変長引数仮パラメータの処理が適切でない(ArrayBuilder.faultyMethod
メソッドを参照)ためにClassCastException
や類似の例外がスローされることがないとわかっていれば、次のオプションのいずれかを使用して、このような可変長引数メソッドに対してコンパイラから生成される警告を抑制できます。
コンストラクタではないstaticメソッドの宣言に、次の注釈を追加します。
@SafeVarargs
@SuppressWarnings
注釈とは異なり、@SafeVarargs
注釈はメソッドの規約のドキュメント化される部分です。この注釈は、メソッドの実装が可変長引数仮パラメータを不適切に処理しないことを表明します。
メソッドの宣言に、次の注釈を追加します。
@SuppressWarnings({"unchecked", "varargs"})
@SafeVarargs
注釈とは異なり、@SuppressWarnings("varargs")
はメソッドの呼出し箇所から生成される警告を抑制しません。
コンパイラ・オプション-Xlint:varargs
を使用します。
たとえば、次に示すバージョンのArrayBuilder
クラスには、addToList2
およびaddToList3
という2つの追加メソッドがあります。
public class ArrayBuilder { public static <T> void addToList (List<T> listArg, T... elements) { for (T x : elements) { listArg.add(x); } } @SuppressWarnings({"unchecked", "varargs"}) public static <T> void addToList2 (List<T> listArg, T... elements) { for (T x : elements) { listArg.add(x); } } @SafeVarargs public static <T> void addToList3 (List<T> listArg, T... elements) { for (T x : elements) { listArg.add(x); } } // ... }
public class HeapPollutionExample { // ... public static void main(String[] args) { // ... ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB); ArrayBuilder.addToList2(listOfStringLists, stringListA, stringListB); ArrayBuilder.addToList3(listOfStringLists, stringListA, stringListB); // ... } }
Javaコンパイラはこの例に対して次の警告を生成します。
addToList
:
[unchecked] Possible heap pollution from parameterized vararg type T
[unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
addToList2
: メソッドが呼び出されたとき(メソッドの宣言では警告は生成されません): [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
addToList3
: メソッドの宣言および呼出しのどちらでも、警告は生成されません。注: Java SE 5および6では、非具象化可能可変長引数仮パラメータを持つ可変長引数メソッドの呼出しによってヒープ汚染が発生するかどうかは、その呼出しを行うプログラマが判断する必要があります。しかし、このプログラマがそのメソッドの作成者でない場合は、これを簡単に判断することはできません。Java SE 7では、このような可変長引数メソッドで可変長引数仮パラメータが適切に処理されること、およびヒープ汚染が発生しないことを保証するのは、メソッドを作成するプログラマの責任です。