JavaのsubListの罠

本当は食べ物の写真とか載せたいんですけどね。。
ついこの前やらかしちゃった事を備忘録としてメモメモ。

Javaでは基本的にSerializableを実装していないと(Externalizableでもいいけど)、Objectをシリアライズできません。例えばMemcachedにそのままObjectをまるっと保存する場合は、ObjectOutputStreamを介して保存する事が多いと思います。(MsgPackとかJSONICとかを使う方が良いかもしれませんが)。

で、この前やらかしてしまったのは、JavaのListインターフェースにあるsubListで返ってくるリストはSerializableインターフェースを実装していないListなので、キャッシュに保存できないでエラーが出まくりでした。と書くと語弊があるので正確に書くと、ArrayListとLinkedListはAbstractListを継承しており、subListの実装はAbstractListが持っています。AbstractList#subListの実装は、RandomAccessを実装しているクラスの場合はRandomAccessSubListを、実装していない場合はSubListのObjectを返します。RandomAccessSubListもSubListもSerializableは実装されていません。

public List<E> subList(int fromIndex, int toIndex) {
    return (this instanceof RandomAccess ?
            new RandomAccessSubList<E>(this, fromIndex, toIndex) :
            new SubList<E>(this, fromIndex, toIndex));
}

ちなみに、subListは要素をコピーして作っているわけでは無く、単純に元リストのラッパークラスのように振る舞っているだけなので、そこも注意なのです。subListで返ってきたリストに対して操作するとどうなるかのサンプルを書きました。

import java.util.ArrayList;
import java.util.List;

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

		// サンプルデータを作る
		ArrayList<Integer> srcList = new ArrayList<Integer>();
		for (int i = 0; i < 10; i++) {
			srcList.add(i);
		}
		System.out.println("src=" + srcList);
		// サブリストを作ってみる
		List subList = srcList.subList(5, 8);
		System.out.println("sub=" + subList);
		// サブリストを操作する
		subList.add(100);

		System.out.println("=====after=====");
		System.out.println("sub=" + subList);
		System.out.println("src=" + srcList);

		System.out.println(subList instanceof Serializable);
	}
}

実行結果

src=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sub=[5, 6, 7]
=====after=====
sub=[5, 6, 7, 100]
src=[0, 1, 2, 3, 4, 5, 6, 7, 100, 8, 9]
false

 

新しいサイトもよろしくお願いします!