Javaコレクションリストをコピーする方法


141

持っていますが、ArrayList正確にコピーしたいと思います。私は、誰かがそれを修正するためにある程度の時間を費やしたという仮定の下で、可能な場合はユーティリティクラスを使用します。だから当然、私Collectionsはcopyメソッドを含むクラスになってしまいます。

私が以下を持っていると仮定します:

List<String> a = new ArrayList<String>();
a.add("a");
a.add("b");
a.add("c");
List<String> b = new ArrayList<String>(a.size());

Collections.copy(b,a);

これは基本的に、bを保持するのに十分な大きさではないと考えているため失敗しますa。はいb、サイズが0であることはわかっていますが、十分に大きいはずです。b最初に記入する必要がある場合Collections.copy()、私の心の中で完全に役に立たない機能になります。それで、コピー機能(これからやります)のプログラミングを除いて、これを行う適切な方法はありますか?


Collections.copy()のドキュメントには、「宛先リストは少なくともソースリストと同じ長さである必要があります。」と記載されています。
DJクレイワース2009年

21
受け入れられた答えが正しいとは思いません
Bozho

3
不正解を受け入れました、ジャスパーフロア。私はあなたがあなたのコードで間違った情報を使わなかったことを心から望みます!
Malcolm

回答:


115

呼び出す

List<String> b = new ArrayList<String>(a);

a内の浅いコピーを作成しますb。すべての要素はb、それらが含まれaていたのとまったく同じ順序で存在します(順序があると想定)。

同様に、

// note: instantiating with a.size() gives `b` enough capacity to hold everything
List<String> b = new ArrayList<String>(a.size());
Collections.copy(b, a);

a内のの浅いコピーも作成しますb。最初のパラメータにb、すべてのの要素を含めるのに十分な容量(サイズではない)がない場合、aがスローされますIndexOutOfBoundsException。が動作するためにが割り当てを必要としないことが期待されており、割り当てがあるCollections.copy場合は、その例外がスローされます。コピーされたコレクションを事前に割り当てることを要求するのは最適化(b)ですが、上に示したような奇妙な副作用のないコンストラクターベースの代替案を前提として必要なチェックが行われるため、この機能はそれだけの価値があるとは思いません。

ディープコピーを作成するにはList、いずれかのメカニズムを介して、が基になる型の複雑な知識を持っている必要があります。StringJava(およびそのことについては.NET)で不変であるs の場合、ディープコピーすら必要ありません。の場合、MySpecialObjectそれを深くコピーする方法を知る必要がありますが、これは一般的な操作ではありません。


注:最初に受け入れられた回答がCollections.copyGoogleのトップの結果でしたが、コメントで指摘されているように、それは完全に間違っていました。


1
@ncasasはい、あります。Javaには一般的な「コピー」機能がないことを嘆いています。実際には、他の作成者がクラスにclone()を実装していないことがよくあります。オブジェクトのあらゆる種類のコピーを実行する機能がないものを残します。または、さらに悪いことに、ドキュメントがまったくないか不十分な実装済みのcloneメソッドがあり、そのため、clone関数は(信頼性が高く、実用的な「何が起こっているかを知る」という意味で)使用できなくなります。
マルコム

133

b持っている能力 3のを、しかし、サイズ 0のは事実ArrayListバッファ容量のいくつかの並べ替えを持っているが、実装の詳細である-それはの一部ではありませんListインターフェースなので、Collections.copy(List, List)それを使用しません。それが特別な場合に醜いでしょうArrayList

MrWigglesが示したように、提供されている例では、コレクションを取得するArrayListコンストラクターを使用する方法があります。

より複雑なシナリオ(実際のコードが含まれている可能性があります)では、Guava内のコレクションが役立つ場合があります。


58

ただやる:

List a = new ArrayList(); 
a.add("a"); 
a.add("b"); 
a.add("c"); 
List b = new ArrayList(a);

ArrayListには、要素をコピーする別のコレクションを受け入れるコンストラクタがあります


7
以下の誰かがコメントするように、これは浅いコピーです。それ以外の場合は、これで十分でしょう。私はそれを指定すべきだったと思います。とにかく、私はとにかく移動しました。
ジャスパーフロア

11
文字列のリストの場合、Stringオブジェクトは不変であるため、ディープコピーは重要ではありません。
Derek Mahar、2011

17

スティーブン・カトゥルカの答え(受け入れられた答え)は間違っています(後編)。それはそれCollections.copy(b, a);がそれをしないディープコピーをすることを説明します。両方、new ArrayList(a);そしてCollections.copy(b, a);浅いコピーのみを行います。違いは、コンストラクターが新しいメモリーを割り当てることと割り当てcopy(...)ないことです。これにより、パフォーマンスが向上するため、配列を再利用できる場合に適しています。

Java標準APIはディープコピーの使用を阻止しようとします。これは、新しいコーダーが定期的にこれを使用することは悪いことであり、これもclone()デフォルトで非公開である理由の1つである可能性があります。

のソースコードは、Collections.copy(...)552行目の http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/にあります。 Collections.java.htm

ディープコピーが必要な場合は、各オブジェクトでforループとclone()を使用して、項目を手動で反復処理する必要があります。


12

リストをコピーする最も簡単な方法は、リストを新しいリストのコンストラクターに渡すことです。

List<String> b = new ArrayList<>(a);

b の浅いコピーになります a

Collections.copy(List,List)(これまで見たことがない)のソースを見ると、インデックスごとに要素を処理するためのようです。List.set(int,E)このように要素0を使用すると、ターゲットリストなどの要素0が上書きされます。許可する必要があるjavadocsからは特に明確ではありません。

List<String> a = new ArrayList<>(a);
a.add("foo");
b.add("bar");

List<String> b = new ArrayList<>(a); // shallow copy 'a'

// the following will all hold
assert a.get(0) == b.get(0);
assert a.get(1) == b.get(1);
assert a.equals(b);
assert a != b; // 'a' is not the same object as 'b'

なぜ「浅い」コピーと言うのですか?
-me

4
「浅いコピー」とは、コピー後、bのオブジェクトはaと同じオブジェクトであり、それらのコピーではないことを意味します。
DJクレイワース2009年

1
Collections.copy()のjavadocには、「宛先リストは少なくともソースリストと同じ長さでなければなりません」と書かれています。
DJクレイワース2009年

私は、関数が実際に何をしているかを確認するためにいくつかの外観を必要とし、質問者がそれが正確に何をしているのか少し混乱したことがわかります
Gareth Davis

それが重要であるかどうかわかりませんか?Stringは不変なので、参照だけが同じではありません。ただし、どちらかのリストの要素を変更しようとしても、他のリストの同じ要素を変更することはありません
David T.

9
List b = new ArrayList(a.size())

サイズは設定しません。初期容量を設定します(サイズ変更が必要になるまでに要素をいくつ収容できるか)。この場合のコピーのより簡単な方法は次のとおりです。

List b = new ArrayList(a);

8

ホイジュイが言うように。Stephen Katulkaからの選択した回答には、Collections.copyに関するコメントが正しくありません。コードの最初の行が彼が望むコピーをしていたので、作者はおそらくそれを受け入れました。Collections.copyへの追加の呼び出しは、再度コピーするだけです。(コピーが2回発生します)。

これを証明するコードは次のとおりです。

public static void main(String[] args) {

    List<String> a = new ArrayList<String>();
    a.add("a");
    a.add("b");
    a.add("c");
    List<String> b = new ArrayList<String>(a);

    System.out.println("There should be no output after this line.");

    // Note, b is already a shallow copy of a;
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) != b.get(i)) {
            System.out.println("Oops, this was a deep copy."); // Note this is never called.
        }
    }

    // Now use Collections.copy and note that b is still just a shallow copy of a
    Collections.copy(b, a);
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) != b.get(i)) {
            System.out.println("Oops, i was wrong this was a deep copy"); // Note this is never called.
        }
    }

    // Now do a deep copy - requires you to explicitly copy each element
    for (int i = 0; i < a.size(); i++) {
        b.set(i, new String(a.get(i)));
    }

    // Now see that the elements are different in each 
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) == b.get(i)) {
            System.out.println("oops, i was wrong, a shallow copy was done."); // note this is never called.
        }
    }
}

5

ここでのほとんどの回答は問題を認識していません。ユーザーは最初のリストから2番目のリストへの要素のコピーを望んでいます。宛先リストの要素は新しいオブジェクトであり、元のリストの要素への参照ではありません。(2番目のリストの要素を変更しても、ソースリストの対応する要素の値は変更されません。)変更可能なオブジェクトの場合、元のリスト要素を単純に参照し、コピーしないため、ArrayList(Collection)コンストラクターを使用できません。コピーするときは、オブジェクトごとにリストクローンが必要です。


5

なぜaddAllメソッドを使用しないのですか?

    List a = new ArrayList();
         a.add("1");
         a.add("abc");

    List b = b.addAll(listA);

//b will be 1, abc

bに既存のアイテムがある場合、または次のようないくつかの要素を後置したい場合でも、

List a = new ArrayList();
     a.add("1");
     a.add("abc");

List b = new ArrayList();
     b.add("x");
     b.addAll(listA);
     b.add("Y");

//b will be x, 1, abc, Y

3

