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