Access to static fields of a class from a character without a class name

Here's an excerpt from a REPL session, which 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 the static method / fields of a Java class using a non-Classname, a custom character?

UPDATE

The following works as expected:

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

Is there a better / more idiomatic way to achieve the same result?

+4
source share
3 answers

If you cannot define a class (possibly programmatically in a macro) at compile time, you need to resort to using reflection. This will do the same as eval 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 is not a macro or a special form. For example, a method name can be provided by the user through a graphical interface or although a socket is at run time.

If you have a class name at compile time, you can use a macro, as suggested by Nicholas. However, there is no need to create code similar to (Integer/parseInt "1") , as it is just syntactic sugar for simpler (and macros) . 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" performed by this macro is to convert a string to a character. You are probably just using a special form . in an external macro (one that somehow acquires method names, for example, by getting passed as arguments or reading them from 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) 
+2
source

In theory, the following approach may work:

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

  • define namespace 'y' (create-ns ...)
  • find all methods for Integer (or any other class) using (.getMethods ...)
  • dynamically create thin wrappers for all of these methods in the "y" namespace

This is pretty ugly, as this approach will also create wrappers for methods that you don't need.

There is no guarantee;)

0
source

I don't think there is a better way than the eval call you are proposing. You can always wrap it in a beautiful macro:

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

Update . As raek suggested here is a version using the Reflector class:

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

Please note that I wrote a macro in both cases just for the convenience of saving some characters. For best performance, you should use invokeStaticMethod directly.

0
source

All Articles