Aus Linux-Magazin 11/2011

Plattformunabhängig mit Java

© mem-film.de, photocase.com

Sun hatte Java mit dem Anspruch der Plattformunabhängigkeit entwickelt – “Write once, run everywhere” ist das Motto der Virtual Machines, die ihren Bytecode abarbeiten. Die Zubereitung einer überall schmackhaften Anwendung bedarf trotzdem erlesener Zutaten.

Java versucht die Plattformunabhängigkeit dadurch zu erreichen, dass es selbst eine Plattform ist. So schafft es eine Abstraktion gegenüber dem Betriebssystem und der Hardware. Im Prinzip erfüllt Java den Anspruch: Crosskompilieren ist nicht notwendig, Datenstrukturen, Algorithmen und der Binärcode laufen auf jeder Java Virtual Machine (JVM).

Aber Java ist nicht gleich Java: Der Markt bietet Implementierungen mehrerer Hersteller für alle gängigen Betriebssysteme in vielen Versionen. Dazu kommen Exoten wie die Micro Edition [1], die nur ein Subset implementiert. Die Unterschiede zwischen den JVMs sind meist subtiler als die unterschiedliche Schreibweise des Dateipfad-Trenners (»/« versus »\« ). Dieser Artikel gibt dem Entwickler einen Leitfaden, anhand dessen er seine Programme hochportabel gestalten kann.

Das Dateisystem

Die Java-Programmierer wickeln Zugriffe auf Dateien meist über IO-Klassen oder noch abstraktere Klassen ab. Trotzdem müssen manche Programme einen Dateinamen übergeben oder das Homedirectory des Benutzers ermitteln.

Die Abstraktionsklasse für Dateizugriffe ist »java.io.File« . Gibt man einen absoluten Dateinamen beim Instanziieren der Klasse an, so ist dies plattformunabhängig und Java tauscht »/« und »\« passend aus. Über die Variablen »File.separator« oder »System.getProperty(“file.separator”)« lässt sich der Trenner der Plattform ermitteln. Zum Navigieren in Pfaden bietet die File-Klasse Methoden, die überall funktionieren; Beachtung verdient nur der in Unix-Pfaden fehlende Laufwerksbuchstabe.

Seit der neuen Version 7 [2] gestattet es Java erstmalig, auf spezielle Eigenschaften der Dateisysteme wie Berechtigungen, Gruppen und so weiter zuzugreifen. Schon länger sind fortgeschrittene Techniken wie Memory Mapped IO möglich, wie sie der Artikel [3] beschreibt.

Drei grafische Frameworks

Die erste Inkarnation eines GUI in Java war das Abstract Windowing Toolkit [4]. AWT beinhaltet Basiskomponenten wie Buttons oder Textfelder und hilft sie anzuordnen. AWT ist nicht besonders flexibel und die GUI-Elemente beschränken sich auf einen Basissatz. Die JVM bildet eine AWT-Applikation auf das Windowing der Plattform ab, auf der sie läuft.

Programmierer, die sich auf die Standardeinstellungen verlassen, indem sie etwa einen Button mit dem Text, der im Button erscheinen soll, instanziieren, sollten auf allen in Frage kommenden Ablaufplattformen testen, ob die Komponenten wie erwartet sichtbar sind. Eventuell nötige Änderungen betreffen meist nur Größe und Anordnung, aber auch das Verhalten beim Ändern der Fenstergröße unterscheidet sich ab und an.

Die zweite Inkarnation eines GUI-Toolkit war und ist Swing ([5], [6]). Sun hatte es erstmals mit Java 1.2 ausgeliefert. Es erweitert Java um Tabellen und andere etwas anspruchsvollere GUI-Komponenten sowie die so genannten Look-and-Feels. Das Metal-Look-and-Feel zeichnet seither die meisten Java-Applikationen aus. Auch aktuelle Java-Applikationen setzen noch auf diese Technologie.

Die Zukunft gehört, glaubt man Oracle, aber dem Framework Java FX. Nach einer desaströsen Version 1.0, in der Programmierer GUIs über eine getrennte Skriptsprache definieren sollten, machte FX 2.0 alles neu und gut [7]. Vor allem Java-Interfaces zu FX – die bei 1.0 fehlten – existieren jetzt. Look-and-Feel ist aber mehr als der Metal-Look. Es ermöglicht das Umschalten zwischen GUI-Familien. Das Codefragment in Listing 1 zeigt, wie man die von einer Plattform bereitgestellten Look-and-Feels auflistet.

Listing 1

Look-and-Feel auflisten

01 javax.swing.UIManager.LookAndFeelInfo[] landfs =
02 javax.swing.UIManager.getInstalledLookAndFeels();
03 for(i=0; i < landfs.length; i++)
04 System.out.println(landfs[i].getName());

Die Ausgabe des Codefragments unterscheidet sich je nach verwendeter Plattform und Java-Version. Oracles Java 1.6 gibt auf Linux Metal, Nimbus, CDE-Motif und GTK+ aus. Metal und (seit Java 1.6 Release 10) dessen peppigere Alternative Nimbus sind Java-eigen, es gibt sie auf allen Plattformen. CDE-Motif, das das Look-and-Feel von Unix-Workstations der 90er Jahre nachstellt, ebenso. Nicht für plattformneutrale Programmierung eignen sich die Look-and-Feels GTK+, Windows, Windows Classic und Mac OS X (siehe Abbildung 1).

Kommen auch ältere Java-Versionen zum Einsatz, ändern sich die Wahlmöglichkeiten abermals: Blackdown-JDK 1.4 bietet Metal, CDE-Motif und Windows, Sun JDK 1.5 Metal, CDE-Motif und GTK+. Um “Runs everywhere” zu erfüllen, muss der Entwickler entweder alle sechs Versionen testen oder versuchen das Programm auf Metal festzunageln.

Dabei reicht es auch nicht, sich auf das Defaultverhalten zu verlassen (ein Standardwert, der sich je nach Plattform unterscheidet), da es über die Kommandozeilenoption »-Djava -Dswing.defaultlaf=Gewünschte_Version« oder eine Properties-Datei beeinflussbar ist. Das Look-and-Feel auszuwählen, das dem verwendeten Betriebssystem am ähnlichsten sieht, gelingt mit der Zeile:

UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

Bei GTK unter Linux verwendet Java sogar die vom Benutzer ausgewählten Themadateien. Obendrein darf ein Anwender eigene Look-and-Feels entwerfen und damit das Standardverhalten überschreiben, sodass dem Programmierer nur noch das Festnageln auf Metal übrig bleibt, um vor Überraschungen sicher zu sein. Und dass selbst Metal nicht gleich Metal ist, musste der Autor dieses Artikels vor Kurzem am eigenen Leib erleben, als eine Applikation, die er zum Testen bekam, auf Linux und Windows anstandslos lief, auf einem Mac jedoch Probleme beim Redraw machte.

Als Alternative zu Swing schickte IBM im Jahr 2001 das Standard Widget Toolkit [8] ins Rennen. SWT versucht – ähnlich wie AWT – die nativen Elemente der darunterliegenden Plattform heranzuziehen. Dies ist ein Segen für die Performance der Anwendung – wenn SWT geeignete Bibliotheken auf der Plattform vorfindet und damit das GUI so schnell wird wie eine native Applikation. Zum Fluch für die Performance gerät die Sache, wenn es keine Bibliothek vorfindet und jede Aktion emulieren muss.

Abbildung 1: Sieben Mal die gleiche Java-Testanwendung gestartet – die zum Teil deutlichen Ausprägungen der beiden einfachen GUI-Elemente ergeben sich durch sieben verschiedene Look-and-Feels: Metal, Nimbus, CDE-Motif, GTK+, Windows, Windows Classic und Mac OS X (von oben nach unten). Metal, Nimbus, CDE-Motif sind für alle Plattformen verfügbar und darum gut portabel.

