1. Введение
В этой статье мы рассмотрим создание методов equals()
и hashCode()
с помощью Eclipse IDE. Мы покажем, насколько мощной и удобной является автоматическая генерация кода Eclipse, а также подчеркнем, что тщательное тестирование кода по-прежнему необходимо.
2. Правила
equals()
в Java используется для проверки эквивалентности двух объектов. Хороший способ проверить это — убедиться, что объекты симметричны, рефлексивны и транзитивны. То есть для трех ненулевых объектов a
, b
и c
:
- Симметричный – a.equals(b) тогда и только тогда, когда b.equals(a)
- Рефлексивный –
a.equals(a)
- Транзитивный - если
a.equals(b)
иb.equals(c)
, тоa.equals(c)
hashCode()
должен подчиняться одному правилу:
- 2 объекта
equals()
должны иметь одинаковое значениеhashCode()
3. Класс с примитивами
Давайте рассмотрим класс Java, состоящий только из примитивных переменных-членов:
public class PrimitiveClass {
private boolean primitiveBoolean;
private int primitiveInt;
// constructor, getters and setters
}
Мы используем Eclipse IDE для генерации equals
() и hashCode
(), используя 'Source->Generate hashCode()
and equals()
'. Eclipse предоставляет диалоговое окно, подобное этому:
Мы можем убедиться, что все переменные-члены включены, выбрав «Выбрать все».
Обратите внимание, что параметры, перечисленные в разделе «Точка вставки:», влияют на стиль сгенерированного кода. Здесь мы не выбираем ни один из этих параметров, выбираем «ОК», и методы добавляются в наш класс:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (primitiveBoolean ? 1231 : 1237);
result = prime * result + primitiveInt;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
PrimitiveClass other = (PrimitiveClass) obj;
if (primitiveBoolean != other.primitiveBoolean) return false;
if (primitiveInt != other.primitiveInt) return false;
return true;
}
Сгенерированный метод hashCode()
начинается с объявления простого числа (31), выполняет различные операции над примитивными объектами и возвращает результат на основе состояния объекта.
equals()
сначала проверяет, являются ли два объекта одним и тем же экземпляром (==), и возвращает true, если это так.
Затем он проверяет, что объект сравнения не является нулевым и что оба объекта относятся к одному и тому же классу, возвращая false, если это не так.
Наконец, equals()
проверяет равенство каждой переменной-члена и возвращает false, если какая-либо из них не равна.
Итак, мы можем написать простые тесты:
PrimitiveClass aObject = new PrimitiveClass(false, 2);
PrimitiveClass bObject = new PrimitiveClass(false, 2);
PrimitiveClass dObject = new PrimitiveClass(true, 2);
assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());
assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());
4. Класс с коллекциями и дженериками
Теперь давайте рассмотрим более сложный класс Java с коллекциями и дженериками:
public class ComplexClass {
private List<?> genericList;
private Set<Integer> integerSet;
// constructor, getters and setters
}
Снова мы используем Eclipse 'Source->Generate hashCode()
and equals()'.
Обратите внимание, что hashCode()
использует instanceOf
для сравнения объектов класса, потому что мы выбрали «Использовать instanceof» для сравнения типов в параметрах Eclipse в диалоговом окне. Мы получаем:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((genericList == null)
? 0 : genericList.hashCode());
result = prime * result + ((integerSet == null)
? 0 : integerSet.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof ComplexClass)) return false;
ComplexClass other = (ComplexClass) obj;
if (genericList == null) {
if (other.genericList != null)
return false;
} else if (!genericList.equals(other.genericList))
return false;
if (integerSet == null) {
if (other.integerSet != null)
return false;
} else if (!integerSet.equals(other.integerSet))
return false;
return true;
}
Сгенерированный метод hashCode()
основан на основных методах Java AbstractList.hashCode()
и AbstractSet.hashCode() .
Они перебирают коллекцию, суммируя значения hashCode()
каждого элемента и возвращая результат.
Точно так же сгенерированный метод equals()
использует AbstractList.equals()
и AbstractSet.equals()
, которые сравнивают коллекции на равенство, сравнивая их поля.
Мы можем проверить надежность, протестировав несколько примеров:
ArrayList<String> strArrayList = new ArrayList<String>();
strArrayList.add("abc");
strArrayList.add("def");
ComplexClass aObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));
ComplexClass bObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));
ArrayList<String> strArrayListD = new ArrayList<String>();
strArrayListD.add("lmn");
strArrayListD.add("pqr");
ComplexClass dObject = new ComplexClass(strArrayListD, new HashSet<Integer>(45,67));
assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());
assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());
5. Наследование
Рассмотрим классы Java, использующие наследование:
public abstract class Shape {
public abstract double area();
public abstract double perimeter();
}
public class Rectangle extends Shape {
private double width;
private double length;
@Override
public double area() {
return width * length;
}
@Override
public double perimeter() {
return 2 * (width + length);
}
// constructor, getters and setters
}
public class Square extends Rectangle {
Color color;
// constructor, getters and setters
}
Если мы попытаемся выполнить «Source->Generate hashCode()
and equals()
» в классе Square
, Eclipse предупредит нас, что «суперкласс «Rectangle» не повторно объявляет equals()
и hashCode()
: результирующий код может работать некорректно. '.
Точно так же мы получаем предупреждение о суперклассе Shape, когда пытаемся сгенерировать hashCode()
и equals()
в классе Rectangle
.
Eclipse позволит нам двигаться вперед, несмотря на предупреждения. В случае с Rectangle
он расширяет абстрактный класс Shape , который не может реализовать
hashCode()
или equals()
, потому что у него нет конкретных переменных-членов. В этом случае мы можем игнорировать Eclipse.
Однако класс Square наследует переменные-члены
ширины
и длины
от класса Rectangle, а также свою собственную переменную цвета. Создание hashCode()
и equals()
в Square
без предварительного выполнения того же самого для Rectangle
означает использование только цвета
в equals()
/ hashCode()
:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((color == null) ? 0 : color.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Square other = (Square) obj;
if (color == null) {
if (other.color != null)
return false;
} else if (!color.equals(other.color))
return false;
return true;
}
Быстрый тест показывает нам, что equals()
/ hashCode()
для Square
недостаточно, если отличается только ширина
, потому что ширина
не включена в вычисления equals()
/ hashCode() :
Square aObject = new Square(10, Color.BLUE);
Square dObject = new Square(20, Color.BLUE);
Assert.assertFalse(aObject.equals(dObject));
Assert.assertFalse(aObject.hashCode() == dObject.hashCode());
Давайте исправим это, используя Eclipse для генерации equals()
/ hashCode()
для класса Rectangle :
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(length);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(width);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Rectangle other = (Rectangle) obj;
if (Double.doubleToLongBits(length)
!= Double.doubleToLongBits(other.length)) return false;
if (Double.doubleToLongBits(width)
!= Double.doubleToLongBits(other.width)) return false;
return true;
}
Мы должны перегенерировать equals()
/ hashCode()
в классе Square
, поэтому Rectangle вызывает
equals()
/ hashCode ()
. В этом поколении кода мы выбрали все параметры в диалоговом окне Eclipse, поэтому мы видим комментарии, сравнения instanceOf
и блоки if :
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((color == null) ? 0 : color.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof Square)) {
return false;
}
Square other = (Square) obj;
if (color == null) {
if (other.color != null) {
return false;
}
} else if (!color.equals(other.color)) {
return false;
}
return true;
}
Повторно запустив наш тест сверху, мы его проходим, потому что hashCode ()
/ equals()
Square
вычисляются правильно.
6. Заключение
Eclipse IDE очень мощная и позволяет автоматически генерировать шаблонный код — геттеры/сеттеры, конструкторы различных типов, equals()
и hashCode()
.
Понимая, что делает Eclipse, мы можем сократить время, затрачиваемое на эти задачи кодирования. Тем не менее, мы все равно должны соблюдать осторожность и проверять наш код с помощью тестов, чтобы убедиться, что мы обработали все ожидаемые случаи.
Фрагменты кода, как всегда, можно найти на GitHub .