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

深入理解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 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 aClass = hashMap.getClass();

String text = "text";

Class aClass1 = text.getClass();

// Object类源码

public final native 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 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的优化以及反射的一些劣势。

@Data

public 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 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 demoClass = methodReflectDemo.getClass();

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

}

}

}

结语

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

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

你可能也喜欢

解压缩密码不知道怎么办啊?别慌,这5个方法帮你轻松破解!
2025年8个最佳免费中文在线观看视频网站
365bet官方投注网站

2025年8个最佳免费中文在线观看视频网站

📅 07-18 👀 4806
同辈表亲英语怎么说
be365是否安全

同辈表亲英语怎么说

📅 10-05 👀 5536
三国志8重制版貂蝉怎么娶-三国志8remake貂蝉夺取攻略分享
您可以在此以批發價購買來自日本的
365bet365娱乐场

您可以在此以批發價購買來自日本的"PALMART"

📅 10-05 👀 5305
纽约证券交易所(NYSE)上市全解析:条件、流程、费用及适合企业指南
破洞牛仔裤,DIY原来这么简单!
365bet官方投注网站

破洞牛仔裤,DIY原来这么简单!

📅 06-30 👀 2848
如何经营好自己的人生,让自己成为人生赢家
365bet365娱乐场

如何经营好自己的人生,让自己成为人生赢家

📅 08-18 👀 1346
您所访问的页面不存在
be365是否安全

您所访问的页面不存在

📅 08-16 👀 7270