Как правильно клонировать объект?

в 7:13, , рубрики: cloneable java, java

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

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

Теперь по порядку. Первый способ подразумевает, что вы будете использовать механизм так называемого «поверхностного клонирования» и сами позаботитесь о клонировании полей-объектов. Метод clone() в родительском классе Object является protected, поэтому требуется переопределение его с объявлением как public. Он возвращает экземпляр объекта с копированными полями-примитивами и ссылками. И получается что у оригинала и его клона поля-ссылки указывают на одни и те же объекты. Пример далее показывает, как одновременно меняется поле у оригинального объекта и клона.

public class CloneTest{
    static class Person implements Cloneable{
        String name;
        int age;
        Car car;
        Person(Car car,int age,String name) {
            this.car = car;
            this.age = age;
            this.name = name;
        }

        @Override
        public String toString() {
            return this.name+" {" +
                    "age=" + age +
                    ", car=" + car +
                    '}';
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    static class Car{
        public String color;

        Car(String color) {
            this.color = color;
        }

        @Override
        public String toString() {
            return "{" +
                    "color car='" + color + ''' +
                    '}';
        }
    }


    public static void main(String[] args) throws CloneNotSupportedException {
        Car car = new Car("Green");
        Person person=new Person(car,25,"Mike");

        Person clone = (Person) person.clone();
        System.out.println(person);
        System.out.println(clone);
        clone.name=new String("Ivan");
        clone.car.color="red";
        System.out.println(person);
        System.out.println(clone);
    }
}
	Вывод:
    	Mike {age=25, car={color car='Green'}}
	Mike {age=25, car={color car='Green'}}
	Mike {age=25, car={color car='red'}}
	Ivan {age=25, car={color car='red'}}

Из примера выше видно, что у клона и оригинала состояние одного из полей изменяется одновременно. Следующий способ заключается в использовании конструктора копирования:

public class Person {
        private int age;
        private String name;
        public Person(int age, String name){
            this.age=age;
            this.name=name;
        }
        // конструктор копии
        public Person(Person other) {
            this(other.getAge(), other.getName());
        }

        public int getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

        @Override
        public String toString() {
            return "Person{" +
                    "age=" + age +
                    ", name='" + name + ''' +
                    '}';
        }

        public static void main(String[] args) {
            Person original = new Person(18, "Grishka");
            Person clone = new Person(original);
            System.out.println(original);
            System.out.println(clone);
        }
}
	Вывод:
	Person{age=18, name='Grishka'}
	Person{age=18, name='Grishka'}

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

Но оба вышеуказанных способа полны потенциальных ошибок и по сути создают копию объекта. Наиболее удобным и гибким способом клонирования является механизм сериализации. Он заключается в сохранении объекта в поток байтов с последующей эксгумацией его от туда. Для примера пригласим кота Ваську, его ждёт пара опытов:

import java.io.*;

class Cat implements Serializable{
    private String name;
    private String color;
    private int age;

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + ''' +
                ", color='" + color + ''' +
                ", age=" + age +
                '}';
    }
}
public class BasketCats{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Cat vaska = new Cat("Vaska","Gray",4);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream ous = new ObjectOutputStream(baos);
        //сохраняем состояние кота Васьки в поток и закрываем его(поток)
        ous.writeObject(vaska);
        ous.close();
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        //создаём кота для опытов и инициализируем его состояние Васькиным
        Cat cloneVaska = (Cat)ois.readObject();
        System.out.println(vaska);
        System.out.println(cloneVaska);
        System.out.println("*********************************************");
        cloneVaska.setColor("Black");
        //Убеждаемся что у кота Васьки теперь есть клон, над которым можно ставить опыты без ущерба Василию
        System.out.println(vaska);
        System.out.println(cloneVaska);

    }
}
	Вывод:
    	Cat{name='Vaska', color='Gray', age=4}
	Cat{name='Vaska', color='Gray', age=4}
	*********************************************
	Cat{name='Vaska', color='Gray', age=4}
	Cat{name='Vaska', color='Black', age=4}

Ни один кот не пострадал в результате тестов, мы видим что Васька был сохранён в поток, из которого затем восстановили независимый клон. Если нет особой необходимости обработки полей во время клонирования объектов, то сериализация является наиболее предпочтительным вариантом для этих целей.

Автор: PikselNsk

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js