第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > 深入理解Java反射机制原理 使用方法

深入理解Java反射机制原理 使用方法

时间:2019-12-10 02:01:21

相关推荐

深入理解Java反射机制原理 使用方法

目录

一、反射基础

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();}}}

结语

自己也是玩心太大,很多时候都是抽空闲时间写的,所以写这一篇文章前前后后花了快一周吧。

此外,由于自己对内存模型那块还不是特别熟悉,所以错误在所难免。希望和各位大佬交流交流、

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。