खोज…


परिचय

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

टिप्पणियों

प्रदर्शन

ध्यान रखें कि प्रतिबिंब प्रदर्शन को कम कर सकता है, इसका उपयोग केवल तब करें जब आपका कार्य प्रतिबिंब के बिना पूरा नहीं हो सकता।

जावा ट्यूटोरियल से प्रतिबिंब एपीआई :

क्योंकि प्रतिबिंब में ऐसे प्रकार शामिल होते हैं जो गतिशील रूप से हल किए जाते हैं, कुछ जावा वर्चुअल मशीन अनुकूलन नहीं किए जा सकते हैं। नतीजतन, चिंतनशील संचालन में उनके गैर-चिंतनशील समकक्षों की तुलना में धीमी गति से प्रदर्शन होता है, और उन्हें कोड के वर्गों से बचा जाना चाहिए जिन्हें प्रदर्शन-संवेदनशील अनुप्रयोगों में अक्सर कहा जाता है।

परिचय

मूल बातें

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

एक साधारण उदाहरण सार्वजनिक निर्माणकर्ताओं और किसी वर्ग के तरीकों को प्राप्त करना होगा:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

// This is a object representing the String class (not an instance of String!)
Class<String> clazz = String.class;

Constructor<?>[] constructors = clazz.getConstructors(); // returns all public constructors of String
Method[] methods = clazz.getMethods(); // returns all public methods from String and parents

इस जानकारी के साथ वस्तु को अलग करना और गतिशील रूप से विभिन्न तरीकों को कॉल करना संभव है।

चिंतन और सामान्य प्रकार

सामान्य प्रकार की जानकारी के लिए उपलब्ध है:

  • विधि पैरामीटर, getGenericParameterTypes() का उपयोग कर।
  • विधि रिटर्न प्रकार, getGenericReturnType() का उपयोग कर।
  • सार्वजनिक क्षेत्र, getGenericType का उपयोग कर।

निम्न उदाहरण दिखाता है कि तीनों मामलों में सामान्य प्रकार की जानकारी कैसे निकाली जाए:

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class GenericTest {

    public static void main(final String[] args) throws Exception {
        final Method method = GenericTest.class.getMethod("testMethod", Map.class);
        final Field field = GenericTest.class.getField("testField");

        System.out.println("Method parameter:");
        final Type parameterType = method.getGenericParameterTypes()[0];
        displayGenericType(parameterType, "\t");

        System.out.println("Method return type:");
        final Type returnType = method.getGenericReturnType();
        displayGenericType(returnType, "\t");

        System.out.println("Field type:");
        final Type fieldType = field.getGenericType();
        displayGenericType(fieldType, "\t");

    }

    private static void displayGenericType(final Type type, final String prefix) {
        System.out.println(prefix + type.getTypeName());
        if (type instanceof ParameterizedType) {
            for (final Type subtype : ((ParameterizedType) type).getActualTypeArguments()) {
                displayGenericType(subtype, prefix + "\t");
            }
        }

    }

    public Map<String, Map<Integer, List<String>>> testField;

    public List<Number> testMethod(final Map<String, Double> arg) {
        return null;
    }

}

इसका परिणाम निम्न आउटपुट में होता है:

Method parameter:
    java.util.Map<java.lang.String, java.lang.Double>
        java.lang.String
        java.lang.Double
Method return type:
    java.util.List<java.lang.Number>
        java.lang.Number
Field type:
    java.util.Map<java.lang.String, java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>>
        java.lang.String
        java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
            java.lang.Integer
            java.util.List<java.lang.String>
                java.lang.String

एक विधि का आह्वान

प्रतिबिंब का उपयोग करते हुए, रनटाइम के दौरान किसी ऑब्जेक्ट का एक तरीका लागू किया जा सकता है।

उदाहरण दिखाता है कि String ऑब्जेक्ट के तरीकों को कैसे लागू किया जाए।

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

String s = "Hello World!";

// method without parameters
// invoke s.length()
Method method1 = String.class.getMethod("length");
int length = (int) method1.invoke(s); // variable length contains "12"

// method with parameters
// invoke s.substring(6)
Method method2 = String.class.getMethod("substring", int.class);
String substring = (String) method2.invoke(s, 6); // variable substring contains "World!"

खेतों को प्राप्त करना और स्थापित करना

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

ऐसा करने के लिए, आपको विधि कक्षा # getField () का उपयोग इस तरह से करना होगा जैसे कि नीचे दिखाया गया है:

// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");

// Get the field in class Field "modifiers". Note that it does not 
// need to be static
Field modifiersField = Field.class.getDeclaredField("modifiers");

// Allow access from anyone even if it's declared private
modifiersField.setAccessible(true);

// Get the modifiers on the "NAME" field as an int.
int existingModifiersOnNameField = nameField.getModifiers();

// Bitwise AND NOT Modifier.FINAL (16) on the existing modifiers
// Readup here https://en.wikipedia.org/wiki/Bitwise_operations_in_C
// if you're unsure what bitwise operations are.
int newModifiersOnNameField = existingModifiersOnNameField & ~Modifier.FINAL;

// Set the value of the modifiers field under an object for non-static fields
modifiersField.setInt(nameField, newModifiersOnNameField);

// Set it to be accessible. This overrides normal Java 
// private/protected/package/etc access control checks.
nameField.setAccessible(true);

 // Set the value of "NAME" here. Note the null argument. 
 // Pass null when modifying static fields, as there is no instance object
nameField.set(null, "Hacked by reflection...");

// Here I can directly access it. If needed, use reflection to get it. (Below)
System.out.println(SomeClass.NAME);

फ़ील्ड प्राप्त करना बहुत आसान है। हम इसका मान प्राप्त करने के लिए फ़ील्ड # प्राप्त () और इसके प्रकारों का उपयोग कर सकते हैं:

// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");

// Set accessible for private fields
nameField.setAccessible(true);

// Pass null as there is no instance, remember?
String name = (String) nameField.get(null);

इस पर ध्यान दें:

कक्षा # getDeclaredField का उपयोग करते समय, कक्षा में एक फ़ील्ड प्राप्त करने के लिए इसका उपयोग करें:

class HackMe extends Hacked {
    public String iAmDeclared;
}

class Hacked {
    public String someState;
}

यहां, HackMe#iAmDeclared को फ़ील्ड घोषित किया गया है। हालाँकि, HackMe#someState एक घोषित क्षेत्र नहीं है क्योंकि यह अपने HackMe#someState , HackMe#someState से विरासत में मिला है।

निर्माता को बुलाओ

कंस्ट्रक्टर ऑब्जेक्ट प्राप्त करना

आप इस तरह Class वस्तु से Constructor वर्ग प्राप्त कर सकते हैं:

Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();

कहाँ constructors चर एक होगा Constructor प्रत्येक सार्वजनिक निर्माता के लिए उदाहरण के वर्ग में घोषित कर दिया।

यदि आप जानते हैं कि निर्माणकर्ता के सटीक पैरामीटर प्रकार आप एक्सेस करना चाहते हैं, तो आप विशिष्ट कंस्ट्रक्टर को फ़िल्टर कर सकते हैं। अगला उदाहरण दिए गए वर्ग के सार्वजनिक कंस्ट्रक्टर को लौटाता है जो एक Integer को पैरामीटर के रूप में लेता है:

Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});

यदि कोई कंस्ट्रक्टर दिए गए कंस्ट्रक्टर के तर्कों से मेल नहीं खाता है तो NoSuchMethodException को फेंक दिया जाता है।

कंस्ट्रक्टर ऑब्जेक्ट का उपयोग करके नया इंस्टेंस

Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));

एक गणन की स्थिरांक प्राप्त करना

इस गणना को उदाहरण के रूप में देते हुए:

enum Compass {
    NORTH(0),
    EAST(90),
    SOUTH(180),
    WEST(270);
    private int degree;
    Compass(int deg){
        degree = deg;
    }
    public int getDegree(){
        return degree;
    }
}