Abbildung 1: Sieben Mal die gleiche Java-Testanwendung gestartet – die zum Teil deutlichen Ausprägungen der beiden einfachen GUI-Elemente ergeben sich durch sieben verschiedene Look-and-Feels: Metal, Nimbus, CDE-Motif, GTK+, Windows, Windows Classic und Mac OS X (von oben nach unten). Metal, Nimbus, CDE-Motif sind für alle Plattformen verfügbar und darum gut portabel.

Java Native Interface

Für das plattformunabhängige Layout bereiten AWT und Swing dem Entwickler die gleiche Problem: Er muss sein Programm auf allen Zielplattformen testen. Bei SWT kommt hinzu, dass es das Java Native Interface [9] verwendet. Mit JNI erreicht Javas Idee der Plattformunabhängigkeit dann auch ihr Ende: Das Interface macht Java-Anwendungen die Routinen vorhandener Binärbibliotheken zugänglich. Dazu muss die Java-Klasse die Bibliothek selbst laden, entweder eine Shared Library bei Linux und Unix oder eine DLL unter Windows.

Wer ein JNI-nutzendes Programm für mehrere Plattformen bereitstellen will, sollte die Shared Libraries am besten selbst einpacken – auch wenn es Standardbibliotheken sind –, um Versionskonflikte zu vermeiden. SWT, das wie erwähnt JNI verwendet, liefert die entsprechenden Binärbibliotheken mit. Beim Paketieren der eigenen Anwendung, baut man entweder einzelne Pakete für die unterschiedlichen Plattformen oder packt alle Versionen ein.

Eine interessante Alternative zu JNI ist Java Native Access (JNA, Homepage und Download unter [10]). Statt die Shared Library hinzuzuladen, stellt JNA nur eine kleine Klasse bereit, die generische Bibliotheken lädt, den Rest erledigt der Entwickler in reinem Java. Im Paket finden sich Interfaces für Linux, Free BSD und Windows für 32 und 64 Bit.

Die Matrix

Java-Kompatibilität entfaltet sich zu einem zweidimensionalen Problem: Auf der einen Achse kann man die Versionen – beginnend mit 1.0 – bis zum gerade erschienenen Java 7 [3] auftragen. Auf der zweiten Achse stehen die JVMs verschiedener Hersteller, die wichtigsten sicherlich Oracle, IBM und Open JDK. Hinzu kommt Android mit der Dalvik Engine, die funktional etwas deutlich anderes ist. Der Entwickler, der seinen Code nur einmal schreiben und eine mobile Version seiner Software unters Volk bringen will, muss alle Besonderheiten dieser Plattform beachten, nicht nur beim sowieso anders gearteten GUI.

Übersetzter Java-Bytecode ist aufwärtskompatibel, Oracle gibt sich mit ausgiebigen Kompatibilitätstests große Mühe, dass die mitgelieferten Runtime-Bibliotheken stabil bleiben. Lediglich die Hersteller von 3rd-Party-Bibliotheken ändern gelegentlich APIs, üblicherweise aber erst, nachdem sie ihre Absicht schon in der Vorversion per Deprecated-Markierung angekündigt haben. Das bedeutet, dass eine mit dem Compiler aus Java 1.0 übersetzte Klasse auch unter Java 7 ausführbar bleibt. In der anderen Richtung gilt dies aber nicht!

Entwickler portabler Programme sollten sich daher nicht am neuesten Stand der Technik orientieren. Gerade wer Software übers Internet verteilt, sollte seine Klassen kompatibel zur vorletzen Version übersetzen. Beim Übersetzen mit Javac sorgt die Option »-target Release« dafür, »-source Release« veranlasst den Compiler auch dazu, so zu kompilieren, als wäre er die angegebene Version.

