类加载机制和反射
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. loadClass(String name, boolean resolve)
2. findClass(String name)
通常从写findClass()
LoadClass()方法执行步骤如下
1.用findLoadedClass(String)来检查是否加载过该类,如果有直接返回
2.在啊父类加载器上调用loadClass(),父类加载器为空,直接使用根类加载器来加载
3.调用findClass(String)方法查找类
18.3 通过反射查看类信息
程序在运行时接收到外部传入的一个对象,该对象编译时类型是Object,但程序又需要调用该对象运行时类型的方法
为了解决这些问题,程序需要在运行时,发现对象和类的真实信息,解决问题有如下两种方法
1. 第一种做法是假设在编译时发现对象和了的真实信息,可以先使用instanceof运算符,在利用强制类型转换会玩为器运行时类型就可以了
2. 程序只能靠运行时信息来发现对象和类的真实性,这就必须使用反射了
18.3.1 获得Class对象
每个类被加载后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类,在java程序中获得Class对象通常有如下三种方法
1.使用Class类的forName(String 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对象
以上是关于类加载机制和反射的主要内容,如果未能解决你的问题,请参考以下文章