Для чего нужен модификатор transient?
Сериализация — это преобразование экземпляра класса в форму, пригодную для его сохранения (например в файл, в БД или для передачи по сети). Сериализованные объекты можно затем восстановить (десериализовать).
Свойства класса, помеченные модификатором transient , не сериализуются.
Обычно в таких полях хранится промежуточное состояние объекта, которое, к примеру, проще вычислить, чем сериализовать, а затем десериализовать. Другой пример такого поля — ссылка на экземпляр объекта, который не требует сериализации или не может быть сериализован.
Что такое transient в Java?
В Java ключевое слово transient используется в контексте сериализации объектов. Когда поле класса помечено как transient , оно исключается из процесса сериализации, что означает, что его значение не сохраняется при преобразовании объекта в последовательность байтов. Это может быть полезно, если есть поля, которые не должны сохраняться или передаваться между системами.
Основные моменты:
- Не участвует в сериализации:
- Поле, помеченное как transient , не участвует в процессе сериализации. При десериализации такое поле получит значение по умолчанию для своего типа.
- Исключение временных данных:
- Ключевое слово transient часто используется для исключения временных данных, кэшей или другой информации, которая не должна сохраняться.
- Сериализация пользовательских объектов:
- При реализации интерфейса Serializable в пользовательских классах, transient может быть применено к полям, которые не подлежат сериализации.
class Person implements Serializable < private static final long serialVersionUID = 1L; private String name; private transient int age; // Поле, помеченное transient >
Модификатор transient
— Привет, Амиго! Хотела тебе порассказывать одно маленькое дополнение к сериализации.
Допустим наш класс содержит ссылку на какой-нибудь InputStream, тогда его нельзя сериализовать, ведь так?
— Да. Ты же сама говорила, что потоки сериализовать нельзя. А сериализовать объект, у которого есть несериализуемые данные – тоже нельзя.
— Да. Именно так. Но что, если класс хранит данные, которые не играют значащей роли в его состоянии, но мешают считаться ему сериализуемым классом? Мало ли что класс может у себя хранить ненужного. Возможно, он может выбросить эти данные в любой момент или даже так и делает постоянно.
Для таких случаев разработчики Java придумали специальное слово – transient . Его можно написать перед переменной класса и она не будет учитываться при сериализации. Ее состояние не будет ни сохраняться, ни восстанавливаться. Как будто и нет ее вовсе. Как раз для таких ситуаций, как мы только что рассмотрели.
Помнишь кеширование и модификатор volatile ? Нет правил без исключений.
Вот тебе один примерчик такого счастья:
Пример «кота» с невидимой для сериализации переменной — in:
class Cat implements Serializable < public String name; public int age; public int weight; transient public InputStream in = System.in; >
Комментарии (91)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Dmitry Shaburov Уровень 33
17 ноября 2023
Raf Java Master Уровень 31
11 сентября 2023
Дисциплина и гринд господа, мы победим!
Ислам Уровень 33
10 июля 2023
Александр Горбунов Уровень 51
18 июня 2023
При сериализации сохраняются все переменные экземпляра (instance variables). Однако переменные, объявленные с модификатором transient, не сохраняются сериализацией, и их значения не включаются в сериализованный поток данных. Также статические переменные (static variables) не сохраняются сериализацией. Для более явного управления процессом сериализации можно использовать массив serialPersistentFields. В этом массиве можно указать конкретные переменные, которые будут сохранены при сериализации, независимо от их модификаторов. Давайте рассмотрим пример:
import java.io.*; public class MyClass implements Serializable < private String name; private transient int age; private static double weight; public MyClass(String name, int age, double weight) < this.name = name; this.age = age; MyClass.weight = weight; >public void displayInfo() < System.out.println("Name: " + name); System.out.println("Age: " + age); System.out.println("Weight: " + weight); >public static void main(String[] args) < MyClass obj = new MyClass("John", 30, 70.5); try < ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.bin")); out.writeObject(obj); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.bin")); MyClass restoredObj = (MyClass) in.readObject(); in.close(); restoredObj.displayInfo(); >catch (IOException | ClassNotFoundException e) < e.printStackTrace(); >> >
Gans Electro Уровень 50
20 апреля 2023
Возможный смысл строчки «Помнишь кеширование и модификатор volatile? Нет правил без исключений.» GPT: Да, сейчас я понимаю, что вы имели в виду. В данном контексте выражение «Нет правил без исключений» скорее всего относится к следующей ситуации: — Обычно, чтобы объект можно было сериализовать, все его поля должны быть сериализуемыми. — Однако, в случае, когда объект содержит поле, которое не является сериализуемым (например, поле типа InputStream), разработчики могут использовать модификатор transient, чтобы указать, что это поле не должно быть учтено при сериализации. — Тем не менее, есть определенные исключения, когда применение модификатора transient может привести к ошибкам или неожиданному поведению объекта при десериализации. Например, если поле было скрыто от сериализации, то при десериализации его значение будет равно значению по умолчанию, а не сохраненному значению. — Таким образом, правило о том, что все поля должны быть сериализуемыми, имеет исключения, но эти исключения могут привести к неожиданному поведению, и их использование следует тщательно обдумывать. Вот почему в статье говорится «Нет правил без исключений».
Что скрывает модификатор transient в Java
Привет! В сегодняшней статье мы рассмотрим модификатор transient в Java. Поговорим о том, зачем данный модификатор нужен и как его правильно использовать. Поехали!
Вспомним сериализацию
Модификатор transient используется в процессе сериализации и десериализации объектов. Поэтому для начала кратко поговорим об этом.Предположим, у нас есть некоторый объект, а у него — поля, каждое из которых имеет какое-то значение. Все это называется состоянием объекта. Сериализация — это конвертация состояния объекта в последовательность байт. Данные байты сохраняются, как правило, в каком-либо файле. Десериализация — это обратный процесс. Представим, что мы сериализовали объект в байты и сохранили данный набор байтов в некотором файле. При десериализации программе нужно:
- Считать набор байтов из файла.
- Сконструировать из данного набора байтов исходный объект и задать каждому полю значение, которое было у объекта на момент сериализации.
Когда это может быть полезным? Например, когда мы хотим, чтобы при завершении работы программа сохраняла свое состояние, и при следующем включении восстанавливала его. Когда вы завершаете работу IntelliJ IDEA, при следующем включении, скорее всего, у вас открыты те же вкладки и классы
Вспомним сериализацию на практике
Что же, теперь рассмотрим сериализацию на практике. Если хочется получше разобраться в теме, советуем почитать материал Сериализация и десериализация в 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'; > >
Мы хотим сериализовывать объекты данного класса в дальнейшем. Напишем метод, который принимает объект 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 Loaded user from file: User
Как видно из вывода, объекты идентичны. Но есть маленькое но… И это как раз то место, когда в игру вступает испанский стыд 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 Loaded user from file: User
Отлично, мы добились поставленной цели и не сохраняем конфиденциальную информацию. Особенно такую информацию… (простите)
Когда использовать transient?
Пример с пользователем был нужен для того, чтобы погрузиться в контекст сериализации. Теперь поговорим предметнее о том, когда следует использовать модификатор transient .
- Поля, которые вычисляются программно
В некоторых классах иногда бывают такие поля, которые вычисляются на основе других полей или же другой информации. Вычисляются, так сказать, на лету. Чтобы привести пример такого поля, представим себе заказ в интернет-магазине или же в каком-нибудь сервисе доставки еды. Каждый заказ, помимо прочей информации, состоит из списка товаров и итоговой стоимости. Она, в свою очередь, складывается из суммарной стоимости каждого товара. Выходит, что итоговую стоимость не стоит задавать “руками”: ее нужно вычислять программно, суммируя стоимость всех товаров. Подобные поля, которые следует вычислять программно, не нужно сериализовывать. Поэтому помечаем их модификатором transient .
class Order implements Serializable < private List- items; private transient BigDecimal totalAmount; //вычисляется на ходу >
- Поля с приватной информацией
Также бывают некоторые классы, которые хранят приватную информацию. Пример такого класса мы рассматривали в начале статьи. Не стоит допускать утечки такой информации за пределы JVM. Поэтому поля с подобными данными необходимо помечать модификатором transient , если вы собираетесь сериализовывать такой класс.
- Поля, которые не реализуют интерфейс 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 .
- Обсудили, в каких ситуациях следует использовать данный модификатор. Таких ситуаций оказалось четыре:
- поля, которые вычисляются программно;
- поля, которые содержат секретную информацию;
- поля, которые не реализуют интерфейс Serializable ;
- поля, которые не являются частью состояния объекта.