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)
======================================================================