ClassTag.runtimeClass.isInstance does not work for

2019-02-20 01:18发布

问题:

Working with scala ClassTags I have seen that classTag.runtimeClass.isInstance doesn't work properly when you use it with AnyVal objects. Here is a snippet where you can test it. Any ideas to make this work for AnyVal objects?

import scala.reflect.ClassTag
import scala.reflect.runtime.{universe => ru}
object Test {
  def extractField[U: ru.TypeTag](json: Map[String, Any], field: String)(implicit classTag: ClassTag[U]): Option[U] = {
    json.get(field) match {
      case Some(value) =>
        if(classTag.runtimeClass.isInstance(value))
          Some(value.asInstanceOf[U])
        else {
          None
        }
      case _ =>
        None
    }
  }
  val map: Map[String,Any] = Map("k1" -> 2.0, "k2" -> "v")
  extractField[Double](map,"k1") // RETURNS NONE
  extractField[String](map,"k2") // RETURNS Some("v")
}

BTW I am working with Scala 2.10

回答1:

Here is much simpler code showing the same issue:

val c = classTag[Double].runtimeClass
println(c) // double
println(c == classOf[Double]) // true
println(c.isInstance(0.0)) // false

isInstance takes an Object. classOf[Double] represents the "class" of JVM primitive double (and classTag[Double].runtimeClass is the same). Since an object can't be a primitive, classOf[Double].isInstance(something) will always be false.

Map[String, Any] doesn't actually contain AnyVals, but only objects; when you write

val map: Map[String,Any] = Map("k1" -> 2.0, "k2" -> "v")

2.0 is automatically boxed to java.lang.Double, so your code correctly tells you there is no Double under this key. But you can write a simple helper function (I thought it was in the standard library somewhere, but don't remember where):

private val boxedClasses = 
  Map[Class[_], Class[_]](classOf[Double] -> classOf[java.lang.Double], ...) // the rest of AnyVal classes
def boxed(c: Class[_]) = boxedClasses.getOrElse(c, c)

and then in case Some(value):

if(boxed(classTag.runtimeClass).isInstance(value))
  Some(value.asInstanceOf[U])

Of course it can't tell the difference between Double and java.lang.Double in your map, because this difference doesn't exist (except at compile time).