对Java Stream流api进行一些实用的封装
在某些业务场景中, 我们经常会对集合中的数据进行处理, Java8 提供了 Stream API 可以让我们很方便地处理集合中的数据, 例如, 分组, 过滤, 排序等, 但是每次处理数据的时候都要开流去处理, 所以我们可以对stream api进行一些简单的封装, 这样的话一些通用场景下就不需要写重复的逻辑了.
功能介绍
经过一阵子的努力, 我简单的封装了一个工具类, 目前工具类中功能大概有以下几个:
- List分组, 可以整合分组后的结果, 例如根据dept对user进行分组后, value整合为user的分数之类的.
- List排序, 单字段 / 多字段排序.
- List根据字段寻找最小值 / 最大值 元素.
- List中某个对象的值合并, 例如求某个字段的总和 / 平均值
- List某个字段统计数据获取(最小值, 最大值, 平均值, 总和等)
- List转Map
- Map的排序, 可以根据key / value进行排序
- List转换为其他集合, 数组转换为集合.
- List根据某个字段去重.
List分组
根据字段简单分组
首先先准备两个函数, 用于判空, 一切都是从这两个函数开始的.
/**
* list判空
*
* @param list 集合
*/
private static void notEmptyCondition(List<?> list) {
if (list == null || list.size() == 0) throw new NullPointerException("这就有点不讲武德了, 给我个空我怎么处理呢.");
}
/**
* map 判空
*
* @param map map集合
*/
private static void notEmptyCondition(Map<?, ?> map) {
if (map == null || map.entrySet().size() == 0) throw new NullPointerException("这就有点不讲武德了, 给我个空我怎么处理呢.");
}
接着准备分组函数
/**
* 简单分组
*
* @param list 集合
* @param classifier 分组字段选择函数
* @param <T> 输入类型
* @param <K> Key的类型
* @return 最后返回Map<K, List < T>
*/
public static <T, K> Map<K, List<T>> simpleGroupingBy(List<T> list, Function<? super T, ? extends K> classifier) {
notEmptyCondition(list);
return list.stream().collect(Collectors.groupingBy(classifier));
}
对分组后的结果进行排序
- 首先准备排序函数
/**
* 基本排序操作, 这里约定了排序字段的选择必须是实现了Comparable接口的.
*
* @param list 集合
* @param desc 是否降序
* @param keySelect 排序字段选择函数
* @param <T> 输入类型
*/
private static <T, U extends Comparable<U>> void sortList
(
List<T> list, boolean desc, Function<? super T, ? extends U> keySelect
) {
// 这里如果是降序的话, 就将List进行反转
if (desc) {
list.sort(Comparator.comparing(keySelect));
Collections.reverse(list); //反转List
} else {
list.sort(Comparator.comparing(keySelect));
}
}
- 分组函数, 和上一个没什么区别, 就是多了个排序.
/**
* 简单分组 + 排序字段的选择
*
* @param list 集合
* @param classifier 分组字段
* @param keySelect 排序字段选择
* @param desc 是否降序
* @param <T> 输入类型
* @param <K> key的类型
* @return 最后返回Map<K, List < T> List为排序之后的List
*/
public static <T, K, C extends Comparable<C>> Map<K, List<T>> simpleGroupingBy
(
List<T> list, Function<? super T, ? extends K> classifier, boolean desc,
Function<? super T, ? extends C> keySelect
) {
notEmptyCondition(list);
sortList(list, desc, keySelect);
return list.stream().collect(Collectors.groupingBy(classifier));
}
简单分组测试
public static void main(String[] args) {
ArrayList<TestUser> list = new ArrayList<TestUser>() {
{
add(new TestUser("用户11111", 1L, 120.0, 11, 20L));
add(new TestUser("用户2", 1L, 110.0, 12, 20L));
add(new TestUser("用户3", 2L, 130.0, 13, 200L));
add(new TestUser("用户4", 2L, 150.0, 14, 20L));
}};
//根据TestUser的DeptId进行分组, 这里返回的Key为DeptId, value为List<TestUser>
Map<Long, List<TestUser>> result = FunctionalUtil.simpleGroupingBy(list, TestUser::getDeptId);
System.err.println("result = " + result);
//根据TestUser的DeptId进行分组, 并对List进行排序, 排序字段为TestUser的winningCount字段
Map<Long, List<TestUser>> result2 = FunctionalUtil.simpleGroupingBy(list, TestUser::getDeptId, true, TestUser::getWinningCount);
System.err.println("result2 = " + result2);
}
控制台打印的结果:
result = {1=[TestUser{username='用户11111', deptId=1, score=120.0, count=11, winningCount=20}, TestUser{username='用户2', deptId=1, score=110.0, count=12, winningCount=20}], 2=[TestUser{username='用户3', deptId=2, score=130.0, count=13, winningCount=200}, TestUser{username='用户4', deptId=2, score=150.0, count=14, winningCount=20}]}
result2 = {1=[TestUser{username='用户2', deptId=1, score=110.0, count=12, winningCount=20}, TestUser{username='用户11111', deptId=1, score=120.0, count=11, winningCount=20}], 2=[TestUser{username='用户3', deptId=2, score=130.0, count=13, winningCount=200}, TestUser{username='用户4', deptId=2, score=150.0, count=14, winningCount=20}]}
选择返回字段分组
在分组后从对象中选取字段进行返回
/**
* 可选返回字段函数式分组
*
* @param list 列表
* @param classifier 分组字段选择
* @param mappingFunction 返回字段
* @param <T> 输入类型
* @param <K> key类型
* @param <U> 最后返回的元素类型
* @return 最后返回Map<K, List < U>
*/
public static <T, K, U> Map<K, List<U>> customizingFieldGroupingBy
(
List<T> list, Function<? super T, ? extends K> classifier,
Function<? super T, ? extends U> mappingFunction
) {
notEmptyCondition(list);
return list.stream()
.collect(Collectors.groupingBy(classifier, Collectors.mapping(mappingFunction, Collectors.toList())));
}
排序结果: 和上面用的排序函数一致.
/**
* 可选返回字段函数式分组 + 排序字段选择
*
* @param list 列表
* @param classifier 分组字段选择
* @param mappingFunction 返回字段
* @param keySelect 排序字段选择
* @param desc 是否降序
* @param <T> 输入类型
* @param <K> key类型
* @param <U> 最后返回的元素类型
* @return 最后返回Map<K, List < U>
*/
public static <T, K, U, C extends Comparable<C>> Map<K, List<U>> customizingFieldGroupingBy
(
List<T> list, Function<? super T, ? extends K> classifier,
Function<? super T, ? extends U> mappingFunction, boolean desc,
Function<? super T, ? extends C> keySelect
) {
notEmptyCondition(list);
sortList(list, desc, keySelect);
return list.stream()
.collect(Collectors.groupingBy(classifier, Collectors.mapping(mappingFunction, Collectors.toList())));
}
选择返回字段分组测试
public static void main(String[] args) {
ArrayList<TestUser> list = new ArrayList<TestUser>() {
{
add(new TestUser("用户11111", 1L, 120.0, 11, 20L));
add(new TestUser("用户2", 1L, 110.0, 12, 20L));
add(new TestUser("用户3", 2L, 130.0, 13, 200L));
add(new TestUser("用户4", 2L, 150.0, 14, 20L));
}};
//根据deptId字段进行分组, 选择score字段进行返回
Map<Long, List<Double>> resultMap = FunctionalUtil.customizingFieldGroupingBy(list, TestUser::getDeptId, TestUser::getScore);
System.err.println("resultMap = " + resultMap);
//根据deptId字段进行分组, 选择score字段进行返回, 按照score降序进行排序
Map<Long, List<Double>> resultMap2 = FunctionalUtil.customizingFieldGroupingBy(list, TestUser::getDeptId, TestUser::getScore, true, TestUser::getScore);
System.err.println("resultMap2 = " + resultMap2);
}
控制台打印的结果:
resultMap = {1=[120.0, 110.0], 2=[130.0, 150.0]}
resultMap2 = {1=[120.0, 110.0], 2=[150.0, 130.0]}
合并结果集分组
最后返回Map<K, Double>, 将对象的某个值进行合并操作, 求和/求平均值操作.
- 准备枚举MergingOperation
/**
* @author nathan
* @date 3/23/2021 9:34 AM
* @description: 合并操作符
*/
public enum MergingOperation {
// 相加 求平均值
SUMMING, AVERAGING
}
- 函数
/**
* 分组后合并操作
*
* @param list 集合List
* @param classifier 分组字段
* @param mergingClassifier 合并字段选择, 支持多字段选择, 限制只能是数字 Integer/Long/Double
* @param <T> 输入类型
* @param <K> key类型
* @return Map<Long, ? extends Number>
*/