반복하는 동안 Java8의 스트림 내에서 객체 수정


87

Java8 스트림에서 객체를 수정 / 업데이트 할 수 있습니까? 예를 들어. List<User> users:

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

답변:


101

예, 스트림 내부의 객체 상태를 수정할 수 있지만 대부분의 경우 스트림 소스 상태를 수정하지 않아야 합니다. 에서 비 간섭 우리가 읽을 수 스트림 패키지 문서의 섹션 :

대부분의 데이터 소스에서 간섭 방지 는 스트림 파이프 라인 실행 중에 데이터 소스가 전혀 수정되지 않도록하는 것을 의미합니다 . 이에 대한 주목할만한 예외는 소스가 동시 수정을 처리하도록 특별히 설계된 동시 컬렉션 인 스트림입니다. 동시 스트림 소스는 특성을 Spliterator보고하는 소스 CONCURRENT입니다.

그래서 괜찮아

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

그러나 이것은 대부분의 경우에 아닙니다

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

ConcurrentModificationExceptionNPE와 같은 예상치 못한 예외가 발생할 수 있습니다 .

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-each 루프가 모두 사용하는 것을 방지 해야 합니다. for(int i=0; ..; ..)이 문제가없는 간단한 루프를 사용해 볼 수 있습니다 (그러나 다른 스레드가 목록을 수정할 때 중단되지는 않습니다). 당신은 또한 같은 방법을 사용할 수 있습니다 list.removeAll(Collection), list.removeIf(Predicate). 또한 java.util.Collections클래스에는 addAll(CollectionOfNewElements,list).
Pshemo

@Pshemo, 이에 대한 한 가지 해결책은 기본 목록 내의 항목으로 ArrayList와 같은 Collection의 새 인스턴스를 만드는 것입니다. 새 목록을 반복하고 기본 목록에서 작업을 수행합니다. new ArrayList <> (users) .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 jul.

그가 그것을 댓글로 게시했다면, 누군가가 댓글로 답변을 게시하지 말아야한다고 말했을 것입니다.
Bill K

1

대신에 이상한 물건을 만드는, 당신은 할 수있는 filter()한 후 map()당신의 결과.

이것은 훨씬 더 읽기 쉽고 확실합니다. 스트림은 단 하나의 루프로 만들 것입니다.


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

0

당신은 사용할 수 있습니다 removeIf 조건부로 목록에서 데이터를 제거 .

예 :-목록에서 모든 짝수를 제거하려면 다음과 같이 할 수 있습니다.

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

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

-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.