Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4214818

equals() symmetry and equals()/hashCode() compatibility should be enforced

    Details

    • Type: Enhancement
    • Status: Closed
    • Priority: P5
    • Resolution: Won't Fix
    • Affects Version/s: 1.2.0
    • Fix Version/s: None
    • Component/s: tools
    • Labels:
    • Subcomponent:
    • CPU:
      generic
    • OS:
      generic

      Description



      Name: igT44549 Date: 02/24/99


      Hello. This isn't actually a bug report -- it's more of a
      feature request. However, I noticed that many of the "bugs"
      submitted here are actually feature requests, so I am assuming
      that this is OK to do.

      The Language Specification states:

      "The general contract of 'equals' is that it implements an
      equivalence realation: [....] It is *symmetric*: for any
      reference values 'x' and 'y', 'x.equals(y)' should return
      'true' if and only if "y.equals(x)' returns 'true'.

      The Language Specification also states:

      "If two objects are equal according to the 'equals' method, then
      calling the 'hashCode' method on each of the two objects must
      produce the same result."

      I strongly agree with both of these specifications.
      Unfortunately, there is nothing in the compiler to enforce these
      rules. As a result, these rules are very often violated (see the
      example below from the "official" Java tutorial). This leads to
      annoying bugs.

      I don't remember enough discrete math to know if it is possible
      for the compiler to check if a given 'equals' method is
      inherently symmetric. Since you have some sharp people there at
      Sun, I thought that you might be able to look into this. If it
      is possible to check whether or not an 'equals' method is
      symmetric, please build this functionality into the compiler.

      If it is not possible to make this check, please consider the
      following: I have noticed that, in most cases, non-symmetric
      'equals' methods contain the keyword 'instanceof', and,
      conversely, every 'equals' method I have ever seen with the
      keyword 'instanceof' in it has been unsymmetric (see example
      below). Would it be possible to have the compiler flag a
      warning if it detects the keyword 'instanceof' in an 'equals'
      method?

      Similarly, it would be nice if the compiler would check for
      hashCode/equals compatibility. Once again, I don't know if this
      is possible, but, if it is possible, please include this feature.

      If it is not possible to do anything to coerce people to follow
      these rules, it might be a good idea to take them out of the
      Language Specification. Although, I do believe that they are
      good rules, the simple fact is that most people don't pay
      attention to them.

      EXAMPLE:

      Consider the class 'Name' from the "official" Java tutorial:

      http://java.sun.com/docs/books/tutorial/collections/interfaces/example-1dot2/Name.java

      or Campione, et al, _The Java Tutorial Continued_, pages 55 and 721:

      import java.util.*;

      public class Name implements Comparable {
          private String firstName, lastName;

      // <snip>

          public boolean equals(Object o) {
              if (!(o instanceof Name))
                  return false;
              Name n = (Name)o;
              return n.firstName.equals(firstName) &&
                     n.lastName.equals(lastName);
          }

          public int hashCode() {
              return 31*firstName.hashCode() + lastName.hashCode();
          }

      // <snip>

      }


      Note that this class is not final, so the author of class 'Name'
      should expect that a user might create a subclass of 'Name'.
      Using the same style of implementing 'equals', we end up with:

      import java.io.PrintWriter;

      public class NameWithTitle extends Name {

          private String title;

          public NameWithTitle( String title, String first, String last ) {
              super( first, last );
              this.title = title;
          }

          public String title() {
              return title;
          }

          public boolean equals( Object o ) {
              if ( !( o instanceof NameWithTitle ) )
                  return false;
              NameWithTitle n = (NameWithTitle) o;
              return n.firstName().equals( this.firstName() ) &&
                     n.lastName().equals( this.lastName() ) &&
                     n.title().equals( this.title() );
          }

          public int hashCode() {
              return super.hashCode() + 17*title.hashCode();
          }

          public String toString() {
              return title + " " + super.toString();
          }

          public int compareTo( Object o ) {
              NameWithTitle n = (NameWithTitle) o;
              int comp = lastName().compareTo( n.lastName() );
              if ( 0 == comp ) {
                  comp = firstName().compareTo( n.firstName() );
                  if ( 0 == comp ) {
                      comp = title().compareTo( n.title() );
                  }
              }
              return comp;
          }

          public static void main( String [] argv ) {
              PrintWriter outwriter = new PrintWriter( System.out, true );
              
              Name gosling = new Name( "James", "Gosling" );
              Name drG = new NameWithTitle( "Dr.", "James", "Gosling" );
               
              outwriter.println();
              outwriter.println( "*** our instances:" );
              outwriter.println();
              outwriter.print( "gosling: " );
              outwriter.println( gosling );
              outwriter.print( "drG: " );
              outwriter.println( drG );
              
              outwriter.println();
              outwriter.println( "*** Test to see if equals is symmetric:" );
              outwriter.println();
              outwriter.print( "gosling.equals( drG ): " );
              outwriter.println( gosling.equals( drG ) );
              outwriter.print( "drG.equals( gosling ): " );
              outwriter.println( drG.equals( gosling ) );

              outwriter.println();
              outwriter.println( "*** Test hashCode/equals compatibility:" );
              outwriter.println();
              outwriter.print( "gosling.equals( drG ): " );
              outwriter.println( gosling.equals( drG ) );
              outwriter.print( "gosling.hashCode() == drG.hashCode(): " );
              outwriter.println( gosling.hashCode() == drG.hashCode() );
              outwriter.println();
              outwriter.print( "drG.equals( gosling ): " );
              outwriter.println( drG.equals( gosling ) );
              outwriter.print( "drG.hashCode() == gosling.hashCode(): " );
              outwriter.println( drG.hashCode() == gosling.hashCode() );

          }

      }

      If we run the main in this class we get:

      >java NameWithTitle

      *** our instances:

      gosling: James Gosling
      drG: Dr. James Gosling

      *** Test to see if equals is symmetric:

      gosling.equals( drG ): true
      drG.equals( gosling ): false

      *** Test hashCode/equals compatibility:

      gosling.equals( drG ): true
      gosling.hashCode() == drG.hashCode(): false

      drG.equals( gosling ): false
      drG.hashCode() == gosling.hashCode(): false

      >

      Note that 'hashCode' and 'equals' are incompatible and 'equals'
      is not symmetric. Both of these problems result from the
      inappropriate use of 'instanceof' in the 'equals' method.

      -- Dave Jones
      (Review ID: 54699)
      ======================================================================

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                wmaddoxsunw William Maddox (Inactive)
                Reporter:
                iris Iris Clark
              • Votes:
                0 Vote for this issue
                Watchers:
                1 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved:
                  Imported:
                  Indexed: