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


274

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

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

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

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


Это уже обсуждалось в вопросе о рекомендации утилиты 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()метод.


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 и использовать его. Таким образом, вы можете написать общий метод для клонирования.


Вы можете быть более конкретным с копировать все поля 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());

Видите ли вы какое-либо влияние на производительность, когда 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. Поведение этих операций будет неопределенным, если указанная коллекция будет изменена во время выполнения операции.


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


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 тоже.


1
Я не знаю, почему люди отвергли этот ответ. Другие ответы должны реализовать clone () или изменить их зависимости, чтобы включить новые библиотеки. Но в библиотеку JSon большинство проектов уже включены. Я проголосовал за это.
— Satish

1
@Satish Да, это единственный ответ, который помог мне, я не уверен, что не так с другими, но независимо от того, что я сделал, клонировал или использовал конструктор копирования, мой оригинальный список обновлялся, но так не происходит так что спасибо автору!
— Параг Павар

Что ж, это правда, что это не чистый ответ Java для знаний, а эффективное решение для быстрого решения этой проблемы
— marcRDZ

отличный хак, экономит много времени
— mxml

6

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

ArrayList<Dog> clonedList = new ArrayList<Dog>(name_of_arraylist_that_you_need_to_Clone);

2

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

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


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

Аккуратный и неприятный трюк! Одна потенциальная проблема: если originalсодержит объекты разных классов, я думаю, что cloneMethod.invokeпроизойдет сбой с исключением, когда он вызывается с неправильным видом объекта. Из-за этого может быть лучше получить определенный клон Methodдля каждого объекта. Или используйте метод клонирования Object(но поскольку этот метод защищен, в некоторых случаях он может потерпеть неудачу).
— Лий

Кроме того, я думаю, что было бы лучше добавить исключение времени выполнения в предложение catch, а не возвращать пустой список.
— Лий

1
List<Dog> dogs;
List<Dog> copiedDogs = dogs.stream().map(dog -> SerializationUtils.clone(dog)).Collectors.toList());

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


0

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

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

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


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 ....


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


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 (). Таким образом, любое изменение, внесенное в новый список, не повлияет на исходный список.


-1

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

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

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


3
не работает, если вы используете List <List <JsonObject >>, например, в моем случае
— djdance

Строки неизменны. Клонирование не имеет смысла, и в вашем примере arrayB и arrayA имеют одинаковые ссылки на объекты - это мелкая копия.
— Кристиан Фрис
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.