15 Andere Sprachen in Verbindung mit Java

Wenden wir uns nun Java und seiner Verbindung anderen Programmiersprachen und Technikwelten zu. Es geht also im Kern um die Beziehung von Java und so genanntem nativen Code. Als wichtiger Vertreter soll der direkte Programmiervorfahre von Java behandelt werden, C/C++. Daneben werden wir uns mit JavaScript und anderen Scriptsprachen im Web beschäftigen und wie sie in Verbindung mit Java genutzt werden können.

15.1 C/C++ - Unterschiede und Gemeinsamkeiten sowie die Einbindung

Java hat als direkter C/C++-Ableger viele Gemeinsamkeiten mit seinem Programmierahn. Wir wollen die entscheidenden Gemeinsamkeiten und vor allem die wichtigsten Unterschiede und Vorteile von Java etwas genauer betrachten.

15.1.1 Unterschiede und Gemeinsamkeiten

Die Gemeinsamkeiten von Java und C/C++ beginnen auf der Syntaxebene. Man kann es so ausdrücken: Was in beiden Sprachen von der Funktion her vollkommen gleich ist, wird auch in der Syntax gleich sein. Sun wollte damit Java für C/C++-Progammierer schon auf den ersten Blick vertraut und verständlich machen und die Einarbeitungszeit kurz halten. Diese Vertrautheit birgt jedoch das Risiko, dass geübte C/C++-Progammierer gleichermaßen viele Dinge so in Java versuchen, wie sie es aus C/C++gewohnt sind, selbst wenn Java dafür andere Mechanismen vorgesehen hat.

Bei der Java-Sicherheit haben wir ja schon gesehen, dass Java im Gegensatz zu C/C++ keine Pointer, keine Mehrfachvererbung, keine Zeigerarithmetik oder ungeprüfte Typumwandlung benutzt. Außerdem entfällt die Speicherallokation. Lassen Sie uns die wichtigsten Unterschiede jetzt im Einzelnen ein bisschen genauer beleuchten.

Pointer

Java besitzt keinen expliziten Pointer-Typ, sondern verwendet dafür immer Referenzen. Diese impliziten Referenzen werden für Objekte, Variablenzuweisungen, an Methoden weitergegebene Argumente und Array-Elemente verwendet. Eigentlich ist es eine kleine Lüge, wenn man Referenzen und Pointer als absolut verschiedene Dinge bezeichnet, denn streng genommen sind sie im Wesentlichen das Gleiche. Auch Java muss ja auf irgendeine Weise Speicher zuweisen und zugewiesenen Speicher durch Verweise ansprechen können. Daher sind diese Referenzen, auch wenn sie anders bezeichnet werden, im Grunde genommen nur die Javaversion von Zeigern. >

Mit einem kleinen, aber feinen (und in Bezug auf Sicherheit extrem wichtigen) Unterschied - man kann auf Referenzen keine Pointer-Arithmetik anwenden und muss es durch das ausgefeilte Referenzkonzept auch nicht. Durch den Wegfall der Pointerarithmetik ist es ebenso nicht möglich, Objekt- und Array-Referenzen in Integerwerte zu casten oder die Größen der primitiven Datentypen in Bytes zu berechnen.

Dieses ausgefeilte Referenzkonzept von Java ermöglicht ebenfalls das einfache Erstellen von Strukturen wie verknüpften Listen, und zwar ohne die Verwendung von expliziten Pointern! Vielmehr wird lediglich ein verknüpfter Listenknoten mit Variablen, die auf den nächsten und vorherigen Knoten zeigen, erstellt. Um dann Elemente in die Liste einzufügen, werden diese Variablen anderen Knotenobjekten zugewiesen.

Arrays

Arrays sind in Java Klassenobjekte, und Referenzen auf Arrays und ihre Inhalte werden wie gerade gesehen nicht durch Pointer-Arithmetik, sondern durch explizite Referenzen realisiert. Die Array-Grenzen werden streng eingehalten, was in Hinblick auf die Sicherheit Java extrem zuverlässig in diesem Bereich macht. In der Praxis bedeutet dies, dass ein Versuch, über das Ende eines Arrays hinaus zu lesen, zu einem Kompilier- oder Laufzeitfehler führt. Wie bei allen Objekten wird durch Weitergabe eines Objekts an eine Methode eine Referenz auf das Originalobjekt erzeugt, also im Falle von Arrays eine Referenz auf das Original-Array. Verändert sich der Inhalt der Array-Referenz, ändert sich auch das Objekt des Original-Arrays.

Die Beseitigung von Arrays aus dem Speicher läuft wie bei allen Objekten automatisch über den Garbage Collector.

Es gibt bei Java-Arrays noch eine Besonderheit gegenüber C/C++. In Java gibt es keine multidimensionalen Arrays wie C und C++, sondern es müssen Arrays erstellt werden, die dann als Inhalt andere Arrays enthalten.

Strings

Strings sind in C und C++ Zeichenketten, die immer mit einem Nullzeichen (\0) enden. Strings werden in C und C++ genauso wie andere Arrays behandelt. Deshalb muss man auch dort extrem aufpassen, dass man mit Pointern das Ende einer Zeichenketten nicht überschreitet. In Java gibt es als rein objektorientierte Sprache ja nur Objekte und deshalb werden natürlich auch Strings als Objekte behandelt. Damit werden alle Methoden, die auf Strings angewandt werden, einen String nur als komplette Einheit behandeln. Strings werden nicht durch ein Nullzeichen (\0) beendet, und das Ende eines Strings kann dementsprechend nicht versehentlich überschritten werden.

Speichermanagement

In C/C++ muss sich ein Programmierer immerfort manuell um die Speicherverwaltung kümmern. Dies beinhaltet das Beschaffen von Speicher für Variablen, Funktionen usw.. Noch entscheidender ist, dass er sich gleichermaßen um das explizite Freigeben von Speicher für jedes Objekt kümmern muss. Hier sind neben fehlerhafter Pointerverwaltung die meisten Probleme bei C/C++-Programmen zu finden. Es wird einfach regelmäßig vergessen, allokierten Speicher mit free() wieder freizugeben.

In Java erfolgt das gesamte Speichermanagement automatisch. Speicher wird beim Erstellen eines Objekts mit dem new-Operator automatisch zugewiesen. Die Garbage Collection - ein Papierkorb, der zur Laufzeit immer als Hintergrundprozess mitläuft - gibt den jeweils zugewiesenen Speicher wieder frei, wenn auf das Objekt keine Referenz mehr vorhanden ist. C-Funktionen wie malloc() und free() existieren in Java nicht mehr. Sollte es aus irgendeinem Grund notwendig sein, manuell den Speicher freizugeben, kann man die Garbage Collection aus Java aufrufen, um die Freigabe des von einem Objekt belegten Speichers zu erzwingen. In diesem Fall werden wie bei der automatischen Freigabe alle Referenzen auf das Objekt entfernt (Zuweisung von Variablen, die es auf Null halten, Entfernen des Objekts von Arrays usw.). Bei der nächsten Ausführung gibt der Java-Papierkorb den zugeteilten Speicher zurück.

Vererbung und Schnittstellen

Sowohl C/C++ als auch Java arbeiten mit dem Mechanismus der Vererbung. Im Gegensatz zu C++ unterstützt Java jedoch keine Mehrfachvererbung. Das erscheint erst einmal als eine große Einschränkung, macht Java-Programme allerdings stabil und leicht wartbar. Dafür bietet Java ja einen ähnlich leistungsfähigen Mechanismus, der dennoch die Wartbarkeit erhält - das Konzept der Schnittstellen, die es in dieser Form in C++ nicht gibt. Objekte können in Java eine beliebige Anzahl an Schnittstellen oder abstrakten Klassen implementieren. Die Java-Schnittstellen sind wie IDL-Schnittstellen (Interface Description Language) aufgebaut.

Eine Schnittstelle ist in der Java-Sprache eine Sammlung von Methodennamen ohne konkrete Definition sowie Konstanten. Obwohl eine einzelne Java-Klasse nur genau eine Superklasse haben kann (und genau eine haben muss!), können in einer Klasse mehrere Schnittstellen implementiert werden.

Datentypen

Die Standardmenge der Datentypen aus C/C++ wird innerhalb von Java um die Datentypen boolean und byte erweitert. In Java haben alle primitiven Datentypen auf allen Plattformen feste Größen und Eigenschaften. In Java wird also die Größe einfacher Datentypen genau spezifiziert, und auch, wie sich die Arithmetik (Berechnungsvorschriften) gegenüber diesen Datentypen verhält. Fast alle wichtigen Prozessoren unterstützen diese Java-Spezifikation und außerdem haben die Java-Bibliotheken portierbare Schnittstellen für die wichtigsten Plattformen integriert. Unter C/C++ gibt es vorzeichenlose Datentypen. Java kennt sie mit Ausnahme von char, einer vorzeichenlosen 16-Bit-Ganzzahl, nicht. Datentypen entstehen in Java ausschließlich durch Verwendung von Klassendefinitionen. Kombinationen von Datentypen wie short int gibt es in Java nicht. Die C/C++-Schlüsselwörter struct, union und typedef gibt es nicht mehr, nur noch Klassendefinitionen.

Bzgl. der Umwandlung von Datentypen in andere Datentypen ist Java bedeutend restriktiver als C/C++. Besonders, wenn es um automatische Umwandlung geht, wo unter C/C++ dabei Informationsgehalt verloren gehen kann. Automatisch wird in Java nur umgewandelt, wenn kein Risiko eines Informationsverlustes besteht (also im Wesentlichen nur dann, wenn der Zieltyp aus gleich vielen oder mehr Byte besteht). Alle anderen Umwandlungen müssen explizit angefordert werden. Insbesondere sind die primitiven Datentypen und Objekte gegen eine direkt Umwandlung ineinander abgeschottet. Dafür gibt es in Java Methoden und spezielle Wrapper-Klassen, um Werte zwischen Objekten und Primitivtypen zu konvertieren.

In Java werden einfache Datentypen immer als Call-by-Value und Referenztypen immer als Call-by-Reference behandelt.

Java kennt keine nicht-initialisierten Variablen, sondern jede Variable erhält einen wohldefinierten Anfangszustand. Die C/C++-typischen Probleme mit nicht initialisierten Variablen können nicht vorkommen.

In Java erhalten alle Referenzdatentypen den Wert null. Dieses reservierte Schlüsselwort weist darauf hin, dass eine Variable nicht auf ein Objekt verweist.

Boolesche Werte

In C/C++ beruhen Entscheidungen auf Grund boolescher Werte innerhalb von Programmsteuerungsanweisungen wie if, while, for oder do genauso wie in Java (wo sie syntaktisch vollkommen gleich sind) auf den beiden möglichen Ausprägungen true und false dieser booleschen Werte. Der entscheidende Unterschied ist, dass Java einen tatsächlichen booleschen Wert (true oder false) als Typ benötigt, während für C und C++ true oder false »0« oder »ungleich 0« sind. Dabei bedeutet »ungleich 0« eine beliebige Ganzzahl. Somit ist es in Java nicht möglich, ein Casting von einem boolean-Typ in einen nicht-boolean-Typ durchzuführen.

Der Unicode-Standard

Java benutzt für die Darstellung eines char-Typs immer 16 Bit, C/C++ wie die meisten konventionellen Programmiersprachen nur 8 Bit. Dies ist darauf zurückzuführen, dass C/C++ den ASCII-Zeichensatz verwendet und Java die neuere Unicode-Codierung, die eine 16-Bit-Codierung für Zeichen benötigt. Auf dieser Unicode-Codierung beruht die Fähigkeit von Java, landesspezifische Zeichen darzustellen, denn selbstverständlich lassen sich in 16 Bit mehr Zeichen codieren als in 8 Bit. >

package und import

Das Packet-Konzept von Java gibt es nicht in C/C++. Dementsprechend gibt es dort die Anweisungen package und import nicht. In Java dient die Anweisung package dazu, mehrere Klassen zu einem Paket zusammenzufassen. Durch die Anweisung import werden einzelne Pakete in einem Programm verfügbar gemacht. Die C/C++-Anweisung include zur Einbinden von Klassen/Dateien wird durch das Statement import und die Paketstruktur in Java völlig überflüssig. Der Compiler und der Interpreter wissen immer exakt, wo sich die einzelnen Klassen befinden.

Der new-Operator

Die meisten Operatoren werden in Java genauso wie in C/C++ verwendet. Eine Besonderheit ist der Operator new, um die Instanz einer Klasse zu erzeugen. Diesen new-Operator gibt es in C/C++ und in Java. Das Schlüsselwort new zum Erstellen eines neuen Objekts wird jedoch in Java viel besser in das Gesamtkonzept integriert. Der Befehl new hat in Java die gleiche Bedeutung wie der Befehl malloc() unter C. Mit malloc() wird in C der Speicher für die Instanz einer struct allokiert. new hat in Java ebenfalls die Aufgabe einer dynamischen Allokierung von Speicher.

Wie wir bei der Objektorientierung von Java gesehen haben, definiert man zur Einhaltung des streng objektorientierten Konzeptes so genannte Metaklassen. Die einzigen Objekte von Metaklassen sind Klassen, und sie vererben die Klassenmethoden. Diese werden nicht an das Objekt selbst, sondern an die Objektklasse vererbt (Methoden zum Erzeugen von Objekten oder zur Initialisierung von Klassenvariablen).

Neben der Konsistenz ist ein weiterer Vorteil des Metaklassenkonzepts, dass die Klassenmethoden in den Klassen überladen werden können. Gibt es für alle Klassen nur eine einzige Metaklasse, dann gibt es für alle Klassen dieselben Klassenmethoden.

C++ verfügt über keine Klassenmethoden und unterstützt das Metaklassenkonzept nicht. An ihre Stelle treten Systemfunktionen, wie etwa besagter new-Operator. Er fällt jedoch unter dem objektorientierten Denkansatz in C++ einfach vom Himmel, während er in Java bedeutend besser in das logische Konzept integriert ist.

Operatoren im Allgemeinen

Java unterstützt fast alle Standard-C-Operatoren. Diese Operatoren haben in Java die gleichen Prioritäten und Verknüpfungen wie in C. Es gibt jedoch Ausnahmen. So unterstützt Java nicht den Kommaoperator, wie er in C existiert, um zwei Ausdrücke in einem Ausdruck zusammenzufassen. Da Java keine Pointer-Arithmetik ermöglicht, fehlen die zugehörigen Operatoren zur Referenzierung * und Dereferenzierung &, sowie der Operator sizeOf. Weiterhin sind die Zeichen `[]' und `.' keine Operatoren in Java. Dafür gibt es in Java einige Operatoren, die eine spezielle Bedeutung haben oder sogar gänzlich neu sind:

Überladung von Operatoren

Im Gegensatz zu C++ können in Java keine Operatoren überladen werden.

Länge der Argumentenliste

In C/C++ gibt es Mechanismen für wahlweise Argumente oder Argumentlisten mit variabler Länge für Funktionen. In Java müssen alle Methodendefinitionen eine festgelegte Anzahl von Argumenten haben.

Befehlszeilenargumente

Auch Befehlszeilenargumente (Programmaufruf-Parameter) verhalten sich in Java anders als in C/C++. Das erste Element im Argumentenvektor (argv[0]) ist in C/C++ immer der Name des Programms. In Java ist das erste Argument das erste der zusätzlichen Argumente. Der Argumentvektor ist also in Java um ein Feld verschoben, und das erste Element im Argumentenvektor (argv[0]) kann leer sein.

Kommentare

Java hat neben den zwei in C/C++ vorhandenen Kommentartypen /* Eingeschlossener Kommentar */ und // noch einen weiteren Kommentartyp, der vom Dokumentationstool javadoc verwendet wird. Er beginnt mit /** und endet mit */.

Präprozessor

In Java gibt es keinen Präprozessor. Dementsprechend gibt es in Java die C/C++-Anweisung #defines, #include oder #ifdef nicht mehr.

Makros

Java besitzt keine Makros.

Konstanten

Java realisiert Konstanten ausschließlich mit dem Schlüsselwort final. Jede Variable, die mit dem Schlüsselwort final deklariert wird, wird damit zu einer Konstanten. Der Wert dieser Variablen muss direkt bei der Initialisierung mit angegeben werden und kann während des Programmverlaufs nicht mehr geändert werden. Das C/C++-Schlüsselwort const gibt es nicht mehr.

Exceptions

Die Ausnahmebehandlung ist zwar eine aus C/C++ stammende Spracheigenschaft von Java. Jedoch ist sie dort nicht integraler Bestandteil der Basissprache (also C), sondern nur unter C++ aufgesetzt. In Java gehören Exceptions zum grundlegenden Sprachkonzept.

Objektorientiertheit

Java ist kompromisslos objektorientiert, während man das von C/C++ nicht behaupten kann. Bereits beim new-Operator haben wir gesehen, dass Java durch das Metaklassenkonzept im Kontrast zu C++ das objektorientierte Konzept einhält. Es gibt keine globalen Variablen oder Methoden. Der Verzicht auf globale Variablen und Methoden oder den Typ von Konstanten, die unter C/C++ durch das Schlüsselwort const realisiert werden - in Java ist das Schlüsselwort nicht mehr vorhanden - und die Unterbindung von Funktionen, die nicht mit einer Klasse verbunden sind (gibt es in C/C++), lassen die Aussage zu, dass Java im Gegensatz zu C++ kompromisslos objektorientiert durchkonzipiert ist.

15.2 Die Verbindung von Java und nativem Code

Java besitzt ein Verfahren, das als die Einbindung von nativem Code bezeichnet wird. Das Wort nativ bezieht sich dabei auf Sprachen wie C und C++. Allerdings können auch andere Sprachen in Java eingebunden werden, was jedoch seltener geschieht. Im Prinzip werden alle Java-Methoden, die in einer anderen Sprache als Java implementiert sind, als native Methoden bezeichnet. Wir werden uns auf C/C++ als Beispiel beschränken, denn das Einbinden von nativem Code ist nicht unproblematisch. Es gibt zwar einige Argumente dafür: Bereits existierende Codes können schnell umgearbeitet und eingebunden werden oder sogar über die Methodenschnittstelle als komplettes Programm einer anderen Sprache in Java eingebunden werden, die Ausnutzung anwendungsspezifischer Stärken anderer Sprachen, Performance oder in anderen Sprachen vorhandenes Know-how der Programmierer. Die Nachteile sind jedoch genauso unzweifelhaft und überwiegen. Einige wesentliche Nachteile einer Verwendung von nativem Code sind die folgenden:

Falls trotz der massiven Bedenken native Code-Elemente mit Java verbunden werden sollen, werden Funktionen, die in anderen Sprachen geschrieben wurden, in dynamisch ladbare Objekte kompiliert (shared libraries auf UNIX-Plattformen und DLLs auf Windows-Plattformen) und in einen Methodenaufruf von Java eingebaut. Zur Laufzeit werden diese Funktionen dann genau wie alle Java-Methoden aufgerufen.

15.2.1 Konventioneller Einbau von C-Code in Java

Wir wollen in diesem Abschnitt die Vorgänge beleuchten, wie C-Code auf einem konventionellen Weg - wir werden das JNI als Alternative gegenüberstellen - über die Schnittstelle der nativen Methode in Java eingebaut wird. Man kann den Einbau eines C-Programms mittels der Schnittstelle der nativen Methode in Java in sieben hintereinander folgende Schritte gliedern.

1. Schreiben Sie den Java-Code mit dem Aufruf der nativen Methoden.
2. Kompilieren Sie den Java-Code wie gewohnt mit dem Java-Compiler.
3. Erstellen Sie die .h-Datei mit dem JDK-Tool javah.
4. Erstellen Sie mit javah eine Stub-Datei.
5. Schreiben Sie die C-Funktion als eine .c-Datei.
6. Erstellen Sie aus der .h-Datei, der Stub-Datei und der .c-Datei, die Sie in den Schritten 3, 4 und 5 erstellt haben, ein dynamisch ladbares Objektmodul (Shared Libraries auf UNIX-Plattformen und DLLs auf Microsoft Windows 95/NT-Plattformen).
7. Starten Sie das Programm.

Schritt 1: Schreiben des Java-Codes

Um eine native C-Funktion zu deklarieren und aufzurufen, stellen Sie das Schlüsselwort native einer nativen Methode voran. Das Schlüsselwort native signalisiert dem Klassenlader, dass die betreffende Methode der Klasse in einer anderen Programmiersprache geschrieben ist. Diese Definition gibt nur die Signatur der nativen Methode an, jedoch nicht ihre Implementierung. Die konkrete Implementierung wird in einer separaten C-Quelldatei vorgenommen.

Beispiel:

class TestNativeMethode
{ 
public native void NativeMethode(); 
..... weitere Methoden .........
}

Der C-Code, der die native Methode implementiert, muss zu einem dynamisch ladbaren Objekt kompiliert und in die Klasse geladen werden, die dieses benötigt. Erst das Laden des Objekts in die Java-Klasse verbindet die Implementierung der nativen Methode mit ihrer Definition.

Das folgende Codestück lädt eine entsprechende Bibliothek namens nativeMethodenbibliothek:

static 
{ 
System.loadLibrary("nativeMethodenbibliothek"); 
}

Dieses Codestück muss nach der Definition der nativen Methode in der Klasse platziert werden.

Als Nächstes erstellen Sie in einer separaten Quelldatei namens Main.java eine Java-Klasse Main, die die Klasse TestNativeMethode instanziert und die native Methode NativeMethode() aufruft.

Beispiel für das Hauptprogrammklasse für die Klasse TestNativeMethode:

class Main 
{ 
public static void main(String args[]) 
{ 
new TestNativeMethode().NativeMethode(); 
} 
}

Aus dem obigen Codestück ist ersichtlich, dass Sie eine native Methode genau wie eine echte Java-Methode aufrufen. Alle Parameter, die ggf. an die Methode weitergegeben werden sollen, können in Klammern hinter dem Methodennamen eingefügt werden.

Schritt 2: Das Kompilieren des Java-Codes

Hierzu gibt es nicht viel zu sagen. Benutzen Sie den Compiler wie gewohnt, um den Code, den Sie in Schritt 1 geschrieben haben, zu kompilieren.

Schritt 3: Erstellen der .h-Datei

Wir werden das JDK-Tool javah in Anhang A noch besprechen. Der javah-Generator erstellt C-Header (Erweiterung .h) und C-Quelldateien für die angegebenen Klassen. Diese so generierten Dateien enthalten alle notwendigen Informationen zur Implementierung von nativen Methoden, beispielsweise #include- und #define-Anweisungen, typedef-Konstrukte u.ä. Normalerweise generiert javah nur ein Headerfile für die angegebenen Klassen. Innerhalb dieses Headerfiles wird eine C-struct deklariert, die alle notwendigen Felder enthält, die mit den Instanzfeldern der ursprünglichen Java-Klassen korrespondieren. Innerhalb dieser Header-Datei wird ebenfalls bereits eine Funktion für jede native Methode definiert, die in der zugehörigen Quelldatei implementiert werden muss. Verwenden Sie also in diesem Schritt das javah-Werkzeug, um aus der Klasse mit der nativen Methode (in unserem Beispiel TestNativeMethode.java) eine C-Header-Datei (.h-Datei) zu erstellen. Die Header-Datei enthält wie gesagt dann eine Struktur, die auf der C-Seite die Java-Klasse und eine C-Funktionsdefinition für die Implementation der in der Java-Klasse definierten nativen Methode einbindet. Für die genauen Befehlsparameter sei auf den Anhang mit der Besprechung der JDK-Tools und die Online-Dokumentation verwiesen.

javah platziert normalerweise die resultierende .h-Datei in das gleiche Verzeichnis wie die Klassendatei und erstellt die Header-Datei mit demselben Namen wie der Klassenname mit der Erweiterung .h. In unserem Beispiel wird also eine Datei namens TestNativeMethode.h erzeugt. Der Inhalt dieser Datei sieht dann ungefähr so aus:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <native.h>
/* Header for class TestNativeMethode */
#ifndef _Included_ TestNativeMethode
#define _Included_ TestNativeMethode
typedef struct ClassTestNativeMethode {
    char PAD;  /* ANSI C requires structures to have a least one member */
} ClassTestNativeMethode;
HandleTo(TestNativeMethode);
extern void TestNativeMethode _ NativeMethode(struct HTestNativeMethode *);
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif

Die Header-Datei enthält die Definition für eine Struktur mit der Bezeichnung der Klasse. Die Glieder dieser Struktur spiegeln die Glieder der entsprechenden Java-Klasse wider. Die Felder in der Struktur entsprechen den Instanzvariablen in der Klasse. Da aber in unserem Beispielfall keine Instanzvariablen vorhanden sind, ist das Feld in der Struktur leer. Die Glieder der Struktur ermöglichen der C-Funktion den Zugriff auf die Instanzvariablen der Java-Klasse.

Die Header-Datei enthält zusätzlich zur C-Struktur, die die Java-Klasse widerspiegelt, auch noch den Prototyp einer C-Funktion (in unserem Beispiel extern void TestNativeMethode_NativeMethode(struct HTestNativeMethode *);). Dies ist der Prototyp der C-Funktion, die Sie in Schritt 5 implementieren müssen. Wenn Sie die eigentliche C-Funktion schreiben, müssen Sie genau diesen Funktions-Prototyp benutzen. Wenn in unserem Beispiel noch weitere native Methoden in der Klasse enthalten wären, so würden auch deren Funktionsprototypen hier stehen. Der Name der C-Funktion wird aus dem Java-Paketnamen, dem Klassennamen und dem Namen der nativen Methode hergeleitet. Daher wird in unserem Beispiel aus der nativen Methode NativeMethode() in der Klasse TestNativeMethode der Funktionsname TestNativeMethode_ NativeMethode(). Da kein Paketname vorhanden ist, fehlt dieser beim Namen des Prototyps im Beispiel.

Die C-Funktion besitzt einen Parameter, den die in der Java-Klasse definierte native Methode nicht hat. Dieser Parameter ist in C++ in etwa mit der Variablen this in Java zu vergleichen.

Schritt 4: Erstellen einer Stub-Datei

Verwenden Sie nun javah mit den entsprechenden Optionen, um aus der Java-Klasse eine Stub-Datei zu erstellen. Stub-Dateien sind C-Dateien, die neben der Header-Datei zusätzliche, notwendige Rumpffunktionen für die Einbindung von nativen Methoden in der Java-Umgebung enthalten. Das Tool generiert standardmäßig eine C-Datei in dem aktuellen Verzeichnis, deren Name identisch zu dem im Aufruf spezifizierten Klassennamen ist. Wenn dieser Klassenname ein Paket enthält, enthalten die C-Dateien sämtliche Komponenten des Paketnamens. Allerdings werden diese nicht durch Punkte, sondern durch Unterstriche getrennt.

Bei Angabe der entsprechenden Option zur Erzeugung von Stub-Dateien generiert in unserem Beispiel eine C-Datei mit Namen TestNativeMethode.C, die schematisch wie folgt aussieht:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <StubPreamble.h>
/* Stubs for class TestNativeMethode */
... weitere Angaben ...

Auf das genaue Aussehen der Stub-Datei braucht nicht unbedingt detaillierter eingegangen zu werden, denn sie wird ja automatisch generiert und Sie müssen sie nur zu einer dynamischen Bibliothek in Schritt 6 kompilieren.

Schritt 5: Schreiben der C-Funktion

Für diesen Schritt muss natürlich vorausgesetzt werden, dass Sie sich soweit in C auskennen, um eine entsprechende C-Datei zu erstellen. Es ist nicht Sinn eines Java-Buchs, auf C-Programmierung einzugehen1. An dieser Stelle finden Sie die wichtigsten Informationen zum Schreiben des eigentlichen Code für die native Methode in einer C-Quelldatei:

Schritt 6: Erstellen einer dynamisch ladbaren Bibliothek

Dieser Schritt ist betriebssytemspezifisch und nutzt zudem die entsprechenden Befehle Ihrer Plattform, um den C-Code, den Sie in Schritt 5 erstellt haben, in eine DLL (dynamisch gelinkte Bibliothek) oder Objektdatei zu verwandeln. Es sprengt natürlich den Rahmen dieses Buchs, auf die diversen plattformspezifischen Besonderheiten und vor allem die vielfältigen C/C++-Entwicklungstools einzugehen. Der Befehl zum Erstellen eines dynamisch ladbaren Objektmoduls sollte aber auf jeden Fall sowohl die C-Datei mit der Implementierung der nativen Methode als auch die in Schritt 4 erstellte Stub-Datei umfassen.

Schritt 7: Der Programmstart

Hierzu gibt es wieder nicht viel zu sagen. Benutzen Sie den Java-Interpreter wie gewohnt, um Ihr Programm zu starten. Sollte Sie eine ähnliche Liste von Fehlermeldungen wie die folgende erhalten, dann haben Sie ihre Bibliothek nicht in Ihren Pfad mit aufgenommen:

java.lang.NullPointerException 
at java.lang.Runtime.loadLibrary(Runtime.java) 
at java.lang.System.loadLibrary(System.java) 
at ListDirJava.(TestNativeMethode.java:5) 
at java.lang.UnsatisfiedLinkError ListDirCat Main.main(Main.java:3)

Der Pfad für die Bibliotheken ist eine Liste mit Verzeichnissen, die das Laufzeitsystem von Java beim Laden von dynamisch gelinkten Bibliotheken durchsucht. Das Verzeichnis, in dem sich die Bibliothek befindet, muss in diesem Pfad enthalten sein.

15.2.2 Das Java Native Interface (JNI)

Wenn man von der Einbindung von nativem Quelltext in Java redet, muss man seit der Version 1.1 das Java Native Interface (JNI) erwähnen. Wir werden es der konventionellen Verbindung von Java und C-Code gegenüberstellen und sehen, dass es viele Gemeinsamkeiten gibt. Es handelt sich um die Standard-Programmierschnittstelle zum Schreiben von nativen Java-Methoden und der Verbindung von der JVM und nativen Applikationen bzw. Sourcequellen. Das primäre Ziel dabei ist die binäre Kompatibilität von nativen Methoden-Libraries über alle JVM-Implementationen hinweg.

Das JNI-Konzept vereinfacht die Verwendung von nativem Code gegenüber der frühen Einbindung von nativem Code in Java. Es gilt jedoch immer noch, dass diese Einbindung kompliziert ist und wesentliche Kenntnisse der Programmiersprache voraussetzt, deren nativen Code man verwenden möchte. Außerdem gelten auch unter JNI im Wesentlichen die gleichen Argumenten gegen die Verwendung von nativem Code wie bei einer konventionellen Einbindung.

Das JNI-Design

Wir wollen hier kurz wesentlichen Aspekte des JNI-Designs anreißen. Native Codezugriffe erfolgen im JNI-Konzept über die Verwendung von JNI-Funktionen. Diese sind über einen Schnittstellenpointer verfügbar. Ein solcher Schnittstellenpointer im JNI-Konzept ist ein Pointer auf einen Pointer. Dieser Pointer zeigt auf ein Array von Pointern, die wiederum jeweils auf eine Schnittstellenfunktion zeigen.

Das JNI-Interface ist wie eine virtuelle C++-Funktionstabelle oder ein COM-Interface organisiert. Der wesentliche Vorteil dieses Konzepts ist, dass JNI-Namesräume unabhängig von dem nativen Code werden.

Native Methoden empfangen die JNI-Schnittstellenpointer als ein Argument. Weitergehende Informationen finden Sie unter /guide/jni/index.html in der JDK-Dokumentation.

15.2.3 JNI-Grundlagen

Um mit dem JNI-Konzept eine Verbindung zwischen Java und C-Code (oder auch anderem nativem Code) zu erstellen, sind im Wesentlichen fünf Schritte erforderlich:

1. Erstellen der Java-JNI-Funktionsdeklaration
2. Generierung der C-Header-Datei
3. Erstellen der C-JNI-Methoden
4. Laden der JNI-Bibliothek in Java
5. Linken der dynamischen Bibliothek

Erstellen der Java-JNI-Funktionsdeklaration

Wenden wir uns dem ersten Schritt zu - dem Erstellen der Java-JNI-Funktionsdeklaration. Wir kennen diesen Schritt im Prinzip schon, denn die Ähnlichkeit zu der bisher besprochenen Technik ist groß. Um native Methoden in Java-Klassen zu benutzen, muss man in der Deklaration der Methode zuerst das Schlüsselwort native einfügen.

Beispiel: public native void meineNativeMethode();

Anschließend wird die Methode System.loadLibrary() in den Java-Source eingebunden. Das Schlüsselwort native signalisiert sowohl dem Java-Compiler als auch dem Java-Interpreter, dass sich der Rumpf der Methode in einer DLL befindet. Die Methode System.loadLibrary() dient zum konkreten Aufrufen dieser dynamische Bibliothek. In dem folgenden Beispiel lädt die Initialisierungmethode der Klasse eine plattformspezifische native Bibliothek, in der die native Methode definiert ist:

package pkg; 
class Cls {
native double f(int i, String s);
static {
System.loadLibrary("pkg_Cls");
}  }

Das Argument der Methode System.loadLibrary() ist ein vorgegebener Library-Name.

Alternativ stehen die Methoden Runtime.getRuntime().loadLibrary() und System.load() zum Laden einer Bibliothek zur Verfügung. Damit können auch Bibliotheken außerhalb des Suchpfades benutzt werden, denn sie werden anhand eines vollständigen Pfadnamens gesucht. Die JNI-Methode RegisterNatives() kann ebenfalls zur Registrierung einer nativen Methode verwendet werden. Die JNI-Methode FindClass() dient zur Suche von Klassen.

Wenn eine native Bibliothek respektive Methode eingebunden ist, wird anschließend der Java-Code wie üblich kompiliert.

Generierung der C-Header-Datei

Der nächste Schritt besteht darin, aus der .class-Datei eine entsprechende Header-Datei mit der Erweiterung .h zu erzeugen. Dazu wird wie gehabt das javah-Tool aus dem JDK eingesetzt. Allerdings müssen Sie die Option -jni verwenden. Der Aufruf sieht also so aus: javah -jni Datei

Erzeugt wird dadurch die C-Header-Datei Datei.h.

In der Header-Datei wird durch die typedef-struct-Anweisung der nativen C-Routine mitgeteilt, wie die Daten in der Java-Klasse angeordnet sind. Die einzelnen Variablen in der Struktur können benutzt werden, um die Klassen und Instanzvariablen von Java zu benutzen. Weiterhin wird in der Header-Datei ein Prototyp angegeben, um die Methode aus dem objektorientierten Namensraum der Java-Klasse in einen C-Namensraum zu überführen. Der Name einer Funktion, der eine native Methode implementiert, ergibt sich dabei immer aus dem Paket-Namen, dem Klassen-Namen und dem Namen der nativen Methode von Java, getrennt durch einen Unterstrich (wir kennen das bereits).

Erstellen der C-JNI-Methoden

In diesem Schritt kommen wir zu einer Neuerung, denn nun müssen die nativen JNI-Methoden implementiert werden. Üblicherweise werden die Implementationsdateien mit dem Klassennamen und einer eindeutigen Endung versehen. Die Implementationsdatei muss die Datei jni.h mittels #include-Anweisung einbinden. jni.h ist im include-Verzeichnis des JDK enthalten. Ebenfalls muss die mittels javah -jni erzeugte Header-Datei eingebunden werden.

Der Funktionskopf in der Implementationsdatei muss den generierten Prototypen aus der Headerdatei entsprechen.

Erstellung der dynamischen Bibliothek

Nachdem nun alle benötigten Dateien vorhanden sind, muss die Implementierungsdatei nur noch übersetzt und mit der Java-Bibliothek zu einer dynamischen Bibliothek zusammen gelinkt werden. Dies entspricht unseren Schritten 4 und 5 bei der konventionellen Technik.

15.2.4 Datenaustausch zwischen Java und C

Der Datenaustausch zwischen Java und C ist - wie wir bereits wissen - nicht ganz unproblematisch. Jedem elementaren Typ in Java wird ein Typ in der Prototyp-Funktion und damit auch in C zugeordnet. Ein gewisses Problem ist dabei der Typ char, der in Java nach dem Unicode-Standard kodiert ist und in C seinem ASCII-Gegenstück zugeordnet werden muss. Dem Java-Typ String kann sogar nur eine Struktur zugeordnet werden.

Wenn Variablen nicht als Parameter einer Methode, sondern als Klassenvariable angelegt werden, ermöglicht dies einen einfachen Zugriff von C aus. Dieser Zugriff wird als »Call by Reference« bezeichnet. Übergibt man die Variablen als Parameter, nennt man diesen Zugriff einen »Call by Value«. Alle Klassenvariablen werden in der .h-Datei zu einem struct zusammengefasst. Auf diesen erhält die aufgerufene C-Funktion als Parameter ein Handle-Pointer. Das in StubPreamble.h definierte unhand()-Makro ermöglicht den Zugriff auf die einzelnen Klassenvariablen. Das unhand()-Makro übernimmt einen Pointer auf das Handle einer Klasse und gibt einen Pointer auf die im .h-File erzeugte Klassenstruktur zurück. Über den Rückgabewert des Makros lassen sich die Instanzvariablen der Java-Klasse direkt auswerten und verändern.

Strings

Wie wir wissen, sind Strings in Java eine eigene Klasse. Möchte man einen String von Java nach C oder umgekehrt übergeben, muss man javaString.h in der C-Implementationsdatei mit einbinden. In javaString.h finden sich die Typdefintionen und Funktionen, um Strings von Java nach C und umgekehrt zu transformieren. Es gibt noch weitere Methoden, um Strings zu bearbeiten, wie z.B. MoveString_GetString(), CString() oder makeJavaString(). Java-Strings bilden eine eigene Klasse, somit wird bei der Konvertierung nicht nur ein elementarer Typ, sondern eine ganze Klasse an die native Methode durchgereicht. Dieses Konzept lässt sich auch auf andere Klassen erweitern.

Felder

Felder werden von Java nach C übergeben, indem vom javah-Tool ein Funktionsparameter vom Typ struct HArrayOf<Objekt> * erzeugt wird. Dieser enthält einen Element-Body, mit dem auf die Feldelemente zugegriffen werden kann. Der Zugriff auf das Feld erfolgt mit dem unhand()-Makro und dem body -Element.

Allgemeines zu JNI-Typen und Datenstrukturen

Es gibt zu jedem Java-Typ ein passendes JNI-Äquivalent, das dem Namen in der Regel nur ein j voranstellt. Um unter JNI-Javatypen in native C-Typen zu mappen, benötigen Sie grundlegende Informationen über die primitiven Typen. Die nachfolgende Tabelle beschreibt die primitiven Java-Typen und ihre maschinenabhängigen nativen Äquivalente:

Java Typ Native Typ Beschreibung
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

Tabelle 15.1:   Die JNI-Typen

Speichermanagement

In Java wird die Speicherverwaltung automatisch vom Garbage Collector erledigt. Wird in einer C-Funktion einer nativen Methode Speicher angelegt, muss sich der Programmierer selbst um die Freigabe kümmern. Da der Java-Interpreter von diesem manuellen Speichermanagement normalerweise nichts mitbekommt, müssen eigene native Methoden implementiert werden, um Speicher freigeben zu können. Unter gewissen Umständen können die Speicherbereiche auch aus Java heraus freigegeben werden. Eine einfache Variante der Freigabe ist der explizite Aufruf der Methode dispose().

15.2.5 Java-C++-Verbindung

Die Ähnlichkeit von Java und C++ legt die Vermutung nahe, dass es relativ einfach ist, C++-Code in Java einzubetten. Leider ist das Gegenteil der Fall. Aus Sicht der Schnittstelle betrachtet unterscheiden Java und C++ die folgenden Charakteristika:

15.2.6 Alternativen zum direkten Einbinden von nativen Methoden

Native Methoden sind nicht die einzige Möglichkeit, C- oder C++-Programme mit Java zu verbinden. Es gibt in der Tat Alternativen, um Java-Programme mit anderen Programmen zu verbinden. Zwei auch in vielen anderen Sprachpaarungen praktizierte Varianten sind die folgenden:

15.3 Verbindung von Java zu JavaScript und anderen Scriptsprachen

Die Verbindung von Java zu Scriptsprachen im Rahmen einer Webseite ist gut möglich. Wir wollen hier zu einem der beiden wichtigsten Vertreter von Scriptsprachen kommen. In Zusammenhang mit Java wird immer wieder der Begriff JavaScript genannt. Die Ähnlichkeit im Namen suggeriert aber eine Verwandtschaft, die so gar nicht vorhanden ist. Insbesondere handelt es sich bei JavaScript nicht um eine von der Syntax an C/C++ angelehnte, objektorientierte Programmiersprache.

JavaScript ist viel einfacher als Java und hat seine Existenzberechtigung da, wo Java überdimensioniert ist, vor allem aufseiten des Clients im WWW. Dies gilt für viele Aktionen im Rahmen einer Webseite oder der Steuerung eines Browsers. JavaScript (in der Betaversion noch LiveScript genannt) wurde in Zusammenarbeit von Sun und Netscape als Java-Ableger entwickelt und sollte als Steuerungstechnik für den Netscape-Navigator 2.0 agieren. Im Dezember 1995 haben die Netscape Communications Corporation und Sun Microsystems JavaScript angekündigt und recht bald als eine offene, plattformunabhängige Scriptsprache eingeführt. Obwohl JavaScript viel einfacher als Java ist, lehnt sich die Syntax an Java an.

Bei den so genannten Scriptsprachen (JavaScript oder auch Microsofts VBScript) handelt es sich um Interpretersprachen. Sie sind allgemein von relativ einfacher Struktur. Scriptsprachen müssen - server- oder clientseitig - als Interpretersprachen in irgendeinen Interpreter implementiert werden, damit sie zu Leben erwachen.

Auf einem Web-Client realisiert man die Verwendung von Scriptsprachen, indem man die Scriptelemente in HTML einbindet. Anschließend werden sie dann von dem Browser (der natürlich diese Scriptsprache verstehen muss) interpretiert. JavaScript zum Beispiel ist so als eine unmittelbare Ergänzung und Erweiterung zu HTML zu sehen und nur als eingebundener Bestandteil eines HTML-Gerüsts zu verwenden.

JavaScript-Programme werden im Gegensatz zu Java-Applets direkt in der HTML-Datei geschrieben oder zumindest als Klartext-Dateien dort referenziert. Sie sind damit in gewisser Weise normaler Bestandteil des HTML-Dokuments, ähnlich wie Überschriften, Verweise oder andere Referenzen.

Sie werden auch nicht wie Java-Applets als vorkompilierte Module in die HTML-Datei integriert, sondern analog HTML als Klartext zur Laufzeit interpretiert. Trotzdem handelt es sich bei JavaScript im Gegensatz zu HTML nicht nur um eine Beschreibungssprache, sondern um eine vollwertige Programmiersprache, wenn auch auf reiner Interpreterbasis. Damit hat JavaScript alle durch das Interpreter-Prinzip bedingten Vorteile, leider aber auch die Nachteile. JavaScript bietet sich daher für kleine und einfache Programmabläufe an, die Aktionen erfordern, die unter HTML nicht oder nur umständlich zu bewerkstelligen sind und Java als zu schweres Geschütz outen.

Scriptsprachen können wie gesagt bei einem Client-Server-System sowohl auf Server, als auch dem Client eingesetzt werden. Wir wollen uns hier nur mit dem clientseitigen Einsatz von JavaScript beschränken. Eigentlich ist es so, dass im Browser eingesetzte Scriptsprachen im Wesentlichen den relativ dummen Browser intelligenter machen. Dazu müssen auf der Client-Seite Fähigkeiten hinzugefügt werden, die dem angefragten Dokument oder dem anfragenden Browser eine Veränderung des Dokuments ermöglichen. Diese Fähigkeit bringt die Möglichkeiten von serverseitigen Programmen zum Client, sie macht den Browser also leistungsstärker und intelligenter.

Obwohl ein Browser normalerweise außer dem Anfordern und Versenden von Daten höchstens noch als Darstellungssoftware für Multimedia-Effekte dient, ist in modernen Browsern die Nutzung dieser Scriptsprache(n) integriert, um sie mit weitergehenden Fähigkeiten auszustatten. Scripte erlauben einem Webbrowser, auf eine intelligente Weise mit Situationen, die sonst ein Programm auf dem Webserver erforderlich machen würden, umzugehen. Darüber hinaus hat der Anwender den Eindruck, dass die Situation viel schneller abgehandelt wird, da der Browser keine Anfrage an den Server schicken und die Antwort nicht anzeigen muss. Mit Sprachen wie JavaScript wird also eigentlich der Browser programmiert.

15.3.1 Kleiner Exkurs zu JavaScript-Interna

Wir sprechen hier zwar hauptsächlich über Java, aber als ein direkter Java-Ableger macht es Sinn, auch JavaScript ein bisschen eingehender zu beleuchten. Deshalb nun ein kleiner Exkurs zu den JavaScript-Interna.

Wir werden als Erstes ohne lange Vorrede mit einem kleinen Beispiel einsteigen. Ein kleines und sehr bekanntes Beispiel in JavaScript ist die Laufschrift in der Statuszeile des Browsers.

Der Source dafür ist ziemlich einfach und leicht nachzuvollziehen, wenn man Java und Objekte kennt. Die einzelnen Details werden etwas weiter hinten im Kapitel erläutert.

<html>
<script language="JavaScript">
<!--
function scrollit_r2l(seed) {
 var msg = "Running all over the world";
 var out = " ";
 var c = 1;
 if (seed > 100) {
 seed--;
 var cmd="scrollit_r2l(" + seed + ")";
 timerTwo=window.setTimeout(cmd,100);
 }
 else if (seed <= 100 && seed > 0) {
 for (c=0 ; c < seed ; c++) {
 out+=" ";
 }
 out+=msg;
 seed--;
 var cmd="scrollit_r2l(" + seed + ")";
 window.status=out;
 timerTwo=window.setTimeout(cmd,100);
 }
 else if (seed <= 0) {
 if (-seed < msg.length) {
 out+=msg.substring(-seed,msg.length);
 seed--;
 var cmd="scrollit_r2l(" + seed + ")";
 window.status=out;
 timerTwo=window.setTimeout(cmd,100);
 }
 else {
 window.status=" ";
 timerTwo = window.setTimeout("scrollit_r2l(100)",75);
 }
 }  }
function lade() {
timerONE=window.setTimeout('scrollit_r2l(100)',500);
}
//-->
</script>
<body onLoad="lade()">
<h1>Willkommen auf meiner Homepage</h1>
</body>
</html>

Abbildung 15.1:  Laufschrift in der Statuszeile per JavaScript

Die Einbindung von JavaScript (oder auch anderen Scripten) in HTML-Seiten erfolgt in der Regel über das <SCRIPT>-Tag.

<SCRIPT language="JavaScript">
<!--
//-->
</SCRIPT>

Das <SCRIPT>-Tag kann an einer beliebigen Stelle innerhalb der Webseite platziert werden. Wenn das Script im <HEAD>-Bereich platziert wird, wird es bereits interpretiert, bevor der Textkörper des Dokuments vollständig geladen wurde. Deshalb sollten Sie Funktionen und Initialisierungscode in den <HEAD>-Abschnitt oder zumindest vor den Body schreiben.

Direkt nach dem einleitenden <SCRIPT>-Tag sollte ein HTML-Kommentar <!-- notiert werden, der erst direkt vor dem abschließenden Tag </SCRIPT> mit dem entsprechenden HTML-Kommentarendezeichen //--> wieder geschlossen wird. Dadurch steht der gesamte JavaScript-Code innerhalb eines HTML-Kommentars. Es können ebenso mehrere in Kommentar-Tags eingeschlossene Scriptelemente hintereinander notiert werden, auch mit verschiedenen Scriptsprachen.

JavaScripts können auch in einer Extra-Datei abgelegt werden. Sie erreichen dies mit dem Attribut src, das ein Tag erweitert, welches externe Dateien nutzen möchte.

Beispiel:

<SCRIPT language="JavaScript" src="meineFunktion.js" type="text/JavaScript">
</SCRIPT>

In Anführungszeichen wird hinter dem Attribut src der Name der separaten Datei angegeben. Dabei gelten beim Referenzieren von separaten JavaScript-Dateien die üblichen Regeln für URLs. Der Container selbst bleibt leer. Die Datei mit dem Quellcode muss wie HTML-Dateien auch eine reine ASCII-Datei sein und ausschließlich JavaScript-Code enthalten. Üblich ist die Dateierweiterung .js, aber das ist nicht zwingend.

Der Aufruf von JavaScripten erfolgt im Wesentlichen über so genannte Event-Handler. Diese sind Bestandteil von HTML und werden als Attribute in HTML-Tags notiert, die auf entsprechend spezifizierte Ereignisse reagieren sollen. Es gibt im Wesentlichen drei Situationen, wann beispielsweise eine JavaScript-Funktion unter HTML aufgerufen wird:

1. Beim Start der HTML-Datei
2. Bei einem Klick auf eine Referenz
3. Beim Überstreichen mit dem Mauszeiger

Der Aufruf beim Start der HTML-Datei erfolgt mit einer Erweiterung des <BODY>-Tags, dem Attribut onload.

<body onload=[ Funktionsname] >  

Der Funktionsname der JavaScript-Funktion wird in Anführungszeichen gesetzt.

Der Aufruf bei einem Klick auf eine Referenz erfolgt mit einer Erweiterung des Referenz-Tags, dem Attribut onclick.

<a href=[URL]  onClick= [ Funktionsname] >...</a>  

Der Aufruf bei Überstreichen mit dem Mauszeiger erfolgt mit einer anderen Erweiterung des Referenz-Tags, dem Attribut onMouseOver. Dabei wird beim Überstreichen eines Verweis-sensitiven Bereichs mit dem Mauszeiger die JavaScript-Funktion direkt aufgerufen.

<a href=[URL] onMouseOver= [ Funktionsname] >...</a>

Neben den hier genannten Methoden gibt es noch diverse weitere Event Handler (onAbort, onBlur, onError, onSubmit usw.), die wir allerdings hier nicht alle durchsprechen wollen. Insbesondere muss beachtet werden, dass die Unterstützung von Event-Handlern in den verschiedenen Browsern sehr unterschiedlich realisiert ist. Für mehr Informationen gibt es viele weiterführende Quellen zu JavaScript.

Alle Anweisungen, die zu HTML zählen, können sowohl klein als auch groß geschrieben werden. JavaScript selbst unterscheidet jedoch wie Java Groß- und Kleinschreibung.

Variablen, Arrays und Datentypen in JavaScript

JavaScript besitzt genauso wie andere Programmiersprachen Variablen. Aber bei konventionellen Programmiersprachen (wie C und C++ und Java) muss der Programmierer den Datentyp festlegen, der mit der Variable abgespeichert wird. Das wird statische Typisierung von Variablen genannt. JavaScript verfolgt bei der Zuweisung von Werten zu Variablen das genaue Gegenteil - das Konzept der so genannten losen Typisierung (loose typing). Dadurch werden Variablentypen niemals deklariert, sondern impliziert. Dies bedeutet, dass zwar die Namen von Variablen festgelegt, dabei jedoch niemals bereits die Variablentypen deklariert werden. Dadurch werden Variablen erst dann zu einem bestimmten Datentyp, wenn ihnen Werte eines bestimmten Typs zugewiesen wurden. Ein wesentlicher Vorteil ist, dass Sie den Datentyp jederzeit ohne großen Aufwand ändern können. Explizites Casting ist nicht notwendig. Wenn Sie den Wert einer Zeichenkette einer Variable zuweisen, können Sie sie später in einen booleschen Variablentyp ändern. Welcher Wert auch immer in der Variable enthalten ist, er definiert den Variablentyp.

Die lose Typisierung von JavaScript hat aber nicht nur Vorteile, was Sie ja mit Ihrer Java-Erfahrung durchaus einschätzen können. Sonst würden die viel mächtigeren Programmiersprachen nicht den Aufwand mit der statischen Typisierung betreiben. Sie können beispielsweise leicht vergessen, welcher Typ einer Variablen zugeordnet ist. Sie sollten Variablen möglichst nie im Typ verändern - sofern es sich vermeiden lässt - und tunlichst sprechende Namen verwenden.

Ein großes Problem in JavaScript ist es, Variablen anzulegen. Nicht etwa, weil es schwer ist. Im Gegenteil - es ist viel zu leicht. Im Allgemeinen werden Variablen in JavaScript über das Schlüsselwort var angelegt.

Beispiel: var Weizenkeim;

Die im Beispiel angelegte Variable können Sie dann im Laufe Ihres Scripts verwenden. Sie hat noch keinen Typ und auch noch keinen Wert. Ihr kann jedoch später - irgendwann im Ablauf Ihres Scriptes - ein Wert zugewiesen werden und damit erhält sie gleichzeitig ihren Variablentyp. Wenn Sie sie jedoch verwenden wollen, bevor sie einen Wert bekommen hat, wird der JavaScript-Interpreter einen Fehler melden. Sicherer ist es auf jeden Fall, wenn Sie beim Anlegen der Variablen ihr gleich einen Wert - einen Defaultwert zur Vorbelegung - zuweisen. Sie können in JavaScript Werte von Variablen verändern, indem Sie einer Variablen einfach den neuen Wert über ein Gleichheitszeichen (einen der Zuweisungsoperatoren zuweisen. Dies funktioniert wie im nachfolgenden Beispiel:

var Weizenkeim= 42;

Die Variable Weizenkeim ist damit als Zahl festgelegt und hat den Wert 42. Wo ist dann aber bisher das Problem? Nun, Variablen können in JavaScript »on the flight« entstehen. Das bedeutet, ganz ohne Verwendung von var. Wenn Sie unbeabsichtigt bei einer Zuweisung einen neuen Variablennamen einführen (z.B. weil Sie sich verschrieben haben), wird diese Variable automatisch erzeugt und die Variable, der Sie eigentlich einen Wert zuweisen wollten, wird weiter mit dem alten Wert geführt.

Obwohl Datentypen bei JavaScript nicht explizit festgelegt werden, gibt es implizit natürlich einige Grundtypen von Variablen. Der Typ Boolean enthält nur einen der beiden Werte wahr oder falsch (true oder false). Datentyp Number kann sowohl einen Ganzzahl als auch einen Gleitkommawert (Kommaanteil wird mit Punkt getrennt) enthalten. Neben der normalen Zahlenschreibweise gibt es die wissenschaftliche Notationsmöglichkeit über die Angaben e oder E. Der Typ String kann eine Reihe von alphanumerischen Zeichen (sprich normalen Text, der mit Zahlen gemischt sein kann) enthalten. Zu guter Letzt gibt es noch Object. Dieser allgemeine Datentyp kann einen Wert eines beliebigen Typs enthalten. Er wird normalerweise für das Speichern von Klassen-Instanzen verwendet.

Beispiel: heute_ist = new Date();

Zusätzlich unterstützt JavaScript Datenfelder (Arrays) jeglicher Datentypen. Ein JavaScript-Array wird anders erzeugt als eine normale Variable. Sie müssen neben dem Namen noch die Anzahl von Elementen angeben, die darin enthalten sein können. Außerdem wird nicht mehr das Schlüsselwort var zur Erzeugung verwendet, sondern das Schlüsselwort new. Das ist dem Vorgang in Java recht ähnlich, aber es fehlt die Typfestlegung.

Beispiel: mein_Array = new Array(29);

Zu beachten ist aber auch, dass JavaScript in keiner Weise die vorgegebenen Grenzen des Arrays beachtet. Sie können also einen Array dynamisch erweitern, indem Sie einfach eine nicht vorhandene Dimension bei einer Zuweisung angeben.

Operatoren unter JavaScript

JavaScript kennt die meisten der unter Java vorkommenden Operatoren. Wenn sie vorhanden sind, haben sie auch die gleiche Bedeutung wie in Java. Allerdings verzichtet JavaScript natürlich auf die Operatoren, die auf Grund des einfacheren Konzepts keinen Sinn machen. Etwa diejenigen, die mit der OO-Theorie direkt in Verbindung stehen. JavaScript kennt folgende Operatoren (der Priorität nach geordnet):

Priorität Operatoren
1 () []
2 ! ~ -1 ++ -- typeof
3 * / %
4 + -2
5 << >> >>>
6 < <= > >=
7 == !=
8 &
9 ^
10 |
11 &&
12 ||
13 ?:
14 = += -= <<= >>= &= ^= |=
15 ,

Tabelle 15.2:   JavaScript-  Operatoren

1

Im Sinn von Negation

2

Im Sinn von Subtraktion

Die reservierten Worte von JavaScript

JavaScript kennt die folgenden Schlüsselworte, die selbstverständlich nicht für Variablen- oder Funktionsbezeicher verwendet werden dürfen. Diverse der Schlüsselworte sind nur reserviert und werden noch nicht verwendet:

Schlüsselwort Beschreibung
abstract reserviert
boolean reserviert
break Abbruch in Schleifen
byte reserviert
case Fallunterscheidungen
catch reserviert
char reserviert
class reserviert
const reserviert
continue Fortsetzung in Schleifen
debugger reserviert
default Fallunterscheidungen
delete Löschen eines Array-Elements oder einer selbst -definierten Objekteigenschaft
do Beginn einer Erweiterung der while-Schleife (do-while)
double reserviert
else Einleitung des alternativen Blocks in einer if-Schleife
enum reserviert
export Objekte oder Funktionen für fremde Scripts ausführbar machen
extends reserviert
false Der Wert falsch
final reserviert
finally reserviert
float reserviert
for Einleitung von for-Schleifen
function Einleitung von Funktionen
goto reserviert
if Einleitung von if-Schleifen
implements reserviert
import Objekte oder Funktionen eines fremden Scripts -importieren
in Bedingte Anweisungen in if-Schleifen
instanceof reserviert
int reserviert
interface reserviert
long reserviert
native reserviert
new Definition von Objekten
null reserviert
package reserviert
private reserviert
protected reserviert
public reserviert
return Übergabe eines Rückgabewertes in Funktionen
short reserviert
static reserviert
super reserviert
switch Fallunterscheidung
synchronized reserviert
this Bezug auf die aktuelle Instanz eines Objekts
throw reserviert
throws reserviert
transient reserviert
true Der Wert wahr
try reserviert
typeof Typ eines Elements
var Definition einer Variablen
void Leerer Funktionstyp
volatile reserviert
while Einleitung einer while-Schleife
with Erlaubt, mehrere Anweisungen mit einem Objekt -durchzuführen

Tabelle 15.3:   JavaScript-  Schlüsselworte

JavaScript-Anweisungen

Anweisungen sind das Herz von JavaScript. Eine Anweisung ist eine Quellcodezeile, die bestimmte Befehle enthält.

Beispiel:

window.setTimeout('fenster_neu()',3000);

JavaScript-Anweisungen können auch über mehrere Zeilen im Editor gehen, denn sie enden immer erst mit einem Semikolon.

Wenn mehrere Anweisungen zu einer Gruppe zusammengefasst werden, faßt man sie wie in Java zu Blöcken zusammen. Diese werden in geschweifte Klammern eingeschlossen ({ }), die Anfang und Ende eines Blocks markieren. Der JavaScript-Interpreter des Browsers wird einen solchen Block als Einheit behandeln und ihn zusammen abarbeiten.

JavaScript-Eigenschaften

In JavaScript werden Eigenschaften normalerweise als Variablen vorkommen. Die Eigenschaften können in JavaScript beliebige Variablenarten zugewiesen bekommen. Um auf eine Eigenschaft zuzugreifen, geben Sie den Namen des zugehörigen Objekts an, dann folgt ein Punkt und zum Schluss die Eigenschaftsvariable (der Name) - bekannte Punktnotation. Da JavaScript allerdings nur objektbasierend und nicht streng objektorientiert ist, kann man auch globale Variablen verwenden.

Kontrollstrukturen in JavaScript

JavaScript kennt die auch in Java verwendeten Kontrollstrukturen. Hier folgen einige der wichtigsten Kontrollstrukturen.

Die if-Bedingung:

if (Bedingung) {
Anweisungen
}
else {
alternative Anweisungen
}

Die while-Bedingung:

while (Bedingung) {
Anweisungen
}

Die for-Schleife:

for (Zählvariable;Bedingung;Veränderung der Zählvariable) {
Anweisungen
}

Funktionen

JavaScript stellt die Möglichkeit bereit, mit Funktionen zu arbeiten. Ein signifikanter Unterschied zu Java, aber was Funktionen sind, muss wohl nicht erklärt werden. Sie können in JavaScript auch eigene Funktionen definieren, um umfangreichere und häufiger benötigte Strukturen zusammenzufassen. Der Interpreter wird beim Laden einer Webseite so markierten Code nicht abarbeiten, sondern erst beim expliziten Aufruf über den Bezeichner samt eventuell notwendiger Parameter. Zur Deklaration einer Funktion dient die folgende Syntax:

function [Name]([Parameterliste]) {
Anweisungen
}

15.3.2 Das Objektmodell von JavaScript

JavaScript ist zwar nicht objektorientiert, kann aber - wie bereits angesprochen - Objekte nutzen und (mit erheblichen Einschränkungen) sogar selbst erzeugen. Genau genommen basiert die Leistungsfähigkeit von JavaScript darauf, Objekte nutzen zu können. Eines der internen Objekte, die JavaScript zur Verfügung stehen, wird navigator genannt. Dieses Objekt enthält Informationen über den Browser, den der Anwender verwendet. Eine unter JavaScript nutzbare Eigenschaft von navigator ist appName. Diese Zeichenkette enthält den Namen des Browsers. Um den Wert der Eigenschaft, also den Namen, zu bekommen, müssen Sie einen Punkt zwischen Objekt und Eigenschaft setzen:

navigator.appName

Das Objektmodell selbst ist kein Bestandteil von JavaScript, sondern übergeordnet. Es kann also von verschiedenen Techniken verwendet werden.

Jede Eigenschaft eines Objekts kann so abgefragt werden und eine Eigenschaft kann auch ein anderes Objekt sein.

In JavaScript werden die meisten Objekte automatisch entstehen. Dann muss sich ein JavaScript-Programmierer eigentlich um nichts mehr kümmern. Die manuelle Erstellung durch den Programmierer geschieht wie in Java mithilfe des reservierten JavaScript-Schlüsselworts new.

Die internen Objekte von JavaScript

Unter JavaScript können Sie u.a. folgende vordefinierten Objekte nutzen:

Objekt Beschreibung
all Das Objekt ermöglicht den direkten Zugriff auf alle Elemente einer HTML-Datei. Es gehört aber nicht zum offiziellen JavaScript-Standard, sondern ist eine Implementation für den Internet Explorer ab der Version 4.0.
anchor Das Objekt beinhaltet alle Verweisanker in einer HTML-Datei.
applet Das Objekt beinhaltet alle Java-Applets in einer HTML-Datei.
Array Über dieses Objekt werden Arrays erzeugt. Dessen Elemente können über einem gemeinsamen Bezeichner und einen Index angesprochen werden.
Boolean Ein Objekt mit Wahrheitswerten.
Date Das Objekt enthält Informationen zu Datum und Uhrzeit.
document Dieses Objekt repräsentiert die Webseite selbst.
event Ein Objekt, das bei Anwenderereignissen erzeugt wird und für die (zentrale) Ereignisbehandlung genutzt werden kann.
form Ein Objekt, das die Formulare einer HTML-Seite repräsentiert.
frame Ein Objekt, das die Framesets und Frames einer HTML-Seite repräsentiert.
Function Ein Objekt mit JavaScript-Funktionen.
history Dieses Objekt enthält Informationen über die URLs, die ein Anwender besucht hat.
image Ein Objekt, über das auf die Grafiken in einer HTML-Datei -zugegriffen werden kann.
layer Die Layer in einer HTML-Datei (Netscape-spezifisch und sogar im neuen Navigator 6 nicht mehr unterstützt).
link Das Objekt, das die Verweise in der aktuellen HTML-Datei -repräsentiert.
location In diesem Objekt werden Informationen über URL-Adressen geführt.
Math Ein Objekt mit zahlreichen mathematischen Konstanten und Methoden.
mimeType Ein Objekt mit Mim-Typ-Informationen.
navigator Die Objektrepräsentation mit Informationen über den -verwendeten WWW-Browser.
Number Ein Objekt mit numerischen Werten.
plugin Ein Objekt, das die vorhandenen Plug-Ins in einem Browser repräsentiert.
RegExp Ein Objekt mit regulären Ausdrücken.
screen Ein Objekt mit Informationen über den verwendeten Bildschirm.
String Ein Objekt für die Manipulation von Zeichen und Zeichenketten.
Style Die Objektrepräsentation der Stil-Attribute eines Elements.
window Dieses Objekt enthält Statusinformationen über das gesamte Browser-Fenster. Jedes Fenster hat sein eigenes window-Objekt. Das window-Objekt ist das höchste Objekt in der Objekthierarchie der JavaScript, die den Browser direkt betreffen.

Tabelle 15.4:   Objekte unter JavaScript

Es gibt neben den hier aufgeführten Objekten weitere Objekte, die sich in der Notation ein wenig von den anderen Objekten unterscheiden, aber sonst ganz »normale« Objekte sind. Dies sind so genannte Objektfelder. Charakteristisch dafür ist, dass diese über einen Feldnamen sowie eine Indexnummer identifiziert werden. Ansonsten ist die Anwendung von Eigenschaften und Methoden vollkommen identisch. Beispiele für solche Objektfelder sind forms[] oder elements[]. Es handelt sich dabei um Arrays, die Objekte enthalten. Wie stehen nun Objektfelder mit den obigen Objekten in Beziehung?

Einige Objektfelder entstehen automatisch, wenn eine Webseite geladen wird und Objekte eines bestimmten Typs darin enthalten sind. Wenn beispielsweise eine Webseite ein Formular enthält, bedeutet dies, ein Objekt des Typs form ist darin enthalten. Wenn nun mehr als ein Formular in einer Webseite vorhanden ist, muss der Browser diese Formulare irgendwie identifizieren und speichern. Jedes Formular wird in einem Feld eines Objektfelds gespeichert, das automatisch generiert wird und das vom Bezeichner meist dem erzeugenden Objekt sehr ähnlich ist (im Fall von Formularen ist das beispielsweise forms - beachten Sie das s). Die Indexnummern entstehen automatisch, wenn der Browser das Objekt bei Abarbeitung der HTML-Seite erzeugt und in einen Schlitz des Arrays einordnet. Das erste im Dokument auftretende Objekt jedes vorkommenden Typs erhält den Index 0, das zweite den Index 1 usw.

Bei Formularen wird das erste Objekt vom Typ form im Array-Eintrag forms[0] gespeichert, das zweite in forms[1] usw.

Die nachfolgende Tabelle gibt die wichtigsten Objektfelder an, deren potenziellen Inhalt sowie eine kleine Beschreibung.

Objektfeld Typ der enthaltenen Objekte Beschreibung
anchors anchor Die im Objektfeld enthaltenen Objekte repräsentieren eine Liste aller Hypertext-Anker in einer Webseite.
applets applet Die enthaltenen Objekte repräsentieren eine Liste aller Applets in einer Webseite.
elements [Eingabelemente eines HTML-Formulars] Die enthaltenen Objekte repräsentieren eine Liste aller Eingabeelemente, die sich in einem als übergeordnetes Objekt angegebenen Formular befinden. Diese werden in JavaScript durch die folgenden Objekte repräsentiert: Button, Checkbox, FileUpload, Hidden, Password, Radio, Reset, Select, Submit, Text und Textarea.
forms form Die enthaltenen Objekte repräsentieren eine Liste aller Formulare in einer Webseite.
frames frame Die enthaltenen Objekte repräsentieren eine Liste aller Frames in einer Webseite.
images image Die enthaltenen Objekte repräsentieren eine Liste aller Bilder in einer Webseite.
links link Die enthaltenen Objekte repräsentieren eine Liste aller Hyperlinks in einer Webseite.
mimeTypes mimeType Die enthaltenen Objekte repräsentieren eine Liste aller Mimetypen in einer Webseite.
options [Liste der Optionen eines Eingabefeldes vom Typ select] Die enthaltenen Objekte repräsentieren eine Liste aller erlaubten Optionen, die bei dem als übergeordnetes Objekt angegebenen Objekt vom Typ select vorkommen.
plugins plugin Die enthaltenen Objekte repräsentieren eine Liste aller in dem Browser installierten Plug-In-Module.

Tabelle 15.5:   Objektfelder unter JavaScript

Die Objekthierarchie in JavaScript

Viele der JavaScript-Objekte stehen in einer Objekthierarchie zueinander. Dies bedeutet, ein Objekt ist die Ableitung eines anderen Objekts. Es erbt damit dessen Eigenschaften und Methoden und erweitert diese sinnvollerweise um irgendwelche zusätzlichen Funktionalitäten. Dieses Unterobjekt besitzt umgekehrt ein übergeordnetes Objekt. Wenn Sie ein in der Objekthierarchie tiefer angesiedeltes Objekt ansprechen wollen, müssen Sie einfach dessen Elternobjekt über die Punktnotation mit angeben.

Beispiel: window.document

Das Objekt window ist das Elternobjekt von document.

Allerdings sind nicht sämtliche Objekte in einer einzigen Hierarchiebeziehung miteinander verbunden. Neben den hierarchisch geordneten JavaScript-Objekten gibt es auch solche, die nicht direkt in diese Hierarchieebenen einzuordnen sind.

15.3.3 Verbindung von Java und JavaScript

JavaScript und Java lassen sich im Rahmen einer Webseite gut miteinander verbinden. Es ist möglich, von Java auf JavaScript zugreifen und umgekehrt. Allerdings ist das Verfahren nicht in allen Browsern gleich gehalten. Um einen externen Zugriff auf Java-Objekte zu erlauben, beinhaltet das Objektmodell von Netscape die Objekte Packages (ein ohne besonderen Konstruktor überall bereitstehendes Toplevel-Objekt für den Zugriff auf jede Javaklasse - etwa direkt aus dem JavaScript-Code heraus), java (ein ohne besonderen Konstruktor überall bereitstehendes Toplevel-Objekt für den Zugriff auf jede Javaklasse in dem Package java.*), JavaArray (eine Instanz eines Java-Arrays, die an JavaScript weitergereicht werden kann), JavaObject (eine Instanz eines Java-Objekts, die an JavaScript weitergereicht werden kann), und JavaPackage (eine JavaScript-Referenz auf ein Java-Package). Damit können Objekte von Java erreicht werden, die zum Core-Java-Standard gehören. Die Objekte netscape und sun sind weitere Toplevel-Objekte für den Zugriff auf die speziellen Java-Klassen in dem Package netscape.* bzw sun.*, die über die virtuelle Maschine des Navigators genutzt werden können. Das gesamte Konzept ist unter dem Namen LiveConnect bekannt und setzt - da eine Verbindung von zwei Technikwelten - natürlich entsprechende Kenntnisse in beiden Welten voraus. Es beinhaltet sowohl den Zugriff aus Java heraus auf JavaScript als auch den umgekehrten Weg.

Der Internet Explorer behandelt nun Java-Applets wie ActiveX-Steuerelemente. Er unterstützt deshalb meist keine direkten Zugriffe wie der Navigator in LiveConnect. Dafür aber gibt es die Möglichkeit, über die Ansteuerung von HTML-Elementen aus JavaScript heraus ein Java-Applet zu nutzen. Das Verfahren wiederum gibt unter Umständen Probleme im Navigator, der die Zugriffe auf HTML-Elemente viel eingeschränkter bereitstellt. Bei vielen einfacheren Zugriffen wird es aber in beiden Welten funktionieren. Betrachten wir die Verbindung von Java und JavaScript getrennt nach den Konzepten.

LiveConnect - die Netscape-Variante

Schauen wir uns zuerst die Richtung von JavaScript auf Java im Netscape-Modell an. Über das Packages-Objekt bzw. die anderen gerade genannten Toplevel-Objekte hat man Zugriff auf alle unter Java als public deklarierten Methoden und Felder direkt aus JavaScript heraus mittels der Standard-Punktnotation von Java (sofern die JVM zur Verfügung steht, was in neueren Browsern aber so gut wie immer der Fall ist). Die Objekte java, netscape und sun sind einerseits Eigenschaften von Packages, andererseits wieder selbst Objekte, die die Pakete java.*, netscape.*, und sun.* repräsentieren.

Aber die Technik geht viel weiter. Der Zugriff aus JavaScript heraus auf den Konstruktor einer beliebigen Java-Klasse erfolgt mit folgender Syntax:

var [JavaScriptObjektvariable] = new Packages.[Klasse];

Das sieht etwa für die Java-Klasse Frame aus JavaScript heraus folgendermaßen aus:

var meinFrame = new Packages.java.awt.Frame();

Es geht sogar noch einfacher, denn die jeweiligen JavaScript-Toplevel-Objekte netscape, sun, und java stehen als Synonyme für die Pakete gleichen Namens. Daher ist ein Zugriff auch ohne das Packages-Schlüsselwort möglich. Das Beispiel von oben sieht dann so aus:

var meinFrame = new java.awt.Frame();

Das Package-Objekt stellt überdies mit der Eigenschaft className den Pfadnamen zu jeder aus JavaScript erreichbaren Java-Klasse zur Verfügung. Damit und mit dem Package-Objekt kann dann sogar auf Java-Klassen außerhalb der direkt auf Toplevel-Ebene zur Verfügung stehenden Pakete zugegriffen werden.

Beachtet werden sollte aber, dass die Technik wie gesagt explizit auf das Netscape-Objektmodell abgestimmt ist. Deshalb werden die Beispiele auch nur im Netscape Navigator funktionieren. Außerdem muss in dem Browser (ab Version 3.0) sowohl die JavaScript-Unterstützung als auch die Java-Unterstützung aktiviert sein.

Der explizite Datenaustausch von JavaScript nach Java und umgekehrt ist nicht ganz trivial (zwischen Java und anderen Sprachen wie VBScript erst recht nicht). Beide Sprachen verfügen nicht über die vollkommen gleichen Datentypen und Objekte. So gilt beispielsweise, dass auf dem Weg von JavaScript nach Java unter LiveConnect die JavaScript-Datentypen String, Number und Boolean in entsprechend Java-Objekte vom Typ String, Float und Boolean umgewandelt werden. Die meisten anderen JavaScript-Objekte werden in Java in ein Objekt vom Typ JSObject gewandelt, das über das Paket netscape.JavaScript unter Java bereitgestellt wird. Auch der umgekehrte Weg der Übergabe von Daten aus Java nach JavaScript liegt fest. Ein boolean-Wert unter Java wird zu Boolean in JavaScript oder die Java-Datentypen byte, char, short, int, long, float und double werden allesamt zu Number.

Die allgemeinen Regeln für die Umwandlung liegen also fest, aber durch die unterschiedlichen Längen der Datentypen und deren Struktur ist eine Konvertierung nicht immer ohne Datenverlust möglich. Ein offensichtliches Beispiel ist der Datentyp long unter Java (64 Bit groß), der zu Number konvertiert wird (unter JavaScript nur 32 Bit groß). Gehen wir aber nun in die Praxis.

Das erste einfache Beispiel demonstriert die Ausgabe auf Systemebene über die Java-Standard-Ausgabemethode println(), die innerhalb eines JavaScripts aufgerufen wird:

<HTML><HEAD>
<SCRIPT LANGUAGE="JavaScript">
function ausgabe() {
java.lang.System.out.println(
"Nutzen von Java auf Systemebene");
}
</SCRIPT>
</HEAD><BODY onload=ausgabe()>
</BODY></HTML>

Um die Wirkung zu sehen, müssen Sie die Java-Konsole des Browsers anzeigen. Dort erfolgt die Ausgabe. Im Communicator (unter dem Menüpunkt Communicator und Extras) kann sie geöffnet werden.

Abbildung 15.2:  Aktivierung der Java-Konsole im Navigator

Abbildung 15.3:  Die println()-Ausgabe per JavaScript-Funktion in der Java-Konsole

Das nächste etwas komplexere Beispiel verwendet eine JavaScript-Funktion, um eine Java-Dialogbox zu erstellen (beachten Sie, dass der Java-Code keinen Schließbefehl für die Dialogbox enthält - Browser Schließen beendet aber auch das Fenster):

<HTML><HEAD>
<SCRIPT LANGUAGE="JavaScript">
function fenster()  {   
var derFrame = new java.awt.Frame();   
var meinFenster = new java.awt.Dialog(derFrame);   
meinFenster.setSize(350,200);   
meinFenster.setTitle("Hello World");   
meinFenster.setVisible(true);
}
</SCRIPT> 
</HEAD><BODY onload=fenster()>
</BODY></HTML>

Das Java-Fenster wird automatisch geöffnet, wenn die Datei in den Navigator geladen wird.

Abbildung 15.4:  Ein per JavaScript-Funktion aufgerufenes Java-Fenster

Das dritte Beispiel greift über Java auf die Systemeinstellungen zu und generiert daraus eine dynamische Webseite:

<HTML>
<HEAD><SCRIPT LANGUAGE="JavaScript">
function spionage() {
var dasSystem = java.lang.System;
var dasOS = dasSystem.getProperty("os.name");
var dieOSversion = dasSystem.getProperty("os.version");
var dieJavaVersion = dasSystem.getProperty("java.version");
document.write("Sie verwenden folgendes Betriebssystem: ");
document.write(dasOS);
document.write("<BR>");
document.write("Die Version ist: ");
document.write(dieOSversion);
document.write("<BR>");
document.write("Ihre Java-Version ist: ");
document.write(dieJavaVersion);
document.write("<BR>");
}
</SCRIPT> 
</HEAD><BODY onload=spionage()>
</BODY></HTML>

Abbildung 15.5:  Systeminfos

Zugriff auf JavaScript aus Java heraus

Wenn man nun unter Java im Rahmen des LiveConnect-Konzepts auf JavaScript zugreifen will, muss in den Klassen das Paket netscape.javascript importiert werden. Das heißt, Sie benötigen über die Core-Java-Klas- sen hinaus von Netscape bereitgestellte Klassen. Dieses Paket netscape.javascript.* enthält neben der Klasse JSException zur Behandlung von JavaScript-Ausnahmen die Klasse JSObject, mit der der Zugriff auf JavaScript, aber auch auf HTML-Elemente aus Java heraus möglich ist. Insbesondere stellt die Klasse die Methode call() bereit, mit der eine JavaScript-Funktion innerhalb von Java mit Parametern aufgerufen werden kann. Eine weitere Methode - eval() - kann einen JavaScript-Ausdruck ebenfalls auswerten - als Aufruf eines einzelnen Strings. Das Konzept funktioniert nicht nur im Navigator, sondern auch im Internet Explorer (zumindest in den neueren Varianten). Demonstrieren wir die Technik in einem kleinen Beispiel Schritt für Schritt. Zuerst das kleine, aber vollständig die Situation beschreibende Beispiel:

import java.awt.*;
import java.applet.*;
import netscape.javascript.*;
public class Applet2 extends Applet {
  JSObject jsHandle;
  /**Das Applet initialisieren*/
  public void init() {
  jsHandle = JSObject.getWindow(this);
  jsHandle.eval("alert('Hallo Welt')");
  }
  public void paint(Graphics g) {
  g.drawString("Das Java-Applet ruft zuerst die JavaScript-Funktion auf", 5, 25);
  }  }

Abbildung 15.6:  Das Applet ruft zuerst eine JavaScript-Funktion auf - Netscape Navigator

Abbildung 15.7:  Der Internet Explorer kann  es auch.

Beachten Sie als ersten Punkt den import-Befehl für die Netscape-spezifischen Klassen. Wenn sich diese nicht im Suchpfad des JDK bzw. Ihrer Java-Entwicklungsumgebung befinden, muss beim Kompilieren der Pfad dorthin unter Umständen explizit beim Compiler angegeben werden. Dies kann mit der Option -classpath erfolgen. Etwa so:

javac -classpath C:\Programme\Netscape\Communicator\Program\java\classes\java40.jar 
Applet2.java

In der Regel ist nach der Installation des Communicators unter Windows im Verzeichnis \Programme\Netscape\Communicator\Program\java\classes das benötigte jar-Paket zu finden.

Abbildung 15.8:  Die Netscape-Erweiterungen für Java

Um nun auf JavaScript aus Java heraus zugreifen zu können, benötigen Sie einen Handle auf ein JavaScript-Objekt. Den erhalten Sie, indem Sie eine JSObject-Variable anlegen und mit der Methode getWindow([Applet]) dieser dann das Applet-Fenster zuweisen. Dabei wird für das übergebene Applet in der Regel this (also das aktuelle Applet-Fenster) verwendet. Bester Platz für eine solche Zuweisung ist die init()-Methode. Mit dem JavaScript-Objekt haben Sie dann diverse Methoden für den Zugriff auf JavaScript-Funktionalitäten zur Verfügung. Wir verwenden hier in dem Beispiel nur die eval()-Methode, um damit eine einfache JavaScript-Anweisung aufzurufen. Interessant ist aber auch die Methode getMember(), um damit einzelne Bestandteile einer Webseite gezielt anzusprechen. Die Methode bekommt als Parameter einen String übergeben, der das Element spezifiziert. Die Methoden können verschachtelt angewandt werden. Im Allgemeinen ist der Übergabewert der ersten getMember()-Methode document für die Webseite. Dieser Rückgabewert wird dann auf ein JSObject gecastet. Dann wird darauf die getMember()-Methode mit dem Namem des HTML-Elements angewandt. Etwa für den Fall eines Formulars mit dem Namen meineForm in einer Webseite sieht das skizziert so aus:

JSObject jsHandle, jsDoc, jsForm;
...
jsHandle = JSObject.getWindow(this);
jsDoc = (JSObject)jsHandle.getMember("document");
jsForm = (JSObject)jsDoc.getMember("meineForm");

Das Verfahren kann so auf jedes Element einer Webseite fortgesetzt werden, wenn man die Verschachtelungen der HTML-Elemente entsprechend beachtet.

Aber zurück zu unserem Beispiel. Wir sind noch nicht ganz fertig, denn die HTML-Datei zum Einbinden des Applets muss noch leicht modifiziert werden. Das <APPLET>-Tag benötigt zwingend das Attribut MAYSCRIPT. Ohne das Attribut wird der Browser in der Regel den Zugriff auf JavaScript aus Java heraus nicht gestatten. Die HTML-Datei sieht also so aus:

<HTML><BODY>
<APPLET CODE = "Applet2.class" 
WIDTH = 350 HEIGHT = 80 MAYSCRIPT></APPLET>
</BODY></HTML>

Abbildung 15.9:  Ohne MAYSCRIPT geht es nicht.

Java - JavaScript-Verbindung über document.applets

Zusätzlich zu dem LiveConnect-Konzepts gibt es auch andere Möglichkeiten, aus JavaScript heraus Java zu nutzen, und zwar sowohl im Navigator (mit ein paar Einschränkungen) als auch im Internet Explorer und Opera, wobei letzterer gelegentlich Ärger machen kann. Das Verfahren beruht darauf, dass man mittels der Syntax document.applets[] ein Applet aus JavaScript heraus ansprechen kann. Dies erfolgt über die Syntax

document.[Applet].[Appletmethode/Appleteigenschaft].

Man hat direkten Zugriff auf alle öffentlichen Methoden und Eigenschaften. So kann man beispielsweise immer auf die Standardmethoden von einem Applet (init(), start(), stop() usw.) zugreifen.

Ein weiteres Beispiel mit dem Zugriff auf Variablen und Methoden in einem Java-Applet und Zugriff auf die Rückgabewerte aus dem Applet innerhalb von JavaScript mit dieser Technik soll nun das Verfahren zeigen. Es geht um die Überprüfung eines Passwortes in einer Webseite, das in einem Java-Applet versteckt und dort überprüft wird. Das Java-Applet verbirgt bei geeigneter Programmierung alle Informationen. Wir verwenden zwar in dem Beispiel aus Gründen der Übersichtlichkeit ein hardcodiertes Passwort, aber Java stellt natürlich zahlreiche Möglichkeiten bereit, um Passwörter individuell zu verwalten. Der Java-Code könnte so aussehen:

import java.awt.Graphics; 
public class Passw extends java.applet.Applet {
public String pw;
private String zugang = "Sesam";
public int ueberprPW() {
if (pw.equals(zugang)) {
return 1;
}
else {
return 2;
}  }
public void paint(Graphics g) {
g.drawString("Ein Java-Applet als Blackbox zum Überprüfen des Passwortes", 5, 25);
}  }

Die Methode public void paint(Graphics g) und die am Anfang notierte Zeile import java.awt.Graphics; sind im Prinzip für die Funktionalität des Applets als Zugangskontrollmechanismus nicht notwendig. Sie dienen nur dazu, dass überhaupt etwas von dem Applet zu sehen ist (was aber für eine Funktionalität wie gesagt absolut nicht notwendig ist).

Der Rest des Applets ist einfach. Wichtig sind die zwei folgenden Variablendeklarationen :

public String pw;
private String zugang = "Sesam";

Die erste Zeile deklariert eine String-Variable unter Java als public. Darauf kann auf JavaScript heraus zugegriffen werden. Sie wird das in einem HTML-Formular eingegebene und an eine JavaScript-Funktion übergebene Passwort aufnehmen. Die zweite Zeile hingegen deklariert eine String-Variable als private. Diese Variable ist somit nicht öffentlich, was in unserem Zusammenhang bedeutet, dass darauf auf JavaScript heraus nicht zugegriffen werden kann (Stichwort Datenkapselung). Und sogar noch mehr. Diese Variable wird durch das äußerst zuverlässige Java-Sicherheitskonzept vor Zugriffen aus anderen Java-Klassen heraus versteckt. Diese Variable ist damit sehr effektiv versteckt. Sie enthält den Kontrollzugangswert, der natürlich nicht nach aussen gegeben werden darf (bei der Wertzuweisung setzt auch die individuelle Verwaltung von Passworten an).

Auf die unter Java als public deklariert Methode public int ueberprPW() kann hingegen wieder aus JavaScript heraus zugegriffen werden. In der - sehr einfachen - Methode wird das übergebene Passwort mit dem versteckten Kontrollwert verglichen und je nach Übereinstimmung ein anderer Rückgabewert der Methode erzeugt, der dann unter JavaScript wieder zur Verfügung steht (weil die gesamte Methode öffentlich ist). Dort kann dann auf Grund des Rückgabewertes entschieden werden, wie weiter zu verfahren ist. Selbstverständlich kann diese Entscheidung auch innerhalb des Java-Applets erfolgen (etwa das Laden von Webseiten oder der Download von Dateien direkt aus dem Applet heraus, was das Verfahren noch viel sicherer macht).

Die HTML-Datei mit dem JavaScript sieht so aus:

<HTML>
<HEAD><SCRIPT LANGUAGE="JavaScript">
function pwtest() {
document.applets[0].pw = document.forms[0].passwort.value;
if (document.applets[0].ueberprPW()==1) {
alert("Ihr Passwort ist korrekt.");
}
else {
alert("Ihr Passwort ist leider falsch.");
}  }
</SCRIPT> 
</HEAD><BODY>
<CENTER><applet code="Passw.class"
 width="400" height="80"></applet></CENTER>
<FORM name="form1">
Geben Sie Ihr Passwort ein: 
<input name="passwort" type=password><P>
<input type=button value="Ueberpruefe PW" onClick="pwtest()">
</FORM>
</BODY></HTML>

Abbildung 15.10:  Das Passwort ist falsch - Navigator

Abbildung 15.11:  So funktioniert es auch mit dem Internet Explorer - das Passwort ist korrekt

Innerhalb der HTML-Datei wird ein Applet referenziert. Das nachfolgende Formular enthält ein Eingabetextfeld, das als Passwort-Feld formatiert ist (type=password) und eine Schaltfläche, die beim Klick darauf eine JavaScript-Funktion aufruft. Die wichtigste Funktionalität des Beispiels steckt in der Zeile

document.applets[0].pw = document.forms[0].passwort.value;

Die linke Seite der Zuweisung referenziert die öffentliche Variable innerhalb des Applets (alle öffentlichen Elemente der in einer Webseite enthaltenen Applets lassen sich so ansprechen), der ein Wert zugewiesen wird. Auf der rechten Seite wird auf das Formular und dort über den Namen des Eingabefeldes auf den durch den Benutzer eingegebenen Wert zugegriffen. Dieser wird der Java-Variablen zugewiesen.

Mit der Syntax

if (document.applets[0].ueberprPW()==1)

wird innerhalb einer JavaScript-if-Abfrage der Rückgabewert der öffentlichen Java-Methode ausgewertet. Das war's.

15.3.4 Applets dynamisch schreiben

Auch jenseits des LiveConnect-Konzepts und des Zugriffs über document.applets kann man aus Scripten heraus Java nutzen. Man kann z.B. die Parameter für ein Java-Applet in einer Webseite setzen. Dabei ist ein einfacher Weg die Beeinflussung von Java-Applets bei der Referenzierung von Applets mittels des <APPLET>-Tags, indem über einen Schreibanweisung einfach die Referenz samt <PARAM>-Werte geschrieben werden, die dann vom Applet aufgenommen werden. Dies kann mit beliebigen Scriptsprachen erfolgen, denen die Webseite als Objekt bereitsteht.

Schauen wir uns zuerst die Java-Datei des Applets an. Diese beinhaltet nur drei Label, die auf einem GridLayout platziert werden. Der Inhalt der Labels wird aus den Übergabewerten der HTML-Referenz entnommen.

import java.awt.*;
import java.applet.*;
public class DynamischApp extends Applet {
  String var0;
  String var1;
  String var2;
  GridLayout gridLayout1 = new GridLayout();
  Label label1 = new Label();
  Label label2 = new Label();
  Label label3 = new Label();
  /**Parameterwert holen  */
  public String getParameter(String key, String def) {
      return (getParameter(key) != null) ? getParameter(key) : def;
  }
  /**Das Applet konstruieren*/
  public DynamischApp() {
  }
  /**Das Applet initialisieren*/
  public void init() {
    try {
      var0 = this.getParameter("param0", "");
    }
    catch(Exception e) {
      e.printStackTrace();
    }
    try {
      var1 = this.getParameter("param1", "");
    }
    catch(Exception e) {
      e.printStackTrace();
    }
    try {
      var2 = this.getParameter("param2", "");
    }
    catch(Exception e) {
      e.printStackTrace();
    }
    try {
      initial();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
  /**Initialisierung der Komponenten*/
  private void initial() throws Exception {
    label1.setAlignment(1);
    label1.setText(var0);
    this.setLayout(gridLayout1);
    label2.setAlignment(1);
    label2.setText(var1);
    label3.setAlignment(1);
    label3.setText(var2);
    gridLayout1.setRows(3);
    this.add(label1, null);
    this.add(label2, null);
    this.add(label3, null);
  }
  /**Applet-Information holen*/
  public String getAppletInfo() {
    return "Applet-Information";
  }
  /**Parameter-Infos holen*/
  public String[][] getParameterInfo() {
    String[][] pinfo = 
      {
      {"param0", "String", ""},
      {"param1", "String", ""},
      {"param2", "String", ""},
      };
    return pinfo;
  }  }

Die HTML-Datei zum Referenzieren sieht so aus:

<HTML><BODY>
<APPLET CODE = "DynamischApp.class"
  WIDTH = 200 HEIGHT = 100>
<PARAM NAME = "param0" VALUE = "In der Wueste">
<PARAM NAME = "param1" VALUE = "gibt es">
<PARAM NAME = "param2" VALUE = "zu wenig Wasser">
</APPLET>
</BODY></HTML>

Abbildung 15.12:  Das Applet mit Original-HTML-Datei

Wir könnten nun zur Abwechselung VBScript verwenden. Visual Basic Script oder kurz VBScript ist eine abgespeckte Version von Visual Basic für Anwendungen (VBA), die ursprünglich nur für den Microsoft Internet Explorer verfügbar war. VBScript ähnelt JavaScript darin, dass es nur innerhalb von HTML-Dokumenten zu referenzieren ist. Es gibt aber auch einige Unterschiede. Während JavaScript in seiner Syntax Java (oder C und C++) ähnlicher ist, ähnelt VBScript eher dem Visual Basic Code. Mit VBScript können Sie Visual Basic-ähnlichen Code in Ihre Dokumente einbetten. Wenn ein VBScript-fähiger Webbrowser auf das <SCRIPT>-Tag stößt, wird der Code kompiliert und dann ausgeführt. In dem nachfolgenden Beispiel könnten wir die VBScrpt-Anweisung document.write "..." verwenden, die unter JavaScript mit Klammern und abschließendem Semikolon notiert wird (document.write("...");). Wir bleiben aber bei JavaScript, da das auch von Navigator und Opera verstanden wird.

<HTML>
<HEAD><SCRIPT LANGUAGE="JavaScript">
document.write('<BODY><APPLET CODE = "DynamischApp.class" WIDTH = 200  HEIGHT = 100>');
document.write('<PARAM NAME = "param0" VALUE = "Im Meer">');
document.write('<PARAM NAME = "param1" VALUE = "dafuer">');
document.write('<PARAM NAME = "param2" VALUE = "umso mehr">');
document.write('</APPLET></BODY></HTML>');
</SCRIPT></HEAD>
<BODY></BODY></HTML>

Abbildung 15.13:  Das Applet mit neu geschriebener HTML-Datei




15.3.5 Verbindung mit weiteren Techniken

Neben der schon angesprochenen Verbindung mit VBScript (Informationen dazu gibt bei Microsoft unter http://www.microsoft.com/intdev) kann man Java auch mit VRML koppeln. VRML ist die Abkürzung für Virtual Reality Modeling Language und ist ein Konstruktionsmittel für den Cyberspace. Es wurde ungefähr zeitgleich mit Java erstmals der Internet-Öffentlichkeit präsentiert. VRML ist ein offener Sprachstandard zur Entwicklung von virtuellen Welten.

VRML wurde aus HTML entwickelt. Zwar steht VRML inzwischen für Virtual Reality Modeling Language. Ursprünglich war es jedoch die Abkürzung für Virtual Reality Markup Language, um die Herkunft HTML deutlich zu machen. VRML und HTML waren vom Aufbau her ursprünglich recht ähnlich, jedoch ist VRML natürlich viel mächtiger und umfangreicher, um die dreidimensionalen Effekte beschreiben zu können.

Geburtsstunde für VRML war im Frühjahr 1994 auf der alljährlich stattfindenden WWW-Konferenz in Genf. Der erste offizielle Standard 1.0 wurde im April 1995 bekannt gegeben. Für die Normung und Koordination von VRML gründeten Pioniere der ersten VRML-Stufe eine Arbeitsgruppe (VAG = VRML Architecture Group), die die Geschicke von VRML in Zukunft lenken sollte.

VRML soll weder eine direkte Konkurrenz zu der Sprache Java, noch eine Ablösung von HTML sein, sondern ist momentan eher als Ergänzung beider Sprachen (natürlich mit unterschiedlichen Schwerpunkten der Erweiterungen) zu sehen. VRML lässt sich durch seine Offenheit recht leicht mit Sprachelementen anderer Internetsprachen kombinieren und mit eigenen Funktionen erweitern. Mittlerweile werden in die VRML-Welten gerne Java-Elemente integriert, die dann für einen Teil der Interaktion mit dem Cyberspace-Surfer zuständig sind. VRML wird also meist für die Konstruktion einer Welt verwendet, während darin vorkommende Objekte oft Java-Applets sind. Ein anschauliches Beispiel ist ein Haus mit diversen Zimmern und Stockwerken (VRML), wo die unterschiedlichsten Objekte (Java-Applets) vorkommen. Durch das Haus - den VRML-Cyberspace - kann man sich bewegen, in die Zimmer eintreten oder die Stockwerke wechseln. Ein klingelndes Telefon, ein Wasserhahn mit fließendem Wasser oder ein Radio mit einstellbarem Sender könnten dort interaktive Java-Applets sein.

Statt Hypertexten legt VRML dreidimensionale Szenarien fest, die auf Grund dieser Beschreibung bei jeder Bildschirmdarstellung von dem VRML-Browser jeweils neu berechnet werden. Im Cyberspace werden bislang starre »statische« WWW-Seiten zu dynamischen dreidimensionalen Welten.

Es gibt durchaus auch Verbindungsmöglichkeiten von Java mit noch weiteren Techniken wie sogar einer so alten Sprache wie Basic und Derivaten. Visual Basic-Programme lassen sich mit einem einfachen Konvertierungsprogramm nach Java übersetzen. Es gibt zumindest Tools, die das versprechen.

15.4 Zusammenfassung

Java lässt sich mit anderen Sprachen durchaus kombinieren. Die direkte Verbindung mit nativem Code - etwa C - ist jedoch mit größter Vorsicht durchzuführen und nicht-trivial. Es ist zudem gefährlich, da das Java-Sicherheitskonzept ausgehebelt wird.

JNI erleichtert die Verbindung von Java und C, stellt aber die grundsätzlichen Probleme und Bedenken bei der direkten Verbindung von Java und nativem Code nicht in Frage. Alternative Wege über externe Schnittstellen sind sicherer und einfacher zu realisieren, auch wenn sie auf Kosten der Performance gehen. Die Verbindung von Java und C++ ist noch schwieriger als die Java-C-Verbindung.

Die Verbindung von Java mit Scriptsprachen ist auf einer anderen Ebene angesiedelt. Hier sollte man eher von einer Ergänzung der unterschiedlichen Welten sprechen. Insbesondere ist die Verbindung - zumindest in die Richtung - »Java aus JavaScript heraus nutzen« nicht sonderlich kompliziert, zumal es verschiedene Wege gibt.

1

Deshalb soll und kann auch das gesamte Verfahren nur sehr theoretisch besprochen werden.


© Copyright Markt+Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH
Elektronische Fassung des Titels: Java 2 Kompendium, ISBN: 3-8272-6039-6 Kapitel: 15 Andere Sprachen in Verbindung mit Java