Beruflich Dokumente
Kultur Dokumente
Der Text, die Abbildungen und Programme wurden mit größter Sorgfalt erarbeitet. Der Autor
kann dennoch für möglicherweise verbliebene fehlerhafte Angaben und deren Folgen weder eine
juristische Verantwortung noch irgendeine Haftung übernehmen. Die in diesem Text erwähnten
Software– und Hardwarebezeichnungen sind in den meisten Fällen auch eingetragene Marken und
unterliegen als solche den gesetzlichen Bestimmungen.
Copyright 1999, 2000, Andreas Keese. Alle Rechte vorbehalten
Inhaltsverzeichnis
1 Einleitung 6
1.1 Lehrbücher zu Java . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2 Über diesen Text . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1
5 Grundlagen der Java-Syntax 28
5.1 Grundsätzliches zur Syntax von Java–Programmen . . . . . . . . 28
5.2 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 32
7 Ausdrücke 36
7.1 Was ist ein Ausdruck ? . . . . . . . . . . . . . . . . . . . . . . . . 36
7.2 Literal–Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . 38
7.2.1 Ganzzahlige Literale . . . . . . . . . . . . . . . . . . . . . 39
7.2.2 Reellwertige Literale . . . . . . . . . . . . . . . . . . . . . 40
7.2.3 Litarale vom Typ boolean . . . . . . . . . . . . . . . . . . 41
7.2.4 Zeichenliterale . . . . . . . . . . . . . . . . . . . . . . . . 42
7.2.5 Zeichenketten . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.3 Verwendung von Variablen . . . . . . . . . . . . . . . . . . . . . 44
7.4 Ausdrücke mit Operatoren . . . . . . . . . . . . . . . . . . . . . . 46
7.4.1 Arithmetische Operatoren . . . . . . . . . . . . . . . . . . 49
7.4.2 Inkrement und Dekrement–Operatoren . . . . . . . . . . . 49
7.4.3 Relationale Operatoren . . . . . . . . . . . . . . . . . . . 52
7.4.4 Logische Operatoren . . . . . . . . . . . . . . . . . . . . . 52
7.4.5 Bitweise Operatoren . . . . . . . . . . . . . . . . . . . . . 53
7.4.6 Zuweisungsoperatoren . . . . . . . . . . . . . . . . . . . . 53
7.4.7 Weitere Operatoren . . . . . . . . . . . . . . . . . . . . . 55
7.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 56
8 Typwandlungen 57
8.1 Automatische Typkonvertierungen . . . . . . . . . . . . . . . . . 57
8.2 Manuelle Typkonvertierungen . . . . . . . . . . . . . . . . . . . . 60
8.3 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 61
2
9.7.1 Die While–Schleife . . . . . . . . . . . . . . . . . . . . . . 67
9.7.2 Die Do–Schleife . . . . . . . . . . . . . . . . . . . . . . . . 69
9.7.3 Die For–Schleife . . . . . . . . . . . . . . . . . . . . . . . 70
9.7.4 break und continue . . . . . . . . . . . . . . . . . . . . . . 74
9.8 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 75
10 Objekte in Java 75
10.1 Objekte und primitive Datentypen . . . . . . . . . . . . . . . . . 76
10.2 Der Lebenszyklus eines Objektes . . . . . . . . . . . . . . . . . . 78
10.2.1 Die Erzeugung von Objekten . . . . . . . . . . . . . . . . 78
10.3 Die Identität eines Objektes . . . . . . . . . . . . . . . . . . . . . 81
10.4 Die Kommunikation mit Objekten . . . . . . . . . . . . . . . . . 83
10.5 Welche Botschaften versteht ein Objekt ? . . . . . . . . . . . . . 85
10.6 Die Zerstörung von Objekten . . . . . . . . . . . . . . . . . . . . 86
10.7 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 87
11 Arrays 88
11.1 Definition von Arrays . . . . . . . . . . . . . . . . . . . . . . . . 88
11.2 Verwendung von Arrays . . . . . . . . . . . . . . . . . . . . . . . 90
11.3 Array–Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
11.4 Primitive Arrays und Objektarrays . . . . . . . . . . . . . . . . . 91
11.5 Referenztypen am Array-Beispiel . . . . . . . . . . . . . . . . . 93
11.6 Mehrdimensionale Arrays . . . . . . . . . . . . . . . . . . . . . . 93
11.7 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 95
12 Klassen in Java 96
12.1 Instanz- und Klassenbestandteile . . . . . . . . . . . . . . . . . . 96
12.2 Zugriff auf Methoden und Attribute . . . . . . . . . . . . . . . . 98
12.3 Die Bestandteile einer Java–Klasse . . . . . . . . . . . . . . . . . 99
12.4 Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
12.5 Definition von Methoden . . . . . . . . . . . . . . . . . . . . . . . 102
12.6 Mehrere Methoden mit gleichem Namen . . . . . . . . . . . . . . 106
12.7 Konstruktoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
12.8 Die Parameterübergabe an eine Methode . . . . . . . . . . . . . 108
12.9 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 109
3
14.4 Die super–Variable . . . . . . . . . . . . . . . . . . . . . . . . . . 117
14.5 Vererbung und Konstruktoren . . . . . . . . . . . . . . . . . . . . 118
14.6 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
14.7 Die Object-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . 121
14.8 Abstrakte Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . 121
14.9 Zuweisung an Variablen und Arrays . . . . . . . . . . . . . . . . 123
14.10Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 125
15 Packages 126
15.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 128
16 Exceptions 128
16.1 Try–catch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
16.2 Ausnahmen werfen . . . . . . . . . . . . . . . . . . . . . . . . . . 131
16.3 Exceptions in Methoden . . . . . . . . . . . . . . . . . . . . . . . 131
16.4 Ein paar abschließende Bemerkungen . . . . . . . . . . . . . . . . 133
16.5 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . 133
C Probleme 142
C.1 Probleme bei Verwendung von javac . . . . . . . . . . . . . . . . 142
C.2 Probleme bei Verwendung von java . . . . . . . . . . . . . . . . . 144
4
D.6 Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Inhaltsverzeichnis
5
1 Einleitung
In der Veranstaltung „Einführung in das Programmieren“ wollen wir Sie dabei
unterstützen, das Programmieren in Java1 zu erlernen. Dabei gehen wir davon
aus, daß Ihre bisherige Erfahrung im Programmieren vernachlässigbar sind.
Bitte beachten Sie:
Dieser Begleittext ist im Wintersemester 1999/2000 entstanden und ist auf die
damalige Form dieser Veranstaltung zugeschnitten. Im Sommersemester 2000
wurde der Fokus der Veranstaltung etwas verändert.
Momentan passen nur die ersten 4 Kapitel sowie die Anhänge zur momentanen
Form der Veranstaltung. Es ist nicht sicher, ob wir auch die späteren Kapitel
überarbeiten werden. Unabhängig davon können Sie die ersten 4 Kapitel den-
noch mit Gewinn für sich nutzen.
6
Das Buch ist gut geeignet, die Veranstaltung im Selbststudium zu ergän-
zen. Auf der Webseite2 des Authors finden Sie weitere Hinweise zum Buch.
• Go To Java 2
Guido Krüger
Addison–Wesley, 1999, ISBN 3–8273–1370–8, 89,90,– DM
Dieses Buch eignet sich sehr gut für Leser mit etwas Programmiererfah-
rung. Das Buch ist in einer Online–Version frei erhältlich, welche wir auf
unserem Server3 spiegeln.
Falls Sie an einem Rechner außerhalb des Netzes der TU Braunschweig
arbeiten, können Sie das Buch auf den Seiten des Authors4 lesen.
• Java in 21 Tagen
Laura Lemay, Charles L. Perkins
Markt u. Technik, 1999, 89,95,– DM.
Dieses Buch eignet sich gut für Programmieranfänger. Es ist im WWW5
frei verfügbar.
• The Java Tutorial Mary Campione, Kathy Walrath
Addison Wesley, 1998, 85,- DM
„The Java Tutorial“ richtet sich an Personen, die bereits programmieren
können. Es beschreibt mit vielen instruktiven Beispielen, wie man in Java
Fenster-, Graphik- oder Internetanwendungen programmiert.
Dabei ist aufgrund der vielen Querverweise im Buch die Html-Version ver-
mutlich besser lesbar als die gedruckte Fassung. Wir spiegeln die Online–
Fassung6 .
Wenn Sie an einem Rechner außerhalb des Netzes der TU Braunschweig
arbeiten, können Sie es auf den Seiten von Sun7 lesen.
7
zu erhalten. Außerdem können Sie anhand der Fragen prüfen, ob Sie das
Kapitel verstanden haben.
• Wir präsentieren viele Beispiele. Bitte vollziehen Sie die Programmierbei-
spiele und Kommandos am Rechner nach.
• Kommandozeilenbefehle präsentieren, schreiben wir als
> Kommando
Dabei bedeutet das führende Größerzeichen, daß Sie diesen Text auf der
Kommandozeile eingeben sollen. Es soll nicht mit eingetippt werden.
Beim Erlernen des Programmierens in Java wünschen wir Ihnen viel Erfolg !
8
Graphik 1 : Aufbau eines Computers
Dateneingabe Datenausgabe
Benutzer−
schnittstelle Tastatur Maus Drucker Bildschirm
Zentraleinheit (CPU)
9
können im ROM Ihres Computers also keine Daten ablegen. Trotzdem könnte
kein Computer ohne ROM arbeiten. Der Grund hierfür ist, daß ein Computer
nichts tun kann, das man ihm nicht in allen Einzelheiten erklärt. Er benötigt
sogar ein Programm, das ihm erklärt, wie er zu starten hat. Beim Start muß der
Computer nämlich alle angeschlossenen Geräte erkennen und in Gang setzen,
und wie er das zu tun hat, ist im ROM beschrieben.
Außerdem muß der Computer direkt nach dem Anschalten das Betriebssystem
starten, auch OS (Operating System) oder DOS (Disk Operating System) ge-
nannt. Ein Betriebssystem ist ein sehr umfangreiches Programm, welches den
Computer durch Menschen benutzbar macht. Das Betriebssystem ermöglicht
dem Computer, mit seinen Benutzern zu kommunizieren (wichtige Betriebssy-
steme sind MS DOS, MS Windows, das Mac-OS sowie Unix-Betriebssysteme
wie Linux oder Solaris). Ohne Betriebssystem wüsste der Computer nicht, was
er auf dem Bildschirm anzeigen soll, und man könnte ihn weder per Maus noch
per Tastatur bedienen.
Wie wird das Betriebssystem gestartet ? Beim Anschalten weiß der Computer
noch gar nicht, wie er das Betriebssystem von der Festplatte laden und starten
soll. Deshalb hat der Hersteller des Computers die hierfür benötigten Program-
me im ROM abgelegt. Beim Start befolgt der Computer zunächst das im ROM
gespeicherte Startprogramm und startet dabei das Betriebssystem.
Die Rechner, an denen Sie im CIP-Pool während des Kurses arbeiten werden,
laufen übrigens alle unter einer Variante des Betriebssystems Unix, welche von
der Firma IBM unter dem Namen AIX vertrieben wird. Beim Bearbeiten der
ersten Hausaufgabe werden Sie einige Dinge über Unix lernen.
10
und verarbeiten kann, nennt man übrigens die Bus-Breite des Prozesso-
res (ein Bus ist ein Bündel von Leitungen, über das Daten transportiert
werden).
Wenn die zu verarbeitenden Daten in größeren Gruppen als 16 Bit vor-
liegen, kann ein 32-Bit-Prozessor also tatsächlich schneller sein als ein
16-Bit-Prozessor. Allerdings müssen die benutzten Programme dazu auch
wirklich eine Verarbeitung in 32-Bit-Portionen vorsehen.
Bevor wir darauf eingehen, zu was für Gruppen man Bits zusammenfaßt, wollen
wir uns überlegen, wie viele Zustände man mit einer vorgegebenen Anzahl von
Bits darstellen kann.
Beispiel: Wenn wir ein Bit verwenden, können wir nur die zwei Zustände
0 und 1 darstellen.
Wenn wir zwei Bits verwenden, können wir die vier Zustände 00, 01, 10,
11 darstellen.
Drei Bits können die acht Zustände 000,001,010,011,100,101,110,111 dar-
stellen.
Es ist sehr leicht zu sehen, daß n Bits genau 2n verschiedene Zustände annehmen
können. Will man also N verschiedene Zustände beschreiben, benötigt man eine
Anzahl von Bits, die sich durch Aufrunden von log2 N ergibt.
Byte: Ein Byte besteht aus 8 Bit und kann 28 = 256 Zustände annehmen.
Word: Die Größe eines Word hängt vom verwendeten Computer ab. Es besteht
in der Regel aus sovielen Bits, wie der Computer zugleich verarbeiten
kann. Heutzutage versteht man unter einem Word meist 8 Byte oder
64 Bit.
Meist verwendet man die Begriffe Byte oder Word, um einen einzelnen Zah-
lenwert zu beschreiben. Jedem Zustand eines Bytes oder Words kann man eine
Dezimalzahl zuweisen. Dazu fasst man die Zustände der einzelnen Bits als Bin-
ärzahl auf.
Der Dezimalwert einer Binärzahl berechnet sich wie folgt: Die Bits der Zahl
werden von rechts beginnend und mit der Zahl 0 startend durchnumeriert. An-
schließend bildet man eine Summe, in welcher man für jedes an der Position i
gesetzte Bit die Zahl 2i einsetzt.
11
Beispiel: Ein weiteres Beispiel: Computer verwenden meist Byte-Werte,
um Buchstaben und andere Zeichen zu beschreiben. Dabei wird jedem
Zeichen ein anderer Zahlenwert zugewiesen. Auf meinem Rechner sehen
einige Beispiele für diese Zuordnung so aus:
Zeichen Zahlenwert Darstellung in Bits
a 97 01100001
b 98 01100010
c 99 01100011
A 65 01000001
# 35 00100011
+ 43 00101011
1 49 00110001
Prüfen Sie bitte nach, ob die dezimale Darstellung und die Bit-Darstellung
zusammenpaßt.
Wir haben nun die Grundeinheiten kennengelernt, mit denen ein Computer
operiert. Diese Einheiten werden Ihnen auch bei der Programmierung mit Java
immer wieder begegnen, denn auch wenn Sie in Java mit Zahlen rechnen, müssen
Sie manchmal angeben, in welcher Grundeinheit die Zahlen gespeichert werden
sollen.
Will man beschreiben, wieviele Daten ein Computer speichern kann, benützt
man noch größere Einheiten:
Beispiel: Neue Rechner für den Heimgebrauch oder fürs Büro haben
heutzutage in der Regel 128 MB Arbeitsspeicher oder mehr und einige
GB Festplattenspeicher; Höchstleistungsrechner haben mehrere GB Ar-
beitsspeicher und mehrere hundert GB Festplattenspeicher.
Beispiel: Wir können dem Prozessor befehlen, eine Zahl in dem Byte
an der Adresse 100 und eine weitere Zahl in dem an der Adresse 200 be-
ginnenden Word zu speichern. Anschließend können wir ihn anweisen, die
12
beiden Zahlen zu addieren und das Ergebnis an der Adresse 300 abzule-
gen. Da hierbei ein Byte und ein Word addiert wird, sollte das Ergebnis
mindestens Word-Größe haben.
Da die zweite Umrechenregel des obigen Beispiels eindeutig ist, könnte man
sie nun in ein Programm für einen Computer umsetzen. Ein Programm ist
die Formulierung eines Algorithmus in einer Programmiersprache. Wie schon
erwähnt, ist ein Computer ziemlich dumm — er versteht nur eine ganz spezielle,
für Menschen ungeeignete Sprache, die Maschinensprache. Maschinensprache
ist eine Sprache, die nur aus Zahlen besteht.
13
Beispiel: Um Sie von Maschinensprache abzuschrecken, zeigen wir Ihnen
ein Maschinensprache-Programm. Dabei zeigen wir Ihnen nicht die Zah-
len, aus denen das Programm besteht sondern eine textliche Darstellung,
in der jeder Zahl ein kurzes Wort wie movl oder imull zugeordnet wurde:
movl 30,12345(%ebp)
movl 50,23456(%ebp)
movl 3456(%ebp),%eax
imull 0xfffffff8(%ebp),%eax
Beispiel: Zum Beispiel könnten die Regeln zur Umwandlung einer Binär-
in eine Dezimalzahl in Java so lauten:
/* die Bits der Binärzahl seien in b[0], b[1] ... bis in b[n]
* gespeichert
*/
dezimal = 0;
for( i = 0; i < n ; i++ ) {
dezimal = dezimal + b[i] * 2^i;
}
return dezimal;
14
Nun hatten wir vorhin doch erwähnt, daß ein Computer nur Maschinenspra-
che versteht — wie kann er dann Programme höherer Programmiersprachen
verstehen ? Eigentlich ist die Antwort offensichtlich — immer wenn ein Com-
puter etwas tun soll, benötigt er eine Vorgehensvorschrift. Damit ein Computer
Programme höherer Programmiersprachen verstehen kann, muß ihm ein Pro-
gramm geben, das für ihn als Übersetzer arbeitet und den Programmtext in
Maschinensprache übersetzt.
Ein solches Programm nennt sich Compiler. Für jede Programmiersprache gibt
es für jeden Rechnertyp einen speziellen Compiler, der gültige Texte der Pro-
grammiersprache zu Maschinencode des jeweiligen Rechners kompiliert (kom-
pilieren == übersetzen). Der Compiler dient dem Computer quasi als Dolmet-
scher für die von uns Menschen erstellten Textdateien.
Um ein Programm zu entwickeln und ablaufen zu lassen, sind also die folgenden
Schritte nötig:
Programmieren
Kompilieren
Maschinensprache
15
B Wie unterscheidet ein Computer Musik-, Graphik- und andere Daten ?
B Was sind Dateneingabe, Zentraleinheit, Speicher, Datenausgabe ?
B Was sind RAM und ROM ?
B Was ist ein Betriebssystem ?
B Wie ist das RAM aufgebaut ?
B Was sind Bits, Bytes, Words, Kilobytes, Megabytes ?
B Warum sind Binärzahlen für Computer wichtig ?
B Was zeichnet einen Algorithmus aus ?
B Was ist der Unterschied zwischen einem Algorithmus und einem Pro-
gramm ?
B Warum programmiert man Computer meistens nicht in Maschinen-
sprache ?
B Welche Beziehung besteht zwischen Maschinensprache, höherer Pro-
grammiersprache und Compiler ?
16
• Umfangreiche Dokumentation: Für all diese fertigen Programmbausteine
(Klassen) und alle anderen Werkzeuge von Java liegt umfangreiche Do-
kumentation von HTML–Dateien vor, welche mit einem WWW–Browser
betrachtet werden kann.
Um die von Java bereitgestellten Programmbausteine zu verwenden, muß
man viel mit dieser Dokumentation arbeiten. Daher werden wir im Rah-
men dieser Veranstaltung ein paar Aufgaben stellen, bei denen Sie sich in
der Dokumentation zurechtfinden müssen.
• Einige Werkzeuge, zum Beispiel ein Werkzeug, das Ihre Programme liest
und die enthaltene Dokumentation in HTML-Dokumenten zusammen-
fasst. Dieses Werkzeug werden Sie hier auch kennenlernen.
• Java–Compiler und Java–Virtual–Machine. Diese Programme sind die Dol-
metscher zwischen Ihnen und dem Computer.
17
kryptischen Symbole in dem Programm bedeuten; im nächsten Kapitel
wird das alles viel klarer werden.
Sie sollten nun eine Textdatei namens Hallo.java mit dem Text des vorigen
Beispiels in ihrem Arbeitsverzeichnis besitzen. Als nächstes soll aus dieser Text-
datei ein vom Computer ausführbares Programm erzeugt werden.
Dazu verwendet man den Java-Compiler. In Kapitel 2.4 wurde ja bereits er-
läutert, daß ein Compiler ein für Menschen verständliches Programm in ein für
Computer verständliches Programm umwandelt.
Bevor Sie den Compiler verwenden können, muß auf Ihrem Computer die Java-
Programmierumgebung installiert und laufbereit sein. Falls Sie im CIP-Pool
der TU Braunschweig arbeiten, dann ist das kein Problem — hier ist alles schon
fertig installiert. Wenn Sie jedoch zuhause arbeiten möchten, müssen Sie selbst
dafür sorgen, daß die Java–Umgebung funktioniert. Dabei helfen Ihnen vielleicht
unsere im WWW verfügbaren Tips weiter (siehe auch WWW-Seite ? ). Wir
gehen im weiteren davon aus, daß Sie eine funktionsfähige Java-Programmier-
umgebung haben.
Aber nun zum Java–Compiler — dieser ist ein Programm namens javac. Um
durch ihn, die zuvor von Ihnen erstellte Datei Hallo.java übersetzen zu lassen,
wechseln Sie bitte auf einer Kommandozeile in das Verzeichnis, in welchem Sie
die Datei abgespeichert haben. Dort geben Sie dann bitte ein:
Der Compiler prüft darauf zunächst, ob die Textdatei Hallo.java ein gültiges
Java–Programm enthält. Java–Programme müssen einen ganz bestimmten Auf-
bau haben, damit sie vom Compiler übersetzt werden können. Nur wenn die
Datei tatsächlich ein gültiges Programm enthält, übersetzt der Compiler sie in
eine dem Computer verständliche Form und dient so als Dolmetscher zwischen
uns und dem Computer. Wenn Sie die Klasse korrekt abgetippt haben, sollte
das Kompilier–Kommando zu keiner Bildschirmausgabe führen.
Der Compiler ist auch kleinsten Tippfehlern gegenüber sehr ungnädig — selbst
wenn Sie beim Abtippen des Programms nur kleine Fehler gemacht haben, kann
es ihnen passieren, daß der Compiler ihr Programm mit einer Fehlermeldung
ablehnt. Falls Ihnen das beim Compiler–Aufruf passiert ist, prüfen Sie bitte, ob
Sie das Beispielprogramm exakt abgetippt haben, korrigieren es, speichern die
Änderungen (!) und probieren dann erneut den Aufruf des Compilers. Wenn das
nicht hilft, lesen Sie bitte unsere Hinweise zu Fehlermeldungen in Anhang C.
Wenn Sie auch damit nicht weiterkommen, holen Sie sich bitte einen Hiwi zur
Hilfe.
Wir gehen im folgenden davon aus, daß der Aufruf des Compilers funktioniert
hat. In diesem Fall hat der Compiler Ihr Programm übersetzt und die übersetzte
Fassung in einer neuen Datei namens Hallo.class gespeichert. Schauen Sie
bitte nach, ob diese Datei tatsächlich erzeugt wurde10 .
Die neu erzeugte Datei Hallo.class enthält die für einen Computer verständ-
liche Version unserer Klasse. Wenn Sie diese Datei mit dem cat-Kommando auf
den Bildschirm ausgeben, erhalten Sie eine ganz wilde Ausgabe — außerdem
10 Wenn Sie nicht wissen, wie das geht, haben Sie vermutlich Anhang A noch nicht gelesen
18
müssen Sie danach möglicherweise eine neue Kommandozeile öffnen, weil Ihre
alte Kommandozeile nur noch komische Zeichen anzeigt. Wir Menschen können
mit dem Inhalt der erzeugten Datei Hallo.class also nicht viel anfangen.
Wir haben Ihnen bisher aber nur die halbe Wahrheit erzählt — es ist zwar tat-
sächlich so, daß der Java–Compiler die Textdatei Hallo.java in eine Maschi-
nnensprache–Datei Hallo.class umgewandelt hat. Es kann jedoch kein tat-
sächlich existierender Computer die vom Java–Compiler benutzte Maschinen-
sprache direkt verstehen. Der Java–Compiler übersetzt nämlich jedes Programm
in eine künstliche Maschinensprache, den Java–Bytecode.
Java–Bytecode ist eine Art Computer–Esperanto. Kein Computer versteht es
direkt, doch es ähnelt den meisten Computermaschinensprachen. Soll ein Com-
puter das in einer Java–Bytecode–Datei enthaltene Programm abarbeiten, so
benötigt er einen weiteren Dolmetscher, der den Java–Bytecode für ihn über-
setzt. Dieser zweite Dolmetscher nennt sich Bytecode-Interpreter11 .
Man tut im übrigen so, als wäre Bytecode tatsächlich eine Maschinensprache
und stellt sich gerne vor, daß irgendwo ein Computer existieren könnte, der den
Bytecode direkt versteht. Da dieser Computer aber nur durch den Bytecode-
Dolmetscher existiert, nennt man den Bytecode-Dolmetscher auch Java Vir-
tual Machine (JVM) (virtueller Java-Computer).
Natürlich können Java-Programme damit nicht so schnell sein wie Programme
anderer Programmiersprachen, welche für den jeweilige Computer direkt ver-
ständlichen Programmcode erzeugen. Der Prozessor Ihres Computes führt ja
kompilierte Java–Programme niemals direkt aus sondern benutzt immer seinen
Bytecode–Dolmetscher.
Der Umweg über den Java–Bytecode ist aber auch ein sehr großer Vorteil und
hat den Einsatz von Java im Internet erst ermöglicht — hierdurch werden
Java-Programme nämlich systemunabhängig. In der Theorie läuft jedes Java-
Programm auf jeder Hardware und auf jedem Betriebssystem, für das eine vir-
tuelle Java–Maschine existiert. Man muß sich also nicht mehr entscheiden, ob
man ein Programm für Windows, Macintosh, Unix oder sonst ein Betriebssy-
stem kompiliert. Wenn die virtuelle Java-Maschine auf diesem Betriebssystem
vorhanden ist, läuft hierauf jedes kompilierte Java–Programm.
Das Programm, welches die virtuelle Maschine simuliert, heißt java. Um die
kompilerte Datei Hallo.class auszuführen, geben Sie bitte auf der Komman-
dozeile das Kommando
Ein Hinweis noch: Beachten Sie, daß die Endung „.class“ beim Aufruf des
Bytecode-Interpreters nicht mit angegeben werden darf. Hingegen muß beim
Aufruf des Compilers javac immer die Endung „.java“ mit angegeben werden.
11 Das englische Wort „Interpreter“ heißt auf deutsch „Dolmetscher“
19
3.3 Zusammenfassung
B Was ist das JDK ?
B Warum benötigt man einen Compiler ?
B Sie sollten den Java-Compiler aufrufen können.
B Was erzeugt der Java-Compiler ?
B Was ist ein Bytecode-Interpreter, und warum wird er benötigt ?
B Wie unterscheiden sich Java-Compiler und Bytecode-Interpreter be-
züglich der Endung von Dateinamen ?
B Überlegen Sie sich, wieso der Bytecode Java so gut für die Verwendung
im Internet geeignet macht — berücksichtigen Sie hierbei, daß für
Menschen verständlicher Programmtext meist als Geschäftsgeheimnis
angesehen wird.
20
Auf dem Bildschirm sollte sich ein zweigeteiltes Fenster öffnen. Im oberen Be-
reich des Fensters sehen Sie eine Schrift sowie ein dreieckiges Symbol, und im un-
teren, dunkleren Bereich sehen Sie Knöpfe, welche mit „Step“,„Run Slow“,„Stop“
und „Quit“ betitelt sind.
Drücken Sie nun einige Male auf den „Step“–Knopf. Sie werden feststellen, daß
sich das dreieckige Symbol bei jedem Druck bewegt und dabei Striche malt, wel-
che eine Art Baum ergeben. Das dreieckige Symbol werden wir im weiteren eine
„Turtle“ nennen — Sie können sich das so vorstellen, daß auf dem Bildschirm
eine kleine Schildkröte herumkrabbelt und dabei Striche zieht.
Nach einigen Schritten wird das Drücken des Knopfes mühselig — drücken Sie
dann den „Run Slow“ Knopf. Die Turtle läuft nun selbstständig über den Bild-
schirm, ohne auf weitere Knopfdrücke zu warten. Durch einen Druck auf den
„Stop“–Knopf können Sie sie jederzeit anhalten.
Probieren Sie das und drücken Sie dann den „Run“–Knopf. Nun läuft die Turtle
so schnell, daß sie gar nicht mehr zu sehen ist. Auch hier können Sie jederzeit
den „Stop“–Knopf drücken. Wenn Sie das Programm verlassen wollen, können
Sie jederzeit den „Quit“–Knopf verwenden.
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapFirst/Kreuz.java
Sie sehen nun das Fenster, welches Sie bereits kennen, und wenn Sie die Turt-
le wie in Abschnitt 4.1 beschrieben laufen lassen, sollte ein Kreuz gezeichnet
werden.
Lassen Sie uns die einzelnen Bestandteile dieses Programms Zeile für Zeile unter-
suchen — Sie werden sicher nicht alle Erläuterungen verstehen. Lesen Sie bitte
die Ausführungen dennoch, um einen ersten Eindruck der Programmierung in
Java zu erhalten.
In Zeile 1–3 steht ein durch die Zeichen „/*“ und „*/“ eingeschlossener Text.
Dies ist ein Kommentar. Der Java–Compiler ignoriert jeden so
umschlossenen Text, auch wenn er aus mehreren Zeilen besteht.
Kommentare dienen dazu, Programme lesbarer zu machen — man
kann in Kommentare Texte schreiben, welche beim Verständnis
der nebenstehenden Programmbefehle helfen.
21
Zeile 4 ist leer. Leerzeilen dienen der besseren Lesbarkeit des Programmes
für Menschen. Für Java haben Leerzeilen gar keine Bedeutung.
In Zeile 5–6 wird Java angewiesen, die Programmbausteine eip.TurtleScreen
und eip.Turtle zu importieren.
Jedes Java–Programm setzt sich aus kleineren Bausteinen zusam-
men, sogenannten Klassen. Einige Klassen sind in Java fest ein-
gebaut und müssen Java nicht erst bekannt gemacht werden.
Die Klassen TurtleScreen und Turtle sind von uns erstellt wor-
den und sind Java normalerweise nicht bekannt. Will man sie
verwenden, muß man Java daher zuerst mitteilen, wo sie auf der
Festplatte zu finden sind.
Dies geschieht durch Angabe eines Verzeichnispfades, gefolgt vom
Namen des Programmbausteins. Wenn wir hier den Befehl
import eip.TurtleScreen
22
Will man etwa ein Programm schreiben, welches zwei Kreuze
zeichnet, so würde man zuerst ein Programmstück (eine Metho-
de) schreiben, welches ein Kreuz zeichnet und dann eine weitere
Methode, welche die erste Methode zweimal verwendet. Die Auf-
teilung eines Programms in viele kleine Stücke ist eine sehr gute
Sache, da sie Programme überschaubarer und leichter zu erweitern
macht.
Es stellt sich die Frage, welche Methode ablaufen soll, wenn man
ein Programm von der Kommandozeile startet (etwa durch den
Befehl „java Kreuz“). In Java ist das so gelöst, daß immer die
sogenannte main–Methode gestartet wird, welche durch den Text
gekennzeichnet ist.
Eine Methode besteht aus einer Reihe von Anweisungen, welche
durch geschweifte Klammern zusammengefasst werden. Im vorlie-
genden Beispiel erkennen Sie ein Klammerpaar, welches in Zeile
10 beginnt und in Zeile 21 geschlossen wird. Die main–Methode
besteht daher aus allen Anweisungen in den Zeilen 11 bis 20.
Zeile 11 Zeile 11 ist die erste Zeile des Hauptprogramms der Kreuz–Klasse.
Wenn Sie die Kreuz–Klasse von der Kommandozeile starten, be-
ginnt hier die Ausführung des Programms.
In Zeile 11 wird auf der rechten Seite des Gleichheitszeichens durch
den Text new TurtleScreen() ein neuer TurtleScreen erzeugt.
Ein TurtleScreen ist ein Fenster, in welchem die Turtle umher-
läuft. Der Befehl new TurtleScreen() öffnet hier ein neues Fen-
ster auf dem Bildschirm Ihres Computers.
Vorher, in Zeile 5, hatten wir Java mitgeteilt, wo es den Pro-
grammbaustein TurtleScreen finden kann. Hätten wir das nicht
getan, wüsste Java mit Zeile 11 nichts anzufangen.
Damit wir mit Java über den neu angelegten TurtleScreen reden
können, müssen wir ihm einen Namen geben. Dies geschieht auf
der linken Seite des Gleichheitszeichens, wo wir eine Variable na-
mens ts erzeugen.
Zeile 11 ist somit die programmtechnische Formulierung des Be-
fehls „Öffne ein neues TurtleScreen–Fenster und benenne es mit
ts“.
Zeile 12 Zeile 12 ähnelt Zeile 11. Durch den Befehl new Turtle(ts) wird
eine neue Turtle angelegt, welche auf den in Zeile 11 angelegten
TurtleScreen gesetzt wird. Diese wird daraufhin als dreieckiges
Symbol auf dem Bildschirmfenster angezeigt.
Die neue Turtle erhält dabei den Namen t.
In Zeile 14–19 werden der in Zeile 12 erzeugten Turtle Botschaften geschickt.
In Zeile 14 steht beispielsweise der Programmbefehl
t.pd();
23
So schicken wir der Turtle namens t die Botschaft „pd()“ (pd
steht für „pendown“). Die Turtle senkt daraufhin ihren Stift.
Jede Botschaft an die Turtle t bewirkt die Ausführung zur Bot-
schaft passenden Programmcodes, welcher innerhalb der Turtle–
Klasse definiert ist. Auf den Befehl pd() hin merkt sich die Turtle,
daß sie ab sofort beim umherlaufen Linien ziehen soll.
Ähnlich sind die Anweisungen „t.fd(100);“ bzw. „t.rt(120);“
Botschaften an die Turtle t, 100 Einheiten nach vorne zu laufen,
bzw. sich um 120 Grad nach rechts zu drehen („fd“ steht für
„forward“ und „rt“ steht für „rightturn“).
Nachdem wir nun jede Zeile des Beispielprogramms besprochen haben, sollten
Sie selbständig kleine Änderungen vornehmen — was passiert, wenn Sie die Be-
fehle t.fd(100) durch t.fd(200) ersetzen ? Was passiert, wenn Sie die Befehle
t.rt(120) durch t.rt(90) ersetzen ? Können Sie die Turtle dazu bringen, ein
Rechteck zu zeichnen ? (natürlich müssen Sie nach jeder Änderung der Datei die
Änderungen speichern, den Compiler aufrufen und dann das Programm erneut
starten).
Ein Hinweis noch: Ihnen sollte aufgefallen sein, daß die meisten Zeilen des Bei-
spielprogrammes mit einem Semikolon enden. In Java müssen die meisten Be-
fehle durch Semikoli abgeschlossen werden. Beachten Sie dies beim Ändern des
Beispielprogramms.
4.3 Turtlebefehle
Hier stellen wir Ihnen alle Befehle vor, welche die Turtle versteht und zeigen
Ihnen in einigen Beispielprogrammen, wie diese Befehle zu verwenden sind. Bei
der Vorstellung der Befehle gehen wir davon aus, daß t eine Turtle bezeichnet.
pu( ) „pen up“ — die Turtle hebt ihren Stift. Beim Bewegen zeichnet
die Turtle nicht.
pd( ) „pen down“ — die Turtle senkt ihren Stift. Beim Bewegen zeich-
net die Turtle.
fd( n ) „forward“ — die Turtle bewegt sich n Schritte vorwärts. Dabei
ist n eine reelle Zahl.
Beispielsweise bewegt sich die Turtle t durch den Befehl
t.fd( 3.5 );
24
hide() Macht die Turtle unsichtbar — sie zeichnet dann zwar noch,
verdeckt aber nicht mehr Teile der Zeichnung.
show() Macht eine zuvor durch hide() versteckte Turtle wieder sicht-
bar.
setpc( color ) Setzt die Farbe, in welcher die Turtle zeichnet. Um diesen
Befehl, in einem eigenen Programm zu verwenden, muß zum
Beginn des Programms der Befehl
import java.awt.Color;
Z.B.:
Weiter unten finden Sie ein Beispiel, das die Verwendung von
Farben demonstriert.
clearScreen( color ) Die Zeichenfläche der Turtle wird in der angegebenen
Farbe gelöscht. Um diesen Befehl, in einem eigenen Programm
zu verwenden, muß zum Beginn des Programms der Befehl
import java.awt.Color;
Wie man Farben verwendet, ist beim Befehl setpc und im unten
stehenden Beispiel beschrieben.
t.debug=true;
25
t.debug=false;
t.setName("Tina Turtle");
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapFirst/Demo1.java
26
Verbotene Befehle:
Die folgenden Befehle dürfen Sie in den ersten Hausaufgaben nicht benutzen.
Sie durchbrechen den Turtle–Gedanken, der vorsieht, daß die Turtle nur lokal,
d.h. in ihrem eigenen Koordinatensytem, arbeitet.
setPosition( x, y) Setzt die Position der Turtle auf dem Zeichenfeld. x und
y sind dabei reelle Zahlen. Wenn der Stift unten ist, wird bei
der Bewegung eine Linie gezeichnet.
Beispielsweise bewegt sich die Turtle t durch den Befehl
t.setPosition( 10, 10 );
double x = t.getX();
t.setHeading(90);
double d = t.getHeading();
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapFirst/Tabu.java
27
5 Grundlagen der Java-Syntax
Es sollen nun einige Grundregeln besprechen, die in jeder Java-Programmdatei
berücksichtigt werden müssen.
/*
* Benutzt die Turtle, um ein Kreuz zu zeichnen.
*/import
eip.TurtleScreen;import eip.Turtle;
class Kreuz {public static void main( String[]
args ) {TurtleScreen ts = new
TurtleScreen(); Turtle t = new Turtle( ts );
t.pd(); t.fd(
200 ); t.bk( 100 ); t.rt( 90
);t.fd( 100 );t.bk( 200 );}}
Dem Compiler ist es egal, wie das Programm formatiert ist. Für uns Menschen
ist aber dieses Programm sehr viel schlechter zu lesen als das ursprüngliche.
Da er nicht die Zeilenstruktur des Programmes verwendet, muß der Compiler
irgendwie anders erkennen, wann ein Befehl im Programm endet und der nächste
Befehl beginnt. Darum muß in Java jede Anweisung mit einem Semikolon enden.
Das gilt aber nicht für die Definition einer Klasse oder Methode.
Wenn der Compiler eine Datei bearbeitet, sieht er zunächst nur einen Strom
von Zeichen. Diese Zeichen faßt er zu größeren Einheiten zusammen. Um etwas
für den Compiler verständliches zu programmieren, müssen Sie wissen, was für
Einheiten der Compiler versteht.
Java identifiziert in seinem Eingabestrom die folgenden Einheiten:
Schlüsselwörter — Ein Schlüsselwort ist ein Wort, welches innerhalb der Pro-
grammiersprache eine genau festgelegte Bedeutung hat. In Java sind fol-
gende Wörter als Schlüsselwörter reserviert (das brauchen Sie sich nicht
zu merken):
abstract boolean break byte case
catch char class const continue
default do double else extends
false final finally float for
goto if implements import instanceof
int interface long native new
package private protected public return
short static super switch synchronized
this true throw throws transient
try void volatile while
28
Jedes dieser Wörter hat für Java eine ganz spezielle Bedeutung. Wannim-
mer eines im Programmtext auftaucht, muß es in genau der vorgesehenen
Art und Weise benutzt werden.
Neben den Schlüsselwörtern kennt Java noch einige Symbole, die beispiels-
weise beim Rechnen mit Zahlen benutzt werden:
= > < ! ~ ? :
== <= >= != && || ++
-- + - * / & |
^ % << >> >>> += -=
*= /= &= |= ^= %= <<=
>>= >>>=
Bei der Wahl von Bezeichnern dürfen Sie Ihrer Phantasie fast freien Lauf
lassen. Allerdings sollten Sie dafür sorgen, daß die von Ihnen gewählten
Bezeichner aussagekräftig sind und das bezeichnete Objekt gut charakte-
risieren. Java ist es egal, ob Sie wir die Turtle mit t oder mit ruebe. Einen
menschlichen Leser würden Sie dadurch aber verwirren.
Ein Bezeichner darf beliebig lang werden, und bei der Wahl eines Bezeich-
ners werden Sie lediglich durch folgende Bedingungen eingeschränkt:
29
Beispiel: Hier eine Liste gültiger Bezeichner:
i3 String MAX_VALUE Lupo1234_392
i mEiNeKlAsSe meineKlasse mein_segel_boot
Nun folgt eine Liste ungültiger Bezeichner:
1haus Ungültig: beginnt mit Ziffer.
class Ungültig: class ist ein Schlüsselwort.
ab-c Ungültig, weil ein Minus nicht in einem Bezeichner
enthalten sein darf. Java würde denken, daß Sie hier
das durch c bezeichnete Objekt vom mit ab bezeich-
neten Objekt substrahieren wollen.
#bdd Ungültig: enthält ein nicht erlaubtes Sonderzeichen.
ein Objekt Ungültig: enthält ein Leerzeichen.
Und schließlich eine Liste von Bezeichnern, die zwar erlaubt sind, die
Sie aber dennoch nicht verwenden sollten.
Meine$Klasse Das Symbol $ sollten Sie nicht verwenden. Die Java-
Umgebung benutzt es nämlich auf eine ganz spezielle
Weise.
Class Java unterscheidet zwischen Groß- und Kleinschrei-
bung und wird diesen Bezeichner daher nicht mit
dem Schlüsselwort class in Verbindung bringen. Ein
menschlicher Leser könnte dadurch aber verwirrt wer-
den.
30
Zeichenketten — Eine Zeichenkette ist ein in doppelten Anführungszeichen
stehender Text. In der Zeichenkette ist jedes Zeichen erlaubt. Es gibt al-
lerdings einige Zeichen, bei denen man sich etwas anstrengen muß, um Sie
in eine Zeichenkette aufzunehmen. Darauf wird später noch eingegangen.
31
Ein mehrzeiliger Kommentar wird durch /* begonnen und endet mit */.
Alles zwischen /* und */ wird vom Compiler ignoriert. Unser Beispiel
könnten wir auch so dokumentieren:
/*
* Die Klasse HalloWelt gibt auf dem Bildschirm den Text
* "Hallo, Welt" aus.
*/
class HalloWelt {
/***********************************************************
* diese Methode wird beim ausführen der Klasse ausgeführt:
*/
public static void main( String[] args )
{
System.out.println("Hallo, Welt //");
}
5.2 Zusammenfassung
Nachdem Sie dies Kapitel gelesen haben, sollten Sie folgende Fragen beantworten
können:
32
6 Variablen und Datentypen
Die Programme, die wir bisher gesehen haben, sind ziemlich langweilig — sie ar-
beiten eine vorgegebene Folge von Befehlen ab und sind überhaupt nicht flexibel.
Für flexible Programme, die bei jedem Aufruf etwas anderes tun, benötigt man
„Variablen“, etwas das bei jedem Programmablauf einen anderen Wert haben
kann.
berechnen.
Der Nachteil der obigen Formel ist, daß sie nur für einen Nettopreis von
10,- DM korrekt ist. Wenn wir in unserem Programm auch mit anderen
Preisen als 10,- DM rechnen wollen, würden wir daher lieber einen Platz-
halter — eine Variable — für den Nettopreis verwenden. Möglicherweise
würden wir schreiben:
Diese Formel wäre für jeden Preis anwendbar. Dazu müsste der Platzhalter
nettopreis den jeweils zu verwendenden Nettopreis enthalten. Das Ergeb-
nis würden wir dann in einem anderen Platzhalter namens bruttopreis
speichern.
Wir können Variablen überall dort einsetzen, wo wir Werte hinschreiben wür-
den. Natürlich müssen wir dabei ein bißchen aufpassen — es wäre sicher nicht
sinnvoll, einen Platzhalter, der eine reelle Zahl enthält, an einer Stelle zu ver-
wenden, an der nur Zeichenketten hinpassen.
Damit man nicht ausversehen eine Variable an einer Stelle verwendet, wo sie
keinen Sinn macht, hat in Java jede Variable einen Typ.
33
Bevor eine Variable in Java das erste Mal verwendet wird, muß man Java daher
Informationen darüber zukommen lassen, welchen Typ diese Variable haben soll.
In Java geschieht dies, indem man eine Anweisung des Aufbaus
TYP variablenname;
verwendet. Sobald Java eine solche Anweisung gefunden wird, merkt sich Java:
Aha, hier ist jetzt eine Variable namens variablenname definiert worden, und
diese Variable darf nur Werte des Typs TYP aufnehmen.
Beispiel:
DemoVars.javaa
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapVariablen/DemoVars.java
In Zeile 6 wird eine Variable namens zaehler als int definiert. Von dieser
Zeile an weiß Java, daß der Bezeichner zaehler für eine Variable steht
und nur ganze Zahlen speichern kann.
In Zeile 7 wird der Variable zaehler der Wert 10 zugewiesen. Vorher hatte
zaehler keinen definierten Wert.
In Zeile 8 wird eine weitere Variable namens inversZaehler definiert, die
reelle Zahlen speichern kann. Außerdem wird dieser Variable gleich der
inverse Wert von zaehler zugewiesen.
In Zeile 9 und 10 werden beide Variablen ausgegeben.
Java kennt mehrere ganzzahlige und mehrere reellwertige Datentypen. Der Grund
hierfür ist folgender: Jede Variable belegt einen gewisse Anzahl von Bytes im
Speicher des Computers. Aus Kapitel 2 wissen Sie, daß man mit n Bytes nur
28·n verschiedene Werte darstellen kann.
Um den Typ einer Variable festzulegen, muß man sich darum vorher Gedanken
machen, wieviele verschiedene Werte sie annehmen können soll und davon aus-
gehend, wieviele Bytes man im Computerspeicher dafür verwenden möchte. Da
man normalerweise nicht mehr Speicherplatz als nötig verschwenden will, bie-
tet Java ganzzahlige und reellwerige Datentypen verschiedener Längen an (die
Länge des Datentyps gibt an, wieviele Bytes seine Variablen belegen).
Java kennt die ganzzahligen Datentypen byte, short, int und long. Am häufig-
sten verwendet man den Datentyp int.
34
GanzZahl.javaa
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapVariablen/GanzZahl.java
Was passiert, wenn eine Variable einen zu großen oder kleinen Wert enthält ?
Um das zu demonstrieren, haben wir ein Programm geschrieben, das eine Zahl
vom Benutzer einliest und anschließend ausgibt:
GanzZahlInput.javaa
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapVariablen/GanzZahlInput.java
Wenn Sie das Programm kompilieren und starten, werden Sie aufgefordert, eine
Zahl einzugeben. Die eingegebene Zahl wird dann der int–Variable i zugewie-
sen.
Probieren Sie es bitte selbst aus ! Wenn Sie die Zahl 231 − 1 eingeben (das
ist die Zahl 2147483647), gibt das Programm sie korrekt wieder aus. Wenn Sie
jedoch größere Zahlen eingeben, kommt es zu komischen Resultaten: statt der
von Ihnen eingegebenen Zahl werden negative Zahlen ausgegeben. Zum Beispiel
macht das Programm bei mir aus der Zahl 123456789012 die Zahl −1097262572.
Diesen Effekt nennt man Overflow — eine Zahl läuft über, wenn man ihr
einen zu großen oder einen zu kleinen Wert zuweist. Der Wert, den die Zahl
dann enthält, ist nicht brauchbar.
Sie müssen also immer darauf achten, daß der ausgewählte Datentyp zu den
Daten passt, die Sie verarbeiten wollen.
6.1.2 Fließkommazahlen
Java kennt die beiden reellwertige Datentypen float und double. float–Zahlen
belegen 4 Byte und double–Zahlen belegen 8 Byte im Speicher.
Sie sollten vorerst immer double–Variablen verwenden, da hier nicht nur der
Wertebereich sondern auch die Anzahl der geführten Nachkommastellen größer
ist als für float–Variablen.
Das folgende Programm demonstriert die Verwendung von reellen Zahlen:
ReellZahl.javaa
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapVariablen/ReellZahl.java
Der Datentyp boolean repräsentiert logische Werte. Ein logischer Wert ist ent-
weder wahr (true) oder unwahr (false).
boolean–Variablen können benutzt werden, um die Resultate von Vergleichen zu
35
speichern. Sie können auch benutzt werden, wenn eine Variable nur zwei Werte
annehmen können soll.
Das folgende Beispiel demonstriert die Verwendung von boolean:
DemoBoolean.javaa
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapVariablen/DemoBoolean.java
Einzelne Zeichen sind in Java vom Typ char. Zeichenketten sind vom Typ String.
Um ein Zeichen in einem Java–Programm anzugeben, muß man es in einfa-
che Anführungszeichen schreiben. Eine Zeichenkette schreibt man in doppelte
Anführungszeichen.
Wenn man zwei Zeichenketten mit + verknüpft, so entsteht eine Zeichenkette,
die den hintereinander gehängten Text beider Zeichenketten enthält.
Beispiel:
DemoString.javaa
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapVariablen/DemoString.java
Auf Zeile 4 wird eine char–Variable namens b definiert, welcher das Zei-
chen „z“ zugewiesen wird. Der Inhalt der Variable hat mit dem Namen
der Variable nichts zu tun.
In Zeile 8 wird die Variable name als String definiert. Ihr Inhalt entsteht
durch Hintereinanderhängen anderer Variablen.
In Zeile 9 wird die Variable sequenz als String definiert. Wir bilden den
Inhalt, indem wir hinter den leeren String "" mit Hilfe des +–Operators
die drei Zeichenvariablen anfügen. Der Leerstring wird hier dazu benutzt,
damit + überhaupt als das Hintereinanderhängen von Zeichenketten in-
terpretiert wird.
7 Ausdrücke
In diesem Kapitel geben wir Ihnen ergänzende Informationen zu Goto Java 2,
Kapitel 5, die Sie ergänzend zu Hausaufgabe 3 lesen sollten.
36
02 public static void main( String[] args ) {
03 int i = 5;
04 float f = 5.0f;
05 System.out.println( 5 );
06 System.out.println("5");
07 System.out.println( i );
08 System.out.println( f );
9 System.out.println( 5 * (i - 3 * f + 1.3 ) );
10 }
11 }
Das Programm gibt einige Werte auf den Bildschirm aus, wobei wir wieder die
bereits bekannte Methode System.out.println verwenden. Auf den ersten Blick
scheint dies ein ganz triviales Programm zu sein.
Aber beachten Sie, auf wie viele unterschiedliche Arten wir die Ausgaberoutine
verwenden: In Zeile 5 geben wir eine Zahl aus. In Zeile 6 geben wir einen String
aus. In Zeile 7 wird der Inhalt einer int–Variable ausgegeben, während in Zeile 8
der Inhalt einer float–Variable ausgegeben wird. Schließlich wird in Zeile 9 noch
vor der Ausgabe eine Rechnung durchgeführt und erst dann der berechnete Wert
ausgegeben.
All diese unterschiedlichen Daten müssen von Java auch unterschiedlich behan-
delt werden — mit Strings muß ein Computer ganz anders umgehen als mit
Zahlen, und mit ganzen Zahlen vom Typ int muß der Computer ganz anders
umgehen als mit reellen Zahlen vom Typ float. Auch muß die Rechnung in Zeile
09 erst vom Computer durchgeführt werden, bevor das Ergebnis ausgegeben
werden kann.
Ähnlich wie bei im obigen Beispiel bei der Verwendung von System.out.println
kann in einem Java–Programm an jeder Stelle, wo irgendein Wert stehen kann,
auch eine Rechnung oder eine Variable oder sonstetwas stehen, das man aus-
werten kann. Dabei darf die Auswertung nur Werte der jeweils erlaubten Da-
tentypen ergeben.
Ausdruck
Definition: Ausdruck
Ein Ausdruck ist etwas, das man auswerten kann. Die Auswertung
eines Ausdruckes ergibt einen Wert. Der Datentyp dieses Wertes
ergibt sich aus dem Aufbau des Ausdrucks. An jeder Stelle, an der ein
Wert eines gewissen Datentyps stehen darf, darf auch ein Ausdruck
dieses Datentyps stehen.
Die System.out.println–Methode darf man auf all die durch uns demonstrierten
Arten verwenden, da sie sowohl Ausdrücke vom Typ int als auch Ausdrücke vom
Typ float als auch Ausdrücke vom Datentyp String verarbeiten kann. Sie kann
auch Ausdrücke aller anderen Datentypen verarbeiten und ist damit ziemlich
einzigartig.
Beispiel: Hier ein paar Beispiele für Ausdrücke. Es seien folgende Varia-
blen definiert:
int i = 5;
float f = 5;
String a = "Hallo";
37
2 Die Zahl 2 ist ein Zahlenliteral. Jedes Literal ist ein Ausdruck,
dessen Datentyp gleich dem Datentyp des Literals ist. Das ganz-
zahlige Literal 2 ist somit ein Ausdruck mit Wert 2 und Datentyp
int
“5” Das String–Literal "5" ist ein Ausdruck mit Wert "5" und Da-
tentyp String
i Auch jede Variable ist ein Ausdruck, deren Wert sich aus dem
Inhalt der Variable ergibt. Mit den obigen Definitionen ist dies
ein Ausdruck vom Typ int mit Wert 5.
f Die Variable f ist hier ein Ausdruck vom Typ float mit Wert 5.0.
i+2 In Ausdrücken können Rechnungen vorkommen . Der Wert einer
Rechnung ergibt sich natürlich als Resultat der Rechnung. Da-
bei sind die Operanden einer Rechnung selbst wieder Ausdrücke.
Der Wert der Rechnung i+2 ergibt sich durch Auswerten des
Ausdrucks links vom +–Zeichen (int mit Wert 5), durch Auswer-
ten des Teils rechts vom +–Zeichen (int mit Wert 2) und durch
Addition der so ermittelten Werte.
Der Datentyp einer Rechnung ergibt sich aus den an der Rech-
nung beteiligten Datentypen (dazu sagen wir im Kapitel über
Typwandlungen mehr). Dieser Ausdruck würde zum Wert 7 aus-
gewertet und hätte den Typ int, da in der Rechnung nur Aus-
drücke vom Typ int vorkommen.
a + “ Welt” Auch dies ist ein Ausdruck. Der String a und der String
" Welt" werden zusammengehängt. Ergebnis ist ein Ausdruck
vom Typ String mit Inhalt "Hallo Welt".
i=5 Auch die Zuweisung eines Wertes an eine Variable ist ein Aus-
druck. Zwar ist der eigentliche Zweck der Zuweisung, der Variable
auf der linken Seite des =–Zeichen Variable den Wert des Aus-
drucks auf der rechten Seite zuzuweisen. Darüberhinaus ist die
Zuweisung in Java aber auch ein Ausdruck, dessen Auswertung
den Wert auf der rechten Seite des =–Zeichens ergibt (dazu gleich
mehr).
Beispielsweise gibt das folgende Java–Programm den Wert 5 auf
den Bildschirm aus und weist der Variable i den Wert 5 zu:
public class DemoAssign {
public static void main( String[] args ) {
int i = 0;
System.out.println( i = 5 );
System.out.println( i );
}
}
7.2 Literal–Ausdrücke
Die wohl einfachsten Ausdrücke sind Literale. Ein Literal ist eine konkrete Zahl
oder ein konkreter String, der in einem Programmtext auftaucht. Jedes Literal
hat einen eindeutig bestimmten Datentyp.
Die nächsten Seiten sollen Ihnen eine gewisse Vorstellung davon vermitteln,
wie Literale geschrieben werden und welchen Datentyp Java welchen Literalen
zuweist.
38
7.2.1 Ganzzahlige Literale
Es gibt drei Typen von ganzzahligen Literalen: dezimale, oktale und hexadezi-
male. Sie werden zwar in dieser Veranstaltung weder oktale noch hexadezimale
Literale verwenden, es ist aber wichtig, daß Sie wissen, wie man diese Literale
schreibt. Es ist nämlich sehr leicht möglich, eine Zahl ausversehen als oktales
Literal zu schreiben. Für diese Zahl könnte sich dann ein unerwarteter Wert
ergeben. Dezimales
Ein dezimales Literal ist eine Zahl in üblicher dezimaler Darstellung. Ein dezi- Literal
males Literal darf mit einem Vorzeichen (+ oder −) beginnen und die Ziffern 0
bis 9 enthalten. Der Wert eines dezimalen Literals ist der Wert der angegebenen
Zahl.
39
Datentyp
Ein ganzzahliges Literales ist immer vom Typ int, es sei denn, man hängt den ganzzahliger
Buchstaben L an. In diesem Fall erhält es den Datentyp long. Literale
Es stellt sich natürlich die Frage, warum man überhaupt ganzzahlige Literale
vom Typ int und Typ long unterscheidet. Der Datentyp long umfaßt doch den
Datentyp int, und daher sollte doch eigentlich der Datentyp long ausreichen,
um alle ganzen Zahlen zu repräsentieren.
Der Grund, warum es dennoch den Datentyp int gibt, wird im nächsten Kapitel
über Typkonvertierungen klar werden. Hier sei nur gesagt, daß Sie dort erfah-
ren werden, daß es zwar gestattet ist, einer long–Variable einen Ausdruck vom
Typ int zuzuweisen, daß es jedoch nicht gestattet ist, einer int–Variable einen
Ausdruck des Typs long zuzuweisen. Das folgende Programmfragment enthält
daher auf Zeile 3 und 4 Fehler:
int i = 5;
long m = i;
int j = 5L; // Fehler !
int k = m; // Fehler !
Würde Java nun auch die Zahlen, hinter denen kein “L” steht, als long–Literale
interpretieren, so wäre auch die erste Zeile des obigen Programmfragmentes
ungültig, und um dies zu vermeiden, gibt es für ganzzahlige Literale diese Un-
terscheidung in die zwei Datentypen int und long.
40
1.f Die Zahl 1.0, Datentyp float
.1D Die Zahl 0.1, Datentyp double
1.0E0 Die Zahl 1.0, Datentyp double
1e5 Die Zahl 1 · 105 , Datentyp double
1e5f Die Zahl 1 · 105 , Datentyp float
+3.14159 Die Zahl +3.14159, Datentyp double
4.10E-10 Die Zahl 4.10 · 10−10 , Datentyp double
4.10E-10f Die Zahl 4.10 · 10−10 , Datentyp float
Beachten Sie bitte, daß Sie an einer Stelle im Programmtext, wo ein Ausdruck
vom Typ double erwartet wird, zwar einen Ausdruck vom Typ float verwenden
dürfen. Sie dürfen jedoch an einer Stelle, wo ein Ausdruck vom Typ float er-
wartet wird, keinen Ausdruck vom Typ double verwenden. Warum das so ist,
erklären wir im nächsten Kapitel über Typwandlungen.
Es gibt zwei boolsche Literale, nämlich true und false. Beide haben den Daten-
typ boolean.
Das Literal true steht für logisch wahre Werte und das Literal false steht für
logisch falsche Werte.
Beispiel: Hier ein paar Beispiele für Ausdrucke, die in der Auswertung
true oder false ergeben:
41
7.2.4 Zeichenliterale
Beispiel: Beispiele für char–Literale sind ’a’, ’B’, ’#’, ’+’, ’1’.
Folgende Texte sind keine gültigen Zeichenliterale, da sie nicht genau ein
Zeichen enthalten: ’’, ’aa’, ’12’.
Unicode
Unicode ist ein Zeichensatz, in dem jedes Zeichen durch eine 16–Bit–Zahl dar-
gestellt wird. Es können also maximal 65536 verschiedene Zeichen dargestellt
werden können. Jedem Zeichen wird dabei eine eindeutige Zahl zwischen 0 und
65535 zugeordnet. Zum Beispiel ist dem Buchstaben “a” die Zahl 97, dem Buch-
staben “A” die Zahl 65 und der Ziffer “1” die Zahl 49 zugeordnet. Zeichen im
Unicode–Zeichensatz sind Buchstaben, Zahlen und alle anderen Sonderzeichen,
die der Computer darstellen kann sowie einige Zeichen mit besonderer Bedeu-
tung wie Tabulator, Zeilenumbruch oder Rückschritt (Backspace). In sind Un-
icode auch Buchstaben anderer Länder (etwa japanische Schriftzeichen) oder
sonstige normalerweise nicht verwendete Zeichen vorgesehen.
Beispiel: Folgendes Programm gibt die Zeichen ’a’, ’B’ und ’#’ und die
diesen zugeordneten Zahlenwerte aus.
Um die Unicode–Zahlenwerte der Zeichen auszugeben, ist es nötig, die Zei-
chen in einen anderen Datentyp als char umzuwandeln (hier int). Die Aus-
gaberoutine System.out.println gibt nämlich Werte des Datentyps char
nicht als Zahl sondern als Zeichen aus.
Es gibt einige besondere Zeichen, die man mit der Tastatur nicht so einfach an-
geben kann. Dazu gehört beispielsweise der Zeilenumbruch. Diese Sonderzeichen
kann man eingeben, indem man eine sogenannte Escape–Sequenz verwendet. Ei-
ne Escape–Sequenz wird durch das Zeichen \, den Backslash, eingeleitet. Immer
wenn ein Zeichenliteral mit einem Backslash beginnt, werden die auf den Backs-
lash folgenden Zeichen interpretiert, um den Wert des Zeichens zu ermitteln.
42
Welche Escape–Sequenzen es gibt, ist in Go To Java 2 in Tabelle 4.2 beschrie-
ben. Den Backslash selbst ist durch ’\\’ gegeben.
7.2.5 Zeichenketten
Als letzte Art von Literalen sind sind schließlich noch die Zeichenketten zu
erwähnen. Eine Zeichenkette ist ein Text in doppelten Hochkommata. Eine Zei-
chenkette hat den Datentyp String, und ihr Wert ist die Zeichenfolge zwischen
den doppelten Hochkommata.
Zwischen den doppelten Hochkommata ist jedes Zeichen erlaubt, das auch in
einem Zeichenliteral verwendet werden könnte. Insbesondere können in einer
Zeichenkette auch die Escape–Sequenzen verwendet werden, die bei Zeichenli-
teralen eingesetzt werden konnten.
Zeichenketten unterscheiden sich deutlich von allen anderen bisher behandelten
Literalen. Alle Literale waren bisher einfache Zahlen. Eine Zeichenkette ist aber
eine Folge von Zahlen.
Eine Zeichenkette muß in der Zeile beendet werden, in der sie begonnen wur-
de. Will man eine Zeichenkette konstruieren, die länger ist, so kann den +–
Operator verwenden. Immer wenn zwischen zwei Zeichenketten ein + steht,
entsteht durch Aneinanderhängen der beiden ursprünglichen Zeichenketten eine
längere Zeichenkette.
43
Beispiel: So kann man eine längere Zeichenkette bauen:
44
Natürlich enthält eine Variable von dem Moment an, in dem sie definiert wurde,
immer irgendeinen Wert. Mit einer Variable ist ja immer ein gewisser Teil des
Arbeitsspeichers assoziiert, und der Wert der Variablen ergibt aus diesem Teil
des Arbeitsspeichers. Man hat aber keinerlei Kontrolle darüber, welchen Wert
der Arbeitsspeicher einer neu definierten Variable enthält. Es geht somit nicht
aus dem Programmtext hervor, welchen Wert eine Variable bei ihrer Definition
erhält, und daher nennt man Variablen, denen noch kein Wert zugewiesen wurde,
nicht initialisiert oder sagt, sie hätten einen unbestimmten Wert.
Da man einer Variable nur dann einen Wert zuweisen darf, wenn dem Compiler
der Datentyp der Variable klar ist, darf man eine Variable nur nach oder während
ihrer Definition initialisieren.
int einInt;
einInt = 2;
Man kann Definition und Initialisierung auch mit einem Befehl erledigen:
int einInt = 2;
{
einInt = 2;
int einInt;
}
Da eine Variable vor ihrer Initialisierung einen undefinierten Wert enthält und
da die Verwendung undefinierter Werte normalerweise ein Fehler ist, kontrolliert
der Java–Compiler bei jeder Verwendung einer Variable, ob sie bereits initia-
lisiert sein kann. Falls der Compiler die Verwendung einer nichtinitialisierten
Variable erkennt, meldet er einen Fehler und schützt so den Programmierer vor
der Verwendung undefinierter Werten.
45
7.4 Ausdrücke mit Operatoren
Neben der Verwendung von Literalen und Variablen gibt es in einem Java–
Programm auch komplexer aufgebaute Ausdrücke — man kann mit Variablen
oder Literalen rechnen, Variablen oder Literale miteinander vergleichen. Da sich
bei einer solchen Rechnung oder einem Vergleich wieder irgendein Wert ergibt,
sind auch Rechnungen und Vergleiche Ausdrücke. Auch Zuweisungen an Varia-
blen sind Ausdrücke, da in Java auch die Zuweisung einen Wert ergibt.
Jeder komplexe Ausdruck kombiniert mit Hilfe von Rechenoperatoren, Ver-
gleichsoperatoren oder Zuweisungsoperatoren andere Ausdrücke zu einem neuen
Wert. Es gibt Operatoren, die nur auf einen einzigen Ausdruck operieren, es gibt
aber auch Operatoren, die zwei Ausdrücke zu einem neuen Resultat verbinden.
Es gibt sogar einen Operator, der drei Ausdrücke kombiniert. Stelligkeit
Die Anzahl der Ausdrücke, auf die ein Operator angewendet wird, um ein Er-
gebnis zu liefern, nennt man die Stelligkeit des Operators.
Jeder komplexe Ausdruck mit einem 1–stelligen Operator hat entweder die Form
46
Ausdrücke mit 2–stelligen Operatoren haben immer die Form
Sie kennen nun Ausdrücke, die aus einem Literal bestehen, Ausdrücke, die aus
einer Variable bestehen und Ausdrücke, in denen einstellige und zweistellige
Operatoren auftauchen. Durch diese Möglichkeiten, Ausdrücke aufzubauen, sind
bereits fast alle erdenklichen Rechnungen in Java formulierbar.
Wir haben die Operator–Ausdrücke nämlich rekursiv, das heißt selbstbezüg-
lich, definiert. So hat ja ein zweistelliger–Ausdruck die Form
Die beiden Ausdrücke Ausdruck1 und Ausdruck2 können nun selbst wieder be-
liebige Ausdrücke sein. Beispielsweise könnte Ausdruck1 ein zweistelliger Operator–
Ausdruck mit einem anderen Operator sein, und Ausdruck2 könnte ein einstel-
liger Präfix–Ausdruck sein. Wir haben also schon Ausdrücke der Form
Beispiel: Zum Beispiel kann die Rechnung a+5+6 auf zwei verschiedene
Arten als Ausdruck interpretiert werden. Beide Arten der Interpretation
lassen sich durch Graphiken interpretieren lassen, die einem auf den Kopf
gestellten Baum ähneln:
Zum einen ist die Rechnung als
+ Ausdruck a + (5 + 6) interpretier-
+
bar, also als Ausdruck, in welchem
durch den Plus–Operator der Aus-
Variable a int−Literal 5 int−Literal 6
druck a und der Ausdruck 5 + 6 ad-
diert werden.
Beispiel:
Operator * Die Rechnung (−5) ∗ (a! − b) läßt
Operator − sich nur auf eine Art als Ausdruck
interpretieren. Die graphische Dar-
Präfixopt − Postfixopt. ! stellung der einzig möglichen Inter-
int−Literal 5 Variable a Variable b pretation sehen Sie links.
Die obenstehenden Beispiele zeigen, daß man einen Ausdruck manchmal auf
mehrere Arten aus kleineren Ausdrücke zusammensetzen kann. Dabei entspricht
47
jede Art, den Ausdruck zusammenzusetzen, einer unterschiedlichen Klamme-
rung. Selbstverständlich entspricht nicht jede Art und Weise, den Ausdruck in
kleinere Einheiten aufzubrechen der mathematischen Gewohnheit.
Beispiel: Es macht für die Rechnung zwar keinen Unterschied, ob der
Ausdruck a + 5 + 6 als a + (5 + 6) oder als (a + 5) + 6 interpretiert wird.
Es macht aber sehr wohl einen Unterschied, ob der Ausdruck a ∗ 5 + 6 als
a ∗ (5 + 6) oder als (a ∗ 5) + 6 interpretiert wird. Genauso macht es einen
Unterschied, ob der Ausdruck a − 5 + 6 als a − (5 + 6) oder als (a − 5) + 6
interpretiert wird.
Um jedem Ausdruck einen eindeutigen Wert zuzuteilen, hat man in der Mathe-
matik vereinbart, daß man Ausdrücke von links nach rechts lesen soll — der
Ausdruck a − 5 − 6 wird als (a − 5) − 6 interpretiert. Außerdem hat man Re-
chenregeln wie Punkt– vor Strichrechnung eingeführt. Es ist in der Mathematik
auch üblich, daß Einstellige Postfixoperatoren zweistelligen Operatoren vorge-
hen — der Ausdruck a + 5! wird deshalb nicht als (a + 5)! sondern als a + (5!)
interpretiert.
Auch in Java werden die üblichen Rechenregeln beachtet. In Java haben die
einzelnen Operatoren unterschiedliche Bindungskraft. Wenn in einem Ausdruck
ein Operator mit höherer Bindungskraft neben einem mit niedrigerer Bindungs-
kraft steht, so zieht der Operator mit der höheren Bindungskraft die daneben
stehenden Ausdrücke an sich.
Beispiel: Beispielsweise hat der Multiplikationsoperator eine höhere Bin-
dungskraft als der Additionsoperator. Daher wird durch Java der Aus-
druck a + 5 ∗ 6 nicht als (a + 5) ∗ 6 sondern als a + (5 ∗ 6) interpretiert —
auch in Java geht also die Multiplikation der Addition vor.
Generell gilt, daß Einstellige Operatoren eine höhere Bindungskraft haben als
zweistellige Operatoren.
Beispiel: Das einstellige Minus hat eine höhere Bindungskraft als der
zweistellige Multiplikationsoperator. Daher wird der Ausdruck −2 + 5
nicht als −(2 + 5) sondern als (−2) + 5 interpretiert.
auch schreiben
48
7.4.1 Arithmetische Operatoren
int einInt = 5;
++einInt;
so heißt er Präinkrement–Operator
• Steht ++ hinter einer Variable wie in
49
double einDouble = 5.321948;
einDouble++;
so heißt er Postinkrement–Operator
• Analog heisst der -- Operator entweder Prädekrement bzw. Postdekrement–
Operator, wenn er vor bzw. hinter einer Variable steht.
Beispiel: Zum Beispiel wird in folgendem Beispiel der Wert der Variable
einInt zweimal erhöht, wobei einmal der Postinkrement- und einmal der
Präinkrement-Operator verwendet wird. Der Wert der Ausdrücke wird
dabei nicht verwendet.
int einInt = 5;
einInt++;
++einInt;
50
wert = wert + 1; // Ausführung des Postinkrementoperators
wert = wert + 1; // Ausführung des Präinkrementoperators
int ergebnis2 = wert;
}
}
Nach Ausführung des vorigen Beispiels haben also die einzelnen Variablen
folgende Werte:
wert == 5
ergebnis1 == 5
ergebnis2 == 7
Es sei hier erwähnt, daß Sie die Verwendung der Postinkrement– und Präinkre-
mentoperatoren in komplizierten Ausdrücken vermeiden sollten, da die durch
sie bewirkten Nebeneffekte oft nur schwer zu verstehen sind.
Beispiel: Erkennen Sie zum Beispiel, welche Werte die Variablen im
folgenden Programm erhalten ?
int i1 = 3;
int i2 = 4;
int ergebnis = ++i1 + --i2 + i1-- * --i2;
int i1 = 3;
int i2 = 4;
i1--;
i2--;
ergebnis = i1 + i2 + i1 * i2;
i1++;
i2--;
einFurchtbarLangerVariablenName=einFurchtbarLangerVariablenName+1;
Schließlich sei noch erwähnt, daß die Inkrement– und Dekrementoperatoren aus-
schließlich auf Variablen anwendbar sind. Das sollte eigentlich klar sein, da man
auch nur Variablen einen Wert zuweisen kann.
Beispiel: Die folgende Anweisungsfolge ist ungültig:
int a=5;
int b=(a*2)++; // Fehler: Zwischenergebnisse kann man nicht verändern
51
7.4.3 Relationale Operatoren
Kapitel 5.3 in Go To Java bedarf m.E. keiner weiteren Erklärung — es sei nur
gesagt, daß Sie alles, was mit Referenztypen zu tun hat, noch nicht verstehen
können und auch nicht verstehen müssen.
Relationale Operatoren vergleichen zwei Werte. Ein relationaler Ausdruck hat
immer den Datentyp boolean, liefert als entweder den Wert true oder false
zurück.
Auch zu Kapitel 5.4 über logischen Operatoren ist nicht viel hinzuzufügen.
Logische Operatoren kombinieren zwei Ausdrücke vom Typ boolean zu einem
neuen Wert des Datentyps boolean. Sie dienen dazu, Wahrheitswerte zu verglei-
chen und kombinieren.
Beispiel: Hier noch ein Beispiel zur Verwendung der logischen Operato-
ren:
52
long b = 4;
boolean w1 = a >= 3 && a < 5; // true
boolean w2 = a == 4 || b == 4; // true
boolean w3 = a >= 3 && ( a==4 || b == 4); // true
boolean w4 = !(a == 3); // false
}
}
In der Auswertung a > 4 && a++ < 5 wird der Teil hinter der logischen
Und–Verknüpfung nicht ausgeführt, weil das Ergebnis des gesamten Aus-
drucks (false) nach Auswertung von a > 4 klar ist (false && irgendwas ist
immer false).
Daher wird aber auch der Postinkrement–Operator nicht ausgeführt. Der
Wert von a ändert sich daher im obigen Beispiel nicht.
Üblicherweise verwendet man immer die Short-Circuit–Operatoren. Ge-
wöhnen Sie sich daher an, niemals Operatoren mit Nebeneffekten in logi-
schen Ausdrücken zu verwenden !
Kapitel 5.5 in Go To Java 2 bedarf m.E. keiner weiteren Erläuterung, wenn Sie
sich mit Binärzahlen auskennen.
7.4.6 Zuweisungsoperatoren
53
int a;
int b = a = 5;
Das funktioniert so: Der Compiler erkennt, daß der Variable b der Wert
zugewiesen wird, der rechts vom ersten Gleichheitszeichen steht. Zunächst
wird der Ausdruck daher als
int b = (a = 5);
Sie können innerhalb jeder Rechnung Zuweisungen vornehmen. Achten Sie dabei
darauf, daß Ihre Programme lesbar bleiben.
int a;
int b;
int c = 5 * (b = 6 - (a = 5) * 20);
Nur, weil diese Anweisungen erlaubt sind, sollten Sie sie nicht verwenden.
Übersichtlicher hätte man dieses Programmfragment schreiben können als
int a = 5;
int b = 6 - a * 20;
int c = 5 * b;
Sehr hilfreich sind die Zuweisungsoperatoren, die bei der Zuweisung noch rech-
nen.
einRelativLangerVariablenName=einRelativLangerVariablenName+5;
auch schreiben
einRelativLangerVariablenName += 5;
Die zweite Schreibweise ist besser lesbar und weniger fehleranfällig. Analog
können Sie die Operatoren *=,-=,/= und die übrigen Zuweisungsoperato-
ren verwenden.
int a = 5;
int b = 3;
int c = 6 * (a += 5) - (b *= 2);
54
Auch hier gilt: Achten Sie primär auf gute Lesbarkeit Ihres Programms
und nicht auf ausgefallene oder trickreiche Verwendung dieser Operatoren.
int a = 5;
int b = 3;
int c = 6 * (b += (a -= ((a += 5) - (b *= 2))));
55
7.5 Zusammenfassung
Sie müssten nun die folgenden Fragen beantworten können:
56
8 Typwandlungen
Hier geben wir Ihnen weitere Informationen zu Goto Java 2, die Sie ergänzend
zu Hausaufgabe 3 lesen sollten.
Sie sollten für die weiteren Ausführungen zunächst in Go To Java Kapitel 4.6
über “Typkonvertierunen” lesen. Dort wird besprochen, welche Typkonvertie-
rungen der Java–Compiler automatisch vornehmen kann — leider werden keine
Beispiele für Typkonvertierungen gebracht, und es wird auch nicht erwähnt,
warum Typkonvertierungen wichtig sind.
int i1 = 5;
int i2 = 3;
float f1 = 3.123f;
float f2 = 2.723f;
double d1 = 3.1234567890123456;
Jede Rechnung mit diesen verschiedenen Variablen muß irgendwie vom Prozes-
sor ausgeführt werden. Besprechen wir das an den Beispielen der Addition und
der Multiplikation.
Übliche Prozessoren enthalten Funktionen, mit denen zwei Integer addiert oder
multipliziert werden können. Sie haben auch Funktionen, mit denen zwei Float
oder zwei sonstige Datentypen addiert oder multipliziert werden können. Das
Ergebnis dieser Funktionen hat dann normalerweise den gleichen Datentyp wie
die Operanden der Rechnung.
Beispiel: Wenn Sie mit zwei int–Variablen rechnen, kann der Prozessor
die Rechnung direkt vornehmen. Das Ergebnis ist dann immer wieder ein
Integer (überlegen Sie sich, warum das Ergebnis der Division nicht der
Erwartung entspricht !)
Genauso ist das Ergebnis beim Rechnen mit float–Variablen immer wie-
der ein Float:
57
Normalerweise enthalten Prozessoren keine Funktionen, die gleichzeitig mit ver-
schiedenen Datentypen rechnen können. Will man also mit zwei Werten ver-
schiedener Datentypen miteinander verrechnen, müssen erst beide Datentypen
in einen gemeinsamen Datentyp konvertiert werden. Danach kann die Rechnung
in diesem Datentyp stattfinden.
Beispiel: Prozessoren sind meist nicht dazu in der Lage, einen Integer-
Wert und einen Float oder einen Float und einen Double zu addieren.
Wenn Sie die Rechnung
i1 + f1;
durchführen wollen, muß sie also vorher noch aufbereitet werden. Der
Prozessor kann entweder zwei Integer oder zwei Float addieren. Es bieten
sich daher zwei Möglichkeiten an, um die Rechnung mit den Funktionen
des Prozessors durchzuführen:
Die Antwort, welche der beiden Möglichkeiten gewählt wird, fällt leicht:
Wenn wir den Float–Wert zu einem Integer wandeln, verschenken wir
Rechengenauigkeit und kommen zu falschen Ergebnissen. Wenn wir den
Integer hingegen in einen Float wandeln, erhalten wir das erwartete Er-
gebnis.
Bei Typwandlungen, die der Compiler automatisch vornimmt, wird immer dar-
auf geachtet, keine Genauigkeit zu zerstören. Datentypen werden in Rechnungen
darum nur zu genaueren Datentypen umgewandelt.
Abbildung 4.1 im Kapitel 4.6 von “Go To Java 2” zeigt genau dies — ein short,
der ja nur die ganzen Zahlen von −21 5 bis 21 5 − 1 darstellen kann, kann au-
tomatisch in die umfangreicheren Datentypen int, long oder float umgewandelt
werden. Hingegen wird der Datentyp double niemals automatisch in einen an-
deren Datentyp gewandelt, da dies der genaueste und umfangreichste Datentyp
der Sprache Java ist.
58
Typkonvertierungen treten immer auf, wenn ein Ausdruck eines gewissen Typs
erwartet wird, aber ein anderer Typ vorliegt. Es kommt des öfteren vor, daß in ei-
nem Ausdruck ein niedriger Genauigkeit benötigt wird, der zu benutzende Wert
aber eine hohe Genauigkeit hat. In solch einem Fall würde eine Typwandlung
Genauigkeit verschenken. Der Java–Compiler würde daher statt eine automati-
sche Typwandlung vorzunehmen, das Programm als fehlerhaft bemäkeln.
Ein gutes Beispiel für Fälle, in denen eine Typkonvertierung zu ungenaueren Ty-
pen nötig ist, bieten die Zuweisungsoperatoren. Der einer Variable zugewiesene
Wert muß ja immer den Datentyp der Variable haben. Wird nun einer Variable
eines “ungenauen” Datentyps ein Wert eines genaueren Datentyps zugewiesen,
so erzeugt das einen Kompilationsfehler.
Alle oben genannten Zuweisungen sind erlaubt, da hier bei den automa-
tischen Typwandlungen keine Genauigkeit verlorengeht.
Hingegen sind die folgenden Anweisungen nicht erlaubt:
01 int i;
02 i = 1; // erlaubt: Zuweisung int-Literal an int
03 i = 1L; // Fehler: long-Literal, aber int erwartet
04 i = 3.141; // Fehler: Zuweisung double-Literal an int
05 float f;
06 f = 1; // erlaubt: Zuweisung int-Literal an float
07 f = 3.141f; // erlaubt: Zuweisung float-Literal an float
08 f = 3.141; // Fehler: double-Literal, doch float nötig
09 double d;
10 d = 1l; // erlaubt: Zuweisung long-Literal an float;
11 d = 3.141f; // erlaubt: Zuweisung float-Literal an double
12 d = 3.141; // erlaubt: Zuweisung double-Literal an double
59
8.2 Manuelle Typkonvertierungen
Oft will man trotzdem Werte höherer Genauigkeit an Stellen verwenden, an
denen eine niedrigere Genauigkeit benötigt wird. Wieder ist die Zuweisung eines
Wertes an eine Variable das beste Beispiel für derartige Fälle.
Wenn wir beispielsweise wissen, daß in einer float–Variable ein Wert ohne Nach-
kommastellen enthalten ist, sollten wir in der Lage sein, diesen als Integer auf-
zufassen.
Oftmals brauchen wir die höhere Genauigkeit auch nicht wirklich. Es ist zum
Beispiel üblich, die Zwischenschritte einer Rechnung in einer hohen Genauigkeit
durchzuführen und dann das Endergebnis der Rechnung nur in einer niedrigen
Genauigkeit zu verwenden.
Java nimmt ohne unser Zutun solche Typwandlungen nicht vor. Wir können
Java aber befehlen, eine Typwandlung vorzunehmen. Wir verwenden dazu einen
Typwandlungsoperator und signalisieren Java damit: An dieser Stelle ist es in
Ordnung, Genauigkeit zu verschenken, wir wissen schon was wir tun.
Type–cast
Definition: Type–cast
(datentyp) wert
i1 = (int)f1;
i2 = (int)d1;
f1 = (float)d1;
Mit den oben definierten Werten für die einzelnen Variablen ergeben sich
hier die folgenden Werte für i1 bis f1:
i1 == (int)f1 == (int)3.123 == 3
i2 == (int)d1 == (int)3.1982 == 3
f1 == (float)d1 == (float)3.1234567890123456 == 3.1234567
Ein Wert niedrigerer Genauigkeit ergibt sich dabei meist aus einem Wert
höherer Genauigkeit durch wegwerfen von Nachkommastellen.
Sie können bei jedem Ausdruck eine Typwandlung vornehmen. Sie dürfen Typ-
wandlungen auch an Stellen vornehmen, an denen sie überflüssig sind.
60
f1 = (float)i1;
d1 = (double)f1;
Eine wichtige Besonderheit ist bei Typwandlungen noch zu erwähnen: Die ein-
zelnen primitiven Datentypen sind nicht nur unterschiedlich genau. Auch der
Wertebereich unterscheidet sich. Auf diese Problematik wollen wir hier aber
nicht weiter eingehen — hier nur ein Beispiel dazu:
Beispiel: Wenn Sie einen zu großen Double–Wert in einen Float wandeln,
wird der Wert als Infinity dargestellt:
8.3 Zusammenfassung
Sie müssten nun die folgenden Fragen beantworten können:
61
9.1 Leere Anweisung
Siehe auch Kapitel 6.1 in Go To Java.
Die einfachste Anweisung in einem Java–Programm ist die leere Anweisung.
Sie hat keine Wirkung. Daß die leere Anweisung erlaubt ist, bedeutet im we-
sentlichen, daß Sie beliebig viele Semikoli setzen dürfen — auch dort, wo nicht
unbedingt eines stehen muß.
Beispiel:
9.2 Blockanweisung
Der Block gruppiert eine Gruppe von Anweisungen zu einer Einheit. Sie hat die
Form
{
Anweisung1;
Anweisung2;
...
}
Sie sollten sich angewöhnen, den Inhalt eines Blockes durch Einrückung der im
Block enthaltenen Zeilen optisch kenntlich zu machen — in allen Beispielpro-
grammen machen wir Ihnen das auch so vor.
Beachten Sie, daß die Beschreibung der Blockanweisung rekursiv ist — der Block
ist eine Anweisung und in einem Block dürfen wieder Anweisungen stehen. Da-
her darf in einem Block auch ein weiterer Block enthalten sein.
62
} // hier endet Block 2
} // hier endet Block 1
} // hier endet der Methodenkörper
} // hier endet der Klassenkörper
9.3 Variablendefinitionen
Variablendefinitionen haben wir schon oft benutzt. Sie wissen bereits, daß man
Variablen nur verwenden kann, nachdem man sie definiert hat.
Zu beachten ist, daß eine Variablen Variablendefinition immer nur für den Block
gilt, in welchem sie definiert wurde. Sobald die Programmausführung einen
Block verläßt, haben die in diesem Block definierten Variablen keine Bedeutung
mehr. Man sagt auch: Variablendefinitionen gelten nur lokal im enthal-
tenden Block.
Wenn in einem Block ein weiterer Block enthalten ist, erbt dieser alle Varia-
blendefinitionen des äußeren Blockes:
63
public class DemoBlock2 {
public static void main( String[] args )
{
int i = 1;
double i = 3.5; //Fehler
}
}
Leider dürfen sich in Java Variablen nicht gegenseitig verdecken — sie dürfen
Variablen auch dann nicht in einem Block definieren, wenn sie zuvor nur in
einem diesen Block umschließenden Block definiert wurden.
9.4 Ausdrucksanweisungen
Zu den Ausführungen über Ausdrucksanweisunge in Kapitel 6.1 von Go To Java
2 braucht nichts hinzuzugefügt zu werden außer einem Beispiel:
64
9.5 If–Anweisung
Kapitel 6.2 von Go To Java 2 sollte so verständlich sein. Nur fehlen mal wieder
Beispiele.
Sie sehen ja in Go To Java 2, daß eine if –Anweisung in zwei Versionen auftreten
kann. Einmal mit und einmal ohne else–Zweig. Beachten Sie bei der Besprechung
aller weiteren Anweisungen, daß eine Anweisung auch ein Block sein kann.
Beispiel: Hier ein Beispiel, bei welchem die Anweisung ein Block ist:
Beispiel: Und hier noch ein Beispiel, in dem es auch einen else–Zweig
gibt:
Das in Go To Java erwähnte Dangling else (“hängendes Else”) bedarf noch einer
weiteren Diskussion: Sie sollten Dangling else—Strukturen immer vermeiden.
Verwenden Sie in Fällen, wo ein Dangling else auftritt, immer Blöcke !
65
Beispiel: Hier tritt ein Dangling else auf — aufgrund der suggestiven
Einrückung ist nicht auf den ersten Blick klar, daß die else–Anweisung
nicht zur ersten sondern zur zweiten if –Anweisung gehört. Das Programm
arbeitet daher nicht so, wie gewünscht.
Das Problem kann durch Verwendung von Blöcken behoben werden. Sie
sollten sich angewöhnen, in solchen leicht mißverständlichen Situationen
immer Blöcke zu verwenden !
9.6 Switch–Anweisung
Die Switch–Anweisung ist in Go To Java in Kapitel 6.2.2 beschrieben.
Zur switch–Anweisung ist hinzuzufügen, daß hinter jedem case eine Reihe von
Anweisungen steht. Wird ein case angesprungen, so werden auch die darauf
folgenden case–Blöcke abgearbeitet, wenn kein break in den Anweisungen steht.
66
System.out.println("Eins");
} else if (zahl == 2) {
System.out.println("Zwei");
} else if ( zahl == 3) {
System.out.println("Drei");
} else if (zahl == 4) {
System.out.println("Vier");
} else {
System.out.println("Diese Zahl kenne ich nicht");
}
9.7 Schleifen
Zu Kapitel 6.3 ist nicht viel hinzuzufügen außer einigen Beispielen.
Die While–Schleife können Sie immer dann einsetzen, wenn Sie eine oder mehre-
re Anweisungen immer wieder ausführen wollen, solange eine gewisse Bedingung
erfüllt ist.
Sie hat die Form
while( Schleifenbedingung )
anweisung;
67
Beispiel: Die Anweisung in Zeile 05 wird niemals ausgeführt, da die
Schleifenbedingung in Zeile 04 immer false ist.
Beispiel: Eine Schleife, die immer wieder ausgeführt wird und niemals
wieder verlassen wird, nennt man Endlosschleife. Das folgende Pro-
gramm ist eine solche, da die Bedingung in Zeile 04 immer true ist.
Dieses Programm beendet sich nie, nachdem Sie es gestartet haben. Es
gibt wieder und wieder “Hallo, Welt” aus. Sie können es auf die harte
Tour unterbrechen, indem Sie die Tastenkombination Ctrl+c bzw. Strg+c
verwenden.
Beispiel: Das folgende Programm gibt die Zahlen von 1 bis 10 auf den
Bildschirm aus. Es verwendet dazu eine Zählervariable. Es demonstriert
außerdem, daß die Schleifenanweisung auch ein Block sein kann.
Man könnte das obige Programm auch noch kompakter schreiben, indem
man den Inkrement–Operator in den Ausgabe–Befehl schreibt:
68
Eine weitere Variante des vorigen Programmes ist lehrreich. Wir können
die Variable i auch innerhalb der Schleifenbedingung inkrementieren. Da-
bei müssen wir aber darauf achten, daß die Schleifenbedingung schon vorm
ersten Durchlauf der Schleife einmal ausgeführt wird.
Auch das folgende Programm gibt die Zahlen 1 bis 10 aus. Machen Sie sich
das klar ! Achten Sie vor allem darauf, daß nun in der Schleifenbedingung
ein echtes kleiner und nicht ein kleiner–gleich–Zeichen steht.
Die do–Schleife arbeitet so ähnlich wie die while–Schleife. Sie hat die Form
do
anweisung;
while ( Schleifenbedingung );
Sie unterscheidet sich von der while–Schleife dadurch, daß der Schleifenkörper
in jedem Fall mindestens einmal durchlaufen wird.
Genau wie bei der while–Schleife wird nach jedem Durchlauf des Schleifenkör-
pers die Schleifenbedingung geprüft. Immer wenn die Bedingung true ergibt,
wird die Schleife erneut durchlaufen.
69
04 do
05 System.out.println("Hallo, Welt");
06 while( true );
07 }
08 }
Beispiel: Das folgende Programm gibt die Zahlen von 1 bis 10 aus.
Beachten Sie den feinen Unterschied zum äquivalenten Programm mit
while–Schleife (class DemoWhile4) aus dem vorigen Abschnitt. Machen
Sie sich klar, warum hier nicht mit i=0 sondern mit i=1 begonnen wird.
do
anweisung;
while( Schleifenbedingung );
anweisung;
while ( Schleifenbedingung )
anweisung;
while ( Schleifenbedingung )
anweisung;
if ( Schleifenbedingung ) {
do
anweisung;
while( Schleifenbedingung);
}
Die For–Schleife ist die wohl am häufigsten verwendete Schleife. Während die
while– und do–Schleife meist dann eingesetzt werden, wenn man beim Beginn
70
der Schleifenabarbeitung noch nicht genau sagen kann, wie oft die Schleife
durchlaufen werden soll, setzt man die for–Schleife meist dann ein, wenn man
schon vor Beginn der Schleife weiß, wie oft die Schleife durchlaufen werden soll.
Meist wird die for–Schleife dazu eingesetzt, eine Variable einen gewissen Wer-
tebereich durchlaufen zu lassen.
Sie hat die Form
In den runden Klammern hinter dem for–Befehl werden drei durch Semikoli
getrennte Ausdrücke erwartet.
Wie eine for–Schleife arbeitet, wird am besten klar, indem wir sie in eine äqui-
valente while–Schleife umschreiben. Die for–Schleife kann man als Abkürzung
für die folgende Schleife interpretieren:
init;
while( test) {
anweisung;
update;
}
Der erste Teil in den runden Klammern (init) wird vor dem Start der Schleife
ausgewertet. Er dient meist dazu, in der Schleife benötigte Variablen zu definie-
ren oder zu initialisieren.
Folgende Anweisungen und Ausdrücke sind hier erlaubt:
• “init” darf eine Liste von Ausdrücken sein, die dann durch Kommata ge-
trennt sein müssen, wie in diesem Beispiel:
71
• “init” darf leer sein:
for( ; false; ) ;
Beispiel: Das folgende Programm gibt die Zahlen von 1 bis 10 aus.
Beispiel: Auch das folgende Programm gibt die Zahlen von 1 bis 10 aus.
Es empfiehlt sich, die Variablendefinition in die Initialisierungsanweisung
zu schreiben.
Beispiel: Das folgende Programm gibt die Zahlen von 1 bis 10 rückwärts
aus:
72
01 public class DemoFor4 {
02 public static void main( String[] args )
03 {
04 for( ; false ; )
05 System.out.println("Hallo, Welt");
06 }
07 }
73
9.7.4 break und continue
Bitte lesen Sie die Ausführungen zu break und Continue in Go To Java, Kapitel
6.3 nach.
74
9.8 Zusammenfassung
Sie müssten nun die folgenden Fragen beantworten können:
10 Objekte in Java
Sie kennen nun die sogenannten imperativen Bestandteile von Java: Variablen,
Verzweigungen und Schleifen. Unser nächstes Ziel ist, Ihnen die objektorientier-
ten Bestandteile zu vermitteln.
In Kapitel ?? wurde unabhängig von Java erläutert, was Klassen und Objekte
sind. Wenn Ihnen die Inhalte nicht mehr geläufig sind, lesen Sie es bitte noch
einmal.
Genau wie in Kapitel ?? beschrieben, ist auch in Java ein Objekt etwas, das einen
75
Zustand, eine Schnittstelle und Verhalten besitzt. Der Zustand eines Objektes
ergibt sich aus seinen Attributen oder Variablen. Seine Schnittstelle und sein
Verhalten ist durch die in seiner Klasse beschriebenen Methoden definiert.
In diesem Kapitel soll nur gezeigt werden, wie man Objekte in Java benutzen
kann, welchen Lebenszyklus ein Objekt hat und wie sich Objekte von den Ihnen
bisher bekannten Datentypen wie int oder float unterscheiden.
Um ein Objekt zu erzeugen, muß zunächst seine Klasse definiert sein. Wie man
eigene Klassen definiert, soll erst in Kapitel 12 beschrieben werden. Um das
Verhalten von Objekten zu demonstrieren, werden wir in diesem Kapitel eine
von Java bereitgestellte Klasse verwenden.
Primitive Datentypen sind also die logischen Datentypen, Zeichen sowie alle
bisher benutzten Zahlen. Primitive Datentypen heißen so, da man als Program-
mierer selbst keine primitiven Datentypen definieren kann — alle primitiven Da-
tentypen sind von den Java–Schöpfern fest eingebaut worden. Vielleicht kommt
diese Benennung aber auch daher, daß primitive Daten im Vergleich mit Objek-
ten so “primitiv” sind.
Primitive Datentypen sind keine Klassen (wir erklären gleich einige Konsequen-
zen), und darum sind ihre Werte und Variablen auch keine Objekte.
Alle primitiven Datentypen haben gemeinsam, daß man die möglichen Werte
eines primitiven Datentyps durch Literale angegeben kann:
int i = 1;
char c = ’a’;
double f = 4.2390823;
Sie haben auch schon Objekte und Klassen kennengelernt, ohne daß Sie es wus-
sten. Zeichenketten sind nämlich keine primitiven Datentypen sondern Objekte
der Klasse String. Da wir bisher nur wenige Fähigkeiten von Strings benutzt
haben, konnten Sie aber bisher vermutlich noch keinen Unterschied zwischen
Strings und den primitiven Datentypen feststellen. In diesem Kapitel wird sich
das etwas ändern.
Objekte kann man in der Regel nicht durch Literale erzeugen. Strings und Ob-
jekte der in Kapitel 11 behandelten Array–Klassen bilden hier die große Aus-
nahme. Die Java–Erfinder waren sich darüber im klaren, daß Strings unheimlich
häufig verwendet werden und haben daher die Möglichkeit geschaffen, Strings
durch Literale zu erzeugen.
76
String einString = "Hallo, Welt";
String name = "Homer";
String wunsch = "Pizza";
77
danach über die String–Variable name angesprochen werden kann.
Auch in Zeile 4 wird ein String–Objekt erzeugt, welches den Text "Pizza"
enthält. Hier wird das Objekt jedoch nicht einfach durch Angabe des
String–Literals sondern durch Verwendung des new–Operators erzeugt.
Der new–Operator wird normalerweise immer benötigt, um ein Objekt
einer Klasse zu erzeugen. Da Strings auch durch Literale erzeugt werden
können, kann man auf den new–Operator bei Strings aber meist verzich-
ten.
In Zeile 5 wird demonstriert, wie man an ein Objekt eine Botschaft
schicken kann. Hier wird an das String–Objekt "Bart Simpson" die Bot-
schaft substring(0,4) geschickt. Das String–Objekt liefert daraufhin ein
String–Objekt zurück, das seine ersten vier Buchstaben, also den Text
"Bart", enthält. In Zeile 6 wird auf die gleiche Weise ein String–Objekt
erzeugt, das die ersten 5 Buchstaben des durch name bezeichneten Ob-
jektes enthält.
In Zeile 7 wird an das durch name bezeichnete Objekt die Botschaft
length() geschickt. Daraufhin liefert das Objekt die Länge des enthal-
tenen Textes.
In Zeile 8 wird an das durch name bezeichnete Objekt die Bot-
schaft concat(" will ") geschickt. Die Botschaft concat veranlaßt
ein String–Objekt, ein neues Objekt zu erzeugen, indem es das in
der concat–Botschaft mit übergebene Objekt an sich selbst anhängt.
Das "Homer Simpson"–Objekt erzeugt also das neue String–Objekt
"Homer Simpson will ".
An dieses String–Objekt wird dann die Botschaft "concat(wunsch)" ge-
schickt, woraufhin das String–Objekt "Homer Simpson will Pizza" er-
zeugt wird.
int a;
a.tueWas(); // gibts nicht
5.quadrieren(); // auch ein Fehler
aufrufen könnte.
78
Die Objekterzeugung geht so vor sich, daß zunächst Speicherplatz für das Ob-
jekt bereitgestellt wird. Diesen Speicherplatz können Sie sich als eine Sammlung
von Variablen vorstellen: Ein Objekt ist eine Art Container, in dem einige Va-
riablen enthalten sind. Dabei entspricht jedes in der Klasse definierte Attribut
einer Variable. Die Variablen, die zu einem Objekt gehören, nennt man auch
Instanzvariablen, und der Zustand eines Objektes bestimmt sich aus den Zu-
ständen seiner Instanzvariablen.
Als erster Schritt wird also das Objekt mitsamt den enthaltenen Variablen an-
gelegt. Da neu angelegte Variablen normalerweise einen undefinierten Zustand
haben und Objekte mit nicht definiertem Zustand in der Regel unerwünscht
sind, wird anschließend ein in der Klasse definiertes Initialisierungsverhalten
ausgeführt, welches den Instanzvariablen bestimmte Werte zuweist.
Dieses Initialisierungsverhalten wird in einer oder mehreren Methoden beschrie-
ben, welche man in der objektorientierten Methodik als Konstruktoren be-
zeichnet. Man kann in Java das Anlegen und das Initialisieren des Objektes
nicht voneinander trennen, insofern empfiehlt es sich davon zu sprechen, daß
der Konstruktor ein Objekt erzeugt und initialisiert.
Einem Konstruktor können auch zusätzliche Daten mitgegeben werden, die zur
Initialisierung verwendet werden.
Konstruktor
Definition: Konstruktor
Ein Konstruktor ist eine Methode, welche ein Objekt erzeugt und da-
bei seinen anfänglichen Zustand initialisiert. Eine Klasse kann meh-
rere Konstruktoren für ihre Objekte definieren.
new Klasse()
verwendet. Allgemeiner geschieht die Erzeugung eines Objektes durch einen Aus-
druck der Form
Dabei sind argument1 bis argumentN Argumente, die zur Initialisierung des
Objektes benutzt werden. Als Konstruktor wird ein solcher gewählt, der zu den
angegebenen Argumenten paßt (falls es in der Klasse keinen solchen Konstruktor
gibt, wird ein Kompilierfehler erzeugt). Was das genau bedeutet, erfahren Sie
in Kapitel 12.
Beispiel: Ein String kann außer per Literal auch per Konstruktor erzeugt
werden.
79
2 String string2 = "Andreas";
3 String string3 = new String("Hallo, Welt");
4 String string4 = new String( string3 );
Beispiel: Angenommen, es sei eine Klasse Kind gegeben, welche ein Kind
durch seinen Namen und sein Alter repräsentiert. Die Klasse könnte wie
folgt definiert sein:
1 class Kind {
2
3 // Instanzvariablen eines Kindes:
4 String name;
5 int alter;
6
7 // Konstruktor:
8 public Kind( String neuerName, int neuesAlter ) {
9 name = neuerName;
10 alter = neuesAlter;
11 }
12 }
In Zeile 4 und 5 sind zwei Attribute, name und alter definiert. In Zeile
8–11 ist eine Methode definiert, deren Name gleich dem Klassennamen
ist. Dies ist ein Konstruktor.
Wenn ein neues Kind erzeugt werden soll, könnte der Konstruktor wie
folgt aufgerufen werden:
Hier wird zunächst ein neues Kind–Objekt erzeugt, welches die beiden
Variablen name und alter enthält. Anschließend wird der Konstruktor der
Klasse (Zeile 8–11) ausgeführt. Der Konstruktor initialisiert daraufhin das
Attribut name zu "Bart Simpson" und das Attribut alter zu 12.
Sie sehen an diesem Beispiel, daß ein Konstruktor eigentlich nichts anderes
als eine Methode mit besonderem Namen ist.
80
10.3 Die Identität eines Objektes
Nachdem wir ein Objekt erzeugt haben, wollen wir es verwenden. Dazu müssen
wir das Objekt identifizieren können — wir benötigen also einen Namen, unter
dem wir es ansprechen können.
Genau wie bei primitiven Datentypen verwenden wir auch für Objekte Varia-
blen. Es gibt dabei jedoch einen großen Unterschied: Während primitive Daten
in einer Variable gespeichert werden, wird ein Objekt durch eine Variable
nur referenziert.
Um primitive Daten in einer Variable zu speichern, verwendet man den Zu-
weisungsoperator. Auch bei der Zuweisung von Objekten verwendet man den
Zuweisungsoperator. Er bewirkt hier aber nicht, daß eine Kopie angelegt wird
sondern daß die zugewiesene Variable danach das Objekt referenziert. Es ist
durchaus möglich, daß mehrere Variablen das gleiche Objekt referenzieren.
Bitte merken Sie sich: Durch die alleinige Verwendung des Zuweisungs-
operators wird kein neues Objekt erzeugt.
int i 1
int j 1
double f 3.141
81
Klasse ReferenzDemo
1 class ReferenzDemo {
2 public static void main( String[] args ) {
3 String name = "Hallo, Welt";
4 String n1 = name;
5 String n2 = new String( name );
6 System.out.println( n1 == name );
7 System.out.println( n2 == name );
8 }
9 }
Die Situation nach Ausführung der Zuweisungen kann hier wie folgt gra-
phisch dargestellt werden: Es gibt zwei String–Objekte mit gleichem In-
halt. Eines wird durch die Variablen name und n1 referenziert, und das
andere wird durch die Variable n2 referenziert:
String−Objekt
String n2 referenziert
Zustand: "Hallo, Welt"
In Zeilen 6 und 7 des vorigen Beispiels verwenden wir den == Operator. Bis-
her haben Sie vermutlich gedacht, daß der ==–Operator zwei Ausdrücke auf
Gleichheit prüfe. Das stimmt aber so nicht ganz: der ==–Operator prüft, ob
zwei Ausdrücke identisch sind.
Zwei primitive Daten mit gleichem Wert sind auch identisch. Zwei Variablen,
die Objekte referenzieren, sind hingegen nur dann identisch, wenn sie beide auf
das gleiche Objekt verweisen. In Zeile 6 des vorigen Beispiels wurde daher true
und in Zeile 7 false ausgegeben. Gleichheit
Um das nochmal ganz deutlich zu machen: Wir unterscheiden zwischen Gleich- und
heit und Identität. Zwei Objekte sind gleich, wenn sie den gleichen Zustand Identität
haben. Zwei Objekte sind identisch, wenn sie beide das gleiche Objekt sind.
Im obigen Beispiel sind die durch name und n2 referenzierten Objekte gleich, da
sie beide den Text "Hallo,Welt" enthalten. Hingegen sind name und n2 nicht
identisch, da sie auf unterschiedliche Objekte verweisen.
Die Identität eines Objektes ist unabhängig von seinem Zustand. Auch wenn
sich der Zustand eines Objektes ändert, bleibt seine Identität erhalten.
Es ist möglich, daß zwei nicht–identische Objekte den gleichen Zustand haben.
Es ist aber nicht möglich, daß zwei identische Objekte unterschiedliche Zustände
haben.
Bitte merken Sie sich folgendes: Identität
82
Identität:
Wenn wir nun ein Objekt auffordern wollen, eine Methode auszuführen, schicken
wir ihm eine Botschaft, deren Aufbau genau dieser Beschreibung entspricht.
Wenn eine Methode kein Ergebnis zurückliefert, schreiben wir die Aufforderung
an das Objekt einObjekt die Methode methodenName auszuführen, wie folgt:
einAusfuehrer.methodenName(eingabe 1 , . . . , eingabe n )
Dabei übergeben wir wir soviele zusätzliche Objekte eingabe 1 , . . . , eingabe n , wie
in der Schnittstelle beschrieben. Das Objekt eingabe i muß dabei Mitglied der
Klasse Klasse i sein.
Wenn die Methode ein Ergebnis liefert, können wir dieses Ergebnis abholen.
Dazu benötigen wir ein Objekt ergebnis der Klasse Klasse r . Wir schreiben in
diesem Fall:
83
ergebnis = einAusfuehrer.methodenName(eingabe 1 , . . . , eingabe n ).
Beispiel: Zum Beispiel ist in der String–Klasse die Methode length de-
finiert, welche die Länge eines Strings zurückgibt. Sie nimmt keinen wei-
teren Parameter entgegen und liefert eine int–Zahl zurück.
Die Länge eines Strings kann man daher ermitteln, indem man dem String
die Botschaft length() schickt:
Bisher haben wir uns das Bild gemacht, daß wir mit Objekten kommunizieren,
indem wir ihnen Botschaften schicken. Dieses Bild ist auch weiterhin richtig, und
es entspricht hervorragend der objektorientierten Denkweise. Es ist nun leider
an der Zeit, diese Denkweise ein wenig an Java anzupassen.
In Java bedeutet der Punkt nach dem Objektnamen eigentlich nicht das Ver-
schicken einer Botschaft. Vielmehr hat man in Java die Vorstellung, daß ein
Objekt ein Container ist, welcher Instanzvariablen und Methoden enthält. Auf
die in einem Objekt enthaltenen Dinge kann man dann zugreifen, indem man
objekt.enthaltenesDing
schreibt. Der Punkt dient also sozusagen dazu, das Objekt aufzumachjen und auf
die enthaltenen Dinge zuzugreifen. Insofern paßt die Vorstellung, man schicke ei-
nem Objekt Botschaften nicht hundertprozentig zu Java. Man kann die Schreib-
weise
objekt.methode()
auch als den Aufruf einer im Objekt enthaltenen Methode verstehen. Natür-
lich ist diese Vorstellung äquivalent zur Vorstellung, daß wir dem Objekt eine
Botschaft schicken.
Man kann den Punkt auch verwenden, um auf Instanzvariablen eines Objektes
zuzugreifen, und hier hört die Analogie zum Verschicken von Botschaften auf.
Weiter oben hatten wir ein Beispiel mit einer Kind–Klasse. Wir können auf die
Attribute eines Kindes dieser Klasse wie folgt zugreifen:
Kind einKind;
einKind.name = "Bart Simpson";
einKind.alter = 12;
Hier dient der Punkt also tatsächlich dazu, auf die in einem Objekt enthaltenen
Variablen zuzugreifen.
Vielleicht sollten wir daher in Zukunft lieber davon reden, daß ein Objekt Me-
thoden enthalte, und daß wir, statt dem Objekt eine Botschaft zu schicken, eine
im Objekt enthaltene Methode aufrufen. Vermutlich werden wir in Zukunft bei-
de Sprechweisen verwenden — sie sind schließlich äquivalent. Das Schicken von
Botschaften scheint dabei auch immer noch anschaulicher, auch wenn es nicht
so hundertprozentig zur Java–Philosophie paßt.
84
10.5 Welche Botschaften versteht ein Objekt ?
Wenn Sie selbst eine Klasse schreiben, wissen Sie natürlich, welche Methoden
enthalten sind. Wie aber können Sie erfahren, welche Methoden fremde Klassen
enthalten ?
Zu Java gehört eine ganze Menge an Dokumentation. Sämtliche in Java ent-
haltenen Klassen sind in der Dokumentation des JDK (Java Development Kit)
beschrieben. Wie diese Dokumentation genau aufgebaut ist, erklären wir Ihnen,
sobald wir Klassen (Kapitel 12) und Pakete (Kapitel 15) besprochen haben.
In den folgenden Hausaufgaben und in den weiteren Texten werden Sie häufig
mit dieser Dokumentation arbeiten müssen. Hier soll Ihnen die Verwendung der
JDK–Dokumentation anhand eines Beispiels demonstriert werden:
http://www.tu-bs.de:82/wir/EIP/jdk1.1.8/docs/api/java.lang.String.html
Bitte klicken Sie sich dorthin, indem Sie von unseren WWW–Seiten un-
ter den Litaturlinks zu Java auf den relativ weit unten stehenden Link
“Java–Api (lokal)” klicken. Sie gelangen dann auf eine Seite, auf der eini-
ge sogenannte “packages” aufgelistet sind.
Dort klicken Sie bitte auf den als java.lang bezeichneten Link. Anschlie-
ßend erhalten Sie eine Liste von Klassen, unter denen sich auch die Klasse
String befindet.
Wenn Sie nun auf den String betitelten Link klicken, gelangen Sie zu der
oben genannten WWW–Seite.
Auf dieser WWW–Seite sind alle Methoden beschrieben, die String–
Objekte verstehen. Bitte lesen Sie den kompletten ersten Teil der Seite
durch und schauen Sie sich unter der Überschrift “Method Index” die
Methode charAt an. Wenn Sie darauf klicken, sollten Sie die folgende Er-
läuterung erhalten:
Parameters:
index - the index of the character.
Returns:
the character at the specified index of this string.
The first character is at index 0.
Throws: StringIndexOutOfBoundsException
if the index is out of range.
Sie erkennen, daß hier eine Methode namens charAt beschrieben wird,
mit der man einen String auffordern kann, ein in ihm enthaltenes Zeichen
zurückliefern.
Beachten Sie, daß die Zeichen eines Strings mit 0 beginnend durchnume-
riert werden. Das erste Zeichen eines Strings erhält man, also indem man
ihm die Botschaft charAt( 0 ) schickt.
85
Mit Hilfe der obigen Beschreibung können wir ein Programm schreiben,
das alle Zeichen eines Strings einzeln ausgibt. Dazu rufen wir in einer
Schleife wiederholt die String–Methode charAt auf und geben das durch
den String zurückgegebene Zeichen mit der Methode System.out.println
aus. Um die Länge des Strings zu ermitteln, schicken wir ihm die Botschaft
length(). Bitte sehen Sie sich auch die Dokumentation zur length–Methode
an.
Klasse DemoStringMethods
1 class DemoStringMethods {
2 static public void main( String[] args ) {
3 String text = "Hallo, Welt";
4 for( int i = 0; i < text.length(); i++ ) {
5 System.out.println( text.charAt( i ) );
6 }
7 }
8 }
86
und ihre Lebensdauer daher durch die Lebensdauer der Variablen begrenzt ist.
Objekte müssen nur deshalb gelöscht werden, da sie unabhängig von Variablen
existieren.
10.7 Zusammenfassung
Sie sollten nun die folgenden Fragen beantworten können:
87
B Welche primitiven Datentypen kennt Java ?
B Nennen Sie einige Unterschiede zwischen primitiven Datentypen und
Klassen !
B Wie erzeugt man ein Objekt ?
B Was ist ein Konstruktor ?
B Beschreiben Sie, was Sie unter der Identität eines Objektes verstehen !
B Enthalten Variablen Objekte ?
B Was bewirkt der Zuweisungsoperator bei primitiven Daten ?
B Was bewirkt der Zuweisungsoperator bei primitiven Objekten ?
B Was bewirkt der ==–Operator bei primitiven Daten ?
B Was bewirkt der ==–Operator bei Objekten ?
B Was ist der Unterschied zwischen Gleichheit und Identität ?
B Wie schickt man einem Objekt eine Botschaft ?
B Wie kann man herausfinden, welche Methoden die Klasse String ent-
hält ?
B Wie werden Objekte zerstört ?
B Wie arbeitet der Garbage–Collector ?
B Warum kann der Garbage–Collector keinen Schaden anrichten ?
11 Arrays
Bevor Sie lernen, selbst Klassen zu definieren, möchten wir Ihnen noch einen
weiteren Typ von sehr wichtigen Objekten vorstellen: Arrays.
Arrays sind Objekte, welche eine größere Anzahl von Zahlenwerten oder anderen
Daten verwalten können. Sie können sich ein Array als eine durchnumerierte
Sammlung von Variablen vorstellen. Arrays sind die einfachste Möglichkeit, die
Java zur Verwaltung mehrerer gleichartiger Daten oder Objekte bietet.
Arrays unterscheiden sich von allen anderen Objekten und Klassen in Java: Die
Array–Klassen haben einen recht eigenartigen Namen, und auch die Konstruktor–
Aufrufe von Arrays unterscheiden sich von normalen Konstruktor–Aufrufen.
88
typ[] arrayVariable;
Da Arrays Objekte sind, müssen sie durch einen Konstruktor erzeugt werden.
Dem Erzeugen eines Arrays muß man angebeben, wie groß das Array sein soll,
d.h. für wie viele Werte oder Objekte im Array Platz sein soll.
Ein Array–Objekt der Größe n eines Datentyps typ wird wie folgt erzeugt:
new typ[ n ]
Auch hier wird eine etwas eigenartige Schreibweise verwendet — normalerwei-
se wird der Konstruktor einer Klasse ja geschrieben als Klasse( argumente ).
Für ein Array würde man also eigentlich erwarten, daß der Konstruktor–Aufruf
als Datentyp[](n) geschrieben wird. Auch hier haben Arrays also eine Sonder-
stellung.
Beispiel: Im folgenden Beispiel wird in Zeile 3 eine Array–Variable na-
mens einIntArray definiert, welche int–Werte speichern kann. In Zeile 4
wird ein Array–Objekt der Größe 100 angelegt und durch einIntArray
referenziert.
In Zeile 6 wird eine Variable zur Referenzierung eines double–Arrays er-
zeugt und mit einem Array der Größe 10 initialisiert.
Zeile 8–9 zeigen, daß man nicht nur Arrays von primitiven Datentypen
sondern auch Arrays von Objekten definieren kann.
Klasse ArrayDefinitionen
1 class ArrayDefinitionen {
2 public static void main( String[] args ) {
3 int[] einIntArray;
4 einIntArray = new int[ 100 ];
5
6 double[] einDoubleArray = new double[ 10 ];
7
8 String[] einStringArray;
9 einStringArray = new String[ 50 ];
10 }
11 }
Bevor wir darauf eingehen, wie man Objekte benutzt, sollen noch ein paar Ei-
genarten von Arrays erwähnt werden:
• Die Größe eines Arrays kann im Nachhinen nicht mehr verändert werden.
Wenn Sie nach der Erzeugung eines Arrays feststellen, daß es zu klein ist,
können Sie es also nicht mehr wachsen lassen.
Statt dessen müssten Sie in diesem Fall ein weiteres, hinreichend großes
Array erzeugen und den Inhalt des alten Arrays in das neue Array hinein-
kopieren.
• Es ist nicht möglich, in einem Array Daten verschiedener Klassen oder
Datentypen zu speichern. Beispielsweise können Sie kein Array erzeugen,
das sowohl int– als auch String–Werte speichern kann15
15 Das stimmt nicht völlig — ein Array kann schon Objekte verschiedener Klassen speichern.
Das verstehen Sie aber erst, wenn Sie wissen, was Vererbung ist. Für die fortgeschrittenen Leser
ein Hinweis: Ein Array des Typs MeineKlasse[] kann selbstverständlich auch Instanzen von
Extensionen von MeineKlasse speichern. Insbesondere kann ein Array vom Typ Object[] alle
Objekte aufnehmen.
89
• Beachten Sie bitte den Unterschied zwischen Arrays primitiver Datentypen
wie int[], double[] und Arrays von Objekten wie String[].
Arrays primitiver Datentypen der Größe n bieten n Positionen, an denen
Daten gespeichert werden, während Arrays von Objekten der Größe n
nicht die Objekte selbst sondern nur Referenzen auf Objekte speichern.
Wir besprechen das weiter unten noch an einem Beispiel.
Man kann bei einem gegebenen Array herausfinden, wie groß es ist. Dazu greift
man auf sein Längen–Attribut zu, indem man schreibt:
array.length
Wannimmer Sie die Größe eines Arrays verwenden, sollten Sie das Längen–
Attribut des Arrays verwenden. Sie sollten die Größe niemals direkt als Literal
im Programm benutzen.
90
Beispiel: Angenommen, Sie wissen, daß ein Array namens meinArray
10 Elemente enthält, welche Sie ausgeben wollen, so sollten Sie dennoch
nicht schreiben
Sie fragen sich vielleicht, warum man in seinem Programm die Größe eines Ar-
rays nicht explizit verdrahten soll. Die Antwort ist einfach: Programme ändern
sich. Wenn Sie später die Größe des Arrays verändern, so müssten Sie alle Stel-
len, an denen Sie die Größe als Zahl geschrieben haben, manuell nachbessern.
Das führt dann meist zu Fehlern.
Wenn Sie von vorneherein darauf achten, daß Ihre Programmteile möglichst we-
nig Annahmen über die zu bearbeitenden Objekte machen, werden Änderungen
einfacher.
11.3 Array–Literale
Auch Arrays kann man als Literale schreiben. Ein Array–Literal hat die Form
{ wert, wert, ...., wert }.
Zum Beispiel wird hier ein durch Verwendung eines Array–Literales ein int–
Array der Größe 5 erzeugt und mit den Zahlen 1, 4, 9, 16, 25 initialisiert:
91
Klasse ArrayPrimObj
1 class ArrayPrimObj {
2 public static void main( String[] args ) {
3 int[] daten = {100, 200, 300, 400};
4 for( int i = 0; i < daten.length; i ++ ) {
5 System.out.println( daten[i] );
6 }
7
8 String[] sDaten = new String [ 4 ];
9 sDaten[0] = "String 1";
10 sDaten[1] = "String 2";
11 sDaten[2] = sDaten[3] = "String 3";
12 for( int i = 0; i < sDaten.length; i++ ) {
13 System.out.println( sDaten[i] );
14 }
15 }
16 }
Um Ihnen deutlich zu machen, wie die beiden Arrays von Java gehandhabt
werden, finden Sie hier zwei Zeichnungen:
int−Array
length=4
Inhalt=: 0 100
2 300
4 400
Das int–Array ist ein Objekt, welches von der Variable daten referenziert wird.
Als Array der Größe 4 enthält es vier von 0 bis 3 durchnumerierte Variablen, in
welchen int–Werte gespeichert werden. Das Array enthält außerdem ein Attribut
namens length, in welchem gespeichert wird, wie viele Speicherplätze das Array
enthält.
String−Objekt
String−Array
referenziert
Inhalt: String 1
length=4
Inhalt=: 0 String−Objekt
referenziert
String[] sDaten; referenziert 1 Inhalt: String 2
2 referenziert
String−Objekt
4
referenziert
Inhalt: String 3
Auch das String–Array ist ein Objekt. Es wird von der Variable sDaten re-
ferenziert. Auch das String–Array enthält vier String–Variablen. Da String–
Variablen aber Objekte nicht speichern sondern nur referenzieren, enthält das
String–Array selbst lediglich vier Verweise auf andere Objekte.
92
11.5 Referenztypen am Array-Beispiel
Nun möchten wir Ihnen noch am Beispiel der Arrays demonstrieren, was es
für die Praxis bedeutet, mit Referenztypen zu arbeiten. Sie sollten dieses Pro-
gramm auf jeden Fall ausprobieren und danach ein wenig mit dem Programm
herumspielen !
Klasse ArrayReferenz
1 class ArrayReferenz {
2 public static void main( String[] args ) {
3 int[] daten = {100, 200, 300, 400};
4
5 int[] datenNeu = daten;
6 datenNeu[1] = 1;
7 datenNeu[3] = 3;
8 datenNeu = new int[4];
9
10 for(int i = 0; i < daten.length; i++ ) {
11 System.out.println( daten[i] );
12 }
13 }
14 }
Im obigen Programm wird ein Array–Objekt erzeugt, welches durch die Variable
daten referenziert wird. In Zeile 5 wird eine weitere Variable, datenNeu definiert,
welche das selbe Objekt referenziert.
Dann wird in Zeile 6 und 7 die Variable datenNeu verwendet, um das Array–
Objekt zu verändern.
Obwohl wir die Variable daten nicht benutzt haben, um das Array zu verändern,
hat sich das durch die Variable daten referenzierte Array hierdurch verändert,
wie die Ausgabe in Zeile 10–12 beweist.
Sie sehen also, daß Änderungen an Objekten alle Variablen beeinflussen, wel-
che das Objekt referenzieren. Wenn man im Umgang mit Referenztypen nicht
aufpasst und sich nicht immer wieder klarmacht, daß die Zuweisung keine Ko-
pie sondern nur eine Referenz erzeugt, kann man daher große Überraschungen
erleben.
int[][] einZweiDIntArray;
Arrays, die Arrays enthalten, nennt man mehrdimensionale Arrays. Unter “Di-
mension” versteht man dabei die Schachtelungstiefe der Arrays. Ein vierdimen-
sionales Array könnte man so definieren:
int[][][][] einVierdimArray;
Mehrdimensionale Arrays eignen sich zur Darstellung von Daten wie Matrizen,
Tensoren, Spreadsheets, und so weiter.
Ein zweidimensionales Array wird wie folgt erzeugt:
93
int[][] zweiDArray = new int[ zeilen ][ spalten ];
Klasse Array2D
1 class Array2D {
2 public static void main( String[] args ) {
3 int[][] eineMatrix = new int[10][10];
4 for( int i=0; i < eineMatrix.length; i++) {
5 for( int j=0; j < eineMatrix.length; j++) {
6 eineMatrix[i][j] = i-j;
7 }
8 }
9
10 int[][] einDreieck = new int[10][1];
11 for( int i=0; i < einDreieck.length; i++) {
12 einDreieck[i] = new int[i+1];
13 }
14 for( int i=0; i < einDreieck.length; i++) {
15 for( int j=0; j < einDreieck[i].length; j++ ) {
16 einDreieck[i][j]=i-j;
17 System.out.print( einDreieck[i][j] );
18 System.out.print(" ");
19 }
20 System.out.println();
21 }
22 }
23 }
94
Klasse Array2D
1 class Array2D {
2 public static void main( String[] args ) {
3
4 // matrixförmiges double--array
5 double[][] matrix = { {1.1, 1.2}, {1.2, 1.3} };
6
7 for( int i = 0; i < matrix.length ; i++ ) {
8 for( int j= 0; j < matrix[i].length; j++ ) {
9 System.out.print( matrix[i][j] );
10 System.out.print(" ");
11 }
12 System.out.println();
13 }
14
15 // dreieckförmiges int--Array
16 int[][] zweiDInt = { {1}, {2,3}, {4,5,6}, {7,8,9,10} };
17
18 for( int i = 0; i < zweiDInt.length ; i++ ) {
19 for( int j= 0; j < zweiDInt[i].length; j++ ) {
20 System.out.print( zweiDInt[i][j] );
21 System.out.print(" ");
22 }
23 System.out.println();
24 }
25
26 }
27 }
11.7 Zusammenfassung
Sie sollten nun die folgenden Fragen beantworten können:
95
12 Klassen in Java
Nun soll es endlich daran gehen, in Java eigene Klassen zu erstellen. Wir schlie-
ßen hier an der in in Kapitel ?? begonnenen Diskussion über Klassen und Ob-
jekte an. Wenn Ihnen die Inhalte von Kapitel ?? nicht mehr geläufig sind, sollten
Sie es nun noch einmal lesen.
96
Objekten selbst sondern an einer zentralen Stelle speichern, die irgendwie
zu den Objekten gehört. Hierzu verwendet man Klassenvariablen.
• Oft benötigt man in Objekten eine Funktionalität, die zwar logisch zum
Objekt gehört, aber nicht auf den Zustand des Objektes angewiesen ist,
und die man daher nicht den Objekten sondern woanders zuordnen möch-
te. Hierzu verwendet man Klassenmethoden.
Wir werden Ihnen gleich erzählen, wie man diese verschiedenen Arten von Me-
thoden und Variablen definieren kann. Vorher aber ein Beispiel, das Sie jetzt
schon verstehen können müssten:
Beispiel:
Klasse Auto a
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapKlassen/Auto.java
97
ein Auto–Objekt namens ferrari erzeugt. Beide Male wird der Konstruktor
verwendet. Im Kapitel 10 haben wir bereits erläutert, wozu Konstruktoren
da sind. Vielleicht sollten Sie sich dieses Kapitel nochmal anschauen ?
In Zeilen 14–20 wird eine Methode void anzeigen() definiert. Dies ist ei-
ne Instanzmethode, welche ein Auto–Objekt anzeigen kann. Als Instanz-
methode kann sie direkt auf die Attribute des Objektes zugreifen. Beim
Aufruf muß ihr daher ein Objekt angegeben werden.
Die anzeigen()–Methode wird in Zeile 30 und in Zeile 33 aufgerufen. Da-
bei wird sie einmal im Objekt mercedes und einmal im Objekt ferrari
aktiviert.
Beachten Sie bitte auch, daß die anzeigen()–Methode auf die Klassenva-
riable kwpsfaktor zugreifen kann — dies geschieht in Zeile 19.
In der anzeigen()–Methode wird in Zeile 17 eine Methode namens
wandlePSzuKW aufgerufen, um die PS–Zahl des Autos in den Kilowatt–
Wert umzuwandeln. Die wandlePSzuKW –Methode ist in Zeilen 22–25
definiert. Da sie als static ausgezeichnet ist, ist sie keine Instanz– sondern
eine Klassenmethode. Als solche kann sie auch auf alle Klassenvariablen
(in diesem Fall nur kwpsfaktor), nicht aber auf Instanzvariablen zugreifen.
Der Grund, daß die wandlePSzuKW –Methode als Klassenmethode und
nicht als Instanzmethode definiert wurde, sollte klar sein: Die Wandlung
eines Pferdestärken in einen Kilowatt–Wert ist nicht von einem konkreten
Auto–Objekt abhängig. Es wäre daher nicht angemessen, diese Methode
als Instanzmethode zu definieren.
Schließlich noch ein Wort zur main–Methode auf Zeile 27–34: Diese Me-
thode ist durch die Definition als static eine Klassenmethode, ist also
nicht mit einem Objekt assoziiert. Die main–Methode macht die Klasse
ausführbar.
In der main–Methode werden zwei Objekte namens mercedes und ferrari
definiert, und es werden die anzeigen()–Methoden beider Objekte aufge-
rufen.
einObjekt.attribut;
// oder
einObjekt.methode();
Genauso wie man auf die Inhalte eines Objektes zugreift, kann man auch auf
in einer Klasse enthaltene Klassenvariablen und Klassenmethoden zugreifen.
98
Auch hier benutzt man den Punkt. Wenn eine Klasse namens EineKlasse eine
Klassenvariable namens klassenVar oder eine Methode klassenMethode() kann
man hierauf von außen wie folgt zugreifen:
EineKlasse.klassenVar;
// oder
EineKlasse.klassenMethode();
Der Punkt dient also dazu, von außen auf die Inhalte eines Objektes oder einer
Klasse zuzugreifen. Ein Objekt kann auf seine Instanzvariablen, Klassenvaria-
blen, Instanzmethoden und Klassenmethoden direkt zugreifen. Genauso können
Sie in einer Klassenmethode direkt auf die Klassenattribute zugreifen.
Beispiel:
Die folgende Klasse verwendet die eben definierte Klasse Auto. Die
kompilierte Datei UseAuto.class sollte sich im gleichen Verzeichnis wie
Auto.class befinden, damit sie funktioniert:
Klasse UseAuto a
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapKlassen/UseAuto.java
Sie sehen am vorigen Beispiel, daß man durch Eingriffe von außen die Funk-
tionsfähigkeit einer Klasse beeinträchtigen kann. Java bietet Mechanismen an,
die derartige Eingriffe in das Innenleben von Klassen und Objekten abwehren.
In einem späteren Kapitel werden Sie lernen, wie man “wehrhafte” Objekte und
Klassen entwickeln kann, die Eingriffe von außen abweisen.
99
class KlassenName // Klassenkopf
{
// Klassenkörper
}
Eine Klassendefinition besteht also aus zwei Teilen: Dem Klassenkopf, in wel-
chem gewisse später zu erläuternde Eigenschaften der Klasse beschrieben werden
und dem Klassenkörper, in welchem die Klasse selbst definiert wird.
In der einfachsten Version besteht der Klassenkopf aus dem Text
class KlassenName
Das Wort class weist den Java–Compiler darauf hin, daß nun die Definition
einer Klasse folgt. Durch den hinter dem Wort class stehenden Namen erfährt
der Compiler den Namen der zu definierenden Klasse. Der Klassenkopf veran-
lasst den Java–Compiler, den darauf folgenden Block als Definition der im Kopf
benannten Klasse zu interpretieren.
Beachten Sie, daß sich der Klassenkörper von den Ihnen bereits bekannten An-
weisungsblöcken unterscheidet — der Klassenkörper selbst enthält nämlich im
Gegensatz zu den Anweisungsblöcken keine Anweisungen sondern eine Folge von
Definitionen.
Abstrakt ist ein Klassenkörper wie folgt aufgabaut:
class KlassenName {
Definition_1
Definition_2
...
Definition_n
}
100
12.4 Attribute
Ein Attribut wird genauso definiert wie eine Variable: Es wird sein Datentyp
oder seine Klasse gefolgt von seinem Namen angegeben. Die Definition eines
Attributs muß mit einem Semikolon beendet werden.
Wenn die Objekte der Klasse eine Instanzvariable namens instanzVariable des
Datentyps Datentyp haben sollen, so wird diese wie folgt definiert (ein funktio-
nierendes Beispiel folgt weiter unten):
Datentyp instanzVariable;
Genau wie alle Variablendefinitionen kann auch ein Klassenattribut oder ein
Instanzattribut schon bei der Definition initialisiert werden. Dazu schreibt man
hinter den Namen des Attributes direkt die Initialisierungszuweisung:
Wird auf diese Weise eine Instanzvariable initialisiert, so erhält diese Instanz-
variable bei jedem neu erzeugten Objekt den angegebenen Wert erhält. Wird
hingegen eine Klassenvariable auf diese Weise initialisiert, so erhält die Klassen-
variable noch vor der ersten Verwendung der Klasse den angegebenen Wert.
Das folgende Beispiel demonstriert die Definition von Instanzvariablen:
Beispiel:
Hier wird eine Klasse Punkt3D definiert, deren Objekte Punkte im dreidi-
mensionalen Raum repräsentieren. Zunächst definieren wir keine Metho-
den sondern nur die Attribute der Objekte.
Objekte dieser Klasse erhalten drei double–Instanzvariablen namens x,y
und z, welche die Koordinaten des Punktes beschreiben.
Die Klasse ist in dieser Form kompilierbar aber nicht ausführbar, da sie
keine main–Methode besitzt.
Klasse Punkt3Da
1 class Punkt3Da {
2
3 // Koordinaten:
4 double x;
5 double y;
6 double z;
7
8 }
Falls wir möchten, daß jeder neu angelegte Punkt einen vorher festge-
legten Zustand erhält, können wir dies dadurch erreichen, daß wir jede
Instanzvariable schon bei der Definition initialisieren.
101
Wenn wir uns entscheiden, jeden neu angelegten Punkt auf die Koordina-
ten (0, 0, 0) zu initialisieren, können wir das so erreichen:
Klasse Punkt3D
1 class Punkt3D {
2
3 // Koordinaten:
4 double x = 0;
5 double y = 0;
6 double z = 0;
7
8 }
Beispiel: Hier noch ein Beispiel ohne tieferen Sinn, in welchem auch
Klassenvariable verwendet werden:
Klasse InstKlassVar
1 class InstKlassVar {
2
3 static double klassenVar1 = 0;
4 static double klassenVar2 = 1.3;
5
6 double instanzVar1 = 3;
7 double instanzVar2 = 5;
8
9 }
Und noch eine hoffentlich überflüssige Bemerkung: Als Attribute können natür-
lich nicht nur primitive Daten sondern auch Objekte anderer Klassen verwendet
werden.
Beispiel:
Das folgende Beispiel verwendet die oben definierte Klasse Punkt3D, um
eine Linie von einem Punkt zum anderem im dreidimensionalen Raum zu
beschreiben.
Eine Linie ist dabei gegeben durch zwei Eckpunkte, welche wir punkt1
und punkt2 nennen, und welche Objekte der Klasse Punkt3D sind:
Klasse Linie
1 class Linie {
2
3 Punkt3D punkt1, punkt2;
4
5 }
Methodenkopf {
102
// Methodenkörper
}
Der Methodenkörper besteht aus einer Folge von Anweisungen, welche bei jeder
Ausführung der Methode abgearbeitet werden.
void nichtsTun() {
}
Diese Methode hat einen leeren Methodenkörper und tut daher beim Auf-
ruf gar nichts.
Der Methodenkopf legt fest, daß die Methode den Namen “nichtsTun”
hat. Das Wort “void” bedeutet, daß sie keinen Wert zurückliefert.
Falls eine Methode keinen Wert zurückliefern soll, wird als Rückgabe–Typ der
Typ void verwendet (eng. void: leer, ungültig). Die zur Ausführung der Methode
benötigten Werte wert1 bis wertN nennt man auch formale Parameter der
Methode.
Soll eine Methode nicht als Instanz– sondern als Klassenmethode definiert wer-
den, so ist vor die Methodendefinition noch das Wort “static” zu setzen:
103
1 void anzeigen()
2 static void zeilenVorschub( int anzahlZeilen )
3 static double summiere( double[] werte )
4 double[] sortiere( double[] werte )
5 static int searchIndex( String[] stringListe, String s2 )
Der Methodenkörper besteht aus einer Folge von Anweisungen. Wenn im Metho-
denkopf Übergabeparameter beschrieben sind, so kann im Methodenkörper auf
diese genauso wie auf alle anderen Variablen zugegriffen werden. Dabei werden
die formalen Parameter bei jedem Aufruf der Methode durch die Aufrufspara-
meter ersetzt.
Ist eine Methode nicht als void gekennzeichnet, so muß sie ein Ergebnis an
den Aufrufer zurückübermitteln. Dazu wird der return–Befehl verwendet. Der
return–Befehl gibt einen Wert zurück und beendet dabei die Bearbeitung der
Methode.
In einer Methode darf der return–Befehl beliebig oft verwendet werden. Es dient
aber normalerweise nicht der Übersichtlichkeit und dem besseren Verständnis
des Programmes, wenn man viele return–Befehle in einer Methode benutzt.
Abstrakt wird der return–Befehl wie folgt benutzt:
return Wert;
Der zurückgegebene Wert muß dabei den Datentyp haben, welchen die Methode
zurückliefern soll.
104
So wäre die Methode korrekt:
Der return–Befehl darf auch in einer als void deklarierten Methode verwendet
werden, um die Methodenbearbeitung abzubrechen und zum Aufrufer der Me-
thode zurückzukehren. In einer void–Methode muß allerdings nicht unbedingt
ein return stehen, denn am Ende eines jeden Methodenkörpers wird vom Java–
Compiler automatisch ein return eingefügt.
In einer void–Methode darf kein Wert zurückgegeben werden. Die Verwendung
des return–Befehles in einer void–Methode sieht so aus:
return;
Beispiel: Hier ein Beispiel für eine void–Methode, in der mehrere return–
Befehle auftauchen. Wenn man sehr viele Fallunterscheidungen hat, kann
es Sinn machen, mehrere return–Befehle in einer Methode zu haben. Nor-
malerweise sollten Sie das jedoch vermeiden. In dieser einfachen Methode
ist die Verwendung mehrerer return–Befehle kein guter Programmierstil:
105
Nun sollen noch einige Beispiel–Methoden gezeigt und besprochen werden.
Klasse DemoMethode a
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapKlassen/DemoMethode.java
Die Klassenmethode quadriereWerte ist als void deklariert und hat einen
formalen Parameter, der innerhalb der Methode als werte angesprochen
werden kann und vom Typ double[] ist. Da die Methode als void dekla-
riert ist, muß in ihr kein return–Befehl stehen. Auf den formalen Parame-
ter kann in der Methode (z.B. in Zeile 5) zugegriffen werden wie auf jede
andere Variable auch.
In Zeilen 25 und 26 wird die Methode verwendet. Dabei wird anstelle des
formalen Parameters werte einmal die Variable w1 und einmal die Varia-
ble w2 eingesetzt. In der Methode wird dann der Inhalt der Parameter
verändert.
Auch die durchschnitt–Methode hat einen formalen Parameter namens
werte. Da sie keine void–Methode ist, muß sie mit return einen Wert
zurückliefern.
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapKlassen/Punkt3D.java
Beispiel: Und hier noch ein Beispiel, welches das vorige Beispiel verwen-
det. Die Klasse Linie3D muß im selben Verzeichnis gespeichert sein wie
die Klasse Punkt3D.
Klasse Linie3D a
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapKlassen/Linie3D.java
106
Methode bei einem Aufruf gemeint ist, müssen sich die Methoden dann durch
die geforderten Parameter unterscheiden.
Beispiel: In der folgenden Klasse ist die Methode max zweimal definiert
— einmal als Methode, welche zwei int–Werte akzeptiert und einmal als
Methode, welche zwei double–Werte akzeptiert.
Klasse MethodenDemo a
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapKlassen/MethodenDemo.java
• Zuerst schaut es nach, ob in der Klasse eine Methode definiert ist, die zu
den aktuellen Parametern im Methodenaufruf stehen.
Im obigen Beispiel geschieht der Aufruf in Zeile 20 mit zwei int–Werten
und in Zeile 21 mit zwei double–Werten. Dem Compiler ist also sofort
klar, daß der Aufruf in Zeile 20 die erste max–Methode auf Zeile 3–9 und
der Aufruf in Zeile 21 die max–Methode auf Zeile 11–17 meint.
• Wenn auf die Parameter im Methodenaufruf keine Methode passt, so
schaut der Compiler nach, ob er die Argumente im Methodenaufruf in
einen anderen Typ verwandeln kann, so daß der Aufruf dann auf eine De-
finition paßt. Beim Wandeln der Typen werden die Regeln aus Kapitel 8
verwendet.
Im obigen Beispiel werden im Aufruf in Zeile 22 ein int und ein double–
Wert verwendet. Eine solche Methode kennt der Compiler nicht. Er kann
aber den int–Wert in einen double–Wert verwandeln und dann die double–
Methode verwenden.
Kurz gesagt: Sie können beliebig viele Methoden des gleichen Namens schreiben,
wenn es eine eindeutige Möglichkeit gibt, anhand der übergebenen Parameter
herauszufinden, welche Methode gemeint ist.
Sie können insbesondere nicht zwei Methoden schreiben, die sich nur durch den
Rückgabewert unterscheiden. Warum ist das so ? Nun, man muß ja den Rück-
gabewert einer Methode nicht unbedingt verwenden. Zum Beispiel ist folgender
Programmtext legal:
int rechne(int i) {
return i * 2;
}
107
Beispiel: Das folgende Beispiel würde nicht kompilieren, da sich die bei-
den Methoden nur durch den Rückgabewert unterscheiden.
12.7 Konstruktoren
Konstruktoren sind besondere Methoden, welche beim Erzeugen von Objek-
ten benutzt werden. Jedesmal wenn ein Objekt erzeugt wird, wird vom Java–
Laufzeitsystem zunächst Speicher für das Objekt bereitgestellt, d.h. für jede
Instanzvariable des Objektes wird Speicher belegt.
Anschließend wird ein Konstruktor aufgerufen. Der Konstruktor dient dazu, die
Instanzvariablen des Objektes zu initialisieren. Ein Konstruktor ist eine Metho-
de, deren Namen gleich dem Namen der Klasse ist.
Eine Klasse darf mehrere Konstruktoren besitzen. Welcher Konstruktor bei der
Erzeugung eines Objektes aufgerufen wird, richtet sich nach den beim Erzeugen
des Objektes durch den new–Operator übergebenen Parametern
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapKlassen/PunktND.java
108
Beispiel:
Klasse ParamUebergabe a
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapKlassen/ParamUebergabe.java
a ist 3
a ist jetzt 5
In main ist i = 3
a[0] ist 2
a[0] ist jetzt 5
In main ist arr[0] = 5
Wie Sie sehen, wirkt sich die Änderung des Parameters a innerhalb der ersten
change–Methode nicht auf die Variable i im Hauptprogramm aus.
Hingegen wirkt sich die Änderung am Array in der zweiten change–Methode
auf das Hauptprogramm aus.
Den Grund für dieses unterschiedliche Verhalten haben wir bereits in einem vo-
rigen Kapitel erklärt — in Kapitel 11.5 haben wir Ihnen den Unterschiend zwi-
schen Referenztypen und primitiven Typen erläutert. Genau dieser Unterschied
ist es, der bewirkt, daß das Array in der Methode verändert, die int–Variable
jedoch nicht.
Wenn Java einen Methodenaufruf sieht, so erzeugt es für jeden Parameter der
Methode eine Variable und weist diesen die übergebenen Variablen zu.
Wenn Sie sich noch an die Ausführungen über die Referenztypen erinnern, sollte
Ihnen nun sofort einleuchten, warum die Variable des primitiven Datentyps nicht
verändert und die Array-Variable verändert wurde.
12.9 Zusammenfassung
Sie sollten nun die folgenden Fragen beantworten können:
109
• Wie definiert man einen Konstruktor und was tut er ?
• Unter welchen Umständen dürfen innerhalb einer Klasse mehrere gleich-
namige Instanz- oder Klassenmethoden definiert sein ?
• Besprechen Sie die Unterschiede zwischen Primitiven Datentypen und Re-
ferenztypen bei der Wertübergabe an eine Methode.
/**
* Ein Javadoc -- Komentar
*/
110
> javadoc datei1.java datei2.java ...
Um alle Dateien in einem Verzeichnis zu dokumentieren könnten Sie eingeben:
> javadoc *.java
Beispiel: Am besten schauen wir uns die Arbeit von javadoc an einem
Beispiel an. Die folgende Klasse tut keine sinnvollen Dinge, zeigt aber den
Einsatz von javadoc–Kommentaren:
Klasse DemoDoc a
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/KapJavaDoc/DemoDoc.java
Bitte speichern Sie die obige Datei in einem eigenen Verzeichnis, wechseln
in das Verzeichnis und geben dann ein:
nach.
13.2 Zusammenfassung
Nach Lesen dieses Kapitels
• sollten Sie wissen, was javadoc tut,
• wie man javadoc einsetzt,
• wie es prinzipiell arbeitet und
• wo Sie weitere Informationen zu javadoc erhalten können.
111
14 Vererbung — Extensionen von Klassen
Sie haben bisher Klassen als Mittel kennengelernt, um Konzepte im Computer
als Einheit von Daten und Verhalten zu repräsentieren. Die objektorientierte
Methodik wäre heute nicht so verbreitet, wenn das Zusammenfügen von Daten
und Methoden der einzige Zweck von Klassen wäre.
Die objektorientierte Methodik wird erst dadurch zu einem mächtigen Werk-
zeug, daß wir gleichartiges Verhalten verschiedener Klassen in einer abstrakteren
Klasse zusammenfassen können.
Um mehrere Klassen zu einer abstrakteren zusammenzufassen, suchen wir nach
Klassen, die im Rahmen unserer Anwendung ähnliche Aufgaben haben oder
ähnliche Dinge beschreiben. Dann abstrahieren wir von diesen Klassen und er-
arbeiten ein gedankliches Konzept, welches die entscheidenden Merkmale und
das gemeinsame Verhalten dieser Klassen enthält.
Dieses Konzept implementieren wir dann als Klasse. Dabei wird alles Verhal-
ten, das den ursprünglichen Klassen gemeinsam ist, in der abstrakteren, über-
geordneten Klasse implementiert. Das gemeinsame Verhalten muß dann nicht
mehrfach in jeder der spezielleren Klassen implementiert werden sondern kann
von der abstrakteren Klasse übernommen (man sagt auch “vererbt”) werden.
Indem man Verhalten vererbt, spart man sich nicht nur viel Programmierarbeit;
die Programme werden auch übersichtlicher und leichter wartbar.
All diese Klassen beschreiben ein Fahrzeug, und vermutlich wäre die fahren()–
Methode in allen Klassen ähnlich. Wir können daher diese vier Klassen in
einer abstrakteren Klasse zusammenfassen, welche wir Fahrzeug nennen. Die
Fahrzeug–Klasse würde dabei alle Attribute und Methoden enthalten, welche
mit dem eigentlichen Vorgang des fahrens zusammenhängen. Sie enthielte also
das Attribut geschwindigkeit und die Methode fahren().
Auch die tanken()–Methode in den Klassen Auto und LKW wären annähernd
identisch (sie würde den Füllstand des Tanks hochsetzen). Wir können daher
die Attribute und Methoden, die mit dem Tanken zusammenhängen in einer
gemeinsamen Klasse namens Motorisiertes Fahrzeug zusammenfassen.
Eine Klasse Unmotorisiertes Fahrzeug würden wir übrigens nicht einführen, da
wir in den Klassen Fahrrad und Kutsche kein eigenständiges Verhalten beschrie-
ben haben, das man allen unmotorisierten Fahrzeugen zuschreiben könnte.
112
Das Verhältnis zwischen all diesen Klassen sieht nun wie folgt aus: Jedes Auto
und jeder LKW ist ein Motorisiertes Fahrzeug. Jedes Motorisierte Fahrzeug
ist auch ein Fahrzeug. Wir meinen hiermit, daß jede Instanz der Klasse Auto
und jedes LKW –Objekt gleichzeitig auch eine Instanz der Klasse Motorisiertes
Fahrzeug ist. Jedes Objekt der Klasse Motorisiertes Fahrzeug ist gleichzeitig ein
Objekt der Klasse Fahrzeug.
Auch jedes Objekt der Klasse Fahrrad oder Kutsche ist gleichzeitig ein Objekt
der Klasse Fahrzeug.
Wir können diese Beziehungen in einem Diagramm darstellen. Wenn jede In-
stanz der Klasse B auch eine Instanz der Klasse A ist, machen wir einen Pfeil
von Klasse B zu Klasse A. Das resultierende Klassendiagramm sieht wie folgt
aus:
Fahrzeug
double geschwindigkeit
void fahren()
Motorisiertes Fahrzeug
Fahrrad Kutsche
double tankFuellstand
void tanken()
Auto LKW
Elternklasse/Vorfahre Eine Klasse A ist eine Elternklasse oder ein Vorfahre von
Klasse B, wenn jedes Objekt von Klasse B gleichzeitig
Objekt von Klasse A ist.
Im vorigen Beispiel ist die Klasse Motorisiertes Fahrzeug
Elternklasse von Auto und LKW . Die Klasse Fahrzeug
ist eine Elternklasse der Klassen Motorisiertes Fahrzeug,
Fahrrad Kutsche, Auto und LKW .
Dabei ist die Fahrzeug–Klasse nur indirekter Vorfahre
der Klassen Auto und LKW aber direkter Vorfahre der
Klassen Fahrrad, Kutsche und Motorisiertes Fahrzeug.
Super–Klasse Ein anderer Begriff für Elternklasse oder Vorfahre.
Erweiterung Eine Klasse B ist eine Erweiterung von Klasse A, wenn
Klasse A eine Elternklasse für Klasse B ist.
Im obigen Beispiel ist die Fahrrad–Klasse eine Erweite-
rung der Fahrzeug–Klasse; die Auto–Klasse ist eine Er-
weiterung der Klasse Motorisiertes Fahrzeug und eine
Erweiterung der Klasse Fahrzeug.
Kindklasse Ein anderer Begriff für Erweiterung.
113
Extension Ein anderer Begriff für Erweiterung.
erben Eine Klasse B erbt von Klasse A, wenn sie eine Erweite-
rung von Klasse A ist. Wenn eine Kindklasse von einer
Elternklasse erbt, so übernimmt sie alles in der Eltern-
klasse beschriebene Verhalten.
Der Effekt ist ungefähr der gleiche als hätten Sie alle Definitionen aus dem
Klassenkörper der Elternklasse in Ihre neue Klasse Kindklasse kopiert.
Die Kindklasse enthält dann alle in der Elternklasse vorgenommenen Definitio-
nen plus die im Klassenkörper der Kindklasse getätigten Definitionen.
Beispiel:
Beispiel: Erweitern einer Klasse
1 class Point {
2 double x, y;
3 void move(double dx, double dy) {
4 x+=dx;
5 y+=dy;
6 }
7 }
8
9 class LabeledPoint extends Point {
10 String label;
11 void setLabel(String newLabel) {
12 label = newLabel;
13 }
14 }
Die Klasse Point stellt einen Punkt mit Koordinaten (x, y) dar, welcher
durch die move–Methode verschoben werden kann.
Die Klasse LabeledPoint stellt einen Punkt dar, welcher zusätzlich eine
Beschriftung trägt. Indem in Zeile 9 die Klasse LabeledPoint als Erweite-
rung der Klasse Point definiert wird, übernimmt LabeledPoint alle Defini-
tionen der Klasse Point. Ein ähnliches Ergebnis hätten wir also erhalten,
wenn wir die Klasse LabeledPoint wie folgt definiert hätten:
114
Beispiel: Erweiterte Klasse
1 class LabeledPoint2 {
2 double x, y;
3 String label;
4
5 void move(double dx, double dy) {
6 x+=dx;
7 y+=dy;
8 }
9
10 void setLabel(String newLabel) {
11 label = newLabel;
12 }
13 }
Die Übernahme von Definitionen aus der Elternklasse geschieht nach folgenden
Regeln:
• Wenn eine Kindklasse eine Elternklasse erweitert, so werden bis auf Kon-
struktordefinitionen alle Definitionen der Elternklasse übernommen (Sie
können davon jedoch einige Definitionen ausnehmen — wie das geht, er-
fahren Sie im Kapitel über “Zugriffsschutz: private, protected, public”).
Die Kindklasse erhält somit alle Instanzvariablen, Klassenvariablen, In-
stanzmethoden und Klassenmethoden der Elternklasse. All diese Dinge
haben in der Kindklasse die gleiche Definition wie in der Elternklasse.
• Definitionen der Elternklasse können in der Kindklasse verdeckt werden,
indem eine Definition gleicher Signatur vorgenommen wird (das wird im
nächsten Beispiel klar). Man sagt hierzu auch, daß eine Definition “über-
schrieben” wird.
Zwei Variablen haben dabei die gleiche Signatur, wenn sie den gleichen
Namen haben. Zwei Methoden besitzen die gleiche Signatur, wenn sie den
gleichen Namen haben und die gleichen formalen Parameter entgegenneh-
men.
• Beim Vererben werden alle Definitionen, die die Elternklasse selbst ge-
erbt hat, weiter vererbt. Hat die Elternklasse eine Methode oder Variable
mit einer neuen Definition überschrieben, so wird nur die überschriebene
Version der Elternklasse weitervererbt.
115
Beispiel:
Beispiel: Methoden überschreiben
1 class Point {
2 double x, y;
3
4 String getDescription() {
5 return "Punkt";
6 }
7 }
8
9 class LabeledPoint extends Point {
10 String label;
11
12 String getDescription() {
13 return "Punkt mit Label";
14 }
15 }
Die objektorientierte Methodik ist zum Teil deshalb so mächtig, weil ererbte
Routinen in der Lage sind, überschriebene Routinen aufzurufen. Um dies zu
verstehen, stellen Sie sich den Methodenaufruf am besten als das Verschicken
von Botschaften vor:
Immer wenn ein Objekt eine Botschaft erhält, schaut es nach, ob in seiner Klasse
eine Methode für diese Botschaft definiert ist. Wenn ja, benutzt es diese Metho-
de. Wenn nein, schaut es in seiner Oberklasse nach und benutzt gegebenenfalls
die dort definierte Methode. Wenn auch in seiner Oberklasse keine passende
Methode definiert ist, so schaut es in der Oberklasse seiner Oberklasse nach
und so weiter.
Entscheidend ist hier, daß das Objekt dies für jede Botschaft tut, die es erhält,
116
selbst wenn die Botschaft in seiner Oberklasse abgesetzt wurde. Schauen Sie
sich das folgende Programmfragment an:
Beispiel: Überschreiben und Botschaften
1 class Point {
2 double x, y;
3
4 String getDescription() {
5 return "Punkt (" + x + "," + y + ")";
6 }
7
8 void output() {
9 System.out.println( getDescription() );
10 }
11 }
12
13 class LabeledPoint extends Point {
14 String label;
15
16 String getDescription() {
17 return "Punkt mit Label:" + label;
18 }
19 }
Was passiert in diesem Beispiel, wenn man einem Objekt der Klasse LabeledPoint
die Botschaft “output()” schickt ? Sie müssten die Frage bereits beantworten
können.
Hier die Antwort: Zuerst schaut das LabeledPoint–Objekt in seiner Klasse nach,
ob es dort eine Methode namens output() findet. Da das nicht der Fall ist,
schaut es dann in seiner Oberklasse Point nach und findet dort die output()–
Methode von Zeile 8–10. Nun wird der Programmtext dieser output()–Methode
ausgeführt.
Bei der Ausführung des Programmtextes wird in Zeile 9 an das Objekt die
Botschaft getDescription() geschickt. Obwohl dies im Programmtext der Klasse
Point geschieht, ist unser Objekt immer noch ein Objekt der Klasse LabeledPoint.
Es schaut daher zuerst in der LabeledPoint–Klasse nach und findet dort die
getDescription()–Methode aus Zeilen 16–17. Nun führt es den in der LabeledPoint
enthaltenen Programmtext der getDescription()–Methode aus.
Es ist also ohne weiteres möglich, daß in der Oberklasse definierte Methoden
Programmtext aufrufen, der in den Unterklassen überschrieben wurde. Es ist
daher möglich, in einer Oberklasse sehr abstrakt formulierten Programmtext zu
schreiben und die Details erst in den Unterklassen einzusetzen.
117
Beispiel: Verwendung von super
1 class Point {
2 double x, y;
3
4 String getDescription() {
5 return "Punkt (" + x + "," + y + ")";
6 }
7
8 void output() {
9 System.out.println( getDescription() );
10 }
11 }
12
13 class LabeledPoint extends Point {
14 String label;
15
16 String getDescription() {
17 return super.getDescription() + " mit Label: " + label;
18 }
19 }
118
sich später irgendwann der Konstruktor für Point ändert, müssten wir auch den
Programmtext für den LabeledPoint–Konstruktor ändern.
Statt dessen sollten wir den Konstruktor der Oberklasse direkt verwenden. Dies
geschieht auch mit Hilfe des super–Schlüsselwortes, allerdings wird es jetzt nicht
wie vorhin als Variable sondern als Methode verwendet:
Beispiel: Konstruktoren der Oberklasse aufrufen
1 class Point { double x, y;
2
3 Point( double newX, double newY ) {
4 x = newX;
5 y = newY;
6 }
7
8 }
9
10 class LabeledPoint extends Point {
11 String label;
12
13 LabeledPoint( double newX, double newY, String newLabel ) {
14 super(x,y);
15 label = newLabel;
16 }
17 }
14.6 Beispiele
Beispiel: Point und LabeledPoint
Das folgende Beispiel besteht aus drei Klassen, welche sich alle in einer
gemeinsamen Datei befinden. Sie können das Beispiel ausführen, indem
Sie auf der Kommandozeile javac Points.java und dann java Demo ein-
geben.
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/ex1/Points.java
119
und überschreibt so die getDescription()–Methode der Oberklasse Point.
Immer wenn nun in einem Objekt der Klasse LabeledPoint die
getDescription()–Methode aufgerufen wird, wird diese überschriebene Me-
thode benutzt.
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/Geom/GeomObjekt.java
Beispiel: Punkta
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/Geom/Punkt.java
Beispiel: Liniea
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/Geom/Linie.java
120
Beispiel: Demoa
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/Geom/Demo.java
class MeineKlasse {
// Klassenkörper
}
Es ist also unmöglich, eine Klasse zu schreiben, die nicht zumindest indirekt von
Object erbt. Die meisten von Object geerbten Methoden sind nur mit größerem
Wissen über Java zu verstehen.
Schauen Sie sich bitte im Java–API die Klasse Object an. Sie erhalten die Do-
kumentation, indem Sie unter
http://www.tu-bs.de:82/wir/EIP/jdk1.1.8/docs/api/packages.html
auf das Paket java.lang und dann auf die Klasse Object klicken.
121
Java bietet spezielle Unterstützung für abstrakte Klassen. Eine abstrakte Klas-
se ist eine Klasse, in der zwar alle nötigen Methodenköpfe definiert sind, in
der einige Methoden jedoch noch keinen Methodenkörper besitzen. Von einer
abstrakten Klasse weiß man somit zwar, wie man jede Methode aufruft und
welchen Zweck jede Methode hat, aber da die Klasse nur ein abstraktes Gedan-
kenmodell ist, weiß man nicht, welches Verhalten die Methoden zeigen sollen.
Eine abstrakte Methode wird definiert, indem der Methodenkörper ausgelassen
und vor den Methodenname das Wort “abstract” gesetzt wird. Beispielsweise
könnte eine abstrakte Methode namens draw wie folgt definiert werden:
abstract void draw();
Sobald eine Klasse mindestens eine abstrakte Methode enthält, muß auch die
Klasse als abstract definiert werden. Dies geschieht, indem in die Klassendefini-
tion das Wort “abstract” gesetzt wird:
abstract class GeometrischesObjekt {
double x, y;
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/Geom2/GeomObjekt.java
Beispiel: Punkta
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/Geom2/Punkt.java
122
Beispiel: Liniea
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/Geom2/Linie.java
Beispiel: Demoa
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/Geom2/Demo.java
In diesem Fall ist jedes Rechteck–Objekt auch eine Flaeche– und eine GeometrischesObjekt–
Instanz. Da GeometrischesObjekt automatisch von der Object–Klasse erbt, ist
jedes Rechteck–Objekt auch eine Instanz der Klasse Object.
Die folgenden Definitionen sind daher gültig:
Sie dürfen jedes Objekt an Variablen seiner Oberklassen zuweisen. Dies ist ei-
gentlich logisch, denn jedes Objekt IST auch ein Objekt seiner Oberklassen:
Jedes Rechteck ist ein Object, jedes Rechteck ist ein GeometrischesObjekt, und
jedes Rechteck ist eine Flaeche; daher sind die obigen Definitionen gültig.
Die folgenden Definitionen hingegen sind allesamt ungültig:
Rechteck einRechteck1 = new Flaeche(); // Fehler
Rechteck einRechteck2 = new Object(); // Fehler
Rechteck einRechteck3 = new Dreieck(); // Fehler
Daß diese Definitionen ungültig sind, sollte einleuchten — denn in unserem
Vererbungsbaum ist zwar jedes Rechteck eine Flaeche, aber nicht jede Flaeche
ist ein Rechteck. Auch ist zwar jedes Rechteck ein Object aber nicht jedes Object
ein Rechteck.
Es ist nun ohne weiteres möglich, Arrays zu erzeugen, welche Objekte verschie-
dener Klassen aufnehmen (man spricht hier auch von “polymorphen Arrays”).
Das folgende Programmfragment ist korrekt:
123
Dreieck einDreieck = new Dreieck();
Rechteck einRechteck = new Rechteck();
Flaeche eineFlaeche = new Rechteck();
Was kann man nun mit den Objekten anstellen, die man in Variablen einer
Oberklasse gespeichert hat ? Man kann ihnen natürlich Botschaften schicken,
aber der Java–Compiler achtet sehr darauf, daß man jedem Objekt nur die
Botschaften schickt, die es sicher verstehen kann.
Angenommen, in der GeometrischesObjekt–Klasse sei eine Methode Methode
draw() definiert, welche ein geometrisches Objekt auf dem Bildschirm zeichnet,
und welche in den erbenden Klassen durch das spezielle benötigte Verhalten
überschrieben wird.
In diesem Fall könnte der obige Programmtext wie folgt fortgesetzt werden:
Hier würde das Array durchlaufen und jedes gespeicherte Objekt würde die
zugehörige draw()–Methode ausführen. Auf diese Art und Weise könnte man
eine ganze Sammlung von geometrischen Objekten zeichnen ohne sich darum zu
kümmern, ob das gerade gezeichnete Objekt ein Dreieck, Rechteck oder sonstwas
ist — das jeweilige Objekt weiß ja immer selbst, wie es zu zeichnen ist.
Es ist hingegen nicht möglich, einem Objekt Botschaften zu schicken, die nur
eine Unterklasse versteht. Angenommen, ein Dreieck–Objekt besitze eine Me-
thode Linie getHypothenuse(), welche in einem Linie–Objekt die Hypothenuse
zurückliefere. Die Klasse GeometrischesObjekt besitze keine solche Methode.
In diesem Fall wäre der folgende Programmtext ungültig:
124
Durch den Type–Cast “(Dreieck)” sagen wir dem Compiler: Compiler, wir ma-
chen hier schon das korrekte, gehe davon aus, daß in einGeomObj ein Objekt
der Klasse Dreieck steht.
Der Compiler würde in solch einem Fall keinen Fehler melden. Wenn wir hierbei
aber einen Fehler machen, entsteht während der Laufzeit des Programms ein
Fehler:
Das obige Fragment ist so kompilierbar, da wir durch den Type–Cast die Über-
prüfung durch den Compiler ausgeschaltet haben. Wenn das Programm abläuft,
merkt das Laufzeitsystem aber, daß hier versucht wird, ein Quadrat als Drei-
eck zu betrachten. Das Programm würde dann einen Fehler ausgeben und sich
beenden.
Beispiel: TypeCast–Demoa
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Vererbung/Cast/CastDemo.java
14.10 Zusammenfassung
Nach Lesen dieses Kapitels sollten Sie
125
• erklären können, wie man Konstruktoren einer Kindklasse schreiben sollte.
• die Beispiele in Kapitel 14.6 verstanden haben und Fragen dazu beant-
worten können.
• beschreiben können, an Variablen welchen Typs man Objekte zuweisen
kann.
• erklären können, warum man nicht direkt alle Methoden von Objekten
aufrufen kann, die in einer Variable der Oberklasse gespeichert sind.
• beschreiben können, wie und warum man im Rahmen von Vererbung den
Type–Cast–Operator einsetzen kann.
• sich überlegen, ob und warum der Einsatz von Type–Cast–Operatoren
gefährlich sein kann.
15 Packages
Zu Java gehören sehr viele verschiedene Klassen. Um nicht die Übersicht zu
verlieren, werden Klassen mit ähnlichem Zweck gemeinsam zu größeren Paketen,
sogenannten “packages” zusammengeschnürt.
Um einen weiteren Zweck von Paketen klarzumachen, stellen Sie sich vor, Sie
hätten eine Klasse namens Y2K erstellt, welche alle Millenium–Bugs vertreibt
und wollten diese Klasse verkaufen. Stellen Sie sich nun vor, ein potentieller
Kunde habe bereits eine eigene Klasse namens Y2K erstellt, benutze diese in
seinem Programm und wolle nicht auf sie verzichten.
Was tun ? Sie könnten natürlich ihre Klasse umbenennen. Aber selbst wenn sie
sie umbenennen, könnte es einen Namenskonflikt mit einem anderen Kunden
geben.
Eine Lösung, derartige Namenskonflikte gar nicht erst zum Problem werden zu
lassen, sind packages. Ein package oder Paket enthält beliebig viele Klassen.
Die in einem package enthaltenen Klassen kann man ansprechen, indem man
hinter den Namen des packages einen Punkt und dann den Namen der Klasse
setzt.
Wenn zum Beispiel ein Paket den Namen de.tubs.cs.sc.akeese hat und in dem
Paket eine Klasse namens Y2K enthalten ist, so könnte man die Klasse als
de.tubs.cs.sc.akeese.Y2K ansprechen.
Es ist üblich, Paket–Namen aus der Internet–Adressierung einer Firma zu ge-
nerieren. Wenn die Firma SUN (www.sun.com) eine Erweiterung zu Java ent-
wickelt, welche nicht im Java–Kern enthalten ist, so würde diese Erweiterung in
einem Paket namens com.sun.paketname enthalten sein.
Da Internet–Adressen eindeutig sind, wird so sichergestellt, daß Pakete unter-
schiedlicher Firmen unterschiedliche Namen enthalten. Somit haben auch in der
Regel Klassen verschiedener Firmen verschiedene Namen und sind deshalb ge-
meinsam einsetzbar.
Alle zu Java gehörigen Klassen sind in Paketen enthalten, die mit dem Text
“java” beginnen. So gibt es ein Paket java.awt, in welchem alle Bestandteile
des “Abstract Windowing Toolkits” enthalten sind, es gibt ein Paket java.math,
126
in welchem spezielle mathematische Klassen enthalten sind, und viele mehr.
Das wichtigste Paket ist das Paket java.lang, in welchem die wichtigsten Java–
Klassen wie z.B. String oder Object enthalten sind.
Wir wollen hier nicht weiter darauf eingehen, wie man packages erstellt, möchten
Ihnen jedoch erläutern, wie man packages benutzt.
Um eine in einem Paket enthaltene Klasse anzusprechen, müssen Sie vor den
Namen der Klasse jedesmal den Namen des Paketes setzen. Wenn der Paketname
lang ist, wird das eine ganze Menge Schreibarbeit. Stellen Sie sich vor, statt
String müssten Sie jedesmal java.lang.String schreiben.
Man kann daher die in einem Paket enthaltenen Namen in ein Programm im-
portieren. Dazu setzt man an den Anfang der Datei den Text
import paketname.*;
import paketname.klasse1;
import paketname.klasse2;
Beispielsweise enthält das Paket java.util die Klasse Dictionary. Wenn Sie diese
Klasse benutzen wollen, haben Sie zwei Möglichkeiten:
Sie können die Klasse direkt verwenden:
class MeineKlasse {
java.util.Dictionary einDict;
Alternativ können Sie die Klasse auch importieren und können dann eine kürzere
Schreibweise wählen:
class MeineKlasse {
Dictionary einDict;
127
Es ist übrigens nicht nötig, Inhalte des java.lang–Paketes zu importieren. Da die
dort enthaltenen Klassen so häufig benutzt werden, wird es automatisch in jedes
Programm exportiert. Der Java–Compiler fügt automatisch in jedes kompilierte
Programm den Befehl
import java.lang.*;
ein.
Eine Übersicht über die zu Java gehörigen Pakete finden Sie in der Dokumen-
tation zum Java API unter
http://www.tu-bs.de:82/wir/EIP/jdk1.1.8/docs/api/packages.html
Bitte klicken Sie sich dorthin, indem Sie von unseren WWW–Seiten unter den
Litaturlinks zu Java auf den relativ weit unten stehenden Link “Java–Api (lo-
kal)” klicken.
Bitte schauen Sie sich auf dieser WWW–Seite um, klicken Sie dort auf das
Package java.lang und schauen Sie sich die Dokumentation der Object–Klasse
in java.lang an.
15.1 Zusammenfassung
Nach Lesen dieses Kapitels sollten Sie
16 Exceptions
Zur Behandlung unerwarteter Situationen bietet Java Unterstützung in Form
von Exceptions oder Ausnahmen.
Den Sinn von Exceptions können wir Ihnen an einem kleinen Beispiel klarma-
chen. Nehmen Sie an, wir schreiben eine Methode, in welcher durch eine Zahl
geteilt werden muß — es könnte etwa aus der zurückgelegten Distanz und ei-
nem Zeitintervall berechnet werden, mit welcher Geschwindigkeit sich ein Auto
bewegt. Eine erste Fassung einer entsprechenden Methode könnte wie folgt aus-
sehen:
Beispiel: Methode mit einem Fehler
1 static int geschwindigkeit( int distanz, int dauer )
2 {
3 return distanz / dauer;
4 }
Diese Methode wird problematisch, wenn von außen eine Dauer von 0 Sekunden
angegeben wird, etwa im Aufruf
128
int speed = geschwindigkeit( 10, 0 );
Was soll in solch einem Fall geschehen — in der Methode würde nun offensicht-
lich versucht, durch 0 zu teilen. Dies wiederum würde einen Fehler erzeugen,
welcher das Programm beenden würde.
Nun ist es nicht besonders wünschenswert, daß ein Programm sich einfach been-
det, wenn irgendwo ein ungültiger Zahlenwert erscheint. Das Programm sollte
zumindest die noch nicht gespeicherten Daten speichern und Aufräumarbeiten
durchführen (etwa die vom Programm erzeugen 5 Gigabyte temporärer Daten
von der Festplatte löschen).
Noch schöner wäre es natürlich, wenn das Programm einen Fehler melden würde.
Man könnte nun vor jedem Aufruf der Funktion überprüfen, ob die Eingabeda-
ten in Ordnung sind. Es ist aber durchaus denkbar, daß vor dem Aufruf einer
Methode nicht von außen prüfbar ist, ob sie durchführbar ist — stellen Sie sich
eine Methode vor, welche auf eine Diskette zugreifen will. Hier hinge es davon
ab, ob eine Diskette im Laufwerk liegt, ob die Methode funktioniert oder nicht.
Beim Programmieren kommt es immer wieder zu Situationen, in denen uner-
wartete Daten oder unerwartete Ereignisse (Ausnahmen) vorliegen. Wenn eine
Ausnahme auftritt, muß sie entweder dort, wo sie aufgetreten ist, behandelt wer-
den, oder sie muß weitergegeben werden, so daß ein anderer Programmteil eine
Möglichkeit hat, auf die Ausnahme zu reagieren. Wird die Ausnahme nirgends
behandelt, bleibt als einzige Alternative der Abbruch des Programms mit einem
Fehler.
16.1 Try–catch
Der try–catch–Block bietet eine Möglichkeit, Programmtext auszuführen, in wel-
chem Ausnahmen auftreten könnten. Dabei ist eine Ausnahme ein Objekt einer
gewissen Klasse, welches erzeugt wird, wenn eine unerwartete Situation aufge-
treten ist.
Sie können sich das wie folgt vorstellen: An einer Stelle der Programmausfüh-
rung tritt eine unerwartete Situation auf (z.B. Teilen durch Null, Diskette fehlt
im Laufwerk). Als Reaktion auf diese Situation wird ein Ausnahme–Objekt er-
zeugt, welches die aufgetretene Situation genauer beschreibt. Dieses Ausnahme–
Objekt wird nun weitergereicht (“geworfen”), bis irgendwo Programmtext ge-
funden wird, welcher das Ausnahmeobjekt auffängt und dann die Ausnahme
behandelt. Wird kein Programmtext gefunden, der das Ausnahmeobjekt fängt,
wird das Programm mit einer Fehlermeldung beendet.
Eine Ausnahme kann nur gefangen werden, wenn sie innerhalb eines try–catch–
Blocks auftritt. Die try–catch–Anweisung ermöglicht erst das Einfangen der
Ausnahmeobjekte und schützt so einen gewissen Programmtextbereich vor ei-
nem ungewollten Programmabbruch. Man verwendet einen try–catch–Block, in-
dem man den Programmtext, in welchem Ausnahmen–Objekte geworfen werden
könnten, in einen try–Block setzt und dann für jeden Ausnahmetyp, der einge-
fangen werden soll, hinter den geschützten Block eine catch–Anweisung setzt:
try {
geschuetzte Anweisungen;
...
129
} catch ( Ausnahmetyp1 x ) {
Ausnahme-Anweisungen für Ausnahmetyp 1;
...
} catch ( Ausnahmetyp2 x ) {
Ausnahme-Anweisungen für Ausnahmetyp 2;
...
}
Der Block zwischen dem try und dem ersten catch ist hierdurch vor jeder Aus-
nahme geschützt, welche durch eine der catch–Anweisungen aufgefangen werden
kann.
Sofern im geschützten Block eine Ausnahme auftritt (wie man selbst Ausnahmen
erzeugt, erzählen wir gleich noch), wird die Ausführung des geschützten Blocks
sofort abgebrochen, und die Ausführung wird in demjenigen Block ausgeführt,
welcher auf das passende catch folgt.
Was da genau passiert, wird nach dem folgenden Beispiel sicher klarer:
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Except/ExcDemo1.java
Der Programmtext von Zeile 6 bis Zeile 8 ist durch den umschliessen-
den try–Block und das folgende catch–Statement vor einem Programmab-
bruch durch alle Ausnahmen des Typs ArithmeticException geschützt.
Beim Ablauf des Programmtextes entsteht in Zeile 7 beim Teilen durch
0 ein ArithmeticException–Objekt, welches dann als Ausnahme geworfen
wird. Dadurch bricht die Ausführung von Zeile 7 sofort ab. Da in Zeile 9 ei-
ne catch–Anweisung für Ausnahmeobjekte des Typs ArithmeticException
steht, wird das Programm dann in Zeile 10 fortgesetzt.
Das gefangene ArithmeticException–Ausnahmeobjekt kann in Zeile 10 un-
ter der Variable e angesprochen werden.
Beispiel:
Spricht man in einem Array nicht existierende Indizes an,
so wird eine ArrayOutOfBoundsException geworfen. Die
Klasse ArrayOutOfBoundsException erbt von der Klasse
IndexOutOfBoundsException.
Im folgenden Programm wird sowohl das Teilen durch 0 als auch das
Verwenden eines zu großen Index abgefangen:
130
Beispiel: Exceptions 2a
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Except/ExcDemo2.java
oder kürzer:
Beispiel: So können Sie selbst eine Exception werfen und wieder fangen:
Beispiel: Throw 1a
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Except/ThrowDemo1.java
131
so wird das Programm im zugehörigen catch–Block fortgesetzt. Wurde der Me-
thode A jedoch ungeschützt aufgerufen, so wird Methode B abgebrochen, und
die Ausnahme wird dort erneut geworfen, wo Methode B aufgerufen wurde.
Dies setzt sich fort, bis ein try–catch–Block gefunden wird, welcher die gewor-
fene Ausnahme fängt. Wird kein try–catch–Block gefunden, der die Ausnahme
behandelt, so wird das Programm mit einer Fehlermeldung abgebrochen.
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Except/MethodsDemo1.java
Wenn eine Methode eine Ausnahme zurückgibt, welche nicht zu den Java–
Laufzeitausnahmen gehört, muß dies im Kopf der Methode vermerkt werden.
Die Java–Laufzeitausnahmen (dies sind alle Ausnahmen, welche von der Klasse
java.lang.RuntimeException vererbt sind) wird dies deshalb nicht gefordert, weil
diese Ausnahmen fast jederzeit auftreten können und daher fast jede Methode
eine RuntimeException werfen kann. Wäre aber im Kopf einer jeden Methoden
vermerkt, daß sie eine Java–Laufzeitausnahme werfen kann, so wäre das kein
besonderer Informationsgewinn.
In einer Methode wird im Methodenkopf beschrieben, welche Ausnahmen ihre
Ausführung erzeugen kann, indem hinter der Kopfdeklaration der Text
folgt.
Beispiel:
Im folgenden Beispiel wirft die geschwindigkeit–Methode eine eigene Aus-
nahme. Da diese Ausnahme keine Java–Laufzeitausnahme ist, muß sie im
Methodenkopf durch die throws–Klausel deklariert werden. Wir deklarie-
ren hier auch überflüssigerweise, daß die Routine eine ArithmeticExcep-
tion (dies ist eine Java–Laufzeitausnahme) werfen kann.
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Except/MethodsDemo2.java
Wenn in einer Methode A eine Methode B aufgerufen wird, welche eine Ausnah-
me von Typ X (abgesehen von RuntimeExceptions) erzeugen kann, so muß die
von B erzeugte Ausnahme entweder innerhalb der Methode A gefangen werden,
oder im Methodenkopf von A muß vermerkt sein, daß Methode A eine Ausnah-
me vom Typ X werfen kann. Andernfalls wird ein Kompilierfehler erzeugt.
132
So wird sichergestellt, daß jede Methode, welche Ausnahmen werfen kann, dies
im Methodenkopf vermerkt.
Beispiel:
Das folgende Beispiel kompiliert nicht korrekt. Der Compiler beschwert
sich, daß die Methode test eine DistanzException werfen kann, ohne daß
das im Kopf der Methode vermerkt ist.
a Siehe http://www.wire.tu-bs.de/lehre/eipclasses/Except/MethodsDemo3.java
Wir können das Beispiel reparieren, indem wir den Kopf der test–Methode
wie folgt ändern:
Wir könnten das Beispiel auch reparieren, indem wir in der test–Methode
einen try–catch–Block einfügen. Die test–Methode sähe dann so aus:
16.5 Zusammenfassung
Nach Lesen dieses Kapitels sollten Sie
133
• beschreiben können, wie Ausnahmen behandelt werden, die innerhalb einer
Methodenausführung auftreten.
• beschreiben können, was man beim Schreiben von Methoden berücksich-
tigen muß, in denen Ausnahmen entstehen können.
• erläutern können, was man beachten muß, wenn man JDK–Funktionen
verwendet, die Ausnahmen werfen können.
A.2 Editoren
A.2.1 Der nedit-Editor
134
A.2.2 Der Emacs-Editor
Der Editor Emacs ist vermutlich der mächtigste Editor überhaupt. Seine Bedie-
nung ist für MS Windows-Kenner nicht sofort einsichtig, und das Kennenlernen
des Emacs deutlich länger, als den Umgang mit nedit zu lernen.
Dafür werden Sie niemals wieder einen anderen Editor benötigen, wenn Sie ihn
erstmal schätzen gelernt haben. Sie sollten sich mit dem Emacs allerdings nur
dann auseinandersetzen, wenn Sie etwas Zeit haben und wissen, daß Sie auch in
Zukunft Programme schreiben werden.
Starten Sie ihn von der Unix-Shell per
Anschließend sollten Sie das Emacs-Tutorial durcharbeiten. Sie starten es, indem
Sie nach dem Start des Emacs die Tastenkombination Strg+h (auf englischen
Tastaturen ist die “Strg”-Taste mit “Ctrl” bezeichnet) und danach die Taste
“t”drücken .
Weitere Hilfe zum Emacs finden Sie hier:
http://www.cs.tu-bs.de/softech/info/
Wenn Sie im CIP-Pool arbeiten, erscheint nun zuerst ein Hilfetext zum apropos-
Befehl (dieser ist ein Bestandteil des man-Befehls), aber wenn Sie mit der Leer-
taste weiterblättern, erscheint etwas weiter unten auch Hilfe zum man-Befehl.
Diese Hilfe verlassen Sie durch drücken der Taste “q”.
Um den Umgang mit man zu trainieren, schauen Sie sich bitte auch die Hilfe zu
anderen Unix-Kommandos an. Wenn Sie Hilfe zum Kopier-Befehl cp oder zum
Directory-Anzeigekommando ls wünschen, probieren Sie aus:
> man cp
> man ls
135
A.3.2 Der Apropos-Befehl
Die oben beschriebene Form des man-Befehles hat einen Nachteil: Sie können sich
nur Informationen zu einem Kommando geben lassen, wenn Sie schon dessen
Namen kennen.
Wenn Sie einen Befehl suchen, der eine gewisse Aufgabe erledigt, dessen Na-
men Sie aber nicht kennen, können Sie das apropos Kommando, gefolgt von
einem Schlüsselwort, verwenden. Daraufhin werden die Kurzbeschreibungen al-
ler durch man dokumentierten Kommandos durchsucht. Alle Kommandos, in
deren Beschreibung dies Schlüsselwort auftaucht, werden aufgelistet.
Wenn Sie ganz allgemein nach Hilfe suchen, könnten Sie zum Beispiel einen der
folgenden Befehle ausprobieren:
Sie erhalten bei jedem apropos-Befehl eine Liste von zum Schlüsselwort passen-
den Kommandos mit einer Kurzerklärung angezeigt.
Diese Beispiele demonstrieren, daß Sie je nach gewählten Schlüsselwort unter-
schiedliche Kommandos angezeigt bekommen. Selbst wenn ein Befehl sinngemäß
etwas mit dem Hilfesystem zu tun hat, nennt apropos help ihn nur dann, wenn
in seiner Kurzbeschreibung auch das Wort help vorkommt. Man muß daher
manchmal ein wenig mit unterschiedlichen Schlüsselwörtern rumprobieren, bis
man einen Befehl gefunden hat.
Außerdem zeigen diese Beispiele, daß apropos auch unerwartete Antworten lie-
fert - so hat der Befehl apropos manual auch die Zeile
angezeigt. Das hat nichts mit manuals zu tun, aber weil in der Beschreibung
die Zeichenkette “manual” vorkommt, hat der apropos-Befehl auch dieses Kom-
mando angezeigt.
Ein weiteres Beispiel: Angenommen, Sie suchen nach einem Befehl, der eine
Eingabe sortiert. Also geben Sie ein
sort(1) - Sorts files, merges files that are already sorted, and
checks files to determine if they have been sorted.
So haben wir also herausgefunden, daß man zum sortieren einer Textdatei den
sort-Befehl verwenden kann. Bevor man den Befehl verwenden kann, braucht
man natürlich noch detailliertere Hilfe:
136
A.4 Ausgabeumleitung
Als nächstes wollen wir Sie mit Ausgabeumleitung bekannt machen. Ausga-
beumleitung ist ein Weg, mehrere Unix-Programme miteinander zu kombinie-
ren. Eine für das Programmieren hilfreiche Anwendung ist z.B. das Blättern
oder Suchen in Ausgaben des Compilers oder anderer Werkzeuge.
Fast alle Unix-Shell-Programme arbeiten wie folgt: Sie nehmen Eingaben aus
Ihrem Eingabekanal entgegen, verarbeiten diese und geben Sie auf ihrem Ausga-
bekanal aus. Ein Eingabekanal kann z.B. eine Datei, Eingaben eines Benutzers
oder Ausgaben eines anderen Programms sein. Ein Ausgabekanal kann z.B. der
Bildschirm, eine Datei oder der Eingabekanal eines anderen Programms sein.
Normalerweise wählt jedes Unix-Programm als Eingabekanal die Tastatur und
als Ausgabekanal den Bildschirm. Nehmen wir beispielsweise den Befehl cat. Er
kopiert alle Daten aus seinem Eingabekanal zu seinem Ausgabekanal. Probieren
wir das aus:
> cat
Sie erhalten nun einen blinkenden Cursor. Geben Sie irgend etwas ein und
drücken die Return-Taste. Als Ergebnis wird Ihre Eingabe ein zweites Mal aus-
geben. Als Sie Return gedrückt haben, hat cat Ihre Eingabe (von der Tastatur)
auf seinen Ausgabekanal (das Bildschirmfenster) kopiert. Die Tatsache, daß Ih-
re Eingabe schon beim Tippen auf dem Bildschirm erschien, darf Sie hier nicht
stören - damit hatte cat nichts zu tun.
Geben Sie noch ein paar Zeilen ein - jedesmal, wenn Sie Return drücken, wird
ihre Eingabe abgeschickt und von cat auf die Ausgabe kopiert. Beenden Sie cat,
indem Sie die Tasten Strg und “d” gleichzeitig drücken. Die Tastenkombination
“Strg+d” bedeutet für cat, daß die Eingabe beendet ist und es zu arbeiten
aufhören kann — Hier gleich eine Warnung: Der Tastendruck Strg+d und das
EOF, das sie beim Programmieren mit C kennenlernen werden, sind nicht das
gleiche — allerdings erzeugt “Strg+d” ein EOF.
Jetzt wollen wir die Ausgabe von cat umleiten, d.h. wir wollen seinen Ausgabe-
kanal verändern. Wenn man die Ausgabe in eine Datei leiten will, so gibt man
nach dem umzuleitenden Befehl den Text > dateiname an, wobei “dateiname”
für den Namen der Zieldatei steht.
Probieren wir das aus:
Geben Sie wieder ein paar Textzeilen, jedesmal gefolgt von Return, ein. Ihnen
fällt sicher auf, daß die Textzeilen nach dem Return nicht ein zweites Mal ausge-
ben werden. Das war erwartet, da der Ausgabekanal nicht mehr der Bildschirm
sondern die Datei testDatei ist. Schließen Sie nach ein paar Zeilen die Eingabe
ab, indem Sie wieder Strg+d drücken.
Benutzen Sie bitte den ls-Befehl, um sich davon zu überzeugen, daß eine Datei
namens testDatei erzeugt wurde. Um den Inhalt dieser Datei am Bildschirm
anzuzeigen, benutzen wir wieder cat, leiten diesmal aber nicht die Ausgabe
sondern die Eingabe um. Dies geschieht, indem man hinter den Befehl den Text
> dateiname schreibt. Probieren Sie das bitte aus:
137
> cat < testDatei
Dies sollte Ihre vorhin angegebenen Zeilen vom Eingabekanal, also der Datei
testDatei auf den Ausgabekanal, also den Bildschirm, ausgeben.
Das man auch auch kombinieren, um eine eine Datei zu kopieren (in der Praxis
verwendet man dafür aber lieber den cp-Befehl):
Dies liefert keine Ausgabe auf dem Bildschirm. Der Eingabekanal war die Datei
testDatei und der Ausgabekanal ging in die Datei kopie. Testen Sie, ob die
Datei auch wirklich kopiert wurde:
Das letzte Beispiel demonstriert eine Eigenschaft vieler Unix-Befehle — oft in-
terpretieren Unix-Befehle Parameter als Dateinamen, welche dann zum Ein-
oder Ausgabekanal werden.
Nun folgen noch ein paar Anwendungen und Beispiele zur Ausgabeumleitung.
Das Umleiten von Ein- und Ausgabekanal funktioniert bei fast allen Unix-
Programmen. Probieren Sie einfach mal die folgenden Kommandos aus (Falls
Sie wissen wollen, was der sort-Befehl tut, können Sie sich darüber mit dem
man-Befehl informieren):
Was ist hier passiert ? Wir haben die Ausgabe von ls in eine Datei geleitet und
anschließend diese Datei als Eingabekanal an den sort-Befehl gegeben. Dieser
hat den Inhalt der Datei absteigend sortiert wieder an seinen Ausgabekanal,
den Bildschirm, ausgegeben.
Mit Hilfe des |-Operators kann man mehrere Ausgabeumleitungen kombinieren.
Der |-Operator leitet den Ausgabekanal eines Programms in den Eingabekanal
eines anderen Programms um. Konstrukte der Form
arbeiten ungefähr wie folgende Sequenz von Befehlen, allerdings ohne eine tem-
poräre Datei anzulegen:
Wir können dies auf das Beispiel, in welchem wir das Verzeichnis sortiert haben,
anwenden — probieren Sie bitte folgende Befehle aus:
138
> ls | sort -r
> ls | sort -r > sorted
> cat sorted
> ls -lF /usr/bin | sort | less
Die letzte Zeile demonstriert, daß man beliebig viele Ausgabeumleitungen hin-
tereinanderschalten kann. Hier wird die Ausgabe von ls an sort weitergeleitet,
dort sortiert und dann an den less-Befehl weitergegeben.
Der less-Befehl gibt die Eingabe seitenweise auf den Bildschirm aus. Sie ver-
lassen ihn durch die Taste “q”. Wenn Sie etwas durch den less-Befehl anzeigen
lassen, können Sie sich mit den Cursor hoch und Cursor runter-Tasten in der
Ausgabe bewegen.
1. Zuerst müssen Sie dem anderen Rechner gestatten, auf Ihren Monitor zu-
zugreifen. Die Methode, die wir hier beschreiben, ist zwar etwas unsicher,
da Sie dem fremden Rechner völligen Zugriff auf Ihren Monitor gewährt,
aber sie demonstriert am besten die Trennung von Bildschirm und Rech-
ner.
Um dem fremden Rechner Zugriff auf Ihren Monitor zu gestatten, geben
Sie in einer Unix-Shell auf dem lokalen Rechner ein:
Damit kann der fremde Rechner Ihren Monitor beschreiben und auch aus-
lesen.
2. Anschließend müssen Sie in Erfahrung bringen, wie das Display heißt, auf
dem Ihr Rechner seine Fenster momentan öffnet. Dazu geben Sie auf der
Kommandozeile ein:
Daraufhin sollte entweder der Text :0.0 oder ein Text der Form rechnername:0.0
ausgeben werden. Merken Sie sich diesen Namen.
139
3. Anschließend müssen Sie sich auf den fremden Rechner einloggen.
4. Nachdem Sie sich auf dem fremden Rechner angemeldet haben, müssen
noch das Display einstellen, auf das er seine Ausgabe leiten soll.
Wenn Sie bei der Ausgabe von
vorhin den Text :0.0 erhalten haben, ist der Name des Displays lokalerRechner.rz.tu-bs.de:0.0.
Ansonsten ist der Name des Displays der vorhin angezeigte Name.
Geben Sie in der telnet-Sitzung23 folgendes ein, wenn vorhin der Name
:0.0 angezeigt wurde:
oder, wenn vorhin ein anderer Name als :0.0 angezeigt wurde, geben Sie
hinter dem Gleichheitszeichen den Namen des vorhin angezeigten Displays
an.
> xclock
Und hier auch gleich eine Tip: Der Pool ist sehr stark ausgelastet. Wenn Sie
im Pool arbeiten und nebenher einen WWW-Browser wie Netscape verwenden
wollen, so können Sie sich mit Ihrer y-Nummer auf einem Rechner außerhalb
des Pools einloggen und dann das Display auf Ihren Arbeitsrechner im Pool
umlenken. Sie können dann Netscape auf dem entfernten Rechner starten.
Und noch ein Tip: Natürlich können Sie sich auf diese Weise auch von anderen
Rechnern aus im CIP-Pool einloggen. Sie können ohne weiteres in einem Institut
oder im Rechenzentrum sitzen, während Sie im Pool im Altbau arbeiten.
140
Das Programm, welches Ihren Quelltext in eine vom Computer verstandene
Form übersetzt, nennt sich Compiler. Der Java-Compiler hat eine Besonderheit
— er erzeugt zwar für einen Computer verständlichen Code, aber der erzeugte
Code ist für keinen existierenden Computer verständlich.
Der Java-Compiler erzeugt Code — den sogenannten Java Bytecode, der von
einem sogenannten virtuellen Computer — der Java Virtual Machine
(JVM) — verstanden werden kann. Die Java Virtual Machine ähnelt real exi-
stierenden Prozessoren, berücksichtigt dabei aber die besonderen Eigenschaften
von Java.
Zum Java-System gehört nun ein Programm, welches diesen virtuellen Compu-
ter auf einem anderen Computer emuliert. Sie müssen also nach dem Kompi-
lieren Ihres Quelltextes ein weiteres Programm starten, welches den Bytecode
entgegennimmt und interpretiert.
Natürlich kann Java damit nicht so schnell sein wie andere Programmierspra-
chen, die direkt Programmcode die jeweilige Zielplattform erzeugen, weil der
Prozessor niemals Java-Code direkt ausführt. Es gibt allerdings inzwischen Lauf-
zeitsysteme, welche welche den Bytecode auf die jeweilige reale Hardware-Plattform
kompilieren und dort direkt ausführen können.
Die Tatsache, daß Java-Programme nicht auf eine spezielle Zielarchitektur kom-
piliert werden, bewirkt also daß Java-Programme nicht so schnell laufen wie in
anderen Sprachen entwickelte Software. Dennoch ist die Verwendung der virtu-
ellen Maschine ein großer Vorteil und hat den Einsatz von Java im Internet erst
ermöglicht — hierdurch wird nämlich Systemunabhängigkeit erreicht.
In der Theorie läuft ein Java-Programm auf jeder Hardware und jedem Be-
triebssystem, für das eine virtuelle Maschine existiert. Man muß sich also nicht
mehr entscheiden, ob man ein Programm für Windows, Macintosh, Linux, HP-
Unix, Be-OS, OS/2 oder sonst ein Betriebssystem entwickelt. Sobald die virtu-
elle Java Maschine auf diesem Betriebssystem vorhanden ist, läuft hierauf jedes
Java-Programm identisch (wohlgemerkt: in der Theorie).
B.2 Kompilation
Wir wollen nun ein erstes Programm kompilieren. Bitte tippen Sie dazu folgen-
des Programm in einem Editor ein — beachten Sie dabei, Groß und Kleinschrei-
bung exakt so wiederzugeben wie hier beschrieben.
Sie sollten immer darauf achten, daß der Name einer Programmdatei mit dem
Namen der enthaltenen Klasse beginnt und auf .java endet. Hier ist der Name
der Klasse “HalloWelt”. Speichern Sie daher den obigen Programmtext in der
Datei HalloWelt.java ab.
141
Um die Klasse zu kompilieren, gehen Sie bitte auf eine Unix-Shell und wechseln
in das Verzeichnis, in welchem Sie die Datei gespeichert haben. Hier geben Sie
dann ein
Wenn Sie beim Abtippen einen Fehler gemacht haben, meldet sich der Compiler
mit einer Fehlermeldung. Lesen Sie bitte die Fehlermeldung aufmerksam —
Sie sollten Fehlermeldungen immer aufmerksam lessen — und korrigieren ihn
bitte im Editor anschließend den in der Fehlermeldung beschriebenen Fehler.
Speichern Sie dann die Datei und kompilieren sie erneut.
Wenn Sie beim abtippen keine Fehler gemacht haben, sollte beim Kompilie-
ren keine Ausgabe auf dem Bildschirm erzeugt worden sein aber die Datei
HalloWelt.class entstanden sein. Prüfen Sie dies bitte nach !
Die Datei HalloWelt.class enthält den Bytecode unserer Klasse. Sie können
das Programm nun laufen lassen, indem Sie die Java-Virtual-Machine mit un-
serem kompilierten Programm starten:
Achten Sie bitte darauf, beim Aufruf von java nicht die Erweiterung .class
anzugeben. Dann wird ihre Klasse nämlich nicht gefunden.
Wenn Sie bis hierher alles nachgemacht haben, sollte der Text “Hallo, Welt” auf
dem Bildschirm ausgegeben werden.
B.3 Datentypen
Um dieses Kapitel zu verstehen, sollten Sie den Anhang ?? zum Aufbau eines
Computers gelesen haben.
Um die Attribute unserer Objekte zu definieren, haben wir bisher Wertebereiche
wie ganze Zahl, reelle Zahl, Zeichenkette oder Vektor verwendet. Wenn wir in
Java die Wertebereiche von Attributen beschreiben, kann man nicht einfach
einen umgangssprachlichen Ausdruck wie “ganze positivie Zahl” verwenden.
Der Java-Compiler muß schon etwas genauer wissen, was für Zahlen zu verwen-
den sind. Letztendlich muß ja jede Zahl in einem Java-Programm im Speicher
des Computers gespeichert werden. Es sollte einleuchtend sein, daß große Zahlen
mehr Platz zur Speicherung benötigen als kleine Zahlen — um eine 20 stellige
Zahl aufzuschreiben, benötigt man ja auch mehr Platz als für eine 2 stellige
Zahl. Es wäre nun sehr unangebracht, wenn der Java-Compiler jede ganzzahlige
Zahl
C Probleme
C.1 Probleme bei Verwendung von javac
Beim Kompilieren einer Klassendatei
142
treten manchmal folgende Fehlermeldungen auf:
Der Compiler findet die Datei nicht. Möglicherweise befinden Sie sich im falschen
Verzeichnis oder haben die Datei im Editor noch nicht oder unter einem anderen
Namen abgespeichert. Prüfen Sie, ob die Datei vorhanden ist:
> ll MeineKlasse.java
Der Compiler findet die zum Java-System gehörigen Klassen nicht. Ihr Java-
Programmiersystem ist nicht korrekt installiert. Sie sollten die Umgebungsva-
riable CLASSPATH prüfen. Siehe Anhang E
Der Compiler findet die Turtle–Graphik nicht. Sie sollten die Turtle–Graphik
installieren und die Umgebungsvariable CLASSPATH prüfen. Siehe Anhang E
143
C.2 Probleme bei Verwendung von java
Beim Ausführen einer Klasse
das Kommando
verwendet.
Sie versuchen eine Klasse auszuführen, in welcher die main-Methode fehlt. Jede
Klasse, die Sie ausführen wollen, benötigt eine solche Methode. (siehe ??).
Sie versuchen eine Klasse auszuführen, in welcher die main-Methode fehlt. Jede
Klasse, die Sie ausführen wollen, benötigt eine solche Methode. (siehe ??).
java.lang.NoClassDefFoundError: ak/Turtle/TurtleScreen
at MeineKlasse.main(Compiled Code)
144
D Goldene Regeln fürs Programmieren
Ihre erstellten Javaprogramme sollen nicht nur syntaktisch richtig, sondern auch
leicht zu lesen sein. Deshalb sollen Sie von der ersten Zeile an auf einen guten
Programmierstil achten. Dieser zeichnet sich vor allem dadurch aus, dass ihre
Programme gut gegliedert sind, Kommentare enthalten und von anderen leicht
verständlich zu lesen sind. Dadurch vermeiden Sie beim Programmieren Fehler
und ihre Programme sind besser wartbar. Um ihnen von Anfang an einen guten
Programmierstil beizubringen, möchten wir ihnen folgende Richtlinien mit auf
den Weg geben, die für diese Lehrveranstaltung verbindlivh sind.
D.1 Allgemeines
Hier nun ein paar allgemein Regeln auf die in den nächsten Abschnitten noch
näher eingegangen wird.
Benennen Sie Klassen nach ihrem Zweck, Variablen nach ihrem Inhalt, Metho-
den nach ihrer Aufgabe. Das Programm wird dadurch lesbarer, da es dann nicht
mehr notwendig ist, bei jedem Vorkommen eines Bezeichners zu überlegen, wozu
der Bezeichner da ist. Lassen Sie nach jedem Schlüsselwort und zwischen den
binären Operatoren ein Leerzeichen frei.
D.2 Quelldateien
Die Quelldateien sollen folgendes Aussehen besitzen:
• Übungskommentar
• package Anweisung
• import Anweisung
145
Der Übungskommentar besteht aus dem Namen des Studenten, seiner Matri-
kelnummer, dem Datum und der Aufgabenstellung. Dieser Kommentar sollte
als spezieller “doc Kommentar” dargestellt werden, d. h. er beginnt mit einem
/** und endet mit */. Diese werden vom javadoc Programm speziell ausgewer-
tet, um eine einfache Onlinedokumentation aus dem Javaquellcode zu erstellen.
Nutzen Sie auch die @author und @version tags. Hier ist kleines Beispiel:
/**
* Die Klasse Kreise mit ihren Daten und Methoden
* Hausaufgabe 3
* @author: Harry Hacker
* @version 1.2.3 31.04.00
*/
Benutzen Sie maximal einen Befehl pro Zeile! Tritt im Programm ein Fehler
auf, kann man seinen Ort häufig schnell auf eine Programmzeile einschränken.
Wenn dort dann mehrere Anweisungen stehen, ist eine Lokalisierung des Fehlers
schwierig. Auch die Fehlermeldungen des Compilers beziehen sich auf eine Zeile.
D.3 Klassen
Es ist üblich, Klassennamen mit einem Grossbuchstaben beginnen zu lassen.
Es empfiehlt sich, englischsprachige Bezeichner zu verwenden, da das gesamte
JDK englischsprachig ist. Wo möglich, sollten Ihre Wahl von Bezeichnern den
im JDK verwendeten Konventionen folgen.
Schreiben Sie als erstes die Variablen und Methoden auf, die als public dekla-
riert werden und dann die als private deklarierten.
D.4 Methoden
Jede Methode (ausser main) beginnt mit einem Kommentar im javadoc Format.
/**
* Berechnet die Fakultaet von n
*/
public int fakultaet(int fac)
{ . . .
}
Vermeiden Sie Programmzeilen, die mehr als 80 Zeichen enthalten und Metho-
den mit mehr als 50 Zeilen, da beides die Übersichtlichkeit reduziert. Manchmal
lässt es sich aber nicht vermeiden, wie z.B. bei langen if/else Anweisungen.
Sie sollten jedoch immer komplexe Probleme in kleinere und einfachere Teilpro-
bleme unterteilen.
146
men, so können Sie die nächstfolgenden Wörter auch mit einem grossen Buch-
staben beginnen lassen, zum Beispiel firstPlayer. Definieren Sie nicht alle
Variablen sofort am Anfang eines Blockes,
public static double sqrt(double a)
{ double xold;
double xnew;
boolean more;
. . .
}
Wenn sich die Variable SIZE_DATEN_ARR später ändern sollte, sind Sie so auf
der sicheren Seite, ausserdem wird der Programmtext durch die Verwendung
sprechender Namen besser lesbar.
Wenn in einem Programmtext die Zahl π benötigt wird, schreiben Sie diese
nicht als 3.14159265358979323846 sondern definieren Sie am Klassenanfang die
Konstante
147
final static double PI = 3.14159265358979323846;
D.6 Kontrollstrukturen
Benutzen Sie eine sinnvolle und einheitliche Einrückung. Die Einrückung soll
die Struktur des Programms deutlich machen. Die Lesbarkeit des Programms
wird deutlich erhöht, wenn zusammengehörige Programmteile auch optisch zu-
sammen gehören. Achten Sie darauf, dass die Einrückung konsistent ist. Die
Einrücktiefe sollte jeweils drei Leerzeichen betragen. Benutzen Sie geschweifte
Klammern um die entsprechenden Zugehörigkeiten anzuzeigen, oder um sie zu
erzwingen. Bei diesem Beispiel ist nicht eindeutig wohin die else Anweisung
gehört.
if (n > 0)
if (a > b)
z = a;
else
z = b;
if (n > 0)
{ if (a > b)
z = a;
else
z = b;
} /* {...} sind hier nicht erforderlich */
if (n > 0)
{ if (a > b)
z = a;
}
else
z = b; /* hier sind {...} erforderlich */
while (i < n)
{ print(a[i]);
i++;
}
148
E Installation von Java und Turtle–Graphik
E.1 Installation von Java
Wenn Sie im CIP–Pool arbeiten, ist dies schon für Sie erledigt. Falls Sie zuhause
arbeiten wollen, holen Sie sich bitte von Sun24 das JDK 1.2 für Ihr Betriebssy-
stem und folgen den Installationsanweisungen.
eip.TurtleScreen
eine Klasse namens TurtleScreen, welche unter der Pfadangabe eip zu finden
ist. Da jeder Benutzer die Dateien auf seiner Festplatte unterschiedlich organi-
siert, muß Java irgendwie mitgeteilt werden, wo auf der Festplatte nach einer
derartigen Klasse gesucht werden soll.
Hierzu verwendet Java die CLASSPATH–Umgebungsvariable. Eine Umgebungs-
variable ist ein Mittel des Betriebssystems zur Konfiguration von Programmen.
Die CLASSPATH–Variable enthält beliebig viele, durch Doppelpunkte getrenn-
te Pfadangaben:
CLASSPATH=pfad1:pfad2:pfad3:...:pfadN
Dabei ist eine Pfadangabe entweder ein Verzeichnispfad oder der Name eines
.zip–Archives. Ein .zip–Archiv ist eine Datei, welche viele andere Dateien
enthält.
CLASSPATH=.:C:\java\jdk\classes.zip:C:\java\turtle
CLASSPATH=.:/usr/local/java/jdk/classes.zip:/usr/local/java/turtle/
Wenn Java nach der Klase eip.TurtleScreen sucht, geht es wie folgt vor: Es
schaut in jedem im CLASSPATH angegebene Verzeichnis nach, ob es eine Datei
ak/Turtle/TurtleScreen.class enthält (unter Windows würde Java schauen,
ob es eine Datei ak\Turtle\TurtleScreen.class findet). Die erste derartige
Datei, die in einem Verzeichnis des CLASSPATH gefunden wird, wird dann be-
nutzt.
24 http://www.java.sun.com/products/
149
Beispiel: Wenn unter Unix der CLASSPATH wie oben angegeben ist, wür-
de Java zuerst nachsehen, ob die Datei ak/Turtle/Turtle.class in der
.zip–Datei /usr/local/java/jdk/classes.zip enthalten ist.
Wenn dies nicht der Fall ist, würde Java nachschauen, ob die Datei
/usr/local/java/turtle/ak/Turtle/Turtle.class existiert.
Falls auch dies nicht der Fall ist, würde Java nachsehen, ob im aktu-
ellen Verzeichnis das Unterverzeichnis ak/Turtle/ und darin die Datei
TurtleScreen.class existiert.
Der CLASSPATH wird immer benutzt, wenn Java eine Datei sucht. Ob Sie Java
innerhalb eines Programms durch den Befehl
import eip.TurtleScreen;
anweisen, die Klasse eip.TurtleScreen zu verwenden, oder ob Sie auf der Kom-
mandozeile ein Javaprogramm per
starten — immer wird die Klasse mit Hilfe des CLASSPATH gesucht.
Dies kann zu verwirrenden Effekten führen, wenn im CLASSPATH nicht auch das
aktuelle Arbeitsverzeichnis namens „.“ enthalten ist. Denn dann kann Java Ihre
Klassendatei selbst dann nicht finden, wenn Sie im aktuellen Arbeitsverzeichnis
zu finden ist.
Wenn im Arbeitsverzeichnis die Datei MeineKlasse.java existiert und „.“ nicht
im CLASSPATH enthalten ist, so erhalten Sie auf den Befehl java MeineKlasse
die Meldung
In solch einem Fall nehmen Sie das aktuelle Arbeitsverzeichnis in Ihren CLASSPATH
auf.
Weiter unten beschreiben wir, wie man den CLASSPATH erweitern kann.
> cd ~
> gzip -d Turtle.tgz
> cd ~/turtle
> tar xvf ~/Turtle.tar
25 http://www.tu-bs.de/institute/wir/eip/
150
Prüfen Sie nun, ob die Datei ~/turtle/ak/Turtle/Turtle.java existiert.
Anschließend setzen Sie bitte, wie im nächsten Kapitel beschrieben, den
CLASSPATH und testen die Turtle durch den Befehl
anschließend entpacken Sie bitte die Datei Turtle.zip hier hinein. Achten
Sie dabei darauf, daß beim Entpacken alle Pfade wiederhergestellt werden.
Wie das geht, ist in der Dokumentation des Entpackers beschrieben.
Sehen Sie nach dem Entpacken nach, ob die Datei
c:\java\turtle\ak\Turtle\Turtle.java
• Wenn Sie unter Unix arbeiten und die bash–Shell verwenden, können Sie
auf der Kommandozeile eingeben
export CLASSPATH=$CLASSPATH:neuerPfad
export CLASSPATH=$CLASSPATH:~/turtle
26 http://www.tu-bs.de/institute/wir/eip/
27 http://www.cdrom.com/pub/infozip/UnZip.html
28 http://www.winzip.com/
151
Diesen Befehl müssen Sie jedesmal erneut eingeben, wenn Sie eine Kom-
mandozeile öffnen. Sie können den Befehl statt dessen auch als letzte Zeile
in die Datei ~/.profile aufnehmen. Dann wird der CLASSPATH bei jedem
anmelden automatisch gesetzt.
• Unter Windows können Sie dazu auf der Kommandozeile eingeben
set CLASSPATH=%CLASSPATH%:neuerPfad
set CLASSPATH=%CLASSPATH%:C:\java\turtle
Diese Zeile müssen Sie jedesmal eingeben, wenn Sie eine Kommandozeile
öffnen. Wenn Sie die obige Zeile als letzte Zeile in die Datei C:\autoexec.bat
aufnhemen, wird der CLASSPATH bei jedem Neustart von Windows auto-
matisch gesetzt.
152