
Cat
वर्ग लिख सकते हैं:
package learn.codegym;
public class Cat {
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void sayMeow() {
System.out.println("Meow!");
}
public void jump() {
System.out.println("Jump!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
आप इसके बारे में सब कुछ जानते हैं, और आप इसके क्षेत्र और विधियों को देख सकते हैं। मान लीजिए आपको अचानक अन्य पशु वर्गों को कार्यक्रम में शामिल करने की आवश्यकता है। Animal
आप शायद सुविधा के लिए मूल वर्ग के साथ कक्षा विरासत संरचना बना सकते हैं । इससे पहले, हमने एक पशु चिकित्सा क्लिनिक का प्रतिनिधित्व करने वाला एक वर्ग भी बनाया था, जिसमें हम एक Animal
वस्तु (एक मूल वर्ग का उदाहरण) पास कर सकते थे, और कार्यक्रम ने कुत्ते या बिल्ली के आधार पर उचित रूप से जानवर का इलाज किया। भले ही ये सबसे सरल कार्य नहीं हैं, प्रोग्राम संकलन समय पर कक्षाओं के बारे में सभी आवश्यक जानकारी सीखने में सक्षम है। तदनुसार, जब आप किसी Cat
वस्तु को पशु चिकित्सा क्लिनिक वर्ग के तरीकों में पास करते हैंmain()
विधि, कार्यक्रम पहले से ही जानता है कि यह एक बिल्ली है, कुत्ता नहीं। अब आइए कल्पना करें कि हम एक अलग कार्य का सामना कर रहे हैं। हमारा लक्ष्य एक कोड विश्लेषक लिखना है। हमें CodeAnalyzer
एक विधि के साथ एक वर्ग बनाने की जरूरत है: void analyzeObject(Object o)
. इस विधि को चाहिए:
- पास की गई वस्तु का वर्ग निर्धारित करें और कंसोल पर वर्ग का नाम प्रदर्शित करें;
- उत्तीर्ण वर्ग के सभी क्षेत्रों के नाम निर्धारित करें, जिनमें निजी भी शामिल हैं, और उन्हें कंसोल पर प्रदर्शित करें;
- उत्तीर्ण वर्ग के सभी तरीकों के नाम निर्धारित करें, जिनमें निजी भी शामिल हैं, और उन्हें कंसोल पर प्रदर्शित करें।
public class CodeAnalyzer {
public static void analyzeClass(Object o) {
// Print the name of the class of object o
// Print the names of all variables of this class
// Print the names of all methods of this class
}
}
अब हम स्पष्ट रूप से देख सकते हैं कि यह कार्य आपके द्वारा पहले हल किए गए अन्य कार्यों से कैसे भिन्न है। हमारे वर्तमान उद्देश्य के साथ, कठिनाई इस तथ्य में निहित है कि न तो हम और न ही कार्यक्रम को पता है कि वास्तव में क्या पारित किया जाएगाanalyzeClass()
तरीका। यदि आप इस तरह का प्रोग्राम लिखते हैं, तो अन्य प्रोग्रामर इसका उपयोग करना शुरू कर देंगे, और वे इस विधि के लिए कुछ भी पास कर सकते हैं - कोई भी मानक जावा क्लास या कोई अन्य क्लास जो वे लिखते हैं। उत्तीर्ण वर्ग में कई प्रकार के चर और विधियाँ हो सकती हैं। दूसरे शब्दों में, हम (और हमारे कार्यक्रम) को पता नहीं है कि हम किन वर्गों के साथ काम करेंगे। लेकिन फिर भी, हमें इस कार्य को पूरा करने की आवश्यकता है। और यहीं पर मानक जावा रिफ्लेक्शन एपीआई हमारी सहायता के लिए आता है। रिफ्लेक्शन एपीआई भाषा का एक शक्तिशाली उपकरण है। ओरेकल के आधिकारिक दस्तावेज की सिफारिश है कि इस तंत्र का उपयोग केवल अनुभवी प्रोग्रामर द्वारा किया जाना चाहिए जो जानते हैं कि वे क्या कर रहे हैं। आप जल्द ही समझ जाएंगे कि हम इस तरह की चेतावनी पहले से क्यों दे रहे हैं :) यहां उन चीजों की सूची दी गई है जो आप रिफ्लेक्शन एपीआई के साथ कर सकते हैं:
- किसी वस्तु के वर्ग को पहचानें/निर्धारित करें।
- क्लास मॉडिफायर्स, फील्ड्स, मेथड्स, कॉन्स्टेंट्स, कंस्ट्रक्टर्स और सुपरक्लासेस के बारे में जानकारी प्राप्त करें।
- पता लगाएं कि कौन सी विधियां कार्यान्वित इंटरफ़ेस से संबंधित हैं।
- उस वर्ग का एक उदाहरण बनाएँ जिसका वर्ग नाम तब तक ज्ञात नहीं है जब तक कि कार्यक्रम निष्पादित नहीं हो जाता।
- नाम से इंस्टेंस फ़ील्ड का मान प्राप्त करें और सेट करें।
- नाम से एक इंस्टेंस विधि को कॉल करें।
किसी वस्तु के वर्ग की पहचान/निर्धारण कैसे करें
आइए बुनियादी बातों से शुरू करें। जावा प्रतिबिंब इंजन का प्रवेश बिंदुClass
वर्ग है। हाँ, यह वास्तव में अजीब लग रहा है, लेकिन यही प्रतिबिंब है :) Class
कक्षा का उपयोग करते हुए, हम सबसे पहले किसी भी वस्तु के वर्ग को निर्धारित करते हैं जो हमारी पद्धति को पारित किया गया है। आइए ऐसा करने का प्रयास करें:
import learn.codegym.Cat;
public class CodeAnalyzer {
public static void analyzeClass(Object o) {
Class clazz = o.getClass();
System.out.println(clazz);
}
public static void main(String[] args) {
analyzeClass(new Cat("Fluffy", 6));
}
}
कंसोल आउटपुट:
class learn.codegym.Cat
दो बातों पर ध्यान दें। Cat
सबसे पहले, हमने जानबूझकर क्लास को एक अलग learn.codegym
पैकेज में रखा । अब आप देख सकते हैं कि getClass()
विधि वर्ग का पूरा नाम लौटाती है। दूसरा, हमने अपने वेरिएबल को नाम दिया है clazz
। यह थोड़ा अजीब लगता है। इसे "वर्ग" कहने में समझदारी होगी, लेकिन "वर्ग" जावा में एक आरक्षित शब्द है। कंपाइलर चर को कॉल करने की अनुमति नहीं देगा। हमें किसी तरह इससे बचना था :) शुरुआत के लिए बुरा नहीं है! क्षमताओं की उस सूची में हमारे पास और क्या था?
क्लास मॉडिफायर्स, फील्ड्स, मेथड्स, कॉन्स्टेंट्स, कंस्ट्रक्टर्स और सुपरक्लासेस के बारे में जानकारी कैसे प्राप्त करें।
अब चीजें और दिलचस्प होती जा रही हैं! वर्तमान कक्षा में, हमारे पास कोई स्थिरांक या मूल वर्ग नहीं है। आइए एक संपूर्ण चित्र बनाने के लिए उन्हें जोड़ें। सबसे सरलAnimal
मूल वर्ग बनाएँ:
package learn.codegym;
public class Animal {
private String name;
private int age;
}
और हम अपनी Cat
कक्षा को इनहेरिट करेंगे Animal
और एक स्थिरांक जोड़ेंगे:
package learn.codegym;
public class Cat extends Animal {
private static final String ANIMAL_FAMILY = "Feline family";
private String name;
private int age;
// ...the rest of the class
}
अब हमारे पास पूरी तस्वीर है! आइए देखें कि कौन सा प्रतिबिंब सक्षम है :)
import learn.codegym.Cat;
import java.util.Arrays;
public class CodeAnalyzer {
public static void analyzeClass(Object o) {
Class clazz = o.getClass();
System.out.println("Class name: " + clazz);
System.out.println("Class fields: " + Arrays.toString(clazz.getDeclaredFields()));
System.out.println("Parent class: " + clazz.getSuperclass());
System.out.println("Class methods: " + Arrays.toString(clazz.getDeclaredMethods()));
System.out.println("Class constructors: " + Arrays.toString(clazz.getConstructors()));
}
public static void main(String[] args) {
analyzeClass(new Cat("Fluffy", 6));
}
}
यहाँ हम कंसोल पर क्या देखते हैं:
Class name: class learn.codegym.Cat
Class fields: [private static final java.lang.String learn.codegym.Cat.ANIMAL_FAMILY, private java.lang.String learn.codegym.Cat.name, private int learn.codegym.Cat.age]
Parent class: class learn.codegym.Animal
Class methods: [public java.lang.String learn.codegym.Cat.getName(), public void learn.codegym.Cat.setName(java.lang.String), public void learn.codegym.Cat.sayMeow(), public void learn.codegym.Cat.setAge(int), public void learn.codegym.Cat.jump(), public int learn.codegym.Cat.getAge()]
Class constructors: [public learn.codegym.Cat(java.lang.String, int)]
उस विस्तृत कक्षा की जानकारी को देखें जो हम प्राप्त करने में सक्षम थे! और न केवल सार्वजनिक सूचना बल्कि निजी सूचना भी! टिप्पणी: private
चर भी सूची में प्रदर्शित होते हैं। कक्षा के हमारे "विश्लेषण" को अनिवार्य रूप से पूर्ण माना जा सकता है: हम वह analyzeObject()
सब कुछ सीखने के लिए विधि का उपयोग कर रहे हैं जो हम कर सकते हैं। लेकिन यह वह सब कुछ नहीं है जो हम प्रतिबिंब के साथ कर सकते हैं। हम साधारण अवलोकन तक ही सीमित नहीं हैं — हम कार्रवाई करने के लिए आगे बढ़ेंगे! :)
उस वर्ग का उदाहरण कैसे बनाया जाए जिसका वर्ग नाम तब तक ज्ञात नहीं है जब तक कि कार्यक्रम निष्पादित न हो जाए।
आइए डिफ़ॉल्ट कंस्ट्रक्टर से शुरू करें। हमारीCat
कक्षा में अभी तक एक नहीं है, तो चलिए इसे जोड़ते हैं:
public Cat() {
}
Cat
प्रतिबिंब ( createCat()
विधि) का उपयोग करके ऑब्जेक्ट बनाने के लिए कोड यहां दिया गया है :
import learn.codegym.Cat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static Cat createCat() throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String className = reader.readLine();
Class clazz = Class.forName(className);
Cat cat = (Cat) clazz.newInstance();
return cat;
}
public static Object createObject() throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String className = reader.readLine();
Class clazz = Class.forName(className);
Object result = clazz.newInstance();
return result;
}
public static void main(String[] args) throws IOException, IllegalAccessException, ClassNotFoundException, InstantiationException {
System.out.println(createCat());
}
}
कंसोल इनपुट:
learn.codegym.Cat
कंसोल आउटपुट:
Cat{name='null', age=0}
name
यह कोई त्रुटि नहीं है: और के मान age
कंसोल पर प्रदर्शित होते हैं क्योंकि हमने क्लास toString()
की विधि में उन्हें आउटपुट करने के लिए कोड लिखा था। Cat
यहां हम एक क्लास का नाम पढ़ते हैं जिसका ऑब्जेक्ट हम कंसोल से बनाएंगे। प्रोग्राम उस वर्ग के नाम को पहचानता है जिसका ऑब्जेक्ट बनाया जाना है। 
newInstance()
करना , हम इस वर्ग की एक नई वस्तु बनाते हैं। यह और बात है अगरCat
कंस्ट्रक्टर तर्कों को इनपुट के रूप में लेता है। चलिए क्लास के डिफॉल्ट कंस्ट्रक्टर को हटाते हैं और अपना कोड फिर से चलाने की कोशिश करते हैं।
null
java.lang.InstantiationException: learn.codegym.Cat
at java.lang.Class.newInstance(Class.java:427)
कुछ गलत हो गया! हमें एक एरर मिली क्योंकि हमने डिफॉल्ट कंस्ट्रक्टर का उपयोग करके ऑब्जेक्ट बनाने के लिए एक मेथड को कॉल किया था। लेकिन हमारे पास अब ऐसा कोई कंस्ट्रक्टर नहीं है। इसलिए जब newInstance()
विधि चलती है, प्रतिबिंब तंत्र हमारे पुराने कंस्ट्रक्टर का उपयोग दो मापदंडों के साथ करता है:
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
लेकिन हमने मापदंडों के साथ कुछ नहीं किया, जैसे कि हम उनके बारे में पूरी तरह से भूल गए हों! कंस्ट्रक्टर को तर्क देने के लिए प्रतिबिंब का उपयोग करने के लिए थोड़ी "रचनात्मकता" की आवश्यकता होती है:
import learn.codegym.Cat;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static Cat createCat() {
Class clazz = null;
Cat cat = null;
try {
clazz = Class.forName("learn.codegym.Cat");
Class[] catClassParams = {String.class, int.class};
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return cat;
}
public static void main(String[] args) {
System.out.println(createCat());
}
}
कंसोल आउटपुट:
Cat{name='Fluffy', age=6}
आइए देखें कि हमारे कार्यक्रम में क्या हो रहा है। Class
हमने वस्तुओं की एक सरणी बनाई ।
Class[] catClassParams = {String.class, int.class};
वे हमारे कंस्ट्रक्टर के मापदंडों के अनुरूप हैं (जो सिर्फ String
और int
पैरामीटर हैं)। हम उन्हें clazz.getConstructor()
विधि में पास करते हैं और वांछित कन्स्ट्रक्टर तक पहुंच प्राप्त करते हैं। उसके बाद, हमें बस इतना करना है कि newInstance()
आवश्यक तर्कों के साथ विधि को कॉल करें, और ऑब्जेक्ट को वांछित प्रकार पर स्पष्ट रूप से कास्ट करना न भूलें: Cat
।
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
अब हमारी वस्तु सफलतापूर्वक बन गई है! कंसोल आउटपुट:
Cat{name='Fluffy', age=6}
ठीक साथ चल रहा है :)
नाम से इंस्टेंस फ़ील्ड का मान कैसे प्राप्त करें और सेट करें।
कल्पना कीजिए कि आप किसी अन्य प्रोग्रामर द्वारा लिखी गई कक्षा का उपयोग कर रहे हैं। इसके अतिरिक्त, आपके पास इसे संपादित करने की क्षमता नहीं है। उदाहरण के लिए, एक जार में पैक की गई रेडी-मेड क्लास लाइब्रेरी। आप कक्षाओं का कोड पढ़ सकते हैं, लेकिन आप इसे बदल नहीं सकते। मान लीजिए कि प्रोग्रामर जिसने इस पुस्तकालय में कक्षाओं में से एक बनाया है (इसे हमारी पुरानीCat
कक्षा होने दें), डिजाइन को अंतिम रूप देने से पहले रात को पर्याप्त नींद लेने में विफल रहने पर, क्षेत्र के लिए गेट्टर और सेटर को हटा दिया age
। अब यह वर्ग आपके पास आ गया है। यह आपकी सभी जरूरतों को पूरा करता है, क्योंकि आपको Cat
अपने कार्यक्रम में केवल वस्तुओं की आवश्यकता होती है। लेकिन आपके पास एक age
क्षेत्र होने के लिए उनकी आवश्यकता है! यह एक समस्या है: हम मैदान तक नहीं पहुँच सकते, क्योंकि इसमें हैprivate
संशोधक, और गेटर और सेटर को नींद से वंचित डेवलपर द्वारा हटा दिया गया था जिसने कक्षा बनाई थी:/ठीक है, प्रतिबिंब इस स्थिति में हमारी मदद कर सकता है! हमारे पास कक्षा के लिए कोड तक पहुंच है Cat
, इसलिए हम कम से कम यह पता लगा सकते हैं कि इसमें कौन से क्षेत्र हैं और उन्हें क्या कहा जाता है। इस जानकारी से लैस होकर हम अपनी समस्या का समाधान कर सकते हैं:
import learn.codegym.Cat;
import java.lang.reflect.Field;
public class Main {
public static Cat createCat() {
Class clazz = null;
Cat cat = null;
try {
clazz = Class.forName("learn.codegym.Cat");
cat = (Cat) clazz.newInstance();
// We got lucky with the name field, since it has a setter
cat.setName("Fluffy");
Field age = clazz.getDeclaredField("age");
age.setAccessible(true);
age.set(cat, 6);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return cat;
}
public static void main(String[] args) {
System.out.println(createCat());
}
}
जैसा कि टिप्पणियों में बताया गया है, name
क्षेत्र के साथ सबकुछ सीधा है, क्योंकि कक्षा डेवलपर्स ने एक सेटर प्रदान किया है। आप पहले से ही जानते हैं कि डिफॉल्ट कंस्ट्रक्टर्स से ऑब्जेक्ट कैसे बनाते हैं: हमारे पास newInstance()
इसके लिए है। लेकिन हमें दूसरे फील्ड के साथ कुछ छेड़छाड़ करनी होगी। आइए जानें कि यहां क्या हो रहा है :)
Field age = clazz.getDeclaredField("age");
यहां, हमारे Class clazz
ऑब्जेक्ट का उपयोग करके, हम विधि age
के माध्यम से फ़ील्ड तक पहुंचते हैं। getDeclaredField()
यह हमें आयु क्षेत्र को एक Field age
वस्तु के रूप में प्राप्त करने देता है। लेकिन यह पर्याप्त नहीं है, क्योंकि हम केवल private
फ़ील्ड्स को मान निर्दिष्ट नहीं कर सकते हैं। ऐसा करने के लिए, हमें विधि का उपयोग करके क्षेत्र को सुलभ बनाने की आवश्यकता है setAccessible()
:
age.setAccessible(true);
एक बार जब हम इसे किसी क्षेत्र में कर लेते हैं, तो हम एक मान निर्दिष्ट कर सकते हैं:
age.set(cat, 6);
जैसा कि आप देख सकते हैं, हमारे Field age
ऑब्जेक्ट में एक प्रकार का इनसाइड-आउट सेटर है, जिसके लिए हम एक इंट वैल्यू पास करते हैं और वह ऑब्जेक्ट जिसका फील्ड असाइन किया जाना है। हम अपना main()
तरीका चलाते हैं और देखते हैं:
Cat{name='Fluffy', age=6}
उत्कृष्ट! हमने यह किया! :) आइए देखें कि हम और क्या कर सकते हैं...
नाम से इंस्टेंस विधि कैसे कॉल करें।
पिछले उदाहरण में स्थिति को थोड़ा बदल दें। मान लीजिए किCat
क्लास डेवलपर ने गेटर्स और सेटर्स के साथ कोई गलती नहीं की है। इस लिहाज से सब ठीक है। अब समस्या अलग है: एक तरीका है जिसकी हमें निश्चित रूप से आवश्यकता है, लेकिन डेवलपर ने इसे निजी बना दिया है:
private void sayMeow() {
System.out.println("Meow!");
}
इसका मतलब है कि अगर हम अपने प्रोग्राम में ऑब्जेक्ट बनाते हैं , तो हम उन पर मेथड को Cat
कॉल नहीं कर पाएंगे। sayMeow()
हमारे पास बिल्लियाँ होंगी जो म्याऊ नहीं करती हैं? यह अजीब है: / हम इसे कैसे ठीक करेंगे? एक बार फिर, रिफ्लेक्शन एपीआई हमारी मदद करता है! हमें जिस विधि की आवश्यकता है उसका नाम हम जानते हैं। बाकी सब कुछ एक तकनीकीता है:
import learn.codegym.Cat;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void invokeSayMeowMethod() {
Class clazz = null;
Cat cat = null;
try {
cat = new Cat("Fluffy", 6);
clazz = Class.forName(Cat.class.getName());
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
sayMeow.setAccessible(true);
sayMeow.invoke(cat);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
invokeSayMeowMethod();
}
}
यहां हम बहुत कुछ वही करते हैं जो हमने एक निजी क्षेत्र में प्रवेश करते समय किया था। सबसे पहले, हमें वह विधि मिलती है जिसकी हमें आवश्यकता होती है। यह एक Method
वस्तु में समझाया गया है:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
विधि getDeclaredMethod()
हमें निजी तरीकों तक पहुंचने देती है। अगला, हम विधि को कॉल करने योग्य बनाते हैं:
sayMeow.setAccessible(true);
और अंत में, हम वांछित वस्तु पर विधि कहते हैं:
sayMeow.invoke(cat);
यहां, हमारी विधि कॉल "कॉलबैक" की तरह दिखती है: हम वांछित विधि () पर ऑब्जेक्ट को इंगित करने के लिए एक अवधि का उपयोग करने के आदी हैं cat.sayMeow()
, लेकिन प्रतिबिंब के साथ काम करते समय, हम उस विधि को पास करते हैं जिस पर हम कॉल करना चाहते हैं वह तरीका। हमारे कंसोल पर क्या है?
Meow!
सब कुछ काम कर गया! :) अब आप विशाल संभावनाएं देख सकते हैं कि जावा का प्रतिबिंब तंत्र हमें देता है। कठिन और अप्रत्याशित परिस्थितियों में (जैसे कि एक बंद पुस्तकालय से कक्षा के साथ हमारे उदाहरण), यह वास्तव में हमारी बहुत मदद कर सकता है। लेकिन, जैसा कि किसी भी महान शक्ति के साथ होता है, यह बड़ी जिम्मेदारी लाता है। Oracle वेबसाइट पर एक विशेष खंड में प्रतिबिंब के नुकसान का वर्णन किया गया है । तीन मुख्य नुकसान हैं:
-
प्रदर्शन और भी बुरा है। प्रतिबिंब का उपयोग करने वाले तरीकों का सामान्य तरीके से बुलाए जाने वाले तरीकों की तुलना में खराब प्रदर्शन होता है।
-
सुरक्षा प्रतिबंध हैं। रिफ्लेक्शन मैकेनिज्म हमें रनटाइम पर प्रोग्राम के व्यवहार को बदलने की सुविधा देता है। लेकिन आपके कार्यस्थल पर, किसी वास्तविक परियोजना पर काम करते समय, आपको उन सीमाओं का सामना करना पड़ सकता है जो इसकी अनुमति नहीं देती हैं।
-
आंतरिक जानकारी के प्रकट होने का जोखिम। यह समझना महत्वपूर्ण है कि प्रतिबिंब एनकैप्सुलेशन के सिद्धांत का सीधा उल्लंघन है: यह हमें निजी क्षेत्रों, विधियों आदि तक पहुंचने देता है। मुझे नहीं लगता कि मुझे यह उल्लेख करने की आवश्यकता है कि ओओपी के सिद्धांतों का प्रत्यक्ष और प्रमुख उल्लंघन का सहारा लिया जाना चाहिए केवल सबसे चरम मामलों में, जब आपके नियंत्रण से बाहर के कारणों के लिए किसी समस्या को हल करने का कोई अन्य तरीका नहीं है।
GO TO FULL VERSION