Object. Практические Задания
Класс Object — корень иерархии классов Java: его методы equals(), hashCode() и toString() есть у любого объекта. По умолчанию equals() сравнивает ссылки, hashCode() не зависит от полей, а toString() возвращает строку вида Book@1b6d3586. Поэтому для классов-значений (точка, книга, деньги) эти три метода почти всегда переопределяют вместе.
На странице 3 практические задачи.
Что повторить перед решением
Задачи опираются на три урока: класс Object и его методы, метод equals() и его контракт, метод toString(). Краткая шпаргалка:
| Метод | Что делает реализация Object | Когда переопределять |
|---|---|---|
equals(Object o) | Сравнивает ссылки, как == | Объекты с одинаковыми данными должны считаться равными |
hashCode() | Возвращает число, не связанное с полями | Всегда вместе с equals(): равные объекты обязаны иметь равный хеш-код |
toString() | getClass().getName() + "@" + Integer.toHexString(hashCode()) | Почти всегда: для логов, отладки и вывода в консоль |
Задача 1. Методы Object по умолчанию
- Создайте класс
Bookс полямиtitle(String),author(String) иyear(int) и конструктором для всех трёх полей. МетодыObjectне переопределяйте. - Создайте два объекта с одинаковыми данными:
new Book("Война и мир", "Толстой", 1869). - Выведите на консоль для каждого объекта: сам объект через
System.out.println(book),book.hashCode()иbook.getClass().getName(). - Выведите результаты сравнений
book1 == book2иbook1.equals(book2). Затем создайте ссылкуBook book3 = book1;и сравнитеbook1сbook3обоими способами. - Письменно (в комментарии к коду) объясните каждую строку вывода: из чего состоит строка
Book@..., почемуequals()вернулfalseдля одинаковых книг иtrueдляbook3.
Подсказка: шестнадцатеричное число после @ в выводе println совпадает с Integer.toHexString(book.hashCode()). Проверьте это в коде.
Задача 2. Переопределение toString() и наследование
- Переопределите в
BookметодtoString()так, чтобы он возвращал строку видаBook{title='Война и мир', author='Толстой', year=1869}. Поставьте аннотацию@Override. - Создайте класс
EBook extends Bookс дополнительным полемfileSizeMb(double). ПереопределитеtoString(), используяsuper.toString(), чтобы не дублировать поля родителя. - Проверьте три места, где
toString()вызывается неявно:System.out.println(book), конкатенация"Книга: " + bookи выводList<Book>черезprintln. - Создайте массив
Book[]из трёх книг. Выведите его черезSystem.out.println(array)и черезArrays.toString(array). Объясните, почему результаты отличаются. - Присвойте переменной
Bookзначениеnullи сравните поведениеSystem.out.println(book)иbook.toString(). Затем выведитеString.valueOf(book)иObjects.toString(book, "книга не найдена").
Задача 3. equals() и оператор ==: класс Point
- Создайте класс
Pointс полямиxиy(int) и переопределитеequals(): точки равны, если совпадают обе координаты. Порядок проверок: та же ссылка,null, класс объекта, приведение типа, сравнение полей. - Напишите метод
main, который проверяет все пункты контрактаequals()и печатает результат каждой проверки:
а) рефлексивность:p.equals(p);
б) симметричность:p1.equals(p2)иp2.equals(p1);
в) транзитивность для трёх равных точек;
г) сравнение сnullвозвращаетfalseбез исключения;
д) сравнение с объектом другого класса, например со строкой"1,2", возвращаетfalse. - Добавьте в
PointполеString label, которое может бытьnull, и включите его в сравнение черезObjects.equals(label, other.label). Проверьте случаи, когдаlabelравенnullу одной и у обеих точек. - Сравните две строки:
String a = "java";иString b = new String("java");через==и черезequals(). Затем сравните два массиваint[]с одинаковым содержимым черезequals()и черезArrays.equals(). Объясните результаты.
Подсказка: если написать метод с сигнатурой equals(Point other), это будет перегрузка, а не переопределение: HashSet и List.contains() его не вызовут. Аннотация @Override сразу покажет такую ошибку при компиляции.
На чём чаще всего ошибаются
- Переопределили
equals(), но забылиhashCode(). Объекты перестают находиться вHashSetиHashMap, хотя вArrayListвсё работает. Эти два метода переопределяют только парой. - Параметр
equals(Point other)вместоequals(Object o). Получается перегрузка: коллекции вызывают версию изObjectи сравнивают ссылки. Защита — аннотация@Override. - Разный набор полей в
equals()иhashCode(). ЕслиhashCode()использует поле, которого нет вequals(), у равных объектов окажутся разные хеш-коды. Хешируйте те же поля, что сравниваете (или их подмножество). - Сравнение строк и полей-объектов через
==внутриequals(). Для ссылочных полей нуженequals(), а если поле может бытьnull—Objects.equals(). - Явный вызов
obj.toString()там, гдеobjможет бытьnull. ПолучитеNullPointerException;println(obj), конкатенация иString.valueOf(obj)безопасно выведутnull.
Перед отправкой решения сверьтесь с рекомендациями по оформлению кода на Java.
Часто задаваемые вопросы
Нужно ли переопределять equals() и hashCode() в каждом классе?
Нет. Их переопределяют в классах-значениях, которые сравнивают по данным и кладут в HashSet или используют ключами HashMap: точка, деньги, ISBN книги. Сервисам, контроллерам, потокам и другим объектам с «личностью» подходит сравнение по ссылке из Object. А вот toString() полезно переопределять почти везде — для логов.
Можно ли возвращать в hashCode() константу, например 42?
Формально да: контракт не нарушен, равные объекты получат равные хеш-коды. Но все объекты попадут в одну корзину, и HashMap превратится в медленный поиск перебором: поиск будет тем медленнее, чем больше элементов. Используйте Objects.hash() по тем же полям, что и в equals().
Все ли поля стоит выводить в toString()?
Нет. Не выводите пароли, токены и персональные данные: строка из toString() часто попадает в логи. Опасны и двусторонние связи: если Order выводит Customer, а тот — список своих Order, вызовы зациклятся и закончатся StackOverflowError. Для таких полей выводите только идентификатор.
Комментарии