简体   繁体   English

不同对象的哈希码相同

[英]hash code of different objects are the same

I am facing a bizarre outcome in a java application (Spring batch job) after one of the internal custom dependencies -a library we have developed in my company- has been upgraded. 内部自定义依赖项之一(我们在我公司开发的库)已升级后,我在Java应用程序(春季批处理作业)中面临一个奇怪的结果。 After the upgrade in the code two new different objects of the same type show to have the same hash code. 在代码中升级后,相同类型的两个新的不同对象显示具有相同的哈希码。

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

I noticed this issue after realizing that one of the unit test which has a HashSet variable was only adding the very first object and ignoring the rests. 在意识到具有HashSet变量的单元测试中的一个仅添加第一个对象而忽略其余对象后,我注意到了这个问题。 Obviously the hashSet is doing what is supposed to do but the objects are not supposed to be the same and are new instances with different Ids. 显然,hashSet正在执行应做的事情,但是对象不应该是相同的,并且是具有不同ID的新实例。 I tested the same thing outside of the unit test within the application and still the same issue. 我在应用程序内的单元测试之外测试了相同的东西,并且仍然是相同的问题。 As soon as I revert back to the old dependency code behaves normally and the above print statements show different numbers! 一旦我恢复到旧的依赖关系,代码就会正常工作,并且上面的打印语句会显示不同的数字! I am sure one of the dependencies is causing this issue abut I am not able to determine the root cause. 我确定其中一个依赖性导致了此问题,但是我无法确定根本原因。 CustomObject is being pulled indirectly through that same dependency and does not have equals() and hashcode() implemented, it only has 通过相同的依赖关系间接拉取CustomObject,并且没有实现equals()和hashcode(),它仅具有

private static final long serialVersionUID = 1L;

Looking at the source of CustomObject reveals this implementation 查看CustomObject的源代码可以揭示此实现

public class CustomObject extends BaseModel implements Serializable

and BaseModel has the equals and hashCode methods defined 并且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);
    }

}

Thank you in advance. 先感谢您。

Clearly something has changed in a base class, and you will just have to find it and fix it, or else implement hashCode() and equals() acceptably in this class. 显然,基类中的某些内容已经发生了变化,您只需要查找并修复它,或者在该类中可接受地实现hashCode()equals()

Somebody somewhere has implemented hashCode() to return 1, which is idiotic. 某处某人已实现hashCode()以返回1,这是愚蠢的。 They would have been better off not to implement it at all. 他们本来最好不要完全执行它。 And it's not hard to find. 而且不难发现。 Just look at the Javadoc for CustomObject and see where it inherits hashCode() from. 只需查看CustomObject的Javadoc,看看它从何处继承hashCode()

I think you have already realized what the answer to your question is, but the code that you added in your update makes it clear: 我认为您已经意识到问题的答案是什么,但是您在更新中添加的代码很清楚:

  1. Your CustomClass extends the BaseClass . 您的CustomClass扩展了BaseClass

  2. The BaseClass overrides Object::hashCode() BaseClass覆盖Object::hashCode()

  3. The override in the version of BaseClass that you showed us will always return 1. It is calling a hashCode(ObjectLocator, HashCodeStrategy2) method with a specific strategy, but the implementation of that method simply ignores the strategy argument. 您向我们展示的BaseClass版本中的替代将始终返回1。它使用特定策略调用hashCode(ObjectLocator, HashCodeStrategy2)方法,但是该方法的实现只是忽略了策略参数。

Now is pretty clear that that version the BaseClass code can only ever return 1 as the hashcode. 现在很清楚,该版本的BaseClass代码只能返回1作为哈希码。 But you say that your code used to work, and you only changed the dependency. 但是您说您的代码曾经可以工作,并且只更改了依赖关系。 From that, we must conclude that the dependency has changed, and that the new version of the dependency is broken. 由此,我们必须得出结论,依赖关系已更改,并且依赖关系的新版本已损坏。


If anything is "bizarre" about this, it is that someone decided to implement the (new) BaseClass like that, and release it without testing it properly. 如果对此有什么“怪异”,那就是有人决定实现这样的(新) BaseClass ,并在没有正确测试的情况下将其释放。


Actually, there is a possible way that you can get your CustomClass to work. 实际上,有一种可能的方法可以使您的CustomClass工作。 The BaseClass::hashCode(ObjectLocator, HashCodeStrategy2) method is public and not final, so you could override it on your CustomClass as follows:. BaseClass::hashCode(ObjectLocator, HashCodeStrategy2)方法是公共的,而不是最终的,因此您可以按以下方法在CustomClass上重写它:

