Як клонувати ArrayList, а також клонувати його вміст?


273

Як я можу клонувати, ArrayListа також клонувати її елементи на Java?

Наприклад, у мене є:

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

І я би сподівався, що об'єкти в clonedListсписку не такі, як у списку собак.


Про це вже йшлося у питанні про рекомендації утиліти Deep clone
Swiety

Відповіді:


199

Вам потрібно буде перебирати елементи та клонувати їх по черзі, вводячи клони в масив результатів під час проходження.

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. Вам доведеться переосмислити цей метод у вашому класі Собака та зробити ручне копіювання полів самостійно.
Хосе

3
Я кажу, створіть фабрику або будівельника, або навіть просто статичний метод, який візьме екземпляр Dog і вручну скопіює поля в новий екземпляр і поверне цей новий екземпляр.
Хосе

196

Я особисто додав би конструктор Собаці:

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. Я дійсно не розумію :(
Доктор aNdRO

Чи можна записати цю функцію для невизначеного Об'єкта (замість собаки)?
Тобі Г.

@TobiG. Я не розумію, що ти маєш на увазі. Ви хочете cloneList(List<Object>)чи Dog(Object)?
cdmckay

@cdmckay Одна функція, яка працює для cloneList (Список <Object>), cloneList (Список <Dog>) та cloneList (Список <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).


84
Наскільки мені відомо, конструктори копій стандартних колекцій створюють дрібну , а не глибоку копію. На поставлене тут запитання шукається глибока відповідь.
Абдул

19
це просто неправильно, копіюючі конвектори роблять дрібну копію - повний пункт питання
NimChimpsky

1
Що стосується цієї відповіді, це те, що якщо ви не мутуєте об’єкти зі списку, додавання або видалення елементів не видаляє їх з обох списків. Це не так дрібно, як просте завдання.
Номенон

42

Java 8 пропонує новий спосіб виклику конструктора копій або методу клонування на елементах собак елегантно і компактно: Потоки , лямбда і колектори .

Конструктор копіювання:

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с

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

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. Причина полягає в тому, що аргумент до 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)));
SaurabhJinturkar

1
@SaurabhJinturkar: Ваша версія не є безпечною для потоків і не повинна використовуватися з паралельними потоками. Це тому, що parallelвиклик робить clonedDogs.addвиклик з декількох потоків одночасно. Версії, які використовують, collectє безпечними для потоків. Це одна з переваг функціональної моделі бібліотеки потоків, той же код може використовуватися і для паралельних потоків.
Лій

1
@SaurabhJinturkar: Також операція збирання проходить швидко. Це робить майже те саме, що і ваша версія, але також працює для паралельних потоків. Ви можете виправити свою версію, використовуючи, наприклад, сумісну чергу замість списку масивів, але я майже впевнений, що це буде набагато повільніше.
Лій

Всякий раз , коли я намагаюся використовувати рішення , яке я отримую Unhandled exception type CloneNotSupportedExceptionна d.clone(). Оголошення винятку або виловлювання його не вирішує.
Густаво

@ Густаво: Це майже напевно, тому що об'єкт, який ви клонуєте, ( 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);

Примітка: Поведінка цих операцій буде невизначена, якщо вказана колекція буде змінена під час операції.


49
Будь ласка, не враховуйте, що всі ці 3 варіанти створюють лише дрібні копії списків
electrobabe

8
Це не глибокий клон, ці два списки зберігають однакові об'єкти, просто скопіювали посилання, але об'єкти Dog, як тільки ви змінили будь-який список, наступний список матиме ті ж зміни. donno y так багато відгуків.
Саорікідо

1
@ Neeson.Z Усі методи створюють глибоку копію списку та неглибокі копії елемента списку. Якщо ви змінюєте елемент списку, то зміна буде відображена іншим списком, але якщо ви зміните один із списку (наприклад, видалення об'єкта), інший список не зміниться.
Алессандро Теруцці

17

Я думаю, що нинішня зелена відповідь погана , чому ви можете запитати?

  • Це може зажадати додати багато коду
  • Він вимагає від вас перелічити всі списки, які потрібно скопіювати, і зробити це

Те, що серіалізація також поганий, вам, можливо, доведеться додавати Serializable в усьому місці.

Отже, яке рішення:

Бібліотека глибокого клонування Java Бібліотека клонування - це невелика ява-бібліотека з відкритим кодом (апаш-ліцензія), яка глибоко клонує об’єкти. Об'єкти не повинні реалізувати інтерфейс 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 вже включені. Я виступав за це.
Сатиш

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себе.

Редагувати : ... і саме це робить код Вархана.


1
І навіть якщо вони це роблять, немає іншого способу отримати доступ до клона (), крім рефлексії, і це все одно не гарантується.
Майкл Майерс

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? Дивіться історію випуску тут: commons.apache.org/proper/commons-lang/release-history.html
informatik01

0

Пакунок import org.apache.commons.lang.SerializationUtils;

Існує метод SerializationUtils.clone(Object);

Приклад

this.myObjectCloned = SerializationUtils.clone(this.object);

відповісти на це питання трохи застаріло. І багато інших відповідей у ​​коментарі, наведеному під питанням.
moskito-x

0

Я тільки що розробив lib, який здатний клонувати об'єкт сутності та об'єкт 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. Припустимо, що ви хочете скопіювати масив String ArrayListA.

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

Дайте мені знати, якщо це не працює для вас.


3
не працює, якщо ви використовуєте List <Список <JsonObject>>, наприклад, у моєму випадку
djdance

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