简体   繁体   中英

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. 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. 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. 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

private static final long serialVersionUID = 1L;

Looking at the source of CustomObject reveals this implementation

public class CustomObject extends BaseModel implements Serializable

and BaseModel has the equals and hashCode methods defined

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.

Somebody somewhere has implemented hashCode() to return 1, which is idiotic. 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.

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 .

  2. The BaseClass overrides 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.

Now is pretty clear that that version the BaseClass code can only ever return 1 as the hashcode. 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.


Actually, there is a possible way that you can get your CustomClass to work. The BaseClass::hashCode(ObjectLocator, HashCodeStrategy2) method is public and not final, so you could override it on your CustomClass as follows:.

@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. But I would still argue that BaseClass is broken:

  • Coding the class so that hashCode returns 1 as the default behavior is nasty.
  • There is no javadoc in BaseClass to explain the need to override the method.
  • Their (unannounced?) change to the behavior of BaseClass is an API breaking change. 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.

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. 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. 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. 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:

  • 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. 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. 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. 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. 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. Use this extended class in your code instead of CustomObject class. 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. This approach is also future proof like the option 2 above.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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