Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Методы класса 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
true

hashCode()

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) из следующего урока делают это автоматически.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.