Вопрос-ответ

How to clone ArrayList and also clone its contents?

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

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

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

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

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

Переведено автоматически
Ответ 1

Лично я бы добавил конструктор в 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;
}

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

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

Ответ 2

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

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() метод.

Ответ 3

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

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

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

Из Effective Java 2nd Edition, пункт 11: Разумное переопределение клонирования


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


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


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

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

  • Они не конфликтуют с правильным использованием конечных полей

  • Они не выдают ненужных проверяемых исключений

  • Они не требуют приведений.

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

Ответ 4

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

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

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

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

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

List<Dog> clonedDogs = dogs.stream().map(Dog::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.*;.


Сборщик для ArrayLists

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

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

2023-11-21 16:53 java collections