การแก้ไขอ็อบเจ็กต์ภายในสตรีมใน Java8 ขณะทำซ้ำ


88

ในสตรีม Java8 ฉันได้รับอนุญาตให้แก้ไข / อัพเดตออบเจ็กต์ภายในหรือไม่ สำหรับเช่น List<User> users:

users.stream().forEach(u -> u.setProperty("value"))

คำตอบ:


102

ได้คุณสามารถแก้ไขสถานะของออบเจ็กต์ภายในสตรีมของคุณได้ แต่ส่วนใหญ่แล้วคุณควรหลีกเลี่ยงการแก้ไขสถานะของแหล่งที่มาของสตรีม จากส่วนการไม่รบกวนของเอกสารชุดสตรีมเราสามารถอ่านได้ว่า:

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

นี่ก็โอเค

  List<User> users = getUsers();
  users.stream().forEach(u -> u.setProperty(value));
//                       ^    ^^^^^^^^^^^^^

แต่ในกรณีส่วนใหญ่ไม่ใช่

  users.stream().forEach(u -> users.remove(u));
//^^^^^                       ^^^^^^^^^^^^

และอาจโยนConcurrentModificationExceptionหรือแม้แต่ข้อยกเว้นอื่น ๆ ที่ไม่คาดคิดเช่น NPE:

List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList());

list.stream()
    .filter(i -> i > 5)
    .forEach(i -> list.remove(i));  //throws NullPointerException

4
วิธีแก้ไขคืออะไรหากคุณต้องการแก้ไขผู้ใช้
Augustas

2
@Augustas ทุกอย่างขึ้นอยู่กับว่าคุณต้องการแก้ไขรายการนี้อย่างไร แต่โดยทั่วไปคุณควรหลีกเลี่ยงรายการIteratorที่ป้องกันไม่ให้แก้ไขรายการ (ลบ / เพิ่มองค์ประกอบใหม่) ในขณะที่ทำซ้ำและทั้งสตรีมและสำหรับแต่ละลูปกำลังใช้งานอยู่ คุณสามารถลองใช้การวนซ้ำแบบธรรมดาfor(int i=0; ..; ..)ซึ่งไม่มีปัญหานี้ (แต่จะไม่หยุดคุณเมื่อเธรดอื่นจะแก้ไขรายการของคุณ) นอกจากนี้คุณยังสามารถใช้วิธีการเช่น,list.removeAll(Collection) list.removeIf(Predicate)นอกจากนี้ยังมีระดับมีไม่กี่วิธีซึ่งอาจจะเป็นประโยชน์เช่นjava.util.Collections addAll(CollectionOfNewElements,list)
Pshemo

@Pshemo วิธีแก้ปัญหาอย่างหนึ่งคือการสร้างอินสแตนซ์ใหม่ของคอลเล็กชันเช่น ArrayList พร้อมกับรายการในรายการหลักของคุณ วนซ้ำในรายการใหม่และดำเนินการกับรายการหลัก: ใหม่ ArrayList <> (ผู้ใช้) .stream.forEach (u -> users.remove (u));
MNZ

2
@Blauhirn แก้อะไร? เราไม่ได้รับอนุญาตให้แก้ไขแหล่งที่มาของสตรีมในขณะที่ใช้สตรีม แต่ได้รับอนุญาตให้แก้ไขสถานะขององค์ประกอบ ไม่สำคัญว่าเราจะใช้ map, foreach หรือวิธีการสตรีมอื่น ๆ
Pshemo

1
แทนที่จะแก้ไขคอลเลกชันฉันขอแนะนำให้ใช้filter()
jontro

5

วิธีการใช้งานจะเป็น:

import static java.util.stream.Collectors.toList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class PredicateTestRun {

    public static void main(String[] args) {

        List<String> lines = Arrays.asList("a", "b", "c");
        System.out.println(lines); // [a, b, c]
        Predicate<? super String> predicate = value -> "b".equals(value);
        lines = lines.stream().filter(predicate.negate()).collect(toList());

        System.out.println(lines); // [a, c]
    }
}

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


3

ในการปรับเปลี่ยนโครงสร้างของแหล่งที่มาของสตรีมดังที่ Pshemo กล่าวไว้ในคำตอบของเขาทางออกหนึ่งคือสร้างอินสแตนซ์ใหม่ที่Collectionคล้ายArrayListกับรายการในรายการหลักของคุณ วนซ้ำในรายการใหม่และดำเนินการกับรายการหลัก

new ArrayList<>(users).stream().forEach(u -> users.remove(u));

1

ในการกำจัดออกจากConcurrentModificationException ให้ใช้CopyOnWriteArrayList


2
นั่นถูกต้องจริง แต่: ขึ้นอยู่กับคลาสเฉพาะที่ใช้ที่นี่ แต่เมื่อคุณรู้เพียงว่าคุณมีList<Something>- คุณจะไม่รู้เลยว่านั่นคือ CopyOnWriteArrayList หรือไม่ ดังนั้นนี่จึงเป็นเหมือนความคิดเห็นธรรมดา ๆ แต่ไม่ใช่คำตอบที่แท้จริง
GhostCat

หากเขาโพสต์เป็นความคิดเห็นมีคนมักจะบอกเขาว่าเขาไม่ควรโพสต์คำตอบเป็นความคิดเห็น
Bill K

1

แทนที่จะสร้างสิ่งแปลก ๆ คุณสามารถทำได้filter()แล้วmap()ผลลัพธ์ของคุณ

อ่านได้มากขึ้นและแน่นอน สตรีมจะทำให้เป็นเพียงลูปเดียว


1

คุณสามารถใช้removeIfเพื่อลบข้อมูลออกจากรายการตามเงื่อนไข

เช่น: - หากคุณต้องการลบเลขคู่ทั้งหมดออกจากรายการคุณสามารถทำได้ดังนี้

    final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());

    list.removeIf(number -> number % 2 == 0);

1

ได้คุณสามารถแก้ไขหรืออัปเดตค่าของวัตถุในรายการในกรณีของคุณได้เช่นเดียวกัน:

users.stream().forEach(u -> u.setProperty("some_value"))

อย่างไรก็ตามคำสั่งดังกล่าวจะทำการปรับปรุงเกี่ยวกับวัตถุต้นทาง ซึ่งอาจไม่เป็นที่ยอมรับในกรณีส่วนใหญ่

โชคดีที่เรามีวิธีอื่นเช่น:

List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());

ซึ่งส่งคืนรายการที่อัปเดตกลับโดยไม่ขัดขวางรายการเก่า


0

ตามที่ได้กล่าวไว้ก่อนหน้านี้คุณไม่สามารถแก้ไขรายการต้นฉบับได้ แต่คุณสามารถสตรีมแก้ไขและรวบรวมรายการในรายการใหม่ได้ นี่คือตัวอย่างง่ายๆในการแก้ไของค์ประกอบสตริง

public class StreamTest {

    @Test
    public void replaceInsideStream()  {
        List<String> list = Arrays.asList("test1", "test2_attr", "test3");
        List<String> output = list.stream().map(value -> value.replace("_attr", "")).collect(Collectors.toList());
        System.out.println("Output: " + output); // Output: [test1, test2, test3]
    }
}

-1

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

สร้างตัวชี้ไปยังหน่วยความจำ (obj ใหม่ในกรณีนี้) และมีการแก้ไขคุณสมบัติของวัตถุ สตรีม Java 8 ไม่อนุญาตให้แก้ไขตัวชี้เองและด้วยเหตุนี้หากคุณประกาศเพียงแค่นับเป็นตัวแปรและพยายามเพิ่มขึ้นภายในสตรีมมันจะไม่ทำงานและทำให้เกิดข้อยกเว้นของคอมไพเลอร์ตั้งแต่แรก

Path path = Paths.get("/Users/XXXX/static/test.txt");



Count c = new Count();
            c.setCount(0);
            Files.lines(path).forEach(item -> {
                c.setCount(c.getCount()+1);
                System.out.println(item);});
            System.out.println("line count,"+c);

public static class Count{
        private int count;

        public int getCount() {
            return count;
        }

        public void setCount(int count) {
            this.count = count;
        }

        @Override
        public String toString() {
            return "Count [count=" + count + "]";
        }



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