简体   繁体   English

Java等于超级和子类

[英]Java equals super and sub class

I have the following two very simple classes: 我有以下两个非常简单的类:

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

And its subclass: 它的子类:

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

This might seem right initially, but in the following case it violates the symmetry principle of the general contract of the specification for Object, which states: "It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true." 这看起来似乎是正确的,但在下面的例子中,它违反了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) The failing case is the following: 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));
    }

}

The first returns true, while the second returns false. 第一个返回true,而第二个返回false。 One solution that might seem right would be to use getClass() instead of instanceof . 一个似乎正确的解决方案是使用getClass()而不是instanceof However, this would not be acceptable in cases where we look for B in collections. 但是,在我们在集合中查找B的情况下,这是不可接受的。 For example: 例如:

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

The method set.contains(new B(1,2)); 方法set.contains(new B(1,2)); would return false. 将返回false。 This example as is, might not be ideal to be logically visualized but imagine if A was a Vehicle class and B was a Car class, with field a being the number of wheels and field b being the number of doors. 这个例子可能不是理想的逻辑可视化,但想象一下如果A是一个Vehicle类而B是一个Car类,其中a是轮子的数量,而字段b是门的数量。 When we call the contains method we essentially ask: "Does our set contain a 4-wheel vehicle?" 当我们调用contains方法时,我们基本上会问:“我们的套装是否包含一个四轮车?” The answer should be yes since it does contain it, regardless of whether it's a car and the number of doors it has. 答案应该是肯定的,因为它确实包含它,无论它是汽车还是它的门数。 The solution Joshua Block suggests in Effective Java 2nd Ed pg 40 is not to have B inherit from A and have an instance of A as a field in B instead: 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;
    }
}

However, does this mean that we lose the power to use inheritance in a great number of cases where it is needed? 但是,这是否意味着我们在需要它的大量情况下失去了使用继承的权力? That is, when we have classes with extra properties that need to extend the more general ones to reuse the code and just add their extra more specific properties. 也就是说,当我们拥有具有额外属性的类时,需要扩展更一般的属性以重用代码并只添加其更具体的属性。

Regardless of the collection issues, I would find it highly unintuitive and confusing for b.equals(a) to return true . 无论收集问题如何,我都会发现b.equals(a)返回true是非常不直观和令人困惑的。

If you want to consider a and b equal in some context, then implement explicitly the equality logic for that context. 如果要在某些上下文中考虑ab相等,则明确实现该上下文的相等逻辑。

1) For example, to use A s and B s in a HashSet by relying on equality logic of A , implement an adapter that will contain A and implement the equals method: 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();
    }
}

Then simply add adapter objects to the set: 然后只需将适配器对象添加到集合中:

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

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

Of course, you can write a wrapper class and pass it A s (and B s) directly, hiding MyAdapter from the client code (it can be private static class in the wrapper class) for better readability. 当然,您可以编写一个包装类并直接将它传递给A (和B s),将MyAdapter隐藏在客户端代码中(它可以是包装类中的private static类),以提高可读性。

2) An option from the standard jdk libraries is to use TreeSet : 2)标准jdk库中的一个选项是使用TreeSet

Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. 请注意,如果要正确实现Set接口,则由set维护的排序(无论是否提供显式比较器)必须与equals一致。 (See Comparable or Comparator for a precise definition of consistent with equals .) This is so because the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare ) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. (有关与equals一致的精确定义,请参阅ComparableComparator 。)这是因为Set接口是根据equals操作定义的, TreeSet实例使用compareTo (或compare )方法执行所有元素比较,因此从集合的角度来看,通过这种方法被认为相等的元素是相等的。 The behavior of a set is well-defined even if its ordering is inconsistent with equals; 集合的行为即使其排序与equals不一致也是明确定义的; it just fails to obey the general contract of the Set interface. 它只是不遵守Set接口的一般合同。

Because TreeSet does not rely on equals , just implement the proper comparator in the same fashion you implemented MyAdapter.equals (to return 0 if a1.getA() == a2.getA() ); 因为TreeSet不依赖于equals ,所以只需实现适当的比较器,就像你实现MyAdapter.equals (如果a1.getA() == a2.getA()则返回0 );

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

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