Java Stream流式处理

目录

      • stream的创建
      • map()、mapToXxx() 映射
      • flatMap() 流的展开、平铺
      • filter() 元素过滤
      • distinct() 去重
      • sorted() 排序
      • skip()跳过元素、limit() 限制数量
      • findFirst、findAny 获取一个元素
      • allMatch()、anyMatch() 整体判断
      • max()、min() 求极值
      • reduce() 归并
      • foreach() 遍历元素
      • collect()、Collectors 收集元素到指定容器中
        • Collectors.toList()、toSet()、toCollection() 收集到单例集合中
        • Collectors.joining() 字符串连接
        • Collectors.partitioningBy() 分区收集
        • Collectors.summarizingXxx() 聚合统计数值字段的多个指标
        • Collectors.groupingBy() 分组
        • toMap()、toConcurrentMap() 收集到map中
      • toArray() 将流转换为数组
      • paralleStream 并行流
      • 注意点

 

stream 流,是jdk8新增的一种集合处理方式,可以将集合转换为流,进而对集合中的元素进行排序、过滤、聚合等⼀系列的操作。
 

stream的创建

//使用集合对象的.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() 映射

将流中的元素映射为对应的值,可以实现流的类型转换。

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();

 

flatMap() 流的展开、平铺

将流中的元素映射为对应的流(列表)

//将 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() 元素过滤

filter()用于筛选流中满足条件的元素,只保留满足条件的元素。

userList = userList.stream()
        // 参数是函数式接口 Predicate,返回布尔值,true=>保留,false=>丢弃
        .filter(user -> StringUtils.isNotBlank(user.getTel()))
        .collect(Collectors.toList());

 

distinct() 去重

调用元素的 equals() 方法进行比较

userList = userList.stream().distinct().collect(Collectors.toList());

 

sorted() 排序

// 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());

都是在副本上操作,元集合不变

 

skip()跳过元素、limit() 限制数量

指定流中要保留的元素个数

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());

 

findFirst、findAny 获取一个元素

//获取流中的第一个元素
T t = stream.findFirst().get();

//从流中获取一个元素,通常是第一个元素,结果具有不确定性,可能返回流中的任何元素
T t = stream.findAny().get();

findFirst()、findAny() 返回 Optional,但选取的元素是null时,这2个方法会直接抛出NPE,后续也就没必要用 Optional 的 orElse()、orElseGet() 来设置为 null 时的默认值。

 

allMatch()、anyMatch() 整体判断

  • allMatch():流中所有元素是否都满足指定条件
  • anyMatch():流中是否有元素满足指定条件(有一个元素满足即可)
//参数都是断言接口Predicate
boolean b1 = userlist.stream().anyMatch(user -> StringUtils.isNotBlank(user.getUsername()));
boolean b2 = userlist.stream().allMatch(user -> StringUtils.isNotBlank(user.getUsername()));

 

max()、min() 求极值

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类型,包含了整个元素。

 

reduce() 归并

逐步将相邻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);

 

foreach() 遍历元素

userlist.stream().forEach(user -> System.out.println(user));

forEach + lambda表达式 搭配使用的注意点

  • lambda表达式 方法体的代码是在匿名函数中,包了一层、不是直接在循环体中,所以lambda表达式方法体中不能使用 break、continue。
  • lambda中使用 return 时,只是终止 lambda 匿名函数的本次调用,即中止循环中的当前批次、类似于continue,并非中止整个循环。
  • 遍历时,不要在循环体中删除当前遍历的集合|stream|arr中的元素,增删元素会导致遍历出错;也不要通过 ele=xxx 的方式直接修改整个元素,因为只是临时变量,这种修改方式对实际元素并没有影响。

 

collect()、Collectors 收集元素到指定容器中

Collectors.toList()、toSet()、toCollection() 收集到单例集合中
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));

 

Collectors.joining() 字符串连接

如果元素是字符串,可以拼接(收集)元素到一个字符串中

//直接拼接,不使用分隔符(即使用空串作为分隔符)
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(" ", "", "!"));
Collectors.partitioningBy() 分区收集

将满足条件(true)的分为一组,将不满足条件(false)的分为一组,2个key分别是true、false

Map<Boolean, List<Integer>> map1 = Stream.of(1, 2, 3, 4, 5).collect(Collectors.partitioningBy(ele -> ele > 3));

 

Collectors.summarizingXxx() 聚合统计数值字段的多个指标

支持 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();

 

Collectors.groupingBy() 分组
//按指定字段分组

//统计来自各省的学生
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。

 

toMap()、toConcurrentMap() 收集到map中

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
  • toConcurrentMap() 的用法与toMap()相同,只不过返回的是并发集合。

 

toArray() 将流转换为数组

也可以理解为:将元素收集到数组中

//直接 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();

 

paralleStream 并行流

//以下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点

  • parallelStream之类的多线程操作,适用于数据量较大的场景,数据量较小时,由于频繁切换线程上下文,效果可能还不如单线程。
  • 使用多线程时一定要注意线程安全问题,对于线程共享资源,该使用线程安全类的使用线程安全类,该加锁的加锁。

 

注意点

  • 数组、集合、stream的过滤、删除之类减少元素的操作,一定要考虑操作后元素数量是否为0,如果操作后变成了空流、空集合、空数组,对后续操作会不会有影响。
  • 数组、集合、stream中允许存在为null的元素,使用元素的成员时,需要考虑元素是否可能为null,避免出现NPE。
  • 集合、stream的很多操作,如果集合、stream自身不为null,即使流中没有元素,也是ok的、不会出现异常。

你可能感兴趣的:(Java,SE,stream,集合处理,流式处理,collect,Collectors)