Olá! No artigo de hoje, veremos o modificador transitório em Java. Vamos falar sobre por que esse modificador é necessário e como usá-lo corretamente. Ir!
átomos . Vamos escrever um método a vergonha espanhola Modificador (finalmente)
Alguém ficou confuso porque salvamos a senha do usuário? Principalmente essa senha... Sim, sim, nós mesmos criamos ela, mas ainda assim... Às vezes há situações em que alguns campos não podem ser serializados, ou é melhor não fazer isso. No exemplo acima, gostaria de salvar todos os campos, exceto a senha. Como conseguir isso? Resposta: use o modificador
Algumas classes às vezes possuem campos que são calculados com base em outros campos ou outras informações. Eles são calculados, por assim dizer, na hora. Para dar um exemplo desse campo, vamos imaginar um pedido em uma loja online ou em algum serviço de entrega de comida. Cada pedido, entre outras informações, consiste em uma lista de mercadorias e um custo total. Ele, por sua vez, consiste no custo total de cada produto. Acontece que o custo final não deve ser definido “manualmente”: deve ser calculado de forma programática, somando o custo de todas as mercadorias. Campos como esses, que deveriam ser calculados programaticamente, não precisam ser serializados. Portanto, nós os marcamos com um modificador
Existem também algumas classes que armazenam informações privadas. Vimos um exemplo dessa classe no início do artigo. Você não deve permitir que tais informações vazem para fora da JVM. Portanto, os campos com esses dados devem ser marcados com um modificador
Às vezes, uma classe contém campos – objetos de outras classes que não implementam a interface
Bem, uma última coisa. Não há necessidade de serializar campos que não fazem parte das informações de estado do objeto. Os exemplos acima se enquadram nesta regra. Mas você também pode incluir aqui todos os outros campos adicionados para depuração ou para executar algum tipo de função de serviço que não contenha informações sobre o estado do objeto.
Vamos lembrar a serialização
O modificadortransient
é usado no processo de serialização e desserialização de objetos. Então, vamos falar brevemente sobre isso primeiro. Suponha que temos algum objeto e ele possui campos, cada um dos quais com algum valor. Tudo isso é chamado de estado do objeto. A serialização é a conversão do estado de um objeto em uma sequência de bytes. Esses bytes geralmente são armazenados em algum arquivo. A desserialização é o processo inverso. Vamos imaginar que serializamos um objeto em bytes e armazenamos esse conjunto de bytes em algum arquivo. Ao desserializar, o programa precisa de:
- Leia um conjunto de bytes de um arquivo.
- Construa um objeto inicial a partir desse conjunto de bytes e defina cada campo com o valor que o objeto tinha no momento da serialização.
Vamos lembrar a serialização na prática
Bem, agora vamos ver a serialização na prática. Caso queira entender melhor o tema, recomendamos a leitura do material Serialização e desserialização em Java . Pois bem, neste artigo iremos exagerar e ir direto aos exemplos. Digamos que temos uma classeUser
com um conjunto de alguns campos, getters e setters e um método toString
:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private String email;
private LocalDate birthDate;
private String login;
private String password;
public User() {}
public User(String firstName, String lastName, String email, LocalDate birthDate, String login, String password) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.birthDate = birthDate;
this.login = login;
this.password = password;
}
/*
Геттеры, Сеттеры
*/
@Override
public String toString() {
return "User{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", birthDate=" + birthDate +
", login='" + login + '\'' +
", password='" + password + '\'' +
'}';
}
}
Queremos serializar objetos desta classe no futuro. Vamos escrever um método que receba um objeto User
e uma string path
- o caminho para o arquivo no qual salvaremos os bytes:
static void serialize(User user, String path) throws IOException {
FileOutputStream outputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
//create 2 threads to serialize the object and save it to a file
outputStream = new FileOutputStream(path);
objectOutputStream = new ObjectOutputStream(outputStream);
// сохраняем an object в файл
objectOutputStream.writeObject(user);
} finally {
// Закроем потоки в блоке finally
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
Também escreveremos um método para desserialização. O método pega uma string path
(o caminho para o arquivo do qual o objeto será “carregado”) e retorna um objeto do tipo User
:
static User deserialize(String path) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = null;
ObjectInputStream objectInputStream = null;
try {
//создаем 2 потока для десериализации an object из file
fileInputStream = new FileInputStream(path);
objectInputStream = new ObjectInputStream(fileInputStream);
//загружаем an object из file
return (User) objectInputStream.readObject();
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
if (objectInputStream != null) {
objectInputStream.close();
}
}
}
Todas as ferramentas estão prontas para uso. É hora de dividir os bytes em main
no qual criamos um objeto de classe User
e o serializamos. Então vamos carregá-lo e compará-lo com o que era originalmente:
public static void main(String[] args) throws IOException, ClassNotFoundException {
// вставьте свой путь до file
final String path = "/home/zor/user.ser";
// create our object
User user = new User();
user.setFirstName("Stefan");
user.setLastName("Smith");
user.setEmail("ssmith@email.com");
user.setBirthDate(LocalDate.of(1991, 7, 16));
user.setLogin("ssmith");
user.setPassword("gemma_arterton_4ever_in_my_heart91");
System.out.println("Initial user: " + user + "\r\n");
serialize(user, path);
User loadedUser = deserialize(path);
System.out.println("Loaded user from file: " + loadedUser + "\r\n");
}
Se executarmos o método, veremos a seguinte saída:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
Como você pode ver na saída, os objetos são idênticos. Mas há um pequeno mas... E é exactamente aqui que transient
entra em jogo .
Modificador (finalmente)transient
Alguém ficou confuso porque salvamos a senha do usuário? Principalmente essa senha... Sim, sim, nós mesmos criamos ela, mas ainda assim... Às vezes há situações em que alguns campos não podem ser serializados, ou é melhor não fazer isso. No exemplo acima, gostaria de salvar todos os campos, exceto a senha. Como conseguir isso? Resposta: use o modificador transient
. transient
é um modificador colocado antes de um campo de classe (semelhante a outros modificadores como public
, final
etc.) para indicar que o campo não deve ser serializado. Os campos marcados com a palavra-chave transient
não são serializados. Agora vamos editar o exemplo com nosso usuário para corrigir uma pequena confusão e não salvar a senha do usuário. Para fazer isso, marque o campo correspondente na classe com a palavra-chave transient
:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private String email;
private LocalDate birthDate;
private String login;
private transient String password;
/*
Конструкторы, геттеры, сеттеры, toString...
*/
}
Se executarmos novamente o método do exemplo acima main
, veremos que a senha não foi salva:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='null'}
Ótimo, atingimos nosso objetivo e não armazenamos informações confidenciais. Principalmente esse tipo de informação... (desculpe)
Quando usar transitório?
Foi necessário um exemplo com um usuário para mergulhar no contexto da serialização. Agora vamos falar mais especificamente sobre quando usar o modificadortransient
.
- Campos que são calculados programaticamente
Algumas classes às vezes possuem campos que são calculados com base em outros campos ou outras informações. Eles são calculados, por assim dizer, na hora. Para dar um exemplo desse campo, vamos imaginar um pedido em uma loja online ou em algum serviço de entrega de comida. Cada pedido, entre outras informações, consiste em uma lista de mercadorias e um custo total. Ele, por sua vez, consiste no custo total de cada produto. Acontece que o custo final não deve ser definido “manualmente”: deve ser calculado de forma programática, somando o custo de todas as mercadorias. Campos como esses, que deveriam ser calculados programaticamente, não precisam ser serializados. Portanto, nós os marcamos com um modificador transient
.
class Order implements Serializable {
private List- items;
private transient BigDecimal totalAmount; //вычисляется на ходу
}
- Campos com informações privadas
Existem também algumas classes que armazenam informações privadas. Vimos um exemplo dessa classe no início do artigo. Você não deve permitir que tais informações vazem para fora da JVM. Portanto, os campos com esses dados devem ser marcados com um modificador transient
se você quiser serializar tal classe.
- Campos que não implementam a interface
Serializable
Às vezes, uma classe contém campos – objetos de outras classes que não implementam a interface Serializable
Serializable
. Exemplos de tais campos são registradores, fluxos de E/S, objetos que armazenam conexões de banco de dados e outras classes de utilitários. Se você tentar serializar um objeto que contém campos não serializáveis, receberá um erro java.io.NotSerializableException
. Para evitar isso, todos os campos que não implementam a interface Serializable
devem ser marcados com um modificador transient
.
public class FileReader implements Serializable {
// Первые 2 поля не реализуют Serializable
// Помечаем их How transient поля
private transient InputStream is;
private transient BufferedReader buf;
private String fileName;
// Constructors, Getters, Setters
public String readFile() throws IOException {
try {
is = new FileInputStream(fileName);
buf = new BufferedReader(new InputStreamReader(is));
String line = buf.readLine();
StringBuilder sb = new StringBuilder();
while (line != null) {
sb.append(line).append("\n");
line = buf.readLine();
}
return sb.toString();
} finally {
if (buf != null) {
buf.close();
}
if (is != null) {
is.close();
}
}
}
}
- Campos com informações sobre o estado do objeto
Bem, uma última coisa. Não há necessidade de serializar campos que não fazem parte das informações de estado do objeto. Os exemplos acima se enquadram nesta regra. Mas você também pode incluir aqui todos os outros campos adicionados para depuração ou para executar algum tipo de função de serviço que não contenha informações sobre o estado do objeto.
transient
Efinal
Resultados
Isso é tudo. Hoje falamos sobre o modificadortransient
:
- Lembramos da serialização na teoria e na prática.
- Percebemos que para não serializar alguns campos da classe, eles precisam ser marcados com um modificador
transient
. - Discutimos em quais situações esse modificador deve ser usado. Houve quatro dessas situações:
- campos que são calculados programaticamente;
- campos que contêm informações secretas;
- campos que não implementam a interface
Serializable
; - campos que não fazem parte do estado do objeto.
GO TO FULL VERSION