非原创:以下内容都是学习Java反射时从网上摘录的信息汇总
Java反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)检查类,接口,变量以及方法的信息。反射还可以让我们在运行期实例化对象,调用方法,通过调用get/set方法获取变量的值。
下面是一个Java反射的简单例子:
Method[] methods = MyObject.class.getMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}
在这个例子中通过调用MyObject类的class属性获取对应的Class类的对象,通过这个Class类的对象获取MyObject类中的方法集合。迭代这个方法的集合并且打印每个方法的名字。
使用Java反射机制可以在运行时期检查Java类的信息,检查Java类的信息往往是你在使用Java反射机制的时候所做的第一件事情,通过获取类的信息你可以获取以下相关的内容:
- Class对象
- 类名
- 修饰符
- 包信息
- 父类
- 实现的接口
- 构造器
- 方法
- 变量
- 注解
Class对象
在你想检查一个类的信息之前,你首先需要获取类的Class对象。Java中的所有类型包括基本类型(int, long, float等等),即使是数组都有与之关联的Class类的对象。如果你在编译期知道一个类的名字的话,那么你可以使用如下的方式获取一个类的Class对象。
Class myObjectClass = MyObject.class;
如果你在编译期不知道类的名字,但是你可以在运行期获得到类名的字符串,那么你则可以这么做来获取Class对象:
String className = ... ;//在运行期获取的类名字符串
Class class = Class.forName(className);
在使用Class.forName()方法时,你必须提供一个类的全名,这个全名包括类所在的包的名字。例如MyObject类位于com.mypkg.myapp包,那么他的全名就是com.mypkg.myapp.MyObject。
如果在调用Class.forName()方法时,没有在编译路径下(classpath)找到对应的类,那么将会抛出ClassNotFoundException。
类名
你可以从Class对象中获取两个版本的类名。
通过getName() 方法返回类的全限定类名(包含包名):
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
String className = aClass.getName();
如果你仅仅只是想获取类的名字(不包含包名),那么你可以使用getSimpleName()方法:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
String simpleClassName = aClass.getSimpleName();
修饰符
可以通过Class对象来访问一个类的修饰符,即public,private,static等等的关键字,你可以使用如下方法来获取类的修饰符:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
int modifiers = aClass.getModifiers();
修饰符都被包装成一个int类型的数字,这样每个修饰符都是一个位标识(flag bit),这个位标识可以设置和清除修饰符的类型。
可以使用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);
包信息
可以使用Class对象通过如下的方式获取包信息:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
Package package = aClass.getPackage();
通过Package对象你可以获取包的相关信息,比如包名。
父类
通过Class对象你可以访问类的父类,如下例:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
Class superclass = aClass.getSuperclass();
可以看到superclass对象其实就是一个Class类的实例,所以你可以继续在这个对象上进行反射操作。
实现的接口
可以通过如下方式获取指定类所实现的接口集合:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
Class[] interfaces = aClass.getInterfaces();
由于一个类可以实现多个接口,因此getInterfaces();方法返回一个Class数组,在Java中接口同样有对应的Class对象。
注意:getInterfaces()方法仅仅只返回当前类所实现的接口。当前类的父类如果实现了接口,这些接口是不会在返回的Class集合中的,尽管实际上当前类其实已经实现了父类接口。
构造器
你可以通过如下方式访问一个类的构造方法:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
Constructor[] constructors = aClass.getConstructors();
返回的Constructor数组包含每一个声明为公有的(Public)构造方法。
如果你知道你要访问的构造方法的方法参数类型,你可以用下面的方法获取指定的构造方法,这例子返回的构造方法的方法参数为String类型:
Class aClass = ...//获取Class对象
Constructor constructor = aClass.getConstructor(new Class[]{String.class});
如果没有指定的构造方法能满足匹配的方法参数则会抛出:NoSuchMethodException。
你可以通过如下方式获取指定构造方法的方法参数信息:
Constructor constructor = ... //获取Constructor对象
Class[] parameterTypes = constructor.getParameterTypes();
利用Constructor对象实例化一个类
Constructor constructor = MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject) constructor.newInstance("constructor-arg1");
constructor.newInstance()方法的方法参数是一个可变参数列表,但是当你调用构造方法的时候你必须提供精确的参数,即形参与实参必须一一对应。在这个例子中构造方法需要一个String类型的参数,那我们在调用newInstance方法的时候就必须传入一个String类型的参数。
方法
你可以通过如下方式访问一个类的所有方法:
Class aClass = ...//获取Class对象
Method[] methods = aClass.getMethods();
返回的Method对象数组包含了指定类中声明为公有的(public)的所有变量集合。
如果你知道你要调用方法的具体参数类型,你就可以直接通过参数类型来获取指定的方法,下面这个例子中返回方法对象名称是“doSomething”,他的方法参数是String类型:
Class aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", new Class[]{String.class});
如果根据给定的方法名称以及参数类型无法匹配到相应的方法,则会抛出NoSuchMethodException。
如果你想要获取的方法没有参数,那么在调用getMethod()方法时第二个参数传入null即可,就像这样:
Class aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", null);
方法参数以及返回类型
你可以获取指定方法的方法参数是哪些:
Method method = ... //获取Class对象
Class[] parameterTypes = method.getParameterTypes();
你可以获取指定方法的返回类型:
Method method = ... //获取Class对象
Class returnType = method.getReturnType();
通过Method对象调用方法
你可以通过如下方式来调用一个方法:
Method method = MyObject.class.getMethod("doSomething", String.class); //获取一个方法名为doSomesthing,参数类型为String的方法
Object returnValue = method.invoke(null, "parameter-value1");
传入的null参数是你要调用方法的对象,如果是一个静态方法调用的话则可以用null代替指定对象作为invoke()的参数,在上面这个例子中,如果doSomething不是静态方法的话,你就要传入有效的MyObject实例而不是null。
Method.invoke(Object target, Object … parameters)方法的第二个参数是一个可变参数列表,但是你必须要传入与你要调用方法的形参一一对应的实参。就像上个例子那样,方法需要String类型的参数,那我们必须要传入一个字符串。
访问一个私有方法你需要调用 Class.getDeclaredMethod(String name, Class[] parameterTypes)或者Class.getDeclaredMethods() 方法。
变量
你可以通过如下方式访问一个类的成员变量:
Field[] fields = aClass.getFields();
返回的Field对象数组包含了指定类中声明为公有的(public)的所有变量集合。
如果你知道你要访问的变量名称,你可以通过如下的方式获取指定的变量:
Class aClass = MyObject.class
Field field = aClass.getField("someField");
在调用getField()方法时,如果根据给定的方法参数没有找到对应的变量,那么就会抛出NoSuchFieldException。
一旦你获取了Field实例,你可以通过调用Field.getName()方法获取他的变量名称,如下例:
Field field = ... //获取Field对象
String fieldName = field.getName();
变量类型
你可以通过调用Field.getType()方法来获取一个变量的类型(如String, int等等)
Field field = aClass.getField("someField");
Object fieldType = field.getType();
获取或设置(get/set)变量值
一旦你获得了一个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);
传入Field.get()/Field.set()方法的参数objetInstance应该是拥有指定变量的类的实例。在上述的例子中传入的参数是MyObject类的实例,是因为someField是MyObject类的实例。
如果变量是静态变量的话(public static)那么在调用Field.get()/Field.set()方法的时候传入null做为参数而不用传递拥有该变量的类的实例。(如果传入拥有该变量的类的实例也可以得到相同的结果)
要想获取私有变量你可以调用Class.getDeclaredField(String name)方法或者Class.getDeclaredFields()方法, 但若要读取私有变量的值, 需要打开访问: field.setAccessible(true);
注解
你可以通过如下方式访问一个类的注解:
Annotation[] annotations = aClass.getAnnotations();
注解是Java 5的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用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) 表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。
类注解
你可以在运行期访问类,方法或者变量的注解信息,下是一个访问类注解的例子:
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());
}
方法注解
下面是一个方法注解的例子:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public void doSomething(){}
}
你可以像这样访问方法注解:
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());
}
}
你可以像这样访问指定的方法注解:
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());
}
参数注解
方法参数也可以添加注解,就像下面这样:
public class TheClass {
public static void doSomethingElse(@MyAnnotation(name="aName", value="aValue") String parameter){
}
}
你可以通过Method对象来访问方法参数注解:
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()方法返回一个注解类型的二维数组,每一个方法的参数包含一个注解数组。
变量注解
下面是一个变量注解的例子:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public String myField = null;
}
你可以像这样来访问变量的注解:
Field field = ... //获取方法对象
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 = ...//获取方法对象
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());
}
Getters and Setters
使用Java反射你可以在运行期检查一个方法的信息以及在运行期调用这个方法,使用这个功能同样可以获取指定类的getters和setters,你不能直接寻找getters和setters,你需要检查一个类所有的方法来判断哪个方法是getters和setters。
首先让我们来规定一下getters和setters的特性:
Getter方法的名字以get开头,没有方法参数,返回一个值。
Setter方法的名字以set开头,有一个方法参数。
setters方法有可能会有返回值也有可能没有,一些Setter方法返回void,一些用来设置值,有一些对象的setter方法在方法链中被调用(这类的setter方法必须要有返回值),因此你不应该妄自假设setter方法的返回值,一切应该视情况而定。
下面是一个获取getter方法和setter方法的例子:
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;
}