
تعكس المقالة التجربة الشخصية للمؤلف - مبرمج متحكم متحمس ، والذي ، بعد سنوات عديدة من الخبرة في تطوير المتحكم الدقيق في C (وقليلًا في C ++) ، أتيحت له الفرصة للمشاركة في مشروع Java رئيسي لتطوير برامج لأجهزة فك التشفير التلفزيونية التي تعمل بنظام Android. خلال هذا المشروع ، تمكنت من جمع ملاحظات حول الاختلافات المثيرة للاهتمام بين لغات Java و C / C ++ ، وتقييم الأساليب المختلفة لكتابة البرامج. لا تدعي المقالة أنها مرجع ، ولا تدرس كفاءة وإنتاجية برامج Java. بل هي مجموعة من الملاحظات الشخصية. هذا هو إصدار Java SE 7 ما لم يتم تحديد خلاف ذلك.
فروق الجملة وبنيات التحكم
باختصار - الاختلافات ضئيلة ، فإن البنية متشابهة للغاية. تتكون كتل الكود أيضًا من زوج من الأقواس المتعرجة {}. قواعد تجميع المعرّفات هي نفسها قواعد C / C ++. قائمة الكلمات الرئيسية هي نفسها تقريبًا في C / C ++. أنواع البيانات المدمجة - مماثلة لتلك الموجودة في C / C ++. المصفوفات - يتم أيضًا الإعلان عن جميع الأقواس باستخدام الأقواس المربعة.
يقوم عنصر التحكم بإنشاء ما إذا كان التبديل متطابقًا تمامًا تقريبًا. من الجدير بالذكر أنه في Java كانت هناك تسميات مألوفة للمبرمجين C (تلك التي تستخدم مع الكلمة الأساسية goto والتي يتم تثبيط استخدامها بشدة). ومع ذلك ، استبعدت Java إمكانية التبديل إلى تسمية باستخدام goto. يجب استخدام التصنيفات فقط للخروج من الحلقات المتداخلة:
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; } }
لتحسين إمكانية قراءة البرامج في Java ، تمت إضافة فرصة مثيرة للاهتمام لفصل أجزاء الأرقام الطويلة بشرطة سفلية:
int value1 = 1_500_000; long value2 = 0xAA_BB_CC_DD;
خارجياً ، لا يختلف برنامج Java كثيرًا عن برنامج C المألوف. الاختلاف البصري الرئيسي هو أن Java لا تسمح بالوظائف والمتغيرات وتعريفات الأنواع الجديدة (الهياكل) والثوابت وما إلى ذلك ، التي توجد "بحرية" في الملف المصدر. Java هي لغة موجهة للكائنات ، لذلك يجب أن تنتمي جميع كيانات البرنامج إلى فئة معينة. فرق كبير آخر هو عدم وجود معالج مسبق. يتم وصف هذين الاختلافين بمزيد من التفصيل أدناه.
نهج الكائن في لغة C
عندما نكتب برامج كبيرة في لغة C ، يجب علينا العمل مع الكائنات. يتم تنفيذ دور الكائن هنا بواسطة هيكل يصف جوهرًا معينًا لـ "العالم الحقيقي":
أيضا في C هناك طرق لمعالجة "الكائنات" - هياكل - وظائف. ومع ذلك ، لا يتم دمج الوظائف بشكل أساسي مع البيانات. نعم ، عادة ما يتم وضعها في ملف واحد ، ولكن في كل مرة يكون من الضروري تمرير مؤشر إلى الكائن ليتم معالجته في الوظيفة "النموذجية":
int process(struct Data *ptr, int arg1, const char *arg2) { return result_code; }
يمكنك استخدام "الكائن" فقط بعد تخصيص الذاكرة لتخزينه:
Data *data = malloc(sizeof(Data));
في برنامج C ، عادة ما يتم تحديد دالة تكون مسؤولة عن تهيئة "الكائن" قبل استخدامه لأول مرة:
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);
نسرد الآن أخطاء وقت التشغيل المحتملة التي يمكن أن يرتكبها المبرمج في دورة حياة "الكائن":
- ننسى لتخصيص ذاكرة "الكائن"
- حدد المقدار الخاطئ للذاكرة المخصصة
- ننسى تهيئة "الكائن"
- نسيان تحرير الذاكرة بعد استخدام الكائن
قد يكون من الصعب للغاية اكتشاف مثل هذه الأخطاء ، حيث لم يتم اكتشافها من قبل المترجم وتظهر أثناء تشغيل البرنامج. علاوة على ذلك ، يمكن أن يكون تأثيرها متنوعًا جدًا ويؤثر على المتغيرات الأخرى و "كائنات" البرنامج.
نهج كائن جافا
في مواجهة OOP - البرمجة الموجهة للكائنات ، ربما سمعت عن أحد الحيتان OOP - التغليف. في Java ، على عكس C ، يتم دمج البيانات وطرق معالجتها معًا وهي كائنات "حقيقية". من حيث OOP ، يسمى هذا التغليف. الفئة هي وصف لكائن ، وأقرب نظير لفئة في C هو تحديد نوع جديد باستخدام بنية typedef. في مصطلحات Java ، تسمى تلك الدوال التي تنتمي إلى فئة أساليب.
تستند أيديولوجية لغة جافا إلى عبارة "كل شيء هو كائن". لذلك ، ليس من المستغرب أن تحظر Java إنشاء كل من الأساليب (الوظائف) وحقول البيانات (المتغيرات) بشكل منفصل عن الفصل. حتى الأسلوب الرئيسي () المألوف ، الذي يبدأ منه البرنامج ، يجب أن ينتمي إلى إحدى الفئات.
يشبه تعريف الفئة في Java تعريف البنية في C. عن طريق وصف فئة ، لا تقوم بإنشاء أي شيء في الذاكرة. يظهر كائن من هذه الفئة في وقت إنشائه بواسطة عامل التشغيل الجديد. إنشاء كائن في Java هو تناظري لتخصيص الذاكرة في لغة C ، ولكن ، على عكس الأخيرة ، يتم استدعاء طريقة خاصة تلقائيًا أثناء إنشاء الكائن - مُنشئ الكائن. يأخذ المنشئ دور التهيئة الأولية للكائن - وهو تناظري لوظيفة init () التي تمت مناقشتها سابقًا. يجب أن يتطابق اسم المنشئ مع اسم الفئة. لا يمكن للمنشئ إرجاع قيمة.
دورة حياة كائن في برنامج Java هي كما يلي:
لاحظ أن عدد الأخطاء المحتملة في برنامج Java أصغر بكثير مما هو عليه في برنامج C. نعم ، لا يزال بإمكانك نسيان إنشاء كائن قبل الاستخدام الأول (والذي ، مع ذلك ، سيؤدي إلى تصحيح أخطاء NullPointerException بسهولة) ، ولكن بالنسبة للأخطاء الأخرى المتأصلة برامج C ، الوضع يتغير جذريا:
- لا يوجد عامل sizeof () في Java. يحسب مترجم Java نفسه مقدار الذاكرة لتخزين الكائن. لذلك ، لا يمكن تحديد الحجم الخاطئ للتحديد.
- تتم تهيئة الكائن في وقت الإنشاء. من المستحيل نسيان التهيئة.
- لا يلزم تحرير الذاكرة التي يشغلها الكائن ؛ يقوم جامع القمامة بهذا العمل. من المستحيل نسيان حذف كائن بعد الاستخدام - هناك احتمال أقل لتأثير "تسرب الذاكرة".
لذلك ، كل شيء في جافا هو كائن من فئة أو أخرى. الاستثناءات هي بدائية تمت إضافتها إلى اللغة لتحسين الأداء واستهلاك الذاكرة. المزيد عن البدائيين أدناه.
جامع الذاكرة والقمامة
تحتفظ Java بالمفاهيم المألوفة في كومة الذاكرة المؤقتة والمكدس لـ C / C ++ ، مبرمج. عند إنشاء كائن باستخدام عامل التشغيل الجديد ، يتم استعارة ذاكرة تخزين الكائن من كومة الذاكرة المؤقتة. ومع ذلك ، فإن الارتباط بكائن (الارتباط هو تناظري للمؤشر) ، إذا كان الكائن الذي تم إنشاؤه ليس جزءًا من كائن آخر ، يتم وضعه على المكدس. في كومة الذاكرة المؤقتة يتم تخزين "أجسام" الكائنات ، وعلى المكدس متغيرات محلية: مراجع للكائنات وأنواع بدائية. إذا كان كومة الذاكرة المؤقتة موجودة أثناء تنفيذ البرنامج وكان متاحًا لجميع سلاسل عمليات البرنامج ، فإن المكدس ينتمي إلى الطريقة ولا يكون موجودًا إلا أثناء تنفيذه ، كما أنه لا يمكن الوصول إلى سلاسل الرسائل الأخرى للبرنامج.
Java غير ضرورية وأكثر من ذلك - لا يمكنك تحرير الذاكرة التي يشغلها كائن يدويًا. يتم هذا العمل من قبل جامع القمامة في الوضع التلقائي. يراقب وقت التشغيل ما إذا كان من الممكن الوصول إلى كل كائن في كومة الذاكرة المؤقتة من الموقع الحالي للبرنامج باتباع الارتباطات من كائن إلى كائن. إذا لم يكن الأمر كذلك ، فسيتم التعرف على هذا الكائن على أنه "قمامة" ويصبح مرشحًا للحذف.
من المهم أن نلاحظ أن الحذف نفسه لا يحدث في الوقت الذي لم يعد فيه الكائن مطلوبًا - يقرر جامع القمامة الحذف ، ويمكن أن يتأخر الحذف بقدر الضرورة حتى ينتهي البرنامج.
بطبيعة الحال ، فإن عمل جامع القمامة يتطلب معالجة علوية. ولكن في المقابل ، يخفف مبرمج الصداع الكبير المرتبط بالحاجة إلى تحرير الذاكرة بعد انتهاء استخدام "الأشياء". في الواقع ، "نأخذ" الذاكرة عندما نحتاجها ونستخدمها ، دون التفكير في أننا بحاجة إلى تحريرها بعد أنفسنا.
بالحديث عن المتغيرات المحلية ، يجب أن نتذكر نهج Java في التهيئة. إذا كان متغير محلي غير مهيأ في C / C ++ يحتوي على قيمة عشوائية ، فلن يسمح المترجم Java ببساطة بتركه غير مهيأ:
int i;
الروابط - مؤشرات الاستبدال
لا تحتوي Java على مؤشرات ؛ وفقًا لذلك ، لا يمتلك مبرمج Java القدرة على ارتكاب أحد الأخطاء العديدة التي تحدث عند العمل مع المؤشرات. عند إنشاء كائن ، تحصل على ارتباط لهذا الكائن:
في لغة C ، كان للمبرمج خيار: كيف يمرر ، على سبيل المثال ، بنية إلى دالة. يمكنك المرور بالقيمة:
يضمن المرور بالقيمة أن الوظيفة لن تغير البيانات في البنية ، ولكنها كانت غير فعالة من حيث الأداء - في وقت استدعاء الوظيفة ، تم إنشاء نسخة من البنية. يعد المرور عبر المؤشر أكثر كفاءة: في الواقع ، تم تمرير العنوان الموجود في الذاكرة حيث يوجد الهيكل إلى الوظيفة.
في Java ، كانت هناك طريقة واحدة فقط لتمرير كائن إلى طريقة - حسب المرجع. يُعد التمرير حسب المرجع في Java بمثابة التمرير عبر المؤشر في لغة C:
- لا تحدث ذاكرة النسخ (الاستنساخ) ،
- في الواقع ، يتم إرسال عنوان موقع هذا الكائن.
ومع ذلك ، على عكس مؤشر لغة C ، لا يمكن زيادة / تقليل ارتباط Java. لن يعمل "الجري" من خلال عناصر المصفوفة باستخدام ارتباط به في Java. كل ما يمكن فعله برابط هو إعطائه قيمة مختلفة.
بالطبع ، يؤدي عدم وجود مؤشرات على هذا النحو إلى تقليل عدد الأخطاء المحتملة ، ومع ذلك ، يبقى المؤشر التماثلي في اللغة - مرجعًا فارغًا يُشار إليه بالكلمة المفتاحية الخالية.
مرجع فارغ هو صداع لمبرمج جافا ، مثل يفرض مرجع الكائن إما للتحقق من وجود قيمة خالية قبل استخدامه أو لمعالجة استثناءات NullPointerException. إذا لم يتم ذلك ، فسوف يتعطل البرنامج.
لذلك ، يتم تمرير جميع الكائنات في جافا من خلال الروابط. يتم تمرير أنواع البيانات البدائية (int ، طويلة ، char ...) حسب القيمة (المزيد عن البدائيين أدناه).
ميزات Java Link
الوصول إلى أي كائن في البرنامج من خلال رابط - من الواضح أن هذا له تأثير إيجابي على الأداء ، ولكنه قد يفاجئ مبتدئًا:
وسيطات الأسلوب وقيم الإرجاع - يتم تمرير كل شيء من خلال الارتباط. بالإضافة إلى المزايا ، هناك عيب مقارنة بلغات C / C ++ ، حيث يمكننا منع الوظائف بشكل صريح من تغيير القيمة التي يتم تمريرها من خلال المؤشر باستخدام مؤهل ثابت:
void func(const struct Data* data) {
أي أن لغة C تسمح لك بتتبع هذا الخطأ في مرحلة التجميع. تحتوي Java أيضًا على كلمة أساسية ثابتة ، ولكنها محجوزة للإصدارات المستقبلية ولا يتم استخدامها حاليًا على الإطلاق. إلى حد ما ، الكلمة الرئيسية النهائية مطلوبة لأداء دورها. ومع ذلك ، فإنه لا يحمي الكائن الذي تم تمريره إلى الطريقة من التغييرات:
public class Main { void func(final Entity data) {
الشيء هو أن الكلمة الأساسية النهائية في هذه الحالة يتم تطبيقها على الرابط ، وليس على الكائن الذي يشير إليه الرابط. إذا تم تطبيق النهائي على البدائي ، فإن المترجم يتصرف كما هو متوقع:
void func(final int value) {
روابط جافا تشبه إلى حد كبير روابط لغة C ++.
بدائيات جافا
يحتوي كل كائن Java ، بالإضافة إلى حقول البيانات ، على معلومات داعمة. إذا أردنا أن نعمل ، على سبيل المثال ، في وحدات بايت منفصلة ويمثل كل بايت كائن ، ثم في حالة صفيف من وحدات البايت ، يمكن أن يتجاوز الحمل الزائد للذاكرة عدة مرات الحجم القابل للاستخدام.
لكي تظل Java فعالة بما يكفي في الحالات الموضحة أعلاه ، تمت إضافة دعم للأنواع البدائية - البدائية - إلى اللغة.
بدائي | عرض | عمق البت | التناظرية الممكنة في C. |
---|
بايت | عدد صحيح | 8 | حرف |
قصير | 16 | قصير |
حرف | 16 | wchar_t |
عدد | 32 | int (طويل) |
طويل | 64 | طويل |
تعويم | أرقام الفاصلة العائمة | 32 | تعويم |
مزدوج | | 64 | مزدوج |
منطقي | منطقي | - | int (C89) / bool (C99) |
جميع البدائيين لديهم نظائرهم في لغة C. ومع ذلك ، فإن معيار C لا يحدد الحجم الدقيق للأنواع الصحيحة ؛ بدلاً من ذلك ، يتم إصلاح نطاق القيم التي يمكن لهذا النوع تخزينها. غالبًا ما يرغب المبرمج في ضمان نفس عمق البت للأجهزة المختلفة ، مما يؤدي إلى ظهور أنواع مثل uint32_t في البرنامج ، على الرغم من أن جميع وظائف المكتبة تتطلب وسيطات من النوع int.
هذه الحقيقة لا يمكن أن تعزى إلى مزايا اللغة.
بدائية الأعداد الصحيحة لـ Java ، على عكس C ، لها أعماق بت ثابتة. وبالتالي ، لا داعي للقلق بشأن عمق البت الفعلي للجهاز الذي يعمل عليه برنامج Java ، بالإضافة إلى ترتيب البايت ("الشبكة" أو "Intel"). تساعد هذه الحقيقة على إدراك مبدأ "أنه مكتوب مرة واحدة - يتم تحقيقه في كل مكان".
بالإضافة إلى ذلك ، في Java يتم توقيع جميع الأعداد الأولية الصحيحة (تفتقر اللغة إلى الكلمة الأساسية غير الموقعة). هذا يزيل صعوبة استخدام المتغيرات الموقعة وغير الموقعة في تعبير واحد متأصل في C.
في الختام ، يتم إصلاح ترتيب البايت في بدائيات متعددة البايت في Java (بايت منخفض في عنوان منخفض ، Little-endian ، ترتيب عكسي).
تشمل عيوب تنفيذ العمليات مع البدائية في جافا حقيقة أنه ، كما هو الحال في برنامج C / C ++ ، يمكن أن يحدث تجاوز في شبكة البت ، دون استثناءات:
int i1 = 2_147_483_640; int i2 = 2_147_483_640; int r = (i1 + i2);
لذلك ، يتم تمثيل البيانات في Java بنوعين من الكيانات: الكائنات والبدائية. ينتهك البدائيون مفهوم "كل شيء هو كائن" ، ولكن في بعض الحالات يكونون أكثر فاعلية من عدم استخدامها.
الوراثة
الميراث هو حوت OOP آخر ربما سمعت عنه. إذا أجبت بإيجاز على السؤال "لماذا يكون الميراث ضروريًا على الإطلاق" ، فسيكون الجواب "إعادة استخدام الشفرة".
افترض أن البرنامج في لغة C ، ولديك "فئة" مكتوبة بشكل جيد ومُصححة - وهي بنية ووظائف لمعالجتها. بعد ذلك ، تنشأ الحاجة إلى إنشاء "فئة" مماثلة ، ولكن مع وظائف محسنة ، ولا تزال هناك حاجة إلى "فئة" أساسية. في حالة لغة C ، لديك طريقة واحدة فقط لحل هذه المشكلة - التكوين. يتعلق الأمر بإنشاء بنية موسعة جديدة - "class" ، والتي يجب أن تحتوي على مؤشر إلى بنية "class" الأساسية:
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); }
تسمح لك Java كلغة موجهة للكائنات بتوسيع وظائف الفئات الموجودة باستخدام آلية الوراثة:
وتجدر الإشارة إلى أن جافا لا تحظر بأي حال استخدام التكوين كوسيلة لتوسيع وظائف الفئات المكتوبة بالفعل. علاوة على ذلك ، في العديد من الحالات ، يكون التكوين أفضل من الميراث.
بفضل الميراث ، يتم ترتيب الفصول الدراسية في Java في هيكل هرمي ، وكل فئة تحتوي بالضرورة على "أب" واحد فقط ويمكن أن يكون لها أي عدد من "الأطفال". على عكس C ++ ، لا يمكن لفصل في Java أن يرث من أكثر من أصل واحد (وهذا يحل مشكلة "وراثة الماس").
أثناء الوراثة ، تصل الفئة المشتقة إلى موقعها جميع الحقول والأساليب العامة والمحمية لفئتها الأساسية ، وكذلك الفئة الأساسية لفئتها الأساسية ، وهكذا إلى أعلى التسلسل الهرمي للميراث.
في الجزء العلوي من التسلسل الهرمي للوراثة هو السلف المشترك لجميع فئات Java - فئة الكائن ، الفئة الوحيدة التي ليس لها أصل.
تحديد النوع الديناميكي
واحدة من النقاط الرئيسية في لغة Java هي دعم تحديد النوع الديناميكي (RTTI). بكلمات بسيطة ، يسمح لك RTTI باستبدال كائن من فئة مشتقة حيث يكون المرجع إلى القاعدة مطلوبًا:
من خلال وجود ارتباط في وقت التشغيل ، يمكنك تحديد النوع الحقيقي للكائن الذي يشير إليه الارتباط - باستخدام المثيل العامل:
if (link instanceof Base) {
تجاوز الأسلوب
إعادة تعريف طريقة أو وظيفة يعني استبدال جسمه في مرحلة تنفيذ البرنامج. يدرك المبرمجون C قدرة اللغة على تغيير سلوك الوظيفة أثناء تنفيذ البرنامج. يتعلق الأمر باستخدام مؤشرات الوظائف. على سبيل المثال ، يمكنك تضمين مؤشر لدالة في بنية الهيكل وتعيين وظائف مختلفة للمؤشر لتغيير خوارزمية معالجة البيانات لهذه البنية:
struct Object {
في جافا ، كما هو الحال في لغات OOP الأخرى ، ترتبط طرق الإلغاء ارتباطًا وثيقًا بالوراثة. يمكن للفئة المشتقة الوصول إلى الطرق العامة والمحمية للفئة الأساسية. إلى جانب حقيقة أنه يمكنه الاتصال بهم ، يمكنك تغيير سلوك إحدى طرق الفئة الأساسية دون تغيير توقيعها. للقيام بذلك ، يكفي تعريف طريقة بنفس التوقيع في الفئة المشتقة:
من المهم جدًا أن يتطابق التوقيع تمامًا (اسم الطريقة ، قيمة الإرجاع ، الوسيطات). إذا كان اسم الطريقة مطابقًا ، وتختلف الوسيطات ، فإن الطريقة تزيد من التحميل ، المزيد عن أي منها أدناه.
تعدد الأشكال
مثل التغليف والميراث ، فإن الحوت الثالث OOP - تعدد الأشكال - لديه أيضًا نوعًا من التناظرية في لغة C ذات الوجهة الإجرائية.
لنفترض أن لدينا العديد من "فئات" الهياكل التي تريد تنفيذ نفس النوع من الإجراءات بها ، وأن الوظيفة التي تؤدي هذا الإجراء يجب أن تكون عالمية - يجب أن تكون "قادرة" على العمل مع أي "فئة" كوسيطة.
الحل المحتمل هو كما يلي: 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; } 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; } }
يبدو الحل مرهقًا ، لكن الهدف يتحقق - الوظيفة العامة الشائعة () يقبل () الكائن "لأي فئة" كوسيطة. الشرط المسبق هو بنية "فئة" في الحقل الأول يجب أن يحتوي على معرف يتم من خلاله تحديد "فئة" الكائن الفعلية. هذا الحل ممكن بسبب استخدام الوسيطة مع نوع "void *". ومع ذلك ، يمكن تمرير مؤشر من أي نوع لمثل هذه الدالة ، على سبيل المثال ، "int *". لن يتسبب هذا في أخطاء الترجمة ، ولكن في وقت التشغيل سوف يتصرف البرنامج بشكل غير متوقع.الآن دعونا نرى كيف يبدو تعدد الأشكال في Java (ومع ذلك ، كما هو الحال في أي لغة OOP أخرى). لنفترض أن لدينا العديد من الفئات التي يجب معالجتها بنفس الطريقة بطريقة ما. على عكس حل اللغة C المقدمة أعلاه ، يجب تضمين هذه الطريقة متعددة الأشكال في جميع فئات المجموعة المحددة ، ويجب أن يكون لجميع إصداراتها نفس التوقيع. 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(); }
والآن يمكن تمرير أي فئة وراثة لـ Base (بفضل تعريف النوع الديناميكي) كحجة: executor(new A()); executor(new B()); executor(new C());
اعتمادًا على أي كائن فئة يتم تمريره كوسيطة ، سيتم استدعاء أسلوب ينتمي إلى هذه الفئة.تتيح لك الكلمة الرئيسية المجردة استبعاد نص الطريقة (اجعلها مجردة ، من حيث OOP). في الواقع ، نحن نخبر المترجم أن هذه الطريقة يجب تجاوزها في الفئات المشتقة منها. إذا لم يكن الأمر كذلك ، يحدث خطأ في الترجمة. تسمى الفئة التي تحتوي على طريقة مجردة واحدة على الأقل أيضًا abstract. يتطلب المترجم وضع علامة على هذه الفئات أيضًا باستخدام الكلمة الأساسية abstract.هيكل مشروع جافا
في Java ، تحتوي جميع ملفات المصدر على الامتداد * .java. ملفات رأس * .h والنماذج الأولية للوظائف أو الفئات مفقودة. يجب أن يحتوي كل ملف Java المصدر على فئة واحدة على الأقل. من المعتاد كتابة اسم الفصل ، بدءًا من الحرف الكبير.يمكن دمج عدة ملفات برمز مصدر في حزمة. للقيام بذلك ، يجب استيفاء الشروط التالية:- يجب أن تكون الملفات ذات التعليمات البرمجية المصدر في نفس الدليل في نظام الملفات.
- يجب أن يتطابق اسم هذا الدليل مع اسم الحزمة.
- في بداية كل ملف مصدر ، يجب الإشارة إلى الحزمة التي ينتمي إليها هذا الملف ، على سبيل المثال:
package com.company.pkg;
لضمان تفرد أسماء الحزم داخل الكرة الأرضية ، يُقترح استخدام اسم المجال "المقلوب" للشركة. ومع ذلك ، هذا ليس شرطا ويمكن استخدام أي أسماء في المشروع المحلي.من المستحسن أيضًا تحديد أسماء حزم صغيرة. حتى يمكن تمييزها بسهولة عن أسماء الفئات.إخفاء التنفيذ
جانب آخر من جوانب التغليف هو فصل الواجهة والتنفيذ. إذا كانت الواجهة قابلة للوصول إلى الأجزاء الخارجية من البرنامج (خارج الوحدة أو الفئة) ، فسيكون التطبيق مخفيًا. في الأدبيات ، غالبًا ما يتم رسم تشابه الصندوق الأسود عندما يكون التنفيذ الداخلي "غير مرئي" من الخارج ، ولكن ما يتم إدخاله في إدخال الصندوق وما يعطيه هو "مرئي".في لغة C ، يتم تنفيذ عمليات الإخفاء داخل الوحدة النمطية ، مع وضع علامات على الوظائف التي لا يجب أن تكون مرئية من الخارج باستخدام الكلمة الأساسية الثابتة. يتم وضع النماذج الأولية للوظائف التي تشكل واجهة الوحدة النمطية في ملف الرأس. الوحدة النمطية في C تعني زوجًا: ملف مصدر بامتداد * .c ورأس بامتداد * .h.تحتوي Java أيضًا على الكلمة الأساسية الثابتة ، ولكنها لا تؤثر على "رؤية" الطريقة أو الحقل من الخارج. للتحكم في "الرؤية" ، هناك 3 معدلات وصول: خاصة ومحمية وعامة.لا تتوفر حقول وأساليب فئة تم تمييزها على أنها خاصة فقط بداخلها. الحقول والأساليب المحمية متاحة أيضًا لأحفاد الصف. المعدل العام يعني أن العنصر المميز يمكن الوصول إليه من خارج الفصل ، أي أنه جزء من الواجهة. من الممكن أيضًا أنه لا يوجد معدِّل ، في هذه الحالة يكون الوصول إلى عنصر الفئة محدودًا بالحزمة التي توجد فيها الفئة.من المستحسن عند كتابة فصل دراسي ، وضع علامة مبدئية على جميع حقول الفصل على أنها خاصة وتوسيع حقوق الوصول حسب الضرورة.طريقة الزائد
إحدى الميزات المزعجة في مكتبة C القياسية هي وجود حديقة حيوانات كاملة من الوظائف التي تؤدي نفس الشيء بشكل أساسي ، ولكنها تختلف في نوع الوسيطة ، على سبيل المثال: fabs () و fabsf () و fabsl () - وظائف للحصول على القيمة المطلقة للمضاعفة والعائمة والطويلة أنواع مزدوجة على التوالي.تدعم Java (بالإضافة إلى C ++) آلية التحميل الزائد للأسلوب - قد تكون هناك عدة طرق داخل فئة ذات اسم متطابق تمامًا ، ولكنها تختلف في النوع وعدد الوسائط. من خلال عدد الحجج ونوعها ، سيختار المترجم النسخة الضرورية من الطريقة نفسها - فهي مريحة للغاية وتحسن من سهولة قراءة البرنامج.في Java ، على عكس C ++ ، لا يمكن زيادة التحميل على العوامل. الاستثناء هو العاملان "+" و "+ =" اللذان يتم تحميلهما بشكل مبدئي لسلاسل السلسلة.الشخصيات والجمل في جاوة
في لغة C ، يجب عليك العمل مع سلاسل خالية طرفية ممثلة بمؤشرات إلى الحرف الأول: char *str;
يجب أن تنتهي هذه الأسطر بحرف فارغ. إذا تم "مسح" بطريق الخطأ ، فسيتم اعتبار السلسلة سلسلة من وحدات البايت في الذاكرة حتى الحرف الفارغ الأول. أي أنه إذا تم وضع متغيرات البرنامج الأخرى في الذاكرة بعد السطر ، فعندئذٍ بعد تعديل مثل هذا الخط التالف ، يمكن تشويه قيمها (وعلى الأرجح).بالطبع ، مبرمج C غير ملزم باستخدام سلاسل كلاسيكية خالية من الطرفية ، ولكن تطبيق تطبيق طرف ثالث ، ولكن هنا يجب أن يوضع في الاعتبار أن جميع الوظائف من المكتبة القياسية تتطلب سلاسل خالية من العناصر كحجج لها. بالإضافة إلى ذلك ، لا يحدد المعيار C الترميز المستخدم ، ويجب أيضًا التحكم في هذه النقطة من قبل المبرمج.في Java ، يمثل نوع char البدائي (وكذلك غلاف الأحرف ، حول الأغلفة أدناه) حرفًا واحدًا وفقًا لمعيار Unicode. يتم استخدام ترميز UTF-16 ، على التوالي ، يشغل حرف واحد 2 بايت في الذاكرة ، مما يسمح لك بتشفير جميع أحرف اللغات المستخدمة حاليًا تقريبًا.يمكن تحديد الأحرف بواسطة Unicode الخاص بها: char ch1 = '\u20BD';
إذا تجاوز Unicode لشخصية الحد الأقصى 216 حرفًا ، فيجب تمثيل هذا الحرف بواسطة int. في السلسلة ، ستشغل حرفين من 16 بت ، ولكن مرة أخرى ، نادرًا ما يتم استخدام الأحرف برمز يتجاوز 216.يتم تنفيذ سلاسل Java بواسطة فئة String المضمنة وتخزين أحرف char 16 بت. تحتوي فئة السلسلة على كل أو كل شيء تقريبًا قد يكون مطلوبًا للعمل مع السلاسل. ليست هناك حاجة للتفكير في حقيقة أن الخط يجب أن ينتهي بالضرورة بالصفر ؛ وهنا من المستحيل "مسح" هذه القيمة التي لا تنتهي أو الوصول إلى الذاكرة خارج الخط. بشكل عام ، عند العمل مع السلاسل في Java ، لا يفكر المبرمج في كيفية تخزين السلسلة في الذاكرة.كما ذكر أعلاه ، لا تسمح Java بالحمل الزائد للمشغل (كما هو الحال في C ++) ، ومع ذلك فإن فئة String استثناء - فقط لأنه يتم في البداية تحميل مشغلي دمج السطر "+" و "+ =" بشكل زائد. String str1 = "Hello, " + "World!"; String str2 = "Hello, "; str2 += "World!";
من الجدير بالذكر أن السلاسل في جافا غير قابلة للتغيير - بمجرد إنشائها ، فإنها لا تسمح بتغييرها. عندما نحاول تغيير الخط ، على سبيل المثال ، مثل هذا: String str = "Hello, World!"; str.toUpperCase(); System.out.println(str);
لذلك لا تتغير السلسلة الأصلية في الواقع. بدلاً من ذلك ، يتم إنشاء نسخة معدلة من السلسلة الأصلية ، والتي بدورها غير قابلة للتغيير: String str = "Hello, World!"; String str2 = str.toUpperCase(); System.out.println(str2);
وبالتالي ، يؤدي كل تغيير في سلسلة في الواقع إلى إنشاء كائن جديد (في الواقع ، في حالات دمج السلاسل ، يمكن للمترجم تحسين الشفرة واستخدام فئة StringBuilder ، والتي سيتم مناقشتها لاحقًا).يحدث أن البرنامج غالبًا ما يحتاج إلى تغيير نفس الخط. في مثل هذه الحالات ، من أجل تحسين سرعة استهلاك البرنامج والذاكرة ، يمكنك منع إنشاء كائنات صف جديدة. لهذه الأغراض ، يجب استخدام فئة 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);
بشكل منفصل ، تجدر الإشارة إلى مقارنة السلاسل. خطأ نموذجي لمبرمج جافا مبتدئ يقارن السلاسل باستخدام عامل التشغيل "==":
لا تحتوي هذه الشفرة بشكل رسمي على أخطاء في مرحلة الترجمة أو أخطاء وقت التشغيل ، ولكنها تعمل بشكل مختلف عما هو متوقع. نظرًا لأن جميع الكائنات والسلاسل ، بما في ذلك في Java ، يتم تمثيلها بواسطة الارتباطات ، فإن المقارنة مع عامل التشغيل "==" تعطي مقارنة للارتباطات ، وليس قيم الكائنات. أي أن النتيجة ستكون صحيحة فقط إذا كان هناك رابطان يشيران بالفعل إلى نفس السطر. إذا كانت السلاسل عبارة عن كائنات مختلفة في الذاكرة ، وتحتاج إلى مقارنة محتوياتها ، فأنت بحاجة إلى استخدام أسلوب equals (): if (usersInput.equals("Yes")) {
الشيء الأكثر إثارة للدهشة هو أنه في بعض الحالات ، تعمل المقارنة باستخدام عامل التشغيل "==" بشكل صحيح: String someString = "abc", anotherString = "abc";
هذا لأنه في الواقع ، تشير بعض السلسلة و سلسلة أخرى إلى نفس الكائن في الذاكرة. يضع المترجم نفس حرفية السلسلة في تجمع السلسلة - يحدث ما يسمى بالاعتقال. ثم في كل مرة تظهر نفس السلسلة الحرفية في البرنامج ، يتم استخدام ارتباط إلى السلسلة من التجمع. إن حبس الأوتار ممكن على وجه التحديد بسبب خاصية عدم ثبات الأوتار.على الرغم من أن مقارنة محتويات السلاسل مسموح بها فقط من خلال طريقة equals () ، إلا أنه من الممكن في Java استخدام السلاسل بشكل صحيح في إنشاءات حالة التبديل (بدءًا من Java 7): String str = new String();
الغريب ، يمكن تحويل أي كائن Java إلى سلسلة. يتم تعريف الأسلوب المقابل لـ String () في الفئة الأساسية لجميع فئات فئة الكائن.نهج معالجة الخطأ
عند البرمجة بلغة C ، قد تجد منهج معالجة الخطأ التالي. تقوم كل وظيفة في مكتبة بإرجاع نوع int. إذا نجحت الوظيفة ، تكون هذه النتيجة 0. إذا كانت النتيجة غير صفرية ، فهذا يشير إلى وجود خطأ. في معظم الأحيان ، يتم تمرير رمز الخطأ من خلال القيمة التي أرجعتها الدالة. نظرًا لأن الدالة يمكنها إرجاع قيمة واحدة فقط ، وهي مشغولة بالفعل بواسطة رمز الخطأ ، يجب إرجاع النتيجة الفعلية للدالة من خلال الوسيطة كمؤشر ، على سبيل المثال ، مثل: int function(struct Data **result, const char *arg) { int errorCode; return errorCode; }
بالمناسبة ، هذه هي إحدى الحالات التي يصبح فيها من الضروري في برنامج C استخدام مؤشر إلى المؤشر.في بعض الأحيان يستخدمون نهجًا مختلفًا. لا تُرجع الدالة رمز الخطأ ، ولكن مباشرةً نتيجة تنفيذه ، وعادةً ما تكون في شكل مؤشر. تتم الإشارة إلى حالة خطأ بمؤشر خالٍ. ثم تحتوي المكتبة عادة على وظيفة منفصلة تقوم بإرجاع رمز الخطأ الأخير: struct Data* function(const char *arg); int getLastError();
بطريقة أو بأخرى ، عند البرمجة في لغة C ، فإن الكود الذي يقوم بالعمل "المفيد" والشفرة المسؤولة عن معالجة الأخطاء تتداخل مع بعضها البعض ، مما يجعل من الواضح أن البرنامج لا يسهل قراءته.في Java ، إذا كنت ترغب في ذلك ، يمكنك استخدام الأساليب الموضحة أعلاه ، ولكن هنا يمكنك تطبيق طريقة مختلفة تمامًا للتعامل مع الأخطاء - معالجة الاستثناء (ومع ذلك ، كما هو الحال في C ++). وتتمثل ميزة معالجة الاستثناء في أنه في هذه الحالة ، يتم فصل الرمز "المفيد" والرمز المسؤول عن معالجة الأخطاء والطوارئ منطقيًا عن بعضهما البعض.يتم تحقيق ذلك باستخدام تركيبات try-catch: يتم وضع الرمز "المفيد" في قسم try ، ويتم وضع رمز معالجة الخطأ في قسم catch.
هناك حالات يتعذر فيها معالجة الخطأ بشكل صحيح في مكان حدوثه. في مثل هذه الحالات ، يتم وضع إشارة في توقيع الطريقة على أن الطريقة يمكن أن تسبب هذا النوع من الاستثناء: public void func() throws Exception {
يجب الآن أن يتم تأطير الاستدعاء لهذه الطريقة بالضرورة في كتلة محاولة التقاط ، أو يجب أيضًا تمييز طريقة الاستدعاء بأنها يمكن أن تطرح هذا الاستثناء.عدم وجود معالج مسبق
بغض النظر عن مدى ملاءمة المعالج المسبق المألوف لمبرمجي C / C ++ ، فهو غائب في لغة Java. ربما قرر مطورو Java أنها تستخدم فقط لضمان قابلية نقل البرامج ، وبما أن Java تعمل في كل مكان (تقريبًا) ، فلا حاجة إلى معالج مسبق فيها.يمكنك تعويض نقص المعالج المسبق باستخدام حقل العلم الثابت والتحقق من قيمته في البرنامج ، عند الضرورة.إذا كنا نتحدث عن تنظيم الاختبار ، فمن الممكن استخدام التعليقات التوضيحية جنبًا إلى جنب مع التفكير (الانعكاس).المصفوفة هي أيضا كائن.
عند العمل مع المصفوفات في C ، يكون خروج المؤشر خارج حدود المصفوفة خطأ خبيثًا للغاية. لن يقوم المترجم بالإبلاغ عنه بأي شكل من الأشكال ، وخلال التنفيذ لن يتم إيقاف البرنامج بالرسالة المقابلة: int array[5]; array[6] = 666;
على الأرجح ، سيستمر البرنامج في التنفيذ ، ولكن سيتم تشويه قيمة المتغير الذي كان موجودًا بعد صفيف الصفيف في المثال أعلاه. قد لا يكون تصحيح هذا النوع من الأخطاء سهلاً.في Java ، المبرمج محمي من هذا النوع من الأخطاء التي يصعب تشخيصها. عند محاولة تجاوز حدود الصفيف ، يتم طرح ArrayIndexOutOfBoundsException. إذا لم تتم برمجة عملية التقاط الاستثناء باستخدام بنية try-catch ، يتعطل البرنامج ويتم إرسال رسالة مقابلة إلى دفق الأخطاء القياسي الذي يشير إلى الملف برمز المصدر ورقم السطر الذي تم تجاوز الصفيف فيه. أي أن تشخيص مثل هذه الأخطاء يصبح مسألة تافهة.هذا السلوك من برنامج Java أصبح ممكنًا لأن الصفيف في Java يمثله كائن. لا يمكن تغيير حجم مصفوفة Java ؛ حيث يكون حجمها مشفرًا في وقت تخصيص الذاكرة. في وقت التشغيل ، يكون الحصول على حجم الصفيف أمرًا بسيطًا مثل: int[] array = new int[10]; int arraySize = array.length;
إذا كنا نتحدث عن المصفوفات متعددة الأبعاد ، فإن مقارنة Java بلغة C ، تقدم Java فرصة مثيرة لتنظيم صفائف "سلم". في حالة صفيف ثنائي الأبعاد ، قد يختلف حجم كل صف فردي عن الباقي: int[][] array = new int[10][]; for (int i = 0; i < array.length; i++) { array[i] = new int[i + 1]; }
كما هو الحال في C ، توجد عناصر المصفوفة في الذاكرة واحدة تلو الأخرى ، لذلك يعتبر الوصول إلى المصفوفة هو الأكثر كفاءة. إذا كنت بحاجة إلى إجراء عمليات إدراج / حذف للعناصر ، أو إنشاء هياكل بيانات أكثر تعقيدًا ، فأنت بحاجة إلى استخدام مجموعات ، مثل مجموعة (مجموعة) ، قائمة (قائمة) ، خريطة (خريطة).نظرًا لنقص المؤشرات وعدم القدرة على زيادة الارتباطات ، يمكن الوصول إلى عناصر المصفوفة باستخدام الفهارس.مجموعات
غالبًا ما تكون وظيفة المصفوفات غير كافية - فأنت بحاجة إلى استخدام هياكل البيانات الديناميكية. نظرًا لأن مكتبة C القياسية لا تحتوي على تنفيذ جاهز لبنى البيانات الديناميكية ، يجب عليك استخدام التطبيق في أكواد المصدر أو في شكل مكتبات.على عكس C ، تحتوي مكتبة Java القياسية على مجموعة غنية من تطبيقات هياكل البيانات أو المجموعات الديناميكية ، والتي يتم التعبير عنها بلغة Java. تنقسم جميع المجموعات إلى 3 فئات كبيرة: قوائم ومجموعات وخرائط.تسمح لك القوائم - المصفوفات الديناميكية - بإضافة / إزالة عناصر. الكثير لا يضمن ترتيب العناصر المضافة ، لكنه يضمن عدم وجود عناصر مكررة. تعمل البطاقات أو المصفوفات النقابية مع أزواج قيمة المفتاح ، وتكون قيمة المفتاح فريدة - لا يمكن أن يكون هناك زوجان بنفس المفاتيح في البطاقة.بالنسبة للقوائم والمجموعات والخرائط ، هناك العديد من عمليات التنفيذ ، تم تحسين كل منها لعملية معينة. على سبيل المثال ، يتم تنفيذ القوائم بواسطة فئتي ArrayList و LinkedList ، مع توفر ArrayList أداء أفضل عند الوصول إلى عنصر عشوائي ، و LinkedList أكثر فعالية عند إدراج / حذف العناصر في منتصف القائمة.يمكن تخزين كائنات Java الكاملة فقط في مجموعات (في الواقع ، مراجع للكائنات) ، لذلك من المستحيل إنشاء مجموعة من الأوليات بشكل مباشر (int ، char ، byte ، إلخ). في هذه الحالة ، يجب استخدام فئات الغلاف المناسبة:بدائي | فئة الالتفاف |
---|
بايت | بايت |
قصير | قصير |
حرف | حرف |
عدد | عدد صحيح |
طويل | طويل |
تعويم | تعويم |
مزدوج | مزدوج |
منطقي | منطقي |
لحسن الحظ ، عند البرمجة في Java ، ليست هناك حاجة لمتابعة المصادفة الدقيقة للنوع البدائي و "غلافه". إذا تلقت الطريقة وسيطة ، على سبيل المثال ، من النوع Integer ، فيمكن تمريرها من النوع int. والعكس صحيح ، حيث يكون نوع int مطلوبًا ، يمكنك استخدام Integer بأمان. وقد تم ذلك بفضل آلية Java المدمجة للتعبئة / التفريغ البدائية.من اللحظات غير السارة ، تجدر الإشارة إلى أن مكتبة Java القياسية تحتوي على فئات مجموعات قديمة تم تنفيذها دون جدوى في الإصدارات الأولى من Java والتي لا ينبغي استخدامها في البرامج الجديدة. هذه هي فئات التعداد ، المتجه ، المكدس ، القاموس ، Hashtable ، الخصائص.التعميمات
تُستخدم المجموعات بشكل شائع كأنواع بيانات عامة. جوهر التعميمات في هذه الحالة هو أننا نحدد النوع الرئيسي للمجموعة ، على سبيل المثال ، ArrayList ، وفي أقواس الزاوية تحدد نوع المعلمة ، والتي تحدد في هذه الحالة نوع العناصر المخزنة في القائمة: List<Integer> list = new ArrayList<Integer>();
يسمح هذا للمترجم بتعقب محاولة إضافة كائن من نوع غير معلمة النوع المحددة: List<Integer> list = new ArrayList<Integer>();
من المهم جدًا أن تمحى معلمة النوع أثناء تنفيذ البرنامج ، ولا يوجد فرق بين ، على سبيل المثال ، كائن من الفئة ArrayList <عدد صحيح>
والكائن الطبقي ArrayList <String>.
ونتيجة لذلك ، لا توجد طريقة لمعرفة نوع عناصر المجموعة أثناء تنفيذ البرنامج: public boolean containsInteger(List list) {
قد يكون الحل الجزئي هو النهج التالي: خذ العنصر الأول من المجموعة وحدد نوعه: public boolean containsInteger(List list) { if (!list.isEmpty() && list.get(0) instanceof Integer) { return true; } return false; }
لكن هذا النهج لن ينجح إذا كانت القائمة فارغة.في هذا الصدد ، فإن تعميمات Java أقل بكثير من تعميمات C ++. تعمل تعميمات Java في الواقع على "قطع" بعض الأخطاء المحتملة في مرحلة التجميع.كرر كل عناصر المصفوفة أو المجموعة
عند البرمجة بلغة C ، غالبًا ما يتعين عليك تكرار كل عناصر المصفوفة: for (int i = 0; i < SIZE; i++) { }
لجعل الخطأ هنا أبسط ، ما عليك سوى تحديد الحجم الخاطئ لصفيف SIZE أو وضع "<=" بدلاً من "<".في Java ، بالإضافة إلى النموذج "المعتاد" لعبارة for ، يوجد نموذج للتكرار على جميع عناصر مصفوفة أو مجموعة (تسمى غالبًا foreach بلغات أخرى): List<Integer> list = new ArrayList<>();
نضمن لك هنا فرز جميع عناصر القائمة ، ويتم التخلص من الأخطاء الكامنة في النموذج "المعتاد" من عبارة for.مجموعات متنوعة
نظرًا لأن جميع الكائنات موروثة من الكائن الجذر ، فإن Java لديها فرصة مثيرة للاهتمام لإنشاء قوائم بأنواع مختلفة من العناصر الفعلية: 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) {
التحويلات
بمقارنة C / C ++ و Java ، من المستحيل عدم ملاحظة عدد التعدادات الوظيفية التي يتم تنفيذها في Java. هنا يعد التعداد فئة كاملة ، وعناصر التعداد هي عناصر من هذه الفئة. يسمح هذا لعنصر تعداد واحد بتعيين عدة حقول من أي نوع في المراسلات: enum Colors {
بصفتها فئة كاملة ، يمكن أن يكون للعد طرق ، وباستخدام مُنشئ خاص ، يمكنك تعيين قيم الحقول لعناصر التعداد الفردية.هناك فرصة منتظمة للحصول على تمثيل سلسلة لعنصر تعداد ورقم تسلسلي بالإضافة إلى مجموعة من جميع العناصر: Colors color = Colors.BLACK; String str = color.toString();
والعكس بالعكس - من خلال تمثيل السلسلة ، يمكنك الحصول على عنصر تعداد ، وكذلك استدعاء طرقه: Colors red = Colors.valueOf("RED");
بطبيعة الحال ، يمكن استخدام التعدادات في تراكيب صناديق المفاتيح.الاستنتاجات
بالطبع ، تم تصميم لغتي C و Java لحل مشاكل مختلفة تمامًا. ولكن ، إذا قارنا مع ذلك عملية تطوير البرمجيات بهاتين اللغتين ، فوفقًا للانطباعات الشخصية للمؤلف ، فإن لغة جافا تفوق بشكل ملحوظ لغة C في راحة برامج الكتابة وسرعتها. تلعب بيئة التطوير (IDE) دورًا هامًا في توفير الراحة. عمل المؤلف مع IntelliJ IDEA IDE. عند البرمجة في Java ، لا يتعين عليك "أن تخاف" باستمرار لارتكاب خطأ - غالبًا ما تخبرك بيئة التطوير بما يلزم إصلاحه ، وأحيانًا ستقوم بذلك نيابة عنك. إذا حدث خطأ في وقت التشغيل ، فسيتم الإشارة إلى نوع الخطأ ومكان حدوثه في التعليمات البرمجية المصدر دائمًا في السجل - تصبح مكافحة هذه الأخطاء مسألة تافهة. لا يحتاج مبرمج C إلى بذل جهود غير إنسانية للتبديل إلى Java ، وكل ذلك لأن بنية اللغة تغيرت قليلاً.إذا كانت هذه التجربة ستكون مثيرة للاهتمام للقراء ، في المقالة التالية سنتحدث عن تجربة استخدام آلية JNI (تشغيل كود C / C ++ الأصلي من تطبيق Java). لا غنى عن آلية JNI عندما تريد التحكم في دقة الشاشة ووحدة Bluetooth ، وفي حالات أخرى عندما تكون قدرات خدمات Android والمديرين غير كافية.