การดำเนินการสตรีมระดับกลางไม่ได้รับการประเมินตามจำนวน


33

ดูเหมือนว่าฉันมีปัญหาในการเข้าใจวิธีที่ Java รวบรวมการดำเนินการในการสตรีมไปป์ไลน์

เมื่อรันโค้ดต่อไปนี้

public
 static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

4คอนโซลเพียงพิมพ์ StringBuilderวัตถุยังคงมีค่า""วัตถุยังคงมีค่า

เมื่อฉันเพิ่มการดำเนินการตัวกรอง: filter(s -> true)

public static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .filter(s -> true)
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

ผลลัพธ์จะเปลี่ยนเป็น:

4
1234

การดำเนินการตัวกรองที่ซ้ำซ้อนนี้ดูเหมือนว่าจะเปลี่ยนพฤติกรรมของไปป์ไลน์ที่สร้างขึ้นได้อย่างไร


2
น่าสนใจ !!!
uneq95

3
ฉันคิดว่านี่เป็นพฤติกรรมการใช้งานเฉพาะ อาจเป็นเพราะสตรีมแรกมีขนาดที่รู้จักกัน แต่สตรีมที่สองไม่มีและขนาดที่กำหนดจะกำหนดว่าจะดำเนินการระหว่างกลางหรือไม่
Andy Turner

เกิดอะไรขึ้นถ้าคุณย้อนกลับตัวกรองและแผนที่
Andy Turner

เมื่อได้ตั้งโปรแกรมสักหน่อยใน Haskell มันมีกลิ่นเล็กน้อยเหมือนการประเมินที่ขี้เกียจเกิดขึ้นที่นี่ การค้นหา google กลับมาสตรีมนั้นมีความเกียจคร้านอย่างแน่นอน อาจเป็นเช่นนั้นหรือ และหากไม่มีตัวกรองหากจาวาฉลาดเพียงพอก็ไม่จำเป็นต้องเรียกใช้การแมป
Frederik

@AndyTurner มันให้ผลเหมือนกันแม้ในการกลับรายการ
uneq95

คำตอบ:


39

การcount()ทำงานของเทอร์มินัลในรุ่น JDK ของฉันสิ้นสุดลงด้วยการดำเนินการกับรหัสต่อไปนี้:

if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
    return spliterator.getExactSizeIfKnown();
return super.evaluateSequential(helper, spliterator);

หากมีการfilter()ดำเนินการในไปป์ไลน์ของการดำเนินงานขนาดของสตรีมซึ่งเป็นที่รู้จักกันในตอนแรกจะไม่สามารถรู้ได้อีกต่อไป (เนื่องจากfilterอาจปฏิเสธองค์ประกอบบางอย่างของสตรีม) ดังนั้นifบล็อกจะไม่ถูกดำเนินการการดำเนินการระหว่างกลางจะถูกดำเนินการและ StringBuilder จึงถูกแก้ไข

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

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


เพราะflatMap()อาจสามารถเปลี่ยนจำนวนองค์ประกอบได้นั่นคือเหตุผลที่ทำไมตอนแรกมันกระตือรือร้น (ตอนนี้ขี้เกียจ)? ดังนั้นทางเลือกจะใช้forEach()และนับแยกหากmap()ในรูปแบบปัจจุบันละเมิดสัญญาฉันเดา
Frederik

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

คุณจะมีข้อเสนอแนะเกี่ยวกับวิธีการบรรลุผลเต็ม 4 1234โดยไม่ต้องใช้ตัวกรองพิเศษหรือผลิตผลข้างเคียงในการดำเนินงานแผนที่ ()?
atalantus

1
int count = array.length; String result = String.join("", array);
JB Nizet

1
หรือคุณสามารถใช้ forEach ถ้าคุณต้องการใช้ StringBuilder หรือคุณสามารถใช้Collectors.joining("")
njzk2

19

ในjdk-9มีเอกสารชัดเจนในเอกสาร java

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

API หมายเหตุ:

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

 List<String> l = Arrays.asList("A", "B", "C", "D");
 long count = l.stream().peek(System.out::println).count();

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


0

นี่ไม่ใช่สิ่งที่. map มีไว้สำหรับ มันควรจะถูกใช้เพื่อเปลี่ยนกระแสของ "บางสิ่งบางอย่าง" ให้กลายเป็นกระแสของ "บางสิ่งบางอย่าง" ในกรณีนี้คุณกำลังใช้ map เพื่อผนวกสตริงไปยัง Stringbuilder ภายนอกหลังจากที่คุณมีกระแสข้อมูลของ "Stringbuilder" ซึ่งแต่ละอันถูกสร้างขึ้นโดยการดำเนินการแม็พต่อท้ายตัวเลขหนึ่งไปยัง Stringbuilder ดั้งเดิม

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

ตัวกรองบังคับให้เรียกใช้เนื้อหาสตรีมเนื่องจากตอนนี้ต้องทำบางสิ่งที่มีความหมายเชิงความคิดกับแต่ละองค์ประกอบสตรีม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.