नमस्ते! हम जेनरिक पर पाठों की अपनी श्रृंखला जारी रखते हैं। हमें पहले इस बात का सामान्य ज्ञान हो गया था कि वे क्या हैं और उनकी आवश्यकता क्यों है। आज हम जेनरिक की कुछ विशेषताओं और उनके साथ काम करने के बारे में और जानेंगे। चल दर! पिछले पाठ
में , हमने सामान्य प्रकार और कच्चे प्रकार के बीच के अंतर के बारे में बात की थी । कच्चा प्रकार एक सामान्य वर्ग है जिसका प्रकार हटा दिया गया है।
प्रलेखन कहता है, "टी - इस क्लास ऑब्जेक्ट द्वारा बनाए गए वर्ग का प्रकार।" इसे प्रलेखन की भाषा से सादे भाषण में अनुवाद करने पर, हम समझते हैं कि वस्तु का वर्ग

List list = new ArrayList();
यहाँ एक उदाहरण है। यहां हम यह नहीं बताते हैं कि किस प्रकार की वस्तुओं को हमारे अंदर रखा जाएगा List
। यदि हम ऐसा बनाने का प्रयास करते हैं List
और इसमें कुछ ऑब्जेक्ट जोड़ते हैं, तो हम आईडिया में एक चेतावनी देखेंगे:
"Unchecked call to add(E) as a member of raw type of java.util.List".
लेकिन हमने इस तथ्य के बारे में भी बात की कि जेनरिक केवल जावा 5 में दिखाई दिए। जब तक यह संस्करण जारी किया गया था, तब तक प्रोग्रामर पहले से ही कच्चे प्रकारों का उपयोग करके कोड का एक गुच्छा लिख चुके थे, इसलिए भाषा की यह विशेषता काम करना बंद नहीं कर सकती थी, और करने की क्षमता जावा में कच्चे प्रकार बनाएं संरक्षित किया गया था। हालाँकि, समस्या अधिक व्यापक निकली। जैसा कि आप जानते हैं, जावा कोड को एक विशेष संकलित प्रारूप में परिवर्तित किया जाता है जिसे बायटेकोड कहा जाता है, जिसे बाद में जावा वर्चुअल मशीन द्वारा निष्पादित किया जाता है। लेकिन अगर हम रूपांतरण प्रक्रिया के दौरान बाइटकोड में टाइप पैरामीटर के बारे में जानकारी डालते हैं, तो यह पहले लिखे गए सभी कोड को तोड़ देगा, क्योंकि जावा 5 से पहले कोई टाइप पैरामीटर नहीं था! जेनरिक के साथ काम करते समय, एक बहुत ही महत्वपूर्ण अवधारणा है जिसे आपको याद रखने की आवश्यकता है। इसे टाइप इरेज़र कहा जाता है. इसका अर्थ है कि एक वर्ग में किसी प्रकार के पैरामीटर के बारे में कोई जानकारी नहीं होती है। यह जानकारी केवल संकलन के दौरान उपलब्ध होती है और रनटाइम से पहले मिटा दी जाती है (पहुंच योग्य नहीं हो जाती)। यदि आप अपने में गलत प्रकार की वस्तु डालने का प्रयास करते हैं List<String>
, तो संकलक एक त्रुटि उत्पन्न करेगा। जेनरिक बनाते समय भाषा के निर्माता यही हासिल करना चाहते हैं: संकलन-समय की जाँच। लेकिन जब आपका पूरा जावा कोड बायटेकोड में बदल जाता है, तो इसमें टाइप पैरामीटर के बारे में जानकारी नहीं होती है। बायटेकोड में, आपकी बिल्ली की सूची तार List<Cat>
से अलग नहीं है । List<String>
बाइटकोड में, कुछ भी नहीं कहता है कि वस्तुओं cats
की एक सूची है । Cat
संकलन के दौरान ऐसी जानकारी मिटा दी जाती है - केवल तथ्य यह है कि आपके पास एक List<Object> cats
सूची प्रोग्राम के बायटेकोड में समाप्त हो जाएगी। आइए देखें कि यह कैसे काम करता है:
public class TestClass<T> {
private T value1;
private T value2;
public void printValues() {
System.out.println(value1);
System.out.println(value2);
}
public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
TestClass<T> result = new TestClass<>();
result.value1 = (T) o1;
result.value2 = (T) o2;
return result;
}
public static void main(String[] args) {
Double d = 22.111;
String s = "Test String";
TestClass<Integer> test = createAndAdd2Values(d, s);
test.printValues();
}
}
हमने अपना सामान्य TestClass
वर्ग बनाया। यह काफी सरल है: यह वास्तव में 2 वस्तुओं का एक छोटा "संग्रह" है, जो वस्तु बनने पर तुरंत संग्रहीत हो जाते हैं। इसके 2 T
क्षेत्र हैं। जब createAndAdd2Values()
विधि निष्पादित की जाती है, तो दो पास की गई वस्तुएं ( Object a
और उन्हें प्रकार Object b
में डाला जाना चाहिए T
और फिर TestClass
वस्तु में जोड़ा जाना चाहिए। विधि में main()
, हम एक बनाते हैं TestClass<Integer>
, यानी Integer
प्रकार तर्क Integer
प्रकार पैरामीटर को प्रतिस्थापित करता है। हम एक Double
और एक String
को भी पास कर रहे हैं विधि createAndAdd2Values()
। क्या आपको लगता है कि हमारा कार्यक्रम काम करेगा? आखिरकार, हमने Integer
प्रकार तर्क के रूप में निर्दिष्ट किया है, लेकिन String
निश्चित रूप से एक को नहीं डाला जा सकता है Integer
! चलो चलाते हैंmain()
विधि और जाँच। कंसोल आउटपुट:
22.111
Test String
यह अनपेक्षित था! ऐसा क्यों हुआ? यह टाइप इरेज़र का परिणाम है। कोड संकलित होने पर Integer
हमारे ऑब्जेक्ट को इंस्टेंट करने के लिए उपयोग किए जाने वाले प्रकार तर्क के बारे में जानकारी मिटा दी गई थी। TestClass<Integer> test
मैदान हो जाता है TestClass<Object> test
। हमारे Double
और String
तर्कों को आसानी से Object
वस्तुओं में परिवर्तित कर दिया गया था (वे Integer
वस्तुओं में परिवर्तित नहीं हुए हैं जैसा कि हम उम्मीद करते हैं!) और चुपचाप जोड़ा गया TestClass
। यहाँ टाइप इरेज़र का एक और सरल लेकिन बहुत ही खुलासा करने वाला उदाहरण दिया गया है:
import java.util.ArrayList;
import java.util.List;
public class Main {
private class Cat {
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
System.out.println(strings.getClass() == numbers.getClass());
System.out.println(numbers.getClass() == cats.getClass());
}
}
कंसोल आउटपुट:
true
true
ऐसा लगता है कि हमने तीन अलग-अलग प्रकार के तर्कों के साथ संग्रह बनाए हैं - String
और Integer
हमारी अपनी Cat
कक्षा। लेकिन बायटेकोड में रूपांतरण के दौरान, तीनों सूचियाँ बन जाती हैं List<Object>
, इसलिए जब प्रोग्राम चलता है तो यह हमें बताता है कि हम तीनों मामलों में एक ही वर्ग का उपयोग कर रहे हैं।
सरणियों और जेनरिक के साथ काम करते समय मिटाना टाइप करें
सरणियों और सामान्य वर्गों (जैसेList
) के साथ काम करते समय एक बहुत ही महत्वपूर्ण बिंदु स्पष्ट रूप से समझा जाना चाहिए। अपने प्रोग्राम के लिए डेटा स्ट्रक्चर चुनते समय आपको इसे भी ध्यान में रखना चाहिए। जेनरिक प्रकार विलोपन के अधीन हैं। रनटाइम पर टाइप पैरामीटर के बारे में जानकारी उपलब्ध नहीं है। इसके विपरीत, जब प्रोग्राम चल रहा होता है तो सरणियाँ अपने डेटा प्रकार के बारे में जानती हैं और जानकारी का उपयोग कर सकती हैं। एक अमान्य प्रकार को सरणी में डालने का प्रयास करने से अपवाद फेंक दिया जाएगा:
public class Main2 {
public static void main(String[] args) {
Object x[] = new String[3];
x[0] = new Integer(222);
}
}
कंसोल आउटपुट:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
क्योंकि सरणियों और जेनरिक के बीच इतना बड़ा अंतर है, उनमें संगतता संबंधी समस्याएं हो सकती हैं। इन सबसे ऊपर, आप सामान्य वस्तुओं की एक सरणी या यहाँ तक कि केवल एक पैरामिट्रीकृत सरणी नहीं बना सकते। क्या यह थोड़ा भ्रमित करने वाला है? चलो एक नज़र मारें। उदाहरण के लिए, आप जावा में इनमें से कुछ भी नहीं कर सकते हैं:
new List<T>[]
new List<String>[]
new T[]
यदि हम वस्तुओं की एक सरणी बनाने का प्रयास करते हैं List<String>
, तो हमें एक संकलन त्रुटि मिलती है जो सामान्य सरणी निर्माण के बारे में शिकायत करती है:
import java.util.List;
public class Main2 {
public static void main(String[] args) {
// Compilation error! Generic array creation
List<String>[] stringLists = new List<String>[1];
}
}
लेकिन ऐसा क्यों किया जाता है? ऐसी सरणियों के निर्माण की अनुमति क्यों नहीं है? यह सभी प्रकार की सुरक्षा प्रदान करने के लिए है। यदि संकलक हमें सामान्य वस्तुओं के ऐसे सरणियाँ बनाने देता है, तो हम अपने लिए बहुत सी समस्याएँ खड़ी कर सकते हैं। यहाँ जोशुआ बलोच की पुस्तक "प्रभावी जावा" से एक सरल उदाहरण दिया गया है:
public static void main(String[] args) {
List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42, 65, 44); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
}
आइए कल्पना करें कि एक सरणी बनाने की List<String>[] stringLists
अनुमति है और संकलन त्रुटि उत्पन्न नहीं करेगा। अगर यह सच होता, तो यहां कुछ चीजें हैं जो हम कर सकते हैं: पंक्ति 1 में, हम सूचियों की एक सरणी बनाते हैं: List<String>[] stringLists
. हमारे सरणी में एक है List<String>
। पंक्ति 2 में, हम संख्याओं की एक सूची बनाते हैं: List<Integer>
. पंक्ति 3 में, हम List<String>[]
एक Object[] objects
वेरिएबल को असाइन करते हैं। जावा भाषा इसकी अनुमति देती है: X
वस्तुओं की एक सरणी X
वस्तुओं और सभी उपवर्गों की वस्तुओं को संग्रहीत कर सकती है X
। Object
तदनुसार, आप किसी सरणी में कुछ भी डाल सकते हैं । पंक्ति 4 में, हम सरणी के एकमात्र तत्व objects()
(a List<String>
) को a से प्रतिस्थापित करते हैं List<Integer>
। इस प्रकार, हम List<Integer>
एक सरणी में डालते हैं जिसका उद्देश्य केवल स्टोर करना थाList<String>
वस्तुओं! हम एक त्रुटि का सामना तभी करेंगे जब हम लाइन 5 को निष्पादित करेंगे। A को ClassCastException
रनटाइम पर फेंका जाएगा। तदनुसार, इस तरह के सरणियों के निर्माण पर प्रतिबंध जावा में जोड़ा गया था। इससे हम ऐसी स्थितियों से बच सकते हैं।
मैं टाइप इरेज़र से कैसे बच सकता हूँ?
खैर, हमने टाइप इरेज़र के बारे में सीखा। आइए सिस्टम को चकमा देने की कोशिश करें! :) कार्य: हमारे पास एक सामान्यTestClass<T>
वर्ग है। हम इस वर्ग के लिए एक विधि लिखना चाहते हैं जो एक नई वस्तु createNewT()
बनाए और लौटाए । T
लेकिन यह असंभव है, है ना? T
संकलन के दौरान प्रकार के बारे में सभी जानकारी मिटा दी जाती है, और रनटाइम पर हम यह निर्धारित नहीं कर सकते कि हमें किस प्रकार की वस्तु बनाने की आवश्यकता है। ऐसा करने का वास्तव में एक पेचीदा तरीका है। आपको शायद याद होगा कि जावा में एक Class
क्लास होती है। हम इसका उपयोग अपनी किसी भी वस्तु के वर्ग को निर्धारित करने के लिए कर सकते हैं:
public class Main2 {
public static void main(String[] args) {
Class classInt = Integer.class;
Class classString = String.class;
System.out.println(classInt);
System.out.println(classString);
}
}
कंसोल आउटपुट:
class java.lang.Integer
class java.lang.String
लेकिन यहां एक पहलू है जिसके बारे में हमने बात नहीं की है। Oracle दस्तावेज़ीकरण में, आप देखेंगे कि Class वर्ग सामान्य है! 
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
Integer.class
सिर्फ नहीं है Class
, बल्कि है Class<Integer>
। वस्तु का प्रकार String.class
केवल नहीं है Class
, बल्कि Class<String>
आदि है। यदि यह अभी भी स्पष्ट नहीं है, तो पिछले उदाहरण में एक प्रकार पैरामीटर जोड़ने का प्रयास करें:
public class Main2 {
public static void main(String[] args) {
Class<Integer> classInt = Integer.class;
// Compilation error!
Class<String> classInt2 = Integer.class;
Class<String> classString = String.class;
// Compilation error!
Class<Double> classString2 = String.class;
}
}
और अब, इस ज्ञान का उपयोग करके, हम टाइप इरेज़र को बायपास कर सकते हैं और अपना कार्य पूरा कर सकते हैं! आइए एक प्रकार के पैरामीटर के बारे में जानकारी प्राप्त करने का प्रयास करें। हमारा प्रकार तर्क होगा MySecretClass
:
public class MySecretClass {
public MySecretClass() {
System.out.println("A MySecretClass object was created successfully!");
}
}
और यहाँ बताया गया है कि व्यवहार में हम अपने समाधान का उपयोग कैसे करते हैं:
public class TestClass<T> {
Class<T> typeParameterClass;
public TestClass(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
public T createNewT() throws IllegalAccessException, InstantiationException {
T t = typeParameterClass.newInstance();
return t;
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
MySecretClass secret = testString.createNewT();
}
}
कंसोल आउटपुट:
A MySecretClass object was created successfully!
हमने अपने सामान्य वर्ग के निर्माता के लिए आवश्यक वर्ग तर्क पारित किया है:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
इसने हमें प्रकार के तर्क के बारे में जानकारी को बचाने की अनुमति दी, इसे पूरी तरह से मिटाए जाने से रोका। नतीजतन, हम एक बनाने में सक्षम थेT
वस्तु! :) इसके साथ ही आज का पाठ समाप्त होता है। जेनरिक के साथ काम करते समय आपको हमेशा टाइप इरेज़र याद रखना चाहिए। यह वर्कअराउंड बहुत सुविधाजनक नहीं लगता है, लेकिन आपको यह समझना चाहिए कि जेनेरिक जावा भाषा का हिस्सा नहीं थे जब इसे बनाया गया था। यह सुविधा, जो हमें पैरामीटरयुक्त संग्रह बनाने और संकलन के दौरान त्रुटियों को पकड़ने में मदद करती है, पर बाद में काम किया गया। कुछ अन्य भाषाओं में जिनमें पहले संस्करण से जेनरिक शामिल थे, कोई टाइप इरेज़र नहीं है (उदाहरण के लिए, C# में)। वैसे, हम जेनरिक का अध्ययन नहीं कर रहे हैं! अगले पाठ में आप जेनरिक की कुछ और विशेषताओं से परिचित होंगे। अभी के लिए, कुछ कार्यों को हल करना अच्छा होगा! :)
GO TO FULL VERSION