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 读取整型字段

使用对应方法读取 byteshortintlong 类型:

@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 读取浮点类型字段

floatdouble 分别使用 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);
}

⚠️ 注意:如果字段为 nullget() 返回 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() + 强转
  • ❌ 操作不当会抛出 IllegalArgumentExceptionIllegalAccessException 等异常
  • ⚠️ 反射破坏封装,仅建议用于测试、框架、调试等场景,生产代码中慎用

完整示例代码已托管至 GitHub:https://github.com/yourname/java-reflection-demo


原始标题:Reading the Value of 'private' Fields from a Different Class in Java

« 上一篇: Java Weekly, 第342期