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