不同对象的哈希代码是相同的



在升级了一个内部自定义依赖项(我们公司开发的库)之后,我在 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 的新实例。我在应用程序中的单元测试之外测试了同样的东西,但仍然是相同的问题。一旦我恢复到旧的依赖代码行为正常,上面的打印语句显示不同的数字!我确定其中一个依赖项导致了此问题,但我无法确定根本原因。 自定义对象是通过相同的依赖项间接拉取的,并且没有实现 equals() 和 hashcode(),它只有

private static final long serialVersionUID = 1L;

查看自定义对象的源代码揭示了此实现

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

提前谢谢你。

显然,基类中发生了一些变化,您只需要找到它并修复它,或者在这个类中实现hashCode()equals()可以接受。

某个地方有人实现了返回 1 的hashCode(),这是愚蠢的。他们最好不要实施它。而且不难找到。只要看看Javadoc的CustomObject,看看它从哪里继承hashCode()

我想你已经意识到你的问题的答案是什么,但是你在更新中添加的代码清楚地表明了这一点:

  1. 您的CustomClass扩展了BaseClass

  2. BaseClass将覆盖Object::hashCode()

  3. 您向我们展示的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方法的默认行为在客观上是错误的。

两个不相等的对象具有相同的哈希代码是可以的。事实上,这是一个数学要求,这是允许的。例如,考虑字符串:有无限多个不相等的字符串("a","aa","aaa"...),但只有2^32个可能的整数值。显然,必须有不同的字符串共享哈希代码。

但是HashSet知道这一点,所以它使用equals的结果以及哈希代码。如果只添加其中一个对象,那么它们不仅具有相同的哈希代码 - 它们是相等的,由equals方法返回。如果没有自定义类的代码,我们无法确定为什么会这样,更不用说它是否是故意的了。

Object 的合约规定,相等的对象必须具有相同的哈希代码。但反之则不然:具有相同哈希代码的对象不必相等。Javadocs明确表示:

  • 如果根据equals(java.lang.Object) 方法,两个对象不相等,则对两个对象中的每一个调用 hashCode 方法必须产生不同的整数结果,则不需要。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

除非类的文档明确告诉您它如何计算其哈希代码,否则您可能不应将其视为已建立的联系人,并且您应该期望它可能会在版本之间更改。

CustomObject类实现(或其祖先之一)是这里的问题。CustomObject的作者(或其祖先之一)以错误的方式覆盖了toStringhashCodeequals方法,而不理解它的语义和含义。以下是解决问题的选项(不一定按该顺序):

  1. 您应该将类中的问题通知依赖库CustomObject作者,并以正确的方式实现或覆盖toStringhashCodeequals方法。但请记住 - 依赖代码作者将来可以再次将您放回这个地方。
  2. 假设CustomObject类不是final- 扩展CustomObject(请注意 - 它最好命名为CustomClass,而不是CustomObject),以便正确实现toStringhashCodeequals方法。在代码中使用此扩展类,而不是CustomObject类。这将给你更好的控制,因为你的依赖代码不会再让你陷入这种麻烦。
  3. 使用AOP在类中引入toStringhashCodeequals方法的重写和正确实现CustomObject。这种方法也像上面的选项 2 一样面向未来。

最新更新