jdk8的新特性总结(二):StreamAPI

这篇文章我们一起来学习java8最重要的新特性之一,强大的StreamAPI,在本文中的代码示例中我们会大量使用lambda表达式,如果对lambda表达式还不太熟悉,可以看一下上一篇文章。

java8中Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

一、首先我们来思考一个问题,什么是Stream(流)?

流是一个数据渠道,用于操作数据源(集合,数组等)所生成的元素序列,总之,集合主要是数据,流主要是计算。

二、Stream操作的三个步骤:

  • 创建Stream

通过一个数据源获取一个流,例如,List中的stream()方法可以直接返回一个Stream对象。

  • 中间操作

我们需要对流中的数据进行的操作,比如循环处理(map),过滤(filter)等

  • 终止操作

流都是惰性求值的,这个我们在后面会讲到,需要进行一个终止操作,这样才会返回中间操作后的数据。

如下图:

Stream操作的三个步骤

注意:

Stream只是一个计算通道,自己不会存储元素;

Stream不会改变源对象,相反,他们会返回一个新的Stream对象。

Stream操作是延时的,只有在执行终止操作时才会执行。

三、创建Steam的方式:

1、由Collection子类(List,Set)来创建流:

java8扩展了Collection接口,提供了stream(返回顺序流)和parallelStream(返回并行流)两个方法。并行流我们后面在详细讨论,示例代码:

@Test
public void test(){
    List<String> list = Arrays.asList("a","b","c");
    Stream stram = list.stream();
    Stream parallelSteam = list.parallelStream();
}

2、由数据来创建流:

数组可以通过Arrays工具类的stream方法来获得一个Steam对象,示例代码:

public void test2(){
    String[] strArr = new String[]{"a","b","c"};
    Stream stram = Arrays.stream(strArr);
    //还有许多重载形式的方法,可以返回带类型的Stream,例如:
    IntStream stram2 = Arrays.stream(new int []{1,2,3,4,5});
}

3、通过具体值来创建流:

通过Stream的静态方法Stream.of(T...values)可以创建一个流,它可以接受任意个值,代码示例:

@Test
public void test3() {
    Stream s = Stream.of("1","2",3,new String[]{"a", "b", "c"});
}

4、通过函数来创建流(无限流)

通过Stream.iterate()和Stream.generate()方法可以创建无限流,什么是无限流,我们来看一下代码示例就明白了:

@Test
public void test4() {
    //Stream.iterate方法第一个方法表示开始值得,第二个参数需要提供一个一元操作函数,我们用lambda表达式传递给它
    Stream stream1 = Stream.iterate(0, (x) -> x + 2);
    stream1.forEach(System.out::println); //输出的是0,2,4,6,8,10....将会一直循环下去,永远不停息
    //Stream.generate需要一个供给型函数接口
    Stream stream2 = Stream.generate(() -> 1);
    stream2.forEach(System.out::println); //输出无数个1,将会一直循环下去,永远不停息
}

备注:实际运用中,我们肯定不会生成一个无限流,除非你想要死循环,我们会结合Stream的终止操作,如limit来获取有指定个数元素的流:

@Test
public void test5(){
    //我们从0开始获取前50个偶数
    Stream stream1 = Stream.iterate(0, (x) -> x + 2).limit(50);
    stream1.forEach(System.out::println);//输出0,2,4,6,8。。。。98
}

四、Stream的中间操作:

Stream可以进行一系列的流水线式的中间操作,除非流水线上触发终止操作,否则,这些中间操作不会进行任何处理,而在终止操作时一次性处理,这个我们叫做Stream的惰性求值。

记住,中间操作不管做多少次,都不会改变原来的流,只会返回一个新的流;

Stream的中间操作可以分为以下几类:

中间操作:筛选与切片

方法 描述 filter(Predicate d) 接受一个断言型函数,对Stream流中的元素进行处理,过滤掉不满足条件的元素 distinct 筛选元素,通过Stream元素中的hasCode和equals方法来去除重复元素 limit(long maxSize) 截断流,使元素不超过manSize指定的数量 skip(Long n) 跳过元素,返回一个扔掉了前n个元素的流,若流中的元素不足n个,则会返回一个空流

中间操作:映射

方法 描述 map(Function f) 接受一个函数型接口作为参数,该函数会对流中的每个元素进行处理,返回处理后的流 mapToDouble(ToDoubleFunction  f) 接口一个函数型接口作为参数,该函数会对流中的每个元素进行处理,并返回一个Double值,最终得到一个Stream<Double> mapToInt(ToIntFunction f) 接口一个函数型接口作为参数,该函数会对流中的每个元素进行处理,并返回一个Int值,最终得到一个Stream<Int> mapToLong(ToLongFunction f) 接口一个函数型接口作为参数,该函数会对流中的每个元素进行处理,并返回一个Long值,最终得到一个Stream<Long> flatMap(Function f) 接受一个函数作为参数,将流中的每个值都转换成一个新流,最后再将这些流连接到一起

中间操作:排序

方法 描述 sorted 返回一个新流,流中的元素按照自然排序进行排序 sorted(Comparator comp) 返回一个新流,并且Comparator指定的排序方式进行排序

中间操作练习:

/**
 * Stream的中间操作练习
 */
public class StreamTest2 {
    List<Employee> emps = Arrays.asList(
            new Employee("张三", 18, 6666.66),
            new Employee("李四", 20, 7777.77),
            new Employee("王五", 36, 8888.88),
            new Employee("田七", 55, 11111.11),
            new Employee("赵六", 55, 9999.99),
            new Employee("赵六", 45, 12222.22)
    );
    /**
     * 筛选与切片 filter, distinct limit skip
     */
    @Test
    public void test1() {
        //1.过滤掉年龄小于25的员工
        emps.stream().filter((e) -> e.getAge() > 25).forEach(System.out::println);
        //2.过滤掉姓名重复的员工
        emps.stream().distinct().forEach(System.out::println);
        //3.获取前三名员工
        emps.stream().limit(3).forEach(System.out::println);
        //4.获取第三名以后的员工
        emps.stream().skip(3).forEach(System.out::println);
        //5.先获取前3名员工,再获取其中年龄大于25的员工。(中间操作可以任意次)
        emps.stream().limit(3).filter(e -> e.getAge() > 25);
    }
    /**
     * 映射操作 map, mapToDouble, mapToInt, mapToLong, flatMap
     */
    @Test
    public void test2() {
        //1. 获取所有员工的姓名
        emps.stream().map(e -> e.getName()).forEach(System.out::println);
        //2. 获取所有员工的工资,这里工资是Double类型,我们可以用mapToDouble方法
        emps.stream().mapToDouble(e -> e.getSalary()).forEach(System.out::println);
        //3. 获取所有员工的年龄,用mapToInt方法
        emps.stream().mapToInt(e -> e.getAge()).forEach(System.out::println);
        //4. 将员工年龄转换成Long型并输出,PS:这里就算不用longValue系统也会自动将getAge转换成Long类型
        emps.stream().mapToLong(e -> e.getAge().longValue()).forEach(System.out::println);
        /**
         * 5.将所有员工的 姓名,年龄,工资转换成一个流并返回
         * 首先我们用map方法来处理,这种方式返回的是六个Stream对象,数据结构可以类似于:
         * [{"张三",18, 6666.66},{"李四",20, 7777.77}, {"王五", 36, 8888.88}, {"赵六", 24, 9999.99}, {"田七", 55, 11111.11}, {"赵六", 45, 12222.22}]
         * 然后我们用flatMap方法来处理,返回的是一个Stream对象,将所有元素连接到了一起,数据结构类似于:
         * ["张三",18, 6666.66,"李四",20, 7777.77, "王五", 36, 8888.88, "赵六", 24, 9999.99, "田七", 55, 11111.11, "赵六", 45, 12222.22]
         */
        emps.stream().map(e -> {
            return Stream.of(e.getName(), e.getAge(), e.getSalary());
        }).forEach(System.out::println);
        emps.stream().flatMap(e -> {
            return Stream.of(e.getName(), e.getAge(), e.getSalary());
        }).forEach(System.out::println);
    }
    /**
     * 排序操作 sorted
     */
    @Test
    public void test3() {
        //1.按照自然排序,注意,需要进行自然排序则对象必须实现Comparable接口
        emps.stream().sorted().forEach(System.out::println);
        //2.按照给定规则进行排序,(按照工资高低进行排序)
        emps.stream().sorted((x,y) -> Double.compare(x.getSalary(),y.getSalary())).forEach(System.out::println);
    }
}

