I have createOld method that I need to override and I cannot change it. I would like to use TypeTag to pattern match provided type in createNew. The goal is to find out how to call createNew from createOld. My current understanding is that compiler doesn't have enough type information about A in createOld method if it doesn't already come with TypeTag[A].
object TypeTagFromClass {
class C1
class C2
// How to get TypeTag[A] needed by createNew?
def createOld[A](c: Class[A]): A = createNew ???
def createNew[A : TypeTag]: A = {
val result = typeOf[A] match {
case a if a =:= typeOf[C1] => new C1()
case a if a =:= typeOf[C2] => new C2()
}
result.asInstanceOf[A]
}
}
A TypeTag is completely compiler-generated, that means that the compiler creates and fills in a TypeTag when one calls a method expecting such a TypeTag . There exist three different forms of tags: scala. reflect. ClassTag.
It means an abstract type member is defined (inside some context, e.g. a trait or class), so that concrete implementations of that context must define that type.
A classOf[T] is a value of type Class[T] . In other words, classOf[T]: Class[T] . For example: scala> val strClass = classOf[String] strClass: Class[String] = class java. lang. String scala> :t strClass Class[String]
A Manifest[T] is an opaque descriptor for type T. Its supported use is to give access to the erasure of the type as a Class instance, as is necessary for the creation of native Arrays if the class is not known at compile time.
It is possible to create a TypeTag from a Class using Scala reflection, though I'm not sure if this implementation of TypeCreator is absolutely correct:
import scala.reflect.runtime.universe._
def createOld[A](c: Class[A]): A = createNew {
val mirror = runtimeMirror(c.getClassLoader) // obtain runtime mirror
val sym = mirror.staticClass(c.getName) // obtain class symbol for `c`
val tpe = sym.selfType // obtain type object for `c`
// create a type tag which contains above type object
TypeTag(mirror, new TypeCreator {
def apply[U <: Universe with Singleton](m: api.Mirror[U]) =
if (m eq mirror) tpe.asInstanceOf[U # Type]
else throw new IllegalArgumentException(s"Type tag defined in $mirror cannot be migrated to other mirrors.")
})
}
However, you don't really need full TypeTag if you don't need to inspect generic parameters and full Scala type information. You can use ClassTags for that:
def createNew[A: ClassTag]: A = {
val result = classTag[A].runtimeClass match {
case a if a.isAssignableFrom(classOf[C1]) => new C1()
case a if a.isAssignableFrom(classOf[C2]) => new C2()
}
result.asInstanceOf[A]
}
Or with some implicit sugar:
implicit class ClassTagOps[T](val classTag: ClassTag[T]) extends AnyVal {
def <<:(other: ClassTag[_]) = classTag.runtimeClass.isAssignableFrom(other.runtimeClass)
}
def createNew[A: ClassTag]: A = {
val result = classTag[A] match {
case a if a <<: classTag[C1] => new C1()
case a if a <<: classTag[C2] => new C2()
}
result.asInstanceOf[A]
}
You can simplify that even further by using plain old Java newInstance() method:
def createNew[A: ClassTag]: A = classTag[A].runtimeClass.newInstance().asInstanceOf[A]
This, of course, would only work if you don't need different constructor parameters for different classes.
Calling this createNew from createOld is much simpler than the one with TypeTags:
def createOld[A](c: Class[A]): A = createNew(ClassTag[A](c))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With