Get TypeTag[A] from Class[A]

后端 未结 2 1493
夕颜
夕颜 2020-12-01 22:04

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 createN

相关标签:
2条回答
  • 2020-12-01 22:55

    So it is not very safe and correct (cause you don't use the power of scala type system), but you can make (using your logic) to do the following:

    def createNew[A](implicit t: TypeTag[A]): A = {
      val result: Any = t.tpe.toString match {
        case "C1" => new C1
        case "C2" => new C2
      }
      result.asInstanceOf[A]
    }
    
    createNew[C1] //> its all ok
    createNew[C2] //> its all ok
    createNew[C3] //> crashes here; lets pretend we got C3 class
    

    To use it with createOld, just pass implicit argument:

    def createOld[A](c: Class[A])(implicit t: TypeTag[A]): A = createNew[A]
    
    createOld[C1] //> its all ok
    createOld[C2] //> its all ok
    createOld[C3] //> crashes here; lets pretend we got C3 class
    

    I think I should not tell you twice that it is not very good. We can improve this code by using shapeless:

    Lets create a poly function, which has a TypeTag as an argument:

     import shapeless._; import scala.reflect.runtime.universe._;
    
     def getTypeTag[T](implicit t: TypeTag[T]) = t //> to get TypeTag of a class
    
     // here is low prority implicit
     trait createPolyNewErr extends Poly1 {
       implicit def newErr[T] = at[T](_ => "Error can not create object of this class")
     } 
    
     object createPolyBew extends createPolyNewError {
       implicit def newC1 = at[TypeTag[C1]](_ => new C1)
       implicit def newC2 = at[TypeTag[C2]](_ => new C2)
     }
    
     createPolyNew(getTypeTag[C1]) //> success
     createPolyNew(getTypeTag[C2]) //> success
     createPolyNew(getTypeTag[C3]) //> String: Error can not create object of this class no crash!
    

    We also can write a function, in order not to use function getTypeTag[T] every time:

     def createPoly[T]
     (implicit t: TypeTag[T],
             cse: poly.Case[createPolyNew.type, TypeTag[T] :: HNil]) = cse(t)
    
     createPoly[C1] //> its all ok
     createPoly[C2] //> its all ok
     createPoly[C3] //> String: Error can not create object of this class no crash!
    
    0 讨论(0)
  • 2020-12-01 23:02

    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))
    
    0 讨论(0)
提交回复
热议问题