![]() |
|
|||||
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. 2.2.1 Kommentare
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
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()3 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()4 -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.javaclass 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. |
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.
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.
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.
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.
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. |
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.
| Zeichen | Unicode |
| Ä,ä | \u00c4, \u00e4 |
| Ö, ö | \u00d6, \u00f6 |
| Ü,ü | \u00dc, \u00fc |
| ß | \u00df |
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.5
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 »$«.6 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.
| 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.
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:
| 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.
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.
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.
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 Sprachen7 . 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 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:
| 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. |
| 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.
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.
| Schlüsselwort | Länge in Bytes | Belegung (Wertebereich) |
| 1 | true oder false | |
| 2 | 16-Bit Unicode Zeichen (0x0000...0xffff) | |
| 1 | -2^7 bis 2^7 – 1 (-128...127) | |
| 2 | -2^15 bis 2^15 – 1 (-32768...32767) | |
| 4 | -2^31 bis 2^31 – 1 (-2147483648...2147483647) | |
| 8 | -2^63 bis 2^63 – 1 (-9223372036854775808...9223372036854775807) | |
| 4 | 1.40239846E-45f…3.40282347E+38f | |
| 8 | 4.94065645841246544E-324... 1.79769131486231570E+308 |
Zwei wesentliche Punkte zeichnen die primitiven Datentypen aus:
| 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. |
| 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. |
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.
|
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 Definition8 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.9 |
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.javaclass 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.