상속 및 재귀


86

다음 클래스가 있다고 가정합니다.

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

}

이제 recursive클래스 A를 호출합니다 .

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)

혼란스러운 부분을 살펴 보겠습니다. 이제 우리 recursive는 클래스 B를 호출합니다 .

예상 :

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

어떻게 이런 일이 발생합니까? 이것이 고안된 예라는 것을 알고 있지만 궁금해합니다.

구체적인 사용 사례 가있는 이전 질문입니다 .


1
필요한 키워드는 정적 및 동적 유형입니다! 당신은 그것을 검색하고 그것에 대해 조금 읽어야합니다.
ParkerHalo 2015


1
원하는 결과를 얻으려면 재귀 방법을 새 개인 방법으로 추출하십시오.
Onots 2015

1
@Onots 재귀 메서드를 정적으로 만드는 것이 더 깨끗할 것이라고 생각합니다.
ricksmt 2015

1
재귀 호출 A이 실제로 현재 개체 의 메서드에 동적으로 전달 된다는 것을 알면 간단 recursive합니다. A객체 로 작업하는 경우 호출은 A.recursive()로 이동하고 B객체를 사용하면로 이동 B.recursive()합니다. 그러나 B.recursive()항상 A.recursive(). 따라서 B개체 를 시작하면 앞뒤로 전환됩니다.
LIProf 2015

답변:


76

이것은 예상됩니다. 이것은의 인스턴스에 대해 발생합니다 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
    }

}

따라서 호출은 A과 사이에서 번갈아 가며 나타납니다 B.

A재정의 된 메서드가 호출되지 않기 때문에 인스턴스의 경우에는 발생하지 않습니다 .


29

때문에 recursive(i - 1);In이 A참조하는 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
    }
}

27

다른 답변은 모두 인스턴스 메서드가 재정의되면 재정의 된 상태로 유지되고을 제외하고 다시 가져올 수 없다는 본질적인 요점을 모두 설명했습니다 super. B.recursive()를 호출합니다 A.recursive(). A.recursive()그런 다음를 호출 recursive()하여 B. 그리고 우리는 우주가 끝날 때까지 앞뒤로 탁구를합니다 StackOverflowError.

하나 쓸 수 있다면 좋은 일 것입니다 this.recursive(i-1)있는 A자신의 구현을 얻을 수 있지만, 아마 그래서 일을 중단하고 다른 불행한 결과를 초래할 것 this.recursive(i-1)A원용하는 B.recursive()등.

예상되는 동작을 얻을 수있는 방법이 있지만 선견지명이 필요합니다. 즉, super.recursive()의 하위 유형에있는 a AA구현 에서 트랩 되기를 원한다는 것을 미리 알아야합니다 . 다음과 같이 수행됩니다.

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자체 논리를 호출하고 있음을 확신합니다.


객체에서 doRecursive()내부 호출이 작동 recursive()하는 이유가 궁금 B합니다. TAsk가 그의 답변에서 썼 듯이 함수 호출은 다음과 같이 작동 this.doRecursive()하고 Object B( this)는 doRecursive()클래스에 A정의되어 private있고 protected상속 되지 않기 때문에 메서드 가 없으므로 상속되지 않습니다.
Timo Denk

1
개체 B는 전혀 호출 할 수 없습니다 doRecursive(). doRecursive()이다 private예. 그러나 B호출 할 때에 대한 액세스 권한이있는 in super.recursive()의 구현을 호출합니다 . recursive()AdoRecursive()
Erick G. Hagstrom

2
이것은 상속을 절대적으로 허용해야하는 경우 Bloch가 효과적인 Java에서 권장하는 접근 방식입니다. 항목 17 : "[표준 인터페이스를 구현하지 않는 구체적인 클래스]로부터 상속을 허용해야한다고 생각한다면, 한 가지 합리적인 접근 방식은 클래스가 재정의 가능한 메서드를 호출하지 않도록하고 사실을 문서화하는 것입니다."
Joe

16

super.recursive(i + 1);in class B는 명시 적으로 슈퍼 클래스의 메서드를 호출하므로 recursiveof A는 한 번 호출됩니다.

그런 다음 recursive(i - 1);클래스 A recursive에서 클래스 의 인스턴스에서 실행되기 때문에 클래스 B를 재정의 recursive하는 클래스 의 메서드를 호출합니다 .AB

그런 다음 B's recursiveA's를 recursive명시 적으로 호출 합니다.


16

그것은 실제로 다른 방법으로 갈 수 없습니다.

를 호출 B.recursive(10);하면 인쇄 B.recursive(10)한 다음 Awith 에서이 메서드의 구현을 호출합니다 i+1.

당신이 전화 그래서 A.recursive(11), 어떤 인쇄 A.recursive(11)부르는 recursive(i-1);것입니다 현재 인스턴스에 대한 방법 B입력 매개 변수를 i-1가 호출 그래서 B.recursive(10)다음과 슈퍼 구현 호출 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이와 유사한 오류가 발생 합니다. 그래서 이것은 다형성 을 지원하기위한 것 입니다.


14

B인스턴스의 recursive메서드가 super클래스 구현을 호출 할 때 작동되는 인스턴스 는 여전히입니다 B. 따라서 슈퍼 클래스의 구현이 recursive추가 자격없이 호출 될 때 이것이 하위 클래스 구현 입니다. 결과는 당신이보고있는 끝없는 루프입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.