Aus Linux-Magazin 03/2015

Java mit anderen Programmiersprachen mischen

© racorn, 123Rf

Bei Java interessiert nicht nur die Bohne, auch die reiche Vielfalt an Bibliotheken und Frameworks hält die Sprache wach. Und wem der Sinn nach einer Melange steht – Java integriert Fremdsprachen aller Geschmacksrichtungen.

Weder Projektleiter noch Programmierer mixen Sprachen aus Vergnügen. Erstere fürchten Komplexität und erhöhte Ansprüche an ihr Team, Letztere tun lieber Dinge, die sie am besten beherrschen – und das ist bei den meisten genau eine Programmiersprache. Trotzdem nützt in manchem Softwareprojekt ein zweite Sprache mehr, als sie schadet:

  • Basar-Methode: Je umfangreicher die Software, desto lohnender ist es, auf bewährte Routinen zurückzugreifen – auch wenn der recycelte Code in einer anderen Sprache geschrieben ist.
  • Performance: Stehen Low-Level-Zugriffe auf Hardware- und Betriebssystem-Ressourcen an, bieten systemnahe Programmiersprachen mehr und performantere Möglichkeiten.
  • Scripting: Skripte und Skriptsprachen geben Powerusern mächtige Werkzeuge in die Hand. Das ist selbst bei komplexen Enterprise-Anwendungen auf J2EE-Basis so.
  • Integrierbarkeit: Java-Anwendungen können als Teil eines größeren Kontextes fungieren und müssen sich anpassen, beispielsweise in das Look&Feel eines Desktops.

Java ist sehr mächtig, deshalb überrascht es nicht, dass die Sprache die Integration von fremdsprachiger Software für jeden dieser Anwendungsfälle unterstützt. Die folgenden Abschnitte zeigen solche Techniken mit ihren Vor- und Nachteilen.

Ohne C geht nichts

Ein Grund für den Erfolg von Java war, dass es den Entwickler von einigen heiklen Dingen verschont, die unter C und C++ notwendig sind, insbesondere die Verwaltung von dynamischem Speicher. Trotzdem war es den Machern von Java von Anfang an klar, dass es Java in der realen Welt ohne den Zugriff auf die vielfältig vorhandenen C-Bibiliotheken schwer haben würde. Deshalb bot schon die allererste Java-Version mit dem Java Native Interface (JNI) die Möglichkeit zur Integration von C-Code.

Der Zugriff von Java nach C erfolgt über zwei Schichten (Abbildung 1). Oben steht eine dünne Java-Klasse, die nicht viel mehr tut, als die Schnittstelle zu beschreiben und per statischer Methode die nächste Schicht zu laden. Technisch ist diese zweite Schicht eine dynamische Bibliothek (»so« -Datei beziehungsweise DLL unter Windows), die die Java-Aufrufe nach C übersetzt.

Ein Beispiel aus der Praxis: In einem Projekt des Autors [1] ging es darum, die Readline-Bibliothek für Java verfügbar zu machen. Denn ein natives Java-Programm kann die Ausgabe auf Stdout nicht einfangen. Readline darf dagegen eine Zeile ausgeben, der Benutzer kann Bash-ähnlich per Tasten darin navigieren und editieren (Abbildung 2).

Abbildung 1: Der Zugriff von Java auf C-Code passiert über Schichten.

Abbildung 1: Der Zugriff von Java auf C-Code passiert über Schichten.

Abbildung 2: Dank Readline lässt sich die Eingabe des vorherigen Befehls einfach editieren.

Abbildung 2: Dank Readline lässt sich die Eingabe des vorherigen Befehls einfach editieren.

Die Bibliothek ist zum Beispiel in Jython [2] im Einsatz; Jython implementiert die Sprache Python in Java. Listing 1 zeigt einen Teil der Klassendefinition. Aus Designgründen sind alle nativen Methoden »private« , zu der Methode »public String readline(String)« (Zeile 17) gibt es also die private Methode »private native String readineImpl(String)« (Zeile 34). Die Besonderheit der Klasse ist, dass sie nicht abstrakt ist und trotzdem für diese nativen Methoden nur die Schnittstelle vorgibt. Die Methode »load()« (ab Zeilen 10) lädt die native Bibliothek.

Das im JDK mitgelieferte Tool »javah« erzeugt aus dem Bytecode der Klasse die C-Headerdatei »org_gnu_readline_Readline.h« :

javah -classpath $(BUILDDIR) -jni org.gnu.readline.Readline

Einen Auszug zeigt Listing 2. Die eigentliche Implementation besteht dann darin, die übergebenen Java-Typen – etwa »jstring« zu »char*« – in ein Format zu wandeln, das die C-Bibliothek versteht. Hier lauern ein paar Fallstricke, insbesondere auf Nicht-Unicode-Systemen.

Listing 1

Ausschnitt der Klasse Readline.java

01 package org.gnu.readline;
02
03 import java.io.*;
04 import java.util.*;
05
06 public class Readline {
07
08 [...]
09
10   public static final void load(ReadlineLibrary lib) throws UnsatisfiedLinkError {
11     [...]
12     System.loadLibrary(lib.getName()); // might throw UnsatisfiedLinkError
13   }
14
15   [...]
16
17   public static String readline(String prompt, boolean addToHist)
18                throws EOFException, IOException, UnsupportedEncodingException {
19     [...]
20     String line = readlineImpl(prompt);
21     if ((line != null) && (addToHist)) {
22       addToHistory(line);
23     }
24     return line;
25   }
26
27   public static void addToHistory(String line) {
28     [...]
29     addToHistoryImpl(line);
30   }
31
32   [...]
33
34   private native static String readlineImpl(String prompt)
35                        throws EOFException, UnsupportedEncodingException;
36
37   private native static void addToHistoryImpl(String line);
38   [...]
39 }

Listing 2

Ausschnitt der erzeugten C-Headerdatei

01 /* DO NOT EDIT THIS FILE - it is machine generated */
02 #include <jni.h>
03 /* Header for class org_gnu_readline_Readline */
04 [...]
05 /*
06  * Class:     org_gnu_readline_Readline
07  * Method:    initReadlineImpl
08  * Signature: (Ljava/lang/String;)V
09  */
10 JNIEXPORT void JNICALL Java_org_gnu_readline_Readline_initReadlineImpl
11   (JNIEnv *, jclass, jstring);
12
13 /*
14  * Class:     org_gnu_readline_Readline
15  * Method:    cleanupReadlineImpl
16  * Signature: ()V
17  */
18 [...]

Magie der Bilder

In Java implementierte Webanwendungen mit intensiver Bildverarbeitung stützen sich häufig auf Jmagick [3]. Das ist ein Java-C-Interface zu Imagemagick und zugleich ein gutes Beispiel, warum JNI-Programmierung Verdruss bereiten kann. Ein Problem betrifft die Stabilität im Ganzen: Eingebundener fehlerhafter C-Code holt Java-untypische Probleme in die Anwendung, oft Abstürze oder Speicherlecks, die den Applikationsserver nach und nach herunterziehen.

Ein zweites Problem ist bei Imagemagick hausgemacht. Dessen Entwickler verändert öfter die Schnittstellen. Deshalb funktioniert der Jmagick-Wrapper nur genau mit der Imagemagick-Version, für die er gebaut ist. Administratoren müssen entweder eine distributionsfremde Version von Imagemagick installieren oder die Jmagick-Version genau für das aktuelle Imagemagick-ABI bauen – was aber Code-Änderungen an Jmagick nach sich zieht. Die Pflege der Codebasis ist dementsprechend schwierig.

Die Alternative Im4java [4], ebenfalls ein Projekt des Autors, verzichtet deshalb auf die Performancevorteile des JNI, bietet dafür aber eine stabile, objektorientierte Schnittstelle auf Imagemagick. Die Einbindung erfolgt hier über den direkten Aufruf des ausführbaren Imagemagick-Programms, typischerweise »convert« , mittels der »ProcessBuilder« -Klasse. Damit läuft der Java-fremde Code in einem anderen Prozess und richtet niemals Schaden an.

Scripting

Als kompilierter Hochsprache fehlen Java die Vorteile des Scripting. Es gab und gibt deshalb Ansätze, diesen Nachteil auszugleichen. Im Ergebnis funktionieren beide Wege, nämlich das Einbinden von Java in die Skriptsprache – hier ist Java die Fremdsprache – und die in Java integrierte Skriptsprache.

Ein wichtiger Vertreter der ersten Kategorie ist Python in seiner »Jython« -Variante. Das ist die Implementation des Sprachstandards von Python mittels Java. Sie macht den Import und das Verwenden von Java-Packages auch aus Python heraus möglich. Das mag aus Python-Sicht unnötig erscheinen, denn (C)Python verfügt über ein prall gefülltes Paketrepository. Für die Integration von Java-Legacy-Anwendungen ist Jython jedoch eine interessante Option.

Eine Sache für Dickhäuter

Mit Java 1.6 hielt im Package »javax.script« die standardisierte Schnittstelle JSR-223 für den Zugriff auf Skriptsprachen Einzug. Der Mechanismus funktioniert ähnlich einfach wie das Anflanschen von JDBC-Treibern. Listing 3 zeigt das Prinzip anhand der im Zuge des Open-JDK-Projekts entwickelte »ScriptEngine« -Klasse »nashorn« . Der Name ist eine Hommage an Mozillas Rhino [5], die erste Javascript-Implementation in Java. Auch Nashorn implementiert eine Javascript-Runtime (Ecmascript Edition 5.1).

Per »ScriptEngineManager« (Zeile 6) erzeugt der Code eine »ScriptEngine« . Sie besitzt diverse »eval()« -Methoden, die Skripte aus Strings oder aus Dateien ausführen. Das Ganze funktioniert auch dynamisch, denn der »ScriptEngineManager« implementiert einen Autodiscovery-Mechanismus und bietet die Engines an. Findet die Anwendung also im Classpath die »ScriptEngine« für Python, führt sie ohne besonderen Programmieraufwand Python-Skripte aus.

Unter [6] liegt ein zugegebenermaßen sehr altes Repository mit »ScriptEngines« für alle möglichen Skriptsprachen. Für Jython selbst gibt es auch eine nicht-JSR-223-konforme Variante, denn Jython stellt seinen Interpreter als Klasse »org.python.util.PythonInterpreter« bereit. Deren »exec()« -Methode führt den übergebenen Python-Code aus.

Listing 3

Java führt eine Javascript-Datei aus

01 import javax.script.*;
02 import java.io.*;
03
04 public class ExecScript {
05     public static void main(String[] args) throws Exception {
06         ScriptEngineManager manager = new ScriptEngineManager();
07         ScriptEngine engine = manager.getEngineByName("nashorn");
08         engine.eval(new FileReader(args[0]));
09     }
10 }

Ein Skriptsprachen-Archipel

Es gibt kaum eine Skriptsprache, die sich nicht in Java integrieren lässt oder eine native Implementation hat. Allein für Lua findet Google mindestens fünf Projekte, unter anderem Lua-J [7]. Ähnlich wie bei (C)Python und Jython gibt es mit Jruby [8] eine Ruby-Implementation in Java. Entgegen dem nicht totzukriegenden Mythos vom langsamen Java zeigen diverse Tests, dass je nach Situation die Java-Implementierung einer Skriptsprache die Originalimplementation – in aller Regel in C/C++ – schlagen kann.

Andere Sprachen nutzen die JVM gleich als Runtime, das heißt, die Compiler erzeugen Java-kompatiblen Bytecode. Prominente Beispiele sind Clojure (Lisp) und Groovy. Eine lange Liste bietet [9].

Desktop-Integration

Die Desktop-Integration ist ein weiterer Grund, um Java mit einer fremden Sprache zu mischen. Die Technik dazu ist vergleichbar zu der anderer Anwendungsszenarien, auch hier kommt JNI zum Einsatz. Für Linux-Desktops gibt es die Projekte Java-Gnome [10] sowie Qt-Jambi [11]. Da Qt selbst plattformunabhängig ist, ist hier die Chance am größten, dass das Integrieren auch unter Windows funktioniert.

Letztlich erweist sich das Programmieren am Desktop als Grenzfall für sinnvolles Java-Mixing. Denn die Desktopbibliotheken sind ständig in Bewegung, jede Anbindung kann den Ereignissen nur hinterherprogrammieren. Da Java selbst inzwischen über eine sehr gute und plattformunabhängige UI-Bibliothek verfügt, beschränken sich die Vorteile der Desktopnutzung auf das einheitliche Look&Feel – vielfach zu wenig für die Nachteile, die man sich einhandelt.

Kein kalter Kaffee

Geografisch mag Java eine einsame Insel sein, die gleichnamige Programmiersprache gibt sich dagegen weltoffen und integriert so ziemlich jede weltweit gebräuchliche Fremdsprache – und häufig auch umgekehrt. Die Verbreitung von Java auf mobilen Geräten hat der heutigen Sprachfertigkeit sicher Vorschub geleistet. Gleichwohl sollte niemand ohne guten Grund fremdsprachigen Code in Javas Häfen entladen. Insbesondere die Portabilität polyglotter Anwendungen scheint gefährdet, und sie ist ja eine der heißen Argumente für Java.

Der Autor

Bernhard Bablok arbeitet bei der Allianz Managed&Operations Services SE als SAP-HR-Entwickler. Wenn er nicht Musik hört, mit dem Radl oder zu Fuß unterwegs ist, beschäftigt er sich mit Themen rund um Linux, Programmierung und neuerdings Kleincomputer.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 3 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben