JavaRush /Java-Blog /Random-DE /Was verbirgt der Transient-Modifikator in Java?
Анзор Кармов
Level 31
Санкт-Петербург

Was verbirgt der Transient-Modifikator in Java?

Veröffentlicht in der Gruppe Random-DE
Hallo! Im heutigen Artikel werden wir uns den transienten Modifikator in Java ansehen. Lassen Sie uns darüber sprechen, warum dieser Modifikator benötigt wird und wie man ihn richtig verwendet. Gehen! Was verbirgt der Transient-Modifikator in Java - 1

Erinnern wir uns an die Serialisierung

Der Modifikator transientwird beim Serialisieren und Deserialisieren von Objekten verwendet. Lassen Sie uns zunächst kurz darüber sprechen. Was verbirgt der Transientenmodifikator in Java - 2?Angenommen, wir haben ein Objekt und es verfügt über Felder, von denen jedes einen bestimmten Wert hat. All dies wird als Zustand des Objekts bezeichnet. Bei der Serialisierung handelt es sich um die Umwandlung des Zustands eines Objekts in eine Folge von Bytes. Diese Bytes werden normalerweise in einer Datei gespeichert. Die Deserialisierung ist der umgekehrte Vorgang. Stellen wir uns vor, wir hätten ein Objekt in Bytes serialisiert und diesen Satz Bytes in einer Datei gespeichert. Beim Deserialisieren benötigt das Programm:
  1. Liest eine Reihe von Bytes aus einer Datei.
  2. Erstellen Sie aus diesem Satz von Bytes ein anfängliches Objekt und setzen Sie jedes Feld auf den Wert, den das Objekt zum Zeitpunkt der Serialisierung hatte.
Wann könnte dies nützlich sein? Wenn wir beispielsweise möchten, dass das Programm seinen Zustand beim Herunterfahren speichert und beim nächsten Einschalten wiederherstellt. Wenn Sie IntelliJ IDEA herunterfahren, werden beim nächsten Einschalten höchstwahrscheinlich dieselben Registerkarten und Klassen geöffnet sein

Erinnern wir uns an die Serialisierung in der Praxis

Schauen wir uns nun die Serialisierung in der Praxis an. Wenn Sie das Thema besser verstehen möchten, empfehlen wir die Lektüre des Materials Serialisierung und Deserialisierung in Java . Nun, in diesem Artikel werden wir es übertreiben und direkt zu den Beispielen gehen. Nehmen wir an, wir haben eine Klasse Usermit einer Reihe einiger Felder, Getter und Setter sowie einer Methode 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 + '\'' +
                '}';
    }
}
Wir wollen in Zukunft Objekte dieser Klasse serialisieren. Schreiben wir eine Methode, die ein Objekt Userund einen String akzeptiert path– den Pfad zu der Datei, in der wir die Bytes speichern:
static void serialize(User user, String path) throws IOException {
    FileOutputStream outputStream = null;
    ObjectOutputStream objectOutputStream = null;
    try {
        //2 Threads erstellen, um das Objekt zu serialisieren und in einer Datei zu speichern
        outputStream = new FileOutputStream(path);
        objectOutputStream = new ObjectOutputStream(outputStream);

        // сохраняем ein Objekt в файл
        objectOutputStream.writeObject(user);
    } finally {
        // Закроем потоки в блоке finally
        if (objectOutputStream != null) {
            objectOutputStream.close();
        }
        if (outputStream != null) {
            outputStream.close();
        }
    }
}
Wir werden auch eine Methode zur Deserialisierung schreiben. Die Methode nimmt einen String path(den Pfad zu der Datei, aus der das Objekt „geladen“ wird) und gibt ein Objekt des Typs zurück User:
static User deserialize(String path) throws IOException, ClassNotFoundException {
    FileInputStream fileInputStream = null;
    ObjectInputStream objectInputStream = null;

    try {

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

        //загружаем ein Objekt из Datei
        return  (User) objectInputStream.readObject();
    } finally {
        if (fileInputStream != null) {
            fileInputStream.close();
        }
        if (objectInputStream != null) {
            objectInputStream.close();
        }
    }
}
Alle Werkzeuge sind einsatzbereit. Es ist Zeit, Bytes in Atome aufzuteilen . Schreiben wir eine Methode main, in der wir ein Klassenobjekt erstellen Userund es serialisieren. Dann laden wir es und vergleichen es mit dem Original:
public static void main(String[] args) throws IOException, ClassNotFoundException {
    // вставьте свой путь до Datei
    final String path = "/home/zor/user.ser";

    // unser Objekt erstellen
    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");
}
Wenn wir die Methode ausführen, sehen wir die folgende Ausgabe:
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'}
Wie Sie der Ausgabe entnehmen können, sind die Objekte identisch. Aber es gibt ein kleines Aber... Und genau hier kommt die spanische Schande transient ins Spiel .

Modifikator (endlich)transient

War irgendjemand verwirrt darüber, dass wir das Passwort des Benutzers gespeichert haben? Besonders ein solches Passwort... Ja, ja, wir haben es uns selbst ausgedacht, aber trotzdem... Manchmal gibt es Situationen, in denen einige Felder nicht serialisiert werden können, oder es ist besser, dies nicht zu tun. Im obigen Beispiel möchte ich alle Felder außer dem Passwort speichern. Wie erreicht man das? Antwort: Verwenden Sie den Modifikator transient. transientist ein Modifikator, der vor einem Klassenfeld platziert wird (ähnlich wie andere Modifikatoren wie usw.), um anzugeben, dass das publicFeld finalnicht serialisiert werden soll. Mit dem Schlüsselwort gekennzeichnete Felder transientwerden nicht serialisiert. Bearbeiten wir nun das Beispiel mit unserem Benutzer, um eine kleine Verwirrung zu beheben und das Passwort des Benutzers nicht zu speichern. Markieren Sie dazu das entsprechende Feld in der Klasse mit dem Schlüsselwort 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...
     */
}
Wenn wir die Methode aus dem obigen Beispiel erneut ausführen main, werden wir feststellen, dass das Passwort nicht gespeichert wird:
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'}
Großartig, wir haben unser Ziel erreicht und speichern keine vertraulichen Informationen. Besonders diese Art von Informationen... (sorry)

Wann sollte man transient verwenden?

Um in den Kontext der Serialisierung einzutauchen, wurde ein Beispiel mit einem Benutzer benötigt. Lassen Sie uns nun genauer darüber sprechen, wann der Modifikator verwendet werden sollte transient.

  • Felder, die programmgesteuert berechnet werden

Einige Klassen verfügen manchmal über Felder, die auf der Grundlage anderer Felder oder anderer Informationen berechnet werden. Sie werden sozusagen spontan berechnet. Um ein Beispiel für einen solchen Bereich zu geben, stellen wir uns eine Bestellung in einem Online-Shop oder einem Lebensmittellieferdienst vor. Jede Bestellung besteht unter anderem aus einer Warenliste und einem Gesamtpreis. Es setzt sich wiederum aus den Gesamtkosten jedes Produkts zusammen. Es stellt sich heraus, dass die endgültigen Kosten nicht „von Hand“ festgelegt werden sollten: Sie müssen programmgesteuert berechnet werden, indem die Kosten aller Waren summiert werden. Felder wie diese, die programmgesteuert berechnet werden sollten, müssen nicht serialisiert werden. Deshalb kennzeichnen wir sie mit einem Modifikator transient.
class Order implements Serializable {

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

}

  • Felder mit privaten Informationen

Es gibt auch einige Klassen, die private Informationen speichern. Ein Beispiel einer solchen Klasse haben wir uns am Anfang des Artikels angesehen. Sie sollten nicht zulassen, dass solche Informationen außerhalb der JVM durchsickern. Daher müssen Felder mit solchen Daten mit einem Modifikator gekennzeichnet werden, transientwenn Sie eine solche Klasse serialisieren möchten.

  • Felder, die die Schnittstelle nicht implementierenSerializable

Manchmal enthält eine Klasse Felder – Objekte anderer Klassen, die die Schnittstelle nicht implementieren Serializable. Beispiele für solche Felder sind Logger, I/O-Streams, Objekte, die Datenbankverbindungen speichern und andere Dienstprogrammklassen. Wenn Sie versuchen, ein Objekt zu serialisieren, das nicht serialisierbare Felder enthält, erhalten Sie eine Fehlermeldung java.io.NotSerializableException. Um dies zu vermeiden, müssen alle Felder, die die Schnittstelle nicht implementieren, Serializablemit einem Modifikator gekennzeichnet werden transient.
public class FileReader implements Serializable {
    // Первые 2 поля не реализуют Serializable
    // Помечаем их Wie 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();
            }
        }
    }
}

  • Felder mit Informationen zum Objektzustand

Nun, noch eine letzte Sache. Es besteht keine Notwendigkeit, Felder zu serialisieren, die nicht Teil der Statusinformationen des Objekts sind. Die obigen Beispiele fallen unter diese Regel. Sie können hier aber auch alle anderen Felder einschließen, die zum Debuggen oder zur Ausführung einer Servicefunktion hinzugefügt wurden und keine Informationen über den Zustand des Objekts enthalten.

transientUndfinal

Ergebnisse

Das ist alles. Heute haben wir über den Modifikator gesprochen transient:
  1. Wir erinnerten uns an die Serialisierung in Theorie und Praxis.
  2. Wir haben festgestellt, dass einige Felder der Klasse mit einem Modifikator markiert werden müssen, damit sie nicht serialisiert werden transient.
  3. Wir haben besprochen, in welchen Situationen dieser Modifikator verwendet werden sollte. Es gab vier solcher Situationen:
    1. Felder, die programmgesteuert berechnet werden;
    2. Felder, die geheime Informationen enthalten;
    3. Felder, die die Schnittstelle nicht implementieren Serializable;
    4. Felder, die nicht Teil des Objektstatus sind.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION