scalagenericsserializationscala-generics

Why scala serializability differs in case classes with same constructor parameter types?


Why am I able to serialize this:

// Serialize: OK
case class ClassWithType2[T:TypeTag](x:T)  {
  val tpe:java.lang.reflect.Type = Util.toJavaClass[T]
}

... but not this

class TypeAware[T:TypeTag]() {
  val tpe:java.lang.reflect.Type = Util.toJavaClass[T]
}

// Serialize: FAIL.
// No valid constructor for ClassWithType1
// in: java.io.ObjectStreamClass.checkDeserialize
case class ClassWithType1[T:TypeTag](x:T) extends TypeAware[T] 

Both seem have the same constructor type prototype:

[T:TypeTag](x:T)

and both extend scala.Serializable and java.io.Serializable

val s1:Serializable = ClassWithType1(x=123)
val s2:Serializable = ClassWithType2(x=123)
val s3:java.io.Serializable = ClassWithType1(x=123)
val s4:java.io.Serializable = ClassWithType2(x=123)

Its there a way to implement TypeAware subclasses that:

Here's the test harness

class TypesTest {

  @Test
  def serializeTypeTest(): Unit = {
    val obj2:Object = ClassWithType2(x=123)
    Util.copyBySerialization(obj2)  // Success!

    val obj1:Object = ClassWithType1(x=123)
    Util.copyBySerialization(obj1) // Fail
  }
}

object Util {
  def toJavaClass[T:TypeTag]: Class[_] = {
    val tpe = typeOf[T]
    runtimeMirror(tpe.getClass.getClassLoader).runtimeClass(tpe.typeSymbol.asClass)
  }

  def copyBySerialization[T](obj: T): T = deserialize(serialize(obj))

  def serialize[T](obj: T): Array[Byte] = {
    val byteOut = new ByteArrayOutputStream()
    val objOut = new ObjectOutputStream(byteOut)
    objOut.writeObject(obj)
    objOut.close()
    byteOut.close()
    byteOut.toByteArray
  }

  def deserialize[T](bytes: Array[Byte]): T = {
    val byteIn = new ByteArrayInputStream(bytes)
    val objIn = new ObjectInputStream(byteIn)
    val obj = objIn.readObject().asInstanceOf[T]
    byteIn.close()
    objIn.close()
    obj
  }

}

Solution

  • Just quoting the Javadoc:

    To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime.

    The ctor for TypeAware includes the implicit parameter.

    Edit: one idea is to make the type tag a member. Or similar. It doesn't save as much syntax.

    abstract class TypeAware {
      protected def tt: TypeTag[_]
      def tpe:java.lang.reflect.Type = Util.toJavaClass(tt)
    }
    
    case class ClassWithType1[T](x:T)(implicit val tt: TypeTag[T]) extends TypeAware
    

    Edit, more linx:

    tech page

    faq

    your question