una objetos lista instanciar dinamicamente crear clase constructor clojure

constructor - objetos - Clojure: creando una nueva instancia a partir del nombre de la clase String



instanciar una clase en java (4)

En Clojure, dado un nombre de clase como una cadena, necesito crear una nueva instancia de la clase. En otras palabras, ¿cómo implementaría new-instance-from-class-name en

(def my-class-name "org.myorg.pkg.Foo") ; calls constructor of org.myorg.pkg.Foo with arguments 1, 2 and 3 (new-instance-from-class-name my-class-name 1 2 3)

Estoy buscando una solución más elegante que

  • llamar al método newInstance de Java en un constructor de la clase
  • usando eval, load-string, ...

En la práctica, lo usaré en clases creadas usando defrecord. Entonces, si hay alguna sintaxis especial para ese escenario, estaría muy interesado.



Dado que ''nuevo'' es una forma especial, no estoy seguro de que pueda hacerlo sin una macro. Aquí hay una forma de hacerlo usando una macro:

user=> (defmacro str-new [s & args] `(new ~(symbol s) ~@args)) #''user/str-new user=> (str-new "String" "LOL") "LOL"

Mira el comentario de Michal sobre las limitaciones de esta macro.


En Clojure 1.3, defrecord definirá automáticamente una función de fábrica usando el nombre de registro con "->" precedido. De manera similar, una variante que toma un mapa será el nombre del registro que se añade con "map->".

user=> (defrecord MyRec [a b]) user.MyRec user=> (->MyRec 1 "one") #user.MyRec{:a 1, :b "one"} user=> (map->MyRec {:a 2}) #user.MyRec{:a 2, :b nil}

Una macro como esta debería funcionar para crear una instancia a partir del nombre de cadena del tipo de registro:

(defmacro newbie [recname & args] `(~(symbol (str "->" recname)) ~@args))


Hay dos buenas maneras de hacer esto. Lo que es mejor depende de la circunstancia específica.

La primera es la reflexión:

(clojure.lang.Reflector/invokeConstructor (resolve (symbol "Integer")) (to-array ["16"]))

Es como llamar (new Integer "16") ... incluya cualquier otro argumento ctor que necesite en el vector to-array. Esto es fácil, pero más lento en tiempo de ejecución que el uso de new sugerencias de tipo suficiente.

La segunda opción es lo más rápida posible, pero un poco más complicada, y utiliza eval :

(defn make-factory [classname & types] (let [args (map #(with-meta (symbol (str "x" %2)) {:tag %1}) types (range))] (eval `(fn [~@args] (new ~(symbol classname) ~@args))))) (def int-factory (make-factory "Integer" ''String)) (int-factory "42")

El punto clave es evaluar el código que define una función anónima, como lo make-factory . Esto es lento , más lento que en el ejemplo de reflexión anterior, así que solo hazlo con la menor frecuencia posible, como una vez por clase. Pero una vez hecho esto, tiene una función normal de Clojure que puede almacenar en algún lugar, en una var como int-factory en este ejemplo, o en un hash-map o en un vector dependiendo de cómo la vaya a usar. En cualquier caso, esta función de fábrica se ejecutará a la velocidad de compilación completa, puede ser incorporada por HotSpot, etc. y siempre se ejecutará mucho más rápido que el ejemplo de reflexión.

Cuando se trata específicamente de clases generadas por deftype o defrecord , puede omitir la lista de tipos, ya que esas clases siempre tienen exactamente dos factores, cada uno con diferentes aridades. Esto permite algo como:

(defn record-factory [recordname] (let [recordclass ^Class (resolve (symbol recordname)) max-arg-count (apply max (map #(count (.getParameterTypes %)) (.getConstructors recordclass))) args (map #(symbol (str "x" %)) (range (- max-arg-count 2)))] (eval `(fn [~@args] (new ~(symbol recordname) ~@args))))) (defrecord ExampleRecord [a b c]) (def example-record-factory (record-factory "ExampleRecord")) (example-record-factory "F." "Scott" ''Fitzgerald)