目录
一、反射基础
1. 反射的用途
2. 了解反射的底层运作
直接使用类
使用反射
总结
3. 反射的缺点
二、在Java中使用反射
1. 获取类型信息
1.1. Object.getClass()
1.2. XXX.class
1.3. Class.forName()
1.4. Integer.TYPE
1.5. 通过反射类ClassAPI获取类
2. 获取类的成员变量
2.1. 获取字段:
2.2. 获取方法:
2.3. 获取构造器:
3. 操作java.lang.reflect.Field类
3.1. 获取字段类型:
3.2. 获取字段修饰符:
3.3. 获取和设置字段值:
4. 反射修改final修饰的属性值
5. 操作java.lang.reflect.Method类
5.1. 获取方法类型的信息:
6. 操作java.lang.reflect.Constructor类
结语
参考:The Reflection API
深入理解Java虚拟机第三版
一、反射基础
1. 反射的用途
反射功能通常用于检查或修改Java虚拟机运行中(runtime)的应用程序的行为,这一句话就精准的描述了反射的全部功能,更详细来说可以分为以下几点:
1. 在运行中分析类的能力,可以通过完全限定类名创建类的对象实例。
2. 在运行中查看和操作对象,可以遍历类的成员变量。
3. 反射允许代码执行非反射代码中非法的操作,可以检索和访问类的私有成员变量,包括私有属性、方法等。
注意:要有选择的使用反射功能,如果可以直接执行操作,那么最好不要使用反射。
2. 了解反射的底层运作
为了彻底理解反射的原理,可以先理解一下虚拟机的工作机制。
通常,java在编译之后,会将Java代码生成为class源文件,JVM启动时,将会载入所有的源文件,并将类型信息存放到方法区中;将所有对象实例存放在Java堆中,同时也会保存指向类型信息的指针。
以下分两种情况来分析,直接使用类和使用反射的区别,以此理解反射的实现原理。
直接使用类
正常流程下,我们要创建一个类的实例,是一定确定这个类的类型信息的,我们知道这个类的名字、方法、属性等等。我们可以很容易的创建实例,也可以通过实例很容易的获取属性、调用方法。
ArrayList<String> list = new ArrayList<>();list.add("A");int size = list.size();
使用反射
在一个方法中,如果我们不知道在实际运行(runtime)时,它将要处理的对象是谁,它的类型信息是怎么样的,那我们如何访问这个对象或为这个对象创建一个新的实例呢?
与直接使用类相反,我们需要先获取到对象在方法区的类型信息,获取到类型信息后,我们就知道这个类的构造器、属性、方法、注解、子类、父类等等信息了,这个时候,我们就可以通过这些类型信息来回调处理对象,来完成自己想要的操作了。
没错,这就是反射的原理了。反射在运行时,通过读取方法区中的字节码,来动态的找到其反射的类以及类的方法和属性等(实际上就是在运行时,根据全类型名在方法区找对应的类),用这些类型信息完成对该类实例的操作,其实就是直接使用类的一个逆向使用。
void reflectMethod(Object obj) {// 处理这个无法明确类型的实例对象// 获取类型信息Class<?> aClass = obj.getClass();Field[] fields = aClass.getFields();Method[] methods = aClass.getMethods();Annotation[] annotations = aClass.getAnnotations();Constructor<?>[] constructors = aClass.getConstructors();Class<?>[] interfaces = aClass.getInterfaces();// ...// 操作属性或方法Field field = fields[0];Object o = field.get(obj); // 获取obj的属性值}
在实际开发过程会遇到很多这种情况,譬如常用到的Bean属性工具类org.springframework.beans.BeanUtils.copyProperties(Object source, Object target),在复制对象属性前,它是并不知道source、target这两个对象有什么属性的,那么这个工具类是如何完成属性复制呢?这里其实就用到了反射功能。可以简单了解下流程:
获取target的类型获取target类中属性、getter和setter方法遍历target中的属性,查询source中是否有属性名相同且支持getter和setter的属性通过source.getter.invoke方法读取值最后通过target.setter.invoke(source.getter.invoke) 设置刚刚从source读取的值循环遍历target所有属性后,就完成了整个属性的复制
这里只是一个简单的反射运用,感兴趣的可以看看源码
总结
直接使用是在运行前就明确类型信息,然后在运行时根据这个类来操作对象;
而反射是运行时先拿到对象,根据对象得到方法区中的类型信息后,再根据属性、方法来操作该对象。
3. 反射的缺点
1. 额外的性能开销(Performance Overhead):由于反射涉及动态类型的解析,它无法执行某些Java虚拟机优化,因此反射操作的性能通常要比非反射操作慢。
2. 安全限制(Security Restrictions):反射需要运行时操作权限,此操作可能在一些安全管理器下不被允许。
3. 内部泄露(Exposure of Internals):由于反射允许代码执行非反射代码中非法的操作(例如访问私有字段和方法),因此使用反射可能会导致意外的副作用,这可能会使代码无法正常工作并可能破坏可移植性。反射性代码破坏了抽象,因此可能会随着平台的升级而改变行为。
二、在Java中使用反射
1. 获取类型信息
1.1. Object.getClass()
从一个实例对象中获取它的类。这仅适用于继承自Object的引用类型(当然Java的类默认继承于Object)。
Map<String, String> hashMap = new HashMap<>();Class<? extends Map> aClass = hashMap.getClass();String text = "text";Class<? extends String> aClass1 = text.getClass();// Object类源码public final native Class<?> getClass();
1.2. XXX.class
直接从未实例化的类获取类。
Class<Integer> integerClass = int.class;Class<HashMap> hashMapClass = HashMap.class;
1.3. Class.forName()
通过完全限定类名获取类。即包名加类名(java.util.HashMap)。否则会报找不到类错误。
Class<HashMap> hashMapClass = Class.forName("java.util.HashMap");// class类源码public static Class<?> forName(String className)throws ClassNotFoundException {Class<?> caller = Reflection.getCallerClass();return forName0(className, true, ClassLoader.getClassLoader(caller), caller);}
1.4. Integer.TYPE
基本类型的包装类通过TYPE获取类。都是Java早期版本的产物,已过时。
// Integer@SuppressWarnings("unchecked")public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");// Double@SuppressWarnings("unchecked")public static final Class<Double> TYPE = (Class<Double>) Class.getPrimitiveClass("double");
1.5. 通过反射类ClassAPI获取类
注意,只有在已经直接或间接获得一个类的情况下,才可以访问这些API。
try {Class<?> className = Class.forName("java.lang.String");// 获取父类Class<?> superclass = className.getSuperclass();// 返回调用类的成员变量,包括所有公共的类、接口和枚举Class<?>[] classes = className.getClasses();// 返回调用类的依赖,包括所有类、接口和显式声明的枚举Class<?>[] declaredClasses = className.getDeclaredClasses();} catch (ClassNotFoundException e) {e.printStackTrace();}
2. 获取类的成员变量
2.1. 获取字段:
2.2. 获取方法:
2.3. 获取构造器:
3. 操作java.lang.reflect.Field类
说明:Field字段具有类型和值。Field提供访问属性对象类型信息的方法;以及获取和设置字段值的方法。
3.1. 获取字段类型:
字段可以是原始类型或引用类型。
有八种基本类型:boolean,byte,short,int,long,char,float,和double。
引用类型是java.lang.Object类的直接或间接子类,包含接口,数组和枚举类型等。
Class<?> className = Class.forName("java.util.HashMap");Field table = className.getDeclaredField("table");Class<?> type = table.getType();
3.2. 获取字段修饰符:
访问修饰符:public,protected,和private仅用于字段的控制运行时行为的修饰符:transient和volatile限制单实例的修饰符:static禁止值修改的修饰符:final注解Class<?> className = Class.forName("java.util.HashMap");Field table = className.getDeclaredField("table");// 获取属性的名字String name = table.getName();// 获取属性的类型Class<?> type = table.getType();// 获取修饰符int modifiers = table.getModifiers();System.out.println(Modifier.toString(modifiers));// 获取注解Override annotation = table.getDeclaredAnnotation(Override.class);Annotation[] declaredAnnotations = table.getDeclaredAnnotations();
3.3. 获取和设置字段值:
给定一个类的实例,可以使用反射来设置该类中字段的值。通常仅在特殊情况下无法以常规方式设置值时才执行此操作。因为这样的访问通常会违反该类的设计意图,所以应绝对谨慎地使用它。
HashMap<String, Object> map = new HashMap<>();map.put("1", 2);Class<? extends HashMap> mapClass = map.getClass();Field capacity = mapClass.getDeclaredField("MAXIMUM_CAPACITY");capacity.setAccessible(true); // 访问私有成员Object o1 = capacity.get(map); // 获取属性值capacity.set(map, 20); // 设置属性值
上面的设置属性值将会报错,因为hashmap中的MAXIMUM_CAPACITY参数是一个被static修饰的成员。
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final int field java.util.HashMap.MAXIMUM_CAPACITY to java.lang.Integer
注意:通过反射设置字段的值会有一定的性能开销,因为必须进行各种操作,例如验证访问权限。从运行时的角度来看,效果是相同的,并且操作是原子的,就好像直接在类代码中更改了值一样。除此之外,反射会破坏Java原本的设定,列如可以重新设置final属性的值等。
4. 反射修改final修饰的属性值
反射功能强大,能修改private以及final修饰的变量。如下代码中,展示了JVM的优化以及反射的一些劣势。
@Datapublic class FieldReflectDemo {// 引用直接指向常量池中的常量值private final String constantStr = "FinalConstantStringField";// JVM优化了getter方法,直接将对constantStr引用全部替换成了常量// public String getConstantStr() {return "FinalConstantStringField";}// 在堆中新建了一个对象private final String newStr = new String("FinalNewStringField");public FieldReflectDemo(){}public static void main(String[] args) {FieldReflectDemo fieldReflectDemo = new FieldReflectDemo();try {Class<?> className = fieldReflectDemo.getClass();Field constantStr = className.getDeclaredField("constantStr");Field newStr = className.getDeclaredField("newStr");// 获取实例对象的字段值System.out.println("constantStr原:" + constantStr.get(fieldReflectDemo));System.out.println("newStr原:" + newStr.get(fieldReflectDemo));constantStr.setAccessible(true);newStr.setAccessible(true);constantStr.set(fieldReflectDemo, "New Filed Name");newStr.set(fieldReflectDemo, "New Filed Name");System.out.println("constantStr反射修改:" + constantStr.get(fieldReflectDemo));System.out.println("newStr反射修改:" + newStr.get(fieldReflectDemo));} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}System.out.println("constantStr实例对象值:" + fieldReflectDemo.getConstantStr());System.out.println("newStr实例对象值:" + fieldReflectDemo.getNewStr());}/*** 输出* constantStr原:FinalConstantStringField* newStr原:FinalNewStringField* constantStr反射修改:New Filed Name* newStr反射修改:New Filed Name* constantStr实例对象值:FinalConstantStringField* newStr实例对象值:New Filed Name*/}
因为JVM在编译时期, 就把final类型的直接赋值的String进行了优化, 在编译时期就会把String处理成常量。反射成功将其值修改成功了,但是在它的get方法中,返回的不是当前变量,而是返回JVM优化好的一个常量值。
5. 操作java.lang.reflect.Method类
说明:
Method方法具有参数和返回值,并且方法可能抛出异常;
Method提供获取参数信息、返回值的方法;
它也可以调用(invoke)给定对象的方法。
5.1. 获取方法类型的信息:
方法声明包含了方法名、修饰符、参数、返回类型以及抛出的多个异常。
以及通过反射调用实例对象的方法。
public class MethodReflectDemo {public MethodReflectDemo() {private void getNothing(String name) {public int getNumByName(String name) throws NullPointerException {if (StringUtils.isEmpty(name))throw new NullPointerException("名字为空");return name.length();}public static void main(String[] args) {MethodReflectDemo methodReflectDemo = new MethodReflectDemo();try {Class<? extends MethodReflectDemo> demoClass = methodReflectDemo.getClass();Method method = demoClass.getDeclaredMethod("getNumByName", String.class);String name = method.getName();System.out.println("方法名:" + name);// 修饰符int modifiers = method.getModifiers();System.out.println("所有修饰符:" + Modifier.toString(modifiers));// 参数Parameter[] parameters = method.getParameters();// 返回类型Class<?> returnType = method.getReturnType();System.out.println("返回类型:" + returnType.getTypeName());// 异常Class<?>[] exceptionTypes = method.getExceptionTypes();System.out.println("");// 实例对象调用方法Object invoke = method.invoke(methodReflectDemo, "名称");System.out.println(invoke);} catch (NoSuchMethodException e) {e.printStackTrace();}}}
6. 操作java.lang.reflect.Constructor类
Constructor与Method相似,但有几点不同:
构造函数没有返回值构造函数无法被实例对象执行,它的调用只能为给定的类创建对象的新实例。
public class ConstructorReflectDemo {public ConstructorReflectDemo() {}private void getNothing(String name) { }public int getNumByName(String name) throws NullPointerException {if (StringUtils.isEmpty(name))throw new NullPointerException("名字为空");return name.length();}public static void main(String[] args) {ConstructorReflectDemo methodReflectDemo = new ConstructorReflectDemo();try {Class<? extends ConstructorReflectDemo> demoClass = methodReflectDemo.getClass();Constructor<? extends ConstructorReflectDemo> constructor = demoClass.getConstructor();String name = constructor.getName();System.out.println("构造方法名:" + name);// 修饰符int modifiers = constructor.getModifiers();System.out.println("所有修饰符:" + Modifier.toString(modifiers));// 参数Parameter[] parameters = constructor.getParameters();// 异常Class<?>[] exceptionTypes = constructor.getExceptionTypes();System.out.println("");// 构造方法无法被调用,只可以创建新实例ConstructorReflectDemo constructorReflectDemo = constructor.newInstance();} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {e.printStackTrace();}}}
结语
自己也是玩心太大,很多时候都是抽空闲时间写的,所以写这一篇文章前前后后花了快一周吧。
此外,由于自己对内存模型那块还不是特别熟悉,所以错误在所难免。希望和各位大佬交流交流、