Wednesday, March 5, 2008

Question: When is an object not equal to itself?

Most developers would agree that the expression object.equal(object) aways evaluates to true, and I thought so too until a short while ago where I found that it can sometimes evaluate to false. Trust me, it can happen. Consider the following code:
  public interface IPerson {
//...
};

private class Person extends Object implements IPerson {
//...
};
The Person class inherits its equals(Object) method from Object, the implementation of which is:
  public boolean equals (Object object) {
return this == object;
}
So if the expression object == object always evaluates to true, and yes, it always evaluates to true, how could it be possible for the expression object.equals(object) to ever evaluate to false?

Answer: When it's a proxy. This is a serious problem, since in most cases where proxies are used the fact that you're using a Proxy should not be something you need to think about. Consider the following code that creates a proxy and then compares it with itself:
  final IPerson person = new Person();

ClassLoader classLoader = Person.class.getClassLoader();
Class[] interfaces = {
IPerson.class
};
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(person, args);
}
};
Object object = Proxy.newProxyInstance(classLoader, interfaces, handler);

boolean equal = object.equals(object);
System.out.println("object.equals(object): " + equal);

boolean identical = object == object;
System.out.println("object == object: " + identical);
This code outputs the following to the console:
  object.equals(object): false
object == object: true
When I first saw this I could not believe my eyes, thinking that it must be a bug in either the JVM or the implementation of the Proxy class, when of course it was neither. The problem lies in the anonymous inner-class implementation of the InvocationHandler interface:
  InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(person, args);
}
};
The receiver is always a Proxy, so when we evaluate object.equals(object), execution quickly jumps to the handler's invoke method, where the expression method.invoke(person, args) is evaluated. The trick is to remember that this line of code is equivalent to:
  object.equals(person)
This evaluates to false because:
  • The object is an instance of Proxy.
  • The person is an instance of IPerson.
  • Both instances are distinct objects.
  • The Proxy class inherits its equals(Object) method from Object.
  • The expression object == person evaluate to false.

It is unfortunate that the Proxy class does not take care of ensuring that the equals(Object) method just works as you would expect, but sadly this is not the case. If you want it to work, and you really do want it to work, your InvocationHandler must be smarter. But I'm saving that for another day.

No comments:

Planet Eclipse

Jazz Community News