Kalıtım ve özyineleme


86

Aşağıdaki sınıflara sahip olduğumuzu varsayalım:

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

}

Şimdi recursiveA sınıfını arayalım :

public class Demo {

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

}

Çıktı, beklendiği gibi 10'dan geri sayılıyor.

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)

Kafa karıştırıcı kısma geçelim. Şimdi recursiveB sınıfını arıyoruz .

Beklenen :

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)

Gerçek :

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

Bu nasıl olur? Bunun planlanmış bir örnek olduğunu biliyorum ama merak etmeme neden oluyor.

Somut bir kullanım örneği olan eski soru .


1
İhtiyacınız olan anahtar kelime statik ve dinamik türlerdir! bunu araştırmalı ve hakkında biraz okumalısın.
ParkerHalo


1
İstediğiniz sonuçları elde etmek için özyinelemeli yöntemi yeni bir özel yönteme çıkarın.
Onots

1
@Onots, yinelemeli yöntemleri statik hale getirmenin daha temiz olacağını düşünüyorum.
ricksmt

1
İçinde özyinelemeli çağrı fark ederseniz Çok basit Aaslında edilir dinamik gönderilen için recursivegeçerli nesnenin yöntemine. Bir Anesneyle çalışıyorsanız , çağrı sizi konumuna A.recursive()ve bir Bnesneyle adresine götürür B.recursive(). Ama B.recursive()her zaman arar A.recursive(). Yani, bir Bnesneye başlarsanız , ileri geri değişir.
LIProf

Yanıtlar:


76

Bu bekleniyor. Bir örneği için olan budur 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
    }

}

Bu nedenle, aramalar Ave arasında değişiyor B.

Bu A, geçersiz kılma yönteminin çağrılmayacağı bir durumda olmaz.


29

Çünkü recursive(i - 1);olarak Aatıfta bulunduğu this.recursive(i - 1);olan B#recursiveikinci durumda. Yani, superve thisde adı verilecek özyinelemeli fonksiyonu alternatif .

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

içinde A

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

27

Diğer yanıtların tümü, bir örnek yöntemi geçersiz kılındığında, geçersiz kılınmış olarak kaldığı ve onu geçme dışında geri alamayacağı temel noktayı açıkladı super. B.recursive()çağırır A.recursive(). A.recursive()sonra recursive()geçersiz kılmayı çözen çağırır B. Ve StackOverflowErrorhangisi önce gelirse , evrenin sonuna veya a'ya kadar ileri geri ping yapıyoruz .

Biri yazabilirsem güzel olur this.recursive(i-1)yılında Akendi uygulamasını almak için, ama muhtemelen bu yüzden, şeyleri kırmak ve diğer talihsiz sonuçlara yol açacağı this.recursive(i-1)içinde Açağırır B.recursive()ve benzeri.

Beklenen davranışı elde etmenin bir yolu vardır, ancak öngörü gerektirir. Başka bir deyişle, uygulamada super.recursive()bir alt türünün Atuzağa düşürülmesini, tabiri caizse, istediğinizi önceden bilmelisiniz A. Böyle yapılır:

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()Çağırdığından beridoRecursive() ve doRecursive()geçersiz asla, Akendi mantığı arayarak her türlü önlem alınmıştır.


Merak ediyorum, nesneden doRecursive()içeriyi aramak neden işe yarıyor. TAsk'ın cevabında yazdığı gibi, bir işlev çağrısı aynı şekilde çalışır ve Object ( ) 'nin bir yöntemi yoktur, çünkü sınıfta tanımlanmıştır ve tanımlanmamıştır ve bu nedenle miras alınmayacaktır, değil mi? recursive()Bthis.doRecursive()BthisdoRecursive()Aprivateprotected
Timo Denk

1
Nesne Bhiç arayamaz doRecursive(). doRecursive()olduğunu privateevet. Ancak Bçağrıldığında super.recursive(), bu , erişime sahip olan recursive()in uygulamasını çağırır . AdoRecursive()
Erick G.Hagstrom

2
Devralmaya kesinlikle izin vermeniz gerekiyorsa Bloch'un Etkili Java'da önerdiği yaklaşım tam olarak budur. Madde 17: "[Standart bir arayüz uygulamayan somut bir sınıftan] kalıtıma izin vermeniz gerektiğini düşünüyorsanız, makul bir yaklaşım, sınıfın geçersiz kılınabilen yöntemlerinden hiçbirini çağırmamasını sağlamak ve gerçeği belgelemektir."
Joe

16

super.recursive(i + 1);sınıfta Bsüper sınıfın yöntemini açıkça çağırır, bu yüzden recursiveof Abir kez çağrılır.

Daha sonra, recursive(i - 1);A sınıfında , bir sınıf örneğinde yürütüldüğünden, sınıfın geçersiz kılınan recursivesınıftaki yöntemi çağırır .BrecursiveAB

Sonra B'ın recursiveçağırır As' recursivevb açıkça ve.


16

Bu aslında başka türlü olamaz.

Aradığınızda B.recursive(10);, o zaman yazdırır B.recursive(10)sonra bu yöntemin uygulanmasını çağırır Aile i+1.

Yani arıyorsun A.recursive(11), hangi baskıA.recursive(11) çağırır recursive(i-1);olan cari örneğinde yöntemi Bgirdi parametresi ile i-1bunu çağırır böylece, B.recursive(10)daha sonra süper uygulama aramaları, i+1hangi 11sonra yinelemeli geçerli örnek özyinelemeli aramaları ile i-1hangi10 , ve yeni sekme burada gördüğünüz döngüyü alın.

Bunun nedeni, üst sınıftaki örneğin yöntemini çağırırsanız, onu çağırdığınız örneğin uygulamasını yine de çağıracağınız içindir.

Bunu hayal edin,

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

"Soyut yöntem bu örnekte çağrılamaz" gibi bir derleme hatası veya bir çalışma zamanı hatası AbstractMethodErrorveya hatta bunun gibi bir derleme hatası yerine "BARK" mesajı alırsınız pure virtual method call. Yani bunların hepsi polimorfizmi desteklemek için .


14

Bir Börneğin recursiveyöntemi supersınıf uygulamasını çağırdığında , üzerinde çalıştırılan örnek hala geçerli olur B. Bu nedenle, süper sınıfın uygulaması recursivedaha fazla nitelik gerektirmeden çağırdığında , bu alt sınıf uygulamasıdır . Sonuç, gördüğünüz hiç bitmeyen döngüdür.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.