Java 8 list processing - add elements conditionally

JavaCollectionsJava 8Java Stream

Java 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionionutView Question on Stackoverflow
Solution 1 - JavaDorian GrayView Answer on Stackoverflow
Solution 2 - Javaernest_kView Answer on Stackoverflow
Solution 3 - JavaJB NizetView Answer on Stackoverflow
Solution 4 - JavaRicolaView Answer on Stackoverflow
Solution 5 - JavaOusmane D.View Answer on Stackoverflow