Даже тривиальные примеры сериализации в Scala не работают. Зачем?
Я пробую простейшие примеры сериализации класса:
@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()
Это исключает исключение "java.io.NotSerializableException: Main $$ anon $1 $Person" на мне. Зачем?
Есть ли простой пример сериализации?
Я также пробовал
@serializable class Person(nm:String) {
private val name:String=nm
}
val fred = new Person("Fred")
...
и попытался удалить @serializable
и некоторые другие перестановки. Файл "test.obj" создается размером более 2 КБ и имеет правдоподобное содержимое.
EDIT:
Чтение "test.obj" назад (из второго ответа ниже) вызывает
Добро пожаловать в Scala версию 2.10.3 (виртуальная машина с 64-разрядным сервером Java HotSpot TM) Java 1.7.0_51). Введите выражения, чтобы они были оценены. Тип: помощь для получения дополнительной информации.
scala > import java.io._ import java.io._
scala > val fis = new FileInputStream ( "test.obj" ) fis: java.io.FileInputStream = [email protected]
scala > val oin = new ObjectInputStream (fis) oin: java.io.ObjectInputStream = [email protected]
scala > val p = oin.readObject java.io.WriteAbortedException: запись прервана; java.io.NotSerializableException: Main $$ anon $1 в java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1354) в java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1990) в java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1915) в java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1798) в java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1350) в java.io.ObjectInputStream.readObject(ObjectInputStream.java:370) в 12) в.() в 7) в.() в $print() at sun.reflect.NativeMethodAccessorImpl.invoke0 (собственный метод) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) в java.lang.reflect.Method.invoke(Method.java:606) в scala.tools.nsc.interpreter.IMain $ReadEvalPrint.call(IMain.scala: 734) в scala.tools.nsc.interpreter.IMain $Request.loadAndRun(IMain.scala: 983) в scala.tools.nsc.interpreter.IMain.loadAndRunReq $1 (IMain.scala: 573) в scala.tools.nsc.interpreter.IMain.interpret(IMain.scala: 604) в scala.tools.nsc.interpreter.IMain.interpret(IMain.scala: 568) в scala.tools.nsc.interpreter.ILoop.reallyInterpret $1 (ILoop.scala: 756) в scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala: 801) в scala.tools.nsc.interpreter.ILoop.command(ILoop.scala: 713) в scala.tools.nsc.interpreter.ILoop.processLine $1 (ILoop.scala: 577) в scala.tools.nsc.interpreter.ILoop.innerLoop $1 (ILoop.scala: 584) в scala.tools.nsc.interpreter.ILoop.loop(ILoop.scala: 587) в scala.tools.nsc.interpreter.ILoop $$ anonfun $process $1.apply $mcZ $sp (ILoop.scala: 878) в scala.tools.nsc.interpreter.ILoop $$ anonfun $process $1.apply(ILoop.scala: 833) в scala.tools.nsc.interpreter.ILoop $$ anonfun $process $1.apply(ILoop.scala: 833) в scala.tools.nsc.util.ScalaClassLoader $.savingContextLoader(ScalaClassLoader.scala: 135) в scala.tools.nsc.interpreter.ILoop.process(ILoop.scala: 833) at scala.tools.nsc.MainGenericRunner.runTarget $1 (MainGenericRunner.scala: 83) в scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala: 96) в scala.tools.nsc.MainGenericRunner $.main(MainGenericRunner.scala: 105) в scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala) Причиненный по: java.io.NotSerializableException: Main $$ anon $1 в java.io.ObjectOutputStream.writeObject0 (ObjectOutputStream.java:1183) в java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1547) в java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1508) в java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1431) в java.io.ObjectOutputStream.writeObject0 (ObjectOutputStream.java:1177) в java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:347) на Main $$ anon $1. (a.scala: 11) на Main $.main(a.scala: 1) на Main.main(a.scala) at sun.reflect.NativeMethodAccessorImpl.invoke0 (собственный метод) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) в java.lang.reflect.Method.invoke(Method.java:606) в scala.tools.nsc.util.ScalaClassLoader $$ anonfun $run $1.apply(ScalaClassLoader.scala: 71) в scala.tools.nsc.util.ScalaClassLoader $class.asContext(ScalaClassLoader.scala: 31) в scala.tools.nsc.util.ScalaClassLoader $URLClassLoader.asContext(ScalaClassLoader.scala: 139) в scala.tools.nsc.util.ScalaClassLoader $class.run(ScalaClassLoader.scala: 71) в scala.tools.nsc.util.ScalaClassLoader $URLClassLoader.run(ScalaClassLoader.scala: 139) в scala.tools.nsc.CommonRunner $class.run(ObjectRunner.scala: 28) в scala.tools.nsc.ObjectRunner $.run(ObjectRunner.scala: 45) в scala.tools.nsc.CommonRunner $class.runAndCatch(ObjectRunner.scala: 35) в scala.tools.nsc.ObjectRunner $.runAndCatch(ObjectRunner.scala: 45) в scala.tools.nsc.ScriptRunner.scala $инструменты $NSC $ScriptRunner $$ runCompiled (ScriptRunner.scala: 171) в scala.tools.nsc.ScriptRunner $$ anonfun $runScript $1.apply(ScriptRunner.scala: 188) в scala.tools.nsc.ScriptRunner $$ anonfun $runScript $1.apply(ScriptRunner.scala: 188) в 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 $. ОжиданиеForThreads (package.scala: 35) в scala.tools.nsc.ScriptRunner.withCompiledScript(ScriptRunner.scala: 130) в scala.tools.nsc.ScriptRunner.runScript(ScriptRunner.scala: 188) в scala.tools.nsc.ScriptRunner.runScriptAndCatch(ScriptRunner.scala: 201) at scala.tools.nsc.MainGenericRunner.runTarget $1 (MainGenericRunner.scala: 76) ... 3 больше
Ответы
Ответ 1
Обратите внимание, что @serializable
scaladoc сообщает, что он устарел с 2.9.0:
Устаревший (начиная с версии 2.9.0) вместо @serializable класса C, используйте класс C extends Serializable
Итак, вам просто нужно использовать Serializable
trait:
class Person(val age: Int) extends Serializable
Это работает для меня (введите :paste
в REPL и вставьте эти строки):
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
Это вывод:
// Exiting paste mode, now interpreting.
import java.io.{ObjectOutputStream, ObjectInputStream}
import java.io.{FileOutputStream, FileInputStream}
defined class Person
os: java.io.ObjectOutputStream = [email protected]
is: java.io.ObjectInputStream = [email protected]
obj: Object = Person(22)
res8: Object = Person(22)
Итак, вы можете видеть, что попытка сериализации [de] была успешной.
Изменить (о том, почему вы получаете NotSerializableException
при запуске Scala script из файла)
Я поместил свой код в файл и попытался запустить его через scala test.scala
и получил точно такую же ошибку, как и вы. Вот мои предположения о том, почему это происходит.
Согласно трассировке стека, странный класс Main$$anon$1
не является сериализуемым. Логический вопрос: почему он в первую очередь? Мы пытаемся сериализовать Person
в конце концов, а не что-то странное.
Scala script отличается тем, что он неявно завернут в объект с именем Main
. Это указывается трассировкой стека:
at Main$$anon$1.<init>(test.scala:9)
at Main$.main(test.scala:1)
at Main.main(test.scala)
Имена здесь предполагают, что статический метод Main.main
- это точка входа в программу, и этот метод делегирует Main$.main
метод экземпляра (object
класс имеет имя после объекта, но с добавлением $
). Этот метод экземпляра в свою очередь пытается создать экземпляр класса Main$$anon$1
. Насколько я помню, анонимные классы называются именно так.
Теперь попробуйте найти точное имя класса Person
(запустите это как Scala script):
class Person(val age: Int) extends Serializable {
override def toString = s"Person($age)"
}
println(new Person(22).getClass)
Отпечатает то, что я ожидал:
class Main$$anon$1$Person
Это означает, что Person
не является классом верхнего уровня; вместо этого это вложенный класс, определенный в анонимном классе, сгенерированном компилятором! Так что у нас есть что-то вроде этого:
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
}
}
}
Но в Scala все вложенные классы называются "вложенными нестатическими" (или "внутренними" ) классами на Java. Это означает, что эти классы всегда содержат неявную ссылку на экземпляр их окружающего класса. В этом случае класс включения Main$$anon$1
. Из-за этого, когда сериализатор Java пытается сериализовать Person
, он транзитно встречает экземпляр Main$$anon$1
и пытается его сериализовать, но поскольку он не является Serializable
, процесс завершается с ошибкой. BTW, сериализация нестатических внутренних классов - это печально известная вещь в мире Java, она, как известно, вызывает проблемы, подобные этой.
Что касается того, почему он работает в REPL, кажется, что в REPL объявленные классы каким-то образом не заканчиваются как внутренние, поэтому у них нет никаких неявных полей. Поэтому для них обычно выполняется сериализация.
Ответ 2
Вы можете использовать Serializable Trait:
Пример тривиальной сериализации с использованием Java Serialization с Serializable Trait:
case class Person(age: Int) extends Serializable
Использование:
Сериализация, запись объекта
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
Что создает следующий вывод
fis: java.io.FileInputStream = [email protected]
oin: java.io.ObjectInputStream = [email protected]
p: Object = Person(31)
Как вы видите, десериализация не может вывести сам тип объекта, что является явным недостатком.
Сериализация с помощью Scala -Pickling
https://github.com/scala/pickling или часть стандартного дистрибутива, начинающегося с Scala 2.11
В коде exmple объект не записывается в файл, а JSON используется вместо последовательности ByteCode Serialization, которая позволяет избежать определенных проблем, возникающих из-за несовместимости байтового кода между различными версиями Scala.
import scala.pickling._
import json._
case class Person(age: Int)
val person = Person(31)
val pickledPerson = person.pickle
val unpickledPerson = pickledPerson.unpickle[Person]
Ответ 3
class Person(age:Int) {}
эквивалентен Java-коду:
class Person{
Person(Int age){}
}
который, вероятно, не тот, который вы хотите. Обратите внимание, что параметр age
просто отбрасывается, а Person
не имеет полей-членов.
Вы хотите либо:
В конце вы можете оставить пустые фигурные скобки. На самом деле это поощряло.