जावा 8 लैम्ब्डा, फंक्शन.सिटी () या टी-> टी


240

मेरे पास Function.identity()विधि के उपयोग के संबंध में एक प्रश्न है ।

निम्नलिखित कोड की कल्पना करें:

Arrays.asList("a", "b", "c")
          .stream()
          .map(Function.identity()) // <- This,
          .map(str -> str)          // <- is the same as this.
          .collect(Collectors.toMap(
                       Function.identity(), // <-- And this,
                       str -> str));        // <-- is the same as this.

क्या कोई कारण है कि आपको (या इसके विपरीत) Function.identity()का उपयोग करना चाहिए str->str। मुझे लगता है कि दूसरा विकल्प अधिक पठनीय (निश्चित रूप से स्वाद का मामला) है। लेकिन, क्या कोई "वास्तविक" कारण है कि किसी को क्यों पसंद किया जाना चाहिए?


6
अंततः, नहीं, इससे कोई फर्क नहीं पड़ेगा।
fge

50
कुछ भी चलेगा। जो भी आपको लगता है कि अधिक पठनीय है। (चिंता न करें, खुश रहें।)
ब्रायन गोएटज

3
मैं t -> tकेवल इसलिए पसंद करूंगा क्योंकि यह अधिक रसीला है।
डेविड कॉनरैड

3
थोड़ा असंबंधित प्रश्न, लेकिन क्या किसी को पता है कि भाषा डिजाइनर पहचान क्यों बनाते हैं () टाइप टी के एक पैरामीटर के बजाय फ़ंक्शन का एक उदाहरण देते हैं और इसे वापस करते हैं इसलिए विधि का उपयोग विधि संदर्भों के साथ किया जा सकता है?
किरील रहमान

मेरा तर्क है कि "पहचान" शब्द के साथ बातचीत करने का एक उपयोग है, क्योंकि कार्यात्मक प्रोग्रामिंग के अन्य क्षेत्रों में इसका एक महत्वपूर्ण अर्थ है।
orbfish

जवाबों:


312

वर्तमान जेआरई कार्यान्वयन के रूप में, Function.identity()हमेशा एक ही उदाहरण लौटाएगा जबकि प्रत्येक की घटना identifier -> identifierन केवल अपना उदाहरण बनाएगी, बल्कि एक अलग कार्यान्वयन वर्ग भी होगी। अधिक जानकारी के लिए, यहां देखें ।

कारण यह है कि संकलक एक सिंथेटिक विधि उत्पन्न करता है जो उस मेमने की अभिव्यक्ति के तुच्छ शरीर को धारण करता है (के मामले में x->x, इसके बराबर return identifier;) और रनटाइम को इस पद्धति को कॉल करने वाले कार्यात्मक इंटरफ़ेस का कार्यान्वयन बनाने के लिए कहता है। इसलिए रनटाइम केवल अलग-अलग लक्ष्य विधियों को देखता है और वर्तमान कार्यान्वयन यह पता लगाने के लिए तरीकों का विश्लेषण नहीं करता है कि क्या कुछ विधियाँ समकक्ष हैं।

इसलिए Function.identity()इसके बजाय का उपयोग करके x -> xकुछ मेमोरी को बचाया जा सकता है, लेकिन अगर आपको वास्तव में ऐसा लगता है कि आपके निर्णय को ड्राइव नहीं करना चाहिए, तो इससे x -> xअधिक पठनीय है Function.identity()

आप यह भी विचार कर सकते हैं कि जब डिबग जानकारी को सक्षम करने के लिए संकलित किया जाता है, तो सिंथेटिक विधि में लाइन डिबग विशेषता होती है, जो लैम्बडा अभिव्यक्ति को पकड़े हुए स्रोत कोड लाइन (एस) की ओर इशारा करती है, इसलिए आपके पास Functionडीबग करते समय किसी विशेष उदाहरण के स्रोत को खोजने का एक मौका होता है। । इसके विपरीत, जब Function.identity()किसी ऑपरेशन को डीबग करने के दौरान दिए गए इंस्टेंस का सामना करना पड़ता है , तो आप यह नहीं जान पाएंगे कि किसने उस तरीके को कॉल किया है और ऑपरेशन को इंस्टेंस पास किया है।


5
अच्छा उत्तर। मुझे डिबगिंग के बारे में कुछ संदेह है। यह कैसे उपयोगी हो सकता है? x -> xफ़्रेम को शामिल करने वाले स्टैक ट्रेस ट्रेस को प्राप्त करना बहुत संभावना नहीं है । क्या आप इस लंबोदर को ब्रेकपाइंट सेट करने का सुझाव देते हैं? आमतौर पर ब्रेकअप को सिंगल-एक्सप्रेशन लैम्बडा (कम से कम एक्लिप्स में) में रखना इतना आसान नहीं है ...
टैगिर वलेव

14
@ वागीर वलेव: आप कोड को डिबग कर सकते हैं जो एक मनमाना फ़ंक्शन प्राप्त करता है और उस फ़ंक्शन की लागू विधि में कदम रखता है। तब आप एक लैम्ब्डा अभिव्यक्ति के स्रोत कोड पर समाप्त हो सकते हैं। एक स्पष्ट लंबोदर अभिव्यक्ति के मामले में आपको पता चल जाएगा कि फ़ंक्शन कहाँ से आता है और पहचानने का एक मौका होता है कि एक पहचान समारोह बनाया गया था, जहां से पारित करने का निर्णय किस स्थान पर है। Function.identity()उस जानकारी का उपयोग करते समय खो जाता है। फिर, कॉल चेन सरल मामलों में मदद कर सकती है, लेकिन इसके बारे में सोचें, जैसे बहु-थ्रेडेड मूल्यांकन जहां मूल सर्जक स्टैक ट्रेस में नहीं है ...
Holger

2
इस संदर्भ में दिलचस्प है: blog.codefx.org/java/instances-non-capturing-lambdas
Wim Deblauwe

13
@Wim Deblauwe: दिलचस्प है, लेकिन मैं इसे हमेशा दूसरे तरीके से देखूंगा: अगर कोई फैक्ट्री पद्धति अपने प्रलेखन में स्पष्ट रूप से यह नहीं बताती है कि यह हर आह्वान पर एक नया उदाहरण लौटाएगा, तो आप यह नहीं मान सकते कि यह होगा। अगर यह नहीं है तो यह आश्चर्यजनक नहीं होना चाहिए। आखिरकार, इसके बजाय कारखाने के तरीकों का उपयोग करने का एक बड़ा कारण है newnew Foo(…)सटीक प्रकार का एक नया उदाहरण बनाने के लिए गारंटी देता है Foo, जबकि, Foo.getInstance(…)(का एक उपप्रकार) का मौजूदा उदाहरण वापस कर सकता है Foo...
Holger

93

आपके उदाहरण में कोई बड़ा अंतर नहीं है str -> strऔर Function.identity()आंतरिक रूप से यह बस है t->t

लेकिन कभी-कभी हम उपयोग नहीं कर सकते Function.identityक्योंकि हम उपयोग नहीं कर सकते Function। यहाँ एक नज़र रखना:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);

यह ठीक संकलन करेगा

int[] arrayOK = list.stream().mapToInt(i -> i).toArray();

लेकिन अगर आप संकलन करने की कोशिश करते हैं

int[] arrayProblem = list.stream().mapToInt(Function.identity()).toArray();

आप उम्मीद कर सकते हैं कि संकलन की त्रुटि mapToIntहै ToIntFunction, जो संबंधित नहीं है Function। इसके अलावा विधि ToIntFunctionनहीं है identity()


3
देखें stackoverflow.com/q/38034982/14731 एक और उदाहरण है, जहां जगह के लिए i -> iके साथ Function.identity()एक संकलक त्रुटि का परिणाम देगा।
गिली

19
मैं पसंद करता हूं mapToInt(Integer::intValue)
shmosel

4
@shmosel जो ठीक है, लेकिन यह ध्यान देने योग्य है कि दोनों समाधान समान रूप से काम करेंगे क्योंकि mapToInt(i -> i)इसका सरलीकरण है mapToInt( (Integer i) -> i.intValue())। जो भी आपको लगता है कि स्पष्ट है संस्करण का उपयोग करें, मेरे लिए mapToInt(i -> i)इस कोड के इरादों को बेहतर दिखाता है।
Pshemo

