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 -> !s.isEmpty()).findFirst().orElse(emptyList());
。这是否合适无法从问题中确定。
@Ricola 如果没有明确定义,编译器不知道 lambda 表达式的目标类型。另一种方法是分两步完成,明确声明 Stream<Supplier<List<Object>>>
作为流的类型。
你也可以使用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 列表处理 - 有条件地添加元素的主要内容,如果未能解决你的问题,请参考以下文章