Programmieren in Java 1.5: Ein kompaktes, interaktives Tutorial 354023134X, 9783540231349

Die Autoren geben eine kompakte und fundierte Einf hrung in die objektorientierte Programmierung mittels Java. Im Zentru

146 27 4MB

German Pages 347 [341] Year 2005

Report DMCA / Copyright

DOWNLOAD PDF FILE

Recommend Papers

Programmieren in Java 1.5: Ein kompaktes, interaktives Tutorial
 354023134X, 9783540231349

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

Die Reihe Xpert.press vermittelt Professionals in den Bereichen Softwareentwicklung, Internettechnologie und IT-Management aktuell und kompetent relevantes Fachwissen über Technologien und Produkte zur Entwicklung und Anwendung moderner Informationstechnologien.

Gregor Fischer · Jürgen Wolff von Gudenberg

Programmieren in Java 1.5 Ein kompaktes, interaktives Tutorial

Mit 62 Abbildungen, 43 Tabellen und CD-ROM

123

Gregor Fischer Universität Würzburg, Lehrstuhl für Informatik II Am Hubland 97074 Würzburg E-mail: [email protected] Jürgen Wolff von Gudenberg Universität Würzburg, Lehrstuhl für Informatik II Am Hubland 97074 Würzburg E-mail: [email protected] Ergänzende Informationen zu diesem Buch sind verfügbar unter: http://www.java-kompakt.de Bibliografische Information der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar.

ISSN 1439-5428 ISBN 3-540-23134-X Springer Berlin Heidelberg New York Dieses Werk ist urheberrechtlich geschützt. Die dadurch begründeten Rechte, insbesondere die der Übersetzung, des Nachdrucks, des Vortrags, der Entnahme von Abbildungen und Tabellen, der Funksendung, der Mikroverfilmung oder der Vervielfältigung auf anderen Wegen und der Speicherung in Datenverarbeitungsanlagen bleiben, auch bei nur auszugsweiser Verwertung, vorbehalten. Eine Vervielfältigung dieses Werkes oder von Teilen dieses Werkes ist auch im Einzelfall nur in den Grenzen der gesetzlichen Bestimmungen des Urheberrechtsgesetzes der Bundesrepublik Deutschland vom 9. September 1965 in der jeweils geltenden Fassung zulässig. Sie ist grundsätzlich vergütungspflichtig. Zuwiderhandlungen unterliegen den Straf-bestimmungen des Urheberrechtsgesetzes. Springer ist nicht Urheber der Daten und Programme. Weder Springer noch die Autoren übernehmen die Haftung für die CD-ROMs und das Buch, einschließlich ihrer Qualität, Handels- und Anwendungseignung. In keinem Fall übernehmen Springer oder die Autoren Haftung für direkte, indirekte, zufällige oder Folgeschäden, die sich aus der Nutzung der CD-ROMs oder des Buches ergeben. Springer ist ein Unternehmen von Springer Science+Business Media springer.de © Springer-Verlag Berlin Heidelberg 2005 Printed in Germany Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutzgesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften. Text und Abbildungen wurden mit größter Sorgfalt erarbeitet. Verlag und Autor können jedoch für eventuell verbliebene fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Satz: Druckfertige Daten der Autoren Herstellung: LE-TeX Jelonek, Schmidt & Vöckler GbR, Leipzig Umschlaggestaltung: KünkelLopka Werbeagentur, Heidelberg Gedruckt auf säurefreiem Papier 33/3142/YL - 5 4 3 2 1 0

Vorwort

Objektorientiert Denken und Programmieren gehört nach wie vor zu den Grundfähigkeiten eines Informatikers. Und nicht nur für diesen: Mit der zunehmendern Computerisierung und Vernetzung vieler, ja fast aller Bereiche in der Wirtschaft und im öffentlichen Leben steigt die Zahl derjenigen, für die Programmierfähigkeiten hilfreich sind, ständig an. Besonders die Sprache Java hat in den letzten Jahren einen beispiellosen Vormarsch angetreten. Bedingt durch die im Java-System enthaltenen umfangreichen Programmbibliotheken, unter anderem zur einheitlichen Gestaltung von grafischen Benutzeroberflächen, und ihren Einsatz und kostenlose Verfügbarkeit im Internet fand Java rasch viele Anhänger. Die zentrale, de facto genormte und frei zugängliche Definition der Sprache sowie ihrer Systemumgebung von und durch Sun Microsystems gewährleisten den Einsatz des gleichen Programms auf fast allen Plattformen, vom Großrechner bis zum Handy. Auch in der Ausbildung hat sich Java durchgesetzt. Im Informatikstudium, Hauptoder Nebenfach, gehört Java zum Pflichtprogramm. Durch Entwicklungswerkzeuge und Standardkomponenten für den professionellen Einsatz dringt Java mehr und mehr in die Unternehmenssoftware vor. Es besteht ein Bedarf an Weiterbildung. Wie lernt man nun Programmieren? Sicher nicht durch Lesen eines Buches! Auch nicht durch Entwerfen schöner Diagramme oder durch Aufstellen abstrakter mathematischer Modelle. Programmieren ist ein Handwerk, welches man letztendlich nur durch das Schreiben von Programmen erlernt. Damit wir uns nicht missverstehen: Das genaue Modellieren der Anwendung sowie die übersichtliche Gestaltung und Beschreibung der Beziehungen der Objekte tragen wesentlich zu einem guten Programm bei. Deshalb gehen wir in dem Buch auch kurz darauf ein und erläutern exemplarisch einige wichtige Konzepte, Strukturen und Entwurfsmuster. Unser Hauptaugenmerk liegt jedoch auf dem handwerklichen Charakter. Um diesen zu betonen, haben wir in Zusammenarbeit mit der Universität Passau (Prof. G. Snelting) für die Virtuelle Hochschule Bayern in den vergangenen Jahren ein Java Online Praktikum entwickelt, ein kompaktes, im Internet präsentiertes, interaktives Tutorial und ein Praktomat genanntes Werkzeug, welches Lösungen zu gestellten

VI

Vorwort

Aufgaben online entgegen nimmt und überprüft. Die elektronische Beurteilung schließt formelle Aspekte wie Einrücktiefe, Namensgebung, Kommentierung und strukturelle Eigenschaften wie richtigen Gebrauch der Datenkapselung oder Benutzung vorgegebener Komponenten ebenso ein wie die funktionelle Überprüfung der eingereichten Lösungen durch eine Vielzahl von Tests. Konsequenterweise enthält die beiliegende CD den Text des Buches in frei navigierbaren HTML Seiten mit interaktiven Beispielen sowie multiple-choice und Programmieraufgaben. Die Beispiele sind vollständige Programme, von denen im Buch nur Ausschnitte gezeigt werden. Sie können aus dem Tutorial heraus verändert, übersetzt und ausgeführt werden. Die Lösungen der Programmieraufgaben werden vom Werkzeug JEEE (Java Exercise Evaluation Environment) automatisch geprüft. Das System und sein Vorläufer wurde in verschiedenen Praktika und Vorlesungen der Universitäten Passau und Würzburg sowie der Virtuellen Hochschule Bayerns eingesetzt. Mehrere hundert Studenten reichten mehr als 4000 Lösungen zu unterschiedlichen Aufgaben (vom größten gemeinsamen Teiler zum Backgammonspiel) ein. Bedingt durch die sofortige Überprüfung gewannen die Programme deutlich an Qualität. Java 1.5 Java ist eine Sprache, die mit der Zeit geht. So sind immer wieder neue erweiterte Versionen erschienen. Lange Zeit war die Version 1.4 aktuell, so auch bei der Erstellung des Java Online Praktikums und der Werkzeuge zum Überprüfen der Lösungsvorschläge. Seit Oktober ist die neue Sprachversion 1.5 als Standard Edition 5.0 (J2SE 5.0) erhältlich. Entgegen den Angaben im Buch befindet sie sich auch auf der Begleit-CD. Die Version 1.5 bringt wesentliche, die Sprache betreffende Neuerungen mit sich. Die wichtigsten sind sicher die parametrisierten (generischen) Typen und die Zusammenfassung von typisierten Konstanten zu Aufzählungstypen mit endlichem Wertebereich. Obwohl die Sprachversion 1.5 während der Erstellung des Buches noch nicht freigegeben war, behandeln wir die neuen, relevanten Konzepte. Das Buch ist also ein Lehrbuch für die neueste Java Version. Das Übersetzen und Ausführen aller Beispielprogramme ist selbstverständlich möglich. Die Umgebung JEEE baut jedoch noch auf der Version 1.4.2 auf. Ebenso beziehen sich die Hinweise zur Installation auf diese Version als die aktuelle.

Vorwort

VII

Aufbau des Buches und der CD Nach einem in die objektorientierte Denkweise einführenden Beispiel beginnen wir mit elementaren Objekten und Ausdrücken und bauen die Sprache dann kontinuierlich auf bis zur Einführung in grafische Benutzeroberflächen und nebenläufige Prozesse. Um schneller zu verwertbaren Ergebnissen zu kommen, beginnen wir dabei häufig mit der Verwendung eines Konstruktes, bevor wir Details seiner Vereinbarung betrachten, z.B. werden Methoden bereits aufgerufen, bevor ihre Vereinbarung behandelt wird. Kapitel 3, 5 und 6 erläutern die Vereinbarung neuer Datenstrukturen und Implementierung von erweiterbaren, abstrakten Datentypen mit Hilfe des Klassenkonzeptes. Sie bilden das Kernstück des Buches und beschreiben die Verwirklichung des objektorientierten Ansatzes in Java. Kapitel 2 und 4 führen in die Java Syntax für Ausdrücke und Anweisungen ein. Der Leser mit etwas Programmiererfahrung möge diese Kapitel überfliegen oder zum Nachschlagen benutzen. Exemplarisch werden Teile der Java Klassenbibliothek in Kapitel 7 und 10 vorgestellt. Spätestens hier soll der Leser zum Blättern in der Java Dokumentation angeregt werden. Kapitel 8 beschreibt die in Java 1.5 hinzukommenden generischen Typen. Auf der beiliegenden CD befinden sich neben dem Text des Buches, den vollständigen Beispielen und Aufgaben und der Software zum Verändern und Überprüfen der Programme auch die Java Entwicklungswerkzeuge, der Browser Mozilla und die Entwicklungsumgebung Eclipse. Aktuelle Hinweise und weitere Informationen stehen auf der Homepage http://www.java-kompakt.de/. Viele Personen haben zur Implementierung des Systems und zur Entstehung des Buches beigetragen, denen unser herzlicher Dank gilt. Besonders erwähnen möchten wir unseren Kollegen Holger Eichelberger sowie aus der Schar von eifrigen Helfern Agathe Weber und Christian Braun.

Würzburg, im Oktober 2004 Gregor Fischer Jürgen Wolff von Gudenberg

Inhaltsverzeichnis

1 Der objektorientierte Ansatz .............................................................................. 1 1.1 Ein einführendes Beispiel ............................................................................. 2 1.1.1 Aktive Objekte ......................................................................................4 1.1.2 Klassifikation ........................................................................................ 4 1.1.3 Datenkapselung ................................................................................... 6 1.2 OOP im Überblick .........................................................................................6 1.3 Programmaufbau ..........................................................................................7 1.4 Java verwenden ......................................................................................... 10 1.4.1 Umgebung ......................................................................................... 11 1.4.2 Syntaxdiagramme .............................................................................. 13 1.4.3 Das Hilfspaket simple ........................................................................ 14 1.5 Gestaltung und Formatierung von Java-Quelltext ...................................... 14

2 Elementare Objekte und Ausdrücke ............................................................... 17 2.1 Behandlung von String Objekten ................................................................17 2.1.1 Zeichenketten als Objekte der Klasse StringBuffer ........................... 18 2.1.2 Zeichenketten als Objekte der Klasse String ..................................... 19 2.1.3 Aufgaben ........................................................................................... 23 2.2 Deklaration von Variablen und Konstanten ................................................ 24 2.3 Namen in Java ............................................................................................27 2.4 Konstruktion von Objekten ......................................................................... 29 2.5 Aufruf von Methoden .................................................................................. 30 2.6 Einfache Ein- und Ausgabe ........................................................................ 31 2.6.1 Ausgabe mit System.out .................................................................... 31 2.6.2 Eingabe über Kommandozeile ...........................................................32 2.6.3 Eingabe mit SimpleInput.in ................................................................ 32 2.6.4 Kommandozeilen-Eingabe mit SimpleInput ....................................... 33 2.6.5 Ausnahmen und Fehler ..................................................................... 33 2.7 Die Zuweisung bei Wert- und Referenzsemantik ....................................... 34 2.7.1 Wert und Referenz .............................................................................34 2.8 Datentypen ................................................................................................. 38 2.8.1 Klassentypen ..................................................................................... 38

X

Inhaltsverzeichnis 2.8.2 Elementare Datentypen ..................................................................... 38 2.8.3 Array-Typen ....................................................................................... 39 2.9 Typ Konversion ...........................................................................................39 2.9.1 Numerische Typanpassung ............................................................... 39 2.10 Numerische Typen ................................................................................... 41 2.10.1 Ganze Zahlen .................................................................................. 42 2.10.2 Realzahlen ....................................................................................... 43 2.10.3 Operatoren im Überblick .................................................................. 45 2.11 Syntax der Ausdrücke .............................................................................. 46 2.11.1 Typspezifische Ausdrücke ............................................................... 46 2.11.2 Numerische Ausdrücke ....................................................................47 2.12 Ganzzahlige Ausdrücke ............................................................................48 2.12.1 Akkumulierende Zuweisungen .........................................................49 2.12.2 Shift-Operationen .............................................................................49 2.13 Reelle Ausdrücke ..................................................................................... 51 2.14 Boolesche Ausdrücke, Bedingungen ........................................................53 2.15 Vergleiche von Werten und Objekten .......................................................55 2.15.1 Vergleich von Zahlen ....................................................................... 55 2.15.2 Objektvergleich ................................................................................ 55 2.16 Der bedingte Ausdruck ............................................................................. 56 2.17 Die Klasse System ................................................................................... 56 2.18 Import von Klassen und Paketen ..............................................................57 2.18.1 Importklausel ................................................................................... 58 2.19 Auftreten von Ausnahmen ........................................................................ 60 2.19.1 Fehlerabbruch ..................................................................................61

3 Vereinbarung neuer Klassen ........................................................................... 63 3.1 Überblick .....................................................................................................64 3.1.1 Attribute ............................................................................................. 64 3.1.2 Methoden ........................................................................................... 65 3.1.3 Konstruktoren .................................................................................... 65 3.2 Entwurf einer Klasse ...................................................................................65 3.3 Klassendeklaration ..................................................................................... 69 3.4 Attributvereinbarung ................................................................................... 71 3.5 Konstruktion von Objekten ......................................................................... 72 3.5.1 Vereinbarung von Konstruktoren ....................................................... 73 3.6 Deklaration von Methoden ..........................................................................75 3.7 Methodenaufruf .......................................................................................... 78 3.8 Redefinition bekannter Methoden ...............................................................79 3.8.1 Objektvergleich .................................................................................. 80 3.9 Aufgaben mit Klassen .................................................................................81 3.9.1 Aufbau und Test von Programmen .................................................... 81 3.10 Klassenattribute ........................................................................................82 3.11 Aufzählungen ............................................................................................85 3.11.1 Aufzählungstypen ............................................................................ 86 3.12 Klassenmethoden .....................................................................................88

Inhaltsverzeichnis

XI

3.13 Arrays als Datenstruktur bzw. -typ ............................................................89 3.13.1 Array-Objekte und ihre Elemente .................................................... 90 3.13.2 Array-Typ und Vereinbarung ........................................................... 91 3.13.3 Mehrdimensionale Arrays ................................................................ 93 3.13.4 Kopieren von Arrays ........................................................................ 94 3.13.5 char-Felder (Zeichenketten) vs. Strings ...........................................94 3.13.6 Mögliche Fehler bei Arrayzugriffen .................................................. 95

4 Algorithmen und Anweisungen ....................................................................... 97 Aufgabentyp 1: Folgenberechnung .................................................................. 97 Aufgabentyp 2: Abbildung, Durchlauf ............................................................... 98 Aufgabentyp 3: Akkumulation, Reduktion .........................................................99 4.1 Iteration .......................................................................................................99 4.1.1 Folgenberechnung ............................................................................. 99 4.1.2 Akkumulation ................................................................................... 103 4.1.3 Durchlaufen einer Menge ................................................................ 104 4.2 Schleifen ...................................................................................................104 4.2.1 while-Schleife ...................................................................................105 4.2.2 do-Schleife ....................................................................................... 105 4.2.3 for-Schleife .......................................................................................106 4.2.4 Allgemeine for-Schleife .................................................................... 108 4.3 Bedingte Anweisungen .............................................................................110 4.4 Funktionen ................................................................................................112 4.5 Rekursion ................................................................................................. 113 4.5.1 Rekursive Algorithmen und Funktionen ........................................... 113 4.5.2 Weitere Beispiele für Rekursion ...................................................... 116 4.6 Fallunterscheidung ................................................................................... 118 4.7 Verlassen von Konstrukten .......................................................................121 4.7.1 return-Anweisung .............................................................................121 4.7.2 Verlassen von Schleifen .................................................................. 122 4.8 Syntaxdiagramm Anweisungen ................................................................ 124

5 Programmieren durch Vertrag ....................................................................... 125 5.1 Abstrakte Datentypen ............................................................................... 127 5.2 Der abstrakte Datentyp Liste .................................................................... 130 5.2.1 Eine Liste ganzer Zahlen ................................................................. 131 5.2.2 Drei Sichten ..................................................................................... 131 5.3 Ein Array als Liste .....................................................................................132 5.3.1 Ringliste ........................................................................................... 134 5.4 Eine verkettete Liste ................................................................................. 135 5.4.1 Einführung ....................................................................................... 135 5.4.2 Veranschaulichung von Listenoperationen ...................................... 137 5.4.3 Implementierung von verketteten Listen .......................................... 138 5.4.4 Diskussion, Kritik ............................................................................. 140 5.5 Eine rekursive Liste .................................................................................. 140 5.5.1 Rekursive Datenstruktur .................................................................. 140

XII

Inhaltsverzeichnis 5.5.2 Rekursive Methoden ........................................................................ 141 5.6 Datenkapselung ........................................................................................142 5.6.1 Öffentliche und geschützte Komponenten ....................................... 142 5.6.2 Klasseninvarianten .......................................................................... 144 5.7 Export ....................................................................................................... 146 5.8 Datenkapselung in Paketen ......................................................................148 5.9 Innere Klassen ..........................................................................................152 5.9.1 Statische Innere Klassen ................................................................. 153 5.10 Interfaces ................................................................................................154 5.10.1 Interfaces als abstrakte Datentypen .............................................. 154 5.10.2 Listen als Interface .........................................................................155 5.10.3 Referenzen auf Schnittstellen. ....................................................... 157 5.10.4 Markierungs-Interface .................................................................... 158 5.11 Import von Interfaces und Konstanten ....................................................159 5.12 Listenaufgaben ....................................................................................... 159 5.12.1 Einführung ..................................................................................... 159 5.12.2 Aufgaben für einfach verkettete Listen .......................................... 159 5.12.3 Aufgaben für doppelt verkettete Listen .......................................... 161 5.13 Funktionsobjekte .................................................................................... 161 5.14 Objekte anonymer Klassen .................................................................... 164 5.15 Iterator .................................................................................................... 165 5.15.1 Ein Listeniterator ............................................................................ 166 5.15.2 Ein Listeniterator im Paket ............................................................. 167 5.15.3 Ein Listeniterator als innere Klasse ............................................... 168 5.15.4 Ein Listeniterator als abstrakte Klasse ...........................................169 5.15.5 Iteratoren in Standardpaketen ....................................................... 171

6 Entwurf von weiterverwertbaren Klassen .....................................................173 6.1 Beispiele zur Vererbung ........................................................................... 174 6.1.1 Spezialisierung ................................................................................ 174 6.1.2 Generalisierung ............................................................................... 175 6.1.3 Hierarchien ...................................................................................... 176 6.1.4 Polymorphie ..................................................................................... 177 6.2 Erweitern von Klassen ..............................................................................179 6.2.1 Regeln und Beispiele ....................................................................... 181 6.3 Untervertrag ..............................................................................................184 6.3.1 Unterverträge ................................................................................... 184 6.4 Die Java Klassenhierarchie ...................................................................... 185 6.4.1 Typanpassung ................................................................................. 186 6.4.2 Allgemeine Listen von Objekten ...................................................... 187 6.5 Erweitern von Interfaces ...........................................................................189 6.6 Polymorphie ..............................................................................................190 6.6.1 Polymorphie ..................................................................................... 190 6.6.2 Dynamisches Binden ....................................................................... 191 6.6.3 Heterogene Listen ........................................................................... 192

Inhaltsverzeichnis

XIII

6.7 Abstrakte Klassen .....................................................................................194 6.8 Aufgaben zur Vererbung .......................................................................... 196

7 Java Standardpakete ...................................................................................... 199 7.1 Hüllklassen ............................................................................................... 200 7.1.1 Konversion mit Strings ..................................................................... 202 7.1.2 Hüllklasse Integer ............................................................................ 202 7.1.3 Hüllklasse Double ............................................................................ 203 7.2 Klonen von Objekten ................................................................................ 203 7.3 Datum ....................................................................................................... 204 7.3.1 Die Klasse Date ............................................................................... 204 7.3.2 Die Klasse GregorianCalendar ........................................................ 205 7.4 Container .................................................................................................. 206 7.4.1 Delegation ........................................................................................206 7.4.2 Listen ............................................................................................... 208 7.4.3 Mengen ............................................................................................ 210 7.4.4 Schlüssellisten, Wörterbücher ......................................................... 211

8 Generische Typen ........................................................................................... 213 8.1 Homogene Standardcontainer ..................................................................214 8.1.1 Listen ............................................................................................... 215 8.1.2 Schlüssellisten, Wörterbücher ......................................................... 216 8.2 Vereinbarung von Typ-parametrisierten Containern ................................ 218 8.2.1 Syntax .............................................................................................. 220 8.3 Parametrisierte Typen .............................................................................. 222 8.3.1 Generizität und Vererbung ...............................................................222 8.3.2 Unbeschränkte Generizität .............................................................. 225 8.3.3 Generizität und Arrays ..................................................................... 226 8.3.4 Typausprägungen ............................................................................ 227 8.4 Generische Methoden .............................................................................. 228 8.4.1 Parametrisierte Parameterlisten ...................................................... 228 8.4.2 Typ-parametrisierte Methoden .........................................................229

9 Ausnahmen ......................................................................................................231 9.1 Ausnahmen .............................................................................................. 231 9.2 Ausnahmebehandlung ..............................................................................232 9.3 Ausnahmeklassen .................................................................................... 234 9.3.1 Ausnahmehierarchie ........................................................................ 234 9.4 Definition eigener Ausnahmen ................................................................. 235 9.4.1 Erweitern der Ausnahmehierarchie ................................................. 235 9.4.2 Auslösen von Ausnahmen ............................................................... 235 9.5 Ausnahmen und Vererbung ......................................................................236

XIV

Inhaltsverzeichnis

10 Ein- und Ausgabe ..........................................................................................237 10.1 Byteweise Ein-/Ausgabe .........................................................................237 10.2 Zeichenweise Ein-/Ausgabe ................................................................... 240 10.3 Serialisierung ..........................................................................................241 10.4 Tokenizer ................................................................................................242 10.5 SimpleInput .............................................................................................243

11 Grafische Benutzeroberflächen ...................................................................247 11.1 Bedienung .............................................................................................. 247 11.2 Programmierung .....................................................................................248 11.3 Komponenten ......................................................................................... 249 11.3.1 Grundkomponenten ....................................................................... 250 11.3.2 Container ....................................................................................... 251 11.4 Ereignisbehandlung ................................................................................252 11.4.1 Ereignis-Hierarchie ........................................................................ 253 11.4.2 Anonyme Beobachterklassen ........................................................ 256 11.5 Layout .....................................................................................................256 11.6 Beispiel ................................................................................................... 258

12 Applets ........................................................................................................... 263 12.1 JApplet ....................................................................................................266

13 Eigenständige Handlungsstränge ............................................................... 267 13.1 Konstruktion ............................................................................................268 13.2 Monitore ..................................................................................................269 13.3 Kommunikation .......................................................................................271

Anhang Anhang A: Das Java2 Software Development Kit ...........................................273 A.1 Installation ................................................................................................ 273 A.2 Der Compiler ............................................................................................ 275 A.3 Der Interpreter ..........................................................................................276 A.4 Das Werkzeug jar .....................................................................................277 A.5 Dokumentation erzeugen mit javadoc ......................................................278

Anhang B: Das interaktive Lernsystem ........................................................... 279 B.1 Das gedruckte Buch .................................................................................279 B.1.1 Präsentation des Textes ..................................................................279 B.1.2 Beispiele .......................................................................................... 280

Inhaltsverzeichnis

XV

B.1.3 Aufgaben ......................................................................................... 280 B.2 Das Buch als HTML-Version ....................................................................281 B.2.1 Navigation ....................................................................................... 281 B.2.2 Java API Dokumentation ................................................................. 281 B.2.3 Beispiele .......................................................................................... 281 B.2.4 Aufgaben ......................................................................................... 282 B.3 JEEE ........................................................................................................ 282 B.3.1 Installation ....................................................................................... 282 B.3.2 Ausführen ........................................................................................ 282 B.3.3 Überprüfen ...................................................................................... 284 B.4 Die Bibliothek simple ................................................................................285 B.4.1 simple.io .......................................................................................... 285 B.4.2 simple.util ........................................................................................ 286 B.5 Entwicklungswerkzeuge ...........................................................................287 B.5.1 J2SDK ............................................................................................. 287 B.5.2 Eclipse ............................................................................................. 287 B.5.3 Dokumentation ................................................................................ 288

Anhang C: Syntax im Überblick ........................................................................289 C.1 Die gesamte Sprache .............................................................................. 289 C.1.1 Vereinbarungen ...............................................................................290 C.1.2 Anweisungen ...................................................................................293 C.1.3 Ausdrücke ....................................................................................... 294 C.1.4 Namen .............................................................................................300 C.1.5 Weitere Syntax ................................................................................ 301 C.2 Java 1.5 Erweiterungen ........................................................................... 303 C.2.1 Generische Typen ........................................................................... 303 C.2.2 Anweisungen ...................................................................................306 C.2.3 Weitere Änderungen ....................................................................... 306

Anhang D: Gestaltung und Formatierung von Java-Quelltext ...................... 307 D.1 Struktur .................................................................................................... 307 D.2 Kommentare ............................................................................................ 308 D.3 Formatierung ............................................................................................312 D.4 Deklarationen ...........................................................................................315 D.5 Anweisungen ........................................................................................... 316

Anhang E: Lösungen der Aufgaben .................................................................319

Tabellenverzeichnis

Tabelle 1: Konstruktoren der Klasse StringBuffer .................................................. 20 Tabelle 2: Zugriffsmethoden der Klasse StringBuffer ............................................ 21 Tabelle 3: Modifikationsmethoden der Klasse StringBuffer ................................... 21 Tabelle 4: Konstruktoren der Klasse String ............................................................21 Tabelle 5: Zugriffsmethoden der Klasse String ...................................................... 22 Tabelle 6: Vergleichsmethoden der Klasse String ................................................. 22 Tabelle 7: Pseudo-Modifikationsmethoden der Klasse String ................................22 Tabelle 8: Konversionsmethoden von elementaren Datentypen zu String ............ 22 Tabelle 9: Wichtige Begriffe zu Wert- und Referenz-Semantik ..............................37 Tabelle 10: Die elementaren Datentypen ...............................................................39 Tabelle 11: Ganzzahlige Typen ............................................................................. 42 Tabelle 12: Gleitkomma-Typen .............................................................................. 43 Tabelle 13: Gültige Gleitkommakonstanten ........................................................... 44 Tabelle 14: Ungültige Gleitkommakonstanten ....................................................... 44 Tabelle 15: Operator-Prioritäten in Java ................................................................ 45 Tabelle 16: Shifts ................................................................................................... 50 Tabelle 17: Methoden von java.lang.Math ............................................................. 53 Tabelle 18: Operatoren .......................................................................................... 53 Tabelle 19: Standarddatenströme in der Klasse System ....................................... 57 Tabelle 20: Ausnahmen bei Arrays ........................................................................ 95 Tabelle 21: Listendurchlauf, Iterator .....................................................................104 Tabelle 22: Zugriffsmethoden für eine rekursive Liste ......................................... 115 Tabelle 23: ADT "komplexe Zahlen": Mathematische Sicht ................................. 128 Tabelle 24: ADT "komplexe Zahlen": Funktionale Sicht .......................................128 Tabelle 25: ADT "komplexe Zahlen": Objektorientierte Sicht ............................... 129 Tabelle 26: Operationen für ADT komplexe Zahlen .............................................130 Tabelle 27: Operationen für ADT Liste .................................................................131 Tabelle 28: Zugriffsrechte .................................................................................... 148 Tabelle 29: Operationen für ADT Liste .................................................................155 Tabelle 30: Iterator-Schnittstelle .......................................................................... 166 Tabelle 31: Java Standardpakete ........................................................................ 199 Tabelle 32: Hüllklassen ........................................................................................ 200 Tabelle 33: Operationen für Listen-Container ...................................................... 209 Tabelle 34: Operationen für Listen-Iteratoren ...................................................... 209 Tabelle 35: Operationen für Schlüssellisten .........................................................211 Tabelle 36: Operationen für Listen-Container ...................................................... 216 Tabelle 37: Operationen für Schlüssellisten .........................................................217 Tabelle 38: Einige wichtige Methoden einer Komponente ................................... 250 Tabelle 39: Listener und ihre Methoden zur Ereignisbehandlung ........................254 Tabelle 40: Arithmetische binäre Operatoren ...................................................... 295

XVIII

Tabellenverzeichnis

Tabelle 41: Boolesche Operatoren ...................................................................... 298 Tabelle 42: Zuweisungsoperatoren ......................................................................299 Tabelle 43: Javadoc Tags .................................................................................... 309

Abbildungsverzeichnis

Abb. 1: Beziehungen zwischen Klassen .................................................................. 3 Abb. 2: Beziehungen zwischen Objekten .................................................................4 Abb. 3: Klassifikation von Musikmedium ..................................................................5 Abb. 4: Klassifikation von Geräten ........................................................................... 5 Abb. 5: Aufbau einer Klasse .....................................................................................8 Abb. 6: Die Klasse TestPreis1 verwendet die Klasse Preis1 ................................... 9 Abb. 7: Die Klassen TestPreis1 und Preis1 in einem Paket .................................... 9 Abb. 8: Importieren von Klassen ............................................................................ 10 Abb. 9: Entwicklungszyklus eine Java Programms ................................................12 Abb. 10: Objektsicht: StringBuffer (Schritt 1) ......................................................... 19 Abb. 11: Objektsicht: StringBuffer (Schritt 2) ......................................................... 19 Abb. 12: Objektsicht: String ....................................................................................20 Abb. 13: Objektsicht: Referenzsemantik bei StringBuffer (Schritt 1) ......................35 Abb. 14: Objektsicht: Referenzsemantik bei StringBuffer (Schritt 2) ......................35 Abb. 15: Objektsicht: Referenzsemantik bei StringBuffer (Schritt 3) ......................36 Abb. 16: Objektsicht: Wertsemantik bei int (Schritt 1) ............................................36 Abb. 17: Objektsicht: Wertsemantik bei int (Schritt 2) ............................................36 Abb. 18: Objektsicht: Wertsemantik bei int (Schritt 3) ............................................36 Abb. 19: Zuweisungskompatibilität primitiver Datentypen ......................................40 Abb. 20: Array ........................................................................................................ 90 Abb. 21: Quadratwurzel ....................................................................................... 100 Abb. 22: Rekursive Berechnung von Potenzen ................................................... 115 Abb. 23: Array Liste ..............................................................................................132 Abb. 24: Listen-Element in der Objektsicht .......................................................... 136 Abb. 25: Verkettete Liste in der Objektsicht ......................................................... 136 Abb. 26: Listen-Element in der Klassensicht ........................................................136 Abb. 27: Verkettete Liste in der Klassensicht .......................................................136 Abb. 28: Anhängen eines Elements an das Ende der verketteten Liste .............. 137 Abb. 29: Rekursive Liste ...................................................................................... 141 Abb. 30: Listeniterator .......................................................................................... 167 Abb. 31: Spezialisierung von CDs ........................................................................174 Abb. 32: CD und MC ohne Generalisierung .........................................................176 Abb. 33: CD und MC mit Generalisierung ............................................................176 Abb. 34: Hierarchie der Musikmedien .................................................................. 177 Abb. 35: Gerätehierarchie .................................................................................... 178 Abb. 36: Musikmedien-Hierarchie ........................................................................ 185 Abb. 37: Klassenhierarchie einiger Standardklassen ...........................................185 Abb. 38: Klassen- und Interfacehierarchie zu List ................................................188 Abb. 39: Zahlungsmittel ....................................................................................... 197 Abb. 40: Kopieren eines Objekts ..........................................................................203

XX

Abbildungsverzeichnis

Abb. 41: Klonen eines Objekts .............................................................................203 Abb. 42: Klassen- und Interfacehierarchie zu List ................................................208 Abb. 43: Hierarchie rund um Map ........................................................................ 211 Abb. 44: Listen-Hierarchie mit generischen Typen .............................................. 215 Abb. 45: Map-Hierarchie mit generischen Typen .................................................217 Abb. 46: Ausnahmehierarchie ..............................................................................234 Abb. 47: Hierarchie zu InputStream ..................................................................... 238 Abb. 48: Hierarchie zu OutputStream .................................................................. 238 Abb. 49: Hierarchie zu Reader .............................................................................240 Abb. 50: Hierarchie zu Writer ............................................................................... 240 Abb. 51: Swing Grundkomponenten .................................................................... 249 Abb. 52: AWT und Swing Container .................................................................... 251 Abb. 53: Ereignis-Hierarchie ................................................................................ 253 Abb. 54: Screenshot zu LayoutMix ...................................................................... 257 Abb. 55: Lebenszyklus eines Applets .................................................................. 263 Abb. 56: Appleterstellung und -verwendung ........................................................ 264 Abb. 57: Screentshot vom Applet TextEvent ....................................................... 264 Abb. 58: Zustände von Thread .............................................................................269 Abb. 59: JEEE ......................................................................................................283 Abb. 60: JEEE-Konsole ........................................................................................283 Abb. 61: JEEE-Konsole mit Funktionstest ........................................................... 284 Abb. 62: Klassen in simple.io ............................................................................... 286

Kapitel 1 Der objektorientierte Ansatz

Programme unterstützen uns im täglichen Leben in fast allen Bereichen. Ob es die programmgesteuerte Waschmaschine oder das Handy mit Internet-Zugang, die Kasse im Supermarkt oder die Verwaltung von Adresslisten auf einem elektronischen Assistenten, das Erstellen und Anzeigen von Bildern und ganzen Filmen oder der Autopilot im Flugzeug ist, Programme werden eingesetzt, um Probleme zu lösen oder Aufgaben zu erfüllen. Ein Programm steuert, regelt und verwaltet. Dazu verwendet es Modelle realer Objekte, stellt sie dar, überträgt sie, vermittelt zwischen unterschiedlichen Anwendungen. Ein Versuch diese unterschiedlichen Aspekte in einem Satz zusammenzufassen, führt zu folgender Aussage: Die Ausführung eines Programms ist die Simulation eines Modells der realen oder einer imaginären Welt. Programmieren bedeutet demzufolge die Modellierung einer Situation und die Umsetzung des Modells in ein ausführbares Programm. Der Weg von einem Problem zu einem lauffähigen Programm gliedert sich in mehrere Phasen, die durchaus verschiedener Natur sein können und an deren letzter Stelle erst die eigentliche Programmierung steht. Wir unterscheiden drei Hauptphasen: 1. Analyse des Problems, 2. Programmentwurf und 3. Implementierung in einer Programmiersprache Wir betonen in diesem Tutorial natürlich den letzten Punkt, werden aber stets darauf bedacht sein, auch Modellierungsfragen zu besprechen, weil in zutreffender Analyse und geschicktem Entwurf der Schlüssel zum erfolgreichen Programm liegt. Die Terminologie, in der man das Problem analysiert, die also aus dem Anwendungsbereich stammt und noch mehr die, in der man das Programm entwirft, spiegelt sich im objektorientierten Fall oft direkt in der Programmiersprache wider.

2

Kapitel 1: Der objektorientierte Ansatz

Man kommt relativ schnell zu ausführbaren Prototypen, die dann erst nach und nach zum endgültigen Programm verfeinert werden. Je mehr Begriffe aus der Welt des Anwenders, der das Problem gestellt hat und das Programm später benutzen will, verwendet werden, umso leichter wird der Dialog zwischen Programmierer und Anwender. Das entbindet natürlich nicht von der notwendigen mathematischen oder strukturellen Aufbereitung des Modells. Objektorientierter Entwurf und Programmierung verwenden Konzepte wie das Vergeben von Aufträgen, das Verbergen von überflüssiger Detailinformation und die Interaktion eigenständiger "Wesen". Die Ausdrucks- und Denkweise aus dem Bereich der Anwendung lassen sich besser als bei der traditionellen Programmierung in das Programm übertragen.

1.1 Ein einführendes Beispiel Beispiel 1: Unterhaltungselektronik

Unsere Freizeitbeschäftigung wird heute mehr und mehr von unterschiedlichen elektronischen Geräten bestimmt. Computerspiele, Audio-CDs, Musikkassetten (MCs) usw. sind in fast jedem Haushalt zu finden. Etwas Ordnung in diese Vielfalt zu bringen soll unser Ziel sein. Beschränken wir uns auf einen kleinen Ausschnitt: Ein Walkman spielt eine Kassette ab. Dass er dazu immer gehen muss, ist sein Problem. Bequemer hat es der Discman, der natürlich Audio-CDs erklingen lässt. Noch nicht ganz erwachsen ist hingegen der Gameboy für Computerspiele.

Objekte sind aktiv Wir unterscheiden also zwischen aktiven Objekten (Männern und Jungen), die etwas tun, und passiven Objekten (CDs, MCs und Spiele), die unterschiedliche Inhalte darstellen. Diese Unterscheidung ist ganz hilfreich, aber letztlich können auch die passiven Medien künstlich aktiviert werden, indem sie etwa mitzählen, wie oft sie schon aufgelegt wurden oder zumindest Auskunft darüber geben, welches Gerät sie benötigen. So kommen wir der weit verbreiteten Sicht eines objektorientierten Programms als eine Menge kommunizierender Objekte schon nahe. Ein Discman erhält eine CD und spielt diese ab. Er reagiert darauf durch seine ihm angemessene Verhaltensweise. Die Objekte können also agieren, sie kennen gewisse Methoden, die ihr Verhalten bestimmen. Diese Methoden sind allen gleichartigen Objekten zu eigen, alle Audio-CDs können von einem Discman gespielt werden, jedoch nicht von einem Walkman.

1.1 Ein einführendes Beispiel

3

Objekte gehören zu Klassen Jedes Objekt wird als ein Exemplar einer Klasse aufgefasst, jede CD ist ein individuelles Exemplar der Klasse aller CDs. Neben den Methoden verfügen die Objekte einer Klasse auch über gemeinsame Attribute, die ihren Zustand beschreiben, z.B. kann eine CD noch original verpackt, noch nie gespielt oder schon oft gehört sein. Dieser Zustand eines Objektes beeinflusst die Verhaltensweise, indem abhängig von seinem Wert gewisse Methoden aufgerufen werden können, oder andere unterschiedlich reagieren. Eine Klasse legt also nicht nur Methoden fest, sondern auch Eigenschaften oder Attribute, deren Werte für jedes individuelle Objekt verschieden sein können. Objekte haben eine Identität, auch solche mit identischen Attributwerten können unterschieden werden. Objekte werden im Verlauf eines Programms geschaffen, konstruiert. Bei der Konstruktion werden einige Attribute gesetzt, andere kommen während des Programmlaufs hinzu. Normalerweise kann der Wert eines Attributes verändert werden. Beziehungen Objekte stehen in Beziehungen zueinander. Wenn wir für eine Klasse ein rechteckiges Bild wählen, in dem der Klassenname, die Attribute und die Methoden aufgeführt sind, so lässt sich folgendes Diagramm (UML Klassendiagramm) zeichnen.

Abbildung 1: Beziehungen zwischen Klassen

Auch Objekte werden durch ein Rechteck dargestellt, auf welches ein mit dem Objektnamen bezeichneter Pfeil verweist. Das Szenario, dass eine Karajan CD, ein Exemplar der Klasse CD, dessen Attribut interpret den Wert "Karajan" besitzt, auf einem Discman der Marke Headache und eine Kassette von Queen gleichzeitig auf einem Walkman der Marke Marathon laufen, wird durch folgendes Bild veranschaulicht.

4

Kapitel 1: Der objektorientierte Ansatz

Abbildung 2: Beziehungen zwischen Objekten

1.1.1 Aktive Objekte Ganz wichtig im objektorientierten Programmieren ist die Idee, dass alle Aktivität von Objekten ausgeht. Im angeführten Beispiel mit der Unterhaltungselektronik lässt sich das noch relativ gut nachvollziehen. Beispiel 2: aktive Objekte

Im obigen Bild spielt ein Discman mit dem Namen discy die CD mit dem Namen cd1. Das lässt sich unmittelbar als Java Fragment schreiben. discy.spiele(cd1);

1.1.2 Klassifikation Die Klassifikation der Objekte in verschiedene Klassen, kann noch weiter getrieben werden. So lassen sich CDs und MCs zum Oberbegriff Musikmedium zusammen fassen. Auch die Tatsache, dass beide gleich lautende Attribute besitzen, regt zu dieser Verallgemeinerung an.

1.1 Ein einführendes Beispiel

5

Abbildung 3: Klassifikation von Musikmedium

Dieses Bild sollte wie folgt gelesen werden: Jede CD und jede MC ist ein Musikmedium und hat deshalb ein Attribut, welches den Interpret bezeichnet. Eine MC kann gelöscht werden, eine CD nicht. Diese Ist-ein Beziehung bezeichnet man im objektorientierten Programmieren als Vererbung1 Eine MC hat und kann alles, was ein Musikmedium kann und bietet darüber hinaus noch die Methode istLeer() an. Ebenso gehören alle Abspielgeräte zu einer Klasse, Kategorie oder Hierarchie. Jedes Gerät besitzt ein Attribut, welches den Firmennamen repräsentiert und eine Abspielmethode. Die wird allerdings erst dann festgelegt, falls klar ist, um was für ein Gerät es sich handelt. Es ist also nicht sinnvoll Objekte der Klasse Gerät anzulegen, denn wir wissen nicht wie das Abspielen funktioniert. Die Klasse Gerät ist abstrakt2. Trotzdem ist es möglich und sinnvoll etwa eine Liste verschiedener Geräte zu verwalten, von denen jedes eine spezifische Abspielmethode besitzt. Jedes Gerät verhält sich also seinem Typ gemäß3.

Abbildung 4: Klassifikation von Geräten

1

Siehe auch: Vererbung (S. 173) Siehe auch: Abstrakte Klassen (S. 194) 3 Siehe auch: Polymorphie (S. 189) 2

6

Kapitel 1: Der objektorientierte Ansatz

1.1.3 Datenkapselung Ein weiteres Merkmal der objektorientierten Vorgehensweise ist die konsequente Umsetzung der Datenkapselung. Darunter versteht das Verbergen von unnötigen Detailinformationen. So ist es für den Aufruf einer Methode nicht wichtig, die Implementierung genau zu kennen. Die Wartbarkeit und Lesbarkeit eines Programms wird erhöht, wenn für das Ändern von Attributen Zugriffsrechte vergeben werden. Am einfachsten ist die Regel, dass im Sinne der Eigenverantwortung der Objekte, Attributwerte nur von dem Objekt selbst verändert werden sollten. Die Attribute interpret und marke sollten in unserem Beispiel nur bei der Konstruktion gesetzt werden. Wenn ein Computerspiel hingegen mitzählen soll, wie oft es bereits aufgerufen wurde, so ist ein schreibender Zugriff auf das Attribut anzahl eigentlich nur im Zusammenhang mit dem Abspielen erwünscht. Das Attribut wird gekapselt.

1.2 OOP im Überblick Wir stellen die wesentlichen Aspekte des objektorientierten Ansatzes zusammen: • Objekte sind eigenständige Einheiten die einen Zustand haben. • Der Zustand wird bestimmt durch die Werte der Attribute und wird nur durch das Objekt selbst verändert. • Objekte werden von anderen Objekten durch Botschaften zur Ausführung von Zustandsänderungen angeregt. • Objekte sind Exemplare einer Klasse, die gemeinsame Attribute und Aktionen zusammenfasst. • Die genaue Datenstruktur und die Wirkungsweise der Methoden sind gekapselt. • Klassen stehen in Beziehung zueinander, es gibt Unterklassen und Oberklassen. • Eine Unterklasse unterscheidet sich von ihrer Oberklasse dadurch, dass sie mehr Merkmale aufweist, also eine Spezialisierung und damit eine Teilmenge beschreibt. • Die Unterklasse übernimmt oder erbt alle Attribute und Methoden der Oberklasse. • Sie kann die ererbten Methoden modifizieren und neue definieren. • Es gibt abstrakte Klassen, von denen keine Objekte ausgeprägt werden können. • Ein Objekt reagiert entsprechend seinem zur Laufzeit festgelegten Typ. Die Vererbung von Methoden und Attributen ist ein wesentliches Merkmal von objektorientierter Programmierung.

1.3 Programmaufbau

7

Ein weiteres wichtiges Merkmal ist die Möglichkeit wiederverwendbaren, weiterverwertbaren Programmcode zu erstellen. Dabei kann durch das Klassenkonzept der auf dem Rechner zur Verfügung stehende Vorrat an Datenstrukturen und Operationen ständig erweitert werden. Es ist in einer gut ausgestatteten Programmierumgebung nicht mehr nötig, das Rad zum x-ten Male neu zu erfinden, sondern man kann komplexe Strukturen und Operationen wie einfache Standardtypen benutzen. Um bei unserem Beispiel zu bleiben, kann man sagen, dass es nun möglich ist, ein Gerät aus fertigen Modulen zusammenzusetzen, ohne sich im Einzelnen um deren Aufbau kümmern zu müssen. Entsprechend vereinfacht sich die Wartung. Die objektorientierte Programmierung unterstützt sowohl die Anwendung von fertigen Programmbausteinen als auch deren Erstellung. Sie unterstützt modulares Vorgehen.

1.3 Programmaufbau Ein objektorientiertes Programm konstruiert (erzeugt) Objekte, die dann miteinander kommunizieren, indem sie eigene Methoden (Handlungsvorschriften) ausführen oder solche anderer Objekte aufrufen. In Java wird dieser Ablauf durch Aufruf der Methode main der Startklasse angestoßen. Das Programm selbst, der Programmtext oder Quelltext, besteht aus einer Reihe von Klassenvereinbarungen. Eine dieser Klassen, die Startklasse enthält eine Methode main. Das einfachste Programm ist also eine Klassenvereinbarung, die nur die Methode main enthält. Beispiel 3: Programmschablone public class Schablone { public static void main(String[] args) { // Hier wuerden die Anweisungen stehen ... } } cdrom:/buch/examples/java/Aufbau/Schablone.java

Dieses Programm tut nichts. Wir wollen an dieser Stelle die Bedeutung der einzelnen Wörter nicht erklären und fassen dieses Beispiel als feste Schablone auf, in der der Name der Klasse und der Rumpf der Methode main verändert werden, um ein neues Programm zu erhalten. Der Rumpf der Methode main ist hier durch einen Kommentar ersetzt. Kommentare dienen zur Dokumentation und Erläuterung des Quelltextes und werden bei der Verarbeitung ignoriert. Kommentare können grundsätzlich an allen Stellen im Quellcode stehen, solange sie dort eindeutig als Kommentare zu identifizieren sind, also z.B. nicht innerhalb von Zeichenketten oder Namen.

8

Kapitel 1: Der objektorientierte Ansatz

Es gibt drei Arten von Kommentaren: • Zeilenend-Kommentare: Beginnen mit // und enden am Zeilenende. Sie werden üblicherweise zum Erläutern von einzelnen Anweisungen verwendet. • Block-Kommentare: Stehen zwischen /* und */. Sie dienen meist zum Deaktivieren von Code-Bereichen, und werden selten zur Dokumentation verwendet. • Javadoc-Kommentare: Wie Block-Kommentare, beginnen jedoch mit /**. Sie werden für die aus dem Quellcode automatisch erzeugte Dokumentation verwendet und stehen daher normalerweise vor Konstrukten, die für die Verwendung des Codes durch andere Programme (oder Programmteile) geeignet sind, wie Klasse, Methoden oder Konstanten. Die JavadocKommentare haben eine interene Struktur, die bei den Quelltext-Konventionen4 beschrieben ist. Beispiel 4:

Das folgende Programm gibt das Produkt von zwei Zahlen aus: public class Produkt { public static void main(String[] args) { int faktor1 = 2; int faktor2 = 3; System.out.println(faktor1 * faktor2); } } cdrom:/buch/examples/java/Aufbau/Produkt.java

Ein einfaches Programm lässt sich durch folgendes Bild veranschaulichen: Wir zeichnen eine Klasse als ein Rechteck mit drei Fächern. Im ersten steht der Klassenname, im zweiten die Attribute und im dritten die Methoden.

Abbildung 5: Aufbau einer Klasse

Üblicherweise besteht ein Programm aus mehreren Klassen, von denen jede in einer eigenen Datei gespeichert wird. Beispiel 5: Die Klasse Preis1

Als Beispiel eines einfachen Programms vereinbaren wir eine Klasse, die einen Preis in Euro und Cent repäsentiert und eine Methode besitzt, die diesen Preis in Cent ausliest. Eine Testklasse konstruiert ein Preis-Objekt und ruft die Methode auf. Außerdem gibt es eine Methode, die einen Preis um einen anderen erhöht. 4

Siehe auch: Quelltext-Konventionen (S. 308)

1.3 Programmaufbau

9

Abbildung 6: Die Klasse TestPreis1 verwendet die Klasse Preis1

System.out.println(p1.getPreisInCent()); // p1 wird um einen Preis p2 erhoeht p1.erhoehe(p2); System.out.println(p1.getPreisInCent()); cdrom:/buch/examples/java/Klasse/TestPreis1.java

Wir zeigen vom Programm hier nur den Methodenaufruf. Zusammengehörige Klassen werden in Paketen gruppiert. Der Paketname entspricht dabei einem Verzeichnis- (Ordner-)Namen im Dateisystem.

Abbildung 7: Die Klassen TestPreis1 und Preis1 in einem Paket

Die Beliebtheit und schnelle Verbreitung von Java wurde wesentlich durch eine Vielzahl vorhandener Standardpakete5 erhöht. Man kommt eigentlich nicht ohne fremde Pakete aus. So werden wesentliche Teile der Ein- und Ausgabe oder auch häufig gebrauchte Datentypen in Standardpaketen zur Verfügung gestellt. Der Import von Standard- oder selbst geschriebenen Paketen ist möglich und geschieht durch Angabe einer import- Klausel. Nur das Paket java.lang, welches die wichtigsten Standardklassen wie z.B. String und Math enthält, steht ohne Import zur Verfügung. Wir wollen hier nicht ins Detail6 gehen. Wir weisen jedoch darauf hin, dass wir für dieses Tutorial ein Paket simple bereit stellen, welches zwei Einzelpakete enthält.

5 6

Siehe auch: Standardpakete (S. 199) Siehe auch: Import von Paketen (S. 57)

10

Kapitel 1: Der objektorientierte Ansatz Beispiel 6: Import von Klassen

Wir importieren die Klasse SimpleInput aus dem Paket simple.io und die Klasse ArrayList aus dem Standardpaket java.util. Zur Veranschaulichung verwenden wir folgendes Bild:

Abbildung 8: Importieren von Klassen

Ein Name für das aktuelle Paket ist nicht notwendig.

1.4 Java verwenden Java wurde von Sun Microsystems als eine Plattform-unabhängige, überall in gleicher Weise einsetzbare, objektorientierte Sprache entwickelt. Gleichzeitig mit dem Vorliegen der ersten Sprachdefinition 1995 wurde auch ein Entwicklungssytem (JDK - Java Development Kit) und eine umfangreiche Sammlung von Paketen oder Programmbibliotheken zur freien Verfügung gestellt. Diese Tatsache und die Möglichkeit, Programme einfach über das Internet aufzurufen, trugen zusammen mit dem allgemeinen Vormarsch der Objektorientierung zur rasanten Verbreitung bei. Da Java eine neue, rein objektorientierte Sprache ist, die auf Erfahrungen zurück greift, die mit C++ gemacht wurden, können wir Java als eine fortgeschrittene und fortschrittliche Sprache bezeichnen. Das aktuelle Java (offiziell Java 2 genannt) unterteilt sich in zwei Komponenten. Das J2RE - Java 2 Runtime Environment ist für die Ausführung von Programmen zuständig. Will man also ein Java Programm verwenden, so ist diese Komponente notwendig und ausreichend. Darüber hinaus gibt es zu Java noch eine ganze Reihe von Werkzeugen für die Erstellung von Java-Programmen, wie z.B. den Java-Compiler (javac), der den Programm-Quelltext in Bytecode übersetzt, welcher dann vom J2RE verwendet werden kann. Diese Werkzeuge sind selbst teilweise wieder Java-Programme und be-

1.4 Java verwenden

11

nötigen daher zur Ausführung das J2RE. Daher werden die Werkzeuge zusammen mit dem J2RE als J2SDK - Java 2 Software Development Kit zusammengefasst. Darüber hinaus gibt es Java noch in unterschiedlichen Varianten für verschiedene Gruppen von Zielgeräten. Normalerweise verwendet man die J2SE - Java 2 Standard Edition, jedoch stehen z.B. für stark eingeschränkte Endgeräte wie Handys oder PDAs die J2ME - Java 2 Micro Edition oder für Firmenanwendungen die J2EE - Java 2 Enterprise Edition zur Verfügung. Wir wollen uns hier lediglich mit der J2SE befassen, grundsätzlich lassen sich die vorgestellten Konzepte und Verfahren jedoch auch auf die anderen Platformen übertragen. Auf der beiliegenden CD finden sich auch die aktuelle Version (1.4.2) des J2SDK für Windows und Linux. Diese und neuere Versionen, wie z.B. die sich zur Zeit in Vorbereitung befindliche Version 1.5, können auch von der Java-Homepage7 heruntergeladen werden. Details zur Installation findet man im Anhang8 oder bei Sun9.

1.4.1 Umgebung Java Programme können in unterschiedlichen Umgebungen erstellt werden. Diese stellen drei Grundfunktionen zur Verfügung: • Eingeben bzw. Ändern (Editieren) des Quellcodes, • Übersetzen (Compilieren) des Quellcodes in Bytecode, • und Ausführen (Interpretieren) des Bytecodes. Die einfachste Umgebung ist die direkte Verwendung der mit dem J2SDK gelieferten Werkzeuge, wobei diese den ersten Schritt nicht explizit unterstützen. Das ist auch nicht nötig, denn zum Bearbeiten des Quellcodes ist generell jeder Editor geeignet. Es ist jedoch von Vorteil, wenn der Editor einen speziellen Modus für Java besitzt, so dass mindestens automatisches Einrücken und Syntax-Highlighting (Hervorherben von syntaktischen Elementen z.B. durch Farben) möglich ist. Das Übersetzen des Quellcodes in Bytecode erfolgt mit dem Programm javac. Javac ist der Compiler des J2SDK. Er übersetzt den Quellcode (z.B. Hello.java) in den "ausführbaren" Bytecode (Hello.class). Der erzeugte Bytecode kann dann auf jedem System, für das Java verfügbar ist, von der Virtuellen Maschine (JVM - Java Virtual Machine) ausgeführt werden. Die JVM wird mit dem Kommando java gestartet. Folgender Ablauf beschreibt den typischen Vorgang bei der Erstellung eines JavaProgramms:

7

http://java.sun.com/j2se/downloads.html Siehe auch: Installation des J2SDK (S. 273) 9 http://java.sun.com/j2se/1.4.2/install.html 8

12

Kapitel 1: Der objektorientierte Ansatz

Abbildung 9: Entwicklungszyklus eine Java Programms

Dieser wird üblicherweise in Zyklen wiederholt, bis das Programm das gewünschte Ergebnis liefert. Der Vorteil dieses Verfahrens liegt darin, dass es gleichermaßen auf praktisch allen Systemen verwendet werden kann, für die ein J2SDK zur Verfügung steht. Weiterhin behält man die volle Kontrolle darüber, was passiert, und kann die Ergebnisse (und Fehler) direkt beobachten. Zur Benutzung des fertigen Programm reicht es aus, die .class-Dateien (z.B. als Archiv) zu verteilen. Diese können dann auf allen Systemen, auf denen eine JVM zur Verfügung steht, ausgeführt werden. Details zur Verwendung des Java-Compilers und zum Aufruf der JVM finden sich wiederum im Anhang10. Statt des manuellen Aufrufs der Werkzeuge kann man auch sogenannte Integrierte Entwicklungsumgebungen (IDE - Integrated Development Environment) verwenden. Diese bieten neben herkömmlichen Editorfunktionen zusätzliche Unterstützung wie z.B. eingebaute Hilfe, automatische Vervollständigung, Fehlerkorrektur und vieles mehr. Das ist zwar prinzipiell ein großer Vorteil, birgt aber gerade für Einsteiger auch die Gefahr der Informationsüberflutung. Solche Entwicklungsumgebungen gibt es in großer Zahl sowohl im kommerziellen als auch im Open Source Umfeld. Auf der CD ist die aktuelle Version von Eclipse für Windows und Linux enthalten. Es sei noch darauf hingewiesen, dass auch die meisten Entwicklungsumgebungen das J2SDK benötigen. Sie bringen meist keinen eigenen Compiler oder gar eine eigene JVM mit. Die Installation des J2SDK ist also auch beim Einsatz einer IDE meist nötig. Damit die Beispiele dieses Tutorials möglichst einfach ausprobiert werden können, haben wir ein Programm in die HTML-Version integriert, die es erlaubt die Beispiele direkt zu starten. Das Programm JEEE ermöglicht es die Beispiele auszuführen (mit Benutzerinteraktion, sofern das vom Beispiel vorgesehen ist) oder diese auch zu verändern. Voraussetzung ist lediglich die Installation des J2SDK.

10

Siehe auch: J2SDK (S. 273)

1.4 Java verwenden

13

Details zum Setup und der Verwendung von JEEE finden sich wiederum im Anhang11.

1.4.2 Syntaxdiagramme Eine Programmiersprache hat feste syntaktische Regeln. Diese sind meist nicht einfach durch Text zu beschreiben, und eine formale textuelle Beschreibung ist nicht gut zu verstehen. Daher verwenden wir Syntaxdiagramme zur Beschreibung. Syntaxdiagramme definieren Schablonen, die durch Ausfüllen zu fertigen Programmen (oder Programmteilen) führen. Folgendes Diagramm zeigt einige der wichtigsten Eigenschaften:

Die abgerundeten Elemente sind sogenannte Terminale, die einen festen, exakt so in das Programm zu übernehmenden Text darstellen. Die eckigen Elemente sind die Nicht-Terminale, die durch etwas anderes ersetzt werden müssen. Was das genau ist, ist jeweils an anderer Stelle beschrieben, die Bezeichnung gibt aber einen Hinweis. Im obigen Bild ist also die "0" ein Terminal, und "1..9" bzw. "0..9" Nicht-Terminale (diese wären dann gemäß einem anderen Syntaxdiagramm durch die entsprechenden Ziffern zu ersetzen). Syntaxdiagramme werden von links oben, den durchgezogenen Linien folgend, von links nach recht und von oben nach unten durchlaufen. Gestrichelte Linien werden in entgegengesetzter Richtung durchlaufen. Kann man an einer Stelle mehreren Wegen folgen, so ist einer davon zu wählen. Erreicht man das Ende der Linie (ganz rechts) so ist dieses Syntaxdiagramm vollständig durchlaufen. Bei Durchlaufen des obigen Diagramms beginnt man nun links oben, und muss sich dann gleich entscheiden. Man kann der Linie nach rechts folgen, dann kommt man an das Terminal "0". Folgt man der Linie dann weiter, so kommt man unweigerlich an das Ende des Diagramms (an der Kreuzung darf man ja nur nach rechts, da nur gestrichelte Linien nach links durchlaufen werden). Eine Dezimalzahl kann also eine einzelne "0" sein.

11

Siehe auch: JEEE (S. 282)

14

Kapitel 1: Der objektorientierte Ansatz

Alternativ kann man anfangs auch der Linie nach unten folgen. Dann kommt man an das Nicht-Terminal "1..9" welches für eine der Ziffern 1 bis 9 steht. Dannach kann man der Linie weiter bis ans Ende folgen. D.h. die Ziffern 1 bis 9 sind auch "Dezimalzahlen". Statt bis an das Ende des Diagramms zu gehen kann man aber auch die Abzweigung nach unten nehmen. Hier kommt man an das Nicht-Terminal "0..9". Von dort aus kann man entweder weiter bis an das Ende des Diagramms, oder man geht nach unten, folgt dann der gestrichelten Linie zunächst nach links und dann noch oben, und kommt dann wieder zum Nicht-Terminal "0..9" und ist damit wieder an derselben Stelle wie oben. Man kann also die "Schleife" beliebig oft durchlaufen. Dies bedeutet also, dass eine Ziffer 1 bis 9, gefolgt von keiner, einer oder beliebig vielen Ziffern 0 bis 9 eine Dezimalzahl darstellt. Man beachte, dass Zahlen die mit einer "0" beginnen und nicht nur aus diesem einen Zeichen bestehen keine Dezimalzahlen sind. Diese werden als Oktal- bzw. Hexadezimalzahlen interpretiert.

1.4.3 Das Hilfspaket simple In Java gibt es verschiedene Wege Daten einzulesen. Diese werden im Abschnitt Ein-/Ausgabe12 behandelt. Allerdings sind diese Möglichkeiten für die normale Verwendung vorgesehen und erfordern daher komplexere Vorgänge, insbesondere z.B die Fehlerbehandlung. Da dies im Moment noch nicht so wichtig für die Programme ist und sie nur unnötig verkompliziert, und damit vom Wesentlichen ablenkt, werden wir in diesem Tutorial eine Hilfsbibliothek verwenden, die es erlaubt, die Eingabe (und einiges andere) vereinfacht vorzunehmen. Die Bibliothek simple stellt dazu Möglichkeiten zur Verfügung Daten (Zahlen, Wörter, Zeilen) von der Tastatur einzulesen, oder auch Zufallszahlen zu erzeugen. Eine genauere Beschreibung der Bibliothek und Details zur Verwendung und zur Einbindung finden sich im Anhang13. Wenn die Beispiele allerdings mit JEEE über die HTML-Version des Tutorials gestartet werden, braucht man sich darum erstmal nicht zu kümmern.

1.5 Gestaltung und Formatierung von Java-Quelltext In kleinen Programmen mag es egal sein, in welcher Form der Quelltext geschrieben wird, wie weit man z.B. einrückt, wo Kommentare stehen etc., aber in einem großen und zeitlich ausgedehnteren Projekt ist es wichtig, dass der Programmcode auch von anderen Programmierern und auch nach Jahren noch leicht lesbar und nachvollziehbar ist.

12 13

Siehe auch: Ein-/Ausgabe (S. 237) Siehe auch: Die Bibliothek: simple (S. 284)

1.5 Gestaltung und Formatierung von Java-Quelltext

15

Deshalb halten wir uns an Formatierungsregeln, die in fast allen Punkten der Java Coding Convention14 von Sun Microsystems entnommen sind. Im Einzelnen werden wir die Konventionen im Text durch in dieser Weise gekennzeichnete Absätze einführen. Die Gestaltungsregeln sind im Anhang15 zusammengefasst.

14 15

http://java.sun.com/docs/codeconv/ Siehe auch: Quelltextkonventionen (S. 307)

Kapitel 2 Elementare Objekte und Ausdrücke

Bereits in der Einführung16 haben wir über Objekte und Klassen gesprochen. Wir fassen noch einmal zusammen: • Objekte sind eigenständige Einheiten, die einen Zustand haben. • Der Zustand wird bestimmt durch die Werte der Attribute und sollte durch das Objekt selbst verwaltet werden. • Objekte werden von anderen Objekten durch Botschaften zur Ausführung von Zustandsänderungen17 angeregt. • Objekte sind Exemplare einer Klasse, die deren gemeinsame Attribute und Aktionen zusammenfasst. • Objekte werden durch den Aufruf von Konstruktoren18 geschaffen. Besonders einfache Elemente, wie Zahlen oder Buchstaben, werden auch in Java nicht als Objekte, sondern als einfache Werte behandelt. Ausdrücke über diese Typen können wie üblich gebildet und ausgewertet werden. Im Einzelnen lassen sich: • arithmetische Ausdrücke von ganzen oder reellen Zahlen bilden, • Zahlen und Buchstaben vergleichen und • Wahrheitswerte definieren und abfragen.

2.1 Behandlung von String Objekten Einfache, immer wieder gebrauchte Klassen sind im Java-Sprachstandard bereits vordefiniert. So z.B. die beiden Klassen String und StringBuffer zur Behandlung von Zeichenketten, die wir in diesem Abschnitt vorstellen wollen.

16 17 18

Siehe auch: Einführung (S. 1) Siehe auch: Methodenaufruf (S. 78) Siehe auch: Konstruktoren (S. 29)

18

Kapitel 2: Elementare Objekte und Ausdrücke

DEFINITION: Eine Zeichenkette (oft auch als String bezeichnet) ist eine endliche Folge von Zeichen.

SEMANTIK: Im Programm werden Strings in Anführungszeichen eingeschlossen. Die Zeichen gehören dem Unicode-Zeichensatz an. Sonderzeichen wie Zeilenende (\n) oder das Anführungszeichen selbst (\") werden mit \ eingeleitet oder als hexadezimale Darstellung der 16 Bit notiert (\u000a, \u0022). 2.1.1 Zeichenketten als Objekte der Klasse StringBuffer SEMANTIK: Objekte werden durch den Aufruf von Konstruktoren19 geschaffen. Dabei wird dem new-Operator ein Klassenname und eine gegebenenfalls leere Argumentliste überreicht, woraus dieser ein Objekt der Klasse produziert.

SEMANTIK: Variablen enthalten einen Verweis (eine Referenz) auf ein Objekt. Dieses Objekt kann durch die Variable angeprochen (referenziert) werden. Beispiel 7: Erzeugen von StringBuffer-Objekten StringBuffer s1 = new StringBuffer(); StringBuffer t1 = new StringBuffer("Eber"); cdrom:/buch/examples/java/Elementar/String1.java

Die Variable s1 verweist nun auf ein StringBuffer-Objekt mit der leeren Zeichenkette, t1 auf eines mit der Zeichenkette "Eber".

19

Siehe auch: Konstruktoraufruf (S. 29)

2.1 Behandlung von String Objekten

19

Wir veranschaulichen diesen Sachverhalt durch folgendes Diagramm:

Abbildung 10: Objektsicht: StringBuffer (Schritt 1)

Die beiden Objekte wurden bei der Deklaration der Variablen20 s1 und t1 konstruiert und den Variablen als Initialwert zugewiesen. Sie können nun durch den Aufruf geeigneter Methoden21 verändert werden. Beispiel 8: Zustandsänderung durch Methodenaufruf s1.append("Stein"); t1.setCharAt(0, 'O'); t1.replace(0, 1, "Tau"); cdrom:/buch/examples/java/Elementar/String2.java

An den leeren String s1 wird "Stein" angehängt, und bei t1 wird der Wert Eber über Ober in Tauber umgewandelt.

Abbildung 11: Objektsicht: StringBuffer (Schritt 2)

2.1.2 Zeichenketten als Objekte der Klasse String Eine etwas andere Semantik zeigt die Klasse String. Das gleiche Beispiel für Objekte dieser Klasse sähe wie folgt aus: Beispiel 9: Behandlung von String-Objekten String s2 = new String(); String t2 = new String("Eber"); s2 = s2.concat("Stein"); t2 = t2.replace('E', 'O'); t2 = "Tau" + t2.substring(1); cdrom:/buch/examples/java/Elementar/String3.java

20 21

Siehe auch: Variablendeklaration (S. 24) Siehe auch: Methodenaufruf (S. 30)

20

Kapitel 2: Elementare Objekte und Ausdrücke

An Stelle der Methode concat() kann auch der Infix-Operator + verwendet werden. Damit ist der Code meist deutlich besser lesbar. SEMANTIK: Die Objekte der Klasse String sind nicht veränderbar, es wird bei jeder Operation ein neuer String erzeugt. Das ist ein eher untypisches Verhalten für Objekte, es ähnelt dem der elementaren Datentypen, welches durch die Implementierung der Operationen der Klasse String bewirkt wird.

Abbildung 12: Objektsicht: String

Eine Auswahl von aufrufbaren Konstruktoren und Methoden der Klasse StringBuffer: Resultat

Name

Argumente

Wirkung

StringBuffer StringBuffer ()

konstruiert StringBuffer-Objekt mit leerer Zeichenkette

StringBuffer StringBuffer (String str)

konstruiert StringBuffer-Objekt für Zeichenkette str

Tabelle 1: Konstruktoren der Klasse StringBuffer

2.1 Behandlung von String Objekten

21

Resultat

Name

Argumente

Wirkung

String

toString

()

Rückgabe der Zeichenkette als String

String

subString

(int start, int end)

Rückgabe der Teil-Zeichenkette von der Position start einschließlich bis end ausschließlich. Zählung beginnt bei 0.

int

length

()

Länge der Zeichenkette.

char

charAt

(int index)

Zeichen an Position index von 0 an gezählt.

Tabelle 2: Zugriffsmethoden der Klasse StringBuffer

Resultat

Name

Argumente

Wirkung

void

setLength

(int newLength)

setzt Länge der Zeichenkette auf newLength. Dabei wird abgeschnitten oder mit dem Nullzeichen '\u0000' aufgefüllt.

void

setCharAt

(int index, char belegt Zeichen an der Position index ch) mit ch

StringBuffer replace

(int start, int ersetzt die Teil-Zeichenkette von der Poend, String str) sition start einschließlich bis end ausschließlich durch str

StringBuffer insert

(int offset, String str)

fügt den String str an der Position offset ein

StringBuffer delete

(int start, int end)

löscht die Teil-Zeichenkette von der Position start einschließlich bis end ausschließlich.

Tabelle 3: Modifikationsmethoden der Klasse StringBuffer

Und hier eine Auswahl von aufrufbaren Konstruktoren und Methoden der Klasse String: Resultat

Name

Argumente

Wirkung

String

String

()

konstruiert String für leere Zeichenkette

String

String

(String value) konstruiert String-Objekt für eine Kopie der Zeichenkette value

String

String

(StringBuffer konstruiert String-Objekt für die Zeibuffer) chenkette, die Inhalt von buffer ist.

Tabelle 4: Konstruktoren der Klasse String

22

Kapitel 2: Elementare Objekte und Ausdrücke

Resultat

Name

Argumente

Wirkung

String

toString

()

Identität

String

subString

(int start, int end)

Rückgabe der Teil-Zeichenkette von der Position start einschließlich bis end ausschließlich

int

length

()

Länge der Zeichenkette.

char

charAt

(int index)

Zeichen an Position index von 0 an gezählt.

Tabelle 5: Zugriffsmethoden der Klasse String

Resultat

Name

Argumente

Wirkung

int

compareTo (String other) vergleicht die Strings lexikografisch und liefert eine Zahl kleiner 0, falls der aufrufende String in der lexikografischen Ordnung vor other kommt, 0 bei Gleichheit und eine Zahl größer 0 sonst

Tabelle 6: Vergleichsmethoden der Klasse String

Resultat

Name

Argumente

Wirkung

String

replace

(char oldChar, liefert neues String-Objekt, in dem alle char newChar) Vorkommen von oldChar durch newChar ersetzt sind

String

concat

(String str)

liefert neues String-Objekt, das durch Anhängen von str an diesen String entsteht

Tabelle 7: Pseudo-Modifikationsmethoden der Klasse String

Resultat

Name

Argumente

Wirkung

String

valueOf

(int i)

liefert String-Repräsentation der ganzen Zahl i

String

valueOf

(boolean b)

liefert String-Repräsentation des logischen Wertes b

String

valueOf

(Object obj)

liefert String-Repräsentation des Objekts obj, die sich durch Aufruf von dessen toString-Methode ergibt. Falls obj undefiniert ist, so wird (null), der String "null" geliefert.

Tabelle 8: Konversionsmethoden von elementaren Datentypen zu String

2.1 Behandlung von String Objekten

23

Wir erkennen aus diesen (unvollständigen) Auflistungen der Methoden noch einmal den wesentlichen Unterschied zwischen Objekten der Klassen StringBuffer und String. Während erstere wie die meisten Objekte agieren, also verändert werden können und auch das Setzen von einzelnen Teilaspekten (Attributen) erlauben, treten letztere wie unteilbare nach ihrer Initialisierung feststehende Werte auf, bei denen der Versuch einer Änderung jedes Mal ein neues Objekt kreiert. Alle Methoden einer zum Java System gehörenden Klasse erfährt man durch Nachschlagen in der mitgelieferten Java API Dokumentation. Wir empfehlen diese Dokumentation während des Programmierens immer griffbereit zu halten. Wir haben in diesem Abschnitt bereits die wichtigsten Grundlagen des objektorientierten Programmierens kennen gelernt. Wir zeigen in der nachfolgenden, zusammenfassenden Aufgabe noch einmal den typischen Aufbau eines Java Programms: • Objekte werden konstruiert und mittels Variablen23 referenziert. • Objekte werden durch Aufruf von Methoden verändert. • Zeichenketten (Werte) werden ausgegeben24. • Durch eine Zuweisung25 werden einer Variablen ein neuer Wert oder ein neues Objekt zugeordnet. 2.1.3 Aufgaben Übung: Die Klassen String und StringBuffer

Diese Übungsaufgaben sollen das Verständnis für die Benutzung der Klassen String und StringBuffer prüfen. Aufgabe 1: Ausgabe

Das folgende Beispiel erläutert einige der Methoden für Strings und StringBuffer in einem vollständigen Programm. public class StringAll { public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer t1 = new StringBuffer("Eber"); s1 = s1.append("Stein"); t1.setCharAt(0, 'O'); t1.replace(0, 1, "Tau"); String s2 = new String(); String t2 = new String("Eber"); s2 = s2.concat("Stein"); t2 = t2.replace('E', 'O'); t2 = new String("Tau").concat(t2.substring(1, 4)); s1 = t1; t1.replace(3, 4, "ch");

23

Siehe auch: Variablen (S. 24) Siehe auch: Ein- und Ausgabe (S. 30) 25 Siehe auch: Zuweisung (S. 33) 24

24

Kapitel 2: Elementare Objekte und Ausdrücke s2 = t2; t2.substring(0, 3).concat("ch").concat(t2.substring(4, 6)); System.out.println("s2 = " + s2); } } cdrom:/buch/examples/java/Elementar/StringAll.java

Geben Sie die Ausgabe des Programms an ! Antwort: .................................................................... (Lösung S. 319)

Aufgabe 2: Zwischenwerte

Ändern Sie das Programm so, dass auch die Zwischenwerte ausgedruckt werden. Das abgegebene Programm soll "StringAlles.java" heißen. (Lösung S. 319) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

Aufgabe 3: Umdrehen

Schreiben Sie nun ein Programm "StringReverse.java", welches einen String einliest und rückwärts ausgibt. (Lösung S. 319) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

2.2 Deklaration von Variablen und Konstanten Objekte und Werte elementarer Datentypen werden durch Variablen angesprochen. SEMANTIK: Jede Variable muss vor ihrer Verwendung definiert werden.

Es werden Variablen für den angegebenen Typ27 angelegt und Speicherplatz reserviert, auf den mit dem Namen28 zugegriffen wird.

27 28

Siehe auch: Datentypen (S. 38) Siehe auch: Namen (S. 26)

2.2 Deklaration von Variablen und Konstanten

25

DEFINITION: Variablen dienen für elementare Typen als Platzhalter für einen Wert des Wertebereichs und für Klassen und Felder (Arrays) als Platzhalter für eine Referenz, also für einen Verweis auf ein Objekt oder Array.

DEFINITION: Eine Referenz, die auf kein Objekte zeigt, wird mit dem Schlüsselwort null bezeichnet.

SEMANTIK: Wird ein Ausdruck angegeben, so bestimmt dieser den Anfangswert. Der Zugriff auf nicht initialisierte Variablen ist nicht erlaubt!

Diese Art der Variablenvereinbarung tritt bei • lokalen Variablen • Attributen29 und Klassenattributen30 auf. Eine etwas erweiterte Form vereinbart die Komponenten eines Arrays31. Außerdem werden Variablen auch durch Parameterlisten von Methoden32 oder Konstruktoren33 eingeführt. In dem folgenden Beispiel werden zunächst zwei Variablen vom Typ int und vom Typ String deklariert (int zahl bzw. String wort) und initialisiert (=2 bzw. ="Hallo"), dann wird eine Konstante vereinbart. Beispiel 10:

int zahl = 2; String wort = "Hallo"; final int MONTH_PER_YEAR = 12;

Der Wert einer Variablen kann im Verlauf des Programms geändert werden. Steht final vor der Deklaration, so ist das nicht erlaubt, man vereinbart eine Konstante. Man beachte, dass man bei einer konstanten Referenz durchaus den Zustand des

29

Siehe auch: Attribute (S. 63) Siehe auch: Klassenattribute (S. 82) Siehe auch: Komponenten eines Arrays (S. 89) 32 Siehe auch: Methoden (S. 75) 33 Siehe auch: Konstruktoren (S. 72) 30 31

26

Kapitel 2: Elementare Objekte und Ausdrücke

Objektes ändern kann (man beachte hierzu auch den Abschnitt Zuweisung bei Wert- und Referenzsemantik34). Benannte Konstanten sind in vielen Fällen hingeschriebenen Zahlenwerten überlegen. Einerseits erhöhen sie durch sprechende Namen die Lesbarkeit, andererseits fördern sie die Anpassbarkeit des Programms, da eine Änderung nur an einer Stelle vorgenommen zu werden braucht und nicht überall im Quelltext gesucht werden muss. Beispiele sind etwa Größen, die sich von Zeit zu Zeit ändern, wie Steuersatz oder solche, die programmtypisch gesetzt werden sollen wie die Kapazität von Zwischenspeichern, Startwerte von Zählern usw. Die Namen von Attributen und Variablen sollten stets mit einem Kleinbuchstaben anfangen, Konstanten werden ausschließlich mit Großbuchstaben geschrieben. Eine Variablendeklaration im Rumpf oder Block einer Methode vereinbart für diesen Block eine lokale Variable. Der Gültigkeitsbereich ist der Rest des Blockes einschließlich geschachtelter Unterblöcke. Lokale Variablen dürfen innerhalb ihres Gültigkeitsbereiches nicht redeklariert werden. Fehlt innerhalb der Deklaration der Initialisierungsausdruck, so werden lokale Variablen nicht initialisiert. Beispiel 11:

Das folgende Beispiel demonstriert das Aufrufen einer Variablen im falschen(!) Gültigkeitsbereich. Daher sei hier darauf hingewiesen, dass dieses Programm, sollten Sie es ausführen wollen, nicht funktionieren wird! // Hier werden i und j als Integer deklariert und zunaechst // mit 0 initialisiert. Der Gueltigkeitsbereich von i erstreckt // sich dabei auf beide Bloecke. int i = 0; { int j = 0; System.out.println(i); } // Die Variable j ist hier jedoch nicht mehr in ihrem // Gueltigkeitsbreich. // Daher erhaelt man beim Kompilieren eine Fehlermeldung! System.out.println(j); cdrom:/buch/examples/java/Elementar/VarDekl.java

34

Siehe auch: Zuweisung bei Wert- und Referenzsemantik (S. 33)

2.3 Namen in Java

27

2.3 Namen in Java Vom Programmierer definierte Größen werden durch Namen angesprochen.

SEMANTIK: Namen bestehen aus beliebig langen Folgen von UnicodeBuchstaben und Unicode-Ziffern, außerdem beginnen sie mit einem Buchstaben.

SEMANTIK: Buchstaben sind die Zeichen von 'a' bis 'z', von 'A' bis 'Z' sowie '$' und '_' . Abhängig von der im Java System eingestellten geografischen Region bzw. Sprachraum sind Umlaute ebenfalls Buchstaben. Von deren Benutzung raten wir jedoch ab. SEMANTIK: Es wird zwischen Groß- und Kleinschreibung unterschieden. Namen dürfen keine Schlüsselwörter verdecken und keine Leerzeichen enthalten. Sie sollten noch dazu nicht mit $ oder _ beginnen, da solche Namen für Systemsoftware reserviert bleiben sollten.

28

Kapitel 2: Elementare Objekte und Ausdrücke

SEMANTIK: Ziffern sind die Zeichen von '0' bis '9'.

Beispiel 12: Beispiele für gültige und ungültige Namen

Bei folgenden Zeichenfolgen handelt es sich um gültige Namen: a, Test13, MyName, PASCAL_SC, intPtr, _dangerous, GueltigerName. Der vorletzte Name ist zwar gültig, jedoch nicht empfehlenswert. Es folgen Beispiele für nicht zulässige Namen: • 42mal: beginnt nicht mit einem Buchstaben • ZZ TOP: Leerzeichen sind nicht erlaubt • int: Schlüsselwörter sind reserviert • Tschüss!: ! ist kein Buchstabe Namen bezeichnen wichtige Programmgrößen wie Variablen, Objekte, Klassen usw. Deshalb sollte bei ihrer Wahl sorgfältig vorgegangen werden und ein Bezug zur bezeichneten Größe bestehen. Einige Namen sind schon von vornherein bekannt. Dazu gehören die Namen der Standarddatentypen wie z.B. int für einen Teilbereich der ganzen Zahlen und double für einen Teilbereich der reellen Zahlen. Als Standardnamen und Schlüsselwörter sind im Einzelnen folgende Wörter reserviert: • Die Standarddatentypen: byte, short, int, long, boolean, char, void, float und double • Die Schlüsselwörter: abstract, assert, break, case, catch, class, const, continue, default, do, else, extends, final, finally, for, goto, if, implements, import, instanceof, interface, native, new, package, private, protected, public, return, static, super, strictfp, switch, synchronized, this, throw, throws, transient, try, volatile und while In Java 1.5 kommt das Schlüsselwort enum hinzu. Aufgabe 4: Gültige Namen

Welche der folgenden Namen sind nicht erlaubt? ❏ ❏ ❏ ❏ ❏

Byte bytes char integer integers

2.4 Konstruktion von Objekten ❏ ❏ ❏ ❏

29

$int _int führerschein fahrzeug-halter (Lösung S. 319)

2.4 Konstruktion von Objekten SEMANTIK: Ein Konstruktor wird mit dem new- Operator aufgerufen. Dabei können Argumente (Parameter) übergeben werden.

SEMANTIK: Es wird Speicherplatz für ein Objekt dieser Klasse angelegt und eine Referenz darauf geliefert. Der Rumpf des Konstruktors35 wird mit dem durch die Argumentausdrücke initialisierten formalen Parameter ausgeführt.

Beispiel 13:

Ein Konstruktor wird folgendermaßen aufgerufen: Klasse k = new Klasse(); // Hier wird zunaechst der parameterlose Konstruktor der // Klasse String aufgerufen: String hallo = new String(); // Und hier einer, der die Zeichenkette initialisiert. String hey = new String("hey"); // Es gibt noch weitere String-Konstruktoren, einige // sind aber auch schon veraltet (abgeschrieben/deprecated). cdrom:/buch/examples/java/Elementar/KonstruktorAufruf.java

35

Siehe auch: Konstruktordeklaration (S. 72)

30

Kapitel 2: Elementare Objekte und Ausdrücke Aufgabe 5: Konstruktoren der Klasse String

Wieviele nicht veraltete Konstruktoren besitzt die Klasse String? ❍ ❍ ❍ ❍ ❍

11 2 9 6 7 (Lösung S. 320)

2.5 Aufruf von Methoden Der Methodenaufruf überträgt die Aktivität auf das durch eine Referenz angegebene (fremde) Objekt. Dieser Operand ist im einfachsten Fall eine Variable.

SEMANTIK: Die als aktuelle Parameter angegebenen Ausdrücke werden ausgewertet, und damit der Methodenrumpf ausgeführt. Genauere Regeln geben wir nach der Beschreibung der Methodenvereinbarung36 bekannt. Beispiel 14: StringBuffer

Hier werden nun zur Demonstration einige Methoden der Klasse StringBuffer aufgerufen. Eine weitere Veranschaulichung dieser Methoden findet sich auch im Kapitel über Zeichenketten37. StringBuffer s1 = new StringBuffer("A"); StringBuffer s2 = new StringBuffer("ja"); s1.append("allo "); s1.replace(0, 1, "H"); s2.replace(0, 2, "du"); s2.insert(0, s1.toString()); System.out.println(s2.toString()); cdrom:/buch/examples/java/Elementar/MethodenAufruf.java

36 37

Siehe auch: Methodenaufruf (S. 78) Siehe auch: Zeichenketten (S. 17)

2.6 Einfache Ein- und Ausgabe

31

2.6 Einfache Ein- und Ausgabe Die Kommunikation eines Programms mit seiner Umgebung bezeichnet man als Ein- bzw. Ausgabe. Dabei hat man in Java verschiedene Möglichkeiten: • Man stellt über eine grafische Benutzeroberfläche38 Ein- und Ausgabefelder zur Verfügung. • Man liefert die Eingaben als Kommandozeilenparameter beim Starten des Programms. Diese stehen dann als Parameter der Methode main zur Verfügung. • Man tippt die Eingaben mit der Tastatur ein. Die Ausgabe wird auf den Bildschirm geschrieben. • Man kommuniziert direkt mit Dateien. Die erste Variante ist für den Nutzer oft die angenehmste, allerdings ist der Programmieraufwand relativ hoch und wir werden ihn vorerst scheuen. Die zweite und dritte Möglichkeit kommunizieren ebenfalls direkt mit Hilfe von lesbaren Zeichenketten (Datentyp String), während bei der letzten Variante für den Computer lesbare Dateien genügen. 2.6.1 Ausgabe mit System.out Beginnen wir mit der einfachen Ausgabe. Beispiel 15: Einfache Ausgabe System.out.println("Hallo Borneo!"); cdrom:/buch/examples/java/Elementar/Borneo.java

Eigentlich genügt es, sich die Anweisung System.out.println() zu merken, die als Argument einen String oder einen Wert eines elementaren Datentyps aufnehmen kann und dessen Repräsentation auf den Bildschirm ausgibt. Vom Abschnitt über Strings39 wissen wir, dass wir mit "+" beliebige Strings zusammenbauen können. Jetzt wollen wir es doch etwas genauer wissen. Ein- und Ausgabe wird mittels Datenströmen modelliert. Dabei ist ein Datenstrom eine im Prinzip unendliche Folge von Werten (hier Zeichen). In der Klasse System40 befindet sich ein Datenstrom-Objekt mit dem Namen out, genauer gesagt ist es ein Objekt von dem Datenstrom-Untertyp PrintStream. Diese Klasse stellt die Methoden println() und print() zur Verfügung. Als Argumente erwarten diese Methoden ein Objekt, einen String oder einen elementaren Wert, dessen Stringrepräsentation sie ausgeben. Bei Objekten wird dazu die Methode toString() aufgerufen. println() bewirkt einen Zeilenvorschub, print() nicht.

38

Siehe auch: Grafische Benutzeroberflächen (S. 247) Siehe auch: Strings (S. 17) 40 Siehe API-Dokumentation: java.lang.System 39

32

Kapitel 2: Elementare Objekte und Ausdrücke

2.6.2 Eingabe über Kommandozeile Wir haben bisher über den Parameter args der main Methode noch nichts gesagt. Das brauchen wir eigentlich auch nicht, denn wir werden ihn fast nie benutzen. Dieses Array41 (aneinanderreihung gleichartiger Elemente, hier von Zeichenketten) dient als Eingabemedium. Beim Aufruf des Interpreters "java" werden hinter dem Klassennamen eine Reihe von Strings angegeben, mit denen dann das Array args initialisiert wird. Mit diesen Kommandozeilenparametern können so Programme parametrisiert werden. Das ist insbesondere für nicht interaktive Programme angebracht. Beispiel 16: Kommandozeilenparameter System.out.println("Erster Parameter: " + args[0]); cdrom:/buch/examples/java/Elementar/Args.java

Die einzelnen Zeichenketten werden ohne Anführungszeichen geschrieben und mit Leerzeichen getrennt. Es können beliebig viele sein, von denen hier nur die erste ausgegeben wird.

2.6.3 Eingabe mit SimpleInput.in Leider ist die Eingabe nicht so einfach wie die Ausgabe. Die Klasse System enthält zwar ein in-Objekt, allerdings ist dieses nicht direkt gebrauchsfertig. Deshalb verschieben wir seine Behandlung. Wir stellen statt dessen eine Klasse SimpleInput42 mit einfachen Einlese-Methoden zur Verfügung. Diese Klasse vereinbart ein Input-Objekt in, welches mit den Methoden • getString(), getWord(), getLine() • getDouble(), getInt() • ... Strings, doubles, ints und Werte anderer elementarer Datentypen43 einliest. Die Methoden sind so geschrieben, dass sie auch bei fehlerhafter Syntax einer Zahl einen gültigen Standardwert liefern (bei int z.B. den Wert Integer.MIN_VALUE, der kleinsten gültigen int-Zahl, oder bei double den Wert Double.NaN, der Not A Number, also keine Zahl ist, aber trotzdem ein gültiger double-Wert). So wird bei falscher Eingabe ein Fehlerabbruch vermieden. Die folgenden Beispiele importieren also die Klasse SimpleInput: import simple.io.SimpleInput;

41

Siehe auch: Array (S. 89) Siehe auch: SimpleInput (S. 284) 43 Siehe auch: Elementare Datentypen (S. 38) 42

2.6 Einfache Ein- und Ausgabe

33

Beispiel 17: Einlesen von Zahlen mit SimpleInput // Zwei Zahlen mit SimpleInput einlesen System.out.print("Ersten Faktor eingeben: "); int factor1 = SimpleInput.in.getInt(); System.out.print("Zweiten Faktor eingeben: "); int factor2 = SimpleInput.in.getInt(); cdrom:/buch/examples/java/Elementar/SimpleIO.java

2.6.4 Kommandozeilen-Eingabe mit SimpleInput Die Klasse SimpleInput hat auch Methoden zur Umwandlung von Strings in elementare Datentypen • parseDouble(String) • parseInt(String) Diese Methoden sind als Klassenmethoden44 von der Klasse selbst aufzurufen. Auch sie erzeugen keinen Fehler. Beispiel 18: Umwandeln von Kommandozeilenparametern in Zahlen // Das Programm wandelt zwei eingegebene Argumente in Integer um // und gibt dann das Produkt der beiden aus. int faktor1 = SimpleInput.parseInt(args[0]); int faktor2 = SimpleInput.parseInt(args[1]); int ergebnis = faktor1 * faktor2; System.out.println(faktor1 + " * " + faktor2 + " = " + ergebnis); cdrom:/buch/examples/java/Elementar/SimpleParse.java

2.6.5 Ausnahmen und Fehler Wir haben bis jetzt außer acht gelassen, dass sowohl beim Einlesen, als auch bei der Ausgabe etwas schief gehen kann. Beispielsweise erwartet unsere getDouble()-Methode eine Zeichenkette, die in korrekter Syntax eine Gleitkommakonstante beschreibt. Die Behandlung solcher Ausnahmen45 beschreiben wir später. Eine Klasse SimpleInputExc, die der Klasse SimpleInput in ihrer Schnittstelle gleicht, bietet Eingabemethoden an, die die Ausnahmen java.io.IOException oder java.lang.NumberFormatException auswerfen, falls ein entsprechender Fehler auftritt. Die aufrufende Methode muss nun diese Ausnahmen behandeln oder selbst auslösen. Details46 hierzu später.

44

Siehe auch: Klassenmethoden (S. 87) Siehe auch: Behandlung solcher Ausnahmen (S. 232) 46 Siehe auch: Ausnahmebehandlung (S. 232) 45

34

Kapitel 2: Elementare Objekte und Ausdrücke

2.7 Die Zuweisung bei Wert- und Referenzsemantik SEMANTIK: Eine Zuweisung ordnet einer Variablen47 einen neuen Wert oder eine neue Referenz zu. Sie beeinflusst dadurch den Zustand.

Links vom Zuweisungszeichen steht eine Variable, rechts ein Ausdruck von passendem Typ48. 2.7.1 Wert und Referenz SEMANTIK: Der Ausdruck wird ausgewertet, und bei elementaren Typen wird der Wert der Variablen als Kopie zugewiesen. Er belegt nun diesen Platz, der alte Wert ist nicht mehr vorhanden. (Wertsemantik)

SEMANTIK: Bei Objekten und Arrays49 wird der Variablen eine Referenz auf das Objekt zugewiesen. Ist der rechte Ausdruck eine Referenz, so wird diese der links stehenden Variablen zugewiesen. Das heißt die Referenz selbst wird kopiert, das referenzierte Objekt dagegen nicht. Es zeigen nun also (mindestens) zwei Verweise auf das gleiche Objekt! Eine Veränderung dieses Objektes, etwa durch Methodenaufruf für eine Variable bewirkt auch, dass sich das Objekt, auf das die andere Variable verweist, verändert, es ist ja das Gleiche! (Referenzsemantik)

47

Siehe auch: Variablen (S. 24) Siehe auch: Anpassung von Datentypen (S. 39) 49 Siehe auch: Felder(Arrays) (S. 89) 48

2.7 Die Zuweisung bei Wert- und Referenzsemantik

35

Man kann diesen Sachverhalt auch anders beschreiben: SEMANTIK: Alternativ: Objekte werden durch Referenzen repräsentiert. Eine Zuweisung kopiert immer einen repräsentierten Wert. Bei Objekten ist das die Referenz, bei elementaren Typen der Wert an sich. Will man erzwingen, dass Objekte kopiert werden, so muss man sie durch einen Konstruktor neu erzeugen. Einige Klassen stellen für das Kopieren von Objekten50 auch die Methode clone() bereit. Beispiel 19: Unterschied zwischen Wert- und Referenzsemantik

Hier wird der Wert von t2 mit dem von t1 überschrieben. Als Ausgabe erfolgt sowohl für t1 als auch für t2 "TestString" (Referenzsemantik). StringBuffer t1 = new StringBuffer("Test"); StringBuffer t2 = t1; t1.append("String"); System.out.println("t1 = " + t1); System.out.println("t2 = " + t2);

// [1] // [2] // [3]

cdrom:/buch/examples/java/Elementar/Zuweisung1.java

Abbildung 13: Objektsicht: Referenzsemantik bei StringBuffer (Schritt 1)

Abbildung 14: Objektsicht: Referenzsemantik bei StringBuffer (Schritt 2)

50

Siehe auch: Klonen von Objekten (S. 203)

36

Kapitel 2: Elementare Objekte und Ausdrücke

Abbildung 15: Objektsicht: Referenzsemantik bei StringBuffer (Schritt 3)

Hier wird x der Wert 4 zugewiesen und y bleibt 5 (Wertsemantik). int x = 5; int y = x; x = 4; System.out.println("x: " + x); System.out.println("y: " + y);

// [1] // [2] // [3] cdrom:/buch/examples/java/Elementar/Zuweisung2.java

Abbildung 16: Objektsicht: Wertsemantik bei int (Schritt 1)

Abbildung 17: Objektsicht: Wertsemantik bei int (Schritt 2)

Abbildung 18: Objektsicht: Wertsemantik bei int (Schritt 3)

Der Unterschied zwischen Wert- und Referenzsemantik ist somit der, dass bei Wertsemantik ein Wert überschrieben wird und beiden Variablen letztendlich eine Kopie desselben Wertes zugewiesen wird. Bei Referenzsemantik wird lediglich eine neue, weitere Referenz auf ein Objekt zugewiesen.

2.7 Die Zuweisung bei Wert- und Referenzsemantik

37

Wir fassen die wichtigsten Begriffe in einer Tabelle zusammen: Wert:

Element eines (elementaren) Datentyps, ergibt sich als Resultat eines Ausdrucks

Wertsemantik:

Neuberechnen und Überschreiben eines Wertes bei Zuweisungen.

Objekt:

Exemplar eines Klassentyps, ergibt sich durch Konstruktoroder Methodenaufruf.

Variable:

Speicherplatz, der einen elementaren Wert oder eine Referenz auf ein Objekt bezeichnet.

Referenz:

Verweis auf ein Objekt, Zeiger (defaultwert: null). Das Objekt wird über diesen Verweis angesprochen.

Referenzsemantik:

Zuweisung einer neuen, weiteren Referenz auf ein Objekt.

Tabelle 9: Wichtige Begriffe zu Wert- und Referenz-Semantik

Zuweisungen lassen sich als Ausdrücke auffassen. Eine Zuweisung, die mit einem ; abgeschlossen wird, gilt als Anweisung. Beispiel 20: Vertauschen von Variablen // Tauschen der Variablen temp = i; i = j; j = temp; cdrom:/buch/examples/java/Elementar/Varexchange.java

Eine Hilfsgröße temp ist nötig, da der alte Wert von i zwischengespeichert werden muss, bevor i der Wert von j zugewiesen wird. Das gilt entsprechend für Referenzen. Aufgabe 6: Zuweisungen StringBuffer a, b, c; int d, e, f; a = new StringBuffer("ha"); b = new StringBuffer("lt"); c = a; c.append("llo"); b.append("we"); c = b; c.append("+ball"); d e f e

= = = =

5; 7; e; 6;

c = new StringBuffer("*" + d + "*" + e + "*" + f + "*"); String s = ("->" + a + "-" + b + "-" + c); System.out.println(s); cdrom:/buch/examples/java/Elementar/ZuweisungsAufgabe.java

38

Kapitel 2: Elementare Objekte und Ausdrücke

Was gibt obiges Programm aus? Antwort: .................................................................... (Lösung S. 320)

2.8 Datentypen DEFINITION: Jeder Typ beschreibt einen Wertebereich. Zu einem Typ gehört aber nicht nur der Wertebereich, sondern auch die darauf ausführbaren Operationen. In Java wird zwischen elementaren Datentypen, Feldern (Arrays) und Klassen unterschieden. 2.8.1 Klassentypen Jede Klasse beschreibt einen eigenen Wertebereich, der aus allen Objekten besteht, die als Exemplare dieser Klasse gebildet werden können. Das gilt sowohl für vom Benutzer definierte Klassen als auch für die in Java schon vorhandenen Klassen. Die Operationen sind die Methoden einer Klasse. SEMANTIK: Jede neue Klassendefinition51 beschreibt einen neuen Typ. Zum Beispiel besteht der Wertebereich der Klassen String und StringBuffer aus allen Zeichenketten. Die Typen sind trotzdem unterschiedlich. Es gibt auch Klassen, die einen leeren Wertebereich besitzen, die also keine Objekte bilden können. Die Klasse Math z.B. fasst lediglich einige Standardfunktionen52 zusammen. 2.8.2 Elementare Datentypen Es gibt numerische Datentypen und den Datentyp char. Deren exakte Beschreibung und Größe kann im Kapitel Numerische Typen53 nachgesehen werden.

51

Siehe auch: Klassendefinition (S. 63) Siehe auch: Standardfunktionen (S. 50) 53 Siehe auch: Numerische Typen (S. 41) 52

2.9 Typ Konversion

Typ

Beschreibung

boolean

Wahrheitswerte true oder false

char

Ein (Unicode-) Zeichen

byte

Teilmenge der ganzen Zahlen (-27 bis 27-1)

short

Teilmenge der ganzen Zahlen (-215 bis 215-1)

int

Teilmenge der ganzen Zahlen (-231 bis 231-1)

long

Teilmenge der ganzen Zahlen (-263 bis 263-1)

float

Teilmenge der reellen Zahlen

double

Teilmenge der reellen Zahlen

39

Tabelle 10: Die elementaren Datentypen

float-Werte haben eine Genauigkeit von ca. 7, double-Werte von etwa 15 Dezimalstellen. Der Datentyp char stellt ein Unicode-Zeichen dar. Damit dient er als Grundlage für die Bearbeitung von textuellen Informationen, da die Unicodefähigkeit von Java bei Verwendung von anderen primitiven Typen nicht gegeben ist. char-Konstanten werden in Apostrophe (') eingeschlossen: 'a'. Sonderzeichen wie Zeilenwechsel ('\n') oder ein Apostroph selbst ('\'') werden mit \ eingeleitet oder als hexadezimale Darstellung der 16 Bit notiert ('\u000a', '\u0022'). 2.8.3 Array-Typen Durch Anhängen eines eckigen Klammerpaares an einen Typ wird ein Array (Feld, eine Aneinanderreihung), dessen Elemente von dem angegebenen Typ sind, vereinbart. Der Wertebereich eines Arrays54 besteht aus allen endlichen Folgen von Werten des Elementtyps.

2.9 Typ Konversion 2.9.1 Numerische Typanpassung Die Wertebereiche der ganzzahligen Datentypen55 sind Teilmengen voneinander, ebenso ist float eine Teilmenge von double. Deshalb ist eine Konversion von Werten dieser Datentypen nach folgendem Bild in Pfeilrichtung in der Regel ohne Informationsverlust möglich und wird von Java implizit durchgeführt.

54 55

Siehe auch: Arrays (S. 89) Siehe auch: Ganzzahlige Ausdrücke (S. 47)

40

Kapitel 2: Elementare Objekte und Ausdrücke

DEFINITION: Typen, zwischen denen eine implizite Konversion stattfindet, heißen passend oder kompatibel. Wenn wir die Richtung, in der die Typanpassung erfolgt, betonen wollen, sagen wir ein Ausdruck ist zu einer Variablen zuweisungskompatibel.

Abbildung 19: Zuweisungskompatibilität primitiver Datentypen

Wir sehen, dass auch der Typ der Unicode-Zeichen char als ganze Zahl aufgefasst werden kann. SEMANTIK: Diese numerische Typangleichung wird bei Zuweisungen implizit zur Anpassung des Typs der rechten Seite an den Variablentyp durchgeführt. Beim Methodenaufruf56 werden so die aktuellen Argumente den formalen Parametern zugewiesen. Es ist also nicht nötig, für jeden numerischen elementaren Datentyp eine eigene Methode vorzusehen, die z.B. die Exponentialfunktion berechnet. Beispiel 21: // Aufruf von Math.exp() mit einer doubleZahl double a = Math.exp(2.0); // Aufruf von Math.exp() mit einer intZahl double b = Math.exp(2); cdrom:/buch/examples/java/Elementar/Cast1.java

56

Siehe auch: Methodenaufruf (S. 30)

2.10 Numerische Typen

41

Falls die Typkonversion mit Informationsverlust verbunden sein könnte, muss die Konversion explizit angestoßen werden. Das geschieht durch Voranstellen des geklammerten Zieltypnamens vor den Ausdruck.

Beispiel 22: // Initialisieren einer int-Variable int zahl1 = Integer.MAX_VALUE; System.out.println("zahl1 = " + zahl1); // Zuweisen des ints auf einen float (automatisches Cast) float zahl2 = zahl1; System.out.println("zahl2 = " + zahl2); // Zuweisen des floats auf eine int (explizites Cast notwendig) zahl1 = (int)zahl2; System.out.println("zahl1 = " + zahl1); cdrom:/buch/examples/java/Elementar/Cast2.java

Jeder beliebige Wert wird in den Typ String konvertiert, wenn er mit einem String-Wert mit dem Operator + verknüpft wird. Das geschieht z. B. bei der Ausgabe. Dabei wird bei Objekten die entsprechende toString()-Methode aufgerufen. Jede Klasse besitzt implizit (genauer gesagt durch Vererbung57) eine Methode toString(). Diese sollte für einen sinnvollen Einsatz für jede Klasse neu definiert werden.

2.10 Numerische Typen In Java stehen elementare Standarddatentypen für ganze Zahlen58 und Gleitkommazahlen59 zur Verfügung.

Mit ihnen können Ausdrücke gebildet und Berechnungen durchgeführt werden, ganz analog zu den bekannten Regeln der Mathematik.

57

Siehe auch: Vererbung (S. 184) Siehe auch: Ganzzahlige Ausdrücke (S. 47) 59 Siehe auch: Arithmetische Ausdrücke (S. 50) 58

42

Kapitel 2: Elementare Objekte und Ausdrücke Beispiel 23:

x = 2.3 * 87.0; i = 2 + 3 * 4; x = i / (7 * 2.8);

2.10.1 Ganze Zahlen Vier verschiedene elementare Datentypen repräsentieren einander umfassende Teilmengen der ganzen Zahlen. Die folgende Tabelle zeigt die Namen und Wertebereiche. Datentyp

benötigter Speicher

Wertebereich

byte

1 Byte (8 Bit)

-128 bis 127

short

2 Byte (16 Bit)

-32.768 bis 32.767

int

4 Byte (32 Bit)

-2.147.483.648 bis 2.147.483.647

long

8 Byte (64 Bit)

-9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807

Tabelle 11: Ganzzahlige Typen

Normalerweise benutzt man zum Rechnen den Typ int, von dem nun Konstanten mit oder ohne Vorzeichen hingeschrieben werden können. SEMANTIK: Integer.MIN_VALUE bzw. Integer.MAX_VALUE bezeichnen die kleinste bzw. größte Zahl dieses Datentyps.

2.10 Numerische Typen

43

Konstanten vom Typ long enden mit einem l oder L. Vom Typ int können Ausdrücke60 gebildet werden. Ganzzahlige Werte können mit System.out.println ausgegeben werden. Ganzzahlige Konstanten können auch in Oktal- oder Hexadezimalform geschrieben werden. Oktalzahlen beginnen mit einer 0, Hexadezimalzahlen mit 0x oder 0X; dann folgen Oktalziffern (0 bis 7) bzw. Hexadezimalziffern (0 bis 7 und a bis f oder A bis F).

2.10.2 Realzahlen Für das numerische Rechnen wichtiger als die ganzen Zahlen sind die reellen Zahlen, die in der Regel nicht exakt auf einem Rechner dargestellt oder verknüpft werden können. In Java stehen 2 Datentypen für solche Gleitkommazahlen mit unterschiedlicher Genauigkeit zur Verfügung Datentyp

benötigter Speicher

Wertebereich

float

4 Byte (32 Bit)

+/- 3.40282347 * 10 38

double

8 Byte (64 Bit)

+/- 1.79769313486231570 * 10308

Tabelle 12: Gleitkomma-Typen

60

Siehe auch: Ausdrücke (S. 47)

44

Kapitel 2: Elementare Objekte und Ausdrücke

Beispiel 24:

Gleitkommakonstante

Typ

2.71818

double

1e34

double

0003.0003f

float

.1e-02

double

Tabelle 13: Gültige Gleitkommakonstanten

Keine Gleitkommakonstante

Grund

100,0

Komma statt Punkt

0x3e4

x nicht zugelassen

Tabelle 14: Ungültige Gleitkommakonstanten

2.10 Numerische Typen

45

Aufgabe 7: Gültige Gleitkommazahlen

Welche der folgenden Zahlen sind gültige Gleitkommazahlen? ❏ ❏ ❏ ❏

12,6 39999.6 5e24 8x3 (Lösung S. 320)

Die Initialisierung der Variablen vom Gleitkommatyp (normalerweise wird double verwendet) muss wie bei ganzen Zahlen für lokale Variablen innerhalb eines Methodenrumpfes61 explizit erfolgen, während Attributwerte mit 0.0 initialisiert werden. Durch eine Zuweisung62 ändert sich der Wert einer Variablen, der alte Wert geht verloren. Es gilt also Wertsemantik. 2.10.3 Operatoren im Überblick Für die numerischen Datentypen können Ausdrücke wie gewohnt gebildet werden. Es gibt die üblichen Operatoren mit den üblichen Vorrangregeln. Klammerung ist möglich. Außerdem gibt es den modulo-Operator % und eine Reihe von logischen oder shift-Operatoren. Eine genaue Aufstellung aller Operatoren ist in der Operatortabelle zu finden: Priorität

Assoziativität

14

61 62

Symbol

Funktion

l

.

Zugriff auf Variablen/ Methoden

14

l

[]

Indexoperator

14

l

()

Funktionsaufruf

13

r

+, -

Vorzeichen

13

r

++, --

unäres Inkrement/Dekrement

13

r

~

bitweise Negation

13

r

!

logische Negation

13

r

()

Cast

12

l

*, /, %

Multiplikative Operatoren

11

l

+, -

Additive Operatoren

10

l

, >>>

Shift-Operatoren

9

l

, =, instanceof

relationale Operatoren

8

l

==, !=

Gleichheitsoperatoren

Siehe auch: Methodenaufruf (S. 30) Siehe auch: Zuweisung (S. 33)

46

Kapitel 2: Elementare Objekte und Ausdrücke

7

l

&

bitweises oder logisches UND (and)

6

l

^

bitweises oder logisches exklusiv-ODER (xor)

5

l

|

bitweises oder logisches inklusiv-ODER (or)

4

l

&&

logisches UND (and)

3

l

||

logisches inklusivODER (or)

2

r

?:

Konditional-Operator

1

r

=, *=, /=, %=, +=, -=, > =, >>> =, &=, ^=, |=

Tabelle 15: Operator-Prioritäten in Java

Diese Tabelle beschreibt alle in Java verfügbaren Operatoren. Dazu gehören Zugriffsoperatoren auf Komponenten von Datenstrukturen, arithmetische Operatoren aber auch Funktionsaufrufe oder Typumwandlung. Im Einzelnen werden die wichtigen Operatoren noch besprochen. Die vielen Stufen der Bindungsstärke, hier Priorität genannt, erlauben das weitgehend klammerfreie Niederschreiben eines Ausdrucks. Je höher die Zahl in der Spalte "Priorität", desto stärker bindet dieser Operator. Zur Klarstellung sind Klammern jedoch oft hilfreich. Die Assoziativität gibt an, wie bei Operatoren gleicher Priorität zusammengefasst wird (von links (l) oder rechts (r)). D.h. der Ausdruck 1/2*x bedeutet 0.5x und nicht den Kehrwert von 2x .

2.11 Syntax der Ausdrücke 2.11.1 Typspezifische Ausdrücke Ein Ausdruck im eigentlichen Sinn berechnet einen Wert eines Typs.

2.11 Syntax der Ausdrücke

47

Die ersten fünf Zeilen dieses Diagramms bezeichnen die typspezifischen Ausdrücke, die in den folgenden Abschnitten behandelt werden. Klassenausdrücke folgen dann in dem Kapitel über Vereinbarung neuer Klassen63. Der bedingte Ausdruck64 in der letzten Zeile verkürzt manchmal das Programm, da er eine Anweisung spart. Er sollte aber von Anfängern nicht verwendet werden, da er sehr unübersichtlich ist.

2.11.2 Numerische Ausdrücke Hier betrachten wir die gemeinsamen Eigenschaften der beiden numerischen Ausdrücke.

Die Semantik sollte klar sein, wenn zur Not die Bedeutung der Operatoren65 nachgeschlagen wird. Der Methodenaufruf liefert hier einen Wert oder eine Referenz zurück. Eine erweiterte Form eines Ausdrucks kann auch als eine Anweisung66 auftreten. Das ist aber hier nicht wichtig.

63

Siehe auch: Vereinbarung neuer Klassen (S. 63) Siehe auch: Bedingter Ausdruck (S. 55) 65 Siehe auch: Bedeutung der Operatoren (S. 41) 66 Siehe auch: Anweisung (S. 123) 64

48

Kapitel 2: Elementare Objekte und Ausdrücke

2.12 Ganzzahlige Ausdrücke SEMANTIK: Für ganze Zahlen, also Werte der Datentypen int, byte, short oder long stehen die arithmetischen Operatoren +, -, *, / mit den üblichen Vorrangregeln ("Punkt vor Strich") zur Verfügung. Klammern sind wie gewohnt einsetzbar. Die unären (Vorzeichen-)Operatoren +, - binden stärker als Multiplikation, Division und modulo-Operation %, die den ganzzahligen Rest bei der Division berechnet. Binäre Operatoren werden von links nach rechts, unäre von rechts nach links ausgeführt. Die Operatoren sind nur für int und long definiert. Werte aus den kleineren Zahlentypen werden automatisch auf int transformiert. SEMANTIK: Bei der ganzzahligen Division werden die Nachkommastellen abgeschnitten, d.h. die betragsmäßig kleinere der beiden ganzen Zahlen, die dem Ergebnis benachbart sind, wird genommen.

SEMANTIK: a%b ist der Rest bei der Division. Da diese abschneidet, gilt für negative a: a%b = -(|a|%b) Es gilt deshalb für positive b: a = (a/b)b + a%b. Für negative b ist a%b = a%|b| Beispiel 25: System.out.println("Ganzzahlige Division: "); System.out.println("b / a = " + b / a); System.out.println("a / b = " + a / b); System.out.println("a % c = " + a % c); System.out.println("a / d = " + a / d); System.out.println("(a/b)*b + a%b = " + ((a/b)*b + a%b)); cdrom:/buch/examples/java/Elementar/IntAusdruck1.java

Zu diesen Operatoren kommen die unären Inkrementierungen (++) und Dekrementierungen (--) hinzu, jeweils als Präfixform, die vor der Verwendung des Operanden ausgewertet wird, oder als Postfix-Operator, der nach der Verwendung des Operanden ausgeführt wird. D.h. beim Präfixoperator wird zuerst erhöht und dieser Wert dann weiter gegeben, beim Postfixoperator entgegengesetzt. Beispiel 26: int a = 4; System.out.println("In- und Dekrementierung: "); System.out.println("a = " + a);

// a

= 4

2.12 Ganzzahlige Ausdrücke System.out.println("a-System.out.println("a System.out.println("--a System.out.println("a

49 = = = =

" " " "

+ + + +

a--); a); --a); a);

// // // //

a-a --a a

= = = =

4 3 2 2

cdrom:/buch/examples/java/Elementar/IntAusdruck2.java

Ganze Zahlen können verglichen werden. Die Vergleichsoperatoren sind ==, !=, =, sie binden schwächer als additive Operatoren, Gleichheit (==) und Ungleichheit (!=) noch schwächer als die anderen. Das Ergebnis ist ein Wahrheitswert vom Typ boolean. Beispiel 27: System.out.println("Vergleiche: "); System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("a < b = " + (a < b)); cdrom:/buch/examples/java/Elementar/IntAusdruck3.java

2.12.1 Akkumulierende Zuweisungen Häufig kommen in Anwendungen aufsaldierende Zuweisungen vor, ein Produkt wird berechnet, ein Zähler hochgezählt. prod = prod * faktor; count = count + 1; Das kann abkürzend mit den akkumulierenden Zuweisungen geschrieben werden, die für jeden binären Operator zur Verfügung stehen. prod *= faktor; count += 1; Noch kürzer und auch wieder übersichtlicher schreibt sich der letzte Ausdruck als count++; oder ++count; Die Inkrementierung und Dekrementierung verändern innerhalb eines Ausdrucks den Wert eines Operanden, erzeugen also einen Seiteneffekt. Sie sollten deshalb nur verwendet werden, falls die Bedeutung ganz klar und der Seiteneffekt erwünscht ist. 2.12.2 Shift-Operationen Ganze Zahlen werden als Dualzahlen - also Bit-Strings von Nullen und Einsen implementiert, negative Zahlen im 1er-Komplement. Die arithmetischen Operationen, vor allem die Multiplikation und die Division, können deshalb durch Linksbzw. Rechtsshift beschleunigt werden.

