Java 改变流中的元素

Java 改变流中的元素,java,java-8,java-stream,Java,Java 8,Java Stream,是否有在流中变异元素的“最佳实践”?我特别指的是流管道内的元素,而不是流管道外的元素 例如,考虑一个需要获取用户列表的情况,为空属性设置默认值并将其打印到控制台。 假设用户类: class User { String name; static User next(int i) { User u = new User(); if (i % 3 != 0) { u.name = "user " + i; }

是否有在流中变异元素的“最佳实践”?我特别指的是流管道内的元素,而不是流管道外的元素

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

假设用户类:

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);
}
有没有更好的方法来实现这一点?可能是使用.filter()来处理空变异,然后合并未过滤流和过滤流?一些可选的巧妙使用?目标是能够在terminal.forEach()之前使用其他非终端操作

本着streams的“精神”,我试图做到这一点,而不需要中间收集和简单的“纯”操作,这些操作不依赖于管道外的副作用


编辑:官方流java文档声明“少量流操作,如forEach()和peek(),只能通过副作用进行操作;考虑到这将是一个不干扰的操作,是什么使它特别危险?我看到的示例超出了管道,这显然是粗略的。

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

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

听起来你在寻找:

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

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

看起来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));
List users=IntStream.range(0,7)
.mapToObj(用户::下一步)
.collect(Collectors.toList());
users.stream()
.filter(it->it.name==null)
.forEach(it->it.name=“defaultValue”);
users.stream()
//其他非终点站作业
//终端运行前
.forEach(it->System.out.println(it.name));

“是否有在流中改变元素的‘最佳实践’?”。是:不要。这种用法显然是对
map()
的滥用。在
forEach()
中执行变异。如果我在.forEach()中执行此操作,那么如何获得对收集的用户对象的引用以在后续工作中执行其他工作?从
peek
javadoc:此方法的存在主要是为了支持调试,您希望在元素流过管道中的某个点时看到这些元素@迪迪尔:嗯,是的,那是因为你实际上不应该修改流管道中的元素。但是,如果你想,这就是如何修改的。这就是问题的真实答案:最好的做法是不要这样做,正如@Tunaki在一篇评论中所说的那样。但是在
forEach
中这样做会很好。你可以执行一个无干扰的操作尽管不建议修改。
peek
的问题是,它本身不做任何事情,而是作为实际终端操作的副产品执行,这取决于它的语义。因此,如果您想对所有元素执行
peek
操作,但要将其与一个终端操作相结合,即在不处理元素的情况下快速循环或甚至预测最终结果,您会遇到麻烦,尤其是因为它可能在某些情况下工作,但在其他情况下会失败。@shmosel:通过让
convert
方法进行转换,这仍然是可能的,或者通常,如果lambda表达式太大,您总是可以将代码放入命名的met中hod并使用新方法引用。这就是我称之为cleaner的方法…我自己不会想到这一点,但它确实似乎是最好的解决方案。正如其他人已经指出的,这显然是对流的滥用。希望我们停止对这个答案的投票,因为它鼓励读者思考这个问题没有问题。不要在流上变异。正如其他人指出的,如果只需要名称,请使用Stream.map。如果在获取名称之前需要其他转换,请使用不可变的用户对象,执行转换,然后通过Stream.map方法更改其名称
.map(user -> (user.name == null) ? "defaultName" : user.name)
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));