Serializable
hizo su trabajo, y ¿qué hay de malo en la implementación automática de todo el proceso? Y los ejemplos que vimos tampoco fueron complicados. ¿Entonces, cuál es el problema? ¿Por qué necesitamos otra interfaz para esencialmente las mismas tareas? El caso es que Serializable
tiene varias carencias. Enumeramos algunos de ellos:
-
Actuación. La
Serializable
interfaz tiene muchas ventajas, pero el alto rendimiento claramente no es una de ellas.En primer lugar,
Serializable
la implementación interna de genera una gran cantidad de información de servicio y todo tipo de datos temporales.En segundo lugar,
Serializable
se basa en la API de Reflection (no es necesario que profundice en esto ahora; puede leer más en su tiempo libre, si está interesado). Esto le permite hacer las cosas aparentemente imposibles en Java: por ejemplo, cambiar los valores de los campos privados. CodeGym tiene un excelente artículo sobre la API de Reflection . Puedes leer sobre eso allí. -
Flexibilidad. No controlamos el proceso de serialización-deserialización cuando usamos la
Serializable
interfaz.Por un lado, es muy conveniente, porque si no estamos particularmente preocupados por el rendimiento, entonces parece bueno no tener que escribir código. Pero, ¿qué sucede si realmente necesitamos agregar algunas de nuestras propias funciones (a continuación, brindaremos un ejemplo) a la lógica de serialización?
Básicamente, lo único que tenemos para controlar el proceso es la
transient
palabra clave para excluir algunos datos. Eso es todo. Esa es toda nuestra caja de herramientas :/ -
Seguridad. Este ítem deriva en parte del ítem anterior.
No hemos pasado mucho tiempo pensando en esto antes, pero ¿qué pasa si alguna información en su clase no está destinada a los ojos y oídos curiosos de los demás? Un ejemplo simple es una contraseña u otros datos personales del usuario, que en el mundo actual se rigen por un montón de leyes.
Si usamos
Serializable
, realmente no podemos hacer nada al respecto. Serializamos todo tal como está.Pero si lo hacemos de la manera correcta, debemos cifrar este tipo de datos antes de escribirlos en un archivo o enviarlos a través de una red. Pero
Serializable
no hace esto posible.

Externalizable
interfaz.
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;
// ...constructor, getters, setters, toString()...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
Como puede ver, ¡tenemos cambios significativos! El principal es obvio: al implementar la Externalizable
interfaz, debe implementar dos métodos requeridos: writeExternal()
yreadExternal()
. Como dijimos anteriormente, la responsabilidad de la serialización y deserialización recaerá en el programador. ¡Pero ahora puede resolver el problema de no tener control sobre el proceso! Todo el proceso es programado directamente por usted. Naturalmente, esto permite un mecanismo mucho más flexible. Además, el problema con la seguridad está resuelto. Como puede ver, nuestra clase tiene un campo de datos personales que no se puede almacenar sin cifrar. Ahora podemos escribir fácilmente código que satisfaga esta restricción. Por ejemplo, podemos agregar a nuestra clase dos métodos privados simples para cifrar y descifrar datos confidenciales. Escribiremos los datos en el archivo y los leeremos del archivo en forma cifrada. El resto de los datos se escribirán y leerán tal cual :) Como resultado, nuestra clase se ve así:
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;
}
}
Implementamos dos métodos que usan los mismos ObjectOutput
parámetros ObjectInput
que ya conocimos en la lección sobre Serializable
. En el momento adecuado, encriptamos o desencriptamos los datos requeridos y usamos los datos encriptados para serializar nuestro objeto. Veamos cómo se ve esto en la práctica:
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("Paul", "Piper", "Paul Piper's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
En los métodos encryptString()
y decryptString()
, agregamos específicamente la salida de la consola para verificar la forma en que se escribirán y leerán los datos secretos. El código anterior mostraba la siguiente línea: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh ¡El cifrado se realizó correctamente! El contenido completo del archivo se ve así: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Ahora intentemos usar nuestra lógica de deserialización.
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();
}
}
Bueno, nada parece complicado aquí. ¡Deberia de funcionar! Lo ejecutamos y obtenemos... Excepción en el hilo "principal" java.io.InvalidClassException: UserInfo; ningún constructor válido 
Serializable
, nos las arreglamos sin uno... :/ Aquí encontramos otro matiz importante. El La diferencia entre Serializable
y Externalizable
radica no solo en el acceso 'ampliado' del programador y la capacidad de controlar el proceso de manera más flexible, sino también en el proceso mismo. Sobre todo, en el mecanismo de deserialización . Al usarSerializable
, la memoria simplemente se asigna para el objeto y luego los valores se leen de la secuencia y se usan para establecer los campos del objeto. Si usamos Serializable
, ¡no se llama al constructor del objeto! Todo el trabajo ocurre a través de la reflexión (la API de Reflection, que mencionamos brevemente en la última lección). Con Externalizable
, el mecanismo de deserialización es diferente. Primero se llama al constructor predeterminado. Solo después de eso se llama al método UserInfo
del objeto creado readExternal()
. Es responsable de configurar los campos del objeto. Es por eso que cualquier clase que implemente la Externalizable
interfaz debe tener un constructor predeterminado . Agreguemos uno a nuestra UserInfo
clase y volvamos a ejecutar el código:
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();
}
}
Salida de la consola: Datos del pasaporte de Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Datos del pasaporte de Paul Piper' } ¡ Ahora eso es algo completamente diferente! Primero, la cadena descifrada con información secreta se mostró en la consola. Luego, el objeto que recuperamos del archivo se mostró como una cadena. Así que hemos resuelto con éxito todos los problemas :) El tema de la serialización y deserialización parece simple, pero, como puede ver, las lecciones han sido largas. ¡Y hay mucho más que no hemos cubierto! Todavía hay muchas sutilezas involucradas al usar cada una de estas interfaces. Pero para evitar que su cerebro explote con demasiada información nueva, enumeraré brevemente algunos puntos más importantes y le daré enlaces a lecturas adicionales. Entonces, ¿qué más necesitas saber? Primero , durante la serialización (independientemente de si está usando Serializable
o Externalizable
), preste atención a static
las variables. Cuando usa Serializable
, estos campos no se serializan en absoluto (y, en consecuencia, sus valores no cambian, porque static
los campos pertenecen a la clase, no al objeto). Pero cuando usasExternalizable
, usted mismo controla el proceso, por lo que técnicamente podría serializarlos. Pero no lo recomendamos, ya que es probable que al hacerlo se creen muchos errores sutiles. En segundo lugar , también debe prestar atención a las variables con el final
modificador. Cuando usa Serializable
, se serializan y deserializan como de costumbre, pero cuando usa Externalizable
, ¡es imposible deserializar una final
variable ! La razón es simple: todos final
los campos se inicializan cuando se llama al constructor predeterminado; después de eso, su valor no se puede cambiar. Por lo tanto, para serializar objetos que tienen final
campos, use la serialización estándar proporcionada por Serializable
. Tercero , cuando usa la herencia, todas las clases descendientes que heredan algúnExternalizable
La clase también debe tener constructores predeterminados. Aquí hay un enlace a un buen artículo sobre los mecanismos de serialización:
¡Hasta la proxima vez! :)
GO TO FULL VERSION