刚学反射,理解起来困难?那就写一个Demo入门吧!

背景

经历了n多次面试之后,每个面试官问必问的一个问题,就是问我用过反射吗,我当时只是颤颤巍巍的回答说具体没有使用过,但是Spring的IOC容器就是根据配置文件信息获取类文件信息进而反射创建bean实例,然后成功把面试官带领到我的八股文阵地,balabalabala说一堆,然后结束。

但是,真的心里踏实吗?最近工作中也是遇到了一个场景需要将实体类转为map,好家伙,不就是反射获取属性信息,执行get方法,然后放到map里面嘛,这不,反射不就用上了嘛,虽然已经工作了,但是还是给之前的自己一个答案吧!
关于反射的介绍详细见另一篇博客,看完详细介绍再看Bean转Map才会不懵逼:点击查看反射详细介绍

Bean转Map

下面问题就是Bean转Map了,先完成功能再考虑性能。
我们咋把一个Bean转成Map呢,需要获取所有的属性,然后把每个属性值都放入map中,还得一一对应,简单来说,先获取所有字段,然后获取所有方法,把字段和方法进行匹配,匹配之后呢,就可以执行方法,然后把结果放入map和字段对应,就结束了,毫无疑问,得用双循环,复杂度O(n^2)
话不多说,上代码!

/**
 * 双循环版本 循环解析1000000(100w)次耗时20s
 * beanmap 0s
 * @param o
 * @return
 */
public static Map<Object, Object> classToMap20s(Object o) {
    HashMap<Object, Object> map = new HashMap<>();
    Class<?> clazz = o.getClass();
    // 所有字段已经拿到!
    Field[] fields = clazz.getDeclaredFields();
    // 所有方法拿到
    Method[] methods = clazz.getDeclaredMethods();
    // 字段方法匹配
    for (Field field : fields) {
        for (Method method : methods) {
            String methodName = method.getName();
            String fieldName = field.getName();
            if (methodName.contains("get") && StringUtils.upperCase(methodName).contains(StringUtils.upperCase(fieldName))) {
                try {
                    map.put(field.getName(), method.invoke(o));
                } catch (InvocationTargetException | IllegalAccessException e) {
                    // InvocationTargetException 被调用的方法内部可能有异常抛出没有被捕获,就报这个
                    // IllegalAccessException 没有对这个类的访问权限的时候报这个异常
                    e.printStackTrace();
                }
            }
        }
    }
    return map;
}

总体思路就是拿到所有字段和方法,然后匹配执行对应方法,把值放入map。
现在功能是实现了,总不能拿个O(n^2)的在项目里跑吧 = =!
所以要考虑着手优化,咋优化呢?思考一下,我们双循环的根源,就是我们拿到一个实体类,不知道这个实体类的字段和方法对应信息,所以才需要双循环解析,对于重复的实体类,都做了重复的解析!

so!把字段和方法的对应关系存到缓存map里面,然后再次解析同一个实体类的时候,就可以直接拿到对应关系,直接一个一个执行方法然后赋值进结果map就好了!话不多说开搞,代码如下:

/**
 *
 * 双循环优化,使用map缓存
 * 循环解析1000000(100w)次耗时1s
 * beanMap 0s
 * 解析1000w次耗时12s
 * beanMap 1s
 * @param o
 * @return
 */
public static Map<Object, Object> classToMap(Object o) {
    HashMap<Object, Object> map = new HashMap<>();
    Class<?> clazz = o.getClass();
    // 所有字段已经拿到!
    Field[] fields = clazz.getDeclaredFields();
    // 所有方法拿到
    Method[] methods = clazz.getDeclaredMethods();
    // 缓存没打到,进行字段方法匹配
    if (!cacheMap.containsKey(clazz)) {
        Map<String, Method> fieldMethodMap = new HashMap<>();
        // 字段方法匹配
        for (Field field : fields) {
            for (Method method : methods) {
                String methodName = method.getName();
                String fieldName = field.getName();
                if (methodName.contains("get") && StringUtils.upperCase(methodName).contains(StringUtils.upperCase(fieldName))) {
                    // 匹配好了放缓存里下次使用
                    fieldMethodMap.put(fieldName, method);
                    try {
                        map.put(field.getName(), method.invoke(o));
                    } catch (InvocationTargetException | IllegalAccessException e) {
                        // InvocationTargetException 被调用的方法内部可能有异常抛出没有被捕获,就报这个
                        // IllegalAccessException 没有对这个类的访问权限的时候报这个异常
                        e.printStackTrace();
                    }
                }
            }
        }
        // 匹配结束了放入缓存
        cacheMap.put(clazz, fieldMethodMap);
    } else {
        // 缓存打到了,直接拿出类信息执行方法赋值
        Map<String, Method> fieldMethod = cacheMap.get(clazz);
        for (String field : fieldMethod.keySet()) {
            try {
                map.put(field, fieldMethod.get(field).invoke(o));
            } catch (InvocationTargetException | IllegalAccessException e) {
                // InvocationTargetException 被调用的方法内部可能有异常抛出没有被捕获,就报这个
                // IllegalAccessException 没有对这个类的访问权限的时候报这个异常
                e.printStackTrace();
            }
        }
    }
    return map;
}

跑他个100w次,从20s到1s,从O(n^2) 到 O(n),拿捏了!

总结:Bean转Map几种方法

  1. bean转json,json再解析成map,比较绕,比较耗时,不推荐
    import com.alibaba.fastjson.JSON;
    Map map = JSON.parseObject(JSON.toJSONString(javaBean), Map.class);
    
  2. 使用反射,效率次之,比较麻烦,可以自己造轮子搞一下,但是尽量别重复造轮子,造轮子是为了学习!
  3. 使用hutool工具包里面的BeanUtil,效率和咱们自己写的反射+缓存效率差不多,使用挺简单,项目中引入这个工具包的直接用就好,挺强大的工具包
    import cn.hutool.core.bean.BeanUtil;
    Map<String, Object> beanToMap = BeanUtil.beanToMap(screenNumPo);
    
  4. 【重点】使用beanMap,效率高,用了缓存,不费时间,还简单
    import org.springframework.cglib.beans.BeanMap;
    BeanMap beanMap = BeanMap.create(screenNumPo);
    // 可以看下BeanMap源码,里面用了WeakHashMap进行缓存,用的真是恰到好处
    private static volatile Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> CACHE = new WeakHashMap();
    

最后还是推荐大家使用BeanMap,效率贼高,使用简单,我也在项目中用了BeanMap做了最后解决方案~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值