反射是java语言中的一个特性,反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。 这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。 反射被视为动态语言的关键。
以上是java关于反射的八股文,可能不是很好背,但是绝大部分java程序员都不会在实际开发中去使用反射。
合理使用反射,可以让代码再不影响性能的基础上更加优美,拓展性更加的好。
下面从几个点来讲解反射的使用
反射的一些问题
使用反射一定性能差?
很多程序员可能
都会以反射性能不好的原因去拒绝使用反射。想要合理(风骚)的使用反射,首先需要了解反射为什么性能不好。反射的性能问题总体来说有三点
1.反射调用过程中会产生大量的临时对象,这些对象会消耗内存,增加gc频率,从而影响性能。
2.反射调用方法时会从方法数组中遍历查找,并且会检查可见性等操作会耗时。
3.反射在达到一定次数时,会动态编写字节码并加载到内存中,这个字节码没有经过编译器优化,也不能享受JIT优化。
那么如何去优化:
1.如果是使用反射去读取字段,读取方法,可以使用一个公共类在初始化的时候扫描运行过程中可能需要使用的反射的类,并缓存反射后的一些结果,如Field,Methord方法。
2.不在超高并发的请求链路中使用反射中的invoke,field的set,get等操作,因为不能享受JIT优化,不过问题不大,实际测试,影响其实很小(只要使用地方不太多,是可以使用的)。
总结一下就是,只需要合理设置缓存,考虑性能问题,在任何地方使用反射都没啥大问题,你的编程的思想才是限制你程序性能的最主要的因素。
反射能做什么?
1. 获取当前的类的class信息
Class my = this.getClass();
System.out.println("我是谁"+my.getName());
System.out.println("我有哪些成员变量"+ Arrays.toString(my.getDeclaredFields()));
System.out.println("我有哪些成员变量(包含私有,但不包括继承的)"+ Arrays.toString(my.getDeclaredFields()));
System.out.println("我有哪些方法"+ Arrays.toString(my.getMethods()));
System.out.println("我有哪些方法(包含私有,但不包括继承的方法)"+ Arrays.toString(my.getDeclaredMethods()));
System.out.println("我有哪些注解(包含继承)"+ Arrays.toString(my.getAnnotations()));
System.out.println("我有哪些注解(不包含继承)"+ Arrays.toString(my.getDeclaredAnnotations()));
这样一来,就获取了当前类的class对象,并获得了成员变量,方法,注解一系列的信息。
2.对成员变量进行操作
Field fieldOne = this.getClass().getDeclaredField("dateList");
System.out.println("字段是什么类型?"+fieldOne.getType());
//这个字段是不是Collection接口的实现类?
if (Collection.class.isAssignableFrom(fieldOne.getType())
&& fieldOne.getGenericType() instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) fieldOne.getGenericType();
Type[] fieldArgTypes = parameterizedType.getActualTypeArguments();
for (Type fieldArgType : fieldArgTypes) {
System.out.println("集合类型是什么类型的集合?"+(Class) fieldArgType);
}
}
//给这个字段赋值,注意:类型需要兼容,可以是子类
fieldOne.set(this, Lists.newArrayList("123123","1212"));
System.out.println("通过反射后设置进去了什么值,通过反射获取出来看看"+fieldOne.get(this));
通过getType可以直接拿到class,并且通过getGenericType返回的ParameterizedType中的ActualTypeArguments可以获取到字段类型
3.对方法进行操作
拿到了xxx.class对象的时候,直接调用getMethod或者getMethods方法就可以,可以拿到方法的Method对象。这里,如果是常用方法,可以把这个对象缓存起来。Method最常用的方法是method.invoke(),通过传入对象本身和方法对应的入参,调用方法。此外,如果是私有方法,可以使用method.setAccessible(true)取消权限检查
Method testMethod = my.getMethod("test",String.class,Integer.class);
testMethod.setAccessible(true);
String result = (String)testMethod.invoke(my,"haha", 1);
System.out.println(result);
4.对抽象父类的泛型获取子类的具体填充类型。
开发的时候有时候父类形如 XXX
利用反射原理,子类的T如果已经确定,调用父类的方法,是可以获得当前this对象的T的class或者是实例.
关键方法 getActualTypeArguments。这个方法可以获得当前泛型类型的数组,作用在实例对象上。
private T createModel() {
try {
Type superClass = getClass().getGenericSuperclass();
//关键代码,拿到当前类<>中的第一个类型例如,getActualTypeArguments的返回K下标为0,V下标为1
Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
Class> clazz = getRawType(type);
return (T) clazz.newInstance();
} catch (Exception e) {
log.error("AbstactYsConfig createModel error",e);
}
return null;
}
private static Class> getRawType(Type type) {
if (type instanceof Class) {
return (Class) type;
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
return (Class) rawType;
} else if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) type).getGenericComponentType();
return Array.newInstance(getRawType(componentType), 0).getClass();
} else if (type instanceof TypeVariable) {
return Object.class;
} else if (type instanceof WildcardType) {
return getRawType(((WildcardType) type).getUpperBounds()[0]);
} else {
String className = type == null ? "null" : type.getClass().getName();
throw new IllegalArgumentException("Expected a Class, ParameterizedType, or GenericArrayType, but <" + type + "> is of type " + className);
}
}