简体   繁体   中英

Need to Reference Trait on Companion Object From Trait on Case Class

I need to access a companion class with a specified trait -- from a trait intended for case classes. I am almost certain that the Scala reflection library can accomplish this but I haven't quite been able to piece it together.

I created test code below that requires one section of ??? be filled in with some reflection magic. The code compiles and runs as is -- with a notification due to the missing functionality.

Some related answers that I have seen on StackOverflow were from 2.10. Scala 2.12 compatible please.

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.")
  }
}

There are lots of ways you can do this, but the following is probably one of the simplest that doesn't involve making assumptions about how Scala's companion object class name mangling works:

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

This works as desired, but I'd probably type it as SupportsField1Companion[_] , and ideally I'd probably avoid having public methods on SupportsField1 that refer to SupportsField1Companion —actually ideally I'd probably avoid this approach altogether, but if you're committed I think the ReflectionUtil solution above is probably reasonable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM