Java8特性-Stream流(提高效率必备)

118 阅读5分钟

Stream流

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。 Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。 Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。 这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。 元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果

可以抽象的理解为将列表或集合中的元素像工厂流水线一样一个一个的拿出来放到工作流当中

Stream的API-创建Stream,文章内容会使用到函数式编程的内容,不了解的可以先看看对应的知识点

通过一个案例可以体验流的强大之处,当前有一个Class名为UserUsernameage 字段,需要从一个UserList中找出姓王的用户并且按照年龄排序,最后将这些人的名字返回

定义User Class

public class User {
    private String name;
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }
}

在没有Stream之前,可能要写下面这么一长串的代码,定义一个返回测试数据的方法

    public static List<User> getUsers() {
        return Arrays.asList(
                new User("王五",20),
                new User("张三",21),
                new User("王八",22),
                new User("王福",18)
        );
    }

需要先拿到这个List,使用for循环筛选出所有name是王开头的用户,接着再调用排序方法传入Comparator(以前没有函数式编程,既不存在lambda表达式)的实例按age排序,最后把包含所有姓王的用户且排好序的User列表for执行循环把name给挑出来放到结果数组中

        List<User> users = getUsers(); // 获取User列表
        ArrayList<User> wangList = new ArrayList<>();
        // 先筛选出姓王的User
        for (User user : users) {
            if (user.getName().startsWith("王")) {
                wangList.add(user);
            }
        }
        // 按照age去排序
        wangList.sort(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getAge() > o2.getAge() ? 1 : (o1.getAge().equals(o2.getAge())) ? 0 : -1;
            }
        });
        ArrayList<String> result = new ArrayList<>();
        for (User user : wangList) {
            result.add(user.getName());
        }
        // 拿到排好序后的name
        System.out.println(result);

接着使用Stream的方式对比下,我们可以使用Collection接口提供的stream方法(List实现了Collection)去创建一个流,下面也使用了Java8引入的lambda 表达式,几行代码完成同样的功能

       List<String> result = users.stream()
                .filter(user->user.getName().startsWith("王"))
                .sorted(Comparator.comparing(User::getAge))
                .map(User::getName)
                .collect(Collectors.toList());

为了方便阅读换行了,但如果你想也可以写成一行,代码一下子简洁了

创建流后可以执行若干的中间操作,这些操作都会返回一个流

可以查看Stream接口的源码,所有抽象方法只要返回值是Stream,都可以链式执行调用a().b().c()...,比如filter,sorted,map方法等

Stream与原来的List没有任何关系

除了使用Collection.stream方法创建流常用以外

  • Stream.of方法,从数组创建一个流,这个方法定义很简单,实际仍然是通过Collectionstream方法创建,但是不用先创建一个Collection,更方便操作
    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }

一行代码即可实现输出流中的内容

    Stream.of("test1","test2","test3").forEach(System.out::println);
  • String.chars方法,可以将一个字符串变成一个一个的IntStream,也就是每个字符对应的ASCⅡ码
    System.out.println("sdjDFSDFdjfklsd".chars().filter(Character::isUpperCase).count());

这行代码会输出大写字母的数量,count函数返回的是long类型

  • IntStream.range方法可以返回int流,接受两个参数range(int startInclusive, int endExclusive),起始且包含值,结束但不包含值,传入(0,4),返回的就是0-3int
    IntStream.range(0, 10).filter(value -> value % 2 != 0).forEach(System.out::println);

这段代码先生成0-9的数字流,过滤掉偶数,然后输出

Stream提供的api有很多,只要看看文档和源码,就能猜到这些方法是做什么的,比如

concat,max,min,count,anyMatch等等,感兴趣的看一看用一用即可

终结操作,返回非Stream的操作,包括void,用完流就被销毁,其中collect操作最为重要

一个流可以有无数的中间操作,但只能有一次终结操作,collect接受的是一个Collector函数接口,但实际上我们并不需要自己编写这个函数,Collectors工具类中,有许多可以拿来即用的方法,比如最常用的Collectors,toList(),toSet(),toCollect,toMap

这里讲讲toCollect这个方法

如果想要返回比如TreeSetTreeMap,用上面出现过的例子演示用法,toCollection接受一个参数是Supplier,需要收集成什么类型,就得传入什么类型,所以可以传入TreeSet::new

    TreeSet<String> names = users.stream()
        .filter(user -> user.getName().startsWith("王"))
        .sorted(Comparator.comparing(User::getAge))
        .map(User::getName)
        .collect(Collectors.toCollection(TreeSet::new));

Collectors工具类集合中,提供了非常多的操作,比如groupingBy,summingInt,partitioningBy

  • groupingBy,按照条件进行分组,下面代码中,根据User对象的年龄排序,在根据姓名去分组,也就是按照同名人去分组,返回的是HashMap类型
    List<User> users = getUsers();
    Map<String, List<User>> collect = users.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.groupingBy(User::getName));
  • joining,按照指定的参数进行字符串拼接,示例代码会对姓名进行拼接,如“张三,王五,李四”,返回类型是String
    String str = users.stream().map(User::getName).collect(Collectors.joining(","));
  • summingInt,属性累计并返回结果,示例代码会对年龄进行计算总和,返回类型是Integer
    Integer sum = users.stream().collect(Collectors.summingInt(User::getAge));
  • partitioningBy,按照truefalse划分区域,返回类型是Map<Boolean,List<T>>
    Map<Boolean, List<User>> collect1 = users.stream().collect(Collectors.partitioningBy(user -> user.getAge() > 20));

其他的不列举了,用到时看看文档就行

Stream 并发流,正确的使用,可以通过并发提高互相独立的操作的性能,Collection.parallelStream可以生成一个并发流,Stream.parallel可以将一个普通流转换成并发流

如某些流操作之间没有依赖关系实际上可以并发的完成,可以使用Collection,可以通过一个比较观察性能上的提升

    long l = System.currentTimeMillis();
    IntStream.range(0, 100_0000_000).filter(i -> i % 2 == 0).count();
    System.out.println(System.currentTimeMillis() - l);

    long l2 = System.currentTimeMillis();
    IntStream.range(0, 100_0000_000).parallel().filter(i -> i % 2 == 0).count();
    System.out.println(System.currentTimeMillis() - l2);

l输出1338毫秒,而l2输出319毫秒,这个简单的测试快了接近4倍,但如果不能保证要执行的操作是线程安全的,不推荐使用

毫无疑问,学会使用Stream,将会有助于提高编码效率 : )