JavaRush /Курсы /Java Syntax Pro /Сравнение объектов

Сравнение объектов

Java Syntax Pro
10 уровень , 4 лекция
Открыта

1. Сравнение объектов в Java

Объекты в Java можно сравнивать как по ссылке, так и по значению.

Сравнение ссылок

Если две переменные указывают на один и тот же объект в памяти, то ссылки, которые хранятся в этих переменных равны. Если сравнить такие переменные с помощью оператора равенства ==, вы получите true, что логично. Тут все просто.

Код Вывод на экран
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

Сравнение по значению

Однако часто можно встретить ситуацию, когда две переменные ссылаются на два разных, но идентичных объекта. Например, две строки, которые содержат одинаковый текст, но находятся в различных объектах.

Для определения идентичности разных объектов нужно использовать метод equals() Пример:

Код Вывод на экран
String a = new String("Привет");
String b = new String("Привет");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

Метод equals есть не только у класса String — он есть у вообще всех классов.

Даже у тех, которые вы только будете писать, и вот почему.



2. Класс Object

Все классы в Java считаются унаследованными от класса Object. Это создатели Java так придумали.

А если некий класс унаследован от класса Object, в этом классе-наследнике появляются все методы класса Object. Это и есть главный эффект наследования.

Другими словами, у каждого класса, даже если это не написано в его коде, есть все методы, которые есть у класса Object.

А среди таких методов есть методы, которые имеют отношение к сравнению объектов. Это метод equals() и метод hashCode().

Код Как будет на самом деле:
class Person
{
   String name;
   int age;
}
class Person extends Object
{
   String name;
   int age;

   public boolean equals(Object obj) { return this == obj; } public int hashCode() { return адрес_объекта_в_памяти; //это дефолтная реализация, но может быть и другая }
}

В примере выше мы создали простой класс Person с параметрами name и age, без единого метода. Однако, т.к. все классы считаются унаследованными от класса Object, у класса Person скрытно появились два метода:

Метод Описание
boolean equals(Object obj)
Сравнивает текущий объект и переданный объект
int hashCode()
Возвращает hash-code текущего объекта

Получается, что методы equals есть у абсолютно всех объектов и можно сравнивать между собой объекты разных типов, и это все будет отлично компилироваться и работать.

Код Вывод на экран
Integer a = 5;
String s = "Привет";
System.out.println(a.equals(s));
System.out.println(s.equals(a));


false
false
Object a = new Integer(5);
Object b = new Integer(5);
System.out.println(a.equals(b)) ;


true

3. Метод equals()

Унаследованный от класса Object метод equals() содержит самый простой алгоритм сравнивания текущего и переданного объектов — он просто сравнивает их ссылки.

Тот же эффект вы получите, если просто сравните переменные класса Person вместо вызова метода equals(). Пример:

Код Вывод на экран
Person a = new Person();
a.name = "Аня";

Person b = new Person();
b.name = "Аня";

System.out.println(a == b);
System.out.println(a.equals(b));






false
false

Метод equals просто сравнивает у себя внутри ссылки a и b.

Однако у класса String сравнение работает по-другому. Почему?

Потому что разработчики класса String написали собственную реализацию метода equals().

Реализация метода equals()

Давайте и мы напишем свою реализацию метода equals в классе Person. Разберем 4 основных случая.

Важно:
Вне зависимости от того, для какого класса переопределять метод equals, он всегда принимает параметр типа Object

Сценарий 1: в метод equals передали тот же самый объект, у которого вызвали метод equals. Если ссылки у текущего и переданного объектов равны, нужно вернуть true. Объект совпадает сам с собой.

В коде это будет выглядеть так:

Код Описание
public boolean equals(Object obj)
{    if (this == obj)
    return true;

   остальной код метода equals
}


Сравниваем ссылки

Сценарий 2: в метод equals передали ссылку null — сравнивать не с чем. Объект, у которого вызвали метод equals, точно не null, значит, в этом случае нужно вернуть false.

В коде это будет выглядеть так:

Код Описание
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   остальной код метода equals
}


Сравниваем ссылки


Переданный объект — null?

Сценарий 3: в метод equals передали ссылку на объект вообще не класса Person. Равен ли объект класса Person объекту класса не-Person? Тут уже решает сам разработчик класса Person — как хочет, так и сделает.

Но обычно все же объекты считаются равными, если это объекты одного класса. Поэтому если в наш метод equals передали объект не класса Person, мы будем всегда возвращать false. А как проверить, какого типа объект? Правильно: с помощью оператора instanceof.

Вот как будет выглядеть наш новый код:

Код Описание
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   остальной код метода equals
}


Сравниваем ссылки


Переданный объект — null?


Если переданный объект не типа Person

4. Сравнение двух объектов Person

Что мы получили в итоге? Если мы дошли до конца метода, значит, у нас объект типа Person и ссылка не null. Тогда преобразовываем его к типу Person и будем сравнивать внутренности обоих объектов. Это и есть наш сценарий номер 4.

Код Описание
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   остальной код метода equals
}


Сравниваем ссылки


Переданный объект — null?


Если переданный объект не типа Person


Операция приведения типа

А как сравнивать два объекта Person? Они равны, если у них равны имена (name) и возраст (age). Итоговый код будет выглядеть так:

Код Описание
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   return this.name == person.name && this.age == person.age;
}


Сравниваем ссылки


Переданный объект — null?


Если переданный объект не типа Person


Операция приведения типа

Но и это еще не все.

Во-первых, поле name имеет тип String, а значит, поля name нужно сравнивать с помощью вызова метода equals.

this.name.equals(person.name)

Во-вторых, поле name вполне себе может быть равным null: тогда вызвать метод equals у него нельзя. Нужна дополнительная проверка на null:

this.name != null && this.name.equals(person.name)

Однако если name равно null в обоих объектах Person, имена все-таки равны.

Код четвертого сценария может выглядеть, например, так:

Person person = (Person) obj;

if (this.age != person.age)
   return false;

if (this.name == null)
   return person.name == null;

return this.name.equals(person.name);


Если возрасты не равны,
сразу return false

Если this.name равно null, нет смысла сравнивать через equals. Тут либо второе поле name равно null, либо нет.

Сравниваем два поля name через equals.


5. Метод hashCode()

Кроме метода equals, который выполняет детальное сравнение всех полей обоих объектов, есть еще один метод, который может использоваться для неточного, но очень быстрого сравнения — hashCode().

Представьте, что вы сортируете в алфавитном порядке список из тысяч слов, и вам нужно постоянно попарно сравнивать слова. А слова длинные, и букв в них много. В общем, такое сравнение будет идти очень долго.

Однако его можно ускорить. Допустим, слова начинаются на разные буквы: сразу же понятно, что они разные. Вот если они начинаются на одинаковые буквы, тогда гарантий нет: в дальнейшем слова могут оказаться и равными, и различными.

Метод hashCode() работает по похожему принципу. Если его вызвать у объекта, то он вернет некое число — аналог первой буквы в слове. Это число обладает такими свойствами:

  • У одинаковых объектов всегда одинаковые hash-code
  • У разных объектов могут быть одинаковые hash-code, а могут быть разные
  • Если у объектов разные hash-code, объекты точно разные

Для большего понимания перепишем эти свойства относительно слов:

  • У одинаковых слов всегда одинаковые первые буквы
  • У разных слов могут быть одинаковые первые буквы, а могут быть и разные
  • Если у слов разные первые буквы, слова точно разные

Последнее свойство и используется для ускоренного сравнения объектов:

Сначала у двух объектов вычисляются hash-code. Если эти hash-code разные, то объекты точно разные, и сравнивать их дальше не нужно.

А вот если hash-code одинаковые, придется все же сравнивать объекты с помощью equals.



6. Контракты в коде

Описанное выше поведение должны реализовывать все классы в Java. Проверить правильность сравнения объектов на уровне компиляции никак нельзя.

Все Java-программисты договорились, что если они пишут свою реализацию метода equals() вместо стандартной (из класса Object), они также должны написать свою реализацию метода hashCode(), чтобы озвученные выше правила сохранялись.

Такая договоренность называется контрактом.

Если вы добавляете в свой класс только реализацию одного метода equals() или только hashCode(), вы грубо нарушаете контракт (нарушаете договоренность). Так делать нельзя.

Если другие программисты будут использовать ваш код, он может работать неправильно. Более того, вы тоже будете использовать код, который работает на основе вышеозвученных контрактов.

Важно!

Все коллекции в Java при поиске элемента внутри коллекции сначала сравнивают hash-code объектов, а только потом вызывают для сравнения метод equals.

Поэтому если вы напишете свой класс, а в нем новую функцию equals, но не напишете метод hashCode() или реализуете его с ошибками, коллекции могут неправильно работать с вашими объектами.

Например, вы добавили объект в список, затем ищете его с помощью метода contains(), а коллекция ваш объект не находит.


Комментарии (764)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Maxim Terletsky Уровень 24
21 июня 2026
hashCode - метод Object, возвращающий числовой хеш (числовое представление) объекта. Используется для быстрого поиска в коллекциях. 1. Равные по equals() объекты → обязательно одинаковый hashCode() 2. Разные объекты по equals() → могут иметь одинаковый hashCode() (коллизия) 3. Повторные вызовы → всегда один и тот же результат Плохая реализация

public int hashCode() {
    return 42;
}
Хорошая реализация (ручная)

public int hashCode() {
    int result = 17;
    result = 31 * result + Objects.hashCode(model);
    result = 31 * result + Integer.hashCode(year);
    return result;
}
Реализация с использованием Objects

public int hashCode() {
    return Objects.hash(model, year);
}
При работе с equals() и hashCode() не забываем про Objects (java.util.Objects) и его методы: • Objects.equals() - безопасное сравнение объектов • Objects.hashCode() - безопасное получение хеша • Objects.hash() - автоматический расчет хеша В классах-обертках для примитивов тоже есть методы для работы с хешем: Integer.hashCode(), Double.hashCode() и др. Остальные методы можно найти в документации. Переопределение equals() и hashCode() с классом Objects

public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof Car)) return false;

    Car car = (Car) obj;

    return year == car.year &&
        Double.compare(price, car.price) == 0 &&
        Objects.equals(model, car.model) &&
        Objects.equals(color, car.color);
}

public int hashCode() {
    return Objects.hash(model, year, price, color);
}
Обезьяна Уровень 43
23 января 2026
Кажется забыли написать про hashСode в лекции)
Anonymous #3466115 Уровень 18
5 февраля 2026
Вот тоже смотрел и не понимал почему какой-то резкий переход от "объясним вам на примере одинаковых слов" до "нужно переопределить метод hashCode чтобы он работал правильно"). А где примеры общепринятой реализации? Как вычислять нужное значение если все поля строки или все поля целочисленные значения? Нужно ли использовать начальное значение и множитель?
Анна Sowa Уровень 13
3 сентября 2026
Это есть в видео к уроку.
Grigoryvvv Уровень 26 Expert
9 декабря 2025
14.12.2025 / 11 уровень. Потратил на лекцию и задачи 12 дней 🤔 2. Обратите внимание на строчку

public boolean equals(Object obj) {   // Апкастинг
Person person = (Person) obj;          // Даункастинг
}
Здесь применены операции преобразования типов ссылок, Апкастинг (Upcasting) – Восходящее преобразование типа ссылки объекта и Даункастинг (Downcasting) – Нисходящее преобразование типа ссылки объекта. Пример. Создадим класс родитель Animal и наследуемый от него класс Cat.

public class Animal {
    String name = "Животное";

    public void makeNoise() {
        System.out.println("Издаёт звук " + name);
    }

public class Cat extends Animal{
    String name = "Кот";

    @Override
    public void makeNoise() {
        System.out.println("Мяукает " + name);
    }

    public void makeNoise2() {
        System.out.println("Шыпит " + name);
    }
}

Класс Cat наследует от класса Animal метод makeNoise() и переопределяет его под свою реализацию. Также в классе Сat есть собственный метод makeNoise2, который не видно из класса родителя Animal, но его мы пока не будем касаться. Создадим класс Solution

public class Solution {

    public static void main(String[] args) {
        Animal cat = new Cat();    // применили Апкастинг (Upcasting)
        animal.makeNoise();
    }
}
Мы видим строчку Animal cat = new Cat(); Обычно мы писали Animal animal = new Animal(); или Cat cat = new Cat(); В строчке Animal cat = new Cat(); мы применили Апкастинг (Upcasting) – Восходящее преобразование типа ссылки объекта. При Апкастинге происходит автоматическое приведение типа ссылки на объект из подкласса (дочернего класса) к его суперклассу (родительскому классу). Это чем-то напоминает сужение и расширение примитивных типов при приведении допустим типа int к типу double.
Grigoryvvv Уровень 26 Expert
9 декабря 2025
Но в отличие от примитивов, при Апкастинге ссылочных объектов, класс объекта не меняется, он как был класса Cat, так и остался. Поменялся только тип ссылки на объект. Апкастинг позволяет использовать полиморфизм, так называемое динамическое связывание: мы можем вызывать методы, определенные в классе Animal (например, makenoise()), но во время выполнения метода будет вызван переопределенный метод из фактического класса объекта (Cat). Строчку Animal cat = new Cat(); можно прочитать как: "Создаём объект класса Cat с с ссылочной переменной cat типа (или класса) Animal." Мы получаем доступ ко всем методам класса родителя Animal, но с реализацией из класса наследника Cat, если эти метода в нем переопределены. Так же в переопределенных методах будут использованы поля (переменные) класса Cat. В консоль выведется

Мяукает Кот
Ну и какая польза нам от этого метода Апкастинга. Сейчас покажу. Давайте перепишем класс Solution

public class Solution {

    public static void main(String[] args) {
        Cat cat = new Cat();
        doAnimalStuff(cat);

    }

    public static void doAnimalStuff(Animal animal) {   // Апкастинг (Upcasting)
        animal.makeNoise();
    }
}
В консоль выведется

Мяукает Кот
Здесь производиться Апкастинг в скобках метода doAnimalStuff при передаче аргумента. В данном случае нам не нужно вручную менять тип переменной cat объекта класса Cat. Когда мы передаём переменную cat в качестве аргумента в метод doanimalstuff(Animal animal), создается новая локальная ссылочная переменная animal внутри этого метода и производится восходящее преобразование типа переменной animal до типа родительского класса Animal.
Grigoryvvv Уровень 26 Expert
10 декабря 2025
Не смотря на то что объект класса Cat созданный в методе main один и в дальнейшем меняться не будем, его переменная cat локальная, т.к. переменные созданные внутри методов видны только внутри методов. Мы не можем вызвать локальную переменную cat внутри метода doAnimalStuff, она доступна только внутри метода main. Поэтому для вызова объекта класса Cat в методе doAnimalStuff создается локальная переменная animal со ссылкой скопированной из переменной cat. Переменная animal тоже локальная и её видно только внутри метода doAnimalStuff. Сам объект, как был класса Cat, так и остался нетронутым в отведенным для него месте в памяти. У него теперь две ссылки, Cat cat в главном методе main и новая ссылка Animal animal в методе doAnimalStuff. Если бы у нас было много дочерних классов (Cat, Dog, Wolf, ...), нам не нужно было бы для каждого из них создавать свой метод doAnimalStuff. Мы бы просто вызывали для каждого объекта дочернего класса тот-же самый единственный метод doAnimalStuff, в этом и польза. Но есть свои ограничения. При Апкастинге нам доступны только методы родительского класса Animal, с реализацией класса Cat, если методы были в нем переопределены. Самостоятельные методы класса Cat нам недоступны. Также нам доступны напрямую только поля (переменные) родительского класса Animal. Переменные в отличие от методов нельзя переопределить. Мы можем напрямую вызвать поле родительского класса. Но переменные дочернего класса Cat нам недоступны. И что-же делать если после Апкастинга нам стал нужен доступ именно к собственным методам и полям дочернего класса Cat. Всё очень просто. Мы до этого с помощью Апкастинга возвели тип ссылки на объект к типу родительского класса. У нас получилось Animal cat. А теперь нам надо понизить тип ссылки на объект к типу дочернего класса, что бы получилось Сat cat. Для этого выполним Даункастинг (Downcasting) – Нисходящее преобразование типа ссылки объекта.
Grigoryvvv Уровень 26 Expert
10 декабря 2025
Давайте перепишем класс Solution

public class Solution {

