Java8—Stream流式编程Optional容器的详细介绍两万字

Posted 刘Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8—Stream流式编程Optional容器的详细介绍两万字相关的知识,希望对你有一定的参考价值。

基于Java8详细介绍了Stream流的含义和大部分API方法的使用,以及Optional容器、并行流等Java8的新特性!

文章目录

1 Stream的概述

public interface Stream< T >
extends BaseStream<T,Stream< T >>

Java8开始提供了Stream API,这些API方法是用函数式编程方式在对一批数据集合进行复杂操作的工具,简称“流”。流的特点如下:

  1. Stream将不同的操作使用不同的方法来描述,比如filter、map、reduce、find、match、sort,它的数据处理类似于数据库的操作。我们只需要编写核心的代码逻辑,这样可以让我们在一定程度上摆脱循环遍历、if判断等控制语句的编写,相当于对集合的操作做了更高层次的抽象。
  2. 相比于集合的操作,很多流操作本身会返回一个流。对于filter过滤、sorted排序、map收集等等操作,它们都是一个个的动作,因此我们还可以将这些动作链接起来,来表达复杂的数据处理流水线,比如先过滤,然后排序,最后收集输出……
  3. 就像集合必须存放数据一样,流也需要一个提供数据的源,这个源可以是集合、数组、或者一个函数!如果数据源是有序的,那么生成的流也是有序的!
  4. 流操作可以单线程顺序执行,也可多线程并行执行。
  5. 获得流之后,流里面的数据就不能在增加或者删除了,所有的操作都是基于流中现有的数据的操作。和迭代器类似,一个流只能被使用(遍历)一次,如果我们还需要其他按要求,此时只能从数据源那么再一次获取一个新的流。
  6. 使用普通集合操作元素,需要我们写迭代的代码,这被称为外部迭代,比如for、while、foreach,而流则帮助我们进行了内部数据迭代,这就有了并行迭代优化速度的可能,而我们无需关心它是怎么做的!
  7. 流元素按需计算,比如要找出大于10 的第一个数字,那么并不需要和所有元素去做比较,只要找出第一个匹配的元素就够了。
  8. Streams有一个BaseStream.close()方法和实现AutoCloseable,但几乎所有的流实例实际上不需要在使用后关闭。 一般来说,只有来源为IO通道的流(如Files.lines(Path, Charset)返回的流 )才需要关闭。 大多数流都由集合,数组或生成函数支持,这些函数不需要特殊的资源管理。 (如果流确实需要关闭,则可以在try -with-resources语句中将其声明为资源。)
  9. Stream API通常是接收一个函数式接口为参数,因此对lambda的支持非常好,Stream API配合lambda表达式,我们可以写出非常漂亮且简练的链式编程代码!可以说,要想把流用得好,那么lambda表达式必须先掌握,关于lambda表达式可以看这篇文章:Java8—一万字的lambda表达式的详细介绍与应用案例

先看看一个常见的普通集合操作和流操作的代码对比,体验流式编程的连续性以及简单性:

public class StreamFrist 
    public static void main(String[] args) 
        //学生集合,学生有age-年龄 ,name-名字,score-分数,三个属性
        List<Student> students = new ArrayList<>();
        students.add(new Student(10, 55, "小花"));
        students.add(new Student(13, 100, "小华"));
        students.add(new Student(9, 85, "晓华"));
        students.add(new Student(8, 70, "肖华"));


        //我们需要筛选出成绩大于等于60的学生名字

        //使用普通集合操作
        ArrayList<String> nameList1 = new ArrayList<>();
        for (Student student : students) 
            if (student.getScore() >= 60) 
                nameList1.add(student.getName());
            
        

        //使用流,不同的操作都是链式的,非常适合人类的思维,而不需要考虑迭代、判断操作
        List<String> nameList2 = students.stream()
                //筛选出成绩大于等于60的学生
                .filter(student -> student.getScore() >= 60)
                //收集学生名字
                .map(Student::getName)
                //返回结果
                .collect(toList());
    

2 流的操作

Stream API定义了许多流的操作,它们大概可以分为两类:

  1. 中间操作
    1. 中间操作的一个显著特征是会返回另一个Stream流,比如filter、sorted、linit等等,这样的好处是我们可以进行链式编程,形成一个操作流水线!
    2. 中间操作的另一个隐藏特征是延迟执行,或者称为惰性求值!因为他只是描述了流水线要进行的操作,而并没有真正的执行这条流水线,它需要一个“触发操作”,这就是终端操作!
  2. 终端操作
    1. 终端操作的一个显著特征是不会返回另一个Stream流,比如count、collect,相当于从流水线中获取结果,只有存在终端操作,中间操作才会执行。
    2. 终端操作的另一个隐藏特征是它会终结这个流,此后这个流不能被重复使用,也被称为及早求值!

案例演示:

@Test
public void test() 
    //学生集合,学生有age-年龄 ,name-名字,score-分数,三个属性
    List<Student> students = new ArrayList<>();
    students.add(new Student(10, 55, "小花"));
    students.add(new Student(13, 100, "小华"));
    students.add(new Student(9, 85, "晓华"));
    students.add(new Student(8, 70, "肖华"));


    //没有终端操作的流,不会执行中间操作
    students.stream()
            //筛选出成绩大于等于60的学生
            .filter(student -> 
                System.out.println("中间操作" + student.getScore());
                return student.getScore() >= 60;
            )
            //收集学生名字
            .map(Student::getName);


    //有终端操作的流,才会执行
    //没有终端操作的流,不会执行中间操作
    students.stream()
            //筛选出成绩大于等于60的学生
            .filter(student -> 
                System.out.println("终端操作" + student.getScore());
                return student.getScore() >= 60;
            )
            //收集学生名字
            .map(Student::getName)
            //collect是一个终端操作
            .collect(toList());


3 流的使用

要想使用流,需要做三件事:

  1. 数据来源,比如集合、数组、函数,用于生成流,这是必须的
  2. 一系列中间流水线操作,用于对数据进行筛选、过滤、整合等等,中间操作不是必须的;
  3. 一个终端操作,用于触发流水线中的中间操作的执行,消耗流并且获取结果;

我们可以发现流的使用和Java构建者模式类似,构建者模式使用一系列操作设置属性和配置,最后调用一个build方法时,对象才被真正创建。而流操作同样使用一系列中间操作,最后调用一个终端操作方法,该方法触发中间操作的执行并获取最终的结果!

Stream提供了非常多的API方法,这些方法可以根据不同的作用进行分类讲解!

3.1 获取流

有以下常见方式获取流:

  1. 从集合
    1. Java8开始Collection超级接口中,提供了一个stream()默认方法,这个方法可以从调用集合中获取全部集合元素的流,因此全部的Collection体系下面的集合都可以调用stream()方法获取流,流元素就是单个集合元素。
    2. 注意,Map集合中没有直接获取流的方法!
  2. 从数组
    1. 数组的Arrays.stream静态方法接收一个数组,返回一个具有数组全部元素的流,流元素就是单个数组元素。
  3. 从文件
    1. Files类中具有读取文件并且生成流的方法,最重要的就是lines方法,用于获取文件的所有行的流。
  4. 从函数
    1. Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。从函数生成的流被称为无限流,通常我们只需要截取一部分即可!
    2. static < T > Stream< T > iterate(T seed, UnaryOperator< T > f),iterate第一个参数是一个初始值,第二个参数是一个依次应用在每个产生的新值上的UnaryOperator,即一元操作器,这个 将上一次生成的值当作参数,生成下一个值。
    3. static < T > Stream< T > generate(Supplier< T > s),generate方法没有指定初始值,后面的值也不是依据前一个值计算出来的,generate接受一个Supplier,它的值就是通过这个生产者返回的!因此,如果我们需要获取随机数,使用generate就很方便!
  5. 指定元素
    1. static < T > Stream< T > of(T… values),返回其元素是指定值的顺序排序流。
    2. static < T > Stream< T > of(T t),返回包含单个元素的顺序 Stream 。

获取流的案例:

/**
 * @author lx
 */
