Как клонировать ArrayList, а также клонировать его содержимое? - PullRequest
246 голосов
/ 04 апреля 2009

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

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

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

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

Ответы [ 17 ]

186 голосов
/ 04 апреля 2009

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

185 голосов
/ 04 апреля 2009

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

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

135 голосов
/ 10 апреля 2012

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

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

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

С Effective Java 2nd Edition , Item 11: Разумное переопределение клона

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

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

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

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

36 голосов
/ 03 ноября 2015

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

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

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. Причина в том, что аргумент 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());
28 голосов
/ 18 февраля 2015

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

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

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

17 голосов
/ 07 августа 2009

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

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

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

Так, каково решение:

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

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

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

3 голосов
/ 10 июня 2016

Я нашел способ, вы можете использовать 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.

2 голосов
/ 04 апреля 2009

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

Редактировать : ... и это именно то, что делает код Вархана.

1 голос
/ 01 октября 2015

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

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<>();
    }
}
0 голосов
/ 24 ноября 2016

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

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

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

...