一、概述
Stream API的概述:
- Stream API是Java 8新特性之一,定义在java.util.stream子包
- 在Java中使用Stream API可以高效的处理集合对象(针对集合进行复杂的过滤等操作)
- 可以让程序员写出高效、干净、简洁的代码
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操作的三个步骤:
- 第一步:
创建Stream流对象
(根据集合或数组就可以生成一个Stream对象)- 第二步:
一系列的中间操作
(将所有操作生成一个执行链
本身并不执行)- 第三步:
终止操作
(只有终止操作之后执行链
中的步骤才会执行)注意事项:
- 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来执行排序的顺序
步骤三:终止操作(将操作链
执行)
- 匹配与查找
- 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);
- 规约
- 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));
- 收集
- 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的效率会更高