जावा: वाइल्डकार्ड या बाउंडेड टाइप पैरामीटर?


83

हाल ही में, मैंने यह लेख पढ़ा: http://download.oracle.com/javase/tutorial/extra/generics/wcc.html

मेरा प्रश्न इस तरह एक विधि बनाने के बजाय है:

public void drawAll(List<? extends Shape> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

मैं इस तरह से एक विधि बना सकता हूं, और यह ठीक काम करता है:

public <T extends Shape> void drawAll(List<T> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

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


? एक शॉर्टहैंड नोटेशन है; आंतरिक रूप से संकलक इसे किसी भी प्रकार के पैरामीटर के साथ बदल देता है; जब संकलक त्रुटि होती है, तो आप सरोगेट प्रकार के पैरामीटर को देखेंगे, बजाय
18

9
सुधार: मेरी पिछली टिप्पणी गलत है। वाइल्डकार्ड मेरे विचार से अधिक परिष्कृत है।
विवादास्पद

वास्तव में यह अधिक जटिल होने पर @irreputable, एक प्रकार के पैरामीटर के साथ इसे बदलने के बारे में आपकी बात पूरी तरह से वैध है; यह सिर्फ एक है जिसे आप घोषित नहीं कर सकते।
यूजीन

अगर हम पर देखने के सिर्फ इन दोनों तरीकों के रूप में वे कर रहे हैं - वहाँ कोई अंतर नहीं और शैली का मामला है, अन्यथा (यदि आप कुछ और सामान्य पैरामीटर जोड़ते हैं), चीजें बदल जाएंगी
यूजीन

जवाबों:


134

यह आप पर निर्भर करता है क्या जरूरत है क्या करना है। यदि आप कुछ ऐसा करना चाहते हैं, तो आपको बंधे हुए प्रकार के पैरामीटर का उपयोग करना होगा:

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}

यहां हमारे पास एक List<T> shapesऔर एक है T shape, इसलिए हम सुरक्षित रूप से कर सकते हैं shapes.add(shape)। यदि यह घोषित किया गया था List<? extends Shape>, तो आप इसे सुरक्षित रूप से नहीं कर सकते add(क्योंकि आपके पास List<Square>Circle) हो सकता है ।

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

यहां तक ​​कि अगर आप फिर से बंधे हुए प्रकार के पैरामीटर का जिक्र नहीं कर रहे हैं, तो एक बंधे प्रकार के पैरामीटर की आवश्यकता तब भी होती है जब आपके पास कई सीमाएं होती हैं। यहाँ एंजेलिका लैंगर के जावा जेनरिक एफएक्यू से एक उद्धरण दिया गया है

एक वाइल्डकार्ड बाउंड और एक प्रकार के पैरामीटर बाउंड के बीच क्या अंतर है?

एक वाइल्डकार्ड में केवल एक बाउंड हो सकता है, जबकि एक प्रकार के पैरामीटर में कई सीमाएं हो सकती हैं। एक वाइल्डकार्ड में एक निचला या एक ऊपरी बाउंड हो सकता है, जबकि एक प्रकार के पैरामीटर के लिए कम बाउंड जैसी कोई चीज नहीं होती है।

वाइल्डकार्ड सीमा और प्रकार पैरामीटर सीमा अक्सर भ्रमित होती हैं, क्योंकि वे दोनों सीमाएं कहलाती हैं और समान सिंटैक्स में होती हैं। [...]

सिंटैक्स :

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

एक वाइल्डकार्ड में केवल एक बाउंड हो सकता है, या तो कम या ऊपरी बाउंड। वाइल्डकार्ड सीमा की सूची की अनुमति नहीं है।

एक प्रकार का पैरामीटर, बाधा में, कई सीमाएं हो सकती हैं, लेकिन एक प्रकार के पैरामीटर के लिए निचली सीमा जैसी कोई चीज नहीं है।

प्रभावी जावा 2 संस्करण से कोटेशन, आइटम 28: एपीआई लचीलापन बढ़ाने के लिए बंधे वाइल्डकार्ड का उपयोग करें :

अधिकतम लचीलेपन के लिए, इनपुट मापदंडों पर वाइल्डकार्ड प्रकारों का उपयोग करें जो उत्पादकों या उपभोक्ताओं का प्रतिनिधित्व करते हैं। […] PECS का उद्देश्य उत्पादक- extends, उपभोक्ता- super[…]

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

PECS सिद्धांत को लागू करते हुए, हम अब अपने addIfPrettyउदाहरण पर वापस जा सकते हैं और निम्नलिखित लिखकर इसे और अधिक लचीला बना सकते हैं :

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

अब हम addIfPrettyकह सकते हैं, ए Circle, टू, ए List<Object>। यह स्पष्ट रूप से टाइपसेफ़ है, और फिर भी हमारी मूल घोषणा इतनी लचीली नहीं थी कि यह अनुमति दे सके।

संबंधित सवाल


सारांश

  • बंधे हुए प्रकार के मापदंडों / वाइल्डकार्ड का उपयोग करें, वे आपके एपीआई के लचीलेपन को बढ़ाते हैं
  • यदि प्रकार को कई मापदंडों की आवश्यकता होती है, तो आपके पास बाध्य प्रकार पैरामीटर का उपयोग करने के अलावा कोई विकल्प नहीं है
  • यदि इस प्रकार को एक निम्नतर की आवश्यकता है, तो आपके पास बाउंडेड वाइल्डकार्ड का उपयोग करने के अलावा कोई विकल्प नहीं है
  • "प्रोड्यूसर्स" में अपरबाउंड्स होते हैं, "उपभोक्ताओं" में लोअरबाउंड्स होते हैं
  • रिटर्न प्रकारों में वाइल्डकार्ड का उपयोग न करें

बहुत बहुत धन्यवाद, आपका स्पष्टीकरण बहुत स्पष्ट और शिक्षाप्रद है
टोनी ले

1
@ टॉनी: पुस्तक में वाइल्डकार्ड बनाम टाइप पैरामीटर के बीच चयन करने के बारे में भी चर्चा की गई है जब कोई बाध्य न हो। अनिवार्य रूप से, यदि प्रकार पैरामीटर केवल एक बार विधि घोषणा में प्रकट होता है, तो वाइल्डकार्ड का उपयोग करें। reverse(List<?>)JLS से भी उदाहरण देखें java.sun.com/docs/books/jls/third_edition/html/…
polygenel

मुझे निम्नलिखित कोड के लिए UnsupportedOperationException मिल रही है, <code> सार्वजनिक स्थैतिक <T संख्या बढ़ाता है> शून्य जोड़ें (सूची <; सुपर T> सूची, T संख्या) {list.add (num); } </ code>
समरा

1
इसके अलावा - आप इस ?तरह से एक पैरामीटर पैरामीटर के रूप में पारित doWork(? type)नहीं कर सकते क्योंकि तब आप उस पैरामीटर को विधि में उपयोग नहीं कर सकते हैं। आपको टाइप पैरामीटर का उपयोग करने की आवश्यकता है।
टॉमाज़ मूलरस्क

1
@VivekVardhan आप वाइल्डकार्ड का उपयोग करके ऐसी कोई विधि नहीं बना सकते हैं, यही बात है। जब निर्णय लेना है कि किसका उपयोग करना है, यह समझाने वाले सर्वोत्तम स्रोतों में से एक है।
यूजीन

6

अपने उदाहरण में आपको वास्तव में टी का उपयोग करने की आवश्यकता नहीं है, क्योंकि आप उस प्रकार का उपयोग कहीं और नहीं करते हैं।

लेकिन अगर आपने कुछ ऐसा किया है:

public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
    T s = shapes.get(0);
    s.draw(this);
    return s;
}

या जैसे पॉलीजेन लुब्रिकेंट ने कहा, यदि आप सूची में टाइप पैरामीटर को दूसरे प्रकार के पैरामीटर के साथ मैच करना चाहते हैं:

public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
    List<T> mergedList = new ArrayList<T>();
    mergedList.addAll(shapes1);
    mergedList.addAll(shapes2);
    for (Shape s: mergedList) {
        s.draw(this);
    }
}

पहले उदाहरण में आपको थोड़ी अधिक प्रकार की सुरक्षा मिलती है, फिर बस शेप में लौटते हुए, क्योंकि आप तब एक फ़ंक्शन का परिणाम पास कर सकते हैं जो शेप का बच्चा ले सकता है। उदाहरण के लिए आप List<Square>मेरी विधि को पास कर सकते हैं , और फिर परिणामी स्क्वायर को एक विधि से गुजार सकते हैं जो केवल वर्ग लेता है। अगर आपने '?' आपको परिणामी आकृति को स्क्वायर में डालना होगा जो सुरक्षित नहीं होगा।

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


मेरा मानना ​​है कि Shape s = ...होना चाहिए T s = ..., अन्यथा return s;संकलन नहीं होना चाहिए।
पॉलीजेन लुब्रिकेंट

1

जेम्स गोसलिंग 4 वें संस्करण के जावा प्रोग्रामिंग से नीचे दिए गए उदाहरण पर विचार करें जहां हम 2 सिंगनलिंक्यू को मर्ज करना चाहते हैं:

public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
    // merge s element into d
}

public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
        // merge s element into d
}

उपरोक्त दोनों विधियों में समान कार्यक्षमता है। तो कौन सा बेहतर है? उत्तर 2 एक है। लेखक के अपने शब्दों में:

"सामान्य नियम वाइल्डकार्ड का उपयोग करना है जब आप कर सकते हैं क्योंकि वाइल्डकार्ड के साथ कोड आमतौर पर कई प्रकार के मापदंडों के साथ कोड की तुलना में अधिक पठनीय होता है। यदि आपको किसी प्रकार के चर की आवश्यकता होती है, तो यह निर्णय लेते हुए कि स्वयं से पूछें कि क्या टाइप चर का उपयोग दो या अधिक मापदंडों से संबंधित है। या रिटर्न प्रकार के साथ एक पैरामीटर प्रकार से संबंधित करने के लिए। यदि उत्तर नहीं है, तो एक वाइल्डकार्ड पर्याप्त होना चाहिए। "

नोट: पुस्तक में केवल दूसरी विधि दी गई है और 'T' के बजाय टाइप पैरामीटर नाम S है। पहला तरीका किताब में नहीं है।


1

जहां तक ​​मैं समझता हूं, वाइल्डकार्ड उन परिस्थितियों में अधिक संक्षिप्त कोड की अनुमति देता है जहां एक प्रकार के पैरामीटर की आवश्यकता नहीं होती है (उदाहरण के लिए, क्योंकि यह कई स्थानों पर संदर्भित है या क्योंकि अन्य उत्तरों में विस्तृत रूप में कई सीमाएं आवश्यक हैं)।

आपके द्वारा दिए गए लिंक में मैं पढ़ता हूं ("जेनेरिक मेथड्स के तहत") निम्नलिखित कथन जो इस दिशा में संकेत करते हैं:

जेनेरिक विधियाँ एक या अधिक प्रकार के तर्कों के बीच निर्भरता को व्यक्त करने के लिए एक प्रकार के मापदंडों का उपयोग करने की अनुमति देती हैं और / या इसके वापसी प्रकार को। यदि इस तरह की निर्भरता नहीं है, तो एक सामान्य विधि का उपयोग नहीं किया जाना चाहिए।

[...]

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

[...]

वाइल्डकार्ड का यह भी लाभ है कि उनका उपयोग विधि हस्ताक्षरों के बाहर, खेतों के प्रकार, स्थानीय चर और सरणियों के रूप में किया जा सकता है।


0

दूसरा तरीका थोड़ा और क्रिया है, लेकिन यह आपको Tइसके अंदर संदर्भित करने की अनुमति देता है:

for (T shape : shapes) {
    ...
}

बस इतना ही फर्क है, जहां तक ​​मैं समझता हूं।

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