Equals, hashCode и toString
Три метода, которые описывают объект
Методы equals(), hashCode()
и toString() определены в Object,
поэтому они есть у любого объекта. Но поведение из Object
часто слишком общее для наших классов — и тогда методы переопределяют.
У каждого из них своя задача: equals() отвечает на вопрос
«считать ли два объекта равными», hashCode() даёт числовой хеш,
согласованный с этим равенством, а toString() формирует
текстовое представление объекта.
equals(): равенство по смыслу
Если класс не переопределяет equals(), используется реализация
из Object. Она считает два объекта равными только тогда,
когда обе ссылки указывают на один и тот же объект — по сути так же, как оператор
== для ссылок.
Point first = new Point(2, 3);
Point second = new Point(2, 3);
System.out.println(first == second); // false
System.out.println(first.equals(second)); // false
Но для класса Point может быть логично считать две точки
равными, если совпадают их координаты. Тогда это правило нужно явно выразить
в equals().
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Point other = (Point) obj;
return x == other.x && y == other.y;
}
Здесь сначала обрабатывается самый простой случай — сравнение объекта с самим собой.
Затем отбрасываются null и объект другого класса,
а после этого сравниваются поля, которые определяют равенство точек.
Корректный equals() должен соблюдать несколько правил:
- объект равен самому себе;
- если
a.equals(b)истинно, то иb.equals(a)должно быть истинно; - если
aравенb, аbравенc, тоaдолжен быть равенc; - результат не должен самопроизвольно меняться, пока данные, участвующие в сравнении, остаются прежними;
- любой ненулевой объект не равен
null.
hashCode(): равные объекты должны быть согласованы
hashCode() возвращает целое число — хеш объекта.
Главное правило здесь связано с equals():
если два объекта равны по equals(), их
hashCode() обязан вернуть одинаковое значение.
Обратное неверно: одинаковый хеш ещё не означает, что объекты равны.
Разные объекты вполне могут получить один и тот же hashCode().
@Override
public int hashCode() {
return java.util.Objects.hash(x, y);
}
Для вычисления хеша используются те же данные x и
y, по которым работает equals().
Так два равных объекта гарантированно получают одинаковый хеш.
equals() — переопредели и hashCode().
Иначе два объекта могут считаться равными, но иметь несогласованные хеши.
Это нарушает контракт этих методов и приводит к неправильному поведению кода,
который рассчитывает на их согласованность.
toString(): объект в читаемом виде
toString() возвращает строковое представление объекта.
Реализация из Object обычно даёт строку вида
Point@1a2b3c. Она полезна как техническое представление,
но почти ничего не говорит о содержимом объекта.
Поэтому для собственных классов toString() часто переопределяют,
включая в результат действительно полезные поля:
@Override
public String toString() {
return "Point{x=" + x + ", y=" + y + "}";
}
Теперь при выводе объекта можно сразу увидеть его состояние:
Point point = new Point(2, 3);
System.out.println(point); // Point{x=2, y=3}
Явно вызывать point.toString() здесь не обязательно:
при передаче объекта в System.out.println()
его строковое представление используется автоматически.
Формат toString() стоит делать коротким и понятным человеку.
Не следует воспринимать стандартную часть после @
как адрес объекта в памяти или как стабильный идентификатор.