Java 8 Iterable.forEach () vs foreach loop


465

ข้อใดต่อไปนี้เป็นแนวทางปฏิบัติที่ดีกว่าใน Java 8

Java 8:

joins.forEach(join -> mIrc.join(mSession, join));

Java 7:

for (String join : joins) {
    mIrc.join(mSession, join);
}

ฉันมีลูปมากมายที่สามารถ "ทำให้ง่ายขึ้น" กับ lambdas แต่มีข้อได้เปรียบอะไรบ้างจากการใช้มัน? มันจะปรับปรุงประสิทธิภาพและความสามารถในการอ่านได้ไหม?

แก้ไข

ฉันจะขยายคำถามนี้ไปยังวิธีการที่ยาวขึ้น ฉันรู้ว่าคุณไม่สามารถส่งคืนหรือทำลายฟังก์ชั่นหลักจากแลมบ์ดาได้และสิ่งนี้ควรนำมาพิจารณาด้วยเมื่อเปรียบเทียบมัน แต่มีอะไรอีกที่ต้องพิจารณาอีกหรือไม่


10
ไม่มีข้อได้เปรียบด้านประสิทธิภาพที่แท้จริงจากอีกข้อหนึ่ง ตัวเลือกแรกคือสิ่งที่ได้รับแรงบันดาลใจจาก FP (ซึ่งเป็นเรื่องปกติที่พูดถึงเช่น "ดี" และ "ชัดเจน" เพื่อแสดงรหัสของคุณ) ในความเป็นจริง - นี่เป็นคำถาม "สไตล์" ค่อนข้าง
Eugene Loy วันที่

5
@Dwb: ในกรณีนี้นั่นไม่เกี่ยวข้อง forEach ไม่ได้ถูกนิยามว่าขนานหรืออะไรทำนองนั้นดังนั้นสองสิ่งนี้จึงเทียบเท่ากันทางความหมาย แน่นอนว่ามันเป็นไปได้ที่จะใช้ forEach รุ่นขนาน (และอาจมีอยู่ในไลบรารีมาตรฐาน) และในกรณีเช่นนี้แลมบ์ดานิพจน์ไวยากรณ์จะมีประโยชน์มาก
AardvarkSoup

8
@AardvarkSoup อินสแตนซ์ที่เรียกว่า forEach คือ Stream ( lambdadoc.net/api/java/util/stream/Stream.html ) หากต้องการร้องขอการประมวลผลแบบขนานสามารถเขียน joins.parallel (). forEach (... )
mschenk74

13
คือjoins.forEach((join) -> mIrc.join(mSession, join));จริงๆ "ความเรียบง่าย" ของfor (String join : joins) { mIrc.join(mSession, join); }? คุณได้เพิ่มขึ้นนับวรรคตอน 9-12 joinเพื่อประโยชน์ของการซ่อนประเภทของ สิ่งที่คุณทำจริง ๆ ก็คือการวางสองข้อความไปยังหนึ่งบรรทัด
Tom Hawtin - tackline

7
อีกประเด็นที่ควรพิจารณาคือความสามารถในการดักจับตัวแปรที่ จำกัด ของ Java ด้วย Stream.forEach () คุณไม่สามารถอัปเดตตัวแปรในตัวเครื่องได้เนื่องจากการจับภาพทำให้พวกเขาเป็นครั้งสุดท้ายซึ่งหมายความว่าคุณสามารถมีพฤติกรรมที่เป็นรัฐในแลมบ์ดา forEach (เว้นแต่คุณจะเตรียมพร้อมสำหรับความอัปลักษณ์เช่นการใช้ตัวแปรสถานะระดับ)
RedGlyph

คำตอบ:


511

for-eachการปฏิบัติที่ดีคือการใช้งาน นอกจากการละเมิดหลักการKeep It Simple, Stupidแล้วการที่ fangled ใหม่forEach()มีข้อบกพร่องอย่างน้อยดังต่อไปนี้:

  • ไม่สามารถใช้ตัวแปรไม่ใช่ครั้งสุดท้าย ดังนั้นรหัสเช่นนี้ไม่สามารถเปลี่ยนเป็น lambda forEach ได้:

    Object prev = null;
    for(Object curr : list)
    {
        if( prev != null )
            foo(prev, curr);
        prev = curr;
    }
    
  • ไม่สามารถจัดการกับข้อยกเว้นที่เลือก แลมบ์ดาไม่ได้ถูกห้ามไม่ให้โยนข้อยกเว้นที่เลือกไว้ แต่อินเทอร์เฟซการใช้งานทั่วไปเช่นConsumerไม่ต้องประกาศใด ๆ ดังนั้นรหัสใด ๆ ที่พ่นข้อยกเว้นการตรวจสอบจะต้องห่อไว้ในหรือtry-catch Throwables.propagate()แต่แม้ว่าคุณจะทำเช่นนั้นมันก็ไม่ชัดเจนเสมอไปว่าเกิดอะไรขึ้นกับข้อยกเว้นโยน มันสามารถกลืนเข้าไปในอวัยวะภายในได้forEach()

  • จำกัด การควบคุมการไหล returnในแลมบ์ดาเท่ากับcontinueในสำหรับแต่ละ breakแต่ไม่มีเทียบเท่ากับ นอกจากนี้ยังเป็นการยากที่จะทำสิ่งต่าง ๆ เช่นค่าส่งคืนไฟฟ้าลัดวงจรหรือตั้งค่าสถานะ (ซึ่งอาจช่วยลดสิ่งเล็กน้อยหากไม่เป็นการละเมิดกฎที่ไม่มีตัวแปรสุดท้าย ) "นี่ไม่ใช่แค่การเพิ่มประสิทธิภาพ แต่สำคัญเมื่อคุณพิจารณาว่าลำดับบางอย่าง (เช่นการอ่านบรรทัดในไฟล์) อาจมีผลข้างเคียงหรือคุณอาจมีลำดับไม่สิ้นสุด"

  • อาจทำงานแบบขนานซึ่งเป็นสิ่งที่น่ากลัวและน่ากลัวสำหรับทุกคนยกเว้น 0.1% ของรหัสของคุณที่ต้องได้รับการปรับให้เหมาะสม ต้องคำนึงถึงโค้ดแบบขนานใด ๆ (แม้ว่าจะไม่ได้ใช้การล็อกแบบระเหยและอื่น ๆ โดยเฉพาะอย่างยิ่งที่น่ารังเกียจของการทำงานแบบมัลติเธรดแบบดั้งเดิม) ข้อผิดพลาดใด ๆ ที่จะหายาก

  • อาจส่งผลกระทบต่อประสิทธิภาพการทำงานเนื่องจาก JIT ไม่สามารถเพิ่มประสิทธิภาพ forEach () + แลมบ์ดาให้อยู่ในระดับเดียวกับลูปธรรมดาโดยเฉพาะตอนนี้แลมบ์ดาใหม่ โดย "การเพิ่มประสิทธิภาพ" ฉันไม่ได้หมายถึงค่าใช้จ่ายในการโทร lambdas (ซึ่งมีขนาดเล็ก) แต่เป็นการวิเคราะห์ที่ซับซ้อนและการแปลงที่คอมไพเลอร์ JIT ที่ทันสมัยทำงานบนโค้ดที่รัน

  • ถ้าคุณทำจำเป็นต้องขนานมันอาจจะเร็วขึ้นมากและไม่ได้ยากมากที่จะใช้ ExecutorService สตรีมมีทั้งแบบอัตโนมัติ (อ่าน: ไม่ค่อยรู้เรื่องปัญหาของคุณมากนัก) และใช้กลยุทธ์แบบขนาน (อ่าน: ไม่มีประสิทธิภาพสำหรับกรณีทั่วไป) (ใช้การแยกส่วนแบบเรียกซ้ำ )

  • ทำให้การดีบักเกิดความสับสนมากขึ้นเนื่องจากลำดับชั้นการโทรซ้อนกันและการบังคับให้ดำเนินการแบบขนาน โปรแกรมดีบั๊กอาจมีปัญหาในการแสดงตัวแปรจากรหัสที่อยู่รอบ ๆ และสิ่งต่าง ๆ เช่นขั้นตอนอาจไม่ทำงานอย่างที่คาดไว้

  • ลำธารทั่วไปยากที่จะรหัสอ่านและการแก้ปัญหา จริงๆแล้วนี่เป็นความจริงของAPI ที่ " คล่องแคล่ว " ที่ซับซ้อนโดยทั่วไป การรวมกันของคำสั่งเดียวที่ซับซ้อนการใช้งาน generics อย่างหนักและการขาดตัวแปรกลางที่สมคบกันเพื่อสร้างข้อความแสดงข้อผิดพลาดที่สับสนและการแก้จุดบกพร่องที่น่าหงุดหงิด แทนที่จะเป็น "วิธีนี้ไม่มีโอเวอร์โหลดสำหรับประเภท X" คุณจะได้รับข้อความแสดงข้อผิดพลาดใกล้เคียงกับ "บางที่คุณทำประเภทให้ยุ่งเหยิง แต่เราไม่รู้ว่าอยู่ที่ไหนหรืออย่างไร" ในทำนองเดียวกันคุณไม่สามารถผ่านและตรวจสอบสิ่งต่าง ๆ ในตัวดีบักได้อย่างง่ายดายเช่นเดียวกับเมื่อรหัสถูกแบ่งออกเป็นหลายชุดคำสั่งและค่ากลางจะถูกบันทึกลงในตัวแปร ในที่สุดการอ่านรหัสและทำความเข้าใจกับประเภทและพฤติกรรมในแต่ละขั้นตอนของการดำเนินการอาจไม่สำคัญ

  • Sticks ออกเช่น thumb ภาษา Java มีคำสั่งสำหรับแต่ละคำสั่งอยู่แล้ว ทำไมแทนที่ด้วยการเรียกใช้ฟังก์ชัน? เหตุใดจึงสนับสนุนให้ซ่อนผลข้างเคียงไว้ที่นิพจน์ เหตุใดจึงต้องสนับสนุนหนึ่งสมุทรที่เทอะทะ? การผสมประจำสำหรับแต่ละคนและใหม่สำหรับแต่ละวิลลี่ -Nilly เป็นสไตล์ที่ไม่ดี รหัสควรพูดเป็นสำนวน (รูปแบบที่เข้าใจได้ง่ายเนื่องจากการทำซ้ำ) และการใช้สำนวนที่น้อยลงจะชัดเจนยิ่งขึ้นรหัสคือเวลาที่ใช้ในการตัดสินใจว่าสำนวนไหนที่จะใช้ )

อย่างที่คุณเห็นฉันไม่ใช่แฟนตัวยงของ forEach () ยกเว้นในกรณีที่มันสมเหตุสมผล

โดยเฉพาะอย่างยิ่งกับฉันเป็นความจริงที่Streamไม่ได้ใช้Iterable(แม้จะมีวิธีการจริงiterator) และไม่สามารถใช้ในแต่ละสำหรับเฉพาะกับ forEach () ผมขอแนะนำให้หล่อ Streams เข้า Iterables (Iterable<T>)stream::iteratorกับ ทางเลือกที่ดีคือการใช้StreamExซึ่งการแก้ไขจำนวนของปัญหาสตรีม API Iterableรวมทั้งการดำเนินการ

ที่กล่าวว่าforEach()มีประโยชน์สำหรับต่อไปนี้:

  • อะตอม iterating กว่ารายการที่ตรงกัน ก่อนหน้านี้รายการที่สร้างขึ้นมีCollections.synchronizedList()อะตอมโดยคำนึงถึงสิ่งต่าง ๆ เช่นรับหรือตั้งค่า แต่ไม่ปลอดภัยสำหรับเธรดเมื่อวนซ้ำ

  • การดำเนินการแบบคู่ขนาน (โดยใช้กระแสขนานตามความเหมาะสม) สิ่งนี้ช่วยให้คุณประหยัดรหัสสองสามบรรทัดโดยใช้ ExecutorService หากปัญหาของคุณตรงกับสมมติฐานประสิทธิภาพที่สร้างขึ้นใน Streams and Spliterator

  • ภาชนะที่เฉพาะเจาะจงซึ่งเช่นรายการที่ซิงโครไนซ์จะได้รับประโยชน์จากการควบคุมการทำซ้ำ (แม้ว่านี่จะเป็นส่วนใหญ่ในทางทฤษฎียกเว้นว่าผู้คนสามารถนำตัวอย่างมาเพิ่มได้)

  • การเรียกใช้ฟังก์ชันเดียวอย่างหมดจดยิ่งขึ้นโดยใช้forEach()และอาร์กิวเมนต์การอ้างอิงเมธอด (เช่น, list.forEach (obj::someMethod)) อย่างไรก็ตามโปรดจำไว้ว่าจุดที่มีข้อยกเว้นที่เลือกตรวจแก้จุดบกพร่องยากขึ้นและลดจำนวนของสำนวนที่คุณใช้เมื่อเขียนรหัส

บทความที่ฉันใช้สำหรับการอ้างอิง:

แก้ไข:ดูเหมือนว่าข้อเสนอดั้งเดิมสำหรับ lambdas (เช่นhttp://www.javac.info/closures-v06a.html ) ได้แก้ไขปัญหาบางอย่างที่ฉันกล่าวถึง (ในขณะที่เพิ่มปัญหาแทรกซ้อนของพวกเขาเอง)


95
“ เหตุใดจึงสนับสนุนให้ซ่อนผลข้างเคียงในที่แสดงออก” เป็นคำถามที่ผิด ฟังก์ชั่นforEachนี้มีเพื่อสนับสนุนสไตล์การใช้งานเช่นการใช้นิพจน์ที่ไม่มีผลข้างเคียง หากคุณพบกับสถานการณ์การforEachทำงานไม่ดีกับผลข้างเคียงของคุณคุณควรได้รับความรู้สึกว่าคุณไม่ได้ใช้เครื่องมือที่เหมาะสมสำหรับงาน ถ้าอย่างนั้นคำตอบง่ายๆก็คือนั่นเป็นเพราะความรู้สึกของคุณถูกต้องดังนั้นจงอยู่ในวงวนสำหรับสิ่งนั้น forวงคลาสสิกไม่เลิกใช้ ...
Holger

17
@ Holger สามารถforEachนำมาใช้โดยไม่มีผลข้างเคียงได้อย่างไร
Aleksandr Dubinsky

14
เอาล่ะฉันไม่แม่นยำพอforEachเป็นเพียงการดำเนินการสตรีมที่มีไว้สำหรับผลข้างเคียง แต่ไม่ใช่ผลข้างเคียงเช่นโค้ดตัวอย่างของคุณการนับเป็นการreduceดำเนินการทั่วไป ฉันขอแนะนำให้ใช้กฎของ thump เพื่อให้ทุกการดำเนินการที่ดำเนินการกับตัวแปรท้องถิ่นหรือจะมีผลต่อการควบคุมการไหล (รวมถึงการจัดการข้อยกเว้น) ในforวงคลาสสิก เกี่ยวกับคำถามเดิมที่ฉันคิดว่าปัญหาเกิดขึ้นจากความจริงที่ว่ามีคนใช้สตรีมที่มีการforวนรอบอย่างง่าย ๆเหนือแหล่งที่มาของสตรีมจะเพียงพอ ใช้สตรีมที่ใช้forEach()งานได้เท่านั้น
Holger

8
@Holger ตัวอย่างของผลข้างเคียงที่forEachควรจะเป็นคืออะไร?
Aleksandr Dubinsky

29
สิ่งที่ประมวลผลทีละรายการและไม่พยายามที่จะกลายพันธุ์ตัวแปรท้องถิ่น เช่นจัดการไอเท็มเองหรือสั่งพิมพ์เขียน / ส่งไปยังไฟล์สตรีมเครือข่าย ฯลฯ ไม่มีปัญหาสำหรับฉันถ้าคุณถามตัวอย่างเหล่านี้และไม่เห็นแอปพลิเคชันใด ๆ การกรองการทำแผนที่การลดการค้นหาและการรวบรวม (ไปยังระดับที่น้อยกว่า) เป็นการดำเนินการที่ต้องการของสตรีม forEach ดูเหมือนว่าฉันจะสะดวกสำหรับการเชื่อมโยงกับ API ที่มีอยู่ และสำหรับการใช้งานแบบขนานแน่นอน สิ่งเหล่านี้ใช้ไม่ได้กับforลูป
Holger

169

ข้อได้เปรียบมาพิจารณาเมื่อการดำเนินการสามารถดำเนินการในแบบคู่ขนาน (ดูhttp://java.dzone.com/articles/devoxx-2012-java-8-lambda-and - ส่วนเกี่ยวกับการวนซ้ำภายในและภายนอก)

  • ข้อได้เปรียบหลักจากมุมมองของฉันคือการใช้งานสิ่งที่ต้องทำภายในลูปสามารถกำหนดได้โดยไม่ต้องตัดสินใจว่าจะดำเนินการแบบขนานหรือเรียงตามลำดับ

  • หากคุณต้องการให้ลูปของคุณทำงานแบบขนานคุณก็สามารถเขียนได้

     joins.parallelStream().forEach(join -> mIrc.join(mSession, join));

    คุณจะต้องเขียนโค้ดเพิ่มเติมสำหรับการจัดการเธรดและอื่น ๆ

หมายเหตุ:สำหรับคำตอบของฉันฉันถือว่าเข้าร่วมใช้java.util.Streamส่วนต่อประสาน หากการรวมใช้เพียงjava.util.Iterableส่วนต่อประสานสิ่งนี้จะไม่เป็นจริงอีกต่อไป


4
สไลด์ของวิศวกร oracle ที่เขาอ้างถึง ( blogs.oracle.com/darcy/resource/Devoxx/ … ) ไม่ต้องพูดถึงความขนานในแลมบ์ดา การขนานกันอาจเกิดขึ้นในวิธีการรวบรวมจำนวนมากเช่นmap& foldที่ไม่เกี่ยวข้องกับ lambdas
โทมัส Jungblut

1
ดูเหมือนจะไม่จริง ๆ ว่ารหัสของ OP จะได้รับประโยชน์จากการขนานแบบอัตโนมัติที่นี่ (โดยเฉพาะอย่างยิ่งเนื่องจากไม่มีการรับประกันว่าจะมีหนึ่ง) เราไม่รู้จริง ๆ ว่า "mIrc" คืออะไร แต่ "เข้าร่วม" ดูเหมือนจะไม่ได้มีอะไรบางอย่างที่เกินความจริง
Eugene Loy วันที่

11
Stream#forEachและIterable#forEachไม่เหมือนกัน OP Iterable#forEachถูกถามเกี่ยวกับ
gvlasov

2
ฉันใช้รูปแบบ UPDATEX เนื่องจากมีการเปลี่ยนแปลงข้อกำหนดในระหว่างเวลาถามคำถามและเวลาที่คำตอบได้รับการปรับปรุง หากไม่มีประวัติคำตอบมันจะยิ่งทำให้ฉันสับสนมากขึ้น
mschenk74

1
ใครจะกรุณาอธิบายให้ฉันทำไมคำตอบนี้ไม่ถูกต้องถ้าjoinsมีการดำเนินการIterableแทนStream? จากสองสิ่งที่ฉันได้อ่าน OP ควรจะสามารถทำได้joins.stream().forEach((join) -> mIrc.join(mSession, join));และjoins.parallelStream().forEach((join) -> mIrc.join(mSession, join));หากjoinsดำเนินการแล้วIterable
Blueriver

112

เมื่ออ่านคำถามนี้เราจะได้รับความประทับใจว่าการIterable#forEachใช้แลมบ์ดานิพจน์เป็นทางลัด / การแทนที่สำหรับการเขียนลูปดั้งเดิมสำหรับแต่ละลูป นี้เป็นเพียงไม่เป็นความจริง. รหัสนี้จาก OP:

joins.forEach(join -> mIrc.join(mSession, join));

จะไม่ได้ตั้งใจจะให้เป็นทางลัดในการเขียน

for (String join : joins) {
    mIrc.join(mSession, join);
}

และไม่ควรใช้วิธีนี้อย่างแน่นอน แต่มีไว้เพื่อเป็นทางลัด (แม้ว่าจะไม่เหมือนกันทุกประการ) สำหรับการเขียน

joins.forEach(new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
});

และมันเป็นการแทนที่สำหรับโค้ด Java 7 ต่อไปนี้:

final Consumer<T> c = new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
};
for (T t : joins) {
    c.accept(t);
}

การแทนที่เนื้อความของลูปด้วยอินเทอร์เฟซที่ใช้งานได้ตามตัวอย่างด้านบนทำให้โค้ดของคุณชัดเจนยิ่งขึ้น: คุณกำลังบอกว่า (1) เนื้อความของลูปไม่ส่งผลกระทบต่อโค้ดที่ล้อมรอบและโฟลว์การควบคุมและ (2) ร่างกายของวงอาจถูกแทนที่ด้วยการใช้งานที่แตกต่างกันของฟังก์ชั่นโดยไม่ส่งผลกระทบต่อรหัสโดยรอบ การไม่สามารถเข้าถึงตัวแปรที่ไม่ใช่ตัวแปรสุดท้ายของขอบเขตด้านนอกไม่ได้เป็นการขาดดุลของฟังก์ชั่น / lambdas มันเป็นคุณสมบัติที่แยกความหมายของความหมายของIterable#forEachจากความหมายของลูปดั้งเดิมสำหรับแต่ละลูป เมื่อหนึ่งคุ้นเคยกับไวยากรณ์ของIterable#forEachมันทำให้รหัสอ่านง่ายขึ้นเพราะคุณได้รับข้อมูลเพิ่มเติมเกี่ยวกับรหัสทันที

การวนซ้ำแบบดั้งเดิมสำหรับแต่ละวงจะยังคงเป็นการปฏิบัติที่ดี (เพื่อหลีกเลี่ยงการใช้คำว่า " การปฏิบัติที่ดีที่สุด " มากเกินไป) ใน Java แต่นี่ไม่ได้หมายความว่าIterable#forEachควรถือว่าเป็นการปฏิบัติที่ไม่ดีหรือมีสไตล์ไม่ดี เป็นวิธีปฏิบัติที่ดีเสมอใช้เครื่องมือที่เหมาะสมในการทำงานและรวมถึงการผสมลูปดั้งเดิมสำหรับแต่ละลูปด้วยIterable#forEachซึ่งเหมาะสม

