Ответ 1
Я боюсь, что с использованием потоков и лямбда ваша производительность может пострадать. Ваше текущее решение возвращает первый допустимый и синтаксический анализ node, однако невозможно прервать операцию в потоке, например, для каждого (source).
Кроме того, поскольку вы можете иметь два разных выхода (возвращаемый результат или исключение), это невозможно будет сделать с помощью однострочного выражения.
Вот что я придумал. Это может дать вам несколько идей:
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;
}
Как уже упоминалось в начале, существует проблема с производительностью, поскольку это попытается проанализировать все допустимые node.
EDIT:
Чтобы избежать разбора всех узлов, вы можете использовать reduce
, но он немного сложнее и уродливее (и нужен дополнительный класс). Это также показывает все ParseException
вместо последнего.
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:
В целом, также можно использовать ленивую оценку при использовании терминальных операторов, таких как 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;
}