开发者

Is there an easy (idiomatic) way to convert a java.lang.reflect.Method to a Scala function?

开发者 https://www.devze.com 2022-12-27 01:43 出处:网络
Can I retrieve a Method via reflection, somehow combine it with a target object, and return it as something that looks like a function in Scala (i.e. you can call it using parenthesis)? The argument l

Can I retrieve a Method via reflection, somehow combine it with a target object, and return it as something that looks like a function in Scala (i.e. you can call it using parenthesis)? The argument list is variable. It doesn't have to be a "first-class" function (I've updated the question), just a syntactic-looking function call, e.g. f(args).

My attempt so far looks something like this (which technically is pseudo-code, just to avoid cluttering up the post with additional definitions):

class method_ref(o: AnyRef, m: java.lang.reflect.Method) {
    def apply(args: Any*): some_return_type = {
        var oa: Array[Object] = args.toArray.map { _.asInstanceOf[Object] }
        println("calling: " + m.toString + " with: " + oa.length)

        m.invoke(o, oa: _*) match  {
            case x: some_return_type => x;
            case u => throw new Exception("unknown result" + u);
        }
    }
}

With the above I was able to get past the compiler errors, but now I have a run-time exception:

Caused by: java.lang.IllegalArgumentException: argument type mismatch

The example usage is something like:

  var f = ... some expression returning method_ref ...;
  ...
  var y = f(x) // looks like a function, doesn'开发者_C百科t it?

UPDATE

Changing the args:Any* to args:AnyRef* actually fixed my run-time problem, so the above approach (with the fix) works fine for what I was trying to accomplish. I think I ran into a more general issue with varargs here.


Sure. Here's some code I wrote that add an interface to a function. It's not exactly what you want, but I think it can be adapted with few changes. The most difficult change is on invoke, where you'll need to change the invoked method by the one obtained through reflection. Also, you'll have to take care that the received method you are processing is apply. Also, instead of f, you'd use the target object. It should probably look something like this:

  def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]) = method match {
    case m if /* m is apply */ => target.getClass().getMethod("name", /* parameter type */).invoke(target, args: _*)
    case _ => /* ??? */
  }

Anyway, here's the code:

import java.lang.reflect.{Proxy, InvocationHandler, Method}

class Handler[T, R](f: Function1[T, R])(implicit fm: Manifest[Function1[T, R]]) extends InvocationHandler {
  def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]) = method.invoke(f, args: _*)
  def withInterface[I](implicit m: Manifest[I]) = {
    require(m <:< manifest[Function1[T, R]] && m.erasure.isInterface)
    Proxy.newProxyInstance(m.erasure.getClassLoader(), Array(m.erasure), this).asInstanceOf[I]
  }
}

object Handler {
  def apply[T, R](f: Function1[T, R])(implicit fm: Manifest[Function1[T, R]]) = new Handler(f)
}

And use it like this:

trait CostFunction extends Function1[String, Int]
Handler { x: String => x.length } withInterface manifest[CostFunction]

The use of "manifest" there helps with syntax. You could write it like this:

Handler({ x: String => x.length }).withInterface[CostFunction] // or
Handler((_: String).length).withInterface[CostFunction]

One could also drop the manifest and use classOf instead with a few changes.


If you're not looking for a generic invoke that takes the method name--but rather, you want to capture a particular method on a particular object--and you don't want to get too deeply into manifests and such, I think the following is a decent solution:

class MethodFunc[T <: AnyRef](o: Object, m: reflect.Method, tc: Class[T]) {
  def apply(oa: Any*): T = {
    val result = m.invoke(o, oa.map(_.asInstanceOf[AnyRef]): _*)
    if (result.getClass == tc) result.asInstanceOf[T]
    else throw new IllegalArgumentException("Unexpected result " + result)
  }
}

Let's see it in action:

val s = "Hi there, friend"
val m = s.getClass.getMethods.find(m => {
  m.getName == "substring" && m.getParameterTypes.length == 2
}).get
val mf = new MethodFunc(s,m,classOf[String])

scala> mf(3,8)
res10: String = there

The tricky part is getting the correct type for the return value. Here it's left up to you to supply it. For example,if you supply classOf[CharSequence] it will fail because it's not the right class. (Manifests are better for this, but you did ask for simple...though I think "simple to use" is generally better than "simple to code the functionality".)

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号