تسلسل الكائنات في Java: من الأساسيات إلى التقنيات المتقدمة
\n\nالتسلسل هو عملية تحويل حالة كائن إلى تيار من البايتات لحفظه (في ملف، قاعدة بيانات) أو نقله عبر الشبكة. العملية العكسية — إلغاء التسلسل — تستعيد الكائن من تيار البايتات. في Java، هذه الآلية مدمجة في المنصة وتستخدم على نطاق واسع في الأنظمة الموزعة، والتخزين المؤقت، وتطبيقات الويب. في هذه المقالة، سنشرح كيفية عمل التسلسل، وما هي الواجهات والفئات المستخدمة، وكيفية تنفيذ التسلسل المخصص بشكل صحيح.
\n\nالأساسيات: واجهة Serializable
\n\nلكي يمكن تسلسل كائن، يجب أن تنفذ فئته واجهة العلامة java.io.Serializable. هذه الواجهة لا تحتوي على طرق — فهي فقط تخبر الآلة الافتراضية بأنه مسموح بتسلسل الكائن.
import java.io.Serializable;\n\npublic class User implements Serializable {\n private static final long serialVersionUID = 1L;\n private String name;\n private int age;\n\n public User(String name, int age) {\n this.name = name;\n this.age = age;\n }\n\n @Override\n public String toString() {\n return "User{name='" + name + "', age=" + age + "}";\n }\n}\n\nالحقل serialVersionUID هو معرف فريد لإصدار الفئة. إذا لم يتم تعريفه بشكل صريح، فستقوم JVM بحسابه تلقائيًا، ولكن هذا قد يؤدي إلى InvalidClassException عند إلغاء التسلسل إذا تغيرت الفئة. يُوصى دائمًا بتعيينه يدويًا.
عملية التسلسل وإلغاء التسلسل
\n\nلكتابة كائن إلى تيار، يُستخدم ObjectOutputStream، وللقراءة يُستخدم ObjectInputStream. لنلق نظرة على مثال أساسي:
import java.io.*;\n\npublic class SerializationDemo {\n public static void main(String[] args) {\n User user = new User("أليكسي", 30);\n\n // التسلسل\n try (FileOutputStream fos = new FileOutputStream("user.ser");\n ObjectOutputStream oos = new ObjectOutputStream(fos)) {\n oos.writeObject(user);\n System.out.println("تم تسلسل الكائن: " + user);\n } catch (IOException e) {\n e.printStackTrace();\n }\n\n // إلغاء التسلسل\n try (FileInputStream fis = new FileInputStream("user.ser");\n ObjectInputStream ois = new ObjectInputStream(fis)) {\n User restoredUser = (User) ois.readObject();\n System.out.println("تم استعادة الكائن: " + restoredUser);\n } catch (IOException | ClassNotFoundException e) {\n e.printStackTrace();\n }\n }\n}\n\nعند إلغاء التسلسل، لا يتم استدعاء مُنشئ الكائن — يتم استعادة الحالة مباشرة من التيار. هذا فرق مهم عن إنشاء الكائن العادي باستخدام new.
الكلمة المفتاحية transient
\n\nأحيانًا يكون من الضروري استبعاد بعض الحقول من التسلسل، مثل كلمات المرور أو الحسابات المؤقتة. لهذا، يُستخدم المُعدِّل transient. الحقول المميزة به تحصل على القيم الافتراضية (0، null، false) عند التسلسل.
public class UserWithPassword implements Serializable {\n private static final long serialVersionUID = 1L;\n private String login;\n private transient String password; // لن يتم تسلسله\n\n public UserWithPassword(String login, String password) {\n this.login = login;\n this.password = password;\n }\n\n @Override\n public String toString() {\n return "User{login='" + login + "', password='" + password + "'}";\n }\n}\n\nبعد إلغاء التسلسل، سيكون الحقل password بقيمة null. هذا مناسب للأمان، ولكنه يتطلب منطقًا إضافيًا للاستعادة.
التسلسل المخصص: طريقتي writeObject و readObject
\n\nإذا كان السلوك القياسي غير مناسب، يمكن إعادة تعريف الطرق الخاصة