Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。是函数式编程的特性。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。 就像工厂流水线一样。

我们就可以把一个Stream当做流水线处理:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");
  
      //移除为B的元素
      Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()){
        if(iterator.next().equals("B")) iterator.remove();
    }
  
      //Stream操作
    list = list     //链式调用
            .stream()    //获取流
            .filter(e -> !e.equals("B"))   //只允许所有不是B的元素通过流水线
            .collect(Collectors.toList());   //将流水线中的元素重新收集起来,变回List
    System.out.println(list);
}
 

对一个数组去重并进行从大到小排列, 再把每个元素+1, 最后只获取前两个元素:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(3);
 
    list = list
            .stream()
                  .distinct()   //去重(使用equals判断)
            .sorted((a, b) -> b - a)    //进行倒序排列
            .map(e -> e+1)    //每个元素都要执行+1操作
            .limit(2)    //只放行前两个元素
            .collect(Collectors.toList());
 
    System.out.println(list);
}
 

当遇到大量的复杂操作时,我们就可以使用Stream来快速编写代码,这样不仅代码量大幅度减少,而且逻辑也更加清晰明了(如果你学习过SQL的话,你会发现它更像一个Sql语句)

执行顺序

Stream流的执行顺序可以分为中间操作和终端操作两个阶段。

  1. 中间操作:中间操作是对流进行转换、过滤、映射等操作,可以链式调用多个中间操作。这些操作不会立即执行,而是在遇到终端操作时才会触发执行。常见的中间操作有filter、map、sorted、distinct等。
  2. 终端操作:终端操作是对流进行最终的处理,会触发中间操作的执行并产生最终的结果。终端操作是流的最后一个操作,一旦执行完毕,流就不能再被使用。常见的终端操作有forEach、collect、reduce、count等。

在执行过程中,中间操作会按照链式调用的顺序依次执行,每个中间操作的结果会作为下一个中间操作的输入。终端操作会触发中间操作的执行,并产生最终的结果。

需要注意的是,Stream流的操作是惰性求值的,即只有在终端操作触发时才会执行中间操作。这种方式可以提高效率,避免不必要的计算。

总结起来,Java Stream流的执行顺序是:中间操作按照链式调用的顺序依次执行,终端操作触发中间操作的执行并产生最终结果。

应用

取一堆(-100, 100)随机数, 只求前10个,且保留小于0的数,从大到小排序并打印:

public static void main(String[] args) {
    Random random = new Random();  //没想到吧,Random支持直接生成随机数的流
    random
            .ints(-100, 100)   //生成-100~100之间的,随机int型数字(本质上是一个IntStream)
            .limit(10)   //只获取前10个数字(这是一个无限制的流,如果不加以限制,将会无限进行下去!)
            .filter(i -> i < 0)   //只保留小于0的数字
            .sorted()    //默认从小到大排序
            .forEach(System.out::println);   //依次打印
}

生成一个统计实例来帮助我们快速进行统计:

public static void main(String[] args) {
    Random random = new Random();  //Random是一个随机数工具类
    IntSummaryStatistics statistics = random
            .ints(0, 100)
            .limit(100)
            .summaryStatistics();    //获取语法统计实例
    System.out.println(statistics.getMax());  //快速获取最大值
    System.out.println(statistics.getCount());  //获取数量
    System.out.println(statistics.getAverage());   //获取平均值
}

对一个含有 “A,B” 的列表, 按照 ”,” 进行元素分割出新的列表:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("A,B");
    list.add("C,D");
    list.add("E,F");   //我们想让每一个元素通过,进行分割,变成独立的6个元素
    list = list
            .stream()    //生成流
            .flatMap(e -> Arrays.stream(e.split(",")))    //分割字符串并生成新的流
            .collect(Collectors.toList());   //汇成新的List
    System.out.println(list);   //得到结果
}

List计算所有数字的和:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    int sum = list
            .stream()
            .reduce((a, b) -> a + b)   //计算规则为:a是上一次计算的值,b是当前要计算的参数,这里是求和
            .get();    //我们发现得到的是一个Optional类实例,通过get方法返回得到的值
    System.out.println(sum);
}