Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: clean way to automatically throw UnsupportedOperationException when calling hashCode() and equals()?

We've got an OO codebase where in quite a lot of cases hashcode() and equals() simply don't work, mostly for the following reason:

There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you are willing to forgo the benefits of object-oriented abstraction.

That's a quote from "Effective Java" by Joshua Bloch and there's more on that subject in a great Artima article here:

http://www.artima.com/lejava/articles/equality.html

And we're perfectly fine with that, this is not what this question is about.

The question is: seen that it is a fact that in some case you cannot satisfy the equals() contract, what would be a clean way to automatically make hashcode() and equals() throw an UnsupportedOperationException?

Would an annotation work? I'm thinking about something like @NotNull: every @NotNull contract violation does throw an exception automatically and you have nothing else to do besides annotating your parameters/return value with @NotNull.

It's convenient, because it's 8 characters ("@NotNull") instead of constantly repeating the same verification/throw exception code.

In the case that I'm concerned about, in every implementation where hashCode()/equals() makes no sense, we're always repeating the same thing:

@Override
public int hashCode() {
    throw new UnsupportedOperationException( "contract violation: calling hashCode() on such an object makes no sense" );
}

@Override
public boolean equals( Object o ) {
    throw new UnsupportedOperationException( "contract violation: calling equals() on such an object makes no sense" );
}

However this is error prone: we may by mistake forget to cut/paste this and it may results in users misusing such objects (say by trying to put them in the default Java collections).

Or if annotation can't be made to create this behavior, would AOP work?

Interestingly the real issue it the very presence of hashCode() and equals() at the top of the Java hierarchy which simply makes no sense in quite some cases. But then how do we deal with this problem cleanly?

like image 923
SyntaxT3rr0r Avatar asked Feb 05 '10 07:02

SyntaxT3rr0r


People also ask

What happens if we do not override hashCode () and equals () in HashMap?

If you don't override hashcode() then the default implementation in Object class will be used by collections. This implementation gives different values for different objects, even if they are equal according to the equals() method.

What is the relationship between hashCode () and equals () method in java?

General contract associated with hashCode() methodIf two objects are equal(according to equals() method) then the hashCode() method should return the same integer value for both the objects.

Which class does override the equals () and hashCode () methods?

The Team class overrides only equals(), but it still implicitly uses the default implementation of hashCode() as defined in the Object class. And this returns a different hashCode() for every instance of the class.


2 Answers

I agree with your assessment of this being a problem with hashCode and equals being defined in Object in the first place. I've long held the view that equality should be handled in the same way as ordering - with an interface saying "I can be compared with an instance of X" and another saying "I can compare two instances of X".

On the other hand, has this actually caused any bugs for you? Have people been trying to use equals and hashCode where they shouldn't? Because even if you can make every single class in your codebase throw an exception when those methods are called inappropriately, that won't be true of other classes you're using, whether from the JDK or third party libraries.

I'm sure you could do this with AOP of some form or other, whether that's normal annotation processing or something else - but do you have evidence that the reward would be worth the effort?

Another way of looking at it: this is only in the case where you're extending another class which already overrides hashCode and equals, right? Otherwise you can use the "equality = identity" nature of Object's hashCode/equals methods, which can still be useful. Do you have very many classes which fall into this category? Could you not just write a unit test to find all such types via reflection, and check that those types throw an exception when you call hashCode/equals? (This could either be automated if they have a parameterless constructor, or have a manual list of types which have been checked - the unit test could fail if there's a new type which isn't on the "known good" list.)

like image 99
Jon Skeet Avatar answered Sep 18 '22 12:09

Jon Skeet


I don’t see why you think that "in some case you cannot satisfy the equals() contract"? The meaning of equality is defined by the class. Thus, using Object’s equal is perfectly valid. If you’re not overriding equals then you’re defining each instance as being unique.

There seems to be a misconception that equals is one of those methods that always needs overriding, and that it must check all of its fields. I would argue for the opposite – don’t override equals unless your definition of equality differs.

I also disagree with the artima article, in particular “Pitfall #3: Defining equals in terms of mutable fields”. It’s perfectly valid for a class to defined its equality based on mutable fields. It’s up the user to be aware of this when using it with collections. If a mutable object defines its equality on its mutable state, then don't expect two instances to be equals after one has changed.

I think that throwing UnsupportedOperation violates the sprint of equals. Object’s equals states:

The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).

So, I should be able to call equals and get a true or false value depending on either Object’s equals definition or the overridden equals definition.

like image 40
Steve Kuo Avatar answered Sep 18 '22 12:09

Steve Kuo