1. 概述
本文将探讨在Java中,如何从一个类外部访问另一个类的 private
字段值。
需要明确的是,private
访问修饰符的设计初衷是为了防止字段被意外滥用,增强封装性。✅ 但在某些特殊场景下(比如单元测试、框架开发、序列化工具等),我们确实需要“破例”访问这些私有成员。此时,Java 的 反射(Reflection)API 就派上用场了。⚠️ 注意:这种操作绕过了编译期检查,属于“黑科技”,生产环境慎用。
2. 示例类定义
我们先定义一个 Person
类,包含多个 private
字段,用于后续演示:
public class Person {
private String name = "John";
private byte age = 30;
private short uidNumber = 5555;
private int pinCode = 452002;
private long contactNumber = 123456789L;
private float height = 6.1242f;
private double weight = 75.2564;
private char gender = 'M';
private boolean active = true;
// getters and setters
}
3. 使私有字段可访问
要读取 private
字段,核心是调用 Field#setAccessible(true)
方法,告诉JVM:“我知道我在做什么,请放行”。
Person person = new Person();
Field nameField = person.getClass().getDeclaredField("name");
nameField.setAccessible(true);
getDeclaredField("name")
:获取当前类中声明的指定字段(不包括继承字段)setAccessible(true)
:暴力开启访问权限,绕过private
限制 ✅
⚠️ 注意:setAccessible(true)
会触发安全管理器检查(SecurityManager),在某些受限环境(如Applet、JEE容器)可能被禁止。
4. 读取私有的基本类型字段
对于基本类型字段,Java反射提供了专用的 getXxx()
方法,性能更高且自动处理类型转换。
4.1 读取整型字段
使用对应方法读取 byte
、short
、int
、long
类型:
@Test
public void whenGetIntegerFields_thenSuccess()
throws Exception {
Person person = new Person();
Field ageField = person.getClass().getDeclaredField("age");
ageField.setAccessible(true);
byte age = ageField.getByte(person);
Assertions.assertEquals(30, age);
Field uidNumberField = person.getClass().getDeclaredField("uidNumber");
uidNumberField.setAccessible(true);
short uidNumber = uidNumberField.getShort(person);
Assertions.assertEquals(5555, uidNumber);
Field pinCodeField = person.getClass().getDeclaredField("pinCode");
pinCodeField.setAccessible(true);
int pinCode = pinCodeField.getInt(person);
Assertions.assertEquals(452002, pinCode);
Field contactNumberField = person.getClass().getDeclaredField("contactNumber");
contactNumberField.setAccessible(true);
long contactNumber = contactNumberField.getLong(person);
Assertions.assertEquals(123456789L, contactNumber);
}
反射还支持两种便捷操作:
✅ 自动装箱(Autoboxing):可以直接获取包装类型
@Test
public void whenDoAutoboxing_thenSuccess()
throws Exception {
Person person = new Person();
Field pinCodeField = person.getClass().getDeclaredField("pinCode");
pinCodeField.setAccessible(true);
Integer pinCode = pinCodeField.getInt(person); // int → Integer
Assertions.assertEquals(452002, pinCode);
}
✅ 类型扩展(Widening):小范围类型可自动转为更大范围的基本类型
@Test
public void whenDoWidening_thenSuccess()
throws Exception {
Person person = new Person();
Field pinCodeField = person.getClass().getDeclaredField("pinCode");
pinCodeField.setAccessible(true);
Long pinCode = pinCodeField.getLong(person); // int → long → Long
Assertions.assertEquals(452002L, pinCode);
}
❌ 但反过来不行!比如用 getInt()
读 long
字段会抛异常,见后文异常章节。
4.2 读取浮点类型字段
float
和 double
分别使用 getFloat()
和 getDouble()
:
@Test
public void whenGetFloatingTypeFields_thenSuccess()
throws Exception {
Person person = new Person();
Field heightField = person.getClass().getDeclaredField("height");
heightField.setAccessible(true);
float height = heightField.getFloat(person);
Assertions.assertEquals(6.1242f, height);
Field weightField = person.getClass().getDeclaredField("weight");
weightField.setAccessible(true);
double weight = weightField.getDouble(person);
Assertions.assertEquals(75.2564, weight);
}
4.3 读取字符类型字段
char
类型使用 getChar()
:
@Test
public void whenGetCharacterFields_thenSuccess()
throws Exception {
Person person = new Person();
Field genderField = person.getClass().getDeclaredField("gender");
genderField.setAccessible(true);
char gender = genderField.getChar(person);
Assertions.assertEquals('M', gender);
}
4.4 读取布尔类型字段
boolean
类型使用 getBoolean()
:
@Test
public void whenGetBooleanFields_thenSuccess()
throws Exception {
Person person = new Person();
Field activeField = person.getClass().getDeclaredField("active");
activeField.setAccessible(true);
boolean active = activeField.getBoolean(person);
Assertions.assertTrue(active);
}
5. 读取私有的对象类型字段
对于对象类型(如 String
),统一使用 Field.get(Object)
方法,返回 Object
类型,需手动强转:
@Test
public void whenGetObjectFields_thenSuccess()
throws Exception {
Person person = new Person();
Field nameField = person.getClass().getDeclaredField("name");
nameField.setAccessible(true);
String name = (String) nameField.get(person); // 强转必不可少
Assertions.assertEquals("John", name);
}
⚠️ 注意:如果字段为 null
,get()
返回 null
,记得判空,避免踩坑 NullPointerException
。
6. 常见异常分析
反射操作不安全,JVM会在运行时抛出多种异常,提前了解可少走弯路。
6.1 IllegalArgumentException
当使用的 getXxx()
方法与字段实际类型不兼容时抛出。
❌ 示例:试图用 getInt()
读取 String
字段:
@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.getInt(person));
}
⚠️ 即使支持类型扩展,也必须“方向正确”。例如 long
字段不能用 getInt()
读:
@Test
public void givenInt_whenGetLongField_thenIllegalArgumentException()
throws Exception {
Person person = new Person();
Field contactNumberField = person.getClass().getDeclaredField("contactNumber");
contactNumberField.setAccessible(true);
Assertions.assertThrows(IllegalArgumentException.class, () -> contactNumberField.getInt(person));
}
6.2 IllegalAccessException
未调用 setAccessible(true)
时尝试访问私有字段,权限不足:
@Test
public void whenFieldNotSetAccessible_thenIllegalAccessException()
throws Exception {
Person person = new Person();
Field nameField = person.getClass().getDeclaredField("name");
// 忘记 setAccessible(true) ❌
Assertions.assertThrows(IllegalAccessException.class, () -> nameField.get(person));
}
6.3 NoSuchFieldException
字段名拼写错误或字段不存在:
@Test
public void whenFieldNotExist_thenNoSuchFieldException()
throws Exception {
Person person = new Person();
Assertions.assertThrows(NoSuchFieldException.class,
() -> person.getClass().getDeclaredField("firstName"));
}
6.4 NullPointerException
传入的字段名为 null
:
@Test
public void whenFieldNameIsNull_thenNullPointerException()
throws Exception {
Person person = new Person();
Assertions.assertThrows(NullPointerException.class,
() -> person.getClass().getDeclaredField(null));
}
7. 总结
- ✅ 使用
Field.setAccessible(true)
可突破private
限制 - ✅ 基本类型优先用
getByte()
、getInt()
等专用方法,性能更好 - ✅ 支持自动装箱和类型扩展(仅限 widening)
- ✅ 对象类型用
get()
+ 强转 - ❌ 操作不当会抛出
IllegalArgumentException
、IllegalAccessException
等异常 - ⚠️ 反射破坏封装,仅建议用于测试、框架、调试等场景,生产代码中慎用
完整示例代码已托管至 GitHub:https://github.com/yourname/java-reflection-demo