Как клонировать ArrayList, а также клонировать его содержимое?

274

Как я могу клонировать, ArrayListа также клонировать его элементы в Java?

Например у меня есть:

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = ....something to do with dogs....

И я ожидаю, что объекты clonedListне такие, как в списке собак.

palig
источник
Это уже обсуждалось в вопросе о рекомендации утилиты Deep clone
Swiety,

Ответы:

200

Вам нужно будет перебирать элементы и клонировать их один за другим, помещая клоны в массив результатов по мере продвижения.

public static List<Dog> cloneList(List<Dog> list) {
    List<Dog> clone = new ArrayList<Dog>(list.size());
    for (Dog item : list) clone.add(item.clone());
    return clone;
}

Очевидно, что для того, чтобы это работало, вам нужно будет заставить ваш Dogкласс реализовать Cloneableинтерфейс и переопределить clone()метод.

Varkhan
источник
19
Вы не можете сделать это в общем, хотя. clone () не является частью интерфейса Cloneable.
Майкл Майерс
13
Но clone () защищен в Object, поэтому вы не можете получить к нему доступ. Попробуйте скомпилировать этот код.
Майкл Майерс
5
Все классы расширяют Object, поэтому они могут переопределять clone (). Это то, что Cloneable для!
Stephan202
2
Это хороший ответ. Cloneable на самом деле интерфейс. Тем не менее, mmyers имеет смысл в том, что метод clone () является защищенным методом, объявленным в классе Object. Вы должны переопределить этот метод в своем классе Dog и выполнить ручное копирование полей самостоятельно.
Хосе
3
Я говорю, создайте фабрику или конструктор, или даже просто статический метод, который возьмет экземпляр Dog и вручную скопирует поля в новый экземпляр и вернет этот новый экземпляр.
Хосе
196

Лично я бы добавил конструктор в Dog:

class Dog
{
    public Dog()
    { ... } // Regular constructor

    public Dog(Dog dog) {
        // Copy all the fields of Dog.
    }
}

Затем просто повторяем (как показано в ответе Вархана):

public static List<Dog> cloneList(List<Dog> dogList) {
    List<Dog> clonedList = new ArrayList<Dog>(dogList.size());
    for (Dog dog : dogList) {
        clonedList.add(new Dog(dog));
    }
    return clonedList;
}

Я нахожу преимущество в том, что вам не нужно разбираться со сломанным Cloneable в Java. Это также соответствует тому, как вы копируете коллекции Java.

Другой вариант - написать свой собственный интерфейс ICloneable и использовать его. Таким образом, вы можете написать общий метод для клонирования.

cdmckay
источник
Вы можете быть более конкретным с копировать все поля DOG. Я действительно не понимаю :(
Доктор АНДРО
Можно ли написать эту функцию для неопределенного объекта (вместо собаки)?
Тоби Г.
@TobiG. Я не понимаю, что вы имеете в виду. Ты хочешь cloneList(List<Object>)или Dog(Object)?
cdmckay
@cdmckay Одна функция, которая работает для cloneList (List <Object>), cloneList (List <Dog>) и cloneList (List <Cat>). Но вы не можете назвать общий конструктор, я думаю ...?
Тоби Г.
@TobiG. Как общая функция клонирования тогда? Это не совсем то, о чем этот вопрос.
cdmckay
143

Все стандартные коллекции имеют конструкторы копирования. Используй их.

List<Double> original = // some list
List<Double> copy = new ArrayList<Double>(original); //This does a shallow copy

clone()был разработан с несколькими ошибками (см. этот вопрос ), поэтому лучше избегать этого.

Из Эффективной Java, 2-е издание , пункт 11: разумно переопределить клон

Учитывая все проблемы, связанные с Cloneable, можно с уверенностью сказать, что другие интерфейсы не должны расширять его, а классы, предназначенные для наследования (пункт 17), не должны его реализовывать. Из-за множества недостатков некоторые опытные программисты просто предпочитают никогда не переопределять метод клонирования и никогда не вызывать его, за исключением, возможно, копирования массивов. Если вы разрабатываете класс для наследования, имейте в виду, что, если вы решите не предоставлять хорошо защищенный метод клонирования, для подклассов будет невозможно реализовать Cloneable.

Эта книга также описывает множество преимуществ конструкторов копирования по сравнению с Cloneable / clone.

  • Они не полагаются на подверженный риску механизм создания внелингвистических объектов
  • Они не требуют невыполнимого соблюдения тонко документированных соглашений
  • Они не конфликтуют с правильным использованием полей
  • Они не выбрасывают ненужные проверенные исключения
  • Они не требуют приведения.

Рассмотрим еще одно преимущество использования конструкторов копирования. Предположим, у вас есть HashSet sи вы хотите скопировать его как TreeSet. Метод клон не может предложить такую функциональность, но это легко с помощью конструктора преобразования: new TreeSet(s).

Роза Перроне
источник
85
Насколько я знаю, конструкторы копий стандартных коллекций создают неглубокую копию, а не глубокую копию. Заданный здесь вопрос ищет подробный ответ.
Абдул
20
это просто неправильно, копирующие конструкторы делают поверхностную копию - главный смысл этого вопроса
NimChimpsky
1
Что правильно в этом ответе, так это то, что если вы не изменяете объекты в списке, добавление или удаление элементов не удаляет их из обоих списков. Это не так мелко, как простое задание.
Нумен
42

Java 8 предоставляет новый способ элегантного и компактного вызова конструктора копирования или метода клонирования на элементах Dogs: потоки , лямбды и коллекторы .

Скопируйте конструктор:

List<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toList());

Выражение Dog::newназывается ссылкой на метод . Он создает объект функции, который вызывает конструктор, Dogкоторый принимает в качестве аргумента другую собаку.

Метод клонирования [1]:

List<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toList());

Получение ArrayListв результате

Или, если вам нужно получить ArrayListобратно (если вы хотите изменить его позже):

ArrayList<Dog> clonedDogs = dogs.stream().map(Dog::new).collect(toCollection(ArrayList::new));

Обновите список на месте

Если вам не нужно сохранять исходное содержимое dogsсписка, вы можете вместо этого использовать replaceAllметод и обновить список на месте:

dogs.replaceAll(Dog::new);

Все примеры предполагаем import static java.util.stream.Collectors.*;.


Коллектор для ArrayListс

Сборщик из последнего примера может быть превращен в метод util. Поскольку это настолько распространенная вещь, мне лично нравится, чтобы она была короткой и красивой. Как это:

ArrayList<Dog> clonedDogs = dogs.stream().map(d -> d.clone()).collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

[1] Примечание к CloneNotSupportedException:

Чтобы это решение работало, cloneметод Dog не должен объявлять, что он выбрасывает CloneNotSupportedException. Причина в том, что аргумент to mapне может генерировать какие-либо проверенные исключения.

Как это:

    // Note: Method is public and returns Dog, not Object
    @Override
    public Dog clone() /* Note: No throws clause here */ { ...

Это не должно быть большой проблемой, так как в любом случае это лучшая практика. ( Effectice Java, например, дает этот совет.)

Спасибо Густаво за то, что заметил это.


PS:

Если вы найдете это красивее, вы можете использовать синтаксис ссылки на метод, чтобы сделать то же самое:

List<Dog> clonedDogs = dogs.stream().map(Dog::clone).collect(toList());
Lii
источник
Видите ли вы какое-либо влияние на производительность, когда Dog (d) является конструктором копирования? List<Dog> clonedDogs = new ArrayList<>(); dogs.stream().parallel().forEach(d -> clonedDogs.add(new Dog(d)));
СаурабДжинтуркар
1
@SaurabhJinturkar: Ваша версия не является поточно-ориентированной и не должна использоваться с параллельными потоками. Это потому, что parallelвызов делает clonedDogs.addвызов из нескольких потоков одновременно. Версии, которые используют, collectявляются потокобезопасными. Это одно из преимуществ функциональной модели потоковой библиотеки, один и тот же код можно использовать для параллельных потоков.
Лий
1
@SaurabhJinturkar: Операция сбора также быстрая. Он делает то же самое, что и ваша версия, но также работает для параллельных потоков. Вы можете исправить свою версию, используя, например, параллельную очередь вместо списка массивов, но я почти уверен, что это будет намного медленнее.
Лий
Всякий раз , когда я пытаюсь использовать решение , которое я получаю Unhandled exception type CloneNotSupportedExceptionна d.clone(). Объявление исключения или его отлов не решают проблему.
Густаво
@Gustavo: Это почти наверняка, потому что объект, который вы клонируете, ( Dogв этом примере) не поддерживает клонирование. Вы уверены, что он реализует Clonableинтерфейс?
Лий
28

В основном есть три способа без итерации вручную,

1 Использование конструктора

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>(dogs);

2 Использование addAll(Collection<? extends E> c)

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(dogs);

3 Использование addAll(int index, Collection<? extends E> c)метода с intпараметром

ArrayList<Dog> dogs = getDogs();
ArrayList<Dog> clonedList = new ArrayList<Dog>();
clonedList.addAll(0, dogs);

NB. Поведение этих операций будет неопределенным, если указанная коллекция будет изменена во время выполнения операции.

javatar
источник
51
пожалуйста, не делайте так, чтобы все эти 3 варианта создавали только мелкие копии списков
electrobabe
9
Это не глубокий клон, эти два списка сохраняют одни и те же объекты, просто скопированы только ссылки, но объекты Dog, после того, как вы изменили любой список, следующий список будет иметь те же изменения. Донно у тебя так много голосов.
Саорикидо
1
@ Neeson.Z Все методы создают полную копию списка и поверхностные копии элемента списка. Если вы измените элемент списка, это изменение будет отражено в другом списке, но если вы измените один из списков (например, удалив объект), другой список не изменится.
Алессандро Теруцци
17

Я думаю, что текущий зеленый ответ плохой , почему вы можете спросить?

  • Может потребоваться добавить много кода
  • Это требует, чтобы вы перечислили все списки для копирования и сделали это

Сериализация также плоха, но вам, возможно, придется добавить Serializable повсеместно.

Итак, каково решение:

Библиотека глубокого клонирования Java Библиотека клонирования - это небольшая библиотека Java с открытым исходным кодом (лицензия Apache), которая глубоко клонирует объекты. Объекты не должны реализовывать интерфейс Cloneable. По сути, эта библиотека может клонировать ЛЮБЫЕ объекты Java. Его можно использовать, например, в реализациях кэша, если вы не хотите, чтобы кэшированный объект был изменен или когда вы хотите создать глубокую копию объектов.

Cloner cloner=new Cloner();
XX clone = cloner.deepClone(someObjectOfTypeXX);

Проверьте это на https://github.com/kostaskougios/cloning

Cojones
источник
8
Одним из предостережений этого метода является то, что он использует отражение, которое может быть немного медленнее, чем решение Вархана.
cdmckay
6
Я не понимаю первый пункт "это требует много кода". Библиотеке, о которой вы говорите, потребуется больше кода. Вопрос только в том, где вы его разместите. В противном случае я согласен, что специальная библиотека для такого рода вещей помогает ..
nawfal
8

Я нашел способ, вы можете использовать json для сериализации / десериализации списка. Сериализованный список не содержит ссылки на исходный объект, если он не сериализован.

Используя gson:

List<CategoryModel> originalList = new ArrayList<>(); // add some items later
String listAsJson = gson.toJson(originalList);
List<CategoryModel> newList = new Gson().fromJson(listAsJson, new TypeToken<List<CategoryModel>>() {}.getType());

Вы можете сделать это, используя Джексона и любую другую библиотеку JSON тоже.

sagits
источник
1
Я не знаю, почему люди отвергли этот ответ. Другие ответы должны реализовать clone () или изменить их зависимости, чтобы включить новые библиотеки. Но в библиотеку JSon большинство проектов уже включены. Я проголосовал за это.
Satish
1
@Satish Да, это единственный ответ, который помог мне, я не уверен, что не так с другими, но независимо от того, что я сделал, клонировал или использовал конструктор копирования, мой оригинальный список обновлялся, но так не происходит так что спасибо автору!
Параг Павар
Что ж, это правда, что это не чистый ответ Java для знаний, а эффективное решение для быстрого решения этой проблемы
marcRDZ
отличный хак, экономит много времени
mxml
6

Я использовал эту опцию всегда:

ArrayList<Dog> clonedList = new ArrayList<Dog>(name_of_arraylist_that_you_need_to_Clone);
besartm
источник
2

Вам нужно будет клонировать ArrayListобъект вручную (перебрав его и скопировав каждый элемент в новый ArrayList), потому что clone()он не сделает это за вас. Причиной этого является то, что объекты, содержащиеся в, ArrayListмогут не реализовывать Clonableсами.

Изменить : ... и это именно то, что делает код Varkhan.

Stephan202
источник
1
И даже если они это сделают, нет никакого способа получить доступ к clone (), кроме отражения, и в любом случае это не гарантирует успеха.
Майкл Майерс
1

Гадкий способ сделать это с помощью отражения. Нечто подобное сработало для меня.

public static <T extends Cloneable> List<T> deepCloneList(List<T> original) {
    if (original == null || original.size() < 1) {
        return new ArrayList<>();
    }

    try {
        int originalSize = original.size();
        Method cloneMethod = original.get(0).getClass().getDeclaredMethod("clone");
        List<T> clonedList = new ArrayList<>();

        // noinspection ForLoopReplaceableByForEach
        for (int i = 0; i < originalSize; i++) {
            // noinspection unchecked
            clonedList.add((T) cloneMethod.invoke(original.get(i)));
        }
        return clonedList;
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        System.err.println("Couldn't clone list due to " + e.getMessage());
        return new ArrayList<>();
    }
}
milosmns
источник
Аккуратный и неприятный трюк! Одна потенциальная проблема: если originalсодержит объекты разных классов, я думаю, что cloneMethod.invokeпроизойдет сбой с исключением, когда он вызывается с неправильным видом объекта. Из-за этого может быть лучше получить определенный клон Methodдля каждого объекта. Или используйте метод клонирования Object(но поскольку этот метод защищен, в некоторых случаях он может потерпеть неудачу).
Лий
Кроме того, я думаю, что было бы лучше добавить исключение времени выполнения в предложение catch, а не возвращать пустой список.
Лий
1
List<Dog> dogs;
List<Dog> copiedDogs = dogs.stream().map(dog -> SerializationUtils.clone(dog)).Collectors.toList());

Это будет глубоко копировать каждую собаку

Раджу К
источник
0

Остальные постеры верны: вам нужно перебрать список и скопировать в новый список.

Однако ... Если объекты в списке неизменны - вам не нужно их клонировать. Если у вашего объекта есть сложный граф объектов - они также должны быть неизменными.

Другое преимущество неизменности заключается в том, что они также безопасны для потоков.

Fortyrunner
источник
0

Вот решение с использованием универсального типа шаблона:

public static <T> List<T> copyList(List<T> source) {
    List<T> dest = new ArrayList<T>();
    for (T item : source) { dest.add(item); }
    return dest;
}
Эндрю Койт
источник
Обобщения хороши, но вам также нужно клонировать элементы, чтобы ответить на вопрос. См stackoverflow.com/a/715660/80425
Дэвид Snabel-Caunt
0

для вас объекты переопределяют метод clone ()

class You_class {

    int a;

    @Override
    public You_class clone() {
        You_class you_class = new You_class();
        you_class.a = this.a;
        return you_class;
    }
}

и вызовите .clone () для Vector obj или ArraiList obj ....

RN3KK Ник
источник
0

Простой способ с помощью commons-lang-2.3.jar этой библиотеки Java для клонирования списка

ссылка для скачивания commons-lang-2.3.jar

Как пользоваться

oldList.........
List<YourObject> newList = new ArrayList<YourObject>();
foreach(YourObject obj : oldList){
   newList.add((YourObject)SerializationUtils.clone(obj));
}

Я надеюсь, что это может быть полезным.

: D

sonida
источник
1
Просто примечание: почему такая старая версия Commons Lang? С историей выпуска можно ознакомиться
informatik01
0

Посылка import org.apache.commons.lang.SerializationUtils;

Есть метод SerializationUtils.clone(Object);

пример

this.myObjectCloned = SerializationUtils.clone(this.object);
Пачеко
источник
это немного устарело, чтобы ответить на этот вопрос. И многие другие ответы в комментариях внизу под вопросом.
moskito-x
0

Я только что разработал библиотеку, которая может клонировать объект сущности и объект java.util.List. Просто скачайте флягу в https://drive.google.com/open?id=0B69Sui5ah93EUTloSktFUkctN0U и используйте статический метод cloneListObject (список списка). Этот метод не только клонирует список, но и все элементы сущности.

Эдуардо де Мело
источник
0

Ниже работал для меня ..

в Dog.java

public Class Dog{

private String a,b;

public Dog(){} //no args constructor

public Dog(Dog d){ // copy constructor
   this.a=d.a;
   this.b=d.b;
}

}

 -------------------------

 private List<Dog> createCopy(List<Dog> dogs) {
 List<Dog> newDogsList= new ArrayList<>();
 if (CollectionUtils.isNotEmpty(dogs)) {
 dogs.stream().forEach(dog-> newDogsList.add((Dog) SerializationUtils.clone(dog)));
 }
 return newDogsList;
 }

Здесь новый список, созданный методом createCopy, создается через SerializationUtils.clone (). Таким образом, любое изменение, внесенное в новый список, не повлияет на исходный список.

gayu312
источник
-1

Я думаю, что нашел действительно простой способ сделать глубокую копию ArrayList. Предполагая, что вы хотите скопировать массив ArringList StringA.

ArrayList<String>arrayB = new ArrayList<String>();
arrayB.addAll(arrayA);

Дайте мне знать, если это не сработает для вас.

jordanrh
источник
3
не работает, если вы используете List <List <JsonObject >>, например, в моем случае
djdance
Строки неизменны. Клонирование не имеет смысла, и в вашем примере arrayB и arrayA имеют одинаковые ссылки на объекты - это мелкая копия.
Кристиан Фрис