类加载机制核心类与常用方法(详细说明 + 代码示例)
在 Java 类加载机制中,核心操作依赖 java.lang.Class(类元数据入口)、java.lang.ClassLoader(类加载器基类)及其子类。这些类提供了丰富的 API,用于获取类元数据、手动加载类、反射操作、验证类加载信息等。本文将详细介绍核心类的常用方法,结合场景化代码说明其用法。
一、核心类概述
| 核心类 | 作用 |
|---|---|
java.lang.Class |
堆中代表类的元数据对象,是访问方法区类信息的唯一入口,支持反射操作。 |
java.lang.ClassLoader |
类加载器基类,定义类加载的核心逻辑(如 loadClass),支持自定义加载规则。 |
| 子类(系统类加载器) | AppClassLoader(应用类加载器)、ExtClassLoader(扩展类加载器),负责默认类加载。 |
二、java.lang.Class 类(核心中的核心)
Class 类是所有类的 "元类",每个被加载的类在堆中只会生成一个 Class 实例。其方法主要用于获取类信息、实例化对象、反射调用方法 / 字段等。
1. 获取 Class 对象的 3 种核心方式(必须掌握)
这是所有反射和类加载操作的前提,3 种方式对应不同场景:
java
public class ClassGetDemo {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 方式1:通过类名.class(编译期确定,不触发类初始化,仅加载)
Class<Parent> class1 = Parent.class;
System.out.println("方式1:" + class1.getName()); // 输出:Parent
// 方式2:通过实例对象.getClass()(运行期确定,类已加载并初始化)
Parent parent = new Parent();
Class<? extends Parent> class2 = parent.getClass();
System.out.println("方式2:" + class2.getName()); // 输出:Parent
// 方式3:通过Class.forName(全限定名)(运行期动态加载,触发类初始化)
Class<?> class3 = Class.forName("Parent"); // 全限定名=包名+类名(无包则直接写类名)
System.out.println("方式3:" + class3.getName()); // 输出:Parent
// 验证:3种方式获取的是同一个Class对象(单例)
System.out.println("class1 == class2:" + (class1 == class2)); // true
System.out.println("class1 == class3:" + (class1 == class3)); // true
}
}
class Parent {
static {
System.out.println("Parent 初始化(仅方式3触发)");
}
}
关键区别:
- 方式 1(
类名.class):编译期解析,不触发类初始化,仅加载类; - 方式 2(
对象.getClass()):运行期获取,类已实例化(必然已初始化); - 方式 3(
Class.forName()):动态加载(支持字符串拼接类名),默认触发类初始化 (可通过重载方法关闭:Class.forName(name, false, classLoader))。
2. 常用核心方法(按功能分类)
(1)获取类的基本信息
java
public class ClassInfoDemo {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("Child");
// 1. 类名相关
System.out.println("全限定名:" + clazz.getName()); // 输出:Child
System.out.println("简单类名:" + clazz.getSimpleName()); // 输出:Child
System.out.println("类的修饰符:" + java.lang.reflect.Modifier.toString(clazz.getModifiers())); // 输出:public
// 2. 父类与接口
System.out.println("父类:" + clazz.getSuperclass().getName()); // 输出:Parent
Class<?>[] interfaces = clazz.getInterfaces();
System.out.println("实现的接口数:" + interfaces.length); // 若未实现接口则为0
// 3. 类的类型判断
System.out.println("是否为接口:" + clazz.isInterface()); // false
System.out.println("是否为数组:" + clazz.isArray()); // false
System.out.println("是否为基本类型:" + clazz.isPrimitive()); // false(int.class才是true)
System.out.println("是否为枚举:" + clazz.isEnum()); // false
}
}
class Parent {}
class Child extends Parent implements Runnable {
@Override
public void run() {}
}
(2)实例化对象(反射创建实例)
java
public class ClassNewInstanceDemo {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> clazz = Class.forName("User");
// 方式1:调用无参构造器(要求无参构造器存在且可访问)
User user1 = (User) clazz.newInstance(); // JDK9+已过时,推荐方式2
System.out.println("user1:" + user1); // 输出:User{name='null', age=0}
// 方式2:通过Constructor调用构造器(支持有参、私有构造器)
// 2.1 调用有参构造器(String, int)
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
User user2 = (User) constructor.newInstance("张三", 20);
System.out.println("user2:" + user2); // 输出:User{name='张三', age=20}
// 2.2 调用私有构造器(setAccessible(true)打破访问限制)
Constructor<?> privateConstructor = clazz.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true); // 允许访问私有构造器
User user3 = (User) privateConstructor.newInstance("李四");
System.out.println("user3:" + user3); // 输出:User{name='李四', age=0}
}
}
class User {
private String name;
private int age;
// 无参构造器(方式1依赖)
public User() {}
// 有参构造器(方式2.1)
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 私有构造器(方式2.2)
private User(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
(3)获取类的字段(静态 / 成员变量)
java
public class ClassFieldDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
Class<?> clazz = Class.forName("User");
User user = (User) clazz.newInstance();
// 1. 获取public字段(包括父类)
Field publicField = clazz.getField("publicAge"); // 要求字段是public
publicField.set(user, 25); // 给实例赋值
System.out.println("publicAge:" + publicField.get(user)); // 输出:25
// 2. 获取当前类的所有字段(包括private,不包括父类)
Field privateField = clazz.getDeclaredField("name");
privateField.setAccessible(true); // 打破private限制
privateField.set(user, "王五"); // 给私有字段赋值
System.out.println("私有name:" + privateField.get(user)); // 输出:王五
// 3. 获取静态字段(类级别的字段,无需实例)
Field staticField = clazz.getDeclaredField("staticName");
staticField.setAccessible(true);
System.out.println("静态字段原值:" + staticField.get(null)); // 静态字段用null作为参数
staticField.set(null, "静态修改后"); // 修改静态字段
System.out.println("静态字段新值:" + staticField.get(null)); // 输出:静态修改后
// 4. 获取字段的详细信息
System.out.println("字段类型:" + privateField.getType().getName()); // 输出:java.lang.String
System.out.println("字段修饰符:" + Modifier.toString(privateField.getModifiers())); // 输出:private
}
}
class User {
private String name;
public int publicAge;
private static String staticName = "静态初始值";
public User() {}
}
(4)获取类的方法(静态 / 成员方法)
java
public class ClassMethodDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class<?> clazz = Class.forName("User");
User user = (User) clazz.newInstance();
// 1. 调用public成员方法(无参)
Method publicMethod = clazz.getMethod("show");
publicMethod.invoke(user); // 输出:User.show() 执行
// 2. 调用public成员方法(有参)
Method publicParamMethod = clazz.getMethod("sayHello", String.class);
String result = (String) publicParamMethod.invoke(user, "赵六");
System.out.println("方法返回值:" + result); // 输出:Hello, 赵六
// 3. 调用private成员方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(user); // 输出:User.privateMethod() 执行
// 4. 调用静态方法(无需实例,参数传null)
Method staticMethod = clazz.getMethod("staticMethod");
staticMethod.invoke(null); // 输出:User.staticMethod() 执行
// 5. 获取方法的详细信息
System.out.println("方法参数类型:" + Arrays.toString(publicParamMethod.getParameterTypes())); // 输出:[class java.lang.String]
System.out.println("方法返回值类型:" + publicParamMethod.getReturnType().getName()); // 输出:java.lang.String
}
}
class User {
public void show() {
System.out.println("User.show() 执行");
}
public String sayHello(String name) {
return "Hello, " + name;
}
private void privateMethod() {
System.out.println("User.privateMethod() 执行");
}
public static void staticMethod() {
System.out.println("User.staticMethod() 执行");
}
}
(5)类加载相关方法
java
public class ClassLoaderRelatedDemo {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("User");
// 1. 获取加载当前类的类加载器
ClassLoader classLoader = clazz.getClassLoader();
System.out.println("类加载器:" + classLoader); // 输出:sun.misc.Launcher$AppClassLoader@18b4aac2
// 2. 判断类是否已初始化(JDK9+)
// System.out.println("是否已初始化:" + clazz.isInitialized()); // true
// 3. 强制初始化类(仅当未初始化时生效)
clazz.initialize(); // 若已初始化,调用无效果
}
}
三、java.lang.ClassLoader 类(类加载器核心)
ClassLoader 是抽象类,所有类加载器(系统类加载器、自定义类加载器)都继承自它。其核心方法用于加载类、获取父加载器、访问类路径等。
1. 核心方法(按功能分类)
(1)加载类的核心方法
java
public class ClassLoaderLoadDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 1. 获取系统类加载器(应用程序类加载器)
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:" + appClassLoader); // AppClassLoader
// 2. 加载类(遵循双亲委派模型)
// 方式1:loadClass(String 全限定名):默认不触发类初始化(仅加载、验证、准备)
Class<?> clazz1 = appClassLoader.loadClass("User");
System.out.println("loadClass加载的类:" + clazz1.getName()); // User
// 方式2:Class.forName():默认触发初始化(底层调用ClassLoader的loadClass)
Class<?> clazz2 = Class.forName("User");
System.out.println("forName加载的类:" + clazz2.getName()); // User
// 3. 获取父加载器(验证双亲委派模型)
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("应用类加载器的父加载器:" + extClassLoader); // ExtClassLoader
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("扩展类加载器的父加载器:" + bootstrapClassLoader); // null(C++实现)
// 4. 加载核心类(由启动类加载器加载)
Class<?> stringClass = Class.forName("java.lang.String");
System.out.println("String类的加载器:" + stringClass.getClassLoader()); // null(启动类加载器)
}
}
关键说明:
loadClass(String name):核心加载方法,遵循双亲委派模型,默认不触发类初始化(仅完成 "加载→验证→准备");findClass(String name):子类重写此方法,实现自定义加载逻辑(如加载网络字节码、加密字节码),默认抛出ClassNotFoundException;defineClass(byte[] b, int off, int len):将字节数组转化为Class对象(最终加载类的核心步骤),子类不能直接调用(需通过findClass间接调用)。
(2)获取资源与类路径
java
public class ClassLoaderResourceDemo {
public static void main(String[] args) {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
// 1. 获取类路径下的资源(如配置文件)
// 相对路径:从classpath根目录开始(src/main/resources下的文件)
URL resource = classLoader.getResource("config.properties");
if (resource != null) {
System.out.println("资源路径:" + resource.getPath());
} else {
System.out.println("未找到资源config.properties");
}
// 2. 获取所有资源(支持通配符)
try {
Enumeration<URL> resources = classLoader.getResources("config.properties");
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
System.out.println("找到资源:" + url.getPath());
}
} catch (IOException e) {
e.printStackTrace();
}
// 3. 获取资源的输入流(直接读取资源内容)
try (InputStream is = classLoader.getResourceAsStream("config.properties")) {
if (is != null) {
// 读取配置文件(示例:按行读取)
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
System.out.println("配置内容:" + line);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(3)自定义类加载器示例(重写 findClass)
自定义类加载器的核心是重写 findClass 方法,实现自定义加载逻辑(如下示例加载本地磁盘上的加密字节码):
java
/**
* 自定义类加载器:加载本地磁盘上的.class文件
*/
public class CustomClassLoader extends ClassLoader {
// 自定义类加载路径(本地磁盘目录)
private String classPath;
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
// 重写findClass:实现自定义加载逻辑
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
try {
// 1. 将类名转为文件路径(com.example.User → com/example/User.class)
String filePath = classPath + File.separator + className.replace(".", File.separator) + ".class";
// 2. 读取.class文件的字节数组
byte[] classBytes = loadClassBytes(filePath);
// 3. 将字节数组转为Class对象(核心方法)
return defineClass(className, classBytes, 0, classBytes.length);
} catch (IOException e) {
throw new ClassNotFoundException("类加载失败:" + className, e);
}
}
// 读取文件字节数组
private byte[] loadClassBytes(String filePath) throws IOException {
File file = new File(filePath);
try (InputStream is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
}
}
// 测试自定义类加载器
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 本地.class文件所在目录(示例:D:/customClasses)
String classPath = "D:/customClasses";
CustomClassLoader customLoader = new CustomClassLoader(classPath);
// 加载自定义路径下的类(全限定名)
Class<?> clazz = customLoader.loadClass("com.example.Test");
System.out.println("加载的类:" + clazz.getName()); // com.example.Test
System.out.println("类加载器:" + clazz.getClassLoader()); // CustomClassLoader@xxx
// 实例化对象(反射)
Object obj = clazz.newInstance();
System.out.println("实例对象:" + obj);
}
}
自定义类加载器的应用场景:
- 加载加密的
.class文件(在loadClassBytes中解密); - 加载网络上的
.class文件(从 HTTP 接口获取字节码); - 热部署(重新加载修改后的类)。
四、常用方法总结(按场景归类)
| 应用场景 | 核心类 | 常用方法 |
|---|---|---|
| 获取 Class 对象 | Class | 类名.class、对象.getClass()、Class.forName() |
| 类信息查询(名称、父类) | Class | getName()、getSuperclass()、getModifiers() |
| 实例化对象 | Class/Constructor | newInstance()、Constructor.newInstance() |
| 操作字段(读 / 写) | Class/Field | getField()、getDeclaredField()、set()、get() |
| 调用方法 | Class/Method | getMethod()、getDeclaredMethod()、invoke() |
| 类加载 | ClassLoader | loadClass()、findClass()、defineClass() |
| 获取类加载器 | Class/ClassLoader | getClassLoader()、getParent()、getSystemClassLoader() |
| 访问资源文件 | ClassLoader | getResource()、getResourceAsStream() |
五、关键注意事项
- 访问权限 :通过
getDeclaredField()/getDeclaredMethod()获取私有字段 / 方法后,需调用setAccessible(true)打破访问限制; - 双亲委派模型 :
ClassLoader.loadClass()遵循双亲委派,自定义类加载器重写findClass()而非loadClass(),避免破坏双亲委派; - 类的唯一性 :同一个类由不同类加载器加载,会被 JVM 视为不同类(
equals()返回 false); - 初始化触发 :
Class.forName()默认触发类初始化,ClassLoader.loadClass()默认不触发,需根据场景选择。
掌握这些核心类和方法,不仅能深入理解类加载机制,还能灵活运用反射实现复杂功能(如框架中的依赖注入、配置解析)。