非具象化可能仮パラメータを可変長引数メソッドに使用する場合のコンパイラの警告とエラーの改善

このページでは、次のトピックについて説明します。

ヒープ汚染

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によって参照されるListlsに代入されると、コンパイラは非チェック警告を生成します。コンパイラは、lList<String>型を参照するかどうか(実際には参照しない)をコンパイル時に判断できません。また、JVMが実行時にそれを判断できないこともわかっています。その結果、ヒープ汚染が発生します。

結果として、コンパイル時に、別の非チェック警告がadd文で生成されます。コンパイラは、変数lList<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を見れば、このようなメソッドについてコンパイラが警告する理由がわかります。このメソッドの最初の文は、可変長引数仮パラメータlObject配列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や類似の例外がスローされることがないとわかっていれば、次のオプションのいずれかを使用して、このような可変長引数メソッドに対してコンパイラから生成される警告を抑制できます。

たとえば、次に示すバージョンの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コンパイラはこの例に対して次の警告を生成します。

: Java SE 5および6では、非具象化可能可変長引数仮パラメータを持つ可変長引数メソッドの呼出しによってヒープ汚染が発生するかどうかは、その呼出しを行うプログラマが判断する必要があります。しかし、このプログラマがそのメソッドの作成者でない場合は、これを簡単に判断することはできません。Java SE 7では、このような可変長引数メソッドで可変長引数仮パラメータが適切に処理されること、およびヒープ汚染が発生しないことを保証するのは、メソッドを作成するプログラマの責任です。


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.