[疯狂Java讲义精粹] 第十三章|类加载机制与反射

本文深入讲解Java反射机制,包括类的加载过程、类加载器的工作原理、如何通过反射获取类信息及创建对象等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java.lang.reflect包下的接口和类, 包括Class, Method, Filed,Constructor和Array等, 这些类分别代表类, 方法, Field, 构造器和数组, Java程序可以使用这些类动态地获取某个对象, 某个类的运行时信息, 并可以动态地创建Java对象, 动态地调用Java方法, 并修改指定对象的Field值. 

1. 类的加载, 连接和初始化

1.1 JVM和类
0. 调用Java命令运行某个Java程序是, 该命令会启动一个Java虚拟机进程, Java程序(即使是多进程程序)运行在JVM里. 

1. 这几种情况JVM进程被终止:
  1. 程序运行完正常结束. 
  2. 运行到System.exit()或Runtime.getRuntime().exit()代码处结束程序. 
  3. 程序运行过程中遇到未捕获的异常或错误而结束. 
  4. 程序所在平台强制结束了JVM进程. 
1.2 类的加载
0. 当程序主动使用某个类时, 如果该类还未被加载到内存中, 则系统会通过加载、连接、初始化3个步骤来对该类进行初始化. (如果没有意外, JVM会连续完成这3个步骤, 所以有时也把这3个步骤统称为类加载或类初始化.) 

1. 类加载指 将类的class文件读入内存, 并为之创建一个java.lang.Class对象, 就是说当程序中使用任何类时, 系统都会为之建立一个java.lang.Class对象. 

2. 类的加载有类加载器(CLassLoader)完成. 类加载器通常由JVM提供. 类加载器除了根类加载器外, 其他的都是用Java语言编写的. JVM提供的类加载器叫系统类加载器, 还可以通过集成ClassLoader基类来创建自己的加载器. 通过不同的类加载器可以从不同来源加载类的二进制数据(如从本机文件系统加载class文件, 从JAR包加载class文件, 从网络加载class文件等). 
1.3 类的连接
0. 类被加载之后, 系统生成一个和它对应的java.lang.Class对象, 然后进入连接阶段把类的二进制数据合并到JRE中. 

1. 类的连接分三个阶段. 
  1. 验证: 检测被加载的类是否有正确的内部结构, 并和其他类协调一致. 
  2. 准备: 为类的静态Field分配内存, 并设置默认初始值. 
  3. 解析: 将类的二进制数据中的符号引用替换成直接引用. 
1.4 类的初始化
0. 类的初始化阶段主要对静态Field初始化. 

1. JVM初始化一个类的步骤: 
  1. 如果这个类还没有被加载和连接, 则程序先加载并连接该类. 
  2. 如果该类的直接父类没有被初始化, 则先初始化其直接父类. (顺着捋, 都能捋到java.lang.Object类, 所以JVM最先初始化的总是java.lang.Object类. 
  3. 如果类中有初始化语句, 则一次执行这些初始化语句. 

1.5 类初始化的时机

0.  对于一个final型的静态Field, 如果该Field的值在编译时就可以确定下来, 那么这个field相当于"宏变量", 编译器在编译时直接把这个Field出现的地方替换成它的值, 因此即使程序使用该静态Field也不会导致该类的初始化. 

1. 使用ClassLoader类的loadClass()放方法加载某个类时, 该方法只是加载该类, 不执行初始化. 使用Class的forName()静态方法才会导致强制初始化该类. 
class Tester
{
	static 
	{
		System.out.println("Tester类的静态初始化块");
	}
}
public class ClassLoaderTest
{
	public static void main(String[] args)
			throws ClassNotFoundException
	{
		ClassLoader cl = ClassLoader.getSystemClassLoader();
		cl.loadClass("Tester");
		System.out.println("系统加载Tester类");
		Class.forName("Tester");
	}
}

2. 类加载器

类加载器负责将.class文件加载到内存中, 并为之生成对应的Class对象. 
2.1 类加载器简介
0. 加入内存中的类有对应的一个java.lang.Class实例. 一个类一旦加入JVM中, 就不会再次被载入了. Then how to 判断是否是同一个类捏? 答: Java中, 类用其全限定类名(包括包名和类名)作为标识; JVM中, 用其全限定类名和其类加载器名作为其唯一标识(如包pg中的Person类用类加载器ClassLoader的kl实例加载, 则JVM中表示为(Person、pg、kl)). 

1. JVM启动时, 形成由3个类加载器组成的初始类加载器层次结构: 
  1. Bootstrap ClassLoader: 根类加载器. 也叫引导(原始)类加载器, 负责加载Java的核心类, 它不是java.lang.ClassLoader的子类而是由JVM自身实现的. 
  2. Extension ClasssLoader: 扩展类加载器. 法则加载JRE扩展目录(%JAVA_HOME%/jre/lib/ext或者由java.ext.dirs系统属性制定的目录)中JAR包的类. 
  3. System ClassLoader: 系统类加载器. 也叫应用加载器, 负责在JVM启动时加载来自java命令的-classpath选项, java.class.path系统属性或CLASSPATH环境变量所指定的JAR包和类路径. 程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器. 如果没有特别指定, 则用户自定义的类加载器都是以系统类加载器作为父加载器. 
2.2 类加载机制
0. 3种: 
  1. 全盘负责. 的那个一个类加载器负责加载某个Class时, 该Class所以来的和引用的其他Class也有该类加载器负责载入, 除非显式使用另外的类加载器载入. 
  2. 父类委托. 即先让parent(父)类加载器尝试加载该Class, 无法加载时才尝试从自己的类路径中加载该类. 
  3. 缓存机制. 保证所有加载过的Class都会被缓存, 程序需要某个Class时, 类加载器先从缓存区中搜寻该Class, 不存在时再读取该类对应的二进制数据, 并将其转换成Class对象存入缓存区. 

1. 系统类加载器是AppClassLoader的实例, 扩展类加载器是ExtClassLoader的实例. 这两个类都是URLClassLoader类的子类. 
2.3 创建并使用自定义的类加载器
0. JVM中除了根类加载器外的所有加载器都是ClassLoader子类的实例. 扩展ClassLoader并重写其方法来自定义类加载器. 

1. ClassLoader类的方法: 
  1. loadClass(String name, boolean resolve): 按指定名称来加载类, 系统就是调用ClassLoader的loadClass()方法来获取指定类对应的Class对象. 
  2. findClass(String name): 按名称查找类. 

2. loadClass()方法的执行步骤: 
  1. 用findLoadedClass(String)检查该类是否已加载, 如果是, 直接返回. 
  2. 调用父类加载器的loadClass()方法, 如果父类加载器为null, 调用跟类加载器加载. 
  3. 调用findClass()方法查找类. 
* 所以这么看来重写findClass()方法来自定义类加载器, 省事多了. 

3. Class defineClass(String name, byte[] b, int off, int len): 负责将指定类的字节码文件(如Hello.class)读入到字节数组b内, 并把它转换为Class对象. 该字节码文件可以源于文件或网络等. 
2.4 URLClassLoader类
0. java.net.URLClassLoader是java.lang.ClassLoader抽象类的实现类, 是扩展类加载器和系统类加载器的父类. 

1. 在应用程序中可以直接使用URLCLassLoader来加载类, 两个构造器: 
  1. URLClassLoader(URL[] urls): 使用默认的父类加载器创建一个ClassLoader对象, 该对象将从urls所指定的系列路径来查询并加载类. 
  2. URLClassLoader(URL[] urls,ClassLoader parent): 使用指定的父类加载器创建一个ClassLoader对象, 该对象将从urls所指定的系列路径来查询并加载类. 

3. 通过反射查看类信息

3.1 获得Class对象
类加载之后系统会为它生成一个对应的Class对象. 获得这个对象的方法有: 
  1. 使用Class类的forName(String clazzName)静态方法. clazzName是某个类的全限定类名(包括包名和类名). 
  2. 调用某个类的class属性. 
  3. 调用某个对象的getClass()方法. 该方法时java.lang.Object类的一个方法, 所以所有对象都可以用. 
3.2 从Class中获取信息
有很多方法. 这些方法都可能有重载版本. 
  1. getConstructor(): 获得public构造器. 
  2. getDeclaredConstructor(): 获得所有构造器. 

  3. .......
    看API文档吧>>http://www.oracle.com/technetwork/java/javase/downloads/index.html

4. 使用反射生成并操作对象

Class对戏那个可以获得该类里的方法, 构造器和Field, 这三个累都位于java.lang.reflect包下, 并实现了java.lang.reflect.Member接口. 
4.1 创建对象
通过反射生成对象的两种方式: 
  1. 使用Class对象的newInstance()方法. 需要Class对象对应的类有默认构造器, 儿执行newInstance()方法就是利用默认构造器创建该类的实例. 
  2. 使用Class对象获得指定的Constructor对象, 再调用Constructor对象的newInstructor()方法创建该Class对象对应类的实例. 
4.2 调用方法
0. 通过Class对象的getMethods()或getMethod()方法能获得全部或指定方法. 

1. Method类有个invoke()方法, 用以调用指定对象的方法:
Object invoke(Object obj, Object... args): obj是执行该方法的主调, args是执行时传入该方法的实参. 

2. 当通过Method的invoke()方法调用对应方法时, Java会要求程序必须有调用该方法的权限. 如果程序需要调用private方法, 可以先调用Method对戏那个的setAccessible方法:
setAccessible(boolean flag): true表示该Method在使用时应该取消访问权检查, false表示要检查. 

3. setAccessible()方法继承自Method的父类AccessibleObject, Constructor和Field类也可以调用该方法. 
4.3 访问属性值
0. 通过Class对象的getFields()或getField()方法能获得全部或指定Field. 

1. Field类量两个方法: 
  1. getXxx(Object obj): 获取obj对象该Field的属性值. 此处Xxx对应8个基本类型, 如果该属性的类型是引用类型, 则取消get后面的Xxx. 
  2. setXxx(Object obj, Xxx val): 将obj对象的该属性值设为val. 此处Xxx对应8个基本类型, 如果该属性的类型是引用类型, 则取消get后面的Xxx. 

2. 例子. 
import java.lang.reflect.*;

class Person
{
	private String name;
	private int age;
	public String toString()
	{
		return "Person[name:" + name + 
		" , age:" + age + " ]";
	}
}
public class FieldTest
{
	public static void main(String[] args) 
		throws Exception
	{
		Person p = new Person();
		Class<Person> personClazz = Person.class;
		Field nameField = personClazz.getDeclaredField("name");
		nameField.setAccessible(true);
		nameField.set(p , "Yeeku.H.Lee");
		Field ageField = personClazz.getDeclaredField("age");
		ageField.setAccessible(true);
		ageField.setInt(p , 30);
		System.out.println(p);
	}
}
4.4 操作数组
0. java.lang.reflect包下还提供了一个Array类, Array对象可以代表所有的数组. 程序可以通过Array动态地创建数组, 操作数组元素. 

1. Array的方法: 
  1. static Object newInstance(Class<?> componentType, int... length): 创建一个据有偶指定的元素类型, 指定维度的新数组. 
  2. Static xxx getXxx(Object array, int index): 返回array数组中的第index个元素. 如果数组元素是引用类型, 则该方法变为get(Object array, int index). 
  3. static void setXxx(Object array, int index, xxx val): 将array数组的第index个元素设为val.  如果数组元素是引用类型, 则该方法变为get(Object array, int index, xxx val). 

2. 例子. 
import java.lang.reflect.*;

public class ArrayTest1
{
	public static void main(String args[])
	{
		try
		{
			Object arr = Array.newInstance(String.class, 10);
			Array.set(arr, 5, "疯狂Java讲义");
			Array.set(arr, 6, "轻量级Java EE企业应用实战");
			Object book1 = Array.get(arr , 5);
			Object book2 = Array.get(arr , 6);
			System.out.println(book1);
			System.out.println(book2);
		}
		catch (Throwable e)
		{
			System.err.println(e);
		}
	}
}

5. 使用反射生成JDK动态代理

java.lang.reflect包提供Proxy类和InvocationHandler接口, 通过它们可以生成JDK动态代理或动态处理对象. 
5.1 使用Proxy和InvocationHandler接口
0. 

6. 反射和泛型



-------
这个坑填
困啊..


































评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值