1. 概要

このクイックチュートリアルでは、JavaでSerializableオブジェクトを検証する方法を示します。

2. シリアル化と逆シリアル化

シリアル化は、オブジェクトの状態をバイトストリームに変換するプロセスです。 シリアル化されたオブジェクトは、主に Hibernate、RMI、JPA、EJB、およびJMSテクノロジで使用されます。

方向を切り替えて、逆シリアル化は、バイトストリームを使用してメモリ内に実際のJavaオブジェクトを再作成する逆のプロセスです。 このプロセスは、オブジェクトを永続化するためによく使用されます

3. シリアル化の検証

さまざまな方法を使用してシリアル化を検証できます。 いくつか見てみましょう。

3.1. 実装シリアル化を検証します

オブジェクトがシリアル化可能かどうかを判断する最も簡単な方法は、そのオブジェクトがjava.io.Serializableまたはjava.io.Externalizableのインスタンスであるかどうかをチェックすることです。 ただし、このメソッドは、オブジェクトをシリアル化できることを保証するものではありません。

Serializableインターフェイスを実装していないAddressオブジェクトがあるとします。

public class Address {
    private int houseNumber;

    //getters and setters
}

Address オブジェクトをシリアル化しようとすると、NotSerializableExceptionが発生する可能性があります。

@Test(expected = NotSerializableException.class)
public void whenSerializing_ThenThrowsError() throws IOException {
    Address address = new Address();
    address.setHouseNumber(10);
    FileOutputStream fileOutputStream = new FileOutputStream("yofile.txt");
    try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
        objectOutputStream.writeObject(address);
    }
}

ここで、Serializableインターフェイスを実装するPersonオブジェクトがあるとします。

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private int age;
    private String name;

    // getters and setters
}

この場合、シリアル化および逆シリアル化して、オブジェクトを再作成することができます。

Person p = new Person();
p.setAge(20);
p.setName("Joe");
FileOutputStream fileOutputStream = new FileOutputStream("yofile.txt");
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
    objectOutputStream.writeObject(p);
}

FileInputStream fileInputStream = new FileInputStream("yofile.txt");
try ( ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
    Person p2 = (Person) objectInputStream.readObject();
    assertEquals(p2.getAge(), p.getAge());
    assertEquals(p2.getName(), p.getName());;
}

3.2. Apache Commons SerializationUtils

オブジェクトのシリアル化を検証する別の方法は、Apache Commons SerializationUtilsserializeメソッドを利用することです。 このメソッドは、シリアル化できないオブジェクトを受け入れません。

コードをコンパイルするために明示的に型キャストすることにより、シリアル化できない Address オブジェクトをシリアル化しようとするとどうなりますか? runtime で、ClassCastExceptionが発生します。

Address address = new Address();
address.setHouseNumber(10);
SerializationUtils.serialize((Serializable) address);

上記を使用して、シリアル化可能なPersonオブジェクトを検証しましょう。

Person p = new Person();
p.setAge(20);
p.setName("Joe");
byte[] serialize = SerializationUtils.serialize(p);
Person p2 = (Person)SerializationUtils.deserialize(serialize);
assertEquals(p2.getAge(), p.getAge());
assertEquals(p2.getName(), p.getName());

3.3. SpringコアSerializationUtils

次に、 spring-coreSerializationUtilsメソッドを見ていきます。これは、ApacheCommonsのメソッドに似ています。 このメソッドは、シリアル化できないアドレスオブジェクトも受け入れません。

このようなコードは、実行時にClassCastExceptionをスローします。

Address address = new Address();
address.setHouseNumber(10);
org.springframework.util.SerializationUtils.serialize((Serializable) address);

シリアル化可能なPersonオブジェクトを試してみましょう。

Person p = new Person();
p.setAge(20);
p.setName("Joe");
byte[] serialize = org.springframework.util.SerializationUtils.serialize(p);
Person p2 = (Person)org.springframework.util.SerializationUtils.deserialize(serialize);
assertEquals(p2.getAge(), p.getAge());
assertEquals(p2.getName(), p.getName());

3.4. カスタムシリアル化ユーティリティ

3番目のオプションとして、要件に応じてシリアル化または逆シリアル化する独自のカスタムユーティリティを作成します。 これを実証するために、シリアル化と逆シリアル化の2つの別々のメソッドを記述します。

1つ目は、シリアル化プロセスのオブジェクト検証の例です。

public static  byte[] serialize(T obj) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(obj);
    oos.close();
    return baos.toByteArray();
}

また、逆シリアル化プロセスを実行するメソッドを記述します。

public static  T deserialize(byte[] b, Class cl) throws IOException, ClassNotFoundException {
    ByteArrayInputStream bais = new ByteArrayInputStream(b);
    ObjectInputStream ois = new ObjectInputStream(bais);
    Object o = ois.readObject();
    return cl.cast(o);
}

さらに、 Class をパラメーターとして受け取り、オブジェクトがシリアル化可能である場合はtrueを返すユーティリティメソッドを作成できます。 このメソッドは、入力クラスを Serializable に割り当てることができるかどうかを検証する際に、プリミティブとインターフェイスが暗黙的にシリアル化可能であることを前提としています。 また、検証プロセス中にtransientフィールドとstaticフィールドを除外します。

このメソッドを実装しましょう:

public static boolean isSerializable(Class<?> it) {
    boolean serializable = it.isPrimitive() || it.isInterface() || Serializable.class.isAssignableFrom(it);
    if (!serializable) {
        return false;
    }
    Field[] declaredFields = it.getDeclaredFields();
    for (Field field : declaredFields) {
        if (Modifier.isVolatile(field.getModifiers()) || Modifier.isTransient(field.getModifiers()) || 
          Modifier.isStatic(field.getModifiers())) {
            continue;
        }
        Class<?> fieldType = field.getType();
        if (!isSerializable(fieldType)) {
            return false;
        }
    }
    return true;
}

ユーティリティメソッドを検証してみましょう。

assertFalse(MySerializationUtils.isSerializable(Address.class));
assertTrue(MySerializationUtils.isSerializable(Person.class));
assertTrue(MySerializationUtils.isSerializable(Integer.class));

4. 結論

この記事では、オブジェクトがシリアル化可能かどうかを判断するためのいくつかの方法について説明しました。 また、同じことを実現するためのカスタム実装も示しました。

カスタムと同様に、このチュートリアルで使用されるすべてのコードサンプルは、GitHubから入手できます。