CodeGym /Java Blog /यादृच्छिक /रिफ्लेक्शन API: प्रतिबिंब. जावाची काळी बाजू
John Squirrels
पातळी 41
San Francisco

रिफ्लेक्शन API: प्रतिबिंब. जावाची काळी बाजू

यादृच्छिक या ग्रुपमध्ये प्रकाशित केले
तरुण पाडावन, नमस्कार. या लेखात, मी तुम्हाला फोर्सबद्दल सांगेन, एक अशी शक्ती जी Java प्रोग्रामर केवळ अशक्य वाटणाऱ्या परिस्थितीत वापरतात. Java ची गडद बाजू म्हणजे रिफ्लेक्शन API. 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);
   }
}
तुम्ही बघू शकता, हा एक अतिशय मूलभूत वर्ग आहे. पॅरामीटर्ससह कन्स्ट्रक्टर मुद्दाम टिप्पणी करतो. आम्ही त्यावर नंतर परत येऊ. जर तुम्ही वर्गातील सामग्री काळजीपूर्वक पाहिली, तर तुम्हाला कदाचित नाव फील्डसाठी गेटरची अनुपस्थिती लक्षात आली असेल . नाव फील्ड स्वतःच खाजगी प्रवेश सुधारकाने चिन्हांकित केले आहे : आम्ही ते वर्गाच्या बाहेर प्रवेश करू शकत नाही याचा अर्थ आम्ही त्याचे मूल्य पुनर्प्राप्त करू शकत नाही . " मग काय प्रॉब्लेम आहे ?" तुम्ही म्हणता. " गेटर जोडा किंवा ऍक्सेस मॉडिफायर बदला". आणि तुम्ही बरोबर असाल, तोपर्यंतMyClassसंकलित AAR लायब्ररीमध्ये किंवा बदल करण्याची क्षमता नसलेल्या दुसर्‍या खाजगी मॉड्यूलमध्ये होते. सराव मध्ये, हे सर्व वेळ घडते. आणि काही निष्काळजी प्रोग्रामर फक्त गेटर लिहायला विसरले . प्रतिबिंब लक्षात ठेवण्याची हीच वेळ आहे! वर्गाच्या खाजगी नाव फील्डवर जाण्याचा प्रयत्न करूया 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
}
नुकतेच काय घडले याचे विश्लेषण करूया. जावामध्ये, नावाचा एक अद्भुत वर्ग आहे Class. हे एक्झिक्यूटेबल Java ऍप्लिकेशनमधील वर्ग आणि इंटरफेसचे प्रतिनिधित्व करते. Classआम्ही आणि मधील संबंध कव्हर करणार नाही ClassLoader, कारण तो या लेखाचा विषय नाही. पुढे, या वर्गाची फील्ड पुनर्प्राप्त करण्यासाठी, तुम्हाला getFields()पद्धत कॉल करणे आवश्यक आहे. ही पद्धत या वर्गाची सर्व प्रवेशयोग्य फील्ड परत करेल. हे आमच्यासाठी कार्य करत नाही, कारण आमचे क्षेत्र खाजगी आहे , म्हणून आम्ही getDeclaredFields()पद्धत वापरतो. ही पद्धत वर्ग फील्डची अॅरे देखील देते, परंतु आता यात खाजगी आणि संरक्षित फील्ड समाविष्ट आहेत. या प्रकरणात, आम्हाला स्वारस्य असलेल्या फील्डचे नाव माहित आहे, म्हणून आम्ही getDeclaredField(String)पद्धत वापरू शकतो, कुठेStringइच्छित फील्डचे नाव आहे. टीप: getFields()आणि getDeclaredFields()पालक वर्गाची फील्ड परत करू नका! मस्त. आम्हाला Fieldआमच्या नावाचा संदर्भ देणारी एक वस्तू मिळाली . फील्ड सार्वजनिक नसल्यामुळे , आम्हाला त्यासह कार्य करण्यासाठी प्रवेश मंजूर करणे आवश्यक आहे. पद्धत setAccessible(true)आम्हाला पुढे जाऊ देते. आता नाव फील्ड आमच्या पूर्ण नियंत्रणाखाली आहे! Fieldआपण ऑब्जेक्टच्या get(Object)पद्धतीवर कॉल करून त्याचे मूल्य पुनर्प्राप्त करू शकता , जेथे Objectआमच्या वर्गाचे उदाहरण आहे MyClass. आम्ही प्रकारात रूपांतरित करतो आणि आमच्या नावString व्हेरिएबलला मूल्य नियुक्त करतो . जर आम्हाला नाव फील्डमध्ये नवीन मूल्य सेट करण्यासाठी सेटर सापडला नाही , तर तुम्ही सेट पद्धत वापरू शकता:

field.set(myClass, (String) "new value");
अभिनंदन! तुम्ही नुकतेच प्रतिबिंबाच्या मूलभूत गोष्टींमध्ये प्रभुत्व मिळवले आहे आणि खाजगी क्षेत्रात प्रवेश केला आहे! try/catchब्लॉक आणि हाताळल्या जाणार्‍या अपवादांच्या प्रकारांकडे लक्ष द्या . IDE तुम्हाला सांगेल की त्यांची उपस्थिती स्वतःच आवश्यक आहे, परंतु ते येथे का आहेत हे तुम्ही त्यांच्या नावावरून स्पष्टपणे सांगू शकता. पुढे! तुमच्या लक्षात आले असेल की, आमच्या MyClassवर्गात आधीपासूनच वर्ग डेटाबद्दल माहिती प्रदर्शित करण्याची पद्धत आहे:

private void printData(){
       System.out.println(number + name);
   }
पण या प्रोग्रामरने इथेही बोटांचे ठसे सोडले. पद्धतीमध्ये खाजगी प्रवेश सुधारक आहे आणि प्रत्येक वेळी डेटा प्रदर्शित करण्यासाठी आम्हाला स्वतःचा कोड लिहावा लागेल. काय गोंधळ. आमचे प्रतिबिंब कुठे गेले? खालील फंक्शन लिहा:

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
}
हुर्रे! आता आमच्याकडे वर्गाच्या खाजगी पद्धतीत प्रवेश आहे. पण जर या पद्धतीत वाद असतील तर काय आणि कन्स्ट्रक्टरने टिप्पणी का दिली? प्रत्येक गोष्ट स्वतःच्या ठरलेल्या वेळेत. सुरुवातीला दिलेल्या व्याख्येवरून हे स्पष्ट आहे की रिफ्लेक्शन तुम्हाला रन टाइममध्ये (प्रोग्राम चालू असताना) वर्गाची उदाहरणे तयार करू देते! वर्गाचे पूर्ण नाव वापरून आपण ऑब्जेक्ट तयार करू शकतो. वर्गाचे पूर्ण नाव हे त्याच्या पॅकेजच्या मार्गासह वर्गाचे नाव आहे .
रिफ्लेक्शन API: प्रतिबिंब.  जावाची काळी बाजू - २
माझ्या पॅकेज पदानुक्रमात, 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 मध्ये लोड केले जात नाहीत. जर तुमचा कोड क्लासचा संदर्भ देत नसेल MyClass, तर ClassLoaderJVM मध्ये क्लास लोड करण्यासाठी जबाबदार असणारा, क्लास कधीही लोड करणार नाही. म्हणजेच तुम्हाला 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टू कॉल पद्धती वापरताना तेच असेल . हे प्रश्न निर्माण करते: रिफ्लेक्शनद्वारे कन्स्ट्रक्टरला कॉल करणे केव्हा उपयोगी पडते? सुरवातीला आधीच नमूद केल्याप्रमाणे, जावा रिफ्लेक्शन API शिवाय आधुनिक जावा तंत्रज्ञान मिळू शकत नाही. उदाहरणार्थ, डिपेंडन्सी इंजेक्शन (DI), जे लोकप्रिय डेरेर तयार करण्यासाठी पद्धती आणि रचनाकारांच्या प्रतिबिंबांसह भाष्य एकत्र करते.Android विकासासाठी लायब्ररी. हा लेख वाचल्यानंतर, तुम्ही आत्मविश्वासाने जावा रिफ्लेक्शन API च्या मार्गाने स्वतःला शिक्षित समजू शकता. ते परावर्तनाला जावाची गडद बाजू म्हणत नाहीत. हे OOP प्रतिमान पूर्णपणे खंडित करते. Java मध्ये, encapsulation इतरांना काही प्रोग्राम घटकांमध्ये प्रवेश लपवते आणि प्रतिबंधित करते. जेव्हा आम्ही खाजगी सुधारक वापरतो, तेव्हा आमचा हेतू असतो की ते फील्ड ज्या वर्गात आहे त्या वर्गातूनच प्रवेश मिळावा. आणि आम्ही या तत्त्वावर आधारित प्रोग्रामचे त्यानंतरचे आर्किटेक्चर तयार करतो. या लेखात, आम्ही पाहिले आहे की तुम्ही कुठेही जबरदस्तीने परावर्तन कसे करू शकता. सर्जनशील डिझाइन नमुना सिंगलटनआर्किटेक्चरल सोल्यूशन म्हणून याचे एक चांगले उदाहरण आहे. मूळ कल्पना अशी आहे की हा पॅटर्न अंमलात आणणारा वर्ग संपूर्ण प्रोग्रामच्या अंमलबजावणीदरम्यान फक्त एकच प्रसंग असेल. डीफॉल्ट कन्स्ट्रक्टरमध्ये खाजगी प्रवेश सुधारक जोडून हे पूर्ण केले जाते. आणि प्रोग्रामरने रिफ्लेक्शन वापरले तर अशा क्लासेसची आणखी उदाहरणे तयार केली तर ते फार वाईट होईल. तसे, मी अलीकडेच एका सहकर्मीला एक अतिशय मनोरंजक प्रश्न विचारल्याचे ऐकले: सिंगलटन पॅटर्न लागू करणारा वर्ग वारशाने मिळू शकतो का? असे होऊ शकते की, या प्रकरणात, प्रतिबिंब देखील शक्तीहीन असेल? लेखाबद्दल तुमचा अभिप्राय आणि तुमचे उत्तर खालील टिप्पण्यांमध्ये द्या आणि तेथे तुमचे स्वतःचे प्रश्न विचारा!
टिप्पण्या
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION