Як правильно клонувати об'єкт?

Для клонування об'єкта в 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(other Person) {
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 підрозділах = new ObjectOutputStream(baos);
//зберігаємо стан кота Васьки в потік і закриваємо його(потік)
підрозділах.writeObject(vaska);
підрозділах.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}

Жоден кіт не постраждав в результаті тестів, ми бачимо що Васька був збережений в потік, з якого потім відновили незалежний клон. Якщо немає особливої необхідності обробки полів під час клонування об'єктів, то серіалізація є найбільш кращим варіантом для цих цілей.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.