【问题标题】:Java 8 list processing - add elements conditionallyJava 8 列表处理 - 有条件地添加元素
【发布时间】: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 添加元素,否则返回等等。

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

【问题讨论】:

  • 这些方法作为对象返回什么,究竟是什么?

标签: java collections java-8 java-stream


【解决方案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 是一种短路运算,因此被广泛使用。
      猜你喜欢
      • 2021-06-30
      • 2022-11-17
      • 2018-08-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多