How do I pull `static final` constants from a Java class into a Clojure namespace?
I am trying to wrap a Java library with a Clojure binding. One particular class in the Java library defines a bunch of static final constants, for example:
class Foo {
public static final int BAR = 0;
public static final int SOME_CONSTANT = 1;
...
}
I had a thought that I might be able to inspect the cla开发者_如何学运维ss and pull these constants into my Clojure namespace without explicitly def
-ing each one.
For example, instead of explicitly wiring it up like this:
(def foo-bar Foo/BAR)
(def foo-some-constant Foo/SOME_CONSTANT)
I'd be able to inspect the Foo
class and dynamically wire up foo-bar
and foo-some-constant
in my Clojure namespace when the module is loaded.
I see two reasons for doing this:
A) Automatically pull in new constants as they are added to the Foo
class. In other words, I wouldn't have to modify my Clojure wrapper in the case that the Java interface added a new constant.
B) I can guarantee the constants follow a more Clojure-esque naming convention
I'm not really sold on doing this, but it seems like a good question to ask to expand my knowledge of Clojure/Java interop.
Thanks
Sadly the macro clojure.contrib.import-static doesn't allow to import all static final fields. You must provide a list of fields to import.
This macro is a idiomatic wrapper for import-static:
(ns stackoverflow
(:use clojure.contrib.import-static)
(:import (java.lang.reflect Modifier)))
(defmacro import-static-fields
"Imports all static final fields of the class as (private) symbols
in the current namespace.
Example:
user> (import-static-fields java.lang.Integer)
#'user/TYPE
user> MAX_VALUE
2147483647
Note: The class name must be fully qualified, even if it has already
been imported."
[class]
(let [final-static-field? (fn [field]
(let [modifiers (.getModifiers field)]
(and (Modifier/isStatic modifiers) (Modifier/isFinal modifiers))))
static-fields (map #(.getName %)
(filter
final-static-field?
(.. Class (forName (str class)) getFields)))]
`(import-static ~class ~@static-fields)))
(This answer now includes two working solutions, one based on my initial idea with intern
and one based on danlei's suggestion to use c.c.import-static
. I guess I'll need to clean this up a bit later, but I can't spend more time on it now...)
To extract static fields:
(filter #(bit-and java.lang.reflect.Modifier/STATIC (.getModifiers %))
(.getFields YourClass))
Then map #(intern *ns* (str "a-prefix-" (.getName %)) (.get YourClass nil))
across that sequence to obtain the value... Note that this bit is untested and, in particular, I'm not sure about that nil
in .get
; experiment with java.lang.Field
and see what works with your class.
Update 2:
Ok, actually an intern
based approach is not that bad readability-wise:
user> (map #(intern *ns* (symbol (str "integer-" (.getName %))) (.get % java.lang.Integer))
(filter #(bit-and java.lang.reflect.Modifier/STATIC
(.getModifiers %))
(.getFields java.lang.Integer)))
(#'user/integer-MIN_VALUE #'user/integer-MAX_VALUE #'user/integer-TYPE #'user/integer-SIZE)
user> integer-MIN_VALUE
-2147483648
user> integer-MAX_VALUE
2147483647
user> integer-TYPE
int
user> integer-SIZE
32
Update: (leaving the first update in place as an alternative solution)
Combining danlei's knowledge of clojure.contrib
with the above yields the following:
user> (map #(eval `(import-static java.lang.Integer ~(symbol (.getName %))))
(filter #(bit-and java.lang.reflect.Modifier/STATIC
(.getModifiers %))
(.getFields java.lang.Integer)))
(#'user/MIN_VALUE #'user/MAX_VALUE #'user/TYPE #'user/SIZE)
user> MIN_VALUE
-2147483648
user> MAX_VALUE
2147483647
user> TYPE
int
user> SIZE
32
It uses eval
... well, so what, it's hardly going to "kill performance" and it's actually fairly readable, which an elaborate expression using intern
might not be. (It's not so bad actually... :-)) If you prefer intern
, though, at least the implementation of import-static
can give you the proper ideas if my sketch above turns out to be incorrect somehow.
I haven't tried it, but maybe clojure.contrib.import-static can do it.
Just checked: You will have to name the methods/fields when using import-static, but I'll leave this answer here because it might be helpful for people searching for related answers.
精彩评论