web-dev-qa-db-ja.com

ArrayList <String>をString []配列に変換します。

私はAndroid環境で作業しており、次のコードを試してみましたが、うまくいっていないようです。

String [] stockArr = (String[]) stock_list.toArray();

次のように定義したとします。

String [] stockArr = {"hello", "world"};

できます。足りないものはありますか。

1049
locoboy

こんな感じで使えます。

List<String> stockList = new ArrayList<String>();
stockList.add("stock1");
stockList.add("stock2");

String[] stockArr = new String[stockList.size()];
stockArr = stockList.toArray(stockArr);

for(String s : stockArr)
    System.out.println(s);
1688

これを試して

String[] arr = list.toArray(new String[list.size()]);
903
st0le

何が起こっているかというと、stock_list.toArray()Object[]ではなくString[]を作成しているため、タイプキャストは失敗しています。1

正しいコードは次のようになります。

  String [] stockArr = stockList.toArray(new String[stockList.size()]);

あるいは

  String [] stockArr = stockList.toArray(new String[0]);

詳細については、2つのList.toArrayのオーバーロードについてのjavadocを参照してください。

後者のバージョンは、結果の配列の型を決定するために長さ0の配列を使用します。 (驚くべきことに、少なくとも最近のJavaリリースでは、事前に割り当てるよりもこれを行う方が早いです。詳細については https://stackoverflow.com/a/4042464/139985 を参照してください。)

技術的な観点からは、このAPIの動作/設計の理由は、List<T>.toArray()メソッドの実装には、実行時の<T>の内容に関する情報がないためです。生の要素型がObjectであることだけがわかっています。対照的に、他の場合では、配列パラメータは配列の基本型を与えます。 (指定された配列がリスト要素を格納するのに十分な大きさである場合はそれが使用されます。それ以外の場合は、同じ型でより大きなサイズの新しい配列が割り当てられ、結果として返されます。)


1 - Javaでは、Object[]String[]と代入互換性がありません。もしそうなら、あなたはこれを行うことができます:

    Object[] objects = new Object[]{new Cat("fluffy")};
    Dog[] dogs = (Dog[]) objects;
    Dog d = dogs[0];     // Huh???

これは明らかに意味のないことです。そのため、配列型は一般に代入互換ではありません。

294
Stephen C

Java 8の代替手段

String[] strings = list.stream().toArray(String[]::new);
140

私は問題を解決する方法を示す多くの答えを見ることができますが、 Stephen's answer だけが問題が起こる理由を説明しようとしているので、私はこの問題についてもっと何かを加えようとします。 Object[] toArrayが、ジェネリックスがJavaに導入されたT[] toArrayに変更されなかった理由についての物語です。


String[] stockArr = (String[]) stock_list.toArray();が機能しないのはなぜですか?

Javaでは、ジェネリック型はコンパイル時にのみ存在します。実行時にジェネリック型に関する情報(あなたの場合のように<String>)は削除されてObject typeに置き換えられます( type erasure を見てください)。そのため、実行時にtoArray()は新しい配列を作成するためにどの正確な型を使用すべきかわからないので、最も安全な型としてObjectを使用します。各クラスはObjectを拡張し、あらゆるクラスのインスタンスを安全に格納できるからです。

今問題はあなたがObject[]のインスタンスをString[]にキャストすることができないということです。

どうして?この例を見てください(class B extends Aと仮定しましょう)。

//B extends A
A a = new A();
B b = (B)a;

このようなコードはコンパイルされますが、参照ClassCastExceptionによって保持されているインスタンスは実際にはa(またはそのサブタイプ)の型ではないため、実行時にBがスローされます。なぜこの問題があるのですか(なぜこの例外をキャストする必要があるのですか)。その理由の1つは、BAにはない新しいメソッド/フィールドを持つことがあるため、保持されたインスタンスがそれらを持っていない(サポートしていない)場合でもb参照を介して新しいメンバーを使用しようとすることです。言い換えれば、私たちは存在しないデータを使おうとすることになり、多くの問題を引き起こす可能性があります。そのため、このような状況を防ぐために、JVMは例外をスローし、さらに危険性のあるコードを阻止します。

あなたは今尋ねることができるでしょう「ではなぜ我々はもっと早く止められないのですか?なぜそのようなキャストを含むコードがコンパイル可能でさえありますか?コンパイラはそれを止めてはいけませんか?」。答えは、コンパイラがa参照によって保持されている実際のインスタンスの種類を確実に把握できないため、B参照のインタフェースをサポートするクラスbのインスタンスを保持する可能性があるためです。この例を見てください。

A a = new B(); 
      //  ^------ Here reference "a" holds instance of type B
B b = (B)a;    // so now casting is safe, now JVM is sure that `b` reference can 
               // safely access all members of B class

それでは配列に戻りましょう。ご想像のとおり、Object[]配列のインスタンスをより正確なString[]型にキャストすることはできません。

Object[] arr = new Object[] { "ab", "cd" };
String[] arr2 = (String[]) arr;//ClassCastException will be thrown

ここで問題は少し異なります。すべての配列がサポートしているだけなので、String[]配列には追加のフィールドやメソッドはありません。

  • []演算子、
  • lengthを提出、
  • objectスーパータイプから継承したメソッド

そのため、それを不可能にしているのはnot arrayインターフェースです。問題はStringsの横のObject[]配列は任意のオブジェクトを格納できます(例えばIntegers)ですので、美しい日にはstrArray[i].substring(1,3)のようなメソッドを呼び出すことになるかもしれませんそのようなメソッドがないInteger

そのため、sureneverになるようにするために、Javaの配列参照では保持することしかできません。

  • 参照と同じ型の配列のインスタンス(参照String[] strArrString[]を保持できます)
  • サブタイプの配列のインスタンス(StringObjectのサブタイプであるため、Object[]String[]を保持できます)、

しかし握ることができない

  • 参照からの配列の型のスーパータイプの配列(String[]Object[]を保持できません)
  • 参照からの型に関連しない型の配列(Integer[]String[]を保持できません)

言い換えれば、このようなことは問題ありません。

Object[] arr = new String[] { "ab", "cd" }; //OK - because
               //  ^^^^^^^^                  `arr` holds array of subtype of Object (String)
String[] arr2 = (String[]) arr; //OK - `arr2` reference will hold same array of same type as 
                                //     reference

この問題を解決する1つの方法は、実行時にすべてのリスト要素間で最も一般的な型を見つけてその型の配列を作成することですが、これはlistのすべての要素が一般的なものから派生した1つの型になる場合は機能しません。見てください

//B extends A
List<A> elements = new ArrayList<A>();
elements.add(new B());
elements.add(new B());

現在最も一般的な型はBではなくAなのでtoArray()

A[] arr = elements.toArray();

Bクラスnew B[]の配列を返します。この配列の問題は、コンパイラがnew A()要素を追加することによってその内容を編集できるようにする一方で、B[]配列はクラスArrayStoreExceptionまたはそのサブクラスの要素のみを保持できるため、Bが得られることです。 B、しかしAのインスタンスはBのすべてのメソッド/フィールドを持つことはできません。だからこの解決策は完璧ではありません。


この問題に対する最善の解決策は、次のようにこの型をメソッドの引数として渡すことで、どの型の配列toArray()が返されるべきかを明示的に伝えることです。

String[] arr = list.toArray(new String[list.size()]);

または

String[] arr = list.toArray(new String[0]); //if size of array is smaller then list it will be automatically adjusted.
83
Pshemo

これを行う正しい方法は次のとおりです。

String[] stockArr = stock_list.toArray(new String[stock_list.size()]);

私はここで他の素晴らしい答えに加えて、あなたがあなたの質問に答えるためにどのようにあなたがJavadocを使ったかもしれないかを説明したいです。

toArray()引数なし)のJavadocは ここ です。ご覧のとおり、このメソッドはObject[]と_(notString[])を返します。これは、リストのランタイム型の配列です。

public Object[] toArray()

このコレクション内のすべての要素を含む配列を返します。コレクションがその要素がそのイテレータによって返される順序について保証をする場合、このメソッドは要素を同じ順序で返さなければなりません。返された配列は、その参照がコレクションによって維持されないという点で「安全」です。 (言い換えれば、このメソッドは、コレクションがArrayによってサポートされている場合でも、新しい配列を割り当てる必要があります)。呼び出し側は、返された配列を自由に変更できます。

ただし、そのメソッドの真下には、 JavadoctoArray(T[] a)があります。ご覧のように、このメソッドはT[]を返します。ここでTは渡される配列の型です。最初はこれが探しているもののように見えますが、配列を渡す理由は正確にはわかりませんそれには、単に型などに使用します。ドキュメントは、渡された配列の目的は基本的に返す配列の型を定義することであることを明確にしています(これはまさにあなたのユースケースです):

public <T> T[] toArray(T[] a)

このコレクション内のすべての要素を含む配列を返します。 返される配列の実行時型は、指定された配列の実行時型です。 指定した配列に収まる場合は、その配列が返されます。それ以外の場合は、指定された配列のランタイム型とこのコレクションのサイズで新しい配列が割り当てられます。コレクションが指定された配列に余裕を持って収まる場合(つまり、配列にコレクションよりも多くの要素がある場合)、コレクションの末尾の直後にある配列内の要素はnullに設定されます。これは、コレクションにnull要素が含まれていないことを呼び出し側が知っている場合にのみ、コレクションの長さを判断するのに役立ちます。)

このコレクションが、その要素がその反復子によって返される順序について保証する場合、このメソッドは要素を同じ順序で返す必要があります。

この実装は、配列がコレクションを格納するのに十分な大きさであるかどうかをチェックします。そうでない場合は、正しいサイズとタイプの新しい配列を(リフレクションを使用して)割り当てます。次に、コレクションに対して繰り返し、各オブジェクト参照を配列の次の連続した要素に格納します。要素が0より大きい場合は、コレクションの末尾の後の最初の位置にnullが格納されます。

もちろん、これら2つの方法の違いを本当に理解するには、(他の回答で説明したように)ジェネリックスを理解することが必要です。それにもかかわらず、あなたが最初にJavadocに行くなら、あなたは通常あなたの答えを見つけて、そして次にあなたが学ぶために他に何が必要であるかをあなた自身のために見るでしょう。

また、ここでJavadocを読むことで、渡す配列の構造を理解するのに役立ちます。実際には問題にならないかもしれませんが、次のように空の配列を渡してはいけません。

String [] stockArr = stockList.toArray(new String[0]);  

Docから、この実装は配列がコレクションを格納するのに十分大きいかどうかをチェックするので。 そうでない場合は、正しいサイズと型の新しい配列を(リフレクションを使用して)割り当てます。サイズを簡単に渡すことができる場合は、新しい配列を作成するための余分なオーバーヘッドは不要です。

通常そうであるように、Javadocは豊富な情報と指示をあなたに提供します。

ちょっと待って、反射は何ですか?

19
Rick Hanlon II