【问题标题】:Using Scala reflection to check for a method on an object or to find key type on a Map使用 Scala 反射检查对象上的方法或查找 Map 上的键类型
【发布时间】:2015-11-28 18:41:39
【问题描述】:

我正在将一个类从 Ruby 移植到 Scala 2.11,该类实现了消息中的变量合并。我想将一个对象数组传递给 merge 方法,并让它在每个对象中搜索消息文本中引用的键。

其核心是一个名为lookUp(key: String, obj: AnyRef) 的方法,它接收要搜索的单个键,以及要搜索该键的单个对象。如果对象是一个 Map,并且 Map 的键是 Symbol 或 String,那么它将在 Map 中查找请求的键。否则,它会检查对象是否有与键名相同的方法,如果有,它将调用该方法。

在现有的 Ruby 代码中,很容易做到这一点:

def look_up(key, obj)
  if obj.respond_to?(:has_key?) && obj.has_key?(key)
    obj[key]
  elsif obj.respond_to?(key)
    obj.send(key)
  elsif obj.instance_variable_defined?(ivar = "@#{key}")
    obj.instance_variable_get(ivar)
  end
end

由于这样做很容易,Ruby 代码还会查找同名的实例变量。我的 Scala 版本不一定需要该功能。

我遇到的一个问题是,我发现的示例要求我提前知道对象的类,以便我可以调用ru.typeOf[MyClass].declaration(ru.TermName("key"))(其中ruscala.reflect.runtime.universe)。

另一个问题是这种消息合并可能每分钟发生数百次,并且反射似乎是一个缓慢而复杂的过程。如果这一切都按计划进行,我可能会按对象类型缓存反射结果。

更新:我正在考虑这样的事情,但这是否有点矫枉过正?或者是否有必要正确捕获 Map 中的类型?此外,这不会编译。 MapSymbolString 不是适合其上下文的类型。

def lookUp[T](obj: T, key: String)(implicit tag: ru.TypeTag[T]): Option[String] = tag.tpe match {
  case ru.TypeRef(a, Map, List(Symbol, _)) => if (obj.contains(Symbol(key))) Some(obj(Symbol(key)).toString) else None
  case ru.TypeRef(a, Map, List(String, _)) => if (obj.contains(key)) Some(obj(key).toString) else None
  case _ =>
    if (/* obj.key() exists */)
      // Some(obj.key().toString)
    else
      None
}

更新 2:我从未想过我可以将asInstanceOfMap[String, _] 之类的东西一起使用。我使用@johny 的第二个代码示例来提出我的解决方案。我在 mutable.HashMap[Class[_], Set[String]] 中按类缓存方法名称。

def lookUp(obj: AnyRef, key: String): Option[String] = obj match {
  case m: Map[_, _] =>
    if (m.asInstanceOf[Map[String, _]].contains(key))
      extractValue(m.asInstanceOf[Map[String, _]](key))
    else if (m.asInstanceOf[Map[Symbol, _]].contains(Symbol(key)))
      extractValue(m.asInstanceOf[Map[Symbol, _]](Symbol(key)))
    else
      None
  case _ =>
    val klass = obj.getClass
    if (!methodsCache.contains(klass))
      methodsCache(klass) = klass.getMethods.toList.filter(_.getParameterCount == 0).map(_.getName).toSet
    val methodNames = methodsCache(klass)
    if (methodsCache(klass).contains(key))
      extractValue(klass.getDeclaredMethod(key).invoke(obj))
    else
      None
}

def extractValue(obj: Any): Option[String] = obj match {
  case null | None => None
  case Some(x) => Some(x.toString)
  case x => Some(x.toString)
}

【问题讨论】:

    标签: scala reflection scala-reflect


    【解决方案1】:
     def lookUp(key: String, obj: AnyRef) {
        obj match {
          case x: Map[String, _] => x(key)
          case _                 => obj.getClass.getDeclaredMethod(key).invoke(obj)
        }
      }
    

    编辑:确保 Map 的键是 String 或 scala.Symbol

    def lookUp(key: String, obj: AnyRef)= {
        obj match {
          case x: Map[_, _]  => 
            if(x.asInstanceOf[Map[String,_]].contains(key))
              x.asInstanceOf[Map[String,_]].get(key)
            else if(x.asInstanceOf[Map[scala.Symbol,_]].contains(scala.Symbol(key)))
              x.asInstanceOf[Map[scala.Symbol,_]].get(scala.Symbol(key))
            else
              None
    
          case _ => Some(obj.getClass.getDeclaredMethod(key).invoke(obj))
        }
      }
    

    以上内容也不能确保返回的输出来自预期的Map

    编辑:跟随@JimCain 的领导

    def lookUp[T:ru.TypeTag](obj: T, key: String): Option[Any] = ru.typeTag[T].tpe match {
        case ru.TypeRef(a, m, l) if(m.name.toString=="Map"&&l.head =:= ru.typeOf[java.lang.String])=> obj.asInstanceOf[Map[String,_]].get(key)
        case ru.TypeRef(a, m,l) if(m.name.toString=="Map"&&l.head =:= ru.typeOf[Symbol])=> obj.asInstanceOf[Map[Symbol,_]].get(scala.Symbol(key))
        case _ => Try(obj.getClass.getDeclaredMethod(key).invoke(obj)) match {
          case Success(x) => Some(x)
          case Failure(_) => None
        }
      }
    

    【讨论】:

    • 来自 REPL:warning: non-variable type argument String in type pattern scala.collection.immutable.Map[String,_] (the underlying of Map[String,_]) is unchecked since it is eliminated by erasure
    • 由于类型擦除,Map 的类型参数不可用。为方便起见,添加了参数String,从而可以避免转换。您还可以使用@unchecked 注释,例如Map[String @unchecked, _],这将删除警告。不利的一面是它将匹配所有Maps,而不管其类型参数如何。
    • 我就是这么想的。但是,我想知道键是String 还是Symbol,所以我知道如何可靠地查找它们。如果键都不是类型,那么我想完全跳过它。
    • 如果字符串和符号是唯一的情况,请参阅编辑。
    • @JimCain,请参阅此编辑。修改您的代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-01
    • 2015-07-09
    • 2022-12-07
    • 1970-01-01
    • 2013-02-02
    • 2021-10-21
    相关资源
    最近更新 更多