सूची <डॉग> सूची <पशु> का एक उपवर्ग है? जावा जेनेरिक क्यों नहीं स्पष्ट रूप से बहुरूपी हैं?


769

मैं थोड़ा उलझन में हूँ कि जावा जेनेटिक्स वंशानुक्रम / बहुरूपता को कैसे संभालता है।

निम्नलिखित पदानुक्रम मान लें -

पशु (माता-पिता)

कुत्ता - बिल्ली (बच्चे)

तो मान लीजिए मेरे पास एक तरीका है doSomething(List<Animal> animals)। सभी विरासत और बहुरूपता के नियमों से, मैं यह मान लेगा कि एक List<Dog> है एक List<Animal>और एक List<Cat> है एक List<Animal>- और इसलिए या तो एक इस विधि के लिए पारित किया जा सकता है। ऐसा नहीं। यदि मैं इस व्यवहार को प्राप्त करना चाहता हूं, तो मुझे स्पष्ट रूप से कहकर पशु के किसी भी उपवर्ग की सूची को स्वीकार करने की विधि बतानी होगी doSomething(List<? extends Animal> animals)

मैं समझता हूं कि यह जावा का व्यवहार है। मेरा सवाल यह है कि क्यों ? बहुरूपता आम तौर पर क्यों निहित है, लेकिन जब यह जेनेरिक की बात आती है तो इसे निर्दिष्ट किया जाना चाहिए?


17
और एक पूरी तरह से असंबंधित व्याकरण प्रश्न जो मुझे अब परेशान कर रहा है - क्या मेरा शीर्षक " जावा के जेनरिक क्यों नहीं हैं " या " जावा के जेनेरिक क्यों नहीं है " ?? क्या एस या एकवचन के कारण "जेनरिक" बहुवचन है क्योंकि यह एक इकाई है?
फ्रोडी

25
जावा में किए गए जेनेरिक पैरामीट्रिक बहुरूपता का एक बहुत ही खराब रूप हैं। उन पर विश्वास मत करो (जैसे मैं इस्तेमाल करता था), क्योंकि एक दिन तुम उनकी दयनीय सीमाओं को मार डालोगे : सर्जन हैंडेबल <स्कैलपेल>, हैंडेबल <स्पंज> कबोओम का विस्तार करता है! [टीएम] की गणना नहीं करता है । आपकी जावा जेनरिक लिमिटेशन है। किसी भी OOA / OOD को जावा में ठीक अनुवाद किया जा सकता है (और Java इंटरफेस का उपयोग करके MI को बहुत अच्छी तरह से किया जा सकता है) लेकिन जेनेरिक सिर्फ इसे काटते नहीं हैं। वे "संग्रह" और प्रक्रियात्मक प्रोग्रामिंग के लिए ठीक हैं जिन्होंने कहा (जो कि ज्यादातर जावा प्रोग्रामर वैसे भी करते हैं ...)।
SyntaxT3rr0r

7
सूची का सुपर वर्ग <डॉग> सूची <पशु> नहीं है, लेकिन सूची <?> (अर्थात अज्ञात प्रकार की सूची)। जेनरिक संकलित कोड में टाइप जानकारी मिटा देता है। ऐसा इसलिए किया जाता है कि कोड जो जेनेरिक (जावा 5 और ऊपर) का उपयोग कर रहा है, जेनेरिक के पहले संस्करणों के बिना जेनेरिक के साथ संगत है।
rai.skumar


9
@froadie के बाद से कोई भी प्रतिक्रिया नहीं दे रहा था ... यह निश्चित रूप से "क्यों जावा के जेनेरिक नहीं हैं ..." होना चाहिए। दूसरा मुद्दा यह है कि "जेनेरिक" वास्तव में एक विशेषण है, और इसलिए "जेनेरिक" "सामान्य" द्वारा संशोधित एक गिरा हुआ बहुवचन संज्ञा का जिक्र है। आप कह सकते हैं कि "वह कार्य एक सामान्य है", लेकिन यह कहना "समारोह सामान्य है" की तुलना में अधिक बोझिल होगा। हालांकि, "जावा में जेनेरिक फ़ंक्शंस और कक्षाएं हैं", यह कहने के लिए थोड़ा बोझिल है कि "जावा में जेनेरिक है"। जैसा कि किसी ने विशेषण पर अपने गुरु की थीसिस लिखी है, मुझे लगता है कि आप एक बहुत ही दिलचस्प सवाल पर ठोकर खा चुके हैं!
डेंटिस्टन

जवाबों:


915

नहीं, एक List<Dog>है नहीं एक List<Animal>। विचार करें कि आप क्या कर सकते हैं List<Animal>- आप इसमें किसी भी जानवर को जोड़ सकते हैं ... जिसमें एक बिल्ली भी शामिल है। अब, क्या आप तार्किक रूप से पिल्लों के कूड़े में एक बिल्ली जोड़ सकते हैं? बिलकुल नहीं।

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

अचानक आपके पास एक बहुत ही उलझन वाली बिल्ली है।

अब, आप नहीं कर सकते हैं एक जोड़ने के Catएक करने के लिए List<? extends Animal>, क्योंकि आप इसे एक नहीं जानता List<Cat>। आप एक मान प्राप्त कर सकते हैं और जान सकते हैं कि यह एक होगा Animal, लेकिन आप मनमाने जानवरों को नहीं जोड़ सकते। रिवर्स इसके लिए सही है List<? super Animal>- उस स्थिति में आप Animalइसे सुरक्षित रूप से जोड़ सकते हैं , लेकिन आपको इसके बारे में कुछ भी पता नहीं है कि इससे क्या प्राप्त किया जा सकता है, क्योंकि यह एक हो सकता है List<Object>


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

66
@Ingo: नहीं, वास्तव में नहीं: आप बिल्ली को जानवरों की सूची में जोड़ सकते हैं, लेकिन आप बिल्ली को कुत्तों की सूची में नहीं जोड़ सकते। कुत्तों की एक सूची केवल जानवरों की एक सूची है यदि आप इसे केवल-पढ़ने वाले अर्थ में मानते हैं।
जॉन स्कीट

12
@JonSkeet - निश्चित रूप से, लेकिन जो एक बिल्ली से एक नई सूची बनाना और कुत्तों की एक सूची बना रहा है, वास्तव में कुत्तों की सूची में बदलाव करता है? यह जावा में एक मनमाना कार्यान्वयन निर्णय है। एक जो तर्क और अंतर्ज्ञान के लिए काउंटर जाता है।
इंगो

7
@Ingo: मैं उस "निश्चित रूप से" के साथ शुरू करने के लिए इस्तेमाल नहीं होता। यदि आपके पास एक सूची है जो शीर्ष पर कहती है "होटल जिन्हें हम जाना चाहते हैं" और फिर किसी व्यक्ति ने इसमें एक स्विमिंग पूल जोड़ा, तो क्या आप इसे वैध मानेंगे? नहीं - यह होटलों की सूची है, जो इमारतों की सूची नहीं है। और ऐसा नहीं है कि मैंने यह भी कहा "कुत्तों की एक सूची जानवरों की सूची नहीं है" - मैंने इसे कोड शब्दों में , कोड फ़ॉन्ट में डाला । मुझे नहीं लगता कि यहां कोई अस्पष्टता है। उपवर्ग का उपयोग करना वैसे भी गलत होगा - यह असाइनमेंट संगतता के बारे में है, उपवर्ग नहीं।
जॉन स्कीट

14
@ruakh: समस्या यह है कि आप उस समय के लिए कुछ कर रहे हैं जो संकलन-समय पर अवरुद्ध हो सकता है। और मुझे लगता है कि सरणी covariance के साथ शुरू करने के लिए एक डिजाइन गलती थी।
जॉन स्कीट

83

आप जो खोज रहे हैं उसे सहसंयोजक प्रकार के पैरामीटर कहा जाता है । इसका मतलब यह है कि यदि एक प्रकार की वस्तु को दूसरे के लिए एक विधि में प्रतिस्थापित किया जा सकता है (उदाहरण के लिए, उसके Animalसाथ प्रतिस्थापित किया जा सकता है Dog), तो वही उन वस्तुओं का उपयोग करने वाले भावों पर लागू होता है (इसलिए List<Animal>उन्हें प्रतिस्थापित किया जा सकता है List<Dog>)। समस्या यह है कि सहसंयोजक सामान्य रूप से उत्परिवर्तित सूचियों के लिए सुरक्षित नहीं है। मान लीजिए कि आपके पास एक है List<Dog>, और इसका उपयोग ए के रूप में किया जा रहा है List<Animal>। क्या होता है जब आप एक बिल्ली को जोड़ने की कोशिश करते हैं List<Animal>जो वास्तव में एक है List<Dog>? स्वचालित रूप से प्रकार के मापदंडों को सहसंयोजक होने की अनुमति देने से प्रकार प्रणाली टूट जाती है।

प्रकार के मापदंडों को सहसंयोजक के रूप में निर्दिष्ट करने की अनुमति देने के लिए वाक्यविन्यास जोड़ना उपयोगी होगा, जो ? extends Fooविधि घोषणाओं से बचा जाता है , लेकिन यह अतिरिक्त जटिलता को जोड़ता है।


44

एक कारण List<Dog>नहीं है List<Animal>, यह है कि, उदाहरण के लिए, आप एक Catमें सम्मिलित कर सकते हैं List<Animal>, लेकिन एक में नहीं List<Dog>... आप जहां संभव हो, वहां जेनरिक को अधिक एक्स्टेंसिबल बनाने के लिए वाइल्डकार्ड का उपयोग कर सकते हैं; उदाहरण के लिए, एक से पढ़ना एक से पढ़ने List<Dog>के समान है List<Animal>- लेकिन लिखना नहीं।

में जावा भाषा जेनेरिक्स और जावा ट्यूटोरियल से जेनेरिक्स पर धारा क्यों कुछ बातें कर रहे हैं के रूप में एक बहुत अच्छा, में गहराई से स्पष्टीकरण या बहुरूपी नहीं हैं या जेनरिक के साथ अनुमति दी।


36

एक बिंदु जो मुझे लगता है कि अन्य उत्तरों का उल्लेख किया जाना चाहिए , जबकि वह है

List<Dog>isn't-एक List<Animal> जावा में

यह भी सच है कि

कुत्तों की एक सूची अंग्रेजी में जानवरों की एक सूची है (अच्छी तरह से, एक उचित व्याख्या के तहत)

ओपी का अंतर्ज्ञान जिस तरह से काम करता है - जो पूरी तरह से वैध है - बाद का वाक्य है। हालाँकि, अगर हम इस अंतर्ज्ञान को लागू करते हैं, तो हमें एक ऐसी भाषा मिलती है जो अपने प्रकार की प्रणाली में जावा-एस्क नहीं है: मान लीजिए कि हमारी भाषा कुत्तों की हमारी सूची में एक बिल्ली को जोड़ने की अनुमति देती है। इसका क्या मतलब होगा? इसका मतलब यह होगा कि सूची कुत्तों की एक सूची है, और केवल जानवरों की एक सूची है। और स्तनधारियों की एक सूची, और चौपायों की एक सूची।

इसे दूसरे तरीके से रखने के लिए: List<Dog>जावा में ए का मतलब अंग्रेजी में "कुत्तों की एक सूची" नहीं है, इसका मतलब है "एक सूची जिसमें कुत्ते हो सकते हैं, और कुछ नहीं"।

आम तौर पर, ओपी का अंतर्ज्ञान खुद को एक ऐसी भाषा के लिए उधार देता है जिसमें वस्तुओं पर परिचालन उनके प्रकार को बदल सकता है , या यों कहें, एक वस्तु का प्रकार (एस) इसके मूल्य का एक (गतिशील) कार्य है।


हाँ, मानव भाषा अधिक फजी है। लेकिन फिर भी, एक बार जब आप कुत्तों की सूची में एक अलग जानवर जोड़ते हैं, तो यह अभी भी जानवरों की एक सूची है, लेकिन अब कुत्तों की सूची नहीं है। अंतर, एक इंसान, फजी लॉजिक के साथ, आमतौर पर यह महसूस करने में कोई समस्या नहीं है।
व्लासेक

के रूप में कोई है जो लगातार तुलना करने के लिए और भी अधिक भ्रामक तुलना पाता है, यह जवाब मेरे लिए यह nailed। मेरी समस्या भाषा अंतर्ज्ञान थी।
फ़्लोनलोन

32

मैं कहूंगा कि जेनरिक की पूरी बात यह है कि यह अनुमति नहीं देता है। सरणियों के साथ स्थिति पर विचार करें, जो उस प्रकार के सह-अस्तित्व की अनुमति देते हैं:

  Object[] objects = new String[10];
  objects[0] = Boolean.FALSE;

यह कोड ठीक संकलित करता है, लेकिन एक रनटाइम त्रुटि ( java.lang.ArrayStoreException: java.lang.Booleanदूसरी पंक्ति में) फेंकता है । यह टाइपसेफ नहीं है। जेनरिक का उद्देश्य संकलन समय प्रकार की सुरक्षा को जोड़ना है, अन्यथा आप जेनरिक के बिना एक सादे वर्ग के साथ चिपक सकते हैं।

अब कई बार जहां अधिक लचीला होना करने की जरूरत है और वह यह है कि क्या ? super Classऔर ? extends Classके लिए कर रहे हैं। पूर्व तब होता है जब आपको एक प्रकार Collection(उदाहरण के लिए) में सम्मिलित करने की आवश्यकता होती है , और उत्तरार्द्ध तब होता है जब आपको इसे एक सुरक्षित तरीके से पढ़ने की आवश्यकता होती है। लेकिन एक ही समय में दोनों करने का एकमात्र तरीका एक विशिष्ट प्रकार है।


13
तर्क है, सरणी covariance एक भाषा डिजाइन बग है। ध्यान दें कि प्रकार के क्षरण के कारण जेनेरिक संग्रह के लिए समान व्यवहार तकनीकी रूप से असंभव है।
माइकल बोर्गवर्ड

" मैं कहूंगा कि जेनरिक की पूरी बात यह है कि यह अनुमति नहीं देता है। " आप कभी भी निश्चित नहीं हो सकते हैं: जावा और स्काला के टाइप सिस्टम अनसाउंड: द एक्स्टिशंसील क्राइसिस ऑफ़ नेल पॉइंटर्स (ओओपीएसएलए 2016 में प्रस्तुत किए गए) (चूंकि यह सही लगता है)
डेविड टोनहोफर

15

इस समस्या को समझने के लिए सरणियों की तुलना करना उपयोगी है।

List<Dog>का उपवर्ग नहीं है List<Animal>
लेकिन Dog[] है के उपवर्ग Animal[]

Arrays reifiable और covariant हैं
Reifiable का अर्थ है कि उनके प्रकार की जानकारी रनटाइम पर पूरी तरह से उपलब्ध है।
इसलिए सरणियाँ रनटाइम प्रकार सुरक्षा प्रदान करती हैं, लेकिन संकलन-समय प्रकार सुरक्षा नहीं।

    // All compiles but throws ArrayStoreException at runtime at last line
    Dog[] dogs = new Dog[10];
    Animal[] animals = dogs; // compiles
    animals[0] = new Cat(); // throws ArrayStoreException at runtime

यह जेनरिक के लिए इसके विपरीत है:
जेनरिक मिटाए जाते हैं और अपरिवर्तनीय होते हैं
इसलिए जेनेरिक रनटाइम प्रकार की सुरक्षा प्रदान नहीं कर सकते हैं, लेकिन वे संकलन-समय प्रकार की सुरक्षा प्रदान करते हैं।
नीचे दिए गए कोड में यदि जेनेरिक सहसंयोजक थे तो लाइन 3 पर ढेर प्रदूषण करना संभव होगा ।

    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = dogs; // compile-time error, otherwise heap pollution
    animals.add(new Cat());

2
यह तर्क दिया जा सकता है कि, ठीक उसी वजह से, जावा में
एर्रेज़

कोवेरिएंट होने के कारण एक संकलक "सुविधा" है।
क्रिस्टिक

6

यहाँ दिए गए उत्तर मुझे पूरी तरह से समझाने नहीं थे। इसलिए इसके बजाय, मैं एक और उदाहरण देता हूं।

public void passOn(Consumer<Animal> consumer, Supplier<Animal> supplier) {
    consumer.accept(supplier.get());
}

ठीक लगता है, है ना? लेकिन आप केवल Consumerएस और Supplierएस के लिए Animalएस पास कर सकते हैं । यदि आपके पास एक Mammalउपभोक्ता है, लेकिन एक Duckआपूर्तिकर्ता है, तो उन्हें फिट नहीं होना चाहिए, हालांकि दोनों जानवर हैं। इसे अस्वीकार करने के लिए, अतिरिक्त प्रतिबंध जोड़े गए हैं।

उपरोक्त के बजाय, हमें उन प्रकारों के बीच संबंधों को परिभाषित करना होगा जो हम उपयोग करते हैं।

ई। जी।,

public <A extends Animal> void passOn(Consumer<A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

यह सुनिश्चित करता है कि हम केवल एक आपूर्तिकर्ता का उपयोग कर सकते हैं जो हमें उपभोक्ता के लिए सही प्रकार की वस्तु प्रदान करता है।

OTOH, हम भी कर सकते हैं

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<A> supplier) {
    consumer.accept(supplier.get());
}

जहां हम दूसरे रास्ते पर जाते हैं: हम इसके प्रकार को परिभाषित करते हैं Supplierऔर इसे प्रतिबंधित करते हैं Consumer

हम भी कर सकते हैं

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

जहां, सहज संबंध होने Life-> Animal-> Mammal-> Dog, Catआदि, हम Mammalएक Lifeउपभोक्ता में भी डाल सकते हैं , लेकिन उपभोक्ता Stringमें नहीं Life


1
4 संस्करणों में, # 2 शायद गलत है। उदाहरण के लिए हम इसे तब तक नहीं कह सकते (Consumer<Runnable>, Supplier<Dog>)जब तक Dogकि उपAnimal & Runnable
शिलालेख नहीं है

5

इस तरह के व्यवहार के लिए आधार तर्क यह है कि Genericsप्रकार के क्षरण के एक तंत्र का पालन करें। तो रन टाइम पर आपके पास कोई तरीका नहीं है अगर आप उस प्रकार के collectionविपरीत की पहचान करें arraysजहां ऐसी कोई इरेज़र प्रक्रिया नहीं है। तो आपके सवाल पर वापस आ रहा हूं ...

तो मान लीजिए कि नीचे दी गई विधि है:

add(List<Animal>){
    //You can add List<Dog or List<Cat> and this will compile as per rules of polymorphism
}

अब अगर जावा कॉलर को इस प्रकार की पशु सूची को जोड़ने की अनुमति देता है, तो आप संग्रह में गलत चीज़ जोड़ सकते हैं और समय के साथ-साथ यह प्रकार के क्षरण के कारण भी चलेगा। जबकि सरणियों के मामले में आपको ऐसे परिदृश्यों के लिए एक रन टाइम अपवाद मिलेगा ...

इस प्रकार संक्षेप में इस व्यवहार को लागू किया जाता है ताकि कोई गलत चीज को संग्रह में शामिल न कर सके। अब मेरा मानना ​​है कि जेनेरिक के बिना विरासत जावा के साथ संगतता देने के लिए टाइप इरेज़र मौजूद है ...।


4

उप- प्रकार मानकीकृत प्रकारों के लिए उप- प्रकार अपरिवर्तनशील है । यहां तक ​​कि कठिन वर्ग Dogका एक उपप्रकार है Animal, पैरामीटरकृत प्रकार List<Dog>का उपप्रकार नहीं है List<Animal>। इसके विपरीत, सहसंयोजक उपप्रकार का उपयोग सरणियों द्वारा किया जाता है, इसलिए सरणी प्रकार Dog[]का उप-प्रकार है Animal[]

अपरिवर्तनीय उप-योग यह सुनिश्चित करता है कि जावा द्वारा लागू किए गए प्रकार की बाधाओं का उल्लंघन नहीं किया जाता है। @Jon Skeet द्वारा दिए गए निम्नलिखित कोड पर विचार करें:

List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);

जैसा कि @ जोंन स्कीट द्वारा कहा गया है, यह कोड गैरकानूनी है, क्योंकि अन्यथा यह एक कुत्ते की उम्मीद होने पर एक बिल्ली को वापस करके प्रकार की बाधाओं का उल्लंघन करेगा।

सरणियों के लिए उपर्युक्त अनुरूप कोड की तुलना करना शिक्षाप्रद है।

Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];

कोड कानूनी है। हालाँकि, एक सरणी स्टोर अपवाद फेंकता है । एक सरणी रन-टाइम पर अपने प्रकार को वहन करती है इस तरह से जेवीएम कोवेरिएंट सबटाइपिंग के प्रकार की सुरक्षा को लागू कर सकता है।

इसे और समझने के लिए आइए javapनीचे के वर्ग द्वारा उत्पन्न बायटेकोड को देखें:

import java.util.ArrayList;
import java.util.List;

public class Demonstration {
    public void normal() {
        List normal = new ArrayList(1);
        normal.add("lorem ipsum");
    }

    public void parameterized() {
        List<String> parameterized = new ArrayList<>(1);
        parameterized.add("lorem ipsum");
    }
}

कमांड का उपयोग करना javap -c Demonstration, यह निम्नलिखित जावा बाइटकोड दिखाता है:

Compiled from "Demonstration.java"
public class Demonstration {
  public Demonstration();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void normal();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return

  public void parameterized();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return
}

देखें कि विधि निकायों के अनुवादित कोड समान हैं। कंपाइलर ने प्रत्येक पैरामीटर प्रकार को इसके द्वारा प्रतिस्थापित किया क्षरण । यह संपत्ति महत्वपूर्ण है जिसका अर्थ है कि यह पीछे की संगतता को नहीं तोड़ती है।

निष्कर्ष में, पैरामीटर-प्रकार के लिए रन-टाइम सुरक्षा संभव नहीं है, क्योंकि कंपाइलर प्रत्येक पैरामीटर प्रकार को अपने क्षरण द्वारा बदल देता है। यह बनाता है कि मानकीकृत प्रकार वाक्यगत शर्करा से अधिक कुछ नहीं हैं।


3

वास्तव में आप जो चाहते हैं उसे प्राप्त करने के लिए एक इंटरफ़ेस का उपयोग कर सकते हैं।

public interface Animal {
    String getName();
    String getVoice();
}
public class Dog implements Animal{
    @Override 
    String getName(){return "Dog";}
    @Override
    String getVoice(){return "woof!";}

}

तब आप संग्रह का उपयोग कर सकते हैं

List <Animal> animalGroup = new ArrayList<Animal>();
animalGroup.add(new Dog());

1

यदि आप सुनिश्चित हैं कि सूची आइटम उस दिए गए सुपर प्रकार के उपवर्ग हैं, तो आप इस दृष्टिकोण का उपयोग करके सूची को डाल सकते हैं:

(List<Animal>) (List<?>) dogs

यह तब उपयोगी होता है जब आप सूची को एक कंस्ट्रक्टर में पास करना चाहते हैं या उस पर चलना चाहते हैं


2
यह वास्तव में हल करने की तुलना में अधिक समस्याएं पैदा करेगा
फेरीबग

यदि आप सूची में कैट को जोड़ने का प्रयास करते हैं, तो सुनिश्चित करें कि यह समस्याएं पैदा करेगा, लेकिन लूपिंग उद्देश्यों के लिए मुझे लगता है कि इसका एकमात्र गैर क्रियात्मक उत्तर है।
sagits

1

उत्तर के साथ ही अन्य उत्तर सही हैं। मैं उन उत्तरों को एक समाधान के साथ जोड़ने जा रहा हूं जो मुझे लगता है कि सहायक होगा। मुझे लगता है कि यह प्रोग्रामिंग में अक्सर आता है। ध्यान देने वाली एक बात यह है कि कलेक्शन (सूची, सेट, आदि) के लिए मुख्य मुद्दा कलेक्शन में जुड़ रहा है। यहीं से चीजें टूट जाती हैं। यहां तक ​​कि हटाना भी ठीक है।

ज्यादातर मामलों में, हम Collection<? extends T>तब उपयोग कर सकते हैं Collection<T>और यह पहली पसंद होनी चाहिए। हालांकि, मुझे ऐसे मामले मिल रहे हैं जहां ऐसा करना आसान नहीं है। यह बहस के लिए है कि क्या हमेशा करना सबसे अच्छा काम है। मैं यहां एक क्लास डाउनकास्ट कलैक्शन प्रस्तुत कर रहा हूं जो कि ए Collection<? extends T>को कन्वर्ट Collection<T>कर सकता है (हम मानक दृष्टिकोण का उपयोग करते समय उपयोग की जाने वाली सूची, सेट, नेविगेबलसेट, ..) के लिए समान कक्षाओं को परिभाषित कर सकते हैं। नीचे इसका उपयोग करने के तरीके का एक उदाहरण है (हम Collection<? extends Object>इस मामले में भी उपयोग कर सकते हैं , लेकिन मैं डाउनकास्टकॉलिनेशन का उपयोग करके इसे स्पष्ट करना आसान बना रहा हूं।

/**Could use Collection<? extends Object> and that is the better choice. 
* But I am doing this to illustrate how to use DownCastCollection. **/

public static void print(Collection<Object> col){  
    for(Object obj : col){
    System.out.println(obj);
    }
}
public static void main(String[] args){
  ArrayList<String> list = new ArrayList<>();
  list.addAll(Arrays.asList("a","b","c"));
  print(new DownCastCollection<Object>(list));
}

अब वर्ग:

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class DownCastCollection<E> extends AbstractCollection<E> implements Collection<E> {
private Collection<? extends E> delegate;

public DownCastCollection(Collection<? extends E> delegate) {
    super();
    this.delegate = delegate;
}

@Override
public int size() {
    return delegate ==null ? 0 : delegate.size();
}

@Override
public boolean isEmpty() {
    return delegate==null || delegate.isEmpty();
}

@Override
public boolean contains(Object o) {
    if(isEmpty()) return false;
    return delegate.contains(o);
}
private class MyIterator implements Iterator<E>{
    Iterator<? extends E> delegateIterator;

    protected MyIterator() {
        super();
        this.delegateIterator = delegate == null ? null :delegate.iterator();
    }

    @Override
    public boolean hasNext() {
        return delegateIterator != null && delegateIterator.hasNext();
    }

    @Override
    public  E next() {
        if(!hasNext()) throw new NoSuchElementException("The iterator is empty");
        return delegateIterator.next();
    }

    @Override
    public void remove() {
        delegateIterator.remove();

    }

}
@Override
public Iterator<E> iterator() {
    return new MyIterator();
}



@Override
public boolean add(E e) {
    throw new UnsupportedOperationException();
}

@Override
public boolean remove(Object o) {
    if(delegate == null) return false;
    return delegate.remove(o);
}

@Override
public boolean containsAll(Collection<?> c) {
    if(delegate==null) return false;
    return delegate.containsAll(c);
}

@Override
public boolean addAll(Collection<? extends E> c) {
    throw new UnsupportedOperationException();
}

@Override
public boolean removeAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.removeAll(c);
}

@Override
public boolean retainAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.retainAll(c);
}

@Override
public void clear() {
    if(delegate == null) return;
        delegate.clear();

}

}


यह एक अच्छा विचार है, इतना है कि यह जावा एसई में पहले से मौजूद है। ; )Collections.unmodifiableCollection
रेडियोडफ़ '

1
सही लेकिन मेरे द्वारा परिभाषित संग्रह को संशोधित किया जा सकता है।
dan b

हां, इसे संशोधित किया जा सकता है। Collection<? extends E>पहले से ही उस व्यवहार को सही ढंग से संभालता है, जब तक कि आप इसे उस तरह से उपयोग नहीं करते हैं जो टाइप-सेफ नहीं है (उदाहरण के लिए इसे किसी और चीज़ में डालना)। मेरे द्वारा देखा जाने वाला एकमात्र लाभ यह है कि जब आप addऑपरेशन कहते हैं , तो यह एक अपवाद फेंकता है, भले ही आपने इसे डाला हो।
व्लासेक

0

जावाएसई ट्यूटोरियल से उदाहरण लेते हैं

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

तो क्यों कुत्तों की एक सूची (हलकों) को स्पष्ट रूप से जानवरों की एक सूची नहीं माना जाना चाहिए (आकार) इस स्थिति के कारण है:

// drawAll method call
drawAll(circleList);


public void drawAll(List<Shape> shapes) {
   shapes.add(new Rectangle());    
}

तो जावा "आर्किटेक्ट्स" के पास 2 विकल्प थे जो इस समस्या का समाधान करते हैं:

  1. यह मत समझिए कि एक उपप्रकार का तात्पर्य यह है कि यह सुपरस्क्रिप्ट है, और एक संकलन त्रुटि देता है, जैसे यह अब होता है

  2. माना जाता है कि उपप्रकार यह सुपरटाइप है और "ऐड" विधि को संकलित करने पर रोक लगा दें (इसलिए ड्राअल विधि में, यदि हलकों की एक सूची, आकार का उपप्रकार पारित किया जाएगा, तो संकलक को पता चल जाना चाहिए और आपको करने में संकलन त्रुटि के साथ प्रतिबंधित करना चाहिए उस)।

स्पष्ट कारणों के लिए, जिसने पहला रास्ता चुना।


0

हमें यह भी ध्यान में रखना चाहिए कि कंपाइलर जेनेरिक कक्षाओं को कैसे खतरे में डालते हैं: जब भी हम जेनेरिक दलीलें भरते हैं तो एक अलग प्रकार में "इंस्टेंटिअट्स"।

इस प्रकार हम है ListOfAnimal, ListOfDog, ListOfCat, आदि, जो अलग वर्गों है कि अंत तक संकलक जब हम सामान्य तर्क निर्दिष्ट द्वारा "निर्मित" जा रहा है। और यह एक फ्लैट पदानुक्रम है (वास्तव में के बारे में Listएक पदानुक्रम नहीं है)।

एक अन्य तर्क यह है कि सामान्य वर्गों के मामले में कोविरेंस का मतलब क्यों नहीं है, यह तथ्य है कि आधार पर सभी वर्ग समान हैं - Listउदाहरण हैं। Listजेनेरिक तर्क को भरने से विशेषज्ञ वर्ग का विस्तार नहीं करता है, यह सिर्फ उस विशेष जेनेरिक तर्क के लिए काम करता है।


0

समस्या को अच्छी तरह से पहचान लिया गया है। लेकिन एक उपाय है; doSomething सामान्य बनाना :

<T extends Animal> void doSomething<List<T> animals) {
}

अब आप DoSomething को या तो List <Dog> या List <Cat> या List <Animal> के साथ कॉल कर सकते हैं।


0

एक अन्य समाधान एक नई सूची का निर्माण करना है

List<Dog> dogs = new ArrayList<Dog>(); 
List<Animal> animals = new ArrayList<Animal>(dogs);
animals.add(new Cat());

0

जॉन स्केट द्वारा उत्तर के लिए, जो इस उदाहरण कोड का उपयोग करता है:

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

गहरे स्तर पर, समस्या है कि यहाँ है dogsऔर animalsशेयर के लिए एक संदर्भ। इसका मतलब है कि इस काम को करने का एक तरीका पूरी सूची की नकल करना होगा, जो संदर्भ समानता को तोड़ देगा:

// This code is fine
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog());
List<Animal> animals = new ArrayList<>(dogs); // Copy list
animals.add(new Cat());
Dog dog = dogs.get(0);   // This is fine now, because it does not return the Cat

कॉल करने के बाद List<Animal> animals = new ArrayList<>(dogs);, आप बाद में सीधे या animalsतो असाइन नहीं कर सकते :dogscats

// These are both illegal
dogs = animals;
cats = animals;

इसलिए आप Animalसूची में गलत उपप्रकार नहीं डाल सकते , क्योंकि कोई गलत उपप्रकार नहीं है - उपप्रकार की कोई भी वस्तु इसमें ? extends Animalजोड़ी जा सकती है animals

जाहिर है, यह सूची के बाद से शब्दार्थ को बदल देता है animalsऔर dogsअब साझा नहीं किया जाता है, इसलिए एक सूची में जोड़ने से दूसरे को नहीं जोड़ा जाता है (जो वास्तव में आप चाहते हैं, उस समस्या से बचने के Catलिए जिसे केवल एक सूची में जोड़ा जा सकता है Dogवस्तुओं को समाहित करना )। साथ ही, पूरी सूची की नकल करना अक्षम हो सकता है। हालाँकि, यह संदर्भ समानता को तोड़कर, समतुल्यता समस्या को हल करता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.