5

मैं जावा जेनेरिक और varargs का उपयोग कर रहा हूँ।varCs और जेनेरिक का उपयोग करते समय ClassCastException

यदि मैं निम्नलिखित कोड का उपयोग करता हूं, तो मुझे ClassCastException मिलेगा, भले ही मैं कास्ट का उपयोग नहीं कर रहा हूं।

स्ट्रेंजर अभी तक, अगर मैं इसे एंड्रॉइड (दल्विक) पर चलाता हूं तो कोई स्टैक ट्रेस अपवाद के साथ शामिल नहीं है, और यदि मैं इंटरफ़ेस को अमूर्त वर्ग में बदलता हूं, तो अपवाद चर e खाली है।

कोड:

public class GenericsTest { 
    public class Task<T> { 
     public void doStuff(T param, Callback<T> callback) { 
      // This gets called, param is String "importantStuff" 

      // Working workaround: 
      //T[] arr = (T[]) Array.newInstance(param.getClass(), 1); 
      //arr[0] = param; 
      //callback.stuffDone(arr); 

      // WARNING: Type safety: A generic array of T is created for a varargs parameter 
      callback.stuffDone(param); 
     } 
    } 

    public interface Callback<T> { 
     // WARNING: Type safety: Potential heap pollution via varargs parameter params 
     public void stuffDone(T... params); 
    } 

    public void run() { 
     Task<String> task = new Task<String>(); 
     try { 
      task.doStuff("importantStuff", new Callback<String>() { 
       public void stuffDone(String... params) { 
        // This never gets called 
        System.out.println(params); 
       }}); 
     } catch (ClassCastException e) { 
      // e contains "java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;" 
      System.out.println(e.toString()); 
     } 
    } 

    public static void main(String[] args) { 
     new GenericsTest().run(); 
    } 
} 

आप इस चलाते हैं, तो आप एक ClassCastException कि Object स्टैक ट्रेस अमान्य पंक्ति संख्या की ओर इशारा करते के साथ String में ढाला नहीं जा सकता है मिलेगा। क्या यह जावा में एक बग है? मैंने इसे जावा 7 और एंड्रॉइड एपीआई 8 में परीक्षण किया है। मैंने इसके लिए कामकाज किया (doStuff -method में टिप्पणी की), लेकिन ऐसा लगता है कि इसे इस तरह से करना है। अगर मैं varargs (T...) हटा देता हूं, तो सब कुछ ठीक काम करता है, लेकिन मेरे वास्तविक कार्यान्वयन को इसकी आवश्यकता है। अपवाद से

स्टैकट्रेस है:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String; 
    at GenericsTest$1.stuffDone(GenericsTest.java:1) 
    at GenericsTest$Task.doStuff(GenericsTest.java:14) 
    at GenericsTest.run(GenericsTest.java:26) 
    at GenericsTest.main(GenericsTest.java:39) 
+1

है कि आपको स्टैकट्रेस की एक प्रति प्रदान कर सकता है। मेरा संदेह यह है कि यह अंतर्निहित कलाकारों से है जो टाइप एरर के कारण होता है। – Matt

+0

प्रश्न में स्टैक ट्रेस जोड़ा गया। – murgo

उत्तर

9

यह अपेक्षित व्यवहार। जब आप जावा में जेनेरिक का उपयोग करते हैं, तो वस्तुओं के वास्तविक प्रकार संकलित बाइटकोड में शामिल नहीं होते हैं (इसे टाइप एरर के रूप में जाना जाता है)। सभी प्रकार Object बन जाते हैं और टाइप किए गए व्यवहार को अनुकरण करने के लिए संकलित कोड में डाले जाते हैं।

इसके अतिरिक्त, varargs सरणी बन जाते हैं, और जब एक सामान्य varargs विधि कहा जाता है, जावा इसे कॉल करने से पहले विधि पैरामीटर के साथ Object[] प्रकार की सरणी बनाता है।

इस प्रकार, आपकी लाइन callback.stuffDone(param);callback.stuffDone(new Object[] { param }); के रूप में संकलित करती है। हालांकि, कॉलबैक के आपके कार्यान्वयन के लिए String[] प्रकार की आवश्यकता होती है। जावा कंपाइलर ने इस टाइपिंग को लागू करने के लिए अपने कोड में एक अदृश्य कलाकार डाला है, और क्योंकि Object[] को String[] पर नहीं डाला जा सकता है, तो आपको अपवाद मिलता है। आपके द्वारा देखे जाने वाले बोगस लाइन नंबर संभवत: आपके कोड में कहीं भी नहीं दिखाई देता है।

इसके लिए एक वर्कअराउंड आपके कॉलबैक इंटरफेस और कक्षा से जेनरिक को पूरी तरह से हटा देना है, सभी प्रकारों को Object के साथ बदलना।

+0

ठीक है, यह लगा कि यह होगा। यह अजीब बात है कि यह नए ऑब्जेक्ट [] {param} में संकलित हो जाता है, न कि नई स्ट्रिंग [] {param} जो काम करेगा। कंपाइलर में बेहतर कलाकारों को करने के लिए सारी जानकारी होती है (यह किसी भी तरह से स्ट्रिंग [] को डालने के लिए जानता है)। उस और टूटे हुए लाइन नंबर में स्टैक ट्रेस ने मुझे लगता है कि यह जावा बग था। Varargs को हटा दिया, मेरे वास्तविक कार्यक्रम में जेनेरिक रखा। – murgo

+0

@murgo: "कंपाइलर की सारी जानकारी है ..." नहीं, ऐसा नहीं है। सरणी तब बनाई जाती है जब आप varargs फ़ंक्शन को कॉल करते हैं, यानी 'doStuff' में, जब आप' callback.stuffDone (param) कहते हैं; '। उस स्थान पर, यह संकलित समय या रनटाइम पर नहीं जानता है, टी क्या होगा। – newacct

0

grahamparks उत्तर सही है। रहस्यमय टाइपकास्ट सामान्य व्यवहार है। यह सुनिश्चित करने के लिए संकलक द्वारा डाला जाता है कि एप्लिकेशन जेनेरिक के संभावित गलत उपयोग के मुकाबले रनटाइम टाइपएफ़ है।

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

+0

चेतावनी थी, जो प्रश्न में दिखाए गए हैं। फिर भी, यह पता चला कि कुछ ख़राब था, क्योंकि मैंने सोचा था कि मैं जेनरिक्स का उपयोग "कामकाजी तरीके" में करूँगा। यह वास्तव में चेतावनी के बजाय एक संकलन त्रुटि होना चाहिए। – murgo

+1

@murgo - वे चेतावनियां हैं क्योंकि कुछ ऐसे मामले हैं जहां उन्हें अनदेखा करना सुरक्षित है। दरअसल, ऐसे मामले हैं जहां सबसे अच्छे समाधान में चेतावनी को अनदेखा/दबाना शामिल है। –

0

वास्तव में टाइप एरर के कारण है, लेकिन यहां महत्वपूर्ण हिस्सा varargs है। वे पहले से ही नोट किए गए हैं, तालिका के रूप में लागू किए गए हैं। तो कंपाइलर वास्तव में आपके पैरा को पैक करने के लिए ऑब्जेक्ट [] बना रहा है और इसलिए बाद में अवैध कास्ट। लेकिन इसके चारों ओर एक हैक है: यदि आप वर्रग के रूप में टेबल को पास करने के लिए काफी अच्छे हैं, तो कंपाइलर इसे पहचान लेगा, इसे फिर से पैक नहीं करेगा और क्योंकि आपने उसे कुछ काम सहेजा है, यह आपको अपना कोड चलाने देगा :-)

निम्न संशोधन के बाद चलाने के लिए प्रयास करें:

public void doStuff(T[] param, Callback callback) {

और

task.doStuff(new String[]{"importantStuff"}, new Callback() {

+0

आपकी टिप्पणी की गई वर्कअराउंड मूल रूप से वही है, पहले ध्यान नहीं दिया है ... – wmz

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