Java学习笔记之反射(Reflection)

什么是反射

能够分析类能力的程序称为反射(reflection)

反射可以用来干什么

Java反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)检查类,接口,变量以及方法的信息。

  • 在运行中分析类的能力
  • 在运行中查看对象
  • 实现通用的数组操作代码
  • 利用Method对象

Class类

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着这个对象所属的类。可以通过专门的Java类访问这些信息,保存这些信息的类被称为Class类
获取Class对象的三种方法:

  1. 通过调用Object类中的getClass()方法返回一个Class对象

    1
    2
    Object myObject = new Object();
    Class myObjectClass = myObject.getClass();
  2. 通过调用静态方法forName获得类名对应的Class对象

    1
    2
    String className = "java.util.Date";
    Class cl = Class.forName(className);

注意:在使用Class.forName()方法时,必须提供一个类的全名,这个全名包括类所在的包的名字。

  1. 如果在调用该方法时,没有找到该路径的类,将会抛出ClassNotFoundException。
    获得Class对象的第三种方法非常简单,如果T是任意的Java类型,T.class将代表匹配的类对象
    1
    2
    Class cl1 = Date.class;
    Class cl2 = int.class;

补充:可通过下面方式访问类的父类

1
2
Object myObject = new Object();
Class myObjectClass = myObject.getSuperclass();

获取类名

通过Class对象可以获取两个版本的类名:

  1. 通过getName()方法返回类的全限定类名(包含包名):

    1
    2
    Class aClass = ... //获取Class对象
    String className = aClass.getName();
  2. 通过getSimpleName()方法返回类名(不包含包名)

    1
    2
    Class aClass = ... //获取Class对象
    String className = aClass.getSimpleName();

获取修饰符

可以通过Class对象的getModifiers()方法来访问一个类的修饰符,该方法通过返回一个整型数值,用不同的位开关描述public/private/static等修饰符的使用状况。

1
2
Class aClass = ... //获取Class对象
int modifiers = aClass.getModifiers();

还可以使用Modifier类中的isPublic、isPrivate或isFinal判断方法或构造器是否是public、private或final。

1
2
3
4
5
6
7
8
9
10
11
12
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);

获取包信息

可通过以下方式获取包信息:

1
2
3
4
5
6
7
8
...
Object object = new Object();
Class cl = object.getClass();
System.out.println(cl.getPackage());
...
/*output
package java.lang, Java Platform API Specification, version 1.7
*/

获取实现的接口集合

可通过调用Class对象的getInterfaces()方法获取一个类实现的接口集合

1
2
Class aClass = ... //获取Class对象
Class[] interfaces = aClass.getInterfaces();

注意:getInterfaces()方法仅仅只返回当前类所实现的接口,不包括当前类的父类所实现的接口。

获取构造器

可通过调用Class对象的getConstructors()方法获取一个类的构造函数

1
2
Class aClass = ... //获取Class对象
Constructor[] constructors = aClass.getConstructors();

返回的Constructor数组包含每一个声明为public的构造方法。


还可以通过给定的构造方法的参数类型获取指定的构造方法,如下:返回的构造方法的方法参数为String类型

1
2
Class aClass = ... //获取Class对象
Constructor constructor = aClass.getConstructor(new Class[]{String.class});

注意:如果没有指定的构造方法能匹配给定的方法参数,则会抛出NoSuchMethodException异常。


还可以获取指定构造方法的方法参数信息

1
2
Constructor constructor = ... //获取Constructor对象
Class[] parameterTypes = constructor.getParameterTypes();


利用Constructor对象实例化一个类

1
2
Constructor constructor = ... //获取Constructor对象
Class[] parameterTypes = constructor.getParameterTypes();

获取方法

可通过调用Class对象的getMethods()方法获取一个类的所有方法

1
2
Class aClass = ...//获取Class对象
Method[] methods = aClass.getMethods();

返回的Method对象数组包含了指定类中声明为public的所有变量集合。


还可以通过具体的参数类型来获取指定的方法

1
2
Class  aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", new Class[]{String.class});

