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 {The
//...
};
private class Person extends Object implements IPerson {
//...
};
Person
class inherits its equals(Object)
method from Object
, the implementation of which is:public boolean equals (Object object) {So if the expression
return this == object;
}
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();This code outputs the following to the console:
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);
object.equals(object): falseWhen 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
object == object: true
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() {The receiver is always a
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(person, args);
}
};
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:
Post a Comment