При сериализации функций в Scala мы должны позаботиться о том, чтобы не захватывать поля, которые не сериализуются.
class Foo(s: Set[Int] = Set(1,2,3)) {
def replicate(): () => Foo = () => new Foo(s)
}
val foo = new Foo()
val fn = foo.replicate()
serialize(fn) // java.io.NotSerializableException: Foo
В приведенном выше фрагменте кода сериализация msg
завершится ошибкой, поскольку функция захватывает this
и Foo
не сериализуемы.
Во избежание захвата this
SIP-21 предлагает споры для Scala. К сожалению, споры существуют только для Scala 2.11.x (см. примечание ). К счастью, я обнаружил в этот шаг (это также описано в SIP-21), что можно избежать захвата ссылки this
, создавая локальные значения вручную, как показано в фрагменте кода ниже.
class Foo(s: Set[Int] = Set(1,2,3)) {
def replicate(): () => Foo = {
() => {
{
val localS = this.s
new Foo(localS)
}
}
}
}
К сожалению, в Scala 2.13.1 это не работает и по-прежнему захватывает this
(попробуйте полный фрагмент кода ниже). Следовательно, мы не можем сериализовать функцию, возвращаемую методом replicate
.
Теперь я нашел следующий обходной путь, но он довольно уродливый.
class Foo(s: Set[Int] = Set(1,2,3)) {
def indirection() = replicate(s)
def replicate(set: Set[Int]): () => Foo = () => new Foo(set)
}
val foo = new Foo()
val msg = foo.indirection()
toStr(msg) // Works :)
Добавив дополнительную косвенную ссылку мы избегаем захвата this
и можем сериализовать функцию без необходимости сериализации Foo
. Однако я не хочу вводить эти дополнительные косвенные указания во все объекты, которые я хочу сериализовать таким образом. Как я могу избежать этого (в Scala 2.13.1)?
Полный фрагмент кода:
// Start writing your ScalaFiddle code here
import java.io._
import java.util.Base64
def serialize(o: Any) = {
val baos = new ByteArrayOutputStream();
val oos = new ObjectOutputStream( baos );
oos.writeObject( o );
oos.close();
Base64.getEncoder().encodeToString(baos.toByteArray())
}
def deserialize(s: String) = {
val data = Base64.getDecoder().decode( s );
val ois = new ObjectInputStream(new ByteArrayInputStream( data ) );
val o = ois.readObject();
ois.close();
o
}
//////////// TRY 1
try {
class Foo(val s: Set[Int] = Set(1,2,3)) {
def replicate(): () => Foo = () => new Foo(s)
}
val foo = new Foo()
val msg = foo.replicate()
val str = serialize(msg)
val reconstructedFoo = deserialize(str).asInstanceOf[() => Foo]()
println(s"Try 1: success, reconstructedFoo.s = ${reconstructedFoo.s}")
} catch {
case e: NotSerializableException => println("TRY 1: Not serializable.")
}
//////////// TRY 2
try {
class Foo(val s: Set[Int] = Set(1,2,3)) {
def replicate(): () => Foo = {
() => {
{
val localS = this.s
new Foo(localS)
}
}
}
}
val foo = new Foo()
val msg = foo.replicate()
val str = serialize(msg)
val reconstructedFoo = deserialize(str).asInstanceOf[() => Foo]()
println(s"Try 2: success, reconstructedFoo.s = ${reconstructedFoo.s}")
} catch {
case e: NotSerializableException => println("TRY 2: Not serializable.")
}
//////////// Solution (ugly)
try {
class Foo(val s: Set[Int] = Set(1,2,3)) {
def indirection() = replicate(s)
def replicate(set: Set[Int]): () => Foo = () => new Foo(set)
}
val foo = new Foo()
val msg = foo.indirection()
val str = serialize(msg)
val reconstructedFoo = deserialize(str).asInstanceOf[() => Foo]()
println(s"Try 3: success, reconstructedFoo.s = ${reconstructedFoo.s}")
} catch {
case e: NotSerializableException => println("TRY 3: Not serializable.")
}