Вопросы 211-220 - Reflection API
Ответ: Reflection API (пакет java.lang.reflect и класс java.lang.Class) позволяет программе во время выполнения исследовать и изменять собственную структуру: узнавать классы, поля, методы, конструкторы, аннотации, создавать объекты и вызывать методы, имена которых неизвестны на этапе компиляции.
Class — описание класса или интерфейсаField — полеMethod — методConstructor — конструкторModifier — модификаторы доступа (public, static, final...)Proxy — динамические проксиClass.forName)Ответ: Есть три основных способа:
| Способ | Когда использовать |
|---|---|
String.class |
Тип известен на этапе компиляции. Работает и для примитивов: int.class |
obj.getClass() |
Есть экземпляр; вернёт фактический (рантайм) класс объекта |
Class.forName("java.lang.String") |
Имя класса известно только как строка. Выбрасывает ClassNotFoundException |
Class<String> c1 = String.class;
Object obj = "hello";
Class<?> c2 = obj.getClass(); // class java.lang.String
Class<?> c3 = Class.forName("java.util.ArrayList");
System.out.println(c1 == c2); // true: Class объект один на класс в загрузчике
Class.forName() инициализирует класс (выполняются статические блоки), а X.class — нет.
Ответ: У Class есть две группы методов — с префиксом getDeclared и без него.
| Метод | Что возвращает |
|---|---|
getMethods(), getFields() |
Только public члены, включая унаследованные |
getDeclaredMethods(), getDeclaredFields() |
Все члены, объявленные в этом классе (любой уровень доступа), без унаследованных |
getConstructors() |
Только public конструкторы |
getDeclaredConstructors() |
Все конструкторы класса |
class User {
private String name;
public int age;
private void secret() { }
public String getName() { return name; }
}
Class<User> c = User.class;
for (Field f : c.getDeclaredFields()) {
System.out.println(f.getName() + " : " + f.getType().getSimpleName()
+ " : " + Modifier.toString(f.getModifiers()));
}
// name : String : private
// age : int : public
c.getDeclaredMethods(); // secret, getName
c.getMethods(); // getName + публичные методы Object (toString, hashCode...)
Ответ: Нужно получить Constructor по типам параметров и вызвать newInstance().
class Person {
private final String name;
public Person() { this("unknown"); }
public Person(String name) { this.name = name; }
}
Class<Person> c = Person.class;
Person p1 = c.getDeclaredConstructor().newInstance(); // пустой конструктор
Person p2 = c.getDeclaredConstructor(String.class).newInstance("Ann"); // с параметром
Class.newInstance() объявлен устаревшим с Java 9 — он пробрасывает любые исключения конструктора, в том числе проверяемые, не объявляя ихInvocationTargetExceptionsetAccessible(true) (так ломают паттерн Singleton)NoSuchMethodExceptionОтвет: Находим Method по имени и типам параметров и вызываем invoke(объект, аргументы...).
class Calc {
public int sum(int a, int b) { return a + b; }
public static int twice(int x) { return x * 2; }
private String secret() { return "hidden"; }
}
Calc calc = new Calc();
Method sum = Calc.class.getMethod("sum", int.class, int.class);
Object r = sum.invoke(calc, 2, 3); // 5 (Integer)
Method twice = Calc.class.getMethod("twice", int.class);
Object r2 = twice.invoke(null, 4); // static: объект = null
Method secret = Calc.class.getDeclaredMethod("secret");
secret.setAccessible(true); // доступ к private
Object r3 = secret.invoke(calc); // "hidden"
NoSuchMethodException — метод с такой сигнатурой не найденIllegalAccessException — нет доступа (private без setAccessible)InvocationTargetException — вызванный метод сам бросил исключение; оригинал доступен через getCause()int.class, а не Integer.class — иначе метод не найдётся. Результат invoke всегда Object (с автоупаковкой).
Ответ: Через Field: методы get()/set() и типизированные getInt(), setInt() и т.д. Для private полей сначала вызывают setAccessible(true).
class Account {
private int balance = 100;
}
Account acc = new Account();
Field f = Account.class.getDeclaredField("balance");
f.setAccessible(true);
int value = (int) f.get(acc); // 100
f.set(acc, 500); // теперь balance == 500
getField() видит только public поля, getDeclaredField() — любые поля этого классаgetDeclaredField подкласса не найти — нужно подняться по getSuperclass()get/set первым аргументом передают nullfinal поле после setAccessible(true) изменить можно, но JIT мог уже подставить константу, поэтому результат непредсказуем. У static final, а также у полей record и hidden-классов изменение вызовет исключениеОтвет: setAccessible(true) отключает проверку модификаторов доступа для конкретного Field, Method или Constructor, поэтому можно обращаться к private членам. Проверка идёт при каждом вызове — отключить её для всего класса нельзя.
--illegal-access удалёнjava.base, закончится InaccessibleObjectExceptionopens в module-info.java или флагом запуска --add-opens java.base/java.lang=ALL-UNNAMED
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
// Java 17+: InaccessibleObjectException:
// Unable to make field private final byte[] java.lang.String.value accessible:
// module java.base does not "opens java.lang" to unnamed module
Ответ:
| Недостаток | Пояснение |
|---|---|
| Производительность | Поиск члена, проверки доступа и упаковка аргументов дают накладные расходы; JIT хуже оптимизирует такие вызовы. Заметно в горячем коде |
| Нет проверки при компиляции | Имена методов и полей — строки; опечатка или рефакторинг (переименование) приведёт к ошибке только в runtime |
| Нарушение инкапсуляции | Доступ к private членам ломает инварианты класса |
| Безопасность | Требуется разрешение ReflectPermission при включённом SecurityManager (он объявлен устаревшим с Java 17) |
| Сложность отладки | Обёрнутые исключения (InvocationTargetException), неочевидные стек-трейсы |
| GraalVM Native Image | Рефлексивные обращения нужно заранее описывать в конфигурации, так как анализ идёт на этапе сборки |
Method/Field и не искать их заново при каждом вызовеMethodHandle / VarHandle (java.lang.invoke) — они быстрее при многократном использованииОтвет: Динамический прокси — объект, который создаётся во время выполнения и реализует заданные интерфейсы. Все вызовы его методов перенаправляются в один метод InvocationHandler.invoke(), где можно добавить логику до и после вызова реального объекта.
interface Service {
String hello(String name);
}
Service real = name -> "Hello, " + name;
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class<?>[] { Service.class },
(p, method, args) -> {
System.out.println("before " + method.getName());
Object result = method.invoke(real, args);
System.out.println("after");
return result;
}
);
proxy.hello("Ann");
// before hello
// after
final классы и методы так проксировать нельзя@Transactional, @Cacheable, @Async)this внутри того же бина обходит прокси — поэтому @Transactional не срабатывает при самовызове.
Ответ: Рефлексия — основа большинства «магии» в Java-фреймворках.
| Фреймворк | Что делает через рефлексию |
|---|---|
| Spring | Сканирует классы с @Component, создаёт бины через конструкторы, внедряет зависимости в поля и конструкторы (@Autowired), создаёт прокси |
| Hibernate / JPA | Читает @Entity, @Column, заполняет поля сущностей при загрузке из БД, создаёт прокси для lazy-загрузки |
| Jackson | Находит геттеры, сеттеры и поля для сериализации JSON в объект и обратно |
| JUnit | Находит методы с @Test, @BeforeEach и вызывает их |
@Retention(RetentionPolicy.RUNTIME)
@interface Inject { }
class Controller {
@Inject
private Service service;
}
static void injectAll(Object target, Map<Class<?>, Object> beans) throws Exception {
for (Field f : target.getClass().getDeclaredFields()) {
if (f.isAnnotationPresent(Inject.class)) {
f.setAccessible(true);
f.set(target, beans.get(f.getType()));
}
}
}