在 Java 8 中懒惰地返回第一个非空列表

Posted

技术标签:

【中文标题】在 Java 8 中懒惰地返回第一个非空列表【英文标题】:Return first non empty list lazyily in Java 8 【发布时间】:2017-07-13 08:07:33 【问题描述】:

我有 N 个从存储库返回数据的列表。我想返回这三个列表中的第一个非空列表(每个列表执行不同的 SQL 来获取数据)。

问题是我想懒惰地这样做,这样如果我已经找到了可接受的结果,我就不需要在数据库上执行 SQL。我的代码是(修改)

@Override
public List<Something> dataService(Data data) 

return firstNonEmptyList(repository.getDataWayOne(data.getParameter()), 
                         repository.getDataWayTwo(data.getParameter()),
                         repository.getDataWayThree(data.getParameter().getAcessoryParameter())
                         Collections.singletonList(repository.getDefaultData(data.getParameter()));



@SafeVarargs
private final List<Something> firstNonEmptyList(List<Something>... lists) 
for (List<Something> list : lists) 
  if (!list.isEmpty()) 
    return list;
  


return null;


这行得通,但它并不懒惰。有什么想法吗?

【问题讨论】:

如果我错了,请纠正我,但我认为如果将列表调用包装在 lambdas 中会很懒惰。在firstNonEmptyList 中,您将评估每个 lambda,直到找到您想要的那个(即返回非空列表的那个)。未被调用的 lambda 永远不会执行查询。无论如何,shmosel 提供的解决方案符合我的描述并且更加优雅。 【参考方案1】:

您可以创建一个供应商流并按照遇到的顺序对其进行评估,直到找到结果:

return Stream.<Supplier<List<Something>>>of(
            () -> repository.getDataWayOne(data.getParameter()),
            () -> repository.getDataWayTwo(data.getParameter()),
            () -> repository.getDataWayThree(data.getParameter().getAcessoryParameter()),
            () -> Collections.singletonList(repository.getDefaultData(data.getParameter()))
        )
        .map(Supplier::get)
        .filter(l -> !l.isEmpty())
        .findFirst()
        .orElse(null);

每个供应商都定义了如何访问结果集,而不是在执行map() 之前实际尝试它。由于filter()map()stateless 操作,因此将调用每个供应商并在尝试下一个供应商之前验证其结果。如果找到非空结果,则流将立即终止,因为findFirst()short-circuiting

【讨论】:

这是让一些人头晕目眩的 Java 8 答案之一。如果只能一步一步解释,为什么是懒加载? 这是一个很好的答案,最Java8-ic的方式,原谅我的法语! @GrzegorzGórkiewicz 我试过了。希望现在更清楚一点。 @shmosel 可以与...filter(List::isEmpty) 一起使用。为了保持一致性,因为您正在映射 Supplier::get? 这是完美的,就像一个魅力。通过了所有测试,它很懒惰。加上解释后我明白了。【参考方案2】:

如果流不是您的菜,您仍然可以使用 lambdas 来实现您想要的,只需对原始代码稍作修改。

public List<Something> dataService(Data data) 
    return firstNonEmptyList(
            () -> repository.getDataWayOne(data.getParameter()),
            () -> repository.getDataWayTwo(data.getParameter()),
            () -> repository.getDataWayThree(data.getParameter().getAcessoryParameter()),
            () -> Collections.singletonList(repository.getDefaultData(data.getParameter()))
        );


private final List<Something> firstNonEmptyList(Supplier<List<Something>>... listSuppliers) 
    for (Supplier<List<Something>> supplier : listSuppliers) 
        List<Something> list = supplier.get();
        if (!list.isEmpty()) 
            return list;
        
    
    return null;

【讨论】:

巧合,我的最后一条评论正是你在这里所做的。它有效(我猜),但问题是它隐藏了懒惰的意图。在这种情况下,欢迎使用更实用的样式。 我实际上认为恰恰相反,firstNonEmptyList 的短路行为很明显。对于流,只有在您非常熟悉流 API 的情况下才很明显,但并不是每个人都如此。另外,该方法上的 Javadocs 可用于帮助使其更加明显。 这也有效并通过了我的所有测试,包括延迟加载。我很感激。我将使用 shmosel 的,因为它消除了辅助方法。

以上是关于在 Java 8 中懒惰地返回第一个非空列表的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中获取第一个非空值?

如何在 Kotlin 中无限懒惰地循环列表?

返回第一个非空值

Leetcode练习(python):第414题:第三大的数:给定一个非空数组,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。要求算法时间复杂度必须是O(n)。

Leetcode练习(python):第414题:第三大的数:给定一个非空数组,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。要求算法时间复杂度必须是O(n)。

Java 8 函数式编程中“减少”函数的第三个参数的目的