Рефлексия
Рефлексия (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
truegetSimpleName() возвращает короткое имя, а 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=120getAccessor() возвращает метод доступа к компоненту — тот самый 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. И помните: рефлексия обходит проверки компилятора — опечатку в имени метода вы увидите только при запуске. Поэтому в обычном коде вызывайте методы напрямую.