«1. Обзор

В этом кратком руководстве мы обсудим, как мы можем получить доступ к значению частного поля из другого класса в Java.

Прежде чем приступить к руководству, нам нужно понять, что модификатор закрытого доступа предотвращает случайное неправильное использование полей. Однако, если мы хотим получить к ним доступ, мы можем сделать это с помощью Reflection API.

2. Пример

Давайте определим пример класса Person с некоторыми приватными полями:

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. Доступ к приватным полям

Чтобы сделать любое приватное поле доступным, мы должны вызвать Field#setAccessible method:

Person person = new Person(); 
Field nameField = person.getClass().getDeclaredField("name"); 
nameField.setAccessible(true);

В приведенном выше примере мы сначала указываем поле, которое мы хотим получить — имя — с помощью метода Class#getDeclaredField. Затем мы делаем поле доступным, используя nameField.setAccessible(true).

4. Доступ к приватным полям-примитивам

Мы можем получить доступ к приватным полям-примитивам, используя методы Field#getXxx.

4.1. Доступ к целочисленным полям

Мы можем использовать методы getByte, getShort, getInt и getLong для доступа к полям 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);
}

Также можно выполнить автоупаковку с примитивными типами:

@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);
    Assertions.assertEquals(452002, pinCode);
}

Методы getXxx для примитивных типов данных также поддерживают расширение:

@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);
    Assertions.assertEquals(452002L, pinCode);
}

4.2. Доступ к полям с плавающим типом

Для доступа к полям с плавающей запятой и 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. Доступ к символьным полям

Для доступа к символьным полям мы можем использовать метод 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. Доступ к логическим полям

Точно так же мы можем использовать метод 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. Доступ к частным полям, являющимся объектами

Мы можем получить доступ к закрытым полям, которые являются объектами, с помощью поля #получить метод. Следует отметить, что общий метод get возвращает объект, поэтому нам нужно привести его к целевому типу, чтобы использовать значение:

@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);
}

6. Исключения

Теперь давайте обсудим исключения которые JVM может генерировать при доступе к закрытым полям.

6.1. IllegalArgumentException

JVM выдаст исключение IllegalArgumentException, если мы используем метод доступа getXxx, несовместимый с типом целевого поля. В нашем примере, если мы напишем nameField.getInt(person), JVM выдаст это исключение, поскольку поле имеет тип String, а не int или Integer:

@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));
}

Как мы уже видели, методы getXxx поддерживают расширение для примитивных типов. Важно отметить, что нам нужно указать правильную цель, чтобы расширение было успешным. В противном случае JVM выдает исключение IllegalArgumentException:

@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

JVM выдаст исключение IllegalAccessException, если мы попытаемся получить доступ к полю, не имеющему прав доступа. В приведенном выше примере, если мы не напишем оператор nameField.setAccessible(true), JVM выдаст исключение:

@Test
public void whenFieldNotSetAccessible_thenIllegalAccessException() 
  throws Exception {
    Person person = new Person();
    Field nameField = person.getClass().getDeclaredField("name");

    Assertions.assertThrows(IllegalAccessException.class, () -> nameField.get(person));
}

6.3. NoSuchFieldException

Если мы попытаемся получить доступ к полю, которого нет в классе Person, то JVM может сгенерировать исключение NoSuchFieldException:

Assertions.assertThrows(NoSuchFieldException.class,
  () -> person.getClass().getDeclaredField("firstName"));

6.4. NullPointerException

Наконец, как и следовало ожидать, JVM генерирует исключение NullPointerException, если мы передаем имя поля как null:

Assertions.assertThrows(NullPointerException.class,
  () -> person.getClass().getDeclaredField(null));

7. Заключение

В этом руководстве мы увидели, как мы можем получить доступ частные поля класса в другом классе. Мы также видели исключения, которые может генерировать JVM, и их причины.

Как всегда, полный код этого примера доступен на GitHub.