Any way to access the type of a Scala Option declaration at runtime using reflection?
Asked Answered
K

4

13

So, I have a Scala class that looks like this:

class TestClass {
  var value: Option[Int] = None
}

and I'm tackling a problem where I have a String value and I want to coerce it into that Option[Int] at runtime using reflection. So, in another piece of code (that knows nothing about TestClass) I have some code like this:

def setField[A <: Object](target: A, fieldName: String, value: String) {
  val field = target.getClass.getDeclaredField(fieldName)
  val coercedValue = ???; // How do I figure out that this needs to be Option[Int] ? 
  field.set(target, coercedValue)   
}

To do this, I need to know that the field is an Option and that the type parameter of the Option is Int.

What are my options for figuring out that the type of 'value' is Option[Int] at runtime (i.e. using reflection)?

I have seen similar problems solved by annotating the field, e.g. @OptionType(Int.class). I'd prefer a solution that didn't require annotations on the reflection target if possible.

Kiblah answered 18/4, 2010 at 21:50 Comment(2)
I don't see the need for reflection. Any value whose static type is Option[Int] is either None or Some[Int].Indurate
Hi Randall. I need to use reflection because the class that is manipulating the fields, like TestClass.value, doesn't have compile-time access to the classes it is manipulating. I've added an example to my question showing how the target objects are being manipulated and highlighting the point at which I need the answer to this question.Kiblah
U
5

It's quite streightforward using Java 1.5 reflection API:

 def isIntOption(clasz: Class[_], propertyName: String) = {
   var result = 
     for {
       method <- cls.getMethods
       if method.getName==propertyName+"_$eq"
       param <- method.getGenericParameterTypes.toList.asInstanceOf[List[ParameterizedType]]
     } yield
       param.getActualTypeArguments.toList == List(classOf[Integer]) 
       && param.getRawType == classOf[Option[_]]
   if (result.length != 1)
     throw new Exception();
   else
     result(0)
 }
Unidirectional answered 19/4, 2010 at 20:6 Comment(3)
Awesome. Thanks. For the record, I'm working on Fields, not Methods. The code I'm using ended up looking like this: if (field.getType == classOf[Option[_]]) val optionFieldType = field.getGenericType.asInstanceOf[ParameterizedType].getActualTypeArguments()(0)Kiblah
Scala automatically wraps fields with methods: foo for getter and foo_$eq for setter.So it's better to use these wrapper methods then fields - in case when they will be overriden in subclass. Otherwise you will break the expected behaviour of the subclass.Unidirectional
I am finding that the "GenericParameterType" of an Option[Int] is "Object" for my Scala case class (scala 2.10). Is there any way to recover the Int type?Chico
T
3

At the byte code level, Java hasn't got Generics. Generics are implemented with polymorphism, so once your source code (in this case Scala) is compiled, generic types disappear (this is called type erasure ). This makes no possible to gather Generic runtime type information via reflection.

A possible --though little dirty-- workaround is to obtain the runtime type of a property you know it has the same type as the Generic parameter. For Option instances we can use get member

object Test {

  def main(args : Array[String]) : Unit = {
    var option: Option[_]= None
    println(getType(option).getName)
    option = Some(1)
    println(getType(option).getName)

  }

  def getType[_](option:Option[_]):Class[_]= {
         if (option.isEmpty) classOf[Nothing] else (option.get.asInstanceOf[AnyRef]).getClass
  }
}
Thoroughgoing answered 19/4, 2010 at 10:51 Comment(2)
Thanks for your answer - lots of useful information. Unfortunately, I expect the value of these vars to typically be None at the time that I want to find out the type, which means the value won't help me to get the type.Kiblah
Not at all- Anyway, if the runtime value of the vars is None most of the times you can coerce it to scala.Nothing, or scala.Null since they are subclasses of every descendant of scala.AnyVal and scala.AnyRef respectivelyThoroughgoing
I
1
class TestClass {
  var value: Option[Int] = None
// ...

  def doSomething {
    value match {
      case Some(i) => // i is an Int here
      case None =>
      // No other possibilities
    }
  }
}
Indurate answered 19/4, 2010 at 0:17 Comment(1)
No, the code that is manipulating TestClass is doing so via reflection - it has no knowledge of TestClass at compile time, and needs to discover the type, including its type parameter, at runtime.Kiblah
P
1

The problem is that JVM implements generics through type erasure. So it's impossible to discover through reflection that the type of value is Option[Int] because at the run-time it actually isn't: it's just Option!

In 2.8 you should be able to use Manifests like this:

var value: Option[Int] = None
val valueManifest = implicitly[scala.reflect.Manifest[Option[Int]]]
valueManifest.typeArguments // returns Some(List(Int))
Pelasgian answered 19/4, 2010 at 10:29 Comment(3)
Reflection knows (at run-time) that parameter/result of the function is exactly Option<Integer>Unidirectional
But it doesn't know that about types of values and fields, and that's what the question was about.Pelasgian
Alexey is right: Like you say, the type parameters are erased from the signature when compiled but it appears they still exist in the byte code in some form that the Reflection API is able to extract and use. Have a look at the answer.Kiblah

© 2022 - 2024 — McMap. All rights reserved.