Java反射是Java编程语言的一种强大功能,它能够检查和修改运行时对象的行为。我们将详细探讨Java反射中的9个主要操作。
1. 获取Class对象
在Java反射中,所有操作起点都是获取对应的Class
对象,由于每个实例对象都属于某个类,所以我们最常见的就是从一个实例对象获取其对应类的Class
对象。
Java提供了两种方式来获取一个类的Class
对象:使用getClass()
方法或使用Class.forName()
静态方法。
1.1 通过实例对象获取Class对象
getClass()
是一个实例方法,它存在于所有Java对象中,因为每个对象都继承自Object
,而Object
类提供了此方法。当在一个特定对象上调用该方法,将返回该对象所属的Class对象。
考虑以下的例子,我们有一个String对象,并调用getClass()
方法获取该对象的Class
对象:
String str = "Hello, World!";
Class strClass = str.getClass();
在这个例子中,由于字符串"Hello, World!"
是一个String
对象,所以getClass()
方法返回的是String
类的Class
对象。
1.2 通过类的全限定名获取Class对象
如果你只知道类的全限定名,即包含包名的类名,如java.lang.String
,可以通过Class
类的静态方法forName(String className)
来获取该类的Class
对象。这种方式常用于动态加载类。
例如,我们如下获取String
类的Class
对象:
Class strClass = Class.forName("java.lang.String"); //动态加载
Class<?> strClass = String.class; //静态加载
此方法将加载名为"java.lang.String"
的类,并返回该类的Class
对象。
总结起来,获取Class对象的主要方式就是通过实例对象的getClass()
方法和Class.forName()
静态方法,而后续的反射操作,都是基于这个Class对象进行的。
2. 创建对象
利用反射,我们可以动态地创建一个对象。通过在获取到的Class对象上调用newInstance()
方法,可以创建该类的新实例,等价于使用new关键字。
2.1 使用newInstance()
创建对象
Class类中的newInstance()
方法会调用类的无参构造函数来创建该类的新实例。这等价于使用new
关键字和无参构造函数创建对象。
例如,假设我们有一个Person
类有一个无参构造函数,我们可以像下面这样创建Person
的新实例:
Class<?> personClass = Person.class;
Person person = (Person) personClass.newInstance();
这里,我们首先获取到Person
类的Class对象,然后通过调用newInstance()
方法创建了Person
的新实例。
2.2 注意事项与限制
-
newInstance()
方法只能调用无参构造函数,如果需要使用带参数的构造函数创建对象,需要使用Constructor类的newInstance(Object... initargs)
方法。 -
调用
newInstance()
方法时,无参构造函数的访问权限需要是可访问的。如果无参构造函数是private
,那么调用newInstance()
方法就会抛出IllegalAccessException
。 -
如果无参构造函数在执行过程中抛出了异常,那么调用
newInstance()
方法也会抛出InstantiationException
。
在使用newInstance()
方法的时候,对上述限制和异常需要有一定的注意和处理。
虽然可以使用newInstance()
方法,但因为其使用的是无参构造函数,所以在需要参数构造的情况下,推荐使用Constructor.newInstance(Object... initargs)
方法。
要注意,从Java 9开始,Class类的newInstance()
方法已经被弃用。取而代之的是,应该使用Class类的getDeclaredConstructor()
方法获取Constructor对象,然后调用Constructor对象的newInstance()
方法来创建类的实例。
以下是使用getDeclaredConstructor().newInstance()
方法创建实例的示例:
Class<?> personClass = Person.class;
Person person = (Person) personClass.getDeclaredConstructor().newInstance();
使用这种方式创建对象,我们可以选择调用哪个构造函数并传入合适的参数,因此它提供了更大的灵活性。
另外,当使用反射创建对象时,可能会抛出各种异常(比如ClassNotFoundException
,InstantiationException
,IllegalAccessException
,NoSuchMethodException
,InvocationTargetException
等),因此在使用反射时,需要进行恰当的异常处理。
3. 获取和操作成员变量
我们可以通过getDeclaredField(String name)
方法从某个Class对象中获取指定的字段,然后可以使用Field类的set(Object obj, Object value)
方法来修改此字段的值。
3.1 获取成员变量
Java反射提供了从Class对象中获取指定字段的方法。具体来说,通过如下几个方法:
getDeclaredField(String name)
:返回一个Field
对象,它反映此Class
对象所表示的类或接口的指定已声明字段。这包括所有的字段,无论其访问权限如何。getDeclaredFields()
:返回此Class
对象表示的类或接口所声明的所有字段的Field
对象数组。getField(String name)
:返回一个Field
对象,它反映此Class
对象所表示的类或接口的指定公开字段,包括其父类或父接口中的字段。getFields()
:返回包含此Class
对象表示的类或接口的所有可访问公共字段的Field
对象的数组。
例如,假设我们有一个Person
类:
public class Person {
private String name;
// getters and setters
}
我们可以使用以下几种方法获取"name"字段以及其他字段:
// 获取单个字段
Class<?> personClass = Person.class;
Field nameField = personClass.getDeclaredField("name");
// 获取所有声明的字段(不包括父类)
Field[] declaredFields = personClass.getDeclaredFields();
// 获取单个公有字段(包括父类)
Field nameFieldPublic = personClass.getField("name");
// 获取所有公有字段(包括父类)
Field[] publicFields = personClass.getFields();
注意:getDeclaredField
和getDeclaredFields
方法会返回所有字段,无论其是否公开;而getField
和getFields
方法只会返回公开的字段,包括从父类继承的字段。
3.2 操作成员变量
获取到Field
对象后,我们可以对其进行操作。Field
类提供了多个方法来设置或获取字段的值。
例如,我们可以使用set(Object obj, Object value)
方法来修改字段的值。此方法接受两个参数:第一个参数是要修改的对象,第二个参数是新的字段值。
假设我们有一个Person
对象person
,我们可以这样修改其"name"字段的值:
nameField.setAccessible(true); // necessary for private fields
nameField.set(person, "Alice");
这段代码首先调用setAccessible(true)
方法允许访问私有字段,然后调用set(Object obj, Object value)
方法将person
的"name"字段值设为"Alice"。
注意,setAccessible
方法是用于削弱Java语言访问控制的,所以需要谨慎使用。此外,对于final
字段,反射不允许改变其值。
总的来说,Java反射提供了灵活的方式来操作类的字段,实现了在运行时动态地获取和设置字段的值。
4. 获取构造函数并创建对象
除了使用newInstance()
方法,我们也可以通过获取类的特定构造函数来创建新对象。这可以通过调用getDeclaredConstructor(Class<?>... parameterTypes)
方法并传入参数类型来实现。
4.1 获取构造函数
Java反射提供了从Class对象中获取构造函数的功能。具体的方式主要有以下几种:
getDeclaredConstructor(Class... parameterTypes)
:返回Constructor
对象,该对象反映此Class
对象所表示的类或接口的指定声明构造函数。该方法接受一个或多个Class类型的参数,表示你要获取的构造函数的参数类型。getDeclaredConstructors()
:返回Constructor
对象的一个数组,这些对象反映此Class
对象表示的类或接口的所有已声明构造函数。getConstructor(Class... parameterTypes)
:返回一个Constructor
对象,该对象反映此Class
对象所表示的类或接口的指定公开(public)构造函数。getConstructors()
:返回包含一个数组,该数组包含Constructor
对象反映由此Class
对象表示的类的所有公开(public)构造函数。
例如,假设我们有一个Person
类,它有一个接受String
类型参数的构造函数:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
// getters and setters
}
我们可以使用以下方法来获取该Person类的构造函数:
// 获取单个指定参数类型的构造函数
Class<?> personClass = Person.class;
Constructor<?> constructor = personClass.getDeclaredConstructor(String.class);
// 获取所有已声明的构造函数
Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
// 获取单个公开(public)的构造函数
Constructor<?> publicConstructor = personClass.getConstructor(String.class);
// 获取所有公开(public)的构造函数
Constructor<?>[] publicConstructors = personClass.getConstructors();
注意,getDeclaredConstructor
和getDeclaredConstructors
方法返回的都是类自身声明的构造函数,无论其访问权限如何;而getConstructor
和getConstructors
方法返回的都是类的公开(public)构造函数,不包括类自身的私有(private)和受保护(protected)构造函数。
4.2 使用构造函数创建对象
获取到Constructor
对象后,我们可以使用它来创建类的新实例。具体来说,Constructor
类的newInstance(Object... initargs)
方法可以创建一个新的对象。这个方法接受一系列参数,用于传递给构造函数。
使用上述Person
类的例子,我们可以这样创建一个新的Person
对象:
Person person = (Person) constructor.newInstance("Alice");
这段代码将创建一个新的Person
对象,其"name"字段的值为"Alice"。
相比直接使用Class
类的newInstance()
方法,使用Constructor
对象创建新实例的好处是可以选择调用哪个构造函数,并传入合适的参数。
5. 获取和调用成员方法
我们也可以使用反射来获取并调用类的成员方法。通过getDeclaredMethod(String name, Class<?>... parameterTypes)
方法可以获取指定的方法,然后通过调用Method类的invoke(Object obj, Object... args)
方法来调用此方法。
5.1 获取成员方法
Java反射提供了获取类的成员方法的功能。具体的方式主要有以下几种:
getDeclaredMethod(String name, Class... parameterTypes)
:返回一个Method
对象,它反映此Class
对象所表示的类或接口的指定已声明的方法。此方法接收两个参数:一个是要获取的方法的名称,另一个是该方法参数的类型。getDeclaredMethods()
:返回包含Method
对象的一个数组,这些对象反映此Class
对象表示的类或接口声明的所有方法, 不包括继承的方法。getMethod(String name, Class... parameterTypes)
:返回一个Method
对象,它反映此Class
对象所代表的类或接口的指定公共(public)成员方法,包括其继承的方法。getMethods()
:返回包含Method
对象的一个数组,这些对象反映此Class
对象所代表的类或接口的所有公共(public)成员方法,包括其继承的方法。
例如,假设我们有一个Person
类,有一个名为"sayHello"的方法:
public class Person {
public void sayHello(String message) {
System.out.println("Hello, " + message);
}
}
我们可以使用以下方法来获取Person
类的方法:
// 获取单个指定参数类型的方法
Class<?> personClass = Person.class;
Method sayHelloMethod = personClass.getDeclaredMethod("sayHello", String.class);
// 获取所有已声明的方法
Method[] declaredMethods = personClass.getDeclaredMethods();
// 获取单个公开(public)的方法,包括父类的方法
Method sayHelloMethodPublic = personClass.getMethod("sayHello", String.class);
// 获取所有公开(public)的方法,包括父类的方法
Method[] publicMethods = personClass.getMethods();
注意,getDeclaredMethod
和getDeclaredMethods
方法会返回类自身声明的所有方法,无论其访问权限如何,而getMethod
和getMethods
方法返回的都是类的公开(public)方法,包括从父类继承的方法。
5.2 调用成员方法
获取到Method
对象后,可以使用它来调用类的成员方法。具体使用Method
类的invoke(Object obj, Object... args)
方法实现。invoke
方法接收两个参数:第一个参数是要调用方法的对象,第二个参数是调用方法时传入的参数。
使用前面Person
类的例子,我们可以这样调用"sayHello"方法:
Person person = new Person();
sayHelloMethod.invoke(person, "Alice");
执行这段代码后,控制台将输出:Hello, Alice
此外,对于访问权限的问题,如私有方法,也是可以通过setAccessible(true)
来关闭Java语言的访问权限检查,从而调用私有方法。此处需要格外注意,这么做可能会导致安全问题,必须要谨慎对待。
6. 获取类名
我们可以通过调用getName()
方法来获取类的全限定名(包括包名)。
在Java反射中,Class
类的getName()
方法可用于获取类的全限定名,也就是类名包含包名。
例如,假设我们有一个完全限定名为"com.example.Person"的类,我们可以这样获取该类的全限定名:
Class<?> personClass = Person.class;
String className = personClass.getName();
执行这段代码后,className
的值将是:“com.example.Person”。
此外,Class
类还有其他一些方法可以获取类的信息:
getSimpleName()
:返回类的简单名字,不包括包名。对于上述例子,getSimpleName()
将返回"Person"。getPackage()
:返回包含此类的Package
对象。可以用来获取包级别的注解、声明的版本等信息。getSuperclass()
:返回表示此Class
所表示的实体(类、接口、基本类型或 void)的超类的Class
。getInterfaces()
:确定此Class
对象所表示的类或接口实现的接口。
这就是关于Java反射获取类名和其他相关类信息的内容。
7. 获取父类
我们可以通过调用getSuperclass()
方法获取类的父类。
在Java反射中,Class
类的getSuperclass()
方法可以获取类的父类信息。
例如,假设我们有一个名为"Person"的类,它继承自"Human"类,我们可以这样获取"Person"类的父类信息:
Class<?> personClass = Person.class;
Class<?> superClass = personClass.getSuperclass();
执行这段代码后,superClass
的值将是代表"Human"类的Class
对象。
除此之外,Class
类提供了一些其他方法来获取类继承相关的信息:
getGenericSuperclass()
:返回表示此Class
所表示的实体(类、接口、基本类型或 void)的直接超类的Type
。如果超类是参数化的,则返回的类型是参数化类型。getInterfaces()
:确定此Class
对象所表示的类或接口实现的接口。getGenericInterfaces()
:返回表示此Class
所表示的实体(类、接口、基本类型或 void)实现的接口的Type
。
例如,获取"Person"类的所有实现的接口:
Type[] interfaces = personClass.getInterfaces();
以上就是关于Java反射中获取类的父类和其他继承相关信息的方法。
8. 获取实现的接口
我们也可以通过使用getInterfaces()
方法获取一个类实现的所有接口。
在Java反射中,Class
类的getInterfaces()
方法可以获取一个类实现的所有接口。它返回一个包含表示这些接口的Class
对象的数组。
例如,如果我们有一个Person
类实现了Runnable
和Comparable
接口:
public class Person implements Runnable, Comparable<Person> {
// ...
}
我们可以这样获取Person
类实现的所有接口:
Class<?> personClass = Person.class;
Class<?>[] interfaceClasses = personClass.getInterfaces();
执行这段代码后,interfaceClasses
将是一个包含表示Runnable
类和Comparable
类的Class
对象的数组。
除了getInterfaces()
方法,Class
类还提供了getGenericInterfaces()
方法,它返回一个Type
对象的数组,这些对象代表这个类所实现的所有接口。相较于getInterfaces()
,getGenericInterfaces()
考虑了泛型接口。
对于前面的Person
类,我们可以这样获取泛型接口:
Type[] genericInterfaces = personClass.getGenericInterfaces();
执行这代码后,genericInterfaces
将是一个包含表示Runnable
接口和Comparable<Person>
接口的Type
对象的数组。
以上就是在Java反射中获取类所实现接口的方法。
9. 获取类的修饰符
我们可以通过getModifiers()
方法获取类的修饰符,比如public、private、static等。然后我们可以使用java.lang.reflect.Modifier
类的静态方法来解析这些修饰符。
在Java反射中,我们可以通过Class
类的getModifiers()
方法获取类的修饰符,该方法返回一个整数,表示类的修饰符。这个整数值表示的修饰符包括public、private、protected、static、final、abstract、interface等。例如,如下的获取方式:
Class<?> personClass = Person.class;
int modifiers = personClass.getModifiers();
然后,我们可以使用java.lang.reflect.Modifier
类的一系列静态方法来解析这个整数值,以此来判断类的修饰符。Modifier
类提供如下一些方法:
isAbstract(int mod)
:判断是否为抽象类。isFinal(int mod)
:判断是否为final类。isInterface(int mod)
:判断是否为接口。isPrivate(int mod)
:判断是否为private类。isProtected(int mod)
:判断是否为protected类。isPublic(int mod)
:判断是否为public类。isStatic(int mod)
:判断是否为static类。
如,判断"Person"类是否为public和abstract:
boolean isPublic = Modifier.isPublic(modifiers);
boolean isAbstract = Modifier.isAbstract(modifiers);
以上主要介绍了如何通过反射获取类的修饰符,以及如何解析获取到的修饰符。