从基类方法调用被覆盖的基类函数

41
public class A {
    public void f1(String str) {
        System.out.println("A.f1(String)");
        this.f1(1, str);
    }

    public void f1(int i, String str) {
        System.out.println("A.f1(int, String)");
    }
}



public class B extends A {
    @Override
    public void f1(String str) {
        System.out.println("B.f1(String)");
        super.f1(str);
    }

    @Override
    public void f1(int i, String str) {
        System.out.println("B.f1(int, String)");
        super.f1(i, str);
    }
}


public class Main {
    public static void main(String[] args) {
        B b = new B();
        b.f1("Hello");
    }
}

我希望这段代码能输出:

B.f1(String)
A.f1(String)
A.f1(int, String)

但我得到的是:

B.f1(String)
A.f1(String)
B.f1(int, String)
A.f1(int, String)
我理解在B的上下文中,A.f1(String) 中的 "this" 是B的实例。 我是否可以按照链式操作 new B1().f1(String) -> (A的) f1(String) -> (A的) f1(int, String)?
这是一个理论性的问题,实际上解决方案显然是在A中实现一个私有函数,使得f1(String)和f1(int, String)都会调用它。
谢谢, Maxim.

2
我尝试使用反射(获取A类的f1(int,String)方法对象并调用它),但它没有起作用。Sun博客中的这篇博客文章指出,这在Java中是不可能的。 - esaj
4个回答

38

很遗憾,不行。

我相信你已经知道,但为了完整起见,我会明确说明 - 只有两个关键字控制方法调用:

  • this - this.method() - 从调用实例的类(实例的“顶部”虚拟表 - 隐含默认值)开始查找方法
  • super - super.method() - 从定义调用方法的类的父类开始查找方法(调用类的父级虚拟表 - 不严格正确,但这样思考更简单 - 感谢@maaartinus)

我可以想象另一个关键字(例如current?)可以做到你所描述的:

  • current - current.method() - 从定义调用方法的类开始查找方法

但Java没有这样的关键字(还没有?)。


据我所知,super.method() 不会从父类的 VT 开始。根本没有 VT 查找,它的工作方式类似于调用私有方法。据我所知,它使用 invokespecial 进行编译。 - maaartinus
@maaartinus - 谢谢 - 啊,我喜欢每天学习新东西。所以,没有 invokevirtualstartingfromparent 指令 - invokespecial 用于 super 调用,而 invokespecial 使用编译时绑定。因此,在 super 调用时,我们已经了解足够的类型信息来进行目标方法的编译时解析。如果该方法在直接超类中未实现,则我假设仍需要等效于向上搜索层次结构的操作。最终结果是否有效相同? - Bert F
我认为在运行时没有任何查找,即使在这种情况下也是如此。IIUIC,“super”实际上意味着第一个拥有它的祖先。编译器必须知道整个层次结构,因此它会将正确的祖先写入类文件。如果该方法不存在(只能通过使用另一个类文件的技巧来完成),则会抛出NoSuchMethodError。 - maaartinus
@maartinus - 我同意,该方法始终在编译时解析。我的意思是,在编译时仍然需要“搜索的相当部分……”,因此最终结果是相同的——找到并调用层次结构中最近的方法。 - Bert F

22

很遗憾,这是不可能的,但有一个简单的解决方法:

public class A {
    public void f1(String str) {
        System.out.println("A.f1(String)");
        privateF1(1, str);
    }

    private void privateF1(int i, String str) {
        System.out.println("A.f1(int, String)");
    }

    public void f1(int i, String str) {
        privateF1(i, str);
    }
}

4
在方法调用前加上this.总是多余的。 - Lew Bloch

8
在Java中,重写的方法是动态绑定的。也就是说,对象实例的类型决定了将调用什么方法。而 final 方法(无法被重写)和private方法(无法被继承)是静态绑定的。
相比之下,在C++中,如果要获得相同的行为,则必须显式地将函数定义为virtual

-1
package main;

public class A {
public void f1(String str) {
    System.out.println("A.f1(String)");
    if (this instanceof B)
        new A().f1(1, str);
    else
        this.f1(1, str);
}

public void f1(int i, String str) {
    System.out.println("A.f1(int, String)");
}

}

class B extends A {
@Override
public void f1(String str) {
    System.out.println("B.f1(String)");
    super.f1(str);
}

@Override
public void f1(int i, String str) {
    System.out.println("B.f1(int, String)");
    super.f1(i, str);
}

public static void main(String[] args) {
A a = new B();
    a.f1("Hello");
}
}

1
这个问题已经超过3年了。你只是发布了一些没有任何解释的代码。另外,你的设计中存在一个缺陷,即A类需要知道所有它的子类(在这个简单的例子中为B),如果你想创建一个新的子类,这就很困难,因为你还需要调整A类。 - Uwe Plonus

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