JavaRush /Java Blog /Random-JA /Java での transient 修飾子には何が隠されているのでしょうか?
Анзор Кармов
レベル 31
Санкт-Петербург

Java での transient 修飾子には何が隠されているのでしょうか?

Random-JA グループに公開済み
こんにちは!今日の記事では、Java の transient 修飾子について見ていきます。この修飾子が必要な理由と、それを正しく使用する方法について説明しましょう。行く! Java での transient 修飾子には何が隠されていますか - 1

連載を思い出しましょう

修飾子は、transientオブジェクトのシリアル化および逆シリアル化のプロセスで使用されます。そこで、最初にこれについて簡単に説明しましょう。Java での transient 修飾子には何が隠されていますか - 2何らかのオブジェクトがあり、それにフィールドがあり、それぞれが何らかの値を持っているとします。これらすべてをオブジェクトの状態と呼びます。シリアル化とは、オブジェクトの状態をバイトのシーケンスに変換することです。これらのバイトは通常、何らかのファイルに保存されます。逆シリアル化はその逆のプロセスです。オブジェクトをバイトにシリアル化し、このバイトのセットを何らかのファイルに保存したと想像してみましょう。逆シリアル化する場合、プログラムには以下が必要です。
  1. ファイルから一連のバイトを読み取ります。
  2. このバイトのセットから初期オブジェクトを構築し、各フィールドをシリアル化時にオブジェクトが持っていた値に設定します。
これはいつ役立つでしょうか? たとえば、プログラムをシャットダウンするときにその状態を保存し、次回電源を入れたときにその状態を復元するようにしたい場合です。IntelliJ IDEA をシャットダウンすると、次回起動したときに同じタブとクラスが開く可能性が高くなります。

シリアル化を実践的に覚えよう

さて、実際にシリアル化を見てみましょう。このトピックをよりよく理解したい場合は、「Java でのシリアル化と逆シリアル化 」という資料を読むことをお勧めします。さて、この記事では、徹底的に説明して、例に直接進みます。Userいくつかのフィールド、ゲッターとセッター、およびメソッドのセットを持つクラスがあるとします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 + '\'' +
                '}';
    }
}
将来的には、このクラスのオブジェクトをシリアル化したいと考えています。オブジェクトUserと文字列path(バイトを保存するファイルへのパス) を受け取るメソッドを作成しましょう。
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();
        }
    }
}
逆シリアル化のメソッドも書きます。このメソッドは文字列path(オブジェクトが「ロード」されるファイルへのパス) を受け取り、次のタイプのオブジェクトを返します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();
        }
    }
}
すべてのツールが使用できるようになりました。バイトをアトムに分割するときが来ました。mainクラス オブジェクトを作成してシリアル化するメソッドを作成しましょうUser。次に、それをロードして、元のものと比較します。
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");
}
このメソッドを実行すると、次の出力が表示されます。
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'}
出力からわかるように、オブジェクトは同一です。しかし、小さいこともあります... そして、これがまさにスペインの恥が transient作用する場所です。

修飾子 (最終的に)transient

ユーザーのパスワードが保存されていることを混乱した人はいませんか? 特にそのようなパスワードは...はい、はい、私たち自身で考え出しましたが、それでも...一部のフィールドをシリアル化できない、またはシリアル化しない方がよい状況が時々あります。上の例では、パスワードを除くすべてのフィールドを保存したいと思います。これを達成するにはどうすればよいでしょうか? 答え: 修飾子を使用しますtransient。 は、フィールドがシリアル化されないことを示すために、transientクラス フィールドの前に置かれる修飾子です ( などの他の修飾子と同様public) 。finalキーワードでマークされたフィールドはtransientシリアル化されません。次に、ユーザーの例を編集して、小さな混乱を修正し、ユーザーのパスワードを保存しないようにしましょう。これを行うには、クラス内の対応するフィールドをキーワードでマークします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...
     */
}
上記の例のメソッドを再度実行するとmain、パスワードが保存されていないことがわかります。
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'}
見事に目標を達成しました。機密情報は保存されません。特にこういう情報は…(すみません)

トランジェントをいつ使用するか?

シリアル化のコンテキストを詳しく説明するには、ユーザーの例が必要でした。ここで、修飾子をいつ使用するかについてより具体的に説明しましょうtransient

  • プログラムで計算されるフィールド

一部のクラスには、他のフィールドまたは他の情報に基づいて計算されるフィールドが含まれる場合があります。いわば、その場で計算されます。このような分野の例として、オンライン ストアや食品配達サービスでの注文を想像してみましょう。各注文は、特に商品のリストと合計コストで構成されます。つまり、各製品の合計コストで構成されます。最終的なコストは「手動」で設定すべきではないことがわかりました。すべての商品のコストを合計して、プログラムで計算する必要があります。このようなプログラムで計算する必要があるフィールドはシリアル化する必要はありません。したがって、それらを修飾子でマークしますtransient
class Order implements Serializable {

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

}

  • 個人情報を含むフィールド

個人情報を保存するクラスもいくつかあります。この記事の冒頭で、そのようなクラスの例を取り上げました。このような情報が JVM の外部に漏洩しないようにしてください。transientしたがって、そのようなクラスをシリアル化する場合は、そのようなデータを含むフィールドを修飾子でマークする必要があります。

  • インターフェースを実装していないフィールドSerializable

場合によっては、クラスにフィールド、つまりインターフェイスを実装していない他のクラスのオブジェクトが含まれることがありますSerializable。このようなフィールドの例としては、ロガー、I/O ストリーム、データベース接続を保存するオブジェクト、およびその他のユーティリティ クラスがあります。シリアル化不可能なフィールドを含むオブジェクトをシリアル化しようとすると、エラーが発生しますjava.io.NotSerializableException。これを回避するには、インターフェースを実装していないすべてのフィールドをSerializable修飾子でマークする必要があります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();
            }
        }
    }
}

  • オブジェクトの状態に関する情報を含むフィールド

さて、最後に一つ。オブジェクトの状態情報の一部ではないフィールドをシリアル化する必要はありません。上記の例はこのルールに当てはまります。ただし、デバッグ用に追加したり、オブジェクトの状態に関する情報を持たないある種のサービス関数を実行するために追加された他のすべてのフィールドをここに含めることもできます。

transientそしてfinal

結果

それだけです。今日は修飾子について話しましたtransient
  1. 理論と実践の連載を思い出しました。
  2. クラスの一部のフィールドをシリアル化しないようにするには、それらのフィールドを修飾子でマークする必要があることに気付きましたtransient
  3. この修飾子がどのような状況で使用されるべきかを議論しました。そのような状況は次の 4 つでした。
    1. プログラムで計算されるフィールド。
    2. 機密情報を含むフィールド。
    3. インターフェースを実装していないフィールドSerializable
    4. オブジェクトの状態の一部ではないフィールド。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION