Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 2 Sprachbeschreibung
  gp 2.1 Anweisungen und Programme
  gp 2.2 Programme
    gp 2.2.1 Kommentare
    gp 2.2.2 Funktionsaufrufe als Anweisungen
    gp 2.2.3 Die leere Anweisung
    gp 2.2.4 Der Block
  gp 2.3 Elemente einer Programmiersprache
    gp 2.3.1 Textkodierung durch Unicode-Zeichen
    gp 2.3.2 Bezeichner
    gp 2.3.3 Reservierte Schlüsselwörter
    gp 2.3.4 Token
    gp 2.3.5 Semantik
  gp 2.4 Datentypen
    gp 2.4.1 Primitive Datentypen
    gp 2.4.2 Wahrheitswerte
    gp 2.4.3 Variablendeklarationen
    gp 2.4.4 Ganzzahlige Datentypen
    gp 2.4.5 Die Fließkommazahlen
    gp 2.4.6 Zeichen
    gp 2.4.7 Die Typanpassung (Das Casting)
    gp 2.4.8 Lokale Variablen, Blöcke und Sichtbarkeit
    gp 2.4.9 Initialisierung von lokalen Variablen
  gp 2.5 Ausdrücke
    gp 2.5.1 Zuweisungsoperator und Verbundoperator
    gp 2.5.2 Präfix- oder Postfix-Inkrement und -Dekrement
    gp 2.5.3 Unäres Minus und Plus
    gp 2.5.4 Arithmetische Operatoren
    gp 2.5.5 Die relationalen Operatoren
    gp 2.5.6 Logische Operatoren
    gp 2.5.7 Reihenfolge und Rang der Operatoren in der Auswertungsreihenfolge
    gp 2.5.8 Was C(++)-Programmierer vermissen könnten
  gp 2.6 Bedingte Anweisungen oder Fallunterscheidungen
    gp 2.6.1 Die if-Anweisung
    gp 2.6.2 Die Alternative wählen mit einer if/else-Anweisung
    gp 2.6.3 Die switch-Anweisung bietet die Alternative
  gp 2.7 Schleifen
    gp 2.7.1 Die while-Schleife
    gp 2.7.2 Schleifenbedingungen und Vergleiche mit ==
    gp 2.7.3 Die do/while-Schleife
    gp 2.7.4 Die for-Schleife
    gp 2.7.5 Ausbruch planen mit break und Wiedereinstieg mit continue
    gp 2.7.6 Break und Continue mit Sprungmarken
  gp 2.8 Methoden einer Klasse
    gp 2.8.1 Bestandteil einer Funktion
    gp 2.8.2 Aufruf
    gp 2.8.3 Methoden ohne Parameter
    gp 2.8.4 Parameter und Wertübergabe
    gp 2.8.5 Methoden vorzeitig mit return beenden
    gp 2.8.6 Nicht erreichbarer Quellcode bei Funktionen
    gp 2.8.7 Rückgabewerte
    gp 2.8.8 Methoden überladen
    gp 2.8.9 Vorinitialisierte Parameter bei Funktionen
    gp 2.8.10 Finale lokale Variablen
    gp 2.8.11 Finale Referenzen in Objekten und das fehlende const
    gp 2.8.12 Rekursive Funktionen
    gp 2.8.13 Die Ackermann-Funktion
    gp 2.8.14 Die Türme von Hanoi
  gp 2.9 Noch mehr Operatoren
    gp 2.9.1 Bit-Operationen
    gp 2.9.2 Vorzeichenlose Bytes in ein Integer und Char konvertieren
    gp 2.9.3 Variablen mit Xor vertauschen
    gp 2.9.4 Die Verschiebe-Operatoren
    gp 2.9.5 Setzen, Löschen, Umdrehen, Testen von Bits
    gp 2.9.6 Der Bedingungsoperator
    gp 2.9.7 Überladenes Plus für Strings

Kapitel 2 Sprachbeschreibung

Wenn ich eine Oper hundertmal dirigiert habe,
dann ist es Zeit, sie wieder zu lernen.
– Arturo Toscanini (1867–1957)

Ein Programm in Java wird nicht umgangssprachlich beschrieben, sondern ein Regelwerk und eine Grammatik definieren die Syntax und die Semantik. In den nächsten Abschnitten werden wir kleinere Beispiele für Java-Programme kennen lernen und dann ist der Weg frei für größere Programme.


Galileo Computing

2.1 Anweisungen und Programme  downtop

Java zählt zu den imperativen Programmiersprachen, in denen der Programmierer die Abarbeitungsschritte seiner Algorithmen durch Anweisungen vorgibt. Diese Befehlsform ist für Programmiersprachen gar nicht selbstverständlich, da es Sprachen gibt, die zu einer Problembeschreibung selbstständig eine Lösung finden. Die Schwierigkeit hierbei liegt darin, die Aufgabe so präzise zu beschreiben, dass das System eine Lösung finden kann. Ein Vertreter dieser Art Sprachen ist Prolog. Auch die Datenbanksprache SQL ist keine imperative Programmiersprache. Denn wie die Datenbank zu unserer Anfrage die Ergebnisse ermittelt, müssen und können wir nicht vorgeben und auch nicht sehen.

Bleiben wir bei imperativen Sprachen. Um eine Aufgabe zu lösen, müssen wir jeden Abarbeitungsschritt angeben. Abarbeitungsfolgen befinden sich in jedem Kochbuch. Betrachten wir ein ...

