Клас із приватним полем

Кожен з вас добре знає про модифікатори доступу полів. І якщо біля поля стоїть модифікатор private, то доступу ззовні у нас не буде.

public class Person {
  private int age;
  public String nickname;
  public Person(int age, String nickname) {
   this.age = age;
   this.nickname = nickname;
  }
}

Перевіримо доступність у нашому Main:

public class Main {
   public static void main(String[] args) {
     Person person = new Person();
     System.out.println(person.nickname);
    //System.out.println(person.age); нема доступу до поля
  }
}

Доступу до поля age у нас немає, але є рефлексія. За допомогою неї ми можемо отримати доступ до приватних полів та працювати з ними.

Отримання приватного поля в об'єкта через рефлексію

Давай отримаємо масив усіх полів нашого класу за допомогою методу getDeclaredFileds(). Він повертає клас Fileds, з яким ми будемо надалі працювати та перетворювати:

public static void main(String[] args) {
        Field[] fields = Person.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(fields);
        actualFieldNames.forEach(System.out::println);
    }

    static List<String> getFieldNames(Field[] fields) {
        List<String> fieldNames = new ArrayList<>();
        for (Field field : fields)
            fieldNames.add(Modifier.toString(field.getModifiers()) + " " + field.getName());
        return fieldNames;
    }
private age
public nickname

У методі getFieldNames ми отримуємо два поля з нашого класу. Метод getModifiers повертає нам модифікатор нашого поля, а getName — назву. Тепер давай спробуємо змінити та отримати доступ до цього поля. Спочатку спробуємо дістати дані з публічного поля:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    Person person = new Person(10, "javaRush");

    Field field = Person.class.getDeclaredField("nickname");
    String nickname = (String) field.get(person);
    System.out.println(nickname);

    System.out.println(person.nickname);
}
javaRush
javaRush

Доступ до поля і за допомогою рефлексії, і за допомогою звернення до об'єкта у нас є. От і чудово! Переходимо до приватного поля.

Змінюємо назву поля, яку ми хочемо отримати, на наше приватне поле age:

public static void main(String[]args)throws NoSuchFieldException, IllegalAccessException{
		Person person = new Person(10, "javaRush");

    Field field = Person.class.getDeclaredField("age");
    int age =(int)field.get(person);
    System.out.println(age);

    //System.out.println(person.age);
}

У нас немає доступу через створений екземпляр класу, тому використовуємо рефлексію та стикаємося з помилкою:

IllegalAccessException

У нас виникає IllegalAccessException. Давай перейдемо всередину і подивимося, що там:

Спробуємо розібратися.

Виняток IllegalAccessException виникає, коли застосунок намагається рефлективно створити екземпляр (окрім масиву), встановити чи отримати поле, або викликати метод, але в метода, який зараз виконується, нема доступу до визначення вказаного класу, поля, методу чи конструктора.

Також тут є способи, які викидають цей виняток. Щоб уникнути викидання такого винятку, ми будемо використовувати спеціальний метод для отримання доступу до приватного поля.

setAccessible(boolean flag)

Цей метод дозволяє уникнути перевірки доступу до поля чи методу класу через його модифікатор доступу. До методу ми можемо передати параметр true или false, який визначить, чи потрібні нам перевірки на доступ до поля. Спробуємо виправити наш код:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    Person person = new Person(10, "javaRush");

    Field field = Person.class.getDeclaredField("age");
    field.setAccessible(true);

    int age = (int) field.get(person);
    System.out.println("Поточне значення - " + age);
}

Дивимося на результат:

Поточне значення - 10

Чудово, ми отримали дані нашого класу. Давай заразом спробуємо змінити наше поле, встановивши в ньому нове значення:

public static void main(String[]args)throws NoSuchFieldException, IllegalAccessException{
Person person = new Person(10, "javaRush");

    Field field = Person.class.getDeclaredField("age");
    field.setAccessible(true);

    field.set(person, 19);

    int age =(int)field.get(person);
    System.out.println("Поточне значення - " + age);
}

Намагаємося змінити наше поле й отримуємо результат:

Поточне значення - 19

Намагаємося виставити setAccessible(false).

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    Person person = new Person(10, "javaRush");

    Field field = Person.class.getDeclaredField("age");

    field.setAccessible(true);
    field.set(person, 19);
    field.setAccessible(false);

    System.out.println("Поточне значення - " + field.get(person));
}

Після повернення доступу на false ми знову стикаємося з нашим винятком, коли намагаємося викликати метод get:

Тому будь обережніше, коли працюєш з private-полями, і пам'ятай, що рефлексія — дуже потужний інструмент для роботи!