助你写出更加有效、简洁、紧凑的代码-Stream

JAVA8中引入的Stream与 I/O中的InputStream和OutputStream是两个不同的概念。这里的Stream其实是函数式编程里Monad的概念。(在函数式编程中,monad是一个表示计算(步骤序列)的结构。一个带有monad结构的类型或该类型的嵌套函数定义了其链式操作的意义。)

Stream中的操作可以分为两大类:
中间操作与结束操作
1. 中间操作:中间操作返回Stream,这样我们就可以在不使用分号的情况下串联多个中间操作
中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。
2. 结束操作:终端操作返回void或者一个非Stream结果值
结束操作又可以分为短路与非短路操作,这个应该很好理解,前者是指遇到某些符合条件的元素就可以得到最终结果;而后者是指必须处理所有元素才能得到最终结果。
image

不同类型的Stream
可以从各种数据源创建Stream,特别是collections,List 和 Set, 支持新方法 stream() 和parallelStream(),以创建顺序或并行Stream。如:

Arrays.asList("a1", "a2", "a3")
    .stream()
    .findFirst()
    .ifPresent(System.out::println);

使用 Stream.of() 从一堆对象引用中创建一个Stream,如:

Stream.of("a1", "a2", "a3")
    .findFirst()
    .ifPresent(System.out::println);

除了常规的对象Stream,Java 8有特殊类型的Stream,用于处理基本数据类型int,long和double。分别是IntStream、LongStream和DoubleStream。
IntStreams可以使用IntStream.range()来代替常规的for循环,如:

IntStream.range(1, 4)
    .forEach(System.out::println);

将普通Stream转为原生类型的Steam:mapToInt()、mapToLong()和mapToDouble

处理顺序
中间操作的一个重要特征是惰性。以下例子中,终端操作是缺失的,在执行此代码片段时,不会向控制台输出任何内容。这是因为中间操作只在出现终端操作时执行:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    }); 

通过终端操作forEach来扩展上面的例子:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    })
    .forEach(s -> System.out.println("forEach: " + s));

控制台输出:

filter:  d2
forEach: d2
filter:  a2
forEach: a2
filter:  b1
forEach: b1
filter:  b3
forEach: b3
filter:  c
forEach: c 

输出顺序:每个元素都沿着链垂直移动。第一个字符串“d2”先filter然后foreach,然后第二个字符串“a2”才被处理。

这种方式可以减少在每个元素上执行的实际操作数,如下例所示:

Stream.of("d2", "a2", "b1", "b3", "c")
                .filter(s -> {
                    System.out.println("filter: " + s);
                    return true;
                })
                .anyMatch(ss->{
                    System.out.println("anyMatch: "+ss);
                    return ss.equals("a2");
                });

控制台输出:

filter: d2
anyMatch: d2
filter: a2
anyMatch: a2

当predicate应用于给定的输入元素时,anyMatch将立即返回true。这对于第二个被传递的“a2”来说是正确的。由于stream链的垂直执行,在这种情况下,filter只会执行两次。因此,filter将尽可能少地被调用。

处理顺序很重要
下一个示例包括两个中间操作 map 和 filter 以及终端操作forEach。我们再一次查看这些操作是如何执行的:

Stream.of("d2", "a2", "b1", "b3", "c")
                .map(s -> {
                    System.out.println("map: " + s);
                    return s.toUpperCase();
                })
                .filter(s -> {
                    System.out.println("filter: " + s);
                    return s.startsWith("A");
                })
                .forEach(s -> System.out.println("forEach: " + s));

控制台输出:

map: d2
filter: D2
map: a2
filter: A2
forEach: A2
map: b1
filter: B1
map: b3
filter: B3
map: c
filter: C

如输出结果所示,底层集合中的每个字符串都被调用了5次map和filter,而forEach只调用一次。
改变操作的顺序,将filter移到链的开头,可以大大减少实际执行次数:

Stream.of("d2", "a2", "b1", "b3", "c")
                .filter(s -> {
                    System.out.println("filter: " + s);
                    return s.startsWith("a");
                })
                .map(s -> {
                    System.out.println("map: " + s);
                    return s.toUpperCase();
                })
                .forEach(s -> System.out.println("forEach: " + s));

控制台输出:

filter: d2
filter: a2
map: a2
forEach: A2
filter: b1
filter: b3
filter: c

如输出结果所示,map只被调用一次,因此操作管道在大量元素输入时执行得更快。

Stream复用

Stream在你调用任何终端操作后,就会关闭:

Stream<String> stringStream = Stream.of("d2", "a2", "b1", "b3", "c")
                .filter(s -> {
                    System.out.println("filter: " + s);
                    return true;
                });
        stringStream.anyMatch(ss->{
            System.out.println("anyMatch: "+ss);
            return ss.equals("a2");
        });
        stringStream.forEach(ss->{
            System.out.println("forEach: "+ss);
        });

在同一条Stream上的调用anyMatch之后调用noneMatch导致以下异常:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at com.java8.stream.StreamTest.t8(StreamTest.java:113)
    at com.java8.stream.StreamTest.main(StreamTest.java:26)

可通过为要执行的每一个终端操作创建一个新的Stream链的方式解决上述问题。

Supplier<Stream<String>> streamSupplier = ()->Stream.of("d2", "a2", "b1", "b3", "c")
                .filter(s -> {
                    System.out.println("filter: " + s);
                    return true;
                });
        streamSupplier.get().anyMatch(ss->{
            System.out.println("anyMatch: "+ss);
            return ss.equals("a2");
        });
        streamSupplier.get().forEach(ss->{
            System.out.println("forEach: "+ss);
        });
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值