जावा में एक एनुम वर्ग किसी भी अन्य वर्ग की तरह है, लेकिन एनम मूल्यों के लिए कुछ निश्चित स्थिरांक हैं। इसके अतिरिक्त इसमें एक फ़ील्ड है जो एक सरणी है जो सभी मानों और दो स्थिर विधियों को नाम values() और valueOf(String) साथ रखती है।
हम इसे देख सकते हैं यदि हम इस वर्ग के सभी क्षेत्रों को मुद्रित करने के लिए परावर्तन का उपयोग करते हैं

for(Field f : Compass.class.getDeclaredFields())
    System.out.println(f.getName());

उत्पादन होगा:

उत्तर
पूर्व
दक्षिण
पश्चिम
डिग्री
Enum $ मान

तो हम किसी भी अन्य वर्ग की तरह परावर्तन के साथ एनम कक्षाओं की जांच कर सकते हैं। लेकिन परावर्तन एपीआई तीन enum- विशिष्ट तरीके प्रदान करता है।

enum check

Compass.class.isEnum();

उन वर्गों के लिए सही है जो एक enum प्रकार का प्रतिनिधित्व करता है।

मूल्यों को पुनः प्राप्त करना

Object[] values = Compass.class.getEnumConstants();

Compass.values () जैसे सभी enum मानों की एक सरणी देता है, लेकिन एक उदाहरण की आवश्यकता के बिना।

enum निरंतर जांच

for(Field f : Compass.class.getDeclaredFields()){
    if(f.isEnumConstant())
        System.out.println(f.getName());
}

सभी वर्ग फ़ील्ड्स की सूची देता है जो एनम मान हैं।

Get Class को इसका (पूरी तरह से योग्य) नाम दिया गया

एक String को एक वर्ग के नाम से देखते हुए, यह Class ऑब्जेक्ट है। इसे Class.forName का उपयोग करके एक्सेस किया जा सकता है:

Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}
जावा एसई 1.2

यह निर्दिष्ट किया जा सकता है, यदि वर्ग आरम्भ किया जाना चाहिए ( forName का दूसरा पैरामीटर) और कौन सा ClassLoader का उपयोग किया जाना चाहिए (तीसरा पैरामीटर):

ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}

प्रतिबिंब का उपयोग करके अतिभारित रचनाकारों को बुलाओ

उदाहरण: संबंधित मानकों को पारित करके विभिन्न निर्माणकर्ताओं को आमंत्रित करें

import java.lang.reflect.*;

class NewInstanceWithReflection{
    public NewInstanceWithReflection(){
        System.out.println("Default constructor");
    }
    public NewInstanceWithReflection( String a){
        System.out.println("Constructor :String => "+a);
    }
    public static void main(String args[]) throws Exception {
        
        NewInstanceWithReflection object = (NewInstanceWithReflection)Class.forName("NewInstanceWithReflection").newInstance();
        Constructor constructor = NewInstanceWithReflection.class.getDeclaredConstructor( new Class[] {String.class});
        NewInstanceWithReflection object1 = (NewInstanceWithReflection)constructor.newInstance(new Object[]{"StackOverFlow"});
        
    }
}

उत्पादन:

Default constructor
Constructor :String => StackOverFlow

स्पष्टीकरण:

  1. Class.forName का उपयोग करके कक्षा का उदाहरण बनाएं: इसे डिफ़ॉल्ट कंस्ट्रक्टर कहते हैं
  2. Class array रूप में पैरामीटर के प्रकार को पास करके getDeclaredConstructor को प्राप्त करें
  3. कंस्ट्रक्टर प्राप्त करने के बाद, Object array रूप में पैरामीटर मान पास करके newInstance बनाएं

निजी और अंतिम चर को बदलने के लिए परावर्तन एपीआई का दुरुपयोग

चिंतन तब उपयोगी होता है जब उसका सही उद्देश्य के लिए सही उपयोग किया जाता है। प्रतिबिंब का उपयोग करके, आप निजी चर का उपयोग कर सकते हैं और अंतिम चर को पुनः आरंभ कर सकते हैं।

नीचे कोड स्निपेट है, जो अनुशंसित नहीं है

import java.lang.reflect.*;

public class ReflectionDemo{
    public static void main(String args[]){
        try{
            Field[] fields = A.class.getDeclaredFields();
            A a = new A();
            for ( Field field:fields ) {
                if(field.getName().equalsIgnoreCase("name")){
                    field.setAccessible(true);
                    field.set(a, "StackOverFlow");
                    System.out.println("A.name="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("age")){
                    field.set(a, 20);
                    System.out.println("A.age="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("rep")){
                    field.setAccessible(true);
                    field.set(a,"New Reputation");
                    System.out.println("A.rep="+field.get(a));
                }
                if(field.getName().equalsIgnoreCase("count")){
                    field.set(a,25);
                    System.out.println("A.count="+field.get(a));
                }
            }                
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

class A {
    private String name;
    public int age;
    public final String rep;
    public static int count=0;
    
    public A(){
        name = "Unset";
        age = 0;
        rep = "Reputation";
        count++;
    }
}

आउटपुट:

A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25

स्पष्टीकरण:

सामान्य परिदृश्य में, private चर को घोषित वर्ग (बिना गेट्टर और सेटर के तरीकों) से बाहर नहीं पहुँचा जा सकता है। final चर को आरंभीकरण के बाद पुन: असाइन नहीं किया जा सकता है।

जैसा कि ऊपर बताया गया है, दोनों बाधाओं को निजी और अंतिम चर को बदलने के लिए दोनों अवरोधों को Reflection किया जा सकता है।

field.setAccessible(true) वांछित कार्यक्षमता प्राप्त करने के लिए महत्वपूर्ण है।

नेस्टेड क्लास के निर्माता को बुलाओ

यदि आप एक आंतरिक नेस्टेड वर्ग का एक उदाहरण बनाना चाहते हैं, तो आपको क्लास # getDeclaredConstructor के साथ एक अतिरिक्त पैरामीटर के रूप में एन्क्लोज़िंग क्लास की एक क्लास ऑब्जेक्ट प्रदान करने की आवश्यकता है।

public class Enclosing{
    public class Nested{
    public Nested(String a){
            System.out.println("Constructor :String => "+a);
        }
    }       
    public static void main(String args[]) throws Exception {
        Class<?> clazzEnclosing = Class.forName("Enclosing");            
        Class<?> clazzNested = Class.forName("Enclosing$Nested");
        Enclosing objEnclosing = (Enclosing)clazzEnclosing.newInstance();
        Constructor<?> constructor = clazzNested.getDeclaredConstructor(new Class[]{Enclosing.class, String.class});
        Nested objInner = (Nested)constructor.newInstance(new Object[]{objEnclosing, "StackOverFlow"});
    }
}

यदि नेस्टेड क्लास स्थिर है, तो आपको इस एन्कोडिंग उदाहरण की आवश्यकता नहीं होगी।

डायनेमिक प्रॉक्सी

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

public class DynamicProxyTest {

    public interface MyInterface1{
        public void someMethod1();
        public int someMethod2(String s);
    }

    public interface MyInterface2{
        public void anotherMethod();
    }
   
    public static void main(String args[]) throws Exception {
        // the dynamic proxy class 
        Class<?> proxyClass = Proxy.getProxyClass(
                ClassLoader.getSystemClassLoader(),
                new Class[] {MyInterface1.class, MyInterface2.class});
        // the dynamic proxy class constructor
        Constructor<?> proxyConstructor = 
            proxyClass.getConstructor(InvocationHandler.class);

        // the invocation handler
        InvocationHandler handler = new InvocationHandler(){
            // this method is invoked for every proxy method call
            // method is the invoked method, args holds the method parameters
            // it must return the method result
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
                String methodName = method.getName();

                if(methodName.equals("someMethod1")){
                    System.out.println("someMethod1 was invoked!");
                    return null;
                }
                if(methodName.equals("someMethod2")){
                    System.out.println("someMethod2 was invoked!");
                    System.out.println("Parameter: " + args[0]);
                    return 42;
                }
                if(methodName.equals("anotherMethod")){
                    System.out.println("anotherMethod was invoked!");
                    return null;
                }
                System.out.println("Unkown method!");
                return null;
            }
        };

        // create the dynamic proxy instances
        MyInterface1 i1 = (MyInterface1) proxyConstructor.newInstance(handler);
        MyInterface2 i2 = (MyInterface2) proxyConstructor.newInstance(handler);

        // and invoke some methods
        i1.someMethod1();
        i1.someMethod2("stackoverflow");
        i2.anotherMethod();
    }
}

इस कोड का परिणाम यह है:

someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!

बुराई जावा प्रतिबिंब के साथ हैक

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

क्या संभव नहीं है

एकमात्र सीमा के साथ सबसे पहले शुरू करें, एकमात्र क्षेत्र का मतलब है कि हम प्रतिबिंब के साथ बदल नहीं सकते हैं। वह Java SecurityManager । यह java.lang.System के रूप में घोषित किया गया है

private static volatile SecurityManager security = null;

यदि हम यह कोड चलाते हैं तो यह सिस्टम क्लास में सूचीबद्ध नहीं होगा

for(Field f : System.class.getDeclaredFields())
    System.out.println(f);

क्योंकि fieldFilterMap में sun.reflect.Reflection मैप की वजह से ही जो मानचित्र को sun.reflect.Reflection है और System.class में सुरक्षा क्षेत्र को System.class और परावर्तन के साथ किसी भी पहुँच के खिलाफ उनकी सुरक्षा करता है। इसलिए हम SecurityManager निष्क्रिय नहीं कर सके।

क्रेजी स्ट्रिंग्स

प्रत्येक जावा स्ट्रिंग को String कक्षा के एक उदाहरण के रूप में JVM द्वारा दर्शाया जाता है। हालांकि, कुछ स्थितियों में जेवीएम स्ट्रिंग्स के लिए उसी उदाहरण का उपयोग करके ढेर जगह बचाता है। यह स्ट्रिंग शाब्दिक के लिए होता है, और स्ट्रिंग के लिए भी होता है जिसे String.intern() कहकर "इंटर्न" किया गया है। इसलिए यदि आपके पास अपने कोड में कई बार "hello" है तो यह हमेशा एक ही वस्तु उदाहरण होता है।

स्ट्रिंग्स को अपरिवर्तनीय माना जाता है, लेकिन उन्हें बदलने के लिए "बुराई" प्रतिबिंब का उपयोग करना संभव है। नीचे दिए गए उदाहरण से पता चलता है कि हम अपने value क्षेत्र को बदलकर एक स्ट्रिंग में पात्रों को कैसे बदल सकते हैं।

public class CrazyStrings {
    static {
        try {
            Field f = String.class.getDeclaredField("value");
            f.setAccessible(true);
            f.set("hello", "you stink!".toCharArray());
        } catch (Exception e) {
        }
    }
    public static void main(String args[])  {
        System.out.println("hello");
    }
}

तो यह कोड "आप बदबू!"

1 = 42

उसी विचार का इस्तेमाल इंटेगर क्लास के साथ किया जा सकता था

public class CrazyMath {
    static {
        try {
            Field value = Integer.class.getDeclaredField("value");    
            value.setAccessible(true);          
            value.setInt(Integer.valueOf(1), 42);      
        } catch (Exception e) {
        }
    }
    public static void main(String args[])  {
        System.out.println(Integer.valueOf(1));
    }
}

सब कुछ सच है

और इस stackoverflow पोस्ट के अनुसार हम प्रतिबिंब का उपयोग वास्तव में कुछ बुराई करने के लिए कर सकते हैं।

public class Evil {    
    static {
        try {
            Field field = Boolean.class.getField("FALSE");
            field.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(null, true);
        } catch (Exception e) {
        }
    }
    public static void main(String args[]){
        System.out.format("Everything is %s", false);
    }
}

ध्यान दें कि हम यहाँ क्या कर रहे हैं, जेवीएम को अनुभवहीन तरीके से व्यवहार करने का कारण बनने जा रहा है। यह बहुत खतरनाक है।



Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow