ทำลายหรือกลับจากสตรีม Java 8 forEach หรือไม่


312

เมื่อใช้การวนซ้ำภายนอกมากกว่าการที่Iterableเราใช้breakหรือreturnจากการปรับปรุงสำหรับแต่ละลูปเป็น:

for (SomeObject obj : someObjects) {
   if (some_condition_met) {
      break; // or return obj
   }
}

เราสามารถbreakหรือreturnใช้การวนซ้ำภายในในนิพจน์แลมบ์ดาของ Java 8 เช่น:

someObjects.forEach(obj -> {
   //what to do here?
})

6
คุณทำไม่ได้ เพียงแค่ใช้จริงforคำสั่ง
Boann


แน่นอนมันเป็นforEach()ไปได้สำหรับ วิธีแก้ปัญหาไม่ดี แต่ก็เป็นไปได้ ดูคำตอบของฉันด้านล่าง
Honza Zidek

พิจารณาแนวทางอื่นคุณเพียงแค่ไม่ต้องการรันโค้ดดังนั้นifเงื่อนไขง่ายๆในการforEachจะทำเคล็ดลับ
โทมัส Decaux

คำตอบ:


373

หากคุณต้องการสิ่งนี้คุณไม่ควรใช้forEachแต่เป็นหนึ่งในวิธีอื่นที่มีอยู่ในสตรีม อันไหนขึ้นอยู่กับว่าเป้าหมายของคุณคืออะไร

ตัวอย่างเช่นหากเป้าหมายของลูปนี้คือการค้นหาองค์ประกอบแรกที่ตรงกับภาคแสดง:

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findFirst();

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

หากคุณต้องการทราบว่ามีองค์ประกอบในคอลเลกชันซึ่งเงื่อนไขเป็นจริงหรือไม่คุณสามารถใช้anyMatch:

boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);

7
วิธีนี้ใช้งานได้หากการค้นหาวัตถุคือเป้าหมาย แต่โดยทั่วไปแล้วเป้าหมายนั้นจะแสดงโดยreturnคำสั่งจากfindSomethingวิธีการ breakมักจะเกี่ยวข้องกับการใช้ในขณะที่ -ชนิดของการดำเนินการ
Marko Topolnik

1
@MarkoTopolnik ใช่โปสเตอร์ต้นฉบับไม่ได้ให้ข้อมูลที่เพียงพอแก่เราในการทราบว่าเป้าหมายคืออะไร "ใช้เวลาในขณะที่" เป็นไปได้ที่สามนอกเหนือจากทั้งสองที่ฉันพูดถึง (มีวิธีง่ายๆในการ "ใช้เวลา" กับสตรีมหรือไม่)
Jesper

3
สิ่งที่เกี่ยวกับเมื่อเป้าหมายคือการใช้พฤติกรรมการยกเลิกอย่างถูกต้อง? เป็นสิ่งที่ดีที่สุดที่เราสามารถทำได้เพื่อโยนข้อยกเว้นรันไทม์ภายในแลมบ์ดาแต่ละครั้งเมื่อเราสังเกตเห็นว่าผู้ใช้ขอยกเลิกหรือไม่
user2163960

1
@HonzaZidek แก้ไข แต่ประเด็นไม่ใช่ว่าเป็นไปได้หรือไม่ แต่สิ่งที่ถูกต้องคือการทำสิ่งต่าง ๆ คุณไม่ควรพยายามบังคับให้ใช้forEachสิ่งนี้ คุณควรใช้วิธีอื่นที่เหมาะสมกว่าแทน
Jesper

1
@ ฉันเห็นด้วยกับคุณฉันเขียนว่าฉันไม่ชอบ "วิธีการแก้ปัญหาข้อยกเว้น" อย่างไรก็ตามข้อความของคุณ "ไม่สามารถทำได้ด้วยforEach" นั้นเป็นเทคนิคที่ไม่ถูกต้อง ฉันชอบโซลูชันของคุณด้วยอย่างไรก็ตามฉันสามารถจินตนาการใช้กรณีที่โซลูชันที่ให้ไว้ในคำตอบของฉันดีกว่า: เมื่อลูปควรจะสิ้นสุดลงเนื่องจากข้อยกเว้นจริง ฉันยอมรับว่าคุณไม่ควรใช้ข้อยกเว้นในการควบคุมโฟลว์
Honza Zidek

