Java 8 简化 JDK 工具 JDFrame 框架:轻松使用 jdk8 自带的 stream 流式处理函数

343 阅读14分钟

🙏废话不多说系列,直接开整🙏

cat010.png

一、背景

    官方介绍:burukeyou的私房工具, 由于经常记不住stream的一些api每次要复制来复制去,想要更加语意化的api;于是想到了以前写大数据Spark pandnas 等DataFrame模型时的api, 然后发现其实也存在java的JVM层的DataFrame模型比如 tablesaw,joinery 但是他们得硬编码去指定字段名,这对于有代码洁癖的burukeyou实在难以忍受,而且我只是简单统计下数据,我想在一些场景下能不能使用匿名函数去指定的字段处理去处理,于是便有了这个一个jvm层级的仿DataFrame工具,语意化和简化java8的stream流式处理工具;

    简要描述-关键字:① 简化 JDK8 自带的 stream 流式处理接口;② 基于 JVM 的 仿照 DataFrame 工具。

二、快速开始

(1)引入 JDFrame 框架 maven 依赖
<!-- [核心] JDFrame 框架 -->
<dependency>
    <groupId>io.github.burukeyou</groupId>
    <artifactId>jdframe</artifactId>
    <version>0.0.2</version>
</dependency>

<!-- 简化对象 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
(2)相关案例
// 获取学生年龄在9到16岁的学学校合计分数最高的前10名的学校
SDFrame<FI2<String, BigDecimal>> sdf2 = SDFrame.read(studentList)
    .whereNotNull(Student::getAge)
    .whereBetween(Student::getAge,9,16)
    .groupBySum(Student::getSchool, Student::getScore)
    .sortDesc(FI2::getC2)
    .cutFirst(10);
(3)完整案例(亲测有效,一目了然不骗人)
【1】数据对象DO准备
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private Integer id;
    private String name;
    private String school;
    private String level;
    private Integer age;
    private BigDecimal score;

    private Integer rank;

    public Student(String level, BigDecimal score) {
        this.level = level;
        this.score = score;
    }

    public Student(Integer id, String name, String school, String level, Integer age, BigDecimal score) {
        this.id = id;
        this.name = name;
        this.school = school;
        this.level = level;
        this.age = age;
        this.score = score;
    }
}
【2】筛选
@Test
void findDemo() {
    System.out.println("筛选相关:(可自行调整筛选输出条件测试哈)");
    SDFrame<Student> data = SDFrame.read(studentList)
        //.whereBetween(Student::getAge, 11, 15) // 过滤年龄在[11,15]岁的
        //.whereBetweenR(Student::getAge, 11, 15) // 过滤年龄在(11,15]岁的, 不含11岁
        //.whereBetweenL(Student::getAge, 11, 15)      // 过滤年龄在[11,15)岁的, 不含15岁
        //.whereNotNull(Student::getName) // 过滤名字不为空的数据, 兼容了空字符串''的判断
        //.whereGt(Student::getAge, 12)    // 过滤年龄大于12岁
        .whereGe(Student::getAge, 14)   // 过滤年龄大于等于14岁
        //.whereLt(Student::getAge, 12)  // 过滤年龄小于12岁的
        //.whereIn(Student::getAge, Arrays.asList(3, 11, 14)) // 过滤年龄为3岁 或者11岁 或者 14岁的数据
        //.whereNotIn(Student::getAge, Arrays.asList(3, 11, 14)) // 过滤年龄不为为3岁 或者11岁 或者 14岁的数据
        //.whereEq(Student::getAge, 11) // 过滤年龄等于11岁的数据
        //.whereNotEq(Student::getAge, 11) // 过滤年龄不等于11岁的数据
        //.whereLike(Student::getName, "jay") // 模糊查询,等价于 like "%jay%"
        //.whereLikeLeft(Student::getName, "jay") // 模糊查询,等价于 like "jay%"
        //.whereLikeRight(Student::getName, "jay") // 模糊查询,等价于 like "%jay"
        ;
    // 输出格式1:按照 JDK 的形式输出
    data.forEach(System.out::println);
    // 输出格式2:按照 JDFrame 的形式(数据库表)输出
    //data.show();
    // 输出格式2.1 :输出指定需要的数据
    data.show(1);// 输出从0~1行数据 data.show(0) 表示输出 列字段名
}

测试结果:

筛选相关:(可自行调整筛选输出条件测试哈)
Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=null)
Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null)
Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=null)
id	name	school	level	age	score	rank	
5 	d   	二中    	一年级  	14 	4    
【3】统计
@Test
void analysis() {
    JDFrame<Student> frame = JDFrame.read(studentList);
    System.out.println("获取年龄最大的学生: " + frame.max(Student::getAge));
    System.out.println("获取学生里最大的年龄: " + frame.maxValue(Student::getAge));
    System.out.println("获取年龄最小的学生: " + frame.min(Student::getAge));
    System.out.println("获取学生里最小的年龄: " + frame.minValue(Student::getAge));
    System.out.println("获取所有学生的年龄的平均值: " + frame.avg(Student::getAge)); // 获取所有学生的年龄的平均值
    System.out.println("获取所有学生的年龄合计: " + frame.sum(Student::getAge)); // 获取所有学生的年龄合计
    System.out.println("同时获取年龄最大和最小的学生: " + frame.maxMin(Student::getAge)); // 同时获取年龄最大和最小的学生
    System.out.println("同时获取学生里最大和最小的年龄: " + frame.maxMinValue(Student::getAge)); // 同时获取学生里最大和最小的年龄
}
【4】去重

