使用 Scala 反射检查对象上的方法或查找 Map 上的键类型
Using Scala reflection to check for a method on an object or to find key type on a Map
我正在将 class 从 Ruby 移植到 Scala 2.11,它实现了消息中的变量合并。我想将一个对象数组传递给合并方法,并让它在每个对象中搜索消息文本中引用的键。
它的核心是一个名为 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 版本中不一定需要该功能。
我遇到的一个问题是,我发现的示例要求我提前知道对象的 class,以便我可以调用 ru.typeOf[MyClass].declaration(ru.TermName("key"))
(其中 ru
是 scala.reflect.runtime.universe
).
另一个问题是此消息合并每分钟可能发生数百次,而反射似乎是一个缓慢而复杂的过程。如果这一切按计划进行,我可能会按对象类型缓存反射结果。
更新:我在想这样的事情,但这是不是太过分了?或者是否需要正确捕获 Map 中的类型?此外,这不会编译。 Map
、Symbol
和 String
不是适合其上下文的类型。
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:我从没想过我可以将 asInstanceOf
与 Map[String, _]
之类的东西一起使用。我使用@johny 的第二个代码示例来提出我的解决方案。我通过 class 在 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)
}
def lookUp(key: String, obj: AnyRef) {
obj match {
case x: Map[String, _] => x(key)
case _ => obj.getClass.getDeclaredMethod(key).invoke(obj)
}
}
编辑:确保 Map 的键是字符串或 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
}
}
我正在将 class 从 Ruby 移植到 Scala 2.11,它实现了消息中的变量合并。我想将一个对象数组传递给合并方法,并让它在每个对象中搜索消息文本中引用的键。
它的核心是一个名为 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 版本中不一定需要该功能。
我遇到的一个问题是,我发现的示例要求我提前知道对象的 class,以便我可以调用 ru.typeOf[MyClass].declaration(ru.TermName("key"))
(其中 ru
是 scala.reflect.runtime.universe
).
另一个问题是此消息合并每分钟可能发生数百次,而反射似乎是一个缓慢而复杂的过程。如果这一切按计划进行,我可能会按对象类型缓存反射结果。
更新:我在想这样的事情,但这是不是太过分了?或者是否需要正确捕获 Map 中的类型?此外,这不会编译。 Map
、Symbol
和 String
不是适合其上下文的类型。
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:我从没想过我可以将 asInstanceOf
与 Map[String, _]
之类的东西一起使用。我使用@johny 的第二个代码示例来提出我的解决方案。我通过 class 在 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)
}
def lookUp(key: String, obj: AnyRef) {
obj match {
case x: Map[String, _] => x(key)
case _ => obj.getClass.getDeclaredMethod(key).invoke(obj)
}
}
编辑:确保 Map 的键是字符串或 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
}
}