ย้ายไปยังรายการถัดไปโดยใช้ Java 8 foreach loop ในสตรีม


127

ฉันมีปัญหากับสตรีมของ Java 8 foreach ที่พยายามย้ายรายการถัดไปแบบวนซ้ำ ฉันไม่สามารถตั้งค่าคำสั่งเช่นcontinue;ใช้return;งานได้ แต่คุณจะออกจากลูปในกรณีนี้ ฉันต้องไปยังรายการถัดไปแบบวนซ้ำ ฉันจะทำเช่นนั้นได้อย่างไร?

ตัวอย่าง (ไม่ทำงาน):

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

ตัวอย่าง (ที่ทำงาน):

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!
}

โปรดโพสต์ตัวอย่างแบบเต็ม แต่น้อยที่สุดเพื่อให้ง่ายต่อการตอบ
Tunaki

ฉันต้องไปยังรายการถัดไปแบบวนซ้ำ
Viktor V.

2
ประเด็นของเขาคือด้วยรหัสที่คุณได้แสดงไว้ไม่มีสิ่งที่continueจะย้ายไปยังรายการถัดไปโดยไม่มีการเปลี่ยนแปลงการทำงานใด ๆ
DoubleDouble

หากเป้าหมายคือเพื่อหลีกเลี่ยงการเรียกใช้บรรทัดที่เกิดขึ้นหลังจากการเรียกเพื่อดำเนินการต่อและคุณไม่ได้แสดงให้เราเห็นเพียงแค่ใส่บรรทัดทั้งหมดเหล่านี้ลงในelseบล็อก หากไม่มีอะไรหลังจากcontinueนั้นให้วางบล็อก if และดำเนินการต่อ: ไม่มีประโยชน์
JB Nizet

คำตอบ:


279

การใช้return;จะทำงานได้ดี จะไม่ป้องกันไม่ให้ลูปเต็ม มันจะหยุดการดำเนินการวนซ้ำปัจจุบันforEachเท่านั้น

ลองใช้โปรแกรมเล็ก ๆ ต่อไปนี้:

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);
    });
}

เอาท์พุท:


สังเกตว่าreturn;มีการดำเนินการสำหรับการbวนซ้ำอย่างไร แต่การcพิมพ์บนการทำซ้ำต่อไปนี้ทำได้ดี

ทำไมถึงได้ผล?

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

อย่างไรก็ตามสิ่งที่ต้องเข้าใจคือนิพจน์แลมบ์ดาเช่น:

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

    System.out.println(str);
}

... ต้องถือว่าเป็น "วิธีการ" ที่แตกต่างกันจริงๆแยกออกจากmainวิธีการโดยสิ้นเชิงแม้ว่าจะอยู่ในตำแหน่งที่สะดวกก็ตาม ดังนั้นreturnคำสั่งเพียงแค่หยุดการทำงานของนิพจน์แลมบ์ดาเท่านั้น

สิ่งที่สองที่ต้องเข้าใจคือ:

stringList.stream().forEach()

... เป็นเพียงลูปปกติภายใต้ฝาครอบที่รันนิพจน์แลมด้าสำหรับการวนซ้ำทุกครั้ง

เมื่อคำนึงถึง 2 ประเด็นนี้โค้ดข้างต้นสามารถเขียนซ้ำได้ด้วยวิธีที่เทียบเท่าดังต่อไปนี้ (เพื่อการศึกษาเท่านั้น):

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);
}

ด้วยรหัสที่เทียบเท่า "เวทมนตร์น้อยกว่า" ขอบเขตของreturnข้อความจะชัดเจนมากขึ้น


3
ฉันได้ลองวิธีนี้แล้วและทำไมมันไม่ได้ผลฉันได้ทำเคล็ดลับนี้ซ้ำอีกครั้งและมันได้ผล ขอบคุณมันช่วยฉัน ดูเหมือนว่าฉันทำงานหนักเกินไป =)
Viktor V.

2
ใช้งานได้เหมือนมีเสน่ห์! คุณช่วยเพิ่มคำอธิบายว่าทำไมจึงได้ผล
sparkyspider

2
@Spider: เพิ่ม
sstan

5

วิธีแก้ปัญหาอื่น: ใช้ตัวกรองด้วยเงื่อนไขกลับหัวของคุณ: ตัวอย่าง:

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

สามารถแทนที่ด้วย

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

แนวทางที่ตรงไปตรงมาที่สุดดูเหมือนจะใช้ "return;" แม้


2

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


0

คุณสามารถใช้ifคำสั่งธรรมดาแทนการดำเนินการต่อ ดังนั้นแทนที่จะใช้วิธีที่คุณมีรหัสคุณสามารถลอง:

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
    });
}

เพรดิเคตในคำสั่ง if จะตรงข้ามกับเพรดิเคตในif(pred) continue;คำสั่งของคุณดังนั้นให้ใช้!(ตรรกะไม่ใช่)

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