Type-parameterized complement to Object.equals(Object)

Remi Forax forax at univ-mlv.fr
Sat Apr 18 11:25:28 UTC 2020


Hi Dmytro,
this was discussed at length at the time Scala was the new kid,
the solution you propose for Java does not work because you can not implement the same interface with different type arguments.

Let say i've a class Vehicle and a subclass Car, using Equable<T>, i will want to write
  class Vehicle implements Equable<Vehicle> { ... }
  class Car extends Vehicle implements Equable<Car> { ... }

The other good reason to not do any changes on equality now is that we may revisit how equality works if we introduce operator overloading on inline type as part of Valhalla.

You can also note that the status quo is not as bad as you may think because all IDEs provide an inspection/analysis to detect such case.

regards,
Rémi

----- Mail original -----
> De: "dmytro sheyko" <dmytro.sheyko.jdk at gmail.com>
> À: "discuss" <discuss at openjdk.java.net>
> Envoyé: Samedi 18 Avril 2020 09:01:37
> Objet: Fwd: Type-parameterized complement to Object.equals(Object)

> Hello,
> 
> I need a sponsor for following JDK change.
> 
> Summary
> -------
> 
> Provide means for type safe comparison for equality by introducing
> type-parameterized interface `java.lang.Equable<T>` with method `boolean
> equ(T)`.
> (The actual names of the interface and the method is subject to discussion.)
> 
> 
> Goals
> -----
> 
> The primary goal is to avoid programmer mistakes when values of different
> and
> incomparable types are compared using `equals` method, which always returns
> `false`
> in such cases according to its contract.
> 
> 
> Non-Goals
> ---------
> 
> Custom hashers and equators for Collection Framework are out of the scope
> of this proposal.
> 
> 
> Motivation
> ----------
> 
> This is quite common approach to use `equals` method to compare value for
> equality.
> However, it may happen that the values have different incomparable types
> and, therefore,
> the result of such a comparison is doomed to `false`. For example,
> instances of
> `java.lang.Integer` and `java.lang.Long` will never be equal even though
> they represent
> the same numerical value. I.e. this code
> 
>    Long l = 42L;
>    Integer i = 42;
>    if (l.equals(i)) { // always false
>        ...
>    } else {
>        ...
>    }
> 
> is very likely a mistake because the else-branch will never be taken. The
> same applies to attempts
> to compare among themselves instances of `java.lang.Short`,
> `java.lang.String`, `java.util.Date`,
> `java.util.List`, `java.util.Set`, `java.util.Map` etc. The reason why such
> erroneous comparisons
> remain unnoticed is the fact that the `equals` method accepts any value.
> One way to make mistakes
> with such comparisons more visible it to use `compareTo` method.
> 
>    Long l = 42L;
>    Integer i = 42;
>    if (l.compareTo(i) == 0) { // compilation error
>        ...
>    } else {
>        ...
>    }
> 
> However this approach has some drawbacks:
> * It's counter-intuitive and unnecessarily verbose. It's not clear at
> glance why `compareTo`
>  is used instead of `equals`.
> * It can be inefficient because there are cases when an attempt to figure
> out whether given value
>  is less or greater that the other value requires more effort than an
> attempt to figure out
>  whether the values are just not equal. For example, when we compare
> strings and have noticed
>  that the strings have different length we can stop here and do not scan
> them in order to
>  figure out, which one is greater than the other. Another example is the
> class that represents
>  rational number as a reduced fraction. To make sure that the rational
> numbers are equal, we can
>  just compare their numerators and denominators. But in order to find out
> which one is greater
>  than the other we can't avoid arithmetic operations (multiplications).
> * It's not applicable to the classes that do not implement
> `java.lang.Comparable<T>`. For example,
>  collections, complex numbers, points in 2D or 3D space etc.
> 
> Therefore it's proposed to introduce interface `Equable` in order to
> explicitly mark comparable classes.
> 
>    public interface Equable<T> {
>        boolean equ(T o);
>    }
> 
> Let `Comparable` extend it and provide default method for `equ`.
> 
>    public interface Comparable<> extends Equable<T> {
>        int compareTo(T o);
> 
>        default boolean equ(T o) {
>            return compareTo(o) == 0;
>        }
>    }
> 
> Provide efficient implementation of `equ` method for some JDK classes, at
> least those that are in
> `java.lang` package. Also it would be desiable to let `java.util.List`,
> `java.util.Set`,
> `java.util.Map` and `java.util.Map.Entry` to extend `Equable` interface as
> well (but without
> extending `Comparable`).
> 
> ===
> 
> I am sending a set of patches. I split the whole change into several
> logical parts.
> 
> * equable-0.patch contain `java.lang.Equable`, `java.lang.Comparable`,
> `java.util.Objects.equ`
> 
> * equable-1.patch contain `j.l.Boolean`, `j.l.Byte`, `j.l.Character`,
> `j.l.Double`, `j.l.Enum`,
>  `j.l.Float`, `j.l.Integer`, `j.l.Long`, `j.l.Short` and `j.l.String`
> 
> * equable-2.patch contain `j.u.Optional`, `j.u.OptionalDouble`,
> `j.u.OptionalInt`,
>  `j.u.OptionalLong`
> 
> * equable-3.patch contain `j.u.List`, `j.u.AbstractList`, `j.u.Set`,
> `j.u.AbstractSet`, `j.u.Map`,
>  `j.u.AbstractMap`
> 
> * equable-4.patch is a tricky one. It contains
> `com.sun.tools.jdi.EventSetImpl`. Previously this class
>  extended `java.lang.ArrayList` and implemented `java.lang.Set` through
> `EventSet`. Therefore it appeared
>  that it implemented both `java.util.List` and `java.util.Set` at the same
> time. This is not right,
>  because these interfaces have different contracts regarding equality. So
> I had to change it by extending
>  `java.lang.AbstractSet`.
> 
> Thank you,
> Dmytro


More information about the discuss mailing list