web-dev-qa-db-ja.com

ジェネリック配列宣言の@SuppressWarnings

コーディングテストの実行中に、Javaでジェネリック型の配列を初期化する必要があるという問題に遭遇しました。それを行う方法を理解しようとしているときに、私は this スタックオーバーフローの質問を調べました。それは、良い方法ではないように思われる警告を抑制しなければならず、他のタイプセーフティの問題があるかもしれません。明確にするために、ここにコードがあります:

_public class GenSet<E> {
  private E[] a;

  public GenSet(Class<E> c, int s) {
    // Use Array native method to create array
    // of a type only known at run time
    @SuppressWarnings("unchecked")
    final E[] a = (E[]) Array.newInstance(c, s);
    this.a = a;
  }

  E get(int i) {
    return a[i];
  }
}
_

これは私が問題について間違っているということですか?この場合、@SuppressWarnings("unchecked")を使用しても問題ありませんか?

ArrayListを使用するだけですか?

6
ford prefect

@SuppressWarnings("unchecked")は、適切に記述されたコードでは正しい場合があり、避けられない場合があります。ジェネリック型の配列の作成は1つのケースです。ジェネリックのCollection<T>またはList<T>を使用してそれを回避できる場合は、私はそれをお勧めしますが、配列のみが機能する場合は、何も実行できません。

ジェネリック型へのキャストも同様のケースです。ジェネリックキャストは実行時に検証できないため、次のようにすると警告が表示されます

List<Integer> numbers = (List<Integer>) object;

だからといって、一般的なキャストを使用するのは悪いスタイルではありません。それは単に、ジェネリックがJavaで設計された方法の制限を反映しています。

私が意図的に警告付きのコードを書いていることを認めるために、そのような行にはコメントを追加することをルールにしました。これは選択を際立たせます。また、@SuppressWarningsは常にターゲット行で直接使用できるとは限らないため、これらの行を簡単に見つけることができます。それがメソッドの一番上にある場合、どの行が警告を生成するかが明確でない場合があります。

((List<Integer>) object).add(1);   // warning: [unchecked] unchecked cast

また、明確にするために、notをrawタイプ(List<T>ではなくList)で回避する必要があります。それは悪い考えです。また、-Xlintを有効にすると警告が表示されるため、状況が改善されることはありません。

8
John Kugelman

代わりにArrayListを使用するかどうかを尋ねているという事実に基づいて、配列の使用は必須ではないと思います。私はArrayListまたは他のコレクションを使用し、それを参照するために実装ではなくインターフェイスに依存します:

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(4);

Javaジェネリックは厳しい状況にあります。一方、Javaには、下位互換性を確保するという自主的な(ただし良い)目標があります。ジェネリックは基本的に1.5より前のバイトコードと互換性がないため、これにより type erasure 。基本的に、ジェネリック型はコンパイル時に削除され、ジェネリックメソッドの呼び出しとアクセスは代わりに型キャストで強化されます。一方、この制限によりジェネリック参照が作成されますreallyあなたmust未チェックのタイプとrawタイプには@SuppressWarningsを使用することがあります。

これが意味することは、コレクションのユーザーをストレージから隔離するためにメソッド呼び出しに依存するコレクションはうまく機能するということですが、- 配列は具体化されます つまり、言語ごとに配列タイプmust =実行時に利用可能。これは、前述したように、ジェネリック型が消去されたコレクションに反します。例:

String[] array1 = new String[1];
Object[] array2 = array1;

参照array2Object[]として宣言されているにもかかわらず、JVMはターゲットとなる配列が文字列のみを保持できることを認識しています。これらの型はreifableであるため、基本的にジェネリックと互換性がありません。

StackOverflowは、Javaジェネリックと配列)のトピックについて多くの質問があります。

5
user22815

この場合、チェックされていないキャスト警告を抑制しても問題ありません。

Eは実行時に不明であるため、チェックされていないキャストです。したがって、ランタイムチェックはObject[]E[]の消去)までのキャストのみをチェックできますが、実際にはE[]自体まではチェックできません。 (たとえば、EIntegerの場合、オブジェクトの実際の実行時クラスがString[]であると、Integer[]でなくても、チェックでキャッチされません。)

Array.newInstance()E[]ではなくObjectを返します(EClassパラメータの型引数です)。これは、両方を作成するために使用できるためです。プリミティブの配列と参照の配列。 Eのような型変数はプリミティブ型を表すことができず、プリミティブ型配列のスーパータイプはObjectのみです。プリミティブ型を表すClassオブジェクトは、型パラメーターとしてラッパークラスを使用してClassにパラメーター化されます。 int.classのタイプはClass<Integer>です。ただし、int.classArray.newInstance()に渡すと、int[]ではなくE[]が作成されます(Integer[]になります)。ただし、参照タイプを表すClass<E>を渡すと、Array.newInstance()E[]を返します。

したがって、基本的には、Class<E>を指定してArray.newInstance()を呼び出すと、常にE[]またはプリミティブの配列が返されます。プリミティブ配列の型はObject[]のサブタイプではないため、Object[]の実行時チェックに失敗します。つまり、結果がObject[]の場合、E[]であることが保証されます。したがって、このキャストは実行時にObject[]までしかチェックせず、Object[]からE[]までの部分はチェックしませんが、この場合、Object[]までのチェックは、それがE[]であることを保証するのに十分であるため、チェックされていませんこの場合、パーツは問題ではなく、事実上完全にチェックされたキャストです。


ちなみに、ここで示したコードからは、GenSetを初期化したり、Array.newInstance()を使用したりするためにクラスオブジェクトを渡す必要はありません。これが必要になるのは、実行時にクラスが実際にクラスEを使用した場合のみです。しかし、そうではありません。それが行うすべては、(クラスの外部に公開されない)配列を作成し、そこから要素を取得することです。これは、Object[]を使用して実現できます。要素を取り出すときは、Eにキャストする必要があります(チェックされていないため、Esを要素に入れるだけで安全であることがわかります)。

public class GenSet<E> {
  private Object[] a;

  public GenSet(int s) {
    this.a = new Object[s];
  }

  @SuppressWarnings("unchecked")
  E get(int i) {
    return (E)a[i];
  }
}
0
user102008