Serialização de Objetos com ObjectOutputStream e ObjectInputStream
Serialização é o processo de converter o estado de um objeto em uma sequência de bytes. Essa abordagem é fundamental para persistir o estado de um objeto em disco, transferi-lo pela rede ou para mecanismos de cache. O processo inverso, que reconstrói o objeto a partir dos bytes, é a desserialização.
A Interface Serializable
Para que um objeto possa ser serializado, sua classe deve implementar a interface java.io.Serializable
. Esta é uma interface de marcação, o que significa que ela não possui métodos. Sua única finalidade é sinalizar para a JVM que os objetos daquela classe podem ser serializados.
Serialização com ObjectOutputStream
A classe ObjectOutputStream
"envolve" um OutputStream
e adiciona a capacidade de escrever objetos inteiros. Ela herda toda a funcionalidade de DataOutputStream
para escrever tipos primitivos e adiciona o método crucial para serializar objetos:
void writeObject(Object obj)
: serializa o objeto e o grava no stream.void writeBoolean(boolean val)
: grava um valor booleano.void writeByte(int val)
: grava um byte.void writeChar(int val)
: grava umchar
.void writeDouble(double val)
: grava umdouble
.void writeFloat(float val)
: grava umfloat
.void writeInt(int val)
: grava umint
.void writeLong(long val)
: grava umlong
.void writeShort(int val)
: grava umshort
.void writeUTF(String str)
: grava uma string em formato UTF-8 modificado.void write(byte[] buf)
: grava um array de bytes.void flush()
: força a escrita dos dados em buffer para o stream.void close()
: fecha o stream.
Desserialização com ObjectInputStream
A classe ObjectInputStream
realiza o processo inverso. Ela herda a funcionalidade de DataInputStream
para ler tipos primitivos e adiciona o método para desserializar objetos:
Object readObject()
: lê e desserializa um objeto do stream.boolean readBoolean()
byte readByte()
char readChar()
double readDouble()
float readFloat()
int readInt()
long readLong()
short readShort()
String readUTF()
int available()
: retorna o número de bytes disponíveis para leitura.void close()
: fecha o stream.
Exemplo Prático de Serialização e Desserialização
import java.io.*;
public class Program {
public static void main(String[] args) {
// Serialização
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {
Person p = new Person("Sam", 33, 178.5, true);
oos.writeObject(p);
System.out.println("Object has been serialized");
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
// Desserialização
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) {
Person p = (Person) ois.readObject();
System.out.printf("Name: %s \t Age: %d \n", p.getName(), p.getAge());
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private double height;
private boolean married;
Person(String n, int a, double h, boolean m) {
this.name = n;
this.age = a;
this.height = h;
this.married = m;
}
String getName() { return name; }
int getAge() { return age; }
double getHeight() { return height; }
boolean isMarried() { return married; }
}
Considerações Importantes
Versionamento com serialVersionUID
Para controlar a compatibilidade entre diferentes versões de uma classe, é uma prática essencial declarar explicitamente um campo serialVersionUID
:
private static final long serialVersionUID = 1L;
Este ID é usado durante a desserialização para verificar se a classe do objeto serializado é compatível com a classe disponível na JVM.
Excluindo Dados com transient
Para impedir que certos campos sejam serializados (como senhas ou dados temporários), marque-os com a palavra-chave transient
.
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // Não será serializado
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password; // Retornará null após a desserialização
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + (password == null ? "TRANSIENT" : "******") + '\'' +
'}';
}
}
Na desserialização, o campo password
receberá seu valor padrão (null
).
Riscos de Segurança
Nunca desserialize dados de uma fonte não confiável. Um invasor pode criar uma sequência de bytes maliciosa que, ao ser desserializada, pode levar à execução de código arbitrário na sua aplicação.
Resumo
- Serialização converte um objeto em bytes; Desserialização o reconstrói.
- Uma classe deve implementar
Serializable
para ser serializável. ObjectOutputStream.writeObject()
serializa, eObjectInputStream.readObject()
desserializa.- É uma prática essencial declarar um
private static final long serialVersionUID
para controlar o versionamento. - Use
transient
para excluir campos da serialização. - Seja extremamente cauteloso com a desserialização de dados de fontes não confiáveis.