【问题标题】:Implementing equals() with JDK Dynamic Proxies使用 JDK 动态代理实现 equals()
【发布时间】:2015-08-09 21:39:40
【问题描述】:

有史以来第一次,我必须使用标准的 JDK 动态代理来实现我自己的代理类。它工作得相当好,除了一个细节:equals(...) 方法。

假设我们有一个像这样的简单接口,我们想要代理它:

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

...我们的实现看起来像这样(标准 Java Bean 生成 hashCode()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;
    }
}

问题是,当我创建代理时,equals(...) 方法不再是对称的:

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 article 中也对此进行了讨论。

我的问题是:如果我希望所有四个“相等”的案例都交付 true,那么最好的(即最安全和最少干扰的)方法是什么?

【问题讨论】:

  • 您必须根据接口的成员方法来实现接口实现的equals 方法。看看List#equals(Object) 是如何做到的(javadoc 中是如何描述的)。

标签: java proxy equals dynamic-proxy


【解决方案1】:

这是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;
}

此代码使用 getID() 而不是直接访问该字段。到目前为止,它运行良好。

【讨论】:

    【解决方案2】:

    您会找到可行的解决方案。但是有一个很大的问题:

    要相互比较的两个对象都必须知道 Proxy-Wrapping。

    JAVA 在技术方面做得很好,代理与其他对象同等处理。但是……

    当我偶然发现这个问题时,我个人的看法是现在和现在:JAVA 应该引入对代理的内置支持,一旦调用 hashcode 和 equals,就会在内部解包。

    代理应该对“正常”实现透明。您不应该担心您是否有代理或原件。但是JAVA在这种方式下做错了。

    一种可能性是让两个对象都知道 Proxy-Wrapping 并在 equals/hashcode 中处理它。但这会使原始类充满它不应该具有的依赖项。

    如果不需要代理行为,另一种可能性是解开代理并使用真实对象。在您创建代理的上下文中,您应该有一个像这样的地图:

    Map<Proxy, Original> map;
    

    您不应该传递 JAVA 代理。每个对象都必须知道,传入了一个代理,因为它们可能将它们存储在激活 equals 和 hashcode 的 Set-Implementations 中。一旦你传递了 JAVA 代理,你就会污染使用具有这种依赖关系的类。

    Java 代理应尽可能隔离。生成 Java 代理的类应该是唯一使用它们的类。

    另一种(更简单的)可能性是使用没有任何 JAVA 代理的标准代理模式。但这里又一次:如果你传递你的代理对象,你也必须考虑在 hashcode/equals 中使用代理包装。

    【讨论】:

      最近更新 更多