Java反射动态的获取到对象的信息以及灵活的调用对象方法等,但是听说效率很慢,测试下。
public class TestUser {
private Integer id;
private String name;
public String sayHi(){ return "hi";}
public Integer getId() { return id;}
public void setId(Integer id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
public class TestDemo {
public static void main(String[] argv){
testCommon();
testReflexNoCache();
}
public void testCommon(){
long start = System.currentTimeMillis();
TestUser user = null;
int i = 0;
while(i<1000000){
++i;
user = new TestUser();
}
long end = System.currentTimeMillis();
System.out.println("普通对象创建耗时:"+(end - start ) + "ms");
}
// 通过反射方式创建TestUser对象
public void testReflexNoCache() throws Exception {
long start = System.currentTimeMillis();
TestUser user = null;
int i = 0;
while(i<1000000){
++i;
user = (TestUser) Class.forName("ReflexDemo.TestUser").newInstance();
}
long end = System.currentTimeMillis();
System.out.println("无缓存反射创建对象耗时:"+(end - start ) + "ms");
}
}
输出:
普通对象创建耗时:10ms
无缓存反射创建对象耗时:966ms
创建100W个对象的情况下,反射居然慢了90倍左右!
public void testReflexWithCache() throws Exception {
long start = System.currentTimeMillis();
TestUser user = null;
Class rUserClass = Class.forName("RefleDemo.TestUser");
int i = 0;
while(i<1000000){
++i;
user = (TestUser) rUserClass.newInstance();
}
long end = System.currentTimeMillis();
System.out.println("通过缓存反射创建对象耗时:"+(end - start ) + "ms");
}
输出:
通过缓存反射创建对象耗时:41ms
通过代码我们可以发现,是Class.forName这个方法比较耗时,它实际上调用了一个本地方法,通过这个方法来要求JVM查找并加载指定的类。
所以我们在项目中使用的时候,可以把Class.forName返回的Class对象缓存起来,下一次使用的时候直接从缓存里面获取,这样就极大的提高了获取Class的效率。同理,在我们获取Constructor、Method等对象的时候也可以缓存起来使用,避免每次使用时再来耗费时间创建。
为什么Java反射性能慢效率低
程序运行期的即时编译器(JIT 编译器,Just In Time Compiler)把字节码文件编译成机器码的过程;其中即时编译器(JIT)在运行期的优化过程对于程序运行来说更重要,Java虚拟机在编译阶段的代码优化就在这里进行,由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能要比非反射操作慢,因此应该避免在对性能敏感的应用程序中频繁使用Java反射来创建对象。
public void testReflexMethod() throws Exception {
long start = System.currentTimeMillis();
Class testUserClass = Class.forName("RefleDemo.TestUser");
TestUser testUser = (TestUser) testUserClass.newInstance();
Method method = testUserClass.getMethod("sayHi");
int i = 0;
while(i<100000000){
++i;
method.invoke(testUser);
}
long end = System.currentTimeMillis();
System.out.println("反射调用方法耗时:"+(end - start ) + "ms");
}
输出:
反射调用方法耗时:330ms
public void testReflexMethod() throws Exception {
long start = System.currentTimeMillis();
Class testUserClass = Class.forName("RefleDemo.TestUser");
TestUser testUser = (TestUser) testUserClass.newInstance();
Method method = testUserClass.getMethod("sayHi");
int i = 0;
while(i<100000000){
++i;
method.setAccessible(true);
method.invoke(testUser);
}
long end = System.currentTimeMillis();
System.out.println("setAccessible=true 反射调用方法耗时:"+(end - start ) + "ms");
}
输出:
setAccessible=true 反射调用方法耗时:188ms
查看API可以了解到,jdk在设置获取字段,调用方法的时候会执行安全访问检查,而此类操作会比较耗时,所以通过setAccessible(true)的方式可以关闭安全检查,从而提升反射效率。
ReflectASM通过字节码生成的方式实现了更为高效的反射机制。执行时会生成一个存取类来 set/get 字段,访问方法或创建实例。
<!--java 反射工具包 -->
<!-- https://mvnrepository.com/artifact/com.esotericsoftware/reflectasm -->
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>reflectasm</artifactId>
<version>1.11.3</version>
</dependency>
asm框架包依赖:
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
</dependency>
创建测试类User:
class User {
public int age;
public String name;
public User() {}
public User(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" + "age=" + age + ", name='" + name + '\'' + '}';
}
public void update(int age, String name) {
this.age = age;
this.name = name;
}
}
ReflectAsm反射来调用构造方法
public class ReflectAsmDemo {
public static void main(String[] args) throws Exception {
testConstructorAccess();
}
/**
* ReflectAsm反射来调用构造方法
*/
public static void testConstructorAccess() {
ConstructorAccess<User> constructorAccess = ConstructorAccess.get(User.class);
User user = constructorAccess.newInstance();
System.out.println(user);
}
}
输出:
User{age=0, name='null'}
public class ReflectAsmDemo {
public static void main(String[] args) throws Exception {
testFieldAccess();
}
/**
* ReflectAsm反射来set/get字段值
*/
public static void testFieldAccess() {
FieldAccess fieldAccess = FieldAccess.get(User.class);
String[] fieldNames = fieldAccess.getFieldNames();
for (String fieldName : fieldNames) {
System.out.println("fieldName=" + fieldName);
}
System.out.println("FieldCount=" + fieldAccess.getFieldCount());
System.out.println("index=" + fieldAccess.getIndex("name"));
User target = new User(28,"lili");
fieldAccess.set(target, "age", 1);
int age = (Integer)fieldAccess.get(target, "age");
System.out.println(age);
}
}
输出:
fieldName=age
fieldName=name
FieldCount=2
index=1
1
FieldAccess访问私有字段,会报错Unable to find non-private field: …。
如果想访问私有字段,可以使用反射功能先放开权限。
Field field = Animal.class.getDeclaredField(“id”);
field.setAccessible(true);
public class ReflectAsmDemo {
public static void main(String[] args) throws Exception {
testMethodAccess();
testMethodIndexAccess();
}
/**
* ReflectAsm反射调用方法
* 用名称定位反射方法
*/
public static void testMethodAccess() {
User target = new User();
MethodAccess methodAccess = MethodAccess.get(User.class);
String[] methodNames = methodAccess.getMethodNames();
for (String methodName : methodNames) {
System.out.println("methodName=" + methodName);
}
methodAccess.invoke(target, "update", 1, "jack");
System.out.println(target);
}
/**
* ReflectAsm反射调用方法
* 用方法和字段的索引定位反射方法,性能高
*/
public static void testMethodIndexAccess() {
User target = new User();
MethodAccess methodAccess = MethodAccess.get(User.class);
int index = methodAccess.getIndex("update", int.class, String.class);
methodAccess.invoke(target, index, 1, "jack");
System.out.println(target);
}
}
输出:
methodName=toString
methodName=update
User{age=1, name='jack'}
User{age=1, name='jack'}
public class ReflectASMDemo {
public static void main(String[] args) throws Exception {
ReflectASMDemo test = new ReflectASMDemo();
test.testJdkReflect();
test.testReflectAsmName();
test.testReflectAsmIndex();
}
/**
* JDK反射调用方法
* @throws Exception
*/
public void testJdkReflect() throws Exception {
User target = new User();
long start = System.currentTimeMillis();
Method method = target.getClass().getMethod("update", int.class, String.class);
for (int i = 0; i < 100000000; i++) {
method.invoke(target, 1, "jack");
}
long end = System.currentTimeMillis();
System.out.println("timeout=" + (end - start));//418 450 426 430
}
/**
* ReflectAsm反射调用方法
* 用名称定位反射方法
*/
public void testReflectAsmName() {
User target = new User();
MethodAccess access = MethodAccess.get(User.class);//生成字节码的方式创建UserMethodAccess
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
access.invoke(target, "update", 1, "jack");
}
long end = System.currentTimeMillis();
System.out.println("timeout=" + (end - start));//88 64 66 50
}
/**
* ReflectAsm反射调用方法
* 用方法和字段的索引定位反射方法,性能高
*/
public void testReflectAsmIndex() {
User target = new User();
MethodAccess access = MethodAccess.get(User.class);
int index = access.getIndex("update", int.class, String.class);
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
access.invoke(target, index, 1, "jack");
}
long end = System.currentTimeMillis();
System.out.println("timeout=" + (end - start));//14 12 11 13
}
/**
* ReflectAsm反射来set/get字段值
*/
public void testFieldAccess() {
User target = new User();
FieldAccess fieldAccess = FieldAccess.get(target.getClass());
fieldAccess.set(target, "age", 1);
int age = (Integer)fieldAccess.get(target, "age");
System.out.println(age);
}
/**
* ReflectAsm反射来调用构造方法
*/
public void testConstructorAccess() {
ConstructorAccess<User> constructorAccess = ConstructorAccess.get(User.class);
User userService = constructorAccess.newInstance();
System.out.println(userService);
}
/**
* 查找方法的索引
*/
public void testIndex() {
User target = new User();
MethodAccess methodAccess = MethodAccess.get(target.getClass());
int index = methodAccess.getIndex("update", int.class, String.class);
System.out.println(index);
}
}
class User {
public int age;
public String name;
public void update(int age, String name) {
}
}
结果发现,reflectASM效率明显高于Java反射获取,并且reflectASM提供通过方法名定位索引,然后通过索引调用方法,进一步提升方法调用效率。
public class BeanUtil {
/**
* 大小写可以忽略
* 下划线 _ 被忽略
* NULL值和空字符串不会覆盖新值
*
* @param source
* @param target
* @param <T>
* @return
*/
public static <T> T copyPropertiesIgnoreCase(Object source, Object target) {
Map<String, Field> sourceMap = CacheFieldMap.getFieldMap(source.getClass());
CacheFieldMap.getFieldMap(target.getClass()).values().forEach((it) -> {
Field field = sourceMap.get(it.getName().toLowerCase().replace("_", ""));
if (field != null) {
it.setAccessible(true);
field.setAccessible(true);
try {
//忽略null和空字符串
if(field.get(source)!=null)
it.set(target, field.get(source));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
System.out.println(target.toString());
return (T) target;
}
private static class CacheFieldMap {
private static Map<String, Map<String, Field>> cacheMap = new HashMap<>();
private static Map<String, Field> getFieldMap(Class clazz) {
Map<String, Field> result = cacheMap.get(clazz.getName());
if (result == null) {
synchronized (CacheFieldMap.class) {
if (result == null) {
Map<String, Field> fieldMap = new HashMap<>();
for (Field field : clazz.getDeclaredFields()) {
fieldMap.put(field.getName().toLowerCase().replace("_", ""), field);
}
cacheMap.put(clazz.getName(), fieldMap);
result = cacheMap.get(clazz.getName());
}
}
}
return result;
}
}
}
MapToObjectIgnoreCaseUtil:
package 对象拷贝;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapToObjectIgnoreCaseUtil {
public static void main(String[] args) throws Exception {
Map<Object, Object> map = new HashMap<>();
map.put("fc_id", "TK");
map.put("fc_merch_id", "123456");
map.put("fc_merch_name", "中金");
MerchInfo merchInfo = new MerchInfo();
System.out.println(merchInfo);
merchInfo = (MerchInfo) MapToObjectIgnoreCaseUtil.mapToObject(map, merchInfo.getClass());
System.out.println(merchInfo);
}
public static Object mapToObject(Map<Object, Object> map, Class<?> beanClass) throws Exception {
if (map == null)
return null;
Map<Object, Object> map2 = new HashMap<>();
Set<Object> keySet = map.keySet();
for(Object key : keySet){
map2.put(key.toString().toLowerCase().replace("_", ""), map.get(key));
}
Object obj = beanClass.newInstance();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
int mod = field.getModifiers();
if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
continue;
}
field.setAccessible(true);
if (map2.containsKey(field.getName().toLowerCase().replace("_", ""))) {
field.set(obj, map2.get(field.getName().toLowerCase().replace("_", "")));
}
}
return obj;
}
}
class MerchInfo{
public String fcID;
public String fcMerchId;
public String fcMerchName;
public String getFcID() {
return fcID;
}
public void setFcID(String fcID) {
this.fcID = fcID;
}
public String getFcMerchId() {
return fcMerchId;
}
public void setFcMerchId(String fcMerchId) {
this.fcMerchId = fcMerchId;
}
public String getFcMerchName() {
return fcMerchName;
}
public void setFcMerchName(String fcMerchName) {
this.fcMerchName = fcMerchName;
}
@Override
public String toString() {
return "MerchInfo{" +
"fcID='" + fcID + '\'' +
", fcMerchId='" + fcMerchId + '\'' +
", fcMerchName='" + fcMerchName + '\'' +
'}';
}
}