Scala reflection: Force init of object members

2019-08-10 11:38发布

问题:

Scala fiddle here

import scala.reflect.ClassTag
import scala.language.existentials

class SomeClass[T <: SomeClass[T, R], R] extends EarlyInit[T, R] {}

trait EarlyInit[T <: SomeClass[T, R], R] {
    self: SomeClass[T, R] =>

      val clazz = getClass.getSuperclass
      val potentialFields = clazz.getDeclaredClasses.toList

      potentialFields.foreach {
        field => {
          // This correctly prints out fields of object members.
          // but invokation throws an error every time.
          println(s"${field.getName}")
        }
      }
}

class SomeThing extends SomeClass[SomeThing, Any] {
    val stringTest = "test"
    object name
    object test
}

object SomeThing extends SomeThing {}

val x = SomeThing

How do I access and initialise object members(name and test) with reflection?

回答1:

You can achieve this with scala reflection API:

import scala.reflect.runtime.universe._

class SomeClass[T <: SomeClass[T, R]: TypeTag, R] extends EarlyInit[T] {}

class EarlyInit[T: TypeTag] {
  val mirror = runtimeMirror(this.getClass.getClassLoader)
  val reflection  = mirror.reflect(this)

  typeTag[T].tpe.members.filter(_.isModule).foreach(m => reflection.reflectModule(m.asModule).instance)
}

class SomeThing extends SomeClass[SomeThing, Any] {
  val stringTest = "test"

  object name {
    println("name initialized")
  }
  object test {
    println("test initialized")
  }
}

object SomeThing extends SomeThing {}

val x = SomeThing

You need to make EarlyInit a class in order to be able to get a TypeTag evidence. Then you just search for all modules and access them (they would be initialized in the process)


Update

You can also simplify EarlyInit a bit and get rid of type parameter. You actually can get a Type of this directly from mirror:

trait EarlyInit {
  val mirror = runtimeMirror(this.getClass.getClassLoader)
  val reflection  = mirror.reflect(this)

  mirror
    .classSymbol(this.getClass)
    .toType
    .members
    .filter(_.isModule)
    .foreach(m => reflection.reflectModule(m.asModule).instance)
}