Як скопіювати список колекцій Java


141

У мене є ArrayListі я хочу його точно скопіювати. Я використовую класи корисних програм, коли це можливо, за умови, що хтось витратив деякий час, роблячи це правильно. Таким чином, природно, я закінчую Collectionsклас, який містить метод копіювання.

Припустимо, у мене є таке:

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 () говорить: "Список призначення повинен бути принаймні таким, як список джерел."
DJClayworth

21
Я не вважаю, що прийнята відповідь є правильною
Божо

3
Ви прийняли невірну відповідь, Джаспер Пол. Я щиро сподіваюся, що ви не використовували неправильну інформацію у своєму коді!
Малькольм

Відповіді:


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елементи 's, то він буде кидати an IndexOutOfBoundsException. Очікується, що ніякі асигнування не потребуватимуть Collections.copyроботи, і якщо такі є, то це викидає це виняток. Це оптимізація, щоб вимагати попередньо розподілити скопійований збірник ( b), але я, як правило, не вважаю, що ця функція варта цього через необхідні перевірки, надані альтернативами на основі конструктора, такими, як показано вище, які не мають дивних побічних ефектів.

Щоб створити глибоку копію, Listчерез будь-який механізм необхідно мати складні знання про базовий тип. У випадку Strings, які незмінні в Java (і .NET з цього приводу), вам навіть не потрібна глибока копія. У випадку з цим MySpecialObjectвам потрібно знати, як зробити глибоку його копію, і це не є загальною операцією.


Примітка. Спочатку прийнята відповідь була найкращим результатом Collections.copyв Google, і вона виявилася неправильною, як зазначено в коментарях.


1
@ncasas Так. Я нарікаю на те, що в Java немає загальної функції "копіювання". На практиці все часто я вважаю, що інші автори не впровадили клон () для своїх класів; він залишає одного без можливості робити будь-яку копію предмета. Або, що ще гірше, я бачу реалізований метод клонування з відсутністю або поганою документацією, що робить функцію клону непридатною (у надійному та практичному сенсі "знати, що відбувається").
Малькольм

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об'єкти незмінні.
Дерек Махар

17

Відповідь Стівена Катулки (прийнята відповідь) неправильна (друга частина). Він пояснює, що Collections.copy(b, a);робить глибоку копію, чого вона не робить. І те, new ArrayList(a);і Collections.copy(b, a);лише робиться неглибока копія. Різниця полягає в тому, що конструктор виділяє нову пам'ять, а copy(...)не робить її, що робить її придатною у випадках, коли ви можете повторно використовувати масиви, оскільки в ній є перевага для продуктивності.

Стандартний API Java намагається відмовити від використання глибоких копій, оскільки це було б погано, якщо нові кодери будуть користуватися цим регулярно, що також може бути однією з причин того clone(), що за замовчуванням не є загальнодоступним.

Вихідний код 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

Якщо вам потрібна глибока копія, ви повинні переглядати елементи вручну, використовуючи цикл і 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 java noob
Martlark

4
Під «дрібною копією» він означає, що після копіювання об’єкти в b є тими ж об’єктами, що і в a, а не їх копії.
DJClayworth

1
Явадок для Collections.copy () говорить: "Список призначення повинен бути принаймні таким, як список джерел."
DJClayworth

Я думаю, я просто маю на увазі, що мені знадобилося кілька поглядів, щоб побачити, що ця функція насправді робила, і я бачу, як запитуючий трохи заплутався в тому, що саме робить
Гарет Дейвіс,

я не впевнений, що це має значення? оскільки String є незмінним, лише посилання не однакові. однак, навіть якщо ви намагаєтесь мутувати елемент у будь-якому списку, він ніколи не мутує той самий елемент у іншому списку
David T.

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

не встановлює розмір. Він встановлює початкову ємність (будучи кількістю елементів, які вона може вмістити, перш ніж їй потрібно змінити розмір). Більш простий спосіб копіювання в цьому випадку:

List b = new ArrayList(a);

8

Як згадує hoijui. Обрана відповідь від Стівена Катулки містить коментар щодо Collections.copy, який є невірним. Автор, ймовірно, прийняв це, тому що перший рядок коду робив копію, яку він хотів. Додатковий дзвінок на Collections.copy просто копіюється знову. (У результаті копія відбувається двічі).

Ось код, щоб довести це.

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

Більшість відповідей тут не розуміють проблеми, користувач хоче мати КОПІЮВАННЯ елементів з першого списку до другого списку, елементи списку призначення - це нові об'єкти, а не посилання на елементи вихідного списку. (означає, що зміна елемента другого списку не повинна змінювати значення для відповідного елемента списку джерел.) Для об'єктів, що змінюються, ми не можемо використовувати конструктор 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об’єкти незмінні.
Дерек Махар

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
Хоча цей код може відповісти на питання, надаючи додатковий контекст щодо того, як та / або чому він вирішує проблему, покращить довгострокове значення відповіді.
Майкл Паркер

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 ((Користувач) 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

І якщо ви використовуєте google guava, рішенням в одному рядку було б

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

Це створює змінний екземпляр списку масивів.


1

Оскільки Java 8 є безпечним для нуля, ви можете використовувати наступний код.

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,4,4,4,] a.copy (b) = [1,2,3,4,5,3,3,3,3,4,4,4]

Однак я очікую методу копіювання, який би взяв додаткові параметри для початкового індексу збору джерела та цілі, а також параметр для підрахунку.

Дивіться Java BUG 6350752


-1

Щоб зрозуміти, чому Collections.copy () викидає IndexOutOfBoundsException, хоча ви зробили резервний масив списку призначення досить великим (через виклик size () у sourceList), дивіться відповідь Абхая Ядава в цьому пов'язаному питанні: Як це зробити скопіюйте java.util.List в інший java.util.List

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.