Как скопировать список коллекций Java

142

У меня есть файл, 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
Вы приняли неправильный ответ, Джаспер Флор. Я искренне надеюсь, что вы не использовали неверную информацию в своем коде!
Малькольм

Ответы:

116

Вызов

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

Чтобы создать глубокую копию, с Listпомощью любого механизма нужно было бы хорошо знать базовый тип. В случае Strings, которые неизменяемы в Java (и .NET в этом отношении), вам даже не нужна глубокая копия. В случае MySpecialObject, вам нужно знать, как сделать его глубокую копию, и это не общая операция.


Примечание. Первоначально принятый ответ был лучшим результатом для Collections.copyGoogle, и он был совершенно неверным, как указано в комментариях.

Стивен Катулка
источник
1
@ncasas Да, это так. Я сожалею о том, что в Java нет универсальной функции «копирования». На практике часто я обнаруживаю, что другие авторы не реализовали clone () для своих классов; он оставляет человека без возможности делать какую-либо копию объекта. Или, что еще хуже, я вижу реализованный метод клонирования без документации или с плохой документацией, что делает функцию клонирования непригодной для использования (в надежном и практичном смысле «знание того, что происходит»).
Malcolm
133

bимеет емкость 3, но размер 0. Тот факт, что ArrayListесть какая-то буферная емкость, является деталью реализации - она ​​не является частью Listинтерфейса, поэтому Collections.copy(List, List)не использует ее. Это было бы некрасиво для частного случая ArrayList.

Как указал MrWiggles, использование конструктора ArrayList, который принимает коллекцию, является способом в приведенном примере.

Для более сложных сценариев (которые вполне могут включать ваш реальный код) вы можете найти коллекции в Guava полезными.

Джон Скит
источник
59

Просто делать:

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

ArrayList имеет конструктор, который примет другую коллекцию для копирования элементов из

tddmonkey
источник
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

Если вам нужна глубокая копия, вам нужно перебрать элементы вручную, используя цикл for и clone () для каждого объекта.

Hoijui
источник
12

Самый простой способ скопировать список - передать его конструктору нового списка:

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

b будет мелкой копией a

Глядя на источник Collections.copy(List,List)(я никогда его раньше не видел), похоже, он предназначен для копирования индекса элементов по индексу. использование List.set(int,E)таким образом элемента 0 приведет к перезаписи элемента 0 в целевом списке и т. д. Не особо ясно из документации, которую я должен был бы признать.

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'
Гарет Дэвис
источник
почему вы говорите «мелкая» копия? - я java noob
Martlark
4
Под «неглубокой копией» он подразумевает, что после копирования объекты в b являются теми же объектами, что и в a, а не их копиями.
DJClayworth,
1
Документ javadoc для Collections.copy () говорит: «Список назначения должен быть не меньше длины исходного списка».
DJClayworth,
Думаю, я просто имею в виду, что мне потребовалось несколько взглядов, чтобы увидеть, что на самом деле делает функция, и я вижу, как спрашивающий немного запутался в том, что именно она делает
Гарет Дэвис
я не уверен, что это важно? поскольку String неизменяем, только ссылки не совпадают. однако, даже если вы попытаетесь изменить элемент в любом списке, он никогда не изменит тот же элемент в другом списке
Дэвид Т.
9
List b = new ArrayList(a.size())

не устанавливает размер. Он устанавливает начальную емкость (сколько элементов может поместиться до того, как потребуется изменить размер). В этом случае более простой способ копирования:

List b = new ArrayList(a);
Cletus
источник
8

Как упоминает Хойджуи. Выбранный ответ Стивена Катулки содержит неверный комментарий о 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
Vin.X
источник
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);
    }
Raen K
источник
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 ((User) 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,3,4,4,4,] a.copy (b) = [1,2,3,4,5,3,3,3,3,4,4,4]

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

См. Ошибку Java 6350752

ordnungswidrig
источник
-1

Чтобы понять, почему Collections.copy () выдает исключение IndexOutOfBoundsException, хотя вы сделали резервный массив целевого списка достаточно большим (с помощью вызова size () в sourceList), см. Ответ Абхая Ядава в этом связанном вопросе: Как сделать скопируйте java.util.List в другой java.util.List

Volkerk
источник