53

สิ่งนี้เป็นไปได้สำหรับIterable.forEach()(แต่ไม่น่าเชื่อถือด้วยStream.forEach()) การแก้ปัญหาไม่ดี แต่มันก็เป็นไปได้

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

ตามเอกสารสำหรับIterable.forEach():

ดำเนินการกับการกระทำที่กำหนดสำหรับแต่ละองค์ประกอบของIterable จนกว่าองค์ประกอบทั้งหมดจะได้รับการประมวลผลหรือการกระทำที่เกิดข้อยกเว้น ... ข้อยกเว้นโยนโดยการกระทำจะถูกส่งไปยังผู้โทร

ดังนั้นคุณจะโยนข้อยกเว้นซึ่งจะทำลายลูปภายในทันที

รหัสจะเป็นแบบนี้ - ฉันไม่สามารถพูดได้ว่าฉันชอบแต่มันใช้งานได้ คุณสามารถสร้างระดับของคุณเองซึ่งทอดตัวBreakExceptionRuntimeException

try {
    someObjects.forEach(obj -> {
        // some useful code here
        if(some_exceptional_condition_met) {
            throw new BreakException();
       }
    }
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

แจ้งให้ทราบว่าtry...catchเป็นไม่ได้รอบแสดงออกแลมบ์ดา แต่ทั่วทั้งforEach()วิธีการ หากต้องการทำให้มองเห็นได้ชัดเจนขึ้นให้ดูการถอดความรหัสต่อไปนี้ซึ่งแสดงให้เห็นชัดเจนยิ่งขึ้น:

Consumer<? super SomeObject> action = obj -> {
    // some useful code here
    if(some_exceptional_condition_met) {
        throw new BreakException();
    }
});

try {
    someObjects.forEach(action);
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

37
ฉันคิดว่านี่เป็นวิธีปฏิบัติที่ไม่ดีและไม่ควรถือเป็นวิธีแก้ปัญหา มันอันตรายเพราะมันอาจทำให้เข้าใจผิดสำหรับมือใหม่ ตามผลของ Java 2nd Edition บทที่ 9 รายการ 57: 'ใช้ข้อยกเว้นสำหรับเงื่อนไขพิเศษ' นอกจากนี้ 'ใช้ข้อยกเว้นรันไทม์เพื่อระบุข้อผิดพลาดในการเขียนโปรแกรม' โดยสรุปฉันขอแนะนำให้ทุกคนที่พิจารณาโซลูชันนี้พิจารณาโซลูชัน @Jesper
Louis F.

15
@LouisF ฉันพูดอย่างชัดเจนว่า "ฉันไม่สามารถพูดได้ว่าฉันชอบ แต่ก็ใช้งานได้" OP ขอให้ "วิธีแยกจาก forEach ()" และนี่คือคำตอบ ฉันยอมรับอย่างเต็มที่ว่าสิ่งนี้ไม่ควรใช้เพื่อควบคุมตรรกะทางธุรกิจ อย่างไรก็ตามฉันสามารถจินตนาการถึงกรณีการใช้งานที่มีประโยชน์บางอย่างเช่นการเชื่อมต่อกับทรัพยากรอย่างฉับพลันไม่สามารถใช้ได้ใน forEach () หรือดังนั้นซึ่งการใช้ข้อยกเว้นไม่ใช่วิธีปฏิบัติที่ไม่ดี ฉันได้เพิ่มย่อหน้าลงในคำตอบเพื่อความชัดเจน
Honza Zidek

1
ฉันคิดว่านี่เป็นทางออกที่ดี หลังจากค้นหาคำว่า "java exceptions" และการค้นหาอื่น ๆ ด้วยคำอีกสองสามคำเช่น "best practice" หรือ "ไม่ถูกตรวจสอบ" เป็นต้นฉันเห็นว่ามีข้อโต้แย้งเกี่ยวกับวิธีใช้ข้อยกเว้น ฉันใช้โซลูชันนี้ในรหัสของฉันเนื่องจากกระแสข้อมูลกำลังทำแผนที่ซึ่งใช้เวลาไม่กี่นาที ฉันต้องการให้ผู้ใช้สามารถยกเลิกงานได้ดังนั้นฉันจึงตรวจสอบจุดเริ่มต้นของการคำนวณแต่ละครั้งสำหรับแฟล็ก "isUserCancelRequested" และโยนข้อยกเว้นเมื่อจริง มันสะอาดรหัสข้อยกเว้นถูกแยกออกเป็นส่วนเล็ก ๆ ของรหัสและใช้งานได้
Jason

2
โปรดทราบว่าStream.forEachไม่ได้Stream.forEachให้การรับประกันที่แข็งแกร่งเหมือนกันเกี่ยวกับข้อยกเว้นที่จะถูกส่งไปยังผู้โทรดังนั้นการขว้างปายกเว้นไม่รับประกันว่าจะทำงานในลักษณะนี้
Radiodef

1
@Radiodef นั่นเป็นจุดที่ถูกต้องขอบคุณ โพสต์ต้นฉบับเป็นเรื่องเกี่ยวกับIterable.forEach()แต่ฉันเพิ่มจุดของคุณไปที่ข้อความของฉันเพียงเพื่อความสมบูรณ์
Honza Zidek

43

ผลตอบแทนในแลมบ์ดาเท่ากับการต่อในแต่ละครั้ง แต่ไม่มีอะไรเทียบเท่ากับการหยุดพัก คุณสามารถส่งคืนเพื่อดำเนินการต่อ:

someObjects.forEach(obj -> {
   if (some_condition_met) {
      return;
   }
})

2
วิธีการแก้ปัญหาที่ดีและมีสำนวนเพื่อตอบสนองความต้องการทั่วไป คำตอบที่ได้รับการยอมรับคาดการณ์ความต้องการ
davidxxx

6
ในคำอื่น ๆ นี้ไม่ได้ "หยุดหรือกลับจาก Java 8 กระแส forEach" ซึ่งเป็นคำถามที่เกิดขึ้นจริง
Jaroslav Záruba

1
สิ่งนี้จะยังคง "ดึง" ระเบียนผ่านสตรีมต้นทางแม้ว่าจะไม่ดีหากคุณทำเพจผ่านชุดข้อมูลระยะไกลบางประเภท
Adrian Baker

23

ด้านล่างคุณจะพบโซลูชันที่ฉันใช้ในโครงการ แทนที่จะforEachใช้เพียงallMatch:

someObjects.allMatch(obj -> {
    return !some_condition_met;
});

11

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

ดังนั้นคุณสามารถเขียนforEachConditionalวิธีดังนี้:

public static <T> void forEachConditional(Iterable<T> source,
                                          Predicate<T> action) {
    for (T item : source) {
        if (!action.test(item)) {
            break;
        }
    }
}

แทนที่จะเป็นPredicate<T>เช่นนั้นคุณอาจต้องการกำหนดอินเทอร์เฟซการทำงานของคุณเองด้วยวิธีการทั่วไปแบบเดียวกัน (สิ่งที่รับTและส่งคืนbool) แต่ด้วยชื่อที่บ่งบอกถึงความคาดหวังที่ชัดเจนยิ่งขึ้น - Predicate<T>ไม่เหมาะที่นี่


1
ฉันขอแนะนำว่าจริงๆแล้วการใช้ Streams API และวิธีการทำงานที่นี่จะดีกว่าการสร้างวิธีการช่วยเหลือนี้หาก Java 8 จะถูกใช้อย่างใด
skiwi

3
นี่เป็นการtakeWhileดำเนินการแบบคลาสสิกและคำถามนี้เป็นเพียงหนึ่งในสิ่งที่แสดงให้เห็นว่ารู้สึกขาด API Streams มากแค่ไหน
Marko Topolnik

2
@Marko: takeWhile รู้สึกเหมือนมันจะเป็นรายการที่ให้ผลการดำเนินการไม่ได้ดำเนินการกับแต่ละรายการ แน่นอนใน LINQ ใน. NET มันจะเป็นรูปแบบที่ไม่ดีในการใช้ TakeWhile กับการกระทำที่มีผลข้างเคียง
Jon Skeet

9

คุณสามารถใช้ java8 + rxjava

//import java.util.stream.IntStream;
//import rx.Observable;

    IntStream intStream  = IntStream.range(1,10000000);
    Observable.from(() -> intStream.iterator())
            .takeWhile(n -> n < 10)
            .forEach(n-> System.out.println(n));

8
Java 9 จะให้การสนับสนุนการดำเนินการ takeWhile บนสตรีม
pisaruk

6

สำหรับประสิทธิภาพสูงสุดในการดำเนินการแบบขนานให้ใช้findAny () ซึ่งคล้ายกับ findFirst ()

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findAny();

อย่างไรก็ตามหากต้องการผลลัพธ์ที่มั่นคงให้ใช้ findFirst () แทน

นอกจากนี้โปรดทราบว่ารูปแบบการจับคู่ (anyMatch () / allMatch) จะส่งกลับค่าบูลีนเท่านั้นคุณจะไม่ได้รับวัตถุที่ตรงกัน


6

อัปเดตด้วย Java 9+ ด้วยtakeWhile:

MutableBoolean ongoing = MutableBoolean.of(true);
someobjects.stream()...takeWhile(t -> ongoing.value()).forEach(t -> {
    // doing something.
    if (...) { // want to break;
        ongoing.setFalse();
    }
});

3

ฉันประสบความสำเร็จด้วยบางสิ่งเช่นนี้

  private void doSomething() {
            List<Action> actions = actionRepository.findAll();
            boolean actionHasFormFields = actions.stream().anyMatch(actionHasMyFieldsPredicate());
            if (actionHasFormFields){
                context.addError(someError);
            }
        }
    }

    private Predicate<Action> actionHasMyFieldsPredicate(){
        return action -> action.getMyField1() != null;
    }

3

คุณสามารถทำได้โดยใช้การผสมผสานของ peek (.. ) และ anyMatch (.. )

ใช้ตัวอย่างของคุณ:

someObjects.stream().peek(obj -> {
   <your code here>
}).anyMatch(obj -> !<some_condition_met>);

หรือเพียงแค่เขียนวิธี util ทั่วไป:

public static <T> void streamWhile(Stream<T> stream, Predicate<? super T> predicate, Consumer<? super T> consumer) {
    stream.peek(consumer).anyMatch(predicate.negate());
}

จากนั้นใช้งานเช่นนี้

streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
   <your code here>
});

0

แล้วอันนี้หล่ะ:

final BooleanWrapper condition = new BooleanWrapper();
someObjects.forEach(obj -> {
   if (condition.ok()) {
     // YOUR CODE to control
     condition.stop();
   }
});

ในกรณีที่BooleanWrapperเป็นชั้นที่คุณต้องใช้ในการควบคุมการไหล


3
หรือ AtomicBoolean
Koekje

1
สิ่งนี้จะข้ามการประมวลผลวัตถุเมื่อ!condition.ok()แต่ไม่ได้ป้องกันการforEach()วนซ้ำวัตถุทั้งหมด หากส่วนที่ช้าคือการforEach()ทำซ้ำและไม่ใช่ของผู้บริโภค (เช่นมันจะได้รับวัตถุจากการเชื่อมต่อเครือข่ายที่ช้า) ดังนั้นวิธีการนี้จะไม่มีประโยชน์มาก
zakmck

ใช่คุณพูดถูกคำตอบของฉันค่อนข้างผิดในกรณีนี้
โทมัส Decaux

0
int valueToMatch = 7;
Stream.of(1,2,3,4,5,6,7,8).anyMatch(val->{
   boolean isMatch = val == valueToMatch;
   if(isMatch) {
      /*Do whatever you want...*/
       System.out.println(val);
   }
   return isMatch;
});

มันจะดำเนินการเฉพาะเมื่อพบการจับคู่และหลังจากพบการจับคู่จะหยุดการทำซ้ำ


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