Hello! In today's article, we will look at the transient modifier in Java. Let's talk about why this modifier is needed and how to use it correctly. Go!
atoms . Let's write a method Spanish shame Modifier (finally)
Was anyone confused that we saved the user’s password? Especially such a password... Yes, yes, we came up with it ourselves, but still... Sometimes there are situations when some fields cannot be serialized, or it is better not to do this. In the example above, I would like to save all fields except the password. How to achieve this? Answer: use the modifier
Some classes sometimes have fields that are calculated based on other fields or other information. They are calculated, so to speak, on the fly. To give an example of such a field, let’s imagine an order in an online store or some food delivery service. Each order, among other information, consists of a list of goods and a total cost. It, in turn, consists of the total cost of each product. It turns out that the final cost should not be set “by hand”: it must be calculated programmatically, summing up the cost of all goods. Fields like these that should be calculated programmatically do not need to be serialized. Therefore, we mark them with a modifier
There are also some classes that store private information. We looked at an example of such a class at the beginning of the article. You should not allow such information to leak outside the JVM. Therefore, fields with such data must be marked with a modifier
Sometimes a class contains fields - objects of other classes that do not implement the interface
Well, one last thing. There is no need to serialize fields that are not part of the object's state information. The examples above fall under this rule. But you can also include here all other fields added for debugging or to perform some kind of service function that do not carry information about the state of the object.
Let's remember serialization
The modifiertransient
is used in the process of serializing and deserializing objects. So let's talk briefly about this first. Suppose we have some object, and it has fields, each of which has some value. All this is called the state of the object. Serialization is the conversion of an object's state into a sequence of bytes. These bytes are usually stored in some file. Deserialization is the reverse process. Let's imagine that we serialized an object into bytes and stored this set of bytes in some file. When deserializing, the program needs:
- Read a set of bytes from a file.
- Construct an initial object from this set of bytes and set each field to the value that the object had at the time of serialization.
Let's remember serialization in practice
Well, now let's look at serialization in practice. If you want to understand the topic better, we recommend reading the material Serialization and deserialization in Java . Well, in this article we will go over the top and go straight to the examples. Let's say we have a classUser
with a set of some fields, getters and setters, and a method 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 + '\'' +
'}';
}
}
We want to serialize objects of this class in the future. Let's write a method that takes an object User
and a string path
- the path to the file in which we will save the bytes:
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();
}
}
}
We will also write a method for deserialization. The method takes a string path
(the path to the file from which the object will be “loaded”) and returns an object of type 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();
}
}
}
All tools are ready for use. It's time to split bytes into main
in which we create a class object User
and serialize it. Then we’ll load it and compare it with what was originally:
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");
}
If we run the method, we will see the following output:
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'}
As you can see from the output, the objects are identical. But there is a small but... And this is exactly where transient
comes into play .
Modifier (finally)transient
Was anyone confused that we saved the user’s password? Especially such a password... Yes, yes, we came up with it ourselves, but still... Sometimes there are situations when some fields cannot be serialized, or it is better not to do this. In the example above, I would like to save all fields except the password. How to achieve this? Answer: use the modifier transient
. transient
is a modifier placed before a class field (similar to other modifiers such as public
, final
etc.) to indicate that the field should not be serialized. Fields marked with the keyword transient
are not serialized. Now let’s edit the example with our user to correct a small confusion and not save the user’s password. To do this, mark the corresponding field in the class with the keyword 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...
*/
}
If we run the method from the example above again main
, we will see that the password is not saved:
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'}
Great, we achieved our goal and do not store confidential information. Especially this kind of information... (sorry)
When to use transient?
An example with a user was needed in order to dive into the context of serialization. Now let's talk more specifically about when to use the modifiertransient
.
- Fields that are calculated programmatically
Some classes sometimes have fields that are calculated based on other fields or other information. They are calculated, so to speak, on the fly. To give an example of such a field, let’s imagine an order in an online store or some food delivery service. Each order, among other information, consists of a list of goods and a total cost. It, in turn, consists of the total cost of each product. It turns out that the final cost should not be set “by hand”: it must be calculated programmatically, summing up the cost of all goods. Fields like these that should be calculated programmatically do not need to be serialized. Therefore, we mark them with a modifier transient
.
class Order implements Serializable {
private List- items;
private transient BigDecimal totalAmount; //вычисляется на ходу
}
- Fields with private information
There are also some classes that store private information. We looked at an example of such a class at the beginning of the article. You should not allow such information to leak outside the JVM. Therefore, fields with such data must be marked with a modifier transient
if you are going to serialize such a class.
- Fields that do not implement the interface
Serializable
Sometimes a class contains fields - objects of other classes that do not implement the interface Serializable
Serializable
. Examples of such fields are loggers, I/O streams, objects that store database connections and other utility classes. If you try to serialize an object that contains non-serializable fields, you will receive an error java.io.NotSerializableException
. To avoid this, all fields that do not implement the interface Serializable
must be marked with a modifier 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();
}
}
}
}
- Fields with information about the object state
Well, one last thing. There is no need to serialize fields that are not part of the object's state information. The examples above fall under this rule. But you can also include here all other fields added for debugging or to perform some kind of service function that do not carry information about the state of the object.
transient
Andfinal
Results
That's all. Today we talked about the modifiertransient
:
- We remembered serialization in theory and practice.
- We realized that in order not to serialize some fields of the class, they need to be marked with a modifier
transient
. - We discussed in what situations this modifier should be used. There were four such situations:
- fields that are calculated programmatically;
- fields that contain secret information;
- fields that do not implement the interface
Serializable
; - fields that are not part of the object's state.
GO TO FULL VERSION