I want to iterate a list with nested list of object similar to below
Country {
String name;
State capital
List<State> states;
}
State {
String name;
City capital
List<City> cities;
}
City {
String name;
}
I am using below code to get the capital list of all states with name 'XYZ'
List<Country> countries = getCountriesList();
if (countries != null) {
countries.stream()
.filter(Objects::nonNull)
.filter(country -> CollectionUtils.isNotEmpty(country.getStates()))
.flatMap(country -> country.getStates().stream())
.filter(Objects::nonNull)
.filter(state -> "XYZ".equalsIgnoreCase(state.getName()))
.map(State::getCapital)
.filter(Objects::nonNull)
.map(capital -> capital.getName())
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
How can I refactor this to avoid using non null at each step?
I'm not sure why you would want to avoid using nonNull
. The current way of doing it is perfectly readable, and it clearly shows your intentions. If I really were to nitpick, I would remove the filter for empty states,
.filter(country -> CollectionUtils.isNotEmpty(country.getStates()))
since this is already covered by the flatMap
in the next line. Also, you can use .toList()
instead of collect
since Java 9.
That said, if you really want to get rid of the nonNull
filters, you can do so by using a lot of Optional
s. The capital and capital name null checks can be done by flat mapping to the optional's stream
, and the state null check can be combined with the "XYZ"
state name filter on the next line.
That gives us:
countries.stream()
.filter(Objects::nonNull)
.flatMap(country -> country.getStates().stream())
.filter(state -> state != null && "XYZ".equalsIgnoreCase(state.getName()))
.flatMap(state -> Optional.ofNullable(state.getCapital()).stream())
.flatMap(capital -> Optional.ofNullable(capital.getName()).stream())
.toList();
The first null check is a bit hard to get rid of, unless you have a known country that has no states:
public static final Country EMPTY = new Country("", null, List.of());
Then you could use Objects.requireNonNullElse
to default to the EMPTY
country:
countries.stream()
.flatMap(country -> Objects.requireNonNullElse(country, Country.EMPTY).getStates().stream())
Really though, with each nonNull
you avoid, you are just making the code harder to read.