Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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