2012-05-26 15 views
16

मान लेते हैं कि वर्ग कुत्ता वर्ग पशु लागू होता है: क्यों इस बहुरूपी बयान अनुमति नहीं है:क्यों पॉलिमॉर्फिज्म जेनेरिक संग्रह और सादे सरणी का इलाज नहीं करता है?

List<Animal> myList = new ArrayList<Dog>(); 

हालांकि, यह सादा सरणियों के साथ अनुमत है:

Animal[] x=new Dog[3]; 
+6

वहाँ उन लोगों का तर्क था जो सरणियों की इजाजत दी है कि एक बुरा विचार था क्या करना है कि कर रहे हैं। – Jeffrey

+3

मिराज और जेनेरिक टाइप करें, संग्रह नहीं, कारण हैं। – duffymo

+1

संक्षिप्त उत्तर "जेनेरिक कंटेनर सरणी नहीं हैं"। लंबे उत्तर, जैसे डफिमो अलरेड ने सुझाव दिया है, "erasures" है: http://code.stephenmorley.org/articles/java-generics-type-erasure/ – paulsm4

उत्तर

21

इसके कारण इस पर आधारित हैं कि जावा जेनिक्स को कैसे लागू करता है।

एक सरणी उदाहरण

सरणियों के साथ आप इस (सरणियों covariant हैं के रूप में दूसरों समझा दिया है)

Integer[] myInts = {1,2,3,4}; 
Number[] myNumber = myInts; 

लेकिन, क्या होगा अगर तुम यह करने के लिए कोशिश कर सकते हैं?

Number[0] = 3.14; //attempt of heap pollution 

यह अंतिम पंक्ति ठीक संकलन होगा, लेकिन अगर आप इस कोड को चलाने के लिए, आप एक ArrayStoreException मिल सकता है। क्योंकि आप एक पूर्णांक सरणी में डबल डालने की कोशिश कर रहे हैं (भले ही किसी संख्या संदर्भ के माध्यम से पहुंचा जा रहा हो)।

इसका मतलब है कि आप संकलक को मूर्ख बना सकते हैं, लेकिन आप रनटाइम प्रकार प्रणाली को मूर्ख नहीं बना सकते हैं। और ऐसा इसलिए है क्योंकि सरणी हम reifiable प्रकार पर कॉल करते हैं। इसका मतलब यह है कि रनटाइम पर जावा जानता है कि इस सरणी को वास्तव में पूर्णांक की सरणी के रूप में तुरंत चालू किया गया था जो कि Number[] के संदर्भ के माध्यम से आसानी से पहुंचा जा सकता है।

तो, जैसा कि आप देख सकते हैं, एक बात वस्तु का वास्तविक प्रकार है, एक और चीज उस संदर्भ का प्रकार है जिसका उपयोग आप इसे एक्सेस करने के लिए करते हैं, है ना?

जावा जेनेरिक्स

के साथ समस्या अब, जावा सामान्य प्रकार के साथ समस्या यह है कि प्रकार की जानकारी संकलक द्वारा खारिज कर दिया है और यह रन टाइम पर उपलब्ध नहीं है। इस प्रक्रिया को type erasure कहा जाता है। जावा में इस तरह के जेनरिक को लागू करने के लिए अच्छे कारण हैं, लेकिन यह एक लंबी कहानी है, और इसे पूर्व-मौजूदा कोड के साथ बाइनरी संगतता के साथ करना है।

लेकिन यहां महत्वपूर्ण बात यह है कि, रनटाइम पर कोई प्रकार की जानकारी नहीं है, यह सुनिश्चित करने का कोई तरीका नहीं है कि हम ढेर प्रदूषण नहीं कर रहे हैं।

उदाहरण के लिए

,

List<Integer> myInts = new ArrayList<Integer>(); 
myInts.add(1); 
myInts.add(2); 

List<Number> myNums = myInts; //compiler error 
myNums.add(3.14); //heap polution 

जावा कम्पाइलर ऐसा करने से रोक नहीं करता है, क्रम प्रकार प्रणाली या तो आप को रोक नहीं सकते, कोई रास्ता नहीं है क्योंकि वहाँ रनटाइम पर, निर्धारित करने के लिए, जो इस सूची था केवल पूर्णांक की सूची होना चाहिए। जावा रनटाइम आपको जो कुछ भी आप इस सूची में रखना चाहते हैं, उसे केवल पूर्णांक होना चाहिए, क्योंकि जब इसे बनाया गया था, तो इसे पूर्णांक की सूची के रूप में घोषित किया गया था।

इस तरह, जावा के डिजाइनरों ने सुनिश्चित किया कि आप कंपाइलर को मूर्ख नहीं बना सकते हैं। यदि आप कंपाइलर को मूर्ख नहीं बना सकते हैं (जैसा कि हम सरणी के साथ कर सकते हैं) तो आप रनटाइम टाइप सिस्टम को भी मूर्ख नहीं बना सकते हैं।

इस प्रकार, हम कहते हैं कि सामान्य प्रकार गैर-उत्तरदायी हैं।

जाहिर है, यह बहुरूपता में बाधा डालता है।

static long sum(Number[] numbers) { 
    long summation = 0; 
    for(Number number : numbers) { 
     summation += number.longValue(); 
    } 
    return summation; 
} 

अब आप इसे इस तरह इस्तेमाल कर सकते हैं:

Integer[] myInts = {1,2,3,4,5}; 
Long[] myLongs = {1L, 2L, 3L, 4L, 5L}; 
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0}; 

System.out.println(sum(myInts)); 
System.out.println(sum(myLongs)); 
System.out.println(sum(myDoubles)); 

लेकिन अगर आप सामान्य संग्रह के साथ एक ही कोड लागू करने के लिए प्रयास करते हैं, आप सफल नहीं होगा:

static long sum(List<Number> numbers) { 
    long summation = 0; 
    for(Number number : numbers) { 
     summation += number.longValue(); 
    } 
    return summation; 
} 
निम्नलिखित उदाहरण पर विचार

यदि आप कोशिश करते हैं तो आपको कंपाइलर त्रुटि मिल जाएगी ...

List<Integer> myInts = asList(1,2,3,4,5); 
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L); 
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0); 

System.out.println(sum(myInts)); //compiler error 
System.out.println(sum(myLongs)); //compiler error 
System.out.println(sum(myDoubles)); //compiler error 

समाधान जावा जेनरिक की दो शक्तिशाली विशेषताओं का उपयोग करना सीखना है जो कोविरिएन्स और contravariance के रूप में जाना जाता है।

सहप्रसरण

सहप्रसरण आप एक संरचना से आइटम पढ़ सकते हैं, लेकिन आप इसे में कुछ भी नहीं लिख सकते हैं के साथ। ये सभी वैध घोषणाएं हैं।

List<? extends Number> myNums = new ArrayList<Integer>(); 
List<? extends Number> myNums = new ArrayList<Float>() 
List<? extends Number> myNums = new ArrayList<Double>() 

और तुम myNums से पढ़ सकते हैं:

Number n = myNums.get(0); 

क्योंकि आप यह सुनिश्चित करें कि जो कुछ भी वास्तविक सूची है हो सकता है, यह (एक संख्या को upcasted जा सकती है, सब कुछ भी है कि संख्या फैली के बाद एक नंबर , सही?)

हालांकि, आपको किसी भी प्रकार की संरचना में कुछ भी लगाने की अनुमति नहीं है।

myNumst.add(45L); //compiler error 

यह अनुमति नहीं दी जाएगी, क्योंकि जावा गारंटी नहीं दे सकता कि सामान्य संरचना में वस्तु का वास्तविक प्रकार क्या है। यह कुछ भी हो सकता है जो संख्या बढ़ाता है, लेकिन संकलक सुनिश्चित नहीं हो सकता है। तो आप पढ़ सकते हैं, लेकिन लिख नहीं सकते।

contravariance

contravariance के साथ आप के सामने कर सकते हैं। आप चीजों को एक सामान्य संरचना में डाल सकते हैं, लेकिन आप इससे बाहर नहीं पढ़ सकते हैं।

List<Object> myObjs = new List<Object(); 
myObjs.add("Luke"); 
myObjs.add("Obi-wan"); 

List<? super Number> myNums = myObjs; 
myNums.add(10); 
myNums.add(3.14); 

इस मामले में, वस्तु की वास्तविक प्रकृति वस्तुओं की एक सूची है, और contravariance के माध्यम से, आप इसे में नंबर डाल सकते हैं, मूल रूप से, क्योंकि सभी नंबरों को उनके पूर्वज के रूप में वस्तु की है। इस प्रकार, सभी संख्या वस्तुएं हैं, और इसलिए यह मान्य है।

हालांकि, आप इस contravariant संरचना से कुछ भी सुरक्षित रूप से पढ़ नहीं सकते हैं मानते हैं कि आपको एक संख्या मिल जाएगी।

Number myNum = myNums.get(0); //compiler-error 

आप देख सकते हैं, तो आप इस लाइन लिखने के लिए करता है, तो संकलक की अनुमति दी है, तो आप क्रम में एक ClassCastException मिलेगा।

प्राप्त करें/रखो सिद्धांत

जैसे, सहप्रसरण का उपयोग जब आप केवल एक संरचना के बाहर सामान्य मूल्यों लेने के लिए, contravariance का उपयोग करना चाहते हैं जब आप केवल एक संरचना में सामान्य मूल्यों डाल दिया और सटीक सामान्य उपयोग करना चाहते हैं टाइप करें जब आप दोनों करना चाहते हैं।

मेरे पास सबसे अच्छा उदाहरण निम्न है जो किसी भी प्रकार की संख्या को किसी सूची से दूसरी सूची में कॉपी करता है। यह केवल स्रोत से आइटम प्राप्त करता है, और यह केवल भाग्य में आइटम रखता है।

public static void copy(List<? extends Number> source, List<? super Number> destiny) { 
    for(Number number : source) { 
     destiny.add(number); 
    } 
} 

सहप्रसरण और contravariance की शक्तियों इस इस तरह के मामले के लिए काम करता है के लिए धन्यवाद:

List<Integer> myInts = asList(1,2,3,4); 
List<Double> myDoubles = asList(3.14, 6.28); 
List<Object> myObjs = new ArrayList<Object>(); 

copy(myInts, myObjs); 
copy(myDoubles, myObjs); 
1

कि बहुत दिलचस्प है। मैं तुम्हें इस सवाल का जवाब नहीं बता सकता, लेकिन यह काम करता है अगर आप पशु की सूची में कुत्तों की एक सूची रखना चाहता हूँ:

List<Animal> myList = new ArrayList<Animal>(); 
myList.addAll(new ArrayList<Dog>()); 
+1

यह काम करता है क्योंकि संग्रह में 'addAll() 'loops को पारित किया जाता है और संग्रह में प्रत्येक आइटम पर' List.add()' को कॉल करता है। और 'सूची ' बिल्कुल तत्व हो सकते हैं जो 'पशु' के उप-वर्ग हैं। – QuantumMechanic

+0

@ क्वांटम मैकेनिक हां, मुझे पता था कि यह क्यों काम करता है, मैंने इसे पोस्ट किया है अगर वह आवश्यक कामकाज पर फंस गया था। –

1

तो यह संकलित संग्रह संस्करण कोड करने के लिए तरीका है:

List<? extends Animal> myList = new ArrayList<Dog>(); 

कारण आपको अरण्यों के साथ इसकी आवश्यकता नहीं है क्योंकि मिरर के कारण अराजकता है - सभी प्राइमेटिव्स के सरणी केवल Object[] हैं और जावा एरे एक टाइप की गई श्रेणी नहीं हैं (जैसे संग्रह हैं)। भाषा को इसके लिए पूरा करने के लिए कभी डिजाइन नहीं किया गया था।

Arrays और जेनेरिक मिश्रण नहीं करते हैं।

+0

यह मेरे पीछे की ओर लगता है, लेकिन हो सकता है कि मैं जो कह रहा हूं उसे गलत व्याख्या कर रहा हूं। आप कहते हैं कि "गैर-प्राइमेटिव के सरणी सभी ऑब्जेक्ट हैं [] और जावा एरे एक टाइपेड क्लास नहीं हैं (जैसे संग्रह हैं)," लेकिन क्या यह विपरीत नहीं है? जावा सरणी रनटाइम पर अपना प्रकार बनाए रखते हैं, लेकिन संग्रह टाइप एरर के कारण नहीं होते हैं। –

5

Arrays सामान्य प्रकारों से दो महत्वपूर्ण तरीकों से भिन्न होते हैं। सबसे पहले, सरणी covariant हैं। यह डरावना ध्वनि शब्द का अर्थ यह है कि यदि सब सुपर का एक उप प्रकार है, तो सरणी प्रकार सब [] सुपर [] का एक उप प्रकार है। जेनेरिक, इसके विपरीत, इनवेरिएंट हैं: के लिए किसी भी दो अलग-अलग प्रकार टाइप 1 और टाइप 2, सूची < टाइप 1 > न तो उपप्रकार है और न ही सूची < टाइपका सुपरटेप है।

[..] सरणी और जेनेरिक के बीच दूसरा बड़ा अंतर यह है कि सरणी संशोधित [जेएलएस, 4.7] हैं। इसका मतलब है कि सरणी रनटाइम पर उनके तत्व प्रकारों को जानते और लागू करती हैं।

[..] जेनेरिक, इसके विपरीत, [जेएलएस, 4.6] मिटाकर कार्यान्वित किया जाता है। इसका अर्थ यह है कि वे केवल संकलन पर संकलित करने के लिए अपनी प्रकार की बाधाओं को लागू करते हैं और रनटाइम पर उनके तत्व प्रकार की जानकारी को छोड़ देते हैं (या मिटते हैं)। मिटा जेनेरिक प्रकारों को विरासत कोड के साथ स्वतंत्र रूप से अंतःक्रिया करने की अनुमति देता है जो जेनेरिक (आइटम 23) का उपयोग नहीं करता है। इन मौलिक मतभेदों के कारण, सरणी और जेनेरिक अच्छी तरह से मिश्रण नहीं करते हैं। उदाहरण के लिए, एक सामान्य प्रकार की एक सरणी बनाना, पैरामीटर प्रकार, या एक प्रकार पैरामीटर बनाना अवैध है। इन सरणी निर्माण अभिव्यक्तियों में से कोई भी कानूनी नहीं है: नया सूची < ई > [], नई सूची < स्ट्रिंग > [], नया ई []। सभी का परिणाम संकलन समय पर जेनेरिक सरणी निर्माण त्रुटियों में होगा। [..]

प्रेंटिस हॉल - प्रभावी जावा 2 संस्करण क्योंकि उस मामले में आप कुत्तों में बिल्लियों डाल सकता है

1
List<Animal> myList = new ArrayList<Dog>(); 

संभव नहीं है: दूसरी ओर

private void example() { 
    List<Animal> dogs = new ArrayList<Dog>(); 
    addCat(dogs); 
    // oops, cat in dogs here 
} 

private void addCat(List<Animal> animals) { 
    animals.add(new Cat()); 
} 

List<? extends Animal> myList = new ArrayList<Dog>(); 

संभव है, लेकिन उस मामले में आप (केवल अशक्त स्वीकार किया जाता है) सामान्य paramteres साथ विधियों का उपयोग नहीं कर सकते हैं:

private void addCat(List<? extends Animal> animals) { 
    animals.add(null);  // it's ok 
    animals.add(new Cat()); // compilation error here 
} 
1

अंतिम जवाब यह है कि जिस तरह से क्योंकि जावा कि जिस तरह से निर्दिष्ट किया गया था है। अधिक सटीक, क्योंकि जावा विनिर्देश * विकसित हुआ है।

हम नहीं कह सकता कि जावा डिजाइनरों की वास्तविक सोच था, लेकिन इस पर विचार करें:

List<Animal> myList = new ArrayList<Dog>(); 
myList.add(new Cat()); // compilation error 

बनाम

Animal[] x = new Dog[3]; 
x[0] = new Cat();  // runtime error 

रनटाइम त्रुटि है कि यहां फेंक दिया जाएगा ArrayStoreException है। यह गैर-प्राइमेटिव्स के किसी भी सरणी के लिए किसी भी असाइनमेंट पर संभावित रूप से फेंक दिया जा सकता है।

कोई ऐसा मामला बना सकता है कि जावा के सरणी प्रकारों का संचालन गलत है ... उपरोक्त उदाहरणों की वजह से।

* ध्यान दें कि जावा एरे का टाइपिंग जावा 1.0 से पहले निर्दिष्ट किया गया था, लेकिन जेनेरिक प्रकार केवल जावा 1.5 में जोड़े गए थे। जावा भाषा में पीछे की संगतता की अति-संग्रह मेटा-आवश्यकता है; यानी भाषा एक्सटेंशन पुराने कोड को तोड़ना नहीं चाहिए। अन्य चीजों के अलावा, इसका मतलब है कि ऐतिहासिक गलतियों को ठीक करना संभव नहीं है, जैसे सरणी टाइपिंग काम करता है। , (यह मानते हुए यह स्वीकार किया जाता है कि एक गलती थी ...)


सामान्य प्रकार तरफ टाइप विलोपन des संकलन त्रुटि की व्याख्या नहीं। संकलन त्रुटि वास्तव में गैर-मिटाए गए जेनेरिक प्रकारों का उपयोग करके संकलन प्रकार की जांच के कारण होती है।

और वास्तव में, आप एक अनचेक टाइपकास्ट (चेतावनी को अनदेखा करें) का उपयोग कर संकलन त्रुटि को विचलित कर सकते हैं और ऐसी स्थिति में समाप्त हो सकते हैं जहां आपके ArrayList<Dog> में वास्तव में रनटाइम पर Cat ऑब्जेक्ट्स शामिल हैं। (कि टाइप एरर का परिणाम है!) लेकिन सावधान रहें, कि एक अनचेक रूपांतरण का उपयोग करके संकलन त्रुटियों का आपका विचलन अप्रत्याशित स्थानों में रनटाइम त्रुटियों के कारण उत्तरदायी है ... यदि आपको यह गलत लगता है। यही कारण है कि यह एक बुरा विचार है।

0

जेनेरिक से पहले के दिनों में, एक नियमित रूप से लिखना जो मनमाना प्रकार के सरणी को सॉर्ट कर सकता है, या तो (1) को कॉन्वेंटेट फैशन में केवल पढ़ने के लिए सरणी बनाने और प्रकार-स्वतंत्र फैशन में तत्वों को स्वैप या पुनर्व्यवस्थित करने में सक्षम होना आवश्यक होगा, या (2) कॉन्वेंटेट फैशन में रीड-राइट एरे बनाएं जिन्हें सुरक्षित रूप से पढ़ा जा सके, और उन चीजों के साथ सुरक्षित रूप से लिखा जा सकता है जो पहले एक ही सरणी से पढ़े गए थे, या (3) एरे तत्वों की तुलना करने के कुछ प्रकार-स्वतंत्र साधन प्रदान करते हैं।यदि शुरुआत से भाषा में कॉन्वर्सेंट और contravariant जेनेरिक इंटरफेस शामिल किया गया था, तो पहला दृष्टिकोण सबसे अच्छा हो सकता है, क्योंकि इससे रन टाइम पर टाइप-चेकिंग करने की आवश्यकता से बचने की संभावना होती है और साथ ही संभावना है कि इस तरह के चेक-चेक असफल हो सकता है फिर भी, चूंकि इस तरह के सामान्य समर्थन मौजूद नहीं थे, इसलिए कुछ भी नहीं था जो व्युत्पन्न-प्रकार सरणी को आधार प्रकार की सरणी के अलावा समझ में लाया जा सकता था।