Di chuyển đến mục tiếp theo bằng cách sử dụng vòng lặp foreach Java 8 trong luồng


126

Tôi gặp sự cố với luồng của Java 8 foreach khi cố gắng chuyển sang mục tiếp theo trong vòng lặp. Tôi không thể đặt lệnh như continue;, chỉ return;hoạt động nhưng bạn sẽ thoát khỏi vòng lặp trong trường hợp này. Tôi cần chuyển sang mục tiếp theo trong vòng lặp. Làm thế nào tôi có thể làm điều đó?

Ví dụ (không hoạt động):

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
            filteredLines = lines.filter(...).foreach(line -> {
           ...
           if(...)
              continue; // this command doesn't working here
    });
}

Ví dụ (đang làm việc):

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
    filteredLines = lines.filter(...).collect(Collectors.toList());
}

for(String filteredLine : filteredLines){
   ...
   if(...)
      continue; // it's working!
}

Vui lòng đăng một ví dụ đầy đủ nhưng tối thiểu để dễ trả lời hơn.
Tunaki

Tôi cần chuyển sang mục tiếp theo trong vòng lặp.
Viktor V.

2
Quan điểm của anh ấy là, với đoạn mã bạn đã hiển thị, không có mã continuevẫn sẽ chuyển sang mục tiếp theo mà không có bất kỳ thay đổi chức năng nào.
DoubleDouble

Nếu mục tiêu là tránh thực hiện các dòng đến sau cuộc gọi tiếp tục và bạn không hiển thị cho chúng tôi, chỉ cần đặt tất cả các dòng này trong một elsekhối. Nếu không có gì sau continueđó, hãy thả khối if và tiếp tục: chúng vô dụng.
JB Nizet

Câu trả lời:


278

Sử dụng return;sẽ hoạt động tốt. Nó sẽ không ngăn hoàn thành vòng lặp đầy đủ. Nó sẽ chỉ dừng việc thực hiện lặp lại hiện tại của forEachvòng lặp.

Hãy thử chương trình nhỏ sau:

public static void main(String[] args) {
    ArrayList<String> stringList = new ArrayList<>();
    stringList.add("a");
    stringList.add("b");
    stringList.add("c");

    stringList.stream().forEach(str -> {
        if (str.equals("b")) return; // only skips this iteration.

        System.out.println(str);
    });
}

Đầu ra:

một
c

Lưu ý cách return;thực thi cho blần lặp, nhưng cin trên lần lặp sau vẫn tốt.

Tại sao điều này hoạt động?

Lý do hành vi có vẻ không trực quan lúc đầu là vì chúng ta đã quen với returncâu lệnh làm gián đoạn việc thực thi toàn bộ phương thức. Vì vậy, trong trường hợp này, chúng tôi hy vọng việc mainthực thi phương thức nói chung sẽ bị tạm dừng.

Tuy nhiên, điều cần hiểu là một biểu thức lambda, chẳng hạn như:

str -> {
    if (str.equals("b")) return;

    System.out.println(str);
}

... thực sự cần được coi là "phương pháp" riêng biệt của riêng nó, hoàn toàn tách biệt với mainphương pháp, mặc dù nó nằm ở vị trí thuận tiện bên trong nó. Vì vậy, thực sự, returncâu lệnh chỉ tạm dừng việc thực thi biểu thức lambda.

Điều thứ hai cần phải hiểu là:

stringList.stream().forEach()

... thực sự chỉ là một vòng lặp bình thường dưới vỏ bọc thực thi biểu thức lambda cho mỗi lần lặp.

Với 2 điểm này, đoạn mã trên có thể được viết lại theo cách tương đương sau (chỉ dành cho mục đích giáo dục):

public static void main(String[] args) {
    ArrayList<String> stringList = new ArrayList<>();
    stringList.add("a");
    stringList.add("b");
    stringList.add("c");

    for(String s : stringList) {
        lambdaExpressionEquivalent(s);
    }
}

private static void lambdaExpressionEquivalent(String str) {
    if (str.equals("b")) {
        return;
    }

    System.out.println(str);
}

Với mã tương đương "ít ma thuật hơn" này, phạm vi của returncâu lệnh trở nên rõ ràng hơn.


3
Tôi đã thử theo cách này và tại sao nó không hoạt động, tôi đã lặp lại thủ thuật này một lần nữa và nó hoạt động. Cảm ơn, nó giúp tôi. Có vẻ như tôi chỉ làm việc quá sức thôi =)
Viktor V.

2
Hoạt động như một sự quyến rũ! Bạn có thể thêm một lời giải thích tại sao nó hoạt động được không?
sparkyspider

2
@Spider: đã thêm.
sstan

5

Một giải pháp khác: đi qua bộ lọc với các điều kiện đảo ngược của bạn: Ví dụ:

if(subscribtion.isOnce() && subscribtion.isCalled()){
                continue;
}

có thể được thay thế bằng

.filter(s -> !(s.isOnce() && s.isCalled()))

Cách tiếp cận đơn giản nhất dường như đang sử dụng "return;" Tuy nhiên.


2

Lambda bạn đang chuyển tới forEach()được đánh giá cho từng phần tử nhận được từ luồng. Bản thân quá trình lặp lại không hiển thị trong phạm vi của lambda, vì vậy bạn không thể thực hiện continuenó như thể forEach()là một macro tiền xử lý C. Thay vào đó, bạn có thể bỏ qua phần còn lại của các câu lệnh trong đó có điều kiện.


0

Bạn có thể sử dụng một ifcâu lệnh đơn giản thay vì tiếp tục. Vì vậy, thay vì cách bạn có mã của mình, bạn có thể thử:

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
            filteredLines = lines.filter(...).foreach(line -> {
           ...
           if(!...) {
              // Code you want to run
           }
           // Once the code runs, it will continue anyway
    });
}

Vị từ trong câu lệnh if sẽ đối lập với vị từ trong if(pred) continue;câu lệnh của bạn , vì vậy chỉ cần sử dụng !(logic thì không).

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.