जावा जेनरिक को कब आवश्यकता होती है <? <T> के बजाय T> का विस्तार होता है और क्या स्विचिंग का कोई नकारात्मक पहलू है?


205

निम्नलिखित उदाहरण को देखते हुए (Hamcrest मैचर्स के साथ JUnit का उपयोग करके):

Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));  

यह JUnit assertThatविधि हस्ताक्षर के साथ संकलित नहीं है:

public static <T> void assertThat(T actual, Matcher<T> matcher)

संकलक त्रुटि संदेश है:

Error:Error:line (102)cannot find symbol method
assertThat(java.util.Map<java.lang.String,java.lang.Class<java.util.Date>>,
org.hamcrest.Matcher<java.util.Map<java.lang.String,java.lang.Class
    <? extends java.io.Serializable>>>)

हालाँकि, यदि मैं assertThatविधि हस्ताक्षर को इसमें परिवर्तित करता हूँ :

public static <T> void assertThat(T result, Matcher<? extends T> matcher)

फिर संकलन काम करता है।

तो तीन प्रश्न:

  1. वर्तमान संस्करण संकलित क्यों नहीं करता है? हालाँकि, मैं यहाँ के सहसंयोजक मुद्दों को पूरी तरह से समझता हूँ, अगर मैं था तो मैं निश्चित रूप से इसे समझा नहीं सकता था।
  2. क्या assertThatविधि को बदलने में कोई नकारात्मक पहलू है Matcher<? extends T>? क्या अन्य मामले हैं जो अगर आपने किए तो वे टूट जाएंगे?
  3. क्या assertThatJUnit में विधि के सामान्यीकरण का कोई मतलब है ? Matcherवर्ग के रूप में, यह आवश्यकता प्रतीत नहीं होता है के बाद से JUnit मैचों विधि है, जो एक प्रकार सुरक्षा जो कुछ नहीं करता मजबूर करने की कोशिश की तरह किसी भी सामान्य, और सिर्फ दिखता साथ टाइप नहीं है कहता है Matcherवास्तव में सिर्फ नहीं होगा मैच, और परीक्षण की परवाह किए बिना विफल हो जाएगा। कोई असुरक्षित ऑपरेशन शामिल नहीं है (या ऐसा लगता है)।

संदर्भ के लिए, यहां JUnit कार्यान्वयन है assertThat:

public static <T> void assertThat(T actual, Matcher<T> matcher) {
    assertThat("", actual, matcher);
}

public static <T> void assertThat(String reason, T actual, Matcher<T> matcher) {
    if (!matcher.matches(actual)) {
        Description description = new StringDescription();
        description.appendText(reason);
        description.appendText("\nExpected: ");
        matcher.describeTo(description);
        description
            .appendText("\n     got: ")
            .appendValue(actual)
            .appendText("\n");

        throw new java.lang.AssertionError(description.toString());
    }
}

यह लिंक बहुत उपयोगी है (जेनेरिक, इनहेरिटेंस और सबटाइप
जाफरी

जवाबों:


145

पहला - मुझे आपको http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html पर निर्देशित करना होगा - वह एक अद्भुत काम करती है।

मूल विचार यह है कि आप उपयोग करते हैं

<T extends SomeClass>

जब वास्तविक पैरामीटर हो सकता है SomeClassया इसका कोई भी उपप्रकार हो सकता है।

आपके उदाहरण में,

Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));

आप कह रहे हैं कि expectedइसमें ऐसी क्लास ऑब्जेक्ट्स हो सकती हैं जो किसी भी क्लास का प्रतिनिधित्व करती हैं जो लागू करता है Serializable। आपका परिणाम मानचित्र कहता है कि यह केवल पकड़ कर रख सकता हैDate कक्षा वस्तुओं को ।

जब आप परिणाम में पास, आप सेटिंग Tबिल्कुल Mapके Stringलिए Dateवर्ग वस्तुओं, जो मेल नहीं खाता Mapका Stringकुछ भी है कि के लिए Serializable

जाँच करने के लिए एक बात - क्या आप सुनिश्चित हैं कि आप चाहते हैं Class<Date>और नहीं Date? का एक नक्शा Stringकरने के लिए Class<Date>सामान्य रूप में बहुत उपयोगी नहीं लग रहा है (सभी इसे पकड़ कर सकते हैं Date.classके उदाहरण के बजाय मूल्यों के रूप में Date)

जेनेरिक करने के लिए assertThat, विचार यह है कि विधि यह सुनिश्चित कर सकती है Matcherकि परिणाम प्रकार में फिट होने वाला पास हो।


इस मामले में, हाँ मुझे कक्षाओं का एक नक्शा चाहिए। मैंने जो उदाहरण दिया, वह मेरे कस्टम वर्गों के बजाय मानक JDK वर्गों का उपयोग करने से वंचित है, लेकिन इस मामले में वर्ग वास्तव में प्रतिबिंब के माध्यम से त्वरित है और कुंजी के आधार पर उपयोग किया जाता है। (एक वितरित ऐप जहां क्लाइंट के पास सर्वर कक्षाएं उपलब्ध नहीं हैं, बस सर्वर साइड काम करने के लिए किस क्लास की कुंजी है)।
यिशै

6
मुझे लगता है कि मेरा दिमाग जहां पर अटका हुआ है, वहां एक मानचित्र क्यों टाइप की कक्षाओं से युक्त है, जो सिर्फ एक प्रकार के मैप्स में फिट नहीं होता है जिसमें टाइप सीरियल के वर्ग हैं। निश्चित रूप से प्रकारों के वर्ग अन्य प्रकार के भी हो सकते हैं, लेकिन इसमें निश्चित रूप से दिनांक शामिल है।
यिशै

यह सुनिश्चित करने पर कि कलाकारों को आपके लिए प्रदर्शन किया जाता है, माचिस माचिस () विधि ध्यान नहीं देती है, इसलिए चूंकि टी का उपयोग कभी नहीं किया जाता है, इसलिए इसे शामिल क्यों करें? (विधि वापसी प्रकार शून्य है)
यशाई

आह्ह - यही मुझे मुखरता के दोष को नहीं पढ़ने के लिए मिलता है। ऐसा लगता है कि यह केवल यह सुनिश्चित करने के लिए है कि एक फिटिंग माचिस को पारित किया जाता है ...
स्कॉट स्टैनफिल्ड

28

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

मैं सूची के संदर्भ में प्रश्न को बहाल करने जा रहा हूं, क्योंकि इसमें केवल एक सामान्य पैरामीटर है और इससे समझने में आसानी होगी।

पैराट्राइज्ड क्लास का उद्देश्य (जैसे कि उदाहरण में सूची <Date>या मानचित्र <K, V>) एक डाउनकास्ट को मजबूर करना और संकलक की गारंटी है कि यह सुरक्षित है (कोई रनटाइम अपवाद नहीं)।

सूची के मामले पर विचार करें। मेरे प्रश्न का सार यह है कि एक विधि जो T टाइप करती है और एक सूची T की तुलना में वंशानुक्रम की श्रृंखला के नीचे कुछ की सूची को स्वीकार नहीं करेगी। इस वंचित उदाहरण पर विचार करें:

List<java.util.Date> dateList = new ArrayList<java.util.Date>();
Serializable s = new String();
addGeneric(s, dateList);

....
private <T> void addGeneric(T element, List<T> list) {
    list.add(element);
}

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

यही बात मैप पर लागू होती है। <String, Class<? extends Serializable>>यह मैप के समान नहीं है <String, Class<java.util.Date>>। वे सहसंयोजक नहीं हैं, इसलिए अगर मैं तारीख वर्गों वाले नक्शे से एक मूल्य लेना चाहता था और इसे अनुक्रमिक तत्वों वाले मानचित्र में डाल दिया, तो यह ठीक है, लेकिन एक विधि हस्ताक्षर जो कहता है:

private <T> void genericAdd(T value, List<T> list)

दोनों करने में सक्षम होना चाहता है:

T x = list.get(0);

तथा

list.add(value);

इस मामले में, भले ही कनिष्ठ विधि वास्तव में इन चीजों की परवाह नहीं करती है, लेकिन विधि हस्ताक्षर के लिए सहसंयोजक की आवश्यकता होती है, जिसे यह नहीं मिल रहा है, इसलिए यह संकलन नहीं करता है।

दूसरे सवाल पर,

Matcher<? extends T>

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

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

