Nessa aula, vamos aprender como guardar objetos de tipos complexos para uso do programa em execuções posteriores, através do processo de serialização.
Ao final da aula, poderemos gravar e restaurar objetos Java a partir de arquivos.
Agora que sabemos como gravar e recuperar dados de tipos primitivos e texto em arquivos usando fluxos de entrada e saída Data(In/Out)putStream, PrintStream e File(In/Out)putStream, podemos pensar no próximo passo:
Gravar e recuperar objetos de tipos complexos em arquivos, permitindo manter o estado do programa após o termino da sua execução ou compartilhar esse estado entre mais de uma instância do programa.
Esse processo é conhecido como *persistência de dados*, em oposição à volatilidade dos dados na memória principal.
Uma das maneiras de realizar a persistência da dados em Java é através de um processo chamado *serialização* de objetos.
Serializar um objeto significa transformá-lo em uma sequência (série) de bytes.
Esses bytes podem então ser armazenados (persistidos) em um arquivo, ou enviados através de uma rede para serem armazenados ou transformados de volta em objeto em outro computador.
O processo de transformar as sequências de bytes de volta em objetos é chamado deserialização.
Para serem serializados em Java, os objetos precisam ser instâncias de uma classe que implemente a interface Serializable.
Alem disso, todos os campos da classe devem ser serializáveis.
É também fortemente recomendado incluir um campo constante de classe *serialVersionUID*, do tipo long, para identificar a classe e garantir a deserialização correta.
O valor de serialVersionUID é arbitrário, mas deve ser única para cada classe.
public class Pessoa implements Serializable {
...
}
public class Pessoa implements Serializable {
private String cpf; // String é Serializable
private String nome;
private LocalDate dataNascimento; // LocalDateTime é Serializable
...
}
public class Pessoa implements Serializable {
private static final long serialVersionUID = 1000L; // Um número por classe.
private String cpf; // String é Serializable
private String nome;
private LocalDate dataNascimento; // LocalDate é Serializable
...
}
Sabendo que uma classe é serializável, podemos passar suas instâncias para um fluxo ObjectOutputStream para serem convertidas em uma sequência de bytes.
Escrevemos o objeto usando o método writeObject().
Para escrever mais de um objeto, podemos colocá-los em uma lista, e então serializar a lista.
Vejamos um exemplo completo com um HashSet<LocalDate>:
Pessoa pessoa = new Pessoa("123456789-00", "Fulano da Silva", LocalDate.parse("1998-12-04"));
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("pessoa.ser"))) {
oos.writeObject(pessoa);
}
catch (IOException e) {
System.err.println("Problema ao abrir ou fechar o arquivo.");
}
Pessoa pessoa1 = new Pessoa("123456789-00", "Fulano da Silva", LocalDate.parse("1998-12-04"));
Pessoa pessoa2 = new Pessoa("987654321-00", "Sicrano de Souza", LocalDate.parse("1962-01-30"));
ArrayList<Pessoa> pessoas = new ArrayList<>();
pessoas.add(pessoa1);
pessoas.add(pessoa2);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("pessoas.ser"))) {
oos.writeObject(pessoas);
}
import java.io.*;
import java.time.*;
import java.util.*;
public class ExemploSerializacao {
public static void serializaDatas() {
HashSet<LocalDate> datas = new HashSet<>();
datas.add(LocalDate.parse("2020-01-01"));
datas.add(LocalDate.now());
datas.add(LocalDate.now().minusDays(1));
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("datas.ser"))) {
oos.writeObject(datas);
}
catch (IOException e) {
System.err.println("Problema ao abrir ou fechar o arquivo.");
}
}
public static void main(String[] args) {
serializaDatas();
}
}
Para deserializar um objeto, usamos um fluxo ObjectInputStream, que converte uma sequência de bytes em um objeto.
Recuperamos o objeto usando o método readObject().
Como o método readObject() retorna uma referência de tipo Object, precisamos fazer um cast explicito para o tipo correto.
Caso o bytecode da classe lida não seja encontrado pela JVM, ocorrerá uma exceção ClassNotFoundException, que precisa ser tratada.
Vamos continuar com o exemplo completo com um HashSet<LocalDateTime>:
Pessoa pessoa;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
pessoa = (Pessoa)ois.readObject();
}
catch (ClassNotFoundException e) {
System.err.println("Problema ao abrir ou fechar o arquivo.");
}
catch (IOException e) {
System.err.println("Problema ao abrir ou fechar o arquivo.");
}
import java.io.*;
import java.time.*;
import java.util.*;
public class ExemploDeserializacao {
@SuppressWarnings("unchecked")
public static HashSet<LocalDate> deserializaDatas() {
HashSet datas = null;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("datas.ser"))) {
datas = (HashSet<LocalDate>)ois.readObject();
}
catch (ClassNotFoundException e) {
System.err.println("Problema ao carregar a classe.");
e.printStackTrace();
}
catch (IOException e) {
System.err.println("Problema ao abrir ou fechar o arquivo.");
}
return datas;
}
public static void main(String[] args) {
System.out.println(deserializaDatas().toString());
}
}
Agora sabemos como realizar a persistência dos dados em Java através da serialização dos objetos.
As sequências de bytes que representam um objeto podem ter diversos formatos, inclusive texto (ex: XML, JSON).
Existem outras formas de persistência (ex: bancos de dados, sistemas de arquivo em rede), que não serão abordadas nesse curso. Mas o princípio de persistência é o mesmo: guardar o estado do programa (ou parte dele) em meio não volátil.
Perguntas:
Exercício:
Até a próxima aula!