本文是给小白的Java EE生存指南的第6篇, 讲点稍微有深度的:反射。
这里不定义什么叫反射,先来看个例子,假设我给你一个Java 类:
package com.example;
public class HelloWorld {
public HelloWorld(){
}
public void sayHello(){
System.out.println("hello world!");
}
}
现在要求:
(1) 你不能使用 HelloWorld hw = new HelloWorld() , 但是要构建一个HelloWorld的实例来.
(2) 调用sayHello() 方法, 但是不能直接用 HelloWorld实例的 hw.sayHello()方法 , 说起来怪拗口的 :-)
用Java的反射功能, 可以很轻松的完成上面的要求:
//第一步, 先把HelloWorld的类装载进来
Class cls = Class.forName("com.example.HelloWorld");
//第二步, 创建一个HelloWorld的实例, 注意, 这里并没有用强制转型把obj转成HelloWorld
Object obj = cls.newInstance();
//第三步, 得到这个类的方法, 注意, 一个类的方法也是对象啊
Method m = cls.getDeclaredMethod("sayHello");
//第四部, 方法调用, 输出"hello world"
m.invoke(obj);
可能有人要问了, 为什么不直接new 出来呢? 通过反射来创建对象,调用方法多费劲啊 ?
这是个好问题,关键点就是: 很多时候我们并不能事先知道要new 什么对象, 相反,我们可能只知道一个类的名称和方法名, 很多时候这些名称都是写在XML配置当中的。
为了更好的说明问题, 来看看几个SSH的例子:
【Struts的例子】
1. 在XML配置文件中定义Action
<action name="HelloWorld" class="example.HelloWorld">
<result>/hello.jsp</result>
</action>
2. 定义Java 类
public class HelloWorld extends ExampleSupport {
public String execute() throws Exception {
......
return SUCCESS;
}
.......
}
Struts 框架的作者事先肯定不知道你会配置一个HelloWorld的Action 。
不过他可以这么做, Struts 在启动以后,解析你配置XML配置文件, 发现名称为HelloWorld的Action, 找到相对于的类名example.HelloWorld, 然后就可以通过反射去实例化这个类。 等到有人调用这个action 的时候, 可以通过反射来调用HelloWorld的execute() 方法。
【Hibernate的例子】
1. 定义Java类和表之间映射, 类名叫Event, 对应的表名是EVENTS 。
<hibernate-mapping package="org.hibernate.tutorial.hbm">
<class name="Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="increment"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
</class>
</hibernate-mapping>
2. 定义Event 类,如下所示:
public class Event {
private Long id;
private String title;
private Date date;
...... 为了节省篇幅, 每个属性的getter /setter 方法略...
}
3. 查询, 你可以用Hibernate 这么查询表中的数据了:
List result = session.createQuery( "from Event" ).list();
for ( Event event : (List<Event>) result ) {
System.out.println( "Event (" + event.getDate() + ") : " + event.getTitle() );
}
Struts 的作者事先也不知道你会配置一个叫Event的类。
不过他会这么处理: 类名(Event)-> 数据库表名(EVENTS) -> 发出SELECT查询表数据 -> 通过反射创建Event的实例 -> 通过反射调用实例的setter方法把数据库的值设置进去
【Spring的例子】
1. 配置一个Bean
<beanid="helloWorld"class="example.HelloWorld">
<propertyname="message"value="Hello World!"/>
</bean>
2. 写一个Java 文件
public class HelloWorld
{
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("My Message : "+ message);
}
}
3. 调用
ApplicationContext context =newClassPathXmlApplicationContext("Beans.xml");
HelloWorld hw=(HelloWorld) context.getBean("helloWorld");
hw.getMessage();
我都懒得解释了, 无非是根据类的名称通过反射创建一个类HelloWorld的实例, 然后再通过反射调用setMessage方法, 这样当你getMessage就有值了。
所以反射是很重要的, 在Java EE世界里, 反射最大的用途就是支持以声明式的方法(在XML中)来描述应用的行为, 是Struts, Hibernate , Spring 的最核心的技术之一。
简单的来讲, 反射能让你在运行时而不是编程时做下面的事情:
(1) 获取一个类的内部结构信息(或者成为元数据), 包括包名,类名, 类所有的方法,
(2) 运行时对一个Java对象进行操作, 包括创建这个类的实例, 设置一个属性的值, 调用这个类的方法等等。
这篇文章只是介绍了反射的一点皮毛和用途, 具体的细节还是等待你自己去发掘吧。
【元编程】
等等,还有一个小问题:为什么叫反射呢?
我想可能是Java程序在运行时能够看到自己的结构和行为吧, 就像看到镜子当中的自己一样, 反射了出来 。
如果扩展一点, 这种用代码来生成代码的方式, 其实叫做“元编程”。
C语言就不具备这样的能力, 经过编译以后, C语言中的struct 名称 , 数组名等信息都已经消失了, 基本上就是指针了。 你可以这么试一试:写个程序,在运行时打印一下一个struct的名称, 看看能不能实现。
但是像其他一些语言, 例如Ruby , 程序在运行时不但能检视自己, 还能动态的修改自己, 比如:给自己加上一个方法, 这种开放的能力给Ruby 编程来了巨大的飞跃, LISP的元编程能力更加强悍, 仅仅使用LISP自己就能定义一个新语言出来。
利用这种能力, 人们可以针对某个领域编写领域特定语言(Domain specific Language, 简称DSL), 然后使用DSL这个语言来进行应用编程, 那效率可不是一般的高, 像Ruby on Rail 不就号称开发速度是Java的10倍嘛!
DSL是另外一个主题了, 你要是感兴趣的话请回复 "想听DSL" , 回复的人多了, 我可以专门开一个主题讲讲 :-)
-------------------------------------------------------------------------------------------------------------------------------
QQ群:135769418 每周日听工作15年的IBM架构师定期分享编程和职场的经验教训。
关注"码农翻身" 微信公共号, 更多精彩文章。