Javaمفهوم المزامنة في جافا
- مزامنة حالة الكائن في جافا
- خطوات الـ Serialization في جافا
- خطوات الـ Deserialization في جافا
- مثال شامل حول المزامنة في جافا
- مفهوم الثابت serialVersionUID في جافا
مزامنة حالة الكائن في جافا
في معظم البرامج التي نستخدمها, نلاحظ أنه يوجد مكان خاص لضبط إعدادات البرنامج يسمح للمستخدم أن يخصص البرنامج بالشكل الذي يريده.
بعد أن يقوم المستخدم بضبط إعدادت برنامجه, نلاحظ أن هذه الإعدادات تظل محفوظة في البرنامج حتى إذا خرج من البرنامج و فتحه من جديد.
فمثلاً في البرامج التي تستخدم للكتابة, تجد في صفحة الإعدادات أنه بإمكانك تغير أنواع الخطوط المستخدمة في البرنامج, و تغيير أحجامها و ألوانها. و وقد تجد في الإعدادات أيضاً أنه بإمكانك إستخدام التطبيق بأكثر من لغة إلخ..
بالنسبة للألعاب, غالباً ما تجد أنه في صفحة الإعدادات يمكنك تحديد ما إذا كنت تريد سماع نغمات حماسية أثناء اللعب أم لا, بالإضافة إلى إمكانية تحديد مستوى الصوت. أيضاً أحياناً تجد أنه يمكنك تحديد مستوى اللعب ( سهل - متوسط - صعب - صعب جداً ).
للحفاظ على إعدادات البرنامج التي ضبطها المستخدم, نقوم بحفظ هذه الإعدادات بداخل ملف و هذا ما ستتعلمه في هذا الدرس.
ملاحظة: عليك فهم درس التعامل مع الملفات جيداً حتى تستطيع فهم هذا الدرس, لأننا سنقوم بتخزن المعلومات في ملف.
شكل الكائن في الذاكرة
أثناء تشغيل البرنامج, كل كائن يتم إنشاءه فيه, يتم تمثيله في الذاكرة كسلسلة كبيرة من الـ Bytes يفهمها نظام التشغيل.
هذه السلسلة تحتوي على جميع معلومات الكائن, مثل:
- الكلاس المشتق منه.
- كل كلاس يرث منه.
- كل إنترفيس يطبقه.
- كل دالة يملكها.
- كل متغير يملكه, بالإضافة إلى نوعه و قيمته الحالية إلخ..
عند تنفيذ جميع الأوامر الموجودة في البرنامج أو عند الخروج منه, يتم مسح جميع البيانات المتعلقة بهذا البرنامج من الذاكرة لأنه لم يعد لها حاجة. أي يتم مسح جميع الكائنات, المتغيرات و الدوال, و بالتالي يتوقف أي إتصال قائم بين البرنامج و الأشياء خارجية مثل ملف, شبكة أو قاعدة بيانات إلخ..
مفهوم الـ Serialization و الـ Deserialization
Serialization تعني حفظ حالة الكائن الحالية بداخل ملف.
عندما نقول: "حفظ حالة الكائن", فنحن بذلك نقصد إنشاء نسخة مطابقة من الكائن الموجود في الذاكرة و وضعها في ملف خارجي.
Deserialization تعني استرجاع حالة الكائن الموجودة في ملف.
عندما نقول: "استرجاع حالة الكائن", فنحن بذلك نقصد خلق الكائن الموجود في ملف خارجي بداخل ذاكرة الجهاز.
ملاحظة: بشكل عام عندما نحفظ حالة الكائن و نسترجعها, نقول أننا نفعل Serialization و لكننا فعلياً نفعل Serialization + Deserialization.
أهمية الـ Serialization
- حفظ حالة الكائن الذي تم إنشاءه في الذاكرة في ملف خارجي.
- حالة الكائن المحفوظة في ملف يمكن إستخدامها متى شئنا لخلق الكائن من جديد في الذاكرة.
- مشاركة حالة الكائن عبر شبكة, حيث أنه يمكن استخدام الملف الذي حفظنا فيه حالة الكائن لخلق الكائن في جهاز آخر.
- تخزين الصور في قواعد البيانات ( الصورة تحفظ في قاعدة البيانات كـ BLOB ).
إذاً في حال أردت حفظ معلومات الكائن قبل الخروج من البرنامج يمكنك إنشاء ملف متزامن يحفظ لك حالة الكائن, و بعدها يمكنك استرجاعها عند تشغيل البرنامج من جديد.
تطبيق الـ Serialization و الـ Deserialization
لتحقيق الـ Serialization, نستخدم الكلاس ObjectOutputStream لإنشاء نسخة من الكائن الموجود في الذاكرة و وضعها في ملف.
لتحقيق الـ Deserialization, نستخدم الكلاس ObjectInputStream لخلق الكائن المحفوظ في الملف في الذاكرة من جديد.
كل كلاس منهم يملك عدة كونستركتورات و دوال, سنشرح فقط الأشياء التي سنستخدمها في هذا الدرس.
خطوات الـ Serialization في جافا
لإنشاء كائن من كلاس معين و حفظ حالته عليك اتباع الخطوات التالية:
- الكائن الذي تريد حفظ حالته, يجب أن يكون في الأساس مشتق من كلاس يفعل implements للإنترفيس Serializable.
- إنشاء ملف إمتداده .ser بواسطة الكلاس FileOutputStream.
- تجهيز كائن من الكلاس ObjectOutputStream الذي يستخدم لكتابة حالة الكائن في الملف.
- نسخ حالة الكائن الموجود في الذاكرة في هذا الملف بواسطة الدالة writeObject().
- عند الإنتهاء من عملية النسخ, نقوم بقطع كل إتصال قمنا بإجرائه مع هذا الملف.
الكلمة المحجوزة transient
في حال أردت عدم نسخ جميع الأشياء المتعلقة بالكائن في الذاكرة, عليك وضع الكلمة transient في تعريف كل شيء لا تريده أن ينسخ في الملف, و عندها سيتم تجاهله.
خطوات الـ Deserialization في جافا
لإسترجاع حالة الكائن التي تم حفظها في ملف معين, عليك اتباع الخطوات التالية:
- إنشاء كائن فارغ من نفس نوع الكائن الذي نريد إستراجع حالته من الملف.
- تجهيز كائن من الكلاس FileInputStream الذي يستخدم لإدخال بيانات ملف محدد في الذاكرة.
- تجهيز كائن من الكلاس ObjectInputStream ليعيد خلق الكائن في الذاكرة.
- قراءة حالة الكائن بواسطة الدالة readObject() و تخزينها في الكائن الفارغ الذي قمنا بإنشائه في الخطوة الأولى, و هنا سيكون عليك أن تفعل Downcasting لتحول نوع الكائن الذي ترجعه الدالة readObject() إلى نوع الكائن الحقيقي لأنها ترجع الكائن الموجود في الذاكرة كـ Object و ليس كنوعه الحقيقي.
- عند الإنتهاء من عملية النسخ, نقوم بقطع كل إتصال قمنا بإجرائه مع هذا الملف.
مثال شامل حول المزامنة في جافا
في المثال التالي قمنا بتعريف كلاس إسمه Editor, يطبق الإنترفيس Serializable, و يملك المتغيرات التالية:
language, encoding, fontSize, fontFamily, autoSave, autoComplete, direction.
المتغير direction قمنا بتعريفه كـ transient لأننا لا نريد أن يتم حفظ قيمته عندما نفعل Serialization.
بعدها قمنا بتعريف كلاس آخر إسمه Main قمنا فيه بتطبيق مبدأي الـ Serialization و الـ Deserialization.
من السطر 22 إلى السطر 52 قمنا بتطبيق مبدأ الـ Deserialization.
من السطر 59 إلى السطر 90 قمنا بتطبيق مبدأ الـ Serialization.
الملف الذي قمنا بتخزين حالة الكائن فيه قمنا بتسميته user-prefrences.ser.
عند تشغيل البرنامج سيتم إنشاؤه في المجلد الذي يحتوي على المشروع.
إنتبه: في حال ظهرت لك مشكلة في الكلاس Editor قم فقط بإضافة الكود التالي بداخل حدود الكلاس, أي في السطر رقم 4 أو في السطر رقم 12 و سنشرح لك معنى هذا السطر لاحقاً.
private static final long serialVersionUID = 1L;
مثال
في المرة الأولى التي تقوم فيها بتشغيل البرنامج ستحصل على النتيجة التالية.
في المرة الثانية التي تقوم فيها بتشغيل البرنامج ستحصل على النتيجة التالية.
بما أنه قد تم إنشاء الملف user-prefrences.ser بنجاح, يمكنك البحث عنه و فتحه بواسطة أي محرر و عندها ستتمكن من رؤية شكل المعلومات التي كانت مسجلة في الذاكرة.
لاحظ أنه لم يتم حفظ قيمة المتغير direction في الملف لأننا قمنا بتعريفها كـ transient, لذلك تم إعطائه القيمة null كقيمة إفتراضية.
مفهوم الثابت serialVersionUID في جافا
كل كلاس يطبق الإنترفيس Serializable يتم إعطاءه رقم إصدار خاص فيه.
هذا الرقم يتم تخزينه في المتغير serialVersionUID.
إذاً كل كلاس يطبق الإنترفيس Serializable, يملك متغير إسمه serialVersionUID حتى لو لم يتم تعريفه.
رقم الإصدار يضمن أن المرسل و المستقبل للملف على الشبكة يملكون نفس نسخة الكلاس للكائن المحفوظ في الملف.
في حال كان رقم الإصدار في كلاس المرسل مختلف عن رقم الإصدار في كلاس المستقبل يتم رمي إستثناء من النوع InvalidClassException.
إذاً رقم الإصدار serialVersionUID مهم جداً عند بناء تطبيق يشارك البيانات بين سيرفر و عميل, أي يوجد تطبيق على السيرفر و تطبيق عند المستخدم العادي مرتبطان مع بعضهما البعض. سنرى ذلك في الدرس التالي.
بشكل عام, تعريف المتغير serialVersionUID ليس أمراً إجبارياً في حال كنت تبني برنامج لا تشارك فيه البيانات مع برنامج آخر, لأن جافا أصلاً ستقوم بتعريفه عنك في حال لم تقم بتعريفه بنفسك, لكن في بعض بيئات العمل مثل بيئة Eclipse, نلاحظ أن الـ Complier يظهر تحذير في حال لم نقم بتعريف المتغير serialVersionUID من جديد في البرنامج, لذلك ننصحك بتعريفه في جميع الحالات لأنه لن يؤثر أصلاً على الكود.
طريقة تعريف الثابت serialVersionUID
في البداية يمكنك وضع أي Access Modifier و لن يشكل ذلك أي فرق هنا, لكنك مجبر على تعريفه كـ static final long.
مثال
في الإصدار الأول من الكلاس Editor وضعنا قيمته 1L
في الإصدار الثاني من الكلاس Editor وضعنا قيمته 2L