【问题标题】:Java: Calling a super method which calls an overridden methodJava:调用调用重写方法的超级方法
【发布时间】:2011-06-03 11:45:19
【问题描述】:
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

我知道从技术上讲我已经覆盖了一个公共方法,但我认为因为我正在调用超级,所以超级中的任何调用都会保留在超级中,这不会发生。关于如何实现它的任何想法?

【问题讨论】:

  • 我怀疑你可能想要“更喜欢组合而不是继承”。

标签: java inheritance overriding super


【解决方案1】:

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

这意味着没有直接的方法从SuperClass.method1() 调用SuperClass.method2() 而不经过SubClass.method2(),除非您正在使用SuperClass 的实际实例。

您甚至无法使用反射实现所需的效果(请参阅the documentation of java.lang.reflect.Method.invoke(Object, Object...))。

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

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

所以当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() 中创建的实例。

【讨论】:

  • 有人可以上传一张这个(双关语)通过堆栈的图表吗?提前致谢!
  • @laycat:不需要图表。请记住,Java 没有super 的“内存”。每次调用方法时,它都会查看实例类型,并开始搜索该类型的方法,无论您调用super 的频率如何。因此,当您在 SubClass 的实例上调用 method2 时,它总是会首先看到来自 SubClass 的实例。
  • @AaronDigulla,您能详细解释一下“Java 没有内存可用于 super”吗?
  • @Truman'sworld:正如我在回答中所说:使用super 不会更改实例。它没有设置一些隐藏字段“从现在开始,所有方法调用都应该使用SuperClass开始”。或者换一种说法:this 的值不会改变。
  • @AaronDigulla,这是否意味着 super 关键字实际上是调用子类中的继承方法而不是转到超类?
【解决方案2】:

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

所以:要么不要覆盖method2(),要么在覆盖的版本中调用super.method2()

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • true,不使用this 也无济于事。不合格的调用隐式使用this
    • 为什么会获得投票?这不是这个问题的答案。当您编写method2() 时,编译器将看到this.method2()。因此,即使您删除了this,它仍然无法正常工作。 @Sean Patrick Floyd 所说的是正确的
    • @Shervin 他没有说错,他只是不清楚如果你忽略this会发生什么@
    • 答案是正确的指出this 指的是“具体的运行实例类”(在运行时已知)而不是(正如海报似乎相信的那样)“当前的编译单元class”(使用关键字的地方,在编译时已知)。但它也可能会产生误导(正如 Shervin 指出的那样):this 也被普通方法调用隐式引用; method2();this.method2(); 相同
    【解决方案4】:

    我是这样想的

    +----------------+
    |     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 Language Specification

    super.Identifier 的形式是指名为Identifier 的字段 当前对象,但将当前对象视为 当前类的超类。

    T.super.Identifier 的形式是指名为Identifier 的字段 对应于T 的词法封闭实例,但具有 instance 被视为T 的超类的一个实例。

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

    因此,如果你从超类中使用super,你会从superduper类[祖父母]执行代码),而如果你从超类中使用this(或者如果它被隐式使用)它会一直指向子类(因为没有人改变它 - 也没有人可以)。

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:
        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

        【讨论】:

          【解决方案7】:

          由于避免方法被覆盖的唯一方法是使用关键字 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
          

          【讨论】:

            【解决方案8】:

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

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

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

            如果你有一个外盒的实例,一个Subclass 对象,你碰巧在盒子里面冒险,即使进入Superclass“区域”,它仍然是外盒的实例。

            更重要的是,在这个程序中,三个类中只有一个对象被创建,所以this 只能引用一件事,它是:

            Netbeans'Heap Walker'所示。

            【讨论】:

              【解决方案9】:

              我不相信你可以直接做到这一点。一种解决方法是在超类中拥有 method2 的私有内部实现,然后调用它。例如:

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

              【讨论】:

                【解决方案10】:

                “this”关键字引用当前类引用。这意味着,当它在方法中使用时,“当前”类仍然是子类,所以答案已经解释了。

                【讨论】:

                  【解决方案11】:

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

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

                  【讨论】:

                    【解决方案12】:

                    在研究类似案例的过程中,我一直在检查子类方法中的堆栈跟踪以找出调用的来源。可能有更聪明的方法可以做到这一点,但它对我来说很有效,而且是一种动态的方法。

                    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 构建完整的堆栈跟踪,仅比较名称而不是完整签名很容易出错。此外,它散发出巨大的设计缺陷。
                    • 从性能的角度来看,我的代码似乎并没有比'new HashMap().size()'产生更大的影响。但是,我可能忽略了您一直在考虑的问题,而且我绝对不是 VM 专家。我通过比较类名看到了你的疑问,但它包含了让我非常确定的包,它是我的父类。无论如何,我喜欢比较签名的想法,你会怎么做?一般来说,如果您有更流畅的方法来确定调用者是超类还是其他任何人,我会很感激。
                    • 如果您需要确定调用者是否是超类,如果重新设计到位,我会认真考虑更长的时间。这是一种反模式。
                    • 我明白这一点,但线程的一般要求是合理的。在某些情况下,超类方法调用与任何嵌套方法调用保持在超类上下文中是有意义的。但是,似乎没有办法在超类中相应地指导方法调用。
                    【解决方案13】:

                    进一步扩展了所提出问题的输出,这将更深入地了解访问说明符和覆盖行为。

                                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
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2014-02-27
                      • 2017-07-07
                      • 1970-01-01
                      • 2012-09-10
                      • 2018-07-08
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多