W3docs

Класс Object в Java

Методы, которые каждый объект Java наследует от java.lang.Object — toString, equals, hashCode, getClass, clone и finalize.

Каждый класс в Java — независимо от того, пишете ли вы extends Object или нет — находится в иерархии типов под java.lang.Object. Это означает, что каждая ссылка, которую вы когда-либо держите, имеет небольшой набор доступных методов: toString, equals, hashCode, getClass и ещё несколько. Понимание того, что они делают по умолчанию и когда их следует переопределять, — это разница между объектами, которые нормально работают с коллекциями, отладчиками и фреймворками, и объектами, которые не работают.

Эта глава — карта территории. Следующие несколько глав подробно рассматривают конкретные методы (equals/hashCode, toString, clone) по одному за раз.

Неявное расширение

Напишите класс без клаузы extends:

public class Box {
  int contents;
}

Компилятор обрабатывает это так, как будто вы написали public class Box extends Object. Именно поэтому вы можете передать Box в любое место, объявленное как Object — каждый ссылочный тип в конечном итоге является Object.

Краткий обзор унаследованных методов

МетодЧто делает по умолчанию
toString()Возвращает ClassName@hashCodeHex — почти никогда не то, что нужно
equals(Object)Равенство ссылок (==)
hashCode()Значение, производное от идентичности объекта, а не его содержимого
getClass()Токен времени выполнения Class<?> — никогда не null
clone()Поверхностная копия поле за полем, но только для классов, реализующих Cloneable; иначе выбрасывает исключение
wait(), notify(), notifyAll()Низкоуровневые примитивы монитора, используемые с synchronized
finalize()Хук, вызываемый сборщиком мусора перед уничтожением объекта — устарел, не использовать

Первые три — toString, equals и hashCode — это те, которые почти каждый класс, несущий значения, должен переопределять, поскольку унаследованные версии описывают идентичность объекта, а не его содержимое. getClass вы вызываете на объектах, но никогда не переопределяете. Примитивы работы с потоками вы редко трогаете напрямую. clone и finalize лучше избегать в современном коде (clone рассматривается в отдельной главе; finalize заменяется try-with-resources и Cleaner).

Примечание
Если вы используете record (Java 16+), компилятор генерирует toString, equals и hashCode за вас из компонентов — все три основаны на содержимом, а не на идентичности. Это устраняет большую часть шаблонного кода, о котором рассказывает эта глава. См. Java Records.

toString — значение по умолчанию бесполезно

Унаследованный toString возвращает что-то вроде Box@1540e19d. Это имя класса и идентификационный хеш-код в шестнадцатеричном виде — бесполезно для логирования, отладки или любой другой диагностики:

Box b = new Box();
System.out.println(b);   // Box@1540e19d

Переопределите его, чтобы возвращать что-то читаемое — println, конкатенация строк, фреймворки логирования и отладчики IDE — все они вызывают toString за вас, поэтому хорошая реализация окупается повсюду:

@Override
public String toString() {
  return "Box[contents=" + contents + "]";
}

Следующая глава подробно рассматривает, как сделать это правильно.

equals и hashCode — они неразрывно связаны

Унаследованный equals возвращает true только тогда, когда обе ссылки указывают на один и тот же объект:

String a = new String("hi");
String b = new String("hi");
System.out.println(a == b);       // false — different objects
System.out.println(a.equals(b));  // true — String overrides equals

String переопределяет equals для сравнения содержимого. Класс, который вы пишете, этого не делает, если только вы сами не переопределите его. И как только вы переопределяете equals, вы должны также переопределить hashCode — иначе ваши объекты будут незаметно некорректно работать в HashSet и HashMap. Глава о equals и hashCode охватывает точные правила.

Внимание
Переопределите equals без переопределения hashCode, и два объекта, которые вы считаете равными, могут оказаться в разных хеш-бакетах — поэтому HashSet с радостью хранит оба, а map.get(key) возвращает null для ключа, который явно присутствует. Всегда переопределяйте их вместе и сохраняйте согласованность: равные объекты должны возвращать равные хеш-коды.

getClass — токен типа времени выполнения

getClass() возвращает объект Class<?>, описывающий тип времени выполнения:

Object o = "hello";
System.out.println(o.getClass().getName());  // java.lang.String

Он используется в рефлективном коде, реализациями equals, которым нужны сравнения точных типов, и в отладочном выводе. Обратите внимание, что getClass() является final — его нельзя переопределить.

clone и Cloneable

Object.clone() является protected и работает только на классах, которые явно объявляют implements Cloneable. Результатом является поверхностная копия: новый экземпляр с теми же значениями полей, включая те же ссылки на изменяемые подобъекты. Это заведомо неудобный API, подробно рассматриваемый в главе о клонировании.

wait / notify

Эти методы, вместе с notifyAll, являются частью оригинальных низкоуровневых примитивов параллелизма Java. Они привязаны к блокам synchronized и используются для построения паттернов ожидания условия. В современном коде предпочитайте высокоуровневые утилиты в java.util.concurrent (блокировки, BlockingQueue, CompletableFuture); необработанный wait/notify редко встречается за пределами внутренних компонентов библиотек.

finalize — не трогайте его

finalize() когда-то был способом, которым сборщик мусора давал объекту последний шанс освободить ресурсы. Он устарел начиная с Java 9 и удалён в более новых версиях. Используйте try-with-resources для ввода-вывода и java.lang.ref.Cleaner в очень редких случаях, когда необходимо выполнить код после сборки мусора.

Рабочий пример

java— editable, runs on the server

Что дальше

Наиболее распространённое — и наиболее подверженное ошибкам — переопределение метода Object — это equals в паре с hashCode. Следующая глава рассматривает контракт, который оба метода должны соблюдать, и паттерны для правильной реализации. Перейдите к Java equals и hashCode.

Практика

Практика
Что возвращает метод `toString()` по умолчанию, унаследованный от `Object`?
Что возвращает метод `toString()` по умолчанию, унаследованный от `Object`?
Was this page helpful?