JavaSE反射篇
Java反射
一、反射是什么
反射(Reflection)是 Java 语言中的一种机制,允许运行中的程序对自身进行检查或 “自省”,并能直接操作程序的内部属性。也就是说,即使在编译时不知道类的具体信息,程序也能在运行时加载类,获取类的结构(包括属性、方法、构造函数等),并对类的对象进行操作。这么说可能有点抽象,我们先了解关于Java反射的api
,掌握了这些api
的使用,也就会使用反射了
在 Java 中,所有的类都是 Class
类的实例。通过 Class
类,我们可以获取类的各种信息,如类的名称、属性、方法、构造函数等。
获取 Class
对象主要有以下三种方式:
- 通过对象的
getClass()
方法:
public class getClass {
public static void main(String[] args) {
String re1 = "这是第一个对象";
String re2 = "这是第二个对象";
Class<? extends String> ob = re1.getClass();
Class<? extends String> ob2 = re2.getClass();
System.out.println(ob);
System.out.println(ob2);
}
}
其中re1
, re2
都是String类的对象,因此输出结果相同,如下
class java.lang.String
class java.lang.String
- 通过类的
class
字面量:
public class getClass {
public static void main(String[] args) {
System.out.println(String.class);
}
}
无论基本类型或是自定义类,jvm
都为其提供了一个静态成员class
,可以直接对类进行访问
- 通过
Class.forName()
静态方法:
先看一下forName
的构造,其中className
是类的全限定名(即包名.类名)
static Class<?> forName(String className)
forName
可以结合配置文件properties
结合使用,yin
public class getClass {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("C:\\Users\\30371\\IdeaProjects\\Review\\src\\test.properties");
Properties prop = new Properties();
prop.load(fis);
String name = prop.getProperty("name");
Class<?> aclass = Class.forName(name);
System.out.println("aclass = " + aclass);
}
}
二、反射的基本操作
(一)获取类的属性
通过 Class
对象,我们可以获取类的属性信息。例如,有一个简单的 Person
类:
public class Person {
private String name;
public int age;
protected String address;
String gender;
}
获取 Person
类属性的代码如下:
Class<?> clazz = Person.class;
// 获取所有公共属性(包括从父类继承的公共属性)
Field[] publicFields = clazz.getFields();
for (Field field : publicFields) {
System.out.println("公共属性: " + field.getName());
}
// 获取所有本类声明的属性,包括私有、受保护和默认访问修饰符的属性,但不包括从父类继承的属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println("本类声明的属性: " + field.getName());
}
getFields()
方法只能获取类的公共属性,包括从父类继承的公共属性;而 getDeclaredFields()
方法能获取类自身声明的所有属性,无论其访问修饰符是什么,但不包括从父类继承的属性,这也称暴力反射
(二)获取类的方法
同样以 Person
类为例,假设该类有如下方法:
public class Person {
// 省略属性部分
public void sayHello() {
System.out.println("Hello!");
}
private void privateMethod() {
System.out.println("This is a private method.");
}
public String getInfo(String prefix) {
return prefix + " Name: " + name + ", Age: " + age;
}
}
获取 Person
类方法的代码如下:
Class<?> clazz = Person.class;
// 获取所有公共方法(包括从父类继承的公共方法)
Method[] publicMethods = clazz.getMethods();
for (Method method : publicMethods) {
System.out.println("公共方法: " + method.getName());
}
// 获取所有本类声明的方法,包括私有、受保护和默认访问修饰符的方法,但不包括从父类继承的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println("本类声明的方法: " + method.getName());
}
类似地,getMethods()
方法获取公共方法,getDeclaredMethods()
方法获取类自身声明的所有方法。并且,我们还可以获取方法的参数类型、返回值类型等详细信息。例如获取 getInfo
方法的参数和返回值类型:
Method getInfoMethod = clazz.getMethod("getInfo", String.class);
Class<?> returnType = getInfoMethod.getReturnType();
Class<?>[] parameterTypes = getInfoMethod.getParameterTypes();
System.out.println("返回值类型: " + returnType.getName());
System.out.println("参数类型: ");
for (Class<?> parameterType : parameterTypes) {
System.out.println(parameterType.getName());
}
(三)获取类的构造函数
继续以 Person
类为例,假设该类有不同的构造函数:
public class Person {
// 省略属性和方法部分
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name) {
this.name = name;
}
}
获取 Person
类构造函数的代码如下:
Class<?> clazz = Person.class;
// 获取所有公共构造函数
Constructor<?>[] publicConstructors = clazz.getConstructors();
for (Constructor<?> constructor : publicConstructors) {
System.out.println("公共构造函数: " + constructor.getName());
}
// 获取所有本类声明的构造函数,包括私有构造函数
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
System.out.println("本类声明的构造函数: " + constructor.getName());
}
通过构造函数,我们可以在运行时创建类的实例。例如使用 public Person(String name, int age)
构造函数创建 Person
实例:
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Person person = (Person) constructor.newInstance("John", 30);
(四)使用反射创建对象和调用方法
反射不仅可以获取类的信息,还能在运行时创建对象并调用对象的方法。
- 创建对象:
Class<?> clazz = Person.class;
// 使用无参构造函数创建对象
Person person1 = (Person) clazz.newInstance();
// 通过指定构造函数创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Person person2 = (Person) constructor.newInstance("Jane", 25);
- 调用方法:
Person person = new Person("Bob", 35);
Class<?> clazz = person.getClass();
// 获取要调用的方法
Method sayHelloMethod = clazz.getMethod("sayHello");
// 调用方法
sayHelloMethod.invoke(person);
// 调用带参数的方法
Method getInfoMethod = clazz.getMethod("getInfo", String.class);
String result = (String) getInfoMethod.invoke(person, "Info: ");
System.out.println(result);
(五)访问私有成员
反射机制还可以突破访问修饰符的限制,访问类的私有成员。不过,在实际应用中,除非有特殊需求,否则不建议这样做,因为这破坏了类的封装性。
以访问 Person
类的私有属性 name
为例:
Person person = new Person("Alice", 40);
Class<?> clazz = person.getClass();
Field nameField = clazz.getDeclaredField("name");
// 打破私有访问限制
nameField.setAccessible(true);
try {
String name = (String) nameField.get(person);
System.out.println("私有属性 name 的值: " + name);
nameField.set(person, "Updated Name");
name = (String) nameField.get(person);
System.out.println("修改后的私有属性 name 的值: " + name);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
通过调用 Field
对象的 setAccessible(true)
方法,我们可以访问和修改私有属性。对于私有方法的调用,也可以采用类似的方式,先通过 getDeclaredMethod
获取私有方法,然后调用 setAccessible(true)
来调用该方法。
三、反射的优缺点
(一)优点
- 高度灵活性:反射允许程序在运行时动态地加载类、创建对象、调用方法和访问属性,这使得程序可以根据不同的运行时条件进行灵活的调整,大大提高了程序的通用性和扩展性。
- 增强代码的可维护性和可扩展性:通过反射,我们可以将一些配置信息(如类名、方法名等)外部化,例如存储在配置文件中。当需求发生变化时,只需要修改配置文件,而不需要修改大量的代码,降低了代码的维护成本,同时也方便了功能的扩展。
- 实现一些高级特性:如动态代理、对象序列化与反序列化、测试框架等,这些特性在 Java 编程中非常重要,而反射是实现它们的关键技术。
(二)缺点
- 性能问题:反射操作比直接的方法调用和属性访问要慢得多。因为反射涉及到在运行时查找类信息、方法信息和属性信息,并且需要进行额外的安全检查等操作。在对性能要求较高的场景中,频繁使用反射可能会导致性能瓶颈。
- 代码可读性和可维护性下降:使用反射的代码往往比直接调用的代码更复杂、更难以理解。反射代码中通常会使用大量的字符串来表示类名、方法名和属性名等,这使得代码的可读性变差,并且在编译时无法进行类型检查,容易出现运行时错误,增加了代码维护的难度。
- 破坏封装性:反射可以访问和修改类的私有成员,这破坏了类的封装性,违背了面向对象编程的基本原则。过度使用反射可能会导致代码的结构混乱,降低代码的可维护性和可测试性。
四、总结
Java 反射机制是一项强大而复杂的特性,它为 Java 程序带来了高度的动态性和灵活性,在框架开发、动态代理、对象序列化与反序列化、测试框架等众多领域有着广泛的应用。然而,反射也存在性能问题、代码可读性和可维护性下降以及破坏封装性等缺点。在实际编程中,我们应该根据具体的需求和场景,合理地使用反射机制,充分发挥其优势,同时尽量避免其带来的负面影响。希望通过本文,你能对 Java 反射机制有更深入的理解,并在今后的编程实践中能够熟练运用反射来解决实际问题。