17

What's the reasoning behind decision to include these methods in the java.lang.Object? Equality and hashing doesn't make sense for many classes.

It would be more logical to make two interfaces:

interface Equalable {
    boolean equals(Equalable other);
}

interface Hashable extends Equalable {
    int hashCode();
}

For example HashSet definition might look like

class HashSet<T extends Hashable> ...

It would prevent one of the common beginner mistakes - using set of items without implementing equals/hashCode.

Kowser
  • 8,123
  • 7
  • 40
  • 63
vbezhenar
  • 11,148
  • 9
  • 49
  • 63
  • Take a look [here](http://msmvps.com/blogs/jon_skeet/archive/2008/12/05/redesigning-system-object-java-lang-object.aspx) for a good article on something related (by Jon Skeet). – Jordão Nov 13 '11 at 18:50
  • 2
    link seems dead. [This](http://codeblog.jonskeet.uk/2008/12/05/redesigning-system-object-java-lang-object/) may be the same article. – tkokasih Apr 27 '15 at 01:58
  • 1
    It was a mistake – Matt Timmermans Jan 06 '20 at 04:43

10 Answers10

15

When we implement an interface we inject (or accept) the contract defined by the interface.

Equalable & Hashable are two different contracts. But if we take a look closely then we will see that both of them depend on each other, which means they are part of a single interface, something like EqualableAndHashable.

Now the obvious question is, whether they should be part of this new EqualableAndHashable interface or Object?

Let's find out. We have == (equal operator) to check equality of two objects. == operator confirms whether values/references are equal for two different primitives/objects. But this is not always possible to answer just by checking with the == operator.

Now question is, whether this equality, which is also a contract, should be injected via interfaces or part of the Object class?

If we take a look, we can't just say something like:

TypeX does not guarantee the equality contract.

It will become a chaos if some object types offer equality and some do not. Which means object of TypeX must honor the equality contract which is true for all other object types as well. So, it must not inject equality from a interface, because equality should be the part of the contract for any object by default, otherwise it will create chaos.

So we need Objects to come up with implementation of equals. But it can't implement only the equals method, it also needs to implement the hashcode method.

Kowser
  • 8,123
  • 7
  • 40
  • 63
  • 2
    `=` is actually the assignment operator ;) – Dave Newton Nov 14 '11 at 00:24
  • 3
    I don't understand the "chaos" you mention. I implement all the time these methods to use my classes in Sets and Maps. It would be simpler for me to avoid the default implementation at all costs, so I'm for removing these methods from Object. :D – The Impaler Oct 24 '17 at 19:04
2

The default implementation in java.lang.Object makes sense. Often times, it's good enough. In JPA/web applications, I find myself very rarely if ever overriding equals and hashCode.

A better question might be: for immutable value objects like String, Long etc., why can't you override the == operator to call equals(), like you can in C#? I've seen far more errors because of that than I have of the default equals/hashCode not doing the right thing. For example,

Long x = obj.getId(); 
Long y = obj2.getId();  
if (x == y) { // oops, probably meant x.equals(y)! }

It's a fair question, though, why the default methods aren't locked behind a tagging interface like the default Object.clone(). There's a default implementation but you have to explicitly acknowledge that you want to use it by implementing Cloneable. There could just as easily have been a similar tagging interface like Collectible or Equatable, and then the signature for collections methods could have been Equatable instead of Object.

wrschneider
  • 17,913
  • 16
  • 96
  • 176
1

Add a note to @Kowser's answer about 'chaos':

The implicit logic of equals requires that it must meet the following characteristics:

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • ...

Obviously, if the equals method is not defined in the Object, it will break 'symmetric' and 'transitive', which is equivalent to breaking the intrinsic logic of equals.

1

(Personally, if they were in an interface, I'd put them both in there to avoid at least one class of equals/hashCode mistakes.)

I think you'd want an implementation in Object, as a fall-back mechanism, meaning everything would have an implementation anyway, interface or not.

I suspect a lot of it is historical; programming Java today looks quite a bit different than programming Java back then did.

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
1

Mhh not sure but when Java 1.0 was released generics did not exist yet. They were added in Java 5.0 in 2004.. so your proposal could not be implemented for Java 1.0

hage
  • 5,966
  • 3
  • 32
  • 42
  • 2
    If `TreeSet` could require keys to implement `Comparable` if no `Comparator` was specified, why couldn't HashSet require `Hashable`? There is more to it than generics. – meriton Nov 13 '11 at 21:08
0

Any object, regardless of its type, can sensibly answer whether it's equivalent to any other object, even if the other object's type is one it's never heard of. If it's never heard of the other object's type, that fact alone is sufficient for it to report that it isn't equivalent to the latter object.

supercat
  • 77,689
  • 9
  • 166
  • 211
0

Its a generic implementation. You are supposed to override the implementation if you need them. Otherwise you have a reasonable default implementation.

At least the equals is a must have. Creating interfaces for basic operations probably would involve to much overhead.

Udo Held
  • 12,314
  • 11
  • 67
  • 93
0

If you have a list of objects and you call the contains method what should Java do? I think the default implementation(comparing references) is a decent decision. This way you won't have to implement yourself equals and hashcode for every class you use in a collection.

Petar Minchev
  • 46,889
  • 11
  • 103
  • 119
0

Originally, in Java, there were no generics. This was worked around by allowing any Object to be a member of any collection, and thus any Object needed hashCode and equals. By now it's too entrenched to change.

ibid
  • 3,891
  • 22
  • 17
  • 1
    The absence of generics is irrelevant. For instance, it didn't stop `TreeSet` from requiring keys to implement `Comparable` if no `Comparator` was specified. – meriton Nov 13 '11 at 21:06
0

Really, it's just for convenience, and it's better this way. Well, think about what it would take to do object equality if you didn't have the .equals method:

AreEqual(Object obj1,Object obj2) {
  if(!obj1 instanceof Equalable) return false;
  if(!obj2 instanceof Equalable) return false;
  return ((Equalable)(obj1).equals((Equalable)obj2);
}

This is true even for a hashCode. Sometimes reference equality is enough. If you made HashSet only accept objects that implement Hashable, then you'd have to explicitly make your classes Hashable, even though you only want reference equality. And you've reduced the universality of an otherwise great data structure.

It's better to have Object have a default (and sometimes sufficient) .equals and .hashCode function and cause a few problems for new comers, than to make frequent users of the language trudge through more red tape.

heneryville
  • 2,853
  • 1
  • 23
  • 30