व्यक्तिगत अनुभव: निम्न स्तर के सी विकास से जावा प्रोग्रामिंग में जाना



लेख लेखक के व्यक्तिगत अनुभव को दर्शाता है, एक एविड माइक्रोकंट्रोलर प्रोग्रामर, जो सी (और सी ++ में थोड़ा) में माइक्रोकंट्रोलर विकास के कई वर्षों के अनुभव के बाद, एंड्रॉइड चलाने वाले टीवी सेट-टॉप बॉक्स के लिए सॉफ्टवेयर विकसित करने के लिए एक प्रमुख जावा परियोजना में भाग लेने का अवसर था। इस परियोजना के दौरान, मैं जावा और सी / सी ++ भाषाओं के बीच दिलचस्प अंतर के बारे में नोट्स एकत्र करने में सक्षम था, लेखन कार्यक्रमों के विभिन्न दृष्टिकोणों का मूल्यांकन करता हूं। लेख एक संदर्भ होने का दिखावा नहीं करता है, यह जावा कार्यक्रमों की दक्षता और उत्पादकता की जांच नहीं करता है। यह व्यक्तिगत टिप्पणियों का संग्रह है। जब तक अन्यथा निर्दिष्ट नहीं किया जाता है, यह एक जावा एसई 7 रिलीज है।

सिंटैक्स मतभेद और नियंत्रण निर्माण


संक्षेप में - अंतर न्यूनतम हैं, वाक्यविन्यास बहुत समान है। कोड ब्लॉक भी घुंघराले ब्रेसिज़ {} की एक जोड़ी से बनते हैं। पहचानकर्ताओं को संकलित करने के नियम C / C ++ के लिए समान हैं। कीवर्ड की सूची लगभग C / C ++ की तरह ही है। अंतर्निहित डेटा प्रकार - सी / सी ++ में उन लोगों के समान। Arrays - सभी को वर्ग कोष्ठक का उपयोग करके भी घोषित किया जाता है।

नियंत्रण इफ़-का निर्माण करता है, जबकि, जबकि, के लिए, स्विच भी लगभग पूरी तरह समान हैं। यह उल्लेखनीय है कि जावा में सी-प्रोग्रामर्स (जो कि गोटो कीवर्ड के साथ उपयोग किए जाते हैं और जिनके उपयोग को दृढ़ता से हतोत्साहित किया जाता है) से परिचित लेबल थे। हालांकि, जावा ने गोटो का उपयोग करके एक लेबल पर स्विच करने की संभावना को बाहर कर दिया। लेबल का उपयोग केवल नेस्टेड छोरों से बाहर निकलने के लिए किया जाना चाहिए:

outer: for (int i = 0; i < 5; i++) { inner: for (int j = 0; j < 5; j++) { if (i == 2) break inner; if (i == 3) continue outer; } } 

जावा में कार्यक्रमों की पठनीयता में सुधार करने के लिए, एक अंडरस्कोर के साथ लंबी संख्या के अंकों को अलग करने के लिए एक दिलचस्प अवसर जोड़ा गया है:

 int value1 = 1_500_000; long value2 = 0xAA_BB_CC_DD; 

बाहरी रूप से, एक जावा प्रोग्राम एक परिचित सी प्रोग्राम से बहुत अलग नहीं है। मुख्य दृश्य अंतर यह है कि जावा फ़ंक्शंस, चर, नए प्रकार (संरचनाओं) की परिभाषाएं, स्थिरांक, और इसी तरह की अनुमति नहीं देता है, जो स्रोत फ़ाइल में "स्वतंत्र रूप से" स्थित हैं। जावा एक वस्तु-उन्मुख भाषा है, इसलिए सभी कार्यक्रम संस्थाओं को किसी न किसी वर्ग से संबंधित होना चाहिए। एक और महत्वपूर्ण अंतर प्रीप्रोसेसर की कमी है। इन दो अंतरों को नीचे और अधिक विवरण में वर्णित किया गया है।

सी भाषा में वस्तु दृष्टिकोण


जब हम C में बड़े प्रोग्राम लिखते हैं, तो हमें मूल रूप से ऑब्जेक्ट्स के साथ काम करना पड़ता है। यहाँ वस्तु की भूमिका "वास्तविक दुनिया" के एक निश्चित सार का वर्णन करने वाली संरचना द्वारा की जाती है:

 //   – «» struct Data { int field; char *str; /* ... */ }; 

सी में भी "ऑब्जेक्ट्स" को संसाधित करने के लिए तरीके हैं -स्ट्रक्टर्स - फ़ंक्शन। हालांकि, फ़ंक्शन अनिवार्य रूप से डेटा के साथ विलय नहीं किए जाते हैं। हां, वे आम तौर पर एक फ़ाइल में रखे जाते हैं, लेकिन हर बार "टिपिकल" फ़ंक्शन में संसाधित होने के लिए ऑब्जेक्ट को पॉइंटर पास करना आवश्यक होता है:

 int process(struct Data *ptr, int arg1, const char *arg2) { /* ... */ return result_code; } 

आप इसे स्टोर करने के लिए मेमोरी आवंटित करने के बाद ही "ऑब्जेक्ट" का उपयोग कर सकते हैं:

 Data *data = malloc(sizeof(Data)); 

एक सी प्रोग्राम में, एक फ़ंक्शन को आमतौर पर परिभाषित किया जाता है जो इसके पहले उपयोग से पहले "ऑब्जेक्ट" के प्रारंभ के लिए जिम्मेदार है:

 void init(struct Data *data) { data->field = 1541; data->str = NULL; } 

तब C में एक "ऑब्जेक्ट" का जीवन चक्र आमतौर पर इस तरह होता है:

 /*    "" */ struct Data *data = malloc(sizeof(Data)); /*  "" */ init(data); /*   "" */ process(data, 0, "string"); /*  ,  ""     . */ free(data); 

अब हम संभावित रनटाइम त्रुटियों को सूचीबद्ध करते हैं जो प्रोग्रामर द्वारा "ऑब्जेक्ट" के जीवन चक्र में किए जा सकते हैं:

  1. "ऑब्जेक्ट" के लिए मेमोरी आवंटित करना भूल जाएं
  2. आवंटित स्मृति की गलत मात्रा निर्दिष्ट करें
  3. "ऑब्जेक्ट" को प्रारंभ करना न भूलें
  4. ऑब्जेक्ट का उपयोग करने के बाद मेमोरी को खाली करना भूल जाएं

ऐसी त्रुटियों का पता लगाना बेहद मुश्किल हो सकता है, क्योंकि वे संकलक द्वारा पता नहीं लगाए जाते हैं और प्रोग्राम ऑपरेशन के दौरान दिखाई देते हैं। इसके अलावा, उनका प्रभाव बहुत विविध हो सकता है और कार्यक्रम के अन्य चर और "वस्तुओं" को प्रभावित कर सकता है।

जावा ऑब्जेक्ट दृष्टिकोण


OOP - ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग के साथ सामना, आपने शायद OOP व्हेल में से एक के बारे में सुना - एनकैप्सुलेशन। जावा में, सी के विपरीत, उन्हें संसाधित करने के लिए डेटा और विधियों को एक साथ जोड़ दिया जाता है और "सच" ऑब्जेक्ट होते हैं। OOP के संदर्भ में, इसे एनकैप्सुलेशन कहा जाता है। एक वर्ग एक वस्तु का वर्णन है, सी में एक वर्ग का निकटतम एनालॉग टाइपडेफ संरचना का उपयोग करके एक नए प्रकार को परिभाषित करना है। जावा के संदर्भ में, उन कार्यों को जो एक वर्ग से संबंधित हैं, विधियों को कहा जाता है।

 //   class Entity { public int field; //   public String str; //   //  public int process(int arg1, String arg2) { /* ... */ return resultCode; } //  public Entity() { field = 1541; str = "value"; } } 

जावा भाषा की विचारधारा "सब कुछ एक वस्तु है" कथन पर आधारित है। इसलिए, यह आश्चर्य की बात नहीं है कि जावा वर्ग से अलग-अलग तरीकों (कार्यों) और डेटा फ़ील्ड (चर) के निर्माण को प्रतिबंधित करता है। यहां तक ​​कि परिचित मुख्य () विधि, जिसमें से कार्यक्रम शुरू होता है, कक्षाओं में से एक से संबंधित होना चाहिए।

