Guava常用集合包应用示例

2,938 阅读5分钟

关于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能够降低我们代码复杂度,更专注于核心业务实现。

存放多个学生多个科目的成绩,计算学生总分及班级科目评论分

数据库有一张表存放有班级所有学生所有科目的考生成绩,读取所有成绩信息,并计算出每个学生的总分及班级科目平均分

示例数据

姓名科目分数学期班级
张三语文952021上三年二班
张三数学802021上三年二班
王五语文902021上三年二班
王五数学752021上三年二班

表结构

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提供丰富的集合来辅助我们解决业务问题,我们熟悉各种集合的特性,了解其数据结构,能够在熟悉需求后快速选择适合自己业务的集合来处理数据