    public static void main(String[] args) {
        Cat cat = new Cat();
        doAnimalStuff(cat);
    }

    public static void doAnimalStuff(Animal animal) {
        animal.makeNoise();
        Cat cat = (Cat) animal; // Даункастинг (Downcasting) 
        cat.makeNoise2();
        System.out.println(cat.name);
    }
}
Вывод в консоль

Мяукает Кот
Шыпит Кот
Кот
Мы выполнили Даункастинг в строке Cat cat = (Cat) animal; Нисходящее преобразование по аналогии с сужением примитивных типов требует явной реализации (указания в скобках подкласса). Кстати переменные Cat cat в методе main и Cat cat в методе doAnimalStuff это две разные локальные переменные. Каждая из них видна только в своём методе. Мы не можем изменить тип переменной Animal animal на Cat animal, т.к. переменная Animal animal определяет тип входящего аргумента в методе doAnimalStuff(Animal animal), поэтому и создали локальную переменную Cat cat в методе doAnimalStuff. Теперь из метода doAnimalStuff мы получили доступ к собственным методам и полям класса Cat.
Soda Уровень 1
23 марта 2026
дай бог тебе здоровья. эту же инфу искал в интернете и выпытывал у нейронок часа три наверно
Grigoryvvv Уровень 26 Expert
23 марта 2026
Спасибо ) Тебе тоже здоровье, брат. Это самое главное в нашей жизни.
Grigoryvvv Уровень 26 Expert
5 декабря 2025
1. В Java принята абстракция что все классы неявно наследуются от одного главного класса Object. В классе Object уже прописаны некоторые часто используемые методы. Когда мы создаем любой свой класс, в него неявно наследуются все методы главного класса. Среди таких методов есть методы, которые имеют отношение к сравнению объектов. Это метод Object.equals() и метод Object.hashCode(). Метод Object.equals() имеет вид:

public boolean equals(Object obj) {
 return this == obj;
}
Где obj – это объект типа Object с которым будет сравниваться текущий объект. Object является корневым классом в иерархии классов Java, а это значит, что переменная obj может принимать в качестве значения любой объект любого класса в Java. this – это текущий объект в котором вызывается метод equals (точнее ссылка на текущий объект, т.к. переменные объектов ссылочные) Создадим класс Cat и два объекта этого класса.

public class Solution {
    public static void main(String[] args) {
        Cat cat1 = new Cat(1);     // создаём объект cat1 типа класса Cat
        Cat cat2 = new Cat(1);     // создаём объект cat2 типа класса Cat
        System.out.println(cat1 == cat2); // сравниваются ссылочные переменные объектов
        System.out.println(cat1.ecuals(cat2)); // сравниваем ссылочные переменные объектов
    }
}

public class Cat {
    private int age;

    public Cat(int age) {
        this.age = age;
    }
}
Вывод в консоль

false
false
При создании своих классов и их объектов, если мы будем пытаться сравнить эти объекты методом ecuals, у нас будет вызываться метод ecuals из главного родительского класса Object, т.к. он неявно наследуется при создании нами любого нашего класса. Как мы помним Object.equals() имеет вид

public boolean equals(Object obj) {
 return this == obj;
}
Grigoryvvv Уровень 26 Expert
5 декабря 2025
То есть, по сути, получается тоже самое. Что мы напрямую сравним наши объекты с помощью оператора “==”, что через метод ecuals, у нас просто будут сравниваться ссылочные переменные, а не сами объекты. Тогда почему же, когда мы сравниваем данные типа String или Integer методом ecuals, у нас сравниваются сами объекты типа String или Integer, а не ссылки их переменных. Ведь по сути класс String и Integer так же неявно наследуют метод Object.equals() из главного корневого родительского класса Object. Дело в том, что типы данных String и Integer очень часто используются. И при создании классов String или Integer, Джеймсом Гослингом (James Gosling) и его командой в {Sun Microsystems} в 1990-х годах, в этих классах были переопределены методы ecuals унаследованные от главного класса Object. То есть методы ecuals в этих классах вручную были переписаны. Затем классы String и Integer были помещены в стандартную библиотеку Java и мы ими пользуемся. Если объяснять на пальцах, то

public class String {       // создали класс String в 1990-х годах 

@Override          // переопределили метод equals главного класса Objec
public boolean equals(Object obj) {
…написали свой код…
}
@Override это знак означающий, что мы знаем о наследуемом методе, но мы в этом методе напишем свой внутренний код, т.к. код наследуемого метода от главного класса нас не устраивает. Главный вывод: Если мы сравниваем с помощью метода ecuals часто используемые типы данных наподобие String или Integer, то будут задействованы методы ecuals этих классов из стандартной библиотеки Java. Если же мы сравниваем свои созданные объекты с помощью метода ecuals, то будет задействован очень простенький метод ecuals из главного класса Objec наследуемый классами наших созданных объектов. Если нас это не устраивает, то нам надо этот наследуемый метод переопределить, т.е. переписать.
SUNSHINE Уровень 30
5 декабря 2025
Ну вот я тебя и догнал ;) Конечно, ты куда более тщательно со всем разбираешься. И это верный подход. Надеюсь, скоро увидимся коллегами в этой индустрии ;) Если не секрет, ты откуда будешь ?)
Grigoryvvv Уровень 26 Expert
7 декабря 2025
Да, разбираюсь не торопясь. Стараюсь для других оставлять полезную информацию.
Vadim Уровень 5
11 ноября 2025
теперь я понимаю почему многие местные пользователи зависают на пол пути уровнем. Потому что надо изучить синтаксис в интернетах, потом только тут проходить обучение.
12 октября 2025
начали за хэшкод, закончили хэдшот
Eugene Volkov Уровень 39
22 сентября 2025
задачки я решил, но весь мозг себе сломал, пока пытался понять, как правильно переопределить методы equals и hashCode. В самом деле гуглить и искть инфу на сторонних ресурсах откровенно задолбало, хоть некотррые комментаторы и утверждают, что так надо
Elisbeth Уровень 30
15 декабря 2025
Плюсую! на этом сайте довольно большие проблемы с теорией, гуглить, конечно, надо уметь, но я и так совмещаю учёбу с работой, купила подписку, почему я не могу получить нормальный хотя бы базовый материал? Копаться по рандомным статьям в инете вообще не повышает мотивацию, так ещё не факт что что-то найдёшь, а все моральные силы уже потратишь
Kanat Shudabaev Уровень 1
3 сентября 2025
А у меня одного так, что примерно после 8 юнита, я ничего толком не понимаю?)
Anonymous #3541949 Уровень 14
3 сентября 2025
несколько дней пытался решить эти задачи, на какое-то время даже забросил сайт, так сильно меня демотивировало непонимание этих методов. Вот сейчас вернулся, решил, а оказалось, что надо было просто вызвать метод другого класса :\
Anton Zorin Уровень 32
23 августа 2025
String s1 = new String("JavaRush"); "JavaRush" (литерал): JVM проверяет пул строк. Если такой строки нет, она создает новый объект String со значением "JavaRush" в пуле строк. new String(...): Ключевое слово new всегда создает новый объект в куче (heap), независимо от того, что находится в пуле. Этот новый объект также инициализируется значением "JavaRush". Итог: В памяти теперь два объекта: Один в String Pool ("JavaRush"). Один в куче (новый объект, созданный через new). Переменная s1 ссылается на объект в куче.