Тема 7. Класс Object и его методы - BelyiZ/JavaCourses GitHub Wiki
Содержание:
- Метод toString
- Методы hashCode и equals
- Метод getClass
- Методы wait и notify
- Метод finalize
- Метод clone
- Список литературы/курсов
В Java определен один специальный класс, называемый Object
(java.lang.Object
). Всего в Object
11 публичных
методов, из них 5 обычных и 6 с нативной реализацией. Хотя можно создать обычный класс, в котором явно не указан
класс-родитель, но фактически все классы Java наследуются от класса Object
. Все остальные классы, даже те, которые
добавляем в свой проект, являются неявно производными от класса Object
. Поэтому все типы и классы могут реализовать те
методы, которые определены в классе Object
. Таким образом все классы являются подклассами, производными от
класса Object
, даже если в объявлении это явно не указано.
В данной теме мы подробно рассмотрим описание методов toString()
, hashCode()
, equals()
,getClass()
. Остальные
методы будут представлены с целью ознакомления, некоторые из них будут более подробно описаны в следующих темах.
Метод toString
Данный метод возвращает строковое представление объекта, то есть позволяет получить текстовое описание любого объекта.
Пример:
public class Demo {
public static void main(String[] args) {
Person alex = new Person("Alex");
System.out.println(alex.toString()); // Будет выводить что-то вида Person@7960847b
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
Получаем значение Person@7960847b
. Из такого описания можно узнать класс объекта, у которого вызвали данный метод. А
также можно различать объекты – разным объектам соответствуют разные цифры, идущие после символа @, они генерируются
относительно хэшкода объекта.
return getClass().getName()+"@"+Integer.toHexString(hashCode());
Полезная особенность: При помощи полиморфизма данный метод можно переопределить в любом классе и возвращать более детальное описание объекта. Благодаря тому, что для каждого объекта можно получить его текстовое представление, можно, например, добавлять в лог более информативную запись.
Настоятельно рекомендуется переопределять метод toString()
в каждом созданном классе. Современные IDE позволяют
автоматически генерировать метод в пару кликов. При это обязательно учитывайте приватность данных. Персональные и
секретные данные не должны попадать в лог программы и другие системы открытого вида.
Пример:
public class Demo {
public static void main(String[] args) {
Person alex = new Person("Alex");
System.out.println(alex.toString()); // Person: Alex
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person: " + name;
}
}
Методы hashCode и equals
Метод equals
В Java при сравнении ссылочных переменных оператором "==" сравниваются не сами объекты, а ссылки на объекты.
Метод equals()
класса Object
сравнивает содержимое объектов и выводит значение типа boolean
. Значение true
-
если содержимое эквивалентно, и false
— если нет.
public boolean equals(Object obj)
указывает, равен ли какой-либо другой объект этому объекту.
Цель метода equals – определить идентичны ли объекты внутри, сравнив внутреннее содержание объектов.
Оператор ==
не рекомендуется для сравнения объектов в Java. При сравнении объектов, оператор "==" вернет true
лишь в
одном случае — когда ссылки указывают на один и тот же объект в куче. В данном случае не учитывается содержимое
переменных класса.
Но стандартная реализация equals()
в классе Object
как раз и основана на использовании оператора сравнения, так как
на этом уровне абстракции никакие другие проверки сделать нельзя. Именно поэтому настоятельно рекомендуется
переопределять метод equals()
во всех созданных классах. В отличие от toString()
, функционал метода сравнения
должен удовлетворять определенным правилам. Это обусловлено тем, что equals()
используется очень часто в работе
Java-приложений, а его неправильная реализация может привести к значительным проблемам в работе программы, сбоям или
неправильному поведению кода.
Вот эти правила сравнения двух объектов:
- Рефлексивность. Для любого ненулевого ссылочного значения
x
,x.equals(x)
должно возвращатьtrue
. - Симметричность. Для любых ненулевых ссылочных значений
x
иy
x.equals(y)
должен возвращатьtrue
, только еслиy.equals(x)
возвращаетtrue
. - Транзитивность. для любых ненулевых ссылочных значений
x
,y
иz
, еслиx.equals(y)
возвращаетtrue
иy.equals(z)
возвращаетtrue
, тогдаx.equals(z)
должен возвращатьtrue
. - Консистентность. Для любых ненулевых ссылочных значений
x
иy
множественные вызовыx.equals(y)
последовательно возвращаютtrue
или последовательно возвращаютfalse
при условии, что никакая информация, используемая в сравненияхequals()
на объектах, не изменяется. - Для любого ненулевого ссылочного значения
x
,x.equals(null)
должен возвращатьfalse
.
Пример:
public class Demo {
public static void main(String[] args) {
Person alex = new Person("Alex");
Person bob = new Person("Bob");
System.out.println(alex.equals(bob)); // false
Person alex2 = new Person("Alex");
System.out.println(alex.equals(alex2); // true
Person tom = null;
alex.equals(tom); // false
alex.equals(alex); // true
alex.equals("Alex"); //false
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person p))
return false;
return this.name.equals(p.name);
}
}
Метод equals
принимает в качестве параметра объект любого типа, который затем приводим к текущему, если они являются
объектами одного класса. Если объекты принадлежат к разным классам, то их сравнение не имеет смысла, и возвращается
значение false
.
Оператор instanceof
позволяет выяснить, является ли переданный в качестве параметра объект объектом определенного
класса, в данном случае класса Person
.
Затем сравниваем по именам. Если они совпадают, возвращаем true, что будет означать, что объекты равны.
Обратите внимание, что обычно необходимо переопределять метод hashCode()
всякий раз, когда метод equals()
переопределяется, чтобы поддерживать общий контракт для методов hashCode()
и equals()
, в котором говорится, что
равные объекты должны иметь одинаковые хэш-коды.
Метод hashCode
public int hashCode()
Метод hashCode()
для каждого объекта возвращает определенное число (хэш-код) - целочисленное значение соответствующее
внутреннему состоянию объекта. Какое именно – решает разработчик класса, как и в случае с методом equals
. По
умолчанию, хэш-код вычисляется на основе ссылки на объект в куче. Этот метод не менее важен для правильного
функционирования приложения, в частности поддерживается для использования хэш-таблиц.
Метод hashCode()
можно использовать для быстрого сравнения объектов, так как у метода equals()
есть большой минус –
он слишком медленно работает. Но, опираясь на правило, что у объектов эквивалентных по методу equals()
должен быть
одинаковый хэш-код, можно быстро понять, что объекты не эквивалентны, если хэш-код у них разный.
Предположим, что есть множество(Set
) из миллиона элементов. Задача - проверить, содержит ли оно
определенный объект или нет.
Не правильное решение - В цикле пройтись по всем элементам и сравнить нужный объект с каждым объектом множества.
Пока не найдем нужный. Даже в случае если его нет выполним миллион сравнений.
Правильное решение - применить метод hashCode()
. То есть вместо вызова метода equals()
у каждого объекта, мы
будем сравнивать их hashCode
. И только если хэш-коды равны, сравнивать уже посредством equals()
. Так в большинстве
случаев мы заменим тяжелую операцию на более простую. Похожий метод используется для ускорения алгоритмов поиска в
классах HashMap
и HashSet
.
Каждый раз когда вызывается метод hashCode()
для одного и того же объекта более одного раза во время выполнения
приложения Java, метод hashCode()
должен последовательно возвращать одно и то же целое число при условии, что никакая
информация, используемая в сравнениях equals для объекта, не изменяется. Это целое число не должно оставаться
согласованным между одним исполнением приложения и другим исполнением того же приложения. Если два объекта равны в
соответствии с методом equals()
, то вызов метода hashCode()
для каждого из двух объектов должен привести к одному и
тому же целочисленному результату.
Стоит обратить внимание, что не требуется, чтобы два неэквивалентных объекта генерировали разный хэш-код, равно как и одинаковый хэш-код у двух объектов не гарантирует их эквивалентность.
Выдача различных целочисленных результатов для неравных объектов может повысить производительность приложения.
Насколько это практически целесообразно, метод hashCode
, определенный классом Object
, возвращает разные целые числа
для разных объектов. Так если метод hashCode()
будет всегда возвращать одно и то же значение для всех объектов класса,
будет удовлетворять всем требованиям к этому методу, но может значительно замедлить работу приложения и резко увеличить
сложность выполнения отдельных операций. С другой стороны, как можно более распределенное присвоение хэш-кодов для
созданных неэквивалентных объектов может сильно упростить некоторые манипуляции и ускорить работу приложения.
Например, так может выглядеть hashCode()
в вышеопределенном классе:
class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person p))
return false;
return this.name.equals(p.name);
}
@Override
public int hashCode() {
return 10 * name.hashCode() + 20456;
}
}
Подведем итог. При реализации hashCode
необходимо помнить:
- у двух разных объектов может быть одинаковый хэш;
- у одинаковых объектов (с точки зрения
equals()
) должен быть одинаковый хэш; - алгоритм вычисления хэша должен быть выбран таким образом, чтобы не было большого количества различных объектов с одинаковым хэш-кодом;
- если переопределяем метод
equals()
, обязательно нужно переопределить методhashCode()
.
Метод getClass
Public final Class getClass()
Возвращает информацию о классе текущего объекта.
Так как в Java мы оперируем терминами ООП, все представлено в виде объектов. В том числе каждый класс - это тоже объект,
который содержит информацию о методах и полях этого класса. Возвращенный объект типа Class
, позволяет получить все
данные начиная с названия класса, заканчивая типами параметров каждого из методов. Но получить таким образом можно
только общую информацию о классе, его описание, никакие данные конкретных объектов этого класса так получить нельзя.
Метод getClass()
имеет модификатор final
и его нельзя переопределять.
Методы wait и notify
Теперь рассмотрим методы, которые были добавлены как часть механизма работы в многопоточной среде.
void wait()
– после вызова метода текущий поток прекращает выполнение операций и переходит в режим ожидания нотификации от другого потока.void wait(long миллисек)
– после вызова метода текущий поток прекращает выполнение операций и переходит в режим ожидания на указанное время.void wait(long миллисек,int наносек)
– после вызова метода текущий поток прекращает выполнение операций и переходит в режим ожидания на указанное время.void notify()
– Отправляет нотификацию о необходимости продолжить выполнение в указанный поток.void notifyAll()
– Отправляет нотификации о необходимости продолжить выполнение во все потоки.
Методы notify() и wait() имеют модификаторы final
и их нельзя переопределять.
Более подробно об этих методах мы поговорим в блоке про многопоточность.
Метод finalize
protected void finalize() throws Throwable
Вызывается сборщиком мусора на объекте, когда сборщик мусора определяет, что больше нет ссылок на объект. Можно
переопределить метод finalize()
для удаления системных ресурсов или для другой очистки.
Общий контракт finalize()
заключается в том, что он вызывается, если и когда виртуальная машина Java определила, что
больше нет никаких ссылок на текущий объект. Метод finalize()
может выполнять любые действия, включая повторное
предоставление этого объекта другим потокам; однако обычная цель finalize()
- выполнить действия по очистке до того,
как объект будет безвозвратно удален. Например, метод finalize()
для объекта, который представляет соединение
ввода-вывода, может разорвать соединение до того, как объект будет окончательно удален.
По умолчанию метод finalize()
в классе Object
не выполняет никаких действий. Подклассы Object могут переопределить
это поведение. Но переопределять этот метод не рекомендуется. Более подробно о финализации мы поговорим в блоке про
модель памяти в Java.
Метод clone
Метод clone()
создан для клонирования объекта – то есть создания его копии/дубликата.
Если его вызвать, то Java-машина создаст и вернет дубликат объекта, у которого вызвали этот метод. Клонирование объекта
в классе Object реализовано очень примитивно – при клонировании создается всего один новый объект: создается еще один
объект и его полям присваиваются значения полей объекта-образца. Если копируемый объект содержит ссылки на другие
объекты, то ссылки будут скопированы, дубликаты тех объектов не создаются. Java-машина не знает, какие объекты можно
клонировать, а какие нет. Файлы, например, клонировать нельзя. Как и поток System.in
.
Для реализации полноценного копирования объекта необходимо использовать специальный интерфейс Cloneable
. Если
разработчик класса считает, что объекты класса можно клонировать, он наследует свой класс от этого интерфейса.
Интерфейс Cloneable
- это так называемый интерфейс-маркер, который не содержит никаких методов. Он используется, чтобы
маркировать (помечать) некоторые классы. Далее следует переопределить метод clone()
и реализовать его так, как
необходимо для копируемого класса. При вызове метода clone()
, Java проверяет, реализует ли объект
интерфейс Cloneable
. Если да — клонирует объект методом clone()
, если нет — выкидывает
исключение CloneNotSupportedException
.