一个操作的多个 Java 个消费者
Multiple Java Consumers for an Action
有没有办法让下面的动作实际上是两个动作合二为一?
static int process(String[] cmd, File dir, Consumer<? super String> action) throws IOException {
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.directory(dir);
pb.redirectErrorStream(true);
Stopwatch sw = Stopwatch.createStarted();
Process p = pb.start();
int exit = -1;
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
br.lines().forEach(action);
} finally {
log.debug("Ending process {} with exist code {} in time {}", pb.command(),
exit = p.destroyForcibly().exitValue(), sw);
}
return exit;
}
所以 Consumer<? super String> action
我通常将其指定为 log::debug
或像 AtomicReference::set
这样的自定义函数,但是如果我想两者兼而有之怎么办?我怎样才能让动作执行一些独立的动作?
我知道我可以创建一个自定义 Consumer
来做任何我想做的事,但我认为这会非常方便
如果我几乎可以像对待 nvarargs 一样对待这个动作,但对于 functions/actions。
创建一个委托给其他消费者的 Consumer
很容易。你只要写
str -> {
action1(str);
action2(str);
// etc
}
因为您没有使用方法引用,所以您始终可以使用 lambda。以这种风格编写 Consumer
也往往比尝试以其他方式组合它们更具可读性,尽管其他方式也是可能的。
这里有几个备选方案,其中一些已经被其他人提到过。
也许最像 lambda 的方法是使用消费者自己的聚合方法 andThen
(在 Consumer
本身中定义):
Stream<X> exes = ....
exes.forEach(action1
.andThen(action2)
.andThen(action3)
.andThen(...)
.andThen(actionN));
其中所有 action?
都声明为 Consumer<X>
或 Consumer<? super X>
。
没有查看文档来确认。我想对于流中的每个元素,操作 1 到 N 都是在同一个线程上一个接一个地执行的。
这里的另一种可能性是使用 peek
基本上将流的元素传递给一个动作而不消耗它们,因为它 returns 一个包含相同元素的流。
Stream<X> exes = ...
exes.peek(action1)
.peek(action2)
.peek(action3)
.peek(...)
.forEach(actionN);
不看文档我敢打赌:
- 您需要实际调用像
forEach
(或 count
、empty
等)这样的最终消费操作,以便执行不同的查看。
- 执行顺序的唯一限制是只要 i < j,对于流中的任何给定元素,action-i 将在 action-j 之前执行。除非我们知道关于流的其他信息(它是 parallel/serial、已排序等),否则不能做出其他假设。
我认为您可能希望根据问题的内容使用 Consumer.andThen
,但是如果某些操作的执行不是 central/main,peek
似乎是一个合理的解决方案对手头的任务来说更像是一个理想的副作用。
也许您想传递一个修改后的流,该流将 "spy" 稍后由流引用传递到的代码处理哪些对象。在那种情况下 peek
如果不是唯一的选择,则更好。
当然你也可以两者结合。
有没有办法让下面的动作实际上是两个动作合二为一?
static int process(String[] cmd, File dir, Consumer<? super String> action) throws IOException {
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.directory(dir);
pb.redirectErrorStream(true);
Stopwatch sw = Stopwatch.createStarted();
Process p = pb.start();
int exit = -1;
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
br.lines().forEach(action);
} finally {
log.debug("Ending process {} with exist code {} in time {}", pb.command(),
exit = p.destroyForcibly().exitValue(), sw);
}
return exit;
}
所以 Consumer<? super String> action
我通常将其指定为 log::debug
或像 AtomicReference::set
这样的自定义函数,但是如果我想两者兼而有之怎么办?我怎样才能让动作执行一些独立的动作?
我知道我可以创建一个自定义 Consumer
来做任何我想做的事,但我认为这会非常方便
如果我几乎可以像对待 nvarargs 一样对待这个动作,但对于 functions/actions。
创建一个委托给其他消费者的 Consumer
很容易。你只要写
str -> {
action1(str);
action2(str);
// etc
}
因为您没有使用方法引用,所以您始终可以使用 lambda。以这种风格编写 Consumer
也往往比尝试以其他方式组合它们更具可读性,尽管其他方式也是可能的。
这里有几个备选方案,其中一些已经被其他人提到过。
也许最像 lambda 的方法是使用消费者自己的聚合方法 andThen
(在 Consumer
本身中定义):
Stream<X> exes = ....
exes.forEach(action1
.andThen(action2)
.andThen(action3)
.andThen(...)
.andThen(actionN));
其中所有 action?
都声明为 Consumer<X>
或 Consumer<? super X>
。
没有查看文档来确认。我想对于流中的每个元素,操作 1 到 N 都是在同一个线程上一个接一个地执行的。
这里的另一种可能性是使用 peek
基本上将流的元素传递给一个动作而不消耗它们,因为它 returns 一个包含相同元素的流。
Stream<X> exes = ...
exes.peek(action1)
.peek(action2)
.peek(action3)
.peek(...)
.forEach(actionN);
不看文档我敢打赌:
- 您需要实际调用像
forEach
(或count
、empty
等)这样的最终消费操作,以便执行不同的查看。 - 执行顺序的唯一限制是只要 i < j,对于流中的任何给定元素,action-i 将在 action-j 之前执行。除非我们知道关于流的其他信息(它是 parallel/serial、已排序等),否则不能做出其他假设。
我认为您可能希望根据问题的内容使用 Consumer.andThen
,但是如果某些操作的执行不是 central/main,peek
似乎是一个合理的解决方案对手头的任务来说更像是一个理想的副作用。
也许您想传递一个修改后的流,该流将 "spy" 稍后由流引用传递到的代码处理哪些对象。在那种情况下 peek
如果不是唯一的选择,则更好。
当然你也可以两者结合。