जावा में एक वर्ग परिभाषा सी में एक संरचना घोषणा के अनुरूप है एक वर्ग का वर्णन करके, आप स्मृति में कुछ भी नहीं बनाते हैं। नए ऑपरेटर द्वारा इस वर्ग की एक वस्तु इसके निर्माण के समय दिखाई देती है। जावा में एक ऑब्जेक्ट बनाना सी भाषा में मेमोरी आवंटित करने का एक एनालॉग है, लेकिन बाद के विपरीत, ऑब्जेक्ट निर्माण के दौरान एक विशेष विधि को स्वचालित रूप से कहा जाता है - ऑब्जेक्ट का निर्माता। कंस्ट्रक्टर ऑब्जेक्ट के प्रारंभिक आरंभीकरण की भूमिका पर लेता है - पहले से चर्चा किए गए इनिट () फ़ंक्शन का एक एनालॉग। कंस्ट्रक्टर का नाम वर्ग के नाम से मेल खाना चाहिए। कंस्ट्रक्टर मान वापस नहीं कर सकता।

जावा प्रोग्राम में किसी ऑब्जेक्ट का जीवन चक्र इस प्रकार है:

 //   (   ,  ) Entity entity = new Entity(); //    entity.process(123, "argument"); 

ध्यान दें कि जावा प्रोग्राम में संभावित त्रुटियों की संख्या C प्रोग्राम की तुलना में बहुत कम है। हां, आप अभी भी पहले उपयोग से पहले ऑब्जेक्ट बनाने के लिए भूल सकते हैं (जो, हालांकि, आसानी से डीबग किए गए NullPointerException को जन्म देगा), लेकिन अन्य त्रुटियों के लिए निहित के रूप में। सी कार्यक्रम, स्थिति मौलिक रूप से बदल रही है:

  1. जावा में कोई आकार-प्रकार () ऑपरेटर नहीं है। जावा कंपाइलर ऑब्जेक्ट को स्टोर करने के लिए मेमोरी की मात्रा की गणना करता है। इसलिए, चयन के गलत आकार को निर्दिष्ट करना संभव नहीं है।
  2. सृजन का आरंभ सृष्टि के समय होता है। आरंभीकरण के बारे में भूलना असंभव है।
  3. ऑब्जेक्ट द्वारा कब्जा की गई मेमोरी को मुक्त करने की आवश्यकता नहीं है, कचरा कलेक्टर यह काम करता है। उपयोग के बाद किसी ऑब्जेक्ट को हटाना भूल जाना असंभव है - "मेमोरी लीक" प्रभाव की संभावना कम है।

तो, जावा में सब कुछ एक वर्ग या किसी अन्य की वस्तु है। प्रदर्शन और स्मृति की खपत में सुधार करने के लिए अपवादों को भाषा में जोड़ा गया है। प्राइमिटिव के बारे में अधिक जानकारी नीचे है।

मेमोरी और कचरा संग्रहकर्ता


जावा एक प्रोग्रामर, सी / सी ++ के लिए ढेर और स्टैक की परिचित अवधारणाओं को बरकरार रखता है। नए ऑपरेटर के साथ ऑब्जेक्ट बनाते समय, ऑब्जेक्ट को स्टोर करने के लिए मेमोरी को ढेर से उधार लिया जाता है। हालाँकि, किसी ऑब्जेक्ट का लिंक (एक लिंक पॉइंटर का एक एनालॉग है), यदि निर्मित ऑब्जेक्ट किसी अन्य ऑब्जेक्ट का हिस्सा नहीं है, तो स्टैक पर रखा गया है। ढेर पर वस्तुओं के "निकायों" को संग्रहीत किया जाता है, और स्टैक पर स्थानीय चर होते हैं: वस्तुओं और आदिम प्रकारों के संदर्भ। यदि कार्यक्रम के निष्पादन के दौरान ढेर मौजूद है और कार्यक्रम के सभी थ्रेड्स के लिए उपलब्ध है, तो स्टैक विधि का है और इसके निष्पादन के दौरान ही मौजूद है, और कार्यक्रम के अन्य थ्रेड्स के लिए भी दुर्गम है।

जावा अनावश्यक है और इससे भी अधिक - आप किसी ऑब्जेक्ट द्वारा कब्जा की गई मेमोरी को मैन्युअल रूप से मुक्त नहीं कर सकते। यह काम कचरा कलेक्टर द्वारा स्वचालित मोड में किया जाता है। रनटाइम मॉनिटर करता है कि क्या वस्तु से वस्तु के लिंक का अनुसरण करके कार्यक्रम के वर्तमान स्थान से ढेर में प्रत्येक वस्तु तक पहुंचना संभव है। यदि नहीं, तो इस तरह के ऑब्जेक्ट को "कचरा" के रूप में मान्यता दी जाती है और विलोपन के लिए एक उम्मीदवार बन जाता है।

यह ध्यान रखना महत्वपूर्ण है कि विलोपन स्वयं उस समय नहीं होता है जब ऑब्जेक्ट "अब आवश्यक नहीं है" - कचरा कलेक्टर विलोपन पर निर्णय लेता है, और जब तक कि कार्यक्रम समाप्त नहीं हो जाता है, तब तक विलोपन को जितना आवश्यक हो उतना देरी हो सकती है।

बेशक, कचरा कलेक्टर के काम में प्रोसेसर ओवरहेड की आवश्यकता होती है। लेकिन बदले में, वह "वस्तुओं" के उपयोग के बाद स्मृति को मुक्त करने की आवश्यकता से जुड़े एक बड़े सिरदर्द के प्रोग्रामर को राहत देता है। वास्तव में, हम मेमोरी को "तब" लेते हैं जब हमें इसकी आवश्यकता होती है और इसका उपयोग करते हैं, यह सोचकर नहीं कि हमें इसे अपने आप से मुक्त करने की आवश्यकता है।

स्थानीय चर के बारे में बोलते हुए, हमें जावा के दृष्टिकोण को उनके आरंभन के लिए याद करना चाहिए। यदि C / C ++ में एक असिंचित स्थानीय चर में एक यादृच्छिक मान होता है, तो जावा कंपाइलर बस इसे असिंचित नहीं रहने देगा:

 int i; //  . System.out.println("" + i); //  ! 

लिंक - रिप्लेसमेंट पॉइंटर्स


जावा में पॉइंटर्स नहीं हैं; तदनुसार, एक जावा प्रोग्रामर में पॉइंटर्स के साथ काम करते समय होने वाली कई त्रुटियों में से एक बनाने की क्षमता नहीं है। जब आप कोई ऑब्जेक्ट बनाते हैं, तो आपको इस ऑब्जेक्ट का लिंक मिलता है:

 //  entity –  . Entity entity = new Entity(); 

सी में, प्रोग्रामर के पास एक विकल्प था: कैसे पास करें, कहें, एक फ़ंक्शन को एक संरचना। आप मूल्य से गुजर सकते हैं:

 //    . int func(Data data);    –   : //    . void process(Data *data); 

मूल्य से गुजरना गारंटी देता है कि फ़ंक्शन संरचना में डेटा को नहीं बदलेगा, लेकिन प्रदर्शन के मामले में अप्रभावी था - जिस समय फ़ंक्शन को बुलाया गया था, संरचना की एक प्रति बनाई गई थी। एक पॉइंटर से गुजरना कहीं अधिक कुशल है: वास्तव में, मेमोरी में पता जहां संरचना स्थित है, फ़ंक्शन को पास किया गया था।

जावा में, किसी विधि को एक विधि पास करने का केवल एक ही तरीका था - संदर्भ द्वारा। जावा में संदर्भ द्वारा पासिंग सी में एक पॉइंटर से गुजरने के अनुरूप है:
  • कॉपी (क्लोनिंग) मेमोरी नहीं होती है,
  • वास्तव में, इस वस्तु के स्थान का पता प्रेषित होता है।

हालाँकि, C भाषा पॉइंटर के विपरीत, जावा लिंक को इंक्रीमेंट / डीग्रेड नहीं किया जा सकता है। जावा में एक लिंक का उपयोग करके सरणी के तत्वों के माध्यम से "रनिंग" काम नहीं करेगा। एक लिंक के साथ किया जा सकता है कि यह एक अलग मूल्य देने के लिए है।

बेशक, इस तरह के बिंदुओं की अनुपस्थिति संभव त्रुटियों की संख्या को कम कर देती है, हालांकि, नल सूचक का एनालॉग भाषा में बना रहता है - शून्य कीवर्ड द्वारा निरूपित एक शून्य संदर्भ।

