Java8的双绝学之一stream能用来做什么?

2021-12-14 14:09:12 浏览数 (1)

前言

✓ 优质技术好文见专栏 个人公众号,分享一些技术上的文章,以及遇到的坑 当前系列:Java8 新特性 系列 源代码 git 仓库 代码Git 仓库地址

强大Stream API

Java 8 是一个非常成功的版本,Java8 新增的Stream,配合同版本出现的 Lambda ,给我们操作集合提供了极大的便利。

Stream API

  • 强大Stream API 的 为什么用强大呢? java8两大招牌 一个是函数式编程 Lambda 表达式,一个是Stream流
  • Stream API(java.util.stream)把真正的函数式编程风格引入到java中,这是目前为止对java类库最好的补充。因为Stream API可以极大的提供Java程序员的生产力,让程序员写出更高效率和干净,简洁的代码
  • Stream是Java8处理结合的关键抽象概念,他可以指定你希望对集合进行的操作,可以执行非常复杂的查找,过滤和映射数据等操作,使用StreamAPI对集合数据进行操作,就类似于使用SQL执行的数据库查询。可以使用Stream API来并行执行操作,简言之,Stream API提供一种高效且易于使用的处理数据的方式

为什么使用Stream API

  • 实际开发中 ,项目中多数据源来自于Mysql,Oracle 等,但现在使用数据源可以更多了,有MongoDB,redis等,而这些NoSql的数据就需要Java层面去处理
  • Stream和 Collection集合的区别
    • Collection 是一种静态内存数据结构的一个容器
    • Stream是有关计算的

    前面是主要面向内存,存储在内存中后者是主要是面向CPU的,通过CPU实现计算

Stream的操作三个步骤

  • 1-创建Stream 一个数据源(如集合,数组)获取一个流
  • 2-中间操作 一个中间操作链,对数据源进行处理
  • 3-终止操作(终端操作) 一旦执行终止操作,就执行中间操作链,并且产生结果,之后不在被使用

流程图

创建各种场景stream流

代码语言:javascript复制
/**
 * @projectName: Java8
 * @package: Stream
 * @className: StreamTest
 * @author: 冷环渊 doomwatcher
 * @description:
 * 
 *  1.Stream 关注的是对数据的运算,与CPU打交道
 *  集合关注的事数据的存储,与内存打交道
 *
 * 2.
 * - Stream 自己不会存储元素
 * - Stream 不会改变源对象 相反,他们会返回一个持有结果的新Stream
 * - Stream 操作是延时执行的,这意味的他们会等到需要结果的时候才执行
 *
 * 3. Stream 执行操作
 *    Stream 的实例化
 *    一系列的中间操作(过滤 映射 。。。。)
 *    终止操作
 *
 * 4. 说明
 * 4.1 一个中间操作链,对数据源进行处理
 * 4.2 一旦执行终止操作,就执行中间操作链,并产生结果,之后,不会再被使用
 *
 * 
 * @date: 2021/12/12 1:37
 * @version: 1.0
 */
public class StreamTest {

    //Stream 创建方式一:通过集合
    @Test
    public void test1() {
        List<Employee> employees = EmployeeData.getEmployees();
        /*
         * default Stream stream() 返回一个程序流
         * 这里我们用的是 Collection接口中就实现的方法
         *  default Stream stream() {
         *    return StreamSupport.stream(spliterator(), false);
         *}
         * */
        Stream<Employee> stream = employees.stream();

        // default Stream parallelStream() 返回一个并行流
        Stream<Employee> parallelStream = employees.parallelStream();
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context: 创建 Stream 的方式二 数组
     * Java 8 中 Arrays 的静态方法 stream() 可以获取数组流
     *    IntStream stream = Arrays.stream(arr); static  Stream stream(T[] array)返回一个流
     * 重载形式:
     * 能够处理 对应基本类型的数组
     *  ① public Static intStream stream(int[] array)
     *  ② public Static LongStraem stream(long[] array)
     *  ③ public Static DoubleStraam stream(double[] array)
     * @date: 2021/12/12 12:38
     * @param
     * @return:
     */
    @Test
    public void Test2() {
        int[] arr = {1, 2, 3, 4, 5, 6};
        IntStream stream = Arrays.stream(arr);
        Employee e1 = new Employee(1001, "tom");
        Employee e2 = new Employee(1002, "Treey");
        Employee[] employees = {e1, e2};
        Stream<Employee> employeeStream = Arrays.stream(employees);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     *  创建 Stream三 : Stream本身的 of() 我们需要创建数据的时候
     * @date: 2021/12/12 12:45
     * @param
     * @return: void
     */
    @Test
    public void Test3() {
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * 创建方式四 创建无限流
     * 我们需要批量的产生数据
     * @date: 2021/12/12 12:47
     * @param
     * @return: void
     */
    @Test
    public void Test4() {
        /* 迭代
         * public static Stream iterate(final T sead,final UnaryOperator)
         * 遍历前十个偶数
         * 这里 参数 UNaryOperator继承自 function 也就是说我们最终调用的方法 是重写的apply
         * @FunctionalInterface
         * public interface UnaryOperator extends Function {
         * static  UnaryOperator identity() {
         *   return t -> t;
         * }
         * }
         * */
        Stream.iterate(0, t -> t   2).limit(10).forEach(System.out::println);
        /*生成
         *  public static Stream generate(Supplier s)
         *
         * */
        Stream.generate(Math::random).forEach(System.out::println);
    }
}

Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止 操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全 部处理,称为“惰性求值”。

筛选与切片

  • filter(Predicate p) 接收 Lambda , 从流中排除某些元素
  • distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
  • limit(long maxSize) 截断流,使其元素不超过给定数量
  • skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一 个空流。与 limit(n) 互补
代码语言:javascript复制
    //filter(Predicate p) 接收 Lambda , 从流中排除某些元素
    @Test
    public void test1() {
        List<Employee> employeeList = EmployeeData.getEmployees();
        Stream<Employee> stream = employeeList.stream();
        //小练习,查询员工表中薪资大于7000的员工
        System.out.println("filter-------------------");
        stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);

        /*limit(long maxSize) 截断流,使其元素不超过给定数量
         * 这里我们用  stream 会抛出异常,所以我们直接调用构造方法,每次都会生成新的流
         * */
        System.out.println("limit-------------------");
        employeeList.stream().limit(3).forEach(System.out::println);

        /*    skip(long n)
         *   跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
         * */
        System.out.println("skip--------------------");
        employeeList.stream().skip(3).forEach(System.out::println);

        //distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去 除重复元素
        System.out.println("distinct----------------");
        employeeList.add(new Employee(1010, "刘强东", 40, 8000));
        employeeList.add(new Employee(1010, "刘强东", 40, 8000));
        employeeList.add(new Employee(1010, "刘强东", 40, 8000));
        employeeList.stream().distinct().forEach(System.out::println);
    }

映射

  • map(Function f) 接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。
  • mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream。
  • mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream。
  • mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream。
  • flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流
代码语言:javascript复制
    /**
     * @author 冷环渊 Doomwatcher
     * @context: 映射
     * @date: 2021/12/12 14:15
     * @param
     * @return: void
     */
    @Test
    public void test2() {
        /*        map(Function f)
         *    接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
         *  映射结果:
         *  aa   --> AA
         *  bb   --> BB
         *  cc   --> CC
         *  dd   --> DD
         * */
        List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
        list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
        System.out.println();
        List<Employee> employeeList = EmployeeData.getEmployees();
        employeeList.stream().map(Employee::getName).filter(name -> name.length() > 3).forEach(System.out::println);
        System.out.println();
        
        /*flatMap(Function f)
         * 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流、*/
        //这里是没有用 flatmap的版本 ,我们需要foreach之后再foreach才可以拆分到所有的元素
        Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest2::fromStringToStream);
        streamStream.forEach(s -> s.forEach(System.out::print));
        System.out.println();
        //使用了 flatmap
        Stream<Character> stream = list.stream().flatMap(StreamAPITest2::fromStringToStream);
        stream.forEach(System.out::print);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * 将字符串中的多个字符构成的集合,转换成对应的Stream的实例
     * @date: 2021/12/12 14:44
     * @param str
     * @return: java.util.stream.Stream
     */
    public static Stream<Character> fromStringToStream(String str) {
        ArrayList<Character> list = new ArrayList<>();
        for (Character c : str.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * =演示 list里放list
     * 【1,2,3,【4,5,6】】
     * 打散开了再放入list
     * 【1,2,3,4,5,6】
     * @date: 2021/12/12 14:33
     * @param
     * @return: void
     */
    @Test
    public void test3() {
        ArrayList list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        ArrayList list1 = new ArrayList();
        list1.add(4);
        list1.add(5);
        list1.add(6);
        list.add(list1);
        System.out.println(list);
    }

排序

  • sorted() 产生一个新流,其中按自然顺序排序
  • sorted(Comparator com) 产生一个新流,其中按比较器顺序排序

Comparator 概念 Comparator 是一个函数式接口。它经常用于没有天然排序的集合进行排序,如 Collections.sortArrays.sort或者对于某些有序数据结构的排序规则进行声明,如 TreeSetTreeMap 。该接口主要用来进行集合排序。 抽象方法 compare 作为Comparator 唯一的抽象方法,int compare(T o1,T o2) 比较两个参数的大小, 返回负整数,零,正整数 ,分别代表 o1、o1=o2、o1>o2,通常分别返回 -1、0 或 1 //输入两个同类型的对象,返回比较结果为int的数字 //`o1o2`,通常分别返回 `-1`、`0` 或 `1` (x1,x2)-> int

代码语言:javascript复制
    /**
     * @author 冷环渊 Doomwatcher
     * @context: 排序
     * @date: 2021/12/12 16:42
     * @param
     * @return: void
     */
    @Test
    public void tset4() {
        //sorted() 产生一个新流,其中按自然顺序排序
        List<Integer> list = Arrays.asList(12, 43, 65, 0, -1, 7);
        list.stream().sorted().forEach(System.out::println);

        /* 这里会出现问题
         * 为什么?
         * 原因:Employee没有实现Comparble接口、
         * List employees = EmployeeData.getEmployees();
         * employees.stream().sorted().forEach(System.out::println);
         * */

        //sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
        List<Employee> employees = EmployeeData.getEmployees();
        employees.stream().sorted((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).forEach(System.out::println);

        System.out.println();
        //小练习,如果年龄一样就按收入排序
        employees.add(new Employee(1011, "冷环渊", 34, 8000));
        employees.stream().sorted((e1, e2) -> {
            int agevalue = Integer.compare(e1.getAge(), e2.getAge());
            if (agevalue != 0) {
                return agevalue;
            } else {
                return -Double.compare(e1.getSalary(), e2.getSalary());
            }
        }).forEach(System.out::println);

    }

Stream 的终止操作

  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例 如:List、Integer,甚至是 void 。
  • 流进行了终止操作后,不能再次使用。

匹配与查找

  • allMatch(Predicate p) 检查是否匹配所有元素
  • anyMatch(Predicate p) 检查是否至少匹配一个元素
  • noneMatch(Predicate p) 检查是否没有匹配所有元素
  • findFirst() 返回第一个元素
  • findAny() 返回当前流中的任意元素
  • count() 返回流中元素总数
  • max(Comparator c) 返回流中最大值
  • min(Comparator c) 返回流中最小值
  • forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。相反,Stream API 使用内部迭 代——它帮你把迭代做了)
代码语言:javascript复制
    /**
     * @author 冷环渊 Doomwatcher
     * @context: 匹配与查找
     * @date: 2021/12/12 17:11
     * @param
     * @return: void
     */
    @Test
    public void test1() {
        //- allMatch(Predicate p) 检查是否匹配所有元素
        List<Employee> employees = EmployeeData.getEmployees();
        boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
        System.out.println("allMatch(Predicate p) 检查是否匹配所有元素: "   allMatch);
        //-  anyMatch(Predicate p) 检查是否至少匹配一个元素
        boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
        System.out.println(" anyMatch(Predicate p) 检查是否至少匹配一个元素: "   anyMatch);
        //- noneMatch(Predicate p) 检查是否没有匹配所有元素
        boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
        System.out.println("noneMatch(Predicate p) 检查是否没有匹配所有元素: "   noneMatch);
        //-  findFirst() 返回第一个元素
        Optional<Employee> first = employees.stream().findFirst();
        System.out.println("findFirst() 返回第一个元素: "   first);
        //- findAny() 返回当前流中的任意元素
        Optional<Employee> any = employees.parallelStream().findAny();
        System.out.println("findFirst() 返回第一个元素: "   any);
        //- count() 返回流中元素总数
        Long salary = employees.stream().filter(e -> e.getSalary() > 5000).count();
        System.out.println("count() 返回流中元素总数: "   salary);
    }


    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * @date: 2021/12/12 23:30
     * @param
     * @return: void
     */
    @Test
    public void test2() {
        //- max(Comparator c) 返回流中最大值
        List<Employee> employees = EmployeeData.getEmployees();
        Stream<Double> stream = employees.stream().map(e -> e.getSalary());
        Optional<Double> max = stream.max(Double::compare);
        System.out.println("返回流中最大值:"   max);
        //- min(Comparator c) 返回流中最小值
        Optional<Employee> min = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println("返回流中最小值: "   min);
        //- forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。相反,Stream API 使用内部迭 代——它帮你把迭代做了)
        employees.stream().forEach(System.out::println);
        //使用集合的方式
        employees.forEach(System.out::println);
    }

规约

map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

  • reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值。返回 T
  • reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值。返回 Optional
代码语言:javascript复制
/**
     * @author 冷环渊 Doomwatcher
     * @context: 规约
     * @date: 2021/12/12 23:56
     * @param
     * @return: void
     */
    @Test
    public void tset3() {

        //- reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值。返回 T
        // 小练习 计算1-10的自然数总和
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Integer resultReduce = list.stream().reduce(0, Integer::sum);
        System.out.println("resultReduce: "   resultReduce);
        //- reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值。返回 Optional
        //    小练习 计算公司所有员工的工资总和
        List<Employee> employees = EmployeeData.getEmployees();
        Optional<Double> slaryStream = employees.stream().map(Employee::getSalary).reduce(Double::sum);
        System.out.println(slaryStream);
    }

收集

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、 Map)。 另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,常用案例如下

常用语法 toList List 把流中元素收集到List List List emps= list.stream().collect(Collectors.toList()); toSet Set 把流中元素收集到Set Set emps= list.stream().collect(Collectors.toSet()); toCollection Collection 把流中元素收集到创建的集合 Collection emps=list.stream().collect(Collectors.toCollection(ArrayList::new)); counting Long 计算流中元素的个数 long count = list.stream().collect(Collectors.counting()); summingInt Integer 对流中元素的整数属性求和 int total=list.stream().collect(Collectors.summingInt(Employee::getSalary)); averagingInt Double 计算流中元素Integer属性的平均值 double avg =list.stream().collect(Collectors.averagingInt(Employee::getSalary)); summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。如:平 均值 int SummaryStatisticsiss=list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); joining String 连接流中每个字符串 String str= list.stream().map(Employee::getName).collect(Collectors.joining()); maxBy Optional 根据比较器选择最大值 Optionalmax=list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary))); minBy Optional 根据比较器选择最小值 Optional min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary))); reducing 归约产生的类型从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐 个结合,从而归约成单个值 int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar,Integer::sum)); collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果转 换函数 int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); groupingBy Map 根据某属性值对流分组,属性为K, 结果为V Map> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus)); partitioningBy Map 根据true或false进行分区 Map> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));

代码语言:javascript复制
    /**
     * @author 冷环渊 Doomwatcher
     * @context: 收集
     * @date: 2021/12/13 0:23
     * @param
     * @return:
     */
    @Test
    public void test4() {
        /* collet(Collector c) 将流转换成其他的形式,接收一个 Collector接口实现,用于给Stream中元素做汇总方法
         * 练习一 查找工资大于6000的员工 返回一个 list 或者set
         * */
        List<Employee> employees = EmployeeData.getEmployees();
        //大于六千的list
        List<Employee> employeeList = employees.stream().filter(employee -> employee.getSalary() > 6000).collect(Collectors.toList());
        employeeList.forEach(System.out::println);
        System.out.println();
        //小于六千的set
        Set<Employee> employeeSet = employees.stream().filter(employee -> employee.getSalary() < 6000).collect(Collectors.toSet());
        employeeSet.forEach(System.out::println);

    }
}

总结

到这里我们已经对Stream的日常使用有了一些了解,可以看到功能和我们的数据库sql语句有相似之处,可以在代码层面处理数据。

0 人点赞