50

Kapitel 2: Elementare Objekte und Ausdrücke

Operand Wirkung

Beispiel Ergebnis

Eingabe in Ergebnis in Bitdarstellung Bitdarstellung

a b verschiebt die Bits 35 >> 2 um b Stellen nach rechts und füllt der Rest mit dem höchstwertigen Bit auf

8

00100011

00001000

a >>> b verschiebt die Bits -1 >> > 3 um b Stellen nach rechts und füllt den Rest mit 0 auf

63

11111111

00011111

Tabelle 16: Shifts

Die obige Tabelle ist falsch! Sie erläutert die Shiftoperationen für Bytes. Ausgeführt werden die Operationen aber für 32-bit Zahlen. Man beachte das folgende Beispiel. Beispiel 28: System.out.println("Verschieben: "); System.out.println("32 um 1 nach links geshiftet " + "(32 >> 22): " + (-1 >>> 22)); System.out.println("-1 um 3 nach rechts geschiftet " + "(-1 >> 3): " + (-1 >> 3));

// 64 // 128 // 256 // 16 // 1023 // -1

cdrom:/buch/examples/java/Elementar/IntAusdruck4.java

Die Auffassung von ganzen Zahlen als Bit-Strings führte zu der Idee, die logischen Operationen bitweise auszuführen. Beispiel 29: System.out.println("Logische System.out.println("31 & 32: System.out.println("32 & 32: System.out.println("31 ^ 32: System.out.println("32 ^ 32: System.out.println("31 | 32: System.out.println("32 | 32:

Operanden: "); " + (31 & 32)); " + (32 & 32)); " + (31 ^ 32)); " + (32 ^ 32)); " + (31 | 32)); " + (32 | 32));

// // // // // //

0 32 63 0 63 32

cdrom:/buch/examples/java/Elementar/IntAusdruck6.java

2.13 Reelle Ausdrücke

51

2.13 Reelle Ausdrücke Das numerische Rechnen, insbesondere im technisch-wissenschaftlichen Bereich, findet im Raum der reellen Zahlen, im Rechner dargestellt als Gleitkommazahlen67 der zwei Typen float und double statt. Intern sind die Werte, Approximationen der reellen Zahlen, in wissenschaftlicher Darstellung als x = mx * 2ex gegeben, wobei die Mantisse mx für float 24 und für double 53 Dualziffern besitzt und zwischen 1 und 2 liegt, führende Nullen also ausgeschlossen sind. Die Darstellung entspricht dem IEEE Format 754 für Gleitkommazahlen. Zahlen wie 0.001 werden mit einem entsprechenden ganzzahligen Exponenten als 1 * 10-3 dargestellt. Auch der Exponentenbereich ist beschränkt. Die Basis ist im Rechner 2, im Programmquelltext jedoch 10. Im Quelltext können für Konstanten auch mehr als eine Ziffer vor dem Dezimalpunkt oder führende Nullen angegeben werden, die Umrechnung in das interne Format erfolgt automatisch, aber nicht fehlerfrei, so ist z.B. 0.1 nicht exakt als endlicher Dualbruch darstellbar. Beispiel 30: System.out.println("Grundrechenarten: "); System.out.println("a + b + c = " + (a + b System.out.println("a - b - c = " + (a - b System.out.println("a * b * c = " + (a * b System.out.println("a / b / c = " + (a / b

+ * /

c)); c)); c)); c));

cdrom:/buch/examples/java/Elementar/RealAusdruck1.java

Neben den 4 Grundrechenarten +, -, *, / stehen für die Datentypen float und double auch Vergleiche und eine Reihe von mathematischen Standardfunktionen zur Verfügung. Beispiel 31: System.out.println("Standardfunktionen: "); System.out.println("sin(a) = " + (Math.sin(a))); System.out.println("pow(a,b) = " + (Math.pow(a, b))); System.out.println("a < b = " + (a < b)); cdrom:/buch/examples/java/Elementar/RealAusdruck2.java

Man beachte außerdem, dass die Operationen in der Regel nicht exakt sind, sondern mit Rundungsfehlern zu kämpfen haben. So ist (1.0/49.0)*49.0 != 49.0/49.0 == 1.0. Die dreimalige Addition von 0.1 ist zwar gleich 3 * 0.1, aber ungleich 0.3. Bei 0.6 ist es gerade umgekehrt. Beispiel 32: Rundungen // Wir berechnen (1.0 / 49.0) * 49. und 49.0 / 49.0, was // mathematisch eigentlich das gleiche Ergebnis liefern muesste. // In Java jedoch gilt: double c = 1.0 / 49.0; double a = c * 49.0; System.out.println("Es gilt: (1.0 / 49.0) * 49.0 = " + a); double b = 49.0 / 49.0; System.out.println("ist nicht gleich 49.0 / 49.0 = " + b); System.out.println();

67

Siehe auch: Gleitkommazahlen (S. 41)

52

Kapitel 2: Elementare Objekte und Ausdrücke

// Wir berechnen nun 0.1 + 0.1 + 0.1 und // fest, dass dies identisch, allerdings System.out.println("0.3:"); a = 0.1; b = a + a + a; c = 3 * a; System.out.println("Es gilt: 0.1 + 0.1 + + c + " = 3 * 0.1"); System.out.println("Aber nicht " + 0.3 + System.out.println();

3 * 0.1 und stellen ungleich 0.3 ist:

0.1 = " + b + " = " " gleich " + c + " !");

// Schliesslich berechnen wir 0.1 + 0.1 + ... und 6 * 0.1 und // stellen hier fest, dass 0.1 + 0.1 + ... gleich 0.6 ist, // aber ungleich 6 * a: System.out.println("0.6:"); a = 0.1; b = a + a + a + a + a + a; c = 6 * a; System.out.println("Es gilt: b = 0.1 + 0.1 + 0.1 + " + "0.1 + 0.1 + 0.1 = " + b); System.out.println("Und ferner: c = 6 * a = " + c); System.out.println("Also nicht: " + c + " gleich " + "0.6!"); cdrom:/buch/examples/java/Elementar/Arithmetik.java

SEMANTIK: Es gelten die üblichen Vorrangregeln ("Punkt vor Strich"). Klammern sind wie gewohnt einsetzbar. Die unären (Vorzeichen-)Operatoren +,- binden jedoch stärker als multiplikative Operatoren. Binäre Operatoren werden von links nach rechts, unäre von rechts nach links ausgeführt. Auch die modulo-Operation %, die den Rest bei der Division berechnet, steht zur Verfügung. Beispiel 33: System.out.println("modulo-Operator: "); System.out.println("a % b= " + a % b); cdrom:/buch/examples/java/Elementar/RealAusdruck3.java

Außerdem treten die von der ganzzahligen Arithmetik68 her bekannten akkumulierenden Zuweisungen auf. Auch unäre Inkrementierungen (++) und Dekrementierungen (--) sind möglich aber eher ungebräuchlich. Die wichtigsten mathematischen Standardfunktionen befinden sich in der Klasse Math.

68

Siehe auch: Ganzzahlige Arithmetik (S. 47)

2.14 Boolesche Ausdrücke, Bedingungen

Methodenname

Funktion

Math.sqrt(double a)

gibt die Wurzel von a zurück

Math.sin(double a)

gibt sin(a) zurück

Math.exp(double a)

gibt ea zurück

53

Math.pow(double a, double b) gibt die Zahl ab zurück Math.random()

gibt eine zufällige Zahl zwischen 0.0 und 1.0 zurück

Math.round(double a)

rundet a auf die nächste Ganzzahl

Math.ceil(double a)

rundet a auf die nächstgrößere Ganzzahl

Math.floor(double b)

gibt die nächstkleinere Ganzzahl zurück

Tabelle 17: Methoden von java.lang.Math

Genauere Information bietet die Java API Dokumentation69. Wenn die Klasse Math mit import static importiert70 wird, kann in Java 1.5 auf den Klassennamen Math beim Aufruf verzichtet werden und die Ausdrücke so vereinfacht werden.

2.14 Boolesche Ausdrücke, Bedingungen DEFINITION: Der Wertebereich des Typs boolean besteht aus den beiden Konstanten true und false. Logische oder boolesche Ausdrücke sind Bedingungen, die auch als Ergebnis von Vergleichen71 auftreten können. Boolesche Werte können mit den folgenden Operatoren verknüpft werden: !

not

|, ||

or

&, &&

and

^

xor

Tabelle 18: Operatoren

69

Siehe API-Dokumentation: java.lang.Math Siehe auch: Import (S. 57) 71 Siehe auch: Vergleichen (S. 55) 70

54

Kapitel 2: Elementare Objekte und Ausdrücke

Die beiden Operatoren ||, && werden üblicherweise verwendet. Sie werten den rechten Operanden nur dann aus, wenn es zur Ergebnisfindung nötig ist. SEMANTIK: a & b liefert true, wenn sowohl a als auch b wahr sind und false, wenn a oder b oder beide falsch sind. Es werden immer beide Teilausdrücke ausgewertet. a && b liefert das gleiche Ergebnis. Wenn a allerdings bereits falsch ist, wird false zurückgegeben und b nicht mehr ausgewertet.

SEMANTIK: a | b ergibt true, wenn mindestens einer der beiden Ausdrücke a oder b wahr ist. Beide Teilausdrücke werden ausgewertet. a || b ergibt true, wenn mindestens einer der beiden Ausdrücke a oder b wahr ist. Ist allerdings bereits a wahr, so wird true zurückgegeben und b nicht mehr ausgewertet.

SEMANTIK: a ^ b ergibt true, wenn beide Ausdrücke einen unterschiedlichen Wahrheitswert haben. Beispiel 34: // Beispiel für & und && : // false, da erster Ausdruck falsch ist und zweiter nicht // ausgewertet wird boolean result1 = (x != 0) && (y == 1 / x);

2.15 Vergleiche von Werten und Objekten

55

// Fehler, da zweiter Ausdruck ausgewertet wird, // Division durch 0 aber nicht erlaubt ist boolean result2 = (x != 0) & (y == 1 / x); cdrom:/buch/examples/java/Elementar/BoolAusdruck.java

Beispiel 35: // Beispiel für |, || und ^ : // true, da mindestens einer der beiden Ausdruecke // - hier beide - wahr ist boolean result1 = (x == 0) | (y == 0); // true, da mindestens einer der beiden Ausdruecke // - hier beide - wahr ist boolean result2 = (x == 0) || (y == 0); // false, da beide Ausdruecke den gleichen Wahrheitswert, // naemlich wahr, haben boolean result3 = (x == 0) ^ (y == 0); cdrom:/buch/examples/java/Elementar/BoolAusdruck2.java

2.15 Vergleiche von Werten und Objekten

2.15.1 Vergleich von Zahlen Zahlen können verglichen werden. Die Vergleichsoperatoren sind ==, !=, =, sie binden schwächer als additive Operatoren, Gleichheit (==) und Ungleichheit (!=) noch schwächer als die anderen. Das Ergebnis ist ein Wahrheitswert vom Typ boolean. Beispiel 36: System.out.println("Vergleiche: "); System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("a < b = " + (a < b)); cdrom:/buch/examples/java/Elementar/IntAusdruck3.java

2.15.2 Objektvergleich Referenzen auf Objekte können ebenfalls mit den Operatoren == und != verglichen werden. Das Ergebnis ist true, falls die Referenzen gleich sind, also zweimal das gleiche Objekt verwendet wird. Darüber hinaus können Objekte mittels der Methode equals(Object) verglichen werden. Für Strings liefert sie z. B. den Zeichenketten-Vergleich. Einzelheiten72 bei der Vereinbarung neuer Klassen. 72

Siehe auch: Die Methode equals() (S. 79)

56

Kapitel 2: Elementare Objekte und Ausdrücke

2.16 Der bedingte Ausdruck Es kommt in der Praxis relativ häufig vor, dass sich der Wert einer Variablen abhängig von einer Bedingung ergibt. Das kann selbstverständlich mit einer bedingten Anweisung73 erledigt werden. Eine etwas kürzere, sich auf Ausdrücke beschränkende Sichtweise dieses Problems wird durch den bedingten Ausdruck, dargestellt durch den Fragezeichenoperator, realisiert. Diese funktionale Sicht ist durch folgende Syntax gegeben:

SEMANTIK: Der Operator ? : bearbeitet also 3 Operanden, eine Bedingung und 2 typ-spezifische Ausdrücke von zuweisungskompatiblem Typ. Ist der Wert der Bedingung true, so ist der Wert des Ausdrucks gleich dem ersten Ausdruck , anderenfalls gleich dem zweiten Ausdruck. Beispiel 37: Bedingter Ausdruck // Das Minimum zweier Variablen laesst sich so bestimmen: min1 = x < y ? x : y; // Dies laesst sich in einen Ausdruck einbauen: min2 = 4 + x < y ? x : y; // oder schachteln: min3 = 0 < x ? (x < y ? x : y) : (x < y ? -y : -x); cdrom:/buch/examples/java/Elementar/BedAusdruck.java

2.17 Die Klasse System In der Klasse System werden grundlegende Objekte und Methoden zur Interaktion mit dem umgebenden System (Betriebssystem) bereitgestellt. Dies sind insbesondere die Standarddatenströme zur Ein- und Ausgabe74:

73 74

Siehe auch: Bedingte Anweisung (S. 110) Siehe auch: Ein- und Ausgabe (S. 30)

2.18 Import von Klassen und Paketen

57

Objekt

Beschreibung

System.in

Die vom Betriebssystem zur Verfügung gestellte Standardeingabe. Normalerweise mit der Tastatur verbunden.

System.out

Die vom Betriebssystem zur Verfügung gestellte Standardausgabe, auf der das Programm weiterverwertbare Informationen ausgibt. Wird üblicherweise auf der Konsole angezeigt oder an das weiterverwertende Programm weitergeleitet.

System.err

Die vom Betriebssystem zur Verfügung gestellte Standardfehlerausgabe. Wird meist auch auf der Konsole angezeigt. Dient normalerweise dazu, Statusinformation (insbesondere Fehlermeldungen) zum Programmablauf auszugeben.

Tabelle 19: Standarddatenströme in der Klasse System

Die Methode getProperties() liefert Informationen aus und über das System, auf dem das Programm läuft, z.B. die Java Version oder den Pfad zum Benutzer-Arbeitsverzeichnis. Die Methode exit(int status) beendet die JVM, und damit auch das Programm. Ein Statuswert gleich 0 bedeutet üblicherweise eine korrekte Terminierung. Das Beenden eines Programms mit System.exit(int status) ist allerdings nicht die normale Art der Terminierung und sollte nur dann verwendet werden, wenn ein Programmstatus als Rückgabewert erwartet wird. Normalerweise terminiert ein Programm durch Erreichen des Endes des Rumpfes der Methode main oder durch Erzeugen eines Fehlerabbruchs. Weitere Elemente werden selten gebraucht. Man kann sie in der Java API Dokumentation75 nachschlagen.

2.18 Import von Klassen und Paketen Die Organisation des Java-Quelltextes in Klassen, die jeweils in einer eigenen Datei liegen, ist für große Programme nicht ausreichend. DEFINITION: Zusammengehörige Klassen bilden ein Paket, ein Modul.

SEMANTIK: Entsprechend besteht der vollständige Klassenname aus dem eigentlichen Klassennamen, dem der Paketname vorangestellt wird.

75

Siehe API-Dokumentation: java.lang.System

58

Kapitel 2: Elementare Objekte und Ausdrücke

Ein solches Modul besteht aber nicht aus einer (Übersetzungs-) Einheit, sondern setzt sich aus mehreren Teilen zusammen. Ein Paket wird in einem Verzeichnis (Ordner) gespeichert. Die Verzeichnishierarchie muss einschließlich aller Namen mit der Pakethierarchie übereinstimmen. 2.18.1 Importklausel Sichtbare Klassen aus anderen Paketen werden mit ihrem vollständigen Namen angesprochen. Alternativ können sie auch importiert werden. Dazu wird für jede importierte Klasse oder für jedes benötigte Paket eine Importklausel vor der Definition der importierenden Klasse angegeben.

Der Paketname wird dem Klassennamen vorangestellt. Sollen alle Klassen aus einem Paket importiert werden, so wird hinter dem trennenden Punkt ein * geschrieben. Wir empfehlen, verwendete Klassen explizit zu importieren. Paketnamen können geschachtelt werden. Der Name des Teilpakets wird dann mit einem trennenden Punkt an den Paketnamen angefügt. Diese Schachtelung hat keine Auswirkungen auf den Namensraum76, lediglich die Verzeichnisstruktur ist anzupassen.

Um Konflikten bei Paketnamen vorzubeugen, verwendet man üblicherweise einen "Internetnamen" in umgekehrter Reihenfolge oder zumindest den Projektnamen als Paketpräfix. So erhält man eindeutige Namen.

76

Siehe auch: Datenkapselung mit Paketen (S. 148)

2.18 Import von Klassen und Paketen

59

In Java 1.5 können auch die Klassenkonstanten77 und -methoden78 einer importierten Klasse direkt sichtbar gemacht werden. Das geschieht durch eine leichte Veränderung der Importklausel.

So werden zum Beispiel durch import static java.lang.Math.*; alle Standardfunktionen direkt sichtbar. Die meist gebrauchten zum Lieferumfang des Java Systems gehörenden Pakete sind Teile der Pakete java oder javax. Außer für das Paket java.lang müssen die in Ihnen enthaltenen Klassen importiert werden. Beispiel 38: Import-Klauseln

import java.io.PrintWriter import java.awt.* Beispiel 39: Import der Eingabeklasse

Wir zeigen drei äquivalente Vorgehensweisen zur Verwendung der Klasse SimpleInput aus dem Paket simple.io. Import aller Klassen des Pakets simple.io. // importiere alles aus simple.io import simple.io.*; public class SimpleIO1 { public static void main(String[] args) { System.out.print("Ersten Faktor eingeben: "); int factor1 = SimpleInput.in.getInt(); System.out.print("Zweiten Faktor eingeben: "); int factor2 = SimpleInput.in.getInt(); } } cdrom:/buch/examples/java/Elementar/SimpleIO1.java

Import nur der Klasse SimpleInput: // importiere nur SimpleInput aus simple.io import simple.io.SimpleInput; public class SimpleIO2 { public static void main(String[] args) { System.out.print("Ersten Faktor eingeben: "); int factor1 = SimpleInput.in.getInt(); System.out.print("Zweiten Faktor eingeben: "); int factor2 = SimpleInput.in.getInt(); } } cdrom:/buch/examples/java/Elementar/SimpleIO2.java

77 78

Siehe auch: Klassenkonstanten (S. 82) Siehe auch: Klassenmethoden (S. 87)

60

Kapitel 2: Elementare Objekte und Ausdrücke

Kein Import, stattdessen Verwendung des vollständigen Klassennames: // importiere nichts public class SimpleIO3 { public static void main(String[] args) { System.out.print("Ersten Faktor eingeben: "); int factor1 = simple.io.SimpleInput.in.getInt(); System.out.print("Zweiten Faktor eingeben: "); int factor2 = simple.io.SimpleInput.in.getInt(); } } cdrom:/buch/examples/java/Elementar/SimpleIO3.java

2.19 Auftreten von Ausnahmen Wir haben sicher schon die Erfahrung gemacht, dass Programme mit einer Fehlermeldung abstürzten, etwa beim Einlesen oder Umwandeln von Daten, die nicht der erwarteten Form entsprachen, beim Überschreiten des Indexbereiches eines Arrays oder beim Zugriff auf leere Referenzen. Beispiel 40: Ausnahme bei Arrayzugriff int[] array = new int[5]; System.out.println(array[7]); cdrom:/buch/examples/java/Ausnahmen/Fehler.java

Erstaunlicherweise kommen jedoch bei arithmetischen Ausdrücken mit Gleitkommazahlen oder bei unseren einfachen Einlesemethoden keine solchen Ausnahmesituationen vor. Beispiel 41: Keine Ausnahme bei Verwendung von SimpleInput double x; double y; do { System.out.print("x eingeben: "); x = SimpleInput.in.getDouble(); System.out.print("y eingeben: "); y = SimpleInput.in.getDouble(); System.out.println("x / y = " + x / y); } while ( !Double.isNaN(x) && !Double.isNaN(y) ); cdrom:/buch/examples/java/Ausnahmen/SimpleKeinFehler.java

In diesem Beispiel werden vordefinierte Werte eingesetzt, anstatt eine Fehlermeldung zu verursachen. Bei der Behandlung von Gleitkommazahlen ist das in Ordnung es gibt ja "Unzahlen" wie Double.NaN oder Double.POSITIVE_INFINITY und man kann meistens nachträglich feststellen, dass etwas schief gegangen ist. Demgegenüber ist das willkürliche Setzen einer ganzen Zahl auf Integer.MIN_VALUE schon gefährlicher, da so ein regulärer Wert gesetzt wurde.

2.19 Auftreten von Ausnahmen

61

2.19.1 Fehlerabbruch Eine Ausnahme muss behandelt79 werden oder wird weiter gereicht. Gelangt sie durch Weitergabe in einer Kommandozeilen-Applikation bis zur virtuellen Maschine, so wird das Programm abgebrochen, sowie eine Fehlermeldung und die Aufrufgeschichte ausgegeben.

79

Siehe auch: Ausnahmebehandlung (S. 232)

Kapitel 3 Vereinbarung neuer Klassen

Die richtige Strukturierung der Daten eines Problems ist für die Qualität und Dauerhaftigkeit einer Lösung mindestens so entscheidend wie die algorithmische Aufbereitung. Die Datensätze für komplexe Strukturen, wie zum Beispiel Walkman oder Warenkorb, müssen aufgebaut werden. Als Bausteine dienen dabei die elementaren Datentypen80 oder bereits existierende Klassen wie String oder StringBuffer. In Java wird zur Definition von Datenstrukturen bzw. Datentypen das Klassenkonzept verwendet, mit dessen Hilfe auch weitere wesentliche Konzepte der objektorientierten Programmierung, die wir in der Begriffseinführung81 vorgestellt haben, verwirklicht werden. • Klassen stellen durch Angabe von Attributen eine Datenstruktur bereit. • Sie erlauben die Konstruktion von Objekten, d.h. Exemplaren (Instanzen) dieser Datenstruktur (Klasse). • Durch Angabe von Methoden werden die Aktionen vorgeschrieben, die Objekte dieses Typs durchführen können. Damit ist eine eigenständige Zustandsverwaltung möglich. • Durch Mechanismen der Datenkapselung legen Klassen die Grundlage für abstrakte Datentypen. • Klassen werden bei der Fehler- und Ausnahmebehandlung benutzt. • Vererbung oder Klassifikation wird mit Hilfe von Klassen realisiert. • Klassen stellen Funktionalität bereit, beschreiben eine Dienstleistung (Service). So lässt sich eine Erweiterung des Sprachumfangs durch Klassenbibliotheken einfach vornehmen. • Klassen erfüllen Verträge82.

80 81 82

Siehe auch: Elementare Datentypen (S. 38) Siehe auch: Einführung (S. 1) Siehe auch: Vertrag (S. 125)

64

Kapitel 3: Vereinbarung neuer Klassen

DEFINITION: Wir fassen eine Klasse als einen Datentyp83 auf, der einerseits die Struktur seiner Exemplare oder Objekte durch Angabe von Attributen festlegt und so einen Wertebereich definiert, andererseits auch durch Angabe von Methoden die Aktionen vorschreibt, die Objekte dieses Typs durchführen können.

3.1 Überblick

3.1.1 Attribute DEFINITION: Der Zustand eines Objekts wird durch die Werte seiner Attribute84 bestimmt. Dieser enge Zustandsbegriff des Programmierers wird ergänzt durch die weite, verhaltensbasierte Sicht des Modellierers: DEFINITION: Eine wichtige Charakterisierung von Objekten ist deren Eigenschaft, abhängig von ihrem Zustand unterschiedlich auf Nachrichten reagieren zu können. Der Zustand des Objekts bestimmt also sein Verhalten. Aus dieser Sicht ist die oben formulierte Zustandssicht zu eng. Ein Zustand kann für verschiedene Werte von Attributen gleich bleiben, nur eine gewisse Auswahl oder ein spezielles Intervall wirken bestimmend. Außerdem kann auch die Aktion, mit der das Objekt gerade beschäftigt ist, zum Zustand beitragen. Beispiel 42: Zustandsänderung

Ein Attribut einer Klasse Zeit ändert jede Sekunde seinen Wert. Der Wechsel zwischen dem Zustand "Tag" und "Nacht" findet allerdings nur einmal am Tag statt.

83 84

Siehe auch: Elementare Datentypen (S. 38) Siehe auch: Klassenattribute (S. 82)

3.2 Entwurf einer Klasse

65

3.1.2 Methoden DEFINITION: Methoden85 sind Handlungsvorschriften, die innerhalb eines Klassenrumpfes vereinbart werden. Sie verwirklichen die Funktionalität der Klasse. Sie dienen dazu, den Zustand von Objekten zu berechnen, abzufragen oder zu verändern. Sie führen Aktionen durch, die auf dem Zustand aufbauen. 3.1.3 Konstruktoren Eine Klasse lässt sich als Konstruktionsvorschrift86 für ihre Exemplare (Instanzen, Objekte) auffassen. Objekte einer Klasse werden durch Aufruf einer speziellen Methode, eines Konstruktors, erzeugt. DEFINITION: Ein Konstruktor ist eine spezielle benutzerdefinierte Methode, die ein Objekt der Klasse erzeugt.

3.2 Entwurf einer Klasse Beim Klassenentwurf ist die Funktionalität (das Verhalten) der Klasse festzulegen. Mit anderen Worten ist eine Dienstleistung zu spezifizieren, die von der Klasse ausgeführt werden soll. Dabei wird einerseits ein Wertebereich festgelegt, andererseits Konstruktoren für Objekte aus diesem Bereich vereinbart und Methoden angegeben, die die möglichen Handlungen der Objekte beschreiben. Prinzipiell sollte die Spezifikation so speziell wie nötig aber auch so allgemein wie möglich gehalten sein, um die Anforderungen des Benutzers klar formulieren zu können aber auch dem Implementierer noch einige Freiheiten und Entwurfsentscheidungen zu überlassen. Beispiel 43: Klasse 'Preis': Spezifikation

Es soll eine Klasse bereitgestellt werden, die einen Preis modelliert. Ein Preis ist ein Paar von zwei nichtnegativen ganzen Zahlen (man denke an Euro und Cent), deren zweite kleiner als 100 ist. Diese Bedingung wollen wir als Klasseninvariante stets aufrecht erhalten. Objekte sollen auf zweierlei Art konstruiert werden können: durch Angabe von 2 Zahlen, die die Bedingungen erfüllen und durch Angabe des gesamten Betrages in einer Zahl (Cent). Auf die Werte der

85 86

Siehe auch: Methodendeklaration (S. 75) Siehe auch: Objekterzeugung (S. 72)

66

Kapitel 3: Vereinbarung neuer Klassen

ersten und zweiten Zahl soll lesend zugegriffen werden können. Ein Preis soll erhöht werden können, indem ein zweiter Preis hinzu addiert, oder der alte Preis um einen Prozentsatz erhöht wird. Später werden wir noch den schreibenden Zugriff auf die Komponenten diskutieren.

Beispiel 44: Klasse 'Preis': Erste Implementierung

Als Beispiel betrachten wir eine Klasse Preis1, die einen Preis mit Hilfe von zwei Attributen euro und cent speichern kann. Bei der Konstruktion aus zwei Zahlen wird die Invariante überprüft. Entsprechen die Zahlen nicht der Vorschrift, so wird ein Preis mit 0 Euro und 0 Cent kreiert und außerdem ein Attribut gesetzt, das anzeigt, dass dieser Preis nicht definiert ist. private int euro = 0; private int cent = 0; private boolean definiert = false; public Preis1() { } public Preis1(int euro, int cent) { if ((euro >= 0) && (cent >= 0) && (cent < 100)) { this.euro = euro; this.cent = cent; definiert = true; } } public Preis1(int ce) { if (ce >= 0) { euro = ce / 100; cent = ce % 100; definiert = true; } } cdrom:/buch/examples/java/Klasse/Preis1.java

Der erste Konstruktor weist keine Werte zu und die Attribute behalten ihre Default-Werte, also 0. Der zweite Konstruktor weist nur ganzzahlige Euro- und Centbeträge zu, und auch das nur, falls diese größer oder gleich 0 bzw. kleiner als 99 sind. Der letzte Konstruktor weist einen Betrag in Cents zu, prüft aber ebenfalls vorher, ob dieser Betrag positiv ist und normiert den Centbetrag. Um die Konstruktoren nach außen sichtbar und damit aufrufbar zu machen, müssen sie als public deklariert werden. Die Erzeugung von Objekten erfolgt dann über den Aufruf von new: // Drei neue Preise erzeugen Preis1 p1 = new Preis1(10, 69); Preis1 p2 = new Preis1(1069); Preis1 p3 = new Preis1(); cdrom:/buch/examples/java/Klasse/Preis1.java

Die Attribute sind, da sie als private gekennzeichnet sind, geschützt und können von außen nicht direkt angesprochen werden. Die Methoden zum Zugriff auf ihre Werte sind ganz einfach. Die Methoden zum Erhöhen setzen die in der Spezifikation als Vorbedingung angegebene Invariante voraus.

3.2 Entwurf einer Klasse

67

public void erhoehe(Preis1 otherPrice) { cent += otherPrice.cent; if (cent > 100) { euro++; cent = cent - 100; } euro += otherPrice.euro; definiert = true; } public void erhoehe(int prozent) { int tCent = euro * 100 + cent; tCent = tCent + (tCent * prozent) / 100; euro = tCent / 100; cent = tCent % 100; } public int getCent() { return this.cent; } public int getEuro() { return this.euro; } public boolean isDefiniert() { return definiert; } public int getPreisInCent() { return 100 * euro + cent; } cdrom:/buch/examples/java/Klasse/Preis1.java

Diese Klasse lässt sich etwa so verwenden: // Teste ob der Preis p3 (bzw. p2) definiert ist. // Falls ja, wird der Euroanteil des Preises ausgegeben. if (p3.isDefiniert()) { System.out.println(p3.getEuro()); } if (p2.isDefiniert()) { System.out.println(p2.getEuro()); } System.out.println(p1.getPreisInCent()); // p1 wird um einen Preis p2 erhoeht und danach ausgegeben. p1.erhoehe(p2); System.out.println("Euro: " + p1.getEuro() + " Cent: " + p1.getCent()); // p1 wird um 10 % erhoeht und danach ausgegeben. p1.erhoehe(10); System.out.println("Euro: " + p1.getEuro() + " Cent: " + p1.getCent()); cdrom:/buch/examples/java/Klasse/Preis1.java

Die gleiche Funktionalität kann auch anders verwirklicht werden. Beispiel 45: Klasse 'Preis': alternative Implementierung

Wir vereinbaren eine Klasse Preis2, die einen Preis vollständig in Cents speichert. private int cent = 0; private boolean definiert = false;

68

Kapitel 3: Vereinbarung neuer Klassen public Preis2() { } public Preis2(int eu, int ce) { if ((eu >= 0) && (ce >= 0) && (ce < 100)) { cent = 100 * eu + ce; definiert = true; } } public Preis2(int ce) { if (ce >= 0) { cent = ce; definiert = true; } } cdrom:/buch/examples/java/Klasse/Preis2.java

Hier ist der dritte Konstruktor am einfachsten, während der zweite die Parameter umrechnet. //Drei Preis2 Preis2 Preis2

neue p1 = p2 = p3 =

Preise erzeugen new Preis2(10, 69); new Preis2(1169); new Preis2(); cdrom:/buch/examples/java/Klasse/Preis2.java

Die Methoden zum Zugriff auf die Werte sind diesmal nicht nur einfache Attributzugriffe, sondern berechnen die geforderten Werte neu. Die Methoden zum Erhöhen setzen wiederum die in der Spezifikation als Vorbedingung angegebene Invariante voraus. public int getCent() { return cent % 100; } public int getEuro() { return cent / 100; } cdrom:/buch/examples/java/Klasse/Preis2.java

// Teste ob p3 definiert ist. // Falls ja, wird der Eurobetrag ausgegeben. if (p3.isDefiniert()) { System.out.println(p3.getEuro()); } System.out.println("Euro: " + p2.getEuro() + " Cent: " + p2.getCent()); cdrom:/buch/examples/java/Klasse/Preis2.java

Neben den syntaktischen Details, die wir in den folgenden Abschnitten noch näher betrachten und erklären werden, zeigen uns die Beispiele typische Merkmale einer Klasse. Eine Klasse implementiert eine Spezifikation, erfüllt einen Vertrag. Dabei sind verschiedene Varianten denkbar. Objekte besitzen einen Zustand, dieser wird bei der Konstruktion gesetzt, kann mit Hilfe der "getter"-Methoden abgefragt und durch andere Methoden (hier die erhoehe Methoden) verändert werden.

3.3 Klassendeklaration

69

Die private vereinbarten Attribute können nicht direkt gelesen oder geschrieben werden. Deshalb kann die Klasseninvariante nicht verletzt werden. Oft werden in Analogie zu den "getter"-Methoden auch "setter"-Methoden zum Setzen der Attribute zur Verfügung gestellt. In diesen sind etwaige Bedingungen an die Attribute zu beachten. Beispiel 46: Setter Methoden

Für unseren Preis kann es sinnvoll sein die Attribute neu zu setzen. public void setCent(int cent) { if ((cent > 0) && (cent 0) { this.euro = euro; } } cdrom:/buch/examples/java/Klasse/Preis1.java

3.3 Klassendeklaration Eine einfache Klassendeklaration besteht aus der Einführung des Klassennamens und einem Rumpf. Wir zeichnen zuerst ein allgemeines Schema, welches wir dann verfeinern.

Im Klassenrumpf werden Attribute, Konstruktoren und Methoden vereinbart. Wir empfehlen diese Reihenfolge einzuhalten.

70

Kapitel 3: Vereinbarung neuer Klassen

Im Folgenden bezeichnen wir Attribute, Konstruktoren und Methoden als Bestandteile einer Klasse. SEMANTIK: Der Klassenname wird eingeführt und bezeichnet im weiteren Programm den durch die Klassendefinition geschaffenen Typ87. Er kann verwendet werden, um Objekte zu vereinbaren und zu konstruieren.

SEMANTIK: Jede Klassendefinition führt einen neuen Typ ein. Im einfachsten Fall ist das die Datenstruktur, die sich als das Kreuzprodukt der Wertebereiche der Attributtypen ergibt. Es gilt die Namensgleichheit, d.h. zwei Klassendefinitionen, deren Bestandteile gleichen Namen und Typ besitzen, die also den gleichen Wertebereich darstellen, bezeichnen dennoch zwei verschiedene Typen.

SEMANTIK: Eine Klassendefinition bildet einen eigenen Namensraum, d.h. die Namen der Bestandteile (außer Konstruktoren) zweier Klassen dürfen gleich sein. Methodennamen können gleich Attributnamen sein.

SEMANTIK: Innerhalb der Klassendefinition dürfen alle Bestandteile verwendet werden, auch wenn sie erst später vereinbart werden.

SEMANTIK: Die Bestandteile einer Klasse werden innerhalb der Definition mit ihrem Namen angesprochen. Der Zugriff von außerhalb wird durch Vergabe von Zugriffsrechten88 geregelt. Durch Verwenden von private, public und zweier weiterer Zugriffsregler wird das Prinzip der Datenkapselung in Java umgesetzt.

SEMANTIK: Als public markierte Bestandteile sind auch von außen verwendbar.

87 88

Siehe auch: Elementare Datentypen (S. 38) Siehe auch: Datenkapselung (S. 142)

3.4 Attributvereinbarung

71

SEMANTIK: private gekennzeichnete Bestandteile sind nur innerhalb der Klasse ansprechbar.

SEMANTIK: Jede public gekennzeichnete Klasse muss in einer eigenen Datei, deren Name gleich dem Klassennamen ist, abgelegt werden. Hier werden Groß- und Kleinbuchstaben unterschieden! Ein Klassenname sollte mit einem Großbuchstaben beginnen.

3.4 Attributvereinbarung Der Zustand eines Objektes wird durch die Werte seiner Attribute bestimmt. Innerhalb der Klassendeklaration werden deshalb die Attribute der Klasse definiert. Syntaktisch geschieht das durch eine Variablendeklaration, der ein Sichtbarkeitsregler vorangestellt wird.

SEMANTIK: Die Attribute werden als Variablen im Sichtbarkeitsbereich der Klassendeklaration eingeführt. Sie können also innerhalb von Konstruktoren oder Methoden verwendet werden.

72

Kapitel 3: Vereinbarung neuer Klassen

SEMANTIK: Falls die explizite Initialisierung der Attribute durch Zuweisung eines Wertes fehlt, wird mit dem Defaultwert, d.h. dem Wert 0 bei numerischen Typen, false bei boolean bzw. null bei Objektreferenzen initialisiert.

SEMANTIK: Mit final gekennzeichnete Attribute sind konstant. Wir empfehlen als erste Faustregel der Datenkapselung alle Attribute mit private als geschützt zu vereinbaren und, wenn gewünscht, lesenden und schreibenden Zugriff darauf mittels öffentlich zugreifbarer (public) Methoden zu regeln. Diese Methoden erhalten von uns immer Namen wie getAttribut oder setAttribut. Ein Attributname sollte mit einem Kleinbuchstaben beginnen.

3.5 Konstruktion von Objekten DEFINITION: Ein Konstruktor ist eine spezielle, benutzerdefinierte Methode, die ein Objekt der Klasse erzeugt. Üblicherweise werden dabei alle oder zumindest einige Attribute gesetzt. Nicht explizit initialisierte Attribute erhalten den Defaultwert. So ist der Zustand eines jeden Objekts immer vollständig definiert. SEMANTIK: Der Name eines Konstruktors ist gleich dem Klassennamen.

SEMANTIK: Ein Konstruktor hat keinen Rückgabetyp.

SEMANTIK: Der Aufruf des Konstruktors erfolgt durch den new Operator. Dieser Aufruf liefert dann als Ergebnis oder Rückgabewert das neu erzeugte Objekt.

3.5 Konstruktion von Objekten

73

SEMANTIK: Es wird der passende Konstruktor der angegebenen Klasse aufgerufen. Passend89 heißt dabei, dass die Ausdrücke, die die Argumentliste bilden, in Typ und Reihenfolge den in einer Konstruktorvereinbarung auftretenden formalen Parametern zuweisbar sind.

3.5.1 Vereinbarung von Konstruktoren Die Vereinbarung von Konstruktoren erfolgt im Klassenrumpf. Im Anschluss an den Klassennamen können in der Parameterliste formale Parameter angegeben werden. Diese werden beim Aufruf durch aktuelle, für die Initialisierung des Objekts benötigte Argumente ersetzt.

Ist kein Konstruktor vereinbart, so wird bei der Objekterzeugung der Standardkonstruktor ohne Argumente aufgerufen, der jede Komponente mit dem Defaultwert initialisiert. Ist irgendein Konstruktor mit Parametern vereinbart, wird der Standardkonstruktor nicht automatisch erstellt. Der Aufruf eines parameterlosen Konstruktors führt dann zu einem Fehler. Die folgenden Regeln gelten gleichermaßen für Konstruktoren wie für allgemeine Methoden90. Es können mehrere Konstruktoren angegeben werden, die sich jedoch in ihrem Parameterprofil, d.h. in Anzahl und/oder Typ der Parameter unterscheiden müssen. Man spricht in diesem Fall vom Überladen der Konstruktoren. SEMANTIK: Die Klassendeklaration ist der Sichtbarkeitsbereich eines Konstruktors, d.h. innerhalb von Konstruktoren kann auf Attribute und andere Methoden zugegriffen werden. Diese beziehen sich dann auf das gerade erzeugt werdende Objekt.

89 90

Siehe auch: Passendes Parameterprofil (S. 78) Siehe auch: Methoden (S. 75)

74

Kapitel 3: Vereinbarung neuer Klassen

SEMANTIK: Der Geltungsbereich der formalen Parameter ist der Konstruktorrumpf. Ihr Name kann daher mit Attributnamen oder Parameternamen anderer Konstruktoren oder Methoden übereinstimmen, muss sich aber von anderen Parameternamen oder lokalen Variablen des gleichen Konstruktors unterscheiden.

SEMANTIK: this bezeichnet stets das handelnde Objekt, zur Verdeutlichung oder Konfliktauflösung. Will man in einem Konstruktor, dessen lokale Variable oder Parameter ein Attribut verdeckt, auf das verdeckte Attribut zugreifen, so kann auf dieses Attribut mit der Selbstreferenz this zugegriffen werden. Die Schreibweise mit this ist für Konstruktoren üblich und wird empfohlen. Sind mehrere Konstruktoren vereinbart, so können diese sich als erste Anweisung gegenseitig aufrufen. Auf diese Weise lassen sich Parameterwerte voreinstellen. Beispiel 47: mehrere Konstruktoren public String titel = ""; public String interpret = ""; public String hersteller = ""; private String genre = ""; private int nTracks = 1; public CD(String titel) { this.titel = titel; } public CD(String titel, String interpret) { this(titel); this.interpret = interpret; } public CD(String titel, String interpret, int nTracks) { this(titel, interpret); if ( nTracks > 0 ) { this.nTracks = nTracks; } }

3.6 Deklaration von Methoden

75

public CD(String titel, String interpret, String hersteller, String genre, int nTracks) { this(titel, interpret, nTracks); this.hersteller = hersteller; this.genre = genre; } cdrom:/buch/examples/java/Klasse/CD.java

Der erste Konstruktor erstellt eine CD mit angegebenem Titel, der zweite mit Titel und Interpret, der dritte mit angegebenem Titel, Interpret und Anzahl der Tracks, und der letzte Konstruktor schließlich erhält alle Daten der CD. Hier wird die Voreinstellung der Attribute verwendet. Das ist nicht die schönste Vorgehensweise. Wir empfehlen bei kaskadierenden Konstruktoren (und Methoden) den mit den meisten Parametern als Basis zu verwenden. public CD2(String titel, String interpret, String hersteller, String genre, int nTracks) { this.titel = titel; this.interpret = interpret; this.hersteller = hersteller; this.genre = genre; if ( nTracks > 0 ) { this.nTracks = nTracks; } } public CD2(String titel, String interpret, int nTracks) { this(titel, interpret, "", "", nTracks); } public CD2(String titel) { this(titel, "", "", "", 1); } cdrom:/buch/examples/java/Klasse/CD2.java

3.6 Deklaration von Methoden DEFINITION: Methoden sind aufrufbare, ausführbare Handlungsvorschriften. Ihre Vereinbarung besteht aus einer Signatur und dem Rumpf.

76

Kapitel 3: Vereinbarung neuer Klassen

SEMANTIK: Die Signatur besteht (unter anderem) aus dem Rückgabetyp (Ergebnistyp des Aufrufes), dem Methodennamen und dem Parameterprofil, welches von den Namen, der Anzahl der formalen Parameter und deren Typen in der vereinbarten Reihenfolge gebildet wird.

SEMANTIK: void kennzeichnet eine Methode, die keinen Wert zurückliefert.

SEMANTIK: Eine normale (Instanz-)Methode wird immer für ein handelndes Objekt aufgerufen.

SEMANTIK: Mit static werden Methoden gekennzeichnet, die ohne Objekt aufgerufen werden. Sie werden als Klassenmethoden91 oder Funktionen 92 bezeichnet und später näher betrachtet. Die Semantik für Methoden-, Funktions- oder Konstruktordefinition und Aufruf ist sehr ähnlich. Wir fassen deshalb die bei der Konstruktorvereinbarung93 formulierte Semantik hier kurz zusammen:

91

Siehe auch: Klassenmethoden (S. 87) Siehe auch: Funktionen (S. 112) 93 Siehe auch: Konstruktorvereinbarung (S. 72) 92

3.6 Deklaration von Methoden

77

SEMANTIK: Die formalen Parameter sind als lokale Variable des Methodenrumpfes aufzufassen. Andere Methoden und Attribute der gleichen Klassenvereinbarung sind im Methodenrumpf direkt sichtbar. Es sei denn, sie werden durch formale Parameter überdeckt, dann kann auf sie mit this zugegriffen werden.

SEMANTIK: Methoden können mit verschiedenem Parameterprofil überladen werden. Beispiel 48: verdecktes Attribut public void setTracksAnzahl(int nTracks) { this.nTracks = nTracks; } cdrom:/buch/examples/java/Klasse/CD.java

Die Schreibweise mit this ist für set-Methoden, die ein Attribut setzen, üblich und wird empfohlen.

Im Methodenrumpf (Block) wird die Funktionalität implementiert. Es können lokale Variablen deklariert werden und mit ihnen und den formalen Parametern Ausdrücke gebildet werden. Der gewünschte Ergebnisausdruck wird mit einer return-Anweisung94 weiter gereicht. Bei void Methoden fehlt ein Rückgabewert, sie geben kein Ergebnis zurück.

Ein Methodenname sollte mit einem Kleinbuchstaben beginnen und eine Tätigkeit ausdrücken.

94

Siehe auch: return-Anweisung (S. 120)

78

Kapitel 3: Vereinbarung neuer Klassen

Die öffnende Klammer des Rumpfes soll hinter der Signatur am Ende der Zeile stehen. Die schließende sthet auf einer eigenen Zeile unter dem Beginn der Vereinbarung. Beispiel 49: Getter und Setter-Methoden

Häufig werden get- und set-Methoden verwendet, um auf die Attribute eines Objekts zuzugreifen. Eine weitere häufig verwendete Methode ist toString(). Sie liefert eine String-Darstellung des Objektes. public int getTracksAnzahl() { return nTracks; } public void setTracksAnzahl(int nTracks) { this.nTracks = nTracks; } public String toString() { return "CD:\n" + " Titel: " + titel + "\n" + " Interpret: " + interpret + "\n" + " Hersteller: " + hersteller + "\n" + " Genre: " + genre + "\n" + " #Tracks: " + nTracks + "\n"; } cdrom:/buch/examples/java/Klasse/CD.java

3.7 Methodenaufruf Der Methodenaufruf überträgt die Aktivität auf das durch eine Referenz angegebene (fremde) Objekt. Dieser Operand ist im einfachsten Fall eine Variable.

DEFINITION: Ein Methodenaufruf passt auf ein Parameterprofil, falls der Name übereinstimmt und die Typen der als Argumente angegebenen Ausdrücke auf die Typen der formalen Parameter passen95.

95

Siehe auch: Anpassung von Datentypen (S. 39)

3.8 Redefinition bekannter Methoden

79

SEMANTIK: Ausgewählt wird die Methode, auf deren Parameterprofil die aktuellen Argumente am besten passen. Dabei werden gleiche Typen gegenüber solchen bevorzugt, die noch an den Typ des formalen Parameters angepasst werden müssen. Gibt es mehrere solcher Methoden oder ist keine zugreifbar96, so ist der Aufruf fehlerhaft.

SEMANTIK: Der Aufruf bewirkt die Ausführung der Methode. Dabei werden zuerst die Argumente in nicht festgelegter Reihenfolge ausgewertet. Die formalen Parameter werden als Variablen erzeugt und mit den Argument-Ausdrücken initialisiert. Die dann folgende Ausführung des Methodenrumpfes endet mit der Rückgabe des Ergebniswertes oder mit dem Erreichen des Endes, falls kein Ergebniswert geliefert wird.

DEFINITION: Die eben beschriebene Art des Aufrufes bezeichnet man als Wertaufruf. Für elementare Werte können dabei durch Ausführung des Rumpfes nur lokal die Parameter verändert werden, also keine Nebenwirkungen auf den aktuellen Argumente auftreten. Für Referenzen hingegen kann zwar ebenfalls der aktuelle Verweis nicht auf ein anderes Objekt gesetzt werden, eine Veränderung des referenzierten Objektes ist jedoch problemlos möglich. Eine Methode ohne Rückgabewert (also mit Rückgabetyp void) entspricht einer Anweisung97. Wird ein numerischer Wert zurückliefert, so ist der Aufruf Teil eines Ausdrucks98. Ist das Ergebnis hingegen eine Referenz, so kann diese wie eine normale Variable verwendet werden.

3.8 Redefinition bekannter Methoden Einige, wenige Methoden sind ähnlich wie der Standardkonstruktor für jede Klasse von vornherein bekannt, sollten jedoch für die neue Klasse angepasst werden. Dazu gehören die Umformung eines Objektes in String-Darstellung und Vergleichsmethoden.

96

Siehe auch: Kapselung (S. 148) Siehe auch: Anweisung (S. 104) 98 Siehe auch: Ausdruck (S. 47) 97

80

Kapitel 3: Vereinbarung neuer Klassen

Für jedes Objekt wird eine String-Darstellung durch Aufruf der Methode toString() geliefert. Um eine sinnvolle Repräsentation zu erhalten, ist deshalb die toString() neu zu vereinbaren. Diese Methode wird bei der Anpassung der Argumente der Ausgabemethode println(Object) oder bei der Stringkonkatenation (+) automatisch aufgerufen. Beispiel 50: Ausgabe von Preis-Objekten public String toString() { return (cent / 100) + "," + (cent % 100) + " Euro"; } System.out.println(p2.toString()); System.out.println(p2); cdrom:/buch/examples/java/Klasse/Preis2.java

3.8.1 Objektvergleich Referenzen auf Objekte können mit den Operatoren == und != verglichen werden. Das Ergebnis ist true, falls die Referenzen gleich sind, also zweimal das gleiche Objekt verwendet wird. Darüber hinaus können Objekte mittels der Methode equals(Object) verglichen werden. Wird diese Methode nicht neu vereinbart, so vergleicht sie wie der Operator == die Referenzen. Für Strings z.B. ist eine sinnvollere Variante vereinbart worden, die die repräsentierten Zeichenketten vergleicht. Beispiel 51: String-Vergleich String s1 = new String("hallo"); String s2 = new String("hallo"); String s3 = s2; // s1 und s2 sind Referenzen auf verschiedene Objekte, // die den gleichen Wert haben. // s3 ist eine Referenz, die auf dasselbe Objekt // verweisst, wie s2 System.out.println("Vergleiche s1 und s2 mit 'equals': " + s1.equals(s2)); // true System.out.println("Vergleich s1 und s2 mit '==': " + (s1 == s2)); // false System.out.println("Vergleiche s2 und s3 mit 'equals': " + s2.equals(s3)); // true System.out.println("Vergleich s2 und s3 mit '==': " + (s2 == s3)); // true cdrom:/buch/examples/java/Elementar/ObjVergleich.java

Will man die Inhalte vergleichen, so ist equals(Object) neu zu vereinbaren. Beispiel 52: Preis-Vergleich public boolean equals(Preis2 other) { return(cent == other.cent); } public int compareTo(Preis2 otherPrice) { if (cent < otherPrice.cent) { return -1; } if (cent > otherPrice.cent) {

3.9 Aufgaben mit Klassen

81

return 1; } return 0; } cdrom:/buch/examples/java/Klasse/Preis2.java

Sollen Objekte der Grösse nach verglichen werden können, so geschieht das am besten mit einer Methode compareTo(Object), die einen int Wert zurückliefern sollte, der bestimmt, ob der Wert des handelnden Objekts kleiner (0) als der des Parameters ist. Für Strings ist auch diese Methode vorhanden. Sie führt den lexikografischen Vergleich durch. Für Benutzer definierte Klassen ist die Methode nicht bekannt. Für die Zusammenarbeit mit der Standardbibliothek ist es jedoch wichtig, die angegebene Schnittstelle einzuhalten. Es ist darauf zu achten, dass Sortierung und Vergleich sich nicht widersprechen. Genau dann, wenn z.compareTo(x) == 0 ist, muss z.equals(x) den Wert true liefern

3.9 Aufgaben mit Klassen Übung: Einfache Aufgaben Aufgabe 8: Finden eines Attributs

Wie kann auf ein Attribut zugegriffen werden? ❏ ❏ ❏ ❏ ❏

überhaupt nicht, die Attribute sind geheim mit Objektname.Attributname, falls es public vereinbart ist durch Aufruf einer Methode durch Angabe der Attributnummer innerhalb der Klassendeklaration immer mit dem Namen (Lösung S. 320)

Aufgabe 9: Konstruktoren

Welche Aussagen sind falsch? ❏ Der Standardkonstruktor kann nur durch einen Konstruktor mit leerer

Parameterliste überschrieben werden. ❏ Der Standardkonstruktor kann nicht überschrieben werden. ❏ Konstruktoren können sich gegenseitig aufrufen. ❏ Jeder Konstruktoraufruf erzeugt ein neues Objekt. (Lösung S. 320)

3.9.1 Aufbau und Test von Programmen Die Ausführung eines objektorientierten Programms beschreibt das Zusammenspiel von Objekten verschiedener Klassen. Zur Erstellung des Programms sind diese Klassen zu vereinbaren.

82

Kapitel 3: Vereinbarung neuer Klassen

Ein Programm besteht normalerweise aus mehreren Klassen. Diese sind sowohl einzeln als auch in ihrer Zusammenarbeit zu testen. Während der globale Test eigentlich immer in einem eigenen Testprogramm erfolgt, ist es für die Einzeltests oft bequem, die main-Methode der Klasse zu verwenden. Dann existieren mehrere Klassen mit einer main-Methode, und es ist klar zu stellen, welche aufgerufen werden soll. Übung: Punkte und Rechtecke

Ein Punkt in der Ebene wird durch seine Koordinaten (x,y) bestimmt. Der Abstand zu einem anderen Punkt (x',y') ist gegeben durch sqrt((x-x')2 + (y-y')2). Aufgabe 10: Die Klasse Point

Implementieren Sie eine Klasse Point, die auch eine distance-Methode zur Abstandsberechnung enthält. Die Methode erwartet als Parameter ein Objekt der Klasse Point und hat als Rückgabewert double. Es sollten zwei Konstruktoren vorgesehen werden. Ein parameterloser Konstruktor der den Punkt auf (0/0) initialisiert und einer, der zwei double Werte als Parameter benötigt. Die Attribute x und y sollten private deklariert sein und jeweils über eine get- und set-Methode verfügen. (Lösung S. 320) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

Aufgabe 11: Die Klasse Rectangle

Ein achsenparalleles Rechteck ist durch die Angabe des linken unteren und des rechten oberen Eckpunktes bestimmt. Schreiben Sie eine Klasse Rectangle, die Zugriffsmethoden auf alle 4 Eckpunkte und eine zur Berechnung der Länge der Diagonalen enthält. Der Konstruktor sollte zwei Objekte der Klasse Point erwarten. Nennen Sie Ihre Zugriffs-Methoden für die vier Ecken getUpperLeft, getUpperRight, getLowerLeft, getLowerRight. Der Rückgabegabewert sollte eine Instanz der Klasse Point sein. Nennen Sie Ihre Methode zur Berechnung der Diagonalen getDiagonalLength. Ihr Rückgabewert ist vom Typ double. (Lösung S. 320) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

3.10 Klassenattribute Eine Klasse beschreibt die Struktur einer Menge von Objekten, deren Zustand durch die individuellen Werte ihrer Attribute bestimmt wird. Es gibt aber auch Werte, die für alle Objekte einer Klasse gleichermaßen relevant sind. Diese Werte

3.10 Klassenattribute

83

beschreiben den Zustand einer Klasse als Ganzes. Sie wirken für die Objekte wie globale Variablen und werden als Klassenattribute verwaltet. SEMANTIK: Klassenattribute sind mit dem Wortsymbol static zu spezifizieren, und falls es sich um Konstanten handelt, zusätzlich mit final. Sie werden außerhalb der Klassendefinition mit dem Klassennamen qualifiziert. Sie können auch wie jedes andere Attribut von jedem Objekt der Klasse verwendet werden. Hierbei greift die Unterscheidung von private und public. Beispiel 53: Klasse 'Preis' mit Umrechnung

In einer Klasse Preis, die einen Preis als einen Euro- und einen Centbetrag speichert, könnte man den Wechselkurs zur DM durch eine Klassenvariable zur Verfügung stellen: public class Preis3 { private static final double TODM = 1.95583; private long value; public Preis3() { value = 0; } public Preis3(long euro, int cent) { value = euro * 100; cent = Math.abs(cent); if (euro < 0) { value += -1 * cent; } else { value += cent; } } public Preis3(long euro) { this(euro, 0); } public long getDM() { return Math.round(value * TODM); } public String toString() { long euro = value / 100; int cent = (int) (value % 100); return euro + " Euro, " + cent + " Cent"; } } cdrom:/buch/examples/java/Klasse/Preis3.java

Die Namen von Konstanten werden üblicherweise vollständig mit Großbuchstaben geschrieben. Die Initialisierung von Klassenvariablen erfolgt selbstverständlich nicht durch einen Konstruktor. Stattdessen wird, falls nötig, ein mit static gekennzeichneter Initialisierungsblock angegeben.

84

Kapitel 3: Vereinbarung neuer Klassen

SEMANTIK: Dieser Block wird wie die Initialisierung der einzelnen Klassenattribute (falls vorhanden) beim Laden der Klasse in der Reihenfolge ausgeführt, in der sie in der Klassendefinition aufgeführt sind. Wir empfehlen alle Initialisierungen in einem Block zusammenzufassen. Dieser Block sollte stets am Anfang des Klassenrumpfes stehen. Beispiel 54: static public class Static { private static int anzahl; private String name; // static-Block static { System.out.println("Das ist eine einmalige Ausgabe."); anzahl = 5; } public Static(String name) { this.name = name; anzahl++; } public static void main(String[] args) { System.out.println("Anzahl = " + anzahl); Static a = new Static("Test"); Static b = new Static("Hallo"); System.out.println("Anzahl = " + anzahl); } } cdrom:/buch/examples/java/Klasse/Static.java

Natürlich kann jede (Objekt-)Methode auf die Klassenattribute zugreifen. Es macht aber mehr Sinn, diese mit eigenen Klassenmethoden99 zu verwalten. 99

Siehe auch: Klassenmethoden (S. 87)

3.11 Aufzählungen

85

3.11 Aufzählungen Klassenattribute bieten die Möglichkeit typsichere Konstanten bereit zu stellen und damit Aufzählungstypen von beliebiger Struktur zu erlauben. Beispiel 55: Ampel

Wir können die drei Ampelfarben als vordefinierte Konstanten der Klasse Ampel14 vereinbaren. Verbergen wir den Konstruktor, so können keine neuen Objekte erzeugt werden und der Wertevorrat der Klasse ist auf diese 3 Konstanten beschränkt. Da wir sie ausgeben wollen, vereinbaren wir eine Methode toString(). public class Ampel14 { // 3 Konstanten public static final Ampel14 ROT = new Ampel14(); public static final Ampel14 GELB = new Ampel14(); public static final Ampel14 GRUEN = new Ampel14(); // verborgener Konstruktor private Ampel14() { } public String toString() { if (ROT == this) { return ("rot"); } if (GELB == this) { return ("gelb"); } if (GRUEN == this) { return ("gruen"); } return "bunt"; } } cdrom:/buch/examples/java/Klasse/Ampel14.java

System.out.println(Ampel14.ROT); System.out.println(Ampel14.GELB); Ampel14 f1 = Ampel14.ROT; Ampel14 f2 = f1; f1 = Ampel14.GELB; System.out.println(f1); System.out.println(f2); cdrom:/buch/examples/java/Klasse/EnumTest14.java

Wie üblich haben wir für jede Klasse eine eigene Datei angelegt. Der Vorteil dieser etwas umständlich anmutenden Vorgehensweise gegenüber einfachen int Konstanten ist einerseits die Kapselung der Objektstruktur, andererseits der abgeschlossene vollständig aufgezählte Wertebereich. Wir erweitern unser Beispiel und lassen jede Ampelfarbe noch einen Wert abspeichern.

86

Kapitel 3: Vereinbarung neuer Klassen Beispiel 56: Ampel mit Wert class AmpelWert14 { // 3 Konstanten public static final AmpelWert14 ROT = new AmpelWert14(1); public static final AmpelWert14 GELB = new AmpelWert14(2); public static final AmpelWert14 GRUEN = new AmpelWert14(3); // Attribut private int value; // verborgener Konstruktor private AmpelWert14(int i) { value = i; } public int getValue() { return value; } } cdrom:/buch/examples/java/Klasse/EnumClass.java

System.out.println(AmpelWert14.ROT); System.out.println(AmpelWert14.GELB); AmpelWert14 f1 = AmpelWert14.ROT; AmpelWert14 f2 = f1; f1 = AmpelWert14.GELB; System.out.println(f1); System.out.println(f2.getValue()); cdrom:/buch/examples/java/Klasse/EnumClass.java

3.11.1 Aufzählungstypen In Java 1.5 können nun Klassen, die eine Aufzählung von Konstanten als Wertebereich beschreiben in einer der Klassenvereinbarung ähnlichen intuitiven Syntax definiert werden. Eine einfache Aufzählungsdeklaration besteht aus der Einführung des Typnamens und der Aufzählung der Namen seiner konstanten Werte. Beispiel 57: einfacher Aufzählungsyp

Das Ampelbeispiel in Java 1.5 public enum Ampel { rot, gruen, gelb }; cdrom:/buch/examples/java/Klasse/Ampel.java

System.out.println(Ampel.rot); System.out.println(Ampel.gelb); Ampel f1 = Ampel.rot; Ampel f2 = f1; f1 = Ampel.gelb; System.out.println(f1); System.out.println(f2); cdrom:/buch/examples/java/Klasse/EnumTest.java

3.11 Aufzählungen

87

Ein komplexeres Beispiel mit zusätzlichen Werten zeigt folgendes Programm: enum AmpelWert { rot (1), gruen(2), gelb(3); private int value; private AmpelWert(int i){ value = i; } public int getValue(){ return value; } } public class EnumWieClass { public static void main(String[] args) { System.out.println(AmpelWert.rot); System.out.println(AmpelWert.gelb); AmpelWert f1 = AmpelWert.rot; AmpelWert f2 = f1; f1 = AmpelWert.gelb; System.out.println(f1); System.out.println(f2.getValue()); } } cdrom:/buch/examples/java/Klasse/EnumWieClass.java

Wir erkennen, dass die Vereinbarung eines Aufzählungstyps im Wesentlichen eine Klassenvereinbarung ist. Einfacher und übersichtlicher ist jedoch die eigentliche Aufzählung des Wertebereiches. Syntax

SEMANTIK: Die Konstanten werden mit Referenzsemantik behandelt. Es ist möglich komplexere Konstanten bereit zu stellen, deren Attributwerte durch einen verkürzten Konstruktoraufruf gesetzt werden.

88

Kapitel 3: Vereinbarung neuer Klassen

3.12 Klassenmethoden In Analogie zu den Klassenattributen und zu deren Verwaltung werden die Klassenmethoden eingeführt: DEFINITION: Eine Klassenmethode ist eine Methode, die nicht auf einem Objekt aufgerufen zu werden braucht.

SEMANTIK: Klassenmethoden werden durch die Kennung static eingeleitet.

SEMANTIK: Klassenmethoden beziehen sich auf kein konkretes Objekt, daher haben sie keinen impliziten this Verweis und dürfen keine (Objekt-)Attribute ansprechen oder (Objekt-)Methoden aufrufen. Klassenattibute dürfen benutzt werden.

SEMANTIK: Sie können innerhalb der definierenden Klasse direkt und sonst mit Hilfe des Klassennamens aufgerufen werden.

SEMANTIK: Klassenmethoden müssen explizit Objekte erzeugen, um Methoden darauf aufzurufen. Andere Klassenmethoden können aufgerufen werden.

Beispiel 58: Preis in Dollar

Wir erweitern unsere Preisklasse um ein Attribut zur Umrechnung in Dollar. Dieses ist nicht konstant und kann mit einer Klassenmethode "berechnet" werden. private static double TODOLLAR = 1.1935; public static void changeDollarValue() { SimpleRandom random = new SimpleRandom(); if (random.nextBoolean()) { TODOLLAR *= (1 + random.nextDouble() * 0.1); } else { TODOLLAR *= (1 - random.nextDouble() * 0.1); } }

3.13 Arrays als Datenstruktur bzw. -typ

89

public long getDollar() { return Math.round(value * TODOLLAR); } cdrom:/buch/examples/java/Klasse/PreisDollar.java

Beispiel 59: Objektanzahl

Wir zählen für eine Klasse die Anzahl der erzeugten Objekte. Dazu wird ein Klassenattribut bei jedem Konstruktoraufruf hochgezählt. Der Wert dieses Zählers kann mit einer Klassenmethode ausgelesen werden. public class Student { private static int anzahl = 0; public Student(String name, int matNr) { this.name = name; this.matNr = matNr; anzahl++; } public static int studentenAnzahl() { return anzahl; } public static void main(String[] args) { Student susi = new Student("Susanne Meier", 1234567); Student peter = new Student("Peter Müller", 3456789); System.out.println("Anzahl der Studenten: " + studentenAnzahl()); } } cdrom:/buch/examples/java/Klasse/Student.java

Eine solche Klassenmethode kennen wir vom ersten Java Programm an, die Methode main der Hauptklasse. Jetzt werden uns sicher einige Fehlermeldungen klar, die wir bisher Kopf schüttelnd hingenommen haben. Die Methode main der angeführten "Hauptklasse" wird beim Programmstart aufgerufen. Klassenmethoden ersetzen die vom imperativen Programmieren bekannten Funktionen und Prozeduren100. Sie sind innerhalb der main-Methode aufrufbar. Oft besteht die Aufgabe einer Klasse darin, eine Anzahl von Hilfsfunktionen101 zur Verfügung zu stellen. Das sind also Methoden, die alle Information über die Parameterliste beziehen, da sie keinen Zugriff auf den Zustand eines Objektes haben bzw. brauchen. Eine solche Hilfsklasse bezeichnet man auf Englisch als "Utility". Ein bekanntes Beispiel aus der Java Klassenbibliothek ist die Klasse Math102.

3.13 Arrays als Datenstruktur bzw. -typ Die einfachste Datenstruktur ist eine Aneinanderreihung von gleichartigen Objekten oder Werten, eine Datenstruktur, die eine feste Anzahl von Elementen oder Be-

100

Siehe auch: Funktionen und Prozeduren (S. 112) Siehe auch: Hilfsfunktionen (S. 112) 102 Siehe API-Dokumentation: java.lang.Math 101

90

Kapitel 3: Vereinbarung neuer Klassen

standteile des gleichen Typs enthält und die es zulässt, dass diese Elemente direkt über ihre Nummer, Position oder Index angesprochen werden können. Da solche Datenstrukturen in Form von Vektoren oder Matrizen auch in der Mathematik sehr häufig vorkommen, gehören sie zur Ausrüstung jeder höheren Programmiersprache, stehen uns also auch in Java zur Verfügung. Wir nennen diese Aneinanderreihung ein Array. Zusammen mit den Daten geht auch die Regel einher, dass Elemente eines Arrays dicht im Speicher liegen und deshalb effizient in Berechnungen eingesetzt werden können. DEFINITION: Bei einem Array-Objekt (Feld, Reihung oder mathematisch: Vektor) handelt es sich um eine Datenstruktur, bei der Werte von einem Komponententyp (Elementtyp) aneinander gereiht werden und so als Gesamtheit angesprochen werden. Mathematisch lässt sich ein Array-Objekt als ein Element des n-fachen kartesischen Produkts des Komponententyps mit sich selbst auffassen, als ein Tupel, bei dem es auf die Reihenfolge ankommt. Eine andere Betrachtungsweise ist die einer Funktion, die ein Anfangsstück der natürlichen Zahlen (mit 0 beginnend) auf Werte des Komponententyps abbildet. SEMANTIK: Jeder beliebige Typ ist als Komponententyp zugelassen, ein elementarer Typ, eine Klasse oder wieder ein Array.

3.13.1 Array-Objekte und ihre Elemente Beispiel 60: int[] quadrate = { 1, 4, 9, 16, 25 }; cdrom:/buch/examples/java/Klasse/ArrayExample.java

Abbildung 20: Array

In diesem Array werden die ersten 5 Quadratzahlen aneinander gereiht, die Zuordnungsvorschrift ist f(n) = (n+1)2.

3.13 Arrays als Datenstruktur bzw. -typ

91

SEMANTIK: Der Wert einer Array-Variablen ist eine Referenz103 auf ein Array-Objekt. Die einzelnen Elemente werden durch ihren Platz in der Reihung wiedergefunden. Dieser Platz wird als Wert eines ganzzahligen Indexausdrucks berechnet; 0 entspricht dabei dem ersten Element. SEMANTIK: Der Indexausdruck wird in eckigen Klammern an den Variablennamen angehängt. Diese Komponenten-Variablen speichern Werte bzw. Referenzen des Komponententyps, je nachdem, ob dieser elementar bzw. eine Klasse oder ein Array-Typ ist. 3.13.2 Array-Typ und Vereinbarung Eine Array-Variable wird wie eine normale Variable vereinbart.

SEMANTIK: Der Typ ist dabei ein Array-Typ, bestimmt durch den Komponententyp und die Anzahl der Indexbereiche (oft als Dimension bezeichnet, in Java streng genommen immer 1). Die Länge spielt keine Rolle. Die Objekte eines Array-Typs können verschiedene Länge besitzen.

103

Siehe auch: Referenz (S. 33)

92

Kapitel 3: Vereinbarung neuer Klassen

Die Initialisierung kann über ein Array-Objekt geschehen, welches wie mit einem Konstruktor erzeugt wird oder direkt hingeschrieben werden kann:

SEMANTIK: Durch Angabe eines ganzzahligen Ausdrucks wird die Länge, also die Anzahl der Elemente, festgelegt. Der Typ long ist dafür nicht zugelassen. Ist ein expliziter Array-Initialisierer angeführt, so wird die Länge daraus berechnet. In diesem Initialisierer muss jedes Element zuweisungskompatibel zum Komponententyp des Arrays sein. Ein Array-Ausdruck bestimmt ein anonymes Array-Objekt, das z.B. an eine Variable zugewiesen oder als Argument einer Methode übergeben werden kann. Fehlt ein initialisierender Ausdruck, so werden die Elemente des Arrays mit ihren Default-Werten besetzt. Eigentlich gibt es zwei alternative Sichtweisen und Regeln der Array-Variablen Vereinbarung. In der ersten, die wir bevorzugen, wird durch das Anhängen der eckigen Klammern an den Komponententypnamen ein neuer Typ, ein Array-Typ gebildet, von dem eine Variable mit einem normalen Namen vereinbart wird. Alternativ werden in der Vereinbarung die eckigen Klammern hinter dem Variablennamen eingefügt. Dadurch wird eine Variable so erweitert, dass über ihren Namen mehrere Objekte des Komponententyps angesprochen werden können. Diese verschiedenen Erklärungen bedeuten aber keinen Unterschied in der Semantik. Die beiden Formen sind austauschbar. Beispiel 61:

Dieses Beispiel zeigt die verschiedenen Möglichkeiten, ein Array-Objekt zu erzeugen und zu initialisieren.

3.13 Arrays als Datenstruktur bzw. -typ

93

int[] primzahlen = { 2, 3, 5, 7, 11 }; double[] dfeld = new double[2]; dfeld[0] = 3.14159; dfeld[1] = 1.2354; String[] titel = new String[] { "Ein", "Feld", "aus", "Strings" }; cdrom:/buch/examples/java/Klasse/ArrayExample.java

SEMANTIK: Die Länge eines Array-Objektes wird in dem Attribut length notiert, auf welches nur lesend zugegriffen werden kann. Beispiel 62: System.out.println("Inhalt des Arrays 'primzahlen':"); for ( int i = 0; i < primzahlen.length; i++ ) { System.out.println(primzahlen[i]); } cdrom:/buch/examples/java/Klasse/ArrayExample.java

Ein Array-Ausdruck ist entweder eine Variable, also eine Referenz auf ein existierendes Array, oder eine Array-Erzeugung. null bezeichnet die leere Referenz, in diesem Fall existiert kein Array.

3.13.3 Mehrdimensionale Arrays Mehrdimensionale Arrays, d.h Felder mit mehreren Indexbereichen, gibt es in Java nicht, sie sind als Arrays mit Komponententyp Array zu realisieren und auch so anzusprechen. Beispiel 63: Mehrdimensionale Arrays

Mehrdimensionale Felder lassen sich auf mehrere Arten in Java erzeugen: int[][] {1, {0, {0, };

matrix1 = { 0, 0}, 1, 0}, 0, 1}

int[][] matrix2 = { {1}, {0, 1}, {0, 0, 1} };

94

Kapitel 3: Vereinbarung neuer Klassen double[][] matrix3x5 = new double[3][5]; char[][][] charbuf = new char[255][][]; cdrom:/buch/examples/java/Klasse/ArrayExample.java

Das Array charbuf verdeutlicht, dass der Wert eines Array-Objekts tatsächlich nur eine Referenz ist, die per default den Wert null besitzt. Es wird ein Feld von Elementen des Typs char[][] und der Länge 255 erzeugt. Jedes seiner Elemente hat nun zwar den Typ char[][], aber, da keine weitere Initialisierung erfolgte, den Wert null. Interessant ist hierbei, dass sich so Array-Objekte unterschiedlicher Länge in einem Array zusammen fassen lassen. Auch die Array-Literale können geschachtelt werden. 3.13.4 Kopieren von Arrays Wie bei allen Objekten bezeichnen die Variablen nur Referenzen. Eine Kopie ist durch Aufruf der Methode clone() zu erreichen. Beispiel 64: Kopieren von Arrays double[] feld = { 1, 0.1, 0.001, 0.0001 }; double[] tiefeKopie = (double[]) feld.clone(); cdrom:/buch/examples/java/Klasse/ArrayExample.java

3.13.5 char-Felder (Zeichenketten) vs. Strings Der Typ für Zeichenfelder, char[], also Arrays mit Komponententyp char, ist ungleich dem Typ String. Es gibt jedoch einen String-Konstruktor, der ein char[] Objekt erwartet und die String-Methode toCharArray(), die ein solches liefert. Strings oder StringBuffer als Objekte bieten mit ihren vielen Methoden sauber beschriebene Datentypen, die in den objektorientierten Ansatz passen. Im Gegensatz dazu bilden Zeichenfelder eine elementare Datenstruktur ohne Methoden. Übung: Sortieren

Eine häufig durchgeführte Aufgabe in der Informatik ist das Sortieren von Daten. Wir wollen eine einfache, nicht sehr effiziente Sortieraufgabe mit Hilfe eines Arrays lösen. Aufgabe 12: Insertion Sort

Gegeben sei ein Array ganzer Zahlen. Schreiben Sie eine Klasse ArraySort, die das Array mit einer Funtion static void sort(int[] a) nach folgendem Algorithmus sortiert:

3.13 Arrays als Datenstruktur bzw. -typ

95

• Ein einelementige Teilfolge, also insbesondere ein Array mit nur einem Element, ist sortiert. • Das Einfügen eines nächsten Elements ist so vorzunehmen, dass auch die neue, längere Folge sortiert ist. D.h. das Array ist zu durchlaufen, bis die Einfügeposition erreicht ist (das nächste Element ist größer oder es gibt kein nächstes Element). Ist die Position besetzt, so muss der restliche Arrayinhalt nach hinten verschoben werden. Ist sie frei, so kann direkt eingefügt werden. • Fahre so fort, bis zum Einfügen des letzten Elementes. (Lösung S. 320) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

3.13.6 Mögliche Fehler bei Arrayzugriffen Beim Zugriff auf Elemente eines Arrays wird geprüft, ob der Index zulässig ist. Es können folgende Laufzeitfehler ausgelöst werden: Laufzeitfehler (Ausnahme)

wird ausgelöst, wenn

ArrayIndexOutOfBoundsException

Index kleiner 0 oder größer gleich der Feldlänge

NegativeArraySizeException

Längenangabe bei Variablenvereinbarung kleiner 0

Tabelle 20: Ausnahmen bei Arrays

Kapitel 4 Algorithmen und Anweisungen

Wir wollen in diesem Abschnitt drei sehr häufig auftretende, miteinander verwandte Aufgabenstellungen präsentieren. Nach einer anschaulichen Einführung werden diese zuerst mathematisch formuliert und dann in den programmiersprachlichen und den objektorientierten Kontext gestellt. Zu ihrer Programmierung werden zwei alternative Lösungsmuster oder -ansätze besprochen. Die Bearbeitung der Aufgaben führt wichtige imperative Programmiertechniken ein und erläutert deren Umsetzung durch Java Anweisungen. Die drei Aufgabentypen sind die Folgenberechnung, Abbildung und Akkumulation, wobei die letzteren in der Regel mit dem Durchlauf durch eine Datenstruktur verbunden sind. Anhand dieser drei Aufgabenstellungen wollen wir zwei wichtige Lösungsmuster vorstellen, die Iteration104 und die für die Informatik fast noch wichtigere Rekursion105.

Aufgabentyp 1: Folgenberechnung Dieser Aufgabentyp wird oft auch als Iteration bezeichnet, nicht zu verwechseln mit dem iterativen Lösungsansatz. DEFINITION: Eine Folge ist gegeben durch einen oder mehrere Anfangswerte und einer Berechnungsvorschrift, die ein Folgenglied aus einem oder mehrerer schon bekannter Folgenglieder bestimmt.

104 105

Siehe auch: Iteration (S. 99) Siehe auch: Rekursion (S. 113)

98

Kapitel 4: Algorithmen und Anweisungen Beispiel 65: Zinsberechnung

Bei einem Grundkapital von x0 und einem Zinssatz von p werden am Ende des ersten Jahres z1=x0* p/100 Euro Zinsen ausbezahlt. Diese werden wieder angelegt, also ist x1=x0+z1. Diese verzinsen sich im Folgejahr wieder usw. Die Kapitalbeträge bilden eine Folge. Gemäß der Vorschrift, die die Folge definiert, ist ein Folgenglied nach dem anderen zu berechnen. Das n-te Folgenglied oder ein Näherungswert für den Grenzwert einer unendlichen Folge sind gefragt. Die Zwischenwerte sind uninteressant. Beispiel 66: Folge von Objekten

Der Wert eines Objektes kann durch eine Reihe von zustandsverändernden Methoden erzielt werden.

Aufgabentyp 2: Abbildung, Durchlauf Beispiel 67: Liste von Quadratzahlen

In einer gegebenen Liste von ganzen Zahlen ist jede Zahl zu quadrieren. Funktional mathematische Sicht: Wende eine Funktion auf alle Elemente einer endlichen Menge an. Beispiel 68: Erhöhen der Preise in einer Liste

In einer gegebenen Liste von Preisen ist jeder Preis um 5% zu erhöhen. Objektorientierte Sicht: Es ist eine Menge gleichartiger Objekte gegeben, für die alle eine Methode anzuwenden (aufzurufen) ist. Wir müssen also eine Menge mit einer geeigneten Datenstruktur zusammenfassen, und diese dann Element für Element durchlaufen. Als Algorithmusbeschreibung verwenden wir für diesen Durchlauf gerne folgenden Pseudocode. foreach element : Menge element.method()

In Java gibt es unterschiedliche Umsetzungen für diese foreach-Schleife.

Aufgabentyp 3: Akkumulation, Reduktion

99

Aufgabentyp 3: Akkumulation, Reduktion Beispiel 69: Gesamtsumme

Die Summe einer Liste von ganzen Zahlen ist zu bestimmen. Funktional mathematische Sicht: Berechne eine Funktion, die endlich viele Eingabeparameter auf einen Ergebniswert reduziert. Beispiel 70: Gesamtpreis

Die Summe aller Preise in einer Liste ist zu bestimmen. Objektorientierte Sicht: Fasse alle Elemente einer Menge zu einem Wert oder Objekt zusammen. Wieder ist eine Datenstruktur zu durchlaufen, eine Operation anzuwenden, aber auch geschickt Buch zu führen. Die beiden letzten Aufgabentypen beschreiben ein Grundmuster, welches noch verallgemeinert werden kann und wird. So können wir den Durchlauf durch alle Elemente einer Datenstruktur mit Hilfe einer Iterator106 genannten Klasse organisieren und die Aufgabe so allgemein lösen, dass die anzuwendende Funktion als Argument (Funktor107) übergeben wird.

4.1 Iteration DEFINITION: Eine Iteration ist eine Berechnungsvorschrift, bei der Schritt für Schritt aus einem Anfangswert ein Ergebnis gewonnen wird. Dazu werden Wiederholungsanweisungen oder Schleifen108 eingesetzt.

4.1.1 Folgenberechnung Beispiel 71: Zinsberechnung

Die Folge: xk+1 = xk + xk * p / 100 bestimmt die Verzinsung eines Grundkapitals x0 nach k Jahren.

106

Siehe auch: Iterator (S. 165) Siehe auch: Funktor (S. 161) 108 Siehe auch: Schleifen (S. 104) 107

100

Kapitel 4: Algorithmen und Anweisungen

Das folgende Programm verwendet eine for-Schleife, die im nächsten Abschnitt genau erklärt wird. int k = 20; double p = 3.5; double x = 173.47; for (int jahr = 0; jahr < k; jahr++) { x = x + x * p / 100; } cdrom:/buch/examples/java/Anweisung/Zinsberechnung.java

Zur Berechnung einer Folge ist kein Array oder eine sonstige Datenstruktur nötig. Man verwendet eine oder mehrere Variablen zur Neuberechnung des nächsten Folgenelementes. In einem weiteren Beispiel ändern wir die Aufgabenstellung etwas ab. Beispiel 72: Quadratwurzel

Für x0 = a konvergiert die Folge xk+1 = 0.5(xk + a /xk ), für k=0,1,2,... gegen die Quadratwurzel aus a, hier sqrt(a) genannt. Das bedeutet, dass der Abstand zwischen dem aktuellen Folgenglied und sqrt(a) immer kleiner wird. Die Konstruktion der Folge wird in dem Bild erläutert.

Abbildung 21: Quadratwurzel

4.1 Iteration

101

Man bestimme die Anzahl K der benötigten Folgenglieder, um für gegebenes a > 0 eine Näherung xK zu berechnen, für deren relativen Fehler gilt: |(xK - sqrt(a)) / sqrt(a)| < 10-8 Die Folge ist also durch einen Anfangswert und eine Rekursionsformel gegeben, in der das (k+1)-te Folgenglied in Abhängigkeit vom k-ten dargestellt ist. Zur Berechnung der Folge ist kein Array oder eine sonstige Datenstruktur nötig, die Anzahl der Folgenglieder soll ja auch erst bestimmt werden. Es genügt immer nur das letzte Element zu speichern. Dieses wird in einer Schleife109 neu berechnet. Das folgende Programm verwendet eine while-Schleife, die im nächsten Abschnitt genau erklärt wird. double a = 30; double eps = 1e-8;

// Wert, dessen Quadratwurzel zu berechnen ist // Genauigkeitsschranke

double x_k = a; // Math.sqrt(double a) berechnet die Quadratwurzel von a. double exact = Math.sqrt(a); int iterations = 0; while ( Math.abs(x_k - exact) > eps ) { x_k = 0.5 * (x_k + a / x_k); iterations++; } System.out.println("Es waren " + iterations + " Iterationsschritte noetig."); cdrom:/buch/examples/java/Anweisung/Quadratwurzel.java

Die Abbruchbedingung war hier besonders einfach, weil wir mit der "exakten" Lösung vergleichen können. Deshalb geben wir auch die Anzahl der Iterationen und nicht den Näherungswert als Ergebnis zurück. Bei anderen Abbruchbedingungen, etwa der Abstand zweier aufeinanderfolgenden Elemente soll klein sein, benötigt man entsprechend mehr aktuelle Werte, die dann in jedem Schritt weitergeschaltet werden. Übung: Quadratwurzel Aufgabe 13: Iterationsanzahl

Welchen Wert hat die Variable iterations im obigen Beispiel nach zweifachem Durchlaufen der while Schleife? Antwort: .................................................................... (Lösung S. 320)

109

Siehe auch: Schleifen (S. 104)

102

Kapitel 4: Algorithmen und Anweisungen

Aufgabe 14: Abbruchbedingung

Ändern Sie die Abbruchbedingung in dem Programm zur Quadratwurzelberechnung so, dass der Abstand der beiden letzten Näherungswerte kleiner als sqrt(a) * eps ist, wobei eps mit einer Methode der Klasse SimpleInput einzulesen ist. Das abgegebene Programm soll "Abbruchbedingung.java" heissen. (Lösung S. 321) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

Beispiel 73: Fibonacci-Zahlen

Die Fibonacci-Zahlen bilden ein Beispiel einer Folge, die zwei Startwerte benötigt. Es gilt: F0 = 0; F1 = 1; Fk = Fk-1 + Fk-2 Bsp: F2 = F1 + F0 = 1; F3 = F2 + F1 = 2; F4 = F3 + F2 = 3; F5 = F4 + F3 = 5; Natürlich lassen sie sich ebenfalls mit einer Schleife110 berechnen. Hier werden zwei alte Zahlen zur Berechnung einer neuen herangezogen. Die Reihenfolge bei der Umsetzung ist zu beachten. int i = 10;

// Index der zu berechnenden Zahl

int fibPrev = 0; int fib = 1; while ( i > 0 ) { int next = fibPrev + fib; fibPrev = fib; fib = next; i--; } System.out.println("Die 10-te Fibonaccizahl ist " + fibPrev); cdrom:/buch/examples/java/Anweisung/Fibonacci.java

Für die beiden anderen Aufgabentypen brauchen wir eine Datenstruktur. Wir lösen die Akkumulation mit einem Array111 und die Abbildung mit einer Liste112.

110

Siehe auch: Schleifen (S. 104)

4.1 Iteration

103

4.1.2 Akkumulation Ein Vektor in der Mathematik ist ein n-Tupel, also eine angeordnete n-elementige Menge. Die einzelnen Werte bezeichnen die Koordinaten eines Punktes im ndimensionalen Raum. Auf sie wird mittels einer Indexangabe zugegriffen, die in Java in eckigen Klammern hinter dem Namen des Vektors (in Java: Array) steht. Das Durchlaufen eines Arrays lässt sich leicht mit einer for-Schleife realisieren. Wichtig ist hier die korrekte Initialisierung des akkumulierenden Wertes. Beispiel 74: Normberechnung

Die Maximumnorm (1-Norm) eines Vektors ist definiert als das Maximum der Absolutbeträge: ||x|| = max(|xi|) int feld[] = {-3, 5, -7, 15, 2, -6, -17}; // zu bearbeitendes Array int maxVal = -1; for (int i = 0; i < feld.length; i++) { // Math.max() und Math.abs() sind vorgefertigte Funktionen. // Detail in der Dokumentation (API) der Klasse Math. maxVal = Math.max(Math.abs(feld[i]), maxVal); } System.out.println("Die 1-Norm des Vektors ist " + maxVal); cdrom:/buch/examples/java/Anweisung/MaxNorm.java

Übung: Normberechnung Aufgabe 15: Maximumnorm

Das Array werde nun mit [2,4,10,9,15,3] initialisiert. Geben sie zu jeder Iteration durch Komma getrennt das Tupel (i,maxVal) an (z.B. (i1,maxVal1), (i2,maxVal2), ..., (in,maxValn) ), wobei die Werte jeweils zu Ende des Schleifenrumpfes anzugeben sind. Antwort: .................................................................... (Lösung S. 321)

Aufgabe 16: Summennorm

Ändern Sie das Programm zur Normberechnung, so dass die Summennorm, also die Summe der Absolutbeträge der Elemente des Vektors berechnet wird. Das abgegebene Programm soll dementsprechend "SummenNorm.java" heissen. (Lösung S. 321) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

111 112

Siehe auch: Array (S. 89) Siehe auch: Liste (S. 130)

104

Kapitel 4: Algorithmen und Anweisungen

4.1.3 Durchlaufen einer Menge Die Realisierung der Abbildung ist, wenn die Datenstruktur richtig gewählt wurde, am einfachsten. So kann zum Beispiel für ein Array leicht eine Schleife hingeschrieben werden, die den Indexbereich durchläuft. Wir wollen den Algorithmus hier für eine allgemeine Liste formulieren und auch ausführen, ohne an diesem frühen Zeitpunkt die Implementierung zu betrachten. Beispiel 75: Liste von Preisen

Der folgende Algorithmus durchläuft eine Liste von Preisen und erhöht jeden. In diesem Algorithmus "personalisieren" wir den Listendurchlauf, d.h. wir betrauen ein Objekt it als Durchläufer (Iterator) mit dieser Aufgabe. Der Iterator wird anfangs auf den Anfang der Liste gesetzt und kennt zwei Methoden: Funktion/Methode

Wirkung

it.hasNext()

liefert einen Wahrheitswert, der angibt, ob noch weitere Listenelemente vorhanden sind.

it.next()

liefert das nächsten Listenelement und setzt den Iterator eins weiter.

Tabelle 21: Listendurchlauf, Iterator

Mit diesen Verabredungen sieht der Algorithmus in Java wie folgt aus: while ( it.hasNext() ) { Preis1 preis = (Preis1)it.next(); preis.erhoehe(prozent); } cdrom:/buch/examples/java/Anweisung/PreisListe.java

Wir verwenden hier die bekannte Klasse113 Preis1. Dem Iterator müssen wir noch sagen, dass er eine Liste von Objekten dieser Klasse durchläuft. Aber das wollen wir hier nicht vertiefen114.

4.2 Schleifen Schleifen oder Wiederholungsanweisungen dienen dazu, Anweisungsfolgen solange auszuführen, bis eine bestimmte Bedingung erfüllt bzw. verletzt ist.

113 114

Siehe auch: Klassenentwurf (S. 65) Siehe auch: Hierarchie (S. 184)

4.2 Schleifen

105

4.2.1 while-Schleife

SEMANTIK: Bei der while-Schleife wird die Bedingung zuerst getestet. Die Anweisung (Schleifenrumpf) wird ausgeführt, falls die Bedingung erfüllt ist; anschließend wird wieder der Test durchgeführt und der Schleifenrumpf ausgeführt, solange bis die Bedingung verletzt ist. Beispiel 76: Quadratzahlen drucken

Folgendes Programm gibt die Quadratzahlen bis zu einer Schranke aus. int i = 1; while ( i 20 ist.

4.2.2 do-Schleife

SEMANTIK: Die Bedingung in der do-Schleife wird nach der Ausführung des Schleifenrumpfes (der Anweisung) getestet. Falls sie erfüllt ist, wird der nächste Durchlauf des Schleifenrumpfes gestartet und dann die Bedingung erneut geprüft. Beispiel 77: Quadratzahlen ohne Multiplikation

Man kann die Quadratzahlen mit Hilfe einer Schleife berechnen. z=i2 ist gleich der Summe der ersten i ungeraden Zahlen. Beginnend mit 1, wird die nächste ungerade Zahl u ermittelt und auf die Summenvariable z addiert:

106

Kapitel 4: Algorithmen und Anweisungen

int i = 1; int z = 0; int u = 1; do { z += u; u += 2; System.out.println("Die " + i + "-te Quadratzahl ist " + z); i++; } while ( i 20. Der Schleifenrumpf ist syntaktisch eine Anweisung, im Normalfall sind mehrere Anweisungen als Block zusammenzufassen. Wir empfehlen immer einen Block zu verwenden, auch wenn nur eine Anweisung vorkommt. Es ist darauf zu achten, dass eine der Anweisungen des Schleifenrumpfes oder die Bedingung selbst den Wert der Bedingung beeinflusst, so dass keine unendliche Schleife vorliegt. Anweisungen, die einen Block eröffnen wie diese Schleifen werden immer von der eröffnenden Klammer { gefolgt, die schließende Klammer } steht in einer eigenen Zeile auf gleicher Höhe wie der Beginn der eröffnenden Anweisung.

4.2.3 for-Schleife Eine allgemeinere Schleife beschreibt die for-Schleife.

In der Initialisierung wird typischerweise eine Laufvariable vereinbart und initialisiert, die zum Durchzählen einer festen Anzahl von Schleifendurchläufen dient.

4.2 Schleifen

107

SEMANTIK: Wird die Laufvariable hier neu vereinbart, so ist ihr Gültigkeitsbereich der Schleifenrumpf.

SEMANTIK: Zuerst wird die Initialisierung (oft eine Zuweisung oder eine Variablendefinition) ausgeführt, anschließend wird die Bedingung ausgewertet. Liefert sie den Wert wahr, so wird der Schleifenrumpf ausgeführt. Dann wird die Weiterschaltung ausgewertet, die üblicherweise den Wert der Laufvariablen verändert und damit die Bedingung beeinflusst. Nun wird die Bedingung erneut getestet und der ganze Vorgang solange wiederholt, bis der Wert false erreicht wird. Syntaktisch ist die Weiterschaltung ein Anweisungsausdruck, in der einfachsten und häufigsten Art eine Weiterzählung.

Auch hier ist der Schleifenrumpf syntaktisch eine Anweisung, im Normalfall sind mehrere Anweisungen als Block zusammenzufassen. Wir empfehlen einen Block, also umschließende Klammern {} in jedem Fall zu verwenden. Da die Laufvariable oft nur zum Zählen der Schleifendurchläufe verwendet wird, sollte sie als lokale Variable für die Schleife vereinbart werden. Auch eine for-Schleife eröffnet einen Block und wird deshalb von der eröffnenden Klammer { gefolgt, die schließende Klammer } steht in einer eigenen Zeile auf gleicher Höhe wie der Beginn der Schleife. Beispiel 78: VektorSumme

Folgende Schleife berechnet die Summe der Komponenten eines Arrays115 feld, deren Anzahl durch feld.length gegeben ist. double sum = 0.0; for (int i = 0; i < feld.length; i++) { sum += feld[i]; } cdrom:/buch/examples/java/Anweisung/VektorSumme.java

115

Siehe auch: Arrays (S. 89)

108

Kapitel 4: Algorithmen und Anweisungen

4.2.4 Allgemeine for-Schleife Eine Variablendefinition kann mehrere Variablen einführen. In Java ist außerdem in einer for-Schleife an Stelle eines Anweisungsausdruckes an beiden Stellen eine Liste von Anweisungsausdrücken erlaubt, die nacheinander ausgewertet werden. Eine Schleife kann so von mehreren Größen gesteuert werden.

In Java 1.5 gibt es noch eine weitere for-Schleife, die alle Elemente einer Datenstruktur, eines Containers bzw. Menge durchläuft. Diese Art von foreach-Schleife ist äußerst hilfreich, weil sie einfach anzuwenden ist und häufig vorkommt.

SEMANTIK: Der Typname bezeichnet den Elementtyp der als Operand gegebenen Datenstruktur, die entweder ein Array ist oder ein Standard Container116. Für diese Struktur wird ein Element vereinbart, welches alle Objekte oder Werte der Datenstruktur durchläuft. Beispiel 79: for-each Array

Das folgende Programm summiert die Werte eines Arrays mit einer for-each Schleife in Java 1.5. double sum = 0.0; for (double t : feld) { sum += t; } cdrom:/buch/examples/java/Anweisung/VektorSumme15.java

Während Arrays fast genau so schön mit einer for-Schleife durchlaufen werden können, wirkt sich die for-each Schleife besonders bei Listen und allgemeinen Containern aus.

116

Siehe auch: Standard Container (S. 206)

4.2 Schleifen

109

Beispiel 80: for-each Preisliste

Das folgende Programm beschreibt die Erhöhung aller Preise in einer spezifischen Liste mit Hilfe einer for-each Schleife in Java 1.5. for (Preis1 preis : liste) { preis.erhoehe(prozent); } cdrom:/buch/examples/java/Anweisung/PreisListeSpezifisch.java

Übung: Schleifen Aufgabe 17: Funktionsergebnis

Wie oft wird die while-Schleife in folgendem Programm beim Aufruf der Funktion mit dem Parameter 5 durchlaufen? public static int schleifen_fkt(int i) { do { i += 6; } while (i < 15); for (int x = 0; x < 10; x++) { i %= 6; i++; } while (i < 7) { i++; } return i; } cdrom:/buch/examples/java/Anweisung/Aufgaben.java

Antwort: .................................................................... (Lösung S. 321)

Aufgabe 18: Vergleich

Welche Schleife wird mindestens einmal durchlaufen ? ❍ ❍ ❍ ❍

while do jede for (Lösung S. 321)

Aufgabe 19: Römische Zahlen

Schreiben Sie ein Programm "Roman.java", welches eine Zahl einliest und in römischen Ziffern ausgibt. Dabei dürfen bis zu 4 gleiche römischen Ziffern hintereinander auftreten. (Lösung S. 321) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

Die folgenden code-Fragmente zeigen, dass die drei Schleifenkonstrukte äquivalent sind, in dem Sinne dass jede Schleife durch eine andere ersetzt werden kann, ohne dass sich die Semantik des Programms ändert.

110

Kapitel 4: Algorithmen und Anweisungen

Beispiel 81: Schleifenfragmente // for-Schleife for (Initialisierung; Bedingung; Weiterschaltung) { Anweisungen; } // while-Schleife Initialisierung; while (Bedingung) { Anweisungen; Weiterschaltung; } // do-Schleife Initialisierung; if (Bedingung) { do { Anweisungen; Weiterschaltung; } while (Bedingung); }

Die for-Schleife ist die kompakteste, deshalb wird sie häufig verwendet, obwohl die while-Schleife eigentlich klarer ist. Eine do-Schleife kommt selten vor.

4.3 Bedingte Anweisungen Eine bedingte Anweisung beschreibt eine Programmverzweigung auf Grund einer Bedingung.

SEMANTIK: Die geklammerte Bedingung hinter if wird als boolescher Ausdruck ausgewertet, d.h. es wird geprüft, ob der Wert wahr oder falsch ist. Im ersten Fall wird die hinter der schließenden Klammer stehende Anweisung ausgeführt. Ist die Bedingung dagegen nicht erfüllt und ist ein else-Teil vorhanden, so wird die dort stehende Anweisung ausgeführt. Ist kein else-Teil vorhanden, so wird keine Anweisung ausgeführt. Dann ist die Ausführung der if Anweisung beendet und es wird zur nächsten Anweisung übergegangen. Sollen hinter if oder else mehrere Anweisungen ausgeführt werden, so sind diese mittels eines Blockes zu klammern. Um dumme Fehler zu vermeiden sollte das immer geschehen.

4.3 Bedingte Anweisungen

111

Anweisungen, die einen Block eröffnen wie die Bedingung oder der elseTeil einer if Anweisung werden immer von der eröffnenden Klammer { gefolgt, die schließende Klammer } steht auf gleicher Höhe wie der Beginn der eröffnenden Anweisung in einer eigenen Zeile, gegebenenfalls gefolgt von else. Eine Bedingung ist der Wert eines logischen Ausdrucks. Ist dieser in einer booleschen Variablen gespeichert, so braucht diese nicht mehr mit true verglichen zu werden. Beispiel 82: Überprüfung, ob die Zahl gerade ist // berechne Rest der Division durch 2 und pruefe auf 0 if ( zahl % 2 == 0 ) { System.out.println("Die Zahl " + zahl + " ist gerade."); } else { System.out.println("Die Zahl " + zahl + " ist ungerade."); } cdrom:/buch/examples/java/Anweisung/BedAnw.java

Aufgabe 20: Bedingungen

Welchen Wert liefert diese Funktion, wenn sie mit dem Parameter 3 aufgerufen wird? public static int if_fkt(int i) { if ( i > 2 ) { i++; } else if ( i > 3 ) { i--; } if ( i > 3 ) { i++; } if ( i < 3 ) { i--; } else { i++; } if ( true ) { i /= 2; } if ( i < 3 ) { i--; } else { if ( i < 4 ) { i++; } if ( i < 4) { i--; } } if ( false ) { i++; } else { i--; } return i--; } cdrom:/buch/examples/java/Anweisung/Aufgaben.java

Antwort: .................................................................... (Lösung S. 321)

112

Kapitel 4: Algorithmen und Anweisungen

Programme sollten robust sein, in dem Sinne, dass unzulässige Eingaben abgefangen werden. Aufgabe 21: Robuste Eingabe

Schreiben Sie ein Programm, welches zwei nicht negative Zahlen einliest, die kleinere von der größeren subtrahiert und den Quotient dieser Differenz mit der kleineren Zahl ausgibt, falls diese ungleich 0 ist. Wiederholen Sie die Eingabe, falls die Zahlen nicht der Aufgabenstellung entsprechend eingelesen werden. (Lösung S. 321) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

4.4 Funktionen Anweisungsfolgen, die öfter gebraucht werden, logisch zusammenhängen oder eine bestimmte Funktionalität verwirklichen, sollten als Methoden oder Funktionen formuliert werden. Dabei können formale Parameter117 angegeben werden, die den Aufruf der Funktionen mit unterschiedlichen Argumenten ermöglichen. In Java als objektorientierter Sprache gibt es eigentlich nur Methoden, aber wir wollen Klassenmethoden118, also solche, die nicht ein Objekt bearbeiten, sondern alle Information über Parameter austauschen, als Funktionen bezeichnen. Funktionen werden in einer Klasse definiert. Sie können innerhalb dieser Klasse direkt und sonst mit Hilfe des Klassennamens aufgerufen werden. main ist eine solche Klassenmethode, die andere Funktionen aufrufen kann. Aufgabe 22: Konkatenation von Zahlen

Schreiben Sie ein Programm "ConcatInt.java", in dem Sie die Methode putTogether verwenden, um 2 ganze Zahlen aneinander zu hängen. Benutzen Sie geeignete Methoden aus simple.io.SimpleInput und java.lang.String (Lösung S. 321) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

Aufgabe 23: Arithmetisches und geometrisches Mittel

Schreiben Sie zwei Funktionen arithMittel() bzw geomMittel() zur Bestimmung des arithmetischen bzw. geometrischen Mittels zweier positiver

117 118

Siehe auch: Deklaration von Methoden (S. 75) Siehe auch: Klassenmethoden (S. 87)

4.5 Rekursion

113

double-Zahlen a und b ( a+(b-a)/2 bzw. (ab)1/2). Schreiben sie ein Testprogramm, das die beiden Zahlen via SimpleInput einliest und "groesser" bzw "kleiner" ausgibt, wenn das arithmetische Mittel größer bzw kleiner als das geometrische Mittel ist. Zusätzliche Ausgaben, etwa die Funktionsergebnisse, sind optional. (Lösung S. 321) An dieser Stelle sollte die gestellte Programmieraufgabe gelöst werden. Die Lösung kann aus der HTML-Version heraus auch überprüft werden.

4.5 Rekursion DEFINITION: Unter Rekursion versteht man den direkten oder indirekten Selbstaufruf einer Methode oder Funktion. Die Rekursion ist ein wesentliches Hilfsmittel beim Algorithmenentwurf und in der funktionalen Programmierung. Besonders interessant wird sie im Zusammenhang mit rekursiven Datentypen. 4.5.1 Rekursive Algorithmen und Funktionen Der Grenzwert einer Folge119, die durch eine Rekursionsvorschrift gegeben ist, lässt sich leicht rekursiv bestimmen. Die Folge x0= a xk+1= 0.5(xk + a/xk), für k=0,1,2,... konvergiert gegen die Quadratwurzel von a. Wir wollen solange einen Näherungswert berechnen, bis dessen Abstand klein genug ist. Dabei gehen wir schrittweise vor, d.h. wir prüfen zuerst, ob unser erster Näherungswert (x0 oder a) schon die Bedingung erfüllt. Dann sind wir fertig und haben die Aufgabe gelöst. Ist der Abstand noch zu groß, versuchen wir es mit dem durch die Rekursionsformel verbesserten Wert. Wir lösen also die gleiche Aufgabe, nur mit einem anderen Startwert. Der Algorithmus ist jedoch kein blindes Ausprobieren, sondern ein gezieltes Verbessern. Es wird der Näherungswert mit verbessertem Startwert berechnet und zurück gegeben. Dabei muss irgendwann die Abbruchbedingung erfüllt sein. Das ist für unsere Folge für a>0 gesichert.

119

Siehe auch: Grenzwert einer Folge (S. 97)

114

Kapitel 4: Algorithmen und Anweisungen

Noch einmal: Wir schreiben eine Methode (Funktion)120, approxWurzel, die prüft, ob ein übergebener Näherungswert gut genug ist. Ist das nicht der Fall, so wird die Funktion noch einmal mit einem neuen Näherungswert aufgerufen. Dieser Aufruf geschieht im Rumpf der Methode selbst, rekursiv. Beispiel 83: Quadratwurzel rekursiv

Ausformuliert in Java sieht das so aus: public static double approxWurzel( double x_0, double a, double eps, int cnt) { // pruefe ob Schranke erfuellt if ( Math.abs(x_0 - Math.sqrt(a)) < eps ) { return x_0; } // ansonsten weitere Iteration return approxWurzel(0.5 * (x_0 + a / x_0), a, eps, cnt + 1); } cdrom:/buch/examples/java/Anweisung/WurzelRekursiv.java

Aufgabe 24: Näherung an Wurzel

Die Funktion werde mit den Parametern (4,4,1e-4,0) aufgerufen. Mit welchen Parametern passiert die erste Rekursion, angegeben analog zu obigem Format. Antwort: .................................................................... (Lösung S. 321)

Beispiel 84: Potenzen

Nehmen wir uns die Berechnung der Potenzen einer Zahl x vor. x0 = 1, xk = x * xk-1 für k > 0. Das lässt sich direkt umsetzen. public static double power(double a, int z) { if ( z < 0 ) { return 1.0 / power(a, -z); } if ( z == 0 ) { return 1.0; } return a * power(a, z - 1); } cdrom:/buch/examples/java/Anweisung/Potenzen.java

Das folgende Bild veranschaulicht die rekursiven Aufrufe durch Schachtelung.

120

Siehe auch: schreiben eine Methode (Funktion) (S. 87)

4.5 Rekursion

115

Abbildung 22: Rekursive Berechnung von Potenzen

Die Anwendung rekursiver Verfahren ist besonders klar und einleuchtend, wenn auch die Datenstrukturen rekursiv angelegt sind. Das ist in der Tat oft der Fall. So ist eine Liste von natürlichen Zahlen entweder leer, oder sie besteht aus einer Zahl und einer Restliste. Eine Implementierung eines solchen Datentyps121 lernen wir später kennen. Hier soll genügen, dass diese Klasse drei Methoden besitzt. Methode:

Wirkung:

getHead()

liefert das 1. Element

getTail()

liefert die Restliste, d.h. die Liste ohne das erste Element.

isEmpty()

testet, ob die Liste leer ist.

Tabelle 22: Zugriffsmethoden für eine rekursive Liste

Die Zugriffsmethoden sind natürlich nur sinnvoll, wenn die Liste nicht leer ist. Nun können wir den Algorithmus zur Bestimmung des Maximums formulieren. Das Maximum der leeren Liste ist 0.Das Maximum einer nichtleeren Liste ist die größere der beiden Zahlen: erstes Element, Maximum der Restliste. Beispiel 85: Maximum einer Liste public static int maximum(IntegerListe list) { // leere Liste if ( list.isEmpty() ) { return 0; } // bilde Maximum aus aktuellem Element und dem Rest der Liste return Math.max(list.getHead(), maximum(list.getTail())); } cdrom:/buch/examples/java/Anweisung/MaxList.java

121

Siehe auch: eines solchen Datentyps (S. 140)

116

Kapitel 4: Algorithmen und Anweisungen

Mit der gleichen Struktur können wir die Abbildungsaufgabe lösen, d.h. eine Funktion auf jedes Listenelement anwenden. Beispiel 86: Liste ausgeben

Die folgende Methode gibt alle Werte einer rekursiven Zahlenliste aus. public static void ausgabe(IntegerListe list) { // leere Liste if ( ! list.isEmpty() ) { System.out.print(list.getHead() + " "); ausgabe(list.getTail()); } } cdrom:/buch/examples/java/Anweisung/MaxList.java

Auch auf ein Array122 können wir rekursiv zugreifen. Der Listenkopf staht in A[0]. Eine Teilliste wird durch ein Array und zwei Indexwerte first und last beschrieben. Sei A ein Array mit n Elementen, so bezeichnet (A,1,n-1) den Schwanz und (A,n-1,n-1) die einelementige Liste, die aus dem letzten Element besteht. Beispiel 87: Rekursiver Zugriff auf Array public static int maximum(int[] array, int first, int last) { // leeres Array if ( first > last ) { return 0; } // bilde Maximum aus aktuellem Element und dem Rest des Array return Math.max(array[first], maximum(array, first + 1, last)); } cdrom:/buch/examples/java/Anweisung/MaxList.java

Aufgabe 25: Maximum im Array

Wieviele Selbstaufrufe benötigt die Funktion maximum beim Aufruf mit ([5,6,3,2,8,9,10,5],0,8) ? Antwort: .................................................................... (Lösung S. 321)

4.5.2 Weitere Beispiele für Rekursion Beim Suchen eines Wertes in einer ungeordneten Liste oder einem ungeordneten Array bleibt einem nichts anderes übrig, als jedes Element zu betrachten. Ist die Liste jedoch sortiert, so bietet sich das als binäre Suche bekannte Verfahren an. Vergleiche das zu suchende Element x mit dem mittleren Listenelement. Ist es größer, so suche x in der hinteren Hälfte, ist es kleiner so in der vorderen.

122

Siehe auch: Array (S. 89)

4.5 Rekursion

117

Beispiel 88: Binäre Suche public static boolean binarySearch( int z, int[] array, int first, int last) { // leeres Feld enthaelt Element nicht if ( first > last ) { return false; } // pruefe ob gesuchtes Element in der Mitte liegt int middle = first + (last - first) / 2; if ( array[middle] == z ) { return true; } else if ( z > array[middle] ) { // Suche in rechter Hälfte return binarySearch(z, array, middle + 1, last); } else { // z < array[middle] // Suche in linker Hälfte weiter return binarySearch(z, array, first, middle - 1); } } cdrom:/buch/examples/java/Anweisung/MaxList.java

Das Array muss für die Anwendung der binären Suche sortiert sein. Das wird mit der Methode Arrays.sort aus dem Paket java.util erreicht. Aufgabe 26: Rekursive Funktion

Was berechnet die folgende Funktion? public static double fun(double s, int e) { if ( e < 0 ) { return 1.0 / fun(s, -e); } else if ( e == 0 ) { return 1; } else { return (((e % 2) == 1) ? s : 1) * fun(s * s, e / 2); } } cdrom:/buch/examples/java/Anweisung/Aufgaben.java

❍ Die Funktion berechnet das Alter einer Fichte mit einem Durchmesser

von s Zentimetern und einer Höhe von e Metern. e

❍ Die Funktion berechnet s mit 2*log (e) Multiplikationen. 2 ❍ Die Funktion berechnet die e-te Wurzel aus s. (Lösung S. 322)

Wir wollen das Umdrehen eines Strings rekursiv formulieren. Das Umdrehen eines Strings geschieht durch Anfügen des ersten Buchstabens hinten an den umgedrehten Rest-String. Beispiel 89: String umdrehen public static String revertString(String str) { if ( str.equals("") ) { return ""; } else { char head = str.charAt(0); String tail = str.substring(1); return revertString(tail) + head; } } cdrom:/buch/examples/java/Anweisung/RevertString.java

118

Kapitel 4: Algorithmen und Anweisungen

Aufgabe 27: reversi

Welchen Wert liefert revertString(tail) in der letzten Anweisungzeile der revertString Funktion, wenn diese mit "hase" aufgerufen wird ? Antwort: .................................................................... (Lösung S. 322)

Die Klasse StringBuffer123 verfügt über eine solche Methode reverse().

4.6 Fallunterscheidung Eine Fallunterscheidung beschreibt eine Programmverzweigung mit mehreren Ausgängen.

123

Siehe API-Dokumentation: java.lang.StringBuffer

4.6 Fallunterscheidung

119

SEMANTIK: In einer switch-Anweisung müssen alle ganzzahligen Konstanten verschieden sein. Die Konstanten müssen zuweisungskompatibel zum ganzzahligen Typ (alle außer long sind erlaubt) des Kontrollausdrucks sein. Ist der Wert des Kontrollausdrucks gleich einer dieser Konstanten, so wird zu der ihr folgenden Anweisung gesprungen, ansonsten zu der auf default folgenden Anweisung, falls eine default-Marke existiert, sonst ist die switch-Anweisung beendet. Es darf höchstens eine default-Marke pro switch-Anweisung stehen. Es werden alle Anweisungen ab der Einsprungstelle ausgeführt. Jede Anweisungsfolge ist mit einer break-Anweisung oder returnAnweisung abzuschließen, um die switch-Anweisung zu verlassen. Ist das nicht der Fall, so sollte ein entsprechender Kommentar auf diesen Sachverhalt hinweisen. Beispiel 90: Ausgabe von Oktal, Dezimal- und Hexadezimalzahlen public static final int HEX = 1; public static final int OCT = 2; public static final int DEC = 3; public static String toString(int zahl, int flag) { // behandle 0 als Sonderfall if ( zahl == 0 ) { return "0"; } // speichere das Vorzeichen boolean negativ = zahl < 0; // berechne Absolutwert if ( negativ ) { zahl = -zahl; } // Basis mit Normalwert 10 int basis = 10; // setze Basis je nach flag um switch ( flag ) { case HEX: basis = 16; break; case OCT: basis = 8; break; case DEC: basis = 10; break; default: // Wir nehmen den Normalfall basis = 10; break; } // lege leeres Ergebnis an String res = ""; // erstelle String, Schleife solange noch Stellen da sind while ( zahl != 0 ) { // bei Stelle < 10 benutze "+" Operator int ziffer = zahl % basis;

120

Kapitel 4: Algorithmen und Anweisungen if (ziffer < 10 ) { res = ziffer + res; } else { // behandle Hexadezimalstellen > 9 als switch switch ( ziffer ) { case 10: res = "a" + res; break; case 11: res = "b" + res; break; case 12: res = "c" + res; break; case 13: res = "d" + res; break; case 14: res = "e" + res; break; case 15: res = "f" + res; break; } } // gehe zur naechsten Ziffer zahl = zahl / basis; } // haenge bei Bedarf Vorzeichen vorne an if ( negativ ) { res = "-" + res; } return res;

} cdrom:/buch/examples/java/Anweisung/Switch.java

Aufgabe 28: switch-Anweisung

Mit welchen Parameter muss diese Funktion aufgerufen werden, um den Wert 11 zurückzugeben? public static int switch_fkt(int i) { switch ( i % 3 ) { case 0: i++; break; case 1: i--; default: i++; switch ( i % 3 ) { case 0: i--; case 1: i += 3; break; } break; } return i; } cdrom:/buch/examples/java/Anweisung/Aufgaben.java

Antwort: .................................................................... (Lösung S. 322)

4.7 Verlassen von Konstrukten

121

4.7 Verlassen von Konstrukten 4.7.1 return-Anweisung Eine wichtige Anweisung ist die Rückkehr aus einer Methode124 (return), die mit und ohne Ergebniswert erfolgt, je nachdem, ob die Methode einen Wert liefert oder nicht (Rückgabetyp void).

SEMANTIK: Die return-Anweisung beendet die Ausführung des Methodenrumpfes. Sie darf nur innerhalb eines Methodenrumpfes stehen.

SEMANTIK: Sie liefert den Wert des Ausdrucks (falls angegeben) als Ergebniswert an das aufrufende Objekt. Der Typ des Ausdrucks muss zum deklarierten Rückgabetyp der Methode zuweisungskompatibel sein. Beispiel 91: Primzahltest

Die Funktion isPrim testet, ob ihr Argument v eine Primzahl ist, indem für alle Zahlen kleiner als die Wurzel von v getestet wird, ob sie v ohne Rest teilen. public static boolean isPrim(int v) { // Zahlen kleiner 2 sind nicht prim if ( v < 2 ) { return false; } // teste solange q