2013-03-13 6 views
17

क्या कोई मुझे इस बारे में बता सकता है कि मुझे इस स्निपेट में ClassCastException क्यों नहीं मिला है? मुझे सख्ती से दिलचस्पी है कि यह क्यों काम नहीं कर रहा है क्योंकि मैं उम्मीद कर रहा था। मुझे इस बात पर परवाह नहीं है कि यह खराब डिजाइन है या नहीं।जावा में आलसी वर्ग कास्ट?

public class Test { 
    static class Parent { 
    @Override 
    public String toString() { return "parent"; } 
    } 

    static class ChildA extends Parent { 
    @Override 
    public String toString() { return "child A"; } 
    } 

    static class ChildB extends Parent { 
    @Override 
    public String toString() { return "child B"; } 
    } 

    public <C extends Parent> C get() { 
    return (C) new ChildA(); 
    } 

    public static void main(String[] args) { 
    Test test = new Test(); 

    // should throw ClassCastException... 
    System.out.println(test.<ChildB>get()); 

    // throws ClassCastException... 
    System.out.println(test.<ChildB>get().toString()); 
    } 
} 

यह जावा संस्करण, संकलन है, और उत्पादन चलाएँ:

$ java -version 
java version "1.7.0_17" 
Java(TM) SE Runtime Environment (build 1.7.0_17-b02) 
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode) 
$ javac -Xlint:unchecked Test.java 
Test.java:24: warning: [unchecked] unchecked cast 
    return (C) new ChildA(); 
      ^
    required: C 
    found: ChildA 
    where C is a type-variable: 
    C extends Parent declared in method <C>get() 
1 warning 
$ java Test 
child A 
Exception in thread "main" java.lang.ClassCastException: Test$ChildA cannot be cast to Test$ChildB 
    at Test.main(Test.java:30) 
+0

के कलाकारों की जरूरत नहीं है, लेकिन मैं यह पहले सवाल की याद दिला रहा हूँ/उत्तर, शायद यह मदद करेगा http://stackoverflow.com/q/6538058/630384 – DHall

+1

एक कारण है कि अनचेक कास्ट चेतावनी क्यों है। इसका शाब्दिक अर्थ यह है कि, कास्ट रनटाइम चेक नहीं कर सकता/नहीं कर सकता है। – millimoose

उत्तर

8

Type erasure: जेनेरिक केवल एक वाक्य रचनात्मक सुविधा है जिसे संकलक (संगतता कारणों के लिए) द्वारा हटाया जाता है और पर प्रतिस्थापित किया जाता है जहां आवश्यक होता है।

रनटाइम में, विधि C getC के प्रकार को नहीं जानता (यही कारण है कि आप new C() को तत्काल नहीं कर सकते हैं)। test.<ChildB>get() का एक आविष्कार वास्तव में test.get का आविष्कार है। return (C) new ChildA() को return (Object) new ChildA() में परिवर्तित किया गया है क्योंकि असंबद्ध प्रकार C का क्षरण Parent (इसकी बाईं ओर बाध्य) है। फिर, कोई कलाकार आवश्यक नहीं है क्योंकि println तर्क के रूप में Object की अपेक्षा करता है।

दूसरी तरफ test.<ChildB>get().toString() विफल रहता है, क्योंकि test.<ChildB>get() को toString() का आह्वान करने से पहले ChildB पर डाला जाता है।

ध्यान दें कि myPrint(test.<ChildB>get()) जैसे आमंत्रण भी असफल हो जाएंगे। से get को टाइप करने के लिए myPrint को लागू करने के लिए Parent से कास्ट किया गया है।उत्पन्न बाईटकोड पर

public static void myPrint(ChildB child) { 
    System.out.println(child); 
} 
+0

मैं इस उत्तर को सही के रूप में चिह्नित कर रहा हूं क्योंकि, जबकि मुझे मिटाने के बारे में पता था, इस जवाब ने डॉट्स को जोड़ा और मुझे यह समझने में मदद की कि संकलित कोड (जैसे 'मायप्रिंट') में वास्तव में पेश किए जा रहे हैं। धन्यवाद! –

+0

मुझे अभी भी यह दिलचस्प लगता है कि पहला कॉल एक कास्ट चेक पेश नहीं करता है, जबकि दूसरा करता है, हालांकि यह 'ऑब्जेक्ट' पर उपलब्ध 'toString' को कॉल कर रहा है। नहीं 'चाइल्डबी' विशिष्ट विधि। –