注意:如果根据给定的方法名称以及参数类型无法匹配到相应的方法,则会抛出NoSuchMethodException


获取指定方法的方法参数以及返回类型

1
2
3
Method method = ... //获取Class对象
Class[] parameterTypes = method.getParameterTypes();
Class returnType = method.getReturnType();


通过Method对象调用方法

1
2
3
//获取一个方法名为doSomesthing,参数类型为String的方法
Method method = MyObject.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");

获取变量

可通过调用Class对象的getFields()方法获取一个类的成员变量

1
2
Class aClass = ... //获取Class对象
Field[] method = aClass.getFields();

返回的Field对象数组包含了指定类中声明为public的所有变量集合


还可以通过具体的变量名称获取指定的变量

1
2
Class aClass = ... //获取Class对象
Field[] method = aClass.getFields("someField");

注意:在调用getField()方法时,如果根据给定的方法参数没有找到对应的变量,那么就会抛出NoSuchFieldException


通过调用Field对象的getName()方法获取它的变量名称

1
2
Field field = ... //获取Field对象
String fieldName = field.getName();


通过调用Field对象getType()方法来获取一个变量的类型(如String, int等等)

1
2
3
Class aClass = ... //获取Class对象
Field field = aClass.getField("someField");
Object fieldType = field.getType();


通过调用Field.get()Field.set()方法获取或设置(get/set)变量值

1
2
3
4
5
6
7
8
Class  aClass = MyObject.class
Field field = aClass.getField("someField");

MyObject objectInstance = new MyObject();

Object value = field.get(objectInstance);

field.set(objetInstance, value);

获取指定类的getters和setters

  • Getter:Getter方法的名字以get开头,没有方法参数,返回一个值
  • Setter:Setter方法的名字以set开头,有一个方法参数

一个获取getter方法和setter方法的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void printGettersSetters(Class aClass){
Method[] methods = aClass.getMethods();

for(Method method : methods){
if(isGetter(method)) System.out.println("getter: " + method);
if(isSetter(method)) System.out.println("setter: " + method);
}
}

public static boolean isGetter(Method method){
if(!method.getName().startsWith("get")) return false;
if(method.getParameterTypes().length != 0) return false;
if(void.class.equals(method.getReturnType()) return false;
return true;
}

public static boolean isSetter(Method method){
if(!method.getName().startsWith("set")) return false;
if(method.getParameterTypes().length != 1) return false;
return true;
}

获取私有变量

可以通过调用Class.getDeclaredField(String name)方法或者Class.getDeclaredFields()方法获取私有变量和受保护变量,不包括超类的成员;Class.getField(String name)Class.getFields()只会返回公有变量,其中包括超类的公有变量,而无法获取私有变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class PrivateObject {

private String privateString = null;

public PrivateObject(String privateString) {
this.privateString = privateString;
}
}
/**********************************************************************/
PrivateObject privateObject = new PrivateObject("The Private Value");

Field privateStringField = PrivateObject.class.
getDeclaredField("privateString");

privateStringField.setAccessible(true);

String fieldValue = (String) privateStringField.get(privateObject);
System.out.println("fieldValue = " + fieldValue);

这个例子会输出”fieldValue = The Private Value”,The Private ValuePrivateObject实例的privateString私有变量的值,注意调用PrivateObject.class.getDeclaredField("privateString")方法会返回一个私有变量,这个方法返回的变量是定义在PrivateObject类中的而不是在它的父类中定义的变量。
注意:privateStringField.setAccessible(true)这行代码,通过调用setAccessible()方法会关闭指定类Field实例的反射访问检查,这行代码执行之后不论是私有的、受保护的以及包访问的作用域,你都可以在任何地方访问,即使你不在他的访问权限作用域之内。但是你如果你用一般代码来访问这些不在你权限作用域之内的代码依然是不可以的,在编译的时候就会报错。

获取私有方法

可以通过调用Class.getDeclaredMethod(String name, Class[] parameterTypes)或者Class.getDeclaredMethods()方法获取私有方法,不包括超类的成员;Class.getMethod(String name, Class[] parameterTypes)Class.getMethods()方法,只会返回公有的方法,其中包括超类的公有方法,而无法获取私有方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PrivateObject {

private String privateString = null;

public PrivateObject(String privateString) {
this.privateString = privateString;
}

private String getPrivateString(){
return this.privateString;
}
}
/*******************************************************************/
PrivateObject privateObject = new PrivateObject("The Private Value");

Method privateStringMethod = PrivateObject.class.
getDeclaredMethod("getPrivateString", null);

privateStringMethod.setAccessible(true);

String returnValue = (String)
privateStringMethod.invoke(privateObject, null);

System.out.println("returnValue = " + returnValue);

这个例子会输出"returnValue = The Private Value"The Private ValuePrivateObject实例的getPrivateString()方法的返回值。PrivateObject.class.getDeclaredMethod("privateString")方法会返回一个私有方法,这个方法是定义在PrivateObject类中的而不是在它的父类中定义的。
注意:Method.setAcessible(true)这行代码,通过调用setAccessible()方法会关闭指定类的Method实例的反射访问检查,这行代码执行之后不论是私有的、受保护的以及包访问的作用域,你都可以在任何地方访问,即使你不在他的访问权限作用域之内。但是你如果你用一般代码来访问这些不在你权限作用域之内的代码依然是不可以的,在编译的时候就会报错。

获取注解

什么是注解?
注解是Java 5的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用Java反射机制进行处理。
下面是一个类注解的例子:

1
2
3
@MyAnnotation(name="someName",  value = "Hello World")
public class TheClass {
}

在TheClass类定义的上面有一个@MyAnnotation的注解。注解的定义与接口的定义相似,下面是MyAnnotation注解的定义:

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)

public @interface MyAnnotation {
public String name();
public String value();
}

在interface前面的@符号表名这是一个注解,一旦你定义了一个注解之后你就可以将其应用到你的代码中。

说明:在注解定义中的两个指示@Retention(RetentionPolicy.RUNTIME)和@Target(ElementType.TYPE),说明了这个注解该如何使用。

  1. @Retention(RetentionPolicy.RUNTIME)表示这个注解可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,这样反射就无法获取它的信息。
  2. @Target(ElementType.TYPE) 表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。

类注解
下是一个访问类注解的例子

1
2
3
4
5
6
7
8
9
10
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

你还可以像下面这样指定访问一个类的注解

1
2
3
4
5
6
7
8
Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}


方法注解
下面是一个方法注解的例子

1
2
3
4
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public void doSomething(){}
}

你可以像这样访问方法注解:

1
2
3
4
5
6
7
8
9
10
Method method = ... //获取方法对象
Annotation[] annotations = method.getDeclaredAnnotations();

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

你可以像这样访问指定的方法注解

1
2
3
4
5
6
7
8
Method method = ... // 获取方法对象
Annotation annotation = method.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}


参数注解
方法参数也可以添加注解,就像下面这样

1
2
3
4
5
public class TheClass {
public static void doSomethingElse(
@MyAnnotation(name="aName", value="aValue") String parameter)
{

}
}

你可以通过Method对象来访问方法参数注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Method method = ... //获取方法对象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();

int i=0;
for(Annotation[] annotations : parameterAnnotations){
Class parameterType = parameterTypes[i++];

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("param: " + parameterType.getName());
System.out.println("name : " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
}

需要注意的是Method.getParameterAnnotations()方法返回一个注解类型的二维数组,每一个方法的参数包含一个注解数组。


变量注解
下面是一个变量注解的例子

1
2
3
4
5
public class TheClass {

@MyAnnotation(name="someName", value = "Hello World")
public String myField = null;
}

你可以像这样来访问变量的注解

1
2
3
4
5
6
7
8
9
10
Field field = ... //获取方法对象</pre>
<pre>Annotation[] annotations = field.getDeclaredAnnotations();

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

你可以像这样访问指定的变量注解

1
2
3
4
5
6
7
8
9
Field field = ...//获取方法对象</pre>
<pre>
Annotation annotation = field.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}

参考资料

  1. Java核心技术(卷Ⅰ)
  2. Java Reflection教程