您的位置:首页 > 编程语言 > Java开发

有关Java反射机制的作用及用法浅析

2017-09-08 01:04 357 查看
本文欢迎转载,转载请注明出处,谢谢! 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();


如果其中业务有变动,那就要同时改十个地方。当时对反射还不是很懂,所以就很笨拙的写了十套差不多的代码。后来想用反射重写一下,一直也没有时间和机会。再后来就不在那个项目组了……(也不知道那个项目现在如何了,若是谁维护时发现了我那段青涩的代码,怕是要骂死我吧)

本文记录了最近一段时间学习反射的一些理解,研究还不够透彻,文笔粗略,各位看官请多包涵。

如何理解Java反射?

查阅了很多关于反射的资料,发现大多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反射机制的浅析,文笔粗略,请多包涵。如果有理解有误的地方,欢迎指正!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 反射 反射机制