web-dev-qa-db-ja.com

継承と再帰

次のクラスがあるとします:

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...

これはどのように起こりますか?これは考案された例であることは知っていますが、不思議に思います。

具体的なユースケース のある古い質問。

84
raupach

これは予想されることです。これは、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のインスタンスの場合、オーバーライドされたメソッドは呼び出されないため、これは発生しません。

75
Tunaki

Arecursive(i - 1);は、2番目のケースでは_B#recursive_であるthis.recursive(i - 1);を参照しているためです。したがって、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
    }
}
_
29
CoderCroc

他の答えはすべて、インスタンスメソッドがオーバーライドされるとオーバーライドされたままになり、superを介してのみ戻されるという本質的なポイントを説明しました。 B.recursive()A.recursive()を呼び出します。 A.recursive()は次にrecursive()を呼び出します。これはBのオーバーライドに解決されます。そして、宇宙の終わりまで、またはStackOverflowErrorのいずれか早い方まで、ピンポンを行ったり来たりします。

this.recursive(i-1)Aに記述して独自の実装を取得できればいいのですが、それはおそらく物事を壊し、他の不幸な結果をもたらすので、Athis.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は独自のロジックを呼び出していることが保証されます。

27

クラスBsuper.recursive(i + 1);は、スーパークラスのメソッドを明示的に呼び出すため、recursiveAが1回呼び出されます。

次に、クラスAのrecursive(i - 1);は、クラスrecursiveBをオーバーライドするクラスrecursiveAメソッドを呼び出します。クラスBのインスタンスで実行されます。

次に、Brecursiveは、Arecursiveを明示的に呼び出します。

16
Eran

Bインスタンスのrecursiveメソッドがsuperclass実装を呼び出すとき、処理されるインスタンスはBのままです。したがって、スーパークラスの実装がさらに修飾なしでrecursiveを呼び出すと、サブクラスの実装になります。結果は、あなたが見ている終わりのないループです。

14
jonrsharpe