जवाबों:
यह मूल रूप से कंपाइलर प्रवंचना के माध्यम से जावा में जेनरिक को लागू करने का तरीका है। संकलित जेनेरिक कोड वास्तव में सिर्फ java.lang.Object
जहाँ आप T
(या किसी अन्य प्रकार के पैरामीटर) के बारे में बात करते हैं - और कंपाइलर को यह बताने के लिए कुछ मेटाडेटा है कि यह वास्तव में एक सामान्य प्रकार है।
जब आप एक सामान्य प्रकार या विधि के खिलाफ कुछ कोड संकलित करते हैं, तो कंपाइलर काम करता है जिसका आप वास्तव में मतलब है (यानी किस प्रकार का तर्क T
है) और संकलन समय पर पुष्टि करता है कि आप सही काम कर रहे हैं, लेकिन उत्सर्जित कोड फिर से बात करता है के संदर्भ में java.lang.Object
- संकलक जहां आवश्यक हो अतिरिक्त जातियां उत्पन्न करता है। निष्पादन के समय, ए List<String>
और ए List<Date>
बिल्कुल समान हैं; अतिरिक्त प्रकार की जानकारी संकलक द्वारा मिटा दी गई है।
इसकी तुलना सी, # से करें, जहां जानकारी निष्पादन के समय में बरकरार रखी जाती है, जिससे कोड में अभिव्यक्तियाँ हो सकती हैं जैसे कि typeof(T)
वह समतुल्य है T.class
- जिसके सिवाय बाद वाला अमान्य है। (.NET जेनिक्स और जावा जेनरिक के बीच आगे के अंतर हैं, आप पर ध्यान दें।) जावा जेनेरिक के साथ काम करते समय टाइप इरेज़र कई "अजीब" चेतावनी / त्रुटि संदेशों का स्रोत है।
अन्य संसाधन:
Object
(कमजोर रूप से टाइप किए गए परिदृश्य में) प्रदान किया जाता है List<String>
, उदाहरण के लिए। जावा में यह सिर्फ संभव नहीं है - आप पता लगा सकते हैं कि यह एक है ArrayList
, लेकिन यह नहीं कि मूल सामान्य प्रकार क्या था। इस तरह की बात क्रमबद्धता / deserialization स्थितियों में आ सकती है, उदाहरण के लिए। एक अन्य उदाहरण यह है कि एक कंटेनर को अपने सामान्य प्रकार के उदाहरणों का निर्माण करने में सक्षम होना चाहिए - आपको उस प्रकार को जावा (जैसे Class<T>
) में अलग से पास करना होगा ।
Class<T>
एक रचनाकार (या सामान्य विधि) के लिए एक पैरामीटर जोड़ने के लिए मजबूर किया जाता है क्योंकि जावा उस जानकारी को बरकरार नहीं रखता है। को देखो EnumSet.allOf
उदाहरण के लिए - विधि करने के लिए सामान्य प्रकार तर्क पर्याप्त होना चाहिए; मुझे "सामान्य" तर्क भी निर्दिष्ट करने की आवश्यकता है? उत्तर: टाइप इरेज़र। इस तरह की चीज एक एपीआई को प्रदूषित करती है। ब्याज से बाहर, क्या आपने .NET जेनरिक का ज्यादा इस्तेमाल किया है? (जारी)
एक साइड-नोट के रूप में, यह वास्तव में यह देखने के लिए एक दिलचस्प अभ्यास है कि कंपाइलर क्या कर रहा है जब यह मिटाता है - पूरी अवधारणा को समझाना थोड़ा आसान बनाता है। एक विशेष ध्वज है जिसे आप कंपाइलर को जावा फाइलों के आउटपुट में दे सकते हैं जिसमें जेनेरिक मिटाए गए और डाले गए हैं। एक उदाहरण:
javac -XD-printflat -d output_dir SomeFile.java
-printflat
झंडा कि संकलक फ़ाइलें उत्पन्न करता है को हस्तांतरित कर दिया जाता है। ( -XD
भाग वह है जो javac
इसे निष्पादन योग्य जार को सौंपने के लिए कहता है जो वास्तव में संकलन को सिर्फ करने के बजाय करता है javac
, लेकिन मैं पचाता हूं ...) यह -d output_dir
आवश्यक है क्योंकि कंपाइलर को नई .java फाइलें डालने के लिए कुछ जगह की आवश्यकता होती है।
यह, ज़ाहिर है, सिर्फ मिटाने से अधिक करता है; सभी स्वचालित सामान संकलक यहाँ किया जाता है। उदाहरण के लिए, डिफॉल्ट कंस्ट्रक्टर को भी डाला जाता है, नए फॉर्च-स्टाइल for
लूप्स को नियमित for
लूप्स तक विस्तारित किया जाता है, आदि छोटी चीजें जो स्वचालित रूप से हो रही हैं, उन्हें देखना अच्छा है।
Erasure, का शाब्दिक अर्थ है कि स्रोत कोड में मौजूद प्रकार की जानकारी संकलित बायटेकोड से मिटा दी जाती है। इसे कुछ कोड के साथ समझते हैं।
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericsErasure {
public static void main(String args[]) {
List<String> list = new ArrayList<String>();
list.add("Hello");
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String s = iter.next();
System.out.println(s);
}
}
}
यदि आप इस कोड को संकलित करते हैं और फिर इसे जावा डिकंपाइलर के साथ अपघटित करते हैं, तो आपको कुछ ऐसा मिलेगा। ध्यान दें कि विघटित कोड में मूल स्रोत कोड में मौजूद प्रकार की जानकारी का कोई निशान नहीं होता है।
import java.io.PrintStream;
import java.util.*;
public class GenericsErasure
{
public GenericsErasure()
{
}
public static void main(String args[])
{
List list = new ArrayList();
list.add("Hello");
String s;
for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s))
s = (String)iter.next();
}
}
jigawot
कहा, यह काम करता है।
जॉन स्केट के पहले से ही पूर्ण उत्तर को पूरा करने के लिए, आपको जावा के पिछले संस्करणों के साथ संगतता की आवश्यकता से टाइप इरेज़र व्युत्पन्न की अवधारणा का एहसास करना होगा ।
प्रारंभ में एक्लिप्सन 2007 में प्रस्तुत किया गया (अब उपलब्ध नहीं), संगतता में वे बिंदु शामिल हैं:
मूल उत्तर:
अत:
new ArrayList<String>() => new ArrayList()
अधिक से अधिक संशोधन के लिए प्रस्ताव हैं । "वास्तविक के रूप में एक अमूर्त अवधारणा को फिर से पाएं" का संदर्भ दें, जहां भाषा निर्माण केवल अवधारणा चीनी होना चाहिए, न कि अवधारणाएं।
मुझे checkCollection
जावा 6 की विधि का भी उल्लेख करना चाहिए , जो निर्दिष्ट संग्रह के एक गतिशील प्रकार के दृश्य को लौटाता है। गलत प्रकार के तत्व को सम्मिलित करने के किसी भी प्रयास का परिणाम तत्काल होगा ClassCastException
।
भाषा में जेनरिक तंत्र संकलन-समय (स्थैतिक) प्रकार की जाँच प्रदान करता है, लेकिन अनियंत्रित जातियों के साथ इस तंत्र को हराना संभव है ।
आमतौर पर यह कोई समस्या नहीं है, क्योंकि कंपाइलर ऐसे सभी अनियंत्रित परिचालन पर चेतावनी जारी करता है।
हालाँकि, ऐसे समय जब अकेले स्थैतिक प्रकार की जाँच पर्याप्त नहीं है, जैसे:
ClassCastException
, यह दर्शाता है कि एक गलत तरीके से टाइप किया गया तत्व एक मानकीकृत संग्रह में रखा गया था। दुर्भाग्य से, अपवाद किसी भी समय हो सकता है जब गलत तत्व डाला जाता है, तो यह आमतौर पर समस्या के वास्तविक स्रोत के रूप में बहुत कम या कोई जानकारी प्रदान करता है।जुलाई 2012 को अपडेट करें, लगभग चार साल बाद:
अब यह (2012) " एपीआई माइग्रेशन कम्पेटिबिलिटी रूल्स (सिग्नेचर टेस्ट) " में विस्तृत है
जावा प्रोग्रामिंग लैंग्वेज इरेज़र का उपयोग करके जेनरिक को लागू करती है, जो यह सुनिश्चित करती है कि विरासत और जेनेरिक संस्करण आमतौर पर समान श्रेणी की फाइलें उत्पन्न करते हैं, जो कि प्रकारों के बारे में कुछ सहायक जानकारी को छोड़कर। बाइनरी संगतता नहीं टूटी है क्योंकि किसी भी क्लाइंट कोड को बदलने या पुन: स्थापित किए बिना एक जेनेरिक क्लास फ़ाइल के साथ एक विरासत वर्ग फ़ाइल को बदलना संभव है।
गैर-जेनेरिक विरासत कोड के साथ इंटरफेस की सुविधा के लिए, एक प्रकार के रूप में एक मानकीकृत प्रकार के मिटाने का उपयोग करना संभव है। इस तरह के एक प्रकार को एक कच्चा प्रकार कहा जाता है ( जावा भाषा विनिर्देश 3 / 4.8 )। कच्चे प्रकार की अनुमति देने से स्रोत कोड के लिए पिछड़ी संगतता भी सुनिश्चित होती है।
इसके अनुसार,
java.util.Iterator
कक्षा के निम्नलिखित संस्करण द्विआधारी और स्रोत कोड पिछड़े संगत दोनों हैं:
Class java.util.Iterator as it is defined in Java SE version 1.4:
public interface Iterator {
boolean hasNext();
Object next();
void remove();
}
Class java.util.Iterator as it is defined in Java SE version 5.0:
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
पहले से ही पूरक जॉन स्कीट उत्तर को लागू करना ...
यह उल्लेख किया गया है कि उन्मूलन के माध्यम से जेनेरिक को लागू करना कुछ कष्टप्रद सीमाओं (जैसे नहीं new T[42]
) की ओर जाता है । यह भी उल्लेख किया गया है कि चीजों को इस तरह से करने का प्राथमिक कारण बाईटकोड में पश्चगामी संगतता थी। यह (ज्यादातर) सच भी है। बाइटकोड जेनरेट किया गया -target 1.5 सिर्फ डी-सुगरेड कास्टिंग -target 1.4 से कुछ अलग है। तकनीकी रूप से, यह रनटाइम में सामान्य प्रकार के इंस्टेंटिएशन तक पहुंच प्राप्त करने के लिए (अपार प्रवंचना के माध्यम से) संभव है, यह साबित करता है कि वास्तव में बाईटकोड में कुछ है।
अधिक दिलचस्प बिंदु (जो उठाया नहीं गया है) यह है कि उन्मूलन का उपयोग करके जेनरिक को लागू करना उच्च-स्तरीय प्रकार प्रणाली को पूरा करने में काफी अधिक लचीलापन प्रदान करता है। इसका एक अच्छा उदाहरण स्काला का जेवीएम कार्यान्वयन बनाम सीएलआर होगा। जेवीएम पर, इस तथ्य के कारण सीधे उच्च-प्रकार को लागू करना संभव है कि जेवीएम स्वयं जेनेरिक प्रकारों पर कोई प्रतिबंध नहीं लगाता है (क्योंकि ये "प्रकार" प्रभावी रूप से अनुपस्थित हैं)। यह सीएलआर के साथ विरोधाभास है, जिसमें पैरामीटर इंस्टेंटिएशन का रनटाइम ज्ञान है। इस वजह से, सीएलआर को स्वयं इस बात की कुछ अवधारणा होनी चाहिए कि कैसे जेनरिक का उपयोग किया जाना चाहिए, सिस्टम को अप्रत्याशित नियमों के साथ विस्तारित करने के प्रयासों को शून्य कर सकता है। परिणामस्वरूप, सीएलआर पर स्काला के उच्च-प्रकार को कंपाइलर के भीतर उत्सर्जित मिटाए जाने के एक अजीब रूप का उपयोग करके लागू किया जाता है,
जब आप रनटाइम में शरारती चीजें करना चाहते हैं, तो इरेज़र असुविधाजनक हो सकता है, लेकिन यह कंपाइलर लेखकों को सबसे अधिक लचीलापन प्रदान करता है। मैं अंदाजा लगा रहा हूं कि यह जल्द ही दूर क्यों नहीं हो रहा है।
जैसा कि मैं इसे समझता हूं (एक .NET पुरुष होने के नाते ) जेवीएम में जेनरिक की कोई अवधारणा नहीं है, इसलिए कंपाइलर ऑब्जेक्ट के साथ प्रकार के मापदंडों को बदल देता है और आपके लिए सभी कास्टिंग करता है।
इसका मतलब यह है कि जावा जेनरिक सिंटैक्स शुगर के अलावा कुछ भी नहीं है और मूल्य प्रकारों के लिए किसी भी प्रदर्शन में सुधार की पेशकश नहीं करते हैं जो संदर्भ के दौरान पास होने पर बॉक्सिंग / अनबॉक्सिंग की आवश्यकता होती है।
अच्छी व्याख्याएँ हैं। मैं केवल यह दिखाने के लिए एक उदाहरण जोड़ता हूं कि प्रकार का क्षय एक डीकॉम्पेलर के साथ कैसे काम करता है।
मूल वर्ग,
import java.util.ArrayList;
import java.util.List;
public class S<T> {
T obj;
S(T o) {
obj = o;
}
T getob() {
return obj;
}
public static void main(String args[]) {
List<String> list = new ArrayList<>();
list.add("Hello");
// for-each
for(String s : list) {
String temp = s;
System.out.println(temp);
}
// stream
list.forEach(System.out::println);
}
}
अपने बायोटेक से डिकम्पोज्ड कोड,
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.Consumer;
public class S {
Object obj;
S(Object var1) {
this.obj = var1;
}
Object getob() {
return this.obj;
}
public static void main(String[] var0) {
ArrayList var1 = new ArrayList();
var1.add("Hello");
// for-each
Iterator iterator = var1.iterator();
while (iterator.hasNext()) {
String string;
String string2 = string = (String)iterator.next();
System.out.println(string2);
}
// stream
PrintStream printStream = System.out;
Objects.requireNonNull(printStream);
var1.forEach(printStream::println);
}
}
जेनेरिक का उपयोग क्यों करें
संक्षेप में, जेनेरिक प्रकार (वर्गों और इंटरफेस) को सक्षम करते हैं जब वर्गों, इंटरफेस और विधियों को परिभाषित करते समय पैरामीटर होते हैं। विधि घोषणाओं में उपयोग किए जाने वाले अधिक परिचित औपचारिक मापदंडों की तरह, प्रकार के पैरामीटर आपके लिए अलग-अलग इनपुट के साथ एक ही कोड को फिर से उपयोग करने का एक तरीका प्रदान करते हैं। अंतर यह है कि औपचारिक मापदंडों के लिए इनपुट मूल्य हैं, जबकि टाइप पैरामीटर के लिए इनपुट प्रकार हैं। गैर-जेनेरिक कोड पर जेनरिक का उपयोग करने वाले ode के कई लाभ हैं:
टाइप एरासुरे क्या है
संकलन समय पर तंग प्रकार की जांच प्रदान करने और सामान्य प्रोग्रामिंग का समर्थन करने के लिए जावा भाषा में जेनरिक की शुरुआत की गई। जेनरिक को लागू करने के लिए, जावा कंपाइलर निम्न प्रकार पर लागू होता है:
[एनबी] पुल की विधि क्या है? शीघ्र ही, जैसे कि एक पैरामीटर इंटरफ़ेस के मामले में Comparable<T>
, यह अतिरिक्त तरीकों को संकलक द्वारा डाला जा सकता है; इन अतिरिक्त तरीकों को पुल कहा जाता है।
कैसे काम करता है Erasure
एक प्रकार की इरेज़र को इस प्रकार परिभाषित किया गया है: सभी प्रकार के मापदंडों को पैरामीटर प्रकार से ड्रॉप करें, और किसी भी प्रकार के वेरिएबल को उसकी बाध्यता के उन्मूलन के साथ बदलें, या ऑब्जेक्ट के साथ यदि इसकी कोई बाध्यता नहीं है, या सबसे बाईं ओर के इरेज़र के साथ यदि यह है तो कई सीमाएँ। यहाँ कुछ उदाहरण हैं:
List<Integer>
, List<String>
और List<List<String>>
है List
।List<Integer>[]
है List[]
।List
स्वयं है, इसी तरह किसी भी कच्चे प्रकार के लिए।Integer
, इसी तरह बिना किसी प्रकार के मापदंडों के लिए होता है।T
परिभाषा में मिटना asList
है Object
, क्योंकि T
कोई बाध्य नहीं है।T
परिभाषा में मिटना max
है Comparable
, क्योंकि T
बाध्य है Comparable<? super T>
।T
के अंतिम परिभाषा max
है Object
, क्योंकि
T
बाध्य है Object
और Comparable<T>
और हम सबसे बाईं ओर के लिए बाध्य का विलोपन ले।जेनरिक का उपयोग करते समय सावधानी बरतने की जरूरत है
जावा में, दो अलग-अलग विधियों में समान हस्ताक्षर नहीं हो सकते हैं। चूंकि जेनेरिक को इरेज़र द्वारा लागू किया जाता है, इसलिए यह भी कहा जाता है कि दो अलग-अलग तरीकों में एक ही इरेज़र के साथ हस्ताक्षर नहीं हो सकते हैं। एक वर्ग दो तरीकों को अधिभारित नहीं कर सकता है जिनके हस्ताक्षर समान मिटाए गए हैं, और एक वर्ग दो इंटरफेस को लागू नहीं कर सकता है जिनके पास एक ही मिटा है।
class Overloaded2 {
// compile-time error, cannot overload two methods with same erasure
public static boolean allZero(List<Integer> ints) {
for (int i : ints) if (i != 0) return false;
return true;
}
public static boolean allZero(List<String> strings) {
for (String s : strings) if (s.length() != 0) return false;
return true;
}
}
हम इस कोड को इस प्रकार काम करना चाहते हैं:
assert allZero(Arrays.asList(0,0,0));
assert allZero(Arrays.asList("","",""));
हालाँकि, इस मामले में दोनों विधियों के हस्ताक्षर एक समान हैं:
boolean allZero(List)
इसलिए, संकलन समय पर एक नाम क्लैश की सूचना है। दोनों विधियों को एक ही नाम देना संभव नहीं है और ओवरलोडिंग द्वारा उनके बीच अंतर करने की कोशिश करें, क्योंकि उन्मूलन के बाद एक विधि कॉल को दूसरे से अलग करना असंभव है।
उम्मीद है, पाठक मज़ा आएगा :)