web-dev-qa-db-ja.com

Java ArrayList.toArray()のジェネリック

次のように定義されたarraylistがあるとします。

_ArrayList<String> someData = new ArrayList<>();
_

後のコードでは、ジェネリックのため、これを言うことができます:

_String someLine = someData.get(0);
_

そして、コンパイラーは文字列を取得することを完全に知っています。イェイジェネリック!ただし、これは失敗します。

_String[] arrayOfData = someData.toArray();
_

toArray()は、定義されたジェネリックではなく、常にオブジェクトの配列を返します。なぜget(x)メソッドは何を返しているのかを知っているのに、toArray()はデフォルトでObjectsになっていますか?

57
rabbit guy

ArrayList <E> クラスのtoArray(T[] a)の実装を見ると、次のようになります。

_public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}
_

このメソッドの問題は、同じジェネリック型の配列を渡す必要があることです。このメソッドが引数を取らない場合、実装は次のようになります。

_public <T> T[] toArray() {
    T[] t = new T[size]; // compilation error
    return Arrays.copyOf(elementData, size, t.getClass());
}
_

しかし、ここでの問題は、コンパイラーがTが何を表しているかを正確に知らないため、Javaで汎用配列を作成できないことです。言い換えれば、非再現可能型の配列の作成JLS§4.7はJavaでは使用できません

Array Store ExceptionJLS§10.5 )からの別の重要な引用:

配列のコンポーネントタイプを再定義できない場合(§4.7)、Java前の段落で説明した仮想マシンはストアチェックを実行できませんでした。これが配列作成の理由です。非特定化可能な要素タイプの式は禁止されています(§15.10.1)。

Javaがオーバーロードされたバージョン toArray(T[] a) を提供したのはこのためです。

ToArray()メソッドをオーバーライドして、Eの配列を返すことを通知します。

したがって、toArray()をオーバーライドする代わりに、toArray(T[] a)を使用する必要があります。

Java Docから興味深いタイプのパラメータのインスタンスを作成できません ).

57
sud29

汎用情報は、実行時に 消去 です。 JVMは、リストが_List<String>_か_List<Integer>_(実行時にList<T>_のTObjectとして解決されるか)を知らないため、唯一の可能な配列タイプは_Object[]_です。

ただし、toArray(T[] array)を使用できます。この場合、JVMは指定された配列のクラスを使用でき、ArrayList実装で確認できます。

_public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
_
20
AdamSkywalker

ListインターフェイスのJavadoc を見ると、toArrayの2番目の形式:<T> T[] toArray(T[] a)に気付くでしょう。

実際、Javadocには、あなたがやりたいことを正確に行う方法の例さえ示されています。

String[] y = x.toArray(new String[0]);

15
ach

注意すべき適切なことは、Javaの配列は実行時にコンポーネントタイプを知っていることです。String[]Integer[]は実行時に異なるクラスであり、実行時にコンポーネントタイプの配列を要求できます。配列を作成するには、実行時にコンポーネントタイプが必要です(コンパイル時にnew String[...]を使用して再構成可能なコンポーネントタイプをハードコーディングするか、Array.newInstance()を使用してクラスオブジェクトを渡す)。

一方、ジェネリックの型引数は実行時には存在しません。 ArrayList<String>ArrayList<Integer>の間には、実行時にまったく違いはありません。すべてArrayListです。

これが、何らかの方法でコンポーネントタイプを個別に渡さずにList<String>を取得してString[]を取得できない基本的な理由です。コンポーネントタイプ情報を持たないものからコンポーネントタイプ情報を取得する必要があります。明らかに、これは不可能です。

5
newacct

配列を作成する代わりにイテレータを使用することもできますが、これは常に奇妙に思えます。なぜget(x)メソッドはそれが何を返しているのか知っているのに、toArray()はデフォルトでObjectsになっていますか?それを設計する半分の方法のように、彼らはこれはここでは必要ないと判断しましたか?

質問の目的は、ジェネリックでtoArray()を使用することだけではなく、ArrayListクラスのメソッドの設計を理解することでもあるように思えるので、追加したいと思います。

ArrayListは次のように宣言されている汎用クラスです

_public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, Java.io.Serializable
_

これにより、クラス内でpublic E get(int index)などの汎用メソッドを使用できるようになります。

ただし、toArray()などのメソッドがEを返さず、むしろ_E[]_を返す場合、少し注意が必要になります。汎用配列を作成することはできないため、public <E> E[] toArray()などの署名を提供することはできません。

配列の作成は実行時に行われ、 Type erasure 、Java RuntimeにはEで表される型の特定の情報はありません。現在、必要なタイプをパラメーターとしてメソッドに渡すことです。したがって、クライアントが必要なタイプを渡すように強制される署名public <T> T[] toArray(T[] a)です。

しかし、一方で、それはpublic E get(int index)に対しても機能します。メソッドの実装を見ると、メソッドは同じObjectの配列を使用して、指定された位置にある要素を返すからです。インデックス、Eにキャストされます

_E elementData(int index) {
    return (E) elementData[index];
}
_

コンパイル時にEObjectに置き換えるのはJavaコンパイラーです

5
senseiwu

配列は、配列の型とは異なる型です。 Stringクラスではなく、StringArrayクラスのようなものです。

可能であれば、汎用メソッドtoArray()は次のようになります

_private <T> T[] toArray() {
    T[] result = new T[length];
    //populate
    return result;
}
_

コンパイル中に、タイプTが消去されます。パート_new T[length]_をどのように置き換える必要がありますか?ジェネリック型情報は利用できません。

(たとえば)ArrayListのソースコードを見ると、同じことがわかります。 toArray(T[] a)メソッドは、指定された配列を満たす(サイズが一致する場合)か、パラメーターのtypeを使用して新しい新しい配列を作成します。これは、ジェネリック型の配列型です。 T.

1
Gerald Mücke

最初に理解しなければならないことは、ArrayList ownが​​Objectの単なる配列であることです

   transient Object[] elementData;

T[]が失敗する理由になると、それはClass<T>なしでジェネリック型の配列を取得できないためです。これは、Javaの型erase( 詳細な説明があります および 作成方法 )。そして、ヒープ上のarray[]はその型を動的に認識しており、int[]String[]にキャストできません。同じ理由で、Object[]T[]にキャストできません。

   int[] ints = new int[3];
   String[] strings = (String[]) ints;//Java: incompatible types: int[] cannot be converted to Java.lang.String[]

   public <T> T[] a() {
      Object[] objects = new Object[3];
      return (T[])objects;
   }
   //ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
   Integer[] a = new LearnArray().<Integer>a();

しかし、あなたがarrayに入れるのは、型がE(コンパイラによってチェックされる)であるオブジェクトなので、安全で正しいEにキャストできます。

  return (E) elementData[index];

要するに、キャストでは持っていないものを取得することはできません。 Object[]しかないので、toArray()Object[]を返すだけです(そうでなければ、このタイプの新しい配列を作成するにはClass<T>を与える必要があります)。 EArrayList<E>に入れると、get()Eを取得できます。

1
Tony

指定された(既知の)タイプの「汎用」配列を作成することができます。通常、コードではこのようなものを使用します。

public static <T> T[] toArray(Class<T> type, ArrayList<T> arrList) {
    if ((arrList == null) || (arrList.size() == 0)) return null;
    Object arr = Array.newInstance(type, arrList.size());
    for (int i=0; i < arrList.size(); i++) Array.set(arr, i, arrList.get(i));
    return (T[])arr;
}
0