1 为何使用反射
案例1:
假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。于是提出一个办法:第一个程序员先编译运行自己的代码,等第二个程序员完成后把他的代码加载进来。但是这怎么实现呢?利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。
案例2:
当你做一个软件可以安装插件的功能,你连插件的类型名称都不知道,你怎么实例化这个对象呢?
因为程序是支持插件的(第三方的),在开发的时候并不知道 。所以,无法在代码中 New出来 ,但反射可以,通过反射,动态加载程序集,然后读出类,检查标记之后再实例化对象,就可以获得正确的类实例。
案例3:
在编码阶段不知道那个类名,要在运行期从配置文件读取类名, 这时候就没有办法硬编码
new ClassName(),而必须用到反射才能创建这个对象.
2 反射的原理
Java的内存模型:
现在编译代码 Student stu = new Student(),首先系统会生成 .class 文件放到类加载器中,类加载器会将 .class 文件进一步处理:根据这个类特有的信息生成一个专属的Class对象,把Class对象存放在堆区,把Student类的方法代码,变量名,方法名,访问权限,返回值等等存在方法区,(作为方法区这个类的各种数据的访问入口。)至此,一个Class对象就创建成功了。然后会给这个Student对象分配内存,也称为初始化,用代码解释new Student()。
从头来看,jvm创建一个对象之前,会检测类加载器中是否加载好这个类的Class对象,如果好了就会给这个Student对象分配内存,也称为初始化,用代码解释new
Student()。---------解释了为何一个Class对象创建成功了就会分配内存。
思考一:
.class文件到底是个什么东西?
java语言源文件为 xx.java
c语言源文件为 xx.c
c++语言源文件为 xx.cpp
这些不同语言的源文件通过不同的机制都可以翻译成二进制编码的文件,(转化为了特殊的数据结结构)可以使得文件体积更轻巧,被快速的加载到JVM内存中。
思考二:
类加载器是怎么处理 .class 文件的呢?
类的加载主要有三步:加载->连接->初始化。连接过程又分为 验证->准备->解析
虚拟机加载完成三件事情:通过一个类的全限定名来获取定义此类的二进制字节流; 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构; 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
3 反射的使用场景
我们什么时候使用反射呢?
例如实际开发中,我们有数据库mysql,oracle,我们的程序在不同的时刻需要加载不同的数据库驱动。笨的方法及时需要哪个数据库就手动修改调用数据库的驱动。这太麻烦了,如果有办法自动匹配对应的数据库就好了;
所以我们先封装好两个类,调用这个类中的驱动就好了。那么好了,我们现在开始运行程序,运行到一半,系统想要调用mysql的数据库驱动,但是封装数据库的驱动的类在哪里呢?而且我们要调用一个类,并不是直接调用的,是创建该类的对象,通过对象再调用。
梳理一下,最终是要创建一个类的对象,我类都不知道在哪怎么创建?-------这个时候可以通过全类名找到这个类,类找到了就创建对象,怎们创建对象上面说过了。
再再梳理一下,我们已经可以不用知道类名就创建了一个这个类的对象,进而使用类中的东西。
代码解释一下:(类所在的包==全类名)
封装两个驱动的类是这样的 MyqlConnection 和 OoracleConnection
在类TestConnection中调用:
Class tc = Class.forName("com.connection.TestConnection")
所有的概念写完了,我们需要mysql的数据库驱动,就 调用MysqlConnection;需要oracle的数据库驱动,就 调用OracleConnection(下面说怎么调)
当然这个案例只是为了解释反射,实际上有很多其他的办法可以解决多个数据库驱动问题。
最后回顾一下,我们是已经把程序运行起来之后,再找到需要的类,这就是反射的功劳。
4 反射的使用
4.1 先上代码演示
获取当前类的Class对象。 (通过forName()动态加载类)
实例化这个Class对象。 (通过newInstance )
获取当前类的某个(些)方法
用方法对象名.invoke ,通过Class对象的实例,调用带相应参数的,当前类的方法。
- 创建student类
- 获取 get方法 ,set方法
- 获取公有变量
- 获取成员变量
- 构造器
4.2 代码总结
(1)获取类的 Class 对象实例的三种方式
第一种,Class.forName 静态方法。
当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象最常用,必须掌握。
Class c1 = Class.forname("main.hw02.反射.Student");
第二种,类 .class方法。
这种方法只适合在编译前就知道操作的 Class,但是这种方法需要导入类的包,依赖性太强,所以用的比第一种稍微要少【重点】
Class c2 = Student.class;
第三种,类对象.getClass() 方法。
这种方法已经创建了对象,那么这个时候就不需要去进行反射了,显得有点多此一举。【不常用,了解即可】
Student stu = new Student();
Class c3 =stu.getClass();
(2)获取对象实例的两种办法
第一种、直接用字节码文件获取对应实例
调用无参构造器 ,若是没有,则会报异常
Student stu = clazz.newInstance();
第二种、有带参数的构造函数的类,先获取到其构造对象,再通过该构造方法类获取实例
获取构造函数类的对象
Constroctor constroctor = clazz.getConstructor(String.class,Integer.class);
/使用构造器对象的newInstance方法初始化对象
Object obj = constroctor.newInstance("龙哥", 29);