11

I am trying the simplest possible serialization examples of a class:

@serializable class Person(age:Int) {}
val fred = new Person(45)
import java.io._
val out = new ObjectOutputStream(new FileOutputStream("test.obj"))
out.writeObject(fred)
out.close()

This throws exception "java.io.NotSerializableException: Main$$anon$1$Person" on me. Why? Is there a simple serialization example? I also tried

@serializable class Person(nm:String) {
    private val name:String=nm
}
val fred = new Person("Fred")
...

and tried to remove @serializable and some other permutations. The file "test.obj" is created, over 2Kb in size and has plausible contents.

EDIT:

Reading the "test.obj" back in (from the 2nd answer below) causes

Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51). Type in expressions to have them evaluated. Type :help for more information.

scala> import java.io._ import java.io._

scala> val fis = new FileInputStream( "test.obj" ) fis: java.io.FileInputStream = java.io.FileInputStream@716ad1b3

scala> val oin = new ObjectInputStream( fis ) oin: java.io.ObjectInputStream = java.io.ObjectInputStream@1f927f0a

scala> val p= oin.readObject java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: Main$$anon$1 at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1354) at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1990) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1915) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1798) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370) at .(:12) at .() at .(:7) at .() at $print() at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:734) at scala.tools.nsc.interpreter.IMain$Request.loadAndRun(IMain.scala:983) at scala.tools.nsc.interpreter.IMain.loadAndRunReq$1(IMain.scala:573) at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:604) at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:568) at scala.tools.nsc.interpreter.ILoop.reallyInterpret$1(ILoop.scala:756) at scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala:801) at scala.tools.nsc.interpreter.ILoop.command(ILoop.scala:713) at scala.tools.nsc.interpreter.ILoop.processLine$1(ILoop.scala:577) at scala.tools.nsc.interpreter.ILoop.innerLoop$1(ILoop.scala:584) at scala.tools.nsc.interpreter.ILoop.loop(ILoop.scala:587) at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply$mcZ$sp(ILoop.scala:878) at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:833) at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:833) at scala.tools.nsc.util.ScalaClassLoader$.savingContextLoader(ScalaClassLoader.scala:135) at scala.tools.nsc.interpreter.ILoop.process(ILoop.scala:833) at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:83) at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96) at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:105) at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala) Caused by: java.io.NotSerializableException: Main$$anon$1 at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1183) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1547) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1508) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1431) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1177) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:347) at Main$$anon$1.(a.scala:11) at Main$.main(a.scala:1) at Main.main(a.scala) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at scala.tools.nsc.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:71) at scala.tools.nsc.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:31) at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:139) at scala.tools.nsc.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:71) at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:139) at scala.tools.nsc.CommonRunner$class.run(ObjectRunner.scala:28) at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:45) at scala.tools.nsc.CommonRunner$class.runAndCatch(ObjectRunner.scala:35) at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:45) at scala.tools.nsc.ScriptRunner.scala$tools$nsc$ScriptRunner$$runCompiled(ScriptRunner.scala:171) at scala.tools.nsc.ScriptRunner$$anonfun$runScript$1.apply(ScriptRunner.scala:188) at scala.tools.nsc.ScriptRunner$$anonfun$runScript$1.apply(ScriptRunner.scala:188) at scala.tools.nsc.ScriptRunner$$anonfun$withCompiledScript$1.apply$mcZ$sp(ScriptRunner.scala:157) at scala.tools.nsc.ScriptRunner$$anonfun$withCompiledScript$1.apply(ScriptRunner.scala:131) at scala.tools.nsc.ScriptRunner$$anonfun$withCompiledScript$1.apply(ScriptRunner.scala:131) at scala.tools.nsc.util.package$.trackingThreads(package.scala:51) at scala.tools.nsc.util.package$.waitingForThreads(package.scala:35) at scala.tools.nsc.ScriptRunner.withCompiledScript(ScriptRunner.scala:130) at scala.tools.nsc.ScriptRunner.runScript(ScriptRunner.scala:188) at scala.tools.nsc.ScriptRunner.runScriptAndCatch(ScriptRunner.scala:201) at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:76) ... 3 more

