1. введение
В этой статье мы рассмотрим создание equals() и hashCode() методов с использованием среды разработки Eclipse. Мы проиллюстрируем, насколько мощной и удобной является автоматическая генерация кода Eclipse, а также подчеркнем, что тщательное тестирование кода по-прежнему необходимо.
2. Правила
equals() в Java используется для проверки эквивалентности 2 объектов. Хороший способ проверить это-убедиться, что объекты симметричны, рефлексивны и транзитивны. То есть для трех ненулевых объектов a , b и c :
- Симметричный – a.равен(b) тогда и только тогда, когда b.равен(a)
- Рефлексивный – a.равно(a)
- Транзитивный – если a.равно(b) и b.равно(c) тогда a.равно(c)
hashCode() должен подчиняться одному правилу:
- 2 объекта, которые равны() должны иметь одинаковое значение хэш-кода()
3. Класс С Примитивами
Давайте рассмотрим класс Java, состоящий только из примитивных переменных-членов:
public class PrimitiveClass { private boolean primitiveBoolean; private int primitiveInt; // constructor, getters and setters }
Мы используем среду разработки Eclipse для генерации equals () и hashCode () с помощью ” Source->Generate hashCode() и 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 SetintegerSet; // constructor, getters and setters }
Снова мы используем Eclipse ‘Source->Generate hashCode() и 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() опирается на методы AbstractList.hashCode() и AbstractSet.hashCode() core Java. Они перебирают коллекцию, суммируя hashCode() значения каждого элемента и возвращая результат.
Аналогично, сгенерированный метод equals() использует AbstractList.equals() и AbstractSet.equals() , которые сравнивают коллекции на равенство, сравнивая их поля.
Мы можем проверить надежность, проверив некоторые примеры:
ArrayListstrArrayList = new ArrayList (); strArrayList.add("abc"); strArrayList.add("def"); ComplexClass aObject = new ComplexClass(strArrayList, new HashSet (45,67)); ComplexClass bObject = new ComplexClass(strArrayList, new HashSet (45,67)); ArrayList strArrayListD = new ArrayList (); strArrayListD.add("lmn"); strArrayListD.add("pqr"); ComplexClass dObject = new ComplexClass(strArrayListD, new HashSet (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() и equals() ” в классе Square , Eclipse предупредит нас, что “суперкласс” Прямоугольник “не объявляет equals() и hashCode() : полученный код может работать неправильно”.
Аналогично, мы получаем предупреждение о суперклассе “Shape”, когда пытаемся сгенерировать hashCode() и equals() в классе Rectangle .
Затмение позволит нам двигаться вперед, несмотря на предупреждения. В случае Rectangle он расширяет абстрактный Shape класс , который не может реализовать hashCode() или equals () , поскольку у него нет конкретных переменных-членов. В этом случае мы можем игнорировать Затмение.
Однако класс Square наследует переменные-члены width и length от Rectangle, а также собственную переменную цвета. Создание hashCode() и equals() in Square без предварительного выполнения того же для Rectangle означает использование только color in 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 недостаточно, если отличается только width , потому что width не включена в 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 ‘s equals () |/hashCode() вызываются. В этом поколении кода мы выбрали все параметры в диалоговом окне Eclipse, поэтому мы видим комментарии, экземпляр сравнения и если блоки:
@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; }
Повторяя наш тест сверху, мы проходим сейчас, потому что Square ‘s hashCode() / equals() вычисляются правильно.
6. Заключение
IDE Eclipse очень мощная и позволяет автоматически генерировать шаблонный код-геттеры/сеттеры, конструкторы различных типов, equals () и hashCode() .
Понимая, что делает Eclipse, мы можем сократить время, затрачиваемое на эти задачи кодирования. Однако мы все равно должны проявлять осторожность и проверять наш код с помощью тестов, чтобы убедиться, что мы обработали все ожидаемые случаи.
Фрагменты кода, как всегда, можно найти на GitHub .