如何使用 lambda 流迭代嵌套列表?
How to iterate nested lists with lambda streams?
我正在尝试将以下代码重构为带有 `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?
看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.
假设 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;
}
}
尝试使用 map
转换原始来源。
rsp.getFirstNodes().stream().map(FirstNode::getSndNodes)
.filter(sndNode-> sndNode.isValid())
.forEach(sndNode->{
// No do the sndNode parsing operation Here.
})
您可以利用 StreamSupport
提供的 stream
方法采用 Spliterator
并且 Iterable
具有 spliterator
方法这一事实。
然后您只需要一种机制将您的结构扁平化为 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);
}
您现在可以直接从您的 Iterable
直播。
初步研究表明,这应该用 flatMap
来完成,但无论如何。
恐怕使用流和 lambda 会影响您的性能。您当前的解决方案 returns 第一个有效且可解析的节点,但是不可能中断流上的操作,例如 for-each (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;
}
如开头所述,可能存在性能问题,因为 这将尝试解析每个有效节点。
编辑:
为了避免解析所有节点,您可以使用 reduce
,但它有点复杂和丑陋(并且需要额外的 class)。这也会显示所有 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
}
编辑2:
一般来说,在使用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;
}
您可以像下面这样迭代嵌套循环
allAssessmentsForJob.getBody().stream().forEach(assessment -> {
jobAssessments.stream().forEach(jobAssessment -> {
if (assessment.getId() == jobAssessment.getAssessmentId()) {
jobAssessment.setAssessment(assessment);
}
});
});
有点晚了,但这里有一个可读的方法:
Result = rsp.getFirstNodes()
.stream()
.flatMap(firstNode -> firstNode.getSndNodes.stream())
.filter(secondNode::isValid))
.findFirst()
.map(node -> this.parseNode(node)).orElse(null);
解释:你得到了所有的firstNodes
和stream()
。出每个 firstNode 你带来 n SndNodes
。您检查每个 SndNodes
以查看找到 第一个 有效 。如果没有有效的 SndNode,那么我们将得到一个空值。如果有,它将被解析为 Result
parseMethod() 与原来的没有变化:
public Result parseNode(SndNode node){
try {
...
... // attempt to parsed node
} catch (ParseException e) {
throw new ParseException;
}
}
我正在尝试将以下代码重构为带有 `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?
看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.
假设 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;
}
}
尝试使用 map
转换原始来源。
rsp.getFirstNodes().stream().map(FirstNode::getSndNodes)
.filter(sndNode-> sndNode.isValid())
.forEach(sndNode->{
// No do the sndNode parsing operation Here.
})
您可以利用 StreamSupport
提供的 stream
方法采用 Spliterator
并且 Iterable
具有 spliterator
方法这一事实。
然后您只需要一种机制将您的结构扁平化为 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);
}
您现在可以直接从您的 Iterable
直播。
初步研究表明,这应该用 flatMap
来完成,但无论如何。
恐怕使用流和 lambda 会影响您的性能。您当前的解决方案 returns 第一个有效且可解析的节点,但是不可能中断流上的操作,例如 for-each (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;
}
如开头所述,可能存在性能问题,因为 这将尝试解析每个有效节点。
编辑:
为了避免解析所有节点,您可以使用 reduce
,但它有点复杂和丑陋(并且需要额外的 class)。这也会显示所有 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
}
编辑2:
一般来说,在使用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;
}
您可以像下面这样迭代嵌套循环
allAssessmentsForJob.getBody().stream().forEach(assessment -> {
jobAssessments.stream().forEach(jobAssessment -> {
if (assessment.getId() == jobAssessment.getAssessmentId()) {
jobAssessment.setAssessment(assessment);
}
});
});
有点晚了,但这里有一个可读的方法:
Result = rsp.getFirstNodes()
.stream()
.flatMap(firstNode -> firstNode.getSndNodes.stream())
.filter(secondNode::isValid))
.findFirst()
.map(node -> this.parseNode(node)).orElse(null);
解释:你得到了所有的firstNodes
和stream()
。出每个 firstNode 你带来 n SndNodes
。您检查每个 SndNodes
以查看找到 第一个 有效 。如果没有有效的 SndNode,那么我们将得到一个空值。如果有,它将被解析为 Result
parseMethod() 与原来的没有变化:
public Result parseNode(SndNode node){
try {
...
... // attempt to parsed node
} catch (ParseException e) {
throw new ParseException;
}
}