类加载机制和反射

Posted xxMYxx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类加载机制和反射相关的知识,希望对你有一定的参考价值。

第18章 类加载机制和反射

java类加载器处理根类加载器之外,其他都是用java写的


18.1 类的加载、连接和初始化

系统可能在第一次使用某一个类时加载这个类,也可能采用预加载机制来加载某一个类

18.1.1 JVM和类

当调用java命令运行某一个java程序时,该命令就会启动一个jvm进程,

不管该类有多复杂,该程序启动多少个线程,他们都是处于JVM进程里

下面情况jvm进程将被终止

1. 程序运行到最后正常结束

2. 程序运行中使用了System.exit() 或者Runtime.getRuntime().exit()代码结束程序

3. 程序执行过程中遇到未捕获的异常或者错误

4. 程序所在平台强制结束jvm进程

 

18.1.2 类的加载

当程序主动使用某一个类时,如果程序还未加载到内存中,则系统会通过加载,连接,初始化三个步骤来对该类进行初始化,这三个步骤就被称为类的加载或者初始化

类加载是指将类的class文件读入内存,并为之创建一个java.lang.Class对象,当程序使用任何类时,系统都会建立一个java.lang.Class对象

 

类的加载由类的加载器完成,加载器通常由JVM提供,这些类加载器也是程序运行的基础,JVM提供的这些类加载器被称为类加载器,除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器

类加载器通常无需等到首次使用才加载,jvm规范允许系统预先加载某些类

18.1.3 类的连接

当类的加载之类,系统会为之生成一个Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中,类的连接又可分为3个阶段

1. 验证 : 验证阶段用于检验被加载类是否有正确的内部结构,并和类协调一致

2. 准备 : 类准备阶段负责为类的类变量分配内存,并设置默认值

3. 解析 : 将类的二进制数据中的符号引用替换成直接引用

18.1.4 类的初始化

在类的初始化阶段,jvm负责对类进行初始化,主要就是对类变量直接进行初始化,

java类中对类变量指定初始值的方式有两种

1. 声明类变量时指定初始值

2. 使用静态初始化块为类变量指定初始值

JVM初始化一个类包括如下几个步骤

1. 假如这个类还没有被加载和连接,则呈现先加载并连接该类

2. 假如这个类的直接父类还没哟初始化,则先初始化其直接父类

3. 假如类中有初始化语句,则系统依次执行这些初始化语句

当执行2 步骤时,对该直接父类也执行上面1~3步骤

18.1.5 类初始化的时机

java程序首次通过如下6种方式来使用某一个类或者接口时,系统就会初始化该了或者接口

1.创建类的实例(使用 new来创建, 通过反射来创建实例,通过反射系列化来创建实例)

2.调用某一个类的类方法(静态方法)

3.访问某一个类或者接口的类变量或者为该类变量赋值

4.使用反射方式来强制创建某一个类或者接口对于的java.lang.Class对象

5.初始化某个类的子类

6.直接使用java.exe命令来运行某个主类。

 

对于final型 类变量,如果该类变量在编译期值就确定下来了,那么这个类变量相当于“宏变量”,java编译器会在编译时就直接把这个类变量出现的地方替换成他的值,即使使用g该静态变量也不会导致类初始化

 

18.2 类的加载器

类的加载器负责将.class文件加载入内存中,并为之生成对应的java.lang.Class对象。

18.2.1 类加载器简介

类的加载器负责将.class文件加载入内存中,并为之生成对应的java.lang.Class对象。一旦一个类别载入JVM中,同一个类就不会被再次加载了

java中一个类用其全限定类名(包名和类名)作为标识

但在jvm中用一个类的全限定名和其类加载器作为唯一标识

 

jvm启动时,回形成由三个类加载器组成的初始化类加载器层次结构

1.Bootstra ClassLoader :根类加载器 它负责加载java核心类

2.Extension ClassLoader  :扩张类加载器 负责加载JRE扩展目录

3.System ClassLoader  :系统加载器 它负责在jvm启动时加载来自java命令的classpath选项

18.2.2 类的加载机制

jvm类的加载机制主要有如下的三种

1. 全盘负责 当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用其他类加载器来加载

2. 父类委托 先让父类加载器视图加载该类Class,只有父类无法加载该类时,才尝试从自己类路径中加载

3. 缓存机制 保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载会先从缓存中搜寻该Class

类加载器之间的父子关系不是继承上的父子关系

用户类加载器 → 系统类加载器 → 扩展类加载器 → 根类加载器

 

类加载器加载Class大致要经过8个步骤

1. 检测此Class是否加载入过 ,如果有直接到第8

2. 如果服了加载器不存在(要么父类时根类加载器,要么自己是各类加载器,跳第4 步)

3. 请求使用父类加载器加载器去载入目标类,成功跳第8 失败跳第5

4. 请求使用根类加载器来加载目标类 成功跳第8 失败跳第 7

5. 当前类加载器尝试寻找Class文件 找到执行第6 失败跳第7

6. 从文件中载入该Class 成功跳第8

7. 抛出文件没找到异常

8. 返回对应的java.lang.Class对象

 

18.2.3 创建并使用自定义的类加载器

jvm除根类加载器之外的所有类加载器都是ClassLoader子类的实例,可以继承ClassLoader来实现自定义的类加载器

ClassLoader类有如下两个关键方法

1. loadClassString nameboolean resolve

2. findClass(String name)

通常从写findClass()

LoadClass()方法执行步骤如下

1.用findLoadedClassString)来检查是否加载过该类,如果有直接返回

2.在啊父类加载器上调用loadClass(),父类加载器为空,直接使用根类加载器来加载

3.调用findClassString)方法查找类

 

18.3 通过反射查看类信息

程序在运行时接收到外部传入的一个对象,该对象编译时类型是Object,但程序又需要调用该对象运行时类型的方法

为了解决这些问题,程序需要在运行时,发现对象和类的真实信息,解决问题有如下两种方法

1. 第一种做法是假设在编译时发现对象和了的真实信息,可以先使用instanceof运算符,在利用强制类型转换会玩为器运行时类型就可以了

2. 程序只能靠运行时信息来发现对象和类的真实性,这就必须使用反射了

18.3.1 获得Class对象

每个类被加载后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类,在java程序中获得Class对象通常有如下三种方法

1.使用Class类的forNameString clazzName)静态方法,该方法需要传入字符串参数,该字符串参数的值时某一个类的全限定类名

2.调用某一个类的class属性来获取该类对应的Class对象

3.调用某一个对象的getClass()方法 该方法是Object类里的一个方法

 

第一种方法和第二种方法都是直接通过类来获取Class对象,相比之下第二种有两种如下优势

1. 代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在

2. 程序性能更好,这种方法无需调用方法

18.3.2 Class中获取信息

Class类提供了大量的实例方法来获取该Class对象对应的详细信息

上面的多个getMethod()方法和getConstructor()方法中,都需要传入福多个类型为Class<?>这是因为 需要通过方法名和形参列表来确认,

 

18.3.3 java 8 新增的方法参数反射

java 8 java.lang.reflect包下新增了一个Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor Method 两个子类

 

18.4 使用反射生成并操作对象

Class对象可以获得类里的方法、构造器、成员变量、这三个类都是位于java.lang.reflect包下,并实现了java.lang.reflect.Member接口 程序可以通过Method对象在执行对应的方法

18.4.1 创建对象

通过反射生成对象有如下两种方法

1. 使用Class对象的newInstance()方法来创建该Class对象类的实例(要求改类有默认构造器)执行newInstance()方法时实际上是厉害默认构造器

先使用Class对象获取指定的Constructor对象,在调用Constructor对象的对象的newInstance()方法来创建该Class对应的实例

18.4.2 调用方法

当获得某一个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()或的所有方法或者指定方法,

每一个Method对象对应一个方法,获得Method对象后,程序就可以通过Method来调用它对应的方法

 

18.4.3  访问成员变量的值

通过Class对象的getField()或者getFields()可以获取类所包含的全部成员变量或指定成员变量

Filed提供了如下两组方法来读取或设置成员变量值

1. getXxx(Object obj ) 获取obj对象的该成员变量的值,此处的Xxx对应8种基本类型,如果是引用类型去掉Xxx

2. SetXxx(Object obj ,Xxx val) obj成员变量设置为val

 

18.4.4 操作数组

java .lang.reflect包下还提供了Array类,Array对象可以代表所有的数组,程序可以通过用Array来动态的创建数据,操作数据元素等

 

18.5 使用反射生成JDK动态代理

java.lang.reflect包下提供了Proxy类和InvovationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象

18.5.1 使用Proxy类和InvovationHandler接口创建动态代理

Proxy提供了创建动态代理类和代理对象的静态方法,它也是所用动态代理类的父类,如果爱在程序中一个或多个接口动态的生成类,就可以使用Proxy来创建动态代理类,如果需要为一个或多个动态接口动态创建实例,也可以使用Proxy来创建动态的实例

 

系统每生成一个代理类,都会有一个与之关联的InvocationHandler对象


以上是关于类加载机制和反射的主要内容,如果未能解决你的问题,请参考以下文章

Java反射与类加载过程会擦出什么样的火花

java8--类加载机制与反射(java疯狂讲义3复习笔记)

类的加载器和反射

类加载机制和反射

java 类加载机制和反射机制

Java核心技术梳理-类加载机制与反射