Java抽象方法与抽象参数以及继承

4

我最近在API和实现方面遇到了一个问题,其中出现了以下类型的代码:

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

实现是一个简单的类:
public class B extends A {
 public B sum(B b) {
  System.out.println("B.sum(B) called");
  return null;
 }
}

在使用它时,我写下:

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

这导致:
B.sum(B) called
A.sum(A) called
A.sum(A) called

我知道B的sum方法不会覆盖A的sum方法,因为它们的签名不同,但我想提供一种有效类型为B的对象的高效实现。我认为这样的设计相当经典,并且我想知道如何设计我的API和实现,使其效率更高。
当然,我可以在B类中提供"sum(A a)"方法,在调用"sum(B b)"或者super之前检查b是否是B的实例。但我认为应该避免使用instanceof来提高效率。(如果它本身很低效的话,可能会更低效)
3个回答

4

instanceof通常可以通过使用访问者模式来避免。根据您的需求,这可能是过度设计或恰到好处。它很灵活,但相当冗长。在下面的示例中,我从A中删除了abstract,以说明它如何与不同类型一起使用。

诀窍在于,当一个对象被要求访问一个访问者时,对象本身选择访问者中正确的accept方法。通过多态解决了"instanceof"检查。(我怀疑它比instanceof更有效率。)

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

输出:

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

免责声明:我写这个访客已经有一段时间了,如果我在这个(几乎未经测试的)代码片段中有任何错误,请纠正我。或者更好的是,自己编辑帖子并改进它 :)


好的,对我来说似乎有些过度了,我想测试一下它的效率(以评估instanceof和多态之间的差异)。如果我有一些数据,我会在这里发布的。 - dodecaplex
SumVisitor 中有一个 bug,但我不够聪明去修复它 - 你从未使用 arg,只是将所有东西加到自己身上,这意味着 x.visit(new SumVisitor(y)) 只是一种迂回的方式来写 x.sum(x)。仅仅将 accept(B) 更改为 return b.sum(arg)return arg.sum(b) 是行不通的,因为两者都会解析为 sum(A) - gustafc
@gustafc,是的。你说得对。我在发布时就有不好的感觉...因此会声明;); 所以我想需要一些双重派遣。修复它的一种方法是将SumVisitor分为左侧访问者和右侧访问者。 - aioobe

3
由于 myA.sum(myB) 可以将 B 实例与 A 实例相加,因此您应该能够更改 Bsum 定义,使其覆盖原有定义。除非 sum 是一个占位符,并且不应该是可交换的。
更新: 如果这还不够,您可以开始使用泛型。以下是我所说的粗略步骤:
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 是正确的,通常被接受的解决方法是使用访问者模式。我提供这些作为不太完整但更简洁的替代方案。

嗯,我可以在类B中添加以下方法来强制动态性:public A sum(A a) { if (a instanceof B) { return this.sum((B) a); } else { return super.sum(a); } }但是这种方法会违背我的初衷(即提供更高效的实现,因为instanceof通常被认为非常低效)。我的问题更多是关于设计模式,因为我觉得这个目的相当普遍。 - dodecaplex
我的观点是sum通常被理解为可交换的。如果您实际上正在实现一个求和函数,那么使用@Override B sum(A a)签名就足以让您得到想要的结果,同时仍然保持可交换性。我意识到sum可能只是一个占位符,因此在结尾处加了一个警告。 - Hank Gay
我不明白这是如何工作的。我从A中删除了abstract,并尝试调用b.sum(a),但它仍然转到B的sum实现。 - aioobe
@aioobe 如果你想调用 A.sum(A),你需要实例化一个 A,例如 A basa = new A();。或者,你可以创建一个新的 A 子类,它不会覆盖 A - Hank Gay

0

那么,你认为什么使得instanceof变慢了呢?在JDK中,它被用于多个地方,其中他们想要为某些众所周知的抽象类或接口的实现提供“快速路径”。通常的建议也适用于这里:“测试,不要猜测。”


你说得对。这正是我从几个邮件列表的讨论中了解到的。我认为有些人将其用作开源XML解析器中某些设计的辩解。如果我没记错,stax api使用枚举来区分节点/注释/属性事件,而不是依赖于instanceof。如果我有时间编写测试,我会在这里发布结果... - dodecaplex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接