Rezept für Flammkuchen

Aus der Hefe, der Milch (lauwarm) und dem Zucker einen dünnflüssigen homogenen Vorteig anrühren. Diesen mit allen anderen Zutaten außer dem Wasser vermischen (am besten mit einem Knethaken oder der Küchenmaschine). Solange Wasser (ca. 2 bis 2 1/2 dl für 500 g Mehl) hinzugeben, bis sich der Teig von der Schüssel (und von den Fingern) löst und sich trotzdem noch feucht anfühlt. Teig zu einem Klumpen formen, mit Mehl bestreuen und mit einem Handtuch abgedeckt ca. 1 Stunde gehen lassen.

Diese Garzeit ist temperaturabhängig. Wenn der Teig in der geheizten Küche, auf einen hohen Schrank gestellt wird, reichen manchmal auch nur 30–40 Minuten. Das Teigvolumen sollte sich danach verdoppelt haben. Generell gilt, dass der Teig eher länger als zu kurz gehen sollte.

In der Zwischenzeit wird der Speck gewürfelt, die Zwiebeln in feine Ringe geschnitten und der Käse gerieben. Die Crème fraîche wird flüssig gerührt und mit Pfeffer, Muskatnuss und nicht zuviel Salz (da ja schon in Speck, Käse und Teig enthalten) gewürzt. Mit dem Pfeffer und der Muskatnuss nicht zu sparsam sein, die Crème sollte hinterher schon recht würzig sein.

Wenn der Teig gegangen ist, kann man schon mal den Ofen einschalten. Der Teig wird nun in zwei Teile aufgeteilt (einen Teil wieder zurücklegen). Den Teigklumpen nochmals kurz von Hand durchkneten (vorher die Hände und den Teig leicht bemehlen) und dann auf einer bemehlten Fläche auf Blechgröße ausrollen. Dabei wird der Teig sehr dünn, je dünner umso besser. Den Teig auf das bemehlte Blech legen und mit der Hälfte der Creme bestreichen. Danach den Speck und die Zwiebeln und zum Schluss den Käse draufgeben und den Kuchen im Ofen garen. Nach ca. 10 Minuten, wenn eine leichte Bräunung eintritt, ist der Flammkuchen fertig.

gp  500 g Weißmehl
gp  40 g Hefe, frisch
gp  EL Milch
gp  1 TL Zucker
gp  1 TL Wasser
gp  1 TL Salz
gp  EL Öl
gp  200 g Speck, je nach Gusto mehr oder weniger
gp  Zwiebeln, in feine Ringe geschnitten
gp  3 dl Crème fraîche
gp  200 g Käse, gerieben
gp  Pfeffer, frisch gemahlener
gp  Salz
gp  Muskatnuss, frisch gerieben

Die Vorschriften kennzeichnen eine klare Sequenz der Operationen. Dies ist eine wichtige Eigenschaft von Java. Zusätzlich erkennen wir an dieser Abarbeitungsfolge weitere wichtige Eigenschaften, die sich auf imperative Programmiersprachen übersetzen lassen: Eine Folge von Anweisungen bildet einen Block. Später werden wir diese Operationen zur Vereinfachung des Programms in einen extra Programmblock setzen und »Unterprogramm« nennen.

Des weiteren lässt sich ein Flammkuchen nicht ohne Kontrolle von Ereignissen zubereiten. Betrachten wir dazu den letzten Satz der Anleitung:

Wenn eine leichte Bräunung eintritt, ist der 
Flammkuchen fertig.

Die Abfrage von Gegebenheiten ist sehr wichtig für imperative Sprachen. Genauso gut Wiederholungen wie

Solange Wasser hinzugeben, bis sich der Teig von 
der Schüssel löst.

Hier ist eine Anweisung solange auszuführen, bis etwas gilt oder nicht mehr gilt. Interessant ist auch folgende Anweisung:

Wenn der Teig gegangen ist, kann man schon mal den 
Ofen einschalten.

Dies ist eine Nebenläufigkeit, die in herkömmlichen Sprachen nicht unterstützt wird. Während der Ofen heiß wird, können wir weiterkneten. Java unterstützt ein paralleles Abarbeiten von Programmteilen.

Elementare Anweisungen

Atomare, also unteilbare Anweisungen, werden auch elementare Anweisungen genannt. Programme bestehen in der Regel aus mehreren Anweisungen, die dann eine Anweisungssequenz ergeben. Die Laufzeitumgebung von Java führt jede einzelne Anweisung der Sequenz in der angegebenen Reihenfolge hintereinander aus.


Galileo Computing

2.2 Programme  downtop

Programme setzen sich aus Anweisungen zusammen. In Java können jedoch nicht einfach Anweisungen in eine Datei geschrieben und dem Compiler übergeben werden. Sie müssen zunächst in einen Rahmen gepackt werden. Dieser Rahmen definiert die Haupt-Klasse und eine Funktion.

Auch wenn die folgenden Programmcodezeilen am Anfang etwas befremdend wirken, werden wir sie zu einem späteren Zeitpunkt noch genauer erklären. Wir geben der folgenden Datei den Namen Main.java.

Listing 2.1   Main.java
class Main
{
  public static void main( String args[] )
  {
  // Hier ist der Anfang unserer Programme

  // Jetzt ist hier Platz für unsere eigenen Anweisungen

  // Hier enden unsere Programme
  }
}

Ein Java-Programm muss immer in einer Klasse definiert sein. Wir haben sie Main genannt, der Name ist jedoch beliebig. In geschweiften Klammern folgen benutzerdefinierte Methoden, also Funktionen, die die Klasse anbietet. Eine Funktion ist eine Sammlung von Anweisungen unter einem Namen. Mathematische Funktionen sind mit Funktionen aus Programmiersprachen vergleichbar. Eine Sinusfunktion schafft es beispielsweise zu einem gegebenen Wert den Sinus zu berechnen. Wir wissen zwar nicht, wie die Funktion das macht, aber sie kann es. Der Begriff »Funktion« und der objektorientierte Name »Methode« sind in diesem Tutorial als synonym anzusehen. Vor einer Methode stehen unterschiedliche Modifizierer. Der Modifizierer static sagt, dass die Methode auch ohne Objekt benutzt werden kann. Wir werden in den folgenden Kapiteln nur mit statischen Methoden arbeiten.

Wir programmieren hier eine besondere Funktion, die sich main() nennt. Die Schlüsselwörter davor und die Angabe in dem Paar runder Klammern hinter dem Namen müssen wir einhalten. Die Funktion main() ist für die Laufzeitumgebung etwas ganz besonderes, denn beim Aufruf des Java-Interpreters mit einem Klassennamen wird unsere Funktion als erstes ausgeführt. Demnach werden genau die Anweisungen ausgeführt, die innerhalb der geschweiften Klammern stehen. Halten wir uns fälschlicherweise nicht an die Syntax für den Startpunkt, so kann der Interpreter die Ausführung nicht beginnen und wir haben einen semantischen Fehler gemacht. Innerhalb von main() befindet sich der Name args, mit dem die Parameter angesprochen werden. Der Name ist willkürlich, wir werden allerdings immer args verwenden.

Hinter den beiden Geteilt-Zeichen // befinden sich Kommentare. Sie gelten bis zum Ende der Zeile und dienen dazu, Erläuterungen zu den Quellcodezeilen hinzuzufügen, die ihn verständlicher machen.

Programme übersetzen und starten

Der Quellcode eines Java-Programms ist so alleine nicht ausführbar. Ein spezielles Programm, der Compiler (auch Übersetzer genannt), transformiert das geschriebene Programm in eine andere Repräsentation. Ein Quellcode mit Anweisungen für Programme muss aber nicht zwingend übersetzt werden. Eine andere Gattung für ein Ablaufmodell ist der Interpreter. Er liest die Datei Schritt für Schritt ein und führt dann die Anweisungen aus. Der Compiler liest die Datei in einem Rutsch und meldet Fehler. Häufig werden Skriptsprachen interpretiert, früher waren es oft BASIC-Programme. Compiler für geläufige Programmiersprachen wie C(++) oder Delphi übersetzen den Quellcode in Maschinen-Code. Das ist Binärcode, der vom Prozessor im Computer direkt ausführbar ist. Da unterschiedliche Rechner aber unterschiedliche Prozessoren besitzen, sind die Programme nicht direkt auf verschiedenen Rechnerplattformem ausführbar. Java ist jedoch als Programmiersprache entworfen worden, die plattformunabhängig ist, sich also nicht an einen physikalischen Prozessor klammert. Der Compiler übersetzt den Quellcode nicht in Binärcode für einen konkreten Prozessor, sondern in einen plattformunabhänigen Code, den Bytecode. Ein Prozessor wie die Intel- oder AMD-CPU kann aber mit diesem Bytecode nichts anfangen. Hier hilft ein Interpreter weiter. Dieser liest Anweisung für Anweisung aus dem Bytecode und führt entsprechende Befehle auf dem Mikroprozessor aus. Daher ist Java eine kompilierte und interpretierte Sprache zugleich.

Zum Übersetzen der Programme bietet Sun im JDK den Compiler javac an. Der Interpreter heißt java. Wir haben im einführenden Kapitel über den Ablauf und die möglichen Fehler schon gesprochen.

Beispiel Das Programm mit dem Namen Main.java übersetzen und starten:
javac Main.java
java  Main

Befindet sich im Quellcode nur die kleinste Ungenauigkeit, die der Compiler nicht toleriert, gibt er einen Fehler aus. Erst wenn Bytecode erzeugt wurde, kann der Interpreter diesen ausführen. Leider sehen wir noch nichts, da wir keine Anweisung gegeben haben. Dies soll sich nun ändern, denn wir wollen lernen, wie eine Bildschirmausgabe aussieht.


Galileo Computing

2.2.1 Kommentare  downtop

Programmieren heißt nicht nur, einen korrekten Algorithmus in einer Sprache auszudrücken, sondern auch, unsere Gedanken verständlich zu formulieren. Dies geschieht beispielsweise durch eine sinnvolle Namensgebung für Programmobjekte, wie Klassen, Funktionen und Variablen. Ein selbsterklärender Klassenname hilft den Entwicklern deutlich. Doch die Lösungsidee und der Algorithmus wird auch durch die schönsten Variablennamen nicht zwingend klarer. Damit Außenstehende unsere Lösungsidee schnell nachvollziehen und verstehen und später das Programm erweitern oder abändern können, werden Kommentare in den Quelltext eingeführt. Sie dienen nur den Lesern der Programme, haben aber auf die Abarbeitung keine Auswirkungen.

Kommentarblöcke können durch /* und */ abgetrennt werden. Zwischen diesen Zeichen kann nahezu beliebiger Text stehen. Da es keinen Präprozessor gibt, ist dies Aufgabe des Compilers, die Bemerkungen aus dem Quelltext zu entfernen. Da im Einzelfall nur Zeilen auskommentiert werden sollen, ist in Java auch das in C++ verwendete // erlaubt, welches wir schon kennengelernt haben.

// Zeilenkommentar

/*
 * Blockkommentar
 */

Dokumentationskommentare

Neben dem Blockkommentar ermöglicht Java eine interessante Möglichkeit der Programmdokumentation. Die Zeichen /** und */ beschreiben einen so genannten Dokumentationskommentar (engl. Doc-Comment), welches vor Methoden- oder Klassendefinitionen angewendet wird.

Beispiel Dokumentationskommentar:
/**
 * Hier drinnen ist ein Doc-Kommentar
 */

Der Teil zwischen den Kommentaren wird mit einem externen Dienstprogramm in HTML- oder Framemaker-Dokumente umgewandelt.

Blockkommentare dürfen nicht geschachtelt sein, wobei /* nicht das Problem ist. Ein Zeilenkommentar darf aber im Blockkommentar enthalten sein.


Galileo Computing

2.2.2 Funktionsaufrufe als Anweisungen  downtop

Bisher kennen wir keine konkreten Anweisungen. Daher möchte ich jetzt eine einfache Anweisung vorstellen, die wichtig für Programme ist: der Funktionsaufruf. Allgemein hat er folgendes Format:

funktionsname();

Innerhalb der Klammern dürfen wir Parameter angeben. Es lassen sich auch Funktionen in dieser Form anwenden, die ein Ausdruck sind und ein Ergebnis zurückgeben, wie eine Sinus-Funktion. Der Rückgabewert wird dann verworfen. Im Fall der Sinus-Funktion macht das wenig Sinn, denn sie macht außer der Berechnung nichts anderes.

Wir interessieren uns für eine Funktion, die eine Zeichenkette auf dem Bildschirm ausgibt. Sie heißt println(). Die meisten Methoden verraten durch ihren Namen, was sie leisten und für eigene Programme ist es sinnvoll, aussagekräftige Namen zu verwenden. Wenn die Java-Entwickler die Methode glubschi() genannt hätten, würde uns der Sinn der Methode versteckt bleiben. println() zeigt jedoch durch den Wortstamm »print« an, dass es etwas geschrieben wird. Die Endung ln (kurz für line) bedeutet, dass noch ein Zeilenvorschubzeichen ausgegeben wird. Umgangssprachlich heißt das: Eine neue Ausgabe beginnt in der nächsten Zeile. Neben println() existiert die Bibliotheksfunktion print(), die keinen Zeilenvorschub macht.

Die printXXX() -Methoden können in Klammern unterschiedliche Parameter bekommen. Ein Parameter ist ein Wert, den wir der Funktion beim Aufruf mitgeben wollen. Wir wollen die Diskussion über Parameter aber noch etwas verschieben. Unser printXXX()-Aufruf soll lediglich Zeichenketten ausgeben. (Ein anderes Wort für Zeichenketten ist String.) Ein String ist eine Folge von Buchstaben, Ziffern und Sonderzeichen in doppelten Anführungszeichen:

"Ich bin ein String"
"Ich auch. Und ich koste 7.59 D"

Um die obere Ausgabe mit dem Funktionsaufruf und einem trennenden Zeilenvorschub auf den Bildschirm zu bringen, schreiben wir:

System.out.println( "Ich bin ein String" );
System.out.println();
System.out.println( "Ich auch. Und ich koste 7.59 D" );

Auch wenn eine Funktion keine Parameter erwartet, muss beim Aufruf hinter dem Funktionsnamen ein Klammerpaar folgen. Dies ist konsequent, da wir so wissen, dass es ein Funktionsaufruf ist und nichts anderes. Andernfalls führt es zu Verwechselungen mit Variablen.

Auch eine weitere Eigenschaft von Java wird an dem Funktionsaufruf sichtbar. Es gibt Methoden, die unterschiedliche Parameter besitzen, aber gleichen Namen tragen. Diese Funktionen nennen wir überladen. Die printXXX()-Methoden sind sogar vielfach überladen und akzeptieren neben Strings auch weitere Werte als Parameter.

Programmieren wir eine ganze Java-Klasse, die etwas auf dem Bildschirm ausgibt.

Listing 2.2   Main.java
class Main
{
  public static void main( String args[] )
  {
  // Hier ist der Anfang unserer Programme

    System.out.println( "Hallo Javanesen" );

  // Hier enden unsere Programme
  }
}
Hinweis Anweisungen wie ein Funktionsaufruf enden immer mit einem Semikolon.

Erste Idee der Objektorientierung

In einer objektorientierten Programmiersprache sind alle Methoden an bestimmte Objekte gebunden (daher der Begriff Objekt-orientiert). Betrachten wir zum Beispiel das Objekt Radio. Ein Radio spielt Musik ab, wenn der Ein-Schalter betätigt wird und ein Sender und die Lautstärke eingestellt sind. Ein Radio bietet also bestimmte Dienste (Operationen) an, wie Musik an/aus, lauter/leiser. Zusätzlich hat ein Objekt auch noch einen Zustand. Es ist zum Beispiel die Farbe oder das Baujahr. Wichtig in objektorientierten Sprachen ist, dass die Operationen und Zustände immer (und da gibt es keine Ausnahmen) an Objekte bzw. Klassen gebunden sind. (Mehr zu dieser Unterscheidung später.) Der Aufruf einer Methode auf ein Objekt richtet die Anfrage genau an ein bestimmtes Objekt. Steht in einem Java-Programm einfach nur die Anweisung lauter, so weiß der Compiler nicht, wen er fragen soll. Was ist, wenn es auch noch einen Fernseher gibt? Aus diesem Grunde verbinden wir das Objekt, das etwas kann, mit der Operation. Ein Punkt trennt das Objekt von der Operation oder dem Zustand.

So gehört auch println() zu einem Objekt, welches die Bildschirmausgabe übernimmt. Dass eine Methode immer zu einem Objekt gehört, können wir auch an unserem eigenen Programm überprüfen. main() gehört zu der Klasse Main, die später ein Objekt bilden kann. Daher können wir in Java auch nicht einfach die Ausgabeanweisung schreiben. println() gehört zu einem Objekt mit dem Namen out. Dieses Objekt ist wiederum Teil der Klasse System. Wir können das vergleichen mit einem Aufruf zum Beispiel von

BRD.aktuellerBundeskanzler.fragen( "Wieso kassieren 
ARD" +
 " und ZDF jährlich 11 Mrd. Mark Rundfunkgebühren"+
 " und müssen davon nichts abführen?" );

Mehr zu diesen Aufrufen zu einem späterem Zeitpunkt. Das obige Beispiel macht aber jetzt schon deutlich, dass Strings mit dem Plus zusammengehängt werden können.


Galileo Computing

2.2.3 Die leere Anweisung  downtop

Die einfachste Anweisung besteht nur aus einem Semikolon und ist die leere Anweisung:

;

Sie wird verwendet, wenn die Sprachgrammatik eine Anweisung vorschreibt, aber wenn in dem Programmablauf keine Anweisung vorkommen muss. So muss etwa hinter dem Schleifenkopf eine Anweisung folgen. Wir werden bei den Schleifen eine Anwendung der leeren Anweisung sehen.


Galileo Computing

2.2.4 Der Block  downtop

Ein Block innerhalb von Methoden oder statistischen Blöcken fasst eine Gruppe von Anweisungen zusammen, die hintereinander ausgeführt werden. Dazu werden die Anweisungen zwischen geschweiften Klammern geschrieben:

{
  Anweisung1;
  Anweisung2;
  ...
}

Ein Block kann überall dort verwendet werden, wo auch eine einzelne Anweisung stehen kann. Der neue Block hat jedoch eine Besonderheit für Variablen, denn der neue Block bildet einen lokalen Bereich für die darin befindlichen Anweisungen inklusive der Variablen. Blöcke können auch geschachtelt werden.


Galileo Computing

2.3 Elemente einer Programmiersprache  downtop

Wir haben nun einige Beispiele für Java-Programme gesehen und wollen nun über das Regelwerk, die Grammatik und Syntax sprechen. Wir wollen uns unter anderem über die Codierung in Unicode, die Token und Bezeichner Gedanken machen.


Galileo Computing

2.3.1 Textkodierung durch Unicode-Zeichen  downtop

Die Algorithmen für Java-Programme bestehen aus einer Folge von Anweisungen und Unterprogrammen. In Anweisungen und Funktionsnamen werden Folgen von Zeichen als Bezeichner eingesetzt, die diese Bezeichner und Funktionen kennzeichnen. Wir müssen ihnen Namen geben, und dabei dürfen wir uns der Zeichen auf der Tastatur bedienen. Der Zeichenvorrat nennt sich auch Lexikalik.

Texte werden in Java durch 16 Bit lange Unicode-Zeichen kodiert. Der Unicode-Zeichensatz beinhaltet die ASCII-Zeichen nach ISO8859-1 (Latin-1), daher gehören alle gewöhnlichen Zeichen auch zum erweiterten Zeichensatz. Da mit 16 Bit etwa 65.000 Zeichen kodiert werden können, sind auch alle wichtigen Zeichensätze für andere Schriftsprachen kodiert. Eine angenehme Konsequenz ist, dass auch der Quellcode in der Landessprache programmiert werden kann. Deutsche Umlaute stellen demnach für den Compiler kein Hindernis dar.

Tipp Obwohl Java intern Unicode für alle Bezeichner einsetzt, ist es dennoch ungünstig, Klassennamen zu wählen, die Unicode-Zeichen enthalten. Das Problem liegt nicht in der Programmiersprache begründet, sondern im Dateisystem der meisten Betriebssysteme. Sie speichern die Namen oft im alten 8-Bit ASCII-Zeichensatz ab.

Schreibweise für Unicode-Zeichen

Kaum ein Editor dürfte in der Lage sein, alle Unicode-Zeichen anzuzeigen. Beliebige Unicode-Zeichen lassen sich als \uxxxx schreiben, wobei x eine hexadezimale Ziffer ist – also 0..1, A..F bzw. a..f. Diese Sequenzen können an beliebiger Stelle eingesetzt werden. So können wir anstatt eines Anführungszeichens alternativ \u0027 schreiben, und dies wird vom Compiler als gleichwertig angesehen. Das Unicode-Zeichen \uffff ist nicht definiert und kann daher bei Zeichenketten als Ende-Symbol verwendet werden. Unicode-Zeichen für deutsche Sonderzeichen sind folgende.

Tabelle 2.1   Deutsche Sonderzeichen in Unicode
Zeichen Unicode
Ä,ä \u00c4, \u00e4
Ö, ö \u00d6, \u00f6
Ü,ü \u00dc, \u00fc
ß \u00df

Anzeige der Unicode-Zeichen

Die Darstellung der Zeichen ist unter den meisten Plattformen noch ein großes Problem. Die Unterstützung für die Standardzeichen des ASCII-Alphabets ist dabei weniger ein Problem als die Sonderzeichen, die der Unicode-Standard definiert. Unter ihnen zum Beispiel der beliebte Smiley :-), der als Unicode \u263A (WHITE SMILING FACE) und \u2369 (WHITE FROWNING FACE) :-( definiert ist. Das vermutlich in nächster Zeit häufig benutzte Euro-Zeichen ist unter \u20ac zu finden.

Tipp Sofern die Sonderzeichen und Umlaute sich auf der Tastatur befinden, sollten keine Unicode-Kodierungen Verwendung finden. Der Autor von Quelltext sollte seine Leser nicht zwingen, eine Unicodetabelle zur Hand zu haben. Die Alternativdarstellung lohnt sich daher nur, wenn der Programmtext bewusst unleserlich gemacht werden soll.

Ein Versuch, den Smiley auf die Standardausgabe zu drucken, scheitert oft an der Fähigkeit des Terminals bzw. der Shell. Hier ist eine spezielle Shell nötig, die aber bei den meisten Systemen erst noch in der Entwicklung ist. Und auch bei grafischen Oberflächen ist die Integration noch mangelhaft. Es wird Aufgabe der Betriebssystementwickler bleiben, dies zu ändern.


Galileo Computing

2.3.2 Bezeichner  downtop

Für Variablen (und damit Konstanten), Methoden, Klassen und Schnittstellen werden Bezeichner vergeben, die die entsprechenden Bausteine anschließend im Programm identifizieren. Unter Variablen sind dann Daten verfügbar, Methoden sind die Prozeduren in objektorientierten Programmiersprachen und Klassen sind die Bausteine objektorientierter Programme.

Ein Bezeichner ist eine Folge von Zeichen, die fast beliebig lang sein kann. (Die Länge ist nur theoretisch festgelegt). Die Zeichen sind Elemente aus dem gesamten Unicode-Zeichensatz und jedes Zeichen ist für die Identifikation wichtig. Das heißt, ein Bezeichner, der 100 Zeichen lang ist, muss auch immer mit allen 100 Zeichen korrekt angegeben werden. Manche C- und FORTRAN-Compiler sind in dieser Hinsicht etwas großzügiger und bewerten nur die ersten Stellen. Jeder Bezeichner muss mit einem Unicode-Buchstaben beginnen. Dies sind Unicode-Zeichen zum Beispiel aus dem Bereich »A« bis »Z« und »a« bis »z« (nicht beschränkt auf lateinische Zeichen) sowie »_« und »$«. Nach dem ersten Buchstaben können neben den Buchstaben auch Ziffern folgen. Dass der Unterstrich mittlerweile mit zu den Buchstaben zählt, ist nicht weiter verwunderlich, doch dass das Dollarzeichen mitgezählt wird, ist schon erstaunlich. Sun erklärt den Einsatz einfach damit, dass diese beiden Zeichen »aus historischen Gründen« mit aufgenommen wurden. Eine sinnvollere Erklärung ist, dies mit der Verwendung von maschinen-generiertem Code zu erklären. Es bleibt noch einmal zu erwähnen, dass zwischen Groß/Kleinschreibung unterschieden wird.

Die folgende Tabelle listet einige gültige und ungültige Bezeichner auf.

Tabelle 2.2   Beispiele für Bezeichner in Java
Gültige Bezeichner Ungültige Bezeichner
mami 2und2macht4
kulliReimtSichAufUlli class
IchWeißIchMussAndréAnrufen hose gewaschen
raphaelIstLieb hurtig!

mami ist genau ein Bezeichner, der nur aus Alphazeichen besteht und daher korrekt ist. Auch kulliReimtSichAufUlli ist korrekt. Der Bezeichner zeigt zusätzlich die in Java übliche Bezeichnerbildung; denn besteht dieser aus mehreren einzelnen Wörtern, werden diese einfach ohne Leerzeichen hintereinandergesetzt, jedes Teilwort (außer dem ersten) beginnt jedoch dann mit einem Großbuchstaben. Leerzeichen sind in Bezeichnern nicht erlaubt und daher ist auch hose gewaschen ungültig. Auch das Ausrufezeichen ist, wie viele Sonderzeichen, ungültig. IchWeißIchMussAndréAnrufen ist jedoch wieder korrekt, auch wenn es ein Apostroph-é enthält. Treiben wir es weiter auf die Spitze, dann sehen wir einen gültigen Bezeichner, der nur aus griechischen Zeichen gebildet ist. Auch der erste Buchstabe ist ein Zeichen, anders als in 2und2macht4. Und class ist ebenso ungültig, da der Name schon von Java belegt ist. Die Tabelle im nächsten Abschnitt zeigt uns, welche Namen wir nicht verwenden können. Für class nehmen Programmierer als Ersatz gerne clazz.


Galileo Computing

2.3.3 Reservierte Schlüsselwörter  downtop

Bestimmte Wörter sind als Bezeichner nicht zulässig, da sie als Schlüsselwörter durch den Compiler besonders behandelt werden. Schlüsselwörter bestimmen die »Sprache« eines Compilers. Nachfolgende Zeichenfolgen sind Schlüsselwörter in Java und daher nicht als Bezeichnernamen möglich:

Tabelle 2.3   Reservierte Schlüsselwörter in Java
abstract boolean break byte
byvalue† case cast† catch
char class const† continue
default do double else
extends final finally float
for future† generic† goto†
if implements import instanceof
int inner† interface long
native new null operator†
outer† package private protected
public rest† return short
static strictfp super switch
synchronized this throw throws
transient try var† void
volatile while    

Obwohl die mit † gekennzeichneten Wörter zurzeit nicht von Java benutzt werden, können doch keine Variablen dieses Namens definiert werden.


Galileo Computing

2.3.4 Token  downtop

Ein Token ist eine lexikalische Einheit, die dem Compiler die Bausteine des Programms liefern. Der Compiler erkennt an der Grammatik einer Sprache, welche Folgen von Zeichen ein Token bilden. Für Bezeichner heißt dies beispielsweise: Nimm die nächsten Zeichen, solange auf einen Buchstaben nur Buchstaben oder Ziffern folgen. Eine Zahl wie 1982 bildet zum Beispiel ein Token durch folgende Regel: Lese solange Ziffern, bis keine Ziffer mehr folgt. Bei Kommentaren bilden die Kombinationen /* und */ ein Token.