เนื่องจากข้อเสียของIterable#forEachได้ถูกกล่าวถึงแล้วในหัวข้อนี้ต่อไปนี้เป็นสาเหตุบางประการที่คุณอาจต้องการใช้Iterable#forEach:

  • วิธีทำให้รหัสของคุณชัดเจนยิ่งขึ้น:ตามที่อธิบายไว้ข้างต้นIterable#forEach สามารถทำให้โค้ดของคุณชัดเจนขึ้นและอ่านได้ในบางสถานการณ์

  • ในการทำให้โค้ดของคุณสามารถขยายและบำรุงรักษาได้มากขึ้น:การใช้ฟังก์ชั่นเนื่องจากเนื้อความของลูปช่วยให้คุณสามารถแทนที่ฟังก์ชั่นนี้ด้วยการใช้งานที่แตกต่างกัน (ดูรูปแบบกลยุทธ์ ) คุณสามารถแทนที่การแสดงออกแลมบ์ดาได้อย่างง่ายดายด้วยการเรียกเมธอดซึ่งอาจถูกเขียนทับโดยคลาสย่อย:

    joins.forEach(getJoinStrategy());

    จากนั้นคุณสามารถกำหนดกลยุทธ์เริ่มต้นโดยใช้ enum ซึ่งใช้อินเทอร์เฟซที่ใช้งานได้ สิ่งนี้ไม่เพียงทำให้โค้ดของคุณสามารถขยายได้มากขึ้น แต่ยังเพิ่มความสามารถในการบำรุงรักษาด้วยเพราะมันจะทำการแยกการใช้ลูปจากการประกาศลูป

  • ที่จะทำให้รหัสของคุณแก้ปัญหาได้มากขึ้น:if(DEBUG)System.out.println()การแยกการดำเนินห่วงจากการประกาศยังสามารถทำให้การแก้จุดบกพร่องมากขึ้นง่ายเพราะคุณอาจจะมีการดำเนินการแก้ปัญหาเฉพาะที่พิมพ์ออกข้อความแก้ปัญหาโดยไม่จำเป็นต้องถ่วงรหัสหลักของคุณด้วย การใช้งานการตรวจแก้จุดบกพร่องอาจเป็นผู้รับมอบสิทธิ์ที่ประดับประดาการใช้งานจริง

  • ในการปรับโค้ดให้เหมาะสมกับประสิทธิภาพ:ขัดกับคำยืนยันบางอย่างในเธรดIterable#forEach นี้ให้ประสิทธิภาพที่ดีกว่าลูปดั้งเดิมสำหรับแต่ละลูปอย่างน้อยเมื่อใช้ ArrayList และรันฮอตสปอตในโหมด "-client" ในขณะที่การเพิ่มประสิทธิภาพนี้มีขนาดเล็กและไม่สำคัญสำหรับกรณีการใช้งานส่วนใหญ่มีสถานการณ์ที่ประสิทธิภาพพิเศษนี้สามารถสร้างความแตกต่างได้ Iterable#forEachเช่นดูแลห้องสมุดแน่นอนจะต้องการที่จะประเมินว่าบางส่วนของการใช้งานวงที่มีอยู่ของพวกเขาควรจะถูกแทนที่ด้วย

    เพื่อสนับสนุนคำแถลงนี้ด้วยข้อเท็จจริงฉันได้ทำไมโครมาตรฐานด้วยCaliperแล้ว นี่คือรหัสทดสอบ (จำเป็นต้องใช้ Caliper ล่าสุดจากคอมไพล์):

    @VmOptions("-server")
    public class Java8IterationBenchmarks {
    
        public static class TestObject {
            public int result;
        }
    
        public @Param({"100", "10000"}) int elementCount;
    
        ArrayList<TestObject> list;
        TestObject[] array;
    
        @BeforeExperiment
        public void setup(){
            list = new ArrayList<>(elementCount);
            for (int i = 0; i < elementCount; i++) {
                list.add(new TestObject());
            }
            array = list.toArray(new TestObject[list.size()]);
        }
    
        @Benchmark
        public void timeTraditionalForEach(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : list) {
                    t.result++;
                }
            }
            return;
        }
    
        @Benchmark
        public void timeForEachAnonymousClass(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(new Consumer<TestObject>() {
                    @Override
                    public void accept(TestObject t) {
                        t.result++;
                    }
                });
            }
            return;
        }
    
        @Benchmark
        public void timeForEachLambda(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(t -> t.result++);
            }
            return;
        }
    
        @Benchmark
        public void timeForEachOverArray(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : array) {
                    t.result++;
                }
            }
        }
    }

    และนี่คือผลลัพธ์:

    เมื่อรันด้วย "-client" Iterable#forEachประสิทธิภาพสูงกว่าแบบดั้งเดิมสำหรับลูปผ่าน ArrayList แต่ก็ยังช้ากว่าการวนซ้ำแบบอาร์เรย์โดยตรง เมื่อรันด้วย "-server" ประสิทธิภาพของวิธีการทั้งหมดจะใกล้เคียงกัน

  • เพื่อให้การสนับสนุนเพิ่มเติมสำหรับการประมวลผลแบบขนาน:มีการกล่าวไว้แล้วว่าความเป็นไปได้ในการใช้งานอินเทอร์เฟซของการทำงานIterable#forEachแบบขนานโดยใช้สตรีมนั้นเป็นสิ่งสำคัญอย่างแน่นอน เนื่องจากCollection#parallelStream()ไม่รับประกันว่าลูปจะดำเนินการแบบขนานจริง ๆ จึงต้องพิจารณาว่านี่เป็นฟีเจอร์เสริม ด้วยการวนซ้ำในรายการของคุณด้วยlist.parallelStream().forEach(...);คุณพูดอย่างชัดเจน: ลูปนี้รองรับการประมวลผลแบบขนาน แต่ไม่ได้ขึ้นอยู่กับมัน นี่เป็นฟีเจอร์และไม่ใช่การขาดดุล!

    ด้วยการย้ายการตัดสินใจสำหรับการประมวลผลแบบขนานออกไปจากการใช้งานลูปจริงของคุณคุณอนุญาตการปรับให้เหมาะสมของโค้ดของคุณโดยไม่ส่งผลกระทบต่อโค้ดซึ่งเป็นสิ่งที่ดี นอกจากนี้หากการใช้งานการสตรีมแบบขนานเริ่มต้นไม่ตรงกับความต้องการของคุณจะไม่มีใครขัดขวางคุณจากการนำเสนอการใช้งานของคุณเอง คุณสามารถจัดหาคอลเลกชันที่ได้รับการปรับให้เหมาะสมที่สุดขึ้นอยู่กับระบบปฏิบัติการที่รองรับขนาดของคอลเลกชันตามจำนวนคอร์และการตั้งค่าที่ต้องการ:

    public abstract class MyOptimizedCollection<E> implements Collection<E>{
        private enum OperatingSystem{
            LINUX, WINDOWS, ANDROID
        }
        private OperatingSystem operatingSystem = OperatingSystem.WINDOWS;
        private int numberOfCores = Runtime.getRuntime().availableProcessors();
        private Collection<E> delegate;
    
        @Override
        public Stream<E> parallelStream() {
            if (!System.getProperty("parallelSupport").equals("true")) {
                return this.delegate.stream();
            }
            switch (operatingSystem) {
                case WINDOWS:
                    if (numberOfCores > 3 && delegate.size() > 10000) {
                        return this.delegate.parallelStream();
                    }else{
                        return this.delegate.stream();
                    }
                case LINUX:
                    return SomeVerySpecialStreamImplementation.stream(this.delegate.spliterator());
                case ANDROID:
                default:
                    return this.delegate.stream();
            }
        }
    }

    สิ่งที่ดีที่นี่คือการใช้ลูปของคุณไม่จำเป็นต้องรู้หรือใส่ใจในรายละเอียดเหล่านี้


5
คุณมีมุมมองที่น่าสนใจในการสนทนานี้และนำคะแนนมาใช้ ฉันจะพยายามพูดกับพวกเขา คุณเสนอให้สลับระหว่างforEachและfor-eachขึ้นอยู่กับเกณฑ์บางอย่างเกี่ยวกับลักษณะของเนื้อความลูป ภูมิปัญญาและวินัยในการปฏิบัติตามกฎดังกล่าวเป็นจุดเด่นของโปรแกรมเมอร์ที่ดี กฎเหล่านี้ก็เป็นสิ่งที่ไม่ดีเช่นกันเพราะคนรอบข้างไม่ทำตามหรือไม่เห็นด้วย เช่นการใช้ข้อยกเว้นที่ทำเครื่องหมายไว้และไม่ได้ตรวจสอบ สถานการณ์นี้ดูเหมือนจะเหมาะสมยิ่งขึ้น แต่ถ้าร่างกาย "ไม่ส่งผลกระทบต่อรหัสเซอร์ราวด์หรือการควบคุมการไหล" ก็ไม่ได้คำนึงถึงฟังก์ชั่นที่ดีกว่านี้หรือ
Aleksandr Dubinsky

4
ขอบคุณสำหรับความคิดเห็นรายละเอียด Aleksandr But, if the body "does not affect surround code or flow control," isn't factoring it out as a function better?. ใช่สิ่งนี้มักจะเกิดขึ้นในความคิดของฉัน - ให้คำนึงถึงลูปเหล่านี้เนื่องจากฟังก์ชั่นเป็นผลที่ตามธรรมชาติ
Balder

