Методы класса Object
Каждый класс в Java наследует от Object несколько методов. Три из них приходится переопределять чаще всего: toString() превращает объект в текст, equals() сравнивает объекты, а hashCode() нужен коллекциям для быстрого поиска.
toString() по умолчанию
Когда объект печатают или склеивают со строкой, Java вызывает его метод toString(). Версия из Object выводит имя класса и шестнадцатеричное число:
class User {
String name = "Анна";
}
void main() {
IO.println(new User());
}Результат (число после @ при каждом запуске может быть другим):
Main$User@2698dc7Для человека такой текст бесполезен.
Свой toString()
Переопределите toString(), и объект будет печататься понятно — в том числе внутри списков и в отладчике:
class User {
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{name=" + name + ", age=" + age + "}";
}
}
void main() {
User user = new User("Анна", 28);
IO.println(user);
IO.println("Пользователь: " + user);
}Результат:
User{name=Анна, age=28}
Пользователь: User{name=Анна, age=28}== и equals()
Оператор == для объектов проверяет, одна ли это ссылка. Метод equals() из Object делает то же самое, поэтому два одинаковых по содержимому объекта считаются разными:
class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
void main() {
Point a = new Point(1, 2);
Point b = new Point(1, 2);
IO.println(a == b);
IO.println(a.equals(b));
}Результат:
false
falseСвой equals()
Чтобы сравнивать объекты по содержимому, переопределяют equals(). Метод принимает Object, поэтому сначала проверяют тип, а потом поля:
class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
return o instanceof Point p && x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
void main() {
Point a = new Point(1, 2);
Point b = new Point(1, 2);
IO.println(a == b);
IO.println(a.equals(b));
}Результат:
false
truehashCode()
hashCode() возвращает целое число — «отпечаток» объекта. Коллекции вроде HashSet и HashMap сначала сравнивают отпечатки и только потом вызывают equals(). Правило простое: равные по equals() объекты обязаны иметь одинаковый hashCode(). Удобнее всего собрать его из тех же полей через Objects.hash(), как в примере выше.
Частая ошибка
Если переопределить equals(), но забыть hashCode(), равные объекты получат разные отпечатки. Множество не найдёт элемент, который в нём есть:
class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
return o instanceof Point p && x == p.x && y == p.y;
}
}
void main() {
Set<Point> points = new HashSet<>();
points.add(new Point(1, 2));
IO.println(points.contains(new Point(1, 2)));
}Результат:
falseВсегда переопределяйте equals() и hashCode() вместе. Записи (record) из следующего урока делают это автоматически.