Java反射入门:用简单案例理清核心用法
本文将结合完整的Java代码案例,从基础概念到实操细节,全面拆解Java反射机制的核心用法。内容涵盖Class对象获取、字段/构造器/方法的反射操作、暴力反射原理及避坑指南,适合Java初学者及需要巩固反射知识点的开发者。
一、反射机制核心概念
反射是Java动态性的核心体现,其核心价值在于:允许程序在运行时而非编译期,获取类的完整结构信息(包括字段、方法、构造器等),并能直接操作类的私有成员,打破了Java的封装限制。
反射的核心入口是 java.lang.Class 类——每个类在JVM中仅存在一个Class对象,它是访问类结构的“钥匙”。
1.1 获取Class对象的3种方式
日常开发中最常用第一种方式(全类名加载),后两种多用于已知类或对象的场景:
// 方式1:通过全类名加载(最常用,动态性最强)
Class clazz = Class.forName("com.qcby1.User");
// 方式2:通过类名.class(编译期确定,静态方式)
Class clazz2 = User.class;
// 方式3:通过对象.getClass()(需先创建对象,灵活性低)
User user = new User();
Class clazz3 = user.getClass();
二、核心实操:反射操作字段(Field)
字段反射是反射机制中最常用的场景之一,核心是区分 getDeclaredFields() 与 getFields() 的差异,以及私有字段的访问方式。
2.1 先看基础:User类字段定义
package com.qcby1;
public class User {
private String name = "zs"; // 私有字段
private String sex = "男"; // 私有字段
public Integer age = 18; // 公共字段
double height = 183; // 默认访问权限字段
protected String phone = "123456";// 受保护字段
}
2.2 获取字段的2类核心方法
|
方法 |
作用 |
是否包含私有成员 |
是否包含父类成员 |
|---|---|---|---|
|
getDeclaredFields() |
获取当前类的所有字段 |
是 |
否 |
|
getFields() |
获取当前类及父类的公共字段(public) |
否 |
是 |
2.3 代码案例:获取并操作字段
package com.qcby1;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
// 1. 获取Class对象
Class clazz = Class.forName("com.qcby1.User");
// 2. 获取无参构造器,创建对象(后续操作字段需用到实例)
User user = (User) clazz.getConstructor().newInstance();
// 3. 获取所有字段(含私有)并打印
Field[] allFields = clazz.getDeclaredFields();
System.out.println("所有字段:");
for (Field field : allFields) {
System.out.println(field);
}
// 4. 获取公共字段(仅public)并打印
Field[] publicFields = clazz.getFields();
System.out.println("\n公共字段:");
for (Field field : publicFields) {
System.out.println(field);
}
// 5. 操作指定字段:私有字段(name)
Field nameField = clazz.getDeclaredField("name");
// 关键:私有/受保护字段需开启暴力反射,关闭权限检查
nameField.setAccessible(true);
nameField.set(user, "ls"); // 赋值
System.out.println("\n私有字段name的值:" + nameField.get(user)); // 取值
// 6. 操作指定字段:公共字段(age)
Field ageField = clazz.getField("age");
ageField.set(user, 20); // 公共字段无需暴力反射
System.out.println("公共字段age的值:" + ageField.get(user));
// 7. 操作指定字段:受保护字段(phone)
Field phoneField = clazz.getDeclaredField("phone");
phoneField.setAccessible(true); // 受保护字段也需暴力反射
phoneField.set(user, "18999999999");
System.out.println("受保护字段phone的值:" + phoneField.get(user));
}
}
2.4 核心要点
a.获取指定字段用
getDeclaredField("字段名")(任意权限)或getField("字段名")(仅public)b.非public字段(private/protected/默认)必须通过
setAccessible(true)开启“暴力反射”,否则抛IllegalAccessException
c.setAccessible(true)仅在运行时临时关闭权限检查,不会修改类的原始访问修饰符。
三、反射操作构造器(Constructor)
构造器的反射核心是:通过“参数列表”区分重载的构造器(构造器名与类名一致,无法通过名称区分)。
3.1 核心方法对比
|
方法 |
作用 |
是否包含私有构造器 |
|---|---|---|
|
getDeclaredConstructors() |
获取当前类的所有构造器 |
是 |
|
getConstructors() |
获取当前类的公共构造器(public) |
否 |
3.2 代码案例:获取构造器并创建对象
package com.qcby1;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.qcby1.User");
// 1. 获取所有构造器(含私有)
Constructor[] allConstructors = clazz.getDeclaredConstructors();
System.out.println("所有构造器:");
for (Constructor constructor : allConstructors) {
System.out.println(constructor);
}
// 2. 获取无参构造器(public)并创建对象
Constructor publicNoArgConstructor = clazz.getConstructor();
User user1 = (User) publicNoArgConstructor.newInstance();
System.out.println("\n无参构造器创建的对象:" + user1);
// 3. 获取带参数的私有构造器(假设User有private User(String name))
Constructor privateConstructor = clazz.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true); // 私有构造器需暴力反射
User user2 = (User) privateConstructor.newInstance("ls");
System.out.println("私有构造器创建的对象:" + user2);
// 4. 获取带多个参数的构造器(假设User有User(Integer age, String sex))
Constructor multiParamConstructor = clazz.getDeclaredConstructor(Integer.class, String.class);
User user3 = (User) multiParamConstructor.newInstance(18, "女");
System.out.println("多参数构造器创建的对象:" + user3);
}
}
3.3 核心要点
a.构造器的唯一标识是“参数列表”(方法签名),获取指定构造器时必须传入参数类型(如
getDeclaredConstructor(String.class));b.通过
newInstance(参数值)调用构造器创建对象,参数顺序需与构造器定义一致;c.私有构造器必须开启暴力反射才能调用,否则抛
IllegalAccessException。
四、反射操作方法(Method)
方法反射的核心是“方法签名”(方法名+参数列表),这是区分重载方法的关键,操作流程与字段、构造器类似。
4.1 先看基础:User类方法定义
package com.qcby1;
public class User {
// 重载方法:方法名相同,参数列表不同
public void run(String name) {
System.out.println("公共带参run方法:" + name);
}
private void run() {
System.out.println("私有无参run方法");
}
void flay(Integer age, String phone) { // 默认权限
System.out.println("默认权限flay方法:" + age + "," + phone);
}
protected void jump() { // 受保护权限
System.out.println("受保护jump方法");
}
public void jump(String name) { // 重载方法
System.out.println("公共带参jump方法:" + name);
}
}
4.2 核心方法对比
|
方法 |
作用 |
是否包含私有方法 |
是否包含父类方法 |
|---|---|---|---|
|
getDeclaredMethods() |
获取当前类的所有方法 |
是 |
否 |
|
getMethods() |
获取当前类及父类的公共方法(public) |
否 |
是(如Object的toString()) |
4.3 代码案例:获取并调用方法
package com.qcby1;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.qcby1.User");
User user = (User) clazz.getConstructor().newInstance();
// 1. 获取所有方法(含私有)
Method[] allMethods = clazz.getDeclaredMethods();
System.out.println("所有方法:");
for (Method method : allMethods) {
System.out.println(method);
}
// 2. 调用公共带参run方法(public void run(String name))
Method runWithParam = clazz.getDeclaredMethod("run", String.class);
runWithParam.invoke(user, "张三"); // 传入对象和方法参数
// 3. 调用私有无参run方法(private void run())
Method runNoParam = clazz.getDeclaredMethod("run");
runNoParam.setAccessible(true); // 私有方法需暴力反射
runNoParam.invoke(user); // 无参方法无需传参数
// 4. 调用默认权限flay方法(void flay(Integer, String))
Method flayMethod = clazz.getDeclaredMethod("flay", Integer.class, String.class);
flayMethod.setAccessible(true); // 默认权限也需暴力反射
flayMethod.invoke(user, 18, "123456");
// 5. 调用重载的jump方法
Method jumpNoParam = clazz.getDeclaredMethod("jump");
jumpNoParam.setAccessible(true);
jumpNoParam.invoke(user);
Method jumpWithParam = clazz.getDeclaredMethod("jump", String.class);
jumpWithParam.invoke(user, "李四");
}
}
4.4 核心要点
a.获取指定方法时,必须传入“方法名+参数类型”(如
getDeclaredMethod("run", String.class)),否则无法区分重载方法;b.通过
invoke(对象, 参数值)调用方法,无参方法可省略参数或传null;c.非public方法(private/protected/默认)需开启暴力反射,否则抛
IllegalAccessException;
d.getMethods()会获取父类的public方法(如Object类的equals()、hashCode()),而getDeclaredMethods()不会。
五、新手必避的3个坑
-
用getField()/getMethod()获取私有成员:直接抛
NoSuchFieldException或NoSuchMethodException,需用getDeclaredXxx(); -
操作非public成员未开启暴力反射:抛
IllegalAccessException,记住:只要不是public,就需要setAccessible(true); -
获取重载方法未指定参数类型:抛
NoSuchMethodException,重载方法的核心是参数列表,必须明确指定。
六、总结
Java反射的核心逻辑可总结为3句话:
1.反射的入口是Class对象,通过 Class.forName(全类名) 动态加载类;
2.getDeclaredXxx() 获取所有成员(含私有),getXxx() 仅获取public成员(含父类);
3.操作非public成员必须开启暴力反射(setAccessible(true)),构造器和方法的唯一标识是参数列表。
更多推荐


所有评论(0)