夯实Java基础(二十四)——Java8新特征之Stream API

一、Stream简介

Java8中除了引入了好用的Lambda表达式、Date API以外,另外还有一大亮点就是Stream API了,也是最值得全部Java开发人员学习的一个知识点,由于它的功能很是的强大,尤为是和前面学习的Lambda表达式、函数式接口、方法引用共同使用的时候。html

Stream流是数据渠道,用于操做数据源所生成的元素序列,即操做集合、数组等等。其中集合和stream的区别是:集合讲的是数据,而stream讲的是计算Stream的API所有都位于java.util.stream这样一个包下,它可以帮助开发人员从更高的抽象层次上对集合进行一系列操做,就相似于使用SQL执行数据库查询。而借助java.util.stream包,咱们能够简明的声明性的表达集合,数组和其余数据源上可能的并行处理。实现从外部迭代到内部迭代的改变。它含有高效的聚合操做、大批量的数据处理,同时也内置了许多运算方式,包括筛选、排序、聚合等等。简单来讲,用Stream来操做集合——减小了代码量,加强了可读性,提升运行效率。java

而后这里要提一下:Stream和Java IO中的stream是彻底不一样概念的两个东西。本文要讲解的stream是可以对集合对象进行各类串行或并发汇集操做,而Java IO流用来处理设备之间的数据传输,它们二者大相径庭。数据库

Stream的主要特色有:
一、stream自己并不存储数据,数据是存储在对应的collection(集合)里,或者在须要的时候才生成的。
二、stream不会修改数据源,老是返回新的stream。
三、stream的操做是延迟(lazy)执行的:仅当最终的结果须要的时候才会执行,即执行到终止操做为止。数组

二、传统方式与使用Stream简单对比

首先咱们先使用之前的方法来对集合进行一些操做,代码示例以下:网络

public class StreamTest {
    public static void main(String[] args) {
        //建立集合
        ArrayList<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");
        list.add("four");
        list.add("five");
        list.add("six");

        //遍历数据,只遍历4个
        for (int i = 0; i < list.size()-1; i++) {
                //过滤掉字符串——two
                if (!(list.get(i).contains("two"))) {
                    System.out.println(list.get(i));
            }
        }
    }
}

我相信像上面这样的代码估计每一个java开发人员分分钟均可实现。但若是要求再复杂一点,代码量就会大量增长,因此再经过建立一个存储数据的集合,而后对集合进行遍历,再经过特定的条件进行筛选而且将结果输出。这样的代码中或多或少会有缺点的:
一、代码量一多,维护确定更加困难。可读性也会变差。并发

二、难以扩展,要修改成并行处理估计会花费很大的精力。app

而后下面展现了用Stream来实现相同的功能,很是的简洁,若是须要添加其余的方法也很是的方便。注:Stream中的一些方法可能暂时不清楚,这不要紧,后面都会一 一介绍它们。dom

//使用filter,limit,forEach方法
list.stream().filter(s->!s.contains("two")).limit(4).forEach(System.out::println);

最终两种方式的代码运行结果是同样的:ide

image

从上面使用Stream的状况下咱们能够发现是Stream直接链接成了一个串,而后将stream过滤后的结果转为集合并返回输出。而至于转成哪一种类型,这由JVM进行推断。整个程序的执行过程像是在叙述一件事,没有过多的中间变量,因此能够减少GC压力,提升效率。因此接下来开始探索Stream的功能吧。函数

三、Stream操做的三个步骤

Stream的原理是:将要处理的元素看作一种流,流在管道中传输,而且能够在管道的节点上处理,包括有过滤、筛选、去重、排序、汇集等。元素在管道中通过中间操做的处理,最后由终止操做获得前面处理的结果。

因此Stream的全部操做分为这三个部分:建立对象-->中间操做-->终止操做,以下图。

image

(1)、建立Stream:一个数据源(如:集合、数组),获取一个流。

(2)、中间操做:一个中间操做链,对数据源的数据进行处理。

(3)、终止操做(终端操做):一个终止操做,执行中间操做链,并产生结果。

四、建立Stream对象

