类加载
1. 类加载器(ClassLoader)
- 负责加载 Java 核心类库(如 rt.jar)。
- 不是由 Java 代码实现,而是由底层 C/C++ 代码实现。
- 是所有其他类加载器的父加载器。
- 负责加载扩展类库(如 lib/ext 目录中的 JAR 包)。
- 父加载器是启动类加载器。
- 负责加载应用程序类路径(classpath)上的类。
- 父加载器是扩展类加载器。
- 也称为系统类加载器。
2. 双亲委派模型(Parent Delegation Model)
- 当一个类加载器接收到类加载请求时,它首先将这个请求委派给它的父加载器。
- 父加载器尝试加载这个类,如果找不到,再交给子加载器去加载。
- 最终,如果所有的父加载器都无法加载这个类,最初的类加载器才会尝试自己加载。
3. 类加载过程
1. 加载(Loading)
- 通过类名获取类的二进制字节流。
- 将字节流转化为方法区中的运行时数据结构。
- 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
2. 链接(Linking)
验证(Verification)
目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证。
可以考虑使用-Xverify:none 参数来关闭大部分的类验证措施,缩短虛拟机类加载的时间。
准备(Preparation)
为静态变量分配内存并设为默认初始值(如 0、null),JVM 会在该阶段对静态变量,分配内存井初始化(对应数据类型的默认初始值如0、OL、null false 等),这些变量所使用的内存都将在方法区中进行分配。
- 解析(Resolution)
- 虛拟机将常量池内的符号引用替换为直接引用的过程。
3.初始化(Initialization)
执行类的初始化代码,包括静态变量的赋值和静态代码块的执行。
初始化顺序按照代码中出现的顺序进行。
4. 自定义类加载器
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] classData = loadClassData(name);
return defineClass(name, classData, 0, classData.length);
} catch (IOException e) {
throw new ClassNotFoundException("Class file not found: " + name, e);
}
}
private byte[] loadClassData(String className) throws IOException {
// 这里简化处理,实际应读取磁盘上的.class文件
String path = classPath + "/" + className.replace('.', '/') + ".class";
try (InputStream is = new FileInputStream(path)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int length;
while ((length = is.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
}
}
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader("path/to/classes");
Class<?> clazz = classLoader.loadClass("com.example.MyClass");
Object instance = clazz.newInstance();
System.out.println(instance);
}
}
5. 类的热替换与热部署
总结
类加载机制的具体示例
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
- 启动类加载器尝试加载 HelloWorld 类(实际上,它会尝试加载 java.lang.* 等核心类库)。
- 因为 HelloWorld 不是核心类库的一部分,所以启动类加载器将加载请求委派给扩展类加载器。
- 扩展类加载器同样无法找到 HelloWorld 类,因此它将加载请求委派给应用程序类加载器。
- 应用程序类加载器搜索类路径(classpath),找到并加载 HelloWorld.class 文件。
- JVM 执行 HelloWorld 类的 main 方法。
示例 2: 自定义类加载器
// MyClass.java
public class MyClass {
public void sayHello() {
System.out.println("Hello from MyClass!");
}
}
然后,编写一个自定义类加载器 FileClassLoader 来加载 MyClass。
import java.io.*;
public class FileClassLoader extends ClassLoader {
private String classPath;
public FileClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String name) {
try {
String fileName = classPath + name.replace('.', '/') + ".class";
InputStream inputStream = new FileInputStream(fileName);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int nextValue = 0;
while ((nextValue = inputStream.read()) != -1) {
byteArrayOutputStream.write(nextValue);
}
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
最后,编写一个测试程序来使用自定义类加载器加载并运行 MyClass。
public class ClassLoaderTest {
public static void main(String[] args) {
FileClassLoader fileClassLoader = new FileClassLoader("path/to/classes/");
try {
Class<?> clazz = fileClassLoader.loadClass("MyClass");
Object instance = clazz.newInstance();
clazz.getMethod("sayHello").invoke(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
反射机制
基本介绍

反射相关类
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象。
- java.lang.reflect.Method: 代表该类的方法。
- java.lang.reflect.Field:代表类的成员变量。
- java.lang.reflect.Constructor:代表类的构造方法。
Class 类
基本介绍
- Class也是类,因此也继承Object类。
- Class类对象不是new出来的,而是系统创建的。
- 类的Class类对象,在内存中只有一份,且只加载一次。
- 每个类的实例都会记录它属于哪个Class实例生成。
- 通过Class可以完整地得到一个类的完整结构,通过一系列API。
- Class对象是存放在堆的。
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等等)。
Class类对象获取
1.前提:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取。
Class cls1 = Class.forName("java.lang.Cat” )
2.前提:若已知具体的类,通过类的 Class 获取,该方式最为安全可靠,程序性能最高。
Class cls2 = Cat.class
3.前提:已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象。
Class class = 对象.getClass();
4.其他方式(类加载器) 。
ClassLoader cl = 对象.getClass().getClassLoader();Class class4 = cl.loadClass(“类的全类名");
5.基本数据(int, char boolean,float,double.byte.long,short)
Class cls = 基本数据类型.class;
6.基本数据类型对应的包装类,可以通过.type 得到Class类对象。
Class cls = 包装类.TYPE;
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
//1. Class.forName
String classAllPath = "com.ben.Car"; //通过读取配置文件获取
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1);
//2. 类名.class , 应用场景: 用于参数传递
Class cls2 = Car.class;
System.out.println(cls2);
//3. 对象.getClass(), 应用场景,有对象实例
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);
//4. 通过类加载器【4种】来获取到类的Class对象
//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);
//cls1 , cls2 , cls3 , cls4 其实是同一个对象
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
//5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到Class类对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);//int
//6. 基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
Class<Integer> type1 = Integer.TYPE;
Class<Character> type2 = Character.TYPE; //其它包装类BOOLEAN, DOUBLE, LONG,BYTE等待
System.out.println(type1);
System.out.println(integerClass.hashCode());//?
System.out.println(type1.hashCode());//?
}
}
有Class对象的类型
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface :接口
- 数组
- enum:枚举
- annotation :注解
- 基本数据类型
- void
反射获取类的结构信息
第一组 Class 类方法
获取构造器
- getConstructor(Class... parameterTypes):返回此 Class 对象所表示的类的公共构造器,该构造器具有与指定参数类型相匹配的形参。
- getDeclaredConstructor(Class... parameterTypes):返回此 Class 对象表示的类声明的构造器,该构造器具有与指定参数类型相匹配的形参。
- getConstructors():返回一个包含此 Class 对象所表示的类的所有公共构造器的数组。
- getDeclaredConstructors():返回反映此 Class 对象表示的类声明的所有构造器的 Constructor 对象的数组。
获取方法
- getMethod(String name, Class... parameterTypes):返回此 Class 对象所表示的类或接口的特定公共成员方法,该方法由名称和参数类型确定。
- getDeclaredMethod(String name, Class... parameterTypes):返回此 Class 对象表示的类或接口声明的特定方法,该方法由名称和参数类型确定。
- getMethods():返回一个包含此 Class 对象所表示的类的所有公共成员方法的数组,包括其继承的公共方法。
- getDeclaredMethods():返回此 Class 对象表示的类或接口声明的所有方法的数组,包括私有、保护、默认(包)访问和公共方法,但不包括继承的方法。
获取字段
- getField(String name):返回此 Class 对象所表示的类或接口的指定的公共成员字段。
- getDeclaredField(String name):返回此 Class 对象表示的类或接口声明的指定字段。
- getFields():返回一个包含此 Class 对象所表示的类的所有公共成员字段的数组。
- getDeclaredFields():返回此 Class 对象表示的类或接口声明的所有字段的数组。
其他重要方法
- getName():以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)的名称。
- getSuperclass():返回表示此 Class 所表示的实体的超类的 Class。如果此 Class 表示 Object 类、一个接口、一个基本类型或 void,则返回 null。
- getInterfaces():确定此对象所表示的类或接口实现的接口。
- isArray():判定此 Class 对象是否表示一个数组类。
- isPrimitive():判定指定的 Class 对象是否表示一个基本类型。
- isAnnotation():如果此 Class 对象表示一个注解类型则返回 true。
- newInstance():创建此 Class 对象所表示的类的一个新实例。这个方法已经被弃用,因为它在类型检查方面存在潜在的安全问题。应该使用 Constructor.newInstance() 方法替代。
使用示例
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
public class ReflectionExample {
private int number;
private String text;
public ReflectionExample() {}
public ReflectionExample(int number, String text) {
this.number = number;
this.text = text;
}
public void printInfo() {
System.out.println("Number: " + number + ", Text: " + text);
}
public static void main(String[] args) {
try {
// 获取ReflectionExample类的Class对象
Class<?> clazz = ReflectionExample.class;
// 获取类的名称
System.out.println("Class Name: " + clazz.getName());
// 获取所有公共方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println("Method: " + method.getName());
}
// 获取所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("Field: " + field.getName());
}
// 获取所有构造器
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("Constructor: " + constructor);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
第二组 Field 类方法
- getName():返回字段的名称,例如"age"或"name"。
- getType():返回一个Class对象,该对象表示字段的类型,例如int.class、String.class等。
- getModifiers():以 int 形式返回修饰符 public 是 1,private 是 2 ,protected 是 4,static 是8,final 是 16 。你可以使用Modifier类来解析这些修饰符。
- isAccessible():检查字段是否可以被当前线程访问。对于私有字段,这个方法通常会返回false,除非你之前调用了setAccessible(true)方法。
- setAccessible(boolean flag):设置字段的访问权限。如果flag为true,则允许反射代码访问私有和受保护的字段。这通常用于绕过Java的访问控制检查。
- get(Object obj):从指定对象中获取字段的值。obj参数应该是包含该字段的实例(对于静态字段,可以是null)。
- set(Object obj, Object value):将字段的值设置为指定值。obj参数应该是包含该字段的实例(对于静态字段,可以是null),value参数是要设置的新值。字段的类型必须与value参数的类型兼容。
- getDeclaredField(String name):这不是Field类的方法,而是Class类的方法。它返回类中声明的指定名称的字段(不考虑修饰符),如果没有找到该字段,则抛出NoSuchFieldException。
import java.lang.reflect.Field;
public class FieldExample {
private String name = "John Doe";
public int age = 30;
public static void main(String[] args) {
try {
// 获取FieldExample类的Class对象
Class<?> clazz = FieldExample.class;
// 获取name字段的Field对象
Field nameField = clazz.getDeclaredField("name");
// 设置name字段为可访问
nameField.setAccessible(true);
// 创建FieldExample类的实例
FieldExample example = new FieldExample();
// 获取name字段的值
String nameValue = (String) nameField.get(example);
System.out.println("Name: " + nameValue);
// 修改name字段的值
nameField.set(example, "Jane Doe");
System.out.println("Updated Name: " + example.name);
// 获取age字段的Field对象并打印其值
Field ageField = clazz.getField("age");
int ageValue = (int) ageField.get(example);
System.out.println("Age: " + ageValue);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
第三组 Method类 方法
- getName():返回方法的名称,例如"myMethod"。
- getReturnType():返回一个Class对象,该对象表示方法的返回类型。
- getParameterTypes():返回一个Class数组,表示方法的参数类型。
- getParameterCount():返回方法的参数个数。
- getModifiers():以 int 形式返回修饰符 public 是 1,private 是 2 ,protected 是 4,static 是8,final 是 16。你可以使用Modifier类来解析这些修饰符。
- isAccessible():检查方法是否可以被当前线程访问。对于私有方法,这个方法通常会返回false,除非你之前调用了setAccessible(true)方法。
- setAccessible(boolean flag):设置方法的访问权限。如果flag为true,则允许反射代码访问私有和受保护的方法。这通常用于绕过Java的访问控制检查。
- invoke(Object obj, Object... args):在指定对象上调用该方法。obj参数应该是包含该方法的实例(对于静态方法,可以是null),args参数是传递给方法的参数值。方法的返回类型将被自动包装在Object中返回。
- getDeclaredMethod(String name, Class... parameterTypes):这不是Method类的方法,而是Class类的方法。它返回类中声明的指定名称和参数类型的方法(不考虑修饰符),如果没有找到该方法,则抛出NoSuchMethodException。
- getMethod(String name, Class... parameterTypes):这也是Class类的方法。它返回类中声明的可被当前线程访问的指定名称和参数类型的公共方法。如果没有找到该方法,或者方法不是公共的,则抛出NoSuchMethodException。
import java.lang.reflect.Method;
public class MethodExample {
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
private int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
try {
// 获取MethodExample类的Class对象
Class<?> clazz = MethodExample.class;
// 获取sayHello方法的Method对象
Method sayHelloMethod = clazz.getMethod("sayHello", String.class);
// 创建MethodExample类的实例
MethodExample example = new MethodExample();
// 调用sayHello方法
sayHelloMethod.invoke(example, "World");
// 获取add方法的Method对象(私有方法)
Method addMethod = clazz.getDeclaredMethod("add", int.class, int.class);
// 设置add方法为可访问
addMethod.setAccessible(true);
// 调用add方法并获取返回值
int result = (int) addMethod.invoke(example, 5, 3);
System.out.println("5 + 3 = " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
第四组 Constructor 类方法
获取构造器信息
- getName():返回构造器的名称。由于构造器的名称总是与类名相同,这个方法通常不是特别有用。
- getParameterTypes():返回一个 Class 对象数组,表示构造器的参数类型。
- getParameterCount():返回构造器接受的参数个数。
- getModifiers():返回构造器的修饰符,可以使用 Modifier 类来解析这些修饰符。
- isAccessible():检查构造器是否可以被当前线程访问。对于私有构造器,这个方法通常会返回 false,除非你之前调用了setAccessible(true)方法。
创建实例
- newInstance(Object... initargs):使用此 Constructor 对象表示的构造器,使用指定的初始化参数来创建和初始化新实例。这个方法在Java 9及以后的版本中被标记为过时(deprecated),因为它可能会抛出各种未检查的异常。建议使用 newInstanceWithArgs 方法或其他更安全的替代方法。
- newInstanceWithArgs(Object... args):这是Java 9引入的一个更安全的方法来创建实例,它会将任何由构造器抛出的异常封装在InvocationTargetException中。
设置访问权限
- setAccessible(boolean flag):设置构造器的访问权限。如果 flag 为 true,则允许反射代码访问私有和受保护的构造器。这通常用于绕过 Java 的访问控制检查。
示例代码
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionConstructorExample {
private int number;
private String text;
public ReflectionConstructorExample() {}
public ReflectionConstructorExample(int number, String text) {
this.number = number;
this.text = text;
}
@Override
public String toString() {
return "ReflectionConstructorExample{number=" + number + ", text='" + text + "'}";
}
public static void main(String[] args) {
try {
// 获取ReflectionConstructorExample类的Class对象
Class<?> clazz = ReflectionConstructorExample.class;
// 获取无参构造器
Constructor<?> noArgsConstructor = clazz.getConstructor();
// 使用无参构造器创建实例
ReflectionConstructorExample instance1 = (ReflectionConstructorExample) noArgsConstructor.newInstance();
System.out.println("Instance created with no-args constructor: " + instance1);
// 获取带参构造器
Constructor<?> argsConstructor = clazz.getConstructor(int.class, String.class);
// 使用带参构造器创建实例
ReflectionConstructorExample instance2 = (ReflectionConstructorExample) argsConstructor.newInstance(42, "Hello, World!");
System.out.println("Instance created with args constructor: " + instance2);
// 如果需要访问私有构造器,可以使用setAccessible(true)
// ...
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}