ArrayListはSerializableだが、subListで取得したListはRandomAccessSubListが実体で、Serializeできない。
HttpSession等はSerializableしか受け付けてくれないので、うっかりSubListを渡さないようにしなければならない。
Listのシリアライズの検証
public static void main(String[] args) throws ClassNotFoundException {
// Arrays.asListの場合、serialize / deserializeできるか
List<String> asList = Arrays.asList("a", "b", "c");
try {
storage = serialize(asList);
try (InputStream is = new ByteArrayInputStream(storage);) {
List<?> asListDeserialized = deserialize(is);
System.out.println("asList: serializable = " + asList.equals(asListDeserialized));
}
} catch (IOException e) {
System.out.println(e.getClass().getName());
}
// List.subListの場合、serialize / deserializeできるか
List<String> subList = asList.subList(0, 2);
try {
storage = serialize(subList);
try (InputStream is = new ByteArrayInputStream(storage);) {
List<?> subListDeserialized = deserialize(is);
System.out.println("subList: serializable = " + subList.equals(subListDeserialized));
}
} catch (IOException e) {
System.out.println(e.getClass().getName());
}
// List.subListをArrays.asListでラッピングすると、serialize / deserializeできるか
List<String> subListWrapped = Arrays.asList(subList.toArray(new String[0]));
try {
storage = serialize(subListWrapped);
try (InputStream is = new ByteArrayInputStream(storage);) {
List<?> subListWrappedDeserialized = deserialize(is);
System.out.println("Arrays.asList(subList.toArray()): serializable = " + subListWrapped.equals(subListWrappedDeserialized));
}
} catch (IOException e) {
System.out.println(e.getClass().getName());
}
// List.subListをArrayListでラッピングすると、serialize / deserializeできるか
List<String> subListWrapped2 = new ArrayList<>(subList);
try {
storage = serialize(subListWrapped2);
try (InputStream is = new ByteArrayInputStream(storage);) {
List<?> subListWrappedDeserialized2 = deserialize(is);
System.out.println("new ArrayList(subList): serializable = " + subListWrapped2.equals(subListWrappedDeserialized2));
}
} catch (IOException e) {
System.out.println(e.getClass().getName());
}
// List.subListをCollections.unmodifiableListでラッピングすると、serialize / deserializeできるか
List<String> subListWrapped3 = Collections.unmodifiableList(subList);
try {
storage = serialize(subListWrapped3);
try (InputStream is = new ByteArrayInputStream(storage);) {
List<?> subListWrappedDeserialized3 = deserialize(is);
System.out.println("Collections.unmodifiableList(subList): serializable = " + subListWrapped3.equals(subListWrappedDeserialized3));
}
} catch (IOException e) {
System.out.println(e.getClass().getName());
}
}
/**
* @param list シリアライズしたいList
* @return シリアライズしたデータの格納用byte配列
* @throws IOException
*/
public static byte[] serialize(List<?> list) throws IOException {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
){
out.writeObject(list);
return bos.toByteArray();
}
}
/**
* @param is シリアライズしたデータのストリーム
* @return デシリアライズしたList
* @throws IOException
* @throws ClassNotFoundException
*/
public static List<?> deserialize(InputStream is) throws IOException, ClassNotFoundException {
try (ObjectInputStream in = new ObjectInputStream(is);) {
List<?> deserialized = (List<?>)in.readObject();
return deserialized;
}
}
出力結果
asList: serializable = true
java.io.NotSerializableException
Arrays.asList(subList.toArray()): serializable = true
new ArrayList(subList): serializable = true
java.io.NotSerializableException
SubListをシリアライズ可能にするには、Arrays.asList(subList.toArray())
かnew ArrayList(subList)
でSubListをラッピングしてあげればいい。
注意すべきはCollectionsクラス。内部でlist instanceof RandomAccess
を判断していて、RandomAccessインタフェースをimplementしたクラスを返すため、Serializeできない問題が残る。