2016-03-26 8 views
8
public class Box<T> { 
    private T element; 

    public T getElement() { 
     return element; 
    } 

    public void setElement(T element) { 
     this.element = element; 
    } 
} 

public class Test { 

    public static void main(String[] args) { 
     List<Box> l = new ArrayList<>(); //Just List of Box with no specific type 
     Box<String> box1 = new Box<>(); 
     box1.setElement("aa"); 
     Box<Integer> box2 = new Box<>(); 
     box2.setElement(10); 

     l.add(box1); 
     l.add(box2); 

     //Case 1 
     Box<Integer> b1 = l.get(0); 
     System.out.println(b1.getElement()); //why no error 

     //Case 2 
     Box<String> b2 = l.get(1); 
     System.out.println(b2.getElement()); //throws ClassCastException 

    } 
} 

सूची l प्रकार Box का तत्व है। 1 के मामले में, मुझे पहला तत्व Box<Integer> के रूप में मिलता है और दूसरे मामले में सूची में दूसरा तत्व Box<String> के रूप में प्राप्त किया जाता है। प्रथम मामले में क्लासकास्ट अपवाद नहीं फेंक दिया गया है।जावा - स्ट्रिंग जेनेरिक प्रकार के रूप में सामान्य वस्तु प्राप्त करने अपवाद फेंकता

मैं b1 और b2 में, डिबग करने की कोशिश की element's प्रकार String और Integer क्रमशः रहे हैं।

क्या यह टाइप एरर से संबंधित है?

Ideone link

+0

मेरा अनुमान है कि 'बी 1' को रनटाइम पर 'बॉक्स ' जैसा माना जाता है लेकिन यह वास्तव में एक अजीब व्यवहार है। –

उत्तर

4

सटीक होने के लिए, समस्या PrintStream#println है। ,

72: invokevirtual #12  // Method blub/Box.getElement:()Ljava/lang/Object; 
75: invokevirtual #13  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 

आप देख सकते हैं संकलक प्रकार मिट और भी Integer के लिए एक डाली छोड़े गए, क्योंकि यह यहाँ आवश्यक नहीं था:

के javap -c Test.class का उपयोग कर संकलित कोड की जांच करते हैं। कंपाइलर ने पहले ही इस्तेमाल किए गए ओवरलोडेड को PrintStream#(Object) पर लिंक किया है। ऐसा नहीं है कि JLS rule §5.3 की वजह से है:

विधि मंगलाचरण रूपांतरण एक विधि या निर्माता मंगलाचरण (§8.8.7.1, §15.9, §15.12) में प्रत्येक तर्क मूल्य पर लागू होता है: तर्क अभिव्यक्ति के प्रकार परिवर्तित किया जाना चाहिए संबंधित पैरामीटर का प्रकार।

  • एक पहचान रूपांतरण (§5.1.1)
  • एक चौड़ा आदिम रूपांतरण (§5.1.2)
  • एक चौड़ा संदर्भ रूपांतरण (§5.1.5)
  • :

    विधि मंगलाचरण संदर्भों निम्न में से एक के उपयोग की अनुमति

  • एक मुक्केबाजी रूपांतरण (§5.1.7) वैकल्पिक रूप से संदर्भ रूपांतरण को बढ़ाकर
  • एक अनबॉक्सिंग रूपांतरण (§5.1.8) वैकल्पिक रूप से एक व्यापक आदिम रूपांतरण के बाद।

    एक चौड़ा संदर्भ रूपांतरण किसी भी संदर्भ प्रकार टी के लिए किसी भी संदर्भ प्रकार S से मौजूद है, बशर्ते एस एक उप-प्रकार है:

तीसरा नियम एक महाप्रकार करने के लिए एक उप-प्रकार से रूपांतरण है (§4.10) टी

की

और प्रकार अनबॉक्स्ड (पांचवें जांच हो सकती है अगर जाँच से पहले किया जाता है: "एक unboxing रूपांतरण")। तो संकलक जांचता है कि IntegerObject का उप प्रकार है और इसलिए इसे #println(Object) पर कॉल करना होगा (यदि आप कॉल किए गए अधिभारित संस्करण को चेक करते हैं तो आपका आईडीई आपको वही बताएगा)।

दूसरी ओर दूसरे संस्करण:

