Search code examples
javainheritanceabstract-classinstanceof

Java abstract method with abstract parameter and inheritance


I recently fumbled into a problem with an API and an implementation where the following type of code appeared:

public abstract class A {
 public A sum(A a) {
  System.out.println("A.sum(A) called");
  return null;
 }
}

The implementation is a simple class:

public class B extends A {
 public B sum(B b) {
  System.out.println("B.sum(B) called");
  return null;
 }
}

When it comes to using it I write:

public class Main {
  public static void main(String[] args) {
    B b = new B();
    A basa = new B();
  
    b.sum(b);
    basa.sum(b);
    basa.sum(basa);
  }  
}

Which results in:

B.sum(B) called
A.sum(A) called
A.sum(A) called

I understand that B's sum does not override A's sum as its signature is different, but I'd like to provide an efficient implementation of sum for objects of effective type B. I think such design is quite classical and I would like to know how I should design my API and implementation so that it is efficient.

Of course I could provide sum(A a) in class B and check if b is an instanceof B before calling either sum(B b) or super, but I thought that instanceof was to be avoided for efficiency reasons. (if it is inefficient, it may be even less efficient with my abstract implementation)


Solution

  • instanceof can usually be avoided by using the visitor pattern. Depending on your needs, it may or may not be an overkill. It's flexible but quite verbose. In the example below I removed abstract from A to illustrate how it works with different types.

    The trick is that when an object is asked to visit a visitor, the object itself chooses the correct accept method in the visitor. The "instanceof"-check is resolved through polymorphism. (I doubt that it's more efficient than an instanceof though.)

    interface Visitor {
        public A accept(A a);
        public B accept(B b);
    }
    
    class A {
        public A sum(A a) {
            System.out.println("A.sum(A) called");
            return null;
        }
    
        public A visit(Visitor sv) {
            return sv.accept(this);
        }
    }
    
    class B extends A {
        public B sum(B b) {
            System.out.println("B.sum(B) called");
            return null;
        }
    
        public B visit(Visitor sv) {
            return sv.accept(this);
        }
    }
    
    public class Test {
    
        public static void main(String[] args) {
            A a = new A();
            B b = new B();
            A basa = new B();
    
            a.visit(new SumVisitor(b));        // a.sum(b);
            b.visit(new SumVisitor(b));        // b.sum(b);
            basa.visit(new SumVisitor(b));     // basa.sum(b);
            basa.visit(new SumVisitor(basa));  // basa.sum(basa);
        }
    
        static class SumVisitor implements Visitor {
            A arg;
            SumVisitor(A arg) { this.arg = arg; }
            public A accept(A a) { return a.sum(arg); }
            public B accept(B b) { return b.sum(arg); }
        }
    }
    

    Output:

    A.sum(A) called
    B.sum(B) called
    B.sum(B) called
    B.sum(B) called
    

    Disclamer; It was a while ago I wrote a visitor, so please correct me if I have any bugs in this (almost untested) code snippet. Or better, edit the post yourself and improve it :)