JDK-8261688 : Augment discussion of equivalence classes in Object.equals and comparison methods
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang
  • Priority: P4
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 17
  • Submitted: 2021-02-13
  • Updated: 2021-02-17
  • Resolved: 2021-02-16
Related Reports
CSR :  
Description
Summary
-------
Augment the discussion of requirements for `equals` and `compareTo` methods, including an update to `BigDecimal`, whose natural order is *not* consistent with equals.

Problem
-------

The related requirements for `equals` and `compareTo` methods is not necessarily clearly explained in the relevant javadoc.

Solution
--------

Update the specs of `Object.equals`, `java.lang.Comparable`, `java.math.BigDecimal`, etc. to better explain the relationships at hand. Use @implSpec, @apiNote, and related tags in Object and elsewhere. These new tags are not spec changes per se, but will affect what text gets @inheritDoc'ed by subclasses.

Specification
-------------

    diff --git a/src/java.base/share/classes/java/lang/Comparable.java b/src/java.base/share/classes/java/lang/Comparable.java
    index 0b5075c5e5f..eede91e30e2 100644
    --- a/src/java.base/share/classes/java/lang/Comparable.java
    +++ b/src/java.base/share/classes/java/lang/Comparable.java
    @@ -64,9 +64,12 @@
      *
      * Virtually all Java core classes that implement {@code Comparable} have natural
      * orderings that are consistent with equals.  One exception is
    - * {@code java.math.BigDecimal}, whose natural ordering equates
    - * {@code BigDecimal} objects with equal values and different precisions
    - * (such as 4.0 and 4.00).<p>
    + * {@link java.math.BigDecimal}, whose {@linkplain java.math.BigDecimal#compareTo natural ordering} equates
    + * {@code BigDecimal} objects with equal numerical values and different representations
    + * (such as 4.0 and 4.00). For {@link java.math.BigDecimal#equals
    + * BigDecimal.equals()} to return true, the representation and
    + * numerical value of the two {@code BigDecimal} objects must be the
    + * same.<p>
      *
      * For the mathematically inclined, the <i>relation</i> that defines
      * the natural ordering on a given class C is:<pre>{@code
    @@ -83,7 +86,12 @@
      * the class's {@link Object#equals(Object) equals(Object)} method:<pre>
      *     {(x, y) such that x.equals(y)}. </pre><p>
      *
    - * This interface is a member of the
    + * In other words, when a class's natural ordering is consistent with
    + * equals, the equivalence classes defined by the equivalence relation
    + * of the {@code equals} method and the equivalence classes defined by
    + * the quotient of the {@code compareTo} method are the same.
    + *
    + * <p>This interface is a member of the
      * <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
      * Java Collections Framework</a>.
      *
    @@ -100,9 +108,9 @@
          * than, equal to, or greater than the specified object.
          *
          * <p>The implementor must ensure
    -     * {@code sgn(x.compareTo(y)) == -sgn(y.compareTo(x))}
    +     * {@link Integer#signum signum}{@code (x.compareTo(y)) == -signum(y.compareTo(x))}
          * for all {@code x} and {@code y}.  (This
    -     * implies that {@code x.compareTo(y)} must throw an exception iff
    +     * implies that {@code x.compareTo(y)} must throw an exception if and only if
          * {@code y.compareTo(x)} throws an exception.)
          *
          * <p>The implementor must also ensure that the relation is transitive:
    @@ -110,22 +118,17 @@
          * {@code x.compareTo(z) > 0}.
          *
          * <p>Finally, the implementor must ensure that {@code x.compareTo(y)==0}
    -     * implies that {@code sgn(x.compareTo(z)) == sgn(y.compareTo(z))}, for
    +     * implies that {@code signum(x.compareTo(z)) == signum(y.compareTo(z))}, for
          * all {@code z}.
          *
    -     * <p>It is strongly recommended, but <i>not</i> strictly required that
    +     * @apiNote
    +     * It is strongly recommended, but <i>not</i> strictly required that
          * {@code (x.compareTo(y)==0) == (x.equals(y))}.  Generally speaking, any
          * class that implements the {@code Comparable} interface and violates
          * this condition should clearly indicate this fact.  The recommended
          * language is "Note: this class has a natural ordering that is
          * inconsistent with equals."
          *
    -     * <p>In the foregoing description, the notation
    -     * {@code sgn(}<i>expression</i>{@code )} designates the mathematical
    -     * <i>signum</i> function, which is defined to return one of {@code -1},
    -     * {@code 0}, or {@code 1} according to whether the value of
    -     * <i>expression</i> is negative, zero, or positive, respectively.
    -     *
          * @param   o the object to be compared.
          * @return  a negative integer, zero, or a positive integer as this object
          *          is less than, equal to, or greater than the specified object.
    diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java
    index a155e1e8ba8..ad09e6bd5d6 100644
    --- a/src/java.base/share/classes/java/lang/Object.java
    +++ b/src/java.base/share/classes/java/lang/Object.java
    @@ -78,11 +78,11 @@ public Object() {}
          *     used in {@code equals} comparisons on the object is modified.
          *     This integer need not remain consistent from one execution of an
          *     application to another execution of the same application.
    -     * <li>If two objects are equal according to the {@code equals(Object)}
    +     * <li>If two objects are equal according to the {@link equals(Object) equals}
          *     method, then calling the {@code hashCode} method on each of
          *     the two objects must produce the same integer result.
          * <li>It is <em>not</em> required that if two objects are unequal
    -     *     according to the {@link java.lang.Object#equals(java.lang.Object)}
    +     *     according to the {@link equals(Object) equals}
          *     method, then calling the {@code hashCode} method on each of the
          *     two objects must produce distinct integer results.  However, the
          *     programmer should be aware that producing distinct integer results
    @@ -127,15 +127,26 @@ public Object() {}
          * <li>For any non-null reference value {@code x},
          *     {@code x.equals(null)} should return {@code false}.
          * </ul>
    +     *
          * <p>
    +     * An equivalence relation partitions the elements it operates on
    +     * into <i>equivalence classes</i>; all the members of an
    +     * equivalence class are equal to each other. Members of an equivalence class
    +     * are substitutable for each other, at least for some purposes.
    +     *
    +     * @implSpec
          * The {@code equals} method for class {@code Object} implements
          * the most discriminating possible equivalence relation on objects;
          * that is, for any non-null reference values {@code x} and
          * {@code y}, this method returns {@code true} if and only
          * if {@code x} and {@code y} refer to the same object
          * ({@code x == y} has the value {@code true}).
    -     * <p>
    -     * Note that it is generally necessary to override the {@code hashCode}
    +     *
    +     * In other words, under the reference equality equivalence
    +     * relation, each equivalence class only has a single element.
    +     *
    +     * @apiNote
    +     * It is generally necessary to override the {@link hashCode hashCode}
          * method whenever this method is overridden, so as to maintain the
          * general contract for the {@code hashCode} method, which states
          * that equal objects must have equal hash codes.
    @@ -183,7 +194,8 @@ public boolean equals(Object obj) {
          * primitive fields or references to immutable objects, then it is usually
          * the case that no fields in the object returned by {@code super.clone}
          * need to be modified.
    -     * <p>
    +     *
    +     * @implSpec
          * The method {@code clone} for class {@code Object} performs a
          * specific cloning operation. First, if the class of this object does
          * not implement the interface {@code Cloneable}, then a
    @@ -214,13 +226,16 @@ public boolean equals(Object obj) {
         protected native Object clone() throws CloneNotSupportedException;
     
         /**
    -     * Returns a string representation of the object. In general, the
    +     * Returns a string representation of the object.
    +     * @apiNote
    +     * In general, the
          * {@code toString} method returns a string that
          * "textually represents" this object. The result should
          * be a concise but informative representation that is easy for a
          * person to read.
          * It is recommended that all subclasses override this method.
    -     * <p>
    +     * The string output is not necessarily stable over time or across JVM invocations.
    +     * @implSpec
          * The {@code toString} method for class {@code Object}
          * returns a string consisting of the name of the class of which the
          * object is an instance, the at-sign character `{@code @}', and
    diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java
    index bb698a555b6..b4e28e6697c 100644
    --- a/src/java.base/share/classes/java/math/BigDecimal.java
    +++ b/src/java.base/share/classes/java/math/BigDecimal.java
    @@ -63,8 +63,7 @@
      * <p>When a {@code MathContext} object is supplied with a precision
      * setting of 0 (for example, {@link MathContext#UNLIMITED}),
      * arithmetic operations are exact, as are the arithmetic methods
    - * which take no {@code MathContext} object.  (This is the only
    - * behavior that was supported in releases prior to 5.)  As a
    + * which take no {@code MathContext} object. As a
      * corollary of computing the exact result, the rounding mode setting
      * of a {@code MathContext} object with a precision setting of 0 is
      * not used and thus irrelevant.  In the case of divide, the exact
    @@ -79,9 +78,8 @@
      * {@code BigDecimal} arithmetic are broadly compatible with selected
      * modes of operation of the arithmetic defined in ANSI X3.274-1996
      * and ANSI X3.274-1996/AM 1-2000 (section 7.4).  Unlike those
    - * standards, {@code BigDecimal} includes many rounding modes, which
    - * were mandatory for division in {@code BigDecimal} releases prior
    - * to 5.  Any conflicts between these ANSI standards and the
    + * standards, {@code BigDecimal} includes many rounding modes.
    + * Any conflicts between these ANSI standards and the
      * {@code BigDecimal} specification are resolved in favor of
      * {@code BigDecimal}.
      *
    @@ -90,6 +88,15 @@
      * and rounding must specify both the numerical result and the scale
      * used in the result's representation.
      *
    + * The different representations of the same numerical value are
    + * called members of the same <i>cohort</i>. The {@linkplain
    + * compareTo(BigDecimal) natural order} of {@code BigDecimal}
    + * considers members of the same cohort to be equal to each other. In
    + * contrast, the {@link equals equals} method requires both the
    + * numerical value and representation to be the same for equality to
    + * hold. The results of methods like {@link scale} and {@link
    + * unscaledValue} will differ for numerically equal values with
    + * different representations.
      *
      * <p>In general the rounding modes and precision setting determine
      * how operations return results with a limited number of digits when
    @@ -3040,16 +3047,21 @@ public BigDecimal stripTrailingZeros() {
         // Comparison Operations
     
         /**
    -     * Compares this {@code BigDecimal} with the specified
    +     * Compares this {@code BigDecimal} numerically with the specified
          * {@code BigDecimal}.  Two {@code BigDecimal} objects that are
          * equal in value but have a different scale (like 2.0 and 2.00)
    -     * are considered equal by this method.  This method is provided
    +     * are considered equal by this method. Such values are in the same <i>cohort</i>.
    +     *
    +     * This method is provided
          * in preference to individual methods for each of the six boolean
          * comparison operators ({@literal <}, ==,
          * {@literal >}, {@literal >=}, !=, {@literal <=}).  The
          * suggested idiom for performing these comparisons is:
          * {@code (x.compareTo(y)} &lt;<i>op</i>&gt; {@code 0)}, where
          * &lt;<i>op</i>&gt; is one of the six comparison operators.
    +
    +     * @apiNote
    +     * Note: this class has a natural ordering that is inconsistent with equals.
          *
          * @param  val {@code BigDecimal} to which this {@code BigDecimal} is
          *         to be compared.
    @@ -3129,8 +3141,8 @@ else if (ys != INFLATED)
          * {@code Object} for equality.  Unlike {@link
          * #compareTo(BigDecimal) compareTo}, this method considers two
          * {@code BigDecimal} objects equal only if they are equal in
    -     * value and scale (thus 2.0 is not equal to 2.00 when compared by
    -     * this method).
    +     * value and scale. Therefore 2.0 is not equal to 2.00 when compared by
    +     * this method.
          *
          * @param  x {@code Object} to which this {@code BigDecimal} is
          *         to be compared.
    diff --git a/src/java.base/share/classes/java/util/Comparator.java b/src/java.base/share/classes/java/util/Comparator.java
    index d009807a75a..b1b445bf55a 100644
    --- a/src/java.base/share/classes/java/util/Comparator.java
    +++ b/src/java.base/share/classes/java/util/Comparator.java
    @@ -38,9 +38,9 @@
      * as {@link Collections#sort(List,Comparator) Collections.sort} or {@link
      * Arrays#sort(Object[],Comparator) Arrays.sort}) to allow precise control
      * over the sort order.  Comparators can also be used to control the order of
    - * certain data structures (such as {@link SortedSet sorted sets} or {@link
    + * certain data structures (such as {@linkplain SortedSet sorted sets} or {@linkplain
      * SortedMap sorted maps}), or to provide an ordering for collections of
    - * objects that don't have a {@link Comparable natural ordering}.<p>
    + * objects that don't have a {@linkplain Comparable natural ordering}.<p>
      *
      * The ordering imposed by a comparator {@code c} on a set of elements
      * {@code S} is said to be <i>consistent with equals</i> if and only if
    @@ -89,6 +89,11 @@
      * equals(Object)} method(s):<pre>
      *     {(x, y) such that x.equals(y)}. </pre>
      *
    + * In other words, when the imposed ordering is consistent with
    + * equals, the equivalence classes defined by the equivalence relation
    + * of the {@code equals} method and the equivalence classes defined by
    + * the quotient of the {@code compare} method are the same.
    +
      * <p>Unlike {@code Comparable}, a comparator may optionally permit
      * comparison of null arguments, while maintaining the requirements for
      * an equivalence relation.
    @@ -112,8 +117,8 @@
          * zero, or a positive integer as the first argument is less than, equal
          * to, or greater than the second.<p>
          *
    -     * The implementor must ensure that {@code sgn(compare(x, y)) ==
    -     * -sgn(compare(y, x))} for all {@code x} and {@code y}.  (This
    +     * The implementor must ensure that {@link Integer#signum signum}{@code (compare(x, y)) ==
    +     * -signum(compare(y, x))} for all {@code x} and {@code y}.  (This
          * implies that {@code compare(x, y)} must throw an exception if and only
          * if {@code compare(y, x)} throws an exception.)<p>
          *
    @@ -122,21 +127,16 @@
          * {@code compare(x, z)>0}.<p>
          *
          * Finally, the implementor must ensure that {@code compare(x, y)==0}
    -     * implies that {@code sgn(compare(x, z))==sgn(compare(y, z))} for all
    +     * implies that {@code signum(compare(x, z))==signum(compare(y, z))} for all
          * {@code z}.<p>
          *
    +     * @apiNote
          * It is generally the case, but <i>not</i> strictly required that
          * {@code (compare(x, y)==0) == (x.equals(y))}.  Generally speaking,
          * any comparator that violates this condition should clearly indicate
          * this fact.  The recommended language is "Note: this comparator
          * imposes orderings that are inconsistent with equals."<p>
          *
    -     * In the foregoing description, the notation
    -     * {@code sgn(}<i>expression</i>{@code )} designates the mathematical
    -     * <i>signum</i> function, which is defined to return one of {@code -1},
    -     * {@code 0}, or {@code 1} according to whether the value of
    -     * <i>expression</i> is negative, zero, or positive, respectively.
    -     *
          * @param o1 the first object to be compared.
          * @param o2 the second object to be compared.
          * @return a negative integer, zero, or a positive integer as the
    @@ -155,8 +155,8 @@
          * {@link Object#equals(Object)}.  Additionally, this method can return
          * {@code true} <i>only</i> if the specified object is also a comparator
          * and it imposes the same ordering as this comparator.  Thus,
    -     * {@code comp1.equals(comp2)} implies that {@code sgn(comp1.compare(o1,
    -     * o2))==sgn(comp2.compare(o1, o2))} for every object reference
    +     * {@code comp1.equals(comp2)} implies that {@link Integer#signum signum}{@code (comp1.compare(o1,
    +     * o2))==signum(comp2.compare(o1, o2))} for every object reference
          * {@code o1} and {@code o2}.<p>
          *
          * Note that it is <i>always</i> safe <i>not</i> to override


Comments
Moving to Approved.
16-02-2021