जावा 8: लंबोदर के पुनरावृत्तियों को गिनने का पसंदीदा तरीका?


83

मैं अक्सर एक ही समस्या का सामना करता हूं। मैं एक के रन गिनती करने की आवश्यकता है लैम्ब्डा के बाहर उपयोग के लिए लैम्ब्डा

उदाहरण के लिए:

myStream.stream().filter(...).forEach(item -> { ... ; runCount++});
System.out.println("The lambda ran " + runCount + "times");

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

मुझे पता है कि विभिन्न तरीके हैं, मैं बस उत्सुक हूं कि इसके लिए आपका पसंदीदा समाधान क्या है?
क्या आप एक AtomicIntegerया एक सरणी संदर्भ या किसी अन्य तरीके का उपयोग करते हैं?


7
@ Sliver2009 नहीं यह नहीं है।
रोहित जैन

4
@ फ़्लोरियन आप AtomicIntegerयहाँ उपयोग करना चाहते हैं।
रोहित जैन

जवाबों:


72

चर्चा के लिए मुझे अपना उदाहरण थोड़ा सुधारना चाहिए:

long runCount = 0L;
myStream.stream()
    .filter(...)
    .forEach(item -> { 
        foo();
        bar();
        runCount++; // doesn't work
    });
System.out.println("The lambda ran " + runCount + " times");

यदि आपको वास्तव में एक लैम्ब्डा के भीतर से एक काउंटर को बढ़ाने की आवश्यकता है, तो ऐसा करने का विशिष्ट तरीका काउंटर को AtomicIntegerया AtomicLongतो बनाना है और फिर उस पर वेतन वृद्धि विधियों में से एक को कॉल करना है।

आप एकल-तत्व intया longसरणी का उपयोग कर सकते हैं , लेकिन यदि धारा समानांतर रूप से चलती है, तो दौड़ की स्थिति होगी।

लेकिन ध्यान दें कि स्ट्रीम में समाप्त होता है forEach, जिसका अर्थ है कि कोई वापसी मूल्य नहीं है। आप को बदल सकता है forEachएक करने के लिए peek, जिसके माध्यम से आइटम गुजरता है, और फिर उन्हें गिनती:

long runCount = myStream.stream()
    .filter(...)
    .peek(item -> { 
        foo();
        bar();
    })
    .count();
System.out.println("The lambda ran " + runCount + " times");

यह कुछ हद तक बेहतर है, लेकिन फिर भी थोड़ा अजीब है। कारण यह है कि है forEachऔर peekकेवल दुष्प्रभाव के माध्यम से अपने काम कर सकते हैं। जावा 8 की उभरती कार्यात्मक शैली दुष्प्रभावों से बचने के लिए है। हमने countधारा पर एक ऑपरेशन में काउंटर के वेतन वृद्धि को हटाकर थोड़ा सा किया । अन्य विशिष्ट दुष्प्रभाव संग्रह में आइटम जोड़ रहे हैं। आमतौर पर इन्हें संग्राहकों के उपयोग से बदला जा सकता है। लेकिन यह जानने के बिना कि आप किस वास्तविक काम को करने की कोशिश कर रहे हैं, मैं और अधिक विशिष्ट सुझाव नहीं दे सकता।


9
यह ध्यान दिया जाना चाहिए कि peekकाम करना बंद हो जाता है, एक बार countकार्यान्वयन SIZEDधाराओं के लिए शॉर्टकट का उपयोग करना शुरू कर देता है। हो सकता है कि filterएड स्ट्रीम के साथ कोई समस्या न हो, लेकिन अगर बाद में किसी ने कोड बदल दिया तो बहुत आश्चर्य हो सकता है ...
Holger

16
घोषणा करें final AtomicInteger i = new AtomicInteger(1);और कहीं न कहीं आपके मेमने का उपयोग करें i.getAndAdd(1)। बंद करो और याद रखो कि कितना अच्छा हुआ करता था int i=1; ... i++
एलिओपी

2
यदि जावा Incrementableसंख्यात्मक कक्षाओं जैसे इंटरफेस को लागू करेगा , जिसमें AtomicIntegerऑपरेटर शामिल होंगे और जैसे कि ++फैंसी-दिखने वाले कार्यों को घोषित करेंगे , तो हमें ऑपरेटर को ओवरलोडिंग की आवश्यकता नहीं होगी और अभी भी बहुत पठनीय कोड होगा।
सेवेरिटीऑन

37

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

    int[] iarr = {0}; // final not neccessary here if no other array is assigned
    stringList.forEach(item -> {
            iarr[0]++;
            // iarr = {1}; Error if iarr gets other array assigned
    });

यदि आप यह सुनिश्चित करना चाहते हैं कि संदर्भ को किसी अन्य सरणी को असाइन नहीं किया गया है, तो आप iarr को अंतिम चर के रूप में घोषित कर सकते हैं। लेकिन जैसा @pisaruk बताते हैं, यह समानांतर काम नहीं करेगा।
थीमाथैगियन

1
मुझे लगता है, foreachसीधे संग्रह पर (धाराओं के बिना) के लिए, यह एक अच्छा पर्याप्त दृष्टिकोण है। धन्यवाद !!
साबिर खान

यह सबसे सरल उपाय है, जब तक आप चीजों को समानांतर में नहीं चला रहे हैं।
डेविड डेमर

17
AtomicInteger runCount = 0L;
long runCount = myStream.stream()
    .filter(...)
    .peek(item -> { 
        foo();
        bar();
        runCount.incrementAndGet();
    });
System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");

15
कृपया अधिक जानकारी के साथ संपादित करें । कोड-ओनली एंड " ट्राय दिस " जवाबों को हतोत्साहित किया जाता है , क्योंकि उनमें कोई खोज योग्य सामग्री नहीं होती है, और यह नहीं समझाते कि किसी को "कोशिश" क्यों करनी चाहिए। हम ज्ञान के लिए संसाधन होने के लिए एक प्रयास करते हैं।
मोगादाद

7
आपका जवाब मुझे भ्रमित करता है। आपको दोनों नाम दो चर मिले runCount। मुझे संदेह है कि आप उनमें से केवल एक का इरादा रखते हैं, लेकिन कौन सा?
ओले वीवी

1
मैंने पाया कि RunCount.getAndIncrement () अधिक उपयुक्त है। बहुत बढ़िया जवाब!
कोस्पोल

2
मेरी AtomicIntegerमदद की, लेकिन मैं इसके साथ हूँnew AtomicInteger(0)
स्टीफन Höltker

1) यह कोड संकलित नहीं करेगा: धारा का कोई टर्मिनल ऑपरेशन नहीं है जो एक लंबा 2 लौटाता है) भले ही यह हो, 'रनकाउंट' मान हमेशा '1' होगा: - स्ट्रीम में कोई टर्मिनल ऑपरेशन नहीं है इसलिए (लैंबडा तर्क) कभी नहीं कहा जाएगा - System.out लाइन प्रदर्शित करने से पहले रन की संख्या
बढ़ाती है

9

आप AtomicInteger का उपयोग नहीं करना चाहिए , आपको चीजों का उपयोग नहीं करना चाहिए जब तक कि आपके पास उपयोग करने के लिए वास्तव में अच्छा कारण न हो। और AtomicInteger का उपयोग करने का कारण केवल समवर्ती एक्सेस या जैसे अनुमति दे सकता है।

जब आपकी समस्या की बात आती है;

धारक को एक लंबोदर के अंदर रखने और बढ़ाने के लिए उपयोग किया जा सकता है। और आप runCount.value पर कॉल करके प्राप्त कर सकते हैं

Holder<Integer> runCount = new Holder<>(0);

myStream.stream()
    .filter(...)
    .forEach(item -> { 
        foo();
        bar();
        runCount.value++; // now it's work fine!
    });
System.out.println("The lambda ran " + runCount + " times");

JDK में कुछ धारक वर्ग हैं। यह एक प्रतीत हो रहा है javax.xml.ws.Holder
ब्रैड कपिट

1
वास्तव में? और क्यों?
lkahtz

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

5

मेरे लिए, यह चाल चली, उम्मीद है कि कोई इसे उपयोगी पाता है:

AtomicInteger runCount = new AtomicInteger(0);
myStream.stream().filter(...).forEach(item -> runCount.getAndIncrement());
System.out.println("The lambda ran " + runCount.get() + "times");

getAndIncrement() जावा प्रलेखन स्थिति:

वरहांडल.गेटएंडएड द्वारा निर्दिष्ट मेमोरी इफेक्ट के साथ एटोमिकली वर्तमान मूल्य में वृद्धि होती है। GetAndAdd (1) के बराबर।


3

इसे करने का एक और तरीका (उपयोगी है यदि आप चाहते हैं कि आपकी गिनती केवल कुछ मामलों में बढ़ाई जाए, जैसे कि एक ऑपरेशन सफल रहा) कुछ इस तरह है, उपयोग करना mapToInt()और sum():

int count = myStream.stream()
    .filter(...)
    .mapToInt(item -> { 
        foo();
        if (bar()){
           return 1;
        } else {
           return 0;
    })
    .sum();
System.out.println("The lambda ran " + count + "times");

जैसा कि स्टुअर्ट मार्क्स ने कहा, यह अभी भी कुछ हद तक अजीब है, क्योंकि यह पूरी तरह से साइड इफेक्ट्स (क्या foo()और क्या bar()कर रहे हैं) के आधार पर परहेज नहीं कर रहा है।

और लैम्बडा में एक वैरिएबल को बढ़ाने का एक और तरीका जो इसके बाहर सुलभ है वह एक क्लास वैरिएबल का उपयोग करना है:

public class MyClass {
    private int myCount;

    // Constructor, other methods here

    void myMethod(){
        // does something to get myStream
        myCount = 0;
        myStream.stream()
            .filter(...)
            .forEach(item->{
               foo(); 
               myCount++;
        });
    }
}

इस उदाहरण में, एक विधि में एक काउंटर के लिए एक वर्ग चर का उपयोग करना शायद समझ में नहीं आता है, इसलिए मैं इसके खिलाफ सावधानी बरतूंगा जब तक कि कोई अच्छा कारण न हो। finalयदि संभव हो तो कक्षा चर को रखना थ्रेड सुरक्षा के संदर्भ में सहायक हो सकता है, ( उपयोग करने पर चर्चा के लिए http://www.javapractices.com/topic/TopicAction.do?Id=23 देखें final)।

इस बात का बेहतर अंदाजा लगाने के लिए कि लंबोदर अपने काम करने के तरीके, https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood पर एक विस्तृत नज़र रखते हैं।


3

यदि आप एक फ़ील्ड नहीं बनाना चाहते हैं क्योंकि आपको केवल स्थानीय रूप से इसकी आवश्यकता है, तो आप इसे एक अनाम वर्ग में संग्रहीत कर सकते हैं:

int runCount = new Object() {
    int runCount = 0;
    {
        myStream.stream()
                .filter(...)
                .peek(x -> runCount++)
                .forEach(...);
    }
}.runCount;

अजीब है, मुझे पता है। लेकिन यह अस्थायी चर को स्थानीय दायरे से बाहर रखता है।


2
पृथ्वी पर यहाँ क्या हो रहा है, थोड़े और स्पष्टीकरण की आवश्यकता है
अलेक्जेंडर मिल्स

मूल रूप से, यह एक अनाम वर्ग पर एक क्षेत्र में वृद्धि और पुनर्प्राप्त कर रहा है। यदि आप मुझे बताते हैं कि आप विशेष रूप से किस उलझन में हैं, तो मैं स्पष्ट करने का प्रयास कर सकता हूं।
shmosel

1
@MrCholo यह एक शुरुआती ब्लॉक है । यह कंस्ट्रक्टर से पहले चलता है।
shmosel

1
@MrCholo नहीं, यह एक उदाहरण इनिशियलाइज़र है।
shmosel

1
@MrCholo एक अनाम वर्ग में स्पष्ट रूप से घोषित निर्माता नहीं हो सकता है।
shmosel

1

कम भी काम करता है, आप इसे इस तरह से उपयोग कर सकते हैं

myStream.stream().filter(...).reduce((item, sum) -> sum += item);

1

एक अन्य विकल्प अपाचे कॉमन म्यूटेबल का उपयोग करना है।

MutableInt cnt = new MutableInt(0);
myStream.stream()
    .filter(...)
    .forEach(item -> { 
        foo();
        bar();
        cnt.increment();
    });
System.out.println("The lambda ran " + cnt.getValue() + " times");

0
AtomicInteger runCount = new AtomicInteger(0);

elements.stream()
  //...
  .peek(runCount.incrementAndGet())
  .collect(Collectors.toList());

// runCount.get() should have the num of times lambda code was executed
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.