Overview
The process of serialization converts the state of an object into a stream of bytes. If we want to store an object in a file, it is necessary to convert it into bytes. These bytes then can be restored into an object, its original form, with the process called deserialization. This is how object persistence takes place. The process of serialization also needs to implement Remote Method Invocation (RMI). RMI helps Java objects to invoke the method of another Java object located in a remote machine. The objects are typically sent via argument to a remote method. The remote machine serializes the object before sending it. Meanwhile, at the receiving end, the machine deserializes it to restore its original form.
In Java, the process of serialization happens automatically via implementation of the Serializable interface. This means that programmers have no control over the whole process that occurs behind the scenes. If we want to serialize an object that contains a lot of attributes and properties or manipulate the bytes to be persisted with custom encryption, it is not possible. Moreover, serializing a bloated object and transmitting it via network is costly. Therefore, for many such reasons we want to have some control over serializability in Java. This is where externalization comes into play. The mechanism of externalization via the Externalizable interface provides the opportunity to implement our own serialization logic.
Therefore, in short, if serialization is the mechanism to automatically converting an object state into a stream of bytes, externalization does the same but with the control given to programmers in converting the object state into a stream of bytes.
The Serializable Interface
In Java, serialization is achieved via the Serializable interface. A class that is interested in serialization implements this interface during its design. As a result, the instances of this class can be saved and restored, availing the facilities of the serialization mechanism. The Serializable interface contains no members. Any class implementing this interface is an indication that the objects of this class are serializable. Any subclasses derived from this class also inherit the serializable property. However, members declared as transient or static are not serializable, hence not persisted.
The Externalizable Interface
The Externalizable interface provides control over the serialization process. Similar to the Serializable interface, a class that implements the Externalizable interface is marked to be persisted, except those members which are declared as transient or static. Because the transient keyword designates a field not to be persisted, we can use it on any field that we do not want to be serialized. Likewise, a field marked with the static keyword also is not serialized simply because static fields do not belong to an object but to a class. In contrast to the Serializable interface, which has no member, the Externalizable interface supplies two methods, called readExternal() and writeExternal(), where we can write our own set of serialization rules.
A Quick Example
The following example illustrates object persistence with the Externalizable interface. Here, we demonstrate how the password field of the PersonalInformation class can be encrypted before persistence.
package com.mano.examples; import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Base64; public class PersonalInformation implements Externalizable { private int id; private String password; private transient String secret = "~!@#$%^&*()"; private static SecretKeySpec secretKey; private static byte [] key; public PersonalInformation(){ id =0; password = ""; } public PersonalInformation(int id, String pass){ this.id = id; this.password = pass; } private static void createKey(String k) { MessageDigest md = null; try { key = k.getBytes("UTF-8"); md = MessageDigest.getInstance("SHA-1"); secretKey = new SecretKeySpec( Arrays.copyOf(md.digest(key), 16), "AES"); } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { ex.printStackTrace(); } } private static String encrypt(String str, String secret) { String retStr=null; try { createKey(secret); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); retStr=Base64.getEncoder() .encodeToString(cipher .doFinal(str.getBytes("UTF-8"))); } catch (Exception ex) { ex.printStackTrace(); } return retStr; } private static String decrypt(String str, String secret) { String retStr=null; try { createKey(secret); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING"); cipher.init(Cipher.DECRYPT_MODE, secretKey); retStr = new String(cipher.doFinal(Base64 .getDecoder().decode(str))); } catch (Exception ex) { ex.printStackTrace(); } return retStr; } @Override public String toString() { return "PersonalInformation{" + "id=" + id + ", password='" + password + ''' + '}'; } @Override public void writeExternal(ObjectOutput out) throws IOException { try { out.write(id); password=encrypt(password,secret); out.writeUTF(password); System.out.println("After Encryption"); System.out.println("ID: "+id+" PASSWORD:"+password); } catch(IOException ex) { ex.printStackTrace(); } } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { try{ id=in.read(); password=decrypt(in.readUTF(),secret); } catch(IOException ex){ ex.printStackTrace(); } } } package com.mano.examples; import java.io.*; public class Main { public static void main(String[] args) { try { String file = "classified.dat"; PersonalInformation pi = new PersonalInformation(101, "abc_123"); System.out.println("Before serialize"); System.out.println(pi.toString()); ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file)); os.writeObject(pi); os.close(); ObjectInputStream is = new ObjectInputStream(new FileInputStream(file)); pi = (PersonalInformation) is.readObject(); System.out.println("After deserialize"); System.out.println(pi.toString()); is.close(); } catch (Exception ex) { ex.printStackTrace(); } } }
Comparing the Serializable and Externalizable Interfaces
- A class that implements the Serializable interface the object persistence is automatically kicked off without any intervention from the programmer. Although a class that implements the Externalizable interface, it is the responsibility of the programmer to provide the norms of serialization.
- Because the serialization process is automatic in case of implementing the Serializable interface, there is no scope of improvement of performance of the process. The Externalizable interface provides complete control over the process and can be used to improve performance.
- Both the Serializable and Externalizable interfaces are used for object serialization. The Serializable interface has no members, whereas the Externalizable interface contains two methods: readExternal() and writeExternal().
Conclusion
The primary utility of the serialization and deserialization processes is to enable save and restore object state functions without direct intervention of the programmer. This is done automatically with the Serializable interface. But, in some situations, we want to have control over these processes, such as imbibing compression or encryption facilities during serialization of objects. This is only possible through the Enternalizable interface.