لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 10/12/22 في كل الموقع
-
1 نقطة
-
ماهي المعرفات الصالحه والغير صالحه python .This is okay 3CPO R2D2 print Whatchamacallit1 نقطة
-
السلام عليكم قمت باعادة تثبيت ثيم laraclassified كود-كانيون لاكن عند لوحة التثبيت في لارفيل يطلب مني Purchase code كيف احصل عليه او كيف اتجاوز هده المشكلة من فضلكم1 نقطة
-
1 نقطة
-
مستقبل البرمجة هو خليط من عدم وجود كود وكود معزز بالذكاء الاصطناعي وبالتالي ، فإن مستقبل الترميز هو تعزيز الترميز والمطورين الذي يعززه الذكاء الاصطناعي أكثر فأكثر. إنه أيضًا اختيار طبيعي لعشرات لغات البرمجة التي عادةً ما يتم استثمارها من قبل شركات BigTech التي لها اهتماماتها الخاصة حولها. تؤثر القوى الثقافية في التكنولوجيا مثل العملات المشفرة و blockchain أيضًا على اعتماد لغات برمجة معينة. بدلاً من استخدام Solidity أو أي لغة متخصصة أخرى لكتابة العقود الذكية ، يستخدم Solana (أحد المنافسين الحاليين لـ Ethereum) Rust. في العام الماضي فقط ، قال 90٪ من المشاركين في استطلاع لمطوري البرمجيات إنهم يستخدمون Rust حاليًا أو استخدموه في الماضي. يعد Codex الخاص بـ OpenAI مثالًا مثيرًا للاهتمام حول كيفية تنفيذ الترميز بدون رمز. بشكل أساسي ، قال OpenAl إنه مع Codex الخاص بـ OpenAI ، إنها طريقة جديدة تمامًا "لكتابة التعليمات البرمجية" باللغة الإنجليزية الطبيعية. يمكن لمبرمج الكمبيوتر الآن استخدام اللغة الإنجليزية لوصف ما يريدون أن يفعله برنامجهم ، وسيقوم نموذج Al التكويني الخاص بـ OpenAl تلقائيًا بإنشاء رمز الكمبيوتر المقابل ، في اختيارك للغة البرمجة. هذا ما أردناه دائمًا - لأجهزة الكمبيوتر أن تفهم ما نريدها أن تفعله ، ثم تفعل ذلك ، دون الحاجة إلى المرور عبر وسيط معقد مثل لغة البرمجة. كيف سنبرمج إذا كانت لدينا واجهة دماغ حاسوبية فعالة؟ ربما لم نعد نستخدم لوحات المفاتيح ولغات البرمجة القديمة. من الواضح أن A.I. لن تتفوق على المطورين البشريين في الترميز في أي وقت قريب ولن تؤدي الأنظمة الأساسية منخفضة التعليمات البرمجية إلى تعطيل المطورين. أما بخصوص تعلم منصات الكود القليل مثل wordpress و frappe وغيرها فهذا لا بأس به بل حتى أنها بدأت تسيطر على السوق ولكن بعد تعلم لغات البرمجة بشكل جيد ، ﻷنه قد تصادف خطأ برمجي أو يُطلب منك تعديل شيفرة . لذلك من المهم تعلم البرمجة الخاصة وهذه بعض المقالات المفيدة https://io.hsoub.com/programming/33986-هل-سيستغني-المستقبل-عن-البرمجة-بشكل-عام-و-من-طرف-الإنسان-بشكل-خاص https://io.hsoub.com/webdev/20369-إذا-كان-ووردبريس-وجوملا-يقومان-بالمهمة-فما-الفائدة-من-إنشاء-موقع-من-الصفر1 نقطة
-
القالب تم تثبيته من قبل مبرمج لايمكنني التواصل معه لاكن لدي الملف الرسمي الدي قام بتنزيله اول مرة أردت تثبيته لاعادة الضبط من الصفر لدلك لا اجد كما شرحت لي لاني لم أحمل شيئا من الموقع1 نقطة
-
انا الحمد لله مشترك في دورتكم وتعلمت اساسيات جافا سكريبت فا لحظت حاجه وهي انو بعض كورسات اساسيات جافا سكريبت الي على الانترنت فيها مقاطع كثيره للكورس الواحد بينما مقاطعكم انتم قليله التي تخص اساسيات جافا سكريبت فهل هاذي طبيعي واتمنى انو ما احد يفهمني غلط ابغا استفسر فقط ماسبب يعني انو المحتوى يختلف عن الموجود في النت1 نقطة
-
الآن ، أنا على وشك التخرج وهدفي هو الحصول على وظيفة مستقرة لأنها أفضل بالنسبة لي من العمل الحر حتي أستطيع تكوين نفسي بشكل صحيح، لذلك مضمون سؤالي ،كيف أجد وظيفة بسرعة بعد التخرج وما هي أفضل الممارسات التي أقوم بها أضمن قبولي (الله أعلى وأعلم)؟1 نقطة
-
في البداية يجب أن تعلم وأن يكون لديك يقين بأن الأرزاق بيد الله سبحانه وتعالى ، ولا أستطيع أن أخبرك أنك سوف تحصل على وظيفة بسرعة بعد التخرج لأن في كل سنة يكون عدد الخريجين كبير وفي الوظائف تحدث المنافسة بين الخريجين والذي يستحق الوظيفة تكون من نصيبه ، فأفضل شيء ممكن أن تفعله بعد التخرج عدم التوقف عن التعلم ويجب أن تستمر بالتعلم وإكتشاف سوق العمل جيداً وتعلم ما هو جديد ولا تتوقف على ما تعلمته بالجامعة ويمكنك الإنضمام لدورات في مجال عملك للحصول على مزيد من الخبرة ، ثم عليك اكتشاف والتعرف والتواصل مع الأشخاص الذين هم بمجالك وتوسيع مجال علاقتك لربما يمكن لأحد من المعارف يرشحك لوظيفة ما في حال كنت شخص قادر على تأدية مهام الوظيفة بكفاءة ، أيضاً السيرة الذاتية هي المعرف الخاص بك لذلك عليك إنشاء سيرة ذاتية صادقة وتشرح فيها ما تعلمته والخبرات التي لديك ، الأن يمكنك الحصول على وظيفة من خلال وسائل التواصل الإجتماعي لذلك قم بنشر أعمالك وما يتعلق في مجال عملك في مواقع التواصل الإجتماعي مثل منصة لينكد إن LinkedIn وغيرها . بإختصار الممارسات التي عليك فعلها :- التعلم المستمر بناء العلاقات مع أصحاب الخبرة بناء سيرة ذاتية حقيقية نشر أعمالك على مواقع التواصل الإجتماعي هناك ممارسات أخرى يمكنك أن تكتشفها في نفسك بعد التخرج ، وبخصوص العمل الحر يمكن أن يؤهلك على وظيفة مستمرة مع أحد العملاء وليس الإكتفاء على إنجاز المشاريع وبيع الخدمات . توفر شركة حسوب منصة بعيد من أجل الحصول على وظائف يمكنك الإطلاع عليها ، ورؤية ماذا المتطلبات للحصول على وظيفة .1 نقطة
-
تعد WhatsApp Business API من Twilio خدمة قوية وسهلة الاستخدام تتيح لك التواصل مع المستخدمين على تطبيق المراسلة الشهير. ستقوم بتوصيل هاتفك الذكي بصندوق الحماية. من Twilio Console ، حدد المراسلة ، ثم انقر فوق "جربه". اختر قسم WhatsApp على الشريط الجانبي الأيسر. ستظهر لك صفحة وضع الحماية في WhatsApp رقم الحماية المخصص لحسابك ، ورمز الانضمام. أنت الآن جاهز لتثبيت تبعيات Python المستخدمة في هذا المشروع: pip install twilio pyngrok حزم Python الثلاثة التي يحتاجها هذا المشروع هي: إطار عمل Django ، لإنشاء تطبيق الويب. مكتبة Twilio Python Helper ، للعمل مع رسائل SMS. Pyngrok ، لجعل تطبيق Django متاحًا مؤقتًا على الإنترنت للاختبار عبر الأداة المساعدة ngrok. 3. تشغيل ngrok ngrok http 8000 افتح ملف settings.py من دليل الرسائل في محرر النصوص أو IDE. ابحث عن السطر الذي يحتوي على المتغير ALLOWED_HOSTS وقم بتغييره على النحو التالي ALLOWED_HOSTS = ['.ngrok.io'] إنشاء خطاف ويب WhatsApp يستخدم Twilio مفهوم webhooks لتمكين تطبيقك من تنفيذ إجراءات مخصصة كنتيجة لأحداث خارجية مثل تلقي رسالة من مستخدم على WhatsApp. الخطاف على الويب ليس أكثر من نقطة نهاية HTTP يستدعيها Twilio بمعلومات حول الحدث. الرد الذي تم إرجاعه إلى Twilio يوفر إرشادات حول كيفية التعامل مع الحدث. سيتضمن الويب هوك لرسالة WhatsApp الواردة معلومات مثل رقم هاتف المستخدم ونص الرسالة. في الرد ، يمكن للتطبيق توفير استجابة لإرسالها مرة أخرى إلى المستخدم. يجب تقديم الإجراءات التي تريد أن يتخذها Twilio ردًا على حدث وارد بلغة مخصصة محددة بواسطة Twilio تستند إلى XML وتسمى TwiML. إضافة نقطة نهاية جديدة افتح ملف settings.py من دليل الرسائل مرة أخرى. ابحث عن المتغير INSTALLED_APPS. هذه قائمة بالعديد من السلاسل ، وهي وحدات معيارية في إطار عمل Django. في نهاية القائمة ، تحتاج إلى إضافة إدخال آخر لتسجيل تطبيق whatsapp الذي قمت بإنشائه مسبقًا. INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'whatsapp.apps.WhatsappConfig', # ← العنصر الجديد ] افتح الملف views.py من الدليل الفرعي whatsapp. هذا هو المكان الذي ستنشئ فيه نقطة النهاية التي ستتعامل مع رسائل WhatsApp الواردة. استبدل محتويات هذا الملف بما يلي: from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt @csrf_exempt def message(self): return HttpResponse('Hello!') وظيفة message () هي وظيفة نقطة النهاية التي سيتم تشغيلها عندما يقوم Twilio بإخطار التطبيق برسالة WhatsApp واردة. في الوقت الحالي ، تقوم هذه الوظيفة بإرجاع استجابة بسيطة بالنص "Hello!". لتسهيل الوصول إلى نقطة النهاية هذه من خلال تطبيق الويب ، يجب تخصيص عنوان URL لها. افتح ملف urls.py من دليل الرسائل وقم بإضافة إدخال جديد إلى قائمة urlpatterns كما هو موضح أدناه: from django.contrib import admin from django.urls import path from whatsapp import views # ← استيراد الدالة urlpatterns = [ path('admin/', admin.site.urls), path('message', views.message), # ← اضافة الى المسار ] استقبال رسائل WhatsApp الخطوة التالية هي تحديث المنطق داخل نقطة نهاية message () لاستخراج المعلومات حول الرسالة الواردة. استبدل محتويات ملف views.py في دليل whatsapp الفرعي بما يلي: from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt @csrf_exempt def message(request): user = request.POST.get('From')# message = request.POST.get('Body')# print(f'{user} يقول {message}')# return HttpResponse('مرحبا!') إرسال رد from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from twilio.twiml.messaging_response import MessagingResponse @csrf_exempt def message(request): user = request.POST.get('From') message = request.POST.get('Body') print(f'{user} يقول {message}') response = MessagingResponse() response.message('شكرا على اهتمامك ، سنقوم بالرد في أقرب وقت') return HttpResponse(str(response)) إذا كان هذا البوت سيستخدم للإنتاج ، فيُنصح رفعه على استضافة vps1 نقطة
-
بما أن لديك الوصول الى phpmyadmin فيمكنك تعديل كلمة المرور عن طريق حقن أخرى مشفرة بذات الخوارزمية التي هي BCrypt. يمكنك مثلا استعمال موقع bcrypt-generator لتوليد كلمة مرور مشفرة ابتداءا من واحدة تختارها. قم لاحقا بلصق الناتج في حقل password في الجدول المستهدف في phpmyadmin لديك. سيمكنك بعد هذا استعمال الكلمة الغير مشفرة لتسجيل دخولك بشكل عادي.1 نقطة
-
كل دورة من دورات حسوب تتكون من عدد من ساعات الفيديو التدريبية، على سبيل المثال دورة تطوير واجهات المستخدم لديها 58 ساعة فيديو تدريبية، ويمكنك معرفة عدد ساعات كل دورة من خلال الصفحة الخاصة بالدورة ، وسوف تجد في التفاصيل (شاهد صفحة الدورة) باللون الأزرق بالضغط عليها سوف ينقلك لمشاهدة محتوى الدورة بالكامل .1 نقطة
-
عدد ساعات دورة تطوير واجهات المستخدم هو 58 ساعة فيديو تدريبية وهي قابلة للزيادة حيث أن الدورة يتم تحديثها واضافة بعض المسارات الجديدة بناء على سوق العمل واحتياجاته وتطورات التقنيات التي يتم دراستها خلال الدورة, يمكنك معرفة المزيد من التفاصيل عن كل دورة من خلال الذهاب الى الصفحة الخاصة بكل دورة, على سبيل المثال دورة تطوير وادهات المستخدم يمكنك الذهاب للصفحة الخاصة بها من هنا, أما باقي الدورات فيمكنك الذهاب الى الصفحة الرئيسية للأكاديمية وتضغط على صورة كل دورة من الدورات لتذهب الى موقعها1 نقطة
-
MERN هي تجميعة من أطر العمل والمكتبات المبنية على جافاسكربت JS-based وهي اختصار الأحرف الاولى من كل من: MongoDB React Express.js NodeJS في حين أن Django هو اطار عمل على لغة بايثون موجه لتطبيقات الويب في الأساس. يتيح لك إنشاء تطبيقات ويب شاملة full stack web apps دون الحاجة إلى تعلم الكثير في وقت واحد. وحتى أن الواجهة الأمامية تستخدم قوالب Django. وهو أسهل نسبيا مقارنة بتجميعة MERN. في Django يوجد ايضا الكثير من الأدوات المضمنة لمساعدة المبتدئين والمحترفين، مثل نظم المصادقة. التي لا تكون متوفرة عادة في MERN stack.1 نقطة
-
يمكنك إستعمال الattribute المسمى dir على الحافظة الرئيسية التي تحيط بالكود (body) بإسناد القيمة rtl لها وتجرب إذا كانت جميع العناصر ستأخذ نفس الإتجاه المطلوب أم لا.. العناصر التي تمتلك الخاصية display بقيمة absolute مثلا ربما تحتاج تعديل قيمة left أو right لها حتى تظهر بالإتجاه الصحيح. طبعاً تبقى هذه إرشادات عامة بدون وجود كود تتم معاينته ومن ثم الحكم عليه. ... <body dir="rtl"> ... </body> ... ... /*display:absolute مثلاً إذا كان لديك الفقرة التالية بخاصية */ p{ /*dir="ltr" هنا في حالة */ display: absolute; left:10px } ... /* تقوم بتغييرها الى التالي */ p{ /*dir="rtl" هنا في حالة */ display: absolute; right:10px } ...1 نقطة
-
لا يوجد قاعدة معينة يمكن من عليها تعديل اتجاه العناصر لأن الموضوع مرن جدا، وقد نجد كل مصمم يصف الموضوع بطريقة ما. ولكن بشكل عام قد تحتاج: جعل اتجاه الصفحة rtl بارفاق القيمة rtl للخاصية dir لعنصر html و body وأي عناصر يتم تجاوز هاته الخاصية فيها. ضبط محاذاة النصوص بجعلها text-align: right ضبط وعكس اتجاه العناصر المعومة، اي تلك التي تمتلك الخاصية float. تعديل تموضع العناصر التي تمتلك وضعية مطلقة. فان كانت تمتلك الخاصية left:0 اعطها right: 0 وهكذا. ضبط خواص الهوامش التلقائية على المحور x. فان كان عنصر ما يمتلك margin-left:auto يجب ان تصبح الخاصية margin-right:auto هذا بجانب انك قد تحتاج بعض التعديلات الجزئية الأخرى، التي تناسب العرض باللغة العربية مثلا.1 نقطة
-
هل عملاء المنافسين يتفاعلون مع المحتوى المُقدم لهم؟ أي نوع من المحتوى يتفاعل معه العملاء أكثر؟ ما هي الرسائل التي يحاول المنافسون الوصول إليها؟ كيف يُقدم المنافسين المحتوى “غير رسمي، رسمي، مُبهج”؟ ما هي CTAs الأكثر استخداما، وفي أي مرحلة من مراحل البيع يقف منافسوك؟1 نقطة
-
تُعَد جافا منصةً برمجيةً ولغة برمجة حاسوب، وواحدةً من أكثر التقنيات انتشارًا في العالم الحديث. تُستخدم كلمة جافا عادةً للإشارة إلى شيئين، هما منصة جافا Java platform، وهي مجموعة من الأدوات التي تتيح تطوير التطبيقات متعددة المنصات بسهولة، ولغة برمجة جافا، وهي لغة برمجة عامة الاستخدام، تُستخدم غالبًا لتطوير البرامج لهذه المنصة. تتميز جافا عن العديد من التقنيات الأخرى بأنها مصممة بحيث يمكن تشغيل التعليمات البرمجية المكتوبة بلغة جافا على أي نظام يمكنه تشغيل آلة جافا الافتراضية Java virtual machine، واختصارًا JVM، وقد رُفع شعار "اكتبه مرةً واحدةً وشغله في أي مكان" للترويج لقدرات جافا متعددة المنصات، حيث يمكن إنشاء بيئات جافا على جميع أنواع الأجهزة، الكبيرة منها والصغيرة، وبالتالي يتمتع مطور جافا بالمرونة، لأن الشيفرة البرمجية تُعامل على أنها غير مرتبطة بالنظام الذي تُشغل عليه. لغة جافا لغة كائنية التوجه، وتشبه من الناحية التركيبية لغة ++C، وستلاحظ أن برامج جافا مبنية دائمًا بتصميم كائني التوجه على عكس بعض اللغات الأخرى التي سبقتها، والتي طبقت مفهوم الأصناف classes ولكنها لم تتطلب استخدامها. تقترن لغة جافا مع آلة جافا الافتراضية JVM -التي تشغّل التعليمات البرمجية المكتوبة بلغة جافا- اقترانًا وثيقًا، على الرغم من أنهما منفصلان، كما يمكن تشغيل الشيفرة البرمجية المكتوبة بلغات أخرى مصممة خصيصًا لآلة جافا الافتراضية، مثل Groovy و Scala عليها. احرص على عدم الخلط بين جافا وجافاسكربت JavaScript، فبالرغم من اشتراكهما في جزء من الاسم، إلا أنهما يختلفان اختلافًا كبيرًا، وتُستخدمان في العديد من البيئات، إلا أن جافاسكربت تستخدم لتعزيز التفاعل داخل متصفح الويب. ما هي استخدامات جافا؟ يمكن العثور على جافا في جميع الأماكن، ربما في جيبك أو على معصمك، حيث إن نظام أندرويد Android، وهو نظام تشغيل مفتوح المصدر مشتق من نظام التشغيل لينكس Linux، ويُشغل ملايين الأجهزة المحمولة حول العالم، يستخدمها مع مكتباته أساسًا لتطبيقات الأجهزة المحمولة المبنية لمنصته، كما يمكن استخدام جافا في تطبيقات سطح المكتب، وتوجد العديد من أنواع التطبيقات التي تعمل على جافا، بدءًا من الألعاب ذات الشعبية الكبيرة -مثل Minecraft- إلى بيئة التطوير المتكاملة Eclipse التي يستخدمها المطورون للعديد من اللغات. كما تعمل جافا على تشغيل عدد من التطبيقات المصممة خصيصًا للويب، ومع التحسينات المطبقة على JavaScript و HTML، لم تعد بريمجات جافا Java applet المعيار الفعلي لتطبيقات الويب التفاعلية، إلا أن الكثيرين لا زالوا يعتمدون على جافا لتوفير تجربة تفاعلية داخل المتصفح، وعلى الرغم من أن جافا ليست شائعة الاستخدام في تطوير الواجهة الأمامية front-end لتطبيقات الويب هذه الأيام، إلا أنها شائعة الاستخدام خلف الكواليس في الكثير من المواقع وتطبيقات الويب، لأن لها نظامًا بيئيًا غنيًا من الأدوات لتشغيل وتوصيل التطبيقات القابلة للتوسع على نطاق كبير، والتي تحافظ على تشغيل بعض أكبر مواقع الويب والعمليات التجارية، وذلك من خلال الإمكانيات التي توفرها منصة جافا التجارية Java EE و خوادم جافا مفتوحة المصدر، مثل WildFly وApache Tomcat . هل جافا مفتوحة المصدر؟ يعد موضوع ترخيص جافا قصةً طويلةً ومعقدةً، ولكن حاليًا توجد معظم المكونات الرئيسية لجافا بموجب تراخيص مفتوحة المصدر، وتتوافر عادةً بدائل مفتوحة للمكونات غير الداخلة في الرخصة المفتوحة. وضعت شركة Sun (وهي المطور الأصلي لجافا) الكثير من مكونات جافا تحت رخصة GNU العامة في عام 2006، وسدت عدة مشاريع، مثل IcedTea، الفجوات الخاصة بأجزاء عُدة تطوير جافا Java Development Kit، والتي تختصر إلى JDK، غير الداخلة في الرخصة المفتوحة، مما يعني أنه من الممكن اليوم تشغيل تطبيقات جافا دون استخدام أي شيفرة تحتاج إلى رخصة. وتجدر الإشارة إلى توافر نسخة مفتوحة المصدر من عُدة تطوير جافا وهي openJDK، التي صدرت تحت رخصة GPL وهي المرجع الرسمي لجافا SE بدءًا من الإصدار 7. كيفية تشغيل تطبيق جافا اتجهت اللغة نحو مفهوم الوحدات modules منذ الإصدار 9، حيث تحتوي الوحدة على مجموعة من الحزم، وتجمّع العديد من تطبيقات جافا الحديثة حزمة وحدة جافا صغيرة مثل جزء من البرنامج نفسه، مما يعني إمكانية تشغيل البرنامج دون الحاجة إلى تنزيل جافا. لا يشمل هذا جميع التطبيقات بالتأكيد، ولا بد من تثبيت بيئة تشغيل جافا Java runtime environment، والتي تختصر إلى JRE، لتشغيل العديد من تطبيقات جافا، ولا توجد بيئة تشغيل واحدة فقط لأن جافا مفتوحة المصدر، ولكن يمكن اختيار بيئة التشغيل التي تناسبك، فمثلًا توفر توزيعات لينكس Linux بيئة تشغيل جافا OpenJRE أو IcedTea، بينما يمكن لمستخدمي ويندوز تنزيل بيئة تشغيل JRE من بوابة مطوري Red Hat، أما مستخدمو ماك Mac OS (و Windows و Linux) فيمكنهم تنزيل بيئة تشغيل جافا JRE من OpenJDK.java.net أو مجتمع Zulu في Azul. تُصمم بيئة تشغيل جافا JRE خصيصًا لنظام تشغيل معين يمكن تثبيتها عليه، حينها نستطيع تشغيل أي تطبيق جافا على الجهاز، وبتثبيت جافا فإنك تزود نظامك بطبقة -تقنيًا هي آلة جافا الافتراضية- يمكن تشغيل أي تطبيق جافا عليها. كيفية البرمجة في جافا جافا هي لغة كائنية التوجه OOP، وصارمة في تحديد النوع strictly typed، وتستخدَم في جميع الصناعات تقريبًا، من المالية إلى النشر إلى تكنولوجيا المعلومات، وتمتلك جمهورًا كبيرًا لأنها متعددة المنصات، وتبدأ مناهج البرمجة الأساسية في العديد من الجامعات بتعليم لغة جافا، لأنها تفرض أفضل ممارسات كتابة الشيفرة، مثل تحديد النطاق وتحديد نوع المتغيرات، والتي تسمح لغات أخرى بتجاهلها، مثل لغة Python ولغة GO، وتعد جافا شائعةً وقديمةً إلى درجة احتوائها على مكتبات لأي مهمة تقريبًا، لذلك يمكن للمبرمج المبتدئ الوصول إلى مجموعة غنية من الدوال الجاهزة للاستخدام. لاستخدام جافا يجب تثبيت عدة تطوير جافا JDK، ويمكنك الانتقاء من عدة خيارات، بما في ذلك OpenJDK و Zulu، وهي JDK تحتوي على JRE، بحيث يمكنك تشغيل التطبيقات التي تطورها. توجد العديد من الطرق لبدء التعرف على جافا، فإذا كنت تفضل مقدمةً ممتعةً، فيمكنك تجربة Greenfoot، وهي بيئة تطوير تفاعلية interactive development environment، وتختصر إلى IDE، مصممة لتعليم جافا، وتساعد المبرمج الجديد على إنشاء ألعاب وتطبيقات ممتعة بواجهة رسومية سهلة. فإذا أردت خطوةً أكثر تقدمًا فاستخدم BlueJ، وهو بيئة تطوير تفاعلية لجافا صممته كلية King's في لندن، وهو يسلط الضوء على عناصر مهمة من الشيفرة، مثل الأصناف والكائنات. تعد بيئة التطوير Eclipse أحد أكثر بيئات برمجة جافا شهرةً وقدرةً، وتساعد في إدارة المكتبات، وحل الأخطاء والتعارضات في التعليمات البرمجية، كما تساعد في التنقيح debug وإنشاء الحزم وغير ذلك، ويوفر Eclipse Che لفرق التطوير مساحة عمل مشتركةً قائمةً على السحابة، مما يضمن استخدام كل شخص في المشروع لنفس البيئة. مستقبل جافا طوَّرت شركة Sun Microsystems لغة جافا بدايةً ودعمتها فترةً من الزمن، ثم انتقلت جافا إلى جناح شركة أوراكل Oracle التي تدعمها هذه الأيام، ولا زالت هنالك نسخة مفتوحة المصدر من جافا هي openJDK يقف خلفها مجتمع عالمي يدعمها ويقف خلف نموها وتطويرها، ورأينا تغير استخدامات جافا بمرور الأيام، لكنها ما زالت ترفع شعارها الشهير "اكتبه مرةً واحدةً وشغله في أي مكان" بقوة، متحديةً أي لغة برمجة أن تضاهيها وتنافسها فيه. لمعرفة المزيد يمكنك العودة إلى مدخل إلى جافا ترجمة -وبتصرف- للمقال What is Java? من موقع opensource.com. اقرأ أيضًا مدخل إلى أساسيات البرمجة بلغة Java: ما هي البرمجة؟ بيئات البرمجة (programming environment) في جافا مقدمة إلى برمجة واجهات المستخدم الرسومية (GUI) في جافا1 نقطة
-
تُشير البرمجة المُعمَّمة generic programming إلى كتابة شيفرةٍ يُمكِن تطبيقها على أنواعٍ كثيرة من البيانات. كنا قد تعرَّضنا بمقال معالجة المصفوفات Arrays في جافا للمصفوفات الديناميكية، والتي يُمكِن عدّها بديلًا عن البرمجة المُعمَّمة، وكتبنا شيفرةً تَعمَل مع مصفوفةٍ ديناميكية من الأعداد الصحيحة. في الحقيقة، لا يُمكِن لتلك الشيفرة أن تَعمَل مع أي نوعٍ غير النوع int، رغم أن الشيفرة المصدرية للمصفوفات الديناميكية من النوع double أو String أو Color أو أي نوع بياناتٍ آخر هي نفسها تقريبًا باستثناء تبديل اسم النوع؛ وبالتالي يبدو من الحماقة إعادة كتابة نفس الشيفرة مرارًا وتكرارًا. تُقدِّم جافا حلًا لتلك المشكلة يُعرَف باسم الأنواع ذات المعاملات غير مُحدَّدة النوع parameterized types. كما رأينا بمقال مفهوم المصفوفات الديناميكية (ArrayLists) في جافا، يُنفِّذ الصنف ArrayList المصفوفات الديناميكية، ونظرًا لكونه نوعًا ذا معاملاتٍ غير مُحدَّدة النوع، فستَجِد أنواعًا؛ مثل النوع ArrayList<String> لتمثيل مصفوفةٍ ديناميكيةٍ من السلاسل النصية من النوع String؛ والنوع ArrayList<Color> لتمثيل مصفوفة ديناميكية من الألوان؛ والنوع ArrayList<T> لأي نوع كائن T. لاحِظ أن ArrayList هي صنفٌ واحدٌ فقط، ولكنه مُعرَّف ليَعمَل مع أنواعٍ مختلفةٍ كثيرة، وهذا هو صميم البرمجة المُعمَّمة. الصنف ArrayList هو في الواقع مجرد صنفٍ واحدٍ ضمن عددٍ كبيرٍ من أصناف جافا القياسية standard classes التي تَستخدِم البرمجة المُعمَّمة. سنناقش خلال المقالات الثلاثة التالية بعضًا من تلك الأصناف، ونتعرَّف على طريقة استخدامها، كما سنتعرَّض لواجهات interfaces وتوابع methods مُعمَّمة. لاحِظ أن جميع الأصناف والواجهات التي سنناقشها خلال تلك الأقسام مُعرَّفةٌ بحزمة package java.util، لذلك ستحتاج إلى كتابة التعليمة import ببداية برامجك لتتمكّن من استخدامها. سنرى في مقال لاحق من هذه السلسلة إمكانية تعريف define أصنافٍ وواجهاتٍ وتوابعٍ مُعمَّمة، ولكن لحين وصولنا إلى تلك الجزئية، سنكتفي بأصناف جافا المُعمَّمة المُعرَّفة مُسبقًا. أخيرًا، سنفحص مجاري التدفق streams بالقسم قادم أيضًا؛ وهي خاصيةٌ جديدةٌ نسبيًا بلغة جافا، وتَستخدِم البرمجة المُعمَّمة بكثرة. لا يُعدّ تصميم مكتبةٍ للبرمجة المُعمَّمة أمرًا سهلًا؛ في حين يتميز التصميم الذي تقترحه جافا لتنفيذ هذا النوع من البرمجة بالكثير من الخاصيات الرائعة، إلا أنه ليس الأسلوب الوحيد الممكن، وهو بالتأكيد ليس الحل الأمثل؛ حيث يُعاني -من وجهة نظر الكاتب- من بعض الخاصيات الشاذة، والتي قد تكون مع ذلك أفضل ما يمكن تحقيقه بالنظر إلى التصميم العام للغة جافا. سنَمُرّ الآن سريعًا على بعض المناهج الأخرى لتنفيذ البرمجة المُعمّمة، لتُكوِّن منظورًا عامًا عنها. البرمجة المعممة بلغة Smalltalk تُعدّ لغة Smalltalk واحدةً من أولى لغات البرمجة كائنية التوجه object-oriented، حيث لا تزال مُستخدَمةً حاليًا، ولكنها ليست شائعة. على الرغم من عدم وصولها إلى شعبية لغاتٍ، مثل Java و ++C، إلا أنها ألهمت الكثير من الأفكار المُستخدَمة بتلك اللغات. تُعد البرمجة بلغة Smalltalk مُعمَّمة بصورةٍ أساسيية نظرًا لتمتُّعها بخاصيتين أساسيتين، هما: ليس المُتغيَّرات بلغة Smalltalk نوعًا؛ أي أن قيم البيانات لها نوعٌ مثل عددٍ صحيح أو سلسلة نصية، ولكن المُتغيَّرات ليس لها نوع، حيث يُمكِن لمُتغيِّر مُعين أن يَحمِل قيمًا بيانيةُ من أي نوع. وبالمثل، لا يوجد للمُعاملات parameters نوعًا، أي يُمكِن تطبيق برنامجٍ فرعي subroutine معينٍ على قيم معاملاتٍ من أي نوع؛ كما يُمكِن لبنيةٍ بيانيةٍ data structure مُعينةٍ أن تَحمِل قيمًا بيانيةً من أي نوع. حيث يمكنك مثلًا وبمجرد تعريف بنية شجرةٍ ثنائيةٍ معينة بلغة Smalltalk، استخدامها مع الأعداد الصحيحة، أو السلاسل النصية، أو التواريخ، أو أي بياناتٍ من أي نوعٍ آخر؛ أي ليس هناك حاجةً لكتابة شيفرةٍ جديدةٍ لكل نوعٍ بياني. تُعدّ أي قيمةٍ بيانيةٍ بمثابة كائن object، كما أن جميع العمليات المُمكِن تطبيقها على الكائنات مُعرَّفةٌ مثل توابع methods ضِمْن صنفٍ معين، ويَنطبِق الأمر نفسه على جميع الأنواع الأساسية primitive بلغة جافا، مثل الأعداد الصحيحة. يعني ذلك أنه عند استخدِام العامل + مثلًا لجمع عددين صحيحين، تُنفَّذ العملية باستدعاء التابع المُعرَّف ضمن الصنف المُمثِل للأعداد الصحيحة. وبالمثل، عندما تُعرِّف صنفًا جديدًا، يُمكِنك تعرّيف العامل + ضمنه، ويمكنك بالتالي كتابة a + b لجمع كائناتٍ تنتمي إلى ذلك الصنف تمامًا مثلما تجمع أعدادًا. لنفترض الآن أنك تَكتُب برنامجًا فرعيًا يَستخدِم العامل + لجمع العناصر الموجودة ضمن قائمة list، حيث تستطيع أن تُطبِق ذلك البرنامج على قائمة أعدادٍ صحيحة أو على قائمةٍ من أي نوعٍ آخر يَتَضمَّن تعريفًا للعامل +. يكون الأمر نفسه إذا عرَّفت برنامجًا فرعيًا يَستخدِم العامل > لترتيب قائمة، حيث يُمكِنك تطبيقه على قوائمٍ تحتوي على أي نوعٍ من البيانات طالما يَتَضمَّن ذلك النوع تعريفًا للعامل >، أي ليس هناك حاجةً لكتابة برنامجٍ فرعي لكل نوعٍ مختلفٍ من البيانات. إذا توفَّرت الخاصيتان السابقتان ضمن لغةٍ معينة، يُمكِننا تطبيق الخوارزميات وبنى البيانات على أي نوعٍ من البيانات طالما كانت العمليات المناسبة مُعرَّفةً ضمن تلك الأنواع، حيث يُمثِل ذلك ماهية البرمجة المُعمَّمة. ربما ترى أن ذلك أمرًا مفيدًا دائمًا، وهو ما قد يدفعك إلى طرح السؤال التالي: لما لا تعمل كل اللغات البرمجية ببساطةٍ بنفس الأسلوب؟ في الحقيقة، في حين يُسهِل ذلك النوع من الحرية من كتابة البرامج، إلا أنه يُصعِّب مهمة كتابتها كتابةً صحيحةً متينة. إذا كان لديك مثلًا بنيةً بيانيةً قادرةً على حمل أي نوعٍ من البيانات، ستَجِد أنه من الصعب التأكُّد من كونها تَحمِل فقط النوع المطلوب من البيانات؛ وبالمثل، إذا كان لديك برنامجًا فرعيًا يُمكِنه ترتيب أي نوعٍ من البيانات، ستَجِد أنه من الصعب التأكُّد من تطبيقه فقط على تلك الأنواع التي تتضمَّن تعريفًا للعامل >. ليس هناك طريقةً تُمكِّن المُصرِّف compiler من التأكُّد من مثل تلك الأشياء، وعليه ستَظهَر المشكلة فقط أثناء زمن التشغيل run time، أي عند محاولة تطبيق عمليةٍ معينةٍ على نوع بياناتٍ لا يتضمَّن تعريفًا لتلك العملية، وعندها سينهار crash البرنامج. البرمجة المعممة بلغة C++ تُعدّ لغة C++ -بخلاف Smalltalk- لغةً صارمةً في تحديد النوع strongly typed، حيث يوجد لكل متغيرٍ variable نوعًا معينًا، ويُمكنه أن يَحمِل فقط قيمًا بيانيةً تنتمي إلى ذلك النوع؛ يعني ذلك أنه يستحيل تطبيق البرمجة المعمَّمة بنفس الكيفية المتوفرة بلغة Smalltalk. تتوفّر خاصية القوالب templates بلغة C++ مما يُمكِّنها من تطبيق نظامٍ آخر من البرمجة المُعمَّمة، حيث يُمكِنك ببساطةٍ كتابة قالب برنامجٍ فرعي subroutine template واحدٍ، بدلًا من كتابة برنامجٍ فرعي subroutine مختلف لترتيب كل نوعٍ من البيانات. لاحِظ أن القالب ليس برنامجًا فرعيًا، وإنما هو أشبه بمصنعٍ لإنشاء البرامج الفرعية. اُنظر المثال التالي: template<class ItemType> void sort( ItemType A[], int count ) { //1 for (int i = count-1; i > 0; i--) { int position_of_max = 0; for (int j = 1; j <= i ; j++) if ( A[j] > A[position_of_max] ) position_of_max = j; ItemType temp = A[i]; A[i] = A[position_of_max]; A[position_of_max] = temp; } } حيث تعني [1]: رتِّب عناصر المصفوفة A ترتيبًا تصاعديًا، حيث تُرتَّب العناصر الموجودة بالمواضع 0 و 1 و 2 وصولًا إلى count-1، وتُدعى الخوارزمية المُستخدَمة للترتيب باسم الترتيب الانتقائي selection sort. تُعرِّف الشيفرة بالأعلى قالب برنامجٍ فرعي؛ فإذا حذفت السطر الأول template<class ItemType>، واستبدلت كلمة int بكلمة ItemType بباقي شيفرة القالب، فستَكون النتيجة برنامجًا فرعيًا subroutine بإمكانه ترتيب مصفوفاتٍ من الأعداد الصحيحة. يُمكِنك في الواقع استبدال أي نوعٍ بالنوع ItemType بما في ذلك الأنواع الأساسية primitive types، حيث يمكنك مثلًا استبدال كلمة string بكلمة ItemType للحصول على برنامج فرعي يُرتِّب مصفوفات سلاسل نصية. هذه ببساطةٍ الطريقة التي يتعامل بها المُصرِّف مع أي قالب؛ فإذا كتبت sort(list,10) بالبرنامج، حيث list مصفوفة أعدادٍ صحيحة من النوع int، فسيستخدم المُصرِّف القالب لتوليد برنامجٍ فرعي لترتيب مصفوفة أعدادٍ صحيحة؛ إذا كتبت sort(cards,10)، حيث cards هي مصفوفة كائناتٍ objects من النوع Card، فسيولّد المُصرِّف برنامجًا فرعيًا لترتيب مصفوفة كائناتٍ من النوع Card. يَستخدِم القالب العامل > للموازنة بين القيم؛ فإذا كان ذلك العامل مُعرَّفًا للقيم من النوع Card، فسينجَح المُصرِّف بتوليد برنامجٍ فرعي لترتيب كانئات النوع Card بالاستعانة بالقالب؛ أما إذا لم يَكُن العامل > مُعرَّفًا للصنف Card، فسيَفشَل المُصرِّف أثناء زمن التصريف compile time وليس أثناء زمن التشغيل run time، مما يتسبَّب بانهيار البرنامج كما هو الحال بلغة Smalltalk. يُمكِنك كتابة تعريفاتٍ لعوامل مثل > لأي نوعٍ بلغة C++، أي يُمكِن للعامل > أن يَعمَل مع قيمٍ من النوع Card. توفَّر C++ قوالبًا للأصناف بالإضافة إلى قوالب البرامج الفرعية subroutine templates؛ فإذا كتبت قالبًا لصنف شجرة ثنائية، فسيُمكِنك استخدِامه لإنشاء أصناف أشجارٍ ثنائيةٍ مُكوَّنةٍ من أعدادٍ صحيحةٍ من النوع int، أو سلاسل نصية من النوع string، أو تواريخ، أو غير ذلك وجميعها بنفس القالب. تأتي الإصدارات الأحدث من C++ مصحوبةً بعددٍ كبيرٍ من القوالب المكتوبة مُسبقًا، فيما يُعرَف باسم مكتبة القوالب القياسية Standard Template Library -أو اختصارًا STL-، والتي يراها الكثير معقدةً للغاية، ولكنها تُعدُّ مع ذلك واحدةً من أكثر خاصيات C++ تشويقًا. البرمجة المعممة بلغة جافا Java مرّت البرمجة المُعمَّمة بلغة جافا بعدة مراحلٍ من التطوير، وفي حين لم تتضمَّن الإصدارات الأولى من اللغة خاصية الأنواع ذات المعامِلات غير محدَّدة النوع parameterized types، إلا أنها وفَّرت أصنافًا تُمثِل بنى البيانات data structures الشائعة؛ حيث صُمِّمت تلك الأصناف لتعمل مع النوع Object أي يُمكِنها أن تَحمِل أي نوعٍ من الكائنات objects. لم تكن هناك أي طريقةٍ لتخصيص أو قصر أنواع الكائنات المسموح بتخزينها ضمن بنيةٍ بيانيةٍ معينة، حيث لم يَكُن الصنف ArrayList مبدئيًا ضمن الأنواع ذات المعاملات غير محدَّدة النوع، أي كان من الممكن لأي مُتغيِّر من الصنف ArrayList أن يحمل أي نوعٍ من الكائنات. إذا كان list مثلًا متغيرًا من النوع ArrayList، فسيعيد list.get(i) قيمةً من النوع Object، وبالتالي إذا استخدم المبرمج المُتغيِّر list لتخزين سلاسلٍ نصية من النوع String، فسيتوجب عليه تحويل نوع type-cast القيمة المعادة من list.get(i) إلى سلسلةٍ نصية من النوع String على النحو التالي: String item = (String)list.get(i); يقع ذلك تحت تصنيف البرمجة المُعمَّمة؛ حيث يُمكِننا بالنهاية استخدام صنفٍ واحدٍ فقط للعمل مع أي نوعٍ من الكائنات، ولكنه في الواقع أشبه بلغة Smalltalk منه بلغة C++. وكما هو الحال مع لغة Smalltalk، سينتج عن ذلك حدوث الأخطاء أثناء زمن التشغيل لا زمن التصريف compile time؛ فإذا افترض مبرمجٌ مثلًا أن جميع العناصر الموجودة ضمن بنيةٍ بيانيةٍ معينة هي سلاسلٌ نصية strings، وحاول معالجتها بناءً على ذلك الأساس؛ فسيقع خطأٌ أثناء زمن التشغيل run time إذا احتوت تلك البنية على نوعٍ آخر من البيانات. عندما يحاول البرنامج أن يُحوِّل type-cast نوع قيمةٍ واقعةٍ ضمن بنيةٍ بيانيةٍ data structure إلى النوع String، سيقع خطأ من النوع ClassCastException بلغة جافا إذا لم تكن تلك القيمة من النوع String بالأساس. أضاف الإصدار الخامس من جافا خاصية الأنواع ذات المعاملات غير محدَّدة النوع، مما ساعد على إنشاء بنى بياناتٍ مُعمَّمة generic data structures يُمكِن فحص نوعها أثناء زمن التصريف لا زمن التشغيل. إذا كانت list مثلًا قائمةٌ من النوع ArrayList<String>، فسيَسمَح المُصرِّف بإضافة الكائنات التي تنتمي إلى النوع String فقط إلى تلك القائمة list. علاوةً على ذلك، تَكون القيمة المعادة من استدعاء list.get(i) من النوع String، ولهذا ليس من الضروري تحويلها إلى النوع الفعلي type-casting. تُعدّ الأصناف ذات المعاملات غير محدَّدة النوع بلغة جافا شبيهةً نوعًا ما بأصناف القوالب template classes بلغة C++، على الرغم من اختلاف طريقة التنفيذ. سنعتمد خلال هذا الفصل على تلك الأصناف، ولكن عليك أن تتذكَّر بأن استخدام تلك المعاملات ليس إلزاميًا عند التعامل مع تلك الأصناف، فما يزال بإمكانك استخدام صنفٍ ذو معاملاتٍ غير محدَّدة النوع كما لو لم يَكُن كذلك، مثل كتابة ArrayList، حيث يُمكِن لأي كائنٍ أن يُخزَّن ضمنه في تلك الحالة؛ وإذا كان ذلك ما تريده حقًا، فمن الأفضل كتابة النوع ArrayList<Object>. لاحِظ وجود فرقٍ كبير بين الأصناف ذات المعاملات غير محدَّدة النوع بلغة جافا Java وبين أصناف القوالب بلغة C++؛ حيث لا تُعدّ أصناف القوالب بلغة C++ أصنافًا من الأساس، ولكنها بمثابة مصانعٍ لإنشاء الأصناف. في كل مرةٍ نَستخدِم خلالها صنف قالبٍ template class معين مع نوعٍ جديد، فسيُصرِّف المُصرِّف صنفًا جديدًا؛ أما بالنسبة للغة جافا، فهناك ملف صنف مُصرَّف وحيد لكل صنفٍ ذو معاملاتٍ غير مُحدَّدة النوع، حيث يوجد مثلًا ملف صنفٍ وحيدٍ اسمه ArrayList.class للصنف ArrayList، وتَستخدِم أنواعٌ، مثل ArrayList<String> و ArrayList<Integer> نفس ذلك الملف المُصرَّف كما هو الحال مع النوع ArrayList العادي. يقتصر دور معامل النوع type parameter، مثل String أو Integer في الواقع على تبليغ المُصرِّف بوجوب تقييد نوع الكائنات المسموح بتخزينها ضمن تلك البنية البيانية، وليس له أي تأثيرٍ خلال زمن التشغيل، حيث يُقال عندها أن معلومة النوع قد حُذِفَت أثناء زمن التشغيل run time، مما يؤدي إلى الكثير من الأمور الغريبة نوعًا ما. لا يُمكِنك مثلًا فحص اختبارٍ مثل: if (list instanceof ArrayList<String>) لأن قيمة العامل instanceof تُحصَّل أثناء زمن التشغيل، ولا يوجد سوى الصنف العادي ArrayList أثناء زمن التشغيل. ولا يُمكِنك أيضًا إجراء تحويلٍ بين الأنواع type-cast إلى ArrayList<String>، بل حتى لا تستطيع استخدام العامل new لإنشاء مصفوفةٍ نوعها الأساسي base type هو ArrayList<String>، كأن تَكْتُب: new ArrayList<String>[N] لأن العامل new يُحصَّل أثناء زمن التشغيل، ولا يكون هناك شيءٌ اسمه ArrayList<String> في ذلك الوقت كما ذكرنا مُسبَقًا، وإنما فقط النوع العادي ArrayList بدون معاملاتٍ غير مُحدَّدة النوع non-parameterized. على الرغم من عدم القدرة على إنشاء مصفوفةٍ من النوع ArrayList<String>، يُمكِنك إنشاء قائمةٍ من النوع ArrayList تحتوي على قائمةٍ أخرى من النوع ArrayList<String>، ويُكتَب النوع على النحو التالي: ArrayList<ArrayList<String>> تظهَر تلك المشكلات لحسن الحظ فقط بالبرمجة المتقدمة نوعًا ما، حيث لا يُواجِه غالبية المبرمجين الذين يَستخدِمون الأصناف ذات المعاملات غير محدَّدة النوع تلك المشكلات، ويُمكِنهم الاستفادة من نموذج البرمجة المُعمَّمة وبأنواع بياناتٍ آمنة type-safe دون أي صعوبة. لاحِظ أنه في حالة كان المُصرِّف قادرًا على استنتاج اسم معامل النوع type parameter المُستخدَم بصنفٍ ذي معاملاتٍ غير مُحدَّدة النوع، يمكن عندها حَذْف اسم معامل النوع. نظرًا لأن المصفوفة المُنشأة في المثال التالي ستكون حتمًا من النوع ArrayList<String> لتتوافق مع نوع المُتغيِّر، فمن الممكن حَذْف كلمة String بتعليمة الباني constructor على النحو التالي: ArrayList<String> words = new ArrayList<>(); إطار جافا للتجميعات تُوفِّر جافا عدة أنواعٍ ذات معاملاتٍ غير مُحدَّدة النوع لتمثيل بنى البيانات الشائعة، حيث يُشار عادةً إلى تلك المجموعة من الأصناف والواجهات interfaces باسم إطار عمل جافا للتجميعات Java Collection Framework -أو اختصارًا JCF-، والتي سنناقشها خلال الأقسام القليلة التالية. يُمكِننا تقسيم بنى البيانات المُعمَّمة generic بإطار عمل جافا للتجميعات إلى تصنيفين، هما التجميعات collections والخرائط maps؛ حيث تشير التجميعة ببساطة إلى تجميعةٍ من الكائنات؛ أما الخريطة فهي تَربُط كائنات مجموعة بكائنات مجموعةٍ أخرى بنفس الأسلوب الذي يَربُط به القاموس التعريفات بالكلمات، أو يَربُط به دليل الهاتف أرقام الهواتف بأسماء الأشخاص. تُشبِه الخريطة القوائم المترابطة association list، التي ناقشناها بمقال البحث والترتيب في المصفوفات Array في جافا؛ حيث تُمثِل الواجهتين Collection<T> و Map<T,S> ذواتا المعاملات غير مُحدَّدة النوع التجميعات والخرائط بلغة جافا، بحيث تُشير T و S إلى أي نوعٍ باستثناء الأنواع الأساسية. تُعدّ الواجهة Map<T,S> مثالًا على الأنواع ذات المعاملات غير مُحدَّدة النوع، ويَملُك تحديدًا معاملي نوع type parameters، هما T و S. سنتناول خلال هذا المقال التجميعات، بينما سنناقش الخرائط تفصيليًا في مقال قادم. تُقسم التجميعات بدورها إلى نوعين، هما القوائم lists والأطقم sets؛ حيث تُخزِّن القوائم الكائنات الموجودة بها وفقًا لتسلسلٍ خطيٍ معين، وهذا يَعنِي أنه يُمكِننا الإشارة إلى العنصر الأول أو الثاني الموجود ضمن قائمةٍ معينة. علاوةً على ذلك، لا بُدّ أن يَتبَع أي عنصرٍ ضمن القائمة باستثناء العنصر الأخير عنصرًا آخرًا. في المقابل، لا يُمكِن لأي طقمٍ set أن يتضمَّن نفس الكائن أكثر من مرة، ولا تُعدّ العناصر الموجودة به مُرتَّبةً وفقًا لأي ترتيب، أي لا ينبغي أن تفكر بها على هذا الأساس. تُمثِل الواجهتان List<T> و Set<T> القوائم والأطقم على الترتيب، وهما مُشتقتان من الواجهة Collection<T>، وهذا يَعنِي أن الكائنات المُنفِّذة للواجهة List<T> أو Set<T> تُنفِّذ الواجهة Collection<T> أيضًا على نحوٍ تلقائي. تُخصِّص الواجهة Collection<T> العمليات العامة المُمكن تطبيقها على أي تجميعة؛ بينما تُخصِّص الواجهتان List<T> أو Set<T> أي عملياتٍ إضافيةٍ أخرى ضروريةً للقوائم والأطقم على الترتيب. لاحِظ أن أي كائن فعليّ سواءٌ كان تجميعةً، أو قائمةً، أو طقمًا set، لا بُدّ أن ينتمي إلى صنفٍ حقيقي concrete class يُنفِّذ الواجهة المقابلة. يُنفِّذ الصنف ArrayList<T> الواجهة List<T> على سبيل المثال، ويُنفِّذ بالتالي Collection<T>؛ وهذا يعني أننا نستطيع استخدام جميع التوابع المُعرَّفة بواجهتي القوائم والتجميعات مع النوع ArrayList. سنفحص أصنافًا مختلفة تُنفِّذ واجهتي الطقم والقائمة بالمقال التالي، ولكن قبل أن نفعل ذلك، سنناقش سريعًا بعضًا من العمليات العامة المتاحة بأي تجميعة. تُخصِّص الواجهة Collection<T> توابعًا لإجراء عددٍ من العمليات الأساسية على أي تجميعةٍ من الكائنات. بما أن التجميعة مفهومٌ عام، فإن العمليات التي يُمكِن تطبيقها عليها في العموم عامةٌ أيضًا؛ وهذا يعني أنها عملياتٌ مُعمَّمة أي قابلة للتطبيق على أنواعٍ مختلفة من التجميعات التي تحتوي بدورها على أنواعٍ مختلفة من الكائنات. فإذا كان coll كائنًا يُنفِّذ الواجهة Collection<T> على سبيل المثال، تَكُون العمليات التالية مُعرَّفةً له: coll.size(): تُعيد عددًا صحيحًا من النوع int يُمثِل عدد الكائنات الموجودة بالتجميعة. coll.isEmpty(): تُعيد قيمةً من النوع المنطقي boolean، حيث تَكُون مساويةً للقيمة true إذا كان حجم التجميعة يُساوي الصفر. coll.clear(): تَحذِف جميع الكائنات الموجودة بالتجميعة. coll.add(tobject): تُضيف tobject إلى التجميعة. لا بُدّ أن يَكون المعامل من النوع T، وإلا سيَحدُث خطأ في بناء الجملة syntax error أثناء زمن التصريف compile time. إذا كان T صنف، فإنه يَشمَل جميع الكائنات التي تنتمي لأي صنفٍ فرعي subclass مُشتقٍّ من T؛ أما إذا كان T واجهة، فإنه يَتضمَّن أي كائنٍ مُنفّذٍ لتلك الواجهة T. يُعيد التابع add() قيمةً من النوع المنطقي تُمثِّل فيما إذا كان التابع قد أجرى تعديلًا فعليًا على التجميعة أم لا؛ فإذا أضفت كائنًا إلى طقمٍ set معين، وكان ذلك الكائن موجودًا بالفعل ضمن ذلك الطقم، لا يكون للتابع أي تأثير. coll.contains(object): تُعيد قيمةً من النوع المنطقي، والتي تكون مساويةً القيمة true إذا كان object موجودًا بالتجميعة. لاحِظ أنه من غير الضروري أن يكون المُعامل object من النوع T؛ فقد ترغب بفحص ما إذا كان object موجودًا ضمن التجميعة بغض النظر عن نوعه. بالنسبة لعملية اختبار التساوي، تُعدّ القيمة الفارغة null مُساويةً لنفسها؛ أما بالنسبة للكائنات غير الفارغة، يختلف المقياس الذي يُحدَّد على أساسه تَساوِي تلك الكائنات من عدمه من نوع تجميعةٍ إلى آخر. coll.remove(object): تَحذِف object من التجميعة إذا كان موجودًا بها، وتُعيد قيمةً من النوع المنطقي تُحدِّد فيما إذا كان التابع قد عثر على object ضمن التجميعة أم لا. ليس من الضروري أن يكون المعامل object من النوع T. تُجرَى عملية اختبار التساوي بنفس الطريقة المُتبعَّة بالتابع contains. coll.containsAll(coll2): تُعيد قيمةً منطقيةً تَكُون مساويةً للقيمة true إذا كانت جميع كائنات التجميعة coll2 موجودةً أيضًا بالتجميعة coll، ويُمكِن للمعامل coll2 أن ينتمي لأي نوع تجميعة. coll.addAll(coll2): تُضيف جميع كائنات التجميعة coll2 إلى coll، حيث يُمكِن للمعامل coll2 أن يُمثِل أي تجميعةٍ من النوع Collection<T>، ولكنه قد يكون أيضًا أعم من ذلك. على سبيل المثال، إذا كان T صنف و S صنفٌ فرعي subclass مُشتقٌ من T، فقد يكون coll2 من النوع Collection<S>، وهو أمرٌ منطقي لأن أي كائن من النوع S هو بالضرورة من النوع T، وبالتالي يُمكِن إضافته إلى coll. coll.removeAll(coll2): تَحذِف أي كائنٍ من التجميعة coll إذا كان موجودًا أيضًا بالتجميعة coll2، حيث يُمكِن للمعامل coll2 أن ينتمي لأي نوع تجميعة. coll.retainAll(coll2): تَحذِف أي كائنٍ من التجميعة coll إذا لم يَكُن موجودًا بالتجميعة coll2؛ أي أنها تُبقِي فقط الكائنات غير الموجودة بالتجميعة coll2، ويُمكِن للمعامل coll2 أن ينتمي لأي نوع تجميعة. coll.toArray(): تُعيد مصفوفةً من النوع Object[] تحتوي على جميع عناصر التجميعة. نلاحظ أن النوع المُعاد من التابع هو Object[] وليس T[]. هناك في الواقع نسخةٌ أخرى من نفس التابع coll.toArray(tarray)، والتي تَستقبِل مصفوفةً من النوع T[] مثل مُعامل وتُعيد مصفوفةً من النوع T[] تحتوي على جميع عناصر التجميعة. في حال كانت المصفوفة المُمرَّرة tarray كبيرةً بما يكفي لحَمْل جميع عناصر التجميعة، فسيُخزَّن التابع العناصرأيضًا بنفس المصفوفة المُمرَّرة ويُعيدها مثل قيمة للتابع؛ أما إذا لم تَكُن المصفوفة كبيرة بما يكفي، يُنشِئ التابع مصفوفةً جديدةً لحمل تلك العناصر، ويَقتصِر في تلك الحالة دور المصفوفة المُمرَّرة tarray على تحديد نوع المصفوفة المُعادة فقط. يُمكِننا مثلًا استدعِاء coll.toArray(new String[0]) إذا كان coll تجميعةً من السلاسل النصية من النوع String، وسيُعيد الاستدعاء السابق بناءً على ذلك مصفوفةً جديدةً من النوع String. طالما أنّ التوابع السابقة مُعرَّفةٌ ضمن الواجهة Collection<T>، فإنها بطبيعة الحال مُعرَّفةٌ ضمْن كل كائنٍ يُنفِّذ تلك الواجهة، ولكن هناك مشكلة، حيث لا يُمكِن تغيير حجم بعض أنواع التجميعات بعد إنشائها، وبالتالي لا يَكون للتوابع المسؤولة عن إضافة الكائنات وحذفها معنىً بالنسبة لتلك التجميعات على الرغم من أنه ما يزال من الممكن استدعائها، ويَحدُث في تلك الحالة اعتراضٌ exception من النوع UnsupportedOperationException أثناء زمن التشغيل. إضافةً لذلك ونظرًا لأن Collection<T> هي واجهة interface وليست صنفًا حقيقيًا concrete class، فإن التنفيذ الفعلي للتابع متروكٌ للأصناف المُنفِّذة للواجهة؛ مما يعني عدم امكانية ضمَان توافق الدلالة الفعلية لتلك التوابع مع ما شرحناه بالأعلى لجميع تجميعات الكائنات من خارج إطار عمل جافا للتجميعات Java Collection Framework. بالنسبة لكفاءة تلك العمليات، فليس من الضروري أن تعمل عمليةٌ معينةٌ بنفس كفاءة أنواعٍ مختلفةٍ من التجميعات؛ حيث أنها بالنهاية مُعرَّفةٌ بكل تجميعةٍ على حدى. يَنطبِق ذلك حتى على أبسط التوابع مثل size()التي فقد تختلف كفائتها تمامًا من تجميعةٍ لأخرى؛ حيث من الممكن أن يتضمَّن تحصيل قيمة التابع size() عَدّ العناصر الموجودة بالتجميعة بالنسبة لبعض أنواع التجميعات، ويكون عندها عدد خطوات العملية مُساويًا لعدد عناصر التجميعة؛ وقد يحتفظ نوعٌ آخر من التجميعات بمتغيرات نسخ instance variables تُحدِّد حجمها الحالي، وعندها يقتصر تحصيل قيمة التابع size() على إعادة قيمة مُتغيِّر، أي يَستغرِق تنفيذ العملية خطوةً واحدةً فقط بغض النظر عن عدد عناصر التجميعة. بناءً على ما سبق، لا بُدّ من الانتباه دائمًا لكفاءة العمليات، واختيار التجميعة بحيث تكون العمليات التي ستجريها أكثر من غيرها ذات الكفاءة الأعلى، وسنرى عدة أمثلة على ذلك في المقالين التاليين. المكررات وحلقات التكرار for-each تُعرِّف الواجهة Collection<T> بعض الخوارزميات المُعمَّمة البسيطة، ولكن كيف يختلف ذلك عن كتابة خوارزميةٍ مُعمَّمةٍ خاصةٍ جديدة؟ لنفترض مثلًا أننا نريد طباعة جميع العناصر الموجودة ضمن التجميعة. لنُنفِّذ ذلك تنفيذًا مُعمَّمًا، نحتاج إلى طريقةٍ ما للمرور عبر جميع عناصر التجميعة واحدًا تلو الآخر. رأينا طريقة فعل ذلك لبعض بنى البيانات data structure؛ فإذا كان لدينا مصفوفةٌ مثلًا، فإننا نستطيع ببساطة استخدام حلقة التكرار for للمرور عبر جميع فهارسها indices. تُعدُّ القائمة المترابطة linked list مثالًا آخر، حيث يُمكِننا المرور عبر عناصرها باستخدام حلقة التكرار while، بحيث نُحرِّك ضمن تلك الحلقة مؤشرًا على طول القائمة. بالنسبة للشجرة الثنائية binary tree، يُمكِننا استخدام برنامجٍ فرعيٍ تعاودي recursive لإجراء ما يُعرَف باسم اجتياز في الترتيب inorder traversal؛ أما بالنسبة للتجميعة collection، فيمكننا تمثيلها بأيٍ مما سبق، وبالتالي علينا الإجابة عن السؤال التالي: كيف سنستطيع كتابة تابعٍ مُعمَّمٍ واحدٍ يُمكِنه العمل مع تجميعاتٍ يُمكِن تخزينها بصيغٍ مختلفةٍ تمامًا؟ يَكْمُن حل تلك المشكلة فيما يُعرَف باسم المُكرِّرات iterators؛ وهو ببساطةٍ كائنٌ يُمكِن استخدامه لاجتياز تجميعة. تختلف طريقة تنفيذ المُكرِّر بحسب نوع التجميعة، لكنها تستخدَم جميعًا بنفس الأسلوب. وبالتالي، أيُّ خوارزميةٍ يعتمد اجتيازها لعناصر التجميعة على وجود مُكرِّر هي خوارزمية مُعمَّمة؛ لأننا ببساطة سنتمكَّن من تطبيقها على أي نوعٍ من التجميعات. قد تبدو فكرة المُكرِّرات غريبةً نوعًا ما خاصةً إذا كانت هذه هي المرة الأولى التي تتعرَّض خلالها للبرمجة المُعمَّمة، ولكن عليك أن تدرك أنها ستساعدك على حل بعض أصعب المشكلات بطريقةٍ أنيقة. تُعرِّف الواجهة Collection<T> تابعًا يُمكِننا استخدامه للحصول على المُكرِّر iterator لأي تجميعة. إذا كانت coll تجميعة، فسيعيد coll.iterator() مُكرِّرًا يُمكِننا اِستخدَامه لاجتياز عناصر التجميعة. يُمكِنك التفكير بالمُكرِّر كما لو كان نوعًا عامًا من المؤشرات يبدأ من مقدمة التجميعة، وبإمكانه التحرُّك على طول التجميعة من عنصرٍ إلى آخر. تُعرَّف المُكرِّرات عن طريق واجهةٍ ذات معاملات غير مُحدَّدة النوع parameterized interface اسمها Iterator<T>. إذا نفِّذت coll الواجهة Collection<T> للنوع T، فسيعيد استدعاء coll.iterator() مُكرِّرًا من النوع Iterator<T>، حيث تُشير T إلى معامل النوع type parameter. تُعرِّف الواجهة Iterator<T> ثلاثة توابع فقط. إذا كان tier يُشير إلى كائن مُنفِّذ للواجهة Iterator<T>، يكون لدينا التوابع التالية: iter.next(): يُعيد العنصر التالي، ويُقدِّم المُكرِّر خطوةً للأمام، وتكون القيمة المُعادة من النوع T. يسمح لك التابع بفحص أحد عناصر التجميعة. لاحِظ أنه لا توجد طريقةٌ لفحص عنصرٍ دون أن يَمُر المُكرِّر عبره خطوةً للأمام. إذا استدعينا هذا التابع ولم يَكن هناك أي عناصرٍ متبقية ضمن التجميعة، فسيحدث اعتراضٌ من النوع NoSuchElementException. iter.hasNext(): يُعيد قيمةً منطقيةً تُمثِل فيما إذا كان هناك عناصرٌ جاهزةٌ متبقيةٌ للمعالجة. ينبغي استدعاء هذا التابع عمومًا قبل استدعاء iter.next(). iter.remove(): إذا استدعيت هذا التابع بعد iter.next()، فسيحذِف العنصر الذي رأيته للتو من التجميعة. لا يستقبل هذا التابع أي مُعاملات، ويَحذِف آخر عنصرٍ أعاده التابع iter.next()، مما قد يؤدي إلى اعتراضٍ من النوع UnsupportedOperationException في حال لم تدعم تلك التجميعة حذف العناصر. نستطيع كتابة شيفرة لطباعة كل العناصر الموجودة بأي تجميعة بالاستعانة بالمُكرِّرات iterators. لنفترض مثلًا أن coll من النوع Collection<String>، وبالتالي سيعيد التابع coll.iterator() قيمةً من النوع Iterator<String>، ويُمكِننا كتابة ما يلي: Iterator<String> iter; // صرِّح عن المُكرِّر iter = coll.iterator(); // استرجع مُكررًا للتجميعة while ( iter.hasNext() ) { String item = iter.next(); // اقرأ العنصر التالي System.out.println(item); } ستَعمَل الصيغة العامة السابقة مع أي أنواعٍ أخرى من المعالجة، حيث تَحذِف الشيفرة التالية مثلًا جميع القيم الفارغة null من أي تجميعةٍ من النوع Collection<Color>، طالما كانت التجميعة تدعم حذف القيم: Iterator<Color> iter = coll.iterator(): while ( iter.hasNext() ) { Color item = iter.next(); if (item == null) iter.remove(); } لاحِظ أنه عند استخدامنا أنواعًا، مثل Collection<T>، أو Iterator<T>، أو أي نوعٍ آخر ذا معاملاتٍ غير مُحدَّدة النوع ضمن شيفرةٍ فعلية، فإننا نستخدمها دائمًا مع أنواعٍ فعليةٍ، مثل String، أو Color في موضع معامل النوع الصوري T؛ حيث يُستخدَم مثلًا مُكرِّرٌ من النوع Iterator<String> للمرور عبر عناصر تجميعة سلاسلٍ نصيةٍ من النوع String؛ بينما يُستخدَم مُكرِّرٌ من النوع Iterator<Color> للمرور عبر عناصر تجميعةٍ من النوع Color وهكذا. تُستخدَم المُكرِّرات عادةً لتطبيق نفس العملية على جميع عناصر تجميعةٍ معينة، ولكن يمكننا استخدام حلقة التكرار for-each بدلًا من المُكرِّر في كثيرٍ من الحالات. كنا قد ناقشنا طريقة استخدام حلقة for-each مع المصفوفات بمقال تعرف على المصفوفات (Arrays) في جافا، ومع النوع ArrayList بمقال المشار إليه سلفًا، ويُمكِنها أن تُستخدَم أيضًا للمرور عبر عناصر أي تجميعة. على سبيل المثال، إذا كان coll تجميعةً من النوع Collection<T>، تُكْتَب حلقة for-each بالصياغة التالية: for ( T x : coll ) { // لكل كائن x من النوع T بالتجميعة coll // عالج x } تمثِّل x بالأعلى مُتغيِّرًا مُتحكِّمًا بالحلقة loop control variable، وسيُسند كل كائنٍ بالتجميعة coll إلى x، وسيُطبَق متن body الحلقة عليه. صرَّحنا عن x لتَكون من النوع T، لأن الكائنات الموجودة بالتجميعة coll من النوع T. إذا كانت namelist تجميعةً من النوع Collection<String> مثلًا، يُمكِننا طباعة جميع الأسماء الموجودة بالتجميعة على النحو التالي: for ( String name : namelist ) { System.out.println( name ); } يُمكِننا بالطبع كْتابة حلقة while مع مُكرِّر بدلًا من حلقة for-each، ولكن الأخيرة أسهل بالقراءة. التساوي Equality والموازنة Comparison تتضمَّن الواجهة Collection عدة توابعٍ methods لفحص تَساوِي كائنين. يبحث التابعان coll.contains(object) و coll.remove(object) على سبيل المثال عن عنصرٍ يُساوِي object ضمن التجميعة. لا يُعد اختبار تساوي كائنين أمرًا بسيطًا كما قد تظنّ، ولا يُعطِي العامل == دائمًا إجاباتٍ معقولةً عند تطبيقه على الكائنات؛ لأنه يَفحَص فيما إذا كان الكائنان متطابقين أي إذا كانا بنفس موضع الذاكرة memory location، وهو ما لا نعنيه عادةً عندما نرغب بفْحَص تَساوِي كائنين، وإنما نَعنِي ما إذا كانا يحملان نفس القيمة، وهو أمرٌ مختلفٌ كليًا. إذا كان لدينا مثلًا قيمتين من النوع String، فلا بُدّ أن نَعُدّهما متساويين إذا تضمَّنا نفس متتالية المحارف بغض النظر عن وجودهما بنفس موضع الذاكرة من عدمه؛ وإذا كان لدينا قيمتين من النوع Date، فلا بُدّ أن نَعُدّهما متساويين إذا كانا يُمثِلان نفس التوقيت. يُعرِّف الصنف Object تابعًا اسمه equals(Object) بهدف فَحْص تساوي كائنين، وبحيث يؤدي نفس دور الاختبار التالي obj1 == obj2، ثم يُعيد قيمةً من النوع المنطقي boolean. تَستخدم كثيرًا من أصناف التجميعات ذلك التابع، ومع ذلك، لا يُعدّ هذا التعريف مناسبًا لكثيرٍ من الأصناف الفرعية المُشتقَّة من الصنف Object، وبالتالي يَنبغي أن يُعاد تعريفه overridden، حيث يُعيد الصنف String مثلًا تعريف التابع equals() بحيث تَكون قيمة str.equals(obj) لسلسلةٍ نصية str مساويةً للقيمة المنطقية true إذا كان obj من النوع String وكان يحتوي على نفس متتالية المحارف الموجود بالسلسلة النصية str. إذا أضفت صنفًا جديدًا، فينبغي أن يحتوي تعريفه على إعادة تعريفٍ للتابع equals() لتحصل على السلوك المطلوب عند فَحص تساوي كائنين من ذلك الصنف. يُمكِننا على سبيل المثال تعريف صنف Card على النحو التالي لنتمكَّن من اِستخدَامه داخل تجميعة collection: public class Card { // صنف لتمثيل ورق اللعب private int suit; // عدد من 0 إلى 3 لتمثيل بطاقات الكوبة والبستوني // والسباتي والديناري private int value; // عدد من 1 إلى 13 لتمثيل قيمة الورقة public boolean equals(Object obj) { try { Card other = (Card)obj; // Type-cast obj to a Card. if (suit == other.suit && value == other.value) { // 1 return true; } else return false; } catch (Exception e) { // 2 return false; } } . . // توابع أخرى وبناةٌ آخرين . } حيث تعني كل من: [1] تملك الورقة الأخرى نفس القيمة والرمز الخاص بهذه الورقة، لذلك يُمكن عدّهما متساويين. [2] سيلتقط الاعتراض NullPointerException الذي يَحدُث إذا كان obj فارغًا، وكذلك الاعتراض ClassCastException الذي يَحدُث إذا لم يَكُن obj من النوع Card. في تلك الحالات، لا يكون obj مُساوٍ لورقة اللعب 'Card'، لذلك أعِد false. لاحِظ أنه في حالة عدم وجود التابع equals() داخل الصنف، لن تعمل توابعٌ، مثل contains() و remove() المُعرَّفة بالواجهة Collection<Card> على النحو المُتوقَّع. يَنطبِق الأمر نفسه على عملية ترتيب العناصر الموجودة ضمن تجميعة، أي عملية ترتيبها تصاعديًا وفقًا لمعيارٍ معين. ليس هناك مفهومٌ بديهيٌ لمعنى الترتيب التصاعدي لعدِّة كائناتٍ objects عشوائية، ولهذا لا بُدّ من إضافة تابعٍ آخر قبل محاولة ترتيب تلك العناصر، بحيث يكون ذلك التابع مسؤولًا عن الموازنة بين تلك العناصر. يجب أن يُنفِّذ أيُّ كائنٍ تَنوِي استخدامه ضمن عملية موازنة الواجهة java.lang.Comparable. لاحِظ أن تلك الواجهة مُعرَّفةٌ مثل واجهةٍ ذات معاملات غير محدَّدة النوع Comparable<T>، مما يعني إمكانية الموازنة مع كائنٍ من النوع T. تُعرِّف الواجهة Comparable<T> التابع التالي: public int compareTo( T obj ) يُعيد الاستدعاء obj1.compareTo(obj2) قيمةً سالبةً، إذا كان obj1 يَأتِي قبل obj2 عندما تكون الكائنات مُرتَّبة ترتيبًا تصاعديًا؛ ويُعيد قيمةً موجبةً، إذا كان obj2 يَأتِي قبل obj1؛ وإذا كان الكائنان مُتساوِيين وفقًا للغرض من الموازنة، يُعيد التابع صفرًا. لا يعني ذلك بالضرورة أن الكائنين مُتساويان وفقًا للتابع الآخر obj1.equals(obj2). فإذا كانت الكائنات قيد الموازنة من النوع Address على سبيل المثال، فقد ترغب بترتيبها وفقًا للرقم البريدي، بحيث تَكُون العناوين ذات نفس الرقم البريدي مُتساوية، ولا يَعنِي ذلك أن تلك العناوين هي نفسها. يُنفِّذ الصنف String الواجهة Comparable<String>، ويُعرِّف التابع compareTo بحيث يُعيد صفرًا فقط إذا كانت السلسلتان النصيتان قيد الموازنة متساويتين. إذا عرَّفت صنفًا خاصًا جديدًا، وكنت ترغب بترتيب الكائنات المنتمية لذلك الصنف، فينبغي أن تَفعَل الشيء نفسه. اُنظر المثال التالي: // 4 public class FullName implements Comparable<FullName> { private String firstName, lastName; // الاسم الأول والأخير غير الفارغين public FullName(String first, String last) { // الباني if (first == null || last == null) throw new IllegalArgumentException("Names must be non-null."); firstName = first; lastName = last; } public boolean equals(Object obj) { try { FullName other = (FullName)obj; // Type-cast obj to type FullName return firstName.equals(other.firstName) && lastName.equals(other.lastName); } catch (Exception e) { return false; // إذا كان `obj` فارغًا أو لم يكن من النوع FullName } } public int compareTo( FullName other ) { if ( lastName.compareTo(other.lastName) < 0 ) { // 1 return -1; } else if ( lastName.compareTo(other.lastName) > 0 ) { // 2 return 1; } else { // 3 return firstName.compareTo(other.firstName); } } . . // توابع أخرى . } وتشير العناصر الآتية إلى: [1]: إذا جاء lastName قبل الاسم الأخير للكائن الآخر، فسيأتي FullName لهذا الكائن قبل FullName للكائن الآخر، ولذلك أعِد قيمةً سالبة. [2]: إذا جاء lastName بعد الاسم الأخير للكائن الآخر، فسيأتي FullName لهذا الكائن بعد FullName للكائن الآخر، ولذلك أعد قيمةً موجبة. [3]: الاسم الأخير لكلا الكائنين هو نفسه، ولذلك سنوازن بين أسمائهما الأولى باستخدام التابع compareTo المُعرَّف بالصنف String. [4]: يمثِّل هذا الصنف الاسم الكامل المُكوَّن من اسمٍ أول واسمٍ أخير. لاحِظ أن الصنف مُعرَّفٌ على النحو التالي class FullName implements Comparable<FullName>، وقد يبدو استخدام كلمة FullName مثل معامل نوع type parameter ضمن اسم الواجهة غريبًا بعض الشيء ولكنه صحيح؛ حيث يعني أننا ننوي موازنة الكائنات المنتمية إلى الصنف FullName مع كائناتٍ أخرى من نفس النوع. قد ترى أنه من البديهي أن تكون عملية الموازنة مع كائنٍ من نفس النوع، ولكنها ليست كذلك بالنسبة لمُصرِّف جافا، ولهذا أضفنا معامل النوع إلى اسم الواجهة على النحو التالي Comparable<FullName>. تُوفِّر جافا طريقةً أخرى لموازنة الكائنات عبر إضافة كائنٍ آخر يكون قادرًا على إجراء الموازنة، ويجب على ذلك الكائن تنفيذ الواجهة Comparator<T>، حيث T هي نوع الكائنات المطلوب موازنتها. تُعرِّف تلك الواجهة التابع التالي: public int compare( T obj1, T obj2 ) يُوازن التابع السابق كائنين من النوع T، حيث يُعيد قيمةً سالبةً أو موجبةً أو صفرًا اعتمادًا على ما إذا كان obj1 يَسبِق obj2، أو إذا كان obj1 يَلحَق obj2، أو إذا كان يُمكِن عَدّهما مُتساويين فيما يتعلق بالموازنة. تُستخدَم تلك الواجهة عادةً لموازنة الكائنات التي لا تُنفِّذ الواجهة Comparable، وكذلك لتخصيص أساليب ترتيبٍ مختلفة لنفس تجميعة الكائنات. لاحِظ أنه نظرًا لأن Comparator هو واجهة من نوع دالة functional interface، وتُستخدَم غالبًا تعبيرات لامدا lambda expressions لتعريفها (انظر مقال تعبيرات لامدا (Lambda Expressions) في جافا). سنناقش خلال المقالين التاليين طريقة استخدام Comparable و Comparator بالتجميعات والخرائط. الأنواع المعممة والأصناف المغلفة لا يُمكِننا تطبيق نموذج البرمجة المُعمَّمة generic programming بلغة جافا على الأنواع الأساسية primitive types كما ذكرنا بمقال مفهوم المصفوفات الديناميكية (ArrayLists) في جافا. أثناء حديثنا عن الصنف ArrayList؛ حيث يمكن لبنى البيانات data structures المُعمَّمة أن تَحمِل كائناتٍ فقط وليست الأنواع الأساسية كائنات. تستطيع في المقابل الأصناف المُغلِّفة wrapper classes، التي تعرَّضنا لها بمقال مفهوم المصفوفات الديناميكية ArrayLists في جافا أن تتجاوز ذلك القيد إلى حدٍ بعيد. يقابل كل نوعٍ أساسي صنفًا مُغلّفًا wrapper class، حيث يوجد لدينا مثلًا الصنف Integer للنوع int؛ والصنف Boolean للنوع boolean؛ والصنف Character للنوع char، وهكذا. يحتوي أي كائنٍ من النوع Integer على قيمةٍ من النوع int، حيث يعمل الكائن ببساطة مثل مغلِّف wrapper لقيمة النوع الأساسي، ويسمح هذا باستخدام النوع الأساسي ضمن سياقاتٍ تتطلّب بالأساس كائناتٍ مثل بنى البيانات المُعمَّمة generic data structures. يُمكِننا على سبيل المثال تخزين قائمة أعدادٍ صحيحة من النوع Integer بمُتغيّرٍ من النوع ArrayList<Integer>، وستكون واجهاتٌ، مثل Collection<Integer> و Set<Integer> مُعرَّفة. يُعرِّف الصنف Integer علاوةً على ذلك التوابع التالية: equals(). compareTo(). toString(). حيث تُنفِّذ ما ينبغي إجراؤه بما يتناسب مع النوع الأساسي المقابل. تنطبق الأمور نفسها على جميع الأصناف المُغلِّفة wrapper classes. تُجري لغة جافا تحويلًا تلقائيًا بين الأنواع الأساسية primitive types وما يُقابِلها من أنواعٍ مُغلِّفة. يعني ذلك أنه بمجرد إنشاء بنية بياناتٍ مُعمَّمة تحمل كائناتٍ تنتمي إلى إحدى الأصناف المُغلَّفة، فمن الممكن استخدام بنى البيانات كما لو كان بإمكانها حَمل قيمٍ من النوع الأساسي. إذا كان numbers مُتغيّرًا من النوع Collection<Integer>، فبإمكانك كتابة numbers.add(17)، أو numbers.remove(42)، ولا يُمكِنك حرفيًا إضافة قيمةٍ من النوع الأساسي مثل 17 إلى numbers؛ وإنما تُحوِّل جافا تلك القيمة تلقائيًا إلى كائنٍ مُغلِّف مُقابِل، أي Integer.valueOf(17)، ثم تُضيف ذلك الكائن إلى التجميعة. تُؤثِر عملية إنشاء كائنٍ جديدٍ على كلٍ من الوقت والذاكرة المُستهلَكين أثناء العملية، وهو ما ينبغي أن تَضعُه بالحسبان تحديدًا إذا كنت مهتمًا بكفاءة البرنامج. تُعدّ مصفوفةٌ من النوع int عمومًا أكثر كفاءةً من مصفوفةٍ من النوع ArrayList<Integer>. ترجمة -بتصرّف- للقسم Section 1: Generic Programming من فصل Chapter 10: Generic Programming and Collection Classes من كتاب Introduction to Programming Using Java. اقرأ أيضًا المقال السابق: تصميم محلل نموذجي تعاودي بسيط Recursive Descent Parser في جافا واجهة المستخدم الحديثة في جافا الأصناف المتداخلة Nested Classes في جافا1 نقطة
-
يوجد مجموعة من الكتب الإنجليزية لتعلم بايثون ومنها: كتاب Think Python: كتاب مجاني ومناسب للمبتدئين، هو عبارة عن مقدمة لبرمجة Python للمبتدئين. يبدأ بالمفاهيم الأساسية للبرمجة، وهو مصمم بعناية لتحديد جميع المصطلحات عند استخدامها لأول مرة ولتطوير كل مفهوم جديد في تقدم منطقي. يتم تقسيم الأجزاء الأكبر حجمًا، مثل البرمجة الموجهة والكائنات العودية recursion إلى سلسلة من الخطوات الأصغر ويتم تقديمها على مدار عدة فصول. كتاب How to Code in Python: كتاب مجاني أيضًا، كتاب مقدم من شركة DigitalOcean (شركة مختصة في الإستضافة والتخزين السحابي)، تم تصميم هذا الكتاب ليتم استخدامه بطريقة منطقية بالنسبة للمبتدئ. بمجرد أن تتعرف على المفاهيم الأساسية المشروحة في الكتاب، يمكنك الاستمرار في استخدام الكتاب كمصدر مرجعي. كتاب Python for Everybody: الهدف من هذا الكتاب المجاني هو تقديم مقدمة في البرمجة. في هذا الكتاب يتم التركيز بشكل أكبر على استخدام Python لحل مشكلات تحليل البيانات الشائعة في عالم المعلوماتية. ملاحظة: تحتوي الأكاديمة على كتاب البرمجة بلغة بايثون، وهو كتاب مترجم إلى العربية مبني على كتاب «How to code in Python» ويأتي شارحًا المفاهيم البرمجية الأساسية بلغة بايثون، رُبط هذا الكتاب مع توثيق لغة بايثون في موسوعة حسوب لتسهيل عملية الاطلاع على أي جزء من اللغة مباشرة وقراءة التفاصيل باللغة العربية. كما يوجد مجموعة ضخمة من المقالات في Python يمكنك الإطلاع عليها من هنا (مقالات بايثون).1 نقطة
-
بشكل إفتراضي يتم تخزين السجلات logs في نظام Linux في أحد المسارات التالية (حسب بنية النظام لديك): /var/log/nginx/nginx_error.log /var/log/nginx/error.log يمكنك عرض آخر جزء من السجل من خلال أمر tail، كالتالي: tail -f /var/log/nginx/error.log لاحظ: يجب تغير المسار في الأمر السابق حسب المسار الذي لديك أما في MacOS فالمسار مختلف وهو: /usr/local/var/log/nginx أما في Windows فيختلف المسار حسب مكان تثبيت الخادم لكن يمكنك الوصول من خلال تنفيذ الأمر التالي: nginx -s reopen أما إن أردت أن يقوم Django بحفظ السجلات فيجب عليك أن تقوم بتعديل ملف settings.py لشمل الكود كالتالي: LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': os.path.join(BASE_DIR, 'debug.log'), }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'DEBUG', 'propagate': True, }, }, }1 نقطة
-
في البداية يجب التفريق بين تحيليل البيانات Data analysis وعلم البيانات Data science، والواضح هو أن علم البيانات أشمل وأعم، ويمكن تعريف كل منهما كالتالي: علم البيانات Data science: هو التعامل مع البيانات الغير المنظمة، فهو مزيج من الإحصاءات، والرياضيات، والبرمجة، وحل المشكلات، وجمع البيانات بطرق مختلفة (مثل سحب البيانات web scraping أو الإقتراعات polls .. إلخ)، أي أنّه علم يضم جميع التقنيات التي تقوم بإستخلاص كل المعلومات من البيانات، وهو مفهوم يستخدم للتعامل مع البيانات الكبيرة Big Data، يغطي هذا المفهوم جوانب إعداد البيانات وتنظيفها وتحليلها. تحليل البيانات Data analysis: هو عملية فحص للبيانات الموجودة بهدف إستخلاص معلومات مفيدة، يمكن أن تكون لمعرفة أسباب أو تفسير لشيء من الماضي من أجل أهداف حاضرة أو مستقبلية، ويستلزم تحليل البيانات الخروج بإحصائيات للوصول إلى نتيجة معينة، يحتاج محلل البيانات لمعرفة كيفية العمل مع الأرقام، يُنظر إلى تحليلات البيانات على أنها أهم فرع في علوم البيانات. البيانات الضخمة Big Data: تُعتبَر البيانات الضخمة أنها كميات هائلة جدًا من البيانات تتزايد وتكبر بإستمرار، وليس من الممكن معالجة هذه البيانات أو حتى التعامل معها، وجمعها استنادًا على الطرق التقليدية بسبب كبر حجمها، حيث يتطلب هذا النوع من البيانات أساليب مُبتكرة لمعالجتها. وفي العادة يتم إستعمال هياكل البيانات Data Structure والخوارزميات Algorithms للتعامل معها. يجب أن يكون عالم البيانات على دراية بالأمور التالية: الرياضيات المتقدمة، مثل الجبر الخطي، والمصفوفات، والإحصاء. لغات البرمجة: بالطبع سيحتاج عالم البيانات أن يكون على دراية واسعة بأحد لغات البرمجة مثل Python و matlab أو R أو C++ إدارة البيانات: يجب أن يكون بإمكان كل عالم بيانات أن يدير البيانات وينظمها أو حتى أن يقوم بتجميعها من خلال العديد من الطرق، مثل سحب البيانات web scraping أو عمل إستطلاعات/إقتراعات Polls، حيث لن يكون تجميع البيانات بشكل يدوي وفردي عملي على الإطلاق. الخوارزميات وهياكل البيانات: من الضروري تعلم الخوارزميات وكيفية عملها وأشهرها مثل خوارزميات البحث والترتيب، وكذلك هياكل البيانات، لأن بدونها سيكون التعامل مع البيانات الضخمة Big Data أمرًا بطيئًا للغاية وغير عملي بالمرة. يتم تدريس الأمور السابقة في شكل مواد متعددة وتختلف تسمية وعدد المواد من جامعة إلى أخرى. إن لم يكن لديك معرفة مسبقة في البرمجة، فأنصحك بأن تبدأ بأحد الدورات التي تهيء لك الأمر في البداية، ويوجد العديد من الدورات التي تقدم هذا المحتوى منها المجاني والمدفوع، مثل دورة CS50 (دورة مجانية، تعد بداية جيدة لمن لا يتقن أي لغة برمجة) أو دورة علوم الحاسوب مقدمة من حسوب (دورة مدفوعة، تحتوي على أغلب التقنيات السابقة، مثل أساسيات البرمجة ولغة JavaScript و Python والخوارزميات وهياكل البيانات وغيرها). بعد ذلك يجب أن تتعمق أكثر في لغة برمجة مثل Python (الأكثر إستعمالًا في الوقت الحالي في علوم البيانات)، كما يجب أن تتقن الرياضيات مثل الجبر الخطي والإحصاء والتعامل مع المصفوفات (يوجد مجموعة المكتبات التي تساعدك في هذا الأمر مثل Numpy لبايثون). يمكنك أيضًا أن تبحث عن مشاريع مفتوحة المصدر على GitHub لقراءة الكود المصدر Source Code لمشاريع عديدة وكبيرة، مما يعطيك فكرة ممتازة عن كيفية عمل مشاريع من الصفر وكيف يتم تطويرها، كما قد تحصل على بعض أفكار لمشاريع مستقبلية من هذه الخطوة.1 نقطة
-
1 نقطة