2010-04-18 55 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

相關問題