欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2023-12-21 13:19:40
...

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

使用类时,都会经历加载、连接、初始化三个步骤

18.1.1 jvm和类
  1. java命令运行某java程序,操作系统中会启动一个java虚拟机进程,无论该java程序多么复杂,该程序包含了多少线程,它们都处于java虚拟机进程里。同一个jvm的所有线程、所有变量都处于同一个进程中,它们都使用该jvm进程的内存区。
  2. jvm终止的情况
    1. 程序运行到最后正常结束
    2. System.exit()、Runtime.getRuntime().exit()
    3. 程序执行过程中遇到未捕获异常或错误
    4. 操作系统强制结束jvm进程(kill -9)
  3. jvm进程结束,该进程内存中状态丢失
18.1.2 类的加载
  1. 类的加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象,同一个类(全限定类名+加载器表示同一个类)只能被加载一次
  2. Class是类的抽象,可以通过这个Class对象获取到这个类中定义的内容。类是对某一类对象的抽象,Class是对类的抽象
  3. 类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的类加载器通常称为系统类加载器,开发者也可以通过继承ClassLoader基类创建自己的类加载器
18.1.3 类的连接
  1. 类的连接:将类的二进制数据合并到JRE中。又分为如下三个阶段
    1. 验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致
    2. 准备:为类的类变量分配内存,设置默认值
    3. 解析:将类的二进制数据中的符号引用替换成直接引用
18.1.4 类的初始化
  1. 类的初始化:就是对类变量赋值,之前都是默认值
  2. 类初始化之前必须先加载和连接
  3. 如果类的直接父类还没被初始化,先初始化其父类
  4. 最后使用类中初始化语句对类变量赋值
18.1.5 类初始化时机
18.1.5.1 类初始化时机
  1. 创建类实例时
    1. new
    2. 反射
    3. 序列化
  2. 调用某类静态方法、静态变量
  3. 使用反射创建该类对应的Class对象时:Class.forName(“Person”)
  4. 初始化子类
  5. java.exe运行主类
18.1.5.2 特殊情况
  1. 如果类的静态成员变量为宏变量,使用它时,不会初始化类,因为对JVM来讲他是个常量
  2. 调用类加载器的loadClass来加载类时,不会初始化。Class.forName时才初始化

18.2 类加载器

18.2.1 类加载器简介
  1. 一旦一个类被加载到JVM中,同一个类不会再次加载
  2. 什么叫同一个类:Java中用全限定类名表示唯一一个类,而JVM中,使用全限定类名+类加载器表示唯一一个类。例如pg包中有Person类,是用ClassLoader的示例k1加载的,那么Person对应的Class对象在内存中表示为(Person,pg,k1),它与(Person,pg,k2)是不同的
  3. JVM启动时,形成由三个类加载器组成的初始类加载器层次结构
    1. (根)类加载器
    2. (扩展)类加载器
    3. (系统)类加载器
  4. 根类加载器
//不是ClassLoader子类
//负责加载$JAVA_HOME/jre/lib、$JAVA_HOME/jre/lib/classes下的JAR包和类
//指定根加载器加载指定附加的类
-Xbootclasspath
-Dsun.boot.class.path   
  1. 扩展类加载器
//负责加载JRE的扩展目录($JAVA_HOME/jre/lib/ext)或系统属性"java.ext.dirs"所指定的目录下的JAR包和类
  1. 系统类加载器
//加载-classpath、CLASSPATH环境变量、java.class.path系统属性所指定的JAR包和类
18.2.2 类加载机制
  1. 全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖和引用的其他Class也由该类加载器加载,除非显示用另外一个类加载器加载
  2. 父类委托:
    1. 概念:如果一个类加载器收到了类加载器的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求最终都会传送到根类加载器中.只有父类加载反馈自己无法加载这个请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
    2. 优点:java类随着它的加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jart之中.无论哪一个类加载器都要加载这个类,最终都是双亲委派模型最顶端的Bootstrap类加载器去加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型.由各个类加载器自行去加载的话,如果用户编写了一个称为“java.lang.Object”的类.并存放在程序的ClassPath中,那系统中将会出现多个不同的Object类。java类型体系中最基础的行为也就无法保证,应用程序也将会一片混乱.
  3. 缓存机制:所有被加载过的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 创建并使用自定义的类加载器
  1. 使用自定义类加载器执行java程序
//CompileClassLoader为自定义的类加载器,实际上就是一个类,这个类有main方法, 需传入两个参数,一个为该类加载器要加载的类名A,以及A的main方法中的参数
//Hello为类A
//"疯狂java讲义"是为main方法中形参列表的字符串数组传值
java CompileClassLoader Hello "疯狂java讲义"
  1. 可以通过继承ClassLoader,并重写其内方法, 来自定义类加载器
//根据指定名称加载类,返回对应Class对象,里面调用了findClass方法
loadClass(String name,boolean resolve)
//根据指定名称来查找类
findClass(String name)
//创建自定义类加载器时一般重写findClass方法而不重写loadClass,避免覆盖默认类加载器的父类委托与缓冲机制两种策略
  1. 自定义类加载器通常可以实现如下功能
    1. 运行前先编译java源文件,从而无需编译,直接执行
    2. 执行代码前,自动验证数字签名
    3. 根据用户提供的代码解密代码,从而可以实现代码混淆器来避免反编译*.class文件
    4. 根据用户需求,动态加载类
    5. 根据用户需求,把其他数据以字节码形式加载到应用中
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程序中的对象运行时出现两种类型,一种是运行时类型,另一种是编译时类型。程序如果想调用该对象运行时类型的成员,有以下两种途径

  1. 编译时知道运行时类型:使用instanceof判断是否可以转换,再使用强制类型转换将变量转换为运行时类型对应的变量
  2. 编译时不知道运行时类型:使用反射
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
  1. 获取Class对应类的构造器
Constructor<T> getConstructor(Class<?> ... parameterTypes):返回此Class对象对应类的、带指定形参列表的public构造器
Constructor<?>[] getConstructors():返回此Class对象对应类的所有public构造器
Constructor<T> getDeclaredConstructor(Class<?> ... parameterTypes):返回此Class对象对应类的、带指定形参列表的与访问权限无关的构造器
Constructor<?>[] getDeclaredConstructors()
  1. 获取方法
Method getMethod(String name,Class<?> ... parameterTypes):name为方法名,parameterTypes为形参类型
Method[] getMethods()
Method getDeclaredMethod(String name,Class<?> ... parameterTypes)
Method[] getDeclaredMethods()
  1. 获取成员变量
Field getField(String name)
Field[] getFields()
Field getDeclaredField(String name)
Field[] getDeclaredFields()
  1. 获取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()
  1. 获取内部类
Class<?>[] getDeclaredClasses()
  1. 获取外部类
Class<?> getDeclaringClass()
  1. 获取实现的接口
Class<?>[] getInterfaces()
  1. 获取继承的父类
Class<? super T> getSuperclass()
  1. 获取修饰符
int getModifiers():获取类或接口的所有修饰符,返回的整数需要用Modifier工具类解码才能获取真实的修饰符
  1. 获取类所在包
Package getPackage()
  1. 获取全限定类名
String getName()
  1. 获取类名简称
String getSimpleName()
  1. 判断该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);
			}
		}
	}
}

上一篇:

下一篇: