【问题标题】:Using Java Optional within stream mapping在流映射中使用 Java Optional
【发布时间】:2021-07-21 07:38:13
【问题描述】:

我有这样的代码:

public void processList(List<String> list) {

    for (String item : list) {
        Object obj = getObjectForString(item);
        if (obj != null) {
            doSomethingWithObject(obj);
        } else {
            System.err.println("Object was null for " + item);
        }
    }
}

理想情况下,我想简化此操作并避免使用list.stream().map( *blah, blah, blah* ) 进行null 检查,如果对象不是null,则避免使用doSomethingWithObject,但否则记录错误(通过使用orElse 方法可选的)。我对 Java 8 的这个功能不是很精通,也不确定是否有一种很好的、​​巧妙的方式来做我想做的事情。有什么建议吗?

编辑以添加失败的尝试:

list.stream()
    .map(p -> getObjectForString(p))
    .map(Optional::ofNullable)
    .forEach(
        p -> p.ifPresentOrElse(
            r -> doSomethingWithObject(r),
            () -> System.err.println("Object was null")
        ));

即使该代码按照我想要的方式运行,它仍然不会像我希望的那样将原始列表中的字符串附加到错误消息中。但也许这太复杂了,无法用这样的流来完成。

【问题讨论】:

  • 你有没有尝试过?成为一名优秀的软件开发人员的很大一部分是对事物保持好奇心和主动性,以了解它们是如何工作的。我建议你阅读相关的Javadoc(你似乎对流有基本的掌握,这很好),尝试一些实验,当你发现一些你不理解的行为时,然后提出一个具体的问题。
  • 这是一次失败的尝试:` list.stream().map(p -> getObjectForString(p)).map(Optional::ofNullable).forEach( p -> p.ifPresentOrElse( r -> doSomethingWithObject(r), () -> System.err.println("Object was null") ));` 看起来 doSomethingWithObject 为列表中的每个项目调用了两次(包括当对象为空时)。我确实得到了一个空对象的错误消息输出,这很好。但是我也没有按照我的意愿将字符串“项目”附加到我的错误消息中(这可能是最棘手的部分)
  • @Adam,您可以将失败的尝试和相关信息粘贴为问题本身的一部分。
  • 映射到Optional 是没有意义的。只需使用.filter(item -&gt; getObjectForString(item) == null)
  • 传递给filter 的谓词告诉要保留哪些元素。 .filter( /* tell which elements should pass */) .forEach( /* tell what to do with the elements that passed the filter */)的逻辑应该很容易理解。但无论如何,看看你的尝试,问问自己,不管它不是那样工作的,这是否比你原来的循环更简单?

标签: java java-stream optional null-check


【解决方案1】:

即使下面的方法不能避免 null 如您在问题中所希望的那样检查,这只是实现相同结果的另一种方法。 (唯一的好处是它节省了 1-2 行代码!)。

下面的代码使用Runnable(不带参数,也不返回任何内容)以及Java 8 的Function

注意:我仍然建议使用普通的for 循环 :-),因为我相信下面的内容可能看起来很花哨,但 for 循环在这个特殊的情况下更容易理解案例。

Function<String, Runnable> func = item -> {
    Object obj = getObjectForString(item);
    return (obj != null) ? ( () -> doSomethingWithObject(obj))
                         : ( () -> System.err.println("Object was null for " + item));        
};

list.stream().map(func).forEach(Runnable::run);

【讨论】:

    【解决方案2】:

    另一种方法是根据项目是否具有关联的对象,将项目收集到单独的 2 个存储桶/分区中。之后,根据需要处理 2 个桶:

    final Boolean HAS_OBJECT = Boolean.FALSE;
    
    Map<Boolean, List<String>> partitionedMap = list.stream()
            .collect(Collectors.partitioningBy(item -> !Objects.isNull(getObjectForString(item))));
    
    partitionedMap.get(HAS_OBJECT).stream()
        .map(item -> getObjectForString(item))
        .forEach(obj -> doSomethingWithObject(obj));
    
    partitionedMap.get(!HAS_OBJECT)
        .forEach(item -> System.err.println("Object was null for " + item));
    

    【讨论】:

    • 我认为doSomethingWithObject 是在从getObjectForString(item) 返回的对象上完成的
    • @Gautham M:谢谢-我错过了;代码已更正。
    【解决方案3】:

    即使在转换之后,我们也应该传播item。巧妙的方法是使用元组或对。

    我使用 vavr 函数库中的 Tuple 来做同样的事情。以下是供您参考的代码

    list.stream()
                    .map(p -> Tuple.of(p, getObjectForString(p)).map2(Optional::ofNullable))
                    .forEach(p -> p._2.ifPresentOrElse(
                                r -> doSomethingWithObject(r),
                                () -> System.err.println("Object was null" + p._1))
                    );
    

    【讨论】:

    • 谢谢。我在这件事上偏离了方向,但现在又回到了它。我可以使用 Pair 来做我想做的事。再次感谢。
    猜你喜欢
    • 2022-11-01
    • 1970-01-01
    • 2023-03-19
    • 2015-09-12
    • 2021-12-02
    • 1970-01-01
    • 1970-01-01
    • 2014-02-25
    相关资源
    最近更新 更多