Skip to content

Instantly share code, notes, and snippets.

@fr33m0nk
Forked from JulienRouse/clojure-java-interop.clj
Created September 4, 2022 04:19
Show Gist options
  • Save fr33m0nk/f0e141910ee3d1da25d8011c2f55ffd0 to your computer and use it in GitHub Desktop.
Save fr33m0nk/f0e141910ee3d1da25d8011c2f55ffd0 to your computer and use it in GitHub Desktop.
Notes about clojure-java interop for a Montreal Clojure Meetup lightning talk
;Montreal Clojure Meetup 2019
;;sources
; - https://clojure.org/reference/java_interop
; - https://leanpub.com/clojure-java-interop/read_sample
; - http://blog.jayfields.com/2011/12/clojure-java-interop.html
; - https://stackoverflow.com/questions/2181774/calling-clojure-from-java
; - https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2-200
;;Class Access
;All classes in java.lang are automatically imported to every namespace
String
;-> java.lang.String
(defn date? [d] (instance? java.util.Date d))
;-> #'user/date?
(.getEnclosingClass java.util.Map$Entry)
;-> java.util.Map
;;Member Access
;Preferred ways of accessing member and methods
(.toUpperCase "fred") ;Method call on instance
;-> "FRED"
(.getName String) ;Method call on Class
;-> "java.lang.String"
(.-x (java.awt.Point. 1 2)) ;Field instance
;-> 1
(System/getProperty "java.vm.version") ;Static method call
;-> "1.6.0_07-b06-57"
Math/PI ;Static field access
;-> 3.141592653589793
;;Dot Special Form
;behind the scene, they are expanded to the dot special form
(.toUpperCase "fred") => (. "fred" toUpperCase)
(.getName String) => (. (identity String) getName)
(.-x (java.awt.Point. 1 2)) => (. (java.awt.Point. 1 2) -x)
(System/getProperty "java.vm.version") => (. System getProperty "java.vm.version")
Math/PI => (. Math PI)
;dot dot
;You can write this
(.. System (getProperties) (get "os.name"))
;which is equivalent to
(. (. System (getProperties)) (get "os.name"))
;also
(-> (System/getProperties) (.get "os.name"))
;doto
(doto (new java.util.HashMap)
(.put "a" 1)
(.put "b" 2))
;constructor
(String. "Hello") => (new String "Hello")
;;Support for Java in Clojure Library Functions
;Lots of goodies like:
; - function to work with Java arrays
; - let/loop/recur able to work with unboxed primitive
; - unchecked operation for when you want extra speed
; - many more!
; see https://clojure.org/reference/java_interop#primitives
;;Implementing Interfaces and Extending Classes
; proxy -> extend classes and/or interfaces
; reify -> to implement interfaces
; gen-class -> generate statically-named classes and .class files
; see https://clojure.org/reference/java_interop#_implementing_interfaces_and_extending_classes
;;Type Hints and Reflection
;Clojures runtime relies on types a lot.
;When it knows what type an object belongs to, it doesn’t spend extra time on reflection and thus performs faster.
;You can help the compiler to guess types by adding hints.
;A type hint takes its place in front of a variable in function signatures or let bindings.
(defn stop-process
[^Process p]
(when (.isAlive p)
(.destroy p)))
; Use this to help see when you can help the compiler
(set! *warn-on-reflection* true)
;Sometimes you need to express complex type to clojure, you may do it with a string like
(defn ^"[Ljava.lang.String;"
args->command
[args]
(into-array String (map str args)))
;;Coercions
;At times it is necessary to have a value of a particular primitive type.
;These coercion functions yield a value of the indicated type as long as such a coercion is possible:
; - bigdec
; - bigint
; - boolean
; - byte
; - char
; - double
; - float
; - int
; - long
; - num
; - short
;;Some optimization tips
;All arguments are passed to Clojure fns as objects, so there’s no point to putting arbitrary
;primitive type hints on fn args (excepting primitive array type hints, and long and double).
;Instead, use the let technique shown below to place args in primitive locals if they need to participate in
;primitive arithmetic in the body.
;example
(defn foo [n]
(loop [i 0]
(if (< i n)
(recur (inc i))
i)))
(time (foo 100000))
;"Elapsed time: 0.391 msecs"
;100000
(defn foo2 [n]
(let [n (int n)]
(loop [i (int 0)]
(if (< i n)
(recur (inc i))
i))))
(time (foo2 100000))
;"Elapsed time: 0.084 msecs"
;100000
;; A bit unrelated but this gist =>  https://gist.github.com/ericnormand/b29ef113401ad2a6f656c4b701fb08a7#file-valentin-waeselynck-clj
;; show many implementations of the Sieves of Eratosthenes, with many implementation trying to get realy speedy, and some use some Java interop
;; like bitSet, boolean-array (and also transient vector which is not interop but very cool too!)
;;
;; And this link is the reflexion of some Clojurian trying to collaborate to solve the problem https://clojureverse.org/t/eratosthenes-party-time-a-k-a-feedback-wanted-on-this-implementation-of-eratosthenes-sieve/3801/12
(ns awtcolor)
(set! *warn-on-reflection* true)
;;Static Colors
(def black "The color black." java.awt.Color/black)
;; Example of wrapping overloaded Java methods
; One solution is to dispatch based on the type of parameters, but is probably not what you want
(defn construct-color-v1
([rgb] ;<- you use the bits of an int to determine the color, does not display well
(java.awt.Color. rgb))
([rgba has-alpha]
(java.awt.Color. rgba has-alpha))
([r g b]
(cond
(instance? java.awt.color.ColorSpace r)
(let [cspace r
components g
alpha b]
(java.awt.Color. ^java.awt.color.ColorSpace cspace (float-array components) (float alpha)))
(float? r)
(java.awt.Color. ^double r ^double g ^double b)
:default
(java.awt.Color. ^long r ^long g ^long b)))
([r g b a]
(if (double? r)
(java.awt.Color. ^double r ^double g ^double b ^double a)
(java.awt.Color. ^long r ^long g ^long b ^long a))))
;Another take on this is to create many clojure function to wrap the overloaded method
(defn construct-color-with-float
([r g b]
(java.awt.Color. (float r) (float g) (float b)))
([r g b a]
(java.awt.Color. (float r) (float g) (float b) (float a))))
(defn construct-color-with-int
([r g b]
(java.awt.Color. (int r) (int g) (int b)))
([r g b a]
(java.awt.Color. (int r) (int g) (int b) (int a))))
(defn construct-color-with-color-space
[^java.awt.color.ColorSpace cspace components alpha]
(java.awt.Color. cspace (float-array components) (float alpha)))
;; Dealing with code that expect a Java array
;One parameter of the Java getColorComponents method is `comp-array`.
;`comp-array` must be a Java array of float, sp you need to wrap every sequence you pass to the method with float-array
;Note that (float-array (float-array [1 2 3])) is the same as (float-array [1 2 3]) so you don't have to
;worry about user wrapping themselves (or not) their sequences.
;Also use `vec` or `into` to transform a Java array back into the Clojure world
(defn get-color-components
([^java.awt.Color color]
(vec (.getColorComponents color nil)))
([^java.awt.Color color comp-array]
(vec (.getColorComponents color (float-array comp-array))));<- here is float-array
([^java.awt.Color color ^java.awt.color.ColorSpace cspace]
(vec (.getColorComponents color cspace))))
; Not also that in this case, if you provide null instead of an array, it works too
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment