JavaRush /Java блогы /Random-KK /Reflection API. Рефлексия. Java тілінің қараңғы жағы
Oleksandr Klymenko
Деңгей
Харків

Reflection API. Рефлексия. Java тілінің қараңғы жағы

Топта жарияланған
Сәлем, жас Падаван. Бұл мақалада мен сізге күш туралы айтып беремін, оның күшін java бағдарламашылары үмітсіз болып көрінетін жағдайда ғана пайдаланады. Сонымен, Java-ның қараңғы жағы -Reflection API
Reflection API.  Рефлексия.  Java-ның қараңғы жағы - 1
Java тіліндегі рефлексия Java Reflection API арқылы жүзеге асырылады. Бұл не рефлексия? Интернетте де танымал қысқа және нақты анықтамасы бар. Рефлексия (лат. Reflexio – кері қайту) – бағдарламаның орындалу барысында оның мәліметтерін зерттеу механизмі. Рефлексия өрістер, әдістер және класс конструкторлары туралы ақпаратты тексеруге мүмкіндік береді. Рефлексия механизмінің өзі компиляция кезінде жетіспейтін, бірақ бағдарламаны орындау кезінде пайда болатын типтерді өңдеуге мүмкіндік береді. Рефлексия және қателер туралы есеп беру үшін логикалық үйлесімді модельдің болуы дұрыс динамикалық codeты жасауға мүмкіндік береді. Басқаша айтқанда, java-да рефлексия қалай жұмыс істейтінін түсіну сіз үшін көптеген керемет мүмкіндіктерді ашады. Сіз сыныптарды және олардың құрамдастарын сөзбе-сөз жонглерліктей аласыз.
Reflection API.  Рефлексия.  Java-ның қараңғы жағы - 2
Мұнда рефлексияға мүмкіндік беретін негізгі тізім берілген:
  • Объектінің класын табу/анықтау;
  • Класс модификаторлары, өрістер, әдістер, тұрақтылар, конструкторлар және суперкласстар туралы ақпарат алу;
  • Орындалған интерфейске/интерфейстерге қандай әдістер тиесілі екенін табыңыз;
  • Кластың данасын жасаңыз және бағдарлама орындалғанға дейін класстың аты белгісіз болады;
  • Нысан өрісінің мәнін аты бойынша алу және орнату;
  • Объектінің әдісін аты бойынша шақырыңыз.
Рефлексия заманауи 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'a for the ' тың жоқтығын көрген боларсыз name. Өрістің өзі nameкіру модификаторымен белгіленген private; біз оған сыныптың өзінен тыс қол жеткізе алмаймыз; =>оның мәнін ала алмаймыз. «Сонымен, мәселе неде? - дейсің. « getterҚатынас модификаторын қосыңыз немесе өзгертіңіз.» Сіз дұрыс айтасыз, бірақ егер MyClassол құрастырылған aar кітапханасында немесе басқа жабық модульде өңдеуге рұқсатсыз болса және іс жүзінде бұл өте жиі болады. Ал кейбір немқұрайлы бағдарламашы жай ғана жазуды ұмытып кетті getter. Рефлексияны еске түсіретін уақыт келді! Класс privateөрісіне кіруге тырысайық : nameMyClass
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
}
Енді мұнда не болғанын анықтап көрейік. Java тілінде тамаша сынып бар Class. Ол орындалатын Java қолданбасындағы сыныптар мен интерфейстерді көрсетеді. Classжәне арасындағы байланысқа тоқталмаймыз ClassLoader. бұл мақаланың тақырыбы емес. Әрі қарай, осы сыныптың өрістерін алу үшін әдісті шақыру керек getFields(), бұл әдіс бізге сыныптың барлық қолжетімді өрістерін қайтарады. Бұл бізге жарамайды, өйткені біздің өрісіміз private, сондықтан біз әдісті қолданамыз.Бұл getDeclaredFields()әдіс сонымен қатар класс өрістерінің жиымын қайтарады, бірақ енді екеуі де privateжәне де protected. Біздің жағдайымызда бізді қызықтыратын өрістің атауын білеміз және біз әдісті пайдалана аламыз getDeclaredField(String), мұнда Stringқажетті өрістің атауы. Ескерту: getFields()және getDeclaredFields()ата-аналық сыныптың өрістерін қайтармаңыз! Field Керемет, біз сілтемесі бар нысанды алдық name. Өйткені өріс болған жоқ публичным(қоғамдық), онымен жұмыс істеуге рұқсат беру керек. Әдіс setAccessible(true)жұмысты жалғастыруға мүмкіндік береді. Енді алқап nameтолығымен біздің бақылауымызда! get(Object)Сіз оның мәнін an objectіні шақыру арқылы ала аласыз Field, мұнда Objectбіздің класстың данасы орналасқан MyClass. Біз оны шығарамыз Stringжәне оны айнымалыға тағайындаймыз name. Егер бізде кенеттен 'a болмаса setter, атау өрісі үшін жаңа мән орнату әдісін пайдалана аламыз set:
field.set(myClass, (String) "new value");
Құттықтаймыз! Сіз рефлексияның негізгі механизмін жаңа ғана меңгердіңіз және өріске қол жеткізе алдыңыз private! Өңделетін ерекше жағдайлардың блоктары try/catchмен түрлеріне назар аударыңыз. IDE өзі олардың міндетті қатысуын көрсетеді, бірақ олардың аты олардың неліктен осында екенін түсіндіреді. Ілгері жүру! Сіз байқағандай, бізде MyClassсынып деректері туралы ақпаратты көрсету әдісі бар:
private void printData(){
       System.out.println(number + name);
   }
Бірақ бұл бағдарламашы мұнда да мұра қалдырды. Әдіс кіру модификаторының астында privateжәне біз әр уақытта шығыс 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();
   }
}
Мұнда proceduresа шамамен өрісті алумен бірдей - біз аты бойынша қажетті әдісті аламыз және оған рұқсат береміз. MethodАл біз қолданатын нысанды шақыру үшін invoke(Оbject, Args)мұнда Оbjectда класс данасы болып табылады MyClass. Args- әдіс аргументтері - біздікі ешқандайда жоқ. Енді ақпаратты көрсету үшін функцияны қолданамыз printData:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // outout 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
}
Ура, енді біз сыныптың жеке әдісіне қол жеткізе аламыз. Бірақ егер әдіс әлі де дәлелдер болса ше және неге түсініктеме берілген конструктор бар? Әр нәрсенің өз уақыты бар. Басындағы анықтамадан рефлексия режимде runtime(бағдарлама жұмыс істеп тұрған кезде) класс данасын жасауға мүмкіндік беретіні анық! Біз класс an objectісін сол класстың толық атымен жасай аламыз. Толық жарамды сынып атауы сыныптың атауы болып табылады, оған жол берілген package.
Reflection API.  Рефлексия.  Java-ның қараңғы жағы - 3
Менің иерархиямда packageтолық аты MyClass« » болады reflection.MyClass. Сіз сондай-ақ сынып атын қарапайым жолмен таба аласыз (ол сынып атауын жол ретінде қайтарады):
MyClass.class.getName()
Рефлексия арқылы класс данасын жасайық:
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, JVM-ге сыныптарды жүктеуге кім жауапты болса, яғни ClassLoader, оны ешқашан жүктемейді. 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 технологиялары, басында айтылғандай, Reflection APIсіз жасай алмайды. Мысалы, DI (тәуелділік инъекциясы), мұнда annotationлар әдістер мен конструкторлардың көрінісімен біріктірілген Android әзірлеуінде танымал Dagger кітапханасын құрайды. Осы мақаланы оқығаннан кейін сіз өзіңізді Reflection API тетіктерінде сенімді түрде жарықтандырдым деп санай аласыз. Рефлексияны java-ның қараңғы жағы деп бекер айтпаған. Ол OOP парадигмасын толығымен бұзады. Java тілінде инкапсуляция кейбір бағдарлама құрамдастарының басқаларына қолжетімділігін жасыру және шектеу үшін қызмет етеді. Жеке модификаторды пайдалану арқылы біз бұл өріске кіру тек осы өріс бар класс ішінде болатынын айтамыз, соның негізінде біз бағдарламаның одан әрі архитектурасын жасаймыз. Бұл мақалада біз кез келген жерге жету үшін рефлексияны қалай қолдануға болатынын көрдік. Архитектуралық шешім түріндегі жақсы мысал генеративті дизайн үлгісі болып табылады - Singleton. Оның негізгі идеясы программаның бүкіл жұмысы барысында осы шаблонды жүзеге асыратын сыныпта бір ғана көшірме болуы керек. Бұл конструктор үшін әдепкі қатынас модификаторын жекеге орнату арқылы орындалады. Ал егер қандай да бір бағдарламашы өзінің рефлексиясымен осындай сыныптарды жасаса, бұл өте жаман болады. Айтпақшы, жақында менің қызметкерімнен естіген өте қызықты сұрақ бар: шаблонды жүзеге асыратын сыныптың Singletonмұрагерлері болуы мүмкін бе? Бұл жағдайда тіпті рефлексия күшсіз болуы мүмкін бе? Мақала бойынша пікіріңізді және түсініктемелерде жауапты жазыңыз, сонымен қатар сұрақтарыңызды қойыңыз! Reflection API-дің шынайы күші Runtime Annotations-пен үйлеседі, ол туралы болашақта Java-ның қараңғы жағы туралы мақалада айтатын боламыз. Назарларыңызға рахмет!
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION