Класс Object

Роль

java.lang.Object — корень иерархии ссылочных типов Java. Если класс не указывает extends, он неявно наследует Object. Массивы тоже являются объектами; примитивы — нет.

Object text = "ready";
Object numbers = new int[] {1, 2, 3};

Переменная типа Object может ссылаться почти на любой объект, но через неё доступны только методы статического типа Object. Реальная переопределённая реализация выбирается полиморфно во время выполнения.

Методы

МетодНазначение и контракт
getClass()возвращает реальный runtime-класс объекта
equals(Object)определяет логическое равенство
hashCode()согласован с equals; нужен хеш-структурам
toString()текстовое представление для диагностики
clone()защищённый механизм поверхностного копирования; требует Cloneable, обычно неудобен
wait()освобождает монитор и ждёт сигнал/таймаут
notify()будит один ожидающий на том же мониторе поток
notifyAll()будит все ожидающие на том же мониторе потоки
finalize()устаревший и ненадёжный механизм финализации; не использовать для освобождения ресурсов

wait, notify и notifyAll нужно вызывать, владея монитором объекта, то есть внутри соответствующего synchronized. Ожидание всегда помещают в цикл: возможны ложные пробуждения, а условие мог изменить другой поток.

synchronized (monitor) {
    while (!ready) {
        monitor.wait();
    }
}

Для нового конкурентного кода чаще подходят блокировки и синхронизаторы из java.util.concurrent.

equals и hashCode

Реализация Object.equals сравнивает идентичность (this == other). Классы-значения обычно переопределяют метод:

record UserId(long value) {}
 
new UserId(7).equals(new UserId(7)); // true

Главный инвариант: если a.equals(b) равно true, то a.hashCode() == b.hashCode(). Обратное не обязательно — коллизии допустимы. Подробно: Equals и hashCode.

toString

Реализация по умолчанию строит строку из имени класса и хеша; это не адрес объекта. Для доменных типов полезен информативный toString, но в него нельзя бездумно включать пароли, токены и персональные данные.

Копирование и ресурсы

clone выполняет поверхностное копирование: ссылки внутри копии всё ещё указывают на те же вложенные объекты. Обычно яснее написать копирующий конструктор или фабрику.

finalize не гарантирует своевременный вызов. Файлы и соединения закрывают через AutoCloseable и try-with-resources, а не через сборщик мусора.

Ловушки

  • Переопределить equals, но оставить несовместимый hashCode.
  • Считать toString безопасным местом для любых данных.
  • Вызывать wait вне synchronized или проверять условие через if.
  • Считать clone глубоким копированием.
  • Пытаться вручную вызвать или полагаться на finalize.

Вопросы на собеседовании

  1. Какие типы не наследуют Object?
  2. Как связан контракт equals с hashCode?
  3. Почему wait вызывают в цикле и под монитором?
  4. Чем поверхностная копия отличается от глубокой?
  5. Почему finalize нельзя использовать для управления ресурсами?

Связанные заметки

Основы ООП · Объекты Java · Equals и hashCode · Переопределение Java · Garbage collector · Работа с исключениями в Java