码农翻身--Java系列

「转载」给小白的Java EE生存指南(6):Java 反射

2018-01-11  本文已影响362人  liwei_happyman

转载自 微信公众号 码农翻身 不用于商业宣传 版权归原作者所有 侵权删

本文是给小白的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> 
  1. 定义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>
  1. 定义Event 类,如下所示:
public class Event {
    private Long id;
    private String title;
    private Date date;
    ...... 为了节省篇幅, 每个属性的getter /setter 方法略...
}
  1. 查询, 你可以用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>
  1. 写一个Java 文件
public  class   HelloWorld
{
    private String message;
    public void setMessage(String message){
        this.message  = message;
    }
    public void getMessage(){
        System.out.println("My Message : "+ message);
    }
}
  1. 调用
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倍嘛!


“码农翻身” 公共号 : 由工作15年的前IBM架构师创建,分享编程和职场的经验教训。

长按二维码, 关注码农翻身

上一篇 下一篇

猜你喜欢

热点阅读