2011-04-28 61 views
4

下面是一個REPL會話的提取物,希望能解釋我想達到的目標:訪問類的靜態字段從非類名稱符號

user> (Integer/parseInt "1") 
1 
user> (def y Integer) 
#'user/y 
user> (y/parseInt "1") 
No such namespace: y 
    [Thrown class java.lang.Exception] 

如何訪問一個Java類的靜態方法/領域使用非Classname,用戶定義的符號?

UPDATE

預期以下工作:

user> (eval (list (symbol (.getName y) "parseInt") "1")) 
1 

是否有更好/更地道的方式來達到同樣的效果?

+0

你能解釋一下你爲什麼要這樣做嗎? – 2011-04-28 09:43:45

+0

我不得不應對設計不佳的Java API,其中沒有方法的基本接口被其他幾個接口擴展,每個接口都提供一種方法。這些方法意味着通過具體實現註冊爲回調,這是通過反射(例如'Registry.registerCallback(ImplementedInterface/CONDITION,concreteInstance,「callbackMethodName」)「)完成的。我想隱藏所有的犛牛刮背後更溫柔的Clojure API代碼,因此我需要動態綁定到接口並使用反射來執行回調註冊。 – skuro 2011-04-28 10:02:57

+0

eval方法有一個鬼鬼祟祟的缺點:eval不能在本地範圍中使用變量;就好像它是在頂層執行的一樣。因此,(讓[x 1](eval'(inc x)))導致「無法解析符號:x」錯誤。 – raek 2011-04-29 09:51:17

回答

2

如果您不能確定類在編譯時(可能在編程宏),你需要求助於使用反射。這將和eval在嘗試編譯代碼時所做的一樣。見clojure.lang.Reflector/invokeStaticMethodhttps://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Reflector.java#L198

(import 'clojure.lang.Reflector) 
;; Here, you can pass *any string you have at runtime* 
(Reflector/invokeStaticMethod Integer "parseInt" (into-array ["1"])) 

這可以以任意方式在運行時使用,因爲它不是一個宏或一種特殊形式。例如,該方法的名稱可以由用戶通過GUI或雖然在運行時的插座給出。

如果在編譯時類的名稱,你可以使用宏作爲薩科建議。然而,這是不必要的構建代碼看起來像(Integer/parseInt "1"),因爲它是更基本的(和宏觀型).特殊形式只是語法糖:(. Integer parseInt "1")

;; Here, the method name needs to be a *string literal* 
(defmacro static-call 
    "Takes a Class object, a string naming a static method of it 
    and invokes the static method with the name on the class with 
    args as the arguments." 
    [class method & args] 
    `(. ~class ~(symbol method) [email protected])) 

但是,這個宏唯一的「真正的工作」是將字符串轉換爲符號。您可能只需在外部宏中使用.特殊格式(即以某種方式獲取方法名稱的格式,例如通過獲取作爲參數傳遞的格式,或通過從var或配置文件讀取它們)。

;; Use ordinary Clojure functions to construct this 
(def the-static-methods {:foo ["Integer" "parseInt"], :bar ["Long" "parseLong"]}) 

;; Macros have access to all previously defined values 
(defmacro generate-defns [] 
    (cons `do (for [[name-keyword [class-string method-string]] the-static-methods] 
       `(defn ~(symbol (name name-keyword)) [x#] 
       (. ~(symbol class-string) ~(symbol method-string) x#))))) 

(generate-defns) 
0

從理論上來說,下面的方法可以工作:

你可以寫一個宏高清別名,讓你做(DEF-稱Y整數)。該宏應該:

  • 定義命名空間 'Y'(創建-NS ...)
  • 找到整數的所有方法(或任何其他類),使用(.getMethods ...)
  • 在命名空間'y'中動態創建所有這些方法的薄包裝

這有點難看,因爲這種方法還會爲您不需要的方法創建包裝。

無擔保;)

+0

感謝您提出的方法。但是,我用另一種更簡潔的方式更新了原來的問題來完成這項工作。這也有點醜陋,你認爲它可以以任何方式改善嗎? – skuro 2011-04-28 12:58:07

0

我不認爲有什麼更好的辦法比eval打電話給你提供。你總是可以包起來在一個不錯的宏:

(defmacro static-call [var method & args] 
    `(-> (.getName ~var) 
     (symbol ~(str method)) 
     (list [email protected]) 
     eval)) 

更新:由於raek建議這裏是一個使用Reflector類版本:

(defmacro static-call [var method & args] 
    `(clojure.lang.Reflector/invokeStaticMethod 
    (.getName ~var) 
    ~(str method) 
    (to-array ~(vec args)))) 

請注意,我已經寫在宏這兩種情況只是爲了方便保存一些字符。爲了獲得更好的性能,你應該直接使用invokeStaticMethod

+0

好點,我想它不能再改進了。 – skuro 2011-04-28 18:43:31

+0

eval在爲靜態調用生成字節碼時使用Clojure的Reflector類。你不必這樣做,因爲這個類還提供反射調用的方法:(clojure.lang.Reflector/invokeStaticMethod ...) – raek 2011-04-29 09:48:09

相關問題