2009-03-27 5 views
121

J'ai un ArrayList et je veux le copier exactement. J'utilise des classes d'utilité si possible en supposant que quelqu'un a passé du temps à le faire correctement. Donc, naturellement, je me retrouve avec la classe Collections qui contient une méthode de copie.Java Collections liste de copie - Je ne comprends pas

Supposons que je donne les résultats suivants:

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

Cela échoue parce que fondamentalement, il pense b n'est pas assez grand pour contenir a. Oui, je sais b a la taille 0, mais il devrait être assez grand maintenant ne devrait-il pas? Si je dois d'abord remplir b, puis Collections.copy() devient une fonction complètement inutile dans mon esprit. Donc, sauf pour la programmation d'une fonction de copie (que je vais faire maintenant) est-il une bonne façon de le faire?

+0

Le doc pour Collections.copy() dit "La liste de destination doit être au moins aussi longtemps que la liste des sources.". – DJClayworth

+20

Je ne pense pas que la réponse acceptée est correcte – Bozho

+3

Vous avez accepté une réponse incorrecte, Jasper Floor. J'espère sincèrement que vous n'avez pas utilisé les mauvaises informations dans votre code! – Malcolm

Répondre

104

Appel

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

crée une copie superficielle de a au sein b. Tous les éléments existeront au sein de b dans le même ordre qu'ils étaient dans a (en supposant qu'il avait une commande).

De même, en appelant

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

crée également une copie superficielle de a au sein b. Si le premier paramètre, b, n'a pas assez de capacité (pas la taille) pour contenir tous les éléments de a, alors il lancera un IndexOutOfBoundsException. L'attente est qu'aucune allocation ne sera requise par Collections.copy pour fonctionner, et le cas échéant, elle lève cette exception. C'est une optimisation pour exiger que la collection copiée soit préallouée (b), mais je ne pense généralement pas que la fonctionnalité en vaut la peine en raison des vérifications requises étant donné les alternatives basées sur le constructeur comme celle montrée ci-dessus qui n'ont pas d'effets secondaires bizarres.

Pour créer une copie profonde, le List, via l'un ou l'autre mécanisme, devrait avoir une connaissance complexe du type sous-jacent. Dans le cas de String, qui sont immuables dans Java (et .NET d'ailleurs), vous n'avez même pas besoin d'une copie en profondeur. Dans le cas de MySpecialObject, vous devez savoir comment en faire une copie profonde et ce n'est pas une opération générique.


Note: La réponse initialement acceptée était le meilleur résultat pour Collections.copy dans Google, et il était dans l'erreur comme indiqué dans les commentaires.

+4

Ceci est encore une copie peu profonde! – joejax

+5

Cette réponse est tout à fait fausse. Pour faire une copie en profondeur, il faut (1) faire une nouvelle liste (2) pour chaque élément de l'ancienne liste, faire une copie complète de cet élément et l'ajouter à la nouvelle liste. Il existe des fonctions * no * intégrées en Java pour effectuer une copie en profondeur. La méthode copy() pour chaque objet (qui s'étend de la classe Object) * ne fait pas * une copie en profondeur. copy() doit être implémenté par l'auteur de la classe. – Malcolm

+1

Sur une note personnelle, je trouve que ce problème est une lacune criante de Java - le fait qu'il n'y a pas de fonction de copie profonde intégrée (ie copier les octets de l'objet x dans un nouvel emplacement en mémoire et me donner une référence à cet objet nouvellement copié en profondeur). – Malcolm

58

Il suffit de faire:

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

ArrayList a un constructeur qui accepte une autre collection de copier les éléments de

+5

comme quelqu'un ci-dessous les commentaires, ceci est une copie peu profonde. Sinon, cela aurait été une bonne réponse. Je suppose que j'aurais dû préciser ça. Peu importe, j'ai déménagé quand même. –

+10

Pour une liste de chaînes, la copie en profondeur n'est pas importante car les objets 'String' sont immuables. –

127

b a une capacité de 3, mais une taille de 0. fait que ArrayList a une sorte de capacité tampon est un détail d'implémentation - il ne fait pas partie de l'interface List, donc Collections.copy(List, List) ne l'utilise pas. Il serait moche pour cela à cas particulier ArrayList. Comme l'a indiqué MrWiggles, utiliser le constructeur ArrayList qui prend une collection est le moyen de l'exemple fourni.

Pour les scénarios plus complexes (qui peuvent inclure votre code réel), vous pouvez trouver le Google Java Collections library utile.

+1

+1 pour expliquer réellement ce qui se passe – basszero

+1

@Jon cool = D Sur quoi travailles-tu avant? – Pacerier

+5

@Pacerier: J'ai travaillé sur la synchronisation mobile, les notifications iPhone via APNS, l'application Windows Phone 7 Google Search et Android Market. –

12

la façon la plus simple de copier une liste est de le transmettre au constructeur de la nouvelle liste:

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

b sera une copie superficielle de a

regardant la source de Collections.copy(List,List) (I Je ne l'avais jamais vu auparavant) il semble que ce soit pour faire face aux éléments index par index. en utilisant List.set(int,E), l'élément 0 écrasera l'élément 0 dans la liste des cibles, etc. Pas particulièrement clair pour les javadocs que je devrais admettre.

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' 
+0

pourquoi dites-vous «peu profonde» copie? - me java noob – Martlark

+4

Par 'copie superficielle' il signifie que, après la copie, les objets dans b sont les mêmes objets que dans a, pas des copies d'eux. – DJClayworth

+1

Le javadoc pour Collections.copy() indique "La liste de destination doit être au moins aussi longue que la liste source." – DJClayworth

3

Si vous souhaitez copier une liste de tableaux, copie en utilisant:

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

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

ne définit pas la taille. Il définit la capacité initiale (c'est-à-dire le nombre d'éléments qu'il peut contenir avant de devoir redimensionner). Une façon plus simple de copie dans ce cas est:

List b = new ArrayList(a); 
0

copie n'est pas inutile si vous imaginez le cas d'utilisation pour copier des valeurs dans une collection existante. C'est à dire. vous voulez remplacer les éléments existants au lieu de les insérer. Exemple: 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]

Cependant, je m'attendrais à une méthode de copie qui prendrait des paramètres supplémentaires pour le début index de la collection source et cible, ainsi qu'un paramètre pour count.

Voir Java BOGUE 6350752

15

La réponse de Stephen Katulka (réponse acceptée) est fausse (la deuxième partie). Il explique que Collections.copy(b, a); effectue une copie en profondeur, ce qui n'est pas le cas. Les deux, new ArrayList(a); et Collections.copy(b, a); font seulement une copie superficielle. La différence est que le constructeur alloue une nouvelle mémoire, ce qui n'est pas le cas pour copy(...), ce qui le rend approprié dans les cas où vous pouvez réutiliser des tableaux, car il présente un avantage de performance. L'API standard Java tente de décourager l'utilisation de copies en profondeur, car il serait mauvais que de nouveaux codeurs l'utilisent régulièrement, ce qui peut aussi être l'une des raisons pour lesquelles clone() n'est pas public par défaut.

Le code source de Collections.copy(...) peut être vu sur la ligne 552 à: http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Core/Collections-Jar-Zip-Logging-regex/java/util/Collections.java.htm

Si vous avez besoin d'une copie en profondeur, vous devez itérer sur les éléments manuellement, en utilisant une boucle et clone() sur chaque objet .

3

Les chaînes peuvent être copiés au avec

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

parce qu'ils sont immuables. Tout autre objet n'est pas -> vous devez répéter et faire une copie par vous-même.

+8

Il s'agit toujours d'une copie superficielle car chaque élément du tableau 'b' pointe vers le même objet' String' correspondant dans 'a'. Cependant, ce n'est pas important car, comme vous le faites remarquer, les objets 'String' sont immuables. –

8

Comme le mentionne Hoijui. La réponse sélectionnée de Stephen Katulka contient un commentaire sur Collections.copy qui est incorrect. L'auteur l'a probablement accepté parce que la première ligne de code faisait la copie qu'il voulait. L'appel supplémentaire à Collections.copy copie juste encore. (Il en résulte que la copie a lieu deux fois).

Voici le code pour le prouver.

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

La plupart des réponses ici ne réalisent pas le problème, l'utilisateur veut avoir une copie des éléments de la première liste à la deuxième liste, les éléments de la liste de destination sont de nouveaux objets et non référence aux éléments de la liste originale. (la modification d'un élément de la deuxième liste ne doit pas modifier les valeurs de l'élément correspondant de la liste source.) Pour les objets mutables, nous ne pouvons pas utiliser le constructeur ArrayList (Collection) car il fera simplement référence à l'élément de liste original et ne le copiera pas. Vous devez avoir un cloner liste pour chaque objet lors de la copie.

1

Tous les autres objets non -> vous devez effectuer une copie et faire une copie par vous-même.

Pour éviter cet outil 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

Ce ne devrait pas être "userList2.add ((User) u.clone());" ? – KrishPrabakar

5

Pourquoi ne pas utiliser simplement la méthode addAll:

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

    List b = b.addAll(listA); 

//b will be 1, abc 

même si vous avez des éléments existants dans b ou si vous voulez Pend certains éléments après ce tels que:

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 
1

Et si vous utilisez la goyave de Google, la solution d'une ligne serait

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

Cela crée une instance de liste de tableau mutable.

1

La sortie suivante illustre les résultats de l'utilisation constructeur de copie et 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) 

La source du programme complet est ici: Java List copy. Mais la sortie est suffisante pour voir comment java.util.Collections.copy() se comporte.

-1

Pour comprendre pourquoi Collections.copy() lance une exception IndexOutOfBoundsException bien que vous ayez rendu le tableau de sauvegarde de la liste de destination suffisamment grand (via l'appel size() sur la sourceList), voir la réponse d'Abhay Yadav. question: How to copy a java.util.List into another java.util.List

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

S'il vous plaît ajouter quelques explications à votre réponse – Sampada

+1

Alors que ce code peut répondre à la question, en fournissant un contexte supplémentaire concernant * comment * et/ou * pourquoi * il résout le problème permettrait d'améliorer la valeur à long terme de la réponse. –

0

vous pouvez Java 8 être null-safe, utilisez le code suivant.

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

ou en utilisant un collecteur

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