Java 如何使用 lambda 流迭代嵌套列表?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29215375/
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
How to iterate nested lists with lambda streams?
提问by membersound
I'm trying to refactor the following code to lambda expressions with `stream, especially the nested foreach loops:
我正在尝试将以下代码重构为带有 `stream 的 lambda 表达式,尤其是嵌套的 foreach 循环:
public static Result match (Response rsp) {
Exception lastex = null;
for (FirstNode firstNode : rsp.getFirstNodes()) {
for (SndNode sndNode : firstNode.getSndNodes()) {
try {
if (sndNode.isValid())
return parse(sndNode); //return the first match, retry if fails with ParseException
} catch (ParseException e) {
lastex = e;
}
}
}
//throw the exception if all elements failed
if (lastex != null) {
throw lastex;
}
return null;
}
I'm starting with:
我从:
rsp.getFirstNodes().forEach().?? // how to iterate the nested 2ndNodes?
采纳答案by Jaroslaw Pawlak
I am afraid that using streams and lambdas, your performance may suffer. Your current solution returns the first valid and parse-able node, however it is not possible to interrupt an operation on stream such as for-each (source).
我担心使用流和 lambdas,你的性能可能会受到影响。您当前的解决方案返回第一个有效且可解析的节点,但是不可能中断对流的操作,例如 for-each ( source)。
Also, because you can have two different outputs (returned result or thrown exception), it won't be possible to do this with single line expression.
此外,因为您可以有两个不同的输出(返回的结果或抛出的异常),所以不可能用单行表达式来做到这一点。
Here is what I came up with. It may give you some ideas:
这是我想出的。它可能会给你一些想法:
public static Result match(Response rsp) throws Exception {
Map<Boolean, List<Object>> collect = rsp.getFirstNodes().stream()
.flatMap(firstNode -> firstNode.getSndNodes().stream()) // create stream of SndNodes
.filter(SndNode::isValid) // filter so we only have valid nodes
.map(node -> {
// try to parse each node and return either the result or the exception
try {
return parse(node);
} catch (ParseException e) {
return e;
}
}) // at this point we have stream of objects which may be either Result or ParseException
.collect(Collectors.partitioningBy(o -> o instanceof Result)); // split the stream into two lists - one containing Results, the other containing ParseExceptions
if (!collect.get(true).isEmpty()) {
return (Result) collect.get(true).get(0);
}
if (!collect.get(false).isEmpty()) {
throw (Exception) collect.get(false).get(0); // throws first exception instead of last!
}
return null;
}
As mentioned at the beginning, there is possible performance issue as this will try to parse every valid node.
正如开头提到的,可能存在性能问题,因为这将尝试解析每个有效节点。
EDIT:
编辑:
To avoid parsing all nodes, you could use reduce
, but it is a bit more complex and ugly (and extra class is needed). This also shows all ParseException
s instead of just last one.
为了避免解析所有节点,您可以使用reduce
,但它有点复杂和丑陋(并且需要额外的类)。这也显示了所有ParseException
s 而不是最后一个。
private static class IntermediateResult {
private final SndNode node;
private final Result result;
private final List<ParseException> exceptions;
private IntermediateResult(SndNode node, Result result, List<ParseException> exceptions) {
this.node = node;
this.result = result;
this.exceptions = exceptions;
}
private Result getResult() throws ParseException {
if (result != null) {
return result;
}
if (exceptions.isEmpty()) {
return null;
}
// this will show all ParseExceptions instead of just last one
ParseException exception = new ParseException(String.format("None of %s valid nodes could be parsed", exceptions.size()));
exceptions.stream().forEach(exception::addSuppressed);
throw exception;
}
}
public static Result match(Response rsp) throws Exception {
return Stream.concat(
Arrays.stream(new SndNode[] {null}), // adding null at the beginning of the stream to get an empty "aggregatedResult" at the beginning of the stream
rsp.getFirstNodes().stream()
.flatMap(firstNode -> firstNode.getSndNodes().stream())
.filter(SndNode::isValid)
)
.map(node -> new IntermediateResult(node, null, Collections.<ParseException>emptyList()))
.reduce((aggregatedResult, next) -> {
if (aggregatedResult.result != null) {
return aggregatedResult;
}
try {
return new IntermediateResult(null, parse(next.node), null);
} catch (ParseException e) {
List<ParseException> exceptions = new ArrayList<>(aggregatedResult.exceptions);
exceptions.add(e);
return new IntermediateResult(null, null, Collections.unmodifiableList(exceptions));
}
})
.get() // aggregatedResult after going through the whole stream, there will always be at least one because we added one at the beginning
.getResult(); // return Result, null (if no valid nodes) or throw ParseException
}
EDIT2:
编辑2:
In general, it is also possible to use lazy evaluation when using terminal operators such as findFirst()
. So with a minor change of requirements (i.e. returning null instead of throwing exception), it should be possible to do something like below. However, flatMap
with findFirst
doesn't use lazy evaluation (source), so this code tries to parse all nodes.
通常,在使用终端运算符(例如findFirst()
. 因此,只要稍微改变需求(即返回 null 而不是抛出异常),就可以执行如下操作。但是,flatMap
withfindFirst
不使用惰性求值(source),因此此代码尝试解析所有节点。
private static class ParsedNode {
private final Result result;
private ParsedNode(Result result) {
this.result = result;
}
}
public static Result match(Response rsp) throws Exception {
return rsp.getFirstNodes().stream()
.flatMap(firstNode -> firstNode.getSndNodes().stream())
.filter(SndNode::isValid)
.map(node -> {
try {
// will parse all nodes because of flatMap
return new ParsedNode(parse(node));
} catch (ParseException e ) {
return new ParsedNode(null);
}
})
.filter(parsedNode -> parsedNode.result != null)
.findFirst().orElse(new ParsedNode(null)).result;
}
回答by membersound
Look at flatMap:
看平面图:
flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element.
flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
返回一个流,该流由通过将提供的映射函数应用于每个元素而生成的映射流的内容替换此流的每个元素的结果组成。
Code sample assuming isValid()
doesn't throw
假设isValid()
不抛出的代码示例
Optional<SndNode> sndNode = rsp.getFirstNodes()
.stream()
.flatMap(firstNode -> firstNode.getSndNodes().stream()) //This is the key line for merging the nested streams
.filter(sndNode -> sndNode.isValid())
.findFirst();
if (sndNode.isPresent()) {
try {
parse(sndNode.get());
} catch (ParseException e) {
lastex = e;
}
}
回答by Masudul
Try to use map
which transform the original source.
尝试使用map
which 转换原始源。
rsp.getFirstNodes().stream().map(FirstNode::getSndNodes)
.filter(sndNode-> sndNode.isValid())
.forEach(sndNode->{
// No do the sndNode parsing operation Here.
})
回答by OldCurmudgeon
You could use that fact that StreamSupport
provides a stream
method that takes a Spliterator
and Iterable
has a spliterator
method.
您可以使用该事实,该事实StreamSupport
提供了一个stream
采用 aSpliterator
并Iterable
拥有一个spliterator
方法的方法。
You then just need a mechanism to flatten your structure into an Iterable
- something like this.
然后你只需要一种机制来将你的结构扁平化成一个Iterable
- 像这样的东西。
class IterableIterable<T> implements Iterable<T> {
private final Iterable<? extends Iterable<T>> i;
public IterableIterable(Iterable<? extends Iterable<T>> i) {
this.i = i;
}
@Override
public Iterator<T> iterator() {
return new IIT();
}
private class IIT implements Iterator<T> {
// Pull an iterator.
final Iterator<? extends Iterable<T>> iit = i.iterator();
// The current Iterator<T>
Iterator<T> it = null;
// The current T.
T next = null;
@Override
public boolean hasNext() {
boolean finished = false;
while (next == null && !finished) {
if (it == null || !it.hasNext()) {
if (iit.hasNext()) {
it = iit.next().iterator();
} else {
finished = true;
}
}
if (it != null && it.hasNext()) {
next = it.next();
}
}
return next != null;
}
@Override
public T next() {
T n = next;
next = null;
return n;
}
}
}
public void test() {
List<List<String>> list = new ArrayList<>();
List<String> first = new ArrayList<>();
first.add("First One");
first.add("First Two");
List<String> second = new ArrayList<>();
second.add("Second One");
second.add("Second Two");
list.add(first);
list.add(second);
// Check it works.
IterableIterable<String> l = new IterableIterable<>(list);
for (String s : l) {
System.out.println(s);
}
// Stream it like this.
Stream<String> stream = StreamSupport.stream(l.spliterator(), false);
}
You can now stream directly from your Iterable
.
您现在可以直接从您的Iterable
.
Initial research suggests that this should be done with flatMap
but whatever.
初步研究表明,这应该以flatMap
任何方式完成。
回答by Sunny Gupta
You can iterate nested loops like below
您可以像下面这样迭代嵌套循环
allAssessmentsForJob.getBody().stream().forEach(assessment -> {
jobAssessments.stream().forEach(jobAssessment -> {
if (assessment.getId() == jobAssessment.getAssessmentId()) {
jobAssessment.setAssessment(assessment);
}
});
});
回答by arthur
A little bit late but here is a readable approach:
有点晚了,但这里有一个可读的方法:
Result = rsp.getFirstNodes()
.stream()
.flatMap(firstNode -> firstNode.getSndNodes.stream())
.filter(secondNode::isValid))
.findFirst()
.map(node -> this.parseNode(node)).orElse(null);
Explanation: you get all the firstNodes
and stream()
them up. Out a each firstNode you bring n SndNodes
. You check each SndNodes
to see find the firstone that is valid. If there is no valid SndNode then we'll get a null. If there is one, it'll get parsed into a Result
解释:你把所有的firstNodes
和stream()
它们都弄起来。拿出一个你带来的每个 firstNode SndNodes
。您检查每个SndNodes
以查看找到第一个有效的。如果没有有效的 SndNode,那么我们将得到一个空值。如果有,它会被解析成一个Result
the parseMethod() doesn't change from the original:
parseMethod() 与原始方法没有变化:
public Result parseNode(SndNode node){
try {
...
... // attempt to parsed node
} catch (ParseException e) {
throw new ParseException;
}
}