类加载和反射
Posted 专治八阿哥的孟老师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类加载和反射相关的知识,希望对你有一定的参考价值。
1. 类的加载和初始化
在程序用到一个类的时候,如果类还没有加载到内存,JVM会对类进行加载、连接和初始化。
类加载由类加载器完成,类加载器通常由JVM提供(系统类加载器)。开发者也可以通过继承ClassLoader创建自己的类加载器。
类加载器加载类的二进制数据,可以从以下几个地方获得:
- 从本地文件系统加载class文件
- 从JAR包加载class文件
- 网络加载class文件
- 动态编译Java源文件实现加载。
类被加载后,系统会为类生成一个对应的Class对象,通过类连接把类的二进制数据合并到JRE中,称为类连接。类连接有三个阶段: - 验证:检验被加载的类是否有正确的内部结构
- 准备:为类的静态属性分配内存,设置默认初始值
- 解析:将二进制数据中的符号引用替换成直接引用
虚拟机对类进行初始化,主要是对静态属性进行初始化。声明静态属性的初始值,可在声明静态属性时指定初始值,也可以使用静态初始块为静态属性指定初始值。
public class ClassTest
static int a = 1;// 声明的时候执行初始值
static int b;
static // 静态代码块,第一次加载类的时候执行
System.out.println("静态代码块");
System.out.println(b);// 输出默认值0
b = 2;
System.out.println(b);// 输出2
//每次new对象都会执行
System.out.println("实例代码块");
测试代码:
public static void main(String[] args)
ClassTest test1 = new ClassTest();
ClassTest test2 = new ClassTest();
输出结果:
JVM初始化一个类的时候,步骤如下:
- 如果类没被加载和连接,程序先加载并连接该类。
- 如果该类的直接父类没有被初始化,则先初始化直接父类
- 如果类中有初始化语句,则系统依次执行这些初始化语句
JAVA中通过以下6中方法,可以触发一个类的初始化:
a) 创建类的实例。new一个对象、通过反序列化或反射来创建类的实例
b) 调用某个类的静态方法
c) 访问某个类或接口的静态属性,或为静态属性赋值
d) 使用反射方式强制创建某个类的java.lang.Class对象
e) 初始化某个类的子类,该子类的所有父类都会被初始化
f) 用java.exe运行某个主类
另外,对于一个final修饰的静态属性,如果该属性在编译时就能得到属性值,访问该属性时不会触发类的初始化。如:
public class ClassTest
final static int a = 1;// 声明的时候执行初始值
public static void main(String[] args)
System.out.println(ClassTest.a);//不会触发ClassTest初始化
2. 类加载器
类加载器负责将.class文件加载到内存中,并生成对应的java.lang.Class对象。同一个类只能被加载到JVM中一次。在Java中,用类的全限定名(包名+类名)作为标识,区分是不是同一个类,即同一个包下不能有重名的类。JVM在加载类的时候,用类的全限定名+加载器的名字作为唯一标识。即同一个类可以被不同的类加载器加载,每个类加载器只能加载一次。
JVM启动时,有三个类加载器:
- Bootstrap ClassLoader:根类加载器,负责加载Java的核心类,它不是ClassLoader的子类,而是由JVM自身实现的。核心类在jre/lib下
- Extension ClassLoader:扩展类加载器,负责加载JRE扩展目录(jre/lib/ext)中的JAR包
- System ClassLoader:系统类加载器,负责在JVM启动时,加载来自java中的-classpath选项或java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。
JVM加载类主要有如下三种机制: - 全盘负责:当一个类加载器负责加载某个Class的时候,该Class所依赖的和引用的其它Class将由该类加载器负责载入。除非显示使用另外一个类加载器来载入。
- 父类委托:先让parent类加载器加载该Class,只有当parent加载器无法加载该类时才尝试从自己的类路径中加载该类。
- 缓存机制:缓存机制将会保证所有被加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存中搜寻该Class,只有当缓存中不存在时,系统才会重新读取该类的二进制数据,并将其转换为Class对象。因此修改了Class后必须重新启动JVM。
public static void main(String[] args) throws IOException
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemLoader);
// 获取系统类加载器的加载路径,通常是CLASSPATH环境变量指定。
// 如果没有指定CLASSPATH,默认是当前路径为系统类加载器的加载路径
Enumeration<URL> eml = systemLoader.getResources("");
while (eml.hasMoreElements())
System.out.println(eml.nextElement());
ClassLoader parentLoader = systemLoader.getParent();
System.out.println("扩展类加载器:"+parentLoader);
System.out.println("扩展类加载器的加载路径:"+System.getProperty("java.ext.dirs"));
System.out.println("扩展类加载器的parent:"+parentLoader.getParent());
3. 反射
3.1 获得Class对象
每个类被加载后,都会为该类生成一个对应的Class对象,通过该Class对象可以访问JVM中的这个类。Java中获得Class对象有三种方式:
- 使用Class.forName(“类全限定名”)
- 调用某个类的class属性。如Person.class
- 调用某个对象的getClass()方法。
通过Class对象可以访问对象的构造器、方法、属性。
3.2 使用反射操作对象
Class类中有可以访问类的Constructor(构造方法)、Methods(方法)、Field(属性)
方法名中带Declared的,表示类中声明的成员,无关访问修饰符。
package classtest;
public class ClassTest
private String name;
public ClassTest()
System.out.println("ClassTest无参构造");
public ClassTest(String name)
this.name = name;
System.out.println("ClassTest有参构造");
public void info()
System.out.println("info()");
public void info(String in)
System.out.println("info(String in)" + in);
private void pri()
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException
Class clazz =Class.forName("classtest.ClassTest");
Constructor[] constructors = clazz.getDeclaredConstructors();// 获取所有构造器
for (Constructor cons : constructors)
System.out.println(cons);
Method[] methods = clazz.getMethods();// 获取类中所有public方法
for (Method m : methods)
System.out.println(m);
Method info = clazz.getMethod("info", String.class);// 获取info(String)这个方法
Field[] fields = clazz.getDeclaredFields();//获取所有声明的属性,与访问权限无关
for(Field f:fields)
System.out.println(f);
类加载机制和反射
第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对象
以上是关于类加载和反射的主要内容,如果未能解决你的问题,请参考以下文章