原生steam只支持对象去重,不支持按特定字段去重:

@Test
void distinctDemo() {
    List<Student> std;
    std = SDFrame.read(studentList).distinct().toLists(); // 根据对象hashCode去重
    System.out.println("根据对象hashCode去重: " + std);
    std = SDFrame.read(studentList).distinct(Student::getSchool).toLists(); // 根据学校名去重
    System.out.println("根据学校名去重: " + std);
    std = SDFrame.read(studentList).distinct(e -> e.getSchool() + e.getLevel()).toLists(); // 根据学校名拼接级别去重复
    System.out.println("根据学校名拼接级别去重复: " + std);
    std =SDFrame.read(studentList).distinct(Student::getSchool).distinct(Student::getLevel).toLists(); // 先根据学校名去除重复再根据级别去除重复
    System.out.println("先根据学校名去除重复再根据级别去除重复: " + std);
}

结果如下:

根据对象hashCode去重: [Student(id=1, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=2, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=null), Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=null), Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=null), Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null), Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=null)]
根据学校名去重: [Student(id=1, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null), Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=null)]
根据学校名拼接级别去重复: [Student(id=1, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=null), Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null), Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=null)]
先根据学校名去除重复再根据级别去除重复: [Student(id=1, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null)]
【5】简单分组聚合

类似sql的 group by语义 简化处理分组和聚合的逻辑, 如果用原生stream需要写可能一大串逻辑.

@Test
void simpleGroup() {
    JDFrame<Student> frame = JDFrame.read(studentList);
    // 等价于 select school,sum(age) ... group by school
    List<FI2<String, BigDecimal>> a = frame.groupBySum(Student::getSchool, Student::getAge).toLists();
    a.forEach(item -> System.out.println("a 学校:" + item.getC1() + "\t年龄:" + item.getC2()));

    // 等价于 select school,max(age) ... group by school
    List<FI2<String, Integer>> a2 = frame.groupByMaxValue(Student::getSchool, Student::getAge).toLists();
    a2.forEach(item -> System.out.println("a2学校:" + item.getC1() + "\t最大年龄:" + item.getC2()));

    //  与 groupByMaxValue 含义一致,只是返回的是最大的值对象
    List<FI2<String, Student>> a3 = frame.groupByMax(Student::getSchool, Student::getAge).toLists();
    a3.forEach(item -> System.out.println("a3学校:" + item.getC1() + "\t最大年龄的学生信息:" + item.getC2()));

    // 等价于 select school,min(age) ... group by school
    List<FI2<String, Integer>> a4 = frame.groupByMinValue(Student::getSchool, Student::getAge).toLists();
    a4.forEach(item -> System.out.println("a4学校:" + item.getC1() + "\t最小年龄:" + item.getC2()));

    // 等价于 select school,count(*) ... group by school
    List<FI2<String, Long>> a5 = frame.groupByCount(Student::getSchool).toLists();
    a5.forEach(item -> System.out.println("a5学校:" + item.getC1() + "\t学生人数:" + item.getC2()));

    // 等价于 select school,avg(age) ... group by school
    List<FI2<String, BigDecimal>> a6 = frame.groupByAvg(Student::getSchool, Student::getAge).toLists();
    a6.forEach(item -> System.out.println("a6学校:" + item.getC1() + "\t学生平均年龄:" + item.getC2()));

    // 等价于 select school,sum(age),count(age) group by school
    List<FI3<String, BigDecimal, Long>> a7 = frame.groupBySumCount(Student::getSchool, Student::getAge).toLists();
    a7.forEach(item -> System.out.println("a7学校:" + item.getC1() + "\t年龄总和:" + item.getC2() + "\t统计年龄人数: " + item.getC3()));

    // (二级分组)等价于 select school,level,sum(age),count(age) group by school,level
    List<FI3<String, String, BigDecimal>> a8 = frame.groupBySum(Student::getSchool, Student::getLevel, Student::getAge).toLists();
    a8.forEach(item -> System.out.println("a8[二级分组]学校:" + item.getC1() + "\t年级:" + item.getC2() + "\t统计此组年龄总和: " + item.getC3()));

    // (三级分组)等价于 select school,level,name,sum(age),count(age) group by school,level,name
    List<FI4<String, String, String, BigDecimal>> a9 = frame.groupBySum(Student::getSchool, Student::getLevel, Student::getName, Student::getAge).toLists();
    a9.forEach(item -> System.out.println("a9[三级分组]学校:" + item.getC1() + "\t年级:" + item.getC2() + "\t名称: " + item.getC3() + "\t此组年龄总和: " + item.getC4()));

}

结果展示:

