2.x. reflection
reflection
Материалы
Reflection (Рефлексия)
Reflection — механизм, позволяющий исследовать и модифицировать структуру и поведение программы во время выполнения. С помощью рефлексии можно анализировать классы, создавать объекты, вызывать методы и изменять поля — даже приватные.
Зачем нужна рефлексия
Рефлексия используется когда структура классов неизвестна на этапе компиляции:
- Фреймворки — Spring, Hibernate, JUnit используют рефлексию для инъекции зависимостей, маппинга объектов, запуска тестов
- Сериализация — Jackson, Gson анализируют поля объектов для преобразования в JSON
- IDE и инструменты — автодополнение, отладчики, анализаторы кода
- Плагины — загрузка и использование классов, неизвестных во время компиляции
- ORM — создание объектов и заполнение полей из базы данных
Получение объекта Class
Class<T> — входная точка в Reflection API. Три способа получить:
// 1. Через литерал .class (известен во время компиляции)
Class<String> stringClass = String.class;
Class<int[]> intArrayClass = int[].class;
// 2. Через объект (getClass())
String str = "Hello";
Class<?> cls = str.getClass();
// 3. По имени класса (динамическая загрузка)
Class<?> cls = Class.forName("java.util.ArrayList");
Class<?> nested = Class.forName("java.util.Map$Entry"); // Вложенный класс
// Для примитивов
Class<Integer> intWrapper = Integer.class;
Class<Integer> intPrimitive = Integer.TYPE; // int.class
Class<Void> voidClass = Void.TYPE; // void.class
Исследование класса
Базовая информация
Class<?> cls = ArrayList.class;
// Имя
String name = cls.getName(); // "java.util.ArrayList"
String simpleName = cls.getSimpleName(); // "ArrayList"
String canonical = cls.getCanonicalName(); // "java.util.ArrayList"
// Пакет и модуль
Package pkg = cls.getPackage(); // java.util
Module module = cls.getModule(); // java.base
// Тип класса
boolean isInterface = cls.isInterface();
boolean isEnum = cls.isEnum();
boolean isRecord = cls.isRecord(); // Java 16+
boolean isAnnotation = cls.isAnnotation();
boolean isArray = cls.isArray();
boolean isPrimitive = cls.isPrimitive();
boolean isAnonymous = cls.isAnonymousClass();
boolean isLocal = cls.isLocalClass();
boolean isMember = cls.isMemberClass();
boolean isSynthetic = cls.isSynthetic(); // Сгенерирован компилятором
// Модификаторы
int modifiers = cls.getModifiers();
boolean isPublic = Modifier.isPublic(modifiers);
boolean isAbstract = Modifier.isAbstract(modifiers);
boolean isFinal = Modifier.isFinal(modifiers);
Иерархия классов
Class<?> cls = ArrayList.class;
// Суперкласс
Class<?> superclass = cls.getSuperclass(); // AbstractList
// Интерфейсы (только непосредственно реализуемые)
Class<?>[] interfaces = cls.getInterfaces();
// [List, RandomAccess, Cloneable, Serializable]
// Все интерфейсы включая унаследованные
Set<Class<?>> allInterfaces = new HashSet<>();
for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
allInterfaces.addAll(Arrays.asList(c.getInterfaces()));
}
// Проверка наследования
boolean isList = List.class.isAssignableFrom(cls); // true
boolean isInstance = cls.isInstance(new ArrayList<>()); // true
Работа с полями (Field)
Получение полей
Class<?> cls = Person.class;
// Только public поля (включая унаследованные)
Field[] publicFields = cls.getFields();
// Все поля класса (включая private, но без унаследованных)
Field[] allFields = cls.getDeclaredFields();
// Конкретное поле
Field nameField = cls.getDeclaredField("name");
Field idField = cls.getField("id"); // Только public
Информация о поле
Field field = Person.class.getDeclaredField("name");
String name = field.getName(); // "name"
Class<?> type = field.getType(); // String.class
int modifiers = field.getModifiers();
boolean isPrivate = Modifier.isPrivate(modifiers);
boolean isStatic = Modifier.isStatic(modifiers);
boolean isFinal = Modifier.isFinal(modifiers);
boolean isTransient = Modifier.isTransient(modifiers);
boolean isVolatile = Modifier.isVolatile(modifiers);
// Для generic-полей
Type genericType = field.getGenericType();
// Например: List<String> → ParameterizedType
Чтение и запись значений
class Person {
private String name = "John";
private static int count = 0;
}
Person person = new Person();
Field nameField = Person.class.getDeclaredField("name");
// Для private полей нужно открыть доступ
nameField.setAccessible(true);
// Чтение
String value = (String) nameField.get(person); // "John"
// Запись
nameField.set(person, "Alice");
// Статические поля — передаём null вместо объекта
Field countField = Person.class.getDeclaredField("count");
countField.setAccessible(true);
int count = (int) countField.get(null);
countField.set(null, 42);
Работа с final полями
class Config {
private final String value = "original";
}
Config config = new Config();
Field field = Config.class.getDeclaredField("value");
field.setAccessible(true);
// Работает, но опасно!
field.set(config, "modified");
System.out.println(field.get(config)); // "modified"
Предупреждение: Модификация
finalполей через рефлексию — плохая практика. JVM может оптимизировать доступ к final полям, и изменения могут не быть видны. Начиная с Java 12 некоторые поля защищены от рефлексии.
Работа с методами (Method)
Получение методов
Class<?> cls = String.class;
// Только public методы (включая унаследованные)
Method[] publicMethods = cls.getMethods();
// Все методы класса (включая private, без унаследованных)
Method[] declaredMethods = cls.getDeclaredMethods();
// Конкретный метод (имя + типы параметров)
Method substring = cls.getMethod("substring", int.class, int.class);
Method privateMethod = cls.getDeclaredMethod("secretMethod");
Информация о методе
Method method = String.class.getMethod("substring", int.class, int.class);
String name = method.getName(); // "substring"
Class<?> returnType = method.getReturnType(); // String.class
Class<?>[] paramTypes = method.getParameterTypes(); // [int.class, int.class]
Class<?>[] exceptions = method.getExceptionTypes();
int modifiers = method.getModifiers();
// Параметры с именами (если скомпилировано с -parameters)
Parameter[] params = method.getParameters();
for (Parameter p : params) {
System.out.println(p.getName() + ": " + p.getType());
}
// Информация о generic
Type genericReturnType = method.getGenericReturnType();
Type[] genericParamTypes = method.getGenericParameterTypes();
// Аннотации
Annotation[] annotations = method.getAnnotations();
boolean isDeprecated = method.isAnnotationPresent(Deprecated.class);
// Специальные свойства
boolean isVarArgs = method.isVarArgs();
boolean isBridge = method.isBridge(); // Мост для generic
boolean isSynthetic = method.isSynthetic(); // Сгенерирован компилятором
boolean isDefault = method.isDefault(); // default в интерфейсе
Вызов методов
class Calculator {
public int add(int a, int b) {
return a + b;
}
private String secret() {
return "secret";
}
public static double pi() {
return 3.14159;
}
}
Calculator calc = new Calculator();
// Вызов public метода
Method addMethod = Calculator.class.getMethod("add", int.class, int.class);
int result = (int) addMethod.invoke(calc, 10, 20); // 30
// Вызов private метода
Method secretMethod = Calculator.class.getDeclaredMethod("secret");
secretMethod.setAccessible(true);
String secret = (String) secretMethod.invoke(calc); // "secret"
// Вызов static метода — передаём null
Method piMethod = Calculator.class.getMethod("pi");
double pi = (double) piMethod.invoke(null); // 3.14159
Вызов методов с varargs
class Formatter {
public String format(String pattern, Object... args) {
return String.format(pattern, args);
}
}
Method formatMethod = Formatter.class.getMethod("format", String.class, Object[].class);
Formatter formatter = new Formatter();
// Важно: varargs передаётся как массив
String result = (String) formatMethod.invoke(formatter,
"Hello, %s! You have %d messages.",
new Object[]{"Alice", 5});
Работа с конструкторами (Constructor)
Получение конструкторов
Class<?> cls = ArrayList.class;
// Только public конструкторы
Constructor<?>[] publicConstructors = cls.getConstructors();
// Все конструкторы (включая private)
Constructor<?>[] allConstructors = cls.getDeclaredConstructors();
// Конкретный конструктор
Constructor<?> defaultCtor = cls.getConstructor();
Constructor<?> capacityCtor = cls.getConstructor(int.class);
Constructor<?> collectionCtor = cls.getConstructor(Collection.class);
Создание объектов
class Person {
private String name;
private int age;
public Person() {
this("Unknown", 0);
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name) {
this(name, 0);
}
}
// Через Class.newInstance() — deprecated в Java 9+
// Person p = (Person) Person.class.newInstance();
// Через Constructor (рекомендуемый способ)
Constructor<Person> ctor = Person.class.getConstructor(String.class, int.class);
Person person = ctor.newInstance("Alice", 25);
// Private конструктор
Constructor<Person> privateCtor = Person.class.getDeclaredConstructor(String.class);
privateCtor.setAccessible(true);
Person p2 = privateCtor.newInstance("Bob");
Работа с массивами (Array)
import java.lang.reflect.Array;
// Создание массива
int[] intArray = (int[]) Array.newInstance(int.class, 10);
String[][] matrix = (String[][]) Array.newInstance(String.class, 3, 4);
// Доступ к элементам
Array.set(intArray, 0, 42);
int value = Array.getInt(intArray, 0); // 42
Array.set(matrix, 0, new String[]{"a", "b", "c", "d"});
// Информация о массиве
int length = Array.getLength(intArray); // 10
Class<?> componentType = intArray.getClass().getComponentType(); // int.class
// Проверка типа массива
boolean isArray = intArray.getClass().isArray(); // true
Работа с аннотациями
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@interface MyAnnotation {
String value();
int priority() default 0;
}
@MyAnnotation(value = "test", priority = 5)
class AnnotatedClass {
@MyAnnotation("field")
private String name;
@MyAnnotation("method")
public void doSomething() {}
}
// На классе
MyAnnotation classAnno = AnnotatedClass.class.getAnnotation(MyAnnotation.class);
String value = classAnno.value(); // "test"
int priority = classAnno.priority(); // 5
// На поле
Field field = AnnotatedClass.class.getDeclaredField("name");
MyAnnotation fieldAnno = field.getAnnotation(MyAnnotation.class);
// На методе
Method method = AnnotatedClass.class.getMethod("doSomething");
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation methodAnno = method.getAnnotation(MyAnnotation.class);
}
// Все аннотации
Annotation[] allAnnotations = AnnotatedClass.class.getAnnotations();
Annotation[] declaredAnnotations = AnnotatedClass.class.getDeclaredAnnotations();
Работа с Generic-типами
class Container<T> {
private List<String> strings;
private Map<String, List<Integer>> complex;
public <E> E transform(T input) { return null; }
}
// Generic поля
Field stringsField = Container.class.getDeclaredField("strings");
Type genericType = stringsField.getGenericType();
if (genericType instanceof ParameterizedType pt) {
Type rawType = pt.getRawType(); // List.class
Type[] typeArgs = pt.getActualTypeArguments(); // [String.class]
}
// Вложенные generic
Field complexField = Container.class.getDeclaredField("complex");
ParameterizedType mapType = (ParameterizedType) complexField.getGenericType();
// Map<String, List<Integer>>
Type[] mapArgs = mapType.getActualTypeArguments();
// [String.class, ParameterizedType(List<Integer>)]
ParameterizedType listType = (ParameterizedType) mapArgs[1];
Type[] listArgs = listType.getActualTypeArguments(); // [Integer.class]
// Type parameters класса
TypeVariable<?>[] typeParams = Container.class.getTypeParameters();
// [T]
// Generic метод
Method transform = Container.class.getMethod("transform", Object.class);
TypeVariable<?>[] methodTypeParams = transform.getTypeParameters();
// [E]
Dynamic Proxy
Proxy позволяет создавать объекты, реализующие интерфейсы с динамической обработкой вызовов:
interface UserService {
User findById(long id);
void save(User user);
}
// InvocationHandler обрабатывает все вызовы
class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Calling: " + method.getName());
long start = System.currentTimeMillis();
try {
Object result = method.invoke(target, args);
System.out.println("Returned: " + result);
return result;
} finally {
long elapsed = System.currentTimeMillis() - start;
System.out.println("Took: " + elapsed + "ms");
}
}
}
// Создание proxy
UserService realService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[] { UserService.class },
new LoggingHandler(realService)
);
// Все вызовы проходят через handler
proxy.findById(42); // Логируется автоматически
Несколько интерфейсов
interface Readable { String read(); }
interface Writable { void write(String data); }
Object proxy = Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class<?>[] { Readable.class, Writable.class },
(proxyObj, method, args) -> {
if (method.getName().equals("read")) {
return "data";
} else if (method.getName().equals("write")) {
System.out.println("Writing: " + args[0]);
return null;
}
throw new UnsupportedOperationException();
}
);
Readable readable = (Readable) proxy;
Writable writable = (Writable) proxy;
setAccessible и модульная система
До Java 9
Field privateField = SomeClass.class.getDeclaredField("secret");
privateField.setAccessible(true); // Работало всегда
Java 9+ (JPMS)
Модульная система ограничивает рефлексию:
// Может бросить InaccessibleObjectException
field.setAccessible(true);
Решения:
# 1. Флаг запуска JVM
java --add-opens java.base/java.lang=ALL-UNNAMED MyApp
# 2. В module-info.java (если контролируете модуль)
module mymodule {
opens com.mypackage to framework.module;
}
// 3. Проверка в коде
if (field.canAccess(object)) {
// Доступ разрешён
} else if (field.trySetAccessible()) {
// Успешно открыли доступ
} else {
// Не удалось получить доступ
}
Исключения Reflection API
try {
Class<?> cls = Class.forName("com.example.MyClass");
Object obj = cls.getConstructor().newInstance();
Method method = cls.getMethod("doSomething", String.class);
method.invoke(obj, "test");
} catch (ClassNotFoundException e) {
// Класс не найден
} catch (NoSuchMethodException e) {
// Метод или конструктор не найден
} catch (NoSuchFieldException e) {
// Поле не найдено
} catch (InstantiationException e) {
// Не удалось создать экземпляр (абстрактный класс, интерфейс)
} catch (IllegalAccessException e) {
// Нет доступа (private без setAccessible)
} catch (IllegalArgumentException e) {
// Неверные аргументы
} catch (InvocationTargetException e) {
// Исключение в вызванном методе
Throwable cause = e.getCause(); // Оригинальное исключение
} catch (InaccessibleObjectException e) {
// Java 9+: модульная система запрещает доступ
}
Практический пример: простой DI-контейнер
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Inject {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Component {}
class SimpleDIContainer {
private final Map<Class<?>, Object> instances = new HashMap<>();
public void register(Class<?> cls) throws Exception {
if (!cls.isAnnotationPresent(Component.class)) {
throw new IllegalArgumentException("Class must be annotated with @Component");
}
Object instance = cls.getConstructor().newInstance();
instances.put(cls, instance);
}
public void injectDependencies() throws Exception {
for (Object instance : instances.values()) {
for (Field field : instance.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class)) {
Object dependency = instances.get(field.getType());
if (dependency != null) {
field.setAccessible(true);
field.set(instance, dependency);
}
}
}
}
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> cls) {
return (T) instances.get(cls);
}
}
// Использование
@Component
class Repository {
public String getData() { return "data"; }
}
@Component
class Service {
@Inject
private Repository repository;
public void process() {
System.out.println(repository.getData());
}
}
SimpleDIContainer container = new SimpleDIContainer();
container.register(Repository.class);
container.register(Service.class);
container.injectDependencies();
Service service = container.get(Service.class);
service.process(); // "data"
Практический пример: сериализация в JSON
public class SimpleJsonSerializer {
public String toJson(Object obj) throws Exception {
StringBuilder json = new StringBuilder("{");
Field[] fields = obj.getClass().getDeclaredFields();
boolean first = true;
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers())) continue;
if (Modifier.isTransient(field.getModifiers())) continue;
field.setAccessible(true);
Object value = field.get(obj);
if (!first) json.append(",");
first = false;
json.append("\"").append(field.getName()).append("\":");
json.append(formatValue(value));
}
return json.append("}").toString();
}
private String formatValue(Object value) {
if (value == null) return "null";
if (value instanceof String) return "\"" + value + "\"";
if (value instanceof Number || value instanceof Boolean) return value.toString();
return "\"" + value.toString() + "\"";
}
}
// Использование
class User {
private String name = "Alice";
private int age = 25;
private transient String password = "secret";
}
String json = new SimpleJsonSerializer().toJson(new User());
// {"name":"Alice","age":25}
Производительность
Рефлексия значительно медленнее прямого доступа:
// Прямой вызов: ~1 наносекунда
person.getName();
// Через рефлексию: ~100-1000 наносекунд (первый вызов)
// ~10-50 наносекунд (последующие вызовы с кэшированным Method)
method.invoke(person);
Оптимизация
// 1. Кэшируйте Method/Field/Constructor объекты
class ReflectionCache {
private static final Map<String, Method> methodCache = new ConcurrentHashMap<>();
public static Method getMethod(Class<?> cls, String name, Class<?>... params)
throws NoSuchMethodException {
String key = cls.getName() + "." + name + Arrays.toString(params);
return methodCache.computeIfAbsent(key, k -> {
try {
Method m = cls.getDeclaredMethod(name, params);
m.setAccessible(true);
return m;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
}
}
// 2. Используйте MethodHandle (Java 7+) для лучшей производительности
import java.lang.invoke.*;
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.findVirtual(String.class, "length",
MethodType.methodType(int.class));
int length = (int) handle.invoke("hello"); // 5
// 3. Используйте VarHandle (Java 9+) для полей
VarHandle varHandle = MethodHandles.lookup()
.findVarHandle(Person.class, "name", String.class);
varHandle.set(person, "Bob");
Лучшие практики
✅ Когда использовать
// Фреймворки и библиотеки
// Тестирование (доступ к private для проверки состояния)
// Сериализация/десериализация
// Динамическая загрузка плагинов
❌ Когда избегать
// Обычный бизнес-код — используйте прямой доступ
// Обход инкапсуляции без веских причин
// Критичные к производительности участки
// Когда есть нормальный API
Безопасность
// 1. Валидируйте имена классов из внешних источников
String className = userInput;
if (!className.matches("^[a-zA-Z][a-zA-Z0-9.]*$")) {
throw new SecurityException("Invalid class name");
}
// 2. Используйте белый список классов
Set<String> allowedClasses = Set.of(
"com.myapp.plugins.Plugin1",
"com.myapp.plugins.Plugin2"
);
if (!allowedClasses.contains(className)) {
throw new SecurityException("Class not allowed");
}
// 3. Минимизируйте setAccessible
// Открывайте доступ только когда действительно нужно
Резюме
| Класс | Назначение |
|---|---|
Class<T> | Метаданные класса, точка входа |
Field | Информация и доступ к полям |
Method | Информация и вызов методов |
Constructor<T> | Создание экземпляров |
Modifier | Декодирование модификаторов |
Array | Работа с массивами |
Proxy | Динамические прокси |
Parameter | Информация о параметрах методов |
AnnotatedElement | Доступ к аннотациям |
Ключевые методы:
| Категория | public (+ унаследованные) | Все (включая private) |
|---|---|---|
| Поля | getFields() | getDeclaredFields() |
| Методы | getMethods() | getDeclaredMethods() |
| Конструкторы | getConstructors() | getDeclaredConstructors() |
| Классы | getClasses() | getDeclaredClasses() |
Помните:
- Рефлексия мощный инструмент, но используйте с осторожностью
- Кэшируйте объекты
Method,Field,Constructor - В Java 9+ учитывайте ограничения модульной системы
- Для критичной производительности рассмотрите
MethodHandle/VarHandle