← Назад к вопросам

Какое копирование для коллекций будешь использовать?

1.3 Junior🔥 201 комментариев
#Коллекции

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Копирование коллекций в Java — выбор правильного подхода

Копирование коллекций — это фундаментальная операция в Java разработке. Выбор правильного метода критичен для производительности и корректности работы приложения. Рассмотрим различные способы и когда их использовать.

Поверхностное копирование (Shallow Copy)

Shallow Copy — копируются только ссылки на объекты, сами объекты не дублируются:

public class ShallowCopyExample {
    static class User {
        String name;
        Address address;

        User(String name, Address address) {
            this.name = name;
            this.address = address;
        }
    }

    static class Address {
        String city;
        Address(String city) {
            this.city = city;
        }
    }

    public static void main(String[] args) {
        // Способ 1: new ArrayList<>(originalList)
        List<User> originalUsers = new ArrayList<>();
        originalUsers.add(new User("John", new Address("New York")));

        List<User> shallowCopy1 = new ArrayList<>(originalUsers);

        // Способ 2: Collections.unmodifiableList (immutable view)
        List<User> shallowCopy2 = Collections.unmodifiableList(originalUsers);

        // Способ 3: Stream API
        List<User> shallowCopy3 = originalUsers.stream()
            .collect(Collectors.toList());

        // ВАЖНО: shallowCopy.get(0).address указывает на ТОТ ЖЕ объект Address!
        shallowCopy1.get(0).address.city = "Boston";
        System.out.println(originalUsers.get(0).address.city); // Boston!
    }
}

Когда использовать:

  • Объекты в коллекции immutable (String, Integer, LocalDate)
  • Не планируешь изменять вложенные объекты
  • Нужна максимальная производительность

Предостережение:

// ОПАСНО при изменении вложенных объектов
List<User> users = new ArrayList<>(originalUsers);
users.get(0).getAddress().setCity("Boston"); // Меняет оригинал!

Глубокое копирование (Deep Copy)

Deep Copy — рекурсивно копируются все объекты, включая вложенные:

public class DeepCopyExample {
    static class User implements Cloneable {
        private String name;
        private Address address;

        public User(String name, Address address) {
            this.name = name;
            this.address = address;
        }

        // Способ 1: Переопределение clone()
        @Override
        public User clone() {
            try {
                User cloned = (User) super.clone();
                // Важно: клонируем и вложенные объекты
                cloned.address = new Address(this.address.city);
                return cloned;
            } catch (CloneNotSupportedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    static class Address implements Cloneable {
        String city;

        Address(String city) {
            this.city = city;
        }

        @Override
        public Address clone() {
            try {
                return (Address) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        List<User> original = new ArrayList<>();
        original.add(new User("John", new Address("New York")));

        // Способ 1: Через clone()
        List<User> deepCopy1 = original.stream()
            .map(User::clone)
            .collect(Collectors.toList());

        // Способ 2: Через конструктор копирования
        List<User> deepCopy2 = original.stream()
            .map(user -> new User(
                user.name,
                new Address(user.address.city)
            ))
            .collect(Collectors.toList());

        // Теперь изменение безопасно
        deepCopy1.get(0).address.city = "Boston";
        System.out.println(original.get(0).address.city); // New York - не изменилось!
    }
}

Способ 2: Конструктор копирования (Copy Constructor)

Copy Constructor — самый читаемый и контролируемый способ:

public class CopyConstructorExample {
    static class User {
        private String name;
        private Address address;
        private List<String> tags;

        // Обычный конструктор
        public User(String name, Address address) {
            this.name = name;
            this.address = address;
            this.tags = new ArrayList<>();
        }

        // Copy Constructor - ЛУЧШИЙ ПОДХОД
        public User(User original) {
            this.name = original.name; // String immutable
            this.address = new Address(original.address); // Deep copy
            this.tags = new ArrayList<>(original.tags); // New list, same items
        }
    }

    static class Address {
        String city;
        String street;

        public Address(String city, String street) {
            this.city = city;
            this.street = street;
        }

        // Copy Constructor для Address
        public Address(Address original) {
            this.city = original.city;
            this.street = original.street;
        }
    }

    public static void main(String[] args) {
        User original = new User("John", new Address("New York", "5th Ave"));
        User copy = new User(original);

        // Безопасное копирование
        copy.address.city = "Boston";
        System.out.println(original.address.city); // New York
    }
}

Способ 3: Сериализация (Serialization)

Serialization — копирование через сериализацию в байты и десериализацию:

public class SerializationCopyExample {
    static class User implements Serializable {
        private static final long serialVersionUID = 1L;
        String name;
        Address address;

        User(String name, Address address) {
            this.name = name;
            this.address = address;
        }
    }

    static class Address implements Serializable {
        private static final long serialVersionUID = 1L;
        String city;

        Address(String city) {
            this.city = city;
        }
    }

    public static <T> T deepCopyBySerialization(T object) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            oos.close();

            ByteArrayInputStream bais = new ByteArrayInputStream(
                baos.toByteArray()
            );
            ObjectInputStream ois = new ObjectInputStream(bais);
            @SuppressWarnings("unchecked")
            T copy = (T) ois.readObject();
            ois.close();

            return copy;
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        List<User> original = new ArrayList<>();
        original.add(new User("John", new Address("New York")));

        // Deep copy через сериализацию
        List<User> copy = deepCopyBySerialization(original);

        copy.get(0).address.city = "Boston";
        System.out.println(original.get(0).address.city); // New York
    }
}

Преимущества:

  • Работает автоматически для сложных структур
  • Обрабатывает циклические ссылки

Недостатки:

  • Медленнее всех способов
  • Требует Serializable интерфейс
  • Проблемы с transient полями

Способ 4: JSON сериализация (Jackson, Gson)

JSON копирование — модерный подход с использованием JSON библиотек:

public class JsonCopyExample {
    static class User {
        String name;
        Address address;

        public User() {}

        public User(String name, Address address) {
            this.name = name;
            this.address = address;
        }

        // Getters/Setters
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public Address getAddress() { return address; }
        public void setAddress(Address address) { this.address = address; }
    }

    static class Address {
        String city;

        public Address() {}

        public Address(String city) {
            this.city = city;
        }

        public String getCity() { return city; }
        public void setCity(String city) { this.city = city; }
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();

        List<User> original = new ArrayList<>();
        original.add(new User("John", new Address("New York")));

        // Deep copy через JSON
        String json = mapper.writeValueAsString(original);
        List<User> copy = mapper.readValue(
            json,
            new TypeReference<List<User>>() {}
        );

        copy.get(0).address.city = "Boston";
        System.out.println(original.get(0).address.city); // New York
    }
}

Преимущества:

  • Простая и элегантная
  • Работает с любыми POJO
  • Игнорирует внутренние состояния

Недостатки:

  • Зависимость от JSON библиотеки
  • Производительность ниже чем clone()

Способ 5: Apache Commons Lang

SerializationUtils из Apache Commons Lang:

import org.apache.commons.lang3.SerializationUtils;

public class ApacheCommonsExample {
    static class User implements Serializable {
        private static final long serialVersionUID = 1L;
        String name;
        String city;
    }

    public static void main(String[] args) {
        List<User> original = new ArrayList<>();
        original.add(new User());

        // Deep copy в одну строку
        List<User> copy = SerializationUtils.clone(original);
    }
}

Способ 6: Stream.collect с Custom Collector

Контролируемое копирование через Collector:

public class CollectorCopyExample {
    public static <T> Collector<T, ?, List<T>> deepCopyList() {
        return Collectors.toCollection(() -> new ArrayList<>(100));
    }

    public static void main(String[] args) {
        List<String> original = Arrays.asList("a", "b", "c");
        List<String> copy = original.stream()
            .collect(deepCopyList());
    }
}

Сравнение методов

МетодСкоростьПростотаКонтрольСценарий использования
new ArrayList<>()Очень быстроОчень простоНизкийImmutable элементы
clone()БыстроСложноВысокийКонтроль над копированием
Copy ConstructorБыстроПростоОчень высокийРекомендуется
SerializationМедленноПростоСреднийLegacy код
JSONМедленноСреднеСреднийAPI интеграции
Apache CommonsБыстроОчень простоНизкийКогда нет времени

Лучшие практики

  1. Для production кода:

    // Реализуй Copy Constructor
    public User(User original) {
        this.name = original.name;
        this.address = new Address(original.address);
    }
    
  2. Для DTO/API моделей:

    // Используй Jackson/Gson
    List<UserDTO> copy = mapper.convertValue(original, 
        new TypeReference<List<UserDTO>>(){});
    
  3. Для простых коллекций immutable объектов:

    List<String> copy = new ArrayList<>(original);
    
  4. Избегай Cloneable, это уже deprecated подход

  5. Документируй, какой вид копирования использует твой метод

Какое копирование для коллекций будешь использовать? | PrepBro