مقدمة في الأنواع العامة
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 53 من 87.
تخيّل أنك بحاجة إلى إنشاء فئة تخزّن قيمة وتسترجعها لاحقًا. من دون الأنواع العامة، سيتعين عليك كتابة فئات منفصلة لكل نوع: واحدة لـ Integer، وواحدة لـ String، وواحدة لـ Double، وهكذا. يؤدي ذلك إلى تكرار الشيفرة وصعوبات في صيانتها.
تحل الأنواع العامة هذه المشكلة من خلال إتاحة كتابة أصناف وطرائق تعمل مع أي نوع، مع الحفاظ في الوقت نفسه على أمان الأنواع. تعرّف عنصرًا نائبًا (يُسمى مَعْلَمة النوع) يُستبدل بنوع فعلي عند استخدام الصنف:
// من دون الأنواع العامة - يستخدم Object، ويتطلب تحويل النوع
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // يلزم تحويل النوع يدويًا
// مع الأنواع العامة - آمن من حيث النوع، ولا يتطلب تحويل النوع
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // لا يلزم تحويل النوعتشير الأقواس الزاوية <T> إلى معامل نوع. عندما تكتب List<String>، فأنت تخبر المُصرّف أن هذه القائمة ستحتوي على سلاسل نصية فقط. إذا حاولت إضافة عدد صحيح، فسيكتشف المُصرّف الخطأ فورًا بدلًا من حدوث فشل أثناء وقت التشغيل.
توفر الأنواع العامة فائدتين أساسيتين: فهي تُغني عن الحاجة إلى التحويل الصريح للأنواع، وتكتشف أخطاء الأنواع وقت الترجمة بدلًا من وقت التشغيل. وهذا يجعل كودك أكثر أمانًا ونظافة. في الدروس التالية، سنستكشف كيفية إنشاء أصناف ودوال عامة خاصة بك.
التحدي
سهللننشئ نظام تخزين بسيطًا يوضّح قوة الأنواع العامة من خلال مقارنة الطريقة القديمة (باستخدام Object مع التحويل) بالطريقة الجديدة (باستخدام الأنواع العامة مع أمان الأنواع).
ستنشىء ثلاثة ملفات لترى الفرق بنفسك:
OldBox.java: أنشئ صنفًا يخزّن القيم بالطريقة السابقة للأنواع العامة. يجب أن يحتوي OldBox على حقل خاصcontentمن النوعObject. أضف مُنشئًا يقبل Object ويخزّنه، وطريقةgetContent()تُعيد Object. تعمل هذه الطريقة، لكنها تتطلب تحويلًا عند استرجاع القيم.GenericBox.java: أنشئ الآن النسخة الحديثة الآمنة من ناحية الأنواع باستخدام الأنواع العامة. يجب أن يستخدم GenericBox مُعامل نوعTفي تعريفه. ويحتاج إلى حقل خاصcontentمن النوعT، ومُنشئ يقبل قيمة من النوعT، وطريقةgetContent()تُعيد النوعT. لا حاجة إلى التحويل عند استخدام هذا الصنف!Main.java: اعرض الطريقتين جنبًا إلى جنب. ستتلقى مُدخلين: كلمة (String) ورقمًا (عددًا صحيحًا).أولًا، استخدم الطريقة القديمة: أنشئ OldBox يخزّن الكلمة، ثم استرجعها بتحويلها إلى String واطبع:
OldBox (with cast): [value]بعد ذلك، استخدم طريقة الأنواع العامة: أنشئ
GenericBox<String>يخزّن الكلمة نفسها، واسترجعها دون تحويل، واطبع:GenericBox (no cast): [value]ثم اعرض عمل الأنواع العامة مع الأعداد الصحيحة: أنشئ
GenericBox<Integer>يخزّن الرقم، واسترجعه، واطبع:GenericBox Integer: [value]أخيرًا، اطبع سطرًا فارغًا يتبعه:
Type safety: Generics catch errors at compile time!
ستتلقى مُدخلين بالترتيب: كلمة (String) ورقمًا (عددًا صحيحًا على هيئة String، وستحوّله).
لاحظ الفرق الأساسي: مع OldBox، عليك تحويل النوع باستخدام (String) box.getContent() والأمل في أن يكون النوع صحيحًا. أما مع GenericBox، فيعرف المترجم النوع ولا حاجة إلى أي تحويل. هذه هي الفائدة الأساسية للأنواع العامة: أمان الأنواع دون عناء التحويل!
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String word = scanner.nextLine();
int number = Integer.parseInt(scanner.nextLine());
// TODO: أنشئ OldBox يخزّن الكلمة
// استرجعها مع التحويل إلى String واطبع: "OldBox (with cast): [value]"
// TODO: أنشئ GenericBox<String> يخزّن الكلمة
// استرجعها دون تحويل النوع واطبع: "GenericBox (no cast): [value]"
// TODO: أنشئ GenericBox<Integer> يخزّن العدد
// استرجعه واطبع: "GenericBox Integer: [value]"
// TODO: اطبع سطرًا فارغًا، ثم اطبع:
// "Type safety: Generics catch errors at compile time!"
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت