java反射调用get/set方法,你还在拼接方法名吗?

摘要:
"id".equals;}).collect;for{//descriptor.getWriteMethod()方法对应set方法MethodreadMethod=descriptor.getReadMethod();System.out.println;try{Objecto=readMethod.invoke;System.out.println;}catch{log.info;returnnull;}}PropertyDescriptor类提供了getReadMethod和getWriteMethod,其实就是对于get/set方法,至于方法名称不需要我们来关于,这样就可以避免方法名拼错的情况了。如果你以为这就是Introspector的全部功能,那就大错特错了。

前言

最新工作中,遇到了通过反射调用get/set方法的地方,虽然反射的性能不是很好,但是相比较于硬编码的不易扩展,getDeclareFields可以拿到所有的成员变量,后续添加或删除成员变量时,不用修改代码,且应用次数只在修改数据时使用,故牺牲一些性能提高扩展性

传统的方式

见过很多人通过反射调用get/set方法都是通过获取属性的name,然后通过字符串截取将首字母大写,再拼上get/set来做

String fieldName =field.getName();
String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

还有稍微好一点的同学,通过fieldName转成字符数组,首个字符-32来避免字符串截取的

String fieldName =field.getName();
char[] chars =fieldName.toCharArray();
chars[0] = (char)(chars[0] - 32);
String getMethodName = "get" + new String(chars);

诚然,我觉得两种方式都可以,但是不知道有没有遇到过,生成的get/set方法并不是已get/set开头的,而是以is开头的,比如boolean类型的成员变量。这个时候我们就需要去判断属性的类型,然后用不同的前缀来拼接get/set方法名。其实,在jdk中已经包含了这样的工具类

Introspector和PropertyDescriptor

关于这两个类的详细介绍,我这里就不说了,简单的理解就是对象信息的描述,里面提供了一些API方便我们拿到对象的信息

复制代码
BeanInfo beanInfo;
try{
    beanInfo =Introspector.getBeanInfo(template.getClass());
} catch(IntrospectionException e) {
    log.info("xxxxxxxxxxxxxxxx", e);
    return null;
}

List<PropertyDescriptor> descriptors = Arrays.stream(beanInfo.getPropertyDescriptors()).filter(p ->{
    String name =p.getName();
    //过滤掉不需要修改的属性
    return !"class".equals(name) && !"id".equals(name);
}).collect(Collectors.toList());

for(PropertyDescriptor descriptor : descriptors) {
    //descriptor.getWriteMethod()方法对应set方法
    Method readMethod =descriptor.getReadMethod();
    System.out.println(descriptor.getName());
    try{
        Object o =readMethod.invoke(template);
        System.out.println(o);
    } catch (IllegalAccessException |InvocationTargetException e) {
        log.info("xxxxxxxxxxxxxxxx", e);
        return null;
    }
}
复制代码

PropertyDescriptor类提供了getReadMethod和getWriteMethod,其实就是对于get/set方法,至于方法名称不需要我们来关于,这样就可以避免方法名拼错的情况了。

另外PropertyDescriptor除了可以通过Introspector获取,也可以自己new来创建,其构造方法还是比较全的

java反射调用get/set方法,你还在拼接方法名吗?第3张

通常传递一个属性的名称和类对象class就可以了

复制代码

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

List<Field> fields = Arrays.stream(template.getClass().getDeclaredFields()).filter(f ->{
    String name =f.getName();
    //过滤掉不需要修改的属性
    return !"id".equals(name) && !"serialVersionUID".equals(name);
}).collect(Collectors.toList());

for(Field field : fields) {
    try{
        PropertyDescriptor descriptor = newPropertyDescriptor(field.getName(), template.getClass());
        Method readMethod =descriptor.getReadMethod();
        Object o =readMethod.invoke(template);
        System.out.println(o);
    } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
}
复制代码

通过上面两种不同的实现方式可以看到,Introspector会额外有一个class属性,但是类似serialVersionUID不会算在内;而自定义PropertyDescriptor需要通过反射拿到所有的属性,虽然不会有class属性,但是serialVersionUID会算在内,使用的时候需要注意一下。

如果你以为这就是Introspector的全部功能,那就大错特错了。Introspector不同于普通的反射,反射一次,一段时间内可重复使用,为什么不是永久呢,看下源码

复制代码
/*** Introspect on a Java Bean and learn about all its properties, exposed
     * methods, and events.
     * <p>
     * If the BeanInfo class for a Java Bean has been previously Introspected
     * then the BeanInfo class is retrieved from the BeanInfo cache.
     *
     * @parambeanClass  The bean class to be analyzed.
     * @returnA BeanInfo object describing the target bean.
     * @exceptionIntrospectionException if an exception occurs during
     *              introspection.
     * @see#flushCaches
     * @see#flushFromCaches
     */
    public static BeanInfo getBeanInfo(Class<?>beanClass)
        throwsIntrospectionException
    {
        if (!ReflectUtil.isPackageAccessible(beanClass)) {
            return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
        }
        ThreadGroupContext context =ThreadGroupContext.getContext();
        BeanInfo beanInfo;
        synchronized (declaredMethodCache) {
            beanInfo = context.getBeanInfo(beanClass);
        }
        if (beanInfo == null) {
            beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
            synchronized(declaredMethodCache) {
                context.putBeanInfo(beanClass, beanInfo);
            }
        }
        return beanInfo;
    }
复制代码

注意中间加粗标红的代码,这里除了同步之外,还做了一个本地的缓存

BeanInfo getBeanInfo(Class<?>type) {
    return (this.beanInfoCache != null)
            ? this.beanInfoCache.get(type)
            : null;
}

这个beanInfoCache 其实是一个WeakHashMap,每次gc被回收,所以上面说一段时间内可以重复使用而不是永久,也是为了避免OOM吧

总结

大概先说这么多吧,虽然算不上什么高级技术,但是能将工作中遇到的小问题解决也是成长啊!

免责声明:文章转载自《java反射调用get/set方法,你还在拼接方法名吗?》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇解决多标签分类问题的技术MySQL学习2---索引下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

vue可调整大小和可拖动的组件---vue-draggable-resizable

border:位置:调整大小=“onResize”:{{width}};{{高度}}&lt;importVueDraggableResizablefrom'vue-dragable-resizeable'导出默认值{data:height:methods:y){this.x=xthis.y=y}}}&lt;...

Map的深浅拷贝的探究

detailsmap.get:"");}}}查看输出:可以看到通过“=”复制的map内容随源map的改变而改变,而通过putAll方法和Iterator复制的map则不受源map改变的影响。如示例中的三种拷贝方法:针对map中的数据为统一的、简单的基本数据类型,当拷贝的数据通过“=”复制map的方法为浅拷贝,putAll方法为深拷贝,iterator遍历添加...

vant-picker二次封装

痛点在项目经常会遇到这样的设计,下拉选择框,在vant中没有提供直接的select组件,但是可以使用Field、Popup和Picker这三个组件组合来完成。this.show;}},watch:{selectValue:function{this.result=newVal;},result{this.$emit;}}};效果链接:https://www....

Element-ui tabs标签标题添加自定义图标

关键点:slot="label"{{item.label}}˂iclass="el-icon-questi...

硬中断与软中断的区别!

在多核系统上,一个中断通常只能中断一个CPU(也有一种特殊情况,即主机上有一个硬件通道。它可以在没有主CPU支持的情况下同时处理多个中断。软中断:1。软中断与硬中断非常相似。生成软中断的进程必须是当前正在运行的进程,因此它们不会中断CPU。...