Is it possible to use reflection to find the actua

2019-05-06 01:19发布

I want to automatically convert/coerce strings into Scala Enumeration Values, but without knowing beforehand what Enumeration subclass (subobject?) the declaration is from.

So, given:

object MyEnumeration extends Enumeration {
  type MyEnumeration = Value
  val FirstValue, SecondValue = Value
}

and

class MyThing {
  import MyEnumeration._
  var whichOne: MyEnumeration = FirstValue
}

how would I implement the following?

val myThing = new MyThing()
setByReflection(myThing, "whichOne", "SecondValue")

What I'm finding is that when I get the class of MyThing.whichOne (via java.lang.Field), the return is 'scala.Enumeration$Value', which isn't specific enough for me to enumerate the names of all the possible values.

2条回答
放我归山
2楼-- · 2019-05-06 01:27

The specific type is lost at runtime however you can capture it at compile time through implicits. You can provide an implicit in your enum like the following.

object MyEnumeration extends Enumeration {
  type MyEnumeration = Value
  val FirstValue, SecondValue = Value
  implicit def convertMyEnumeration( value: String ) =
    MyEnumeration.withName( value )
}

class MyThing {
  import MyEnumeration._
  var whichOne: MyEnumeration = FirstValue
}

val myThing = new MyThing()
myThing.whichOne = "SecondValue"

You could also do the following as well if the type system is unable to resolve your enum to apply the correct implicit in your usages you can default to using polymorphism if and provide a setter like the following.

class MySuperThing {
  def setWhichOne( value: String }
}

class MyThing extends MySuperThing {
  import MyEnumeration._
  var whichOne: MyEnumeration = FirstValue
  def setWhichOne( value: String ) = MyEnumeration.withName( value )
}

val myThing: MySuperThing = new MyThing()
myThing.setWhichOne( "SecondValue" )
查看更多
相关推荐>>
3楼-- · 2019-05-06 01:29

Check out my EnumReflector, which will tell you all about enum field types, as long as they're declared in the package scope (no inner-class enums, function scope enums).

You need to supply it the scala type of the enum field (which can found through scala's reflection interface) (see below for how to get this from a Class[_])

val enumObjectType:Type = ... object's scala.Enumeration.Value field

val isEnum = EnumReflector.isEnumeration(enumObjectType)

val reflector = EnumReflector(typ)
val eid = reflector.toID(enumObject)
val enum = reflector.fromID(eid)
assertTrue(eid eq enum)

How to get the enumObjectType?

val typ = typeOf[EC]
// OR
val typ = getTypeForClass(classOf[EC]) // or from your package scanner

val enumGetters = classAccessors( typ ).filter(EnumReflector.isEnumeration(_))

// Let's looks at the 1st enum field
val reflector = EnumReflector(enumGetters.head.returnType)
val fieldName = enumGetters.head.name.toString

... now you need to use Java reflection to find the same method for this class. Scala has instance reflection but requires you do reflection on the instance at runtime (which is slow). Better to get a handle to Java reflection invoke method:

 val enumGetter = toJavaClass(typ).getMethods.filter(_.getName==fieldName).head

Starting with your

 val obj = new MyThing()

you can now get the object's enum field and interpret its name or id through the reflector:

 val enumObj = enumGetter.invoke(obj)
 val name = reflector.toName(enumObj)
 val enumObj2 reflector.fromName(name)
 assertTrue( enumObj2 eq enumObj )

Support functions:

import scala.reflect.runtime.universe._
val mirror = runtimeMirror(this.getClass.getClassLoader)

def toJavaClass(tpe:Type) = mirror.runtimeClass(tpe.typeSymbol.asClass)
def getTypeForClass(clazz: Class[_]): Type = mirror.classSymbol(clazz).toType

def classGetters(typ:Type) = typ.members.collect{case m:MethodSymbol=> m}.filter(_.isGetter)
def classAccessors(typ:Type) = typ.members.collect{case m:MethodSymbol=> m}.filter(_.isAccessor)

There's a unit test in the project that demonstrates it.

查看更多
登录 后发表回答