सुपर क्लास से उपवर्ग तक स्पष्ट कास्टिंग


161
public class Animal {
    public void eat() {}
}

public class Dog extends Animal {
    public void eat() {}

    public void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = (Dog) animal;
    }
}

असाइनमेंट Dog dog = (Dog) animal;एक संकलन त्रुटि उत्पन्न नहीं करता है, लेकिन रनटाइम पर यह एक उत्पन्न करता है ClassCastException। कंपाइलर इस त्रुटि का पता क्यों नहीं लगा सकता है?


51
आप संकलक को त्रुटि का पता नहीं लगाने के लिए कह रहे हैं।
मौरिसियो

जवाबों:


326

एक कलाकार का उपयोग करके आप अनिवार्य रूप से संकलक को बता रहे हैं "मुझ पर विश्वास करो। मैं एक पेशेवर हूं, मुझे पता है कि मैं क्या कर रहा हूं और मुझे पता है कि यद्यपि आप इसकी गारंटी नहीं दे सकते हैं, मैं आपको बता रहा हूं कि यह animalचर निश्चित रूप से है कुत्ता बनने जा रहा है। "

चूंकि जानवर वास्तव में एक कुत्ता नहीं है (यह एक जानवर है, आप ऐसा कर सकते हैं Animal animal = new Dog();और यह एक कुत्ता होगा) VM रनटाइम पर एक अपवाद फेंकता है क्योंकि आपने उस विश्वास का उल्लंघन किया है (आपने कंपाइलर को बताया कि सब कुछ ठीक होगा और यह ठीक है नहीं!)

कंपाइलर केवल आँख बंद करके सबकुछ स्वीकार करने की तुलना में थोड़ा होशियार है, यदि आप विभिन्न विरासत पदानुक्रमों में वस्तुओं का प्रयास करते हैं और उदाहरण के लिए एक स्ट्रिंग को डॉग डालते हैं) तो कंपाइलर इसे आपको वापस फेंक देगा क्योंकि यह जानता है कि यह संभवतः कभी काम नहीं कर सकता है।

क्योंकि आप अनिवार्य रूप से कंपाइलर को शिकायत करने से रोक रहे हैं, हर बार जब आप डालते हैं, तो यह जाँचना महत्वपूर्ण है कि यदि आप एक स्टेटमेंट (या उस प्रभाव के लिए कुछ) ClassCastExceptionका उपयोग करके नहीं करेंगे instanceof


धन्यवाद, लेकिन आपको कुत्ते से पशु की आवश्यकता है यदि नहीं, तो काम न करें :)
delive

66
मुझे प्यार है कि आपने इसे कितना नाटकीय बना दिया है
हेंड एंग्रियन

3
@ निश्चित रूप से आप करते हैं, लेकिन सवाल के अनुसार, से विस्तार Dog करता है Animal!
माइकल बेरी

52

क्योंकि सैद्धांतिक रूप से एक कुत्ता Animal animal हो सकता है:

Animal animal = new Dog();

आमतौर पर, डाउनकास्टिंग एक अच्छा विचार नहीं है। आपको इससे बचना चाहिए। यदि आप इसका उपयोग करते हैं, तो आप बेहतर तरीके से एक चेक शामिल करते हैं:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}

लेकिन निम्नलिखित कोड संकलन त्रुटि उत्पन्न करता है कुत्ता कुत्ता = नया पशु (); (असंगत प्रकार)। लेकिन इस स्थिति में कंपाइलर पहचान लेता है कि जानवर एक सुपर क्लास है और डॉग एक उपवर्ग है। इसके अलावा असाइनमेंट गलत है। लेकिन जब हम डॉग डॉग = (डॉग) जानवर डालते हैं; यह स्वीकार करता है। कृपया मुझे इस बारे में समझाएं
सरवनन

3
हां, क्योंकि एनिमल सुपरक्लास है। हर जानवर एक कुत्ता नहीं है, है ना? आप केवल उनके प्रकारों या उनके सुपरपाइपों द्वारा कक्षाओं का उल्लेख कर सकते हैं। उनके उपप्रकार नहीं।
Bozho

43

इस प्रकार के ClassCastException से बचने के लिए, यदि आपके पास:

class A
class B extends A

आप बी में एक कंस्ट्रक्टर को परिभाषित कर सकते हैं जो ए की एक वस्तु लेता है। इस तरह हम "कास्ट" कर सकते हैं जैसे:

public B(A a) {
    super(a.arg1, a.arg2); //arg1 and arg2 must be, at least, protected in class A
    // If B class has more attributes, then you would initilize them here
}

24

माइकल बेरी द्वारा दिए गए उत्तर को विस्तृत करना।

Dog d = (Dog)Animal; //Compiles but fails at runtime

यहाँ आप संकलक से कह रहे हैं "मुझ पर विश्वास करो। मुझे पता dहै कि वास्तव में एक Dogवस्तु का जिक्र है " हालांकि यह नहीं है। याद रखें कि कंपाइलर को हम पर भरोसा करने के लिए मजबूर किया जाता है जब हम एक डाउनकास्ट करते हैं

संकलक केवल घोषित संदर्भ प्रकार के बारे में जानता है। रनवे पर JVM जानता है कि वास्तव में वस्तु क्या है।

इसलिए जब रनवे पर JVM यह बताता Dog dहै कि वास्तव में यह Animalएक Dogवस्तु का उल्लेख कर रहा है न कि किसी वस्तु का। अरे ... आपने कंपाइलर से झूठ बोला और बड़ा मोटा फेंका ClassCastException

तो अगर आप डाउनकास्टिंग कर रहे हैं तो आपको instanceofखराब होने से बचने के लिए परीक्षण का उपयोग करना चाहिए ।

if (animal instanceof Dog) { Dog dog = (Dog) animal; }

अब एक सवाल हमारे दिमाग में आता है। क्यों नरक संकलक डाउनकास्ट की अनुमति दे रहा है जब अंततः यह एक फेंकने जा रहा है java.lang.ClassCastException?

उत्तर यह है कि सभी कंपाइलर यह सत्यापित कर सकते हैं कि दो प्रकार एक ही वंशानुक्रम में हैं, इसलिए डाउनकास्ट से पहले जो भी कोड आ सकता है, उसके आधार पर यह संभव है कि animalयह प्रकार का हो dog

संकलक को उन चीजों की अनुमति देनी चाहिए जो रनटाइम के दौरान संभव हो सकती हैं।

निम्नलिखित कोड स्निपेट पर विचार करें:

public static void main(String[] args) 
{   
    Dog d = getMeAnAnimal();// ERROR: Type mismatch: cannot convert Animal to Dog
    Dog d = (Dog)getMeAnAnimal(); // Downcast works fine. No ClassCastException :)
    d.eat();

}

private static Animal getMeAnAnimal()
{
    Animal animal = new Dog();
    return animal;
}

हालांकि, यदि कंपाइलर को यकीन है कि कास्ट संभव काम नहीं करेगा, तो संकलन विफल हो जाएगा। IE यदि आप विभिन्न वंशानुगत पदानुक्रमों में वस्तुओं को डालने का प्रयास करते हैं

String s = (String)d; // ERROR : cannot cast for Dog to String

डाउनकास्टिंग के विपरीत, अपकास्टिंग कार्य अंतर्निहित रूप से होता है क्योंकि जब आप upcast करते हैं तो आप अंतर्निहित विधि की संख्या को सीमित कर रहे हैं जिसे आप डाउनकास्टिंग के विपरीत कह सकते हैं, जिसका अर्थ है कि बाद में, आप एक और अधिक विशिष्ट विधि को लागू करना चाहते हैं।

Dog d = new Dog(); Animal animal1 = d; // Works fine with no explicit cast Animal animal2 = (Animal) d; // Works fine with n explicit cast

उपरोक्त अपकास्ट दोनों बिना किसी अपवाद के ठीक काम करेंगे क्योंकि एक डॉग आईएस-ए एनिमल, एनीथिंग एनी एनिमल कर सकता है, कुत्ता कर सकता है। लेकिन यह सच नहीं है vica-versa।


1

कोड एक संकलन त्रुटि उत्पन्न करता है क्योंकि आपका उदाहरण प्रकार एक जानवर है:

Animal animal=new Animal();

जावा में कई कारणों से डाउनकास्टिंग की अनुमति नहीं है। देखें यहाँ जानकारी के लिए।


5
कोई संकलन त्रुटि नहीं है, यही उनके सवाल का कारण है
क्लेरेंस लियू

1

जैसा कि समझाया गया है, यह संभव नहीं है। यदि आप उपवर्ग की एक विधि का उपयोग करना चाहते हैं, तो सुपरक्लास में विधि जोड़ने की संभावना का मूल्यांकन करें (हो सकता है कि वह खाली हो) और उपवर्गों से कॉल करें जिसे आप चाहते हैं (उपवर्ग) बहुरूपता के लिए धन्यवाद। इसलिए जब आप d.method () कॉल करते हैं, तो कॉल कास्टिंग में सफल हो जाएगी, लेकिन अगर वस्तु एक कुत्ता नहीं होगी, तो कोई समस्या नहीं होगी


0

@Caumons के उत्तर को विकसित करने के लिए:

कल्पना कीजिए कि एक पिता वर्ग में कई बच्चे हैं और उस वर्ग में एक सामान्य क्षेत्र को जोड़ने की आवश्यकता है। यदि आप उल्लिखित दृष्टिकोण पर विचार करते हैं, तो आपको प्रत्येक बच्चे को एक-एक करके कक्षा में जाना चाहिए और नए क्षेत्र के लिए अपने कंस्ट्रक्टर्स को रिफैक्ट करना चाहिए। इसलिए यह समाधान इस परिदृश्य में एक आशाजनक समाधान नहीं है

अब इस समाधान पर एक नज़र डालें।

एक पिता प्रत्येक बच्चों से एक आत्म वस्तु प्राप्त कर सकता है। यहाँ एक पिता वर्ग है:

public class Father {

    protected String fatherField;

    public Father(Father a){
        fatherField = a.fatherField;
    }

    //Second constructor
    public Father(String fatherField){
        this.fatherField = fatherField;
    }

    //.... Other constructors + Getters and Setters for the Fields
}

यहां हमारा बच्चा वर्ग है जो अपने पिता के निर्माण में से एक को लागू करना चाहिए, इस मामले में उपरोक्त निर्माणकर्ता:

public class Child extends Father {

    protected String childField;

    public Child(Father father, String childField ) {
        super(father);
        this.childField = childField;
    }

    //.... Other constructors + Getters and Setters for the Fields

    @Override
    public String toString() {
        return String.format("Father Field is: %s\nChild Field is: %s", fatherField, childField);
    }
}

अब हम आवेदन का परीक्षण करते हैं:

public class Test {
    public static void main(String[] args) {
        Father fatherObj = new Father("Father String");
        Child child = new Child(fatherObj, "Child String");
        System.out.println(child);
    }
}

और यहाँ परिणाम है:

फादर फील्ड है: फादर स्ट्रिंग

चाइल्ड फील्ड है: बाल स्ट्रिंग

अब आप आसानी से अपने बच्चों के कोड को तोड़ने के लिए चिंतित होने के बिना पिता वर्ग में नए क्षेत्रों को जोड़ सकते हैं;

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