①、使用Java8中被扩展的Collection接口,其中提供了两个获取流的方法:

  • default Stream<E> stream() 返回一个数据流
  • default Stream<E> parallelStream() 返回一个并行流
        //一、方法一:经过Collection
        ArrayList<String> list=new ArrayList<>();
        //获取顺序流,即有顺序的获取
        Stream<String> stream = list.stream();
        //获取并行流,即同时取数据,无序
        Stream<String> stream1 = list.parallelStream();

②、使用Java8 中Arrays的静态方法stream()获取数组流:

  • public static <T> Stream<T> stream(T[] array) 返回一个流
        //二、方法二:经过数组
        String str[]=new String[]{"A","B","C","D","E"};
        Stream<String> stream2 = Arrays.stream(str);

③、使用Stream类自己的静态方法of(),经过显式的赋来值建立一个流,它能够接受任意数量的参数:

  • public static<T> Stream<T> of(T... values) 返回一个流
        //三、方法三:经过Stream的静态方法of()
        Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5, 6);

④、建立无限流(迭代、生成)

  • public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  • public static<T> Stream<T> generate(Supplier<T> s)
        //4.、方式四:建立无限流(迭代、生成)
        //迭代(须要传入一个种子,也就是起始值,而后传入一个一元操做)
        Stream.iterate(2, (x) -> x * 2).limit(10).forEach(System.out::println);
        //生成(无限产生对象)
        Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);

以上的四种方式学习前三中便可,第四种不经常使用。除了上面这些额外还有其余建立Stream对象的方式,如Stream.empty():建立一个空的流、Random.ints():随机数流等等。

五、Stream中间操做

中间操做的全部操做会返回一个新的流,但它不会修改原始的数据源,而且操做是延迟执行的(lazy),意思就是在终止操做开始的时候才中间操做才真正开始执行。

中间操做主要有如下方法:filter、limit、 skip、distinct、map (mapToInt, flatMap 等)、 sorted、 peek、 parallel、 sequential、 unordered等。

为了更好地举例,咱们先建立一个Student类:

public class Student {
    private int id;
    private String name;
    private int age;
    private String address;

    public Student() { }

    public Student(int id, String name, int age, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id &&
                age == student.age &&
                Objects.equals(name, student.name) &&
                Objects.equals(address, student.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, address);
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

后面的全部测试数据用会用下面这一组数据(必要时会改):

        List<Student> students= Arrays.asList(
                new Student(1, "张三", 18, "北京"),
                new Student(2, "李四", 19, "上海"),
                new Student(3, "王五", 20, "广州"),
                new Student(4, "赵六", 21, "浙江"),
                new Student(5, "孙七", 22, "深圳")
        );

而后开始介绍流的中间操做的方法使用:

①、filter(Predicate<? super T> predicate):筛选:接收 Lambda表达式,表示从流中过滤某些元素。

//筛选年龄大于19岁而且住在浙江的学生
students.stream().filter(s -> s.getAge() > 19).filter(s -> "浙江".equals(s.getAddress())).forEach(System.out::println);

运行结果为:

image

这里咱们建立了五个学生对象,而后通过filter的筛选,筛选出年龄大于19岁而且家庭住址是在浙江的学生集合。

 

②、limit(long maxSize):截断:表示使其元素不超过给定数量。

        //给定获取3个数据
        students.stream().limit(3).forEach(System.out::println);

运行结果:

image

咱们只让它截断3个,每次都是从第一个数据开始截取。

 

③、skip(long n):跳过:表示跳过前 n 个元素。若流中元素不足 n 个,则返回一个空流。它与 limit(n)互补。

        //跳过前3个数据
        students.stream().skip(3).forEach(System.out::println);

运行结果:

image

能够发现输出的数据刚好与limit方法互补。

 

④、distinct():筛选:去除流中重复的元素。

这里我将第二个数据改为和第一个同样,看结果会怎样。

    public static void main(String [] args) {
        List<Student> students= Arrays.asList(
                new Student(1, "张三", 18, "北京"),
                new Student(1, "张三", 18, "北京"),
                new Student(3, "王五", 20, "广州"),
                new Student(4, "赵六", 21, "浙江"),
                new Student(5, "孙七", 22, "深圳")
        );
        //去除重复元素
        students.stream().distinct().forEach(System.out::println);

    }
}

运行结果:

image

能够发现相同的元素被去除了,可是注意:distinct 须要实体中重写hashCode()和 equals()方法才可使用。

 

⑤、map(Function<? super T, ? extends R> mapper):映射(转换):将一种类型的流转换为另一种类型的流。

为了便于理解咱们来分析一下,map函数中须要传入一个实现Function<T,R>函数式接口的对象,而该接口的抽象方法apply接收T类型,返回是R类型,因此map也能够理解为映射关系。

image

        //lambda表达式
        students.stream().map(s->s.getName()).forEach(System.out::println);
        //方法引用
        students.stream().map(Student::getName).forEach(System.out::println);

运行结果:

image

上面的例子中将Student对象转换为了普通的String对象,获取Student对象的名字。

 

⑥、flatMap(Function<? super T, ? extends Stream<? extends R>> mapper):转换合并:将流中的每一个值都转换成另外一个流,而后把全部流组合成一个流而且返回。

它和map有点相似。flatMap在接收到Stream后,会将接收到的Stream中的每一个元素取出来放入另外一个Stream中,最后将一个包含多个元素的Stream返回。

image

        List<Student> student1= Arrays.asList(
                new Student(1, "张三", 18, "北京"),
                new Student(2, "李四", 19, "上海")
        );

        List<Student> student2= Arrays.asList(
                new Student(3, "王五", 20, "广州"),
                new Student(4, "赵六", 21, "浙江"),
                new Student(5, "孙七", 22, "深圳")
        );

        //lambda
        Stream.of(student1,student2).flatMap(student -> student.stream()).forEach(System.out::println);
        //方法引用
        Stream.of(student1,student2).flatMap(List<Student>::stream).forEach(System.out::println);
        //常规方法
        Stream.of(student1,student2).flatMap(new Function<List<Student>, Stream<?>>() {
            @Override
            public Stream<?> apply(List<Student> students) {
                return students.stream();
            }
        }).forEach(System.out::println);

运行结果都是:

image

由于flatMap中最后须要将每一个元素组合成一个流,因此flatMap方法形参返回了一个stream流。

若是map和flatmap还不清楚能够参考这篇博客:java8 stream流操做的flatMap(流的扁平化) 写的很清楚。

 

⑦、sorted():天然排序:按天然顺序排序的流元素。

这里就不用Student对象做为举例了,不然要在Student类中实现Comparable接口。

        //天然排序
        List<String> list = Arrays.asList("CC", "BB", "EE", "AA", "DD");
        list.stream().sorted().forEach(System.out::println);

运行结果:

image

上面使用String中默认实现的接口自动完成排序。

 

⑧、sorted(Comparator<? super T> comparator):自定排序:按提供的比较符排序的流元素 。

        //自定排序
        students.stream().sorted((s1,s2)-> {
                    if(s1.getId()>s2.getId()){
                        return s1.getId().compareTo(s2.getId());
                    }else{
                        return -s1.getAge().compareTo(s2.getAge());
                    }
                }).forEach(System.out::println);

运行结果:

image

上面的代码表示为先按照id进行排序,若是id相同则按照年龄降序排序,你能够将其余id改成 1测试一下Open-mouthed smile

六、Stream终止操做

终止操做主要有如下方法:forEach、 forEachOrdered、anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、count、min、 max、 reduce、 collect、toArray、iterator。下面只介绍一下经常使用的方法。

注意:Stream流只要进行了终止操做后,就不能再次使用了,再次使用得从新建立流。

①、void forEach(Consumer<? super T> action):遍历:接收Lambda表达式,遍历流中的全部元素。

forEach上面已经用的够多的了,这里就不说了。

 

②、anyMatch/allMatch/noneMatch:匹配:返回值为boolean,参数为(Predicate<? super T> predicate)。

  • allMatch——检查是否匹配全部元素
  • anyMatch——检查是否至少匹配一个元素
  • noneMatch——检查是否没有匹配的元素
        boolean b = students.stream().allMatch((s) -> s.getAge() > 20);
        System.out.println("allMatch()"+"\t"+b);

        boolean b1 = students.stream().anyMatch((s) -> s.getAge() > 21);
        System.out.println("anyMatch()"+"\t"+b1);

