JavaRush /Blogue Java /Random-PT /O que o modificador transitório esconde em Java?
Анзор Кармов
Nível 31
Санкт-Петербург

O que o modificador transitório esconde em Java?

Publicado no grupo Random-PT
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! O que o modificador transitório esconde em Java - 1

Vamos lembrar a serialização

O modificador transienté usado no processo de serialização e desserialização de objetos. Então, vamos falar brevemente sobre isso primeiro. O que o modificador transitório esconde em Java - 2Suponha 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:
  1. Leia um conjunto de bytes de um arquivo.
  2. 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.
Quando isso pode ser útil? Por exemplo, quando queremos que o programa salve seu estado ao desligar e o restaure na próxima vez que for ligado. Ao desligar o IntelliJ IDEA, você provavelmente terá as mesmas guias e classes abertas na próxima vez que ligá-lo

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 classe Usercom 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 Usere 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 átomos . Vamos escrever um método mainno qual criamos um objeto de classe Usere 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 a vergonha espanhola 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, finaletc.) para indicar que o campo não deve ser serializado. Os campos marcados com a palavra-chave transientnã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 modificador transient.

  • 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 transientse você quiser serializar tal classe.

  • Campos que não implementam a interfaceSerializable

Às vezes, uma classe contém campos – objetos de outras classes que não implementam a interface 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 Serializabledevem 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.

transientEfinal

Resultados

Isso é tudo. Hoje falamos sobre o modificador transient:
  1. Lembramos da serialização na teoria e na prática.
  2. Percebemos que para não serializar alguns campos da classe, eles precisam ser marcados com um modificador transient.
  3. Discutimos em quais situações esse modificador deve ser usado. Houve quatro dessas situações:
    1. campos que são calculados programaticamente;
    2. campos que contêm informações secretas;
    3. campos que não implementam a interface Serializable;
    4. campos que não fazem parte do estado do objeto.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION