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.