重写equals和hashCode时必须考虑哪些问题/陷阱?
当前回答
逻辑上我们有:
a.getClass().equals(b.getClass()) && a.equals(b) ⇒ a.hashCode() == b.hashCode()
但反之亦然!
其他回答
超类中有两个方法,如java.lang.Object。我们需要将它们重写为自定义对象。
public boolean equals(Object obj)
public int hashCode()
相等的对象只要相等就必须产生相同的哈希码,然而不相等的对象不需要产生不同的哈希码。
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
如果你想了解更多,请点击这个链接http://www.javaranch.com/journal/2002/10/equalhash.html
这是另一个例子, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
玩得开心!@ .@
我发现的一个问题是两个对象包含彼此的引用(一个例子是父/子关系,在父对象上有一个方便的方法来获取所有的子对象)。 例如,在执行Hibernate映射时,这类事情相当常见。
如果在hashCode或equals测试中包含关系的两端,则有可能进入以StackOverflowException结束的递归循环。 最简单的解决方案是在方法中不包括getChildren集合。
对于继承友好的实现,请查看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()必须跨继承层次结构工作。
如果您正在使用对象关系映射器(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()中不变的属性子集
仍然令人惊讶的是,没有人推荐番石榴库。
//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());
}
推荐文章
- 漂亮地打印Java集合(toString不返回漂亮输出)
- 静态嵌套类在Java,为什么?
- 如何防止Eclipse在启动时挂起?
- Optional和Optional的区别是什么?flatMap和Optional.map?
- Java中枚举的命名:单数还是复数?
- JavaBean和POJO之间的区别是什么?
- 注释在Java中如何使用,在哪里使用?
- 如何在Ubuntu下安装JDK 11 ?
- Spring Boot -无法确定数据库类型为NONE的嵌入式数据库驱动程序类
- 如何转换/解析从字符串到字符在java?
- 如何在Android中动态更改菜单项文本
- 如何比较两个没有时间部分的日期?
- 如何在Java中找到给定类的所有子类?
- 匿名类的访问构造函数
- 从Java访问Kotlin扩展函数