परिचय
JSE 5.0 सह प्रारंभ करून, Java भाषेच्या शस्त्रागारात जेनेरिक जोडले गेले.
जावा मध्ये जेनेरिक काय आहेत?
जेनेरिक ही जेनेरिक प्रोग्रामिंगची अंमलबजावणी करण्यासाठी Java ची खास यंत्रणा आहे — डेटा आणि अल्गोरिदमचे वर्णन करण्याचा एक मार्ग जो तुम्हाला अल्गोरिदमचे वर्णन न बदलता वेगवेगळ्या डेटाटाइपसह कार्य करू देतो. ओरॅकल वेबसाइटवर जेनेरिकसाठी समर्पित एक स्वतंत्र ट्यूटोरियल आहे: "
धडा ". जेनेरिक्स समजून घेण्यासाठी, आपल्याला प्रथम त्यांची आवश्यकता का आहे आणि ते काय देतात हे शोधणे आवश्यक आहे. ट्यूटोरियलचा "
जेनेरिक्स का वापरा? " विभाग म्हणतो की संकलित वेळी अधिक मजबूत प्रकार तपासणे आणि स्पष्ट जातींची गरज दूर करणे हे दोन उद्देश आहेत.
चला आमच्या प्रिय
ट्यूटोरियल पॉइंट ऑनलाइन जावा कंपाइलरमधील काही चाचण्यांची तयारी करूया. समजा तुमच्याकडे खालील कोड आहे:
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
समस्या अशी आहे की आमच्या सूचीमध्ये ऑब्जेक्ट्स संग्रहित केले जातात.
स्ट्रिंग हे ऑब्जेक्टचे वंशज आहे (सर्व Java क्लासेसला
ऑब्जेक्ट द्वारे स्पष्टपणे वारसा मिळत असल्याने ), याचा अर्थ आम्हाला स्पष्ट कास्ट आवश्यक आहे, परंतु आम्ही एक जोडले नाही. कॉन्कटेनेशन ऑपरेशन दरम्यान, ऑब्जेक्ट वापरून स्टॅटिक
String.valueOf(obj) पद्धत कॉल केली जाईल. अखेरीस, ते ऑब्जेक्ट क्लासच्या
toString पद्धतीला कॉल करेल . दुसऱ्या शब्दांत, आमच्या
सूचीमध्ये एक
ऑब्जेक्ट आहे . याचा अर्थ असा की जिथे आम्हाला विशिष्ट प्रकारची आवश्यकता आहे (
ऑब्जेक्ट नाही ), आम्हाला टाइप रूपांतरण स्वतः करावे लागेल:
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);
}
}
}
तथापि, या प्रकरणात, कारण
सूची ऑब्जेक्ट्स घेते, ती केवळ
String s नाही तर
Integer s देखील संचयित करू शकते. पण सर्वात वाईट गोष्ट अशी आहे की कंपाइलरला येथे काहीही चुकीचे दिसत नाही. आणि आता आम्हाला RUN TIME मध्ये एक त्रुटी मिळेल ("रनटाइम त्रुटी" म्हणून ओळखली जाते). त्रुटी असेल:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
हे फार चांगले नाही हे तुम्ही मान्य केलेच पाहिजे. आणि हे सर्व कारण कंपाइलर ही कृत्रिम बुद्धिमत्ता नाही जी नेहमी प्रोग्रामरच्या हेतूचा अचूक अंदाज लावू शकते. Java SE 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 च्या जुन्या आवृत्त्यांशी बॅकवर्ड सुसंगत करण्यासाठी डिझाइन केलेले आहेत आणि त्याच वेळी कंपाइलरला Java च्या नवीन आवृत्त्यांमध्ये टाइप परिभाषांमध्ये मदत करण्यास परवानगी देतात.
कच्चा प्रकार
जेनेरिकबद्दल बोलताना, आमच्याकडे नेहमी दोन श्रेणी असतात: पॅरामीटराइज्ड प्रकार आणि कच्चे प्रकार. कच्चे प्रकार हे कोन कंसात "प्रकार स्पष्टीकरण" वगळणारे प्रकार आहेत:
पॅरामीटराइज्ड प्रकारांमध्ये, एक "स्पष्टीकरण" समाविष्ट आहे:
जसे आपण पाहू शकता, आम्ही स्क्रीनशॉटमध्ये बाणाने चिन्हांकित केलेले असामान्य बांधकाम वापरले आहे.
हा विशेष वाक्यरचना आहे जो Java SE 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("अनचेक केलेले") भाष्य वापरू शकतो . पण तुम्ही ते वापरायचे का ठरवले आहे याचा विचार करा. नियम क्रमांक एक लक्षात ठेवा. कदाचित तुम्हाला एक प्रकारचा युक्तिवाद जोडण्याची आवश्यकता आहे.
जावा जेनेरिक पद्धती
जेनेरिक्स तुम्हाला अशा पद्धती तयार करू देतात ज्यांचे पॅरामीटर प्रकार आणि रिटर्न प्रकार पॅरामीटराइज्ड आहेत. ओरॅकल ट्यूटोरियलमध्ये या क्षमतेसाठी एक वेगळा विभाग समर्पित आहे: "
जेनेरिक पद्धती ". या ट्यूटोरियलमध्ये शिकवलेले वाक्यरचना लक्षात ठेवणे महत्त्वाचे आहे:
- यामध्ये अँगल ब्रॅकेटमधील प्रकार पॅरामीटर्सची सूची समाविष्ट आहे;
- प्रकार पॅरामीटर्सची यादी पद्धतीच्या रिटर्न प्रकारापूर्वी जाते.
चला एक उदाहरण पाहू:
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));
}
}
}
तुम्ही Util वर्ग पाहिल्यास , तुम्हाला दिसेल की त्यात दोन सामान्य पद्धती आहेत. प्रकार अनुमानाच्या शक्यतेबद्दल धन्यवाद, आम्ही एकतर प्रकार थेट कंपाइलरला सूचित करू शकतो किंवा आम्ही ते स्वतः निर्दिष्ट करू शकतो. दोन्ही पर्याय उदाहरणात सादर केले आहेत. तसे, आपण विचार केल्यास वाक्यरचना खूप अर्थपूर्ण आहे. जेनेरिक पद्धत घोषित करताना, आम्ही पद्धतीच्या आधी प्रकार पॅरामीटर निर्दिष्ट करतो, कारण जर आम्ही पद्धती नंतर प्रकार पॅरामीटर घोषित केले, तर JVM कोणता प्रकार वापरायचा हे शोधण्यात सक्षम होणार नाही.
त्यानुसार, आम्ही प्रथम घोषित करतो की आम्ही T प्रकार पॅरामीटर वापरू , आणि नंतर आम्ही म्हणतो की आम्ही हा प्रकार परत करणार आहोत. स्वाभाविकच,
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);
}
}
}
हे अगदी व्यवस्थित चालेल. पण जोपर्यंत कंपाइलरला समजते की ज्या पद्धतीला कॉल केला जात आहे त्याचा रिटर्न प्रकार
पूर्णांक आहे . कन्सोल आउटपुट स्टेटमेंट खालील ओळीने बदला:
System.out.println(Util.getValue(element) + 1);
आम्हाला एक त्रुटी मिळाली:
bad operand types for binary operator '+', first type: Object, second type: int.
दुसऱ्या शब्दांत, टाइप इरेजर आली आहे. कंपाइलर पाहतो की कोणीही प्रकार निर्दिष्ट केलेला नाही, म्हणून प्रकार
ऑब्जेक्ट म्हणून दर्शविला जातो आणि पद्धत त्रुटीसह अयशस्वी होते.
सामान्य वर्ग
केवळ पद्धतींचे पॅरामीटराइज्ड केले जाऊ शकत नाही. वर्ग तसेच करू शकता. ओरॅकलच्या ट्यूटोरियलचा
"जेनेरिक प्रकार" विभाग यासाठी समर्पित आहे. चला एक उदाहरण विचारात घेऊया:
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
पुन्हा, हे प्रकार इरेजर आहे. क्लास यापुढे टाइप पॅरामीटर वापरत नसल्यामुळे, कंपाइलर ठरवतो की, आम्ही एक
List पास केल्यामुळे, List<Integer> ही पद्धत सर्वात योग्य आहे. आणि आम्ही एका त्रुटीने अयशस्वी होतो. म्हणून, आमच्याकडे नियम # 2 आहे: जर तुमच्याकडे सामान्य वर्ग असेल, तर नेहमी प्रकार पॅरामीटर्स निर्दिष्ट करा.
निर्बंध
आम्ही जेनेरिक पद्धती आणि वर्गांमध्ये निर्दिष्ट केलेल्या प्रकारांना प्रतिबंधित करू शकतो. उदाहरणार्थ, समजा आम्हाला कंटेनरने टाइप आर्ग्युमेंट म्हणून फक्त
संख्या स्वीकारायची आहे . या वैशिष्ट्याचे वर्णन ओरॅकलच्या ट्युटोरियलच्या
बाउंडेड टाइप पॅरामीटर्स विभागात केले आहे. चला एक उदाहरण पाहू:
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> {
जेनेरिक्स वाइल्डकार्डला देखील समर्थन देतात ते तीन प्रकारांमध्ये विभागलेले आहेत:
तुमचा वाइल्डकार्ड वापरताना
गेट-पुट तत्त्वाचे पालन केले पाहिजे . हे खालीलप्रमाणे व्यक्त केले जाऊ शकते:
- जेव्हा तुम्हाला केवळ संरचनेतून मूल्ये मिळतात तेव्हा विस्तारित वाइल्डकार्ड वापरा .
- जेव्हा तुम्ही केवळ संरचनेत मूल्ये ठेवता तेव्हा सुपर वाइल्डकार्ड वापरा .
- आणि जेव्हा तुम्ही दोघांना एखाद्या स्ट्रक्चरमधून/मध्ये मिळवायचे असेल आणि ठेवायचे असेल तेव्हा वाइल्डकार्ड वापरू नका.
या तत्त्वाला प्रोड्यूसर एक्स्टेंड्स कन्झ्युमर सुपर (PECS) तत्त्व असेही म्हणतात.
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);
}
परंतु जर तुम्ही
एक्स्टेंड्सला सुपरने बदलले तर सर्वकाही ठीक आहे. कारण आम्ही
सूचीची सामग्री प्रदर्शित करण्यापूर्वी मूल्यासह भरतो, तो एक
उपभोक्ता आहे . त्यानुसार, आम्ही सुपर वापरतो.
वारसा
जेनेरिकमध्ये आणखी एक मनोरंजक वैशिष्ट्य आहे: वारसा. ओरॅकलच्या ट्यूटोरियलमध्ये "
जेनेरिक्स, इनहेरिटन्स आणि सबटाइप " अंतर्गत जेनेरिक्ससाठी वारसा कार्य करण्याच्या पद्धतीचे वर्णन केले आहे . महत्त्वाची गोष्ट म्हणजे खालील गोष्टी लक्षात ठेवणे आणि ओळखणे. आम्ही हे करू शकत नाही:
List<CharSequence> list1 = new ArrayList<String>();
कारण वारसा जेनेरिकसह वेगळ्या पद्धतीने कार्य करते:
आणि येथे आणखी एक चांगले उदाहरण आहे जे त्रुटीसह अयशस्वी होईल:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
पुन्हा, येथे सर्वकाही सोपे आहे.
List<String> हे List<object> चा वंशज नाही , जरी
स्ट्रिंग ऑब्जेक्टचा वंशज आहे .
तुम्ही जे शिकलात ते बळकट करण्यासाठी, आम्ही तुम्हाला आमच्या Java कोर्समधील व्हिडिओ धडा पाहण्याची सूचना देतो
निष्कर्ष
म्हणून आम्ही जेनेरिकशी संबंधित आमची स्मृती ताजी केली आहे. तुम्ही त्यांच्या क्षमतांचा क्वचितच पुरेपूर फायदा घेतल्यास, काही तपशील अस्पष्ट होतात. मला आशा आहे की या लहान पुनरावलोकनाने तुमची स्मरणशक्ती वाढण्यास मदत केली आहे. आणखी चांगल्या परिणामांसाठी, मी जोरदार शिफारस करतो की आपण खालील सामग्रीसह स्वत: ला परिचित करा:
GO TO FULL VERSION