java Lambdas,多个 forEach 与铸造
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25439277/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Lambdas, multiple forEach with casting
提问by David Blevins
Need some help thinking in lambdas from my fellow StackOverflow luminaries.
需要一些帮助来思考我的 StackOverflow 名人的 lambda。
Standard case of picking through a list of a list of a list to collect some children deep in a graph. What awesome ways could Lambdas
help with this boilerplate?
通过一个列表的一个列表挑选一个列表来收集一个图表深处的一些孩子的标准案例。什么很棒的方法可以Lambdas
帮助这个样板?
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
for (final Service service : server.findServices()) {
if (service.getContainer() instanceof Engine) {
final Engine engine = (Engine) service.getContainer();
for (final Container possibleHost : engine.findChildren()) {
if (possibleHost instanceof Host) {
final Host host = (Host) possibleHost;
for (final Container possibleContext : host.findChildren()) {
if (possibleContext instanceof Context) {
final Context context = (Context) possibleContext;
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
}
}
}
}
}
}
return list;
}
Note the list itself is going to the client as JSON
, so don't focus on what is returned. Must be a few neat ways I can cut down the loops.
请注意,列表本身将作为 发送给客户端JSON
,因此不要关注返回的内容。必须有一些巧妙的方法可以减少循环。
Interested to see what my fellow experts create. Multiple approaches encouraged.
有兴趣看看我的专家同行们创造了什么。鼓励多种方法。
EDIT
编辑
The findServices
and the two findChildren
methods return arrays
在findServices
和两个findChildren
方法返回数组
EDIT - BONUS CHALLENGE
编辑 - 奖金挑战
The "not important part" did turn out to be important. I actually need to copy a value only available in the host
instance. This seems to ruin all the beautiful examples. How would one carry state forward?
“不重要的部分”确实很重要。我实际上需要复制一个仅在host
实例中可用的值。这似乎毁了所有美丽的例子。一个人将如何推进国家发展?
final ContextInfo info = new ContextInfo(context.getPath());
info.setHostname(host.getName()); // The Bonus Challenge
回答by Stuart Marks
It's fairly deeply nested but it doesn't seem exceptionally difficult.
它的嵌套相当深,但似乎并不特别困难。
The first observation is that if a for-loop translates into a stream, nested for-loops can be "flattened" into a single stream using flatMap
. This operation takes a single element and returns an arbitrary number elements in a stream. I looked up and found that StandardServer.findServices()
returns an array of Service
so we turn this into a stream using Arrays.stream()
. (I make similar assumptions for Engine.findChildren()
and Host.findChildren()
.
第一个观察结果是,如果 for 循环转换为流,嵌套的 for 循环可以使用flatMap
. 此操作采用单个元素并返回流中的任意数量的元素。我查了一下,发现它StandardServer.findServices()
返回一个数组,Service
所以我们使用Arrays.stream()
. (我对Engine.findChildren()
和做了类似的假设Host.findChildren()
。
Next, the logic within each loop does an instanceof
check and a cast. This can be modeled using streams as a filter
operation to do the instanceof
followed by a map
operation that simply casts and returns the same reference. This is actually a no-op but it lets the static typing system convert a Stream<Container>
to a Stream<Host>
for example.
接下来,每个循环中的逻辑进行instanceof
检查和强制转换。这可以使用流作为一个filter
操作来建模,instanceof
然后是一个map
简单的转换和返回相同引用的操作。这实际上是一个空操作,但它允许静态类型系统将 a 转换Stream<Container>
为 a Stream<Host>
。
Applying these transformations to the nested loops, we get the following:
将这些转换应用于嵌套循环,我们得到以下结果:
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.forEach(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
});
return list;
}
But wait, there's more.
但是等等,还有更多。
The final forEach
operation is a slightly more complicated map
operation that converts a Context
into a ContextInfo
. Furthermore, these are just collected into a List
so we can use collectors to do this instead of creating and empty list up front and then populating it. Applying these refactorings results in the following:
最后一个forEach
操作是一个稍微复杂的map
操作,将 a 转换Context
为 a ContextInfo
。此外,这些只是收集到 a 中,List
因此我们可以使用收集器来执行此操作,而不是预先创建和清空列表,然后填充它。应用这些重构会产生以下结果:
public List<ContextInfo> list() {
final StandardServer server = getServer();
return Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.map(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
}
I usually try to avoid multi-line lambdas (such as in the final map
operation) so I'd refactor it into a little helper method that takes a Context
and returns a ContextInfo
. This doesn't shorten the code at all, but I think it does make it clearer.
我通常会尽量避免多行 lambdas(例如在最终map
操作中),因此我会将其重构为一个小辅助方法,该方法接受 aContext
并返回 a ContextInfo
。这根本没有缩短代码,但我认为它确实使它更清晰。
UPDATE
更新
But wait, there's still more.
但是等等,还有更多。
Let's extract the call to service.getContainer()
into its own pipeline element:
让我们将调用提取service.getContainer()
到它自己的管道元素中:
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.filter(container -> container instanceof Engine)
.map(container -> (Engine)container)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
// ...
This exposes the repetition of filtering on instanceof
followed by a mapping with a cast. This is done three times in total. It seems likely that other code is going to need to do similar things, so it would be nice to extract this bit of logic into a helper method. The problem is that filter
can change the number of elements in the stream (dropping ones that don't match) but it can't change their types. And map
can change the types of elements, but it can't change their number. Can something change both the number and types? Yes, it's our old friend flatMap
again! So our helper method needs to take an element and return a stream of elements of a different type. That return stream will contain a single casted element (if it matches) or it will be empty (if it doesn't match). The helper function would look like this:
这暴露了过滤的重复,instanceof
然后是带有强制转换的映射。这总共做三遍。其他代码似乎很可能需要做类似的事情,因此最好将这一部分逻辑提取到辅助方法中。问题是filter
可以改变流中元素的数量(删除不匹配的元素)但不能改变它们的类型。并且map
可以改变元素的类型,但不能改变它们的数量。可以改变数量和类型吗?是的,是我们的老朋友flatMap
再次!所以我们的辅助方法需要获取一个元素并返回一个不同类型的元素流。该返回流将包含单个强制转换元素(如果匹配)或者它将为空(如果不匹配)。辅助函数如下所示:
<T,U> Stream<U> toType(T t, Class<U> clazz) {
if (clazz.isInstance(t)) {
return Stream.of(clazz.cast(t));
} else {
return Stream.empty();
}
}
(This is loosely based on C#'s OfType
construct mentioned in some of the comments.)
(这松散地基于OfType
某些评论中提到的C#构造。)
While we're at it, let's extract a method to create a ContextInfo
:
在此过程中,让我们提取一个方法来创建一个ContextInfo
:
ContextInfo makeContextInfo(Context context) {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
}
After these extractions, the pipeline looks like this:
在这些提取之后,管道看起来像这样:
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren()))
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(this::makeContextInfo)
.collect(Collectors.toList());
Nicer, I think, and we've removed the dreaded multi-line statement lambda.
我认为更好,而且我们已经删除了可怕的多行语句 lambda。
UPDATE: BONUS CHALLENGE
更新:奖励挑战
Once again, flatMap
is your friend. Take the tail of the stream and migrate it into the last flatMap
before the tail. That way the host
variable is still in scope, and you can pass it to a makeContextInfo
helper method that's been modified to take host
as well.
再一次,flatMap
是你的朋友。取流的尾部并将其迁移到flatMap
尾部之前的最后一个。这样host
变量仍然在范围内,您可以将它传递给一个makeContextInfo
已经修改为采用的辅助方法host
。
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren())
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(ctx -> makeContextInfo(ctx, host)))
.collect(Collectors.toList());
回答by Edwin Dalorzo
This would be my version of your code using JDK 8 streams, method references, and lambda expressions:
这将是我使用 JDK 8 流、方法引用和 lambda 表达式的代码版本:
server.findServices()
.stream()
.map(Service::getContainer)
.filter(Engine.class::isInstance)
.map(Engine.class::cast)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(Host.class::isInstance)
.map(Host.class::cast)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(Context.class::isInstance)
.map(Context.class::cast)
.map(context -> {
ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
In this approach, I replace your if-statements for filter predicates. Take into account that an instanceof
check can be replaced with a Predicate<T>
在这种方法中,我将您的 if 语句替换为过滤谓词。考虑到instanceof
支票可以替换为Predicate<T>
Predicate<Object> isEngine = someObject -> someObject instanceof Engine;
which can also be expressed as
也可以表示为
Predicate<Object> isEngine = Engine.class::isInstance
Similarly, your casts can be replaced by Function<T,R>
.
同样,您的演员表可以替换为Function<T,R>
.
Function<Object,Engine> castToEngine = someObject -> (Engine) someObject;
Which is pretty much the same as
这与
Function<Object,Engine> castToEngine = Engine.class::cast;
And adding items manually to a list in the for loop can be replaced with a collector. In production code, the lambda that transforms a Context
into a ContextInfo
can (and should) be extracted into a separate method, and used as a method reference.
手动将项目添加到 for 循环中的列表可以替换为收集器。在生产代码中,将 aContext
转换为 a的 lambdaContextInfo
可以(并且应该)被提取到一个单独的方法中,并用作方法引用。
回答by user11153
Solution to bonus challenge
奖金挑战的解决方案
Inspired by @EdwinDalorzo answer.
受到@EdwinDalorzo 回答的启发。
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<>();
final StandardServer server = getServer();
return server.findServices()
.stream()
.map(Service::getContainer)
.filter(Engine.class::isInstance)
.map(Engine.class::cast)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(Host.class::isInstance)
.map(Host.class::cast)
.flatMap(host -> mapContainers(
Arrays.stream(host.findChildren()), host.getName())
)
.collect(Collectors.toList());
}
private static Stream<ContextInfo> mapContainers(Stream<Container> containers,
String hostname) {
return containers
.filter(Context.class::isInstance)
.map(Context.class::cast)
.map(context -> {
ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
info.setHostname(hostname); // The Bonus Challenge
return info;
});
}
回答by David Blevins
First attempt beyond ugly. It will be years before I find this readable. Has to be a better way.
第一次尝试超越丑陋。我要过几年才能找到这种可读性。必须有更好的方法。
Note the findChildren
methods return arrays which of course work with for (N n: array)
syntax, but not with the new Iterable.forEach
method. Had to wrap them with Arrays.asList
请注意,findChildren
方法返回的数组当然适用于for (N n: array)
语法,但不适用于新Iterable.forEach
方法。不得不用它们包裹Arrays.asList
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
asList(server.findServices()).forEach(service -> {
if (!(service.getContainer() instanceof Engine)) return;
final Engine engine = (Engine) service.getContainer();
instanceOf(Host.class, asList(engine.findChildren())).forEach(host -> {
instanceOf(Context.class, asList(host.findChildren())).forEach(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
});
});
});
return list;
}
The utility methods
实用方法
public static <T> Iterable<T> instanceOf(final Class<T> type, final Collection collection) {
final Iterator iterator = collection.iterator();
return () -> new SlambdaIterator<>(() -> {
while (iterator.hasNext()) {
final Object object = iterator.next();
if (object != null && type.isAssignableFrom(object.getClass())) {
return (T) object;
}
}
throw new NoSuchElementException();
});
}
And finally a Lambda-powerable implementation of Iterable
最后是一个由 Lambda 驱动的实现 Iterable
public static class SlambdaIterator<T> implements Iterator<T> {
// Ya put your Lambdas in there
public static interface Advancer<T> {
T advance() throws NoSuchElementException;
}
private final Advancer<T> advancer;
private T next;
protected SlambdaIterator(final Advancer<T> advancer) {
this.advancer = advancer;
}
@Override
public boolean hasNext() {
if (next != null) return true;
try {
next = advancer.advance();
return next != null;
} catch (final NoSuchElementException e) {
return false;
}
}
@Override
public T next() {
if (!hasNext()) throw new NoSuchElementException();
final T v = next;
next = null;
return v;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
Lots of plumbing and no doubt 5x the byte code. Must be a better way.
大量的管道,毫无疑问是字节码的 5 倍。一定有更好的办法。