Generate equals() and hashCode() with Eclipse – 用Eclipse生成equals()和hashCode()

最后修改: 2016年 10月 2日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this article, we explore generating equals() and hashCode() methods using the Eclipse IDE. We’ll illustrate how powerful and convenient the Eclipse’s code auto-generation is, and also emphasize that diligent testing of code is still necessary.

在这篇文章中,我们将探讨使用 Eclipse IDE 生成 equals()hashCode() 方法。我们将说明 Eclipse 的代码自动生成功能是多么强大和方便,同时也强调对代码进行勤奋的测试仍然是必要的。

2. Rules

2.规则

equals() in Java is used for checking if 2 objects are equivalent. A good way to test this is to ensure objects are symmetric, reflexive, and transitive. That is, for three non-null objects a, b, and c:

equals()在Java中被用来检查2个对象是否相等。测试的一个好方法是确保对象是对称的、反身的和传递的。也就是说,对于三个非空对象a, b, 和c

  • Symmetric – a.equals(b) if and only if b.equals(a)
  • Reflexive – a.equals(a)
  • Transitive – if a.equals(b) and b.equals(c) then a.equals(c)

hashCode() must obey one rule:

hashCode()必须遵从一条规则。

  • 2 objects which are equals() must have the same hashCode() value

3. Class With Primitives

3.带有基元的类

Let’s consider a Java class composed of only primitive member variables:

让我们考虑一个只由原始成员变量组成的Java类。

public class PrimitiveClass {

    private boolean primitiveBoolean;
    private int primitiveInt;

    // constructor, getters and setters
}

We use the Eclipse IDE to generate equals() and hashCode() using ‘Source->Generate hashCode() and equals()‘. Eclipse provides a dialog box like this:

我们使用Eclipse IDE,使用’Source->Generate hashCode() and equals()‘来生成equals()和hashCode() 。Eclipse提供了一个这样的对话框。

eclipse-equals-hascode

We can ensure all member variables are included by choosing ‘Select All’.

我们可以通过选择 “全部选择 “来确保包括所有成员变量。

Note that the options listed beneath Insertion Point: affect the style of the generated code. Here, we don’t select any of those options, select ‘OK’ and the methods are added to our class:

请注意,”插入点 “下面列出的选项会影响生成的代码的风格。在这里,我们不选择任何一个选项,选择 “OK”,这些方法就被添加到我们的类中。

@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;
}

The generated hashCode() method starts with a declaration of a prime number (31), performs various operations on primitive objects and returns its result based on the object’s state.

生成的hashCode()方法以一个质数(31)的声明开始,对原始对象进行各种操作,并根据对象的状态返回其结果。

equals() checks first if two objects are the same instance (==) and returns true if they are.

equals()首先检查两个对象是否是同一个实例(==),如果是,则返回true。

Next, it checks that the comparison object is non-null and both objects are of the same class, returning false if they are not.

接下来,它检查比较对象是否为非空,并且两个对象是否属于同一类别,如果不是,则返回false。

Finally, equals() checks the equality of each member variable, returning false if any of them is not equal.

最后,equals()检查每个成员变量的平等性,如果其中任何一个不平等,则返回false。

So we can write simple tests:

所以我们可以编写简单的测试。

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. Class With Collections and Generics

4.带有集合和泛型的类

Now, let’s consider a more complex Java class with collections and generics:

现在,让我们考虑一个更复杂的带有集合和泛型的Java类。

public class ComplexClass {

    private List<?> genericList;
    private Set<Integer> integerSet;

    // constructor, getters and setters
}

Again we use Eclipse ‘Source->Generate hashCode() and equals()’. Notice the hashCode() uses instanceOf to compare class objects, because we selected ‘Use ‘instanceof’ to compare types’ in the Eclipse options on the dialog. We get:

我们再次使用Eclipse’Source->Generate hashCode()equals()’。注意hashCode()使用instanceOf来比较类对象,因为我们在对话框的Eclipse选项中选择了’使用’instanceof’来比较类型’。我们得到。

@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;
}

The generated hashCode() method relies on AbstractList.hashCode() and AbstractSet.hashCode() core Java methods. These iterate through a collection, summing hashCode() values of each item and returning a result.

生成的hashCode()方法依赖于AbstractList.hashCode()AbstractSet.hashCode()核心Java方法。这些方法遍历一个集合,对每个项目的hashCode()值进行求和,并返回一个结果。

Similarly, the generated equals() method uses AbstractList.equals() and AbstractSet.equals(), which compare collections for equality by comparing their fields.

同样,生成的equals()方法使用AbstractList.equals()AbstractSet.equals(),它们通过比较集合的字段来比较是否相等。

We can verify the robustness by testing some examples:

我们可以通过测试一些例子来验证其稳健性。

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. Inheritance

5.继承权

Let’s consider Java classes that use inheritance:

让我们考虑一下使用继承的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
}

If we attempt the ‘Source->Generate hashCode() and equals()‘ on the Square class, Eclipse warns us that ‘the superclass ‘Rectangle’ does not redeclare equals() and hashCode() : the resulting code may not function correctly’.

如果我们在Square类上尝试’Source->Generate hashCode()equals()‘,Eclipse警告我们’超类’Rectangle’没有重新声明equals()hashCode():产生的代码可能无法正常工作’。

Similarly, we get a warning about the superclass ‘Shape’ when we attempt to generate hashCode() and equals() on the Rectangle class.

同样,当我们试图在Rectangle类上生成hashCode()equals()时,我们得到一个关于超类 “Shape “的警告。

Eclipse will allow us to plow forward despite warnings. In the case of Rectangle, it extends an abstract Shape class that cannot implement hashCode() or equals() because it has no concrete member variables. We can ignore Eclipse for that case.

尽管有警告,Eclipse还是会允许我们继续前进。在 Rectangle 的例子中,它扩展了一个抽象的 Shape 类,该类不能实现 hashCode()equals(),因为它没有具体的成员变量。对于这种情况,我们可以忽略 Eclipse。

The Square class, however, inherits width and length member variables from Rectangle, as well as it’s own color variable. Creating hashCode() and equals() in Square without first doing the same for Rectangle means using only color in equals()/hashCode():

然而,Square 类从 Rectangle 继承了 widthlength 成员变量,以及它自己的颜色变量。在Square中创建hashCode()equals(),而不先对Rectangle做同样的操作,意味着在equals()/hashCode中只使用color

@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;
}

A quick test shows us that equals()/hashCode() for Square are not sufficient if it’s only the width that differs, because width is not included in equals()/hashCode() calculations:

一个简单的测试告诉我们,如果只有width不同,那么Squareequals()/hashCode()是不够的,因为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());

Let’s fix this by using Eclipse to generate equals()/hashCode() for the Rectangle class:

让我们通过使用 Eclipse 为 Rectangle 类生成 equals()/hashCode() 来解决这个问题。

@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;
}

We must re-generate equals()/hashCode() in the Square class, so Rectangle‘s equals()/hashCode() are invoked. In this generation of code, we’ve selected all the options in the Eclipse dialog, so we see comments, instanceOf comparisons, and if blocks:

我们必须在Square类中重新生成equals()/hashCode(),所以Rectangleequals()/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;
}

Re-running our test from above, we pass now because Square‘s hashCode()/equals() are calculated correctly.

重新运行上面的测试,我们现在通过了,因为SquarehashCode()/equals()被正确计算。

6. Conclusion

6.结论

The Eclipse IDE is very powerful and allows auto-generation of a boilerplate code – getters/setters, constructors of various types, equals(), and hashCode().

Eclipse IDE非常强大,可以自动生成模板代码–getters/setters、各种类型的构造函数、equals()hashCode()

By understanding what Eclipse is doing, we can decrease time spent on these coding tasks. However, we must still use caution and verify our code with tests to ensure we have handled all the expected cases.

通过了解Eclipse在做什么,我们可以减少花在这些编码任务上的时间。然而,我们仍然必须谨慎行事,用测试来验证我们的代码,以确保我们处理了所有预期的情况。

Code snippets, as always, can be found over on GitHub.

像往常一样,可以在GitHub上找到代码片段