प्रतिबिंब एपीआई किसके लिए है?

जावा का प्रतिबिंब तंत्र एक डेवलपर को उनके नाम जाने बिना रनटाइम पर कक्षाओं, इंटरफेस, फ़ील्ड और विधियों के बारे में परिवर्तन करने और जानकारी प्राप्त करने की अनुमति देता है।

रिफ्लेक्शन एपीआई आपको नई वस्तुओं को बनाने, विधियों को कॉल करने और फ़ील्ड मान प्राप्त करने या सेट करने देता है।

आइए प्रतिबिंब का उपयोग करके आप जो कुछ भी कर सकते हैं उसकी एक सूची बनाएं:

  • किसी वस्तु के वर्ग को पहचानें/निर्धारित करें
  • क्लास मॉडिफायर्स, फील्ड्स, मेथड्स, कॉन्स्टेंट्स, कंस्ट्रक्टर्स और सुपरक्लासेस के बारे में जानकारी प्राप्त करें
  • पता लगाएं कि कौन सी विधियां कार्यान्वित इंटरफेस से संबंधित हैं
  • उस वर्ग का एक उदाहरण बनाएँ जिसका वर्ग नाम तब तक ज्ञात नहीं है जब तक कि कार्यक्रम निष्पादित नहीं हो जाता
  • नाम से इंस्टेंस फ़ील्ड का मान प्राप्त करें और सेट करें
  • नाम से एक इंस्टेंस विधि को कॉल करें

लगभग सभी आधुनिक जावा प्रौद्योगिकियां प्रतिबिंब का उपयोग करती हैं। यह आज के अधिकांश जावा/जावा ईई ढांचे और पुस्तकालयों के अंतर्गत आता है, उदाहरण के लिए:

  • वेब एप्लिकेशन बनाने के लिए स्प्रिंग फ्रेमवर्क
  • JUnit परीक्षण ढांचा

यदि आपने पहले कभी इन तंत्रों का सामना नहीं किया है, तो आप शायद पूछ रहे हैं कि यह सब क्यों आवश्यक है। उत्तर काफी सरल है, लेकिन बहुत अस्पष्ट भी है: प्रतिबिंब नाटकीय रूप से लचीलेपन और हमारे एप्लिकेशन और कोड को अनुकूलित करने की क्षमता को बढ़ाता है।

लेकिन हमेशा फायदे और नुकसान होते हैं। तो चलिए कुछ विपक्षों का जिक्र करते हैं:

  • आवेदन सुरक्षा का उल्लंघन। प्रतिबिंब हमें उस कोड तक पहुंचने देता है जिसे हमें नहीं करना चाहिए (encapsulation का उल्लंघन)।
  • सुरक्षा प्रतिबंध। प्रतिबिंब के लिए रनटाइम अनुमतियों की आवश्यकता होती है जो सुरक्षा प्रबंधक चलाने वाले सिस्टम के लिए उपलब्ध नहीं हैं।
  • कम प्रदर्शन। जावा में प्रतिबिंब वर्ग को लोड करने के लिए खोजने के लिए क्लासपाथ को स्कैन करके गतिशील रूप से प्रकार निर्धारित करता है। यह प्रोग्राम के प्रदर्शन को कम करता है।
  • बनाए रखना मुश्किल है। प्रतिबिंब का उपयोग करने वाले कोड को पढ़ना और डिबग करना मुश्किल है। यह कम लचीला और बनाए रखने में कठिन है।

प्रतिबिंब एपीआई का उपयोग कर कक्षाओं के साथ काम करना

सभी प्रतिबिंब संचालन एक java.lang.Class ऑब्जेक्ट से शुरू होते हैं। प्रत्येक प्रकार की वस्तु के लिए, java.lang.Class का एक अपरिवर्तनीय उदाहरण बनाया जाता है। यह ऑब्जेक्ट गुण प्राप्त करने, नई ऑब्जेक्ट बनाने और कॉलिंग विधियों के तरीके प्रदान करता है।

आइए java.lang.Class के साथ काम करने के लिए बुनियादी तरीकों की सूची देखें :

तरीका कार्य
स्ट्रिंग गेटनाम (); वर्ग का नाम लौटाता है
int getModifiers (); एक्सेस संशोधक लौटाता है
पैकेज गेटपैकेज (); पैकेज के बारे में जानकारी लौटाता है
क्लास गेटसुपरक्लास (); मूल वर्ग के बारे में जानकारी लौटाता है
कक्षा [] getInterfaces (); इंटरफेस की एक सरणी देता है
कंस्ट्रक्टर [] getConstructors (); क्लास कंस्ट्रक्टर के बारे में जानकारी लौटाता है
फील्ड्स [] getFields (); एक वर्ग के क्षेत्र लौटाता है
फ़ील्ड getField (स्ट्रिंग फ़ील्डनाम); नाम से किसी वर्ग का विशिष्ट क्षेत्र लौटाता है
विधि [] getMethods (); तरीकों की एक सरणी देता है

कक्षाओं, इंटरफेस, फ़ील्ड और विधियों के बारे में डेटा प्राप्त करने के लिए ये सबसे महत्वपूर्ण तरीके हैं। ऐसी विधियाँ भी हैं जो आपको फ़ील्ड मान प्राप्त करने या सेट करने और निजी फ़ील्ड तक पहुँचने देती हैं। हम उन्हें थोड़ी देर बाद देखेंगे।

अभी हम java.lang.Class को प्राप्त करने के बारे में बात करेंगे । हमारे पास ऐसा करने के तीन तरीके हैं।

1. Class.forName का उपयोग करना

चल रहे एप्लिकेशन में, आपको कक्षा प्राप्त करने के लिए forName (स्ट्रिंग क्लासनाम) विधि का उपयोग करना होगा।

यह कोड दर्शाता है कि हम प्रतिबिंब का उपयोग करके कक्षाएं कैसे बना सकते हैं। चलिए एक व्यक्ति वर्ग बनाते हैं जिसके साथ हम काम कर सकते हैं:


package com.company;

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

और हमारे उदाहरण का दूसरा भाग वह कोड है जो प्रतिबिंब का उपयोग करता है:


public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

यह दृष्टिकोण संभव है यदि वर्ग का पूरा नाम ज्ञात हो। तब आप स्टैटिक Class.forName() विधि का उपयोग करके संबंधित वर्ग प्राप्त कर सकते हैं । इस पद्धति का उपयोग आदिम प्रकारों के लिए नहीं किया जा सकता है।

2. क्लास का उपयोग करना

यदि कोई प्रकार उपलब्ध है लेकिन उसका कोई उदाहरण नहीं है, तो आप प्रकार के नाम में .class जोड़कर कक्षा प्राप्त कर सकते हैं। आदिम प्रकार की कक्षा प्राप्त करने का यह सबसे आसान तरीका है।


Class aClass = Person.class;

3. .getClass() का उपयोग करना

यदि कोई ऑब्जेक्ट उपलब्ध है, तो क्लास प्राप्त करने का सबसे आसान तरीका है object.getClass() को कॉल करना ।


Person person = new Person();
Class aClass = person.getClass();

पिछले दो दृष्टिकोणों में क्या अंतर है?

A.class का उपयोग करें यदि आप जानते हैं कि कोडिंग के समय आप किस क्लास ऑब्जेक्ट में रुचि रखते हैं। यदि कोई उदाहरण उपलब्ध नहीं है, तो आपको .class का उपयोग करना चाहिए ।

एक वर्ग के तरीके प्राप्त करना

आइए उन तरीकों को देखें जो हमारी कक्षा के तरीकों को लौटाते हैं: getDeclaredMethods() और getMethods()

getDeclaredMethods() एक सरणी देता है जिसमें सार्वजनिक, निजी, डिफ़ॉल्ट और संरक्षित विधियों सहित क्लास ऑब्जेक्ट द्वारा दर्शाए गए वर्ग या इंटरफ़ेस के सभी घोषित तरीकों के लिए विधि ऑब्जेक्ट होते हैं, लेकिन विरासत में मिली विधियाँ नहीं

getMethods() क्लास ऑब्जेक्ट द्वारा दर्शाए गए वर्ग या इंटरफ़ेस के सभी सार्वजनिक तरीकों के लिए मेथड ऑब्जेक्ट वाली एक सरणी देता है - जो कि क्लास या इंटरफ़ेस द्वारा घोषित की जाती हैं, साथ ही सुपरक्लास और सुपरइंटरफेस से विरासत में मिली हैं

आइए देखें कि उनमें से प्रत्येक कैसे काम करता है।

चलिए getDeclaredMethods() से शुरू करते हैं । दो तरीकों के बीच के अंतर को समझने में हमारी मदद करने के लिए, नीचे हम अमूर्त संख्या वर्ग के साथ काम करेंगे। चलिए एक स्टैटिक मेथड लिखते हैं जो हमारे मेथड ऐरे को List<String> में बदल देगा :


import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

यहाँ इस कोड को चलाने का परिणाम है:

बाइटवैल्यू
शॉर्टवैल्यू
इंटवैल्यू
लॉन्गवैल्यू
फ्लोट फ्लोटवैल्यू;
doubleValue

ये संख्या वर्ग के अंदर घोषित तरीके हैं । GetMethods() क्या लौटाता है? उदाहरण में दो पंक्तियों को बदलते हैं:


final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

ऐसा करने से, हम विधियों के निम्नलिखित सेट देखेंगे:

बाइटवैल्यू
शॉर्टवैल्यू
इंटवैल्यू
लॉन्गवैल्यू
फ्लोट फ्लोटवैल्यू;
डबलवैल्यू
प्रतीक्षा
प्रतीक्षा
प्रतीक्षा स्ट्रिंग हैशकोड getClass
के बराबर है सूचित करें सभी को सूचित करें




क्योंकि सभी वर्ग ऑब्जेक्ट इनहेरिट करते हैं, हमारा तरीका ऑब्जेक्ट क्लास के सार्वजनिक तरीकों को भी लौटाता है ।

एक वर्ग के क्षेत्र प्राप्त करना

वर्ग के क्षेत्र प्राप्त करने के लिए getFields और getDeclaredFields विधियों का उपयोग किया जाता है एक उदाहरण के रूप में, आइए LocalDateTime क्लास को देखें। हम अपना कोड फिर से लिखेंगे:


import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

इस कोड को निष्पादित करने के परिणामस्वरूप, हमें LocalDateTime वर्ग में निहित फ़ील्ड्स का सेट मिलता है।

न्यूनतम
अधिकतम
सीरियल संस्करण यूआईडी
दिनांक
समय

विधियों की हमारी पिछली परीक्षा के अनुरूप, देखते हैं कि क्या होता है यदि हम कोड को थोड़ा बदलते हैं:


final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

आउटपुट:

मिन
मैक्स

अब आइए इन तरीकों के बीच के अंतर को समझते हैं।

GetDeclaredFields विधि इस वर्ग या इंटरफ़ेस द्वारा घोषित सभी क्षेत्रों के लिए फ़ील्ड ऑब्जेक्ट्स की एक सरणी लौटाती हैकक्षावस्तु।

GetFields विधि वर्ग या इंटरफ़ेस द्वारा दर्शाए गए सभी सार्वजनिक क्षेत्रों के लिए फ़ील्ड ऑब्जेक्ट्स की एक सरणी लौटाती हैकक्षावस्तु।

अब आइए LocalDateTime के अंदर देखें ।

वर्ग कामिनऔरमैक्सफ़ील्ड सार्वजनिक हैं, जिसका अर्थ है कि वे getFields विधि के माध्यम से दिखाई देंगे । इसके विपरीत, दतारीख,समय,सीरियलवर्जनयूआईडीविधियों में निजी संशोधक होता है, जिसका अर्थ है कि वे getFields विधि के माध्यम से दिखाई नहीं देंगे , लेकिन हम उन्हें getDeclaredFields का उपयोग करके प्राप्त कर सकते हैं । इस प्रकार हम निजी क्षेत्रों के लिए फील्ड ऑब्जेक्ट्स तक पहुंच सकते हैं।

अन्य तरीकों का विवरण

अब इसका समय क्लास क्लास के कुछ तरीकों के बारे में बात करने का है , अर्थात्:

तरीका कार्य
getModifiers हमारी कक्षा के लिए संशोधक प्राप्त करना
getPackage पैकेज प्राप्त करना जिसमें हमारी कक्षा शामिल है
getSuperclass अभिभावक वर्ग प्राप्त करना
getInterfaces एक वर्ग द्वारा कार्यान्वित इंटरफेस की एक सरणी प्राप्त करना
getName पूरी तरह से योग्य वर्ग का नाम प्राप्त करना
getSimpleName एक वर्ग का नाम प्राप्त करना

getModifiers ()

संशोधक को a का उपयोग करके एक्सेस किया जा सकता हैकक्षावस्तु।

संशोधक सार्वजनिक , स्थिर , इंटरफ़ेस आदि जैसे कीवर्ड हैं । हम getModifiers () विधि का उपयोग करके संशोधक प्राप्त करते हैं:


Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

यह कोड एक का मान सेट करता हैint यहाँचर जो थोड़ा सा क्षेत्र है। संबंधित बिट को सेट या साफ़ करके प्रत्येक एक्सेस संशोधक को चालू या बंद किया जा सकता है। हम java.lang.reflect.Modifier वर्ग में विधियों का उपयोग करके संशोधक की जांच कर सकते हैं :


import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

याद रखें कि हमारे व्यक्ति की घोषणा कैसी दिखती है:


public class Person {
   …
}

हमें निम्न आउटपुट मिलता है:

वर्ग संशोधक: 1
सार्वजनिक है: सत्य
स्थिर है: असत्य
अंतिम है: असत्य
सार है: असत्य
है इंटरफ़ेस: असत्य

यदि हम अपनी कक्षा को सार बनाते हैं, तो हमारे पास:


public abstract class Person { … }

और यह आउटपुट:

वर्ग संशोधक: 1025
सार्वजनिक है: सत्य
स्थिर है: असत्य
अंतिम है: असत्य
सार है: सत्य
है इंटरफ़ेस: असत्य

हमने एक्सेस संशोधक को बदल दिया है, जिसका अर्थ है कि हमने संशोधक वर्ग के स्थिर तरीकों के माध्यम से लौटाए गए डेटा को भी बदल दिया है।

गेटपैकेज ()

केवल एक वर्ग को जानकर हम उसके पैकेज के बारे में जानकारी प्राप्त कर सकते हैं:


Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

गेटसुपरक्लास ()

यदि हमारे पास एक क्लास ऑब्जेक्ट है, तो हम उसके मूल वर्ग तक पहुँच सकते हैं:


public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

हमें प्रसिद्ध वस्तु वर्ग मिलता है:


class java.lang.Object

लेकिन अगर हमारी कक्षा में एक और मूल वर्ग है, तो हम इसे इसके बजाय देखेंगे:


package com.company;

class Human {
    // Some info
}

public class Person extends Human {
    private int age;
    private String name;

    // Some info
}

यहां हमें अपना मूल वर्ग मिलता है:


class com.company.Human

गेटइंटरफेस ()

यहां बताया गया है कि हम कक्षा द्वारा कार्यान्वित इंटरफेस की सूची कैसे प्राप्त कर सकते हैं:


public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

और अपने व्यक्ति वर्ग को बदलना न भूलें :


public class Person implements Serializable { … }

आउटपुट:

[इंटरफ़ेस java.io.Serializable]

एक वर्ग कई इंटरफेस को लागू कर सकता है। इसलिए हमें एक सरणी मिलती हैकक्षावस्तुओं। जावा रिफ्लेक्शन एपीआई में, इंटरफेस का भी प्रतिनिधित्व किया जाता हैकक्षावस्तुओं।

कृपया ध्यान दें: विधि केवल निर्दिष्ट वर्ग द्वारा लागू किए गए इंटरफेस को लौटाती है, उसके मूल वर्ग को नहीं। वर्ग द्वारा कार्यान्वित इंटरफेस की पूरी सूची प्राप्त करने के लिए, आपको वर्तमान वर्ग और उसके सभी पूर्वजों को वंशानुक्रम श्रृंखला को संदर्भित करने की आवश्यकता है।

getName () और getSimpleName () और getCanonicalName ()

आइए एक आदिम, एक नेस्टेड वर्ग, एक अनाम वर्ग और स्ट्रिंग वर्ग को शामिल करते हुए एक उदाहरण लिखें:


public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitive)");
        printNamesForClass(String.class, "String.class (ordinary class)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

हमारे कार्यक्रम का परिणाम:

int वर्ग (आदिम):
getName ()): int
getCanonicalName ()): int
getSimpleName ()): int
getTypeName (): int

String.class (साधारण वर्ग):
getName ()): java.lang.String
getCanonicalName () ): java.lang.String
getSimpleName ()): स्ट्रिंग
getTypeName (): java.lang.String

java.util.HashMap.SimpleEntry.class (नेस्टेड क्लास):
getName ()): java.util.AbstractMap$SimpleEntry
getCanonicalName ( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (अनाम आंतरिक वर्ग):
getName() ): TestReflection$1
getCanonicalName ()): अशक्त
getSimpleName()):
getTypeName(): TestReflection$1

अब हमारे प्रोग्राम के आउटपुट का विश्लेषण करते हैं:

  • getName() इकाई का नाम लौटाता है।

  • getCanonicalName() जावा लैंग्वेज स्पेसिफिकेशन द्वारा परिभाषित बेस क्लास का कैनोनिकल नाम लौटाता है। यदि आधार वर्ग का कोई विहित नाम नहीं है (अर्थात, यदि यह एक स्थानीय या अनाम वर्ग है या एक सरणी है जिसका तत्व प्रकार एक विहित नाम नहीं है) तो शून्य वापस आ जाता है।

  • getSimpleName() स्रोत कोड में निर्दिष्ट बेस क्लास का सरल नाम देता है। बेस क्लास गुमनाम होने पर एक खाली स्ट्रिंग लौटाता है।

  • getTypeName() इस प्रकार के नाम के लिए एक सूचनात्मक स्ट्रिंग लौटाता है।