Serializable
هل قامت بوظيفتها، وما الذي لا يعجبك في التنفيذ التلقائي للعملية برمتها؟ وكانت الأمثلة التي نظرنا إليها أيضًا غير معقدة. إذا ما هي المشكلة؟ لماذا نحتاج إلى واجهة أخرى لنفس المهام بشكل أساسي؟ والحقيقة هي أن Serializable
لديه العديد من أوجه القصور. نذكر بعضًا منها:
-
أداء. تتمتع الواجهة
Serializable
بالعديد من المزايا، ولكن من الواضح أن الأداء العالي ليس واحدًا منها.أولاً،
Serializable
يُنشئ التنفيذ الداخلي لـ s كمية كبيرة من معلومات الخدمة وجميع أنواع البيانات المؤقتة.ثانيًا،
Serializable
يعتمد على Reflection API (ليس عليك التعمق في هذا الأمر الآن؛ يمكنك قراءة المزيد في وقت فراغك، إذا كنت مهتمًا). يتيح لك هذا الشيء القيام بالأشياء التي تبدو مستحيلة في Java: على سبيل المثال، تغيير قيم الحقول الخاصة. يحتوي CodeGym على مقالة ممتازة حول Reflection API . يمكنك أن تقرأ عنها هناك. -
المرونة. نحن لا نتحكم في عملية التسلسل وإلغاء التسلسل عندما نستخدم الواجهة
Serializable
.من ناحية، إنه مريح للغاية، لأنه إذا لم نكن مهتمين بشكل خاص بالأداء، فيبدو من الجيد ألا نضطر إلى كتابة التعليمات البرمجية. ولكن ماذا لو كنا بحاجة حقًا إلى إضافة بعض الميزات الخاصة بنا (سنقدم مثالاً أدناه) إلى منطق التسلسل؟
في الأساس، كل ما علينا التحكم في العملية هو الكلمة
transient
الرئيسية لاستبعاد بعض البيانات. هذا كل شيء. هذا هو صندوق أدواتنا بأكمله :/ -
حماية. هذا البند مشتق جزئيا من البند السابق.
لم نقضي الكثير من الوقت في التفكير في هذا الأمر من قبل، ولكن ماذا لو كانت بعض المعلومات في صفك غير مخصصة لأعين وآذان الآخرين المتطفلين؟ مثال بسيط هو كلمة المرور أو بيانات المستخدم الشخصية الأخرى، والتي تخضع في عالم اليوم لمجموعة من القوانين.
إذا استخدمنا
Serializable
، لا يمكننا فعل أي شيء حيال ذلك. نحن نتسلسل كل شيء كما هو.ولكن إذا قمنا بذلك بالطريقة الصحيحة، فيجب علينا تشفير هذا النوع من البيانات قبل كتابتها إلى ملف أو إرسالها عبر الشبكة. ولكن
Serializable
لا يجعل هذا ممكنا.
Externalizable
.
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long SERIAL_VERSION_UID = 1L;
// ...constructor, getters, setters, toString()...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
كما ترون، لدينا تغييرات كبيرة! السبب الرئيسي واضح: عند تنفيذ Externalizable
الواجهة، يجب عليك تنفيذ طريقتين مطلوبتين: writeExternal()
و readExternal()
. وكما قلنا سابقًا، فإن مسؤولية التسلسل وإلغاء التسلسل تقع على عاتق المبرمج. ولكن الآن يمكنك حل مشكلة عدم السيطرة على العملية! تتم برمجة العملية برمتها مباشرة بواسطتك. وبطبيعة الحال، هذا يسمح بآلية أكثر مرونة. بالإضافة إلى ذلك، تم حل مشكلة الأمان. كما ترون، يحتوي فصلنا على حقل بيانات شخصية لا يمكن تخزينه بدون تشفير. الآن يمكننا بسهولة كتابة التعليمات البرمجية التي تلبي هذا القيد. على سبيل المثال، يمكننا أن نضيف إلى فصلنا طريقتين خاصتين بسيطتين لتشفير وفك تشفير البيانات الحساسة. سنقوم بكتابة البيانات إلى الملف وقراءتها من الملف في شكل مشفر. سيتم كتابة بقية البيانات وقراءتها كما هي :) ونتيجة لذلك، يبدو صفنا كما يلي:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long serialVersionUID = 1L;
public UserInfo() {
}
public UserInfo(String firstName, String lastName, String superSecretInformation) {
this.firstName = firstName;
this.lastName = lastName;
this.superSecretInformation = superSecretInformation;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.getFirstName());
out.writeObject(this.getLastName());
out.writeObject(this.encryptString(this.getSuperSecretInformation()));
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
firstName = (String) in.readObject();
lastName = (String) in.readObject();
superSecretInformation = this.decryptString((String) in.readObject());
}
private String encryptString(String data) {
String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
System.out.println(encryptedData);
return encryptedData;
}
private String decryptString(String data) {
String decrypted = new String(Base64.getDecoder().decode(data));
System.out.println(decrypted);
return decrypted;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSuperSecretInformation() {
return superSecretInformation;
}
}
لقد قمنا بتنفيذ طريقتين تستخدمان نفس ObjectOutput
المعلمات ObjectInput
التي التقينا بها بالفعل في الدرس حول Serializable
. في اللحظة المناسبة، نقوم بتشفير أو فك تشفير البيانات المطلوبة، ونستخدم البيانات المشفرة لإجراء تسلسل لكائننا. دعونا نرى كيف يبدو هذا في الممارسة العملية:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
UserInfo userInfo = new UserInfo("Paul", "Piper", "Paul Piper's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
في encryptString()
الأساليب decryptString()
، أضفنا على وجه التحديد مخرجات وحدة التحكم للتحقق من النموذج الذي سيتم كتابة البيانات السرية وقراءتها. يعرض الكود أعلاه السطر التالي: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh نجح التشفير! تبدو المحتويات الكاملة للملف كما يلي: ¬н sr UserInfoГ!}͐џC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx الآن دعونا نحاول استخدام منطق إلغاء التسلسل الخاص بنا.
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
حسنًا، لا شيء يبدو معقدًا هنا. يجب أن تعمل! نقوم بتشغيله ونحصل على... استثناء في مؤشر الترابط "الرئيسي" java.io.InvalidClassException: UserInfo؛ لا يوجد مُنشئ صالح عفوًا! :( على ما يبدو، الأمر ليس بهذه السهولة! ألقت آلية إلغاء التسلسل استثناءً وطلبت منا إنشاء مُنشئ افتراضي. أتساءل لماذا. مع ، Serializable
مررنا بمنشئ واحد... :/ هنا واجهنا فارقًا بسيطًا مهمًا آخر. الفرق بين Serializable
و Externalizable
لا يكمن فقط في وصول المبرمج "الموسع" والقدرة على التحكم في العملية بشكل أكثر مرونة، ولكن أيضًا في العملية نفسها. قبل كل شيء، في آلية إلغاء التسلسل . عند الاستخدام Serializable
، يتم تخصيص الذاكرة ببساطة للكائن، و ثم تتم قراءة القيم من المجرى واستخدامها لتعيين حقول الكائن. إذا استخدمنا Serializable
، فلن يتم استدعاء مُنشئ الكائن! كل العمل يحدث من خلال الانعكاس (واجهة API للانعكاس، والتي ذكرناها باختصار في الدرس الأخير) Externalizable
. "آلية إلغاء التسلسل مختلفة. يتم استدعاء المُنشئ الافتراضي أولاً. فقط بعد ذلك يتم استدعاء طريقة UserInfo
الكائن الذي تم إنشاؤه readExternal()
. وهو مسؤول عن تعيين حقول الكائن. ولهذا السبب يجب أن يكون لدى أي فئة تنفذ Externalizable
الواجهة مُنشئ افتراضي . دعونا نضيف واحدًا إلى فصلنا UserInfo
ونعيد تشغيل الكود:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
إخراج وحدة التحكم: بيانات جواز سفر Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'بيانات جواز سفر Paul Piper' } الآن هذا شيء مختلف تمامًا! أولاً، تم عرض السلسلة التي تم فك تشفيرها والتي تحتوي على معلومات سرية على وحدة التحكم. ثم تم عرض الكائن الذي استردناه من الملف كسلسلة! لقد نجحنا في حل جميع المشكلات :) يبدو موضوع التسلسل وإلغاء التسلسل بسيطًا، ولكن، كما ترون، كانت الدروس طويلة. وهناك الكثير الذي لم نغطيه! لا يزال هناك العديد من التفاصيل الدقيقة عند استخدام كل من هذه الواجهات. ولكن لتجنب انفجار عقلك من المعلومات الجديدة المفرطة، سأدرج بإيجاز بعض النقاط الأكثر أهمية وأعطيك روابط لقراءة إضافية. إذًا، ما الذي تحتاج إلى معرفته أيضًا؟ أولاً ، أثناء عملية التسلسل (بغض النظر عما إذا كنت تستخدم Serializable
أو Externalizable
)، انتبه إلى static
المتغيرات. عند استخدام Serializable
، لا يتم إجراء تسلسل لهذه الحقول على الإطلاق (وبالتالي، لا تتغير قيمها، لأن static
الحقول تنتمي إلى الفئة، وليس الكائن). ولكن عند استخدامك Externalizable
، فإنك تتحكم في العملية بنفسك، لذا يمكنك من الناحية الفنية إجراء تسلسل لها. ولكننا لا ننصح بذلك، نظرًا لأنه من المحتمل أن يؤدي ذلك إلى حدوث الكثير من الأخطاء الدقيقة. ثانيًا ، يجب أيضًا الانتباه إلى المتغيرات التي تحتوي على final
المُعدِّل. عند استخدامك Serializable
، يتم إجراء تسلسل لها وإلغاء تسلسلها كالمعتاد، ولكن عند استخدامك Externalizable
، فمن المستحيل إلغاء تسلسل final
متغير ! السبب بسيط: final
تتم تهيئة كافة الحقول عند استدعاء المنشئ الافتراضي - وبعد ذلك، لا يمكن تغيير قيمتها. لذلك، لإجراء تسلسل للكائنات التي تحتوي على final
حقول، استخدم التسلسل القياسي الذي يوفره Serializable
. ثالثًا ، عند استخدام الوراثة، Externalizable
يجب أن تحتوي جميع الفئات التابعة التي ترث بعض الفئات أيضًا على مُنشئات افتراضية. هنا رابط لمقالة جيدة حول آليات التسلسل:
حتى المرة القادمة! :)
المزيد من القراءة: |
---|
GO TO FULL VERSION