Генерировать equals () и hashCode () с Eclipse

Генерация equals () и hashCode () с Eclipse

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 → GeneratehashCode() andequals()». Eclipse предоставляет диалоговое окно, подобное этому:

eclipse-equals-hascode

Мы можем обеспечить включение всех переменных-членов, выбрав «Выбрать все».

Обратите внимание, что параметры, перечисленные под точкой вставки: влияют на стиль сгенерированного кода. Здесь мы не выбираем ни один из этих вариантов, выбираем «ОК», и методы добавляются в наш класс:

@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() сначала проверяет, являются ли два объекта одним и тем же экземпляром (==), и возвращает истину, если они есть.

Затем он проверяет, что объект сравнения не равен NULL, и оба объекта принадлежат одному классу, и возвращает 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 integerSet;

    // constructor, getters and setters
}

Мы снова используем Eclipse «Источник → Создать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() основан на основных методах JavaAbstractList.hashCode() иAbstractSet.hashCode(). Они выполняют итерацию по коллекции, суммируяhashCode() значений каждого элемента и возвращая результат.

Точно так же сгенерированный методequals() используетAbstractList.equals() иAbstractSet.equals(), которые сравнивают коллекции на равенство, сравнивая свои поля.

Мы можем проверить надежность, протестировав несколько примеров:

ArrayList strArrayList = 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
}

Если мы попытаемся «Источник → СгенерироватьhashCode() иequals()» в классеSquare, Eclipse предупредит нас, что «суперкласс Rectangle» не объявляет повторноequals() иhashCode(): полученный код может работать некорректно ».

Точно так же мы получаем предупреждение о суперклассе Shape, когда пытаемся сгенерироватьhashCode() иequals() в классеRectangle.

Затмение позволит нам пахать вперед, несмотря на предупреждения. В случаеRectangle он расширяет абстрактный классShape, который не может реализоватьhashCode() илиequals(), потому что у него нет конкретных переменных-членов. Мы можем проигнорировать Eclipse для этого случая.

Однако классSquare наследует переменные-членыwidth иlength от Rectangle, а также собственную цветовую переменную. СозданиеhashCode() иequals() вSquare без предварительного выполнения того же самого дляRectangle означает использование толькоcolor в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‘sequals() /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;
}

Повторно запустив наш тест сверху, мы прошли его, потому чтоSquare‘shashCode() /equals() вычислены правильно.

6. Заключение

Eclipse IDE очень мощная и позволяет автоматически создавать шаблонный код - геттеры / сеттеры, конструкторы различных типов,equals() иhashCode().

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

Фрагменты кода, как всегда, можно найтиover on GitHub.