1 什么是Java的反射呢?
Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。反射就是把java里面的各个对象映射成一个个java类。
2 Java反射有什么作用呢
Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过Jcreator和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。
3 获得字节码的三种方法
1 通过类的实例的getClass()方法获取,如 Class clazz=new Date().getClass()得到了Date类的字节码对象。
2 通过类的class属性获取。如Class clazz=String.class或者Class clazz=System.class.值得注意的是,java中八个主类型 (byte,char,short,int,long,float,double,boolean)和一个返回类型void都有class属性,并且都返 回自己的字节码对象。如int.class,boolean.class,float.class等。其中他们的包装类的TYPE属性也分别返回它们主类 型的那份字节码,如int.class==Integer.TYPE为true,void.class==Void.TYPE为true.
3 通过Class类的forName()方法获取,如Class clazz=Class.forName("java.lang.Math");
类
使用Java反射,你可以在运行时检查Java类。检查类是使用反射时经常做的第一件事情。从类中可以获取以下信息:
(1) 类名
(2) 类修饰符 (public, private, synchronized等)
(3) 包信息
(4) 父类
(5) 实现的接口
(6) 构造函数
(7) 方法
(8) 字段
(9) 注解
3.1类名
从Class对象中可以获取两个不同的类名。完全限定类名(包括包名)可以使用getName()或getCanonicalName()方法获取,例如:
Class aClass = MyObject.class;
String className = aClass.getName();
String className1 = aClass.getCanonicalName();
如果想要获取不含包名的类名可以使用getSimpleName() 方法,如下:
Class aClass = MyObject.class;
String simpleClassName = aClass.getSimpleName();
3.2修饰符
使用Class对象可以获取一个类的修饰符.类的修饰符即关键字"public","private", "static"等. 如下:
Class aClass = MyObject.class;
int modifiers = aClass.getModifiers();
修饰符被包装进一个int内,每一个修饰符都是一个标志位(置位或清零)。可以使用java.lang.reflect.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)
3.3包信息
使用Class对象可以获取包信息,如下:
Class aClass = MyObject.class;
Package package = aClass.getPackage();
String packageName = package.getname();
从Package对象中你可以访问诸如名字等包信息。您还可以访问类路径上这个包位于JAR文件中Manifest这个文件中指定的信息。例如,你可以在Manifest文件中指定包的版本号。可以在java.lang.Package中了解更多包类信息。
3.4父类
通过Class对象可以获取类的父类,如下:
Class aClass = MyObject.class;
Class superclass = aClass.getSuperclass();
父类的Class对象和其它Class对象一样是一个Class对象,可以继续使用反射.
3.5实现的接口
通过给定的类可以获取这个类所实现的接口列表,如下:
Class aClass = MyObject.class;
Class[] interfaces = aClass.getInterfaces();
一个类可以实现多个接口。因此返回一个Class数组。在Java反射机制中,接口也由Class对象表示。
注意:只有给定类声明实现的接口才会返回。例如,如果类A的父类B实现了一个接口C,但类A并没有声明它也实现了C,那么C不会被返回到数组中。即使类A实际上实现了接口C,因为它的父类B实现了C。
为了得到一个给定的类实现接口的完整列表,需要递归访问类和其超类。
3.6构造函数
使用Class对象可以获取类的构造函数,如下:
Class aClass = MyObject.class;
Constructor[] constructors = aClass.getConstructors();
关于构造函数更详细信息参见 构造函数这节。
3.7方法
使用Class对象可以获取类的方法,如下:
Class aClass = MyObject.class;
Method[] methods = aClass.getMethods();
关于方法更详细信息参见方法这节.
3.8字段
使用Class对象可以获取类的字段(成员变量),如下:
Class aClass = MyObject.class;
Field[] fields = aClass.getFields();
3.9注解
使用Class对象可以获取类的注解,如下:
Class aClass = MyObject.class;
Annotation[] annotations = aClass.getAnnotations();
关于注解更详细信息参见注解这节.
4. 构造函数
使用Java反射可以在运行时检查类的构造函数并实例化对象。这是通过Java类java.lang.reflect.Constructor来实现的。以下是Java Construcor对象的更多细节:
(1) 获取Constructor对象
(2) 构造函数参数
(3) 使用Constructor对象实例化对象
4.1获取Constructor对象
Constructor类是从Class对象获取的,举例:
Class aClass = MyObject.class;
Constructor[] constructors = aClass.getConstructors();
Constructor数组为每一个在类中声明的public构造函数保存一个Constructor实例。
如果知道要访问的构造函数确切的参数类型,可以不获取构造函数数组。本示例将返回给定类中接受一个字符串作为参数的公共构造函数。
Class aClass = MyObject.class;//MyObject有一个参数为字符串的公共构造函数
Constructor constructor = aClass.getConstructor(new Class[]{String.class});
如果没有匹配给定的构造函数参数,在这个例子当中是String.class,会抛出 NoSuchMethodException 异常.
4.2构造函数参数
可以知道给定的构造函数接受什么参数,如下:
Class aClass = MyObject.class;//MyObject有一个参数为字符串的公共构造函数
Constructor constructor = aClass.getConstructor(new Class[]{String.class});
Class[] parameterTypes = constructor.getParameterTypes();
4.3使用Constructor对象实例化对象
可以像这样实例化对象:
//获取使用字符串作为参数的constructor
Constructor constructor = MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject)constructor.newInstance("constructor-arg1");
Constructor.newInstance() 方法使用可变长度的参数,但是在调用构造函数时必须为每一个参数提供一个准确的参量.在这个例子中构造函数接受一个字符串作为参数,所以必须要提供一个字符串。
5. 字段
使用Java反射你可以在运行时检查类的字段(成员变量)并 get / set它们.这是通过Java类java.lang.reflect.Field来完成的.以下是JavaField对象更多细节:
(1) 获取Field 对象
(2) 字段名称
(3) 字段类型
(4) 获取和设置字段值
5.1获取Field 对象
Field类是从Class对象获取的.举例:
Class aClass = MyObject.class;
Field[] methods = aClass.getFields();
Field数组为类中声明的每一个public字段保存一个Field 实例。
如果知道要访问的字段名称,可以这样获取它:
Class aClass = MyObject.class
Field field = aClass.getField("someField");
根据下面 MyObject 声明的someField字段,以上的例子将返回Field实例:
public class MyObject{
public String someField = null;
}
如果不存在getField()方法中所给参数名字对应的字段,会抛出NoSuchFieldException异常.
5.2字段名称
一旦获得一个 Field实例,可以使用Field.getName()方法获取它的字段名字,如下:
Field field = ... //获取 field 对象
String fieldName = field.getName();
5.3字段类型
你可以使用Field.getType()方法确定一个字段的类型(String,int等):
Field field = aClass.getField("someField");
Object fieldType = field.getType();
5.4获取和设置字段值
一旦获得一个Field引用,可以使用Field.get() 和 Field.set()方法获取和设置它的值,如下:
Class aClass = MyObject.class
Field field = aClass.getField("someField");
MyObject objectInstance = new MyObject();
Object value = field.get(objectInstance);
field.set(objetInstance, value);
传递给get和set方法的对象实例参数应该是拥有该字段的类的一个实例。在上述的例子中使用了MyObject的一个实例,因为someField是MyObject类的一个实例成员。静态字段(public static)给get和set方法传递null作为参数,而不是以上传递的objectInstance参数。
6. 方法
使用Java反射你可以在运行时检查类的方法并调用它们.这是通过Java类java.lang.reflect.Method来实现的.本章将会更详细的介绍 Java Method对象.下面是本章所涵盖的主题:
(1) 获取 Method对象
(2) 方法的参数和返回值类型
(3) 使用Method对象调用方法
6.1获取Method对象
Method类是从 Class 对象中获取的.举例:
Class aClass = MyObject.class;
Method[] methods = aClass.getMethods();
Method数组将为类中声明的每一个public的方法保存一个Method实例.
如果你知道要访问方法的确切的参数类型,可以不必获取方法数组。本示例返回给定类中接受一个字符串作为参数的公共方法”doSomething”。
Class aClass = MyObject.class;
Method method = aClass.getMethod("doSomething", new Class[]{String.class});
如果没有方法匹配所给的方法名和参数,在这个例子中是String.class,将抛出NoSuchMethodException异常.
如果你想访问的方法没有参数,传递 null作为参数类型数组,如下:
Class aClass = MyObject.class;
Method method = aClass.getMethod("doSomething", null);
6.2方法的参数和返回值类型
使用Method对象可以获取方法的参数,如下:
Method method = ... //获取 method – 如上
Class[] parameterTypes = method.getParameterTypes();
亦可以获取方法的返回值类型,如下:
Method method = ... // obtain method - see above
Class returnType = method.getReturnType();
6.3使用Method对象调用方法
可以像这样调用方法:
//get method that takes a String as argument
Method method = MyObject.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");
空参数是你想要调用该方法的对象。如果该方法是静态的,使用null,而不是一个对象实例。在这个例子中,如果doSomething(String.class)不是静态的,你需要提供有效的MyObject实例而不是null作为参数;
Method.invoke(Object target, Object ...parameters)方法接受可变长度的参数,但是你在调用时必须为每一个参数提供一个准确的参量。在这个例子中,方法以字符串作为参数的,所以必须提供一个字符串。
7. get和set方法
使用Java反射可以在运行时检查类的方法并调用它们。这可以用来检测一个给定的类有哪些get和set方法。可以通过扫描一个类的所有方法并检查每个方法是否是get或set方法。
下面是一段用来找到类的get和set方法的代码:
public staticvoid printGettersSetters(Class aClass){
Method[]methods = aClass.getMethods();
for(Methodmethod : methods){
if(isGetter(method))System.out.println("getter: " + method);
if(isSetter(method))System.out.println("setter: " + method);
}
}
public staticboolean 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;
returntrue;
}
public staticboolean isSetter(Method method){
if(!method.getName().startsWith("set"))return false;
if(method.getParameterTypes().length!= 1) return false;
return true;
}
8. 私有字段和方法
可以通过Java反射访问类的私有字段和方法。
8.1访问私有字段
要想访问私有字段你需要调用Class.getDeclaredField(Stringname)或 Class.getDeclaredFields()方法. Class.getField(String name) 和 Class.getFields()方法仅返回public字段。下面是一个简单的示例,类有一个私有字段,代码通过反射来访问这个私有字段:
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",即代码示例最开始创建的PrivateObject实例的私有字段 privateString的值。
注意PrivateObject.class.getDeclaredField("privateString")的使用。这个方法仅仅返回特定类声明的字段,而不包括任何父类中声明的字段。
注意粗体代码.通过调用Field.setAcessible(true)方法关闭了特定Field实例的访问检查,现在通过反射可以访问它,即使它是私有的,保护的或包范围,甚至调用者不属于这些范围。但编译器不允许使用普通的代码该字段,因为仅适用于反射。
8.2访问私有方法
想要访问私有方法需要调用Class.getDeclaredMethod(String name,Class[] parameterTypes) 或Class.getDeclaredMethods() 方法. Class.getMethod(String name, Class[]parameterTypes) 和 Class.getMethods() 方法仅返回public方法。下面是一个简单的示例,类有一个私有方法,代码通过反射来访问这个私有方法:
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 ",即代码示例最开始创建的PrivateObject实例调用的私有方法 getPrivateString()的返回值。
注意PrivateObject.class.getDeclaredMethod("privateString")方法的使用。这个方法仅仅返回特定类声明的方法,而不包括任何父类中声明的方法。
注意粗体代码.通过调用Method.setAcessible(true)方法关闭了特定Method实例的访问检查。现在通过反射可以访问它,即使它是私有的,保护的或包范围,甚至调用者不属于这些范围。但编译器不允许使用普通的代码访问该方法,因为仅适用于反射。
9. Array
在Java反射中使用数组有时候有点棘手,特别是如果你想获得某种类型的数组的Class对象,如int[]等。本节将讨论如何通过Java反射创建数组和Class对象。以下是本章涵盖的主题:
(1) java.lang.reflect.Array
(2) 创建数组
(3) 访问数组
(4) 获取数组的Class对象
(5) 获取数组的组件类型
9.1 java.lang.reflect.Array
通过Java反射机制使用数组是由thejava.lang.reflect.Array类完成的。不要把这个类和Java集合框架中的java.util.Arrays类相混淆,java.util.Arrays类包含数组排序,将它们转换为集合等公共方法。
9.2 创建数组
通过Java反射机制创建数组是由java.lang.reflect.Array类来完成的,如下:
int[] intArray = (int[]) Array.newInstance(int.class, 3);
这行代码创建了一个int数组。Array.newInstance()方法的第一个参数告诉我们数组中的元素类型。第二个参数声明了数组需要为多少个元素分配空间。
9.3 访问数组
使用Java反射机制可以访问数组的元素。这是通过 Array.get(...) 和 Array.set(...) 方法实现的,如下:
int[] intArray = (int[]) Array.newInstance(int.class, 3);
Array.set(intArray, 0, 123);
Array.set(intArray, 1, 456);
Array.set(intArray, 2, 789);
System.out.println("intArray[0] = " + Array.get(intArray, 0));
System.out.println("intArray[1] = " + Array.get(intArray, 1));
System.out.println("intArray[2] = " + Array.get(intArray, 2));
这段代码输出:
intArray[0] = 123
intArray[1] = 456
intArray[2] = 789
9.4 获取数组的Class对象
获取数组的Class对象可以使用无反射的代码,如下:
Class stringArrayClass = String[].class;
使用 Class.forName()方法并不易懂。例如,你可以像下面这样获取int数组的Class对象:
Class intArray = Class.forName("[I");
字母 I在JVM中代表一个。左边的 [ 表示它是一个int数组的class。这对其它的基本类型也有效。
对于对象,你需要使用一个稍微不同的记号:
Class stringArrayClass = Class.forName("[Ljava.lang.String;");
注意到左边的” [L”和右边的” ; ”之间的类名。它代表对象数组的给定类型。
另外一个需要注意的是你不能够使用Class.forName()方法获取基本类型的Class对象。下面两个例子都会抛出ClassNotFoundException异常:
Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");
可以像下面这样获取基本数据类型和对象的Class名:
public Class getClass(String className){
if("int" .equals(className)) return int .class;
if("long".equals(className)) return long.class;
...
return Class.forName(className);
}
一旦你获得某个类型的Class对象,有一个简单的方法来获得该类型的数组的Class对象。解决方案是创建所需类型的空数组并从这个空数组获取Class对象,如下:
Class theClass = getClass(theClassName);
Class stringArrayClass = Array.newInstance(theClass, 0).getClass();
这提供了一个单一的、统一的方法来访问任何类型的数组的Class对象。
要确保 Class对象是一个数组,可以使用Class.isArray()方法来校验:
Class stringArrayClass = Array.newInstance(String.class, 0).getClass();
System.out.println("is array: " + stringArrayClass.isArray());
9.5 获取数组的组件类型
一旦获取到某个数组的Class对象可以使用 Class.getComponentType()方法来获取它的组件类型。组件类型就是数组中元素的类型。例如,int数组的组件类型是 int.class Class对象.String[]数组的组件类型是 java.lang.String Class对象。
下面是获取数组的组件类型的示例:
String[] strings = new String[3];
Class stringArrayClass = strings.getClass();
Class stringArrayComponentType = stringArrayClass.getComponentType();
System.out.println(stringArrayComponentType);
输出 "class java.lang.String"即为String数组的组件类型.
10. 注解
使用Java反射你可以在运行时访问Java类中的注解.下面是本章涵盖的主题:
(1)什么是Java注解?
(2)类注解
(3)方法注解
(4)参数注解
(5)字段注解
10.1 什么是Java注解?
注解是Java5的一个新功能。注解是一种可以在Java代码中插入的注释或元数据。这些注解可以在编译时由预编译工具进行处理,也可以在运行时通过Java反射机制来处理。下面是一个类注解的示例:
@MyAnnotation(name="someName", value = "Hello World")
public class TheClass {
}
TheClass类的上面有 @MyAnnotation 注解.注解像接口那样定义。下面是MyAnnotation注解 的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
public String name();
public String value();
}
在interface前面的”@”标志它是一个注解。一旦你定义了那个注解你就可以在代码中使用它,就像上面的例子那样。
在注解中定义的两个指令@Retention(RetentionPolicy.RUNTIME)和 @Target(ElementType.TYPE)表明注解是如何使用的
@Retention(RetentionPolicy.RUNTIME)表明在运行时可以使用反射来访问这个注解。如果没有设置这个指令,在运行时这个注解将不会被保存,因此通过反射访问不到。
@Target(ElementType.TYPE) 表明注解仅能用于类、接口(包括注释类型)或枚举声明等类型上。你也可以指定 METHOD 或FIELD,或者@Target什么都不指定,这样它可以用在任何程序元素上。
10.2 类注解
可以在运行时获取类、方法或字段的注解。下面是获取类注解的示例:
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());
}
}
也可以获取指定的类注解,如下:
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());
}
10.3 方法注解
下面是方法注解的示例:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public void doSomething(){}
}
获取方法的所有注解,如下:
Method method = ... //obtain method object
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());
}
}
获取方法的特定注解:
Method method = ... // obtain method object
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());
}
10.4 参数注解
也可以给方法声明的参数添加注解,如下:
public class TheClass {
public static void doSomethingElse(
@MyAnnotation(name="aName", value="aValue") String parameter){
}
}
从Method对象获取参数注解:
Method method = ... //obtain method object
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() 方法返回的是二维的Annotation数组,每个方法参数都有一个一维的Annotation数组。
10.5 字段注解
下面是字段注解示例:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public String myField = null;
}
获取字段的所有注解,如下:
Field field = ... //obtain field object
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());
}
}
获取指定字段的注解,如下:
Field field = ... // obtain method object
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());
}
11. 泛型
经常在文章和论坛里读到说所有Java泛型信息在编译的时候被擦除了所以在运行时访问不到任何泛型信息。这并不完全正确。在极少数的情况下,在运行时是可以访问泛型信息的。这些情况实际上涵盖一些我们需要的Java泛型信息。本章将解释这些情况。下面是本章涵盖的主题:
(1) 泛型反射的经验法则
(2) 泛型方法的返回值类型
(3) 泛型方法的参数类型
(4) 泛型字段类型
11.1 泛型反射的经验法则
使用Java的泛型通常分为两种不同的情况:
(1)声明一个可参数化的类/接口。
(2)使用参数化的类。
当写一个类或接口时,可以指定它应该是可参数化的。就像java.util.List接口那样,可以参数化java.util.List来创建一个String列表而不是创建Object列表。
java.util.List在运行时会检查参数化的类型,但是没有办法知道参数化的是什么类型。这是因为在相同的应用程序中类型可以被参数化为所有的类型。但是,当你检查方法或字段所声明使用的参数化类型,你可以在运行时看到可参数化的类型被参数化为什么类型。总之,你不能在运行时看到类型本身被参数化为什么类型,但你可以在使用和参数化它的字段和方法中看到它。
使用Java反射,你可以在运行时检查Java类。检查类是使用反射时经常做的第一件事情。从类中可以获取以下信息:
(1) 类名
(2) 类修饰符 (public, private, synchronized等)
(3) 包信息
(4) 父类
(5) 实现的接口
(6) 构造函数
(7) 方法
(8) 字段
(9) 注解
3.1类名
从Class对象中可以获取两个不同的类名。完全限定类名(包括包名)可以使用getName()或getCanonicalName()方法获取,例如:
Class aClass = MyObject.class;
String className = aClass.getName();
String className1 = aClass.getCanonicalName();
如果想要获取不含包名的类名可以使用getSimpleName() 方法,如下:
Class aClass = MyObject.class;
String simpleClassName = aClass.getSimpleName();
3.2修饰符
使用Class对象可以获取一个类的修饰符.类的修饰符即关键字"public","private", "static"等. 如下:
Class aClass = MyObject.class;
int modifiers = aClass.getModifiers();
修饰符被包装进一个int内,每一个修饰符都是一个标志位(置位或清零)。可以使用java.lang.reflect.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)
3.3包信息
使用Class对象可以获取包信息,如下:
Class aClass = MyObject.class;
Package package = aClass.getPackage();
String packageName = package.getname();
从Package对象中你可以访问诸如名字等包信息。您还可以访问类路径上这个包位于JAR文件中Manifest这个文件中指定的信息。例如,你可以在Manifest文件中指定包的版本号。可以在java.lang.Package中了解更多包类信息。
3.4父类
通过Class对象可以获取类的父类,如下:
Class aClass = MyObject.class;
Class superclass = aClass.getSuperclass();
父类的Class对象和其它Class对象一样是一个Class对象,可以继续使用反射.
3.5实现的接口
通过给定的类可以获取这个类所实现的接口列表,如下:
Class aClass = MyObject.class;
Class[] interfaces = aClass.getInterfaces();
一个类可以实现多个接口。因此返回一个Class数组。在Java反射机制中,接口也由Class对象表示。
注意:只有给定类声明实现的接口才会返回。例如,如果类A的父类B实现了一个接口C,但类A并没有声明它也实现了C,那么C不会被返回到数组中。即使类A实际上实现了接口C,因为它的父类B实现了C。
为了得到一个给定的类实现接口的完整列表,需要递归访问类和其超类。
3.6构造函数
使用Class对象可以获取类的构造函数,如下:
Class aClass = MyObject.class;
Constructor[] constructors = aClass.getConstructors();
关于构造函数更详细信息参见 构造函数这节。
3.7方法
使用Class对象可以获取类的方法,如下:
Class aClass = MyObject.class;
Method[] methods = aClass.getMethods();
关于方法更详细信息参见方法这节.
3.8字段
使用Class对象可以获取类的字段(成员变量),如下:
Class aClass = MyObject.class;
Field[] fields = aClass.getFields();
关于字段更详细信息参见 字段这节.
3.9注解
使用Class对象可以获取类的注解,如下:
Class aClass = MyObject.class;
Annotation[] annotations = aClass.getAnnotations();
关于注解更详细信息参见注解这节.
4. 构造函数
使用Java反射可以在运行时检查类的构造函数并实例化对象。这是通过Java类java.lang.reflect.Constructor来实现的。以下是Java Construcor对象的更多细节:
(1) 获取Constructor对象
(2) 构造函数参数
(3) 使用Constructor对象实例化对象
4.1获取Constructor对象
Constructor类是从Class对象获取的,举例:
Class aClass = MyObject.class;
Constructor[] constructors = aClass.getConstructors();
Constructor数组为每一个在类中声明的public构造函数保存一个Constructor实例。
如果知道要访问的构造函数确切的参数类型,可以不获取构造函数数组。本示例将返回给定类中接受一个字符串作为参数的公共构造函数。
Class aClass = MyObject.class;//MyObject有一个参数为字符串的公共构造函数
Constructor constructor = aClass.getConstructor(new Class[]{String.class});
如果没有匹配给定的构造函数参数,在这个例子当中是String.class,会抛出 NoSuchMethodException 异常.
4.2构造函数参数
可以知道给定的构造函数接受什么参数,如下:
Class aClass = MyObject.class;//MyObject有一个参数为字符串的公共构造函数
Constructor constructor = aClass.getConstructor(new Class[]{String.class});
Class[] parameterTypes = constructor.getParameterTypes();
4.3使用Constructor对象实例化对象
可以像这样实例化对象:
//获取使用字符串作为参数的constructor
Constructor constructor = MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject)constructor.newInstance("constructor-arg1");
Constructor.newInstance() 方法使用可变长度的参数,但是在调用构造函数时必须为每一个参数提供一个准确的参量.在这个例子中构造函数接受一个字符串作为参数,所以必须要提供一个字符串。
5. 字段
使用Java反射你可以在运行时检查类的字段(成员变量)并 get / set它们.这是通过Java类java.lang.reflect.Field来完成的.以下是JavaField对象更多细节:
(1) 获取Field 对象
(2) 字段名称
(3) 字段类型
(4) 获取和设置字段值
5.1获取Field 对象
Field类是从Class对象获取的.举例:
Class aClass = MyObject.class;
Field[] methods = aClass.getFields();
Field数组为类中声明的每一个public字段保存一个Field 实例。
如果知道要访问的字段名称,可以这样获取它:
Class aClass = MyObject.class
Field field = aClass.getField("someField");
根据下面 MyObject 声明的someField字段,以上的例子将返回Field实例:
public class MyObject{
public String someField = null;
}
如果不存在getField()方法中所给参数名字对应的字段,会抛出NoSuchFieldException异常.
5.2字段名称
一旦获得一个 Field实例,可以使用Field.getName()方法获取它的字段名字,如下:
Field field = ... //获取 field 对象
String fieldName = field.getName();
5.3字段类型
你可以使用Field.getType()方法确定一个字段的类型(String,int等):
Field field = aClass.getField("someField");
Object fieldType = field.getType();
5.4获取和设置字段值
一旦获得一个Field引用,可以使用Field.get() 和 Field.set()方法获取和设置它的值,如下:
Class aClass = MyObject.class
Field field = aClass.getField("someField");
MyObject objectInstance = new MyObject();
Object value = field.get(objectInstance);
field.set(objetInstance, value);
传递给get和set方法的对象实例参数应该是拥有该字段的类的一个实例。在上述的例子中使用了MyObject的一个实例,因为someField是MyObject类的一个实例成员。静态字段(public static)给get和set方法传递null作为参数,而不是以上传递的objectInstance参数。
6. 方法
使用Java反射你可以在运行时检查类的方法并调用它们.这是通过Java类java.lang.reflect.Method来实现的.本章将会更详细的介绍 Java Method对象.下面是本章所涵盖的主题:
(1) 获取 Method对象
(2) 方法的参数和返回值类型
(3) 使用Method对象调用方法
6.1获取Method对象
Method类是从 Class 对象中获取的.举例:
Class aClass = MyObject.class;
Method[] methods = aClass.getMethods();
Method数组将为类中声明的每一个public的方法保存一个Method实例.
如果你知道要访问方法的确切的参数类型,可以不必获取方法数组。本示例返回给定类中接受一个字符串作为参数的公共方法”doSomething”。
Class aClass = MyObject.class;
Method method = aClass.getMethod("doSomething", new Class[]{String.class});
如果没有方法匹配所给的方法名和参数,在这个例子中是String.class,将抛出NoSuchMethodException异常.
如果你想访问的方法没有参数,传递 null作为参数类型数组,如下:
Class aClass = MyObject.class;
Method method = aClass.getMethod("doSomething", null);
6.2方法的参数和返回值类型
使用Method对象可以获取方法的参数,如下:
Method method = ... //获取 method – 如上
Class[] parameterTypes = method.getParameterTypes();
亦可以获取方法的返回值类型,如下:
Method method = ... // obtain method - see above
Class returnType = method.getReturnType();
6.3使用Method对象调用方法
可以像这样调用方法:
//get method that takes a String as argument
Method method = MyObject.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");
空参数是你想要调用该方法的对象。如果该方法是静态的,使用null,而不是一个对象实例。在这个例子中,如果doSomething(String.class)不是静态的,你需要提供有效的MyObject实例而不是null作为参数;
Method.invoke(Object target, Object ...parameters)方法接受可变长度的参数,但是你在调用时必须为每一个参数提供一个准确的参量。在这个例子中,方法以字符串作为参数的,所以必须提供一个字符串。
7. get和set方法
使用Java反射可以在运行时检查类的方法并调用它们。这可以用来检测一个给定的类有哪些get和set方法。可以通过扫描一个类的所有方法并检查每个方法是否是get或set方法。
下面是一段用来找到类的get和set方法的代码:
public staticvoid printGettersSetters(Class aClass){
Method[]methods = aClass.getMethods();
for(Methodmethod : methods){
if(isGetter(method))System.out.println("getter: " + method);
if(isSetter(method))System.out.println("setter: " + method);
}
}
public staticboolean 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;
returntrue;
}
public staticboolean isSetter(Method method){
if(!method.getName().startsWith("set"))return false;
if(method.getParameterTypes().length!= 1) return false;
return true;
}
8. 私有字段和方法
可以通过Java反射访问类的私有字段和方法。
8.1访问私有字段
要想访问私有字段你需要调用Class.getDeclaredField(Stringname)或 Class.getDeclaredFields()方法. Class.getField(String name) 和 Class.getFields()方法仅返回public字段。下面是一个简单的示例,类有一个私有字段,代码通过反射来访问这个私有字段:
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",即代码示例最开始创建的PrivateObject实例的私有字段 privateString的值。
注意PrivateObject.class.getDeclaredField("privateString")的使用。这个方法仅仅返回特定类声明的字段,而不包括任何父类中声明的字段。
注意粗体代码.通过调用Field.setAcessible(true)方法关闭了特定Field实例的访问检查,现在通过反射可以访问它,即使它是私有的,保护的或包范围,甚至调用者不属于这些范围。但编译器不允许使用普通的代码该字段,因为仅适用于反射。
8.2访问私有方法
想要访问私有方法需要调用Class.getDeclaredMethod(String name,Class[] parameterTypes) 或Class.getDeclaredMethods() 方法. Class.getMethod(String name, Class[]parameterTypes) 和 Class.getMethods() 方法仅返回public方法。下面是一个简单的示例,类有一个私有方法,代码通过反射来访问这个私有方法:
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 ",即代码示例最开始创建的PrivateObject实例调用的私有方法 getPrivateString()的返回值。
注意PrivateObject.class.getDeclaredMethod("privateString")方法的使用。这个方法仅仅返回特定类声明的方法,而不包括任何父类中声明的方法。
注意粗体代码.通过调用Method.setAcessible(true)方法关闭了特定Method实例的访问检查。现在通过反射可以访问它,即使它是私有的,保护的或包范围,甚至调用者不属于这些范围。但编译器不允许使用普通的代码访问该方法,因为仅适用于反射。
9. Array
在Java反射中使用数组有时候有点棘手,特别是如果你想获得某种类型的数组的Class对象,如int[]等。本节将讨论如何通过Java反射创建数组和Class对象。以下是本章涵盖的主题:
(1) java.lang.reflect.Array
(2) 创建数组
(3) 访问数组
(4) 获取数组的Class对象
(5) 获取数组的组件类型
9.1 java.lang.reflect.Array
通过Java反射机制使用数组是由thejava.lang.reflect.Array类完成的。不要把这个类和Java集合框架中的java.util.Arrays类相混淆,java.util.Arrays类包含数组排序,将它们转换为集合等公共方法。
9.2 创建数组
通过Java反射机制创建数组是由java.lang.reflect.Array类来完成的,如下:
int[] intArray = (int[]) Array.newInstance(int.class, 3);
这行代码创建了一个int数组。Array.newInstance()方法的第一个参数告诉我们数组中的元素类型。第二个参数声明了数组需要为多少个元素分配空间。
9.3 访问数组
使用Java反射机制可以访问数组的元素。这是通过 Array.get(...) 和 Array.set(...) 方法实现的,如下:
int[] intArray = (int[]) Array.newInstance(int.class, 3);
Array.set(intArray, 0, 123);
Array.set(intArray, 1, 456);
Array.set(intArray, 2, 789);
System.out.println("intArray[0] = " + Array.get(intArray, 0));
System.out.println("intArray[1] = " + Array.get(intArray, 1));
System.out.println("intArray[2] = " + Array.get(intArray, 2));
这段代码输出:
intArray[0] = 123
intArray[1] = 456
intArray[2] = 789
9.4 获取数组的Class对象
获取数组的Class对象可以使用无反射的代码,如下:
Class stringArrayClass = String[].class;
使用 Class.forName()方法并不易懂。例如,你可以像下面这样获取int数组的Class对象:
Class intArray = Class.forName("[I");
字母 I在JVM中代表一个。左边的 [ 表示它是一个int数组的class。这对其它的基本类型也有效。
对于对象,你需要使用一个稍微不同的记号:
Class stringArrayClass = Class.forName("[Ljava.lang.String;");
注意到左边的” [L”和右边的” ; ”之间的类名。它代表对象数组的给定类型。
另外一个需要注意的是你不能够使用Class.forName()方法获取基本类型的Class对象。下面两个例子都会抛出ClassNotFoundException异常:
Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");
可以像下面这样获取基本数据类型和对象的Class名:
public Class getClass(String className){
if("int" .equals(className)) return int .class;
if("long".equals(className)) return long.class;
...
return Class.forName(className);
}
一旦你获得某个类型的Class对象,有一个简单的方法来获得该类型的数组的Class对象。解决方案是创建所需类型的空数组并从这个空数组获取Class对象,如下:
Class theClass = getClass(theClassName);
Class stringArrayClass = Array.newInstance(theClass, 0).getClass();
这提供了一个单一的、统一的方法来访问任何类型的数组的Class对象。
要确保 Class对象是一个数组,可以使用Class.isArray()方法来校验:
Class stringArrayClass = Array.newInstance(String.class, 0).getClass();
System.out.println("is array: " + stringArrayClass.isArray());
9.5 获取数组的组件类型
一旦获取到某个数组的Class对象可以使用 Class.getComponentType()方法来获取它的组件类型。组件类型就是数组中元素的类型。例如,int数组的组件类型是 int.class Class对象.String[]数组的组件类型是 java.lang.String Class对象。
下面是获取数组的组件类型的示例:
String[] strings = new String[3];
Class stringArrayClass = strings.getClass();
Class stringArrayComponentType = stringArrayClass.getComponentType();
System.out.println(stringArrayComponentType);
输出 "class java.lang.String"即为String数组的组件类型.
10. 注解
使用Java反射你可以在运行时访问Java类中的注解.下面是本章涵盖的主题:
(1)什么是Java注解?
(2)类注解
(3)方法注解
(4)参数注解
(5)字段注解
10.1 什么是Java注解?
注解是Java5的一个新功能。注解是一种可以在Java代码中插入的注释或元数据。这些注解可以在编译时由预编译工具进行处理,也可以在运行时通过Java反射机制来处理。下面是一个类注解的示例:
@MyAnnotation(name="someName", value = "Hello World")
public class TheClass {
}
TheClass类的上面有 @MyAnnotation 注解.注解像接口那样定义。下面是MyAnnotation注解 的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
public String name();
public String value();
}
在interface前面的”@”标志它是一个注解。一旦你定义了那个注解你就可以在代码中使用它,就像上面的例子那样。
在注解中定义的两个指令@Retention(RetentionPolicy.RUNTIME)和 @Target(ElementType.TYPE)表明注解是如何使用的
@Retention(RetentionPolicy.RUNTIME)表明在运行时可以使用反射来访问这个注解。如果没有设置这个指令,在运行时这个注解将不会被保存,因此通过反射访问不到。
@Target(ElementType.TYPE) 表明注解仅能用于类、接口(包括注释类型)或枚举声明等类型上。你也可以指定 METHOD 或FIELD,或者@Target什么都不指定,这样它可以用在任何程序元素上。
10.2 类注解
可以在运行时获取类、方法或字段的注解。下面是获取类注解的示例:
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());
}
}
也可以获取指定的类注解,如下:
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());
}
10.3 方法注解
下面是方法注解的示例:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public void doSomething(){}
}
获取方法的所有注解,如下:
Method method = ... //obtain method object
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());
}
}
获取方法的特定注解:
Method method = ... // obtain method object
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());
}
10.4 参数注解
也可以给方法声明的参数添加注解,如下:
public class TheClass {
public static void doSomethingElse(
@MyAnnotation(name="aName", value="aValue") String parameter){
}
}
从Method对象获取参数注解:
Method method = ... //obtain method object
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() 方法返回的是二维的Annotation数组,每个方法参数都有一个一维的Annotation数组。
10.5 字段注解
下面是字段注解示例:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public String myField = null;
}
获取字段的所有注解,如下:
Field field = ... //obtain field object
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());
}
}
获取指定字段的注解,如下:
Field field = ... // obtain method object
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());
}
11. 泛型
经常在文章和论坛里读到说所有Java泛型信息在编译的时候被擦除了所以在运行时访问不到任何泛型信息。这并不完全正确。在极少数的情况下,在运行时是可以访问泛型信息的。这些情况实际上涵盖一些我们需要的Java泛型信息。本章将解释这些情况。下面是本章涵盖的主题:
(1) 泛型反射的经验法则
(2) 泛型方法的返回值类型
(3) 泛型方法的参数类型
(4) 泛型字段类型
11.1 泛型反射的经验法则
使用Java的泛型通常分为两种不同的情况:
(1)声明一个可参数化的类/接口。
(2)使用参数化的类。
当写一个类或接口时,可以指定它应该是可参数化的。就像java.util.List接口那样,可以参数化java.util.List来创建一个String列表而不是创建Object列表。
java.util.List在运行时会检查参数化的类型,但是没有办法知道参数化的是什么类型。这是因为在相同的应用程序中类型可以被参数化为所有的类型。但是,当你检查方法或字段所声明使用的参数化类型,你可以在运行时看到可参数化的类型被参数化为什么类型。总之,你不能在运行时看到类型本身被参数化为什么类型,但你可以在使用和参数化它的字段和方法中看到它。
11.2 泛型方法的返回值类型
如果你获取到一个java.lang.reflect.Method 对象,可以获取它的返回值类型信息。下面是一个示例,一个类有一个有参数化返回值类型的方法:
public class MyClass {
protected List<String> stringList = ...;
public List<String> getStringList(){
return this.stringList;
}
}
在这个类中可以获取 getStringList()方法的泛型返回值类型。换句话说,可以探测到getStringList()返回的是List<String> 而不仅是一个 List。如下:
Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}
代码输出 "typeArgClass = class java.lang.String"。
Type[]数组 typeArguments包含一项 -一个代表类java.lang.String的 Class实例 。 Class实现了 Type接口。
11.3 泛型方法的参数类型
通过Java反射可以在运行时访问参数类型的泛型类型。下面的示例中,类有一个使用参数化的List作为参数的方法:
public class MyClass {
protected List<String> stringList = ...;
public void setStringList(List<String> list){
this.stringList = list;
}
}
可以访问方法参数的泛型参数类型,如下:
Method method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
代码输出"parameterArgType= class java.lang.String"。
Type[]数组 parameterArgTypes包含一项 -一个代表类java.lang.String的 Class实例 。 Class实现了 Type接口。
11.4 泛型字段类型
可以访问public字段的泛型类型。字段即类的成员变量-静态的或实例变量。下面是一个例子,类有一个实例变量stringList.
public class MyClass {
public List<String> stringList = ...;
}
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
代码将输出"fieldArgClass = class java.lang.String"。
Type数组fieldArgTypes包含一项 – 一个代表类java.lang.String的 Class实例 。 Class实现了 Type接口。