java反射速度探寻-Method/Field读写谁快?

背景

因为工作涉及基础组件的维护,经常要写一些工具,少不了的要用到反射,因此,特意针对反射常用到的 Field 和Method,水一篇文章来测试一下使用它们读取数据的速度。

用这个问题询问一下GPT,得到如下答案:

在Java中,使用Method.invoke来调用方法通常会比使用Field.setField.get来读写字段慢。这是因为Method.invoke涉及到方法调用的动态解析和参数传递,还可能涉及到方法的执行逻辑,这些都增加了额外的开销。

相比之下,Field.setField.get直接访问类的字段,它们不需要进行方法的动态解析,也不涉及方法的执行。因此,从性能角度来看,使用Field.setField.get来读写字段通常会比使用Method.invoke来调用方法更高效。

然而,需要注意的是,使用反射(无论是Method还是Field)都会带来一定的性能开销,特别是在频繁访问的情况下。因此,在性能敏感的应用中,应该尽量避免使用反射,或者至少尽量减少反射的使用次数。如果可能的话,使用直接的字段访问或方法调用通常是更高效的选择。

答案已经知道了,但我觉得还是要去实验一下,看看 Method 究竟慢了多少。

测试

准备

我们构建一个实体类:

java 复制代码
@Data
public static class TestBean {
    private String col1;
    private String col2;
    private String col3;
    private String col4;
    private String col5;
    private String col6;
}

编写一个耗时计算方法:

java 复制代码
interface NoArgFunc {
    void call();
}

private static void duration(String msg, NoArgFunc func) {
    System.out.println(msg);
    long start = System.currentTimeMillis();
    func.call();
    System.out.printf("耗时: %d ms\n", System.currentTimeMillis() - start);
}

写数测试

我们为TestBean的6个字段写入100w次,进行测试:

java 复制代码
private static void doReflectionTest() {
    Field[] fields = TestBean.class.getDeclaredFields();
    for (Field field : fields) {
        field.setAccessible(true);
    }
    Method[] setters = Arrays.stream(TestBean.class.getMethods())
            .filter(method -> method.getName().startsWith("set"))
            .toList().toArray(new Method[0]);
    int cnt = 100 * 10000;
    TestBean bean = new TestBean();
    duration("测试 Field: ", () -> {
        try {
            for (int i = 0; i < cnt; i++) {
                for (Field field : fields) {
                    field.set(bean, "123");
                }
            }
        } catch (Exception ignored) {
        }
    });
    System.out.println("bean: " + bean);
    System.out.println("==========================================");
    duration("测试 Method: ", () -> {
        try {
            for (int i = 0; i < cnt; i++) {
                for (Method setter : setters) {
                    setter.invoke(bean, "abc");
                }
            }
        } catch (Exception ignored) {
        }
    });
    System.out.println("bean: " + bean);
}

结果如下:

可见,进行100w*6次调用,Field的速度接近Method的3倍!那确实差距很大。

读数测试

添加代码,同样读取TestBean的6个字段100w次,进行测试:

java 复制代码
private static void doReflectionTest() {
    Field[] fields = TestBean.class.getDeclaredFields();
    for (Field field : fields) {
        field.setAccessible(true);
    }
    Method[] setters = Arrays.stream(TestBean.class.getMethods())
            .filter(method -> method.getName().startsWith("set"))
            .toList().toArray(new Method[0]);
    Method[] getters = Arrays.stream(TestBean.class.getMethods())
            .filter(method -> method.getName().startsWith("get"))
            .toList().toArray(new Method[0]);
    int cnt = 100 * 10000;
    TestBean bean = new TestBean();
    duration("测试 Field: ", () -> {
        try {
            for (int i = 0; i < cnt; i++) {
                for (Field field : fields) {
                    field.set(bean, "123");
                }
            }
        } catch (Exception ignored) {
        }
    });
    System.out.println("bean: " + bean);
    System.out.println("==========================================");
    duration("测试 Method: ", () -> {
        try {
            for (int i = 0; i < cnt; i++) {
                for (Method setter : setters) {
                    setter.invoke(bean, "abc");
                }
            }
        } catch (Exception ignored) {
        }
    });
    System.out.println("bean: " + bean);
    System.out.println("==========================================");
    duration("测试 Field getter: ", () -> {
        try {
            for (int i = 0; i < cnt; i++) {
                for (Field field : fields) {
                    field.get(bean);
                }
            }
        } catch (Exception ignored) {
        }
    });
    System.out.println("==========================================");
    duration("测试 Method getter: ", () -> {
        try {
            for (int i = 0; i < cnt; i++) {
                for (Method getter : getters) {
                    getter.invoke(bean);
                }
            }
        } catch (Exception ignored) {
        }
    });
}

运行结果如下:

好吧,差距更大。

硬编码setter/getter测试

我们按GPT推荐的,手写一堆setter、getter调用测试一下:

java 复制代码
private static void doHardCodeTest() {
    TestBean bean = new TestBean();
    int cnt = 100 * 10000;
    duration("硬编码测试 setter: ", () -> {
        for (int i = 0; i < cnt; i++) {
            bean.setCol1("abc");
            bean.setCol2("abc");
            bean.setCol3("abc");
            bean.setCol4("abc");
            bean.setCol5("abc");
            bean.setCol6("abc");
        }
    });
    duration("硬编码测试 getter: ", () -> {
        for (int i = 0; i < cnt; i++) {
            bean.getCol1();
            bean.getCol2();
            bean.getCol3();
            bean.getCol4();
            bean.getCol5();
            bean.getCol6();
        }
    });
}

运行结果如下:

耗时要比反射方式少一半,看来反射是真的耗时啊!

结果

GPT没有骗我,经过简单的测试,Field要比Method调用快3倍左右,而硬编码调用setter/getter还要比Field调用快1倍多。

所以说,BeanUtils 的 copyProperties 还是很耗时的,如果有需要用到copyProperties的,还是建议用cglibcopier或者MMapstruct,前者动态生成代码,后者编译时生成代码,效率都很不错,下次有时间研究一下。

相关推荐
君顾11 小时前
代驾跑腿业务系统实战开发指南
java·开发语言·代驾跑腿
NGINX开源社区2 小时前
NGINX Ingress Controller 5.5:安全性与性能提升,迁移更轻松
java·服务器·nginx
云烟成雨TD2 小时前
Micrometer 系列【25】Spring Boot Actuator | Spring MVC、Spring WebFlux 指标
java·spring boot·micrometer
liudashuang20172 小时前
Kafka Producer 隐藏深坑:Sender 线程自阻塞(自死锁)导致 BufferExhaustedException 完整复盘
java·分布式·kafka·linq
风筱2 小时前
Idea的CC GUI插件安装Claude Code SDK失败
java·ide·ai编程
m0_527034332 小时前
异步任务审核系统设计:消息队列、超时重试与失败补偿
java·大数据·开发语言
xbgRS2 小时前
springBoot项目配置加载优先级
java·spring boot
zzzll11112 小时前
Loop Engineering:循环工程的原理、实践与应用
java·数据库·python
假客套2 小时前
记一次若依 Excel 导出踩坑:Permission denied、401 报错完整排查记录
java·若依·linux服务器
岁岁养乐多3 小时前
深入解析 Spring @Cacheable 注解:从声明式缓存 到多维替代方案
java