2
เกี่ยวกับปัญหาด้านประสิทธิภาพ - ฉันเดาว่ามันขึ้นอยู่กับลักษณะของลูปเป็นอย่างมาก ในโครงการที่ฉันกำลังทำงานฉันใช้ลูปแบบฟังก์ชันคล้ายกับIterable#forEachก่อนหน้า Java 8 เพียงเพราะการเพิ่มประสิทธิภาพ โปรเจ็กต์ที่สงสัยมีหนึ่งลูปหลักคล้ายกับลูปเกมโดยมีจำนวนลูปย่อยที่ซ้อนกันที่ไม่ได้กำหนดซึ่งลูกค้าสามารถปลั๊กอินผู้เข้าร่วมลูปเป็นฟังก์ชันได้ Iteable#forEachโครงสร้างซอฟต์แวร์ดังกล่าวอย่างมากได้รับประโยชน์จาก
Balder

7
มีประโยคหนึ่งที่ส่วนท้ายสุดของคำวิจารณ์ของฉัน: "รหัสควรพูดเป็นสำนวนและสำนวนที่น้อยกว่านั้นจะใช้รหัสที่ชัดเจนยิ่งขึ้นรหัสคือและใช้เวลาน้อยลงในการตัดสินใจว่าสำนวนใดที่จะใช้" ฉันเริ่มซาบซึ้งในจุดนี้มากเมื่อฉันเปลี่ยนจาก C # เป็น Java
Aleksandr Dubinsky

7
นั่นเป็นข้อโต้แย้งที่ไม่ดี คุณสามารถใช้มันเพื่อพิสูจน์สิ่งที่คุณต้องการ: ทำไมคุณไม่ควรใช้ for for loop เพราะในขณะที่ loop นั้นดีพอและนั่นก็เป็นสำนวนที่น้อยกว่า Heck เหตุใดจึงใช้วนรอบสลับหรือลอง / จับคำสั่งเมื่อข้ามไปสามารถทำทั้งหมดนั้นและอื่น ๆ
tapichu

13

forEach()สามารถนำมาใช้เพื่อให้เร็วกว่าสำหรับแต่ละลูปเนื่องจากตัววนซ้ำรู้วิธีที่ดีที่สุดในการวนรอบองค์ประกอบเมื่อเทียบกับวิธีตัววนซ้ำมาตรฐาน ดังนั้นความแตกต่างคือวงภายในหรือวงภายนอก

ตัวอย่างเช่นArrayList.forEach(action)อาจนำมาใช้เป็นเพียงแค่

for(int i=0; i<size; i++)
    action.accept(elements[i])

ซึ่งตรงข้ามกับการวนรอบแต่ละครั้งซึ่งต้องใช้การนั่งร้านเป็นจำนวนมาก

Iterator iter = list.iterator();
while(iter.hasNext())
    Object next = iter.next();
    do something with `next`

อย่างไรก็ตามเราต้องคิดค่าใช้จ่ายสองครั้งด้วยการใช้forEach()อันแรกคือการทำแลมบ์ดาออบเจ็กต์อีกอันกำลังเรียกใช้แลมบ์ดา พวกเขาอาจไม่สำคัญ

ดูhttp://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/สำหรับการเปรียบเทียบการทำซ้ำภายใน / ภายนอกสำหรับกรณีการใช้งานที่แตกต่างกัน


8
ทำไม iterable ถึงรู้วิธีที่ดีที่สุด แต่ iterator ไม่ได้?
mschenk74

2
ไม่มีความแตกต่างที่สำคัญ แต่จำเป็นต้องมีรหัสเพิ่มเติมเพื่อให้สอดคล้องกับส่วนต่อประสานตัววนซ้ำซึ่งอาจมีราคาแพงกว่า
ZhongYu

1
@ zhong.j.yu หากคุณใช้งานคอลเล็กชันคุณยังสามารถใช้ Iterable ได้อีกด้วย ดังนั้นไม่มีรหัสค่าใช้จ่ายในแง่ของ "การเพิ่มรหัสเพิ่มเติมเพื่อใช้วิธีการอินเทอร์เฟซที่ขาดหายไป" ถ้านั่นคือจุดของคุณ ดังที่ mschenk74 กล่าวว่าดูเหมือนจะไม่มีเหตุผลใด ๆ ที่ทำให้คุณไม่สามารถบิดตัววนซ้ำเพื่อรู้วิธีวนซ้ำคอลเลกชันของคุณในวิธีที่ดีที่สุด ฉันยอมรับว่าอาจมีค่าใช้จ่ายสำหรับการสร้างตัววนซ้ำ แต่อย่างจริงจังสิ่งเหล่านั้นมักจะถูกจนคุณสามารถพูดได้ว่าพวกเขามีค่าใช้จ่ายเป็นศูนย์ ...
Eugene Loy

4
ตัวอย่างเช่นการวนต้นไม้: void forEach(Consumer<T> v){leftTree.forEach(v);v.accept(rootElem);rightTree.forEach(v);}นี่คือสง่างามยิ่งกว่าการวนซ้ำภายนอกและคุณสามารถตัดสินใจได้ว่าจะซิงโครไนซ์อย่างไรดีที่สุด
ratchet freak

1
ความสนุกเพียงอย่างเดียวที่แสดงความคิดเห็นในString.joinวิธีการ (ไม่ถูกต้องการเข้าร่วมที่ไม่ถูกต้อง) คือ "จำนวนองค์ประกอบที่ไม่น่าจะคุ้มกับค่าใช้จ่ายของอาร์เรย์ Arrays.stream" ดังนั้นพวกเขาใช้หรูสำหรับห่วง
Tom Hawtin - tackline

7

TL; DR : List.stream().forEach()เร็วที่สุด

ฉันรู้สึกว่าฉันควรเพิ่มผลลัพธ์ของฉันจากการเปรียบเทียบซ้ำ ฉันใช้วิธีที่ง่ายมาก (ไม่มีกรอบการเปรียบเทียบ) และทำการเปรียบเทียบ 5 วิธีที่แตกต่างกัน:

  1. คลาสสิก for
  2. คลาสสิก foreach
  3. List.forEach()
  4. List.stream().forEach()
  5. List.parallelStream().forEach

ขั้นตอนการทดสอบและพารามิเตอร์

private List<Integer> list;
private final int size = 1_000_000;

public MyClass(){
    list = new ArrayList<>();
    Random rand = new Random();
    for (int i = 0; i < size; ++i) {
        list.add(rand.nextInt(size * 50));
    }    
}
private void doIt(Integer i) {
    i *= 2; //so it won't get JITed out
}

รายการในชั้นนี้จะต้องซ้ำกันและมีบางส่วนdoIt(Integer i)นำไปใช้กับสมาชิกทุกคนทุกครั้งผ่านวิธีการที่แตกต่างกัน ในชั้นเรียนหลักฉันใช้วิธีการทดสอบสามครั้งเพื่ออุ่นเครื่อง JVM จากนั้นฉันเรียกใช้วิธีการทดสอบ 1,000 ครั้งโดยรวมเวลาที่ใช้สำหรับวิธีการวนซ้ำแต่ละครั้ง (โดยใช้System.nanoTime()) หลังจากเสร็จแล้วฉันก็หารผลรวมนั้นด้วย 1,000 และนั่นคือผลลัพธ์เวลาเฉลี่ย ตัวอย่าง:

myClass.fored();
myClass.fored();
myClass.fored();
for (int i = 0; i < reps; ++i) {
    begin = System.nanoTime();
    myClass.fored();
    end = System.nanoTime();
    nanoSum += end - begin;
}
System.out.println(nanoSum / reps);

ฉันใช้ตัวนี้กับซีพียู Core i5 4 กับรุ่น java 1.8.0_05

คลาสสิก for

for(int i = 0, l = list.size(); i < l; ++i) {
    doIt(list.get(i));
}

เวลาดำเนินการ: 4.21 ms

คลาสสิก foreach

for(Integer i : list) {
    doIt(i);
}

เวลาดำเนินการ: 5.95 ms

List.forEach()

list.forEach((i) -> doIt(i));

เวลาดำเนินการ: 3.11 ms

List.stream().forEach()

list.stream().forEach((i) -> doIt(i));

เวลาดำเนินการ: 2.79 ms

List.parallelStream().forEach

list.parallelStream().forEach((i) -> doIt(i));

เวลาดำเนินการ: 3.6 ms


23
คุณจะรับหมายเลขเหล่านั้นได้อย่างไร คุณใช้เฟรมเวิร์กมาตรฐานใด หากคุณไม่ได้ใช้เพียงแค่System.out.printlnแสดงข้อมูลอย่างไร้เดียงสาผลลัพธ์ทั้งหมดนั้นไร้ประโยชน์
Luiggi Mendoza

2
ไม่มีกรอบ System.nanoTime()ฉันใช้ หากคุณอ่านคำตอบคุณจะเห็นว่ามันทำอย่างไร ผมไม่คิดว่ามันจะทำให้เห็นไร้ประโยชน์เช่นนี้เป็นญาติคำถาม ฉันไม่สนใจว่าวิธีการใดวิธีการหนึ่งทำได้ดีฉันสนใจว่ามันทำได้ดีเพียงใดเมื่อเปรียบเทียบกับวิธีอื่น
Assaf

31
และนั่นคือจุดประสงค์ของการวัดขนาดเล็กที่ดี เนื่องจากคุณไม่ได้ปฏิบัติตามข้อกำหนดดังกล่าวผลลัพธ์จึงไร้ประโยชน์
Luiggi Mendoza

6
ฉันสามารถแนะนำให้รู้จัก JMH แทนนี่คือสิ่งที่ใช้สำหรับ Java เองและใช้ความพยายามอย่างมากในการรับหมายเลขที่ถูกต้อง: openjdk.java.net/projects/code-tools/jmh
dsvensson

1
ฉันเห็นด้วยกับ @LuiggiMendoza ไม่มีวิธีที่จะรู้ว่าผลลัพธ์เหล่านี้มีความสอดคล้องหรือถูกต้อง พระเจ้าทรงทราบว่าฉันได้ทำการวัดประสิทธิภาพมาแล้วหลายครั้งเพื่อรายงานผลลัพธ์ที่แตกต่างกันโดยเฉพาะอย่างยิ่งขึ้นอยู่กับลำดับการทำซ้ำขนาดและสิ่งที่ไม่
mmm

6

ฉันรู้สึกว่าฉันจำเป็นต้องเพิ่มความคิดเห็นของฉันเล็กน้อย ...

เกี่ยวกับกระบวนทัศน์ \ style

นั่นอาจเป็นสิ่งที่น่าสังเกตมากที่สุด FP กลายเป็นที่นิยมเนื่องจากสิ่งที่คุณสามารถหลีกเลี่ยงผลข้างเคียง ฉันจะไม่เจาะลึกลงไปถึงข้อดีที่คุณจะได้รับจากสิ่งนี้เนื่องจากสิ่งนี้ไม่เกี่ยวข้องกับคำถาม

อย่างไรก็ตามฉันจะบอกว่าการทำซ้ำโดยใช้ Iterable.forEach ได้รับแรงบันดาลใจจาก FP และค่อนข้างเป็นผลมาจากการนำ FP มาสู่ Java (แดกดันฉันจะบอกว่าไม่มีประโยชน์มากสำหรับ forEach ใน FP บริสุทธิ์เนื่องจากไม่ทำอะไรเลยยกเว้นการแนะนำ ผลข้างเคียง).

ในที่สุดฉันจะบอกว่ามันค่อนข้างเป็นเรื่องของรสนิยม \ style \ กระบวนทัศน์ที่คุณกำลังเขียน

เกี่ยวกับการขนาน

จากมุมมองประสิทธิภาพไม่มีข้อได้เปรียบที่โดดเด่นจากการใช้ Iterable.forEach มากกว่า foreach (... )

เอกสารอ้างอิงอย่างเป็นทางการของIterable.forEach :

ดำเนินการตามที่กำหนดในเนื้อหาของ Iterable ในองค์ประกอบลำดับที่เกิดขึ้นเมื่อวนซ้ำจนกว่าองค์ประกอบทั้งหมดจะได้รับการประมวลผลหรือการกระทำที่เกิดข้อยกเว้น

... เช่นเอกสารค่อนข้างชัดเจนว่าจะไม่มีการขนานโดยนัย การเพิ่มหนึ่งรายการเป็นการละเมิด LSP

ขณะนี้มี "คอลเลกชันคู่ขนาน" ที่สัญญาไว้ใน Java 8 แต่จะทำงานกับสิ่งที่คุณต้องการให้ฉันชัดเจนยิ่งขึ้นและใช้ความระมัดระวังเป็นพิเศษในการใช้ (ดูตัวอย่างคำตอบของ mschenk74)

BTW: ในกรณีนี้Stream.forEachจะถูกนำมาใช้และไม่รับประกันว่างานจริงจะดำเนินการในแบบคู่ขนาน (ขึ้นอยู่กับการรวบรวมพื้นฐาน)

