需要从案例类的特征中引用同伴对象的特征

时间:2019-01-26 02:39:18

标签: scala generics reflection companion-object scala-2.12

我需要从用于案例类的特征中访问具有指定特征的伴随类。我几乎可以肯定,Scala反射库可以完成此操作,但我还无法将其组合在一起。

我在下面创建了需要???的测试代码?充满一些反射魔法。该代码按原样编译并运行-由于缺少功能而发出通知。

我在StackOverflow上看到的一些相关答案来自2.10。请兼容Scala 2.12。

import scala.reflect.{ClassTag, classTag}

//for companion object
//accesses Fields of the associated case class to ensure the correctness
//note: abstract class -- not a trait due to issues using ClassTag on a trait
abstract class SupportsField1Companion[T: ClassTag] {
  //gets the names of all Fields on the associated case class
  val fieldNamesOfInstancedClass: Array[String] =
    classTag[T].runtimeClass.getDeclaredFields.map(_.getName)

  //prints the name and fields of the associated case class -- plus extra on success
  def printFieldNames(extra: String = ""): Unit = {
    val name = classTag[T].runtimeClass.getCanonicalName
    val fields = fieldNamesOfInstancedClass.reduceLeft(_ + ", " + _)
    println(s"Fields of $name: $fields" + extra)
  }
}

//for case classes
//IMPORTANT -- please do not parameterize this if possible
trait SupportsField1 {
  //some data for printing
  val field1: String = this.getClass.getCanonicalName + ": field1"

  //should get a reference to the associated companion object as instance of SupportsFieldsCompanion
  def getSupportsFieldsCompanion: SupportsField1Companion[this.type] = //this.type may be wrong
    ??? //TODO reflection magic required -- need functionality to retrieve companion object cast as type

  //calls a function on the associated Companion class
  def callPrintFuncOnCompanion(): Unit =
    getSupportsFieldsCompanion.printFieldNames(s" -- from ${this.getClass.getCanonicalName}")
}

//two case classes with the SupportsFieldsCompanion trait to ensure data is accessed correctly
object ExampleA extends SupportsField1Companion[ExampleA] {}
case class ExampleA() extends SupportsField1 {
  val fieldA: String = "ExampleA: fieldA"
}
object ExampleB extends SupportsField1Companion[ExampleB] {}
case class ExampleB() extends SupportsField1 {
  val fieldB: String = "ExampleB: fieldB"
}

object Run extends App {
  //create instanced classes and print some test data
  val exampleA = ExampleA()
  println(exampleA.field1) //prints "ExampleA: field1" due to trait SupportsFields
  println(exampleA.fieldA) //prints "ExampleA: fieldA" due to being of class ExampleA
  val exampleB = ExampleB()
  println(exampleB.field1) //prints "ExampleB: field1" due to trait SupportsFields
  println(exampleB.fieldB) //prints "ExampleB: fieldB" due to being of class ExampleB

  //via the SupportsFieldsCompanion trait on the companion objects,
  //call a function on each companion object to show that each companion is associated with the correct case class
  ExampleA.printFieldNames() //prints "Fields of ExampleA: fieldA, field1"
  ExampleB.printFieldNames() //prints "Fields of ExampleB: fieldB, field1"

  //test access of printFieldNames on companion object from instanced class
  try {
    exampleA.callPrintFuncOnCompanion() //on success, prints "Fields of ExampleA: fieldA, field1 -- from ExampleA"
    exampleB.callPrintFuncOnCompanion() //on success, prints "Fields of ExampleB: fieldB, field1 -- from ExampleB"
  } catch {
    case _: NotImplementedError => println("!!! Calling function on companion(s) failed.")
  }
}

1 个答案:

答案 0 :(得分:2)

您可以通过多种方法来执行此操作,但是以下可能是最简单的方法之一,其中不涉及对Scala的伴随对象类名称处理方式进行假设:

def getSupportsFieldsCompanion: SupportsField1Companion[this.type] =
  scala.reflect.runtime.ReflectionUtils.staticSingletonInstance(
    this.getClass.getClassLoader,
    this.getClass.getCanonicalName
  ).asInstanceOf[SupportsField1Companion[this.type]]

这可以按需工作,但是我可能将其键入为SupportsField1Companion[_],理想情况下,我可能会避免在SupportsField1上使用引用SupportsField1Companion的公共方法,实际上是理想情况下,我可能会完全避免使用这种方法,但是,如果您决心这样做,我认为上述ReflectionUtil解决方案可能是合理的。