概述
本文将探讨如何在Eclipse IDE中自动生成equals()
和hashCode()
方法。我们将展示Eclipse的强大且便利的代码自动生成功能,并强调尽管如此,对代码的精心测试仍然是必要的。
规则
在Java中,equals()
用于检查两个对象是否等价。测试这个方法的一个好方法是确保对象满足对称性、反射性和传递性。对于三个非空对象a、b和c:
- 对称性 - 如果a.equals(b)`,则b.equals(a)*
- 反射性 - a.equals(a)
- 传递性 - 如果a.equals(b)并且b.equals(c),则a.equals(c)
hashCode()
必须遵守一个规则:
- 对于
equals()
为真的两个对象,它们的hashCode()
值必须相同
包含基本类型的类
让我们考虑一个只包含基本成员变量的Java类:
public class PrimitiveClass {
private boolean primitiveBoolean;
private int primitiveInt;
// constructor, getters and setters
}
使用Eclipse IDE,我们可以通过“源”>“生成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());
包含集合和泛型的类
现在,考虑一个更复杂的Java类,带有集合和泛型:
public class ComplexClass {
private List<?> genericList;
private Set<Integer> integerSet;
// constructor, getters and setters
}
再次使用Eclipse的“源”>“生成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;
}
生成的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());
继承
考虑使用继承的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
}
如果我们尝试在Square
类上使用“源”>“生成hashCode()
和equals()
”,Eclipse会警告我们:“超类Rectangle
没有重新声明equals()
和hashCode()
:生成的代码可能无法正常工作”。
类似地,当我们尝试在Rectangle
类上生成hashCode()
和equals()
时,也会收到关于超类Shape
的警告。
尽管有警告,Eclipse仍然允许我们继续。在Rectangle
的情况下,它扩展了一个不能实现hashCode()
或equals()
的抽象Shape
类,因为没有具体的成员变量。在这种情况下,我们可以忽略Eclipse的警告。
然而,Square
类继承了Rectangle
的width
和length
成员变量,以及它自己的颜色变量。在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;
}
快速测试表明,如果仅width
不同,Square
的equals()
和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());
让我们修复这个问题,使用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;
}
我们需要重新生成Square
类的equals()
和hashCode()
,以便调用Rectangle
的版本。在这个生成的代码中,我们在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
的hashCode()
和equals()
计算正确。
结论
Eclipse IDE非常强大,能自动生成模板代码,如getter和setter、各种构造函数、equals()
和hashCode()
。理解Eclipse的运作方式可以帮助我们减少这类编码任务的时间。但仍然需要谨慎,并通过测试确保处理了所有预期情况。
代码片段如往常一样可以在GitHub上找到。