JavaRush /בלוג Java /Random-HE /מה מסתיר השינוי החולף ב-Java?
Анзор Кармов
רָמָה
Санкт-Петербург

מה מסתיר השינוי החולף ב-Java?

פורסם בקבוצה
שלום! במאמר של היום, נסתכל על השינוי החולף בג'אווה. בואו נדבר על מדוע יש צורך בשינוי זה וכיצד להשתמש בו נכון. ללכת! מה מסתיר השינוי החולף ב-Java - 1

בואו נזכור את ההסדרה

המשנה transientמשמש בתהליך של הסידרה וסיריאליזציה של אובייקטים. אז בואו נדבר קודם כל בקצרה על זה. מה מסתיר השינוי החולף ב-Java - 2נניח שיש לנו אובייקט כלשהו, ​​ויש לו שדות, שלכל אחד מהם יש ערך כלשהו. כל זה נקרא מצב האובייקט. סריאליזציה היא המרה של מצב אובייקט לרצף של בתים. בתים אלה מאוחסנים בדרך כלל בקובץ כלשהו. דה-סריאליזציה היא תהליך הפוך. הבה נדמיין שצירפנו אובייקט לבייטים ואחסנו את קבוצת הבתים הזו בקובץ כלשהו. בעת הוצאת סדרת, התוכנית צריכה:
  1. קרא קבוצה של בתים מקובץ.
  2. בנה אובייקט ראשוני מקבוצת בתים זו והגדר כל שדה לערך שהיה לאובייקט בזמן הסדרה.
מתי זה יכול להיות שימושי? לדוגמה, כאשר אנו רוצים שהתוכנית תשמור את מצבה בעת כיבוי ותשחזר אותה בפעם הבאה שהיא מופעלת. כאשר אתה מכבה את IntelliJ IDEA, סביר להניח שיהיו לך אותן כרטיסיות ושיעורים פתוחים בפעם הבאה שתפעיל אותו

בואו נזכור את ההמשכה בהמשכה בפועל

ובכן, עכשיו בואו נסתכל על סדרה בפועל. אם אתה רוצה להבין את הנושא טוב יותר, אנו ממליצים לקרוא את החומר Serialization and deserialization ב-Java . ובכן, במאמר זה נעבור למעלה ונעבור ישר לדוגמאות. נניח שיש לנו מחלקה Userעם קבוצה של כמה שדות, גטרים ומגדירים, ושיטה toString:
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String firstName;
    private String lastName;
    private String email;
    private LocalDate birthDate;
    private String login;
    private String password;

    public User() {}

    public User(String firstName, String lastName, String email, LocalDate birthDate, String login, String password) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.birthDate = birthDate;
        this.login = login;
        this.password = password;
    }

    /*
        Геттеры, Сеттеры
     */

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", birthDate=" + birthDate +
                ", login='" + login + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
אנחנו רוצים לעשות סדרה של אובייקטים מהמחלקה הזו בעתיד. נכתוב שיטה שלוקחת אובייקט Userומחרוזת path- הנתיב לקובץ בו נשמור את הבייטים:
static void serialize(User user, String path) throws IOException {
    FileOutputStream outputStream = null;
    ObjectOutputStream objectOutputStream = null;
    try {
        //create 2 threads to serialize the object and save it to a file
        outputStream = new FileOutputStream(path);
        objectOutputStream = new ObjectOutputStream(outputStream);

        // сохраняем an object в файл
        objectOutputStream.writeObject(user);
    } finally {
        // Закроем потоки в блоке finally
        if (objectOutputStream != null) {
            objectOutputStream.close();
        }
        if (outputStream != null) {
            outputStream.close();
        }
    }
}
נכתוב גם שיטה לדה-סריאליזציה. השיטה לוקחת מחרוזת path(הנתיב לקובץ שממנו "ייטען" האובייקט) ומחזירה אובייקט מסוג User:
static User deserialize(String path) throws IOException, ClassNotFoundException {
    FileInputStream fileInputStream = null;
    ObjectInputStream objectInputStream = null;

    try {

        //создаем 2 потока для десериализации an object из file
        fileInputStream = new FileInputStream(path);
        objectInputStream = new ObjectInputStream(fileInputStream);

        //загружаем an object из file
        return  (User) objectInputStream.readObject();
    } finally {
        if (fileInputStream != null) {
            fileInputStream.close();
        }
        if (objectInputStream != null) {
            objectInputStream.close();
        }
    }
}
כל הכלים מוכנים לשימוש. הגיע הזמן לפצל בתים לאטומים . בוא נכתוב שיטה mainשבה ניצור אובייקט מחלקה Userונעשה אותו בסידרה. לאחר מכן נטען אותו ונשווה אותו למה שהיה במקור:
public static void main(String[] args) throws IOException, ClassNotFoundException {
    // вставьте свой путь до file
    final String path = "/home/zor/user.ser";

    // create our object
    User user = new User();
    user.setFirstName("Stefan");
    user.setLastName("Smith");
    user.setEmail("ssmith@email.com");
    user.setBirthDate(LocalDate.of(1991, 7, 16));
    user.setLogin("ssmith");
    user.setPassword("gemma_arterton_4ever_in_my_heart91");

    System.out.println("Initial user: " + user + "\r\n");


    serialize(user, path);
    User loadedUser = deserialize(path);
    System.out.println("Loaded user from file: " + loadedUser + "\r\n");
}
אם נריץ את השיטה, נראה את הפלט הבא:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}

Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
כפי שניתן לראות מהפלט, האובייקטים זהים. אבל יש אבל קטן... וכאן בדיוק נכנסת לתמונה הבושה הספרדית transient .

משנה (סוף סוף)transient

האם מישהו התבלבל מכך ששמרנו את הסיסמה של המשתמש? במיוחד סיסמה כזו... כן, כן, המצאנו את זה בעצמנו, אבל בכל זאת... לפעמים יש מצבים שבהם לא ניתן לעשות סדרה של שדות מסוימים, או שעדיף לא לעשות זאת. בדוגמה למעלה, אני רוצה לשמור את כל השדות מלבד הסיסמה. איך להשיג זאת? תשובה: השתמש במשנה transient. transientהוא משנה המוצב לפני שדה מחלקה (בדומה למתאמים אחרים כגון public, finalוכו') כדי לציין שהשדה לא צריך להיות מסודר. שדות המסומנים במילת המפתח transientאינם מסודרים. עכשיו בואו נערוך את הדוגמה עם המשתמש שלנו כדי לתקן בלבול קטן ולא לשמור את הסיסמה של המשתמש. לשם כך, סמן את השדה המתאים בכיתה עם מילת המפתח transient:
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String firstName;
    private String lastName;
    private String email;
    private LocalDate birthDate;
    private String login;
    private transient String password;

    /*
        Конструкторы, геттеры, сеттеры, toString...
     */
}
אם נריץ שוב את השיטה מהדוגמה למעלה main, נראה שהסיסמה לא נשמרת:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}

Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='null'}
נהדר, השגנו את המטרה שלנו ולא אוחסנים מידע סודי. במיוחד מידע מסוג זה... (סליחה)

מתי להשתמש בזמני?

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

  • שדות המחושבים באופן פרוגרמטי

לחלק מהשיעורים יש לפעמים שדות שמחושבים על סמך שדות אחרים או מידע אחר. הם מחושבים, כביכול, תוך כדי תנועה. כדי לתת דוגמה לתחום כזה, בואו נדמיין הזמנה בחנות מקוונת או שירות משלוחי מזון כלשהו. כל הזמנה, בין שאר המידע, מורכבת מרשימת סחורות ועלות כוללת. זה, בתורו, מורכב מהעלות הכוללת של כל מוצר. מסתבר שאין להגדיר את העלות הסופית "ביד": יש לחשב אותה באופן תכנותי, תוך סיכום העלות של כל הסחורה. שדות כמו אלה שיש לחשב באופן תכנותי אינם צריכים להיות מסודרים. לכן, אנו מסמנים אותם עם משנה transient.
class Order implements Serializable {

    private List items;
    private transient BigDecimal totalAmount; //вычисляется на ходу

}

  • שדות עם מידע פרטי

יש גם כמה שיעורים המאחסנים מידע פרטי. הסתכלנו על דוגמה לשיעור כזה בתחילת המאמר. אסור לאפשר למידע כזה לדלוף מחוץ ל-JVM. לכן, שדות עם נתונים כאלה חייבים להיות מסומנים במשנה transientאם אתה מתכוון לעשות מחלקה כזו בסידרה.

  • שדות שאינם מיישמים את הממשקSerializable

לפעמים מחלקה מכילה שדות-אובייקטים של מחלקות אחרות שאינן מיישמות את הממשק Serializable. דוגמאות לשדות כאלה הם לוגרים, זרמי I/O, אובייקטים המאחסנים חיבורי מסד נתונים ומחלקות שירות אחרות. אם תנסה לעשות סדרה של אובייקט שמכיל שדות שאינם ניתנים להמשכה, תקבל שגיאה java.io.NotSerializableException. כדי להימנע מכך, כל השדות שאינם מיישמים את הממשק Serializableחייבים להיות מסומנים במשנה transient.
public class FileReader implements Serializable {
    // Первые 2 поля не реализуют Serializable
    // Помечаем их How transient поля
    private transient InputStream is;
    private transient BufferedReader buf;
    private String fileName;

    // Constructors, Getters, Setters

    public String readFile() throws IOException {
        try {
            is = new FileInputStream(fileName);
            buf = new BufferedReader(new InputStreamReader(is));
            String line = buf.readLine();
            StringBuilder sb = new StringBuilder();
            while (line != null) {
                sb.append(line).append("\n");
                line = buf.readLine();
            }
            return sb.toString();
        } finally {
            if (buf != null) {
                buf.close();
            }
            if (is != null) {
                is.close();
            }
        }
    }
}

  • שדות עם מידע על מצב האובייקט

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

transientוfinal

תוצאות

זה הכל. היום דיברנו על המשנה transient:
  1. זכרנו בהמשכה בתיאוריה ובפרקטיקה.
  2. הבנו שכדי לא לסמן חלק מהשדות בכיתה, צריך לסמן אותם ב-modifier transient.
  3. דנו באילו מצבים יש להשתמש בשינוי זה. היו ארבעה מצבים כאלה:
    1. שדות המחושבים באופן פרוגרמטי;
    2. שדות המכילים מידע סודי;
    3. שדות שאינם מיישמים את הממשק Serializable;
    4. שדות שאינם חלק ממצב האובייקט.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION