java初学者一枚,入行刚几个月,文中观点仅代表个人理解,不足之处请多指教。
反射在java中是一个非常重要的概念,经常会听到说“反射是框架的灵魂”这句话,但是对于初学者来说仅仅是会使用框架,框架底层的实现原理是不太清楚的,本人也是作为一个初学者,目前仅停留在会使用框架的层面,但是深刻理解了反射机制可以让我们在使用框架的时候能够大概清楚它内部是怎样操作的,而具体的实现方式则需要去阅读源码了。总而言之反射是一个很重要的概念也许对于目前阶段的我们来说并没有感受到它的重要性,但是随着学习的不断深入,就会深刻体会到这一点。
理解Java反射机制的第一步是理解Class类,java中万物皆是对象,对象是由类实例化出来的,其实类也是对象,java中的类就是Class类的对象(可以把Class类理解成一个最顶层的概念,所有的类都是它的对象),这张图可以较为形象的表达这一概念:
知道了由Class类的存在,那么Class类又有什么作用呢,Class类的作用在于:只要得到了Class类就可以知道类的内部所有的成员变量和成员方法并且可以调用它们。要使用反射就必须得到相应的Class类。那么知道类内部所有的成员变量与成员方法又意味着什么呢,直接new出类的一个对象不是也同样可以知道类内部的成员变量与成员方法吗?本人一开始在理解反射机制时也在这个问题上纠结了很久,经过了很长的过程才逐渐明白其中的奥妙,下面是本人对于这个问题的理解,请耐心往下看。
java中还有两个常常听到的概念叫做“运行时”和“编译时”,对应相关的有“编译时创建对象”和“运行时创建对象”,那么什么是运行时什么是编译时呢,如果我们一开始就是使用Eclipse这样的工具来编写Java程序,可能对于“编译时”不够了解,java在真正执行输出结果之前有一个编译过程,java代码可以直接使用记事本等工具来编辑,然后通过“javac”命令进行编译,然后执行输出结果,至于“javac”命令以及编译过程相关的只是又是另外一个知识点了,在此不做过多讲述,可自行搜索相关资料查阅。我们只需知道java程序在运行之前有一个编译的过程(经过编译过程之后会在项目下生成“类名.class”文件,叫做字节码文件),这个编译的过程会将你程序内部所有可能用到的对象进行编译,如果它发现你new出对象的这个类不存在(未定义)就会报错,那在eclipse等工具中直接进行编写代码时编辑器就会帮你报错,这是因为这种高级的编辑器会帮你执行编译这个过程,也会对代码进行检查,但是如果是用记事本进行编辑的,记事本是不会报错的因为它没有检查编译这个功能。我们常用的直接new对象的方式( Student stu=new Student() )就是在编译时期就会创建对象,而若Student类还未定义则程序不能通过编译,也就无法进行后续的操作,这种方式就存在弊端。在一个团队中如果很多人一起协作开发,如果张三开发的功能中需要用到李四定义的类,但是李四工作比较多这个类还没有进行定义,那么张三就无法用这个类,那怎么办呢,就一直等吗,李四没有定义那个类,张三就不工作了吗?智慧的程序员们自然是不会让自己陷入这样的尴尬境地。于是就引出了“运行时创建对象”这个概念
“运行时”是在程序通过了编译之后的阶段,“运行时创建对象”可以理解为绕过编译在调用对象变量或者方法的时候才去创建这个对象。反射其实可以通俗的理解为(当然这是个人理解没有相关官方文档的支撑哈),绕过编译时期的检查在程序运行的时候创建类,这样的解释还是有一些晦涩难懂,举个栗子:张三李四协作开发程序,张三要用Student类,Student类由李四进行定义开发,但是李四很忙还没有定义好,张三不能直接用new的方式来创建对象(因为编译会报错,eclipse也会报错),没有对象自然也不能调用其中的成员变量与成员方法,那怎么办呢,张三就通过“反射”来处理这件事,使程序绕过编译,这样张三就可以调用类内部的成员变量与成员方法,并且进行后续的程序开发,不会耽误工作。java中还有两个概念与此对应叫做“动态加载”和“静态加载”,动态加载就是运行时加载创建对象(反射),静态加载就是在编译时期加载创建对象(直接new)。
通过上一段非常冗长的解释(只是将自己的理解尽可能详尽的解释了一通,对本人这样的初学者可能会有更好的理解对于中高级的大神可能没什么用哈),我们可以这样理解要使用反射就必须要获取Class类,Class类的获取方式有三种,以Student为例子:
第一种:
Student s1=new Student();
Class c1=s1.getClass();//这种方式并无意义,因为已经new出了Student,证明Student类通过了编译,直接可以用s1来调用Student类中的成员变量与成员方法,不用再getClass(),因此此方法并不实用;
第二种:
Class c1=Student.class;//这种方法也无意义,因为Student类没有定义,这样编写也不能通过编译;
第三种:
Class c1=Class.forName("com.luo.demo.Student");//括号中是Student类的全路径,这样的方式是推荐的,首先它可以通过程序的编译,其次它只需要知道类的名字了,这样就在程序运行的过程中动态创建了Student类的对象,并且可以直接使用c1来调用Student类的方法和变量;
第三种方式是推荐的方式,由此我们得到了一个类(Student类)的Class类,不管Student类有没有进行定义,我们都可以调用它的成员,并且可以通过编译(当然在运行时如果这个类还没有定义的话也是会报错的);
反射的精髓在于只需要知道类的名字,就可以知道这个类内部的所有信息(包括private私有的也可以得到),不仅是知道其中的信息并且还可以调用使用。通过Class c1=Class.forName("com.luo.demo.Student");这样的方式得到了Class类,就可以利用Java反射包中自带的一些方法获取并调用类的成员变量与成员方法。常用的方法有newInstance(),invoke()等等,可以自行搜索哦。
框架中使用反射无处不在,在此以ORM(对象关系映射)框架为例讲述一下为什么我们只创建了一个实体类比如Student,在项目中使用的时候为什么数据库返回的结果能够映射成Student对象返回给前端。
在ORM框架中O就是对象,R是关系(当下流行的关系型数据库) ,M是映射就是一一对应,一一对应就是对象的属性与数据表的字段一一对应。数据库在返回查询结果时通过反射调用了对象的Getter/Setter方法从而,返回给前端的就是一个有数据的对象。
Class类很重要,在理解java底层的一些基本概念及原理中很关键,比如类加载、泛型的本质等,而它也是理解反射机制中非常重要的一环,可以多阅读相关的博客技术文档来进一步理解它。本人比较喜欢专研原理一类的东西,这样可能有时候好有时候不好,有的会认为对于技术人员来说死抠原理概念并没有什么用,但是知其然也要知其所以然,我觉得并不算一件坏事。理解了反射的基本内涵之后在今后使用框架的时候多多思考其中的原理,就可以更深一步理解其中的含义,本人也尚在学习过程中,对于一个流行的技术框架来说光理解反射肯定也是不够的,学无止境,终身学习。本人是java初学者,文中定有不恰当的理解,非常希望阅读到本文的读者在发现这些错误之后能够告诉我帮助我。