a 学校:一中	年龄:34
a 学校:二中	年龄:27
a 学校:三中	年龄:29
a2学校:一中	最大年龄:12
a2学校:二中	最大年龄:14
a2学校:三中	最大年龄:15
a3学校:一中	最大年龄的学生信息:Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=null)
a3学校:二中	最大年龄的学生信息:Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=null)
a3学校:三中	最大年龄的学生信息:Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=null)
a4学校:一中	最小年龄:11
a4学校:二中	最小年龄:13
a4学校:三中	最小年龄:14
a5学校:一中	学生人数:3
a5学校:二中	学生人数:2
a5学校:三中	学生人数:2
a6学校:一中	学生平均年龄:11.33
a6学校:二中	学生平均年龄:13.50
a6学校:三中	学生平均年龄:14.50
a7学校:一中	年龄总和:34	统计年龄人数: 3
a7学校:二中	年龄总和:27	统计年龄人数: 2
a7学校:三中	年龄总和:29	统计年龄人数: 2
a8[二级分组]学校:一中	年级:一年级	统计此组年龄总和: 22
a8[二级分组]学校:一中	年级:三年级	统计此组年龄总和: 12
a8[二级分组]学校:二中	年级:一年级	统计此组年龄总和: 27
a8[二级分组]学校:三中	年级:二年级	统计此组年龄总和: 29
a9[三级分组]学校:一中	年级:一年级	名称: a	此组年龄总和: 22
a9[三级分组]学校:一中	年级:三年级	名称: jay	此组年龄总和: 12
a9[三级分组]学校:二中	年级:一年级	名称: c	此组年龄总和: 13
a9[三级分组]学校:二中	年级:一年级	名称: d	此组年龄总和: 14
a9[三级分组]学校:三中	年级:二年级	名称: e	此组年龄总和: 29
【6】排序相关

简化原生stream的排序方式,直接指定字段即可,不用使用Comparator还要去关注升序还是降序

⚠特别注意⚠:① 不支持中文排序;② 多字段排序失败,指挥按照最后一个排序写法进行排序;

@Test
void orderList() {
    // 注意:中文不支持排序。

    // 等价于 order by age desc
    System.out.println("按照年龄降序:");
    SDFrame.read(studentList).sortDesc(Student::getAge).show();

    //  等价于 order by age desc, level asc
    System.out.println("按照年龄降序-年级升序:【JDFrame严重BUG】不支持多字段排序,只识别最后一个字段的sortXXX(YYYY)");
    SDFrame.read(studentList).sortDesc(Student::getAge).sortAsc(Student::getLevel).show();
    //SDFrame.read(studentList).sortDesc(Student::getAge).sortAsc(Student::getScore).show();

    // 等价于 order by age asc
    System.out.println("按照年龄升序:");
    SDFrame.read(studentList).sortAsc(Student::getAge).show();

    // 使用Comparator 排序
    System.out.println("按照年纪升序:(比较器)");
    SDFrame.read(studentList).sortAsc(Comparator.comparing(Student::getAge)).show();

}

测试结果:

按照年龄降序:
id	name	school	level	age	score	rank	
7 	e   	三中    	二年级  	15 	5    	    	
5 	d   	二中    	一年级  	14 	4    	    	
6 	e   	三中    	二年级  	14 	5    	    	
4 	c   	二中    	一年级  	13 	3    	    	
3 	jay 	一中    	三年级  	12 	2    	    	
1 	a   	一中    	三年级  	11 	1    	    	
2 	a   	一中    	一年级  	11 	1    	    	

按照年龄降序-年级升序:【JDFrame严重BUG】不支持多字段排序,只识别最后一个字段的sortXXX(YYYY)
id	name	school	level	age	score	rank	
5 	d   	二中    	一年级  	14 	4    	    	
4 	c   	二中    	一年级  	13 	3    	    	
2 	a   	一中    	一年级  	11 	1    	    	
3 	jay 	一中    	三年级  	12 	2    	    	
1 	a   	一中    	三年级  	11 	1    	    	
7 	e   	三中    	二年级  	15 	5    	    	
6 	e   	三中    	二年级  	14 	5    	    	

按照年龄升序:
id	name	school	level	age	score	rank	
1 	a   	一中    	三年级  	11 	1    	    	
2 	a   	一中    	一年级  	11 	1    	    	
3 	jay 	一中    	三年级  	12 	2    	    	
4 	c   	二中    	一年级  	13 	3    	    	
5 	d   	二中    	一年级  	14 	4    	    	
6 	e   	三中    	二年级  	14 	5    	    	
7 	e   	三中    	二年级  	15 	5    	    	

按照年纪升序:(比较器)
id	name	school	level	age	score	rank	
1 	a   	一中    	三年级  	11 	1    	    	
2 	a   	一中    	一年级  	11 	1    	    	
3 	jay 	一中    	三年级  	12 	2    	    	
4 	c   	二中    	一年级  	13 	3    	    	
5 	d   	二中    	一年级  	14 	4    	    	
6 	e   	三中    	二年级  	14 	5    	    	
7 	e   	三中    	二年级  	15 	5    
【7】连接矩阵相关
// API 信息接口
append(T t);                    // 等价于集合 add
union(IFrame<T> other);         //  等价于集合 addAll
join(IFrame<K> other, JoinOn<T,K> on, Join<T,K,R> join);   // 等价于 sql内连接
leftJoin(IFrame<K> other, JoinOn<T,K> on, Join<T,K,R> join);   // 等价于sql左连接,如果左连接失败,K值为null,需手动判断
rightJoin(IFrame<K> other, JoinOn<T,K> on, Join<T,K,R> join);    // 等价于sql右连接,如果右连接失败,T值为null,需手动判断

内连接例子:

@Test
void joinMatlab() {
    System.out.println("======== 矩阵1 =======");
    SDFrame<Student> sdf = SDFrame.read(studentList);
    sdf.show(20);// 显示从1~20条数据

    // 获取学生年龄在9到16岁的学学校合计分数最高的前10名
    SDFrame<FI2<String, BigDecimal>> sdf2 = SDFrame.read(studentList)
        .whereNotNull(Student::getAge)
        .whereBetween(Student::getAge, 9, 16)
        .groupBySum(Student::getSchool, Student::getScore)
        .sortDesc(FI2::getC2)
        .cutFirst(10);
    System.out.println("======== 矩阵2 =======");
    sdf2.show();// 显示所有

    // 类似于SQL种的 select a.*,b.* from sdf a inner join sdf2 b on  a.school = b.c1
    SDFrame<UserInfo> frame = sdf.join(sdf2, (a, b) -> a.getSchool().equals(b.getC1()), (a, b) -> {
        UserInfo userInfo = new UserInfo();
        userInfo.setSchoolName(a.getSchool());
        userInfo.setSchoolScoreSum(b.getC2().intValue());
        userInfo.setNumIndex(String.valueOf(a.getId()));
        return userInfo;
    });
    System.out.println("======== 连接后结果 =======");
    frame.show(100);// 显示从1~100行数据
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class UserInfo {
    private String schoolName;// 学校ID
    private Integer schoolScoreSum;// 按学校分组的总分数
    private String numIndex;// 序号
}

测试结果:

======== 矩阵1 =======
id	name	school	level	age	score	rank	
1 	a   	一中    	三年级  	11 	1    	    	
2 	a   	一中    	一年级  	11 	1    	    	
3 	jay 	一中    	三年级  	12 	2    	    	
4 	c   	二中    	一年级  	13 	3    	    	
5 	d   	二中    	一年级  	14 	4    	    	
6 	e   	三中    	二年级  	14 	5    	    	
7 	e   	三中    	二年级  	15 	5    	    	

======== 矩阵2 =======
c1	c2	
三中	10	
二中	7 	
一中	4 	

======== 连接后结果 =======
schoolName	schoolScoreSum	numIndex	
一中        	4             	1       	
一中        	4             	2       	
一中        	4             	3       	
二中        	7             	4       	
二中        	7             	5       	
三中        	10            	6       	
三中        	10            	7   
【8】百分数转换
@Test
void transferData() {
    // 等价于 select round(score*100, 2) from student
    SDFrame<Student> map2 = SDFrame.read(studentList).mapPercent(Student::getScore, Student::setScore,2);
    map2.show();
}

// 输出结果
id	name	school	level	age	score 	rank	
1 	a   	一中    	三年级  	11 	100.00	    	
2 	a   	一中    	一年级  	11 	100.00	    	
3 	jay 	一中    	三年级  	12 	200.00	    	
4 	c   	二中    	一年级  	13 	300.00	    	
5 	d   	二中    	一年级  	14 	400.00	    	
6 	e   	三中    	二年级  	14 	500.00	    	
7 	e   	三中    	二年级  	15 	500.00	
【9】分区(发现问题-需要对编译环境进行额外设置)
@Test
void partitionList() {
    // 将每个5个元素分成一个小集合,用于将大任务拆成小任务[使用 JDK8+ 自带的吧]
    List<List<Student>> lists = SDFrame.read(studentList).partition(5).stream().toList();

    // 遇到问题:Unable to make field private final java.util.ArrayList java.util.ArrayList$SubList.root accessible: module java.base does not "opens java.util" to unnamed module @1ce92674
    //  - 解决方案:(https://blog.csdn.net/qq_31532979/article/details/138070564)
}

image.png

扩展学习:使用 JDK8+ 内置的流式操作处理 分片集合操作(以简单的基本数据类型为例子)
@Test
void partitionList() {
    // 将每个5个元素分成一个小集合,用于将大任务拆成小任务
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    int MAX_SEND = 4;// 按每3个一组分割
    int limit = (list.size() + MAX_SEND - 1) / MAX_SEND; // 计算切分次数

    //方法一:使用流遍历操作
    List<List<Integer>> mgList = new ArrayList<>();
    Stream.iterate(0, n -> n + 1).limit(limit)
        .forEach(i -> mgList.add(list.stream().skip(i * MAX_SEND).limit(MAX_SEND)
                                 .collect(Collectors.toList())));
    System.out.println("JDK8+流式遍历操作 实现集合拆分:\n" + JSONUtil.toJsonPrettyStr(mgList));

    //方法二:获取分割后的集合
    List<List<Integer>> splitList = Stream.iterate(0, n -> n + 1).limit(limit).parallel()
        .map(a -> list.stream().skip(a * MAX_SEND).limit(MAX_SEND).parallel().collect(Collectors.toList()))
        .toList();
    System.out.println("JDK8+实现集合拆分:\n" + JSONUtil.toJsonPrettyStr(splitList));

}

// 结果如下:
JDK8+流式遍历操作 实现集合拆分:
[
    [
        1,
        2,
        3,
        4
    ],
    [
        5,
        6,
        7,
        8
    ],
    [
        9,
        10
    ]
]
JDK8+实现集合拆分:
[
    [
        1,
        2,
        3,
        4
    ],
    [
        5,
        6,
        7,
        8
    ],
    [
        9,
        10
    ]
]
【10】生成序号

按照age排序,然后根据当前顺序生成排序号到rank字段 (序号从0开始)

@Test
void genOrderIndex() {
    SDFrame<Student> students = SDFrame.read(studentList)
        .sortDesc(Student::getAge)
        .addSortNoCol(Student::setRank); // 写法一:默认设置字段序号列
    	//.addSortNoCol(i -> (Objects.isNull(i.getRank()) ? 0 : i.getRank()) + 1)  // 写法二:自定义(见[自定义生成序号])
    students.forEach(System.out::println);
}
// 写法一输出结果:
Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=0)
Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=1)
Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=2)
Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=3)
Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=4)
Student(id=1, name=a, school=一中, level=三年级, age=11, score=1, rank=5)
Student(id=2, name=a, school=一中, level=一年级, age=11, score=1, rank=6)


// 【注意】如果是需要从自定义的序号开始,注意输出结果的不同!
// 写法二输出结果:两列(第一列为 student实体对象信息列,第二列 为自定义需要列)
// 如果需要获取 学生信息,需要再一次针对 students.getC1(),才能得到对应的学生集合信息。

自定义生成序号(排序起始值和跨度)

@Test
void genOrderIndex() {
    SDFrame<FI2<Student, Integer>> fi2s = SDFrame.read(studentList)
        .sortDesc(Student::getAge)
        //.addSortNoCol(Student::setRank);
        .addSortNoCol(i -> (Objects.isNull(i.getRank()) ? 0 : i.getRank()) + 1);
    fi2s.forEach(item -> {
        item.getC1().setRank(item.getC2());
        System.out.println(item.getC1());
    });
}

输出结果

Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=1)
Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=2)
Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=3)
Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=4)
Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=5)
Student(id=1, name=a, school=一中, level=三年级, age=11, score=1, rank=6)
Student(id=2, name=a, school=一中, level=一年级, age=11, score=1, rank=7)

// fi2s.show(); 的输出结果
c1                                                                       	  c2	
Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=null)  	1 	
Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=null)  	2 	
Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null)  	3 	
Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=null)  	4 	
Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=null)	5 	
Student(id=1, name=a, school=一中, level=三年级, age=11, score=1, rank=null)  	6 	
Student(id=2, name=a, school=一中, level=一年级, age=11, score=1, rank=null)  	7 	
【11】生成排名号

按照age降序排序,然后根据当前顺序生成排名号到rank字段 (排名从0开始)与序号不同的是, 排名是如果值相同认为排名一样。

@Test
void genOrder() {
    // 按照age降序排序,然后根据当前顺序生成排名号到rank字段 (排名从0开始)与序号不同的是, 排名是如果值相同认为排名一样。
    SDFrame<Student> df = SDFrame.read(studentList).addRankingSameColDesc(Student::getAge, Student::setRank);
    df.show(20);
}

输出结果

id	name	school	level	age	score	rank	
7 	e   	三中    	二年级  	15 	5    	1   	
5 	d   	二中    	一年级  	14 	4    	2   	
6 	e   	三中    	二年级  	14 	5    	2   	
4 	c   	二中    	一年级  	13 	3    	3   	
3 	jay 	一中    	三年级  	12 	2    	4   	
1 	a   	一中    	三年级  	11 	1    	5   	
2 	a   	一中    	一年级  	11 	1    	5  
【12】补充条目
// 1. 补充缺失的学校条目
@Test
void addOtherInfo() {
    // 所有需要的学校条目
    List<String> allDim = Arrays.asList("一中", "二中", "三中", "四中", "五中");
    // 根据学校字段和allDim比较去补充缺失的条目, 缺失的学校按照ReplenishFunction生成补充条目作为结果一起返回
    SDFrame<Student> studentSDFrame = SDFrame.read(studentList).replenish(Student::getSchool, allDim, (school) -> {
        Student student = new Student();
        student.setSchool(school);
        return student;
    });
    studentSDFrame.show();

    // 输出Student对象信息:Student(id=null, name=null, school=四中, level=null, age=null, score=null, rank=null)
    studentSDFrame.stream().filter(item -> item.getSchool().equals("四中")).toList().forEach(System.out::println);
}

// 输出结果:
id	name	school	level	age	score	rank	
1 	a   	一中    	三年级  	11 	1    	    	
2 	a   	一中    	一年级  	11 	1    	    	
3 	jay 	一中    	三年级  	12 	2    	    	
4 	c   	二中    	一年级  	13 	3    	    	
5 	d   	二中    	一年级  	14 	4    	    	
6 	e   	三中    	二年级  	14 	5    	    	
7 	e   	三中    	二年级  	15 	5    	    	
  	    	五中    	     	   	     	    	
  	    	四中    	     	   	     	    	

Student(id=null, name=null, school=四中, level=null, age=null, score=null, rank=null)
    
    
    
// 2.分组补充组内缺失的条目
// 按照学校进行分组, 汇总所有年级allDim. 然后与allDim比较补充每个分组内缺失的年级,缺失的年级按照ReplenishFunction生成补充条目
@Test
void addOtherInfo() {
    // 2.按照学校进行分组, 汇总所有年级allDim. 然后与allDim比较补充每个分组内缺失的年级,缺失的年级按照ReplenishFunction生成补充条目
    SDFrame.read(studentList).replenish(Student::getSchool,Student::getLevel,(school,level) -> {
        Student student = new Student();
        student.setSchool(school);
        student.setLevel(level);
        return student;
    }).show(30);
}
// 输出结果
id	name	school	level	age	score	rank	
1 	a   	一中    	三年级  	11 	1    	    	
2 	a   	一中    	一年级  	11 	1    	    	
3 	jay 	一中    	三年级  	12 	2    	    	
  	    	一中    	二年级  	   	     	    	
4 	c   	二中    	一年级  	13 	3    	    	
5 	d   	二中    	一年级  	14 	4    	    	
  	    	二中    	三年级  	   	     	    	
  	    	二中    	二年级  	   	     	    	
6 	e   	三中    	二年级  	14 	5    	    	
7 	e   	三中    	二年级  	15 	5    	    	
  	    	三中    	一年级  	   	     	    	
  	    	三中    	三年级  	  

【最后】全部测试示例奉上

import cn.hutool.json.JSONUtil;
import io.github.burukeyou.dataframe.iframe.JDFrame;
import io.github.burukeyou.dataframe.iframe.SDFrame;
import io.github.burukeyou.dataframe.iframe.item.FI2;
import io.github.burukeyou.dataframe.iframe.item.FI3;
import io.github.burukeyou.dataframe.iframe.item.FI4;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@SpringBootTest
class JDFrameTests {

    static List<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student(1, "a", "一中", "三年级", 11, new BigDecimal(1)));
        studentList.add(new Student(2, "a", "一中", "一年级", 11, new BigDecimal(1)));
        studentList.add(new Student(3, "jay", "一中", "三年级", 12, new BigDecimal(2)));
        studentList.add(new Student(4, "c", "二中", "一年级", 13, new BigDecimal(3)));
        studentList.add(new Student(5, "d", "二中", "一年级", 14, new BigDecimal(4)));
        studentList.add(new Student(6, "e", "三中", "二年级", 14, new BigDecimal(5)));
        studentList.add(new Student(7, "e", "三中", "二年级", 15, new BigDecimal(5)));
    }

    @Test
    void demo() {
        System.out.println("【官方案例】统计每个学校的里学生年龄不为空并且年龄在9到16岁间的合计分数,并且获取合计分前2名的学校: ");
        // 等价于SQL:
        //       select school,sum(score)
        //       from students
        //       where age is not null and age >=9 and age <= 16
        //       group by school
        //       order by sum(score) desc
        //       limit 2
        SDFrame<FI2<String, BigDecimal>> sdf2 = SDFrame.read(studentList)
                .whereNotNull(Student::getAge)
                .whereBetween(Student::getAge, 9, 16)
                .groupBySum(Student::getSchool, Student::getScore)
                .sortDesc(FI2::getC2)
                .cutFirst(2);
        sdf2.show();
    }

    @Test
    void findDemo() {
        System.out.println("筛选相关:(可自行调整筛选输出条件测试哈)");
        SDFrame<Student> data = SDFrame.read(studentList)
                //.whereBetween(Student::getAge, 11, 15) // 过滤年龄在[11,15]岁的
                //.whereBetweenR(Student::getAge, 11, 15) // 过滤年龄在(11,15]岁的, 不含11岁
                //.whereBetweenL(Student::getAge, 11, 15)      // 过滤年龄在[11,15)岁的, 不含15岁
                //.whereNotNull(Student::getName) // 过滤名字不为空的数据, 兼容了空字符串''的判断
                //.whereGt(Student::getAge, 12)    // 过滤年龄大于12岁
                .whereGe(Student::getAge, 14)   // 过滤年龄大于等于14岁
                //.whereLt(Student::getAge, 12)  // 过滤年龄小于12岁的
                //.whereIn(Student::getAge, Arrays.asList(3, 11, 14)) // 过滤年龄为3岁 或者11岁 或者 14岁的数据
                //.whereNotIn(Student::getAge, Arrays.asList(3, 11, 14)) // 过滤年龄不为为3岁 或者11岁 或者 14岁的数据
                //.whereEq(Student::getAge, 11) // 过滤年龄等于11岁的数据
                //.whereNotEq(Student::getAge, 11) // 过滤年龄不等于11岁的数据
                //.whereLike(Student::getName, "jay") // 模糊查询,等价于 like "%jay%"
                //.whereLikeLeft(Student::getName, "jay") // 模糊查询,等价于 like "jay%"
                //.whereLikeRight(Student::getName, "jay") // 模糊查询,等价于 like "%jay"
                ;
        // 输出格式1:按照 JDK 的形式输出
        data.forEach(System.out::println);
        // 输出格式2:按照 JDFrame 的形式(数据库表)输出
        //data.show();
        // 输出格式2.1 :输出指定需要的数据
        data.show(1);// 输出从0~1行数据 data.show(0) 表示输出 列字段名
    }

    @Test
    void analysis() {
        JDFrame<Student> frame = JDFrame.read(studentList);
        System.out.println("获取年龄最大的学生: " + frame.max(Student::getAge));
        System.out.println("获取学生里最大的年龄: " + frame.maxValue(Student::getAge));
        System.out.println("获取年龄最小的学生: " + frame.min(Student::getAge));
        System.out.println("获取学生里最小的年龄: " + frame.minValue(Student::getAge));
        System.out.println("获取所有学生的年龄的平均值: " + frame.avg(Student::getAge)); // 获取所有学生的年龄的平均值
        System.out.println("获取所有学生的年龄合计: " + frame.sum(Student::getAge)); // 获取所有学生的年龄合计
        System.out.println("同时获取年龄最大和最小的学生: " + frame.maxMin(Student::getAge)); // 同时获取年龄最大和最小的学生
        System.out.println("同时获取学生里最大和最小的年龄: " + frame.maxMinValue(Student::getAge)); // 同时获取学生里最大和最小的年龄
    }

    @Test
    void distinctDemo() {
        List<Student> std;
        std = SDFrame.read(studentList).distinct().toLists(); // 根据对象hashCode去重
        System.out.println("根据对象hashCode去重: " + std);
        std = SDFrame.read(studentList).distinct(Student::getSchool).toLists(); // 根据学校名去重
        System.out.println("根据学校名去重: " + std);
        std = SDFrame.read(studentList).distinct(e -> e.getSchool() + e.getLevel()).toLists(); // 根据学校名拼接级别去重复
        System.out.println("根据学校名拼接级别去重复: " + std);
        std = SDFrame.read(studentList).distinct(Student::getSchool).distinct(Student::getLevel).toLists(); // 先根据学校名去除重复再根据级别去除重复
        System.out.println("先根据学校名去除重复再根据级别去除重复: " + std);
    }

    @Test
    void simpleGroup() {
        JDFrame<Student> frame = JDFrame.read(studentList);
        // 等价于 select school,sum(age) ... group by school
        List<FI2<String, BigDecimal>> a = frame.groupBySum(Student::getSchool, Student::getAge).toLists();
        a.forEach(item -> System.out.println("a 学校:" + item.getC1() + "\t年龄:" + item.getC2()));

        // 等价于 select school,max(age) ... group by school
        List<FI2<String, Integer>> a2 = frame.groupByMaxValue(Student::getSchool, Student::getAge).toLists();
        a2.forEach(item -> System.out.println("a2学校:" + item.getC1() + "\t最大年龄:" + item.getC2()));

        //  与 groupByMaxValue 含义一致,只是返回的是最大的值对象
        List<FI2<String, Student>> a3 = frame.groupByMax(Student::getSchool, Student::getAge).toLists();
        a3.forEach(item -> System.out.println("a3学校:" + item.getC1() + "\t最大年龄的学生信息:" + item.getC2()));

        // 等价于 select school,min(age) ... group by school
        List<FI2<String, Integer>> a4 = frame.groupByMinValue(Student::getSchool, Student::getAge).toLists();
        a4.forEach(item -> System.out.println("a4学校:" + item.getC1() + "\t最小年龄:" + item.getC2()));

        // 等价于 select school,count(*) ... group by school
        List<FI2<String, Long>> a5 = frame.groupByCount(Student::getSchool).toLists();
        a5.forEach(item -> System.out.println("a5学校:" + item.getC1() + "\t学生人数:" + item.getC2()));

        // 等价于 select school,avg(age) ... group by school
        List<FI2<String, BigDecimal>> a6 = frame.groupByAvg(Student::getSchool, Student::getAge).toLists();
        a6.forEach(item -> System.out.println("a6学校:" + item.getC1() + "\t学生平均年龄:" + item.getC2()));

        // 等价于 select school,sum(age),count(age) group by school
        List<FI3<String, BigDecimal, Long>> a7 = frame.groupBySumCount(Student::getSchool, Student::getAge).toLists();
        a7.forEach(item -> System.out.println("a7学校:" + item.getC1() + "\t年龄总和:" + item.getC2() + "\t统计年龄人数: " + item.getC3()));

        // (二级分组)等价于 select school,level,sum(age),count(age) group by school,level
        List<FI3<String, String, BigDecimal>> a8 = frame.groupBySum(Student::getSchool, Student::getLevel, Student::getAge).toLists();
        a8.forEach(item -> System.out.println("a8[二级分组]学校:" + item.getC1() + "\t年级:" + item.getC2() + "\t统计此组年龄总和: " + item.getC3()));

        // (三级分组)等价于 select school,level,name,sum(age),count(age) group by school,level,name
        List<FI4<String, String, String, BigDecimal>> a9 = frame.groupBySum(Student::getSchool, Student::getLevel, Student::getName, Student::getAge).toLists();
        a9.forEach(item -> System.out.println("a9[三级分组]学校:" + item.getC1() + "\t年级:" + item.getC2() + "\t名称: " + item.getC3() + "\t此组年龄总和: " + item.getC4()));

    }

    @Test
    void orderList() {
        // 注意:中文不支持排序。

        // 等价于 order by age desc
        System.out.println("按照年龄降序:");
        SDFrame.read(studentList).sortDesc(Student::getAge).show();

        //  等价于 order by age desc, level asc
        System.out.println("按照年龄降序-年级升序:【JDFrame严重BUG】不支持多字段排序,只识别最后一个字段的sortXXX(YYYY)");
        SDFrame.read(studentList).sortDesc(Student::getAge).sortAsc(Student::getLevel).show();
        //SDFrame.read(studentList).sortDesc(Student::getAge).sortAsc(Student::getScore).show();

        // 等价于 order by age asc
        System.out.println("按照年龄升序:");
        SDFrame.read(studentList).sortAsc(Student::getAge).show();

        // 使用Comparator 排序
        System.out.println("按照年纪升序:(比较器)");
        SDFrame.read(studentList).sortAsc(Comparator.comparing(Student::getAge)).show();

    }

    @Test
    void joinMatlab() {
        System.out.println("======== 矩阵1 =======");
        SDFrame<Student> sdf = SDFrame.read(studentList);
        sdf.show(20);// 显示从1~20条数据

        // 获取学生年龄在9到16岁的学学校合计分数最高的前10名
        SDFrame<FI2<String, BigDecimal>> sdf2 = SDFrame.read(studentList)
                .whereNotNull(Student::getAge)
                .whereBetween(Student::getAge, 9, 16)
                .groupBySum(Student::getSchool, Student::getScore)
                .sortDesc(FI2::getC2)
                .cutFirst(10);
        System.out.println("======== 矩阵2 =======");
        sdf2.show();// 显示所有

        SDFrame<UserInfo> frame = sdf.join(sdf2, (a, b) -> a.getSchool().equals(b.getC1()), (a, b) -> {
            UserInfo userInfo = new UserInfo();
            userInfo.setSchoolName(a.getSchool());
            userInfo.setSchoolScoreSum(b.getC2().intValue());
            userInfo.setNumIndex(String.valueOf(a.getId()));
            return userInfo;
        });
        System.out.println("======== 连接后结果 =======");
        frame.show(100);// 显示从1~100行数据
    }

    @Test
    void transferData() {
        // 等价于 select round(score*100,2) from student
        SDFrame<Student> map2 = SDFrame.read(studentList).mapPercent(Student::getScore, Student::setScore, 2);
        map2.show();
    }

    @Test
    void partitionList() {
        // 将每个5个元素分成一个小集合,用于将大任务拆成小任务
        //List<List<Student>> lists = SDFrame.read(studentList).partition(5).stream().toList();
        // 遇到问题:Unable to make field private final java.util.ArrayList java.util.ArrayList$SubList.root accessible: module java.base does not "opens java.util" to unnamed module @1ce92674
        //  - 解决方案:(https://blog.csdn.net/qq_31532979/article/details/138070564)

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        int MAX_SEND = 4;// 按每3个一组分割
        int limit = (list.size() + MAX_SEND - 1) / MAX_SEND; // 计算切分次数
        //方法一:使用流遍历操作
        List<List<Integer>> mgList = new ArrayList<>();
        Stream.iterate(0, n -> n + 1).limit(limit)
                .forEach(i -> mgList.add(list.stream().skip(i * MAX_SEND).limit(MAX_SEND)
                        .collect(Collectors.toList())));
        System.out.println("JDK8+流式遍历操作 实现集合拆分:\n" + JSONUtil.toJsonPrettyStr(mgList));
        //方法二:获取分割后的集合
        List<List<Integer>> splitList = Stream.iterate(0, n -> n + 1).limit(limit).parallel()
                .map(a -> list.stream().skip(a * MAX_SEND).limit(MAX_SEND).parallel().collect(Collectors.toList()))
                .toList();
        System.out.println("JDK8+实现集合拆分:\n" + JSONUtil.toJsonPrettyStr(splitList));

    }

    @Test
    void genOrderIndex() {
        SDFrame<FI2<Student, Integer>> fi2s = SDFrame.read(studentList)
                .sortDesc(Student::getAge)
                //.addSortNoCol(Student::setRank);
                .addSortNoCol(i -> (Objects.isNull(i.getRank()) ? 0 : i.getRank()) + 1);
        fi2s.forEach(item -> {
            item.getC1().setRank(item.getC2());
            System.out.println(item.getC1());
        });
    }

    @Test
    void genOrder() {
        // 按照age降序排序,然后根据当前顺序生成排名号到rank字段 (排名从0开始)与序号不同的是, 排名是如果值相同认为排名一样。
        SDFrame<Student> df = SDFrame.read(studentList).addRankingSameColDesc(Student::getAge, Student::setRank);
        df.show(20);
    }

    @Test
    void addOtherInfo() {
        //// 1. 根据学校字段和allDim比较去补充缺失的条目, 缺失的学校按照ReplenishFunction生成补充条目作为结果一起返回
        //// 所有需要的学校条目
        //List<String> allDim = Arrays.asList("一中", "二中", "三中", "四中", "五中");
        //SDFrame<Student> studentSDFrame = SDFrame.read(studentList).replenish(Student::getSchool, allDim, (school) -> {
        //    Student student = new Student();
        //    student.setSchool(school);
        //    return student;
        //});
        //studentSDFrame.show();
        //// 输出Student对象信息:Student(id=null, name=null, school=四中, level=null, age=null, score=null, rank=null)
        //studentSDFrame.stream().filter(item -> item.getSchool().equals("四中")).toList().forEach(System.out::println);

        // 2.按照学校进行分组, 汇总所有年级allDim. 然后与allDim比较补充每个分组内缺失的年级,缺失的年级按照ReplenishFunction生成补充条目
        SDFrame.read(studentList).replenish(Student::getSchool,Student::getLevel,(school,level) -> {
            Student student = new Student();
            student.setSchool(school);
            student.setLevel(level);
            return student;
        }).show(30);

    }

}

@Data
@NoArgsConstructor
@AllArgsConstructor
class UserInfo {
    private String schoolName;// 学校ID
    private Integer schoolScoreSum;// 分数
    private String numIndex;//
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Student {

    private Integer id;
    private String name;
    private String school;
    private String level;
    private Integer age;
    private BigDecimal score;

    private Integer rank;

    public Student(String level, BigDecimal score) {
        this.level = level;
        this.score = score;
    }

    public Student(Integer id, String name, String school, String level, Integer age, BigDecimal score) {
        this.id = id;
        this.name = name;
        this.school = school;
        this.level = level;
        this.age = age;
        this.score = score;
    }
}

至此,常用的功能示例完毕,建议还是使用 内部 JDK8+ 的流式处理吧😄(主要是 ① 学习成本;② 安全可靠性考量);

附录


🙏至此,非常感谢阅读🙏

cat010.png