背景
在接手的项目中,很多地方使用了java8的stream流对集合进行处理,由于这是我第一次接触,一开始都看不太懂代码含义,学习之后发现流非常好用。在后台开发过程中,很多时候都涉及到对List和Map内部数据的处理,常规写法都要各种for循环,不仅写起来麻烦,可读性也较差。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。极大提高了生产力,让我们写出更高效、干净、简洁的代码。
Stream操作之所以简洁高效,是因为具有两个基础的特征:
1、Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格。 这样做可以对操作进行优化。
2、内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,无须在代码中显示使用循环。
下面通过阅读jdk源码关于流的注释整理一下其基本用法。
前置基础:函数式接口
有且只有一个抽象方法的接口,即适用于Lambda表达式使用的接口。接口使用@FunctionalInterface注解标注。
函数式接口常用的地方就是作为函数的参数。由于只有一个方法,所以我们可以在把这个接口作为参数的方法被调用的时候再定义函数的内容。
在java.util.function包里定义了许多常见的函数式接口,但基础类型的接口只有几种,其他都是在基础类型上演变出来的。
Interface Consumer
该接口包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据并且不返回结果。
Interface Function
该接口包含抽象方法 R apply(T t); 接受一个T类型的入参返回一个R类型的结果。
Interface Predicate
该接口包含抽象方法boolean test(T t);接受一个T类型的入参,如果入参和断言器匹配返回true,否则返回false
interface Supplier
该接口包含抽象方法T get();无入参,每次调用返回一个全新的T类型的结果。
interface BinaryOperator
operator是一种特殊的Function,它的入参和出参为相同的类型T
。
集合与流之间的相互转换
集合->流:
Collection接口中提供了stream()和parallelStream()两种方法将当前集合转换为流。
使用并行流处理集合中的元素,会使用流创建的默认线程池去并发处理,线程池的线程数为机器的CPU核心数。
需要注意的是,在同一个项目中多处使用parallelStream都是使用同一个默认线程池去处理集合,如果某一个并发流处理一直占用线程池中的线程,其他地方的并发流都会阻塞
流->集合:
调用Stream的collect方法,入参传入Collectors的toMap,toSet,toList或toCollection将流转换为相应的集合。
Stream常用操作
filter过滤
Stream
传入一个断言类lamda表达式,用于对流中元素进行过滤,匹配的元素保留下来(元素传入表达式返回true)
map映射
Stream
传入一个函数类lamda表达式,原本流中类型为T的元素经过表达式映射成R类型。该方法常用于DO,DTO,VO集合之间的转换
flatmap扁平化映射
Stream
flatMap可以将一个集合流展平为集合中的元素流。用于将多个集合合并成一个集合。
函数类lamda表达式入参类型T可以是集合也可以是包含集合的实体类,出参为展平的元素流。
Demo如下:
public class JavaStreamFlatMapAggregateExample {
public static void main(String[] args) {
State karnataka = new State();
karnataka.addCity("Bangalore");
karnataka.addCity("Mysore");
State punjab = new State();
punjab.addCity("Chandigarh");
punjab.addCity("Ludhiana");
List
allStates = Arrays.asList(karnataka, punjab); //Java Stream flatMap way
List
allCities = allStates.stream().flatMap(e -> e.getCities().stream()).collect(Collectors.toList()); System.out.println(allCities);
//legacy way
allCities = new ArrayList
(); for(State state : allStates) {
for(String city : state.getCities())
allCities.add(city);
}
System.out.println(allCities);
}
}
class State {
private List
cities = new ArrayList<>(); public void addCity(String city) {
cities.add(city);
}
public List
getCities() { return this.cities;
}
}
distinct去重
Stream
将流冲重复元素去重
sorted排序
Stream
传入比较器,将流中元素进行排序,也可以不传入参,按照元素compareTo方法进行排序。
peek观察
Stream
对流中每个元素调用传入的消费类lamda表达式,不改变元素的值,因此返回的流和之前的流中元素相同。该接口主要用于调试,例如可以在表达式中将流中元素的值打印出来。
limit截断
Stream
返回原始流中前maxSize个元素组成的新的流
skip跳过
Stream
丢弃前n个元素。返回剩下的元素,如果流中元素少于n,将返回一个空的流。
forEach遍历
void forEach(Consumer super T> action);
对于流中每个元素执行action操作,该接口与peek接口功能类似,只不过是个最终的操作,不再返回流。
reduction
Optional
传入一个运算器,将流中每个元素累积执行运算器,最终返回单个结果,如将元素每个值累加。
补充:Optional是一个容器,里面的值可能为空或不为空,如果容器中存在值,isPresent方法会返回true并且get方法会返回该值
min,max,count
Optional
Optional
long count();
根据传入的比较器,返回流中的最小值,最大值。返回流中的元素个数,以上三个方法都是一种特殊的reduction
match匹配
boolean anyMatch(Predicate super T> predicate);
boolean allMatch(Predicate predicate);
boolean noneMatch(Predicate predicate);
传入一个断言类lamda表达式,流中任意元素/所有元素/没有元素 满足断言(传入表达式返回true),则返回true。