Mutate elements in a Stream

喜夏-厌秋 提交于 2019-12-21 10:15:20

问题


Is there a 'best practice' for mutating elements within a Stream? I'm specifically referring to elements within the stream pipeline, not outside of it.

For example, consider the case where I want to get a list of Users, set a default value for a null property and print it to the console.

Assuming the User class:

class User {
    String name;

    static User next(int i) {
        User u = new User();
        if (i % 3 != 0) {
            u.name = "user " + i;
        }
        return u;
    }
}

In java 7 it'd be something along the lines of:

for (int i = 0; i < 7; i++) {
    User user = User.next(i);
    if(user.name == null) {
        user.name = "defaultName";
    }
    System.out.println(user.name);
}

In java 8 it would seem like I'd use .map() and return a reference to the mutated object:

IntStream.range(0, 7)
    .mapToObj(User::next)
    .map(user -> {
        if (user.name == null) {
            user.name = "defaultName";
        }
        return user;
    })
    //other non-terminal operations
    //before a terminal such as .forEach or .collect
    .forEach(it -> System.out.println(it.name));

Is there a better way to achieve this? Perhaps using .filter() to handle the null mutation and then concat the unfiltered stream and the filtered stream? Some clever use of Optional? The goal being the ability to use other non-terminal operations before the terminal .forEach().

In the 'spirit' of streams I'm trying to do this without intermediary collections and simple 'pure' operations that don't depend on side effects outside the pipeline.

Edit: The official Stream java doc states 'A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care.' Given that this would be a non-interfering operation, what specifically makes it dangerous? The examples I've seen reach outside the pipeline, which is clearly sketchy.


回答1:


Don't mutate the object, map to the name directly:

IntStream.range(0, 7)
    .mapToObj(User::next)
    .map(user -> user.name)
    .map(name -> name == null ? "defaultName" : name)
    .forEach(System.out::println);



回答2:


It would seem that Streams can't handle this in one pipeline. The 'best practice' would be to create multiple streams:

List<User> users = IntStream.range(0, 7)
    .mapToObj(User::next)
    .collect(Collectors.toList());

users.stream()
    .filter(it -> it.name == null)
    .forEach(it -> it.name = "defaultValue");

users.stream()
    //other non-terminal operations
    //before terminal operation
    .forEach(it -> System.out.println(it.name));



回答3:


It sounds like you're looking for peek:

.peek(user -> {
    if (user.name == null) {
        user.name = "defaultName";
    }
})

...though it's not clear that your operation actually requires modifying the stream elements instead of just passing through the field you want:

.map(user -> (user.name == null) ? "defaultName" : user.name)


来源:https://stackoverflow.com/questions/33764216/mutate-elements-in-a-stream

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!