Phá vỡ hoặc trả về từ luồng Java 8 forEach?


313

Khi sử dụng phép lặp bên ngoài qua một vòng lặp,Iterable chúng tôi sử dụng breakhoặc returntừ vòng lặp nâng cao cho mỗi vòng lặp như:

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

Làm thế nào chúng ta có thể breakhoặc returnsử dụng phép lặp nội bộ trong biểu thức lambda Java 8 như:

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

6
Bạn không thể. Chỉ cần sử dụng một fortuyên bố thực sự .
Boann


Tất nhiên có thể cho forEach(). Giải pháp là không tốt đẹp, nhưng nó có thể. Xem câu trả lời của tôi dưới đây.
Honza Zidek

Hãy xem xét một cách tiếp cận khác, bạn chỉ muốn không thực thi mã , vì vậy, một ifđiều kiện đơn giản bên trong forEachsẽ thực hiện thủ thuật.
Thomas Decaux

Câu trả lời:


374

Nếu bạn cần điều này, bạn không nên sử dụng forEach , nhưng một trong những phương pháp khác có sẵn trên luồng; cái nào, phụ thuộc vào mục tiêu của bạn là gì.

Ví dụ: nếu mục tiêu của vòng lặp này là tìm phần tử đầu tiên khớp với một số vị từ:

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

(Lưu ý: Điều này sẽ không lặp lại toàn bộ bộ sưu tập, bởi vì các luồng được đánh giá một cách lười biếng - nó sẽ dừng ở đối tượng đầu tiên phù hợp với điều kiện).

Nếu bạn chỉ muốn biết liệu có một yếu tố nào trong bộ sưu tập mà điều kiện này là đúng hay không, bạn có thể sử dụng anyMatch:

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

7
Điều này hoạt động nếu tìm một đối tượng là mục tiêu, nhưng mục tiêu đó thường được phục vụ bởi một returntuyên bố từ một findSomethingphương thức. breakthường được kết hợp với mất thời gian hoạt động.
Marko Topolnik

1
@MarkoTopolnik Vâng, người đăng ban đầu chưa cung cấp cho chúng tôi đầy đủ thông tin để biết chính xác mục tiêu là gì; "mất thời gian" là khả năng thứ ba bên cạnh hai khả năng tôi đã đề cập. (Có cách nào đơn giản để thực hiện "mất thời gian" với các luồng không?).
Jesper

3
Thế còn khi mục tiêu là thực hiện đúng hành vi hủy bỏ thì sao? Có phải điều tốt nhất chúng ta có thể làm chỉ là ném ngoại lệ thời gian chạy vào forEach lambda khi chúng tôi nhận thấy rằng người dùng yêu cầu hủy bỏ?
user2163960

1
@HonzaZidek Đã chỉnh sửa, nhưng vấn đề không phải là có thể hay không, mà là cách đúng đắn để làm mọi việc. Bạn không nên cố gắng sử dụng forEachcho việc này; thay vào đó, bạn nên sử dụng một phương pháp khác phù hợp hơn.
Jesper

1
@Jesper Tôi đồng ý với bạn, tôi đã viết rằng tôi không thích "Giải pháp ngoại lệ". Tuy nhiên, từ ngữ của bạn "Điều này là không thể với forEach" về mặt kỹ thuật là không chính xác. Tôi cũng thích giải pháp của bạn, tuy nhiên tôi có thể tưởng tượng các trường hợp sử dụng trong đó giải pháp được cung cấp trong câu trả lời của tôi là thích hợp hơn: khi vòng lặp nên kết thúc vì một ngoại lệ thực sự . Tôi đồng ý rằng bạn thường không nên sử dụng các ngoại lệ để kiểm soát luồng.
Honza Zidek 17/03/2016

54

Điều này có thể cho Iterable.forEach()(nhưng không đáng tin cậy với Stream.forEach()). Giải pháp là không tốt đẹp, nhưng nó có thể.

CẢNH BÁO : Bạn không nên sử dụng nó để kiểm soát logic kinh doanh, mà hoàn toàn để xử lý một tình huống đặc biệt xảy ra trong quá trình thực thi forEach(). Chẳng hạn như tài nguyên đột nhiên dừng truy cập, một trong các đối tượng được xử lý đang vi phạm hợp đồng (ví dụ: hợp đồng nói rằng tất cả các yếu tố trong luồng không được nullnhưng đột nhiên và bất ngờ một trong số chúng lànull ) v.v.

Theo tài liệu cho Iterable.forEach():

Thực hiện hành động đã cho cho từng phần tử của Iterable cho đến khi tất cả các phần tử đã được xử lý hoặc hành động đưa ra một ngoại lệ ... Các ngoại lệ được ném bởi hành động được chuyển tiếp đến người gọi.

Vì vậy, bạn ném một ngoại lệ sẽ ngay lập tức phá vỡ vòng lặp nội bộ.

Mã sẽ giống như thế này - tôi không thể nói tôi thích nó nhưng nó hoạt động. Bạn tạo lớp riêng của bạn BreakExceptionmà mở rộng RuntimeException.

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
}

Lưu ý rằng không phảitry...catch là xung quanh biểu thức lambda, mà là xung quanh toàn bộ phương thức. Để làm cho nó rõ hơn, hãy xem bản phiên mã sau đây cho thấy rõ hơn: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
Tôi nghĩ rằng đây là một thực tiễn xấu và không nên được coi là một giải pháp cho vấn đề. Nó là nguy hiểm vì nó có thể gây hiểu lầm cho người mới bắt đầu. Theo Ấn bản Java hiệu quả lần 2, Chương 9, Mục 57: 'Chỉ sử dụng ngoại lệ cho các điều kiện đặc biệt'. Hơn nữa, 'Sử dụng ngoại lệ thời gian chạy để chỉ ra lỗi lập trình'. Nói một cách dứt khoát, tôi rất khuyến khích bất cứ ai xem xét giải pháp này xem xét giải pháp @Jesper.
Louis F.

15
@LouisF. Tôi nói rõ ràng "Tôi không thể nói tôi thích nó nhưng nó hoạt động". OP đã hỏi "làm thế nào để thoát khỏi forEach ()" và đây là câu trả lời. Tôi hoàn toàn đồng ý rằng điều này không nên được sử dụng để kiểm soát logic kinh doanh. Tuy nhiên tôi có thể tưởng tượng một số trường hợp sử dụng hữu ích, như là một kết nối đến tài nguyên đột nhiên không có sẵn ở giữa forEach () hoặc như vậy, trong đó sử dụng ngoại lệ không phải là thực tiễn xấu. Tôi đã thêm một đoạn để câu trả lời của tôi được rõ ràng.
Honza Zidek

1
Tôi nghĩ rằng đây là một giải pháp tốt. Sau khi tìm kiếm Google cho "ngoại lệ java" và các tìm kiếm khác với một vài từ như "thực tiễn tốt nhất" hoặc "không được kiểm tra", v.v., tôi thấy có tranh cãi về cách sử dụng ngoại lệ. Tôi đã sử dụng giải pháp này trong mã của mình vì luồng đang thực hiện bản đồ sẽ mất vài phút. Tôi muốn người dùng có thể hủy tác vụ vì vậy tôi đã kiểm tra ở đầu mỗi phép tính cho cờ "isUser HủyRequested" và ném một ngoại lệ khi đúng. Thật sạch, mã ngoại lệ được tách ra thành một phần nhỏ của mã và nó hoạt động.
Jason

2
Lưu ý rằng Stream.forEachkhông không cung cấp bảo lãnh mạnh mẽ như vậy về trường hợp ngoại lệ được chuyển tiếp đến người gọi, vì vậy ném một ngoại lệ không được bảo đảm để làm việc theo cách này cho Stream.forEach.
Radiodef

1
@Radiodef Đó là một điểm hợp lệ, cảm ơn. Bài viết gốc là về Iterable.forEach(), nhưng tôi đã thêm điểm của bạn vào văn bản của tôi chỉ vì sự hoàn chỉnh.
Honza Zidek

43

Một sự trở lại trong lambda tương đương với sự tiếp tục trong mỗi lần, nhưng không có gì tương đương với một lần nghỉ. Bạn chỉ có thể quay trở lại để tiếp tục:

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

