问题
I recently fumbled into a problem with an API and an implementation where the following type of code appeared:
The API is an abstract class:
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:
% java Main
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 would like to now how I should design my API and implementation so that it is efficient.
Of course, I could provide sum(A a) in class be and check if b is an instanceOf B before calling either sum(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 as my "abstract" implementation.
回答1:
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 :)
回答2:
Since B
instances can be summed with A
instances using myA.sum(myB)
, you should be able to change B
's definition of sum
so that it does override, unless of course sum
is a placeholder and isn't something that should be commutative.
UPDATE:
If this is insufficient, you could start getting fancy with generics. Here's a rough pass at what I mean:
public abstract class A {
public <T extends A> T sum(T a) {
System.out.println("A.sum(A) called");
return null;
}
public static void main(String args[]) {
B b = new B();
b.sum(b);
A basa = new B();
basa.sum(b);
basa.sum(basa);
}
public static class B extends A {
@Override
public <T extends A> T sum(T b) {
System.out.println("B.sum(B) called");
return null;
}
}
}
@aioobe is right that the generally accepted work-around is to use the Visitor pattern. I'm offering these as less complete but less verbose alternatives.
回答3:
So, what makes you think instanceof
is slow? It's used in several places in the JDK where they want to provide a "fast path" for certain well-known implementations of an abstract class or interface. The usual advice applies here: "Test, don't guess."
来源:https://stackoverflow.com/questions/3148192/java-abstract-method-with-abstract-parameter-and-inheritance