@Override
public int hashCode(ObjectLocator locator, HashCodeStrategy2 strategy) {
    return System.identityHashCode(this);
}

And indeed, it may be that the implementers of BaseClass intend you to do this. 确实, BaseClass的实现者可能打算让您这样做。 But I would still argue that BaseClass is broken: 但我仍然会认为BaseClass已损坏:

  • Coding the class so that hashCode returns 1 as the default behavior is nasty. 对类进行编码,以使hashCode返回1,因为默认行为是讨厌的。
  • There is no javadoc in BaseClass to explain the need to override the method. BaseClass没有Javadoc来解释重写该方法的需要。
  • Their (unannounced?) change to the behavior of BaseClass is an API breaking change. 他们对BaseClass行为的更改(未宣布?)是API的重大更改。 You shouldn't do stuff like that, without very good reason, and without warning. 没有充分的理由和警告,您不应该这样做。
  • The default behavior of the corresponding equals method is objectively wrong. 相应的equals方法的默认行为在客观上是错误的。

It's fine for two non-equal objects to have the same hash code. 两个不相等的对象具有相同的哈希码是可以的。 In fact, it's a mathematical requirement that this be allowed. 实际上,允许这样做是数学上的要求。 Think about strings, for instance: there are infinitely many non-equal strings ("a", "aa", "aaa"...) yet only 2^32 possible int values. 例如,考虑一下字符串:存在无限多个不相等的字符串(“ a”,“ aa”,“ aaa” ...),但只有2 ^ 32个可能的int值。 Clearly there must be different strings that share a hash code. 显然,必须有共享哈希码的不同字符串。

But HashSet knows this, so it uses the result from equals as well as the hash code. 但是HashSet知道这一点,因此它使用来自equals的结果以及哈希码。 If only one of the objects is being added, then they don't just have there same hash code -- they are equal, as returned by the equals method. 如果仅添加一个对象,则它们不只是具有相同的哈希码-它们相等,由equals方法返回。 We can't determine why this is, let alone whether it's intentional, without the custom class's code. 没有自定义类的代码,我们无法确定为什么这样做,更不用说它是否是有意的了。

The contract for Object says that equal objects must have the same hash code. 对象合同规定 ,相等的对象必须具有相同的哈希码。 But the converse is not true: objects with the same hash code do not have to be equal. 但是反之则不正确:具有相同哈希码的对象不必相等。 The Javadocs say this explicitly: Javadocs明确地这样说:

  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. 不是必需的:如果两个对象根据equals(java.lang.Object)方法是不相等的,然后调用hashCode方法在各两个对象的必须产生不同的整数结果。 However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables. 但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

Unless a class's docs explicitly tell you how it computes its hash code, you should probably not consider that an established contact, and you should expect that it could change between versions. 除非类的文档明确告诉您它如何计算其哈希码,否则您可能不应该认为已建立联系,并且应该期望它可以在版本之间进行更改。

CustomObject class implementation (or one of its ancestors) is the problem here. CustomObject类的实现(或其祖先之一)。 Author of CustomObject (or one of its ancestors) has overridden toString , hashCode and equals methods in a wrong way without understanding the semantics of it and the implications of it. CustomObject作者(或其祖先之一)以错误的方式覆盖了toStringhashCodeequals方法,而没有理解其语义及其含义。 Here are your options (not necessarily in that order) to fix the problem: 以下是解决问题的选项(不一定按此顺序):

  1. You should notify the author of your dependency library about the problem in CustomObject class and get toString , hashCode and equals methods implemented or overridden in the right way. 您应该将CustomObject类中的问题通知给依赖库的作者,并以正确的方式获取toStringhashCodeequals方法。 But remember - dependency code author can put you back in this place again in future. 但是请记住-依赖性代码作者可以在将来再次将您带回到这个地方。
  2. Assuming CustomObject class is not final - extend CustomObject (please note - it is better named as CustomClass , not CustomObject ) to have right implementation of toString , hashCode and equals methods. 假设CustomObject类不是final -扩展CustomObject (请注意-最好将其命名为CustomClass ,而不是CustomObject )以正确实现toStringhashCodeequals方法。 Use this extended class in your code instead of CustomObject class. 在代码中使用此扩展类,而不要使用CustomObject类。 This will give you better control because your dependency code cannot put you in this trouble again. 这将为您提供更好的控制,因为您的依赖关系代码不会再使您陷入此麻烦。
  3. Use AOP to introduce overridden and right implementation of toString , hashCode and equals methods in CustomObject class. 使用AOPCustomObject类中引入toStringhashCodeequals方法的重写和正确实现。 This approach is also future proof like the option 2 above. 像上面的选项2一样,这种方法也是未来的证明。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM