JavaRush /בלוג Java /Random-HE /ממשק להחצנה ב-Java

ממשק להחצנה ב-Java

פורסם בקבוצה
שלום! היום נמשיך את ההקדמה שלנו ליצירת אובייקטי ג'אווה בהמשכה וביטול סדרה. בהרצאה האחרונה הכרנו את ממשק ה- Serializable marker , הסתכלנו על דוגמאות לשימוש בו, וגם למדנו כיצד לשלוט בתהליך ההמשכה באמצעות מילת המפתח החולפת . ובכן, "לנהל את התהליך", כמובן, זו מילה חזקה. יש לנו מילת מפתח אחת, מזהה גרסה אחד, וזה בעצם הכל. שאר התהליך "מחובר" בתוך Java, ואין גישה אליו. מנקודת מבט של נוחות, זה, כמובן, טוב. אבל מתכנת בעבודתו צריך להתמקד לא רק בנוחות שלו, נכון? :) ישנם גורמים נוספים שיש לקחת בחשבון. לכן, Serializable הוא לא הכלי היחיד להסדרה-deserialization ב-Java. היום נכיר את הממשק Externalizable . אבל עוד לפני שעברנו ללמוד אותו, אולי יש לך שאלה סבירה: למה אנחנו צריכים עוד כלי? Serializableהתמודדתי עם העבודה שלי, והיישום האוטומטי של התהליך כולו לא יכול שלא לשמוח. גם הדוגמאות שראינו לא היו מסובכות. אז מה הקטע? למה ממשק נוסף לאותה משימה בעצם? העובדה היא Serializableשיש לזה מספר חסרונות. בואו נרשום כמה מהם:
  1. ביצועים. לממשק Serializableיתרונות רבים, אך ברור שביצועים גבוהים אינם אחד מהם.

היכרות עם הממשק הניתן להחצנה - 2

ראשית , המנגנון הפנימי Serializableמייצר כמות גדולה של מידע שירות וסוגים שונים של נתונים זמניים במהלך הפעולה.
שנית (אתה לא צריך להיכנס לזה עכשיו ולקרוא בנוח אם אתה מעוניין), העבודה Serializableמבוססת על שימוש ב-Reflection API. המתקן הזה מאפשר לך לעשות דברים שנראים בלתי אפשריים ב-Java: למשל, לשנות את הערכים של שדות פרטיים. ל-JavaRush יש מאמר מצוין על Reflection API , תוכלו לקרוא עליו כאן.

  1. גְמִישׁוּת. אנחנו לא שולטים בתהליך הסידרה-דה-סריאליזציה בכלל בעת השימוש ב- Serializable.

    מצד אחד, זה מאוד נוח, כי אם לא באמת אכפת לנו מהביצועים, היכולת לא לכתוב קוד נראית נוחה. אבל מה אם אנחנו באמת צריכים להוסיף כמה מהתכונות שלנו (דוגמה של אחת מהן תהיה להלן) להיגיון הסדרתי?

    בעיקרו של דבר, כל מה שיש לנו כדי לשלוט בתהליך הוא מילת מפתח transientלהחריג נתונים מסוימים, וזהו. בערך כמו "ערכת כלים" :/

  2. בְּטִיחוּת. נקודה זו נובעת חלקית מהקודמתה.

    לא חשבנו על זה הרבה בעבר, אבל מה אם מידע מסוים בכיתה שלך אינו מיועד ל"אוזניים של אנשים אחרים" (ליתר דיוק, עיניים)? דוגמה פשוטה היא סיסמה או נתונים אישיים אחרים של המשתמש, שבעולם המודרני מוסדרים על ידי חבורה של חוקים.

    באמצעות Serializable, אנחנו למעשה לא יכולים לעשות שום דבר בנידון. אנחנו מסדרים הכל כמו שהוא.

    אבל, בצורה טובה, עלינו להצפין סוג זה של נתונים לפני כתיבתם לקובץ או שידורם דרך הרשת. אבל Serializableזה לא נותן את ההזדמנות הזו.

היכרות עם הממשק הניתן להחצנה - 3ובכן, בוא נראה סוף סוף איך תיראה כיתה באמצעות ה- 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; אין בנאי חוקי היכרות עם הממשק הניתן להחצנה - 4 אופס :( התברר שזה לא כל כך פשוט! מנגנון הדה-סריאליזציה זרק חריג ודרש מאיתנו ליצור בנאי ברירת מחדל. מעניין למה? 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ממחלקה כלשהי חייבות להיות גם בוני ברירת מחדל. להלן כמה קישורים למאמרים טובים על מנגנוני סדרה: נתראה! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION