При работе с объектами классов надо учитывать, что они все представляют ссылочные типы, то есть указывают на какой-то объект, расположенный в памяти. Чтобы понять возможные трудности, с которыми мы можем столкнуться, рассмотрим пример:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom");
tom.print(); // Person Tom
Person bob = tom;
bob.name = "Bob";
tom.print(); // Person Bob
}
}
class Person{
String name;
Person(String name){ this.name = name; }
void print(){
System.out.println("Person " + name);
}
}
Здесь создаем два объекта Person и один присваиваем другому. Но, несмотря на то, что мы изменяем только объект bob, вместе с ним изменяется и объект tom. Потому что после присвоения они указывают на одну и ту же область в памяти, где собственно данные об объекте Person и его полях и хранятся.
Чтобы избежать этой проблемы, необходимо создать отдельный объект для переменной bob, например, с помощью метода clone:
class Person implements Cloneable{ // применяем интерфейс Cloneable
String name;
Person(String name){ this.name = name; }
void print(){
System.out.println("Person " + name);
}
public Person clone() throws CloneNotSupportedException{
return (Person) super.clone();
}
}
Для реализации клонирования класс Person должен применить интерфейс Cloneable, который определяет метод clone.
Реализация этого метода просто возвращает вызов метода clone для родительского класса - то есть класса Object с преобразованием к типу Person.
Кроме того, на случай если класс не поддерживает клонирование, метод должен выбрасывать исключение CloneNotSupportedException, что определяется с помощью оператора throws.
Затем с помощью вызова этого метода мы можем осуществить копирование:
public class Program{
public static void main(String[] args) {
try{
Person tom = new Person("Tom");
Person bob = tom.clone();
bob.name = "Bob";
tom.print(); // Person Tom
bob.print(); // Person Bob
}
catch(CloneNotSupportedException ex){
System.out.println("Clonable not implemented");
}
}
}
Однако выше описанный способ осуществляет неполное копирование (shallow copy) и подойдет, если клонируемый объект не содержит сложных объектов. Например, пусть класс Person имеет поле, которое хранит ссылку на объект Company - условно место работы человека:
class Company{
String name;
Company(String name){ this.name = name; }
}
class Person implements Cloneable{
String name;
Company company;
Person(String name, Company company){
this.name = name;
this.company = company;
}
void print(){
System.out.printf("Person %s works in %s\n", name, company.name);
}
public Person clone() throws CloneNotSupportedException{
return (Person) super.clone();
}
}
Класс Person по прежнему реализует метод clone(), то есть с копированием вроде не должно быть проблем:
public class Program{
public static void main(String[] args) {
try{
Company yandex = new Company("Yandex");
Person tom = new Person("Tom", yandex);
Person bob = tom.clone(); // копируем объект tom
bob.name = "Bob";
bob.company.name = "Google";
tom.print(); // Person Tom works in Google
bob.print(); // Person Bob works in Google
}
catch(CloneNotSupportedException ex){
System.out.println("Clonable not implemented");
}
}
}
Но посмотрим на консольный вывод программы:
Person Tom works in Google Person Bob works in Google
В этом случае, хотя переменные tom и bob будут указывать на разные объекты в памяти, но эти объекты при этом будут указывать на один объект Company.
И в этом случае нам необходимо выполнить полное копирование или так называниемое "глубокое копирование" (deep copy). Для этого надо определить метод клонирования и у класса Company:
class Company implements Cloneable{
String name;
Company(String name){ this.name = name; }
public Company clone() throws CloneNotSupportedException{
return (Company) super.clone();
}
}
И затем исправим метод clone() в классе Person следующим образом:
class Person implements Cloneable{
String name;
Company company;
Person(String name, Company company){
this.name = name;
this.company = company;
}
void print(){
System.out.printf("Person %s works in %s\n", name, company.name);
}
public Person clone() throws CloneNotSupportedException{
Person person = (Person) super.clone();
person.company =(Company) company.clone();
return person;
}
}
Код класса Program остается тем же:
public class Program{
public static void main(String[] args) {
try{
Company yandex = new Company("Yandex");
Person tom = new Person("Tom", yandex);
Person bob = tom.clone(); // копируем объект tom
bob.name = "Bob";
bob.company.name = "Google";
tom.print(); // Person Tom works in Google
bob.print(); // Person Bob works in Google
}
catch(CloneNotSupportedException ex){
System.out.println("Clonable not implemented");
}
}
}
Но теперь мы получим другой консольный вывод:
Person Tom works in Yandex Person Bob works in Google