Java 8 list processing - add elements conditionally
JavaCollectionsJava 8Java StreamJava Problem Overview
I have the following piece of code:
List<Object> list = new ArrayList<>();
list.addAll(method1());
if(list.isEmpty()) { list.addAll(method2()); }
if(list.isEmpty()) { list.addAll(method3()); }
if(list.isEmpty()) { list.addAll(method4()); }
if(list.isEmpty()) { list.addAll(method5()); }
if(list.isEmpty()) { list.addAll(method6()); }
return list;
Is there a nice way to add elements conditionally, maybe using stream operations? I would like to add elements from method2 only if the list is empty otherwise return and so on.
Edit: It's worth to mention that the methods contain heavy logic so need to be prevented from execution.
Java Solutions
Solution 1 - Java
You could try to check the return value of addAll
. It will return true
whenever the list has been modified, so try this:
List<Object> list = new ArrayList<>();
// ret unused, otherwise it doesn't compile
boolean ret = list.addAll(method1())
|| list.addAll(method2())
|| list.addAll(method3())
|| list.addAll(method4())
|| list.addAll(method5())
|| list.addAll(method6());
return list;
Because of lazy evaluation, the first addAll
operation that added at least one element will prevent the rest from bein called. I like the fact that "||" expresses the intent quite well.
Solution 2 - Java
I would simply use a stream of suppliers and filter on List.isEmpty
:
Stream.<Supplier<List<Object>>>of(() -> method1(),
() -> method2(),
() -> method3(),
() -> method4(),
() -> method5(),
() -> method6())
.map(Supplier<List<Object>>::get)
.filter(l -> !l.isEmpty())
.findFirst()
.ifPresent(list::addAll);
return list;
findFirst()
will prevent unnecessary calls to methodN()
when the first non-empty list is returned by one of the methods.
EDIT:
As remarked in comments below, if your list
object is not initialized with anything else, then it makes sense to just return the result of the stream directly:
return Stream.<Supplier<List<Object>>>of(() -> method1(),
() -> method2(),
() -> method3(),
() -> method4(),
() -> method5(),
() -> method6())
.map(Supplier<List<Object>>::get)
.filter(l -> !l.isEmpty())
.findFirst()
.orElseGet(ArrayList::new);
Solution 3 - Java
A way of doing it without repeating yourself is to extract a method doing it for you:
private void addIfEmpty(List<Object> targetList, Supplier<Collection<?>> supplier) {
if (targetList.isEmpty()) {
targetList.addAll(supplier.get());
}
}
And then
List<Object> list = new ArrayList<>();
addIfEmpty(list, this::method1);
addIfEmpty(list, this::method2);
addIfEmpty(list, this::method3);
addIfEmpty(list, this::method4);
addIfEmpty(list, this::method5);
addIfEmpty(list, this::method6);
return list;
Or even use a for loop:
List<Supplier<Collection<?>>> suppliers = Arrays.asList(this::method1, this::method2, ...);
List<Object> list = new ArrayList<>();
suppliers.forEach(supplier -> this.addIfEmpty(list, supplier));
Now DRY is not the most important aspect. If you think your original code is easier to read and understand, then keep it like that.
Solution 4 - Java
You could make your code nicer by creating the method
public void addAllIfEmpty(List<Object> list, Supplier<List<Object>> method){
if(list.isEmpty()){
list.addAll(method.get());
}
}
Then you can use it like this (I assumed your methods are not static methods, if they are you need to reference them using ClassName::method1
)
List<Object> list = new ArrayList<>();
list.addAll(method1());
addAllIfEmpty(list, this::method2);
addAllIfEmpty(list, this::method3);
addAllIfEmpty(list, this::method4);
addAllIfEmpty(list, this::method5);
addAllIfEmpty(list, this::method6);
return list;
If you really want to use a Stream, you could do this
Stream.<Supplier<List<Object>>>of(this::method1, this::method2, this::method3, this::method4, this::method5, this::method6)
.collect(ArrayList::new, this::addAllIfEmpty, ArrayList::addAll);
IMO it makes it more complicated, depending on how your methods are referenced, it might be better to use a loop
Solution 5 - Java
You could create a method as such:
public static List<Object> lazyVersion(Supplier<List<Object>>... suppliers){
return Arrays.stream(suppliers)
.map(Supplier::get)
.filter(s -> !s.isEmpty()) // or .filter(Predicate.not(List::isEmpty)) as of JDK11
.findFirst()
.orElseGet(Collections::emptyList);
}
and then call it as follows:
lazyVersion(() -> method1(),
() -> method2(),
() -> method3(),
() -> method4(),
() -> method5(),
() -> method6());
method name for illustration purposes only.