你好!在今天的文章中,我們將了解Java中的transient修飾符。讓我們來談談為什麼需要這個修飾符以及如何正確使用它。去!
原子了。讓我們寫一個方法西班牙恥辱 修改器(最後)
有人對我們保存使用者密碼感到困惑嗎?尤其是這樣的密碼……是的,是的,我們自己想出來的,但是還是……有時會出現某些字段無法序列化的情況,或者最好不要這樣做。在上面的範例中,我想保存除密碼之外的所有欄位。如何實現這項目標?答:使用修飾符
某些類別有時具有根據其他欄位或其他資訊計算的欄位。可以說,它們是動態計算的。為了舉一個這樣的領域的例子,讓我們想像一下線上商店或某些食品配送服務中的訂單。除其他資訊外,每個訂單還包含商品清單和總成本。反過來,它由每種產品的總成本組成。事實證明,最終成本不應該「手動」設定:它必須以程式設計方式計算,總結所有商品的成本。像這樣應該以程式設計方式計算的欄位不需要序列化。因此,我們用修飾符標記它們
還有一些類別儲存私有資訊。我們在文章開頭查看了此類別的一個範例。您不應該允許此類資訊洩漏到 JVM 之外。
有時,一個類別包含欄位 - 未實作該介面的其他類別的物件
好吧,最後一件事。無需序列化不屬於物件狀態資訊的欄位。上面的例子就屬於這個規則。但您也可以在此處包含為偵錯或執行某種不攜帶有關物件狀態資訊的某種服務功能而新增的所有其他欄位。
讓我們記住序列化
修飾符transient
用於物件的序列化和反序列化過程中。那我們先簡單談談這一點。假設我們有一些對象,並且它有字段,每個字段都有一些值。所有這些都稱為物件的狀態。序列化是將物件的狀態轉換為位元組序列。這些位元組通常儲存在某個檔案中。反序列化是相反的過程。假設我們將一個物件序列化為位元組並將這組位元組儲存在某個檔案中。反序列化時,程式需要:
- 從檔案中讀取一組位元組。
- 從這組位元組建構一個初始對象,並將每個欄位設定為該對像在序列化時具有的值。
讓我們記住實踐中的序列化
好吧,現在讓我們看看實踐中的序列化。如果您想更了解主題,我們建議您閱讀材料Java 中的序列化和反序列化。好吧,在這篇文章中我們將超越頂部並直接進入範例。假設我們有一個類,User
其中包含一組字段、getter 和 setter 以及一個方法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
:
- 我們記住了理論和實踐中的序列化。
- 我們意識到,為了不序列化類別的某些字段,需要用修飾符來標記它們
transient
。 - 我們討論了在什麼情況下應該使用這個修飾符。這樣的情況有四種:
- 以程式設計方式計算的欄位;
- 包含秘密訊息的欄位;
- 未實現介面的欄位
Serializable
; - 不屬於對象狀態的欄位。
GO TO FULL VERSION