Serializable
I coped with my job, and the automatic implementation of the entire process cannot but rejoice. The examples we looked at weren't complicated either. So what's the deal? Why another interface for essentially the same task? The fact is that Serializable
it has a number of disadvantages. Let's list some of them:
-
Performance. The interface has
Serializable
many advantages, but high performance is clearly not one of them.
Firstly , the internal mechanism Serializable
generates a large amount of service information and various types of temporary data during operation.
Secondly (you don’t have to go into this now and read at your leisure if you’re interested), the work Serializable
is based on using the Reflection API. This contraption allows you to do things that would seem impossible in Java: for example, change the values of private fields. JavaRush has an excellent article about the Reflection API , you can read about it here.
-
Flexibility. We don't control the serialization-deserialization process at all when using the
Serializable
.On the one hand, this is very convenient, because if we don’t really care about performance, the ability to not write code seems convenient. But what if we really need to add some of our own features (an example of one of them will be below) to the serialization logic?
Essentially, all we have to control the process is a keyword
transient
to exclude some data, and that's it. Sort of like a “toolkit” :/ -
Safety. This point partially follows from the previous one.
We haven’t thought much about this before, but what if some information in your class is not intended for “other people’s ears” (more precisely, eyes)? A simple example is a password or other personal user data, which in the modern world is regulated by a bunch of laws.
Using
Serializable
, we actually can’t do anything about it. We serialize everything as is.But, in a good way, we must encrypt this kind of data before writing it to a file or transmitting it over the network. But
Serializable
it doesn’t give this opportunity.
Externalizable
.
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long SERIAL_VERSION_UID = 1L;
//...конструктор, геттеры, сеттеры, toString()...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
As you can see, we have made significant changes! The main one is obvious: when implementing an interface, Externalizable
you must implement two mandatory methods - writeExternal()
and readExternal()
. As we said earlier, all responsibility for serialization and deserialization will lie with the programmer. However, now you can solve the problem of lack of control over this process! The entire process is programmed directly by you, which, of course, creates a much more flexible mechanism. In addition, the security problem is also solved. As you can see, we have a field in our class: personal data that cannot be stored unencrypted. Now we can easily write code that meets this constraint. For example, add two simple private methods to our class for encrypting and decrypting secret data. We will write them to a file and read them from the file in encrypted form. And we will write and read the rest of the data as is :) As a result, our class will look something like this:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long serialVersionUID = 1L;
public UserInfo() {
}
public UserInfo(String firstName, String lastName, String superSecretInformation) {
this.firstName = firstName;
this.lastName = lastName;
this.superSecretInformation = superSecretInformation;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.getFirstName());
out.writeObject(this.getLastName());
out.writeObject(this.encryptString(this.getSuperSecretInformation()));
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
firstName = (String) in.readObject();
lastName = (String) in.readObject();
superSecretInformation = this.decryptString((String) in.readObject());
}
private String encryptString(String data) {
String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
System.out.println(encryptedData);
return encryptedData;
}
private String decryptString(String data) {
String decrypted = new String(Base64.getDecoder().decode(data));
System.out.println(decrypted);
return decrypted;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSuperSecretInformation() {
return superSecretInformation;
}
}
We have implemented two methods that use the same ObjectOutput out
and as parameters ObjectInput
that we have already encountered in the lecture about Serializable
. At the right time, we encrypt or decrypt the necessary data, and in this form we use it to serialize our object. Let's see how this will look in practice:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
UserInfo userInfo = new UserInfo("Ivan", "Ivanov", "Ivan Ivanov's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
In the encryptString()
and methods decryptString()
, we specifically added output to the console to check in what form the secret data will be written and read. The code above outputs the following line to the console: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Encryption succeeded! The full contents of the file look like this: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Now let's try to use the deserialization logic we wrote.
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
Well, there doesn’t seem to be anything complicated here, it should work! Let's run... Exception in thread "main" java.io.InvalidClassException: UserInfo; no valid constructor Oops :( It turned out to be not so simple! The deserialization mechanism threw an exception and required us to create a default constructor. I wonder why? Serializable
We managed without it... :/ Here we come to another important nuance. The difference between Serializable
and Externalizable
lies not only in “extended” access for the programmer and the ability to more flexibly manage the process, but also in the process itself. First of all, in the deserialization mechanism ... When used, Serializable
memory is simply allocated for an object, after which values are read from the stream, which fill all its fields . If we use Serializable
, the object constructor is not called! All work is done through reflection (Reflection API, which we briefly mentioned in the last lecture). In the case of , the Externalizable
deserialization mechanism will be different. At the beginning, the default constructor is called. And only then UserInfo
is called on the created object method readExternal()
, which is responsible for filling in the fields of the object. That is why any class that implements the interface Externalizable
must have a default constructor . Let's add it to our class UserInfo
and rerun the code:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
Console output: Ivan Ivanov's passport data UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Ivan Ivanov's passport data'} A completely different matter! First, the decrypted string with secret data was output to the console, and then our object recovered from the file in string format! This is how we successfully resolved all the problems :) The topic of serialization and deserialization seems to be simple, but as you can see, our lectures turned out to be long. And that's not all! There are many more subtleties when using each of these interfaces, but so that now your brain does not explode from the volume of new information, I will briefly list a few more important points and provide links to additional reading. So what else do you need to know? Firstly , when serializing (it doesn’t matter whether you use Serializable
or Externalizable
), pay attention to the variables static
. When used, Serializable
these fields are not serialized at all (and, accordingly, their value does not change, since static
the fields belong to the class, not the object). But when using it, Externalizable
you control the process yourself, so technically this can be done. But it is not recommended, as this is fraught with subtle errors. Secondly , attention should also be paid to variables with the modifier final
. When used, Serializable
they are serialized and deserialized as usual, but when used, it is impossible Externalizable
to deserialize final
a variable ! The reason is simple: all final
-fields are initialized when the default constructor is called, and after that their value cannot be changed. Therefore, to serialize objects containing final
-fields, use standard serialization via Serializable
. Thirdly , when using inheritance, all inheriting classes descending from some Externalizable
class must also have default constructors. Here are some links to good articles about serialization mechanisms:
See you! :)
GO TO FULL VERSION