如何使用lambda流迭代嵌套列表?

我正在尝试使用`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; } 

我开始:

 rsp.getFirstNodes().forEach().?? // how to iterate the nested 2ndNodes? 

我担心使用溪流和lambdas,你的表现可能会受到影响。 您当前的解决方案返回第一个有效且可解析的节点,但是无法中断流上的操作,例如for-each( 源 )。

此外,因为您可以有两个不同的输出(返回结果或抛出exception),所以不可能使用单行表达式执行此操作。

这就是我想出的。 它可能会给你一些想法:

 public static Result match(Response rsp) throws Exception { Map> 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; } 

正如开头所提到的,可能存在性能问题,因为这会尝试解析每个有效节点


编辑:

为了避免解析所有节点,你可以使用reduce ,但它有点复杂和丑陋(需要额外的类)。 这也显示了所有ParseException而不是最后一个。

 private static class IntermediateResult { private final SndNode node; private final Result result; private final List exceptions; private IntermediateResult(SndNode node, Result result, List 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.emptyList())) .reduce((aggregatedResult, next) -> { if (aggregatedResult.result != null) { return aggregatedResult; } try { return new IntermediateResult(null, parse(next.node), null); } catch (ParseException e) { List 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:

通常,在使用诸如findFirst()类的终端运算符时,也可以使用延迟求值。 因此,通过稍微改变需求(即返回null而不是抛出exception),应该可以执行类似下面的操作。 但是,使用findFirst flatMap不使用延迟评估( 源 ),因此此代码尝试解析所有节点。

 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; } 

看看flatMap:

flatMap(Function> mapper)
返回一个流,该流包含将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果。

代码示例假设isValid()不抛出

 Optional 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; } } 

尝试使用转换原始源的map

  rsp.getFirstNodes().stream().map(FirstNode::getSndNodes) .filter(sndNode-> sndNode.isValid()) .forEach(sndNode->{ // No do the sndNode parsing operation Here. }) 

有点晚了,但这是一个可读的方法:

  Result = rsp.getFirstNodes() .stream() .flatMap(firstNode -> firstNode.getSndNodes.stream()) .filter(secondNode::isValid)) .findFirst() .map(node -> this.parseNode(node)).orElse(null); 

说明 :获取所有firstNodesstream()它们。 输出每个firstNode,你带来n个SndNodes 。 您检查每个SndNodes以查找找到一个有效的 SndNodes 。 如果没有有效的SndNode,那么我们将得到一个null。 如果有,它将被解析为Result

parseMethod()不会改变原始:

 public Result parseNode(SndNode node){ try { ... ... // attempt to parsed node } catch (ParseException e) { throw new ParseException; } } 

您可以使用StreamSupport提供一个带SpliteratorIterablespliterator方法的spliterator方法。

然后,您只需要一种机制将您的结构展平为Iterable – 就像这样。

 class IterableIterable implements Iterable { private final Iterable> i; public IterableIterable(Iterable> i) { this.i = i; } @Override public Iterator iterator() { return new IIT(); } private class IIT implements Iterator { // Pull an iterator. final Iterator> iit = i.iterator(); // The current Iterator Iterator 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 = new ArrayList<>(); List first = new ArrayList<>(); first.add("First One"); first.add("First Two"); List second = new ArrayList<>(); second.add("Second One"); second.add("Second Two"); list.add(first); list.add(second); // Check it works. IterableIterable l = new IterableIterable<>(list); for (String s : l) { System.out.println(s); } // Stream it like this. Stream stream = StreamSupport.stream(l.spliterator(), false); } 

您现在可以直接从您的Iterable流式传输。

最初的研究表明,这应该用flatMap完成,但无论如何。

您可以迭代嵌套循环,如下所示

 allAssessmentsForJob.getBody().stream().forEach(assessment -> { jobAssessments.stream().forEach(jobAssessment -> { if (assessment.getId() == jobAssessment.getAssessmentId()) { jobAssessment.setAssessment(assessment); } }); });