Часто при программировании нам приходиться проверять равенство объектов. Но как же работает сравнение объектов в языке Java? Расскажем вам в этой статье.
Статья ориентирована на читателей среднего уровня подготовки Java.
Сравнение с помощью == и equals в Java
Приведем пример программного кода:
1 2 3 4 |
String s1 = new String("vscode.ru"); String s2 = new String("vscode.ru"); System.out.println(s1 == s2); //каким здесь будет результат сравнения? System.out.println(s1.equals(s2)); //а здесь? |
Но, в чем же разница между этими двумя записями? Какое отличие между оператором сравнения == и методом equals?
Все очень просто. Метод equals в Java при сравнении проверяет и сопоставляет само содержимое объектов (их значения) и на основе этого делает заключение равны они (true) или нет (false).
Оператор == (в случае с примитивными типами данных) сравнивает значения переменных и возвращает результат, НО в случае со ссылочными типами данных (объекты, массивы и т.д.) сравнивает ссылки на объекты в памяти компьютера, и на основании равенства или неравенства ссылок возвращает результат (true или false). Вот в чём отличие метода equals и оператора ==.
Вы можете почитать подробную статью про оба вида типов данных и их различия в соответствующей статье.
Метод equals — это метод класса Object. Каждый объект неявно унаследован от класса Object и они могут вызывать метод equals.
Возвращаясь к примеру, приведенному в начале подраздела, можно сделать вывод о том, каким будет результат операции сравнения в обоих случаях:
1 2 |
System.out.println(s1 == s2); //возвращено false. Сравниваются ссылки, а они различны System.out.println(s1.equals(s2)); //возвращено true, поскольку значения объектов равны (они идентичны) |
Стоит понимать, что вместо класса String, мог быть объявлен любой другой ссылочный тип данных (Object, ArrayList<>, ваш пользовательский класс и т.д.).
Переопределение метода equals в Java
Когда же стоит переопределять метод equals? Это стоит делать только тогда, когда для вашего класса определено понятие логической эквивалентности, которая не совпадает с тождественностью объектов.
Например, для классов Integer и String данное понятие можно применить.
Переопределение метода equals нужно не только для того, чтобы удовлетворить ожидания программистов, оно также позволяет использовать экземпляры класса в качестве ключей в некой схеме или элементов в неком наборе, имеющих необходимое и предсказуемое поведение. Что это значит?
Давайте рассмотрим пример кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Address { private Integer number; private String street; public Address(Integer number, String street) { this.number = number; this.street = street; } public Integer getNumber() { return number; } public String getStreet() { return street; } public static void main(String[] args) { List<Address> addressList = new ArrayList<>(); addressList.add(new Address(1, "Test street")); addressList.add(new Address(1, "Test street")); while (addressList.remove(new Address(1, "Test street"))); System.out.println(addressList.size()); } } |
Мы создаем список объектов Address и добавляем объект класса Address с одинаковым конструктором. Добавляем данный объект 2 раза в List, и с помощью цикла удаляем его. Затем выводим в консоль длину списка.
После выполнения программы, в консоли отобразится число 2. Но почему же количество записей в списке не изменилось? Ведь мы пытались их удалить. В примере объекты с равными полями number и street, и всё-таки — почему они не удалились?
При удалении объектов из списка неявно вызывается метод equals и если объект который мы передаем для удаления содержится в списке, то он удаляется. Реализация equals в классе Object проверяет только равенство ссылок. А ссылки у экземпляров классов различные, потому что это совершенно разные объекты, каждый их них был создан с помощью оператора new. Как же это исправить? Необходимо переопределить метод equals в классе Address:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Address address = (Address) o; if (number != null ? !number.equals(address.number) : address.number != null) return false; if (street != null ? !street.equals(address.street) : address.street != null) return false; return true; } |
Запустим программу снова и увидим, что количество объектов в списке стало равным нулю.
Скачать исходник программы из этого урока, вы можете, нажав на кнопку ниже:
Скачать исходник
Правила переопределения метода equals в Java
Какие правила существуют для правильного переопределения метода equals? Их пять.
- Рефлексивность: для каждого ненулевого объекта х выражение x.equals(x) должно возвращать логическое значение true.
- Симметричность: для всех ненулевых объектов х и у выражение x.equals(y) должно возвращать значение true тогда и только тогда, когда y.equals(x) возвратит true.
- Транзитивность: для всех трёх ненулевых объектов х, у и z, если х.equals(y) возвращает логическое true и у.equals(z) возвращает true, то и выражение х.equals(z) должно всегда возвращать true.
- Непротиворечивость: если для всех ненулевых объектов х и у несколько раз вызвать х.equals(у), то всегда должно возвращаться значение true (либо false), при условии, что никакая информация,содержащаяся в объектах, не поменялась.
- И последнее: для всех ненулевых х выражение х.equals(null) должно всегда возвращать логическое значение false.
Если у вас нет склонности к математике, то всё это выглядит страшным, не так ли? Но вчитайтесь несколько раз и разберитесь с вышесказанным. На самом деле всё очень просто. Данные правила нарушать нельзя. В противном случае вы рискуете обнаружить, что ваше приложение работает неустойчиво, неправильно или завершается с ошибкой, а установить источник ошибок такого класса крайне затруднительно.
Поделиться в соц. сетях: