Java:调用超级方法,该方法调用覆盖的方法


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

我的预期输出:

子类method1
超类method1
超类method2

实际输出:

子类method1
超类method1
子类method2

从技术上讲,我知道我已经重写了一个公共方法,但是我发现由于我正在调用超级方法,因此超级方法中的任何调用都将保留在超级方法中,这不会发生。关于如何实现它的任何想法?


2
我怀疑您可能想“更喜欢继承而不是继承”。
Tom Hawtin-抢险进球

Answers:


79

关键字super不“粘”。每个方法调用都是单独处理的,因此即使您必须SuperClass.method1()通过调用super,也不会影响将来可能进行的任何其他方法调用。

这意味着没有直接的方法来调用SuperClass.method2()SuperClass.method1()没有去,虽然SubClass.method2(),除非你有一个实际的实例工作SuperClass

您甚至无法使用Reflection实现理想的效果(请参阅的文档java.lang.reflect.Method.invoke(Object, Object...))。

[编辑]似乎仍然有些混乱。让我尝试不同的解释。

调用时foo(),实际上是在调用this.foo()。Java只是让您省略this。在问题的示例中,类型thisSubClass

因此,当Java在中执行代码时SuperClass.method1(),它最终到达this.method2();

使用super不会更改指向的实例this。因此呼叫转到,SubClass.method2()因为this类型为SubClass

当您想象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()


有人可以通过堆栈上传这个(双关语意)图表吗?提前致谢!
躺猫2014年

2
@laycat:不需要图表。只要记住Java没有的“内存”即可super。每次调用方法时,无论您调用的频率如何,它都会查看实例类型,并开始搜索具有该类型的方法super。因此,当您调用method2的实例时SubClass,它将始终从SubClass第一个开始看到该实例。
亚伦·迪古拉

@AaronDigulla,您能详细解释一下“ Java没有超级存储空间”吗?
MengT 2014年

@ Truman'sworld:正如我在回答中所说:使用super不会更改实例。从现在开始,它并没有设置任何隐藏字段,所有方法调用都应使用SuperClass“。或this换一种说法:的值不变。
亚伦·迪古拉

@AaronDigulla,这是否意味着super关键字实际上是在子类中调用继承的方法,而不是转到超类?
MengT 2014年


8

您使用的this关键字实际上是指“您正在使用的对象的当前正在运行的实例”,也就是说,您正在调用this.method2();您的超类,也就是说,它将在您所使用的对象上调用method2()重新使用,这是SubClass。


8
是的,不使用this也无济于事。不合格的调用隐式使用this
肖恩·帕特里克·弗洛伊德

3
为什么会对此表示反对?这不是这个问题的答案。当您编写时method2(),编译器会看到this.method2()。因此,即使您删除了this它,仍然无法使用。@Sean Patrick Floyd说的是正确的
Shervin Asgari 2011年

4
@Shervin他并没有说错什么,他只是没有弄清楚如果您不注意会发生什么情况this
Sean Patrick Floyd

4
正确的答案是指出this“具体的运行实例类”(在运行时中已知),而不是(如发布者所认为的那样)“当前的编译单元类”(其中使用了关键字,在编译时间)。但这也可能会引起误解(如Shervin所指出的那样):this也被普通方法调用隐式引用;method2();this.method2();
leonbloy 2011年

7

我是这样想的

+----------------+
|     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是指与Identifier相对应的词汇包围实例的名称字段T,但将该实例视为的超类的实例T

用外行的术语来说,this基本上是一个对象(* the **对象;可以在变量中移动的同一对象),实例化类的实例,数据域中的普通变量;super就像是指向要执行的借用代码块的指针,更像是单纯的函数调用,它相对于被调用的类。

因此,如果您super从超类使用,您将从超级duper类[祖父母]执行代码,而如果您this从超类使用(或隐式使用)代码,则它会一直指向子类(因为没有人更改它,并且没人可以)。


2

如果您不希望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");
  }
}

如果它不能以这种方式工作,那么多态将是不可能的(或者至少是有用的一半)。


2

由于避免方法被重写的唯一方法是使用关键字super,所以我想过将method2()从SuperClass移到另一个新的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

2

this 总是指当前正在执行的对象。

为了进一步说明这一点,这里是一个简单的草图:

+----------------+
|  Subclass      |
|----------------|
|  @method1()    |
|  @method2()    |
|                |
| +------------+ |
| | Superclass | |
| |------------| |
| | method1()  | |
| | method2()  | |
| +------------+ |
+----------------+

如果您有外框的实例,那么Subclass无论您碰巧在框内冒险,甚至到Superclass “区域”,它仍然是外部框的实例。

更重要的是,在此程序中,在三个类this中仅创建了一个对象,因此只能引用一件事,它是:

在此处输入图片说明

Netbeans的 “ Heap Walker”所示。


2
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();

输出

子类method1
超类method1
超类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

“ this”关键字是指当前的类参考。这意味着,当在方法内部使用它时,“当前”类仍是SubClass,因此将解释答案。


1

总而言之,这指向当前对象,并且Java中的方法调用本质上是多态的。因此,执行的方法选择完全取决于此操作所指向的对象。因此,从父类调用方法method2()会调用子类的method2(),因为这指向子类的对象。不管使用哪个类,它的定义都不会改变。

PS。与方法不同,class的成员变量不是多态的。


0

在研究类似情况的过程中,我最终通过检查子类方法中的堆栈跟踪来找出调用的来源。也许有更聪明的方法可以这样做,但是对我来说这是可行的,而且是一种动态方法。

public void method2(){
        Exception ex=new Exception();
        StackTraceElement[] ste=ex.getStackTrace();
        if(ste[1].getClassName().equals(this.getClass().getSuperclass().getName())){
            super.method2();
        }
        else{
            //subclass method2 code
        }
}

我认为有解决该问题的问题是合理的。当然,有多种方法可以使用不同的方法名称甚至不同的参数类型来解决问题,就像在线程中已经提到的那样,但是在我的情况下,我不希望因不同的方法名称而感到困惑。


此代码危险,危险且昂贵。创建异常需要VM构造完整的堆栈跟踪,仅比较名称而不比较完整签名容易出错。而且,它冒着巨大的设计缺陷。
M. le Rutte

从性能的角度来看,我的代码似乎没有比“ new HashMap()。size()”产生更大的影响。但是,我可能忽略了您一直在考虑的问题,因此,我不是VM专家。通过比较类名,我看到了您的疑问,但是它包含了使我非常确定的软件包,这是我的父类。无论如何,我更喜欢比较签名的想法,您将如何做?通常,如果您可以更轻松地确定呼叫者是超类还是其他我将不胜感激的人。
击败Beat Siegrist的

如果需要确定呼叫者是否为超类,那么如果进行重新设计,我将认真考虑更长的时间。这是一种反模式。
M. le Rutte

我明白这一点,但是线程的一般请求是合理的。在某些情况下,任何嵌套方法调用都将超类方法调用保留在超类上下文中是有意义的。但是,似乎没有办法在超类中相应地指导方法调用。
击败Siegrist

0

进一步扩展了提出的问题的输出,这将使您对访问说明符和覆盖行为有更多的了解。

            package overridefunction;
            public class SuperClass 
                {
                public void method1()
                {
                    System.out.println("superclass method1");
                    this.method2();
                    this.method3();
                    this.method4();
                    this.method5();
                }
                public void method2()
                {
                    System.out.println("superclass method2");
                }
                private void method3()
                {
                    System.out.println("superclass method3");
                }
                protected void method4()
                {
                    System.out.println("superclass method4");
                }
                void method5()
                {
                    System.out.println("superclass method5");
                }
            }

            package overridefunction;
            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");
                }
                // @Override
                private void method3()
                {
                    System.out.println("subclass method3");
                }
                @Override
                protected void method4()
                {
                    System.out.println("subclass method4");
                }
                @Override
                void method5()
                {
                    System.out.println("subclass method5");
                }
            }

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

            subclass method1
            superclass method1
            subclass method2
            superclass method3
            subclass method4
            subclass method5
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.