10

I want to iterate nested lists using java8 streams, and extract some results of the lists on first match. Unfortunately I have to also get a values from the parent content if a child element matches the filter.

How could I do this?

java7

Result result = new Result();

//find first match and pupulate the result object.
for (FirstNode first : response.getFirstNodes()) {
    for (SndNode snd : first.getSndNodes()) {
        if (snd.isValid()) {
            result.setKey(first.getKey());
            result.setContent(snd.getContent());
            return;
        }
    }
}

java8

 response.getFirstNodes().stream()
        .flatMap(first -> first.getSndNodes())
        .filter(snd -> snd.isValid())
        .findFirst()
        .ifPresent(???); //cannot access snd.getContent() here
Christian
  • 22,585
  • 9
  • 80
  • 106
membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • 1
    possible duplicate of [Java 8 - Streams Nested ForEach with different Collection](http://stackoverflow.com/questions/25357043/java-8-streams-nested-foreach-with-different-collection) – Bruno Ribeiro Mar 24 '15 at 14:49

2 Answers2

13

When you need both values and want to use flatMap (as required when you want to perform a short-circuit operation like findFirst), you have to map to an object holding both values

response.getFirstNodes().stream()
  .flatMap(first->first.getSndNodes().stream()
    .map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
  .filter(e->e.getValue().isValid())
  .findFirst().ifPresent(e-> {
    result.setKey(e.getKey().getKey());
    result.setContent(e.getValue().getContent());
  });

In order to use standard classes only, I use a Map.Entry as Pair type whereas a real Pair type might look more concise.

In this specific use case, you can move the filter operation to the inner stream

response.getFirstNodes().stream()
  .flatMap(first->first.getSndNodes().stream()
     .filter(snd->snd.isValid())
     .map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
  .findFirst().ifPresent(e-> {
    result.setKey(e.getKey().getKey());
    result.setContent(e.getValue().getContent());
  });

which has the neat effect that only for the one matching item, a Map.Entry instance will be created (well, should as the current implementation is not as lazy as it should but even then it will still create lesser objects than with the first variant).

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks for the explaination. Looking at your code I feel it's still better to stick stick to the java7 fashioned iteration style in case multiple/parent elements have to be accessed. – membersound Mar 24 '15 at 15:33
  • 2
    Yeah, the good old `for` loops aren’t outdated. Maybe a future Java version with language support for pairs/tuples opens the possibility for a nicer stream solution… – Holger Mar 24 '15 at 16:07
-1

It should be like this:

Edit: Thanks Holger for pointing out that the code won't stop at the first valid FirstNode

response.getFirstNodes().stream()
  .filter(it -> {it.getSndNodes().stream().filter(SndNode::isValid).findFirst(); return true;})
  .findFirst()
  .ifPresent(first -> first.getSndNodes().stream().filter(SndNode::isValid).findFirst().ifPresent(snd -> {
    result.setKey(first.getKey());
    result.setContent(snd.getContent());
  }));

A test can be found here

vsnyc
  • 2,117
  • 22
  • 35
  • 2
    This doesn’t do anything useful. The question’s original code called `result.setKey(first.getKey()); result.setContent(snd.getContent());` and stopped processing subsequent items. Your code does nothing like that. – Holger Mar 24 '15 at 15:07
  • I was showing how to access `snd.getContent()` within `ifPresent()`, have modified the code now – vsnyc Mar 24 '15 at 15:18
  • @Holger can I get the -1 off please, I have tested that my code works and does what the op intended – vsnyc Mar 24 '15 at 15:44
  • 1
    I didn’t downvote so I can’t do anything about it. Besides that, using `forEach` still doesn’t solve the question’s task which is to stop after the first match. Of course, testing with only one matching item won’t exhibit this behavior… – Holger Mar 24 '15 at 16:06
  • No problems, thanks for pointing mistakes in my code. I have now fixed the code to do the right thing. I'll leave the answer out there because it's correct. Whoever thought the answer is worth downvoting, it would have helped if I was told the reason. – vsnyc Mar 24 '15 at 16:56