Introdução à serialização Java

Introdução à serialização Java

1. Introdução

Serialização é a conversão do estado de um objeto em um fluxo de bytes; a desserialização faz o oposto. Em outras palavras, serialização é a conversão de um objeto Java em um fluxo estático (sequência) de bytes que pode ser salvo em um banco de dados ou transferido por uma rede.

2. Serialização e desserialização

O processo de serialização é independente da instância, ou seja, objetos podem ser serializados em uma plataforma e desserializados em outra. Classes that are eligible for serialization need to implement a special marker interfaceSerializable.

AmbosObjectInputStreameObjectOutputStream são classes de alto nível que estendemjava.io.InputStreamejava.io.OutputStream respectivamente. ObjectOutputStream pode gravar tipos primitivos e gráficos de objetos em umOutputStream como um fluxo de bytes. Esses fluxos podem ser lidos subsequentemente usandoObjectInputStream.

O método mais importante emObjectOutputStream é:

public final void writeObject(Object o) throws IOException;

Que pega um objeto serializável e o converte em uma sequência (fluxo) de bytes. Da mesma forma, o método mais importante emObjectInputStream é:

public final Object readObject()
  throws IOException, ClassNotFoundException;

O qual pode ler um fluxo de bytes e convertê-lo novamente em um objeto Java. Isso pode ser convertido de volta ao objeto original.

Vamos ilustrar a serialização com uma classePerson. Observe questatic fields belong to a class (as opposed to an object) and are not serialized. Além disso, observe que podemos usar a palavra-chavetransient para ignorar os campos de classe durante a serialização:

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    static String country = "ITALY";
    private int age;
    private String name;
    transient int height;

    // getters and setters
}

O teste abaixo mostra um exemplo de como salvar um objeto do tipoPerson em um arquivo local e, em seguida, ler este valor de volta em:

@Test
public void whenSerializingAndDeserializing_ThenObjectIsTheSame() ()
  throws IOException, ClassNotFoundException {
    Person person = new Person();
    person.setAge(20);
    person.setName("Joe");

    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile.txt");
    ObjectOutputStream objectOutputStream
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(person);
    objectOutputStream.flush();
    objectOutputStream.close();

    FileInputStream fileInputStream
      = new FileInputStream("yourfile.txt");
    ObjectInputStream objectInputStream
      = new ObjectInputStream(fileInputStream);
    Person p2 = (Person) objectInputStream.readObject();
    objectInputStream.close();

    assertTrue(p2.getAge() == p.getAge());
    assertTrue(p2.getName().equals(p.getName()));
}

UsamosObjectOutputStream para salvar o estado deste objeto em um arquivo usandoFileOutputStream. O arquivo“yourfile.txt” é criado no diretório do projeto. Este arquivo é então carregado usandoFileInputStream.ObjectInputStream seleciona este fluxo e o converte em um novo objeto chamadop2.

Por fim, testamos o estado do objeto carregado e ele corresponde ao estado do objeto original.

Observe que o objeto carregado deve ser convertido explicitamente em um tipoPerson.

3. Advertências de serialização Java

Existem algumas advertências que dizem respeito à serialização em Java.

3.1. Herança e composição

Quando uma classe implementa a interfacejava.io.Serializable, todas as suas subclasses também são serializáveis. Ao contrário, quando um objeto tem uma referência a outro objeto, esses objetos devem implementar a interfaceSerializable separadamente, ou então umNotSerializableException será lançado:

public class Person implements Serializable {
    private int age;
    private String name;
    private Address country; // must be serializable too
}

Se um dos campos em um objeto serializável consiste em uma matriz de objetos, então todos esses objetos devem ser serializáveis ​​também, ou então umNotSerializableException será lançado.

3.2. UID da versão serial

The JVM associates a version (long) number with each serializable class. É utilizado para verificar se os objetos salvos e carregados possuem os mesmos atributos e, portanto, são compatíveis na serialização.

Esse número pode ser gerado automaticamente pela maioria dos IDEs e é baseado no nome da classe, seus atributos e modificadores de acesso associados. Quaisquer alterações resultam em um número diferente e podem causar umInvalidClassException.

Se uma classe serializável não declara umserialVersionUID, o JVM irá gerar um automaticamente em tempo de execução. No entanto, é altamente recomendável que cada classe declare seuserialVersionUID, pois a classe gerada depende do compilador e, portanto, pode resultar emInvalidClassExceptions inesperado.

3.3. Serialização personalizada em Java

Java especifica uma maneira padrão pela qual os objetos podem ser serializados. As classes Java podem substituir esse comportamento padrão. A serialização personalizada pode ser particularmente útil ao tentar serializar um objeto que possui alguns atributos não serializáveis. Isso pode ser feito fornecendo dois métodos dentro da classe que queremos serializar:

private void writeObject(ObjectOutputStream out) throws IOException;

and

private void readObject(ObjectInputStream in)
  throws IOException, ClassNotFoundException;

Com esses métodos, podemos serializar esses atributos não serializáveis ​​em outras formas que podem ser serializadas:

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private transient Address address;
    private Person person;

    // setters and getters

    private void writeObject(ObjectOutputStream oos)
      throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(address.getHouseNumber());
    }

    private void readObject(ObjectInputStream ois)
      throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        Integer houseNumber = (Integer) ois.readObject();
        Address a = new Address();
        a.setHouseNumber(houseNumber);
        this.setAddress(a);
    }
}
public class Address {
    private int houseNumber;

    // setters and getters
}

O seguinte teste de unidade testa essa serialização personalizada:

@Test
public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame()
  throws IOException, ClassNotFoundException {
    Person p = new Person();
    p.setAge(20);
    p.setName("Joe");

    Address a = new Address();
    a.setHouseNumber(1);

    Employee e = new Employee();
    e.setPerson(p);
    e.setAddress(a);

    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile2.txt");
    ObjectOutputStream objectOutputStream
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(e);
    objectOutputStream.flush();
    objectOutputStream.close();

    FileInputStream fileInputStream
      = new FileInputStream("yourfile2.txt");
    ObjectInputStream objectInputStream
      = new ObjectInputStream(fileInputStream);
    Employee e2 = (Employee) objectInputStream.readObject();
    objectInputStream.close();

    assertTrue(
      e2.getPerson().getAge() == e.getPerson().getAge());
    assertTrue(
      e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber());
}

Neste código, vemos como salvar alguns atributos não serializáveis ​​serializandoAddress com serialização personalizada. Observe que devemos marcar os atributos não serializáveis ​​comotransient para evitar oNotSerializableException.

4. Conclusão

Neste tutorial rápido, analisamos a serialização Java, discutimos coisas importantes a serem lembradas e mostramos como fazer a serialização personalizada.

Como sempre, o código-fonte usado neste tutorial está disponívelover on GitHub.