反射

76 阅读13分钟

反射

复习

1、数据传递结构 --- 场景:在系统与系统之间进行数据的传递。传递的数据要考虑:格式(要能够表现丰富的数据结构),简洁(文本本身要简洁--起到节约信道的作用;书写和解析要简洁--方便开发人员的构建和读取)。

2、XML--可扩展标记性语言

2-1、可扩展--允许我们自定义标记、属性、标记的嵌套关系、文本内容等。

2-2、标记性语言---只有4个概念:标记(标签)、属性、文本和注释。 标记: <标记名>

书写:有开始标记<标记>就要有结束标记</标记>,除非是单标记<标记/>。

属性: 属性名="属性值"

属性:属性书写的位置是在标记的<>当中,跟在标记名后面,多个属性之间用空格分隔,“”不是表示这个值是字符串,而是所有的属性值都要做这个标识。

文本:写在标记开始和结束符号之间的文本内容。

注释:

3、XML以后大家会大量的使用,用来做配置。我们需要掌握它的几个可能在操作和面试当中遇到的知识点:

3-1、合法的XML

除了要有正确的标记、属性、文本和注释的写法以外,还有三个要点:

a、有开始标记必须要有结束标记,除非是单标记;

b、一篇XML能且只能有一个根标记

c、标记与标记之间要有正确的嵌套关系---包含了别人的开始,就必须包含别人的结束

3-2、有效的XML

一篇XML,它当中的内容必须符合“文档类型定义”。 这个定义相当于就是这篇XML的说明书,说明了它有哪些标记、属性、嵌套关系等等,构造XML的和解析XML的都要遵循它。

构造“文档类型定义”的两种手段:DTD和Schema。

3-3、合法的XML与有效的XML之间的关系

3-4、解析XML的手段

DOM解析:它是把整篇XML读到内存中,然后形成一颗文档树。树上的每一个节点(Node) 都是XML中的一个元素。最上面是根标记,然后是它的子标记和它的属性作为下一层,子标记的子标记和属性又作为再下一层。

效果:它可以读一次,然后在内存中进行反复的遍历(循着这颗树的路径-XPath);当然它也更耗内存,所以适用于小型的XML

SAX解析:它是把整篇XML文档从头到尾读取一次,然后采用时间触发的方式来操作读到的元素。

效果:只能从头到尾读一次,过了就不能回头。不耗内存,适用于大型的XML。

4、在实操过程中最常用的数据交换手段是"JSON"。JSON -- JavaScript Object Notation -- JS对象简谱。

5、JSON和Javascript是什么关系?

JSON是一种数据交换结构,本质上它是一个字符串。只是它的内容结构是和JS对象的字面量表现形式是一样的,所以这种交换结构的字符串命名为“JSON”。

6、JSON有两种结构:

6-1、数组结构:[元素1,元素2,元素3]

6-2、对象结构:{"属性名1":属性值1,"属性名2":属性值2,......}

当然两种结构可以相互嵌套;

7、FastJson的作用,它是阿里巴巴设计的一个专门用于在Java当中适用的JSON工具。

它的核心用途就2个:把一个java对象转成正确格式的JSON格式字符串;把一个正确格式的JSON字符串转成Java对象。

8、与FastJson同样功能的转换工具很多,只是目前FastJson是最快的。由于它是阿里巴巴开发的,所以它不在JDK的内库当中,需要我们在项目工程当中额外导入。

8-1、在这个导入的过程中,我们使用到了Maven工具。这个家伙就是一个帮助我们从各个公司的提供的下载处把我们要用的jar包导入到我们本地。

8-2、我们导入的都是jar包,所谓jar包,就是别人写好的Java类文件(class文件)打的一个压缩包。

9、FastJson常用API的掌握

9-1、java对象与Json的转换

9-2、List集合对象与Json的转换

9-3、Map集合对象与Json的转换

反射(reflect)

反射在Java中的地位非常高,有“Java灵魂”的称呼。作为一个初级程序员,也许并没有机会直接操作它,但不是因为它不常用,而是没有资格去操作它。它常常是在我们后面要用到的很多框架当中的底层,除非你是框架的开发设计人员,否则你确实很少有机会去玩儿它。

反射:在运行时探究和使用编译时未知的类

很明显在这句话中有两个时:运行时、编译时。根据我们的开发流程,都是先书写一个类,然后在代码里面产生它的对象,调用它的方法或属性,然后编译!编译结束以后,再运行,就能看到相应的效果---所以这是一个先编译后运行的流程。

但是反射这句话把整个过程给颠倒了,我们在是运行起来以后,才知道我们要产生哪个类的对象,调用它的哪个方法或属性。

虽然我们到目前为止写代码的时候没有遇到这种情况,但是我们用人家的程序的时候天天都在看到这种情况的发生。比如:IDEA这个软件就是用Java写的,它在运行起来以后可以探究到我们自己在它运行之后才编译好的Student类里面有哪些属性和行为,然后让我们通过联想的方式可以看到。

这就是Java语言提供的动态性效果。这里的动态性指的是“运行”起来以后,对应的“静态性”不是static而是编译后运行前。

编程语言分为:动态语言和静态语言。动态语言可以在程序运行起来之后,给它的类增加或修改或减少属性和行为。当然,Java作为静态语言做不到。 Java虽然不是动态语言,但是它具有一定的动态性。这个动态性就体现在“反射”上,虽然它没有办法在运行期给类增加/修改/删除成员内容,但是它可以探究和使用,而这就是反射提供的效果。

我希望大家在学完本章内容以后做到三点:

1、知道什么是“运行时探究和使用编译时未知的类”;是一个什么效果;

2、掌握Java反射的基本API;

3、在以后使用框架的时候,遇到这种效果,能够知道人家底层使用的是“反射”,并且能够根据反射的知识去学习和使用这些框架。

Java中的反射相关知识点

在我们前面学习当中,我们已经知道了Java的工作流程:

1、先编写Java源文件;

2、然后编译Java原文件为class文件;

3、运行,而运行又分为3步:

3-1、加载

3-2、校验

3-3、解释执行

今天要讲的“反射”在这几个过程中和“加载”的关系很密切,所以我们要再深入“加载”来聊一聊。

1、加载谁?

2、谁加载?

3、加载成什么?

第一个问题很好回答,加载的是class文件。细节在于:一个Java类就会编译成一篇class文件,那么加载class文件就是在加载这个类,所以这个过程也叫做“类加载”;

第二个问题:在JVM当中,提供了一种叫做“类加载器”的东西,来完成加载这个动作。“类加载器”也叫做ClassLoader,它是JDK当中已经写好了的,提供给JVM用来做加载动作的。

Java在这里还专门设计了一个叫做“双亲委托模型”的东西。Java的类加载提供了三种加载器,如果再加上我们自定义的,那就是4种:BootstrapClassLoader、ExtClassLoader、ApplicationClassLoad、最后自定义的。

在加载的过程中:

BootstrapClassLoader负责加载的是JDK中的那些最基本常用的类;

ExtClassLoader负责加载的是jre/lib/ext这个包里面的常用类;

ApplicationClassLoader是加载我们在应用层面自己写的类。

加载的方式是:向上询问,向下负责。

第三个问题:class文件是一篇字节码文件,那么到了内存当中,它是什么形式呢?那么,这是今天的关键了,它被加载成了一个对象。这个对象的类型是Class类型的,所以也被叫做Class对象。

Class对象是专门用来装一个类的内容信息的。一个类只需要一个Class对象。所以,在这句代码中其实产生了两个对象:

Student stu = new Student();

1、new出来的,交给stu去指向的,是Student的“实例对象”;表示一个学生对象,里面是该学生的数据和它可以执行的行为;

2、但是实例对象是运行期产生的,而在它之前需要先加载Student这个类,那么就会产生一个Class对象,在它当中装的是Student类的内容信息,包括:这个类的名字、父类是谁、接口有哪些、放在哪个包、有什么属性、构造、行为.....所以这个Class对象又被叫做“类模版对象”。

3、类模版对象和实例对象的关系

3-1、必须先有模版对象,然后才能有实例对象;

3-2、模版对象只需要1个,实例对象可以根据要求产生无数个。

3-3、理论上,模版对象是JVM使用的,用来记载这个类的信息;而实例对象是我们的应用程序用的,用来装载数据和具体调用方法完成功能实现的。

反射在做什么事情呢?反射其实就是通过获取这个类的Class对象,我们就能够知道这个类的信息(探究),拿到这些信息以后,比如:拿到它的构造方法的信息,我们就可以产生实例对象了;拿到它的属性信息,我们就可以给属性进行赋值取值了;这就是所谓的“使用”。当然前提条件是获取它的Class对象。

反射的基本操作步骤:

  1. 获取一个类的Class对象;

  2. 通过Class对象完成一个类的信息的探究。主要是探究3个东西:构造、属性和行为;

  3. 探究到以后,就可以使用这些信息了。探究到构造就可以产生实例对象;探究到属性就可以对属性赋值取值;探究到方法就可以调用。

获取一个Class对象的反射API

方式1、类型名.class

注意

所有的类型(包括void)都有Class对象;

基本数据类型可以通过自己.class,也能通过自己的包装类.TYPE 获取到该基本类型的Class对象;

不管用哪种方式(包括后面的方式二和方式三)获取Class对象,同一类型的只有一个Class对象。

方式2、实例对象.getClass()

注意

getClass()来自于Object,所以只有Objet的子类才可以通过这种方式获取;接口、基本数据类型、void都不能通过这种方式获取它们的Class对象。

方式3、Class.forName(类型的字符串名称);

注意

这种方式是需要做异常处理的,因为有可能这个字符串对应的类型找不到,所以forName方法抛出了一个编译时异常ClassNotFoundException。 这种方式只能获取类、接口、数组的Class对象,不支持基本数据类型和void。

其中数组的字符串类名有古怪:

引用数据类型数组--"[L元素的类型名";

基本数据类型数组--除了long,多是"[类型名的首字母大写"(比如"[I",“[D”,"[B"),只有两个例外:"[J"是long[],“[Z”是boolean[]。

最后,我们需要对比一下这三种方式:

方式1 是可以获取所有的Java类型;

方式2 只能获取Object的子类类型;

方式3 可以获取所有的引用数据类型。

除了方式3,其他两种方式都没有“动态性”。参看chapter16中的TestClass代码。

探究一个Class对象的反射API

探究构造方法

getConstructors() -- 获取所有的公共构造

getDeclaredConstructors() -- 获取所有声明的构造

这两个方法都是返回的Constructor[]。

getConstructor(参数的class对象) -- 获取指定的公共构造

getDeclaredConstructor(参数的class对象) -- 获取指定的声明构造

探究属性

getFields() -- 获取所有的公共属性

getDeclaredFields() -- 获取所有声明的属性

getField(属性名) -- 获取某个指定的公共属性

getDeclaredField(属性名) -- 获取某个指定的申明属性

探究方法

getMethods()

getDeclaredMethods()

getMethod(方法名,参数的class对象)

getDeclaredMethod(方法名,参数的class对象)

总结一下

这里的探究告诉了我们,在一个类的Class对象里面封装好了这个类的所有信息(只要你在类的代码里面声明的东西,都可以获取到)。

当然对我们最有意义的,最常用的,就是获取构造、属性和方法的那3组12个get。不要死记,其实就是4个单词和单复数的区别。

使用探究到的信息的反射API

探究到构造,就要产生实例对象

Constructor对象有一个叫做newInstance的方法,通过这个方法可以产生实例对象。

注意

1、获取到的Constructor对象的参数要和newInstance方法中传递的参数匹配!!!--- 这个其实就是形参和实参的匹配关系。

2、由于我们通过getDeclaredConstructor是可以找到这个类所有的构造方法,但是调用newInstance的时候还是要看该构造的访问修饰符。如果调用处与定义的时候规定的访问权限不匹配,仍然不能产生对象成功。会报:IllegalAccessException。

探究到属性,就要给这个属性赋值取值

Field对象身上有一个get()/set()的方法,可以给这个属性进行取值/赋值。当然仍然有前提条件,要遵守这个属性定义的访问修饰符。

探究到方法,就要调用这个方法

Method对象身上有一个invoke()的方法,可以调用这个method。当然仍然有前提条件,要遵守这个方法定义的访问修饰符。

总结一下

Constructor的newInstance、Method的invoke、Field的get/set。在操作的时候要注意以下几点:

1、调用处的位置要和目标定义的访问修饰符保持一致;

2、Method和Field的操作要注意和实例对象的绑定关系。

\