Java is super and subclass

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

      

Initially this may sound correct, but in the following case it violates the principle of symmetry of the general specification contract for Object, which states: "It's symmetric: for any non-zero reference values ​​x and y, x.equals (y) must return true if and only if y.equals (x) returns true ". http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object) The imperfect case is as follows:

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 former returns true and the latter returns false. One solution that might seem correct would be to use getClass()

instead instanceof

. However, this would be unacceptable in cases where we are looking for B in collections. For example:

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

      

The method set.contains(new B(1,2));

returns false. This example as it is may not be ideal for logical visualization, but imagine that A was the vehicle class and B was the car class, and field a was the number of wheels and field b was the number of doors. When we call the addition method, we basically ask, "Does our set contain a 4-wheel vehicle?" The answer must be "yes" as it contains it, whether it is a car and the number of doors it has. Joshua Block's solution suggests in Effective Java 2nd Ed pg 40 not to have B inherit from A and instead have an instance of A as a field in 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 ability to use inheritance in a large number of cases when it is necessary? That is, when we have classes with additional properties that we need to propagate more general ones, in order to reuse the code and just add their additional more specific properties.

+3


source to share


1 answer


Regardless of the build issues, I would find it extremely unintuitive and confusing to b.equals(a)

return true

.

If you want to consider a

and b

equal in some context, then explicitly implement equality logic for that context.

1) For example, to use a

and b

in HashSet

, relying on equality logic a

, execute adapter , which will contain a

and implement the method 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 just add adapter objects to the set:

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

      



Then it set.contains(new MyAdapter(new B(1,2)));

returns true

.

Of course, you can write a wrapper class and pass it a

(and b

s) directly, hiding MyAdapter

from the client code (it could be a class private static

in a wrapper class) for better readability.

2) An option from the standard jdk libraries is to use TreeSet

:

Note that the ordering supported by the set (regardless of the comparator) must match equals

if it correctly implements the interface Set

. (See Comparable

or Comparator

for a precise definition consistent with equals

.) This is because the interface Set

is defined in terms of equals

, but the instance TreeSet

does all the comparison elements using the method compareTo

(or compare

), so the two elements that are considered equal in this way are, with point of view of the set, equal.
The behavior of a set is well defined, even if its ordering is incompatible with equals; it just doesn't obey the general contract of the Set interface.

Since it TreeSet

doesn't rely on equals

, just implement the correct comparator in the same way you have implemented MyAdapter.equals

(to return 0

if a1.getA() == a2.getA()

);

+1


source







All Articles