zoukankan      html  css  js  c++  java
  • Java之Stream流

    Stream流的初步学习

    初次学习Stream流的学习笔记,学习之前先了解一下函数式接口

    概述

    API是一个程序向使用者提供的一些方法,通过这些方法就能实现某些功能.所以对于流API来
    说,重点是怎么理解"流"这个概念,所谓的流:就是数据的渠道,所以,流代表的是一个对象的
    序列.它和Java I/O类里使用的"流"不同.虽然在概念上与java.util.stream中定义的流是类
    似的,但它们是不同的.流API中的流是描述某个流类型的对象.

    流API中的流操作的数据源,是数组或者是集合.它本身是不存储数据的,只是移动数据,在移动
    过程中可能会对数据进行过滤,排序或者其它操作.但是,一般情况下(绝大数情况下),流操作
    本身不会修改数据源.比如,对流排序不会修改数据源的顺序.相反,它会创建一个新的流,其
    中包含排序后的结果.

    “Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何
    元素(或其地址值)。

    Stream(流)是一个来自数据源的元素队列

    • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
    • 数据源 流的来源。 可以是集合,数组 等。

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

    • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent
      style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
    • 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭
      代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。

    当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结
    果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以
    像链条一样排列,变成一个管道。

    获取流

    java.util.stream.Stream 是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)

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

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

    可以看出,Stream流是与容器相关的.

    下面例子演示如何获取Stream流:

    package com.wzlove.stream.demo2;
    
    import java.util.*;
    import java.util.stream.Stream;
    
    /**
     * @创建人 王智
     * @创建时间 2018/7/28
     * @描述 演示获取Stream流
     */
    public class ConvertStream {
    
        public static void main(String[] args) {
            // 单列集合根据Collection的stream方法获取流
            // 数组集合
            ArrayList<String> arrayList = new ArrayList<>();
            Stream<String> stream1 = arrayList.stream();
            // set集合
            Set<String> set = new HashSet<>();
            Stream<String> stream2 = set.stream();
            // Map集合不能直接转化为Stream流,但是可以先将Map集合转化为Set集合
            Map<String,String> map = new HashMap<>();
            // map集合转化为Set集合再转化为Stream流
            Stream<String> stream3 = map.keySet().stream();
            Stream<String> stream4 = map.values().stream();
            Stream<Map.Entry<String, String>> stream5 = map.entrySet().stream();
    
            // 使用第二种方式获取Stream流
            // 传递数组
            Stream<int[]> stream6 = Stream.of(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9});
            // 传入可变参数
            Stream<Integer> stream7 = Stream.of(1, 2, 3, 4, 5, 6);
        }
    }
    

    Stream的常用方法

    流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

    • 延迟方法:返回值类型仍然是Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方
      法均为延迟方法。)
    • 终结方法:返回值类型不再是Stream 接口自身类型的方法,因此不再支持类似StringBuilder 那样的链式调
      用。本小节中,终结方法包括count 和forEach 方法。

    注意 : Stream流只能被使用一次,再次使用会抛出异常,说流已经关闭.

    逐一处理

    方法不一一演示,最后统一演示:

    • void forEach(Consumer<? super T> action);该方法接收一个Consumer 接口函数,会将每一个流元素交给该函数进行处理。

    终结方法,遍历之后就不能再调用Stream流中的其他方法

    过滤

    • Stream filter(Predicate<? super T> predicate);
      该接口接收一个Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

    映射

    • Stream map(Function<? super T, ? extends R> mapper);
      该接口需要一个Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

    统计个数

    • long count();该方法返回一个long值代表元素个数(不再像旧集合那样是int值)

    也是一个终结方法

    取用前几个

    • Stream limit(long maxSize); 参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。

    跳过前几个

    • Stream skip(long n); 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。

    组合

    • static Stream concat(Stream<? extends T> a, Stream<? extends T> b) :
      如果有两个流,希望合并成为一个流,那么可以使用Stream 接口的静态方法concat

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

    演示前几个常用方法:

    package com.wzlove.stream.demo2;
    
    import java.util.ArrayList;
    
    /**
     * @创建人 王智
     * @创建时间 2018/7/28
     * @描述 测试Stream流的常用方法
     */
    public class StreamUsuallyMethod {
    
        public static void main(String[] args) {
            // 准备数据
            ArrayList<Integer> arrayList = new ArrayList<>();
            arrayList.add(5);
            arrayList.add(3);
            arrayList.add(9);
            arrayList.add(1);
            arrayList.add(4);
            arrayList.add(2);
            arrayList.add(3);
            arrayList.add(6);
            arrayList.add(8);
            arrayList.add(7);
            // 测试Stream的forEach方法
            System.out.print("forEach方法的测试 : ");
            arrayList.stream().forEach(elem -> System.out.print(elem + "  "));
            System.out.println();
    
            // 测试Filter方法
            // 获取集合中大于5的数字
            System.out.print("获取集合中大于5的数字 : ");
            arrayList.stream().filter(elem -> elem > 5).forEach(elem -> System.out.print(elem + "   "));
            System.out.println();
            // 获取集合中大于2小于7的数字
            System.out.print("获取集合中大于2小于7的数字 : ");
            arrayList.stream().filter(elem -> elem > 2).filter(elem -> elem < 7).forEach(elem -> System.out.print(elem + "   "));
            System.out.println();
    
    
            // 测试Map方法
            // 将集合转化为String集合
            System.out.print("map方法将int转化为String : ");
            arrayList.stream().map(elem -> String.valueOf(elem)).forEach(elem -> System.out.print(elem + "   "));
            System.out.println();
        }
    }
    

    举个例子,使用所有的常用方法:

    package com.wzlove.stream.demo2;
    
    import java.util.ArrayList;
    import java.util.stream.Stream;
    
    /**
     * @author 王智
     * @date 2018/7/28
     * @描述
     *  1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
     * 2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
     * 3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
     * 4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
     * 5. 将两个队伍合并为一个队伍;存储到一个新集合中。
     * 6. 根据姓名创建Person 对象;存储到一个新集合中。
     * 7. 打印整个队伍的Person对象信息。
     */
    public class StreamDemo {
    
        public static void main(String[] args) {
    
            //第一支队伍
            ArrayList<String> one = new ArrayList<>();
            one.add("迪丽热巴");
            one.add("宋远桥");
            one.add("苏星河");
            one.add("石破天");
            one.add("石中玉");
            one.add("老子");
            one.add("庄子");
            one.add("洪七公");
            //第二支队伍
            ArrayList<String> two = new ArrayList<>();
            two.add("古力娜扎");
            two.add("张无忌");
            two.add("赵丽颖");
            two.add("张三丰");
            two.add("尼古拉斯赵四");
            two.add("张天爱");
            two.add("张二狗");
            /*
             *  1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
             * 2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
             * 3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
             * 4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
             * 5. 将两个队伍合并为一个队伍;存储到一个新集合中。
             * 6. 根据姓名创建Person 对象;存储到一个新集合中。
             * 7. 打印整个队伍的Person对象信息。
             */
            // 直接使用Stream流来完成
            Stream.concat(
                    one.stream().filter(elem -> elem.length() == 3).limit(3),
                    two.stream().filter(elem -> elem.startsWith("张")).skip(2))
                    .map(elem -> new Person(elem))
                    .forEach(elem -> System.out.println(elem));
        }
    }
    

    方法引用

    很少使用

    :: 写法就是方法的引用,它的出现是为了简化Lambda的写法.

    System.out 对象中有一个重载的println(String) 方法恰好就是我们所需要的。那么对于
    printString 方法的函数式接口参数,对比下面两种写法,完全等效:

    • Lambda表达式写法: s -> System.out.println(s);
    • 方法引用写法: System.out::println

    第一种语义是指:拿到参数之后经Lambda之手,继而传递给System.out.println 方法去处理。

    第二种等效写法的语义是指:直接让System.out 中的println 方法来取代Lambda。两种写法的执行效果完全一
    样,而第二种方法引用的写法复用了已有方案,更加简洁。

    注:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常

    类型有
    这些很少用,是要能看懂就行

    • 通过对象名引用成员方法(demo1)

        package com.wzlove.method.demo1;
        /*
            定义一个打印的函数式接口
         */
        @FunctionalInterface
        public interface Printable {
            //定义字符串的抽象方法
            void print(String s);
        }
        
        package com.wzlove.method.demo1;
        
        public class MethodRerObject {
            //定义一个成员方法,传递字符串,把字符串按照大写输出
            public void printUpperCaseString(String str){
                System.out.println(str.toUpperCase());
            }
        }
      
        package com.wzlove.method.demo1;
        
        /**
         * @author 王智
         * @date 2018/7/28
         * @描述 使用方法引用简化Lambda表达式,对象调用方法
         */
        public class Demo1 {
        
            public static void printString(Printable printable){
                printable.print("java");
            }
        
            public static void main(String[] args) {
        
                // 简化前的写法
                printString(s->{
                    // 创建对象
                    MethodRerObject methodRerObject = new MethodRerObject();
                    // 调用方法
                    methodRerObject.printUpperCaseString(s);
                });
                // 简化后的写法,对象调用方法
                // 前提是对象存在,方法存在
                printString(new MethodRerObject()::printUpperCaseString);
            }
        }
      
    • 通过类名称引用静态方法(demo2)

      package com.wzlove.method.demo2;

      /**

      • @author 王智

      • @date 2018/7/28

      • @描述 计算绝对值的接口
        */
        @FunctionalInterface
        public interface Calcable {

        int intAbs(int num);
        }

      package com.wzlove.method.demo2;

      /**

      • @author 王智

      • @date 2018/7/28

      • @描述
        */
        public class Demo2 {

        public static int methodAds(int num, Calcable calcable){
        return calcable.intAbs(num);
        }

        public static void main(String[] args) {
        // Lambda的写法
        int num1 = methodAds(-100,num->Math.abs(num));
        System.out.println(num1);
        // 简化Lambda的写法
        // 前提是类存在,静态方法存在
        int result = methodAds(-100,Math::abs);
        System.out.println(result);
        }
        }

    • 通过super引用成员方法

    • 通过this引用成员方法

    • 类的构造器的使用(demo3)

      package com.wzlove.method.demo3;

      public class Person {
      private String name;

        public Person() {
        }
      
        public Person(String name) {
            this.name = name;
        }
      
        public String getName() {
            return name;
        }
      
        public void setName(String name) {
            this.name = name;
        }
      

      }

      package com.wzlove.method.demo3;
      /*
      定义一个创建Person对象的函数式接口
      */
      @FunctionalInterface
      public interface PersonBuilder {
      //定义一个方法,根据传递的姓名,创建Person对象返回
      Person builderPerson(String name);
      }

      package com.wzlove.method.demo3;

      /**

      • @author 王智

      • @date 2018/7/28

      • @描述
        */
        public class Demo3 {

        public static void buildPerson(String name, PersonBuilder pb){

         Person person = pb.builderPerson(name);
         System.out.println(person.getName());
        

        }

        public static void main(String[] args) {

         buildPerson("杨紫",(name)->{
             return new Person(name);
         });
         // 方法引用
         // 有参构造
         buildPerson("迪丽热巴",Person::new);
        

        }

      }

    • 数组的构造器的引用(demo4)

      package com.wzlove.method.demo4;
      /*
      定义一个创建数组的函数式接口
      */
      @FunctionalInterface
      public interface ArrayBuilder {
      //定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
      int[] builderArray(int length);
      }

      package com.wzlove.method.demo4;

      /**

      • @author 王智

      • @date 2018/7/28

      • @描述
        */
        public class Demo4 {

        public static int[] builderArr(int length, ArrayBuilder ab){

         return ab.builderArray(length);
        

        }

        public static void main(String[] args) {
        int[] arr1 = builderArr(5,length->new int[length]);
        // 创建数组
        int[] arr = builderArr(5,int[] :: new );
        }
        }

  • 相关阅读:
    基于Python的人脸动漫转换
    let 与 var的区别
    【LeetCode】汇总
    【HDU】4632 Palindrome subsequence(回文子串的个数)
    【算法】均匀的生成圆内的随机点
    【LeetCode】725. Split Linked List in Parts
    【LeetCode】445. Add Two Numbers II
    【LeetCode】437. Path Sum III
    【LeetCode】222. Count Complete Tree Nodes
    【LeetCode】124. Binary Tree Maximum Path Sum
  • 原文地址:https://www.cnblogs.com/wadmwz/p/9384668.html
Copyright © 2011-2022 走看看