文章目录
- 引入
- 类加载过程
- 1. 通过 new 创建对象
- 2. 通过反射创建对象
- 2.1 触发加载但不初始化
- 2.2 按需触发初始化
- 2.3 选择性初始化控制
- 核心用法
- 示例
- 1. 通过无参构造函数创建实例对象
- 2. 通过有参构造函数创建实例对象
- 3. 反射通过私有构造函数创建对象, 破坏单例模式
- 4. 通过反射获得类的public属性值, 演示getField与getDeclaredField两者的区别
引入
当我们刚接触java语言的时候, 我们最常写的代码应该就是初始化某个对象, 然后调用该 对象的方法。 如:
MyClass obj = new MyClass();
obj.doSth();
上面的这种用法的前提是, 我们在写代码的时候已经确定要去创建MyClass类的具体实例对象。
那如果我们想在代码运行的时候才去指定具体对象的类(比如根据传入的参数名称确定创建的类名),普通的硬编码方式将无法实现需求 ;反射登场了。
反射为什么可以实现呢, 这个就要先介绍一下类加载的过程了。
类加载过程
- 加载(Loading)
JVM将类的字节码文件(.class)加载到内存,创建Class对象。 - 链接(Linking)
- 验证:确保字节码符合规范。
- 准备:为静态变量分配内存并赋予默认值(如int初始化为0)。
- 解析:将符号引用转换为直接引用。
- 初始化(Initialization)
执行类的静态代码块(static {})和静态变量显式赋值。
1. 通过 new 创建对象
new关键字会直接触发类的完整加载、链接和初始化过程:
- 若类未加载:
- 立即执行加载、链接,完成后强制触发类的初始化(执行static代码块和初始化静态变量)。 - 初始化完成后:调用构造函数创建对象。
示例:
// 第一次使用类时触发初始化
MyClass obj = new MyClass();
特点:
- 类必须在编译时已知(硬编码依赖)。
- 初始化在对象创建时必定发生。
2. 通过反射创建对象
通过反射(Class.newInstance()或Constructor.newInstance())创建对象时,允许分阶段控制类的加载过程:
2.1 触发加载但不初始化
使用ClassLoader.loadClass()可加载类但不初始化:
ClassLoader loader = MyClass.class.getClassLoader();
Class<?> clazz = loader.loadClass("MyClass"); // 仅加载和链接,不初始化
此时尚未执行静态代码块或静态变量显式赋值。
2.2 按需触发初始化
在首次需要初始化时才触发(如反射调用newInstance()):
Object obj = clazz.newInstance(); // 触发初始化 → 执行static代码块
2.3 选择性初始化控制
通过Class.forName可指定是否初始化:
public class Main {
public static void main(String[] args) throws Exception {
// 反射示例:
ClassLoader loader = MyClass.class.getClassLoader();
// 加载类但不初始化(第三个参数为类加载器)
System.out.println("加载类但不初始化1...");
Class<?> clazz2 = Class.forName("com.test.galaxy.MyClass", false, loader);
// 加载类但不初始化
System.out.println("加载类但不初始化2...");
Class<?> clazz = loader.loadClass("com.test.galaxy.MyClass"); // 无输出
// 触发初始化前,类的静态代码块仍未执行
System.out.println("准备创建对象...");
Object obj = clazz.newInstance(); // 输出:静态代码块执行!
// 加载类同时触发初始化
System.out.println("加载类同时触发初始化...");
Class<?> clazz1 = Class.forName("com.test.galaxy.MyClass2");
}
}
class MyClass {
static {
System.out.println("静态代码块执行!"); // 初始化触发
}
}
class MyClass2 {
static {
System.out.println("静态代码块2执行!"); // 初始化触发
}
}
特点:
- 类的加载步骤可拆分(加载、链接、初始化分开触发)。
- 初始化在需要时才发生(如通过newInstance())。
- 灵活支持运行时动态加载类(例如插件化架构)。
核心用法
反射允许程序在运行时动态获取类的信息并操作类或对象。核心类是 Class,关键操作包括:
- 动态创建对象(newInstance())
- 调用方法(method.invoke())
- 访问/修改字段(field.get()/set())
示例
1. 通过无参构造函数创建实例对象
import java.lang.reflect.Constructor;
public class ReflectionExample1 {
public static void main(String[] args) {
try {
// 1. 获取Class对象(触发类加载,可能初始化)
Class<?> clazz = Class.forName("com.test.galaxy.User");
// 2. 获取无参构造方法(需处理异常)
Constructor<?> constructor = clazz.getDeclaredConstructor();
// 3. 调用newInstance()创建实例(无参数)
Object instance = constructor.newInstance();
System.out.println("实例创建成功:" + instance.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class User {
public User() {
System.out.println("无参构造函数被调用!");
}
}
关键说明
- Class.forName():动态加载类,默认触发初始化。
- getDeclaredConstructor():传入空参数类型列表表示获取无参构造方法。
- 私有构造方法处理:若构造函数是私有(private),需调用 constructor.setAccessible(true) 解除访问限制。
2. 通过有参构造函数创建实例对象
import java.lang.reflect.Constructor;
public class ReflectionExample2 {
public static void main(String[] args) {
try {
// 1. 获取Class对象(注意使用全限定类名)
Class<?> clazz = Class.forName("com.test.galaxy.User2");
// 2. 指定参数类型列表,获取有参构造方法
Class<?>[] paramTypes = {String.class, int.class}; // 参数类型顺序严格匹配
Constructor<?> constructor = clazz.getDeclaredConstructor(paramTypes);
// 3. 传递参数值实例化对象
Object[] initArgs = {"张三", 25}; // 参数值顺序与类型列表一致
Object instance = constructor.newInstance(initArgs);
System.out.println("实例创建成功:" + instance.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class User2 {
private String name;
private int age;
public User2(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造函数被调用!name=" + name + ", age=" + age);
}
}
关键说明
- 参数类型匹配:必须精确指定参数类型(如 int.class 不能写作 Integer.class)。
- 参数值顺序:传入的参数值顺序需与声明时一致。
- 可变长参数处理:若构造方法参数为可变长度(如 String…),类型写为 String[].class。
3. 反射通过私有构造函数创建对象, 破坏单例模式
import java.lang.reflect.Constructor;
class Singleton {
private static Singleton instance;
private Singleton() {
// 私有构造函数
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class ReflectionExample3 {
public static void main(String[] args) {
try {
// 通过正常方式获取单例对象
Singleton instance1 = Singleton.getInstance();
System.out.println("正常实例:" + instance1);
// 方式 1:通过反射创建新实例(直接访问构造函数)
Class<Singleton> clazz = Singleton.class;
Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); // 访问私有构造函数
Singleton instance2 = constructor.newInstance();
// 方式 2:通过反射多次创建实例(动态控制)
for (int i = 0; i < 3; i++) {
Constructor<Singleton> ctor = clazz.getDeclaredConstructor();
ctor.setAccessible(true);
Singleton instance = ctor.newInstance();
System.out.println("反射实例 " + (i+1) + ": " + instance);
}
// 验证两个实例是否相同
System.out.println("instance1 == instance2 ? " + (instance1 == instance2));
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. 通过反射获得类的public属性值, 演示getField与getDeclaredField两者的区别
- getField() 的特点
- 只能获取 当前类及继承链中声明为 public 的属性;
- 无法获取非 public 属性;
- 可以直接访问继承的父类 public 属性。
- getDeclaredField() 的特点
- 能获取 当前类中声明的所有属性(包括 private/protected/public);
- 无法获取父类声明的属性;
- 访问非 public 属性需通过 setAccessible(true)。
import java.lang.reflect.Field;
class Parent {
public String parentPublicField = "Parent-Public";
private String parentPrivateField = "Parent-Private";
}
class Child extends Parent {
public String childPublicField = "Child-Public";
private String childPrivateField = "Child-Private";
}
public class ReflectionExample4 {
public static void main(String[] args) {
Child child = new Child();
Class<?> clazz = Child.class;
try {
// ======================= 使用 getField() ========================
// 1. 获取子类的 public 属性(成功)
Field childPublicField = clazz.getField("childPublicField");
System.out.println("[getField] 子类 public 属性: " + childPublicField.get(child));
// 2. 获取父类的 public 属性(成功)
Field parentPublicField = clazz.getField("parentPublicField");
System.out.println("[getField] 父类 public 属性: " + parentPublicField.get(child));
// 3. 尝试获取子类的 private 属性(失败,触发异常)
clazz.getField("childPrivateField");
} catch (Exception e) {
System.err.println("[getField 失败] " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
try {
// ================== 使用 getDeclaredField() ======================
// 1. 获取子类的 public 属性(成功)
Field childPublicDeclaredField = clazz.getDeclaredField("childPublicField");
System.out.println("[getDeclaredField] 子类 public 属性: " + childPublicDeclaredField.get(child));
// 2. 获取子类的 private 属性(需解除访问限制)
Field childPrivateDeclaredField = clazz.getDeclaredField("childPrivateField");
childPrivateDeclaredField.setAccessible(true); // 强制访问私有属性
System.out.println("[getDeclaredField] 子类 private 属性: " + childPrivateDeclaredField.get(child));
// 3. 尝试获取父类的属性(失败,无论是否是 public)
clazz.getDeclaredField("parentPublicField");
} catch (Exception e) {
System.err.println("[getDeclaredField 失败] " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
}
}