Friday, July 3, 1998

Question

The Java standard convention for equals() methods is something like this:

  public boolean equals(Object o) {
  
    // make sure o has the right type
    if ((o != null) && (o instanceof ThisClass)) {
    
      check the state of the two objects to see if they're equal
      return true if they match, false if they don't
      
    }
	
    // The class didn't match so  
    return false;
  }
Looking through the Java source code, you find this idiom all over the place.

Now consider this equals() method:

  public boolean equals(ThisClass o) {
  
    // we know o has the right type o has the right type 
    // because of the method signature
    
    check the state of the two objects to see if they're equal
    return true if they match, false if they don't


  }
One of my students pointed out that a class using either of these signatures would have equivalent behavior. Suppose we have two objects, t1 and t2, and both are instances of ThisClass. Consider this statement:

boolean result1 = t1.equals(t2);
If ThisClass has the first equals() method, t2 passes the type check, and the two object are compared according to their state. If ThisClass has the second equals() method, t2 is passed to that method and the two object are compared according to their state. In either case the result is the same.

Now consider this statement:

boolean result2 = t1.equals(o);
If ThisClass has the first, standard equals() method, o fails the type check and the method returns false. If ThisClass has the second equals() method, the equals() method inherited from java.lang.Object is invoked instead. This equals() method tests for object identity and returns false.

Thus in all possible cases I can think of, the results are the same. You can look at more complicated interactions involving subclasses, but since all Java method are invoked dynamically (all methods are virtual in C++ terms) this doesn't seem to make a difference. So here's today's question:

Is there any good reason to prefer one variant of the equals() method over the other?

A free copy of JavaBeans goes to the person submitting the best answer received by Monday morning. I'll collect interesting answers and post them here as well. Previous questions can be found on this page.

Answer

The exact answer to this weekend's question turns out to be quite complex. I'm not sure if anybody got it exactly right, but I was able to piece together the complete answer from several emails, and the Java Language Specification.

Recall that the basic question is whether these two equals() methods can be interchanged in a user-defined class without any effects on client classes. Is there any reason to prefer one over the other?

  public boolean equals(Object o) {
  
    // make sure o has the right type
    if ((o != null) && (o instanceof ThisClass)) {
    
      check the state of the two objects to see if they're equal
      return true if they match, false if they don't
      
    }
	
    // The class didn't match so  
    return false;
  }
  public boolean equals(ThisClass o) {
  
    // we know o has the right type o has the right type 
    // because of the method signature
    
    check the state of the two objects to see if they're equal
    return true if they match, false if they don't


  }
Several people noted the asymmetry in the null tests between the two classes. As Jan Niehusmann pointed out, the null check in the standard version is superfluous. instanceof always returns false if its left argument is null. However, the test should be present in the second version, e.g.

  public boolean equals(ThisClass o) {
  
    if (o != null) {    
      check the state of the two objects to see if they're equal
      return true if they match, false if they don't
    }
    return false;


  }
Much more seriously, and unfixably, however, are the issues of polymorphic overloaded methods, which a number of people noticed. Consider this test program:

public class ThisClass extends Object {

  int state;
  
  public ThisClass(int state) {
  
    this.state = state;
    
  }


  public boolean equals(ThisClass tc) {
  
     if (tc != null && this.state == tc.state) {
       return true;
     }
     
     return false;
  
  }

  public static void main(String[] args) {
  
    Object o1 = new ThisClass(1);
    Object o2 = new ThisClass(1);
    
    if (o1.equals(o2)) {
      System.out.println("o1 equals o2");    
    }
    else {
      System.out.println("o1 does not equal o2");        
    }

    ThisClass t1 = new ThisClass(1);
    ThisClass t2 = new ThisClass(1);
    
    if (t1.equals(t2)) {
      System.out.println("t1 equals t2");    
    }
    else {
      System.out.println("t1 does not equal t2");        
    }

    ThisClass t3 = new ThisClass(1);
    Object o3 = new ThisClass(1);
    
    if (t3.equals(o3)) {
      System.out.println("t3 equals o3");    
    }
    else {
      System.out.println("t3 does not equal o3");        
    }

    if (o3.equals(t3)) {
      System.out.println("o3 equals t3");    
    }
    else {
      System.out.println("o3 does not equal t3");        
    }

  }

}
Here's the output:

o1 does not equal o2
t1 equals t2
t3 does not equal o3
o3 does not equal t3

There are two different problems here. One is that Java does not check the exact type of an argument before choosing which overloaded method to pass it to. Thus t3.equals(o3) is false because, even though o3 is an instance of ThisClass, it is only known to be an instance of java.lang.Object, and thus the equals() method of java.lang.Object which tests for object identity is chosen.

o3.equals(t3) is false for a different reason. At compile time the compiler decides that the method desired is named equals and takes a single arument of type ThisClass. However, no such method is found in java.lang.Object, so the nearest matching method after widening conversion is selected. This is the standard equals(Object o) method. At runtime, there is a method in o3 with a more specific signature that does expect an argument of type ThisClass. However, the decision of the type of argument has already been made. In other words, the method is late bound, but the method argument types are early bound. Complete details can be found in Section 15.11 of the Java Language Specification.

Finally, I'll note that Jon Bodner ran some performance tests that indicated that the non-standard equals() method is faster in situations where it applies. According to Bodner

I just wrote that program to test out the two different equals(), and I was _really_ surprised. It turns out that the second equals (public boolean equals(ThisClass o)) is FAR faster than the standard format. I've included the program, and then test results run under MRJ 2.0 and CodeWarrior Pro 3.

The most interesting conclusion I can draw from this is that well-implemented templates would significantly IMPROVE the performance of data structures like trees and hashmaps. I also forgot about the additional type cast needed in the first case; after you determine it's of the right type, you still need to cast it so you can access its fields. That's two run-time type identification hits, which might be one of the sources of the large difference.

Erwin Moedersheim and Jon Bodner both get a free copy of JavaBeans just as soon as they send me their snail mail addresses. A new question tomorrow.

Reader Responses

From: "Erwin Moedersheim" <ibm.erwin@novamark.com>
Subject: Q of the day: equals(...)

The example below illustrates why changing the signature of the equals
method is dangerous. While references to instance methods are indeed
resolved dynamically, the correct results will only be obtained if both
method have the same signature. In the Java class file, a method declared as
"public boolean equals(ThisClass o)" will be known as
"equals(LThisClass;)Z", while the method in class Object is known as
"(Ljava/lang/Object;)Z". Now if the compiler encounters a reference to an
Object, it will insert a bytecode referring to the latter method, no matter
what the actual class of that object is. Below a simple example of how this
can give unexpected results

package test;

public class A {
  // For illustration only.
  private int ivInt = 37;

  // This "simpler" equals method will be known
  // as "equals(Ltest/A;)Z", which is not the same
  // as the intended equals "(Ljava/lang/Object;)Z"
  public boolean equals(A anA) {
    return (this.ivInt == anA.ivInt);
  }

  public static void main(String[] args) {
    A a1 = new A();
    A a2 = new A();
    // The following will invoke "equals(Ltest/A;)Z".
    // The result will be "true".
    System.out.println(a2.equals(a1));

    // Now assign the same instance to a variable of the Object class
    Object obj = a1;

    // The following will invoke "(Ljava/lang/Object;)Z".
    // The result will be "false".
    System.out.println(a2.equals(obj));
  }
}

While we are on the subject, I noticed that your example uses the
"instanceof" operator to test the type of the argument. This operator is
relatively expensive. Programmers will have a tendency not to compare apples
and oranges, so that we can assume that case where types do not conform is
actually rare. This leads us to another suggestion:

  public boolean equals(Object obj) {
    boolean result;
    try {  // Just cast it and handle the ClassCastException if necessary
      if (obj != null) {
        result = this.myComparisonAlgorithm((MyClass) obj);
      } else {
        result = false;
      }
    } catch (ClassCastException cce) {
      result = false;
    }
    return result;
  }

Cheers,
Erwin

Date: Fri, 3 Jul 1998 14:38:22 -0700
To: elharo@ibiblio.org
From: Jon Bodner <jbodner@pacbell.net>
Subject: Question of the day
Status:   

Elliotte,

I've got an answer and it's an "it depends."  I don't have a copy of the
source of a recent JVM (the C code, not the API stuff), so I can't give a
definite answer.

The first thought I had is that the test for null is still required, even
in the second equals(), so you can't avoid that condition entirely.  That
would make the second equals():

public boolean equals(ThisClass o) {
	if(o != null) {
		//test state
	}
	return false;
}

Now, how fast is virtual method lookup when the expected parameters are of
type Object?  I'd guess that there has to be an optimization in there;
after all, you don't need to check any hierarchy, since everything descends
from Object.  Furthermore, the lack of templates make Object parameters
fairly common in data structures, so it's a common enough case to be worthy
of optimization.  This would make method dispatch quicker in the first
equals() case.

The last bit is the performance of instanceof.  Is the instanceof operator
more expensive than checking the types on parameters?  If it's
significantly more expensive, the second equals() wins.  If not, the
overhead of method dispatch means the first equals() wins.

My gut feeling is that it's a wash, but optimizations could be done in a
JVM which would give either one the advantage.  I might work on a little
program to test the two cases and see which one wins on the JVMs I have
access to at home (only the CW JVMs and MRJ).

-jon

Date: Fri, 3 Jul 1998 15:26:40 -0700
To: elharo@ibiblio.org
From: Jon Bodner <jbodner@pacbell.net>
Subject: Second reply to today's question

Elliotte,

I just wrote that program to test out the two different equals(), and I was
_really_ surprised.  It turns out that the second equals (public boolean
equals(ThisClass o)) is FAR faster than the standard format.  I've included
the program, and then test results run under MRJ 2.0 and CodeWarrior Pro 3.

The most interesting conclusion I can draw from this is that
well-implemented templates would significantly IMPROVE the performance of
data structures like trees and hashmaps.  I also forgot about the
additional type cast needed in the first case; after you determine it's of
the right type, you still need to cast it so you can access its fields.
That's two run-time type identification hits, which might be one of the
sources of the large difference.

-jon

Code:
class TestClass {
	int a;
	char b;

	public TestClass(int a, char b) {
		this.a = a;
		this.b = b;
	}

	public boolean equals(Object o) {
		if((o!=null) && (o instanceof TestClass)) {
			TestClass o2 = (TestClass)o;
			if(o2.a == a && o2.b == b) {
				return true;
			}
		}
		return false;
	}
}

class TestClass2 {
	int a;
	char b;

	public TestClass2(int a, char b) {
		this.a = a;
		this.b = b;
	}

	public boolean equals(TestClass2 o) {
		if((o != null)&&(o.a == a && o.b == b)) {
			return true;
		}
		return false;
	}
}

public class Main {
	public static void main(String args[]) {
		TestClass a1,a2,a3;
		TestClass2 b1,b2,b3;
		long startTime;

		a1 = new TestClass(1,'c');
		a2 = new TestClass(2,'d');
		a3 = new TestClass(1,'c');

		b1 = new TestClass2(1,'c');
		b2 = new TestClass2(2,'d');
		b3 = new TestClass2(1,'c');

		boolean ans;
		System.out.println("First equals(): public boolean
equals(Object o)");

		System.out.print("Case 1: Same object: ");
		startTime = new java.util.Date().getTime();
		for(int i = 0;i<10000;i++) {
			ans = a1.equals(a1);
		}
		System.out.println(new
java.util.Date().getTime()-startTime+"ms");

		System.out.print("Case 2: Same type, no match: ");
		startTime = new java.util.Date().getTime();
		for(int i = 0;i<10000;i++) {
			ans = a1.equals(a2);
		}
		System.out.println(new
java.util.Date().getTime()-startTime+"ms");

		System.out.print("Case 3: Same type, match: ");
		startTime = new java.util.Date().getTime();
		for(int i = 0;i<10000;i++) {
			ans = a1.equals(a3);
		}
		System.out.println(new
java.util.Date().getTime()-startTime+"ms");

		System.out.print("Case 4: Different type: ");
		startTime = new java.util.Date().getTime();
		for(int i = 0;i<10000;i++) {
			ans = a1.equals(b1);
		}
		System.out.println(new
java.util.Date().getTime()-startTime+"ms");

		System.out.println("Second equals(): public boolean
equals(ThisClass o)");

		System.out.print("Case 1: Same object: ");
		startTime = new java.util.Date().getTime();
		for(int i = 0;i<10000;i++) {
			ans = b1.equals(b1);
		}
		System.out.println(new
java.util.Date().getTime()-startTime+"ms");

		System.out.print("Case 2: Same type, no match: ");
		startTime = new java.util.Date().getTime();
		for(int i = 0;i<10000;i++) {
			ans = b1.equals(b2);
		}
		System.out.println(new
java.util.Date().getTime()-startTime+"ms");

		System.out.print("Case 3: Same type, match: ");
		startTime = new java.util.Date().getTime();
		for(int i = 0;i<10000;i++) {
			ans = b1.equals(b3);
		}
		System.out.println(new
java.util.Date().getTime()-startTime+"ms");

		System.out.print("Case 4: Different type: ");
		startTime = new java.util.Date().getTime();
		for(int i = 0;i<10000;i++) {
			ans = b1.equals(a1);
		}
		System.out.println(new
java.util.Date().getTime()-startTime+"ms");

	}
}

Results:

(MRJ 2.0, run four times to assure consistent results)
First equals(): public boolean equals(Object o)
Case 1: Same object: 28ms
Case 2: Same type, no match: 28ms
Case 3: Same type, match: 29ms
Case 4: Different type: 17ms
Second equals(): public boolean equals(ThisClass o)
Case 1: Same object: 5ms
Case 2: Same type, no match: 5ms
Case 3: Same type, match: 6ms
Case 4: Different type: 4ms

First equals(): public boolean equals(Object o)
Case 1: Same object: 28ms
Case 2: Same type, no match: 28ms
Case 3: Same type, match: 28ms
Case 4: Different type: 18ms
Second equals(): public boolean equals(ThisClass o)
Case 1: Same object: 5ms
Case 2: Same type, no match: 6ms
Case 3: Same type, match: 5ms
Case 4: Different type: 4ms

First equals(): public boolean equals(Object o)
Case 1: Same object: 28ms
Case 2: Same type, no match: 28ms
Case 3: Same type, match: 28ms
Case 4: Different type: 17ms
Second equals(): public boolean equals(ThisClass o)
Case 1: Same object: 6ms
Case 2: Same type, no match: 6ms
Case 3: Same type, match: 5ms
Case 4: Different type: 5ms

First equals(): public boolean equals(Object o)
Case 1: Same object: 28ms
Case 2: Same type, no match: 28ms
Case 3: Same type, match: 28ms
Case 4: Different type: 18ms
Second equals(): public boolean equals(ThisClass o)
Case 1: Same object: 6ms
Case 2: Same type, no match: 5ms
Case 3: Same type, match: 6ms
Case 4: Different type: 4ms

(CW Pro 3, also run 4 times)
First equals(): public boolean equals(Object o)
Case 1: Same object: 32ms
Case 2: Same type, no match: 31ms
Case 3: Same type, match: 36ms
Case 4: Different type: 33ms
Second equals(): public boolean equals(ThisClass o)
Case 1: Same object: 9ms
Case 2: Same type, no match: 10ms
Case 3: Same type, match: 10ms
Case 4: Different type: 10ms

First equals(): public boolean equals(Object o)
Case 1: Same object: 32ms
Case 2: Same type, no match: 32ms
Case 3: Same type, match: 31ms
Case 4: Different type: 34ms
Second equals(): public boolean equals(ThisClass o)
Case 1: Same object: 9ms
Case 2: Same type, no match: 10ms
Case 3: Same type, match: 10ms
Case 4: Different type: 10ms

First equals(): public boolean equals(Object o)
Case 1: Same object: 32ms
Case 2: Same type, no match: 32ms
Case 3: Same type, match: 32ms
Case 4: Different type: 33ms
Second equals(): public boolean equals(ThisClass o)
Case 1: Same object: 9ms
Case 2: Same type, no match: 10ms
Case 3: Same type, match: 9ms
Case 4: Different type: 10ms

First equals(): public boolean equals(Object o)
Case 1: Same object: 47ms
Case 2: Same type, no match: 45ms
Case 3: Same type, match: 44ms
Case 4: Different type: 48ms
Second equals(): public boolean equals(ThisClass o)
Case 1: Same object: 9ms
Case 2: Same type, no match: 9ms
Case 3: Same type, match: 10ms
Case 4: Different type: 10ms





From: "Bruce Eckel" <beckel@sprintmail.com>
To: "Elliotte Rusty Harold" <elharo@ibiblio.org>
Subject: equals() method
Date: Sun, 5 Jul 1998 10:52:18 -0600


> Now consider this equals() method:
>
>
>   public boolean equals(ThisClass o) {
>
>     // we know o has the right type o has the right type
>     // because of the method signature
>
>     check the state of the two objects to see if they're equal
>     return true if they match, false if they don't
>
>
>   }
>

This won't work, since what you've done is overload the equals() method, and
not override the equals method in the base class. That is, if you want to
override it, it will have to have the same signature. Thus, you MUST have an
equals() with an argument of Object.


> The Java standard convention for equals() methods is something like this:
>
>   public boolean equals(Object o) {
>
>     // make sure o has the right type
>     if ((o != null) && (o instanceof ThisClass)) {
>
>       check the state of the two objects to see if they're equal
>       return true if they match, false if they don't
>
>     }
>

It turns out that if you say:

if (o instanceof ThisClass)

You automatically get the check for o != null.

================================
Bruce Eckel             Reply to Bruce@EckelObjects.com
http://www.BruceEckel.com
Contains free electronic books: "Thinking in Java" and "Thinking in C++ 2nd
ed"





Date: Sun, 5 Jul 1998 20:41:59 +0100 (BST)
From: Barry Cornelius <Barry.Cornelius@durham.ac.uk>
To: elharo@ibiblio.org
Subject: Question of the day: equals method

Although there are plenty of situations where declaring equals with a
TypeClass argument works OK, there are a couple of areas where you will 
run into problems.

Problem 1

The main reason why you will want to use an argument of class Object is
because you may later want to use your class with a collection class such
as the class Vector (or Stack or Hashtable) from java.util.Vector or one
of the new collection classes from JDK 1.2 (HashSet, TreeSet, ArrayList,
LinkedList, HashMap and TreeMap).

These collection classes are wonderful because they allow to create
dynamically growing collections of objects.  But the methods of these
classes do not know what sort of objects you are storing in the
collection.  So when you call some method of the collection class that
says "is this object in the collection already?" or "at what index is this
element in the collection?", then the method will behind the scenes call: 
   public boolean equals(Object o)

If, within the class TypeClass, you have declared equals with an argument
of class Object, then your method will be called.  However, if you have
declared equals with a TypeClass argument, it will not be called:
the method called equals from the class Object will be called instead. 
This will just do a byte-by-byte comparison of the two values.  This is
likely to lead to the wrong answer.

For example, suppose you have declared a class called Person that has
fields for a name and an amount of money.  Now, in a program, use
java.util.Vector to declare a "population": 
  Vector pop = new Vector();
and then put people in your vector:
  Person p = new Person("tom", 10.0);
  pop.addElement(p);
  p = new Person("dick", 30.0);
  pop.addElement(p);
  p = new Person("harry", 20.0);
  pop.addElement(p);
Now try and see if you can find these people.  Use something like:
  String target = input.readLine();
  p = new Person(target, 5.0);
  System.out.println(pop.contains(p));
  System.out.println(pop.indexOf(p));
Because the contains and indexOf methods are working on values of type
Object, they will use a method with signature:
  public boolean equals(Object o)

If the class Person that you have provided contains:
  public boolean equals(Object o) {
    return name.equals(((Person) o).name);
  }
then this declaration is overriding the one declared in the class Object
and the contains and indexOf methods will use this declaration.

If, instead, you declare an equals method with an argument of class
Person, then you are not overriding the equals of class Object: instead,
you are overloading equals.  And the contains and indexOf methods will use
the equals method of Object. 

Problem 2

The second problem only arises if the class TypeClass is part of an
inheritance hierarchy, i.e., you have something like:
  public class TypeClass extends SuperClass {

Suppose s is an object of class SuperClass and t is an object of class
TypeClass, and suppose you have arranged for s to refer to a TypeClass
object, e.g., by:
  s = t;
What happens when a program executes:
  boolean result = s.equals(t);

If you adopt the policy of declaring any equals methods with an Object
argument, then it will be the equals method of TypeClass that will be
called.  However, if TypeClass declares an equals method that has an
argument of class TypeClass, then the above call will lead to the
equals method of SuperClass being called.

Conclusion

I would suggest that an argument of class Object is always used.  If you
don't, it will probably come back to haunt you later: you may spend many
hours trying to figure out why you are getting the wrong results. 

--
Barry Cornelius                      Telephone: (0191 or +44 191) 374 4717
User Services, Information Technology Service,            Office: 374 2892   
Science Site, University of Durham, Durham, DH1 3LE, UK      Fax: 374 7759
http://www.durham.ac.uk/~dcl0bjc       mailto:Barry.Cornelius@durham.ac.uk

Date: Mon, 6 Jul 1998 00:37:37 +0200
From: Jan Niehusmann <jan@gondor.com>
To: elharo@ibiblio.org
Subject: Your question regarding equals()

First, you can write 

if (o instanceof ThisClass) {

instead of 

if ((o != null) && (o instanceof ThisClass)) {

because, if 'Java in a nutshell' is right, '[instanceof] returns false
if the value on its left is null' (p. 35). But now for your question:

The two forms of equals differ, if you subclass your class again.

Look:
class A {
	public boolean equals(A o) {
		return true; // this class is very equal :-)
	}
}

class B {
	public boolean equals(B o) {
		return false; // i'm different...
	}
}
[...]

	A a=new A(); B b=new B();
	
	b.equals(b); // returns false - b is really ugly...
	b.equals(a); // surprise: returns true... 
 		    
if both methods were defined a equals(Object ...), the equals method
of B were called (as it overrides A's method). But equals(B o) doesn't
override equals(A o), it only overloads it, so equals(A o) is still
accessible and will be called in the case above. 

Bye, Jan



Date: Fri, 03 Jul 1998 21:50:24 -0400
From: Jack Harich <happyjac@mindspring.com>
To: elharo@ibiblio.org
Subject: Question of the Day

Re: Is there any good reason to prefer one variant of the equals()
method over the other? 

A. public boolean equals(Object o)

B. public boolean equals(ThisClass o)


Well, we have two good reasons to prefer A:

- A allows a generic signature, useful in interfaces or subclasses.

- B is missing the null check. They are _not_ functionally equivalent.
For B to behave properly it will have to null check or put everything in
a try/catch, otherwise it cannot reliably avoid exceptions.

BTW, love your website. Very useful.

Happy Jack

From: "Stanley Yamane" <syamane@shore.net>
To: <elharo@ibiblio.org>
Subject: Question of the day
Date: Sat, 4 Jul 1998 23:54:31 -0400
   

Hopefully I've understood the question right, but it seems to me that
actually, the two versions are not equivalent.  The case where they differ
is when object identity inequality is not sufficient to guarantee value
inequality.

For example, if I created:

String s = "Hello";
String t = "Hello";

you would expect (s.equals(t)) to return 'true', and it will both in the
standard and JDK idioms.  If you went on to assign these variables to
generic Object variables:

Object o1 = s;
Object o2 = t;

you might expect that (o1.equals(o2)) would also return 'true'.

If you wrote the equals() method the standard (first) way, this would be
the result.

However, if you wrote the equals() method the way it appears in the JDK
idiom, you would get back 'false' because, as you noted, the
java.lang.Object equals() method would get called, and that only checks for
object identity.

This becomes more serious when you start tossing these JDK idiom objects
into a Vector or other collection class.  Because the collection classes
pass objects around via java.lang.Object handles, the JDK idiom object's
equals() method never gets called.

I've attached a few code examples that show up the difference.

..Stan

PS. I suppose that if you can guarantee these cases don't apply then you
may be able to save some time (if the JIT can bind some of those method
invocations together), although the NULL check should be put into the JDK
idiom version somewhere if its going to examine object state so that
shouldn't count into any timing difference between the two version.


Date: Sun, 5 Jul 1998 20:00 +0100 (BST)
From: scorp@btinternet.com (Dave Harris)
Subject: Question of the day
To: elharo@ibiblio.org

> public boolean equals(ThisClass o) {

This method only comes into play for arguments of static type "ThisClass".
Thus:
    t1.equals( t2 ); // True.
    t1.equals( (Object) t2 );  // False.

Because in the second line, the wrong version of equals() is called. This
is rarely what is wanted. For example, if you used instances of
"ThisClass" as keys for a Hashtable, Hashtable would treat them as Objects
and would call the wrong method.

However, it can be worth writing:
    boolean equals( Object o ) {
        return (o instanceof ThisClass) &&
               equals( (ThisClass) o );
    }
 
    boolean equals( ThisClass o ) {
        if (o != null)
            // Compare state.
    }

This avoids the typecheck where the type is statically known, still works
in the general case, and avoids duplicating code.

Dave Harris

From: "Danny Lie" <lie@cs.utwente.nl>
To: <elharo@ibiblio.org>
Subject: Question of the Day
Date: Mon, 6 Jul 1998 00:09:47 +0200

Suppose ThisClass uses the first method:

Object o=t2;
boolean result1=t1.equals(o);

Object o passes the type check and the two objects are compared.

Suppose ThisClass uses the second method:

Object o=t2;
boolean result2=t1.equals(o);

Object o does not passes the type check and the equals() method inherited
from java.lang.Object is invoked instead and will return false, which is not
always correct.

Date: Sun, 05 Jul 1998 21:22:40 -0400
From: Tom Marcoux <marcouxt@Frb.GOV>
To: elharo@ibiblio.org
Subject: Question of the Day

I read the Week 4 - More Objects/Equals() notes a couple of times to
help me come up with an intelligible response so here goes...

A beginner like me might benefit, during my debugging efforts, by having
the capability of adding message code in the equals() method to tell me
whether the object is failing the state test or the class test.  The
Overload of java.lang.Object method using ThisClass won't allow me to do
this.  The Override method should leave me with no excuses if something
goes wrong in testing.

Thanks for the chance to win!

Tom Marcoux

Date: Mon, 06 Jul 1998 08:58:00 +0200
From: Andreas Schaefer <andreas.schaefer@credit-suisse.com>
Subject: Question of the Day

Hi

Because equals is defined in the Object class (SUPERclass of all
classes) one could create an equals not only checking the own class but
also some related classes (e.g. String and StringBuffer). So only the
first version would work.

So let us suppose String and StringBuffer would not be final (even if
they are):

public MyString extends String {
    public boolean equals( Object o ) {

        if( o != null ) {
            if( o instanceof String ) {
                return o.equals( this );
            } else {
                if( o instanceof StringBuffer ) {
                    return o.toString().equals( this );
                }
            }
        }
        return false;
    }
}
-------------------- End Code Snippet -------------------------------

Now use:
StringBuffer sb_temp = new StringBuffer();
sb_temp.append(  "Hello" );
MyString ms_temp = new MyString( "Hello" );
boolean b_temp = ms_temp.equals( sb_temp );

Because the second version would call the equals of the Object class and
this would
only check the address equality it would always return false in they are
not the same
object so the code above would always have b_temp == false. But in the
my version
b_temp == true.

Bye-bye
Andreas Schaefer
MAD plaNET PLC
www.madplanet.ch
andreas.schaefer@madplanet.ch

P.S.: If would be great if you add a mailto link in the Question of the
Day. So one had not to look for an EMail because I do not want to bother
someone.

Date: Mon, 06 Jul 1998 09:41:13 +0200
From: Andreas Schaefer <andreas.schaefer@credit-suisse.com>
Subject: Question of the Day

Hi

Another explanation why one should use version one:

In the class hirarchy one could overwrite the previous method by using
the same signature. So if a super class overwrite the equals method in a
way that is not usefull for the sub class one have to overwrite only the
equals method. In the version two a method for the sub class has to be
written:
- public boolean equals( thisclass o );
- public boolean equals( superclass o );
This could be lead to several method signature which is not transparent
to the users of this class. It is also a way to create endless loops
when equals( superclass ) class equals( thisclass) and if in a sub class
of the sub class equals( thisclass ) calls equals( superclass).

So version one leads to a clear and intuitive design which goes along
with the object oriented design.

Andreas Schaefer
MAD plaNET PLC
www.madplanet.ch
andreas.schaefer@madplanet.ch

From: Neil Padgen <nrp@chernikeeff.co.uk>
To: elharo@ibiblio.org
Subject: Saturday's Question of the Day

Interesting question.  I'd consider the second equals() method preferable
to the first, because it's intuitive.  After all, when writing an operation
for ThisClass, what could be simpler than giving ThisClass as the type for
the argument? 

The second equals() method also gives the opportunity for compile-time
checking.  For example, if there was no way to convert a String into
ThisClass, then the code

	String s = "hello world";
	boolean result1 = t1.equals(s);

could be flagged as invalid at compile-time with the second equals() method,
but would have to wait until runtime with the first equals() method, as
everything eventually falls back to being an Object.

However, I'd hope that the first equals() method has some advantage over
the second, otherwise it seems a pretty major omission by Sun if they've
used the first equals() method all over the place in the Java source code! 

-- Neil

Date: Mon, 06 Jul 1998 11:04:01 +0200
From: Pierre MONDIE <pmondie@u-bourgogne.Fr>
Subject: question of July 4th

The answer to the question you're asking is not easy, but from the
performance POV, the answer seems clear to me :

As you wrote :
------------------------------
public boolean equals(Object o) {
   if ((o != null) && (o instanceof ThisClass)) {
   }
}

Looking through the Java source code, you find this idiom all over the
place.
----------------------------------
Why did Java coders find usefull to use this idiom so often instead of
relying on heritage from the Object Class ? Maybe just because relying
on heritage at runtime costs alot of resources, because you might have
to parse the whole Class hierarchy to get a result. For sure, they might
also wish to throw comprehensive/documented exceptions.

Overwriting the "equals" method without any type check ensures to catch
the call ASAP and handle it fast.

If you use :
public boolean equals(TheGoodClass AnObj) {....}

and then ask :

A.equals(B);

If B's type is not TheGoodClass, the Java VM will have to parse the
Class hierarchy before returning false. If, as a good OO coder, you rely
alot on polymorphism, you might find alot of calls to mother classes.

From: DaveHunter <dave@monisys.ca>
Reply-To: dave@monisys.ca
To: elharo@ibiblio.org
Subject: Question of the Day-all Java method are invoked dynamically?

> "but since all Java method are invoked dynamically (all methods are
virtual in C++ terms) this doesn't
>seem to make a difference. So here's today's question: Is there any
good reason to prefer one variant of the
>equals() method over the other?"


 "but since all Java method are invoked dynamically"
Overloaded methods are  staticaly bound (early) - determined by the
compiler (at compile time -not runtime)
Class  methods are  staticaly bound -Although you should always invoke a
class method with the class name.
So not 'all' Java method are invoked dynamically.

> boolean result1 = t1.equals(t2);
> If ThisClass> has the first equals() method, t2 passes the type check,
and the two
> object are compared according to their state. If ThisClass has the
second equals()
> method, t2 is passed to that method and the two object are compared
according to their
>  state. In either case the result is the same.

"If ThisClass> has the first equals() method,"
"If ThisClass has the second equals() method,"
Becauuse you have overloaded the  equals() method these questions are
determined at compile time.
The method that is invoked is simply determined by the type of variable
of  t2
but NOT the actual Object  t2 is refering to:

If the type of variable  t2 is type Object: the compiler will invoke the
Object eqals(Object o) method
If the type of variable  t2 is type ThisClass: the compiler will invoke
the ThisClass equals(ThisClass o) method

 ThisClass t2  =  new ThisClass( ) ; VS
Object t2  =  new ThisClass( ) ;
or ThisClass t2  =  new ChildClass( ) ;

 boolean result1 = t1.equals(t2);
 The compiler will determine the  equals() method
based on the type of variable  t2 (Object , ThisClass,ChildClass)
This is static binding.


>Thus in all possible cases I can think of, the results are the same."
No.
In Java,the inheritted Object equals( ) method tests for identity not
equality.
Any Overridden method should implement an  equality test.
You have not overidden the equals method here but overloaded it.
Thus in many  cases, the results aredifferent

Since overloaded methods are not dynamically bound they  are not
polymorphic.
Overloading methods is just a convienance for the users of your
classs.This is not polymorhisim.


I would appreciate it if you wouuld post my answer
(Feel free to fix all typos,gramos etc).

TTY later
Dave Hunter


[ Cafe au Lait | Books | Trade Shows | Links | FAQ | Tutorial | User Groups ]

Copyright 1998 Elliotte Rusty Harold
elharo@metalab.unc.edu
Last Modified July 6, 1998