Сәлем, жас Падаван. Бұл мақалада мен сізге күш туралы айтып беремін, оның күшін java бағдарламашылары үмітсіз болып көрінетін жағдайда ғана пайдаланады. Сонымен, Java-ның қараңғы жағы -
Java тіліндегі рефлексия Java Reflection API арқылы жүзеге асырылады. Бұл не рефлексия? Интернетте де танымал қысқа және нақты анықтамасы бар. Рефлексия (лат. Reflexio – кері қайту) – бағдарламаның орындалу барысында оның мәліметтерін зерттеу механизмі. Рефлексия өрістер, әдістер және класс конструкторлары туралы ақпаратты тексеруге мүмкіндік береді. Рефлексия механизмінің өзі компиляция кезінде жетіспейтін, бірақ бағдарламаны орындау кезінде пайда болатын типтерді өңдеуге мүмкіндік береді. Рефлексия және қателер туралы есеп беру үшін логикалық үйлесімді модельдің болуы дұрыс динамикалық codeты жасауға мүмкіндік береді. Басқаша айтқанда, 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
өрісіне кіруге тырысайық : 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()
, бұл әдіс бізге сыныптың барлық қолжетімді өрістерін қайтарады. Бұл бізге жарамайды, өйткені біздің өрісіміз 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
.
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-ның қараңғы жағы туралы мақалада айтатын боламыз. Назарларыңызға рахмет!
GO TO FULL VERSION