EDIT (आगे के चिंतन और अनुभव के बाद):

AssertThat विधि हस्ताक्षर के साथ बड़े मुद्दों में से एक T के सामान्य पैरामीटर के साथ एक चर T को समान करने का प्रयास है। यह काम नहीं करता है, क्योंकि वे सहसंयोजक नहीं हैं। इसलिए उदाहरण के लिए आपके पास एक T हो सकता है, List<String>लेकिन फिर एक ऐसा मैच पास करें जिसके लिए कंपाइलर काम करता है Matcher<ArrayList<T>>। अब अगर यह एक प्रकार का पैरामीटर नहीं था, तो चीजें ठीक हो जाएंगी, क्योंकि सूची और एरियर लाईट सहसंयोजक हैं, लेकिन चूंकि जेनरिक, जहां तक ​​कंपाइलर का संबंध है, एरियर लाईटर की आवश्यकता है, यह उन कारणों के लिए एक सूची बर्दाश्त नहीं कर सकता है, जो मुझे आशा है कि स्पष्ट है ऊपर में से।


मुझे अभी भी नहीं मिलता है कि मैं क्यों नहीं बना सकता। मैं दिनांक की सूची को क्रमबद्ध करने की सूची में क्यों नहीं बदल सकता?
थॉमस अहले

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

मैं देखता हूं, लेकिन क्या होगा अगर मुझे किसी तरह पुराने संदर्भ से छुटकारा मिल गया, जैसे कि मैं List<Date>एक विधि से टाइप के साथ लौटा हूं List<Object>? यह सुरक्षित होना चाहिए, भले ही जावा द्वारा अनुमति न हो।
थॉमस अहले

14

यह करने के लिए नीचे फोड़े:

Class<? extends Serializable> c1 = null;
Class<java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date

आप देख सकते हैं कि क्लास संदर्भ c1 में एक लंबा उदाहरण हो सकता है (चूंकि किसी निश्चित समय पर अंतर्निहित वस्तु हो सकती है List<Long>), लेकिन स्पष्ट रूप से किसी तिथि को नहीं डाला जा सकता है क्योंकि कोई गारंटी नहीं है कि "अज्ञात" वर्ग दिनांक था। यह टाइपसेफ़ नहीं है, इसलिए कंपाइलर इसे बंद कर देता है।

हालाँकि, यदि हम किसी अन्य वस्तु का परिचय देते हैं, तो सूची (आपके उदाहरण में यह वस्तु माचिस है) कहते हैं, तो निम्न सत्य हो जाता है:

List<Class<? extends Serializable>> l1 = null;
List<Class<java.util.Date>> l2 = null;
l1 = l2; // wont compile
l2 = l1; // wont compile

... हालाँकि, यदि सूची का प्रकार बन जाता है? T के बजाय T को फैलाता है ...।

List<? extends Class<? extends Serializable>> l1 = null;
List<? extends Class<java.util.Date>> l2 = null;
l1 = l2; // compiles
l2 = l1; // won't compile

मुझे लगता है कि बदलकर Matcher<T> to Matcher<? extends T>, आप मूल रूप से l1 = l2 असाइन करने के समान परिदृश्य पेश कर रहे हैं;

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


9

आपके मूल कोड के संकलन का कारण <? extends Serializable>यह नहीं है मतलब है, "कोई भी वर्ग जो Serializable का विस्तार करता है," लेकिन "कुछ अज्ञात लेकिन विशिष्ट वर्ग जो Serializable का विस्तार करता है।"

उदाहरण के लिए, कोड को लिखित रूप में दिया गया है, यह असाइन new TreeMap<String, Long.class>()>करने के लिए पूरी तरह से मान्य है expected। यदि संकलक कोड को संकलित करने की अनुमति देता है, तो assertThat()निश्चित रूप से टूट जाएगा क्योंकि यह Dateऑब्जेक्ट्स के बजाय ऑब्जेक्ट्स से अपेक्षा करेगा कि Longयह मानचित्र में मिल जाए।


1
मैं काफी फॉलो नहीं कर रहा हूं - जब आप कहते हैं "मतलब नहीं है ... लेकिन ...": क्या अंतर है? (जैसे, "ज्ञात लेकिन निरर्थक" वर्ग का एक उदाहरण क्या होगा जो पूर्व परिभाषा को फिट करता है, लेकिन बाद का नहीं?)
पाउंडिफ़डे

हां, यह थोड़ा अजीब है; सुनिश्चित नहीं है कि इसे बेहतर तरीके से कैसे व्यक्त किया जाए ... क्या यह कहना अधिक समझ में आता है, "'?" एक प्रकार है जो अज्ञात है, एक प्रकार है जो कुछ भी मेल खाता है? "
इरिकसन

1
यह समझाने में क्या मदद मिल सकती है कि "किसी भी वर्ग के लिए जो सीरियल को विस्तार देता है" आप बस इस्तेमाल कर सकते हैं<Serializable>
c0der

8

वाइल्डकार्ड को समझने का मेरे लिए एक तरीका यह है कि वाइल्डकार्ड संभावित वस्तुओं के प्रकार को निर्दिष्ट नहीं कर रहा है, जो जेनेरिक संदर्भ "दिया" हो सकता है, लेकिन अन्य जेनेरिक संदर्भों का प्रकार जो इसके साथ संगत है (यह भ्रामक लग सकता है) ...) इस तरह, इसका पहला उत्तर बहुत ही भ्रामक है।

दूसरे शब्दों में, List<? extends Serializable>इसका मतलब यह है कि आप अन्य सूचियों के संदर्भ को निर्दिष्ट कर सकते हैं जहां प्रकार कुछ अज्ञात प्रकार है जो कि क्रमिक का उपवर्ग है। ए सिंगिंग लिस्ट के संदर्भ में यह मत सोचिए कि वह धारावाहिकों के उपवर्गों को धारण करने में सक्षम है (क्योंकि यह गलत शब्दार्थ है और जेनरिक की गलतफहमी की ओर ले जाता है)।


यह निश्चित रूप से मदद करता है, लेकिन "ध्वनि भ्रामक हो सकता है" एक तरह से "ध्वनि भ्रामक लगता है।" अनुवर्ती के रूप में, इसलिए, इस स्पष्टीकरण के अनुसार, मिलानकर्ता के साथ विधि <? extends T>संकलित करता है?
यिशै

अगर हम लिस्ट <Serializable> के रूप में परिभाषित करते हैं, तो यह वही काम करता है? मैं आपके 2nd पैरा के बारे में बात कर रहा हूं। यानी बहुरूपता इसे संभाल लेगा?
सुपुन विजेरथने

3

मुझे पता है कि यह एक पुराना सवाल है, लेकिन मैं एक उदाहरण साझा करना चाहता हूं जो मुझे लगता है कि बहुत अच्छी तरह से बाउंडेड वाइल्डकार्ड की व्याख्या करता है। java.util.Collectionsइस विधि प्रदान करता है:

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

यदि हमारे पास एक सूची है T, तो सूची, निश्चित रूप से, प्रकार के उदाहरण हैं, जो विस्तार करते हैं T। यदि सूची में पशु हैं, तो सूची में कुत्ते और बिल्ली (दोनों पशु) शामिल हो सकते हैं। कुत्तों के पास एक संपत्ति है "woofVolume" और बिल्लियों के पास एक संपत्ति है "meowVolume।" जबकि हम इन उपवर्गों के विशेष गुणों के आधार पर छाँटना पसंद Tकर सकते हैं, हम इस विधि की अपेक्षा कैसे कर सकते हैं? तुलनित्र की एक सीमा यह है कि यह केवल एक ही प्रकार ( T) के केवल दो चीजों की तुलना कर सकता है । तो, बस आवश्यकता होती है Comparator<T>इस विधि को प्रयोग करने योग्य बनाया जाएगा। लेकिन, इस पद्धति के निर्माता ने माना कि यदि कोई चीज है T, तो यह भी सुपरक्लासेस का एक उदाहरण है T। इसलिए, वह की एक तुलनाकारी उपयोग करने के लिए हमें की अनुमति देता है Tया के किसी भी सुपर क्लास T, यानी ? super T


1

क्या होगा अगर आप का उपयोग करें

Map<String, ? extends Class<? extends Serializable>> expected = null;

हां, यह उस तरह का है जो मेरे उपरोक्त उत्तर पर चला रहा था।
ग्रीनमीनी

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