По суті, рефлексія означає використання коду вашої програми як даних.
Тому використання рефлексії може бути хорошою ідеєю, коли код вашої програми є корисним джерелом даних. (Але є компроміси, тому це може бути не завжди гарною ідеєю.)
Наприклад, розглянемо простий клас:
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
}
Знову ж таки, існує повторювана закономірність, яку можна описати алгоритмом:
- проксі-реєстратор - клас, який реалізує інтерфейс
- він має конструктор, який займає іншу реалізацію інтерфейсу та реєстратор
- для кожного методу в інтерфейсі
- реалізація записує повідомлення "$ namename name"
- а потім викликає той же метод у внутрішньому інтерфейсі, передаючи всі аргументи
а вхід цього алгоритму - визначення інтерфейсу.
Рефлексія дозволяє визначити новий клас за допомогою цього алгоритму. Java дозволяє зробити це за допомогою методів java.lang.reflect.Proxy
класу, і є бібліотеки, які дають вам ще більше сил.
То які недоліки відображення?
- Ваш код стає важче зрозуміти. Ви є одним рівнем абстракції, надалі вилученим з конкретних ефектів вашого коду.
- Ваш код стає важче налагоджувати. Особливо для бібліотек, що генерують код, виконаний код може бути не тим кодом, який ви написали, а створеним вами кодом, і налагоджувач не зможе показати вам цей код (або дозволить вам розмістити точки прориву).
- Ваш код стає повільнішим. Динамічне зчитування інформації про тип та доступ до полів за їх ручками виконання замість жорсткого кодування доступ повільніше. Динамічне генерування коду може пом'якшити цей ефект, ціною налагодження якого ще важче.
- Ваш код може стати більш крихким. Доступ до динамічного відображення не перевіряється компілятором, але видає помилки під час виконання.