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.
- String
- -> java.lang.String
- (defn date? [d] (instance? java.util.Date d))
- -> #'user/date?
- (.getEnclosingClass java.util.Map$Entry)
- -> java.util.Map
- (.getComponentType String/1)
- -> java.lang.String
Accès membre
|
(.instanceMember instance args*) (.instanceMember Classname args*) (.-instanceField instance) (Classname/staticMethod args*) (Classname/.instanceMethod instance args*) Classname/staticField |
- (.toUpperCase "fred")
- -> "FRED"
- (.getName String)
- -> "java.lang.String"
- (.-x (java.awt.Point. 1 2))
- -> 1
- (System/getProperty "java.vm.version")
- -> "1.6.0_07-b06-57"
- Math/PI
- -> 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 :
- (.instanceMember instance args*) ==> (. instance instanceMember args*)
- (.instanceMember Classname args*) ==> (. (identity Classname) instanceMember args*)
- (.-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+) |
- 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 :
- (.. System (getProperties) (get "os.name"))
se développe en :
- (. (. 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 :
- (-> (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.
- (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 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 (`.`).
- (new Classname args*)
peut être écrit :
- (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.
- (map (memfn charAt i) ["fred" "ethel" "lucy"] [1 2 3])
- -> (\ \ \)
Notez qu'il est presque toujours préférable de le faire directement maintenant, avec une syntaxe comme :
- (map #(.charAt %1 %2) ["fred" "ethel" "lucy"] [1 2 3])
- -> (\ \ \)
| (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.
- (bean java.awt.Color/black)
- -> {:RGB -16777216, :alpha 255, :blue 0, :class java.awt.Color,
- :colorSpace #object[java.awt.color.ICC_ColorSpace 0x5cb42b "java.awt.color.ICC_ColorSpace@5cb42b"],
- :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.
- (defn len [x]
- (.length x))
-
- (defn len2 [^String x]
- (.length x))
-
- user=> (time (reduce + (map len (repeat 1000000 "asdf"))))
- "Elapsed time: 3007.198 msecs"
- 4000000
- user=> (time (reduce + (map len2 (repeat 1000000 "asdf"))))
- "Elapsed time: 308.045 msecs"
- 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 :
- (set! *warn-on-reflection* true)
- -> true
-
- (defn foo [s] (.charAt s 1))
- -> Reflection warning, line: 2 - call to charAt can't be resolved.
- -> #user/foo
-
- (defn foo [^String s] (.charAt s 1))
- -> #user/foo
Pour les valeurs de retour des fonctions, l'indication de type peut être placée avant le vecteur de paramètres :
- (defn hinted-single ^String [])
-
- -> #user/hinted-single
-
- (defn hinted
- (^String [])
- (^Integer [a])
- (^java.util.List [a & args]))
-
- -> #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` :
- Les variables locales liées par `let` ou `loop` peuvent être de types primitifs, le type de leur forme initiale étant alors inféré (éventuellement primitif).
- Les formes récursives redéfinissant les variables locales primitives le font sans boxing et vérifient l'identité des types primitifs.
- Les opérations arithmétiques (+, -, *, /, inc, dec, <, <=, >, >= ...) sont surchargées pour les types primitifs ayant la même sémantique.
- Les fonctions `aget` et `aset` sont surchargées pour les tableaux de types primitifs.
- Fonctions `aclone` et `alength` pour les tableaux de types primitifs.
- Constructeurs pour les tableaux de types primitifs : `float-array`, `int-array`,...
- Indications de type pour les tableaux de types primitifs : `^int`, `^float`,...
- Opérations de conversion `int`, `float`, etc. : elles produisent des types primitifs lorsque le consommateur accepte un type primitif.
- La fonction de conversion `num` convertit les types primitifs pour forcer l'arithmétique générique.
- Fonctions de conversion de tableaux : `int`, `long`, etc. : elles produisent `int[]`, `long[]`,...
- Un ensemble d'opérations « non vérifiées » pour des performances maximales, mais potentiellement dangereuses, sur les entiers (`int`/`long`) : `unchecked-multiply`, `unchecked-dec`, `unchecked-inc`, `unchecked-negate`, `unchecked-add`, `unchecked-subtract`, `unchecked-remainder`, `unchecked-divide`.
- Une variable dynamique pour échanger automatiquement les opérations sûres avec les opérations non vérifiées : `*unchecked-math*`.
- Les macros amap et areduce permettent de traiter fonctionnellement (c'est-à-dire de manière non destructive) un ou plusieurs tableaux afin de produire respectivement un nouveau tableau ou une nouvelle valeur agrégée.
Plutôt que d'écrire ce code Java :
vous pouvez écrire ceci en Clojure :
- (defn asum [^floats xs]
- (areduce xs i ret (float 0)
- (+ 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.
- (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
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 :
- (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
- Tous les arguments des fonctions Clojure sont passés sous forme d'objets. Il est donc inutile d'ajouter des annotations de type primitif arbitraires aux arguments (à l'exception des annotations de type pour les tableaux primitifs, ainsi que pour les types `long` et `double`, comme indiqué). Utilisez plutôt la technique `let` présentée pour entreposer les arguments dans des variables locales primitives s'ils doivent participer à des opérations arithmétiques primitives dans le corps de la fonction.
- `(let [foo (int bar)] ...)` est la méthode correcte pour obtenir une variable locale primitive. N'utilisez pas `^Integer`,...
- N'utilisez pas les opérations arithmétiques non vérifiées à la hâte, sauf si vous souhaitez tronquer les résultats. HotSpot optimise efficacement la gestion des dépassements de capacité, ce qui génère une exception au lieu d'une troncature silencieuse. Dans un exemple typique, cela représente un gain de vitesse d'environ 5 %, ce qui est très appréciable. De plus, les personnes qui liront votre code ne savent pas si vous utilisez les opérations non vérifiées pour éviter la troncature ou pour améliorer les performances ; il est donc préférable de les réserver au premier cas et de les commenter si c'est pour la seconde raison.
- Il est généralement inutile d'optimiser une boucle externe ; cela peut même être contre-productif car vous représentez des types primitifs devant être reconditionnés pour devenir des arguments de l'appel interne. La seule exception concerne les avertissements de réflexion : vous devez les supprimer dans tout code fréquemment appelé.
- Presque systématiquement, lorsqu'on présente un code optimisé avec des indications, la version plus rapide en contient beaucoup moins que l'originale. Si une indication n'apporte aucune amélioration, supprimez-la.
- Beaucoup semblent croire que seules les opérations non vérifiées effectuent des calculs primitifs ; c'est faux. Lorsque les arguments sont des variables locales primitives, les opérateurs + et * classiques effectuent des calculs primitifs avec vérification de dépassement de capacité : c'est rapide et sûr.
- Ainsi, la méthode la plus simple pour des calculs rapides consiste à ne pas toucher aux opérateurs et à s'assurer que les littéraux et variables locales sources sont primitifs. Les opérations arithmétiques sur les types primitifs produisent des types primitifs. Si vous utilisez une boucle (ce qui est probable si vous devez optimiser), assurez-vous d'abord que ses variables locales sont des types primitifs. Ainsi, si vous produisez accidentellement un résultat intermédiaire encapsulé, une erreur se produira lors de la récursion. Ne résolvez pas cette erreur en forçant le type du résultat intermédiaire ; identifiez plutôt l'argument ou la variable locale qui n'est pas primitif.
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.
- (stream-seq! stream) ↠ seq
- (stream-reduce! f [init-val] stream) ↠ val
- (stream-transduce! xf f [init-val] stream) ↠ val
- (stream-into! to-coll [xf] stream) ↠ to-coll
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.
- (clojure.xml/parse "/Users/rich/dev/clojure/build.xml")
- -> {: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 :
- La possibilité d'utiliser les espaces de noms Clojure pour localiser une variable quelconque et renvoyer son interface clojure.lang.IFn.
- Une méthode pratique, read, pour lire des données à l'aide du lecteur edn de Clojure.
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 :
- clojure.java.api.Clojure
- clojure.lang.IFn
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 :
- IFn plus = Clojure.var("clojure.core", "+");
- plus.invoke(1, 2);
Les fonctions de clojure.core sont chargées automatiquement. Les autres espaces de noms peuvent être chargés via require :
- IFn require = Clojure.var("clojure.core", "require");
- 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 :
- IFn map = Clojure.var("clojure.core", "map");
- IFn inc = Clojure.var("clojure.core", "inc");
- 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 :
- IFn printanLength = Clojure.var("clojure.core", "*print-length*");
- IFn deref = Clojure.var("clojure.core", "deref");
- deref.invoke(printLength);