五、Stream的终止操作:

Stream的终止操作用来获取一系列流水线操作的最终结果,这个结果可以是任何值,例如boolean,List,Integer甚至可以是void,终止操作也分为以下几大类:

终止操作:查找与匹配

方法 描述 allMatch(Predicate p) 传入一个断言型函数,对流中所有的元素进行判断,如果都满足返回true,否则返回false。 anyMatch(Predicate p) 传入一个断言型函数,对流中所有的元素进行判断,只要有一个满足条件就返回true,都不满足返回false。 noneMatch(Predicate p) 所有条件都不满足,返回true,否则返回false。 findFirst() 返回流中的第一个元素。 findAny() 返回流中的任意一个元素。 count() 返回流中元素的个数。 max(Comparator c) 按照给定的排序规则进行排序后,返回流中最大值的元素 min(Comparator c) 按照给定的排序规则进行排序后,返回流中最小值的元素 forEach(Consumer c) 内部迭代。

终止操作:规约

方法 描述 reduce(T iden, BinaryOperator bo) 可以将流中的元素反复结合起来,得到一个值,返回T reduce(BinaryOperator bo) 可以将流中的元素反复结合起来,得到一个值,返回Optional。(Optional我们后面再聊)

终止操作:收集

方法 描述 collect(Collector c) 将流中的元素转换成其他形式,接受一个Collector接口的实现,用于处理Stream流中的元素,将流转换成其他形式的对象。

collector接口中方法的实现决定了如何对流执行收集操作(如收集到List,Set,Map)。java8中的Collectors类提供了很多静态方法,可以非常方便的创建常用的收集器实例,具体方法与实例:

Collectors中创建收集器实例的方法
方法 返回类型 作用
toList List<T> 将流中的元素收集到List中,例如:list.stream.collect(Collectors.toList());
toSet Set<T> 将流中的元素收集到set中:Set<Employee> emps= list.stream().collect(Collectors.toSet())
toCollection Collection<T> 将流中的元素收集到创建的集合:list.stream().collect(Collectors.toCollection(ArrayList::new))
counting Integer 计算流中元素的个数:long count = list.stream().collect(Collectors.counting());
summingInt Long 对流中的整数属性进行求和:inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt Double 计算流中元素的平均值:doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 返回一个IntSummaryStatistics,可以通过这个对象获取统计值,如平均值:IntSummaryStatistics iss=list.stream().collect(Collectors.summarizingInt(Employee::getSalary))
joining String 连接流中的每个字符串:String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional<T> 根据比较器选择最大值:Optional<Employee> min = emps.stream().collect(Collectors.minBy((x ,y) -> Double.compare(x.getSalary(), y.getSalary())));
minBy Optional<T> 根据比较器选择最小值:Optional<Employee> max = emps.stream().collect(Collectors.maxBy((x ,y) -> Double.compare(x.getSalary(), y.getSalary())));
reducing 规约产生的类型 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值。Integer totalAge = emps.stream().collect(Collectors.reducing(0, Employee::getAge, Integer::sum));
colectingAndThen 转换函数返回的类型 包裹一个收集器,对其结果进行转换:int inthow= emps.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map<K, List<T>> 根据某属性对结果进行分组,属性为K,结果为V:Map<String, List<Employee>> kv= emps.stream().collect(Collectors.groupingBy(Employee::getName));
partitioningBy Map<boolean, ListM<T>> 根据true或false进行分区:Map<Boolean,List<Employee>> vd = emps.stream().collect(Collectors.partitioningBy(e -> e.getAge() > 30));

终止操作练习:

/**
 * Stream的终止操作练习
 */
public class StreamTest3 {
    List<Employee> emps = Arrays.asList(
            new Employee("张三", 17, 6666.66),
            new Employee("李四", 20, 7777.77),
            new Employee("王五", 36, 8888.88),
            new Employee("田七", 55, 11111.11),
            new Employee("赵六", 55, 9999.99),
            new Employee("赵六", 45, 12222.22)
    );

    /**
     * 终止操作:查找与匹配 allMatch, anyMatch, noneMatch, findFirst, findAny, count, max, min ,forEach
     */
    @Test
    public void test1() {
        //1.查看是否有员工年龄是否都大于18
        boolean flag1 = emps.stream().allMatch(e -> e.getAge() > 18);
        System.out.println(flag1);   // false
        //2.先去掉张三,然后再判断所有员工年龄是否都大于18
        boolean flag2 = emps.stream().filter(e -> !"张三".equals(e.getName())).allMatch(e -> e.getAge() > 18);
        System.out.println(flag2);  //true
        //3.是否有员工年龄大于50
        boolean flag3 = emps.stream().filter(e -> !"张三".equals(e.getName())).anyMatch(e -> e.getAge() > 50);
        System.out.println(flag3);  //true
        //4.没有员工的年龄大于50?
        boolean flag4 = emps.stream().filter(e -> !"张三".equals(e.getName())).noneMatch(e -> e.getAge() > 50);
        System.out.println(flag4);  //false
        //5.先按照年龄进行排序,然后返回第一个员工。optional是java8用来包装可能出现空指针的对象的对象
        Optional<Employee> op1 = emps.stream().sorted((x, y) -> Integer.compare(x.getAge(), y.getAge())).findFirst();
        System.out.println(op1.get());  //Employee{name='张三', age=17, salary=6666.66}
        //6. 查找任意一名员工的姓名,当使用顺序流时,返回的是第一个对象,当使用并行流时,会随机返回一名员工的姓名
        Optional<String> op2 = emps.parallelStream().map(e -> e.getName()).findAny();
        System.out.println(op2.get()); //会随机获取一名员工
        //7. 查询员工人数
        Long count = emps.stream().count();
        System.out.println(count);
        //8.查询员工工资最大的员工信息。PS: 这个也可以通过先按照工资排序,然后取第一个元素来实现
        Optional<Employee> maxSalary = emps.stream().max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
        System.out.println(maxSalary.get());
        //9.查询员工最小年龄
        Optional<Employee> minAge = emps.stream().max((x, y) -> -Integer.compare(x.getAge(), y.getAge()));
        System.out.println(minAge.get());
        //10.循环输出所有员工的信息
        emps.stream().forEach(System.out::println);
    }
    /**
     * 终止操作:规约 reduce
     * 规约操作两个重载方法的区别是:
     * 一个未指定起始值,有可能返回null,所以返回的是一个Optional对象
     * 一个指定了起始值,不可能返回null,所以返回值可以确定是一个Employee
     */
    @Test
    public void test2() {
        //1.将所有员工的名字加上 -> 下一个员工的名字: 如 张三 -> 李四
        Optional<Employee> op1 = emps.stream().reduce((x,y) -> {x.setName(x.getName() + "->" + y.getName()); return x;});
        System.out.println(op1.get().getName());  //张三->李四->王五->田七->赵六->赵六
        //2.将所有员工的名字加上 -> 下一个员工的名字,并且开始以王八开始;
        // PS:测试时,请将例子1注释掉,不然会影响emps对象
        Employee emp = emps.stream()
                .reduce(new Employee("王八", 65, 8888.88)
                        , (x,y) -> {
                            x.setName(x.getName() + "->" + y.getName());
                            return x;
                        });
        System.out.println(emp.getName());  //王八->张三->李四->王五->田七->赵六->赵六
    }

    /**
     * 终止操作:收集
     */
    @Test
    public void test3(){
        //1.按年龄排序后收集成一个list并返回
        List<Employee> list = emps.stream().sorted((x, y) -> Integer.compare(x.getAge(), y.getAge()))
                  .collect(Collectors.toList());
        list.forEach(System.out::println);
        //2.按工资高低排序后收集成一个Set返回
        Set<Employee> set = emps.stream().sorted((x, y) -> Integer.compare(x.getAge(), y.getAge()))
                .collect(Collectors.toSet());
        set.forEach(System.out::println);
        //3.按工资排序后收集到指定的集合中,这里指定LinkedList
        LinkedList<Employee> linkedList = emps.stream().sorted((x, y) -> Integer.compare(x.getAge(), y.getAge()))
                .collect(Collectors.toCollection(LinkedList::new));
        linkedList.forEach(System.out::println);
        //4.计算流中元素的个数:
        long count = emps.stream().collect(Collectors.counting());
        System.out.println(count);
        //5.对所有员工的年龄求和:
        int inttotal = emps.stream().collect(Collectors.summingInt(Employee::getAge));
        System.out.println(inttotal);
        //6.计算所有员工工资的平均值:
        Double doubleavg= emps.stream().collect(Collectors.averagingDouble(Employee::getSalary));
        System.out.println(doubleavg);
        //7.返回一个IntSummaryStatistics,可以通过这个对象获取统计值,如平均值:
        IntSummaryStatistics iss =emps.stream().collect(Collectors.summarizingInt(Employee::getAge));
        System.out.println(iss.getAverage());
        System.out.println(iss.getMax());
        System.out.println(iss.getMin());
        //8.连接所有员工的名字:
        String str= emps.stream().map(Employee::getName).collect(Collectors.joining());
        System.out.println(str);
        //9.相当于先按照工资进行排序,再取出排在第一位的员工
        Optional<Employee> min = emps.stream().collect(Collectors.minBy((x ,y) -> Double.compare(x.getSalary(), y.getSalary())));
        System.out.println(min);
        //10.相当于先按照工资进行排序,再取出排在最后一位的员工
        Optional<Employee> max = emps.stream().collect(Collectors.maxBy((x ,y) -> Double.compare(x.getSalary(), y.getSalary())));
        System.out.println(max);
        //11.从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值。
        Integer totalAge = emps.stream().collect(Collectors.reducing(0, Employee::getAge, Integer::sum));
        System.out.println(totalAge);
        //12.包裹一个收集器,对其结果进行转换:
        int inthow = emps.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
        System.out.println(inthow);
        //13.根据某属性对结果进行分组,属性为K,结果为V:
        Map<String, List<Employee>> kv = emps.stream().collect(Collectors.groupingBy(Employee::getName));
        System.out.println(emps);
        //14.根据true或false进行分区,年龄大于30的分在true区,小于30的分在false区
        Map<Boolean,List<Employee>> vd = emps.stream().collect(Collectors.partitioningBy(e -> e.getAge() > 30));
        System.out.println(vd);
    }
}

总结:

Stream的总结其实就是一句话,记住Stream操作的三个步骤,创建流 -> 一系列中间操作 -> 终止操作拿到返回结果。

PS:上面练习的代码放在了我的git仓库中,感兴趣的可以down下来再练练:https://github.com/caishi13202/jdk8.git

猜你喜欢

转载自blog.csdn.net/caishi13202/article/details/82631779