95: invokevirtual #12  // Method blub/Box.getElement:()Ljava/lang/Object; 
98: checkcast  #14  // class java/lang/String 
101: invokevirtual #15  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 

Box#getElement की पुनः प्राप्त प्रकार की जांच के लिए एक checkcast है वास्तव में एक String है। यह आवश्यक है, क्योंकि आपने संकलक को बताया है कि यह String होगा (सामान्य प्रकार Box<String> b2 = l.get(1); के कारण) और यह विधि PrintStream#(String) से जुड़ा हुआ है। यह चेक उल्लिखित ClassCastException के साथ विफल रहता है, क्योंकि Integer को String पर नहीं डाला जा सकता है।

1

संकलन समय पर संकलक प्रकार जानता है और सही पैरामीटर प्रकार के साथ विधि के लिए System.out.println(..) की कॉल जोड़ता है। पहले मामले में संकलक println(Object) पर कॉल को हल करता है। क्योंकि b1.getElement()Object देता है, String एक Object है, विधि कॉल सही है और कोई अपवाद नहीं उठाता है। दूसरे मामले में संकलक की वजह से println(String) पर कॉल को हल करता है, लेकिन b2.getElement()Integer देता है। इसे स्ट्रिंग में नहीं डाला जा सकता है और ClassCastException उठाया जा सकता है। इसलिए b2.getElement() स्ट्रिंग के रूप में लिखा गया है, भले ही यह वास्तव में एक पूर्णांक शामिल हैं -

+1

तो system.out.println (10) आपकी राय में काम नहीं करता है। –

+0

'b.getElement()' एक 'इंटीजर' देता है। मुझे लगता है कि आप println संस्करण का मतलब है जो पैरामीटर के रूप में ऑब्जेक्ट लेता है। – user7

3

ठीक है, समस्या यह है कि b2 गलत तरीके सेBox<String> जा रहा है के रूप में चिह्नित है जब यह वास्तव में Box<Integer> (box2 के प्रकार) है है। कंपाइलर अधिभारित println विधि को कॉल करने का प्रयास करता है जो ऑब्जेक्ट लेते हुए विधि के बजाय एक स्ट्रिंग लेता है और इसलिए आपको क्लासकास्ट अपवाद मिलता है। Println का ऑब्जेक्ट संस्करण स्ट्रिंग के लिए अपने तर्क का एक स्पष्ट रूपांतरण करता है (कॉल टू टूस्ट्रिंग() के माध्यम से) लेकिन विधि का स्ट्रिंग संस्करण ऐसा नहीं करता है।

अंतर्निहित समस्या सूची l के लिए प्रकार पैरामीटर को पूरी तरह से निर्दिष्ट करने के बजाय कच्चे प्रकार का उपयोग कर रही है - यह List<Box<?>> होना चाहिए था। तो आपके पास b1 और b2 बॉक्स के रूप में होगा और System.out.println का दायां अधिभार चुना गया होगा।

+0

तो कारण यह है कि जब मैं 'b1.getElement() 'को कॉल करने का प्रयास करता हूं तो यह समस्या तब नहीं होती है जब ऑब्जेक्ट पैरामीटर के साथ println कहा जाता है? – user7

+0

हां, बी 1 भी गलत टाइप किया गया है लेकिन रनटाइम पर इससे कोई फर्क नहीं पड़ता क्योंकि यह ऑब्जेक्ट में डाला जाता है। – sisyphus

0

विधि println इंटीजर पैरामीटर के लिए परिभाषित नहीं किया गया है, इसलिए आपका कोड println (ऑब्जेक्ट ऑब्जेक्ट) को कॉल करेगा जो स्ट्रिंग को प्रिंट करने के लिए आवश्यक स्ट्रिंग प्राप्त करने के लिए object.toString() को कॉल करेगा। कोई प्रकार की जांच नहीं है क्योंकि सब कुछ एक वस्तु है।

दूसरे मामले में आपका कोड println (स्ट्रिंग कुछ स्ट्रिंग) को कॉल करना चाहता है और इसके कारण यह जांच करेगा कि कुछ स्ट्रिंग वास्तव में एक स्ट्रिंग है और क्योंकि यह एक अपवाद फेंकने वाला नहीं है।

संबंधित मुद्दे