        boolean b2 = students.stream().noneMatch((s) -> s.getAge() == 22);
        System.out.println("noMatch()"+"\t"+b2);

运行结果:

image

 

③、findFirst/findAny:查找:返回Optional<T>,无参数。

  • findFirst——返回第一个元素
  • findAny——返回当前流中的任意元素
        //先按id排好序
        Optional<Student> first = students.stream().sorted((s1, s2) -> s1.getId().compareTo(s2.getId())).findFirst();
        System.out.println("findFirst()"+"\t"+first.get());

        Optional<Student> any = students.stream().filter((s)->s.getAge()>19).findAny();
        System.out.println("findAny()"+"\t"+any.get());

运行结果:

image

 

④、long count():统计:返回流中元素的个数。

        //统计流中数量
        long count = students.stream().count();
        System.out.println("count()"+"\t"+count);

运行结果:

image

 

⑤、max/min:大小值:返回Optional<T>,无参数。

  • max——返回流中最大值
  • min——返回流中最小值
        //获取年龄最大值
        Optional<Student> max = students.stream().max((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
        System.out.println("max()"+"\t"+max.get());
        //获取年龄最小值
        Optional<Student> min = students.stream().min((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
        System.out.println("min()"+"\t"+min.get());

运行结果:

image

 

⑥、reduce:归约:能够将流中元素反复结合在一块儿,获得一个值。

  • T reduce(T identity, BinaryOperator<T> accumulator)
  • Optional<T> reduce(BinaryOperator<T> accumulator)
        //累加0-10
        List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        Integer sum = list.stream().reduce(0, (x, y) -> x + y);
        //一、T reduce(T identity, BinaryOperator<T> accumulator);
        System.out.println("reduce1--"+sum);

        //二、Optional<T> reduce(BinaryOperator<T> accumulator);
        Optional<Integer> sum1 = list.stream().reduce(Integer::sum);
        System.out.println("reduce2--"+sum1.get());

        Optional<Integer> sum2 = list.stream().reduce(new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer1, Integer integer2) {
                return Integer.sum(integer1,integer2);
            }
        });
        System.out.println("reduce3--"+sum2.get());

运行结果:

image

备注:map和reduce的链接一般称为map-reduce模式,因Google用它来进行网络搜索而出名。

七、Collectors的使用

Stream流与Collectors中的方法是很是好的搭档,经过组合来以更简单的方式来实现更增强大的功能,咱们利用Stream中的collect方法来实现。

collect:收集:将流转换为其余形式。它接收一个 Collector接口的实现,用于给Stream中元素作汇总的方法。

Collector接口中方法得实现决定了如何对流执行收集操做(如收集到List,Set,Map)。可是Collectors实用类提供了不少静态方法,能够方便地建立常见得收集器实例。

因此下面逐一介绍Collectors中的方法:

①、Collectors.toList():将流转换成List。

        /**
         * Collectors.toList():将流转换成List
         */
        List<String> list = students.stream().map(student -> student.getName()).collect(Collectors.toList());
        System.out.println("toList----"+list);

 

②、Collectors.toSet():将流转换为Set。

        /**
         * Collectors.toSet():将流转换成Set
         */
        Set<String> set = students.stream().map(student -> student.getName()).collect(Collectors.toSet());
        System.out.println("toSet----"+set);

 

③、Collectors.toCollection():将流转换为其余类型的集合。

        /**
         * Collectors.toCollection():将流转换为其余类型的集合
         */
        TreeSet<String> treeSet = students.stream().map(student -> student.getName()).collect(Collectors.toCollection(TreeSet::new));
        System.out.println("toCollection----"+treeSet);

 

④、Collectors.counting():元素个数。

        /**
         * Collectors.counting():元素个数
         */
        Long aLong = students.stream().collect(Collectors.counting());
        System.out.println("counting----"+aLong);

 

⑤、Collectors.averagingInt()、Collectors.averagingDouble()、Collectors.averagingLong():求平均数。

这三个方法均可以求平均数,不一样之处在于传入得参数类型不一样,可是返回值都为Double。

        /**
        *  Collectors.averagingInt()
        *  Collectors.averagingDouble()
        *  Collectors.averagingLong()
        *  求平均数
        */
        Double aDouble = students.stream().collect(Collectors.averagingInt(student -> student.getAge()));
        System.out.println("averagingInt()----"+aDouble);

        Double aDouble1 = students.stream().collect(Collectors.averagingDouble(student -> student.getAge()));
        System.out.println("averagingDouble()----"+aDouble1);

        Double aDouble2 = students.stream().collect(Collectors.averagingLong(student -> student.getAge()));
        System.out.println("averagingLong()----"+aDouble2);

 

⑥、Collectors.summingDouble()、Collectors.summingDouble()、Collectors.summingLong():求和。

这三个方法均可以求和,不一样之处在于传入得参数类型不一样,返回值为Integer, Double, Long。

        /**
         *  Collectors.summingInt()
         *  Collectors.summingDouble()
         *  Collectors.summingLong()
         *  求和
         */
        Integer integer = students.stream().collect(Collectors.summingInt(student -> student.getAge()));
        System.out.println("summingInt()----"+integer);

        Double aDouble3 = students.stream().collect(Collectors.summingDouble(student -> student.getAge()));
        System.out.println("summingDouble()----"+aDouble3);

        Long aLong1 = students.stream().collect(Collectors.summingLong(student -> student.getAge()));
        System.out.println("summingLong()----"+aLong1);

 

⑦、Collectors.maxBy():求最大值。

        /**
         * Collectors.maxBy():求最大值
         */
        Optional<Integer> integer1 = students.stream().map(student -> student.getId()).collect(Collectors.maxBy((x, y) -> Integer.compare(x, y)));
        System.out.println("maxBy()----"+integer1.get());

 

⑧、Collectors.minBy():求最小值。

        /**
         * Collectors.minBy():求最小值
         */
        Optional<Integer> integer2 = students.stream().map(student -> student.getId()).collect(Collectors.minBy((x, y) -> Integer.compare(x, y)));
        System.out.println("maxBy()----"+integer2.get());

 

 

⑨、Collectors.groupingBy():分组 ,返回一个map。

        /**
         * Collectors.groupingBy():分组 ,返回一个map
         */
        Map<Integer, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getId));
        System.out.println(listMap);

其中Collectors.groupingBy()还能够实现多级分组,以下:

        /**
         * Collectors.groupingBy():多级分组 ,返回一个map
         */

        //先按name分组而后得出每组的学生数量,使用重载的groupingBy方法,第二个参数是分组后的操做
        Map<String, Long> stringLongMap = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
        System.out.println("groupingBy()多级分组----"+stringLongMap);

        //先按id分组而后再按年龄分组
        Map<Integer, Map<Integer, List<Student>>> integerMapMap = students.stream().collect(Collectors.groupingBy(Student::getId, Collectors.groupingBy(Student::getAge)));
        System.out.println("groupingBy()多级分组----"+integerMapMap);

        //遍历
        Map<Integer,List<Student>> map = new HashMap<>();
        Iterator<Map.Entry<Integer, Map<Integer, List<Student>>>> iterator = integerMapMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<Integer, Map<Integer, List<Student>>> entry = iterator.next();
            System.out.println("key="+entry.getKey()+"----value="+entry.getValue());
        }

 

⑩、Collectors.partitioningBy():分区。按true和false分红两个区。

        /**
         * Collectors.partitioningBy():分区,分红两个区
         */
        Map<Boolean, List<Student>> listMap1 = students.stream().collect(Collectors.partitioningBy(student -> student.getAge() > 18));
        System.out.println(listMap1);

⑪、Collectors.joining():拼接。按特定字符将数据拼接起来。

        /**
         * Collectors.joining():拼接
         */
        String str = students.stream().map(student -> student.getName()).collect(Collectors.joining("---"));
        System.out.println(str);

八、结束语

Java8中的Stream提供的功能很是强大,用它们来操做集合会让咱们用更少的代码,更快的速度遍历出集合。并且在java.util.stream包下还有个类Collectors,它和stream是好很是好的搭档,经过组合来以更简单的方式来实现更增强大的功能。虽然上面介绍的都是Stream的一些基本操做,可是只要你们勤加练习就能够灵活使用,很快的能够运用到实际应用中。

相关文章
相关标签/搜索