Section courante

A propos

Section administrative du site

Interopérabilité Java

Accès aux classes

Classname
Classname$InnerClass
Classname/N
primitive/N

Les symboles représentant des noms de classes sont résolus en une instance de la classe. Les classes internes ou imbriquées sont séparées de leur classe externe par un $. Les noms de classes pleinement qualifiés sont toujours valides. Si une classe est importée dans l'espace de noms, elle peut être utilisée sans qualification. Toutes les classes de java.lang sont automatiquement importées dans chaque espace de noms.

Un symbole dont la partie «ns» désigne une classe ou un type primitif, et dont la partie «name» est un chiffre unique compris entre 1 et 9, désigne une classe de tableau du type et de la dimension de ce composant. Ajouté dans la version 1.12.

  1. String
  2. -> java.lang.String
  3. (defn date? [d] (instance? java.util.Date d))
  4. -> #'user/date?
  5. (.getEnclosingClass java.util.Map$Entry)
  6. -> java.util.Map
  7. (.getComponentType String/1)
  8. -> java.lang.String

Accès membre

(.instanceMember instance args*)
(.instanceMember Classname args*)
(.-instanceField instance)
(Classname/staticMethod args*)
(Classname/.instanceMethod instance args*)
Classname/staticField

  1. (.toUpperCase "fred")
  2. -> "FRED"
  3. (.getName String)
  4. -> "java.lang.String"
  5. (.-x (java.awt.Point. 1 2))
  6. -> 1
  7. (System/getProperty "java.vm.version")
  8. -> "1.6.0_07-b06-57"
  9. Math/PI
  10. -> 3.141592653589793

Les formes idiomatiques privilégiées pour accéder aux membres d'un champ ou d'une méthode sont indiquées ci-dessus. La forme `instanceMember` fonctionne aussi bien pour les champs que pour les méthodes. La forme `instanceField` est requise s'il existe à la fois un champ et une méthode sans argument portant le même nom.

Depuis Clojure 1.12, `Classname/.instanceMethod` fait référence à une méthode d'instance. Lorsqu'une méthode d'instance est spécifiée, l'instance doit être fournie après le membre et avant les arguments. Lorsqu'une méthode d'instance qualifiée est présente, la classe qualifiante prévaut sur toute information de type supplémentaire concernant l'instance pour la résolution de la méthode d'instance.

Les formes non qualifiées `.` sont développées en appels à l'opérateur point (décrit ci-dessous) lors de l'expansion des macros. Les expansions sont les suivantes :

  1. (.instanceMember instance args*) ==> (. instance instanceMember args*)
  2. (.instanceMember Classname args*) ==> (. (identity Classname) instanceMember args*)
  3. (.-instanceField instance) ==> (. instance -instanceField)

Valeurs de méthodes

Depuis Clojure 1.12, les programmeurs peuvent utiliser des méthodes qualifiées comme des fonctions ordinaires dans un contexte de valeur; le compilateur génère automatiquement la fonction englobante. Utilisées comme valeurs, les méthodes qualifiées ne fournissent que la classe et le nom de la méthode, et ne peuvent donc pas résoudre les méthodes surchargées. Par conséquent, le compilateur génère un appel par réflexion lorsqu'une méthode qualifiée ne peut être résolue en raison d'une surcharge. Les développeurs peuvent ajouter des métadonnées `:param-tags` aux méthodes qualifiées pour spécifier la signature d'une méthode spécifique, la «résolvant». Les métadonnées `:param-tags` sont ignorées pour les méthodes non qualifiées comme `.instanceMember`.

La forme spéciale Dot

(. instance-expr member-symbol)
(. Classname-symbol member-symbol)
(. instance-expr -field-symbol)
(. instance-expr (method-symbol args*)) ou (. instance-expr method-symbol args*)
(. Classname-symbol (method-symbol args*)) ou (. Classname-symbol method-symbol args*)

Forme spéciale.

La forme spéciale «.» est la base de l'accès aux éléments Java. Elle peut être considérée comme un opérateur d'accès aux membres, et/ou interprétée comme «dans la portée de».

Si le premier opérande est un symbole qui correspond à un nom de classe, l'accès est considéré comme portant sur un membre statique de la classe nommée. Notez que les classes imbriquées sont nommées `EnclosingClass$NestedClass`, conformément à la spécification JVM. Sinon, il est présumé qu'il s'agit d'un membre d'instance et le premier argument est évalué pour produire l'objet cible.

Dans le cas particulier de l'appel à un membre d'instance d'une instance de `Class`, le premier argument doit être une expression qui s'évalue en l'instance de la classe ; notez que la forme privilégiée en haut de page développe `Classname` en `(identity Classname)`.

Si le second opérande est un symbole et qu'aucun argument n'est fourni, il est interprété comme un accès à un champ : le nom du champ correspond au nom du symbole et la valeur de l'expression est la valeur du champ, sauf s'il existe une méthode publique sans argument du même nom, auquel cas l'opération est résolue en un appel à cette méthode. Si le second opérande est un symbole commençant par un tiret (-), le symbole membre sera uniquement interprété comme un accès à un champ (jamais comme une méthode d'arité 0) et est à privilégier lorsque tel est l'objectif.

Si le second opérande est une liste ou si des arguments sont fournis, il est interprété comme un appel de méthode. Le premier élément de la liste doit être un symbole simple et le nom de la méthode correspond au nom du symbole. Les arguments, le cas échéant, sont évalués de gauche à droite et transmis à la méthode correspondante, qui est appelée et sa valeur est renvoyée. Si la méthode ne retourne aucune valeur, la valeur de l'expression sera nulle. Notez que l'inclusion du nom de la méthode dans une liste avec ses arguments est facultative dans la forme canonique, mais peut s'avérer utile pour regrouper les arguments dans les macros construites à partir de cette forme.

Notez que les valeurs de retour booléennes seront converties en booléens, les caractères en caractères et les types numériques primitifs en nombres, sauf s'ils sont immédiatement utilisés par une méthode prenant un type primitif.

Les formes d'accès aux membres présentées en début de section sont à privilégier dans tous les cas, sauf dans les macros.

(.. instance-expr member+)
(.. Classname-symbol member+)

  1. member ↠ fieldName-symbol or (instanceMethodName-symbol args*)

Macro. Permet d'accéder aux membres (.) du premier élément du premier argument, puis au membre suivant du résultat,... Par exemple :

  1. (.. System (getProperties) (get "os.name"))

se développe en :

  1. (. (. System (getProperties)) (get "os.name"))

mais est plus facile à écrire, à lire et à comprendre. Voir aussi la macro -> pouvant être utilisée de la même manière :

  1. (-> (System/getProperties) (.get "os.name"))

(doto instance-expr (instanceMethodName-symbol args*)*)

Macro. Évalue l'expression d'instance, puis appelle successivement toutes les méthodes/fonctions avec les arguments fournis sur l'objet résultant, et le renvoie.

  1. (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))
  2. -> {a=1, b=2}

(Classname. args*)
(Classname/new args*)
(new Classname args*)

Forme spéciale.

Les arguments, s'il y en a, sont évalués de gauche à droite et passés au constructeur de la classe nommée par `Classname`. L'objet construit est renvoyé.

Comme illustré, outre la forme spéciale canonique `new`, Clojure prend en charge la macro-expansion spéciale des symboles contenant un point (`.`).

  1. (new Classname args*)

peut être écrit :

  1. (Classname. args*) ;note point final

Cette dernière syntaxe se développe en `(new Classname args)` lors de l'expansion de la macro.

Depuis Clojure 1.12, une forme qualifiée peut également être utilisée (elle n'est pas réécrite lors de l'expansion de la macro) :

(Classname/new args*)

Comme les méthodes, les constructeurs qualifiés Classname/new peuvent être utilisés dans des contextes de valeur et prennent des métadonnées :param-tags.

(instance? Class expr)

Évalue l'expression et vérifie si elle est une instance de la classe. Renvoie vrai ou faux.

(set! (. instance-expr instanceFieldName-symbol) expr)
(set! (. Classname-symbol staticFieldName-symbol) expr)

Forme spéciale d'affectation.

Lorsque le premier opérande est un formulaire d'accès à un membre de champ, l'affectation se fait au champ correspondant. S'il s'agit d'un champ d'instance, l'expression d'instance est évaluée, puis l'expression elle-même.

Dans tous les cas, la valeur de l'expression est renvoyée.

Remarque : il est impossible d'affecter des paramètres de fonction ou des liaisons locales. Seuls les champs Java, les variables, les références et les agents sont modifiables en Clojure.

(memfn method-name arg-names*)

Macro. Génère un code qui crée une fonction prenant en paramètre un objet et ses arguments, puis appelle la méthode d'instance nommée sur cet objet en lui passant les arguments. À utiliser lorsque vous souhaitez traiter une méthode Java comme une fonction de première classe.

  1. (map (memfn charAt i) ["fred" "ethel" "lucy"] [1 2 3])
  2. -> (\ \ \)

Notez qu'il est presque toujours préférable de le faire directement maintenant, avec une syntaxe comme :

  1. (map #(.charAt %1 %2) ["fred" "ethel" "lucy"] [1 2 3])
  2. -> (\ \ \)

(bean obj)

Prend un objet Java et renvoie une implémentation en lecture seule de l'abstraction de carte basée sur ses propriétés JavaBean.

  1. (bean java.awt.Color/black)
  2. -> {:RGB -16777216, :alpha 255, :blue 0, :class java.awt.Color,
  3.     :colorSpace #object[java.awt.color.ICC_ColorSpace 0x5cb42b "java.awt.color.ICC_ColorSpace@5cb42b"],
  4.     :green 0, :red 0, :transparency 1}

Prise en charge de Java dans les fonctions de la bibliothèque Clojure

De nombreuses fonctions de la bibliothèque Clojure définissent une sémantique pour les objets de types Java. `contains?` et `get` fonctionnent avec les `Map`, les tableaux et les chaînes de caractères Java (ces deux dernières avec des clés entières). `count` fonctionne avec les chaînes de caractères, les collections et les tableaux Java. `nth` fonctionne avec les chaînes de caractères, les listes et les tableaux Java. `seq` fonctionne avec les tableaux de références, les itérables et les chaînes de caractères Java. La majeure partie de la bibliothèque étant construite sur ces fonctions, l'utilisation d'objets Java dans les algorithmes Clojure est largement prise en charge.

Implémentation d'interfaces et extension de classes

Clojure permet la création dynamique d'objets implémentant une ou plusieurs interfaces et/ou étendant une classe grâce à la macro `proxy`. Les objets résultants appartiennent à une classe anonyme. Vous pouvez également générer des classes nommées statiquement et des fichiers `.class` avec `gen-class`. Depuis Clojure 1.2, `reify` est également disponible pour l'implémentation d'interfaces.

Des annotations Java peuvent être associées aux classes, constructeurs et méthodes via les métadonnées de `gen-class` et les constructions de types Clojure ; consultez la documentation sur les types de données pour un exemple.

(proxy [class-and-interfaces] [args] fs+)

class-and-interfaces- un vecteur de noms de classes
args- un vecteur (éventuellement vide) d'arguments au constructeur de la superclasse.
f ↠ (name [params*] body) ou (name ([params*] body) ([params+] body) ...)

Macro

Développe le code qui crée une instance d'une classe proxy implémentant la ou les classes/interfaces spécifiées, en appelant les fonctions fournies. Une seule classe, si elle est fournie, doit être appelée en premier. Sinon, la classe Object est utilisée par défaut. Les noms d'interfaces doivent correspondre à des types d'interface valides. Si aucune fonction de méthode n'est fournie pour une méthode de classe, la méthode de la superclasse sera appelée. Si aucune fonction de méthode n'est fournie pour une méthode d'interface, une exception UnsupportedOperationException sera levée si elle est appelée. Les fonctions de méthode sont des fermetures et peuvent capturer l'environnement dans lequel le proxy est appelé. Chaque fonction de méthode prend un premier argument implicite supplémentaire, lié à `this`. Notez que si les fonctions de méthode peuvent être fournies pour redéfinir des méthodes protégées, elles n'ont aucun autre accès aux membres protégés, ni à la classe parente, car ces capacités ne peuvent pas être proxifiées.

Tableaux

Clojure prend en charge la création, la lecture et la modification des tableaux Java. Il est recommandé de limiter l'utilisation des tableaux à l'interopérabilité avec les bibliothèques Java qui les requièrent comme arguments ou les utilisent comme valeurs de retour.

Notez que de nombreuses autres fonctions Clojure interagissent avec les tableaux, notamment via la bibliothèque `seq`. Les fonctions listées ici servent à la création initiale de tableaux, ou à la modification ou à des opérations plus performantes sur les tableaux.

Méthodes à nombre variable d'arguments (vararg)

Les méthodes Java à arguments variables traitent le dernier paramètre `varargs` comme un tableau. Elles peuvent être appelées depuis Clojure en passant explicitement un tableau à la place des arguments variables.

Selon le type des arguments variables, utilisez les constructeurs de tableaux spécifiques pour les types primitifs ou `into-array` pour créer un tableau d'un type spécifique. Consultez la FAQ pour des exemples.

Catégorie Fonctions
Créer un tableau à partir d'une collection existante aclone amap to-array to-array-2d into-array
Prise en charge des tableaux multidimensionnels aget aset to-array-2d make-array
Constructeurs de tableaux spécifiques au type boolean-array byte-array char-array double-array float-array int-array long-array object-array short-array
Conversions de tableaux primitifs booleans bytes chars doubles floats ints longs shorts
Modifier un tableau aset
Traiter un tableau existant aget alength amap areduce

Indications de type

Clojure prend en charge l'utilisation d'indications de type pour aider le compilateur à éviter la réflexion dans les zones critiques du code en termes de performances. En règle générale, il est conseillé d'éviter les indications de type jusqu'à ce qu'un goulot d'étranglement des performances soit identifié. Les indications de type sont des métadonnées placées sur les symboles ou les expressions utilisés par le compilateur. Elles peuvent être placées sur les paramètres de fonction, les noms liés par `let`, les noms de variables (lorsqu'elles sont définies) et les expressions.

  1. (defn len [x]
  2.   (.length x))
  3.  
  4. (defn len2 [^String x]
  5.   (.length x))
  6.  
  7. user=> (time (reduce + (map len (repeat 1000000 "asdf"))))
  8. "Elapsed time: 3007.198 msecs"
  9. 4000000
  10. user=> (time (reduce + (map len2 (repeat 1000000 "asdf"))))
  11. "Elapsed time: 308.045 msecs"
  12. 4000000

Une fois qu'une indication de type est placée sur un identificateur ou une expression, le compilateur tente de résoudre les appels aux méthodes correspondantes lors de la compilation. De plus, il suit l'utilisation des valeurs de retour et infère les types nécessaires, etc. Ainsi, très peu d'indications suffisent pour obtenir une série d'appels entièrement résolus à la compilation. Notez que les indications de type sont inutiles pour les champs statiques ou les valeurs de retour des méthodes statiques, car le compilateur dispose toujours de ces informations.

L'option `warn-on-reflection` (désactivée par défaut) permet au compilateur d'afficher un avertissement lorsqu'il ne parvient pas à résoudre un appel direct :

  1. (set! *warn-on-reflection* true)
  2. -> true
  3.  
  4. (defn foo [s] (.charAt s 1))
  5. -> Reflection warning, line: 2 - call to charAt can't be resolved.
  6. -> #user/foo
  7.  
  8. (defn foo [^String s] (.charAt s 1))
  9. -> #user/foo

Pour les valeurs de retour des fonctions, l'indication de type peut être placée avant le vecteur de paramètres :

  1. (defn hinted-single ^String [])
  2.  
  3. -> #user/hinted-single
  4.  
  5. (defn hinted
  6.   (^String [])
  7.   (^Integer [a])
  8.   (^java.util.List [a & args]))
  9.  
  10. -> #user/hinted

Alias

Clojure fournit des alias pour les types primitifs Java et les tableaux qui ne possèdent pas de représentation classique sous forme de noms de classes Java. Ces types sont représentés conformément à la spécification des descripteurs de champs Java. Par exemple, les tableaux d'octets (byte-array []) ont pour type «[B]».

Alias Clojure Type Java correspondant Description
int int (primitif) Représente un entier primitif Java sur 32 bits.
ints int[] Représente un tableau d'entiers primitifs (int).
long long (primitif) Représente un entier long primitif Java sur 64 bits.
longs long[] Représente un tableau d'entiers longs primitifs (long).
float float (primitif) Représente un nombre à virgule flottante simple précision (32 bits).
floats float[] Représente un tableau de nombres flottants simple précision.
double double (primitif) Représente un nombre à virgule flottante double précision (64 bits).
doubles double[] Représente un tableau de nombres flottants double précision.
void void Indique l'absence de valeur de retour pour une méthode.
short short (primitif) Représente un entier primitif Java sur 16 bits.
shorts short[] Représente un tableau d'entiers primitifs de type short.
boolean boolean (primitif) Représente une valeur logique vraie ou fausse.
booleans boolean[] Représente un tableau de valeurs booléennes primitives.
byte byte (primitif) Représente un entier primitif Java sur 8 bits.
bytes byte[] Représente un tableau d'octets (byte).
char char (primitif) Représente un caractère Unicode (16 bits).
chars char[] Représente un tableau de caractères Unicode.
objects Object[] Représente un tableau d'objets Java génériques.

param-tags

Depuis Clojure 1.12, les développeurs peuvent fournir des métadonnées `:param-tags` aux méthodes qualifiées afin de spécifier la signature d'une méthode spécifique, la « résolvant ». Les métadonnées `:param-tags` sont un vecteur de zéro ou plusieurs étiquettes : `[... étiquette ...]`. Une étiquette est une valeur de métadonnées `:tag` valide, telle que décrite ci-dessus. Chaque étiquette correspond à un paramètre de la signature souhaitée (l'arité doit correspondre au nombre d'étiquettes). Les paramètres dont le type n'est pas surchargé peuvent utiliser le caractère de substitution `_` à la place de l'étiquette. Lorsque vous fournissez des métadonnées `:param-tags` à une méthode qualifiée, ces métadonnées doivent permettre au compilateur de la résoudre en une méthode unique lors de la compilation.

Une nouvelle syntaxe de lecture de métadonnées `^[ ... ]` associe des métadonnées `:param-tags` aux symboles membres, de la même manière que `^tag` associe des métadonnées `:tag` à un symbole.

Prise en charge des types primitifs Java

Clojure prend en charge la manipulation et l'arithmétique performantes des types primitifs Java dans les contextes locaux. Tous les types primitifs Java sont pris en charge : `int`, `float`, `long`, `double`, `boolean`, `char`, `short` et `byte` :

Plutôt que d'écrire ce code Java :

  1. static public float asum(float[] xs){
  2.   float ret = 0;
  3.   for(int i = 0; i < xs.length; i++)
  4.     ret += xs[i];
  5.   return ret;
  6. }

vous pouvez écrire ceci en Clojure :

  1. (defn asum [^floats xs]
  2.   (areduce xs i ret (float 0)
  3.     (+ ret (aget xs i))))

Le code résultant s'exécute à la même vitesse (avec l'option `-server` de Java).

L'avantage principal est qu'aucune modification particulière n'est requise lors de la conception initiale du code. Bien souvent, ces optimisations sont superflues. Si une portion de code constitue un goulot d'étranglement, il est possible de l'accélérer par de légères modifications.

  1. (defn foo [n]
  2.   (loop [i 0]
  3.     (if (< i n)
  4.       (recur (inc i))
  5.       i)))
  6.  
  7. (time (foo 100000))
  8. "Elapsed time: 0.391 msecs"
  9. 100000
  10.  
  11. (defn foo2 [n]
  12.   (let [n (int n)]
  13.     (loop [i (int 0)]
  14.       (if (< i n)
  15.         (recur (inc i))
  16.         i))))
  17.  
  18. (time (foo2 100000))
  19. "Elapsed time: 0.084 msecs"
  20. 100000

Les fonctions prennent en charge de façon limitée les arguments primitifs et les types de retour : les annotations de type pour `long` et `double` (uniquement ceux-ci) génèrent des surcharges de type primitif. Notez que cette fonctionnalité est restreinte aux fonctions d'arité inférieure ou égale à 4.

Ainsi, une fonction définie comme :

  1. (defn foo ^long [^long n])

Les deux prennent et renvoient des valeurs de type primitif long (les invocations avec un argument encapsulé et en fait tout objet entraînent un cast et une délégation à la surcharge de type primitif).

Conversions

Conversions de types primitifs

Il est parfois nécessaire d'obtenir une valeur d'un type primitif particulier. Ces fonctions de conversion produisent une valeur du type indiqué, pour autant qu'une telle conversion soit possible : bigdec bigint boolean byte char double float int long num short

Conversion d'interfaces fonctionnelles

Les programmes Java émulent les fonctions à l'aide d'interfaces fonctionnelles, qui possèdent une unique méthode.

Les développeurs Clojure peuvent appeler des méthodes Java prenant des interfaces fonctionnelles en leur passant des fonctions ayant l'arité correspondante. Le compilateur Clojure convertit implicitement les fonctions vers l'interface fonctionnelle requise en construisant un adaptateur lambda. Vous pouvez convertir explicitement une fonction vers une interface fonctionnelle en spécifiant le nom de la liaison dans une liaison `let`, par exemple pour éviter la construction répétée d'adaptateurs dans une boucle : `(let [^java.util.function.Predicate p even?] ...)`.

Depuis Clojure 1.12, toutes les implémentations IDeref (delay, future, atom,...) implémentent directement l'interface Supplier.

Quelques conseils d'optimisation

Prise en charge des flux Java

Les collections Clojure implémentent les interfaces de collections Java, offrant un accès aux flux et aux séparateurs. Les vecteurs persistants Clojure implémentent un séparateur personnalisé prenant en charge les flux parallèles.

Depuis la version 1.12, Clojure fournit des fonctions permettant d'interagir avec les flux de manière idiomatique ; toutes ces fonctions se comportent de façon analogue à leurs équivalents Clojure.

Toutes ces opérations sont des opérations de flux terminal (elles consomment le flux).

Prise en charge XML simplifiée

La distribution inclut une prise en charge XML simplifiée, disponible dans le fichier src/clj/clojure/xml.clj. Tous les noms de ce fichier appartiennent à l'espace de noms clojure.xml.

(parse source)

Analyse et charge la source, qui peut être un fichier, un flux d'entrée ou une chaîne de caractères représentant un URI. Renvoie une arborescence de la structure clojure.xml/element, contenant les clefs :tag, :attrs et :content, ainsi que les fonctions d'accès tag, attrs et content.

  1. (clojure.xml/parse "/Users/rich/dev/clojure/build.xml")
  2. -> {:tag :project, :attrs {:name "clojure", :default "jar"}, :content [{:tag :description, ...

Appeler Clojure depuis Java

Le paquet clojure.java.api fournit une interface minimale pour initialiser l'accès à Clojure depuis d'autres langages JVM. Il offre notamment :

Les interfaces (IFns) offrent un accès complet aux API de Clojure. Vous pouvez également accéder à toute autre bibliothèque écrite en Clojure, après avoir ajouté son code source ou sa version compilée au classpath.

L'API Java publique pour Clojure comprend les classes et interfaces suivantes :

Toutes les autres classes Java doivent être considérées comme des détails d'implémentation, et les applications doivent éviter de s'y fier.

Pour rechercher et appeler une fonction Clojure :

  1. IFn plus = Clojure.var("clojure.core", "+");
  2. plus.invoke(1, 2);

Les fonctions de clojure.core sont chargées automatiquement. Les autres espaces de noms peuvent être chargés via require :

  1. IFn require = Clojure.var("clojure.core", "require");
  2. require.invoke(Clojure.read("clojure.set"));

Les instructions IFn peuvent être passées à des fonctions d'ordre supérieur, par exemple, l'exemple ci-dessous passe inc à map :

  1. IFn map = Clojure.var("clojure.core", "map");
  2. IFn inc = Clojure.var("clojure.core", "inc");
  3. map.invoke(inc, Clojure.read("[1 2 3]"));

La plupart des instructions `if` en Clojure font référence à des fonctions. Cependant, certaines font référence à des valeurs de données n'étant pas des fonctions. Pour accéder à ces dernières, utilisez `deref` au lieu d'appeler la fonction :

  1. IFn printanLength = Clojure.var("clojure.core", "*print-length*");
  2. IFn deref = Clojure.var("clojure.core", "deref");
  3. deref.invoke(printLength);


Dernière mise à jour : Lundi, le 2 février 2026