1

在内部自定义依赖项之一(我们在我公司开发的库)升级后,我在 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);
    }

}

先感谢您。

4

4 回答 4

3

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

某个地方的某个人已经实现hashCode()了返回 1,这很愚蠢。他们最好根本不实施它。而且不难找到。只需查看 JavadocCustomObject并查看它从何处继承即可hashCode()

于 2018-05-19T03:18:42.023 回答
2

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

  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 作为默认行为是令人讨厌的。
  • 没有 javadocBaseClass解释需要覆盖该方法。
  • 他们(未经宣布?)对行为的更改BaseClassAPI 重大更改。你不应该做那样的事情,没有很好的理由,没有警告。
  • equals对应方法的默认行为客观上是错误的。
于 2018-05-19T03:37:53.737 回答
1

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

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

Object的合约规定相等的对象必须具有相同的哈希码。但反之则不然:具有相同哈希码的对象不必相等。Javadocs 明确地说明了这一点:

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

除非一个类的文档明确告诉你它是如何计算它的哈希码的,否则你可能不应该认为这是一个已建立的联系,你应该期望它可以在版本之间发生变化。

于 2018-05-19T02:34:11.193 回答
0

CustomObject类实现(或其祖先之一)是这里的问题。作者CustomObject(或其祖先之一)在不了解其语义及其含义的情况下以错误的方式覆盖了toString,hashCode和方法。equals以下是您解决问题的选项(不一定按此顺序):

  1. 您应该通知依赖库的作者有关CustomObjectclass 和 get中的问题toStringhashCode以及equals以正确方式实现或覆盖的方法。但请记住 - 依赖代码作者将来可以让你再次回到这个地方。
  2. 假设CustomObject类不是final- 扩展CustomObject(请注意 - 最好命名为CustomClass, not CustomObject)以正确实现toString,hashCodeequals方法。在您的代码中使用此扩展类而不是CustomObject类。这会给你更好的控制,因为你的依赖代码不能让你再次陷入这个麻烦。
  3. 用于在类AOP中引入toString,hashCodeequals方法的重写和正确实现CustomObject。这种方法也像上面的选项 2 一样是未来的证明。
于 2018-05-19T03:29:19.377 回答