写作原因:Java反射注解这一块一直是笔者的盲区,但是Java系开发者都知道这一块的重要性。以熟悉的Android开发为例,通过利用反射注解大神们创造了可以减少大量重复赘余代码和清晰逻辑结构的依赖注入框架。本文也是为日后的依赖注入剖析做预备。由于水平限制,一些地方可能存在疏漏,望指出。
本章先从反射开始吧。java是一门面向对象的语言,java中有一句话概括了这个特点:万物皆对象。平常我们创建的对象是通过某个类产生的,那么类本身呢?不是说万物皆对象吗?还有基本对象和静态成员变量呢?于是java中便引入定义:类本身也是Class类的实例对象。Class普通类是Class类的对象。一切操作都将使用Object完成,类、数组的引用都可以使用Object进行接收。这就真正实现了万物皆对象的思想。
定义
那么一个类作为一个对象这个怎么定义呢?有如下三种方法:
Person person = new Person();
Class c1 = Person.class;
//这个实例化说明每个类都有一个静态的成员变量Class;
class c2 = person.getClass();
//通过某个类的对象来获得这个对象的类类型;
Class c3 = Class.forName("类的具体名");
//通过类的具体名称(包括包名)来获得;
注意上面c1、c2、c3是相等的,他们都是指向Person这个Class类实例化的对象的。现在我们有了类这个对象(好绕口,其实我们现在要做的就是把所有都看成对象,包括类),我们能做什么呢?我们可以通过这个类对象来调用Class类的方法来实现一些功能。具体有哪些功能呢?
实现动态加载类的功能
由于反射是在编译之后进行的,所以可以通过反射来绕过编译期加载类,实现运行时动态加载类的效果。这样有什么好处呢?通过动态加载类,我们可以完成功能模块的更新而不需重新编译,在需要某个模块时可以动态添加。具体操作我们通过一个Demo来呈现。下面展示一下这个Demo,比如我们要写一个QQ,除了聊天功能(固定,这里我们使用静态加载作比较),我们还提供各种其他功能供用户选择安装使用,比如QQMusic和QQHealth,用户根据需要安装使用。如果使用静态加载类,要实现以上功能我们只能一次性全部把功能写到QQ这个程序里面,而且就算写进去了日后要升级还是得重新再源代码里面添加加载类,这样及其不合理。这时使用反射动态加载就十分必要了。下面我们写一下这个例子,该例子共有5个类,分别为QQ.java、QQChat.java、QQHealth.java、QQMusic.java、FuncAble.java:
public class QQ {
public static void main(String args[]){
QQChat chat = new QQChat();
chat.run();
//
System.out.println("Please input the application you want to open:");
Scanner s = new Scanner(System.in);
String name = s.next();
try {
Class health = Class.forName(name);
FuncAble fa = (FuncAble) health.newInstance();
fa.run();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
QQ.java中实现了静态加载聊天功能和动态加载其它功能的逻辑。我们根据用户输入的类名来加载类类型并实例化为对象然后调用对象的方法,注意我们使用了FuncAble这个接口。为什么要使用接口呢?这里利用了多态性,通过使用接口来兼容各个类对象的实例化。这样下次在增加新的类功能模块时就不用重新去写QQ类的内部逻辑了,只需实现FuncAble接口然后写我们需要的类的逻辑就行了。
获取类内部信息
大家一定很好奇IDE的只能提示是怎么实现的吧?没错,它们也是利用反射机制完成的。下面我展示一下利用反射来模拟IDE的智能提示功能。这里我们写了一个类名叫ClassInfo,用该类来获取类的内部信息,详细方法见代码中的注释。
public class ClassInfo {
//获取方法信息
public static void getMethodInfo(Object obj){
Class c = obj.getClass();
//获取类类型
Method[] ms = c.getDeclaredMethods();
//获取方法对象数组
for(Method m : ms){
String modifierName = Modifier.toString(m.getModifiers());
//获取修饰符
String methodName = m.getName();
//获取方法名
Class[] paraTypes = m.getParameterTypes();
//获取形参类类型
String paraTypeName = "";
for(Class para : paraTypes){
paraTypeName= paraTypeName + " "+para.getSimpleName();
}
Class returnType = m.getReturnType();
String returnTypeName = returnType.getSimpleName();
//获取返回类型
//也可以使用getName(),不过会包含包名
System.out.println(modifierName +" "+returnTypeName+" "+methodName+"("+paraTypeName+")");
}
}
//获取成员信息
public static void getFieldInfo(Object obj){
Class c = obj.getClass();
Field[] fields = c.getDeclaredFields();
//获取成员对象数组
for(Field f : fields){
String fieldName = f.getName();
//获取成员名
Class fieldType = f.getType();
//获取成员类类型
int modifier = f.getModifiers();
//获取修饰符
String modifierName = Modifier.toString(modifier);
String fieldTypeName = fieldType.getSimpleName();
System.out.println(modifierName+" " + fieldTypeName + " "+fieldName);
}
}
//获取构造函数信息
public static void getConstructorInfo(Object obj){
Class c = obj.getClass();
Constructor[] constructors = c.getDeclaredConstructors();
//获取构造函数对象数组
for(Constructor constructor : constructors){
String constructorName = constructor.getName();
//获取构造函数名
String paraTypeName = "";
Class[] paraTypes = constructor.getParameterTypes();
//获取形参类类型
for(Class p : paraTypes){
paraTypeName = paraTypeName+" "+p.getSimpleName()+" ";
//获取形参类型名
}
String modifierName = Modifier.toString(c.getModifiers());
System.out.println(modifierName+" "+constructorName+"("+paraTypeName+")");
}
}
}
有了这个类,我们就可以根据某个类的实例对象查看该类的方法,成员及构造函数了。
通过反射操作类内部
这里讲述通过反射操作方法的例子,修改成员变量类似。还记得以前我剖析过鸿洋大神对SP的封装工具类SPUtils。他就是通过反射将SP.Editor里面的apply()方法取出然后调用实现将IO操作异步进行的工作(commit是同步的)。首先应该知道:方法名称和方法的参数列表唯一决定方法。所以我们可以通过类类型的getMethod(name,parameterTypes)
获得唯一方法,然后通过方法对象的invoke(对象,参数列表)
来实现方法间接操作对象从而调用方法。代码片段见下:
获取方法:名称参数列表
//getDeclaredMethod()
a1 = c.getMethod(name,parameterTypes)
parameterTypes = new Class[]{int.class,int class};//int.class,int.class
Object o = a1.invoke(对象,new Object[]{10,20})//方法操作对象(或者对象,10,20)
方法如果没有返回值返回null,有返回值返回具体的值
总结
最后回答一下前面那个问题:基本数据类型跟void关键字也是类类型,数组也一样。就是说它们也可以是对象,如int.class等。博主写这篇文章真是几经周折,先是eclipse崩溃,然后使用Atom写文章,文章写完后Atom挂掉了……又重写了一遍。不说了,都是泪。