最近在学习j2ee和设计模式两门课时同时遇到这个技术,按道理讲是挺基础的一个概念,但是在以前的编程中没有应用到,所以也没有注意,现在设计模式刚刚入门,发现反射技术真的是非常的好用。
首先看一个实体类
public class Admin {
// Field
private int id = 13416123;
private String name = "xujingfeng";
// Constructor
public Admin(){
System.out.println("Admin.Admin()");
}
public Admin(String name){
System.out.println("Admin.Admin()" + name);
}
// Method
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
其实一个类说的详细点主要是由以下几个部分组成:Class = Field + Constructor + Method (这里说主要由这几个组成,其实还可以有注解Annotation之类生僻的概念,不做讨论了)
Field 就是常说的 属性+属性值,这里其实可以这样理解,属性主要是针对类的,属性值主要是针对对象的。为了方便解释反射,直接在类中定义了默认初始值,让类也具备了属性值。
Constructor 包括了有参构造和无参构造...这些构造方法。
Method就是那些有参,无参,又返回,无返回的各类方法。
这样我们就讲类总结了,主要由这些部分组成,看完组成,我还没有提到反射呢,不急。
想想我们平时主要是怎么使用类的?是不是拿到一个类,然后new一个对象,就像这样:
Admin admin = new Admin();
再总结一下,我们面向对象编程,把所有东西抽象成类,但是实际使用的大部分是对象,也就是 类 -> 对象。(当然,还有静态类的概念,不要纠结...我是为了讲出我想讲的所以这么讲的)
这个时候我说,在项目有中有些时候你不能这么新建对象!为什么呢?这样也可以得到admin对象啊?问题就在于
这样把对象生成的方式写死了,如果我现在要new的是一个Student对象,你就必须要修改源代码
Student student = new Student();
你可能会想,拜托,这不就是一句话改一下吗,有必要使用一个新的技术吗?再说了这是两个完全不同的对象,需求肯定也不一样啊,改就改了啊!
先解决第一个疑惑,没错,这的确只是生成了一个对象,改起来比较简单,但是,如果我项目中有1000个这样类似需要修改的地方,你都要一个个找出来手动改掉?显然,我们程序需要一个通用的方法,来新建对象,这样只需要修改一个地方,大家就都改了。
再来解决第二个疑惑,其实项目设计中,大多会讲究设计模式的一些原则,里式替换原则等等,所以你程序的正确写法可能变成这样。
Object admin = new Admin();
好像不就是多态吗?那是面向对象的特性叫多态,但是这么写的好处,需要学习设计模式才能体会,不在今天的讨论范围。也就是说,左边是固定的,右边是可变的,这个时候,王晖大大的教诲就会萦绕在我们耳边:面向对象和设计模式同时需要我们做一个事,那就是把可变的东西东西封装起来。
怎么封装?那就要用到反射了,讲到这儿终于回到正题了。
还是先看看,如果我们用反射,怎么新建一个对象吧。
public void test() throws Exception {
// 类全名
String className = "cczu.xu.Admin";
// 得到类字节码
Class clazz = Class.forName(className);
// 创建对象1: 默认构造函数简写
Admin admin = (Admin) clazz.newInstance();
}
这样通过反射就可以实现创建对象了,和之前直接new对象最本质的区别就是,现在的className是字符串,可以通过读取配置文件来获取,当然我这里为了省事是写死的,完全可以把字符串写在配置文件中,然后可以结合泛型,动态的创建配置文件中对象。
在这个过程中其实不难发现,Class.forName通过类全名获取类字节码,实际上我们干了一件和以往不同的事,那就是,我们由 类名 -> 类字节码 -> 对象 不一样要使用new关键字了。
知道对象也得到Class,也就是 对象 -> 类
Class clazz = admin.getClass();
Class clazz = admin.Class;
所以,反射机制解决一个问题就是,动态生成对象,说的通俗一点就是说,现在我们可以在配置文件里面写上类的全名,然后用反射机制,封装成方法,这样就可以直接生成对象了。通过上面的例子,还可以发现打破我们以往有类得到对象习惯,最起码让你知道了,由对象还可以得到类。
反射还有其他作用,我们获取到类之后,往往不仅仅需要他的实例,可能还需要得到他内部的东西(内部东西,就是在最前面讲的那三个东西Field + Constructor + Method)
再简单说下另一个作用吧,其实还有其他作用的。
// 反射技术
public class App {
// 1. 创建对象
@Test
public void testInfo() throws Exception {
// 类全名
String className = "cczu.xu.Admin";
// 得到类字节码
Class<?> clazz = Class.forName(className);
// 创建对象1: 默认构造函数简写
//Admin admin = (Admin) clazz.newInstance();
// 创建对象2: 通过带参数构造器创建对象
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
Admin admin = (Admin) constructor.newInstance("Jack");
}
@Test
//2. 获取属性名称、值
public void testField() throws Exception {
// 类全名
String className = "cczu.xu.Admin";
// 得到类字节码
Class<?> clazz = Class.forName(className);
// 对象
Admin admin = (Admin) clazz.newInstance();
// 获取所有的属性名称
Field[] fs = clazz.getDeclaredFields();
// 遍历:输出每一个属性名称、值
for (Field f : fs) {
// 设置强制访问
f.setAccessible(true);
// 名称
String name = f.getName();
// 值
Object value = f.get(admin);
System.out.println(name + value);
}
}
@Test
//3. 反射获取方法
public void testMethod() throws Exception {
// 类全名
String className = "<span style="font-family: Arial, Helvetica, sans-serif;">cczu.xu.Admin</span><span style="font-family: Arial, Helvetica, sans-serif;">";</span>
// 得到类字节码
Class<?> clazz = Class.forName(className);
// 对象
Admin admin = (Admin) clazz.newInstance();
// 获取方法对象 public int getId() {
Method m = clazz.getDeclaredMethod("getId");
// 调用方法
Object r_value = m.invoke(admin);
System.out.println(r_value);
}
}
其中注意一个细节
clazz.getDeclaredXX()
拿到的是全部的方法,属性,如果是
clazz.getXX()
拿到的就是一些受限制的,例如getMethod()就只有公共方法才能被获取到
有了反射,我们就可以洞悉一个类了。至于反射的具体应用,我现在只在工厂模式,BeanUtils组件,重写BaseServlet,BaseDao这些地方用到,但至少可以明晰一点,在
大量的公用方法,工具类,以及框架的书写中,会大量使用到反射的。先记录这么多吧。