Java object field access

629 views
Skip to first unread message

Petr Gladkikh

unread,
Jul 26, 2011, 2:10:23 AM7/26/11
to clo...@googlegroups.com
I am trying to construct java object and assign it's fields from a map.
That is given Java object of class Something { long id; String name; }
and Clojure map {:id 12, :name "Impostor"}
I would like to set fields of java object to respective values from map.

Now, this works
(let [o (Something.)]
(set! (. o :id) 12)
(set! (. o :name) "Impostor")
o)

But as soon as I use some value to get field expression compiler
starts complaining "Invalid assignment target".
that is
(let [o (Something.)
ff :id]
(set! (. o ff) 12)
o)
I do not understand why this problem occurs. Any variations that I
tried to made it work do not do the trick.
Including weird (or ridiculous) ones like (set! (. o (symbol (str ":"
(name ff))) 12)

I suspect that this has something to do with compiler that needs field
names at compile time but Clojure could use reflection for this...

Can anyone point to what is wrong here?

By the way is there already some function that allows to set fields of
an object from a map?

--
Petr Gladkikh

Shantanu Kumar

unread,
Jul 26, 2011, 3:52:09 AM7/26/11
to Clojure
Use this function:

(defn ^java.lang.reflect.Field get-field
"Return Field object"
[^Class class ^String field-name]
(let [f (.getDeclaredField class field-name)]
(.setAccessible f true)
f))

> (let [o (Something.)]
>   (set! (. o :id) 12)
>   (set! (. o :name) "Impostor")
>   o)

...as follows (it works even with private fields):

(let [o (Something.)]
(.set (get-field "id") o 12)
(.set (get-field "name") o "Impostor")
  o)

Hope this helps.

Regards,
Shantanu

Shantanu Kumar

unread,
Jul 26, 2011, 3:54:11 AM7/26/11
to Clojure


On Jul 26, 12:52 pm, Shantanu Kumar <kumar.shant...@gmail.com> wrote:
> Use this function:
>
> (defn ^java.lang.reflect.Field get-field
>   "Return Field object"
>   [^Class class ^String field-name]
>   (let [f (.getDeclaredField class field-name)]
>     (.setAccessible f true)
>     f))
>
> > (let [o (Something.)]
> >   (set! (. o :id) 12)
> >   (set! (. o :name) "Impostor")
> >   o)
>
> ...as follows (it works even with private fields):
>
> (let [o (Something.)]
>   (.set (get-field "id")   o 12)
>   (.set (get-field "name") o "Impostor")
>   o)

Oops! this should be:

(let [o (Something.)]
  (.set (get-field Something "id")   o 12)
  (.set (get-field Something "name") o "Impostor")

Alan Malloy

unread,
Jul 26, 2011, 4:28:01 AM7/26/11
to Clojure
Clojure *could* use reflection to do this...unless your object had a
field named ff! It has to decide at compile time how to look up a
field, and at that time it doesn't know your object won't have a .ff
field, so it figures, sure, I'll set the ff field.

If you really want to do this (hint: you don't), you can manually deal
with the reflection that the compiler would generate, as Shantanu
outlines.

Petr Gladkikh

unread,
Jul 26, 2011, 6:02:39 AM7/26/11
to clo...@googlegroups.com

Could you elaborate on this? What would you use instead in this case?
My motivation is need to construct list of Java objects and I would
like to have some concise syntax to write them. So I decided to do
this with maps.
I wrote a function that acts as constructor. But long list of

(set! (. obj :aa) (:aa props))
(set! (. obj :bb) (:bb props))
(set! (. obj :cc) (:cc props))
(set! (. obj :dd) (:dd props))

looks not very lispy. Maybe I should use macros instead?

--
Petr Gladkikh

Ken Wesson

unread,
Jul 26, 2011, 6:14:41 AM7/26/11
to clo...@googlegroups.com

Untested! But should give a general idea how to do this sort of thing:

(defmacro defsetter [class keys]
(let [o (gensym)
p (gensym)]
`(defn ~(symbol (str "set-" (.toLowercase (str class))))
[~o ~p]
~@(map
(fn [k]
`(set! (. ~o ~k) (~k ~p)))
keys))))

(defsetter Foo [:a :b])

(set-foo a-foo {:a 0 :b 42})

(defsetter Bar [:x :y :z])

(ser-bar a-bar {:x 4 :y 8 :z 15})

--
Protege: What is this seething mass of parentheses?!
Master: Your father's Lisp REPL. This is the language of a true
hacker. Not as clumsy or random as C++; a language for a more
civilized age.

Shantanu Kumar

unread,
Jul 26, 2011, 6:31:25 AM7/26/11
to Clojure
> My motivation is need to construct list of Java objects and I would
> like to have some concise syntax to write them. So I decided to do
> this with maps.

Perhaps something like this:


(defn ^java.lang.reflect.Field get-field
"Return Field object"
[class-or-obj ^String field-name]
(let [c (if (class? class-or-obj)
class-or-obj
(class class-or-obj))
f (.getDeclaredField c field-name)]
(.setAccessible f true)
f))


(defn as-str
[a]
(if (instance? clojure.lang.Named a)
(name a)
(str a)))


(defn set-field-value
[obj [field-name value]]
(let [f (get-field obj (as-str field-name))]
(.set f obj value)
obj))


;; returns the object
(reduce set-field-value
(Something.)
{:id 10 :name "Impostor!"})

Regards,
Shantanu

Kevin Downey

unread,
Jul 26, 2011, 4:03:15 PM7/26/11
to clo...@googlegroups.com
you guys realize there are functions in contrib that do the reflection
for you, yes?

> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

--
And what is good, Phaedrus,
And what is not good—
Need we ask anyone to tell us these things?

.Bill Smith

unread,
Jul 26, 2011, 7:11:07 PM7/26/11
to clo...@googlegroups.com
Petr,

I do not know of a function in clojure core or clojure contrib that does what you request.  I wrote a function called set-bean for that purpose: https://github.com/billsmith/clojure-code/blob/master/clj/billsmith/util.clj.

Here is an example of how to use set-bean:

    (billsmith.util/set-bean (java.util.GregorianCalendar.) {:firstDayOfWeek 3, :lenient true, :minimalDaysInFirstWeek 7})

It is not very efficient, and I would not recommend using it in a tight loop.  However, it is adequate for occasional use.

Bill Smith

Petr Gladkikh

unread,
Jul 30, 2011, 1:00:06 AM7/30/11
to clo...@googlegroups.com

I tried this since I have not used macroses for real problem so far.
And it actually works.
But I do not understand why it works.
I have class:
class Foo {
public String s;
public int v;
public String toString() { return "{" + s + "," + v + "}"; }
}

Then in Clojure:
(defsetter abcde [:s :v])
(let [afoo (actialpackage.Foo.)]
(set-abcde afoo {:s "S" :v 42})
(println afoo))

But at the moment (defsetter abcde [:s :v]) is expanded nothing is
known about actual class.
So it is not clear to me why this works but giving field names at
runtime does not.

Can anyone clarify this?
Maybe this wokrs because in this case compiler can infer type of java
object at compile time?

--
Petr Gladkikh

Ken Wesson

unread,
Jul 30, 2011, 1:37:33 AM7/30/11
to clo...@googlegroups.com
On Sat, Jul 30, 2011 at 1:00 AM, Petr Gladkikh <petr...@gmail.com> wrote:
> I tried this since I have not used macroses for real problem so far.
> And it actually works.
> But I do not understand why it works.
> I have class:
> class Foo {
>    public String s;
>    public int v;
>    public String toString()  { return "{" + s + "," + v + "}"; }
> }
>
> Then in Clojure:
>  (defsetter abcde [:s :v])
>  (let [afoo (actialpackage.Foo.)]
>   (set-abcde afoo {:s "S" :v 42})
>   (println afoo))
>
> But at the moment  (defsetter abcde [:s :v]) is expanded nothing is
> known about actual class.
> So it is not clear to me why this works but giving field names at
> runtime does not.
>
> Can anyone clarify this?
> Maybe this wokrs because in this case compiler can infer type of java
> object at compile time?

The macro expands into field-accessing code. If the compiler infers
the type, this becomes fast bytecode to access the fields. Otherwise
it becomes slowish reflection calls, but still works.

.Bill Smith

unread,
Jul 30, 2011, 11:41:46 AM7/30/11
to clo...@googlegroups.com
Oh sorry, I saw the title and thought "bean access" rather than "field access".  Obviously the code I posted relies on the presence of setters rather than fields.
Reply all
Reply to author
Forward
0 new messages