一、概述

Stream API的概述:

  1. Stream API是Java 8新特性之一,定义在java.util.stream子包
  2. 在Java中使用Stream API可以高效的处理集合对象(针对集合进行复杂的过滤等操作)
  3. 可以让程序员写出高效、干净、简洁的代码

Stream API和Collection(集合)的关系:

  • Collection是容器,是用来存放数据的,重点在于数据存储
  • Stream则是针对集合元素提供便捷的计算,重点在于数据计算(对容器内元素排序、过滤等)
  • 集合面对的是内存,Stream则面对的是CPU

为什么要使用Stream API?

  • NOSQL只能通过JAVA层面过滤数据:因为现在获取数据的方式增多,不仅可以通过关系型数据库同时还可以通过非关系型数据库NoSQL(Redis)获取,如果在关系型数据库中需要对指定的数据进行过滤是可以使用where子句来过滤数据的,例如:select xxx from xxx where xxx。但是如果需要通过非关系型数据库NoSQL来获取数据,那么只能在Java层面过滤,而Stream可以针对集合进行高效的处理。
  • 性能优势:在Stream中提供了并行流parallelStream,在CPU多核情况下使用并行流来遍历会比for遍历的效率高(虽然for偏向底层效率更高,但是在并行情况下并行流效率可能更高)
  • 代码简洁易懂:Stream API真正的将函数式编程引入到Java中,可以将原先臃肿复杂的代码变得简洁易懂(同时依赖于Stream的延迟执行的特性)

Stream操作的三个步骤:

  1. 第一步:创建Stream流对象(根据集合或数组就可以生成一个Stream对象)
  2. 第二步:一系列的中间操作(将所有操作生成一个执行链本身并不执行)
  3. 第三步:终止操作(只有终止操作之后执行链中的步骤才会执行)

注意事项:

  • Stream本身并不存储对象,数据是存储在集合中的
  • Stream并不会改变源对象(也就是并不会改变原先集合中的数据)
  • 操作是延迟的,只有等到需要结束的时候(第三步终止操作),才会一次性将整个执行链的操作执行
  • Stream一旦执行了终止操作,就无法再进行终止操作或者中间操作了(如果需要只能重新获取新的流对象)

二、Stream操作的三个步骤

Stream的三个操作:① 创建Stream对象、② 执行中间操作、③终止操作

  • 中间操作执行完毕之后一定要执行终止操作,因为中间操作是’惰性执行’的过程
    (案例使用forEach(System.out::println)来保证终止操作)
  • 中间操作是并不是真正执行操作,而是生成操作链
  • 第三步"终止操作"时候才将整个"操作链"执行
步骤一:创建一个Stream
  • 创建方式一:根据集合Collection对象创建
List l1 = new ArrayList();
Stream s1 = l1.stream();
// 通过调用集合对象类的stream()方法
  • 创建方式二:根据集合使用Arrays工具类创建
IntStream s3 = Arrays.stream(new int[]{1, 2, 3, 4, 5, 6});
// 使用Arrays工具类的stream方法将数组作为参数传入即可得到
  • 创建方式三:使用Stream类的of()静态方法创建
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
// 使用Stream的of静态方法,传入目标元素即可
  • 创建方式四:通过创建无限流的方式实现(了解即可)
步骤二:中间操作(最终会形成一个操作链,并不执行)
1. 筛选与切片
  • filter(Predicate p)表示的是接收Lambda,用于从流中排除某些元素
List list = new ArrayList();
list.add(new Person("小明",22),new Person("小红",44));
list.stream().filter(person -> person.age() > 30).forEach(System.out::println);
//首先是使用list的stream床架流对象,之后调用filter,其中填入Lambda表达式判断年龄
//最后调用ForEach来执行终止操作一次触发执行链执行即可
  • limit(n)截断流,使其返回指定数量的元素
list.stream().limit(3).forEach(System.out::println);
  • skip(n),返回一个跳过指定数量元素的流
list.stream().skip(3).forEach(System.out::println)
  • distinct()去重操作,通过使用hashCode()和equals()去重复元素
list.stream().distinct().forEach(System.out::println);
//这里需要注意将对象所在的类的equals()和hashCode()方法进行重写操作
2. 映射
  • map(Function f)-接收一个函数作为参数,将元素转换为其他形式或者提取信息,该函数会应用到每个元素上,并将其映射为一个新的元素(也就是通过传入一个转换关系,将集合中的所有数据进行转换
//将集合中的的字符从小写转换为大写
List<java.lang.String> list = Arrays.asList("aa", "bb", "cc", "dd");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
//通过传入str -> str.toUpperCase()映射关系将所有字符进行映射
//映射结果就是AA BB CC DD
3. 排序
  • sorted():产生一个新的流,按照自然顺序进行排序
Arrays.asList(1,3,77,4,2).stream().sorted().forEach(System.out::println);
//通过stream()转换为流对象,之后直接使用sorted()进行自然排序
  • sorted(Comparator com):产生一个新的流,按照比较器的规则进行排序
list.sorted((e1,e2) -> e1.getAge() - e2.getAge()).forEach(System.out::println);
// 可以使用Lambda来执行排序的顺序
步骤三:终止操作(将操作链执行)
  1. 匹配与查找
  • allMatch(Predicate p)检查是否匹配所有元素
    可以没有任何中间操作直接使用allMatch(),返回的是布尔值,表示的所有元素是否符合要求
System.out.println(list.stream().allMatch(person -> person.getAge() > 18));
// 返回集合中是否所有的元素的age都是大于18
  • anyMatch(Predicate p)检查是否匹配其中一个,返回的也是布尔值
System.out.println(list.stream().anyMatch(person -> person.getAge() > 18));
// 返回集合中是否有任一的元素的age是大于18
  • findFirst()表示的是返回第一个元素,返回的结果是optional类型
System.out.println(list.stream().findFirst());
  • count()表示的统计个数
System.out.println(employees.stream().count());
  • max(Comparator c)返回最大值、min(Comparator c)返回最小值
employees.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
// 传入的就是比较规则
  • forEach(Consumer c)内部迭代
list.stream().forEach(System.out::println);
  1. 规约
  • reduce(T identity,BinaryOperator),其中第一个参数表示的是起始值
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(integers.stream().reduce(0, (x1, x2) -> x1 + x2));
  1. 收集
  • collect(Collector c),由于使用Stream并不会对元数据产生影响,使用该方法可以将得到的结果作为新的集合进行返回操作,需要填写参数是Collectors.xxx方法
List<Employee> collect = 
employees.stream().filter(e -> e.getAge() > 18).collect(Collectors.toList());

三、使用建议

  • 对于简单的遍历,那么可以直接使用for、iterator
  • 对于很多步骤处理的迭代逻辑,可以使用Stream使得代码简洁明了
  • 在数据量比较小的情况下,原生的for遍历效率还是最高的(但是可以牺牲一定的效率让增加程序的可读性)
  • 但是在大数据以及多核CPU并行流的加持下,Stream的效率会更高