Клонирование

1. Что такое клонирование?

Иногда необходимо на основе существующего объекта создать второй такой же - то есть создать его клон. Это процесс в Java называется клонированием.

Для клонирования объекта в Java можно воспользоваться тремя способами:

  1. Переопределение метода clone() и реализация интерфейса Cloneable().
  2. Использование конструктора копирования.
  3. Использовать для клонирования механизм сериализации.

2. Переопределение метода clone()

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

Object.clone() выбрасывает исключение CloneNotSupportedException при попытке клонировать объект не реализующий интерфейс Cloneable

Метод clone() в родительском классе Object является protected, поэтому желательно переопределить его как public. Реализация по умолчанию метода Object.clone() выполняет неполное/поверхностное (shallow) копирование. Рассмотрим пример:

Пример 1. Поверхностное клонирование

public class Car implements Cloneable {
    private String name;
    private Driver driver;

    public Car(String name, Driver driver) {
        this.name = name;
        this.driver = driver;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Driver getDriver() {
        return driver;
    }

    public void setDriver(Driver driver) {
        this.driver = driver;
    }

    @Override
    public Car clone() throws CloneNotSupportedException {
        return (Car) super.clone();
    }
}
public class Driver implements Cloneable {
    private String name;
    private int age;

    public Driver(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public Driver clone() throws CloneNotSupportedException {
        return (Driver) super.clone();
    }
}
public class CloneCarDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Car car = new Car("Грузовик", new Driver("Василий", 45));
        Car clonedCar = car.clone();
        System.out.println("Оригинал:\t" + car);
        System.out.println("Клон:   \t" + clonedCar);

        Driver clonedCarDriver = clonedCar.getDriver();
        clonedCarDriver.setName("Вася");

        System.out.println("Оригинал после изменения имени водителя:\t" + car);
        System.out.println("Клон после изменения имени водителя:   \t\t" + clonedCar);
    }
}

В этом примере клонируются объект класса Car. Клонирование выполняется поверхностное - новый объект clonedCar содержит ссылку на тот же объект класса Driver, что и объект car. Если вас это не устраивает, то необходимо самим написать "глубокое" клонирование - создать новый объект класса Driver. Перепишем метод clone() класса Car:

Пример 2. Глубокое клонирование

    @Override
    public Car clone() throws CloneNotSupportedException {
        Car newCar = (Car) super.clone();
        Driver driver = this.getDriver().clone();
        newCar.setDriver(driver);
        return newCar;
    }

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

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

Пример 3. Конструктор копирования с поверхностным клонированием

public class Car implements Cloneable {
    private String name;
    private Driver driver;

    public Car(String name, Driver driver) {
        this.name = name;
        this.driver = driver;
    }

    /**
     * Конструктор копирования.
     *
     * @param otherCar
     */
    public Car(Car otherCar) {
        this(otherCar.getName(), otherCar.getDriver());
    }
 
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Driver getDriver() {
        return driver;
    }

    public void setDriver(Driver driver) {
        this.driver = driver;
    }
}

Опять же  - пример показывает неглубокое клонирование. Перепишем конструктор для реализации "глубокого" копирования:

Пример 4.  Конструктор копирования с "глубоким" клонированием

    public Car(Car otherCar) throws CloneNotSupportedException {
        this(otherCar.getName(), otherCar.getDriver().clone());
    }
Read also:
Trustpilot
Trustpilot
Comments
mifmif
Jun 6, 2017
Paul
Mar 9, 2020
Где пример копирования с помощью сериализации? Ответ, мы ранее показывали как записать объект в файл, не пойдет. Покажите, как использовать ByteArrayOutputStream.
Lebed_Ev
May 16, 2022
В примере про поверхностное копирование добавьте, пожалуйста, вывод в консоль ссылку на объект Driver, иначе дальше текст становится не совсем понятен. Вроде говорится, что ссылка не поменялась, а в коде этого нет. К примеру, я как новичок, не сразу понял о чем речь, и пришлось копировать код в идею и проверять. Спасибо.