Что такое рефлексия и почему она полезна? - PullRequest
1902 голосов
/ 01 сентября 2008

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

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

Ответы [ 21 ]

1577 голосов
/ 01 сентября 2008

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

Например, скажем, у вас есть объект неизвестного типа в 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 #). В динамически типизированных языках описанный выше вариант использования менее необходим (так как компилятор разрешит вызов любого метода для любого объекта, если во время выполнения произойдет сбой, если он не существует), но во втором случае поиска методов, которые помечены или работа определенным образом все еще распространена.

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

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

222 голосов
/ 01 сентября 2008

Отражение - это способность языка проверять и динамически вызывать классы, методы, атрибуты и т. Д. Во время выполнения.

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

Отражение важно, поскольку оно позволяет вам писать программы, которым не нужно «знать» все во время компиляции, что делает их более динамичными, поскольку они могут быть связаны во время выполнения. Код может быть написан для известных интерфейсов, но фактические классы, которые будут использоваться, могут быть созданы с использованием отражения из файлов конфигурации.

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

102 голосов
/ 02 сентября 2008

Одним из моих любимых способов отражения является метод дампа ниже. Он принимает любой объект в качестве параметра и использует API отражения Java для печати каждого имени и значения поля.

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 (int i = 0; i < fields.length; i++) {
                buffer.append(tabs.toString());
                fields[i].setAccessible(true);
                buffer.append(fields[i].getName());
                buffer.append("=");
                try {
                    Object value = fields[i].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(e.getMessage());
                }
                buffer.append("\n");
            }
            oClass = oClass.getSuperclass();
        }
        buffer.append(tabs.toString());
        buffer.append("}\n");
    }
    return buffer.toString();
}
72 голосов
/ 17 октября 2014

Использование отражения

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

Возможности расширения

Приложение может использовать внешние пользовательские классы, создавая экземпляры объектов расширяемости, используя их полностью определенные имена. Браузеры классов и среды визуальной разработки Браузер классов должен иметь возможность перечислять членов классов. Визуальные среды разработки могут выиграть от использования информации о типах, доступной в рефлексии, чтобы помочь разработчику в написании правильного кода. Отладчики и тестовые инструменты Отладчики должны иметь возможность проверять частные члены в классах. В тестовых системах можно использовать отражение, чтобы систематически вызывать API-интерфейсы обнаруживаемого набора, определенные в классе, для обеспечения высокого уровня покрытия кода в наборе тестов.

Недостатки отражения

Отражение является мощным, но не должно использоваться без разбора. Если возможно выполнить операцию без использования отражения, то предпочтительно избегать ее использования. При обращении к коду с помощью отражения следует помнить следующее.

  • Повышение производительности

Поскольку отражение включает в себя типы, которые разрешаются динамически, определенные оптимизации виртуальных машин Java не могут быть выполнены. Следовательно, отражающие операции имеют более низкую производительность, чем их неотражающие аналоги, и их следует избегать в разделах кода, которые часто вызываются в чувствительных к производительности приложениях.

  • Ограничения безопасности

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

  • Воздействие внутренних органов

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

источник: API отражения

39 голосов
/ 01 сентября 2008

Отражение - это ключевой механизм, позволяющий приложению или фреймворку работать с кодом, который еще не был написан!

Возьмем, к примеру, ваш типичный файл web.xml. Он будет содержать список элементов сервлета, которые содержат вложенные элементы класса сервлета. Контейнер сервлета обработает файл web.xml и создаст новый новый экземпляр каждого класса сервлета с помощью отражения.

Другим примером может быть Java API для синтаксического анализа XML (JAXP) . Когда поставщик синтаксического анализатора XML подключается через общеизвестные системные свойства, которые используются для создания новых экземпляров с помощью отражения.

И, наконец, наиболее полный пример - Spring , который использует отражение для создания своих бинов и для интенсивного использования прокси

34 голосов
/ 01 сентября 2008

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

Reflection - это способность «отражать» структуру вашей программы. Или более конкретно. Чтобы посмотреть на имеющиеся у вас объекты и классы и программно получить информацию о методах, полях и интерфейсах, которые они реализуют. Вы также можете посмотреть на такие вещи, как аннотации.

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

32 голосов
/ 08 июля 2013

Отражение позволяет создавать экземпляры новых объектов, вызывать методы и операции get / set с переменными класса динамически во время выполнения, не имея предварительного знания о его реализации.

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

//Here the method takes a string parameter if there is no param, put null.
Method method = aClass.getMethod("method_name", String.class); 

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

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

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

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");

//Turn off access check for this field
privateStringField.setAccessible(true);

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

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

21 голосов
/ 22 июня 2012

Пример:
Возьмем, к примеру, удаленное приложение, которое дает вашему приложению объект, который вы получаете с помощью их методов API. Теперь, основываясь на объекте, вам может потребоваться выполнить какое-то вычисление.
Поставщик гарантирует, что объект может быть 3-х типов, и нам нужно выполнять вычисления в зависимости от типа объекта.
Таким образом, мы можем реализовать в 3 классах, каждый из которых содержит различную логику. Очевидно, что информация об объекте доступна во время выполнения, поэтому вы не можете статически кодировать для выполнения вычислений, поэтому отражение используется для создания экземпляра объекта класса, который требуется для выполнения вычисление на основе объекта, полученного от провайдера.

20 голосов
/ 08 сентября 2014

Java Reflection довольно мощный и может быть очень полезным. Java Reflection позволяет проверять классы, интерфейсы, поля и методы во время выполнения, , не зная имен классов, методов и т. Д. Во время компиляции. Также возможно создавать новые объекты, вызывать методы и получать / устанавливать значения полей, используя отражение.

Быстрый пример Java Reflection, чтобы показать вам, как выглядит отражение:

Method[] methods = MyObject.class.getMethods();

    for(Method method : methods){
        System.out.println("method = " + method.getName());
    }

Этот пример получает объект Class из класса MyObject. Используя объект класса, пример получает список методов этого класса, выполняет итерацию методов и выводит их имена.

Как именно все это работает, здесь объясняется

Редактировать : После почти 1 года я редактирую этот ответ, так как, читая об отражении, я еще несколько раз использовал Reflection.

  • Spring использует конфигурацию бина, такую ​​как:


<bean id="someID" class="com.example.Foo">
    <property name="someField" value="someValue" />
</bean>

Когда контекст Spring обрабатывает этот элемент , он будет использовать Class.forName (String) с аргументом "com.example.Foo" для создания экземпляра этого класса.

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

  • Junit использует Reflection специально для тестирования частных / защищенных методов.

Для частных методов,

Method method = targetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

Для частных полей,

Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
15 голосов
/ 06 февраля 2012

Согласно моему пониманию:

Отражение позволяет программисту динамически получать доступ к объектам в программе. т.е. при кодировании приложения, если программист не знает о классе или его методах, он может использовать этот класс динамически (во время выполнения) с помощью отражения.

Он часто используется в сценариях, где имя класса часто меняется. Если возникает такая ситуация, то программисту сложно переписать приложение и снова и снова менять имя класса.

Вместо этого, используя отражение, нужно беспокоиться о возможном изменении имени класса.

...