【JDK8 新特性 5】Stream流介绍和常用方法的使用

2022-11-07 14:34:21 浏览数 (1)

上一篇文章:(2条消息) 【JDK8 新特性4】方法引用_一切总会归于平淡的博客-CSDN博客

目录

1、 Stream 流介绍

2、获取Stream流的两种方式

方式1 : 根据Collection获取流

方式2 : Stream中的静态方法of获取流

3、Stream常用方法和注意事项

3.1 Stream常用方法

3.2 Stream注意事项(重要)

4、Stream流的forEach(遍历)方法

5、Stream流的count(计数)方法

6、Stream流的fifilter(过滤)方法

7、Stream流的limit(截取)方法

8、Stream流的skip(截取)方法

9、Stream流的map(映射)方法

10、Stream流的sorted(排序)方法

11、Stream流的distinct(去重)方法

12、Stream流的match(匹配)方法

13、Stream流的fifind(找第一)方法

14、Stream流的max和min(大和小)方法

15、Stream流的map和reduce组合使用

16、Stream流的mapToInt(转换Int)

17、Stream流的concat(合并)方法


1、 Stream 流介绍

当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。我们来体验集合操作数据的弊端。

一个ArrayList集合中存储有以下数据:张三,李四,王五,张小明

需求:

1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据

代码如下:

代码语言:javascript复制
public class Test {

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张三", "李四", "王五", "张小明", "张二狗");
        // 1.拿到所有姓张的
        ArrayList<String> zhangList = new ArrayList<>();

        for (String name : list) {
            if (name.startsWith("张")) {
                zhangList.add(name);
            }
        }
        // 2.拿到名字长度为3个字的
        ArrayList<String> threeList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
                threeList.add(name);
            }
        }
        // 3.打印这些数据
        for (String name : threeList) {
            System.out.println(name);
        }
    }
}

循环遍历的弊端

这段代码中含有三个循环,每一个作用不同:

  1. 首先筛选所有姓张的人;
  2. 然后筛选名字有三个字的人;
  3. 最后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。

这是理所当然的么?不是。

循环是做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使用另一个循环从头开始。

那Stream能给我们带来怎样更加优雅的写法呢?

Stream的更优写法

下面来看一下借助Java 8的Stream API,修改后的代码:

代码语言:javascript复制
public class Test {

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张三", "李四", "王五", "张小明", "张三丰");

        list.stream().filter(s -> s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(System.out::println);
    }
}

直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印

我们真正要做的事情内容被更好地体现在代码中。

Stream流式思想概述

注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!

Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。

Stream可以看作是流水线上的一个工序。

在流水线上,通过多个工序让一个原材料加工成一个商品。

Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约。

2、获取Stream流的两种方式

java.util.stream.Stream<T> 是JDK 8新加入的流接口。

获取一个流非常简单,有以下几种常用的方式:

  • 所有的 Collection 集合都可以通过 stream 默认方法获取流;
  • Stream 接口的静态方法 of 可以获取数组对应的流。

方式1 : 根据Collection获取流

首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。

代码语言:javascript复制
 public static void main(String[] args) {
        // List
        List<String> list = new ArrayList<>();
        // 获取流
        Stream<String> stream = list.stream();

        // Map
        Map<String,Object> map = new HashMap<>();
        // 获取流
        Stream<String> stream1 = map.keySet().stream();
        Stream<Object> stream2 = map.values().stream();
        Stream<Map.Entry<String, Object>> stream3 = map.entrySet().stream();
    }
}

差不多就是这个样亚子吧。

注:

java.util.Map 接口不是 Collection 的子接口,所以获取对应的流需要分key、value或entry等情况:

方式2 : Stream中的静态方法of获取流

由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:

代码语言:javascript复制
public class Test {

    public static void main(String[] args) {

        String[] arr = {"aa", "bb", "cc"};
        Stream<String> stream = Stream.of(arr);

        Integer[] arr2 = {11, 22, 33};
        Stream<Integer> stream2 = Stream.of(arr2);
        
    }
}

注意:基本数据类型的数组不行

注: of 方法的参数其实是一个可变参数,所以支持数组。

3、Stream常用方法和注意事项

3.1 Stream常用方法

Stream流模型的操作很丰富,这里介绍一些常用的API。

这些方法可以被分成两种:

方法名

方法作用

返回值类型

方法种类

count

统计个数

long

终结

forEach

逐一处理

void

终结

fifilter

过滤

Stream

函数拼接

limit

取用前几个

Stream

函数拼接

skip

跳过前几个

Stream

函数拼接

map

映射

Stream

函数拼接

concat

组合

Stream

函数拼接

  • 终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。终结方法包括 count 和forEach 方法。
  • 非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法。)

更多方法,请自行参考API文档。

3.2 Stream注意事项(重要)

1. Stream只能操作一次 。

2. Stream方法返回的是新的流 。

3. Stream不调用终结方法,中间的操作不会执行。

4、Stream流的forEach(遍历)方法

forEach 用来遍历流中的数据

该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。例如:

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        one.stream().forEach(System.out::println);
    }
}

5、Stream流的count(计数)方法

Stream流提供 count 方法来统计其中的元素个数:

该方法返回一个long值代表元素个数。基本使用:

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        System.out.println(one.stream().count());
    }
}

6、Stream流的fifilter(过滤)方法

fifilter用于过滤数据,返回符合过滤条件的数据

可以通过 filter 方法将一个流转换成另一个子集流。方法声明:

该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

Stream流中的 filter 方法基本使用的代码如:

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        one.stream().filter(s -> s.length() < 3).forEach(System.out::println);
    }
}

在这里通过Lambda表达式来指定了筛选的条件:姓名长度为少于3个字。

7、Stream流的limit(截取)方法

limit 方法可以对流进行截取,只取用前n个。方法签名:

参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。基本使用:

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        one.stream().limit(4).forEach(System.out::println);
    }
}

8、Stream流的skip(截取)方法

如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        one.stream().skip(2).forEach(System.out::println);
    }
}

9、Stream流的map(映射)方法

如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:

该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

Stream流中的 map 方法基本使用的代码如:

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("11", "22", "33");
        Stream<Integer> result = original.map(Integer::parseInt);
        result.forEach(s -> System.out.println(s   44));
    }
}

这段代码中, map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为 Integer 类对象)。

10、Stream流的sorted(排序)方法

如果需要将数据排序,可以使用 sorted 方法。方法签名:

基本使用

Stream流中的 distinct 方法基本使用的代码如:

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("11", "22", "33");
        original.map(Integer::parseInt)
                //  根据元素的自然顺序排序
                .sorted()
                // 根据比较器指定的规则排序
                .sorted((o1, o2) -> o2 - o1)
                .forEach(System.out::println);
    }
}

这段代码中,map 方法的参数通过方法引用,将字符串类型转换成为了int类型, sorted 方法根据元素的自然顺序排序,也可以指定比较器排序。

11、Stream流的distinct(去重)方法

如果需要去除重复数据,可以使用 distinct 方法。方法签名:

基本使用

Stream流中的 distinct 方法基本使用的代码如:

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("11", "22", "33","11","22","33");
        original.distinct().forEach(System.out::println);
    }
}

如果是自定义类型如何是否也能去除重复的数据呢?我们来试试看

自定义类型是根据对象的hashCode和equals来去除重复元素的。

12、Stream流的match(匹配)方法

如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法。方法签名:

基本使用

Stream流中的 Match 相关方法基本使用的代码如:

基本使用

Stream流中的 Match 相关方法基本使用的代码如:

allMatch: 元素是否全部满足条件

anyMatch: 元素是否任意有一个满足条件

noneMatch: 元素是否全部不满足条件

13、Stream流的fifind(找第一)方法

如果需要找到某些数据,可以使用 find 相关方法,它们俩都是找第一个元素。方法签名:、

基本使用

Stream流中的 find 相关方法基本使用的代码如

14、Stream流的max和min(大和小)方法

如果需要获取最大和最小值,可以使用 max 和 min 方法。方法签名:

基本使用

Stream流中的 max 和 min 相关方法基本使用的代码如:

15、Stream流的reduce(归纳)方法

如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法。方法签名:

基本使用

Stream流中的 reduce 相关方法基本使用的代码如:

15、Stream流的map和reduce组合使用

代码语言:javascript复制
public class Test {
    public static void main(String[] args) {
        // 求出所有年龄的总和
        int totalAge = Stream.of(new Person("小明", 58, 178),
                new Person("小黄", 56, 177),
                new Person("小红", 54, 168)
        ).map((p) -> p.getAge()).reduce(0, Integer::sum);
        // 打印
        System.out.println("所有年龄的总和:"   totalAge);

        // 找出最大年龄
        int AgeMax = Stream.of(new Person("小明", 58, 178),
                new Person("小黄", 56, 177),
                new Person("小红", 54, 168)
        ).map((p) -> p.getAge()).reduce(0, (x, y) -> x > y ? x : y);
        //打印
        System.out.println("最大年龄:"   AgeMax);

        // 统计 数字2 出现的次数
        int count = Stream.of(1, 2, 2, 1, 3, 2).map(i -> {
            if (i == 2) {
                return 1;
            } else {
                return 0;
            }
        }).reduce(0, Integer::sum);
        System.out.println("数字2 出现的次数:"   count);
    }
}

16、Stream流的mapToInt(转换Int)

如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。方法签名:

基本使用

Stream流中的 mapToInt 相关方法基本使用的代码如:

17、Stream流的concat(合并)方法

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :

备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。

该方法的基本使用代码如:

0 人点赞