वंशानुक्रम और पुनरावृत्ति


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.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
आपके लिए आवश्यक कीवर्ड स्थिर और गतिशील प्रकार है! आपको इसके लिए खोज करनी चाहिए और इसके बारे में थोड़ा पढ़ना चाहिए।
पार्करहेलो


1
अपने इच्छित परिणाम प्राप्त करने के लिए एक नई निजी विधि के लिए पुनरावर्ती विधि निकालें।
ओनोट्स

1
@ मुझे लगता है कि पुनरावर्ती तरीकों को स्थिर बनाने से क्लीनर होगा।
रिक्शे

1
यह सरल है यदि आप ध्यान दें कि पुनरावर्ती कॉल Aवास्तव में गतिशील रूपrecursive से वर्तमान वस्तु की विधि के लिए भेजा जाता है । यदि आप किसी Aऑब्जेक्ट के साथ काम कर रहे हैं , तो कॉल आपको A.recursive(), और Bऑब्जेक्ट के साथ , करने के लिए ले जाता है B.recursive()। लेकिन B.recursive()हमेशा बुलाता है A.recursive()। इसलिए, यदि आप एक Bवस्तु शुरू करते हैं, तो यह आगे और पीछे स्विच करता है।
लिपप्रो

जवाबों:


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);में Aसंदर्भित करता है this.recursive(i - 1);जो B#recursiveदूसरे मामले में। तो, superऔर वैकल्पिक रूप से पुनरावर्ती कार्य thisमें बुलाया जाएगा ।

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

अन्य उत्तरों ने सभी आवश्यक बिंदुओं को समझाया है, कि एक बार एक इंस्टेंस विधि के ओवरराइड होने के बाद यह ओवरराइड हो जाता है और इसके अलावा कुछ भी नहीं मिल रहा है superB.recursive()आह्वान करता है A.recursive()A.recursive()फिर आह्वान करता है recursive(), जो अंदर में ओवरराइड करने का संकल्प करता है B। और हम ब्रह्मांड या ए के अंत तक आगे और पीछे पिंग करते हैंStackOverflowError , जो भी पहले आता है।

यह अच्छा होगा अगर एक लिख सकता है this.recursive(i-1)में Aअपने स्वयं के कार्यान्वयन प्राप्त करने के लिए, लेकिन यह शायद चीजों को तोड़ने के लिए और अन्य दुर्भाग्यपूर्ण परिणाम होंगे, इसलिए this.recursive(i-1)में Aआह्वान B.recursive()और इसके आगे।

अपेक्षित व्यवहार प्राप्त करने का एक तरीका है, लेकिन इसके लिए दूरदर्शिता की आवश्यकता है। दूसरे शब्दों में, आपको पहले से पता होना चाहिए कि आप फंसने super.recursive()के Aलिए एक उपप्रकार चाहते हैं, इसलिए 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इसलिए आश्वासन दिया जाता है कि यह अपना तर्क कह रहा है।


मुझे आश्चर्य है, क्यों वस्तु से doRecursive()अंदर बुला काम करता है। जैसा कि TAsk ने अपने उत्तर में लिखा है कि एक फंक्शन कॉल जैसे काम करता है और ऑब्जेक्ट ( ) का कोई तरीका नहीं है क्योंकि यह क्लास में परिभाषित किया गया है और नहीं और इसलिए विरासत में नहीं मिलेगा, है ना? recursive()Bthis.doRecursive()BthisdoRecursive()Aprivateprotected
टिमो डेन्क

1
ऑब्जेक्ट Bबिल्कुल भी कॉल नहीं कर सकता doRecursive()doRecursive()है private, हाँ। लेकिन जब Bकॉल करता है super.recursive(), तो उस कार्यान्वयन को लागू करता recursive()है A, जिसकी पहुंच है doRecursive()
एरिक जी। हेगस्ट्रॉम

2
यह बिल्कुल वही तरीका है जो बलोच प्रभावी जावा में अनुशंसा करता है यदि आपको पूरी तरह से विरासत की अनुमति देनी चाहिए। आइटम 17: "अगर आपको लगता है कि आपको [एक ठोस वर्ग जो एक मानक इंटरफ़ेस को लागू नहीं करता है] से वंशानुक्रम की अनुमति देनी चाहिए, तो एक उचित दृष्टिकोण यह सुनिश्चित करना है कि वर्ग कभी भी अपने किसी भी अधिकनीय तरीके को लागू न करे और इस तथ्य को दस्तावेज़ में न लाए।"
जो

16

super.recursive(i + 1); क्लास में B सुपर वर्ग की विधि स्पष्ट रूप से कहता है, इसलिए recursiveकी Aएक बार कहा जाता है।

फिर, recursive(i - 1);क्लास ए recursiveमें क्लास में उस विधि को कहेंगे Bजो ओवरराइड करती हैrecursive वर्ग की Aहै, क्योंकि यह वर्ग का एक उदाहरण पर निष्पादित किया जाता है B

तब Bके recursiveकहेंगे A's recursiveइतने पर स्पष्ट रूप से, और।


16

वह वास्तव में किसी अन्य तरीके से नहीं जा सकता।

जब आप फोन B.recursive(10);है, तो यह प्रिंट करता है, B.recursive(10)तो में इस विधि के कार्यान्वयन कॉल Aके साथ 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को और अधिक योग्यता के बिना कॉल किया जाता है, तो यह उपवर्ग कार्यान्वयन है । इसका परिणाम यह है कि आप कभी न खत्म होने वाले लूप हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.