反射是 Java 中一种强大的编程技术,允许在 运行时 检查和操作类、方法、字段等程序 元信息 。
通过反射,你可以在不知道类名的情况下,动态地加载类、创建对象,调用方法,访问字段等。
接下来就开始介绍一下 Java 反射 的基本概念、用途和示例。
1.什么是反射
反射是 Java 在运行时检查和操作类、方法、字段等信息的能力。通过反射,你可以动态地获取和使用类的信息,而不需要在编译时就知道类的全部细节。
Java 反射的核心是
java.lang.reflect
包,它提供了许多类和接口来实现反射功能。
我们有一个 Student
类
class Student {
private String name;
public String get(){
return "abc";
}
}
根据 Student
创建一个它的实例,然后我们想根据实例来获取类的信息(比如 name 或者 get 方法等),这个反向拿的过程就叫做反射。
2.反射的主要用途
- 动态加载类: 在运行时根据类的名称动态加载类。这使得你可以根据配置文件或用户输入加载不同的类。
- 创建对象: 通过类的信息创建对象实例,而不是使用
new
关键字。使得可以在运行时创建任意类的对象。 - 调用方法: 在运行时通过方法名动态调用类的方法。使得可以在不知道方法名称的情况下调用特定的方法。
- 访问字段: 动态访问和修改类的字段,无需事先知道字段的名称。
- 获取注解信息: 通过反射可以获取类、方法、字段等元素上的注解信息,对一些框架的实现是非常重要的。
3.反射潜在的问题
尽管 Java 反射提供了这些强大的功能,但它也带来了一些潜在的问题和性能开销:
-
性能开销: 反射操作通常比直接的静态类型操作慢,因为它绕过了编译时的类型检查,可能导致运行时的性能损失。
-
类型安全性: 反射绕过了编译时的类型检查,可能引入运行时的错误。因此,在使用反射时,必须格外小心,确保类型安全性。
-
封装性破坏: 反射使得可以访问和修改私有成员,这可能破坏了类的封装性。过度使用反射可能导致代码难以理解和维护。
4.反射的基本用法
例如 现在有这么一个
Student
类
package com.example.demo.test;
class Student {
// 私有的名字
private String name = "张三";
public String getName() {
return name;
}
// 私有的方法 说 hello
private void sayHello(String str){
System.out.println("Hello " + str);
}
}
下面开始展示反射的常用操作
- 通过类名加载类
Class<?> clazz = Class.forName("com.example.demo.test.Student");
- 创建对象
Object instance = clazz.getDeclaredConstructor().newInstance();
- 获取方法并调用
Method method = clazz.getDeclaredMethod("sayHello", String.class);
// 如果方法是私有的,需要设置为可访问
method.setAccessible(true);
method.invoke(instance, "Reflection!");
输出结果:
Hello Reflection!
- 获取字段并修改值
// 打印 旧的 name
Method method2 = clazz.getDeclaredMethod("getName");
String oldName = (String) method2.invoke(instance);
System.out.println("Old name is " + oldName);
// 获得私有的 name 字段
Field field = clazz.getDeclaredField("name");
// 如果字段是私有的,需要设置为可访问
field.setAccessible(true);
// 不仅可以访问私有属性 name,还可以修改 name 的值
field.set(instance, "李四");
// 打印 新的 name
String newName = (String) method2.invoke(instance);
System.out.println("New name is " + newName);
输出结果:
Old name is 张三
New name is 李四
完整的代码:
public static void main(String[] args) throws Exception{
// ============================================================================
// 通过类名加载类
Class<?> clazz = Class.forName("com.example.demo.test.Student");
// ============================================================================
Object instance = clazz.getDeclaredConstructor().newInstance();
// 创建对象
// ============================================================================
Method method = clazz.getDeclaredMethod("sayHello", String.class);
// 如果方法是私有的,需要设置为可访问
method.setAccessible(true);
method.invoke(instance, "Reflection!");
// ============================================================================
// 打印 旧的 name
Method method2 = clazz.getDeclaredMethod("getName");
String oldName = (String) method2.invoke(instance);
System.out.println("Old name is " + oldName);
// 获得私有的 name 字段
Field field = clazz.getDeclaredField("name");
// 如果字段是私有的,需要设置为可访问
field.setAccessible(true);
// 不仅可以访问私有属性 name,还可以修改 name 的值
field.set(instance, "李四");
// 打印 新的 name
String newName = (String) method2.invoke(instance);
System.out.println("New name is " + newName);
// ============================================================================
}
上述代码演示了通过反射加载类、创建对象、调用方法以及访问字段的过程。
总结
反射让我们可以在 Java 运行时检查和操作类、方法、字段等信息,但是,需要注意的是反射通常会导致性能损失,并且由于绕过了编译时的类型检查,可能会引入运行时的错误。因此,在使用反射时,务必谨慎操作,确保类型安全性。