ArrayListをコピーする場合は、次のコマンドを使用してコピーします。

List b = new ArrayList();
b.add("aa");
b.add("bb");

List a = new ArrayList(b);

3

文字列はで深くコピーすることができます

List<String> b = new ArrayList<String>(a);

不変だからです。他のすべてのオブジェクトではありません->自分で反復してコピーを行う必要があります。


8
配列の各要素が内のb対応する同じStringオブジェクトを指すため、これはまだ浅いコピーaです。ただし、ご指摘のStringとおり、オブジェクトは不変であるため、これは重要ではありません。
Derek Mahar、2011

3
private List<Item> cloneItemList(final List<Item> items)
    {
        Item[] itemArray = new Item[items.size()];
        itemArray = items.toArray(itemArray);
        return Arrays.asList(itemArray);
    }

4
回答に説明を追加してください
サンパダ

1
このコードは質問に答えることがありますが、問題を解決する方法および/または理由に関する追加のコンテキストを提供すると、回答の長期的な価値が向上します。
Michael Parker

1

他のすべてのオブジェクトではありません->自分で反復してコピーを行う必要があります。

これを回避するには、Cloneableを実装します。

public class User implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;

    private String user;
    private String password;
    ...

    @Override
    public Object clone() {
        Object o = null;
        try {
          o = super.clone();
        } catch(CloneNotSupportedException e) {
        }
        return o;
     }
 }

....

  public static void main(String[] args) {

      List<User> userList1 = new ArrayList<User>();

      User user1 = new User();
      user1.setUser("User1");
      user1.setPassword("pass1");
      ...

      User user2 = new User();
      user2.setUser("User2");
      user2.setPassword("pass2");
      ...

      userList1 .add(user1);
      userList1 .add(user2);

      List<User> userList2 = new ArrayList<User>();


      for(User u: userList1){
          u.add((User)u.clone());
      }

      //With this you can avoid 
      /*
        for(User u: userList1){
            User tmp = new User();
            tmp.setUser(u.getUser);
            tmp.setPassword(u.getPassword);
            ...
            u.add(tmp);               
        }
       */

  }

2
「userList2.add((User)u.clone());」ではないはずです。?
KrishPrabakar

1

次の出力は、コピーコンストラクターとCollections.copy()を使用した結果を示しています。

Copy [1, 2, 3] to [1, 2, 3] using copy constructor.

Copy [1, 2, 3] to (smaller) [4, 5]
java.lang.IndexOutOfBoundsException: Source does not fit in dest
        at java.util.Collections.copy(Collections.java:556)
        at com.farenda.java.CollectionsCopy.copySourceToSmallerDest(CollectionsCopy.java:36)
        at com.farenda.java.CollectionsCopy.main(CollectionsCopy.java:14)

Copy [1, 2] to (same size) [3, 4]
source: [1, 2]
destination: [1, 2]

Copy [1, 2] to (bigger) [3, 4, 5]
source: [1, 2]
destination: [1, 2, 5]

Copy [1, 2] to (unmodifiable) [4, 5]
java.lang.UnsupportedOperationException
        at java.util.Collections$UnmodifiableList.set(Collections.java:1311)
        at java.util.Collections.copy(Collections.java:561)
        at com.farenda.java.CollectionsCopy.copyToUnmodifiableDest(CollectionsCopy.java:68)
        at com.farenda.java.CollectionsCopy.main(CollectionsCopy.java:20)

完全なプログラムのソースはここにあります:Javaリストのコピー。しかし、出力はjava.util.Collections.copy()の動作を確認するには十分です。


1

そして、グアグアバを使用している場合、1行の解決策は

List<String> b = Lists.newArrayList(a);

これにより、可変配列リストのインスタンスが作成されます。


1

Java 8はnullセーフなので、次のコードを使用できます。

List<String> b = Optional.ofNullable(a)
                         .map(list -> (List<String>) new ArrayList<>(list))
                         .orElseGet(Collections::emptyList);

またはコレクターを使用する

List<String> b = Optional.ofNullable(a)
                         .map(List::stream)
                         .orElseGet(Stream::empty)
                         .collect(Collectors.toList())

0

一部の値を既存のコレクションにコピーするユースケースを想像すると、コピーは役に立ちません。つまり、挿入する代わりに既存の要素を上書きしたいのです。

例:a = [1,2,3,4,5] b = [2,2,2,2,3,3,3,3,3,4,4,4、] a.copy(b) = [1,2,3,4,5,3,3,3,3,4,4,4]

ただし、ソースコレクションとターゲットコレクションの開始インデックスの追加パラメーター、およびカウントのパラメーターを取るコピーメソッドが必要です。

Javaバグ6350752を参照してください


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.