Comment copier la liste des collections Java


141

J'ai un ArrayListet je veux le copier exactement. J'utilise des classes d'utilité lorsque cela est possible en supposant que quelqu'un a passé du temps à le corriger. Alors naturellement, je me retrouve avec la Collectionsclasse qui contient une méthode de copie.

Supposons que j'ai ce qui suit:

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 qu'il pense que ce bn'est pas assez grand pour tenir a. Oui, je sais qu'il ba la taille 0, mais il devrait être assez grand maintenant, n'est-ce pas? Si je dois remplir en bpremier, cela Collections.copy()devient une fonction complètement inutile dans mon esprit. Donc, à part la programmation d'une fonction de copie (ce que je vais faire maintenant), y a-t-il un moyen approprié de le faire?


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

21
Je ne pense pas que la réponse acceptée soit 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 du

Réponses:


115

Appel

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

crée une copie superficielle de l' aintérieur b. Tous les éléments existeront à l'intérieur bdans le même ordre exact dans lequel ils étaient a(en supposant qu'il y avait un ordre).

De même, appeler

// 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 l' aintérieur b. Si le premier paramètre,, bn'a pas assez de capacité (pas de taille) pour contenir tous ales éléments de, alors il lancera un IndexOutOfBoundsException. On s'attend à ce qu'aucune allocation ne soit requise Collections.copypour fonctionner, et le cas échéant, il lève cette exception. C'est une optimisation d'exiger que la collection copiée soit préallouée ( b), mais je ne pense généralement pas que la fonctionnalité en vaille 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 étranges.

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


Remarque: La réponse acceptée à l'origine était le meilleur résultat pour Collections.copyGoogle, et elle était tout à fait erronée comme indiqué dans les commentaires.


1
@ncasas Oui, c'est vrai. Je déplore le fait qu'il n'y ait pas de fonction générique de "copie" en Java. En pratique, trop souvent, je trouve que d'autres auteurs n'ont pas implémenté clone () pour leurs classes; cela laisse une personne sans la possibilité de faire une sorte de copie d'un objet. Ou, pire encore, je vois une méthode de clonage implémentée avec une documentation inexistante ou médiocre, ce qui rend la fonction de clonage inutilisable (dans un sens fiable et pratique "savoir ce qui se passe").
Malcolm

133

ba une capacité de 3, mais une taille de 0. Le fait d' ArrayListavoir une sorte de capacité tampon est un détail d'implémentation - il ne fait pas partie de l' Listinterface, donc Collections.copy(List, List)ne l'utilise pas. Ce serait moche pour cela à des cas spéciaux ArrayList.

Comme MrWiggles l'a indiqué, utiliser le constructeur ArrayList qui prend une collection est le moyen de le faire dans l'exemple fourni.

Pour des scénarios plus compliqués (qui peuvent inclure votre vrai code), vous pouvez trouver les collections de Guava utiles.


58

Faites simplement:

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

ArrayList a un constructeur qui acceptera une autre Collection pour copier les éléments depuis


7
comme quelqu'un ci-dessous le commente, il s'agit d'une copie superficielle. Sinon, cela aurait été une bonne réponse. Je suppose que j'aurais dû le préciser. Peu importe, je suis parti de toute façon.
Jasper Floor

11
Pour une liste de chaînes, la copie complète n'est pas importante car les Stringobjets sont immuables.
Derek Mahar

17

La réponse de Stephen Katulka (réponse acceptée) est fausse (la deuxième partie). Il explique que cela Collections.copy(b, a);fait une copie profonde, ce qui n'est pas le cas. Les deux, new ArrayList(a);et Collections.copy(b, a);ne font qu'une copie superficielle. La différence est que le constructeur alloue une nouvelle mémoire, et copy(...)non, ce qui le rend approprié dans les cas où vous pouvez réutiliser des tableaux, car il y a un avantage en termes de performances.

L'API standard Java tente de décourager l'utilisation de copies approfondies, car il serait mauvais que de nouveaux codeurs l'utilisent régulièrement, ce qui peut également être l'une des raisons pour lesquelles ce clone()n'est pas public par défaut.

Le code source de Collections.copy(...)peut être consulté sur la ligne 552 à l' adresse : 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 complète, vous devez parcourir les éléments manuellement, en utilisant une boucle for et clone () sur chaque objet.


12

la manière la plus simple de copier une liste est de la passer au constructeur de la nouvelle liste:

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

b sera une copie superficielle de a

En regardant la source de Collections.copy(List,List)(je ne l'avais jamais vu auparavant), il semble que ce soit pour faire face aux éléments index par index. utiliser List.set(int,E)ainsi l'élément 0 écrasera l'élément 0 dans la liste cible, etc. Pas particulièrement clair d'après 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'

pourquoi dites-vous copie «superficielle»? - me java noob
Martlark

4
Par «copie superficielle», il signifie qu'après la copie, les objets en b sont les mêmes objets qu'en a, et non des copies.
DJClayworth

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

Je suppose que je veux juste dire qu'il m'a fallu quelques regards pour voir ce que la fonction faisait réellement et je peux voir comment l'interrogateur s'est un peu confondu avec exactement ce qu'elle fait
Gareth Davis

je ne suis pas sûr que ce soit important? puisque String est immuable, seules les références ne sont pas les mêmes. cependant, même si vous essayez de muter un élément dans l'une ou l'autre liste, il ne mute jamais le même élément dans l'autre liste
David T.

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 dans lesquels il peut s'intégrer avant de devoir redimensionner). Une façon plus simple de copier dans ce cas est:

List b = new ArrayList(a);

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 ne fait que copier à nouveau. (Résultant en la copie se produisant 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 une référence aux éléments de la liste d'origine. (signifie que changer un élément de la deuxième liste ne devrait pas changer 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 d'origine et ne copiera pas. Vous devez avoir un cloneur de liste pour chaque objet lors de la copie.


5

Pourquoi n'utilisez-vous pas simplement la addAllméthode:

    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 que vous souhaitez suspendre certains éléments après lui, 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

3

Si vous souhaitez copier une ArrayList, copiez-la en utilisant:

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

List a = new ArrayList(b);

3

Les chaînes peuvent être copiées en profondeur avec

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

parce qu'ils sont immuables. Tous les autres objets non -> vous devez itérer et faire une copie par vous-même.


8
Il s'agit toujours d'une copie superficielle car chaque élément du tableau bpointe vers le même Stringobjet correspondant dans a. Cependant, ce n'est pas important car, comme vous le faites remarquer, les Stringobjets sont immuables.
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);
    }

4
Veuillez ajouter quelques explications à votre réponse
Sampada

1
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire sur la manière et / ou la raison pour laquelle il résout le problème améliorerait la valeur à long terme de la réponse.
Michael Parker

1

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

Pour éviter cette implémentation 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
Ne devrait-il pas être "userList2.add ((User) u.clone ());" ?
KrishPrabakar

1

La sortie suivante illustre les résultats de l'utilisation du constructeur de copie et de 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: Copie de la liste Java . Mais la sortie est suffisante pour voir comment se comporte java.util.Collections.copy ().


1

Et si vous utilisez google guava, la solution en une seule ligne serait

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

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


1

Avec Java 8 étant de type null-safe, vous pouvez utiliser le code suivant.

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

Ou en utilisant un collectionneur

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

0

La copie n'est pas inutile si vous imaginez le cas d'utilisation pour copier certaines valeurs dans une collection existante. Par exemple, vous souhaitez écraser les éléments existants au lieu de les insérer.

Un 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 l'index de début de la collection source et cible, ainsi qu'un paramètre pour le nombre.

Voir Java BUG 6350752


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.