3 ms·
The mistake wasn't in giving Object those functions. You get "=" behavior for all types in a given language, and it's nice to be able to control this somehow (i
by kstenerud 7y ago
The mistake wasn't in giving Object those functions. You get "=" behavior for all types in a given language, and it's nice to be able to control this somehow (i.e. they were actually thinking a little bit about the equality problem here). The problem came when they decided that the default behavior should be a pointer comparison rather than a member-by-member sub-equals. As for hashCode(), it should have thrown an exception as the default behavior, because there's no way a language designer can anticipate what makes a proper hash for class XYZ.
- barrkel 7y agoRecursive by default (which is what member-by-member implies) leads trivially to infinite recursion in reference loops, which your default equals would need to guard against, greatly increasing its complexity. Reference equality is generally fine for mutable types, you probably only want value equality for immutable types. If you use value equality for mutable types, you start getting into trouble when you use them as e.g. hash keys.
- kstenerud 7y agoYou are correct, but it's far better to have expensive-by-default but safe operations vs the counterintuitive mess you end up with when comparing references by default. The point is that you need to counter human nature, which is to not do work you can get away with not doing. If the worst case result of not doing the work (i.e. not creating an equals method) is slow performance, that's better than getting incorrect results. You'll have a much harder time designing a useable language if your understanding of human psychology is poor. Besides, in 90% of cases, you won't have potentially recursive types, and so a smart compiler can optimize for this when generating a default equals method. In fact, many cases could just degenerate into a memcmp.
- renox 7y agoI'm not so sure, when Java was created computers weren't that fast.. Of course, now with computers fast enough that Python and JavaScript are king, sure!
- lazulicurio 7y agoMember-by-member sub-equals might make more sense in some situations, but not all (as the article discusses, equality is context-dependent). By making equals() an instance method, encapsulation is broken by tying an external concern (equality) to the internal implementation of a class. It would have been better if Object had no instance equals() or hashCode() method at all, with static methods providing non-reference-equality semantics. Something like: @FunctionalInterface interface Equivalence<T> { boolean equals(T one, T other); } @FunctionalInterface interface Comparison<T> extends Equivalence<T> { int compare(T one, T other); @Override default boolean equals(final T one, final T other) { return compare(one, other) == 0; } } @FunctionalInterface interface HashFunction<T> { int hashCode(T obj); } interface Equalitor<T> extends Equivalence<T>, HashFunction<T> { } interface Comparator<T> extends Comparison<T>, Equalitor<T> { } interface Equatable { Equalitor<? super Object> equivalence(); } interface Comparable extends Equatable { @Override Comparator<? super Object> equivalence(); } final class Objects { static int compare(final Object one, final Object other) { if (one == other) { return 0; } if (one == null) { return -1; } if (other == null) { return 1; } if (one instanceof Comparable oneCmp && other instanceof Comparable otherCmp) { final Comparison<? super Object> cmp = oneCmp.equivalence(); if (equals(cmp, otherCmp.equivalence())) { return cmp.compare(one, other); } } throw new SomeRuntimeException(); } static boolean equals(final Object one, final Object other) { if (one == other) { return true; } if (one instanceof Equatable oneEq && other instanceof Equatable otherEq) { final Equivalence<? super Object> eq = oneEq.equivalence(); if (equals(eq, otherEq.equivalence())) { return eq.equals(one, other); } } return false; } static int hashCode(final Object obj) { if (obj instanceof Equatable objEq) { final HashFunction<? super Object> hf = objEq.equivalence(); return hf.hashCode(obj); } return someIntrinsicHashCode(obj); } } (The generic parameters aren't correct, but to give a rough idea)