关于Guava集合包
Guava提供了丰富的集合包,对于业务操作非常友好,本篇文章对比使用Guava集合包和不适用Guava集合包,看看Guava集合包给我们集合操作带来了哪些便利。
集合处理中的应用
对List<对象>进行分组
业务介绍:有一个订单列表,里面包含多个用户的多个订单,我们需要对按照用户对订单列表进行分组
应用类
ArrayListMultimap, 当然Guava还提供了LinkedListMultimap,api类似,只是存放值的集合换成了LinkedList
实现对比
/**
* 订单实体
*/
public class Order {
private String userId;
private String orderId;
private Double price;
private Date createTime;
private Date updateTime;
}
public class Test {
/**
* 自己实现分组
*/
public void group(List<Order> orderList) {
Map<String, List<Order>> userOrdersMap = new HashMap<>();
for(Order order: orderList) {
String userId = order.getUserId();
if(userOrdersMap.containsKey(userId)) {
userOrdersMap.get(userId).add(order);
} else {
List<Order> orders = new ArrayList<>();
orders.add(order);
userOrdersMap.put(user, orders);
}
}
}
/**
* 使用Guava进行分组分组
*/
public void guavaGroup(List<Order> orderList) {
ArrayListMultimap<String, Order> arrayListMultimap = ArrayListMultimap.create();
orderList.forech(e -> arrayListMultimap.put(e.getUserId(), e));
// ArrayListMultimap 常用API asMap() get(key)返回值为List<T>
}
}
结论
查看Guava源码会发现ArrayListMultimap的put方法与我们自己实现分组的代码逻辑是类似的,ArrayListMultimap相当于是对我们常用列表业务操作进行封装,在处理列表业务较多的项目,引入Guava能够降低我们代码复杂度,更专注于核心业务实现。
存放多个学生多个科目的成绩,计算学生总分及班级科目评论分
数据库有一张表存放有班级所有学生所有科目的考生成绩,读取所有成绩信息,并计算出每个学生的总分及班级科目平均分
示例数据
| 姓名 | 科目 | 分数 | 学期 | 班级 |
|---|---|---|---|---|
| 张三 | 语文 | 95 | 2021上 | 三年二班 |
| 张三 | 数学 | 80 | 2021上 | 三年二班 |
| 王五 | 语文 | 90 | 2021上 | 三年二班 |
| 王五 | 数学 | 75 | 2021上 | 三年二班 |
表结构
CREATE TABLE `test_score` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`student_name` varchar(255) NOT NULL COMMENT '学生姓名',
`subject` varchar(255) NOT NULL COMMENT '科目',
`semester` varchar(255) NOT NULL COMMENT '学期',
`score` double NOT NULL COMMENT '分数',
`class_grade` varchar(255) NOT NULL COMMENT '班级',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
业务场景
统计班级考试成绩统计,统计每个学生的总分、班级科目平均分,班级总分平均分,汇总成统计信息
业务分析
这样一个统计业务如果使用sql处理,需要每个业务单独进行sql查询再汇总,具体如下:
- 每个学生总分
select student_name, sum(score) gross_score from test_score where semester = '2021上' and class_grade = '三年二班' group by student_name;
- 班级科目平均分
select subject,avg(score) as avg from test_score where semester = '2021上' and class_grade = '三年二班' group by subject;
- 班级总分平均分
SELECT avg(t.sum) avg FROM (SELECT sum(score) AS sum FROM test_score WHERE semester = '2021上' AND class_grade = '三年二班' GROUP BY student_name) AS t
这样的sql还是比较简单的,但是对于一个业务来说,需要进行三次数据库交互,且设计多次sql函数操作和分组,最终结果还是需要进行聚合处理,无论在数据库层面还是代码层面,都是比较复杂的,且不利于维护,下面我将使用Guava的HashBasedTable来解决此业务,减少sql操作与sql复杂度,且代码复杂度较低。
代码实现
- 进行普通查询,获取到三年二班2021上学期所有学生的成绩,返回结果映射为List
SELECT student_name, subject,semester,score,class_grade FROM test_score WHERE semester = '2021上' AND class_grade = '三年二班'
/**
* 学生科目成绩实体
*/
public class TestScore {
private String studentName;
private String subject;
private String semester;
private Double score;
private classGrade;
// 省略get set toString ...
}
/**
* 统计业务实现示例
*/
public class TestScoreService {
/**
* 使用假数据进行测试 真是场景 testScoreList通过执行上面sql获得
*/
public static void main(String[] args) {
TestScore t1 = new TestScore("张三", "语文", "三年二班", 80D, "2021上");
TestScore t2 = new TestScore("张三", "数学", "三年二班", 80D, "2021上");
TestScore t3 = new TestScore("李四", "语文", "三年二班", 100D, "2021上");
TestScore t4 = new TestScore("李四", "数学", "三年二班", 90D, "2021上");
ArrayList<TestScore> testScoreList = Lists.newArrayList(t1, t2, t3, t4);
// 2.初始化HashBasedTable HashBasedTable<R, C, V> R相当行 就是学生姓名 C 相当于列 就是科目
HashBasedTable<String, String, Double> table = HashBasedTable.create();
for (TestScore testScore : testScoreList) {
table.put(testScore.getStudentName(), testScore.getSubject(), testScore.getScore());
}
//3.计算每个学生的总分
Map<String, Map<String, Double>> studentSubjectScoreMap = table.rowMap();
Map<String, Double> studentGrossScoreMap = Maps.newHashMap();
for (Map.Entry<String, Map<String, Double>> entry : studentSubjectScoreMap.entrySet()) {
String studentName = entry.getKey();
// 总分
double grossScore = entry.getValue().values().stream().mapToDouble(e -> e).sum();
studentGrossScoreMap.put(studentName, grossScore);
}
// 4.计算班级总分平均分
double grossScoreAvg = studentGrossScoreMap.values().stream().mapToDouble(e -> e).average().orElse(-1);
// 5.计算科目平均分
Map<String, Map<String, Double>> subjectStudentScoreMap = table.columnMap();
Map<Object, Object> subjectScoreAvgMap = Maps.newHashMap();
for (Map.Entry<String, Map<String, Double>> entry : subjectStudentScoreMap.entrySet()) {
String subject = entry.getKey();
// 科目平均分
double subjectScoreAvg = entry.getValue().values().stream().mapToDouble(e -> e).average().orElse(-1);
subjectScoreAvgMap.put(subject, subjectScoreAvg);
}
// 学生总分
System.out.println(studentGrossScoreMap);
// 班级科目平均分
System.out.println(subjectScoreAvgMap);
// 班级总分平均分
System.out.println(grossScoreAvg);
}
}
执行结果
{李四=190.0, 张三=160.0}
{数学=85.0, 语文=90.0}
175.0
使用HashBasedTable小结
根据上述业务场景,HashBasedTable的用处就是将我们的数据结构封装成类似于excel表格,有自己的行和列,我们把学生每个科目的成绩放在同一行,同一个科目的成绩放在同一列,使用提供的rowMap(),columnMap()的api帮助我们快速提出行或列的数据,然后进行计算处理
BiMap
BiMap维护双向映射关系,可以通过键找值
示例
public class Demo {
public static void main(String[] args) {
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("张三", 28);
String name = biMap.inverse().get(28);
// put已经存在的值会抛出异常
// biMap.put("李四", 28); // 抛出IllegalArgumentExceptionvalue: already present: 28
// 使用forcePut()可以避免异常
biMap.forcePut("李四", 28);
}
}
结论
bimap提供双向关系维护,但是业务上需要保证值唯一,如果不唯一,需使用forcePut
总结
guava提供丰富的集合来辅助我们解决业务问题,我们熟悉各种集合的特性,了解其数据结构,能够在熟悉需求后快速选择适合自己业务的集合来处理数据