Даже тривиальные примеры сериализации в 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 не имеет полей-членов.

Вы хотите либо:

  • @serializable case class Person(age:Int)

  • @serializable class Person(val age:Int)

В конце вы можете оставить пустые фигурные скобки. На самом деле это поощряло.