serialVersionUIDとは
serialVersionUIDはSerializableなオブジェクトに定義するバージョン番号のようなものです。データのシリアライズ/デシリアライズする際に、互換性を確認するためにこのserialVersionUIDを使用します。
また、serialVersionUIDを定義しない場合、自動で計算されることになります。
InvalidClassException
これは、Serializableなオブジェクトをデシリアライズする際、serialVersionUIDが異なる場合に発生します。
java.io.InvalidClassException
com.sample.data.SampleSerializableData;
Incompatible class (SUID):
com.sample.data.SampleSerializableData:
static final long serialVersionUID =4092797215335171425L;
but expected com.sample.data.SampleSerializableData:
static final long serialVersionUID =5140160798577733211L;
今回、SerializableなJavaクラスをKotlin化した際、誤ってserialVersionUIDを削除してしまったため、アプリバージョンアップの際にデータのデシリアライズができなくなってしまいました。
回避方法
今回、既にアプリのバージョンアップを何度か挟んでしまっていたため、serialVersionUIDを復活させてしまうと、今後はKotlin版のSerializableオブジェクトがデシリアライズできなくなってしまいます。そのため、以下の記事を参考にデシリアライザをカスタマイズすることで回避しました。
カスタムInputStream
ObjectInputStream
をextendsして readClassDescriptor
をoverrideしています。戻り値の
ObjectStreamClass
はSerializationの情報を持つオブジェクトです。クラス名とserialVersionUIDを含んでいます。
内容はコード内のコメントに記載しました。
class CustomInputStream(inputStream: InputStream) : ObjectInputStream(inputStream) {
@Throws(IOException::class, ClassNotFoundException::class)
override fun readClassDescriptor(): ObjectStreamClass? {
// streamからクラス記述子を読み出します
val resultClassDescriptor = super.readClassDescriptor() ?: return null
// Class名を取得
val localClass = try {
Class.forName(resultClassDescriptor.name)
} catch (e: ClassNotFoundException) {
return resultClassDescriptor
}
// クラス名を、現在のアプリバイナリの中から探します
val localClassDescriptor = ObjectStreamClass.lookup(localClass)
// only if class implements serializable
if (localClassDescriptor != null) {
// 現在のアプリバイナリと、streamの中のそれぞれのserialVersionUIDを比較します
val localSUID = localClassDescriptor.serialVersionUID
val streamSUID = resultClassDescriptor.serialVersionUID
// serialVersionUIDが異なる場合
if (streamSUID != localSUID) {
// streamのserialVersionUIDが正として、localClassDescriptorを使います
return localClassDescriptor
}
}
return resultClassDescriptor
}
}
val objectInputStream = CustomInputStream(byteArrayInputStream)
val data = objectInputStream.readObject() as? SampleSerializableData
かなり強引な技ではあるので、どうにも回避方法がなくなってしまった場合の奥の手として使うのが良いと思います。また、利用箇所も限定的にして、他のSerializableには影響が出ないようにしましょう。