Салам, жаш Падаван. Бул макалада мен сизге Күч жөнүндө айтып берем, анын күчүн Java программисттери үмүтсүз көрүнгөн кырдаалда гана колдонушат. Ошентип, Javaнын караңгы тарабы -
Java тorнде чагылдыруу Java Reflection API аркылуу ишке ашырылат. Бул эмнени чагылдыруу? Интернетте да популярдуу болгон кыска жана так аныктамасы бар. Рефлексия (Late Reflexio – артка кайтуу) – программанын аткарылышы учурундагы маалыматтарды изилдөө механизми. Рефлексия талаалар, методдор жана класстын конструкторлору жөнүндө маалыматты текшерүүгө мүмкүндүк берет. Рефлексия механизми өзү компиляция учурунда жок болгон, бирок программаны аткарууда пайда болгон типтерди иштетүүгө мүмкүндүк берет. Рефлексия жана каталарды билдирүү үчүн логикалык когеренттүү моделдин болушу туура динамикалык codeду түзүүгө мүмкүндүк берет. Башкача айтканда, javaда рефлексия кантип иштээрин түшүнүү сиз үчүн бир катар укмуштуу мүмкүнчүлүктөрдү ачат. Сиз класстарды жана алардын компоненттерин түзмө-түз жонглёр кыла аласыз.
Бул жерде чагылдырууга мүмкүндүк берген негизги тизмеси:
Менин иерархиямда
Reflection API
- an objectтин классын табуу/аныктоо;
- Класстын модификаторлору, талаалары, методдору, константалары, конструкторлору жана суперкласстары жөнүндө маалымат алуу;
- Ишке ашырылган интерфейске/интерфейстерге кайсы ыкмалар таандык экенин табыңыз;
- Класстын үлгүсүн түзүңүз жана программа аткарылмайынча класстын аты белгисиз;
- Объект талаасынын маанисин аты боюнча алуу жана коюу;
- Объекттин ыкмасын аты менен чакырыңыз.
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
талаасына кирүүгө аракет кылалы : name
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
}
Келгиле, бул жерде эмне болгонун аныктап көрөлү. 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
.
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нын караңгы тарабы жөнүндө сүйлөшөбүз. Конул бурганын учун рахмат!
GO TO FULL VERSION