1. 概述
在上一篇文章中,我们介绍了如何通过 Java 反射读取其他类中的私有(private)字段值。但实际开发中,我们有时不仅需要读取,还需要修改字段值——比如在某些框架或工具类库中,目标类的字段不可见或无法直接访问。
本文将聚焦于如何使用 Java 的 Reflection API 来设置字段值,包括基本类型和对象类型。✅
提示:本文示例继续沿用上一篇中的
Person
类,结构如下(便于上下文理解):public class Person { private int age; private short uidNumber; private int pinCode; private long contactNumber; private float height; private double weight; private char gender; private boolean active; private String name; // getter 方法省略 }
2. 设置基本类型字段
对于基本类型字段,Java 反射提供了专用的 setXxx()
方法,比通用的 set()
更高效且类型安全。⚠️ 推荐优先使用。
2.1 设置整型字段
可以通过以下方法分别设置 byte
、short
、int
、long
类型字段:
setByte()
setShort()
setInt()
setLong()
@Test
public void whenSetIntegerFields_thenSuccess()
throws Exception {
Person person = new Person();
Field ageField = person.getClass()
.getDeclaredField("age");
ageField.setAccessible(true);
byte age = 26;
ageField.setByte(person, age);
Assertions.assertEquals(age, person.getAge());
Field uidNumberField = person.getClass()
.getDeclaredField("uidNumber");
uidNumberField.setAccessible(true);
short uidNumber = 5555;
uidNumberField.setShort(person, uidNumber);
Assertions.assertEquals(uidNumber, person.getUidNumber());
Field pinCodeField = person.getClass()
.getDeclaredField("pinCode");
pinCodeField.setAccessible(true);
int pinCode = 411057;
pinCodeField.setInt(person, pinCode);
Assertions.assertEquals(pinCode, person.getPinCode());
Field contactNumberField = person.getClass()
.getDeclaredField("contactNumber");
contactNumberField.setAccessible(true);
long contactNumber = 123456789L;
contactNumberField.setLong(person, contactNumber);
Assertions.assertEquals(contactNumber, person.getContactNumber());
}
✅ 支持自动拆箱(Unboxing)
即使传入的是包装类型(如 Integer
),setXxx()
方法也能自动拆箱:
@Test
public void whenDoUnboxing_thenSuccess()
throws Exception {
Person person = new Person();
Field pinCodeField = person.getClass()
.getDeclaredField("pinCode");
pinCodeField.setAccessible(true);
Integer pinCode = 411057;
pinCodeField.setInt(person, pinCode); // 自动拆箱
Assertions.assertEquals(pinCode, person.getPinCode());
}
✅ 支持窄化转换(Narrowing)
反射允许小范围类型赋值给大范围字段(如 short
→ int
),但反过来不行。下面这个例子是合法的:
@Test
public void whenDoNarrowing_thenSuccess()
throws Exception {
Person person = new Person();
Field pinCodeField = person.getClass()
.getDeclaredField("pinCode");
pinCodeField.setAccessible(true);
short pinCode = 4110;
pinCodeField.setInt(person, pinCode); // short → int,合法
Assertions.assertEquals(pinCode, person.getPinCode());
}
❌ 但如果你尝试把 int
值塞进 short
字段,且值超出范围,则会抛异常(见第 4 节)。
2.2 设置浮点型字段
对应 float
和 double
字段,使用:
setFloat()
setDouble()
@Test
public void whenSetFloatingTypeFields_thenSuccess()
throws Exception {
Person person = new Person();
Field heightField = person.getClass()
.getDeclaredField("height");
heightField.setAccessible(true);
float height = 6.1242f;
heightField.setFloat(person, height);
Assertions.assertEquals(height, person.getHeight());
Field weightField = person.getClass()
.getDeclaredField("weight");
weightField.setAccessible(true);
double weight = 75.2564;
weightField.setDouble(person, weight);
Assertions.assertEquals(weight, person.getWeight());
}
2.3 设置字符型字段
使用 setChar()
设置 char
类型字段:
@Test
public void whenSetCharacterFields_thenSuccess()
throws Exception {
Person person = new Person();
Field genderField = person.getClass()
.getDeclaredField("gender");
genderField.setAccessible(true);
char gender = 'M';
genderField.setChar(person, gender);
Assertions.assertEquals(gender, person.getGender());
}
2.4 设置布尔型字段
使用 setBoolean()
设置 boolean
字段:
@Test
public void whenSetBooleanFields_thenSuccess()
throws Exception {
Person person = new Person();
Field activeField = person.getClass()
.getDeclaredField("active");
activeField.setAccessible(true);
activeField.setBoolean(person, true);
Assertions.assertTrue(person.isActive());
}
简单粗暴,没啥可说的。
3. 设置对象类型字段
对于引用类型(如 String
、自定义对象等),统一使用通用的 Field.set(Object obj, Object value)
方法:
@Test
public void whenSetObjectFields_thenSuccess()
throws Exception {
Person person = new Person();
Field nameField = person.getClass()
.getDeclaredField("name");
nameField.setAccessible(true);
String name = "Umang Budhwar";
nameField.set(person, name);
Assertions.assertEquals(name, person.getName());
}
⚠️ 注意:这里传入的 value
必须与字段声明类型兼容,否则会抛 IllegalArgumentException
。
4. 常见异常处理
反射操作容易“翻车”,下面列出两个最常踩坑的异常。
4.1 IllegalArgumentException
当传入的值类型与字段类型不兼容时抛出。典型场景:
- 用
setInt()
给String
字段赋值 - 窄化转换失败(如
long
值太大,无法转为int
)
示例:尝试给 String
字段用 setInt()
赋值 ❌
@Test
public void givenInt_whenSetStringField_thenIllegalArgumentException()
throws Exception {
Person person = new Person();
Field nameField = person.getClass()
.getDeclaredField("name");
nameField.setAccessible(true);
Assertions.assertThrows(IllegalArgumentException.class, () -> nameField.setInt(person, 26));
}
另一个坑:虽然支持窄化,但如果值超出目标字段范围,也会失败。比如下面这个 long
值其实可以转成 int
,但如果 pinCodeField
是 short
类型且值超限,就会炸。
但注意:本例中 pinCode
是 int
,而传入的是 long
,JVM 不允许自动窄化(防止精度丢失),所以也会抛异常:
@Test
public void givenInt_whenSetLongField_thenIllegalArgumentException()
throws Exception {
Person person = new Person();
Field pinCodeField = person.getClass()
.getDeclaredField("pinCode");
pinCodeField.setAccessible(true);
long pinCode = 411057L;
Assertions.assertThrows(IllegalArgumentException.class, () -> pinCodeField.setLong(person, pinCode));
}
✅ 正确做法是先手动转类型:pinCodeField.setInt(person, (int) pinCode);
4.2 IllegalAccessException
如果你没调用 setAccessible(true)
就去访问私有字段,JVM 会直接拒绝并抛出此异常。
这是新手最容易忽略的点,尤其在单元测试里容易忘记模拟权限开放。
@Test
public void whenFieldNotSetAccessible_thenIllegalAccessException()
throws Exception {
Person person = new Person();
Field nameField = person.getClass()
.getDeclaredField("name");
// 忘记调用 setAccessible(true) ❌
Assertions.assertThrows(IllegalAccessException.class, () -> nameField.set(person, "Umang Budhwar"));
}
✅ 踩坑提示:所有 getDeclaredField()
拿到的私有成员,必须 setAccessible(true)
才能读写,否则权限不足。
5. 总结
本文系统讲解了如何通过 Java 反射设置各类字段值,涵盖:
- 基本类型:使用专用
setXxx()
方法更安全高效 - 对象类型:统一用
set()
方法 - 支持自动拆箱和窄化转换(但需小心溢出)
- 两个关键异常:
IllegalArgumentException
(类型不匹配)、IllegalAccessException
(权限不足)
反射虽强大,但也容易带来性能损耗和安全风险,建议仅在框架、序列化、测试工具等必要场景使用。生产代码中滥用反射,后期维护会哭的 😅
完整示例代码已托管至 GitHub:https://github.com/dev-example/tutorials/tree/master/core-java-modules/core-java-reflection-2