CodeGym /Java Blog /अनियमित /प्रतिबिंब एपीआई: प्रतिबिंब। जावा का डार्क साइड
John Squirrels
स्तर 41
San Francisco

प्रतिबिंब एपीआई: प्रतिबिंब। जावा का डार्क साइड

अनियमित ग्रुप में प्रकाशित
अभिवादन, युवा पदवन। इस लेख में, मैं आपको फ़ोर्स के बारे में बताऊंगा, एक ऐसी शक्ति जिसे जावा प्रोग्रामर केवल असंभव प्रतीत होने वाली स्थितियों में उपयोग करते हैं। जावा का डार्क साइड रिफ्लेक्शन एपीआई है। जावा में, प्रतिबिंब को जावा परावर्तन 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एक संकलित एएआर पुस्तकालय में या किसी अन्य निजी मॉड्यूल में बदलाव करने की क्षमता नहीं थी। व्यवहार में, यह हर समय होता है। और कुछ लापरवाह प्रोग्रामर बस गेटटर लिखना भूल गए । प्रतिबिंब को याद करने का यही सही समय है! आइए 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। यह निष्पादन योग्य जावा एप्लिकेशन में कक्षाओं और इंटरफेस का प्रतिनिधित्व करता है। Classहम और के बीच संबंध को कवर नहीं करेंगे ClassLoader, क्योंकि यह इस लेख का विषय नहीं है। अगला, इस वर्ग के क्षेत्रों को पुनः प्राप्त करने के लिए, आपको getFields()विधि को कॉल करने की आवश्यकता है। यह विधि इस वर्ग के सभी सुलभ क्षेत्रों को लौटा देगी। यह हमारे लिए काम नहीं करता है, क्योंकि हमारा क्षेत्र निजी है , इसलिए हम getDeclaredFields()विधि का उपयोग करते हैं। यह विधि वर्ग क्षेत्रों की एक सरणी भी लौटाती है, लेकिन अब इसमें निजी और संरक्षित क्षेत्र शामिल हैं। इस मामले में, हम उस क्षेत्र का नाम जानते हैं जिसमें हम रुचि रखते हैं, इसलिए हम getDeclaredField(String)विधि का उपयोग कर सकते हैं, जहांStringवांछित क्षेत्र का नाम है। टिप्पणी: getFields()और getDeclaredFields()माता-पिता वर्ग के क्षेत्र वापस न करें! महान। हमें अपने नाम कोField संदर्भित करने वाली एक वस्तु मिली । चूंकि फ़ील्ड सार्वजनिक नहीं थी , इसलिए हमें इसके साथ काम करने की अनुमति देनी चाहिए। विधि हमें आगे बढ़ने देती है। अब नाम क्षेत्र हमारे पूर्ण नियंत्रण में है! आप ऑब्जेक्ट की विधि को कॉल करके इसका मान प्राप्त कर सकते हैं , जहाँ हमारी कक्षा का एक उदाहरण है। हम टाइप को कन्वर्ट करते हैं और वैल्यू को हमारे नाम वेरिएबल में असाइन करते हैं। यदि हमें नाम फ़ील्ड में नया मान सेट करने के लिए कोई सेटर नहीं मिल रहा है , तो आप सेट विधि का उपयोग कर सकते हैं: setAccessible(true)Fieldget(Object)ObjectMyClassString

field.set(myClass, (String) "new value");
बधाई हो! आपने अभी प्रतिबिंब की मूल बातों में महारत हासिल की है और एक निजी क्षेत्र में प्रवेश किया है! try/catchब्लॉक और संभाले जा रहे अपवादों के प्रकार पर ध्यान दें । आईडीई आपको बताएगा कि उनकी उपस्थिति स्वयं आवश्यक है, लेकिन आप उनके नामों से स्पष्ट रूप से बता सकते हैं कि वे यहां क्यों हैं। आगे बढ़ते रहना! जैसा कि आपने देखा होगा, हमारी 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का एक उदाहरण भी है । विधि के तर्क हैं, हालांकि हमारे पास कोई नहीं है। अब हम सूचना प्रदर्शित करने के लिए फ़ंक्शन का उपयोग करते हैं: MyClassArgsprintData

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
}
हुर्रे! अब हमारे पास कक्षा की निजी पद्धति तक पहुंच है। लेकिन क्या होगा अगर विधि में तर्क हैं, और निर्माता ने टिप्पणी क्यों की है? सब कुछ अपने नियत समय पर। शुरुआत में दी गई परिभाषा से यह स्पष्ट है कि प्रतिबिंब आपको रन टाइम पर एक क्लास का उदाहरण बनाने देता है (जब प्रोग्राम चल रहा होता है)! हम क्लास के पूरे नाम का उपयोग करके ऑब्जेक्ट बना सकते हैं। वर्ग का पूरा नाम वर्ग का नाम है, जिसमें इसके पैकेज का पथ भी शामिल है ।
प्रतिबिंब एपीआई: प्रतिबिंब।  जावा का डार्क साइड - 2
मेरे पैकेज पदानुक्रम में, MyClass का पूरा नाम "प्रतिबिंब.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
}
जब जावा एप्लिकेशन शुरू होता है, तो सभी कक्षाएं जेवीएम में लोड नहीं होती हैं। यदि आपका कोड कक्षा को संदर्भित नहीं करता है 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कॉल विधियों का उपयोग करते समय यह वही होगा । यह सवाल पूछता है: प्रतिबिंब के माध्यम से कन्स्ट्रक्टर को कॉल करना कब आसान होता है? जैसा कि शुरुआत में पहले ही उल्लेख किया गया है, आधुनिक जावा प्रौद्योगिकियां जावा रिफ्लेक्शन एपीआई के बिना नहीं मिल सकती हैं। उदाहरण के लिए, डिपेंडेंसी इंजेक्शन (DI), जो लोकप्रिय डेयर बनाने के लिए तरीकों और निर्माणकर्ताओं के प्रतिबिंब के साथ एनोटेशन को जोड़ती हैAndroid विकास के लिए पुस्तकालय। इस लेख को पढ़ने के बाद, आप विश्वास के साथ खुद को जावा रिफ्लेक्शन एपीआई के तरीकों से शिक्षित मान सकते हैं। वे जावा के अंधेरे पक्ष को कुछ भी नहीं कहते हैं। यह OOP प्रतिमान को पूरी तरह से तोड़ देता है। जावा में, एनकैप्सुलेशन कुछ प्रोग्राम घटकों तक दूसरों की पहुंच को छुपाता है और प्रतिबंधित करता है। जब हम निजी संशोधक का उपयोग करते हैं, तो हम चाहते हैं कि उस क्षेत्र को केवल उस वर्ग के भीतर से एक्सेस किया जाए जहां वह मौजूद है। और हम इस सिद्धांत के आधार पर प्रोग्राम के बाद के आर्किटेक्चर का निर्माण करते हैं। इस लेख में, हमने देखा है कि आप प्रतिबिंब का उपयोग कैसे कहीं भी अपना रास्ता तय करने के लिए कर सकते हैं। रचनात्मक डिजाइन पैटर्न सिंगलटनवास्तु समाधान के रूप में इसका एक अच्छा उदाहरण है। मूल विचार यह है कि इस पैटर्न को लागू करने वाले वर्ग के पास पूरे कार्यक्रम के निष्पादन के दौरान केवल एक उदाहरण होगा। यह निजी एक्सेस संशोधक को डिफ़ॉल्ट कन्स्ट्रक्टर में जोड़कर पूरा किया जाता है। और यह बहुत बुरा होगा यदि कोई प्रोग्रामर प्रतिबिंब का उपयोग करता है जो ऐसी कक्षाओं के अधिक उदाहरण बनाता है। वैसे, मैंने हाल ही में एक सहकर्मी को एक बहुत ही दिलचस्प सवाल पूछते हुए सुना है: क्या सिंगलटन पैटर्न को लागू करने वाली कक्षा विरासत में मिल सकती है? क्या ऐसा हो सकता है कि इस मामले में प्रतिबिंब भी शक्तिहीन होगा? लेख के बारे में अपनी प्रतिक्रिया दें और नीचे दी गई टिप्पणियों में अपना उत्तर दें, और वहां अपने प्रश्न पूछें!
टिप्पणियां
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION