CodeGym /Java блог /Случаен /API за отражение: Отражение. Тъмната страна на Java
John Squirrels
Ниво
San Francisco

API за отражение: Отражение. Тъмната страна на Java

Публикувано в групата
Поздрави, млад падаван. В тази статия ще ви разкажа за Силата, сила, която Java програмистите използват само в привидно невъзможни ситуации. Тъмната страна на Java е Reflection API. В Java отражението се реализира с помощта на Java Reflection API.

Какво е отражение на Java?

В интернет има кратко, точно и популярно определение. Отражението ( от къснолатински reflexio - да се върна ) е механизъм за изследване на данни за програма, докато тя работи. Reflection ви позволява да изследвате информация за полета, методи и конструктори на класове. Reflection ви позволява да работите с типове, които не са присъствали по време на компorране, но които са станали достъпни по време на изпълнение. Отражението и логически последователният модел за издаване на информация за грешки правят възможно създаването на правилен динамичен code. С други думи, разбирането How работи отражението в Java ще отвори редица невероятни възможности за вас. Можете буквално да жонглирате с класове и техните компоненти. Ето основен списък на това, което отражението позволява:
  • Научете/определете класа на обекта;
  • Получавайте информация за модификаторите, полетата, методите, константите, конструкторите и суперкласовете на даден клас;
  • Разберете кои методи принадлежат към внедрен(и) интерфейс(и);
  • Създайте екземпляр на клас, чието име на клас е неизвестно до момента на изпълнение;
  • Получаване и задаване на стойности на полета на обект по име;
  • Извикване на метод на обект по име.
Отражението се използва в почти всички съвременни Java технологии. Трудно е да си представим, че Java, като платформа, би могла да постигне такова широко разпространение без размисъл. Най-вероятно нямаше да има. След като като цяло сте се запознали с рефлексията като теоретична концепция, нека пристъпим към нейното практическо приложение! Няма да научим всички методи на Reflection API – само тези, които действително ще срещнете на практика. Тъй като отражението включва работа с класове, ще започнем с прост клас, наречен MyClass:

public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
Както можете да видите, това е много основен клас. Конструкторът с параметри е умишлено коментиран. Ще се върнем към това по-късно. Ако сте разгледали внимателно съдържанието на класа, вероятно сте забелязали липсата на getter за полето за име . Самото поле за име е маркирано с модификатора за частен достъп: нямаме достъп до него извън самия клас, което означава, че не можем да извлечем неговата стойност. " И така, Howъв е проблемът ?" ти каза. „Добавяне на инструмент за получаване or промяна на модификатора за достъп“. И ще бъдеш прав, освен акоMyClassе бил в компorрана AAR библиотека or в друг частен модул без възможност за извършване на промени. На практика това се случва постоянно. И някой небрежен програмист просто е забравил да напише getter . Това е моментът да си припомним размисъла! Нека се опитаме да стигнем до полето за лично име на MyClassкласа:

public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; // No getter =(
   System.out.println(number + name); // Output: 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name); // Output: 0default
}
Нека анализираме Howво се случи току-що. В Java има прекрасен клас, наречен Class. Той представлява класове и интерфейси в изпълнимо Java приложение. Няма да разглеждаме връзката между Classи ClassLoader, тъй като това не е темата на тази статия. След това, за да извлечете полетата на този клас, трябва да извикате getFields()метода. Този метод ще върне всички достъпни полета на този клас. Това не работи за нас, защото нашето поле е частно , така че използваме getDeclaredFields()метода. Този метод също така връща масив от полета на клас, но сега включва частни и защитени полета. В този случай знаем името на полето, което ни интересува, така че можем да използваме метода getDeclaredField(String), къдетоStringе името на желаното поле. Забележка: getFields()и getDeclaredFields()не връщайте полетата на родителски клас! Страхотен. Имаме Fieldобект, който се позовава на нашето име . Тъй като полето не беше публично , трябва да предоставим достъп за работа с него. Методът setAccessible(true)ни позволява да продължим по-нататък. Сега полето за име е под наш пълен контрол! Можете да извлечете стойността му, като извикате метода Fieldна обекта get(Object), където Objectе екземпляр на нашия MyClassклас. Преобразуваме типа в Stringи присвояваме стойността на нашата променлива име . Ако не можем да намерим сетер , който да зададе нова стойност в полето за име, можете да използвате метода set :

field.set(myClass, (String) "new value");
Честито! Току-що сте усвоor основите на размисъла и сте получor достъп до лично поле! Обърнете внимание на try/catchблока и типовете изключения, които се обработват. IDE ще ви каже, че тяхното присъствие е необходимо само по себе си, но можете ясно да разберете по имената им защо са тук. Преместване на! Както може би сте забелязали, нашият MyClassклас вече има метод за показване на информация за данните на класа:

private void printData(){
       System.out.println(number + name);
   }
Но този програмист остави пръстовите си отпечатъци и тук. Методът има модификатор за частен достъп и ние трябва да пишем собствен code, за да показваме данни всеки път. Каква бъркотия. Къде отиде нашето отражение? Напишете следната функция:

public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
Процедурата тук е приблизително същата като тази, използвана за извличане на поле. Достъпваме до желания метод по име и предоставяме достъп до него. И на Methodобекта наричаме invoke(Object, Args)метода, където Objectсъщо е екземпляр на MyClassкласа. Argsса аргументите на метода, въпреки че нашият няма такива. Сега използваме printDataфункцията за показване на информация:

public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // Output: 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// Output: 0new value
}
ура! Сега имаме достъп до частния метод на класа. Но Howво ще стане, ако методът има аргументи и защо конструкторът е коментиран? Всичко в своето време. От дефиницията в началото става ясно, че отражението ви позволява да създавате екземпляри на клас по време на изпълнение (докато програмата работи)! Можем да създадем обект, използвайки пълното име на класа. Пълното име на класа е името на класа, включително пътя на неговия пакет .
API за отражение: Отражение.  Тъмната страна на Java - 2
В моята пакетна йерархия пълното име на MyClass ще бъде "reflection.MyClass". Има и лесен начин да научите името на клас (връщане на името на класа като низ):

MyClass.class.getName()
Нека използваме отражението на Java, за да създадем екземпляр на класа:

public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass); // Output: created object reflection.MyClass@60e53b93
}
Когато се стартира Java приложение, не всички класове се зареждат в JVM. Ако вашият code не се отнася до MyClassкласа, тогава ClassLoader, който отговаря за зареждането на класове в JVM, никога няма да зареди класа. Това означава, че трябва ClassLoaderда го заредите и да получите описание на класа под формата на Classпроменлива. Ето защо имаме forName(String)метода, където Stringе името на класа, чието описание ни трябва. След получаване на Сlassобекта, извикването на метода newInstance()ще върне Objectобект, създаден с помощта на това описание. Всичко, което остава, е да предоставим този обект на нашитеMyClassклас. Готино! Това беше трудно, но разбираемо, надявам се. Сега можем да създадем екземпляр на клас буквално в един ред! За съжаление, описаният подход ще работи само с конструктора по подразбиране (без параметри). Как се извикват методи и конструктори с параметри? Време е да разкоментираме нашия конструктор. Както се очаква, newInstance()не може да намери конструктора по подразбиране и вече не работи. Нека пренапишем инстанцията на класа:

public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);// Output: created object reflection.MyClass@60e53b93
}
Методът getConstructors()трябва да бъде извикан в дефиницията на класа, за да получи конструктори на класа, а след това getParameterTypes()трябва да бъде извикан, за да получи параметрите на конструктора:

Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
Това ни дава всички конструктори и техните параметри. В моя пример имам предвид конкретен конструктор със специфични, предварително известни параметри. И за да извикаме този конструктор, използваме newInstanceметода, на който предаваме стойностите на тези параметри. Същото ще бъде и при използване invokeна методи за извикване. Това повдига въпроса: кога извикването на конструктори чрез отражение е полезно? Както вече споменахме в началото, съвременните Java технологии не могат да минат без Java Reflection API. Например, инжектиране на зависимости (DI), което комбинира анотации с отразяване на методи и конструктори, за да формира популярния Darerбиблиотека за разработка на Android. След като прочетете тази статия, можете уверено да смятате, че сте обучени в начините на Java Reflection API. Те не наричат ​​отражението тъмната страна на Java за нищо. Той напълно нарушава ООП парадигмата. В Java капсулирането скрива и ограничава достъпа на другите до определени програмни компоненти. Когато използваме частния модификатор, възнамеряваме това поле да бъде достъпно само от класа, където съществува. И ние изграждаме последващата архитектура на програмата въз основа на този принцип. В тази статия видяхме How можете да използвате отражението, за да си проправите път навсякъде. Творческият дизайнерски модел Singletonе добър пример за това като архитектурно решение. Основната идея е, че клас, прилагащ този модел, ще има само един екземпляр по време на изпълнение на цялата програма. Това се постига чрез добавяне на модификатора за частен достъп към конструктора по подразбиране. И би било много лошо, ако програмистът използва отражение, за да създаде повече екземпляри на такива класове. Между другото, наскоро чух колега да задава много интересен въпрос: може ли клас, който прилага модела Singleton, да бъде наследен? Възможно ли е в този случай дори размисълът да е безсилен? Оставете отзивите си за статията и отговора си в коментарите по-долу и задайте своите въпроси там!
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION