Welcome! Please see the About page for a little more info on how this works.

0 votes
in Java Interop by

I'm studying clojure & currently experimenting with swing & java interoperability. I have this namespace:

(ns test.core
  (:gen-class
   :name Testing
   :extends javax.swing.JFrame
   :init init
   :state state
   :constructors {[String] [String]})
  (:import (javax.swing JFrame)))

(defn -init [x]
      [[x] ()])

so i can now instantiate a JFrame with (let [jf (Testing. "example jframe")]..

I'm kind of lost with the :init, :state, :constructors tags used in :gen-class.

1) do i need to use :state in this example? From what i understand here:
https://clojuredocs.org/clojure.core/gen-class
and with the "com.example" code in the comments, :state is used in order to allow changes on an otherwise immutable object by creating a new object with changed state. But in the above case the jframe is already mutable so there's no need for using :state anyway.. Is this thinking correct ?

2) :init is a function that will return a vector with the superclass constructor arguments and the state. What's exactly the use of :constructors? Do i get to define a custom constructor which also calls the superclass constructor, like i would do in java? Or is this not possible at all and i should just call a separate function after the object is initialized, something like

(let [jf (Testing. "example jframe")]
(doto jf
    (.setSize 200 300)
    (.setVisible true)
    etc..

thank you in advance

2 Answers

+2 votes
by
selected by
 
Best answer

Rather than go the lower-level (but perhaps more featured) gen-class route, you can get good results using clojure.core/proxy. Proxy lets you define a stub class implementation that can subclass existing classes, call superclass method implementations, override, etc., without having to go the AOT-compiled route the genclass requires. The downside is it's not as fast (e.g. if you're proxying an object with a method invocation that's on a hot path, that's maybe not so good and gen-class or other methods are necessary). In fact, this is pretty common for wrapping swing components (even seesaw does it ).

My recommendation is to try (if interface based) reify | deftype, then proxy, then gen-class, then if it's easier or you're willing, java. Most of my use-cases have been handled through basic clojure interop, without the need for gen-class though.

But in the above case the jframe is already mutable so there's no need for using :state anyway.. Is this thinking correct ?

If you can get a handle on the JFrame, then yes you can mutate it through interop. Since this class inherits from it, then that's plausible. The "state" implementation is sort of a clojure-specific implementation detail for providing an idiomatic way to package your own stuff along with generated classes (rather than, say, a bunch of individual instance vars, you'd typically just pack a map of state - which can include atoms and stuff for mutation if you'd like).

Or is this not possible at all and i should just call a separate function after the object is initialized, something like

You can define your own constructors and map them the the superclass constructor, as shown in this useful guide. I think the "initialize-after" is a fine method and limits the class-specific junk you have to put into gen-class. Since you can still operate on the object via interop...it's a viable and common option.

by
thank you very much both for taking the time to respond. I'm using seesaw in my little testing project but some components are missing (JDesktopPane, JInternalFrame) thus i had to dig through gen-class and got confused a bit (also read the kotka.de guide, it's on the top 3 links when you google gen-class :)
Tom it is definitely more clear now, will look into reify/deftypes next
0 votes
by

You can use :constructors to make your own constructors, but probably a much better solution than doing any of this is to use Seesaw, which is a great Clojure wrapper for Swing.

https://github.com/daveray/seesaw

...