Java 8列表处理 - 有条件地添加元素

ion*_*nut 51 java collections java-8 java-stream

我有以下代码:

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;
Run Code Online (Sandbox Code Playgroud)

是否有一种很好的方式有条件地添加元素,可能使用流操作?我想只在列表为空时添加来自method2的元素,否则返回等等.

编辑:值得一提的是,这些方法包含大量逻辑,因此需要防止执行.

Dor*_*ray 67

您可以尝试检查返回值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;
Run Code Online (Sandbox Code Playgroud)

由于延迟评估,addAll添加至少一个元素的第一个操作将阻止其余部分被调用.我喜欢"||"的事实 很好地表达了意图.

  • @Darkwing但是...... OR的顺序很重要!逻辑OR是短路操作,因此被广泛使用. (10认同)
  • 一种罕见的情况,其中一个人关心`addAll`的返回值.确实非常聪明,高效和可读!;-). (5认同)
  • @Aomine我没有发现滥用布尔表达式来模仿可读和可维护的有序执行.下一个最好的家伙可能会重新排序表达式,因为"它是一个OR表达式,顺序应该无关紧要"并且繁荣你有一个很难找到的bug.关于有一个变量只是为了编译imho的必要评论也是一个糟糕的设计暗示.因此,虽然这会以某种方式回答问题,但我不建议在生产代码中使用它. (5认同)
  • @Darkwing它不是编译器/ jvm的特定行为,语言规范本身定义了懒惰评估的行为.我不是**假设**什么,我**知道**语言是如何定义的.这种行为是众所周知的,例如,为了首先进行空检查而使用惰性评估,之后将其解除引用.至少你有一个非常奇怪的定义**假设**在语言规范中定义的东西.**假设**意味着对实际行为的某种不确定性.如果你确切知道自己在做什么,就不要假设它. (5认同)
  • 这是我唯一能解决的问题,这是一个非常罕见的情况,其中`addAll`结果实际上很重要.做得很好,先生! (4认同)
  • @DorianGray希望这些方法在代码中不会被命名为method1 ... method6!这并没有说明他们做了什么.并且务实地说顺序存在差异,但从逻辑上讲它并不重要.这就是为什么在假设执行顺序时你应该非常小心,因为你知道你的编译器/ jvm将如何翻译/执行表达式.如果您要优化到最后一位,请确保以最有意义的方式对其进行排序,但没有业务逻辑依赖它,除非有非常大的好处/需求. (3认同)
  • @DorianGray和缺乏评论不是我的问题,有必要的评论是我的问题. (3认同)
  • 关键是代码气味已经存在,我没有提起,我提出了一个很好的方法来解决一个大气味.如果人们认为这里的顺序无关紧要,那就像删除空检查并在之后向原作者抱怨NullPointerExceptions. (3认同)
  • 同意.我相信它不能比这更简单或更易读. (2认同)
  • @DorianGray我知道执行顺序,但我关心OR语句的逻辑概念.这就是你看到它时首先看到的东西.您可能知道订单,但即使是原则上知道的人可能会假设OR语句的内容可以重新排列为OR语句的内容,如果其中一个子表达式为真,则评估为true,其余的是实施细节.正是我的观点,一些代码需要注释才能使读者理解其行为及其语义.那是代码味道. (2认同)

ern*_*t_k 45

我只想使用一系列供应商并过滤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;
Run Code Online (Sandbox Code Playgroud)

findFirst()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);
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,如果`methodX()`返回一个`List`而不是另一种类型的`Collection`,则可能直接返回该列表,而不是创建一个新列表并添加到:`.map( Supplier :: get).filter(s - >!s.isEmpty()).findFirst().orElse(emptyList());`.这个问题是否合适无法确定. (5认同)
  • 您也可以使用`this :: method1`来减少样板量 (5认同)
  • @SebastiaanvandenBroek肯定是一个品味问题 - 我不同意你的评估.问题中的代码具有相同的逻辑复制/粘贴5次.上面的代码没有这样的重复 - 对我而言,这使得功能逻辑相当优越. (3认同)

JB *_*zet 17

一种不重复自己的方法就是提取一个为你做的方法:

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

然后

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;
Run Code Online (Sandbox Code Playgroud)

甚至使用for循环:

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

现在DRY不是最重要的方面.如果您认为原始代码更易于阅读和理解,那么请保持原样.


Ric*_*ola 12

您可以通过创建方法使您的代码更好

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

然后你可以像这样使用它(我假设你的方法不是静态方法,如果你需要使用它们引用它们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;
Run Code Online (Sandbox Code Playgroud)

如果你真的想使用Stream,你可以这样做

 Stream.<Supplier<List<Object>>>of(this::method1, this::method2, this::method3, this::method4, this::method5, this::method6)
                .collect(ArrayList::new, this::addAllIfEmpty, ArrayList::addAll);
Run Code Online (Sandbox Code Playgroud)

IMO使它变得更复杂,取决于你的方法被引用的方式,使用循环可能更好


Ous*_* D. 6

你可以这样创建一个方法:

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);
}
Run Code Online (Sandbox Code Playgroud)

然后按如下方式调用它:

lazyVersion(() -> method1(),
            () -> method2(),
            () -> method3(),
            () -> method4(),
            () -> method5(),
            () -> method6());
Run Code Online (Sandbox Code Playgroud)

方法名称仅用于说明目的.