[Java Collection]List分组之简单应用.

前言

今天有一个新需求, 是对一个List进行分组, 于是便百度到一些可用的代码以及我们项目使用的一些tools, 在这里总结下方便以后查阅.

一: 需求

现在我们一个数据库表t_series_value_rate存储的是每个汽车对应的保值率. 其中一个车系id可以对应多条数据.表内容部分截取如下:

其中series_id是车系id, car_year代表这个车的年限. 比如说车系id为1的车1年的保值率为81.5%, 2年的保值率为73.7%.

那么现在我需要传递过来一个series_id list去查询出相应的数据, 我们是对数据库表t_series_value_rate查询所有做了缓存的, 如果我们对传入的series_id list进行遍历的话势必会很慢. 所以如果能够根据series_id进行分组的话, 那么效率就会高的多.

二: 代码示例

对外暴漏的API接口, 方便别的项目调用:

public List<HashMap<Long, List<SeriesValueRateDTO>>> listSeriesValueRates(List<Long> seriesIds) throws Exception {
    ApiResponse response = httpGet("/api/server/series-value-rates/list-series-value-rates.htm?seriesIds=" + Joiner.on(",").skipNulls().join(seriesIds));
    return JSON.parseObject(response.getJsonObject().get("data").toString(), new TypeReference<List<HashMap<Long, List<SeriesValueRateDTO>>>>(){});
}

这里我们传入的是一个车系id集合. 然后继续往下调用:

public List<SeriesValueRateDTO> listSeriesValueRate(final long seriesId) {
    String key = "listSeriesValueRate" + seriesId;
    return managerService.get(key, new Callable<List<SeriesValueRateDTO>>() {
        @Override
        public List<SeriesValueRateDTO> call() throws Exception {
            Map<Long, List<SeriesValueRateDTO>> valueRateDTOs = listAllSeriesValueRate();
            List<SeriesValueRateDTO> dtos = valueRateDTOs.get(seriesId);
            return dtos;
        }
    }, CommonConstants.ExpiredTime.ONE_DAY);
}

这里使用了DCache去缓存不同的seriesId对应的数据, 接下来再看看查询所有车系保值率数据(listAllSeriesValueRate()):

private LoadingCache<String, Map<Long, List<SeriesValueRateDTO>>> cache = CacheBuilder.newBuilder()
            .expireAfterWrite(12,TimeUnit.HOURS)
                .build(new CacheLoader<String, Map<Long, List<SeriesValueRateDTO>>>() {
    @Override
    public Map<Long, List<SeriesValueRateDTO>> load(String k) {
        List<SeriesValueRateDTO> valueRateDTOs = Lists.newArrayList();
        List<SeriesValueRateEntity> entities = seriesValueRateEntityDao.findAll(SeriesValueRateEntity.Fields.seriesId.notNull());
        for (SeriesValueRateEntity entity : entities) {
            SeriesValueRateDTO dto = new SeriesValueRateDTO();
            dto.setSeriesId(entity.getSeriesId());
            dto.setCarYear(entity.getCarYear());
            dto.setValueRate(entity.getValueRate());
            valueRateDTOs.add(dto);
        }

        //按照seriesId进行分组
        Map<Long, List<SeriesValueRateDTO>> map = Maps.newHashMap(); //第17行
        GroupUtils.listGroup2Map(valueRateDTOs, map, SeriesValueRateDTO.class, "getSeriesId");//第18行

        return map;
    }
});

这里使用了GuavaCache去缓存所有的车系保值率数据, 然后这里使用了GroupUtils去进行分组, 分组是按照"getSeriesId"来获取seriesId进行分组. 我们来查看下分组前的数据结构(代码中第17行处查看debug数据):

然后再看看分组后的数据结构(运行完第18行数据结果):

很显然, 数据已经进行了分组, 最后看看我们是如何高效率的通过传入的seriesIds取值的:

public List<HashMap<Long, List<SeriesValueRateDTO>>> listSeriesValueRates() {
    WebContext context = WebContext.get();
    List<Long> ids =context.getRequiredLongList("seriesIds");
    List<HashMap<Long, List<SeriesValueRateDTO>>> seriesValueRateDTOs = Lists.newArrayList();
    for (long seriesId : ids) {
        HashMap<Long, List<SeriesValueRateDTO>> map = Maps.newHashMap();
        List<SeriesValueRateDTO> dtos = seriesValueRateEntityService.listSeriesValueRate(seriesId);
        map.put(seriesId, dtos);
        seriesValueRateDTOs.add(map);
    }

    return seriesValueRateDTOs;
}

public List<SeriesValueRateDTO> listSeriesValueRate(final long seriesId) {
    String key = "listSeriesValueRate" + seriesId;
    return managerService.get(key, new Callable<List<SeriesValueRateDTO>>() {
        @Override
        public List<SeriesValueRateDTO> call() throws Exception {
            Map<Long, List<SeriesValueRateDTO>> valueRateDTOs = listAllSeriesValueRate();
            List<SeriesValueRateDTO> dtos = valueRateDTOs.get(seriesId);
            return dtos;
        }
    }, CommonConstants.ExpiredTime.ONE_DAY);
}

这里再放上SeriesValueRateDTO:

public class SeriesValueRateDTO {
    /**
     * 车系id
     */
    private long seriesId;
    /**
     * 保值率
     */
    private Double valueRate;
    /**
     * 车辆年限
     */
    private int carYear;

    public long getSeriesId() {
        return seriesId;
    }

    public void setSeriesId(long seriesId) {
        this.seriesId = seriesId;
    }

    public Double getValueRate() {
        return valueRate;
    }

    public void setValueRate(Double valueRate) {
        this.valueRate = valueRate;
    }

    public int getCarYear() {
        return carYear;
    }

    public void setCarYear(int carYear) {
        this.carYear = carYear;
    }
}

三: 分组工具类GroupUtils

这里直接铺上代码, 其实也很简单, 具体使用规则请参考上面.

public class GroupUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(GroupUtils.class);
    /**
     * 分组依赖接口
     */
    public interface GroupBy<T> {
        T groupby(Object obj);
    }

    /**
     *
     * @param colls
     * @param gb
     * @return
     */
    public static final <T extends Comparable<T>, D> Map<T, List<D>> group(Collection<D> colls, GroupBy<T> gb) {
        if (colls == null || colls.isEmpty()) {
            LOGGER.info("分组集合不能为空!");
            return null;
        }
        if (gb == null) {
            LOGGER.info("分组依赖接口不能为Null!");
            return null;
        }
        Iterator<D> iter = colls.iterator();
        Map<T, List<D>> map = new HashMap<T, List<D>>();
        while (iter.hasNext()) {
            D d = iter.next();
            T t = gb.groupby(d);
            if (map.containsKey(t)) {
                map.get(t).add(d);
            } else {
                List<D> list = new ArrayList<D>();
                list.add(d);
                map.put(t, list);
            }
        }
        return map;
    }
    /**
     * 将List<V>按照V的methodName方法返回值(返回值必须为K类型)分组,合入到Map<K, List<V>>中<br>
     * 要保证入参的method必须为V的某一个有返回值的方法,并且该返回值必须为K类型
     *
     * @param list
     *            待分组的列表
     * @param map
     *            存放分组后的map
     * @param clazz
     *            泛型V的类型
     * @param methodName
     *            方法名
     */
    public static <K, V> void listGroup2Map(List<V> list, Map<K, List<V>> map, Class<V> clazz, String methodName) {
        // 入参非法行校验
        if (null == list || null == map || null == clazz) {
            LOGGER.info("CommonUtils.listGroup2Map 入参错误,list:" + list + " ;map:" + map + " ;clazz:" + clazz + " ;methodName:" + methodName);
            return;
        }

        // 获取方法
        Method method = getMethodByName(clazz, methodName);
        // 非空判断
        if (null == method) {
            return;
        }

        // 正式分组
        listGroup2Map(list, map, method);
    }
    /**
     * 根据类和方法名,获取方法对象
     *
     * @param clazz
     * @param methodName
     * @return
     */
    public static Method getMethodByName(Class<?> clazz, String methodName) {
        Method method = null;
        // 入参不能为空
        if (null == clazz) {
            LOGGER.info("GroupUtils.getMethodByName 入参错误,clazz:" + clazz + " ;methodName:" + methodName);
            return method;
        }

        try {
            method = clazz.getDeclaredMethod(methodName);
        } catch (Exception e) {
            LOGGER.info("类获取方法失败!");
        }

        return method;
    }
    /**
     * 将List<V>按照V的某个方法返回值(返回值必须为K类型)分组,合入到Map<K, List<V>>中<br>
     * 要保证入参的method必须为V的某一个有返回值的方法,并且该返回值必须为K类型
     *
     * @param list
     *            待分组的列表
     * @param map
     *            存放分组后的map
     * @param method
     *            方法
     */
    @SuppressWarnings("unchecked")
    public static <K, V> void listGroup2Map(List<V> list, Map<K, List<V>> map, Method method) {
        // 入参非法行校验
        if (null == list || null == map || null == method) {
            LOGGER.info("GroupUtils.listGroup2Map 入参错误,list:" + list + " ;map:" + map + " ;method:" + method);
            return;
        }

        try {
            // 开始分组
            Object key;
            List<V> listTmp;
            for (V val : list) {
                key = method.invoke(val);
                listTmp = map.get(key);
                if (null == listTmp) {
                    listTmp = new ArrayList<V>();
                    map.put((K) key, listTmp);
                }
                listTmp.add(val);
            }
        } catch (Exception e) {
            LOGGER.info("分组失败!");
        }
    }
}

最后大家可以根据自己的需求来选择改造或使用. 回头发现项目中能学到的东西很多, 记录下来希望以后能够多看看. 2016/12/06 http://www.cnblogs.com/wang-meng/

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小樱的经验随笔

【Java学习笔记之二十八】深入了解Java8新特性

前言: Java 8 已经发布很久了,很多报道表明java 8 是一次重大的版本升级。在Java Code Geeks上已经有很多介绍Java 8新特性的文章,...

3687
来自专栏ImportSource

Java8真不用再搞循环了?

Java8以后真的不用循环了?真的不用了? 好吧,本文分享的内容是java8之前和java8之后一些代码的不同写法,我们会先介绍java8之前和java8之后不...

2.5K11
来自专栏蘑菇先生的技术笔记

探索c#之不可变数据类型

2094
来自专栏阿杜的世界

【译】Java 8的新特性—终极版1. 简介2. Java语言的新特性3. Java编译器的新特性4. Java官方库的新特性5. 新的Java工具6. JVM的新特性7. 结论8. 参考资料

前言: Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级。在Java Code Geeks上已经有很多介绍Java 8新特性的文章,...

1164
来自专栏从流域到海域

《笨办法学Python》 第39课手记

《笨办法学Python》 第39课手记 本节课讲列表的操作,用来做练习的代码中出现了之前用到过的几个函数,是一节复习课。你需要记住它们。 原代码如下: ten_...

2047
来自专栏HansBug's Lab

1012: [JSOI2008]最大数maxnumber

1012: [JSOI2008]最大数maxnumber Time Limit: 3 Sec  Memory Limit: 162 MB Submit: 443...

2845
来自专栏积累沉淀

【译】Java 8的新特性—终极版

声明:本文翻译自Java 8 Features Tutorial – The ULTIMATE Guide,翻译过程中发现并发编程网已经有同学翻译过了:...

24910
来自专栏C/C++基础

动态联编实现原理分析

所谓动态联编,是指被调函数入口地址是在运行时、而不是在编译时决定的。C++语言利用动态联编来完成虚函数调用。C++标准并没有规定如何实现动态联编,但大多数的C+...

831
来自专栏开发与安全

《linux c 编程一站式学习》课后部分习题解答

1、假设变量x和n是两个正整数,我们知道x/n这个表达式的结果要取Floor,例如x是17,n是4,则结果是4。如果希望结果取Ceiling应该怎么写表达式呢?...

4576
来自专栏ml

Guava------------Cache使用方法

    简单从这几个方面描述一下如何使用Cache,对Cache的各种原理介绍此处不涉及.     1.使用场景     2.如何使用Cache     3.创...

7738

扫码关注云+社区

领取腾讯云代金券