How to flatten nested map of lists with Java 8 Stream?

Varda Elentári :

I have a structure that looks like this:

public class Category {
    private String tag;
    private String name;
    private String description;
    private List<Item> items;
}

and Item looks like this

public class Item {
    private String itemTag;
    private String itemName;
    private String itemType;
    private Integer itemStatus;
    private List<Item> items;
}

It's not the best design - I know, but I have no power to change that design.

I'm trying to find a way to flatten this structure to a single Stream and find an Item with matching itemTag. Using this code:

String tagToFind = "someTag";
List<Category> categories = getCategoriesList(); // <-- returns a list of Category
Item item = categories.stream()
                .flatMap(category -> category.getItems().stream())
                .filter(tagToFind.equals(item.getItemTag()))
                .findFirst();

But this only searches one level of the item list. If I want to go a level deeper I can simply do :

Item item = categories.stream()
                .flatMap(category -> category.getItems().stream())
                .flatMap(item->item.getItems().stream()))
                .filter(tagToFind.equals(item.getItemTag()))
                .findFirst();

Which works fine. But I'm trying to find a more scalable way of doing this where it can go as deep as the nested lists go. Is there an efficient way of doing this?

Samuel Philipp :

You need a separate method for the recursion. You can do it like this:

Optional<Item> item = categories.stream()
        .flatMap(category -> category.getItems().stream())
        .flatMap(MyClass::flatMapRecursive)
        .filter(i -> tagToFind.equals(i.getItemTag()))
        .findFirst();

Use this flatMapRecursive() method:

public Stream<Item> flatMapRecursive(Item item) {
    return Stream.concat(Stream.of(item), item.getItems().stream()
            .flatMap(MyClass::flatMapRecursive));
}

One more thing to consider: The flatMapRecursive() method does no null checks, so every item need at least an empty list, otherwise you will get a NullPointerException.

If null values are possible for the items you can prevent this using an Optional:

public Stream<Item> flatMapRecursive(Item item) {
    return Stream.concat(Stream.of(item), Optional.ofNullable(item.getItems())
            .orElseGet(Collections::emptyList)
            .stream()
            .flatMap(MyClass::flatMapRecursive));
}

Or doing the null check of items before using it:

public Stream<Item> flatMapRecursive(Item item) {
    if (item.getItems() == null) {
        return Stream.empty();
    }
    return Stream.concat(Stream.of(item), item.getItems().stream()
            .flatMap(MyClass::flatMapRecursive));
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=89187&siteId=1