क्या जावा 8 एक मूल्य या फ़ंक्शन को दोहराने का एक अच्छा तरीका प्रदान करता है?


118

कई अन्य भाषाओं में, उदाहरण के लिए। हास्केल, एक मूल्य को दोहराना या कई बार कार्य करना आसान है, जैसे। मान 1 की 8 प्रतियों की सूची प्राप्त करने के लिए:

take 8 (repeat 1)

लेकिन मुझे यह अभी तक जावा 8 में नहीं मिला है। क्या जावा 8 के जेडीके में ऐसा कोई कार्य है?

या वैकल्पिक रूप से एक सीमा के बराबर कुछ

[1..8]

यह जावा में वर्बोज़ स्टेटमेंट के लिए एक स्पष्ट प्रतिस्थापन प्रतीत होगा

for (int i = 1; i <= 8; i++) {
    System.out.println(i);
}

कुछ पसंद है

Range.from(1, 8).forEach(i -> System.out.println(i))

हालांकि यह विशेष उदाहरण वास्तव में बहुत अधिक संक्षिप्त नहीं दिखता है ... लेकिन उम्मीद है कि यह अधिक पठनीय है।


2
क्या आपने स्ट्रीम एपीआई का अध्ययन किया है ? जहां तक ​​जेडीके की बात है, वह आपका सबसे अच्छा दांव होना चाहिए। यह एक रेंज फंक्शन है, यही मुझे अब तक मिला है।
मार्को टोपोलनिक

1
@MarkoTopolnik स्ट्रीम क्लास को हटा दिया गया है (अधिक सटीक रूप से इसे कई अन्य वर्गों के बीच विभाजित किया गया है और कुछ तरीकों को हटा दिया गया है)।
assylias

3
आप पाश क्रिया के लिए कहते हैं! यह एक अच्छी बात है कि आप कोबोल के दिनों में आसपास नहीं थे। आरोही संख्या प्रदर्शित करने के लिए कोबोल में 10 से अधिक घोषणात्मक बयान लिए। इन दिनों युवा लोग इसकी सराहना नहीं करते हैं कि उनके पास यह कितना अच्छा है।
गिल्बर्ट ले ब्लैंक

1
@ गिलबर्टलेबैंक वर्बोसिटी का इससे कोई लेना-देना नहीं है। लूप्स कंपोजेबल नहीं हैं, स्ट्रीम हैं। लूप अपरिहार्य पुनरावृत्ति की ओर ले जाते हैं, जबकि धाराएं पुन: उपयोग की अनुमति देती हैं। इस तरह की धाराएं छोरों की तुलना में मात्रात्मक रूप से बेहतर अमूर्त होती हैं और इन्हें प्राथमिकता दी जानी चाहिए।
एलेन ओ'डे

2
@GilbertLeBlanc और हमें बर्फ में, नंगे पैरों में कोड करना था।
दाऊद इब्न करीम

जवाबों:


155

इस विशिष्ट उदाहरण के लिए, आप यह कर सकते हैं:

IntStream.rangeClosed(1, 8)
         .forEach(System.out::println);

यदि आपको 1 से अलग एक कदम की आवश्यकता है, तो आप एक मैपिंग फ़ंक्शन का उपयोग कर सकते हैं, उदाहरण के लिए, 2 के चरण के लिए:

IntStream.rangeClosed(1, 8)
         .map(i -> 2 * i - 1)
         .forEach(System.out::println);

या एक कस्टम पुनरावृत्ति बनाएँ और पुनरावृत्ति के आकार को सीमित करें:

IntStream.iterate(1, i -> i + 2)
         .limit(8)
         .forEach(System.out::println);

4
क्लोज़र पूरी तरह से बेहतर के लिए जावा कोड को बदल देगा। उस दिन का इंतजार ...
मार्को टोपोलनिक

1
@jwenting यह वास्तव में निर्भर करता है - आम तौर पर GUI सामान (स्विंग या JavaFX) के साथ, जो अनाम कक्षाओं के कारण बहुत सारे बॉयलर प्लेट को हटा देता है
assylias

8
@jwenting FP में अनुभव वाले किसी भी व्यक्ति के लिए, कोड जो उच्च-क्रम के कार्यों के चारों ओर घूमता है, एक शुद्ध जीत है। उस अनुभव के बिना किसी के लिए, यह आपके कौशल को उन्नत करने का समय है --- या जोखिम को धूल में पीछे छोड़ दिया जा रहा है।
मार्को टोपोलनिक

2
@MarkoTopolnik आप javadoc के थोड़े नए संस्करण का उपयोग करना चाह सकते हैं (आप 78 के निर्माण की ओर इशारा कर रहे हैं, नवीनतम का निर्माण 105 है: download.java.net/lambda/b105/docs/api/java/util/stream/… )
मार्क रोटेवेल

1
@GememeMoss आप अभी भी एक ही पैटर्न ( IntStream.rangeClosed(1, 8).forEach(i -> methodNoArgs());) का उपयोग कर सकते हैं, लेकिन यह IMO को भ्रमित करता है और उस स्थिति में एक लूप इंगित करता है।
अक्शिलिअस

65

यहाँ एक और तकनीक है जिसे मैंने दूसरे दिन चलाया:

Collections.nCopies(8, 1)
           .stream()
           .forEach(i -> System.out.println(i));

Collections.nCopiesकॉल एक बनाता Listयुक्त nआप जो भी मान प्रदान की प्रतियां। इस मामले में यह बॉक्सिंग Integerमूल्य 1 है। बेशक यह वास्तव में nतत्वों के साथ एक सूची नहीं बनाता है ; यह एक "वर्चुअलाइज्ड" सूची बनाता है जिसमें केवल मान और लंबाई होती है, और getसीमा के भीतर कोई भी कॉल केवल मान लौटाता है। इस nCopiesविधि के आसपास किया गया है क्योंकि कलेक्शन फ्रेमवर्क को JDK 1.2 में वापस लाया गया था। बेशक, इसके परिणाम से एक स्ट्रीम बनाने की क्षमता जावा एसई 8 में जोड़ी गई थी।

बड़ी बात, एक ही तरह की लाइनों के बारे में एक ही काम करने का दूसरा तरीका।

हालांकि, यह तकनीक दृष्टिकोण IntStream.generateऔर IntStream.iterateदृष्टिकोण से तेज है , और आश्चर्यजनक रूप से, यह IntStream.rangeदृष्टिकोण से भी तेज है ।

के लिए iterateऔर generateपरिणाम शायद बहुत आश्चर्य की बात नहीं है। स्ट्रीम फ्रेमवर्क (वास्तव में, इन स्ट्रीम के लिए Spliterators) इस धारणा पर बनाया गया है कि लैम्ब्डा संभावित रूप से प्रत्येक बार अलग-अलग मान उत्पन्न करेगा, और यह कि वे एक अनबिके परिणाम उत्पन्न करेंगे। यह समानांतर विभाजन को विशेष रूप से कठिन बनाता है। iterateविधि क्योंकि प्रत्येक कॉल पिछले एक का परिणाम की आवश्यकता है भी इस मामले के लिए समस्याग्रस्त है। तो धाराओं का इस्तेमाल करके generateऔर iterateदोहराया स्थिरांक पैदा करने के लिए बहुत अच्छी तरह से नहीं करते हैं।

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

Collections.nCopiesतकनीक के बाद से वहाँ का कोई आदिम विशेषज्ञताओं हैं, आदेश मानों का प्रबंधन कैसे में unboxing / मुक्केबाजी करना है List। चूंकि मूल्य हर बार समान होता है, इसलिए यह मूल रूप से एक बार बॉक्सिंग किया जाता है और उस बॉक्स को सभी nप्रतियों द्वारा साझा किया जाता है । मुझे संदेह है कि बॉक्सिंग / अनबॉक्सिंग अत्यधिक अनुकूलित है, यहां तक ​​कि आंतरिक भी है, और यह अच्छी तरह से इनलेट किया जा सकता है।

यहाँ कोड है:

    public static final int LIMIT = 500_000_000;
    public static final long VALUE = 3L;

    public long range() {
        return
            LongStream.range(0, LIMIT)
                .parallel()
                .map(i -> VALUE)
                .map(i -> i % 73 % 13)
                .sum();
}

    public long ncopies() {
        return
            Collections.nCopies(LIMIT, VALUE)
                .parallelStream()
                .mapToLong(i -> i)
                .map(i -> i % 73 % 13)
                .sum();
}

और यहाँ JMH परिणाम हैं: (2.8GHz Core2Duo)

Benchmark                    Mode   Samples         Mean   Mean error    Units
c.s.q.SO18532488.ncopies    thrpt         5        7.547        2.904    ops/s
c.s.q.SO18532488.range      thrpt         5        0.317        0.064    ops/s

Ncopies संस्करण में पर्याप्त मात्रा में विचरण होता है, लेकिन कुल मिलाकर यह रेंज संस्करण की तुलना में 20x तेज आराम से लगता है। (मैं यह मानने को तैयार हूँ कि मैंने कुछ गलत किया है, हालाँकि।)

मुझे आश्चर्य है कि nCopiesतकनीक कितनी अच्छी तरह काम करती है। आंतरिक रूप से यह बहुत विशेष नहीं करता है, वर्चुअलाइज्ड सूची की धारा के साथ बस उपयोग करके कार्यान्वित किया जा रहा है IntStream.range! मुझे उम्मीद थी कि इसे तेजी से आगे बढ़ाने के लिए एक विशेष स्प्लिटेटर बनाना आवश्यक होगा, लेकिन यह पहले से ही बहुत अच्छा लग रहा है।


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

1
तो इसका मतलब है कि LongStream.rangeकी तुलना में काफी धीमी है IntStream.range? इसलिए यह अच्छी बात है कि IntStream(लेकिन LongStreamसभी पूर्णांक प्रकारों के लिए उपयोग ) की पेशकश नहीं करने का विचार छोड़ दिया गया है। ध्यान दें कि अनुक्रमिक उपयोग के मामले में, स्ट्रीम का उपयोग करने का कोई कारण नहीं है: Collections.nCopies(8, 1).forEach(i -> System.out.println(i));वैसा ही करता है Collections.nCopies(8, 1).stream().forEach(i -> System.out.println(i));लेकिन इससे भी अधिक कुशल हो सकता हैCollections.<Runnable>nCopies(8, () -> System.out.println(1)).forEach(Runnable::run);
Holger

1
@ होलजर, ये परीक्षण स्वच्छ प्रकार प्रोफ़ाइल पर किए गए थे, इसलिए वे वास्तविक दुनिया से असंबंधित हैं। संभवतः LongStream.rangeइससे भी बदतर प्रदर्शन करता है, क्योंकि इसमें दो नक्शे LongFunctionहैं, जबकि ncopiesतीन नक्शे हैं IntFunction, ToLongFunctionऔर LongFunction, इस प्रकार सभी लंबोदर मोनोमोर्फिक हैं। पूर्व-प्रदूषित प्रकार प्रोफ़ाइल (जो वास्तविक दुनिया के मामले के करीब है) पर इस परीक्षण को चलाने से पता चलता है कि ncopies1.5x धीमी है।
टैगिर वलेव

1
समयपूर्व अनुकूलन
एफटीडब्ल्यू

1
संपूर्णता के लिए, एक बेंचमार्क देखना अच्छा होगा जो इन दोनों तकनीकों की तुलना एक पुराने पुराने forलूप से करता है। जबकि आपका समाधान Streamकोड से अधिक तेज़ है , मेरा अनुमान है कि एक forलूप इन दोनों को महत्वपूर्ण अंतर से हरा देगा।
टाइपरजर

35

पूर्णता के लिए, और इसलिए भी कि मैं खुद की मदद नहीं कर सका :)

स्थिरांक का एक सीमित क्रम उत्पन्न करना हास्केल में जो कुछ भी आपको दिखाई देगा, वह काफी करीब है, केवल जावा स्तर के शब्दशः के साथ।

IntStream.generate(() -> 1)
         .limit(8)
         .forEach(System.out::println);

() -> 1केवल 1 उत्पन्न करेगा, क्या यह इरादा है? तो आउटपुट होगा 1 1 1 1 1 1 1 1
क्रिश्चियन उलेनबूम

4
हां, ओपी के पहले हास्केल उदाहरण के अनुसार take 8 (repeat 1)। assylias बहुत ज्यादा सभी अन्य मामलों को कवर किया।
क्लेस्ट्रॉस्क

3
Stream<T>generateकुछ अन्य प्रकार की अनंत धारा प्राप्त करने के लिए भी एक सामान्य तरीका है, जिसे उसी तरह सीमित किया जा सकता है।
zstewart

11

एक बार एक दोहराने समारोह के रूप में कहीं परिभाषित किया गया है

public static BiConsumer<Integer, Runnable> repeat = (n, f) -> {
    for (int i = 1; i <= n; i++)
        f.run();
};

आप इसे अभी और फिर इस तरह से उपयोग कर सकते हैं, जैसे:

repeat.accept(8, () -> System.out.println("Yes"));

हास्केल के लिए प्राप्त करना और उसके बराबर होना

take 8 (repeat 1)

आप लिख सकते हैं

StringBuilder s = new StringBuilder();
repeat.accept(8, () -> s.append("1"));

2
यह एक कमाल है। हालांकि मैं इसे संशोधित, यात्रा वापस की संख्या प्रदान करने के लिए बदलने के द्वारा Runnableकरने के लिए Function<Integer, ?>और उसके बाद का उपयोग कर f.apply(i)
फोंस

0

यह समय समारोह को लागू करने के लिए मेरा समाधान है। मैं एक जूनियर हूं, इसलिए मैं मानता हूं कि यह आदर्श नहीं हो सकता है, मुझे यह सुनकर खुशी होगी कि अगर यह किसी भी कारण से एक अच्छा विचार नहीं है।

public static <T extends Object, R extends Void> R times(int count, Function<T, R> f, T t) {
    while (count > 0) {
        f.apply(t);
        count--;
    }
    return null;
}

यहाँ कुछ उदाहरण उपयोग है:

Function<String, Void> greet = greeting -> {
    System.out.println(greeting);
    return null;
};

times(3, greet, "Hello World!");
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.