Serializable
لقد تعاملت مع وظيفتي، والتنفيذ التلقائي للعملية برمتها لا يمكن إلا أن نفرح. الأمثلة التي نظرنا إليها لم تكن معقدة أيضًا. إذن ما هو الاتفاق؟ لماذا واجهة أخرى لنفس المهمة في الأساس؟ والحقيقة هي أن Serializable
لديها عددا من العيوب. دعونا قائمة بعض منهم:
-
أداء. تتمتع الواجهة
Serializable
بالعديد من المزايا، ولكن من الواضح أن الأداء العالي ليس واحدًا منها.
أولاً ، تقوم الآلية الداخلية Serializable
بتوليد كمية كبيرة من معلومات الخدمة وأنواع مختلفة من البيانات المؤقتة أثناء التشغيل.
ثانيًا (ليس عليك الخوض في هذا الآن والقراءة في وقت فراغك إذا كنت مهتمًا)، Serializable
يعتمد العمل على استخدام Reflection API. تتيح لك هذه الأداة الغريبة القيام بأشياء قد تبدو مستحيلة في Java: على سبيل المثال، تغيير قيم الحقول الخاصة. لدى JavaRush مقالة ممتازة حول 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;
//...конструктор, геттеры, сеттеры, 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 out
المعلمات 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("Ivan", "Ivanov", "Ivan Ivanov'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
لا يتم استدعاء منشئ الكائن!كل العمل يتم من خلال الانعكاس (Reflection 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();
}
}
إخراج وحدة التحكم: بيانات جواز سفر إيفان إيفانوف UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='بيانات جواز سفر إيفان إيفانوف'} مسألة مختلفة تمامًا! أولاً، تم إخراج السلسلة التي تم فك تشفيرها مع البيانات السرية إلى وحدة التحكم، ثم تم استرداد كائننا من الملف بتنسيق سلسلة! هذه هي الطريقة التي نجحنا في حل جميع المشكلات بها :) يبدو أن موضوع التسلسل وإلغاء التسلسل بسيط، ولكن كما ترون، تبين أن محاضراتنا طويلة. وهذا ليس كل شيء! هناك العديد من التفاصيل الدقيقة عند استخدام كل من هذه الواجهات، ولكن حتى لا ينفجر عقلك الآن من حجم المعلومات الجديدة، سأدرج بإيجاز بعض النقاط الأكثر أهمية وأقدم روابط لقراءة إضافية. إذن ماذا تحتاج إلى معرفته؟ أولاً ، عند إجراء التسلسل (لا يهم ما إذا كنت تستخدم Serializable
أو Externalizable
)، انتبه إلى المتغيرات static
. عند استخدامها، Serializable
لا يتم إجراء تسلسل لهذه الحقول على الإطلاق (وبالتالي، لا تتغير قيمتها، لأن static
الحقول تنتمي إلى الفئة، وليس الكائن). ولكن عند استخدامه، Externalizable
يمكنك التحكم في العملية بنفسك، لذلك يمكن القيام بذلك من الناحية الفنية. لكن لا ينصح بذلك، لأن هذا محفوف بالأخطاء الدقيقة. ثانيًا ، ينبغي أيضًا الانتباه إلى المتغيرات التي تحتوي على المُعدِّل final
. عند استخدامها، Serializable
يتم تسلسلها وإلغاء تسلسلها كالمعتاد، ولكن عند استخدامها، من المستحيل Externalizable
إلغاء final
تسلسل متغير ! السبب بسيط: final
تتم تهيئة جميع الحقول عند استدعاء المنشئ الافتراضي، وبعد ذلك لا يمكن تغيير قيمتها. لذلك، لإجراء تسلسل للكائنات التي تحتوي final
على حقول، استخدم التسلسل القياسي عبر Serializable
. ثالثًا ، عند استخدام الوراثة، يجب أن تحتوي جميع الفئات الموروثة التي تنحدر من Externalizable
فئة معينة أيضًا على مُنشئات افتراضية. فيما يلي بعض الروابط لمقالات جيدة حول آليات التسلسل:
أرك لاحقًا! :)
GO TO FULL VERSION