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()
and methods 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
אתה שולט בתהליך בעצמך, כך שטכנית ניתן לעשות זאת. אבל זה לא מומלץ, מכיוון שזה כרוך בטעויות עדינות. שנית , יש לשים לב גם למשתנים עם ה-modifier final
. כשמשתמשים בהם, Serializable
הם מסודרים וסידריאליים כרגיל, אבל כשמשתמשים בהם, אי אפשר Externalizable
לבטל final
משתנה ! הסיבה פשוטה: כל final
השדות מאותחלים כאשר נקרא בנאי ברירת המחדל, ולאחר מכן לא ניתן לשנות את הערך שלהם. לכן, כדי לעשות סדרה של אובייקטים המכילים final
שדות, השתמש בסריאליזציה רגילה באמצעות Serializable
. שלישית , בעת שימוש בירושה, כל המחלקות היורשתות שיורדות Externalizable
ממחלקה כלשהי חייבות להיות גם בוני ברירת מחדל. להלן כמה קישורים למאמרים טובים על מנגנוני סדרה:
נתראה! :)
GO TO FULL VERSION