Метод equals()
обозначает отношение эквивалентности объектов. Эквивалентным называется
отношение, которое является симметричным, транзитивным, рефлексивным и
постоянным.
- Рефлексивность: для любого ненулевого x, x.equals(x) вернет true;
- Транзитивность: для любого ненулевого x, y и z, если x.equals(y) и y.eqals(z) вернет true, тогда и x.equals(z) вернет true;
- Постоянство: для любых объектов x и y x.equals(y) возвращает одно и тоже, если информация, используемая в сравнениях, не меняется;
- Симметричность: для любого ненулевого x и y, x.equals(y) должно вернуть true, тогда и только тогда, когда y.equals(x) вернет true.
При сравнении своей реализации объектов необходимо переопределять метод equals. Так же при переопределении equals, необходимо производить переопределить метод hashCode(), но об этом поговорим позже.
Предположим, что у нас есть класс App, в котором есть три поля: str1, str2 и num. Нам необходимо провести сравнение
экземпляров объектов на равенство, его полей.
Сначала напишем класс без переопределения.
package my.Equals;
public class App
{
String str1 = new String("Test1");
String str2 = new String("Test2");
int num = 5;
public static void main( String[] args )
{
App app1 = new App();
App app2 = new App();
System.out.println( app1.equals(app2) );
}
}
/* результат:
*
flase
*/
Как вы видите, на выходе получаем false. Это кажется странным, ведь оба экземпляра класса равны. Почему
же получился такой результат? Все очень просто. В базовом классе Object метод equals сравнивает
содержимое объектов. В нашем случае сравниваются ссылки на класс,
а они как не трудно догадаться, не равны. Что бы избежать данной ошибки необходимо
произвести переопределение метода equals.
package my.Equals;
public class App
{
String str1 = new String("Test1");
String str2 = new String("Test2");
int num = 5;
@Override
public boolean equals(Object obj) {
// проверяет не равен
ли obj – null
if(obj == null) {return false;}
// проверяет является ли obj объектом App
if(!(obj instanceof App)){return false;}
App obj1
= (App) obj;
//
сравнивает поля экземпляров класса
return str1.equals(obj1.str1)
&& str2.equals(obj1.str2)
&& num == obj1.num;
}
public static void main( String[] args )
{
App app1 = new App();
App app2 = new App();
System.out.println( app1.equals(app2) );
}
}
/* результат:
*
true
*/
Теперь мы получили то, что и ожидали.
Если чуть изменить наш пример, и прописать в методе main:
App app1 = new App();
App app2 = new App();
App2.str1 = "New test2";
System.out.println( app1.equals(app2) );
то метод equals вернет false.
Еще одной важной деталью является то, что тип аргумента, метода equals, должен быть Object, а не классом, который сравнивается.
Как я и обещал, поговорим о методе hashCode().
При переопределении метода equals() необходимо переопределять метод hashCode(). Это связано с тем, что для идентичных объектов разных
экземпляров класса могут возвращаться
разные значения. А это не всегда является корректным.
Пример переопределения метода hashCode. Для этого немного изменим предыдущий пример.
package my.Equals;
public class App
{
String str1 = new String("Test1");
String str2 = new String("Test2");
int num = 5;
@Override
public boolean equals(Object obj) {
if(obj == null) {return false;}
if(!(obj instanceof App)){return false;}
App obj1 = (App) obj;
return str1.equals(obj1.str1)
&& str2.equals(obj1.str2)
&& num == obj1.num;
}
@Override
public int hashCode() {
int hash = 37;
hash = hash*17 + str1.hashCode();
hash = hash*17 + str2.hashCode();
hash = hash*17 + num;
return hash;
}
public static void main( String[] args )
{
App app1 = new App();
App app2 = new App();
System.out.println("HashCode: app1.str1 - "
+ app2.str1.hashCode() + "; app2.str1 -
"
+ app2.str1.hashCode());
System.out.println("HashCode: app1.str2 - "
+ app2.str2.hashCode() + "; app2.str -
"
+ app2.str2.hashCode());
System.out.println( app1.equals(app2) );
//
изменяем значение str1, для экземпляра класса app2
app2.str1 = "test";
System.out.println("HashCode: app1.str1 -
"
+ app2.str1.hashCode() + "; app2.str1 -
"
+ app2.str1.hashCode());
System.out.println("HashCode: app1.str2 - "
+ app2.str2.hashCode() + "; app2.str -
"
+ app2.str2.hashCode());
System.out.println( app1.equals(app2) );
}
}
/* результат:
* HashCode: app1.str1 - 80698815; app2.str1 - 80698815
* HashCode: app1.str2 - 80698816; app2.str - 80698816
* true
*
* HashCode: app1.str1 - 3556498; app2.str1 - 3556498
* HashCode: app1.str2 - 80698816; app2.str - 80698816
* false
*/
Как видно из примера, при правильном переопределении метода, у идентичных
объектов экземпляра класса hash
code имеет одинаковое
значение.
Однако одинаковое значение объектов не всегда гарантирует эквивалентность
объектов. Это можно увидеть по второй части результата примера.
Отсюда вытекает три правила:
·
При переопределении метода equals необходимо переопределять метод hashCode;
·
Если объекты экземпляров класса эквивалентны, согласно
equals, то и hashCode
должен возвращать одинаковые значения;
·
Однако при одинаковых значениях hashCode, объекты могут быть не эквивалентными.
интересно было бы, если при
ОтветитьУдалитьSystem.out.println("HashCode: app1.str1 - "
+ app2.str1.hashCode() + "; app2.str1 - "
+ app2.str1.hashCode());
результат был бы разным, пытаясь вывести одно и то же
согласен. пример получился не удачный :)
УдалитьВ примере опечатка
ОтветитьУдалить"HashCode: app1.str1 - "+ app2.str1.hashCode()
нужно заменить на:
"HashCode: app1.str1 - "+ app1.str1.hashCode()