+0

यदि जेनिक्स संकलन पर हटा दिए जाते हैं, तो मैं सामान्य प्रकार से संकलित करने के लिए आपको '.jar' क्यों दे सकता हूं? उनमें से कुछ निशान होना चाहिए, भले ही वे रनटाइम के दौरान उपयोग नहीं किए जाते हैं। –

11

इस विलोपन टाइप करने के लिए कारण है। संकलन समय, जब

public <C extends Parent> C get() { 
    return (C) new ChildA(); 
} 

संकलन बस की जाँच करता है कि ChildAParent की एक उप-प्रकार है और इस तरह डाली निश्चित रूप से असफल नहीं होंगे। यह पता है कि आप कमजोर जमीन पर हैं, ChildAC टाइप करने के लिए असाइन करने योग्य नहीं हो सकता है, इसलिए यह एक अनचेक-कास्ट चेतावनी जारी करता है जो आपको बताता है कि कुछ गलत हो सकता है। (यह कोड को संकलित करने की बजाय इसे संकलित करने की अनुमति क्यों देता है? जावा प्रोग्रामर को अपने पुराने प्री-जेनेरिक कोड को न्यूनतम पुनर्लेखन के साथ माइग्रेट करने की आवश्यकता से प्रेरित किया जाता है।)

अब get() क्यों असफल नहीं होता: C प्रकार पैरामीटर के लिए कोई रनटाइम घटक नहीं है; संकलन के बाद टाइप तर्क आसानी से प्रोग्राम से मिटा दिया जाता है और इसके ऊपरी बाउंड (Parent) के साथ प्रतिस्थापित किया जाता है। तो कॉल ChildA के साथ असंगत होने पर भी सफल होगा, लेकिन पहली बार जब आप वास्तव में get() के परिणाम का उपयोग ChildB के रूप में एक कास्ट (Parent से ChildB तक) करेंगे और आपको अपवाद मिलेगा।

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

6

देखो:

12 invokevirtual Test.get() : Test$Parent [30] 
15 invokevirtual java.io.PrintStream.println(java.lang.Object) : void [32] 
18 getstatic java.lang.System.out : java.io.PrintStream [24] 
21 aload_1 [test] 
22 invokevirtual Test.get() : Test$Parent [30] 
25 checkcast Test$ChildB [38] 
28 invokevirtual Test$ChildB.toString() : java.lang.String [40] 
31 invokevirtual java.io.PrintStream.println(java.lang.String) : void [44] 

println करने के लिए पहली कॉल सिर्फ कॉल की Object संस्करण का उपयोग करता है ताकि कोई डाली आवश्यक है।

+0

धन्यवाद! यह अभी भी दिलचस्प है कि संकलक दूसरे मामले में आवश्यक कलाकारों को मानता है। –

4

यदि संकलित समय प्रकार की जांच को अनचेक कास्ट द्वारा अवरुद्ध किया जाता है, तो यह स्पष्ट नहीं है कि जेएलएस पढ़ने से, रनटाइम प्रकार की जांच कब होनी चाहिए। मुझे लगता है कि संकलक को यह मानने की अनुमति है कि प्रकार ध्वनि हैं, और यह जितनी देर हो सके रनटाइम चेक देरी कर सकता है। यह एक बुरी खबर है, क्योंकि यह प्रत्येक कंपाइलर के idiosyncrasy पर निर्भर करता है, इसलिए कार्यक्रम का व्यवहार अच्छी तरह परिभाषित नहीं है।

जाहिर है, संकलक बदल देती है पहले println

रूप
Parent tmp = test.<ChildB>get(); // ok at runtime 
System.out.println(tmp); 

हम ऐसा करने के लिए संकलक पर कोई गलती नहीं लगा सकेंगे, यह पूरी तरह कानूनी है।

संकलक भी इसलिए इस तरह के एक साधारण प्रोग्राम के लिए

ChildB tmp = test.<ChildB>get(); // fail at runtime 
System.out.println(tmp); 

करने के लिए कोड बदल सकता है, क्रम व्यवहार JLS द्वारा अपरिभाषित है।


दूसरे println का व्यवहार भी अनिर्धारित है। संकलक निकालना toString() एक सुपर क्लास से एक तरीका है कि कोई समस्या नहीं है, इसलिए यह मैं एक जवाब नहीं है उपवर्ग

Parent tmp = test.<ChildB>get(); 
String str = tmp.toString(); 
System.out.println(str); 
संबंधित मुद्दे