एक शून्य संदर्भ एक जावा प्रोग्रामर के लिए सिरदर्द है, जैसा कि ऑब्जेक्ट संदर्भ को उपयोग करने से पहले या तो नल के लिए जाँचने के लिए या NullPointerException अपवादों को संभालने के लिए बाध्य करता है। यदि ऐसा नहीं किया जाता है, तो प्रोग्राम क्रैश हो जाएगा।

तो, जावा में सभी ऑब्जेक्ट लिंक के माध्यम से पारित किए जाते हैं। आदिम डेटा प्रकार (int, long, char ...) मूल्य द्वारा पारित किए जाते हैं (आदिम के बारे में अधिक जानकारी नीचे दी गई है)।

जावा लिंक सुविधाएँ


कार्यक्रम में किसी भी वस्तु तक पहुंच एक लिंक के माध्यम से है - यह स्पष्ट रूप से प्रदर्शन पर सकारात्मक प्रभाव डालता है, लेकिन यह एक नौसिखिया को आश्चर्यचकित कर सकता है:

 //  ,   entity1   . Entity entity1 = new Entity(); entity1.field = 123; //   entity2,     entity1. //    !   ! Entity entity2 = entity1; //   entity1  entity2         . entity2.field = 777; //  entity1.field  777. System.out.println(entity1.field); 

विधि तर्क और वापसी मान - सब कुछ लिंक के माध्यम से पारित किया जाता है। फायदे के अलावा, सी / सी ++ भाषाओं की तुलना में एक खामी है, जहां हम स्पष्ट रूप से एक कांस्टेबल क्वालीफायर का उपयोग करके एक पॉइंटर के माध्यम से पारित मूल्य को बदलने से कार्यों को प्रतिबंधित कर सकते हैं:

 void func(const struct Data* data) { //  ! //    ,    ! data->field = 0; } 

यही है, सी भाषा आपको संकलन चरण पर इस त्रुटि को ट्रैक करने की अनुमति देती है। जावा में कॉन्स्टेबल कीवर्ड भी है, लेकिन यह भविष्य के संस्करणों के लिए आरक्षित है और वर्तमान में इसका उपयोग नहीं किया जाता है। कुछ हद तक, अंतिम कीवर्ड को अपनी भूमिका को पूरा करने के लिए कहा जाता है। हालांकि, यह परिवर्तन से विधि को दी गई वस्तु की रक्षा नहीं करता है:

 public class Main { void func(final Entity data) { //    . //    final,    . data.field = 0; } } 

बात यह है कि इस मामले में अंतिम कीवर्ड लिंक पर लागू होता है, न कि उस वस्तु पर जो लिंक को इंगित करता है। यदि अंतिम को आदिम पर लागू किया जाता है, तो संकलक अपेक्षा के अनुसार व्यवहार करता है:

 void func(final int value) { //    . value = 0; } 

जावा लिंक सी ++ भाषा लिंक के समान हैं।

जावा आदिम


प्रत्येक जावा ऑब्जेक्ट, डेटा फ़ील्ड के अलावा, सहायक जानकारी शामिल है। यदि हम संचालित करना चाहते हैं, उदाहरण के लिए, अलग-अलग बाइट्स में और प्रत्येक बाइट को एक ऑब्जेक्ट द्वारा दर्शाया जाता है, तो बाइट्स की एक सरणी के मामले में, मेमोरी ओवरहेड कई बार प्रयोग करने योग्य आकार से अधिक हो सकता है।
जावा के लिए ऊपर वर्णित मामलों में पर्याप्त कुशल बने रहने के लिए, आदिम प्रकारों - आदिम - भाषा के लिए समर्थन जोड़ा गया था।
आदिमरायथोड़ी गहराईसी में संभव एनालॉग
बाइटपूर्णांक8चार
कम16कम
चार16wchar_t
पूर्णांक32int (लंबा)
लंबे समय तक64लंबे समय तक
नावफ्लोटिंग पॉइंट नंबर32नाव
दोहरा64दोहरा
बूलियनबूलियन-int (C89) / बूल (C99)

सी भाषा में सभी प्राइमिटिव्स के अपने एनालॉग हैं। हालांकि, सी मानक पूर्णांक प्रकारों के सटीक आकार का निर्धारण नहीं करता है, इसके बजाय, इस प्रकार के मूल्यों की सीमा तय कर सकते हैं। अक्सर, प्रोग्रामर विभिन्न मशीनों के लिए एक ही बिट गहराई सुनिश्चित करना चाहता है, जो प्रोग्राम में uint32_t जैसे प्रकारों की उपस्थिति की ओर जाता है, हालांकि सभी लाइब्रेरी फ़ंक्शंस में इंट प्रकार के तर्कों की आवश्यकता होती है।
इस तथ्य को भाषा के लाभों के लिए जिम्मेदार नहीं ठहराया जा सकता है।

सी के विपरीत जावा पूर्णांक प्राइमेटिव्स में बिट गहराई निर्धारित है। इस प्रकार, आपको उस मशीन की वास्तविक बिट गहराई के बारे में चिंता करने की ज़रूरत नहीं है जिस पर जावा प्रोग्राम चल रहा है, साथ ही बाइट ऑर्डर ("नेटवर्क" या "इंटेल") के बारे में भी। यह तथ्य सिद्धांत को महसूस करने में मदद करता है "यह एक बार लिखा गया है - यह हर जगह पूरा होता है"।

इसके अलावा, जावा में सभी पूर्णांक प्राइमेटिव्स पर हस्ताक्षर किए गए हैं (भाषा में अहस्ताक्षरित कीवर्ड का अभाव है)। यह सी में निहित एकल अभिव्यक्ति में हस्ताक्षरित और अहस्ताक्षरित चर का उपयोग करने की कठिनाई को समाप्त करता है।

अंत में, जावा में मल्टी-बाइट प्राइमेटिव्स में बाइट ऑर्डर निर्धारित है (कम पते पर कम बाइट, लिटिल-एंडियन, रिवर्स ऑर्डर)।

जावा में आदिम के साथ संचालन के कार्यान्वयन के नुकसान में यह तथ्य शामिल है कि यहां, C / C ++ प्रोग्राम में, बिट ग्रिड का ओवरफ्लो हो सकता है, जिसमें कोई अपवाद नहीं फेंका जा सकता है:

 int i1 = 2_147_483_640; int i2 = 2_147_483_640; int r = (i1 + i2); // r = -16 

तो, जावा में डेटा को दो प्रकार की संस्थाओं द्वारा दर्शाया गया है: ऑब्जेक्ट और प्राइमेटिव। आदिम "सब कुछ एक वस्तु है" की अवधारणा का उल्लंघन करते हैं, लेकिन कुछ स्थितियों में वे उनका उपयोग न करने के लिए बहुत प्रभावी हैं।

विरासत


वंशानुक्रम एक और ओओपी व्हेल है जिसके बारे में आपने शायद सुना हो। यदि आप संक्षेप में इस सवाल का जवाब देते हैं कि "वंशानुक्रम क्यों आवश्यक है", तो उत्तर होगा "कोड का पुन: उपयोग"।

मान लीजिए कि आप C में प्रोग्राम करते हैं, और आपके पास एक अच्छी तरह से लिखा और डीबग किया गया "क्लास" है - एक संरचना और इसे संसाधित करने के लिए कार्य करता है। अगला, आवश्यकता एक समान "वर्ग" बनाने के लिए उठती है, लेकिन बढ़ी हुई कार्यक्षमता के साथ, और मूल "वर्ग" अभी भी आवश्यक है। सी भाषा के मामले में, आपके पास इस समस्या को हल करने का केवल एक ही तरीका है - रचना। यह एक नई विस्तारित संरचना बनाने के बारे में है - "वर्ग", जिसमें आधार "कक्षा" संरचना के लिए एक सूचक होना चाहिए:

 struct Base { int field1; char *field2; }; void baseMethod(struct Base *obj, int arg); struct Extended { struct Base *base; int auxField; }; void extendedMethod(struct Extended *obj, int arg) { baseMethod(obj->base, 123); /* ... */ } 

जावा एक वस्तु-उन्मुख भाषा के रूप में आपको वंशानुक्रम तंत्र का उपयोग करके मौजूदा वर्गों की कार्यक्षमता का विस्तार करने की अनुमति देता है:

 //   class Base { protected int baseField; private int hidden; public void baseMethod() { } } //   -   . class Extended extends Base { public void extendedMethod() { //    public  protected     . baseField = 123; baseMethod(); // !   private  ! hidden = 123; } } 

यह ध्यान दिया जाना चाहिए कि जावा किसी भी तरह से पहले से ही लिखित कक्षाओं की कार्यक्षमता का विस्तार करने के तरीके के रूप में रचना के उपयोग को प्रतिबंधित नहीं करता है। इसके अलावा, कई स्थितियों में, रचना विरासत के लिए बेहतर है।

उत्तराधिकार के लिए धन्यवाद, जावा में कक्षाएं एक पदानुक्रमित संरचना में व्यवस्थित होती हैं, प्रत्येक कक्षा में आवश्यक रूप से एक और केवल "माता-पिता" होते हैं और उनमें "बच्चों" की संख्या हो सकती है। C ++ के विपरीत, जावा में एक वर्ग एक से अधिक अभिभावकों से विरासत में नहीं मिल सकता है (यह "हीरे की विरासत" की समस्या को हल करता है)।

वंशानुक्रम के दौरान, व्युत्पन्न वर्ग अपने स्थान के सभी सार्वजनिक और संरक्षित क्षेत्रों और अपने आधार वर्ग के तरीकों के साथ-साथ अपने आधार वर्ग के आधार वर्ग तक पहुंच जाता है, और इसलिए वंशानुक्रम पदानुक्रम।

वंशानुक्रम पदानुक्रम के शीर्ष पर सभी जावा वर्गों के सामान्य पूर्वज हैं - ऑब्जेक्ट क्लास, केवल एक ही है जिसमें माता-पिता नहीं हैं।

गतिशील प्रकार की पहचान


जावा भाषा के प्रमुख बिंदुओं में से एक गतिशील प्रकार की पहचान (RTTI) के लिए समर्थन है। सरल शब्दों में, आरटीटीआई आपको एक व्युत्पन्न वर्ग की एक वस्तु को स्थानापन्न करने की अनुमति देता है जहां आधार के संदर्भ की आवश्यकता होती है:

 //     Base link; //         link = new Extended(); 

रनटाइम पर एक लिंक होने के बाद, आप उस ऑब्जेक्ट के सही प्रकार को निर्धारित कर सकते हैं जिसके लिए लिंक संदर्भित है - इंस्टोफ़ ऑपरेटर का उपयोग करना:

 if (link instanceof Base) { // false } else if (link instanceof Extended) { // true } 

विधि ओवरराइड


एक विधि या फ़ंक्शन को फिर से परिभाषित करने का मतलब है कि उसके या उसके शरीर को कार्यक्रम के निष्पादन चरण में बदलना। C प्रोग्रामर प्रोग्राम निष्पादन के दौरान किसी फ़ंक्शन के व्यवहार को बदलने के लिए एक भाषा की क्षमता से अवगत होते हैं। यह फ़ंक्शन पॉइंटर्स का उपयोग करने के बारे में है। उदाहरण के लिए, आप संरचना की संरचना में एक फ़ंक्शन के लिए एक पॉइंटर को शामिल कर सकते हैं और इस संरचना के डेटा प्रोसेसिंग एल्गोरिदम को बदलने के लिए पॉइंटर को विभिन्न कार्यों को असाइन कर सकते हैं:

 struct Object { //   . void (*process)(struct Object *); int data; }; void divideByTwo(struct Object *obj) { obj->data = obj->data / 2; } void square(struct Object *obj) { obj->data = obj->data * obj->data; } struct Object obj; obj.data = 123; obj.process = divideByTwo; obj.process(&obj); // 123 / 2 = 61 obj.process = square; obj.process(&obj); // 61 * 61 = 3721 

जावा में, अन्य ओओपी भाषाओं की तरह, ओवरराइडिंग तरीकों को इनहेरिटिकली इनहेरिटेंस से जोड़ा जाता है। एक व्युत्पन्न वर्ग को आधार वर्ग के सार्वजनिक और संरक्षित तरीकों तक पहुंच प्राप्त होती है। इस तथ्य के अलावा कि वह उन्हें कॉल कर सकता है, आप आधार वर्ग के तरीकों में से एक के व्यवहार को उसके हस्ताक्षर को बदलने के बिना बदल सकते हैं। ऐसा करने के लिए, यह एक विधि को परिभाषित करने के लिए पर्याप्त है जो व्युत्पन्न वर्ग में समान हस्ताक्षर के साथ है:

 //   -   . class Extended extends Base { //  . public void method() { /* ... */ } //     ! // E      . //     . public void method(int i) { /* ... */ } } 

यह बहुत महत्वपूर्ण है कि हस्ताक्षर (विधि का नाम, वापसी मूल्य, तर्क) बिल्कुल मेल खाते हैं। यदि विधि का नाम मेल खाता है, और तर्क अलग-अलग हैं, तो विधि ओवरलोड होती है, जिसके बारे में और नीचे।

बहुरूपता


एन्कैप्सुलेशन और इनहेरिटेंस की तरह, तीसरी OOP व्हेल - बहुरूपता - भी प्रक्रियात्मक रूप से उन्मुख सी भाषा में किसी प्रकार का एनालॉग है।

मान लीजिए कि हमारे पास संरचनाओं के कई "वर्ग" हैं, जिनके साथ आप एक ही प्रकार की कार्रवाई करना चाहते हैं, और इस क्रिया को करने वाले फ़ंक्शन को सार्वभौमिक होना चाहिए - तर्क के रूप में किसी भी "वर्ग" के साथ काम करने में "सक्षम" होना चाहिए। एक संभावित समाधान इस प्रकार है:

  /*   */ enum Ids { ID_A, ID_B }; struct ClassA { int id; /* ... */ } void aInit(ClassA obj) { obj->id = ID_A; } struct ClassB { int id; /* ... */ } void bInit(ClassB obj) { obj->id = ID_B; } /* klass -   ClassA, ClassB, ... */ void commonFunc(void *klass) { /*   */ int id = (int *)klass; switch (id) { case ID_A: ClassA *obj = (ClassA *) klass; /* ... */ break; case ID_B: ClassB *obj = (ClassB *) klass; /* ... */ break; } /* ... */ } 

समाधान बोझिल लग रहा है, लेकिन लक्ष्य प्राप्त किया जाता है - सार्वभौमिक फ़ंक्शन कॉमनफंक () किसी भी "वर्ग" के "ऑब्जेक्ट" को एक तर्क के रूप में स्वीकार करता है। पूर्वापेक्षा एक "वर्ग" संरचना है पहले क्षेत्र में एक पहचानकर्ता होना चाहिए जिसके द्वारा वस्तु का वास्तविक "वर्ग" निर्धारित किया जाता है। "शून्य *" प्रकार के साथ तर्क के उपयोग के कारण ऐसा समाधान संभव है। हालांकि, किसी भी प्रकार का एक पॉइंटर ऐसे फ़ंक्शन को पारित किया जा सकता है, उदाहरण के लिए, "इंट *"। यह संकलन त्रुटियों का कारण नहीं होगा, लेकिन समय पर कार्यक्रम अप्रत्याशित रूप से व्यवहार करेगा।

अब देखते हैं कि जावा में बहुरूपता कैसे दिखता है (हालांकि, किसी भी अन्य ओओपी भाषा के रूप में)। मान लें कि हमारे पास कई कक्षाएं हैं जिन्हें किसी तरह से उसी तरीके से संसाधित किया जाना चाहिए। ऊपर प्रस्तुत भाषा C के समाधान के विपरीत, यह बहुरूपिक विधि MUST को दिए गए सेट के सभी वर्गों में शामिल किया जाना चाहिए, और इसके सभी संस्करणों में समान हस्ताक्षर होने चाहिए।

 class A { public void method() {/* ... */} } class B { public void method() {/* ... */} } class C { public void method() {/* ... */} } 

अगला, आपको कंपाइलर को उस विधि के संस्करण को कॉल करने के लिए मजबूर करने की आवश्यकता है जो संबंधित वर्ग से संबंधित है।

 void executor(_set_of_class_ klass) { klass.method(); } 

यही है, निष्पादक () विधि, जो कार्यक्रम में कहीं भी हो सकती है, को सेट (ए, बी या सी) से किसी भी वर्ग के साथ काम करने में सक्षम होना चाहिए। हमें किसी तरह संकलक को "बताना" चाहिए जो _set_of_class_ हमारी कई कक्षाओं को दर्शाता है। यहाँ विरासत काम में आती है - कुछ बेस क्लास के सेट डेरिवेटिव से सभी वर्गों को बनाना आवश्यक है, जिसमें एक पॉलीमॉर्फिक विधि शामिल होगी:

 abstract class Base { abstract public void method(); } class A extends Base { public void method() {/* ... */} } class B extends Base { public void method() {/* ... */} } class C extends Base { public void method() {/* ... */} }   executor()   : void executor(Base klass) { klass.method(); } 

और अब कोई भी वर्ग जो आधार का उत्तराधिकारी है (गतिशील प्रकार की पहचान के लिए धन्यवाद) उसे एक तर्क के रूप में पारित किया जा सकता है:

 executor(new A()); executor(new B()); executor(new C()); 

तर्क के रूप में किस क्लास ऑब्जेक्ट को पास किया जाता है, इस पर निर्भर करते हुए, इस क्लास से संबंधित विधि को बुलाया जाएगा।

अमूर्त कीवर्ड आपको विधि के शरीर को बाहर करने की अनुमति देता है (इसे सार बनाएं, ओओपी के संदर्भ में)। वास्तव में, हम संकलक को बता रहे हैं कि इस विधि को इससे प्राप्त वर्गों में ओवरराइड किया जाना चाहिए। यदि यह स्थिति नहीं है, तो संकलन त्रुटि उत्पन्न होती है। एक वर्ग जिसमें कम से कम एक अमूर्त विधि होती है, उसे अमूर्त भी कहा जाता है। कंपाइलर को कीवर्ड अमूर्त के साथ ऐसी कक्षाओं को भी चिह्नित करने की आवश्यकता होती है।

जावा प्रोजेक्ट संरचना


जावा में, सभी स्रोत फ़ाइलों में एक्सटेंशन * .java है। फ़ंक्शंस या कक्षाओं की * .h हेडर फाइलें और प्रोटोटाइप दोनों गायब हैं। प्रत्येक जावा स्रोत फ़ाइल में कम से कम एक वर्ग होना चाहिए। कक्षा का नाम लिखने के लिए प्रथागत है, जो एक बड़े अक्षर से शुरू होता है।

स्रोत कोड वाली कई फ़ाइलों को एक पैकेज में जोड़ा जा सकता है। ऐसा करने के लिए, निम्नलिखित शर्तों को पूरा करना होगा:
  1. स्रोत कोड वाली फाइलें फ़ाइल सिस्टम में समान निर्देशिका में होनी चाहिए।
  2. इस निर्देशिका का नाम पैकेज के नाम से मेल खाना चाहिए।
  3. प्रत्येक स्रोत फ़ाइल की शुरुआत में, जिस पैकेज से यह फ़ाइल संबंधित है उसे इंगित किया जाना चाहिए, उदाहरण के लिए:

 package com.company.pkg; 

ग्लोब के भीतर पैकेज नामों की विशिष्टता सुनिश्चित करने के लिए, कंपनी के "उल्टे" डोमेन नाम का उपयोग करने का प्रस्ताव है। हालांकि, यह कोई आवश्यकता नहीं है और स्थानीय परियोजना में किसी भी नाम का उपयोग किया जा सकता है।

यह भी अनुशंसा की जाती है कि आप लोअरकेस पैकेज नाम निर्दिष्ट करें। इसलिए उन्हें आसानी से वर्ग के नामों से अलग किया जा सकता है।

कार्यान्वयन की चिंता


इनकैप्सुलेशन का दूसरा पहलू इंटरफ़ेस और कार्यान्वयन का पृथक्करण है। यदि इंटरफ़ेस प्रोग्राम के बाहरी हिस्सों (मॉड्यूल या वर्ग के लिए बाहरी) तक पहुंच योग्य है, तो कार्यान्वयन छिपा हुआ है। साहित्य में, एक ब्लैक बॉक्स सादृश्य अक्सर खींचा जाता है जब आंतरिक कार्यान्वयन बाहर से "दिखाई नहीं देता है", लेकिन बॉक्स के इनपुट में क्या खिलाया जाता है और क्या देता है यह "दृश्यमान" है।

सी में, कार्यान्वयन को छिपाना एक मॉड्यूल के अंदर किया जाता है, उन कार्यों को चिह्नित करता है जो स्थिर कीवर्ड के साथ बाहर से दिखाई नहीं देना चाहिए। मॉड्यूल के इंटरफ़ेस को बनाने वाले कार्यों के प्रोटोटाइप को हेडर फ़ाइल में रखा गया है। C में एक मॉड्यूल का अर्थ है एक जोड़ी: एक्सटेंशन के साथ एक स्रोत फ़ाइल * .c और एक्सटेंशन के साथ एक हेडर * .h।

जावा में भी स्थैतिक कीवर्ड है, लेकिन यह बाहर से विधि या क्षेत्र की "दृश्यता" को प्रभावित नहीं करता है। "दृश्यता" को नियंत्रित करने के लिए 3 एक्सेस मॉडिफ़ायर हैं: निजी, संरक्षित, सार्वजनिक।

निजी के रूप में चिह्नित एक वर्ग के क्षेत्र और तरीके केवल उसके अंदर उपलब्ध हैं। संरक्षित क्षेत्र और विधियां भी वर्ग के वंशजों के लिए सुलभ हैं। सार्वजनिक संशोधक का अर्थ है कि चिह्नित तत्व वर्ग के बाहर से सुलभ है, अर्थात यह इंटरफ़ेस का हिस्सा है। यह भी संभव है कि कोई संशोधक नहीं है, इस मामले में वर्ग तत्व तक पहुंच उस पैकेज द्वारा सीमित है जिसमें कक्षा स्थित है।

यह अनुशंसा की जाती है कि कक्षा लिखते समय, शुरू में कक्षा के सभी क्षेत्रों को निजी के रूप में चिह्नित करें और आवश्यक के रूप में एक्सेस अधिकारों का विस्तार करें।

विधि अधिभार


सी मानक पुस्तकालय की कष्टप्रद विशेषताओं में से एक कार्य के एक पूरे चिड़ियाघर की उपस्थिति है जो अनिवार्य रूप से एक ही कार्य करते हैं, लेकिन तर्क के प्रकार में भिन्न होते हैं, उदाहरण के लिए: फ़ैब्स (), फैबसेफ़ (), फैबसेल () - डबल, फ्लोट और लंबे समय के लिए पूर्ण मूल्य प्राप्त करने के लिए कार्य। क्रमशः डबल प्रकार।

जावा (साथ ही सी ++) एक विधि अधिभार तंत्र का समर्थन करता है - एक वर्ग में पूरी तरह से समान नाम वाले कई तरीके हो सकते हैं, लेकिन प्रकार और तर्कों में भिन्नता है। तर्कों और उनके प्रकार की संख्या से, कंपाइलर विधि के आवश्यक संस्करण को स्वयं चुन लेगा - यह बहुत सुविधाजनक है और कार्यक्रम की पठनीयता में सुधार करता है।

जावा में, C ++ के विपरीत, ऑपरेटरों को ओवरलोड नहीं किया जा सकता है। अपवाद "+" और "+ =" ऑपरेटर हैं, जो शुरू में स्ट्रिंग स्ट्रिंग्स के लिए अतिभारित होते हैं।

जावा में वर्ण और स्ट्रिंग्स


C - , :

 char *str; //  ASCII  wchar_t *strw; //   ""  

. «», . , ( ) .

C- - , , , - . , C , .

जावा में, आदिम चार प्रकार (और साथ ही चरित्र आवरण, नीचे रैपर के बारे में) यूनीकोड ​​मानक के अनुसार एक एकल वर्ण का प्रतिनिधित्व करते हैं। UTF-16 एन्कोडिंग का उपयोग किया जाता है, क्रमशः, एक वर्ण मेमोरी में 2 बाइट्स रखता है, जो आपको वर्तमान में उपयोग की जाने वाली भाषाओं के लगभग सभी पात्रों को एनकोड करने की अनुमति देता है।

वर्णों को उनके यूनिकोड द्वारा निर्दिष्ट किया जा सकता है:

 char ch1 = '\u20BD'; 

यदि किसी वर्ण का यूनिकोड चार के लिए अधिकतम 216 से अधिक है, तो इस तरह के चरित्र को इंट द्वारा दर्शाया जाना चाहिए। स्ट्रिंग में, यह 16 बिट्स के 2 वर्णों पर कब्जा कर लेगा, लेकिन फिर, 216 से अधिक कोड वाले वर्णों का उपयोग बहुत कम किया जाता है।

जावा स्ट्रिंग्स अंतर्निहित स्ट्रिंग वर्ग द्वारा कार्यान्वित किए जाते हैं और 16-बिट वर्णों को संग्रहीत करते हैं। स्ट्रिंग क्लास में स्ट्रिंग्स के साथ काम करने के लिए आवश्यक सभी या लगभग सभी चीजें होती हैं। इस तथ्य के बारे में सोचने की ज़रूरत नहीं है कि लाइन को जरूरी रूप से शून्य के साथ समाप्त होना चाहिए, यहां यह असंभव है कि यह शून्य समाप्ति वर्ण या लाइन से परे मेमोरी तक "मिटा" करना असंभव है। सामान्य तौर पर, जब जावा में तार के साथ काम करते हैं, तो प्रोग्रामर यह नहीं सोचता है कि स्ट्रिंग को मेमोरी में कैसे संग्रहीत किया जाता है।

जैसा कि ऊपर उल्लेख किया गया है, जावा ऑपरेटर को ओवरलोडिंग (सी ++ में) की अनुमति नहीं देता है, हालांकि, स्ट्रिंग वर्ग एक अपवाद है - केवल इसके लिए लाइन "+" और "+ =" को मर्ज करते हैं, शुरू में ओवरलोड होते हैं।

 String str1 = "Hello, " + "World!"; String str2 = "Hello, "; str2 += "World!"; 

यह उल्लेखनीय है कि जावा में तार अपरिवर्तनीय हैं - एक बार निर्मित होने के बाद, वे अपने परिवर्तन की अनुमति नहीं देते हैं। जब हम लाइन को बदलने की कोशिश करते हैं, उदाहरण के लिए, इस तरह:

 String str = "Hello, World!"; str.toUpperCase(); System.out.println(str); //   "Hello, World!" 

तो मूल स्ट्रिंग वास्तव में नहीं बदलती है। इसके बजाय, मूल स्ट्रिंग की एक संशोधित प्रति बनाई गई है, जो बदले में अपरिवर्तनीय भी है:

 String str = "Hello, World!"; String str2 = str.toUpperCase(); System.out.println(str2); //   "HELLO, WORLD!" 

इस प्रकार, वास्तविकता में एक स्ट्रिंग के प्रत्येक परिवर्तन के परिणामस्वरूप एक नई वस्तु का निर्माण होता है (वास्तव में, विलय के तार के मामलों में, कंपाइलर कोड को अनुकूलित कर सकता है और स्ट्रिंगबर्स्ट क्लास का उपयोग कर सकता है, जिसे बाद में चर्चा की जाएगी)।

ऐसा होता है कि प्रोग्राम को अक्सर एक ही पंक्ति को बदलने की आवश्यकता होती है। ऐसे मामलों में, प्रोग्राम की गति और मेमोरी की खपत को अनुकूलित करने के लिए, आप नई पंक्ति वस्तुओं के निर्माण को रोक सकते हैं। इन उद्देश्यों के लिए, StringBuilder वर्ग का उपयोग किया जाना चाहिए:

 String sourceString = "Hello, World!"; StringBuilder builder = new StringBuilder(sourceString); builder.setCharAt(4, '0'); builder.setCharAt(8, '0'); builder.append("!!"); String changedString = builder.toString(); System.out.println(changedString); //   "Hell0, W0rld!!!" 

अलग-अलग, यह तार की तुलना का उल्लेख करने योग्य है। एक नौसिखिए जावा प्रोग्रामर की एक विशिष्ट गलती "==" ऑपरेटर का उपयोग करते हुए तार की तुलना कर रही है:

 //    "Yes" // ! if (usersInput == "Yes") { //    } 

इस तरह के कोड में औपचारिक रूप से संकलन चरण या रनटाइम त्रुटियों में त्रुटि नहीं होती है, लेकिन यह अपेक्षा से अलग काम करता है। चूंकि जावा में सभी वस्तुओं और तारों को लिंक द्वारा दर्शाया गया है, इसलिए "==" ऑपरेटर के साथ तुलना करना लिंक की तुलना देता है, वस्तुओं का मूल्य नहीं। यही है, परिणाम केवल तभी सच होगा जब 2 लिंक एक ही पंक्ति को संदर्भित करते हैं। यदि तार मेमोरी में अलग-अलग ऑब्जेक्ट हैं, और आपको उनकी सामग्री की तुलना करने की आवश्यकता है, तो आपको बराबर () विधि का उपयोग करने की आवश्यकता है:

 if (usersInput.equals("Yes")) { //    } 

सबसे आश्चर्यजनक बात यह है कि कुछ मामलों में, "==" ऑपरेटर के सही उपयोग से तुलना की जाती है:

 String someString = "abc", anotherString = "abc"; //   "true": System.out.println(someString == anotherString); 

ऐसा इसलिए है, क्योंकि वास्तव में, someString और अन्यString स्मृति में समान ऑब्जेक्ट को संदर्भित करते हैं। संकलक स्ट्रिंग स्ट्रिंग में समान स्ट्रिंग शाब्दिक स्थान रखता है - तथाकथित इंटर्नमेंट होता है। फिर हर बार कार्यक्रम में एक ही स्ट्रिंग शाब्दिक दिखाई देता है, पूल से स्ट्रिंग का लिंक उपयोग किया जाता है। स्ट्रिंग्स की अपरिवर्तनीयता के कारण स्ट्रिंग्स का इंटर्नमेंट ठीक संभव है।

हालाँकि स्ट्रिंग की सामग्री की तुलना केवल बराबरी () विधि से की जाती है, जावा में स्विच-केस कंस्ट्रक्शन (जावा 7 से शुरू) में सही ढंग से स्ट्रिंग्स का उपयोग करना संभव है:

 String str = new String(); // ... switch (str) { case "string_value_1": // ... break; case "string_value_2": // ... break; } 

उत्सुकता से, किसी भी जावा ऑब्जेक्ट को एक स्ट्रिंग में परिवर्तित किया जा सकता है। संबंधित कक्षा के सभी वर्गों के लिए आधार श्रेणी में इसी स्ट्रिंग () पद्धति को परिभाषित किया गया है।

दृष्टिकोण को संभालने में त्रुटि


C में प्रोग्रामिंग करते समय, आप निम्नलिखित त्रुटि हैंडलिंग दृष्टिकोण के साथ आ सकते हैं। लाइब्रेरी का प्रत्येक फ़ंक्शन एक इंट प्रकार देता है। यदि फ़ंक्शन सफल है, तो यह परिणाम 0. है। यदि परिणाम नॉनज़रो है, तो यह एक त्रुटि इंगित करता है। सबसे अधिक बार, त्रुटि कोड फ़ंक्शन द्वारा लौटाए गए मूल्य के माध्यम से पारित किया जाता है। चूंकि फ़ंक्शन केवल एक मान वापस कर सकता है, और यह पहले से ही त्रुटि कोड द्वारा कब्जा कर लिया गया है, फ़ंक्शन के वास्तविक परिणाम को एक संकेतक के रूप में तर्क के माध्यम से लौटाया जाना चाहिए, उदाहरण के लिए, जैसे:

 int function(struct Data **result, const char *arg) { int errorCode; /* ... */ return errorCode; } 

वैसे, यह उन मामलों में से एक है जब सी प्रोग्राम में एक पॉइंटर को पॉइंटर का उपयोग करना आवश्यक हो जाता है।

कभी-कभी वे एक अलग दृष्टिकोण का उपयोग करते हैं। फ़ंक्शन एक त्रुटि कोड नहीं लौटाता है, लेकिन सीधे इसके निष्पादन का परिणाम है, आमतौर पर एक पॉइंटर के रूप में। एक त्रुटि स्थिति एक शून्य सूचक के साथ इंगित की जाती है। फिर लाइब्रेरी में आमतौर पर एक अलग फ़ंक्शन होता है जो अंतिम त्रुटि का कोड लौटाता है:

 struct Data* function(const char *arg); int getLastError(); 

एक तरह से या दूसरे, जब सी में प्रोग्रामिंग करते हैं, तो कोड जो "उपयोगी" काम करता है और त्रुटियों को संभालने के लिए जिम्मेदार कोड एक-दूसरे को इंटरलेक्ट करता है, जो स्पष्ट रूप से प्रोग्राम को पढ़ने में आसान नहीं बनाता है।

जावा में, यदि आप चाहें, तो आप ऊपर वर्णित दृष्टिकोण का उपयोग कर सकते हैं, लेकिन यहां आप त्रुटियों से निपटने के लिए पूरी तरह से अलग तरीके से आवेदन कर सकते हैं - अपवाद हैंडलिंग (हालांकि, सी ++ में)। अपवाद से निपटने का लाभ यह है कि इस मामले में "उपयोगी" कोड और त्रुटियों और आकस्मिकताओं से निपटने के लिए जिम्मेदार कोड को तार्किक रूप से एक दूसरे से अलग किया जाता है।

इसे ट्राइ-कैच कंस्ट्रक्शन का उपयोग करके प्राप्त किया जाता है: "उपयोगी" कोड को ट्राइ सेक्शन में रखा गया है, और एरर हैंडलिंग कोड को कैच सेक्शन में रखा गया है।

 //       try (FileReader reader = new FileReader("path\\to\\file.txt")) { //    -   . while (reader.read() != -1){ // ... } } catch (IOException ex) { //     } 

ऐसी स्थितियां हैं जब त्रुटि की सही ढंग से इसकी घटना के स्थान पर प्रक्रिया करना संभव नहीं है। ऐसे मामलों में, एक संकेत विधि हस्ताक्षर में रखा जाता है कि विधि इस प्रकार के अपवाद का कारण बन सकती है:

 public void func() throws Exception { // ... } 

अब इस पद्धति के लिए कॉल को आवश्यक रूप से कोशिश करने वाले ब्लॉक में तैयार किया जाना चाहिए, या कॉलिंग विधि को भी चिह्नित किया जाना चाहिए कि यह इस अपवाद को फेंक सकता है।

प्रीप्रोसेसर की कमी


कोई फर्क नहीं पड़ता कि C / C ++ प्रोग्रामर से परिचित प्रीप्रोसेसर कितना सुविधाजनक है, यह जावा भाषा में अनुपस्थित है। जावा डेवलपर्स ने शायद फैसला किया कि इसका उपयोग केवल कार्यक्रमों की पोर्टेबिलिटी सुनिश्चित करने के लिए किया जाता है, और चूंकि जावा हर जगह (लगभग) चलता है, इसलिए इसमें प्रीप्रोसेसर की आवश्यकता नहीं है।

आप एक स्थिर फ्लैग फ़ील्ड का उपयोग करके प्रीप्रोसेसर की कमी के लिए क्षतिपूर्ति कर सकते हैं और कार्यक्रम में इसके मूल्य की जांच कर सकते हैं, जहां आवश्यक हो।

यदि हम परीक्षण के संगठन के बारे में बात कर रहे हैं, तो प्रतिबिंब (प्रतिबिंब) के साथ संयोजन में एनोटेशन का उपयोग करना संभव है।

एक सरणी भी एक वस्तु है।


सी में सरणियों के साथ काम करते समय, सरणी की सीमाओं से परे सूचकांक बाहर निकलना एक बहुत ही कपटी गलती है। कंपाइलर इसे किसी भी तरह से रिपोर्ट नहीं करेगा, और निष्पादन के दौरान प्रोग्राम को संबंधित संदेश के साथ रोका नहीं जाएगा:

 int array[5]; array[6] = 666; 

सबसे अधिक संभावना है, कार्यक्रम निष्पादन जारी रखेगा, लेकिन ऊपर दिए गए उदाहरण में सरणी सरणी के बाद स्थित चर का मूल्य विकृत हो जाएगा। इस तरह की त्रुटि को डीबग करना आसान नहीं हो सकता है।

जावा में, प्रोग्रामर को इस तरह की हार्ड-टू-डायग्नोसिस त्रुटियों से सुरक्षित किया जाता है। जब आप सरणी के सीमा से आगे जाने की कोशिश करते हैं, तो एक ArrayIndexOutOfBoundsException को फेंक दिया जाता है। यदि अपवाद पकड़ने का प्रयास ट्राइ-कैच निर्माण का उपयोग करके नहीं किया गया था, तो प्रोग्राम क्रैश हो जाता है और एक समान संदेश मानक त्रुटि स्ट्रीम में भेजा जाता है जो फ़ाइल को स्रोत कोड और लाइन संख्या के साथ इंगित करता है जहां सरणी पार हो गई थी। यही है, ऐसी त्रुटियों का निदान एक तुच्छ मामला बन जाता है।

जावा प्रोग्राम का यह व्यवहार संभव है क्योंकि जावा में सरणी किसी ऑब्जेक्ट द्वारा दर्शाई गई है। जिस समय मेमोरी आबंटित होती है, तब जावा सरणी को आकार नहीं दिया जा सकता है। रन समय में, सरणी का आकार प्राप्त करना उतना ही सरल है:

 int[] array = new int[10]; int arraySize = array.length; // 10 

अगर हम बहुआयामी सरणियों के बारे में बात करते हैं, तो, सी भाषा की तुलना में, जावा "सीढ़ी" सरणियों को व्यवस्थित करने का एक दिलचस्प अवसर प्रदान करता है। द्वि-आयामी सरणी के मामले के लिए, प्रत्येक अलग-अलग पंक्ति का आकार बाकी से भिन्न हो सकता है:

 int[][] array = new int[10][]; for (int i = 0; i < array.length; i++) { array[i] = new int[i + 1]; } 

जैसे सी में, एरे के तत्व एक-एक करके मेमोरी में स्थित होते हैं, इसलिए एरे तक पहुंच को सबसे कुशल माना जाता है। यदि आपको तत्वों के डालने / हटाने के संचालन की आवश्यकता है, या अधिक जटिल डेटा संरचनाएं बनाना है, तो आपको संग्रह (जैसे) सेट (सूची), एक सूची (सूची), एक मानचित्र (मानचित्र) का उपयोग करने की आवश्यकता है।

पॉइंटर्स की कमी और इंक्रीमेंट लिंक की अक्षमता के कारण, अनुक्रमित का उपयोग करके सरणी के तत्वों तक पहुंच संभव है।

संग्रह


अक्सर सरणियों की कार्यक्षमता पर्याप्त नहीं है - फिर आपको गतिशील डेटा संरचनाओं का उपयोग करने की आवश्यकता है। चूंकि मानक सी लाइब्रेरी में गतिशील डेटा संरचनाओं का एक तैयार-निर्मित कार्यान्वयन शामिल नहीं है, इसलिए आपको स्रोत कोड में या पुस्तकालयों के रूप में कार्यान्वयन का उपयोग करना होगा।

सी के विपरीत, मानक जावा लाइब्रेरी में जावा के संदर्भ में व्यक्त गतिशील डेटा संरचनाओं या संग्रह के कार्यान्वयन का एक समृद्ध सेट होता है। सभी संग्रह को 3 बड़े वर्गों में विभाजित किया गया है: सूची, सेट और नक्शे।

सूचियाँ - डायनेमिक सरणियाँ - आपको आइटम जोड़ने / निकालने की अनुमति देती हैं। कई जोड़े गए तत्वों के क्रम को सुनिश्चित नहीं करते हैं, लेकिन गारंटी देते हैं कि कोई डुप्लिकेट तत्व नहीं हैं। कार्ड या सहयोगी सरणियाँ कुंजी-मूल्य जोड़े के साथ काम करते हैं, और कुंजी मूल्य अद्वितीय है - कार्ड में एक ही कुंजी के साथ 2 जोड़े नहीं हो सकते।

सूचियों, सेटों और मानचित्रों के लिए, कई कार्यान्वयन हैं, जिनमें से प्रत्येक को एक विशिष्ट ऑपरेशन के लिए अनुकूलित किया गया है। उदाहरण के लिए, सूची को ArrayList और LinkedList वर्गों द्वारा कार्यान्वित किया जाता है, ArrayList द्वारा मनमाना तत्व तक पहुँचने पर बेहतर प्रदर्शन प्रदान करने के लिए, और LinkedList सूची के मध्य में तत्वों को सम्मिलित / हटाते समय अधिक कुशल होता है।

केवल पूर्ण जावा वस्तुओं को संग्रह में संग्रहीत किया जा सकता है (वास्तव में, वस्तुओं के संदर्भ में), इसलिए सीधे आदिम (इंट, चार, बाइट, आदि) का संग्रह बनाना असंभव है। इस मामले में, उपयुक्त आवरण वर्गों का उपयोग किया जाना चाहिए:
आदिमरैपिंग क्लास
बाइटबाइट
कमकम
चारचरित्र
पूर्णांकपूर्णांक
लंबे समय तकलंबे समय तक
नावफ्लोट
दोहरादोहरा
बूलियनबूलियन

सौभाग्य से, जावा में प्रोग्रामिंग करते समय, आदिम प्रकार के सटीक संयोग और इसके "आवरण" का पालन करने की आवश्यकता नहीं है। यदि विधि को एक तर्क प्राप्त होता है, उदाहरण के लिए, इंटेगर का प्रकार, तो इसे टाइप इंट पास किया जा सकता है। और इसके विपरीत, जहां इंट प्रकार की आवश्यकता होती है, आप सुरक्षित रूप से इंटेगर का उपयोग कर सकते हैं। यह संभव बनाया गया था कि आदिम पैकिंग के लिए जावा अंतर्निहित तंत्र के लिए धन्यवाद।

अप्रिय क्षणों में, यह उल्लेख किया जाना चाहिए कि मानक जावा लाइब्रेरी में पुरानी संग्रह कक्षाएं शामिल हैं जो जावा के पहले संस्करणों में असफल रूप से लागू की गई थीं और जिनका उपयोग नए कार्यक्रमों में नहीं किया जाना चाहिए। ये वर्ग गणना, सदिश, ढेर, शब्दकोश, हैशटेबल, गुण हैं।

सामान्यीकरण


. , , , ArrayList, -, :

 List<Integer> list = new ArrayList<Integer>(); 

, -:

 List<Integer> list = new ArrayList<Integer>(); //  ! list.add("First"); 

, - , , ,
 ArrayList<Integer> 
 ArrayList<String>. 
:

 public boolean containsInteger(List list) { //  ! if (list instanceof List<Integer>) { return true; } return false; } 

: :

 public boolean containsInteger(List list) { if (!list.isEmpty() && list.get(0) instanceof Integer) { return true; } return false; } 

लेकिन सूची खाली होने पर यह दृष्टिकोण काम नहीं करेगा।

इस संबंध में, जावा सामान्यीकरण C ++ सामान्यीकरण से काफी कम हैं। जावा सामान्यीकरण वास्तव में संकलन चरण में कुछ संभावित त्रुटियों को "काट" करना है।

किसी सरणी या संग्रह के सभी तत्वों पर Iterate करें


C में प्रोग्रामिंग करते समय, आपको अक्सर सरणी के सभी तत्वों पर चलना होगा:

 for (int i = 0; i < SIZE; i++) { /* ... */ } 

यहाँ एक गलती करना सरल है, बस SIZE सरणी का गलत आकार निर्दिष्ट करें या "<" के बजाय "<=" डालें।

जावा में, कथन के "सामान्य" रूप के अलावा, किसी सरणी या संग्रह के सभी तत्वों पर पुनरावृत्ति करने के लिए एक रूप है (जिसे अक्सर अन्य भाषाओं में फ़ॉरच कहा जाता है):

 List<Integer> list = new ArrayList<>(); // ... for (Integer i : list) { // ... } 

यहां हमें सूची के सभी तत्वों के माध्यम से सॉर्ट करने की गारंटी दी गई है, कथन के "सामान्य" रूप में निहित त्रुटियों को समाप्त कर दिया गया है।

विविध संग्रह


चूंकि सभी ऑब्जेक्ट रूट ऑब्जेक्ट से विरासत में मिले हैं, इसलिए जावा में विभिन्न वास्तविक प्रकार के तत्वों के साथ सूची बनाने का एक दिलचस्प अवसर है:

 List list = new ArrayList<>(); list.add(new String("First")); list.add(new Integer(2)); list.add(new Double(3.0));         instanceof: for (Object o : list) { if (o instanceof String) { // ... } else if (o instanceof Integer) { // ... } else if (o instanceof Double) { // ... } } 

स्थानांतरण


सी / सी ++ और जावा की तुलना में, यह नोटिस करना असंभव नहीं है कि जावा में कितने अधिक कार्यात्मक गणना लागू हैं। यहाँ एन्यूमरेशन एक पूर्ण विकसित वर्ग है, और एन्यूमरेशन तत्व इस क्लास की वस्तुएं हैं। यह एक गणन तत्व को पत्राचार में किसी भी प्रकार के कई क्षेत्रों को सेट करने की अनुमति देता है:

 enum Colors { //     -   . RED ((byte)0xFF, (byte)0x00, (byte)0x00), GREEN ((byte)0x00, (byte)0xFF, (byte)0x00), BLUE ((byte)0x00, (byte)0x00, (byte)0xFF), WHITE ((byte)0xFF, (byte)0xFF, (byte)0xFF), BLACK ((byte)0x00, (byte)0x00, (byte)0x00); //  . private byte r, g, b; //  . private Colors(byte r, byte g, byte b) { this.r = r; this.g = g; this.b = b; } //  . public double getLuma() { return 0.2126 * r + 0.7152 * g + 0.0722 * b; } } 

एक पूर्ण वर्ग के रूप में, एक एन्यूमरेशन में विधियां हो सकती हैं, और एक निजी कंस्ट्रक्टर का उपयोग करके, आप व्यक्तिगत एन्यूमरेशन तत्वों के क्षेत्र मूल्यों को सेट कर सकते हैं।

एक संलयन तत्व, एक सीरियल नंबर, साथ ही सभी तत्वों की एक सरणी का एक स्ट्रिंग प्रतिनिधित्व प्राप्त करने का एक नियमित अवसर है:

 Colors color = Colors.BLACK; String str = color.toString(); // "BLACK" int i = color.ordinal(); // 4 Colors[] array = Colors.values(); // [RED, GREEN, BLUE, WHITE, BLACK] 

और इसके विपरीत - स्ट्रिंग प्रतिनिधित्व द्वारा, आप एक गणना तत्व प्राप्त कर सकते हैं, और इसके तरीकों को भी कॉल कर सकते हैं:

 Colors red = Colors.valueOf("RED"); // Colors.RED Double redLuma = red.getLuma(); // 0.2126 * 255 

स्वाभाविक रूप से, एन्यूमरेशन का उपयोग स्विच-केस कंस्ट्रक्शन में किया जा सकता है।

निष्कर्ष


बेशक, सी और जावा भाषाओं को पूरी तरह से अलग-अलग समस्याओं को हल करने के लिए डिज़ाइन किया गया है। लेकिन, अगर हम इन दो भाषाओं में सॉफ्टवेयर विकास प्रक्रिया की तुलना करते हैं, तो, लेखक के व्यक्तिपरक छापों के अनुसार, जावा भाषा लेखन कार्यक्रमों की सुविधा और गति में C को काफी पीछे छोड़ देती है। विकास वातावरण (IDE) सुविधा प्रदान करने में महत्वपूर्ण भूमिका निभाता है। लेखक ने IntelliJ IDEA IDE के साथ काम किया। जावा में प्रोग्रामिंग करते समय, आपको गलती करने के लिए लगातार "डर" होना चाहिए - अक्सर विकास का माहौल आपको बताएगा कि क्या तय करने की आवश्यकता है, और कभी-कभी यह आपके लिए ऐसा करेगा। यदि कोई रनटाइम त्रुटि हुई, तो स्रोत कोड में त्रुटि का प्रकार और उसकी घटना का स्थान हमेशा लॉग में इंगित किया जाता है - ऐसी त्रुटियों के खिलाफ लड़ाई एक तुच्छ मामला बन जाता है। एक सी प्रोग्रामर को जावा पर स्विच करने के लिए अमानवीय प्रयास करने की आवश्यकता नहीं है, और सभी क्योंकि भाषा का सिंटैक्स थोड़ा बदल गया है।

यदि यह अनुभव पाठकों के लिए दिलचस्प होगा, तो अगले लेख में हम जेएनआई तंत्र (जावा एप्लिकेशन से देशी सी / सी ++ कोड चलाने) के अनुभव के बारे में बात करेंगे। जेएनआई तंत्र अपरिहार्य है जब आप स्क्रीन रिज़ॉल्यूशन, ब्लूटूथ मॉड्यूल और अन्य मामलों में जब एंड्रॉइड सेवाओं और प्रबंधकों की क्षमता पर्याप्त नहीं है, को नियंत्रित करना चाहते हैं।

Source: https://habr.com/ru/post/hi412775/


All Articles