मैंने सोचा था कि मैं इस सॉफ्टबॉल की पेशकश करूँगा जिसे पार्क के बाहर मारना चाहूंगा। जेनरिक क्या हैं, जेनेरिक के क्या फायदे हैं, क्यों, कहाँ, मुझे उनका उपयोग कैसे करना चाहिए? कृपया इसे काफी बुनियादी रखें। धन्यवाद।
मैंने सोचा था कि मैं इस सॉफ्टबॉल की पेशकश करूँगा जिसे पार्क के बाहर मारना चाहूंगा। जेनरिक क्या हैं, जेनेरिक के क्या फायदे हैं, क्यों, कहाँ, मुझे उनका उपयोग कैसे करना चाहिए? कृपया इसे काफी बुनियादी रखें। धन्यवाद।
जवाबों:
मुझे वास्तव में खुद को दोहराने से नफरत है। मुझे एक ही चीज टाइप करने से ज्यादा नफरत है जितना मुझे करना है। मैं मामूली अंतर के साथ कई बार चीजों को आराम करना पसंद नहीं करता।
बनाने के बजाय:
class MyObjectList {
MyObject get(int index) {...}
}
class MyOtherObjectList {
MyOtherObject get(int index) {...}
}
class AnotherObjectList {
AnotherObject get(int index) {...}
}
मैं एक पुन: प्रयोज्य वर्ग का निर्माण कर सकता हूं ... (उस मामले में जहां आप किसी कारण से कच्चे संग्रह का उपयोग नहीं करना चाहते हैं)
class MyList<T> {
T get(int index) { ... }
}
अब मैं 3x अधिक कुशल हूं और मुझे केवल एक प्रति को बनाए रखना है। आप कम कोड क्यों बनाए रखना चाहते हैं?
यह गैर-संग्रह कक्षाओं जैसे कि एक Callable<T>
या Reference<T>
अन्य वर्गों के साथ बातचीत करने के लिए भी सही है । क्या तुम सच में विस्तार करने के लिए करना चाहते हैं Callable<T>
और Future<T>
और हर अन्य संबद्ध वर्ग प्रकार सुरक्षित संस्करण बनाने के लिए?
मैं नही।
टाइपकास्ट करने की आवश्यकता नहीं है जावा जेनेरिक का सबसे बड़ा लाभ है , क्योंकि यह संकलन-समय पर प्रकार की जाँच करेगा। यह ClassCastException
s की संभावना को कम करेगा जिसे रनटाइम पर फेंका जा सकता है, और अधिक मजबूत कोड हो सकता है।
लेकिन मुझे संदेह है कि आप इससे पूरी तरह परिचित हैं।
हर बार जब मैं जेनरिक को देखता हूं तो यह मुझे सिरदर्द देता है। मुझे लगता है कि जावा का सबसे अच्छा हिस्सा यह सादगी और न्यूनतम सिंटैक्स है और जेनेरिक सरल नहीं हैं और नए सिंटैक्स की एक महत्वपूर्ण राशि जोड़ते हैं।
पहले तो, मुझे या तो जेनरिक का लाभ दिखाई नहीं दिया। मैंने 1.4 सिंटैक्स से जावा सीखना शुरू किया (भले ही जावा 5 उस समय बाहर था) और जब मैंने जेनरिक का सामना किया, तो मुझे लगा कि यह लिखने के लिए अधिक कोड था, और मुझे वास्तव में लाभ समझ में नहीं आया।
मॉडर्न IDE जेनेरिक से राइटिंग कोड को आसान बनाते हैं।
अधिकांश आधुनिक, सभ्य IDE सामान्य रूप से कोड पूरा करने के साथ, जेनरिक के साथ कोड लिखने में सहायता करने के लिए पर्याप्त स्मार्ट हैं।
यहाँ एक के Map<String, Integer>
साथ बनाने का एक उदाहरण है HashMap
। कोड मुझे टाइप करना होगा:
Map<String, Integer> m = new HashMap<String, Integer>();
और वास्तव में, यह सिर्फ एक नया बनाने के लिए टाइप करने के लिए बहुत कुछ है HashMap
। हालांकि, वास्तव में, मुझे केवल इतना ही लिखना था कि ग्रहण से पहले मुझे पता था कि मुझे क्या चाहिए:
Map<String, Integer> m = new Ha
Ctrl+Space
सच है, मुझे HashMap
उम्मीदवारों की एक सूची से चयन करने की आवश्यकता थी , लेकिन मूल रूप से आईडीई को पता था कि क्या जोड़ना है, जिसमें सामान्य प्रकार शामिल हैं। सही उपकरण के साथ, जेनेरिक का उपयोग करना बहुत बुरा नहीं है।
इसके अलावा, चूंकि प्रकार ज्ञात हैं, जेनेरिक संग्रह से तत्वों को पुनः प्राप्त करते समय, IDE कार्य करेगा जैसे कि वह वस्तु पहले से ही अपने घोषित प्रकार की वस्तु है - यह जानने के लिए IDE के लिए कास्टिंग करने की कोई आवश्यकता नहीं है कि वस्तु का प्रकार क्या है है।
जेनरिक का एक प्रमुख लाभ यह है कि यह नए जावा 5 सुविधाओं के साथ अच्छा खेलता है। यहाँ एक पूर्णांक को वापस लेने Set
और उसके कुल की गणना करने का एक उदाहरण है :
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);
int total = 0;
for (int i : set) {
total += i;
}
उस कोड ऑफ कोड में, तीन नए जावा 5 फीचर मौजूद हैं:
सबसे पहले, प्राइमेटिक्स की जेनरिक और ऑटोबॉक्सिंग निम्नलिखित लाइनों की अनुमति देती हैं:
set.add(10);
set.add(42);
पूर्णांक 10
को Integer
मान के साथ ऑटोबॉक्स किया गया है 10
। (और उसी के लिए 42
) फिर उस Integer
में टॉस किया Set
जाता है, जिसे Integer
s धारण करना ज्ञात है में फेंकने की कोशिश करने से String
संकलन में त्रुटि होगी।
अगला, प्रत्येक लूप के लिए उन तीनों को लेता है:
for (int i : set) {
total += i;
}
सबसे पहले, Set
युक्त Integer
s का उपयोग प्रत्येक लूप में किया जाता है। प्रत्येक तत्व को एक घोषित किया जाता है int
और इसे अनुमति दी जाती है क्योंकि Integer
इसे वापस आदिम में अनबॉक्स किया जाता है int
। और यह तथ्य यह है कि यह अनबॉक्सिंग ज्ञात है क्योंकि जेनेरिक का उपयोग यह निर्दिष्ट करने के लिए किया गया था कि इसमें Integer
आयोजित किए गए थे Set
।
जेनरिक वह गोंद हो सकता है जो जावा 5 में शुरू की गई नई विशेषताओं को एक साथ लाता है, और यह सिर्फ कोडिंग को सरल और सुरक्षित बनाता है। और ज्यादातर समय IDE अच्छे सुझावों के साथ आपकी मदद करने के लिए पर्याप्त स्मार्ट होते हैं, इसलिए आमतौर पर, यह बहुत अधिक टाइपिंग नहीं करेगा।
और स्पष्ट रूप से, जैसा कि Set
उदाहरण से देखा जा सकता है , मुझे लगता है कि जावा 5 सुविधाओं का उपयोग कोड को अधिक संक्षिप्त और मजबूत बना सकता है।
संपादित करें - जेनरिक के बिना एक उदाहरण
निम्नलिखित Set
जेनेरिक के उपयोग के बिना उपरोक्त उदाहरण का एक चित्रण है । यह संभव है, लेकिन बिल्कुल सुखद नहीं है:
Set set = new HashSet();
set.add(10);
set.add(42);
int total = 0;
for (Object o : set) {
total += (Integer)o;
}
(नोट: उपरोक्त कोड संकलन-समय पर अनियंत्रित रूपांतरण चेतावनी उत्पन्न करेगा।)
गैर-जेनरिक संग्रह का उपयोग करते समय, संग्रह में दर्ज किए जाने वाले प्रकार प्रकार की वस्तुएं हैं Object
। इसलिए, इस उदाहरण में, एक Object
वह है जिसे add
सेट में एड किया जा रहा है ।
set.add(10);
set.add(42);
उपरोक्त पंक्तियों में, ऑटोबॉक्सिंग खेल में है - आदिम int
मूल्य 10
और वस्तुओं 42
में ऑटोबॉक्सिंग किया जा रहा है Integer
, जिन्हें इसमें जोड़ा जा रहा है Set
। हालांकि, ध्यान रखें, Integer
ऑब्जेक्ट्स को Object
एस के रूप में संभाला जा रहा है , क्योंकि कंपाइलर को यह जानने में मदद करने के लिए कोई प्रकार की जानकारी नहीं है कि किस प्रकार की Set
अपेक्षा की जानी चाहिए।
for (Object o : set) {
यह वह हिस्सा है जो महत्वपूर्ण है। प्रत्येक लूप के काम करने का कारण यह Set
है कि Iterable
इंटरफ़ेस लागू होता है, जो Iterator
यदि मौजूद है, तो एक प्रकार की जानकारी के साथ लौटता है । ( Iterator<T>
, यह है)
हालाँकि, चूंकि कोई प्रकार की जानकारी नहीं है, इसलिए Set
वह लौटाएगा Iterator
जो मानों Set
को Object
एस के रूप में लौटाएगा , और इसीलिए प्रत्येक लूप में प्राप्त किया जा रहा तत्व प्रकार का होना चाहिए Object
।
अब जब Object
से पुनर्प्राप्त किया गया है Set
, तो Integer
इसके अलावा प्रदर्शन करने के लिए इसे मैन्युअल रूप से डाला जाना चाहिए :
total += (Integer)o;
यहां, ए से टाइपकास्ट किया Object
जाता है Integer
। इस मामले में, हम जानते हैं कि यह हमेशा काम करेगा, लेकिन मैनुअल टाइपकास्टिंग हमेशा मुझे लगता है कि यह एक नाजुक कोड है, जो एक छोटे से बदलाव के कारण क्षतिग्रस्त हो सकता है, जहां। (मुझे लगता है कि हर टाइपकास्ट ClassCastException
होने की प्रतीक्षा है, लेकिन मैं पचाता हूं ...)
Integer
अब एक में unboxed है int
और में इसके प्रदर्शन करने के लिए अनुमति दी int
चर total
।
मुझे उम्मीद है कि मैं यह बता सकता हूं कि जावा 5 की नई सुविधाओं का उपयोग गैर-जेनेरिक कोड के साथ संभव है, लेकिन यह जेनेरिक के साथ कोड लिखने के रूप में साफ और सीधे-आगे नहीं है। और, मेरी राय में, जावा 5 में नई सुविधाओं का पूरा लाभ उठाने के लिए, किसी को जेनरिक में देखना चाहिए, यदि बहुत कम से कम, समय-समय पर चेक टाइप करने के लिए अमान्य टाइपकास्ट को रोकने के लिए संकलन समय की जाँच करने की अनुमति देता है।
आप जावा बग डेटाबेस खोज करने के लिए बस से पहले 1.5 जारी किया गया था थे, तो आप के साथ सात गुना अधिक कीड़े पाते हैं NullPointerException
की तुलना में ClassCastException
। इसलिए ऐसा नहीं लगता है कि बग को खोजने के लिए, या कम से कम ऐसे कीड़े हैं जो थोड़े से धुएं के परीक्षण के बाद बने रहते हैं।
मेरे लिए जेनेरिक का बड़ा फायदा यह है कि वे महत्वपूर्ण महत्वपूर्ण जानकारी कोड में दस्तावेज करते हैं। अगर मैं उस प्रकार की जानकारी को कोड में दर्ज़ नहीं करना चाहता था, तो मैं एक डायनामिक रूप से टाइप की गई भाषा का उपयोग करूँगा, या कम से कम अधिक अंतर्निहित प्रकार के अनुमान वाली भाषा का उपयोग करूँगा।
किसी वस्तु के संग्रह को अपने आप में रखना एक बुरी शैली नहीं है (लेकिन तब आम शैली प्रभावी रूप से इनकैप्सुलेशन को अनदेखा करना है)। यह बल्कि इस बात पर निर्भर करता है कि आप क्या कर रहे हैं। "एल्गोरिदम" के लिए संग्रह पास करना जेनरिक के साथ (संकलन के समय या पहले) जांचना थोड़ा आसान है।
जावा में जनक पारमार्थिक बहुरूपता की सुविधा देते हैं । प्रकार के मापदंडों के अनुसार, आप प्रकारों के लिए तर्क पास कर सकते हैं। जिस तरह एक String foo(String s)
मॉडल कुछ व्यवहार की तरह है, न केवल किसी विशेष स्ट्रिंग के लिए, बल्कि किसी भी स्ट्रिंग के लिए s
, इसलिए एक प्रकार जैसे List<T>
मॉडल कुछ व्यवहार, न केवल एक विशिष्ट प्रकार के लिए, बल्कि किसी भी प्रकार के लिए । List<T>
का कहना है कि किसी भी प्रकार के लिए T
, वहाँ का एक प्रकार है List
जिसका तत्व हैं T
रों । तो List
वास्तव में एक प्रकार का निर्माता है । यह एक तर्क के रूप में एक प्रकार लेता है और परिणामस्वरूप एक अन्य प्रकार का निर्माण करता है।
यहां कुछ सामान्य प्रकार के उदाहरण दिए गए हैं जिनका मैं हर दिन उपयोग करता हूं। सबसे पहले, एक बहुत ही उपयोगी सामान्य इंटरफ़ेस:
public interface F<A, B> {
public B f(A a);
}
यह इंटरफ़ेस कहता है कि कुछ दो प्रकारों के लिए, A
और B
, एक फ़ंक्शन (जिसे कहा जाता है f
) एक लेता है A
और एक रिटर्न देता है B
। जब आप इस इंटरफ़ेस को लागू करते हैं, A
और B
किसी भी प्रकार का आप चाहते हैं, जब तक आप एक फ़ंक्शन प्रदान करते हैं f
जो पूर्व लेता है और बाद में वापस करता है। यहाँ इंटरफ़ेस का एक उदाहरण कार्यान्वयन है:
F<Integer, String> intToString = new F<Integer, String>() {
public String f(int i) {
return String.valueOf(i);
}
}
जेनरिक से पहले, बहुरूपता के द्वारा प्राप्त किया गया था उपवर्गीकरण का उपयोग कर extends
कीवर्ड। जेनरिक के साथ, हम वास्तव में उपवर्ग के साथ दूर कर सकते हैं और इसके बजाय पैरामीट्रिक बहुरूपता का उपयोग कर सकते हैं। उदाहरण के लिए, किसी भी प्रकार के हैश कोड की गणना के लिए उपयोग किए जाने वाले एक पैरामीटर (जेनेरिक) वर्ग पर विचार करें। Object.hashCode () को ओवरराइड करने के बजाय, हम इस तरह एक जेनेरिक क्लास का उपयोग करेंगे:
public final class Hash<A> {
private final F<A, Integer> hashFunction;
public Hash(final F<A, Integer> f) {
this.hashFunction = f;
}
public int hash(A a) {
return hashFunction.f(a);
}
}
यह वंशानुक्रम का उपयोग करने की तुलना में बहुत अधिक लचीला है, क्योंकि हम भंगुर पदानुक्रमों को बंद किए बिना रचना और पैरामीट्रिक बहुरूपता का उपयोग करने के विषय के साथ रह सकते हैं।
जावा के जेनेरिक हालांकि सही नहीं हैं। आप प्रकारों पर अमूर्त कर सकते हैं, लेकिन आप उदाहरण के लिए, प्रकार के रचनाकारों पर अमूर्त नहीं कर सकते। यही है, आप "किसी भी प्रकार के टी" के लिए कह सकते हैं, लेकिन आप "किसी भी प्रकार के टी के लिए नहीं कह सकते हैं जो एक प्रकार का पैरामीटर ए" लेता है।
मैंने जावा जेनरिक की इन सीमाओं के बारे में एक लेख लिखा था, यहाँ।
जेनरिक के साथ एक बड़ी जीत यह है कि वे आपको उपवर्ग से बचने देते हैं। सबक्लासिंग में भंगुर वर्ग पदानुक्रमों का परिणाम होता है जो विस्तार के लिए अजीब होते हैं, और ऐसी कक्षाएं जिन्हें पूरे पदानुक्रम को देखे बिना व्यक्तिगत रूप से समझना मुश्किल है।
जेनरिक से पहले Wereas आप की तरह कक्षाएं हो सकता है Widget
के लिए बढ़ा दी FooWidget
, BarWidget
और BazWidget
, जेनरिक के साथ आप एक ही सामान्य वर्ग हो सकता है Widget<A>
कि एक लेता है Foo
, Bar
या Baz
इसके निर्माता आप देने के लिए Widget<Foo>
, Widget<Bar>
और Widget<Baz>
।
जेनरिक बॉक्सिंग और अनबॉक्सिंग के प्रदर्शन हिट से बचते हैं। मूल रूप से, ArrayList vs List <T> को देखें। दोनों एक ही मूल बातें करते हैं, लेकिन सूची <T> बहुत तेज़ होगी क्योंकि आपको ऑब्जेक्ट से बॉक्स / टू नहीं करना है।
Generics के लिए सबसे अच्छा लाभ कोड का पुन: उपयोग है। आओ हम कहते हैं कि आपके पास बहुत सी व्यावसायिक वस्तुएं हैं, और आप प्रत्येक इकाई के लिए समान कार्य करने के लिए बहुत समान कोड लिखने जा रहे हैं। (IE लाइनक्यू टू SQL ऑपरेशंस)।
जेनरिक के साथ, आप एक वर्ग बना सकते हैं, जो दिए गए आधार वर्ग से विरासत में दिए गए किसी भी प्रकार को संचालित करने में सक्षम होगा या किसी दिए गए इंटरफ़ेस को लागू कर सकता है जैसे:
public interface IEntity
{
}
public class Employee : IEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
}
public class Company : IEntity
{
public string Name { get; set; }
public string TaxID { get; set }
}
public class DataService<ENTITY, DATACONTEXT>
where ENTITY : class, IEntity, new()
where DATACONTEXT : DataContext, new()
{
public void Create(List<ENTITY> entities)
{
using (DATACONTEXT db = new DATACONTEXT())
{
Table<ENTITY> table = db.GetTable<ENTITY>();
foreach (ENTITY entity in entities)
table.InsertOnSubmit (entity);
db.SubmitChanges();
}
}
}
public class MyTest
{
public void DoSomething()
{
var dataService = new DataService<Employee, MyDataContext>();
dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
var otherDataService = new DataService<Company, MyDataContext>();
otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });
}
}
ऊपर दिए गए DoSomething विधि में विभिन्न प्रकारों को दिए गए एक ही सेवा के पुन: उपयोग पर ध्यान दें। सचमुच सुरुचिपूर्ण!
आपके काम के लिए जेनरिक का उपयोग करने के कई अन्य महान कारण हैं, यह मेरा पसंदीदा है।
मैं उन्हें पसंद करता हूं क्योंकि वे आपको एक कस्टम प्रकार को परिभाषित करने का एक त्वरित तरीका देते हैं (जैसा कि मैं वैसे भी उनका उपयोग करता हूं)।
उदाहरण के लिए, एक स्ट्रिंग और पूर्णांक से मिलकर एक संरचना को परिभाषित करने के बजाय, और फिर उन संरचनाओं की एक सरणी का उपयोग करने के तरीके पर वस्तुओं और विधियों के एक पूरे सेट को लागू करने के लिए, आप बस एक शब्दकोश बना सकते हैं
Dictionary<int, string> dictionary = new Dictionary<int, string>();
और कम्पाइलर / आईडीई बाकी भारी उठाने का काम करता है। एक शब्दकोश विशेष रूप से आपको कुंजी के रूप में पहले प्रकार का उपयोग करने देता है (कोई दोहराए गए मान नहीं)।
टाइप किए गए संग्रह - भले ही आप उनका उपयोग नहीं करना चाहते हों, लेकिन आपको उनके साथ अन्य पुस्तकालयों, अन्य स्रोतों से निपटने की संभावना है।
कक्षा निर्माण में सामान्य टाइपिंग:
सार्वजनिक वर्ग Foo <T> {सार्वजनिक T get () ...
कास्टिंग से परहेज - मुझे हमेशा से नापसंद चीजें पसंद हैं
नए तुलनित्र {सार्वजनिक int तुलना (ऑब्जेक्ट ओ) {अगर (ओ इंस्टोफ़ क्लासइलेक्ट्रॉनिक के बारे में) ...
जहाँ आप अनिवार्य रूप से ऐसी स्थिति की जाँच कर रहे हैं जो केवल मौजूद होनी चाहिए क्योंकि इंटरफ़ेस वस्तुओं के संदर्भ में व्यक्त किया गया है।
जेनेरिक के लिए मेरी प्रारंभिक प्रतिक्रिया आपके समान थी - "बहुत गन्दा, बहुत जटिल"। मेरा अनुभव यह है कि बिट के लिए उपयोग करने के बाद आप उनके लिए अभ्यस्त हो जाते हैं, और उनके बिना कोड स्पष्ट रूप से निर्दिष्ट कम महसूस होता है, और बस कम आरामदायक होता है। उस के अलावा, जावा दुनिया के बाकी उन्हें उपयोग करता है ताकि आप कार्यक्रम के साथ अंत में, सही पाने के लिए जा रहे हैं?
एक अच्छा उदाहरण देने के लिए। कल्पना कीजिए कि आपके पास फू नामक एक वर्ग है
public class Foo
{
public string Bar() { return "Bar"; }
}
उदाहरण 1 अब आप फू वस्तुओं का संग्रह करना चाहते हैं। आपके पास दो विकल्प हैं, LIst या ArrayList, ये दोनों समान तरीके से काम करते हैं।
Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();
//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());
उपरोक्त कोड में, अगर मैं फू के बजाय फायरट्रेक के एक वर्ग को जोड़ने की कोशिश करता हूं, तो ArrayList इसे जोड़ देगा, लेकिन फू की जेनरिक सूची एक अपवाद को फेंक देगी।
उदाहरण दो।
अब आपके पास अपनी दो सरणी सूची है और आप प्रत्येक पर बार () फ़ंक्शन को कॉल करना चाहते हैं। चूंकि hte ArrayList ऑब्जेक्ट्स से भरा है, इसलिए आपको बार कॉल करने से पहले उन्हें कास्ट करना होगा। लेकिन चूंकि फू की जेनेरिक सूची में केवल फोस हो सकते हैं, आप बार () को सीधे उन पर कॉल कर सकते हैं।
foreach(object o in al)
{
Foo f = (Foo)o;
f.Bar();
}
foreach(Foo f in fl)
{
f.Bar();
}
क्या आपने कभी एक विधि (या एक वर्ग) नहीं लिखी है जहाँ विधि / वर्ग की प्रमुख अवधारणा विशिष्ट डेटा प्रकार के मापदंडों / उदाहरण चर (थिंक लिंक्ड सूची, अधिकतम / मिनट फ़ंक्शंस, बाइनरी खोज) से कसकर बाध्य नहीं थी , आदि।)।
क्या आप कभी नहीं चाहते हैं कि आप कट-एन-पेस्ट पुन: उपयोग करने या मजबूत-टाइपिंग से समझौता किए बिना अल्गर्थ / कोड का पुन: उपयोग कर सकें (उदाहरण के लिए मैं List
स्ट्रिंग्स चाहता हूं , List
चीजों की नहीं जो मुझे आशा है कि तार हैं!)।
इसलिए आपको जेनेरिक (या कुछ बेहतर) का उपयोग करना चाहिए ।
यह मत भूलो कि जेनरिक केवल कक्षाओं द्वारा उपयोग नहीं किए जाते हैं, उनका उपयोग विधियों द्वारा भी किया जा सकता है। उदाहरण के लिए, निम्नलिखित स्निपेट लें:
private <T extends Throwable> T logAndReturn(T t) {
logThrowable(t); // some logging method that takes a Throwable
return t;
}
यह सरल है, लेकिन बहुत सुरुचिपूर्ण ढंग से उपयोग किया जा सकता है। अच्छी बात यह है कि यह विधि जो कुछ भी थी उसे वापस लौटा देती है। यह तब मदद करता है जब आप ऐसे अपवादों को संभाल रहे होते हैं जिन्हें कॉल करने वाले को वापस फेंकने की आवश्यकता होती है:
...
} catch (MyException e) {
throw logAndReturn(e);
}
मुद्दा यह है कि आप एक विधि से गुजरने के द्वारा प्रकार को नहीं खोते हैं। आप केवल एक के बजाय सही प्रकार के अपवाद को फेंक सकते हैं Throwable
, जो कि आप सभी जेनरिक के बिना कर सकते हैं।
यह सामान्य तरीकों के लिए एक उपयोग का सिर्फ एक सरल उदाहरण है। जेनेरिक विधियों के साथ कुछ अन्य साफ-सुथरी चीजें हैं। मेरे विचार में सबसे अच्छे, जेनेरिक के साथ जिक्र है। निम्नलिखित उदाहरण लें (जोश बलोच के प्रभावी जावा 2 संस्करण से लिया गया):
...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
return new HashMap<K, V>();
}
यह बहुत कुछ नहीं करता है, लेकिन सामान्य प्रकार लंबे (या नेस्टेड; यानी Map<String, List<String>>
) होने पर यह कुछ अव्यवस्थाओं में कटौती करता है ।
Throwable
निकाय को किसी विधि निकाय के भीतर से नहीं फेंक सकते हैं जिसमें विशिष्ट घोषित अपवाद हैं। विकल्प या तो प्रत्येक अपवाद प्रकार को वापस करने के लिए अलग-अलग तरीके लिख रहा है, या कलाकारों को गैर-जेनेरिक विधि के साथ कर रहा है जो एक रिटर्न देता है Throwable
। पूर्व बहुत क्रियात्मक है और काफी बेकार है और बाद वाले को संकलक से कोई मदद नहीं मिलेगी। जेनरिक का उपयोग करके, कंपाइलर आपके लिए सही कास्ट डालेगा। तो, सवाल यह है: क्या यह जटिलता के लायक है?
मिकेल के रूप में प्राथमिक लाभ, कई वर्गों को परिभाषित करने की आवश्यकता के बिना मजबूत-टाइपिंग है।
इस तरह आप कर सकते हैं सामान:
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
जेनरिक के बिना, आपको अपने कार्यों तक पहुंचने के लिए ब्लाह [0] को सही प्रकार से डालना होगा।
jvm वैसे भी डालती है ... यह स्पष्ट रूप से कोड बनाता है जो सामान्य प्रकार को "ऑब्जेक्ट" के रूप में मानता है और वांछित तात्कालिकता के लिए जाति बनाता है। जावा जेनरिक सिंटैक्टिक शुगर है।
मुझे पता है कि यह एक सी # प्रश्न है, लेकिन अन्य भाषाओं में भी जेनेरिक का उपयोग किया जाता है, और उनके उपयोग / लक्ष्य काफी समान हैं।
जावा संग्रह जावा 1.5 के बाद से जेनरिक का उपयोग करता है । इसलिए, उनका उपयोग करने के लिए एक अच्छी जगह तब है जब आप अपना संग्रह जैसी वस्तु बना रहे हों।
एक उदाहरण जो मुझे लगभग हर जगह दिखाई देता है वह एक जोड़ी वर्ग है, जो दो वस्तुओं को रखता है, लेकिन उन वस्तुओं के साथ एक सामान्य तरीके से निपटने की जरूरत है।
class Pair<F, S> {
public final F first;
public final S second;
public Pair(F f, S s)
{
first = f;
second = s;
}
}
जब भी आप इस जोड़ी वर्ग का उपयोग करते हैं, तो आप निर्दिष्ट कर सकते हैं कि आप किस प्रकार की वस्तुओं से निपटना चाहते हैं और रन टाइम के बजाय किसी भी प्रकार की कास्ट समस्याओं का संकलन समय पर दिखाई देगा।
जेनरिक के पास 'सुपर' और 'एक्सटेंडेड' कीवर्ड से परिभाषित सीमाएँ भी हो सकती हैं। उदाहरण के लिए, यदि आप एक सामान्य प्रकार से निपटना चाहते हैं, लेकिन आप यह सुनिश्चित करना चाहते हैं कि यह फू नामक एक वर्ग का विस्तार करता है (जिसमें एक सेटलाइट विधि है):
public class FooManager <F extends Foo>{
public void setTitle(F foo, String title) {
foo.setTitle(title);
}
}
हालांकि यह अपने आप में बहुत दिलचस्प नहीं है, यह जानना उपयोगी है कि जब भी आप एक FooManager के साथ व्यवहार करते हैं, तो आप जानते हैं कि यह MyClass प्रकार को संभाल लेगा, और MyClass Foo का विस्तार करता है।
सन जावा प्रलेखन से, "मुझे जेनेरिक का उपयोग क्यों करना चाहिए?" के जवाब में।
"जेनरिक आपके लिए संकलनकर्ता के संग्रह के प्रकार को संप्रेषित करने का एक तरीका प्रदान करता है, ताकि इसकी जाँच की जा सके। एक बार जब संकलनकर्ता को संग्रह के तत्व प्रकार का पता चल जाता है, तो संकलक जाँच सकता है कि आपने संग्रह का लगातार उपयोग किया है और सम्मिलित कर सकते हैं। संग्रह से निकाले जा रहे मूल्यों पर सही कास्ट ... जेनेरिक का उपयोग करने वाला कोड स्पष्ट और सुरक्षित है .... संकलक संकलन समय पर सत्यापित कर सकता है कि प्रकार की बाधाएं चलाने के समय [जोर मेरा] पर उल्लंघन नहीं किया गया है । कार्यक्रम चेतावनी के बिना संकलित करता है, हम निश्चितता के साथ कह सकते हैं कि यह क्लासकस्टेसेप्शन को रन टाइम पर नहीं फेंकेगा। विशेष रूप से बड़े कार्यक्रमों में जेनरिक का उपयोग करने का शुद्ध प्रभाव, पठनीयता और मजबूती में सुधार होता है । [जोर मेरा]
जेनरिक आपको उन वस्तुओं को बनाने की अनुमति देता है जो दृढ़ता से टाइप की जाती हैं, फिर भी आपको विशिष्ट प्रकार को परिभाषित करने की आवश्यकता नहीं है। मुझे लगता है कि सबसे अच्छा उपयोगी उदाहरण सूची और समान कक्षाएं हैं।
जेनेरिक सूची का उपयोग करके आप एक सूची सूची सूची जो आप चाहते हैं और आप हमेशा मजबूत टाइपिंग को संदर्भित कर सकते हैं, आपको कन्वर्ट करने की ज़रूरत नहीं है या कुछ भी नहीं जैसे आप एक ऐरे या मानक सूची के साथ करेंगे।
जेनरिक आपको वस्तुओं और डेटा संरचनाओं के लिए मजबूत टाइपिंग का उपयोग करने देता है जो किसी भी वस्तु को धारण करने में सक्षम होना चाहिए। जेनेरिक संरचनाओं (बॉक्सिंग / अनबॉक्सिंग) से वस्तुओं को पुनर्प्राप्त करते समय यह थकाऊ और महंगी टाइपकास्ट को भी समाप्त करता है।
एक उदाहरण जो दोनों का उपयोग करता है वह एक लिंक की गई सूची है। एक लिंक्ड लिस्ट क्लास कितना अच्छा होगा यदि वह केवल ऑब्जेक्ट फू का उपयोग कर सके? किसी भी प्रकार की वस्तु को संभालने वाली एक लिंक्ड सूची को लागू करने के लिए, यदि आप चाहते हैं कि सूची में केवल एक ही प्रकार की वस्तु हो, तो एक काल्पनिक श्रेणी के आंतरिक वर्ग में लिंक की गई सूची और नोड्स सामान्य होने चाहिए।
जेनरिक (विशेषकर कलेक्शंस / लिस्ट के साथ) का उपयोग करने का एक और फायदा आपको कंपाइल टाइम टाइप चेकिंग से मिलता है। वस्तुओं की सूची के बजाय सामान्य सूची का उपयोग करते समय यह वास्तव में उपयोगी है।
एकल सबसे अधिक कारण वे टाइप सुरक्षा प्रदान करते हैं
List<Customer> custCollection = new List<Customer>;
विरोध के रूप में,
object[] custCollection = new object[] { cust1, cust2 };
एक साधारण उदाहरण के रूप में।
सारांश में, जेनेरिक आपको अधिक प्राथमिक रूप से निर्दिष्ट करने की अनुमति देते हैं कि आप क्या करने का इरादा रखते हैं (मजबूत टाइपिंग)।
आपके लिए इसके कई लाभ हैं:
क्योंकि कंपाइलर को पता है कि आप क्या करना चाहते हैं, यह आपको कई प्रकार के कास्टिंग को छोड़ने की अनुमति देता है क्योंकि यह पहले से ही जानता है कि प्रकार संगत होगा।
इससे आपको अपने प्रोग्राम के सही होने के बारे में पहले की प्रतिक्रिया भी मिलती है। ऐसी चीजें जो पहले रनटाइम में विफल हो जाती थीं (जैसे कि कोई वस्तु वांछित प्रकार में डाली नहीं जा सकती थी), अब संकलन-समय पर विफल हो जाती है और आप अपने परीक्षण-विभाग द्वारा एक गुप्त बग रिपोर्ट दर्ज करने से पहले गलती को ठीक कर सकते हैं।
कंपाइलर अधिक अनुकूलन कर सकता है, जैसे मुक्केबाजी से बचना आदि।
जोड़ने / विस्तार करने के लिए चीजों की एक जोड़ी (देखने के .NET बिंदु से बोल):
जेनेरिक प्रकार आपको भूमिका-आधारित कक्षाएं और इंटरफेस बनाने की अनुमति देते हैं। यह पहले से ही अधिक बुनियादी शब्दों में कहा गया है, लेकिन मुझे लगता है कि आप अपना कोड उन कक्षाओं के साथ डिज़ाइन करना शुरू करते हैं जो एक प्रकार-अज्ञेय तरीके से कार्यान्वित किए जाते हैं - जिसके परिणामस्वरूप अत्यधिक पुन: प्रयोज्य कोड होता है।
विधियों पर सामान्य तर्क एक ही काम कर सकते हैं, लेकिन वे कास्टिंग करने के लिए "बताओ मत पूछो" सिद्धांत को लागू करने में भी मदद करते हैं, अर्थात "मुझे वह दें जो मैं चाहता हूं, और यदि आप नहीं कर सकते, तो आप मुझे बताएं"।
मैं उदाहरण के लिए उनका उपयोग स्प्रिंगडाइम और हाइबरनेट के साथ लागू एक जेनरिकडाओ में करता हूं जो इस तरह दिखता है
public abstract class GenericDaoHibernateImpl<T>
extends HibernateDaoSupport {
private Class<T> type;
public GenericDaoHibernateImpl(Class<T> clazz) {
type = clazz;
}
public void update(T object) {
getHibernateTemplate().update(object);
}
@SuppressWarnings("unchecked")
public Integer count() {
return ((Integer) getHibernateTemplate().execute(
new HibernateCallback() {
public Object doInHibernate(Session session) {
// Code in Hibernate for getting the count
}
}));
}
.
.
.
}
इस डीएओ के मेरे कार्यान्वयन को जेनरिक का उपयोग करके डेवलपर को उन इकाइयों को पारित करने के लिए मजबूर करता है, जिन्हें वे केवल जेनरिकडॉ उप-वर्ग के द्वारा डिजाइन किए गए हैं।
public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
public UserDaoHibernateImpl() {
super(User.class); // This is for giving Hibernate a .class
// work with, as generics disappear at runtime
}
// Entity specific methods here
}
मेरी छोटी रूपरेखा अधिक मजबूत है (छानने, आलसी लोडिंग, खोज जैसी चीजें हैं)। मैंने आपको उदाहरण देने के लिए यहां सरलीकरण किया है
मैं, स्टीव और आप की तरह, शुरुआत में कहा "बहुत गन्दा और जटिल" लेकिन अब मुझे इसके फायदे दिखाई दे रहे हैं
स्पष्ट रूप से "टाइप सेफ्टी" और "नो कास्टिंग" जैसे लाभों का पहले ही उल्लेख किया गया है, इसलिए शायद मैं कुछ अन्य "लाभों" के बारे में बात कर सकता हूं जो मुझे आशा है कि यह मदद करता है।
सबसे पहले, जेनेरिक एक भाषा-स्वतंत्र अवधारणा है और, आईएमओ, यदि आप एक ही समय में नियमित (रनटाइम) बहुरूपता के बारे में सोचते हैं तो यह अधिक समझ में आता है।
उदाहरण के लिए, जैसा कि हम ऑब्जेक्ट ओरिएंटेड डिज़ाइन से जानते हैं, बहुरूपता में रनटाइम नोटिफ़िकेशन होता है, जहाँ रनटाइम पर कॉलर ऑब्जेक्ट का पता लगाया जाता है, क्योंकि प्रोग्राम एक्जीक्यूशन जाता है और संबंधित विधि को रनटाइम प्रकार के आधार पर कहा जाता है। जेनरिक में, विचार कुछ हद तक समान है लेकिन सब कुछ संकलन के समय होता है। इसका क्या मतलब है और आप इसका उपयोग कैसे करते हैं?
(चलो इसे कॉम्पैक्ट रखने के लिए जेनेरिक विधियों के साथ छड़ी करें) इसका मतलब है कि आप अभी भी अलग-अलग कक्षाओं पर एक ही विधि रख सकते हैं (जैसे कि आप पहले पॉलीमॉर्फिक कक्षाओं में थे) लेकिन इस बार वे संकलक द्वारा ऑटो-जेनरेट किए गए प्रकारों पर निर्भर करते हैं संकलन के समय। आप संकलन के समय आपके द्वारा दिए गए प्रकार पर अपने तरीकों का उपयोग करते हैं। इसलिए, आपके द्वारा किए जाने वाले हर एक प्रकार के लिए स्क्रैच से तरीकों को लिखने के बजाय, जैसा कि आप रनटाइम पोलिमोर्फ़िज्म (विधि को ओवरराइड करना) करते हैं, आप कंपाइलर्स को संकलन के दौरान काम करने देते हैं। इसका एक स्पष्ट लाभ है क्योंकि आपको अपने सिस्टम में उपयोग किए जा सकने वाले सभी संभावित प्रकारों का अनुमान लगाने की आवश्यकता नहीं है जो कोड परिवर्तन के बिना इसे और अधिक स्केलेबल बनाता है।
कक्षाएं बहुत समान तरीके से काम करती हैं। आप टाइप करते हैं और कोड कंपाइलर द्वारा जनरेट किया जाता है।
एक बार जब आप "संकलन समय" का विचार प्राप्त करते हैं, तो आप "बाउंडेड" प्रकारों का उपयोग कर सकते हैं और कक्षाओं / विधियों के माध्यम से एक पैरामीट्रिक प्रकार के रूप में पारित किया जा सकता है। तो, आप नियंत्रित कर सकते हैं कि किस माध्यम से पारित किया जाए जो एक शक्तिशाली चीज है विशेष रूप से आपके पास अन्य लोगों द्वारा खपत की जाने वाली एक रूपरेखा है।
public interface Foo<T extends MyObject> extends Hoo<T>{
...
}
कोई भी अब MyObject के अलावा sth सेट नहीं कर सकता है।
इसके अलावा, आप अपने तरीके के तर्कों पर "बाधाओं को लागू" कर सकते हैं जिसका अर्थ है कि आप यह सुनिश्चित कर सकते हैं कि आपके दोनों विधि तर्क एक ही प्रकार पर निर्भर होंगे।
public <T extends MyObject> foo(T t1, T t2){
...
}
आशा है कि यह सब समझ में आता है।
मैंने एक बार इस विषय पर बात की थी। आप मेरी स्लाइड्स, कोड और ऑडियो रिकॉर्डिंग http://www.adventuresinsoftware.com/generics/ पर पा सकते हैं ।
संग्रह के लिए जेनरिक का उपयोग करना सरल और साफ है। यहां तक कि अगर आप इसे हर जगह पर पंट करते हैं, तो संग्रह से प्राप्त लाभ मेरे लिए एक जीत है।
List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
stuff.do();
}
बनाम
List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
Stuff stuff = (Stuff)i.next();
stuff.do();
}
या
List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
Stuff stuff = (Stuff)stuffList.get(i);
stuff.do();
}
यह अकेले जेनेरिक की सीमांत "लागत" के लायक है, और आपको इसका उपयोग करने और मूल्य प्राप्त करने के लिए एक सामान्य गुरु होने की आवश्यकता नहीं है।
जेनरिक आपको विशिष्ट प्रकार का समर्थन प्रदान करते हुए अधिक पुन: प्रयोज्य वस्तुओं / विधियों को बनाने की क्षमता भी देता है। आप कुछ मामलों में बहुत अधिक प्रदर्शन प्राप्त करते हैं। मैं जावा जेनरिक पर पूर्ण युक्ति नहीं जानता, लेकिन .NET में मैं टाइप पैरामीटर पर अवरोधों को निर्दिष्ट कर सकता हूं, जैसे कि इम्प्लीमेंट्स ए इंटरफ़ेस, कंस्ट्रक्टर, और व्युत्पत्ति।
जेनेरिक एल्गोरिदम को लागू करने के लिए प्रोग्रामर को सक्षम करना - जेनेरिक का उपयोग करके, प्रोग्रामर जेनेरिक एल्गोरिदम को लागू कर सकते हैं जो विभिन्न प्रकारों के संग्रह पर काम करते हैं, अनुकूलित किए जा सकते हैं, और टाइप-सुरक्षित और पढ़ने में आसान हैं।
संकलित समय पर मजबूत प्रकार की जांच - एक जावा संकलक सामान्य कोड की जाँच करने के लिए मजबूत प्रकार लागू करता है और यदि कोड प्रकार की सुरक्षा का उल्लंघन करता है तो त्रुटियों को जारी करता है। संकलन समय त्रुटियों को ठीक करना रनटाइम त्रुटियों को ठीक करने से आसान है, जिसे ढूंढना मुश्किल हो सकता है।
जातियों का उन्मूलन।