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

mem*_*und 17 java lambda java-8 java-stream

我正在尝试使用`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;
}
Run Code Online (Sandbox Code Playgroud)

我开始时:

rsp.getFirstNodes().forEach().?? // how to iterate the nested 2ndNodes?
Run Code Online (Sandbox Code Playgroud)

小智 18

看看flatMap:

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

代码示例假设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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 有关该解决方案的更多细节将非常方便. (4认同)

Jar*_*lak 9

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

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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


编辑:

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

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
}
Run Code Online (Sandbox Code Playgroud)

EDIT2:

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

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;
}
Run Code Online (Sandbox Code Playgroud)

  • @membersound我已经编辑了我的答案并添加了另一个解决方案 - 有点丑陋,但不解析所有节点.另外,正如有人建议的那样,为什么抛出最后一个`ParseException`有点不清楚,所以我也修复了它 - 现在如果没有`Result`你会看到所有`ParseException'. (2认同)
  • 我认为这很好地说明了为什么使用流并不总是最好的解决方案.:) (2认同)