Dirk Louis Peter Müller
Jetzt lerne ich
Java Der einfache Einstieg in die Internetprogrammierung
Markt+Technik Verlag
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei der Deutschen Bibliothek erhältlich.
Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig auch eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.
10 9 8 7 6 5 4 3 2 1 03 02 01 00
ISBN 3-8272-6040-X © 2000 by Markt+Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: NOWAK werbeagentur & medien, Pfaffenhofen Lektorat: Jürgen Bergmoser,
[email protected] Herstellung: Claudia Bäurle,
[email protected] Satz: text&form, Fürstenfeldbruck Druck und Verarbeitung: Media Print, Paderborn Printed in Germany
23 Inhaltsverzeichnis
Vorwort
13
Teil 1: Grundlagen und Textbildschirmanwendungen
17
1
Bevor es losgeht
19
2
Der erste Kontakt – von Applets und Applikationen
31
3
Von Daten, Operatoren und Objekten
53
4
Programmfluss und Fehlererkennung mit Exceptions
95
5
Objektorientierte Programmierung mit Java
135
6
Ein- und Ausgabe
173
7
Collections und weitere nützliche Klassen
199
Teil 2: Grafische Benutzerschnittstellen mit AWT und Swing
227
8
Das AWT (Abstract Window Toolkit)
229
9
Oberflächen mit Swing
253
10 Grafik, Grafik, Grafik
275
11 Bilder, Bilder, Bilder
315
12 Text, Text, Text
331
13 Menüs und andere Oberflächenelemente
363
Teil 3: Applet-Programmierung
405
14 Applets und das World Wide Web (WWW)
407
15 Threads und Animation
439
16 Übergabe von Parametern an Applets
473
17 Sound bei Applets/Applikationen
481
18 Datenbankanbindung mit JDBC
495
5
Übersicht
6
Anhang A: Installation des JDK
511
Anhang B: Programmerstellung
515
Anhang C: Debuggen
519
Anhang D: Schlüsselwörter und Java-Klassenübersicht
523
Anhang E: HTML-Grundlagen
531
Anhang F: Literatur und Adressen
537
Stichwortverzeichnis
541
Inhaltsverzeichnis 24 Inhaltsverzeichnis
Vorwort
13
Teil 1: Grundlagen und Textbildschirmanwendungen
17
1
Bevor es losgeht
19
1.1 1.2 1.3 1.4 1.5 1.6 1.7
Was ist Java? – I. Teil Was ist ein Programm? Wie werden Programme erstellt? Von Compilern und Interpretern Was ist Java? – II. Teil Welcher Java-Compiler ist der richtige? Ausstattung und Bezugsquellen
19 22 24 24 25 28 29
2
Der erste Kontakt – von Applets und Applikationen
31
2.1 2.2 2.3 2.4 2.5 2.6 2.7
Was unterscheidet Applets und Applikationen? Die erste Java-Applikation Das erste Java-Applet Ein einfaches Webdokument erstellen Test Lösungen Zusammenfassung
32 34 41 46 49 50 51
3
Von Daten, Operatoren und Objekten
53
3.1 3.2 3.3 3.4 3.5 3.6 3.7
Variablen und Anweisungen Operatoren Typumwandlung Objekte und Klassen Arrays Vordefinierte Klassen und Packages Test
54 63 67 71 84 87 88
7
Inhaltsverzeichnis
8
3.8 3.9
Lösungen Zusammenfassung
89 94
4
Programmfluss und Fehlererkennung mit Exceptions
95
4.1 4.2 4.3 4.4 4.5 4.6 4.7
Die Axiomatik des Programmablaufs Modularisierung durch Klassen und Methoden Kontrollstrukturen Fehlerbehandlung durch Exceptions Test Lösungen Zusammenfassung
96 97 110 122 127 129 132
5
Objektorientierte Programmierung mit Java
135
5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8
Vererbung Methoden (Klassenfunktionen) Variablen- und Methodensichtbarkeit Innere Klassen Mehrfachvererbung und Interfaces Test Lösungen Zusammenfassung
136 147 153 163 163 167 169 172
6
Ein- und Ausgabe
173
6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9
Streams Ausgaben auf den Bildschirm Ausgabe in Dateien Eingaben von Tastatur Aus Dateien lesen Rund um Strings Test Lösungen Zusammenfassung
174 174 178 180 185 186 195 196 197
7
Collections und weitere nützliche Klassen
199
7.1 7.2 7.3
Zufallszahlen erzeugen Zeit- und Datumsangaben Zeichenketten zerlegen
200 202 204
Inhaltsverzeichnis 7.4 7.5 7.6 7.7 7.8
Der Einsatz komplexer Datenstrukturen Algorithmen Test Lösungen Zusammenfassung
205 219 220 221 225
Teil 2: Grafische Benutzerschnittstellen mit AWT und Swing
227
8
Das AWT (Abstract Window Toolkit)
229
8.1 8.2 8.3 8.4 8.5 8.6
Der AWT-Reiseführer Aufbau einer GUI-Anwendung Das Event-Modell des AWT Test Lösungen Zusammenfassung
230 231 237 246 248 251
9
Oberflächen mit Swing
253
9.1 9.2 9.3 9.4 9.5 9.6 9.7 9.8
AWT oder Swing? Let’s Swing: die Grundlagen Unterschiede in der Programmierung mit Swing und AWT Chamäleon sein mit UIManager und LookAndFeel Ein umfangreicheres Beispiel Test Lösungen Zusammenfassung
254 258 262 265 267 268 270 274
10
Grafik, Grafik, Grafik
275
10.1 10.2 10.3 10.4 10.5 10.6 10.7 10.8
Das Arbeitsmaterial des Künstlers Erweitertes Layout mit Panel-Containern Kreise, Rechtecke und Scheiben Freihandlinien Noch mehr Grafik mit Java2D Test Lösungen Zusammenfassung
276 289 292 298 302 307 308 313
9
Inhaltsverzeichnis 11
Bílder, Bilder, Bilder
315
11.1 11.2 11.3 11.4 11.5 11.6
Der Bildbetrachter Dateien öffnen und speichern: die Klasse FileDialog Laden und Anzeigen von Bildern: das Image-Objekt Test Lösungen Zusammenfassung
315 322 324 327 327 329
12
Text, Text, Text
331
12.1 12.2 12.3 12.4 12.5 12.6 12.7 12.8 12.9 12.10
Ein Texteditor Umgang mit Text: JTextField, JTextArea und JTextPane Kombinationsfelder Eigene Dialoge Nach Textstellen suchen Unterstützung der Zwischenablage Drucken mit PrinterJob Test Lösungen Zusammenfassung
332 333 341 345 350 353 355 357 358 360
13
Menüs und andere Oberflächenelemente
363
13.1 Die Komponentenhierarchie erstellt von ciando 13.2 Die Basisklasse Component 13.3 13.4 13.5 13.6 13.7 13.8 13.9 13.10 13.11 13.12
10
Statische Textfelder (Label, JLabel) Schaltflächen (Button, JButton) Eingabefelder (TextField und TextArea, JTextField und JTextArea) Optionen (Checkbox, JCheckBox, JRadioButton) Listen- und Kombinationsfelder (List und Choice, JList und JComboBox) Bildlaufleisten (Scrollbar, JScrollBar) Menüleisten (Menubar) Test Lösungen Zusammenfassung
364 366 368 370 373 377 381 385 387 391 394 403
Inhaltsverzeichnis Teil 3: Applet-Programmierung
405
14
Applets und das World Wide Web (WWW)
407
14.1 14.2 14.3 14.4 14.5 14.6 14.7 14.8 14.9
Das Web Webseiten und Applets über das WWW anbieten Applet-Besonderheiten Applets in Applikationen verwandeln Applets und jar-Dateien Böse Welt: Applets und die Sicherheit Test Lösungen Zusammenfassung
408 412 415 423 433 434 435 436 437
15
Threads und Animation
439
15.1 15.2 15.3 15.4 15.5 15.6 15.7 15.8
Multithreading mit Java Eigene Threads erzeugen: die Klasse Thread Eigene Threads erzeugen: das Runnable Interface Wissenswertes rund um Threads Threads und Animation Test Lösungen Zusammenfassung
440 445 450 451 455 463 464 471
16
Übergabe von Parametern an Applets
473
16.1 16.2 16.3 16.4
Übergabe von Parametern an Applets Test Lösungen Zusammenfassung
473 476 476 480
17
Sound bei Applets/Applikationen
481
17.1 17.2 17.3 17.4 17.5
Besonderheiten des File-Transfers übers Web Bild- und Klangdateien in Applets Test Lösungen Zusammenfassung
482 483 492 492 493
11
Inhaltsverzeichnis
12
18
Datenbankanbindung mit JDBC
495
18.1 18.2 18.3 18.4 18.5 18.6 18.7 18.8 18.9
Datenbanken-ABC Die JDBC Einrichten der Datenbank Zugriff auf eine Datenbank Datenbanken und Applets Zertifikate und vertrauenswürdige Applets Test Lösungen Zusammenfassung
496 498 500 502 506 507 508 508 509
Anhang A: Installation des JDK
511
Anhang B: Programmerstellung
515
B.1 B.2 B.3
515 516 518
Erstellung von Applikationen Erstellung von Applets Erstellung von HTML-Dokumenten
Anhang C: Debuggen
519
C.1 C.2
519 520
Grundsätzliches Vorgehen Der JDK-Debugger
Anhang D: Schlüsselwörter und Java-Klassenübersicht
523
D.1 D.2
523 524
Schlüsselwörter Klassenübersicht
Anhang E: HTML-Grundlagen
531
E.1 E.2 E.3
531 532 533
Tags Das Grundgerüst Layout- und Formatierungsbefehle
Anhang F: Literatur und Adressen
537
Stichwortverzeichnis
541
Vorwort 25 Vorwort
Sehr wahrscheinlich haben Sie das Wort »Java« seit dem Erdkundeunterricht in der Schule nur noch selten gehört. In den letzten Jahren dürfte es Ihnen jedoch wieder häufiger begegnet sein, allerdings nicht in den Auslagen der Reisebüros, sondern beim Surfen im fast schon legendären World Wide Web des Internets. Irgendwann hat Sie die Neugier gepackt und Sie wollen nun endlich wissen, was genau hinter diesem Schlagwort steckt und wieso jeder davon redet. Sicherlich ist Ihnen schon bekannt, dass Java eine Programmiersprache ist. Zwei Merkmale unterscheiden Java von anderen bekannten Programmiersprachen wie Basic, C oder Pascal: ✘ Es ist brandneu (Java gibt es in der jetzigen Form erst seit 1993). ✘ Es ist die Sprache des World Wide Web. Dies und eine Reihe weiterer Vorteile, auf die wir im Lauf dieses Buches noch eingehen werden, machen Java zu einer der attraktivsten Programmiersprachen, mit denen man sich derzeit auseinandersetzen kann. Ob Sie also bereits seit Jahren mit Basic oder Pascal programmieren und mit Java nun den Sprung ins Internet schaffen wollen, oder ob Sie sich Java als Ihre erste Programmiersprache überhaupt ausgesucht haben – mit Java haben Sie die richtige Wahl getroffen und mit diesem Buch auch. Dieses Buch führt Sie auf leicht verständliche und gleichsam unterhaltsame Weise in die Grundlagen der Programmierung mit Java ein. Vorhandene Programmierkenntnisse können von Vorteil sein, werden aber nicht voraus-
13
Vorwort gesetzt. Schritt für Schritt werden Sie sich in Java einarbeiten und erfahren, wie Sie die Mächtigkeit der Sprache für Ihre Zwecke nutzen können. Sie werden Ihre ersten eigenständigen Java-Applikationen schreiben, Applets zur Unterstützung von HTML-Dokumenten implementieren, mit Animationen und Threads programmieren. Aber auch das Umfeld, in das Java eingebettet ist, wollen wir nicht vernachlässigen. Falls Sie das World Wide Web bisher nur aus der Sicht Ihres Browsers kennen, haben Sie hier die Gelegenheit, einen Blick hinter die Kulissen zu werfen und Ihre erste eigene WWW-Seite aufzusetzen. Wir möchten Ihnen aufzeigen, wie Sie Ihre JavaProgramme sinnvoll einsetzen können, wie Sie Java-Programme portieren und wie Sie Ihre Programme ins Internet bringen. Am Ende eines jeden Kapitels finden Sie eine Reihe von Testfragen und eine Zusammenfassung des behandelten Stoffs sowie einige Übungsaufgaben, die zur Vertiefung und Wiederholung des Stoffs dienen, vor allem aber auch die Lust am Programmieren anregen sollen. Programmierkenntnisse sind wie gesagt nicht erforderlich, aber Sie sollten auch nicht zu den Zeitgenossen gehören, die bisher noch jedem Computer erfolgreich aus dem Weg gegangen sind. Spaß am Programmieren können wir Ihnen nur dann vermitteln, wenn Sie selbst ein wenig guten Willen und Ausdauer mitbringen. Gute Laune, eine Portion Neugier, dieses Buch – was brauchen Sie noch? Um mit Java programmieren zu können, brauchen Sie ein entsprechendes Entwicklungspaket (JDK von Sun, Visual J++, JBuilder, Borland C++ 5 mit Java-Addon etc.). Für welches Entwicklungspaket Sie sich entscheiden, ist an sich gar nicht so wichtig, da wir uns in diesem Buch – abgesehen von der Erläuterung der Programmerstellung – nicht mit irgendwelchen compilerspezifischen Eigenheiten aufhalten, sondern vor allem dem mehr oder weniger verbindlichen Java-Standard widmen. Achten Sie aber darauf, dass Ihre Java-Umgebung Java 2 (bzw. 1.2.2) unterstützt. Wir haben zum Austesten der Programme den Java-Compiler schlechthin verwendet: ✘ den JDK VON SUN (JDK = Java Development Kit), als den Urvater aller Java-Compiler, der nicht nur stets auf dem aktuellsten Stand ist, sondern zudem auch frei über das Netz verfügbar ist. Die wichtigsten Arbeitsschritte bei der Programmierung mit dem JDK werden in den einleitenden Kapiteln und im Anhang dieses Buchs vorgestellt (siehe Kapitel 2 und Anhang B). Unabhängig davon, für welche Entwicklungsumgebung Sie sich entscheiden, sollten Sie bei Ihrer Wahl darauf achten, dass der Java-Interpreter Ihres
14
Vorwort Entwicklungspakets zu Prozessor und Betriebssystem Ihres Computers passt und dass Ihr Betriebssystem Multithreading erlaubt (beispielsweise WinNT, Win95, Linux, Sun Solaris, MacOS, aber nicht Windows 3.11). Viel Erfolg mit Java wünschen Ihnen
die Autoren
Vorwort zur dritten Auflage Die vorliegende Auflage steht ganz im Zeichen von Swing. Swing ist Javas neue Klassenbibliothek zur Erstellung grafischer Benutzeroberflächen und teils Ersatz, teils Erweiterung zum AWT. Nachdem wir Ihnen Swing schon in der letzten Auflage vorgestellt haben, sind wir nun noch einen Schritt weiter gegangen und haben den ganzen Teil 2 zur Erstellung grafischer Benutzeroberflächen auf Swing umgestellt (was allerdings nicht bedeutet, dass die AWT-Grundlagen vernachlässigt würden). Wir bleiben damit unserem Grundsatz treu, Sie stets über die aktuellen Entwicklungen beim Java-Standard auf dem Laufenden zu halten. Werfen Sie Ihren Java-Compiler an und swingen Sie mit! Was hat sich noch geändert? Der erste Teil wurde um ein Kapitel zur Verwendung der Collection- und Utility-Klassen erweitert. In diesem Kapitel erfahren Sie, wie man Zufallszahlen erzeugt, Zeit und Datum ermittelt und Daten in fortgeschrittenen Datenstrukturen (wie Listen und Hash-Tabellen) verwaltet. Das Kapitel zur Thread-Programmierung wurde komplett überarbeitet, um Ihnen dieses schwierige Thema noch klarer und verständlicher zu machen. Schließlich haben wir in den Beispielprogrammen die Namensgebung für unsere Bezeichner normiert: ✘ class CBeispielKlasse. Klassen beginnen stets mit einem C und einem Großbuchstaben. Das C dient der Unterscheidung zwischen den von uns definierten Klassen und den Klassen der Java-Bibliotheken. ✘ m_klassenVariable. Klassenvariablen beginnen mit m_ (m steht für member) und Kleinbuchstaben.
15
Vorwort ✘ lokaleVariable. Lokale Variablen von Methoden beginnen mit Kleinbuchstaben. ✘ tueDies(). Methoden beginnen ebenfalls mit Kleinbuchstaben. An dem Konzept des Buchs (lockerer Stil, verständliche Beschreibungen, viel Information), das bei den Lesern großen Anklang gefunden hat, hat sich ansonsten nichts geändert. Überhaupt haben die Leser regen Anteil an dem Buch genommen und wir möchten uns auf diesem Wege für das viele Lob, die Anregungen und auch die Kritik bedanken.
Peter und Dirk
[email protected] [email protected] Saarbrücken, den 24. September 1999
16
TEIL 1
Grundlagen und Textbildschirmanwendungen Im Gegensatz zu anderen Java-Lehrbüchern, die gleich in den ersten Kapiteln voll in die Applet-Programmierung einsteigen, ist es das Anliegen dieses Buchs, Ihre Programmierkenntnisse auf eine solide Basis zu stellen. Das Buch ist daher in drei Teile gegliedert, von denen sich der erste mit der allgemeinen Java-Syntax und der Erstellung von Textbildschirmanwendungen, der zweite mit der Programmierung grafischer Oberflächen für Windowing-Programme und erst der dritte mit den Besonderheiten der Applet-Programmierung befasst. Die drei Teile dieses Buchs sind allerdings nicht nur den drei Arten von Programmen gewidmet, die Sie mit Java erstellen können (KonsolenApplikationen, GUI1-Applikationen und Applets), sondern geben auch die drei Stufen vor, die Sie auf dem Weg zum Java-Programmierer erklimmen müssen: ✘ die Beherrschung der grundlegenden Java-Syntax (Teil 1) ✘ die Einarbeitung in die Möglichkeiten des AWT (einschließlich Swing) und damit in die Programmierung von Applikationen für WindowingSysteme wie Microsoft Windows, XWindows, OSF Motif (Teil 2) ✘ die Besonderheiten der Applet-Programmierung (Teil 3) Nachdem Sie diese drei Teile bearbeitet haben und gewissermaßen vom Kaffeetrinker zum Kaffeekenner gereift sind, steigen wir im letzten Kapitel noch in ein Gebiet der Programmierung ein, das auch für Java-Programmierer immer interessanter wird: die Einbindung von Datenbanken in Programme. Abgerundet wird das Buch durch einen mehrteiligen Anhang, in dem Sie unter anderem eine HTML-Referenz, Hilfestellungen zur Installation und zur Fehlersuche (Debuggen) sowie ein Literaturverzeichnis finden.
1 Graphical User Interface = grafische Benutzeroberfläche
17
Kapitel 1
Bevor es losgeht 1 Bevor es losgeht
Ich weiß, ich weiß – Sie sitzen vor Ihrem Bildschirm, haben bereits Ihre Java-Entwicklungsumgebung installiert, brennen darauf, Ihr erstes Java-Programm zu schreiben und sind einigermaßen ungehalten, sich erst noch durch etliche Seiten theoretischer Ausführungen quälen zu müssen. Müssen Sie nicht! Wenn Sie Ihre Entwicklungsumgebung schon eingerichtet haben und mit der Programmerstellung prinzipiell vertraut sind, überspringen Sie dieses Kapitel einfach. Nur wenn Sie ein absoluter Neuling in der Programmierung sind oder von Java nicht viel mehr wissen, als dass es eine Programmiersprache ist, sollten Sie dieses Kapitel unbedingt vorab durchlesen. Alle anderen können nach Bedarf auch noch später hierher zurückkehren.
1.1
Was ist Java? – I. Teil
Java entstand 1993 als Forschungsprojekt der Firma Sun, wobei schon auf andere Vorarbeiten zurückgegriffen werden konnte. Der konkrete Anlass war der einsetzende Boom des World Wide Web, das nach einer geeigneten Programmiersprache verlangte. Java ist vor diesem Hintergrund zu betrachten und daher untrennbar mit dem Internet und seinen Erfordernissen verbunden. Wie Sie im Laufe des Buches lernen werden, ist es mit Java relativ einfach, Anwendungen zu schreiben, die mit anderen Computern via Internet kommunizieren, Grafiken oder Webseiten anzeigen und interaktive Fenster und Oberflächen verwenden. Natürlich können auch alle anderen Aufgaben programmiert werden, die bisher durch den Einsatz klassischer Programmiersprachen gelöst wurden.
19
KAPITEL
1
Bevor es losgeht
Wie steht es in diesem Zusammenhang mit der Verwandtschaft von Java zu den anderen Programmiersprachen? Man entwickelt schließlich keine neue Programmiersprache, ohne die eigenen Erfahrungen mit den etablierten Programmiersprachen einfließen zu lassen. C/C++-Programmierer wird es freuen zu hören, dass Java stark an C++ angelehnt ist. Die Gründe hierfür sind zweifellos in der Objektorientierheit, der Schnelligkeit und der Leistungsfähigkeit von C++ zu suchen, aber natürlich auch in der bisherigen Bedeutung dieser Sprache fürs Internet. Programmierer, die von Pascal, Basic oder anderen objektorientierten Sprachen herkommen, wird es freuen zu hören, dass Java viel unnötigen Ballast, den C++ mit sich schleppt, abgeworfen hat und dadurch wesentlich einfacher zu erlernen und zu programmieren ist. Diese Entschlackung dient nicht nur der Entlastung des Programmierers, sondern soll vor allem auch die Entwicklung »sicherer« Programme gewährleisten. Für eine Internet-Sprache ist dies ein ganz wesentlicher Aspekt, denn unsachgemäß programmierte Anwendungen, die in Extremsituationen ein unerwünschtes Verhalten zeigen oder abstürzen, können in einem Computer-Netzwerk schnell zu einem Sicherheitsrisiko werden. Natürlich liegt die Verantwortung für die Sicherheit der Anwendungen letztendlich beim Programmierer. Je komplizierter und undurchsichtiger die Konzepte einer Sprache aber sind, um so wahrscheinlicher ist es, dass der Programmierer unbeabsichtigt Fehler einbaut. In Java hat man dies erkannt und beispielsweise die gesamte Zeiger-Programmierung und die dynamische Speicherverwaltung aus den Händen des Programmierers genommen und Compiler und Interpreter übertragen. Falls Sie schon eine andere objektorientierte Programmiersprache, wie z.B. C++, beherrschen: Die folgenden Konzepte gibt es in Java nicht: 1. Zeiger (die dynamische Speicherverwaltung wird intern vorgenommen) 2. Funktionen (statt allein stehender Funktionen gibt es nur noch Methoden (Elementfunktionen) von Klassen) 3. Strukturen und Unions 4. Arrays und Zeichenketten gibt es nur noch als Objekte 5. Typendefinition (typedefs) 6. Templates 7. Mehrfachvererbung (nur in gemäßigter Form) 8. Überladung von Operatoren
20
Was ist Java? – I. Teil Java deshalb als Schmalspur-C++ zu bezeichnen, wäre aber völlig falsch. Von der Leistungsfähigkeit her steht Java C++ kaum etwas nach. Betrachtet man sich obige Liste etwas genauer, lässt sich feststellen, dass viele Konzepte, die C++ von C übernommen hat, zugunsten einer konsequenteren objektorientierten Programmierung aufgegeben wurden (dies betrifft die Sprachelemente 2 bis 5, die alle im Klassenkonzept aufgegangen sind). Java ist daher mittlerweile die Standardprogrammiersprache an allen US-amerikanischen Universitäten und wird auch an deutschen Universitäten immer beliebter. Andererseits wurde auf fortgeschrittenere objektorientierte Konzepte (Punkte 6 bis 8), die im Wesentlichen der Wiederverwertung objektorientierten Quellcodes dienen, aber für Einsteiger (und auch oft noch für Fortgeschrittene) manchmal schwierig zu handhaben sind, verzichtet. Was geblieben ist, ist eine relativ leicht zu erlernende, konsequent objektorientierte Sprache, die Ihnen einiges zu bieten hat: ✘ Objektorientiertheit ✘ statische Typbindung, aber späte Methodenbindung ✘ dynamische Speicherverwaltung und Garbage Collection ✘ Multithreading ✘ Exception-Handling Neben Java hört man auch viel von JavaScript. JavaScript wurde von Netscape entwickelt und als Erweiterung des HTML-Standards implementiert (in Browsern, die diese Erweiterung nicht unterstützen, werden die Scripte einfach ignoriert). JavaScript ist an Java angelehnt und sollte daher für C/C++- und Java-Programmierer leicht zu erlernen sein. Die Verwandtschaft zu Java hört damit aber auch schon auf. Während man mit Java echte Programme schreibt, die entweder als eigenständige Anwendungen (Applikationen) oder als Teil eines HTML-Dokuments (Applets) ausgeführt werden, wird JavaScript-Code direkt in den HTML-Code eingefügt und vom Browser interpretiert (sofern dieser JavaScript unterstützt). Neben JavaScript gibt es mittlerweile auch noch weitere Scriptsprachen, die vornehmlich von den verschiedenen Browser-Entwicklern angeboten werden und vor allem für die Designer von HTML-Seiten interessant sind, die sich nicht mit dem Erlernen einer echten Programmiersprache belasten möchten.
21
KAPITEL
1 1.2
Bevor es losgeht
Was ist ein Programm?
Prinzipiell sind Programme nichts anderes als eine Folge von Befehlen, die an einen Computer gerichtet sind und von diesem befolgt werden. Im Grunde genommen funktionieren Programme also genauso wie Kochrezepte: Sie als Programmierer sind der Koch, der das Buch schreibt. Jedes Kochrezept entspricht einem Programm und der Computer, der Ihre Programme ausführt, ist der Leser. Abb. 1.1: Analogie zwischen Programmen und Kochrezepten
Koch/Programmierer
Kochrezept/Programmerstellung
1 1.Aufgabe tue dies 2 2. Aufgabe tue das 3 3. Aufgabe ...
Algorithmus
class HalloWelt { public static
Quelltext
011001 010010 .......
Maschinencode
Nachkochen/Ausführung
Leider ist die Realität wie üblich etwas komplizierter als das Modell. Im Falle des Kochrezepts können wir einfach davon ausgehen, dass Schreiber und Leser die gleiche Sprache sprechen. Im Falle des Programmierers und des Computers ist dies natürlich nicht der Fall, denn Sie als Programmierer sprechen an sich Deutsch und der Computer spricht ... ja, welche Sprache versteht eigentlich ein Computer?
22
Was ist ein Programm? Ich wünschte, Sie hätten diese Frage nicht gestellt, denn die Antwort ist äußerst unerfreulich. Der Computer, in diesem Fall sollte man genauer von dem Prozessor des Computers sprechen, versteht nur einen ganz begrenzten Satz elementarer Befehle – den sogenannten Maschinencode, der zu allem Unglück noch binär codiert ist und daher als eine Folge von Nullen und Einsen vorliegt. Können Sie sich vorstellen, Ihre Programme als Folge von Nullen und Einsen zu schreiben? Wahrscheinlich genauso wenig, wie Ihr Computer in der Lage ist, Deutsch zu lernen. Wir haben also ein echtes Verständigungsproblem. Um dieses zu lösen, müssen Sie – als der Intelligentere – dem Computer schon etwas entgegenkommen. Kehren wir noch einmal zu unserem Kochbuch zurück und stellen Sie sich vor, ein Chinese würde ein Kochbuch schreiben, das auf dem deutschen Buchmarkt erscheinen soll. Zwar findet der Chinese keinen geeigneten Übersetzer, der das Buch ordentlich vom Chinesischen ins Deutsche übersetzen könnte, aber er erinnert sich seiner Englischkenntnisse, die für ein Kochbuch absolut ausreichend sein sollten. Er schreibt also sein Buch in Englisch und lässt es dann von einem Übersetzer ins Deutsche übertragen. Gleiches geschieht auch bei der Programmierung. Statt Ihre Programme in Deutsch aufzusetzen, bedienen Sie sich einer Programmiersprache (wie Java, C, Pascal, Basic etc.), für die es einen passenden Übersetzer gibt (in diesem Fall auch Compiler genannt), der Ihre Anweisungen in Maschinencode umwandeln kann. Ich denke, das ist jetzt schon etwas klarer. Was aber genau ist jetzt das Programm? Die noch in Deutsch formulierten Befehle? Die in Java formulierten Befehle? Oder die binär codierten Maschinenanweisungen? Im weitesten Sinne können Sie in allen drei Fällen von Ihrem Programm reden. Wenn Sie es dagegen genau nehmen wollen, bezeichnen Sie die noch in Ihrer Sprache aufgesetzte Befehlsfolge als Algorithmus, die in Java formulierte Version des Algorithmus als Quelltext Ihres Programms und erst den vom Compiler erzeugten Maschinencode als Ihr Programm. Mehr oder weniger unabsichtlich sind wir damit bereits in die Programmerstellung abgeglitten, die im nächsten Abschnitt noch einmal zusammengefasst wird.
23
KAPITEL
1 1.3
Bevor es losgeht
Wie werden Programme erstellt?
Die Entwicklung von Computerprogrammen läuft unabhängig von der verwendeten Sprache üblicherweise nach dem folgenden Muster ab: 1. Man hat ein Problem, eine Idee, eine Aufgabe, zu deren Lösung man einen Computer einsetzen möchte. 2. Als Nächstes wird die Aufgabe als Algorithmus, also als eine Folge von Befehlen formuliert. Größere Probleme werden dabei in Teilaufgaben und Teilaspekte aufgeteilt. (Ob der Algorithmus tatsächlich auf dem Papier oder nur im Kopf des Programmierers entwickelt wird, hängt von der Komplexität der Aufgabe und der Genialität des Programmierers ab). 3. Der Algorithmus wird in für den Computer verständliche Anweisungen einer Programmiersprache umgesetzt. Dies ergibt den sogenannten Quelltext oder Quellcode. 4. Dieser Quelltext muss dann durch ein spezielles Programm, den Compiler, in Maschinenanweisungen übersetzt werden, die das eigentliche Herz des Computers – der Prozessor – versteht und ausführen kann. 5. Das ausführbare Programm wird gestartet, das heißt in den Hauptspeicher geladen und vom Prozessor ausgeführt.
1.4
Von Compilern und Interpretern
Bei einigen Programmiersprachen fallen die Schritte 4 und 5 zusammen. Es wird also nicht das ganze Programm erst übersetzt (kompiliert) und dann bei Bedarf ausgeführt. Stattdessen wird bei der Ausführung des Programms der Quelltext Zeile für Zeile eingelesen, übersetzt und ausgeführt. In diesem Fall spricht man von Interpreter-Sprachen, weil das Programm nicht als ausführbare Datei, sondern bloß als Quelltext vorliegt, der nur mit Hilfe eines speziellen Programms (des Interpreters), welches die zeilenweise Übersetzung während des Programmlaufs übernimmt, ausgeführt werden kann. In dem Beispiel aus Abschnitt 1.2 würde dies bedeuten, dass der chinesische Koch seine Kochkünste nicht in Buchform, sondern als Hörkassette herausgegeben hat und Sie mit einem Dolmetscher (Interpreter) an der Seite diese Kassette abspielen und die Rezepte nachkochen.
24
Was ist Java? – II. Teil Abgesehen davon, dass Sie wahrscheinlich niemals auf die Idee kommen werden, sich eine chinesische Kassette mit Kochrezepten zu kaufen, haften interpretierten Programmen zwei wesentliche Nachteile an: ✘ Da die Erzeugung des Maschinencodes erst während der Ausführung vorgenommen wird, dürfte klar sein, dass solche Programme wesentlich langsamer ablaufen als kompilierte Programme. ✘ Da diese Programme als Quelltext vertrieben werden, sind der unauthorisierten Nutzung des Programmtextes und der für das Programm entwickelten Algorithmen Tür und Tor geöffnet. Auf der anderen Seite haben diese Programme den Vorteil, dass Sie sehr gut zu portieren sind, das heißt, die Übertragung von einem Computer auf einen anderen ist unproblematisch. Bekannte Vertreter von Compiler-Sprachen sind C, C++ und Pascal. Das klassische Beispiel für eine interpretierte Programmiersprache ist Basic. Jetzt fragen Sie sich sicherlich, wo Java einzuordnen ist.
1.5
Was ist Java? – II. Teil
Ist Java nun eine Compiler- oder eine Interpreter-Sprache? Nun, die Einteilung ist hier nicht ganz so klar. Man kann zwar vielerorts lesen, es sei eine Interpreter-Sprache, aber das wird der Lage nicht ganz gerecht, denn tatsächlich ist Java beides. Java-Quellcode wird zunächst mit einem Compiler (er heißt JAVAC) übersetzt, allerdings nicht in den Maschinencode des jeweiligen Prozessors, sondern in sogenannten Bytecode. Man kann sich diesen Bytecode als einen Maschinencode eines virtuellen Prozessors vorstellen, das heißt eines Prozessors, den es gar nicht gibt! Damit der Bytecode nun von einem echten Prozessor ausgeführt werden kann, muss er während des Programmlaufs in dessen Maschinencode übersetzt werden, das heißt, ein Interpreter ist zum Ausführen von Java-Programmen notwendig, den man einfach wie die Sprache getauft hat, also JAVA.
25
KAPITEL
1
Bevor es losgeht
Abb. 1.2: Erstellung von Java-Programmen
class HalloWelt { public static
Java Compiler (Pentium)
Java Compiler (PowerPC)
011001100 010010010 100101010
Java Interpreter (Pentium)
Java Code
Java Compiler (SPARC)
Java Bytecode (Plattformunabhängig)
Java Interpreter (PowerPC)
Java Interpreter (SPARC)
Warum diese seltsame Mischung aus Compiler- und Interpretersprache? Die Antwort liegt eigentlich auf der Hand. Es geht darum, die Vorteile beider Systeme miteinander zu verbinden: ✘ Der vorkompilierte Bytecode kann wesentlich schneller interpretiert werden. ✘ Die Übersetzung in Bytecode schützt Ihre Algorithmen vor unerwünschter Nachahmung.
26
Was ist Java? – II. Teil ✘ Der Bytecode ist plattformunabhängig. Gleichgültig, auf welchem Computer der Bytecode erstellt worden ist, er wird immer identisch aussehen. Es ist dadurch möglich, Bytecode von einem Rechner zum nächsten zu transferieren (zum Beispiel über das Internet) und dort ausführen zu lassen, ohne die geringste Änderung vornehmen zu müssen. (Wenn Sie schon in konventionellen Sprachen wie C programmiert haben und das Problem hatten, ein Programm zu entwerfen, das auf verschiedenen Betriebssystemen, Prozessoren und Compilern läuft, werden Sie diesen Vorteil von Java sicherlich zu schätzen wissen.) ✘ Der Interpreter hat die Möglichkeit, die Programmausführung zu überwachen und beispielsweise (absichtliche oder unabsichtliche) unkorrekte Speicherzugriffe oder Datenmanipulationen zu erkennen und direkt mit einer Fehlermeldung abzufangen. Dies ist insbesondere unter Sicherheitsaspekten interessant. Da Java für das Internet und somit beliebigen Datenaustausch konzipiert ist, muss weitgehend sichergestellt sein, dass ein Java-Programm, das man mit seinem Browser durch einen mehr oder weniger achtlosen Klick startet, nicht irgendwelche üblen Sachen anstellt. Ein Interpreter, der ständig darauf achtet, was das Java-Programm gerade tun will, ist dafür ein geeigneter Ansatz. Zusammenfassend lässt sich sagen, dass Java-Programme aufgrund des Compiler/Interpreter-Ansatzes hardwareunabhängig und somit portabel sind und gleichzeitig ein relativ hohes Maß an Sicherheit vor Manipulationen bieten. Diese Vorteile gehen jedoch zu Lasten der Geschwindigkeit. Interpretierte Java-Programme sind trotz Bytecode ca. fünf bis zehn Mal langsamer als vergleichbare C/C++-Programme. Dies sollte Sie aber nicht abschrecken. Moderne Prozessoren sind schnell genug und viele Programme verbringen die meiste Zeit mit Warten auf Benutzereingaben, so dass der Geschwindigkeitsnachteil gar nicht zum Tragen kommt. Mittlerweile gibt es auch so genannte Just-in-time (JIT) Compiler, die der Java-Interpreter während der Programmausführung aufruft für Programmteile, die oft benötigt werden. Der JIT-Compiler übersetzt dann diese Teile in echten Maschinencode für den jeweiligen Prozessor, so dass diese Programmteile mit maximaler Geschwindigkeit ausgeführt werden können. Manche Entwicklungsumgebungen bieten ferner auch Compiler an, die den Bytecode komplett in Maschinencode übersetzen (wie ein konventioneller C++-Compiler beispielsweise), der nur noch wenig langsamer als C++-Code ist.
27
KAPITEL
1 1.6
Bevor es losgeht
Welcher Java-Compiler ist der richtige?
Um in die Java-Programmierung einzusteigen, ist im Prinzip eine Entwicklungsumgebung so gut wie die andere. Auch im Hinblick auf die Programmbeispiele in diesem Buch sollte es da keine Unterschiede geben – vorausgesetzt Ihre Entwicklungsumgebung hält sich an das von Sun vorgegebene »Java Developer Kit« (JDK) Java 2 Standard Edition Version 1.3, kurz J2SE (vielerorts wird noch die Version 1.2.2 verwendet). Unterschiede gibt es aber in dem Komfort und dem Preis-Leistung-Verhältnis der verschiedenen Programme. Hier ein kurzer Vergleich nach Entwicklungsumgebung, Editor, Compiler, Linker, Applet-Viewer, Debugger, Preis-Leistungs-Verhältnis: ✘ Sun JDK – der Urvater aller Java-Compiler und trotzdem immer als erster auf dem aktuellsten Stand. Das JDK (Java Developer Kit) besteht aus einer Sammlung aller für die Programmierung erforderlichen Tools (Compiler, Interpreter, Debugger, AppletViewer etc.), den zu Java gehörenden Standardbibliotheken und einer Reihe von Beispielprogrammen. Da es allerdings über keine integrierte Entwicklungsumgebung verfügt, ist die Arbeit mit ihm etwas unhandlich. Programmierer, die von Unix herkommen, wird dies vermutlich weniger stören als Windows-Anwender, die zur Arbeit mit dem JDK auf das MS-DOS-Fenster zurückgreifen müssen. Den mangelnden Bedienungskomfort gleicht der JDK durch seine Leistungsfähigkeit, seine Aktualität, seine naturgemäße optimale Unterstützung des Java-Standards und seinen günstigen Preis aus (der JDK kann frei aus dem Internet heruntergeladen werden, siehe unten). ✘ Visual J++ – der Microsoft-Compiler, womit vielleicht schon alles gesagt ist. ✘ webgain VisualCafe Standard Version: Eine vollwertige, häufig gelobte Entwicklungsumgebung, die kostenlos für Privatanwender verfügbar ist. ✘ JBuilder – Der JBuilder vereinigt Leistungsfähigkeit und optimale Unterstützung des Java-Standards mit den Annehmlichkeiten einer integrierten Entwicklungsumgebung (IDE) und der visuellen Programmierung auf der Grundlage der JavaBeans. Der JBuilder stammt von Borland, das aufgrund der Qualität seiner Produkte (Delphi, C++Builder) von Sun als Partner zur Entwicklung des JavaBeans-Konzepts auserkoren wurde. Den JBuilder gibt es in verschiedenen Versionen für Windows und Linux. Die kostenlose Foundation-Version kann man sich von der Bor-
28
Ausstattung und Bezugsquellen land-Website, www.borland.com, herunterladen. Sie findet sich übrigens auch auf der Begleit-CD zu dem Buch »Jetzt lerne ich JBuilder«, das eine Adaption dieses Titels für den JBuilder ist. Auch wenn der JBuilder gerade auch dem Anfänger den Einstieg in die Programmierung erleichtern kann, möchten wir Ihnen als ersten Compiler doch das JDK ans Herz legen (siehe Webadressen im nachfolgenden Abschnitt und im Anhang).
TIP
Gerade weil dem JDK der Komfort einer integrierten Entwicklungsumgebung und die Leichtigkeit und trügerische Sicherheit der visuellen Programmierung mit JavaBeans1 fehlt, halten wir es für den Einstieg ideal. Wir möchten, dass Sie erst einmal selbst lernen, wie man gute und sichere Programme schreibt und sich nicht gleich von Anfang an auf eine Entwicklungsumgebung verlassen, die Ihnen zwar vieles abnimmt, aber auch vieles vor Ihnen verbirgt. Wir wollen nicht, dass Sie einfach aufs Geratewohl programmieren, Sie sollten auch verstanden haben, was Sie programmieren. Später können Sie dann jederzeit auf eine integrierte Entwicklungsumgebung umsteigen. Ganz abgesehen von dem erzieherischen Wert des JDK ist es auch frei verfügbar, so dass gerade Anfänger, die erst einmal austesten wollen, ob Java für Sie die richtige Programmiersprache ist, sich nicht gleich in finanzielle Abenteuer stürzen müssen.
1.7
Ausstattung und Bezugsquellen
2
Bevor wir im nächsten Kapitel voll in die Programmierung einsteigen, sollten Sie Ihren Computer vorbereitet haben. Folgende Zutaten sind notwendig: ✘ ein Texteditor – Ein Editor ist ein rudimentäres Textverarbeitungssystem. Die meisten Entwicklungsumgebungen verfügen über einen eingebauten
1 Ein JavaBean (Java-Bohne) ist ein funktionell abgeschlossener Programmierbaustein, dessen Aufbau dem JavaBeans-Standard (siehe unter http://java.sun.com/beans/) entspricht. Mit JavaBeans kann man Programme nach dem Baukastenprinzip zusammenstellen – fast so wie man früher mit Legos gebaut hat. Während die Entwicklung eigener Beans recht kompliziert sein kann, ist die Verwendung bestehender Beans meist ein Kinderspiel, insbesondere dann, wenn man eine RAD-Umgebung wie den JBuilder verwendet. 2 Beachten Sie, dass sich die Verzeichnisstruktur von Websites ändern kann. Falls Sie eines der in diesem Abschnitt angegebenen Unterverzeichnisse nicht mehr finden sollten, beginnen Sie einfach mit dem Home-Verzeichnis.
29
KAPITEL
1
Bevor es losgeht
Editor. Aber auch ein einfacher Texteditor, der unformatierten ASCIIText (also einfach nur die Buchstaben ohne Formatangaben wie fett, kursiv etc.) abspeichert, ist vollkommen ausreichend (solche Texteditoren gehören heute zum Umfang jedes Betriebssystems, beispielsweise Notepad unter Windows 95/98/2000, der vi oder emacs unter Unix etc.) ✘ einen WWW-Browser, wie zum Beispiel der Netscape Navigator, der Internet Explorer von Microsoft (Letzterer gehört mittlerweile zum Standardumfang von Windows 95/98/2000, ist vor einiger Zeit aber wegen seiner Sicherheitsmängel ins Gerede gekommen) oder auch HotJava, ein Browser, der komplett in Java geschrieben ist. Wenn Sie noch keinen WWW-Browser Ihr Eigen nennen, können Sie sich auch mit dem Applet-Viewer Ihrer Entwicklungsumgebung begnügen. Den Netscape Navigator können Sie über http://home.netscape.com beziehen. Den HotJava-Browser finden Sie unter http://java.sun.com/products/ HotJava. Wenn Sie sich den Internet Explorer herunterladen wollen, gehen Sie zu http://www.microsoft.com. ✘ ein Java-Entwicklungstoolkit (beispielsweise das JDK von Sun, das unter http://www.javasoft.com als public domain zur Verfügung steht; Installationshinweise siehe Anhang A) oder den JBuilder von Borland (Inprise), den Sie über den Handel oder direkt von Borland beziehen können (www.borland.com). Damit sind Sie gerüstet und das Abenteuer kann beginnen!
30
Kapitel 2
Der erste Kontakt – von Applets und Applikationen 2 Der erste Kontakt – von Applets und Applikationen
Bestimmt sind Sie schon ganz gespannt und wollen nun endlich wissen, wie man mit Java Programme schreiben kann. Wir werden uns daher auch nicht mehr mit langen Vorreden aufhalten, sondern gleich mit zwei einfachen Beispielen beginnen – einer Java-Applikation und einem Java-Applet. Beide Programme werden nur aus wenigen Zeilen Quelltext bestehen, aber diese werden es in sich haben. Sie sollten darüber aber nicht erschrecken und Sie brauchen sich auch nicht darum zu sorgen, wie es mit der JavaProgrammierung weitergeht, wenn schon die einfachsten Programme so kompliziert und unverständlich sind. Das Problem, speziell für uns als Autoren, liegt darin, dass man in Java auch für die einfachsten Programme auf eine Reihe weit fortgeschrittener Konzepte vorgreifen muss. Versuchen wir einfach, aus der Not eine Tugend zu machen. Statt gleich alles bis ins Detail verstehen zu wollen, verschaffen wir uns erst einmal einen Überblick. Lernziel Was uns im Augenblick interessiert, ist die Unterscheidung zwischen Applikationen und Applets, wie beide Typen von Java-Programmen grundsätzlich aufgebaut sind und wie wir die Programme auf dem Computer zur Ausführung bringen.
31
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
Sie werden in diesem Kapitel ✘ den Unterschied zwischen Applets und Applikationen kennen lernen, ✘ Ihre erste Java-Applikation schreiben, ✘ Ihr erstes Java-Applet ausprobieren, ✘ den Umgang mit Compiler und Interpreter üben, ✘ eine simple HTML-Seite zum Aufruf von Applets aufsetzen. Voraussetzung Sie sollten Ihr Java-Entwicklungspaket korrekt installiert haben (siehe Anhang A), eine gewisse Vorfreude empfinden und sich gegen mögliche Frustrationen ob all der neuen Konzepte, die gleich auf Sie einströmen werden, wappnen.
2.1
Was unterscheidet Applets und Applikationen?
Es gibt zwei verschiedene Arten von Java-Programmen, nämlich Applets und Applikationen. Applets Applet bedeutet soviel wie »little application« und deutet auch schon an, was
darunter zu verstehen ist: Applets sind keine selbstständigen Programme, sondern brauchen immer eine bestimme Umgebung, in der sie existieren und ablaufen können. Diese Umgebung stellt in der Regel ein WWWBrowser zur Verfügung (der natürlich Java-fähig sein muss). Alle modernen Browser wie der Netscape Navigator, der Internet-Explorer, HotJava und andere bieten diese Unterstützung. Die eigentliche Aufgabe dieser Browser ist es, Sie durchs World Wide Web zu geleiten, HTML-Dokumente auszumachen und anzuzeigen sowie die grundlegenden Formen des Datenaustauschs im Internet zu unterstützen. Das Web selbst ist nichts anderes als die Gesamtheit der HTML-Dokumente im Internet, die untereinander über sogenannte Links in Verbindung stehen. Sie beginnen eine Reise durchs Web damit, dass Sie eine beliebige, in HTML geschriebene Webseite in Ihren Browser laden. Bestimmte Stichworte in dem Text des Dokuments sind besonders hervorgehoben – hinter Ihnen verbergen sich die Links zu weiteren HTML-Dokumenten. Wenn Sie ein solches Stichwort anklicken, ermittelt der Browser die Adresse, unter der das zugehörige Dokument zu finden ist, fordert es von dem entsprechenden Server an (der Server wird als Teil der Adresse angegeben) und zeigt es, wenn alles gut geht, auf Ihrem Bildschirm an.
32
Was unterscheidet Applets und Applikationen? Die Erstellung von HTML-Dokumenten ist nicht sehr schwierig, so dass praktisch jeder, der Zugang zu einem Server hat, auf dem er seine Webdokumente anbieten kann, am World Wide Web teilhaben und es erweitern kann. Bis hierher sind die Webdokumente allerdings recht statische Gebilde, die sich kaum von den typischen Textdokumenten der PC-Textverarbeitungsprogramme unterscheiden: elektronische Abbilder gedruckter Texte. Interessant wäre es, den Webseiten ein flexibles Aussehen zu verleihen (beispielsweise durch Einblendung des jeweils aktuellen Datums), den Leser einer Webseite mit dieser interagieren zu lassen (über Schaltflächen, Listen, sensitive Grafiken) und Daten vom Leser abzufragen (über Dialogfelder) und an den Besitzer der Website zurückzuliefern. Um dies möglich zu machen, gibt es zwei unterschiedliche Ansätze: die CGI1-Schnittstelle, die vor allem der Rückmeldung von Daten dient, und die Verwendung von Java-Applets. Die Unterstützung von Webseiten durch Programme kann serverseitig oder clientseitig erfolgen. Bei der serverseitigen Unterstützung werden die Programme auf der Server-Maschine ausgeführt (z.B. CGI-Skripte, ASP-Seiten). Als Ergebnis liefern die Programme meist eine Webseite an den Browser zurück. Bei der clientseitigen Unterstützung werden die Programme auf der Client-Maschine, also auf dem Computer des Websurfers, ausgeführt (Applets, JavaScript). Die Oberflächenelemente der Programme können in der Webseite angezeigt werden und mit dem Leser der Webseite interagieren. Wenn Sie als Anbieter eines Webdokuments dieses um ein Java-Applet bereichern wollen, programmieren Sie das Applet, nehmen in Ihr HTMLDokument einen Verweis auf das Applet auf und speichern HTML-Dokument und Applet zusammen auf Ihrem Server. Greift jetzt ein Internet-User mit seinem Webbrowser auf Ihr HTML-Dokument zu, lädt der Webbrowser zuerst das HTML-Dokument. Blättert der User zu der Stelle, an der das Applet eingeblendet werden soll, lädt der Webbrowser dieses automatisch von Ihrer Website und führt es aus. Für den Leser des Webdokuments erscheint das Applet wie ein integraler Bestandteil des Dokuments, so dass ihm unter Umständen gar nicht bewusst wird, dass hier im Hintergrund ein Programm abläuft. Soviel zu den Applets, mit denen wir uns im dritten Teil des Buches eingehender befassen werden. In den nachfolgenden Kapiteln werden wir uns allerdings erst einmal mit der allgemeinen Java-Programmierung und der Erstellung von Java-Applikationen beschäftigen.
1 Common Gate Interface
33
KAPITEL
2
Abb. 2.1: Kommunikation zwischen Server und Client für Java-unterstützte Webdokumente
Der erste Kontakt – von Applets und Applikationen
Request Client Server HTML + Applet
Ausführung des Applets
Applikationen Im Gegensatz zu Applets sind Applikationen vollwertige und eigenständige
Programme, die zur ihrer Ausführung keinen Browser und keine besondere Umgebung benötigen (bis auf den Java-Interpreter natürlich). Java-Applikationen stellen also das Pendant zu den üblichen C-, Pascal- oder Basic-Programmen dar.
2.2
Die erste Java-Applikation
In vielen Lehrbüchern über Programmiersprachen (insbesondere C) beginnt man mit dem Erstellen eines kleinen Programms, das die Meldung »Hello World« auf den Bildschirm ausgibt. Wir wollen uns dieser Tradition anschließen und eine Applikation erzeugen, die sich mit einem freudigen »Hallo Welt« meldet. Und so schaut das Programm aus: // Dies ist die erste Applikation // Datei CHalloWelt.java class CHalloWelt { public static void main (String args[]) { System.out.println("Hallo Welt!"); } }
34
Die erste Java-Applikation Applikationen erstellen und ausführen Um diese – und jede andere – Applikation auf Ihrem Computer zu erstellen und auszuführen, gehen Sie folgendermaßen vor: 1. Öffnen Sie einen Texteditor. Rufen Sie einen beliebigen Texteditor auf, mit dem Sie Ihren Quellcode als ASCII-Text abspeichern können. Unter Windows eignet sich beispielsweise der Notepad-Editor, den Sie über das Start-Menü mit START/PROGRAMME/ZUBEHÖR/EDITOR aufrufen können. Unter Linux können Sie den vi oder KEdit verwenden. (Integrierte Entwicklungsumgebungen wie der JBuilder von Borland oder Visual J++ von Microsoft verfügen hierfür über einen eingebauten Editor.) 2. Geben Sie den Java-Quelltext ein. Legen Sie in Ihrem Editor eine neue Datei an, tippen Sie obigen Quelltext ein und speichern Sie die Datei unter dem Namen CHalloWelt.java. Wichtig ist dabei, dass die Quelltextdatei exakt den gleichen Namen trägt wie die in dem Quelltext definierte Hauptklasse (hier also class CHalloWelt), wobei auch die Groß- und Kleinschreibung zu beachten ist. Weiterhin wichtig ist, dass die Datei die Dateiendung .java trägt! Bei Verwendung von Notepad gibt es manchmal Probleme, weil der Notepad-Editor die Dateiendung .txt anhängt (aus CHalloWelt.java wird dann CHalloWelt.java.txt). Wechseln Sie dann in den Windows Explorer, markieren Sie die Datei, rufen Sie den Befehl DATEI/UMBENENNEN auf und löschen Sie im Explorer-Fenster die unerwünschte .txt-Extension. (JBuilder und andere integrierte Entwicklungsumgebungen arbeiten meist mit Projekten. Lesen Sie in der Dokumentation zu Ihrem Compiler nach, wie Sie neue Projekte anlegen und Quelltexte eingeben.) 3. Kompilieren Sie den Quelltext. Falls Sie mit dem JDK und einem einfachen Texteditor arbeiten (und nicht mit einer Entwicklungsumgebung), müssen Sie ein Konsolenfenster öffnen, in diesem zum Verzeichnis Ihrer Java-Quelldatei wechseln und dort den Java-Compiler javac aufrufen: Prompt:> javac CHalloWelt.java . Im Gegensatz zu Linux-Anwendern sind viele Windows-Anwender heutzutage gar nicht mehr mit dem Umgang mit der Konsole vertraut. Unter Windows heißt die Konsole »MS-DOS-Eingabeaufforderung« und wird
35
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
über das Start-Menü aufgerufen (START/PROGRAMME/MS-DOS-EINGABEAUFFORDERUNG). In der Konsole müssen Sie nun mit Hilfe des cd-Befehls in das Verzeichnis wechseln, in dem Sie die Datei CHalloWelt.java abgespeichert haben. Nehmen wir an, es war das Verzeichnis c:\Java\Kap02. Dann tippen Sie hinter dem Prompt der Konsole den Befehl cd c:\Java\Kap021 ein und schicken ihn durch Drücken der Æ-Taste ab. Nun kommen wir zum eigentlichen Kompilieren, bei dem der Java-Quelltext (die Datei mit der Extension .java) an den Java-Compiler (javac) übergeben wird. Tippen Sie zum Kompilieren folgenden Befehl ein: javac CHalloWelt.java Schicken Sie den Befehl wiederum durch Drücken der Æ-Taste ab. Dieser Aufruf erzeugt eine ausführbare Bytecode-Datei mit der Extension .class – in unserem Beispiel also CHalloWelt.class. Sollten Sie beim Abschicken des Befehls eine Meldung in der Form »Befehl oder Dateiname nicht gefunden« erhalten, ist Ihr System nicht so eingerichtet, dass Sie die Java-Entwicklungsprogramme aus jedem beliebigen Verzeichnis aufrufen können. Sie müssen dann dem Programmnamen javac den vollständigen Pfad voranstellen, der zu dem Programm führt. Wenn Sie Java im Verzeichnis C:\jdk1.3 installiert haben, würde der Aufruf beispielsweise C:\jdk1.3\bin\javac lauten. Bequemer ist es, wenn Sie den Pfad zu den Java-Entwicklungsprogrammen in ihren Systempfad eintragen (siehe Anhang A). (In JBuilder und anderen integrierten Entwicklungsumgebungen rufen Sie den entsprechenden Befehl zur Kompilation Ihres Projekts auf.) 4. Lassen Sie die fertige Applikation ausführen. Dazu rufen Sie den Java-Interpreter (java) auf und übergeben diesem als Parameter den Namen Ihrer kompilierten Bytecode-Datei, aber ohne die Endung .class, d.h., in der MS-DOS-Eingabeaufforderung wird Folgendes eingegeben: Prompt:> java CHalloWelt (In JBuilder und anderen integrierten Entwicklungsumgebungen rufen Sie den entsprechenden Befehl zur Ausführung Ihres Projekts auf.)
1 Achtung: Falls Sie die Datei auf einer anderen Partition der Festplatte abgespeichert (z.B. D:) haben als die Windows-Standardpartition C:, dann müssen Sie vor dem cdBefehl erst die richtige Partition anwählen, z.B. durch d: und dann erst cd d:\Java\Kap02!
36
Die erste Java-Applikation
Abb. 2.2: Kompilation und Ausführung der Applikation CHalloWelt im Verzeichnis d:\Java\kap02
Der Quelltext der Applikation // Datei CHalloWelt.java // Dies ist die erste Applikation class CHalloWelt { public static void main (String args[]) { System.out.println("Hallo Welt!"); } }
Gehen wir nun den Quelltext des Programms durch. Dabei sollte es uns nicht darum gehen, alle syntaktischen Elemente von Java auf einmal verstehen zu wollen. Wichtiger ist es zu verstehen, wie das Grundgerüst einer Java-Applikation aussieht und woran der Compiler eine Java-Applikation erkennt. Im nachfolgenden Abschnitt werden wir uns dann mit dem Grundgerüst von Java-Applets beschäftigen. // Dies ist die erste Applikation
Die erste Zeile des Quelltextes ist ein Kommentar. Eingeleitet wird der Kommentar durch die doppelten Schrägstriche //, die dem Compiler mitteilen, dass der Rest der Zeile nur als Gedächtnisstütze für den Programmierer gedacht ist und bei der Übersetzung ignoriert werden kann. Unter dem Kommentar wird es dann schon recht kryptisch. Springen wir gleich in die achte Zeile, in der wir den Text wiedererkennen, den die Applikation ausgeben soll: System.out.println("Hallo Welt!");
37
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
Damit der Compiler erkennt, dass es sich bei »Hallo Welt!« um einen einfachen Text und nicht etwa um einen Programmierbefehl handelt, wird der Text in Anführungszeichen gesetzt. Woher aber weiß das Programm, was es mit diesem Text zu tun hat? Dazu bedarf es spezieller Anweisungen, die den Text auf Ihrem Bildschirm ausgeben. Diese Anweisungen brauchen Sie nicht selbst zu implementieren. Dazu gibt es schon eine vordefinierte Funktion, die – wie Sie sich vielleicht schon denken können – println() heißt und zum Standardumfang von Java gehört. Um einen Text auszugeben, brauchen Sie diesen also lediglich der Funktion println() zu übergeben, wozu Sie den Text zwischen die Klammern der Funktion schreiben. Wir sind damit auf ein ganz zentrales Konzept zur Modularisierung von Programmcode gestoßen. Modularisierung durch Funktionen Grundsätzlich bestehen Programme aus Anweisungen – einzelnen Befehle, die der Computer nacheinander ausführen soll (in den nächsten Kapiteln werden wir viele Beispiele für solche Anweisungen sehen). Man könnte nun ein Programm so aufsetzen, dass man die Befehle einfach in der Reihenfolge, in der sie abgearbeitet werden, untereinander aufschreibt (wie es früher in Basic der Fall war). Nun kehren aber bestimmte Aufgaben bei der Programmierung immer wieder, so zum Beispiel die Ausgabe von Text auf einen Bildschirm, die Berechnung von Sinus-Werten, das Öffnen einer Datei und so weiter und so fort. Damit man nun nicht fortwährend das Rad neu erfinden muss, fasst man die Anweisungen zur Lösung eines solches Problems in einer Funktion zusammen. Fortan muss man zur Ausgabe eines Textes nicht mehr die ganzen Anweisungen zur Textausgabe niederzuschreiben, sondern braucht nur noch die entsprechende Funktion aufzurufen. Und da man den auszugebenden Text der Funktion erst beim Aufruf übergibt, kann die Funktion sogar beliebigen Text ausgeben (wie dies im Einzelnen funktioniert, werden wir in Kapitel 5.2 klären). Funktionen kapseln Quellcode und dienen der Lösung genau definierter Teilprobleme. Glücklicherweise sind für die wichtigsten Probleme bereits entsprechende Funktionen vorhanden – sie gehören zur Standardausstattung von Java (wie zum Beispiel println() für die Textausgabe). Die Frage ist nur: Wo findet man diese Funktionen? Oder um die Frage zu verallgemeinern: Wie wird der Quellcode in Java-Programmen organisiert?
38
Die erste Java-Applikation Funktionen, Klassen, Packages In Java-Programmen dürfen Anweisungen (also der eigentliche Code, der vom Computer ausgeführt werden soll) nicht einfach irgendwo im Quelltext herumstehen. Vielmehr ist es so, dass Anweisungen nur innerhalb von Funktionsdefinitionen erlaubt sind. Aber auch Funktionsdefinitionen dürfen nicht an beliebiger Stelle stehen. Funktionen sind in Java nur als Elemente von Klassen erlaubt, und in Übereinstimmung mit der objektorientierten Terminologie sollten wir daher nicht von Funktionen, sondern von Elementfunktionen oder Methoden sprechen. Klassen dürfen dagegen an beliebiger Stelle im Quelltext definiert werden. Wir nehmen es in diesem Buch mit der Unterscheidung zwischen »Funktionen« und »Methoden« nicht so ganz genau und verwenden beide Begriffe synonym. In C++ wäre eine solche Nachlässigkeit nicht statthaft, da man zwischen reinen Funktionen, die außerhalb jeder Klasse definiert sind, und Elementfunktionen oder Methoden, die innerhalb einer Klasse definiert sind, unterscheiden muss. Da es in Java aber wie gesagt keine Funktionen außerhalb von Klassen gibt, sollte klar sein, dass es sich, auch wenn wir von Funktionen sprechen, letztlich um Methoden handelt.
Um es also noch einmal in aller Deutlichkeit zu sagen: Java-Programme bestehen praktisch nur aus Klassendefinitionen. Diese Klassen definieren Variablen und Methoden und nur in diesen Methoden stehen die eigentlichen Anweisungen, die das Programm auszuführen hat. Betrachten wir jetzt noch einmal den Aufbau unseres ersten Programms: 1: 2: 3: 4: 5: 6: 7:
class CHalloWelt { public static void main (String args[]) { System.out.println("Hallo Welt!"); } }
In Zeile 1 definieren wir eine eigene Klasse namens CHalloWelt. Beginn Analyse und Ende der Klassendefinition werden durch das geschweifte Klammernpaar gekennzeichnet. Innerhalb der Klasse CHalloWelt definieren wir die Methode main() (Zeile 3 bis 6). Die Anweisungen, die zu der Methode ge-
39
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
hören, werden wiederum in geschweifte Klammern gefasst. In unserem Fall ist die einzige Anweisung der Aufruf der Methode println(). Ähnlich wie man Funktionen, sorry... Methoden, in Klassen organisiert (wobei alle Methoden einer Klasse üblicherweise einem gemeinsamen Aufgabengebiet angehören), kann man Klassen in sogenannten Packages zusammenfassen. Mehr dazu erfahren Sie in Kapitel 3.6 und am Ende von Abschnitt 4.2. Eine Übersicht über die Standard-Packages von Java und deren Klassen finden Sie im Anhang dieses Buches. Der eigentliche Code, die Anweisungen des Programms, ist also verteilt auf die Methoden des Programms. Und da eine Methode einen andere Methode aufrufen kann, ist es kein Problem, den Programmablauf zu steuern und von Methode zu Methode weiterzuführen. Mit welcher Methode wird nun aber begonnen? Welche Methode wird aufgerufen, wenn wir das Programm starten? Der Programmstart Jede Java-Applikation beginnt mit einer main()-Funktion, die folglich in jeder Java-Applikation definiert werden muss. Genau dies geschieht in unserem kleinen Beispielprogramm. class CHalloWelt { public static void main (String args[]) { System.out.println("Hallo Welt!"); } }
Da Methoden nur innerhalb von Klassen definiert werden dürfen, müssen wir zuerst die Alibi-Klasse CHalloWelt definieren. Diese enthält dann die Definition unserer main()-Funktion. Und das war schon das ganze Programm. Das Grundgerüst einer Java-Applikation besteht aus einer Hauptklasse, in der die Methode main() definiert wird. Die Signatur dieser Funktion ist fest vorgegeben: public static void main (String args[])
Die Ausführung der Applikation beginnt mit der ersten Anweisung im Funktionskörper von main().
40
Das erste Java-Applet
2.3
Das erste Java-Applet
Als Nächstes implementieren wir das Programm aus Abschnitt 2.2 als Applet. Da Applets wie gesagt nur zur Unterstützung von WWW-Dokumenten dienen, ändern wir die Grußformel in »Hallo World Wide Web!«. Und so schaut das Applet aus: // Datei CHalloWWW.java // Dies ist das erste Applet public class CHalloWWW extends java.applet.Applet { public void paint(java.awt.Graphics gc) { gc.drawString("Hallo World Wide Web!",100,100); } }
Applets erstellen und ausführen Um dieses – und jedes andere – Applet auf Ihrem Computer zu erstellen und auszuführen, gehen Sie anfangs genauso wie bei der Erstellung einer Applikation vor: 1. Rufen Sie einen beliebigen Texteditor auf, mit dem Sie Ihren Quellcode als ASCII-Text abspeichern können (also beispielsweise wieder den Notepad-Editor von Windows). 2. Öffnen Sie ein neue Datei, tippen Sie Ihren Quelltext ein und speichern Sie die Datei unter einem passenden Namen und mit der Dateiextension .java. Speichern als CHalloWWWW.java
3. Kompilieren Sie Ihre Applikation. Das fertige Programm erhält die Extension .class. Prompt:> javac CHalloWWWW.java
Bevor wir das Applet ausführen und austesten können, müssen wir noch eine zugehörige HTML-Seite aufsetzen, die das Applet aufruft. 4. Legen Sie mit Ihrem Editor eine neue Textdatei an, in die Sie folgenden Code eingeben:
41
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
5. Speichern Sie die Datei unter einem beliebigen Namen aber mit der Extension .html (z.B. webseite.html) in dem gleichen Verzeichnis wie das Java-Applet. Speichern als Webseite.html
6. Laden Sie die HTML-Datei in Ihren Browser oder AppletViewer. Prompt:> appletviewer Webseite.html
Mehr zur Erstellung von HTML-Dokumenten finden Sie direkt im nachfolgenden Abschnitt 2.4 und im Anhang E.
Abb. 2.3: Unser erstes Applet im AppletViewer
Der Applet-Quelltext Schauen wir uns nun den Quelltext an. Unser Wissen über das Grundgerüst der Java-Applikationen kommt uns dabei bereits zugute, so dass wir gleich etwa tiefer in die Materie einsteigen können. Lassen Sie sich aber nicht verdrießen, wenn Sie in den Details nicht immer folgen können. Wichtig ist der grobe Überblick. Kommentare Wie die Java-Applikation beginnen wir auch das Java-Applet mit einem Kommentar. Alle Zeichen einer Zeile, die nach dem // erscheinen, werden von dem Java-Compiler als Kommentar angesehen und ignoriert, da sie nur für den Programmierer als Gedächtnisstütze dienen sollen. Bei kleinen Pro-
42
Das erste Java-Applet grammen sind Kommentare noch nicht so wichtig, aber bei etwas größeren Programmierprojekten sind sie unverzichtbar, damit der Programmierer oder andere, die den Code lesen wollen, nachvollziehen können, was das Programm an der jeweiligen Stelle macht. Mehrzeilige Kommentare kann man auch mit Hilfe der »Klammern« /* und */ einfügen: /* Dies ist alles Kommentar */
Die Hauptklasse des Applets Die nächsten Zeilen des Beispiels sehen schon ziemlich wild aus und wir werden uns langsam von außen nach innen vorarbeiten. public class CHalloWWW extends java.applet.Applet { }
Hier wird eine Klasse mit dem Namen CHalloWWW definiert. Klassen sind wie gesagt die Grundbausteine, aus denen ein Java-Programm zusammengesetzt ist. Die Definition einer Klasse beginnt mit dem Schlüsselwort class, gefolgt von dem Namen der neuen Klasse (in diesem Beispiel CHalloWWW). Dem Modifizierer public, der in obigem Beispiel noch vor dem Schlüsselwort class steht, werden wir uns später zuwenden. Sie können sich aber schon merken, dass die Hauptklasse eines Applets unbedingt mit dem Modifizierer public deklariert werden muss. Es gibt schon viele fertige Klassen, die von Java bereitgestellt werden. Ein Beispiel ist die Klasse Applet, die mit vollem Namen java.applet.Applet heißt und wie jede Klasse sogenannte Variablen und Methoden enthält, die ihre Eigenschaften und Fähigkeiten bestimmen. Wenn man als Programmierer nun eine maßgeschneiderte Klasse braucht, hat man zwei Möglichkeiten. Entweder definiert man eine ganz neue Klasse oder man baut auf einer schon vorhandenen Klasse und deren Fähigkeiten auf. Im letzteren Fall verwendet man das Schlüsselwort extends, das dem Compiler anzeigt, dass die neue Klasse (die abgeleitete Klasse) alle Elemente der spezifizierten vorhandenen Klasse (der Basisklasse) erbt. In der weiteren Definition der abgeleiteten Klasse wird diese um zusätzliche Variablen und Methoden erweitert.
43
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
Mit diesen Informationen können wir die obigen Zeilen nun besser verstehen: Es wird eine eigene Klasse mit dem Namen CHalloWWW definiert, die von der Basisklasse java.applet.Applet abgeleitet wird. Danach folgt ein Klammerpaar, das den Anfang und das Ende der Klassendefinition markiert. Die Klassendefinition Im Inneren der Klammern werden die Eigenschaften und Fähigkeiten der Klasse festgelegt, also was diese Klasse machen und können soll. Wie schon erwähnt, unterscheidet man dabei Variablen und Methoden. Variablen präsentieren die Daten, mit denen Ihr Programm arbeitet. Jeder Variable wird im Arbeitsspeicher des Computers ein bestimmter Speicherplatz zugeordnet, wo der Wert der Variablen zwischengespeichert wird. Variablen speichern Zahlen, Zeichen, boolesche Werte oder auch Instanzen Ihrer Klassen. Welche Art von Werten eine Variable aufnehmen kann, wird durch ihren Datentyp festgelegt. Methoden hingegen sind Unterprogramme, also eine bestimmte Abfolge an Anweisungen, die irgendeine Teilaufgabe lösen. Man kann ihnen beim Aufruf Werte (Parameter) mitgeben und die Methode kann auch einen Wert als Resultat zurückgeben. Aus diesem Grund nennt man sie auch häufig Elementfunktionen, da wie bei einer mathematischen Funktion (y=f(x)) ein Parameter (das x) mitgegeben und ein Resultat (y) zurückgeliefert wird. Die paint()-Methode Schauen wir nun wieder das Beispiel an. Im Innern der Klammern finden wir die Definition einer solchen Methode: public void paint(java.awt.Graphics gc) { gc.drawString("Hallo World Wide Web!",100,100); }
Es wird eine Methode paint() definiert, die als Parameter eine Variable namens gc erwartet. Die Angabe java.awt.Graphics bezeichnet dabei den Datentyp von gc. Auch der Datentyp des Rückgabewerts der Methode wird schon festgelegt: durch das Schlüsselwort void vor dem Methodennamen. Es handelt sich dabei um einen ganz speziellen Typ, der nichts anderes besagt, als dass gar kein Wert zurückgeliefert wird. (Das public ignorieren wir noch, damit es nicht zu kompliziert wird.)
44
Das erste Java-Applet Die Anweisungen einer Methode müssen in einem Anweisungsblock stehen, der durch ein Klammernpaar { } begrenzt wird. Im obigen Beispiel ist die einzige Anweisung der Aufruf der Methode gc.drawString(), die als Parameter die Zeichenkette "Hallo World Wide Web" erhält sowie die x- und y-Koordinaten der Stelle, wo die Meldung ausgegeben werden soll. Ganz unscheinbar aber sehr wichtig ist das Semikolon am Ende des Methodenaufrufs. Jede Anweisung muss in Java (wie in C und C++) mit einem Semikolon abgeschlossen werden. Der Compiler benötigt es für seinen Übersetzungsvorgang von Quellcode nach Bytecode. Testen Sie dies ruhig einmal: Löschen Sie das Semikolon und kompilieren Sie das Beispiel erneut. Sie werden eine Fehlermeldung erhalten. Das Semikolon zu vergessen, ist ein sehr häufiger Fehler beim Eintippen von Quelltext! Vordefinierte Methoden Sie werden sich vielleicht fragen, wo die Methode gc.drawString() auf einmal herkommt, da sie ansonsten nirgendwo auftaucht und somit auch die Definition fehlt, was sie überhaupt machen soll. Die Antwort liefert uns die Parametervariable gc. Ihr Datentyp ist java.awt.Graphics, der eine der zahlreichen fertigen Klassen in Java bezeichnet. Somit handelt es sich bei gc um eine sogenannte Instanz der Klasse java.awt.Graphics – ähnlich wie Sie eine Instanz der Klasse Mensch sind –, die daher über alle Eigenschaften dieser Klasse verfügt, insbesondere die Methode drawString(), auf die über den sogenannten Punkt-Operator (.) zugegriffen werden kann. Automatisch aufgerufene Methoden Verbleiben wir noch einen Moment bei der paint()-Methode. Sie gehört zu einer speziellen Gruppe von Methoden, die zu bestimmten Zeiten im Lebenslauf eines Applets automatisch aufgerufen werden. Speziell die Methode paint() wird immer dann aufgerufen und ausgeführt, wenn das Applet gezeichnet werden muss. Dies ist beispielsweise dann der Fall, wenn ein Browser auf einer Webseite den Verweis auf Ihr Applet findet. Das Applet wird geladen und die Methode paint(), die festlegt, was das Applet im Webdokument anzeigt, wird aufgerufen. Die weiteren Methoden, die zu dieser Gruppe der automatisch aufgerufenen Applet-Methoden gehören, sind: init(), start(), stop() und destroy(). Damit haben wir die Erläuterung des ersten Applets schon hinter uns. Wenn Ihnen noch nicht alles klar geworden ist, brauchen Sie nicht weiter beunruhigt zu sein. Dieses Kapitel dient nur zum Reinschnuppern, damit Sie einen ersten Eindruck von Java erhalten und schon einigen Grundideen begegnen.
45
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
Das Grundgerüst eines Java-Applets besteht aus einer Hauptklasse, die von der vordefinierten Klasse java.applet.Applet abgeleitet und als public deklariert wird. Die Deklaration der Hauptklasse enthält zumindest folgende Angaben: public class KLASSENNAME extends java.applet.Applet
Statt der Eintrittsfunktion main() definieren Sie in einem Applet eine oder mehrere der folgenden Methoden, die automatisch aufgerufen werden: init(), start(), paint(), stop() und destroy().
2.4
Ein einfaches Webdokument erstellen
Da ein Applet wie schon oben erwähnt nur in der Umgebung eines Browsers »leben« kann, müssen wir noch ein einfaches Webdokument erzeugen, mit dem unser erstes Applet geladen werden kann: Hallo Applet HTML ruft Applet. Bitte antworten.
Um diese Webseite aufzusetzen und zusammen mit Ihrem Applet auszuprobieren, gehen Sie wie folgt vor: 1. Öffnen Sie eine neue Textdatei, tippen Sie den obigen Text ein und speichern Sie die Datei mit der Extension .html. 2. Rufen Sie Ihren Webbrowser auf und laden Sie das HTML-Dokument.
46
Ein einfaches Webdokument erstellen
Damit das Applet aus obigem Beispiel korrekt innerhalb des HTML-Dokuments ausgeführt werden kann, müssen HTML-Dokument (.html) und Applet (.class) in einem Verzeichnis stehen. Ansonsten bedarf es eines speziellen Parameters zum applet-Tag, der den Pfad zum Applet angibt (siehe Anhang E).
Abb. 2.4: Das HTMLDokument mit Applet
Der HTML-Quelltext Werfen wir zum Abschluss noch einen kurzen Blick auf den HTML-Code unseres ersten Webdokuments (eine Übersicht der wichtigsten HTMLBefehle finden Sie im Anhang dieses Buches). HTML-Code ist im Vergleich zu Java die Einfachheit selbst. Der Quelltext eines HTML-Dokuments besteht hauptsächlich aus dem anzuzeigenden Text, plus einer Reihe von HTML-Befehlen, die beschreiben, wie der Text in Bereiche aufzuteilen ist, wie der Browser die einzelnen Bereiche formatieren und anzeigen soll und welche weiteren Elemente (beispielsweise Grafiken und Applets) geladen werden sollen. HTML-Tags und Bereiche Diese HTML-Befehle, die als Tags bezeichnet werden, treten üblicherweise paarweise auf. Der erste Befehl kennzeichnet den Anfang des Bereichs, auf den der HMTL-Befehl anzuwenden ist (beispielsweise ). Der zweite Befehl des Paares wird durch einen Schrägstrich gekennzeichnet und definiert das Ende des Bereichs, auf den der HMTL-Befehl anzuwenden ist (beispielsweise ).
47
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
Das Grundgerüst eines HTML-Dokuments Das erste Tag im Quelltext sollte stets lauten, um dem Browser mitzuteilen, dass nun HTML-Code folgt (das Ende des Dokuments wird durch angezeigt). Danach beginnt die Kopfzeile, auch Header genannt. In diesem Bereich, der durch die Tags und definiert wird, können Sie einen Titel für Ihr HTML-Dokument spezifizieren sowie verschiedene interne Informationen für den Browser übergeben (Letztere werden nicht angezeigt). Der Text des Titels wird von dem Tag-Paar und umschlossen. Unter dem Header folgt das eigentliche Dokument, der Körper ( und ). Hier stehen Ihnen eine Reihe von Möglichkeiten zur Verfügung. In unserem Beispiel beschränken wir uns darauf, einen einfachen Text auszugeben (»HTML ruft Applet. Bitte antworten.«) und unser Applet aufzurufen. Einfachen Text können Sie überall innerhalb der Tags und eintippen. Für spezielle Elemente wie Menüs, Tabellen oder auch Applets müssen Sie dagegen die passenden Tags verwenden (siehe Anhang). Die meisten Browser sind in Bezug auf die Verwendung der Tags , und recht großzügig. Wundern Sie sich also nicht, wenn Sie auf HTML-Dokumente treffen, in denen Sie diese strenge Dreigliederung nicht wiederfinden können oder in denen die zugehörigen abschließenden Tags fehlen. Applets aufrufen
Die Tags und bezeichnen den Anfang und das Ende des Applet-Aufrufs. Neu für uns ist die Möglichkeit, einem HTML-Tag ein Argument zu übergeben. Im Falle des -Tags sind es gleich drei Argumente: code
48
Dem Parameter code wird der Name des kompilierten JavaApplets übergeben.
Test
width
Der Wert für width gibt an, wieviele Pixel die Ausgabe des Applets im HTML-Dokument in der Breite einnehmen soll.
height
Der Wert für height gibt an, wieviele Pixel die Ausgabe des Applets im HTML-Dokument in der Höhe einnehmen soll.
Damit wäre der Einstieg in die Java-Programmierung geschafft. In den nächsten Kapiteln wird die Behandlung der syntaktischen Elemente von Java im Vordergrund stehen. Sie werden lernen, aus welchen Elementen Java-Programme aufgebaut sind und wie diese Elemente korrekt und sinnvoll eingesetzt werden. Danach sollten Sie in der Lage sein, beliebige JavaProgramme lesen und verstehen zu können und eigene Java-Programme zur Lösung von Problemen aufsetzen zu können.
2.5
Test
1. Wie hängen Quelltext, Bytecode und Maschinencode voneinander ab? 2. Was ist der Unterschied zwischen einem Applet und einer Applikation? Versuchen Sie sich an einige Punkte zu erinnern und füllen Sie den folgenden Multiple-Choice-Test aus: − Applets werden nur zusammen mit HTML-Dokumenten verwendet − Applets sind eigenständige Programme − Die Ausführung von Applikationen beginnt mit der main()-Methode − Die Ausführung von Applets beginnt mit der main()-Methode − Applikationen können nicht im Internet ausgeführt werden − Ein Webbrowser kann eine Java-Applikation sein und Java-Applets
ausführen 3. Wozu braucht man einen Java-Compiler, wozu einen Java-Interpreter? 4. Für C++-Programmierer: Wo ist der Linker versteckt? 5. Schreiben Sie eine eigene Applikation, die folgenden Text ausgibt: "Hallo Welt. Hier ist ". 6. Schreiben Sie ein eigenes Applet, das Sie als Leser einer HTML-Seite begrüßt. 7. Speichern Sie den Quelltext Ihres Applets unter einem Namen, der nicht wie der Name Ihrer Hauptklasse lautet. Versuchen Sie das Applet zu kompilieren (speichern Sie danach den Quelltext wieder unter dem Namen der Hauptklasse).
49
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
8. Löschen Sie in Ihrem Quelltext das Schlüsselwort public aus der Klassendeklaration. Versuchen Sie den Quelltext zu kompilieren (machen Sie auch diese Änderung wieder rückgängig). 9. Sofern Sie auf Ihrem System über eine eigene Homepage verfügen, kopieren Sie diese und laden Sie die Homepage in Ihren Texteditor. Fügen Sie in den HTML-Code der Homepage den Aufruf Ihres Applets ein und testen Sie die neue Homepage in Ihrem Browser aus.
2.6
Lösungen
1. Quelltext wird in Java-Syntax aufgesetzt und ist für den Computer unverständlich. Der Quelltext muss daher vom Compiler übersetzt werden. Der Java-Compiler erzeugt aus dem Quelltext binären Bytecode, der noch prozessorunspezifisch und daher gut portabel ist. Erst der JavaInterpreter erzeugt aus dem Bytecode prozessorspezifischen Maschinencode, der auf dem Computer ausgeführt werden kann. 2. Folgende Aussagen treffen zu: − Applets werden nur zusammen mit HTML-Dokumenten verwendet. − Die Ausführung von Applikationen beginnt mit der main()-Methode. − Ein Webbrowser kann eine Java-Applikation sein und Java-Applets
ausführen. 3. Der Java-Compiler übersetzt Ihren Quelltext in prozessorunspezifischen Bytecode. Dieser kann mit Hilfe eines passenden Java-Interpreters auf einem Computer ausgeführt werden. Den Compiler benötigt man also für die Erstellung, den Interpreter für die Ausführung von Java-Programmen. 4. Im Interpreter. 5. Der Quelltext zur Lösung dieser Aufgabe könnte beispielsweise folgendermaßen aussehen: class CAnmeldung { public static void main (String args[]) { System.out.println("Hallo Welt. Hier ist ??? "); } }
50
Zusammenfassung 6. Der Quelltext zur Lösung dieser Aufgabe könnte beispielsweise folgendermaßen aussehen: public class CGruss extends java.applet.Applet { public void paint(java.awt.Graphics gc) { gc.drawString("Willkommen zu meiner Homepage", 100,100); } }
7. Wenn Sie einen Java-Quelltext (.java) unter einem anderen als dem Namen der im Quelltext definierten Hauptklasse speichern, ernten Sie bei der Kompilation eine Fehlermeldung. Der javac-Compiler des JDK weist Sie beispielsweise darauf hin, dass in einer .java-Datei eine Hauptklasse gleichen Namens definiert sein muss. 8. Der Compiler erkennt die Hauptklasse eines Java-Quelltextes an der public-Deklaration der Klasse. Ist keine Klasse in dem Quelltext als public deklariert, wird keine .class-Datei angelegt. 9. Achten Sie darauf, dass die Applet-Klasse (.class) in dem gleichen Verzeichnis wie das HTML-Dokument Ihrer Homepage steht.
2.7
Zusammenfassung
✘ Java-Applikationen sind eigenständige Programme, die von Betriebssystemebene aus aufgerufen werden. ✘ Java-Applets sind Hilfsprogramme, die zur Unterstützung von Webseiten programmiert werden. Java-Applets werden automatisch von Seiten eines Webbrowsers aufgerufen, wenn dieser in einem HTML-Dokument auf einen Applet-Verweis trifft. ✘ Das Grundgerüst einer Java-Applikation besteht aus einer Hauptklasse, in der die Methode public static void main (String args[]) definiert wird. Die Ausführung einer Java-Applikation beginnt mit der ersten Anweisung in der main()-Funktion. ✘ Das Grundgerüst eines Java-Applets besteht aus einer Hauptklasse, die als public class KLASSENNAME extends java.applet.Applet deklariert wird. Die Ausführung eines Java-Applets beginnt mit dem Aufruf der Funktionen init(), start() und paint().
51
KAPITEL
2
Der erste Kontakt – von Applets und Applikationen
✘ Das Grundgerüst eines HTML-Dokuments besteht aus den Abschnitten und . ✘ Innerhalb des -Bereichs eines HTML-Dokuments können JavaApplets aufgerufen werden. Hierzu verwendet man das Tag , dem als Argumente der Name der .class-Datei des Applets und die Breite und Höhe der Anzeige des Applets im Webdokument übergeben werden.
52
Kapitel 3
Von Daten, Operatoren und Objekten 3 Von Daten, Operatoren und Objekten
Anscheinend haben Sie dieses Buch nach dem vorangehenden Kapitel doch nicht in den Altpapiercontainer geworfen, und das ist auch gut so. Denn der schwierigste, weil verwirrendste Teil liegt bereits hinter uns! Der erste Kontakt mit einer Programmiersprache ist nicht gerade leicht: Und wenn dies schon für Sprachen wie Pascal und C gilt, so ist es erst recht wahr für Java mit seinem konsequent objektorientierten Aufbau. Viele unbekannte Konzepte, seltsame Schreibweisen und Begriffe prasseln da auf den Anfänger ein und die nagende Frage taucht auf: Soll ich mir das antun ? »Der Zweifel ist’s, der Gutes böse macht!« (Goethe, Iphigenie auf Tauris) Halten Sie also noch ein bißchen durch, ab diesem Kapitel wird alles leichter. Fortan werden wir systematisch an die Sache herangehen, wir werden die aufregende Welt der Java-Programmierung Stück um Stück für uns erobern und wir werden unserer eigenen Kreativität als Programmierer Tür und Tor öffnen. Lernziel Letztendlich dient jedes Programm der Verarbeitung von Daten. In diesem Kapitel erfahren Sie, wie Daten in Programmen durch Variablen und Konstanten repräsentiert werden und wie Sie Daten manipulieren können. Schließlich werfen wir noch einen ersten Blick auf die Bausteine der objektorientierten Programmierung, die Klassen.
53
KAPITEL
3
Von Daten, Operatoren und Objekten
Sie lernen in diesem Kapitel, ✘ was Variablen sind, ✘ warum jede Variable einem Datentyp angehört, ✘ wie man Variablen benutzt, ✘ wie man Operatoren auf Variablen anwendet, ✘ was Variablen von Konstanten unterscheidet, ✘ wie Datentypen umgewandelt werden, ✘ was objektorientierte Programmierung ausmacht, ✘ was Klassen sind, ✘ wie man mit Klassen programmiert, ✘ was Arrays sind, ✘ wie man die von Java vordefinierten Klassen verwendet. Folgende Schlüsselwörter und Elemente lernen Sie kennen Variablen, Konstanten, Datentypen (int, short, long, float, double, bool, char, class), Instanzbildung (new). Voraussetzung Wenn Sie wissen, wie das Grundgerüst einer Java-Applikation aussieht (siehe Kapitel 2.2) und wie Sie eine Java-Applikation in Ihrer Entwicklungsumgebung erstellen und ausführen (siehe Anhang B), dann sind Sie bereit für diese Lektion.
3.1
Variablen und Anweisungen
Die Aufgabe eines jeden Computerprogramms ist die Verarbeitung von irgendwelchen Informationen, die im Computerjargon meist Daten genannt werden. Das können Zahlen sein, aber auch Buchstaben, ganze Texte oder Bilder und Zeichnungen. Dem Rechner ist diese Unterscheidung gleich, da er letztlich alle Daten in Form von endlosen Zahlenkolonnen in Binärdarstellung (nur Nullen und Einsen) verarbeitet. Zahlensysteme Erinnern Sie sich noch, als in der Schule die verschiedenen Zahlensysteme
durchgenommen wurden: das uns so vertraute aus Indien stammende Zehnersystem, das babylonische Sexagesimalsystem und das künstlich anmutende Dual- oder Binärsystem? Ich für meinen Teil fand es ebenso interes-
54
Variablen und Anweisungen sant zu erfahren, dass die Einteilung unserer Stunden in 60 statt 100 Minuten auf die Babylonier zurückgeht, wie es mich langweilte, Zahlen ins Dualsystem umzurechnen und als Folge von Nullen und Einsen darzustellen. Wer ist denn so dumm, freiwillig mit Binärzahlen zu rechnen? Nun, ich wünschte, meine Lehrer hätten mich gewarnt, aber vermutlich wussten die Lehrer damals selbst noch nicht, was man alles mit Binärzahlen anfangen kann (vielleicht haben die Lehrer uns ja auch gewarnt und wir haben es nur verschlafen). Jedenfalls rechnen Computer nur im Binärsystem: ✘ zum einem weil die beiden einzigen möglichen Werte 0 und 1 sich gut mit elektronischen Signalen darstellen lassen (Strom an, Strom aus), ✘ zum anderen weil es sich im Binärsystem sehr leicht rechnen lässt, vorausgesetzt man stößt sich nicht an der kryptischen Darstellung der Zahlen. Damit wären wir wieder beim eigentlichen Thema. Für den Computer sind also sämtliche Daten (nicht nur die Zahlen) Folgen von Nullen und Einsen, weil er diese am schnellsten und einfachsten verarbeiten kann. Wie diese Daten und die Ergebnisse seiner Berechnungen zu interpretieren sind, ist dabei nicht sein Problem – es ist unser Problem. Datentypen machen das Leben leichter Stellen Sie sich vor, wir schreiben das Jahr 1960 und Sie sind stolzer Besitzer einer Rechenmaschine, die Zahlen und Text verarbeiten kann. Beides allerdings in Binärformat. Um Ihre Freunde zu beeindrucken, lassen Sie den »Computer« eine kleine Subtraktion berechnen, sagen wir: 8754 – 398 = ?
Zuerst rechnen Sie die beiden Zahlen durch fortgesetzte Division durch 2 ins Binärsystem um (wobei die nicht teilbaren Reste der aufeinander folgenden Divisionen, von rechts nach links geschrieben, die gewünschte Binärzahl ergeben). 10001000110010 – 110001110 = ?
Die Binärzahlen stanzen Sie sodann als Lochkarte und lassen diese von Ihrem Computer einlesen. Dann drücken Sie noch die Taste für Subtraktion und ohne Verzögerung erscheint das korrekte Ergebnis: 10000010100100
Zweifelsohne werden Ihre Freunde von dieser Maschine äußerst beeindruckt sein und ich selbst wünschte, ich hätte im Mathematik-Unterricht eine derartige praktische Hilfe gehabt. Trotzdem lässt sich nicht leugnen, dass die Interpretation der Binärzahlen etwas unhandlich ist, und zwar erst recht,
55
KAPITEL
3
Von Daten, Operatoren und Objekten
wenn man neben einfachen ganzen Zahlen auch Fließkommazahlen, Texte und Bitmaps in Binärformat speichert. Für die Anwender von Computern ist dies natürlich nicht zumutbar und die Computer-Revolution – die vierte der großen Revolutionen (nach der Glorious Revolution, England 1688, der französischen Revolution von 1789 und der Oktoberrevolution, 1917 in Rußland) – hätte nicht stattgefunden, hätte man nicht einen Ausweg gefunden. Dieser bestand nun einfach darin, es der Software – dem laufenden Programm – zu überlassen, die vom Anwender eingegebenen Daten (seien es Zahlen, Text, Bitmaps etc.) in Binärformat umzuwandeln und umgekehrt die auszugebenden Daten wieder vom Binärformat in eine leicht lesbare Form zu verwandeln. »Gemeinheit«, höre ich Sie aufbegehren, »da wurde das Problem ja nur vom Anwender auf den Programmierer abgewälzt.« Ganz so schlimm ist es nicht. Der Java-Compiler nimmt uns hier das Gröbste ab. Alles, was wir zu tun haben, ist, dem Compiler anzugeben, mit welchen Daten wir arbeiten möchten und welchem Datentyp diese Daten angehören (sprich, ob es sich um Zahlen, Text oder Sonstiges handelt). Schauen wir uns gleich mal ein Beispiel an: public class CErstesBeispiel { public static void main(String[] args) { int erste_Zahl; int zweite_Zahl; int ergebnis; erste_Zahl = 8754; zweite_Zahl = 398; System.out.println("1. Zahl System.out.println("2. Zahl }
= " + erste_Zahl); = " + zweite_Zahl);
}
Das Grundgerüst, das bereits in Abschnitt 2.2 vorgestellt wurde, übernehmen wir einfach wie gehabt. Wenden wir unsere Aufmerksamkeit gleich den Vorgängen in der main()-Funktion zu. Dort werden zuerst die für die Berechnung benötigten Variablen deklariert.
56
Variablen und Anweisungen
Die Variablen eines Programms sind nicht mit den Variablen mathematischer Berechnungen gleichzusetzen. Variablen bezeichnen Speicherbereiche im RAM (Arbeitsspeicher), in denen ein Programm Werte ablegen kann. Um also mit Daten arbeiten zu können, müssen Sie zuerst eine Variable für diese Daten deklarieren. Der Compiler sorgt dann dafür, dass bei Ausführung des Programms Arbeitsspeicher für die Variable reserviert wird. Für den Compiler ist der Variablenname einfach ein Verweis auf den Anfang eines Speicherbereichs. Als Programmierer identifiziert man eine Variable mehr mit dem Wert, der gerade in dem zugehörigen Speicherbereich abgelegt ist. Bei der Deklaration geben Sie nicht nur den Namen der Variablen an, sondern auch deren Datentyp. Dieser Datentyp gibt dem Compiler an, wie der Inhalt des Speicherbereichs der Variablen zu interpretieren ist. In obigem Beispiel benutzen wir nur den Datentyp int, der für einfache Ganzzahlen steht. Zu jeder Variablendeklaration gehört auch die Angabe eines Datentyps. Dieser gibt dem Compiler an, wie der Speicherinhalt der Variablen zu interpretieren ist. int erste_Zahl;
Dank des Datentyps können wir der Variablen erste_Zahl direkt eine Ganzzahl zuweisen und brauchen nicht wie im obigen Beispiel des Lochkartenrechners die Dezimalzahl in Binärcode umzurechnen: erste_Zahl = 8754;
int erste_Zahl;
Abb. 3.1: Deklaration und Zuweisung
erste_Zahl=8754;
8754
Adresse 0x0FFE
Adresse 0x0FFE
57
KAPITEL
3
Von Daten, Operatoren und Objekten
Der »Wert« der Variablen Wenn eine Variable einen Speicherbereich bezeichnet, dann ist der Wert einer Variablen der interpretierte Inhalt des Speicherbereichs. In obigem Beispiel wäre der Wert der Variablen erste_Zahl nach der Anweisung erste_Zahl = 8754;
also 8754. Wenn Sie der Variablen danach einen anderen Wert zuweisen würden, beispielsweise erste_Zahl = 5;
wäre der Wert in der Folge gleich 5. Was die Variablen für den Programmierer aber so wertvoll macht, ist, dass er sich nicht mehr um die Speicherverwaltung zu kümmern braucht. Es ist zwar von Vorteil, wenn man weiß, dass hinter einer Variablen ein Speicherbereich steht, für die tägliche Programmierarbeit ist es aber meist nicht erforderlich. Wir sprechen nicht davon, dass wir mit Hilfe des Variablennamens einen eindeutig bezeichneten Platz im Arbeitsspeicher referenzieren und in diesen einen Wert schreiben, wir sagen einfach, dass wir der Variablen einen Wert zuweisen. Wir sprechen nicht davon, dass das interpretierte Bitmuster in dem Speicherbereich der int-Variable erste_Zahl gleich 5 ist, wir sagen einfach, erste_Zahl ist gleich 5. Wir sprechen nicht davon, dass wir mit Hilfe des Variablennamens einen eindeutig bezeichneten Platz im Arbeitsspeicher referenzieren und dessen Wert auslesen, wir sagen einfach, dass wir den Wert der Variablen auslesen. Mit Variablen arbeiten Fassen wir noch einmal die drei wichtigsten Schritte bei der Arbeit mit Variablen zusammen: 1. Variablen müssen deklariert werden. Die Deklaration teilt dem Compiler nicht nur mit, wie der Speicherbereich für die Variable eingerichtet werden soll, sie zeigt dem Compiler überhaupt erst an, dass es sich bei dem von Ihnen gewählten Namen um einen Variablennamen handelt. 2. Variablen werden initialisiert. Als Initialisierung bezeichnet man die anfängliche Zuweisung eines Wertes an eine Variable. Die Initialisierung erfolgt meist im Zuge der Deklaration oder kurz danach, um zu verhindern, dass man den Wert einer Variablen ausliest, der zuvor kein vernünftiger Wert zugewiesen wurde. 3. Variablen werden benutzt, d.h., ihre Werte werden in Anweisungen ausgelesen oder neu gesetzt.
58
Variablen und Anweisungen
// Datei CErstesBeispiel.java public class CErstesBeispiel { public static void main(String[] args) { int ersteZahl; int zweiteZahl; int ergebnis; ersteZahl = 8754; zweiteZahl = 398; ergebnis = ersteZahl - zweiteZahl; System.out.println("8754 - 398 = " + ergebnis); } }
Das Wunder der Deklaration Zum Teufel mit diesen Wortspielen! Soll das jetzt bedeuten, dass die Deklaration einer Variablen ihrer Geburt gleichkommt? Genau das! C-Programmierer werden jetzt ins Grübeln kommen. Sollte man nicht zwischen Deklaration und Definition unterscheiden, und wenn ja, wäre dann nicht eher die Definition der Variablen mit ihrer Geburt zu vergleichen. Schon richtig, aber in Java wird nicht mehr zwischen Deklaration und Definition unterschieden. In C bezeichnete man als Deklaration, die Einführung des Variablennamens zusammen mit der Bekanntgabe des zugehörigen Datentyps. Die Reservierung des Speichers und die Verbindung des Speichers mit der Variablen, erfolgte aber erst in einem zweiten Schritt, der sogenannten Definition. Allerdings ist die Unterscheidung etwas verwischt, denn die Deklaration einfacher Variablen schließt meist deren Definition ein. In Java schließlich ist die Variablendeklaration immer mit einer Speicherreservierung verbunden. Jetzt wissen wir also, wozu Variablen deklariert werden, wir wissen, welche Vorgänge mit der Deklaration verbunden sind, und wir wissen, dass die Deklaration immer der Benutzung der Variablen vorangehen muss, da der Compiler ja sonst nichts mit dem Variablennamen anfangen kann. Was wir nicht wissen, ist, was es genau heißt, wenn wir so salopp sagen, »die Deklaration muss der Benutzung vorangehen.« Um nicht schon wieder vorgreifen
59
KAPITEL
3
Von Daten, Operatoren und Objekten
zu müssen, verweisen wir diesmal auf die weiter unten folgenden Abschnitte 3.4., »Methoden von Klassen«, und 5.3, »Viererlei Variablen«, wo wir diese Frage klären werden. Im Moment, da wir uns nur mit sogenannten lokalen Variablen beschäftigen, die innerhalb einer Methode deklariert werden (die anderen Variablentypen hängen mit der Definition von Klassen zusammen und werden später beschrieben (Abschnitt 3.4 und 5.3)), begnügen wir uns mit dem Hinweis, dass die Deklaration der Variablen vor, d.h. im Quelltext über, der Benutzung der Variablen stehen muss. Am übersichtlichsten ist es, die Deklarationen gebündelt an den Anfang der Methode zu stellen. Die einfachen Datentypen Nun aber wieder zurück zu Variablen und Datentypen. Außer dem Datentyp int für Ganzzahlen kennt Java noch eine Reihe weiterer einfacher Daten-
typen: Tabelle 3.1: Datentyp Einfache Datentypen boolean
Beschreibung
Wertebereich
Boolescher Wert (wahr, falsch)
true, false
char
Zeichen, Buchstabe
Unicode-Werte
byte
ganze Zahl
–128 bis +127
short
ganze Zahl
–32768 bis 32767
int
ganze Zahl
–2.147.483.648 bis +2.147.483.647
long
ganze Zahl
–9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807
float
Fließkommazahl
–3,40282347E+38 bis +3,40282347E+38
Fließkommazahl
–1,7976931348623157E+308 bis +1,7976931348623157E+308
1
double
Unicode In der Tabelle ist für char-Variablen der Unicode angegeben. Was sich recht
unscheinbar anhört, ist eine bahnbrechende Neuerung! Unicode ist ein standardisierter Zeichensatz mit 65.536 Zeichen, mit dem alle diversen Umlaute und Sonderzeichen aller gängigen Sprachen, ja sogar japanische und chinesische Schriftzeichen dargestellt werden können!
1 Für Fließkommazahlen (Zahlen mit Nachkommaanteil) sollten Sie generell den Datentyp double verwenden. Mit diesem lassen sich nicht nur größere Zahlen darstellen, kleinere Zahlen lassen sich zudem in größerer Präzision (mit mehr Nachkommastellen) darstellen. Falls Geschwindigkeit allerdings eine Rolle spielt, sind float-Werte schneller!
60
Variablen und Anweisungen Wie Sie sehen, gibt es verschiedene Datentypen mit unterschiedlichen Wertebereichen. Um z.B. eine ganze Zahl abzuspeichern, haben Sie die Wahl zwischen byte, short, int und long! Die größeren Wertebereiche erkauft man sich mit einem höheren Speicherverbrauch. Eine long-Variable benötigt beispielsweise doppelt so viel Speicher wie eine int-Variable. Glücklicherweise ist Arbeitsspeicher kein allzu großes Problem mehr und viele Programmierer verwenden standardmäßig long für ganzzahlige Werte und double für Fließkommazahlen. Der Datentyp legt also nicht nur fest, wie der Wert der Variablen zu interpretieren ist, er gibt auch an, wie groß der für die Variable bereitzustellende Speicherbereich sein muss. Schauen wir uns einige Beispiele an: int ganzeZahl; double krumme_Zahl; boolean ja, nein, oder_doch; boolean Antwort; short klein = -4; char buchstabe; char Ziffer; ganzeZahl = 3444; krumme_Zahl = 47.11; buchstabe = 'Ü'; Ziffer = '4'; Antwort = true;
Wie Sie an den Beispielen sehen, kann man auch mehrere Variablen des gleichen Typs durch Komma getrennt auf einmal deklarieren und es ist sogar erlaubt, eine Variable direkt im Zuge ihrer Deklaration zu initialisieren, d.h. ihr einen ersten Wert zuzuweisen (siehe klein). Das hört sich ganz so an, als sei der Java-Compiler, der Ihren Quelltext in binären Bytecode übersetzt, recht großzügig, was die verwendete Syntax angeht. Nun, dem ist keineswegs so. Java für Beamte Auch wenn Ihnen die Syntax von Java einerseits viele Möglichkeiten offen lässt, ist sie andererseits doch recht starr vorgegeben und der Compiler wacht penibel darüber, dass Sie sich an die korrekte Syntax halten.
61
KAPITEL
3
Von Daten, Operatoren und Objekten
Wenn Sie es sich also nicht mit dem Compiler verderben wollen, sollten Sie insbesondere auf folgende Punkte achten: ✘ Alle Anweisungen (also Zuweisungen, Funktionsaufrufe und Deklarationen) müssen mit einem Semikolon abgeschlossen werden. krumme_Zahl = 47.11;
✘ Java unterscheidet streng zwischen Groß- und Kleinschreibung. Wenn Sie also eine Variable namens krumme_Zahl deklariert haben, dann müssen Sie auch krumme_Zahl schreiben, wenn Sie auf die Variable zugreifen wollen, und nicht krumme_zahl, Krumme_Zahl oder KRUMME_ZAHL. Und natürlich gibt es auch Regeln für die Einführung von Bezeichnern, also beispielsweise Variablennamen. Wir wollen uns an dieser Stelle aber nicht mit den endlosen Vorschriften befassen, welche die Konstruktion von Variablennamen regeln. Merken Sie sich einfach Folgendes: ✘ Variablennamen können beliebig lang sein, müssen mit einem Buchstaben, '_' oder '$' beginnen und dürfen nicht identisch zu einem Schlüsselwort der Sprache sein. Sie dürfen also Ihre Variable nicht class nennen, da dies ein reserviertes Wort ist. Im Anhang finden Sie eine Liste mit den verbotenen Begriffen. Neben Buchstaben und Ziffern sind fast alle Unicode-Zeichen erlaubt. Insbesondere die Umlaute sind erlaubt. Wenn Sie also eine Variable Begrüßung nennen wollen, brauchen Sie sie nicht wie in anderen Programmiersprachen als Begruessung zu deklarieren, sondern können ruhig Begrüßung schreiben. In Klassen- und Dateinamen sollten Sie allerdings keine Umlaute verwenden, da dies auf gewissen Plattformen zu Schwierigkeiten bei der Kompilation führen kann. Variablen versus Konstanten Muss man wirklich erst erwähnen, dass man den Wert einer Variablen ändern kann, indem man ihr einen neuen Wert zuweist (d.h. einen neuen Wert in ihren Speicherbereich schreibt), während der Wert einer Konstanten unverändert bleibt? Wohl nicht. Interessanter ist es schon zu erfahren, wie man mit Konstanten arbeitet. Dazu gibt es zwei Möglichkeiten:
62
Operatoren Erstens: Sie tippen die Konstante direkt als Wert ein, man spricht dann von sogenannten Literalen. krumme_Zahl = 47.11; // Zuweisung eines Literals krumme_Zahl = ganzeZahl + 47.11;
Da mit einem Literal kein Datentyp verbunden ist, muss der Compiler den Datentyp aus der Syntax des Literals ablesen: Datentyp
Literal
boolean
true, false
char
'c', 'Ü',
char (Sonderzeichen)
'\n', '\\',
char (Unicode)
'\u1234'
String
"Dies ist ein String"
int
12, -128
Tabelle 3.2: Literale
oktal: 077 hexadezimal: 0xFF1F long
12L, 1400000
float
12.4f, 10e-2f
double
47.11, 1e5
Zweitens: Sie deklarieren eine Variable mit dem Schlüsselwort final: final double krumme_Zahl = 47.11; final double PI = 3.141592654;
Da der Wert einer solchen »konstanten Variablen« nach der Deklaration nicht mehr verändert werden kann, folgt zwangsläufig, dass sie bei der Deklaration initialisiert werden muss.
3.2
Operatoren
Nachdem wir nun gesehen haben, was Variablen sind und wie man sie definiert und ihnen einen Wert zuweist, können wir nun endlich damit loslegen, mit ihnen auch etwas zu machen. Dazu dienen bei den bisher vorgestellten einfachen Datentypen vor allem sogenannte Operatoren.
63
KAPITEL
3
Von Daten, Operatoren und Objekten
// Datei COperatoren.java public class COperatoren { public static void main(String[] args) { int x,y,z; int ergebnis_1,ergebnis_2; x = 1; y = 2; z = 3; ergebnis_1 = x + y * z; ergebnis_2 = (5 - 3) * z; System.out.println(ergebnis_1); System.out.println(ergebnis_2);
// = 7 // = 6
x = x + z; System.out.println(x); x += z; System.out.println(x); x += 1; System.out.println(x); x++; System.out.println(x); }
// = 4 // = 7 // = 8 // = 9
}
Das schaut doch ziemlich vertraut aus, oder? Eigentlich genau so, wie man es von algebraischen Gleichungen her kennt. Aber achten Sie bitte auf die letzten Zeilen des Beispiels. Hier sehen wir seltsame Konstruktionen, die wir nun erklären wollen: x = x + z;
Diese Anweisung bewirkt, dass der Computer die aktuellen Werte von x und z zusammenaddiert und dann in x speichert, d.h., die Variable x enthält nach Ausführung dieser Zeile als neuen Wert die Summe aus ihrem alten Wert und z. Da das Hinzuaddieren eines Wertes zum Wert einer Variablen sehr häufig vorkommt, gibt es dafür eine Kurzschreibweise, nämlich: x += z;
64
Operatoren Dies teilt dem Rechner mit, dass er zum Wert von x den Inhalt von z hinzuaddieren und das Ergebnis wieder in x speichern soll. Sehr oft möchte man eine Variable hochzählen (inkrementieren). Java kennt auch hierfür einen speziellen Operator: ++. x++;
Diese Anweisung erhöht den Wert von x um 1. Äquivalente Anweisungen wären: x = x + 1; oder x += 1;
Aber Programmierer sind schreibfaul und x++ sieht ja auch viel geheimnisvoller aus! Das oben Gesagte gilt gleichermaßen für die anderen Grundrechenarten (–, *, /) und das Dekrementieren von Variablen (--). Die verschiedenen Operatoren In Java gibt es natürlich noch andere Operatoren. Die wichtigsten sind: Operator
Beschreibung
Beispiel
++
Inkrement, Dekrement
Erhöht oder erniedrigt den Wert einer Variablen um 1
logisches NICHT
Negiert den Wahrheitswert einer Aussage (beispielsweise eines Vergleichs). Wird meist in Kontrollstrukturen (siehe Kapitel 4) verwendet.
Multiplikation, Division
Multiplikation und Division
Modulo-Division
Liefert den Rest einer ganzzahligen Division
--
!
*
/
%
Tabelle 3.3: Operatoren
4 % 3 liefert z.B. 1 –
+
Subtraktion, Addition
>= Vergleich
==
!=
Subtraktion und Addition Zum Vergleich zweier Werte. Die Operatoren liefern true oder false zurück.
Vergleich (gleich, ungleich) Zum Vergleich auf Gleichheit oder Ungleichheit. Die Operatoren liefern true oder false zurück.
65
KAPITEL
3
Tabelle 3.4: Operator Operatoren (Fortsetzung) &&
Von Daten, Operatoren und Objekten
Beschreibung
Beispiel
logisches UND
Verknüpft zwei Aussagen. Liefert true, wenn beide Aussagen true sind. if ( (x < 1) &&
||
logisches ODER
Verknüpft zwei Aussagen. Liefert true, wenn eine der beiden Aussagen true ist. if ( (x < 1) ||
&
bitweises UND
(y > 1) )
(y > 1) )
UND-Verknüpfung der Binärpräsentation zweier Zahlen. var1 = 1; // ...0001 var2 = 5; // ...0101 var3 = var1 && var2; // ...0001
|
bitweises ODER
ODER-Verknüpfung der Binärpräsentation zweier Zahlen. var1 = 1; // ...0001 var2 = 5; // ...0101 var3 = var1 || var2; // ...0101
~
bitweises Komplement
Umkehrung der Binärpräsentation einer Zahl. var1 = 1; var2 = ~var1;
// ...0001 // ...1110
Die Reihenfolge in der Tabelle deutet die Priorität der Operatoren bei der Auswertung von Ausdrücken an. Beispielsweise sind * und / höher eingestuft als + und –, was genau der altbekannten Schulregel entspricht »Punktrechnung vor Strichrechnung«. Ein Ausdruck ist eine Berechnung aus Variablen, Konstanten und Operatoren, die auf der rechten Seite einer Zuweisung steht. Wenn man sich bei der Reihenfolge nicht ganz sicher ist oder eine bestimmte Reihenfolge der Auswertung erzwingen möchte, kann dies durch die Verwendung von Klammern erreicht werden. Aber auch wenn keine direkte Notwendigkeit zum Setzen von Klammern besteht, können Sie diese verwenden, um eine Berechnung besser lesbar zu machen. z *= ((2*loop)/(2*loop-1)) * ((2*loop)/(2*loop+1));
66
Typumwandlung
3.3
Typumwandlung
Damit wissen Sie schon fast alles, was ein guter Java-Programmierer über Variablen, Operatoren und einfache Datentypen (nennt man manchmal auch elementare oder built-in Datentypen) wissen muss. Aber ein wichtiger Aspekt fehlt noch: Was passiert, wenn Ausdrücke mit verschiedenen Datentypen auftreten? Darf man Datentypen mischen? Die Antwort kommt von Radio Eriwan: Ja, aber ... Automatische Typumwandlung Schauen wir zunächst ein Code-Beispiel an. public class CDemo1 { public static void main(String[] args) { int x = 4711; double y; y = x; System.out.println(y); } }
Die Variable y kann nur Fließkommazahlen (double) speichern, soll aber einen int-Wert zugewiesen bekommen. Ist diese Zuweisung erlaubt? Ja! Die Umformatierung des Integer-Wertes 4711 in den Fließkommawert 4711.0 bereitet dem Compiler keine Mühen. Doch nicht immer geht alles so glatt! public class CDemo2 { public static void main(String[] args) { int x; double y = 3.14; x = y; System.out.println(x); } }
67
KAPITEL
3
Von Daten, Operatoren und Objekten
In diesem Falle soll die Integer-Variable x einen Fließkommawert (double) aufnehmen. Ist diese Zuweisung erlaubt? Ja und nein! Wenn Sie obigen Code kompilieren, beschwert sich der Compiler, weil er eine Fließkommazahl in eine Integer-Variable quetschen soll und dies ist meist mit Datenverlusten verbunden. Mit Hilfe einer expliziten Typumwandlung können wir den Compiler aber zwingen, die gewünschte Umformatierung vorzunehmen. Explizite Typumwandlung Um eine Typumwandlung zu erzwingen, die der Compiler nicht automatisch unterstützt, stellt man einfach dem zu konvertierenden Wert den gewünschten Datentyp in Klammern voran. Im Beispiel Demo2 würden wir also schreiben: x = (int) y;
Aber man muss auf der Hut sein. Hier soll eine Bruchzahl in einen ganzzahligen Wert umgewandelt werden. Der Compiler behilft sich in diesem Fall einfach damit, dass er den Nachkommateil wegwirft und x den Wert 3 zuweist. Es gehen also Daten verloren bei der Umwandlung (Neudeutsch cast) von double zu int. Bereichsüberschreitung Manchmal merkt man auch gar nicht, dass man den falschen Typ verwendet hat. Dann kann auch der Compiler nicht mehr helfen. public class CDemo3 { public static void main(String[] args) { int x,y; short z; x = 30000; y = 30000; z = (short) (x + y); System.out.println(z); } }
Eine böse Falle! x + y ergibt 60000 und das ist außerhalb des Wertebereichs von short! Das Ergebnis lautet in diesem Fall -5536.
68
Typumwandlung Wie kommt dieses merkwürdige Ergebnis zu Stande? Als Integer-Wert wird 60000 als 32-Bit-Wert codiert: 0000 0000 0000 0000 1110 1010 0110 0000
Codierung von short
Eine short-Variable verfügt aber nur über 16 Bit Arbeitsspeicher. Der Compiler schneidet bei der Typumwandlung also erst einmal die obersten 16 Bit weg. Übrig bleibt: 1110 1010 0110 0000
Dieses Bitmuster wird nun als short-Wert interpretiert. Das bedeutet, dass das oberste Bit zur Codierung des Vorzeichens und nur die fünfzehn unteren Bits zur Codierung des Wertes benutzt werden. 110 1010 0110 0000 = 27232
Nun muss man noch wissen, dass der Compiler die negativen Zahlen von unten nach oben quasi rückwärts zählt, wobei die größte, nicht mehr darstellbare negative Zahl 32768 ist. -32768 + 27232 = -5536. Voilà, da haben wir unseren Wert. Division Im nächsten Versuch soll ein einfacher Bruch berechnet werden. So einfach und doch ein Stolperstein für viele Programmierer. public class CDemo4 { public static void main(String[] args) { int x,y; double z1,z2; x = 3; y = 4; z1 = x / y; z2 = 3/4; System.out.println(z1); System.out.println(z2); } }
Was glauben Sie, welche Werte z1 und z2 haben? Bestimmt nicht 0,75, wie man leichtfertig annehmen könnte. Beide sind 0! Wie kommt denn das?
69
KAPITEL
3
Von Daten, Operatoren und Objekten
Nun, denken Sie an die Beamtenmentalität des Compilers. Er wertet Schritt für Schritt und streng nach Vorschrift die Ausdrücke aus. Bei z1 = 3/4; wird zunächst die Division 3/4 ausgeführt. Da beide beteiligten Operanden ganzzahlig sind, wird nach einer »internen Dienstanweisung« auch das Ergebnis 0.75 in einen ganzzahligen Wert konvertiert, d.h., der Nachkommateil fällt weg und es bleibt eine Null übrig. Nun erst erfolgt die Zuweisung an die double-Variable z1. Pflichtbewusst wird daher die int-0 in eine double-0.0 konvertiert und an z1 zugewiesen. Analoges passiert bei z2 = x/y. Was kann man nun tun, um das gewünschte Ergebnis zu erhalten? Eine weitere »interne Dienstvorschrift« sagt dem Compiler, dass alle Operanden eines Ausdrucks den gleichen Datentyp haben müssen, und zwar den »größten«, der auftaucht. Es reicht also, wenn wir einen Operanden explizit umwandeln lassen: z1 = (double) x / y; z2 = (double) 3/4;
Das Voranstellen des gewünschten Datentyps in Klammern veranlasst den Compiler, aus der ganzzahligen 3 eine double-3.0 zu machen. Dadurch greift beim nachfolgenden Auswerten der Division die besagte Regel, dass alle Operanden den größten auftretenden Typ haben müssen. Der Compiler castet daher auch die 4 zu 4.0 und wir haben eine reine double-Division 3.0 / 4.0 vorliegen. Das Ergebnis ist daher auch ein double-Wert und z1 und z2 erhalten beide den korrekten Wert 0.75. Bei Zahlenkonstanten wie 3/4 kann man auch gleich eine double-Zahl schreiben, also z1 = 3.0/4.0;. Sie haben aber wohl schon gemerkt, dass man sehr leicht Fehler einbauen kann, besonders bei etwas größeren Programmen oder langen Formeln, die berechnet werden sollen. Daher unser Tip: Verwenden Sie nach Möglichkeit bei Berechnungen immer nur einen einzigen Datentyp, vorzugsweise double. Alle beteiligten Variablen sollten diesen Typ haben und auftretende Zahlenkonstanten immer in Dezimalschreibweise (also 47.0, 1.0 usw.) schreiben. Sie werden sich dadurch manche Fehlersuche ersparen! Wenn Sie viele Berechnungen durchführen, führt allerdings der Datentyp float zur schnelleren Abarbeitung.
70
Objekte und Klassen
3.4
Objekte und Klassen
Wie schon mehrfach angeklungen ist, existieren neben den beschriebenen elementaren Datentypen noch komplexere und das sind diese seltsamen Teile, die wir nun schon mehrere Male angetroffen, aber meist mehr oder weniger ignoriert haben: die Klassen. Java für Philosophen Bevor wir uns konkret anschauen, wie man eigene Klassen erstellt und bereits vordefinierte Klassen in seinen Programmen verwendet, wollen wir einen kurzen Blick auf die Philosophie werfen, die hinter dem Schlagwort Objektorientierung steckt, denn OOP (objektorientierte Programmierung) steht mehr für eine spezielle Sichtweise als eine ganz neue Programmiertechnik. Zäumen wir das Pferd von hinten auf und stellen wir uns zunächst die Frage: Wie sieht denn die nicht objektorientierte Programmierung aus? Nun, man definiert die notwendigen Variablen ähnlich wie in den kleinen Beispielen von vorhin und dann setzt man die Anweisungen auf, die mit diesen Variablen arbeiten. Fast alle Programmiersprachen bieten dabei die Möglichkeit, Anweisungen in sogenannten Funktionen zu bündeln und auszulagern. Der Programmierer hat dann die Möglichkeit, seinen Code in mehrere Funktionen aufzuteilen, die jede eine bestimmte Aufgabe erfüllen (beispielsweise das Einlesen von Daten aus einer Datei, die Berechnung einer mathematischen Funktion, die Ausgabe des Ergebnisses auf dem Bildschirm). Damit diese Funktionen zusammenarbeiten können, tauschen sie auf verschiedenen Wegen Variablen und Variablenwerte aus. Bei diesem Modell haben wir auf der einen Seite die Daten (abgespeichert in Variablen) und auf der anderen Seite die Funktionen, die mit Daten arbeiten. Dabei sind beide Seiten prinzipiell vollkommen unabhängig voneinander. Welche Beziehung zwischen den einzelnen Funktionen einerseits und den Funktionen und den Daten andererseits besteht, wird erst klar, wenn man versucht nachzuvollziehen, wie die Funktionen bei Ausführung des Programms Daten austauschen. Die Erfahrungen mit diesem Modell haben gezeigt, dass bei Programmprojekten, die etwas größer werden, sich sehr leicht Fehler einschleichen: Da verändert eine Funktion A nebenbei eine Variable, die später eine Funktion B an ganz anderer Stelle im Programm zum Absturz bringt. Die Fehlersuche dauert dann entsprechend lange, weil die Zusammenarbeit von Daten und Funktionen kaum nachzuvollziehen ist! Ferner tendieren solche Programme dazu, sehr chaotisch zu sein. Eine Wartung (Modifizierung, Erweiterung) zu
71
KAPITEL
3
Von Daten, Operatoren und Objekten
einem späteren Zeitpunkt ist oft ein kühnes Unterfangen, vor allem, wenn es nicht mehr derselbe Programmierer ist, der nun verzweifelt zwischen Hunderten von Funktionen herumirrt und versucht, die Zusammenhänge und Wirkungsweise zu verstehen. Schlaue Köpfe kamen daher auf die Idee, eine ganz andere Sichtweise anzunehmen und diese in der Programmiersprache umzusetzen. Ausgangspunkt war dabei die Vorstellung, dass bestimmte Daten und die Funktionen, die mit diesen Daten arbeiten, untrennbar zusammengehören. Eine solche Einheit von logisch zusammengehörigen Daten und Funktionen bildet ein Objekt. Abstrakt formuliert beschreiben die Daten (Variablen) dabei die Eigenschaften des Objekts, und die Funktionen (die dann meist Methoden heißen) legen sein Verhalten fest. Der Datentyp, der die gleichzeitige Deklaration von Datenelementen und Methoden erlaubt, ist die Klasse, angezeigt durch das Schlüsselwort class. Objekte Im Grunde ist dies gar nicht so neu. Denken Sie nur an die einfachen Daund alte tentypen und die Operatoren. Stellen Sie sich eine int-Variable einfach als Datentypen ein Objekt mit einem einzigen Datenelement, eben der int-Variable, vor.
Die Funktionen, die mit diesem Objekt verbunden sind, sind dann die Operatoren, die auf int-Variablen angewendet werden können (Addition, Subtraktion, Vergleiche etc.). Der Vorteil der Klassen liegt allerdings darin, dass in einem Datentyp mehrere Datenelemente vereinigt werden können und dass Sie in Form der Methoden der Klasse selbst festlegen können, welche Operationen auf den Variablen der Klasse erlaubt sind. Klassen deklarieren Der erste Schritt bei der objektorientierten Programmerstellung ist die Zerlegung des Problems in geeignete Objekte und die Festlegung der Eigenschaften und Verhaltensweisen, sprich der Datenelemente und der Methoden, die diese Objekte haben sollten. Dies ist z.T. sehr schwierig und braucht oft viel Erfahrung, damit durch sinnvolles Bestimmen der Programmobjekte auch die Vorteile der Objektorientiertheit zum Tragen kommen können! Überlegen wir uns gleich mal eine kleine Aufgabe. Angenommen, Sie sollen für Ihre Firma ein Programm zur Verwaltung der Mitarbeiter schreiben. Wie könnte eine Aufteilung in Objekte aussehen? Welche Eigenschaften und Methoden sind erforderlich?
72
Objekte und Klassen Das Schlüsselwort class Eine naheliegende Lösung ist, die Mitarbeiter als die Objekte anzusehen. Schaffen wir uns also den Prototyp eines Mitarbeiters und implementieren wir diesen in Form der Klasse CMitarbeiter. class CMitarbeiter { }
Wir haben gerade eine Klasse kreiert! War doch gar nicht schwer, oder? Nun müssen wir unserer Klasse noch Eigenschaften und Methoden zuweisen. Eigenschaften von Klassen Was brauchen wir, um einen Mitarbeiter zu beschreiben? Na klar, einen Namen und Vornamen wird er wohl haben. Und ein Gehalt kriegt er fürs fleißige Werkeln. Erweitern wir also die Klasse um diese Eigenschaften in Form von geeigneten Variablen: class CMitarbeiter { String m_name; String m_vorname; int m_gehalt; }
// Monatsgehalt
Langsam nimmt unser Objekt konkrete Formen an! Den Datentyp int kennen Sie ja schon. String ist kein einfacher Datentyp (daher haben wir ihn im vorigen Abschnitt auch nicht kennen gelernt), sondern ebenfalls ein Objekt, eine Klasse also, genau wie unser CMitarbeiter. Im Gegensatz zu unserer Klasse ist String schon von anderen Leuten erstellt worden (genau gesagt von den Programmierern der Firma Sun) und wird jedem Java-Entwicklungspaket zusammen mit Hunderten anderer nützlicher Klassen mitgegeben. Aber auf diesen Punkt kommen wir in Kürze ausführlicher zu sprechen. Merken Sie sich im Moment, dass String eine Klasse ist und dazu dient, Zeichenketten (englisch Strings) aufzunehmen und zu verarbeiten.
73
KAPITEL
3
Von Daten, Operatoren und Objekten
Instanz- Diese Variablen, die innerhalb einer Klasse, aber außerhalb aller Methoden variablen der Klasse deklariert werden, nennt man Instanzvariablen. Alle Methoden
der Klasse können auf diese Variablen zugreifen. Machen wir nun weiter mit dem Ausbau unserer eigenen Klasse. Nehmen wir an, dass Ihr Chef von Ihrem Programm erwartet, dass es folgende Dinge kann: ✘ die persönlichen Daten eines Mitarbeiters ausgeben ✘ sein Gehalt erhöhen Sie scheinen einen netten Chef zu haben! Auf den Gedanken, das Gehalt zu senken, kommt er gar nicht. Lassen wir ihm keine Chance, es sich anders zu überlegen, und versuchen wir, seinen Anforderungen zu entsprechen. Methoden von Klassen Beachten Sie bitte, dass persönliche Daten ausgeben und Gehalt erhöhen Aktionen sind, die auf den Daten des Mitarbeiters operieren. Folglich werden diese als Methoden der Klasse CMitarbeiter implementiert: class CMitarbeiter { String m_name; String m_vorname; int m_gehalt;
// Monatsgehalt
CMitarbeiter(String pName, String pVorname, int pGehalt) { m_name = pName; m_vorname = pVorname; m_gehalt = pGehalt; } void datenAusgeben() { System.out.println("\n"); System.out.println("Name : " + m_name); System.out.println("Vorname : " + m_vorname); System.out.println("Gehalt : " + m_gehalt + " DM"); }
74
Objekte und Klassen
void gehaltErhoehen(int pErhoehung) { m_gehalt += pErhoehung; } } //Ende der Klassendeklaration
Die Klasse CMitarbeiter besitzt nun drei Methoden mit den Namen CMitarbeiter, datenAusgeben und gehaltErhoehen. Bevor wir uns diese drei Methoden im Einzelnen anschauen wollen, sollten Übung wir uns überlegen, wie eine Methodendeklaration im Allgemeinen aussehen sollte. Stellen Sie sich vor, dass Sie selbst gerade dabei sind, eine Programmiersprache wie Java zu entwickeln, und stellen Sie zusammen, was für die Deklaration einer Methode erforderlich ist: 1. Zuerst braucht die Methode einen Namen, damit sie später aufgerufen Lösung werden kann. Wie bei den Variablennamen verbirgt sich hinter dem Methodennamen eine Adresse. Diese weist bei den Methoden allerdings nicht auf einen Speicherbereich, in dem ein Wert abgelegt ist, sondern auf den Code der Methode. (Tatsächlich werden beim Aufruf eines Programms ja nicht nur die Daten in den Arbeitsspeicher kopiert, auch der Programmcode, die auszuführenden Maschinenbefehle, wird in den Speicher geladen.) Wird eine Methode aufgerufen, sorgt der Compiler dafür, dass der Code der Methode ausgeführt wird. Nach der Abarbeitung der Anweisungen der Methode wird das Programm hinter dem Aufruf der Methode weitergeführt. Damit hätten wir auch schon den zweiten wichtigen Bestandteil unserer Methodendeklaration: 2. Die Anweisungen, die bei Aufruf der Methode ausgeführt werden sollen. Denken Sie dabei daran, dass zusammengehörende Anweisungsblöcke in geschweifte Klammern gefasst werden. 3. Letztlich sollte der Compiler schnell erkennen können, dass ein Name eine Methode bezeichnet. Vereinbaren wir daher einfach, dass auf den Methodennamen zwei Klammern folgen sollen. Unsere Methodendeklaration sieht damit folgendermaßen aus: methodenName() { Anweisungen; }
75
KAPITEL
3
Von Daten, Operatoren und Objekten
Mittlerweile haben wir die dritte Art von Bezeichnern (Namen, die der Programmierer einführt und per Deklaration dem Compiler bekannt gibt) kennen gelernt. Die erste Art von Bezeichnern waren die Variablennamen, die zweite Art von Bezeichnern stellen die Namen dar, die wir den selbst definierten Klassen geben, und die dritte Art von Bezeichnern sind die Methodennamen. Woher nimmt die Methode die Daten, mit denen sie arbeitet? ✘ Nun, zum einen ist eine Methode ja Bestandteil einer Klassendeklaration. Für die Methode bedeutet dies, dass sie auf alle Instanzvariablen ihrer Klasse zugreifen kann (zur Erinnerung: dies sind die Variablen, die innerhalb der Klasse, aber außerhalb jeder Methode deklariert sind). ✘ Zum anderen kann eine Methode natürlich auch eigene, sogenannte lokale Variablen definieren. Von diesen haben wir in den vorangegangenen Abschnitten bereits eifrig Gebrauch gemacht. Alle dort deklarierten Variablen waren lokale Variablen der Methode main(). Diese lokalen Variablen sind keine Klassenelemente, folglich können sie nicht in jeder beliebigen Methode der Klasse benutzt werden, sondern nur innerhalb der Methode, in der sie deklariert sind. Wie aber, wenn zwei Methoden unterschiedlicher Klassen Daten austauschen sollen? 4. Für den Austausch über Klassengrenzen hinweg sehen wir sogenannte Parameter vor. Dies sind Variablen, die innerhalb der Klammern der Methodendeklaration deklariert werden. Bei Aufruf der Methode werden diesen Parametern Werte übergeben (die sogenannten Argumente), die dann innerhalb der Methode wie lokale Variablen benutzt werden können. 5. Schließlich soll die Methode auch noch Daten nach außen exportieren. Zu diesem Zweck definiert jede Methode einen Rückgabewert, dessen Datentyp vor den Methodennamen gestellt wird. Später in Abschnitt 5.2 werden wir dann noch sehen, wie mit Hilfe des Schlüsselwortes return dieser Rückgabewert an den Aufrufer der Methode zurückgeliefert wird. Eine vollständige Methodendeklaration würde jetzt folgendem Schema folgen:
76
Objekte und Klassen
Rückgabetyp methodenName(Deklarationen_der_Parameter) { lokaleVariablen; Anweisungen; }
Schauen wir uns jetzt die beiden Methoden datenAusgeben und gehaltErhoehen aus unserem Beispiel an. void datenAusgeben() { System.out.println("\n"); System.out.println("Name : " + m_name); System.out.println("Vorname : " + m_vorname); System.out.println("Gehalt : " + m_gehalt + " DM"); }
Die leeren Klammern () besagen, dass keine Parameter übergeben werden. Das void zeigt an, dass auch kein Wert zurückgegeben wird. Die Methode datenAusgeben erwartet also weder irgendwelche Parameter noch gibt sie beim Ausführen einen Wert zurück. Schauen wir nun in das Innere der Methode (also was zwischen dem Klammernpaar { } steht). Dort finden wir wieder die Methode System.out.println(), die wir schon println() die ganze Zeit zur Ausgabe benutzen. Ihr können Sie als Parameter einen auszugebenden Text (eingeschlossen in Hochkommata) oder Variablen der einfachen Datentypen und der Klasse String übergeben, deren Inhalt ausgegeben werden soll. Mehrere in einer Zeile auszugebende Texte und Variablen können Sie mit Hilfe des +-Operators verbinden. Das Zeichen »\n« bewirkt bei der Ausgabe einen zusätzlichen Zeilenumbruch und dient hier nur zur optischen Verschönerung. Gehen wir weiter zur Methode gehaltErhoehen. void gehaltErhoehen(int pErhoehung) { m_gehalt += pErhoehung; }
77
KAPITEL
3
Von Daten, Operatoren und Objekten
Das Schlüsselwort void gibt wiederum an, dass die Methode keinen Wert an die aufrufende Stelle zurückgibt. In den Klammern finden wir als Parameter eine int-Variable namens Erhöhung, die im Anweisungsteil zum aktuellen Gehalt addiert wird. Konstruktoren von Klassen Nun zu der Methode, die den gleichen Namen trägt wie die ganze Klasse: CMitarbeiter(String pName, String pVorname, int pGehalt) { m_name = pName; m_vorname = pVorname; m_gehalt = pGehalt; }
Dies ist eine ganz besondere Klassenfunktion, nämlich ein Konstruktor. Jede Klasse braucht einen oder sogar mehrere Konstruktoren, die beim Initialisieren der Variablen der Klasse behilflich sind. In unserem Fall übergeben wir die persönlichen Daten des Mitarbeiters an den Konstruktor, der sie den richtigen Variablen zuweist. Bitte beachten Sie, dass die Parameter anders heißen als die Klassenvariablen (schließlich stellen die Parameter eigenständige Variablen dar und müssen daher auch eigene Namen haben). Jede Klasse braucht zumindest einen Konstruktor zur Initialisierung ihrer Instanzvariablen. Wenn Sie selbst keinen solchen Konstruktor vorsehen, weist der Compiler der Klasse einen Standardkonstruktor zu. Damit ist die Mitarbeiter-Klasse fürs Erste vollendet! Das war doch nicht allzu schwer?! Nun wollen wir diese Klasse auch benutzen. Wir nehmen das Grundgerüst für ein Java-Programm, fügen die Klassendefinition von Mitarbeiter hinzu und erzeugen dann in der main()-Methode einige Instanzen unserer neuen Klassen. Instanzen Moment mal, Instanzen? Was soll denn das sein? Denken Sie am besten an den Mitarbeiter aus der realen Welt, nachdem wir die Klasse CMitarbeiter modelliert haben. Die Klasse CMitarbeiter ist völlig abstrakt; eine Idee, eine Beschreibung, einfach nicht existent! Hugo Piepenbrink oder Erna Mustermann oder so ähnlich heißen die Menschen, die mit Ihnen zusammen in der Firma arbeiten! Sie sind die Instanzen des abstrakten Begriffs CMitarbeiter!
78
Objekte und Klassen Anders ausgedrückt: Unsere Klasse CMitarbeiter ist keine Variable, sondern stellt einen neuen Datentyp dar. Wie im Falle der einfachen Datentypen müssen wir also zuerst Variablen dieses Datentyps deklarieren. Nur dass wir diese Variablen als Instanzen oder Objekte ihrer Klasse bezeichnen. Wozu diese terminologischen Spitzfindigkeiten? Im Grunde sind Instanzen auch nur Variablen, aber für den Compiler ist mit der Einrichtung einer Instanz natürlich etwas mehr Aufwand verbunden, als mit der Einrichtung einer int-Variablen. So muss er den Speicher für die Datenelemente der Instanz (kurz der Instanzvariablen) bereitstellen, die Instanz mit den Methoden der Klasse verbinden und den Konstruktor zur Initialisierung der Instanzvariablen aufrufen. Eine Übersicht zur Terminologie der verschiedenen Variablentypen finden Sie übrigens in Abschnitt 5.3. Mit Klassen programmieren Kommen wir zurück zu unserer Klasse CMitarbeiter und schauen wir uns an, wie wir Instanzen dieser Klasse bilden und verwenden können. // Datei CMitarbeiterBeispiel.java public class CMitarbeiterBeispiel { public static void main(String[] args) { // 2 neue Mitarbeiter instanziieren CMitarbeiter billy = new CMitarbeiter("Gates","Bill",3000); CMitarbeiter stevie = new CMitarbeiter("Jobs","Steve",3500); // Daten ausgeben billy.datenAusgeben(); stevie.datenAusgeben(); // Gehalt von a erhöhen billy.gehaltErhoehen(500);
79
KAPITEL
3
Von Daten, Operatoren und Objekten
// Kontrolle billy.datenAusgeben(); stevie.datenAusgeben(); } } class CMitarbeiter { String m_name; String m_vorname; int m_gehalt;
// Monatsgehalt
CMitarbeiter(String pName, String pVorname, int pGehalt) { m_name = pName; m_vorname = pVorname; m_gehalt = pGehalt; } void datenAusgeben() { System.out.println("\n"); System.out.println("Name : " + m_name); System.out.println("Vorname : " + m_vorname); System.out.println("Gehalt : " + m_gehalt + " DM"); } void gehaltErhoehen(int pErhoehung) { m_gehalt += pErhoehung; } }
Sie sollten das Beispiel in den Editor eingeben, kompilieren und ausführen. Denken Sie daran, dass die Datei den gleichen Namen wie die Hauptklasse tragen muss, also in diesem Fall CMitarbeiterBeispiel.java. Wenn die erste Begeisterung über das funktionierende Programm vorbei ist, können Sie sich wieder setzen und die nachfolgenden Erläuterungen lesen.
80
Objekte und Klassen
Abb. 3.2: Ausgabe des Programms CMitarbeiter Beispiel
Instanzen werden mit new gebildet Das Meiste sollte Ihnen mittlerweile schon vertraut vorkommen. Spannend wird es in der main()-Funktion der Hauptklasse. Dort werden Instanzen der Klasse CMitarbeiter angelegt: CMitarbeiter billy = new CMitarbeiter("Gates","Bill",3000);
Was macht der Compiler, wenn er diese Zeile antrifft? Nun, er legt eine neue Variable mit Namen billy an! Das sagt ihm die Seite links von dem Gleichheitszeichen. Die rechte Seite teilt ihm mit, dass er eine neue Instanz der Klasse CMitarbeiter erzeugen soll. Dazu wird mit Hilfe des Schlüsselwortes new der Konstruktor der Klasse aufgerufen, der drei Parameter erwartet, die wir ihm ordnungsgemäß übergeben. Instanzen von Klassen müssen mit dem Operator new gebildet werden.
81
KAPITEL
3
Von Daten, Operatoren und Objekten
Instanzen sind Referenzen Damit Sie später nicht den Durchblick verlieren, wollen wir an dieser Stelle etwas technischer werden, denn es besteht ein fundamentaler Unterschied zwischen den Variablenvereinbarungen int billy = 4;
und CMitarbeiter billy = new CMitarbeiter("Gates","Bill",3000); int billy Im ersten Fall wird eine int-Variable angelegt, d.h., der Compiler ordnet dem Namen billy einen bestimmten Speicherbereich zu. Gleichzeitig ini-
tialisieren wir die Variable mit dem Wert 4, wobei der Wert 4 direkt in dem Speicherbereich der Variable abgelegt wird (siehe Abb. 3.3). Abb. 3.3: Instanzbildung
int erste_Zahl;
erste_Zahl=8754;
8754
Adresse 0x0FFE
Adresse 0x0FFE
CMitarbeiter billy billy=new CMitarbeiter("Gates","Billy",3000);
m_name
Adresse 0x0FFE
Adresse0x0FFE
m_vorname m_gehalt Adresse 0x1F01
82
Objekte und Klassen Im zweiten Fall wird zwar ebenfalls eine Variable billy angelegt, aber in ihr CMitarbeiter wird nicht einfach ein Wert abgelegt. Stattdessen wird mit Hilfe des new- billy Operators der Konstruktor der Klasse CMitarbeiter aufgerufen. Dieser bildet eine Instanz der Klasse, die im Speicher angelegt wird – aber nicht in dem Speicherbereich, der für die Variable billy eingerichtet wurde. Tatsächlich existiert die Instanz ganz unabhängig irgendwo im Speicher. Bei der Zuweisung der Instanz an die Variable billy wird dann nicht etwa der Inhalt aus der Instanz in den Speicherbereich der Variable billy kopiert. Nein, stattdessen wird in der Variablen billy die Adresse des Speicherbereichs der Instanz abgespeichert. Ist dies erst einmal geschehen, sprechen wir wieder einfach von der Instanz billy und sehen großzügig darüber hinweg, dass billy eigentlich nur eine Variable ist, die eine Speicherzelle bezeichnet, in der ein Verweis (eine Referenz) auf die eigentliche Instanz abgespeichert ist. Noch deutlicher werden die Vorgänge, wenn wir die Instanzbildung in zwei Schritte zerlegen: CMitarbeiter billy; billy = new CMitarbeiter("Gates","Bill",3000);
Zuerst wird eine Variable vom Typ CMitarbeiter deklariert, die zu diesem Zeitpunkt noch keinen gültigen Wert besitzt. In der zweiten Zeile wird mit dem new-Operator eine neue Instanz kreiert und billy erhält dann den Verweis auf die Speicherzellen, wo die Instanz der Klasse zu finden ist. Dies ist eine äquivalente Möglichkeit. Meistens werden Sie in Programmen die kompakte Variante sehen. Sie wissen ja, Programmierer sind schreibfaul und lieben das Kryptische ... Alle Variablen von Klassen sind Referenzen. Gewöhnen Sie es sich an, Referenzen direkt mit einer Instanz zu verbinden (beispielsweise durch Aufruf des new-Operators oder durch Zuweisung eines Wertes). Ansonsten kann es zu Fehlern kommen, wenn Sie Referenzen verwenden, die wahllos auf irgendeinen Speicherbereich und nicht auf eine konkrete Instanz verweisen. Zugriff auf Instanzen Wie erfolgt nun der Zugriff auf die Instanzen billy und stevie? Nehmen wir beispielsweise die Anweisung billy.datenAusgeben(). Man gibt einfach den Namen der Instanz an und den Namen der gewünschten Methode, verbunden durch einen besonderen Operator, den Punkt-Operator ».«.
83
KAPITEL
3
Von Daten, Operatoren und Objekten
Verfügt die Methode über Parameter, werden diesen in der Klammer Argumente übergeben. Wichtig bei der Parameterübergabe ist vor allem die Reihenfolge. Sie muss identisch sein mit der Reihenfolge in der Definition der Methode. Der Konstruktor der Klasse CMitarbeiter muss also immer zuerst zwei Zeichenketten für die Namen erhalten und dann eine Zahl für das Gehalt.
3.5
Arrays
Nun wäre es recht unpraktisch, wenn wir uns in dem Beispiel für jeden neuen Mitarbeiter, für den wir eine Instanz der Klasse CMitarbeiter anlegen, auch einen neuen Variablennamen ausdenken müssten. Wenn die Firma etwas größer ist, dann kommen wir schon in arge Bedrängnis. Aber glücklicherweise gibt es dafür eine Konstruktion, die man Feld oder Array nennt. Am besten schauen wir uns gleich ein Beispiel für die Definition eines Arrays an: int[] feld = new int[100];
Obige Deklaration erzeugt ein Array mit dem Namen feld und 100 Elementen, wobei als Elemente nur Integer-Werte erlaubt sind. Möchte man andere Werte in dem Array ablegen, tauscht man einfach den Datentyp int in der Deklaration gegen einen beliebigen anderen Datentyp oder eine Klasse aus : int[] vektor = new int[3]; boolean[] feld_3 = new boolean[3400]; CMitarbeiter[] personalListe = new CMitarbeiter[4000]; double[][] matrix = new double[3][3];
Sicherlich ist Ihnen aufgefallen, dass der Operator [] bei der Array-Definition die entscheidende Rolle spielt; er gibt an, wieviele Elemente in das Array aufgenommen werden können. Mit seiner Hilfe können auch mehrdimensionale Arrays angelegt werden, die man sich als eindimensionale Arrays vorstellen kann, deren Elemente wiederum aus Arrays bestehen. Der []-Operator wird aber nicht nur bei der Definition der Arrays, sondern auch zum Zugriff auf einzelne Elemente der Arrays verwendet: Man braucht lediglich in den eckigen Klammern anzugeben, auf das wievielte Element zugegriffen werden soll. Die Zahl in den Klammern nennt man daher auch Index und die Art des Zugriffs »indizierten Zugriff«.
84
Arrays
Wenn Sie ein Array von zehn Elementen eines elementaren Datentyps deklarieren, beispielsweise int-Werte, dann sind in dem Array direkt zehn int-Werte enthalten (allerdings alle 0). Wenn Sie ein Array von zehn Objekten einer Klasse definieren, dann enthält das Array nur Null-Verweise (null Referenzen). Sie müssen den einzelnen Array-Elementen erst Objekte der Klasse zuweisen. vektor[0] = 4; feld_3[4] = false; matrix[0][10] = 1.72; // Objekte in Array ablegen personalListe[0] = new CMitarbeiter("Schramm", "Herbert",3500); personalListe[1] = billy; // billy sei eine // CMitarbeiter-Instanz // Objekte in Array verwenden personalListe[0].datenAusgeben(); personalListe[1].datenAusgeben();
Sie sehen, Arrays sind ganz einfach zu verwenden. Aber eine Regel müssen Sie sich besonders nachhaltig einprägen: Das erste Element eines Arrays hat den Index 0 (in Worten NULL!). Diese seltsame Eigenschaft hat Java von der Programmiersprache C geerbt, wo sie schon Tausenden von Programmierern zahllose Stunden an Fehlersuche verursacht hat. Wieso? Der Grund liegt wohl in der menschlichen Psyche. Wenn Sie wie oben das Array personalListe mit 4000 Einträgen definiert haben, erfordert es geradezu übermenschliche Kräfte, um Ihrem Gehirn die fixe Idee auszutreiben, dass der Eintrag personalListe[4000] existiert. Da bei der Definition des Arrays aber die Anzahl an Elementen angegeben wird und der erste Eintrag bei 0 beginnt, ist das falsch. Das letzte gültige Element ist personalListe[3999]. Im Gegensatz zu anderen Programmiersprachen werden Sie bei Java immerhin während der Programmausführung darauf hingewiesen, dass ein Zugriff auf nicht legale Elemente eines Feldes stattfindet, und der Java-Interpreter bricht ab mit der Fehlermeldung ArrrayIndexOutOfBoundsException.
85
KAPITEL
3
Von Daten, Operatoren und Objekten
Das erste Element eines Arrays hat immer den Index 0. Nun sind Sie auch gerüstet, um die main()-Funktion, die in jedem Programmbeispiel auftaucht, etwas besser zu verstehen: public static void main(String[] args)
Wie Sie mittlerweile wissen, stehen in den runden Klammern die Parameter, die diese Funktion erwartet. String[] args bedeutet, dass main() ein Array von String-Objekten als Parameter erwartet. In Kapitel 6.6 werden wir noch ein kleines Beispiel dazu sehen, wie man mit Hilfe von args Kommandozeilenargumente einliest und innerhalb des Programms verarbeitet. Arrays sind Klasseninstanzen Noch eine letzte Bemerkung zu Arrays: Jedes Array, das Sie anlegen, ist selbst automatisch eine Instanz der Klasse Array (auch so eine von den vielen schon mitgelieferten Klassen in Java, allerdings eine ganz besondere). Es gibt daher auch Methoden und Variablen, auf die Sie zugreifen können (wenn man sie kennt!). Eine nützliche Instanzvariable heißt length und liefert die Größe des Feldes zurück: CMitarbeiter[] personalListe = new CMitarbeiter[100]; int anzahlElemente; // .... anzahlElemente = personalListe.length; // Gibt die Größe aus, also 100 System.out.println("Array Größe ist " + anzahlElemente);
Nach diesem ersten intensiven Kontakt mit Klassen wenden wir uns im nächsten Kapitel wieder anderen Grundbestandteilen von Java zu (obwohl Klassen uns auch da begegnen werden), bevor Sie in Kapitel 5 in die Tiefen der objektorientierten Programmierung eintauchen!
86
Vordefinierte Klassen und Packages
3.6
Vordefinierte Klassen und Packages
Zum Schluss aber noch einige wichtige Informationen über die Klassen, die schon fix und fertig in Java integriert sind wie die String-Klasse. Diese Klassen sind in logische Gruppen sortiert, die sich Packages nennen. Dieses Buch behandelt den neuen Java Standard 2 mit fast 20 Packages und Hunderten von Klassen. Eine immense Zahl, nicht wahr? Alle werden wir im Laufe des Buches nicht kennen lernen, aber die wichtigsten und nützlichsten. Danach werden Sie als alter Java-Hase kein Problem mehr haben, in der Java-Dokumentation herumzustöbern und hilfreiche Klassen zu entdecken und in Ihre Programme einzubauen, evtl. sogar zu modifizieren. Ja, auch das geht (meistens jedenfalls)! Was muss man tun, um solche fertigen Klassen in eigenen Programmen zu verwenden? Ganz einfach: Man importiert die Klassen durch Angabe des Package-Namens. Die String-Klasse befindet sich beispielsweise im Package java.lang. In unser Programm importieren wir sie mit Hilfe der Anweisung: import java.lang.String;
Meistens braucht man mehrere Klassen aus einem Package. Anstatt nun jede einzelne Klasse explizit zu importieren, kann man auch das ganze Package importieren: import java.lang.*;
Der * ist also eine Art Platzhalter. Bestimmt nagen im Moment an Ihnen die Zweifel, ob Sie das richtige Buch lesen. Wieso hat denn das Beispiel von vorhin geklappt? Da war weit und breit kein Import-Schnickschnack. Es geht wohl doch ohne! Was also soll das alles? Nun ja, es gibt ein Package, das automatisch vom Compiler importiert wird, weil in ihm so viele wichtige und immer wieder benötigte Klassen sind, dass praktisch kein Programm ohne es auskäme. Und jetzt raten Sie mal, wie dieses Package heißt! Genau. Aber für die anderen 21 Packages gilt das oben Gesagte. Sie müssen die einzelnen Klassen oder das jeweilige gesamte Package explizit importieren.
3.7
Test
1. Datentypen sind das A und O der Variablendeklaration. Zählen Sie die Schlüsselwörter auf, mit denen die verschiedenen Datentypen bei der Variablendeklaration spezifiziert werden.
87
KAPITEL
3
Von Daten, Operatoren und Objekten
2. Welche der folgenden Variablennamen sind nicht zulässig? 123 zähler JW_Goethe JR.Ewing _intern double Liebe ist
3. Welche der folgenden Variablendeklarationen sind nicht zulässig? int 123; char c; boolean option1, option2; boolean option1 option2; short y = 5; short x = 5+1; short x = y; // y wie oben
4. Warum führt der folgende Code zu einem Compiler-Fehler? long x; x = 5; long x; x = 4;
5. Die Division haben Sie in Abschnitt 3.3 kennen gelernt. Erinnern Sie sich noch an den Unterschied zwischen double y; y = 250/4;
und double y; y = 250.0/4;
6. Warum rechnet der Computer mit Binärzahlen? 7. Was sind Klassen?
88
Lösungen 8. Welche Beziehung besteht zwischen einer Klasse und ihren Instanzen? 9. Angenommen, Sie wollten einen Flugsimulator schreiben. Ihre Szenerie ist ganz einfach aufgebaut und besteht praktisch nur aus einem Untergrund und zwei Hochhäusern, die umflogen werden sollen. Überlegen Sie sich, welche Klassen Sie für diesen Flugsimulator definieren müssen, welche Eigenschaften und Methoden benötigt werden und welche Instanzen Sie erzeugen müssen. 10. Angenommen, Sie haben von einem anderen Programmierer eine Klasse CAuto mit den Eigenschaften m_geschwindigkeit und m_benzinverbrauch sowie den Methoden anlassen, beschleunigen und bremsen. Sie wissen sonst nichts über die Implementierung der Klasse. Wie rufen Sie die Methode anlassen auf? 11. Was passiert in obiger Aufgabe, wenn Sie die Methode bremsen aufrufen, bevor Sie die Methoden anlassen und beschleunigen aufgerufen haben? 12. Welches Package muss nicht explizit importiert werden?
3.8
Lösungen
1. int, short, byte, long char boolean float, double
2. Die folgenden Bezeichner sind nicht zulässig: 123 JR.Ewing double Liebe ist
// // // //
Ziffer als erstes Zeichen Punkt in Namen reserviertes Schlüsselwort Leerzeichen in Namen
3. Die folgenden Deklarationen sind nicht zulässig: int 123; // ungültiger Bezeichner boolean option1 option2; // fehlendes Komma short x = y; // x zuvor deklariert
4. Die Variable x wird zweimal definiert. Würde der Compiler diesen Anweisungen folgen, würde er für jede Definition einen Speicherbereich reservieren und mit dem Namen x verbinden. Die Variable x wäre dann mit zwei Speicherbereichen verbunden. Dies darf aber nicht sein – zwi-
89
KAPITEL
3
Von Daten, Operatoren und Objekten
schen einer Variablen und ihrem Speicherbereich muss immer eine eindeutige Beziehung bestehen. 5. 250/4 ist eine Division von Ganzzahlen. Der Compiler liefert daher auch ein ganzzahliges Ergebnis zurück: 62. Im Falle von 250.0/4 wandelt der Compiler alle Werte in double-Fließkommazahlen um. Das Ergebnis lautet daher 62.5. 6. Computer sind elektronische Rechner, in denen Daten in Form von Spannungswerten verarbeitet werden. Einfacher als die Unterscheidung verschiedener Spannungswerte ist die Entscheidung, ob überhaupt Spannung vorhanden ist oder nicht. Ja oder Nein, Null oder Eins. Darum werden alle Daten binärkodiert. 7. Klassen sind spezielle Datentypen, in denen verschiedene Variablen und Methoden zusammen deklariert werden können. In Java bestehen Programme praktisch nur aus der Definition von Klassen. 8. Klassen sind Datentypen, Instanzen sind Variablen von Klassen. 9. Die erste Klasse dient zur Beschreibung des Flugzeugs. Als Eigenschaften sollten Sie zumindest zwei Gruppen von Instanzvariablen deklarieren: − eine Gruppe, die das Flugzeug beschreibt (Flugzeugtyp, Anzahl Tur-
binen, Spannweite, Höchstgeschwindigkeit etc.) − eine Gruppe, die den aktuellen Zustand des Flugzeugs beschreibt
(Position, Flughöhe, Geschwindigkeit etc.) Als Methoden brauchen Sie wenigstens Beschleunigen() und Bremsen(). //Hilfsklasse für Positionsangaben class CKoord { int m_x; int m_y; CKoord(int x, int y) { m_x = x; m_y = y; } }
90
Lösungen
//Flugzeugklasse class CFlugzeug { String m_typ; int m_turbinen; int m_spannweite; int m_maxGeschw; CKoord m_position; int m_hoehe; int m_aktGeschw; CFlugzeug(String typ, int turb, int weite, int geschw) { m_typ = typ; m_turbinen = turb; m_spannweite = weite; m_maxGeschw = geschw; m_position = new CKoord(0,0); m_hoehe = 0; m_aktGeschw = 0; } int beschleunigen() { // Akt_Geschw erhöhen, Höhe und Position ändern return m_aktGeschw; } int bremsen() { // Akt_Geschw senken, Höhe und Position ändern return m_aktGeschw; } }
Für die Hochhäuser brauchen Sie vor allem Instanzvariablen für die Position sowie Breite, Höhe und Tiefe. Wenn Sie möchten, können Sie auch eine Methode vorsehen, die aufgerufen wird, wenn ein Flugzeug in ein Hochhaus kracht. // Klasse für Hochhäuser class CHochhaus
91
KAPITEL
3
Von Daten, Operatoren und Objekten
{ CKoord m_position; int m_breite; int m_hoehe; int m_tiefe; CHochhaus(CKoord pos, int b, int h, int t) { m_position = pos; m_breite = b; m_hoehe = h; m_tiefe = t; } }
Für jedes Flugzeug, das in Ihrer Szenerie herumfliegt, bilden Sie eine eigene Instanz. Da es zwei Hochhäuser gibt, benötigen Sie zwei Instanzen der Klasse CHochhaus. Für den Boden brauchen Sie im Prinzip keine spezielle Klasse. public class CFlugzeugSimulator { public static void main(String[] args) { CFlugzeug meines = new CFlugzeug("Sportflugzeug",1,5,300); CHochhaus haus1 = new CHochhaus(new CKoord(50,100),40,120,40); CHochhaus haus2 = new CHochhaus(new CKoord(150,80),30,140,40); meines.beschleunigen(); meines.bremsen(); } }
92
Lösungen 10. Zuerst müssen Sie die Klasse CAuto importieren. Unter der Annahme, dass die Klasse CAuto als public deklariert ist und keinem besonderen Package angehört, kopieren Sie die class-Datei in das Verzeichnis Ihres Programms und setzen die Anweisung import CAuto;
an den Anfang Ihres Quelltextes. Dann bilden Sie eine Instanz der Klasse: CAuto meinAuto = new CAuto();
und rufen bei Bedarf einfach die Methode anlassen() auf: meinAuto.anlassen();
11. Eigentlich sollte es nichts ausmachen, wenn die Methode bremsen(), vor den Methoden anlassen() und beschleunigen() aufgerufen wird. Was aber wirklich passiert, hängt natürlich von der Implementierung ab, die Ihr Freund vorgesehen hat. Nehmen wir an, die Methode bremsen() reduziert die aktuelle Geschwindigkeit um 10 km/h. Dann kann es passieren, dass ein Aufruf der Methode bremsen() vor dem Aufruf der Methode beschleunigung() die aktuelle Geschwindigkeit auf –10 km/h zurücksetzt. Ein unnötiger Fehler. Gemäß den Regeln der Objektorientiertheit sollte die Klasse selbst dafür sorgen, dass ihre Daten (Instanzvariablen) nur vernünftige Werte annehmen. Die Methode bremsen() sollte also ständig die aktuelle Geschwindigkeit kontrollieren und diese nicht unter 0 km/h herabsetzen. Auf diese Weise sorgt die interne Implementierung der Klasse dafür, dass die Integrität ihrer Daten erhalten bleibt und Programmierfehler durch unsachgemäßen Gebrauch der Klasse weitestgehend verhindert werden. Man bezeichnet dies auch als Information hiding oder Kapselung. 12. Das Package java.lang braucht nicht explizit importiert zu werden.
93
KAPITEL
3 3.9
Von Daten, Operatoren und Objekten
Zusammenfassung
✘ Daten werden in Programmen durch Variablen repräsentiert. ✘ Jeder Variablen entspricht ein Speicherbereich, in dem der aktuelle Wert der Variablen abgelegt wird. ✘ Variablen müssen deklariert werden, um dem Compiler den Namen der Variable bekannt zu machen. Bei der Deklaration der Variablen wird auch der Datentyp der Variable angegeben, der festlegt, welche Werte die Variable aufnehmen kann und welche Operationen auf der Variable durchgeführt werden können. ✘ Durch das Schlüsselwort final kann man festlegen, dass sich der Wert einer Variablen nach der Deklaration nicht mehr ändern darf. ✘ Java kennt einfache Datentypen (boolean, byte, short, int, long, float, double) und Klassen (class Name). ✘ Klassen definieren Eigenschaften (Datenelemente) und Verhaltensweisen (Elementfunktionen oder Methoden genannt). ✘ Variablen von Klassen nennt man Instanzen. ✘ Instanzen werden mit dem Operator new und durch Aufruf des Konstruktors der Klasse gebildet. ✘ Konstruktoren sind spezielle Methoden von Klassen, die bei der Instanzbildung aufgerufen und zur Intialisierung der Datenelemente benutzt werden. ✘ Klassen können in Packages verwaltet werden. Um vordefinierte Klassen in ein Programm aufzunehmen, benutzt man das Schlüsselwort import. ✘ Java-Programme sind Ansammlungen von Klassendefinitionen.
94
Kapitel 4
Programmfluss und Fehlererkennung mit Exceptions 4 Programmfluss und Fehlererkennung mit Exceptions
In diesem Kapitel werden wir ein Thema aufgreifen, das im letzten Kapitel schon unterschwellig mit angeklungen ist, ohne dass wir jedoch etwas wirklich Konkretes dazu angemerkt hätten. Es geht um die Steuerung des Programmflusses. Hier kommen eine Reihe unterschiedlicher Konzepte zum Tragen, deren korrekter und sinnvoller Einsatz oftmals den Unterschied zwischen einem guten und einem schlechten Programm ausmacht. Lernziel In diesem Kapitel sollen Sie lernen, wie man durch Modularisierung und mit Hilfe von Bedingungen, Schleifen und Exceptions aus Spagetti-Code strukturierte Programme macht. Sie lernen in diesem Kapitel, ✘ warum Programme durch Klassen und Methoden modularisiert werden, ✘ wie man eigene Packages definiert, ✘ wie man auf Klassen aus anderen Dateien zugreift, ✘ wie Verzweigungen implementiert und eingesetzt werden, ✘ wie Schleifen implementiert und eingesetzt werden, ✘ wie man eine Exception-Behandlung implementiert.
95
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
Folgende Schlüsselwörter und Elemente lernen Sie kennen Packages (package, import), Verzweigungen (if, else, switch), Schleifen (for, while) und Exceptions (raise, throw, try) sowie die Schlüsselwörter continue und break. Voraussetzung Voraussetzung für dieses Kapitel ist, dass Sie wissen, wie das Grundgerüst einer Java-Applikation aussieht (Kapitel 2.2), dass Sie wissen, wie Sie eine Java-Applikation in Ihrer Entwicklungsumgebung erstellen und ausführen (Anhang B), und dass Sie sich bereits mit Variablen und Operatoren auskennen (Kapitel 3). Schauen Sie sich vielleicht auch noch einmal die Beispiele zur Übergabe von Parametern an Methoden aus Kapitel 3.4, Abschnitt »Methoden von Klassen«, an.
4.1
Die Axiomatik des Programmablaufs
Tragen wir zuerst einmal zusammen, was Sie schon über den Programmfluss wissen (oder zu wissen glauben). 1. Die Ausführung einer Applikation beginnt mit der Methode main(). 2. Die Anweisungen in der main()-Methode werden der Reihe nach von oben nach unten ausgeführt. 3. Trifft der Compiler dabei auf einen Methodenaufruf, springt die Ausführung in die erste Zeile dieser Methode. Sind alle Anweisungen der Methode abgearbeitet, kehrt die Programmausführung in die aufrufende Methode zurück und wird mit der auf den Methodenaufruf folgenden Zeile fortgesetzt. 4. Mit der letzten Anweisung der main()-Methode endet das Programm. Sie denken jetzt, ein Programm besteht aus Anweisungen. Falsch gedacht! Ein Java-Programm besteht nicht aus Anweisungen, sondern aus Deklarationen – um genau zu sein, aus Klassendeklarationen. Jede Klasse kann Datenelemente und Methoden definieren und nur in diesen Methoden findet man die Anweisungen eines Programms (siehe Kapitel 2.2 und 3.4).
96
Modularisierung durch Klassen und Methoden
4.2
Modularisierung durch Klassen und Methoden
Gäbe es nicht die Möglichkeit, dass eine Methode eine andere Methode aufruft, müssten Sie alle Anweisungen des Programms untereinander in die main()-Methode schreiben. Das klingt nicht so problematisch, solange die Programme klein sind, aber es wird zum Horror, wenn Sie beginnen, größere Programme zu schreiben. Beginnen wir mit einem ganz einfachen Beispiel. Sie haben ein Sparkonto mit 15.000,– DM angelegt, das Ihnen die Bank mit 3,5% verzinst. Jetzt brennen Sie natürlich darauf zu wissen, wie sich Ihr Geld in Windeseile von Jahr zu Jahr vermehrt. Gehen wir davon aus, dass Sie die jedes Jahr hinzukommenden Zinsen nicht abheben, sondern weiter verzinsen lassen, dann berechnet sich Ihr Guthaben nach n Jahren zu:
Zinssatz Endkapital = Startkapital ∗ 1 + 100
n
Lassen Sie uns also einmal ausrechnen, welcher Geldsegen sich in den folgenden sieben Jahren über Sie ergießt. Das zugehörige Programm sieht folgendermaßen aus: // Datei CZinsen.java public class CZinsen { public static void main(String[] args) { double startkapital = 15000; double zinssatz = 3.5; double laufzeit; double endkapital; //Berechnung des Endkapitals endkapital = startkapital * Math.pow((1 + zinssatz/100),1); System.out.println("Nach 1. Jahr: " + (int) endkapital + " DM"); endkapital = startkapital * Math.pow((1 + zinssatz/100),2); System.out.println("Nach 2. Jahr: " + (int) endkapital + " DM");
97
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
endkapital = startkapital * Math.pow((1 + zinssatz/100),3); System.out.println("Nach 3. Jahr: " + (int) endkapital + " DM"); endkapital = startkapital * Math.pow((1 + zinssatz/100),4); System.out.println("Nach 4. Jahr: " + (int) endkapital + " DM"); endkapital = startkapital * Math.pow((1 + zinssatz/100),5); System.out.println("Nach 5. Jahr: " + (int) endkapital + " DM"); endkapital = startkapital * Math.pow((1 + zinssatz/100),6); System.out.println("Nach 6. Jahr: " + (int) endkapital + " DM"); endkapital = startkapital * Math.pow((1 + zinssatz/100),7); System.out.println("Nach 7. Jahr: " + (int) endkapital + " DM"); } } Abb. 4.1: Ausgabe des Zinsen-Programms
Führen Sie das Programm zum Test einmal aus. Das Programm arbeitet ordnungsgemäß und doch stimmt etwas nicht damit. (Nein, es liegt nicht an der Formel, dass die Zinserträge so niedrig sind; dafür müssen Sie schon Ihre Bank verantwortlich machen.) Das Problem ist der Quelltext. Jedem Programmierer, der etwas auf sich hält, wird beim Anblick dieses Programms das Herz stocken. Hier wird eine
98
Modularisierung durch Klassen und Methoden wiederkehrende Aufgabe (die Zinsberechnung) jedesmal neu implementiert. Das macht das Programm äußerst unübersichtlich und erschwert das Debuggen, sprich das Ausmerzen von Fehlern. Wenn sich jetzt ein Fehler in die Formel eingeschlichen hätte, müssten Sie diesen sieben Mal korrigieren. In größeren Programmen hätten Sie vielleicht sogar noch das Problem, dass Sie gar nicht mehr wissen, wo überall in Ihrem Quelltext diese Formel auftaucht. Teilprobleme in Methoden implementieren I Die Lösung dieses Problems liegt aber auf der Hand. Öfter wiederkehrende Aufgaben werden in Form von Methoden implementiert. Und dies soll nun Ihre Übung sein. Schreiben Sie das obige Programm neu, wobei Sie die Zinsberechnung als Übung eigene Methode implementieren und diese in der main()-Methode für die ersten sieben Jahre aufrufen. Große Schwierigkeiten sollte Ihnen dies nicht bereiten, da Sie die Berechnung fast unverändert übernehmen können. Lediglich die Laufzeit ändert sich bei jedem Aufruf der Methode und muss dieser daher als Parameter übergeben werden. Wenn alles geklappt hat, könnte Ihr Programm jetzt ungefähr folgenderma- Lösung ßen aussehen. // Datei CZinsberechnung.java public class CZinsberechnung { public static void zins_berechnen(double laufzeit) { double startkapital = 15000; double zinssatz = 3.5; double endkapital; //Berechnung des Endkapitals endkapital = startkapital * Math.pow((1 + zinssatz/100),laufzeit); System.out.println("Nach " + (int) laufzeit + ". Jahr: " + (int) endkapital + " DM"); } public static void main(String[] args) { zins_berechnen(1); zins_berechnen(2); zins_berechnen(3);
99
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
zins_berechnen(4); zins_berechnen(5); zins_berechnen(6); zins_berechnen(7); } } Abb. 4.2: Ausgabe des Programms CZinsberechnung
Die Aufnahme der println()-Anweisung in die Methode ist eher ungewöhnlich, führt den Effekt der Auslagerung in eine Methode aber noch stärker vor Augen. Was hier vorgeführt wurde, ist die Modularisierung des Codes. Programme, die Teilprobleme in Form von Methoden lösen, sind besser lesbar, besser wartbar, leichter zu debuggen und der modulare Code ist leichter wiederzuverwerten und besser zwischen Programmen auszutauschen (bestes Beispiel hierfür sind die Klassenbibliotheken von Java, in deren Klassen und Methoden bereits viele Standardprobleme für Sie gelöst sind und die Sie durch Importierung in Ihre Programme aufnehmen können (siehe 3.6 und den letzten Abschnitt dieses Unterkapitels)). Teilprobleme in Methoden implementieren II Wenn Sie Teilprobleme in Methoden isolieren, sollten Sie versuchen, die Methode so allgemein wie nur möglich und sinnvoll zu halten. Was bedeutet das? Bleiben wir einfach bei obigem Beispiel. Wir haben jetzt zwar eine Methode zur Zinsberechnung, doch ihr Einsatzbereich ist recht beschränkt. Wenn Sie im weiteren Verlauf des Programms Zinsberechnungen durch-
100
Modularisierung durch Klassen und Methoden führen wollen, ohne das Ergebnis gleich auszugeben, oder Berechnungen ausführen, bei denen Sie den Zinssatz und die Laufzeit variieren wollen, können Sie obige Methode nicht verwenden. Welche Grundsätze lassen sich daraus für eine brauchbarere Implementierung der Methode ableiten? Modularisierung durch Methoden: ✘ Die Methode sollte sich streng darauf beschränken, nur die von ihr geforderte Aufgabe zu erledigen (also nur die Zinsberechnung und nicht schon die Weiterverarbeitung des Ergebnisses). ✘ Soll die Methode Ergebnisse zurückliefern, sollte sie dazu einen Rückgabewert definieren. ✘ Schließlich sollten alle wichtigen Optionen, über die man die Arbeitsweise der Methode vielleicht anpassen möchte, als Parameter an die Methode übergeben werden (im Falle unserer Zinsberechnung wären dies die Variablen startkapital, zinssatz und laufzeit). Unsere neue, allgemein verwendbare Methode sähe dann folgendermaßen aus: // Datei CZinsberechnung2.java public class CZinsberechnung2 { public static double zins_berechnen( double startkapital, double zinssatz, double laufzeit) { double endkapital; //Berechnung des Endkapitals endkapital = startkapital * Math.pow((1 + zinssatz/100),laufzeit); return endkapital; } ...
Startkapital, Zinssatz und Laufzeit werden jetzt als Parameter an die Methode übergeben. Das Ergebnis der Berechnung wird am Ende der Methode mit Hilfe des Schlüsselworts return zurückgeliefert. Ein Aufruf dieser Methode könnte nun folgendermaßen aussehen: ... public static void main(String[] args)
101
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
{ double endkapital; endkapital = zins_berechnen(15000,3.5,7); System.out.println("Der Endbetrag nach 7 Jahren: " + (int) endkapital); }
Teilprobleme in Klassen implementieren Methoden sind Teile von Klassen. Wenn Sie also eine Methode in mehreren Programmen verwenden wollen, müssen Sie die Methode mit der Klasse, in der sie definiert ist, importieren. Bevor wir uns dies im folgenden Abschnitt noch einmal genauer ansehen, wollen wir uns überlegen, wie wir diesen Umstand (und damit die Möglichkeiten der objektorientierten Programmierung) nutzen können. Im Abschnitt 3.4 ist angeklungen, dass Klassen in der objektorientierten Programmierung Objekte der realen Welt repräsentieren (beispielsweise die Mitarbeiter einer Firma, das Flugzeug eines Flugzeugsimulators, die verschiedenen geometrischen Formen eines Grafikprogramms oder auch Zeichenketten, die sogenannten Strings). In diesem Kapitel sind wir auf eine weitere Verwendung gestoßen, nämlich Klassen als Sammlungen von Methoden. Dabei steht es Ihnen durchaus frei, ein Sammelsurium an verschiedensten, aber häufig benötigten Methoden in einer Klasse zu vereinen (geschehen beispielsweise in der Klasse java.lang.System). Sinnvoller ist es jedoch, solche Sammelklassen einem bestimmten Thema zu widmen und nur solche Methoden in die Klasse aufzunehmen, die mit diesem Thema zu tun haben. Die Klasse repräsentiert dann zwar immer noch kein echtes Objekt, aber immerhin ein definiertes Thema (wie z.B. bei der Java-Klasse java.lang.Math). Übung Das Thema für unsere Sammelklasse liegt auf der Hand, es lautet »Zins-
berechnung«. Als weise objektorientierte Programmierer tippen wir nicht gleich sinnlos drauf los, sondern überlegen uns zuerst, wie man die Klasse sinnvoll aufbauen könnte. Lösung Zuerst einmal brauchen wir einen passenden, aussagekräftigen, aber nicht zu langen Namen, wie zum Beispiel CZins.
Dann wollen wir den Leistungsumfang der Klasse erhöhen, indem wir eine zweite Methode in die Klasse aufnehmen, die das Endkapital ohne Zinses-
102
Modularisierung durch Klassen und Methoden zins berechnet (also für den Fall, dass die Zinserträge nicht auf dem Sparbuch, sondern einem anderen Konto gutgeschrieben werden). Unsere Klasse sähe jetzt folgendermaßen aus: class CZins { public static double ertragZ(double startkapital, double zinssatz, double laufzeit) { //Berechnung mit Zinseszins return startkapital * Math.pow((1 + zinssatz/100),laufzeit); } public static double ertrag(double startkapital, double zinssatz, double laufzeit) { //Berechnung ohne Zinseszins return startkapital * (1 + zinssatz/100 * laufzeit); } }
Wir haben diesmal das Ergebnis der Berechnungen nicht mehr in einer eigenen lokalen Variable (endkapital) abgespeichert, sondern direkt über die return-Anweisung an den Aufrufer der Methode zurückgeliefert. Wie könnte man aus dieser Sammelklasse eine »echt« objektorientierte Klasse machen? Beachten Sie, dass die von uns getroffene Unterscheidung in Sammelklasse und »echt« objektorientierte Klasse sich rein auf die Art der Verwendung der Klasse bezieht. Rein programmiertechnisch sind beides echte objektorientierte Klassen. Nur dass wir die Instanz einer Sammelklasse nicht benutzen, um ein Objekt zu repräsentieren, sondern bloß, um auf die Methoden der Klasse zugreifen zu können.
103
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
Die Instanzen unserer »echt« objektorientierten Klasse sollen reelle Objekte repräsentieren. Was böte sich da an? Sparbücher! Ein Sparbuch ist charakterisiert durch seine Einlage und seine Verzinsung. Für beide muss die Klasse Sparbuch also Datenelemente vorsehen. Des weiteren braucht die Klasse einen Konstruktor, der für jede Instanz der Klasse (also jedes einzelne Sparbuch) Einlage und Verzinsung festlegt. Schließlich wollen wir fünf Methoden vorsehen: ✘ einzahlen(). Die Einlage wird um einen bestimmten Betrag erhöht. ✘ abheben(). Die Einlage wird um einen bestimmten Betrag vermindert. ✘ ertragZ(). Berechnet den voraussichtlichen Ertrag nach n Jahren mit Zinseszins. ✘ ertrag(). Berechnet den voraussichtlichen Ertrag nach n Jahren ohne Zinseszins. Die Klasse CSparbuch und eine passende Hauptklasse, die Sparbuch verwendet, sähe dann folgendermaßen aus: // Datei CSparbuchNutzen1.java class CSparbuch { double kapital; double zinssatz; CSparbuch(double kap, double zins) { kapital = kap; zinssatz = zins; } void einzahlen(double betrag) { kapital += betrag; } void abheben(double betrag) { kapital -= betrag; }
104
Modularisierung durch Klassen und Methoden
double ertragZ(double laufzeit) { return kapital * Math.pow((1 + zinssatz/100),laufzeit); } double ertrag(double laufzeit) { return kapital * (1 + zinssatz/100 * laufzeit); } } //Hauptklasse des Programms class CSparbuchNutzen1 { public static void main(String[] args) { CSparbuch meinSparbuch = new CSparbuch(0,3); meinSparbuch.einzahlen(10000); System.out.println("Ertrag nach 5 Jahren :"); System.out.println("\t ohne Zinseszins: " + (int) meinSparbuch.ertrag(5)); System.out.println("\t mit Zinseszins: " + (int) meinSparbuch.ertragZ(5)); } }
Die Klasse ist für einen professionellen Einsatz noch nicht ausgereift genug. Dies liegt daran, dass bei der Verzinsung davon ausgegangen wird, dass sich der in dem Datenelement kapital festgehaltene Betrag schon das ganze Jahr auf dem Sparbuch befindet. Tatsächlich müsste man Buch darüber führen, wie sich die Einlage des Sparbuchs übers Jahr verändert hat, und jeden Betrag für sich verzinsen. Klassen importieren Bisher hatten wir es vorwiegend mit ganz einfach gestrickten Programmen zu tun, die lediglich aus einer einzigen Klassendefinition bestanden. Die Quelltextdatei dieser Programme haben wir mit der Extension .java abgespeichert und kompiliert. Der Compiler hat dann eine zugehörige, binäre .class-Datei erzeugt, die wir mit Hilfe des Interpreters ausführen konnten.
105
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
Mehrere Klassen in einer Datei Nun haben wir aber zwei Klassen, die wir verwenden möchten. Die Klasse CSparbuch und die Hauptklasse eines Programms, das die Klasse CSparbuch verwendet. Im einfachsten Fall schreiben wir alle Klassen, die wir nutzen, in eine Quelltextdatei (siehe obiges Beispiel CSparbuchNutzen1). Der Compiler erzeugt dann für jede in der .java-Quelldatei definierte Klasse eine eigene binäre .class-Datei. Zur Ausführung des Programms wird die .class-Datei der Hauptklasse mit dem Interpreter aufgerufen. In diesem Fall gibt es kaum Probleme: ✘ Der Compiler kennt alle Klassen, die im Programm benutzt werden; die Hauptklasse des Programms erkennt der Compiler daran, dass sie den gleichen Namen wie die Quelltextdatei trägt. ✘ Der Interpreter weiß, wo er die .class-Dateien der verwendeten Klassen finden kann (stehen alle im Verzeichnis des Programms). ✘ Der Zugriff auf die Klassen und deren Elemente ist wie in den bisherigen Beispielen uneingeschränkt möglich – wir können uns sogar das Schlüsselwort public in den Deklarationen sparen. Klassen importieren Nun wäre es aber unschön, wenn man die Definition jeder benutzten Klasse in den Quelltext des Programms kopieren müsste, obwohl eine binäre .class-Datei für die Klasse schon längstens vorliegt. Das Programm CSparbuchNutzen2 verwendet daher das Schlüsselwort import, um die Klasse Sparbuch zu importieren. Voraussetzung ist natürlich, dass eine .class-Datei für die Klasse Sparbuch in dem Verzeichnis des Programms CSparbuchNutzen2 vorhanden ist (die .class-Datei können Sie beispielsweise durch die Kompilierung der Datei CSparbuchNutzen1.java erstellen). // Datei CSparbuchNutzen2.java import CSparbuch; //Hauptklasse des Programms public class CSparbuchNutzen2 {
106
Modularisierung durch Klassen und Methoden
public static void main(String[] args) { CSparbuch meinSparbuch = new CSparbuch(0,3); meinSparbuch.einzahlen(10000); System.out.println("Ertrag nach 5 Jahren :"); System.out.println("\t ohne Zinseszins: " + (int) meinSparbuch.ertrag(5)); System.out.println("\t mit Zinseszins: " + (int) meinSparbuch.ertragZ(5)); } }
In der ersten Zeile wird die Klasse CSparbuch importiert. Voraussetzung ist Analyse dabei, dass sich die Datei CSparbuch.class in dem gleichen Verzeichnis wie das Programm CSparbuchNutzen2 befindet. Danach kann man auf die Klasse CSparbuch genauso zugreifen, als ob sie direkt in dem Quelltextmodul des Programms CSparbuchNutzen2 definiert wäre. Eigene Klassen in Packages Im Laufe Ihrer Tätigkeit als Java-Entwickler werden Sie immer mehr eigene Klassen implementieren und erstellen und irgendwann entsteht die Notwendigkeit, diese Klassen zu ordnen. Dazu dient das Konzept der Packages, auf die wir schon in Abschnitt 3.6 kurz eingegangen sind. Im Grunde ist es ganz einfach: ✘ Packages verwalten zwar Klassen, werden aber aus Quelltextdateien aufgebaut. Indem Sie an den Anfang einer Quelltextdatei (Extension .java) folgende Anweisung stellen: package PackageName;
legen Sie fest, dass alle Klassen, die in dieser Datei definiert werden, dem Package PackageName zugeordnet werden. ✘ Wenn Sie umgekehrt Klassen aus einem Package importieren wollen, benutzen Sie das Schlüsselwort import: import PackageName.KlasseSowieso;
wobei Sie auch den Platzhalter * zur Auswahl aller Klassen des Package einsetzen können: import PackageName.*;
107
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
Um also die Klasse CSparbuch in einem eigenen Package namens Zinsrechnung abzulegen, brauchen Sie nur die Klassendefinition in eine eigene Quelltextdatei zu kopieren und dieser die Anweisung package Zinsrechnung;
voranzustellen (die package-Anweisung muss die erste Quelltextzeile in der Datei sein). Um dann in dem Programm CSparbuchNutzen2 auf die Klasse CSparbuch zugreifen zu können, müssen wir nur die Anweisung import CSparbuch;
in import Zinsrechnung.CSparbuch;
erweitern, oder? Leider nein! Ganz so einfach ist es dann doch nicht. Es gibt da noch zwei Besonderheiten der Packages zu beachten. ✘ Packages müssen sich auf Ihrem Computer in einer gleich aufgebauten Verzeichnisstruktur widerspiegeln. Für jedes Package müssen Sie ein eigenes Verzeichnis anlegen und in dieses die .class-Dateien der JavaKlassen des Package kopieren. Wenn Sie Packages mit Hilfe des .-Operators hierarchisch gliedern, müssen auch die entsprechenden Verzeichnisse in gleicher Weise hierarchisch gegliedert werden. ✘ Packages dienen nicht nur der Gruppierung von Klassen, sie regeln auch den Zugriff auf Klassen. So kann eine Klasse, die einem Package angehört, ohne Probleme auf alle anderen Klassen des Package zugreifen. Dagegen ist der Zugriff auf eine Klasse eines anderen Package nur dann möglich, wenn die Klasse, auf die zugegriffen werden soll, als public deklariert wurde (Gleiches gilt für Methoden und Datenelemente von Klassen). Im Übrigen arbeiten Sie auch dann mit Packages, wenn Sie Ihre Quelltextdateien nicht explizit einem bestimmten Package zuordnen. Ihre Klassen gehören dann dem Unnamed-Package an. Übung Setzen wir jetzt ein Programm CSparbuchNutzen3 auf, dass eine Instanz der Klasse CSparbuch aus dem Package Zinsrechnung benutzt.
108
Modularisierung durch Klassen und Methoden Wir beginnen mit der Einrichtung der Verzeichnisstruktur:
Lösung
1. Legen Sie ein Verzeichnis für das Programm an und dazu ein Unterverzeichnis, das den gleichen Namen wie das Package trägt: Zinsrechnung. Dann erstellen wir die Klasse CSparbuch: 2. Öffnen Sie eine neue Datei für die Klasse CSparbuch. 3. Übernehmen Sie den Quelltext der Klasse CSparbuch aus der Datei CSparbuchNutzen1.java, aber deklarieren Sie die Klasse CSparbuch und alle ihre Methoden als public. 4. Setzen Sie an den Anfang der Datei die Anweisung package Zinsrechnung;. 5. Speichern Sie die Datei als CSparbuch.java in dem Unterverzeichnis Zinsrechnung. 6. Kompilieren Sie die Datei. Jetzt brauchen wir noch das eigentliche Programm: 7. Öffnen Sie eine neue Datei. 8. Übernehmen Sie den Quelltext der Klasse CSparbuchNutzen2, aber nennen Sie Klasse und Datei in CSparbuchNutzen3 um und ändern Sie die import-Anweisung in import Zinsrechnung.CSparbuch;. 9. Speichern Sie die Datei in dem Verzeichnis des Programms. 10. Kompilieren Sie die Datei. 11. Führen Sie das Programm aus. Abb. 4.3: Ausgabe des Programms CSparbuchNutzen3
109
KAPITEL
4 4.3
Programmfluss und Fehlererkennung mit Exceptions
Kontrollstrukturen
Nachdem wir im obigen Abschnitt gesehen haben, wie man den Programmablauf in Methoden aufteilt, wollen wir uns jetzt ansehen, wie der Programmablauf innerhalb einer Methode gesteuert werden kann. Dabei hat es der Programmierer mit zwei typischen Problemstellungen zu tun, die ständig wiederkehren: ✘ das Testen von Bedingungen (wenn Bedingung erfüllt, dann tue dies und das, sonst etwas anderes) und ✘ das unzählige Wiederholen von bestimmten Anweisungen. Für beide Aufgaben gibt es in Java spezielle Konstrukte, die wir nun kennen lernen werden. Bedingungen if – else Für Bedingungen verwendet man die folgende Konstruktion: if (Bedingung) { //Bedingung ist wahr Anweisungen; } else { //Bedingung ist falsch Anweisungen; }
Machen wir uns das am besten mit Beispielen klar: boolean es_regnet; // irgendwo wird es_regnet auf true oder false gesetzt // hier testen wir nun diese Variable if(es_regnet == true) { System.out.println("Heute regnet es!"); } else { System.out.println("Heute regnet es nicht!"); }
110
Kontrollstrukturen Wenn es alternativ nichts Besonderes zu tun gibt, kann der else-Teil auch wegfallen. Außerdem dürfen die Klammernpaare {} weggelassen werden, wenn nach if bzw. else jeweils nur eine einzige Anweisung (wie im Beispiel oben) steht. Bedingungen und boolesche Ausdrücke Die auszuwertende Bedingung muss einen Wahrheitswert liefern, also wahr (true) oder falsch (false) sein. Daher war auch die zu testende Variable bisher vom Typ boolean. In der Klammer dürfen aber auch logische Ausdrücke stehen, deren Auswertung durch den Compiler einen Wahrheitswert zurückgibt: int a,b,c; // ... a, b, c erhalten irgendwo Werte zugewiesen // Auf Gleichheit testen if( a == b && a == c) System.out.println("a, b und c sind identisch.");
Bitte beachten Sie, dass ein Vergleich auf Gleichheit mit dem Operator == erfolgen muss. Wenn Sie nur ein Gleichheitszeichen tippen (ein sehr beliebter Fehler!), wird sich der Compiler beschweren. Weitere Vergleichsoperatoren sind: = ==, !=
Vergleich größer, größer gleich Vergleich kleiner, kleiner gleich Vergleich gleich, ungleich
Schauen wir uns noch ein weiteres Beispiel an, das uns gleichzeitig noch etwas über objektorientierte Programmierung lehrt. Wenn Sie eine Klasse benutzen, die von einem anderen Programmierer Kapselung implementiert wurde, informieren Sie sich meist nur über die zur Verfügung stehenden Datenelemente und Methoden. Wie diese Methoden implementiert sind, interessiert Sie nicht und soll Sie auch nicht interessieren. Man bezeichnet dieses Konzept als Kapselung oder Information Hiding. Die konkrete Implementierung einer Klasse ist für den Benutzer der Klasse unwichtig, er braucht für den korrekten Umgang mit der Klasse nur zu wissen, auf welche Datenelemente und Methoden er zugreifen kann. Damit geht aber eine besondere Verantwortung bei der Implementierung der Klasse einher. Dazu gehört auch, dass jede Methode darauf achtet, dass sie korrekt
111
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
eingesetzt wird. Schauen wir uns dazu die beiden folgenden Methoden an, die Sie bereits aus Abschnitt 4.2 kennen und die Kapitalflüsse von Sparbüchern regeln. public void einzahlen(double betrag) { kapital += betrag; } public void abheben(double betrag) { kapital -= betrag; }
In diesem einfachen Beispiel hätte prinzipiell eine Methode gereicht, der man dann einen positiven oder negativen Betrag übergibt – je nachdem, ob man einzahlen oder abheben möchte. Übersichtlicher und für den Benutzer der Klasse intuitiv leichter umzusetzen, ist aber die Vorgabe zweier Methoden mit entsprechenden Namen. Vielleicht möchte man mit dem Abheben von Geldern auch noch weitere Bearbeitungsschritte verbinden (beispielsweise wenn nur ein bestimmter Betrag abgehoben werden darf), die beim Einzahlen nicht anfallen. Auch in solchen Fällen ist die Implementierung zweier Methoden vorteilhaft. Allerdings müssen Sie dann darauf achten, dass die Methode einzahlen() nicht durch Übergabe eines negativen Betrags zum Vermindern des Einlagekapitals führt bzw. umgekehrt das Kapital beim Abheben erhöht wird. Wir fügen daher if-Bedingungen in die Methoden ein, die sicherstellen, dass nur positive Beträge bearbeitet werden. public void einzahlen(double betrag) { if(betrag < 0) System.out.println( "Nur positive Beträge erlaubt"); else kapital += betrag; } public void abheben(double betrag) { if(betrag < 0) System.out.println( "Nur positive Beträge erlaubt"); else kapital -= betrag; } Übung Wenn Sie möchten, können Sie jetzt als Übung auch vorsehen, dass ab
einer Einlage von 50.000,– DM der Zinssatz um 0,5% erhöht wird und dass nicht mehr als 3000,– DM abgehoben werden können.
112
Kontrollstrukturen
public void einzahlen(double betrag) { if(betrag= 50000 && kapital-betrag < 50000) zinssatz += 0.5; } } public void abheben(double betrag) { if(betrag 3000) System.out.println("Limit sind 3000 DM"); else kapital -= betrag; if(kapital < 50000 && kapital+betrag >= 50000) zinssatz -= 0.5; }
Lösung
Mehrfachbedingungen switch Oft ergibt sich die Situation, dass eine Variable auf mehrere Werte getestet werden soll, damit dann entsprechende Anweisungen abgearbeitet werden. char option; option = 'S'; if(option == 'A') { System.out.println("Gewählte Option Abbrechen"); } if(option == 'S') { System.out.println("Gewählte Option Speichern"); } if(option == 'L') { System.out.println("Gewählte Option Laden"); }
113
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
Eine elegantere Formulierung erlaubt in solchen Fällen die switch-Anweisung: switch(option) { case 'A': System.out.println( "Gewählte Option Abbrechen"); break; case 'S': System.out.println( "Gewählte Option Speichern"); break; case 'L': System.out.println( "Gewählte Option Laden"); break; default : System.out.println( "Ungültige Option"); break; }
Der switch-Befehl erhält als Argument die zu testende Variable, die dann mit den case-Werten verglichen wird. Wird eine Übereinstimmung gefunden, werden die entsprechenden Anweisungen abgearbeitet. Natürlich kann es auch einmal passieren, dass der Ausdruck in der switchBedingung mit keinem der case-Werte übereinstimmt. Wenn Sie möchten, können Sie solche Fälle mit dem default-Label abfangen. case-Werte müssen Konstanten sein.
Wie sieht nun die Abarbeitung der switch-Anweisung aus? Das Programm geht der Reihe nach alle case-Konstanten durch. Trifft es auf eine case-Konstante, die mit dem Wert der switch-Bedingung übereinstimmt, führt es die zugehörigen Anweisungen und die Anweisungen aller folgenden case-Blöcke aus – so lange bis es auf eine break-Anweisung trifft. Wenn Sie dies unterbinden wollen (und meist werden Sie es unterbinden wollen), beenden Sie die case-Blöcke mit break-Anweisungen, die die Ausführung der switch-Anweisung beenden. Das Programm wird dann direkt hinter der switch-Anweisung fortgesetzt. Übung Nehmen wir an, in obiger switch-Anweisung wäre die Variable option gleich 'S'. Denken Sie sich weiterhin die break-Anweisungen aus der switch-Anweisung weg. Welche Ausgaben würde die switch-Anweisung
114
Kontrollstrukturen dann erzeugen? Wenn Sie sich nicht ganz sicher sind, probieren Sie es doch einfach in einem kleinen Programm aus. Beim Vergleich der Variablen option mit case 'A' erkennt das Programm, Lösung dass keine Übereinstimmung vorliegt. Das Programm springt direkt zur nächsten case-Marke: 'S'. Hier liegt eine Übereinstimmung vor, der Anweisungblock der case-Marke wird ausgeführt. Da der Anweisungsblock nicht mit einer break-Anweisung endet, werden auch noch die Anweisungen der Label 'L' und default ausgeführt. Die gesamte Ausgabe sieht unerfreulicherweise daher folgendermaßen aus: Gewählte Option Speichern Gewählte Option Laden Ungültige Option
Viel mehr gibt es über Vergleiche nicht zu berichten und wir machen gleich weiter mit den Schleifen. Hier stehen uns zwei Konstrukte zur Verfügung: die so genannte for-Schleife und die while-Schleife. Die Schleifen for, while Wenn Sie bestimmte Anweisungen mehrmals hintereinander ausführen müssen, wobei sich lediglich einzelne Parameter nach einem bestimmten Muster verändern, bietet sich eine for-Schleife oder eine while-Schleife an. Obige Beschreibung klingt furchtbar, dabei ist es im Grunde ganz einfach. Schauen wir uns am besten wieder ein Beispiel an. int i; // for Schleife for(i = 1; i 120) && (y < 95 && y > 75))
und if (Math.sqrt(Math.pow(x,2)+Math.pow(y,2)) == 1)
4. In der 9. Übung aus Kapitel 3 haben Sie zwei Klassen für einen Flugsimulator aufgesetzt. Überlegen Sie sich jetzt, wie Sie feststellen können, wann das Flugzeug auf den Boden gestürzt oder in das Hochhaus hineingeflogen ist.
127
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
5. Worin besteht der Fehler in der folgenden Schleife? int i,j,k; i = 1; System.out.println("i \t j \t k"); while (i < 10) { j = i*i - 1; k = j*j - 1; System.out.println(i + " \t" + j + " \t" +k); }
6. Die folgende Schleife definiert zwei Schleifenvariablen. Wie sieht die Ausgabe der Schleife aus? int n,m; for(n = 0, m = 0; n < 10 && m haus1.m_position.X && meines.m_position.X < haus1.m_position.X + haus1.m_breite) && (meines.m_position.Y > haus1.m_position.Y && meines.m_position.Y < haus1.m_position.Y + haus1.m_tiefe)
129
KAPITEL
4
Programmfluss und Fehlererkennung mit Exceptions
&& (meines.m_hoehe