यह वास्तव में एक दिलचस्प सवाल है। जवाब, मुझे डर है, जटिल है।
tl; डॉ
अंतर को काम करने में जावा के प्रकार के अनुमान विनिर्देश के कुछ काफी गहराई से पढ़ने को शामिल किया गया है , लेकिन मूल रूप से यह उबलता है:
- अन्य सभी चीजें समान हैं, कंपाइलर सबसे विशिष्ट प्रकार को संक्रमित करता है जो यह कर सकता है।
- हालांकि, अगर यह एक प्रकार के पैरामीटर के लिए एक प्रतिस्थापन पा सकता है जो सभी आवश्यकताओं को पूरा करता है, तो संकलन सफल होगा, हालांकि प्रतिस्थापन अस्पष्ट हो जाता है।
- के लिए
with
एक (स्पष्ट रूप से अस्पष्ट) प्रतिस्थापन है जो सभी आवश्यकताओं को पूरा करता है R
:Serializable
- इसके लिए
withX
, अतिरिक्त प्रकार के पैरामीटर की शुरूआत बाधा को ध्यान में रखते हुए, पहले F
हल करने के लिए मजबूर R
करती है F extends Function<T,R>
। R
(बहुत अधिक विशिष्ट) को हल करता है, String
जिसका अर्थ है कि असफलता का अनुमान F
।
यह अंतिम गोली बिंदु सबसे महत्वपूर्ण है, लेकिन सबसे हाथ में लहराती भी है। मैं इसे बेहतर बनाने के बेहतर तरीके के बारे में नहीं सोच सकता, इसलिए यदि आप अधिक विवरण चाहते हैं, तो मेरा सुझाव है कि आप नीचे दिए गए पूर्ण विवरण को पढ़ें।
क्या यह अभिप्रेत व्यवहार है?
मैं एक अंग पर यहाँ जा रहा हूँ, और कहते हैं कि नहीं ।
मैं सुझाव नहीं दे रहा हूं कि कल्पना में एक बग है, और अधिक कि (मामले में withX
) भाषा डिजाइनरों ने अपने हाथों को ऊपर रखा है और कहा है "कुछ परिस्थितियां हैं जहां प्रकार का अनुमान बहुत कठिन हो जाता है, इसलिए हम बस विफल हो जाएंगे" । भले ही withX
आप चाहते हैं कि प्रतीत होने के लिए सम्मान के साथ संकलक का व्यवहार , मैं इस बात पर विचार करूंगा कि वर्तमान में एक सकारात्मक डिजाइन के निर्णय के बजाय वर्तमान कल्पना के पक्ष-प्रभाव का होना।
यह मायने रखता है, क्योंकि यह सवाल सूचित करता है कि क्या मुझे अपने आवेदन डिजाइन में इस व्यवहार पर भरोसा करना चाहिए? मेरा तर्क है कि आपको नहीं करना चाहिए, क्योंकि आप इस बात की गारंटी नहीं दे सकते कि भविष्य के संस्करण इस तरह से व्यवहार करते रहेंगे।
हालांकि यह सच है कि भाषा डिजाइनर मौजूदा अनुप्रयोगों को तोड़ने के लिए बहुत कोशिश करते हैं जब वे अपनी कल्पना / डिजाइन / संकलक को अपडेट करते हैं, तो समस्या यह है कि जिस व्यवहार पर आप भरोसा करना चाहते हैं वह वह है जहां कंपाइलर वर्तमान में विफल रहता है (अर्थात मौजूदा एप्लिकेशन नहीं )। लैंगगेज अपडेट गैर-संकलन कोड को हर समय संकलन कोड में बदल देते हैं। उदाहरण के लिए, निम्नलिखित कोड को जावा 7 में संकलित नहीं करने की गारंटी दी जा सकती है , लेकिन जावा 8 में संकलित किया जाएगा :
static Runnable x = () -> System.out.println();
आपका उपयोग-मामला अलग नहीं है।
एक और कारण मैं आपके withX
विधि का उपयोग करने के बारे में सतर्क रहूँगा जो कि F
स्वयं पैरामीटर है। आम तौर पर, एक विधि पर एक सामान्य प्रकार के पैरामीटर (जो रिटर्न प्रकार में प्रकट नहीं होता है) एक साथ हस्ताक्षर के कई हिस्सों के प्रकारों को बांधने के लिए मौजूद है। यह कह रहा है:
मुझे परवाह नहीं है कि क्या T
है, लेकिन यह सुनिश्चित करना चाहता हूं कि मैं जहां भी उपयोग करूं, T
वह उसी प्रकार का हो।
तार्किक रूप से, फिर, हम उम्मीद करेंगे कि प्रत्येक प्रकार का पैरामीटर कम से कम दो बार एक विधि हस्ताक्षर में दिखाई दे, अन्यथा "यह कुछ भी नहीं कर रहा है"। F
आपके withX
हस्ताक्षर में केवल एक बार दिखाई देता है, जो मुझे एक प्रकार के पैरामीटर का उपयोग करने का सुझाव देता है जो भाषा की इस विशेषता के इरादे से इनलाइन नहीं है ।
एक वैकल्पिक कार्यान्वयन
इसे थोड़ा और अधिक "इच्छित व्यवहार" तरीके से लागू करने का एक तरीका यह होगा कि आप अपने with
तरीके को 2 की श्रृंखला में विभाजित करें :
public class Builder<T> {
public final class With<R> {
private final Function<T,R> method;
private With(Function<T,R> method) {
this.method = method;
}
public Builder<T> of(R value) {
// TODO: Body of your old 'with' method goes here
return Builder.this;
}
}
public <R> With<R> with(Function<T,R> method) {
return new With<>(method);
}
}
यह तो निम्नानुसार इस्तेमाल किया जा सकता है:
b.with(MyInterface::getLong).of(1L); // Compiles
b.with(MyInterface::getLong).of("Not a long"); // Compiler error
इसमें आपके जैसा कोई बाहरी प्रकार का पैरामीटर शामिल नहीं withX
है। विधि को दो हस्ताक्षरों में तोड़कर, यह इस बात की मंशा को भी बेहतर ढंग से व्यक्त करता है कि आप क्या करने की कोशिश कर रहे हैं, एक प्रकार की सुरक्षा से:
- पहली विधि एक वर्ग (
With
) स्थापित करती है जो विधि संदर्भ के आधार पर प्रकार को परिभाषित करती है।
- Scond विधि (
of
) रोकें के प्रकार value
क्या आप पहले सेट के साथ संगत होना करने के लिए।
भाषा का एक भविष्य का एकमात्र तरीका यह संकलित करने में सक्षम होगा यदि कार्यान्वित पूर्ण बतख-टाइपिंग, जो कि संभावना नहीं है।
एक अंतिम नोट इस पूरी बात को अप्रासंगिक बनाने के लिए: मुझे लगता है कि मॉकिटो (और विशेष रूप से इसकी स्टबिंग कार्यक्षमता) मूल रूप से पहले से ही ऐसा कर सकता है जिसे आप अपने "प्रकार सुरक्षित जेनेरिक बिल्डर" के साथ हासिल करने की कोशिश कर रहे हैं। शायद आप इसके बजाय बस का उपयोग कर सकते हैं?
पूर्ण (ईश) व्याख्या
मैं के माध्यम से काम करने के लिए जा रहा हूँ प्रकार निष्कर्ष प्रक्रिया दोनों के लिए with
और withX
। यह काफी लंबा है, इसलिए इसे धीरे-धीरे लें। लंबे होने के बावजूद, मैंने अभी भी बहुत सारे विवरणों को छोड़ दिया है। आप अपने आप को समझाने के लिए और अधिक विवरण (लिंक का पालन करें) की युक्ति का उल्लेख करना चाह सकते हैं कि मैं सही हूं (मैंने अच्छी तरह से गलती की होगी)।
इसके अलावा, चीजों को थोड़ा सरल बनाने के लिए, मैं अधिक न्यूनतम कोड नमूने का उपयोग करने जा रहा हूं। मुख्य अंतर यह है कि इसे बाहर स्वैप है Function
के लिए Supplier
है, इसलिए कम प्रकार और खेल में मानकों हैं। यहां एक पूर्ण स्निपेट है जो आपके द्वारा वर्णित व्यवहार को पुन: पेश करता है:
public class TypeInference {
static long getLong() { return 1L; }
static <R> void with(Supplier<R> supplier, R value) {}
static <R, F extends Supplier<R>> void withX(F supplier, R value) {}
public static void main(String[] args) {
with(TypeInference::getLong, "Not a long"); // Compiles
withX(TypeInference::getLong, "Also not a long"); // Does not compile
}
}
आइए बदले में प्रत्येक विधि आह्वान के लिए टाइप प्रयोज्यता निष्कर्ष और प्रकार अनुमान प्रक्रिया के माध्यम से काम करते हैं:
with
हमारे पास है:
with(TypeInference::getLong, "Not a long");
प्रारंभिक बाउंड सेट, बी 0 , है:
सभी पैरामीटर अभिव्यक्ति प्रयोज्यता के अनुकूल हैं ।
इसलिए, प्रयोज्यता अनुमान , सी , के लिए प्रारंभिक बाधा निर्धारित है:
TypeInference::getLong
के साथ संगत है Supplier<R>
"Not a long"
के साथ संगत है R
यह बी सेट के 2 को कम करता है :
R <: Object
( बी 0 से )
Long <: R
(पहले बाधा से)
String <: R
(दूसरे बाधा से)
चूंकि इसमें बाध्य ' गलत ' शामिल नहीं है , और (मुझे लगता है) सफल होने (देने ) का संकल्प है , तो आह्वान लागू है।R
Serializable
इसलिए, हम आह्वान करते हैं कि हम आह्वान के प्रकार पर जाएं ।
नया बाधा सेट, सी , संबंधित इनपुट और आउटपुट चर के साथ है:
TypeInference::getLong
के साथ संगत है Supplier<R>
- इनपुट चर: कोई नहीं
- आउटपुट चर:
R
इसमें इनपुट और आउटपुट चर के बीच कोई अन्य निर्भरता नहीं है , इसलिए इसे एक ही चरण में कम किया जा सकता है , और अंतिम बाउंड सेट, बी 4 , बी 2 के समान है । इसलिए, संकल्प पहले की तरह सफल हुआ, और संकलक ने राहत की सांस ली!
withX
हमारे पास है:
withX(TypeInference::getLong, "Also not a long");
प्रारंभिक बाउंड सेट, बी 0 , है:
R <: Object
F <: Supplier<R>
केवल दूसरा पैरामीटर अभिव्यक्ति प्रयोज्यता के अनुकूल है । पहला वाला ( TypeInference::getLong
) नहीं है, क्योंकि यह निम्नलिखित स्थिति को पूरा करता है:
यदि m
एक सामान्य विधि है और विधि आह्वान स्पष्ट प्रकार के तर्क प्रदान नहीं करता है, तो स्पष्ट रूप से टाइप की गई लंबोदर अभिव्यक्ति या एक सटीक विधि संदर्भ अभिव्यक्ति जिसके लिए संबंधित लक्ष्य प्रकार (हस्ताक्षर से प्राप्त m
) एक प्रकार का पैरामीटर है m
।
इसलिए, प्रयोज्यता अनुमान , सी , के लिए प्रारंभिक बाधा निर्धारित है:
"Also not a long"
के साथ संगत है R
यह बी सेट के 2 को कम करता है :
R <: Object
( बी 0 से )
F <: Supplier<R>
( बी 0 से )
String <: R
(अड़चन से)
फिर, चूंकि इसमें बाध्य ' गलत ' नहीं है, और सफल होने (देने ) का संकल्प है , तो आह्वान लागू है।R
String
एक बार फिर आह्वान के प्रकार
इस बार, नया बाधा सेट, C , संबंधित इनपुट और आउटपुट चर के साथ है:
TypeInference::getLong
के साथ संगत है F
- इनपुट चर:
F
- आउटपुट चर: कोई नहीं
फिर से, हमारे पास इनपुट और आउटपुट चर के बीच कोई अन्य निर्भरता नहीं है । हालांकि इस समय, वहाँ है एक इनपुट चर ( F
,) तो हम चाहिए हल प्रयास करने से पहले इस कमी । तो, हम अपने बाउंड सेट बी 2 से शुरू करते हैं ।
हम एक उपसमूह V
इस प्रकार निर्धारित करते हैं:
हल करने के लिए प्रवेश चर के एक सेट को देखते हुए, V
इस सेट के संघ और सभी चर होने चाहिए, जिस पर इस सेट में कम से कम एक चर का संकल्प निर्भर करता है।
बी 2 में दूसरे बाउंड द्वारा , F
निर्भर करने का संकल्प R
, इसलिए V := {F, R}
।
हम V
नियम के अनुसार एक सबसेट चुनते हैं :
आइए , सभी के लिए इस तरह { α1, ..., αn }
के अनइंस्टैंटेड वेरिएबल्स का एक गैर-रिक्त उपसमूह बनें , यदि कोई वैरिएबल के रिज़ॉल्यूशन पर निर्भर करता है , तो या तो एक तात्कालिकता है या कुछ ऐसा है ; (ii) इस संपत्ति के साथ कोई गैर-खाली उचित उपसमूह मौजूद नहीं है।V
i (1 ≤ i ≤ n)
αi
β
β
j
β = αj
{ α1, ..., αn }
उस का एकमात्र सबसेट V
इस संपत्ति को संतुष्ट करता है {R}
।
तीसरी बाउंड ( String <: R
) का उपयोग करके हम R = String
इसे अपने बाउंड सेट में तुरंत शामिल करते हैं और शामिल करते हैं। R
अब हल हो गया है, और प्रभावी रूप से दूसरा बाध्य हो गया है F <: Supplier<String>
।
(संशोधित) दूसरे बाउंड का उपयोग करना, हम तुरंत करते हैं F = Supplier<String>
। F
अब हल हो गया है।
अब F
यह हल हो गया है, हम कमी के साथ आगे बढ़ सकते हैं , नए अवरोध का उपयोग करते हुए:
TypeInference::getLong
के साथ संगत है Supplier<String>
- ... कम करने के
Long
लिए संगत है String
- ... जो झूठ को कम करता है
... और हमें एक संकलक त्रुटि मिलती है!
'विस्तारित उदाहरण' पर अतिरिक्त नोट
विस्तारित उदाहरण में कुछ दिलचस्प मामलों है कि सीधे ऊपर कामकाज में शामिल नहीं हैं पर सवाल दिखता में:
- जहाँ मान प्रकार विधि वापसी प्रकार का एक उपप्रकार है (
Integer <: Number
)
- जहाँ क्रियात्मक इंटरफ़ेस अनुमानात्मक प्रकार में विरोधाभासी है (अर्थात
Consumer
इसके बजाय Supplier
)
विशेष रूप से, दिए गए इनवोकेशन में से 3 संभावित रूप से 'अलग' संकलक व्यवहार का सुझाव देने के लिए बाहर खड़े होते हैं जो स्पष्टीकरण में वर्णित हैं:
t.lettBe(t::setNumber, "NaN"); // Does not compile :-)
t.letBeX(t::getNumber, 2); // !!! Does not compile :-(
t.lettBeX(t::setNumber, 2); // Compiles :-)
इन 3 में से दूसरा ठीक withX
ऊपर की प्रक्रिया के समान ही गुजर जाएगा (बस के Long
साथ Number
और String
साथ बदल Integer
)। यह अभी तक एक और कारण बताता है कि आपको अपने वर्ग डिजाइन के लिए इस प्रकार के असफल व्यवहार पर भरोसा नहीं करना चाहिए, क्योंकि यहां संकलन करने में विफलता एक वांछनीय व्यवहार नहीं है।
अन्य 2 के लिए (और वास्तव में Consumer
आपके द्वारा काम करने की इच्छा रखने वाले अन्य किसी भी इनवोकेशन के माध्यम से), व्यवहार स्पष्ट होना चाहिए यदि आप ऊपर दिए गए तरीकों में से एक के लिए निर्धारित प्रकार की प्रक्रिया के माध्यम से काम करते हैं (यानी with
, पहले के लिए, withX
के लिए तीसरे)। केवल एक छोटा सा बदलाव है, जिस पर आपको ध्यान देना चाहिए:
- पहले पैरामीटर पर बाधा (
t::setNumber
साथ संगत है Consumer<R>
) इसके बजाय कम हो जाएगी क्योंकि यह करता है । यह कमी पर जुड़े प्रलेखन में वर्णित है।R <: Number
Number <: R
Supplier<R>
मैं इसे उपरोक्त प्रक्रियाओं में से किसी एक के माध्यम से काम करने के लिए पाठक के लिए एक एक्सर्साइज़ के रूप में छोड़ता हूं, अतिरिक्त ज्ञान के इस टुकड़े से लैस, खुद को यह दिखाने के लिए कि वास्तव में एक विशेष आह्वान क्यों करता है या संकलन नहीं करता है।