Объекты Java
Класс и объект
Класс описывает структуру и поведение типа, объект — конкретный экземпляр этого класса.
final class Counter {
private int value;
void increment() {
value++;
}
int value() {
return value;
}
}
Counter first = new Counter();
Counter second = new Counter();first и second имеют одинаковый класс, но разную идентичность и независимое состояние.
У объекта удобно различать:
- идентичность — это тот же экземпляр или другой;
- состояние — значения полей;
- поведение — доступные операции и поддерживаемые инварианты.
Создание
Выражение new выделяет объект, инициализирует его поля и вызывает конструктор:
Order order = new Order(id, items);Переменная order хранит ссылку. Конкретное место объекта в памяти и способ представления ссылки не являются контрактом языка. Создание объекта завершается только после успешного конструктора; если конструктор выбросил исключение, вызывающий код не получает ссылку на полностью созданный экземпляр.
Ссылки и aliasing
List<String> left = new ArrayList<>();
List<String> right = left;
right.add("A");
System.out.println(left); // [A]Две ссылки указывают на один mutable-объект — это aliasing. Он нужен, но усложняет рассуждение о состоянии. Защита границ объекта:
final class Team {
private final List<String> members;
Team(List<String> members) {
this.members = List.copyOf(members);
}
List<String> members() {
return members;
}
}Копирование на входе не позволяет вызывающему коду изменить состояние через исходный список.
Идентичность и равенство
== сравнивает ссылки. equals может задавать логическое равенство:
record UserId(long value) {}
UserId a = new UserId(7);
UserId b = new UserId(7);
a == b; // false
a.equals(b); // trueДля entity идентичность обычно определяется стабильным идентификатором, для value object — совокупностью значимых полей. Контракт должен быть согласован с hashCode.
Жизненный цикл
Объект становится кандидатом на сборку мусора, когда до него нельзя добраться из GC roots по цепочке сильных ссылок. Сборка не означает немедленного удаления и не должна использоваться как механизм закрытия файлов, сокетов или соединений.
Ресурсный объект закрывают явно:
try (InputStream input = Files.newInputStream(path)) {
return input.readAllBytes();
}Неизменяемые объекты
Неизменяемый объект не меняет наблюдаемое состояние после создания. Для этого обычно:
- валидируют состояние в конструкторе;
- не предоставляют mutator-методы;
- не отдают mutable-внутренности;
- делают defensive copy;
- используют
finalдля полей и, при необходимости, класса.
Один final не обеспечивает глубокую неизменяемость.
Ловушки
- Путать ссылку и объект.
- Ожидать копию объекта при обычном присваивании.
- Сравнивать value objects через
==. - Отдавать наружу внутреннюю mutable-коллекцию.
- Полагаться на сборщик мусора для закрытия ресурсов.
- Вызывать переопределяемые методы из конструктора и публиковать частично созданный объект.
Вопросы на собеседовании
- Чем класс отличается от объекта?
- Что копируется при присваивании ссылочной переменной?
- Чем идентичность отличается от логического равенства?
- Когда объект становится достижимым для GC?
- Что нужно для практической неизменяемости класса?
- Почему
final-поле со списком ещё не делает объект immutable?
Связанные заметки
Основы ООП · Класс Object · Конструкторы Java · Equals и hashCode · Immutable классы и данные · Garbage collector