Избегайте захвата `this` при сериализации функций в Scala - PullRequest
0 голосов
/ 07 февраля 2020

При сериализации функций в 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.")
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...