Problematisch wird es in einer Sprache immer dann, wenn der Compiler die Token nicht voneinander unterscheiden kann. Daher fügen wir Trennzeichen (engl. White-Spaces), auch Wortzwischenräume genannt, ein. Zu den Trennern zählen Leerzeichen, Tabulatoren, Zeilenvorschub- und Seitenvorschubzeichen. Außer als Trennzeichen haben diese Zeichen keine Bedeutung. Daher können sie in beliebiger Anzahl zwischen die Token gesetzt werden. Das heißt auch, beliebig viele Leerzeichen sind zwischen Token gültig. Und da wir damit nicht geizen müssen, können sie einen Programmabschnitt enorm verdeutlichen. Programme sind besser lesbar, wenn sie luftig formatiert sind.


Galileo Computing

2.3.5 Semantik  downtop

Die Syntax eines Java-Programms definiert die Token und bildet so das Vokabular. Richtig geschriebene Programme müssen aber dennoch nicht korrekt sein. Unter dem Begriff »Semantik« fassen wir daher die Bedeutung eines syntaktisch korrekten Programms zusammen. Die Semantik bestimmt daher, was das Programm macht. Die Abstraktionsreihenfolge ist also Lexikalik, Syntax und Semantik. Der Compiler durchläuft diese Schritte bevor er den Bytecode erzeugen kann.


Galileo Computing

2.4 Datentypen  downtop

Java nutzt, wie es für imperative Programmiersprachen typisch ist, Variablen zum Ablegen von Daten. Eine Variable ist ein reservierter Speicherbereich und belegt eine feste Anzahl von Bytes. Alle Variablen (und auch Ausdrücke) haben einen Typ, der zur Übersetzungszeit bekannt ist. Der Typ wird auch Datentyp genannt, da eine Variable einen Datenwert, auch »Datum« genannt, hält. Für jeden Typ lässt sich die Speichergröße berechnen. Beispiele für Datentypen sind: Ganzzahlen, Fließkommazahlen, Wahrheitswerte, Zeichen und Objekte. Da jede Variable einen festen Datentyp hat, zählt Java zu den streng-typisierten Sprachen . Der Datentyp erlaubt dem Übersetzer auch, die Daten im Speicher nach bestimmten Regeln zu behandeln. Wenn wir einen Speicherauszug lesen und dort die Bitinformationen 01011010, 11010010, 01010011, 10100010 finden, ist uns auch nicht klar, ob dies nun vier Buchstaben sind, eine Fließkommazahl oder eine Ganzzahl ist.

Die grobe Richtung

Die Datentypen in Java zerfallen in zwei Kategorien: Primitive Typen und Referenztypen (auch Klassentypen). Die primitiven Typen sind die eingebauten Datentypen, die nicht als Objekte verwaltet werden. Referenztypen sind genau das, was noch übrig bleibt. Es gibt aber auch Sprachen, die keine primitiven Datentypen besitzen. Als Beispiel sei noch einmal auf das unten angesprochene Smalltalk verwiesen.

Warum Sun sich für diese Teilung entschieden hat, lässt sich aus zwei Gründen erklären:

gp  Viele Programmierer kennen Syntax und Semantik von C(++) und anderen imperativen Programmiersprachen. Auf die neue Sprache Java zu wechseln fällt leicht, und das objektorientierte Denken aus C++ verhilft dazu, sich auf der Insel zurechtzufinden.
gp  Der andere Grund ist die Geschwindigkeit der häufig vorkommenden elementaren Rechenoperationen. Ein Compiler kann einerseits viele Optimierungen vornehmen, und das ist bei einer Sprache, die in der Industrie eingesetzt wird, ein wichtiges Kriterium. Die Klassen liefern andererseits die optimale Unterstützung für eine Wiederverwendung und skalierbare Produkte.

Wir werden uns im Folgenden erst mit primitiven Datentypen beschäftigen. Referenzen werden nur dann eingesetzt, wenn Objekte ins Spiel kommen. Dies dauert jedoch noch etwas.


Galileo Computing

2.4.1 Primitive Datentypen  downtop

In Java gibt es einige eingebaute Datentypen für ganze Zahlen, Gleitkommazahlen nach IEEE 754, Zeichen, Wahrheitswerte. (Strings werden bevorzugt behandelt, sind aber lediglich Verweise auf Objekte.) Die folgende Tabelle gibt darüber einen Überblick. Anschließend betrachten wir jeden Datentyp präziser.

Tabelle 2.4   Java-Datentypen und deren Wertebereiche
Schlüsselwort Länge in Bytes Belegung (Wertebereich)

boolean

1 true oder false

char

2 16-Bit Unicode Zeichen (0x0000...0xffff)

byte

1 -2^7 bis 2^7 – 1 (-128...127)

short

2 -2^15 bis 2^15 – 1 (-32768...32767)

int

4 -2^31 bis 2^31 – 1 (-2147483648...2147483647)

long

8 -2^63 bis 2^63 – 1 (-9223372036854775808...9223372036854775807)

float

4 1.40239846E-45f…3.40282347E+38f

double

8 4.94065645841246544E-324... 1.79769131486231570E+308

Zwei wesentliche Punkte zeichnen die primitiven Datentypen aus:

gp  Alle Datentypen haben eine festgesetzte Länge, die sich unter keinen Umständen ändert. Der Nachteil, dass der Programmierer die Länge eines Datentyps nicht kennt, besteht in Java nicht. In den Sprachen C/C++ bleibt dies immer unsicher und die Umstellung auf 64-Bit-Maschinen bringt viele Probleme mit sich. Bei der Betrachtung der Auflistung fällt auf, dass char 16 Bit lang ist.
gp  Die numerischen Datentypen byte, short, int und long sind vorzeichenbehaftet, Fließkommzahlen sowieso. Dies ist leider nicht immer praktisch, aber wir müssen uns stets daran erinnern. Probleme gibt es, wenn wir einem Byte zum Beispiel den Wert 240 zuweisen wollen. Ein char ist im Prinzip ein vorzeichenloser Ganzzahltyp.

Galileo Computing

2.4.2 Wahrheitswerte  downtop

Der Datentyp boolean beschreibt einen Wahrheitswert, der entweder true oder false ist. Die Zeichenketten true und false sind reservierte Wörter und bilden so genannte Literale. Kein anderer Wert ist für Wahrheitswerte möglich.

Der boolesche Typ wird beispielsweise bei Bedingungen, Verzweigungen oder Schleifen benötigt.

Abbildung


Galileo Computing

2.4.3 Variablendeklarationen  downtop

Mit Variablen lassen sich Daten speichern, die vom Programm gelesen und geschrieben werden können. Um Variablen zu nutzen, müssen sie deklariert werden. Wir sprechen hier auch von der Definition einer Variablen. Die Schreibweise einer Variablendeklaration ist immer die Gleiche: Hinter dem Typnamen folgt der Name der Variablen.

Typname Variablenname;

Ein Variablenname (der dann Bezeichner ist) kann alle Buchstaben und Ziffern des Unicode-Zeichensatzes beinhalten, mit der Ausnahme, dass am Anfang einer Zeichenkette keine Ziffer stehen darf. Ebenfalls darf der Variablenname mit keinem reservierten Schlüsselwort identisch sein.

Hinweis Zwei Variablen ähnlicher Schreibweise, etwa counter und counters, führen schnell zur Verwirrung. Als Programmierer sollten wir uns konsistent an ein Namensschema halten.

Die Variablendeklaration ist eine Anweisung und wird daher mit einem Semikolon abgeschlossen.

Werden mehrere Variablen gleichen Typs bestimmt, so können diese mit einem Komma getrennt werden. Eine Deklaration kann in jeden Block geschrieben werden:

Typname Variablenname1[, Variablenname2, ... ]

Schreiben wir ein einfaches Programm, welches eine Wahrheitsvariable definiert und zuweist. Die Variablenbelegung erscheint zusätzlich auf dem Bildschirm.

Listing 2.3   FirstVariable.java
class FirstVariable
{
  public static void main( String args[] )
  {
    boolean trocken;

    trocken = true;

    System.out.print( "Ist die Socke trocken? " );
    System.out.println( trocken );
  }
}

Die Zeile trocken = true ist eine Zuweisung, die die Variable trocken mit einem Wert belegt. Sie ist ebenfalls eine Anweisung und wird mit einem Semikolon beendet. Steht auf der rechten Seite keine Variable, so steht dort ein Literal, eine Konstante, wie in unserem Fall true. Wir haben schon erwähnt, dass es für Wahrheitswerte nur die Literale true und false gibt.


Galileo Computing

2.4.4 Ganzzahlige Datentypen  down