反復中に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));
//^^^^^                       ^^^^^^^^^^^^

そして、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
ユーザーを変更したい場合の解決策は何ですか?
オーガスタス2016年

2
@Augustasそれはすべてあなたがこのリストをどのように修正したいかに依存します。ただし、一般的にはIterator、反復中にリストの変更(新しい要素の削除/追加)が妨げられ、ストリームとfor-eachループの両方がそれを使用することを避ける必要があります。for(int i=0; ..; ..)この問題がないような単純なループを使用してみることができます(ただし、他のスレッドがリストを変更しても停止しません)。list.removeAll(Collection)、などのメソッドを使用することもできますlist.removeIf(Predicate)。また、java.util.Collectionsクラスには、のように役立つ可能性のあるメソッドがいくつかありますaddAll(CollectionOfNewElements,list)
pshemo 2016年

@Pshemo、その解決策の1つは、プライマリリスト内のアイテムを使用してArrayListのようなコレクションの新しいインスタンスを作成することです。新しいリストを繰り返し処理し、プライマリリストで操作を実行します。newArrayList<>(users).stream.forEach(u-> users.remove(u));
MNZ 2016

2
@Blauhirn何を解決しますか?ストリームの使用中にストリームのソースを変更することは許可されていませんが、その要素の状態を変更することは許可されています。map、foreach、またはその他のストリームメソッドを使用するかどうかは関係ありません。
pshemo 2017年

1
コレクションを変更する代わりに、使用することをお勧めしますfilter()
jontro 2017

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が彼の回答で述べたように、ストリームのソースで構造変更を行うための1つの解決策は、プライマリリスト内のアイテムを使用してCollectionlikeの新しいインスタンスを作成することArrayListです。新しいリストを繰り返し処理し、プライマリリストで操作を実行します。

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

1

ConcurrentModificationExceptionを取り除くには、CopyOnWriteArrayListを使用します


2
これは実際には正しいですが、これはここで使用される特定のクラスによって異なります。しかし、自分が持っていることだけを知っているList<Something>場合、それがCopyOnWriteArrayListであるかどうかはわかりません。つまり、これは平凡なコメントのようなものですが、本当の答えではありません。
GhostCat 2017

もし彼がそれをコメントとして投稿したとしたら、誰かが彼に答えをコメントとして投稿すべきではないと言ったでしょう。
ビル

1

奇妙なものを作成する代わりにfilter()map()あなたの結果をます。

これははるかに読みやすく、確実です。ストリームは1つのループだけでそれを作ります。


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

これは少し遅いかもしれません。しかし、ここに使用法の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.