← Назад к вопросам

Как обойтись без копирования ссылочного поля

2.0 Middle🔥 161 комментариев
#ООП#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как обойтись без копирования ссылочного поля

Это вопрос о работе со ссылками на объекты и избежании полного копирования объектов в памяти. В Java есть несколько подходов: неглубокое копирование (shallow copy), использование неизменяемых объектов, делегирование и композиция.

Проблема

public class Person {
    private String name;
    private Address address;  // ссылочное поле
    
    // Плохо: глубокое копирование
    public Person(Person original) {
        this.name = original.name;
        this.address = new Address(original.address);  // Копируем объект
    }
}

Решение 1: Неглубокое копирование (Shallow Copy)

Просто скопируй ссылку, не сам объект:

public class Person {
    private String name;
    private Address address;
    
    // Конструктор копирования - неглубокое копирование
    public Person(Person original) {
        this.name = original.name;
        this.address = original.address;  // Копируем только ссылку
    }
    
    // Или через clone()
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();  // Неглубокое копирование
    }
}

// Использование
Person p1 = new Person("Alice", new Address("NYC"));
Person p2 = new Person(p1);  // Обе переменные указывают на один Address

p2.address.setCity("Boston");
System.out.println(p1.address.getCity());  // "Boston" - изменение видно везде!

Решение 2: Неизменяемые объекты (Immutable)

Лучший подход — сделать ссылочное поле неизменяемым:

// Неизменяемый Address
public final class Address {
    private final String city;
    private final String street;
    private final String zipCode;
    
    public Address(String city, String street, String zipCode) {
        this.city = city;
        this.street = street;
        this.zipCode = zipCode;
    }
    
    public String getCity() {
        return city;
    }
    
    // Никакие setter'ов - поле невозможно изменить
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Address)) return false;
        Address address = (Address) o;
        return Objects.equals(city, address.city) &&
               Objects.equals(street, address.street) &&
               Objects.equals(zipCode, address.zipCode);
    }
}

// Person с неизменяемым Address
public final class Person {
    private final String name;
    private final Address address;  // final
    
    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }
    
    // Copy constructor - безопасная копия без глубокого копирования
    public Person(Person original) {
        this.name = original.name;
        this.address = original.address;  // Безопасно копировать ссылку
    }
    
    public String getName() {
        return name;
    }
    
    public Address getAddress() {
        return address;
    }
}

// Использование
Address addr = new Address("NYC", "5th Ave", "10001");
Person p1 = new Person("Alice", addr);
Person p2 = new Person(p1);  // Безопасно - Address неизменяем

p1.getAddress();  // Никогда не изменится
p2.getAddress();  // Одинаковый объект, но это OK

Решение 3: Builder Pattern

Для создания объектов без копирования:

public class Person {
    private final String name;
    private final Address address;
    
    private Person(Builder builder) {
        this.name = builder.name;
        this.address = builder.address;
    }
    
    public static class Builder {
        private String name;
        private Address address;
        
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        
        public Builder address(Address address) {
            this.address = address;
            return this;
        }
        
        public Builder from(Person person) {
            this.name = person.name;
            this.address = person.address;
            return this;
        }
        
        public Person build() {
            return new Person(this);
        }
    }
}

// Использование
Person p1 = new Person.Builder()
    .name("Alice")
    .address(new Address("NYC", "5th Ave", "10001"))
    .build();

Person p2 = new Person.Builder()
    .from(p1)  // Копируем без глубокого копирования
    .name("Bob")  // Меняем только нужное поле
    .build();

Решение 4: Record (Java 16+)

Модерный способ с встроенной поддержкой неизменяемости:

public record Address(
    String city,
    String street,
    String zipCode
) {}

public record Person(
    String name,
    Address address
) {}

// Использование
Address addr = new Address("NYC", "5th Ave", "10001");
Person p1 = new Person("Alice", addr);
Person p2 = new Person(p1.name(), p1.address());  // Копия без глубокого копирования

// Или с методом copy
Person p3 = p1.withName("Bob");  // Java 16+ генерирует автоматически

Решение 5: Делегирование (Delegation)

Вместо копирования - делегируй операции:

public class PersonProxy {
    private final Person original;
    
    public PersonProxy(Person original) {
        this.original = original;  // Просто ссылка
    }
    
    public String getName() {
        return original.getName();
    }
    
    public Address getAddress() {
        return original.getAddress();
    }
    
    // Дополнительная логика
    public boolean leavesInUSA() {
        return original.getAddress().isUSA();
    }
}

// Использование
Person person = new Person("Alice", address);
PersonProxy proxy = new PersonProxy(person);  // Без копирования

Решение 6: Clone с неглубоким копированием

public class Person implements Cloneable {
    private String name;
    private Address address;
    
    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();  // Неглубокое копирование
    }
    
    // Или явный copy constructor
    public Person(Person original) {
        this.name = original.name;      // String копируется, но он immutable
        this.address = original.address; // Ссылка копируется, не объект
    }
}

Решение 7: Copy-on-Write Pattern

Для потокобезопасного доступа без копирования до момента изменения:

public class PersonCopyOnWrite {
    private volatile Address address;
    
    public synchronized void setAddress(Address newAddress) {
        this.address = newAddress;  // Копируем ссылку, не объект
    }
    
    public Address getAddress() {
        return address;  // Читаем без копирования
    }
    
    public void updateCity(String newCity) {
        // Копируем только при изменении
        Address current = this.address;
        Address updated = new Address(
            newCity,
            current.getStreet(),
            current.getZipCode()
        );
        setAddress(updated);
    }
}

Сравнение подходов

// 1. Неглубокое копирование - быстро, но опасно
person2 = new Person(person1);  // Изменение address влияет на обе переменные

// 2. Неизменяемые объекты - безопасно и быстро (рекомендуется)
person2 = new Person(person1);  // Безопасно копировать ссылку

// 3. Builder - гибко
person2 = new Builder().from(person1).name("Bob").build();

// 4. Record (Java 16+) - современно
person2 = person1;  // Безопасно, так как Record immutable

// 5. Глубокое копирование - медленно, но полностью независимо
person2 = person1.deepCopy();  // Новый объект Address

Best Practices

  1. Предпочитай неизменяемые объекты — это решает большинство проблем:
public final class Address {
    private final String city;  // final
    private final String street; // final
    // Никаких setter'ов
}
  1. Используй Record для данных (Java 16+):
public record Person(String name, Address address) {}
  1. Если нужна изменяемость, используй Builder для создания вариаций:
Person modified = new Builder().from(original).field(newValue).build();
  1. Избегай глубокого копирования без необходимости:
// ❌ Плохо
this.address = new Address(original.address);

// ✅ Хорошо
this.address = original.address;
  1. Тестируй shared references если используешь неглубокое копирование:
Person p1 = new Person("Alice", address);
Person p2 = new Person(p1);
address.setCity("Boston");
assertEquals(p1.getAddress().getCity(), "Boston");
assertEquals(p2.getAddress().getCity(), "Boston"); // Один объект!

Итоги

  • Неглубокое копирование — копируем ссылку, а не объект
  • Неизменяемые объекты — лучший способ сделать это безопасным
  • Record (Java 16+) — современный выбор
  • Builder — для создания вариаций объектов
  • Избегай глубокого копирования если не нужно полной независимости
  • Документируй семантику копирования — shallow vs deep
Как обойтись без копирования ссылочного поля | PrepBro