Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing equals() with JDK Dynamic Proxies

For the first time ever, I have to implement my own proxy classes using the standard JDK Dynamic Proxy. It works fairly well, except for one detail: the equals(...) method.

Let's assume that we have a simple Interface like this, which we want to proxy:

public interface MyInterface {
    public String getID();
    public void setID(String id);
}

... and our implementation looks like this (standard Java Bean with generated hashCode() and equals):

public class MyImplementation implements MyInterface {
    private String id;

    public String getID() { return this.id; }
    public void setID(String id) { this.id = id; }

    // hash code & equals generated by eclipse

    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (this.databaseId == null ? 0 :      
        this.id.hashCode());
        return result;
    }

    public final boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (this.getClass() != obj.getClass()) {
            return false;
        }
        MyImplementation other = (MyImplementation) obj;
        if (this.databaseId == null) {
            if (other.databaseId != null) {
                return false;
            }
        } else if (!this.databaseId.equals(other.databaseId)) {
            return false;
        }
        return true;
    }
}

The problem is, that when I create a proxy, the equals(...) method is no longer symmetric:

original.equals(original); // true
proxy.equals(original);    // true, as the proxy forwards the call to the wrapped object
original.equals(proxy);    // false
proxy.equals(proxy);       // false

This is also discussed in this article.

My question is: if I want all four "equals" cases to deliver true, what's the best (i.e. safest and least intrusive) way to go about it?

like image 426
Alan47 Avatar asked May 27 '15 16:05

Alan47


2 Answers

Here is a possible alternative of equals();

public final boolean equals(final Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (! (obj instanceof MyInterface)) // neither a Proxy nor a MyImplementation
    return false;

    MyInterface other = (MyInterface) obj;
    if (this.getID() == null) {
       if (other.getID() != null) {
               return false;
       }
    } else if (!this.getID().equals(other.getID())) {
      return false;
    }
    return true;
}

This code uses getID() instead of direct access to the field. So far, it works well.

like image 50
fcmonoid Avatar answered Oct 05 '22 21:10

fcmonoid


You will find a solution that will work. But there is a huge problem:

Both objects to compare with each other have to be aware of Proxy-Wrapping.

JAVA does it right in the the technical context, that Proxies are handled equally to other objects. But...

My personal opinion as I stumbled over this issue was and is right now: JAVA should introduce built-in support for proxies, that internally are unwrapped as soon as hashcode and equals are called.

Proxies should be transparent to the "normal" implementation. You should not bother about wether you have a proxy or the original. But JAVA does it wrong in this way.

One possibility is to make both objects aware of Proxy-Wrapping and handle it in equals/hashcode. But this floods the original class with a dependency that it should not have.

Another possibility is to unwrap the Proxy if proxy behaviour is not needed and use the real object instead. In the context you create the Proxy you should have a Map like this:

Map<Proxy, Original> map;

You should not pass JAVA proxies around. Every object has to know, that a proxy was passed in as they may store them in Set-Implementations where equals and hashcode are activated. As soon as you pass around JAVA Proxies you pollute using classes with this dependency.

Java Proxies should be as isolated as possible. The class that produces Java Proxies should be the only class that uses them.

Another (more boiler-plated) possibility is to use the standard proxy pattern without any JAVA proxies. But here again: You have to consider Proxy-Wrapping in hashcode/equals as well if you pass your proxied objects around.

like image 27
oopexpert Avatar answered Oct 05 '22 19:10

oopexpert