2
Giải pháp tốt đẹp và thành ngữ để giải quyết các yêu cầu chung. Câu trả lời được chấp nhận ngoại suy yêu cầu.
davidxxx

6
nói cách khác, đây không phải là "Phá vỡ hoặc trả lại từ luồng Java 8 forEach" mà là câu hỏi thực tế
Jaroslav Záruba

1
Điều này vẫn sẽ "kéo" các bản ghi qua luồng nguồn, điều này thật tệ nếu bạn đang xem qua một số loại dữ liệu từ xa.
Adrian Baker

23

Dưới đây bạn tìm thấy giải pháp tôi đã sử dụng trong một dự án. Thay vào đó forEachchỉ sử dụng allMatch:

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

11

Hoặc là bạn cần phải sử dụng một phương pháp trong đó sử dụng một vị chỉ ra xem có nên tiếp tục đi (vì vậy nó có giờ nghỉ thay) hoặc bạn cần ném một ngoại lệ - tất nhiên đó là một cách tiếp cận rất xấu xí.

Vì vậy, bạn có thể viết một forEachConditionalphương pháp như thế này:

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

Thay vào đó Predicate<T>, bạn có thể muốn xác định giao diện chức năng của riêng mình với cùng một phương thức chung (một cái gì đó lấy Tvà trả về a bool) nhưng với các tên biểu thị kỳ vọng rõ ràng hơn - Predicate<T>không lý tưởng ở đây.


1
Tôi đề nghị rằng thực sự sử dụng API Streams và cách tiếp cận chức năng ở đây tốt hơn là tạo phương thức trợ giúp này nếu Java 8 đang được sử dụng bằng mọi cách.
skiwi

3
Đây là takeWhilehoạt động cổ điển và câu hỏi này là một trong những điều chứng minh mức độ thiếu của API Streams được cảm nhận.
Marko Topolnik

2
@Marko: TakeWhile cảm thấy giống như đó sẽ là một hoạt động mang lại các mục, không thực hiện một hành động trên mỗi mục. Chắc chắn trong LINQ in .NET, việc sử dụng TakeWhile với một hành động có tác dụng phụ là một hình thức kém.
Jon Skeet

9

Bạn có thể sử dụng 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 sẽ cung cấp hỗ trợ cho hoạt động TakeWhile trên các luồng.
pisaruk

6

Để có hiệu suất tối đa trong các hoạt động song song, hãy sử dụng findAny () tương tự như findFirst ().

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

Tuy nhiên Nếu muốn có kết quả ổn định, hãy sử dụng findFirst ().

Cũng lưu ý rằng các mẫu phù hợp (anyMatch () / allMatch) sẽ chỉ trả về boolean, bạn sẽ không nhận được đối tượng phù hợp.


6

Cập nhật với Java 9+ với takeWhile:

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

3

Tôi đã đạt được điều gì đó như thế này

  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

Bạn có thể đạt được điều đó bằng cách sử dụng kết hợp peek (..) và anyMatch (..).

Sử dụng ví dụ của bạn:

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

Hoặc chỉ viết một phương thức sử dụng chung:

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

Và sau đó sử dụng nó, như thế này:

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

0

Cái này thì sao:

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

Trường hợp BooleanWrappermột lớp bạn phải thực hiện để kiểm soát dòng chảy.


3
Hoặc nguyên tử?
Koekje

1
Điều này bỏ qua việc xử lý đối tượng khi !condition.ok() , tuy nhiên nó không ngăn được forEach()vòng lặp trên tất cả các đối tượng. Nếu phần chậm là phần forEach()lặp và không phải là người tiêu dùng của nó (ví dụ: phần này lấy các đối tượng từ kết nối mạng chậm), thì cách tiếp cận này không hữu ích.
zakmck

Có bạn đúng, câu trả lời của tôi là khá sai trong trường hợp này.
Thomas 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;
});

Nó sẽ chỉ hoạt động khi tìm thấy kết quả khớp và sau khi tìm thấy kết quả, nó dừng lại.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.