【问题标题】:Why can't I use filter as my last step in a stream为什么我不能使用过滤器作为我在流中的最后一步
【发布时间】:2020-01-22 04:16:22
【问题描述】:

我一直被告知不通过诸如 collect 和 findFirst 之类的方法终止 Stream 是不好的做法,但对于为什么在博客中没有太多谈论它没有真正的反馈。

看下面的例子,我没有使用大量的嵌套 if 检查,而是使用 Optional 来取回一个 List 值。如您所见,我的最后一步是在该 Stream 中进行过滤。这对我来说按预期工作,即取回一个列表。为什么这是错误的,我应该怎么写呢?

import lombok.Getter;
import lombok.Setter;
import java.util.*;

public class Main {
    public static void main(String[] args) {

        RequestBean requestBean = new RequestBean();
        // if I uncomment this I will get the list values printed as expected
//        FruitBean fruitBean = new FruitBean();
//        AnotherBean anotherBean = new AnotherBean();
//        InnerBean innerBean = new InnerBean();
//        requestBean.setFruitBeans(Collections.singletonList(fruitBean));
//        fruitBean.setAnotherBeans(Collections.singletonList(anotherBean));
//        anotherBean.setInnerBeans(Collections.singletonList(innerBean));
//        List<String> beans = Arrays.asList("apple", "orange");
//        innerBean.setBeans(beans);

        List<String> result = getBeanViaOptional(requestBean);

        if(result != null){
            for(String s : result){
                System.out.println(s);
            }
        }else {
            System.out.println("nothing in list");
        }

    }

    private static List<String> getBeanViaOptional(RequestBean bean){
        Optional<List<String>> output = Optional.ofNullable(bean)
                .map(RequestBean::getFruitBeans)
                .map(n -> n.get(0))
                .map(FruitBean::getAnotherBeans)
                .map(n -> n.get(0))
                .map(AnotherBean::getInnerBeans)
                .map(n -> n.get(0))
                .map(InnerBean::getBeans)
                // why is this bad practice to end with a filter. how should I write this then?
                .filter(n -> n.contains("apple"));

        if(!output.isPresent()){
            throw new CustomException();
        }

        return output.get();
    }

    // not using this. just to show that optional was preferable compared to this.
    private static List<String> getBeanViaIfChecks(RequestBean bean){
        if(bean != null){
            if(bean.getFruitBeans() != null){
                if(bean.getFruitBeans().get(0) != null){
                    if(bean.getFruitBeans().get(0).getAnotherBeans() != null){
                        if(bean.getFruitBeans().get(0).getAnotherBeans().get(0) != null){
                            if(bean.getFruitBeans().get(0).getAnotherBeans().get(0).getInnerBeans() != null){
                                if(bean.getFruitBeans().get(0).getAnotherBeans().get(0).getInnerBeans().get(0) != null){
                                    return bean.getFruitBeans().get(0).getAnotherBeans().get(0).getInnerBeans().get(0).getBeans();
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }
}

@Getter
@Setter
class RequestBean{
    List<FruitBean> fruitBeans;
}

@Getter
@Setter
class FruitBean{
    List<AnotherBean> anotherBeans;
}

@Getter
@Setter
class AnotherBean{
    List<InnerBean> innerBeans;
}

@Getter
@Setter
class InnerBean{
    List<String> beans;
}

class CustomException extends RuntimeException{
    // do some custom exception stuff
}

【问题讨论】:

  • 但是这个filter方法来自Optional而不是Stream
  • 我不知道为什么/何时会出现get(0) 的此类用例,虽然我不确定是否有更清洁的方法来避免它们。在阅读功能性方法时,它们确实让我的眼睛发痒。
  • 这两种方法之间需要注意的另一点是,传统的for 循环方法不等同于函数方法,因为它不会抛出CustomException
  • @Naman 响应来自公司结构中另一个团队的微服务,它提供了这种响应格式,其中列表中只能有一个元素,因此使用 .get(0)。 (至于为什么他们决定使用列表,我不知道)。

标签: java java-8


【解决方案1】:

对于流,当没有终端操作时,通常不会执行任何中间操作。您的示例使用Optional。它的操作mapfilter 与流中的一些中间操作同名,但又有所不同。你的例子在你的问题提出的那一行是好的(不是坏的做法)。

另一件事是(正如 Aomine 已经指出的那样).orElseThrow 是获取Optional 中的值并在没有异常时抛出异常的更短方法。更重要的是使用.orElseThrow更安全 (或.orElse,如果有默认值)。 Optional.get() 应尽可能避免。如果没有价值,您将获得NoSuchElementException。这几乎与不使用Optional 时获得NullPointerException 一样糟糕。 Optional使用得当可以保护你免受NullPointerException的伤害。

【讨论】:

  • "....Optional.get() 应尽可能避免" true 但是如果您在调用 get 之前检查可选值中的存在,就像 OP 所做的那样,那么它是完全安全的;但是正如您所暗示的那样...几乎总是有更好的方法可以在不使用 get 的情况下从可选项中获取值。
  • 是的,如果您在使用.get() 之前检查是否存在是安全的,但如果您经常使用.get(),则很容易忘记并且很难检查大型代码库。获取值的其他方法在语法上确保在不存在的情况下进行一些处理。这也是使用null 的优势。如果您之前检查过,访问可能是null 的变量是安全的。但它很容易忘记,但很难检查它是否在某些地方被遗忘。
【解决方案2】:

我一直被告知不通过以下方式终止 Stream 是不好的做法 collect 和 findFirst 等方法,但没有关于原因的真实反馈 博客中没有太多提及。

这真的取决于上下文,如果你说“我可以用一个中间操作结束一个流,例如filter而不调用终端操作(一个消耗流的操作) ever" 那么是的,这是一种不好的做法,而且有点毫无意义,因为您刚刚定义了一些标准,但从未要求过“结果”。

从某种意义上说,流是惰性的,除非终端操作告诉它们,否则它们不会做任何事情,例如collectfindFirst

如果您说“从方法返回流是不好的做法”,那么 关于是否有一个should return a stream or a collection,可能值得阅读这个答案。

此外,请注意您的getBeanViaOptional 逻辑在Optional&lt;T&gt; 而不是Stream&lt;T&gt; 上运行。是的,它们都有mapflatMapfilter,但请注意Optional&lt;T&gt; 只能包含一个值 或者它是,而流可以有一个或多个。

您使用 Optional 而不是命令式 ifs 的方法在可读性、维护等方面显然更好,所以我建议您继续使用这种方法,尽管您可以通过使用来稍微改进它orElseThrow即:

return Optional.ofNullable(bean)
               .map(RequestBean::getFruitBeans)
               .map(n -> n.get(0))
               .map(FruitBean::getAnotherBeans)
               .map(n -> n.get(0))
               .map(AnotherBean::getInnerBeans)
               .map(n -> n.get(0))
               .map(InnerBean::getBeans)
               .filter(n -> n.contains("apple"))
               .orElseThrow(CustomException::new);

【讨论】:

    猜你喜欢
    • 2012-03-21
    • 2018-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多