I'm trying to understand the following issue that occurs when trying to serialize/deserialize a very simple data structure:
case class SimpleClass(i: Int)
object SerializationDebug {
def main(args: Array[String]) {
val c = SimpleClass(0)
val l1 = List(c)
serializationSaveToFile("test", l1)
val l2 = serializationLoadFromFile("test") // .asInstanceOf ...
}
def serializationSaveToFile(fn: String, o: Any) {
val fos = new FileOutputStream(fn)
val oos = new ObjectOutputStream(fos)
oos.writeObject(o)
oos.close()
}
def serializationLoadFromFile(fn: String): Any = {
val fis = new FileInputStream(fn)
val ois = new ObjectInputStream(fis)
return ois.readObject()
}
}
When trying to run this code I get java.lang.ClassNotFoundException: SimpleClass
in the deserialization step. The current results of my investigations are:
SimpleClass
by some built-in type, i.e., I can deserialize List[Int]
or List[(Int, Double)]
without a problem. Mixing built-in types with my SimpleClass
(i.e. having a List[Any]
) again throws an exception.SimpleClass
in other scopes (for instance nested in the object or in the local scope even) but that did not change anything. Also, having a normal (non-case) class extending Serializable
gives the same result.Array[SimpleClass]
instead of List
does work! Trying other containers confirms this strange inconsistency: having SimpleClass
as type parameter within an immutable map works, in case of a mutable map I get the exception.In case it matters: My Scala version is 2.10.0; JDK is 1.7.0.
What is going on here? Is this supposed to fail or is it some kind of bug? My actual problem at hand involves a much more complex data structure (a lot of nesting; mixture of built-in and own classes). Any suggestions to serializing/deserializing this data structure in a minimal-intrusive simple way (i.e. without having to find working combinations of container classes and their type parameters) are also welcome!
This solution works fine for me:
val ois = new ObjectInputStream(new FileInputStream(fileName)) {
override def resolveClass(desc: java.io.ObjectStreamClass): Class[_] = {
try { Class.forName(desc.getName, false, getClass.getClassLoader) }
catch { case ex: ClassNotFoundException => super.resolveClass(desc) }
}
}
of course, when writing object, we use the same way:
val ois = new ObjectOutputStream(new FileOutputStream(path))
ois.writeObject(myobject)
@trenobus @bluenote10 related to your findings:
I just ran into this today and I think it's because the class' name is different (mangled) when you define it in the console. For instance, I serialized a class One
from scala
> import java.io._
import java.io._
> case class One(s: String, b: Boolean)
defined class One
> new ObjectOutputStream(new FileOutputStream("data")) writeObject One("abc", true)
I then tried to deserialize it in java from the same file, having prepared a similar class named One
in the top level (I made SerialVersionUIDs be the same as well, didn't include it here since it didn't matter), but got this error:
Exception in thread "main" java.lang.ClassNotFoundException: $line4.$read$$iw$$iw$One
Suggesting that scala (rightfully) creates a new JVM class each time you define a class, and presumably keeps an internal mapping so that you can refer to it by its defined name (One
).
Similarly, because each such class is different in JVM, if you redefine class One
in that same scala console, and then try to deserialize from the data
file, you'll get an object of the original class (not of the new class that you redefined).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With