Java反射
概述
- Java反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)检查类,接口,变量以及方法的信息。
- 反射还可以让我们在运行期实例化对象,调用方法,通过调用get/set方法获取变量的值, 即使方法或字段是私有的的也可以通过反射的形式调用
使用场景:
- 在某些情况下,我们要使用的类在运行时才能确定,但是这个类符合某种特定的规范,例如JDBC。因为我们无法在编译期就使用它,所以只能通过反射来使用运行时才存在的类。
- 我们对于类的内部信息不可知,必须要等到运行时才能获取类的具体信息。比如ORM框架,在运行时才能够获取类中的各个字段,然后通过反射的形式获取其字段名和值,存入数据库。
- 注解相关。例如JUnit,使用反射来判断类中的方法是否有
@Test
注解,来运行单元测试。
Class对象
当我们编写完一个Java项目之后,所有的Java文件都会被编译成一个.class文件,这些Class对象承载了这个类型的父类、接口、构造函数、方法、成员变量等原始信息,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。
在你想检查一个类的信息之前,你首先需要获取类的Class对象。Java中的所有类型包括基本类型(int, long, float等等),即使是数组都有与之关联的Class类的对象。有三种方式来获取一个类的Class对象。
- 如果在编译期就知道类的名称,可以使用类字面量(class-literal)来获取Class对象,
Foo.class
。
Class myObjectClass = MyObject.class;
- 如果已经得到一个对象,可以通过对象的
object.getClass()
方法获取Class对象。
TestClass testClass = new TestClass();
Class<?> clazz = testClass.getClass();
- 如果在编译期获取不到目标类型,但是在知道它的完整类路径(全类名),那么可以通过
Class.forName()
方法来获取Class对象。
Class<?> clazz = Class.forName("com.example.TestClass");
Class对象的一些方法
基本方法
-
String name = clazz.getName()
获取类的全类名(包括包信息) -
String simpleName = clazz.getSimpleName()
获取类名 -
Package package = clazz.getPackage()
获取包信息 -
Class superClass = clazz.getSuperclass()
获取父类的Class对象 -
Class[] interfaces = clazz.getInterfaces()
获取类所实现的接口集合
getInterfaces()
返回的仅仅只是当前类所实现的接口,不包括父类实现的接口 -
int modifiers = clazz.getModifiers()
返回类修饰符
每个修饰符都是一个位标识(flag bit),将这些修饰符封装成一个int类型的值。可以通过java.lang.Modifier
类中的方法来检查修饰符的类型。
或者可以通过Modifier.isAbstract(int modifiers); Modifier.isFinal(int modifiers); Modifier.isInterface(int modifiers); Modifier.isNative(int modifiers); Modifier.isPrivate(int modifiers); Modifier.isProtected(int modifiers); Modifier.isPublic(int modifiers); Modifier.isStatic(int modifiers); Modifier.isStrict(int modifiers); Modifier.isSynchronized(int modifiers); Modifier.isTransient(int modifiers); Modifier.isVolatile(int modifiers);
Modifier.toString(int mod)
静态方法输出类修饰符。
构造器
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
通过方法签名应该很容易看出来,有参数的方法是通过构造器的参数类型获取唯一的构造器,没有参数的是获取类所有的构造函数。带有Declared
的是获取类自身所有的构造函数,public、default、protected和private的。而不带Declared
则只返回public的构造函数。
方法
Method[] getMethods()
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
和获取构造函数的方法类似,不过带有Declared
的是获取自身所有的方法,不包括从父类中继承的。而不带Declared
的则返回该类所有public的方法,包括从父类中继承下来的。其中,参数name
为方法的名称,parameterTypes
为参数的类型。
成员变量
Field[] getFields()
Field getField(String name)
Field[] getDeclaredFields()
Field getDeclaredField(String name)
这个和获取方法的差不多,带有Declared
的是获取自身所有的成员变量,不包括从父类中继承的。而不带Declared
的则返回该类所有public的成员变量,包括从父类中继承下来的。其中,参数name
为成员变量的名称。
注解
Annotation[] getAnnotations()
<A extends Annotation> A getAnnotation(Class<A> annotationClass)
Annotation[] getDeclaredAnnotations()
<A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)
和获取方法的差不多。annotationClass
为注解的Class对象。注意,getDeclaredAnnotation(Class<A> annotationClass)
方法是Java8新添加的方法
Constructor对象
利用Java的反射机制你可以检查一个类的构造方法,并且可以在运行期创建一个对象。这些功能都是通过java.lang.reflect.Constructor这个类实现的。
首先展示我们测试用的类。
public class Student {
@MyAnnotation(name = "annotation")
private int age;
private List<String> parent;
public Student(int age) {
this.age = age;
}
public int getAge() {
return age;
}
@MyAnnotation(name = "annotation")
public void setAge(@MyAnnotation(name = "annotation") int age) {
this.age = age;
}
public List<String> getParent() {
return parent;
}
public void setParent(List<String> parent) {
this.parent = parent;
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String name();
}
利用Contructor对象实例化一个类
方法定义:T newInstance(Object ... initargs)
代码如下:
Class<Student> clazz = Student.class;
// 获得一个Contructor对象,参数类型为int
Constructor<Student> constructor = clazz.getConstructor(int.class);
int age = 5;
// 利用Contructor对象示例化一个实例
Student student = constructor.newInstance(age);
System.out.println("student's age is :" + student.getAge()); // 打印出 student's age is :5
Method对象
使用Java反射你可以在运行期检查一个方法的信息以及在运行期调用这个方法,通过使用java.lang.reflect.Method类就可以实现上述功能。
通过Method对象调用方法
方法定义:Object invoke(Object obj, Object... args)
参数obj
为调用方法的实例对象,如果方法是静态方法,则obj
传null
。参数args
是原方法的参数。
如果方法的返回值为void
,则invoke
的返回值为null
。
代码如下:
Method setMethod = clazz.getMethod("setAge", int.class);
int anotherAge = 20;
// 通过invoke调用方法,参数1为对象实例,如果该方法为静态方法,则传null。接下来的参数是原方法的参数。
setMethod.invoke(student, anotherAge);
Method getMethod = clazz.getMethod("getAge");
Object result = getMethod.invoke(student);
System.out.println("student's age is :" + result); // 打印出 student's age is :20
通过Method对象获取方法参数以及返回类型
-
Class<?>[] getParameterTypes()
获取方法所有的参数类型 -
Class<?> getReturnType()
获取方法的返回类型
代码如下:
// 获取方法所有的参数类型
Class[] parameterTypes = setMethod.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType); // 打印出int
}
// 获取方法的返回类型
Class returnType = setMethod.getReturnType();
System.out.println(returnType); // 打印出void
通过Method对象获取泛型参数类型和泛型返回类型
-
Type getGenericReturnType()
获取泛型返回类型 -
Type[] getGenericParameterTypes()
获取泛型参数类型
上文说的getReturnType
和getParameterTypes
返回的值原始类型(raw type),无法获得泛型类型。而getGenericReturnType
和getGenericParameterTypes
可以返回参数化类型。当然,如果方法本身返回的不是参数化类型,那么这两个方法和getReturnType
等效果相同。
首先,我们先科普一下Type
接口。
Type is the common superinterface for all types in the Java
programming language. These include raw types, parameterized types,
array types, type variables and primitive types.
- raw types : 例如 List
- parameterized types : 例如
List<String>
- array types : 例如 String[]
- type variables : 例如
interface List<E>
中的E - primitive types : 例如 int
然后我们来看一下代码:
Method getParentMethod = clazz.getMethod("getParent");
Type returnType = getParentMethod.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
// 将returnType转化为子类ParameterizedType。此时的parameterizedType为List<String>
ParameterizedType parameterizedType = (ParameterizedType) returnType;
// 通过getActualTypeArguments方法获取泛型类型。此时为String
Type[] typeArgs = parameterizedType.getActualTypeArguments();
for (Type typeArg : typeArgs) {
System.out.println(typeArg); // 打印出 String
}
}
上述代码以getGenericReturnType
方法为例,getGenericParameterTypes
方法是类似的。通过getGenericReturnType
返回的是整个参数化类型,如代码中的List<String>
。通过ParameterizedType
的getActualTypeArguments
方法返回的才是泛型里的参数类型,如List<String>
中的String
。
通过Method对象获取方法注解
Annotation[] getAnnotations()
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
Annotation[] getDeclaredAnnotations()
获取方法注解和获取类注解类似,下面我们看一下示例:
Class<Student> clazz = Student.class;
Method method = clazz.getDeclaredMethod("setAge", int.class);
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation.name()); // 打印出annotation
通过Method对象获取参数注解
-
Annotation[][] getParameterAnnotations()
返回一个二维数组,每一个方法的参数包含一个注解数组
示例代码如下:
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
// 遍历获得各个参数的注解数组
for (Annotation[] annotations : parameterAnnotations) {
// 遍历注解数组,获取每个注解
for (Annotation annotation : annotations) {
if (annotation instanceof MyAnnotation) {
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println(myAnnotation.name()); // 打印出annotation
}
}
}
Field对象
使用Java反射机制你可以运行期检查一个类的变量信息(成员变量)或者获取或者设置变量的值。通过使用java.lang.reflect.Field类就可以实现上述功能。
获取或设置(get/set)变量值
-
Object get(Object obj)
获取成员变量的值,参数obj
为对象实例,如果是静态成员变量,则传入null
即可。 -
void set(Object obj, Object value)
设置成员变量的值,参数obj
为对象实例,如果是静态成员变量,则传入null
即可。value
为欲设置的值。
除了上诉两个方法,Field
类还提供了获取和设置基础类型的方法。例如setBoolean
、getBoolean
等方法。
代码示例如下:
Class<Student> clazz = Student.class;
Field ageField = clazz.getDeclaredField("age");
Student student = new Student(15);
// 通过get方法获取成员变量的值
Object age = ageField.get(student);
System.out.println(age); // 输出15
// 通过set方法设置成员变量的值
ageField.set(student, 20);
System.out.println(ageField.get(student)); // 输出20
通过Field对象获取成员变量的泛型参数类型
-
Type getGenericType()
获取带泛型参数的成员变量类型(即List<String>,而不仅仅是raw type)
和Method对象的方法类似,获取的Type是ParameterizedType,强转之后,通过ParameterizedType的getActualTypeArguments()
方法可以获取成员变量实际的泛型参数类型。
代码示例如下:
Class<Student> clazz = Student.class;
Field parentField = clazz.getDeclaredField("parent");
Type type = parentField.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] typeArgs = parameterizedType.getActualTypeArguments();
for (Type typeArg : typeArgs) {
System.out.println(typeArg); // 输出class java.lang.String
}
}
通过Field对象获取注解
Annotation[] getAnnotations()
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
Annotation[] getDeclaredAnnotations()
和Method的方法类似。示例如下:
Field ageField = clazz.getDeclaredField("age");
MyAnnotation myAnnotation = ageField.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.name()); // 打印出annotation
AnnotatedElement接口
上文中我们介绍了通过Class
,Constructor
,Method
,Field
来获取注解,可以观察到他们调用的方法都是相同的,因为他们都实现了AnnotatedElement
接口。
首先我们看一下文档:
Represents an annotated element of the program currently running in this
VM. This interface allows annotations to be read reflectively. All
annotations returned by methods in this interface are immutable and
serializable. The arrays returned by methods of this interface may be modified
by callers without affecting the arrays returned to other callers.
该接口表示当前运行在VM的程序中,被注解标注的元素,这就要求该注解的Retention
是RetentionPolicy.RUNTIME
。当然这个接口位于java.lang.reflect
包中,所以当然是为了反射而存在的咯。而且接口中方法返回的注解都是不可变的(immutable),所以不用担心因为注解在运行时被改变影响到其它代码。
接下来我们看一下接口的几个方法:
-
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
: 判断该类型的注解是否存在。 -
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
: 根据注解类型返回该AnnotatedElement
上的注解,如果不存在,则返回null
。 -
Annotation[] getAnnotations()
: 返回所有的注解,如果没有注解存在,则返回空数组。 -
Annotation[] getDeclaredAnnotations()
: 返回所有的注解,不包括从父类中继承来的,如果注解不存在,则返回空数组。
Java8以前的注解不允许重复出现相同类型的注解,但是Java8可以通过元注解@Repeatable
来表示一个注解是可重复出现的,所以AnnotatedElement
接口又添加了几个和重复注解相关的方法。
-
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
: 返回所有同一种类型的注解。如果不存在,则返回空数组。 -
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
: 返回所有同一种类型的注解,不包括从父类继承来的。如果不存在,则返回空数组。 -
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
: 根据注解类型返回注解,不包括从父类中继承来的。如果不存在,返回null
。这个和重复注解没关系,但也是Java8中添加的新方法。