【问题标题】:Better way than Stream.peek()比 Stream.peek() 更好的方法
【发布时间】:2017-06-26 07:47:25
【问题描述】:

“peek”主要用于调试。如果我想在流的中间调用流上的方法,这会改变流对象的状态。

Stream.of("Karl", "Jill", "Jack").map(Test::new).peek(t->t.setLastName("Doe"));

我能做到:

Stream.of("Karl", "Jill", "Jack").map(Test::new).map(t->{t.setLastName("Doe"); return t;});

但这看起来很难看。这是不应该做的事情还是有更好的方法来做到这一点?

编辑:forEach 可以工作,只是它是一个终端操作,所以之后你不能继续在流上工作。然后我希望制作一个 Collection,做 forEach,然后再次开始流式传输 Collection。

编辑:map(Class::processingMethod) 是我现在正在做的,但由于processingMethod 只是返回this,这似乎是对地图的滥用。另外,它读起来不像业务逻辑。

最终编辑:我接受了@Holger 的回答。不能期望Stream.peek 处理 Stream 上的所有元素,因为它不是终端操作。 map 也是如此。即使您可能已经终止了您的流以保证它会处理所有操作,但您不应该编写期望每个用户都这样做的代码。因此,要进行处理,您应该在Collection 上使用forEach,然后如果您愿意,可以再次开始流式传输Collection。

【问题讨论】:

  • 您在寻找forEach吗?地图适用于每个元素。
  • @matt - forEach 可以工作,只是它是一个终端操作,所以你不能在之后继续处理流。然后我希望制作一个集合,执行forEach,然后再次开始流式传输集合。你认为有理由这样处理吗?

标签: java lambda java-8


【解决方案1】:

您过度使用方法引用。 Test::new 的简单性毫无价值,如果它使您的其他流使用复杂化。

一个明确的解决方案是:

Stream.of("Karl", "Jill", "Jack")
      .map(first -> { Test t = new Test(first); t.setLastName("Doe"); return t; })
      …

或者更好

Stream.of("Karl", "Jill", "Jack").map(first -> new Test(first, "Doe")) …

假设该类具有接受这两个名称的不那么牵强的构造函数。

上面的代码解决了用例,其中操作操作本地构造的对象,因此只有当对象将被后续 Stream 操作使用时,该操作才相关。对于其他情况,当操作对 Stream 之外的对象产生副作用时,滥用 map 几乎具有“In Java streams, is peek really only for debugging?”中解释的 peek 的所有缺点

【讨论】:

  • 谢谢@Holger。我明白你在说什么,但问题实际上是关于在流期间进行处理而不仅仅是进行转换。所以,如上面的答案所示,做map(Class::method)确实是唯一的方法,但它似乎读错了。
  • @Nicholas: 滥用map 的副作用与使用peek 相同。如果它仅限于像您的示例中那样操作本地创建的对象,它可能会起作用,但是,像我的回答一样,将创建和操作融合到一个步骤中会更加清晰。在所有其他情况下,滥用map 几乎具有peek 中解释的所有缺点In Java streams, is peek really only for debugging?。
  • 我认为这就是我正在寻找的答案。如果我要编写一个希望您滥用 map 并将其误用作终端操作的库,您会说这是一个糟糕的库。所以,我应该使用forEach,因为它是终端操作。
【解决方案2】:

您不能使用方法引用,即使您创建另一个带有两个参数的构造函数。

唯一的办法是:

.map(token -> {Test t = new Test(token); token.setLastname("joe"); return t;})

【讨论】:

  • 类有一个构造函数,它接受一个字符串,但它是first的名字。添加另一个参数会很有用(甚至可能存在这样的构造函数),但它不能与 Stream 中如此简单的方法引用一起使用,因为应该使用流元素(名字)和常量(姓氏)"Doe".
  • 这里的答案似乎是“否”。您是说 map 是 在流期间添加处理的方式。这就是我所做的,但它似乎暗示了一些没有发生的事情,即我不是在映射,而是只是执行一个函数并返回相同的对象。
【解决方案3】:

并非在所有情况下都为您想要实现的目标定义新方法或构造函数。您可以为具有以下功能的功能创建自己的工具类Functions:

public static <T, R> Function<T,R> of(Function<T, R> function){
    return function;
}


public static <T> Function<T,T> peek(Consumer<? super T> peeker){
    return t -> {
        peeker.accept(t);
        return t;
    };
}

然后你可以像这样使用它们:

Stream.of("Karl", "Jill", "Jack").map(Functions.of(Test::new).andThen(Functions.peek(t -> t.setLastName("Doe"))));

流不应该为通过映射为每个元素设置姓氏而烦恼(应该没有副作用)。因此,我在这个例子中直接将其组合到构造函数中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-23
    • 2019-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多