Ficheros binarios
Interfaz Serializable
La interfaz Serializable es una interfaz marcador (marker interface) de Java, lo que significa que no contiene métodos, pero indica al sistema que una clase puede ser "serializada".
La serialización es el proceso de convertir un objeto en bytes para que pueda almacenarse en un archivo, transmitirse a través de una red o guardarse en una base de datos. La deserialización es el proceso opuesto, que convierte los bytes de vuelta en un objeto. La interfaz Serializable permite que un objeto se convierta en un formato que pueda ser fácilmente guardado o transferido.
Para que un objeto sea serializable, su clase debe implementar la interfaz Serializable. Esto no requiere que se implementen métodos específicos (ya que es un marcador), pero la clase debe cumplir algunos requisitos.
Requisitos para la serialización:
- La clase debe implementar la interfaz
Serializable. - Todos los objetos referenciados dentro del objeto también deben ser serializables (si no, se generará una excepción
NotSerializableException). - El identificador
serialVersionUIDes importante para garantizar la compatibilidad entre diferentes versiones de una clase serializada.
Atributo serialVersionUID
La interfaz Serializable recomienda el uso de un campo estático llamado serialVersionUID. Este identificador permite que el sistema verifique si la versión del objeto serializado es compatible con la versión de la clase durante la deserialización.
Si la clase se modifica (por ejemplo, si se agregan o cambian campos), puede dar problemas al intentar deserializar un objeto guardado antes de la modificación. El serialVersionUID garantiza que la clase que se usa para la deserialización sea compatible con la versión del archivo o red que contiene los objetos serializados.
Ejemplo:
private static final long serialVersionUID = 1L;
Consideraciones importantes:
- No se pueden serializar ciertas clases: Clases como
Thread,Socket,Optionalu otras relacionadas con la interacción del sistema operativo no pueden ser serializadas de forma estándar. - Evitar serializar campos innecesarios: Si no se desea que algún campo se serialice, puede marcarse como
transient. Estos campos serán ignorados durante la serialización.private transient String contrasinal;
Los atributos estáticos no se serializan, por lo que no se almacena su valor en los archivos.
Serialización: guardar el objeto en un archivo
La clase FileOutputStream en Java, que también pertenece al paquete java.io, se utiliza para escribir datos en archivos en formato binario. Su principal tarea es escribir bytes en un archivo, lo que es útil cuando queremos guardar información en formato binario (como imágenes, audio, o cualquier otro archivo no textual).
Constructores de FileOutputStream:
| Constructor | Descripción |
|---|---|
FileOutputStream(String name) | Crea un objeto FileOutputStream para escribir datos en un archivo especificado por el nombre (ruta relativa o absoluta). Si el archivo no existe, será creado. |
FileOutputStream(File file) | Crea un FileOutputStream a partir de un objeto File que representa el archivo donde se escribirá. |
La serialización de objetos en Java a un archivo binario puede hacerse mediante el uso de las clases ObjectOutputStream, que forma parte del paquete java.io. Esto permite guardar objetos en un formato binario que puede ser deserializado posteriormente.
Métodos principales de ObjectOutputStream:
| Método | Descripción |
|---|---|
void writeObject(Object obj) | Serializa el objeto especificado y lo escribe en el flujo de salida. |
void flush() | Fuerza la escritura de todos los datos en el flujo de salida. |
void close() | Cierra el flujo de salida, garantizando que todos los objetos se escriban correctamente. |
class Persona implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private String nombre;
private int edad;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
@Override
public String toString() {
return "Persona[nombre=" + nombre + ", edad=" + edad + "]";
}
}
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
public class EjemploSerializacion {
public static void main(String[] args) {
try {
// Crear objeto
Persona persona = new Persona("Carlos", 30);
// Crear flujo de salida para guardar el objeto
FileOutputStream archivo = new FileOutputStream("persona.ser");
ObjectOutputStream objeto = new ObjectOutputStream(archivo);
// Serializar el objeto
objeto.writeObject(persona);
// Cerrar los flujos
objeto.close();
archivo.close();
System.out.println("Objeto serializado correctamente.");
} catch (IOException e) {
System.out.println("Error al escribir en el archivo");
System.out.println(e);
}
}
}
Deserialización: leer un objeto de un archivo
La clase FileInputStream en Java pertenece al paquete java.io y se utiliza para leer archivos en formato binario. Su función principal es proporcionar un flujo de entrada para leer datos de archivos, como números o caracteres, como una secuencia de bytes.
La clase FileInputStream permite leer archivos en formato binario, sin interpretar el contenido como texto, sino como una secuencia de bytes. Aunque se puede utilizar para leer cualquier tipo de archivo (imágenes, audio, documentos binarios), su principal aplicación es para leer archivos que no sean de texto.
Constructores de FileInputStream:
| Constructor | Descripción |
|---|---|
FileInputStream(String name) | Crea un objeto FileInputStream para leer el archivo especificado por el nombre (como ruta relativa o absoluta). |
FileInputStream(File file) | Crea un FileInputStream a partir de un objeto File que representa el archivo que se desea leer. |
Métodos principales de FileInputStream:
| Método | Descripción |
|---|---|
read() | Lee un solo byte del archivo. Devuelve el valor del byte leído o -1 si no hay más datos en el archivo. |
read(byte[] b) | Lee múltiples bytes del archivo y los almacena en el array b. Devuelve el número de bytes leídos o -1 si no hay más datos. |
read(byte[] b, int off, int len) | Lee hasta len bytes y los almacena en el array b comenzando en el índice off. |
skip(long n) | Salta n bytes en el flujo de entrada, sin leer ningún dato. |
available() | Devuelve el número de bytes disponibles para leer sin bloquear el proceso. |
close() | Cierra el flujo de entrada y libera los recursos asociados al archivo. |
La deserialización de objetos en Java desde un archivo binario puede hacerse mediante el uso de la clase ObjectInputStream, que forma parte del paquete java.io. Esto permite leer objetos en un formato binario.
Métodos principales de ObjectInputStream:
| Método | Descripción |
|---|---|
Object readObject() | Lee un objeto serializado y lo restaura. Devuelve un objeto de tipo Object, por lo que normalmente debe hacerse un casting al tipo correcto. |
void close() | Cierra el flujo de entrada. |
Ahora, para recuperar el objeto que guardamos anteriormente en el archivo binario, utilizamos la clase ObjectInputStream.
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
public class DeserializacionEjemplo {
public static void main(String[] args) {
try {
// Crear flujo de entrada para leer el objeto
FileInputStream archivo = new FileInputStream("persona.ser");
ObjectInputStream objeto = new ObjectInputStream(archivo);
// Deserializar el objeto
Persona persona = (Persona) objeto.readObject();
// Cerrar los flujos
objeto.close();
archivo.close();
// Mostrar objeto deserializado
System.out.println("Objeto deserializado: " + persona);
} catch (IOException e) {
System.out.println("Error al leer el archivo");
System.out.println(e);
} catch (ClassNotFoundException e) {
System.out.println("No se encuentra la clase");
System.out.println(e);
}
}
}
Ejemplo de utilización junto al patrón Singleton
Vamos a ampliar el ejercicio 1 de la T04.01 utilizando excepciones, y los patrones MVC y Singleton. Además haremos la persistencia de datos utilizando archivos binarios.
Cada vez que se modifique el modelo de datos se debería guardar la información en el archivo correspondiente. Vamos paso a paso:
- Implementamos la interfaz
Serializabletanto en el controlador como en todas las clases del modelo. - Creamos una clase que guarde y lea con métodos.