JavaRush /Java блогу /Random-KY /Reflection API. Рефлексия. Java караңгы тарабы
Oleksandr Klymenko
Деңгээл
Харків

Reflection API. Рефлексия. Java караңгы тарабы

Группада жарыяланган
Салам, жаш Падаван. Бул макалада мен сизге Күч жөнүндө айтып берем, анын күчүн Java программисттери үмүтсүз көрүнгөн кырдаалда гана колдонушат. Ошентип, Javaнын караңгы тарабы -Reflection API
Reflection API.  Рефлексия.  Javaнын караңгы тарабы - 1
Java тorнде чагылдыруу Java Reflection API аркылуу ишке ашырылат. Бул эмнени чагылдыруу? Интернетте да популярдуу болгон кыска жана так аныктамасы бар. Рефлексия (Late Reflexio – артка кайтуу) – программанын аткарылышы учурундагы маалыматтарды изилдөө механизми. Рефлексия талаалар, методдор жана класстын конструкторлору жөнүндө маалыматты текшерүүгө мүмкүндүк берет. Рефлексия механизми өзү компиляция учурунда жок болгон, бирок программаны аткарууда пайда болгон типтерди иштетүүгө мүмкүндүк берет. Рефлексия жана каталарды билдирүү үчүн логикалык когеренттүү моделдин болушу туура динамикалык codeду түзүүгө мүмкүндүк берет. Башкача айтканда, javaда рефлексия кантип иштээрин түшүнүү сиз үчүн бир катар укмуштуу мүмкүнчүлүктөрдү ачат. Сиз класстарды жана алардын компоненттерин түзмө-түз жонглёр кыла аласыз.
Reflection API.  Рефлексия.  Javaнын караңгы тарабы - 2
Бул жерде чагылдырууга мүмкүндүк берген негизги тизмеси:
  • an objectтин классын табуу/аныктоо;
  • Класстын модификаторлору, талаалары, методдору, константалары, конструкторлору жана суперкласстары жөнүндө маалымат алуу;
  • Ишке ашырылган интерфейске/интерфейстерге кайсы ыкмалар таандык экенин табыңыз;
  • Класстын үлгүсүн түзүңүз жана программа аткарылмайынча класстын аты белгисиз;
  • Объект талаасынын маанисин аты боюнча алуу жана коюу;
  • Объекттин ыкмасын аты менен чакырыңыз.
Рефлексия дээрлик бардык заманбап 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"а үчүн" жок экенин көргөн чыгарсыз 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(), бул ыкма бизге класстын бардык жеткorктүү талааларын кайтарып берет. Бул бизге ылайыктуу эмес, анткени биздин талаабыз private, ошондуктан биз методду колдонобуз.Бул getDeclaredFields()ыкма класс талааларынын массивдерин да кайтарат, бирок азыр экөө privateтең protected. Биздин шартта, биз бизди кызыктырган талаанын атын билебиз жана биз методду колдоно алабыз getDeclaredField(String), бул жерде Stringкаалаган талаанын аты. Эскертүү: getFields()жана getDeclaredFields()ата-энелер классынын талааларын кайтарба! Field Жакшы, биз шилтемеси бар an objectти алдык 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Жана биз колдонгон an objectти чакыруу үчүн 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Бул an 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сиз кыла алbyte. Мисалы, DI (Dependency Injection), мында annotationлар ыкмаларды жана конструкторлорду чагылдыруу менен айкалышып, Android иштеп чыгуусунда популярдуу болгон Dagger китепканасын түзөт. Бул макаланы окугандан кийин, сиз өзүңүздү Reflection API механизмдеринде ишенимдүү деп эсептей аласыз. Рефлексия жаванын караңгы жагы деп бекеринен айтылган эмес. Бул толугу менен OOP парадигмасын бузат. Java тorнде инкапсуляция программанын кээ бир компоненттеринин башкаларына кирүү мүмкүнчүлүгүн жашыруу жана чектөө үчүн кызмат кылат. Жеке модификаторду колдонуу менен биз бул талаага кирүү бул талаа бар класстын ичинде гана болоорун билдиребиз, ошонун негизинде биз программанын андан аркы архитектурасын курабыз. Бул макалада биз каалаган жерге жетүү үчүн чагылдырууну кантип колдонсоңуз болорун көрдүк. Архитектуралык чечим түрүндөгү жакшы мисал генеративдик дизайн үлгүсү болуп саналат - Singleton. Анын негизги идеясы программанын бүткүл иштөөсүндө, бул шаблонду ишке ашырган класста бир гана нуска болушу керек. Бул конструктор үчүн демейки кирүү модификаторун купуяга коюу менен жасалат. Ал эми кандайдыр бир программист өзүнүн ой жүгүртүүсү менен ушундай класстарды түзсө, бул абдан жаман болот. Баса, жакында эле менин кызматкеримден уккан абдан кызыктуу суроо бар: шаблонду ишке ашырган класстын Singletonмураскорлору болушу мүмкүнбү? Бул учурда ой жүгүртүү да алсыз болушу мүмкүнбү? Макала боюнча пикириңизди жана жоопту комментарийге жазыңыз, ошондой эле суроолоруңузду бериңиз! Reflection API'нин чыныгы күчү Runtime Annotations менен айкалышып келет, ал жөнүндө биз келечектеги макалада Javaнын караңгы тарабы жөнүндө сүйлөшөбүз. Конул бурганын учун рахмат!
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION