繁体   English   中英

Java等于超级和子类

[英]Java equals super and sub class

我有以下两个非常简单的类:

   public class A {

    private int a;

    public A(int a)
    {
    this.a=a;
    }
    public int getA(){
        return a;
    }
    public boolean equals(Object o)
    {
        if (!(o instanceof A))
            return false;
        A other = (A) o;
        return a == other.a;
    }
}

它的子类:

public class B extends A{
    private int b;
    public B(int a, int b)
    {
        super(a);
        this.b = b;
    }
    public boolean equals(Object o)
    {
        if (!(o instanceof B))
            return false;
        B other = (B) o;
        return super.getA() == other.getA() && b == other.b;
    }
}

这看起来似乎是正确的,但在下面的例子中,它违反了Object规范的一般契约的对称原则,该原则规定:“它是对称的:对于任何非空引用值x和y,x.equals(y)当且仅当y.equals(x)返回true时,才应返回true。“ http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object)失败的情况如下:

public class EqualsTester {

    public static void main(String[] args) {
        A a = new A(1);
        B b = new B(1,2);
        System.out.println(a.equals(b));
        System.out.println(b.equals(a));
    }

}

第一个返回true,而第二个返回false。 一个似乎正确的解决方案是使用getClass()而不是instanceof 但是,在我们在集合中查找B的情况下,这是不可接受的。 例如:

Set<A> set = new HashSet<A>();
set.add(new A(1));

方法set.contains(new B(1,2)); 将返回false。 这个例子可能不是理想的逻辑可视化,但想象一下如果A是一个Vehicle类而B是一个Car类,其中a是轮子的数量,而字段b是门的数量。 当我们调用contains方法时,我们基本上会问:“我们的套装是否包含一个四轮车?” 答案应该是肯定的,因为它确实包含它,无论它是汽车还是它的门数。 Joshua Block在Effective Java 2nd Ed第40页中建议的解决方案是不要让B继承自A并且将A的实例作为B中的字段来代替:

public class B {
    private A a;
    private int b;
    public B(int a, int b)
    {
        this.a = new A(a);
        this.b = b;
    }

    public A getAsA()
    {
        return A;
    }  
    public boolean equals(Object o)
    {
        if (!(o instanceof B))
            return false;
        B other = (B) o;
        return a.getA() == other.getA() && b == other.b;
    }
}

但是,这是否意味着我们在需要它的大量情况下失去了使用继承的权力? 也就是说,当我们拥有具有额外属性的类时,需要扩展更一般的属性以重用代码并只添加其更具体的属性。

无论收集问题如何,我都会发现b.equals(a)返回true是非常不直观和令人困惑的。

如果要在某些上下文中考虑ab相等,则明确实现该上下文的相等逻辑。

1)例如,要通过依赖A相等逻辑在HashSet使用AB ,实现一个包含A适配器并实现equals方法:

class MyAdapter {
   private final A a;

   MyAdapter(A a) {
      this.a = a;
   }

   public boolean equals(Object o) {
        if (!(o instanceof MyAdapter)) {
            return false;
        }
        MyAdapter other = (MyAdapter) o;
        return a.getA() == other.a.getA();
    }
}

然后只需将适配器对象添加到集合中:

Set<MyAdapter> set = new HashSet<>();
set.add(new MyAdapter(new A(1)));

然后set.contains(new MyAdapter(new B(1,2))); 返回true

当然,您可以编写一个包装类并直接将它传递给A (和B s),将MyAdapter隐藏在客户端代码中(它可以是包装类中的private static类),以提高可读性。

2)标准jdk库中的一个选项是使用TreeSet

请注意,如果要正确实现Set接口,则由set维护的排序(无论是否提供显式比较器)必须与equals一致。 (有关与equals一致的精确定义,请参阅ComparableComparator 。)这是因为Set接口是根据equals操作定义的, TreeSet实例使用compareTo (或compare )方法执行所有元素比较,因此从集合的角度来看,通过这种方法被认为相等的元素是相等的。 集合的行为即使其排序与equals不一致也是明确定义的; 它只是不遵守Set接口的一般合同。

因为TreeSet不依赖于equals ,所以只需实现适当的比较器,就像你实现MyAdapter.equals (如果a1.getA() == a2.getA()则返回0 );

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM