Jak skopiować listę kolekcji Java

141

Mam ArrayListi chcę go dokładnie skopiować. Gdy to możliwe, używam klas użytkowych, zakładając, że ktoś poświęcił trochę czasu na poprawienie tego. Więc naturalnie otrzymuję Collectionsklasę, która zawiera metodę kopiowania.

Załóżmy, że mam:

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);

To się nie udaje, ponieważ zasadniczo uważa, że bnie jest wystarczająco duży, aby go pomieścić a. Tak, wiem, że bma rozmiar 0, ale teraz powinien być wystarczająco duży, prawda? Jeśli muszę bnajpierw wypełnić , to Collections.copy()w moim umyśle staje się całkowicie bezużyteczną funkcją. Więc poza programowaniem funkcji kopiowania (co teraz zrobię), czy istnieje właściwy sposób na zrobienie tego?

Jasper Floor
źródło
Dokument dla Collections.copy () mówi: „Lista docelowa musi być przynajmniej tak długa, jak lista źródłowa”.
DJClayworth,
21
Nie sądzę, aby zaakceptowana odpowiedź była poprawna
Bozho
3
Przyjąłeś nieprawidłową odpowiedź, Jasper Floor. Mam szczerą nadzieję, że nie użyłeś niewłaściwych informacji w swoim kodzie!
Malcolm,

Odpowiedzi:

115

Powołanie

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

tworzy płytką kopię awewnątrz b. Wszystkie elementy będą istniały bw dokładnie tej samej kolejności, w jakiej się znajdowały a(zakładając, że ma kolejność).

Podobnie dzwoniąc

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

tworzy również płytką kopię awewnątrz b. Jeśli pierwszy parametr b,, nie ma wystarczającej pojemności (nie rozmiaru), aby pomieścić wszystkie aelementy, to wyrzuci IndexOutOfBoundsException. Oczekuje się, że żadne alokacje nie będą wymagane Collections.copydo działania, a jeśli takowe są, zgłasza ten wyjątek. Jest to optymalizacja wymagająca, aby skopiowana kolekcja była wstępnie przydzielona ( b), ale generalnie nie uważam, że ta funkcja jest tego warta ze względu na wymagane kontrole, biorąc pod uwagę alternatywy oparte na konstruktorach, takie jak ta pokazana powyżej, które nie mają dziwnych skutków ubocznych.

Aby utworzyć głęboką kopię, za Listpośrednictwem dowolnego mechanizmu musiałby mieć skomplikowaną wiedzę o typie bazowym. W przypadku Strings, które są niezmienne w Javie (i .NET w tym przypadku), nie potrzebujesz nawet głębokiej kopii. W przypadku MySpecialObject, musisz wiedzieć, jak zrobić jego głęboką kopię i nie jest to operacja ogólna.


Uwaga: pierwotnie zaakceptowana odpowiedź była najwyższym wynikiem Collections.copyw Google i była całkowicie błędna, jak wskazano w komentarzach.

Stephen Katulka
źródło
1
@ncasas Tak, to robi. Ubolewam nad faktem, że w Javie nie ma ogólnej funkcji „kopiowania”. W praktyce często stwierdzam, że inni autorzy nie zaimplementowali clone () dla swoich klas; pozostawia bez możliwości wykonania jakiejkolwiek kopii obiektu. Lub, co gorsza, widzę zaimplementowaną metodę klonowania bez dokumentacji lub słabą, co powoduje, że funkcja klonowania jest bezużyteczna (w wiarygodnym i praktycznym sensie „wiedząc, co się dzieje”).
Malcolm,
133

bma pojemność 3, ale rozmiar 0. Fakt, że ArrayListma jakąś pojemność bufora, jest szczegółem implementacji - nie jest częścią Listinterfejsu, więc Collections.copy(List, List)go nie używa. Byłoby to brzydkie w wyjątkowych przypadkach ArrayList.

Jak wskazał MrWiggles, w podanym przykładzie sposobem jest użycie konstruktora ArrayList, który pobiera kolekcję.

