重写equals和hashCode时必须考虑哪些问题/陷阱?
当前回答
Equals()方法用于确定两个对象是否相等。
因为10的int值总是等于10。但是这个equals()方法是关于两个对象是否相等的。当我们说对象时,它会有属性。要决定是否相等,就要考虑这些性质。没有必要考虑所有属性来确定相等性,可以根据类定义和上下文来确定相等性。然后equals()方法可以被重写。
无论何时重写equals()方法,我们都应该重写hashCode()方法。如果不是,会发生什么?如果我们在应用程序中使用哈希表,它将不能像预期的那样运行。由于hashCode用于确定存储的值是否相等,因此它不会为键返回正确的对应值。
给出的默认实现是对象类中的hashCode()方法,该方法使用对象的内部地址并将其转换为整数并返回。
public class Tiger {
private String color;
private String stripePattern;
private int height;
@Override
public boolean equals(Object object) {
boolean result = false;
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Tiger tiger = (Tiger) object;
if (this.color == tiger.getColor()
&& this.stripePattern == tiger.getStripePattern()) {
result = true;
}
}
return result;
}
// just omitted null checks
@Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.color.hashCode();
hash = 7 * hash + this.stripePattern.hashCode();
return hash;
}
public static void main(String args[]) {
Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
Tiger siberianTiger = new Tiger("White", "Sparse", 4);
System.out.println("bengalTiger1 and bengalTiger2: "
+ bengalTiger1.equals(bengalTiger2));
System.out.println("bengalTiger1 and siberianTiger: "
+ bengalTiger1.equals(siberianTiger));
System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
System.out.println("siberianTiger hashCode: "
+ siberianTiger.hashCode());
}
public String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
this.height = height;
}
}
示例代码输出:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
其他回答
我发现的一个问题是两个对象包含彼此的引用(一个例子是父/子关系,在父对象上有一个方便的方法来获取所有的子对象)。 例如,在执行Hibernate映射时,这类事情相当常见。
如果在hashCode或equals测试中包含关系的两端,则有可能进入以StackOverflowException结束的递归循环。 最简单的解决方案是在方法中不包括getChildren集合。
理论(适用于语言律师和数学爱好者):
Equals () (javadoc)必须定义等价关系(它必须是自反的、对称的和可传递的)。此外,它必须是一致的(如果对象没有被修改,那么它必须一直返回相同的值)。此外,o.equals(null)必须总是返回false。
hashCode() (javadoc)也必须是一致的(如果对象没有按照equals()进行修改,它必须一直返回相同的值)。
这两种方法的关系是:
当a. = (b)时,则a.hashCode()必须与b. hashcode()相同。
在实践中:
如果你覆盖了一个,那么你也应该覆盖另一个。
使用用于计算equals()的相同字段集来计算hashCode()。
使用Apache Commons Lang库中的优秀辅助类EqualsBuilder和HashCodeBuilder。一个例子:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
还记得:
当使用基于哈希的集合或映射(如HashSet、LinkedHashSet、HashMap、Hashtable或WeakHashMap)时,确保你放入集合中的关键对象的hashCode()在对象在集合中时永远不会改变。确保这一点的防弹方法是使您的密钥不可变,这还有其他好处。
如果您正在使用对象关系映射器(Object-Relationship Mapper, ORM)(如Hibernate)处理持久化的类,如果您不认为这已经不合理地复杂,那么有一些问题值得注意!
惰性加载对象是子类
如果您的对象是使用ORM持久化的,那么在许多情况下,您将使用动态代理来避免过早地从数据存储中加载对象。这些代理被实现为您自己类的子类。这意味着This . getclass () == o.getClass()将返回false。例如:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
如果您正在处理ORM,使用o instanceof Person是唯一能够正确运行的方法。
惰性加载对象具有空字段
orm通常使用getter强制加载惰性加载的对象。这意味着如果person被惰性加载,person.name将为空,即使person. getname()强制加载并返回“John Doe”。根据我的经验,这种情况在hashCode()和equals()中更常见。
如果您正在处理ORM,请确保始终使用getter,并且永远不要在hashCode()和equals()中使用字段引用。
保存一个对象会改变它的状态
持久对象通常使用id字段保存对象的键。当对象第一次保存时,该字段将自动更新。不要在hashCode()中使用id字段。但是你可以在equals()中使用它。
我经常使用的一个模式是
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
但是:你不能在hashCode()中包含getId()。如果这样做,当对象被持久化时,它的hashCode将发生变化。如果对象在HashSet中,您将“永远”找不到它。
在我的Person示例中,我可能会使用getName()来表示hashCode, getId()加上getName()(只是为了偏执)来表示equals()。对于hashCode()来说,如果存在一些“冲突”风险是可以的,但对于equals()来说就绝对不行。
hashCode()应该使用equals()中不变的属性子集
对于继承友好的实现,请查看Tal Cohen的解决方案,如何正确地实现equals()方法?
简介:
在《有效Java编程语言指南》(Addison-Wesley, 2001)一书中,Joshua Bloch声称“根本没有办法扩展可实例化类并在保留等于契约的同时添加一个方面。”塔尔不同意。
他的解决方案是通过调用另一个非对称的blindlyEquals()来实现equals()。blindlyEquals()被子类覆盖,equals()被继承,并且永远不会被覆盖。
例子:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
注意,如果要满足利斯科夫替换原则,equals()必须跨继承层次结构工作。
仍然令人惊讶的是,没有人推荐番石榴库。
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
推荐文章
- 增强的for循环中的Null检查
- c# vs Java Enum(适合c#新手)
- 如何在不强制转换的情况下将double转换为long ?
- for循环和for-each循环在性能上有区别吗?
- 你如何比较两个版本的字符串在Java?
- 为什么在Java和。net中不能修改字符串?
- java.util.Random真的那么随机吗?我怎么能生成52!(阶乘)可能的序列?
- ZoneOffset之间的区别是什么。UTC和ZoneId.of(“UTC”)?
- 类未找到:IntelliJ中的空测试套件
- 将JAR文件添加到Spark作业- Spark -submit
- REST API - dto还是不是?
- JavaFX应用程序图标
- Java:强/软/弱/幻影引用的区别
- 在序列化和反序列化期间JSON属性的不同名称
- 获取Android设备名称