当前位置: 首页 > news >正文

Java的反射

一、什么是反射

反射是获取类信息的一种能力。反射让 Java 程序可以在运行时动态地加载、访问和修改类、方法、字段等元数据。这种动态能力使得 Java 具备了非常灵活的编程方式,可以在不知道具体实现类的情况下,依然能够操作对象的内部细节。

简而言之,反射就是一种让代码能够“自我了解”的方式。程序运行时能够告诉用户某一个类自己有多少方法、构造器,甚至可以调用自己的方法和修改自己的字段。

二、类信息来自哪里

1、类信息有什么

类的所有元数据和相关信息,这些信息描述了类的结构、行为和属性。通过反射,Java 提供了一个强大的机制,允许开发者在运行时动态获取这些信息。类信息不仅包括类的基本结构(如类名、父类、实现的接口等),还包括类中定义的字段、方法、构造器等详细信息。

1.基本信息

1.1 类名

类的名称,如java.lang.String 或自定义类名。

通过反射获取:clazz.getName()

1.2 父类

类的直接父类,即该类继承的类。每个类都继承自 Object 类(除非显式指定其他父类)。

通过反射获取:clazz.getSuperclass()

1.3 接口

类实现的接口。一个类可以实现多个接口。

通过反射获取:clazz.getInterfaces()

1.4 修饰符

类的访问修饰符(如 public, private, abstract, final 等),以及是否为内部类等信息。

通过反射获取:clazz.getModifiers()

1.5 构造方法

类的构造方法信息,包括构造方法的名称、参数类型、返回类型等。

通过反射获取:clazz.getConstructors(), clazz.getDeclaredConstructors()

2. 字段信息

2.1 字段

类中定义的字段(成员变量)的信息,包括字段名、类型、修饰符等。

通过反射获取:clazz.getFields()(公有字段)、clazz.getDeclaredFields()(所有字段)

2.2 字段修饰符

字段的访问修饰符(如 public, private, protected, static 等)。

通过反射获取:field.getModifiers()

2.3 字段类型

字段的数据类型,可以是基本类型或自定义的类类型。

通过反射获取:field.getType()

2.4 字段值

可以动态地获取或修改对象的字段值,包括私有字段的访问。

通过反射获取:field.get(Object obj), field.set(Object obj, Object value)

3. 方法信息

3.1 方法

类中定义的方法,包括方法名、返回类型、参数类型等。

通过反射获取:clazz.getMethods()(公有方法)、clazz.getDeclaredMethods()(所有方法)

3.2 方法修饰符

方法的访问修饰符(如 public, private, static, final 等)。

通过反射获取:method.getModifiers()

3.3 方法参数类型

方法的参数类型,指明该方法需要的参数。

通过反射获取:method.getParameterTypes()

3.4 方法返回类型

方法的返回类型,即该方法返回的值的类型。

通过反射获取:method.getReturnType()

3.5 方法调用

通过反射可以动态地调用类中的方法,甚至是私有方法。

通过反射调用:method.invoke(Object obj, Object... args)

4. 注解信息

4.1 类注解

类上定义的注解,反射可以用来检查类是否有特定的注解,并获取注解的内容。

通过反射获取:clazz.getAnnotations()

4.2 字段、方法、构造方法的注解

字段、方法和构造方法也可以有注解,反射可以获取它们的注解信息。

通过反射获取:field.getAnnotations(), method.getAnnotations(), constructor.getAnnotations()

5. 内部类信息

5.1 内部类

类内部定义的嵌套类,包括静态内部类和非静态内部类。

通过反射获取:clazz.getDeclaredClasses()

6. 枚举信息

6.1 枚举类型

如果类是枚举类型,反射可以获取枚举的成员。

通过反射获取:clazz.getEnumConstants()

7. 数组信息

7.1 数组类型

如果类是数组类型,反射可以获取数组的元素类型。

通过反射获取:clazz.getComponentType()

8. 类加载器信息

8.1 类加载器

类加载器负责将类的字节码加载到 JVM 中。通过反射可以获取类的类加载器。

通过反射获取:clazz.getClassLoader()

9. 类的安全信息

9.1 安全性检查

Java 的 SecurityManager 可以用来限制反射的某些操作,尤其是在沙箱环境中。

通过反射获取:clazz.isAnnotation(), clazz.isInterface(), clazz.isEnum()

2、类信息来自哪里

1.Java程序运行的三个阶段

1.1源代码编译阶段(磁盘)

在这个阶段,Java 源代码(通常是 .java 文件)将被 Java 编译器(javac)编译成字节码文件(.class 文件)。这个字节码是平台无关的,可以在任何支持 Java 的操作系统上运行。

源代码文件:开发者用 .java 后缀编写的代码文件。

编译过程:javac 编译器将 .java 文件编译成 .class 文件,.class 文件中存储的是 JVM(Java Virtual Machine)能够理解的字节码。

javac HelloWorld.java   # 生成 HelloWorld.class 字节码文件
1.2 类加载阶段(Class类对象阶段)

Java 程序的类加载过程由 JVM 完成。JVM 会通过类加载器(ClassLoader)来加载字节码文件(.class 文件)。这个阶段可以分为几个子步骤:

1.2.1 加载

当 JVM 启动时,它通过类加载器加载所需的类文件。在类加载过程中,JVM 会将 .class 文件读取到内存中,并生成一个对应的 Class 对象,这个对象包含类的元数据。加载的方式主要是以下几种:

引导类加载器:负责加载 JDK 核心类库(如 java.lang 包下的类)。

扩展类加载器:加载 JDK 扩展库中的类(如 lib/ext 目录下的类)。

系统类加载器:负责加载用户类路径(classpath)中的类。

1.2.2 链接

链接过程会对加载的类进行验证、准备和解析:

验证:验证字节码文件的合法性,确保它符合 JVM 的要求,防止非法代码的执行。

准备:为类的静态变量分配内存,并赋初始值。

解析:将常量池中的符号引用替换为直接引用。

1.2.3 初始化

在类被加载并链接后,JVM 会执行类的初始化操作。初始化的主要任务是执行静态初始化器(如 static 块或静态变量的初始化)。这一步保证了类的静态内容被正确初始化。

1.3 运行时阶段

经过前面的编译和加载,Java 程序就准备好运行了。此时,JVM 将开始执行类中的 main 方法或其他指定的入口方法,进入程序的实际执行阶段。

1.3.1 解释执行与即时编译

JVM 通过解释执行或者即时编译来执行字节码:

解释执行:JVM 解释器按行解释字节码并执行。

即时编译:JIT 编译器将字节码转换为本地机器代码并直接执行,这可以显著提高性能,因为编译后代码的执行速度比解释执行要快。

1.3.2 垃圾回收

在 Java 程序的执行过程中,JVM 会负责垃圾回收(GC)。JVM 定期回收不再使用的对象和内存,以保证程序的内存使用效率。

1.3.3 线程调度与执行

如果程序使用了多线程,JVM 还会调度和管理线程的执行。多线程的并发执行与同步也是 JVM 执行的关键部分。

2.类信息的来源

在 Java 中,每个类在加载时,JVM 会为其生成一个 Class 对象。这个Class 对象包含了该类的元数据,包括类名、字段、方法、构造器等信息。每个 Class 对象都是唯一的。

通过类名直接获取: Class.forName("类名")

通过对象获取:类对象.getClass()

通过 .class 获取: 类名.class

只有获取了类对象才能获取类信息

 

 

三、如何获取类信息

以Student类为例:

package 反射;

public class Student {
	// 属性
	private String name;
	public int age;
	double height;
	protected char sex;

	// 方法
	private void run() {
		System.out.println("学生跑的老快");
	}

	public int getAge() {
		return this.age;
	}

	public String getNameString(String name) {
		return this.name = name;
	}

	protected void run(String a, int b) {
		System.out.println(a + " " + b);
	}
// 构造方法
	Student() {

	}

	Student(double height, char sex) {
		this.height = height;
		this.sex = sex;
	}

	Student(String name, int age, double height, char sex) {
		this.name = name;
		this.age = age;
		this.height = height;
		this.sex = sex;
	}

	public Student(int age, double height, char sex) {
		this.age = age;
		this.height = height;
		this.sex = sex;
	}

	private Student(String name, double height, char sex) {
		this.name = name;
		this.height = height;
		this.sex = sex;
	}

	protected Student(String name, int age, char sex) {
		this.name = name;
		this.age = age;
		this.sex = sex;
	}

	private Student(char sex) {
		this.sex = sex;
	}
}

1、获取Class对象

Class clazz = Class.forName("反射.Student");

// Class clazz = Student.class;

// Student stu1 = new Student();
// Class clazz = stu1.getClass();

2、获取构造方法

获取所有构造方法的信息

//	获取构造器
Constructor[] constructors = clazz.getDeclaredConstructors();
System.out.println(Arrays.toString(constructors));

根据不同构造方法中的参数类型进行对应构造方法的获取

Constructor constructor1 = clazz.getDeclaredConstructor(String.class,int.class,double.class,char.class);
System.out.println(constructor1);
			
Constructor constructor2 = clazz.getDeclaredConstructor(int.class,double.class,char.class);
System.out.println(constructor2);
Constructor constructor3 = clazz.getDeclaredConstructor(String.class,double.class,char.class);
System.out.println(constructor3);
Constructor constructor4 = clazz.getDeclaredConstructor(String.class,int.class,char.class);
System.out.println(constructor4);

3、获取字段

加上declare能获取所有,不加只能获取public修饰的内容

Field[] fields = clazz.getDeclaredFields();
System.out.println(Arrays.toString(fields));

Field[] fields1 = clazz.getFields();
System.out.println(Arrays.toString(fields1));

获取指定名字的字段

Field nameField = clazz.getDeclaredField("name");
System.out.println(nameField);

4、获取方法

获取所有普通方法的信息

//	获取方法
Method[] methods= clazz.getDeclaredMethods();
System.out.println(Arrays.toString(methods));

Method[] methods1= clazz.getMethods();
System.out.println(Arrays.toString(methods1));

获取指定方法

Method runMethod = clazz.getDeclaredMethod("run");
Method runMethod1 = clazz.getDeclaredMethod("run",String.class,int.class);
System.out.println(runMethod);
System.out.println(runMethod1);
			
Method getAge = clazz.getDeclaredMethod("getAge");
System.out.println(getAge);
Method getNameString = clazz.getDeclaredMethod("getNameString",String.class);
System.out.println(getNameString);

四、如何使用类信息

1、暴力反射

private 修饰的数据是在其他类中访问不到的,所以只能忽略访问权限修饰符的安全检查。

暴力反射针对的就是private修饰的数据。

Class clazz = Class.forName("反射.Student");
		
Constructor constructor = clazz.getDeclaredConstructor();
Student student =(Student) constructor.newInstance();
Constructor constructor1 = clazz.getDeclaredConstructor(double.class,char.class);
Student student1 = (Student) constructor1.newInstance(185.5,'男');

Constructor constructor2 = clazz.getDeclaredConstructor(char.class);
// 暴力反射
constructor2.setAccessible(true);
Student student2 = (Student) constructor2.newInstance('女');

2、对属性赋值取值

set() 方法 需要两个参数,分别是对象和当前要赋的值

Field nameField = clazz.getDeclaredField("name");

//	private 修饰的name需要暴力反射
nameField.setAccessible(true);

nameField.set(student, "张三");
System.out.println(nameField.get(student));
Field ageField = clazz.getField("age");
ageField.setAccessible(true);
ageField.set(student, 10);
System.out.println(ageField.get(student));
		
Field heightField = clazz.getDeclaredField("height");
heightField.set(student, 158.3);
System.out.println(heightField.get(student));
		
Field sexField = clazz.getDeclaredField("sex");
sexField.set(student, '女');
System.out.println(sexField.get(student));

3、动态调用方法

在程序运行时动态地决定调用哪个方法,甚至可以通过参数传递动态执行的行为

Method run = clazz.getDeclaredMethod("run");
run.setAccessible(true);
run.invoke(student);
		
Method getAge = clazz.getDeclaredMethod("getAge");
getAge.invoke(student);
		
Method getStringName = clazz.getDeclaredMethod("getNameString", String.class);
String name= (String) getStringName.invoke(student, "老铁");
System.out.println(name);
		
Method run1 = clazz.getDeclaredMethod("run", String.class,int.class);
run1.invoke(student, "666",99);

五、反射的特点

1、反射的优势

优势

说明

动态性

在运行时动态加载和调用方法与字段。

灵活性

可以实现如依赖注入、工厂模式等设计模式

支持多框架

Spring、Hibernate 等框架广泛使用反射

可访问私有成员

反射可以让你访问甚至修改类的私有字段和方法

 2、反射的不足

缺点

说明

性能开销

反射机制的调用相对较慢,特别是在频繁使用时

类型安全问题

使用反射时,编译时无法检查类型,可能导致运行时错误

代码复杂性

过度使用反射可能导致代码变得难以维护和调试

 3、反射中常见的类

类 

说明

Class 类

代表类的元数据,获取类信息、创建实例等

Method 类

代表类中的方法,提供方法信息,动态调用

Field 类

代表类中的字段,提供字段信息和动态修改字段的能力

Constructor 类

代表类的构造方法,用于动态创建对象

Array 类

处理数组的反射,提供数组创建、操作方法

4、反射的性能开销

与直接调用方法或访问字段相比,反射的速度要慢很多。这是因为反射涉及到大量的元数据查找和动态代理操作

相关文章:

  • 深度解析Unity3D渲染管线:网格、材质与GPU渲染的协同逻辑
  • Spring Boot 中自动装配机制的原理
  • golang floate64保留2位小数
  • 力扣 买卖股票的最佳时机
  • ANR小记
  • 记录一次部署PC端网址全过程
  • 电商物流系统方案详解
  • AI改文(小说推文Java版)
  • 【干货分享】Autosar CanIf 模块的应用干货笔记1
  • 智能硬件新时代,EasyRTC开启物联音视频新纪元
  • softgym安装
  • ✨2.快速了解HTML5的标签类型
  • Windows系统安装GPU驱动
  • AutoDock CrankPep or ADCP进行蛋白质多肽对接
  • 网络安全重点总结
  • 智慧校园综合管理平台解决方案
  • Linux配置SSH公钥认证与Jenkins远程登录进行自动发布
  • Ollama 在 LangChain 中的使用
  • Tauri+Trae+Deepseek写几个小游戏
  • 使用 Openpyxl 操作 Excel 文件详解
  • 卢正已任上海市司法局党委委员、副局长
  • 国家主席习近平在莫斯科出席红场阅兵式
  • 市自规局公告收回新校区建设用地,宿迁学院:需变更建设主体
  • 新华时评:直播间里“家人”成“韭菜”,得好好管!
  • 国铁集团:铁路五一假期运输收官,多项运输指标创历史新高
  • “80后”海南琼海市长傅晟,去向公布