1
मुझे लगता है कि विधि संदर्भों का उपयोग करने में प्रदर्शन लाभ हो सकते हैं, लेकिन यह ज्यादातर सिर्फ एक व्यक्तिगत प्राथमिकता है। मुझे यह अधिक वर्णनात्मक i -> iलगता है , क्योंकि एक पहचान समारोह की तरह दिखता है, जो इस मामले में नहीं है।
शमोसेल

@shmosel मैं प्रदर्शन अंतर के बारे में बहुत कुछ नहीं कह सकता ताकि आप सही हो सकें। लेकिन अगर प्रदर्शन कोई समस्या नहीं है, तो मैं i -> iअपने लक्ष्य के साथ रहूंगा क्योंकि इंटेरियर को इंट में मैप करना है (जो mapToIntकाफी अच्छी तरह से पता चलता है) स्पष्ट रूप से कॉल intValue()करने की विधि नहीं। यह मानचित्रण कैसे प्राप्त किया जाएगा यह वास्तव में महत्वपूर्ण नहीं है। तो बस असहमत होने के लिए सहमत होने देता है लेकिन संभव प्रदर्शन अंतर को इंगित करने के लिए धन्यवाद, मुझे उस दिन एक करीब से देखने की आवश्यकता होगी।
साहीमो

44

से JDK स्रोत :

static <T> Function<T, T> identity() {
    return t -> t;
}

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


8
मुझे आश्चर्य है कि अगर यह एक लैम्बडा से संबंधित उत्तर को एक वस्तु बनाने से अमान्य करता है - या यदि यह एक विशेष कार्यान्वयन है।
orbfish

28
@orbfish: यह पूरी तरह से लाइन में है। हर घटना t->tस्रोत कोड में एक वस्तु और के कार्यान्वयन बना सकते Function.identity()है एक घटना। इसलिए आह्वान करने वाली सभी कॉल साइटें identity()उस एक ऑब्जेक्ट को साझा करेंगी जबकि सभी साइट स्पष्ट रूप से लैम्ब्डा अभिव्यक्ति का उपयोग करते हुए t->tअपनी खुद की वस्तु बनाएंगे। यह विधि Function.identity()किसी भी तरह से विशेष नहीं है, जब भी आप एक आमतौर पर इस्तेमाल किए जाने वाले लैंबडा एक्सप्रेशन को एन्कैप्सुलेट करने के लिए एक फैक्ट्री मेथड बनाते हैं और लैम्बडा एक्सप्रेशन को दोहराने के बजाय उस विधि को कॉल करते हैं, तो आप वर्तमान कार्यान्वयन को देखते हुए कुछ मेमोरी को बचा सकते हैं ।
होल्गर

मैं अनुमान लगा रहा हूं कि ऐसा इसलिए है क्योंकि कंपाइलर t->tहर बार एक नई वस्तु के निर्माण का अनुकूलन करता है, जब भी विधि को बुलाया जाता है और जब भी विधि को बुलाया जाता है, उसी को पुन: चक्रित करता है?
डैनियल ग्रे

1
@DanielGray निर्णय रनटाइम पर किया जाता है। संकलक एक invokedynamicनिर्देश सम्मिलित करता है जो एक तथाकथित बूटस्ट्रैप विधि को निष्पादित करके अपने पहले निष्पादन से जुड़ा हुआ है, जो लंबोदर अभिव्यक्तियों के मामले में स्थित है LambdaMetafactory। यह कार्यान्वयन एक निर्माता, एक कारखाने विधि या कोड को हमेशा एक ही वस्तु को लौटाने का निर्णय करता है। यह पहले से मौजूद हैंडल के लिंक को वापस करने का भी निर्णय ले सकता है (जो वर्तमान में ऐसा नहीं होता है)।
होल्गर

@ होल्गर क्या आपको यकीन है कि पहचान के लिए यह कॉल इनबिल्ड नहीं होगी, फिर संभावित रूप से मोनोमोर्फाइज्ड (और फिर से इनलाइन) होगी?
जेसन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.