第18章:类加载机制与反射
程序员文章站
2023-12-21 13:19:40
...
18.1 类的加载、连接和初始化
使用类时,都会经历加载、连接、初始化三个步骤
18.1.1 jvm和类
- java命令运行某java程序,操作系统中会启动一个java虚拟机进程,无论该java程序多么复杂,该程序包含了多少线程,它们都处于java虚拟机进程里。同一个jvm的所有线程、所有变量都处于同一个进程中,它们都使用该jvm进程的内存区。
- jvm终止的情况
- 程序运行到最后正常结束
- System.exit()、Runtime.getRuntime().exit()
- 程序执行过程中遇到未捕获异常或错误
- 操作系统强制结束jvm进程(kill -9)
- jvm进程结束,该进程内存中状态丢失
18.1.2 类的加载
- 类的加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象,同一个类(全限定类名+加载器表示同一个类)只能被加载一次
- Class是类的抽象,可以通过这个Class对象获取到这个类中定义的内容。类是对某一类对象的抽象,Class是对类的抽象
- 类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的类加载器通常称为系统类加载器,开发者也可以通过继承ClassLoader基类创建自己的类加载器
18.1.3 类的连接
- 类的连接:将类的二进制数据合并到JRE中。又分为如下三个阶段
- 验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致
- 准备:为类的类变量分配内存,设置默认值
- 解析:将类的二进制数据中的符号引用替换成直接引用
18.1.4 类的初始化
- 类的初始化:就是对类变量赋值,之前都是默认值
- 类初始化之前必须先加载和连接
- 如果类的直接父类还没被初始化,先初始化其父类
- 最后使用类中初始化语句对类变量赋值
18.1.5 类初始化时机
18.1.5.1 类初始化时机
- 创建类实例时
- new
- 反射
- 序列化
- 调用某类静态方法、静态变量
- 使用反射创建该类对应的Class对象时:Class.forName(“Person”)
- 初始化子类
- java.exe运行主类
18.1.5.2 特殊情况
- 如果类的静态成员变量为宏变量,使用它时,不会初始化类,因为对JVM来讲他是个常量
- 调用类加载器的loadClass来加载类时,不会初始化。Class.forName时才初始化
18.2 类加载器
18.2.1 类加载器简介
- 一旦一个类被加载到JVM中,同一个类不会再次加载
- 什么叫同一个类:Java中用全限定类名表示唯一一个类,而JVM中,使用全限定类名+类加载器表示唯一一个类。例如pg包中有Person类,是用ClassLoader的示例k1加载的,那么Person对应的Class对象在内存中表示为(Person,pg,k1),它与(Person,pg,k2)是不同的
- JVM启动时,形成由三个类加载器组成的初始类加载器层次结构
- (根)类加载器
- (扩展)类加载器
- (系统)类加载器
- 根类加载器
//不是ClassLoader子类
//负责加载$JAVA_HOME/jre/lib、$JAVA_HOME/jre/lib/classes下的JAR包和类
//指定根加载器加载指定附加的类
-Xbootclasspath
-Dsun.boot.class.path
- 扩展类加载器
//负责加载JRE的扩展目录($JAVA_HOME/jre/lib/ext)或系统属性"java.ext.dirs"所指定的目录下的JAR包和类
- 系统类加载器
//加载-classpath、CLASSPATH环境变量、java.class.path系统属性所指定的JAR包和类
18.2.2 类加载机制
- 全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖和引用的其他Class也由该类加载器加载,除非显示用另外一个类加载器加载
- 父类委托:
- 概念:如果一个类加载器收到了类加载器的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求最终都会传送到根类加载器中.只有父类加载反馈自己无法加载这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
- 优点:java类随着它的加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jart之中.无论哪一个类加载器都要加载这个类,最终都是双亲委派模型最顶端的Bootstrap类加载器去加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型.由各个类加载器自行去加载的话,如果用户编写了一个称为“java.lang.Object”的类.并存放在程序的ClassPath中,那系统中将会出现多个不同的Object类。java类型体系中最基础的行为也就无法保证,应用程序也将会一片混乱.
- 缓存机制:所有被加载过的Class都被缓存,当需要使用某Class,先从缓存中搜索该Class,缓存区中不存在该Class对象,系统才读取该类对应的二进制文件。所以修改Class后,必须重启JVM才生效
18.2.3 类加载器之间的关系
public class ClassLoaderPropTest {
public static void main(String[] args) {
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
//getParent方法获取到的不是继承意义上的父加载器,而是加载类时,父类委托的加载机制所使用的父加载器
ClassLoader extensionLader = systemLoader.getParent();
ClassLoader rootLader = extensionLader.getParent();
//1. 系统类加载器:AppClassLoader
System.out.println(systemLoader);
//2. 扩展类加载器:ExtClassLoader
System.out.println(extensionLader);
//3. AppClassLoader、ExtClassLoader都继承了ClassLoader,AppClassLoader、ExtClassLoader本身没继承关系
//4. 扩展类加载器的父加载器是根类加载器,但根类加载器不是用java实现,所以返回null
System.out.println(rootLader);
}
}
18.2.4 创建并使用自定义的类加载器
- 使用自定义类加载器执行java程序
//CompileClassLoader为自定义的类加载器,实际上就是一个类,这个类有main方法, 需传入两个参数,一个为该类加载器要加载的类名A,以及A的main方法中的参数
//Hello为类A
//"疯狂java讲义"是为main方法中形参列表的字符串数组传值
java CompileClassLoader Hello "疯狂java讲义"
- 可以通过继承ClassLoader,并重写其内方法, 来自定义类加载器
//根据指定名称加载类,返回对应Class对象,里面调用了findClass方法
loadClass(String name,boolean resolve)
//根据指定名称来查找类
findClass(String name)
//创建自定义类加载器时一般重写findClass方法而不重写loadClass,避免覆盖默认类加载器的父类委托与缓冲机制两种策略
- 自定义类加载器通常可以实现如下功能
- 运行前先编译java源文件,从而无需编译,直接执行
- 执行代码前,自动验证数字签名
- 根据用户提供的代码解密代码,从而可以实现代码混淆器来避免反编译*.class文件
- 根据用户需求,动态加载类
- 根据用户需求,把其他数据以字节码形式加载到应用中
18.2.5 使用URLClassLoader从本地或远程主机加载类
package wusihan_0102_ClassLoader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.SQLException;
public class URLClassLoaderTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, SQLException,
InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException,
IllegalArgumentException, InvocationTargetException {
//这个urls相当于为该加载器配置classpath,路径一定要写最后一个/
//file前缀表示从本地文件系统加载类,如果为http前缀表示从互联网加载类
URL[] urls = { new URL("file:D:/workspace/wusihan_0100_DailyTest/bin/") };
//可以使用如下构造器,指定URLClassLoader的父加载器
//URLClassLoader ul = new URLClassLoader(urls,ClassLoader.getSystemClassLoader());
URLClassLoader ul = new URLClassLoader(urls);
//可以不用在classpath中添加该类所在路径就可以使用该类
Method m = ul.loadClass("com.wsh.object.Animal").getMethod("beat");
m.invoke(ul.loadClass("com.wsh.object.Animal").newInstance(), args);
}
}
18.3 通过反射查看类信息
Java程序中的对象运行时出现两种类型,一种是运行时类型,另一种是编译时类型。程序如果想调用该对象运行时类型的成员,有以下两种途径
- 编译时知道运行时类型:使用instanceof判断是否可以转换,再使用强制类型转换将变量转换为运行时类型对应的变量
- 编译时不知道运行时类型:使用反射
18.3.1 获取Class对象
//1.使用Class自身静态方法,传入的字符串必须是全限定类名
//这种方法适用于程序只能获取类名对应的字符串
Class a = Class.forName("com.wsh.object.Animal");
//2.使用某个类的class属性
//此种方法相对于第1种更安全(类不存在编译时会报错),效率更高(不用调用方法)
Class b = URLClassLoaderTest.class;
//3.使用某个对象的getClass()方法
Animal c = new Animal();
Class d = c.getClass();
18.3.2 Class
- 获取Class对应类的构造器
Constructor<T> getConstructor(Class<?> ... parameterTypes):返回此Class对象对应类的、带指定形参列表的public构造器
Constructor<?>[] getConstructors():返回此Class对象对应类的所有public构造器
Constructor<T> getDeclaredConstructor(Class<?> ... parameterTypes):返回此Class对象对应类的、带指定形参列表的与访问权限无关的构造器
Constructor<?>[] getDeclaredConstructors()
- 获取方法
Method getMethod(String name,Class<?> ... parameterTypes):name为方法名,parameterTypes为形参类型
Method[] getMethods()
Method getDeclaredMethod(String name,Class<?> ... parameterTypes)
Method[] getDeclaredMethods()
- 获取成员变量
Field getField(String name)
Field[] getFields()
Field getDeclaredField(String name)
Field[] getDeclaredFields()
- 获取Annotation
//有些Annotation信息只保留在源码级别上,而Class获取的是运行时信息,无法获取到这种Annotation
<A extends Annotation>A getAnnotation(Class<A annotationClass):获取Class对象对应类上存在的类型为annotationClass的Annotation,如果该类型的Annotation不存在,返回null
<A extends Annotation>A getDeclaredAnnotation(Class<A annotationClass)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()
- 获取内部类
Class<?>[] getDeclaredClasses()
- 获取外部类
Class<?> getDeclaringClass()
- 获取实现的接口
Class<?>[] getInterfaces()
- 获取继承的父类
Class<? super T> getSuperclass()
- 获取修饰符
int getModifiers():获取类或接口的所有修饰符,返回的整数需要用Modifier工具类解码才能获取真实的修饰符
- 获取类所在包
Package getPackage()
- 获取全限定类名
String getName()
- 获取类名简称
String getSimpleName()
- 判断该Class对象对应类是否为接口、枚举、注解等
boolean isAnnotation():是否为注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):该Class是否被注解annotationClass修饰
boolean isAnonymousClass():是否为匿名类
boolean isArray():是否为数组类
boolean isEnum():是否为枚举类
boolean isInterface():是否为接口
boolean isInstance(Object obj):判断obj是否为该Class的实例,等同于obj instanceof Object
18.4 使用反射生成并操作对象
18.4.1 创建对象
Class a = Class.forName("wusihan_0102_ClassLoader.ClassTest");
//1.通过Class对象的newInstance()调用默认构造器创建对象
ClassTest b= (ClassTest) a.newInstance();
//2.通过Class获取Constructor对象,再使用Constructor类的newInstance方法创建对象,这种方式可以使用有参构造器创建对象
Constructor c = a.getConstructor();
ClassTest d = (ClassTest) c.newInstance();
18.4.2 调用方法
Class<?> a = Class.forName("wusihan_0102_ClassLoader.ClassTest");
Object b = a.newInstance();
Method m = a.getMethod("test", String.class);
//Method的invoke方法,第一个参数为调用该方法的对象,第二个参数为该方法形参列表的实际参数值
m.invoke(b, "参数含");
18.4.3 访问成员变量值
Animal an = new Animal();
Class<Animal> a = Animal.class;
//成员变量age在Animal类中为private修饰,想获取private属性的成员,需要用getDeclaredField方法
Field f = a.getDeclaredField("age");
//由于age为private,如果想修改其对应值,需要用setAccessible方法取消其访问权限
//Method、Constructor、Field都有setAccessible方法
f.setAccessible(true);
//getXxx(Object obj)方法,获取obj对象的该成员变量的值,如果为引用类型,省略Xxx
System.out.println(f.get(an));
//setXxx(Object obj,Xxx val):为obj对象的成员变量赋值为val
f.setInt(an, 60);
System.out.println(f.get(an));
18.4.4 操作数组
//等同于String[] arr = new String[2];
Object arr = Array.newInstance(String.class, 2);
//等同于arr[0]="df";
Array.set(arr,0,"df");
//等同于System.out.println(arr[0])
System.out.println(Array.get(arr, 0));
18.7 反射和泛型
18.7.1 泛型和Class类
package wusihan_0102_ClassLoader;
import java.sql.Date;
public class CrazyitObjectFactory2 {
public static <T> T getInstance(Class<T> cls){
try {
return cls.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
//此时不再需要类型转换
Date d = CrazyitObjectFactory2.getInstance(Date.class);
}
}
18.7.2 使用反射获取泛型信息
package wusihan_0102_ClassLoader;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
public class GenericTest {
private Map<String ,Integer> score;
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
Class<GenericTest> clazz = GenericTest.class;
Field f = clazz.getDeclaredField("score");
//Field的方法,获取成员变量类型
Class<?> a = f.getType();
//interface java.util.Map
System.out.println(a);
//Field的方法,获取成员变量的泛型类型
Type gType = f.getGenericType();
//java.util.Map<java.lang.String, java.lang.Integer>
System.out.println(gType);
if(gType instanceof ParameterizedType){
ParameterizedType pType = (ParameterizedType)gType;
//ParameterizedType方法,获取原始类型:
Type rType = pType.getRawType();
//interface java.util.Map
System.out.println(rType);
//ParameterizedType方法,获取泛型类型数组
Type[] tArgs = pType.getActualTypeArguments();
for(Type tArg:tArgs){
//class java.lang.String
//class java.lang.Integer
System.out.println(tArg);
}
}
}
}