深入理解Java反射机制原理、使用方法 您所在的位置:网站首页 反射镜的功能和作用 深入理解Java反射机制原理、使用方法

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

2024-07-17 09:36| 来源: 网络整理| 查看: 265

目录

一、反射基础

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 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 hashMap = new HashMap(); Class getClass();

1.2. XXX.class

直接从未实例化的类获取类。

Class integerClass = int.class; Class hashMapClass = HashMap.class;

1.3. Class.forName()

通过完全限定类名获取类。即包名加类名(java.util.HashMap)。否则会报找不到类错误。

Class 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 TYPE = (Class) Class.getPrimitiveClass("int"); // Double @SuppressWarnings("unchecked") public static final Class TYPE = (Class) 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. 获取字段:

Class API

是否是列表

是否获取父类属性

能否获取私有成员

getDeclaredField()

no

no

yes

getField()

no

yes

no

getDeclaredFields()

yes

no

yes

getFields()

yes

yes

no

2.2. 获取方法:

Class API

是否是列表 

是否获取父类属性

能否获取私有成员

getDeclaredMethod()

no

no

yes

getMethod()

no

yes

no

getDeclaredMethods()

yes

no

yes

getMethods()

yes

yes

no

2.3. 获取构造器:

Class API

是否是列表

是否获取父类属性

能否获取私有成员

getDeclaredConstructor()

no

N/A1

yes

getConstructor()

no

N/A1

no

getDeclaredConstructors()

yes

N/A1

yes

getConstructors()

yes

N/A1

no

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 map = new HashMap(); map.put("1", 2); 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 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[] exceptionTypes = constructor.getExceptionTypes(); System.out.println(""); // 构造方法无法被调用,只可以创建新实例 ConstructorReflectDemo constructorReflectDemo = constructor.newInstance(); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { e.printStackTrace(); } } }

结语

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

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有