疯狂Java讲义笔记-第18章 类的加载机制与反射

第18章 类的加载机制与反射

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

18.1.1 JVM和类

以下情况,JVM进程将被终止

  • 程序运行到最后正常结束
  • 程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序。
  • 程序遇到未捕获的异常或错误而结束。
  • 程序所在平台强制结束了JVM进程。
18.1.2 类的加载
  • 类的加载指的是将类的class文件读入内存,并为之创建java.lang.Class对象。
  • 程序使用类时,系统都会建立与之对应的java.lang.Class对象。
  • 类的加载由类加载器完成,通常由JVM提供,可继承ClassLoader来继承。
18.1.3 类的连接

连接分为三个阶段:验证、准备和解析。

  • 验证:验证阶段用于检验被加载的类是否有正确的结构,并与其它类协调一致。
  • 准备:为类的静态Field分配内存,并设置默认初始值
  • 解析:将类的二进制数据中的符号引用改为直接引用。
18.1.4 类的初始化

初始化阶段主要对静态Field进行初始化赋初值。两种方式:

  • 声明静态Field时指定初始值
  • 使用静态初始化块为静态Field指定初始值

初始化类包含步骤:

  • 加载并连接类
  • 先初始化直接父类
  • 依次执行初始化语句
18.1.4 类初始化时机

程序遇到以下6种情况时初始化类或接口:

  • 创建类的实例:使用new 或 反射来实例化
  • 调用类的静态方法
  • 访问静态Field或为静态Field赋值
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。即:创建java.lang.Class对象时会初始化其对应的类的Field。
  • 初始化某个类的子类时会初始化其父类。
  • 直接使用java.exe命令来运行某个主类,会初始化该主类。

final 修饰的静态Field的值(感觉像是基本类型的值)如果在编译期就能确定下来,那么Java编译器将直接把这个Field出现的地方用值来替换。相当于"宏变量"。

18.2 类加载器

18.2.1 类加载器简介

  • 对象有唯一的标识
  • Java中类有唯一的标识:包+类名
  • JVM中一旦一个类被载入JVM中,同一个类不会再被载入了(可能不一定,可能使用别的类加载器就可以实现类被多次使用时多次被载入),所以也要标识类:一个类用全限定类名和其类加载器作为唯一标识。如:pg包中有Person类,使用类加载器ClassLoader实例为k1则该Person类对应的Class对象在JVM中标识为(Person、pg、k1)。

因此同一个类的java.lang.Class对象使用(Person、pg、k1)形式来标识,(Person、pg、k1)和(Person、pg、k2)也标识不同Class对象。
JVM启动时,使用3个类加载器来加载类:

  • Bootstrap ClassLoader:根类加载器,不是java.lang.ClassLoader的子类而是由JVM自身实现。加载Java核心类。加载jre及jre/lib中的jar包。
  • Extension ClassLoader: 扩展类加载器,加载JRE扩展目录中的jar包。
  • System ClassLoader: 系统扩展加载器,加载-classpath选项、java.class.path系统属性或CLASSPATH环境变量所指定的JAR包和类路径。

18.2.2 类加载机制

  • 全盘委托:类加载器加载某个类时,该Class所依赖的和引用的类其他Class也将由该类加载器进行加载。
  • 父类委托:先让父加载器进行加载,如果不能加载,则尝试从自己的类路径进行加载。
  • 缓存机制:将会保证所有加载过的Class都被缓存,当程序需要使用某个Class时,类加载器会从缓存区中搜索该Class,只有缓存区不存在该对象时,系统才会读取对应的二进制数据,将其转换为Class对象,存入缓存区。这就是修改Class后,必须重启JVM才能生效的原因。

类加载器加载Class8个步骤:

  • 请查书

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

通过扩展ClassLoader的子类,并重写ClassLoader的方法来实现。以下为两个关键方法:

  • loadClass(String name, boolean resolve)
  • findClass(String name)

推荐使用findClass()方法而不是重写loadClass()方法。loadClass()方法执行过程如下:

  • 用findLoadedClass(String)来检查是否已经加载类,如果已经加载则直接返回。
  • 在父类加载器上调用loadClass()方法。如果父类加载器为null,则使用根类加载器来加载。
  • 调用findClass(String)方法查找类。

重写findClass()方法可以避免覆盖默认类加载器的父类委托、缓冲机制两种策略;如果重写loadClass()方法,则实现逻辑更加复杂。
ClassLoader中其他方法请查看相关API。

18.2.4 URLClassLoader类

请查看相关API

18.3 通过反射查看类信息

Java程序有两种类型:编译时类型和运行时类型。为了找出运行时对象和类的具体信息有两种做法:

  • 使用instanceof
  • 使用反射

18.3.1 获得Class对象

Java程序中获取Class对象通常有如下3种方式:

  • 调用Class类的forName(String clazzName)静态方法
  • 调用某个类的class属性,如:Person类的Person.class方法。该方法代码更安全:程序在编译阶段就可以检查访问的Class对象是否存在。性能更好:不能调用方法所以更好。通常使用该方法。
  • 调用对象的getClass方法。

18.3.2 从Class中获取信息

通过Class对象可以获取对应类的构造方法、所有Field、所有方法和所有类的注解。请查阅相关API。

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

18.4.1 创建对象

  • 使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方法要求Class对象的对应类有默认构造器,执行newInstance()方法会调用默认构造器来创建该类。
  • 使用Class对象获取Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。

为了使用指定的构造器来创建Java对象,需要如下三个步骤:

  • 获取Class对象
  • 利用Class对象的getConstructor()方法来获取指定的构造器
  • 调用Constrctor的newInstance()来创建Java对象

18.4.2 调用方法

获取Class对象后,调用getMethods()或getMethod()方法来获取全部方法或指定方法–这两个方法的返回值是Method数组,或者Method对象。
每个Method对象对应一个方法,获得Method对象后,程序就可以通过该Method来调用它对应的方法。通过调用invoke()方法:

  • Object invoke(Object obj, Object… args): obj为调用方法的对象,args为参数。

18.4.3 访问属性值

有getFields()和getField()方法,类似调用方法,具体去看书。

18.4.4 操作数组

类似操作查看Array类API

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

使用Proxy类和InvocationHandler接口
部分内容略,具体查看书。
动态代理相关知识查看“动态代理详解”(先占坑)。

18.6 反射和泛型

Class对应的类暂时未知,使用Class<?>表示。通过反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换。

18.6.1 使用反射来获取泛型信息

使用Class泛型可以避免使用强制类型转换。

18.6.2 使用反射来获取泛型信息

通过Field对象可以获取相关泛型信息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值