7

The following example function, which uses Clojure's special form for java interop to call a class constructor, causes a reflection warning:

(defn test-reflection-err []
  (new java.util.HashMap {}))

That message reads:

Reflection warning, /Users/ethan/Projects/scicloj/tablecloth.time/src/tablecloth/time/index.clj:26:3 - call to java.util.HashMap ctor can't be resolved.

I've tried placing type hints to avoid this but am not sure where to place them to prevent the reflection error. Does anyone know how to do this?

I've tried:

(defn test-reflection-err []
  (^TreeMap new java.util.HashMap {}))

and

(defn test-reflection-err []
  (doto ^TreeMap (new java.util.HashMap {})))
user2864740
  • 60,010
  • 15
  • 145
  • 220
fraxture
  • 5,113
  • 4
  • 43
  • 83

2 Answers2

5

You need to add a hint to the constructor argument:

(let [^java.util.Map m {}]
  (new java.util.HashMap m))
Lee
  • 142,018
  • 20
  • 234
  • 287
  • Am I doing something wrong? I still get a reflection warning with this technique. It does succeed, with or without the type hint. – Alan Thompson Jan 12 '21 at 15:49
  • @AlanThompson - I'm not sure what `spyxx` does but for me in a clojure 1.10.1 REPL if I do `(set! *warn-on-reflection* true)` then `(new java.util.HashMap {})` raises a reflection warning while `(let [^java.util.Map m {}] (new java.util.HashMap m))` does not. I think your type hint for `m2` is in the wrong place at least and it should be `(def ^java.util.Map m2 {})` – Lee Jan 12 '21 at 16:09
  • I added some updates to the other answer to clarify the unexpected behavior I was seeing. – Alan Thompson Jan 12 '21 at 17:14
  • @Lee wondering if you can explain, maybe even add to the answer, why it is that this works. One thing that confuses me is that the reflection warning says it's the call to the HashMap ctor that it can't resolve. Yet we fix this by adding a type hint to the Map argument that is passed to the HashMap ctor. It'd be great if you could provide some more clarity about what is going on -- time permitting of course ;). – fraxture Jan 14 '21 at 05:54
  • @fraxture HashMap has two single-arity constructors for an int size or a Map. Hence, the type annotation is needed on the arg to explicitly resolve which one to use I think. – Shaun Lebron Feb 07 '23 at 23:18
-2

Unless you really need to eliminate all reflection for some reason, the easiest answer is to put this line in your project.clj:

:global-vars {*warn-on-reflection* false}

I could not get the type hint to eliminate the warning when using an empty Clojure map as a constructor arg. Observe this code:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(dotest
  (newline)
  (spyxx {:a 1}) ; `spyxx` prints expression, type, and value

  (newline)
  (spyxx (java.util.HashMap.))     ; Works
  (spyxx (new java.util.HashMap))  ; Works

  (newline)
  (let [m ^java.util.Map {}]
    (spyxx (new java.util.HashMap m))  ; Reflection Warning
    (spyxx (java.util.HashMap. m)))    ; Reflection Warning

  (newline)
  (let [^java.util.Map m {}]
    (spyxx (new java.util.HashMap m))  ; Works
    (spyxx (java.util.HashMap. m))))   ; Works

(def ^java.util.Map m3 {})
(dotest
  (newline)
  (spyxx m3)
  (spyxx (java.util.HashMap. m3))) ; Works

with the following results. Note that all of them work, but you get a reflection warning where indicated above:

--------------------------------------
   Clojure 1.10.2-alpha1    Java 15
--------------------------------------

Testing tst.demo.core

{:a 1} => <#clojure.lang.PersistentArrayMap {:a 1}>

(java.util.HashMap.)     => <#java.util.HashMap {}>
(new java.util.HashMap)  => <#java.util.HashMap {}>

(new java.util.HashMap m)  => <#java.util.HashMap {}>
(java.util.HashMap. m)     => <#java.util.HashMap {}>

(new java.util.HashMap m)  => <#java.util.HashMap {}>
(java.util.HashMap. m)     => <#java.util.HashMap {}>

m3 => <#clojure.lang.PersistentArrayMap {}>
(java.util.HashMap. m3)   => <#java.util.HashMap {}>

It seems you don't need the empty Clojure map as a constructor arg, and then the problem goes away. You can also make the warning go away by setting *warn-on-reflection* to false (either in project.clj or manually in the code).

If you use a type hint, it is easy to get it in the wrong place where it will be silently ignored.


Update

Consider this extra code:

(dotest
  (newline)
  (set! *warn-on-reflection* false) ; not executed upon parsing
  (let [m4 ^java.util.Map {}]
    (spyxx (new java.util.HashMap m4)) ; Reflection Warning
    (spyxx (java.util.HashMap. m4))))  ; Reflection Warning

(newline)
; vvv executed when parsed before following lines
(set! *warn-on-reflection* false)
(let [m5 ^java.util.Map {}]
  (spyxx (new java.util.HashMap m5))  ; Works
  (spyxx (java.util.HashMap. m5)))    ; Works

The first test generates a reflection warning when parsed (before the test is run), since the parser seems to be evaluating constant expressions (speculation).

The 2nd test does not generate warnings, since Clojure is reading, parsing, and evaluating each form in turn. Thus the call to (set! *warn-on-reflection* false)
is executed to disable warnings before the following lines are read & evaluated.

Alan Thompson
  • 29,276
  • 6
  • 41
  • 48