Java:调用调用了覆盖方法的超级方法

112
public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

我的期望输出:

子类方法1
父类方法1
父类方法2

实际输出:

子类方法1
父类方法1
子类方法2

我知道技术上我重写了一个公共方法,但我认为由于我在调用super时,super内部的任何调用都应该留在super中,但事实并非如此。你有什么想法吗?


2
我猜你可能想要“优先使用组合而非继承”。 - Tom Hawtin - tackline
13个回答

98
关键字super不会“粘着”。每个方法调用都是独立处理的,因此即使您通过调用super到达了SuperClass.method1(),这也不会影响您将来可能进行的任何其他方法调用。
这意味着除非您使用实际的SuperClass实例,否则无法直接从SuperClass.method1()调用SuperClass.method2()而不经过SubClass.method2()
甚至使用反射(请参见java.lang.reflect.Method.invoke(Object, Object...)的文档)也无法实现所需的效果。 [编辑]仍然存在一些困惑。让我尝试另一种解释。
当您调用foo()时,实际上是调用this.foo()。Java只是让您省略this。在问题中的示例中,this的类型为SubClass
因此,当Java执行SuperClass.method1()中的代码时,它最终到达this.method2(); 使用super不会更改由this指向的实例。因此,由于this的类型为SubClass,所以调用转到SubClass.method2()
也许你可以这样理解,当Java传递参数时,会将this作为一个隐藏的第一个参数传递进去:
public class SuperClass
{
    public void method1(SuperClass this)
    {
        System.out.println("superclass method1");
        this.method2(this); // <--- this == mSubClass
    }

    public void method2(SuperClass this)
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1(SubClass this)
    {
        System.out.println("subclass method1");
        super.method1(this);
    }

    @Override
    public void method2(SubClass this)
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1(mSubClass);
    }
}

如果你跟踪调用堆栈,你会发现this从未改变,它始终是在main()中创建的实例。


有没有人能够上传一个图表,展示这个(有意味的)通过堆栈的过程?先感谢了! - laycat
2
@laycat:不需要画图。只需记住Java对super没有"内存"。每次调用方法时,它都会查看实例类型,并开始搜索具有此类型的方法,无论您调用super多少次。因此,当您在SubClass实例上调用method2时,它总是首先查看SubClass中的方法。 - Aaron Digulla
@AaronDigulla,你能详细解释一下“Java没有超级内存”的问题吗? - MengT
@Truman'sworld:就像我在答案中所说的那样:使用super不会改变实例。它不会设置一些隐藏字段“从现在开始,所有方法调用都应该使用SuperClass”。或者换句话说:this的值不会改变。 - Aaron Digulla
@AaronDigulla,这是否意味着super关键字实际上是在子类中调用继承的方法,而不是转到超类? - MengT
@Truman'sworld:不,super 只影响当前的方法调用。它不会“粘”或应用于任何基于 this 的后续方法调用。 - Aaron Digulla

17

你只能在覆盖方法中访问被覆盖的方法(或覆盖类的其他方法)。

因此:要么不覆盖method2(),要么在覆盖版本中调用super.method2()


11

我这样想

+----------------+
|     super      |
+----------------+ <-----------------+
| +------------+ |                   |
| |    this    | | <-+               |
| +------------+ |   |               |
| | @method1() | |   |               |
| | @method2() | |   |               |
| +------------+ |   |               |
|    method4()   |   |               |
|    method5()   |   |               |
+----------------+   |               |
    We instantiate that class, not that one!

让我把这个子类稍微向左移一下,以便显示下面的内容... (哇,我真的很喜欢ASCII图形)
We are here
        |
       /  +----------------+
      |   |     super      |
      v   +----------------+
+------------+             |
|    this    |             |
+------------+             |
| @method1() | method1()   |
| @method2() | method2()   |
+------------+ method3()   |
          |    method4()   |
          |    method5()   |
          +----------------+

Then we call the method
over here...
      |               +----------------+
 _____/               |     super      |
/                     +----------------+
|   +------------+    |    bar()       |
|   |    this    |    |    foo()       |
|   +------------+    |    method0()   |
+-> | @method1() |--->|    method1()   | <------------------------------+
    | @method2() | ^  |    method2()   |                                |
    +------------+ |  |    method3()   |                                |
                   |  |    method4()   |                                |
                   |  |    method5()   |                                |
                   |  +----------------+                                |
                   \______________________________________              |
                                                          \             |
                                                          |             |
...which calls super, thus calling the super's method1() here, so that that
method (the overidden one) is executed instead[of the overriding one].

Keep in mind that, in the inheritance hierarchy, since the instantiated
class is the sub one, for methods called via super.something() everything
is the same except for one thing (two, actually): "this" means "the only
this we have" (a pointer to the class we have instantiated, the
subclass), even when java syntax allows us to omit "this" (most of the
time); "super", though, is polymorphism-aware and always refers to the
superclass of the class (instantiated or not) that we're actually
executing code from ("this" is about objects [and can't be used in a
static context], super is about classes).

换句话说,引用自Java语言规范

表单super.Identifier指的是当前对象的名为Identifier的字段,但将当前对象视为当前类的超类的实例。

表单T.super.Identifier指的是名为T的词汇封闭实例对应的名为Identifier的字段,但将该实例视为T的超类的实例。

通俗易懂地说,this基本上是一个对象(*那个**对象;你可以在变量中移动的同一个对象),实例化类的实例,数据域中的普通变量;super就像是一个指向要执行的借来的代码块的指针,更像是一个简单的函数调用,并且它是相对于调用它的类而言的。
因此,如果您从超类使用super,则会执行来自超级父类[祖父母]的代码,而如果您从超类使用this(或者如果它被隐式使用),它将继续指向子类(因为没有人更改它-也没有人能够)。

8

你正在使用 this 关键字,它实际上指的是“当前正在运行的对象实例”,也就是说,你正在调用你的超类上的 this.method2(); 方法,它会在你正在使用的对象上调用 method2() 方法,也就是 SubClass。


9
正确的,并且不使用“this”也无济于事。一个未经限定的调用会隐含地使用“this”。 - Sean Patrick Floyd
3
为什么这个回答会得到点赞?这不是这个问题的答案。当你写method2()时,编译器会看到this.method2()。所以即使你去掉了this,它仍然不起作用。@Sean Patrick Floyd所说的是正确的。 - Shervin Asgari
4
@Shervin他说的话并没有错,只是没有清楚地表达如果你省略了“this”会发生什么。 - Sean Patrick Floyd
4
答案正确地指出了this指的是“具体运行实例类”(在运行时已知),而不是(像发布者似乎认为的那样)“当前编译单元类”(关键字所在的位置,在编译时已知)。但它也可能会产生误导(正如Shervin所指出的):this还隐含地与纯方法调用相关联; method2();this.method2();相同。 - leonbloy

3
如果您不希望superClass.method1调用subClass.method2,可以将method2设置为私有方法,这样就无法重写它。
以下是建议:
public class SuperClass {

  public void method1() {
    System.out.println("superclass method1");
    this.internalMethod2();
  }

  public void method2()  {
    // this method can be overridden.  
    // It can still be invoked by a childclass using super
    internalMethod2();
  }

  private void internalMethod2()  {
    // this one cannot.  Call this one if you want to be sure to use
    // this implementation.
    System.out.println("superclass method2");
  }

}

public class SubClass extends SuperClass {

  @Override
  public void method1() {
    System.out.println("subclass method1");
    super.method1();
  }

  @Override
  public void method2() {
    System.out.println("subclass method2");
  }
}

如果不是这样工作的话,多态将是不可能的(或者至少不会有一半的用处)。

3
class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        SuperClass se=new SuperClass();
        se.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }
}


class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

调用

SubClass mSubClass = new SubClass();
mSubClass.method1();

输出

子类方法1
父类方法1
父类方法2


2
“this” 指的是当前正在执行的对象。
为了进一步说明这一点,这里有一个简单的草图:
+----------------+
|  Subclass      |
|----------------|
|  @method1()    |
|  @method2()    |
|                |
| +------------+ |
| | Superclass | |
| |------------| |
| | method1()  | |
| | method2()  | |
| +------------+ |
+----------------+

如果您有外部盒子的实例,一个名为Subclass的对象,无论您进入盒子内部的哪个区域,甚至是Superclass的区域,它仍然是外部盒子的实例。
此外,在此程序中,只创建了三个类中的一个对象,因此this只能引用一件事情,那就是:

enter image description here

如图所示,在Netbeans的“堆分析器”中。

2

由于避免方法被覆盖的唯一方法是使用关键字super,因此我考虑将SuperClass中的method2()移至另一个新的Base类中,然后从SuperClass中调用它:

class Base 
{
    public void method2()
    {
        System.out.println("superclass method2");
    }
}

class SuperClass extends Base
{
    public void method1()
    {
        System.out.println("superclass method1");
        super.method2();
    }
}

class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

输出:

subclass method1
superclass method1
superclass method2

1

我不相信你可以直接这样做。一个解决方法是在超类中拥有一个私有的内部实现方法2,并调用它。例如:

public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.internalMethod2();
    }

    public void method2()
    {
        this.internalMethod2(); 
    }
    private void internalMethod2()
    {
        System.out.println("superclass method2");
    }

}

1
总之,这指向当前对象,Java中的方法调用本质上是多态的。因此,执行方法的选择完全取决于由this指向的对象。因此,从父类调用方法method2()会调用子类的method2(),因为this指向子类对象。无论在哪个类中使用,this的定义都不会改变。
附:与方法不同,类的成员变量不具有多态性。

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