【问题标题】:Inheritance and recursion继承和递归
【发布时间】:2016-04-04 12:37:06
【问题描述】:

假设我们有以下类:

class A {

    void recursive(int i) {
        System.out.println("A.recursive(" + i + ")");
        if (i > 0) {
            recursive(i - 1);
        }
    }

}

class B extends A {

    void recursive(int i) {
        System.out.println("B.recursive(" + i + ")");
        super.recursive(i + 1);
    }

}

现在让我们在 A 类中调用 recursive

public class Demo {

    public static void main(String[] args) {
        A a = new A();
        a.recursive(10);
    }

}

输出如预期的那样从 10 开始倒计时。

A.recursive(10)
A.recursive(9)
A.recursive(8)
A.recursive(7)
A.recursive(6)
A.recursive(5)
A.recursive(4)
A.recursive(3)
A.recursive(2)
A.recursive(1)
A.recursive(0)

让我们进入令人困惑的部分。现在我们在 B 类中调用 recursive

预期

B.recursive(10)
A.recursive(11)
A.recursive(10)
A.recursive(9)
A.recursive(8)
A.recursive(7)
A.recursive(6)
A.recursive(5)
A.recursive(4)
A.recursive(3)
A.recursive(2)
A.recursive(1)
A.recursive(0)

实际

B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
..infinite loop...

这是怎么发生的?我知道这是一个精心设计的例子,但它让我感到好奇。

concrete use case 的较早问题。

【问题讨论】:

  • 你需要的关键字是静态类型和动态类型!您应该搜索并阅读一些相关信息。
  • 为了得到你想要的结果,将递归方法提取到一个新的私有方法中。
  • @Onots 我认为将递归方法设为静态会更简洁。
  • 如果您注意到A 中的递归调用实际上是动态分派 到当前对象的recursive 方法,这很简单。如果您正在使用A 对象,则调用会将您带到A.recursive(),并使用B 对象将您带到B.recursive()。但是B.recursive() 总是调用A.recursive()。所以,如果你启动一个B 对象,它就会来回切换。

标签: java inheritance recursion


【解决方案1】:

其他答案都解释了要点,即一旦实例方法被覆盖,它就会保持被覆盖,除非通过super,否则无法将其取回。 B.recursive() 调用 A.recursive()A.recursive() 然后调用recursive(),它解析为B 中的覆盖。我们来回打乒乓球直到宇宙尽头或StackOverflowError,以先到者为准。

如果可以在A 中编写this.recursive(i-1) 来获得自己的实现,那就太好了,但这可能会破坏事情并产生其他不幸的后果,所以A 中的this.recursive(i-1) 调用B.recursive() 等等第四次。

有一种方法可以获得预期的行为,但它需要有远见。换句话说,您必须事先知道您希望A 子类型中的super.recursive() 被困在A 实现中。这样做是这样的:

class A {

    void recursive(int i) {
        doRecursive(i);
    }

    private void doRecursive(int i) {
        System.out.println("A.recursive(" + i + ")");
        if (i > 0) {
            doRecursive(i - 1);
        }
    }
}

class B extends A {

    void recursive(int i) {
        System.out.println("B.recursive(" + i + ")");
        super.recursive(i + 1);
    }
}

由于A.recursive() 调用doRecursive() 并且doRecursive() 永远不能被覆盖,A 确信它正在调用自己的逻辑。

【讨论】:

  • 我想知道,为什么在 recursive() 中从对象 B 调用 doRecursive() 有效。正如 TAsk 在他的回答中所写的那样,函数调用的工作方式类似于 this.doRecursive() 和 Object B (this) 没有方法 doRecursive() 因为它在类 A 中定义为 private 而不是 protected 和因此不会被继承,对吧?
  • 对象B 根本无法调用doRecursive()doRecursive()private,是的。但是当B 调用super.recursive() 时,会调用recursive()A 中的实现,它可以访问doRecursive()
  • 如果您绝对必须允许继承,这正是 Bloch 在 Effective Java 中推荐的方法。第 17 条:“如果你觉得你必须允许从 [一个不实现标准接口的具体类] 继承,一个合理的方法是确保该类永远不会调用它的任何可覆盖方法并记录事实。”
【解决方案2】:

这实际上不可能有任何其他方式。

当您调用B.recursive(10); 时,它会打印B.recursive(10),然后使用i+1A 中调用此方法的实现。

所以你调用A.recursive(11),它打印A.recursive(11),它在当前实例B上调用recursive(i-1);方法,输入参数i-1,所以它调用B.recursive(10),然后调用super使用i+1(即11)实现,然后递归调用当前实例递归使用i-1(即10),您将得到您在此处看到的循环。

这都是因为如果你在超类中调用实例的方法,你仍然会调用你正在调用它的实例的实现。

想象一下,

 public abstract class Animal {

     public Animal() {
         makeSound();
     }

     public abstract void makeSound();         
 }

 public class Dog extends Animal {
     public Dog() {
         super(); //implicitly called
     }

     @Override
     public void makeSound() {
         System.out.println("BARK");
     }
 }

 public class Main {
     public static void main(String[] args) {
         Dog dog = new Dog();
     }
 }

您将得到“BARK”而不是编译错误,例如“无法在此实例上调用抽象方法”或运行时错误 AbstractMethodError 甚至 pure virtual method call 或类似的东西。所以这一切都是为了支持polymorphism

【讨论】:

    【解决方案3】:

    B 实例的recursive 方法调用superclass 实现时,被操作的实例仍然是B。因此,当超类的实现调用recursive 而没有进一步限定时,那就是子类实现。结果就是您看到的永无止境的循环。

    【讨论】:

      【解决方案4】:

      这是意料之中的。这就是 B 的实例所发生的情况。

      class A {
      
          void recursive(int i) { // <-- 3. this gets called
              System.out.println("A.recursive(" + i + ")");
              if (i > 0) {
                  recursive(i - 1); // <-- 4. this calls the overriden "recursive" method in class B, going back to 1.
              }
          }
      
      }
      
      class B extends A {
      
          void recursive(int i) { // <-- 1. this gets called
              System.out.println("B.recursive(" + i + ")");
              super.recursive(i + 1); // <-- 2. this calls the "recursive" method of the parent class
          }
      
      }
      

      因此,电话在AB 之间交替。

      A 的实例不会发生这种情况,因为不会调用被覆盖的方法。

      【讨论】:

        【解决方案5】:

        因为A 中的recursive(i - 1); 指的是this.recursive(i - 1);,在第二种情况下是B#recursive。因此,superthis 将在递归函数中调用交替

        void recursive(int i) {
            System.out.println("B.recursive(" + i + ")");
            super.recursive(i + 1);//Method of A will be called
        }
        

        A

        void recursive(int i) {
            System.out.println("A.recursive(" + i + ")");
            if (i > 0) {
                this.recursive(i - 1);// call B#recursive
            }
        }
        

        【讨论】:

          【解决方案6】:

          B中的super.recursive(i + 1);显式调用超类的方法,所以A中的recursive被调用一次。

          然后,A 类中的recursive(i - 1); 将调用B 类中的recursive 方法,该方法覆盖A 类的recursive,因为它是在B 类的实例上执行的。

          那么Brecursive会显式调用Arecursive,以此类推。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2022-11-27
            • 1970-01-01
            • 2016-03-18
            • 2015-12-11
            • 2017-12-23
            • 1970-01-01
            • 2011-02-02
            相关资源
            最近更新 更多