JavaRush /Blog Java /Random-ES /¿Qué esconde el modificador transitorio en Java?
Анзор Кармов
Nivel 31
Санкт-Петербург

¿Qué esconde el modificador transitorio en Java?

Publicado en el grupo Random-ES
¡Hola! En el artículo de hoy, veremos el modificador transitorio en Java. Hablemos de por qué es necesario este modificador y cómo usarlo correctamente. ¡Ir! ¿Qué oculta el modificador transitorio en Java? - 1

Recordemos la serialización

El modificador transientse utiliza en el proceso de serializar y deserializar objetos. Así que primero hablemos brevemente sobre esto. ¿Qué esconde el modificador transitorio en Java? - 2Supongamos que tenemos algún objeto y tiene campos, cada uno de los cuales tiene algún valor. Todo esto se llama estado del objeto. La serialización es la conversión del estado de un objeto en una secuencia de bytes. Estos bytes suelen almacenarse en algún archivo. La deserialización es el proceso inverso. Imaginemos que serializamos un objeto en bytes y almacenamos este conjunto de bytes en algún archivo. Al deserializar, el programa necesita:
  1. Leer un conjunto de bytes de un archivo.
  2. Construya un objeto inicial a partir de este conjunto de bytes y establezca cada campo en el valor que tenía el objeto en el momento de la serialización.
¿Cuándo podría resultar útil? Por ejemplo, cuando queremos que el programa guarde su estado al cerrarlo y lo restaure la próxima vez que lo encienda. Cuando cierre IntelliJ IDEA, lo más probable es que tenga las mismas pestañas y clases abiertas la próxima vez que lo encienda.

Recordemos la serialización en la práctica.

Bueno, ahora veamos la serialización en la práctica. Si desea comprender mejor el tema, le recomendamos leer el material Serialización y deserialización en Java . Bueno, en este artículo repasaremos la parte superior e iremos directamente a los ejemplos. Digamos que tenemos una clase Usercon un conjunto de algunos campos, captadores y definidores, y un 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 de esta clase en el futuro. Escribamos un método que tome un objeto Usery una cadena path: la ruta al archivo en el que guardaremos los bytes:
static void serialize(User user, String path) throws IOException {
    FileOutputStream outputStream = null;
    ObjectOutputStream objectOutputStream = null;
    try {
        //crear 2 subprocesos para serializar el objeto y guardarlo en un archivo
        outputStream = new FileOutputStream(path);
        objectOutputStream = new ObjectOutputStream(outputStream);

        // сохраняем un objeto в файл
        objectOutputStream.writeObject(user);
    } finally {
        // Закроем потоки в блоке finally
        if (objectOutputStream != null) {
            objectOutputStream.close();
        }
        if (outputStream != null) {
            outputStream.close();
        }
    }
}
También escribiremos un método para la deserialización. El método toma una cadena path(la ruta al archivo desde el cual se “cargará” el objeto) y devuelve un objeto de tipo User:
static User deserialize(String path) throws IOException, ClassNotFoundException {
    FileInputStream fileInputStream = null;
    ObjectInputStream objectInputStream = null;

    try {

        //создаем 2 потока для десериализации un objetoа из archivo
        fileInputStream = new FileInputStream(path);
        objectInputStream = new ObjectInputStream(fileInputStream);

        //загружаем un objeto из archivo
        return  (User) objectInputStream.readObject();
    } finally {
        if (fileInputStream != null) {
            fileInputStream.close();
        }
        if (objectInputStream != null) {
            objectInputStream.close();
        }
    }
}
Todas las herramientas están listas para usar. Es hora de dividir los bytes en átomos . Escribamos un método mainen el que creamos un objeto de clase Usery lo serializamos. Luego lo cargaremos y lo compararemos con lo que era originalmente:
public static void main(String[] args) throws IOException, ClassNotFoundException {
    // вставьте свой путь до archivo
    final String path = "/home/zor/user.ser";

    // creamos nuestro objeto
    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");
}
Si ejecutamos el método, veremos el siguiente resultado:
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 puede ver en el resultado, los objetos son idénticos. Pero hay un pequeño pero... Y es precisamente aquí donde entra en juego la vergüenza española transient .

Modificador (finalmente)transient

¿Alguien quedó confundido porque guardamos la contraseña del usuario? Especialmente una contraseña así... Sí, sí, se nos ocurrió a nosotros mismos, pero aún así... A veces hay situaciones en las que algunos campos no se pueden serializar, o es mejor no hacerlo. En el ejemplo anterior, me gustaría guardar todos los campos excepto la contraseña. ¿Cómo lograr esto? Respuesta: usa el modificador transient. transientes un modificador colocado antes de un campo de clase (similar a otros modificadores como public, finaletc.) para indicar que el campo no debe serializarse. Los campos marcados con la palabra clave transientno están serializados. Ahora editemos el ejemplo con nuestro usuario para corregir una pequeña confusión y no guardar la contraseña del usuario. Para hacer esto, marque el campo correspondiente en la clase con la palabra clave 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...
     */
}
Si volvemos a ejecutar el método del ejemplo anterior main, veremos que la contraseña no se guarda:
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'}
Genial, logramos nuestro objetivo y no almacenamos información confidencial. Especialmente este tipo de información... (lo siento)

¿Cuándo utilizar transitorio?

Se necesitaba un ejemplo con un usuario para poder profundizar en el contexto de la serialización. Ahora hablemos más específicamente sobre cuándo usar el modificador transient.

  • Campos que se calculan mediante programación

Algunas clases a veces tienen campos que se calculan en función de otros campos u otra información. Se calculan, por así decirlo, sobre la marcha. Para dar un ejemplo de este campo, imaginemos un pedido en una tienda online o algún servicio de entrega de comida a domicilio. Cada pedido, entre otra información, consta de una lista de bienes y un costo total. Este, a su vez, consiste en el costo total de cada producto. Resulta que el costo final no debe fijarse "a mano": debe calcularse mediante programación, sumando el costo de todos los bienes. Campos como estos que deben calcularse mediante programación no necesitan serializarse. Por eso, los marcamos con un modificador transient.
class Order implements Serializable {

    private List items;
    private transient BigDecimal totalAmount; //вычисляется на ходу

}

  • Campos con información privada

También hay algunas clases que almacenan información privada. Vimos un ejemplo de dicha clase al principio del artículo. No debe permitir que dicha información se filtre fuera de la JVM. Por lo tanto, los campos con dichos datos deben marcarse con un modificador transientsi va a serializar dicha clase.

  • Campos que no implementan la interfaz.Serializable

A veces una clase contiene campos-objetos de otras clases que no implementan la interfaz Serializable. Ejemplos de dichos campos son registradores, flujos de E/S, objetos que almacenan conexiones de bases de datos y otras clases de utilidades. Si intenta serializar un objeto que contiene campos no serializables, recibirá un error java.io.NotSerializableException. Para evitar esto, todos los campos que no implementan la interfaz Serializabledeben marcarse con un modificador transient.
public class FileReader implements Serializable {
    // Первые 2 поля не реализуют Serializable
    // Помечаем их Cómo 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 con información sobre el estado del objeto.

Bueno, una última cosa. No es necesario serializar campos que no forman parte de la información de estado del objeto. Los ejemplos anteriores caen bajo esta regla. Pero también puede incluir aquí todos los demás campos agregados para depurar o realizar algún tipo de función de servicio que no contengan información sobre el estado del objeto.

transientYfinal

Resultados

Eso es todo. Hoy hablamos del modificador transient:
  1. Recordamos la serialización en teoría y práctica.
  2. Nos dimos cuenta de que para no serializar algunos campos de la clase, es necesario marcarlos con un modificador transient.
  3. Discutimos en qué situaciones se debe utilizar este modificador. Hubo cuatro situaciones de este tipo:
    1. campos que se calculan mediante programación;
    2. campos que contienen información secreta;
    3. campos que no implementan la interfaz Serializable;
    4. campos que no forman parte del estado del objeto.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION