Declare Member Variables with gen-class in Clojure
I'm 开发者_开发知识库learning how to extend Java classes in Clojure, but I don't see a way declare new member variables; I only see a way for methods.
(ns test.aclass
(:gen-class
:methods [[foo [] String]]))
Is there a :members
keyword or another way of declaring member variables?
:state name
If supplied, a public final instance field with the given name will be created. You must supply an :init function in order to provide a value for the state. Note that, though final, the state can be a ref or agent, supporting the creation of Java objects with transactional or asynchronous mutation semantics.
There is an example on the website of how it can be used.
I was having some trouble with this too. The example below is not elegant but it's pretty simple for writing dumb little glue classes in Clojure rather than Java. Note all I did for thread safety is to ensure that the field updates are atomic -- I didn't do any other concurrency stuff, and that may make a real difference.
The init method creates the instance variables for the object. The setfield
and getfield
macros abbreviate the bookkeeping of the atomic update.
(ns #^{:doc "A simple class with instance vars"
:author "David G. Durand"}
com.tizra.example )
(gen-class
:name com.tizra.example.Demo
:state state
:init init
:prefix "-"
:main false
:methods [[setLocation [String] void]
[getLocation [] String]]
)
(defn -init []
"store our fields as a hash"
[[] (atom {:location "default"})])
(defmacro setfield
[this key value]
`(swap! (.state ~this) into {~key ~value}))
(defmacro getfield
[this key]
`(@(.state ~this) ~key))
(defn -setLocation [this ^java.lang.String loc]
(setfield this :location loc))
(defn ^String -getLocation
[this]
(getfield this :location))
You have to compile this, and make sure the stub class produced is on your classpath, and then you can make instances, etc. just like any other java class.
=> (com.tizra.example.Demo.)
#<Demo com.tizra.example.Demo@673a95af>
=> (def ex (com.tizra.example.Demo.))
#'user/ex
=> (.getLocation ex)
"default"
=> (.setLocation ex "time")
nil
=> (.getLocation ex)
"time"
I found the longer summary at this blog quite helpful: http://kotka.de/blog/2010/02/gen-class_how_it_works_and_how_to_use_it.html
The body of a proxy
is a lexical closure, so you can just close around whatever variables you need. If, God forbid, you need to mutate them, then close around an atom:
(defn lying-list [init-size]
(let [the-size (atom init-size)]
(proxy [java.util.ArrayList] []
(size [] @the-size)
(remove [idx] (reset! the-size idx), true))))
There's really no need for actual Java fields here.
精彩评论