Обработка списка в Java 8 - добавление элементов условно

У меня есть следующий кусок кода:

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;

Есть хороший способ добавить элементы условно, может быть, с использованием потоковых операций? Я хотел бы добавить элементы из method2 только в том случае, если список пуст, в противном случае вернуть и так далее.

Изменение: Стоит отметить, что методы содержат тяжелую логику, поэтому необходимо предотвратить выполнение.

Ответы

Ответ 1

Я бы просто использовал поток поставщиков и фильтр на 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() предотвратит ненужные вызовы methodN() когда один из методов methodN() первый непустой список.

РЕДАКТИРОВАТЬ:
Как отмечено в комментариях ниже, если ваш объект list не инициализируется чем-то еще, то имеет смысл просто возвращать результат потока напрямую:

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);

Ответ 2

Вы можете попробовать проверить возвращаемое значение addAll. Он будет возвращать true всякий раз, когда список был изменен, поэтому попробуйте это:

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;

Из-за ленивых вычислений первая операция addAll которая добавила хотя бы один элемент, предотвратит вызов остальных. Мне нравится тот факт, что "||" выражает намерение довольно хорошо.

Ответ 3

Способ сделать это без повторения - это извлечь метод, который сделает это за вас:

private void addIfEmpty(List<Object> targetList, Supplier<Collection<?>> supplier) {
    if (targetList.isEmpty()) {
        targetList.addAll(supplier.get());
    }
}

А потом

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;

Или даже использовать цикл for:

List<Supplier<Collection<?>>> suppliers = Arrays.asList(this::method1, this::method2, ...);
List<Object> list = new ArrayList<>();
suppliers.forEach(supplier -> this.addIfEmpty(list, supplier));

Теперь СУХОЙ не самый важный аспект. Если вы думаете, что ваш исходный код легче читать и понимать, то продолжайте в том же духе.

Ответ 4

Вы можете сделать свой код лучше, создав метод

public void addAllIfEmpty(List<Object> list, Supplier<List<Object>> method){
    if(list.isEmpty()){
        list.addAll(method.get());
    }
}

Затем вы можете использовать его следующим образом (я предположил, что ваши методы не являются статическими, если они вам нужны, вы должны ссылаться на них, используя 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;

Если вы действительно хотите использовать поток, вы можете сделать это

 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 это усложняет, в зависимости от того, как ссылки на ваши методы, может быть лучше использовать цикл

Ответ 5

Вы можете создать метод как таковой:

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);
}

и затем назовите это следующим образом:

lazyVersion(() -> method1(),
            () -> method2(),
            () -> method3(),
            () -> method4(),
            () -> method5(),
            () -> method6());

Название метода только для иллюстрации.