परिचय
JSE 5.0 से शुरू होकर, जेनरिक को जावा भाषा के शस्त्रागार में जोड़ा गया।
जावा में जेनरिक क्या हैं?
जेनेरिक प्रोग्रामिंग को लागू करने के लिए जेनरिक जावा का विशेष तंत्र है - डेटा और एल्गोरिदम का वर्णन करने का एक तरीका जो आपको एल्गोरिदम के विवरण को बदले बिना विभिन्न डेटाटाइप्स के साथ काम करने देता है। Oracle वेबसाइट में जेनरिक को समर्पित एक अलग ट्यूटोरियल है: "
सबक "। जेनरिक को समझने के लिए, आपको सबसे पहले यह पता लगाना होगा कि उनकी आवश्यकता क्यों है और वे क्या देते हैं। "
जेनरिक का उपयोग क्यों करें? " ट्यूटोरियल का खंड कहता है कि कुछ उद्देश्य संकलन समय पर मजबूत प्रकार की जांच कर रहे हैं और स्पष्ट जातियों की आवश्यकता को समाप्त कर रहे हैं।
आइए हमारे प्रिय
ट्यूटोरियलस्पॉट ऑनलाइन जावा कंपाइलर में कुछ परीक्षणों की तैयारी करें। मान लें कि आपके पास निम्न कोड है:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List list = new ArrayList();
list.add("Hello");
String text = list.get(0) + ", world!";
System.out.print(text);
}
}
यह कोड पूरी तरह से ठीक चलेगा। लेकिन क्या हो अगर बॉस हमारे पास आए और कहे कि "हैलो, दुनिया!" एक अत्यधिक उपयोग किया जाने वाला वाक्यांश है और आपको केवल "हैलो" वापस करना चाहिए?
हम ", दुनिया!" को जोड़ने वाले कोड को हटा देंगे। यह काफी हानिरहित लगता है, है ना? लेकिन हमें वास्तव में संकलन समय पर एक त्रुटि मिलती है:
error: incompatible types: Object cannot be converted to String
समस्या यह है कि हमारी सूची में वस्तुओं को संग्रहीत करता है।
स्ट्रिंग ऑब्जेक्ट का वंशज है (चूंकि सभी जावा वर्ग अंतर्निहित रूप से
ऑब्जेक्ट इनहेरिट करते हैं), जिसका अर्थ है कि हमें एक स्पष्ट कास्ट की आवश्यकता है, लेकिन हमने एक नहीं जोड़ा। कॉन्टेनेशन ऑपरेशन के दौरान, ऑब्जेक्ट का उपयोग करके स्टैटिक
String.valueOf(obj) मेथड को कॉल किया जाएगा। आखिरकार, यह ऑब्जेक्ट क्लास की
टूस्ट्रिंग विधि को कॉल करेगा । दूसरे शब्दों में, हमारी
सूची में एक
वस्तु है ।
इसका मतलब यह है कि जहाँ भी हमें एक विशिष्ट प्रकार ( ऑब्जेक्ट नहीं ) की आवश्यकता होती है, हमें स्वयं रूपांतरण करना होगा:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List list = new ArrayList();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println("-" + (String)str);
}
}
}
हालांकि, इस मामले में, क्योंकि
सूची वस्तुओं को लेती है, यह न केवल
स्ट्रिंग एस, बल्कि
इंटीजर एस भी स्टोर कर सकती है। लेकिन सबसे बुरी बात यह है कि कंपाइलर को यहां कुछ भी गलत नहीं दिखता। और अब हमें रन टाइम पर त्रुटि मिलेगी ("रनटाइम त्रुटि" के रूप में जाना जाता है)। त्रुटि होगी:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
आप सहमत होंगे कि यह बहुत अच्छा नहीं है। और यह सब इसलिए क्योंकि कंपाइलर एक कृत्रिम बुद्धिमत्ता नहीं है जो प्रोग्रामर के इरादे का हमेशा सही अनुमान लगाने में सक्षम हो। जावा एसई 5 ने जेनेरिक को पेश किया ताकि हम कंपाइलर को हमारे इरादों के बारे में बता सकें - हम किस प्रकार का उपयोग करने जा रहे हैं। हम संकलक को बताकर अपना कोड ठीक करते हैं कि हम क्या चाहते हैं:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List<String> list = new ArrayList<>();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println("-" + str);
}
}
}
जैसा कि आप देख सकते हैं, हमें अब स्ट्रिंग में कास्ट की आवश्यकता नहीं है । इसके अलावा, हमारे पास प्रकार के तर्क के आसपास कोण कोष्ठक हैं। अब कंपाइलर हमें क्लास को तब तक कंपाइल नहीं करने देगा जब तक कि हम लिस्ट में 123 जोड़ने वाली लाइन को हटा नहीं देते, क्योंकि यह एक
इंटीजर है । और यह हमें ऐसा बताएगा। बहुत से लोग जेनरिक को "सिंटैक्टिक शुगर" कहते हैं। और वे सही हैं, क्योंकि जेनरिक संकलित होने के बाद, वे वास्तव में एक ही प्रकार के रूपांतरण बन जाते हैं। आइए संकलित कक्षाओं के बायटेकोड को देखें: एक जो एक स्पष्ट कास्ट का उपयोग करता है और एक जो जेनरिक का उपयोग करता है:
संकलन के बाद, सभी जेनरिक मिटा दिए जाते हैं। इसे "
टाइप इरेज़र" कहा जाता है"। टाइप इरेज़र और जेनरिक को JDK के पुराने संस्करणों के साथ पिछड़े संगत होने के लिए डिज़ाइन किया गया है, साथ ही साथ कंपाइलर को जावा के नए संस्करणों में टाइप परिभाषाओं में मदद करने की अनुमति देता है।
कच्चे प्रकार
जेनरिक की बात करें तो हमारे पास हमेशा दो श्रेणियां होती हैं: पैरामीटरयुक्त प्रकार और कच्चे प्रकार। कच्चे प्रकार वे प्रकार हैं जो कोण कोष्ठक में "प्रकार स्पष्टीकरण" को छोड़ देते हैं:
हाथ में पैरामिट्रीकृत प्रकार, एक "स्पष्टीकरण" शामिल करते हैं:
जैसा कि आप देख सकते हैं, हमने एक असामान्य निर्माण का उपयोग किया, जिसे स्क्रीनशॉट में एक तीर द्वारा चिह्नित किया गया है। यह विशेष सिंटैक्स है जिसे जावा एसई 7 में जोड़ा गया था। इसे "
हीरा " कहा जाता है। क्यों? कोण कोष्ठक एक हीरा बनाते हैं:
<> । आपको यह भी पता होना चाहिए कि डायमंड सिंटैक्स "
टाइप इंट्रेंस " की अवधारणा से जुड़ा है। आखिरकार, कंपाइलर,
<> देखकरदाईं ओर, असाइनमेंट ऑपरेटर के बाईं ओर देखता है, जहां यह वेरिएबल का प्रकार ढूंढता है जिसका मान असाइन किया जा रहा है। इस भाग में उसे जो मिलता है, उसके आधार पर वह दाईं ओर के मान के प्रकार को समझता है। वास्तव में, यदि बाईं ओर एक सामान्य प्रकार दिया गया है, लेकिन दाईं ओर नहीं, तो संकलक प्रकार का अनुमान लगा सकता है:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List<String> list = new ArrayList();
list.add("Hello, World");
String data = list.get(0);
System.out.println(data);
}
}
लेकिन यह नई शैली को जेनरिक के साथ और पुरानी शैली को उनके बिना मिला देता है। और यह अत्यधिक अवांछनीय है। उपरोक्त कोड को संकलित करते समय, हमें निम्न संदेश मिलता है:
Note: HelloWorld.java uses unchecked or unsafe operations
वास्तव में, आपको यहां एक हीरा जोड़ने की आवश्यकता क्यों है, यह समझ से बाहर है। लेकिन यहाँ एक उदाहरण है:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List<String> list = Arrays.asList("Hello", "World");
List<Integer> data = new ArrayList(list);
Integer intNumber = data.get(0);
System.out.println(data);
}
}
आपको याद होगा कि
ArrayList में एक दूसरा कंस्ट्रक्टर है जो एक तर्क के रूप में एक संग्रह लेता है। और यहीं पर कुछ भयावह छिपा है। डायमंड सिंटैक्स के बिना, कंपाइलर यह नहीं समझता है कि उसे धोखा दिया जा रहा है। डायमंड सिंटैक्स के साथ, यह करता है। तो, नियम # 1 है: हमेशा पैरामिट्रीकृत प्रकारों के साथ हीरा सिंटैक्स का उपयोग करें। अन्यथा, हम उस स्थान से चूकने का जोखिम उठाते हैं जहां हम कच्चे प्रकारों का उपयोग कर रहे हैं। "अनियंत्रित या असुरक्षित संचालन का उपयोग करता है" चेतावनियों को समाप्त करने के लिए, हम किसी विधि या वर्ग पर
@SuppressWarnings("unchecked") एनोटेशन का उपयोग कर सकते हैं। लेकिन इस बारे में सोचें कि आपने इसे इस्तेमाल करने का फैसला क्यों किया। नियम नंबर एक याद रखें। शायद आपको एक प्रकार का तर्क जोड़ने की जरूरत है।
जावा जेनेरिक तरीके
जेनरिक आपको ऐसे तरीके बनाने देते हैं जिनके पैरामीटर प्रकार और रिटर्न प्रकार पैरामीटरयुक्त होते हैं। Oracle ट्यूटोरियल में एक अलग खंड इस क्षमता के लिए समर्पित है: "
सामान्य तरीके "। इस ट्यूटोरियल में पढ़ाए गए सिंटैक्स को याद रखना महत्वपूर्ण है:
- इसमें कोण कोष्ठक के अंदर प्रकार के मापदंडों की एक सूची शामिल है;
- प्रकार पैरामीटर की सूची विधि के रिटर्न प्रकार से पहले जाती है।
आइए एक उदाहरण देखें:
import java.util.*;
public class HelloWorld {
public static class Util {
public static <T> T getValue(Object obj, Class<T> clazz) {
return (T) obj;
}
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList("Author", "Book");
for (Object element : list) {
String data = Util.getValue(element, String.class);
System.out.println(data);
System.out.println(Util.<String>getValue(element));
}
}
}
यदि आप
यूटिल क्लास को देखते हैं, तो आप देखेंगे कि इसके दो सामान्य तरीके हैं। प्रकार के अनुमान की संभावना के लिए धन्यवाद, हम या तो संकलक को सीधे प्रकार का संकेत दे सकते हैं, या हम इसे स्वयं निर्दिष्ट कर सकते हैं। दोनों विकल्प उदाहरण में प्रस्तुत किए गए हैं। वैसे, अगर आप इसके बारे में सोचते हैं तो सिंटैक्स बहुत मायने रखता है। एक सामान्य विधि की घोषणा करते समय, हम विधि से पहले प्रकार के पैरामीटर को निर्दिष्ट करते हैं, क्योंकि यदि हम विधि के बाद प्रकार के पैरामीटर की घोषणा करते हैं, तो JVM यह पता लगाने में सक्षम नहीं होगा कि किस प्रकार का उपयोग करना है।
तदनुसार, हम पहले घोषणा करते हैं कि हम टी प्रकार के पैरामीटर का उपयोग करेंगे , और फिर हम कहते हैं कि हम इस प्रकार को वापस करने जा रहे हैं। स्वाभाविक रूप से,
Util.<Integer>getValue(element, String.class) एक त्रुटि के साथ विफल हो जाएगा:
असंगत प्रकार: Class<String> को Class<Integer> में परिवर्तित नहीं किया जा सकता है । सामान्य तरीकों का उपयोग करते समय, आपको हमेशा टाइप इरेज़र याद रखना चाहिए। आइए एक उदाहरण देखें:
import java.util.*;
public class HelloWorld {
public static class Util {
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList(2, 3);
for (Object element : list) {
System.out.println(Util.<Integer>getValue(element) + 1);
}
}
}
यह ठीक चलेगा। लेकिन जब तक संकलक समझता है कि जिस विधि को बुलाया जा रहा है उसका रिटर्न प्रकार
Integer है । कंसोल आउटपुट स्टेटमेंट को निम्न पंक्ति से बदलें:
System.out.println(Util.getValue(element) + 1);
हमें त्रुटि मिलती है:
bad operand types for binary operator '+', first type: Object, second type: int.
दूसरे शब्दों में, प्रकार मिटाया गया है। कंपाइलर देखता है कि किसी ने भी प्रकार निर्दिष्ट नहीं किया है, इसलिए प्रकार को
ऑब्जेक्ट के रूप में इंगित किया गया है और विधि त्रुटि के साथ विफल हो जाती है।
सामान्य वर्ग
न केवल तरीकों को पैरामिट्रीकृत किया जा सकता है। क्लास भी कर सकते हैं।
Oracle के ट्यूटोरियल का "जेनेरिक प्रकार" खंड इसके लिए समर्पित है। आइए एक उदाहरण पर विचार करें:
public static class SomeType<T> {
public <E> void test(Collection<E> collection) {
for (E element : collection) {
System.out.println(element);
}
}
public void test(List<Integer> collection) {
for (Integer element : collection) {
System.out.println(element);
}
}
}
यहाँ सब कुछ सरल है। यदि हम सामान्य वर्ग का उपयोग करते हैं, तो वर्ग के नाम के बाद प्रकार पैरामीटर का संकेत दिया जाता है।
अब मुख्य विधि में इस वर्ग का एक उदाहरण बनाते हैं :
public static void main(String []args) {
SomeType<String> st = new SomeType<>();
List<String> list = Arrays.asList("test");
st.test(list);
}
यह कोड अच्छा चलेगा। संकलक देखता है कि संख्याओं की
सूची और
स्ट्रिंग्स का
संग्रह है । लेकिन क्या होगा अगर हम टाइप पैरामीटर को खत्म कर दें और ऐसा करें:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
हमें त्रुटि मिलती है:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
फिर से, यह टाइप इरेज़र है। चूंकि वर्ग अब एक प्रकार के पैरामीटर का उपयोग नहीं करता है, इसलिए संकलक यह तय करता है कि, चूंकि हमने एक
सूची पारित की है, सूची के साथ विधि <पूर्णांक> सबसे उपयुक्त है। और हम एक त्रुटि के साथ असफल हो जाते हैं। इसलिए, हमारे पास नियम #2 है: यदि आपके पास एक सामान्य वर्ग है, तो हमेशा प्रकार के पैरामीटर निर्दिष्ट करें।
प्रतिबंध
हम सामान्य तरीकों और एन कक्षाओं में निर्दिष्ट प्रकारों को प्रतिबंधित कर सकते हैं।
उदाहरण के लिए, मान लीजिए कि हम चाहते हैं कि एक कंटेनर केवल एक नंबर को टाइप तर्क के रूप में स्वीकार करे । यह सुविधा Oracle के ट्यूटोरियल के
बाउंडेड टाइप पैरामीटर्स सेक्शन में वर्णित है । आइए एक उदाहरण देखें:
import java.util.*;
public class HelloWorld {
public static class NumberContainer<T extends Number> {
private T number;
public NumberContainer(T number) { this.number = number; }
public void print() {
System.out.println(number);
}
}
public static void main(String []args) {
NumberContainer number1 = new NumberContainer(2L);
NumberContainer number2 = new NumberContainer(1);
NumberContainer number3 = new NumberContainer("f");
}
}
जैसा कि आप देख सकते हैं, हमने प्रकार पैरामीटर को
संख्या वर्ग/इंटरफ़ेस या उसके वंशजों तक सीमित कर दिया है। ध्यान दें कि आप न केवल एक वर्ग, बल्कि इंटरफेस भी निर्दिष्ट कर सकते हैं। उदाहरण के लिए:
public static class NumberContainer<T extends Number & Comparable> {
जेनरिक भी
वाइल्डकार्ड का समर्थन करते हैं वे तीन प्रकारों में विभाजित हैं:
वाइल्डकार्ड के आपके उपयोग को
गेट-पुट सिद्धांत का पालन करना चाहिए । इसे निम्नानुसार व्यक्त किया जा सकता है:
- जब आप केवल संरचना से मान प्राप्त करते हैं तो विस्तृत वाइल्डकार्ड का उपयोग करें ।
- किसी संरचना में केवल मान डालने पर सुपर वाइल्डकार्ड का उपयोग करें ।
- और जब आप दोनों किसी संरचना से/से प्राप्त करना और रखना चाहते हैं तो वाइल्डकार्ड का उपयोग न करें।
इस सिद्धांत को प्रोड्यूसर एक्सटेंड्स कंज्यूमर सुपर (पीईसीएस) सिद्धांत भी कहा जाता है।
यहाँ Java के Collections.copy मेथड के लिए सोर्स कोड से एक छोटा सा उदाहरण दिया गया है :
और यहाँ एक छोटा सा उदाहरण है कि क्या काम नहीं करेगा:
public static class TestClass {
public static void print(List<? extends String> list) {
list.add("Hello, World!");
System.out.println(list.get(0));
}
}
public static void main(String []args) {
List<String> list = new ArrayList<>();
TestClass.print(list);
}
लेकिन अगर आप
विस्तार को सुपर से बदलते हैं , तो सब कुछ ठीक है। क्योंकि हम
सूची की सामग्री को प्रदर्शित करने से पहले एक मूल्य के साथ पॉप्युलेट करते हैं, यह एक
उपभोक्ता है । तदनुसार, हम सुपर का उपयोग करते हैं।
विरासत
जेनरिक की एक और दिलचस्प विशेषता है: वंशानुक्रम। जिस तरह से वंशानुक्रम जेनरिक के लिए काम करता है उसका वर्णन Oracle के ट्यूटोरियल में "
जेनेरिक, इनहेरिटेंस और सबटाइप्स " के तहत किया गया है। महत्वपूर्ण बात निम्नलिखित को याद रखना और पहचानना है। हम ऐसा नहीं कर सकते:
List<CharSequence> list1 = new ArrayList<String>();
क्योंकि वंशानुक्रम जेनरिक के साथ अलग तरह से काम करता है:
और यहाँ एक और अच्छा उदाहरण है जो एक त्रुटि के साथ विफल हो जाएगा:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
यहाँ फिर से सब कुछ सरल है।
सूची <स्ट्रिंग> सूची <ऑब्जेक्ट> का वंशज नहीं है , भले ही
स्ट्रिंग ऑब्जेक्ट का वंशज है ।
आपने जो सीखा है उसे सुदृढ़ करने के लिए, हमारा सुझाव है कि आप हमारे जावा कोर्स से एक वीडियो सबक देखें
निष्कर्ष
इसलिए हमने जेनरिक से संबंधित अपनी स्मृति को ताज़ा किया है। यदि आप शायद ही कभी उनकी क्षमताओं का पूरा लाभ उठाते हैं, तो कुछ विवरण अस्पष्ट हो जाते हैं। मुझे उम्मीद है कि इस संक्षिप्त समीक्षा ने आपकी याददाश्त को जॉग करने में मदद की है। और भी बेहतर परिणामों के लिए, मैं दृढ़ता से अनुशंसा करता हूं कि आप स्वयं को निम्नलिखित सामग्री से परिचित कराएं:
GO TO FULL VERSION