Вітання! У сьогоднішній статті ми розглянемо модифікатор transient Java. Поговоримо про те, навіщо цей модифікатор потрібен і як правильно його використовувати. Поїхали!
атоми байти. Напишемо метод іспанський сором Модифікатор (ну нарешті)
Нікого не збентежило, що пароль користувача зберегли? Особливо такий пароль… Так-так, ми самі його придумали, але все ж таки… Часом бувають ситуації, коли деякі поля неможливо серіалізувати, чи краще цього не робити. У прикладі вище хотілося б зберігати всі поля, крім пароля. Як це досягти? Відповідь: використовувати модифікатор
У деяких класах іноді бувають такі поля, які обчислюються на основі інших полів або іншої інформації. Обчислюються, так би мовити, на льоту. Щоб навести приклад такого поля, уявимо собі замовлення в інтернет-магазині або в якомусь сервісі доставки їжі. Кожне замовлення, крім іншої інформації, складається зі списку товарів та підсумкової вартості. Вона, своєю чергою, складається із сумарної вартості кожного товару. Виходить, що підсумкову вартість не варто ставити "руками": її потрібно обчислювати програмно, підсумовуючи вартість усіх товарів. Подібні поля, які потрібно обчислювати програмно, не потрібно серіалізувати. Тому помічаємо їх модифікатором
Також бувають деякі класи, які зберігають приватну інформацію. Приклад такого класу ми розглядали на початку статті. Не слід допускати витоку такої інформації за межі JVM. Тому поля з подібними даними необхідно позначати модифікатором
Іноді клас містить поля - об'єкти інших класів, які не реалізують інтерфейс
Та й останнє. Не потрібно серіалізувати поля, які не є частиною інформації про стан об'єкта. Приклади вище підпадають під це правило. Але також сюди можна включити й інші поля, додані для дебага чи виконання якоїсь службової функції, які мають інформацію про стан об'єкта.
Згадаймо серіалізацію
Модифікаторtransient
використовується в процесі серіалізації та десеріалізації об'єктів. Тому для початку коротко поговоримо про це. Припустимо, ми маємо певний об'єкт, а в нього — поля, кожне з яких має якесь значення. Усе це називається станом об'єкта. Серіалізація — конвертація стану об'єкта в послідовність байт. Дані байти зберігаються, зазвичай, у якомусь файлі. Десеріалізація – це зворотний процес. Уявимо, що ми серіалізували об'єкт в байти і зберегли цей набір байтів у файлі. При десеріалізації програмі потрібно:
- Вважати набір байтів з файлу.
- Сформулювати з даного набору байтів вихідний об'єкт і задати кожному полю значення, яке було об'єкта на момент серіалізації.
Згадаймо серіалізацію на практиці
Що ж, тепер розглянемо серіалізацію практично. Якщо хочеться краще розібратися в темі, радимо почитати матеріал Серіалізація та десеріалізація в 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 {
//створюємо 2 потоки для серіалізації об'єкта та збереження його у файл
outputStream = new FileOutputStream(path);
objectOutputStream = new ObjectOutputStream(outputStream);
// сохраняем об'єкт в файл
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 потока для десериализации об'єкта из файлу
fileInputStream = new FileInputStream(path);
objectInputStream = new ObjectInputStream(fileInputStream);
//загружаем об'єкт из файлу
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 {
// вставьте свой путь до файлу
final String path = "/home/zor/user.ser";
//створюємо наш об'єкт
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
.
- Поля, які обчислюються програмно
У деяких класах іноді бувають такі поля, які обчислюються на основі інших полів або іншої інформації. Обчислюються, так би мовити, на льоту. Щоб навести приклад такого поля, уявимо собі замовлення в інтернет-магазині або в якомусь сервісі доставки їжі. Кожне замовлення, крім іншої інформації, складається зі списку товарів та підсумкової вартості. Вона, своєю чергою, складається із сумарної вартості кожного товару. Виходить, що підсумкову вартість не варто ставити "руками": її потрібно обчислювати програмно, підсумовуючи вартість усіх товарів. Подібні поля, які потрібно обчислювати програмно, не потрібно серіалізувати. Тому помічаємо їх модифікатором transient
.
class Order implements Serializable {
private List- items;
private transient BigDecimal totalAmount; //вычисляется на ходу
}
- Поля з приватною інформацією
Також бувають деякі класи, які зберігають приватну інформацію. Приклад такого класу ми розглядали на початку статті. Не слід допускати витоку такої інформації за межі JVM. Тому поля з подібними даними необхідно позначати модифікатором transient
, якщо ви збираєтеся серіалізувати такий клас.
- Поля, які не реалізують інтерфейс
Serializable
Іноді клас містить поля - об'єкти інших класів, які не реалізують інтерфейс Serializable
Serializable
. Приклад таких полів - логери, потоки введення-виведення, об'єкти, які зберігають з'єднання з базою даних та інші службові класи. Якщо спробувати серіалізувати об'єкт, який містить поля, що не серіалізуються, виникне помилка java.io.NotSerializableException
. Щоб уникнути цього, всі поля, які не реалізують інтерфейс Serializable
, необхідно позначати модифікатором transient
.
public class FileReader implements Serializable {
// Первые 2 поля не реализуют Serializable
// Помечаем их як 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
; - поля, які є частиною стану об'єкта.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