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?
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));
}
Another way :
public Item getFirstItemWithTag(List<Category> categories, String tag) {
List<List<Item>> items = categories
.stream()
.map(Category::getItems)
.collect(Collectors.toList());
for(List<Item> items1 : items) {
List<Item> itemsToAdd = items1.stream().filter(Objects::nonNull).collect(Collectors.toList());
Optional<Item> first = itemsToAdd
.stream()
.filter(item -> item != null && tag.equals(item.getItemTag()))
.findFirst();
if (first.isPresent()) {
return first.get();
}
do {
Stream<Item> itemStream = itemsToAdd
.stream()
.map(Item::getItems)
.flatMap(Collection::stream)
.filter(Objects::nonNull);
first = itemsToAdd
.stream()
.filter(item -> item != null && tag.equals(item.getItemTag()))
.findFirst();
if (first.isPresent()) {
return first.get();
}
itemsToAdd = itemStream
.collect(Collectors.toList());
} while (!itemsToAdd.isEmpty());
}
return null;
}
This also removes the null entries of Item
and is faster than collecting full list of Item
s before filtering as it filters as it discovers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With