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.
精彩评论