Ссылочные типы и копирование объектов

Последнее обновление: 29.09.2025

При работе с объектами классов надо учитывать, что они все представляют ссылочные типы, то есть указывают на какой-то объект, расположенный в памяти. Чтобы понять возможные трудности, с которыми мы можем столкнуться, рассмотрим пример:

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 и его полях и хранятся.

Клонирование объектов в Java

Метод clone

Чтобы избежать этой проблемы, необходимо создать отдельный объект для переменной 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.

Неполное копирование объектов в Java

И в этом случае нам необходимо выполнить полное копирование или так называниемое "глубокое копирование" (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
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850