По сути, отражение означает использование кода вашей программы в качестве данных.
Поэтому, использование отражения может быть хорошей идеей, когда код вашей программы является полезным источником данных. (Но есть компромиссы, поэтому это не всегда может быть хорошей идеей.)
Например, рассмотрим простой класс:
public class Foo {
public int value;
public string anotherValue;
}
и вы хотите сгенерировать XML из него. Вы можете написать код для генерации XML:
public XmlNode generateXml(Foo foo) {
XmlElement root = new XmlElement("Foo");
XmlElement valueElement = new XmlElement("value");
valueElement.add(new XmlText(Integer.toString(foo.value)));
root.add(valueElement);
XmlElement anotherValueElement = new XmlElement("anotherValue");
anotherValueElement.add(new XmlText(foo.anotherValue));
root.add(anotherValueElement);
return root;
}
Но это много стандартного кода, и каждый раз, когда вы меняете класс, вы должны обновлять код. Действительно, вы могли бы описать, что делает этот код как
- создать элемент XML с именем класса
- за каждое свойство класса
- создать элемент XML с именем свойства
- поместите значение свойства в элемент XML
- добавить элемент XML в корень
Это алгоритм, и входом алгоритма является класс: нам нужно его имя, а также имена, типы и значения его свойств. Вот где приходит рефлексия: она дает вам доступ к этой информации. Java позволяет вам проверять типы, используя методы Class
класса.
Еще несколько вариантов использования:
- определить URL-адреса в веб-сервере на основе имен методов класса и параметры URL-адресов на основе аргументов метода
- преобразовать структуру класса в определение типа GraphQL
- вызывать каждый метод класса, имя которого начинается с «test» в качестве примера модульного теста
Однако полное отражение означает не только просмотр существующего кода (который сам по себе известен как «самоанализ»), но также изменение или генерацию кода. Для этого в Java есть два известных варианта использования: прокси и макеты.
Допустим, у вас есть интерфейс:
public interface Froobnicator {
void froobnicateFruits(List<Fruit> fruits);
void froobnicateFuel(Fuel fuel);
// lots of other things to froobnicate
}
и у вас есть реализация, которая делает что-то интересное:
public class PowerFroobnicator implements Froobnicator {
// awesome implementations
}
И на самом деле у вас есть вторая реализация:
public class EnergySaverFroobnicator implements Froobnicator {
// efficient implementations
}
Теперь вам также нужен вывод журнала; вы просто хотите получить сообщение в журнале всякий раз, когда вызывается метод. Вы можете явно добавить вывод журнала в каждый метод, но это будет раздражать, и вам придется делать это дважды; один раз для каждой реализации. (Так что даже больше, когда вы добавляете больше реализаций.)
Вместо этого вы можете написать прокси:
public class LoggingFroobnicator implements Froobnicator {
private Logger logger;
private Froobnicator inner;
// constructor that sets those two
public void froobnicateFruits(List<Fruit> fruits) {
logger.logDebug("froobnicateFruits called");
inner.froobnicateFruits(fruits);
}
public void froobnicateFuel(Fuel fuel) {
logger.logDebug("froobnicateFuel( called");
inner.froobnicateFuel(fuel);
}
// lots of other things to froobnicate
}
Опять же, однако, есть повторяющийся шаблон, который может быть описан алгоритмом:
- прокси-сервер журнала является классом, который реализует интерфейс
- у него есть конструктор, который принимает другую реализацию интерфейса и регистратор
- для каждого метода в интерфейсе
- реализация регистрирует сообщение «$ methodname named»
- а затем вызывает тот же метод на внутреннем интерфейсе, передавая все аргументы
и ввод этого алгоритма - определение интерфейса.
Отражение позволяет вам определить новый класс, используя этот алгоритм. Java позволяет вам делать это, используя методы java.lang.reflect.Proxy
класса, и есть библиотеки, которые дают вам еще больше возможностей.
Так каковы недостатки отражения?
- Ваш код становится сложнее для понимания. Вы - один уровень абстракции, более удаленный от конкретных эффектов вашего кода.
- Ваш код становится сложнее для отладки. Особенно в библиотеках, генерирующих код, выполняемый код может быть не тем кодом, который вы написали, а кодом, который вы сгенерировали, и отладчик может не показать вам этот код (или позволить вам устанавливать точки останова).
- Ваш код становится медленнее. Динамическое чтение информации о типе и доступ к полям с помощью их дескрипторов времени выполнения вместо жесткого кодирования происходит медленнее. Динамическая генерация кода может смягчить этот эффект за счет того, что отладка еще сложнее.
- Ваш код может стать более хрупким. Доступ к динамическому отражению не проверяется типом компилятором, но выдает ошибки во время выполнения.