Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Рефлексия

Рефлексия (reflection) — возможность программы во время работы узнавать устройство своих классов: какие у них поля, методы, конструкторы, аннотации — и пользоваться ими, зная только имена. В обычном коде рефлексия почти не нужна, но на ней построены фреймворки: Spring создаёт объекты, JUnit находит тесты, библиотеки JSON читают поля объектов.

Объект Class

Всё начинается с объекта Class — описания класса. Его можно получить у любого объекта методом getClass() или прямо из имени класса через .class:

void main() {
    String text = "Привет";
 
    Class<?> type = text.getClass();
    IO.println(type.getSimpleName());
    IO.println(type.getName());
    IO.println(type == String.class);
}

Результат:

String
java.lang.String
true

getSimpleName() возвращает короткое имя, а getName() — полное, вместе с пакетом. Для каждого класса существует ровно один объект Class, поэтому их можно сравнивать через ==.

Поля и методы

getDeclaredFields() и getDeclaredMethods() возвращают поля и методы, объявленные в самом классе, включая закрытые. Их порядок не гарантирован, поэтому для вывода отсортируем по имени:

class User {
    private String name;
    private int age;
 
    public String greet() {
        return "Привет, " + name;
    }
 
    public int getAge() {
        return age;
    }
}
 
void main() {
    for (Field field : User.class.getDeclaredFields()) {
        IO.println("Поле: " + field.getType().getSimpleName() + " " + field.getName());
    }
 
    Arrays.stream(User.class.getDeclaredMethods())
            .map(Method::getName)
            .sorted()
            .forEach(name -> IO.println("Метод: " + name));
}

Результат (порядок полей может отличаться):

Поле: String name
Поле: int age
Метод: getAge
Метод: greet

Вызов метода по имени

Метод можно найти по имени и типам параметров, а затем вызвать через invoke. Первый аргумент invoke — объект, у которого вызываем метод, остальные — аргументы:

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
 
    public int multiply(int a, int b) {
        return a * b;
    }
}
 
void main() throws Exception {
    Calculator calc = new Calculator();
 
    for (String operation : List.of("add", "multiply")) {
        Method method = Calculator.class.getMethod(operation, int.class, int.class);
        Object result = method.invoke(calc, 6, 7);
        IO.println(operation + " = " + result);
    }
}

Результат:

add = 13
multiply = 42

Имя метода здесь — обычная строка. Его можно прочитать из файла настроек или из запроса пользователя: так работают маршрутизаторы веб-фреймворков.

Создание объекта

Объект тоже можно создать, зная только класс: getDeclaredConstructor находит конструктор по типам параметров, а newInstance вызывает его с аргументами:

record Report(String title) {
    Report {
        IO.println("Создан отчёт: " + title);
    }
}
 
void main() throws Exception {
    Class<?> type = Report.class;
    Object report = type.getDeclaredConstructor(String.class).newInstance("Продажи");
 
    IO.println(report);
}

Результат:

Создан отчёт: Продажи
Report[title=Продажи]

Компоненты записей

У записей есть метод getRecordComponents(): он возвращает компоненты в порядке объявления. С его помощью легко написать, например, простое превращение записи в строку вида «ключ=значение»:

record Product(String name, double price, int stock) {}
 
String describe(Record value) throws Exception {
    List<String> parts = new ArrayList<>();
    for (RecordComponent component : value.getClass().getRecordComponents()) {
        Object fieldValue = component.getAccessor().invoke(value);
        parts.add(component.getName() + "=" + fieldValue);
    }
    return String.join("; ", parts);
}
 
void main() throws Exception {
    IO.println(describe(new Product("Чай", 4.5, 120)));
}

Результат:

name=Чай; price=4.5; stock=120

getAccessor() возвращает метод доступа к компоненту — тот самый name() или price(). Так устроены библиотеки, которые превращают записи в JSON.

Частая ошибка

Метод getMethod ищет только открытые (public) методы. Метод без модификатора доступа он не найдёт и выбросит исключение:

class Greeter {
    String greet(String name) {
        return "Привет, " + name;
    }
}
 
void main() throws Exception {
    Method method = Greeter.class.getMethod("greet", String.class);
    IO.println(method.invoke(new Greeter(), "Анна"));
}

Результат:

Exception in thread "main" java.lang.NoSuchMethodException: Main$Greeter.greet(java.lang.String)
	at Main.main(Main.java:8)

Для любых методов класса, включая закрытые, используйте getDeclaredMethod. И помните: рефлексия обходит проверки компилятора — опечатку в имени метода вы увидите только при запуске. Поэтому в обычном коде вызывайте методы напрямую.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.