Java 反射体系
目录
1 反射机制的概述
2 获取Class类的对象
3 反射获取构造方法并使用
4 反射获取成员方法并使用
5 反射获取成员变量并使用
6 反射获取注解的值
7 反射优缺点
1 反射机制的概述
Java反射(Reflection)是Java语言的一个重要特性,它允许运行中的Java程序对自身进行检查,并且可以直接操作程序的内部属性。通过反射,我们可以在运行时获取类的信息、创建对象、调用方法以及访问和修改字段等。
Java反射体系主要包括以下几个核心类:
-
Class类:代表一个类或接口。每个类都有一个对应的
Class
对象,该对象包含了类的所有信息,如名称、父类、实现的接口、构造器、方法和字段等。可以通过以下几种方式获得Class
对象:类名.class
对象.getClass()
Class.forName("全限定类名")
-
Constructor类:代表类的构造方法。通过
Class
对象可以获取Constructor
对象数组,进而创建实例。- 例如:
Constructor<?>[] constructors = clazz.getConstructors();
- 例如:
-
Method类:代表类的方法。通过
Class
对象可以获取Method
对象,从而调用方法。- 例如:
Method method = clazz.getMethod("methodName", paramTypes);
- 例如:
-
Field类:代表类的字段(成员变量)。通过
Class
对象可以获取Field
对象,用于访问或修改字段值。- 例如:
Field field = clazz.getField("fieldName");
- 例如:
-
Modifier类:提供了一组静态方法来处理类和成员的修饰符。可以用来判断是否为public, private, static, final等。
- 例如:
boolean isPublic = Modifier.isPublic(field.getModifiers());
- 例如:
2 获取Class类的对象
获取Class
对象是Java反射机制中的一个基本操作。Class
对象代表了运行时类的信息,通过它可以访问类的构造器、方法、字段等。下面详细介绍几种获取Class
对象的方法,并提供相应的可运行示例。
获取Class
对象的方式
-
使用
.class
语法:- 适用于任何类型(包括类、接口、数组、基本数据类型及void)。
- 这是最简单和最常用的方法。
-
使用对象的
.getClass()
方法:- 只有当已经有了某个类的对象实例时才能使用这种方法。
- 返回的是该对象的实际类型,如果对象被子类化,则返回的是子类的
Class
对象。
-
使用
Class.forName()
方法:- 需要传入类的全限定名(包名+类名)作为参数。
- 通常用于加载配置文件中指定的类或在运行时动态加载类。
- 如果找不到对应的类,会抛出
ClassNotFoundException
。
示例代码: 这里给出几个简单的例子来演示如何获取Class
对象
public class ClassExample {public static void main(String[] args) {// 1. 使用 .class 语法Class<?> stringClass = String.class;System.out.println("Using .class: " + stringClass.getName());// 2. 使用对象的 getClass() 方法String str = "Hello, World!";Class<?> strClass = str.getClass();System.out.println("Using getClass(): " + strClass.getName());// 3. 使用 Class.forName() 方法try {Class<?> forNameClass = Class.forName("java.lang.String");System.out.println("Using Class.forName(): " + forNameClass.getName());} catch (ClassNotFoundException e) {e.printStackTrace();}}
}
输出结果:
Using .class: java.lang.String
Using getClass(): java.lang.String
Using Class.forName(): java.lang.String
注意事项:
- 当使用
Class.forName()
时,确保提供的类名是正确的,否则程序将抛出ClassNotFoundException
异常。 .getClass()
方法返回的是对象的实际类型,因此如果对象是某个子类的实例,那么.getClass()
将返回子类的Class
对象,而不是父类的。- 对于基本类型(如int, boolean等),可以直接使用
int.class
,boolean.class
等来获取对应的Class
对象。对于它们的包装类型(如Integer, Boolean),可以使用相同的.class
语法或Class.forName()
方法。
3 反射获取构造方法并使用
在Java中,反射不仅可以用来获取类的信息,还可以用来创建对象实例。通过Class
对象,我们可以访问类的构造方法(Constructor),并使用这些构造方法来创建新的对象实例。下面我将详细介绍如何使用反射来获取构造方法,并给出一个可运行的例子。
获取构造方法:
-
无参数构造方法:
- 使用
getConstructor()
方法来获取公共的(public)无参数构造方法,如果没有找到匹配的构造方法,会抛出NoSuchMethodException
。 - 使用
getDeclaredConstructor()
方法来获取所有可见性的无参数构造方法,如果没有找到匹配的构造方法,会抛出NoSuchMethodException
。
- 使用
-
带参数的构造方法:
- 使用
getConstructor(Class<?>... parameterTypes)
来获取特定签名的公共构造方法(public),如果没有找到匹配的构造方法,会抛出NoSuchMethodException
。 - 使用
getDeclaredConstructor(Class<?>... parameterTypes)
来获取特定签名的所有可见性构造方法,如果没有找到匹配的构造方法,会抛出NoSuchMethodException
。
- 使用
-
所有构造方法:
- 使用
getConstructors()
来获取所有的公共(public)构造方法,不会抛出NoSuchMethodException
,但如果该类没有公共构造方法,则返回的数组长度为0。 - 使用
getDeclaredConstructors()
来获取所有可见性的构造方法。
- 使用
创建对象实例:
一旦获取了构造方法,就可以使用newInstance(Object... initargs)
方法来创建对象实例。需要注意的是,如果构造方法是私有的或有其他访问限制,需要先调用setAccessible(true)
来允许访问。
示例代码:
import java.lang.reflect.Constructor;public class ReflectionConstructorExample {public static void main(String[] args) {try {// 1. 获取Class对象Class<?> personClass = Class.forName("om.miracle.service.reflex.Person");// 2. 获取无参数构造方法Constructor<?> noArgsConstructor = personClass.getConstructor();// 使用无参数构造方法创建对象Object person1 = noArgsConstructor.newInstance();System.out.println("Created object with no-arg constructor: " + person1);// 3. 获取带参数的构造方法Constructor<?> fullArgsConstructor = personClass.getConstructor(String.class, int.class);// 使用带参数的构造方法创建对象Object person2 = fullArgsConstructor.newInstance("Alice", 30);System.out.println("Created object with full-arg constructor: " + person2);// 4. 获取私有构造方法并创建对象Constructor<?> privateConstructor = personClass.getDeclaredConstructor(int.class);privateConstructor.setAccessible(true); // 设置为可访问Object person3 = privateConstructor.newInstance(25);System.out.println("Created object with private constructor: " + person3);// 5.获取所有公共构造方法Constructor<?>[] constructors = personClass.getDeclaredConstructors();for (Constructor<?> c : constructors) {System.out.println("Public constructor: " + c);}} catch (Exception e) {e.printStackTrace();}}
}class Person {private String name;private int age;public Person() {this.name = "Unnamed";this.age = 0;}public Person(String name, int age) {this.name = name;this.age = age;}private Person(int age) {this.name = "Unnamed";this.age = age;}@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + "}";}
}
输出结果:
Created object with no-arg constructor: Person{name='Unnamed', age=0}
Created object with full-arg constructor: Person{name='Alice', age=30}
Created object with private constructor: Person{name='Unnamed', age=25}
Public constructor: private com.miracle.service.reflex.Person(int)
Public constructor: public com.miracle.service.reflex.Person(java.lang.String,int)
Public constructor: public com.miracle.service.reflex.Person()
4 反射获取成员方法并使用
在Java中,反射不仅可以用来获取类的构造方法和创建对象实例,还可以用来访问和调用类中的成员方法。通过Class
对象,我们可以获取类的方法(Method),并使用这些方法来执行相应的操作。下面我将详细介绍如何使用反射来获取成员方法,并给出一个可运行的例子。
获取成员方法:
-
获取公共方法:
- 使用
getMethod(String name, Class<?>... parameterTypes)
方法来获取特定签名的公共方法。 - 使用
getMethods()
方法来获取所有公共方法(包括继承自父类和实现接口的方法)。
- 使用
-
获取所有可见性的方法:
- 使用
getDeclaredMethod(String name, Class<?>... parameterTypes)
方法来获取特定签名的所有可见性方法(不包括继承的方法)。 - 使用
getDeclaredMethods()
方法来获取所有可见性的方法(不包括继承的方法)。
- 使用
调用成员方法:
一旦获取了方法对象,就可以使用invoke(Object obj, Object... args)
方法来调用该方法。如果方法是静态的,则第一个参数可以是null
。如果方法是非静态的,则需要提供一个有效的对象实例作为第一个参数。
示例代码:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;public class ReflectionMethodExample {public static void main(String[] args) {try {// 1. 获取Class对象Class<?> personClass = Class.forName("om.miracle.service.reflex.Person");// 2. 创建Person对象Constructor<?> constructor = personClass.getConstructor(String.class, int.class);Person person = (Person) constructor.newInstance("Alice", 30);// 3. 获取公共方法sayHello()Method sayHelloMethod = personClass.getMethod("sayHello");// 调用sayHello()方法String greeting = (String) sayHelloMethod.invoke(person);System.out.println(greeting); // 输出: Hello, Alice!// 4. 获取带参数的方法setAge(int)Method setAgeMethod = personClass.getMethod("setAge", int.class);// 调用setAge(int)方法setAgeMethod.invoke(person, 35);// 5. 获取私有方法getPrivateInfo()Method getPrivateInfoMethod = personClass.getDeclaredMethod("getPrivateInfo");getPrivateInfoMethod.setAccessible(true); // 设置为可访问String privateInfo = (String) getPrivateInfoMethod.invoke(person);System.out.println(privateInfo); // 输出: Private info for Alice, 35} catch (Exception e) {e.printStackTrace();}}
}class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String sayHello() {return "Hello, " + name + "!";}public void setAge(int age) {this.age = age;}private String getPrivateInfo() {return "Private info for " + name + ", " + age;}@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + "}";}
}
输出结果:
Hello, Alice!
Private info for Alice, 35
5 反射获取成员变量并使用
在Java中,反射还可以用来访问和操作类中的成员变量(字段)。通过Class
对象,我们可以获取类的字段(Field),并使用这些字段来读取或设置其值。下面我将详细介绍如何使用反射来获取成员变量,并给出一个可运行的例子。
获取成员变量:
-
获取公共字段:
- 使用
getField(String name)
方法来获取特定名称的公共字段。 - 使用
getFields()
方法来获取所有公共字段(包括继承自父类和实现接口的字段)。
- 使用
-
获取所有可见性的字段:
- 使用
getDeclaredField(String name)
方法来获取特定名称的所有可见性字段(不包括继承的字段)。 - 使用
getDeclaredFields()
方法来获取所有可见性的字段(不包括继承的字段)。
- 使用
读取和设置成员变量:
一旦获取了字段对象,就可以使用以下方法来读取或设置字段值:
get(Object obj)
:获取指定对象上该字段的值。set(Object obj, Object value)
:设置指定对象上该字段的值为给定的新值。
如果字段是私有的或者有其他访问限制,需要先调用setAccessible(true)
来允许访问。
示例代码:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;public class ReflectionFieldExample {public static void main(String[] args) {try {// 1. 获取Class对象Class<?> personClass = Class.forName("om.miracle.service.reflex.Person");// 2. 创建Person对象Constructor<?> constructor = personClass.getConstructor(String.class, int.class);Person person = (Person) constructor.newInstance("Alice", 30);// 3. 获取公共字段nameField nameField = personClass.getField("name");// 读取name字段的值String name = (String) nameField.get(person);System.out.println("Name: " + name); // 输出: Name: Alice// 4. 设置name字段的值nameField.set(person, "Bob");System.out.println("New Name: " + person.getName()); // 输出: New Name: Bob// 5. 获取私有字段ageField ageField = personClass.getDeclaredField("age");ageField.setAccessible(true); // 设置为可访问// 读取age字段的值int age = (int) ageField.get(person);System.out.println("Age: " + age); // 输出: Age: 30// 6. 设置age字段的值ageField.set(person, 35);System.out.println("New Age: " + person.getAge()); // 输出: New Age: 35} catch (Exception e) {e.printStackTrace();}}
}class Person {public String name; // 公共字段private int age; // 私有字段public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + "}";}
}
输出结果:
Name: Alice
New Name: Bob
Age: 30
New Age: 35
这个例子展示了如何使用反射来动态地访问和修改类中的成员变量,即使这些变量是私有的。注意,在实际开发中,应该谨慎使用反射来直接访问私有字段,因为这可能会破坏封装性和安全性。
6 反射获取注解的值
反射还可以用来访问类、方法或字段上的注解。通过Class
对象、Method
对象或Field
对象,我们可以检查这些元素上是否存在特定的注解,并且可以读取注解中的值。下面我将详细介绍如何使用反射来获取注解的值,并给出一个可运行的例子。
获取注解:
-
检查注解的存在:
- 使用
isAnnotationPresent(Class<? extends Annotation> annotationClass)
方法来检查某个元素上是否存在指定类型的注解。
- 使用
-
获取注解实例:
- 使用
getAnnotation(Class<T> annotationClass)
方法来获取特定类型的注解实例。 - 使用
getAnnotations()
方法来获取所有注解的数组。 - 使用
getDeclaredAnnotations()
方法来获取所有直接声明的注解(不包括继承的注解)。
- 使用
-
读取注解的值:
- 一旦获取了注解实例,就可以通过调用注解接口中定义的方法来读取注解中的值。
示例代码:
import java.lang.annotation.*;
import java.lang.reflect.Method;// 定义一个自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyAnnotation {String value();int count() default 1;
}class MyClass {@MyAnnotation(value = "Hello", count = 5)public void myMethod() {System.out.println("This is a method with an annotation.");}
}public class ReflectionAnnotationExample {public static void main(String[] args) {try {// 1. 获取Class对象Class<?> myClass = Class.forName("MyClass");// 2. 获取myMethod方法Method myMethod = myClass.getMethod("myMethod");// 3. 检查myMethod方法上是否存在MyAnnotation注解if (myMethod.isAnnotationPresent(MyAnnotation.class)) {// 4. 获取MyAnnotation注解实例MyAnnotation myAnnotation = myMethod.getAnnotation(MyAnnotation.class);// 5. 读取注解的值String value = myAnnotation.value();int count = myAnnotation.count();// 输出注解的值System.out.println("Value: " + value); // 输出: Value: HelloSystem.out.println("Count: " + count); // 输出: Count: 5} else {System.out.println("The method does not have the specified annotation.");}} catch (Exception e) {e.printStackTrace();}}
}
输出结果:
Value: Hello
Count: 5
这个例子展示了如何使用反射来动态地获取方法上的注解,并读取注解中的具体值。同样的方法也可以应用于类和字段上的注解。注意,为了能够在运行时通过反射访问注解,注解的保留策略必须是RetentionPolicy.RUNTIME
。
7 反射优缺点
优点
- 高度灵活:能够在运行时发现并使用类型信息。
- 支持动态编程:允许程序在运行时改变行为。
- 便于调试和测试:可以方便地查看内部结构,对于复杂系统的调试非常有用。
缺点
- 性能开销:反射比直接调用慢得多,因为JVM需要做额外的工作来解析字符串形式的类名、方法名等。
- 安全问题:由于反射可以绕过正常的访问控制,可能会带来一些安全隐患。
- 可读性和维护性降低:过度使用反射会使代码变得难以理解和维护。
总之,反射提供了一种强大而灵活的方式来操作运行时的数据结构,但在使用时应该考虑到其潜在的缺点,并确保只在确实需要的地方使用。