次のように定義されたarraylistがあるとします。
_ArrayList<String> someData = new ArrayList<>();
_
後のコードでは、ジェネリックのため、これを言うことができます:
_String someLine = someData.get(0);
_
そして、コンパイラーは文字列を取得することを完全に知っています。イェイジェネリック!ただし、これは失敗します。
_String[] arrayOfData = someData.toArray();
_
toArray()
は、定義されたジェネリックではなく、常にオブジェクトの配列を返します。なぜget(x)
メソッドは何を返しているのかを知っているのに、toArray()
はデフォルトでObjectsになっていますか?
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 Exception( JLS§10.5 )からの別の重要な引用:
配列のコンポーネントタイプを再定義できない場合(§4.7)、Java前の段落で説明した仮想マシンはストアチェックを実行できませんでした。これが配列作成の理由です。非特定化可能な要素タイプの式は禁止されています(§15.10.1)。
Javaがオーバーロードされたバージョン toArray(T[] a)
を提供したのはこのためです。
ToArray()メソッドをオーバーライドして、Eの配列を返すことを通知します。
したがって、toArray()
をオーバーライドする代わりに、toArray(T[] a)
を使用する必要があります。
汎用情報は、実行時に 消去 です。 JVMは、リストが_List<String>
_か_List<Integer>
_(実行時にList<T>
_のT
がObject
として解決されるか)を知らないため、唯一の可能な配列タイプは_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());
_
List
インターフェイスのJavadoc を見ると、toArray
の2番目の形式:<T> T[] toArray(T[] a)
に気付くでしょう。
実際、Javadocには、あなたがやりたいことを正確に行う方法の例さえ示されています。
String[] y = x.toArray(new String[0]);
注意すべき適切なことは、Javaの配列は実行時にコンポーネントタイプを知っていることです。String[]
とInteger[]
は実行時に異なるクラスであり、実行時にコンポーネントタイプの配列を要求できます。配列を作成するには、実行時にコンポーネントタイプが必要です(コンパイル時にnew String[...]
を使用して再構成可能なコンポーネントタイプをハードコーディングするか、Array.newInstance()
を使用してクラスオブジェクトを渡す)。
一方、ジェネリックの型引数は実行時には存在しません。 ArrayList<String>
とArrayList<Integer>
の間には、実行時にまったく違いはありません。すべてArrayList
です。
これが、何らかの方法でコンポーネントタイプを個別に渡さずにList<String>
を取得してString[]
を取得できない基本的な理由です。コンポーネントタイプ情報を持たないものからコンポーネントタイプ情報を取得する必要があります。明らかに、これは不可能です。
配列を作成する代わりにイテレータを使用することもできますが、これは常に奇妙に思えます。なぜ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];
}
_
コンパイル時にE
をObject
に置き換えるのはJavaコンパイラーです
配列は、配列の型とは異なる型です。 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.
最初に理解しなければならないことは、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>
を与える必要があります)。 E
をArrayList<E>
に入れると、get()
でE
を取得できます。
指定された(既知の)タイプの「汎用」配列を作成することができます。通常、コードではこのようなものを使用します。
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;
}