1

我需要从用于案例类的特征访问具有指定特征的伴随类。我几乎可以肯定 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.")
  }
}
4

1 回答 1

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解决方案以上大概是合理的。

于 2019-01-26T10:57:52.500 回答