天天看点

【Java】反射

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

一直说反射反射,但是为什么要反射却不甚理解。“明明直接 <code>new</code> 一个对象就可以,为什么还需要使用反射呢?”

反射库

反射就是围绕着 <code>Class</code> 对象和 <code>java.lang.reflect</code> 类库的,就是各种的 <code>API</code> 调用。

涉及的类:

作用

Class

描述类的信息

Constructor

构造器类

Method

方法类

Field

字段类

涉及的方法:

方法名

getXXX()

获取公有的构造器、方法、属性

getDeclaredXXX()

获取所有的构造器、方法、属性

掌握以下几种差不多就入门了:

知道获取 <code>Class</code> 对象的三种途径;

通过 <code>Class</code> 对象创建出对象,获取到构造器、方法、属性;

通过反射的 <code>API</code> 修改属性的值、调用方法。

<code>Class</code> 对象

我们肯定都碰到过类型强转失败(<code>ClassCastException</code>)的情况,那么为什么编译时能通过,运行时却报错呢?<code>JVM</code> 是怎么知道类型不匹配的呢?实际上它是通过 <code>Class</code> 对象来判断的。

<code>.java</code> 文件经过 <code>javac</code> 命令编译成 <code>.class</code> 文件,当我们执行了初始化操作( <code>new</code>、类初始化时父类也一同被初始化、反射等)后,会由类加载器(双亲委派模型)将 <code>.class</code> 文件内容加载到方法区中,并在 <code>Java</code> 堆中创建一个 <code>java.lang.Class</code> 类的对象,这个 <code>Class</code> 对象代表着类相关的信息。

既然说,<code>Class</code> 对象代表着类相关的信息,那说明只要类有什么东西(构造器、方法、属性),在 <code>Class</code> 对象里都能找得到,可以在 <code>IDEA</code> 里面查看 <code>java.lang.Class</code> 类包含哪些方法和属性。

获取 <code>Class</code> 对象的三种途径:

通过控制台打印可知:在运行期间,一个类,只有一个 <code>Class</code> 对象产生。

三种方式的区别:

第一种,对象都有了还要反射干什么;

第二种,需要导入类所在的包,依赖太强,不导包就编译错误;

第三种,类全限定名字符串可以作为参数传入,也可从配置文件(常用)中读取。

示例

<code>JDBC</code> 的代码(硬编码):

后来为什么要改成下面的形式呢:

理由很简单,我们不想修改代码,把需要变动的内容写进配置文件,不香吗?但凡有一天,我们的 <code>username</code>,<code>password</code>,<code>url</code> 甚至是数据库都改了,我们都能够通过修改配置的方式去实现。不需要动丝毫的代码,改下配置就完事了,这就能提供程序的灵活性。修改代码的风险和代价比修改配置大,即使不知道代码的实现,都能通过修改配置来完成要做的事。

像这种通过修改配置文件来进行动态响应的,其内部很可能就是通过反射来做的。

<code>Servlet</code> 开发是这样获取页面参数的,一堆的 <code>getParameter()</code> 模板代码:

而 <code>Spring MVC</code> 是这样获取页面参数的:

为什么我们写上 <code>JavaBean</code>,保持字段名与参数名相同,就能 “自动” 得到对应的值呢,其实就是通过反射来做的。

现有系统:

<code>Student</code> 类:

配置文件 <code>Student.properties</code>:

测试类:

需求:

当我们升级这个系统,需要 <code>main()</code> 方法打印其他内容时,不需要修改 <code>Student</code> 类,新写一个 <code>Student2</code> 的类,并将 <code>Student.properties</code> 文件的内容修改一下就可以了。既存代码就一点不用改动,这也符合对扩展开放、对修改关闭的开闭原则。

要替换的 <code>Student2</code> 类:

配置文件更改为:

泛型用在编译期,编译过后泛型擦除(可以通过 <code>ParameterizedType</code> 获取泛型类型)成 <code>Object</code> 或上限类型(父类类型)。所以是可以通过反射越过泛型检查的,一般不会这样使用,除非自己需要一些特殊的操作。

为什么要使用反射?

通过上面几个示例,我们可以知道为什么要使用反射了:

提高程序的灵活性,并符合开闭原则,如 <code>JDBC</code>、通过反射运行配置文件内容;

屏蔽掉实现的细节,更方便使用,如 <code>Spring MVC</code> 中页面参数与 <code>JavaBean</code> 的映射。

我们写业务代码是用不到反射的,自己去写组件才会用到反射。

当然,如果自定义注解的话,那么是需要用到反射对注解做相应处理的。

参考:

java 的反射到底是有什么用处?怎么用? - Java3y

Java 基础之 — 反射(非常重要)