Для клонирования объекта в Java можно пользоваться тремя способами:
- Переопределение метода clone() и реализация интерфейса Cloneable();
- Использование конструктора копирования;
- Использовать для клонирования механизм сериализации
Теперь по порядку. Первый способ подразумевает, что вы будете использовать механизм так называемого «поверхностного клонирования» и сами позаботитесь о клонировании полей-объектов. Метод 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