อัปเดต:อาจไม่ชัดเจนและยืดออกเล็กน้อย แต่มีอีกแง่มุมหนึ่งของสไตล์และมุมมองการอ่าน

ครั้งแรกของทั้งหมด - forloops เก่าธรรมดาล้วนและเก่า ทุกคนรู้จักพวกเขาอยู่แล้ว

ข้อสองและสำคัญกว่า - คุณอาจต้องการใช้ Iterable.forEach กับ lambdas แบบซับไลน์เท่านั้น หาก "ร่างกาย" หนักขึ้น - พวกเขามักจะอ่านไม่ออก คุณมี 2 ตัวเลือกจากที่นี่ - ใช้คลาสภายใน (yuck) หรือใช้ forloop เก่าแบบธรรมดา คนมักจะรำคาญเมื่อเห็นสิ่งเดียวกัน (iteratins มากกว่าคอลเลกชัน) การทำ vays / style ต่าง ๆ ใน codebase เดียวกันและดูเหมือนว่าจะเป็นกรณีนี้

อีกครั้งนี่อาจจะใช่หรือไม่ใช่ปัญหา ขึ้นอยู่กับคนที่ทำงานกับรหัส


1
ความเท่าเทียมไม่ต้องการ "คอลเลกชันแบบขนาน" ใหม่ ขึ้นอยู่กับว่าคุณขอกระแสต่อเนื่อง (โดยใช้ collection.stream ()) หรือขนาน (ใช้ collection.parallelStream ())
JB Nizet

@JBNizet ตามเอกสาร Collection.parallelStream () ไม่รับประกันว่าการใช้งานคอลเลกชันจะส่งคืนพารัลล์สตรีม ฉันกำลังสงสัยตัวเองจริง ๆ เมื่อสิ่งนี้อาจเกิดขึ้น แต่อาจขึ้นอยู่กับคอลเลกชัน
Eugene Loy วันที่

ตกลง นอกจากนี้ยังขึ้นอยู่กับการสะสม แต่ประเด็นของฉันคือลูป foreach แบบขนานนั้นพร้อมใช้งานกับคอลเลกชันมาตรฐานทั้งหมดแล้ว (ArrayList ฯลฯ ) ไม่จำเป็นต้องรอ "คอลเลกชันแบบขนาน"
JB Nizet

@JBNizet เห็นด้วยกับประเด็นของคุณ แต่นั่นไม่ใช่สิ่งที่ฉันหมายถึงโดย "คอลเล็กชันขนาน" ในตอนแรก ฉันอ้างอิง Collection.parallelStream () ซึ่งเพิ่มใน Java 8 เป็น "ชุดสะสมแบบขนาน" โดยการเปรียบเทียบกับแนวคิดของ Scala ที่ทำเหมือนกันมาก นอกจากนี้ไม่แน่ใจว่ามันถูกเรียกในบิตของ JSR ได้อย่างไรฉันเห็นเอกสารสองฉบับที่ใช้คำศัพท์เดียวกันสำหรับคุณลักษณะ Java 8 นี้
Eugene Loy วันที่

1
สำหรับย่อหน้าสุดท้ายคุณสามารถใช้การอ้างอิงฟังก์ชั่น:collection.forEach(MyClass::loopBody);
วงล้อประหลาด

6

หนึ่งในforEachข้อ จำกัด ของฟังก์ชั่น upleasing มากที่สุดคือการขาดการสนับสนุนข้อยกเว้นตรวจสอบ

วิธีแก้ไขปัญหาหนึ่งที่เป็นไปได้คือการแทนที่เทอร์มินัลforEachด้วยลูป foreach แบบเก่าแบบธรรมดา:

    Stream<String> stream = Stream.of("", "1", "2", "3").filter(s -> !s.isEmpty());
    Iterable<String> iterable = stream::iterator;
    for (String s : iterable) {
        fileWriter.append(s);
    }

นี่คือรายการคำถามยอดนิยมที่มีวิธีแก้ปัญหาอื่น ๆ เกี่ยวกับการจัดการข้อยกเว้นที่ตรวจสอบภายใน lambdas และลำธาร:

ฟังก์ชั่น Java 8 Lambda ที่ส่งข้อยกเว้น?

Java 8: Lambda-Streams, กรองตามวิธีการยกเว้น

ฉันจะโยนข้อยกเว้นตรวจสอบจากภายในสตรีม Java 8 ได้อย่างไร

Java 8: การตรวจสอบข้อยกเว้นบังคับได้รับการจัดการในการแสดงออกแลมบ์ดา ทำไมต้องบังคับไม่ใช่เลือกหรือไม่


3

ข้อดีของการใช้ Java 1.8 forEach มากกว่า 1.7 Enhanced for loop คือขณะที่เขียนโค้ดคุณสามารถมุ่งเน้นไปที่ตรรกะทางธุรกิจเท่านั้น

วิธี forEach ใช้วัตถุ java.util.function.Consumer เป็นอาร์กิวเมนต์ดังนั้นมันช่วยในการมีตรรกะทางธุรกิจของเราในสถานที่แยกต่างหากที่คุณสามารถนำมาใช้ใหม่ได้ตลอดเวลา

ดูตัวอย่างด้านล่าง

  • ที่นี่ฉันได้สร้างคลาสใหม่ที่จะแทนที่ยอมรับวิธีการเรียนจาก Consumer Class ซึ่งคุณสามารถเพิ่มฟังก์ชั่นเพิ่มเติมได้มากกว่าการย้ำ .. !!!!!!

    class MyConsumer implements Consumer<Integer>{
    
        @Override
        public void accept(Integer o) {
            System.out.println("Here you can also add your business logic that will work with Iteration and you can reuse it."+o);
        }
    }
    
    public class ForEachConsumer {
    
        public static void main(String[] args) {
    
            // Creating simple ArrayList.
            ArrayList<Integer> aList = new ArrayList<>();
            for(int i=1;i<=10;i++) aList.add(i);
    
            //Calling forEach with customized Iterator.
            MyConsumer consumer = new MyConsumer();
            aList.forEach(consumer);
    
    
            // Using Lambda Expression for Consumer. (Functional Interface) 
            Consumer<Integer> lambda = (Integer o) ->{
                System.out.println("Using Lambda Expression to iterate and do something else(BI).. "+o);
            };
            aList.forEach(lambda);
    
            // Using Anonymous Inner Class.
            aList.forEach(new Consumer<Integer>(){
                @Override
                public void accept(Integer o) {
                    System.out.println("Calling with Anonymous Inner Class "+o);
                }
            });
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.