我在Java应用程序(Spring批处理作业)中面临一个奇怪的结果,其中一个内部自定义依赖项 - 我们公司开发的库 - 已经升级。 在代码中升级之后,两个相同类型的新不同对象显示具有相同的哈希码。
CustomObject oj1 = new CustomObject();
oj1.setId(1234L);
CustomObject oj2 = new CustomObject();
oj2.setId(9999L);
System.out.println(oj1); //Prints CustomObject@1
System.out.println(oj2); //Prints CustomObject@1
System.out.println(oj1.hashCode()); //Prints 1
System.out.println(oj2.hashCode()); //Prints 1
在发现具有HashSet变量的单元测试之一只添加了第一个对象并忽略了休止符后,我注意到了这个问题。显然,hashSet正在做本应该做的事情但是对象不应该是相同的并且是具有不同ID的新实例。我在应用程序中测试了单元测试之外的相同内容,但问题仍然存在。一旦我恢复到旧的依赖代码行为正常,上面的打印语句显示不同的数字! 我确信其中一个依赖项导致此问题,我无法确定根本原因。 CustomObject是通过相同的依赖项间接提取的,并且没有实现equals()和hashcode(),它只有
private static final long serialVersionUID = 1L;
查看CustomObject的来源揭示了这种实现
public class CustomObject extends BaseModel implements Serializable
和BaseModel定义了equals和hashCode方法
import org.jvnet.jaxb2_commons.lang.*;
import org.jvnet.jaxb2_commons.locator.ObjectLocator;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import java.io.Serializable;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BaseModel")
@XmlSeeAlso({
CustomObject.class
})
public abstract class BaseModel implements Serializable, Equals2, HashCode2
{
private final static long serialVersionUID = 1L;
public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy2 strategy) {
if ((object == null)||(this.getClass()!= object.getClass())) {
return false;
}
if (this == object) {
return true;
}
return true;
}
public boolean equals(Object object) {
final EqualsStrategy2 strategy = JAXBEqualsStrategy.INSTANCE;
return equals(null, null, object, strategy);
}
public int hashCode(ObjectLocator locator, HashCodeStrategy2 strategy) {
int currentHashCode = 1;
return currentHashCode;
}
public int hashCode() {
final HashCodeStrategy2 strategy = JAXBHashCodeStrategy.INSTANCE;
return this.hashCode(null, strategy);
}
}
提前谢谢。
答案 0 :(得分:3)
显然,基类中的某些内容已发生变化,您只需要找到并修复它,或者在此类中实现hashCode()
和equals()
。
有人在某处实施hashCode()
以返回1,这是愚蠢的。他们最好不要实施它。并且不难发现。只需查看CustomObject
的Javadoc,然后查看它从hashCode()
继承的位置。
答案 1 :(得分:2)
我认为您已经意识到问题的答案是什么,但您在更新中添加的代码清楚地表明了这一点:
您的CustomClass
扩展了BaseClass
。
BaseClass
会覆盖Object::hashCode()
您向我们展示的BaseClass
版本中的覆盖将始终返回1.它使用特定策略调用hashCode(ObjectLocator, HashCodeStrategy2)
方法,但是该方法的实现只是忽略了策略参数。
现在非常清楚,BaseClass
代码的版本只能返回1作为哈希码。但是你说你的代码曾经工作过,而你只改变了依赖关系。从那时起,我们必须得出结论,依赖性已经改变,并且依赖的新版本被破坏了。
如果有任何关于此事的“奇怪”,那就是有人决定像这样实现(新)BaseClass
,并在没有正确测试的情况下发布它。
实际上,您可以通过某种方式让CustomClass
工作。 BaseClass::hashCode(ObjectLocator, HashCodeStrategy2)
方法是公开的,而非最终方法,因此您可以在CustomClass
上覆盖它,如下所示:。
@Override
public int hashCode(ObjectLocator locator, HashCodeStrategy2 strategy) {
return System.identityHashCode(this);
}
事实上,BaseClass
的实施者可能会打算这样做。但我仍然认为BaseClass
已被打破:
hashCode
返回1,因为默认行为是讨厌的。BaseClass
中没有javadoc来解释覆盖该方法的必要性。BaseClass
行为的更改是 API违反更改。没有很好的理由,没有警告,你不应该做那样的事情。equals
方法的默认行为是客观错误的。答案 2 :(得分:1)
两个不相等的对象具有相同的哈希码是很好的。事实上,允许这是一个数学要求。想想字符串,例如:有无数多个不相等的字符串(" a"," aa"," aaa" ...)但只有2 ^ 32个可能的int值。显然,必须有不同的字符串共享哈希码。
但是HashSet知道这一点,所以它使用equals
的结果以及哈希码。如果只添加了其中一个对象,那么它们就不会有相同的哈希码 - 它们是相同的,由equals
方法返回。如果没有自定义类的代码,我们无法确定原因,更不用说它是否有意。
contract for Object表示相等的对象必须具有相同的哈希码。但反过来却不是这样:具有相同哈希码的对象不必相等。 Javadocs明确地说明了这一点:
- 不要求如果两个对象根据equals(java.lang.Object)方法不相等,则在两个对象中的每一个上调用hashCode方法必须产生不同的整数结果。但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能。
除非某个类的文档明确告诉您它如何计算其哈希代码,否则您可能不应该考虑已建立的联系人,并且您应该期望它可以在不同版本之间进行更改。
答案 3 :(得分:0)
CustomObject
类实现(或其祖先之一)就是问题所在。 CustomObject
(或其中一位祖先)的作者以错误的方式覆盖了toString
,hashCode
和equals
方法,却没有理解它的语义及其含义。以下是解决问题的选项(不一定按此顺序):
CustomObject
课程中的问题,并以正确的方式实施或覆盖toString
,hashCode
和equals
方法。但请记住 - 依赖代码作者可以在将来再次让你回到这个地方。CustomObject
课程不是final
- 延伸CustomObject
(请注意 - 最好将其命名为CustomClass
,而不是CustomObject
)才能正确实施toString
,hashCode
和equals
方法。在代码中使用此扩展类而不是CustomObject
类。这将为您提供更好的控制,因为您的依赖代码不能再次让您陷入麻烦。AOP
在toString
课程中介绍hashCode
,equals
和CustomObject
方法的重写和正确实施。这种方法也是未来证据,如上面的选项2所述。