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

2011年03月21日に質問されました。  ·  閲覧回数 1.5M回  ·  ソース

locoboy picture
2011年03月21日

私はAndroid環境で作業していて、次のコードを試しましたが、機能していないようです。

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

私が次のように定義すると:

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

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

回答

Prince John Wesley picture
2011年03月21日
1786

このように使用します。

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);
st0le picture
2011年03月21日
958

これを試して

String[] arr = list.toArray(new String[list.size()]);
Stephen C picture
2011年03月21日
311

何が起こっていることはつまりstock_list.toArray()作成されるObject[]ではなくString[]ので、型キャストが1を失敗しています。

正しいコードは次のとおりです。

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

あるいは

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

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

後者のバージョンでは、長さがゼロの配列を使用して、結果の配列のタイプを判別します。 (驚くべきことに、少なくとも最近の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???

これは明らかにナンセンスであり、そのため、配列型は一般的に割り当て互換ではありません。

Vitalii Fedorenko picture
2014年04月20日
160

Java 8の代替手段:

String[] strings = list.stream().toArray(String[]::new);
Pshemo picture
2013年07月28日
88

問題を解決する方法を示す多くの答えを見ることができますが、問題が発生する理由を説明しようとしているのはスティーブンの答えだけなので、このテーマについてさらに何かを追加しようと思います。


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

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

ここで問題となるのは、 Object[]インスタンスをString[]キャストできないことです。

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

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

このようなコードはコンパイルされますが、参照aによって保持されているインスタンスは実際にはタイプB (またはそのサブタイプ)ではないため、実行時にClassCastExceptionスローされます。 なぜこの問題があるのですか(なぜこの例外をキャストする必要があるのですか)? 理由の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[]列の横にStrings (例えばIntegers 1美しい一日、私たちのようなメソッド呼び出ししようとして終わるということも可能であるので) strArray[i].substring(1,3)そのようなメソッドを持たないIntegerインスタンスのstrArray[i].substring(1,3)

だから、この状況が決して起こらないことを確認したJava配列参照にのみ保持することができます

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

でも我慢できない

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

言い換えれば、このようなものはOKです

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つの方法は、実行時にすべてのリスト要素の中で最も一般的な型を見つけてその型の配列を作成することですが、これはリストのすべての要素が一般的なものから派生した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.
Rick Hanlon II picture
2013年07月28日
21

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

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

ここで他のすばらしい回答に追加し、Javadocを使用して質問に回答する方法を説明したいと思います。

toArray() (引数なし)のJavadocはここにありString[]ではなくObject[]を返します。

public Object[] toArray()

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

ただし、そのメソッドのすぐ下には toArray(T[] a) JavadocT[]返します。ここで、 Tは渡す配列のタイプです。最初はこれが探しているもののように見えますが、正確な理由は不明です。 '配列を渡します(配列に追加するのか、型だけに使用するのかなど)。 ドキュメントは、渡された配列の目的が本質的に返される配列のタイプを定義することであることを明確にしています(これはまさにあなたのユースケースです):

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

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

このコレクションが、その要素がイテレータによって返される順序について何らかの保証を行う場合、このメソッドは同じ順序で要素を返す必要があります。

この実装は、配列がコレクションを含むのに十分な大きさであるかどうかをチェックします。 そうでない場合は、正しいサイズとタイプの新しい配列を割り当てます(リフレクションを使用)。 次に、コレクションを反復処理し、要素0から始まる、配列の次の連続する要素に各オブジェクト参照を格納します。配列がコレクションよりも大きい場合、コレクションの終了後の最初の場所にnullが格納されます。

もちろん、これら2つの方法の違いを実際に理解するには、ジェネリックス(他の回答で説明されている)を理解する必要があります。 それでも、最初にJavadocにアクセスすると、通常は答えが見つかり、他に何を学ぶ必要があるかを自分で確認できます(本当にそうしている場合)。

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

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

なぜなら、ドキュメントから、この実装は配列がコレクションを含むのに十分な大きさであるかどうかをチェックするからです。 そうでない場合は、正しいサイズとタイプの新しい配列を割り当てます(リフレクションを使用)。 サイズを簡単に渡すことができれば、新しい配列を作成する際に余分なオーバーヘッドは必要ありません。

通常の場合と同様に、Javadocは豊富な情報と方向性を提供します。

ちょっと待ってください、反射は何ですか?