 A serialização
A serialização é um processo que converte um objeto em uma sequência de bytes, a partir da qual ele pode ser completamente restaurado. Por que isso é necessário? 
O fato é que, durante a execução normal do programa, a vida útil máxima de qualquer objeto é conhecida - desde o início do programa até o final. A serialização permite expandir essa estrutura e "dar vida" ao objeto da mesma maneira entre os lançamentos do programa.
Um bônus adicional a tudo é a preservação da plataforma cruzada. Não importa qual sistema operacional você tenha, a serialização converte o objeto em um fluxo de bytes, que pode ser restaurado em qualquer sistema operacional. Se você precisar transferir um objeto pela rede, poderá serializá-lo, salvá-lo em um arquivo e transferi-lo pela rede para o destinatário. Ele poderá recuperar o objeto recebido. A serialização também permite que você chame remotamente métodos (Java RMI) que estão em máquinas diferentes com, possivelmente, sistemas operacionais diferentes, e trabalhe com eles como se estivessem na máquina do processo java de chamada.
A implementação de um mecanismo de serialização é bastante simples. Sua classe precisa implementar a interface 
serializável . Essa interface é um identificador que não possui métodos, mas informa à jvm que objetos dessa classe podem ser serializados. Como o mecanismo de serialização está conectado ao sistema básico de entrada / saída e converte o objeto em um fluxo de bytes, para executá-lo, é necessário criar um fluxo de saída 
OutputStream , empacotá-lo no 
ObjectOutputStream e chamar o 
método writeObject (). Para restaurar um objeto, você precisa compactar um 
InputStream em um 
ObjectInputStream e chamar o 
método readObject ().Durante o processo de serialização, junto com o objeto serializável, seu gráfico de objetos é salvo. I.e. todos os objetos relacionados a isso, objetos de outras classes também serão serializados com ele.
Considere um exemplo de serialização de um objeto da classe Person.
import java.io.*; class Home implements Serializable { private String home; public Home(String home) { this.home = home; } public String getHome() { return home; } } public class Person implements Serializable { private String name; private int countOfNiva; private String fatherName; private Home home; public Person(String name, int countOfNiva, String fatherName, Home home) { this.name = name; this.countOfNiva = countOfNiva; this.fatherName = fatherName; this.home = home; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", countOfNiva=" + countOfNiva + ", fatherName='" + fatherName + '\'' + ", home=" + home + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Home home = new Home("Vishnevaia 1"); Person igor = new Person("Igor", 2, "Raphael", home); Person renat = new Person("Renat", 2, "Raphael", home);  
Conclusão: Before Serialize: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@355da254} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@355da254} After Restored From Byte: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} After Restored: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae} 
Neste exemplo, a classe 
Home é criada para demonstrar que, ao serializar um objeto 
Person , o gráfico de seus objetos é serializado com ele. A classe 
Home também deve implementar a interface 
Serializable , caso contrário, uma 
java.io.NotSerializableException ocorrerá. O exemplo também descreve a serialização usando a classe 
ByteArrayOutputStream .
Uma conclusão interessante pode ser tirada dos resultados da execução do programa: 
ao restaurar objetos que tinham uma referência ao mesmo objeto antes da serialização, esse objeto será restaurado apenas uma vez . Isso pode ser visto nos mesmos links nos objetos após a recuperação:
 After Restored From Byte: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@27973e9b} After Restored: Person{name='Igor', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae} Person{name='Renat', countOfNiva=2, fatherName='Raphael', home=Home@312b1dae} 
No entanto, também é visto que, ao gravar com dois fluxos de saída (temos 
ObjectInputStream e 
ByteArrayOutputStream ), o objeto 
inicial será recriado, apesar do fato de já ter sido criado anteriormente em um dos fluxos. Vemos isso em diferentes endereços de objetos 
domésticos recebidos em dois fluxos. Acontece que se você serializar com um fluxo de saída e depois restaurar o objeto, temos a garantia de restaurar a rede completa de objetos sem duplicatas desnecessárias. Obviamente, durante a execução do programa, o estado dos objetos pode mudar, mas isso está na consciência do programador.
O problemaO exemplo também mostra que, ao restaurar um objeto, pode ocorrer uma 
ClassNotFoundException . Qual o motivo disso? O fato é que podemos facilmente serializar um objeto da classe 
Person em um arquivo, transferi-lo pela rede para nosso amigo, que pode restaurar o objeto por outro aplicativo no qual a classe 
Person simplesmente não existe.
Sua serialização. Como fazer?E se você quiser gerenciar a serialização? Por exemplo, seu objeto armazena o nome de usuário e a senha dos usuários. Você precisa serializá-lo para transmissão adicional pela rede. Passar uma senha nesse caso é extremamente não confiável. Como resolver este problema? Existem duas maneiras. Primeiro, use a palavra-chave 
transitória . Segundo, em vez de perceber o interesse 
serializável , use sua extensão - a interface 
Externalizável . Considere os exemplos do primeiro e do segundo métodos para compará-los.
A primeira maneira - serialização usando transiente import java.io.*; public class Logon implements Serializable { private String login; private transient String password; public Logon(String login, String password) { this.login = login; this.password = password; } @Override public String toString() { return "Logon{" + "login='" + login + '\'' + ", password='" + password + '\'' + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Logon igor = new Logon("IgorIvanovich", "Khoziain"); Logon renat = new Logon("Renat", "2500RUB"); System.out.println("Before: \n" + igor); System.out.println(renat); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Externals.out")); out.writeObject(igor); out.writeObject(renat); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Externals.out")); igor = (Logon) in.readObject(); renat = (Logon) in.readObject(); System.out.println("After: \n" + igor); System.out.println(renat); } } 
Conclusão: Before: Logon{login='IgorIvanovich', password='Khoziain'} Logon{login='Renat', password='2500RUB'} After: Logon{login='IgorIvanovich', password='null'} Logon{login='Renat', password='null'} 
A segunda maneira - serialização com a implementação da interface Externalizável import java.io.*; public class Logon implements Externalizable { private String login; private String password; public Logon() { } public Logon(String login, String password) { this.login = login; this.password = password; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(login); } @Override public String toString() { return "Logon{" + "login='" + login + '\'' + ", password='" + password + '\'' + '}'; } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { login = (String) in.readObject(); } public static void main(String[] args) throws IOException, ClassNotFoundException { Logon igor = new Logon("IgorIvanovich", "Khoziain"); Logon renat = new Logon("Renat", "2500RUB"); System.out.println("Before: \n" + igor); System.out.println(renat); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Externals.out")); out.writeObject(igor); out.writeObject(renat); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Externals.out")); igor = (Logon) in.readObject(); renat = (Logon) in.readObject(); System.out.println("After: \n" + igor); System.out.println(renat); } } 
Conclusão: Before: Logon{login='IgorIvanovich', password='Khoziain'} Logon{login='Renat', password='2500RUB'} After: Logon{login='IgorIvanovich', password='null'} Logon{login='Renat', password='null'} 
A primeira diferença entre as duas opções que chama sua atenção é o tamanho do código. Ao implementar a interface 
Externalizável , precisamos substituir dois métodos: 
writeExternal () e 
readExternal () . No método 
writeExternal () , indicamos quais campos serão serializados e como, em 
readExternal (), como lê-los. Ao usar a palavra 
transitório, indicamos explicitamente quais campos ou campos não precisam ser serializados. Também observamos que no segundo método, criamos explicitamente um construtor padrão, além disso, um construtor público. Por que isso é feito? Vamos tentar executar o código sem esse construtor. E veja a saída:
 Before: Logon{login='IgorIvanovich', password='Khoziain'} Logon{login='Renat', password='2500RUB'} Exception in thread "main" java.io.InvalidClassException: Logon; no valid constructor at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169) at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2043) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) at Logon.main(Logon.java:45) 
Temos a exceção 
java.io.InvalidClassException . Qual o motivo disso? Se você seguir o rastreamento da pilha, poderá descobrir que existem linhas no construtor da classe 
ObjectStreamClass :
  if (externalizable) { cons = getExternalizableConstructor(cl); } else { cons = getSerializableConstructor(cl); 
Para a interface 
Externalizable , o método construtor 
getExternalizableConstructor () será chamado 
, dentro do qual, através do 
Reflection , tentaremos obter o construtor padrão da classe para a qual estamos restaurando o objeto. Se não conseguimos encontrá-lo ou não é 
público , obtemos uma exceção. Você pode contornar essa situação da seguinte maneira: não crie explicitamente nenhum construtor na classe e preencha os campos usando setters e obtenha o valor com getters. Em seguida, ao compilar a classe, um construtor padrão será criado, que estará disponível para 
getExternalizableConstructor () . Para 
Serializable, o método 
getSerializableConstructor () obtém o construtor da classe 
Object e procura a classe desejada, se não a encontrar, obtemos uma exceção 
ClassNotFoundException . Acontece que a principal diferença entre 
Serializable e 
Externalizable é que o primeiro não precisa de um construtor para criar a recuperação do objeto. Ele simplesmente se recuperará completamente dos bytes. No segundo, durante a restauração, um objeto será criado primeiro usando o construtor no ponto de declaração e, em seguida, os valores de seus campos de bytes recebidos durante a serialização serão gravados nele. Pessoalmente, eu prefiro o primeiro método, é muito mais simples. Além disso, mesmo se ainda precisarmos definir o comportamento da serialização, não podemos usar 
Externalizable , nem implementar 
Serializable adicionando (sem substituir) os métodos 
writeObject () e 
readObject () . Mas, para que eles “trabalhem”, sua assinatura deve ser rigorosamente observada.
 import java.io.*; public class Talda implements Serializable { private String name; private String description; public Talda(String name, String description) { this.name = name; this.description = description; } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); System.out.println("Our writeObject"); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); System.out.println("Our readObject"); } @Override public String toString() { return "Talda{" + "name='" + name + '\'' + ", description='" + description + '\'' + '}'; } public static void main(String[] args) throws IOException, ClassNotFoundException { Talda partizanka = new Talda("Partizanka", "Viiiski"); System.out.println("Before: \n" + partizanka); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Talda.out")); out.writeObject(partizanka); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Talda.out")); partizanka = (Talda) in.readObject(); System.out.println("After: \n" + partizanka); } } 
Conclusão: Before: Talda{name='Partizanka', description='Viiiski'} Our writeObject Our readObject After: Talda{name='Partizanka', description='Viiiski'} 
Dentro de nossos métodos adicionados, 
defaultWriteObject () e 
defaultReadObject () são chamados. Eles são responsáveis pela serialização padrão, como se funcionasse sem os métodos que adicionamos.
De fato, esta é apenas a ponta do iceberg; se você continuar a se aprofundar no mecanismo de serialização, então com um alto grau de probabilidade, poderá encontrar mais nuances, descobrindo o que dizemos: "Serialização ... não é tão simples".