乐趣区

手把手带你体验Stream流

前言

只有光头才能变强。

文本已收录至我的 GitHub 仓库,欢迎 Star:https://github.com/ZhongFuCheng3y/3y

上一篇讲解到了 Lambda 表达式的使用《最近学到的 Lambda 表达式基础知识》,还没看的同学可以先去阅读一下哈~

相信也有不少的同学想要知道:Lambda 表达式在工作中哪个场景会用得比较多?跟 Lambda 搭边的,使用 Stream 流 会比较多

一般人第一次看 Stream 流的代码,都会有点看不懂(它的代码看起来好像就不是写 Java 一样.),希望这篇文章能带大家入个门

一、体验 Stream 流

大家在自学时,大多数会学过一个程序:算出从数组元素的和,当时我们是怎么写的?一般来说是这样的:

public static void main(String[] args) {int[] nums = {1, 2, 3};
    int sum = 0;
    for (int i : nums) {sum += i;}
    System.out.println("结果为:" + sum);
}

如果我们使用 Stream 流的话,可以这样:

public static void main(String[] args) {int[] nums = {1, 2, 3};
    int sum2 = IntStream.of(nums).sum();
    System.out.println("结果为:" + sum2);
}

代码量 上可以明显看出,用 Stream 流的方式会少一些。

我理解的 Stream 流编程就是:某些场景会经常用到操作(求和 / 去重 / 过滤 …. 等等),已经封装好 API 给你了,你自己别写了,调我给你提供的 API 就好了

1.1 支持并发

回到我们最原始的代码:

public static void main(String[] args) {int[] nums = {1, 2, 3};
    int sum = 0;
    for (int i : nums) {sum += i;}
    System.out.println("结果为:" + sum);
}

如果我们想要 for 循环的内部支持并发的话,显然不太好去写。但使用 Stream 流的方式,调用一个方法就可以支持并发(parallel):

public static void main(String[] args) {int[] nums = {1, 2, 3};
    int sum2 = IntStream.of(nums).parallel().sum();
    System.out.println("结果为:" + sum2);
}

优点:调 API 肯定是比自己写的代码量要少。

缺点:不太方便调试

为什么要使用 Stream 流在我看来就是以上两个原因:

  • 方便并发
  • 代码量少(直接调用 API)

二、如何使用 Stream 流?

使用 Stream 流分为三步:

  1. 创建 Stream 流
  2. 通过 Stream 流对象执行中间操作
  3. 执行最终操作,得到结果

2.1 创建流

创建流我们最常用的就是 从集合中 创建出流

/**
 * 返回的都是流对象
 * @param args
 */
public static void main(String[] args) {List<String> list = new ArrayList<>();
    // 从集合创建
    Stream<String> stream = list.stream();
    Stream<String> stream1 = list.parallelStream();

    // 从数组创建
    IntStream stream2 = Arrays.stream(new int[]{2, 3, 5});

    // 创建数字流
    IntStream intStream = IntStream.of(1, 2, 3);

    // 使用 random 创建
    IntStream limit = new Random().ints().limit(10);

}

2.2 执行中间操作

怎么理解中间操作?意思是这样的:在上面我们已经能创建出 Stream 了,我们是对 Stream 进行操作,对 Stream 操作返回完返回的还是 Stream,那么我们称这个操作为中间操作。

比如,我们现在有个字符串my name is 007,代码如下:

String str = "my name is 007";

Stream.of(str.split(" ")).filter(s -> s.length() > 2)
    .map(s -> s.length()).forEach(System.out::println);

分解:

1、从字符串数组创建出流对象:

Stream<String> split = Stream.of(str.split(" "));

2、通过流对象的 API 执行中间操作(filter),返回的还是流对象:

Stream<String> filterStream = split.filter(s -> s.length() > 2);

3、通过返回的流对象再执行中间操作(map),返回的还是流对象:

Stream<Integer> integerStream = filterStream.map(s -> s.length());

因为中间操作返回的都是 流对象 ,所以我们可以 链式调用

注意:Stream 上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行(惰性求值)。

比如说,peek()是一个中间操作,返回的是 Stream 流对象,只要它不执行最终的操作,这个 Stream 是不会执行的。

String str = "my name is 007";
Stream.of(str.split(" ")).peek(System.out::println); // 不会有信息打印

2.3 执行最终操作

最终操作返回的不再是 Stream 对象,调用了最终操作的方法,Stream 才会执行。还是以上面的例子为例:

String str = "my name is 007";
Stream.of(str.split(" ")).peek(System.out::println).forEach(System.out::println)

这次我们加入了最终操作,所以这次的 Stream 流会被执行,由于中间操作和最终操作都是执行打印,所以会看到两次打印:

至于中间操作和最终操作怎么区分,我们以 返回值 来看就行了。中间操作返回的是 Stream 实例对象,最终操作返回的不是 Stream 实例对象:

最后

这篇文章主要跟大家一起初步认识一下 Stream 流,至于中间操作、最终操作的 API 讲解我就不写了(网上的教程也很多)

使用 Stream 的原因我认为有两个:

  1. JDK 库提供现有的 API,代码写起来简洁优化
  2. 方便并发。大家可以记住一个结论:在 多核 情况下,可以使用 并行 Stream API 来发挥多核优势。在单核的情况下,我们自己写的for 性能不比 Stream API 差多少

参考资料:

  • Java8 中的流操作 - 基本使用 & 性能测试
  • Java 8 的 Stream 代码,你能看懂吗?

乐于输出 干货 的 Java 技术公众号:Java3y。公众号内 有 200 多篇原创 技术文章、海量视频资源、精美脑图,关注即可获取!

觉得我的文章写得不错,点

退出移动版