equals() и hashCode()
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 42 из 87.
При сравнении объектов в Java оператор == проверяет, указывают ли две ссылки на одну и ту же область памяти. Чтобы сравнить фактическое содержимое объектов, вам необходимо переопределить метод equals().
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
}Метод equals() следует стандартному шаблону: сначала проверьте, является ли это тем же самым объектом, затем проверьте тип и, наконец, сравните соответствующие поля.
Всякий раз, когда вы переопределяете equals(), вы также должны переопределить hashCode(). Это контракт в Java: равные объекты должны иметь одинаковый хэш-код. Хэш-коллекции, такие как HashMap и HashSet, полагаются на это правило для правильной работы.
@Override
public int hashCode() {
return Objects.hash(name, age);
}Вспомогательный метод Objects.hash() генерирует хеш-код на основе тех же полей, которые используются в equals(). Если два объекта Person имеют одинаковые name и age, они будут считаться равными и будут генерировать идентичные хеш-коды, что обеспечивает согласованное поведение в коллекциях.
Задание
ЛегкоДавайте создадим систему учета студентов, которая правильно реализует равенство объектов. Вы создадите класс Student, в котором два студента считаются равными, если у них одинаковый идентификатор студента и имя — независимо от того, являются ли они одним и тем же объектом в памяти.
Вы организуете свой код в двух файлах:
Student.java: Создайте класс, представляющий студента с тремя приватными полями:studentId(String),name(String) иgpa(double). Включите конструктор для инициализации всех полей и методы-геттеры для каждого поля.Переопределите метод
equals()так, чтобы два объекта Student считались равными, если у них одинаковыйstudentIdИ одинаковоеname. Следуйте стандартному шаблону: проверьте на идентичность ссылок, проверьте на null и тип, затем сравните соответствующие поля.Переопределите метод
hashCode(), используяObjects.hash()с теми же полями, которые использовались вequals(). Не забудьте импортироватьjava.util.Objectsв начале вашего файла.Main.java: Оживите ваш класс Student, сравнивая объекты студентов. Вы получите шесть входных данных, представляющих двух студентов: ID, имя и GPA первого студента, а затем ID, имя и GPA второго студента.Создайте оба объекта Student, затем выполните три сравнения и выведите результаты:
- Выведите
Same reference:, а затем результат проверки, равен ли первый студент самому себе (всегда должно быть true) - Выведите
Content equality:, а затем результат проверки, равен ли первый студент второму (на основе вашей реализации equals) - Выведите
Hash codes match:, а затем результат проверки, совпадают ли хэш-коды обоих студентов
- Выведите
Вы получите шесть входных данных: ID первого студента (String), имя первого студента (String), GPA первого студента (double), ID второго студента (String), имя второго студента (String) и GPA второго студента (double).
Обратите внимание, что когда у двух студентов совпадают ID и имена, и equals() возвращает true, И их хэш-коды совпадают — это контракт, который вы должны соблюдать!
Шпаргалка
Оператор == проверяет, указывают ли две ссылки на одну и ту же область памяти, а не имеют ли объекты одинаковое содержимое.
Чтобы сравнить содержимое объектов, переопределите метод equals(), следуя этому шаблону:
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // Та же ссылка
if (obj == null || getClass() != obj.getClass()) return false; // Null или другой тип
Person person = (Person) obj; // Приведение к нужному типу
return age == person.age && name.equals(person.name); // Сравнение полей
}Всякий раз, когда вы переопределяете equals(), вы также должны переопределить hashCode(). Это контракт Java: равные объекты должны иметь одинаковый хэш-код.
@Override
public int hashCode() {
return Objects.hash(name, age);
}Используйте Objects.hash() с теми же полями, которые используются в equals(). Это гарантирует правильную работу хэш-коллекций, таких как HashMap и HashSet.
Попробуйте сами
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Чтение данных первого студента
String id1 = scanner.nextLine();
String name1 = scanner.nextLine();
double gpa1 = scanner.nextDouble();
scanner.nextLine(); // поглощение символа новой строки
// Чтение данных второго студента
String id2 = scanner.nextLine();
String name2 = scanner.nextLine();
double gpa2 = scanner.nextDouble();
// TODO: Создайте два объекта Student, используя входные данные
// TODO: Выведите "Same reference: ", а затем результат сравнения student1 с самим собой
// TODO: Выведите "Content equality: ", а затем результат сравнения student1 с student2
// TODO: Выведите "Hash codes match: ", а затем результат проверки, имеют ли оба студента одинаковый хэш-код
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»