W przypadku bardziej skomplikowanych scenariuszy (które mogą obejmować Twój prawdziwy kod), kolekcje w Guava mogą okazać się przydatne.

Jon Skeet
źródło
58

Po prostu zrób:

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

ArrayList ma konstruktora, który zaakceptuje inną Collection do skopiowania elementów

tddmonkey
źródło
7
jak ktoś poniżej komentuje, to jest płytka kopia. W przeciwnym razie byłaby to dobra odpowiedź. Przypuszczam, że powinienem to określić. Nieważne, i tak poszedłem dalej.
Jasper Floor
11
W przypadku listy ciągów głębokie kopiowanie nie jest ważne, ponieważ Stringobiekty są niezmienne.
Derek Mahar,
17

Odpowiedź Stephena Katulki (odpowiedź przyjęta) jest błędna (część druga). Wyjaśnia, że Collections.copy(b, a);robi głęboką kopię, której nie robi. Jedno new ArrayList(a);i Collections.copy(b, a);drugie, zrób tylko płytką kopię. Różnica polega na tym, że konstruktor przydziela nową pamięć, a copy(...)nie, co czyni go odpowiednim w przypadkach, w których można ponownie używać tablic, ponieważ ma tam przewagę wydajności.

Standardowy interfejs API języka Java próbuje zniechęcać do korzystania z głębokich kopii, ponieważ byłoby źle, gdyby nowi programiści używali go regularnie, co może być również jednym z powodów, dla clone()których domyślnie nie jest on publiczny.

Kod źródłowy Collections.copy(...)można znaleźć w linii 552 pod adresem : http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/ Collections.java.htm

Jeśli potrzebujesz głębokiej kopii, musisz ręcznie iterować po elementach, używając pętli for i clone () na każdym obiekcie.

hoijui
źródło
12

najprostszym sposobem skopiowania listy jest przekazanie jej do konstruktora nowej listy:

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

b będzie płytką kopią a

Patrząc na źródło Collections.copy(List,List)(nigdy wcześniej tego nie widziałem) wydaje się, że chodzi o radzenie sobie z indeksem elementów po indeksie. użycie List.set(int,E)elementu 0 spowoduje nadpisanie elementu 0 na liście docelowej itp. Nie jest to szczególnie jasne z javadocs, które muszę przyznać.

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'
Gareth Davis
źródło
dlaczego mówisz „płytka” kopia? - ja java noob
Martlark
4
Przez „płytką kopię” oznacza, że ​​po skopiowaniu obiekty z b są tymi samymi obiektami, co w a, a nie ich kopiami.
DJClayworth
1
Element javadoc dla Collections.copy () mówi: „Lista docelowa musi być co najmniej tak długa, jak lista źródłowa”.
DJClayworth,
Chyba chodziło mi o to, że zajęło mi kilka spojrzeń, aby zobaczyć, co właściwie robi ta funkcja i widzę, jak pytający trochę się pomylił z tym, co dokładnie robi
Gareth Davis
nie jestem pewien, czy to ma znaczenie? ponieważ String jest niezmienny, tylko odwołania nie są takie same. Jednakże, nawet jeśli u starają się mutować element w każdym liście, to nigdy nie mutuje ten sam element w innej liście
David T.
9
List b = new ArrayList(a.size())

nie ustawia rozmiaru. Ustawia początkową pojemność (czyli ile elementów może pomieścić, zanim będzie trzeba zmienić rozmiar). Prostszym sposobem kopiowania w tym przypadku jest:

List b = new ArrayList(a);
cletus
źródło
8

Jak wspomina Hoijui. Wybrana odpowiedź od Stephena Katulki zawiera niepoprawny komentarz dotyczący Collections.copy. Autor prawdopodobnie to zaakceptował, ponieważ pierwsza linia kodu wykonywała kopię, której chciał. Dodatkowe wywołanie Collections.copy po prostu kopiuje ponownie. (Powoduje to dwukrotne wykonanie kopii).

Oto kod, który to udowodni.

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.
        }
    }
}
Michael Welch
źródło
5

Większość odpowiedzi tutaj nie zdaje sobie sprawy z problemu, użytkownik chce mieć KOPIĘ elementów z pierwszej listy do drugiej listy, elementy listy docelowej to nowe obiekty, a nie odniesienia do elementów oryginalnej listy. (oznacza, że ​​zmiana elementu drugiej listy nie powinna zmieniać wartości dla odpowiedniego elementu listy źródłowej.) W przypadku obiektów mutowalnych nie możemy użyć konstruktora ArrayList (Collection), ponieważ będzie on po prostu odnosił się do oryginalnego elementu listy i nie będzie kopiowany. Podczas kopiowania musisz mieć klonera listy dla każdego obiektu.

yasirmcs
źródło
5

Dlaczego po prostu nie używasz addAllmetody:

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

    List b = b.addAll(listA);

//b will be 1, abc

nawet jeśli masz istniejące elementy w b lub chcesz zawiesić po nim niektóre elementy, takie jak:

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
Vin.X
źródło
3

Jeśli chcesz skopiować ArrayList, skopiuj ją za pomocą:

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

List a = new ArrayList(b);
Martin C.
źródło
3

Ciągi można kopiować głęboko

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

ponieważ są niezmienne. Każdy inny obiekt nie -> musisz samemu powtórzyć i zrobić kopię.

felix
źródło
8
To wciąż jest płytka kopia, ponieważ każdy element tablicy bwskazuje na ten sam odpowiedni Stringobiekt w a. Nie jest to jednak ważne, ponieważ, jak zauważyłeś, Stringobiekty są niezmienne.
Derek Mahar,
3
private List<Item> cloneItemList(final List<Item> items)
    {
        Item[] itemArray = new Item[items.size()];
        itemArray = items.toArray(itemArray);
        return Arrays.asList(itemArray);
    }
Raen K.
źródło
4
Proszę dodać wyjaśnienie do swojej odpowiedzi
Sampada
1
Chociaż ten kod może odpowiedzieć na pytanie, dostarczenie dodatkowego kontekstu dotyczącego tego, jak i / lub dlaczego rozwiązuje problem, poprawiłoby długoterminową wartość odpowiedzi.
Michael Parker
1

Każdy inny obiekt nie -> musisz samemu powtórzyć i zrobić kopię.

Aby uniknąć tego narzędzia 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);               
        }
       */

  }
Juan Castillo
źródło
2
Czy nie powinno to być "userList2.add ((User) u.clone ());" ?
KrishPrabakar
1

Poniższe dane wyjściowe ilustrują wyniki użycia konstruktora kopiującego i 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)

Źródło pełnego programu jest tutaj: kopia listy Java . Ale dane wyjściowe są wystarczające, aby zobaczyć, jak zachowuje się java.util.Collections.copy ().

pwojnowski
źródło
1

A jeśli używasz google guava, rozwiązaniem będzie jedna linia

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

Tworzy to zmienną instancję listy tablic.

vsingh
źródło
1

Ponieważ Java 8 jest bezpieczna dla wartości null, możesz użyć następującego kodu.

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

Lub za pomocą kolektora

List<String> b = Optional.ofNullable(a)
                         .map(List::stream)
                         .orElseGet(Stream::empty)
                         .collect(Collectors.toList())
Nicolas Henneaux
źródło
0

Kopiowanie nie jest bezużyteczne, jeśli wyobrażasz sobie przypadek użycia kopiowania niektórych wartości do istniejącej kolekcji. To znaczy chcesz nadpisać istniejące elementy zamiast wstawiać.

Przykład: 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]

Spodziewałbym się jednak metody kopiowania, która wymagałaby dodatkowych parametrów dla indeksu początkowego kolekcji źródłowej i docelowej, a także parametru dla count.

Zobacz Java BUG 6350752

ordnungswidrig
źródło
-1

Aby zrozumieć, dlaczego Collections.copy () zgłasza wyjątek IndexOutOfBoundsException, mimo że tablica zapasowa listy docelowej jest wystarczająco duża (za pomocą wywołania size () w sourceList), zobacz odpowiedź Abhaya Yadava w tym pokrewnym pytaniu: Jak skopiuj java.util.List do innej java.util.List

volkerk
źródło