3

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:

  • avoid having to declare tpe in every subclass (as ClassWithType2 does)?
  • allows the object to be serialized

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
  }

}
user48956
  • 14,850
  • 19
  • 93
  • 154
  • TypeAware doesn't extends java.io.Serializable. – som-snytt Aug 18 '16 at 22:36
  • Why would it be required to? I'm asking to serialize ClassWithType1, which claims to be Serializable. NB. The serialization fails because it can't find a constructor for ClassWithType1, not because TypeAware or its members can't be serialized (serializing the java.reflect.Type works fine). – user48956 Aug 18 '16 at 22:44
  • Every few years, I try to refresh my memory about Java serialization, but I don't think today is the day. But see my answer; the ctor is not nullary. – som-snytt Aug 18 '16 at 22:52

1 Answers1

3

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

som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • So it seems that making TypeAware extends Serializable fixes this by adding a missing constructor to its subclass. Not very intuitive, but I'll take it. – user48956 Aug 19 '16 at 00:10
  • I guess it's worth reading the docs if you have to use it. There's also a faq entry about the decision to require the marker trait. – som-snytt Aug 19 '16 at 04:26