public class CreateTest 

    /**
     * @author lx
     */
    class Filter 
        private int x;

        public int getX() 
            return x;
        

        public void setX(int x) 
            this.x = x;
        

        public Filter(int x) 
            this.x = x;
        

        public Filter() 
        

        @Override
        public String toString() 
            return "Filter" +
                    "x=" + x +
                    '';
        
    


    /**
     * 从集合获取流
     */
    @Test
    public void test() 
        List<Filter> filters = new ArrayList<>();
        filters.add(new Filter(0));
        filters.add(new Filter(3));
        filters.add(new Filter(9));
        filters.add(new Filter(8));
        //从集合
        Stream<Filter> stream = filters.stream();
        stream.forEach(System.out::println);
    

    /**
     * 从数组获取流
     */
    @Test
    public void test1() 
        Filter[] filArr = new Filter[]new Filter(1),
                new Filter(3),
                new Filter(9),
                new Filter(8);

        //从数组
        Stream<Filter> stream = Arrays.stream(filArr);
        stream.forEach(System.out::println);
    


    /**
     * 从文件获取流
     */
    @Test
    public void test2() 
        //读取文件的所有行的流
        try (Stream<String> lines = Files.lines(Paths.get("target/classes/lines.txt"))) 
            lines.forEach(System.out::println);
         catch (IOException e) 
            e.printStackTrace();
        
    


    /**
     * iterate获取10个偶数
     */
    @Test
    public void iterate() 
        Stream.iterate(0, n -> n + 2)
                //取前十个数据(后面会讲,这叫做“筛选”)
                .limit(10)
                .forEach(System.out::println);


    

    /**
     * iterate获取下标
     * 采用Stream通过下标遍历集合
     */
    @Test
    public void iterate2() 
        ArrayList<Object> objects = new ArrayList<>();
        objects.add(1);
        objects.add(3);
        objects.add(2);
        objects.add(4);
        Stream.iterate(0, i -> i + 1)
                //截取前集合长度的数据
                .limit(objects.size())
                .forEach(i -> System.out.println(i + "-->" + objects.get(i)));

    

    /**
     * generate获取10个随机数
     */
    @Test
    public void generate() 
        Stream.generate(Math::random)
                .limit(10)
                .forEach(System.out::println);
    


    //复杂数据生成

    /**
     * iterate获取10个斐波那契数
     */
    @Test
    public void iterateFibonacci() 
        //斐波那契数列的规律:F(0)=0,F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
        //我们初始化数组new int[]0, 1
        //后续的数组的第1个元素是前一个数组的第2个元素,后续的数组的第2个元素是前一个数组的第1、2个元素的和
        //这样实际上生成的数组如下:
        //new int[]0, 1
        //new int[]1, 1
        //new int[]1, 2
        //new int[]2, 3
        //new int[]3, 5
        //new int[]5, 8
        //new int[]8, 13
        //new int[]13, 21
        //new int[]21, 34
        //取每个数组的第一个元素就是斐波那契数

        Stream.iterate(new int[]0, 1,
                t -> new int[]t[1], t[0] + t[1])
                //生成10个数组
                .limit(10)
                //获取每个数组的第一个元素(后面会讲,这叫做“映射”)
                .map(t -> t[0])
                .forEach(System.out::println);
    


    /**
     * Stream.of
     */
    @Test
    public void of() 
        Stream.of(1, 2, 3, "11").forEach(System.out::println);
    

3.2 筛选操作

Stream API提供了对流元素的筛选操作,既有普通的条件筛选filter,也有特殊的筛选,比如distinct去重、limit和skip截取!

筛选操作是一个中间操作!

Stream< T > filter(Predicate< ? super T > predicate)

filter是使用最广泛的筛选操作,它接受一个断言,返回由与此给定断言匹配的此流的元素组成的流。

Stream< T > distinct()

返回一个由该流的不同元素(根据元素的equals方法)组成的流。对于有序流,选择不同的元素是稳定的(对于重复的元素,首先在遇到顺序中出现的元素被保留。)对于无序流,不能保证稳定性。

Stream< T > limit(long maxSize)

返回由该流的前maxSize元素组成的流,limit会最多截取流元素的前maxSize个。

Stream< T > skip(long n)

在丢弃流的前n个元素后,返回由该流的前n元素之后的元素组成的流。 如果此流包含少于n元素,那么将返回一个空流。

使用案例:

/**
 * @author lx
 */
public class FilterTest 


    List<Student> students = new ArrayList<>();

    @Before
    public void test() 
        students.add(new Student(10, 55, "小花"));
        students.add(new Student(13, 100, "小华"));
        students.add(new Student(9, 85, "晓华"));
        students.add(new Student(8, 70, "肖华"));
        students.add(new Student(8, 70, "肖华"));
    

    @Test
    public void filter() 
        System.out.println("filter筛选成绩大于等于70的学生");
        //filter筛选成绩大于等于70的学生
        students.stream().filter(student -> student.getScore() >= 70).forEach(System.out::println);

        System.out.println("filter+distinct筛选成绩大于等于70的学生,且去除重复数据");
        //filter+distinct筛选成绩大于等于70的学生,且去除重复数据
        students.stream().filter(student -> student.getScore() >= 70).distinct().forEach(System.out::println);

        System.out.println("limit最多截取前2个数据");
        //limit最多截取前2个数据
        students.stream().filter(student -> student.getScore() >= 70).limit(2).forEach(System.out::println);

        System.out.println("skip丢弃前2个数据");
        //skip丢弃前2个数据
        students.stream().filter(student -> student.getScore() >= 70).skip(2).forEach(System.out::println);

        System.out.println("skip丢弃前1个数据,limit最多截取前1个数据");
        students.stream().filter(student -> student.getScore() >= 70).skip(1).limit(1).forEach(System.out::println);
    

    static class Student 
        private int age;
        private int score;
        private String name;

        public int getAge() 
            return age;
        

        public void setAge(int age) 
            this.age = age;
        

        public int getScore() 
            return score;
        

        public void setScore(int score) 
            this.score = scoreJava8  Stream流式编程

java8新特性

Java8新特性Optional类

java8中optional和.stream().map()

Java8中你可能不知道的一些地方之Stream实战

Java8新特性代码示例(附注释)- 方法引用,Optional, Stream