विधि में एक और विधि के रूप में एक ही erasure है


381

एक ही कक्षा में निम्नलिखित दो विधियाँ होना कानूनी क्यों नहीं है?

class Test{
   void add(Set<Integer> ii){}
   void add(Set<String> ss){}
}

मुझे मिलता है compilation error

मेथड ऐड (सेट) में एक ही इरेज़र ऐड (सेट) होता है जो टाइप टेस्ट में दूसरी विधि के रूप में होता है।

जब मैं इसके आसपास काम कर सकता हूं, मैं सोच रहा था कि जेवैक को यह पसंद क्यों नहीं है।

मैं देख सकता हूं कि कई मामलों में, उन दो तरीकों का तर्क बहुत समान होगा और एक एकल द्वारा प्रतिस्थापित किया जा सकता है

public void add(Set<?> set){}

विधि, लेकिन यह हमेशा मामला नहीं होता है।

यह अतिरिक्त कष्टप्रद है यदि आप constructorsउन दो को लेना चाहते हैं जो उन तर्कों को लेते हैं क्योंकि तब आप उनमें से किसी एक का नाम नहीं बदल सकते constructors


1
क्या होगा यदि आप डेटा संरचनाओं से बाहर निकलते हैं और आपको अभी भी अधिक संस्करणों की आवश्यकता है?
ओमि यदन

1
आप कस्टम वर्जन बना सकते हैं जो बेस संस्करणों से विरासत में मिला है।
विल्मा

1
ओपी, क्या आप कंस्ट्रक्टर समस्या के कुछ समाधान के साथ आए थे? मुझे दो प्रकारों को स्वीकार करने की आवश्यकता है Listऔर मुझे नहीं पता कि इसे कैसे संभालना है।
टॉम ज़ातो -

9
जावा के साथ काम करते समय, मुझे वास्तव में C # ...
Svish

1
@ TomášZato, मैंने उस निर्माणकर्ता के लिए डमी परम को जोड़कर हल किया: बूलियन noopSignatureOverload।
Nthalk

जवाबों:


357

यह नियम विरासत कोड में संघर्ष से बचने के लिए है जो अभी भी कच्चे प्रकार का उपयोग करता है।

यहाँ एक दृष्टांत दिया गया है कि यह अनुमति क्यों नहीं दी गई, जेएलएस से खींची गई। मान लीजिए, जेनेरिक को जावा में पेश किए जाने से पहले, मैंने कुछ कोड इस तरह लिखे:

class CollectionConverter {
  List toList(Collection c) {...}
}

आप इस तरह मेरी कक्षा का विस्तार करें:

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}

जेनरिक की शुरुआत के बाद, मैंने अपनी लाइब्रेरी को अपडेट करने का फैसला किया।

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}

आप कोई भी अपडेट करने के लिए तैयार नहीं हैं, इसलिए आप अपनी Overriderकक्षा को अकेले छोड़ देते हैं। ठीक से ओवरराइड करने के लिएtoList()विधि , भाषा डिजाइनरों ने फैसला किया कि एक कच्चा प्रकार किसी भी उत्पन्न प्रकार के लिए "ओवरराइड-समतुल्य" था। इसका मतलब यह है कि यद्यपि आपकी विधि हस्ताक्षर अब औपचारिक रूप से मेरे सुपरक्लास हस्ताक्षर के बराबर नहीं है, फिर भी आपकी विधि ओवरराइड होती है।

अब, समय बीतता है और आप तय करते हैं कि आप अपनी कक्षा को अपडेट करने के लिए तैयार हैं। लेकिन आप थोड़ा पेंच करते हैं, और मौजूदा, कच्ची toList()विधि को संपादित करने के बजाय , आप इस तरह एक नई विधि जोड़ते हैं :

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}

कच्चे प्रकार के ओवरराइड तुल्यता के कारण, toList(Collection<T>)विधि को ओवरराइड करने के लिए दोनों विधियां एक वैध रूप में हैं । लेकिन निश्चित रूप से, कंपाइलर को एकल विधि को हल करने की आवश्यकता है। इस अस्पष्टता को खत्म करने के लिए, वर्गों को कई तरीकों की अनुमति नहीं है जो कि ओवरराइड-समतुल्य हैं - अर्थात्, एक ही पैरामीटर प्रकारों के साथ कई विधियाँ इरेज़र के बाद होती हैं।

कुंजी यह है कि यह एक भाषा नियम है जिसे कच्चे प्रकारों का उपयोग करके पुराने कोड के साथ संगतता बनाए रखने के लिए डिज़ाइन किया गया है। यह किसी प्रकार के मापदंडों के उन्मूलन के लिए आवश्यक सीमा नहीं है; क्योंकि विधि-निर्धारण संकलन-समय पर होता है, इसलिए विधि पहचानकर्ता के लिए सामान्य प्रकार जोड़ना पर्याप्त होता।


3
शानदार जवाब और उदाहरण! मुझे यकीन नहीं है, हालांकि, अगर मैं आपके अंतिम वाक्य को पूरी तरह से समझता हूं ("क्योंकि विधि का समाधान संकलन-समय पर होता है, तो मिटाने से पहले, इस काम को करने के लिए प्रकार के संशोधन की आवश्यकता नहीं होती है")। क्या आप थोड़ा विस्तार कर सकते हैं?
जोनास आयशर

2
समझ में आता है। मैंने अभी कुछ समय टेंपरेचर मेथड्स में टाइप रिवीजन के बारे में सोचने में बिताया, लेकिन हां: कंपाइलर सुनिश्चित करता है कि टाइप इरिटेशन से पहले सही मेथड सिलेक्ट हो जाए। सुंदर। यदि यह विरासत कोड संगतता मुद्दों से प्रभावित नहीं थे।
जोनास आयशर

1
@daveloyall नहीं, मुझे ऐसे किसी विकल्प की जानकारी नहीं है javac
इरकसन

13
पहली बार जब मैंने जावा त्रुटि का सामना किया जो कि कोई त्रुटि नहीं है और इसे संकलित किया जा सकता है यदि केवल जावा के लेखक चेतावनी का उपयोग करते हैं जैसा कि हर कोई करता है। केवल वे सोचते हैं कि वे सब कुछ बेहतर जानते हैं।
टॉम ज़ातो -

1
@ TomášZato नहीं, मैं उस के लिए किसी भी स्वच्छ workarounds का पता नहीं है। यदि आप कुछ गंदा चाहते हैं, तो आप पास कर सकते हैं List<?>और कुछ enumऐसे हैं जो आप संकेत प्रकार को परिभाषित करते हैं। प्रकार-विशिष्ट तर्क वास्तव में एनम पर एक विधि में हो सकता है। वैकल्पिक रूप से, आप दो अलग-अलग निर्माणकर्ताओं के साथ एक वर्ग के बजाय, दो अलग-अलग कक्षाएं बनाना चाहते हैं। सामान्य तर्क एक सुपरक्लास या एक सहायक वस्तु होगी जिसमें दोनों प्रकार के प्रतिनिधि होते हैं।
इरिकसन

117

जावा जेनरिक टाइप इरेज़र का उपयोग करता है। कोण कोष्ठक ( <Integer>और <String>) में बिट हटा दिया जाता है, इसलिए आप एक समान हस्ताक्षर वाले दो तरीकों के साथ समाप्त करेंगे ( add(Set)आप त्रुटि में देखते हैं)। इसकी अनुमति नहीं है क्योंकि रनटाइम को नहीं पता होगा कि प्रत्येक मामले के लिए किसका उपयोग करना है।

यदि जावा को कभी भी जेनरिक खोज मिलती है, तो आप ऐसा कर सकते हैं, लेकिन यह अब संभव नहीं है।


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

5
@Stilgar प्रतिबिंब के माध्यम से बुलाया या निरीक्षण किया जा रहा विधि को रोकने के लिए क्या है? Class.getMethods () द्वारा लौटाए गए तरीकों की सूची में दो समान तरीके होंगे, जो समझ में नहीं आएंगे।
एड्रियन मौट

5
प्रतिबिंब की जानकारी में जेनेटिक्स के साथ काम करने के लिए आवश्यक मेटाडेटा शामिल हो सकता है। यदि आप पहले से संकलित लाइब्रेरी आयात करते हैं तो जावा कंपाइलर सामान्य तरीकों के बारे में कैसे नहीं जानता है?
स्टिलगर

4
फिर गेटमैथोड विधि को ठीक करने की आवश्यकता है। उदाहरण के लिए एक अधिभार परिचय जो सामान्य अधिभार निर्दिष्ट करता है और मूल विधि को गैर-जेनेरिक संस्करण ही लौटाता है, जेनेरिक के रूप में एनोटेट किए गए किसी भी तरीके को वापस नहीं करता है। बेशक यह संस्करण 1.5 में किया जाना चाहिए था। यदि वे ऐसा करते हैं, तो वे विधि की पिछड़ी संगतता को तोड़ देंगे। मैं अपने कथन के साथ खड़ा हूं कि इस प्रकार का व्यवहार इस व्यवहार को निर्धारित नहीं करता है। यह कार्यान्वयन है कि सीमित संसाधनों के कारण संभवतः पर्याप्त काम नहीं मिला।
स्टिलगर

1
यह एक सटीक उत्तर नहीं है, लेकिन यह जल्दी से एक उपयोगी कथा में इस मुद्दे को जोड़ देता है: विधि हस्ताक्षर बहुत समान हैं, कंपाइलर अंतर नहीं बता सकता है, और फिर आपको "अनसुलझे संकलन समस्याएं" मिलेंगी।
worc

46

ऐसा इसलिए है क्योंकि Java Generics को Type Erasure के साथ कार्यान्वित किया जाता है ।

