सामान्य रिटर्न प्रकार ऊपरी बाउंड - इंटरफ़ेस बनाम वर्ग - आश्चर्यजनक रूप से मान्य कोड


171

यह एक 3rd पार्टी लाइब्रेरी एपीआई से एक वास्तविक दुनिया का उदाहरण है, लेकिन सरलीकृत है।

Oracle JDK 8u72 के साथ संकलित

इन दो तरीकों पर विचार करें:

<X extends CharSequence> X getCharSequence() {
    return (X) "hello";
}

<X extends String> X getString() {
    return (X) "hello";
}

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

Integer x = getCharSequence();

और यह संकलन करता है? कंपाइलर को पता होना चाहिए कि Integerलागू नहीं होता है CharSequence। के लिए कॉल

Integer y = getString();

एक त्रुटि देता है (उम्मीद के मुताबिक)

incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String

क्या कोई समझा सकता है कि इस व्यवहार को वैध क्यों माना जाएगा? यह कैसे उपयोगी होगा?

क्लाइंट को पता नहीं है कि यह कॉल असुरक्षित है - क्लाइंट का कोड बिना किसी चेतावनी के संकलन करता है। संकलन उस के बारे में चेतावनी क्यों नहीं देगा / एक त्रुटि जारी करेगा?

इसके अलावा, यह इस उदाहरण से कैसे अलग है:

<X extends CharSequence> void doCharSequence(List<X> l) {
}

List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles

List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error

List<Integer>उम्मीद के मुताबिक, पास करने की कोशिश में त्रुटि होती है:

method doCharSequence in class generic.GenericTest cannot be applied to given types;
  required: java.util.List<X>
  found: java.util.List<java.lang.Integer>
  reason: inference variable X has incompatible bounds
    equality constraints: java.lang.Integer
    upper bounds: java.lang.CharSequence

यदि वह त्रुटि के रूप में रिपोर्ट की गई है, तो क्यों Integer x = getCharSequence();नहीं?


15
दिलचस्प! एलएचएस पर कास्टिंग Integer x = getCharSequence();संकलित करेगा, लेकिन आरएचएस पर कास्टिंग Integer x = (Integer) getCharSequence();संकलन विफल हो जाता है
फ्लेक्स

जावा कंपाइलर का आप किस संस्करण का उपयोग कर रहे हैं? कृपया इस जानकारी को प्रश्न में निर्दिष्ट करें।
फेडरिको पेराल्टा शेफ़नर

@FedericoPeraltaSchaffner यह नहीं देख सकता कि यह क्यों मायने रखता है - यह सीधे JLS के बारे में एक सवाल है।
बोरिस स्पाइडर

@BoristheSpider क्योंकि java8 के लिए प्रकार का अनुमान तंत्र बदल गया है
Federico Peralta Schaffner

1
@FedericoPeraltaSchaffner - मैंने पहले से ही [जावा -8] के साथ प्रश्न को टैग किया था, लेकिन मैंने अब पोस्ट में संकलक संस्करण को जोड़ा।
एडम मिशालिक

जवाबों:


184

CharSequenceएक है interface। इसलिए, भले ही इसे SomeClassलागू न किया CharSequenceजाए, वर्ग बनाना पूरी तरह से संभव होगा

class SubClass extends SomeClass implements CharSequence

इसलिए आप लिख सकते हैं

SomeClass c = getCharSequence();

क्योंकि अनुमान प्रकार Xएक प्रतिच्छेदन प्रकार है SomeClass & CharSequence

अंतिम Integerहोने के कारण Integerयह थोड़ा अजीब है , लेकिन finalइन नियमों में कोई भूमिका नहीं निभाता है। उदाहरण के लिए आप लिख सकते हैं

<T extends Integer & CharSequence>

दूसरी ओर, Stringएक नहीं है interface, इसलिए इसका SomeClassउप-प्रकार प्राप्त करने के लिए विस्तार करना असंभव होगा String, क्योंकि जावा कक्षाओं के लिए एकाधिक-विरासत का समर्थन नहीं करता है।

Listउदाहरण के साथ , आपको यह याद रखने की आवश्यकता है कि जेनेरिक न तो सहसंयोजक हैं और न ही कंट्राविरेंट। इसका मतलब यह है कि यदि Xका उप-प्रकार है Y, List<X>तो न तो कोई उप-प्रकार है और न ही कोई सुपर-टाइप है List<Y>। चूंकि Integerलागू नहीं होता है CharSequence, आप List<Integer>अपनी doCharSequenceपद्धति में उपयोग नहीं कर सकते ।

हालाँकि आप इसे संकलन के लिए प्राप्त कर सकते हैं

<T extends Integer & CharSequence> void foo(List<T> list) {
    doCharSequence(list);
}  

यदि आपके पास एक विधि है जो इस तरह से लौटती है List<T>:

static <T extends CharSequence> List<T> foo() 

तुम कर सकते हो

List<? extends Integer> list = foo();

फिर, इसका कारण यह है कि अनुमानित प्रकार है Integer & CharSequenceऔर यह उप-प्रकार है Integer

जब आप कई सीमाएँ (जैसे <T extends SomeClass & CharSequence>) निर्दिष्ट करते हैं, तो अंतःक्रियात्मक प्रकार अंतर्निहित होते हैं ।

अधिक जानकारी के लिए, यहाँ जेएलएस का एक हिस्सा है जहाँ यह बताता है कि किस प्रकार की सीमाएँ काम करती हैं। आप कई इंटरफेस शामिल कर सकते हैं, जैसे

<T extends String & CharSequence & List & Comparator>

लेकिन केवल पहला बाउंड एक गैर-इंटरफ़ेस हो सकता है।


62
मुझे नहीं पता था कि आप &जेनेरिक परिभाषा में एक डाल सकते हैं । +1
फ्लेक्स

13
@flkes आप एक से अधिक रख सकते हैं, लेकिन केवल पहला तर्क एक गैर-इंटरफ़ेस हो सकता है। <T extends String & List & Comparator>ठीक है, लेकिन <T extends String & Integer>नहीं है, क्योंकि Integerइंटरफ़ेस नहीं है।
पॉल बोडिंगटन 12

7
@PaulBoddington इन विधियों का कुछ व्यावहारिक उपयोग है। उदाहरण के लिए यदि प्रकार वास्तव में संग्रहीत डेटा के लिए उपयोग नहीं किया जाता है। इस के लिए उदाहरण हैं Collections.emptyList()और साथ ही Optional.empty()। ये एक सामान्य इंटरफ़ेस का कार्यान्वयन करते हैं, लेकिन कुछ भी संग्रहीत नहीं करते हैं।
स्टीफन डॉलसे

6
और कोई भी नहीं कहता है कि finalसंकलन-समय पर होने वाला एक वर्ग finalरनटाइम पर होगा ।
होल्गर

7
@Federico Peralta Schaffner: यहाँ बिंदु है, विधि getCharSequence()जो भी Xकॉलर की आवश्यकता है, उसे वापस करने का वादा करती है , जिसमें एक प्रकार का विस्तार Integerऔर कार्यान्वयन शामिल है CharSequenceयदि कॉलर को इसकी आवश्यकता है और इस वादे के तहत, परिणाम को असाइन करने की अनुमति देना सही है Integer। यह विधि है getCharSequence()जो टूटी हुई है क्योंकि यह अपना वादा नहीं रखती है, लेकिन यह संकलक की गलती नहीं है।
होल्गर

59

वह प्रकार जो आपके कंपाइलर द्वारा असाइन किए जाने से पहले अनुमानित Xहै Integer & CharSequence। यह प्रकार अजीब लगता है , क्योंकि Integerअंतिम है, लेकिन यह जावा में पूरी तरह से मान्य प्रकार है। यह तब डाली जाती है Integer, जो पूरी तरह से ठीक है।

Integer & CharSequenceप्रकार के लिए एक संभव मूल्य है null:। निम्नलिखित कार्यान्वयन के साथ:

<X extends CharSequence> X getCharSequence() {
    return null;
}

निम्नलिखित असाइनमेंट काम करेगा:

Integer x = getCharSequence();

इस संभावित मूल्य के कारण, कोई कारण नहीं है कि असाइनमेंट गलत होना चाहिए, भले ही यह स्पष्ट रूप से बेकार हो। एक चेतावनी उपयोगी होगी।

वास्तविक समस्या एपीआई है, कॉल साइट नहीं

वास्तव में, मैंने हाल ही में इस एपीआई डिजाइन विरोधी पैटर्न के बारे में ब्लॉग किया है । आपको (लगभग) मनमाने प्रकारों को वापस करने के लिए कभी भी एक जेनेरिक विधि डिज़ाइन नहीं करनी चाहिए क्योंकि आप (लगभग) कभी भी इस बात की गारंटी नहीं दे सकते कि बांटे गए प्रकार को वितरित किया जाएगा। अपवाद ऐसी विधियाँ हैं Collections.emptyList(), जिनके मामले में सूची की शून्यता (और सामान्य प्रकार का क्षरण) वह कारण है जिसके लिए कोई भी निष्कासन <T>कार्य करेगा:

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.