Java のシリアライズと逆シリアライズ

JavaBeginner
オンラインで実践に進む

はじめに

シリアライゼーションと逆シリアライゼーションは、Java において重要なプロセスであり、それぞれオブジェクトをバイトストリームに変換し、それを再度オブジェクトに復元することを可能にします。シリアライゼーションと逆シリアライゼーションを使用すると、オブジェクトの状態を保存または永続化したり、ネットワークを介してオブジェクトを送信したりすることができます。この実験では、ObjectInputStream クラスと ObjectOutputStream クラスを使用して Java でオブジェクトをシリアライズおよび逆シリアライズする方法を学びます。

Serializable インターフェイスを実装する

オブジェクトをシリアライズ可能にするには、Serializable インターフェイスを実装する必要があります。これは、実装するメソッドが必要ないマーカーインターフェイスです。Serializable インターフェイスは、このクラスのオブジェクトがシリアライズ可能であることを JVM に示すために使用されます。

import java.io.Serializable;

public class Student implements Serializable {
    private String name;
    private int age;

    // コンストラクタと getter/setter メソッド
}

シリアライズするオブジェクトを作成する

次の手順でシリアライズする Student クラスのオブジェクトを作成します。

Student student1 = new Student("Alice", 22);
Student student2 = new Student("Bob", 21);

オブジェクトをシリアライズする

オブジェクトをシリアライズするには、ObjectOutputStream のインスタンスを作成し、シリアライズしたいオブジェクトを渡してそのwriteObject()メソッドを呼び出す必要があります。これにより、オブジェクトがバイトストリームとしてファイルに書き込まれ、後で逆シリアライズに使用できるようになります。

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;

public class SerializationDemo {
    public static void main(String[] args) {
        try {
            FileOutputStream fileOut = new FileOutputStream("students.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);

            out.writeObject(student1);
            out.writeObject(student2);

            out.close();
            fileOut.close();

            System.out.println("Serialized data is saved in students.ser file");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

オブジェクトを逆シリアライズする

オブジェクトを逆シリアライズするには、ObjectInputStream のインスタンスを作成し、そのreadObject()メソッドを呼び出して、先ほどシリアライズしたファイルからバイトストリームを読み取る必要があります。これにより、オブジェクトが返され、それを元のクラス型にキャストできます。

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

public class SerializationDemo {
    public static void main(String[] args) {
        try {
            FileInputStream fileIn = new FileInputStream("students.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);

            Student student1 = (Student) in.readObject();
            Student student2 = (Student) in.readObject();

            in.close();
            fileIn.close();

            // 学生を表示する
            System.out.println(student1);
            System.out.println(student2);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

一時的なフィールドを処理する

クラスにシリアライズしたくないフィールドがある場合、それらをtransientと宣言できます。一時的なフィールドは、シリアライズと逆シリアライズの際に無視されます。たとえば、シリアライズしたくないtransientフィールドを Student クラスに追加しましょう:

import java.io.Serializable;

public class Student implements Serializable {
    private String name;
    private int age;
    private transient String password;

    //コンストラクタ、getter/setter メソッド
}

シリアライズと逆シリアライズをカスタマイズする

writeObject()readObject() メソッドを実装することで、シリアライズと逆シリアライズのプロセスをカスタマイズすることもできます。一時的なフィールドを処理したり、シリアライズと逆シリアライズ中に追加の処理を行いたい場合に便利です。

private void writeObject(ObjectOutputStream oos) throws IOException {
    // デフォルトのシリアライズ
    oos.defaultWriteObject();

    // ファイルに書き込む前にパスワードを暗号化する
    String encryptedPassword = encrypt(password);
    oos.writeObject(encryptedPassword);
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    // デフォルトの逆シリアライズ
    ois.defaultReadObject();

    // ファイルから読み取った後にパスワードを復号化する
    String encryptedPassword = (String) ois.readObject();
    password = decrypt(encryptedPassword);
}

バージョニングを処理する

クラスにプロパティを追加または削除することでクラスを更新すると、クラスのバージョンが変更され、古いオブジェクトを逆シリアライズする際にエラーが発生する可能性があります。クラスに固有の識別子を指定するために serialVersionUID フィールドを使用することで、バージョニングに対応することができます。これにより、クラス定義が変更されても、シリアライズと逆シリアライズが正常に機能することが保証されます。

import java.io.Serializable;

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;

    //コンストラクタ、getter/setter メソッド
}

コードを実行する

SerializationDemo クラスをコンパイルし、次のコマンドを使用してターミナルで実行します。

javac SerializationDemo.java
java SerializationDemo

出力には、シリアライズされたデータと逆シリアライズされたオブジェクトが表示されるはずです。

学生クラスを変更する

Student クラスを更新して、フィールドを追加または削除します。

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    private String email; // フィールドを追加

    //コンストラクタ、getter/setter メソッド
}

SerialVersionUID を使ったバージョニングの処理

これでクラスを変更したので、serialVersionUID を更新することでバージョニングに対応する必要があります。これにより、クラスが変更される前にシリアライズされたオブジェクトを依然として逆シリアライズできるようになります。

public class Student implements Serializable {
    private static final long serialVersionUID = 2L;

    private String name;
    private int age;
    private String email; // フィールドを追加

    //コンストラクタ、getter/setter メソッド
}

まとめ

この実験では、Java において ObjectInputStream と ObjectOutputStream を使ってオブジェクトをシリアライズおよび逆シリアライズする方法を学びました。また、transient フィールドの扱い方、シリアライズと逆シリアライズのプロセスをカスタマイズする方法、および serialVersionUID を使ったバージョニングの対応方法についても学びました。シリアライズと逆シリアライズは、Java においてデータを保存および送信するための重要なプロセスであり、オブジェクトの現在の状態を保存し、必要に応じて後で復元するために使用できます。