开发者

Accessing static fields of a class from a non-Classname symbol

Here's an extract from a REPL session that hopefully explains what I want to achieve:

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

How can I access static method/fields of a Java class using a non-Classname, user defined symbol?

UPDATE

The following works as expected:

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

Is there a bette开发者_如何学Cr/more idiomatic way to achieve the same result?


If you cannot determine the class (possibly programmatically in a macro) during compile time, you need to resort to use reflection. This would do the same thing as eval does when it tries to compile the code. See clojure.lang.Reflector/invokeStaticMethod: https://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"]))

This can be used in arbitrary ways at runtime, since it's not a macro or a special form. For example, the name of the method could be given by the user via a GUI or though a socket at runtime.

If you have the name of the class at compile time, you can use a macro as Nicolas suggested. However, it's unnecessary to construct the code to look like (Integer/parseInt "1"), since it's just syntactic sugar for the more basic (and macro friendly) . special form: (. 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) ~@args))

However, the only "real work" this macro performs is to convert the string into a symbol. You would probably just use the . special form in an outer macro (the one that acquires the names of the methods somehow, e.g. by getting those passed as arguments, or by reading them from a var or from a configuration file).

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


In theory the following approach could work:

You can write a macro def-alias that allows you to do (def-alias y Integer). This macro should:

  • define a namespace 'y' (create-ns ...)
  • find all methods for Integer (or any other class), using (.getMethods ...)
  • dynamically create thin wrappers for all those methods in namespace 'y'

It's kinda ugly since this approach will also create wrappers for methods you don't need.

No guarantees ;)


I don't think there's any better way than the eval call you provided. You could always wrap it up in a nice macro:

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

Update: As suggested by raek here's a version using the Reflector class:

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

Note that I've written a macro in both cases only for the convenience of saving some characters. For better performance you should use invokeStaticMethod directly.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