Serializable
Cumpri meu trabalho e a implementação automática de todo o processo não pode deixar de me alegrar. Os exemplos que vimos também não foram complicados. Então qual é o problema? Por que outra interface para essencialmente a mesma tarefa? O fato é que Serializable
tem uma série de desvantagens. Vamos listar alguns deles:
-
Desempenho. A interface tem
Serializable
muitas vantagens, mas o alto desempenho claramente não é uma delas.
Em primeiro lugar , o mecanismo interno Serializable
gera uma grande quantidade de informações de serviço e vários tipos de dados temporários durante a operação.
Em segundo lugar (você não precisa entrar nisso agora e ler quando quiser, se estiver interessado), o trabalho Serializable
é baseado no uso da API Reflection. Essa engenhoca permite fazer coisas que pareceriam impossíveis em Java: por exemplo, alterar os valores de campos privados. JavaRush tem um excelente artigo sobre a API Reflection , você pode ler sobre ele aqui.
-
Flexibilidade. Não controlamos o processo de serialização-desserialização ao usar o
Serializable
.Por um lado, isso é muito conveniente, porque se não nos importamos realmente com o desempenho, a capacidade de não escrever código parece conveniente. Mas e se realmente precisarmos adicionar alguns de nossos próprios recursos (um exemplo de um deles estará abaixo) à lógica de serialização?
Essencialmente, tudo o que temos para controlar o processo é uma palavra-chave
transient
para excluir alguns dados, e pronto. Mais ou menos como um “kit de ferramentas” :/ -
Segurança. Este ponto decorre parcialmente do anterior.
Não pensamos muito sobre isso antes, mas e se alguma informação da sua aula não for destinada aos “ouvidos de outras pessoas” (mais precisamente, aos olhos)? Um exemplo simples é uma senha ou outros dados pessoais do usuário, que no mundo moderno são regulamentados por uma série de leis.
Usando
Serializable
, na verdade não podemos fazer nada a respeito. Serializamos tudo como está.Mas, no bom sentido, devemos criptografar esse tipo de dados antes de gravá-los em um arquivo ou transmiti-los pela rede. Mas
Serializable
não dá essa oportunidade.
Externalizable
.
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long SERIAL_VERSION_UID = 1L;
//...конструктор, геттеры, сеттеры, toString()...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
Como você pode ver, fizemos mudanças significativas! A principal delas é óbvia: ao implementar uma interface, Externalizable
você deve implementar dois métodos obrigatórios - writeExternal()
e readExternal()
. Como dissemos anteriormente, toda a responsabilidade pela serialização e desserialização será do programador. Porém, agora você pode resolver o problema da falta de controle desse processo! Todo o processo é programado diretamente por você, o que, claro, cria um mecanismo muito mais flexível. Além disso, o problema de segurança também está resolvido. Como você pode ver, temos um campo em nossa classe: dados pessoais que não podem ser armazenados sem criptografia. Agora podemos escrever facilmente um código que atenda a essa restrição. Por exemplo, adicione dois métodos privados simples à nossa classe para criptografar e descriptografar dados secretos. Iremos gravá-los em um arquivo e lê-los no arquivo de forma criptografada. E vamos escrever e ler o restante dos dados como estão :) Como resultado, nossa classe ficará mais ou menos assim:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long serialVersionUID = 1L;
public UserInfo() {
}
public UserInfo(String firstName, String lastName, String superSecretInformation) {
this.firstName = firstName;
this.lastName = lastName;
this.superSecretInformation = superSecretInformation;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.getFirstName());
out.writeObject(this.getLastName());
out.writeObject(this.encryptString(this.getSuperSecretInformation()));
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
firstName = (String) in.readObject();
lastName = (String) in.readObject();
superSecretInformation = this.decryptString((String) in.readObject());
}
private String encryptString(String data) {
String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
System.out.println(encryptedData);
return encryptedData;
}
private String decryptString(String data) {
String decrypted = new String(Base64.getDecoder().decode(data));
System.out.println(decrypted);
return decrypted;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSuperSecretInformation() {
return superSecretInformation;
}
}
Implementamos dois métodos que usam os mesmos ObjectOutput out
parâmetros ObjectInput
que já encontramos na palestra sobre Serializable
. No momento certo, criptografamos ou descriptografamos os dados necessários e, desta forma, os utilizamos para serializar nosso objeto. Vamos ver como isso ficará na prática:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
UserInfo userInfo = new UserInfo("Ivan", "Ivanov", "Ivan Ivanov's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
Nos métodos encryptString()
e decryptString()
, adicionamos especificamente uma saída ao console para verificar de que forma os dados secretos serão gravados e lidos. O código acima gera a seguinte linha no console: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Criptografia bem-sucedida! O conteúdo completo do arquivo é assim: ¬н sr UserInfoГ!}ҐџC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx Agora vamos tentar usar a lógica de desserialização que escrevemos.
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
Bem, não parece haver nada complicado aqui, deve funcionar! Vamos executar... Exceção no thread "main" java.io.InvalidClassException: UserInfo; nenhum construtor válido Ops :( Acontece que não é tão simples! O mecanismo de desserialização lançou uma exceção e exigiu que criássemos um construtor padrão. Eu me pergunto por quê? Serializable
Conseguimos sem ele... :/ Aqui chegamos a outra nuance importante A diferença entre Serializable
e Externalizable
reside não apenas no acesso “estendido” do programador e na capacidade de gerenciar o processo com mais flexibilidade, mas também no próprio processo. Em primeiro lugar, no mecanismo de desserialização ... Quando usada, Serializable
a memória é simplesmente alocado para um objeto, após o qual os valores são lidos do fluxo, que preenchem todos os seus campos . Se usarmos Serializable
, o construtor do objeto não é chamado! Todo o trabalho é feito por meio de reflexão (API Reflection, que mencionamos brevemente no último palestra). No caso de , o Externalizable
mecanismo de desserialização será diferente. No início é chamado o construtor padrão. E só depois UserInfo
é chamado no objeto criado o método readExternal()
, que é responsável por preencher os campos do objeto. Ou seja por que qualquer classe que implementa a interface Externalizable
deve ter um construtor padrão . Vamos adicioná-lo à nossa classe UserInfo
e executar novamente o código:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
Saída do console: dados do passaporte de Ivan Ivanov UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='dados do passaporte de Ivan Ivanov'} Um assunto completamente diferente! Primeiro, a string descriptografada com dados secretos foi enviada para o console e, em seguida, nosso objeto foi recuperado do arquivo em formato de string! Foi assim que resolvemos todos os problemas com sucesso :) O tema serialização e desserialização parece simples, mas como você pode ver, nossas palestras acabaram sendo longas. E isso não é tudo! Existem muito mais sutilezas ao usar cada uma dessas interfaces, mas para que agora seu cérebro não exploda com o volume de novas informações, listarei brevemente mais alguns pontos importantes e fornecerei links para leituras adicionais. Então, o que mais você precisa saber? Primeiramente , ao serializar (não importa se você usa Serializable
ou Externalizable
), preste atenção nas variáveis static
. Quando usados, Serializable
esses campos não são serializados (e, portanto, seu valor não muda, pois static
os campos pertencem à classe e não ao objeto). Mas ao usá-lo, Externalizable
você mesmo controla o processo, então tecnicamente isso pode ser feito. Mas não é recomendado, pois está repleto de erros sutis. Em segundo lugar , deve-se prestar atenção também às variáveis com o modificador final
. Quando usados, Serializable
eles são serializados e desserializados normalmente, mas quando usados, é impossível Externalizable
desserializar final
uma variável ! A razão é simples: todos final
os campos são inicializados quando o construtor padrão é chamado e, depois disso, seu valor não pode ser alterado. Portanto, para serializar objetos contendo final
-fields, use a serialização padrão via Serializable
. Terceiro , ao usar herança, todas as classes herdadas descendentes de alguma Externalizable
classe também devem ter construtores padrão. Aqui estão alguns links para bons artigos sobre mecanismos de serialização:
Vê você! :)
GO TO FULL VERSION