【问题标题】:Scala/Java reflective programming: invoke constructor by typecasted argumentsScala/Java 反射式编程:通过类型转换的参数调用构造函数
【发布时间】:2016-05-17 03:23:33
【问题描述】:

在库 json4s 中,我打算为一些格式错误的数据(主要是 XML -> JSON 转换的结果)编写一个弱类型的反序列化器

我希望动态程序获取给定构造函数的类型信息(简单,例如'Int'),将其应用于已解析的字符串(例如“12.51”),自动将字符串转换为类型(在本例中为 12.51应类型转换为 13),然后调用构造函数。

我想出了以下实现:

import org.json4s.JsonAST.{JDecimal, JDouble, JInt, JString}
import org.json4s._
import scala.reflect.ClassTag

object WeakNumDeserializer extends Serializer[Any] {

  def cast[T](cc: Class[T], v: Any): Option[T] = {
    implicit val ctg: ClassTag[T] = ClassTag(cc)

    try {
      Some(v.asInstanceOf[T])
    }
    catch {
      case e: Throwable =>
        None
    }
  }

  override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Any] = Function.unlift{
    tuple: (TypeInfo, JValue) =>

      tuple match {
        case (TypeInfo(cc, _), JInt(v)) =>
          cast(cc, v)
        case (TypeInfo(cc, _), JDouble(v)) =>
          cast(cc, v)
        case (TypeInfo(cc, _), JDecimal(v)) =>
          cast(cc, v)
        case (TypeInfo(cc, _), JString(v)) =>
          cast(cc, v.toDouble)
        case _ =>
          None
      }
  }
}

但是在真正的 Double => Int 情况下执行上述代码总是会产生 IllegalArgumentException。调试显示该行:

v.asInstanceOf[T]

在内存中不会将Double类型转换为Int,类型擦除后它仍然是Double数字,在反射中使用它调用构造函数后会触发错误。

我如何绕过这个并让反射函数解决这个问题? 有没有办法告诉 Java 编译器实际将其转换为 Int 类型?

更新:为了帮助验证您的答案,我发布了我的测试用例:

case class StrStr(
                   a: String,
                   b: String
                 )

case class StrInt(
                   a: String,
                   b: Int
                 )

case class StrDbl(
                   a: String,
                   b: Double
                 )

case class StrIntArray(
                        a: String,
                        b: Array[Int]
                      )

case class StrIntSeq(
                      a: String,
                      b: Seq[Int]
                    )

case class StrIntSet(
                      a: String,
                      b: Set[Int]
                    )

class WeakSerializerSuite extends FunSuite with TestMixin{

  implicit val formats = DefaultFormats ++ Seq(StringToNumberDeserializer, ElementToArrayDeserializer)

  import org.json4s.Extraction._

  test("int to String") {

    val d1 = StrInt("a", 12)
    val json = decompose(d1)

    val d2 = extract[StrStr](json)
    d2.toString.shouldBe("StrStr(a,12)")
  }

  test("string to int") {
    val d1 = StrStr("a", "12")
    val json = decompose(d1)

    val d2 = extract[StrInt](json)
    d2.toString.shouldBe("StrInt(a,12)")
  }

  test("double to int") {
    val d1 = StrDbl("a", 12.51)
    val json = decompose(d1)

    val d2 = extract[StrInt](json)
    d2.toString.shouldBe("StrInt(a,12)")
  }

  test("int to int array") {
    val d1 = StrInt("a", 12)
    val json = decompose(d1)

    val d2 = extract[StrIntArray](json)
    d2.copy(b = null).toString.shouldBe("StrIntArray(a,null)")
  }

  test("int to int seq") {
    val d1 = StrInt("a", 12)
    val json = decompose(d1)

    val d2 = extract[StrIntSeq](json)
    d2.toString.shouldBe("StrIntSeq(a,List(12))")
  }

  test("int to int set") {
    val d1 = StrInt("a", 12)
    val json = decompose(d1)

    val d2 = extract[StrIntSet](json)
    d2.toString.shouldBe("StrIntSet(a,Set(12))")
  }

  test("string to int array") {
    val d1 = StrStr("a", "12")
    val json = decompose(d1)

    val d2 = extract[StrIntArray](json)
    d2.copy(b = null).toString.shouldBe("StrIntArray(a,null)")
  }

  test("string to int seq") {
    val d1 = StrStr("a", "12")
    val json = decompose(d1)

    val d2 = extract[StrIntSeq](json)
    d2.toString.shouldBe("StrIntSeq(a,List(12))")
  }

  test("string to int set") {
    val d1 = StrStr("a", "12")
    val json = decompose(d1)

    val d2 = extract[StrIntSet](json)
    d2.toString.shouldBe("StrIntSet(a,Set(12))")
  }

【问题讨论】:

    标签: java scala reflection casting


    【解决方案1】:

    我找到了第一个解决方案,TL:DR:它完全荒谬且不合逻辑,并且绝对充满了已建立的强类型语言的样板。请发布您认为更好的答案:

    abstract class WeakDeserializer[T: Manifest] extends Serializer[T] {
    
    //  final val tpe = implicitly[Manifest[T]]
    //  final val clazz = tpe.runtimeClass
    
      // cannot serialize
      override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = PartialFunction.empty
    }
    
    object StringToNumberDeserializer extends WeakDeserializer[Any] {
    
      override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Any] = {
    
        case (TypeInfo(cc, _), JString(v)) =>
          cc match {
            case java.lang.Byte.TYPE => v.toByte
            case java.lang.Short.TYPE => v.toShort
            case java.lang.Character.TYPE => v.toInt.toChar
            case java.lang.Integer.TYPE => v.toInt
            case java.lang.Long.TYPE => v.toLong
            case java.lang.Float.TYPE => v.toFloat
            case java.lang.Double.TYPE => v.toDouble
            case java.lang.Boolean.TYPE => v.toBoolean
            //TODO: add boxed type
          }
      }
    }
    
    object ElementToArrayDeserializer extends WeakDeserializer[Any] {
    
      val listClass = classOf[List[_]]
      val seqClass = classOf[Seq[_]]
      val setClass = classOf[Set[_]]
      val arrayListClass = classOf[java.util.ArrayList[_]]
    
      override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Any] = {
    
        case (ti@ TypeInfo(this.listClass, _), jv) =>
          List(extractInner(ti, jv, format))
    
        case (ti@ TypeInfo(this.seqClass, _), jv) =>
          Seq(extractInner(ti, jv, format))
    
        case (ti@ TypeInfo(this.setClass, _), jv) =>
          Set(extractInner(ti, jv, format))
    
        case (ti@ TypeInfo(this.arrayListClass, _), jv) =>
          import scala.collection.JavaConverters._
    
          new java.util.ArrayList[Any](List(extractInner(ti, jv, format)).asJava)
    
        case (ti@ TypeInfo(cc, _), jv) if cc.isArray =>
          val a = Array(extractInner(ti, jv, format))
          mkTypedArray(a, firstTypeArg(ti))
      }
    
      def mkTypedArray(a: Array[_], typeArg: ScalaType) = {
        import java.lang.reflect.Array.{newInstance => newArray}
    
        a.foldLeft((newArray(typeArg.erasure, a.length), 0)) { (tuple, e) => {
          java.lang.reflect.Array.set(tuple._1, tuple._2, e)
          (tuple._1, tuple._2 + 1)
        }}._1
      }
    
      def extractInner(ti: TypeInfo, jv: JValue, format: Formats): Any = {
        val result = extract(jv, firstTypeArg(ti))(format)
        result
      }
    
      def firstTypeArg(ti: TypeInfo): ScalaType = {
        val tpe = ScalaType.apply(ti)
        val firstTypeArg = tpe.typeArgs.head
        firstTypeArg
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-13
      • 2011-06-19
      • 1970-01-01
      • 1970-01-01
      • 2019-11-23
      • 1970-01-01
      相关资源
      最近更新 更多