Alt und neu?

Wie andere Sprachen auch ändert Java von Version zu Version das eine oder andere, zum Beispiel das Handling der Generics. Java 5 hatte diese Container-Typangabe für Vektoren oder Ähnliches eingeführt, um für etwas mehr Typsicherheit zu sorgen. In Java 7 änderte sich deren Verhalten wieder.

Wer nun mit gleicher Codebasis Java-ME-Anwendungen für Handys entwickeln will, kämpft damit, dass ME auf Java 1.1 fußt, das die Generics noch nicht kannte. Dann kodiert der Entwickler nach altem Standard und muss mit Warnungen leben. Entwickelt er den Code aber unter Java 5 oder 6 und kommt später auf die Idee, eine Java-ME-Version zu bauen, muss er viel Code umschreiben.

Zum Teil unterscheiden sich die JVMs auch in der Speicherverwaltung, was dazu führen kann, dass die identische Applikation mit dem gleichen den Speicher begrenzenden Parameter unter JVM A einwandfrei läuft und unter JVM B mit vollgelaufenem Speicher abstürzt. 64-Bit-Systeme benötigen zum Beispiel mehr Platz im Heap, weil alle Zeiger doppelt so groß sind. Es ist zwar möglich, entsprechend mehr Speicher zu adressieren, aber auch das Verwalten des Speichers benötigt den doppelten Platz.

Auch bei der Wahl der Entwicklungsumgebung gibt es Feinheiten, die dazu führen, dass derselbe Quellcode in Eclipse funktioniert und sich daraus ein Paket bauen lässt, mit Ant und Javac aber nicht. Der Hintergrund ist, dass Eclipse einen eigenen Java-Compiler verwendet und dieser fehlerhafte Methoden einfach ausblendet. Um den eigenen Testaufwand gering zu halten, supporten daher viele Anbieter kommerzieller, in Java geschriebener Produkte nur genau eine JVM-Version.

Testen, testen, testen

Java-Entwickler plagen bei der plattformunabhängigen Entwicklung viel weniger Sorgen als andere. Die virtuelle Maschine abstrahiert Betriebssystem und Hardware in hohem Maße, selbst für Details wie Byteorder oder Pfadangaben ist gesorgt. Trotzdem erspart dies dem Programmierer nicht, ausgiebig mit verschiedenen Plattformen zu testen. Das müssen nicht alle Java-Versionen auf allen Betriebssystemen sein, aber zumindest die vorletzte. Entwickler von GUI-Applikationen, die Swing als Basis benutzen, sollten zudem ihren Code mit den gängigen Look-and-Feels in Augenschein nehmen. (jk)

Infos

  1. Java ME: http://www.oracle.com/technetwork/java/javame/
  2. Java Platform Standard Edition 7 Documentation: http://download.oracle.com/javase/7/docs/
  3. Carsten Zerbst, “Was Java 7 Neues bringt”: Linux-Magazin 09/11, S. 100
  4. “The AWT in 1.0 and 1.1”: http://java.sun.com/products/jdk/awt/
  5. “Creating a GUI With JFC/Swing”: http://download.oracle.com/javase/tutorial/uiswing
  6. Bernhard Bablok, “Nützliche Komponenten für Swing-Anwendungen – Coffeeshop”: Linux-Magazin 04/06, S. 136
  7. “What Is JavaFX?”: http://download.oracle.com/javafx/2.0/overview/jfxpub-overview.htm
  8. SWT: http://www.eclipse.org/swt/
  9. JNI: http://download.oracle.com/javase/7/docs/technotes/guides/jni/
  10. JNA: https://github.com/twall/jna

Der Autor

Konstantin Agouros arbeitet bei der N.runs AG als Berater für Netzwerksicherheit. Dabei liegt sein Schwerpunkt im Bereich der Telekommunikationsanbieter. Sein Buch “DNS/DHCP” ist bei Open Source Press erschienen.

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