JavaRush /Java Blog /Random-IT /Cosa nasconde il modificatore transitorio in Java?
Анзор Кармов
Livello 31
Санкт-Петербург

Cosa nasconde il modificatore transitorio in Java?

Pubblicato nel gruppo Random-IT
Ciao! Nell'articolo di oggi esamineremo il modificatore transitorio in Java. Parliamo del motivo per cui è necessario questo modificatore e di come utilizzarlo correttamente. Andare! Cosa nasconde il modificatore transitorio in Java - 1

Ricordiamo la serializzazione

Il modificatore transientviene utilizzato nel processo di serializzazione e deserializzazione degli oggetti. Quindi parliamo brevemente di questo prima. Cosa nasconde il modificatore transitorio in Java - 2Supponiamo di avere un oggetto con campi, ognuno dei quali ha un valore. Tutto questo è chiamato lo stato dell'oggetto. La serializzazione è la conversione dello stato di un oggetto in una sequenza di byte. Questi byte sono solitamente memorizzati in qualche file. La deserializzazione è il processo inverso. Immaginiamo di aver serializzato un oggetto in byte e di aver memorizzato questo insieme di byte in un file. Durante la deserializzazione, il programma necessita di:
  1. Legge un insieme di byte da un file.
  2. Costruisci un oggetto iniziale da questo insieme di byte e imposta ciascun campo sul valore che l'oggetto aveva al momento della serializzazione.
Quando potrebbe essere utile? Ad esempio, quando vogliamo che il programma salvi il suo stato allo spegnimento e lo ripristini alla successiva accensione. Quando chiudi IntelliJ IDEA, molto probabilmente avrai le stesse schede e classi aperte la prossima volta che lo accendi

Ricordiamo la serializzazione in pratica

Bene, ora diamo un'occhiata alla serializzazione in pratica. Se vuoi comprendere meglio l'argomento ti consigliamo di leggere il materiale Serializzazione e deserializzazione in Java . Bene, in questo articolo andremo oltre e andremo direttamente agli esempi. Diciamo di avere una classe Usercon un insieme di alcuni campi, getter e setter e un metodo 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 + '\'' +
                '}';
    }
}
Vogliamo serializzare gli oggetti di questa classe in futuro. Scriviamo un metodo che accetta un oggetto Usere una stringa path: il percorso del file in cui salveremo i byte:
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();
        }
    }
}
Scriveremo anche un metodo per la deserializzazione. Il metodo prende una stringa path(il percorso del file da cui verrà “caricato” l'oggetto) e restituisce un oggetto di 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();
        }
    }
}
Tutti gli strumenti sono pronti per l'uso. È ora di dividere i byte in atomi . Scriviamo un metodo mainin cui creiamo un oggetto classe Usere lo serializziamo. Quindi lo caricheremo e lo confronteremo con quello che era originariamente:
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 eseguiamo il metodo, vedremo il seguente output:
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'}
Come puoi vedere dall'output, gli oggetti sono identici. Ma c’è un piccolo ma… Ed è proprio qui che entra in gioco la vergogna spagnola transient .

Modificatore (finalmente)transient

Qualcuno era confuso dal fatto che abbiamo salvato la password dell'utente? Soprattutto una password del genere... Sì, sì, l'abbiamo inventata noi stessi, ma comunque... A volte ci sono situazioni in cui alcuni campi non possono essere serializzati, oppure è meglio non farlo. Nell'esempio sopra, vorrei salvare tutti i campi tranne la password. Come raggiungere questo obiettivo? Risposta: usa il modificatore transient. transientè un modificatore posizionato prima di un campo di classe (simile ad altri modificatori come public, finalecc.) per indicare che il campo non deve essere serializzato. I campi contrassegnati con la parola chiave transientnon vengono serializzati. Ora modifichiamo l'esempio con il nostro utente per correggere una piccola confusione e non salvare la password dell'utente. Per fare ciò, contrassegna il campo corrispondente nella classe con la parola chiave 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 eseguiamo nuovamente il metodo dell'esempio precedente main, vedremo che la password non viene salvata:
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'}
Ottimo, abbiamo raggiunto il nostro obiettivo e non conserviamo informazioni riservate. Soprattutto questo tipo di informazioni... (scusa)

Quando utilizzare il transitorio?

Era necessario un esempio con un utente per immergersi nel contesto della serializzazione. Ora parliamo più nello specifico di quando utilizzare il modificatore transient.

  • Campi calcolati a livello di codice

Alcune classi talvolta dispongono di campi calcolati in base ad altri campi o ad altre informazioni. Vengono calcolati, per così dire, al volo. Per fare un esempio di tale campo, immaginiamo un ordine in un negozio online o in un servizio di consegna di cibo. Ogni ordine, tra le altre informazioni, consiste in un elenco di merci e un costo totale. A sua volta, consiste nel costo totale di ciascun prodotto. Si scopre che il costo finale non dovrebbe essere fissato “a mano”: deve essere calcolato in modo programmatico, sommando il costo di tutti i beni. Campi come questi che devono essere calcolati a livello di codice non devono essere serializzati. Pertanto, li contrassegniamo con un modificatore transient.
class Order implements Serializable {

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

}

  • Campi con informazioni private

Esistono anche alcune classi che memorizzano informazioni private. Abbiamo visto un esempio di tale classe all'inizio dell'articolo. Non dovresti consentire che tali informazioni fuoriescano dalla JVM. Pertanto, i campi con tali dati devono essere contrassegnati con un modificatore transientse si intende serializzare tale classe.

  • Campi che non implementano l'interfacciaSerializable

A volte una classe contiene campi-oggetto di altre classi che non implementano l'interfaccia Serializable. Esempi di tali campi sono logger, flussi I/O, oggetti che memorizzano connessioni al database e altre classi di utilità. Se provi a serializzare un oggetto che contiene campi non serializzabili, riceverai un errore java.io.NotSerializableException. Per evitare ciò, tutti i campi che non implementano l'interfaccia Serializabledevono essere contrassegnati con un modificatore 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();
            }
        }
    }
}

  • Campi con informazioni sullo stato dell'oggetto

Bene, un'ultima cosa. Non è necessario serializzare i campi che non fanno parte delle informazioni sullo stato dell'oggetto. Gli esempi sopra riportati rientrano in questa regola. Ma puoi anche includere qui tutti gli altri campi aggiunti per il debug o per eseguire qualche tipo di funzione di servizio che non riporta informazioni sullo stato dell'oggetto.

transientEfinal

Risultati

È tutto. Oggi abbiamo parlato del modificatore transient:
  1. Abbiamo ricordato la serializzazione in teoria e in pratica.
  2. Ci siamo accorti che per non serializzare alcuni campi della classe è necessario contrassegnarli con un modificatore transient.
  3. Abbiamo discusso in quali situazioni dovrebbe essere utilizzato questo modificatore. C'erano quattro situazioni simili:
    1. campi calcolati a livello di codice;
    2. campi che contengono informazioni segrete;
    3. campi che non implementano l'interfaccia Serializable;
    4. campi che non fanno parte dello stato dell'oggetto.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION