Jak sklonować listę ogólną w języku Java?

155

Mam ArrayList<String>kopię, którą chciałbym zwrócić. ArrayListma metodę klonowania, która ma następujący podpis:

public Object clone()

Po wywołaniu tej metody, jak mam rzutować zwrócony Object z powrotem do ArrayList<String>?

Bill the Lizard
źródło
16
Nie, to ważne pytanie. Java nie obsługuje „prawdziwych” typów ogólnych, z wymazywaniem typów w czasie wykonywania i wszystkimi innymi, więc tego rodzaju szczegóły mogą być trudne. Ponadto interfejs Cloneable i mechanizm metody Object.clone () są podobnie zagmatwane.
Outlaw Programmer
OK, głównie robię C #, gdzie jest to naprawdę łatwe. Daj mi znać, jeśli chcesz, abym usunął komentarze z tego pytania.
Espo
1
Możesz zostawić komentarz. Myślę, że moje zmiany wyjaśniły, z czym mam problem.
Bill the Lizard
2
Twój komentarz jest w porządku, choć trochę protekcjonalny. Wyobrażam sobie, że wiele przeszkód, przez które muszą przejść programiści Java, wydaje się głupim programistom .NET.
Outlaw Programmer
1
@Oscar, chce klonować, a nie wywoływać polecenia clone. Może to nie być to samo, co klon tak naprawdę nie klonuje. Myślę, że o to chodzi. To naprawdę trudne pytanie.
Rafa

Odpowiedzi:

62
ArrayList newArrayList = (ArrayList) oldArrayList.clone();
Vinko Vrsalovic
źródło
43
To zadziała dobrze w przypadku Strings (o co chodzi w pytaniu), ale warto zauważyć, że ArrayList.clone wykona płytką kopię, więc jeśli na liście znajdują się zmienne obiekty, nie zostaną one sklonowane (i zmieni jeden w jednej liście nie zmieni, że jedna na drugiej liście, jak również.
pkaeding
49
Należy unikać używania typów surowych w żadnym innym kodzie niż starszym. Lepiej użyj ArrayList<String> newArrayList = (ArrayList<String>) oldArrayList.clone();.
cdmckay
20
Szkoda, że ​​ArrayList ma metodę #clone, ale sama List nie. Westchnienie.
rogerdpack
12
Nie powiązane, ale w tym momencie: użyj List<String>zamiast ArrayList<String>po lewej stronie. W ten sposób kolekcje powinny być używane przez większość czasu.
Christophe Roussy
3
Nie mogłem użyć tej metody do zduplikowania ArrayList. Nie rozpoznano metody clone ().
Jack
318

Dlaczego chcesz klonować? Tworzenie nowej listy ma zwykle większy sens.

List<String> strs;
...
List<String> newStrs = new ArrayList<>(strs);

Zadanie wykonane.

Tom Hawtin - haczyk
źródło
17
Możesz nie wiedzieć, jaki to typ listy. Może to być LinkedList, MyOwnCustomList lub podklasa ArrayList, w którym to przypadku nowe ArrayList byłoby niepoprawnym typem.
Steve Kuo
51
Czy obchodzi mnie, której implementacji użyła oryginalna lista? Prawdopodobnie obchodzi mnie, której implementacji używa nowa lista.
Tom Hawtin - tackline
12
@Steve Kuo: Podpis ArrayList(Collection<? extends E> c)oznacza, że ​​nie ma znaczenia, jakiego rodzaju listy użyjesz jako argumentu.
cdmckay
3
To znaczy, to nie jest głęboka kopia, prawda?
4
@YekhezkelYovel Nie, nie byłoby.
Tom Hawtin - tackline
19

Oto kod, którego używam do tego:

ArrayList copy = new ArrayList (original.size());
Collections.copy(copy, original);

Nadzieja jest dla ciebie przydatna

Niemiecki
źródło
3
I unikaj używania surowych typów ... więc zamiast ArrayListużywaćArrayList<YourObject>
milosmns
2
docs.oracle.com/javase/8/docs/api/java/util/… Nie działa, ponieważ rozmiary list są różne.
MLProgrammer-CiM
Dlaczego nie miałbyś po prostu użyć przeciążenia kopiowania pliku ArrayList konstruktora?
Tom Hawtin - tackline
19

W Javie 8 można go sklonować za pomocą strumienia.

import static java.util.stream.Collectors.toList;

...

List<AnObject> clone = myList.stream().collect(toList());
Simon Jenkins
źródło
Można to zrobić tak jak List <AnObject> xy = new ArrayList <> (oldList);
mirzak
1
To nie jest głęboka kopia, zmiany w elementach jednej listy można zobaczyć w innej
Inchara
2
Gdzie w pytaniu określono, że wymagana jest głęboka kopia? Założenie jest takie, że wymagana jest inna kolekcja zawierająca te same obiekty. Jeśli chcesz zejść z głębokiej ścieżki kopiowania, otwierasz zupełnie nową puszkę robaków.
Simon Jenkins,
Nie jest to jednak klon, prawda? Z dokumentacji API „Nie ma gwarancji co do typu, zmienności, możliwości serializacji lub bezpieczeństwa wątków zwróconej listy”. Euw, nie chcę jednego z nich. toCollectionmoże być lepszym wyborem.
Tom Hawtin - tackline
16

Należy pamiętać, że Object.clone () ma poważne problemy i odradza się jego używanie w większości przypadków. Pełną odpowiedź można znaleźć w punkcie 11 książkiEfektywna Java ” Joshuy Blocha. Uważam, że możesz bezpiecznie używać Object.clone () na tablicach typów prymitywnych, ale poza tym musisz rozsądnie używać i nadpisywać clone. Prawdopodobnie lepiej będzie zdefiniować konstruktor kopiujący lub statyczną metodę fabryki, która jawnie klonuje obiekt zgodnie z twoją semantyką.

Julien Chastang
źródło
15

Myślę, że to powinno załatwić sprawę przy użyciu Collection API:

Uwaga : metoda kopiowania działa w czasie liniowym.

//assume oldList exists and has data in it.
List<String> newList = new ArrayList<String>();
Collections.copy(newList, oldList);
Aaron
źródło
13
Dlaczego po prostu nie użyć new ArrayList <String> (oldList)?
cdmckay
4
Wierzę, że to by nie zadziałało, z doc * Lista docelowa musi być przynajmniej tak długa, jak lista źródeł. Jeśli jest dłuższy, pozostałe elementy listy docelowej pozostają niezmienione.
Greg Domjan
@GregDomjan nie to, że zgadzam się, że to najlepszy sposób, ale jest to sposób na zrobienie tego. Aby obejść swój problem, jest to tak proste, jak to: List <String> newList = new ArrayList <> (oldList.size ());
nckbrz
1
Nie jestem pewien, czy jest to związane z Javą 8, ale nawet przy określaniu rozmiaru nadal pojawia się wyjątek IndexOutOfBoundsException: destination.size () <source.size (): 0 <2 on List<MySerializableObject> copyList = new ArrayList<>(mMySerializableObjects.size()); Wydaje się, że używanie copyList.addAll(original);jest dobrą alternatywą
Gene Bo
@GeneBo Nie określa rozmiaru. Określa pojemność, a to zupełnie inna sprawa. Radość z tajemniczych intargumentów. Nie mam pojęcia, dlaczego chcesz użyć tej mało znanej metody statycznej zamiast starej, dobrej addAll.
Tom Hawtin - linka
8

Uważam, że użycie addAll działa dobrze.

ArrayList<String> copy = new ArrayList<String>();
copy.addAll(original);

zamiast generycznej składni używane są nawiasy

Allain Lalonde
źródło
5
To zadziała w przypadku ciągów znaków, ale nie w przypadku obiektów modyfikowalnych. Te również będziesz chciał sklonować.
jodonnell
Tak, jego pytanie dotyczy strun. I ma problem z generykami, które tak naprawdę nie lubią rzeczy odlewniczych w tym przypadku.
Allain Lalonde,
Ponadto ArrayList.clone wykona tylko płytkie klonowanie, więc zmienne obiekty na liście również nie zostaną sklonowane przy użyciu tej metody.
rozpoczęcie
Powinno to być ArrayList <String> btw. Ponadto prawdopodobnie lepiej będzie, jeśli użyjesz nowej ArrayList <String> (oryginalna), ponieważ jest mniej pisana i równie przejrzysta.
cdmckay
4
Dlaczego po prostu nie użyć new ArrayList<String>(original)?
user102008
6

Jeśli chcesz tego, aby móc zwrócić Listę w getterze, lepiej byłoby zrobić:

ImmutableList.copyOf(list);
Uri Shalit
źródło
4

Aby sklonować ogólny interfejs, taki jak java.util.Listty, wystarczy go przesłać. oto przykład:

List list = new ArrayList();
List list2 = ((List) ( (ArrayList) list).clone());

Jest to trochę trudne, ale działa, jeśli jesteś ograniczony do zwrócenia pliku List interfejsu, więc każdy może zaimplementować twoją listę, kiedy tylko zechce.

Wiem, że ta odpowiedź jest bliska ostatecznej odpowiedzi, ale moja odpowiedź odpowiada, jak to wszystko zrobić, gdy pracujesz z List- generycznym rodzicem - nieArrayList

Ahmed Hamdy
źródło
2
zakładasz
@JuanCarlosDiaz będzie, to jest zadawane pytanie było o ArrayList, więc odpowiedziałem to dla ArrayList :)
Ahmed hamdy
2

Zachowaj ostrożność podczas klonowania ArrayLists. Klonowanie w Javie jest płytkie. Oznacza to, że sklonuje tylko samego Arraylist, a nie jego członków. Więc jeśli masz ArrayList X1 i sklonujesz ją do X2, każda zmiana w X2 będzie również widoczna w X1 i odwrotnie. Podczas klonowania wygenerujesz tylko nową ArrayList ze wskaźnikami do tych samych elementów w oryginale.

Juan Besa
źródło
2

Powinno to również działać:

ArrayList<String> orig = new ArrayList<String>();
ArrayList<String> copy = (ArrayList<String>) orig.clone()
pkaeding
źródło
2
List<String> shallowClonedList = new ArrayList<>(listOfStrings);

Należy pamiętać, że jest to tylko płytka, a nie głęboka kopia, tj. otrzymasz nową listę, ale wpisy są takie same. Nie stanowi to problemu dla prostych strun. Get jest trudniejszy, gdy pozycje listy same w sobie są obiektami.

Robert
źródło
1
ArrayList first = new ArrayList ();
ArrayList copy = (ArrayList) first.clone ();
jodonnell
źródło
1

Nie jestem specjalistą od Java, ale mam ten sam problem i próbowałem rozwiązać tą metodą. (Zakłada się, że T ma konstruktora kopiującego).

 public static <T extends Object> List<T> clone(List<T> list) {
      try {
           List<T> c = list.getClass().newInstance();
           for(T t: list) {
             T copy = (T) t.getClass().getDeclaredConstructor(t.getclass()).newInstance(t);
             c.add(copy);
           }
           return c;
      } catch(Exception e) {
           throw new RuntimeException("List cloning unsupported",e);
      }
}
Petr
źródło
Nie ma żadnej gwarancji, że Listklasa implementacji ma publiczny konstruktor bez argumentów. Na przykład, Lists powroty przez Array.asList, List.oflub List.subListprawdopodobnie nie.
Tom Hawtin - tackline