2010-04-18 57 views
2

我可以通过反射检索一个方法,以某种方式将它与目标对象合并,并将其作为Scala中的函数返回(即,您可以使用圆括号调用它)?参数列表是可变的。它不一定是“头等舱”功能(我更新了问题),而只是一个看起来语法上的函数调用,例如, F(参数)。是否有一种简单(惯用)的方式将java.lang.reflect.Method转换为Scala函数?

我尝试到目前为止看起来是这样的(这在技术上是伪代码,只是为了避免弄乱了新的定义后):

 
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); 
     } 
    } 
} 

通过以上我能闯过编译错误,但现在我有一个运行时异常:

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

的例子用法是一样的东西:

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

UPDATE

更改ARGS:任何*成参数:AnyRef *居然修好了我的运行时间问题,因此上述方法(与修订)工作正常,我试图完成。我想我在这里遇到了可变参数更普遍的问题。

+0

哦......我错过了关于“简单”的事情。不,没有。曾经,但它是实验性的,并被放弃。 – 2010-04-18 05:12:19

+0

你可以随时解释_where_你得到错误,因为你发布的method_ref不会被编译。 – 2010-04-18 13:39:48

+0

它实际上看起来像改变Any * AnyRef *解决了我的问题,但我不明白为什么。 – 2010-04-18 23:06:52

回答

2

如果您不想寻找一个采用方法名称的通用invoke - 而是想要在特定对象上捕获特定的方法 - 而且您不想过度深入清单和这样,我认为下面是一个体面的解决方案:

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) 
    } 
} 

让我们来看看它在行动:

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 

棘手的部分是获得正确类型的返回值。这是由你来提供的。例如,如果您提供classOf[CharSequence],它将失败,因为它不是正确的类。 (清单更适合这个,但你确实要求简单...虽然我认为“简单易用”通常比“简单编写功能”更好。)

+0

在我的情况下,返回类型是固定的,所以不需要额外的复杂度。我仍然为可变参数感到困惑。我看到你扔了一个“_.asInstanceOf [AnyRef]”,我有“_.asInstanceOf [Object]”。 – 2010-04-18 23:10:05

+0

@Alex:'AnyRef'是'Object'的Scala。我用'Object'来表示“这里是Java-y的东西”和“AnyRef'”来表示“这是一个Scala-y的东西”。 – 2010-04-19 02:58:06

4

当然。下面是我写的一个代码,它将一个接口添加到函数中。这不完全是你想要的,但我认为它可以适应几乎没有变化。最困难的变化是invoke,你需要通过反射获得的方法来改变被调用的方法。另外,您必须注意您正在处理的收到的方法是apply。此外,而不是f,你会使用目标对象。它可能应该是这个样子:

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 _ => /* ??? */ 
    } 

总之,这里的代码:

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) 
} 

而且使用这样的:

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

使用 “表现” 也有帮助句法。你可以这样写:

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

也可以删除清单并使用classOf来代替一些更改。

+0

哎呀。应该有更简单的事情。请参阅我迄今为止尝试过的更新的问题,此时编译但在运行时失败。 – 2010-04-18 13:33:27

相关问题