你好,我是看山。
今天我们通过基准测试验证下到底有多慢。
结果是我万万没想到的,跑了好几遍基准测试,不得不承认之前是自己不严谨了。
演示代码
先定义使用场景:为一个JavaBean的属性赋值。
@Data
public static class User {
private String username;
private int level;
}
为User的属性赋值,我们有三种方式。
原生方式,作为基准:
final User user = new User();
user.setUsername("看山");
user.setLevel(100);
通过反射:
final Class<User> clazz = User.class;
final Constructor<User> constructor = clazz.getConstructor();
final User user = constructor.newInstance();
final Field usernameField = clazz.getDeclaredField("username");
usernameField.setAccessible(true);
usernameField.set(user, "看山");
final Field levelField = clazz.getDeclaredField("level");
levelField.setAccessible(true);
levelField.set(user, 100);
反射也可以通过调用setter方法赋值:
final Class<User> clazz = User.class;
final Constructor<User> constructor = clazz.getConstructor();
final User user = constructor.newInstance();
final Method setUsernameMethod = clazz.getMethod("setUsername", String.class);
setUsernameMethod.invoke(user, "看山");
final Method setLevelMethod = clazz.getMethod("setLevel", int.class);
setLevelMethod.invoke(user, 100);
使用内省赋值:
final BeanInfo beanInfo = Introspector.getBeanInfo(User.class);
final User user = new User();
final PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor prop : props) {
if ("username".equals(prop.getName())) {
final Method method = prop.getWriteMethod();
method.invoke(user, "看山");
} else if ("level".equals(prop.getName())) {
final Method method = prop.getWriteMethod();
method.invoke(user, 100);
}
}
内省方式也可以直接指定属性:
final User user = new User();
final PropertyDescriptor usernameProp = new PropertyDescriptor("username", User.class);
final Method usernameWriteMethod = usernameProp.getWriteMethod();
usernameWriteMethod.invoke(user, "看山");
final PropertyDescriptor levelProp = new PropertyDescriptor("level", User.class);
final Method levelWriteMethod = levelProp.getWriteMethod();
levelWriteMethod.invoke(user, 100);
好了,基准测试的几种情况已经准备好了,为了达到更充分的验证,我们分别循环执行10、100、200 、500次,我们跑一下基准测试看看效果。
测试效果
保留下500次循环的数据(回复:Java可以获取源码)
Benchmark Score Error
BeanSetJmhTest.testBase 671.299 ± 14.201 基准
BeanSetJmhTest.testAccessFieldCacheByReflectField 6451.184 ± 212.541 反射-缓存-属性赋值
BeanSetJmhTest.testMethodCacheByReflect 13381.968 ± 1921.017 反射-缓存-方法赋值
BeanSetJmhTest.testMethodCacheByIntrospector 13523.807 ± 2146.288 内省-缓存-方法赋值
BeanSetJmhTest.testMethodByReflect 44874.497 ± 14215.009 反射-方法赋值
BeanSetJmhTest.testAccessFieldByReflect 57989.549 ± 282731.822 反射-属性赋值
BeanSetJmhTest.testAccessFieldCacheByIntrospectorDirectProp 121879.007 ± 28027.596 内省-缓存-指定属性赋值
BeanSetJmhTest.testAccessFieldCacheByIntrospectorProps 167602.264 ± 30272.412 内省-缓存-属性循环赋值
BeanSetJmhTest.testAccessFieldByIntrospectorProps 204765.110 ± 53973.520 内省-属性循环赋值
BeanSetJmhTest.testAccessFieldByIntrospectorDirectProp 783250.528 ± 40212.597 内省-指定属性赋值
可视化结果:
基准测试结果
从结果看:
- 在设置属性方面,反射性能优于内省;【上面结果是在JDK21测试的,试过JDK8、JDK17结果相似,不太确定有些文章说的内省性能优于反射是怎么测试的。】;
- 有缓存的逻辑性能会明显优于没有缓存的逻辑,无论是反射还是内省;
- 非必要情况,不要使用反射和内省,直接用JavaBean的setter赋值,性能差的太多。