突变流中的元素

时间:2015-11-17 18:17:58

标签: java java-8 java-stream

是否存在改变流中元素的'最佳实践'?我特指的是流管道中的元素,而不是它之外的元素。

例如,考虑我想获取用户列表的情况,为null属性设置默认值并将其打印到控制台。

假设用户类:

class User {
    String name;

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

在java 7中,它有点像:

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

在java 8中,我似乎使用.map()并返回对变异对象的引用:

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

有没有更好的方法来实现这一目标?也许使用.filter()来处理null变异然后连接未过滤的流和过滤的流?有些巧妙使用Optional?目标是能够在终端.forEach()之前使用其他非终端操作。

在流的“精神”中,我试图在没有中间集合和简单的“纯粹”操作的情况下执行此操作,这些操作不依赖于管道外的副作用。

编辑:官方Stream java doc声明'少量流操作,例如forEach()和peek(),只能通过副作用运行;这些都应该小心使用。鉴于这将是一个不干扰的操作,具体是什么使它危险?我见过的例子到达管道之外,这显然是粗略的。

3 个答案:

答案 0 :(得分:8)

不要改变对象,直接映射到名称:

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

答案 1 :(得分:3)

Streams似乎无法在一个管道中处理这个问题。最佳实践&#39;将创建多个流:

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

答案 2 :(得分:2)

听起来你正在寻找peek

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

...虽然不清楚您的操作实际上是否需要修改流元素而不是仅仅通过您想要的字段:

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