需要一些帮助思考来自我的同伴StackOverflow名人的lambda。
通过列表列表选择以在图表中深入收集一些孩子的标准情况。什么样的方法可以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;
}
请注意,列表本身将作为JSON
发送到客户端,因此请不要关注返回的内容。必须是一些简洁的方法,我可以减少循环。
有兴趣了解我的同事专家创建的内容。鼓励采用多种方法。
修改
findServices
和两个findChildren
方法返回数组
编辑 - 奖金挑战
“不重要的部分”确实变得很重要。我实际上需要复制host
实例中仅提供的值。这似乎毁了所有美丽的例子。如何让国家前进?
final ContextInfo info = new ContextInfo(context.getPath());
info.setHostname(host.getName()); // The Bonus Challenge
答案 0 :(得分:36)
这是相当深的嵌套,但它似乎并不特别困难。
第一个观察结果是,如果for循环转换为流,则可以使用flatMap
将嵌套的for循环“展平”为单个流。此操作采用单个元素并在流中返回任意数字元素。我查了一下,发现StandardServer.findServices()
返回Service
数组,因此我们使用Arrays.stream()
将其转换为流。 (我对Engine.findChildren()
和Host.findChildren()
做了类似的假设。
接下来,每个循环中的逻辑执行instanceof
检查和强制转换。这可以使用流作为filter
操作进行建模,以执行instanceof
后跟map
操作,该操作只是转换并返回相同的引用。这实际上是一个无操作,但它允许静态类型系统将Stream<Container>
转换为Stream<Host>
。
将这些转换应用于嵌套循环,我们得到以下结果:
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;
}
但等等,还有更多。
最终forEach
操作是一项稍微复杂的map
操作,可将Context
转换为ContextInfo
。此外,这些只是收集到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());
}
我通常会尝试避免使用多行lambda(例如在最后的map
操作中),所以我将它重构为一个小的帮助器方法,它接受Context
并返回{{1 }}。这根本不会缩短代码,但我认为它确实使代码更加清晰。
<强>更新强>
但是等等,还有更多。
让我们将对ContextInfo
的调用提取到它自己的管道元素中:
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()))
// ...
上重复过滤,然后使用强制转换进行映射。这总共完成了三次。似乎其他代码可能需要做类似的事情,所以将这些逻辑提取到辅助方法中会很好。问题是instanceof
可以更改流中的元素数量(删除不匹配的元素),但不能更改其类型。 filter
可以更改元素的类型,但不能更改它们的数量。有什么东西可以改变数量和类型吗?是的,这是我们的老朋友map
了!所以我们的helper方法需要获取一个元素并返回一个不同类型的元素流。该返回流将包含单个转换元素(如果匹配)或者它将为空(如果它不匹配)。辅助函数如下所示:
flatMap
(这是基于一些评论中提到的C#的<T,U> Stream<U> toType(T t, Class<U> clazz) {
if (clazz.isInstance(t)) {
return Stream.of(clazz.cast(t));
} else {
return Stream.empty();
}
}
结构。)
虽然我们正在使用它,但我们提取一个方法来创建OfType
:
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;
}
好吧,我想,我们已经删除了可怕的多行语句lambda。
更新:奖金挑战
再一次, 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());
是你的朋友。抓住流的尾部并将其迁移到尾部前的最后flatMap
。这样flatMap
变量仍在范围内,您可以将其传递给host
辅助方法,该方法已被修改为也可以makeContextInfo
。
host
答案 1 :(得分:26)
这将是我使用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());
在这种方法中,我替换了过滤谓词的if语句。考虑到instanceof
支票可以替换为Predicate<T>
Predicate<Object> isEngine = someObject -> someObject instanceof Engine;
也可以表示为
Predicate<Object> isEngine = Engine.class::isInstance
同样,您的演员阵容可以由Function<T,R>
替换。
Function<Object,Engine> castToEngine = someObject -> (Engine) someObject;
与
几乎相同Function<Object,Engine> castToEngine = Engine.class::cast;
手动将项目添加到for循环中的列表可以用收集器替换。在生产代码中,将Context
转换为ContextInfo
的lambda可以(并且应该)提取到单独的方法中,并用作方法参考。
答案 2 :(得分:2)
受@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;
});
}
答案 3 :(得分:1)
首次尝试超越难看。我会发现这个可读性还需要几年时间。必须是一个更好的方式。
注意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;
}
实用方法
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();
});
}
最后是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();
}
}
很多管道,毫无疑问是字节码的5倍。必须是更好的方式。