RM-BLOG

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

【java】Serializableについて

javaにはオブジェクトの内容をそのままファイルにして出力したり、そのファイルを読み込んでオブジェクトとして復帰させる方法が存在する。
Serializableというインターフェースを実装することでそれが可能になる。
ある目的のために少し勉強したことがあるので、簡単な実現方法と共にここにメモ書きを残す。


 

 
■基本知識
○クラス名の後に「implements Serializable」を書く。
○Serializableはjava.ioに所属しているので冒頭に「import java.io.Serializable」の宣言が必要。

import java.io.Serializable;

public class SerializableTestObject implements Serializable{
…

 



■出力方法
○ObjectOutputStreamを使う。
○出力されるファイルはバイナリファイルになる。
 が、無理やりテキストエディタで開いて読むと、なんとなくどんなオブジェクトかくらいは見えたりする。

private static final File OBJECT_FILE = new File("test.obj");

SerializableTestObject obj = new SerializableTestObject();

ObjectOutputStream oos =  new ObjectOutputStream(new FileOutputStream(OBJECT_FILE));
oos.writeObject(obj);

 



■読込方法
○ObjectInputStreamを使う。
○読み込んだ直後はObject型で返ってくるので、適切なオブジェクトへのキャストが必要。

private static final File OBJECT_FILE = new File("test.obj");


ObjectInputStream ois = new ObjectInputStream(new FileInputStream(OBJECT_FILE));
SerializableTestObject obj = (SerializableTestObject)ois.readObject();

 



■Serializable未実装オブジェクトの扱いについて
Serializableを実装したクラスの中にSerializable未実装のフィールドを置いたりすると、ObjectOutputStream#writeObjectはコケる(java.io.NotSerializableExceptionが発生する)。
intとかStringとかList程度であれば問題ない。
javaのAPIを調べるとSerializableにはかなり多くのサブインターフェースが存在しており、
また直接Serializabaleを実装している標準のクラスも少なくはないので、基本的には困らないと思う。

当然未実装のオブジェクトというのも世の中には存在している。
例えばjava.io.BufferedReaderなんかはSerializableを実装しておらず、上でいった「コケる」の対象になる。
コーディングの例として、Serializableを実装したクラス「SerializableTestObject」のフィールドとしてBufferedReaderを用意しておき、コンストラクタでインスタンス生成するケースを考える。

public class SerializableTestObject implements Serializable {
	private static final long serialVersionUID = 1l;
	
	private String name;
	private int number;
	
	private List list;
	
	private BufferedReader br = null;

	public SerializableTestObject(String name_arg,int number_arg,List list_arg) {
		this.name = name_arg;
		this.number = number_arg;
		this.list = list_arg;
		
		try {
			this.br = new BufferedReader(new FileReader("memo.html"));
		} catch(Throwable e) {
			e.printStackTrace();
		}
		
	}
}

↑のようなコーディングをした場合、この「SerializableTestObject」をObjectOutputStream#writeObjectで出力しようとすると落ちる。

ただこのようなオブジェクトを含む場合でも、宣言したまま初期化しないか、明示的にnullにすると出力できる。

	private BufferedReader br;
	
	public SerializableTestObject(String name_arg,int number_arg,List list_arg) {
		this.name = name_arg;
		this.number = number_arg;
		this.list = list_arg;
		
		//try {
		//	this.br = new BufferedReader(new FileReader("memo.html"));
		//} catch(Throwable e) {
		//	e.printStackTrace();
		//}
		
	}

↑こんな感じ。
「private BufferedReader br;」で宣言したままコンストラクタを含み何もしないと一応「BufferedReader」を出力することはできる。
明示的に「private BufferedReader br = null;」と宣言しても同様。
ただここまでしてSerializable実装のオブジェクトに乗せて出力する意味があるとは思えない……

これの回避方法として、比較的すぐ考え付くのが、
「そのクラスを継承(extends)したうえでさらにSerializableも実装した別クラスを個別に自作すればいいんじゃないか」という案だが、
これはできないようになっている。
例えば以下のようなクラス

import java.io.*;

public class SerializableOriginalBufferedReader extends BufferedReader implements Serializable {
	public SerializableOriginalBufferedReader(Reader in) {
		super(in);
	}
	
	public SerializableOriginalBufferedReader(Reader in,int sz) throws IOException {
		super(in,sz);
	}
}

↑を作成した場合、インスタンス生成までは問題なくできるが、ObjectOutputStream#writeObject時に「java.io.InvalidClassException: SerializableOriginalBufferedReader; no valid constructor」と言われてコケる。
そのクラスの親クラスがSerializable未実装だった場合は、そのクラス自体でSeriazalizableを実装しても出力できないということである。