検索

キーワード


【Java】 シリアライズの方法を解説!直列化と復元方法をまとめて紹介

  • 公開日:2020-09-28 17:41:34
  • 最終更新日:2021-01-25 18:54:35
【Java】 シリアライズの方法を解説!直列化と復元方法をまとめて紹介

こんにちは、駆け出しプログラマーの若江です!

ここでは初学者として学習を終えた私からアウトプットの意味も込めて、

シリアライズについて紹介させていただきます。

できる限り初学者が理解しやすい内容として紹介させていただくので、参考となれば幸いです!


関連記事リンク:インターフェースとは? / 継承方法を紹介 / importの使い方 / インスタンスとは? / メソッドとは? / フィールドとは? / プリミティブ型の種類 / 例外の書き方

シリアライズとデシリアライズの使い方

シリアライズはオブジェクト(インスタンス)のデータを直列化することを指します。

Java ではデータの直列化によって処理が可能となるクラスが用意されているため、

シリアライズと併せて利用することができる機能を含めて、

ここではその役割や使い方の紹介、直列化を元に戻すデシリアライズについて紹介させていただきます。


シリアライズとは

シリアライズは「直列化」を意味する単語でオブジェクトが持つデータを、

コンピューターが読み書きできるようにバイナリデータへ変換する役割を持ちます。

このバイナリデータは加工ができるように一列に並ぶ形で処理がされるため「直列化」と呼ばれます。


シリアライズの書き方

Serializable の付与

シリアライズを行うには、Serializable インターフェース を実装しなくてはなりません。

Serializable インターフェースは「マーカーインターフェース」という種類のインターフェースです。

マーカーインターフェースは、実装したクラスで何が出来るかという目印の役割を果たします。

目印以外の機能を持っていないため、Serializable インターフェースにはメソッドもフィールドもなく、

クラスに実装するためのメソッドもありません。


まずは Serializable インターフェースの実装をしてみましょう。

◆ Serializable インターフェースの実装例

import java.io.Serializable;
public class SerializeObject implements Serializable{
}


上記で SerializeObject クラスに Serializable の目印が付いたので、

SerializeObject クラスはシリアライズ対象のクラスとなりました。


型の確認

Serializable によってシリアライズ対象となったクラスであっても、

フィールドの型がプリミティブ型もしくは、シリアライズ対象の型以外であった場合、

シリアライズの対象外となり実行時に例外( NotSerializableException )が投げられます。


【シリアライズ対象クラス】

import java.io.Serializable;
public class SerializeObject implements Serializable {
}

【シリアライズ対象外クラス】

public class SerializeNon {
}

【上記2クラスをフィールドの型にしたクラス】

import java.io.Serializable;
public class SerializeTest implements Serializable{

	SerializeObject sample = new SerializeObject(); // シリアライズ対象クラスのため可
	SerializeNon sampleNon = new SerializeNon(); // シリアライズ対象外のため不可
}


シリアライズ対象外の SerializeNon クラスの型をフィールドに持っているため、

実行時に例外( NotSerializableException )が投げられる。


プリミティブ型以外の型をフィールドへ使う際は、

そのクラスがシリアライズ対象クラスかどうかに気を付けましょう。


OutputStream の利用

シリアライズ対象クラスを用意すると次は、実際にシリアライズするためのクラスの用意が必要です。

シリアライズ対象クラスのシリアライズにはObjectOutputStreamwriteObjectメソッドを使います。

ただしシリアライズは書き込み先クラスとセットで利用するため、

下記構文だけでは利用できませんので注意しましょう。

 ※ ObjectOutputStream は writeObject 以外にもシリアライズ用の様々なメソッドを持っています。

ObjectOutputStream をインスタンス化することで、シリアライズ用のメソッドが利用できます。

・シリアライズの基本構文:

 ObjectOutputStream 変数名 = new ObjectOutputStream( new 書き込み先コンストラクタ);

 変数名.writeObject(シリアライズ対象クラスのインスタンス名);


先ほどのシリアライズ対象クラスを writeObject メソッドでシリアライズしてみましょう。

ここでは一例として書き込み先に FileOutputStream を利用します。


◆シリアライズ

【シリアライズ対象クラス】 先述の例からフィールドを追加

import java.io.Serializable;
public class SerializeObject implements Serializable{
	int num = 1;
}

【シリアライズ実行クラス】 上記クラスを bin ファイルへシリアライズ

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeTest {

	public static void main(String[] args) throws Exception {

		// シリアライズ対象クラスをインスタンス化
		SerializeObject seri = new SerializeObject();

		// ObjectOutputStream でシリアライズ
		ObjectOutputStream test = new ObjectOutputStream(new FileOutputStream("D:\\fileTest\\sample.bin"));
		test.writeObject(seri);
		test.close(); // ObjectOutputStream を閉じる
	}
}

※例外処理が必要なため、throws句や try / catch が必要です。


上記流れを解説します。

SerializeObject クラス(シリアライズ対象クラス)の作成

SerializeTestクラス(シリアライズ実行クラス)の作成

SerializeTest クラスに SerializeObject クラスのインスタンスを生成

④ファイル名を指定した FileOutputStream クラスのインスタンスを生成

 ※ここではどのフォルダへ生成するかまで指定しました。「Dドライブの fileTest フォルダに sample.bin を生成」

⑤引数に生成した FileOutputStream インタンスを持つ ObjectOutputStream のインスタンスを生成

ObjectOutputStream のメソッド writeObject を使って SerializeObject インスタンスをシリアライズ

ObjectOutputStream を閉じる


上記まででシリアライズができました。


OutputStream を継承したクラス

先ほどの例で利用した「FileOutputStream」クラスはファイルへの書き込みをするためのクラスです。

ファイルへ書き込んだデータは他者との受け渡しが可能であり、

いつでも復元展開できる状態でファイルを保持しておくことができるため「永続化」が可能という特徴があります。

永続化:半永久的にインスタンスの状態を保ち、必要に応じて復元できる状態を指します。


またFileOutputStream 以外にもByteArrayOutputStreamというメモリ上でのデータ展開ができるものや、

PipedOutputStreamといった通信パイプを作成できるものなどが存在し、

これらは直列化とセットで利用されます。


ちなみに先述例のシリアライズしたファイルは、

そのまま開いてもその内容を私たちが読める形にはなっていません。

後に紹介する「デシリアライズ」で復元することで、元の形に戻すことができます。



シリアライズで使う他機能

transient 修飾子

transient 修飾子の付いた変数はシリアライズ、デシリアライズ対象外となります。

クラスのいち部分だけシリアライズが必要ない場合などに使います。


◆ transient 修飾子の使用例

import java.io.Serializable;
public class SerializeObject implements Serializable{
	int num = 1;
	transient int number = 2;
}


変数 number はシリアライズ対象外となりました。

後のデシリアライズで復元した際に transient の変数が対象外となっているか確認します。


ちなみに static なものはインスタンス化されるものではなく、

クラスで情報が共有されるものなのでシリアライズの必要がありません。

そのため transient 同様シリアライズの対象外となります。


serialVersionUID

serialVersionUID はシリアライズ時と復元時で同じバージョンのクラスかを判断するために使われます。

シリアライズ対象クラスのメソッドや変数に何らかの変更があった際、

シリアライズ時と復元時で整合性が保てなくなる可能性があります。

このような問題を解消するために serialVersionUID で同一のバージョンであるかを比較します。

もしもシリアライズ対象クラスの変数やメソッドなどに変更があれば

serialVersionUID も変更するようにしましょう。


シリアライズする際に serialVersionUID を使ってそのファイルへ番号を振ることができます。

 ※ serialVersionUID を記述しなかった場合は自動で番号が割り振られます。

serialVersionUID は long 型の定数で付与します。

・serialVersionUID の基本構文: private static final long serialVersionUID = 整数 L;


◆ serialVersionUID の付与例

import java.io.Serializable;
public class SerializeObject implements Serializable{

	private static final long serialVersionUID = 1L;

	int num = 1;
}


シリアライズ時に例えば 1L だった定数を復元時に 2L へ変更すると例外が発生します。

こちらもデシリアライズの紹介で例外を確認してみましょう。



シリアライズ対象クラスの継承

Serializable を実装したクラスを継承すると、子クラスもシリアライズ対象クラスとなります。

ObjectOutputStream と FileOutputStream クラスを使って子クラスのシリアライズを確認しましょう。


◆ Serializable 実装クラスの継承例

【親クラス】 Serializable インターフェースの実装

import java.io.Serializable;
public class SerializeParent implements Serializable{

	private static final long SerialVersionUID = 1L;

	int num = 1;
}

【子クラス】 親クラスの Serializable を継承しているためシリアライズ対象クラス

public class SerializeChild extends SerializeParent {
	int number = 10;
}

【実行クラス】 子クラスをエラーなくシリアライズできる

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeTest {
	public static void main(String[] args) throws Exception{

		SerializeChild test = new SerializeChild();

		ObjectOutputStream sample = new ObjectOutputStream(new FileOutputStream("D:\\fileTest\\test.bin"));
		sample.writeObject(test);
	}
}


子クラスに Serializable インターフェースを実装していなくてもシリアライズができました。



デシリアライズ

シリアライズしたファイルを、シリアライズする前の状態に復元する役割を持っています。

デシリアライズするには「ObjectInputStream」クラスと「readObject」メソッドを使います。

・デシリアライズの基本構文:

 ObjectInputStream 変数名① = new ObjectInputStream(new 読み込み用コンストラクタ);

 デシリアライズするクラス型 変数名② = (デシリアライズするクラス名)変数名①.readObject();


ObjectInputStream が持つ readObject メソッドを使って復元します。

今回は読み込み用のクラスに FileInputStream を利用します。


◆ デシリアライズの例

【シリアライズ対象クラス】

import java.io.Serializable;
public class SerializeObject implements Serializable{

	private static final long serialVersionUID =1L;

	int num = 1;
}

【シリアライズ実行クラス】 上記クラスをシリアライズ

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeTest {
	public static void main(String[] args) throws Exception {

		SerializeObject seri = new SerializeObject();

		ObjectOutputStream test = new ObjectOutputStream(new FileOutputStream("D:\\fileTest\\sample.bin"));
		test.writeObject(seri);
		test.close();
	}
}

【デシリアライズ実行クラス】

import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeTest {
	public static void main(String[] args) throws Exception {

		ObjectInputStream test = new ObjectInputStream(new FileInputStream("D:\\fileTest\\sample.bin"));
		SerializeObject seri = (SerializeObject)test.readObject();

		System.out.println(seri.num);
	}
}


writeObject メソッドの時とは違い 、

readObject メソッドからはデシリアライズした型が返ってくるため、

代入する変数に適した型へキャストを行う必要があります。

(SerializeObject seri = (SerializeObject)test.readObject(); の (SerializeObject) 部分が型のキャスト)


シリアライズ同様デシリアライズにも FileInputStream 以外に、

ByteArrayInputStream」や「PiptdInputStream」、「SequenceInputStream」などのクラスがあります。


transient 修飾子の確認

シリアライズで紹介した transient 修飾子を付与した場合に

シリアライズ対象外となっているかをデシリアライズで確認してみましょう。


◆デシリアライズで transient の確認例

【シリアライズ対象クラス】 transient 修飾子を付与した変数 number を作成

import java.io.Serializable;
public class SerializeObject implements Serializable{
	private static final long serialVersionUID =1L;

	int num = 1;
	transient int number = 10;
}

【シリアライズ実行クラス】

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeTest {
	public static void main(String[] args) throws Exception {

		SerializeObject seri = new SerializeObject();

		ObjectOutputStream test = new ObjectOutputStream(new FileOutputStream("D:\\fileTest\\sample.bin"));
		test.writeObject(seri);
		test.close();
	}
}

【デシリアライズ実行クラス】

import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeTest {
	public static void main(String[] args) throws Exception {

		ObjectInputStream test = new ObjectInputStream(new FileInputStream("D:\\fileTest\\sample.bin"));
		SerializeObject seri = (SerializeObject)test.readObject();

		System.out.println(seri.num); // 結果:1 を表示
		System.out.println(seri.number); // 結果:0 を表示
	}
}


変数 number はシリアライズされていないため 0 が出力されました。


serialVersionUID の例外確認

続いて serialVersionUID でシリアライズ後に定数を変更して例外が出ることを確認しましょう。


◆デシリアライズで serialVersionUID の動作確認例

【シリアライズ対象クラス】

import java.io.Serializable;
public class SerializeObject implements Serializable{

	private static final long serialVersionUID =1L;

	int num = 1;
}

【シリアライズ実行クラス】

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeTest {

	public static void main(String[] args) throws Exception {

		SerializeObject seri = new SerializeObject();

		ObjectOutputStream test = new ObjectOutputStream(new FileOutputStream("D:\\fileTest\\sample.bin"));
		test.writeObject(seri);
		test.close();
	}
}

【デシリアライズ実行クラス】

import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeTest {
	public static void main(String[] args) throws Exception {

		ObjectInputStream test = new ObjectInputStream(new FileInputStream("D:\\fileTest\\sample.bin"));
		SerializeObject seri = (SerializeObject)test.readObject();

		System.out.println(seri.num); // 結果:1 を表示
	}
}


上記ではまだデシリアライズができます。

ではこのままシリアライズ対象クラスの serialVersionUID を変更してみます。

【シリアライズ対象クラス】 serialVersionUID を 2L に変更

import java.io.Serializable;
public class SerializeObject implements Serializable{

	private static final long serialVersionUID =2L;

	int num = 1;
}


もう一度先ほどのデシリアライズを実行すると、 serialVersionUID が異なる例外が発生します。

Exception in thread "main" java.io.InvalidClassException: serialize.SerializeObject; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1958)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1827)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2115)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1646)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at serialize.DeserializeTest.main(DeserializeTest.java:13)


これで serialVersionUID の比較ができていることが確認できました。



まとめ

シリアライズとデシリアライズを利用するとデータの直列化が可能となり、

直列化データに対応した機能を使うことができるようになります。

そしてこのような機能の中には、

クラス情報の送受信や指定した時点のクラスの情報を永続的に保持することができる機能などがあります。

使用頻度は比較的少ないかもしれませんが、シリアライズも Java の基礎です。

しっかり押さえておきましょう。


関連記事リンク

インターフェースとは? / 継承方法を紹介 / importの使い方 / インスタンスとは? / メソッドとは? / フィールドとは? / プリミティブ型の種類 / 例外の書き方




【著者】

若江

30代で異業種となるIT業界へ転職した駆け出しのプログラマです。これまで主に Java や Ruby、HTML/CSS を使って学習を目的としたショップサイトや掲示板サイトの作成を行いました。プログラマとしての経験が浅いからこそ、未経験者の目線に近い形で基礎の紹介をしていきたいと思います。

よく読まれている記事
【Java】JSPでタグライブラリを使う(JSTL)

【Java】JSPでタグライブラリを使う(JSTL)

こんにちは。エンジニアの新田です!ここでは、システムエンジニアとして働いている私が、システム開発手法や開発言語について紹介していこうと思います。今回は、JSPの標準タグライブラリ「JSTL」について紹介します。Javaについて勉強している方、Webアプリケーションを構築したいと思っている方の参考になれば幸いです!関連記事リンク: 【Java】JSPの基本的な構文/【Java】JSPのアクションタグ

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!そもそもStringとは何?『 String 』... Java言語において文字列のデータ型を指します。基本デ

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!Stringクラスformatメソッドの文字列整形【java.utilパッケージ】Formatterクラスfo

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者にも✅、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!文字列を扱う3つのクラス【java.langパッケージ】java.langパッケージの文字列を扱うクラスにはS

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!プリミティブ型と参照型プログラム開発では型を持った変数を使ってデータのやり取りをしますが、データ型によって仕様