本文欢迎转载,转载请注明出处,谢谢! http://blog.csdn.net/colton_null 作者:喝酒不骑马 Colton_Null from CSDN
一直想要研究一下Java的反射机制。但是一直以来没有时间好好研究反射的来龙去脉。其实如果没有对于反射用法的需求,而强行想学习反射的话是很难理解它的意义的。
前一段在工作中,我碰到了一个惹人烦的功能。当时用的是Jfinal框架的Model类对数据库进行持久化操作。同样的界面,分10种情况,分别对应十个表。也就是说在Java中对应十个Model类。
假设每个Model类中都有一个queryInfo()这个方法,十种情况分别要调用十次queryInfo()这个方法。
比如:
Model1 model1 = new Model1();
model1 = model1.queryInfo();
Model2 model2 = new Model2();
model2 = model2.queryInfo();
...
Model10 model10 = new Model3();
model10 = model10.queryInfo();
如果其中业务有变动,那就要同时改十个地方。当时对反射还不是很懂,所以就很笨拙的写了十套差不多的代码。后来想用反射重写一下,一直也没有时间和机会。再后来就不在那个项目组了……(也不知道那个项目现在如何了,若是谁维护时发现了我那段青涩的代码,怕是要骂死我吧)
本文记录了最近一段时间学习反射的一些理解,研究还不够透彻,文笔粗略,各位看官请多包涵。
查阅了很多关于反射的资料,发现大多blog或者资源上都偏重于如何使用反射,但是对反射的意义描述却很少。大部分都停留在“反射允许我们在运行时发现和使用类的信息”层面上,这种概念如果硬生生理解的话很难琢磨透。
我的理解是,Java反射可以允许开发人员以字符串形式传入类名、方法名等,就可以生成该类或者调用某个方法。这样会使代码及其灵活,利于维护。
还是利用我上面说到的那个例子。如果通过用户每次进入不同类型的界面时,根据界面类别传入”Model1”、”Model2”等这样的字符串,那么我就可以直接用反射机制实例化该类并调用其中queryInfo()方法。这样,之前十套代码的工作量就可以浓缩到一套代码中完成了。如果要修改queryInfo相关业务,那么只需要修改一处即可。
或许上面的表述还是不好理解。这里我准备了一个例子,更加方便理解反射的用法和意义所在。
新建如下4个类,Animals.java,Dog.java, Cat.java, Monkey.java。其中Dog, Cat, Monkey类继承Animals。
Animals.java
public abstract class Animals {
//动物名字
public String name;
/**
* 叫一叫
*/
public abstract void makeNoise();
}
Cat.java
public class Cat extends Animals {
public Cat(){}
public Cat(String name){
this.name = name;
}
@Override
public void makeNoise() {
System.out.println(name + "可以 喵喵喵");
}
}
Dog.java
public class Dog extends Animals {
public Dog(){}
public Dog(String name){
this.name = name;
}
@Override
public void makeNoise() {
System.out.println(name + "可以 汪汪汪");
}
}
Monkey.java
public class Monkey extends Animals {
public Monkey(){}
public Monkey(String name){
this.name = name;
}
@Override
public void makeNoise() {
System.out.println(name + "可以 嗷嗷嗷");
}
}
再创建一个测试类TestDemo.java
public class TestDemo {
// 这里要填写类的全路径
private static String[] classNames = new String[]{"myz.com.myz_20170907.test1.Monkey",
"myz.com.myz_20170907.test1.Dog",
"myz.com.myz_20170907.test1.Cat"};
public static void main(String[] args) {
try {
// 通过遍历classNames数组,分别实例化3个类并执行makeNoise()方法
for (String className : classNames){
Class clazz = Class.forName(className);
Method method = clazz.getDeclaredMethod("makeNoise");
Constructor constructor = clazz.getDeclaredConstructor(new Class[]{String.class});
// 执行makeNoise方法
method.invoke(constructor.newInstance(new Object[]{className.split("\\.")[className.split("\\.").length - 1]}));// 按照"."分割字符串取最后一段
}
} catch (ClassNotFoundException e) {
System.out.println("生成Class失败!");
} catch (NoSuchMethodException e) {
System.out.println("无此方法");
}
catch (IllegalAccessException e) {
System.out.println("生成对象失败!IllegalAccessException");
} catch (InstantiationException e) {
System.out.println("生成对象失败!InstantiationException");
} catch (InvocationTargetException e) {
System.out.println("方法调用失败!");
}
}
}
这里,我通过遍历包含三个类名全路径的数组,动态的实例化三个类,并分别执行makeNoise()方法。最终得到的结果为
输出:
Monkey可以 嗷嗷嗷
Dog可以 汪汪汪
Cat可以 喵喵喵
也就是说,现在有三种动物,如果我再添加10种动物,只需将这十种动物的类名传入,就可以实例化相应的对象了。如果按照传统做法,分别需要new出来10个对象,再分别调用makeNoise方法。如果是100种动物呢?传统做法的弊端就显露出来了。而利用反射,我无需在编译的时候创建那么多对象,只需要传入相应的类名,就可以在运行时生成想要创建的对象。
这也就是对于反射概念中提到的“程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载”。用户选择哪个动物在编译时是完全不知道的,运行起来后用户才会有选择动物的操作。那么不论用户选择哪种动物,通过反射可以在程序运行时动态生成和使用相对应的对象,这就是反射的意义。
正是由于有了反射机制,才有的Spring、Mybatis等各种优秀的框架。Spring的核心IOC(控制反转)的底层实现跟反射机制是密不可分。最近博主也在研究Spring的原理,有时间的话也会分享一下学习Spring原理的心得。
这里还要简单提一下Annotation注解。注解也是和反射密不可分的。通过反射,可以获取到某个类、某个参数、属性、方法等上的注解的信息,这样可以根据注解信息进行相关的操作。可以理解为,注解即为一个标识,遇到什么标识就该做什么标识的事。
以上就是我对Java反射机制的浅析,文笔粗略,请多包涵。如果有理解有误的地方,欢迎指正!