2012-02-06 19 views
10

En Clojure, ¿cómo usar una clase Java que está almacenada en una variable?En Clojure, ¿cómo usar una clase Java dinámicamente?

¿Cómo debo corregir el siguiente código?

(def a java.lang.String) 
(new a "1"); CompilerException java.lang.IllegalArgumentException: Unable to resolve classname: a 

¿Y por qué funciona bien?

(def a str) 
(a "1") 
+2

Pensé que esto ha aparecido antes y de hecho lo ha hecho: ver [Clojure: crear una instancia nueva a partir del nombre de la clase String] (http: // stackoverflow.com/q/3748559/232707) con una gran respuesta de Chouser que menciona tanto 'clojure.lang.Reflector/invokeConstructor' como otro enfoque, una especie de término medio entre" estático + rápido "y" dinámico + lento "(podría llámalo "muy dinámico + lento una vez, estático + rápido después"), que bien puede interesarte. –

Respuesta

8

La solución más elegante es escribir construct que hace lo mismo que new pero es capaz de recibir una clase dinámica:

(defn construct [klass & args] 
    (clojure.lang.Reflector/invokeConstructor klass (into-array Object args))) 
(def a HashSet) 
(construct HashSet '(1 2 3)); It works!!! 

Esta solución supera la limitación de la respuesta @mikera 's (ver comentarios).

Gracias especiales a @Michał Marczyk que me hicieron saber invokeConstructor respondiendo a otra pregunta mía: Clojure: how to create a record inside a function?.

Otra opción es almacenar la llamada al constructor como una función anónima. En nuestro caso:

(def a #(String. %1)) 
(a "111"); "111" 
6

El problema es que Clojure implementa la interoperabilidad de Java utilizando un número de formas especiales:

user=> (doc new) 
------------------------- 
new 
Special Form 
    Please see http://clojure.org/special_forms#new 
nil 

esto significa básicamente la sintaxis "normal" Clojure es alterado para permitir construcciones más prácticas cuando se llama a Java. Como solución reflexión ingenua a su dinámica de Java necesita, puede aprovechar eval:

user=> (def a String) ; java.lang package is implicitly imported 
#'user/a 
user=> `(new ~a "test") ; syntax quote to create the correct form 
(new java.lang.String "test") 
user=> (eval `(new ~a "test")) ; eval to execute 
"test" 

La misma estrategia funciona con todas las otras formas de interoperabilidad especiales, como method invocation.


EDIT: mirar también a la answer de @mikera una alternativa más rendimiento a través de la API de reflexión de Java.

7

Cuando se define una de esta manera, se obtiene un var contiene un java.lang.Class

(def a java.lang.String) 

(type a) 
=> java.lang.Class 

A continuación, tiene 2 opciones:

A: Construir la nueva instancia dinámicamente al encontrar el constructor de Java utilizando la API de reflexión. Tenga en cuenta que como señala Yehonathan es necesario utilizar la clase exacta se define en la firma constructora (una subclase no funcionará, ya que no va a encontrar la firma correcta):

(defn construct [klass & args] 
    (.newInstance 
    (.getConstructor klass (into-array java.lang.Class (map type args))) 
    (object-array args))) 

(construct a "Foobar!") 
=> "Foobar!" 

B: Construir el uso de Clojure interoperabilidad de Java, que requerirá una eval:

(defn new-class [klass & args] 
    (eval `(new ~klass [email protected]))) 

(new-class a "Hello!") 
=> "Hello!" 

Tenga en cuenta que el método a es considerablemente más rápido (alrededor de 60 veces más rápido en mi máquina), creo que principalmente porque evita la sobrecarga de invocar el compilador Clojure para cada sentencia eval.

+0

Existe un problema con 'construct' al pasar como argumento una clase derivada de la clase definida en la firma. Por ejemplo '(construir HashSet '(8))' causa una excepción mientras '(nuevo HashSet' (8))' no. – viebel

+0

Opción B: se ve bien. ¿Existen limitaciones/inquietudes debido al uso de 'eval'? – viebel

+1

Posibles desventajas de eval: Cuidado con los valores proporcionados por el exterior: podría haber un riesgo de seguridad de inyección de código. También eval tiene una sobrecarga adicional ya que invoca el compilador Clojure. También puede conducir a un código "inteligente" que es difícil de mantener si no tiene cuidado. En general, eval está bien si se usa con precaución. – mikera

Cuestiones relacionadas