सरणियाँ सहसंयोजक क्यों हैं, लेकिन जेनेरिक अपरिवर्तनीय हैं?


160

जोशुआ बलोच द्वारा प्रभावी जावा से,

  1. एरियर्स दो महत्वपूर्ण तरीकों से सामान्य प्रकार से भिन्न होते हैं। पहले सरणियाँ सहसंयोजक हैं। जेनरिक अपरिवर्तनीय हैं।
  2. सहसंयोजक का सीधा अर्थ है कि यदि X Y का उप-प्रकार है तो X [] भी Y [] का उप प्रकार होगा। Arrays सहसंयोजक हैं क्योंकि स्ट्रिंग ऑब्जेक्ट का उपप्रकार है

    String[] is subtype of Object[]

    Invariant का सीधा अर्थ है कि भले ही X, Y का उपप्रकार हो या न हो,

     List<X> will not be subType of List<Y>.

मेरा सवाल यह है कि जावा में कोवेरिएंट बनाने का फैसला क्यों? अन्य एसओ पद भी हैं जैसे एरेस क्यों अपरिवर्तनीय हैं, लेकिन सूचियां सहसंयोजक हैं? , लेकिन वे स्काला पर ध्यान केंद्रित कर रहे हैं और मैं पालन करने में सक्षम नहीं हूं।


1
क्या यह इसलिए नहीं है क्योंकि बाद में जेनरिक जोड़े गए थे?
सोतीरियोस डेलिमोलिस

1
मुझे लगता है कि सरणियों और संग्रह के बीच तुलना अनुचित है, संग्रह पृष्ठभूमि में सरणियों का उपयोग करता है !!
अहमद अदेल इस्माइल

4
@ EL-conteDe-monteTereBentikh सभी संग्रह नहीं, उदाहरण के लिए LinkedList
पॉल बेलोरा

@PaBBellora मुझे पता है कि मैप्स संग्रह कार्यान्वयनकर्ताओं से अलग हैं, लेकिन मैंने एससीपीजे 6 में पढ़ा कि संग्रह आमतौर पर सरणियों पर निर्भर होते हैं !!
अहमद अदेल इस्माइल

क्योंकि कोई ArrayStoreException नहीं है; संग्रह में गलत तत्व डालने पर जहां सरणी के पास है। इसलिए संग्रह केवल पुनर्प्राप्ति समय पर ही मिल सकता है और वह भी कास्टिंग के कारण। इसलिए जेनरिक इस मुद्दे को हल करना सुनिश्चित करेगा।
कानागावेलु सुगुमार

जवाबों:


150

वाया विकिपीडिया :

जावा और सी # के शुरुआती संस्करणों में जेनरिक (उर्फ पैरामीट्रिक बहुरूपता) शामिल नहीं था।

इस तरह की सेटिंग में, सरणियों को अपरिवर्तनीय उपयोगी पॉलीमॉर्फ़िक कार्यक्रमों से बाहर किया जाता है। उदाहरण के लिए, ऐरे को फेरबदल करने के लिए एक फ़ंक्शन लिखने पर विचार करें, या एक फ़ंक्शन जो Object.equalsतत्वों पर विधि का उपयोग करके समानता के लिए दो सरणियों का परीक्षण करता है। कार्यान्वयन सरणी में संग्रहीत सटीक प्रकार के तत्व पर निर्भर नहीं करता है, इसलिए सभी प्रकार के सरणियों पर काम करने वाले एकल फ़ंक्शन को लिखना संभव है। प्रकार के कार्यों को लागू करना आसान है

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

हालाँकि, यदि सरणी प्रकारों को अपरिवर्तनीय माना जाता है, तो यह केवल इन प्रकारों के सरणी पर इन फ़ंक्शन को कॉल करना संभव होगा Object[]। उदाहरण के लिए, स्ट्रिंग्स की एक सरणी में फेरबदल नहीं किया जा सकता है।

इसलिए, जावा और सी # दोनों प्रकार के सरणी प्रकार को सहानुभूतिपूर्वक व्यवहार करते हैं। उदाहरण के लिए, C में # string[]का उप-प्रकार है object[], और जावा में String[]इसका उप-प्रकार है Object[]

इस सवाल का जवाब "क्यों सरणियों covariant हैं?", या अधिक सही, "क्यों थे covariant बनाया सरणियों समय में ?"

जब जेनरिक की शुरुआत की गई थी, तो उन्हें जॉन स्केट द्वारा इस उत्तर में दिए गए कारणों के लिए उद्देश्यपूर्ण ढंग से सहसंयोजक नहीं बनाया गया था :

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

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

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

विकिपीडिया लेख में वर्णित सरणियों को सहवर्ती बनाने के लिए मूल प्रेरणा जेनरिक पर लागू नहीं हुई क्योंकि वाइल्डकार्ड ने उदाहरण के लिए सहसंयोजक (और विपरीत) की अभिव्यक्ति को संभव बनाया:

boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);

3
हां, Arrays बहुरूपी व्यवहार के लिए अनुमति देता है, हालांकि, यह रनटाइम एक्सपेक्टेशन (जेनरिक के साथ संकलन-समय अपवादों के विपरीत) का परिचय देता है। जैसे:Object[] num = new Number[4]; num[1]= 5; num[2] = 5.0f; num[3]=43.4; System.out.println(Arrays.toString(num)); num[0]="hello";
२२:०२ पर:::

21
यह सही है। Arrays ArrayStoreExceptionमें जरूरत के अनुसार पुन: प्रयोज्य प्रकार और थ्रो हैं । स्पष्ट रूप से उस समय इसे एक योग्य समझौता माना गया था। इसके विपरीत आज के साथ: कई संबंध सरणी एक गलती के रूप में कोविरेंस, पूर्वव्यापी में।
पॉल बेलोरा

1
"कई" इसे गलती क्यों मानते हैं? यह सरणी covariance नहीं होने से कहीं अधिक उपयोगी है। आपने कितनी बार एक ArrayStoreException देखी है; वे काफी दुर्लभ हैं। यहाँ विडंबना अक्षम्य इमो है ... जावा में अब तक की सबसे खराब गलतियों में से एक है, साइट-साइट विचरण उर्फ ​​वाइल्डकार्ड।
स्कॉट

3
@ScottMcKinney: "कई" इसे गलती क्यों मानते हैं? AIUI, इसका कारण यह है कि सरणी सहसंयोजक को सभी सरणी असाइनमेंट ऑपरेशन पर गतिशील प्रकार के परीक्षणों की आवश्यकता होती है (हालांकि संकलक अनुकूलन शायद मदद कर सकता है?) जो एक महत्वपूर्ण रनटाइम ओवरहेड का कारण बन सकता है।
डोमिनिक डेरीसे

धन्यवाद, डोमिनिक, लेकिन मेरे अवलोकन से यह "कई" कारण प्रतीत होता है कि यह एक गलती है जो कुछ अन्य लोगों ने कहा है कि तोते की तर्ज पर अधिक है। फिर से, कोवेरियन पर एक नए सिरे से नज़र डालें, तो यह नुकसानदायक से कहीं अधिक उपयोगी है। फिर से, वास्तविक BIG गलती जावा द्वारा वाइल्डकार्ड के माध्यम से साइट-साइट जेनेरिक विचरण किया गया। मुझे लगता है कि "बहुत से" स्वीकार करना चाहते हैं की तुलना में अधिक समस्याएं पैदा हुई हैं।
स्कॉट

30

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

उदाहरण के लिए:

String[] strings = new String[2];
Object[] objects = strings;  // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime

यदि यह सामान्य संग्रह के साथ अनुमति दी गई थी:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;  // let's say it is valid
objects.add(12);  // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this

लेकिन यह बाद में समस्या पैदा करेगा जब कोई व्यक्ति सूची तक पहुंचने का प्रयास करेगा:

String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String

मुझे लगता है कि पॉल बेलोरा का जवाब अधिक उपयुक्त है क्योंकि वह WHY Arrays पर टिप्पणी करने के लिए सहसंयोजक हैं। यदि सरणियों को अपरिवर्तनीय बनाया गया था, तो इसका जुर्माना। आप इसके साथ प्रकार मिटा देंगे। प्रकार Erasure संपत्ति का मुख्य कारण पिछड़े संगतता के लिए सही है?
57१

@ user2708477, हाँ, पिछड़े संगतता के कारण टाइप इरेज़र पेश किया गया था। और हां, मेरा जवाब शीर्षक में सवाल का जवाब देने की कोशिश करता है, क्यों जेनेरिक अपरिवर्तनीय हैं।
Katona

तथ्य यह है कि arrays उनके प्रकार का मतलब है कि जबकि covariance कोड को एक सरणी में कुछ स्टोर करने के लिए पूछने की अनुमति देता है जहां यह फिट नहीं होगा - इसका मतलब यह नहीं है कि ऐसी दुकान को जगह लेने की अनुमति होगी। नतीजतन, एरियर्स होने के कारण शुरू किए गए खतरे का स्तर बहुत कम होता है, अगर वे अपने प्रकारों को नहीं जानते हैं।
सुपरकैट

@supercat, सही है, जो मैं इंगित करना चाहता था वह यह है कि जगह में प्रकार के क्षरण के साथ जेनरिक के लिए, कोवरियन को रनटाइम चेक की न्यूनतम सुरक्षा के साथ लागू नहीं किया जा सकता था
काटोना

1
मुझे व्यक्तिगत रूप से लगता है कि यह उत्तर सही स्पष्टीकरण प्रदान करता है कि क्यों Arrays सहसंयोजक हैं जब संग्रह नहीं हो सकते। धन्यवाद!
asgs

22

यह मदद हो सकती है : -

जेनरिक सहसंयोजक नहीं हैं

जावा भाषा में एरर्स कोवेरिएंट हैं - जिसका अर्थ है कि अगर इंटेगर नंबर (जो यह करता है) का विस्तार करता है, तो न केवल एक इंटीजर भी एक नंबर है, बल्कि एक इंटीजर [] भी एक है Number[], और आप पास या असाइन करने के लिए स्वतंत्र हैं Integer[]जहाँ के Number[]लिए कहा जाता है (अधिक औपचारिक रूप से, यदि संख्या पूर्णांक का एक सुपरटेप है, तो Number[]इसका एक सुपरस्क्रिप्ट है Integer[]।) आप सोच सकते हैं कि जेनेरिक प्रकारों के बारे में भी यही सच है - List<Number>यह एक सुपर -टाइप है List<Integer>, और यह कि आप एक List<Integer>जहां List<Number>से उम्मीद की जा सकती है। दुर्भाग्य से, यह उस तरह से काम नहीं करता है।

यह पता चलता है कि यह एक अच्छा कारण है जो इस तरह से काम नहीं करता है: यह उस प्रकार को तोड़ देगा जिस प्रकार की सुरक्षा जेनरिक प्रदान करने वाली थी। कल्पना कीजिए कि आप ए List<Integer>को असाइन कर सकते हैं List<Number>। फिर निम्नलिखित कोड आपको कुछ ऐसा करने की अनुमति देगा जो एक पूर्णांक में नहीं था List<Integer>:

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

क्योंकि ln एक है List<Number>, इसमें एक फ्लोट जोड़ना पूरी तरह से कानूनी लगता है। लेकिन अगर ln के साथ उपनाम दिया गया था li, तो यह li की परिभाषा में निहित प्रकार-सुरक्षा वादा तोड़ देगा - कि यह पूर्णांकों की एक सूची है, यही कारण है कि सामान्य प्रकार सहसंयोजक नहीं हो सकते हैं।


3
Arrays के लिए, आपको एक ArrayStoreExceptionरनटाइम मिलता है।
सोतीरियो डेलिमोलिस

4
मेरा सवाल है WHYArrays बना covariant। जैसा कि सोतीरियोस ने उल्लेख किया है, अर्रेस के साथ किसी को भी अर्रेस्टस्टोर एक्ससेप्शन मिलेगा रनटाइम पर, अगर एरेस को अपरिवर्तनीय बना दिया गया था, तो हम संकलन समय पर इस त्रुटि का सही पता लगा सकते हैं?
२१:५१ पर erto१

@eagertoLearn: जावा की एक प्रमुख अर्थगत कमजोरी यह है कि यह अपने प्रकार की प्रणाली में कुछ भी नहीं कर सकता है "Array जो कुछ भी नहीं है, लेकिन डेरिवेटिव्स को अलग रखता है Animal, जिसे" Array से प्राप्त किसी भी वस्तु को स्वीकार करने की आवश्यकता नहीं है "Array जिसमें कुछ भी नहीं होना चाहिए Animal, लेकिन और बाहरी रूप से आपूर्ति किए गए संदर्भों को स्वीकार करने के लिए तैयार होना चाहिए Animal। कोड जिसे पूर्व की आवश्यकता है उसे एक सरणी को स्वीकार करना चाहिए Cat, लेकिन बाद वाला कोड ऐसा नहीं होना चाहिए। यदि कंपाइलर दो प्रकारों को अलग कर सकता है, तो यह संकलन-समय की जाँच प्रदान कर सकता है। दुर्भाग्य से, केवल एक चीज उन्हें अलग करती है ...
सुपरकैट

... क्या कोड वास्तव में उनमें कुछ भी स्टोर करने की कोशिश करता है, और रनटाइम तक यह जानने का कोई तरीका नहीं है।
सुपरकैट

3

ऐरे कम से कम दो कारणों से सहसंयोजक हैं:

  • यह उन संग्रहों के लिए उपयोगी है जो जानकारी रखते हैं जो कभी भी सहसंयोजक में नहीं बदलेगा। टी के एक संग्रह के लिए सहसंयोजक होने के लिए, इसके बैकिंग स्टोर को सहसंयोजक भी होना चाहिए। जबकि एक अपरिवर्तनीय Tसंग्रह को डिज़ाइन कर सकता है जो T[]अपने बैकिंग स्टोर (जैसे कि एक पेड़ या लिंक की गई सूची का उपयोग करके) का उपयोग नहीं करता था , ऐसे संग्रह के साथ-साथ एक सरणी द्वारा समर्थित एक प्रदर्शन करने की संभावना नहीं होगी। कोई यह तर्क दे सकता है कि सहसंयोजक अपरिवर्तनीय संग्रह के लिए प्रदान करने का एक बेहतर तरीका एक "सहसंयोजक अपरिवर्तनीय सरणी" को परिभाषित करना होगा जो वे एक बैकिंग स्टोर का उपयोग कर सकते हैं, लेकिन बस सरणी सहसंयोजक को अनुमति देना शायद आसान था।

  • एरे अक्सर कोड द्वारा उत्परिवर्तित किया जाएगा जो यह नहीं जानता कि किस प्रकार की चीज उन में होने जा रही है, लेकिन उस सरणी में कुछ भी नहीं डाला जाएगा जो उसी सरणी से बाहर नहीं पढ़ा गया था। इसका एक प्रमुख उदाहरण कोड को छांटना है। अवधारणात्मक रूप से यह संभव है कि सरणी प्रकारों में स्वैप या परमिट तत्वों को शामिल करने के तरीके शामिल हों (ऐसे तरीके किसी भी सरणी प्रकार पर समान रूप से लागू हो सकते हैं), या "सरणी जोड़तोड़" ऑब्जेक्ट को परिभाषित करते हैं जो एक सरणी और एक या अधिक चीजों के संदर्भ में पकड़ रखते हैं। जो इससे पढ़ा गया था, और इसमें पहले से पढ़ी गई वस्तुओं को उस सरणी में संग्रहीत करने के तरीके शामिल हो सकते हैं, जिनसे वे आए थे। यदि सरणियाँ सहसंयोजक नहीं थीं, तो उपयोगकर्ता कोड इस प्रकार को परिभाषित करने में सक्षम नहीं होगा, लेकिन रनटाइम में कुछ विशेष तरीके शामिल हो सकते हैं।

तथ्य यह है कि arrays covariant एक बदसूरत हैक के रूप में देखा जा सकता है, लेकिन ज्यादातर मामलों में यह काम करने वाले कोड के निर्माण की सुविधा प्रदान करता है।


1
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.- अच्छा बिंदु
२१:

3

पैरामीट्रिक प्रकारों की एक महत्वपूर्ण विशेषता पॉलीमॉर्फिक एल्गोरिदम लिखने की क्षमता है, अर्थात एल्गोरिदम जो डेटा पैरामीटर पर संचालित होते हैं, भले ही इसके पैरामीटर मान की परवाह किए बिना Arrays.sort()

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

<E extends Comparable<E>> void sort(E[]);

वास्तव में उपयोगी होने के लिए, वाइल्डकार्ड प्रकारों को वाइल्डकार्ड कैप्चर की आवश्यकता होती है, और इसके लिए एक प्रकार के पैरामीटर की धारणा की आवश्यकता होती है। समय सारणी में से कोई भी जावा में शामिल नहीं किया गया था, और संदर्भ प्रकार के सहसंयोजक सरणियों ने पॉलीमॉर्फिक एल्गोरिदम को अनुमति देने के लिए बहुत सरल तरीके की अनुमति दी:

void sort(Comparable[]);

हालाँकि, उस सादगी ने स्टेटिक टाइप सिस्टम में एक खामी खोली:

String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException

संदर्भ प्रकार की एक सरणी तक हर लेखन पहुंच के रनटाइम चेक की आवश्यकता होती है।

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


2

जेनेरिक्स अपरिवर्तनीय हैं : से जेएसएल 4.10 :

... जेनेरिक प्रकारों के माध्यम से सबटाइपिंग का विस्तार नहीं होता है: टी <: यू का मतलब यह नहीं है कि C<T><: C<U>...

और कुछ पंक्तियाँ, जेएलएस यह भी बताता है कि
एरेस सहसंयोजक (पहली गोली) हैं:

4.10.3 सरणी प्रकारों के बीच घटाव

यहां छवि विवरण दर्ज करें


2

मुझे लगता है कि उन्होंने पहले स्थान पर एक गलत निर्णय लिया, जिसने सरणी सहसंयोजक बना दिया। यह यहाँ वर्णित के रूप में प्रकार की सुरक्षा को तोड़ता है और पिछड़े संगतता के कारण वे इसके साथ फंस गए और इसके बाद उन्होंने जेनेरिक के लिए समान गलती नहीं करने की कोशिश की। और यह एक कारण है कि जोशुआ बलोच ने "प्रभावी जावा (दूसरा संस्करण)" पुस्तक के आइटम 25 में yra को पसंद किया है।


जोश ब्लॉक जावा के संग्रह ढांचे (1.2) के लेखक थे, और जावा के जेनरिक (1.5) के लेखक थे। तो जिस लड़के ने जेनरिक का निर्माण किया है, जिसके बारे में सभी को शिकायत है, वह भी संयोग से है, जिसने किताब लिखी थी और कहा था कि वे जाने के लिए बेहतर तरीका हैं? बहुत बड़ा आश्चर्य नहीं!
24

1

मेरा लेना: जब कोड ए ए] की उम्मीद कर रहा है और आप इसे बी [] देते हैं जहां बी ए का एक उपवर्ग है, तो चिंता करने के लिए केवल दो चीजें हैं: जब आप ऐरे तत्व को पढ़ते हैं तो क्या होता है, और यदि आप लिखते हैं तो क्या होता है यह। इसलिए यह सुनिश्चित करने के लिए भाषा नियम लिखना मुश्किल नहीं है कि सभी मामलों में टाइप सुरक्षा सुरक्षित है (मुख्य नियम यह है कि ArrayStoreExceptionयदि आप ए को बी [] में चिपकाने की कोशिश करते हैं तो फेंक दिया जा सकता है)। एक सामान्य के लिए, हालाँकि, जब आप एक वर्ग की घोषणा करते हैं, तो कक्षा के शरीर में SomeClass<T>किसी भी तरीके Tका उपयोग किया जा सकता है, और मैं अनुमान लगा रहा हूं कि नियमों को लिखने के लिए सभी संभावित संयोजनों को काम करने के लिए यह बहुत जटिल है चीजों की अनुमति है और जब वे नहीं हैं।

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