2013-08-01 11 views
27

यदि आप जावा में एक सामान्य वर्ग बनाते हैं (कक्षा में सामान्य प्रकार पैरामीटर हैं), क्या आप जेनेरिक तरीकों का उपयोग कर सकते हैं (विधि सामान्य प्रकार पैरामीटर लेती है)?जेनेरिक कक्षाओं में जावा जेनेरिक विधियां

public class MyClass { 
    public <K> K doSomething(K k){ 
    return k; 
    } 
} 

public class MyGenericClass<T> { 
    public <K> K doSomething(K k){ 
    return k; 
    } 

    public <K> List<K> makeSingletonList(K k){ 
    return Collections.singletonList(k); 
    } 
} 

आप एक सामान्य विधि के साथ उम्मीद करेंगे के रूप में, मैं doSomething(K)MyClass के उदाहरण पर किसी भी वस्तु के साथ कॉल कर सकते हैं:

निम्नलिखित उदाहरण पर विचार

MyClass clazz = new MyClass(); 
String string = clazz.doSomething("String"); 
Integer integer = clazz.doSomething(1); 

हालांकि, अगर मैं करने की कोशिश MyGenericClassके उदाहरणों का उपयोग के बिना एक सामान्य प्रकार, निर्दिष्ट करते हुए मैं doSomething(K) को Object पर कॉल करता हूं, भले ही क्या K में पारित किया गया था:

MyGenericClass untyped = new MyGenericClass(); 
// this doesn't compile - "Incompatible types. Required: String, Found: Object" 
String string = untyped.doSomething("String"); 

अजीब तरह, यह करता है, तो वापसी प्रकार एक सामान्य वर्ग है संकलन करेगी - उदा List<K> (वास्तव में, यह समझाया जा सकता है - नीचे उत्तर देखें): इसके अलावा

MyGenericClass untyped = new MyGenericClass(); 
List<String> list = untyped.makeSingletonList("String"); // this compiles 

, यह भले ही वाइल्डकार्ड के साथ करता है, तो सामान्य वर्ग के लिखे जाने संकलन होगा,:

MyGenericClass<?> wildcard = new MyGenericClass(); 
String string = wildcard.doSomething("String"); // this compiles 
  • है एक अच्छा कारण है कि एक untyped जेनेरिक वर्ग में एक सामान्य विधि बुला क्यों काम नहीं करना चाहिए?

  • क्या सामान्य वर्गों और जेनेरिक तरीकों से संबंधित कुछ चालाक चाल है जो मुझे याद आ रही है?

संपादित करें:

, स्पष्ट मैं एक untyped या कच्चे टाइप सामान्य वर्ग सामान्य वर्ग के प्रकार पैरामीटर (क्योंकि वे प्रदान की नहीं किया गया है) नहीं सम्मान करने के लिए उम्मीद करेंगे करने के लिए। हालांकि, यह मेरे लिए स्पष्ट नहीं है कि एक अवांछित या कच्ची टाइप वाली जेनेरिक कक्षा का अर्थ यह होगा कि सामान्य तरीकों को सम्मानित नहीं किया जाता है।

यह पारदर्शित करता है कि यह समस्या एसओ, सीएफ पर पहले से ही उठाई जा चुकी है। this question। इसका उत्तर यह बताता है कि जब एक वर्ग अपने कच्चे रूप में/सभी जेनरिक को क्लास से हटा दिया जाता है - जेनेरिक तरीकों के टाइपिंग सहित।

हालांकि, वास्तव में यह कोई स्पष्टीकरण नहीं है कि ऐसा क्यों है। तो मुझे अपने प्रश्न को स्पष्ट करने की अनुमति दें:

  • जावा अनियमित या कच्चे प्रकार के सामान्य वर्गों पर जेनेरिक विधि टाइपिंग क्यों हटाता है? क्या इसके लिए कोई अच्छा कारण है, या यह सिर्फ एक निरीक्षण था?

संपादित करें - JLS की चर्चा:

यह सुझाव दिया है (यह सवाल पिछले तो सवाल करने के लिए और के जवाब में) है कि इस JLS 4.8 में व्यवहार किया जाता है, जिसमें कहा गया है:

एक कन्स्ट्रक्टर (§8.8) का प्रकार, उदाहरण विधि (§8.4, §9.4), या गैर स्थैतिक क्षेत्र (§8।3) एक कच्चे प्रकार सी कि अपनी सुपर-क्लास या superinterfaces से नहीं ली गई है एम कच्चे प्रकार है कि सी के लिए इसी सामान्य घोषणा में अपनी तरह का विलोपन से मेल खाती है है

यह मुझे कैसे इस के लिए स्पष्ट है एक untyped वर्ग से संबंधित है - कक्षा जेनेरिक प्रकार erasure प्रकार के साथ बदल दिया जाता है। यदि कक्षा जेनेरिक बाध्य हैं, तो मिटा प्रकार उन सीमाओं से मेल खाता है। यदि वे बाध्य नहीं हैं, तो मिटा प्रकार ऑब्जेक्ट है - उदा।

// unbound class types 
public class MyGenericClass<T> { 
    public T doSomething(T t) { return t; } 
} 
MyGenericClass untyped = new MyGenericClass(); 
Object t = untyped.doSomething("String"); 

// bound class types 
public class MyBoundedGenericClass<T extends Number> { 
    public T doSomething(T t) { return t; } 
} 
MyBoundedGenericClass bounded = new MyBoundedGenericClass(); 
Object t1 = bounded.doSomething("String"); // does not compile 
Number t2 = bounded.doSomething(1); // does compile 

जबकि सामान्य तरीकों उदाहरण तरीके हैं, यह मेरे लिए स्पष्ट नहीं है कि JLS 4.8 सामान्य तरीकों पर लागू होता है। जेनेरिक विधि का प्रकार (<K> पहले उदाहरण में) untyped नहीं है, क्योंकि यह प्रकार विधि पैरामीटर द्वारा निर्धारित किया जाता है - केवल कक्षा untyped/raw-typed है।

+5

[कच्चे प्रकार के संयोजन और जेनेरिक तरीके] के संभावित डुप्लिकेट (http://stackoverflow.com/questions/11007723/combining-raw-types-and-generic-methods)। यह भी देखें: [यह सामान्य जावा कोड संकलित क्यों नहीं होगा?] (Http://stackoverflow.com/questions/662191/why-wont-this-generic-java-code-compile) –

+0

http://stackoverflow.com/प्रश्न/14882003/जेनेरिक-शिकंजा-अप-गैर-संबंधित संग्रह/14882590 # 14882590 :) – Affe

+0

@PaulBellora - धन्यवाद।पहला बहुत संबंधित है - हालांकि एसओ की मेरी अत्यधिक खोज इसे नहीं मिली, इसलिए संदर्भ के लिए धन्यवाद। दूसरा कम प्रासंगिक है, क्योंकि इसमें केवल सामान्य वर्ग शामिल हैं - जेनेरिक तरीकों से नहीं। – amaidment

उत्तर

7

'पिछड़ा संगतता के लिए' वर्ग जेनेरिक प्रकारों के प्रकार के क्षरण के लिए पर्याप्त कारण लगता है - इसकी आवश्यकता है उदा। आपको एक अनियमित सूची वापस करने और इसे कुछ विरासत कोड में पास करने की अनुमति देने के लिए। जेनेरिक तरीकों के लिए इसका विस्तार एक मुश्किल उप-मामले की तरह लगता है।

4.8 (जिसे आप उद्धृत करते हैं) से जेएलएस स्निपेट कन्स्ट्रक्टर, इंस्टेंस विधियों और सदस्य फ़ील्ड को कवर करता है - जेनेरिक तरीके सामान्य रूप से इंस्टेंस विधियों का एक विशेष मामला है। तो ऐसा लगता है कि आपका केस इस स्निपेट द्वारा कवर किया गया है।

जेएलएस 4 का अनुकूलन।8 इस विशिष्ट मामले में:

एक सामान्य विधि के प्रकार कच्चे प्रकार है कि सामान्य घोषणा में अपनी तरह का विलोपन से मेल खाती है सी

करने के लिए इसी

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

शायद ऐसी समस्याएं हो सकती हैं जहां क्लास जेनेरिक पैरामीटर विधि जेनेरिक पैरामीटर के साथ इंटरैक्ट करते हैं - आपके कोड में वे पूरी तरह से स्वतंत्र हैं, लेकिन आप अन्य मामलों की कल्पना कर सकते हैं जहां उन्हें एक साथ असाइन/मिश्रित किया जाता है। मुझे लगता है कि यह JLS के अनुसार, अनुशंसित नहीं हैं कच्चे प्रकार के उपयोग की ओर इशारा लायक है:

कच्चे प्रकार के उपयोग केवल विरासत कोड की अनुकूलता के लिए एक रियायत के रूप में अनुमति दी है। के बाद लिखे गए कोड में कच्चे प्रकारों का उपयोग जावा प्रोग्रामिंग भाषा में सामान्यता का परिचय दृढ़ता से निराश है। ऐसा नहीं है कि जावा प्रोग्रामिंग भाषा के भविष्य के संस्करणों कच्चे प्रकार के उपयोग की अनुमति नहीं देंगे संभव है

जावा डेवलपर्स के बारे में सोच से कुछ यहाँ स्पष्ट है:

http://bugs.sun.com/view_bug.do?bug_id=6400189

(बग + तय यह दर्शाता है कि इस प्रकार के एरर के प्रयोजनों के लिए विधि के प्रकार के प्रकार को विधि के प्रकार के हिस्से के रूप में माना जाता है)

this request भी है, जहां कोई आपको व्यवहार का अनुरोध करने लगता है वर्णन - केवल वर्ग सामान्य पैरामीटर, नहीं अन्य जेनरिक मिटा - लेकिन यह इस तर्क के साथ खारिज कर दिया गया था:

अनुरोध ताकि प्रकार घोषणा Foo<T> में, विलोपन केवल T पैरामिट्रीकृत प्रकार से निकाल देता है प्रकार विलोपन संशोधित करने के लिए है। फिर, ऐसा होता है कि Map<K,V> की घोषणा के भीतर, Set<Map.Entry<K,V>>Set<Map.Entry> पर मिटा देता है।

लेकिन Map<K,V> में ऐसी विधि थी जिसने Map<String,V> टाइप किया था, इसका क्षरण केवल Map<String> होगा। प्रकार पैरामीटर की संख्या बदलने के लिए टाइप एरर के लिए विशेष रूप से संकलन-समय विधि समाधान के लिए भयानक है। हम पूरी तरह से इस अनुरोध को स्वीकार नहीं करेंगे।

कच्चे प्रकार (Map) का उपयोग करने में सक्षम होने की अपेक्षा करने के लिए बहुत अधिक है, जबकि अभी भी जेनेरिक की कुछ प्रकार की सुरक्षा प्राप्त हो रही है (Set<Map.Entry>)।

+0

मैं सहमत हूं कि पीछे की संगतता एक अच्छा कारण है - यह सिर्फ इतना है कि यह स्पष्ट नहीं है कि इसे प्राप्त करने के लिए _all_ जेनेरिकों का प्रकार क्यों आवश्यक होगा। बग रिपोर्ट के लिए +1, जिसमें इस समस्या को एक बग के रूप में पहचाना जाता है, लेकिन यह तय करने से यह संगतता को प्रभावित करता है (जो कि समानता के लिए जरूरी नहीं है जैसा कि यह संगतता के लिए आवश्यक था ...)। दुर्भाग्यवश ऐसा लगता है कि फिक्स को अभी तक जारी संस्करण में शामिल नहीं किया गया है ... – amaidment

+0

यह अनुरोध भी है: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6256320 जहां कोई अनुरोध करने लगता है आपके द्वारा वर्णित व्यवहार - केवल सामान्य जेनेरिक पैरामीटर मिटाएं, अन्य जेनेरिक नहीं - लेकिन इसे अस्वीकार कर दिया गया था –

+0

+1 अच्छा शोध - आपके उत्तर में लिंक किए गए बग/अनुरोध के मूल्यांकन को उद्धृत करने पर विचार करें। @ मामिडमेंट मैं इस जवाब को बक्षीस के लिए अनुशंसा करता हूं क्योंकि यह भाषा डिजाइनरों के इरादे को जानने के लिए सबसे निकटतम है। –

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

, यदि आप एक सामान्य वर्ग के कच्चे फार्म का उपयोग करें, तो वर्ग पर सभी जेनरिक, जैसे कि आपका makeSingletonList और doSomething तरीके के रूप में भी असंबंधित सामान्य तरीकों, कच्चे हो जाते हैं। जैसा कि मैं समझता हूं, जावा कोड लिखित प्री-जेनरिक के साथ पिछड़ा संगतता प्रदान करना है।

यदि आपके सामान्य प्रकार पैरामीटर T के लिए कोई उपयोग नहीं है, तो इसे MyGenericClass से हटा दें, जिससे K के साथ सामान्य तरीके से आपकी विधियां छोड़ दें। अन्यथा आपको इस तथ्य के साथ रहना होगा कि क्लास टाइप पैरामीटर T कक्षा में किसी और चीज पर जेनेरिक का उपयोग करने के लिए अपनी कक्षा में दिया जाना चाहिए।

+0

धन्यवाद सभी जेनरिक, न सिर्फ सामान्य वर्ग मानकों को मिटाने के लिए एक ज्यादा स्थिरता की तरह लगता है,। उनका उपरोक्त उदाहरण में उपयोग नहीं किया जाता है क्योंकि मैं एक संक्षिप्त एसएससीसीई चाहता था। साथ ही, यह मुझे स्पष्ट नहीं है कि कच्चे वर्ग में सभी जेनेरिकों को मिटाना क्यों पीछे की संगतता सुनिश्चित करता है। – amaidment

+0

आप लगता @amaidment इस इंटरफ़ेस है: 'इंटरफ़ेस सी { कश्मीर foo(); } '। व्यवहार जवाब में बताया गया है सुनिश्चित करता है कि कुछ पूर्व सामान्य 'C' सुरक्षित रूप से स्वचालित रूप से किया जा सकता है पुराने कोड बदलाव के बिना" generified "। अन्यथा 'सी' इंटरफेस के उपयोगकर्ताओं को इस तरह 'foo'' कॉल करना चाहिए:' c। foo() ' – MAnyKey

+0

@MAnyKey - आप, मुझे लगता है कि आप बिंदु नहीं छूटा है किसी भी मामले में एक जवाब के रूप आपका जवाब पोस्ट करने के लिए ... कर सकते हैं। आप 'C' के untyped कार्यान्वयन है, तो न केवल वर्ग जेनरिक (' T') 'Object' (जो उम्मीद की जा करने के लिए है), लेकिन यह भी सामान्य विधि (' कश्मीर foo() ') माना जाएगा को अवांछित माना जाएगा, इसलिए 'foo()' के कार्यान्वयन को 'सार्वजनिक ऑब्जेक्ट foo() {...} 'होना चाहिए, जो मूल प्रश्न पर वापस आता है। – amaidment

0

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

उदाहरण के लिए, इस कोड पर विचार करें:

List list = new ArrayList(); 

इससे इस बीमारी के सामान्य कोड है, और एक बार जेनरिक शुरू किए गए थे, इस के लिए एक कच्चे सामान्य प्रकार समकक्ष के रूप में व्याख्या की गई थी:

List<?> list = new ArrayList<>(); 

क्यों कि ? एक extends या super कीवर्ड नहीं है के बाद यह, यह इस में तब्दील हो जाता:

List<Object> list = new ArrayList<>(); 

कि जेनरिक से पहले इस्तेमाल किया गया था List के संस्करण एक Object इस्तेमाल किया एक सूची तत्व का उल्लेख करने के लिए, और इस संस्करण भी एक का उपयोग करता है एक सूची तत्व का संदर्भ देने के लिए Object, इसलिए पीछे की संगतता बरकरार रखी गई है।

+0

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

+0

4.8 से जेएलएस स्निपेट (आपके द्वारा लिंक किए गए प्रश्न से) कन्स्ट्रक्टर, इंस्टेंस विधियों और सदस्य फ़ील्ड को कवर करता है - जेनेरिक तरीके सामान्य रूप से इंस्टेंस विधियों का एक विशेष मामला है। तो मुझे लगता है कि tbodt का जवाब आपकी स्थिति को कवर करता है? –

+0

'पीछे की संगतता के लिए' पर्याप्त कारण नहीं है? कुछ सोच यहां स्पष्ट है: http://bugs.sun.com/view_bug.do?bug_id=6400189 (बग + यह दिखाता है कि एक विधि के रिटर्न प्रकार को इस प्रकार के मिटाए जाने के प्रयोजनों के लिए विधि के प्रकार के हिस्से के रूप में माना जाता है) –

2

मुझे जेनेरिक को पूरी तरह से त्यागने का एक कारण मिला है (हालांकि यह काफी अच्छा नहीं है)। कारण है: जेनेरिक बाध्य हो सकते हैं। इस वर्ग पर विचार करें:

public static class MyGenericClass<T> { 
    public <K extends T> K doSomething(K k){ 
     return k; 
    } 

    public <K> List<K> makeSingletonList(K k){ 
     return Collections.singletonList(k); 
    } 
} 

संकलक doSomething में जेनरिक त्यागने के लिए है, जब आप जेनरिक बिना वर्ग का उपयोग करें। और मुझे लगता है कि सभी जेनेरिक इस व्यवहार के अनुरूप होने के लिए त्याग दिए जाते हैं।

makeSingletonList संकलित करता है क्योंकि जावा List से List<K> (हालांकि संकलक चेतावनी प्रदर्शित करता है) से एक अनचेक कास्ट करता है।

+0

नहीं - विधि प्रकार ('') वर्ग प्रकार से स्वतंत्र ('') (जब तक अन्यथा विवश, जैसे '' अपने उदाहरण के अनुसार), और एक दूसरे से स्वतंत्र। इस प्रकार हमारे पास कक्षा 'सी <टी हो सकती है' विधि ''विधि' सार्वजनिक टी डूसोमिंग (टी टी) {वापसी टी; } ', और इसे 'नया सी () .do कुछ (" स्ट्रिंग ") कहा जा सकता है। यह संकलित करेगा (लेकिन भविष्य के डेवलपर्स के लिए बहुत स्पष्ट नहीं होगा ...) क्योंकि क्लास जेनेरिक प्रकार के बजाय विधि जेनेरिक प्रकार लिया जाता है, और वे स्वतंत्र होते हैं। – amaidment

+0

यह मैंने लिखा कुछ भी विरोधाभास नहीं करता है। हां, वर्ग प्रकार से स्वतंत्र विधि प्रकार। लेकिन अगर विधि प्रकार वर्ग प्रकारों से बाधित हैं, तो जेनरिक्स के बिना कक्षा का उपयोग करते समय संकलक को इसे सभी को त्यागना होगा। – MAnyKey

+0

+1 विधि प्रकार के उपयोग के लिए एक उदाहरण है कि यह वर्ग प्रकार पर निर्भर करता है, क्योंकि कच्चे प्रकार का उपयोग होने पर जेनिक्स को कक्षा में कहीं भी छोड़ने के लिए पर्याप्त कारण है। – yair

1

इस मौलिक सवाल का जवाब नहीं है, लेकिन क्यों makeSingletonList(...) जबकि doSomething(...) संकलित के मुद्दे का समाधान करता है नहीं करता है:

MyGenericClass के untyped कार्यान्वयन में:

MyGenericClass untyped = new MyGenericClass(); 
List<String> list = untyped.makeSingletonList("String"); 

... बराबर है करने के लिए:

MyGenericClass untyped = new MyGenericClass(); 
List list = untyped.makeSingletonList("String"); 
List<String> typedList = list; 

यह संकलन (कई चेतावनियों के साथ) होगा, लेकिन फिर रनटाइम त्रुटियों के लिए खुला होगा।

MyGenericClass untyped = new MyGenericClass(); 
List<Integer> list = untyped.makeSingletonList("String"); // this compiles!!! 
Integer a = list.get(0); 

जो संकलित लेकिन जब आप कोशिश करते हैं और String मूल्य बाहर निकलना और एक Integer करने के लिए इसे डाली एक क्रम ClassCastException फेंक देंगे ...:

दरअसल, यह भी अनुरूप है।

0

MAnyKeys पर मेरी टिप्पणी से जवाब दें:

मुझे लगता है कि पूर्ण प्रकार विलोपन उल्लेख पीछे की ओर संगतता के साथ संयुक्त समझ में आता है। जब ऑब्जेक्ट जेनेरिक प्रकार तर्कों के बिना बनाया जाता है तो यह माना जा सकता है (या shoudl be?) माना जाता है कि ऑब्जेक्ट का उपयोग गैर जेनेरिक कोड में होता है।

public class MyGenericClass { 
    public Object doSomething(Object k){ 
     return k; 
    } 
} 

इस तरह कहा जाता है:

इस विरासत कोड पर विचार करें

MyGenericClass foo = new MyGenricClass(); 
NotKType notKType = foo.doSomething(new NotKType()); 

अब वर्ग और यह विधि है सामान्य बना रहे हैं:

public class MyGenericClass<T> { 
    public <K extends KType> K doSomething(K k){ 
     return k; 
    } 
} 

अब ऊपर फोन करने वाले कोड wouldn अब NotKType के रूप में संकलित नहीं है KType का एक प्रकार नहीं है। इससे बचने के लिए सामान्य प्रकार Object के साथ प्रतिस्थापित किए जाते हैं। यद्यपि ऐसे मामले हैं जहां इससे कोई फर्क नहीं पड़ता है (जैसे आपका उदाहरण), संकलक के विश्लेषण के लिए कम से कम जटिल होता है। शायद असंभव भी हो सकता है।

मुझे पता है कि यह szenario थोड़ा सा निर्माण लगता है लेकिन मुझे यकीन है कि यह समय-समय पर होता है।

संबंधित मुद्दे