stream 流,是jdk8新增的一种集合处理方式,可以将集合转换为流,进而对集合中的元素进行排序、过滤、聚合等⼀系列的操作。
//使用集合对象的.stream() 方法创建
Stream<User> stream = userList.stream();
//使用静态方法 Stream.of() 创建,参数个数不定
Stream<Integer> stream = Stream.of(1, 2, 3);
//空流,类似于空集合,不为null,只是没有存储元素
Stream<User> stream = Stream.of();
//jdk封装了一些常见类型的流,eg. IntStream、LongStream、DoubleStream
//这些流根据自身的元素类型,提供了一些额外的方法,eg. sum()、average()
IntStream intStream = IntStream.of(1, 2, 3);
int sum = intStream.sum();
double average = intStream.average().orElse(0.00);
将流中的元素映射为对应的值,可以实现流的类型转换。
map()、mapToXxx() 返回的也是 Stream
//收集指定字段的值,User => Long,可用使用lambda表达式,也可以直接写方法名
List<Long> userIdList1 = userList.stream()
//.map(user -> user.getUserId())
.map(User::getUserId)
.collect(Collectors.toList());
//转换数据表中的配置,String => Integer
String dayNumConfigStr = "1,3,7";
List<Integer> collect = Arrays.stream(dayNumConfigStr.split(","))
.map(Integer::valueOf)
.collect(Collectors.toList());
//做一些自定义处理,收集返回的元素
userList.stream()
.map(user -> {
String tel = user.getTel();
if (StringUtils.isNotBlank(tel) && tel.length() == 11) {
//手机号脱敏
tel = tel.replace(tel.substring(3, 7), "****");
//属性改变
user.setTel(tel);
}
//如果返回的就是原对象(引用相同),则后续需要collect()才会将元素的属性变化应用到流中对应的元素上,否则修改会被丢弃(无效)
//此时流中的元素已经是目标对象了,无需把collect()返回的新集合赋给userList
return user;
})
.collect(Collectors.toList());
mapToXxx() 可以直接映射为指定类型,得到指定类型的流。
相比于map(),mapToXxx() 还提供了该种类型的流的特有方法,比如数值型求和、平均数、最值
int maxDayCount = Arrays.stream(dayNumConfigStr.split(","))
//也可以写成lambda表达式 .mapToInt(dayCountStr -> Integer.valueOf(dayCountStr))
.mapToInt(Integer::valueOf)
.max()
.getAsInt();
将流中的元素映射为对应的流(列表)
//将 user 映射为对应的 List
List<List<Order>> userOrderList = userList.stream()
.flatMap(user -> Stream.of(user.getOrderList()))
.collect(Collectors.toList());
//将user映射为对应的orderList,再map映射为对应的orderIdList
List<Long> userOrderIdList = userList.stream()
//如果 user.getOrderList() 为null,会抛NPE,先判断一下
.flatMap(user -> user.getOrderList() != null ? user.getOrderList().stream().map(Order::getOrderId) : Stream.of())
//最后得到的是这些用户所有order的orderId列表
.collect(Collectors.toList());
//也可以多次使用 flatMap 进行映射
List<Long> userOrderIdList1 = userList.stream()
//流中的元素从user映射为对应的orderList
.flatMap(user -> Stream.of(user.getOrderList()))
//过滤掉空集合
.filter(orderList -> CollectionUtils.isNotEmpty(orderList))
//流中的元素从orderList转换为对应的orderIdList
.flatMap(orderList -> orderList.stream().map(Order::getOrderId))
.collect(Collectors.toList());
//统计这些用户所有订单的总金额
BigDecimal reduce = userList.stream()
.flatMap(user -> Stream.of(user.getOrderList()))
.filter(orderList -> CollectionUtils.isNotEmpty(orderList))
.flatMap(orderList -> orderList.stream().map(Order::getAmount))
.reduce(BigDecimal.ZERO, BigDecimal::add);
filter()用于筛选流中满足条件的元素,只保留满足条件的元素。
userList = userList.stream()
// 参数是函数式接口 Predicate,返回布尔值,true=>保留,false=>丢弃
.filter(user -> StringUtils.isNotBlank(user.getTel()))
.collect(Collectors.toList());
调用元素的 equals() 方法进行比较
userList = userList.stream().distinct().collect(Collectors.toList());
// Comparator.comparing() 第一个参数指定要比较的值,第二个参数指定排序方式
// Comparator.naturalOrder()自然排序、升序,Comparator.reverseOrder()降序
userList = userList.stream()
.sorted(Comparator.comparing(User::getAge, Comparator.naturalOrder()))
// .sorted(Comparator.comparing(user -> user.getAge(), Comparator.naturalOrder()))
.collect(Collectors.toList());
//第二个参数可以缺省,缺省时默认为自然排序、升序
userlist = userlist.stream()
.sorted(Comparator.comparing(User::getAge))
// .sorted(Comparator.comparing(user -> user.getAge()))
.collect(Collectors.toList());
// User::getAge 这种形式指定的字段,降序也可以写成:Comparator.comparing(升序).reversed()
userList = userList.stream()
.sorted(Comparator.comparing(User::getAge, Comparator.naturalOrder()).reversed())
.collect(Collectors.toList());
// User::getAge 这种方式只能指定排序字段,字段不能参与计算,lambda表达式指定的字段则可以参与运算,更加灵活
orderList = orderList.stream()
.sorted(Comparator.comparing(order -> order.getActualAmount()*0.8 + order.getTotalAmount()*0.2, Comparator.naturalOrder()))
.collect(Collectors.toList());
//支持多个排序字段
userList = userList.stream()
.sorted(Comparator.comparing(UserBo::getId, Comparator.reverseOrder())
.thenComparing(UserBo::getBirthday))
.collect(Collectors.toList());
//可以实现函数式接口 Comparator 自定义排序规则
userList = userList.stream()
// int compare(T o1, T o2),结果可以看成 前者-后者,负数=>小于,正数=>大于,0=>等于
.sorted((user1, user2) -> user1.getAge() - user2.getAge())
.collect(Collectors.toList());
都是在副本上操作,元集合不变
指定流中要保留的元素个数
List<User> resultList = userlist.stream()
//只保留2个元素
.limit(2)
.collect(Collectors.toList());
//skip+limit可以实现内存版的分页
List<User> resultList = userlist.stream()
//跳过指定数量的元素
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
.collect(Collectors.toList());
//获取流中的第一个元素
T t = stream.findFirst().get();
//从流中获取一个元素,通常是第一个元素,结果具有不确定性,可能返回流中的任何元素
T t = stream.findAny().get();
findFirst()、findAny() 返回 Optional
//参数都是断言接口Predicate
boolean b1 = userlist.stream().anyMatch(user -> StringUtils.isNotBlank(user.getUsername()));
boolean b2 = userlist.stream().allMatch(user -> StringUtils.isNotBlank(user.getUsername()));
max()、min()用于获取流中指定字段的值最大、最小的元素。
//lambda表达式的参数是2个元素,Integer.compare()的参数是要比较的2个值,实质是指定要比较的字段,元素、值要对应
Optional<User> max2 = userlist.stream().max((user1, user2) -> Integer.compare(user1.getId(), user2.getId()));
//可以简写如下
Optional<User> max1 = userlist.stream().max(Comparator.comparingInt(user -> user.getId()));
返回值是Optional类型,包含了整个元素。
逐步将相邻2个元素替换为1个元素,减少流中元素数量,直到流中只剩下一个元素,常用于求和、积、最值。
//未设置初始值时,reduce()返回Optional,直接get()没有值时会抛出异常,尽量用有默认值的方法代替
//可以直接写函数名
Integer sum2 = Stream.of(1, 2, 3).reduce(Integer::sum).orElse(0);
Integer max2 = Stream.of(1, 2, 3).reduce(Math::max).orElse(0);
//也可以写成lambda表达式
int sum1 = Stream.of(1, 2, 3).reduce((ele1, ele2) -> ele1 + ele2).orElse(0);
int max1 = Stream.of(1, 2, 3).reduce((ele1, ele2) -> Math.max(ele1, ele2)).orElse(0);
//可以设置一个初始值,相当于在流中的第一个元素之前暂时插入一个新元素参与计算
//设置初始值时,reduce()返回的是目标类型,
//经常设置0、1之类不影响计算结果的初始值,即使流中没有元素(空集合),也能作为默认结果返回
Integer sum4 = Stream.of(1, 2, 3).reduce(0, Integer::sum);
Integer max4 = Stream.of(1, 2, 3).reduce(0, Math::max);
int sum3 = Stream.of(1, 2, 3).reduce(0, (ele1, ele2) -> ele1 + ele2);
int max3 = Stream.of(1, 2, 3).reduce(0, (ele1, ele2) -> Math.max(ele1, ele2));
//常和map()搭配使用,map()映射某个数值字段,reduce()对该字段进行计算
//计算订单总金额
BigDecimal totalActualAmount = orderList.stream()
.map(Order::getActualAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
userlist.stream().forEach(user -> System.out.println(user));
forEach + lambda表达式 搭配使用的注意点
List<Integer> collect1 = Stream.of(1, 2, 3).collect(Collectors.toList());
Set<Integer> collect2 = Stream.of(1, 2, 3).collect(Collectors.toSet());
//可以指定目标集合类型
LinkedList<Integer> collect3 = Stream.of(1, 2, 3).collect(Collectors.toCollection(LinkedList::new));
CopyOnWriteArrayList<Integer> collect4 = Stream.of(1, 2, 3).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
如果元素是字符串,可以拼接(收集)元素到一个字符串中
//直接拼接,不使用分隔符(即使用空串作为分隔符)
String str1 = Stream.of("hello", "word").collect(Collectors.joining());
//可以指定分隔符
String str2 = Stream.of("hello", "word").collect(Collectors.joining(" "));
//可以指定分隔符、前缀、后缀
String str3 = Stream.of("hello", "word").collect(Collectors.joining(" ", "", "!"));
将满足条件(true)的分为一组,将不满足条件(false)的分为一组,2个key分别是true、false
Map<Boolean, List<Integer>> map1 = Stream.of(1, 2, 3, 4, 5).collect(Collectors.partitioningBy(ele -> ele > 3));
支持 Int、Long、Double 类型
//参数指定要聚合统计的字段、表达式值
IntSummaryStatistics intSummaryStatistics = Stream.of(1, 2, 3).collect(Collectors.summarizingInt(t -> t));
IntSummaryStatistics intSummaryStatistics1 = studentList.stream().collect(Collectors.summarizingInt(student -> student.getAge()));
// XxxSummaryStatistics 中已经封装好了各个统计指标,IntSummaryStatistics{count=3, sum=6, min=1, average=2.000000, max=3}
long count = intSummaryStatistics.getCount();
double average = intSummaryStatistics.getAverage();
int max = intSummaryStatistics.getMax();
int min = intSummaryStatistics.getMin();
long sum = intSummaryStatistics.getSum();
//按指定字段分组
//统计来自各省的学生
Map<String, List<Student>> map1 = studentList.stream()
.collect(Collectors.groupingBy(Student::getProvince));
//可以对各分组进一步处理,第一个参数指定分组字段,第二个参数指定 Collectors
//统计来自各省的学生人数
Map<String, Long> map2 = studentList.stream()
.collect(Collectors.groupingBy(Student::getProvince, Collectors.counting()));
//统计学生总分
Map<Long, Double> studentTotalScoreMap = studentScoreList.stream()
.collect(Collectors.groupingBy(StudentScore::getStudentId, Collectors.summingDouble(StudentScore::getScore)));
//先按年级分组,再按班级分组
Map<String, Map<String, List<Student>>> gradeClazzMap = studentList.stream()
.collect(Collectors.groupingBy(Student::getGrade, Collectors.groupingBy(Student::getClazz)));
//先按年级分组,再按学号映射
Map<String, Map<String, Student>> gradeNoMap = studentList.stream()
.collect(Collectors.groupingBy(Student::getGrade, Collectors.toMap(Student::getNo, Function.identity())));
//搭配 keySet() 也可实现指定字段的去重
Set<Long> userIdSet = orderList.stream()
.collect(Collectors.groupingBy(Order::getUserId))
.keySet();
以上 groupBy() 都有对应的 groupingByConcurrent() 方法,返回的是并发集合 ConcurrentMap。
Collectors 的 partitioningBy()、Collectors.groupingBy() 方法都可以将流转化为map,此外 toMap()、toConcurrentMap() 也可以将流转换为map。
// 第一个参数指定key,第二个参数指定value,第三个参数指定key出现重复时要保留的键值对(元素)
// 实现函数式接口 BiFunction,参数1是之前已存储到map中的元素,参数2是将要存储到map中、但又与参数1存在冲突|key重复的元素,返回要保留的元素
// 根据需要来,不一定只保留其中一个,比如value是String类型,可以返回 str1+","+str2 拼接的字符串
Map<Long, User> userMap = userList.stream()
.collect(Collectors.toMap(User::getUserId, Function.identity(), (user1, user2) -> user2));
//如果不指定第三个参数,key出现重复时会直接抛出异常,不推荐此种方式
Map<Long, User> userMap = userList.stream()
.collect(Collectors.toMap(User::getUserId, Function.identity()));
Function.identity()
是返回参数自身,也可换成lambda表达式 t -> t
也可以理解为:将元素收集到数组中
//直接 toArray() 或 .map().toArray(),得到的都是 Object[]
Object[] objectArr1 = Arrays.stream(dayNumConfigStr.split(","))
.toArray();
Object[] objectArr2 = Arrays.stream(dayNumConfigStr.split(","))
.map(Integer::valueOf)
.toArray();
//可以 mapToXxx().toArray(),得到指定类型的数组
int[] dayCountArr = Arrays.stream(dayNumConfigStr.split(","))
.mapToInt(Integer::valueOf)
.toArray();
//以下2种方式构建的都是串行流
Stream<Integer> stream1 = Stream.of(1, 2);
Stream<User> stream2 = userlist.stream();
//以下2种方式构建的是并行流。调用parallel()方法,可以将串行流转换为并行流
Stream<Integer> parallelStream1 = Stream.of(1, 2).parallel();
Stream<User> parallelStream2 = userlist.parallelStream();
并行流的使用方式和串行流相同,只是串行流是单线程执行流操作,并行流使用多线程执行流操作。
涉及多线程时,注意2点