आपके तरीकों का संकलन, संकलन समय पर, कुछ इस तरह किया जाएगा:

विधि समाधान संकलन समय पर होता है और प्रकार के मापदंडों पर विचार नहीं करता है। ( इरिकसन का जवाब देखें )

void add(Set ii);
void add(Set ss);

दोनों विधियों में समान मापदंडों के बिना एक ही हस्ताक्षर है, इसलिए त्रुटि।


21

समस्या यह है कि है Set<Integer>और Set<String>वास्तव में एक के रूप में इलाज कर रहे हैं SetJVM से। सेट के लिए एक प्रकार (आपके मामले में स्ट्रिंग या पूर्णांक) का चयन करना केवल संकलक द्वारा उपयोग की जाने वाली चीनी है। JVM Set<String>और के बीच अंतर नहीं कर सकता Set<Integer>


7
यह सच है कि जेवीएम रनटाइम में प्रत्येक को अलग करने के लिए कोई जानकारी नहीं है Set, लेकिन चूंकि विधि संकल्प संकलन-समय पर होता है, जब आवश्यक जानकारी उपलब्ध होती है, यह प्रासंगिक नहीं है। समस्या यह है कि इन अधिभारों को कच्चे प्रकार के भत्ते के साथ संघर्ष करना होगा, इसलिए उन्हें जावा सिंटैक्स में अवैध बना दिया गया था।
इरिकसन

@erickson यहां तक ​​कि जब कंपाइलर को पता है कि कॉल करने का कौन सा तरीका है, तो यह बाइटकोड में नहीं हो सकता है, वे दोनों बिल्कुल एक जैसे दिखते हैं। आपको यह बदलने की आवश्यकता होगी कि एक विधि कॉल कैसे निर्दिष्ट की जाती है, क्योंकि (Ljava/util/Collection;)Ljava/util/List;यह काम नहीं करता है। आप उपयोग कर सकते हैं (Ljava/util/Collection<String>;)Ljava/util/List<String>;, लेकिन यह एक असंगत परिवर्तन है और आप उन स्थानों पर अकल्पनीय समस्याओं में भाग लेंगे जहां आपके पास सभी मिट चुके प्रकार हैं। आपको संभवतः पूरी तरह से क्षरण छोड़ना होगा, लेकिन यह बहुत जटिल है।
Maaartinus

@maaartinus हाँ, मैं मानता हूँ कि आपको विधि विनिर्देशक बदलना होगा। मैं कुछ ऐसी अनचाही समस्याओं के बारे में जानने की कोशिश कर रहा हूं जिसके कारण उन्हें उस प्रयास को छोड़ना पड़ा।
इरिकसन

7

एक एकल विधि को परिभाषित करें जैसे प्रकार के बिना void add(Set ii){}

आप अपनी पसंद के आधार पर विधि को कॉल करते समय प्रकार का उल्लेख कर सकते हैं। यह किसी भी प्रकार के सेट के लिए काम करेगा।


3

यह संभव हो सकता है कि कंपाइलर सेट (इंटेगर) को जावा बाइट कोड में सेट (ऑब्जेक्ट) में तब्दील कर दे। यदि यह स्थिति है, तो सेट (पूर्णांक) का उपयोग केवल वाक्यविन्यास जाँच के लिए संकलन चरण में किया जाएगा।


5
यह तकनीकी रूप से सिर्फ कच्चा प्रकार है Set। बाइट कोड में जेनरिक मौजूद नहीं है, वे ढलाई के लिए वाक्य रचना चीनी हैं और संकलन-समय की सुरक्षा प्रदान करते हैं।
आंद्रेज डॉयल

1

मैं इस में टकरा गया जब कुछ लिखने की कोशिश की: Continuable<T> callAsync(Callable<T> code) {....} और Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} वे संकलक के लिए 2 परिभाषा बन जाते हैं Continuable<> callAsync(Callable<> veryAsyncCode) {...}

प्रकार erasure का शाब्दिक अर्थ है, जेनेरिक से टाइप तर्कों की जानकारी को मिटाना। यह बहुत कष्टप्रद है, लेकिन यह एक सीमा है जो कुछ समय के लिए जावा के साथ रहेगी। कंस्ट्रक्टर्स के मामले में बहुत कुछ नहीं किया जा सकता है, उदाहरण के लिए कंस्ट्रक्टर में विभिन्न मापदंडों के साथ 2 नए उपवर्ग। या इसके बदले आरंभीकरण के तरीकों का उपयोग करें ... (वर्चुअल कंस्ट्रक्टर?) विभिन्न नामों के साथ ...

इसी तरह के ऑपरेशन के तरीकों के लिए नाम बदलने में मदद मिलेगी, जैसे

class Test{
   void addIntegers(Set<Integer> ii){}
   void addStrings(Set<String> ss){}
}

या कुछ और वर्णनात्मक नामों के साथ, oyu मामलों के लिए स्व-दस्तावेजीकरण, जैसे addNamesऔर addIndexesऐसे।

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