MKaama
  • 1,732
  • 2
  • 19
  • 28
  • 1
    I can't reproduce your example. For me the serialization works on Scala 2.10.3 . Which version of Scala are you using In general the Java Byte Code Serialization (mostly the deserialization) for Scala works. But there are some corner cases that create problems. Especially that there is no compatibility between major Scala releases bears a lot of problems. Have a look at the new scala-pickling library in Scala 2.11 : http://parleys.com/play/51c3799fe4b0d38b54f4625a/chapter0/about – Andreas Neumann Apr 20 '14 at 13:43
  • 1
    I tried this example on 2.9.3, 2.10.2, 2.10.3, and 2.11.0-RC4 (used `extends Serializable` instead of `@serializable`) and they all worked correctly using java 7 – Noah Apr 20 '14 at 14:11
  • Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51). – MKaama Apr 20 '14 at 14:13
  • This exception appears on two different computers, Windows8 and Windows7, with the Scala and Java versions as above. – MKaama Apr 20 '14 at 14:18

3 Answers3

30

Note that @serializable scaladoc tells that it is deprecated since 2.9.0:

Deprecated (Since version 2.9.0) instead of @serializable class C, use class C extends Serializable

So you just have to use Serializable trait:

class Person(val age: Int) extends Serializable

This works for me (type :paste in REPL and paste these lines):

import java.io.{ObjectOutputStream, ObjectInputStream}
import java.io.{FileOutputStream, FileInputStream}

class Person(val age: Int) extends Serializable {
  override def toString = s"Person($age)"
}

val os = new ObjectOutputStream(new FileOutputStream("/tmp/example.dat"))
os.writeObject(new Person(22))
os.close()

val is = new ObjectInputStream(new FileInputStream("/tmp/example.dat"))
val obj = is.readObject()
is.close()
obj

This is the output:

// Exiting paste mode, now interpreting.

import java.io.{ObjectOutputStream, ObjectInputStream}
import java.io.{FileOutputStream, FileInputStream}
defined class Person
os: java.io.ObjectOutputStream = java.io.ObjectOutputStream@5126abfd
is: java.io.ObjectInputStream = java.io.ObjectInputStream@41e598aa
obj: Object = Person(22)
res8: Object = Person(22)

So, you can see, the [de]serialization attempt was successful.

Edit (on why you're getting NotSerializableException when you run Scala script from file)

I've put my code into a file and tried to run it via scala test.scala and got exactly the same error as you. Here is my speculation on why it happens.

According to the stack trace a weird class Main$$anon$1 is not serializable. Logical question is: why it is there in the first place? We're trying to serialize Person after all, not something weird.

Scala script is special in that it is implicitly wrapped into an object called Main. This is indicated by the stack trace:

at Main$$anon$1.<init>(test.scala:9)
at Main$.main(test.scala:1)
at Main.main(test.scala)

The names here suggest that Main.main static method is the program entry point, and this method delegates to Main$.main instance method (object's class is named after the object but with $ appended). This instance method in turn tries to create an instance of a class Main$$anon$1. As far as I remember, anonymous classes are named that way.

Now, let's try to find exact Person class name (run this as Scala script):

class Person(val age: Int) extends Serializable {
  override def toString = s"Person($age)"
}

println(new Person(22).getClass)

This prints something I was expecting:

class Main$$anon$1$Person

This means that Person is not a top-level class; instead it is a nested class defined in the anonymous class generated by the compiler! So in fact we have something like this:

object Main {
  def main(args: Array[String]) {
    new {  // this is where Main$$anon$1 is generated, and the following code is its constructor body
      class Person(val age: Int) extends Serializable { ... }
      // all other definitions
    }
  }
}

But in Scala all nested classes are something called "nested non-static" (or "inner") classes in Java. This means that these classes always contain an implicit reference to an instance of their enclosing class. In this case, enclosing class is Main$$anon$1. Because of that when Java serializer tries to serialize Person, it transitively encounters an instance of Main$$anon$1 and tries to serialize it, but since it is not Serializable, the process fails. BTW, serializing non-static inner classes is a notorious thing in Java world, it is known to cause problems like this one.

As for why it works in REPL, it seems that in REPL declared classes somehow do not end up as inner ones, so they don't have any implicit fields. Hence serialization works normally for them.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • Amazing, this works in my Scala, too! I am still trying to figure out, what is the principal difference. override def toString? – MKaama Apr 20 '14 at 15:21
  • Something must be wrong with my REPL, I get different results when running `scala test.scala` or pasting the program into REPL. BTW, the original over-2KB-objects were wrong, the correct output file is 48 bytes. – MKaama Apr 20 '14 at 15:30
  • 1
    @MadaaKaama actual reason is likely that REPL wraps all your class definitions is some kind of jail with the gibberish names (thus allowing you redefine your classes, but making a work harder for serialization) – om-nom-nom Apr 20 '14 at 15:45
  • @MadaaKaama, no, the principal difference is `extends Serializable` instead of `@serializable`. – Vladimir Matveev Apr 20 '14 at 16:11
  • @VladimirMatveev No, `extends Serializable` also causes "java.io.NotSerializableException: Main$$anon$1" when executed from file and `@serializable` works correctly when pasted into REPL or compiled. Actually, I still cannot understand why invocation with `scala test.scala` fails. – MKaama Apr 20 '14 at 17:24
  • @MadaaKaama, see my updated answer. It seems that I was able to find the reason of your error. – Vladimir Matveev Apr 20 '14 at 18:44
  • @VladimirMatveev Great research! Also, I upgraded my JDK to **Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_05)** and now serialization works in all cases, supposedly with the same REPL and the same mangling, and produces a small (37 bytes) neat object file. – MKaama Apr 20 '14 at 21:57
4

You could use the Serializable Trait:

Trivial Serialization example using Java Serialization with the Serializable Trait:

case class Person(age: Int) extends Serializable

Usage:

Serialization, Write Object

val fos = new FileOutputStream( "person.serializedObject" )
val o = new ObjectOutputStream( fos )
o writeObject Person(31)

Deserialization, Read Object

val fis = new FileInputStream( "person.serializedObject" )
val oin = new ObjectInputStream( fis )
val p= oin.readObject

Which creates following output

fis: java.io.FileInputStream = java.io.FileInputStream@43a2bc95
oin: java.io.ObjectInputStream = java.io.ObjectInputStream@710afce3
p: Object = Person(31)

As you see the deserialization can't infer the Object Type itself, which is a clear drawback.

Serialization with Scala-Pickling

https://github.com/scala/pickling or part of the Standard-Distribution starting with Scala 2.11

In the exmple code the object is not written to a file and JSON is used instead of ByteCode Serialization which avoids certain problems originating in byte code incompatibilities between different Scala version.

import scala.pickling._
import json._

case class Person(age: Int)

val person = Person(31)
val pickledPerson = person.pickle
val unpickledPerson = pickledPerson.unpickle[Person]
Andreas Neumann
  • 10,734
  • 1
  • 32
  • 52
1

class Person(age:Int) {} is equivalent to the Java code:

class Person{
    Person(Int age){}
}

which is probably not what you want. Note that the parameter age is simply discarded and Person has no member fields.

You want either:

  1. @serializable case class Person(age:Int)

  2. @serializable class Person(val age:Int)

You can leave out the empty curly brackets at the end. In fact, it's encouraged.

My other car is a cadr
  • 1,429
  • 1
  • 13
  • 21
  • I am aware that the `age` field disappears. My objective is to ensure that serialization works. I started with a more complex example with arrays, which did not work, and narrowed it down to an empty class, which still doesn't work! Adding val changes nothing, still the same exception! – MKaama Apr 20 '14 at 13:19