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

Posted

技术标签:

【中文标题】Java 8 列表处理 - 有条件地添加元素【英文标题】:Java 8 list processing - add elements conditionally 【发布时间】:2019-05-26 05:38:06 【问题描述】:

我有以下代码:

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】:

你可以通过创建方法让你的代码更好

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,你可以这样做

 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 它使它变得更加复杂,这取决于您的方法被引用的方式,使用循环可能会更好

【讨论】:

【参考方案2】:

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

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

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

【讨论】:

【参考方案3】:

我会简单地使用供应商流并过滤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() 进行不必要的调用。

编辑: 正如下面的 cmets 所述,如果您的 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);

【讨论】:

值得注意的是,如果 methodX() 返回 List 而不是其他类型的 Collection,则可以直接返回该列表,而不是创建新列表并添加到那:.map(Supplier::get).filter(s -&gt; !s.isEmpty()).findFirst().orElse(emptyList());。这是否合适无法从问题中确定。 @Ricola 如果没有明确定义,编译器不知道 lambda 表达式的目标类型。另一种方法是分两步完成,明确声明 Stream&lt;Supplier&lt;List&lt;Object&gt;&gt;&gt; 作为流的类型。 你也可以使用this::method1来减少样板的数量 @BoristheSpider 同意(只是不想假设代码在实例方法中) @SebastiaanvandenBroek 当然是个人喜好问题——我不同意你的评价。问题中的代码具有相同的逻辑复制/粘贴 5 次。上面的代码没有这样的重复——对我来说,这使得功能逻辑相当优越。【参考方案4】:

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

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

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

【讨论】:

你应该将方法命名为addIfEmpty 而不是addIfNotEmpty【参考方案5】:

您可以尝试检查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 操作将阻止调用其余元素。我喜欢“||”这一事实很好地表达了意图。

【讨论】:

关注addAll 的返回值的罕见情况之一。确实非常聪明、高效且易读! ;-)。 @Aomine 我没有发现滥用布尔表达式来模仿可读和可维护的有序执行。下一个最好的家伙可能会重新排序表达式,因为“这是一个 OR 表达式,顺序不重要”,并且你很难找到错误。关于有一个变量来编译恕我直言的必要评论也暗示了糟糕的设计。因此,虽然这以某种方式回答了这个问题,但我不建议在生产代码中使用它。 @Darkwing 这不是编译器/jvm 特定的行为,语言规范本身定义了惰性评估的行为。我没有假设任何事情,我知道语言是如何定义的。这种行为是众所周知的,例如惰性评估用于首先放置一个空检查,然后取消引用它。至少您对语言规范中定义的假设有一个非常奇怪的定义。 假设这个词暗示了对实际行为的某种不确定性。如果您确定自己在做什么,就不要假设它。 关键是代码气味已经存在,我没有提出来,我提出了一个解决大气味的好方法。如果人们认为这里的顺序无关紧要,这就像删除一个空检查并事后向原作者抱怨 NullPointerExceptions。 @Darkwing 但是... OR 的顺序很重要!逻辑 OR 是一种短路运算,因此被广泛使用。

以上是关于Java 8 列表处理 - 有条件地添加元素的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 动画列表:有条件地添加 ListView 项

如何有条件地将小部件添加到列表中?

可以使用内联语法有条件地将元素添加到数组中吗?

python学习小总结(列表元组字典集合字符串)

有条件地在 knockout.js 中添加元素属性

有条件地从Java 8中的List中删除元素[重复]