-2

I tried to write the following code using the stream API, but found that being not able to access the Parent objects in the stream of Grandchild objects, it is way easier to use classic for-loops. How could a solution using the stream API look like? And are there any advantages to such a solution? The task is to find certain elements in a structure like this: parent -> List -> List and to get for each matching Grandchildren a String of nameOfParent.nameOfGrandchild. Simple, working code with for-loops:

        List<String> grandchilds = new ArrayList<>();
        for (Parent parent : parents) {
            String parentName = parent.getName() + ".";
            for (Child child : parent.getChilds()) {
                if (null != child.getTags() && child.getTags().size() == 1
                        && SEARCHED_TAG.equals(child.getTags().get(0))) {
                    for (Grandchild gc : child.getGrandchilds()) {
                        grandchilds.add(parentName + gc.getName());
                    }
                }
            }
        }

I was at this point using stream API where i realized that accessing properties of parent is no longer possible when i have the stream of Grandchild.

            List<Grandchild> grandchildren = parents.stream()
                .flatMap(parent -> parent.getChilds().stream())
                .filter(child -> null != child.getTags() && child.getTags().size() == 1 && SEARCHED_TAG.equals(child.getTags().get(0)))
                .flatMap(child -> child.getGrandchilds().stream())
                .collect(Collectors.toList());        

I found some hints like in the following questions, but the resulting code seemed unnecessarily complex and there is the occasional recommendation to use for-loops instead.

Access element of previous step in a stream or pass down element as "parameter" to next step?

Get parent object from stream

How to iterate nested for loops referring to parent elements using Java 8 streams?

Pao
  • 843
  • 5
  • 17

1 Answers1

1

Rather than chaining flatMap() calls in a single pipeline, nest one inside the other:

List<String> grandchilds = parents.stream()
  .flatMap(parent -> parent.getChilds().stream()
    .filter(tagMatch(SEARCHED_TAG))
    .flatMap(child -> child.getGrandchilds().stream().map(gc -> parent.getName() + "." + gc.getName())))
  .toList();

private static Predicate<Child> tagMatch(String tag) {
  return child -> {
    List<String> tags = child.getTags();
    return tags != null && tags.size() == 1 && tags.get(0).equals(tag);
  };
}

I didn't test that and may have missed a parens in there, but that's the gist of it.

As far as advantages go, I don't see any. That aspect is opinion-based, but in my opinion, the for-loops are easier to read and verify, even after factoring out the rather complicated filtering predicate.

erickson
  • 265,237
  • 58
  • 395
  • 493