反射
啥是反射
Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java反射机制主要提供以下功能:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时获取泛型信息。
- 在运行时调用任意一个对象的成员变量和方法。
- 在运行时处理注解。
- 生成动态代理。
Java反射机制主要涉及以下类:
- Class类:反射的核心类,可以获取类的属性,方法等信息。
- Field类:表示类的成员变量,可以用来获取和设置类之中的字段信息。
- Method类:代表类的方法,可以用来获取类中的方法信息或者执行类和对象的方法。
- Constructor类:代表类的构造方法。
Java反射机制的应用场景:
- 在运行时动态创建对象和调用方法,增强代码的灵活性和扩展性。
- 在框架开发中,例如Spring、Hibernate等,通过反射机制实现注解驱动的开发。
- 在测试框架中,例如JUnit,通过反射机制实现测试用例的自动发现和执行。
- 在性能监控和故障排查中,通过反射机制动态获取系统信息,例如JMX(Java Management Extensions)技术。
反射机制原理
反射机制允许 Java 程序在运行时调用Reflection API取得任何类的内部信息(比如成员变量、构造器、成员方法等),并能操作类的实例对象的属性以及方法。
在Java 程序中,JVM 加载完一个类后,在堆内存中就会产生该类的一个 Class 对象,一个类在堆内存中最多只会有一个 Class 对象,这个Class 对象包含了该类的完整结构信息,我们通过这个 Class 对象便可以得到该类的完整结构信息。
这个 Class 对象就像是一面镜子,我们透过这面镜子可以清楚地看到类的结构信息。因此,我们形象的将获取Class对象的过程称为:反射。
原文链接:blog.csdn.net/weixin_4539…
使用反射
下面是Class类的一些常用方法
获取成员变量 getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)//获取指定公共属性的Field对象
getDeclaredField(变量名)//获取指定包括私有,不包括继承的Field对象
获取成员方法 getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)//获取指定方法的Method对象 getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法 getDeclaredMethod(方法名,int.class,String.class)//获取指定包括私有,不包括继承的Method对象
获取构造方法 getConstructor(参数类型列表)//获取公开的构造方法 getConstructors()//获取所有的公开的构造方法 getDeclaredConstructors()//获取所有的构造方法,包括私有 getDeclaredConstructor(int.class,String.class)//获取指定包括私有,不包括继承的Constructor对象
总结: getXXXs()//获取公开的包括继承的XXX对象
getDeclaredXXXs()//获取包括私有,不包括继承的XXX对象
getXXX(xx)//获取指定xx的XXX对象(公开的)
getDeclaredXXX(xx)//获取指定xx的XXX对象(包括私有,不包括继承)
其中XXX表示 Field/Method/Constructor 三者之一
方法名get后如果接Declared可以获取私有属性或者方法
方法名最后如果有s可以获取所有的XXX对象
其他方法 getInterfaces()//返回一个包含class对象的数组,存放该类或者接口实现的接口
newInstance()//使用无参构造创建一个类的实例
getName()//返回该类的完整名
实体类
package reflect.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/*****UTF-8*****
* Description:
* Author: wnan
* Date: 2024/6/7 12:23
* Proverbs: 吃的苦中苦,方为人上人
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String[] userName;
protected Integer userAccount;
int userAddr;
public char[] email;
public void water() {
System.out.println("-----无参方法,无返回值-------");
}
public void water(String kind) {
System.out.println("-----有参方法,无返回值-------" + kind);
}
public String soil() {
return "这是函数返回值";
}
}
反射演示类
package reflect;
import java.io.FilterOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/*****UTF-8*****
* Description:
* Author: wnan
* Date: 2024/6/7 11:40
* Proverbs: 吃的苦中苦,方为人上人
*/
public class Reflect {
//调用forName方法,传入类名,返回类对象
Class<?> clazz = Class.forName("reflect.entity.User");
public Reflect() throws ClassNotFoundException {
}
public void getConstructors() {
/*
* 获得构造方法
*/
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
}
public void getMethods() {
/*
* 获取类的方法
*/
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
}
public void getFields() {
/*
*获得类字段
*/
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
}
public void createConstructor() throws Exception {
/**
* 建立对象
*/
Constructor<?> constructor = clazz.getConstructor(String[].class, Integer.class, int.class, char[].class);
Object targetConstructor = constructor.newInstance(new String[]{"huahua"}, 123, 0, new char[]{'4', '5', '6'});
System.out.println(targetConstructor);
}
public void updateField() throws Exception {
/**
* 通过反射修改属性
*/
Constructor<?> constructor = clazz.getConstructor(String[].class, Integer.class, int.class, char[].class);
Object targetConstructor = constructor.newInstance(new String[]{"huahua"}, 123, 0, new char[]{'4', '5', '6'});
// private
Field userName = clazz.getDeclaredField("userName");
System.out.println(userName);
// protected
Field userAccount = clazz.getDeclaredField("userAccount");
System.out.println(userAccount);
//default
Field userAddr = clazz.getDeclaredField("userAddr");
System.out.println(userAddr);
//public
Field email = clazz.getDeclaredField("email");
System.out.println(email);
Field email1 = clazz.getField("email");
System.out.println(email1);
// 取消权限访问控制
userAccount.setAccessible(true);
//获取更改前name的值
System.out.println("更改前:" + userAccount.get(targetConstructor));
System.out.println(targetConstructor);
userAccount.set(targetConstructor, 456);
System.out.println("更改后:" + userAccount.get(targetConstructor));
System.out.println(targetConstructor);
try {
Field userName1 = clazz.getField("userName");
} catch (NoSuchFieldError exception) {
System.out.println(exception.getMessage());
}
}
public void invokeMethod() throws Exception {
Constructor<?> constructor = clazz.getConstructor();
Object targetConstructor = constructor.newInstance();
// 有参
Method method1 = clazz.getDeclaredMethod("water", String.class);
method1.invoke(targetConstructor, "66666");
// 无参
Method method2 = clazz.getDeclaredMethod("water");
method2.invoke(targetConstructor);
// 有返回值
Method method3 = clazz.getDeclaredMethod("soil");
Object invoke3 = method3.invoke(targetConstructor);
System.out.println("invoke3 : " + invoke3);
}
}
获得Class对象
在Java中,
Class对象代表了类的元数据,即类的类型信息。有两种常见的方式来获取一个类的Class对象:使用Class.forName()方法和直接使用.class属性。这两种方式的区别在于它们的使用场景和执行时机。
Class.forName("reflect.entity.User"):
- 这个方法是在运行时动态加载类的方法。
Class.forName()方法接受一个字符串参数,这个字符串是类的完全限定名(包括包名和类名)。- 当使用
Class.forName()时,Java虚拟机(JVM)会加载并初始化该类,这意味着类的静态初始化块会被执行。- 这种方式通常用于在运行时根据配置文件或用户输入动态加载类。
reflect.entity.User.class:
- 这种方式是直接引用一个类的
Class对象,它是编译时静态绑定的。- 使用
.class属性获取Class对象时,不会触发类的初始化,只有当类首次被使用时才会初始化。- 这种方式通常用于当你已经知道要加载哪个类时,例如,当你需要获取一个已知类的
Class对象来进行反射操作时。 简而言之,Class.forName()用于动态加载和初始化类,而.class用于静态引用已知的类。选择哪种方式取决于你的具体需求和场景。如果你需要在运行时根据条件加载不同的类,那么Class.forName()是合适的;如果你已经知道要操作的类,那么使用.class会更加直接和高效。
//调用forName方法,传入类名,返回类对象
Class<?> clazz = Class.forName("java.lang.String");
Class clazz = java.lang.String.class
获取类的构造方法
public void getConstructors() {
/*
* 获得构造方法
*/
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
}
public reflect.entity.User(java.lang.String[],java.lang.Integer,int,char[])
public reflect.entity.User()
获取类的方法
public void getMethods() {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
}
public boolean reflect.entity.User.equals(java.lang.Object)
public java.lang.String reflect.entity.User.toString()
public int reflect.entity.User.hashCode()
...
获取类的字段
public void getFields() {
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
}
public char[] reflect.entity.User.email
创建对象
public void createConstructor() throws Exception {
Constructor<?> constructor = clazz.getConstructor(String[].class,Integer.class,int.class,char[].class);
Object targetConstructor = constructor.newInstance(new String[]{"huahua"},123,0,new char[]{'4','5','6'});
System.out.println(targetConstructor);
}
User(userName=[huahua], userAccount=123, userAddr=0, email=[4, 5, 6])
调用方法
public void invokeMethod() throws Exception {
Constructor<?> constructor = clazz.getConstructor();
Object targetConstructor = constructor.newInstance();
// 有参
Method method1=clazz.getDeclaredMethod("water",String.class);
method1.invoke(targetConstructor,"66666");
// 无参
Method method2 = clazz.getDeclaredMethod("water");
method2.invoke(targetConstructor);
// 有返回值
Method method3 = clazz.getDeclaredMethod("water");
Object invoke3 = method3.invoke(targetConstructor);
System.out.println("invoke3 : "+ invoke3);
}
-----有参方法,无返回值-------66666
-----无参方法,无返回值-------
invoke3 : 这是函数返回值
访问字段(字段要public修饰)
在Java的反射API中,
Class类提供了两种方法来获取类的字段(Field)信息:getDeclaredField和getField。这两个方法的主要区别在于它们获取的字段范围和访问权限。
getDeclaredField(String name):
- 这个方法用于获取类中声明的指定名称的
Field对象,包括私有(private)、保护(protected)、默认(package-private)访问级别的字段。- 它不会获取从超类继承的字段。
- 使用这个方法获取的字段,即使是非公共字段,也可以通过设置
AccessibleObject.setAccessible(true)来取消Java语言访问检查,从而在运行时访问和修改这些字段。getField(String name):
- 这个方法用于获取类中声明的指定名称的
Field对象,但它只获取公共(public)字段。- 它会获取从超类继承的公共字段。
- 使用这个方法获取的字段通常是公共的,因此可以直接访问,不需要取消Java语言访问检查。 在你提供的代码示例中,
getDeclaredField("name")用于获取Student类中声明的名为"name"的字段,无论它的访问级别是什么。然后通过setAccessible(true)取消了Java语言的访问检查,这样就可以在运行时修改这个字段,即使它是私有的。 如果你尝试使用getField("name")来获取同一个私有字段,它会抛出NoSuchFieldException,因为它只能找到公共字段。
public void updateField() throws Exception{
/**
* 通过反射修改属性
*/
Constructor<?> constructor = clazz.getConstructor(String[].class,Integer.class,int.class,char[].class);
Object targetConstructor = constructor.newInstance(new String[]{"huahua"},123,0,new char[]{'4','5','6'});
// private
Field userName = clazz.getDeclaredField("userName");
System.out.println(userName);
// protected
Field userAccount = clazz.getDeclaredField("userAccount");
System.out.println(userAccount);
//default
Field userAddr = clazz.getDeclaredField("userAddr");
System.out.println(userAddr);
//public
Field email = clazz.getDeclaredField("email");
System.out.println(email);
Field email1 = clazz.getField("email");
System.out.println(email1);
// 取消权限访问控制,开启了,才可以修改非公有变量
userAccount.setAccessible(true);
//获取更改前name的值
System.out.println("更改前:"+userAccount.get(targetConstructor));
System.out.println(targetConstructor);
userAccount.set(targetConstructor,456);
System.out.println("更改后:"+userAccount.get(targetConstructor));
System.out.println(targetConstructor);
try{
Field userName1 = clazz.getField("userName");
}catch(NoSuchFieldError exception){
System.out.println(exception.getMessage());
}
}
private java.lang.String[] reflect.entity.User.userName
protected java.lang.Integer reflect.entity.User.userAccount
int reflect.entity.User.userAddr
public char[] reflect.entity.User.email
public char[] reflect.entity.User.email
更改前:123
User(userName=[huahua], userAccount=123, userAddr=0, email=[4, 5, 6])
更改后:456
User(userName=[huahua], userAccount=456, userAddr=0, email=[4, 5, 6])
java.lang.RuntimeException: java.lang.NoSuchFieldException: userName
反射的应用场景
框架和库:许多Java框架和库使用反射来实现动态加载和配置。例如,Spring框架使用反射来实现依赖注入和AOP编程。
序列化和反序列化:Java的序列化和反序列化机制使用了反射。通过反射,可以在运行时动态地读取和写入对象的字段。
单元测试:JUnit等单元测试框架使用反射来自动化执行测试用例。通过反射,测试框架可以自动发现和执行类中的测试方法。
动态代理:Java动态代理机制利用了反射来实现代理对象的动态创建和方法调用的拦截。
反射的注意事项
在使用反射时,我们需要注意以下几点:
性能开销:反射的操作相比普通的Java代码会有一定的性能开销。因此,在性能要求较高的场景下,应尽量避免过度使用反射。
访问权限:通过反射可以访问和修改类的私有成员,但这可能违反了类的封装性。在使用反射时,应注意尊重类的访问权限。
异常处理:使用反射时,可能会抛出ClassNotFoundException、NoSuchMethodException等异常。在使用反射的代码中,要适当地处理这些异常。
方法
| 方法名 | 功能 |
|---|---|
| Class.forName | 加载并初始化指定的类,返回该类的Class对象。 |
| getClass | 获取对象的类对象。 |
| getSimpleName | 获取对象的类名(不包括包名)。 |
| getName | 获取对象的完整类名(包括包名)。 |
| newInstance | 创建一个类的实例,该类必须有无参构造函数。 |
| getConstructors | 获取类的所有公共构造方法。 |
| getDeclaredConstructors | 获取类的所有构造方法,包括私有的。 |
| getMethod | 获取一个公共方法,方法名和参数类型指定。 |
| getDeclaredMethod | 获取一个方法,包括私有的,方法名和参数类型指定。 |
| getMethods | 获取类的所有公共方法,包括继承的方法。 |
| getDeclaredMethods | 获取类的所有方法,不包括继承的方法,但包括私有和受保护的。 |
| getField | 获取一个公共字段。 |
| getDeclaredField | 获取一个字段,包括私有的。 |
| getFields | 获取所有公共字段,包括继承的字段。 |
| getDeclaredFields | 获取所有字段,不包括继承的字段,但包括私有和受保护的。 |
| invoke | 调用Method对象代表的方法。 |
| setAccessible | 将反射的对象设置为可访问,可以忽略访问权限修饰符的安全检查。 |
| getAnnotations | 获取类的所有注解。 |
| getAnnotation | 获取类特定的注解。 |
| isAnnotationPresent | 判断类是否有特定注解。 |
| getClass | 从对象的getClass()方法获取对象的Class对象。 |
| getSimpleName | 获取类的简单名称,不包括包名。 |
| getName | 获取类的全限定名称,包括包名。 |
| getSuperclass | 获取当前类的超类(父类)。 |
| isArray | 判断是否为数组类型。 |
| isAssignableFrom | 判断当前Class对象是否与指定的Class对象相同,或是其超类或接口。 |
| isInstance | 判断指定的对象是否是当前Class对象的实例。 |
| getComponentType | 获得数组的对应Class类 |
| getPackage | 获取类所属的包名称 |