Java8新特性之Stream流(基础篇)

时间:2022-07-22
本文章向大家介绍Java8新特性之Stream流(基础篇),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

什么是 Stream?

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  1. 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  2. 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  3. 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  1. Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  2. 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

Stream的特点

  • 无存储。Stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
  • 为函数式编程而生。对Stream的任何修改都不会修改背后的数据源,比如对Stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新Stream。
  • 惰式执行。Stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
  • 可消费性。Stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。

对于流的处理,主要有三种关键性操作:分别是流的创建、中间操作(intermediate operation)以及最终操作(terminal operation)。

通过已有的集合来创建流

在Java 8中,除了增加了很多Stream相关的类以外,还对集合类自身做了增强,在其中增加了stream方法,可以将一个集合类转换成流。

/**
 * 通过集合生成,应用中最常用的一种
 */
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream1 = integerList.stream();
		
/**
 * 通过数组生成
 * 通过Arrays.stream方法生成流,并且该方法生成的流是数值流【即IntStream】而不是Stream<Integer>。补充一点使用数值流可以避免计算过程中拆箱装箱,提高性能。
 */
int[] intArr = new int[]{1, 2, 3, 4, 5};
IntStream stream2 = Arrays.stream(intArr);

以上,通过一个已有的List创建一个流。除此以外,还有一个parallelStream方法,可以为集合创建一个并行流。(多线程方式,需要考虑线程安全问题)

这种通过集合创建出一个Stream的方式也是比较常用的一种方式。

通过Stream创建流

可以使用Stream类提供的of方法,直接返回一个由指定元素组成的流。

/**
 * 通过值生成
 * 通过Stream的of方法生成流,通过Stream的empty方法可以生成一个空流
 */
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

Stream中间操作(流水线的中间操作)

Stream有很多中间操作,多个中间操作可以连接起来形成一个流水线,每一个中间操作就像流水线上的一个工人,每人工人都可以对流进行加工,加工后得到的结果还是一个流。

filter筛选

通过使用filter方法进行条件筛选,filter的方法参数为一个条件

/**
 * filter筛选
 * 通过使用filter方法进行条件筛选,filter的方法参数为一个条件
 */
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().filter(i -> i > 3);

//stream.forEach(System.out::println);

limit返回指定流个数

通过limit方法指定返回流的个数,limit的参数值必须>=0,否则将会抛出异常

/**
 * limit返回指定流个数
 * 通过limit方法指定返回流的个数,limit的参数值必须>=0,否则将会抛出异常
 */
List<Integer> integerList1 = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream1 = integerList1.stream().limit(3);

//stream1.forEach(System.out::println);

skip跳过流中的元素

通过skip方法跳过流中的元素,上述例子跳过前两个元素,所以打印结果为2,3,4,5,skip的参数值必须>=0,否则将会抛出异常

/**
 * skip跳过流中的元素
 * 通过skip方法跳过流中的元素,上述例子跳过前两个元素,所以打印结果为2,3,4,5,skip的参数值必须>=0,否则将会抛出异常
 */
List<Integer> integerList2 = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream2 = integerList2.stream().skip(2);

//stream2.forEach(System.out::println);

sorted对流进行排序

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法进行排序

/**
* sorted对流进行排序
* sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法进行排序
*/
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().sorted().forEach(System.out::println);

distinct去重

distinct主要用来去重,以下代码片段使用 distinct 对元素进行去重

/**
* distinct去重
* distinct主要用来去重,以下代码片段使用 distinct 对元素进行去重
*/
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().distinct().forEach(System.out::println);

map流映射

所谓流映射就是将接受的元素映射成另外一个元素

/**
 * map流映射(所谓流映射就是将接受的元素映射成另外一个元素)
 */
List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");
Stream<Integer> stream3 = stringList.stream().map(String::length);

//stream3.forEach(System.out::println);

flatMap流转换

将一个流中的每个值都转换为另一个流

/**
 * flatMap流转换(将一个流中的每个值都转换为另一个流)
 */
List<String> wordList = Arrays.asList("Hello", "World");

List<String> strList = wordList.stream()
        .map(w -> w.split(" "))
        .flatMap(Arrays::stream)
        .distinct()
        .collect(Collectors.toList());

//strList.forEach(System.out::println);

元素匹配

  1. allMatch匹配所有
  2. anyMatch匹配其中一个
  3. noneMatch全部不匹配
    /**
     * 元素匹配 - allMatch匹配所有
     */
    List<Integer> integerList3 = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList3.stream().allMatch(i -> i > 3)) {
        System.out.println("值大于3");
    }


    /**
     * 元素匹配 - anyMatch匹配其中一个
     */
    List<Integer> integerList4 = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList4.stream().anyMatch(i -> i > 3)) {
        System.out.println("存在大于3的值");
    }


    /**
     * 元素匹配 - noneMatch全部不匹配
     */
    List<Integer> integerList5 = Arrays.asList(1, 2, 3, 4, 5);
    if (integerList5.stream().noneMatch(i -> i > 3)) {
        System.out.println("值都小于3");
    }
}

Stream最终操作

Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作(terminal operation)

最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能在使用任何中间操作,否则将抛出异常:

java.lang.IllegalStateException: stream has already been operated upon or closed

count统计

count用来统计流中的元素个数。

/**
 * 统计流中元素个数
 * 通过使用count方法统计出流中元素个数
 */
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Long result = integerList.stream().count();

//System.out.println("result = " + result);

findFirst查找第一个

通过findFirst方法查找到第一个大于三的元素并打印

/**
 * 查找 - findFirst查找第一个
 * 通过findFirst方法查找到第一个大于三的元素并打印
 */
List<Integer> integerList2 = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result2 = integerList2.stream().filter(i -> i > 3).findFirst();

//System.out.println("result2 = " + result2);

findAny随机查找一个

通过findAny方法查找到其中一个大于三的元素并打印,因为内部进行优化的原因,当找到第一个满足大于三的元素时就结束,该方法结果和findFirst方法结果一样。提供findAny方法是为了更好的利用并行流,findFirst方法在并行上限制更多

/**
 * 查找 - findAny随机查找一个
 * 通过findAny方法查找到其中一个大于三的元素并打印,因为内部进行优化的原因,当找到第一个满足大于三的元素时就结束,该方法结果和findFirst方法结果一样。提供findAny方法是为了更好的利用并行流,findFirst方法在并行上限制更多
 */
List<Integer> integerList3 = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result3 = integerList3.stream().filter(i -> i > 3).findAny();

//System.out.println("result3 = " + result3);

min/max获取最小最大值

获取流中最小最大值

/**
 * 获取流中最小最大值
 * 通过min/max获取最小最大值
 */
//Optional<Integer> min = menu.stream().map(Dish::getCalories).min(Integer::compareTo);
//Optional<Integer> max = menu.stream().map(Dish::getCalories).max(Integer::compareTo);

// 也可以写成:
//OptionalInt min = menu.stream().mapToInt(Dish::getCalories).min();
//OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max();

forEach遍历

Stream 提供了方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数

/**
 * forEach 输出了10个随机数
 */
 Random random = new Random();
 random.ints().limit(10).forEach(System.out::println);

collect汇总

collect就是一个归约操作,可以接受各种做法作为参数,将流中的元素累积成一个汇总结果

/**
 * collect
 * collect就是一个归约操作,可以接受各种做法作为参数,将流中的元素累积成一个汇总结果
 */
List<String> strings = Arrays.asList("AAAAA", "BBBB", "CCC", "DD", "E");
List<Integer> collect = strings.stream()
        .filter(str -> str.length() <= 3)
        .map(String::length)
        .sorted()
        .limit(2)
        .distinct()
        .collect(Collectors.toList());

collect.forEach(System.out::println);

最终得到一个List 数组,也就是流最终的归宿。

参考

?Java8新特性之Stream流(基础篇) ?Java8新特性之Stream流(高级篇)