25

Что такое рефлексия и зачем она нужна?

22

Что такое рефлексия и почему она полезна?

Меня особенно интересует Java, но я предполагаю, что принципы одинаковы для любого языка.

5 ответ(ов)

20

Термин "рефлексия" (reflection) используется для описания кода, который может исследовать другой код в той же системе (или сам себя).

Например, представьте, что у вас есть объект неизвестного типа в Java, и вы хотите вызвать метод 'doSomething', если он существует. Статическая типизация Java не предназначена для поддержки этого, если объект не соответствует известному интерфейсу. Однако с помощью рефлексии ваш код может посмотреть на объект и выяснить, есть ли у него метод под названием 'doSomething', а затем вызвать его при необходимости.

Вот пример кода на Java (предположим, объектом является foo):

Method method = foo.getClass().getMethod("doSomething", null);
method.invoke(foo, null);

Одним из распространенных случаев использования рефлексии в Java является работа с аннотациями. Например, JUnit 4 использует рефлексию, чтобы просмотреть ваши классы в поисках методов, помеченных аннотацией @Test, и затем вызывает их во время выполнения юнит-теста.

Вы можете найти несколько хороших примеров рефлексии, чтобы начать, по адресу: http://docs.oracle.com/javase/tutorial/reflect/index.html.

И в заключение, да, концепции довольно схожи в других статически типизированных языках, которые поддерживают рефлексию (например, C#). В динамически типизированных языках необходимость в описанном выше случае меньше (так как компилятор позволяет вызывать любой метод на любом объекте, а ошибка возникает во время выполнения, если метод не существует), но второй случай, касающийся поиска методов, которые помечены или работают определенным образом, все еще распространен.

Обновление из комментария:

Возможность проверять код в системе и видеть типы объектов не является рефлексией, а скорее интроспекцией типов (Type Introspection). Рефлексия же — это способность вносить изменения во время выполнения, используя интроспекцию. Это различие важно, поскольку некоторые языки поддерживают интроспекцию, но не поддерживают рефлексию. Одним из таких примеров является C++.

3

Рефлексия — это способность языка программирования инспектировать и динамически вызывать классы, методы, атрибуты и т.д. во время выполнения.

Например, все объекты в Java имеют метод getClass(), который позволяет определить класс объекта, даже если вы не знаете его на этапе компиляции (например, если вы объявили его как Object). Это может показаться тривиальным, но такая рефлексия невозможна в менее динамичных языках, таких как C++. Болееadvanced использование рефлексии позволяет перечислять и вызывать методы, конструкторы и т.д.

Рефлексия важна, поскольку она позволяет писать программы, которые не должны "знать" все на этапе компиляции, что делает их более динамичными, так как они могут связываться во время выполнения. Код может быть написан с использованием известных интерфейсов, но фактические классы могут быть созданы с помощью рефлексии из файлов конфигурации.

Многие современные фреймворки широко используют рефлексию именно по этой причине. Большинство других современных языков также используют рефлексию, а в языках сценариев (таких как Python) она еще более глубоко интегрирована, поскольку это выглядит более естественно в рамках общего программного моделирования этих языков.

1

Вы можете использовать следующий метод для вывода всех полей и их значений объекта в Java, используя рефлексию. Этот метод под названием dump принимает любой объект в качестве параметра и печатает все его поля и их значения.

Вот код метода:

import java.lang.reflect.Array;
import java.lang.reflect.Field;

public static String dump(Object o, int callCount) {
    callCount++;
    StringBuffer tabs = new StringBuffer();
    for (int k = 0; k < callCount; k++) {
        tabs.append("\t");
    }
    StringBuffer buffer = new StringBuffer();
    Class oClass = o.getClass();
    if (oClass.isArray()) {
        buffer.append("\n");
        buffer.append(tabs.toString());
        buffer.append("[");
        for (int i = 0; i < Array.getLength(o); i++) {
            if (i > 0) buffer.append(", ");
            Object value = Array.get(o, i);
            if (value.getClass().isPrimitive() ||
                    value.getClass() == java.lang.Long.class ||
                    value.getClass() == java.lang.String.class ||
                    value.getClass() == java.lang.Integer.class ||
                    value.getClass() == java.lang.Boolean.class
                    ) {
                buffer.append(value);
            } else {
                buffer.append(dump(value, callCount));
            }
        }
        buffer.append(tabs.toString());
        buffer.append("]\n");
    } else {
        buffer.append("\n");
        buffer.append(tabs.toString());
        buffer.append("{\n");
        while (oClass != null) {
            Field[] fields = oClass.getDeclaredFields();
            for (Field field : fields) {
                try {
                    tabs.append("\t");
                    field.setAccessible(true);
                    buffer.append(tabs.toString());
                    buffer.append(field.getName());
                    buffer.append("=");
                    Object value = field.get(o);
                    if (value != null) {
                        if (value.getClass().isPrimitive() ||
                                value.getClass() == java.lang.Long.class ||
                                value.getClass() == java.lang.String.class ||
                                value.getClass() == java.lang.Integer.class ||
                                value.getClass() == java.lang.Boolean.class
                                ) {
                            buffer.append(value);
                        } else {
                            buffer.append(dump(value, callCount));
                        }
                    }
                } catch (IllegalAccessException e) {
                    buffer.append("Ошибка доступа: ");
                    buffer.append(e.getMessage());
                }
                buffer.append("\n");
            }
            oClass = oClass.getSuperclass();
        }
        buffer.append(tabs.toString());
        buffer.append("}\n");
    }
    return buffer.toString();
}

Этот метод рекурсивно обходит поля объекта и его суперклассов, выводя имена и значения каждого из них. Если поле является массивом или сложным объектом, метод вызывает сам себя для дальнейшего обхода. Это позволяет легко визуализировать структуру объектов в процессе отладки или анализа.

0

Не каждый язык программирования поддерживает рефлексию, но принципы обычно схожи в языках, которые её поддерживают.

Рефлексия — это возможность "отражать" структуру вашей программы. Более конкретно, это возможность заглянуть в объекты и классы, которые у вас есть, и программно получать информацию о методах, полях и интерфейсах, которые они реализуют. Также можно исследовать аннотации и другие метаданные.

Рефлексия полезна во многих ситуациях. Например, везде, где требуется динамически подключать классы в ваш код. Многие ORM (объектно-реляционные сопоставители) используют рефлексию, чтобы создавать объекты из баз данных, не зная заранее, какие объекты им понадобятся. Архитектуры дополнений — ещё одно место, где рефлексия оказывается полезной. Возможность динамически загружать код и определять, есть ли там типы, реализующие нужный интерфейс для использования в качестве плагина, важна в таких ситуациях.

0

Reflection позволяет динамически создавать новые объекты, вызывать методы и выполнять операции получения/установки значений переменных класса во время выполнения, не имея предварительного знания о его реализации.

Class myObjectClass = MyObject.class;
Method[] method = myObjectClass.getMethods();

// Здесь метод принимает строковый параметр; если параметра нет, передайте null.
Method method = myObjectClass.getMethod("method_name", String.class); 

Object returnValue = method.invoke(null, "parameter-value1");

В приведенном выше примере параметр null указывает на объект, на котором вы хотите вызвать метод. Если метод статический, передайте null. Если метод нестатический, то необходимо передать действительный экземпляр MyObject вместо null.

Reflection также позволяет вам получить доступ к частным членам и методам класса:

public class A {
  private String str = null;

  public A(String str) {
    this.str = str;
  }
}

Затем можно сделать следующее:

A obj = new A("Some value");

Field privateStringField = A.class.getDeclaredField("privateString");

// Отключаем проверку доступа для этого поля
privateStringField.setAccessible(true);

String fieldValue = (String) privateStringField.get(obj);
System.out.println("fieldValue = " + fieldValue);
  • Для инспекции классов (также известной как интроспекция) не требуется импортировать пакет рефлексии (java.lang.reflect). Метаданные класса можно получить через java.lang.Class.

Reflection — это очень мощный API, однако его чрезмерное использование может замедлить приложение, так как он разрешает все типы во время выполнения.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь