© Excellent backgrounds/Shutterstock.com
Multilingual: Clojure in Java-Applikationen

Mehrsprachig glücklich


Viele JVM-Sprachen nutzen das Java-Ökosystem und haben dadurch den Vorteil, Java-Bibliotheken ohne großen Aufwand nutzen zu können. In umgekehrter Richtung sieht es nicht immer so einfach aus. Gerade dynamische Sprachfeatures lassen sich in Java-Code nur umständlich verwenden. Diese Artikelserie geht auf die populärsten JVM-Sprachen und ihre Integration in Java-Applikationen ein. Den Anfang macht der Lisp-Dialekt Clojure.

Ein Großteil aller Systeme, die auf der Java Virtual Machine ihren Dienst tun, ist in Java geschrieben. Trotzdem war die JVM schon immer offen für weitere Sprachen. Solange diese gültigen Bytecode erzeugen, können sie alle Vorteile der JVM nutzen. Mit der Einführung von invokedynamic wurde die Tür zur effizienten polyglotten Entwicklung schließlich ganz aufgestoßen [1]. Inzwischen existieren mehrere Dutzend JVM-Sprachen [2], die mehr oder minder bekannt sind. Eine der interessantesten davon ist Clojure, ein Lisp-Dialekt, der funktionale Programmierung mit all seinen Vorteilen bereitstellt. Will man nicht gleich sein ganzes Projekt in dieser Sprache erneut entwickeln, stellt sich unweigerlich die Frage, wie sich Clojure-Code sauber in Java-Projekte integrieren lässt.

Java Interop – Minimales Interface reicht aus

Clojure stellt ab Version 1.6 ein minimales API zur Kommunikation mit Java zur Verfügung, das aus dem Interface clojure.lang.IFn und der Klasse clojure.java.api.Clojure besteht. In Listing 1 ist ein einfaches Beispiel aus der offiziellen Dokumentation [3] dargestellt. Mit Clojure.var wird dort die Funktion + geladen. Anschließend ruft invoke diese Funktion auf und gibt ein Ausgabeobjekt zurück.

Auch komponierte Funktionen sind möglich, indem die einzelnen Funktionen zuerst geladen und dann mit übergeben werden, wie im Beispiel von map und inc. Der Aufruf zeigt auch, wie Datenstrukturen als String übergeben werden können. Der String "[1 2 3]" wird mit der Clojure-Funktion read dynamisch in einen Clojure-Vektor umgewandelt.

Diese einfache Art und Weise, Clojure einzubinden, führt aber zu vielen Baustellen im Code, an denen Funktionen geladen werden. Das möchte man vermeiden, da es die Codebasis schwieriger zu warten und zu lesen macht. Zusätzlich zum vorhandenen Boilerplate-Code, der beim Laden der Funktion entsteht, ist nämlich auch noch ein Cast auf den richtigen Typen notwendig.

Listing 1: Clojure in Java integrieren

Ifn plus = Clojure.var("clojure.core", "+"); Long result = (Long) plus.invoke(1, 2); System.out.println(result); // 3 // Geht auch mit Funkionen Ifn map = Clojure.var("clojure.core", "map"); Ifn inc = Clojure.var("clojure.core", "inc"); map.invoke(inc, Clojure.read("[1 2 3]"));

Gemeinsame Typen nutzen

Wie sieht eine saubere Lösung dieses Problems aus? Dazu hilft es, zuerst die verschiedenen Datentypen zu betrachten, die Clojure bereitstellt [4]. In diesem Zug wird nur auf die zu Java nativ kompatiblen Typen eingegangen, da für diese keine spezielle Konvertierungslogik notwendig ist. Clojure unterstützt alle primitiven Datentypen von Java ohne explizite Konvertierung. Intern benutzt es Long für die Darstellung natürlicher Zahlen. Sollte eine Zahl größer als der Wertebereich von Long werden, wird automatisch auf den Datentyp BigInt gemappt:

=> (instance? Long 1) true => (class 1000000000000000000) java.lang.Long => (class 10000000000000000000) clojure.lang.BigInt

Strings und Characters sind beide direkt aus Java entnommen und besitzen keine eigene Implementierung:

...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang