जावा 8 तुलनित्र प्रकार के अनुमान से बहुत भ्रमित


84

मैं विशेष रूप से स्थैतिक तरीकों का उपयोग करने के बारे में Collections.sortऔर के बीच अंतर को देख रहा हूं list.sort, Comparatorचाहे लंबोदर अभिव्यक्ति में परम प्रकार की आवश्यकता हो। शुरू करने से पहले, मुझे पता है कि मैं Song::getTitleअपनी समस्याओं को दूर करने के लिए विधि संदर्भों, उदाहरणों का उपयोग कर सकता हूं, लेकिन यहां मेरी क्वेरी इतनी अधिक नहीं है जिसे मैं ठीक करना चाहता हूं, लेकिन कुछ ऐसा है जिसका मुझे जवाब चाहिए, अर्थात जावा कंपाइलर इसे इस तरह से क्यों संभाल रहा है। ।

ये मेरी खोज हैं। मान लें कि हमारे पास कुछ ArrayListप्रकार हैं Song, कुछ गानों के साथ, 3 मानक तरीके हैं:

    ArrayList<Song> playlist1 = new ArrayList<Song>();

    //add some new Song objects
    playlist.addSong( new Song("Only Girl (In The World)", 235, "Rhianna") );
    playlist.addSong( new Song("Thinking of Me", 206, "Olly Murs") );
    playlist.addSong( new Song("Raise Your Glass", 202,"P!nk") );

यहां दोनों प्रकार की सॉर्ट विधि के लिए एक कॉल है जो काम करती है, कोई समस्या नहीं:

Collections.sort(playlist1, 
            Comparator.comparing(p1 -> p1.getTitle()));

playlist1.sort(
            Comparator.comparing(p1 -> p1.getTitle()));

जैसे ही मैं श्रृंखला शुरू करता हूं thenComparing, निम्नलिखित होता है:

Collections.sort(playlist1,
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing(p1 -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

यानी सिंटैक्स त्रुटियां क्योंकि यह p1अब के प्रकार को नहीं जानता है । इसलिए इसे ठीक करने के लिए मैं Songपहले पैरामीटर (तुलना के) में प्रकार जोड़ता हूं :

Collections.sort(playlist1,
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing((Song p1) -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

अब यहाँ CONFUSING भाग आता है। पी के लिए laylist1.sort, यानी सूची, यह दोनों thenComparingकॉल के लिए सभी संकलन त्रुटियों को हल करता है। हालाँकि, इसके लिए Collections.sort, यह पहले वाले के लिए हल करता है, लेकिन पिछले एक के लिए नहीं। मैंने परीक्षण में कई अतिरिक्त कॉल जोड़े thenComparingऔर यह हमेशा पिछले एक के लिए एक त्रुटि दिखाता है, जब तक कि मैं (Song p1)पैरामीटर के लिए नहीं डालता ।

अब मैं इसे बनाने TreeSetऔर उपयोग करने के साथ आगे परीक्षण करने के लिए चला गया Objects.compare:

int x = Objects.compare(t1, t2, 
                Comparator.comparing((Song p1) -> p1.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );


    Set<Song> set = new TreeSet<Song>(
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

के रूप में एक ही बात होती है, के लिए TreeSet, कोई संकलन त्रुटियाँ नहीं हैं, लेकिन Objects.compareअंतिम कॉल के लिए thenComparingएक त्रुटि दिखाती है।

क्या कोई यह बता सकता है कि ऐसा क्यों हो रहा है और यह भी कि (Song p1)जब केवल तुलना विधि (बिना thenComparingकॉल किए) कॉल किया जा रहा है तो इसका उपयोग करने की कोई आवश्यकता क्यों नहीं है ।

एक ही विषय पर एक अन्य प्रश्न है जब मैं यह करने के लिए TreeSet:

Set<Song> set = new TreeSet<Song>(
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

यानी Songतुलना विधि कॉल के लिए पहले लैम्ब्डा पैरामीटर से प्रकार को हटा दें , यह कॉल के तहत सिंटैक्स त्रुटियों को दिखाता है और पहली कॉल करने के लिए thenComparingलेकिन अंतिम कॉल के लिए नहीं thenComparing- ऊपर जो हो रहा था उसके लगभग विपरीत! जबकि, अन्य सभी 3 उदाहरणों के साथ अर्थात Objects.compare, List.sortऔर Collections.sortजब मैं उस पहले Songपरम प्रकार को हटाता हूं तो यह सभी कॉल के लिए सिंटैक्स त्रुटियों को दर्शाता है।

अग्रिम में बहुत धन्यवाद।

एक्लिप्स केपलर SR2 में मुझे मिलने वाली त्रुटियों के स्क्रीनशॉट को शामिल करने के लिए संपादित किया गया था, जो अब मैंने पाया है कि ग्रहण विशिष्ट हैं क्योंकि जब कमांड लाइन पर JDK8 जावा कंपाइलर का उपयोग करके संकलित किया जाता है तो यह ठीक संकलन करता है।

ग्रहण में त्रुटियों को क्रमबद्ध करें


यह उपयोगी होगा यदि आप अपने प्रश्न में सभी संकलन त्रुटि संदेश शामिल करते हैं जो आपको अपने सभी परीक्षणों में मिल रहे हैं।
एरन

1
ईमानदार होने के लिए, मुझे लगता है कि किसी के लिए यह देखना सबसे आसान होगा कि स्रोत कोड को चलाने से समस्याएं क्या हैं।
ट्रैंक्विलिटी

उदाहरण में t1और किस प्रकार के हैं ? मैं उनका अनुमान लगाने की कोशिश कर रहा हूं, लेकिन संकलक के प्रकार के निष्कर्ष पर मेरा प्रकार व्यक्त करना असत्य है। :-)t2Objects.compare
स्टुअर्ट मार्क्स

1
इसके अलावा आप क्या संकलक का उपयोग कर रहे हैं?
स्टुअर्ट मार्क्स

1
आपको यहां दो अलग-अलग मुद्दे मिलेंगे। उत्तरदाताओं में से एक ने कहा कि आप विधि संदर्भों का उपयोग कर सकते हैं, जिसे आपने छांटा है। जिस तरह लंबोदर "स्पष्ट रूप से टाइप किए गए" और "निहित रूप से टाइप किए गए" फ्लेवर दोनों में आते हैं, विधि संदर्भ "सटीक" (एक ओवरलोड) और "एक्सपेक्ट" (मल्टीपल ओवरलोड) फ्लेवर में आते हैं। या तो एक सटीक विधि रेफरी या एक स्पष्ट लैम्ब्डा का उपयोग अतिरिक्त टाइपिंग जानकारी प्रदान करने के लिए किया जा सकता है यदि मौजूद नहीं है। (स्पष्ट प्रकार के गवाहों और जातियों का उपयोग भी किया जा सकता है, लेकिन अक्सर बड़े हथौड़े होते हैं।)
ब्रायन गोएटज

जवाबों:


105

सबसे पहले, आपके द्वारा कहे गए सभी उदाहरण त्रुटियों को संदर्भ कार्यान्वयन (JDK 8 से javac 8) के साथ ठीक संकलित करते हैं। वे IntelliJ में भी ठीक काम करते हैं, इसलिए इसकी संभावित त्रुटियां जो आप देख रहे हैं, ग्रहण-विशिष्ट हैं।

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

जब आप कहें

Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));

comparing()तर्क प्रकार और तर्क प्रकार दोनों को हल करने के लिए पर्याप्त प्रकार की जानकारी है p1comparing()कॉल के हस्ताक्षर से अपने लक्ष्य प्रकार हो जाता है Collections.sort, तो यह जाना जाता है comparing()एक लौटना चाहिए Comparator<Song>, और इसलिए p1होना चाहिए Song

लेकिन जब आप पीछा करना शुरू करते हैं:

Collections.sort(playlist1,
                 comparing(p1 -> p1.getTitle())
                     .thenComparing(p1 -> p1.getDuration())
                     .thenComparing(p1 -> p1.getArtist()));

अब हम एक समस्या है। हम जानते हैं कि यौगिक अभिव्यक्ति comparing(...).thenComparing(...)का एक लक्ष्य प्रकार है Comparator<Song>, लेकिन क्योंकि श्रृंखला के लिए रिसीवर अभिव्यक्ति comparing(p -> p.getTitle()), एक सामान्य विधि कॉल है, और हम इसके प्रकार के मापदंडों को इसके अन्य तर्कों से समझ नहीं सकते हैं, हम भाग्य से बाहर हैं । चूंकि हम इस अभिव्यक्ति के प्रकार को नहीं जानते हैं, इसलिए हम नहीं जानते हैं कि इसकी एक thenComparingविधि है, आदि।

इसे ठीक करने के कई तरीके हैं, जिनमें से सभी में अधिक प्रकार की जानकारी सम्मिलित करना शामिल है ताकि श्रृंखला में प्रारंभिक वस्तु को ठीक से टाइप किया जा सके। यहाँ वे घटती वांछनीयता के बढ़ते क्रम में और घुसपैठ को बढ़ा रहे हैं:

  • एक सटीक विधि संदर्भ (कोई अधिभार के साथ एक) का उपयोग करें, जैसे Song::getTitle। यह तब comparing()कॉल के लिए प्रकार चर का अनुमान लगाने के लिए पर्याप्त प्रकार की जानकारी देता है , और इसलिए इसे एक प्रकार देता है, और इसलिए श्रृंखला को जारी रखें।
  • एक स्पष्ट लैम्ब्डा का उपयोग करें (जैसा कि आपने अपने उदाहरण में किया था)।
  • comparing()कॉल के लिए एक प्रकार का गवाह प्रदान करें Comparator.<Song, String>comparing(...):।
  • रिसीवर अभिव्यक्ति के लिए कास्टिंग करके, कलाकारों के साथ एक स्पष्ट लक्ष्य प्रकार प्रदान करें Comparator<Song>

13
+1 वास्तव में ओपी को जवाब देने के लिए "कम्पाइलर क्यों नहीं समझ सकता है" केवल वर्कअराउंड / सॉल्यूशन देने के बजाय।
जोफ्री

ब्रायन आपके जवाब के लिए धन्यवाद। हालाँकि, मुझे अभी भी कुछ अनुत्तरित लगता है, क्यों List.sort कलेक्शन के लिए अलग-अलग व्यवहार करता है। Sort, जिसमें पूर्व में केवल पैरामीटर प्रकार को शामिल करने के लिए पहले लैम्ब्डा की आवश्यकता होती है, लेकिन बाद वाले को अंतिम की भी आवश्यकता होती है, जैसे कि यदि कोई तुलना है। 5 तब के बाद कॉलिंग के बाद मुझे कॉल करना होगा (सॉन्ग पी 1) तुलना में और आखिरी में फिर कॉलिंग। इसके अलावा अपने मूल पोस्ट में आप ट्रीसैट का निचला उदाहरण देखेंगे जहां मैं सभी परम प्रकारों को हटा देता हूं और फिर भी तब के लिए अंतिम कॉल करना ठीक है लेकिन अन्य नहीं हैं - इसलिए यह अलग तरह से व्यवहार करता है।
ट्रैंक्विलिटी

3
@ user3780370 क्या आप अभी भी ग्रहण संकलक का उपयोग कर रहे हैं? मैंने इस व्यवहार को नहीं देखा है, अगर मैं आपके प्रश्न को सही ढंग से समझता हूं। क्या आप (a) इसे JDK 8 से javac के साथ आज़मा सकते हैं और (b) यदि यह अभी भी विफल रहता है, तो कोड को पोस्ट करें?
ब्रायन गोएटज

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

2
मुझे लगता है कि आपका मतलब है Comparator.<Song, String>comparing(...)
shmosel

23

समस्या टाइपिंग है। (Song s)पहली तुलना में जोड़ने के बिना , comparator.comparingइनपुट के प्रकार को नहीं जानता है, इसलिए यह ऑब्जेक्ट को डिफॉल्ट करता है।

आप इस समस्या को 3 में से 1 तरीके से ठीक कर सकते हैं:

  1. नए जावा 8 विधि संदर्भ सिंटैक्स का उपयोग करें

     Collections.sort(playlist,
                Comparator.comparing(Song::getTitle)
                .thenComparing(Song::getDuration)
                .thenComparing(Song::getArtist)
                );
    
  2. स्थानीय संदर्भ में प्रत्येक तुलना चरण को बाहर निकालें

      Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist());
    
      Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration());
    
        Collections.sort(playlist,
                byName
                .thenComparing(byDuration)
                );
    

    संपादित करें

  3. तुलनित्र द्वारा दिए गए प्रकार को मजबूर करना (ध्यान दें कि आपको इनपुट प्रकार और तुलना कुंजी प्रकार दोनों की आवश्यकता है)

    sort(
      Comparator.<Song, String>comparing((s) -> s.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );
    

मुझे लगता है कि "अंतिम" thenComparingवाक्यविन्यास त्रुटि आपको गुमराह कर रही है। यह वास्तव में पूरी श्रृंखला के साथ एक प्रकार की समस्या है, यह केवल संकलक है श्रृंखला के अंत को केवल एक सिंटैक्स त्रुटि के रूप में चिह्नित करना है क्योंकि जब अंतिम रिटर्न प्रकार मेरे अनुमान से मेल नहीं खाता है।

मुझे यकीन नहीं है कि क्यों Listएक बेहतर हीन कार्य कर रहा है Collectionक्योंकि यह एक ही कब्जा प्रकार करना चाहिए, लेकिन स्पष्ट रूप से नहीं।


इसे क्यों पता है, ArrayListलेकिन Collectionsसमाधान के लिए नहीं (श्रृंखला में पहला कॉल करने के लिए एक Songपैरामीटर है)?
सोतिरियोस डेलिमोलिस

4
आपके उत्तर के लिए धन्यवाद, हालांकि, यदि आप मेरी पोस्ट पढ़ते हैं, तो आप कहेंगे मैंने कहा: "शुरू होने से पहले, मुझे पता है कि मैं विधि संदर्भों का उपयोग कर सकता हूं, उदाहरण के लिए गीत :: getTitle मेरी समस्याओं को दूर करने के लिए, लेकिन यहां मेरी क्वेरी इतनी नहीं है कुछ मैं ठीक करना चाहता हूं, लेकिन कुछ ऐसा है जिसका जवाब मुझे चाहिए, यानी जावा कंपाइलर इस तरह से क्यों संभाल रहा है। "
त्रिकोणीयता

मुझे इस बात का जवाब चाहिए कि जब मैं लंबर एक्सप्रेशन का उपयोग करता हूं तो कंपाइलर उस तरह का व्यवहार क्यों कर रहा है। यह तुलना करना स्वीकार करता है (s -> s.getArtist ()) लेकिन तब जब मैं श्रृंखला .thenComparing (s -> s.getDuration ()) उदाहरण के लिए यह मुझे दोनों कॉल्स के लिए सिंटैक्स त्रुटियां देता है, अगर मैं एक स्पष्ट प्रकार जोड़ता हूं तुलनात्मक कॉल, उदाहरण के लिए तुलना ((सॉन्ग s) -> s.getArtist ()) तब यह समस्या ठीक करता है और List.sort और TreeSet के लिए यह अतिरिक्त परम प्रकार जोड़ने के लिए सभी अतिरिक्त संकलन त्रुटियों को हल करता है, हालाँकि, के लिए द कलेक्शन.सोर्ट एंड ऑब्जेक्ट्स.कॉम उदाहरण के बाद आखिरी बार फिर भी काम करते हैं। अभी भी असफलता है
ट्रैंक्विलिटी

1

इस संकलन समय त्रुटि से निपटने का दूसरा तरीका:

स्पष्ट रूप से पहले और फिर जाने के लिए फ़ंक्शन के चर की तुलना करें। मेरे पास org.bson.Documents ऑब्जेक्ट की सूची है। कृपया नमूना कोड देखें

Comparator<Document> comparator = Comparator.comparing((Document hist) -> (String) hist.get("orderLineStatus"), reverseOrder())
                       .thenComparing(hist -> (Date) hist.get("promisedShipDate"))
                       .thenComparing(hist -> (Date) hist.get("lastShipDate"));
list = list.stream().sorted(comparator).collect(Collectors.toList());

0

playlist1.sort(...) Playlist1 की घोषणा से टाइप चर E के लिए सॉन्ग की एक सीमा बनाता है, जो तुलनित्र को "तरंग" देता है।

में Collections.sort(...), ऐसी कोई बाध्यता नहीं है, और कंपाइलर के प्रकार से अनुमान बाकी के अनुमान लगाने के लिए पर्याप्त नहीं है।

मुझे लगता है कि आपको "सही" व्यवहार मिलेगा Collections.<Song>sort(...), लेकिन आपके लिए इसे परीक्षण करने के लिए एक जावा 8 स्थापित नहीं है।


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