2015-09-10 9 views
14
@Test 
public void test() { 
    MyProperties props = new MyProperties(); 
    props.setProperty("value", new Date()); 

    StringUtils.isNullOrEmpty(props.getProperty("value")); 
} 

public class MyProperties { 
    private Map<String, Object> properties = new HashMap<String, Object>(); 

    public void setProperty(String name, Object value) { 
     properties.put(name, value); 
    } 

    @SuppressWarnings("unchecked") 
    public <T> T getProperty(String name) { 
     return (T) properties.get(name); 
    } 
} 

public class StringUtils { 

    public static boolean isNullOrEmpty(Object string) { 
     return isNullOrEmpty(valueOf(string)); 
    } 

    public static String valueOf(Object string) { 
     if (string == null) { 
      return ""; 
     } 
     return string.toString(); 
    } 

    public static boolean isNullOrEmpty(String string) { 
     if (string == null || string.length() == 0) { 
      return false; 
     } 
     int strLength = string.length(); 
     for (int i = 0; i < strLength; i++) { 
      char charAt = string.charAt(i); 
      if (charAt > ' ') { 
       return true; 
      } 
     } 
     return false; 
    } 

} 

वर्षों से, यह यूनिट परीक्षण पास हो रहा है। फिर जावा 8 में अपग्रेड करने के बाद, कुछ वातावरण में, जब कोड जावैक के माध्यम से संकलित किया जाता है, तो यह स्ट्रिंगयूटिलसिस नलऑरिएक्टी (स्ट्रिंग) ओवरलोड चुनता है।जावा कंपाइलर गलत अधिभार चुनने

java.lang.ClassCastException: java.util.Date cannot be cast to java.lang.String at com.foo.bar.StringUtils_UT.test(StringUtils_UT.java:35)

इकाई परीक्षण जब संकलित और चींटी के माध्यम से मेरी मशीन पर चलने पास (चींटी 1.9.6, jdk_8_u60, विंडोज 7 64 बिट), लेकिन पर विफल रहता है: यह निम्न त्रुटि संदेश के साथ विफल इकाई परीक्षण का कारण बनता है एक और चींटी और जावा के समान संस्करण (चींटी 1.9.6 jdk_8_u60, उबंटू 12.04.4 32 बिट) के साथ।

जावा का type inference, जो संकलन करते समय सभी लागू ओवरलोड से सबसे विशिष्ट ओवरलोड चुनता है, जावा 8 में बदल दिया गया है। मुझे लगता है कि मेरे मुद्दे के साथ कुछ करने के लिए कुछ है।

मुझे पता है कि कंपाइलर MyProperties.getProperty (...) विधि के वापसी प्रकार को टी के रूप में नहीं देखता है। चूंकि संकलक getProperty (...) विधि के रिटर्न प्रकार को नहीं जानता है, इसलिए यह StringUtils.isNullorEmpty (ऑब्जेक्ट) के बजाय StringUtils.isNullorEmpty (स्ट्रिंग) क्यों चुन रहा है - जो हमेशा काम करना चाहिए?

क्या यह जावा में एक बग है या जावा 8 के प्रकार अनुमान के परिणाम का परिणाम है? इसके अलावा, जावा के समान संस्करण का उपयोग करने वाले विभिन्न वातावरण इस कोड को अलग-अलग संकलित क्यों करेंगे?

+1

अपने मामले सभी जावा-8 JREs 'StringUtils.isNullOrEmpty (स्ट्रिंग) का चयन करना चाहिए' अधिभार केवल .. चेक [इस अतः] (http://stackoverflow.com/questions/30521974/why-does में -ज-जावा -8-जेनेरिक-टाइप-इनफरेंस-पिक-इट-ओवरलोड) जो इस मामले में किस ओवरलोड को चुना जाएगा ... – Codebender

+3

ईमानदार 'getProperty' होने के लिए मुझे एक बुरा मजाक लग रहा है। हालांकि यह जावा 8 में एक समस्या की तरह दिखता है, यह अच्छा है कि अब आप उस सुगंधित कोड पर कुछ ध्यान दें। चूंकि आप 'ऑब्जेक्ट' को छोड़कर * मूल्य * के लिए किसी भी अन्य प्रकार का उपयोग नहीं कर रहे हैं, 'टी' ऑब्जेक्ट' से कुछ और नहीं हो सकता है। क्या आप व्याख्या कर सकते हैं कि आप 'टी' के साथ क्या जादू होने की उम्मीद करते हैं? यदि नहीं, तो इसे हटा दें और 'Properties.get (name)' को 'ऑब्जेक्ट' के रूप में वापस करें। एक चेतावनी को अनदेखा करना बीटीडब्ल्यू शायद ही कभी एक अच्छा विचार है :)। – Tom

+3

हाँ, आपका कोड सभी के साथ टूट गया है, लेकिन यह काम करने के लिए _happened_ है। अब यह वापसी का समय है। –

उत्तर

7

यह कोड की गंध करता है। हां, यह जावा 7 के तहत गुजरता है, और हाँ यह जावा 7 के साथ ठीक है, लेकिन कुछ निश्चित रूप से गलत है।

सबसे पहले, इस सामान्य प्रकार के बारे में बात करते हैं।

@SuppressWarnings("unchecked") 
public <T> T getProperty(String name) { 
    return (T) properties.get(name); 
} 

आप क्या T होना चाहिए अनुमान एक नज़र में कर सकते हैं? तो मुझे लगता है कि सही लाइन पर इंटेलीजे साथ जावा 7 अनुपालन मोड वाले डाले चलाने के लिए, मैं वापस इस बहुत उपयोगी ClassCastException मिलती है:

Cannot cast java.util.Date to T

तो यह संकेत मिलता है कि कुछ स्तर पर, जावा जानता था कि वहाँ था यहां कुछ बंद है, लेकिन यह (T) से (Object) से उस कलाकार को बदलने के लिए चुना गया है।

@SuppressWarnings("unchecked") 
public <T> Object getProperty(String name) { 
    return (Object) properties.get(name); 
} 

इस मामले में, डाली निरर्थक है, और आप के रूप में आप उम्मीद करेंगे वापस एक Object नक्शे से मिलता है। फिर, सही अधिभार कहा जाता है।

अब, जावा 8 में, चीजें थोड़ा और अधिक हैं; चूंकि आप वास्तव में getProperty विधि को एक प्रकार प्रदान नहीं करते हैं, इसलिए यह वास्तव मेंjava.util.DateT पर नहीं डाला जा सकता है।


अंत में, मैं मुख्य बिंदु पर glossing हूँ:

जेनरिक का यह प्रयोग टूटा और गलत है।

आप भी जेनिक्स की आवश्यकता नहीं है। आपका कोड या तो String या Object को संभाल सकता है, और आपके मानचित्र में केवल Object एस है।

आपको getProperty विधि से केवल Object वापस करना चाहिए, क्योंकि यह वही है जो आप अपने मानचित्र से किसी भी तरह से वापस कर सकते हैं।

public Object getProperty(String name) { 
    return properties.get(name); 
} 

यह मतलब है कि आप अब सीधे String की एक हस्ताक्षर के साथ विधि में कॉल करने के लिए (जब से तुम अब में एक Object गुजर रहे हैं) की क्षमता मिलता है, लेकिन इसका मतलब यह है अपने टूट जेनरिक कि अंत में कोड को आराम में रखा जा सकता है।


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

@SuppressWarnings("unchecked") 
public <T> T getProperty(String name, Class<T> clazz) { 
    return (T) properties.get(name); 
} 

तो फिर आप अपने विधि इस प्रकार आह्वान सकता:

अब हम क्या T है के रूप में पूरी तरह से आश्वस्त हो जाते हैं, और जावा 8 इस कोड के साथ सामग्री है। यह अभी भी एक गंध का थोड़ा सा है, क्योंकि आप Map<String, Object> में चीजें संग्रहीत कर रहे हैं; अगर आपको Object ओवरराइड विधि मिली है और आप गारंटी दे सकते हैं कि उस मानचित्र की सभी ऑब्जेक्ट्स का अर्थपूर्ण toString है, तो मैं व्यक्तिगत रूप से उपर्युक्त कोड से बचूंगा।

+0

वह संभवतः मानचित्र में डालने पर ऑब्जेक्ट पर स्ट्रिंग कास्टिंग कर रहा है। हालांकि, घोषित प्रकार पर ओवरलोडिंग कार्य, कक्षा के वास्तविक प्रकार नहीं। संभवतया, जेनेरिक उपयोग केवल 'exampleof' का उपयोग करने के बजाय सही प्रकार को कॉल करने का प्रयास था, NullOrEmpty। – Powerlord

+0

नहीं, सम्मिलन पर कोई कास्टिंग नहीं है। कम से कम, परीक्षण यही इंगित करता है। यदि वह व्यवहार वास्तव में हो रहा है, तो इसे परीक्षण में पकड़ा जाना चाहिए था। – Makoto

+0

यह मेरे हिस्से पर बुरा शब्द था क्योंकि आपको इसे किसी ऑब्जेक्ट (सेटप्रोपर्टी के लिए) ले जाने के लिए कुछ करने की आवश्यकता नहीं है। – Powerlord

2

जावा 8 ने target type inference में सुधार किया है। इसका मतलब है कि संकलक प्रकार पैरामीटर का अनुमान लगाने के लिए लक्ष्य प्रकार का उपयोग करेगा।

आपके मामले में, इसका मतलब है कि इस बयान में

StringUtils.isNullOrEmpty(props.getProperty("value")); 

जावा getProperty विधि के प्रकार पैरामीटर निर्धारित करने के लिए isNullOrEmpty के पैरामीटर प्रकार का उपयोग करेगा। लेकिन isNullOrEmpty के 2 ओवरलोड हैं, एक Object ले रहा है और एक String ले रहा है। T पर कोई बाध्यता नहीं है, इसलिए संकलक सबसे विशिष्ट विधि का चयन करेगा जो मेल खाता है - ओवरलोड जो String लेता है। TString होने का अनुमान है।

आपकी कास्ट T अनचेक है, इसलिए संकलक इसे अनुमति देता है, लेकिन यह आपको Object को T पर कास्टिंग करने के बारे में एक अनचेक कास्ट चेतावनी देता है। हालांकि, जब isNullOrEmpty विधि कहा जाता है, तो वर्ग कास्ट अपवाद फेंक दिया जाता है, क्योंकि मूल वस्तु वास्तव में Date थी, जिसे String में परिवर्तित नहीं किया जा सकता है।

यह अनचेक कास्ट चेतावनी को अनदेखा करने के खतरों को दर्शाता है।

यह जावा 7 में नहीं हुआ था, क्योंकि बेहतर लक्ष्य प्रकार अनुमान मौजूद नहीं था। कंपाइलर अनुमानित Object

जावा 8 में बेहतर लक्ष्य प्रकार अनुमान ने खुलासा किया है कि आपकी getProperty विधि अनचेक किए गए कास्ट चेतावनी को गलत तरीके से अनदेखा कर रही है जिसे आप @SuppressWarnings से दबा रहे हैं।

इसे ठीक करने के लिए, एक ओवरलोडेड विधि भी न करें जो String लेती है। String - ओवरलोड के अंदर विशिष्ट तर्क को Object ले जाएं।

public static boolean isNullOrEmpty(Object o) { 
    // null instanceof String is false 
    String string = (o instanceof String) ? ((String) o) : valueOf(o); 
    if (string == null || string.length() == 0) { 
     return false; 
    } 
    int strLength = string.length(); 
    for (int i = 0; i < strLength; i++) { 
     char charAt = string.charAt(i); 
     if (charAt > ' ') { 
      return true; 
     } 
    } 
    return false; 
} 

बेशक इसका मतलब है कि getProperty पद्धति पर जेनरिक व्यर्थ कर रहे हैं। उन्हें हटा दो।

public Object getProperty(String name) { 
    return properties.get(name); 
} 
+0

.... अगर आप इसके बारे में सोचते हैं तो यह समझ में आता है। आखिरकार, यदि आप जेनेरिक का उपयोग कर रहे हैं, तो आप सामान्य प्रकार को 'ऑब्जेक्ट' के रूप में कितनी बार सेट करते हैं? सचमुच, ऐसा करने का एकमात्र कारण गैर-सामान्य प्रकारों का उपयोग न करने के लिए संकलक चेतावनी से बचना है। – Powerlord

+0

मेरे पास कंपाइलर के व्यवहार के इस बदलाव के बारे में जावा 8 के लिए किसी प्रकार की रिलीज नोट के बारे में एक अस्पष्ट स्मृति है लेकिन अब इसे खोजने में सक्षम नहीं लगता है। बोनस प्वाइंट अगर आप इसे एक लिंक प्रदान कर सकते हैं! – Lii

+0

@Lii सभी शानदार विवरण प्राप्त करने के लिए डुप्लिकेट प्रश्न पर जाएं। हालांकि, आपको शायद अपनी जिज्ञासा पर पछतावा होगा। –

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