C-Programmierung lernen. 9783827314055, 3827314054, 3827313422, 3827317762, 3827317177, 3827317940, 3827316502


191 66 5MB

German Pages 529 Year 1998

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Programmieren lernen......Page 3
2.12 Fragen und Übungen......Page 5
5.10 Fragen und Übungen 337......Page 6
9.6 Fragen und Übungen......Page 7
S Stichwortverzeichnis......Page 8
V Vorwort......Page 9
1.1.1 Der Inhalt dieses Buchs......Page 11
1.1.2 Das Konzept......Page 14
1.1.4 Voraussetzungen......Page 15
1.1.5 Die Programmiersprachen......Page 17
1.1.8 Informationen im Internet......Page 19
1.1.9 Icons und typografische Konventionen......Page 20
1.2 Der Umgang mit der Konsole des Betriebssystems......Page 22
1.3 Lizenzvereinbarungen......Page 25
1.4 Warum individuelle Programme?......Page 27
1.5.1 Einige Worte zum Lernen......Page 29
1.5.2 Die Etappen auf dem Weg zum Profi......Page 31
1.6.1 Die Dokumentation......Page 34
1.6.2 Suchen im Internet......Page 36
1.7 Zusammenfassung......Page 44
1.8 Fragen und Übungen......Page 45
2 Erste Schritte......Page 47
2.1 Einige Begriffe zuvor......Page 48
2.2.1 Ein wichtiger Hinweis zu Kylix......Page 50
2.2.2 Erzeugen einer neuen Konsolenanwendung......Page 51
2.2.3 Der Rahmen-Quellcode einer Konsolenanwendung......Page 54
2.2.4 Das Projekt......Page 55
2.2.5 Das erste Delphi/Kylix-Programm......Page 56
2.2.6 Die Kylix Open Edition-Besonderheiten......Page 59
2.3.1 Das Programm in einer ersten Version......Page 62
2.3.2 Formatieren der Ausgabe......Page 65
2.3.3 Umgang mit Fehlern, die durch ungültige Eingaben verursacht werden......Page 66
2.4.1 Ein Projekt für eine normale Anwendung......Page 69
2.4.2 Entwurf des Formulars......Page 71
2.4.3 Einstellen der Eigenschaften......Page 74
2.4.4 Ereignisorientiertes Programmieren......Page 77
2.4.5 Programmieren der Berechnung......Page 79
2.4.7 Das Programm für den Beenden-Schalter......Page 83
2.5 Grundlagen zum Umgang mit der Delphi/Kylix-Entwicklungsumgebung......Page 84
2.6.1 Entwicklung einer Konsolenanwendung......Page 85
2.6.2 Kompilieren des Programms......Page 88
2.6.3 Starten des Programms......Page 91
2.7 Hello World in Java mit Sun ONE Studio 4......Page 92
2.7.1 Erzeugung eines neuen Projekts......Page 94
2.7.2 Anlegen einer Start-Klasse für das Projekt......Page 97
2.7.3 Programmieren und Starten des Programms......Page 103
2.8.1 Pakete......Page 105
2.8.2 Integration von externen Projekten......Page 106
2.9 Entwickeln einer Anwendung mit grafischer Oberfläche mit Sun ONE Studio 4......Page 107
2.9.1 Erzeugen des Projekts und des Startformulars......Page 108
2.9.2 Entwurf des Formulars......Page 110
2.9.3 Einstellen der Eigenschaften......Page 111
2.9.4 Programmieren......Page 112
2.9.6 Die vom Compiler erzeugten Dateien......Page 115
2.10 Die Weitergabe einer Anwendung......Page 116
2.12 Fragen und Übungen......Page 117
3.1 Wie arbeitet ein Computer?......Page 119
3.1.1 Das Grundprinzip: EVA......Page 120
3.1.2 Die CPU, Maschinensprache und Controller......Page 121
3.1.3 Das BIOS und das Betriebssystem......Page 123
3.1.5 Die Rolle des Arbeitsspeichers......Page 127
3.2.1 Bits und Bytes......Page 129
3.2.2 Zahlendarstellung im Computer......Page 130
3.2.3 Wie werden Texte gespeichert?......Page 134
3.2.5 Binäre Daten: Speichern von Bildern, Musikstücken und anderen speziellen Daten......Page 138
3.2.6 Speicheradressen und Variablen......Page 140
3.3.1 Texteditoren, Programmiereditoren, Entwicklungsumgebungen......Page 141
3.3.2 Maschinencode und Assembler......Page 142
3.3.3 Compiler......Page 143
3.3.4 CPU- und Betriebssystem-spezifische Programme......Page 144
3.3.5 Interpreter......Page 145
3.3.6 Zwischencode- und Just-In-Time-Compiler......Page 146
3.4.1 Makros......Page 150
3.4.2 Komponentenbasierte Anwendungen......Page 152
3.4.3 Verteilte Anwendungen......Page 155
3.4.5 Internetanwendungen......Page 157
3.5.1 C und C++......Page 160
3.5.2 Java......Page 161
3.5.3 Visual Basic 6......Page 162
3.5.4 Delphi und Kylix......Page 163
3.5.5 C#, J# und Visual Basic .NET......Page 164
3.5.6 JavaScript......Page 165
3.6 Algorithmen......Page 166
3.6.1 Pseudocode, Schleifen und Verzweigungen......Page 168
3.6.2 Der komplette Kaffeekochen-Algorithmus......Page 171
3.6.3 Programme im Computer: Anweisungen, Befehle, Variablen, Schleifen und Verzweigungen......Page 172
3.7 Zusammenfassung......Page 178
3.8 Fragen und Übungen......Page 179
4 Grundlagen der Programmierung......Page 181
4.1 Variablen......Page 182
4.2.1 Datentypen in einem Programm......Page 187
4.2.2 Numerische Datentypen......Page 190
4.2.3 Datentypen für Zeichen und Zeichenketten......Page 197
4.2.5 Der boolesche Datentyp......Page 199
4.2.6 Datentyp-Konvertierungen......Page 200
4.2.7 Überläufe......Page 206
4.2.8 Das Schreiben von Konstanten (Literalen)......Page 209
4.3 Strukturen......Page 212
4.4 Elementare Anweisungen......Page 213
4.4.1 Grundlagen zum Schreiben von Anweisungen......Page 215
4.4.2 Kommentare......Page 219
4.4.3 Zuweisungen und arithmetische Ausdrücke......Page 221
4.5.1 Funktions- und Klassenbibliotheken......Page 231
4.5.2 Der Aufruf von Prozeduren, Funktionen und Methoden......Page 238
4.5.3 Ein Delphi/Kylix-Beispiel......Page 241
4.6.1 Grundlagen......Page 245
4.6.2 Einfaches Debuggen mit Sun ONE Studio 4......Page 248
4.6.3 Einfaches Debuggen mit Delphi und Kylix......Page 249
4.7 Abfangen von Ausnahmen......Page 250
4.7.1 Abfangen in Delphi und Kylix......Page 251
4.7.2 Abfangen in Java......Page 255
4.8 Zusammenfassung......Page 256
4.9 Fragen und Übungen......Page 257
5 Die Strukturierung eines Programms......Page 259
5.1 Strukturierte und unstrukturierte Programme......Page 260
5.2 Vergleichsausdrücke......Page 262
5.3 Grundlagen zu Schleifen und Verzweigungen......Page 273
5.4 Schleifen......Page 276
5.5 Verzweigungen......Page 288
5.6 Funktionen und Prozeduren......Page 297
5.6.1 Einfache Funktionen......Page 298
5.6.2 Prozeduren......Page 307
5.6.3 Funktionen und Prozeduren mit Referenzargumenten......Page 310
5.6.4 Weitere Techniken, die hier nicht besprochen werden......Page 315
5.7.1 Delphi/Kylix......Page 316
5.7.2 Java......Page 321
5.7.3 Komplettierung der Java-Konsolen-Tool-Klasse......Page 325
5.8 Variablen in Modulen......Page 328
5.9 Zusammenfassung......Page 336
5.10 Fragen und Übungen......Page 337
6 Objektorientierte Programmierung......Page 339
6.1 Was ist objektorientierte Programmierung?......Page 340
6.1.1 Unterschiede zur strukturierten Programmierung......Page 341
6.1.2 Was ist denn nun ein Objekt?......Page 345
6.2.1 Die Probleme der strukturierten Programmierung......Page 347
6.2.2 Die Lösung der Probleme der strukturierten Programmierung durch die OOP......Page 350
6.2.3 Die weiteren Vorteile der OOP......Page 351
6.3.1 Klassen deklarieren......Page 356
6.3.2 Instanzen erzeugen......Page 360
6.3.3 Wie werden Objekte zerstört?......Page 367
6.4 Grundsätze zum Entwurf von Klassen......Page 370
6.5 Eine kleine Übung......Page 373
6.6 Die Referenz self bzw. this......Page 378
6.7 Private und öffentliche Elemente einer Klasse......Page 382
6.8 Überladen von Methoden......Page 386
6.9 Initialisieren von Klassen: Konstruktoren......Page 389
6.9.1 Konstruktoren in Java......Page 390
6.9.2 Konstruktoren in Delphi und Kylix......Page 392
6.10 Aufräumarbeiten: Destruktoren......Page 394
6.10.1 Destruktoren in Delphi und Kylix......Page 395
6.11 Datenkapselung......Page 396
6.11.1 Die klassische Kapselung in Java......Page 397
6.11.2 Bessere Kapselung mit Ausnahmen......Page 398
6.11.3 Moderne Kapselung......Page 401
6.12 Vererbungs-Grundlagen......Page 404
6.13 Weitere Möglichkeiten, die nicht besprochen werden......Page 406
6.15 Fragen und Übungen......Page 407
7.1 Speichern im Arbeitsspeicher......Page 409
7.1.2 Arrays......Page 410
7.1.3 Auflistungen......Page 416
7.2.1 Welche Daten werden in Dateien verwaltet?......Page 423
7.2.2 Textdateien lesen......Page 424
7.2.3 Textdateien schreiben......Page 427
7.3 Zusammenfassung......Page 428
7.4 Fragen und Übungen......Page 429
8.1 Einleitung......Page 431
8.2.1 Das Projekt und das Startformular......Page 432
8.2.2 Eine Klasse für die Personen......Page 433
8.2.3 Ein Dialog für Meldungen......Page 434
8.3 Einlesen der Daten......Page 437
8.4 Test des ersten Entwurfs......Page 442
8.5 Programmierung der eigentlichen Funktionalität des Programms......Page 443
8.5.1 Anzeigen der Personendaten......Page 445
8.5.2 Programmieren der Bewegungsschalter......Page 446
8.5.3 Speichern der Änderungen des Anwenders......Page 447
8.5.5 Speichern der Daten in der Textdatei......Page 448
8.6 Weitere interessante Features......Page 450
8.7 Zusammenfassung......Page 451
9 Daten in Datenbanken verwalten......Page 453
9.1.1 Datenbanken, Datenbanksysteme und Datenbankmanagementsysteme......Page 454
9.1.2 Objektorientierte Datenbanksysteme......Page 456
9.1.3 Relationale Datenbanksysteme......Page 457
9.1.4 SQL......Page 458
9.2.2 Anlegen einer Datenbank über den MySQL-Monitor......Page 460
9.3.1 Daten anfügen......Page 467
9.3.2 Daten abfragen......Page 469
9.3.3 Daten ändern......Page 470
9.3.4 Daten löschen......Page 471
9.4.1 Der JDBC-Treiber......Page 472
9.4.2 Treiber laden und Verbindung aufbauen......Page 473
9.4.3 Daten abfragen und bearbeiten......Page 475
9.4.4 Daten über SQL direkt anfügen, ändern und löschen......Page 481
9.5 Zusammenfassung......Page 483
9.6 Fragen und Übungen......Page 484
N Nachwort......Page 485
A.1.1 Kapitel 1......Page 487
A.1.2 Kapitel 2......Page 488
A.1.3 Kapitel 3......Page 489
A.1.4 Kapitel 4......Page 490
A.1.5 Kapitel 5......Page 492
A.1.6 Kapitel 6......Page 495
A.1.8 Kapitel 9......Page 496
A.2 Glossar......Page 498
A.3 ASCII-Tabelle ISO-8859-1......Page 509
A.4 Wichtige One Studio 4-Tastenkombinationen......Page 511
A.5 Wichtige Kylix/Delphi-Tastenkombinationen......Page 512
B......Page 515
D......Page 516
E......Page 517
I......Page 518
K......Page 519
O......Page 520
S......Page 521
U......Page 522
Z......Page 523
Ins Internet: Weitere Infos zum Buch, Downloads, etc.......Page 0
© Copyright......Page 529
Recommend Papers

C-Programmierung lernen.
 9783827314055, 3827314054, 3827313422, 3827317762, 3827317177, 3827317940, 3827316502

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

Sandini Bib

Programmieren lernen

Sandini Bib

Die Lernen-Reihe In der Lernen-Reihe des Addison-Wesley Verlages sind die folgenden Titel bereits erschienen bzw. in Vorbereitung: André Willms C-Programmierung lernen 432 Seiten, ISBN 3-8273-1405-4 André Willms C++-Programmierung lernen 408 Seiten, ISBN 3-8273-1342-2 Guido Lang, Andreas Bohne Delphi 6 lernen 432 Seiten, ISBN 3-8273-1776-2 Walter Herglotz HTML lernen 323 Seiten, ISBN 3-8273-1717-7 Judy Bishop Java lernen 636 Seiten, ISBN 3-8273-1794-0 R. Allen Wyke, Donald B. Thomas Perl 5 lernen 441 Seiten, ISBN 3-8273-1650-2 Michael Ebner SQL lernen 336 Seiten, ISBN 3-8273-1515-8 René Martin XML und VBA lernen 336 Seiten, ISBN 3-8273-1952-8 René Martin VBA mit Word 2002 lernen 393 Seiten, ISBN 3-8273-1897-1 René Martin VBA mit Office 2000 lernen 576 Seiten, ISBN 3-8273-1549-2 Dirk Abels Visual Basic 6 lernen 425 Seiten, ISBN 3-8273-1371-6 Patrizia Sabrina Prudenzi VBA mit Excel 2000 lernen 512 Seiten, ISBN 3-8273-1572-7

Sandini Bib

Jürgen Bayer

Programmieren lernen Anfangen, anwenden, verstehen

An imprint of Pearson Education München • Boston • San Francisco • Harlow, England Don Mills, Ontario • Sydney • Mexico City Madrid • Amsterdam

Sandini Bib

Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar. 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 Produkt 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 05 04 03 02 ISBN 3-8273-1951-X © 2002 by Addison Wesley Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: Korrektorat: Lektorat: Herstellung: CD-Mastering: Satz: Druck und Verarbeitung: Printed in Germany

4

Barbara Thoben, Köln Simone Meißner, Fürstenfeldbruck Christiane Auf, [email protected] Tobias Draxler, [email protected] Ulrike Hempel, [email protected] Gregor Kopietz, [email protected] mediaService, Siegen Media Print, Paderborn

Sandini Bib

I

Inhaltsverzeichnis

le

n e rn

V

Vorwort

1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8

Einführung Zum Buch Der Umgang mit der Konsole des Betriebssystems Lizenzvereinbarungen Warum individuelle Programme? Der Weg zum Profi Wie sucht ein Programmierer Informationen? Zusammenfassung Fragen und Übungen

11 11 22 25 27 29 34 44 45

2 2.1 2.2 2.3

Erste Schritte Einige Begriffe zuvor Hello World in Delphi/Kylix Eine Delphi/Kylix-Konsolenanwendung zur Berechnung eines Nettobetrags Entwicklung einer einfachen Delphi/Kylix-Anwendung mit grafischer Oberfläche Grundlagen zum Umgang mit der Delphi/KylixEntwicklungsumgebung Hello World in Java Hello World in Java mit Sun ONE Studio 4 Grundlagen zum Umgang mit Sun ONE Studio 4 Entwickeln einer Anwendung mit grafischer Oberfläche mit Sun ONE Studio 4 Die Weitergabe einer Anwendung Zusammenfassung Fragen und Übungen

47 48 50

2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11 2.12

9

62 69 84 85 92 105 107 116 117 117

5

Sandini Bib

3 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 4 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9

Basiswissen Wie arbeitet ein Computer? Wie werden Programme und Daten gespeichert? Wie werden Programme geschrieben und für den Computer übersetzt? Übersicht über die aktuellen Software-Architekturen Übersicht über die aktuellen Programmiersprachen Algorithmen Zusammenfassung Fragen und Übungen

119 119 129

Grundlagen der Programmierung Variablen Grundlagen zu Datentypen Strukturen Elementare Anweisungen Verwenden der Bibliotheken einer Programmiersprache Einfaches Debuggen Abfangen von Ausnahmen Zusammenfassung Fragen und Übungen

181 182 187 212 213

5 5.1 5.2 5.3 5.4 5.5 5.6 5.7

Die Strukturierung eines Programms Strukturierte und unstrukturierte Programme Vergleichsausdrücke Grundlagen zu Schleifen und Verzweigungen Schleifen Verzweigungen Funktionen und Prozeduren Bibliotheken: Funktionen und Prozeduren in eigenen Modulen 5.8 Variablen in Modulen 5.9 Zusammenfassung 5.10 Fragen und Übungen

6

, Q K D OW V Y H U ] HL F K Q L V

141 150 160 166 178 179

231 245 250 256 257 259 260 262 273 276 288 297 316 328 336 337

Sandini Bib

6 6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 6.10 6.11 6.12 6.13 6.14 6.15

Objektorientierte Programmierung Was ist objektorientierte Programmierung? Welche Vorteile bietet die OOP? Einfache Klassen und deren Anwendung Grundsätze zum Entwurf von Klassen Eine kleine Übung Die Referenz self bzw. this Private und öffentliche Elemente einer Klasse Überladen von Methoden Initialisieren von Klassen: Konstruktoren Aufräumarbeiten: Destruktoren Datenkapselung Vererbungs-Grundlagen Weitere Möglichkeiten, die nicht besprochen werden Zusammenfassung Fragen und Übungen

339 340 347 356 370 373 378 382 386 389 394 396 404 406 407 407

7 7.1 7.2 7.3 7.4

Daten speichern Speichern im Arbeitsspeicher Verwalten von (Text-)Dateien Zusammenfassung Fragen und Übungen

409 409 423 428 429

8 8.1 8.2 8.3 8.4 8.5

Programmieren einer Beispielanwendung Einleitung Vorbereitungen Einlesen der Daten Test des ersten Entwurfs Programmierung der eigentlichen Funktionalität des Programms 8.6 Weitere interessante Features 8.7 Zusammenfassung

431 431 432 437 442

9 Daten in Datenbanken verwalten 9.1 Was ist eine Datenbank? 9.2 Datenbankdesign light: Erzeugen der Beispieldatenbank mit MySQL 9.3 Daten mit SQL bearbeiten 9.4 Daten in Java-Programmen bearbeiten 9.5 Zusammenfassung 9.6 Fragen und Übungen

453 454

443 450 451

460 467 472 483 484

,QKDOWVYHU]HLFKQLV

7

Sandini Bib

8

N

Nachwort

485

A A.1 A.2 A.3 A.4 A.5

Anhang Lösungen zu den Fragen und Übungen Glossar ASCII-Tabelle ISO-8859-1 Wichtige One Studio 4-Tastenkombinationen Wichtige Kylix/Delphi-Tastenkombinationen

487 487 498 509 511 512

S

Stichwortverzeichnis

515

, Q K D OW V Y H U ] HL F K Q L V

Sandini Bib

V

Vorwort

le

n e rn

Hallo lieber Leser1. Sie wollen programmieren lernen!? Dann halten Sie wohl das richtige Buch in Ihren Händen. Dieses Buch vermittelt die Grundlagen der Programmierung, zeigt daneben aber auch aktuelle Wege zur Lösung von Programmier-Problemen. Trotz des Ziels (das natürlich auch ein wenig Theorie erfordert), ist das Buch nicht allzu theoretisch. Schon von Anfang an können (und sollen) Sie eigene Programme entwickeln und ausführen. Das Buch erläutert in einer verständlichen Form alle wichtigen Handwerkszeuge eines Programmierers und geht dabei einen sehr modernen Weg. Sie lernen neben den absoluten Grundlagen z. B. auch, wie Sie heutzutage Programmier-Probleme effizient lösen, wie Sie Programme entwickeln, die in Fenstern dargestellt werden, wie Sie objektorientiert programmieren und wie Sie in einem Programm auf die in einer Datenbank gespeicherten Daten zugreifen. Wenn Sie dieses Buch gelesen und die einzelnen praktischen Themen nachvollzogen haben, kennen Sie zwar (natürlich) noch nicht alle Programmier-Techniken. Sie beherrschen aber die Grundlagen und sind in der Lage, mit ein wenig zusätzlicher Recherche Programme zur Lösung der allermeisten »normalen« Probleme zu schreiben. Wollen Sie z. B. für einen Freund oder eine Freundin ein Programm zur Verwaltung deren Musik-CD-Sammlung schreiben? Das wird nach dem Durcharbeiten dieses Buchs für Sie kein Problem darstellen. Programmieren ist ein weites Feld. Deshalb stand ich bei der Konzeption dieses Buchs vor der Entscheidung, entweder nur die Kern-Themen oder auch mehr am Rande angesiedelte Themen zu behandeln. Einige dieser Rand-Themen (wie z. B. spezielle Algorithmen2) wären sicherlich 1.

Obwohl ich selbst ein ausgesprochener Feminist bin, verwende ich im Buch ausschließlich die übliche männliche Form. Immer doppelgleisig zu schreiben (nach dem Muster »der/die Leser(in)« wäre einfach zu aufwändig und würde den Lesefluss behindern.

2.

Hier finden Sie bereits die erste Erläuterung eines Fachworts: Ein Algorithmus ist die Beschreibung der einzelnen, zur Lösung eines Problems notwendigen Schritte.

9

Sandini Bib

für einige Leser sehr interessant. Hätte ich diese aber im Buch behandelt, hätte ich die zentralen Themen reduzieren müssen. Und dagegen spricht mein Stil, einzelne Themen immer so zu beschreiben, dass Sie (der Leser) darin alle wichtigen Informationen finden und über (möglichst) alle potenziellen Probleme informiert werden. Also habe ich entschieden, die Rand-Themen nicht im Buch zu behandeln. Damit Sie sich aber trotzdem darüber informieren können, finden Sie auf der Buch-CD einige zusätzliche Artikel .

-

Ich beschreibe alle Programmierthemen im Buch anhand der Programmiersprachen Object Pascal (in Form von Delphi 6 und Kylix 2) und Java. Es mag vielleicht etwas ungewöhnlich sein, dass Sie in einem Anfänger-Buch gleich zwei Programmiersprachen lernen. Aber dieses Vorgehen entspricht dem Ziel dieses Buchs, sich nicht auf die Spezialitäten einer Programmiersprache zu beschränken, sondern Ihnen die generellen und gemeinsamen Grundlagen zu vermitteln. Daneben erhalten Sie auch einen recht guten Überblick über die unterschiedlichen Konzepte zweier Programmiersprachen. Zum Anderen sind Delphi, Kylix und Java sehr aktuelle Sprachen. Schließlich können Sie auch selbst entscheiden, welche dieser Sprachen Sie zum Nachvollziehen der Beispiele und zur Vertiefung des Erlernten verwenden. Ich danke meinem Lektor Tobias Draxler für die nette und verständnisvolle Unterstützung und vor allen Dingen auch für die sehr hilfreiche konstruktive Kritik. Seinen Ideen folgend beschreibe ich im Buch u. a. auch die Programmierung unter Linux. Meinem guten Freund Alistair Firth danke ich für die Begutachtung des Manuskripts und für die hilfreichen Anregungen. Ich denke, dass er nun programmieren kann und mir in unseren gemeinsamen Projekten deshalb in Zukunft viel Arbeit abnimmt, sodass ich (noch) öfter die Möglichkeit habe, in Urlaub zu fahren. Und ich danke auch Merve und Sefa, den Nachbarskindern, die mir gezeigt haben, wie viele Spiele auf meinem SuSE Linux installiert sind und die mich fast jeden Tag aus meinem Schreiballtag herausgerissen und mich immer wieder auf neue Ideen gebracht haben. Ich wünsche Ihnen viel Spaß beim Lesen und vor allen Dingen auch beim Programmieren. Falls Sie Fragen, Anregungen oder Kritik zum Buch haben, senden Sie mir einfach eine E-Mail. Jürgen Bayer [email protected]

10

9RUZRUW

Sandini Bib

1

Einführung

le

n e rn

Sie erfahren in dieser Einführung: • was in diesem Buch enthalten ist, • welche Zielgruppe dieses Buch anvisiert und welche Voraussetzungen Sie als Leser mitbringen sollten, • wie Sie mit der Konsole Ihres Betriebssystems umgehen, • welches Betriebssystem und welche Hardware Sie besitzen sollten, • warum überhaupt individuelle Programme geschrieben werden, wo es doch so viele fertige Programme gibt, • wie im Allgemeinen der Weg aussieht, der zum Programmier-Profi führt, • wie Sie effizient lernen und • wo und wie Sie beim Programmieren Informationen, Lösungen und Hilfestellungen suchen und finden. Dieses Kapitel schafft die Voraussetzungen dafür, dass Sie die weiteren Kapitel des Buchs erfolgreich durcharbeiten können. Ich vermittle hier noch kein Programmierwissen im eigentlichen Sinn.

1.1

Zum Buch

1.1.1 Der Inhalt dieses Buchs Dieses Buch hilft Ihnen dabei, Programmieren zu lernen. Es behandelt die grundlegenden Themen, die Sie kennen müssen, um erfolgreich in die Programmierung einzusteigen. Wenn Sie das Buch gelesen (und verstanden) haben, wird es für Sie kein Problem sein, die Features einer Programmiersprache zu erforschen und damit Programme zu entwickeln.

=XP %XFK

11

Sandini Bib

Die meisten Bücher zu einzelnen Programmiersprachen setzen voraus, dass Sie die Grundlagen der Programmierung beherrschen. Kaum ein Autor hat in seinem speziellen Buch Platz für grundlegende Erläuterungen wie beispielsweise die Beschreibung der Bedeutung von Variablen bei der Programmierung. Programmiereinsteiger müssen aber auch diese Grundlagen irgendwann einmal lernen. Das ist das Thema dieses Buchs. Ich lege dabei Wert auf das aus heutiger Sicht wichtige Wissen. Dazu gehören neben den „normalen“ Grundlagen (Grundwissen, Umgang mit Compilern, Interpretern und Entwicklungsumgebungen, Programmstrukturierung etc.) auch Informationen zu einem guten Programmierstil (den viele Programmierer leider nicht beherrschen). Eher theoretische Themen wie spezielle Algorithmen zur Lösung komplexer Probleme werden im Buch nicht behandelt. Als Programmiersprachen setze ich im Buch Java und Object Pascal ein. Sie können mit beiden Sprachen unter Windows oder unter Linux programmieren. Für Java verwenden Sie dabei für beide Betriebssysteme identische Werkzeuge. Als Java-Entwicklungsumgebung setze ich im Buch Sun ONE Studio 4 ein. Object Pascal-Programme entwickeln Sie unter Windows mit Delphi 6 oder unter Linux mit Kylix 2. Delphi und Kylix unterscheiden sich kaum. Das Buch wird deshalb also nicht unübersichtlich. Der Vorteil ist, dass es für die Arbeit mit dem Buch vollkommen unerheblich ist, ob Sie nun Windows oder Linux einsetzen (obwohl bei Linux schon einige Einschränkungen auf bestimmte Distributionen bestehen, wie ich es im CD-Artikel „Installation“ beschreibe).

Die Programmiersprachen

Zum Zeitpunkt der Drucklegung dieses Buchs hat Borland gerade die neuen Versionen von Kylix und Delphi angekündigt. Es kann also sein, dass Sie mit Delphi 7 oder Kylix 3 arbeiten. Für das Buch macht das aber keinen Unterschied. Lediglich die Menüs, die Fenster und die Dialoge dieser Entwicklungsumgebungen könnten dann etwas anders aufgebaut sein, als ich es im Buch beschreibe. In Kapitel 2 lernen Sie zunächst, wie Sie Java- und Object PascalProgramme grundsätzlich entwickeln. In diesem Kapitel schreiben Sie bereits Ihre ersten Programme, ohne allerdings Näheres zu dem zu erfahren, was Sie da machen. Dieses Kapitel zeigt, wie Programme heutzutage geschrieben werden und erläutert die zwei grundsätzlichen Programm-Arten am Beispiel. Außerdem lernen Sie hier, mit den Entwicklungsumgebungen für Object Pascal und Java umzugehen.

Der Aufbau des Buchs

12

(LQIKUXQJ

Sandini Bib

In Kapitel 3 gehe ich dann auf das zum Programmieren notwendige Basiswissen ein. Ich erläutere in diesem Kapitel zunächst die grundsätzliche Arbeitsweise des Computers (aus Programmierersicht) und zeige, wie Programme aus dessen Sicht aussehen. Danach erfahren Sie, wie Daten in Programmen und in Dateien gespeichert werden. Hier lernen Sie u. a., was Bits und Bytes sind und wie Textdaten so transformiert werden, dass diese in Form von Zahlen gespeichert werden können. Danach eige ich, wie Programme geschrieben und für den Computer übersetzt werden, beschreibe die verschiedenen Arten von Programmen und beleuchte die Unterschiede zwischen den einzelnen Programmiersprachen. Schließlich erfahren Sie noch, was ein Algorithmus ist und lernen, einen solchen zu entwerfen. Nachdem Sie wissen, wie Programme grundsätzlich arbeiten und entwickelt werden, beschreibe ich in Kapitel 4 die grundlegenden Techniken, die Sie beim Programmieren tagtäglich einsetzen. Sie lernen in diesem Kapitel, welche Programmtechniken Sie zur Umsetzung eines Algorithmus in einem Programm verwenden (wobei ich noch nicht auf Schleifen und Verzweigungen eingehe). Da Sie gerade am Anfang viele Fehler machen werden, erfahren Sie in diesem Kapitel gleich auch, wie Sie logische Fehler in Ihren Programmen auf eine möglichst einfache Weise finden und wie Sie Fehler, die durch ungültige Eingaben entstehen, behandeln. In Kapitel 5 lernen Sie dann, Ihre Programme zu strukturieren. Ich zeige zunächst, wie Sie einen Programmteil wiederholt oder bedingungsabhängig ausführen können. Dann lernen Sie, Programmteile, die Sie an mehreren Stellen wiederverwenden können, in Funktionen oder Prozeduren zu schreiben. Dabei gehe ich auch noch ein wenig auf die veraltete strukturierte Programmierung ein (nur damit Sie wissen, worum es sich dabei handelt). Das Kapitel 6 behandelt die Grundprinzipien der objektorientierten Programmierung (OOP). In diesem Kapitel beschreibe ich zunächst, was die objektorientierte von der klassischen strukturierten Programmierung unterscheidet und beleuchte die Vorteile der OOP. Danach lernen Sie die Grundprinzipien der OOP kennen und erstellen Ihre ersten eigenen Klassen. Ich lege in diesem Kapitel (wie auch in Kapitel 4 und 5) viel Wert auf die Grundlagen und darauf, dass Sie wissen, warum Sie objektorientiert programmieren sollten. Die meisten Programme verwalten Daten im Arbeitsspeicher und in Dateien oder Datenbanken. Deshalb zeige ich zunächst in Kapitel 7, wie Sie Daten effektiv im Programm verwalten und wie Sie einfache Textdateien lesen und schreiben. In diesem Kapitel erfahren Sie einiges über moderne Techniken der Speicherung von Daten im Arbeitsspeicher.

=XP %XFK

13

Sandini Bib

Um ein wenig Praxis in das Buch zu bringen, erstellen Sie in Kapitel 8 ein Programm, über das Sie Adressdaten in einer Textdatei verwalten und bearbeiten können. Das Beispielprogramm setzt viele der bis zu diesem Kapitel erlernten Techniken, wie z. B. die objektorientierte Programmierung, das Strukturieren von Programmen und das Abfangen von Fehlern ein, und soll Ihnen zeigen, wie diese Techniken in der Praxis verwendet werden. In Kapitel 9 zeige ich dann, wie Sie in Java-Programmen Daten in Datenbanken verwalten. Dabei gehe ich auf die Grundlagen von Datenbanken, auf die Standard-Datenbank-Abfragesprache SQL (Structured Query Language) und auf die Verwaltung von Datenbanken in einem Programm ein. Mit diesem Kapitel schließe ich das Buch ab.

1.1.2 Das Konzept Das Konzept des Buchs ist so angelegt, dass Ihnen die einzelnen Themen grundlegend erläutert werden. Sie werden keine vertieften, speziellen Informationen finden, aber immer das wichtige Grundlagenwissen. Da Programme und die zum Programmieren verwendeten Tools leider auch manchmal etwas komplex sind, beschreibe ich immer auch die möglichen Probleme und zeige den Lösungsweg (ohne allerdings zu tief darauf einzugehen). Die behandelten Programmierthemen verdeutliche ich mit vielen möglichst einfachen Beispielen. Die Beispiele finden Sie natürlich auch auf der Buch-CD. Um das Verständnis dieser Programme nicht unnötig zu erschweren, verwende ich in den Beispielen möglichst1 keine englischen, sondern deutsche Begriffe (obwohl ich selbst lieber englische Begriffe einsetze). Jedes Kapitel enthält am Ende eine Zusammenfassung, die Sie darüber informiert, was Sie nach der Lektüre dieses Kapitels (und nach dem damit verbundenen Ausprobieren an Ihrem Computer) wissen sollten. Alle Kapitel bis auf Kapitel 1 und 8 sind zudem mit einer Fragen-/Aufgabensammlung abgeschlossen. Hier können Sie Ihr neu erworbenes Wissen testen. Die Lösungen zu den Fragen und Aufgaben finden Sie im Anhang. In einem Programmierbuch kann ich natürlich nicht verhindern, Fachvokabular einzusetzen. Ich erläutere aber möglichst jeden neuen Begriff. Begriffe, die nicht direkt mit dem jeweiligen Thema in Verbindung

1.

14

Auch wenn ich mich noch so angestrengt habe: in einigen Fällen haben sich mit Sicherheit auch englische Begriffe in die Beispiele eingemogelt

-

(LQIKUXQJ

Sandini Bib

stehen, aber Ihnen bekannt sein sollten, erkläre ich (hoffentlich immer ) mit einer Fußnote. Außerdem finden Sie im Anhang ein Glossar, das die Fachbegriffe noch einmal näher erläutert.

-

1.1.3 Die Zielgruppe Die Zielgruppe dieses Buchs sind Leser, die mit Windows oder Linux sicher umgehen können, die aber noch nie selbst programmiert haben. Sie sollten also Programme bedienen, mit Dateien umgehen (speichern, öffnen, kopieren etc.) und Programme installieren können. Ich verzichte im Buch auf grundlegende Erläuterungen zu diesen Dingen um das eigentliche Thema möglichst umfangreich behandeln zu können. In welche Richtung Sie bei der Programmierung später gehen, ist im Moment unerheblich. Ob Sie Spieleprogrammierer werden wollen, in der Forschung mit künstlicher Intelligenz experimentieren oder datenbankgestützte Anwendungen für mittelständische Unternehmen entwickeln wollen, macht für dieses Buch keinen Unterschied. Alle Programmierer müssen die Grundlagen beherrschen.

1.1.4 Voraussetzungen Was Sie wissen sollten Da dieses Buch ein Grundlagenbuch ist, setze ich keine besonderen Kenntnisse voraus. Sie sollten lediglich recht sicher mit Windows bzw. Linux umgehen können, je nachdem, welches Betriebssystem Sie einsetzen (andere Betriebssysteme werden im Buch nicht berücksichtigt). Prinzipiell reicht es aus, wenn Sie Programme starten und installieren und mit der Eingabeaufforderung (Windows) bzw. der Shell (Linux) umgehen können. Für den Fall, dass Sie diese noch nicht kennen, beschreibe ich ab Seite 22 kurz, worum es sich dabei handelt und wie Sie diese starten und benutzen. Betriebssystem- und Hardware-Voraussetzungen Die Entwicklungsumgebungen bzw. Tools für die verwendeten Programmiersprachen setzen einiges an Ihrem System voraus, das ich im Folgenden beschreibe. In der Datei links.htm finden Sie Links zu Internetseiten, in denen die Systemvoraussetzungen vom jeweiligen Hersteller umfassend beschrieben werden.

=XP %XFK

15

Sandini Bib

Wenn Sie Windows einsetzen, benötigen Sie:

Windows-Voraussetzungen

• Windows 98, Me, NT 4, 2000 oder XP, • einen Prozessor mit mindestens 500 MHz oder • einen Prozessor mit mindestens 166 MHz, wenn Sie Sun ONE Studio 4 nicht installieren, • mindestens 256 MB Arbeitsspeicher oder • mindestens 32 MB Arbeitsspeicher, wenn Sie Sun ONE Studio 4 nicht installieren, • 75 bis 160 MB freien Festplattenplatz für Delphi, • 125 MB freien Festplattenplatz für Sun ONE Studio 4 oder 70 MB für das Java-SDK, • 160 MB freien Festplattenplatz für die optionale Java-Dokumentation. Unter Linux benötigen Sie ein System mit den folgenden Mindestdaten:

Linux-Voraussetzungen

• idealerweise Red Hat Linux ab Version 7.1 oder SuSE Linux ab Version 7.2, • einen Intel-kompatiblen Prozessor mit mindestens 500 MHz oder • einen Intel-kompatiblen Prozessor mit mindestens 200 MHz, wenn Sie Sun ONE Studio 4 nicht installieren, • mindestens 256 MB Arbeitsspeicher oder • mindestens 64 MB Arbeitsspeicher, wenn Sie Sun ONE Studio 4 nicht installieren, • 110 MB freien Festplattenplatz für Kylix, • 125 MB freien Festplattenplatz für Sun ONE Studio 4 oder 75 MB für das Java-SDK, • 145 MB freien Festplattenplatz für die optionale Java-Dokumentation. Falls Ihre Rechner die Anforderungen für Sun ONE Studio 4 nicht erfüllt, sollten Sie trotzdem versuchen, diese Entwicklungsumgebung zu installieren. Eventuell ist es nun auch einmal Zeit, den Rechner ein wenig aufzurüsten. Ohne Sun ONE Studio 4 können Sie zwar die meisten Java-Beispiele des Buchs nachvollziehen, nicht aber die Java-Programme, die mit einer grafischen Oberfläche arbeiten. Linux-Besonderheiten Kylix 2 unterstützt Red Hat Linux ab Version 7.1, SuSE ab Version 7.2 und Mandrake ab Version 8.0. Diese Distributionen dürften also keine Probleme verursachen. Die meisten Kylix-Features wurden ebenfalls unter ALT Linux Master 2.0 und Caldera Open Linux 3.1.1 getestet. Kylix dürfte in diesen Linux-Distributionen wohl einigermaßen gut laufen.

16

(LQIKUXQJ

Sandini Bib

Java unterstützt offiziell laut Sun (dem Hersteller) nur Red Hat Linux 6.2 und 7.1. Java 1.4 wurde allerdings auch in den englischen Versionen von TurboLinux 6.5, Caldera 3.1, Cobalt mit Kernel 3.2 und glibc und SuSE 7.1 getestet. Sun ONE Studio 4 wurde laut Sun lediglich unter Red Hat Linux 7.2 getestet, läuft aber auch auf meinem SuSE Linux 7.3. Unter anderen Distributionen, auf denen das Java-SDK installiert werden kann, sollte Sun ONE Studio 4 allerdings auch ausgeführt werden können, da diese Entwicklungsumgebung anscheinend2 komplett in Java programmiert wurde. Ich verwende für das Buch SuSE Linux 7.3 in der deutschen Version.

1.1.5 Die Programmiersprachen Ich habe lange nach den „richtigen“ Programmiersprachen für dieses Buch gesucht, was keine leichte Aufgabe war. Damit Sie verstehen, warum ich Object Pascal und Java als optimale Sprachen gefunden habe, zeige ich den Weg auf, der zu dieser Entscheidung geführt hat. Nebenbei erfahren Sie hier auch einiges über moderne Programmiersprachen und die Qual der Wahl, vor der Sie später bei der Entwicklung eigener Anwendungen stehen werden. Eine „exotische“ Sprache wie Eiffel oder Python wollte ich nicht verwenden, es sollte schon eine Sprache sein, die von möglichst vielen Programmierern eingesetzt wird (und eine, die ich kenne ). Außerdem sollte es sich dabei um eine „echte“ Programmiersprache handeln und nicht um eine Scriptsprache (wie JavaScript). Übrig blieben die Sprachen C++, Visual Basic 6, Object Pascal (in Form von Delphi und Kylix), Visual Basic. NET, C# und Java.

-

Die ersten Entscheidungen fielen recht schnell: C++ ist zu kompliziert und zu fehlerträchtig für den Beginn. Visual Basic 6 beherrscht wichtige Konzepte wie das der objektorientierten Programmierung nicht und muss zudem gekauft werden. Object Pascal, Visual Basic .NET, C# und Java sind sehr gut geeignet, die Grundlagen der Programmierung zu lernen. Jede dieser Sprachen wäre für dieses Buch gut geeignet. Ein weiteres Kriterium zur Auswahl einer Programmiersprache war die Möglichkeit, die notwendigen Entwicklungs-Tools auf allen gängigen Windows- und Linux-Betriebssystemen installieren zu können. Das für

2.

Wenigstens ist dies für die NetBeans-Entwicklungsumgebung der Fall, die die Basis von ONE ist.

=XP %XFK

17

Sandini Bib

Visual Basic .NET und C# notwendige .NET-Framework SDK lässt sich leider nur unter Windows NT 4, Windows 2000 und Windows XP ab der Version „Professional“ installieren. Windows 95 bis Me und Windows XP Home bleiben außen vor. Ein .NET-Framework für Linux ist zurzeit zwar (im Mono-Projekt; www.go-mono.com) in der Entwicklung, aber noch lange nicht fertiggestellt. C# wäre in meinen Augen die optimale Programmiersprache für dieses Buch. Ich kann aber von Lesern, die das Programmieren erst lernen wollen, nicht verlangen, eines der professionellen Windows-Betriebssysteme zu kaufen und zu installieren. Was wohl besonders dann gilt, wenn es sich beim Leser um einen Linux-Anhänger handelt .

-

Übrig blieben also Object Pascal und Java. Die Object Pascal-Entwicklungsumgebungen Delphi und Kylix bieten einen hohen, sehr modernen Komfort bei der Entwicklung von Programmen. Eigentlich würde Object Pascal für das Buch ausreichen. Um aber auch eine Sprache zu behandeln, die anderen Konzepten folgt als Object Pascal, und die zudem sehr viel Ähnlichkeit mit C++, C# und JavaScript hat, setze ich im Buch auch Java ein. So lernen Sie gleich zwei moderne und wichtige Programmiersprachen kennen. Delphi läuft unter Windows, Kylix ist eine nahezu identische Entwicklungsumgebung für Linux. Beide Varianten arbeiten mit der Programmiersprache Object Pascal und sind zueinander kompatibel. Borland bietet für Delphi und Kylix je eine kostenlose Edition an, die Delphi Personal Edition bzw. die Kylix Open Edition. Delphi kann unter Windows ab Version 98 ausgeführt werden. Kylix läuft ohne Probleme auf verschiedenen Linux-Distributionen und mit einigen Patches auch auf anderen LinuxSystemen (siehe im Artikel „Installation“ auf der Buch-CD).

Delphi und Kylix

Über die Delphi Personal Edition und die Kylix Open Edition können Sie problemlos einfache Anwendungen erzeugen. Die kommerziellen Editionen dieser Entwicklungsumgebungen besitzen zusätzliche Features zur Erzeugung von Internet- und verteilten Anwendungen, zusätzliche Komponenten, Features zum Datenbankzugriff und natürlich noch ein Menge andere Dinge. Für das Buch reichen die kostenlosen Editionen von Delphi und Kylix aber aus. Leider fehlt diesen der Datenbankzugriff, der in professionellen Programmen eine große Rolle spielt. Den Datenbankzugriff zeige ich deshalb im Buch am Beispiel von Java. Wenn Sie die Unterschiede zwischen den einzelnen Delphi- und KylixEditionen kennen lernen wollen, lesen Sie die Feature-Vergleiche auf den Seiten www.borland.com/delphi/pdf/del6_feamatrix.pdf und www.borland.com/kylix/pdf/kyl2_feamatrix.pdf.

18

(LQIKUXQJ

Sandini Bib

Die für Java notwendigen Programme und Tools können Sie unter den verschiedensten Betriebssystemen, also auch unter allen aktuellen Windows- und Linux-Versionen, installieren. Da Java prinzipiell eine kostenfreie Programmiersprache ist, enthält die Java-Installation alles, was Sie beim Programmieren (mindestens) benötigen. Eine Entwicklungsumgebung, von denen Sie im Internet zahlreiche finden, müssen Sie aber separat installieren. Hilfreiche Informationen dazu finden Sie auf der Website von Simon Peter: www.simonpeter.com/techie/java_ide.html. Für dieses Buch verwende ich Sun ONE Studio 4, die Entwicklungsumgebung von Sun.

Java

Sie sehen, wie viele Überlegungen in die Wahl der Programmiersprache einfließen. Das ist nicht nur beim Schreiben eines Buchs der Fall, sondern oft auch in professionellen Softwareprojekten. Für Sie als Programmierer bedeutet dies, dass Sie sich nicht nur auf eine Programmiersprache konzentrieren sollten. Das Buch geht deshalb auch den Weg, gleich zwei moderne Programmiersprachen mit unterschiedlichen Konzepten zu beschreiben.

1.1.6 Die Installation Die Installation der benötigen Programme beschreibe aus Platzgründen ich nicht im Buch, sondern im Artikel „Installation“, den Sie im Ordner Dokumente auf der Buch-CD finden. Sie können diesen Artikel auch im Internet unter der Adresse www.juergen-bayer.net/buecher/programmierenlernen/artikel/installation.html abrufen.

1.1.7 Die Buch-CD Auf der beiliegenden CD finden Sie alle Beispiele des Buchs, Installationsprogramme für die im Buch eingesetzten Programme, zusätzliche Dokumente und eine HTML-Datei links.htm, die alle im Buch angesprochenen Internetadressen als Link enthält. Im Ordner Tipps finden Sie zwei hilfreiche HTML-Dateien, die wichtige Tipps und Tricks zu Delphi, Kylix und Java enthalten und die so organisiert sind, dass Sie diese Tipps und Tricks sehr schnell finden.

1.1.8 Informationen im Internet Auf meiner Website finden Sie unter der Adresse www.juergen-bayer.net/ buecher/programmierenlernen alle Artikel, die auch auf der CD gespeichert sind (Artikel im Internet abzurufen ist in meinen Augen oft einfacher als eine CD einzulegen und zu durchsuchen), weitere Informationen zum Buch, ein aktuelles Dokument mit Links zu den im Buch angesproche-

=XP %XFK

19

Sandini Bib

nen Themen und gegebenenfalls ein Erratum, in dem ich eventuelle Fehler (die ja leider immer wieder vorkommen) korrigiere und Updates beschreibe.

1.1.9 Icons und typografische Konventionen Dieses Buch verwendet einige typografische Konventionen, die dem allgemeinen Standard entsprechen, und verschiedene Icons, die Ihnen die Orientierung erleichtern sollen. Syntaxbeschreibungen Syntaxbeschreibungen3 (die im Buch eher selten vorkommen, weil das Buch mehr auf Beispielen aufbaut) erläutern, wie Sie Ihren Quellcode schreiben müssen, damit dieser korrekt übersetzt werden kann. Sie sind in der Schriftart Courier gedruckt. Das folgende Beispiel zeigt die Syntaxbeschreibung einer If-Verzweigung in Object Pascal: if Vergleichsausdruck then begin Anweisungen end;

Kursive Wörter in diesen Beschreibungen sind Platzhalter für von Ihnen anzugebende Informationen. Im obigen Beispiel müssen Sie für Vergleichsausdruck z. B. einen Vergleich und für Anweisungen eine oder mehrere Anweisungen eintragen. Das folgende Beispiel demonstriert dies: if i = 1 then begin writeln('i ist gleich 1'); end;

Was Sie an diesen kursiven Wörtern tatsächlich angeben, bleibt natürlich Ihnen überlassen. Es muss lediglich in logischer und syntaktischer Hinsicht passen.

3.

20

Als „Syntax“ werden die Regeln bezeichnet, nach denen die Worte und Zeichen einer Sprache aneinander gereiht werden müssen, damit ein Compiler oder Interpreter, der das Programm übersetzt, diese versteht.

(LQIKUXQJ

Sandini Bib

In vielen Syntaxbeschreibungen außerhalb dieses Buchs werden einzelne Elemente in eckigen Klammern dargestellt. Diese Elemente sind optional, d. h. sie können angegeben werden, müssen aber nicht. Lassen Sie das entsprechende Element weg, so wird automatisch eine Voreinstellung verwendet. Die eckigen Klammern sind dann übrigens nur Teil der Syntaxbeschreibung und werden nicht im Programmcode angegeben. Beispiel-Listings Alle Programmierthemen werden an Hand von Beispielen erläutert und vertieft. Beispiel-Listings werden in der Schriftart Courier dargestellt: 01 02 03 04 05 06

write("Geben Sie eine Zahl ein: "); readln(zahl1); write("Geben Sie eine weitere Zahl ein: "); readln(zahl2); ergebnis = zahl1 + zahl2; writeln(ergebnis);

Die einzelnen Zeilen von kompletten Beispielen werden immer numeriert. Das erleichtert den Bezug auf Zeilen, wenn Beispiele im Buch erläutert werden. Die Zahlen dürfen Sie nicht mit eingeben, wenn Sie Beispiel-Listings nachvollziehen. Exkurse Für den Fall, dass ein Begriff, der eigentlich nicht direkt zum jeweiligen Thema gehört, doch kurz beschrieben werden soll, erfolgt diese Beschreibung in Form eines Exkurses. Ein Exkurs wird wie dieser Absatz dargestellt. Typografische Konventionen im Fließtext Im normalen Text werden sprachspezifische Schlüsselwörter in der Schriftart Courier dargestellt. Wörter, die Teile der Benutzerschnittstelle, wie z. B. Menübefehle und Schalter bezeichnen, werden so dargestellt: DATEI/ÖFFNEN. Der Schrägstrich trennt die einzelnen Ebenen von Menübefehlen. Das Beispiel bezeichnet z. B. den Befehl ÖFFNEN im DATEI-Menü. Internetadressen werden folgendermaßen gekennzeichnet: www.addison-wesley.de. Tastenkappen wie (F1) stehen für Tasten und Tastenkombinationen, die Sie betätigen können, um bestimmte Aktionen zu starten.

=XP %XFK

21

Sandini Bib

Die Symbole Zur Erleichterung der Orientierung verwendet dieses Buch verschiedene Symbole für Textabschnitte mit besonderer Bedeutung. Über dieses Symbol werden Hinweise und besondere Informationen gekennzeichnet, die Sie beachten sollten. Programmiersprachen und die beim Programmieren verwendeten Komponenten bieten häufig einige Fallen, in die Sie arglos tappen können. Spezielle „Achtung“-Hinweise, die mit diesem Icon gekennzeichnet sind, machen auf diese Fallen aufmerksam und bieten natürlich – sofern möglich – gleich auch eine Lösung. Dieses Symbol kennzeichnet allgemeine Tipps und Hinweise zur Lösung bestimmter Probleme, die im Buch nicht weiter behandelt werden. An diesem „Referenz“-Symbol finden Sie Hinweise auf Webseiten, CDDateien und Bücher mit weiteren Informationen. Das Übungs-Icon kennzeichnet Fragen und Aufgaben, über die Sie Ihr Wissen testen können. Diese Übungen finden Sie immer am Ende eines Kapitels. Programm-Beispiele, die in der Programmiersprache Java verfasst sind, erkennen Sie an diesem Symbol. Dieses Symbol steht für Beispiel-Programme in der Programmiersprache Object Pascal (in Form von Delphi bzw. Kylix).

1.2

Der Umgang mit der Konsole des Betriebssystems

Windows und Linux besitzen eine Konsole, in der Sie Befehle eingeben und Programme starten können. In Windows wird diese als „Eingabeaufforderung“ bezeichnet, Linux nennt die Konsole „Shell“. Diese Konsole kennt dazu einige Befehle wie beispielsweise dir zum Auflisten des Inhalts des aktuellen Ordners und copy zum Kopieren mit Dateien. Daneben erlaubt eine Konsole aber auch das eingeschränkte Programmieren in Batch-Dateien (Windows) bzw. Shell-Skripten (Linux). Mit Hilfe solcher Programme, die in einfachen Textdateien gespeichert sind, wer-

22

(LQIKUXQJ

Sandini Bib

den meist einfache, häufig wiederkehrende Aufgaben automatisiert. Unter Linux werden viele Programme beispielsweise über Shell-Skripte installiert. Die Eingabeaufforderung starten Sie unter Windows über das Startmenü im Ordner Programme/Zubehör.

Abbildung 1.1: Die Windows-Eingabeaufforderung

Linux besitzt gleich mehrere Shells, die unterschiedliche Befehle verstehen und früher (als es in UNIX noch keine grafische Oberfläche gab) für verschiedene Zwecke verwendet wurden. In der C-Shell können Sie Shell-Skripte beispielsweise in einer der Programmiersprache C ähnlichen Syntax schreiben. Heute wird wohl überwiegend die bash4-Shell eingesetzt. Wenn Ihr Linux nicht direkt mit einer Shell, sondern mit der KDE (der heutzutage standardmäßig benutzten grafischen Oberfläche) startet, öffnen Sie eine Shell entweder über das Programm-Menü (System/Kommandozeilen), den Ausführen-Dialog ((Alt) + (F2)), indem Sie bash eingeben und (¢) betätigen oder über das Muschel-Icon in der Kontrollleiste.

Linux Shells

Diese Informationen beziehen sich auf die KDE in der Version 2.2. In anderen Versionen oder bei einer angepassten Oberfläche kann der Start einer Shell auch auf andere Weise erfolgen.

4.

bash steht für „Bourne Again Shell“, ein Wortspiel, das sich auf die alte Bourne-Shell bezieht.

'HU 8PJDQJ PLW GHU .RQVROH GHV %HWULHEVV\VWHPV

23

Sandini Bib

Abbildung 1.2: Die Linux-bash-Shell in der KDE Aktueller Ordner

In der Konsole ist immer ein Ordner aktuell. In Windows bis XP ist dies standardmäßig der Stammordner der Betriebssystempartition, in Linux und Windows startet die Konsole in Ihrem Home-Ordner. Mit dem Befehl cd Ordner können Sie (in Windows und Linux) in einen anderen Ordner wechseln. In Windows können Sie durch die Eingabe des Laufwerk-Bezeichners (c:, d: etc.) zu einem anderen Laufwerk wechseln.

Befehle

In der Konsole können Sie Befehle eingeben. Shell-Befehle unterscheiden sich je nach Konsole und Betriebssystem. Viele grundsätzliche Befehle sind aber in Windows und Linux auch identisch. In beiden Betriebssystemen können Sie beispielsweise dir eingeben, um den Inhalt des aktuellen Ordners anzeigen zu lassen. Betätigen Sie (¢), um den Befehl auszuführen. Daneben können Sie auch Programme in der Konsole starten, indem Sie einfach deren Namen eingeben und (¢) betätigen. In Windows können Sie beispielsweise durch die Eingabe von notepad den standardmäßigen Texteditor starten, in Linux können Sie zum Start eines Texteditors emacs oder kedit eingeben (um nur zwei der Texteditoren von Linux zu nennen). Umgebungsvariablen: Die Path-Variable In Windows und Linux existieren einige so genannte Umgebungsvariablen. Umgebungsvariablen sind Daten, die systemweit gelten und die über einen Namen angesprochen werden können. Viele Anwendungen wie beispielsweise Compiler fragen bestimmte Umgebungsvariablen ab, die meist der Konfiguration dienen. Umgebungsvariablen können beliebig im System über Shell-Skripte, Batch-Dateien oder System-Konfigurations-Dialoge gesetzt werden.

24

(LQIKUXQJ

Sandini Bib

Eine wichtige Umgebungsvariable ist Path (Windows) bzw. PATH (Linux). Diese Variable verwaltet die Verzeichnispfade zu wichtigen Anwendungen. Wenn Sie in einer Konsole oder über den Ausführen-Dialog des Betriebssystems (START / AUSFÜHREN bei Windows, (Alt) + (F2) bei der Linux-KDE) ein Programm aufrufen, sucht das Betriebssystem zunächst im aktuellen Ordner nach diesem Programm. Wird das Programm dort nicht gefunden, durchsucht das Betriebssystem die einzelnen, in der Path-Variablen angegebenen Pfade. Deshalb können Sie unter Windows beispielsweise einfach notepad eingeben, um den Standard-Texteditor zu starten, und unter Linux gimp, um das Standard-Grafikprogramm zu starten.

Die Path-Variable verwaltet Pfade zu wichtigen Anwendungen

In Windows können Sie den Wert der Path-Variablen abfragen, indem Sie an der Konsole Path eingeben. Eine typische Einstellung auf einem einfachen Windows 2000-System wäre z. B.: C:\WINNT\system32;C:\WINNT;C:\WINNT\System32\Wbem;

Unter Linux fragen Sie PATH über echo $PATH ab. Eine (für SuSE Linux 7.3) typische Einstellung wäre: /usr/local/bin:/usr/bin:/usr/X11R6/bin:/bin:/usr/lib/java/bin: /usr/games/bin:/usr/games:/opt/gnome/bin:/opt/kde2/bin: /opt/kde/bin:.:/opt/gnome/bin

Die meisten oder alle Linux-Programme sind in einem der in der PATHVariablen enthaltenen Ordner installiert oder werden dort über einen Link (eine Datei, die auf den originalen Speicherort des Programms verweist) repräsentiert. Deshalb können Sie unter Linux normalerweise alle Programme direkt über deren Namen aufrufen. In Windows sind per Voreinstellung nur die Systemordner im Pfad enthalten. Hier können Sie also nur Systemprogramme direkt aufrufen.

1.3

Lizenzvereinbarungen

Wenn Sie Java, Delphi oder Kylix auf Ihrem System installieren, erwerben (bzw. besitzen) Sie damit (wenn Sie diese Programmiersprachen legal erworben haben) eine Lizenz zur Benutzung. Wenn Sie nun Programme entwickeln, sollten Sie die Lizenzvereinbarungen der Hersteller beachten. Im Verlauf der Installation von Delphi und Kylix werden Ihnen diese angezeigt. Sie finden die entsprechenden Dateien aber auch in den Installationsdateien. Sie sollten diese Vereinbarungen lesen (womit ich in Bezug auf eventuelle Rechtsstreitigkeiten entlastet bin ).

-

/L]HQ]YHUHLQEDUXQJHQ

25

Sandini Bib

Besonders wichtig sind die Lizenzvereinbarungen in Bezug auf den Verkauf von Programmen, die Sie mit Java, Delphi oder Kylix entwickelt haben. Das Wichtigste habe ich für Sie zusammengefasst. Java Java ist eine freie Programmiersprache. Sie können Java-Programme an andere Personen weitergeben und auch zu kommerziellen Zwecken einsetzen. Lediglich wenn Ihre Programme spezielle Komponenten verwenden, müssen Sie deren separate Lizenzbestimmungen beachten. Informationen zur Java-Lizenzierung finden Sie im Internet an der Adresse servlet.java.sun.com/help/legal_and_licensing. Delphi Personal Edition Programme, die Sie mit der Delphi Personal Edition entwickelt haben, dürfen Sie nicht zu kommerziellen Zwecken einsetzen oder auf irgendeine Art verkaufen (auch nicht tauschen). Sie dürfen diese Programme nur für eigene, persönliche Zwecke einsetzen oder an andere Personen ohne Gegenleistung weitergeben. Für kommerzielle Programme müssen Sie eine der professionellen Delphi-Editionen kaufen. Kylix Open Edition Bei der Kylix Open Edition sieht das Ganze etwas anders aus. Für persönliche Zwecke können Sie diese Edition genau wie Delphi einsetzen. Wenn Sie aber Programme an andere Personen weitergeben wollen, muss dies unter den Bedingungen der „GNU General Public License“ (GPL) geschehen.

General Public License

Der Kernpunkt dieser öffentlichen Lizenz ist, dass Entwickler, die unter der GPL-Lizenz entwickeln, immer den Quellcode ihrer Programme mit ausliefern müssen. Andere Programmierer dürfen diesen Quellcode verändern, daraus eigene Programme erzeugen und diese sogar verkaufen (was Sie natürlich auch dürfen). Da dabei aber immer laut den Lizenzbestimmungen ein Hinweis auf den Urheber des Programms und die vorgenommenen Veränderungen in das Programm integriert werden müssen, sind Ihre Urheberrechte geschützt. Ziel dieser Lizenz ist, Betriebssysteme und Programme zu schaffen, deren Quellen frei verfügbar und für jeden zugänglich und nutzbar sind. So können sich sehr viele Programmierer an der Weiterentwicklung von Programmen beteiligen. Linux, das auch unter GPL lizenziert ist, ist ein gutes Beispiel dafür. Jeder kann den Quellcode von Linux (der entsprechend der Lizenz der Distribution beiliegt) verändern und verbessern.

26

(LQIKUXQJ

Sandini Bib

Genau das machten und machen sehr viele Programmierer. Viele dieser Änderungen werden immer wieder in den Linux-Kernel oder in LinuxDistributionen aufgenommen. Weil sehr viele Entwickler an Verbesserungen und Neuentwicklungen beteiligt sind, entwickelt sich Linux damit zu einem immer perfekteren und gleichzeitig kostenfreien (oder bei den Distributionen wenigstens sehr preisgünstigen) Betriebssystem. Kylix-Open-Edition-Programme müssen, wie alle unter GPL lizenzierten Programme, einen Hinweis auf die GPL-Lizenz enthalten. Kylix erledigt das aber automatisch für Sie. Beim Start einer mit der Kylix Open Edition erzeugten Anwendung gibt diese einen Hinweis auf die Lizenz aus. Lesen Sie die offiziellen Informationen zur GPL-Lizenz, die Sie auf der Buch-CD oder an der Adresse www.gnu.org/copyleft/gpl.html finden, um Näheres zu erfahren. Wichtig ist diese Lizenz auch für die Weitergabe von Kylix-Bibliotheks-Komponenten, die Sie in Ihren Programmen nutzen. Einige Komponenten dürfen Sie gar nicht, einige eingeschränkt und andere nur unter der Mitlieferung des Quellcodes mit dem Programm weitergeben. Lesen Sie dazu die Lizenzbestimmungen, die Sie im Kylix-Ordner in der Datei license.txt finden.

1.4

Warum individuelle Programme?

Auf dem Software-Markt können Sie eine fast unüberschaubare Menge an Standardanwendungen für alle möglichen Problemlösungen kaufen oder kostenfrei erhalten. Und in vielen Fällen reichen diese Standardanwendungen auch vollkommen aus. Wenn Sie Texte verfassen wollen und keine Textverarbeitung besitzen, werden Sie wohl kaum eine selbst programmieren (wofür Sie auch als erfahrener Programmierer wohl einige Monate benötigen würden), sondern ein passendes Programm kaufen oder als Freeware aus dem Internet downloaden. Wollen Sie CDs brennen, werden Sie die dazu notwendige Brennsoftware ebenfalls eher kaufen oder downloaden, als selbst zu programmieren. Wenn Sie ein wenig (im Internet) suchen, finden Sie zum einen ganz normale Standardanwendungen wie Microsoft Word oder StarOffice. Sie finden aber auch eine Vielzahl an Programmen, die zur Lösung kleinerer Probleme entwickelt wurden und sehr spezielle Standardanwendungen. So gibt es z. B. Finanzbuchhaltungsprogramme, die speziell auf die Bedürfnisse von Handwerksbetrieben zugeschnitten sind. Und vielleicht auch solche für Ingenieure, die auf dem Planeten Magrathea5 ar5.

Magrathea: Planet, auf dem Hyperraum-Ingenieure vor langer, langer Zeit Materie durch weiße Löcher im All heranzogen, um sie in Traumplaneten zu verwandeln. Frei nach meiner fünfteiligen Lieblings-Trilogie.

      

27

Sandini Bib

beiten. Warum existiert also trotzdem Bedarf für individuell entwickelte Programme? Eine Antwort auf diese Frage ist: Weil nahezu jeder Mensch und jede Firma eine andere Vorgehensweise bei der Bearbeitung von Problemen hat und sich wünscht, dass ein Programm genau ihren Bedürfnissen entspricht. Standardanwendungen entsprechen oft nicht diesen Anforderungen. Das beginnt schon bei der Steuerung der Anwendung, die für den einen oder anderen zu kompliziert ist, und endet noch lange nicht bei dem Wunsch, bestimmte Funktionalitäten der Anwendung in einer anderen Art und Weise auszuführen, als es mit der Anwendung möglich ist. Und vielleicht fehlen sogar Funktionalitäten, die der Anwender bzw. die Firma dringend benötigt. Standardanwendungen lassen sich oft zwar in gewissen Grenzen an die Bedürfnisse der Benutzer anpassen, für viele Anwender und Firmen reicht diese Anpassung jedoch nicht aus. Firmen arbeiten sehr häufig mit einer ganz eigenen Geschäftslogik6, die sich häufig mit Standardanwendungen nicht exakt abbilden lässt. Es gibt zwar spezielle Anwendungen (wie die der Firma SAP7), mit denen sehr viele verschiedene Geschäftsmodelle abgebildet werden können. Aber auch diese Anwendungen zwingen eine Firma, ihren Geschäftsablauf wenigstens teilweise zu ändern. Daneben sind diese mächtigen Standardanwendungen oft auch sehr komplex, so dass deren Benutzung und Anpassung schwierig ist. Firmen bevorzugen, wie normale Benutzer auch, einfach anzuwendende Programme, die möglichst genau auf die Bedürfnisse der Anwender abgestimmt sind. Und diese müssen dann eben individuell programmiert werden.

Standardanwendungen sind oft ungeeignet

Aber auch die Anpassung von Standardsoftware ist ein Betätigungsfeld für Softwareentwickler. Mit Hilfe einer in die Anwendung integrierten Programmiersprache können Sie diese häufig an individuelle Bedürfnisse anpassen. Microsoft Word, Excel und Access erlauben beispielsweise eine recht umfangreiche Programmierung über so genannte Makros, die in der Programmiersprache Visual Basic geschrieben werden.

Anpassung von Standardanwendungen

28

6.

Die Logik, mit der Firmen ihre Geschäfte abwickeln, wird als Geschäftslogik bezeichnet. Jede Firma besitzt ihre eigene Geschäftslogik. Beispielsweise werden Aufträge nur per Fax entgegengenommen, Lieferungen nur per UPS oder per Post ausgesendet, Zahlungen auch in Teilzahlungen akzeptiert und regelmäßig die Rabatte von Kunden, die 3 Monate nicht bestellt haben, um 1 % gekürzt. Diese Geschäftslogik ist in der Praxis meist recht komplex und schwer zu verstehen.

7.

SAP ist der Hersteller einer Palette von umfangreichen Anwendungen zur Abwicklung aller Geschäftsprozesse, die in einem Unternehmen auftreten. Die bekannteste Anwendung dieser Firma ist SAP R/3, die Lösungen bietet für die Auftragsabwicklung, den Einund Verkauf, das Rechnungs- und das Personalwesen.

(LQIKUXQJ

Sandini Bib

In seltenen Fällen kommt es auch einmal vor, dass es eine spezielle Anwendung noch gar nicht gibt oder dass diese einfach zu teuer ist. Dann ist auch wieder der Programmierer gefragt.

Neuentwicklung

Natürlich werden Softwareentwickler auch bei den Firmen benötigt, die Standardsoftware entwickeln. Gerade weil Betriebssysteme und die dazugehörige Software sich immer schneller weiterentwickeln, braucht man immer auch Programmierer, die Standardsoftware an die neuen Betriebssysteme anpassen und weiterentwickeln. Sie sehen also: Für Softwareentwickler gibt es genug zu tun.

1.5

Der Weg zum Profi

Mit diesem Buch haben Sie den Weg zum Profi bereits eingeschlagen. Dieser Weg ist normalerweise nicht besonders schwierig, dafür aber nicht gerade kurz. Das Erreichen der einzelnen Meilensteine kostet etwas Zeit, weil Sie dazu jeweils eine Menge lernen müssen. Damit Sie diesen Weg kennen lernen und einschätzen können, zeige ich, welche Etappen Sie zurücklegen müssen. Damit Sie diesen Weg möglichst effizient gehen können und nicht durch vielleicht falsche Lernmethoden gebremst werden, erfahren Sie zuvor, wie Sie möglichst effizient lernen.

1.5.1 Einige Worte zum Lernen Das effiziente (und damit leicht fallende) Lernen ist eine wichtige Voraussetzung auf dem Weg zum Programmierer. Ich bin kein Profi in der Theorie des Lernens. Meine eigenen Lern-Erfahrungen, Erfahrungen in vielen Seminaren und das Studium verschiedener Literatur haben mir aber gezeigt, dass es unterschiedliche Lern-Typen gibt und wie der Mensch prinzipiell lernt. Wenn Sie Ihren Typ kennen, Ihr Lernen daran anpassen, und wenn Sie wissen, wie ein Mensch prinzipiell lernt, fällt es Ihnen wahrscheinlich leichter. Frederic Vester, ein bekannter Kybernetiker, der sich neben Umweltfragen auch mit dem menschlichen Denken beschäftigt (und der u. a. die bekannten Umwelt-Strategiespiele Ökolopoly und Ecopolicy entwickelt hat), beschreibt in seinem hervorragenden Buch „Denken, Lernen, Vergessen“, dass es grundsätzlich vier verschiedene Lern-Typen gibt. Der erste Typ lernt durch Kommunikation, benötigt also immer wieder Erklärungen einer anderen Person, die idealerweise seinem Lernmuster entsprechen. Verständnisprobleme werden durch Fragen, Argumente und Gegenargumente aus der Welt geschafft. Der zweite Typ lernt mehr durch seine Augen, also durch Beobachtung und Experimente. Der dritte Typ lernt im Wesentlichen durch Anfassen und Fühlen (was zuge-

'HU :HJ ]XP 3URIL

Lern-Typen

29

Sandini Bib

gebenermaßen beim Lernen der Programmierung etwas schwierig ist). Der letzte Lern-Typ lernt eher in Form abstrakter Formeln, also rein über seinen Intellekt. 18 Sekunden Zeit

Das Lernen selbst geschieht in drei Schritten: Wenn Sie etwas erfahren, lesen oder sehen, rotiert dieser Eindruck etwa 18 Sekunden lang in Ihrem Ultrakurzzeit-Gedächtnis. Nur wenn Sie dieses neue „Wissen“ innerhalb dieser 18 Sekunden bewusst noch einmal abrufen oder mit anderem, bereits gespeichertem Wissen assoziieren, wird das Wissen in das Kurzzeit-Gedächtnis übertragen. Rufen Sie das neue Wissen nicht ab, geht es unwiederbringlich verloren. Diese Art der Speicherung schützt uns davor, in unserem täglichen Leben zu viele Eindrücke zu speichern und nach kurzer Zeit einen „Informationsüberlauf“ zu erleiden.

Kurzzeit- und

Neues Wissen bleibt für etwa 20 Minuten im Kurzzeit-Gedächtnis gespeichert. Unter normalen Umständen wird dieses Wissen dann nach diesen 20 Minuten in das Langzeit-Gedächtnis übertragen. Wissen, das im Langzeit-Gedächtnis gespeichert wurde, bleibt dort für immer oder wenigstens für eine lange Zeit erhalten (wenn es auch manchmal etwas „vergraben“ ist). Lediglich altersbedingte oder krankhafte Umstände oder ein Schock (wie bei einem Unfall) können die Übertragung in das Langzeitgedächtnis verhindern. Bei vielen älteren Menschen ist eben diese Übertragung in das Langzeitgedächtnis gestört. Diese Menschen können sich oft sehr genau an frühere Dinge erinnern, aber nicht mehr an den vorigen Tag.

Langzeitgedächtnis

In vielen Fällen reicht eine einmalige Speicherung im Gedächtnis aber nicht dazu aus, eine Thematik zu beherrschen oder sich klar und deutlich daran zu erinnern. Häufig benötigt unser Gehirn die mehrfache Aufnahme des Wissens, quasi um das Muster, das das Wissen im Gehirn erzeugt hat, durch neue Eindrücke und Assoziationen zu verstärken. Sie können sich das Ganze so vorstellen, dass ein erstes Lernen, eine erste Erfahrung ein nur schwaches Muster in das Gedächtnis prägt. Das Erinnern fällt dann oft noch schwer. Erst wenn weiteres, zu diesem Muster passendes Wissen oder weitere Assoziationen hinzukommen, wird der Abdruck verstärkt und die Erinnerung immer deutlicher. Frederic Vester liefert in seinem Buch wissenschaftliche Nachweise und Erklärungsmuster für diese Theorie, die aber hier den Rahmen sprengen würden. Interessant ist, dass die Theorie des „Einprägens“ durch die alte Redewendung „sich etwas einprägen“ bestärkt wird. Für das Lernen der Programmierung bedeutet dies in meinen Augen, dass Sie Themen, die Sie noch nicht kennen, nicht einfach aufnehmen (lesen, hören) können, sondern zwischenzeitlich (idealerweise in 18Sekunden-Intervallen) immer wieder innehalten und über das neu Gelernte kurz nachdenken (sofern es für Sie wichtig ist). Versuchen Sie, das

Programmieren lernen

30

(LQIKUXQJ

Sandini Bib

neu Gelernte mit bereits vorhandenem Wissen zu assoziieren. Damit erzeugen Sie wohl die stärksten Wissensmuster im Gedächtnis. Nach dem grundsätzlichen Lernen einer neuen Thematik sollten Sie diese dann noch auf eine andere Weise vertiefen. Ich denke, für die meisten LernTypen ist das Nachvollziehen des gelernten Stoffs am eigenen Beispiel (also quasi im Experiment) ideal. Wenn Sie beispielsweise die Grundlagen der strukturierten Programmierung lernen, sollten Sie die im Buch erlernten Konzepte über eigene Programme noch einmal nachvollziehen. Die dabei notwendige enorme Gedächtnisleistung (Sie müssen ja alles selbst nachvollziehen) reicht häufig aus, das Erlernte dauerhaft und jederzeit abrufbar zu speichern. Das Wissensmuster wird später noch weiter verstärkt, wenn Sie das Erlernte in der Praxis anwenden (und dabei neue Erfahrungen sammeln). Für andere Lern-Typen ist es wahrscheinlich effizienter, den gelernten Stoff noch einmal an Hand anderer Quellen, beispielsweise des Internets, auf eine andere Art zu erfassen oder sich schwierige Themen von einer anderen Person erläutern zu lassen. Finden Sie heraus, welcher Lern-Typ Sie sind, und das Lernen wird Ihnen leicht fallen. Ich bin übrigens ein Lern-Typ, der mit Erklärungen anderer nicht viel anfangen kann (diese verwirren mich sehr schnell und versperren damit die weitere Informationsaufnahme), der sein Grundwissen in schriftlicher Form bezieht (dabei habe ich genug Zeit und Raum für eigene Gedanken) und der dieses Wissen in relativ kurzen Abständen direkt nach dem Lesen durch eigene Experimente so vertieft, dass es dauerhaft gespeichert bleibt. Häufig lese ich lediglich ein paar Sätze, um dann stundenlang zu experimentieren. Aber es funktioniert. Wie Sie sehen.

1.5.2 Die Etappen auf dem Weg zum Profi Grundlagen lernen Als wichtige Basis der Programmierung müssen Sie die Grundlagen lernen, weswegen Sie ja dieses Buch lesen. Ich habe im Laufe meiner Dozententätigkeit so einige Teilnehmer kennen gelernt, die zwar bereits jahrelang programmierten, die aber wichtige Grundlagen einfach nicht kannten. Natürlich können auch Sie so programmieren. Sie werden dabei aber so viele Fehler machen, dass die Zeit, die Sie für die Beseitigung dieser Fehler aufbringen müssen, die relativ kurze Zeit, die Sie zum Erlernen der Grundlagen benötigen, bei weitem überwiegt. Abgesehen davon werden Ihre Programme dann sehr unübersichtlich, kryptisch programmiert, unsauber und damit langsam und nur sehr schwer wartund erweiterbar. Aber Ihnen wird das ja nicht passieren, Sie lesen schließlich dieses Buch .

-

'HU :HJ ]XP 3URIL

31

Sandini Bib

Programmiersprache(n) lernen Wenn Sie die Grundlagen der Programmierung kennen (ich vermeide bewusst das Wort „beherrschen“, die Beherrschung der Programmierung kommt erst mit der Zeit in der Praxis), sollten Sie eine oder vielleicht auch zwei Programmiersprachen lernen. Nehmen Sie sich aber nicht allzu viel vor, meist stehen Sie vor einem ziemlich großen Berg neuen Wissens, das erst erlernt werden muss. Programmierer sollten aber schon mit mehr als einer Programmiersprache umgehen können, schon allein deshalb, weil verschiedene Sprachen ganz unterschiedliche Möglichkeiten bieten. Da Sie die Grundlagen der Programmierung ja bereits kennen, können Sie diese meist innerhalb weniger Stunden auf die neue Programmiersprache anwenden. Aufwändiger ist da schon die Erforschung der Funktions- oder Klassenbibliothek einer Programmiersprache, die in der Regel sehr viele Möglichkeiten bietet. Hierbei müssen Sie oft erst die zugrunde liegende Technologie verstehen, was manchmal nicht allzu einfach ist. Die Technik, mit der Sie in Java ein Windows-Fenster mit Steuerelementen erzeugen, ist beispielsweise deutlich anders als diejenige, die Sie in Delphi oder C# verwenden. Viele moderne Programmiersprachen besitzen in diesem Bereich aber auch sehr viele Gemeinsamkeiten. Delphi, Visual Basic, J# und C# unterscheiden sich beispielsweise prinzipiell nicht allzu sehr bei der Erstellung der Oberfläche einer Anwendung (über die Steuerelemente der Klassenbibliothek).

Klassenbibliothek

Sie sollten aber nie versuchen, alle Möglichkeiten der Funktions- oder Klassenbibliothek zu erlernen. Meist ist diese Bibliothek einfach zu mächtig. Sie sollten sich lediglich wichtige Grundlagen aneignen, wissen, was prinzipiell möglich ist und wo Sie erfahren, wie das eine oder andere Feature eingesetzt wird, wenn Sie dieses benötigen. Ab Seite 34 zeige ich, wie Sie nach solchen Informationen suchen. Ideal ist, wenn Sie sich mit einem Feature erst dann tiefer gehend beschäftigen, wenn Sie dieses bei der Programmierung einsetzen müssen. Auf diese Weise lernen Sie die Anwendung eines Features meist quasi nebenbei (weil es in der Praxis geschieht) und sehr dauerhaft. Außerdem vermeiden Sie so, dass Sie den Umgang mit Features lernen, die dann, wenn Sie diese zum ersten Mal in der Praxis einsetzen wollen, hoffnungslos veraltet sind. Technologien lernen Wenn Sie Ihre bevorzugten Programmiersprache(n) kennen, sollten Sie sich mit speziellen Technologien beschäftigen. Dazu gehören Datenbankprogramme, Internetprogramme, Mehrschichten-Anwendungen

32

(LQIKUXQJ

Sandini Bib

und die Verwendung spezieller Server wie des Microsoft Index Servers (der häufig auf Webservern eingesetzt wird, um die dort verwalteten Dokumente suchbar zu machen). Wichtig dabei ist, dass Sie einzelne, zurzeit für Sie noch nicht wichtige Technologien zunächst nur grundlegend kennen, also lediglich wissen, worum es sich dabei handelt. Ein wenig hilft dieses Buch dabei (Seite 150). Erst wenn Sie eine dieser Technologien anwenden müssen, sollten Sie diese in der gebotenen Tiefe erlernen. Ansonsten laufen Sie Gefahr zu viel Wissen anzusammeln (und darüber auch zu viel Zeit zu verbringen), das Sie eventuell nie anwenden werden. Ihr(e) Beziehungspartner(in) und/oder Ihre Freunde werden es Ihnen danken … Projektierung lernen Wenn Sie kommerziell Programme entwickeln (die Sie also an Kunden verkaufen), sollten Sie schließlich noch lernen, wie Sie Projekte abwickeln. Dazu gehören der Umgang mit Kunden, das Umsetzen von Kundenanforderungen, die Erstellung eines Pflichtenhefts (das im Prinzip auflistet, was alles Bestandteil des zu entwickelnden Programms ist), die Einschätzung der benötigten Zeit, das Erkennen potenzieller Fehlerquellen und das Denken in einem weiteren Rahmen als der Kunde (um weitere Möglichkeiten und potenzielle Fehler zu erkennen). Einige dieser Punkte behandle ich im Artikel „Programmentwurf“, den Sie auf der Buch-CD finden. Das meiste Wissen wird sich aber im Laufe der Zeit bei Ihren eigenen Projekten ansammeln, bis Sie schließlich SoftwareProjekte relativ sicher angehen und vor allen Dingen auch korrekt einschätzen können. Dabei schadet es auch nicht, einmal das eine oder andere Buch zur Projektierung zu lesen. Dabei sollten Sie aber berücksichtigen, dass einige dieser Bücher sehr theoretisch sind und das Vorgehen nach den darin vorgestellten Schemata für kleinere bis mittlere Projekte oft viel zu viel Aufwand verursacht. Viel Geld verdienen Schließlich sollten Sie natürlich auch die Früchte Ihrer Lern-Arbeit ernten und viel Geld mit Ihren Programmen verdienen. Programmierer sind schließlich Allround-Spezialisten mit sehr viel Wissen. Und das will bezahlt werden.

'HU :HJ ]XP 3URIL

33

Sandini Bib

1.6

Wie sucht ein Programmierer Informationen?

Ein Programmierer kann und sollte auch gar nicht alles von dem wissen, was seine Programmiersprachen beherrschen. Programmiersprachen und die beim Programmieren verwendeten Features und Tools sind einfach zu komplex. Wenn Sie ein Feature benötigen oder dessen Anwendung erläutert haben müssen, können Sie Ihre Fragen heutzutage wunderbar über verschiedene Medien klären, wobei das Internet wohl die größte Rolle spielt. Ich zeige Ihnen nun, wie Sie dabei vorgehen können. Die Informationssuche in Büchern (die wohl enorm wichtig ist ) lasse ich hier weg, Bücher kennen Sie ja. Dass ich die Informationssuche bereits an dieser Stelle beschreibe (wo Sie doch häufig noch gar nicht wissen, wonach Sie suchen), hat eine Bedeutung: So können Sie nämlich Fragen selbst klären, die bei der Lektüre dieses Buchs eventuell offen bleiben.

-

1.6.1 Die Dokumentation Jede Programmiersprache liefert normalerweise eine Dokumentation mit. Für Java haben Sie diese z. B. bei der Installation separat installiert. Delphi und Kylix installieren die Dokumentation automatisch. In der Dokumentation werden normalerweise die Syntax und die Features der Programmiersprache beschrieben, oft aber auch allgemeine Vorgehensweisen (wie beispielsweise Informationen zur Gestaltung der Oberfläche einer Anwendung). Meine Erfahrungen mit diversen Dokumentationen haben mir gezeigt, dass Sie meist mit den allgemeinen (Grundlagen-)Themen einer Dokumentation nicht viel anfangen können, weil diese zu komplex sind, zu viel Eigenwerbung beinhalten und zu unkritisch sind. Lesen Sie lieber ein Buch zur jeweiligen Programmiersprache, wenn Sie Grundlagenwissen benötigen. Das ist meist effizienter und macht auch mehr Spaß. Suchen in der Dokumentation Wichtig ist die Dokumentation aber für das Nachschlagen der Syntax der verwendeten Features und der Möglichkeiten der Programmiersprache. Wenn Sie das Feature, nach dem Sie suchen, bereits kennen, können Sie normalerweise direkt im Index der Dokumentation danach suchen. In Delphi öffnen Sie die Dokumentation dazu über den Befehl DELPHI HELP im HELP-Menü, in Kylix wählen Sie den Befehl KYLIX-HILFE im HILFE-Menü. Im Delphi-Hilfethemen-Fenster können Sie direkt zum Index wechseln und dort nach einem Bezeichner suchen. Abbildung 1.3 zeigt die Suche nach einer Hilfe zur Writeln-Prozedur.

34

(LQIKUXQJ

Sandini Bib

Abbildung 1.3: Suche im Index der Delphi-Hilfe

In Kylix müssen Sie im zuerst erscheinenden Hilfefenster zunächst auf HELP TOPICS klicken, um das Hilfethemen-Fenster anzuzeigen (das dann ähnlich aussieht wie das von Delphi). Für Java starten Sie die Dokumentation über die Datei index.html in dem Ordner, in dem Sie die Hilfe entpackt haben. Diese Startseite bietet Links zu allen verfügbaren Dokumentationen. Die für Programmierer wohl wichtigste Dokumentation, die „Java 2 ... API Specification“, die die Bibliothek dieser Sprache beschreibt, können Sie auch direkt über die Datei index.html im Ordner api öffnen. Leider können Sie die HTML-Version der Java-Dokumentationen nicht komfortabel durchsuchen. Sie müssen sich zum benötigten Thema durchklicken. Suchen können Sie online (im Internet) über die Adresse java.sun.com/j2se/1.4/search.html. Windows-Benutzer können aber an der Adresse www.confluent.fr/javadoc/jdk14e.html eine Windows-Hilfe-Version der Java-Dokumentation downloaden (immerhin 21 MB). Diese können Sie dann wie bei der Delphi-Hilfe über den Index durchsuchen. Kontextsensitive Hilfe in Delphi und Kylix In Delphi/Kylix können Sie auch einfach den Eingabecursor im Quellcode auf einen Bezeichner setzen und (F1) betätigen. Die Entwicklungs-

:LH VXFKW HLQ 3URJUDPPLHUHU ,QIRUPDWLRQHQ"

35

Sandini Bib

umgebung öffnet die Hilfe dann kontextsensitiv, also mit Informationen zum aktuellen Kontext (zum Bezeichner, auf dem der Cursor steht). Stellen Sie den Cursor z. B. auf Writeln, wird die Hilfe direkt mit der Beschreibung dieser Prozedur geöffnet. Die kontextsensitive Hilfe funktioniert auch auf Formularen (siehe Seite 71). Selektieren Sie beispielsweise eine Textbox und betätigen Sie (F1), zeigt die Hilfe die Beschreibung dieses Steuerelements an (Abbildung 1.4).

Abbildung 1.4: Delphis Hilfe zum Edit-Steuerelement

Wie Sie im Laufe der Zeit noch erkennen werden, sind gerade bei Steuerelementen deren Eigenschaften sehr wichtig. Über den Verweis PROPERTIES bzw. EIGENSCHAFTEN können Sie diese in einer Liste anzeigen lassen. Über einen Klick auf einen Eintrag in der Liste öffnen Sie dann eine Hilfe zur jeweiligen Eigenschaft. Wichtig sind daneben auch die Methoden und Ereignisse (Events), aber dazu erfahren Sie mehr in den folgenden Kapiteln.

1.6.2 Suchen im Internet Im Internet finden Sie Unmengen von Informationen zu allen möglichen Programmiersprachen. Sie müssen nur an den richtigen Stellen suchen.

36

(LQIKUXQJ

Sandini Bib

Suchen von Webseiten mit Google Wahrscheinlich kennen Sie die Suche nach Webseiten über eine der vielen Suchmaschinen bereits. Für den Fall, dass dies noch nicht der Fall ist, zeige ich hier kurz, wie Sie damit umgehen. Zum Suchen von Webseiten können Sie unter einer Vielzahl von Suchmaschinen wählen. Ich gehe hier allerdings nur auf Google (www.google.de) ein, eine der in meinen Augen besten Suchmaschinen. Auf der Google-Suchseite können Sie recht komplexe Suchbegriffe eingeben (die Ihnen über den Link SUCHTIPPS erläutert werden). In den meisten Fällen reicht eine einfache Suche nach einem oder mehreren Suchbegriffen jedoch aus. In Abbildung 1.5 suche ich nach einer Möglichkeit, in Java zu drucken.

Abbildung 1.5: Suche nach Webseiten über Google

Das Ergebnis dieser Suche ist schon viel versprechend (Abbildung 1.6).

:LH VXFKW HLQ 3URJUDPPLHUHU ,QIRUPDWLRQHQ"

37

Sandini Bib

Abbildung 1.6: Ergebnis einer Google-Suche nach „java drucken“

Da das Internet eine fast unendlich große Menge an Informationen bietet, finden Sie in vielen Fällen natürlich auch Informationen, mit denen Sie nichts anfangen können. Im Suchergebnis sind häufig auch Werbeseiten verschiedener Firmen zur finden (die in der Regel für unseren Zweck uninteressant sind). Wichtig ist, dass Sie möglichst viele eindeutige Suchbegriffe angeben. Da weitaus mehr Informationen in englischer als in deutscher Sprache zu finden sind, sollten Sie auf jeden Fall auch englische Begriffe ausprobieren. Meine Suche zum Drucken in Java führte auf jeden Fall recht schnell zum Erfolg (ich wusste vorher wirklich nicht, wie es geht, jetzt weiß ich es ). Der erste im Ergebnis (Abbildung 1.6) dargestellte Artikel erklärt das Ganze sehr gut. Sie haben natürlich nicht immer so viel Glück und finden die Lösung Ihres Problems auf Anhieb. Manchmal müssen Sie ein wenig im Ergebnis der Suche „surfen“, um die benötigten Informationen zu finden, und manchmal müssen Sie auch verschiedene Suchbegriffe ausprobieren.

-

Wenn Sie die Links der Google-Ergebnisseite (und auch alle anderen Links auf irgendwelchen Seiten) mit der rechten Maustaste anklicken, können Sie in den Standard-Browsern im Kontextmenü den Befehl IN NEUEM FENSTER ÖFFNEN (oder ähnlich) wählen, um die Webseite, die der jeweilige Link referenziert, in einem separaten Fenster zu öffnen. So bleibt die Suchergebnisseite erhalten, sodass Sie ohne Probleme auch andere Webseiten besuchen können.

38

(LQIKUXQJ

Sandini Bib

Newsgroups Newsgroups sind für Programmierer eine sehr wichtige Informationsquelle. Im Internet existieren sehr viele Newsgroups, die sich immer nur mit einem speziellen Thema beschäftigen. Die aktuellen Beiträge einer Newsgroup können Sie in einem normalen Mail-Programm ähnlich einer E-Mail lesen. Genauso einfach können Sie eine E-Mail an die Newsgroup senden, um selbst etwas zum Thema beizutragen. Die meisten ursprünglichen Beiträge einer Newsgroup sind allerdings Fragen. Auf diese Fragen antworten dann häufig irgendwelche netten Menschen und lösen damit in vielen Fällen die Probleme des Fragenden. Manchmal werden aber auch bestimmte Themen in einer Newsgroup diskutiert. Ein Beitrag löst dann häufig eine Kette von Antworten und neuen Beiträgen aus, die sich auf den ursprünglichen Beitrag oder auf eine der zugehörigen Antworten beziehen. Diese Kette wird als Thread (Faden) bezeichnet. Ich kann an dieser Stelle nicht auf die oft unterschiedliche Konfiguration eines Mail-Programms zur Darstellung der Beiträge einer Newsgroup eingehen. Mail-Programme sind mittlerweile Bestandteil jedes Browsers. In den meisten Fällen müssen Sie ein neues Konto einrichten, das auf einen Newsserver verweist. Dazu können Sie die NewsserverAdressen msnews.microsoft.com (eher Microsoft-orientierte Themen) und news.t-online.de (eher allgemeine Themen) einsetzen. Diese Newsserver verwalten teilweise dieselben Newsgroups (und gleichen sich dabei auf eine magische Weise irgendwie gegeneinander ab). Nun müssen Sie nur noch eine oder mehrere Newsgroups „abonnieren“, was aber lediglich bedeutet, dass Sie die Beiträge dieser Newsgroup in Ihrem Mail-Programm anzeigen wollen. Wie das geht, hängt wiederum von Ihrem Mail-Programm ab. In Microsoft Outlook Express wählen Sie dazu den Befehl NEWSGROUPS im EXTRAS-Menü.

Abbildung 1.7: Abonnieren von Newsgroups in Outlook Express

:LH VXFKW HLQ 3URJUDPPLHUHU ,QIRUPDWLRQHQ"

39

Sandini Bib

Falls Ihr Mail-Programm die Möglichkeit bietet, die angezeigten Newsgroups einzuschränken (wie ich es in Abbildung 1.7 gemacht habe), sollten Sie das auch nutzen. Etwas problematisch ist dabei, dass oft sehr viele Newsgroups existieren. Am Namen der Newsgroup können Sie aber meist erkennen, welches Thema die Newsgroup hat und in welcher Sprache dort gemailt wird. Im nächsten Abschnitt zeige ich, wie Sie nach Newsgroup-Beiträgen flexibel suchen. Im Suchergebnis sehen Sie dann auch die Namen der Newsgroups und können so recht einfach feststellen, welche Sie abonnieren sollten. Die Beiträge der abonnierten Newsgroups können Sie dann wie normale Mails lesen (Abbildung 1.8).

Abbildung 1.8: Anzeige der Beiträge einer Newsgroup in Outlook Express

Beachten Sie, dass ein Newsreader-Programm beim ersten Download von Beiträgen oft nur die aktuellsten vom Newsserver herunterlädt. Outlook Express lädt z. B. nur immer die letzten 300 Kopfzeilen der auf dem Newsserver gespeicherten Beiträge. Über spezielle Befehle (bei Outlook Express ist das der Befehl WEITERE 300 KOPFZEILEN ABRUFEN im EXTRAS-Menü) können Sie aber auch weitere, ältere Beiträge in die Liste aufnehmen. Wie Sie Abbildung 1.8 entnehmen können, antworten manchmal sehr viele Programmierer auf eine Frage (was jedoch nicht unbedingt immer der Fall ist, manchmal gibt es auch keine Antworten auf einen Beitrag). Sie können nun selbst eine Frage oder einen Beitrag in die Newsgroup mailen, indem Sie einfach eine neue E-Mail an die Adresse der News-

40

(LQIKUXQJ

Sandini Bib

group senden (die vom Mail-Programm normalerweise automatisch eingetragen wird). Wenn Sie einer Newsgroup eine Mail senden, beachten Sie die Regeln, die sich im Internet für die Newsgroup-Kommunikation entwickelt haben. Dazu gehört neben einem meist lockeren, sachlichen, aber freundlichen Umgangston, dass Sie Ihre Beiträge beziehungsweise Fragen immer nur an eine Newsgroup senden, die sich auch mit einem passenden Thema beschäftigt. Senden Sie z. B. keine Frage zum Drucken unter Java an eine Newsgroup, deren Thema Datenbanken sind. Falls Sie das trotzdem machen, erhalten Sie nur in den seltensten Fällen eine Antwort, und die ist manchmal ziemlich rüde. Lesen Sie einfach einige Beiträge der Newsgroup, um zu erfahren, auf welche Weise die Leute dort miteinander kommunizieren. Achten Sie auch darauf, dass Ihre Beiträge nicht zu lang werden, was besonders dann gilt, wenn es sich um eine Frage handelt. Nur wenige Leute haben Zeit und Lust, lange Fragen zu lesen, und Sie erhalten in diesem Fall häufig einfach keine Antwort. Suche nach Newsgroup-Beiträgen Das Durchgehen der Beiträge einer Newsgroup im Mail-Programm führt in den seltensten Fällen zur Lösung eines Problems. Bevor Sie jedoch eine Frage in die Newsgroup mailen, sollten Sie zunächst nach einem Beitrag suchen, der Ihr Problem vielleicht löst. Google archiviert alle Newsgroup-Beiträge der wichtigsten Newsgroups und ermöglicht über die Adresse groups.google.com/advanced_group_search sehr komfortabel in diesem Archiv zu suchen. Hier können Sie nach Stichworten suchen und dabei sogar die Suche auf bestimmte Themen, Autoren und Newsgroups einschränken. Abbildung 1.9 zeigt eine Suche nach der Möglichkeit einer Konsolen-Eingabe in Java-Programmen (was leider nicht allzu einfach ist).

:LH VXFKW HLQ 3URJUDPPLHUHU ,QIRUPDWLRQHQ"

41

Sandini Bib

Abbildung 1.9: Suche bei der Newsgroup-Archivsuche von Google

Da die weitaus meisten Newsgroup-Beiträge in englischer Sprache verfasst sind, sollten Sie möglichst auch immer mit englischen Begriffen suchen. Wie bei der normalen Google-Suche können Sie auch hier mehrere Suchbegriffe eingeben. Eine Besonderheit ist die Eingabe im Feld NEWSGROUP. Hier können Sie den kompletten oder auch nur einen teilweisen Namen von einer oder mehreren Newsgroups eingeben, um die Suche auf diese Newsgroups einzuschränken. Mehrere Namen trennen Sie einfach durch Semikola. Wenn Sie einen Namen nur teilweise eingeben wollen, verwenden Sie dazu wie in Abbildung 1.9 den Platzhalter „*“. Das Beispiel sucht in allen Newsgroups, die den Begriff „Java“ im Namen tragen (also in allen Java-Newsgroups). Das Ergebnis wird wieder in einer übersichtlichen Liste dargestellt (Abbildung 1.10).

42

(LQIKUXQJ

Sandini Bib

Abbildung 1.10: Ergebnis einer Google-Newsgroup-Suche nach einer Konsolen-Eingabe in Java-Programmen

Die Beiträge, denen ein „Re:“ vorangestellt ist, sind übrigens Antworten auf vorherige Beiträge. Das sind oft die für Sie interessantesten Beiträge, weil es sich meist um Antworten auf Fragen handelt. Wie bei der normalen Google-Suche müssen Sie auch hier oft ein wenig surfen, um zur Lösung Ihres Problems zu gelangen. Beachten Sie dabei, dass nicht unbedingt jeder, der eine Antwort auf eine Frage schreibt, auch eine gute Lösung bietet. Manchmal müssen Sie auch in anderen Beiträgen nachschauen, um eine möglichst optimale Lösung zu finden. Wenn Sie einen interessanten Beitrag geöffnet haben, empfiehlt sich eigentlich immer, den Verweis COMPLETE TREAD (X ARTICLES) oben im Dokument anzuklicken. Sie sehen dann den gesamten Thread („Faden“), also den ursprünglichen Beitrag und alle Antworten darauf. In diesen Beiträgen finden Sie häufig eine Lösung.

:LH VXFKW HLQ 3URJUDPPLHUHU ,QIRUPDWLRQHQ"

Der Thread

43

Sandini Bib

Abbildung 1.11: Beitrag einer Newsgroup inklusive Thread

1.7

Zusammenfassung

In diesem Kapitel haben Sie zunächst erfahren, was in diesem Buch behandelt wird, wie Sie mit der Konsole des Betriebssystems umgehen und wie Sie die im Buch verwendeten Programmiersprachen installieren. Daneben wissen Sie, warum Sie dieses Buch überhaupt lesen (also warum Firmen und Anwender immer wieder die Dienste von Programmierern in Anspruch nehmen oder selbst programmieren müssen). Dabei haben Sie den Unterschied zwischen der Erweiterung von Standardanwendungen und der Neuprogrammierung kennen gelernt. Sie wissen nun, wie der Weg zum Profi-Programmierer prinzipiell aussieht und welche Meilensteine Sie auf diesem Weg erreichen müssen. Dabei haben Sie gleich noch erfahren, wie Sie möglichst effizient lernen. Für den Fall, dass Sie ein Feature, das Sie benötigen, doch noch nicht kennen (was auch bei erfahrenen Programmierern tagtäglich vorkommt), wissen Sie nun auch, wie Sie erfolgreich in der Dokumentation Ihrer Programmiersprache und im Internet nach Informationen suchen. In diesem Zusammenhang können Sie nun auch Beiträge einer Newsgroup lesen und eigene Beiträge verfassen (wenn Sie ein wenig geübt haben ...).

44

(LQIKUXQJ

Sandini Bib

1.8

Fragen und Übungen

1. Warum besteht häufig der Bedarf, Standardanwendungen durch in-

tegrierte Programme zu erweitern? 2. Sie haben das Problem, eine Datenbank in Java ansprechen zu müs-

sen. Sie kennen Datenbanken, wissen aber nicht, welche Komponenten Sie einsetzen müssen und wie Sie diese programmieren. Wie finden Sie die benötigten Informationen?

  

45

Sandini Bib

Sandini Bib

2

Erste Schritte

le

n e rn

Sie lernen in diesem Kapitel: • wie Sie mit Delphi oder Kylix eine Konsolenanwendung entwickeln, • wie Sie mit Delphi oder Kylix eine einfache Anwendung mit grafischer Oberfläche entwickeln, • wie Sie mit einem einfachen Editor ein Java-Konsolenprogramm schreiben, • wie Sie dieses Programm kompilieren (für den Computer übersetzen) und Kompilierfehler auswerten, • wie Sie ein Java-Programm direkt über den Java-Interpreter starten und • wie Sie Sun ONE Studio 4 einsetzen, um einfache Java-Anwendungen zu entwickeln. Dieses Kapitel soll Ihnen vermitteln, wie Programme prinzipiell geschrieben, übersetzt und ausgeführt werden, und den Umgang mit den Delphi/Kylix- und Java-Werkzeugen erläutern. Es zeigt dabei zwei unterschiedliche Wege. Unter Delphi, Kylix und Sun One Studio 4 entwickeln Sie einige Beispiel-Programme mit Hilfe der Entwicklungsumgebung. Entwicklungsumgebungen liefern einfach zu viele Features, als dass Sie diese heutzutage ignorieren könnten. Außerdem können Delphi- und Kylix-Programme prinzipiell gar nicht ohne die Entwicklungsumgebung erstellt werden. Damit Sie aber auch lernen, ein Programm in einem einfachen Editor zu schreiben und „von Hand“ zu kompilieren, beschreibe ich im Java-Abschnitt auch, wie Sie den JavaCompiler (der Ihren Quellcode in ausführbare Programme übersetzt) direkt nutzen. Sie entwerfen in diesem Kapitel jeweils zuerst eine einfache Konsolenanwendung. Diese Art Anwendung ist noch recht einfach zu verstehen. Dabei lernen Sie bereits den grundsätzlichen Umgang mit der Entwicklungsumgebung bzw. mit dem Compiler. Sie erfahren aber auch, wie Sie

47

Sandini Bib

einfache Anwendungen mit einer grafischen Oberfläche entwickeln. Dieses Thema ist bereits etwas komplexer und setzt einiges an Wissen voraus, das ich eigentlich erst in späteren Kapiteln vermittle. Sehen Sie dieses Kapitel als eine Art Workshop, der Ihnen zeigt, wie es geht, und wenden Sie das Gelernte in den späteren Kapiteln an, wobei Sie Ihr Wissen dann natürlich noch vertiefen.

2.1

Einige Begriffe zuvor

Bevor Sie beginnen zu programmieren, sollten Sie einige der in diesem Kapitel verwendeten Begriffe kennen, die ich im weiteren Verlauf nicht weiter erläutere: • Quellcode: Als Quellcode (auch: Quelltext) wird ein in Textform vorliegendes ursprüngliches Programm bezeichnet. Wenn Sie ein Programm entwickeln, schreiben Sie zunächst den Quellcode. Dieser wird dann später von einem Compiler oder Interpreter in ein ausführbares Programm übersetzt. • Compiler: Ein Compiler übersetzt einen Quellcode in ein ausführbares Programm und speichert dieses in eine Datei. Ein so übersetztes Programm können Sie meist direkt über das Betriebssystem ausführen. Bei einigen Sprachen wie Java werden vom Compiler übersetzte Programme aber auch über spezielle Anwendungen ausgeführt, wie Sie es noch ab Seite 91 näher erfahren. • Interpreter: Ein Interpreter übersetzt den Quellcode eines Programms, ähnlich wie ein Compiler. Ein Interpreter speichert das Ergebnis aber nicht in eine Datei, sondern führt den übersetzten Code gleich aus. Zur Ausführung von Programmen, die interpretiert werden, benötigen Sie bei einfachen Interpretern den Quellcode. Einige Programmiersprachen wie Java verwenden auch ein Zwischending: Ein Compiler übersetzt den Quellcode, allerdings nicht in ausführbaren Code, sondern in einen speziellen Code. Ein Interpreter übersetzt diesen Code dann in ausführbaren Programmcode. • Syntax: Die Regeln, die eine Programmiersprache zur Erstellung von Programmen bestimmt, werden als Syntax bezeichnet. Java legt beispielsweise fest, dass Anweisungen mit einem Semikolon abgeschlossen und dass mehrere zusammengehörige Anweisungen in geschweifte Klammern eingefügt werden müssen. Die Regeln der im Buch verwendeten Programmiersprachen lernen Sie hauptsächlich in Kapitel 4 und 5. Im vorliegenden Kapitel gehe ich nur grundlegend auf die Syntax von Java und Object Pascal ein. • Debuggen: Beim Programmieren macht jeder Programmierer Fehler. Viele Programme sind einfach zu komplex, als dass das Programm

48

   

Sandini Bib

beim ersten Versuch fehlerfrei laufen kann. Sie kennen wohl alle irgendwelche Windows- oder Linux-Programme, die Fehler aufweisen. Diese Fehler werden häufig erst in einer späteren Version des Programms beseitigt. Ziel eines jeden Programmierers ist es natürlich, schon in der ersten Version fehlerfreie Programme zu erzeugen. Deshalb müssen Fehler gesucht und beseitigt werden. Dieses Suchen von Fehlern in einem Programm wird als Debugging bezeichnet. Viele Progammiersprachen stellen dem Programmierer dazu spezielle Werkzeuge, die so genannten Debugger, zur Verfügung. Auf das Debugging gehe ich grundlegend in Kapitel 4 ein. Die Bezeichnung „Debugging“ hat eine Geschichte. Diese Geschichte kursiert in verschiedenen Varianten. Meine Variante basiert auf Informationen der US Navy (www.history.navy.mil/photos/pers-us/uspers-h/ g-hoppr.htm und www.hopper.navy.mil/grace/grace.htm): Grace Murray Hopper, damals Lieutenant und später Admiral der US Naval Reserve (USNR), war 1945 hauptsächlich an der Weiterentwicklung eines der ersten Computer, des Mark II, beteiligt. Bei der Suche nach einem Fehler im Programm (das damals noch über Schalttafeln „geschrieben“ wurde) fand sie eine tote Motte, die einige Anschlussstellen eines Relais elektrisch überbrückte und damit den Fehler auslöste. In das Arbeitsprotokoll schrieb sie, dass sie den ersten Fall eines „Bug“ gefunden hatte. Bug ist die englische Bezeichnung für Käfer. Die Beseitigung der Motte bezeichnete sie dann noch als „debugging“. Ein Photo dieses Protokolls finden Sie unter www.history.navy.mil/photos/images/ h96000/h96566k.jpg.

Grace M. Hopper

Grace M. Hopper war übrigens eine der Pionierinnen der Computerund Programmentwicklung. Sie entwickelte den ersten Compiler und war maßgeblich an der Entwicklung von COBOL beteiligt, der Programmiersprache, in der in den ersten Jahren des Computers die meisten Programme entwickelt wurden. Deshalb wird Grace M. Hopper auch oft für das Jahr-2000-Problem verantwortlich gemacht. Die COBOLEntwickler entschieden damals nämlich, dass zur Speicherung des Jahresteils eines Datums zwei Stellen ausreichen. Und das wurde wohl von vielen Entwicklern einfach übernommen. Das Ganze ist irgendwie witzig: Grace M. Hopper fand den ersten Bug und war gleichzeitig mitverantwortlich für den größten Bug der Geschichte.

   

49

Sandini Bib

2.2

Hello World in Delphi/Kylix

Das erste Programm, das Sie entwickeln, ist eine einfache Konsolenanwendung, die den Text „Hello World“ ausgibt. Die Ausgabe von Hello World ist bei Programmier-Anfängern ein alter Brauch. Sie sollten sich an diesen Brauch halten. Über die Begrüßung der Welt vertreiben Sie wahrscheinlich den Fehlerteufel aus Ihren Programmen, der ansonsten über Nacht sehr schwer zu findende Fehler in Ihre Programme einbaut. Starten Sie zunächst Delphi bzw. Kylix. Delphi finden Sie unter Windows im Startmenü. Unter Linux rufen Sie zum Start von Kylix das Shell-Skript startkylix auf. Bevor Sie beginnen zu programmieren, möchte ich Ihnen den folgenden Hinweis ans Herz legen: Wenn Sie ein Delphi- oder Kylix-Projekt oder eine Datei aus diesem Projekt an eine andere Stelle kopieren wollen (z. B. als Sicherheitskopie auf eine ZIP-Disk), verwenden Sie dazu nicht die Speichern unter-Funktion der Entwicklungsumgebung. Sie bringen damit Ihr Projekt durcheinander, weil die Entwicklungsumgebung dann den neuen Speicherort der Dateien als Basis für das Projekt verwendet. Kopieren Sie Dateien einfach über die entsprechenden Tools Ihres Betriebssystems. Die auf den folgenden Seiten immer wieder genannten Tastenkombinationen zum Umgang mit der Entwicklungsumgebung finden Sie im Anhang in einer übersichtlichen Tabelle.

2.2.1 Ein wichtiger Hinweis zu Kylix Wenn Sie unter Linux mit der KDE arbeiten, müssen Sie beachten, dass diese einige Tastenkombinationen mit besonderen Funktionen belegt. Über (Strg) (F1) bis (Strg) (F12) können Sie z. B. zu einem der Arbeitsbereiche wechseln. Leider handelt es sich dabei häufig um Tastenkombinationen, die auch in Kylix verwendet werden. Die KDE hat aber immer Vorrang. Wenn Sie z. B. in Kylix (Strg) (F9) betätigen um ein Programm zu kompilieren, wechselt die KDE stattdessen zum Arbeitsbereich 9. Dieses Verhalten ist schon sehr ärgerlich, weil ein vernünftiges Arbeiten mit Kylix ohne die von der KDE belegten Tastenkombinationen nicht möglich ist.

50

   

Sandini Bib

Sie können die Tastenzuordnungen der KDE aber auch anpassen. Rufen Sie dazu den Befehl TASTENZUORDNUNG im KDE-Menü EINSTELLUNGEN/ERSCHEINUNGSBILD auf. Entfernen Sie alle Tastenkombinationen, die Sie unter Kylix benötigen oder legen Sie diese auf andere Kombinationen um. Speichern Sie Ihre Einstellungen idealerweise als separates Schema, damit Sie später ohne Probleme zwischen dem KDE-Standard und Ihren speziellen Kylix-Einstellungen wechseln können. Die für Kylix wichtigen Tastenkombinationen finden Sie im Anhang. Im GNOME-Desktop trat das Problem übrigens nicht auf.

2.2.2 Erzeugen einer neuen Konsolenanwendung Delphi und Kylix starten mit einem neuen, leeren Projekt für eine Anwendung mit grafischer Oberfläche (Abbildung 2.1 und Abbildung 2.2)

Abbildung 2.1: Der Startbildschirm von Delphi

    

51

Sandini Bib

Abbildung 2.2: Der Startbildschirm von Kylix

An den Abbildungen erkennen Sie auch, dass Delphi und Kylix prinzipiell nur wenig unterscheidet. Um eine Konsolenanwendung zu programmieren, müssen Sie zunächst ein entsprechendes Projekt erzeugen. Wählen Sie bei Delphi dazu den Befehl NEW/OTHER im FILE-Menü. In Kylix (das ja in deutscher Sprache vorliegt) rufen Sie den Befehl NEU im DATEI-Menü auf. Im danach geöffneten Dialog wählen Sie den Eintrag CONSOLE APPLICATION bzw. KONSOLEN-ANWENDUNG (Abbildung 2.3).

Abbildung 2.3: Auswahl einer Konsolenanwendung als neues Projekt in Delphi

52

   

Sandini Bib

Lassen Sie sich nicht von der Vielzahl der Möglichkeiten verwirren. Delphi und Kylix sind (wie Java) umfangreiche Programmiersprachen mit sehr vielen Möglichkeiten. Gerade das macht diese Programmiersprachen so mächtig. Am Anfang können Sie aber nicht erfassen, was die einzelnen Einträge in diesem Dialog (und damit die Möglichkeiten der Programmentwicklung mit Delphi/Kylix) bedeuten. Später, wenn Sie die Grundlagen beherrschen und sich mit weiteren Themen beschäftigen, werden Sie auch die meisten der erweiterten Möglichkeiten verstehen. Alle kenne allerdings selbst ich nicht ... Nach dem Erzeugen einer neuen Konsolenanwendung sieht Delphi aus wie in Abbildung 2.4.

Abbildung 2.4: Delphi mit einer neuen Konsolenanwendung

Die Anweisung in den geschweiften Klammern zwischen begin und end ist ein Kommentar. Kommentare werden zu Dokumentationszwecken verwendet und gehören nicht zum eigentlichen Programm. Der Kommentar in der Delphi-Konsolenanwendung ist ein spezieller Kommentar, der automatisch in der „To Do“-Liste von Delphi erscheint (die aber leider in der Personal-Edition nicht enthalten ist). In Kylix sieht eine Konsolenanwendung ähnlich aus. Das Programm ist aber gegenüber Delphi ein wenig einfacher aufgebaut, weil die usesAnweisung und der Kommentar fehlen. Die uses-Anweisung bindet in Delphi die Bibliothek SysUtils ein (die auch unter Kylix zur Verfügung steht). Diese Bibliothek benötigen Sie in einfachen Anwendungen noch nicht.

    

53

Sandini Bib

2.2.3 Der Rahmen-Quellcode einer Konsolenanwendung Eine Konsolenanwendung besitzt in Delphi und Kylix einen festgelegten Rahmen: program Programmname; {$APPTYPE CONSOLE} begin end. program

Die erste Anweisung teilt dem Compiler mit, dass hier ein Programm entwickelt wird (und keine Bibliothek, die mit library eingeleitet wird) und definiert gleich auch den Programmnamen. Der Name des Programms muss mit dem Dateinamen identisch sein.1 Solange die Datei noch nicht gespeichert ist, heißt das Programm Project1 oder ähnlich. Wenn Sie die Datei speichern, wird der Programmname automatisch von Delphi bzw. Kylix angepasst.

{$APPTYPE

Die Anweisung {$APPTYPE CONSOLE} ist eine spezielle Compileranweisung. Über diese Anweisung erfährt der Compiler, dass das Programm eine Konsolenanwendung ist.

CONSOLE}

begin ... end

Das eigentliche Programm wird dann zwischen begin und end geschrieben. Diese Schlüsselwörter2 kennzeichnen in Object Pascal einen Programmblock. Ein Programmblock fasst mehrere Anweisungen zu einer Einheit zusammen. Der Programmblock der Konsolenanwendung steht für das gesamte Programm. Der Punkt hinter dem letzten end steht übrigens für das Ende der Programmdatei.

uses

Eine mit Delphi erzeugte Konsolenanwendung besitzt per Voreinstellung noch eine zusätzliche Anweisung: uses SysUtils;

54

1.

In einfachen Konsolenanwendungen kommt es noch nicht zu Fehlern, wenn der Programmname nicht dem Namen der Programmdatei entspricht. In Anwendungen, die mit einer Oberfläche arbeiten, assoziiert die Entwicklungsumgebung aber verschiedene Dateien mit dem Programm. Nennen Sie das Programm um, ohne den Programmdateinamen anzupassen, findet Delphi bzw. Kylix diese Dateien nicht mehr und meldet beim Kompilieren entsprechende Fehler.

2.

Als Schlüsselwort wird ein Wort bezeichnet, das in einer Programmiersprache eine festgelegt Bedeutung besitzt

   

Sandini Bib

Mit dieser Anweisung, die Sie auch in Kylix verwenden können (weil Kylix auf derselben Programmiersprache basiert), binden Sie die Bibliothek SysUtils in das Programm ein. Eine Bibliothek enthält vordefinierte Programmteile (Funktionen). Die SysUtils-Bibliothek ist ein Bestandteil von Delphi bzw. Kylix und enthält viele „Utilities“ (kleine, nützliche Funktionen). Durch das Einbinden einer Bibliothek können Sie diese in Ihren Programmen verwenden. Die Funktionen der SysUtils-Bibliothek werden eigentlich immer benötigt, weswegen diese Bibliothek in Delphi standardmäßig eingebunden wird. Auf Bibliotheken und deren Bedeutung komme ich noch in Kapitel 4 zurück. Für eine einfache Konsolenanwendung können Sie auf die Einbindung dieser Bibliothek verzichten, ein Einbinden bringt aber keine Nachteile.

2.2.4 Das Projekt Delphi und Kylix arbeiten mit Projekten. In einem Projekt werden alle Dateien verwaltet, die zu einem Programm gehören. Ein Projekt erleichtert zum einen das Wiederfinden der Programmdateien (über die Projektverwaltung, die Sie über das ANSICHT- bzw. VIEW-Menü öffnen können). Zum anderen erkennt die Entwicklungsumgebung über das Projekt, welche Dateien kompiliert werden müssen, wenn Sie das Programm erzeugen. Die Einstellungen eines Projekts werden in Delphi und Kylix in einigen Dateien gespeichert, die im Projektordner angelegt werden. Die Hauptdatei besitzt die Endung .dpr und enthält gleichzeitig den Start-Quellcode des Programms. Die Dateien mit der Endung .cfg und .dof (Delphi) bzw. .conf und .kof (Kylix) verwalten projektspezifische Daten wie spezielle Einstellungen des Compilers. Zum Öffnen eines Projekts benötigen Sie lediglich die .dpr-Datei (und natürlich die Quellcode-Dateien der Anwendung). Die anderen Dateien sollten Sie aber nicht löschen, damit Ihre speziellen Einstellungen nicht verloren gehen (im weiteren Verlauf dieses Buchs werden Sie einige wenige Compiler-Einstellungen ändern). Speichern des Projekts Bevor Sie beginnen zu programmieren, speichern Sie das Projekt idealerweise. Dabei sollten Sie einen wichtigen Grundsatz beachten:

    

55

Sandini Bib

Speichern Sie alle Dateien eines Projekt grundsätzlich in einem separaten Ordner. Idealerweise sollte ein Ordner nur die Dateien enthalten, die zu einem Projekt gehören. Damit erleichtern Sie sich zum einen das Wiederfinden und Kopieren eines Projekts und sorgen zum anderen dafür, dass Sie keine Probleme mit der Benennung der Dateien erhalten. Dieser Ratschlag kommt aus der Praxis: Viele Anfänger speichern in meinen Seminaren (trotz meines Tipps) alle Projekte in einem einzigen Ordner und haben dann nach einigen Projekten enorme Probleme, die Dateien eines Projekts wiederzufinden. Dabei sollten Sie wohl alle Projekte zusammenhängend speichern. Wenn Sie meinem Vorschlag folgen, erzeugen Sie dazu den Ordner C:\Projekte\Delphi (Windows) bzw. /projekte/kylix (Linux). Sie können die dazu notwendigen Ordner direkt im Speichern-Dialog erzeugen, den Sie in Delphi über den Befehl SPEICHERN im DATEI-Menü und in Kylix über den Befehl SAVE im FILE-Menü öffnen. Das zweite Icon von rechts in der Symbolleiste dieses Dialogs erlaubt das Anlegen neuer Ordner. Erzeugen Sie zudem einen Unterordner für Ihr erstes Projekt. Nennen Sie diesen Ordner vielleicht hello. In diesem Ordner speichern Sie die Projektdatei dann ab.

Projekte sollten in einem eigenen Ordner gespeichert werden

2.2.5 Das erste Delphi/Kylix-Programm Um nun das erste Delphi/Kylix-Programm zu erstellen, erweitern Sie das Grundgerüst folgendermaßen: 01 02 03 04 05 06 07 08 09

program hello; {$APPTYPE CONSOLE} uses SysUtils; begin writeln('Hello World'); end.

Sie verwenden in diesem Beispiel die writeln-Prozedur, die einen Text an der Konsole ausgibt. Eine Prozedur ist ein bereits vorhandenes Teilprogramm, das einen festgelegten Job ausführt (in diesem Fall die Ausgabe eines Textes an der Konsole). Die writeln-Prozedur gehört zu der Bibliothek von Delphi bzw. Kylix. Eine Bibliothek ist eine Sammlung von Prozeduren (und anderen Dingen), die Sie bei der Programmierung nutzen können.

56

   

Sandini Bib

Dieser Prozedur übergeben Sie einen Text, den Sie in Apostrophe einschließen müssen. Daran erkennt der Compiler, dass es sich um einen Text handelt. Die gesamte Anweisung wird mit einem Semikolon abgeschlossen, was dem Compiler mitteilt, dass die Anweisung zu Ende ist. Wundern Sie sich nicht, wenn Sie unter Windows arbeiten und Umlaute, die Sie in Ihren Konsolenanwendungen ausgeben, mit eigenartigen Zeichen dargestellt werden. Das liegt daran, dass die WindowsKonsole einen anderen Zeichensatz verwendet als der Editor, mit dem Sie das Programm schreiben. Dieses Problem müssen Sie zunächst in Kauf nehmen, weil dessen Lösung nicht trivial ist. Im Artikel „KonsoleIO“ auf der Buch-CD finden Sie eine Lösung. Delphi und Kylix unterscheiden Groß- und Kleinschreibung nicht (wie es aber bei Java der Fall ist). Deshalb ist es prinzipiell unwichtig, wie Sie die Anweisungen schreiben. Sie können also auch Writeln('Hello World');

schreiben. Für Delphi und Kylix macht das keinen Unterschied. Kompilieren des Programms Das Programm können Sie nun über das PROJECT- bzw. PROJEKT-Menü oder über (Strg) (F9) kompilieren. In der (Linux-)KDE ist (Strg) (F9) per Voreinstellung mit einem Wechsel zum Arbeitsbereich 9 belegt. Diese Tastenkombination funktioniert in diesem Fall unter Kylix nicht. Beachten Sie dazu meine Hinweise auf Seite 50. Wenn Delphi/Kylix beim Kompilieren keine Fehler meldet, wurde das Programm erzeugt. Meist meldet die Entwicklungsumgebung aber gleich mehrere Fehler, weil Sie (und ich) eben nicht fehlerfrei sind und schon einmal Programme schreiben, die der Compiler nicht versteht. Ein Fehler, den ich beispielsweise sehr häufig mache, ist, dass ich in Object Pascal Texte versehentlich in Anführungszeichen einschließe: Writeln("Hello World");

Solche Fehler werden als Syntaxfehler bezeichnet, weil Sie die Syntax des Programms betreffen. Syntaxfehler werden im Gegensatz zu Laufzeitfehlern, die erst dann auftreten, wenn das Programm ausgeführt wird, beim Kompilieren gemeldet. Die Entwicklungsumgebung meldet alle Syntaxfehler nach einem Kompilier-Versuch in dem Fenster, das den Programmcode enthält (Abbildung 2.5). Deshalb sind diese Fehler auch sehr einfach zu finden.

    

Syntaxfehler

57

Sandini Bib

Abbildung 2.5: Kylix meldet Fehler beim Kompilieren

Das Schöne daran ist, dass Sie einfach auf einen der Fehler doppelklicken können um die fehlerhafte Zeile rot zu markieren und den Eingabecursor auf die Fehlerstelle zu bewegen. Starten des Programms Wenn Sie das Programm fehlerfrei kompiliert haben, können Sie dieses ausführen. Prinzipiell ist dies immer auch innerhalb der Entwicklungsumgebung möglich, indem Sie (F9) betätigen. Leider sehen Sie dann zunächst nicht allzu viel. Das Programm wird, da es ein Konsolenprogramm ist, in der Konsole ausgeführt, die nur für die Abarbeitung des Programms einen kurzen Moment geöffnet wird. Ist das Programm beendet, wird auch die Konsole wieder geschlossen. Sie sehen nur auf sehr langsamen Computern überhaupt, dass etwas passiert. Sie können das erzeugte Programm auch in der Konsole direkt starten. Delphi und Kylix erzeugen ausführbare Dateien, die direkt vom Betriebssystem ausgeführt werden können (ohne Delphi bzw. Kylix!). Sie müssen also lediglich den Namen des Programms eingeben. Ein DelphiHello-World-Programm können Sie unter Windows beispielsweise so starten wie in Abbildung 2.6.

Abbildung 2.6: Start des Delphi-Hello-World-Programms in der Konsole unter Windows

58

   

Sandini Bib

Sinnvoll ist das aber eigentlich während der Entwicklung eines Programms nicht, wozu besitzen Sie schließlich eine Entwicklungsumgebung. Also müssen Sie einen kleinen Trick anwenden, damit das Programm anhält, wenn es in der Entwicklungsumgebung gestartet wurde: 01 02 03 04 05 06 07 08 09 10

program hello; {$APPTYPE CONSOLE} uses SysUtils; begin writeln('Hello World'); readln; end.

Die readln- Prozedur (Read Line) ist eigentlich dazu gedacht, Eingaben entgegenzunehmen. Da diese Prozedur aber darauf wartet, dass der Anwender (¢) betätigt, hält das Programm an der Stelle dieser Anweisung an. Nun können Sie das Programm auch in Delphi direkt starten. In Kylix passiert allerdings auch jetzt noch nicht allzu viel.

2.2.6 Die Kylix Open Edition-Besonderheiten Um Konsolenanwendungen in Kylix ausführen zu können, müssen Sie eine Einstellung des Projekts ändern. Wählen Sie dazu den Befehl PARAMETER im START-Menü. Im Startparameter-Dialog schalten Sie die Option STARTER-ANWENDUNG VERWENDEN ein (Abbildung 2.7).

    

59

Sandini Bib

Abbildung 2.7: Einstellung der Startparameter den Kylix-Projekts für den Start einer Konsolenanwendung

Damit legen Sie fest, dass Ihr Programm in der bash-Shell gestartet wird. Diese Shell wird dabei so geöffnet, dass Kylix in der Lage ist, das Programm zu kontrollieren (was zur Fehlerbeseitigung beim Debugging notwendig ist). Ändern Sie den im Texteingabefeld angegebenen Aufruf der Shell nur, wenn Sie genau wissen, was Sie tun. Nachdem Sie die neue Einstellung mit OK bestätigt haben, startet das Programm auch unter Kylix in einer Konsole, wenn Sie (F9) betätigen. Diese Einstellung müssen Sie übrigens für jedes neue Kylix-Konsolenanwendungs-Projekt erneut vornehmen. Wie Sie ja bereits wissen, ist die Kylix Open Edition speziell für die Entwicklung von Programmen vorgesehen, die unter der GNU General Public License (GPL) vertrieben werden. Weil jede GPL-Anwendung einen Hinweis auf die Lizenz enthalten muss, gibt eine Anwendung, die Sie mit der Kylix Open Edition erzeugen, einen entsprechenden Hinweis aus (Abbildung 2.8).

GPL-Hinweise

60

   

Sandini Bib

Abbildung 2.8: Das Hello-World-Programm, gestartet unter Kylix

Dieser Hinweis ist (leider) notwendig und muss im Programm enthalten bleiben. Eine andere Besonderheit von Kylix ist, dass Sie das bash-Debug-Fenster, in dem die Anwendung ausgeführt wird, nicht ohne weiteres vergrößern oder verkleinern können. Wenn Sie dies tun, hält Kylix das Programm mit einer Meldung an (Abbildung 2.9).

Abbildung 2.9: Kylix hat das Programm angehalten, weil das Fenster vergrößert oder verkleinert wurde.

Linux sendet einem Programm das Signal SIGWINCH (Signal Window Changed), wenn ein Fenster irgendwie verändert wurde. Dummerweise reagiert Kylix auf dieses Signal so, dass das Programm angehalten wird (was möglicherweise ein Bug in Kylix ist). In einem angehaltenen Programm können Sie nach Fehlern suchen. Das Debuggen beschreibe ich in Kapitel 4. Nach einer Veränderung der Fenstergröße der Konsole gibt es aber nichts zu debuggen. Um das Programm weiter auszuführen, betätigen Sie (F9), was auch manchmal mehrfach notwendig ist. Wenn das Programm dann wieder ausgeführt wird, können Sie zum bash-Debug-Fenster wechseln und das Programm weiter testen. Sie können das Programm aber auch einfach mit (Strg) (F2) beenden.

    

61

Sandini Bib

2.3

Eine Delphi/Kylix-Konsolenanwendung zur Berechnung eines Nettobetrags

Die ersten Konsolenanwendung, die Sie entwickelt haben, gab lediglich einen Text an der Konsole aus. Nun wollen wir eine Anwendung entwickeln, die ein wenig mehr macht. Das Programm soll vom Anwender zwei Eingaben anfordern: einen Bruttobetrag und einen Steuerwert. Daraus soll es dann den Nettobetrag ausrechnen und als Ergebnis ausgeben. Das Programm soll etwa so arbeiten, wie es Abbildung 2.10 zeigt.

Abbildung 2.10: Eine einfache Nettoberechnung unter Windows

2.3.1 Das Programm in einer ersten Version Erzeugen Sie in Delphi/Kylix zunächst ein neues Projekt vom Typ Konsolenanwendung. Speichern Sie dieses Projekt in einem neuen Ordner unter dem Namen Nettoberechnung. Den Ordner sollten Sie wieder im Projekte-Ordner anlegen. Nennen Sie die Projektdatei beim Speichern dann ebenfalls Nettoberechnung. Das Programm schreiben Sie wie bei dem Hello-World-Programm innerhalb von begin und end. Es enthält nun aber mehrere Anweisungen. Ich stelle diese Anweisungen in mehreren Schritten vor, damit Sie die Entwicklung nachvollziehen können. Text ausgeben Zunächst muss das Programm den Text „Geben Sie den Bruttobetrag ein:“ ausgeben. Dazu können Sie die writeln-Prozedur benutzen. Diese Prozedur erzeugt aber hinter dem Text einen Zeilenumbruch (das „ln“ im Namen steht für „Line“). Die folgende Eingabe würde dann in der nächsten Zeile erwartet. Um keinen Zeilenumbruch zu erzeugen, verwenden Sie stattdessen die write- Prozedur:

62

   

Sandini Bib

01 02 03 04 05 06 07 08 09

program nettoberechnung; {$APPTYPE CONSOLE} uses SysUtils; begin write('Geben Sie den Bruttobetrag ein: '); End.

Eingaben entgegennehmen Im nächsten Schritt soll das Programm auf eine Eingabe des Anwenders warten. Dazu können Sie die readln-Prozedur einsetzen. Diese Prozedur wartet darauf, dass der Anwender (¢) betätigt. readln ist aber auch in der Lage, die Zeichen, die der Anwender vor (¢) eingegeben hat, einzulesen und zwischenzuspeichern. Da diese Zeichen gespeichert werden müssen, benötigen Sie eine Variable, in die readln die eingelesenen Zeichen schreiben kann. Eine Variable ist im Prinzip so etwas wie ein Stück vom Arbeitsspeicher, in das Ihr Programm oder eine aufgerufene Prozedur Daten schreiben und diese auch wieder auslesen kann. Variablen müssen deklariert werden. Deklarieren bedeutet, dass Sie die Variable dem Compiler bekannt geben. Damit weiß dieser, dass er bei der Erzeugung des Programms dafür sorgen muss, dass bei der Ausführung ein entsprechend großes Stück vom Arbeitsspeicher für das Programm reserviert wird. Die Deklaration nehmen Sie in einer einfachen Konsolenanwendung oberhalb des Programmblocks (also oberhalb begin) vor. Kapitel 4 geht noch näher auf Variablen ein. Dort kläre ich, warum Sie bei der Deklaration einen Datentypen angeben müssen. Der Datentyp der Variablen des Beispielprogramms ist double. Damit legen Sie fest, dass die Variable Zahlen mit Dezimalstellen speichern kann. Außerdem erhält die Variable eine Namen, damit sie im Programm angesprochen werden kann. Diese Variable übergeben Sie dann der readlnProzedur (ähnlich wie Sie der write-Prozedur einen Text übergeben haben):

     

Variablen deklarieren und verwenden

63

Sandini Bib

01 02 03 04 05 06 07 08 09 10 11 12

program nettoberechnung; {$APPTYPE CONSOLE} uses SysUtils; var brutto: double; begin write('Geben Sie den Bruttobetrag ein: '); readln(brutto); end.

Die Eingabe des Bruttowerts funktioniert nun bereits. Das Programm muss aber noch einen Steuerwert anfordern und den Nettowert berechnen. Für die Eingabe des Steuerwerts benötigen Sie eine weitere Variable. Aber auch zur Speicherung des berechneten Nettowerts müssen3 Sie eine Variable einsetzen. Den Inhalt dieser Variablen geben Sie schließlich nur noch aus. Die erste Version des Programms sieht dann so aus: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20

3.

64

program nettoberechnung; {$APPTYPE CONSOLE} uses SysUtils; var brutto: double; var steuer: double; var netto: double; begin write('Geben Sie den Bruttobetrag ein: '); readln(brutto); write('Geben Sie den Steuerwert ein: '); readln(steuer); netto := brutto * (1 - (steuer / (100 + steuer))); write('Der Nettobetrag ist: '); writeln(netto); readln; end.

„Müssen“ ist an dieser Stelle nicht ganz korrekt. Bei der Programmierung kann man viele Wege gehen. Das Ergebnis kann beispielsweise auch ohne Variable berechnet und direkt ausgegeben werden.

   

Sandini Bib

Ich denke, dass ich den Programm-Quelltext nicht komplett erläutern muss. Die meisten einzelnen Anweisungen kennen Sie ja bereits. Neu ist die Berechnung des Nettobetrags in Zeile 16. Die Berechnung Bei der Berechnung des Nettobetrags handelt es sich um eine Zuweisung. Dabei wird das Ergebnis der rechts von := stehenden Berechnung in die links stehende Variable geschrieben. Die Berechnung selbst verwendet die in der Mathematik üblichen Zeichen. Diese Zeichen gehören natürlich zur Syntax der Programmiersprache und werden vom Compiler entsprechend ihrer Bedeutung interpretiert. Neu ist auch, dass Sie der writeln-Prozedur (wie jeder anderen Prozedur) auch eine Variable übergeben können. writeln gibt dann einfach den Inhalt dieser Variablen aus, im Beispiel also den errechneten Nettobetrag. Das Programm funktioniert schon recht gut (Abbildung 2.11). Achten Sie aber zunächst darauf, dass Sie beim Testen nur Zahlen eingeben.

Abbildung 2.11: Die erste Version der Nettoberechnung unter Windows

Wie Sie Abbildung 2.11 entnehmen können, entspricht die Ausgabe des errechneten Nettobetrags nicht dem, was die Aufgabenstellung (Abbildung 2.10 auf Seite 62) fordert. writeln verwendet zur Ausgabe von Zahlen mit Dezimalstellen per Voreinstellung leider die wissenschaftliche Notation. Bei dieser Notation wird die Zahl so dargestellt, dass eine Zahl mit einer Ziffer vor dem Komma dargestellt und mit einer Potenz von 10 multipliziert wird. 5.1E+3 steht für 5,1 * 103, also 5,1 * 1000 (5100). Bei der Ausgabe wird zudem die englische Zahlendarstellung verwendet, bei der der Punkt das Dezimaltrennzeichen ist.

2.3.2 Formatieren der Ausgabe Sie können (natürlich) auch erreichen, dass die Zahl in einer besser lesbaren Form ausgegeben wird. Jede Programmiersprache stellt dazu Features zur Formatierung zur Verfügung (das Formatieren einer Ausgabe

     

65

Sandini Bib

ist eben ein wichtiges Thema). In Object Pascal können Sie zur Formatierung die Funktion FormatFloat verwenden. Wenn Sie den Nettobetrag mit führender Null zwei Stellen nach dem Komma formatieren wollen, verwenden Sie die folgende Anweisung: writeln(FormatFloat('0.00', netto));

Diese Anweisung ist bereits etwas komplexer. Die FormatFloat-Funktion wird in den Klammern der writeln-Prozedur aufgerufen. Der Compiler sorgt dafür, dass diese Funktion vor writeln aufgerufen wird. Eine Funktion gibt immer Daten zurück. Die FormatFloat-Funktion gibt die formatierte Zahl zurück. Damit diese Funktion weiß, wie sie formatieren soll und was formatiert werden soll, übergeben Sie einen Text mit FormatierInformationen und die zu formatierende Zahl. Die Syntax der Formatierzeichen ist für die FormatFloat-Funktion festgelegt. Sie können diese Syntax in der Hilfe zu dieser Funktion nachlesen. Der Text '0.00' sorgt dafür, dass die Zahl mit führender Null und zwei Nachkommaziffern ausgegeben wird. Das Ergebnis der Formatierung wird dann von der writeln-Prozedur weiterverarbeitet und an der Konsole ausgegeben. Das Resultat entspricht also bereits der gestellten Aufgabe (Abbildung 2.10 auf Seite 62).

2.3.3 Umgang mit Fehlern, die durch ungültige Eingaben verursacht werden Das Nettoberechnungs-Programm ist noch nicht perfekt. Wenn Sie das Programm ausführen und eine ungültige Zahl eingeben (z. B. 75oo, mit zwei kleinen O an Stelle der Null, was ein häufiger Fehler bei der Eingabe ist), erhalten Sie eine Fehlermeldung. Wird das Programm über Delphi/Kylix ausgeführt, hält die Entwicklungsumgebung das Programm an und meldet den Fehler in einem Dialog (Abbildung 2.12).

Abbildung 2.12: Fehlermeldung bei der Ausführung unter Kylix

Das Programm meldet eine Ausnahme (Exception). Ausnahmen werden während der Ausführung eines Programms erzeugt, nachdem ein Zustand eingetreten ist, der verhindert, dass das Programm weiter feh-

Ausnahmen, Laufzeitfehler und logische Fehler

66

   

Sandini Bib

lerfrei ausgeführt wird. Ausnahmen werden auch als Laufzeitfehler4 bezeichnet, weil es sich um Fehler handelt, die während der Laufzeit eines Programms auftreten. Die Ausnahme im Beispiel wurde dadurch verursacht, dass die Eingabe nicht in eine Zahl umgewandelt werden kann. In einfachen Programmen ist ein solcher Umwandlungsfehler eigentlich die einzig mögliche Ausnahme. Später, wenn Ihre Programme komplexer werden, werden Sie wahrscheinlich häufiger mit Ausnahmen konfrontiert. Wenn Ihr Programm beispielsweise eine Datei oder eine Datenbank öffnen will, die aber nicht vorhanden ist, resultiert dies in einer Ausnahme. Ausnahmen können Sie abfangen, was ich in Kapitel 4 zeige. Ausnahmen werden häufig durch Bugs verursacht. Ein Bug ist ein logischer Fehler im Programm. Ein Programm, das Bugs aufweist, enthält Fehler in der Programmlogik. Solche Fehler werden Sie in Kapitel 4 kennen lernen und dort auch erfahren, wie Sie diese beseitigen (Ihr Programm also „debuggen“) können. Der logische Fehler in unserem Nettoberechnungsprogramm ist der, dass das Programm fehlerhafte Eingaben nicht berücksichtigt. Delphi und Kylix halten das Programm beim Eintritt einer Ausnahme in der Entwicklungsumgebung an. Delphi markiert die fehlerauslösende Anweisung und gibt Ihnen die Möglichkeit, den Fehler zu debuggen (Abbildung 2.13).

Das Programm wird bei einem Fehler angehalten

Abbildung 2.13: Delphi hat das Programm aufgrund einer fehlerhaften Eingabe angehalten. 4.

Um genau zu sein handelt es sich bei Laufzeitfehlern nicht um Ausnahmen. Ein Laufzeitfehler führt dazu, dass ein Programm sofort (mit einer Fehlermeldung) beendet wird. Eine Ausnahme kann hingegen abgefangen und behandelt werden. Das Programm kann nach dem Eintritt einer Ausnahme in diesem Fall weiter ausgeführt werden. Unbehandelte Ausnahmen führen aber in der Regel zu einem Laufzeitfehler.

     

67

Sandini Bib

Viel können Sie hier noch nicht debuggen. Der Fehler tritt intern auf, bei der Konvertierung der Eingabe zu einer Zahl. Immerhin wissen Sie, von welcher Anweisung der Fehler ausgelöst wird. Beenden Sie das Programm einfach über (Strg) (F2). In Kapitel 4 erfahren Sie mehr über das Debuggen. In Kylix sieht das Ganze leider nicht so aus. Kylix hält das Programm zwar ebenfalls an, markiert allerdings nicht die fehlerauslösende Anweisung. Beenden können Sie aber auch hier über (Strg) (F2). Leider ist diese Tastenkombination (wie viele andere) in der KDE per Voreinstellung belegt (die KDE wechselt dann zum Arbeitsbereich 2). Sie können das Programm auch über den Befehl PROGRAMM ZURÜCKSETZEN im STARTMenü beenden. Beachten Sie meine Hinweise zu Linux auf Seite 50. Wenn Sie das Programm direkt über die Konsole des Betriebssystems ausführen, wird die Ausnahme an der Konsole gemeldet (Abbildung 2.14).

Fehlermeldung an der Konsole

Abbildung 2.14: Fehlermeldung aufgrund einer ungültigen Eingabe unter Windows

Ein Windows-Programm meldet den Fehler zudem direkt nach der Eingabe in einem separaten Dialog (Abbildung 2.15).

Abbildung 2.15: Zusätzliche Fehlermeldung unter Windows

Etwas verwirrend ist, dass Windows nicht die wahre Ursache des Fehlers erkennt und einen „Unbekannten Softwarefehler“ meldet. Nun wissen Sie, woher die manchmal sehr eigenartigen Windows-Fehlermeldungen kommen. Die Zahlen, die in den Fehlermeldungen angegeben werden, beziehen sich übrigens auf Speicheradressen. Dazu werden Sie im nächsten Kapitel noch mehr erfahren.

68

   

Sandini Bib

Damit Ausnahmen nicht zu den etwas undurchsichtigen Fehlermeldungen führen, können Sie diese abfangen und eine eigene Fehlermeldung ausgeben. Da es sich dabei bereits um eine komplexere Programmierung handelt, erfahren Sie erst in Kapitel 4, wie Sie dies programmieren.

2.4

Entwicklung einer einfachen Delphi/KylixAnwendung mit grafischer Oberfläche

Die wenigsten Programme arbeiten heutzutage noch an der Konsole. Ein modernes Programm besitzt normalerweise eine Oberfläche mit Fenstern und Bedienelementen. Sie kennen solche Programme bereits von Ihrer Arbeit mit Windows bzw. einem der Desktops von Linux (KDE, GNOME etc.). Programme mit einer grafischen Oberfläche bieten dem Anwender enorme Vorteile, allein schon dadurch, dass sie in der Regel wesentlich einfacher zu bedienen sind als Konsolenanwendungen. Solch ein Programm wollen wir nun auch entwickeln. Damit Sie einen direkten Vergleich haben, soll die Nettoberechnung in einer grafischen Anwendung umgesetzt werden. Um nicht immer „Anwendung mit grafischer Oberfläche“ schreiben zu müssen, bezeichne ich eine solche Anwendung ab hier einfach als „normale Anwendung“. Unter Windows wäre zwar auch der Begriff „Windows-Anwendung“ sinnvoll, unter Linux passt dieser Begriff aber (natürlich) nicht.

2.4.1 Ein Projekt für eine normale Anwendung Erzeugen Sie zur Erstellung einer normalen Anwendung ein neues Projekt. In Delphi wählen Sie dazu den Befehl APPLICATION im FILE/NEWMenü. In Kylix wählen Sie den Befehl NEUE ANWENDUNG im DATEIMenü. Beide Entwicklungsumgebungen erzeugen ein Projekt, das bereits ein Fenster enthält (Abbildung 2.16).

        

69

Sandini Bib

Abbildung 2.16: Eine neue normale Anwendung in Delphi

Ein Fenster wird üblicherweise auch als „Formular“ bezeichnet, was wohl dadurch begründet ist, dass ein Fenster, das Eingabeelemente enthält, wie ein (Papier-)Formular zur Eingabe von Daten verwendet werden kann. Speichern Sie das Projekt ab, bevor Sie beginnen zu programmieren. Damit erreichen Sie, dass Delphi das Programm beim Testen im Projektordner erzeugt und nicht irgendwo auf der Festplatte, und gehen sicher, dass Sie das Speichern später nicht vergessen. Später brauchen Sie dann immer nur (Strg) (s) zu betätigen, um die Datei, an der Sie gerade arbeiten, zu speichern. Machen Sie es sich zur Gewohnheit, in (mehr oder weniger) regelmäßigen Abständen zu speichern. Entwicklungsumgebungen stürzen schon einmal ab. Das Speichern umfasst nun zwei Dateien: die Projektdatei und die Formulardatei. Sie müssen beide Dateien unterschiedlich benennen. Nennen Sie die Projektdatei (die die Endung .dpr besitzt) vielleicht nettoberechnung und die Formulardatei (die die Endung .pas besitzt) fstart. In Wirklichkeit speichert Delphi übrigens noch eine dritte Datei für das Formular und weitere Dateien für das Projekt, die ich im nächsten Abschnitt beschreibe.

70

   

Sandini Bib

Sie können Delphi und Kylix auch so einstellen, dass beim Testen (mit (F9)) immer automatisch gespeichert wird. Diese Einstellung ist sehr zu empfehlen, denn auch beim Testen können Programme und Entwicklungsumgebungen abstürzen. In Delphi wählen Sie dazu den Befehl ENVIRONMENT OPTIONS im TOOLS-Menü. Im Dialog zur Einstellung der Umgebungs-Optionen schalten Sie die Option EDITOR FILES im AUTOSAVE OPTIONS-Block des PREFERENCES-Registers ein. In Kylix sieht das Ganze ähnlich aus, lediglich mit deutschen Bezeichnungen: Über das Menü TOOLS erreichen Sie die UMGEBUNGSOPTIONEN. Hier schalten Sie die Option EDITORDATEIEN im Block OPTIONEN FÜR AUTOSPEICHERN des PRÄFERENZEN-Registers ein. Die Dateien eines Projekts für eine normale Anwendung Für eine normale Anwendung erzeugt Delphi/Kylix zunächst, genau wie bei einer Konsolenanwendung, die Projektdatei mit der Endung .dpr. Zusätzlich werden pro Formular je zwei Dateien erzeugt. Die .dfm(Delphi Form) bzw. xfm-Datei (Kylix Form) enthält die Definition des Formulars, also dessen Einstellung und alle Steuerelemente, die auf dem Formular angelegt wurden. Die .pas-Datei enthält den Programmcode des Formulars, also alle Programme, die Sie für ein Formular schreiben. Daneben erzeugt die Entwicklungsumgebung oft noch eine Datei mit der Endung .res. Diese Datei verwaltet Bilder, die Sie auf den Formularen Ihrer Programme ablegen. Andere Projektdateien speichern wieder wie bei einer Konsolenanwendung projektspezifische Einstellungen.

2.4.2 Entwurf des Formulars Für unseren Zweck reicht eine Anwendung mit einem einzigen Formular aus. Sie müssen das Formular nun nur noch mit passenden Eingabeelementen versehen und ein wenig programmieren . Das Ergebnis soll in etwas so aussehen wie in Abbildung 2.17.

-

Abbildung 2.17: Eine Delphi-Nettoberechnung unter Windows

        

71

Sandini Bib

Die Arbeitsweise dieses Programms dürfte klar sein: Der Anwender kann den Bruttobetrag und den Steuerwert eingeben und den Nettobetrag über den RECHNEN-Schalter ausrechnen lassen. Der BEENDEN-Schalter soll das Programm beenden. Steuerelemente Zur Erstellung einer Programmoberfläche stellen Ihnen Delphi und Kylix so genannte Steuerelemente zur Verfügung. Ein Steuerelement wird, wie der Name schon sagt, zur Steuerung einer Anwendung verwendet. Normalerweise sind Steuerelemente dazu gedacht, dass der Anwender etwas darin eingibt, mit der Maus darauf klickt, etwas aus einer Liste auswählt usw. Sie kennen die meisten Steuerelemente wohl bereits von Ihrer täglichen Arbeit mit Windows bzw. Linux. Viele Steuerelemente werden übrigens mehr oder weniger direkt vom Betriebssystem (bzw. bei Linux von einem Fenstermanager) zur Verfügung gestellt und sehen deshalb in allen Anwendungen prinzipiell gleich aus. Die Delphi/Kylix-Steuerelemente finden Sie oben im Delphi/KylixHauptfenster (Abbildung 2.18 und Abbildung 2.19).

Abbildung 2.18: Die Delphi-Steuerelemente

Abbildung 2.19: Die Kylix-Steuerelemente

Die Steuerelemente sind in Kategorien eingeteilt, damit Sie diese besser finden. Lassen Sie sich von der Vielzahl der Steuerelemente nicht verwirren. Zunächst benötigen Sie nur die drei wichtigsten. Die anderen Steuerelemente können Sie später immer noch erforschen. Wie Sie sehen, besitzt Kylix wesentlich weniger Steuerelemente als Delphi. Die Steuerelemente von Delphi und Kylix sind übrigens zwar ähnlich, aber nicht identisch. Die Grundfunktionalität von Steuerelementen wird im Fall von Delphi und Kylix (wie auch bei den meisten anderen Sprachen mit Ausnahme von Java) vom Betriebssystem zur Verfügung gestellt, und das ist bei Delphi eben ein anderes als bei Kylix. Deshalb können Sie Delphi-Projekte mit Formularen und Steuerelementen zunächst auch nicht unter Kylix kompilieren. Die in Kylix verwendete Steuerelementbibliothek können Sie aber (in einer Windows-Adaption) auch in der Professional- und Enterprise-Edition von Delphi nutzen

Steuerelemente werden vom Betriebssystem zur Verfügung gestellt

72

   

Sandini Bib

und so Programme entwickeln, die Sie unter Windows und Linux kompilieren und ausführen können. Aber das ist nur ein Hinweis am Rande. Sie können einzelne Steuerelemente nun auf dem Formular platzieren, indem Sie diese im Steuerelement-Register anklicken und danach auf das Formular klicken. Mit der Hilfe der Maus können Sie die Steuerelemente dann an ihren vorgesehenen Ort bewegen. Über die Anfasser (das sind die kleinen Kästchen, die ein Steuerelement besitzt, wenn es aktiviert wurde) können Sie das Steuerelement in der Größe verändern. Für die Nettoberechnung benötigen Sie die folgenden Steuerelemente: • Label: Ein Label wird zur Beschriftung von Formularen und zur Ausgabe von Daten verwendet. Ein Label erlaubt keine Eingaben. • Edit: Ein Edit-Steuerelement (das oft auch als Textbox oder Textfeld bezeichnet wird) wird in der Regel dazu verwendet, den Anwender Texte oder Zahlen eingeben zu lassen. In einem solchen Steuerelement können Sie aber auch Daten ausgeben. • Button: Ein Button-Steuerelement ist ein Schalter, der vom Anwender betätigt werden kann um eine bestimmte Aktion auszulösen.

Sie können Steuerelemente, die bereits auf einem Formular angelegt sind, auch kopieren. Selektieren Sie die zu kopierenden Steuerelemente auf dem Formular, indem Sie mit der Maus einen „Rahmen“ um diese ziehen. Jedes Steuerelement, das vom Rahmen berührt wird, wird in die Selektion aufgenommen, wenn Sie die Maustaste loslassen. Kopieren Sie die Steuerelemente dann in die Zwischenablage ((Strg) (c)) und fügen Sie den Inhalt der Zwischenablage direkt wieder ein ((Strg) (v)). Die nun selektierten neuen Steuerelemente können Sie schließlich gemeinsam über die Maus an ihren vorgesehenen Ort bewegen. Legen Sie auf diese Art die für die Nettoberechnung benötigten Steuerelemente auf dem Formular an, bis dieses in etwa so aussieht wie in Abbildung 2.20.

        

73

Sandini Bib

Abbildung 2.20: Der erste Entwurf der Nettoberechnung in Kylix

2.4.3 Einstellen der Eigenschaften Nun müssen Sie einige Eigenschaften der Steuerelemente und des Formulars einstellen. Eigenschaften beeinflussen das Verhalten oder das Aussehen der Steuerelemente. Die Beschriftung des Formulars, der Label und der Schalter wird z. B. in der Eigenschaft Caption eingestellt. Steuerelemente besitzen daneben aber eine Vielzahl weiterer Eigenschaften. Wichtige sind z. B. die Breite (Width), die Höhe (Height), die Farbe (Color) und der Inhalt von Textboxen (Text). Die wichtigste Eigenschaft ist jedoch Name. Über diese Eigenschaft stellen Sie den Namen der Steuerelemente ein. Über diesen Namen können Sie die Steuerelemente später im Programm ansprechen. Eigenschaften stellen Sie ein, indem Sie das Steuerelement mit der Maus aktivieren und den Wert im Objektinspektor eintragen. Abbildung 2.21 zeigt, wie die Eigenschaft Name der ersten Textbox (die zurzeit noch Edit1 heißt) eingestellt wird.

Abbildung 2.21: Der Objektinspektor von Kylix. Eingestellt wird die Eigenschaft Name der ersten Textbox.

74

   

Sandini Bib

Falls der Objektinspektor nicht sichtbar ist, können Sie diesen über (F11) anzeigen. Suchen Sie dann die Eigenschaft in der linken Spalte und tragen Sie den Wert in der rechten Spalte ein. Mit (F11) können Sie auch sehr schnell in den Objektinspektor wechseln. Benennen Sie Steuerelemente, die Sie im Programm ansprechen wollen, immer mit einem aussagekräftigen Namen. Sie erreichen damit, dass Ihr Programm wesentlich übersichtlicher wird. Wenn Sie beispielsweise in einem Programm den Namen brutto sehen, erkennen Sie daran die Bedeutung des Namens recht gut. Übernehmen Sie hingegen die voreingestellten Namen (wie edit1, edit2 etc.), macht dies das Programm unnötig schwerer verständlich, als es möglicherweise sowieso schon ist. Viele Programmierer benennen Steuerelemente mit einer bestimmten Konvention. Einige stellen beispielsweise zwei bis drei kleingeschriebene Zeichen vor den eigentlichen Namen, die den Typ des Steuerelements angeben. Eine Textbox (die in Delphi ein Edit-Steuerelement ist), können Sie beispielsweise mit dem Präfix „txt“ versehen. Die Textbox zur Eingabe des Bruttobetrags würde demnach also txtBrutto heißen. Andere Programmierer hängen die Bezeichnung des Steuerelements an den Namen an: bruttoTextbox. Das Ganze bringt den Vorteil, dass Sie im Programm genau erkennen, um was es sich handelt. Die Verwendung solcher speziellen Bezeichner gehört allerdings schon zum Bereich „Programmier-Philosophie“ und wird im Internet teilweise heftig diskutiert. Ich benenne die Steuerelemente folgendermaßen: • txtBrutto: Textbox zur Eingabe des Bruttobetrags • txtSteuer: Textbox zur Eingabe des Steuerwerts • txtNetto: Textbox zur Ausgabe des errechneten Nettobetrags • btnRechnen: Schalter zur Ausführung der Berechnung • btnBeenden: Schalter zum Beenden des Programms Eine Benennung mit einer Konvention macht zwar etwas Arbeit, besitzt aber eigentlich nur Vorteile. Sie erkennen im Programm schon am Namen, worum es sich handelt, was in vielen Fällen ein weiteres Nachschauen unnötig macht, wenn Sie versuchen das Programm zu verstehen. Der Name txtBrutto sagt z. B. alles aus, was notwendig ist: Es handelt sich um eine Textbox, die einen Bruttowert verwalten soll. Das ist sehr eindeutig. Ich empfehle Ihnen, ebenfalls eine solche Konvention zu verwenden. Festgelegte Konventionen gibt es aber für Delphi und Kylix wohl nicht. Leiten Sie den Präfix einfach aus der Bezeichnung des

        

75

Sandini Bib

Steuerelements ab (wobei ich die Ausnahme bei dem Edit-Steuerelement mache, das üblicherweise als Textbox bezeichnet wird). Verwechseln Sie die Benennung eines Steuerelements nicht mit dessen Beschriftung. Ein Button-Steuerelement, das eine Beschriftung besitzt, kann beispielsweise calculate heißen und mit Rechnen beschriftet sein. Neben dem Namen müssen Sie noch die Eigenschaft Caption der Labelund der Button-Steuerelemente einstellen. Markieren Sie dazu jedes dieser Steuerelemente nacheinander und stellen Sie den neuen Wert ein. Wenn Sie die Eigenschaften der Steuerelemente nacheinander einstellen, erleichtern Sie sich die Arbeit. Stellen Sie also zuerst beispielsweise die Eigenschaft Name der ersten Textbox ein. Aktivieren Sie dann die zweite Textbox und betätigen Sie (F11). Da der Objektinspektor immer auf der zuletzt eingestellten Eigenschaft stehen bleibt und nach (F11) gleich in die Wertspalte dieser Eigenschaft wechselt, können Sie sofort den Namen der zweiten Textbox einstellen etc. Der Inhalt der Textboxen wird nicht in der Eigenschaft Caption (die diese gar nicht besitzen), sondern in der Eigenschaft Text eingestellt. Setzen Sie diese Eigenschaften auf einen Wert, der als Voreinstellung verwendet werden kann. Für die Nettoberechnung bietet sich die Zahl Null an. Das Formular sollte ebenfalls einen Namen und eine sinnvolle Beschriftung erhalten. Um die Eigenschaften des Formulars einstellen zu können, müssen Sie auf eine freie Stelle des Formulars klicken (also nicht auf ein Steuerelement). Ich benenne das Formular, mit dem meine Anwendungen starten, immer mit StartForm. Der Name eines Formulars hat nichts mit dem Namen der Datei zu tun, in der das Formular gespeichert ist. Ein Formular mit den Namen StartForm kann also z. B. in einer Datei fStart.pas gespeichert sein. Delphi und Kylix verlangen sogar (unsinnigerweise), dass der Name eines Formulars ein anderer ist als der Name der Formulardatei. Sie können ein Formular, das in einer Datei mit dem Namen StartForm.pas gespeichert ist, also nicht StartForm nennen. Deshalb habe ich die Formulardatei beim Speichern auch fStart.pas genannt .

-

Das Ergebnis soll dann in etwa aussehen wie in Abbildung 2.22.

76

   

Sandini Bib

Abbildung 2.22: Das Nettoberechnungs-Formular in Delphi

2.4.4 Ereignisorientiertes Programmieren Sie werden gleich beginnen, mit dem Formular zu programmieren. Dazu werden Sie Methoden erzeugen, die auf bestimmte Ereignisse reagieren. Sie sollten wissen, worum es sich dabei handelt. Methoden Eine Methode ist zunächst so etwas wie eine Prozedur (oder Funktion). Prozeduren haben Sie bereits verwendet. Das Nettoberechnungsprogramm von Seite 62 nutzt z. B. die Prozeduren write, writeln und readln. Methoden unterscheiden sich nur in geringem Maße von Prozeduren (und Funktionen). Eigentlich handelt es sich dabei nur um einen Begriff zur Kennzeichnung von Prozeduren und Funktionen, die in einem bestimmten Kontext verwendet werden. Methoden enthalten nämlich genau wie Prozeduren und Funktionen eine oder mehrere Programmanweisungen, die unter dem Namen der Methode aufgerufen werden können. Der Unterschied zwischen Prozeduren/Funktionen und Methoden ist, dass Methoden immer zu Objekten gehören. Ich will noch nicht auf dieses etwas komplexe Thema eingehen. In Kapitel 6 erfahren Sie mehr darüber. An dieser Stelle ist lediglich wichtig, dass ein Formular ein Objekt ist. Funktionen und Prozeduren, die zum Formular gehören, werden deshalb als Methoden bezeichnet. Ich will von vornherein die korrekten Fachbegriffe verwenden. Deswegen lesen Sie den Begriff „Methode“ bereits in diesem Kapitel recht häufig.

Methoden sind Prozeduren oder Funktionen in Objekten

Ereignisse In einer Anwendung, die mit Formularen arbeitet, werden Programme anders ausgeführt als in einer Konsolenanwendung. Wie Sie in den ersten Beispielen bereits gesehen haben, beginnt ein Konsolenprogramm immer an der ersten Anweisung hinter begin und endet an der letzten Anweisung vor end. Das Programm wird sequenziell von oben nach unten abgearbeitet. Ist das Programm unten angelangt, wird es beendet.

        

77

Sandini Bib

Eine Anwendung mit grafischer Oberfläche arbeitet aber ganz anders. Eine solche Anwendung startet, indem das Hauptformular geöffnet wird. Betätigen Sie für unsere Beispielanwendung einfach einmal (F9) und Sie wissen, was ich meine. Das Formular wird geöffnet und ... wartet. Es wartet so lange, bis etwas passiert. Wenn Sie auf den kleinen Schalter zum Minimieren in der Symbolleiste des Formulars klicken, reagiert es, indem es sich verkleinert. Wenn Sie mit der Maus auf den Rand des Formulars klicken und die Maus ziehen, regiert das Formular ebenfalls, dieses Mal, indem es sich verkleinert oder vergrößert. Klicken Sie auf den Symbolleistenschalter zum Schließen, reagiert das Formular wieder, es schließt sich. Dasselbe geschieht auch, wenn Sie bei geöffnetem Formular (Alt) (F4) betätigen. Das Formular reagiert auf verschiedene Ereignisse, teilweise mit unterschiedlichen Programmen.

Anwendungen mit grafischer Oberfläche arbeiten anders als Konsolenanwendungen

Dieses Basisverhalten des Formulars ist bereits vordefiniert. Sie müssen nichts weiter programmieren, damit ein Formular geschlossen, in der Größe verändert, minimiert oder maximiert werden kann. Ein Formular sollte aber natürlich ein wenig mehr können. Unser NettoberechnungsFormular muss z. B. darauf reagieren, dass der Anwender den RECHNENoder den BEENDEN-Schalter betätigt. Und hier kommen Ereignisbehandlungsmethoden ins Spiel. Ein Formular kann nämlich auf die verschiedensten Ereignisse reagieren. Dazu programmieren Sie eine Ereignisbehandlungsmethode. Die Entwicklungsumgebung hilft dabei, Sie müssen lediglich das richtige Ereignis für das richtige Steuerelement auswählen. Der Rumpf der Ereignisbehandlungsmethode wird automatisch erzeugt. Das einzige, was Sie machen müssen ist, die Reaktion auf das Ereignis zu programmieren.

Ereignisbehandlungsmethoden reagieren auf Ereignisse

Sie können für die einzelnen Steuerelemente und für das Formular eine große Anzahl an verschiedenen Ereignissen auswerten. Für ein Textfeld können Sie z. B. darauf reagieren, • dass der Anwender mit der Maus in dieses Feld klickt, • dass der Anwender auf diesem Feld einen Doppelklick ausführt, • dass der Anwender den Eingabefokus5 auf dieses Feld setzt, • dass der Anwender die Tastatur betätigt, • dass der Anwender das Feld mit dem Eingabefokus verlässt, • dass der Anwender die Maus über dem Feld bewegt oder • dass der Anwender Daten von einem anderen Programm in dieses Feld zieht. 5.

78

Der Eingabefokus bezeichnet das Steuerelement, das gerade Eingaben empfängt. Wenn Sie z. B. mit der Maus oder der Tab-Taste zu einem Textfeld wechseln, besitzt dieses den Eingabefokus und erhält alle Eingaben.

   

Sandini Bib

Das ist aber nur eine kleine Auswahl der möglichen Ereignisse. Die meisten Steuerelemente stellen noch eine Vielzahl weiterer Ereignisse zur Verfügung. Die Idee dahinter ist, dem Programmierer möglichst für alles, was vorkommen kann, ein Ereignis zu liefern. Sie können nun für jedes Steuerelement und für das Formular verschiedene Ereignisse auswerten, wenn Ihr Programm dies erfordert. So können Sie z. B. beim Verlassen eines Textfeldes den eingegebenen Wert überprüfen und dem Benutzer bei einer ungültigen Eingabe eine entsprechende Meldung übergeben. Dazu schreiben Sie eine Ereignisbehandlungsmethode für das „LostFocus“-Ereignis der Textbox. In vielen Fällen reicht aber die Reaktion auf die Betätigung der Schalter eines Formulars vollkommen aus. Manchmal müssen Sie auch auf das Öffnen eines Formulars reagieren, um dieses zu initialisieren, oder auf das Schließen, um abschließende Aufräumarbeiten zu erledigen. Die ersten Programme, die Sie in diesem Kapitel entwickeln, reagieren lediglich auf die Betätigung eines Schalters. In Kapitel 8 werden Sie aber auch das Öffnen und Schließen eines Formulars behandeln. Vielleicht wollen Sie wissen, wie Ereignisse prinzipiell funktionieren. Eigentlich ist das Ganze recht einfach. Formulare werden immer vom Betriebssystem erzeugt und stehen deswegen unter dessen Kontrolle. Das Betriebssystem behandelt daneben auch alle Eingaben des Anwenders. Klickt der Anwender mit der Maus auf ein Formular, erkennt das Betriebssystem, welches Formular unter der Maus liegt, und sendet dem Programm, das für das Formular verantwortlich ist, eine Nachricht mit der Information, auf welches Formular der Anwender geklickt hat und welche Position die Maus dabei hatte. Das Programm fängt diese Nachricht (intern) ab und wertet sie aus. An Hand der übergebenen Mausposition erkennt das Programm, auf welches Steuerelement geklickt wurde. Es überprüft, ob für dieses Steuerelement und das entsprechende Ereignis eine Ereignisbehandlungsmethode existiert und ruft diese gegebenenfalls auf.

2.4.5 Programmieren der Berechnung Das Programmieren der Berechnung ist nun, nach der ganzen LayoutArbeit und dem neuen Wissen über Ereignisse, ein Leichtes. Die Aufgabe bestimmt, dass die Berechnung ausgeführt wird, wenn der Anwender den RECHNEN-Schalter betätigt. Dazu müssen Sie eine Ereignisbehandlungsmethode erstellen, die auf die Betätigung des Schalters reagiert.

        

79

Sandini Bib

Bevor Sie beginnen zu programmieren, sollten Sie noch einmal die Namen Ihrer Steuerelemente überprüfen. Sie erzeugen gleich Methoden, die automatisch mit dem Namen der Steuerelemente versehen werden. Wenn Sie die Steuerelemente später umbenennen, werden Ihre Bezeichnungen im Programm inkonsistent. Beim Versuch, die erzeugten Methoden umzubenennen, können am Anfang allerlei Fehler auftreten, was besonders dann gilt, wenn Sie die Methode einfach löschen. Für den Anfang (und eigentlich auch später) sollten Sie diese Fehler möglichst vermeiden, indem Sie die Steuerelemente vor dem Programmieren sinnvoll benennen und den Namen danach nicht mehr ändern. Das Erzeugen einer Methode zur Reaktion auf die Betätigung des Schalters ist sehr einfach. Dazu klicken Sie doppelt auf den Schalter. Die Entwicklungsumgebung öffnet das Programmfenster des Formulars und erzeugt gleich eine passende Methode für die Auswertung der SchalterBetätigung (Abbildung 2.23).

Ereignisbehandlungsmethoden erzeugen

Abbildung 2.23: Das Programmfenster des Formulars mit einer Methode, die auf die Betätigung des Rechnen-Schalters reagiert

Den Aufbau dieser Methode erläutere ich jetzt nicht, weil dies schon einiges Grundwissen erfordert. In Kapitel 6 erfahren Sie mehr zu Methoden. Delphi/Kylix hat die Methode auf jeden Fall bereits mit der Betätigung des Schalters verknüpft. Immer wenn der Schalter betätigt wird, wird diese Methode aufgerufen. Sie müssen nun nur noch programmieren. Ereignisbehandlungsmethoden für die anderen Ereignisse eines Steuerelements oder des Formulars erzeugen Sie übrigens über den Objektinspektor, indem Sie im Register EREIGNISSE bzw. EVENTS auf das entsprechende Ereignis klicken.

80

   

Sandini Bib

Beim Programmieren in einem Formular beziehen Sie sich auf die Steuerelemente, die Sie auf dem Formular angelegt haben. Sie kennen ja deren Name (und der ist sogar aussagekräftig ). Um die Eingabe des Anwenders aus einer Textbox auszulesen, lesen Sie die Eigenschaft Text aus (die eben diese Eingabe verwaltet). In diese Eigenschaft können Sie auch Texte hineinschreiben um diese auszugeben. So können Sie in unserem Programm den Nettobetrag ausgeben. Um eine Eigenschaft eines Steuerelements im Programm anzusprechen, verwenden Sie die folgende Syntax:

-

Bezug auf Steuerelemente

Steuerelementname.Eigenschaftname

Die Eigenschaft Text der Brutto-Textbox erreichen Sie also z. B. so: txtBrutto.Text

Eigentlich könnte die Berechnung nun sehr einfach aussehen (und der Berechnung aus der Konsolenanwendung sehr ähnlich sein): 01 02 03 04

procedure TStartForm.btnRechnenClick(Sender: TObject); begin Netto.Text := Brutto.Text * (1-(Steuer.Text / (100 + Steuer.Text))); end;

Object Pascal, die Sprache von Delphi und Kylix, ist aber eine so genannte typsichere Sprache. Der Compiler lässt arithmetische Berechnungen nur mit Zahlen zu, nicht mit Texten. Eine Textbox speichert in ihrer Eigenschaft Text aber nun einen Text. Um mit den Texteingaben rechnen zu können, müssen Sie diese in eine Zahl konvertieren. Dazu können Sie Funktionen verwenden, die Ihnen Object Pascal zur Verfügung stellt. Die Funktion StrToFloat konvertiert beispielsweise eine Zeichenkette in eine Zahl mit Dezimalstellen. Zur Speicherung der konvertierten Zahlen können Sie wieder Variablen einsetzen. Innerhalb einer Methode müssen Sie Variablen über dem beginSchlüsselwort deklarieren. Ich deklariere im Beispiel (wie bei der Konsolenanwendung) zwei Variablen für die Eingabe und eine für den errechneten Nettobetrag: 01 02 03 04 05

Eingaben konvertieren

procedure TStartForm.btnRechnenClick(Sender: TObject); var brutto: double; var steuer: double; var netto: double; begin

        

81

Sandini Bib

Nun können Sie die Eingaben einlesen, konvertieren und den Variablen zuweisen: 06 brutto := StrToFloat(txtBrutto.Text); 07 steuer := StrToFloat(txtSteuer.Text);

Die Berechnung sieht dann genauso aus wie bei der Konsolenanwendung:

Rechnen

08 netto := brutto * (1 - (steuer / (100 + steuer)));

Nun müssen Sie das Ergebnis noch ausgeben. Die in der Variable netto gespeicherte Zahl können Sie nicht direkt der Eigenschaft Text der Textbox txtNetto zuweisen. Es handelt sich eben um eine Zahl, die vom Compiler nicht implizit in einen Text umgewandelt wird. Das ist aber nicht besonders schlimm. Da die Ausgabe sowieso formatiert werden soll, können Sie einfach die Funktion FormatFloat einsetzen, die Sie bereits von der Konsolen-Variante des Programms her kennen:

Ausgeben

09 txtNetto.Text := FormatFloat('0.00', netto); 10 end;

Netterweise gibt FormatFloat eine Zeichenkette zurück, die direkt in die Text-Eigenschaft der Textbox geschrieben werden kann. In der Konsolenanwendung wurden die notwendigen Konvertierungen automatisch von den Prozeduren readln, write und writeln vorgenommen. Deshalb mussten Sie dort nicht selbst konvertieren. Das komplette Programm zeigt Abbildung 2.24.

Abbildung 2.24: Die Methode zur Berechnung des Nettowerts in Kylix

82

   

Sandini Bib

2.4.6 Umgehen mit ungültigen Eingaben Genau wie die Konsolen-Variante dieses Programms reagiert auch die neue Anwendung allergisch auf ungültige Eingaben. Geben Sie beispielsweise einen Text ein, der nicht in eine Zahl konvertiert werden kann, erzeugt das Programm eine Ausnahme (Abbildung 2.25).

Abbildung 2.25: Ausnahme aufgrund einer ungültigen Eingabe in einem Kylix-Programm

Anders als bei der Konsolenanwendung wird das Programm aber nicht beendet. Der Debugger hält das Programm zwar wieder an, wenn Sie dort aber einfach (F9) betätigen, können Sie noch einmal eingeben und die Berechnung erneut ausführen. Normalerweise sollten Sie solche Ausnahmen abfangen, aber diese Technik erläutere ich erst in Kapitel 4. Wird die Anwendung übrigens direkt ausgeführt (über den Aufruf der erzeugten ausführbaren Datei), wird die Ausnahme zwar auch gemeldet. Das Programm springt dann aber nicht in den Debugger. Und das reicht für die erste Anwendung mit grafischer Oberfläche vollkommen aus.

2.4.7 Das Programm für den Beenden-Schalter Um das Programm nun abzuschließen, müssen Sie noch die Funktionalität des BEENDEN-Schalters programmieren. Klicken Sie dazu wieder doppelt auf den Schalter. Die erzeugte Methode programmieren Sie folgendermaßen: 11 12 13 14

procedure TStartForm.btnBeendenClick(Sender: TObject); begin Close(); end;

Sie rufen damit eine vordefinierte Methode des Formulars auf, die das Fenster schließt und die Anwendung damit beendet.

        

83

Sandini Bib

2.5

Grundlagen zum Umgang mit der Delphi/ Kylix-Entwicklungsumgebung

Die Borland-Entwicklungsumgebung ist sehr mächtig und kann im Rahmen dieses Buchs nicht erschöpfend behandelt werden. Damit Sie mit Ihren Projekten einigermaßen gut umgehen können, zeige ich aber wenigstens die wichtigsten von den Dingen, die Sie noch nicht kennen. Ein wichtiges Tool der Entwicklungsumgebung ist die Projektverwaltung, die Sie über das VIEW- bzw. ANSICHT-Menü oder über (Strg) (Alt) (F11) öffnen können. Über die Projektverwaltung erreichen Sie alle Dateien Ihres Projekts. Klicken Sie einfach doppelt auf den jeweiligen Eintrag, um die entsprechende Datei zu öffnen.

Die Projektverwaltung

Abbildung 2.26: Die Projektverwaltung von Delphi

(Strg) (Alt) + Funktionstaste schaltet unter Linux zu einer der Konsolen des Betriebssystems um. Deshalb funktioniert keine der Tastenkombinationen mit (Strg) (Alt) + Funktionstaste innerhalb von Kylix. Linux selbst startet mit sechs vordefinierten Konsolen, Desktops wie KDE und GNOME laufen auf der siebten. Die elfte, die Sie mit (Strg) (Alt) (F11) erreichen würden, ist per Voreinstellung nicht vorhanden, weswegen Linux nach der Betätigung dieser Tasten einen schwarzen Bildschirm anzeigt. Über (Strg) (Alt) (F6) kommen Sie wieder zu Ihrem Desktop zurück. Eine schnelle Übersicht über Ihre Formulare erhalten Sie über das Formulare-Fenster, das Sie mit (ª) (F12) öffnen können. Bei der Eingabe in Programm-Fenster können Sie ein nettes Feature von Delphi/Kylix nutzen: Wenn Sie nur den Anfang eines Namens eingeben und dann (Strg) (Leertaste) betätigen, öffnet die Entwicklungsumgebung ein Fenster mit einer Liste aller Möglichkeiten, die Sie an dieser Stelle verwenden können. Geben Sie z. B. nur „txt“ ein und betätigen (Strg) (Leertaste), finden Sie alle Textboxen in der Liste (womit die Namenskonvention schon einen großen Vorteil zeigt).

Die automatische Syntaxhilfe

84

   

Sandini Bib

Abbildung 2.27: Die Eingabehilfe von Delphi

Sie können nun einen Eintrag in der Liste auswählen (z. B. über die Cursortasten). Dann schreiben Sie einfach den Text weiter, der dem ausgewählten Namen folgen soll, im Beispiel also einen Punkt. Delphi und Kylix ergänzen Ihre Eingabe automatisch durch den ausgewählten Eintrag. Wenn Sie den Punkt schreiben, öffnet sich die Liste gleich wieder, dieses Mal automatisch. Dann sehen Sie alle Eigenschaften des Steuerelements und können diese wieder auswählen. Sie können auch die Anfangsbuchstaben des Namens eingeben, um den passenden Eintrag zu aktivieren. Schreiben Sie dann wieder einfach den Text, der dem ausgewählten Eintrag folgen soll.

2.6

Hello World in Java

Für Java verzichte ich zunächst auf eine Entwicklungsumgebung, damit Sie lernen, Java-Programme in einem einfachen Editor zu schreiben, „von Hand“ zu kompilieren und auszuführen.

2.6.1 Entwicklung einer Konsolenanwendung Das erste Java-Programm ist wieder eine einfache Konsolenanwendung. Dieses Programm schreiben Sie ganz ohne eine Entwicklungsumgebung und kompilieren es über den Java-Compiler. Erzeugen Sie dazu zunächst einen Ordner für Java-Projekte und einen Ordner für das neue Projekt in Ihrem Projekte-Ordner. Nennen Sie den Projektordner vielleicht hello. Unter Windows sollte dies nun der Ordner C:\Projekte\Java\Hello sein, unter Linux der Ordner /projekte/java/hello.

 

85

Sandini Bib

Unter Windows können Sie zur Erstellung der Java-Datei den StandardTexteditor verwenden. Ich verwende allerdings dazu den freien Programmeditor JCreator LE, den Sie auf der Buch-CD oder im Internet an der Adresse www.jcreator.com finden. Dieser hervorragende Editor bietet einige wichtige Hilfsmittel wie ein Syntax-Highlighting, bei dem die einzelnen Bestandteile eines Programms in unterschiedlichen Farben dargestellt werden, und eine Möglichkeit, ein Programm direkt über den Editor zu kompilieren und zu starten. Leider läuft dieser Editor nur unter Windows. Um den Dateinamen beim Speichern korrekt angeben zu können, sollten Sie allerdings darauf achten, dass Windows in Datei-Dialogen alle Dateiendungen anzeigt.6 Standardmäßig zeigt Windows die Endungen „registrierter“ Dateitypen nicht an. Zu den registrierten Dateien gehören beispielsweise Word-, Excel- und Bilddateien, aber eben auch Textdateien. Abgesehen davon, dass das Verbergen der Dateiendung registrierter Dateien in meinen Augen absolut keinen Sinn macht und oft zu Verwirrungen führt6: Im Windows-Editor führt diese Einstellung dazu, dass dieser beim Speichern einer Datei die Endung .txt immer automatisch an den Dateinamen anhängt. Wenn Sie eine Datei beispielsweise hello.java nennen, nennt der Editor diese hello.java.txt. Wenn Sie diese Datei dann kompilieren wollen, wundern Sie sich, dass der Compiler die Datei nicht findet. Sie sollten diese Einstellung auf jeden Fall korrigieren, falls Ihr System Dateiendungen registrierter Dateien nicht anzeigt. Wählen Sie dazu im Explorer den Befehl ORDNEROPTIONEN im EXTRAS-Menü. Klicken Sie auf das Register ANSICHT und schalten Sie die Option DATEINAMENERWEITERUNG BEI BEKANNTEN DATEITYPEN AUSBLENDEN aus. Unter Linux verwenden Sie einen der vielen Texteditoren (vi, emacs, kedit, kwrite etc.) zur Erzeugung der Java-Datei. Da ich eher aus der Windows-Welt stamme, verwende ich kwrite, den erweiterten Texteditor der KDE. Dieser Texteditor stellt Java-Programme mit einem sehr hilfreichen Syntax-Highlighting dar. Speichern Sie die neue Datei im neuen Ordner unter dem Namen hello.java. Achten Sie dabei auf die Kleinschreibung des Dateinamens. Java unterscheidet Groß- und Kleinschreibung. 6.

86

Bei Access-Datenbanken, die die Endung .mdb tragen, existiert beispielsweise oft eine gleichnamige Datei mit der Endung .ldb. Wenn Sie die Endung nicht sehen, wissen Sie nicht, welche Datei die Datenbankdatei ist.

   

Sandini Bib

In dieser Textdatei schreiben Sie nun das erste Programm: 01 public class hello 02 { 03 public static void main(String[] args) 04 { 05 System.out.println("Hello World"); 06 } 07 }

Achten Sie dabei auf die Schreibweise der einzelnen Bezeichner. Wie bereits gesagt, unterscheidet Java Groß- und Kleinschreibung. Wenn Sie beispielsweise versehentlich „system“ statt „System“ schreiben, meldet der Compiler beim Kompilieren einen Fehler (siehe Seite 90). Damit Sie den Quelltext einigermaßen verstehen, folgen hier einige Erläuterungen: Die erste Zeile leitet eine Klasse ein. Sie wissen jetzt wahrscheinlich nicht, was eine Klasse ist. Das ist auch gut so, denn wüssten Sie dies, müssten Sie dieses Buch nicht lesen. Java ist komplett objektorientiert. Klassen sind die Basis der objektorientierten Programmierung. Eine Klasse enthält – vereinfacht gesagt – Programmcode, der thematisch zusammengehört. Auf die objektorientierte Programmierung gehe ich noch später, in Kapitel 6, ein. Zunächst müssen diese Erläuterungen ausreichen. Eine Java-Anwendung besteht immer mindestens aus einer Klasse, mit der die Anwendung startet. Diese Klasse muss übrigens denselben Namen tragen wie die Datei, in der sie gespeichert ist (allerdings ohne Endung), wobei Sie auch wieder auf die Groß-/Kleinschreibung achten müssen.

Die Start-Klasse

In Zeile 2 wird über die geschweifte Klammer ein Block eröffnet, der in der letzten Zeile wieder abgeschlossen wird. Alle Anweisungen innerhalb dieser Klammern gehören zu diesem Block. Der erste Block im Hello-World-Programm steht für alles, was die Klasse beinhaltet. In der dritten Zeile wird eine Methode eingeleitet. Eine Methode gehört zu einer Klasse und enthält Programm-Anweisungen. Eine Klasse kann mehrere Methoden besitzen, die dann bei der weiterführenden Programmierung zur Strukturierung eines Programms und zur Wiederverwendung von Programmteilen eingesetzt werden. Diese Methoden erläutere ich in Kapitel 5 und 6. Genau wie bei der Klasse werden auch die Anweisungen der Methode in geschweifte Klammern eingeschlossen.

 

Methoden

87

Sandini Bib

Die Methode der Klasse des Beispiels besitzt eine besondere Bedeutung. Java-Programme starten immer mit einer Methode, die main heißt und die genauso aufgebaut ist wie die Methode im Beispiel. Der Inhalt der Klammern (String[] args) bezieht sich dabei auf Argumente, die Sie beim Aufrufen des Programms später übergeben können. Diese Argumente vernachlässige ich allerdings in diesem Kapitel. Innerhalb der Methode gibt das Programm den Text "Hello World" an der Konsole aus. Es nutzt dazu auch wieder eine Methode, nämlich die Methode println der Klasse out. Diese Klasse, die ein Bestandteil der JavaKlassenbibliothek ist, dient der Ausgabe von einfachen Daten an der Konsole und besitzt entsprechende Methoden. Die println-Methode gibt beispielsweise einen Text aus, der dieser Methode in den runden Klammern übergeben werden muss. Das „ln“ am Ende des Namens der println-Methode steht übrigens für „Line“: println gibt (im Gegensatz zu print) hinter dem Text noch einen Zeilenumbruch aus. Weil die outKlasse in einer Java-Bibliothek gespeichert ist, müssen Sie diese bei der Verwendung der Klasse noch zusätzlich angeben. Deswegen steht noch System vor der Klasse. Wenn Sie die Datei nun speichern, können Sie diese kompilieren.

2.6.2 Kompilieren des Programms Kompilieren unter Windows Unter Windows öffnen Sie zum Kompilieren idealerweise die Konsole und wechseln in den Ordner, in dem die Java-Datei gespeichert ist (falls Sie nicht mehr wissen, wie das geht: Geben Sie den Laufwerknamen ein und betätigen Sie (¢), falls die Klasse auf einem anderen Laufwerk gespeichert ist. Wechseln Sie gegebenenfalls mit cd \ in den Stammordner und dann mit cd Ordnername in den Ordner, in dem Sie die Datei gespeichert haben): cd \projekte\java\hello

88

   

Sandini Bib

Sie können den Windows-Explorer so einrichten, dass Sie jederzeit vom Explorer aus die Konsole für einen bestimmten Ordner öffnen können. Wählen Sie dazu den Befehl ORDNEROPTIONEN im EXTRASMenü des Explorers. Wechseln Sie zum Register DATEITYPEN und suchen Sie den Eintrag mit dem Dateityp Dateiordner (ab Windows Version NT 4 finden Sie in der linken Spalte als Erweiterung zu diesem Eintrag den Text N. ZUTR). Markieren Sie diesen Eintrag und betätigen Sie den Schalter ERWEITERT oder BEARBEITEN (je nach WindowsVersion). Im erscheinenden Dialog zur Bearbeitung von Dateitypen betätigen Sie den Schalter NEU. Geben Sie im Feld VORGANG dann eine Bezeichnung ein, z. B. „Konsole“. Im Feld ANWENDUNG FÜR DIESEN VORGANG geben Sie nun unter Windows NT, 2000 und XP Folgendes ein: C:\Winnt\System32\cmd.exe /K cd %1. Unter Windows 95, 98 und Me müsste die Einstellung folgendermaßen aussehen: C:\Windows\command.com /K cd %1. Den Pfad zu Ihrem Kommandointerpreter (cmd.exe, command.exe bzw. command.com) müssen Sie natürlich gegebenenfalls für Ihr System anpassen. Suchen Sie diese Datei dann idealerweise über den Windows-Suchen-Dialog. So wissen Sie auch, wie Ihr Kommandointerpreter wirklich heißt. Nachdem Sie diese Einstellungen vorgenommen haben, können Sie im Explorer einen beliebigen Ordner mit der rechten Maustaste anklicken und im Kontextmenü den neuen Befehl ausführen. Windows öffnet dann die Konsole und springt direkt in diesen Ordner. Zum Kompilieren rufen Sie nun den Java-Compiler unter Angabe des Dateinamens auf: javac hello.java

Wenn der Compiler keine Fehler meldet, ist Ihr erstes Programm fertig. Die Auswertung eventueller Fehler beschreibe ich nach dem Kompilieren unter Linux. Kompilieren unter Linux Unter Linux kompilieren Sie die .java-Datei in einer Shell. Um die Standard-Shell direkt in dem Ordner zu öffnen, das gerade im Konqueror der KDE angezeigt wird, können Sie dort einfach (Strg) (T) betätigen. Wechseln Sie in der Shell gegebenenfalls mit cd Ordnername in den Ordner, das die Datei speichert. Dort rufen Sie dann den Java-Compiler unter Übergabe des Dateinamens auf. Bei der Angabe des Dateinamens

 

89

Sandini Bib

müssen Sie den aktuellen Pfad in den Dateinamen aufnehmen, was durch die Angabe eines Punktes (der in Linux – und Windows – für den aktuellen Pfad steht) geschieht: javac ./hello.java

Auswertung eventueller Fehlermeldungen Natürlich sind Ihre Programme – genau wie meine – nicht immer (oder bei komplexeren Programmen eigentlich nie) fehlerfrei. Enthält das Programm Fehler, kompiliert der Compiler das Programm nicht und meldet die Fehler. Um dies zu demonstrieren, verändere ich den Programmcode des Beispiels ein wenig: 01 public class hello 02 { 03 public static void main(String[] args) 04 { 05 system.out.println("Hello World"); 06 } 07 }

Beim Kompilieren meldet javac nun einen Fehler (Abbildung 2.28).

Abbildung 2.28: javac-Fehlermeldung unter Linux

Diese Fehlermeldung ist recht eindeutig. Im Beispiel werden Sie die fehlerhafte Kleinschreibung des Namens System sehr schnell finden. Andere Fehlermeldungen sind oft leider weniger sprechend. Dann müssen Sie beim Anpassen des Quellcodes mehr oder weniger probieren. Hilfreich ist auf jeden Fall, dass der Compiler die Fehlerzeile (im Beispiel ist das Zeile 5) und die Position des Fehlers anzeigt.

90

   

Sandini Bib

2.6.3 Starten des Programms Wenn Sie das Programm erfolgreich kompiliert haben, hat der Compiler eine Datei mit dem Namen java.class erzeugt. Das ist die kompilierte Version Ihres Programms. Da Java-Programme nicht direkt vom Betriebssystem ausgeführt werden können (wozu Sie in Kapitel 3 mehr erfahren), starten Sie dieses Programm über den Java-Interpeter. Geben Sie dazu einfach java hello ein. Der Java-Interpreter sucht in diesem Fall eine Datei mit dem Namen hello.class und führt diese aus. Wenn Sie ein Java-Programm unter Windows oder Linux ausführen und bei der Angabe der auszuführenden Datei einen Pfad mit angeben (z. B. javac ./hallo oder javac /projekte/java/hello/hello), startet das Programm bei Ihnen eventuell mit der schwer nachvollziehbaren Ausnahme java.lang.NoClassDefFoundError. Diese Ausnahme, die vielfältige Ursachen haben kann, wird erzeugt, wenn der Java-Interpreter eine benötigte Klasse nicht findet. Es scheint so zu sein, dass der Java-Interpreter die Klassen der Java-Bibliothek nicht findet, wenn die Dateiangabe einen Pfad enthält. Starten Sie java also immer aus dem Ordner, in dem die .class-Datei gespeichert ist und geben Sie keinen Pfad an. Wenn keine Fehler auftreten, gibt das Programm „Hello World“ an der Konsole aus (Abbildung 2.29)

Abbildung 2.29: Das Java-Programm wurde unter Windows kompiliert und aufgerufen.

Auch nach dem Starten des Programms kann es wie in allen Programmen zu Fehlern kommen. Ein häufiger Fehler ist z. B., dass das Java-Programm die vom Java-Interpreter erwartete main-Methode nicht besitzt oder diese Methode falsch programmiert wurde. Im folgenden Programm fehlen beispielsweise die Argumente dieser Methode:

 

Fehler im Programmablauf

91

Sandini Bib

01 public class hello 02 { 03 public static void main() 04 { 05 System.out.println("Hello World"); 06 } 07 }

Kompilieren können Sie das Programm ohne Probleme. Beim Aufruf tritt aber die Ausnahme NoSuchMethodError auf (Abbildung 2.30).

Abbildung 2.30: Beim Aufruf des Programms meldet Java einen Fehler, weil die Methode main falsch programmiert wurde.

Ausnahmen sind in einfachen Java-Programmen recht selten. Die einzige Ausnahme, die zurzeit möglich ist, ist die, dass die main-Methode nicht korrekt programmiert wurde. Im nächsten Abschnitt erfahren Sie, wie Sie Anwendungen mit Formularen in Java programmieren. Dann können natürlich auch Ausnahmen auftreten, wenn Sie numerische Eingaben erwarten, diese in Zahlen umkonvertieren und der Anwender ungültige Daten eingibt (wie bei Delphi und Kylix).

2.7

Hello World in Java mit Sun ONE Studio 4

Damit Sie lernen, mit Sun ONE Studio 4 umzugehen, entwerfen Sie nun das Hello-World-Programm mit Hilfe dieser Entwicklungsumgebung.

92

   

Sandini Bib

Sun ONE Studio 4 ist sehr komplex. Sie können bei der Entwicklung eines Java-Programms mit Sun ONE Studio 4 recht viele verschiedene Wege gehen und eine Vielzahl an Features nutzen. Ich kann den Umgang mit dieser Entwicklungsumgebung im Rahmen dieses Buchs nur ansatzweise beschreiben und zeige deshalb einen Weg zur Erstellung eines Programms, den ich für den Anfang für möglichst einfach und ideal halte. Leider ist dieser Weg trotzdem ein wenig komplizierter, als ich es gerne hätte. Ich wollte aber nicht auf die grundsätzliche Beschreibung von Sun ONE Studio 4 verzichten, weil Sie mit dieser Entwicklungsumgebung sehr wichtige Hilfsmittel beim Schreiben von Programmen und vor allen Dingen einen Debugger zum Testen Ihrer Programme (den ich in Kapitel 4 beschreibe) besitzen. Wenn Sie intensiver mit Sun ONE Studio 4 programmieren wollen, müssen Sie sich wohl noch ein wenig mehr mit den Konzepten dieser Entwicklungsumgebung beschäftigen. Die Tastenkombinationen, die ich in diesem Abschnitt beschreibe, finden Sie in einer übersichtlichen Form auch im Anhang. Sun ONE Studio 4 benötigt scheinbar sehr viele Systemressourcen. Wenn Sie gleichzeitig mit dieser Entwicklungsumgebung andere Programme geöffnet haben, kann es schon einmal passieren, dass diese oder das gesamte System abstürzen (was bei mir allerdings nur unter Windows der Fall war). Speichern Sie also Ihre aktuelle Arbeit immer ab, wenn Sie mit Sun ONE Studio 4 arbeiten. Die Editoren von Sun ONE Studio 4 sind zudem leicht „buggy“ (fehlerbehaftet). Es kann schon einmal passieren, dass der Quellcode- oder der Formulareditor nicht auf das Klicken mit der Maus reagieren, sodass Sie nicht die Möglichkeit haben, Ihr Programm weiter zu bearbeiten. In einigen Fällen hilft dann ein Schließen und erneutes Öffnen der entsprechenden Datei. Häufig müssen Sie Sun ONE Studio 4 aber auch komplett neu starten. Unter Linux funktionieren zudem (wie bei Kylix) einige der Tastenkombinationen, die ich in im weiteren Verlauf beschreibe, nicht, wenn Sie Sun ONE Studio 4 in der KDE starten. Das liegt daran, dass die KDE einige Tastenkombinationen für sich in Anspruch nimmt. Beachten Sie dazu meine Hinweise auf Seite 50.

        

93

Sandini Bib

2.7.1 Erzeugung eines neuen Projekts Nachdem Sie Sun ONE Studio 4 gestartet und den eventuellen Willkommensdialog geschlossen haben, sieht die Entwicklungsumgebung in etwa aus wie in Abbildung 2.31.

Abbildung 2.31: Sun ONE Studio 4

Projekte In Sun ONE Studio 4 arbeiten Sie wie bei Delphi und Kylix mit einem Projekt. Das Projekt fasst (zumindest) alle Dateien zusammen, die zu einem Programm gehören. Beim ersten Start hat die Entwicklungsumgebung bereits ein Projekt geöffnet, das als Default bezeichnet wird. Dieses Projekt ist auch gleich ein Beweis dafür, dass Sun ONE Studio 4 etwas anders mit dem Begriff Projekt umgeht als andere Entwicklungsumgebungen (wie z. B. Delphi und Kylix). Das Default-Projekt besteht nämlich per Voreinstellung aus einem Ordner sampledir, der gleich mehrere Unterordner mit unterschiedlichen Programmen enthält. Sie können in einem Sun ONE Studio 4-Projekt also beliebig viele Programme verwalten.

Projekte verwalten die Dateien eines Programms

Wenn Sie allerdings nur ein Programm in einem Projekt verwalten, ermöglicht dies projektspezifische Compiler-Einstellungen und erleichtert das Kompilieren und Starten des Programms. Außerdem ist das Einbinden von externen (nicht zum Projekt gehörenden) Bibliotheken, das

94

   

Sandini Bib

ich in Kapitel 5 beschreibe, nur über ein Einzelprogramm-Projekt möglich. Wenn Sie mehrere Programme in einem Projekt verwalten, müssen Sie zum Kompilieren und Starten immer erst den Ordner auswählen, in dem das Programm gespeichert ist, und Sie können keine externen Bibliotheken einbinden. Erzeugen eines neuen Projekts Sie sollten für Ihre Java-Programme jeweils eigene Projekte anlegen, auch wenn diese zurzeit noch recht einfach strukturiert sind. Wenn Sie sich an die Arbeit mit Projekten gewöhnen, haben Sie weniger Probleme bei der Programmierung.

Projekt erzeugen

Erzeugen Sie für das erste mit Sun ONE Studio 4 erstellte Programm also ein neues Projekt. Dazu verwenden Sie den Projektmanager, den Sie über das PROJECT-Menü öffnen können. Legen Sie über den NEW-Schalter ein neues Projekt an, das Sie vielleicht HelloOne nennen. Über den Projektmanager können Sie Ihre Projekte später wieder öffnen. Sun ONE Studio 4 startet aber auch immer mit dem zuletzt aktuellen Projekt, sodass Sie beim nächsten Start direkt an diesem Projekt weiterarbeiten können. Sun ONE Studio 4 verwaltet die Einstellungen aller Projekte in einem Ordner, der so benannt ist wie das jeweilige Projekt und der im Ordner system\projects im Benutzerordner angelegt wird. Unter Windows haben Sie den Benutzerordner beim ersten Start der Entwicklungsumgebung angeben müssen. Wenn Sie meinem Vorschlag :_Artikel „Installation“ gefolgt sind, ist das der Ordner C:\Dokumente und Einstellungen\\Eigene Dateien\OneStudio4 (sofern C: Ihr Systemlaufwerk ist). Unter Linux verwendet Sun ONE Studio 4 automatisch den Ordner ffjuser40ce in Ihrem Home-Ordner. Wenn Sie nichts weiter machen und in diesem Projekt neue Klassen anlegen, werden diese in dem Ordner system\projects\\Files im Benutzerordner gespeichert. Damit haben Sie immer alle Dateien, die zu einem Projekt gehören, und die zugehörigen Einstellungen gemeinsam in einem Ordner gespeichert. Eigentlich ist das auch eine Vorgehensweise, die Sie bevorzugen sollten, wenn Sie konsequent mit Sun ONE Studio 4 arbeiten. Dummerweise passt das nicht in unser Konzept, alle Projekte in einem speziellen Ordner zu speichern, auf das alle Benutzer Zugriff haben. Unter Windows hätten Sie zwar bei der Installation einen solchen allgemeinen Ordner für die Benutzerdaten angeben können (was Sie übrigens auch nachträglich über die Windows-Registry ändern können). Schauen Sie sich aber einfach einmal die vielen Ordner an, die Sun ONE Studio 4 im Benutzerordner angelegt hat, dann wis-

        

95

Sandini Bib

sen Sie, warum ich dieses Vorgehen nicht bevorzuge. Unter Linux können Sie den Benutzerordner erst gar nicht anpassen. Ich gehe also einen anderen Weg. Zur Verwaltung der Dateien eines Projekts können Sie auch einen spezifischen Ordner mit dem Projekt verbinden („mounten“). Wählen Sie dazu den Befehl MOUNT FILESYSTEM im FILE-Menü. Klicken Sie dann auf den Eintrag LOCAL DIRECTORY im erscheinenden NEW WIZARD-Dialog. Nachdem Sie den NEXT-Schalter betätigt haben, können Sie den Ordner auswählen, in dem Sie die Dateien des Projekts verwalten wollen.

Ordner mounten

Wenn Sie meinem Vorschlag bei der Entwicklung der ersten JavaKonsolenanwendung gefolgt sind, besitzen Sie bereits einen Ordner \Projekte\Java (Abbildung 2.32).

Abbildung 2.32: Auswahl eines Ordners zur Verbindung mit einem Projekt

Sie können über den New Wizard den für das Projekt benötigten Unterordner anlegen, indem Sie auf das Ordnersymbol (das dritte von rechts) klicken. Klicken Sie danach einmal auf den neuen Ordner und geben Sie den Namen ein. Für das Beispiel eignet sich der Name HelloOne, da Sie bereits ein Projekt Hello besitzen. Wählen Sie den Ordner nun aus und betätigen Sie den FINISH-Schalter, um diesen mit dem Projekt zu verbinden. Der neu verbundene Ordner wird im FILESYSTEMS-Register des Explorer-Fensters angezeigt.

96

   

Sandini Bib

Beachten Sie, dass Sun ONE Studio 4-Projekte nun in zwei verschiedenen Ordnern verwaltet werden. Ein Ordner speichert die Java-Dateien, im Sun ONE Studio 4-Benutzerordner werden die Projekteinstellungen verwaltet. Für Ihre ersten einfachen Projekte ist das aber nicht weiter schlimm. Sie sollten auf jeden Fall die Java-Dateien sichern, wenn Sie ein Backup Ihrer Arbeit machen. Da Sie zurzeit noch keine wesentlichen Änderungen an den Projekteinstellungen vornehmen, können Sie die Projekte im Notfall recht einfach wiederherstellen, indem Sie neue Projekte erzeugen und mit den bereits vorhandenen Projektordnern verbinden. Durch diese Möglichkeit, Ordner mit einem Projekt zu verbinden, können Sie übrigens auch mehrere Programme in einem Projekt zusammenfassen, so wie es im Default-Projekt für die Beispieldateien der Fall ist.

2.7.2 Anlegen einer Start-Klasse für das Projekt In dem neuen Projekt legen Sie nun eine Start-Klasse an. Beachten Sie, dass es unter Sun ONE Studio 4 (leider) eine Vielzahl an Wegen gibt, einem Projekt eine Datei hinzuzufügen. Einige davon führen nicht zum wahrscheinlich erwarteten Ergebnis. Ich kann hier nicht alle Wege beschreiben. Gehen Sie also bitte genauso vor, wie ich es hier zeige. Aktivieren Sie zunächst das Projekt-Register im Explorer und wählen Sie das Projekt aus (Abbildung 2.33).

Abbildung 2.33: Auswahl des Projekts im Sun ONE Studio 4-Explorer

Nun können Sie zwei Wege gehen, um dem Projekt eine neue Datei hinzuzufügen. Über den Befehl NEW im File-Menü fügen Sie die Datei in

        

97

Sandini Bib

den Sun ONE Studio 4-Projektordner (im Benutzerordner) hinzu (nicht in den mit dem Projekt verbundenen Ordner!). Mit diesem Befehl können Sie später das Projekt auswählen, dem die Datei hinzugefügt werden soll (Abbildung 2.35). Das aktuelle Projekt ist voreingestellt, Sie können aber auch eines der anderen bereits vorhandenen Projekte auswählen oder einen der vielen anderen Speicherorte auswählen, die der Dialog Ihnen anbietet. Wenn Sie hingegen mit der rechten Maustaste im Explorer auf das Projekt klicken und im Kontextmenü den Befehl ADD NEW wählen, speichern Sie die Datei ausschließlich im aktuellen Projekt und erhalten lediglich die Möglichkeit den Dateiordner auszuwählen (Abbildung 2.36). Zuvor wählen Sie aber unter den vordefinierten Vorlagen (Templates) eine für die neue Datei aus (Abbildung 2.34). Um dem Projekt eine StartKlasse für eine Konsolenanwendung hinzuzufügen, wählen Sie die Vorlage MAIN im CLASSES-Ordner.

Abbildung 2.34: Auswahl einer Vorlage für eine Konsolenanwendungs-Start-Klasse

Im nächsten Schritt geben Sie den Namen der neuen Klasse ein und wählen den Speicherort (das so genannte Paket) für diese Klasse aus. Wenn Sie zum Hinzufügen den Befehl NEW im FILE-Menü gewählt haben, können Sie aus der Vielzahl an grundsätzlichen Möglichkeiten wählen, wobei das aktuelle Projekt bereits voreingestellt ist (Abbildung 2.35).

Auswahl des Speicherorts in Variante 1

98

   

Sandini Bib

Abbildung 2.35: Auswahl des Projekts zur Speicherung der Datei

Haben Sie hingegen den Befehl ADD NEW im Kontextmenü des Projekteintrags gewählt, können Sie einen der verbundenen Dateiordner auswählen (Abbildung 2.36).

Auswahl des Speicherorts in Variante 2

Abbildung 2.36: Auswahl eines Dateiordners zur Speicherung der Datei unter Windows

Die erste Variante zum Hinzufügen (Abbildung 2.35) bietet im unteren Bereich auch die Möglichkeit, einen verbundenen Dateiordner auszuwählen. Wenn Sie aber diese Möglichkeit benutzen, wird die neue Datei nicht dem Projekt zugeordnet. Dann können Sie das Programm in der Entwicklungsumgebung nicht über das Projekt starten. Verwenden Sie also die zweite Variante, wenn Sie die neue Datei dem verbundenen Dateiordner hinzufügen wollen.

        

99

Sandini Bib

An dieser Stelle entscheidet sich auch, ob Sie Ihre Dateien im Projektordner im Benutzerordner von Sun ONE Studio 4 verwalten (Variante 1) oder in einem eigenen Projektordner (Variante 2). Diese Entscheidung überlasse ich Ihnen. Falls Sie den Benutzerordner von Sun ONE Studio 4 verwenden, können Sie den verbundenen Dateiordner wieder aus dem Projekt entfernen („unmounten“). Pakete

Da Sie nun ab hier immer wieder mit dem Begriff Paket (Package) konfrontiert werden, wollen Sie vielleicht wissen, was ein Paket ist. Also: Pakete sind ein spezielles Java-Konzept, über das Klassen thematisch organisiert werden. Eine Java-Datei enthält dazu die package-Anweisung, wenn die enthaltenen Klassen einem speziellen Paket zugeordnet sein sollen: package Paketname;

Damit können Klassen beliebigen Paketen zugeordnet werden, auch solchen, die noch gar nicht existieren. Ein Paket ist lediglich eine logische Organisationsform. Es kann durchaus sein, dass die Klassen eines Pakets in unterschiedlichen Dateiordnern gespeichert sind. Durch das Paketkonzept erleichtert Java zum einen das Finden von Klassen in einer Bibliothek. Da Pakete normalerweise thematisch organisiert sind, müssen Sie lediglich in der Dokumentation in einem zu Ihrem Problem passenden Paket suchen, um die benötigten Klassen zu finden. In der Java-Bibliothek sind alle Klassen z. B. in Paketen organisiert, deren Name mit java. beginnt. Das Paket java.print enthält beispielsweise Klassen, die zum Drucken verwendet werden. Der andere Vorteil von Paketen ist, dass so auch unterschiedliche Klassen mit gleichen Namen verwendet werden können, sofern diese in verschiedenen Paketen verwaltet werden. Auf das Paketkonzept komme ich noch einmal in Kapitel 4 bei der Besprechung der Benutzung der Java-Bibliothek zurück. Sun ONE Studio 4 betrachtet nun jeden mit einem Projekt verbundenen Dateiordner als ein Paket, obwohl das eigentlich nicht ganz korrekt ist (in einem Dateiordner können auch mehrere Java-Dateien gespeichert sein, die unterschiedlichen Paketen zugeordnet sind). In späteren Projekten werden Sie neben dem Ordner, der die Projektdateien enthält, auch weitere Ordner mit einem Projekt verbinden. Diese Ordner enthalten dann eigene Bibliotheken mit vorgefertigten Klassen, die Sie in verschiedenen Projekten wiederverwenden. Wenn Sie dem Sun ONE Studio 4-Konzept folgen, speichern Sie alle JavaDateien, die zu einem Paket gehören, in einem separaten Ordner. In Kapitel 5 zeige ich, wie Sie damit umgehen.

100

   

Sandini Bib

Im Feld PACKAGE können Sie nun einen eigenen Paketnamen eingeben. Sie sollten hier aber die Voreinstellung übernehmen. Wenn Sie in der zweiten Variante (Speicherung der Datei in einem verbundenen Ordner) einen eigenen Paketnamen eingeben, erzeugt Sun ONE Studio 4 zur Speicherung der Datei einen entsprechenden Unterordner im ausgewählten Dateiordner. Geben Sie nun noch gegebenenfalls einen Namen für die neue Klasse ein. Wenn Sie den Eintrag übernehmen, wird die Klasse mit Main benannt, was auch vollkommen in Ordnung ist. Wenn Sie nun den NEXT-Schalter betätigen, können Sie verschiedene Einstellungen für die Klasse vornehmen, was aber an dieser Stelle zu weit führen würde. Betätigen Sie also einfach den FINISH-Schalter. Sun ONE Studio 4 erzeugt nun eine Java-Datei mit einer Main-Klasse und einer main-Methode, ähnlich der, die Sie bereits von Hand erzeugt haben: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23

/* * Main.java * * Created on 3. August 2002, 11:29 */ /** * * @author Administrator */ public class Main { /** Creates a new instance of Main */ public Main() { } /** * @param args the command line arguments */ public static void main(String[] args) { } }

Wenn Sie die Datei im Sun ONE Studio 4-Benutzerordner verwalten, enthält diese noch eine zusätzliche Anweisung: package Projects.Projektname.Files;

        

101

Sandini Bib

Damit wird diese Datei einem entsprechenden Paket zugewiesen. Lassen Sie diese Anweisung in der Klasse stehen. Ohne eine korrekte Paketzuordnung können Sie das Programm in Sun ONE Studio 4 nicht starten und später auch nicht debuggen. Wenn Sie die Datei in einem speziellen Ordner verwalten, entfällt diese Zuweisung. Die Klasse wird dann im globalen Paket verwaltet, was auch vollkommen in Ordnung ist. Wenn Sie die Datei in einem verbundenen Ordner verwalten, legt Sun ONE Studio 4 im Projekt lediglich einen Verweis auf die Datei an. Deshalb können Sie das Programm dann aber auch über das Projekt starten. Im FILESYSTEMS-Register des Explorers finden Sie die neue Datei, wenn Sie den verbundenen Ordner öffnen. Sie können die Dateien eines Projekts jederzeit über das Projekt-Register oder das FILESYSTEMS-Register des Explorers öffnen. Unbekannte und überflüssige Anweisungen

Die automatisch erzeugte Datei enthält einige Anweisungen, die Sie wahrscheinlich noch nicht kennen. Alle Anweisungen, die in /* und */ eingeschlossen sind, sind Kommentare. Kommentare werden üblicherweise dazu verwendet, ein Programm zu dokumentieren, und werden vom Compiler nicht berücksichtigt. Sie können die wenig aussagekräftigen Kommentare der neuen Datei löschen, um mehr Übersicht zu erhalten. Das Schlüsselwort public vor der Klassendeklaration in Zeile 11 bewirkt, dass diese Klasse öffentlich ist. Klassen, die in Paketen organisiert sind, müssen öffentlich sein, damit sie von außerhalb verwendet werden können. Die Anweisungen in Zeile 14 und 15 definieren einen so genannten Konstruktor. Ein solcher wird lediglich bei „echten“ Klassen benötigt und kann daher gelöscht werden. Konstruktoren beschreibe ich in Kapitel 6. Auf das Wesentliche reduziert, sieht die Klasse dann so aus: 01 public class Main 02 { 03 public static void main(String[] args) 04 { 05 } 06 }

102

   

Sandini Bib

2.7.3 Programmieren und Starten des Programms Nun können Sie programmieren. Sie werden feststellen, dass Sun ONE Studio 4 einige hilfreiche Features besitzt. Neben einer farblichen Unterscheidung verschiedener Schlüsselwörter übernimmt der Editor beispielsweise das Einrücken von Programmcode fast automatisch für Sie. Ein anderes sehr hilfreiches Feature ist, dass Sun ONE Studio 4 Ihnen mehr oder weniger automatisch alle Möglichkeiten des aktuellen Quellcode-Kontextes anzeigt. Schreiben Sie beispielsweise nur „Sys“ und betätigen Sie danach (Strg) (Leertaste). Sun ONE Studio 4 zeigt eine Liste mit allen Elementen an, die mit „Sys“ beginnen und im aktuellen Kontext möglich sind.

Nützliche Features

Abbildung 2.37: Die Elementliste von Sun ONE Studio 4

Wählen Sie das benötigte Element aus der Liste aus (für unser Beispiel ist das der Eintrag SYSTEM). Anders als in Delphi und Kylix können Sie zum einfügen eines Elements in den Quellcode leider nicht einfach den Text weiter schreiben, der dem ausgewählten Element folgen soll. Sie müssen zur Auswahl eines Elements (¢) betätigen. Obwohl das im Vergleich zu Delphi und Kylix etwas umständlich ist, ersparen Sie sich damit einige Schreibarbeit und finden alle im Moment möglichen ProgrammElemente recht schnell. Wenn Sie den Punkt nach einem Klassen- oder Objektnamen schreiben, öffnet sich die Liste auch automatisch. Erzeugen Sie nun das einfache Programm, indem Sie über die printlnMethode „Hello World“ ausgeben. 01 public class Main 02 { 03 public static void main(String[] args) 04 { 05 System.out.println("Hello World"); 06 } 07 }

        

103

Sandini Bib

Kompilieren und Starten Sie können das Projekt nun über (Strg) (ª) (F9) kompilieren. Sun ONE Studio 4 kompiliert dann alle Dateien, die zum Projekt gehören, wobei allerdings immer nur die Dateien mit einbezogen werden, die seit dem letzten Kompilieren geändert wurden. Einzelne Dateien können Sie über (F9) kompilieren, indem Sie diese zuvor im Explorer markieren. Eventuelle Kompilierfehler zeigt Sun ONE Studio 4 im „Output Window“ an (Abbildung 2.38).

Abbildung 2.38: Sun ONE Studio 4 meldet Fehler beim Kompilieren, weil aus Versehen die out-Klasse großgeschrieben wurde.

Sehr hilfreich ist, dass Sun ONE Studio 4 die Fehlerzeile im Quellcode markiert, wenn Sie den Fehler im Output-Fenster anklicken. Sun ONE Studio 4 bietet wie viele andere Entwicklungsumgebungen neben dem Kompilieren auch ein Erzeugen an („to build“). Betätigen Sie dazu (Strg) (ª) (F11) für das gesamte Projekt und (F11) für einzelne Dateien. Der Unterschied ist, dass ein einfaches Kompilieren nur dann erfolgt, wenn der Quellcode seit dem letzten Kompilieren geändert wurde. Wenn Sie die Datei dagegen „erzeugen“, wird eine eventuell bereits vorhandene .class-Datei vor dem Kompilieren gelöscht. In einigen Fällen (vor allen Dingen in größeren Projekten), bei denen das Kompilieren nicht zum erwarteten Ergebnis führt, weil Sun ONE Studio 4 geänderte Dateien aus irgendwelchen Gründen nicht kompiliert, hilft ein explizites Erzeugen. Sie können das Projekt nun mit (Strg) (ª) (F6) starten, wobei auch gleich eine Kompilierung vorgenommen wird, sofern diese notwendig ist. Beim ersten Start müssen Sie zunächst die Klasse auswählen, mit der Ihr Programm starten soll. Diese Einstellung wird im Projekt für spätere Starts gespeichert. Alternativ können Sie auch mit (Strg) (ª) (F5) starten. Mit dieser Start-Variante besitzen Sie dann die Möglichkeit, das Programm zu debuggen, wie ich es in Kapitel 4 zeige.

104

   

Sandini Bib

Das Starten über (Strg) (ª) (F5) oder (Strg) (ª) (F6) funktionierte in meinem Fall unter Linux nicht immer, wobei ich nicht herausfinden konnte, woran das lag. Ein wiederholtes Betätigen dieser Tastekombinationen führte in einigen Fällen zum Erfolg. In anderen Fällen konnte ich ein Programm aber nur starten, indem ich die Startdatei im Explorer markierte und (F6) betätigte. (F6) steht für das Starten einer einzelnen Datei, was in Projekten verwendet wird, die mehr als eine ausführbare Datei besitzen. Die Ausgaben einer Konsolenanwendung erscheinen nicht in einer Konsole des Betriebssystems, sondern im „Output Window“ der Entwicklungsumgebung. Nach der Ausführung des Programms müssen Sie im Register der Entwicklungsumgebung (oben) auf EDITING klicken, um wieder zum Editierfenster zu gelangen. Sie können das Editierfenster aber auch alternativ im DEBUGGING-Register anzeigen, indem Sie dort (Strg) (3) betätigen. Dasselbe ist für den Explorer über (Strg) (2) möglich.

2.8

Grundlagen zum Umgang mit Sun ONE Studio 4

2.8.1 Pakete Wenn Sie eine Klasse direkt einem verbundenen Dateiordner unterordnen, wird diese per Voreinstellung in keinem speziellen, sondern dem globalen Paket verwaltet. Wenn Sie allerdings Klassen in untergeordneten Ordnern erzeugen, werden diese automatisch einem Paket zugeordnet, dessen Name vom Ordnernamen abgeleitet wird. Wenn Sie z. B. den Ordner C:\Projekte\Java\HelloOne (Windows) bzw. /usr/projekte/ java/HelloOne (Linux) mit einem Projekt verbinden und darin einen Ordner Start anlegen, werden alle in diesem Ordner angelegten Klassen dem Paket Start zugeordnet. Legen Sie in diesem Ordner einen Unterordner mit dem Namen Main an, werden die darin enthaltenen Klassen dem Paket Start.Main zugeordnet. Verwalten Sie Ihre Klassen hingegen im Benutzerordner von Sun ONE Studio 4, werden diese dem Paket Projects..Files zugeordnet. Das Ganze ist etwas kompliziert, aber leider notwendig zu wissen, denn:

        

105

Sandini Bib

Wenn Sie in Sun ONE Studio 4 ein Programm starten, wird dieses nur dann korrekt ausgeführt, wenn die Startklasse dem passenden Paket zugeordnet ist. Wenn Sie Klassen über Sun ONE Studio 4 anlegen, werden diese automatisch dem passenden Paket zugeordnet. Wenn Sie aber andere Klassen in ein Projekt integrieren, müssen Sie selbst auf eine korrekte Einstellung des Paketnamens achten. Wenn Sie dies unterlassen, meldet Sun ONE Studio 4 beim Start des Programms die Ausnahme NoClassDefFoundError mit dem Fehler „wrong name“. Hinzu kommt, dass Paketnamen keine Leerzeichen und andere Sonderzeichen beinhalten dürfen. Die Ordner müssen also entsprechend benannt sein. Wenn Sie bereits andere Java-Programme besitzen oder diese mit einer anderen Entwicklungsumgebung entwickeln, müssen Sie die entsprechenden Ordner zur Ausführung dieser Programme in Sun ONE Studio 4 mit einem neuen oder vorhandenen Projekt verbinden und die package-Anweisung in diesen Programmen entsprechend anpassen. Die Buchbeispiele konnte ich leider nicht so anpassen, dass Sie diese in einem einzigen verbundenen Ordner in Sun ONE Studio 4 darstellen und ohne Probleme aufrufen können. Neben der Tatsache, dass die Ordnernamen dann keine Leerzeichen beinhalten dürften, würden die resultierenden Paketnamen leider zu lang werden, sodass Sun ONE Studio 4 einige Programme in tief verschachtelten Ordnern nicht ausführen könnte. Legen Sie also ein neues Projekt für die Buchbeispiele und in diesem für jedes Beispielprogramm, das Sie in Sun ONE Studio 4 betrachten wollen, einen neuen verbundenen Ordner an.

2.8.2 Integration von externen Projekten Wenn Sie externe Projekte (beispielsweise die Beispiele dieses Buchs) in Sun ONE Studio 4 integrieren wollen, gehen Sie folgendermaßen vor: • Speichern Sie die Java-Dateien in einem Ordner Ihrer Festplatte, falls das noch nicht der Fall ist, • legen Sie in Sun ONE Studio 4 ein neues Projekt an, • verbinden Sie den Ordner der Java-Dateien im FILESYSTEMS-Register des Explorers mit diesem Projekt, • wechseln Sie zum Projekt-Register des Explorers, • klicken Sie mit der rechten Maustaste auf den Projekteintrag und wählen Sie den Befehl ADD EXISTING, • wählen Sie im erscheinenden Dialog die Klassen aus, die im verbundenen Dateiordner gespeichert sind. Sie können den gesamten Datei-

106

   

Sandini Bib

ordner oder mehrere einzelne Klassen auswählen, indem Sie beim Klicken mit der Maus (Strg) betätigen. Alternativ können Sie die erste Klasse auswählen, (ª) betätigen und danach auf die letzte Klasse klicken, • betätigen Sie den OK-Schalter, um Ihre Auswahl zu bestätigen, • falls Ihr Projekt mehrere Klassen enthält, klicken Sie die Start-Klasse nun im Explorer mit der rechten Maustaste an und wählen Sie den Befehl SET AS PROJECT MAIN CLASS im TOOLS-Menü des Kontextmenüs. Wenn Sie das Projekt nun mit (Strg) (ª) (F5) starten, müsste alles funktionieren.

2.9

Entwickeln einer Anwendung mit grafischer Oberfläche mit Sun ONE Studio 4

In Java können Sie nicht nur einfache Konsolenanwendungen programmieren. Java stellt auch eine Menge an Features für die Entwicklung von Anwendungen mit einer grafischen Oberfläche zur Verfügung, ähnlich denen, die Sie bereits in Delphi bzw. Kylix verwendet haben. Leider ist deren Programmierung nicht allzu einfach, was zumindest dann gilt, wenn Sie eine solche Anwendung ohne eine Entwicklungsumgebung entwickeln. Sun ONE Studio 4 stellt aber einen grafischen Editor zur Verfügung, der die Arbeit erheblich vereinfacht. Als Beispiel verwende ich die Nettoberechnung, die Sie bereits mit Delphi oder Kylix programmiert haben. So können Sie das, was Sie im ersten Teil des Kapitels gelernt haben, hier bereits anwenden. Java ist sehr komplex im Bereich der Programmierung von und mit Formularen und Steuerelementen. Ich kann aus Platzgründen hier nur einen kleinen Ausschnitt der Möglichkeiten zeigen. Damit sind Sie aber in der Lage, einfache Anwendungen mit einer grafischen Oberfläche zu entwickeln. Halten Sie sich möglichst an meine Anleitung, damit beim Programmieren nicht allzu viel daneben geht.

          

107

Sandini Bib

Ein erster Hinweis folgt direkt hier: Sun ONE Studio 4 funktioniert leider nicht immer zuverlässig. Neben dem scheinbar in einigen Fällen zu großen Ressourcen-Hunger, der dazu führen kann, dass andere Programme oder das ganze System (wenigstens unter Windows) abstürzen, werden Sie u. U. bei der Arbeit mit dem Formular- oder dem Quellcodeeditor Probleme haben. Es kann dabei schon einmal passieren, dass das Markieren von Steuerelemente über die Maus oder die Eingabe von Quellcode nicht möglich ist. In einigen Fällen hilft dann ein wiederholtes Öffnen der entsprechenden Daten. Manchmal müssen Sie aber Sun ONE Studio 4 komplett neu starten, um sinnvoll weiterarbeiten zu können.

2.9.1 Erzeugen des Projekts und des Startformulars Zur Entwicklung einer Anwendung mit grafischer Oberfläche erzeugen Sie zunächst ein neues Projekt, das Sie vielleicht Nettoberechnung nennen. Verbinden Sie dann einen neuen Ordner Nettoberechnung, den Sie in Ihrem Projekte-Ordner anlegen, mit diesem Projekt. Nun fügen Sie dem Projekt ein neues Fenster hinzu. Dazu selektieren Sie zunächst das Projekt im Projekt-Register des Explorers, klicken das Projekt mit der rechten Maustaste an und wählen wie bei der Konsolenanwendung den Befehl ADD NEW im Kontextmenü. Hier wählen Sie aber nun die Vorlage JFRAME aus dem Ordner GUI FORMS (Abbildung 2.39).

Abbildung 2.39: Auswahl eines JFrame-Formulars für eine Java-Anwendung

108

   

Sandini Bib

Ein JFrame ist ein Formular, das dem ähnlich ist, das Sie von Delphi bzw. Kylix her kennen. Dieses Formular eignet sich ideal für eine normale Anwendung mit Fenstern. Nachdem Sie den NEXT-Schalter betätigt haben, geben Sie im nächsten Schritt noch einen Namen ein. Nennen Sie das Formular vielleicht StartForm. Die folgenden Schritte können Sie übergehen, indem Sie einfach den FINISH-Schalter betätigen. Wenn das Formular hinzugefügt wurde, zeigt die Entwicklungsumgebung direkt den Form Editor an, mit dem Sie das Formular bearbeiten können. Diesen Editor erreichen Sie auch, indem Sie im Explorer auf das Formular doppelklicken. In einem zweiten Fenster wird eventuell der Quellcode des Formulars angezeigt. Diesen sollten Sie zunächst ignorieren. Java-Formulare werden – anders als Delphi/Kylix-Formulare – komplett als Quellcode implementiert. Damit haben Sie aber nicht viel zu tun, wenn Sie eine Entwicklungsumgebung verwenden. Diese bietet Ihnen einen einfachen Formular-Editor, den Sie mit der Maus bearbeiten können, und übernimmt die Erzeugung des Quellcodes automatisch für Sie.

Abbildung 2.40: Der Formulareditor von Sun ONE Studio 4

          

109

Sandini Bib

2.9.2 Entwurf des Formulars Als Erstes müssen Sie eine Einstellung ändern, damit Sie das Formular auf die gewohnte Weise entwerfen können. Java-Formulare arbeiten nämlich mit ziemlich komplizierten so genannten Layout-Managern, die das Layout eines Formulars automatisch einstellen. Der voreingestellte Layout-Manager flowLayout sorgt dafür, dass Ihre Steuerelemente immer automatisch auf die maximal mögliche Größe vergrößert werden. Und das ist eher störend als hilfreich. Klicken Sie zur Änderung des Layout-Managers mit der rechten Maustaste auf das Formular und wählen Sie den Befehl SET LAYOUT. Wählen Sie dann den Eintrag Null Layout (was dafür steht, dass kein Layout-Manager verwendet wird). Nun können Sie Steuerelemente auf dem Formular ablegen. Die Standard-Steuerelemente finden Sie im oberen Bereich des Formulareditors im Register SWING. Das erste Steuerelement in der Reihenfolge ist ein Label (JLabel) zur Beschriftung, das zweite ein normaler Schalter (JButton). Das neunte Steuerelement (JTextField) ist eine Textbox. Platzieren Sie drei Label, drei Textboxen und zwei Schalter auf dem Formular, indem Sie diese in der Toolbox anklicken und danach auf dem Formular klicken. Platzieren Sie diese Steuerelemente und stellen Sie die Größe ein, bis das Formular in etwa so aussieht wie in Abbildung 2.41.

Abbildung 2.41: Das Formular zur Nettoberechnung in einer ersten Rohfassung

Sie können die Steuerelemente auch auf dem Formular markieren und über (Strg) (c) in die Zwischenablage kopieren. Mehrer Steuerelemente markieren Sie, indem Sie beim Klicken (ª) betätigen. Das Einfügen mit (Strg) (v) funktioniert aber nur, wenn kein Steuerelement markiert ist. Klicken Sie dazu vor dem Einfügen auf einen freien Bereich des Formulars. Die eingefügten Steuerelemente werden über den Quell-Steuerelementen angelegt, so dass es so aussieht, als ob nichts passiert wäre. Ziehen Sie das eingefügte Steuerelement dann einfach an seinen Platz.

110

   

Sandini Bib

2.9.3 Einstellen der Eigenschaften Nun müssen Sie wie bei Delphi und Kylix zunächst einige Eigenschaften der Steuerelemente einstellen. Die wichtigste ist einmal wieder der Name der Steuerelemente. Wählen Sie die Steuerelemente dazu einzeln aus und klicken Sie im Eigenschaften-Fenster auf das Register CODE GENERATION. Den Namen stellen Sie in der Eigenschaft VARIABLE NAME ein (Abbildung 2.42).

Abbildung 2.42: Einstellung der Eigenschaft Variable Name für die erste Textbox

Vergeben Sie wieder sinnvolle Namen. Ich übernehme die Namen der Delphi/Kylix-Anwendung (txtBrutto, txtSteuer, txtNetto, btnRechnen und btnBeenden). Die Label benenne ich nicht, weil ich diese im Programm nicht anspreche. Nun stellen Sie noch den Text der Label, der Schalter und der Textfelder ein. Wählen Sie dazu das Register PROPERTIES im Eigenschaftenfenster und schreiben Sie entsprechende Werte in die Eigenschaft Text. Das Ergebnis soll so aussehen wie in Abbildung 2.43.

Abbildung 2.43: Das Formular zur Nettoberechnung im fertigen Entwurf

          

111

Sandini Bib

Stellen Sie dann die Eigenschaft title des Formulars ein, indem Sie auf einem freien Bereich des Formulars klicken und die Eigenschaft im PROPERTIES-Register des Eigenschaftenfensters suchen. Als Titel bietet sich natürlich „Nettoberechnung“ an. Verhindern, dass das Formular automatisch verkleinert wird

Abschließend sollten Sie noch dafür sorgen, dass das Formular beim Start nicht automatisch verkleinert wird, was ansonsten der Fall wäre. Bei markiertem Formular klicken Sie dazu auf das Register CODE GENERATION des Eigenschaftenfensters. Setzen Sie die Eigenschaft FORM SIZE POLICY auf GENERATE RESIZE CODE. Nun wird Ihr Formular immer auf die Größe eingestellt, die Sie in der Entwicklungsumgebung festlegen. Bei der Höhe müssen Sie allerdings die Höhe der hinzukommenden Titelleiste mit einrechnen. Gestalten Sie Ihr Formular also etwas höher als notwendig.

2.9.4 Programmieren Nun können Sie programmieren. Dazu klicken Sie wieder einfach doppelt auf den RECHNEN-Schalter. Sun One Studio 4 ist dabei etwas störrisch und öffnet in vielen Fällen die Beschriftung des Schalters zur Bearbeitung, anstatt den Quellcodeeditor zu öffnen. Probieren Sie es dann einfach mit einem schnelleren Doppelklick. Das funktioniert in den meisten Fällen. Falls Sie Probleme damit haben, können Sie die notwendige Methode auch über das Kontextmenü des Schalters erzeugen. Markieren Sie den Schalter, klicken Sie mit der rechten Maustaste darauf und wählen Sie den Befehl EVENTS / ACTION / ACTIONPERFORMED. Methode zur Ereignisbehandlung erzeugen

Die Entwicklungsumgebung erzeugt automatisch eine Methode zur Behandlung des Ereignisses „Betätigung des Schalters“ und zeigt den Quellcode des Formulars an. Lassen Sie sich nicht von dem komplizierten Code verwirren. Das, was Sie dort sehen, gehört bereits zu den erweiterten objektorientierten Techniken. Für Sie ist – wie bei Delphi und Kylix – lediglich wichtig, was Sie innerhalb der automatisch erzeugten Methoden programmieren. Bei dem Quellcode des Formulars habe sogar ich einige Probleme zu verstehen, was da wirklich passiert. Und ich programmiere nun schon einige Jahre.

112

   

Sandini Bib

Abbildung 2.44: Der Quellcode-Editor von Sun ONE Studio 4 mit einer Methode für die Betätigung des Rechnen-Schalters

Die blau hinterlegten Bereiche (in Abbildung 2.44 sind diese natürlich grau) stellen Quellcode dar, der von der Entwicklungsumgebung automatisch erzeugt wird und den Sie gar nicht bearbeiten können. So können Sie auch weniger Fehler machen. Nur den Quellcode in den weißen Bereichen können Sie bearbeiten. Die im unteren Bereich von Abbildung 2.44 sichtbare main-Methode zeigt übrigens, wie eine solche Anwendung startet, nämlich im Prinzip genau wie eine Konsolenanwendung. Nur dass hier das Formular erzeugt und geöffnet wird. Aber das gehört auch schon zu den erweiterten Techniken. Innerhalb der Methode btnRechnenActionPerformed können Sie nun programmieren. Zur Programmierung einer Nettoberechnung gehen Sie prinzipiell so vor wie in Delphi und Kylix. Zunächst benötigen Sie drei Variablen. In Java-Programmen deklarieren Sie diese einfach da, wo Sie sie benötigen. Die Deklaration sieht etwas anders aus als in Delphi und Kylix:

Programmierung der Berechnung

01 private void btnRechnenActionPerformed(java.awt.event.ActionEvent evt) { 02 /* Deklaration der benötigten Variablen */ 03 double brutto; 04 double steuer; 05 double netto;

Nun können Sie die eingegebenen Daten einlesen. Anders als in Delphi und Kylix lesen Sie hier keine Eigenschaft aus. Sie rufen zum Einlesen der Eingaben eine Methode auf. Die Methode getText gibt das, was in der

          

113

Sandini Bib

Textbox gespeichert ist, als Text zurück. Die Eingaben müssen Sie auch in Java konvertieren. Dazu verwenden Sie die Java-Methode Double. parseDouble, der Sie den zu konvertierenden Text übergeben. Diese Methode arbeitet fast genauso wie die Object Pascal-Funktion StrToFloat. Sie übergeben den zu konvertierenden Text (den Sie über die getText-Methode ermitteln) und erhalten eine Zahl zurück: 06 07

brutto = Double.parseDouble(txtBrutto.getText()); steuer = Double.parseDouble(txtSteuer.getText());

Nun folgen nur noch die Berechnung: 08

netto = brutto * (1 - (steuer / (100 + steuer)));

und die Ausgabe des Ergebnisses. Wie bei Object Pascal müssen Sie die Zahl nun wieder in einen Text konvertieren, damit Sie diesen in die Textbox schreiben können. Leider stellt Java keine einfache Funktion dazu zur Verfügung. In Delphi und Kylix konnten Sie die Funktion FormatFloat verwenden. In Java sieht der Quellcode zur Umwandlung und zur Formatierung einer Zahl etwas komplizierter aus: 09 java.text.DecimalFormat df = new java.text.DecimalFormat("0.00"); 10 txtNetto.setText(df.format(netto)); 11 }

Zunächst erzeugen Sie in Zeile 9 ein Objekt. In Kapitel 6 werden Sie lernen, was das ist und wie Sie damit umgehen. Dieses spezielle Objekt wird in Java dazu verwendet, dezimale Zahlen zu formatieren. Dazu verwenden Sie die format-Methode. Im Prinzip ist das das, was Sie auch in Delphi bzw. Kylix mit der FormatFloat-Funktion gemacht haben. Der Unterschied ist hier nur, dass Sie statt einer einfachen Funktion ein Objekt und dessen Methode verwenden. Falls Sie nun Probleme haben, dies zu verstehen, versuchen Sie es erst gar nicht weiter. Im weiteren Verlauf des Buchs lernen Sie, was ein Objekt ist. Dann wissen Sie ziemlich genau, was hier passiert. Nehmen Sie den Quellcode als Beispiel dafür, wie Sie in Java Zahlen formatiert ausgeben können. Ich musste dieses Feature übrigens selbst erst recherchieren ... Anders als in Delphi und Kylix können Sie auch nicht in eine Eigenschaft schreiben, um den Text eines Textfelds zu setzen. Dazu verwenden Sie die setText-Methode des Textfelds, wie Sie es in Zeile 10 sehen. Programmieren des BeendenSchalters

114

Nun müssen Sie nur noch die Methode für den BEENDEN-Schalter programmieren. Klicken Sie im Formulareditor doppelt auf diesen Schalter, um eine Methode zu erzeugen, die auf die Betätigung des Schalters reagiert. Programmieren Sie dann die folgende Anweisung:

   

Sandini Bib

12 private void btnBeendenActionPerformed(java.awt.event.ActionEvent evt) 13 dispose(); 14 }

Damit ist die Programmierung abgeschlossen und Sie können das Programm testen.

2.9.5 Testen des Programms Nun können Sie das Programm testen. Starten Sie das Projekt wie bei einer Konsolenanwendung mit (ª) (Strg) (F6). Beim ersten Start müssen Sie wieder die Projekt-Startklasse angeben. Wenn der Compiler dann keine Fehler meldet, öffnet sich nach einer kurzen Zeit das Formular und Sie können Zahlen eingeben und rechnen (Abbildung 2.45).

Abbildung 2.45: Das Nettoberechnungs-Programm unter Linux

Wenn Sie einmal ungültige Zahlen oder nichts eingeben und den RECHNEN-Schalter betätigen, wird auch in diesem Programm eine Ausnahme erzeugt. Diese wird allerdings lediglich im Output Window von Sun ONE Studio 4 gemeldet. Das Programm läuft nach der Ausnahme einfach weiter. Wenn Sie das Programm direkt über den Java-Interpreter starten, wird die Ausnahme ebenfalls direkt in der Konsole gemeldet. Wie bei Delphi und Kylix sollten Sie solche Ausnahmen abfangen. In Kapitel 4 zeige ich, wie das geht. Zurzeit müssen Sie sich noch mit der einfachen Meldung in der Konsole begnügen.

Ausnahmen bei ungültigen Eingaben

2.9.6 Die vom Compiler erzeugten Dateien Wenn Sie ein Formular mit Sun ONE Studio 4 entwickeln und kompilieren, erzeugt die Entwicklungsumgebung mehrere .class-Dateien. In meinem Fall wurden z. B. die Dateien StartForm.class, StartForm$1.class, StartForm$2.class und StartForm$3.class erzeugt. Alle diese Dateien sind notwendig, wenn Sie das Programm über den Java-Interpreter (z. B. auf einem anderen Rechner) ausführen wollen. Was die Entwicklungsumgebung damit bezweckt, konnte ich allerdings nicht herausfinden.

          

115

Sandini Bib

2.10 Die Weitergabe einer Anwendung Die Weitergabe einer Anwendung ist eigentlich ein komplexes Thema, das gar nicht in ein Anfänger-Buch hineingehört. Es kann aber sein, dass Sie Ihre einfachen Anwendungen an Freunde weitergeben sollen. Deswegen, und weil die Weitergabe bei Java Probleme verursachen kenn, beschreibe ich wenigstens die Grundlagen. Die im unteren Bereich von Abbildung 2.44 sichtbare main-Methode zeigt übrigens, wie eine solche Anwendung startet, nämlich im Prinzip genau wie eine Konsolenanwendung. Nur dass hier das Formular erzeugt und geöffnet wird. Aber das gehört auch schon zu den erweiterten Techniken. Profis verwenden für die Weitergabe von Anwendungen spezielle Programme, die ein Installationsprogramm erzeugen und die automatisch alle benötigten Dateien auf den Rechner des Anwenders kopieren. Für eine Beschreibung dieser Tools bleibt mit hier kein Platz. Einfache Delphi/Kylix-Anwendungen wie die, die Sie in diesem Buch entwickeln, können Sie aber auch einfach auf den anderen Rechner kopieren. Da Sie keine speziellen externen Bibliotheken verwenden, funktioniert diese Technik meist ohne Probleme. Was allerdings nicht möglich ist, ist, dass Sie eine Kylix-Anwendung auf einem Windows-Rechner ausführen oder eine Delphi-Anwendung auf einem Linux-Rechner. Bei Java sieht das Ganze anders aus. Java-Programme laufen auf allen Betriebssystemen, auf denen Java installiert ist. Es ist also zunächst kein Problem, ein unter Linux entwickeltes Programm unter Windows auszuführen. In vielen Fällen reicht dazu das Kopieren der .class-Dateien, die das kompilierte Programm beinhalten. Leider ist das Ganze nicht mehr so einfach, wenn Ihre Programme Bibliotheken benutzen, die nicht zum Java-Standard gehören. Diese Bibliotheken müssen auf dem anderen Rechner dann ebenfalls, idealerweise im Lib-Ordner des Java-Runtime-Environment (JRE), verfügbar sein. Und das betrifft sogar bereits Ihre ersten Programme, die mit Formularen arbeiten, wenn Sie als Layout-Manager z. B. absoluteLayout verwenden. Denn dieser gehört (leider) nicht zum Java-Standard. Wenn Sie ein solches Programm auf einem Rechner ausführen, der die Java-Standardinstallation besitzt, resultiert das in der Ausnahme NoClassDefFoundError, weil der Java-Interpreter die benötigte Klasse nicht findet. Sie müssten sich dann damit auseinander setzen, in welcher Bibliothek die fehlenden Klassen zu finden sind, und diese dann auf den Zielrechner kopieren. Aber das soll nur ein Hinweis sein, für den Fall, dass Sie bei der Ausführung Ihrer Programme die Ausnahme NoClassDefFoundError erhalten.

116

   

Sandini Bib

2.11 Zusammenfassung In diesem Kapitel haben Sie zunächst gelernt, wie Sie in Java und Delphi/Kylix eine einfache Konsolenanwendung erzeugen. Sie kennen zwar die grundlegenden Techniken noch nicht, die beim Programmieren angewendet werden, können aber bereits Programme schreiben, die an der Konsole Eingaben entgegennehmen und Texte ausgeben. Dabei können Sie auch bereits mit den Fehlermeldungen des Compilers umgehen, auch wenn Sie diese zurzeit noch nicht richtig verstehen. Mit Ausnahmen, die bei ungültigen Eingaben entstehen, können Sie nur insoweit umgehen, dass Sie das Programm in der Entwicklungsumgebung beenden. Sie können aber nicht nur Konsolenanwendungen entwickeln, sondern auch einfache Anwendungen mit einer grafischen Oberfläche, also mit Fenstern und Steuerelementen. Sie wissen, was einfache Programme von solchen mit grafischer Oberfläche unterscheidet und wie Sie in diesen Programmen Eingaben entgegennehmen und Ergebnisse ausgeben. Im Prinzip kennen Sie nun bereits die zwei grundlegenden Arten der Programmierung: die sequentielle, bei der ein Programm von oben nach unten abgearbeitet wird, und die ereignisorientierte, bei der ein Programm immer erst dann aufgerufen wird, wenn der Anwender das Ereignis auslöst (also z. B. einen Schalter betätigt).

2.12 Fragen und Übungen 1. Wie erzeugen Sie aus einem Java-Quelltext (mit der Endung .java) ein

ausführbares Programm? 2. Wie führen Sie ein Java-Programm (das in einer Datei mit der Endung

.class gespeichert ist) aus? 3. Welche Vorteile bietet eine Entwicklungsumgebung wie beispielswei-

se Delphi oder Kylix gegenüber dem einfachen Entwickeln in einem Texteditor? 4. Was sollten Sie beachten, wenn Sie ein neues Delphi/Kylix-

Programm peichern? 5. Was ist ein Projekt? 6. Nennen Sie zwei Unterschiede zwischen einer Konsolenanwendung

und einer Anwendung mit grafischer Oberfläche.

=XVDPPHQIDVVXQJ

117

Sandini Bib

Sandini Bib

3

Basiswissen

le

n e rn

Sie lernen in diesem Kapitel: • wie ein Computer grundsätzlich arbeitet und welche Bedeutung das Betriebssystem, das BIOS und der Arbeitsspeicher für Programmierer besitzen, • auf welche Weise Daten auf Speichermedien und im Arbeitsspeicher gespeichert werden, • wie Programme geschrieben und für den Computer übersetzt werden, • welche Anwendungsarchitekturen es gibt, • was die wichtigsten Programmiersprachen voneinander unterscheidet, • welche Bedeutung Algorithmen für die Programmierung haben, • welche Strukturelemente in Algorithmen verwendet werden und • welche Bedeutung Schleifen und Verzweigungen für die Darstellung eines Algorithmus haben. Nachdem Sie in Kapitel 2 Ihre ersten Erfolge beim Programmieren hatten, behandelt dieses Kapitel einige Basisfragen, die für das Verständnis des Buchs wichtig sind. Es zeigt, wie ein Computer und dessen Programme grundsätzlich funktionieren, was ein Programm aus der Sicht des Computers eigentlich ist, unter welchen Architekturen und mit welchen Programmiersprachen Sie heutzutage Software entwickeln können und was ein Algorithmus ist.

3.1

Wie arbeitet ein Computer?

Dieses Buch ist kein Buch über Computer an sich. Also keine Bedenken: Ich gehe nicht im Detail auf die Arbeitsweise eines Computers ein. Ein Programmierer muss aber dessen Funktionsweise wenigstens grundlegend kennen. Deswegen beschreibe ich, was die Aufgabe eines Betriebssystems ist, wie ein Programm prinzipiell arbeitet und welche Rolle der Arbeitsspeicher dabei spielt. Ich gehe in diesem Abschnitt übrigens da-

:LH DUEHLWHW HLQ &RPSXWHU"

119

Sandini Bib

von aus, dass Sie den grundsätzlichen Aufbau des Computers kennen (also wissen, was die Begriffe CPU, Arbeitsspeicher, Festplatte, CD-ROMLaufwerk etc. bedeuten).

3.1.1 Das Grundprinzip: EVA Das Grundprinzip eines Computers wird als EVA bezeichnet. EVA bedeutet „Eingabe, Verarbeitung und Ausgabe“. Daten werden in verschiedenen Formen eingegeben, verarbeitet (berechnet) und wieder ausgegeben. Eingabegeräte sind z. B. die Tastatur, die Maus, die Festplatte, ein Scanner oder ein (DSL-)Modem. Ausgabegeräte sind z. B. der Monitor, ein Drucker, die Festplatte oder wieder ein (DSL-)Modem. Wenn Sie in einer Textverarbeitung einen Text eingeben, werden Ihre Tastenbetätigungen (die Eingabe) vom Programm verarbeitet (analysiert) und auf dem Monitor und im Druck in Form von Zeichen ausgegeben. Wenn Sie die Datei speichern, führt Ihr Befehl (die Eingabe) dazu, dass die im Arbeitsspeicher gespeicherten Daten in eine Datei geschrieben werden. Öffnen Sie in derselben Textverarbeitung eine Datei, wird diese vom Datenträger gelesen (Eingabe), verarbeitet (korrekt interpretiert) und als Dokument auf dem Bildschirm ausgegeben. Immer wieder ist EVA im Spiel. EVA bedeutet also, dass Daten von einem Eingabemedium gelesen, verarbeitet und wieder ausgegeben werden. EVAs überall

Obwohl EVA das Grundprinzip des Computers ist, müssen Sie sich bei der Programmierung allerdings kaum Gedanken um dieses Prinzip machen: Jedes Programm besteht automatisch aus mindestens einem, meist aber ziemlich vielen EVAs. Die einfache Konsolen-Nettoberechnung aus Kapitel 2 besitzt beispielsweise nur ein EVA (Bruttobetrag und Steuerwert eingeben, Nettobetrag ausrechnen, Nettobetrag ausgeben). Die Fenster-Variante besitzt aber schon zwei: eines, das auf die Betätigung des RECHNEN-Schalters reagiert, und eines für den BEENDEN-Schalter. Ein komplexeres Rechen-Programm wie der Windows-Taschenrechner besteht aus recht vielen einzelnen EVAs, die Ihre einzelnen Tastaturoder Mauseingaben auswerten. Normalerweise wissen Sie recht sicher, woher die Daten stammen, die verarbeitet und ausgegeben werden sollen, und wohin die Daten ausgegeben werden sollen, weil die Aufgabenstellung Ihres Programms dies so festlegt. Ein einfaches Programm zur Berechnung eines Bruttobetrags erwartet die Eingabe z. B. entweder direkt über die Tastatur oder in einem Steuerelement und gibt das Ergebnis an der Konsole oder in einem weiteren Steuerelement aus. Ein Programm zur statistischen Auswertung der Umsätze eines Unternehmens wird die Basisdaten aus einer Datenbank oder einer Datei auslesen und wahrscheinlich in grafischer

120

%DVLVZLVVHQ

Sandini Bib

Form auf dem Drucker ausgeben. Die Frage ist dabei lediglich, wie Sie die Daten einlesen und wie Sie das Ergebnis ausgeben. Jede Programmiersprache bietet Ihnen dazu verschiedene Hilfsmittel, deren Handhabung zwar erlernt werden muss, die dann aber mit wenig Aufwand zu schnellen Ergebnissen führen.

3.1.2 Die CPU, Maschinensprache und Controller Bei der Verarbeitung von Programmen spielt die CPU (Central Processing Unit, der Prozessor des Rechners) die wichtigste Rolle. Die CPU kennt einige hundert fest verdrahtete einfache Befehle, über die grundlegende mathematische Operationen wie Additionen, Subtraktionen, Multiplikationen, Divisionen und das Ansprechen der Speichermedien und der Ausgabemedien möglich sind. Die Summe dieser Befehle wird als Maschinensprache bezeichnet. Jedes Programm führt auf der untersten Ebene meist mehrere Millionen Befehle in der CPU aus. Ein CPU-Befehl besteht aus einer Folge von Nullen und Einsen. Ein typischer Befehl sieht beispielsweise so aus: 00000000010000001000000001100100. Ein Teil der Folge steht für den Befehl selbst, ein weiterer Teil für variable Argumente des Befehls. Bei einer Multiplikation enthält die Befehlsfolge beispielsweise den Multiplikations-Befehl und die Adressen der zu multiplizierenden Speicherbereiche. Dass ein Maschinensprache-Befehl aus einzelnen Nullen und Einsen zusammengesetzt ist, liegt daran, dass die CPU im Prinzip aus sehr vielen kleinen elektronischen Schaltern besteht, die Strom entweder ein- oder ausschalten. Eine Null in einem Befehl repräsentiert einen ausgeschalteten, eine Eins einen eingeschalteten Schalter. Wenn die CPU einen Befehl ausführt, werden die für die Ausführung von Befehlen zuständigen Schalter entsprechend dem Befehl ein- bzw. ausgeschaltet, womit der Strom in spezielle Bahnen gelenkt wird. Was dabei wirklich in der CPU passiert, ist ziemlich komplex. Im Prinzip geht es hier nur darum, dass Sie wissen, warum Maschinensprache aus Nullen und Einsen besteht und warum Maschinensprache für den Menschen sehr schwer verständlich ist.

Maschinensprache

In den Anfangstagen des Computers wurden Programme oft in Maschinensprache geschrieben, weil zu dieser Zeit noch keine höheren Sprachen zur Verfügung standen. Die Programmierung sah damals allerdings (als die Computer noch kaum leistungsfähig waren) so aus, dass entweder Schalter auf einfachen Schalttafeln umgelegt oder Lochkarten gestanzt wurden (die dann in den Computer eingelesen wurden). Sie können auch heute noch in dieser Sprache entwickeln. Allerdings wird dazu meist eine Abstraktion der Maschinensprache verwendet. Diese Abstraktion wird als Assemblersprache bezeichnet. Eine Assemblerspra-

Assembler

:LH DUEHLWHW HLQ &RPSXWHU"

121

Sandini Bib

che stellt die CPU-Befehle in einer für Menschen einfacher lesbaren Form dar, wie Sie noch auf Seite 142 sehen. Ein spezielles Programm, ein Assembler, übersetzt diese Befehle dann in Maschinensprache. Die Entwicklung eines Assemblerprogramms ist allerdings keine einfache Aufgabe und heutzutage, wo meist grafische Oberflächen für Programme verwendet werden und Programme sehr komplex sind, eigentlich auch unsinnig. Schließlich gibt es moderne höhere Programmiersprachen, die die Programmentwicklung erheblich vereinfachen, wie Sie ja bereits in Kapitel 2 gesehen haben. Controller

Moderne Computer überlassen nicht nur der CPU die gesamte Arbeit. Verschiedene Controller übernehmen wichtige Teilaufgaben. Die Tastatur und eine PS/2-Maus werden beispielsweise vom Tastatur-Controller verarbeitet, ein spezieller I/O-Chip übernimmt das Handling der seriellen und parallelen Schnittstellen, der Diskettenlaufwerke und in einigen Fällen auch der Festplatten. Diese Controller sind meist Bestandteil des Motherboards1. Andere Controller sind Teil einer Erweiterungskarte. Moderne Festplatten, Grafik- und SCSI-Karten enthalten beispielsweise eigene Controller. Insgesamt wird die CPU damit von vielen Aufgaben entlastet und die Performance des Gesamtsystems erhöht.

Taktrate

Bei der Bewertung der Leistung einer CPU spielt neben der Anzahl und Mächtigkeit der Befehle (die bei neueren CPUs immer wieder erhöht wird) die Taktrate eine wichtige Rolle. Die Taktrate wird in Hertz gemessen. Hertz bedeutet „Takte pro Sekunde“. Eine CPU mit 2000 MHz ist also in der Lage 2000 Millionen Takte pro Sekunde auszuführen. Ein Takt ist im Prinzip das einmalige Ein- und Ausschalten des Stroms in der CPU. Ein CPU-(Maschinensprache-)Befehl benötigt eine festgelegte Anzahl an Takten. Ein einfacher Befehl wird beispielsweise in zwei Takten ausgeführt, komplexere Befehle benötigen mehr Takte. Daraus ergibt sich, dass eine CPU, die eine hohe Taktrate besitzt, mehr Befehle in einem gegebenen Zeitrahmen ausführen kann als eine CPU mit einer niedrigen Taktrate (also „schneller“ ist). Die Gesamt-Performance des Systems ergibt sich aber nicht nur aus der CPU-Taktrate, sondern auch aus der Anzahl der CPUs (mit speziellen Motherboards ist es auch möglich, mehrere CPUs gleichzeitig zu betreiben), der Geschwindigkeit des System-Bus (der für die Datenübertragung zwischen CPU, Arbeitsspeicher, Grafikkarte, Festplatte und anderen Medien verantwortlich ist), der Geschwindigkeit der Grafikkarte, der Größe des Arbeitsspeichers, der Geschwindigkeit der Festplatte und anderen Faktoren.

1.

122

Ein Motherboard ist eine große Platine mit vielen Schaltkreisen, die definierte Aufgaben im Computer übernehmen, und mit standardisierten Steckplätzen für die CPU, den Arbeitsspeicher, die Grafikkarte etc. Alle Bestandteile des Computers sind mehr oder weniger direkt mit dem Motherboard verbunden.

%DVLVZLVVHQ

Sandini Bib

3.1.3 Das BIOS und das Betriebssystem Das BIOS Die Befehle der CPU ermöglichen lediglich einfachste Operationen wie Additionen, Multiplikationen und das Kopieren von Daten zwischen Speicherbereichen. Programme, die lediglich CPU-Befehle nutzen könnten, wären sehr aufwändig. Wenn ein Programm direkt über CPUBefehle beispielsweise eine Datei von der Festplatte lesen wollte, müsste dieses Programm den genauen Aufbau der Festplatte kennen um die richtigen Speicherbereiche der Festplatte ansprechen zu können. Problematisch dabei ist, dass jeder Computer unterschiedliche Hardware besitzen kann, die auf verschiedene Weise angesprochen werden muss. Da die CPU nur grundlegende Befehle beherrscht, müssten Programme jede Hardware-Variante berücksichtigen, wenn sie nur auf der CPU aufsetzen würden. Um dieses Problem zu lösen besitzt jeder (normale) Computer ein so genanntes BIOS (Basic In and Out System, das Basis-Ein-und-AusgabeSystem). Das BIOS ist ein Programm, das im ROM (Read Only Memory) des Computers auf dem Motherboard gespeichert ist. Ein ROM ist ein Speicher, der einmal dauerhaft beschrieben wurde und mit normalen Mitteln dann nur noch gelesen werden kann. Das BIOS enthält spezielle Befehle, die jeweils mehrere CPU-Befehle aufrufen und damit wesentlich mächtiger sind. Eine Besonderheit des BIOS ist, dass dieses die verschiedenen Hardware-Varianten berücksichtigt, also für den Zugriff auf verschiedene Varianten dieselben Befehle zur Verfügung stellt.

Mächtigere Befehle

Aus diesem Grund verwaltet das BIOS auch Einstellungen zur Hardware des Systems. Diese Einstellungen können Sie erforschen und verändern, wenn Sie beim Booten des Systems eine bestimmte Tastenkombination betätigen (die kurz nach dem Start des Rechners auf dem Bildschirm angezeigt wird). Diese Einstellungen werden in einem speziellen überschreibbaren Speicher auf dem Motherboard gespeichert. Damit die so gespeicherten Konfigurationsdaten nicht verloren gehen, besitzt das Motherboard eine kleine Batterie, die ständig Strom für die Erhaltung des BIOS-Datenspeichers liefert.

Hardware-

Das BIOS ist daneben auch noch dafür zuständig, beim Booten des Rechners das Betriebssystem auf einem festgelegten Bereich der Festplatte, der CD bzw. eines anderen bootfähigen Speichermediums zu suchen, in den Arbeitsspeicher zu laden und auszuführen.

Booten

:LH DUEHLWHW HLQ &RPSXWHU"

Einstellungen

123

Sandini Bib

Das Betriebssystem und Hardware-Treiber Das Betriebssystem, das vom BIOS beim Bootvorgang in den Arbeitsspeicher geladen wird, ermöglicht erst die Arbeit mit dem Computer. Es bietet dem Anwender eine Infrastruktur, über die Programme ausgeführt und Dateien verwaltet werden können. Treiber

Für den Anwender unsichtbar verwaltet das Betriebssystem normalerweise verschiedene Treiber, die den Zugriff auf spezielle oder erweiterte Funktionen der Hardware ermöglichen. Diese Treiber sprechen die Hardware häufig direkt an, unter Umgehung der CPU. So ist es beispielsweise für Programme über das Betriebssystem möglich, die 3D-Funktionen einer Grafikkarte zu nutzen oder die grafischen Features eines Druckers anzusprechen. Der Zugriff über das Betriebssystem geschieht dabei immer auf die gleiche Weise, unabhängig von der Art der Hardware. Der Treiber, der normalerweise vom Hersteller der Hardware entwickelt wurde, übernimmt die physische Steuerung der Hardware, die bei verschiedener Hardware ganz unterschiedlich sein kann. Ein Drucker erwartet beispielsweise bestimmte Steuerungsbefehle zur Formatierung von Zeichen oder zum Wechsel der aktuellen Seite, verschiedene Drucker arbeiten mit vollkommen unterschiedlichen Befehlssätzen. Für ein Windows-Programm ist es prinzipiell unwichtig, welcher Drucker angeschlossen ist, es nutzt lediglich die zum Zugriff auf den Drucker vorgesehenen Betriebssystemfunktionen. Die eigentliche Steuerung des Druckers übernimmt der Druckertreiber, der vom Betriebssystem mit den Aufgaben betreut wird, die das Programm ursprünglich an das Betriebssystem übergeben hat.

BetriebssystemFunktionen

124

Das Betriebssystem enthält aber noch wesentlich mehr Funktionalität. Moderne Betriebssysteme bieten Programmen zusätzlich eine große Anzahl einfach anzuwendender Funktionen für allgemeine Aufgaben. Programme müssen nicht mehr auf die komplizierten BIOS- und CPU-Befehle zugreifen oder die Hardware (über den Treiber) direkt ansprechen, sondern können die wesentlich einfacher anzuwendenden Betriebssystem-Funktionen nutzen. Das Einlesen einer Datei erfordert unter Windows beispielsweise nur den Aufruf einer einzigen Funktion. Der Ausdruck von Text- oder Grafikdaten ist ebenfalls für Programme recht einfach über die dafür zuständigen Betriebssystemfunktionen. Grafische Betriebssysteme bieten Programmen zudem spezielle Fenster und Steuerelemente, die genutzt werden, um die Oberfläche einer Anwendung zu erzeugen. Daneben stehen einem Programm auch Funktionen zum Zeichnen von grafischen Elementen wie einfachen Rechtecken, Kreisen, aber auch zur Ausgabe von Bildern auf dem Bildschirm zur Verfügung.

%DVLVZLVVHQ

Sandini Bib

Viele Betriebssysteme wie Windows und Linux kapseln den Zugriff auf die Hardware komplett. Ein Programm muss nicht mehr direkt auf die CPU, das BIOS oder auf die Hardware zugreifen, sondern kann dazu Betriebssystemfunktionen nutzen. Bei „sicheren“ Betriebssystemen wie Windows 2000 und Windows XP besteht sogar gar keine Möglichkeit, direkt auf die Hardware zuzugreifen. Weil dieser Zugriff nur über Betriebssystemfunktionen möglich ist, kann das Betriebssystem sicherstellen, dass (möglichst) keine Hardware-Funktionen aufgerufen werden, die Probleme verursachen könnten (wie z. B. das versehentliche Überschreiben eines Festplattenbereichs).

Kapselung des Hardware-Zugriffs

Die Zusammenarbeit Abbildung 3.1 zeigt die Zusammenarbeit zwischen Anwendungen, dem Betriebssystem, dem BIOS und der Computer-Hardware (wozu auch die CPU gehört).

Programm Betriebssystem BIOS Hardware

Abbildung 3.1: Die Zusammenarbeit von Programmen, dem Betriebssystem und dem BIOS

Einfache Betriebssysteme wie z. B. DOS liefern ihren Anwendungen natürlich wesentlich weniger Funktionen als komplexe Betriebssysteme wie Windows oder Linux. Wenn Sie schon mal mit einem einfachen DOS-Programm gearbeitet haben, werden Sie wissen, dass Ein- und Ausgaben unter DOS normalerweise direkt an der Konsole erfolgen. Komplexere DOS-Programme, die dem Anwender Windows-ähnliche Fenster anbieten, müssen diese Funktionalität selbst implementieren,

:LH DUEHLWHW HLQ &RPSXWHU"

125

Sandini Bib

können dabei also nicht auf das Betriebssystem zurückgreifen. Windows und das X Window-System von Linux bieten Programmen dagegen eine Vielzahl von Funktionen für Ein- und Ausgaben. Jedes Fenster einer grafischen Anwendung und alle darin enthaltenen Elemente werden mit Hilfe von Betriebssystemfunktionen auf dem Bildschirm ausgegeben2, jeder Ausdruck auf dem Drucker erfolgt über Betriebssystemfunktionen usw. Den Ablauf der Erzeugung einer Programmoberfläche (in Form eines Fensters) illustriert Abbildung 3.2. Abbildung 3.3 zeigt, wie ein Programm prinzipiell Daten auf dem Drucker ausgibt. 1.

übergibt die Kontrolle an das Programm

Programm

3.

xxxx xxxx xxxx

fordert ein Fenster an erzeugt das Fenster

2. Betriebssystem

Abbildung 3.2: Ein Programm verwendet Betriebssystemfunktionen zum Erzeugen von Fenstern.

1.

Programm

3.

sendet Druckauftrag

steuert den Drucker 2. Betriebssystem

Abbildung 3.3: Ein Programm verwendet Betriebssystemfunktionen zum Drucken von Daten.

2.

126

Was bei Linux nicht so ganz korrekt ist, denn hier stellt nicht das Betriebssystem, sondern ein spezielles Programm (meist das X Window-System) die grundlegende Funktionalität für grafische Oberflächen zur Verfügung. Auf diesem Programm bauen Fenstermanager wie die KDE und GNOME auf, die einen Desktop zur Verfügung stellen und Fenster ganz individuell darstellen.

%DVLVZLVVHQ

Sandini Bib

3.1.4 Was ist ein Programm? Ein Programm ist eine gespeicherte Folge von Anweisungen. Diese Anweisungen können Aktionen bewirken (wie Eingaben entgegennehmen oder Dateien einlesen), Berechnungen ausführen, Daten ausgeben, aber auch dafür sorgen, dass ein Teil des Programms wiederholt oder abhängig von einer Bedingung der eine oder andere Teil des Programms ausgeführt wird. Beim Einlesen einer Textdatei werden beispielsweise häufig die einzelnen Zeilen in einer Schleife eingelesen, verarbeitet und ausgegeben, bis das Ende der Datei erreicht ist. Diese bei der Programmierung sehr wichtige Strukturierung eines Programms behandelt das Buch ausführlich in Kapitel 5. Ein einfaches Programm zur Berechnung einer mathematischen Formel enthält z. B. Anweisungen, die die Eingaben des Anwenders entgegennehmen, Anweisungen, die ein Ergebnis berechnen, und Anweisungen, die das Ergebnis auf dem Bildschirm oder dem Drucker ausgeben. Solch ein Programm haben Sie in Kapitel 2 schon selbst geschrieben. Da Programme immer von Betriebssystemen ausgeführt werden, kann ein Programm aus Betriebssystem-Funktionen und Struktur-Anweisungen (Schleifen, Verzweigungen) bestehen. Das Betriebssystem sorgt bei der Ausführung des Programms dafür, dass die Betriebssystem-Funktionen in passende CPU- oder BIOS-Befehle und Treiber-Funktionsaufrufe umgesetzt werden. Programme können, wenn es das Betriebssystem zulässt, aber auch direkte CPU- oder BIOS-Befehle enthalten. Einfache Programme können sogar nur aus CPU- oder BIOS-Befehlen bestehen.

3.1.5 Die Rolle des Arbeitsspeichers Programme bestehen also aus einzelnen Anweisungen. Diese Anweisungen sind zusammenhängend in Form einer Datei auf einem Speichermedium wie z. B. der Festplatte gespeichert. Eine Datei ist eine zusammengehörige Folge von Daten, die vom Betriebssystem als Ganzes eingelesen und verwendet werden kann. Im Falle eines Programms besteht eine Datei aus mehreren einzelnen Anweisungen. Solche Dateien besitzen unter Windows die Endung .exe, was für „executable“, also „ausführbar“ steht. Linux verwendet keine Dateiendungen für ausführbare Dateien, sondern erkennt den Typ der Daten an Informationen, die versteckt in der Datei gespeichert werden. Wenn Sie eine Programmdatei ausführen (in Windows z. B. über einen Doppelklick auf diese Datei oder über die Auswahl der Datei über das Startmenü), wird die Datei zunächst als Ganzes von der Festplatte in den Arbeitsspeicher gelesen. Windows erkennt an der Dateiendung,

:LH DUEHLWHW HLQ &RPSXWHU"

Schneller Speicher für Programme

127

Sandini Bib

dass es sich um eine ausführbare Datei handelt, Linux erkennt dies an der Datei selbst. Das Betriebssystem interpretiert die gespeicherten Daten also als Programm-Anweisungen, liest diese einzeln aus dem Arbeitsspeicher und führt Anweisung für Anweisung aus.

ausführbare Datei

Arbeitsspeicher

0010010100100110 0000011011000101 0100101001010000 0100100101001010 1110101001010010 0011010100110101 0110101000010010 0101010010100101

0010010100100110 0000011011000101 0100101001010000 0100100101001010 1110101001010010 0011010100110101 0110101000010010 0101010010100101

0010010100100110

CPU

Abbildung 3.4: Eine ausführbare Datei wird in den Arbeitsspeicher geladen und dann Anweisung für Anweisung von der CPU ausgeführt.

Dass die Datei dazu in den Arbeitsspeicher des Computers geladen wird, hat einen Grund: Fast alle Programme werden nicht einfach nur von oben nach unten abgearbeitet, sondern enthalten an vielen Stellen Rücksprünge zu vorhergehenden oder Sprünge zu später folgenden Anweisungen. Damit erreicht man beim Programmieren, dass bestimmte Programmteile mehrfach wiederholt und andere bedingungsabhängig ausgeführt werden können. Die dazu verwendeten Techniken werden in Kapitel 5 behandelt. Würde die CPU die einzelnen Anweisungen immer wieder von der im Vergleich zum Arbeitsspeicher sehr langsamen Festplatte lesen müssen, würden Programme nur sehr schleppend ausgeführt werden. Um die Ausführung zu beschleunigen, wird das gesamte Programm vor der Ausführung also in den Arbeitsspeicher geladen. Speichern von Daten

128

Der Arbeitsspeicher wird aber nicht nur zur Speicherung ausführbarer Programme verwendet. Viele Schritte zur Verarbeitung eingegebener Daten sind so komplex, dass ein Programm Zwischenergebnisse berechnen muss, die in späteren Anweisungen weiterverwendet werden. Diese

%DVLVZLVVHQ

Sandini Bib

Zwischenergebnisse werden dazu einfach im Arbeitsspeicher abgelegt. Viele Programme arbeiten auch mit variablen Daten, die beim Start des Programms oder bei der Ausführung bestimmter Aktionen dynamisch ermittelt werden. Zur Weiterverarbeitung dieser variablen Daten werden diese ebenfalls in den Arbeitsspeicher geschrieben. Eine weitere (aber nicht die letzte) Verwendung des Arbeitsspeichers ist das Einlesen von Dateien, damit diese schneller verarbeitet werden können, als wenn die Daten jeweils von der Festplatte gelesen werden. Programme nutzen dazu Variablen, wie Sie dies auch bereits in den Beispielen des vorherigen Kapitels gemacht haben. In Kapitel 4 erfahren Sie mehr dazu.

3.2

Wie werden Programme und Daten gespeichert?

Ein wichtiger Schlüssel zum Verständnis von Programmen und des Computers ist das Wissen, wie Programme und Daten gespeichert werden. Prinzipiell kommen Sie bei der Programmierung zwar eigentlich nie in Kontakt mit den Speichertechniken, die der jeweilige Computer verwendet. Begriffe wie Bit und Byte gehören aber zum allgemeinen Wissen und sollten von Programmierern schon verstanden werden. Auch die Kenntnis der Verwaltung von Zahlen und Zeichen im Speicher oder auf einem Speichermedium ist bei der Programmierung sehr häufig hilfreich und manchmal sogar notwendig. Ein Begriff wie Unicode taucht auch später noch häufiger auf. Wenn Sie mit einem Programm wie beispielsweise einer Textverarbeitung arbeiten, speichern Sie Ihre Arbeit normalerweise in eine Datei, sodass Sie diese zu einem späteren Zeitpunkt wieder verwenden können. Während der Arbeit verwaltet ein Programm seine Daten im Arbeitsspeicher. Die folgenden Abschnitte erläutern nun, in welcher Form dies erfolgt.

3.2.1 Bits und Bytes Auf allen persistenten3 Speichermedien werden Daten in Form von Dateien gespeichert. Eine Datei ist eine zusammenhängende Folge von Daten und besteht aus einzelnen Bytes. Aber auch im Arbeitsspeicher werden Daten in Form einzelner oder zusammenhängender Bytes verwaltet.

3.

persistent = dauerhaft

:LH ZHUGHQ 3URJUDPPH XQG 'DWHQ JHVSHLFKHUW"

129

Sandini Bib

Ein Byte ist die kleinste Speichereinheit im Computer und besteht aus acht Bits. Ein Bit kann nur den Zustand 0 oder 1 annehmen. Auf der niedrigsten Ebene rechnet ein Computer immer mit den beiden Zuständen Eingeschaltet (1) und Ausgeschaltet (0). Das liegt daran, dass die CPU und der Arbeitsspeicher im Prinzip aus vielen kleinen elektronischen Schaltern bestehen, die nichts weiter können, als Strom ein- und auszuschalten. Wenn der Rechner also Daten lesen, verarbeiten und ausgeben will, muss er die Daten irgendwie so verpacken, dass diese mit den beiden Zuständen 0 und 1 dargestellt werden können. Acht Bits sind ein Byte

Dass jeweils acht Bit zu einem Byte zusammengefasst werden, hat den Grund, dass das Verwalten einzelner Bits für Programmierer zu kompliziert sein würde. Die Verwaltung eines Byte ist dagegen wesentlich einfacher. Dass dazu genau acht Bits verwendet werden, hängt damit zusammen, dass acht Bits im häufig zur Darstellung von Zahlen verwendeten Hexadezimalsystem (Zahlensystem mit der Basis 16) sehr einfach dargestellt werden können. Ein Byte besteht also aus acht Bits, die jeweils den Zustand 0 (ausgeschaltet) oder 1 (eingeschaltet) annehmen können. Ein Programm kann nur ganze Bytes speichern, keine einzelnen Bits (obwohl es Möglichkeiten gibt, die einzelnen Bits eines Bytes zu setzen und abzufragen, aber das ist eine spezielle Programmiertechnik). Um Daten nun in einzelnen Bytes speichern zu können, müssen diese transformiert (umgewandelt) werden.

3.2.2 Zahlendarstellung im Computer Für Zahlen ist die Transformation in einzelne Bytes relativ unproblematisch. Jede (dezimale) Zahl kann in eine duale Zahl umgerechnet werden. Eine duale Zahl wird mit den Ziffern 0 und 1 dargestellt. Die dezimale Zahl 3 kann z. B. dual mit 00000011 dargestellt werden. Dateien und der Arbeitsspeicher speichern Daten ausschließlich mit den Zuständen 0 und 1. In einer Datei steht eine 1 für eine gesetzte Speicherstelle, eine 0 für eine nicht gesetzte. Auf einer Festplatte bedeutet dies, dass der jeweilige Speicherbereich entweder magnetisiert ist oder nicht. Auf einer CD wird eine 1 über ein (über einen CD-Brenner gebranntes) „Loch“ in der Oberfläche realisiert. Der Arbeitsspeicher besteht im Prinzip aus einer Vielzahl einzelner elektronischer Schalter (Transistoren), die entweder ein- oder ausgeschaltet sein können, also wieder die Zustände 1 und 0 annehmen können. Für Programmierer ist die Umrechnung dezimal dargestellter Zahlen in Dualzahlen nur in bestimmten Fällen wichtig. Programmiersprachen ermöglichen normalerweise immer auch die Verwendung dezimal dar-

130

%DVLVZLVVHQ

Sandini Bib

gestellter Zahlen, die bei der Übersetzung des Programms automatisch umgerechnet werden. Um aber auch spezifische Programmier-Situationen zu beherrschen, bei denen dual dargestellte Zahlen verwendet werden, sollten Sie verstehen, wie die Umrechnung erfolgt. Umrechnung vom Dezimal- in das Dualsystem Das dezimale Zahlensystem, mit dem wir normalerweise rechnen, basiert auf der Zahl 10. Mathematisch betrachtet bedeutet die Zahl 123 beispielsweise (3 * 100) + (2* 101) + (1 * 102), also (3 * 1) + (2 * 10) + (3 * 100). Die rechte Ziffer besitzt immer den Exponenten 0, die Ziffer daneben den Exponenten 1 usw. Natürlich rechnet wohl kaum ein Mensch bewusst eine im Dezimalsystem dargestellte Zahl in dieser Form um, wir kennen die Bedeutung der einzelnen Ziffern ja recht genau. Was im menschlichen Gehirn abläuft, ist aber eigentlich nichts anderes als die dargestellte mathematische Umrechnung. Wenn Sie nun eine Dezimalzahl in eine duale Zahl umrechnen wollen, müssen Sie die Wertigkeit der einzelnen Ziffern einer dualen Zahl berücksichtigen. Die äußerst rechte Ziffer hat die Wertigkeit 20 (also 1 im Dezimalsystem, weil eine Zahl mit dem Exponenten 0 immer 1 ergibt), die von rechts aus betrachtet zweite Ziffer besitzt die Wertigkeit 21 usw. Um eine duale Zahl in eine Dezimalzahl umzurechnen, multiplizieren Sie einfach die Wertigkeit mit der Ziffer. Steht an der rechten Ziffer eine 1, bedeutet dies so viel wie 1 * 20. Auf diese Weise addieren Sie die einzelnen Ziffern miteinander. Die duale Zahl 00001110 kann also z. B. so umgerechnet werden: 0*20 + 1*21 + 1*22 + 1*23 + 0*24 + 0*25 + 0*26 + 0*27 + 0*28 = 0*1 + 1*2 + 1*4 + 1*8 + 0*16 + 0*32 + 0*64 + 0*128 =0+2+4+8+0+0+0+0 = 1410 Um duale von dezimalen Zahlen unterscheiden zu können, werden diese häufig mit einer tief gestellten 10 (Zahl im Dezimalsystem) bzw. einer tief gestellten 2 (Zahl im Dualsystem) gekennzeichnet: 12310, 11110112. Wenn Sie von einer dezimalen Zahl in eine duale Zahl zurückrechnen wollen (was Sie eigentlich bei der Programmierung nur äußerst selten müssen), beginnen Sie mit der höchstmöglichen Wertigkeit. Soll z. B. die dezimale Zahl 195 umgerechnet werden, überprüfen Sie zuerst, ob die höchste Wertigkeit (in einem Byte ist das 27 = 128) in diese Zahl hineinpasst. Ist das der Fall, setzen Sie das äußerst linke Bit und ziehen die Wertigkeit von der Zahl ab:

:LH ZHUGHQ 3URJUDPPH XQG 'DWHQ JHVSHLFKHUW"

131

Sandini Bib

19510 = 10000002 Rest 6710

Dann überprüfen Sie für die nächste Wertigkeit (26 = 64), ob diese vom Rest abgezogen werden kann: 19510 = 11000002 Rest 310

So gehen Sie weiter vor, bis kein Rest mehr übrig bleibt: 19510 = 110000112 Rest 010

Eigentlich ist diese Umrechnung doch ziemlich einfach, oder? Wie werden ganze Zahlen und Zahlen mit Dezimalstellen gespeichert? Ganze Zahlen

Einfache ganze Zahlen ohne Vorzeichen werden in ihrer dualen Form in einzelnen Bytes gespeichert. Ein Byte kann die Zahlen 0 bis 255 verwalten. Ist die zu speichernde Zahl größer als 255, werden mehrere Bytes zu einer Speichereinheit zusammengefasst. Zwei Bytes können schon ganze Zahlen im Bereich von 0 bis 65535 speichern. Bei negativen und dezimalen Zahlen wird das Ganze etwas komplizierter. Bei negativen Zahlen verwenden die meisten Programme das linke Bit zur Kennzeichnung, ob es sich um eine negative oder positive Zahl handelt. Ein Byte, das Zahlen mit Vorzeichen verwaltet, kann den Wertebereich -128 bis +127 verwalten, zwei Byte verwalten den Bereich -32768 bis +32767. Dieser etwas eigenartige Bereich, bei dem der Absolutwert4 der negativen Zahl größer ist als der Absolutwert der positiven Zahl, ergibt sich aus speziellen Speichertechniken, mit denen verhindert wird, dass die Zahl 0 zweimal verwaltet werden kann (als -0 und +0). Der mögliche Zahlenbereich wird damit optimiert.

Festkomma- und Fließkommazahlen

Bei Dezimalzahlen werden solche mit Festkomma und mit Fließkomma unterschieden. Festkommazahlen werden genau wie ganze Zahlen gespeichert. Beim Interpretieren der gespeicherten Daten setzt das Programm das Komma an eine festgelegte Stelle, beispielsweise vor die vierte Ziffer von rechts. Die gespeicherte Zahl 1234 wird in diesem Beispiel also als 1,234 interpretiert. Komplizierter ist das Speichern von Zahlen mit möglichst großer Anzahl an Dezimalstellen. Bei diesen Zahlen wird – einfach ausgedrückt – ein Teil der Bitfolge dazu verwendet, festzulegen, an welcher Stelle sich das Dezimaltrennzeichen befindet. Daraus ergibt sich, dass nur Zahlen mit sehr kleinem Absolutwert viele Ziffern nach dem Komma verwalten können. In der Regel werden dabei maximal sieben oder maximal 16 Ziffern verwaltet (je nach Größe des verwendeten Speicherbereichs). Zahlen mit einem großen Absolutwert 4.

132

Als Absolutwert wird der Wert einer Zahl ohne Vorzeichen bezeichnet

%DVLVZLVVHQ

Sandini Bib

können nicht mehr die maximale Anzahl an Nachkommaziffern verwalten. Sehr große (bzw. bei negativen Zahlen sehr kleine) Zahlen können nur noch mit einer oder zwei Nachkomma-Ziffern verwaltet werden. Da das Komma bei dieser Speichertechnik je nach Größe der Zahl quasi fließt, werden diese Zahlen als Fließkommazahlen bezeichnet. Die Größe der Zahl und die Anzahl der Zahlen und der möglichen Nachkommaziffern hängt natürlich von der verwendeten Anzahl Bytes ab. Die meisten Programmiersprachen speichern Fließkommazahlen in vier oder acht Byte. Vier Byte ermöglichen normalerweise Zahlen im Bereich von ±1,5 * 10-45 bis ±3,4 * 1038 mit bis zu sieben Dezimalstellen, acht Byte ermöglichen Zahlen im Bereich von ±5,0 * 10-324 bis ±1,7 * 10308. Fragen Sie mich bloß nicht, wie diese Bereiche zustande kommen, die Logik der Speicherung ist schon recht komplex. Wichtig ist aber, dass ein Vier-Byte-Speicherbereich bei fünf Ziffern vor dem Komma schon nur noch zwei Ziffern nach dem Komma verwalten kann. Überzählige Ziffern werden in der Regel einfach abgeschnitten (einige Programmiersprachen runden die Zahl auch passend auf oder ab). Die Genauigkeit dezimaler Zahlen ist also bei der Speicherung eingeschränkt. Manchmal sind die errechneten Werte nicht so genau, wie sie eigentlich sein sollten. Beim Rechnen mit Dezimalzahlen muss ein Programmierer immer auf die verfügbare Genauigkeit achten.

Genauigkeit

Viele Programmiersprachen kennen aber auch Zahlen mit wesentlich mehr Dezimalziffern (in der Regel 28), über die hochgenaue Berechnungen ausgeführt werden können. Solche Berechnungen werden beispielsweise im wissenschaftlichen und finanzmathematischen Bereich eingesetzt. Falls Sie jetzt besorgt sind, dass Sie die Transformation von Zahlen in das Dualsystem und zurück zum dezimalen System durchführen müssen, kann ich Ihre Bedenken zerstreuen. Sie haben mit der Transformation der zu speichernden Zahlen normalerweise nichts zu tun. Diese Arbeit übernimmt das Betriebssystem oder die Programmiersprache. Sie teilen dem Computer lediglich (über bei der Programmierung verwendete Datentypen) mit, welchen Zahlenbereich Sie speichern wollen. Das Hexadezimalsystem Das hexadezimale Zahlensystem wird zwar nur selten von Programmiersprachen, aber häufig von Programmierern zur Darstellung von Zahlen verwendet. Wenn Sie in HTML-Dokumenten z. B. eine spezielle Farbe verwenden wollen, müssen Sie diese als Hexadezimalzahl angeben.

:LH ZHUGHQ 3URJUDPPH XQG 'DWHQ JHVSHLFKHUW"

133

Sandini Bib

Das Hexadezimalsystem arbeitet mit der Basis 16. Zur Darstellung der Zahlen 10 bis 15 werden die Buchstaben A bis F verwendet. Die hexadezimale Zahl 0F16 steht zum Beispiel für die dezimale Zahl 15, die hexadezimale Zahl 1016 steht für die dezimale Zahl 16. Die Umrechnung von dezimalen in hexadezimale Zahlen erfolgt ähnlich wie bei den dualen Zahlen, nur eben mit der Basis 16. Die Umrechnung soll deshalb hier kein Thema sein. Ein ganz normaler Taschenrechner hilft Ihnen dabei. Nibble

Interessant ist aber, dass Zahlen im Dualsystem sehr einfach mit Hexadezimalzahlen dargestellt werden können. Dazu wird ein Byte in zwei so genannte Nibble von je vier Bit aufgeteilt. Jedes Nibble kann von einer hexadezimalen Ziffer dargestellt werden, weil der Wertebereich eines Nibble von 0 bis 15 reicht. Die Zahl 1111|11112 (zum besseren Verständnis habe ich die einzelnen Nibble mit einem Strich getrennt dargestellt) entspricht z. B. der Zahl F|F 16, die Zahl 0011|11002 entspricht der Zahl 3|C16.

3.2.3 Wie werden Texte gespeichert? Texte, die im Arbeitsspeicher oder in einer Datei gespeichert werden sollen, müssen ähnlich Zahlen auch in einzelnen zusammenhängenden Bytes dargestellt werden. Wenn Sie beispielsweise in einer Textverarbeitung eine Zeichentaste betätigen, muss das Betriebssystem bzw. das Programm dafür sorgen, dass das gewählte Zeichen im Arbeitsspeicher gespeichert und als korrektes Zeichen auf dem Bildschirm ausgegeben wird. Die dazu notwendige Transformation übernimmt wieder das Betriebssystem oder die Programmiersprache. Sie brauchen sich also eigentlich nicht darum zu kümmern. Beim Programmieren werden Sie aber immer wieder mit dem für einzelne Zeichen verwendeten Zahlencode konfrontiert, z. B. dann, wenn Sie Texte sortieren oder einzelne Zeichen in Form ihres Zahlencodes in einen Programmquelltext eingeben wollen (bzw. müssen). Sie sollten also wissen, wie Zeichen transformiert werden. Ein-ByteSpeicherung

134

Im Prinzip ist die Transformation von Texten in Bytes sehr einfach. In einem Byte können, wie Sie ja bereits wissen, die Zahlen 0 bis 255 gespeichert werden. Ältere Systeme assoziieren ein Zeichen mit einer bestimmten in einem Byte speicherbaren Zahl. Das kleine a besitzt beispielsweise meist den Zahlencode 97. Auf diese Weise kann ein Byte 256 Zeichen verwalten. Welche Zahl welches Zeichen darstellt, ist im Verlauf der Entwicklung der ersten Computer von verschiedenen Instituten genormt worden und wird in Tabellen festgelegt, die für die einzelnen Sprachregionen meist sehr unterschiedlich aussehen. Da in den älteren Tabellen (die nur ein Byte zur Speicherung verwenden) lediglich

%DVLVZLVVHQ

Sandini Bib

256 Zeichen möglich sind, können nicht annähernd alle Zeichen der Welt-Sprachen in einer Tabelle dargestellt werden. Deshalb existieren für die verschiedenen Sprachregionen dieser Welt unterschiedliche (logische) Tabellen, die die möglichen Zahlencodes mit oft ganz anderen Zeichen assoziieren. Die bekannteste Tabelle ist die ASCII5-Tabelle, die in vielen älteren Betriebssystemen wie DOS und Windows 3.x eingesetzt wird. Diese Tabelle wird auch manchmal als ANSI6-Tabelle bezeichnet. Der Unterschied zwischen ASCII und ANSI ist nicht ganz klar oder im Lauf der Zeit verschwommen. Für einige Leute ist eine ASCII-Tabelle eine Tabelle, die nur Zeichen im Bereich von 0 bis 127 kennt (was auch die ursprüngliche Art der Speicherung war, denn dafür wurden nur sieben Bit pro Zeichen benötigt, was früher teuren Speicherplatz sparte). Alle Tabellen, die auch die Zeichen 128 bis 255 speichern, werden von diesen Leuten als ANSI-Tabelle bezeichnet. Für viele ist eine ASCII-Tabelle aber auch eine, die 256 Zeichen kennt. Ich verwende hier einfach den allgemeineren Begriff ASCII.

ASCII und ANSI

ASCII-Tabellen liegen in mehreren, sprachspezifischen Varianten vor. Die einzelnen Varianten wurden früher einfach mit Nummern gekennzeichnet. Die alte ASCII-Tabelle für die USA besitzt z. B. die Nummer 437, die für Westeuropa die Nummer 1250. Modernere ASCII-Tabellen sind von der „International Organization for Standardization“ (ISO) genormt, deshalb wesentlich besser standardisiert und werden etwas anders benannt. In westlichen Ländern wird z. B. fast ausschließlich die Tabelle ISO-8859-1 eingesetzt. In dem Zusammenhang sprechen Programmierer auch häufig von Zeichensätzen. Der Zeichensatz ISO-Latin-1 definiert beispielsweise die Zeichen, die in westlichen Ländern (in Ländern mit lateinischer Sprachabstammung) verwendet werden. Richtig klar wird der Zusammenhang zwischen dem Zeichensatz ISO-Latin-1 und der Zeichentabelle ISO-8859-1 allerdings nicht. Einige Leute meinen, dass es sich dabei um dasselbe handelt, andere denken, dass ISOLatin-1 nur definiert, welche Zeichen enthalten sein müssen, aber nicht, welchen Zeichencode diese besitzen (das macht dann ISO-88591). Im Allgemeinen ist mit beiden Begriffen aber dasselbe gemeint. Was Sie auch daran erkennen, das beispielsweise der Zeichensatz ISO-Latin-2 durch die ASCII-Tabelle ISO-8859-2 abgebildet wird.

ISO-Tabellen und Zeichensätze

Allen ASCII-Varianten (die in der modernen Variante nach Regionen wie Westeuropa/USA und Osteuropa unterteilt sind) ist gemeinsam, 5.

American Standard Code for Information Interchange

6.

American National Standards Institute, ein Institut zur Standardisierung von allgemein verwendeten Techniken ähnlich dem deutschen DIN-Institut (Deutsche Industrie Norm).

:LH ZHUGHQ 3URJUDPPH XQG 'DWHQ JHVSHLFKHUW"

135

Sandini Bib

dass die ersten 127 Zeichen immer dieselben sind. Erst ab dem Zeichen 128 unterscheiden sich die verschiedenen regionsspezifischen Varianten. Daneben ist allen Tabellen gemeinsam, dass die Zeichen bis zum Zeichen 31 Steuerzeichen sind. Viele dieser Steuerzeichen wurden früher unter textbasierten Betriebssystemen zur Steuerung des Druckers bzw. des Bildschirms verwendet, besitzen heute aber keine Bedeutung mehr. Einige Steuerzeichen, wie die Zeichen 10 (Zeilenvorschub), 13 (Wagenrücklauf) und 9 (Tabulator) werden aber heute immer noch verwendet. Wenn Sie z. B. mit Word einen Text schreiben und die ReturnTaste betätigen um eine neue Zeile zu beginnen, fügen Sie im Prinzip das Zeichen 10 in Ihren Text ein.7 Hervorragende Erläuterungen zu Zeichensätzen und Zeichentabellen und eine Übersicht über die zurzeit gängigen ISO-8859-Tabellen finden Sie unter der Adresse czyborra.com/charsets/iso8859.html. Tabelle 3.1 listet einige ASCII-Zeichen der in westlichen Ländern verwendeten Tabelle ISO-8859-1 auf. Im Anhang finden Sie eine komplette ISO-8859-1-Tabelle. Auf anderen Betriebssystemen werden übrigens auch andere Tabellen verwendet. Ältere IBM-Betriebssysteme arbeiten beispielsweise mit der EBCDIC-Tabelle. Zeichen Tabulator

9

Zeilenvorschub

10

Wagenrücklauf

13

Leerzeichen

32

0

48

1

49

...

...

9

57

A

65

B

66

...

...

Z

90

a

97

7.

136

Dezimalcode

Dass nur das Zeichen 10 verwendet wird, ist nicht ganz korrekt. Die meisten Programme verwenden leider immer noch eine Kombination der Zeichen 10 (Line Feed = Zeilenvorschub) und 13 (Carriage Return = Wagenrücklauf) zur Darstellung einer neuen Zeile. Diese (historisch gewachsene) Eigenart resultiert aus dem Verhalten einer Schreibmaschine: Bei dieser müssen Sie zum Beginnen einer neuen Zeile den Wagen zurückschieben und die Zeilenvorschubtaste betätigen.

%DVLVZLVVHQ

Sandini Bib

Zeichen

Dezimalcode

b

98

...

...

z

122

Tabelle 3.1: Auszug aus einer ASCII-Tabelle

ASCII-Tabellen werden auf modernen Systemen nur noch eingeschränkt eingesetzt. Bedeutung besitzen diese Tabellen aber noch im Internet. Hier werden Zeichendaten aufgrund der Beschränkungen von an der Datenübertragung beteiligten Systemen noch in Form von einzelnen Bytes übertragen. Internetprogramme verwenden in westlichen Ländern aber die Tabelle ISO-8859-1, weswegen normalerweise keine Fehlinterpretationen von Zeichen vorkommen (was früher, unter den alten ASCII-Tabellen noch sehr häufig vorkam). Moderne Betriebssysteme verwenden meist keine ASCII-Tabellen, sondern speichern ein Zeichen in zwei Byte, womit 65535 verschiedene Zeichen möglich sind. Dieser so genannte Unicode kann damit bis auf wenige Ausnahmen alle Sprachen dieser Welt abbilden. Ein großes Problem der ASCII-Tabellen, nämlich das schwierige Austauschen von Texten zwischen verschiedenen Regionen dieser Welt, entfällt damit weitgehend. Da die ersten 255 Zeichen der Unicode-Tabelle der ASCIITabelle ISO-8859-1 entsprechen, ist Unicode (wenigstens in westlichen Ländern) kompatibel zu ASCII. Das ist für uns Programmierer ziemlich wichtig zu wissen, denn so können wir die Standard-Zeichencodes anwenden, ohne uns Gedanken um die Art der Speicherung machen zu müssen. Der Zeichencode 13 steht immer für einen Wagenrücklauf, der Zeichencode 10 immer für einen Zeilenumbruch, das „a“ besitzt immer den Code 97.

Unicode

Eine sehr gute Beschreibung des Unicode-Standards finden Sie unter der Adresse czyborra.com/unicode/characters.html. Textdateien Einfache Textdateien speichern heute zumeist noch einzelne ASCIIZeichen, weil viele Betriebssysteme (wie Windows 95, 98 und Me) keinen oder nur einen eingeschränkten Unicode-Support bieten. Auf modernen Betriebssystemen werden Textdateien aber auch optional in Unicode-Form gespeichert. Textdateien ist gemeinsam, dass die einzelnen Zeilen entweder durch eine Kombination der Zeichen 13 (Carriage Return = Wagenrücklauf) und 10 (Line Feed = Zeilenvorschub) oder nur durch das Zeichen 10 abgeschlossen werden. Welche Technik verwendet wird, hängt vom Betriebssystem ab. Linux und Unix verwenden bei-

:LH ZHUGHQ 3URJUDPPH XQG 'DWHQ JHVSHLFKHUW"

137

Sandini Bib

spielsweise nur das Zeichen 10, Windows die Zeichen 13 und 10. Die Kombination der Zeichen 13 und 10 wurde übrigens von der alten Schreibmaschinen-Technik übernommen. Was immer das auch für einen Sinn hatte ... Formatierungen in Textdokumenten Einfache Textdateien speichern lediglich die einzelnen Zeichen des Textes. Viele Text-Dokumente verwalten daneben aber auch Formatierungen, wie z. B. die Schriftart, die Schriftgröße und die Schriftauszeichnung (fett, kursiv etc.). Diese Formatierungen werden neben den Bytes für die einzelnen Zeichen in Form separater Bytes gespeichert. Das Ganze kann natürlich sehr kompliziert werden, ist für Programmierer aber eigentlich nicht besonders wichtig, da es Programme, Tools oder Komponenten gibt, über die solche Dateien sehr einfach gelesen und geschrieben werden können. Oft ist das Schreiben und Lesen lediglich über die jeweilige Textverarbeitung möglich, einige Hersteller stellen aber auch Funktionen zur Verfügung, über die ein Programm solche Text-Dokumente verarbeiten kann. Für das relativ allgemeine RTF-Format (bei dem die Formatierungen in Form spezieller Befehle in Textform gespeichert sind) besitzen die meisten Programmiersprachen aber auch eigene Funktionen zum Lesen, Verarbeiten und Speichern.

3.2.4 Kombinierte Dateien Dateien speichern oft nicht nur Texte oder Zahlen, sondern eine Kombination von beidem. Stellen Sie sich eine Datei vor, die mehrere Adressen Ihrer Freunde speichert. Neben dem Namen jeder Person werden vielleicht noch die Straße, der Ort, die Telefonnummer und die Postleitzahl gespeichert. Eine solche Datei würde aus einzelnen Adressen bestehen, die sich aus den Teilen einer Adresse (den Feldern) zusammensetzen. Die einzelnen Felder speichern meist Texte, bei der Postleitzahl aber z. B. eine Zahl.

3.2.5 Binäre Daten: Speichern von Bildern, Musikstücken und anderen speziellen Daten Neben Programmcode, Zahlen und Zeichen werden natürlich noch andere Daten, wie z. B. Bilder oder Musik, in Dateien gespeichert. Ich will nicht näher darauf eingehen, wie diese Daten tatsächlich abgelegt werden, weil Programmierer eigentlich nie direkt mit der Speichertechnik konfrontiert werden. Ich erläutere aber die Grundlagen, damit Sie verstehen, was binäre Daten sind.

138

%DVLVZLVVHQ

Sandini Bib

Gespeicherte Texte setzen sich aus einzelnen Zeichen und Format-Informationen zusammen. Ein Text besteht also aus vielen einzelnen Bytes, die jeweils ein Zeichen darstellen, und aus dazugehörigen Bytes für die Formatierungen. Im Prinzip kann man sagen, dass ein Text eine Folge einzelner Einheiten ist. Jede Einheit verwaltet einzelne Zeichen und deren Formatierung. Bilder, Musikstücke, Videos und ähnliche Dateien bestehen hingegen nicht aus einzelnen Einheiten, sondern stellen im Prinzip eine einzige große Einheit dar. Deshalb werden bei diesen Dateien keine Bytes, sondern einzelne Bits verwaltet. Bei Bilddateien legt man z. B. fest, dass 1, 8, 15 oder 16 Bits (oder mehr) einen Bildpunkt darstellen. Ein Bild ist meist aus sehr vielen einzelnen Punkten zusammengesetzt. Eine BildSpeichereinheit legt die Farbe eines Bildpunktes fest. Wird ein Bildpunkt in einem Bit verwaltet, können nur die Farben Schwarz und Weiß dargestellt werden, bei 16 Bits pro Bildpunkt sind schon 65535 Farben möglich. Allgemeine Informationen zum Bild, wie beispielsweise Angaben zur Farbtiefe, zur Breite und Höhe und zu einer eventuellen Komprimierung werden in den ersten Bytes der Bilddatei, im so genannten Header gespeichert. Solche Daten, die Informationen in einzelnen Bits speichern, werden im Allgemeinen als binäre Daten bezeichnet, Dateien mit solchen Daten heißen binäre Dateien. Ein Programm, das Bilddateien speichert und auswertet, muss nun dafür sorgen, dass immer ganze Bytes gespeichert werden, weil dies vom Betriebssystem verlangt wird (ein Byte ist schließlich die kleinste Speichereinheit). Eine Bilddatei mit 1-Bit-Farbtiefe, die nur aus vier einzelnen Bildpunkten besteht (solche Bilddateien gibt es natürlich nicht, aber dieses Beispiel veranschaulicht die Thematik besser als eine Bilddatei mit 10.000 Bildpunkten), würde also für die Bildpunkte ein ganzes Byte speichern, wobei die oberen vier Bits einfach unbenutzt bleiben würden. Um zu erkennen, welches Format eine Bilddatei besitzt, speichert das Programm am Anfang der Datei, im so genannten Header, zusätzlich noch Informationen über die Farbtiefe und die Größe des Bildes. An Hand dieses Headers kann ein Programm beim Lesen der Datei ermitteln, wie die einzelnen Bits der Bilddaten ausgewertet werden müssen.

Datei-Header

Dieses bei Bilddateien verwendete Prinzip wird auch bei anderen Dateien, wie Musik- und Videodateien, angewendet. Natürlich ist das Speichern dieser Daten in der Praxis wesentlich komplexer, als ich es hier darstelle. Schließlich existieren die verschiedensten Formate zur Speicherung von Bild-, Musik- und Videodaten (bei Bildern z. B. in Form der

:LH ZHUGHQ 3URJUDPPH XQG 'DWHQ JHVSHLFKHUW"

139

Sandini Bib

Formate Bitmap, GIF, TIFF, JPEG etc.). Jedes Format verwaltet die Daten anders, einige Formate komprimieren die gespeicherten Daten sogar, sodass die Dateien insgesamt kleiner werden.

3.2.6 Speicheradressen und Variablen Der Arbeitsspeicher besteht aus einer meist sehr großen Anzahl einzelner Bytes. Wenn Daten im Arbeitsspeicher gespeichert werden, belegen diese immer eine bestimmte Anzahl zusammenhängender Bytes. ASCIITextdaten mit zwei Zeilen, die jeweils drei Zeichen enthalten, belegen beispielsweise unter Windows acht und unter Linux sieben Byte im Arbeitsspeicher. Eine Variable, die einen Wert zwischen 0 und 255 speichern soll, belegt dagegen nur ein Byte. Damit die so gespeicherten Daten vom verarbeitenden Programm wiedergefunden werden, wird der Arbeitsspeicher adressiert. Dabei werden einfach die Bytes gezählt. Ein Programm, das einen 2-Byte-Zahlwert an der Adresse 1000 abgelegt hat (also am Byte mit der Nummer 1000), kann den Speicher an Hand dieser Adresse wieder auslesen. Speicheradressen werden übrigens üblicherweise in hexadezimaler Form angegeben. Variablen

Bei der modernen Programmierung müssen Sie eigentlich nie die Adressen kennen, an denen Ihr Programm Daten verwaltet (früher war das allerdings anders, da musste ein Programmierer die Adressen sehr wohl kennen). Programmiersprachen stellen Ihnen, wie Sie ja bereits wissen, Variablen zur Verfügung. Wenn Sie eine Variable deklarieren, sorgt der Compiler oder Interpreter, der das Programm in Maschinencode übersetzt (siehe ab Seite 143), automatisch dafür, dass bei der Ausführung des Programms ein passender Speicherbereich reserviert wird. Beim Übersetzen des Programms ersetzt der Compiler bzw. Interpreter alle Stellen, an denen Sie die Variable verwenden, durch die entsprechende Speicheradresse. Sie müssen sich also nicht weiter darum kümmern.

Direkter

Einige Programmiersprachen wie C und C++ erlauben aber auch den direkten Zugriff auf Speicheradressen, mit der Begründung, dass damit in manchen Situationen schnellere Programme geschrieben werden können. Da ein Programmierer dadurch aber auch sehr viele komplizierte Fehler verursachen kann (wenn das Programm versehentlich auf einen Speicherbereich zugreift, der gar nicht für das Programm reserviert ist oder der andere Daten verwaltet als eigentlich erwartet), lassen moderne Programmiersprachen den direkten Zugriff normalerweise erst gar nicht mehr zu.

Speicherzugriff

140

%DVLVZLVVHQ

Sandini Bib

3.2.7 Wer liest und interpretiert die gespeicherten Daten? Das Betriebssystem speichert die Daten. Die Interpretation der gespeicherten Daten ist allerdings nicht Sache des Betriebssystems, sondern Aufgabe des Programms. Das Betriebssystem ermöglicht dem Programm lediglich, seine Daten (in Form von Dateien) auf ein Speichermedium oder im Arbeitsspeicher zu speichern und wieder einzulesen. Einfache Daten, wie Zahlen und Texte, werden vom Compiler bzw. Interpreter automatisch korrekt ausgewertet. Zum Lesen und Schreiben von Daten, die in speziellen Formaten vorliegen (z. B. Bilder), finden Sie oft in der Bibliothek der Programmiersprache passende Features. Falls diese nicht in der Programmiersprache vorhanden sind, können Sie in der Regel externe Komponenten benutzen, die Sie teilweise kaufen müssen, die aber auch häufig kostenlos im Internet zur Verfügung stehen (besonders für freie Programmiersprachen wie Java). Eine sehr häufige Aufgabe beim Programmieren, das Speichern von vielen verschiedenen zusammenhängenden Informationen, wie z. B. der Kunden-, Artikel- und Bestellinformationen in einem Verkaufsgeschäft, übernehmen so genannte Datenbanksysteme. Im Vergleich zum reinen Speichern von Dateien sind Programme, die Datenbanksysteme zum Speichern der Daten verwenden, für Programmierer meist sehr einfach zu erstellen und vor allen Dingen auch sehr schnell im Zugriff. Datenbanken behandelt das Buch in Kapitel 9.

3.3

Wie werden Programme geschrieben und für den Computer übersetzt?

3.3.1 Texteditoren, Programmiereditoren, Entwicklungsumgebungen Wie Programme heutzutage geschrieben werden, wissen Sie bereits. Sie haben ja schließlich schon eigene Programme entwickelt. Programme werden eigentlich immer zunächst als Quellcode in einer Textdatei geschrieben. Im einfachsten Fall nutzen Sie dazu einen Texteditor. Für viele Programmiersprachen können Sie aber auch Entwicklungsumgebungen nutzen, die das Programmieren über zahlreiche Features erheblich vereinfachen. Ein Zwischending sind Programmier-Editoren. Diese bieten nur grundlegende Features, wie beispielsweise die farbliche Hervorhebung spezieller Programmteile, die Verwaltung von Projekten und den Aufruf des Compilers. Spezielle Features wie Debugger und eine visuelle Formulargestaltung (wie bei Delphi/Kylix) fehlen diesen einfa-

:LH ZHUGHQ 3URJUDPPH JHVFKULHEHQ XQG IU GHQ &RPSXWHU EHUVHW]W"

141

Sandini Bib

chen (und meist kostenfreien) Editoren normalerweise. Im Internet finden Sie für die verschiedenen Sprachen eine Vielzahl an teilweise sehr guten Programmiereditoren. Einige Vertreter dieser Spezies sind sogar in der Lage, verschiedene Programmiersprachen zu erkennen und einen Quelltext mit korrekten farblichen Hervorhebungen darzustellen.

3.3.2 Maschinencode und Assembler Früher wurden Programme direkt in der Sprache geschrieben, die der Computer versteht: der Maschinensprache. Wie Sie ja bereits wissen, besteht diese Sprache aus einzelnen Befehlen, die aus Nullen und Einsen zusammengesetzt sind. Über diese Befehle können Sie in Maschinensprache Daten im Speicher ablegen, zwischen Speicherbereichen kopieren, Speicherbereiche addieren, multiplizieren, dividieren und so weiter. Die ersten Computer waren so einfach, dass der Prozessor (der damals noch aus Schaltrelais oder Schaltröhren bestand) nur sehr wenige Befehle verstand. Damals war ein Programmierer froh, wenn er es schaffte, eine einfache mathematische Berechnung zu programmieren, die dann vielleicht Daten von einer Lochkarte einlas (die natürlich auch binär kodiert waren) und das Ergebnis wieder auf einer Lochkarte ausgab. Damals war Maschinensprache noch übersichtlich, weil der Computer und die zu lösenden Probleme sehr einfach waren. Die ersten Maschinensprache-Programme wurden übrigens über elektrische Steckoder Schalttafeln „geschrieben“, die modernere Variante nutzte dann bereits Lochkarten (bei denen ein Loch eine 1 darstellte). Heute lässt wohl kein Computer mehr zu, Programme direkt in Maschinensprache zu entwickeln. Wenn Sie einige Monate Zeit haben, können Sie aber versuchen, das Format von ausführbaren Dateien zu erforschen, und diese mit den entsprechenden Maschinensprachebefehlen dann selbst erzeugen. Assembler

Die ersten echten Programmiersprachen waren so genannte Assemblersprachen. Eine Assemblersprache besteht aus Befehlen, die die einzelnen Maschinensprache-Befehl in einer für den Menschen leichter lesbaren Form darstellen, und einer Syntax zur Programmierung. Ein Assembler-Programm wird in einer Textdatei gespeichert. Damit das Programm ausgeführt werden kann, wird es von einem Assembler in Maschinencode übersetzt. Ein einfaches Assembler-Programm, das nichts anderes macht als in einer Konsolenanwendung den Bildschirm zu löschen, sieht beispielsweise so aus: mov cx, 2000 mov bx, 0B800h mov es, bx

142

%DVLVZLVVHQ

Sandini Bib

mov di, 0 mov ax, 0720h rep stosw

Assemblerprogramme sind sehr schwer zu verstehen, weil sie lediglich auf den Maschinensprachebefehlen einer CPU-Familie basieren und weil Sie dazu gute Kenntnisse des internen Aufbaus der CPU, des Arbeitsspeichers und anderer Computerbestandteile besitzen müssen. Assemblerbefehle sind aber wesentlich leichter zu lesen als Maschinencode. Zudem bieten viele Assembler auch schon erweiterte Features, wie das Entwickeln und Benutzen von Funktionen und sogar objektorientierte Programmierung. Heute werden nur noch sehr wenige Programme in einer Assemblersprache programmiert. Die Assemblerprogrammierung ist in Bezug auf die heutigen komplexen Probleme einfach viel zu aufwändig. Zwei Zahlen über ein Assemblerprogramm zu addieren ist einfach, eine Anwendung zu entwickeln, die Adressen in einer Datenbank verwaltet und die Suche nach diesen ermöglicht, ist nahezu unmöglich. Assemblerprogramme kommen teilweise noch in Computerspielen vor, die zwar grundsätzlich meist in den höheren Programmiersprachen C und C++ geschrieben werden. Besonders zeitkritische Routinen werden aber auch heute noch in einigen Fällen in einer Assemblersprache entwickelt. Virenprogrammierer werden Ihre Programme wohl auch noch in einigen Fällen in Assembler entwickeln. Assemblerprogramme werden, wenn sie korrekt programmiert sind, in der Regel etwas schneller ausgeführt als Programme, die in einer höheren Programmiersprache geschrieben wurden. Das liegt daran, dass Assemblerprogramme sehr direkt mit der CPU und dem Arbeitsspeicher arbeiten. Deshalb können Sie diese Programme in modernen Programmiersprachen auch häufig noch in höhere Programme einbinden. Aber selbst ganze Betriebssysteme wurden und werden nicht in einer Assemblersprache, sondern einer höheren, einfacher anzuwendenden Programmiersprache entwickelt. Bei Linux ist das z. B. die Sprache C (allerdings mit eingebetteten Assembler-Routinen).

3.3.3 Compiler Programme werden heute fast ausschließlich in einer höheren Programmiersprache geschrieben, die wesentlich einfacher anzuwenden ist als eine Assemblersprache und viel mehr Features bietet. Diese Programme werden meist von einem Compiler in Maschinencode übersetzt. Ein Compiler speichert die übersetzten Befehle (genau wie ein Assembler) in eine ausführbare Datei (Abbildung 3.5).

:LH ZHUGHQ 3URJUDPPH JHVFKULHEHQ XQG IU GHQ &RPSXWHU EHUVHW]W"

143

Sandini Bib

Quellcodedatei Write(”Zahl 1: ”); Readln(zahl1); Write(”Zahl 2”); readln(zahl2); Writeln(zahl1 + zahl2);

ausführbare Datei

Compiler

0010010100100110 0000011011000101 0100101001010000 0100100101001010 1110101001010010 0011010100110101 0110101000010010 0101010010100101 1110010100101001 1001010010100000 1101001100011111 1101001100011000 0011011000110101 1101101010011001 Abbildung 3.5: Kompilieren einer Quellcodedatei in eine ausführbare Datei

Die in der ausführbaren Datei gespeicherten Befehle können nun vom Betriebssystem oder direkt von der CPU ausgeführt werden, wenn die Datei gestartet wird. Für die Ausführung des Programms ist nur noch die ausführbare Datei notwendig. Der Anwender startet diese Datei (über die Betriebssystem-Oberfläche oder eine Konsole), die Datei wird in den Arbeitsspeicher geladen und Anweisung für Anweisung von der CPU ausgeführt.

3.3.4 CPU- und Betriebssystem-spezifische Programme Eine CPU besitzt einen bestimmten Satz an Befehlen. Diese Befehle sind bei unterschiedlichen CPU-Typen (z. B. Intel- und Sun-CPUs) teilweise komplett verschieden. Ein Programm, das für eine Intel-CPU entwickelt wurde, kann nicht auf einer Sun-CPU mit deren völlig anderen Befehlssatz ausgeführt werden. Um aber Programme, die für ältere Versionen einer CPU entwickelt wurden, auch auf neueren Versionen ausführen zu können, enthalten die neue Versionen immer den kompletten Befehlssatz der alten CPU. Dabei werden CPUs in Familien eingeteilt. X86

144

Eine wichtige Familie ist dabei die X86-Familie. Diese steht für alle Prozessoren, die auf der uralten 8086-CPU basieren (also vom 8086-Prozessor über den 80486 bis zum Pentium IV und den AMD-Prozessoren). Alle CPUs, die den ursprünglichen Befehlssatz besitzen, werden dieser

%DVLVZLVVHQ

Sandini Bib

Familie zugerechnet (also auch AMD-Prozessoren). Es gibt natürlich auch neuere Familien, weil neuere Prozessoren immer auch mächtigere Befehle besitzen, auf die moderne Programme oft nicht verzichten können. Zur Pentium-Familie gehören z. B. alle Pentium- und die Pentiumkompatiblen AMD-Prozessoren. Assembler und Compiler erzeugen Maschinencode, der normalerweise einer CPU-Familie zugeordnet ist (in Einzelfällen auch einzelnen CPUTypen). Diese Programme laufen also auf allen CPUs einer bestimmten Familie, aber nicht auf CPUs anderer Familien. Hinzu kommt, dass Programme immer auch BIOS- und Betriebssystembefehle nutzen. Ein Maschinencode-Programm, das unter Windows entwickelt wurde, kann deswegen nicht direkt8 unter Linux ausgeführt werden.

3.3.5 Interpreter Ein Interpreter arbeitet etwas anders als ein Compiler. Er übersetzt die Befehle einer höheren Programmiersprache nicht in eine ausführbare Datei, sondern interpretiert und übersetzt einzelne Anweisungen und sendet diese direkt an die CPU bzw. an das Betriebssystem. Ein Interpreter benötigt immer die Quellcodedatei zur Ausführung des Programms. Beispiele für moderne interpretierte Programmiersprachen sind Perl, VBScript und JavaScript. (Perl-)Quellcodedatei write “x:\n”; $x = ; chop(x); write “y:\n”; $y = ; chop($y); $z = $x * $y; print “$x * $y = $z”; Print Zahl1 +

write “x:\n”;

Interpreter

0010010100100110 0110101001010010 0101010010100111 0000001110101000

CPU

Abbildung 3.6: Ausführung eines Perl-Programms durch den Perl-Interpreter

8.

Verschiedene spezielle Programme sind aber auch in der Lage, Windows unter Linux (oder umgekehrt) zu simulieren, sodass ältere Windows-Programme auch unter Linux laufen (und umgekehrt).

:LH ZHUGHQ 3URJUDPPH JHVFKULHEHQ XQG IU GHQ &RPSXWHU EHUVHW]W"

145

Sandini Bib

Vor- und Nachteile

Ein Vorteil eines Interpreters ist, dass ein Programm ohne große Probleme auch auf anderen Betriebssystemen ausgeführt werden kann, sofern dieses Programm keine für ein Betriebssystem spezifischen Features nutzt und ein passender Interpreter zur Verfügung steht. Dieser Vorteil wird in der Praxis allerdings recht selten genutzt. Der andere wichtige Vorteil ist die Tatsache, dass ein Programm ohne große Probleme direkt auf dem Rechner, auf dem das Programm ausgeführt wird, nachträglich geändert werden kann. Zur Änderung eines kompiliertes Programms benötigen Sie hingegen immer den zur Programmiersprache passenden Compiler, der meistens auf den Computern, auf denen das Programm ausgeführt wird, nicht vorliegt. Dieser Nachteil von Compilern ist allerdings in der Praxis relativ unwichtig und kann durch moderne Programmiertechniken ohne große Probleme ausgeglichen werden. Interpretierte Programme besitzen also keine wesentlichen Vorteile gegenüber kompilierten. Ein gravierender Nachteil eines Interpreters gegenüber einem Compiler ist die langsame Ausführung des Programms. Das Einlesen, das Überprüfen auf mögliche Fehler und das schließliche Übersetzen jeder einzelnen Anweisungen kostet Zeit. Der andere Nachteil ist, dass der Programmierer immer den Quellcode mitliefern muss, wenn sein Programm auf einem anderen Computer ausgeführt werden soll.

Interpreter in Browsern

Ausnahmen

In einigen Programmiersprachen ist aber der Einsatz eines Compilers gar nicht möglich. JavaScript-Programme sind beispielsweise oft in HTML-Dateien eingebunden. Auf diese Weise werden spezielle Features wie Menüs auf Webseiten programmiert, die mit HTML alleine nicht machbar sind. Da HTML-Dateien in Textform vorliegen, ist ein Kompilieren der enthaltenen Programme nicht möglich. Also wird der JavaScript-Programmcode von einem Interpreter ausgeführt, der Teil des Browsers ist. Ausnahmen von der Regel sind spezielle Programmiersprachen wie Java, C# und Visual Basic .NET. Der mit diesen Sprachen erzeugte Programmcode wird zwar auch von Interpretern ausgeführt. Diese interpretieren aber keinen Quellcode, sondern einen speziellen Zwischencode.

3.3.6 Zwischencode- und Just-In-Time-Compiler Ein Mittelding zwischen Compiler- und Interpretersprachen sind Programmiersprachen wie Java und die neuen Microsoft .NET-Sprachen (wie C# und Visual Basic .NET). Die Compiler dieser Programmiersprachen übersetzen einen Quellcode nicht in Maschinencode, sondern in speziellen Zwischencode. Zwischencode ist bereits so etwas wie Maschinensprache (enthält also einzelne sehr einfache Befehle), allerdings in einer sehr allgemeinen Form, die nicht spezifisch für eine bestimmte

146

%DVLVZLVVHQ

Sandini Bib

CPU-Familie oder ein bestimmtes Betriebssystem gilt. Ein kompiliertes Java-, C#- oder Visual Basic .NET-Programm kann deswegen auch nicht direkt von der CPU oder dem Betriebssystem ausgeführt werden. Im einfachsten Fall ist zur Ausführung eines Zwischencode-Programms ein spezieller Interpreter notwendig. Dieser übersetzt nun aber keinen Quellcode, sondern die speziellen Zwischenbefehle in Maschinencode. (Java-)Quellcodedatei class Hello { public static void main(...) { System.out.println(“Hello”); } }

Compiler Zwischencodedatei 01101100 11011000 01101111 01101010 01101010 00110100 11010100 00101001

01110000 01101010 01101010 00001001 01010100 11101110 00000000 00101010

Abbildung 3.7: Übersetzung eines Java-Quellcodes in eine Java-Zwischencodedatei Zwischencodedatei 01101100 11011000 01101111 01101010 01101010 00110100 11010100 00101001

01110000 01101010 01101010 00001001 01010100 11101110 00000000 00101010 01101100 01110000

Interpreter 0010010100100110

CPU

Abbildung 3.8: Ausführung einer Java-Zwischencodedatei durch einen Java-Interpreter

:LH ZHUGHQ 3URJUDPPH JHVFKULHEHQ XQG IU GHQ &RPSXWHU EHUVHW]W"

147

Sandini Bib

Vor- und Nachteile

Zwischencode-Programme besitzen gegenüber interpretierten Quellcode-Programmen und auch gegenüber Maschinencode-Programmen einige Vorteile. Interpreter, die Quellcode in Maschinencode umsetzen, sind langsamer als Interpreter, die Zwischencode übersetzen. Das liegt einmal daran, dass Quellcode vor der Übersetzung immer erst noch auf die Einhaltung der Syntaxregeln der Programmiersprache überprüft werden muss, und zum anderen, dass Zwischencode den Befehlen einer Maschinensprache bereits sehr nahe kommt und die Übersetzung deswegen wesentlich einfacher ist. Ein anderer, nicht unwesentlicher Vorteil von Zwischencode-Programmen (gegenüber interpretierten Quellcode-Programmen) ist, dass der Quellcode des Programms nicht mit ausgeliefert werden muss, damit dieses auf einem Computer ausgeführt werden kann. Ein wichtiger Vorteil gegenüber Maschinencode-Programmen ist, dass ein Zwischencode-Programm erst bei der Ausführung in Maschinencode umgesetzt wird. Auf dem Betriebssystem muss prinzipiell nur ein passender Interpreter vorliegen. Wenn gleichzeitig noch alle im Programm benötigten Bibliotheken für das Betriebssystem vorhanden sind, kann dasselbe Programm prinzipiell ohne Probleme auf verschiedenen Betriebssystemen ausgeführt werden. Ein unter Windows kompiliertes Java-Programm kann beispielsweise auch ohne Probleme unter Linux ausgeführt werden. Der Interpreter setzt die Zwischencode-Befehle ja schließlich erst in spezifischen Maschinencode um, wenn das Programm ausgeführt wird. Das führt in der Regel zu einem anderen Vorteil. Maschinencode-Programme werden normalerweise (automatisch) so kompiliert, dass sie auf allen CPU-Typen einer Familie ausgeführt werden können und möglichst auch alle zusammengehörigen Betriebssystem-Varianten berücksichtigen (also beispielsweise auf Windows 95 bis XP laufen). Die erzeugten Befehle sind deswegen nicht für alle Systeme optimal. Zwischencode-Interpreter können jedoch sehr optimierten Maschinencode erzeugen, wenn sie speziell für einen CPU-Typ und/oder ein spezielles Betriebssystem entwickelt wurden. Damit wird der Nachteil von interpretierten Zwischencode-Programmen, dass diese grundsätzlich langsamer ausgeführt werden als Maschinencode-Programme, ein wenig aufgehoben (und fällt beim Just-In-Time-Kompilieren, wie ich es im nächsten Abschnitt beschreibe, fast ganz weg). Ein anderer Nachteil dieser Programme ist, dass sie natürlich keine Betriebssystem-spezifischen Features benutzen können, sondern immer nur die Features, die auf allen relevanten Betriebssystemen verfügbar sind.

Laufzeitum-

Der Interpreter und die Bibliotheken einer Zwischencode-Programmiersprache werden auch als Laufzeitumgebung oder Virtuelle Maschine bezeichnet. Eine Laufzeitumgebung beinhaltet alles das, was zur Ausführung von interpretierten Programmen notwendig ist (also den

gebung, Virtuelle Maschine

148

%DVLVZLVVHQ

Sandini Bib

Interpreter, die Bibliotheken und andere notwendige Dinge). Bei java.sun.com können Sie Java-Laufzeitumgebungen für die verschiedensten Betriebssysteme downloaden. Jede dieser Laufzeitumgebungen enthält einen spezifischen Java-Interpreter und alle allgemeinen JavaBibliotheken. Java-Programme, die auf einem beliebigen System kompiliert wurden, können deswegen auf verschiedenen Betriebssystemen ausgeführt werden. Laufzeitumgebungen müssen auch nicht immer komplett vorliegen. Die Laufzeitumgebung für C#- und Visual Basic .NET-Programme ist zurzeit beispielsweise komplett nur für Windows verfügbar. Für Linux gibt es eine Variante, die einfache Konsolenanwendungen ausführen kann. Die Umsetzung der Bibliothek, die die Formulare und Steuerelemente enthält, fehlt in dieser Variante noch. Just-In-Time-Compiler Zwischencode-Programme sind im Vergleich zu Maschinencode-Programmen recht langsam, wenn sie interpretiert werden. Ein Interpreter muss eben alle Programmteile immer wieder neu übersetzen, auch wenn einzelne mehrfach verwendet werden. Dieser große Nachteil wird in vielen Laufzeitumgebungen dadurch ausgeglichen, dass für die Übersetzung keine Interpreter, sondern so genannte Just-In-Time-Compiler (abgekürzt als „JIT“) eingesetzt werden. Ein Just-In-Time-Compiler arbeitet ähnlich einem Interpreter. Er speichert aber bereits übersetzte Programmteile im Arbeitsspeicher (in seinem Cache9). Dabei werden immer nur die beim Ablauf des Programms gerade aktuellen Programmteile übersetzt (weswegen es auch „Just-In-Time“ heißt). Werden diese bereits übersetzten Programmteile dann im weiteren Verlauf des Programms noch einmal aufgerufen, liest der Just-In-Time-Compiler diese einfach aus dem Cache und sendet den Maschinencode direkt zur CPU bzw. zum Betriebssystem. Ein Just-In-Time-Compiler ist also nur bei der ersten Ausführung eines Programmteils langsamer als ein MaschinencodeProgramm. Ab der zweiten Ausführung ist die Geschwindigkeit aber mindestens dieselbe. In der Regel führt die bessere Optimierung des interpretierten Programms ab der zweiten Ausführung eines Programmteils auch zu einer höheren Performance als bei den meist allgemein gehaltenen Maschinencode-Programmen. Just-In-Time-Compiler sind häufig mit Zwischencode-Interpretern verwoben. Der Java-Interpreter benutzt ab der Version 1.2 z. B. implizit den Java-JIT (was Sie über eine Umgebungsvariable aber auch ausschalten 9.

Ein Cache ist im Allgemeinen ein schneller Speicherbereich (z. B. im Arbeitsspeicher), in dem ein Programm oder das Betriebssystem Daten, die von einem langsamen Speicher (z. B. der Festplatte) gelesen werden, für eine weitere Benutzung temporär zwischenspeichert. Werden diese Daten mehr als einmal benötigt, können sie aus dem schnelleren Cache gelesen werden.

:LH ZHUGHQ 3URJUDPPH JHVFKULHEHQ XQG IU GHQ &RPSXWHU EHUVHW]W"

149

Sandini Bib

können). Einige Just-In-Time-Compiler sind auch in der Lage, aus dem Zwischencode eine komplette ausführbare Datei zu erzeugen. Wird diese dann aufgerufen, entfällt das Interpretieren und Just-In-TimeKompilieren komplett, womit die Ausführungsgeschwindigkeit erheblich verbessert wird. Da die ausführbare Datei die Zwischencodedatei nicht ersetzt und in der Regel erst auf dem Zielsystem erzeugt wird, bleibt die Portabilität von Zwischencodeprogrammen auch mit dieser Variante erhalten. Just-In-Time-Compiler sind mittlerweile so gut, dass moderne Programmier-Systeme wie Java und Microsoft .NET erst gar keine Maschinencode-Compiler mehr verwenden, sondern direkt mit Just-In-TimeCompilern arbeiten.

3.4

Übersicht über die aktuellen Software-Architekturen

Software wird heutzutage in verschiedenen Architekturen entwickelt. Zwei Software-Architekturen kennen Sie bereits aus Kapitel 2: Konsolenund einfache normale Anwendungen mit Fenstern und Steuerelementen. Darauf will ich hier auch nicht weiter eingehen. Ein Programmierer sollte aber noch ein wenig mehr von dem wissen, was auf modernen Systemen möglich ist. Deshalb finden Sie hier eine kurze Beschreibung der aktuellen Software-Architekturen. Sie werden diese aber nicht im Rahmen dieses Buchs näher kennen lernen oder selbst Programme dafür entwickeln. Sie müssen schließlich erst einmal die Grundlagen kennen.

3.4.1 Makros Als Makro werden im Allgemeinen selbst geschriebene Programme bezeichnet, die in eine Standardanwendung integriert sind. Viele dieser Anwendungen, wie Microsoft Office oder Corel Draw, ermöglichen Ihnen, ihre bereits vorhandene Funktionalität über benutzerdefinierte Programme zu erweitern. Dabei können Sie natürlich immer auch auf die Funktionen der Anwendung zurückgreifen. Viele Anwendungen machen es Ihnen mit der Möglichkeit Makros aufzuzeichnen sogar sehr einfach, eigene Programme zu erstellen. Makros erleichtern die Arbeit mit Standardanwendungen

150

Makros helfen Ihnen bei Aufgaben, die immer wieder anfallen, für die die Anwendung aber keine direkte und einfach anzuwendende Unterstützung bietet. Wenn Sie beispielsweise in einer Textverarbeitung häufiger ein Wort fett, kursiv und in roter Schrift formatieren müssen, hilft

%DVLVZLVVHQ

Sandini Bib

ein Makro, das diese Schritte automatisch ausführt, enorm bei Ihrer täglichen Arbeit. Aber Makros sind auch häufig wesentlich komplexer und führen Aufgaben aus, die die Anwendung selbst nicht beherrscht. In den Büchern der Lernen-Reihe werden beispielsweise alle größeren Quellcodes üblicherweise mit Zeilennummern versehen. Es wäre nun sehr aufwändig gewesen, diese immer wieder selbst von Hand hinzuzufügen, besonders dann, wenn ein Quellcode nachträglich doch noch einmal geändert werden muss. Also hat ein Mitarbeiter des Verlags ein Makro geschrieben, das die aktuell selektierten Zeilen einliest, diese mit einer Zeilennummer versieht und wieder in das Dokument zurückschreibt. Um eine Startnummer angeben zu können (für Quellcodes, die in mehreren Teilen dargestellt werden), hat der Programmierer ein Formular in das Makro integriert, in dem vor der Ausführung der Nummerierung die notwendigen Einstellungen vorgenommen werden können. Die Erstellung und das Debuggen des Makros hat wohl etwas Zeit gekostet, dafür können Autoren ihre Quellcodes nun innerhalb von Bruchteilen einer Sekunde ohne viel Arbeit nummerieren. In den meisten Anwendungen können Sie Makros über selbst definierte Menü- oder Symbolleisten-Befehle oder über zugewiesene Tastenkombinationen aufrufen. Die Arbeit mit dem Makro ist dann für Sie (bzw. den Anwender sehr einfach). Anwender, die Ihr Makro nicht kennen, wissen dann häufig gar nicht, dass es sich nicht um eine Funktionalität der Anwendung handelt. Makros werden nicht nur für einfache Aufgaben eingesetzt. Einige Firmen setzen für ihre Geschäfte auch komplexe Anwendungen ein, die über Makros in Standardanwendungen programmiert wurden. Sehr häufig werden dazu Datenbankprogramme verwendet, weil diese für eine häufige Aufgabe beim Programmieren, die Verwaltung von Daten in einer Datenbank, bereits eine gute Grundfunktionalität bieten. Der Anwender sieht bei diesen häufig gar nicht mehr, dass eine Standardanwendung dahintersteckt, weil die Anwendung alle Standardfenster, Menü- und Symbolleisten versteckt und nur die eigenen anzeigt.

Makros auch

Die Programmierung mit Makros ist jedoch häufig gegenüber echten Programmen eingeschränkt. Oft beherrscht die Programmiersprache nicht alle notwendigen Techniken (weil es sich häufig um ScriptingSprachen handelt, siehe Seite 157). Besonders aber bei der Gestaltung der Benutzeroberfläche lassen viele Standardanwendungen zu wünschen übrig. Deshalb muss ein Programmierer sehr gut überlegen, ob zur Lösung einer Aufgabe Makros oder separate, echte Anwendungen entwickelt werden sollten.

Eingeschränkte

         

für komplexe Programme

Programmierung

151

Sandini Bib

3.4.2 Komponentenbasierte Anwendungen Moderne Anwendungen nutzen, neben den Funktionen der Bibliothek der Programmiersprache, meist auch Komponenten. Die Steuerelemente, die Sie in der Delphi/Kylix-Anwendung aus Kapitel 2 auf dem Formular platziert haben, sind z. B. solche Komponenten. Eine Komponente enthält einen fertig programmierten, allgemein anwendbaren Programmteil. Komponenten können in den verschiedensten Anwendungen eingesetzt werden. Eine Komponente, die das Drucken von Texten und Grafiken und eine Vorschau des Ausdrucks ermöglicht, kann beispielsweise von einem Textverarbeitungsprogramm, aber auch von einer Adressverwaltungs-Anwendung genutzt werden. Komponenten werden normalerweise von Programmierern so entwickelt, dass eine Nutzung nicht nur für den aktuell benötigten Zweck, sondern auch in anderen Software-Projekten möglich ist. Dazu werden Komponenten oft auch sehr genau auf Fehler überprüft, sodass der Programmierer sich bei deren Verwendung auf die Funktionalität verlassen kann. Komponenten auch im Computer

Im Prinzip können Sie komponentenbasierte Anwendungen mit einem Computer vergleichen: Ein solcher besteht nämlich aus einer Menge Komponenten, wie z. B. der CPU, der Grafikkarte und der Festplatte, und der eigentlichen „Anwendung“, nämlich dem Motherboard, auf dem alle Komponenten aufgesteckt und durch das diese gesteuert werden. Einzelne Komponenten sind zwar für sich alleine nicht lauffähig, können aber ohne Probleme auch in anderen (kompatiblen) Computern eingesetzt werden. Einige Computerkomponenten, wie beispielsweise der Drucker, können ohne Probleme auch von mehreren Computern gleichzeitig verwendet werden. Der Sinn dabei ist, dass Computerkomponenten nur einmal entwickelt werden müssen und dann in verschiedenster Weise wiederverwendet werden können. Müsste ein Computerhersteller hingegen bei der Entwicklung eines neuen Computers alle Bestandteile immer wieder neu entwickeln, weil diese fest zum Motherboard (zum „Programm“) gehören, hätte dieser sehr viel Arbeit. Durch die Verwendung fertiger, getesteter und qualitativ hochwertiger Komponenten spart der Hersteller eine Menge Arbeit und erreicht eine höhere Qualität (was auch immer Ziel bei der Anwendungsentwicklung ist).

Interne und externe Komponenten

152

Programmkomponenten gibt es in zwei Varianten. Interne Komponenten gehören entweder zur Bibliothek der Programmiersprache (wie z. B. die Steuerelemente) oder sind in separaten, mit dem Compiler der Programmiersprache vorkompilierten Dateien gespeichert und werden über programmiersprachen-abhängige Techniken in ein Quellcode-Programm eingebunden. Wird die Anwendung kompiliert, wird der Programmcode dieser Komponenten vom Compiler in die erzeugte ausführbare

%DVLVZLVVHQ

Sandini Bib

Datei kopiert (weswegen ich diese Komponenten auch „interne Komponenten“ nenne). Die ausführbare Datei ist also im Ergebnis für sich alleine lauffähig. Externe Komponenten liegen hingegen immer in einer separaten Datei vor. Sie werden zwar ähnlich internen Komponenten in ein QuellcodeProgramm eingebunden, beim Kompilieren wird deren Programmcode aber nicht in die ausführbare Datei kopiert. Um diese auszuführen, muss immer auch die Komponentendatei vorhanden sein. Solche Komponenten können meist auch von Anwendungen verwendet werden, die in verschiedenen Programmiersprachen entwickelt wurden (was bei internen Komponenten prinzipiell nicht möglich ist). Ich selbst habe beispielsweise vor einigen Monaten mit Visual Basic 6 eine Komponente zum schnellen Ausdruck von Berichten, die ihre Daten aus einer Datenbank beziehen, entwickelt. Diese Komponente kann ich heute ohne Probleme auch in meinen neuen Delphi-Programmen einsetzen. Die Arbeit hat sich gelohnt, die Komponente ist einfach einzusetzen und macht ihre Arbeit sehr gut (ohne mich zu sehr zu loben, aber das muss ja auch einmal sein). Die Zusammenarbeit von Programmen mit Komponenten stellt Abbildung 3.9 dar.

Adressverwaltung

Textverarbeitung

Koponente zum Bearbeiten von Dokumenten

Komponente zum Drucken

Komponente zum DatenbankZugriff

Dokument

Drucker

Datenbank

Abbildung 3.9: Ein Beispiel für die Zusammenarbeit von Anwendungen mit Komponenten

         

153

Sandini Bib

Schnittstelle

Damit externe Komponenten von verschiedenen Anwendungen eingesetzt werden können (die in verschiedenen Programmiersprachen entwickelt wurden), basieren diese meist auf einem allgemeinen Kommunikationsmodell, das die Schnittstelle zur Komponente beschreibt. Eine Schnittstelle definiert, auf welche Weise die Kommunikation zwischen zwei Objekten stattfindet. Das mag etwas abstrakt klingen, ist aber eigentlich ganz einfach. Hier hilft wieder die Computer-Analogie: Um beispielsweise Grafikkarten verschiedener Hersteller in einem Computer einsetzen zu können, wurden und werden immer wieder neue Standards entwickelt. Der derzeit aktuelle Standard ist AGP. Dieser Standard definiert, wie die Grafikkarten-Steckplätze auf dem Motherboard aussehen müssen, wie die Anschlüsse dieser Steckplätze angesprochen werden und welche Befehle darüber gesendet werden können. Bei Programmkomponenten werden dazu spezielle Modelle wie COM (altes MicrosoftKonzept), .NET (neues Microsoft-Konzept), JavaBeans (Java-Konzept) oder CORBA (allgemeines, systemunabhängiges Konzept) verwendet. Auf diese Konzepte gehe ich im Buch allerdings nicht weiter ein. Für den Anwender sehen komponentenbasierte Anwendungen aus wie ganz normale Anwendungen. Lediglich wenn eine Komponente, die von einer Anwendung verwendet wird, nicht auf dem Computer des Anwenders installiert ist, kommt der Anwender mit dieser Art der Softwarearchitektur in Form einer entsprechenden Fehlermeldung in Berührung. Um beim Vergleich mit dem Computer zu bleiben: Interne Komponenten können Sie mit den internen Komponenten des Computers vergleichen. Um zwei Computer herzustellen, benötigen Sie auch zwei „Kopien“ einer Grafikkarte, einer CPU etc., die quasi in das Ergebnis „hineinkopiert“ werden. Wenn Sie allerdings zwei Computer mit einem Drucker ausstatten wollen, können Sie einfach dazu einen einzigen Drucker verwenden. Das wäre dann das Äquivalent zu einer externen Komponente. Ein Beispiel für eine externe Komponente ist die von Microsoft entwickelte Komponente Microsoft Rich Textbox Control, mit der ein Programm dem Anwender eine Möglichkeit zur Verfügung stellen kann, RTF10Dateien zu erzeugen und zu bearbeiten. Damit können Sie ohne großen Aufwand eine einfache Textverarbeitung programmieren. Wie Sie aus diesem Beispiel erkennen, werden Komponenten nicht nur vom Programmierer selbst, sondern auch von anderen Programmierern bzw. Firmen entwickelt und vertrieben. Ihnen stehen unzählige fertige kaufbare oder kostenfreie Komponenten zur Verfügung (die Sie natürlich über das Internet suchen und erwerben können). 10. RTF (Richt Text Format)-Dateien sind Textdateien mit Formatierungen ähnlich den Dateien, die Microsoft Word erzeugt.

154

%DVLVZLVVHQ

Sandini Bib

3.4.3 Verteilte Anwendungen Mit Hilfe der Komponentenstandards COM, .NET, JavaBeans und CORBA ist es ohne große Probleme möglich, Komponenten auf anderen Rechnern laufen zu lassen als die eigentliche Anwendung. Die komplette Anwendung wird damit auf mehrere Computer verteilt. Diese Art der Architektur wird meist nur in Firmen eingesetzt. So kann in einer Firma z. B. ein Computer dafür zuständig sein, Lieferscheine und Rechnungen zu drucken. Dieser Computer führt dazu eine Komponente mit Funktionen für das Drucken aus. Die Sachbearbeiter der Firma arbeiten mit einer Auftragsverwaltungsanwendung, die zwar teilweise lokal auf ihren Computern ausgeführt wird, die aber eben auch diese DruckKomponente nutzt, um Lieferscheine und Rechnungen zu drucken. Die Komponente enthält natürlich eine Menge an Programmierung, so dass die Auftragsverwaltungsanwendungen auf den einzelnen Computern nur die auszudruckenden Daten an die jeweilige Funktion übergeben müssen und keine Information darüber, wie gedruckt werden soll. Abbildung 3.10 veranschaulicht dieses Szenario. (Client-)Computer mit derAuftragsverwaltungsanwendung

(Server-)Computer mit Komponente zum Drucken von Lieferscheinen und Rechnungen

Drucker

Abbildung 3.10: Beispiel für eine einfache verteilte Softwarearchitektur

         

155

Sandini Bib

An einer verteilten Anwendung sind immer mindestens ein Server und meist mehrere Clients beteiligt. Der Server („Bediener“) stellt den Clients (den „Kunden“) bestimmte Dienste zur Verfügung, wie z. B. das Drucken von Lieferscheinen und Rechnungen. Deshalb werden verteilte Anwendungen häufig auch als Client/Server-Anwendung bezeichnet. Verteilte Anwendungen, die eigentlich nur in größeren Firmen eingesetzt werden, besitzen (natürlich) einige Vorteile gegenüber normalen Anwendungen: • Die auf einem Server ausgeführten Komponenten können von mehreren Anwendungen auf verschiedenen Clients verwendet werden. Die einzelnen Clients können deswegen recht leistungsschwach sein, wenn die größte Rechenarbeit in der Komponente ausgeführt wird (der Rechner, der die Komponente speichert, muss dann natürlich sehr leistungsstark sein). • Falls eine Änderung der Programmierung der Komponente notwendig ist, muss eine neue Version nur ein einziges Mal (auf dem Server) installiert werden. Im Idealfall ist keine Änderung der Programme auf den Clients notwendig. Dieser Vorteil wird besonders dann deutlich, wenn mehrere hundert Clients mit einer Server-Komponente arbeiten. • Ein weiterer Vorteil einer verteilten Architektur ist, dass über ServerKomponenten sehr gut Benutzerrechte verwaltet werden können. Das kann so programmiert sein, dass Benutzer in der Client-Anwendung auf dem Client einen Benutzernamen und ein Passwort eingeben müssen und die Client-Anwendung diese Login-Informationen an die Komponente weitergibt. Die Komponente überprüft dann bei der Ausführung bestimmter Aktionen, ob der eingeloggte Benutzer das dazu notwendige Recht besitzt. In komplexen Systemen sind Änderungen an den Benutzerrechten auf diese Weise recht einfach über die zentrale Komponente möglich und unabhängig von allen anderen Technologien, die die Server-Komponente selbst wieder einsetzt. Für den Einsatz einer verteilten Architektur gibt es noch mehr Gründe, die an dieser Stelle allerdings zu weit führen würden. Schließlich beschreiben ganze Bücher das Prinzip der verteilten Anwendung. Die aufgelisteten Features sind allerdings in meinen Augen schon die wichtigsten. Verteilte Anwendungen werden, genau wie einfache Anwendungen, mit modernen Programmiersprachen wie C++, C#, Delphi, Kylix, Java oder Visual Basic .NET entwickelt.

156

%DVLVZLVVHQ

Sandini Bib

3.4.4 Scripting-Programme Scripting-Programme sind einfache Anwendungen, die nicht kompiliert sind, sondern im Quellcode einer speziellen Scriptsprache gespeichert sind. Diese Anwendungen werden bei der Ausführung von einem Interpreter ausgeführt. Der Windows Scripting Host (WSH) ermöglicht z. B. die Ausführung von Visual Basic Script-Dateien (mit der Endung .vbs) oder JScript11-Dateien (mit der Endung .js) direkt in Windows (z. B. über einen Doppelklick auf der Datei). Die verschiedenen Linux-Shells erlauben die Ausführung von Shell-Skripten (die nichts anderes sind als Scripting-Programme) in von der Shell abhängigen Programmiersprachen. Über solche Programme können Sie immer wiederkehrende gleich bleibende Aufgaben wie das Kopieren von Dateien oder das Anlegen neuer Benutzer auf Ihrem System vereinfachen. Unter Windows spielen Scripting-Programme keine große Rolle. In Linux werden allerdings sehr viele Anwendungen über solche Programme installiert oder gestartet. Die meisten Scriptsprachen bieten jedoch keine Möglichkeit, eine Benutzeroberfläche zu erzeugen, was deren Verwendung erheblich einschränkt. Aber diese Sprachen sind eben nicht für komplette Programme, sondern für einfache „Skripts“ vorgesehen (wie der Name schon sagt). Scripting-Programme finden Sie aber auch in HTML-Dokumenten, wo diese die HTML-Seite um Features erweitern, die mit reinem HTML nicht möglich sind. Viele HTML-Seiten werden beispielsweise über JavaScript-Programme um Menüs erweitert, die vom Benutzer sehr einfach bedient werden können. Hier spielt die Anwendung einer Scriptsprache eine sehr große Rolle.

3.4.5 Internetanwendungen Haben Sie schon einmal bei www.amazon.de ein Buch gekauft? Oder bei www.ebay.de irgendetwas ersteigert? Oder eine Suchmaschine wie www.google.de benutzt? Dann haben Sie bereits mit einer Internetanwendung gearbeitet. Und dann wissen Sie auch, dass diese Anwendungsarchitektur gar nicht mehr aus unserem täglichen Leben wegzudenken ist. Falls Sie diese Art Anwendung noch nicht kennen, sollten Sie schnell einmal bei www.amazon.de vorbeischauen, dort ein wenig herumsurfen und vielleicht ein Buch kaufen. Ich warte so lange ...

11. JScript ist das Microsoft-Äquivalent zu JavaScript

         

157

Sandini Bib

HTML und Webserver

Die Basis einer reinen Internetanwendung (die im Browser ausgeführt wird) ist HTML (Hypertext Markup Language). HTML ist eine spezielle, textbasierte Sprache zur Definition von Dokumenten mit formatierten Texten, Bildern und verschiedenen Ein- und Ausgabeelementen. HTML ist allerdings keine Programmiersprache, sondern dient lediglich der Beschreibung des Aussehens einer Webseite. Ein Webserver stellt HTMLDateien (und andere Dateien) zur Verfügung. Wenn Sie im Browser www.amazon.de eingeben, sprechen Sie bei Amazon den Webserver mit dem Namen „www“ an und rufen dort die HTML-Datei ab, die Amazon als Start-Dokument für den Online-Shop vorgesehen hat. Viele HTMLDateien werden jedoch nicht statisch auf einem Webserver gespeichert, sondern bei jeder Anforderung erneut dynamisch erzeugt. Das ist dann die reine Form einer Internetanwendung. Über verschiedene HTMLEingabeelemente (wie Verweise, Schalter und Texteingabefelder) erlaubt eine HTML-Seite häufig auch Eingaben, die auf dem Webserver ausgewertet werden. Wenn Sie z. B. bei www.google.de einen Suchbegriff eingeben und die Suche über den GOOGLE-SUCHE-Schalter starten, rufen Sie auf dem Webserver ein Programm auf, das Ihre Eingabe auswertet, ein entsprechendes Ergebnis-HTML-Dokument erzeugt und Ihnen dieses zusendet. Internetanwendungen ähneln verteilten Anwendungen. Der Hauptteil des Programms wird auf einem Server ausgeführt. Im Normalfall ist der Client aber hier eben ein Browser, der HTML-Dokumente darstellt. Der Server, der in Wirklichkeit ein programmierbarer Webserver ist, stellt diese HTML-Dokumente dynamisch zusammen.

Der Vorteil

158

Der enorme Vorteil einer Internetanwendung ist, dass diese normalerweise über einen einfachen Browser von jedem Ort der Welt aus verwendet werden kann (einige spezielle Internetanwendungen erfordern aber auch die Installation einer separaten Client-Software). Abbildung 3.11 demonstriert dies am Beispiel eines Online-Bestellshops.

%DVLVZLVVHQ

Sandini Bib

Client Client WebBrowser

WebBrowser

Client

WebBrowser

Webserver

ShopAnwendung

BestellDatenbank

Abbildung 3.11: Ein Online-Bestellshop im Süden Deutschlands, der über das Internet von Clients im Norden von Deutschland verwendet wird

Internetanwendungen können Sie mit den meisten modernen Programmiersprachen entwickeln. Einige, wie Perl, PHP und die Programmierumgebung ASP, sind sogar primär für die Entwicklung solcher Anwendungen gedacht. Die mit diesen Programmiersprachen entwickelten Internetanwendungen werden normalerweise vollständig auf dem Webserver ausgeführt. Der Browser übernimmt im einfachsten Fall nur die Darstellung des HTML-Dokuments. In einigen Internetanwendungen werden aber auch Teile des Programms auf dem Client ausgeführt. Das hat den Grund, dass das zur Darstellung der Oberfläche im Internet-Browser verwendete HTML nur wenige dynamische Elemente besitzt und nur über Schalter und Verweise auf Benutzereingaben reagieren kann. Komplexe Benutzeroberflä-

         

Programme in HTML-Seiten

159

Sandini Bib

chen mit einer Menüstruktur sind mit HTML alleine nicht möglich. Dazu werden in HTML eingebundene Programme verwendet, wozu meist die Programmiersprache JavaScript und eher selten VBScript verwendet wird. Eine andere Möglichkeit zur Erweiterung von HTML-Seiten sind Java-Applets. Java-Applets sind spezielle in Java entwickelte Komponenten, die primär für den Gebrauch in HTML-Seiten vorgesehen sind. Mit Java-Applets können Sie die Oberfläche eines HTML-Dokuments nahezu mit allen Steuerelementen erweitern, die Sie auch von der grafischen Oberfläche Ihres Betriebssystems her kennen.

3.5

Übersicht über die aktuellen Programmiersprachen

Es gibt eine Unmenge an Programmiersprachen, die alle ihre Vor- und Nachteile haben. Jede Sprache verwendet einen etwas anderen Ansatz zur Entwicklung eines Programms. Die Grundlagen (die Sie in diesem Buch ja lernen) sind aber immer dieselben (wenn auch manche Programmiersprachen eine etwas exotische Vorstellung davon haben). Damit Sie einen kleinen Überblick über die zurzeit aktuellen Programmiersprachen erhalten, stelle ich diese kurz vor. Auf ältere und heute nicht mehr allzu häufig eingesetzte Sprache gehe ich dabei allerdings nicht ein. An der Adresse www.ualberta.ca/HELP/TUTOR/ProgTut.html finden Sie eine Übersicht über die aktuellen Programmiersprachen mit Links zu weiteren Informationen.

3.5.1 C und C++ C ist eine sehr alte Sprache, die aber auch heute noch eine große Bedeutung besitzt. Die Syntax ähnelt der von Java12. C ist aber darauf ausgerichtet, möglichst direkt auf die Hardware, den Speicher oder das Betriebssystem zuzugreifen. C-Programmierung ist deswegen nicht allzu einfach. Sie können mit C dafür aber sehr schnelle Programme entwickeln (was auch daran liegt, dass diese von hochoptimierten Compilern kompiliert werden). C++ basiert auf C, besitzt aber wesentlich mehr vorgefertigte Komponenten und die Möglichkeit, objektorientiert (und damit moderner) zu programmieren. Die hohe Geschwindigkeit von Cund C++-Programmen ist der Grund dafür, dass viele Betriebssysteme (!) wie z. B. Linux, viele Spiele und Hardware-Treiber komplett in diesen Sprachen geschrieben wurden und noch immer werden. 12. Java orientiert sich allerdings an C und C++ und nicht umgekehrt.

160

%DVLVZLVVHQ

Sandini Bib

Ein typisches C++-Programm, das nur Daten an der Konsole einliest und ausgibt, sieht beispielsweise so aus (ohne dass ich dieses hier allerdings erkläre!): #include #include int main() { string Eingabe, Ausgabe; cout > Eingabe; Ausgabe = "Hallo " + Eingabe; cout 0) and (steuer '' then formatierteZahl := FormatFloat(Zahlformat, Zahl) else formatierteZahl := FloatToStr(Zahl); { Zahl ausgeben } write(formatierteZahl); { Zeilenumbruch erzeugen, wenn notwendig } if Zeilenumbruch then writeln; end; procedure KonsoleTool.TextEingeben(InfoText: string); var eingabe: string; begin { Den Benutzer einen Text eingeben lassen } write(InfoText); readln(eingabe); { Eingabe in der Eigenschaft ablegen } EingegebenerText := Eingabe; { bei Texteingaben ist die Eingabe immer OK } EingabeOK := true; end; procedure KonsoleTool.ZahlEingeben(InfoText: string); var eingabe: string; begin { Den Benutzer einen Text eingeben lassen } write(InfoText); readln(eingabe);

      

Sandini Bib

65 { Konvertieren der Zahl und gleichzeitiges Überprüfen, 66 ob die Eingabe in eine Zahl konvertiert werden kann. 67 Dazu wird eine Ausnahmebehandlung eingesetzt. } 68 try 69 EingegebeneZahl := StrToFloat(eingabe); 70 { Wenn das Programm hier ankommt, konnte die Eingabe 71 konvertiert werden } 72 EingabeOK := true; 73 except 74 { Die Eingabe konnte nicht konvertiert werden } 75 EingabeOK := false; 76 end; 77 end; 78 end.

Die Methode TextAusgeben (Zeile 23) ist sehr einfach und muss wohl nicht näher erläutert werden). Die Methode ZahlAusgeben (Zeile 32) überprüft, ob überhaupt ein Zahlformat definiert wurde, und formatiert die übergebene Zahl nur dann. Falls das Zahlformat nicht gültig ist, erzeugt die Formatierung eine Ausnahme. Diese wird nicht behandelt, sondern einfach weitergegeben. Das aufrufende Programm wird so auf jeden Fall über ungültige Formate informiert. Die Methode TextEingeben (Zeile 47) ist wieder sehr einfach. Der eingegebene Text wird lediglich in der Eigenschaft EingegebenerText gespeichert. Da bei der Eingabe von Text keine Fehler auftreten können, wird die Eigenschaft EingabeOK in Zeile 56 bei jeder Eingabe auf true gesetzt. Es ist wichtig, dass Sie auch an solche Dinge denken. Die Eigenschaft EingabeOK kann aufgrund vorhergehender fehlerhafter Zahleingaben auch auf false gesetzt sein. Sie wissen nicht, wie ein Programmierer, der diese Klasse anwendet, mit dieser Eigenschaft umgeht. Auch wenn diese Eigenschaft bei der Eingabe von Text eigentlich nicht sinnvoll ist: Sie existiert und deshalb kann es sein, dass sie von einem Programmierer bei der Eingabe von Text berücksichtigt wird. Wenn die Methode TextEingabe diese Eigenschaft nicht auf true setzen würden, würde Ihre Klasse u. U. logische Fehler im Programm verursachen, die dann vielleicht nur sehr schwierig lokalisiert werden könnten. Die Methode zur Eingabe einer Zahl (Zeile 59) ist etwas komplexer. Hier kann es sein, dass die Eingabe des Anwenders nicht in eine Zahl konvertiert werden kann. Deshalb wird die Eingabe zunächst in einer StringVariablen entgegengenommen (Zeile 64). Um zu erkennen, ob die Eingabe konvertiert werden kann, wird die Konvertierung innerhalb einer Ausnahmebehandlung vorgenommen, die in Zeile 68 beginnt. Falls die

   

377

Sandini Bib

Konvertierung fehlerfrei ausgeführt werden kann, führt das Programm die Anweisung in Zeile 72 aus und setzt damit die Eigenschaft EingabeOK auf true. Schlägt die Konvertierung fehl, verzweigt das Programm in den except-Block, der in Zeile 73 beginnt, und setzt die Eigenschaft EingabeOK auf false. Wenn Sie diese Klasse ausprobieren, hält der Delphi/Kylix-Debugger das Programm per Voreinstellung an, wenn Sie eine ungültige Zahl eingeben und folglich die Ausnahme eintritt. Dieses Problem habe ich bereits in Kapitel 5, bei der Besprechung von Referenzargumenten, angesprochen. Um dieses Verhalten umzustellen, müssen Sie festlegen, dass der Debugger entweder bei allen Ausnahmen oder nur bei spezifischen nicht anhält. Die dazu notwendigen Einstellungen finden Sie in den Debugger-Optionen (im Menü TOOLS/DEBUGGER OPTIONS bzw. TOOLS/DEBUGGER-OPTIONEN). Im Register LANGUAGE EXCEPTIONS bzw. SPRACH-EXCEPTIONS können Sie festlegen, dass der Debugger bei Ausnahmen grundsätzlich nicht anhält oder dass er lediglich spezielle Ausnahmen ignoriert. Den Typ der Ausnahme finden Sie heraus, wenn Sie das Programm testen und einen ungültigen Wert eingeben. Delphi und Kylix nennen den Typ der Ausnahme in der erscheinenden Ausnahme-Meldung. Die Ausnahme, die in unserem Fall erzeugt wird, ist vom Typ EConvertError.

6.6

Die Referenz self bzw. this

Innerhalb einer Methode können Sie auf die anderen Methoden oder die Eigenschaften des Objekts zugreifen. Die Methoden der KonsoleToolKlasse greifen beispielsweise auf alle Eigenschaften des Objekts zurück. Normalerweise schreiben Sie dazu einfach den Namen des jeweiligen Elements, wie dies in der Methode ZahlAusgeben in Zeile 38 und 40 z. B. der Fall ist: 36 37 38 39 40

{ Zahl formatieren if Zahlformat > '' formatierteZahl else formatierteZahl

bzw. in einen String konvertieren } then := FormatFloat(Zahlformat, Zahl) := FloatToStr(Zahl);

Wenn Sie einen Bezeichner wie formatierteZahl verwenden, sucht der Compiler zunächst innerhalb der aktuellen Methode nach einer Variable oder einem Argument, die/das einen solchen Namen trägt. Wird er dort nicht fündig, sucht er eine Ebene höher, also in den Eigenschaften und Methoden. Findet er dort auch nichts, sucht er noch nach globalen Variablen, Funktionen und Prozeduren mit dem eingegebenen Namen.

378

      

Sandini Bib

In unserem Beispiel funktioniert der gewünschte Zugriff, weil innerhalb der Methode keine Variablen oder Argumente mit einem identischen Namen deklariert sind. Der Compiler schreibt die Daten in die Eigenschaften. Wenn aber innerhalb einer Methode gleichnamige Variablen oder Argumente vorkommen, müssen Sie dem Compiler explizit mitteilen, dass Sie auf Eigenschaften bzw. Methoden zugreifen wollen. Als (zugegebenermaßen etwas konstruiertes) Beispiel deklariere ich die Methode TextEingeben ein wenig um: 47 procedure KonsoleTool.TextEingeben(InfoText: string); 48 var EingegebenerText: string; 49 begin 50 { Den Benutzer einen Text eingeben lassen } 51 write(InfoText); 52 readln(EingegebenerText); 53 { Eingabe in der Eigenschaft ablegen } 54 EingegebenerText := EingegebenerText; 55 { bei Texteingaben ist die Eingabe immer OK } 56 EingabeOK := true; 57 end;

Das Problem wird hier deutlich in Zeile 54 sichtbar. Für den Compiler bedeutet diese Anweisung, dass das Programm den Wert der Variablen EingegebenerText aus dieser Variable ausliest und wieder dort hineinschreibt. Abgesehen davon, dass der Compiler solche Anweisungen einfach wegoptimiert, ist die Zuweisung unsinnig. Um solche Probleme zu lösen, bieten Delphi, Kylix und Java eine spezielle Referenz an, die die Instanz referenziert, mit der das Programm gerade arbeitet. In Delphi und Kylix heißt diese Referenz self, in Java this. Diese Referenzen werden innerhalb der Methoden der Klasse verwendet wie eine normale Referenz. Sie können also z. B. explizit auf Elemente der Klasse verweisen: 54

self und this referenzieren das aktuelle Objekt

self.EingegebenerText := EingegebenerText;

Auch wenn Sie es jetzt vielleicht noch nicht glauben, werden Sie self und this sehr häufig benötigen. Eine typische Anwendung ist z. B. die in Methoden, die zum Setzen von Eigenschaften verwendet werden (wie es bei der Kapselung verwendet wird, die ich ab Seite 396 beschreibe):

'LH 5HIHUHQ] VHOI E]Z WKLV

379

Sandini Bib

01 public class Kreis 02 { 03 double Radius; 04 void setRadius(double Radius) 05 { 06 this.Radius = Radius; 07 } 08 }

Setzen Sie self bzw. this konsequent ein, wenn Sie innerhalb von Methoden auf Elemente der Klasse zugreifen. Dann erkennen Sie am Quellcode sehr deutlich, dass es sich dabei um einen Methoden- oder Eigenschaften-Zugriff handelt. Ihr Programm wird leichter lesbar, verständlicher und damit auch besser wartbar. Ich halte mich daran und setze im weiteren Verlauf dieses Kapitels self bzw. this ein, wenn eine Methode auf Eigenschaften oder andere Methoden einer Klasse zugreift. Woher stammt this bzw. self? Es ist ziemlich interessant zu erfahren, woher self und this stammen. Wenn Sie das wissen, können Sie auch erklären, wie es sein kann, dass ein Programm die Methoden einer Klasse lediglich einmalig speichert, auch wenn mehrere Instanzen der Klasse erzeugt werden. Wie Sie ja bereits wissen, werden die Methoden einer Klasse immer nur einmalig im Arbeitsspeicher abgelegt. Eine Instanz einer Klasse speichert lediglich die Eigenschaften, also die Daten der Objekte. In Objekten einer Klasse, die Kreisdaten verwalten soll, 01 public class Kreis 02 { 03 double Radius; 04 double Umfang() 05 { 06 return Radius * 2 * 3.1415927; 07 } 08 }

wird der Radius beispielsweise in den Instanzen verwaltet, die Methode Umfang hingegen separat. Wenn Sie z. B. drei Instanzen dieser Klasse erzeugen:

380

      

Sandini Bib

Kreis kreis1 = new Kreis(); kreis1.Radius = 100; Kreis kreis2 = new Kreis(); kreis2.Radius = 150; Kreis kreis3 = new Kreis(); kreis3.Radius = 200;

belegt das Programm zwar drei Bereiche im Arbeitspeicher. Diese speichern aber nur die Daten, in unserem Fall den Radius. Wenn Sie im Programm eine Methode aufrufen, System.out.println(kreis1.Umfang());

erkennt der Compiler am Datentyp der Referenz, die Sie verwenden (kreis1), um welche Klasse es sich handelt. Da der Compiler die Methoden der Klasse selbst im Speicher ablegt, weiß er, wo diese zu finden sind (relativ von der Start-Adresse der Anwendung im Arbeitsspeicher aus gesehen). Er setzt den Methodenaufruf so um, dass die Methode im Programm korrekt aufgerufen wird. Die Methode, die ja lediglich einmalig vorhanden ist, muss aber wissen, von welcher Instanz aus sie aufgerufen wurde. Wenn die Methode Eigenschaften der Klasse liest oder beschreibt, muss ja schließlich die richtige Instanz dazu verwendet werden. Und hier kommen self und this ins Spiel. Der Compiler erweitert die Argumentliste von Methoden nämlich implizit um ein Argument vom Typ der Klasse, das bei Delphi und Kylix self und bei Java this heißt. Die Methode Umfang sieht dann im kompilierten Programm prinzipiell so aus: 04 05 06 07

double Umfang(Kreis this) { return this.Radius * 2 * 3.1415927; }

Beim Aufruf einer Methode übergibt der Compiler automatisch eine Referenz auf die Instanz, von der aus der Aufruf erfolgt, an dieses neue Argument (Zeile 4). Die Methode kann darüber nun die Instanz lokalisieren und verwendet immer die korrekten Daten. Um dies zu ermöglichen, assoziiert der Compiler beim Kompilieren Eigenschaften und Methoden, die innerhalb der Methode verwendet werden, zudem automatisch mit der this- bzw. self-Referenz, falls dies noch nicht der Fall ist (Zeile 6).

'LH 5HIHUHQ] VHOI E]Z WKLV

381

Sandini Bib

6.7

Private und öffentliche Elemente einer Klasse

Eine Klasse sollte natürlich Eigenschaften und Methoden besitzen, die von außen über eine Referenz auf eine Instanz dieser Klasse verwendet werden können. Diese Elemente werden als öffentliche (Public-)Elemente bezeichnet. Innerhalb einer Klasse gibt es aber auch immer wieder den Bedarf, Daten zu speichern und Methoden zu programmieren, die privat sind und nur innerhalb der Klasse verwendet werden können. Private Eigenschaften werden häufig für die Kapselung verwendet, die ich weiter unten, ab Seite 396, beschreibe. Private Methoden werden häufig verwendet, wenn ein bestimmter Algorithmus innerhalb mehrerer (öffentlicher) Methoden einer Klasse benötigt wird. Im Prinzip ist das vergleichbar mit privaten Variablen und privaten Funktionen/Prozeduren in den Modulen der strukturierten Programmierung. In dem Zusammenhang spricht man übrigens häufig von der Sichtbarkeit von Klassenelementen. Öffentliche Elemente sind innerhalb und außerhalb der Klasse sichtbar, private Elemente nur innerhalb der Methoden der Klasse. Jede Programmiersprache stellt Ihnen Sprachelemente zur Verfügung, über die Sie die Sichtbarkeit der Klassenelemente festlegen können. Sichtbarkeit in Delphi/Kylix-Klassen In einer Object Pascal-Klasse sind alle Elemente per Voreinstellung öffentlich. Sie können über die Schlüsselwörter private und public aber auch Blöcke für private bzw. öffentliche Elemente einrichten. Das folgende Beispiel demonstriert dies an einer Klasse, die zur Speicherung von Kreisdaten verwendet werden soll. Die Methode PI, die den Wert der gleichnamigen Kreiszahl berechnen und zurückgeben soll, ist als private Methode deklariert. Diese Methode wird von den Methoden Umfang und Oberflaeche aufgerufen, die PI für die Berechnung benötigen. PI wird berechnet, weil diese Zahl möglichst genau ermittelt werden soll. Um das Beispiel übersichtlich zu programmieren, habe ich nur eine einfache Berechnung von PI implementiert. Komplexere Algorithmen wie der von Euler berechnen PI wesentlich genauer. Im Internet finden Sie mehr Informationen dazu.

382

      

Sandini Bib

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

unit CKreis; interface uses Math; type Kreis = class { Private Klassenelemente } private function PI(): extended; { Öffentliche Klassenelemente } public Radius: double; function Umfang(): extended; function Oberflaeche(): extended; end; implementation function Kreis.PI(): extended; var i: integer; begin { PI möglichst einfach berechnen } result := 4 * ArcTan(1); end; function Kreis.Umfang(): extended; begin result := self.PI() * 2 * self.Radius; end; function Kreis.Oberflaeche(): extended; begin result := self.PI() * self.Radius * self.Radius; end; end.

Alle Deklarationen unterhalb von private oder public bis zum nächsten Sichtbarkeitsschlüsselwort bzw. bis zum Ende der Klassendeklaration besitzen die entsprechende Sichtbarkeit. Die Methode PI kann in diesem Beispiel nur innerhalb der Klasse aufgerufen werden, was ja auch in den Methoden zur Berechnung des Umfangs und der Oberfläche in Zeile 30 und 35 geschieht. Von außen – über eine Instanz dieser Klasse –



      

383

Sandini Bib

können nur die öffentlichen Elemente, also im Beispiel die Eigenschaft Radius und die Methoden Umfang und Oberflaeche verwendet werden: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

program PrivateElemente; {$APPTYPE CONSOLE} uses SysUtils, CKreis; var kreis1, kreis2: Kreis; begin { Zwei Instanzen der Klasse erzeugen } kreis1 := Kreis.Create(); kreis2 := Kreis.Create(); { Instanzen initialisieren } kreis1.Radius := 0.5; kreis2.Radius := 25; { und verwenden } writeln('Ein Kreis mit einem Radius von ', kreis1.Radius, ' besitzt einen Umfang von ', kreis1.Umfang()); writeln('Ein Kreis mit einem Radius von ', kreis2.Radius, ' besitzt einen Umfang von ', kreis2.Umfang()); end.

Wenn Sie versuchen, die Methode PI aufzurufen, writeln(kreis1.PI());

meldet der Compiler den Fehler „Undeclared identifier: 'PI'“. Diese Fehlermeldung ist etwas irreführend, weil PI ja deklariert ist. Der Compiler sollte besser melden, dass diese Methode von außen nicht aufgerufen werden kann.

384

      

Sandini Bib

Sie können in Delphi und Kylix private und public auch so verwenden, wie es in anderen Sprachen üblich ist, und diese Schlüsselwörter direkt vor jede Deklaration setzen: type Kreis = class private function PI(): extended; public Radius: double; public function Umfang(): extended; public function Oberflaeche(): extended; end;

Diese Art der Deklaration ist wesentlich sprechender und auch näher am allgemeinen Standard. Sichtbarkeit in Java-Klassen In Java deklarieren Sie private und öffentliche Elemente ebenfalls über die Schlüsselwörter private und public. Sie müssen diese Schlüsselwörter allerdings jeder Eigenschaft und Methode voranstellen: 01 public class Kreis 02 { 03 public double Radius; 04 05 private double PI() 06 { 07 return 4 * Math.atan(1); 08 } 09 10 public double Umfang() 11 { 12 return this.PI() * this.Radius * 2; 13 } 14 15 public double Oberflaeche() 16 { 17 /* PI möglichst einfach berechnen */ 18 return this.PI() * this.Radius * this.Radius; 19 } 20 21 }

Geben Sie weder private noch public an, ist das Element automatisch öffentlich. Wie bei Delphi und Kylix können die Methoden der Klasse auf alle Elemente zugreifen, auch auf private. Von außen kann ein Programm aber nur öffentliche Elemente verwenden:



      

385

Sandini Bib

01 public class PrivateElemente 02 { 03 public static void main(String[] args) 04 { 05 /* Zwei Instanzen der Kreis-Klasse erzeugen */ 06 Kreis kreis1 = new Kreis(); 07 Kreis kreis2 = new Kreis(); 08 09 /* Instanzen initialisieren */ 10 kreis1.Radius = 0.5; 11 kreis2.Radius = 25; 12 13 /* und verwenden */ 14 System.out.println("Ein Kreis mit einem Radius von " + 15 kreis1.Radius + " besitzt einen Umfang von " + 16 kreis1.Umfang()); 17 18 System.out.println("Ein Kreis mit einem Radius von " + 19 kreis2.Radius + " besitzt einen Umfang von " + 20 kreis2.Umfang()); 21 22 } 23 24 }

Wenn Sie in Java-Programmen versuchen, ein privates Element einer Klasse zu verwenden, meldet der Compiler den Fehler „Elemementname has private access5 in Klassenname“. Diese Fehlermeldung ist wesentlich aussagekräftiger als die des Delphi/Kylix-Compilers.

6.8

Überladen von Methoden

In einigen Fällen ist es sinnvoll, mehrere Varianten einer Methode zu besitzen, die unterschiedliche Argumente besitzen. Dieses Konzept setzt besonders Java sehr intensiv ein. Die println-Methode des System.outObjekts existiert in Java 1.4 z. B. in zehn verschiedenen Varianten. Die erste Variante besitzt kein Argument, die anderen Varianten arbeiten mit einem Argument unterschiedlichen Datentyps. So können Sie beim Aufruf dieser Methode entweder kein Argument oder eines mit einem nahezu beliebigen Datentyp übergeben. In Wirklichkeit handelt es sich aber nicht um nur eine einzige Methode. println ist tastsächlich zehn Mal deklariert. Lediglich die Argumentliste 5.

386

Zugriff

      

Sandini Bib

unterscheidet sich bei den verschiedenen Deklarationen. Und das ist genau der Punkt: Überladene Methoden besitzen denselben Namen, unterscheiden sich aber in der Argumentliste. Mit diesem Wissen können Sie eigene überladene Methoden in Ihre Klassen implementieren. In einer Klasse zur Speicherung von Personendaten wäre es z. B. sinnvoll, Methoden zu besitzen, über die die Eigenschaften der Objekte über eine einzige Anweisung beschrieben werden könnten. Wenn die Klasse folgende Eigenschaften besitzt:

Überladene Methoden in Java

01 public class Person 02 { 03 /* Eigenschaften */ 04 public String Vorname; 05 public String Nachname; 06 public String Ort;

wären zwei Varianten einer Methode Init zum Setzen dieser Eigenschaften sinnvoll: 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 }

/* Methode zum Schreiben des Vor- und Nachnamens */ public void Init(String vorname, String nachname) { this.Vorname = vorname; this.Nachname = nachname; } /* Zweite Variante der Init-Methode */ public void Init(String vorname, String nachname, String ort) { this.Vorname = vorname; this.Nachname = nachname; this.Ort = ort; }

Wenn Sie nun Instanzen dieser Klasse besitzen, können Sie die eine oder andere dieser Methoden aufrufen: /* Instanzen der Person-Klasse erzeugen */ Person person1 = new Person(); Person person2 = new Person(); /* Instanzen initialisieren */ person1.Init("Zaphod", "Beeblebrox"); person2.Init("Fred-Bogus", "Trumper", "New York");

    

387

Sandini Bib

Der Compiler entscheidet an Hand der Anzahl und des Datentyps der übergebenen Argumente, welche Variante er aufrufen muss. Im Beispiel sucht er beim ersten Aufruf nach einer Variante mit Argumenten, die dem folgenden Muster entsprechen: String, String. Beim zweiten Aufruf wird das Muster String, String, String mit den vorhandenen Methoden verglichen. Daraus ergibt sich, dass überladene Methoden sich entweder in der Anzahl oder den Datentypen der einzelnen Argumente unterscheiden müssen. Der Rückgabewert von Methoden wird nicht vom Compiler zur Unterscheidung verwendet (weil der Compiler nicht erkennen kann, welcher Rückgabetyp in einem Ausdruck, der eine Methode aufruft, erwartet wird). Überladene Methoden in Delphi und Kylix

In Delphi und Kylix funktioniert das Ganze prinzipiell auf dieselbe Weise. Sie müssen überladene Methoden hier allerdings mit dem Schlüsselwort overload kennzeichnen, das Sie an die Methodendeklaration anhängen: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

388

unit CPerson; interface type Person = class public Vorname: string; public Nachname: string; public Ort: string; { Methode zum Schreiben des Vor- und Nachnamens } public procedure Init(vorname: string; nachname: string); overload; { Zweite Variante der Init-Methode } public procedure Init(vorname: string; nachname: string; ort: string); overload; end; implementation { Methode zum Schreiben des Vor- und Nachnamens } procedure Person.Init(vorname: string; nachname: string); begin self.Vorname := vorname; self.Nachname := nachname; end;

      

Sandini Bib

28 { Zweite Variante der Init-Methode } 29 procedure Person.Init(vorname: string; nachname: string; 30 ort: string); 31 begin 32 self.Vorname := vorname; 33 self.Nachname := nachname; 34 self.Ort := ort; 35 end; 36 37 end.

Im nächsten Abschnitt beschreibe ich Konstruktoren, über die Sie Instanzen direkt bei der Erzeugung initialisieren können. Diese machen die hier entwickelte Init-Methode scheinbar überflüssig. Bedenken Sie aber, dass diese Methode lediglich ein Beispiel für das Überladen von Methoden sein soll. Außerdem ist diese Methode u. U. auch nützlich, wenn Konstruktoren vorhanden sind, da sie auch nach der Erzeugung einer Instanz aufgerufen werden kann.

6.9

Initialisieren von Klassen: Konstruktoren

Objekte müssen recht häufig bei der Erzeugung initialisiert werden. Bisher sind wir dabei so vorgegangen, dass wir nach der Erzeugung die entsprechenden Eigenschaften beschrieben haben: Person person1 = new Person(); person1.Vorname = "Zahphod"; person1.Nachname = "Beeblebrox";

Das ist allerdings etwas umständlich. Es wäre wesentlich besser, wenn das Objekt direkt bei der Erzeugung initialisiert werden könnte: Person person1 = new Person("Zaphod", "Beeblebrox");

Und genau dafür setzen Sie Konstruktoren ein. Ein Konstruktor wird immer dann automatisch aufgerufen, wenn ein Objekt erzeugt wird. Sie können eigene Konstruktoren in eine Klasse integrieren, um das Objekt bei der Erzeugung zu initialisieren. Konstruktoren sind Methoden sehr ähnlich, werden aber etwas anders deklariert. Grundsätzlich geben Konstruktoren keinen Rückgabewert zurück, dürfen also nicht als Funktion deklariert werden.

      

389

Sandini Bib

6.9.1 Konstruktoren in Java In Java muss ein Konstruktor den Namen der Klasse tragen und darf nicht mit einem Rückgabetyp gekennzeichnet werden. Ansonsten gleicht die Deklaration der einer Methode. Sie können so viele Argumente übergeben, wie Sie benötigen: 01 public class Person 02 { 03 /* Eigenschaften */ 04 String Vorname; 05 String Nachname; 06 07 /* Konstruktor */ 08 public Person(String vorname, String nachname) 09 { 10 this.Vorname = vorname; 11 this.Nachname = nachname; 12 } 13 14 }

Innerhalb des Konstruktors können Sie alles mögliche programmieren. In den meisten Konstruktoren werden Eigenschaften mit den Werten übergebener Argumente initialisiert, so wie es im Beispiel auch der Fall ist. In seltenen Fällen werden Konstruktoren aber auch verwendet, um Ressourcen zu öffnen, mit denen das Objekt arbeiten muss. Ein Objekt zum Schreiben einer Textdatei sollte z. B. im Konstruktor die zu beschreibende Datei öffnen oder erzeugen, damit die Methode, die zum Schreiben verwendet wird, eine geöffnete Datei benutzen kann. Solch ein Konstruktor würde dann den Dateinamen übergeben bekommen. Wenn Sie nun eine Instanz der Klasse erzeugen, sind Sie gezwungen, die Argumente entsprechend zu belegen: Person person1 = new Person("Zaphod", "Beeblebrox");

Der Compiler lässt eine Erzeugung ohne Argumente nicht mehr zu: Person person1 = new Person(); // Fehler

Er meldet den Fehler „Cannot resolve symbol“, weil er keinen Konstruktor findet, der keine Argumente besitzt. Der Standardkonstruktor

390

Sie werden sich nun wahrscheinlich fragen, warum es denn vorher immer möglich war, Instanzen ohne Argumente zu erzeugen. Die Antwort darauf ist, dass der Compiler einer Klasse automatisch einen Konstruktor hinzufügt, wenn diese keinen eigenen besitzt. Dieser Konstruktor

      

Sandini Bib

macht nichts weiter und besitzt deshalb auch keine Argumente, kann aber eben bei der Erzeugung einer Instanz der Klasse aufgerufen werden. Dieser Konstruktor wird auch als Standardkonstruktor bezeichnet. Der Java-Compiler fügt einen Standardkonstruktor nur dann hinzu, wenn die Klasse keine eigenen Konstruktoren implementiert. Wenn Sie ermöglichen wollen, dass eine Instanz der Klasse auch ohne Argumente erzeugt werden kann, müssen Sie einen eigenen Standardkonstruktor implementieren: 01 public class Person 02 { 03 /* Eigenschaften */ 04 String Vorname; 05 String Nachname; 06 07 /* Standardkonstruktor, der nichts weiter macht, aber ermöglicht, 08 dass Instanzen der Klasse auch ohne Argumente erzeugt werden 09 können */ 10 public Person() 11 { 12 } 13 14 /* Spezieller Konstruktor */ 15 public Person(String vorname, String nachname) 16 { 17 this.Vorname = vorname; 18 this.Nachname = nachname; 19 } 20 21 }

Sie können nun so viele Konstruktoren in die Klasse integrieren, wie Sie benötigen. Wie bei überschriebenen Methoden müssen sich diese lediglich in den Argumenten unterscheiden. Wenn die Klasse z. B. weitere Eigenschaften zur Speicherung der Adresse besitzt, können Sie in einem weiteren Konstruktor zusätzlich zum Namen auch den Ort und die Straße übergeben. Dabei müssen Sie lediglich überlegen, welche Varianten sinnvoll sind. Da Sie über das Vorhandensein des Standardkonstruktors auch entscheiden können, ob eine Instanz ohne Argumente erzeugt werden kann, können Sie auch erzwingen, dass eine erzeugte Instanz immer initialisiert werden muss. Lassen Sie den Standardkonstruktor dazu einfach weg. Eine Klasse, die zum Schreiben von Textdateien verwendet werden soll und die im Konstruktor diese Datei öffnen muss, muss beispielsweise mit einem Dateinamen initialisiert werden. Ein Standard-

      

Initialisierung erzwingen

391

Sandini Bib

konstruktor wäre hier nicht angebracht, weil in diesem der Dateiname nicht bekannt wäre.

6.9.2 Konstruktoren in Delphi und Kylix Konstruktoren werden in Delphi und Kylix über das Schlüsselwort constructor nach dem folgenden Schema deklariert: constructor Name([Argumentliste]);

Als Name können Sie prinzipiell jeden verwenden, Sie sollten sich aber an das Delphi/Kylix-Schema halten und den Konstruktor Create nennen. Konstruktoren können wie bei Java überladen werden. Da Delphi/Kylix-Klassen den Konstruktor zumindest von der impliziten Basisklasse TObject erben, sollten Sie sich angewöhnen, als erste Anweisung in einem Konstruktor den geerbten Konstruktor aufzurufen. Delphi und Kylix stellen dazu das Schlüsselwort inherited („vererbt“) zur Verfügung. Da Sie über dieses Schlüsselwort auch geerbte Methoden aufrufen können, müssen Sie den Namen des Konstruktors angeben: inherited Create;

Sie stellen damit sicher, dass der Konstruktor der Basisklasse auf jeden Fall und in der richtigen Reihenfolge aufgerufen wird. Wird ein Objekt Ihrer Klasse erzeugt, wird erst der Konstruktor von TObject aufgerufen und danach erst Ihrer. Für TObject ist das zwar scheinbar nicht unbedingt notwendig (es funktioniert auch ohne einen Aufruf des geerbten Konstruktors). Spätestens aber, wenn Sie Ihre Klassen von speziellen anderen Klassen ableiten, wird der Aufruf des geerbten Konstruktors sehr wichtig. In Java ist dieser Aufruf übrigens nur in Sonderfällen notwendig. Der Java-Compiler integriert einen Aufruf des geerbten Konstruktors automatisch in die Konstruktoren neuer Klassen. Die Java-Klasse zur Speicherung von Personendaten sieht in Delphi und Kylix dann so aus:

392

      

Sandini Bib

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

unit CPerson; interface type Person = class public Vorname: string; public Nachname: string; public Ort: string; { Konstruktor } constructor Create(vorname: string; nachname: string); overload; { Zweite Variante des Konstruktors } constructor Create(vorname: string; nachname: string; ort: string); overload; end; implementation { Konstruktor } constructor Person.Create(vorname: string; nachname: string); begin { Aufruf des geerbten Konstruktors } inherited Create; { Initialisieren der Eigenschaften } self.Vorname := vorname; self.Nachname := nachname; end; { Zweite Variante des Konstruktors } constructor Person.Create(vorname: string; nachname: string; ort: string); begin { Aufruf des geerbten Konstruktors } inherited Create; { Initialisieren der Eigenschaften } self.Vorname := vorname; self.Nachname := nachname; self.Ort := ort; end; end.

      

393

Sandini Bib

Beim Erzeugen von Instanzen können Sie nun die entsprechenden Argumente übergeben: person1 := Person.Create('Zaphod', 'Beeblebrox'); person2 := Person.Create('Fred-Bogus', 'Trumper', 'New York');

Anders als viele andere Compiler integriert der Delphi/Kylix-Compiler den Standardkonstruktor auch in Klassen, die eigene Konstruktoren implementieren. Sie können also auch eine Instanz der Person-Klasse ohne Argumente erzeugen, ohne dass Sie einen eigenen Standardkonstruktor in die Klasse integrieren müssen.

6.10 Aufräumarbeiten: Destruktoren Destruktoren werden für Aufräumarbeiten verwendet, die beim Zerstören eines Objekts automatisch ausgeführt werden müssen. Ein Destruktor wird immer dann automatisch aufgerufen, wenn ein Objekt zerstört wird. Destruktoren benötigen Sie eigentlich seltener. Nur dann, wenn ein Objekt in einem Konstruktor externe Ressourcen öffnet, z. B. eine Datei, werden diese üblicherweise im Destruktor wieder geschlossen. Verwenden Sie Destruktoren möglichst sparsam und bedächtig. Es ist wesentlich sinnvoller, einer Klasse, die mit einer externen Ressource (Datei, Datenbank etc.) arbeiten soll, Methoden zum Öffnen und Schließen dieser Ressource hinzuzufügen, als die Ressource im Konstruktor zu öffnen und im Destruktor zu schließen. Über separate Methoden kann der Programmierer, der die Klasse einsetzt, entscheiden, wann die Ressource geöffnet und wieder geschlossen wird. Ein entsprechend erstellter Konstruktor und Destruktor lässt dem Programmierer aber keine Wahl. Die vielen Klassen aus verschiedenen Bibliotheken, die genau diesen Weg gehen, den ich hier vorschlage, beweisen, dass es der richtige Weg ist. Ein großes Problem mit Destruktoren in Java ist, dass Sie nie wissen, wann der Garbage Collector endlich Zeit hat, die verwaisten Objekte zu zerstören. Erst dann wird nämlich der Destruktor aufgerufen. In JavaProgrammen kann es deswegen vorkommen, dass eine externe Ressource, die erst im Destruktor geschlossen wird, wesentlich länger geöffnet bleibt, als Sie dies erwarten. Das kann dann auch zu Problemen führen, wenn nämlich ein anderer Programmteil genau diese Ressource benötigt. Ich beschreibe Destruktoren deshalb hier nur an einem abstrakten Beispiel.

394

      

Sandini Bib

6.10.1 Destruktoren in Delphi und Kylix In Delphi wird ein Destruktor mit dem Schlüsselwort destructor ähnlich einem Konstruktor deklariert. Als Name müssen Sie Destroy angeben, obwohl Sie ja zum Zerstören eines Objekts die Free-Methode aufrufen und nicht Destroy. Die Erklärung dafür ist, dass Free noch ein wenig mehr macht, als nur das Objekt zu zerstören. Free überprüft nämlich zuvor, ob das Objekt überhaupt noch existiert, und ruft nur dann Destroy auf. Im Fehlerfall erzeugt Free eine Ausnahme. Damit stellen Delphi und Kylix sicher, dass beim mehrfachen Zerstören keine kritischen Speicherfehler entstehen können. Da ein Destruktor automatisch aufgerufen wird, darf er keine Argumente besitzen (wer sollte diese auch übergeben!?). Ähnlich dem Konstruktor sollten Sie im Destruktor den von TObject geerbten Destruktor aufrufen, damit dieser seine eventuellen Aufräumarbeiten ausführen kann. Dieser Aufruf sollte immer am Ende Ihres Destruktors erfolgen, damit die korrekte Aufrufreihenfolge beim Zerstören gewährleistet ist. Eine Demo-Klasse mit einem einfachen Destruktor sieht dann so aus: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19

unit CDemo; interface type Demo = class { Destruktor } destructor Destroy; override; end; implementation { Destruktor } destructor Demo.Destroy; begin writeln('Der Destruktor wurde aufgerufen'); { Aufruf des geerbten Destruktors } inherited destroy; end; end.

    

395

Sandini Bib

Der Destruktor muss mit dem Schlüsselwort override gekennzeichnet werden. Damit stellen Sie sicher, dass beim Aufruf von Free auch wirklich Ihr und nicht der von TObject geerbte Destruktor aufgerufen wird. Ohne dieses Schlüsselwort kompiliert Delphi bzw. Kylix das Programm zwar auch, beim Aufruf von Free wird Ihr Destruktor aber gar nicht aufgerufen. Probieren Sie das einfach einmal selbst mit einem Destruktor mit und ohne override aus.

6.10.2 Destruktoren in Java In Java wird ein Destruktor folgendermaßen deklariert: protected void finalize() { }

Das protected-Schlüsselwort besitzt eine besondere Bedeutung, das bei der Vererbung wichtig wird. Der Destruktor sollte auf jeden Fall mit diesem Schlüsselwort deklariert werden. Eine public-Deklaration dürfen Sie nicht vornehmen, weil der Destruktor nicht von außen aufgerufen werden darf. Eine private-Deklaration würde bei abgeleiteten Klassen verhindern, dass diese in ihren Destruktoren den geerbten Destruktor aufrufen. Ansonsten müssen Sie nichts weiter beachten. Der Garbage Collector ruft den Destruktor automatisch auf, wenn er ein Objekt zerstört. Der Compiler integriert den Aufruf des von Object geerbten Destruktors automatisch in neue Destruktoren, so dass Sie diese in Java nicht explizit aufrufen müssen.

6.11 Datenkapselung Die Datenkapselung, die auch kurz als Kapselung bezeichnet wird, ist ein wichtiges Grundkonzept der OOP. Kapselung bedeutet, dass Objekte den Zugriff auf ihre Daten selbst kontrollieren. Besitzt ein Objekt nur einfache Eigenschaften, kann es den Zugriff auf seine Daten nicht kontrollieren. Ein Programmierer, der dieses Objekt benutzt, kann in die Eigenschaften hineinschreiben, was immer zu dem Datentyp der Eigenschaften passt. Das kann dazu führen, dass das Objekt ungültige Daten speichert und damit u. U. beim Aufruf von Methoden fehlerhaft reagiert. Das Objekt zum Drucken von Daten, das wir ab Seite 370 entworfen haben, ist ein gutes Beispiel dafür. Stellt der Programmierer die Eigenschaften, die die Ränder bestimmen, z. B. auf negative Werte ein, wird der Ausdruck wahrscheinlich etwas konfus aussehen. Kontrolliert das Ob-

396

      

Sandini Bib

jekt jedoch den Zugriff auf diese Eigenschaften, so ist ein fehlerhaftes Einstellen (wie z. B. auf negative Werte) erst gar nicht möglich. Das Objekt kann dann unter allen Umständen sicher arbeiten. Kapselung können Sie auf zwei Arten realisieren. Die ältere Methode ist, die zu kapselnden Daten des Objekts in privaten Eigenschaften zu speichern und für den Zugriff auf diese Daten jeweils eine Methode zum Schreiben und eine zum Lesen des Werts zur Verfügung zu stellen. Diese Methoden können den Zugriff auf die Daten sehr gut kontrollieren. Die andere, modernere Variante stellen nicht alle Programmiersprachen zur Verfügung: In dieser Variante können Sie Eigenschaften so deklarieren, dass diese die Methoden zum Lesen und Schreiben bereits integrieren. Solche Eigenschaften sehen nach außen aus wie normale Eigenschaften, rufen aber beim Schreiben und beim Lesen eine spezielle Methode auf.

Klassische und moderne Kapselung

6.11.1 Die klassische Kapselung in Java In Java können Sie Daten lediglich auf die klassische Art kapseln. Verwalten Sie die Daten dazu in privaten Variablen und stellen Sie eine Lese- und eine Schreibmethode zur Verfügung. Die Schreibmethode kann entscheiden, welche Daten in das Objekt geschrieben werden, die Lesemethode entscheidet, wie die Daten zurückgegeben werden. Das folgende Beispiel demonstriert die klassische Kapselung an einer KreisKlasse, die den Zugriff auf den Radius kontrolliert: 01 public class Kreis 02 { 03 /* Private Eigenschaft für den Radius */ 04 private double radius; 05 06 /* Methode zum Setzen des Radius */ 07 public void setRadius(double wert) 08 { 09 /* Überprüfen des Werts */ 10 if (wert > 0) 11 this.radius = wert; 12 } 13 14 /* Methode zum Lesen des Radius */ 15 public double getRadius() 16 { 17 return this.radius; 18 } 19

'DWHQNDSVHOXQJ

397

Sandini Bib

20 21 22 23 24 25 26 }

/* Methode zum Lesen des Umfangs */ public double getUmfang() { return this.radius * 2 * 3.1415927; }

Der Radius ist nun so gekapselt, dass nur das Schreiben eines Wertes größer Null möglich ist. Das Lesen ist ohne Einschränkungen möglich. Diese Klasse implementiert noch eine weitere Methode zum gekapselten Zugriff. Die Methode getUmfang ermittelt den Umfang und gibt diesen zurück. Im Prinzip handelt es sich dabei auch um gekapselte Daten. Der Umfang ist sogar so gekapselt, dass er nur gelesen werden kann. Das Datum Umfang wird also noch wesentlich besser geschützt als der Radius. Dass der Umfang in Wirklichkeit aus dem Radius berechnet wird, muss das Programm, das diese Klasse anwendet, nicht wissen. Ich habe die Namen der Methoden mit set und get begonnen, weil das in Java so üblich ist. Bei der Verwendung werden nun die Methoden zum Setzen und zum Lesen aufgerufen: /* Instanz der Klasse Kreis erzeugen */ Kreis kreis1 = new Kreis(); /* Radius setzen */ kreis1.setRadius(100); /* Radius und Umfang ausgeben */ System.out.println("Ein Kreis mit einem Radius von " + kreis1.getRadius() + " besitzt einen Umfang von " + kreis1.getUmfang());

6.11.2 Bessere Kapselung mit Ausnahmen Etwas ungeschickt an dem vorherigen Beispiel ist, dass das aufrufende Programm nicht mitbekommt, dass die Schreibmethode fehlgeschlagen ist, wenn ein ungültiger Wert übergeben wird. Ein Anwender erkennt höchstens, dass die Berechnungen falsch sind. Das Programm arbeitet mit einem logischen Fehler weiter, wenn ein ungültiger Radius übergeben wird.

398

      

Sandini Bib

Eine Methode, die den Zugriff auf die Daten kontrolliert, muss dem Programmierer bzw. Anwender das Fehlschlagen der Methode also so mitteilen, dass dieser gezwungen ist das Fehlschlagen zu quittieren. Dafür verwenden Sie eine eigene Ausnahme, die Sie in der Methode zum Schreiben erzeugen. Ich will hier nicht zu tief auf das Thema Ausnahmen eingehen. Sie können eigene Ausnahmeklassen erzeugen, die Sie für Ihre Ausnahmen verwenden können. Ich setze aber einfach die Basisklasse aller Ausnahmen, die Klasse Exception ein. Für den Anfang reicht das vollkommen aus. Ausnahmen in Java Eine Ausnahme erzeugen Sie in Java über die throw-Anweisung. Dieser müssen Sie dabei ein neues Ausnahme-Objekt übergeben. Dazu können Sie ganz einfach die Exception-Klasse verwenden, der Sie im Konstruktor den Fehlertext übergeben können. Der Java-Compiler verlangt, dass Methoden, die Ausnahmen erzeugen, mit dem throws-Schlüsselwort unter Angabe der erzeugten Ausnahmen deklariert werden. Die Methode zum Schreiben des Radius sieht dann optimiert so aus: 07 08 09 10 11 12 13 14 15 16

public void setRadius(double wert) throws Exception { /* Überprüfen des Werts */ if (wert > 0) this.radius = wert; else /* Ausnahme erzeugen */ throw new Exception("Der Radius muss ein Wert " + "größer Null sein");

Im Programm muss diese Ausnahme nun abgefangen werden: /* Radius setzen */ try { kreis1.setRadius(-100); } catch (Exception e) { System.out.println("Radius konnte nicht gesetzt werden: " + e.getMessage()); }

'DWHQNDSVHOXQJ

399

Sandini Bib

Ausnahmen machen Programme fehlerfreier

Über die Methode getMessage erhalten Sie Zugriff auf die Fehlermeldung. Der Anwender wird nun auf jeden Fall darüber informiert, dass beim Setzen des Radius etwas nicht funktioniert hat. Der Programmierer kann nun gegebenenfalls nach einem logischen Fehler im Programm suchen, der den Radius zu klein eingestellt hat, und diesen beseitigen. Sie haben mit Ihrer Klasse dafür gesorgt, dass die Anwendung möglichst einfach und fehlerfrei ist. Ausnahmen in Delphi und Kylix In Delphi und Kylix erzeugen Sie eine Ausnahme ähnlich wie in Java, lediglich mit Hilfe der raise-Anweisung und mit den Object PascalTechniken zur Erzeugung von Objekten. Delphi und Kylix stellen wie Java eine Klasse Exception zur Verfügung, die Sie verwenden können: Das folgende Beispiel demonstriert, wie in Pascal eine Exception ausgelöst wird. Eine der Java Kreisklasse ähnliche Klasse sieht dann in Delphi bzw. Kylix so aus: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

400

unit CKreis; interface uses SysUtils; type Kreis = class { Private Eigenschaft für den Radius } private radius: double; { Methode zum Setzen des Radius } public procedure setRadius(wert: double); { Methode zum Lesen des Radius } public function getRadius(): double; { Methode zum Lesen des Umfangs } public function getUmfang(): double; end; implementation procedure Kreis.setRadius(wert: double); begin { Überprüfen des Werts } if wert > 0 then self.radius := wert

      

Sandini Bib

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

else { Ausnahme erzeugen } raise Exception.Create('Der Radius muss ein Wert ' + 'größer Null sein'); end; function Kreis.getRadius(): double; begin result := self.radius; end; function Kreis.getUmfang(): double; begin result := self.radius * 2 * 3.1415927; end; end.

Ein Programm, das diese Klasse verwendet, sollte die Ausnahme nun abfangen: 01 02 03 04 05 06 07 08 09 10 11 12 13

{ Instanz der Klasse Kreis erzeugen } kreis1 := Kreis.Create(); { Radius setzen } try kreis1.setRadius(-100); except on e: Exception do begin writeln('Radius konnte nicht gesetzt werden: ' + e.Message); end; end;

Anders als Java zwingen Delphi und Kylix ein Programm aber nicht dazu, Ausnahmen abzufangen.

6.11.3 Moderne Kapselung Die Kapselung der Daten eines Objekts über Zugriffsmethoden ist in der Praxis bei der Verwendung des Objekts oft etwas mühselig. In Delphi und Kylix können Sie stattdessen auch die moderne Variante verwenden, bei der Eigenschaften nach außen ganz normal aussehen, aber intern Methoden ausrufen.

'DWHQNDSVHOXQJ

401

Sandini Bib

Dazu benötigen Sie zunächst einmal, genau wie bei der klassischen Kapselung, eine private Eigenschaft, die den Wert speichert, und je eine Schreib- und Lesemethode. Diese Zugriffsmethoden werden allerdings nun privat deklariert. Der Public-Block wird um eine spezielle Deklaration erweitert: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

402

unit CKreis; interface uses SysUtils; type Kreis = class { Private Eigenschaft für den Radius } private radiusWert: double; { Methode zum Setzen des Radius } private procedure setRadius(wert: double); { Methode zum Lesen des Radius } private function getRadius(): double; { Methode zum Lesen des Umfangs } public function getUmfang(): double; { Deklaration der speziellen Eigenschaften } public property Radius: double read getRadius write setRadius; public property Umfang: double read getUmfang; end; implementation procedure Kreis.setRadius(wert: double); begin { Überprüfen des Werts } if wert > 0 then self.radius := wert else { Ausnahme erzeugen } raise Exception.Create('Der Radius muss ein Wert ' + 'größer Null sein'); end;

      

Sandini Bib

41 42 43 44 45 46 47 48 49 50 51

function Kreis.getRadius(): double; begin result := self.radiusWert; end; function Kreis.getUmfang(): double; begin result := self.radiusWert * 2 * 3.1415927; end; end.

Die Deklaration in Zeile 21 und 22 erzeugt eine spezielle Eigenschaft. Die Lesemethode wird über die read-Anweisung zugewiesen, die Schreibmethode über die write-Anweisung. Die Implementierung der Zugriffsmethoden hat sich im Vergleich zum Beispiel bei der einfachen Kapselung nicht wesentlich geändert. Lediglich der Name der privaten Eigenschaft wurde geändert, da die spezielle Eigenschaft bereits Radius heißt. Das Beispiel implementiert auch gleich in Zeile 24 und 25 die Eigenschaft Umfang, der lediglich eine Lesemethode zugewiesen wird. Diese Eigenschaft kann deshalb nur gelesen werden. Das Schreiben verhindert der Compiler. Bei der Verwendung dieser Klasse sehen diese speziellen Eigenschaften nun aus wie normale: { Instanz der Klasse Kreis erzeugen } kreis1 := Kreis.Create(); { Radius setzen } try kreis1.Radius := -100; except on e: Exception do begin writeln('Radius konnte nicht gesetzt werden: ' + e.Message); end; end; { Radius und Umfang ausgeben } writeln('Ein Kreis mit einem Radius von ', kreis1.Radius, ' besitzt einen Umfang von ', kreis1.Umfang);

Ansonsten verhält sich das Programm aber genauso wie bei der klassischen Kapselung.

'DWHQNDSVHOXQJ

403

Sandini Bib

6.12 Vererbungs-Grundlagen Die Vererbung ist ein wichtiges Grundkonzept der OOP, das zwar in eigenen Programmen relativ selten eingesetzt wird. Bei der Verwendung von Bibliotheken werden Sie aber recht häufig damit konfrontiert. Wenn Sie in Java z. B. ein Formular erzeugen, leiten Sie Ihre Formularklasse immer von einer Basis-Formularklasse ab. Bei dieser Art Vererbung erkennen Sie den enormen Vorteil: Ihre neuen Klassen erben die komplette Funktionalität der Basisklasse und müssen diese lediglich um neue Funktionalität erweitern. Für eine umfassende Beschreibung der Vererbung bleibt mir hier leider kein Platz. Deshalb beschreibe ich an einem einfachen JavaBeispiel lediglich die Grundlagen. Die Grundlagen der Vererbung

Die Grundlagen der Vererbung sind einfach: Sie können eine Klasse von einer anderen, der so genannten Basisklasse ableiten. Als (Java-)Beispiel verwende ich eine Person-Klasse: 01 public class Person 02 { 03 /* Eigenschaften */ 04 public String Vorname; 05 public String Nachname; 06 07 /* Konstruktor */ 08 public Person(String vorname, String nachname) 09 { 10 this.Vorname = vorname; 11 this.Nachname = nachname; 12 } 13 14 /* Methode zum Ermitteln des vollen Namens */ 15 public String VollerName() 16 { 17 return this.Vorname + " " + this.Nachname; 18 } 19 }

Von dieser Klasse leite ich eine TitelPerson-Klasse ab, deren Instanzen die Daten von Personen speichern sollen, die einen Titel besitzen: 01 public class TitelPerson extends Person 02 {

404

      

Sandini Bib

Eine abgeleitete Klasse erbt alle Elemente der Basisklasse. Ein TitelPersonObjekt besitzt also auf jeden Fall die Eigenschaften Vorname und Nachname und die Methode VollerName. Sie können die neue Klasse nun z. B. um eine Eigenschaft erweitern: 03 04

/* Eigenschaft für den Titel */ public String Titel;

Methoden fügen Sie auf dieselbe Weise hinzu. Methoden werden aber häufig auch überschrieben. Dabei versehen Sie eine geerbte Methode mit einer neuen Implementierung. Bei einem TitelPerson-Objekt muss die Methode VollerName z. B. zusätzlich zum Namen auch den Titel ausgeben: 05 06 07 08 09 10

/* Die Methode zum Ermitteln des vollen Namens wird überschrieben */ public String VollerName() { return this.Titel + super.VollerName(); }

Das Beispiel nutzt bereits eine wichtige Technik: Die neue Methode ruft über das super-Schlüsselwort die geerbte Methode auf. So nutzt diese neue Methode den bereits vorhandenen Programmcode und erweitert gleichzeitig die Funktionalität. Der Name des super-Schlüsselworts kommt übrigens daher, dass eine Basisklasse auch als Superklasse bezeichnet wird. Das Hinzufügen der Titel-Eigenschaft und das Überschreiben der VollerName-Methode würde prinzipiell ausreichen, um die TitelPerson-Klasse zu definieren. Leider beginnen hier bereits die Probleme: Die neue Klasse besitzt keinen Konstruktor. Der Compiler erzeugt, wie Sie bereits wissen, automatisch einen Standardkonstruktor. Dieser ruft implizit den geerbten Standardkonstruktor auf. Die Basisklasse besitzt aber keinen solchen, weil ein spezieller Konstruktor implementiert ist. Der Compiler beschwert sich, dass er den Standardkonstruktor der Basisklasse nicht aufrufen kann. Sie müssen der neuen Klasse einen eigenen Konstruktor hinzufügen, der den speziellen Konstruktor der Basisklasse aufruft, wozu Sie wieder das super-Schlüsselwort verwenden: 11 12 13 14 15 16 17

Erste Probleme mit dem Konstruktor

/* Konstruktor */ public TitelPerson(String vorname, String nachname, String titel) { /* Aufruf des geerbten Konstruktors */ super(vorname, nachname); /* Eigene Initialisierungen */

9HUHUEXQJV*UXQGODJHQ

405

Sandini Bib

18 this.Titel = titel; 19 } 20 } TitelPersonObjekte verhalten sich anders als Person-Objekte

Sie können nun Person- und TitelPerson-Objekte erzeugen und im Programm verwenden. Die Methode VollerName verhält sich bei einem TitelPerson-Objekt anders als bei einem Person-Objekt (was bereits zum komplexen Thema Polymorphismus6 gehört ...): /* Instanz der Klasse Person erzeugen */ Person p1 = new Person("Fred-Bogus","Trumper"); // gibt "Fred-Bogus Trumper" aus /* Instanz der Klasse TitelPerson erzeugen */ TitelPerson p2 = new TitelPerson("Dr.", "Jean-Claude","Vigneron"); // gibt "Dr. Jean-Claude Vigneron" aus

Denken Sie jetzt nicht, dass die Vererbung trivial ist. Sie müssen noch einiges mehr beachten, als ich hier dargestellt habe. Im Artikel „OOPGrundlagen“ finden Sie dazu alle notwendigen Informationen. Dort beschreibe ich auch, wie sie in Object Pascal Klassen von Basisklassen ableiten.

6.13 Weitere Möglichkeiten, die nicht besprochen werden Im Rahmen dieses Kapitels konnte ich nicht alle Konzepte der OOP besprechen. Komplexe Konzepte, die in der Praxis eher seltener genutzt werden, blieben außen vor. Dazu gehören die folgenden: • Vererbung in der Tiefe Die Vererbung konnte ich in diesem Kapitel nur anschneiden. Sie müssen noch einiges mehr über die dabei verwendeten Konzepte und die Fallen der Vererbung lernen. • Polymorphismus und virtuelle Methoden Dieses komplexe Konzept, das nur sehr selten eingesetzt wird, will ich hier erst gar nicht versuchen zu erläutern, weil ich allein dazu schon mehrere Seiten benötigen würde ...

6.

406

Vielgestaltigkeit

      

Sandini Bib

• Schnittstellen Schnittstellen sind ein wichtiges Thema bei der modernen OOP, aber genauso schwierig zu erläutern wie der Polymorphismus. Im Artikel „OOP-Grundlagen“, den Sie auf der Buch-CD finden, werden diese Themen sehr ausführlich besprochen.

6.14 Zusammenfassung In diesem Kapitel haben Sie zunächst erfahren, warum Sie überhaupt objektorientiert programmieren sollten. Sie kennen die wesentlichen Nachteile der strukturierten und die Vorteile der objektorientierten Programmierung. Zur Umsetzung eigener objektorientierter Programme können Sie in Java, Delphi und Kylix einfache Klassen mit Eigenschaften und Methoden programmieren. Sie können diese Klassen über Konstruktoren so ausstatten, dass bei der Erzeugung von Instanzen Initialisierungsdaten übergeben werden können. Daneben kennen Sie das Konzept der Destruktoren und wissen prinzipiell, wofür Sie diese einsetzen, auch wenn Ihnen im Moment noch keine sinnvolle Anwendung einfällt (aber Destruktoren sollten Sie ja auch möglichst vermeiden ...). Um Objekte zu erzeugen, die den Zugriff auf ihre Daten selbst kontrollieren, können Sie das Konzept der Datenkapselung umsetzen. Dabei kennen Sie das klassische Konzept, das in Java verwendet wird, und das moderne, das Sie in Delphi und Kylix verwenden können. Schließlich kennen Sie noch die Grundlagen der Vererbung und können in Java eigene Klassen von vorhandenen Klassen ableiten. Insgesamt sind Sie nun in der Lage, einfache objektorientierte Programme zu schreiben, was aber für die meisten Fälle in der Praxis ausreicht.

6.15 Fragen und Übungen 1. Was ist eine Klasse? 2. Was passiert, wenn Sie eine Objektvariable einer anderen Objekt-

variable zuweisen? 3. Welche Bedeutung besitzt die Referenz self bzw. this? 4. Was ist ein Standardkonstruktor? 5. Welche Daten sollten sinnvollerweise dem Konstruktor einer Klasse

übergeben werden? 6. Was bedeutet Kapselung?

=XVDPPHQIDVVXQJ

407

Sandini Bib

Sandini Bib

7

Daten speichern

le

n e rn

Sie lernen in diesem Kapitel: • wie Sie eine Liste von Daten in einfachen Arrays speichern, • wie Sie in Java die wesentlich flexibleren Auflistungen prinzipiell zur Verwaltung von Massendaten im Arbeitsspeicher einsetzen und • wie Sie in Java Textdateien lesen und schreiben. Dieses Kapitel beschäftigt sich mit dem sehr wichtigen Speichern von Daten im Arbeitsspeicher und in Dateien. Ich gehe aufgrund der Mächtigkeit der einzelnen Themen nur grundsätzlich darauf ein und zeige, wie Sie Daten prinzipiell speichern, ohne dabei alle Möglichkeiten der einzelnen Sprachen zu beleuchten. Sie lernen zunächst, Massendaten in einfachen Arrays („Listen von Variablen“) zu speichern. Danach zeige ich, wie Sie das wesentlich modernere und flexiblere Konzept der Auflistungen einsetzen. Um das Ganze ein wenig praxisorientiert darzustellen, lernen Sie danach, wie Sie Textdateien einlesen und schreiben. Bis auf die grundlegenden Themen beschreibe ich alle Themen nur für Java. Zwei Programmiersprachen einzubeziehen würde einfach zu viel Platz kosten.

7.1

Speichern im Arbeitsspeicher

Sehr häufig müssen Programme Massendaten im Arbeitsspeicher ablegen, um direkt mit diesen Daten arbeiten zu können. In vielen Fällen werden Daten, die in einer Datei oder Datenbank gespeichert werden, in den Speicher gelesen, um zu erreichen, dass ein Programm möglichst effizient mit den Daten arbeiten kann. Auf den folgenden Seiten zeige ich nun eine klassische und eine moderne Möglichkeit, Massendaten im Arbeitsspeicher zu verwalten.

    

409

Sandini Bib

7.1.1 Grundlagen Massendaten werden immer listenförmig gespeichert. Dazu verwenden Sie einfache Arrays oder moderne Auflistungen. Beide Varianten speichern zumindest eine Liste von einzelnen Werten. Es kommt aber auch häufig vor, dass die zu speichernden Daten komplexer sind. Wenn Sie z. B. die Daten von Personen speichern wollen, gehören dazu Angaben zum Vornamen, zum Nachnamen, zum Ort und zur Straße. Wenn ich Sie nun frage, wie Sie diese Daten grundsätzlich speichern, kennen Sie die Antwort: in Strukturen oder besser in Objekten. Das Array oder die Auflistung speichert dann einfach eine Liste von Referenzen auf einzelne Objekte. Auch wenn Sie die Daten mehrerer tausend Personen speichern müssen, ist es kein Problem, dazu Objekte zu verwenden, und bringt in der Praxis eigentlich auch nur Vorteile.

7.1.2 Arrays Arrays sind eine einfache Form der listenförmigen Speicherung. Ein Array ist im Prinzip eine Liste von einzelnen Variablen, die zusammenhängend gespeichert werden und die über einen Namen und einen Index gezielt angesprochen werden können. Einfache Arrays in Delphi und Kylix In Object Pascal deklarieren Sie ein einfaches Array wie im folgenden Beispiel: var zahlen: array[0..2] of byte;

Das Array besitzt in diesem Beispiel drei Elemente, die über den Index 0 bis 2 angesprochen werden können. Der Datentyp der einzelnen Elemente ist byte. Im Arbeitsspeicher werden die einzelnen Elemente direkt hintereinander angelegt (Abbildung 7.1). Der Index bezeichnet dann den Teil-Speicherbereich des einzelnen Elements.

00000000 00000000 00000000 0

1

2

Abbildung 7.1: Ein Array aus drei Byte-Werten

In Object Pascal können Sie den Bereich des Index angeben. Sie können das Array beispielsweise auch beim Index 1 beginnen lassen. Dass ich das Array bei Null beginne, hat den Grund, dass viele Programmiersprachen (wie C++, C# und Java) Arrays grundsätzlich und ausschließlich

410

   

Sandini Bib

bei Null beginnen. Wenn ich Arrays in Object Pascal auf dieselbe Weise verwalte, muss ich in anderen Sprachen nicht umdenken. Über den Index können Sie die einzelnen Variablen nun gezielt ansprechen: zahlen[0] := zahlen[1] := zahlen[2] := ... writeln('Die

1; 2; 4; erste Zahl ist ', zahlen[0]);

Im Arbeitsspeicher sieht das Beispiel-Array dann prinzipiell so aus wie in Abbildung 7.2.

00000001 00000010 00000100 0

1

2

Abbildung 7.2: Ein Byte-Array, das die Zahlen 1, 2 und 4 speichert

Die Arbeit mit einem Array gleicht der mit einfachen Variablen. Der Unterschied ist lediglich, dass Sie den Index mit angeben müssen. Der Vorteil gegenüber einzelnen Variablen ist, dass Sie den Index auch über eine Variable angeben und so z. B. in einer Schleife durch das Array gehen können:

Arrays können in Schleifen bearbeiten werden

for i := 0 to 2 do writeln('Zahl ', i, ' ist ', zahlen[i]);

Per Voreinstellung können Sie in Delphi und Kylix auch auf Arrayelemente zugreifen, die gar nicht existieren, wenn Sie als Index eine Variable einsetzen. So können Sie z. B. das Element mit dem Index 3 beschreiben: i := 3; zahlen[i] = 255;

Delphi und Kylix überprüfen zunächst nicht, ob der Index passt. Da Arrays im Speicher lediglich hintereinander gelegte Variablen sind, wird ein Speicherbereich überschrieben, der hinter dem Array liegt. Das ist aber mit ziemlicher Sicherheit ein Speicherbereich, der zu einer anderen Variablen gehört. Sie überschreiben dann vollkommen andere Daten und produzieren in Ihrem Programm sehr schwer wiegende Fehler.

    

411

Sandini Bib

Wenn Sie hingegen die Option RANGE CHECKING bzw. BEREICHSÜBERPRÜFUNG in den Compileroptionen einschalten (im Menü PROJECT/ OPTIONS/COMPILER), überprüft das Programm bei jedem Arrayzugriff, ob der Bereich des Arrays überschritten wird, und erzeugt in diesem Fall eine Ausnahme vom Typ ERangeError. Da Ausnahmen im Programm gemeldet werden (sofern sie nicht ignoriert werden), erhalten Sie dann eine Information darüber, dass in Ihrem Programm ein Fehler aufgetreten ist. Schalten Sie diese Option also auf jeden Fall ein. Arrays eignen sich nicht für dynamische Daten

Arrays werden nur noch für spezielle Probleme eingesetzt

Arrays eignen sich bereits recht gut zum Speichern von listenförmigen Daten. Arrays führen aber auch zu Problemen: Bei der Deklaration legen Sie in den meisten Programmiersprachen, wie auch in Object Pascal, die Anzahl der Elemente fest. In vielen Fällen wissen Sie aber bei der Deklaration nicht, wie viele Elemente tatsächlich gespeichert werden müssen. Wenn Sie z. B. Personendaten aus einer Datei in ein Array einlesen, wissen Sie nicht, wie viele Personen tatsächlich in der Datei gespeichert sind. Dieses Problem lösen die ab Seite 416 besprochenen Auflistungen. Arrays werden deshalb in modernen Programmen nur noch zur Lösung spezieller Probleme eingesetzt, bei denen eine Auflistung nicht angebracht ist. Auflistungen sind zwar flexibler, aber beim sequenziellen Durchgehen normalerweise auch immer etwas langsamer als Arrays, weswegen einfache Probleme mit Arrays häufig performanter gelöst werden können. Das folgende Beispiel demonstriert ein solches einfaches Problem. Ein Programm soll Lottozahlen ermitteln. Die einzelnen Zahlen ermittelt das Programm über die Random-Funktion per Zufall. Ab der zweiten Zahl muss jedoch überprüft werden, ob die aktuell gezogene Zahl bereits zuvor gezogen wurde. Zur Lösung dieses Problems setzt das Programm ein Array ein, in dem die gezogenen Zahlen abgelegt werden. Ab der zweiten Ziehung wird das Array durchsucht und damit überprüft, ob die aktuell gezogene Zahl bereits existiert:

412

   

Sandini Bib

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

program Arrays; {$APPTYPE CONSOLE} uses SysUtils; var zahlen: array[0..6] of integer; var zahlOK: boolean; i, j: integer; begin Randomize; for i := 0 to 6 do begin repeat zahlOK := True; Zahlen[i] := Random(49) + 1; for j := 0 to i - 2 do if zahlen[j] = zahlen[i] then zahlOK := False; until zahlOK = true; end; { Ausgabe der gezogenen Zahlen } for i := 0 to 6 do writeln(zahlen[i]); end.

Das Problem des Ziehens von Lottozahlen ist schon etwas komplexer und kann prinzipiell nur über ein Array gelöst werden. In Zeile 11 wird zunächst der Zufallsgenerator initialisiert. Das ist notwendig, um bei jeder Ausführung des Programms unterschiedliche Zahlen zu erhalten. Würden Sie den Zufallsgenerator nicht initialisieren, würden die gezogenen Zahlen bei jedem Programmstart dieselben sein. Probieren Sie es einfach aus. In Zeile 13 beginnt eine äußere Schleife, die sieben Zahlen ermittelt. Um die Zufallszahl bei Zahlen, die bereits gezogen wurden, wiederholt ermitteln zu können, beginnt in Zeile 15 eine mittlere Schleife. Diese setzt in Zeile 16 zunächst die Variable zahlOK auf true, weil angenommen wird, dass die ermittelte Zahl eindeutig ist. In Zeile 17 wird dann eine Zufallszahl zwischen 1 und 49 ermittelt. Um diese Zahl mit den bereits gezogenen Zahlen zu vergleichen, beginnt in Zeile 18 eine innere Schleife. Über den Vergleich in Zeile 19 entscheidet das Programm, ob die aktuell gezogene Zahl bereits im Array vorkommt. Falls das der Fall ist, wird in Zeile 20 die Variable zahlOK auf false gesetzt. Diese Variable

    

413

Sandini Bib

wird im Schleifenfuß in Zeile 21 abgefragt. So wird die mittlere Schleife so lange wiederholt ausgeführt, bis die gezogene Zahl eindeutig ist. Arrays in Java In Java ist ein Array keine einfache Speicherstruktur wie in Object Pascal, sondern ein Objekt, das vor der Benutzung wie alle Objekte erzeugt werden muss. Ein Array, das drei Zahlen speichert, wird z. B. so erzeugt: byte[] zahlen = new byte[3];

Der Index eines Arrays beginnt in Java immer bei 0. Der Zugriff auf ein Array sieht aber genauso aus wie in Object Pascal: /* Array beschreiben */ zahlen[0] = 1; zahlen[1] = 2; zahlen[2] = 4; /* Array in Schleife auslesen */ for (int i = 0; i < 3; i++) { System.out.println(zahlen[i]); }

Mehrdimensionale Arrays Die Arrays, die Sie bisher kennen gelernt haben, waren einfache, eindimensionale Arrays, die sich nur in einer Dimension ausbreiteten. Sie können in allen Programmiersprachen aber auch Arrays erzeugen, die aus mehr als einer Dimension bestehen. Obwohl diese Arrays heutzutage kaum noch eingesetzt werden, sollten Sie wissen, worum es sich dabei handelt. Wenn Sie z. B. Personendaten speichern wollen, könnten Sie dazu ein zweidimensionales Array verwenden. Dieses würde schematisch so aussehen wie in Abbildung 7.3.

Merril Ralf

1 2 0

NewYork Wien NewYork

Trumper Overturf Packer

0 Fred-Bogus

1

2

Abbildung 7.3: Ein zweidimensionales Array mit drei „Datensätzen“, die aus jeweils drei Feldern bestehen.

414

   

Sandini Bib

Das Array speichert nun drei „Datensätze“ mit je drei „Feldern“. Solch ein Array deklarieren Sie ganz einfach. In Object Pascal sieht die Deklaration so aus: var personen: array[0..2, 0..2] of string;

In Java wird ein zweidimensionales Array so deklariert: String personen[][] = new String[3][3];

Beim Zugriff auf die Elemente des Arrays geben Sie nun die Indizes aller Dimensionen an. In Object Pascal geben Sie beide Indizes in einer Klammer an: personen[0,0] personen[0,1] personen[0,2] personen[1,0] personen[1,1] personen[1,2] personen[2,0] personen[2,1] personen[2,2]

:= := := := := := := := :=

'Fred-Bogus'; 'Trumper'; 'New York'; 'Merril'; 'Overturf'; 'Wien'; 'Ralf'; 'Packer'; 'New York';

In Java geben Sie die Indizes in separaten Klammern an: personen[0][0] personen[0][1] personen[0][2] personen[1][0] personen[1][1] personen[1][2] personen[2][0] personen[2][1] personen[2][2]

= = = = = = = = =

"Fred-Bogus"; "Trumper"; "New York"; "Merril"; "Overturf"; "Wien"; "Ralf"; "Packer"; "New York";

Sie können natürlich auch wieder schleifengesteuert durch das Array gehen. Die folgende Object Pascal-Schleife schreibt die gespeicherten Daten an die Konsole:

    

415

Sandini Bib

for i := 0 to 2 do begin for j := 0 to 2 do begin writeln(personen[i,j], ' '); end; writeln; end;

Zwei- und mehrdimensionale Arrays werden, wie bereits gesagt, heutzutage nicht mehr allzu häufig eingesetzt. Wenn Sie strukturierte Daten speichern müssen, eignen sich dazu besser eindimensionale Arrays oder Auflistungen, die einzelne Objekte referenzieren. Da Objekte die unterschiedlichsten Eigenschaften und auch Methoden besitzen können, haben Sie wesentlich mehr Möglichkeiten als mit zweidimensionalen Arrays. Ein großes Problem zweidimensionaler Arrays ist, dass die Datentypen der einzelnen Felder nicht unterschiedlich sein können. In Objekten können Sie aber (natürlich) auch unterschiedliche Daten verwalten. Bei der Besprechung von Auflistungen komme ich darauf zurück. Lediglich im mathematischen Bereich mag es manchmal noch sinnvoll sein, mit zwei- oder mehrdimensionalen Arrays zu arbeiten. Aber diese spezielle Art der Mathematik (die mehrdimensionale) habe ich nie verstanden ...

7.1.3 Auflistungen Auflistungen (englisch „Collections“) sind prinzipiell so etwas wie Arrays, nur wesentlich mächtiger. Auflistungen speichern ebenfalls eine Liste von Daten, allerdings ist diese auf eine Dimension begrenzt. Das ist aber nicht weiter schlimm, denn Auflistungen referenzieren in den meisten Fällen Objekte. Auflistungen sind Objekte

Auflistungen sind selbst aber auch Objekte und besitzen deshalb Methoden und Eigenschaften. Eine sehr hilfreiche Methode ist z. B. die, über die Sie die Liste der gespeicherten Werte bei den meisten Auflistungen in der Laufzeit des Programms dynamisch erweitern können. Andere Methoden erlauben bei einigen Auflistungen das Suchen in den gespeicherten Daten, meist ist auch das Löschen von Elemente über eine Methode möglich. Eine Eigenschaft oder eine weitere Methode gibt bei vielen Auflistungen die Anzahl der aktuell gespeicherten Elemente zurück. In den verschiedenen Programmiersprachen gibt es eine große Anzahl verschiedener Auflistungen, die unterschiedlich spezialisiert sind. Ei-

416

   

Sandini Bib

nige Auflistungen assoziieren die gespeicherten Daten mit einem Schlüssel, über den der Zugriff auf die Daten sehr einfach und schnell ist. Andere Auflistungen sortieren ihre Liste automatisch, weitere sind spezialisiert auf die Speicherung von Zeichenketten. Ich kann natürlich hier nicht alle Auflistungsarten besprechen. Sie erfahren also nur, wie Sie eine der wichtigsten Arten benutzen, eine Auflistung, die Objekte speichert und diese mit einem Schlüssel assoziiert. Zur Demonstration speichert diese Auflistung einzelne Person-Objekte. Die Klasse dieser Objekte stelle ich hier nicht mehr vor. Ich verwende einfach die Klasse, die wir in Kapitel 6 entworfen haben. Da Sie zurzeit noch keine Dateien oder Datenbanken auslesen können, lege ich die Daten noch im Programm an. Da Sie hier nur das Prinzip lernen sollen, zeige ich lediglich, wie Sie mit einer Java-Auflistung arbeiten. Delphi und Kylix bleiben außen vor. Die Assoziation von Daten mit einem Schlüssel ist in der Programmierung eine weit verbreitete Technik. Besonders in Datenbanken wird dieses Konzept intensiv eingesetzt. Eine (ideal gestaltete) Datenbank, die Kundendaten speichert, assoziiert z. B. jeden Kunden mit einer eindeutigen Kundennummer. Über diesen Schlüssel können Daten sehr schnell gefunden und ausgelesen werden. Um die Daten eines Kunden zu bearbeiten, benötigt ein Mensch oder ein Programm lediglich die Kundennummer. Dieses Konzept dürfte Ihnen nicht fremd sein, weil Ihre Daten sicher in irgendwelchen Datenbanken gespeichert sind: Als Kunde bei verschiedenen Firmen, als Student an einer Universität (wobei die Matrikelnummer der Schlüssel ist) oder einfach nur als Staatsbürger von Deutschland (wobei die Personalausweisnummer der Schlüssel ist). In Datenbanken werden Sie noch öfter mit diesen Schlüsseln konfrontiert werden. Sie können dieses Konzept aber auch direkt in Ihren Programmen umsetzen, indem Sie Massendaten in Auflistungen speichern, die einen Schlüssel für jedes gespeicherte Objekt verwalten. Häufig werden Auflistungen z. B. verwendet, um die in einer Datenbank oder einer Datei gespeicherten Daten in den Arbeitsspeicher zu lesen und dort sehr schnell bearbeiten zu können. Die Java-Hashtable-Klasse Java stellt mit der Klasse Hashtable eine Auflistung zur Verfügung, die beliebige Objekte referenzieren und diese mit einem Schlüssel assoziieren kann.

    

417

Sandini Bib

Die Beschreibung dieser Klasse finden Sie in der „Java 2 Platform API Specification“, die Sie im Ordner api der Java-Dokumentation finden. Öffnen Sie die Datei index.html und klicken Sie im rechten Fensterbereich auf den Link JAVA.UTIL, um die Dokumentation des Pakets java.util zu öffnen. In der Liste der Klassen finden Sie auch die Klasse Hashtable. Um Daten in einem Hashtable-Objekt speichern zu können, müssen Sie zunächst (natürlich) eine Instanz erzeugen: Hashtable ht = new Hashtable();

Die Hashtable-Klasse wird im Paket java.util verwaltet. Dieses Paket sollten Sie also oben in der Java-Datei importieren: import java.util.*;

Nun können Sie beliebige Objekte an die Liste anfügen. Dazu verwenden Sie die put-Methode: ht.put(Schlüssel, Objekt);

Der Schlüssel ist üblicherweise ein String, kann aber unter Java eigentlich jedes beliebige Objekt sein. Als zu speicherndes Objekt können Sie ebenfalls jedes beliebige Objekt anfügen. Hashcode

Der Name der Hashtable-Klasse kommt daher, dass aus dem Schlüssel intern ein so genannter Hashcode errechnet wird. Ein Hashcode ist normalerweise ein Integer-Wert, der nach einem komplexen Algorithmus berechnet wird und der ein Datum in einer kompakteren Form darstellt. Der Java-Hashcode der Zeichenkette "Die Antwort auf die Frage aller Fragen ist 42" ist z. B. -200667539. Sie können das selbst ausprobieren. Strings sind in Java Objekte, die wie alle Java-Objekte von der Basisklasse Object abgeleitet sind und von dieser Klasse die Methode hashCode erben. Diese Methode errechnet den Hashcode und gibt diesen zurück. Wenn Sie eine String-Variable erzeugen und initialisieren, können Sie den Hashcode ermitteln: /String s = "Die Antwort auf die Frage aller Fragen ist 42"; System.out.println(s.hashCode()); // -200667539

418

   

Sandini Bib

Hashcodes sind immer eindeutig. Es kann nicht vorkommen, dass zwei unterschiedliche Zeichenketten denselben Hashcode liefern. Ein Hashtable-Objekt speichert nun für jedes Objekt, auf das in der Liste verwiesen wird, nicht den Schlüssel, sondern den Hashcode. Damit wird einfach weniger Speicherplatz benötigt, als wenn der Schlüssel selbst abgelegt wird. Wenn Sie später beim Zugriff auf die einzelnen Objekte den Schlüssel angeben, wird dieser wieder automatisch in seinen Hashcode umgerechnet und mit den gespeicherten Hashcodes verglichen. Weil Hashtable-Objekte den Hashcode des Schlüssels speichern, können Sie neben einfachen Strings auch jedes beliebige Objekt als Schlüssel verwenden. Da alle Objekte von der Basisklasse Object abgeleitet sind, besitzen diese auch eine Methode hashCode. Das Ganze ist aber eher verwirrend als nützlich. In den meisten Fällen werden einfache Zeichenketten oder numerische Werte als Schlüssel verwendet (in Java können Sie allerdings keine einfachen Datentypen als Schlüssel einsetzen, weil diese keine Objekte sind). Die Hashtable-Klasse arbeitet ausschließlich mit Objekten. Einfache Datentypen können Sie nicht in Hashtable-Instanzen speichern (was aber mit anderen Auflistungen durchaus möglich ist). Sie können noch nicht einmal einen einfachen Datentyp als Schlüssel verwenden. Wenn Sie das trotzdem versuchen, erhalten Sie beim Kompilieren den Fehler „Cannot resolve symbol“. Setzen Sie als Schlüssel aber einen String ein, funktioniert das Hinzufügen. Ein String ist in Java nämlich in Wirklichkeit ein Objekt. Sie können nun z. B. Person-Objekte separat erzeugen und hinzufügen: Person p = new Person("Fred-Bogus", "Trumper", "New York"); ht.put("1000", p);

Das Beispiel geht davon aus, dass Sie eine Person-Klasse mit einem Konstruktor zur Verfügung haben, der drei Argumente vom Typ String besitzt. Die put-Methode fügt die Referenz auf das Objekt an die interne Liste an und assoziiert diese mit dem angegebenen Schlüssel. Ein solches Hinzufügen ist sinnvoll, wenn Sie bereits Objekte besitzen, die Sie hinzufügen wollen. Dann müssen Sie das Objekt natürlich nicht – wie im Beispiel – zuvor erzeugen. In den meisten Programmen werden Objekte, die einer Auflistung hinzugefügt werden sollen, aber direkt beim Hinzufügen erzeugt. Die Verwaltung der Objekte ausschließlich in der Auflistung ist

    

419

Sandini Bib

normalerweise das Ziel solcher Programme. Dann können Sie das Objekt wesentlich direkter erzeugen und hinzufügen: ht.put("1000", new Person("Fred-Bogus", "Trumper", "New York")); ht.put("1001", new Person("Merril", "Overturf", "Wien")); ht.put("1002", new Person("Ralf", "Packer", "New York")); Schlüssel sind eindeutig

Das Beispiel erzeugt drei Person-Objekte und fügt diese direkt der Auflistung hinzu. Die Objekte werden dabei mit den Schlüsseln "1000", "1001" und "1002" assoziiert, was für das Beispiel so etwas wie eine Kundennummer darstellen soll. Beachten Sie, dass Schlüssel (natürlich) immer eindeutig sein müssen. Java-Hashtable-Objekte lassen zwar (anders als manche anderen Auflistungen) scheinbar das Hinzufügen mehrerer Objekte mit gleichen Schlüsseln zu. Beim wiederholten Hinzufügen wird aber die Referenz auf das Objekt mit dem angegebenen Schlüssel durch die neue Referenz ersetzt. Dieses Feature können Sie sehr gut nutzen, um ein Objekt in der Liste gegen ein anderes auszutauschen. Beim Hinzufügen müssen Sie aber auf den Schlüssel achten, sodass Sie nicht versehentlich ein bereits vorhandenes Objekt ersetzen.

Schneller Zugriff über den Schlüssel

Nun können Sie über den Schlüssel sehr schnell auf die gespeicherten Objekte zugreifen. Dazu verwenden Sie die get-Methode, der Sie den Schlüssel übergeben. Diese Methode gibt eine Referenz auf das gespeicherte Objekt zurück. Da die Auflistung nicht weiß, welche Objekte tatsächlich gespeichert sind, wird eine Referenz von Typ Object zurückgegeben. Object ist, wie Sie bereits wissen, die Basisklasse aller JavaObjekte. Eine Referenz vom Typ Object kann jedes Objekt verwalten (weswegen Sie der Auflistung auch beliebige Objekte hinzufügen können). Zu erklären, warum das so ist, ist mir an dieser Stelle leider nicht möglich, weil Sie dazu die komplexen OOP-Konzepte Vererbung und Polymorphismus kennen müssen (für die ich im Buch keinen bzw. nur wenig Platz habe). Lesen Sie gegebenenfalls den Artikel „OOP-Grundlagen“, den Sie auf der Buch-CD finden. Dort beschreibe ich diese Konzepte sehr ausführlich. Das für Sie im Moment Wichtige ist, dass Sie die zurückgegebene Referenz in eine Referenz vom Typ Person konvertieren müssen. Dazu können Sie einfach einen Typecast verwenden. Idealerweise verwenden Sie zum Zugriff auf das Objekt eine Variable: Person p = (Person)ht.get("1002"); System.out.println(p.VollerName());

420

   

Sandini Bib

Existiert ein Element mit dem angegebenen Schlüssel, gibt get eine Referenz darauf zurück. Diese Referenz wird in eine Referenz vom Typ Person umgewandelt und der Variablen p zugewiesen. Über diese Referenz kann das Objekt dann bearbeitet werden. Das Beispiel geht davon aus, dass die Person-Klasse eine Methode VollerName besitzt, die den vollen Namen der Person zurückgibt. Existiert kein Element mit dem angegebenen Schlüssel, gibt get eine Nullreferenz zurück. Eine Nullreferenz ist eine Referenz, die gar kein Objekt referenziert. Der Wert dieser Referenz ist in Java der Wert null, der eben für solche Nullreferenzen steht. In Object Pascal ist das der Wert nil. Mit einer solchen Referenz können Sie nicht arbeiten. Java lässt zwar die Typumwandlung einer Nullreferenz zu, dabei kommt allerdings wieder eine Nullreferenz heraus.

Nullreferenzen

Sie können die Referenz nun glücklicherweise einfach mit null vergleichen, um festzustellen, ob eine Person mit dem angegebenen Schlüssel gefunden wurde: p = (Person)ht.get("2001"); if (p != null) System.out.println(p.VollerName()); else System.out.println("Es wurde keine Person mit dem " + "Schlüssel '2001' gefunden");

Der Zugriff über den Schlüssel ist normalerweise (bei den meisten Auflistungsarten) ernorm schnell. Das liegt daran, dass Auflistungen die Daten nicht einfach nur listenförmig im Speicher verwalten. Die Daten werden mit teilweise sehr komplexen Techniken gespeichert. Ohne näher darauf einzugehen, will ich wenigstens die Bezeichnungen dieser Techniken nennen, damit Sie u. U. an anderen Stellen nachschlagen können. Meist werden dazu so genannte binäre Bäume oder balancierte binäre Bäume verwendet. Da diese prinzipbedingt schon mit einem Schlüssel arbeiten, ist der Zugriff über diesen sehr performant.

Sehr schneller Zugriff über den Schlüssel

Löschen von Elementen In vielen Programmen müssen Sie nicht nur Daten dynamisch hinzufügen, sondern während der Laufzeit des Programms auch wieder löschen. Stellen Sie sich eine einfache Adressverwaltung vor, bei der Sie die Adressdaten beim Start der Anwendung aus einer Datei in eine Auflistung einlesen und diese dann im Programm bearbeiten. Das beim Einlesen der Daten notwendige Hinzufügen von Objekten beherrschen Sie nun. Sie müssen aber auch Objekte aus der Auflistung entfernen können. Dazu verwenden Sie bei einer Java-Hashtable-Instanz die Methode remove:

    

421

Sandini Bib

ht.remove("1002");

Falls der Schlüssel nicht existiert, macht diese Methode einfach gar nichts, erzeugt also auch keine Ausnahme. Beachten Sie, dass das Objekt dabei nicht unbedingt wirklich gelöscht wird. Sie entfernen ja nur die Referenz aus der Auflistung. Falls das Objekt noch über eine andere Referenz referenziert wird, bleibt es bestehen, bis die andere Referenz ungültig wird. Über die Auflistung können Sie das Objekt dann aber nicht mehr erreichen. In Object Pascal ist das Ganze noch etwas komplexer, denn dort müssen Sie Objekte ja über deren Free-Methode explizit aus dem Speicher entfernen. Sie müssten also zuerst die Free-Methode für ein in der Auflistung referenziertes Objekt aufrufen und erst danach die nun tote Referenz aus der Auflistung entfernen. Sequenzielles Durchgehen In vielen Programmen müssen Sie eine Auflistung sequenziell (der Reihe nach) durchgehen. In einer einfachen Adressverwaltung müssen Sie z. B. irgendwann die gespeicherten Daten in eine Datei zurückschreiben. Die Technik des sequenziellen Durchgehens unterscheidet sich bei den verschiedenen Auflistungen. In manchen können Sie ähnlich einem Array über einen Integer-Index auf die Elemente zugreifen. Eine Eigenschaft oder Methode der Auflistung liefert Ihnen dazu die Anzahl der aktuell gespeicherten Elemente. Mit einer Java-Hashtable-Instanz ist das Durchgehen aber leider nicht so einfach, wie es eigentlich sein sollte. Sie benötigen dazu eine Variable vom Typ Enumeration. Dieser Variable weisen Sie die Rückgabe der Methode elements des Hashtable-Objekts zu. Eine Enumeration ist ein Objekt. Über dessen Methode hasMoreElements erfahren Sie, ob noch weitere Elemente oder ob überhaupt Elemente gespeichert sind. Die Methode nextElement gibt das nächste Element zurück. Nach dem Aufruf der elements-Methode der Hashtable-Instanz steht das Enumeration-Objekt vor dem ersten Element. nextElement gibt also beim ersten Aufruf die erste gespeicherte Person-Referenz zurück. Das folgende Beispiel sagt wahrscheinlich mehr aus, als diese kurze Beschreibung: Enumeration enum = ht.elements(); while (enum.hasMoreElements()) { p = (Person)enum.nextElement(); System.out.println(p.VollerName()); }

422

   

Sandini Bib

Sie müssen beachten, dass die Reihenfolge beim sequenziellen Durchgehen nicht der entspricht, die Sie beim Hinzufügen festgelegt haben. Ein Hashtable-Objekt speichert seine Daten, wie bereits gesagt, nicht einfach nur hintereinander. Die Reihenfolge der Daten ist – aus unserer Sicht – mehr oder weniger zufällig. Wenn Sie Daten in einer bestimmten Reihenfolge speichern oder sogar sortieren wollen, müssen Sie eine der anderen Auflistungen wie eine ArrayList-Auflistung (Speicherung in Reihenfolge; Zugriff über einen Integer-Index; Möglichkeit des Einfügens von Elementen mitten in die Liste) verwenden. Nun sind Sie bereits in der Lage, komplexe Daten auf eine sehr einfache Weise im Arbeitsspeicher zu verwalten und diese sogar über einen Schlüssel sehr einfach und schnell zu lokalisieren. Im Moment fehlen Ihnen aber wahrscheinlich noch Ideen, wie Sie dies einsetzen können. Im nächsten Abschnitt beschreibe ich, wie Sie Textdateien lesen und schreiben können. Dann können Sie z. B. schon Adressdaten in einer Datei verwalten und in eine Auflistung einlesen.

7.2

Verwalten von (Text-)Dateien

Bisher haben Sie alle Daten lediglich im Arbeitsspeicher verwaltet. Diese Daten gingen verloren, wenn das Programm beendet wurde. In RealWorld-Programmen müssen Sie Daten aber natürlich auch dauerhaft speichern. Dazu können Sie einfache Dateien verwenden. Die modernere Alternative zu Dateien, nämlich Datenbanken, bespreche ich grundlegend in Kapitel 9.

7.2.1 Welche Daten werden in Dateien verwaltet? Datenbanken sind mittlerweile so einfach und flexibel zu programmieren, dass die Speicherung von Daten in Dateien immer mehr auf dem Rückmarsch ist. Früher wurden hingegen viele Daten in Dateien verwaltet. Dazu wurden so genannte „Dateien mit wahlfreiem Zugriff“ verwendet. Diese Dateien werden auch als Random-1Dateien bezeichnet. Eine solche Datei besteht aus mehreren Datensätzen, die sequenziell hintereinander gespeichert sind. Ein Datensatz besteht aus mehreren Feldern, die die einzelnen Daten speichern. Eine solche Datei, die Personendaten speichert, würde z. B. Datensätze mit den Feldern Vorname, Nachname, Postleitzahl, Straße und Ort verwalten. Das Feld Postleitzahl wäre vom 1.

Random-Dateien

„random“ ist der englische Begriff für „wahlfrei“, aber auch für „zufällig“, „willkürlich“ etc.

  

423

Sandini Bib

Typ Integer, alle anderen wären String-Felder. Wenn in der Datei zwei Personen gespeichert wären, würde diese zwei Datensätze verwalten. Abbildung 7.4 zeigt das Schema einer solchen Datei. Fred-Bogus

Trumper

NewYork

Merril

Overturf

Wien

Abbildung 7.4: Eine Random-Datei mit zwei Datensätzen

Das „wahlfrei“ in der Bezeichnung solcher Dateien kommt daher, dass ein Programm mit entsprechenden Features der Programmiersprache gezielt auf einzelne Datensätze zugreifen kann. Ein Programm kann also z. B. den zweiten Datensatz auslesen und diesen sogar direkt in der Datei mit neuen Werten beschreiben. Solche Techniken wurden früher angewendet, als der Arbeitsspeicher noch sehr klein war. Heute würden solche Dateien wahrscheinlich eher in einem Rutsch in den Speicher gelesen, dort bearbeitet und in einem Rutsch wieder zurückgeschrieben werden. Dazu würden natürlich Auflistungen verwendet werden. Früher mussten Sie den Umgang mit solchen Dateien noch lernen. Heute übernehmen diese Aufgabe Datenbanken, weswegen Sie sich nicht mehr mit Random-Dateien auseinander setzen müssen. Trotzdem sollten Sie natürlich wissen, worum es sich dabei handelt. Textdateien

Die Arbeit mit Dateien ist aber auch heute noch wichtig. Eine sehr häufige Aufgabe beim Programmieren ist nämlich das Lesen und Schreiben von Textdateien. Textdateien sind, wie Sie ja bereits wissen, Dateien, die einzelne Zeichen sequenziell hintereinander speichern. Einzelne Zeilen sind durch ein Carriage Return2- und ein Line Feed3-Zeichen (die ASCIIZeichen 13 und 10 unter Windows) bzw. nur durch ein Line Feed-Zeichen (unter Linux) getrennt. Alle Programmiersprachen stellen zum Lesen und Schreiben von Textdateien meist einfach anzuwendende Features bereit. Ich zeige die Programmierung am Beispiel von Java.

7.2.2 Textdateien lesen In Java haben Sie einige Möglichkeiten, eine Textdatei zu lesen. Ich zeige hier nur eine, damit Sie das Prinzip verstehen. Die Textdatei, die ich einlesen will, speichert die Namen, die Telefonnummern und die E-Mail-Adresse von einzelnen Personen und sieht so aus:

424

2.

Wagenrücklauf

3.

Zeilenvorschub

   

Sandini Bib

Donald;Duck;012345-9999;0177-12345678;[email protected] Daisy;Duck;012345-8888;0172-555444;[email protected] Dagobert;Duck;012345-1;0172-123123;[email protected]

Dieses Beispiel ist eine typische Anwendung von Textdateien. Strukturierte Daten werden auch heute noch in einigen Fällen in Dateien verwaltet. Dazu werden aber keine Random-Dateien, sondern eben Textdateien verwendet. Das gilt besonders dann, wenn diese Daten von anderen Systemen stammen, die die gängigen Datenbankformate nicht kennen. Außerdem ist es eine gute Übung zum Lesen von Textdateien, denn die einzelnen „Felder“ müssen nach dem Lesen noch getrennt ermittelt werden. Der nach meinen Recherchen in Java ideale (weil relativ einfache und effiziente Weg) eine Textdatei zu lesen ist, eine Instanz der Klasse BufferedReader zu verwenden. Ein solches Objekt ermöglicht das zeilenweise Einlesen von Textdateien auf eine sehr effiziente Weise.

Lesen über einen BufferedReader

Beim Erzeugen übergeben Sie diesem Objekt eine neue Instanz der Klasse FileReader. Ein FileReader-Objekt wird zum allgemeinen Lesen von Dateien verwendet. Das BufferedReader-Objekt verwendet das FileReaderObjekt zum eigentlichen Lesen der Daten und ermöglicht Ihnen einen vereinfachten Zugriff darauf. Dem FileReader-Objekt übergeben Sie bei der Erzeugung den Dateinamen: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15

String dateiname = "C:\\Projekte\\Java\\Personen.txt"; /* Eine Instanz von BufferedReader zum zeilenweisen Lesen erzeugen */ BufferedReader in = null; try { in = new BufferedReader(new FileReader(dateiname)); } catch (FileNotFoundException e) { System.out.println("Die Datei '" + dateiname + "' wurde nicht gefunden"); return; }

Da der Dateiname immer absolut angegeben werden muss, habe ich diesen in Zeile 1 auf eine Datei im Ordner C:\Projekte\Java gesetzt. Die doppelten Backslashs bewirken, dass die Sonderbedeutung der Backslashs aufgehoben wird und im Ergebnis nur jeweils ein Backslash herauskommt. Der Dateiname ist hier für Windows angegeben. Unter Linux

  

425

Sandini Bib

müssen Sie den Dateinamen natürlich anders angeben (/projekte/java/ personen.txt). Die Erzeugung des BufferedReader-Objekts muss in einer Ausnahmebehandlung erfolgen. Deswegen wird das Objekts in Zeile 8 innerhalb eines try-Blocks erzeugt. Die Variable ist außerhalb des Blocks in Zeile 5 deklariert, damit diese unterhalb der Ausnahmebehandlung zur Verfügung steht. Ich habe die Variable mit null initialisiert, da der Compiler ansonsten beim Kompilieren den Fehler meldet, dass diese Variable uninitialisiert sein könnte. In Zeile 10 wird dann noch die beim Öffnen der Datei eventuell auftretende Ausnahme abgefangen. Die Art der abzufangenden Ausnahme meldet der Compiler, wenn Sie das Programm ohne Ausnahmebehandlung versuchen zu kompilieren. Zeilenweise Lesen

426

Nun können Sie die Textdatei zeilenweise einlesen. Dazu verwenden Sie die readLine-Methode des BufferedReader-Objekts. Diese Methode gibt die eingelesene Zeile zurück oder null, wenn keine (weitere) Zeile gelesen werden kann. Sie können die einzelnen Zeilen einfach in einer Schleife einlesen. Das Ganze muss wieder in eine Ausnahmebehandlung eingefügt werden, die auf I/O4-Fehler reagiert: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

try { /* Textdatei zeilenweise lesen */ String zeile; do { zeile = in.readLine(); if (zeile != null) { System.out.println(zeile); } } while (zeile != null);

4.

Input / Output

/* Datei schließen */ in.close(); } catch (IOException e) { System.out.println("Fehler beim Lesen der Daten: " + e.getMessage()); }

   

Sandini Bib

Das Beispiel gibt in Zeile 25 lediglich die eingelesene Zeile aus. Das Zerlegen der Zeile in die einzelnen Bestandteile zeige ich bei der Besprechung der Beispielanwendung in Kapitel 8. In Zeile 30 wird die Datei schließlich geschlossen. Sie sollten nie vergessen, Dateien, die Sie geöffnet haben, auch wieder zu schließen. So stellen Sie sicher, dass die Dateien nicht versehentlich geöffnet bleiben und so eventuell von anderen Anwendungen nicht gelesen werden können.

7.2.3 Textdateien schreiben Das Schreiben von Textdateien ist ähnlich „einfach“ wie das Lesen. Sie benötigen dazu idealerweise (weil wahrscheinlich am performantesten) eine Instanz der Klasse BufferedWriter, der Sie im Konstruktor eine Instanz der Klasse FileWriter übergeben. In deren Konstruktor übergeben Sie den Dateinamen. Das Ganze muss einmal wieder in eine Ausnahmebehandlung eingefügt werden. Dieses Mal verwende ich lediglich eine globale Ausnahmebehandlung, weil ich keine spezielle Fehlermeldung ausgeben will. Die Systemfehlermeldung, die die getMessage-Methode des Exception-Objekts zurückgibt, reicht mir für dieses Programm vollkommen aus: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19

String dateiname = "/projekte/java/demo.txt"; // Linux-Dateiname! /* Ermitteln der auf dem aktuellen System gültigen Zeilen-Trennzeichen (nur LF oder CR+LF) */ String zeilenTrennzeichen = System.getProperty("line.separator"); /* Eine Instanz von BufferedWriter zum Schreiben erzeugen */ BufferedWriter out = null; try { out = new BufferedWriter(new FileWriter(dateiname)); /* Zwei Zeilen out.write("Die out.write("ist out.write("Nur out.write("die

schreiben */ Antwort auf die Frage der Fragen "); 42." + zeilenTrennzeichen); leider kennt niemand mehr "); Frage." + zeilenTrennzeichen);

  

427

Sandini Bib

20 21 22 23 24 25 26 27

/* Datei schließen */ out.close(); } catch (Exception e) { System.out.println("Fehler beim Schreiben der Datei: " + e.getMessage()); }

In Zeile 5 wende ich einen kleinen „Trick“ an. Damit das Programm möglichst auf Windows- und Linux-Systemen läuft, ermittle ich die auf dem aktuellen System gültigen Zeichen für die Trennung von Zeilen in Textdateien. Die Methode getProperty der System-Klasse gibt diese Information (neben einigen anderen System-Eigenschaften) zurück. Diese Lösung habe ich übrigens (natürlich) im Internet gefunden. Nachdem in Zeile 12 dann das BufferedWriter-Objekt erzeugt wurde, schreibt das Beispielprogramm zwei Zeilen in die Textdatei. Dazu nutzen es die write-Methode. Diese Methode erzeugt keinen Zeilenumbruch, weswegen Sie zur Erzeugung eines solchen die aktuellen Zeilentrennzeichen anhängen müssen (was in Zeile 16 und 18 des Quellcodes geschieht). Schließlich müssen Sie die Datei nur noch schließen (Zeile 21). Im folgenden Beispielprogramm nutze ich die hier erlernten Techniken.

7.3

Zusammenfassung

In diesem Kapitel haben Sie zunächst gelernt, mit einfachen Arrays umzugehen. Sie können nun eine Liste von Daten in einem eindimensionalen Array verwalten und kennen auch die Grenzen dieser Art der Speicherung. Um flexibler programmieren zu können, sind Sie in der Lage, eine der Auflistungen Ihrer Programmiersprache zu verwenden. Sie müssen vielleicht noch einmal nachlesen, wie Sie diese verwenden, aber Sie kennen die Vorteile und das Prinzip dieser Art der Speicherung von Massendaten. Sie können sogar Objekte über eine Auflistung verwalten. Um Daten in einer Datei zu verwalten, sind Sie in der Lage unter Java eine Textdatei zu lesen und zu schreiben. Sie kennen das Prinzip der strukturierten Speicherung von Daten in Textdateien und können z. B. die Daten von Personen in einer solchen Datei verwalten.

428

   

Sandini Bib

7.4

Fragen und Übungen

1. Welche Vorteile bieten Auflistungen gegenüber einfachen Arrays? 2. Welchen Vorteil bieten Arrays gegenüber Auflistungen? 3. Was können Sie alles in einer Auflistung verwalten? 4. Wie finden Sie heraus, wie Sie in Delphi oder Kylix eine Textdatei

lesen und schreiben?

  

429

Sandini Bib

Sandini Bib

8

Programmieren einer Beispielanwendung

le

n e rn

Sie lernen in diesem Kapitel: • wie Sie mit dem bisher Gelernten in der Praxis eine Anwendung zur Verwaltung von Adressdaten in einer Textdatei entwickeln. Das Kapitel soll nach der ganzen Theorie zu einem ersten größeren Erfolg führen und Ihnen zeigen, zu was Sie mittlerweile bereits in der Lage sind. Sie entwickeln im Verlauf dieses Kapitels eine Anwendung, die Adressdaten in einer Textdatei verwaltet und diese dem Anwender in einem Formular zur Verfügung stellt. Diese Anwendung beinhaltet sehr viele der bisher gelernten Techniken und führt sogar (wie immer in der Praxis) zu neuen Problemen, die aber natürlich gelöst werden. Das Beispiel soll ein wenig Leben in die graue Theorie bringen und das, was Sie bisher gelernt haben, in der Praxis anwenden. Und es soll auch Spaß machen . Mir ist dabei übrigens bewusst, dass es sich bereits um ein komplexes Programm handelt. Das Nachvollziehen meiner Programmierung ist mit Sicherheit nicht allzu einfach. Aber dieses Programm zeigt sehr viele Techniken, die in der Praxis wichtig sind. Und es zeigt auch, wie Sie das, was Sie bisher gelernt haben, anwenden. Nebenbei erhalten Sie auch noch eine erste einfache Telefonadress-Verwaltung.

-

8.1

Einleitung

Um ein sinnvolles Programm zu erzeugen, soll die Beispielanwendung die Daten der Textdatei aus Kapitel 7 einlesen und in einem Formular darstellen. Der Anwender soll die Möglichkeit haben, schrittweise durch die einzelnen Adressen zu gehen. Um die Daten im Programm sinnvoll zu speichern, verwende ich eine Auflistung, die einzelne Person-Objekte verwaltet.

(LQOHLWXQJ

431

Sandini Bib

Bei der Entwicklung eines solchen, bereits komplexen Programms treten sehr viele Probleme auf, die natürlich gelöst werden müssen. Dabei hilft das Internet. Suchen Sie bei www.google.de, groups.google.com/ advanced_group_search oder auf speziellen Java-Seiten wie dem in Entwicklerkreisen bekannten Java- und Internet Glossary von Mindprod (www.mindprod.com/jgloss.html) nach Lösungsansätzen. So bin auch ich vorgegangen, als ich bei der Entwicklung des Beispielprogramms auf Probleme stieß.

8.2

Vorbereitungen

8.2.1 Das Projekt und das Startformular Erzeugen Sie zunächst in Sun ONE Studio 4 ein neues Projekt, das Sie vielleicht Textdatenbank nennen. Fügen Sie dem Projekt ein Formular vom Typ JFrame hinzu. Stellen Sie das Layout des Formulars auf das NullLayout um und stellen Sie die Eigenschaft FORM SIZE POLICY auf GENERATE RESIZE CODE. Wenn Sie nicht mehr wissen, wie das geht, lesen Sie in Kapitel 2 nach. Wenn Sie unter Sun ONE Studio 4 ein neues Formular erzeugen, erstellen Sie in Wirklichkeit eine neue Klasse. Diese Klasse wird, wenn Sie ein JFrame-Formular benutzen, von der Basisklasse JFrame abgeleitet und erbt deswegen alle Eigenschaften und Methoden dieser Klasse. Das grundsätzliche Verhalten eines Formulars ist bereits in JFrame definiert. Sie müssen lediglich Steuerelemente hinzufügen und Programme schreiben. Alles das, was Sie dem Formular hinzufügen, wird als zusätzliche Eigenschaft (z. B. die Steuerelemente) oder zusätzliche Methode (z. B. die Methoden der Steuerelemente) in Ihrer neuen Klasse gespeichert. Platzieren Sie JLabel-, JTextField- und JButton-Steuerelemente auf dem Formular, bis dieses in etwa so aussieht wie in Abbildung 8.1.

432

       

Sandini Bib

Abbildung 8.1: Das Formular für das Programm zum Bearbeiten einer Textdatei

Die Steuerelemente habe ich folgendermaßen benannt: • Textfelder: txtVorname, txtNachname, txtTelefon, txtHandy und txtEMail • Schalter unten links: btnErster, btnVorheriger, btnNaechster und btnLetzter • Schalter rechts: btnNeu, btnSpeichern und btnBeenden Die Schalter mit den Zeichen || sollen dem Anwender die Möglichkeit geben, durch die einzelnen Adressen zu gehen. Der Schalter NEU soll eine neue Personen erzeugen, der Schalter SPEICHERN soll die aktuell angezeigten Daten (in der Auflistung) speichern.

8.2.2 Eine Klasse für die Personen Zur Speicherung der Personendaten benötigen Sie eine Klasse. Diese Klasse sollte Eigenschaften für die einzelnen Adressbestandteile besitzen. Methoden müssen nicht unbedingt enthalten sein. Eine Methode VollerName, die den vollen Namen der Person zurückgibt, ist aber immer hilfreich. Diese Klasse fügen Sie dem Projekt nun hinzu. Nennen Sie die Klasse vielleicht Person und programmieren Sie sie folgendermaßen:

9RUEHUHLWXQJHQ

433

Sandini Bib

01 public class Person 02 { 03 /* Eigenschaften */ 04 public String Vorname; 05 public String Nachname; 06 public String Telefon; 07 public String Handy; 08 public String EMail; 09 10 /* Methode zur Rückgabe des vollen Namens */ 11 public String VollerName() 12 { 13 return this.Vorname + " " + this.Nachname; 14 } 15 }

Eine Instanz dieser Klasse soll später eine Person aus der Textdatei repräsentieren und besitzt deswegen entsprechende Eigenschaften. Ein Konstruktor ist zurzeit noch nicht notwendig. Die Methode VollerName wird zwar wahrscheinlich ebenfalls nicht benötigt, ist aber sicherlich bei späteren Erweiterungen des Programms hilfreich.

8.2.3 Ein Dialog für Meldungen Ein Programm muss immer wieder Meldungen ausgeben. Unser Beispielprogramm benötigt an einigen Stellen einen Dialog für Meldungen. Für den Fall, dass beim Einlesen der Datei ein Fehler auftritt, muss dieser z. B. gemeldet werden, damit der Anwender informiert ist. Viele Programmiersprachen stellen dazu einen oder mehrere vorgefertigte Dialoge zur Verfügung. In Delphi und Kylix nutzen Sie für einfache Meldungen die Funktion ShowMessage und für komplexere Meldungen (mit verschiedenen Bestätigungsschaltern) die Funktion MessageDlg. Dialoge sind so definiert, dass der Benutzer den Dialog erst schließen muss, bevor das Programm weiterläuft. Solche Dialoge kennen Sie bereits. Schreiben Sie z. B. in einem Editor einen Text und schließen diesen, so fragt das Programm in einem Dialog, ob Sie die Datei vor dem Schließen speichern wollen. Leider besitzt Java scheinbar (nach meinen Recherchen) keinen vorgefertigten Dialog für Meldungen. Sie müssen einen eigenen erzeugen. Aber das ist dann auch gleich eine gute Übung.

434

       

Sandini Bib

Fügen Sie dem Projekt ein Formular vom Typ JDialog hinzu. Nennen Sie dieses Objekt vielleicht MessageDialog. Platzieren Sie darauf ein JButtonund ein JTextArea-Steuerelement. Ich verwende für die Ausgabe der Meldung kein Label, weil ein Label nicht in der Lage ist mehrere Zeilen Text auszugeben. Geben Sie den Steuerelementen die Namen btnOK und txtMessage. Stellen Sie die Eigenschaften des JTextArea-Steuerelements dann folgendermaßen ein:

Ein JDialog-Objekt wird für Dialoge verwendet

• lineWrap: true. Damit erreichen Sie, dass das Steuerelement den Text automatisch am rechten Rand umbricht • wrapStyleWord: true. Diese Einstellung bewirkt, dass das Steuerelement die Zeilen nicht einfach am letzten darstellbaren Zeichen einer Zeile umbricht, sondern dafür sorgt, dass immer ganze Wörter in die nächste Zeile umbrochen werden • editable: false. Damit erreichen Sie, dass der Anwender den Text nicht editieren kann • opaque: false. Über diese Einstellung erreichen Sie, dass der Hintergrund des Steuerelements transparent wird. Im Ergebnis sieht das Steuerelement nun aus wie ein Label, ist aber in der Lage, mehrere Zeilen Text anzuzeigen. Passen Sie dann noch den Text des Schalters an, bis der Dialog in etwa so aussieht wie in Abbildung 8.2.

Abbildung 8.2: Ein einfacher Dialog für Meldungen mit markiertem JTextArea-Steuerelement

Nun müssen Sie noch dafür sorgen, dass das Programm dem Dialog eine Meldung und idealerweise auch den Titel des Dialogs übergeben kann und dass dieser vom Anwender geschlossen werden kann. Das Programm wird später eine neue Instanz dieser Klasse erzeugen und diese über die show-Methode öffnen. Es wäre also sinnvoll, wenn die Meldung direkt im Konstruktor übergeben werden könnte. Der Konstruktor existiert bereits:

9RUEHUHLWXQJHQ

Meldung und Titel im Konstruktor übergeben

435

Sandini Bib

01 public MessageDialog(java.awt.Frame parent, boolean modal) { 02 super(parent, modal); 03 initComponents(); 04 }

Diesem Konstruktor übergeben Sie bei der Erzeugung eines Dialogs per Voreinstellung ein „Parent“-Formular, das diesen Dialog quasi besitzt. Das bewirkt u. a., dass der Dialog über dem anderen Formular und in dessen Mitte angezeigt wird. Der Datentyp des parent-Arguments ist Frame, weil das die Basisklasse aller Formulare ist. Das zweite Argument entscheidet darüber, ob der Dialog modal oder unmodal ist. Modale Dialoge müssen vom Anwender erst geschlossen werden, bevor das Programm weiterläuft. Unmodale Dialoge werden zwar angezeigt, das Programm läuft aber im Hintergrund weiter. Modale Dialoge sind der Quasi-Standard beim Programmieren, weil es eigentlich immer notwendig ist, das Programm so lange anzuhalten, wie der Dialog geöffnet ist. In Zeile 2 wird der von der Basisklasse JDialog geerbte Konstruktor unter Übergabe der Argumente aufgerufen. Zeile 3 ruft eine private Methode auf, die die Steuerelemente erzeugt, die Sie im Formulareditor auf dem Dialog anlegen. Sie müssen den Konstruktor nun so anpassen, dass eine Meldung und der Titel übergeben werden können: 01 public MessageDialog(java.awt.Frame parent, boolean modal, 02 String meldung, String titel) 03 { 04 super(parent, modal); 05 initComponents(); 06 this.txtMessage.setText(meldung); 07 this.setTitle(titel); 08 }

Achten Sie darauf, dass Sie im Konstruktor eines Formulars immer erst nach dem Aufruf von initComponents auf die Steuerelemente zugreifen. Vor diesem Aufruf sind die Steuerelemente noch nicht erzeugt. Ein Zugriff auf die Steuerelemente würde dann zu einer Ausnahme mit der wenig aussagekräftigen Meldung „null“ führen (weil Sie versuchen, mit einem Nullzeiger zu arbeiten). Der OK-Schalter

436

Nun fehlt nur noch die Methode für die Behandlung der Betätigung des OK-Schalters. Klicken Sie wieder doppelt auf den Schalter, um diese Methode zu erzeugen. Über den Aufruf der hide-Methode können Sie den Dialog schließen:

       

Sandini Bib

01 private void btnOKActionPerformed(java.awt.event.ActionEvent evt) { 02 /* Dialog schließen */ 03 this.hide(); 04 }

Dummerweise kompiliert der Compiler diese Klasse nun nicht. Die Klasse enthält nämlich eine main-Methode, die eine Instanz der Klasse erzeugt und den alten Konstruktor aufruft. Diese Methode ist für unsere Zwecke vollkommen sinnlos, weil der Dialog nicht als separates Programm gestartet werden soll. Löschen Sie die main-Methode. Nun können Sie den Dialog kompilieren.

Entfernen der main-Methode

8.2.4 Ein Tipp Da Ihr Programm nun recht komplex wird, verlieren Sie schnell die Übersicht und finden zu bearbeitende Methoden u. U. nicht mehr allzu schnell. Sie können die einzelnen Elemente Ihres Programms aber recht einfach über den Explorer erreichen. Öffnen Sie in dessen Projekt-Register den Eintrag, der für das Formular steht. Öffnen Sie danach den Eintrag CLASS Formularname. Dort finden Sie einzelne Bereiche, in denen alle Eigenschaften (die hier als „Fields“ bezeichnet werden), Konstruktoren und Methoden übersichtlich aufgelistet werden. Über einen Doppelklick auf einem Eintrag können Sie sehr effizient zum entsprechenden Quellcode wechseln.

8.3

Einlesen der Daten

Beim Start der Anwendung müssen Sie nun zunächst die Textdatei einlesen. Die Daten sollen in eine Auflistung vom Typ ArrayList gelesen werden, da hier kein Schlüssel verwaltet wird. Eine ArrayList-Auflistung ermöglicht den einfachen Zugriff auf die einzelnen Elemente über einen Integer-Index. Da diese Auflistung so lange leben soll wie das Formular (sie soll ja die eingelesenen Personendaten verwalten), müssen Sie sie als private Eigenschaft der Formularklasse deklarieren. Öffnen Sie den QuellcodeEditor für das Start-Formular über einen Doppelklick auf den Eintrag CLASS STARTFORM im Projekt-Register des Explorers. Da die ArrayList-Klasse dem Paket java.util entstammt, sollten Sie dieses Paket importieren. Das Paket java.io sollten Sie gleich mit importieren, weil Sie es für das Einlesen der Textdatei benötigen:

  

437

Sandini Bib

import java.util.*; import java.io.*; public class StartForm extends javax.swing.JFrame { /* Private Eigenschaft für die Auflistung */ private ArrayList personen; ... Wo werden die Daten eingelesen?

Nun stellt sich die Frage, wo (bzw. wann) die Daten eingelesen und in der Auflistung gespeichert werden. Die Antwort darauf ist: wenn das Formular geöffnet wird. Dazu müssen Sie auf das Ereignis windowOpened reagieren. Sie könnten statt des Ereignisses windowOpened auch den Konstruktor des Formulars verwenden, um das Formular zu initialisieren. Das Ergebnis ist eigentlich dasselbe (ich kann dabei kaum Unterschiede erkennen). Diese Technik habe ich bereits bei der Erzeugung eines eigenen Dialogs genutzt (siehe Seite 434). Klicken Sie zur Erzeugung einer Ereignisbehandlungsmethode für das windowOpened-Ereignis (das entsprechend dem Namen immer dann aufgerufen wird, wenn das Formular geöffnet wird) mit der rechten Maustaste auf einen freien Bereich des Formulars und wählen Sie den Befehl EVENTS / WINDOW / WINDOWOPENED. Der Editor erzeugt eine passende Methode: private void formWindowOpened(java.awt.event.WindowEvent evt) { // Add your handling code here: }

Als Erstes sollten Sie in dieser Methode nun die Auflistung erzeugen: 01 private void formWindowOpened(java.awt.event.WindowEvent evt) { 02 personen = new ArrayList();

Dann können Sie die Textdatei einlesen. Zur Vereinfachung verwende ich hier nur eine Ausnahmebehandlung: 03 04 05 06 07 08 09 10 11

438

String dateiname = "C:\\Projekte\\Java\\Personen.txt"; BufferedReader in = null; try { in = new BufferedReader(new FileReader(dateiname)); String zeile; do { zeile = in.readLine();

       

Sandini Bib

Die Angabe des Dateinamens direkt im Programm ist für die Praxis nicht besonders ideal. Normalerweise müssten Sie den Dateinamen in einer separaten Konfigurationsdatei verwalten, die Sie beim Start des Programms einlesen. Alternativ könnten Sie die Datei auch in demselben Ordner erwarten, in dem das Programm gespeichert ist. Für das Beispielprogramm würde eine solche Verwaltung des Dateinamens aber zu weit führen, weil es in Java leider nicht allzu einfach ist, den Ordner zu ermitteln, aus dem heraus ein Programm gestartet wurde. Nach dem Einlesen einer Zeile wird eine private Methode leseZeile aufgerufen (die noch programmiert werden muss), die die Zeile zerlegen und in ein Objekt der Klasse Person schreiben soll. Da dieses Zerlegen auch fehlschlagen kann (z. B. wenn weniger oder mehr als fünf Teilstrings in einer Zeile gespeichert sind), soll diese Methode eine Funktion sein, die true oder false zurückgibt. Beim Aufruf kann dann überprüft werden, ob das Zerlegen eventuell fehlgeschlagen ist: 12 13

if (lesePerson(zeile) == false) {

Für den Fall, dass das Einlesen fehlgeschlagen ist, wird der vorgefertigte Dialog geöffnet: 14 15 16 17 18 19 20

/* Dialog erzeugen */ MessageDialog dialog = new MessageDialog(this, true, "Die Zeile '" + zeile + "' konnte nicht " + "korrekt eingelesen werden. Überprüfen Sie " + "die Datei", "Fehler"); /* Dialog öffnen */ dialog.show();

Das Programm erzeugt den Dialog zunächst in Zeile 15 bis 18, wobei die im Konstruktor erwarteten Argumente übergeben werden. Im ersten Argument wird das Parent-Formular definiert, das ja das aktuelle Formular selbst ist. Das zweite Argument legt fest, dass der Dialog modal geöffnet werden soll. Im dritten und vierten Argument werden dann die Meldung und der Titel des Dialogs übergeben. Im Fehlerfall wird das Programm auch gleich wieder beendet. Dabei sollten Sie die zurzeit noch geöffnete Datei zuvor schließen: 21 22 23

in.close(); System.exit(1); }

  

439

Sandini Bib

Die exit-Methode der System-Klasse ist eine besondere Methode zum Beenden eines Programms. Diese Methode führt dazu, dass das Programm sofort beendet wird. Für ein normales Schließen eines Formulars ist exit nicht geeignet. Sie sollten diese Methode immer nur im Fehlerfall aufrufen (exit wird allerdings auch intern in der Methode exitForm aufgerufen, damit das Programm beendet wird). Zum normalen Schließen eines Formulars verwenden Sie die dispose-Methode der Formularklasse. Damit stellen Sie sicher, dass alle Ereignismethoden, die nach dem Schließen eines Formulars abgearbeitet werden sollen, auch wirklich aufgerufen werden. Der exit-Methode übergeben Sie einen Integer-Wert. Der hier übergebene Wert kann über eine Umgebungsvariable des Betriebssystems von einem eventuellen Batch- oder Scriptprogramm, das unser Programm gestartet hat, ausgelesen werden. An einem Wert ungleich 0 erkennt dieses Programm dann, dass ein Fehler aufgetreten ist. Schließlich müssen Sie die Schleife noch abschließen und die Datei schließen: 24 25 26

} while (zeile != null); in.close(); }

In der Ausnahmebehandlung wird wieder der vorgefertigte Dialog geöffnet, um eine Fehlermeldung auszugeben. Außerdem wird das Programm auch hier wieder beendet: 27 catch (Exception e) 28 { 29 /* Dialog erzeugen */ 30 MessageDialog dialog = new MessageDialog(this, true, 31 "Fehler beim Lesen der Datei: " + e.getMessage(), "Fehler"); 32 33 /* Programm beenden */ 34 System.exit(1); 35 } 36 }

Damit ist die Programmierung der Ereignisbehandlungsmethode für das windowOpened-Ereignis zunächst abgeschlossen. Die nun zu programmierende Methode lesePerson können Sie einfach in einem freien Bereich der Formular-Klasse programmieren (nur nicht innerhalb einer anderen Methode): 01 private boolean lesePerson(String zeile) 02 {

440

       

Sandini Bib

Das Problem, das Sie nun lösen müssen, ist, die eingelesene Zeile in einzelne Bestandteile zu zerlegen. Ansätze zur Lösung dieses Problems finden Sie (wie immer) über die Newsgroup- oder die allgemeine Suche von Google. Sie werden schnell herausfinden, dass Sie dazu eine Instanz der Klasse StringTokenizer verwenden. In der Java-Api-Spezifikation, die Sie unter der Datei index.htm im Ordner bin der Java-Dokumentation finden, erfahren Sie mehr über die Konstruktoren und Methoden dieser Klasse. Beim Erzeugen einer Instanz dieser Klasse können Sie den zu zerlegenden String und das Trennzeichen übergeben. Zuvor sollten Sie aber noch überprüfen, ob die Zeile leer ist. Dazu sollten Sie zuvor alle rechts und links in der Zeile eventuell vorhandenen Leerzeichen entfernen. Für diese Aufgabe können Sie eine Methode des Strings verwenden (Strings sind in Java ja auch Objekte). Die Methode trim entfernt alle linken und rechten Leerzeichen: 03

zeile = zeile.trim();

Eine leere Zeile sollte einfach übergangen, aber nicht als Fehler gewertet werden. Dazu müssen Sie die Zeile mit einem leeren String ("") vergleichen. Beim Vergleich mit einem leeren String müssen Sie – wie bei allen Stringvergleichen in Java – aufpassen: Java-Strings sind einzelne Objekte. Wenn Sie zwei Strings miteinander vergleichen, vergleichen Sie die Referenzen, nicht den Inhalt der Objekte. Für Stringvergleiche müssen Sie in Java immer die compareTo-Methode verwenden. Diese Methode gibt 0 zurück, wenn beide Strings gleich sind: 04 05 06 07

if (zeile.compareTo("") == 0) { return true; }

Wenn die Zeile nicht leer ist, können Sie das StringTokenizer-Objekt erzeugen und über die Methode countTokens ermitteln, wie viele einzelne Token der String enthält: 08 09 10 11 12 13

else { StringTokenizer st = new StringTokenizer(zeile, ";"); if (st.countTokens() == 5) {

  

441

Sandini Bib

Wenn der String genau fünf Token enthält, können Sie diese über die nextToken-Methode auslesen. Diese Methode liest jeweils das nächste Token. Zuvor müssen Sie eine Instanz der Person-Klasse erzeugen, der Sie die einzelnen Token zuweisen: 14 15 16 17 18 19 20 21 22

/* Erzeugen eines neuen Person-Objekts */ Person p = new Person(); /* Schreiben der Eigenschaften */ p.Vorname = st.nextToken(); p.Nachname = st.nextToken(); p.Telefon = st.nextToken(); p.Handy = st.nextToken(); p.EMail = st.nextToken();

Dieses Objekt müssen Sie nun nur noch an die Auflistung anfügen. Eine ArrayList-Auflistung besitzt dazu die Methode add. Dieser Methode übergeben Sie nur das Objekt oder, am zweiten Argument, optional noch den Index, an dem das Objekt angefügt werden soll. Wenn Sie den Index nicht übergeben, wird das Objekt an das Ende angefügt: 23 24

/* Anfügen des Objekts an die Auflistung */ personen.add(p);

Schließlich geben Sie für den Erfolgsfall noch true und für den Fehlerfall false zurück: 25 26 27 28 29 30 31 32 33 } 34 }

/* Erfolg zurückmelden */ return true; } else { /* Nicht die korrekte Anzahl Teilstrings ermittelt */ return false; }

8.4

Test des ersten Entwurfs

Nun sollten Sie das Programm ein erstes Mal testen. Erzeugen Sie das Programm auf jeden Fall komplett über (ª) (Strg) (F11). Durch dieses explizite Erzeugen erreichen Sie, dass alle Ihre Änderungen berücksichtigt werden. In einigen Fällen meint die Entwicklungsumgebung ansonsten, dass einige Dateien nicht kompiliert werden müssen, obwohl Sie diese verändert haben.

442

       

Sandini Bib

-

Nachdem Sie Ihre Syntaxfehler beseitigt haben ( ) testen Sie das Programm. Sie können zwar noch nicht viel machen, aber die Daten werden (hoffentlich) erfolgreich eingelesen. Sie sollten aber auch Ihre Fehlerbehandlungen testen. Provozieren Sie dazu die erwarteten Fehler, indem Sie eine Zeile der Datei manipulieren und die Datei in einem weiteren Test umbenennen, sodass sie nicht gefunden wird. Das Programm müsste dann den Fehler melden (Abbildung 8.3).

Abbildung 8.3: Fehlermeldung über den eigenen Dialog unter Windows

8.5

Programmierung der eigentlichen Funktionalität des Programms

Nun, da die Textdatei eingelesen ist, programmieren Sie die eigentliche Funktionalität des Programms. Die Grundidee, die hinter dem Programm steckt, ist, die aktuelle Position in der Auflistung über eine Integer-Variable zu verwalten und nach dem Weiterbewegen des „Positionszeigers“ die Daten der aktuellen Person in den Textfeldern darzustellen. Nach dem Start des Programms soll der erste „Datensatz“ automatisch angezeigt werden. Sie müssen aber auch darauf reagieren, dass die Textdatei eventuell leer ist. Dann soll einfach ein neuer Datensatz (eine neue, leere Person) erzeugt und „angezeigt“ werden. Für den Positionszeiger benötigen Sie eine Variable, die während der gesamten Laufzeit des Formulars gültig ist und die in allen Methoden des Formulars verwendet werden kann. Diese Variable deklarieren Sie also wieder auf der Klassenebene: public class StartForm extends javax.swing.JFrame { /* Private Eigenschaft für die Auflistung */ private ArrayList personen; /* Private Eigenschaft für den Positionszeiger */ private int position = 0; ...

   

       

443

Sandini Bib

In der Methode, die auf das Ereignis windowOpened (also auf das Öffnen des Formulars) reagiert, programmieren Sie nun die Anzeige der Daten der ersten Person und reagieren darauf, dass noch keine Personen existieren. Diese Programmierung nehmen Sie unten unterhalb der bereits vorhandenen Programmierung vor: 01 private void formWindowOpened(java.awt.event.WindowEvent evt) { 02 personen = new ArrayList(); ... 34 35 36 37 38 39

... /* Programm beenden */ System.exit(1); } /* Die Daten der ersten Person anzeigen oder neue Person erzeugen */

Sie können über die size-Methode der ArrayList-Auflistung ermitteln, wie viele Elemente die Liste speichert. Wenn Personen gespeichert sind, soll die erste angezeigt werden: 40 41 42 43 44

if (personen.size() > 0) { /* Auflistung ist nicht leer: Person anzeigen */ zeigePerson(); }

Das Programm arbeitet mit einer (privaten) Methode zeigePerson. Diese Methode soll die Daten der aktuellen Person in den Textfeldern ausgeben und wird im Programm an einigen Stellen aufgerufen werden. Ich programmiere diese Methode später. Zunächst reagiert das Programm darauf, dass die Textdatei keine Personendaten speichert: 45 else 46 { 47 /* Auflistung ist leer: Neue "leere" Person erzeugen */ 48 personen.add(new Person()); 49 50 /* Dem Anwender eine Meldung übergeben */ 51 MessageDialog dialog = new MessageDialog(this, true, 52 "Die Personen-Datei ist zurzeit noch leer. " + 53 "Das Programm hat automatisch eine neue Person angelegt.", 54 "Information"); 55 dialog.show(); 56 } 57 }

444

       

Sandini Bib

Für den Fall, dass die Textdatei leer ist, wird zunächst eine neue „leere“ Person erzeugt. Damit der Anwender informiert ist, gibt das Programm (über unsere MessageDialog-Klasse) dann noch eine entsprechende Meldung aus. Das Erzeugen einer neuen Person bei einer leeren Datei ist übrigens ein kleiner Trick. Damit vermeide ich den ansonsten sehr komplizierten Umgang mit dem Sonderfall, dass die Datei leer ist.

8.5.1 Anzeigen der Personendaten Nun müssen Sie eine private Methode programmieren, die die Daten der aktuellen Person anzeigt. Die aktuelle Person wird über den Positionszeiger (die Eigenschaft position) gekennzeichnet. Diese Methode muss also nichts weiter machen, als eine Referenz auf das entsprechende Person-Objekt zu ermitteln, dessen Eigenschaften auszulesen und in die Textfelder zu schreiben. Eine ArrayList-Auflistung ermöglicht über die Methode get den Zugriff über einen Integer-Index. Wie bereits bei der Hashtable-Auflistung müssen Sie die zurückgegebene Referenz in den erwarteten Typ konvertieren: 01 private void zeigePerson() 02 { 03 /* Ermitteln der aktuellen Person */ 04 Person p = (Person)personen.get(this.position); 05 06 /* Ausgeben dieser Daten in den Textfeldern */ 07 this.txtVorname.setText(p.Vorname); 08 this.txtNachname.setText(p.Nachname); 09 this.txtTelefon.setText(p.Telefon); 10 this.txtHandy.setText(p.Handy); 11 this.txtEMail.setText(p.EMail); 12 }

Wenn Sie das Programm nun starten, wird die erste der gespeicherten Personen angezeigt (Abbildung 8.4).

   

       

445

Sandini Bib

Abbildung 8.4: Anzeige einer Person im Beispielprogramm unter Linux

8.5.2 Programmieren der Bewegungsschalter Die Programmierung der Schalter zum Bewegen in den Daten ist nun sehr einfach. Sie müssen in den einzelnen Methoden lediglich den Positionszeiger entsprechend umdefinieren und die Methode zeigePerson aufrufen. Bei den Schaltern für den vorherigen und den nächsten Datensatz müssen Sie allerdings aufpassen, dass der Positionszeiger nicht auf einen ungültigen Wert gesetzt wird. Die einzelnen Methoden erzeugen Sie einmal wieder über einen Doppelklick auf den Schaltern. Die entsprechenden Methoden sehen dann so aus: 01 private void btnErsterActionPerformed(java.awt.event.ActionEvent evt) { 02 /* Positionszeiger setzen */ 03 this.position = 0; 04 /* Personendaten anzeigen */ 05 zeigePerson(); 06 } 01 private void btnVorherigerActionPerformed(java.awt.event.ActionEvent evt) { 02 /* Positionszeiger setzen, wenn möglich */ 03 if (this.position > 0) 04 { 05 this.position--; 06 /* Personendaten anzeigen */ 07 zeigePerson(); 08 } 09 } 01 private void btnNaechsterActionPerformed(java.awt.event.ActionEvent evt) { 02 /* Positionszeiger setzen, wenn möglich */ 03 if (this.position < personen.size() -1) 04 {

446

       

Sandini Bib

05 this.position++; 06 /* Personendaten anzeigen */ 07 zeigePerson(); 08 } 09 } 01 private void btnLetzterActionPerformed(java.awt.event.ActionEvent evt) { 02 /* Positionszeiger setzen */ 03 this.position = personen.size() -1; 04 /* Personendaten anzeigen */ 05 zeigePerson(); 06 }

Der Umgang mit der aktuellen Position ist etwas schwierig. Sie müssen beachten, dass die Position niemals ungültig werden darf. Der Wert der Eigenschaft position darf also nicht kleiner als Null oder größer als die Anzahl der aktuell gespeicherten Elemente - 1 werden. Wenn Sie dies programmiert haben und das Programm testen, können Sie mit den Bewegungs-Schaltern bereits durch die gespeicherten Personen gehen. Nun müssen Sie nur noch ermöglichen, dass die geänderten Daten einer Person gespeichert werden, dass eine neue Person angelegt werden kann und dass die ganze Arbeit wieder in die Textdatei zurückgeschrieben wird.

8.5.3 Speichern der Änderungen des Anwenders Zum Speichern der im Formular geänderten Daten beschreiben Sie einfach das aktuelle Person-Objekt, ähnlich wie Sie beim Lesen das aktuelle Objekt ausgelesen haben (nur eben umgekehrt). Die Programmierung kann komplett in der Ereignisbehandlungsmethode des actionPerformedEreignisses des Schalters btnSpeichern erfolgen: 01 private void btnSpeichernActionPerformed(java.awt.event.ActionEvent evt) { 02 /* Aktuelle Person ermitteln */ 03 Person p = (Person)personen.get(this.position); 04 05 /* Inhalt der Textfelder in diese Person schreiben */ 06 p.Vorname = this.txtVorname.getText(); 07 p.Nachname = this.txtNachname.getText(); 08 p.Telefon = this.txtTelefon.getText(); 09 p.Handy = this.txtHandy.getText(); 10 p.EMail = this.txtEMail.getText(); 11 }

   

       

447

Sandini Bib

8.5.4 Neuanlegen einer Person Das Neuanlegen einer Person ist genauso einfach wie das Speichern, was übrigens vorwiegend daran liegt, dass wir objektorientiert programmieren. Sie müssen lediglich ein neues Person-Objekt an die Auflistung anfügen, den Positionszeiger auf dieses neue Element setzen und die Textfelder leeren. Das Leeren der Textfelder übernimmt einfach unsere Methode zeigePerson: 01 private void btnNeuActionPerformed(java.awt.event.ActionEvent evt) { 02 /* Neue Person an die Auflistung anfügen */ 03 personen.add(new Person()); 04 05 /* Positionszeiger auf den neuen Datensatz setzen */ 06 this.position = personen.size() - 1; 07 08 /* Person anzeigen, um die Felder zu leeren */ 09 zeigePerson(); 10 }

8.5.5 Speichern der Daten in der Textdatei Schließlich müssen Sie nur noch den Beenden-Schalter mit Funktionalität versehen und die Daten der Auflistung in die Textdatei zurückschreiben. In der actionPerformed-Ereignisbehandlungsmethode des Beenden-Schalters programmieren Sie nun lediglich einen Aufruf der dispose-Methode: 01 private void btnBeendenActionPerformed(java.awt.event.ActionEvent evt) { 02 /* Formular normal schließen, damit das Closed-Ereignis 03 aufgerufen wird */ 04 this.dispose(); 05 }

Das Speichern der Daten in die Textdatei übernimmt das Ereignis windowClosed des Formulars. Dieses Ereignis wird auch dann aufgerufen, wenn das Formular auf eine andere Weise als über den Beenden-Schalter geschlossen wird.

448

       

Sandini Bib

Sie sollten sich diese Technik gut merken, denn das ist auch in anderen Programmiersprachen enorm wichtig: Abschließende „Aufräumarbeiten“ sollten Sie in einem Formular immer in einer Methode vornehmen, die auf das Schließen des Formulars reagiert. Den Destruktor sollten Sie dazu übrigens nicht verwenden. Sie wissen nie genau, wann der Garbage Collector den Destruktor aufruft und ob dies überhaupt geschieht. Das Ereignis windowClosed wird hingegen immer und sofort aufgerufen, wenn das Formular geschlossen wird (und das gilt nicht nur für Java). Programmieren Sie das Speichern also in der Methode für das Ereignis windowClosed. Hier gehen Sie einfach die Auflistung durch und erzeugen aus jedem Objekt je eine Textzeile, die Sie in die Datei schreiben. Sie müssen das Schreiben der Datei wieder in eine Ausnahmebehandlung einfügen, aber das kennen Sie ja mittlerweile. Ich denke, der folgende Quellcode spricht für sich: 01 private void formWindowClosed(java.awt.event.WindowEvent evt) { 02 /* Daten in die Textdatei zurückschreiben */ 03 String dateiname = "C:\Projekte\Java\Personen.txt"; 04 05 /* Datei zum Beschreiben öffnen */ 06 try 07 { 08 /* BufferedWriter-Instanz zum Schreiben erzeugen */ 09 BufferedWriter br = new BufferedWriter( 10 new FileWriter(dateiname)); 11 12 /* Aktuelle Zeilen-Trennzeichen ermitteln */ 13 String zeilenTrennzeichen = System.getProperty( 14 "line.separator"); 15 16 /* Die einzelnen Personen durchgehen und in die 17 Datei schreiben */ 18 for (int i = 0; i < personen.size(); i++) 19 { 20 /* Referenz auf die Person holen */ 21 Person p = (Person)personen.get(i); 22 23 /* Eine Textzeile zusammensetzen */ 24 String zeile = p.Vorname + ";" + p.Nachname + ";" + 25 p.Telefon + ";" + p.Handy + ";" + p.EMail; 26 27 /* Diese Zeile in die Datei schreiben */ 28 br.write(zeile + zeilenTrennzeichen); 29 }

   

       

449

Sandini Bib

30 31 32 33 34 35 36 37 38 39 40 41 42 }

/* Textdatei schließen */ br.close(); } catch (Exception e) { /* Fehlermeldung anzeigen */ MessageDialog dialog = new MessageDialog(this, true, "Fehler beim Schreiben der Datei: " + e.getMessage(), "Fehler"); dialog.show(); }

8.6

Weitere interessante Features

Nun ist das Beispielprogramm in einer ersten Version fertig. Ich gehe nicht mehr weiter auf dieses Programm ein und überlasse Ihnen die Weiterentwicklung. Interessante Features wären noch: • eine Möglichkeit, Adressen zu suchen, • ein Zwang, beim Ändern und Anfügen mindestens den Vor- und den Nachnamen einzugeben, • eine Möglichkeit für den Anwender, die Daten zwischendurch in die Textdatei zu schreiben, • ein automatisches Speichern beim Wechsel des Datensatzes oder beim Schließen des Formulars, • das Speichern nur gefüllter Adressen in windowClosed (leere Adressen sollten nicht in die Textdatei gespeichert werden), • die Verwaltung der Datei im Ordner der Anwendung und das dynamische Ermitteln dieses Ordners im Programm (was eigenartigerweise in Java sehr kompliziert ist), • die Verwaltung des Dateinamens der Textdatei in einer Konfigurationsdatei (was ebenfalls recht kompliziert zu sein scheint), • oder die Möglichkeit, den Dateinamen beim Aufruf des Programms zu übergeben. Viel Spaß bei der weiteren Programmierung dieses Beispiels

450

       

-.

Sandini Bib

8.7

Zusammenfassung

In diesem Kapitel haben Sie erfahren, wie Sie eine bereits komplexere Anwendung in der Praxis entwickeln. Sie können nun Anwendungen schreiben, die Daten in Textdateien verwalten und dem Anwender diese Daten zur Bearbeitung zur Verfügung stellen. In diesem Zusammenhang haben Sie die Bedeutung von Auflistungen zur Speicherung von Massendaten erkannt. Sie haben zudem gelernt, dass das Abfangen von Ausnahmen in einer Anwendung enorm wichtig ist. Sie wissen nun, dass das Schreiben eigener Methoden in der Regel zur einer enormen Arbeitserleichterung im weiteren Verlauf der Programmierung führt und Ihr Programm nebenbei auch übersichtlicher macht. Schließlich haben Sie nun etwas mehr Erfahrung mit der ereignisorientierten Programmierung und können auch bereits eigene Dialogformulare gestalten und in Ihren Anwendungen einsetzen.

=XVDPPHQIDVVXQJ

451

Sandini Bib

Sandini Bib

9

Daten in Datenbanken verwalten

le

n e rn

Sie lernen in diesem Kapitel: • was eine Datenbank ist, • wie Sie Datenbanken mit der Datenbank-Standardsprache SQL erzeugen, • wie Sie in Java auf eine Datenbank zugreifen, • wie Sie Daten abfragen, • wie Sie Daten an eine Datenbank anfügen • und wie Sie Daten in einer Datenbank ändern und löschen. Das Kapitel soll Ihnen einen Einblick in die Welt der Datenbanken verschaffen. Ich kann hier nur absolut grundlegend auf Datenbanken eingehen. Das Thema ist an sich so komplex, das ganze Bücher darüber geschrieben werden. Ich zeige aber, was eine Datenbank prinzipiell ist und wie Sie mit der Standard-Datenbank-Manipulations- und Abfragesprache SQL (Structured Query Language) prinzipiell arbeiten. Da Java einen sehr einfachen Zugriff auf Datenbanken ermöglicht (und weil die Datenbank-Komponenten in der Personal- bzw. Open-Edition von Delphi und Kylix fehlen), verwende ich Java für das Beispielprogramm. Das Prinzip ist aber in anderen Sprachen dasselbe. Sie nutzen lediglich andere Objekte zum Zugriff. Als Datenbank verwende ich MySQL, ein frei verfügbares Datenbanksystem der Firma MySQL AB, das unter Windows und Linux ausgeführt werden kann.

453

Sandini Bib

Die in diesem Kapitel am Beispiel beschriebenen Grundprinzipien des Datenbankdesigns und die verwendeten SQL-Anweisungen kann ich im Einzelfall nicht umfassend erläutern. Ich will aber erreichen, dass Sie wissen, was eine Datenbank ist und wie Sie mit SQL prinzipiell Datenbanken erzeugen, abfragen und manipulieren können. Wenn Sie das alles, was hier beschrieben wird, besser verstehen oder alle Möglichkeiten kennen lernen wollen, müssen Sie noch andere Literatur zu Rate ziehen. Das Buch „SQL lernen“ aus der Lernen-Reihe von Addison-Wesley behandelt z. B. SQL wesentlich ausführlicher, als ich es hier kann.

9.1

Was ist eine Datenbank?

Eine Datenbank speichert Daten (was auch sonst ...). Diese Speicherung erfolgt aber, im Gegensatz zu Dateien, auf eine sehr effiziente und flexible Weise. Moderne Datenbanksysteme haben keine Probleme damit, Millionen von Datensätzen zu verwalten. Über eine spezielle Abfragesprache (meist SQL) können Sie gezielt Daten aus der Datenbank auslesen. Und das wird in der Regel mit einer sehr hohen Geschwindigkeit ausgeführt, auch wenn sehr viele Datensätze gespeichert sind. Daneben ermöglicht eine Datenbank aber auch das einfache Anfügen, Ändern und Löschen von Daten. An eine Datenbank können Sie z. B. ohne Probleme einen einzelnen Datensatz anfügen. Die Arbeit des Abfragens, des Hinzufügens, des Änderns und des Löschens übernimmt die Datenbank für Sie. Wenn Sie hingegen mit einer einfachen Datei zur Speicherung von Daten arbeiten würden, müssten Sie das alles selbst programmieren. OK, in Kapitel 8 erschien dies relativ einfach. Wenn Sie aber mehrere Tausend oder sogar Millionen Datensätze verwalten, können Sie diese nicht mehr so einfach in den Speicher lesen und dort bearbeiten. Dann müssten Sie mit den recht komplexen Random-Dateien arbeiten. Datenbanken erlauben aber noch wesentlich mehr als einfache Dateien. So können Sie die Daten beim Einlesen nach allen denkbaren Kriterien sortieren, die Daten verschiedener Bereiche miteinander in Beziehung setzen und die so in Beziehung stehenden Daten gezielt gemeinsam abfragen.

9.1.1 Datenbanken, Datenbanksysteme und Datenbankmanagementsysteme Eine Datenbank ist im Prinzip eine Datei oder eine Sammlung von Dateien, in denen Daten gespeichert sind. Mit diesen Dateien können Sie direkt nichts anfangen, weil Sie nicht wissen, auf welche Weise die Daten dort verwaltet werden. Ausnahmen bilden Textdatenbanken wie

454

      

Sandini Bib

die, die Sie in Kapitel 8 kennen gelernt haben. Die dabei verwendeten Dateien können Sie mit den Möglichkeiten der Programmiersprache bearbeiten. Aber solche „unechten“ Datenbanken besitzen nur sehr einfache Möglichkeiten (im Prinzip die, die Sie selbst programmieren). Echte Datenbanken sind hingegen immer Bestandteil von so genannten Datenbanksystemen (DBS). Ein Datenbanksystem besteht aus der Datenbank (also aus den Dateien, die die Daten speichern) und einem so genannten Datenbankmanagementsystem (DBMS). Das DBMS übernimmt das Management der Dateien. Wenn Sie z. B. einen Datensatz hinzufügen, senden Sie dem DBMS einen entsprechenden Befehl. Dieses übernimmt dann das Hinzufügen des Datensatzes in die entsprechende Datei. Ähnliches passiert, wenn Sie Daten abfragen. Ihr Programm sendet dem DBMS einen Befehl, dieses interpretiert den Befehl, liest die Datensätze aus, die zu Ihrer Abfrage passen, und liefert diese zurück.

Programm sendet Befehl zum Hinzufügen

DBMS

fügt Datensätze an

Datenbank

Datenbanksysteme bestehen aus Datenbanken und DBMS

Programm sendet Befehl zum Auslesen

liefert Datensätze zur Bearbeitung

DBMS liest Datensätze aus Datenbank

Abbildung 9.1: Darstellung des Hinzufügens und des Auslesens von Daten in einem DBS

Sie können aber z. B. in einer Artikeldatenbank auch den Befehl senden, die Preise aller Artikel, die der Kategorie „Prozessoren“ angehören, um zehn Prozent zu erhöhen. Das DBMS interpretiert diesen Befehl, sucht alle Artikel heraus, die der angegebenen Bedingung entsprechen, und erhöht deren Feld Preis um zehn Prozent. Es nimmt Ihnen und dem Programm damit eine Menge an Arbeit ab. Moderne Datenbankmanagementsysteme sind selbstständige Programme, die allerdings im Hintergrund (ohne eine Oberfläche) ausgeführt werden (unter Windows normalerweise als Dienst und unter Linux als Daemon). Einfache DBMS liegen aber auch schon einmal le-

:DV LVW HLQH 'DWHQEDQN"

455

Sandini Bib

diglich als Bibliothek vor, die von einem Programm genutzt werden kann. Das hängt ganz von der Art des Datenbanksystems ab. Für Sie als Programmierer spielt das allerdings keine große Rolle, denn Sie nutzen in modernen Programmiersprachen immer dieselben Objekte zum Zugriff. Was im Hintergrund passiert, bekommen Sie gar nicht mit. Lediglich aus der Sicht der Performance ist der Unterschied wichtig: DBMS, die als Programm ausgeführt werden, können Daten effizient im Arbeitsspeicher zwischenspeichern („cachen“) und erhöhen damit bei wiederholten Zugriffen die Performance um ein Vielfaches. Einfache DBMS, die nicht als Programm ausgeführt werden, sind z. B. Access und dBASE. Als Programm werden die Datenbankmanagementsysteme von Oracle, MySQL, vom Microsoft SQL Server und von Borland Interbase ausgeführt.

9.1.2 Objektorientierte Datenbanksysteme Die modernsten Datenbanksysteme sind objektorientiert. Diese Datenbanksysteme sind eigentlich ganz einfach (weil objektorientiert). Die Daten werden in Form von Auflistungen (!) von Objekten verwaltet. Wenn Sie mit einem Programm auf solche Daten zugreifen, fragen Sie Daten ab (z. B. alle Kunden, die in Köln wohnen) und erhalten eine Auflistung von Kundenobjekten zurück, mit der Sie im Programm auf die gewohnte Weise arbeiten können. Die Klassen der Auflistungen und der gespeicherten Objekte sind dabei allerdings speziell und werden vom DBMS zur Verfügung gestellt. Mit einem speziellen Administrationswerkzeug oder über eine spezielle Datenbank-Befehlssprache erzeugen Sie bei der Gestaltung einer Datenbank die entsprechenden Klassen innerhalb der Datenbank. Die Arbeit mit diesen Auflistungen und Objekten unterscheidet sich aber nicht von der mit normalen Objekten. Sie können die Auflistung durchgehen, gezielt über den Schlüssel nach Daten suchen, Objekte auslesen und diese auch verändern. Im Gegensatz zu der Anwendung aus Kapitel 8 werden Ihre Änderungen aber in der Regel sofort und automatisch in die Datenbank geschrieben. Sie haben also nur sehr wenig Arbeit damit. Ein wesentliches Feature von OOPDatenbanken ist, dass einzelne Objekte ohne Probleme direkt mit anderen Objekten in Beziehung gesetzt werden können. In einer Bestelldatenbank können Sie so ein Kunde-Objekt z. B. mit den Bestell-Objekten in Beziehung setzen, die die Bestellungen des Kunden speichern. Die Auflösung dieser Beziehungen im Programm ist dann mehr als einfach. Sie müssen lediglich die Eigenschaft Bestellungen des Kunde-Objekts auslesen. Diese Eigenschaft wird dann auf eine Auflistung verweisen, die Sie ganz einfach durchgehen können und an die Sie genauso einfach neue Datensätze (hier: Bestellungen) anfügen können.

456

      

Sandini Bib

Leider sind objektorientierte Datenbanksysteme noch lange kein Standard. Die wenigsten Firmen setzen solche Systeme heute schon ein (obwohl es diese seit mehreren Jahren gibt). Deshalb gehe ich nicht weiter darauf ein. Wenn Sie einmal eines der besten OOP-Datenbanksysteme kennen lernen wollen, schauen Sie bei www.cache.de vorbei. Diese Datenbank von InterSystems ist sehr einfach zu programmieren, dabei aber in der Lage sehr große Datenmengen mit einer hohen Geschwindigkeit zu verarbeiten. Viele große Firmen setzen dieses System ein. Als Programmierer erhalten Sie eine kostenlose Entwicklerversion.

9.1.3 Relationale Datenbanksysteme Relationale Datenbanksysteme verwalten keine Objekte, sondern speichern die Daten in logischen Tabellen. Auf diese DBS gehe ich näher ein, weil sie zurzeit noch der aktuelle Standard sind. Ich will relationale Datenbanken direkt am Beispiel erläutern. Eine einfache relationale Datenbank, die Buchdaten speichern soll, besteht aus zwei Tabellen. Eine Tabelle speichert die Daten der Bücher, eine weitere die der Autoren. Abbildung 9.2 stellt diese Datenbank dar.

Abbildung 9.2: Zwei Tabellen einer einfachen relationalen Datenbank

Diese Tabellen stellen lediglich eine logische Sicht auf die Daten dar. Das DBMS speichert die Daten auf eine vollkommen andere Weise, die uns in der Regel aber unbekannt ist (weil es sich dabei meist um ein gut gehütetes Geheimnis des DBMS-Herstellers handelt). Die logische Sicht auf relationale Datenbanken ist aber bei allen relationalen DBMS gleich: Eine Tabelle verwaltet einzelne Datensätze, die aus einzelnen Feldern bestehen. Im Prinzip können Sie sich einen Datensatz als Objekt vorstellen, das ja auch aus einzelnen Feldern (den Eigenschaften) besteht. Leider sind die einzelnen Datensätze in relationalen Datenbanken nicht wirklich Objekte, was die Arbeit mit den Daten ein wenig erschwert.

:DV LVW HLQH 'DWHQEDQN"

457

Sandini Bib

Beziehungen Die beiden Tabellen des Beispiels stehen miteinander in Beziehung. Erkennen Sie diese Beziehung? Jeder Autor besitzt eine eindeutige Id. In der Bücher-Tabelle ist diese Id angegeben. Damit wird jedem Buch ein Autor zugewiesen. Die Daten der Autoren und der Bücher werden separat gespeichert (eben in zwei unterschiedlichen Tabellen). Damit wird erreicht, dass Daten nicht redundant (mehrfach) gespeichert werden. Würde die Datenbank keine Autoren-Tabelle besitzen und die Daten des jeweiligen Autors in der Bücher-Tabelle verwalten, würden Daten mehrfach gespeichert werden. Natürlich können Sie auch solche Datenbanken gestalten. Dann haben Sie aber mit massiven Problemen zu kämpfen, wenn Daten geändert werden müssen. Wenn sich beispielsweise der Nachname eines Autors ändert (was zugegebenermaßen in der Praxis nicht vorkommt), müssten Sie den Namen in allen Buch-Datensätzen ändern, die diesen Autor speichern. Eine Verwaltung der Autoren in einer separaten Tabelle macht das Ändern aber sehr einfach. Da die Bücher-Tabelle nur die Id (den Schlüssel) des Autors speichert, müssen dort gar keine Änderungen vorgenommen werden.

9.1.4 SQL Zur Abfrage und Manipulation relationaler Datenbanken stellen alle DBMS eine spezielle Sprache zur Verfügung. Die meisten verwenden dabei SQL (Structured Query Language1). SQL ist eine ursprünglich von E. F. Codd, einem damaligen Mitglied des IBM Research Laboratory, entwickelte und seit einigen Jahren vom ANSI2-Institut standardisierte Sprache, die speziell für relationale Datenbanksysteme entwickelt wurde. SQL stellt mehrere Befehle zur Verfügung, die Sie in Ihren Programmen über spezielle Objekte an das DBMS senden können. Wenn Sie z. B. in der Beispieldatenbank alle Bücher des Autors mit der Id 1 sortiert nach Titel auslesen wollen, verwenden Sie den folgenden SQL-Befehl: SELECT * FROM Bücher WHERE Autor = 1 ORDER BY Titel;

Einen neuen Autor anfügen können Sie z. B. so: INSERT INTO Autoren (Id, Vorname, Nachname) VALUES (4, 'Peter', 'Hoeg');

Das DBMS empfängt diesen Befehl, interpretiert ihn und führt ihn aus. Im ersten Fall würde das DBMS eine Liste aller Bücher zurückgeben, die im Feld Autor den Wert 1 gespeichert haben. Im zweiten Fall würde es einen neuen Datensatz in der Autoren-Tabelle anlegen.

458

1.

Strukturierte Abfragesprache

2.

American National Standards Institute, www.ansi.org

      

Sandini Bib

SQL ist sehr mächtig. Ganze Bücher beschäftigen sich mit dieser Sprache. Sie können über SQL nicht einfach nur Daten gezielt und sortiert abfragen, anfügen, verändern oder löschen. Sie können auch in SQLAbfragen direkt arithmetische Berechnungen vornehmen und dabei spezielle SQL-Funktionen (wie z. B. SUM zur Berechnung der Summe eines Feldes) aufrufen. Ein wesentliches Feature ist aber die Möglichkeit, die Beziehungen zwischen Tabellen in einer SQL-Anweisung auflösen zu können. Wenn Sie z. B. alle Bücher auslesen und zu jedem Buch die vollen Autor-Informationen erhalten wollen, verwenden Sie die folgende (anfänglich etwas schwer zu verstehende) SQL-Anweisung:

Auflösen von Beziehungen über SQL

SELECT ISBN, Titel, Vorname, Nachname FROM Bücher INNER JOIN Autoren ON Bücher.Autor = Autoren.AutorId;

Diese Abfrage fragt einige der Felder der Tabellen Bücher und Autoren ab. Die Abfrage setzt die Tabellen dabei direkt über die INNER JOIN-Klausel in Beziehung, wobei die Felder angegeben werden, über die die Beziehung realisiert ist. Das Ergebnis dieser Abfrage ist eine temporäre (nur im Arbeitsspeicher existierende) Tabelle, die die Daten der beiden Datenbanktabellen zusammengefasst darstellt (Abbildung 9.3).

Abbildung 9.3: Das Ergebnis einer Abfrage, die zwei Tabellen kombiniert

Im Ergebnis der Abfrage kommen Daten nun zwar redundant vor. Das ist aber absolut in Ordnung, denn das Ergebnis ist nur temporär. Das Ergebnis kann auf eine sehr einfache Weise im Programm verarbeitet werden. Das Programm kann z. B. eine Liste der Bücher mit den AutorenNamen drucken. Wie das prinzipiell geht, erfahren Sie im weiteren Verlauf dieses Kapitels. Dass die Beziehungen zwischen Tabellen in Abfragen explizit aufgelöst werden müssen, ist übrigens ein großes Problem relationaler Datenbanken. In objektorientierten Datenbanken müssen Sie so etwas nicht machen. In einer solchen Datenbank würden Sie mit einer SQL ähnlichen Sprache einfach eine Auflistung aller Bücher abfragen (die eventuell einem bestimmten Kriterium entsprechen). Die einzelnen BuchObjekte, die Sie damit erhalten, würden einfach eine Eigenschaft Autor besitzen, die auf ein Autor-Objekt verweist. Wenn Sie die Buch-Auflistung durchgehen, können Sie über die Autor-Eigenschaft sofort auch die Daten des Autors auslesen.

:DV LVW HLQH 'DWHQEDQN"

459

Sandini Bib

9.2

Datenbankdesign light: Erzeugen der Beispieldatenbank mit MySQL

Nun wollen wir direkt zur Praxis übergehen. Sie erzeugen dazu mit SQL zunächst die Beispieldatenbank, die ich in Abbildung 9.2 auf Seite 457 vorgestellt habe.

9.2.1 MySQL Als Datenbanksystem verwende ich MySQL. MySQL ist ein DBMS, das nur unter bestimmten Umständen kostenpflichtig ist. In den meisten Fällen können Sie MySQL kostenfrei nutzen. Sie finden MySQL auf der Buch-CD oder im Internet unter der Adresse www.mysql.com. Dort können Sie sich auch die Lizenzbestimmungen anschauen. Die Installation und den Start von MySQL beschreibe ich aus Platzgründen nicht hier, sondern im separaten Artikel „Installation“, den Sie auf der Buch-CD oder unter der Adresse www.juergen-bayer.net/ buecher/programmierenlernen/artikel/installation.html finden. Ich gehe im weiteren Verlauf dieses Kapitels davon aus, dass MySQL installiert und erfolgreich gestartet ist. Die MySQL-Dokumentation finden Sie im Programmordner im Ordner docs. Öffnen Sie dort die Datei index_toc.html. Eine durchsuchbare Version finden Sie im Internet unter der Adresse www.mysql.com/doc/ en/index.html.

9.2.2 Anlegen einer Datenbank über den MySQL-Monitor MySQL beinhaltet mit dem MySQL-Monitor ein einfaches Administrationsprogramm, das Sie an der Konsole aufrufen können, um eine Datenbank zu administrieren. Dieses Programm starten Sie normalerweise unter Angabe eines Benutzernamens und eines Passworts: mysql –u Benutzername Passwort

MySQL ist ein geschütztes Datenbanksystem. Normalerweise müssen Sie sich immer mit einem Benutzernamen und einem Passwort einloggen. Diese Namen und Passwörter werden in der Systemdatenbank mysql (in der Tabelle user) verwaltet und können vom Administrator in der Systemdatenbank oder über spezielle SQL-Anweisungen bearbeitet werden. Nach der MySQL-Installation existiert lediglich ein Benutzer, der root. Dieser Benutzer ist unter MySQL der Super-Administrator, der

460

      

Sandini Bib

alle verfügbaren Rechte besitzt. Wenn Sie beim Start von mysql keinen Benutzernamen eingeben, werden Sie automatisch als der aktuelle Systembenutzer eingeloggt. Da alle Benutzer des lokalen Systems per Voreinstellung volle Rechte besitzen (was in Systemen, die in Firmen eingesetzt werden, natürlich ein riesengroßes Sicherheitsloch darstellt), funktioniert der Start des SQL-Monitors auch ohne Angabe des Benutzers: mysql

Im nun geöffneten Konsolenprogramm können Sie Befehle eingeben. Neben den SQL-Befehlen sind auch eine Menge spezieller MySQLBefehle möglich. Der Befehl show databases; zeigt z. B. alle aktuell bestehenden Datenbanken an. Nach der Installation sind das die Datenbanken mysql und test.

Abbildung 9.4: Abfrage der aktuell vorhandenen Datenbanken im MySQL-Monitor

In der Datenbank mysql verwaltet MySQL alle Einstellungen des Systems. Die Datenbank test ist eine leere Testdatenbank. Datenbank anlegen Die Datenbank legen Sie nun über eine SQL Anweisung an. Beachten Sie, dass MySQL bei Datenbanknamen Groß- und Kleinschreibung unterscheidet (was bei anderen Namen aber nicht der Fall ist): CREATE DATABASE Buchdaten;

SQL-Anweisungen werden (ähnlich wie Programmanweisungen in Object Pascal und Java) immer mit einem Semikolon abgeschlossen.

            

461

Sandini Bib

Üblicherweise werden die Schlüsselwörter einer SQL-Anweisung großgeschrieben. Obwohl alle DBMS, die ich kenne, auch mit kleingeschriebenen SQL-Anweisungen zurechtkommen, halte ich mich in diesem Buch an den Brauch. Anweisungen, die nicht zum StandardSQL gehören, sondern eine spezielle Erweiterung von MySQL sind, schreibe ich zur Unterscheidung klein. Meldet das Programm nach der Ausführung dieser Anweisung „OK“, können Sie nun zu dieser Datenbank wechseln: USE Buchdaten;

Das Programm meldet „Database changed“, wenn der Wechsel erfolgreich war. Tabellen werden über CREATE TABLE angelegt

Datenfelder besitzen einen Datentyp

Nun legen Sie die Tabellen an. Dazu verwenden Sie die SQL-Anweisung CREATE TABLE. Tabellen bestehen aus Datensätzen, die wiederum aus einzelnen Feldern bestehen. Ein Feld speichert einen Teil des Datensatzes, der nicht weiter zerlegt werden kann. Eine Autoren-Tabelle besteht z. B. aus den Feldern Id, Vorname und Nachname. Der Vor- und der Nachname werden in separaten Feldern verwaltet. Damit ist sichergestellt, dass die Tabelle mit der maximal möglichen Flexibilität bearbeitet werden kann. So können Sie z. B. beim Abfragen der Daten nach dem Nachnamen sortieren. Oder Sie können alle Autoren abfragen, deren Nachname Adams ist. Würden Sie den Vor- und den Nachnamen hingegen in einem einzigen Feld verwalten, wäre so etwas nicht möglich. Dieses separate Speichern einzelner Informationen gehört übrigens schon zu den wichtigen Prinzipien des Datenbank-Designs. Ein Datenfeld besitzt ähnlich einer Variable in einem Programm einen Datentyp. Die Datentypen von Datenfeldern gleichen den Datentypen eines Programms, werden aber anders benannt. Die Namen sind zwar vom ANSI-Komitee festgelegt, viele Datenbanksysteme verwenden aber vom Standard abweichende Namen. In der Praxis bleibt Ihnen nichts weiter übrig, als in der Dokumentation des DBS nachzuschlagen. In MySQL verwenden Sie für Integer-Felder den Datentyp int und für Textfelder den Datentyp varchar. varchar steht für „variable char“. Ein solches Feld ist in der Datei immer nur so groß wie die darin gespeicherten Daten und belegt deswegen nur den minimal benötigten Speicherplatz. Sie müssen bei der Erzeugung eines solchen Feldes die maximale Größe der späteren Daten angeben. In der Praxis können Sie ruhig sehr große Werte verwenden. Nur dann, wenn Sie erreichen wollen, dass die Tabelle in einem solchen Feld nicht mehr als eine bestimmte Anzahl Zeichnen speichert, sollten Sie die Anzahl niedrig definieren. Stellen Sie aber lieber zu viel als zu wenig mögliche Zeichen ein, dann haben Sie bei

462

      

Sandini Bib

der Benutzung der Datenbank keine Probleme zu erwarten. MySQL erlaubt in einem solchen Feld maximal 255 Zeichen. Die Autor-Tabelle wird nun folgendermaßen angelegt: CREATE TABLE Autoren ( Id int PRIMARY KEY NOT NULL, Vorname varchar(255) NOT NULL, Nachname varchar(255) NOT NULL);

Zum Anlegen der Tabelle können Sie die einzelnen Zeilen separat eingeben. Der MySQL-Monitor wartet mit der Ausführung des Befehls, bis Sie das abschließende Semikolon eingegeben haben, und ermöglicht nach einer eingegebenen und bestätigten Zeile die Eingabe weiterer Zeilen. Wenn das Programm dann nach der letzten Zeile „Query OK“ meldet, wurde die Tabelle angelegt. Sie können SQL-Anweisungen auch in eine Textdatei ablegen und diese im MySQL-Monitor in einem Rutsch über den Befehl source Dateiname ausführen. Das Feld Id wird im Beispiel mit einer besonderen Bedeutung versehen. Durch die Angabe von PRIMARY KEY wird das Feld zum Primärschlüssel der Tabelle. Ein Primärschlüssel hat die Bedeutung, dass er einen Datensatz eindeutig identifiziert. Über die Autor-Id kann ein Autor eindeutig ermittelt werden, weil die Id (zunächst theoretisch) eindeutig ist. In der Datenbank sorgt die Festlegung, dass das Feld der Primärschlüssel ist, automatisch für diese Eindeutigkeit. Der Primärschlüssel ist ein sehr wichtiges Konzept relationaler Datenbanken. Jede Tabelle sollte einen Primärschlüssel besitzen (was aber niemand erzwingt). Durch das Vorhandensein eines Primärschlüssels vermeiden Sie viele Probleme und erleichtern Programmen den Zugriff auf die Daten.

Der Primärschlüs-

Im Beispiel definiere ich alle Felder zusätzlich über die Angabe von NOT NULL so, dass das jeweilige Feld beim Hinzufügen oder Ändern von Datensätzen nicht leer sein darf. Datenbankfelder, die nicht mit NOT NULL de-

Verhindern, dass

sel identifiziert einen Datensatz eindeutig

Felder leer bleiben

finiert wurden, zwingen den Anwender bzw. das Programm nicht, beim Anlegen oder Ändern eines Datensatzes Daten abzulegen. Das Feld kann in diesem Fall leer bleiben und speichert den speziellen Datenbankwert Null. Diesen Wert sollten Sie nicht mit dem Nullzeiger bei der objektorientierten Programmierung verwechseln. Der Wert Null steht in einer Datenbank dafür, dass ein Feld leer ist. Leere Felder sind in der Praxis meist schwer zu handhaben. Normalerweise sollten Sie dafür sorgen, dass alle Felder auch mit Daten versorgt werden, wenn Datensätze angefügt oder geändert werden. In einigen Fällen ist es aber auch sinnvoll, leere Felder zuzulassen.

            

463

Sandini Bib

Unter MySQL funktioniert die Definition von NOT NULL nicht so, wie es dem allgemeinen Standard entspricht. MySQL legt nämlich ungefragt für alle Felder eine Defaultwert-Einstellung an (die auch in anderen Datenbanken möglich ist, die Sie dort über DEFAULT Wert für jedes Feld aber explizit festlegen müssen). Bei Zahlfeldern wird 0 als Voreinstellung verwendet, bei Textfeldern eine leere Zeichenkette. Wenn Sie einen Datensatz anfügen, der einige Felder nicht beschreibt, verwendet ein DBMS für die unbeschriebenen Felder die definierten Defaultwerte. Diese Felder sind dann nicht leer. Weil MySQL grundsätzlich Defaultwerte vergibt, erzeugen solche Anweisungen keinen Fehler. Wieder abweichend vom üblicherweise verwendeten Standard verwendet MySQL diesen Defaultwert sogar beim Ändern von Daten, wenn für einzelne Felder der Wert Null übergeben wird. Etwas eigenartig und schwer zu verstehen ist dabei die Tatsache, dass eine leere Zeichenkette nicht Null ist: Eine leere Zeichenkette ist tatsächlich ein normaler Wert (der allerdings leer ist). Sie beginnen hier übrigens damit, die Datenbank so zu gestalten, dass diese möglichst keine ungültigen Daten speichert. Sie legen die dazu notwendigen Regeln direkt in der Datenbank fest. Verletzt ein Programm oder ein Anwender eine dieser Regeln, erzeugt die Datenbank eine Ausnahme und ändert nichts an den Daten. Damit erreichen Sie, dass alle Programme und alle Anwender, die diese Datenbank verwenden, sich an die Regeln halten müssen. Nun legen Sie noch die Bücher-Tabelle an: CREATE TABLE Buecher ( ISBN varchar(255) PRIMARY KEY NOT NULL, Titel varchar(255) NOT NULL, Autor int NOT NULL, Verliehen_an varchar(255));

Der Name von Tabellen und Feldern muss den üblichen Regeln entsprechen. Das Prinzip dabei ist: Beginnen Sie den Namen mit einem Buchstaben. Dann können Zahlen und der Unterstrich folgen. Verwenden Sie möglichst keine Umlaute, weil diese – aufgrund unterschiedlicher Zeichensätze – zu massiven Problemen führen könnten. Die BücherTabelle heißt deswegen Buecher. Die ISBN-Nummer ist in dieser Tabelle der Primärschlüssel. Ich habe für dieses Feld den Datentyp varchar gewählt, weil ISBN-Nummern häufig auch den Buchstaben X beinhalten.

464

      

Sandini Bib

Das Feld Autor besitzt den Datentyp int. Es ist sehr wichtig, dass dieses Feld, über das ja die Beziehung zur Autorentabelle aufgelöst wird, denselben Datentyp aufweist wie das Primärschlüsselfeld der AutorenTabelle. Für dieses Feld habe ich ebenfalls NOT NULL angegeben. Damit ist gewährleistet, dass für ein Buch auch ein Autor angegeben werden muss. Nun kommt der vielleicht etwas schwierigere (aber auch letzte) Teil. Sie können die Beziehung, zwischen den Tabellen, die zurzeit lediglich theoretisch besteht, nämlich in der Datenbank festlegen (was allerdings leider in MySQL nicht funktioniert, wie ich unten noch erläutere). Dazu ändern Sie die Struktur der Bücher-Tabelle ab und fügen einen so genannten Fremdschlüssel (Foreign Key) hinzu. Ein Fremdschlüssel referenziert den Primärschlüssel einer anderen Tabelle (also in unserem Fall der Autoren-Tabelle). In der Bücher-Tabelle muss dieser Fremdschlüssel auf dem Feld Autor liegen und das Feld Id der Autoren-Tabelle referenzieren. Ein Fremdschlüssel gehört zur Gruppe der Einschränkungen (Constraints). Eine Einschränkung schränkt die Möglichkeiten der Eingabe in einem Feld ein. Ein Fremdschlüssel bewirkt, dass im Feld Autor der Bücher-Tabelle nur Ids von Autoren eingetragen werden können, die in der Autoren-Tabelle existieren. Damit sichern Sie ab, dass die BücherTabelle keine Autor-Id speichert, die nicht existiert. Neben Fremdschlüssel-Einschränkungen existieren noch andere, wie z. B. Überprüfungs-Einschränkungen, die den Wert eines Feldes bedingungsabhängig überprüfen und nur bestimmte Werte zulassen. Auf diese Einschränkungen gehe ich aber nicht weiter ein.

Definieren der Beziehung

Zum Anlegen der Fremdschlüssel-Einschränkung für die Bücher-Tabelle verwenden Sie nun die folgende Anweisung: ALTER TABLE Buecher ADD CONSTRAINT fk_buecher_autoren FOREIGN KEY (Autor) REFERENCES Autoren (Id);

            

465

Sandini Bib

MySQL ist – anders als viele andere Datenbanksysteme – in der Lage, Tabellen in verschiedenen Datenbankformaten zu speichern. Beim Anlegen einer Tabelle können Sie das Format über die TYPE-Anweisung angeben. Wenn Sie nichts angeben, wird das Standardformat verwendet. Ich will hier nicht näher auf Datenbankformate eingehen. Für Sie ist lediglich wichtig, dass die unterschiedlichen Formate unterschiedliche Features unterstützen. Und dazu gehören leider auch Fremdschlüssel. Diese werden nämlich in MySQL eigenartigerweise (Fremdschlüssel sind ein wichtiges Konzept relationaler Datenbanken) nur im Datenbankformat InnoDB unterstützt. Leider wird dieses Datenbankformat, das einen speziellen Server benötigt, wiederum nicht unter allen Betriebssystemen unterstützt (z. B. nicht unter Windows 2000). Da die Konfiguration zudem recht kompliziert und fehlerträchtig ist, habe ich darauf verzichtet, dieses Format zu verwenden. Sie müssen also damit leben, dass Ihre Datenbank zurzeit noch in der Bücher-Tabelle das Eintragen von Autoren erlaubt, die gar nicht existieren. Ich wollte das wichtige Anlegen eines Fremdschlüsssels – das in allen anderen mir bekannten DBS funktioniert – aber wenigstens zeigen. Überprüfen der Datenbank Zum Überprüfen Ihrer Arbeit können Sie den Befehl show columns from Tabellenname; aufrufen. Der MySQL-Monitor zeigt die Struktur der angegebenen Tabelle in einer übersichtlichen Form an (Abbildung 9.5).

Abbildung 9.5: Anzeige der Struktur der Tabellen der Buchdatenbank unter Windows

466

      

Sandini Bib

Datenbank löschen Wenn Sie eine Datenbank löschen wollen, weil Sie z. B. beim Anlegen Fehler gemacht haben, verwenden Sie einfach die DROP DATABASE-Anweisung: DROP DATABASE Buchdaten;

Beenden Mit dem Befehl quit können Sie den MySQL-Monitor beenden.

9.3

Daten mit SQL bearbeiten

Damit Sie Ihre Datenbank gleich ein wenig mit Leben füllen, zeige ich nun, wie Sie mit SQL Daten anfügen, abfragen, ändern und löschen. Starten Sie den MySQL-Monitor und wechseln Sie zur Buchdatenbank: USE Buchdaten;

9.3.1 Daten anfügen Über die SQL-Anweisung INSERT INTO können Sie nun Datensätze anfügen. Dabei verwenden Sie das folgende Schema: INSERT INTO Tabellenname (Feldliste) VALUES (Wertliste);

In der Feldliste geben Sie alle Felder der Tabelle an, in die Sie Daten einfügen wollen. Sie müssen nicht in alle Felder Daten einfügen, weswegen die Angabe einer Feldliste sinnvoll ist. Einzelne Feldnamen trennen Sie durch Kommata. In der Wertliste geben Sie dann die Werte der einzelnen Felder an. Dabei verwenden Sie Literale, die Sie von der Programmierung her gewohnt sind: Zahlen werden in der englischen Schreibweise angegeben, Zeichenketten können Sie in einfache Apostrophe oder in Anführungszeichen einschließen. Die Reihenfolge und die Anzahl der Feldwerte muss natürlich der Reihenfolge und Anzahl der in der Feldliste angegebenen Felder entsprechen. So können Sie nun an die Autoren-Tabelle drei Datensätze anfügen: INSERT INTO Autoren (Id, Vorname, Nachname) VALUES (1, "Douglas", "Adams"); INSERT INTO Autoren (Id, Vorname, Nachname) VALUES (2, "John", "Irving"); INSERT INTO Autoren (Id, Vorname, Nachname) VALUES (3, "Matt", "Ruff");

   

467

Sandini Bib

Und natürlich auch Bücher an die Bücher-Tabelle: INSERT INTO Buecher (ISBN, Titel, Autor) VALUES ("3453209613", "Per Anhalter durch die Galaxis", 1); INSERT INTO Buecher (ISBN, Titel, Autor) VALUES ("3453210727", "Der lange dunkle Fünfuhrtee der Seele", 1); ...

Auf der Buch-CD finden Sie im Ordner \Beispiele\Kapitel 09\B Daten anfügen eine Textdatei, die die entsprechenden Befehle beinhaltet. Beim Anlegen der Bücher verzichte ich auf das Angeben des Wertes für das Feld Verliehen_an, weil dieses Feld zurzeit noch nicht belegt werden soll. Fehler bei der Verletzung des Primärschlüssels

Wenn Sie nun versuchen, einen Autor oder ein Buch mit einem existierenden Primärschlüssel anzulegen, resultiert dies in einem Fehler (Abbildung 9.6).

Abbildung 9.6: MySQL meldet unter Linux einen Fehler beim Anlegen eines vierten Autors mit einer Id, die bereits existiert.

Das DBMS verhindert das Anfügen, weil dadurch die Eindeutigkeit des Primärschlüssels verloren gehen würde. Und das ist auch gut so. In einem Programm würde ein solcher Fehler eine Ausnahme erzeugen, die Sie natürlich abfangen können. Nun, da Sie bereits Daten anfügen können, sollten Sie diese auch abfragen können.

468

      

Sandini Bib

9.3.2 Daten abfragen Das Abfragen von Daten ist prinzipiell einfach. Die SELECT-Anweisung, die Sie dazu benutzen, ist aber sehr komplex. Dafür können Sie damit schon bei der Abfrage sehr viele Probleme lösen. Ich zeige hier nur eine Möglichkeit, Daten gezielt abzufragen und dabei zu sortieren. Die einzelnen Bestandteile der SELECT-Anweisung sind, bis auf SELECT und FROM, optional. Das (vereinfachte) Schema dieser Anweisung ist das folgende: SELECT Feldliste FROM Tabelle [WHERE Bedingung] [ORDER BY Feldliste];

Zumindest müssen Sie eine Liste abzufragender Felder und den Tabellennamen angeben. Die folgende Anweisung liefert die Felder Vorname und Nachname für alle Datensätze der Autoren-Tabelle: SELECT Vorname, Nachname FROM Autoren;

Wenn Sie alle Felder abfragen wollen, können Sie auch in der Feldliste einen Stern angeben: SELECT * FROM Autoren;

Die Angabe nur einzelner Felder ist effizienter, da das Ergebnis der Abfrage dann kleiner ist. Über ORDER BY können Sie nach beliebigen Feldern sortieren: SELECT ISBN, Titel FROM Buecher ORDER BY Titel;

Das ist bereits ein sehr nettes Feature. Sie haben damit absolut keine Probleme, Daten aus einer Datenbank sortiert abzufragen und auszugeben. Schließlich (aber für die SELECT-Anweisung lange nicht endlich) können Sie Daten noch bedingungsabhängig abfragen. Die Bedingung verwendet logische Ausdrücke, wie Sie diese bereits von der Programmierung her kennen. Die Vergleichsoperatoren sind dieselben wie bei Object Pascal. In der Bedingung beziehen Sie sich auf die einzelnen Felder. So können Sie z. B. gezielt die Bücher des Autors mit der Id 1 abfragen: SELECT ISBN, Titel FROM Buecher WHERE Autor = 1 ORDER BY Titel;

Das Ergebnis kann sich bereits sehen lassen (Abbildung 9.7).

   

469

Sandini Bib

Abbildung 9.7: Das Ergebnis einer SQL-Abfrage im SQL-Monitor unter Windows Komplexere Anweisungen lösen die Beziehung auf

Über eine komplexere Anweisung können Sie auch die Beziehung zwischen Tabellen auflösen. So können Sie z. B. die ISBN-Nummer aller Bücher gemeinsam mit dem Vor- und Nachnamen des Autors abfragen. Um im Ergebnis klarere Feldnamen zu erhalten, können Sie diese bei der Abfrage mit einem neuen Namen versehen (Abbildung 9.8).

Abbildung 9.8: Abfrage der in Beziehung stehenden Daten zweiter Tabellen

Auf diese Art der Abfrage gehe ich aber nicht weiter ein. Ich wollte diese Möglichkeit nur zeigen, damit Sie wissen, was eine solche Abfrage ist.

9.3.3 Daten ändern Das Ändern von Daten über SQL ist relativ einfach, wenn Sie sich mit der SELECT-Anweisung auskennen. Die zum Ändern verwendete UPDATEAnweisung arbeitet nämlich mit einer identischen WHERE-Klausel. Das Schema dieser Anweisung sieht so aus: UPDATE Tabelle SET Feld1 = Wert [, Feld2 = Wert] [Feld2 = Wert] [...] [WHERE Bedingung]

470

      

Sandini Bib

Die Angabe der Bedingung ist zwar optional, wenn Sie diese aber nicht angeben, ändern Sie alle Datensätze. Und das ist nur in seltenen Fällen sinnvoll. In der Liste der zu ändernden Felder können Sie ein oder beliebig viele Felder mit neuen Werten versehen. Die Werte geben Sie wieder wie gewohnt an. Zahlen in der englischen Schreibweise, Zeichenketten in Anführungszeichen. So können Sie z. B., wenn Sie alle Bücher von Douglas Adams verliehen haben, das Feld Verliehen_an dieser Bücher aktualisieren: UPDATE Buecher SET Verliehen_an = "Zaphod" WHERE Autor = 1;

Natürlich können Sie auch einzelne Datensätze aktualisieren. Dann beziehen Sie sich auf den Primärschlüssel: UPDATE Buecher SET Verliehen_an = "Trillian" WHERE ISBN = "342312721X";

Sie können über diese Anweisung auch sehr komplexe Aktualisierungen vornehmen, aber ich denke, für den Anfang reicht das, was ich gezeigt habe. Beim eventuellen Ändern des Primärschlüsselwerts einer Tabelle, die in einer anderen Tabelle referenziert wird (also z. B. der Id eines Autors), müssen Sie vorsichtig sein. Wenn diese Id in der anderen Tabelle gespeichert ist, bringen Sie Ihre Datenbank damit durcheinander. Das gilt allerdings nur für MySQL, denn andere Datenbanksysteme unterstützen Fremdschlüssel. Ein Fremdschlüssel verhindert, dass ein in einer anderen Tabelle referenzierter Primärschlüsselwert geändert wird. Der Fremdschlüssel muss dazu natürlich beim Erzeugen der Datenbank (oder später) angelegt worden sein.

9.3.4 Daten löschen Das Löschen von Daten ist in SQL ebenfalls sehr einfach. Dazu verwenden Sie die DELETE-Anweisung, die auch wieder mit einer WHERE-Klausel arbeitet: DELETE FROM Tabelle [WHERE Bedingung];

Die Bedingung ist wieder optional. Wenn Sie diese nicht angeben, löschen Sie alle Datensätze. Gehen Sie mit dieser Anweisung also vorsichtig um. Die Daten werden unwiderruflich gelöscht, Sie können das Löschen nicht rückgängig machen. So könnten Sie z. B. alle Bücher des Autors mit der Id 1 löschen: DELETE FROM Buecher WHERE Autor = 1;

   

471

Sandini Bib

Beim Löschen von Datensätzen einer Tabelle, die in einer anderen Tabelle referenziert wird, müssen Sie wie beim Aktualisieren vorsichtig sein. Sie können Ihre Datenbank damit in einen inkonsistenten Zustand bringen. So kann es z. B. sein, dass Sie den Autor mit der Id 1 löschen, dieser aber noch in der Bücher-Tabelle referenziert wird. Das gilt einmal wieder nur für MySQL. DBMS, die Fremdschlüssel unterstützen, verhindern, dass solche Datensätze gelöscht werden.

9.4

Daten in Java-Programmen bearbeiten

In Java-Programme können Sie Daten sehr einfach bearbeiten, wozu Sie natürlich SQL einsetzen. Java bietet aber auch eine vereinfachte Möglichkeit, bei der Sie die Features eines Objekts nutzen, das die SQLAnweisungen automatisch erzeugt. Ich zeige im Folgenden nun, wie Sie die Java-Objekte zur Arbeit mit Datenbanken nutzen. Um nicht wieder eine komplexe Beispielanwendung mit grafischer Oberfläche zu erzeugen, die zu einem im Buch ziemlich unübersichtlichen Programm führen würde, zeige ich die Grundlagen lediglich in einer Konsolenanwendung. Ich denke, mit dem Wissen, das Sie bis hier erworben haben, sind Sie in der Lage, eine eigene Anwendung mit einer grafischen Oberfläche zu erzeugen. Eine Abschlussübung des Kapitels wird dann auch sein, ein Programm zur Bearbeitung der Buchdatenbank zu entwickeln . Dieses Programm finden Sie natürlich auch in den Lösungen auf der CD.

-

9.4.1 Der JDBC-Treiber Die Datenbank-Features von Java werden als JDBC (Java Database Connectivity) bezeichnet. JDBC ist eine Bibliothek mit mehreren Klassen, die Sie bei der Programmierung nutzen. Um mit einem Datenbanksystem arbeiten zu können, benötigen Sie zusätzlich zu JDBC einen Treiber. Ein Treiber übernimmt die direkte Kommunikation mit dem DBMS, sorgt also dafür, dass Sie SQL-Anweisungen zum DBMS senden können und das Ergebnis zurückerhalten. In Java selbst ist kein direkter JDBC-Treiber integriert. Java besitzt lediglich einen JDBC-Treiber, der eine Brücke nach ODBC (Open Database Connectivity) darstellt. ODBC ist ein mittlerweile veralteter offener Standard, der Programmen den Zugriff auf Datenbanksysteme über einfache Funktionen erlaubt (nicht über Objekte). Laut Sun soll der JDBCTreiber für ODBC nur zu Experimentierzwecken und dann verwendet werden, wenn kein direkter JDBC-Treiber zur Verfügung steht.

472

      

Sandini Bib

Die meisten JDBC-Treiber werden von externen Herstellern zur Verfügung gestellt und müssen teilweise gekauft werden. Auf der Seite INDUSTRY.JAVA.SUN.COM/PRODUCTS/JDBC/DRIVERS finden Sie eine Übersicht über die aktuell zur Verfügung stehenden Treiber für verschiedene Datenbanken (zurzeit ca. 160). Für MySQL-Datenbanken können Sie den Treiber von MySQL-AB verwenden, den Sie bereits installiert haben, wenn Sie den Artikel „Installation“ auf der Buch-CD nachvollzogen haben. Ein JDBC-Treiber ist meist nur eine einzige Datei, die lediglich in den Ordner lib\ext der Java-Laufzeitumgebung kopiert werden muss.

9.4.2 Treiber laden und Verbindung aufbauen Um mit einer Datenbank arbeiten zu können, müssen Sie in Java-Programmen zunächst den JDBC-Treiber laden (in anderen Programmiersprachen ist dies nicht notwendig). Dazu verwenden Sie die statische Methode forName der Java-Klasse Class. Dieser Methode übergeben Sie den vollen Namen der Treiber-Klasse (im Fall des MySQL AB-Treibers ist das der Name com.mysql.jdbc.Driver) :

Laden des MySQLTreibers

Class.forName("com.mysql.jdbc.Driver");

Was Sie hier machen, erscheint vielleicht ein wenig eigenartig. Sie laden über diese Anweisung die Klasse Driver in den Speicher. Sie erzeugen noch keine Instanz dieser Klasse. Das Erzeugen einer Instanz übernimmt die DriverManager-Klasse, die Sie im weiteren Verlauf der Programmierung verwenden, um eine Verbindung zur Datenbank aufzubauen. Um Instanzen zu erzeugen, muss die entsprechende Klasse im Speicher verwaltet werden. Wenn Sie selbst im Programm Instanzen erzeugen, erkennt der Compiler an der Deklaration der Variablen, welche Klassen geladen werden müssen, und sorgt dafür, dass diese auch in den Speicher geladen werden. Bei einem JDBC-Treiber werden die Instanzen der Driver-Klasse aber vom Treiber-Manager (der DriverManager-Klasse) erzeugt. Der Compiler kann nicht wissen, welche Klassen geladen werden sollen. Deswegen müssen Sie die Treiberklasse explizit laden. Obwohl der Treiber-Manager die Instanz der Treiber-Klasse implizit erzeugt und intern für den Datenbankzugriff verwendet, sollten Sie beim Laden der Klasse auch eine Instanz erzeugen. Beim Instanzieren kann es nämlich vorkommen, dass Fehler auftreten. Diese Fehler können Sie gezielt abfangen, wenn Sie selbst testweise eine Instanz erzeugen. Dazu verwenden Sie die Methode newInstance des Class-Objekts, das Class.forName zurückgibt (ein Class-Objekt wird u. a. zur Instanzierung dynamisch geladener Klassen verwendet). forName verlangt, dass Sie die Ausnahme ClassNotFoundException abfangen, newInstance verlangt das Abfangen der

       

473

Sandini Bib

Ausnahmen InstantiationException (Fehler bei der Instanzierung) und IllegalAccessException (Fehler beim Zugriff). Sie müssen diese Ausnahmen also abfangen. Um einigermaßen aussagekräftige Fehlermeldungen auszugeben, aber nicht zu viel programmieren zu müssen, fange ich die ClassNotFoundException-Ausnahme separat und alle anderen Ausnahmen in einer allgemeinen Ausnahmebehandlung ab: 01 import java.sql.*; 02 03 public class DatenBearbeiten 04 { 05 public static void main(String args[]) 06 { 07 /* Treiber laden */ 08 System.out.println("Lade Treiber ..."); 09 try 10 { 11 Class.forName("com.mysql.jdbc.Driver").newInstance(); 12 } 13 catch (ClassNotFoundException e) 14 { 15 System.out.println("Die Treiber-Klasse wurde nicht " + 16 "gefunden. Installieren Sie den MySql-Treiber."); 17 System.exit(1); 18 } 19 catch (Exception e) 20 { 21 System.out.println("Der JDBC-Treiber kann nicht " + 22 "geladen werden: " + e.getMessage()); 23 System.exit(1); 24 } 25

Das Programm importiert in Zeile 1 zunächst alle Klassen des Pakets java.sql, das die Klassen beinhaltet, die Sie zur Arbeit mit Datenbanken einsetzen. In Zeile 11 wird der Treiber geladen. Beim Eintritt einer Ausnahme (Zeilen 15-17 und 21-23) wird das Programm einfach beendet, was wieder über System.exit(1) geschieht. Das funktioniert auch in einer Anwendung mit grafischer Oberfläche und gibt dem System zurück, dass ein Fehler aufgetreten ist (dieser Rückgabewert kann von einem Batch-Programm oder Shell-Script aus einer Systemvariable ausgelesen werden). Aufbau einer Verbindung

474

Wenn der Treiber erfolgreich geladen ist, müssen Sie eine Verbindung zur Datenbank aufbauen. Diese Verbindung verwenden Sie, um Daten abzufragen, anzufügen, zu ändern, zu löschen, um neue Tabellen anzulegen

      

Sandini Bib

und was sonst noch alles mit SQL möglich ist. Für die Verbindung benötigen Sie eine Variable vom Typ java.sql.Connection. Über die getConnectionMethode des Treiber-Managers erzeugen Sie eine Instanz dieser Klasse. Dabei übergeben Sie einen URL (Uniform Ressource Locator), wie er im Internet verwendet wird (um z. B. die Adresse einer Website anzugeben). Der URL besitzt für eine MySQL-Datenbank das folgende Schema: jdbc:mysql://Rechneradresse/Datenbankname

Die Rechneradresse wird in der im Internet üblichen Form angegeben. Ich will hier nicht näher darauf eingehen, Sie können MySQL-Datenbanken ohne Probleme auch auf anderen Rechnern oder eben sogar über das Internet ansprechen. Läuft die Datenbank im lokalen Netz, geben Sie einfach den Rechnernamen an. Für eine Datenbank, die auf dem Rechner läuft, auf dem das Programm ausgeführt wird, können Sie localhost angeben. Neben dem URL übergeben Sie der getConnection-Methode dann noch einen Benutzernamen und das Passwort des Benutzers. Das Ganze muss natürlich wieder in eine Ausnahmebehandlung eingefügt werden: 26 27 28 29 30 31 32 33 34 35 36 37 38

System.out.println("Baue Verbindung auf ..."); Connection connection = null; try { connection = DriverManager.getConnection( "jdbc:mysql://localhost/Buchdaten", "root", ""); } catch (Exception e) { System.out.println("Fehler beim Öffnen der Verbindung: " + e.getMessage()); System.exit(1); }

Das Beispiel erzeugt die Verbindung in Zeile 30, wobei als Benutzer „root“ ohne Passwort angegeben wird. Das Passwort dieses Benutzers ist nämlich nach einer Neuinstallation noch leer (und sollte natürlich aus Sicherheitsgründen gegebenenfalls geändert werden). Mit dieser Verbindung können Sie dann arbeiten.

9.4.3 Daten abfragen und bearbeiten Das Abfragen von Daten ist nun sehr einfach. Sie benötigen dazu ein Objekt der Klasse Statement. Die executeQuery-Methode dieses Objekts führt eine SELECT-Anweisung aus und gibt ein ResultSet-Objekt zurück, über das Sie die Daten durchgehen und sogar auch bearbeiten können.

       

475

Sandini Bib

Erzeugen eines Statement-Objekts Sie sollten zuerst das Statement-Objekt erzeugen. Dieses Objekt können Sie für alle SQL-Abfragen verwenden, die Sie auf der geöffneten Datenbank ausführen wollen. Zur Erzeugung verwenden Sie die Methode createStatement des Connection-Objekts: 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

System.out.println("Erzeuge Statement-Objekt ..."); Statement statement = null; try { statement = connection.createStatement(); } catch (Exception e1) { System.out.println("Fehler beim Erzeugen des " + "Statement-Objekts: " + e1.getMessage()); /* Verbindung schließen */ try { connection.close(); } catch (Exception e2) {} System.exit(1); }

Das Erzeugen muss wieder in einer Ausnahmebehandlung erfolgen. Eine Ausnahme beim Erzeugen wird wieder als kritischer Fehler gewertet, weshalb das Programm in Zeile 52 beendet wird. Zuvor muss aber die Verbindung geschlossen werden. Verbindungen sollten Sie immer schließen, bevor Sie ein Programm beenden. In Zeile 51 ruft das Programm deshalb die close-Methode des Connection-Objekts auf. Da diese Methode verlangt, dass die Ausnahme SQLException abgefangen wird, ist das Schließen in eine innere Ausnahmebehandlung eingeschlossen. Diese reagiert aber nicht auf einen eventuellen Fehler, der catch-Block enthält keine Anweisungen. Beim Beenden des Programms sind eventuelle Fehlermeldungen recht sinnlos. Dass das Programm für die Ausnahmebehandlungen zwei Variablen verwendet (e1 und e2), liegt übrigens daran, dass die Variable in der inneren Ausnahmebehandlung anders benannt sein muss als in der äußeren. Abfragen von Daten Nun können Sie Daten abfragen. Dazu verwenden Sie die executeQueryMethode des Statement-Objekts. Dieser Methode übergeben Sie eine SQL-SELECT-Anweisung. executeQuery gibt eine Instanz der Klasse3 Result-

3.

476

In Wirklichkeit ist ResultSet keine Klasse, sondern eine Schnittstelle (englisch „Interface“). Auf dieses schwierige Thema gehe ich im Buch aber nicht ein. Informationen dazu finden Sie im Artikel „OOP-Grundlagen“ auf der Buch-CD.

      

Sandini Bib

Set zurück. Über dieses Objekt können Sie die abgefragten Daten sehr

flexibel bearbeiten. Ein ResultSet-Objekt ist sehr mächtig und besitzt sehr viele Methoden. Ich kann hier nur die wichtigsten vorstellen. Lesen Sie in der Java-APIDokumentation nach, wenn Sie mehr über diese Klasse erfahren wollen. executeQuery erzeugt im Fehlerfall eine Ausnahme vom Typ SQLException,

die abgefangen werden muss: 55 56 57 58 59 60 61 62 63 64 65 66 67

System.out.println("Frage Daten ab ..."); ResultSet resultSet = null; try { resultSet = statement.executeQuery( "SELECT * FROM Buecher ORDER BY ISBN"); } catch (SQLException e) { System.out.println("Fehler bei der Abfrage der Daten: " + e.getMessage()); }

Daten durchgehen Als SQL-Anweisung können Sie alle SELECT-Anweisungen übergeben, die für die Datenbank möglich sind. Damit können Sie Daten sehr flexibel abfragen. Das ResultSet-Objekt wird mit den abgefragten Datensätzen gefüllt und verwaltet diese intern im Arbeitsspeicher. Sie können nun z. B. sequenziell durchgehen. Das ResultSet-Objekt verwaltet einen internen Datensatzzeiger. Solch ein Zeiger wird im Allgemeinen als Cursor bezeichnet. Nach dem Öffnen einer Abfrage steht dieser vor dem ersten Datensatz (was eigentlich bei solchen Techniken, die in ähnlicher Form auch in anderen Sprachen verwendet werden, unüblich ist, dort steht der Cursor nach dem Öffnen dann auf dem ersten Datensatz). Über die next-Methode bewegen Sie den Cursor um einen Datensatz weiter. Wenn Sie next direkt nach dem Öffnen aufrufen, steht der Cursor also auf dem ersten Datensatz. Wenn Sie die Daten sequenziell durchgehen wollen, müssen Sie überprüfen, ob der Cursor nach dem Aufruf von next nicht hinter dem letzten Datensatz steht. Das ist nämlich nach dem Aufruf von next der Fall, wenn der Cursor zuvor auf dem letzten Datensatz stand. Diese Überprüfung nehmen Sie über die Methode isAfterLast vor. Zudem müssen Sie überprüfen, ob die Abfrage eventuell keine Daten er-

       

477

Sandini Bib

gab. Das können Sie einfach erledigen, indem Sie die Rückgabe von next überprüfen. next gibt nämlich nur dann true zurück, wenn der Cursor auf einem Datensatz steht. Prinzipiell sieht das Durchgehen dann so aus: if (resultSet.next() == false) { System.out.println("Die Abfrage ergab keine Daten."); } else { do { /* Daten verarbeiten */ ... /* Cursor auf den nächsten Datensatz setzen */ resultSet.next(); } while (resultSet.isAfterLast() == false); } Zugriff auf die Felder über getMethoden

Innerhalb der Schleife können Sie nun auf den aktuellen Datensatz zugreifen. Dazu stehen Ihnen eine Menge verschiedener Methoden zur Verfügung, deren Name immer mit „get“ beginnt. Diese einzelnen Methoden geben den Wert eines Datenfeldes als speziellen Datentyp zurück. getString liefert z. B. einen String, getInt einen int-Wert und getDouble einen double-Wert. Voraussetzung dafür ist natürlich, dass das Datenfeld einen Datentyp besitzt, der identisch ist oder entsprechend konvertiert werden kann. Allen diesen Methoden können Sie den Index des Feldes (bezogen auf die Angabe der Felder in der Abfrage) oder den Namen des Feldes übergeben. Die Übergabe des Index ist in der Praxis nicht zu empfehlen, weil Sie häufig nicht wissen, welchen Index ein Feld besitzt (wenn Sie mit SELECT * ... abfragen) oder der Index aufgrund einer Änderung der Abfrage (was in der Praxis häufiger passiert) sich verschiebt. Fragen Sie die Daten also über den Namen des Feldes ab. Das Ganze muss einmal wieder in eine Ausnahmebehandlung eingefügt werden, die auf die Ausnahme SQLException reagiert. Diese Ausnahme wird z. B. erzeugt, wenn Sie beim Lesen einen nicht existierenden Feldnamen übergeben oder wenn ein Wert nicht konvertiert werden kann: 68 69 70 71

478

try { System.out.println("Gehe Daten durch..."); if (resultSet != null)

      

Sandini Bib

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

{ /* Auf den ersten Datensatz stellen und gleichzeitig überprüfen, ob die Abfrage eventuell keine Daten ergab */ if (resultSet.next() == false) { System.out.println("Die Abfrage ergab keine Daten."); } else { /* ResultSet in einer Schleife durchgehen */ do { /* Datenfelder auslesen */ System.out.println(resultSet.getString("ISBN")); System.out.println(resultSet.getString("Titel")); System.out.println(resultSet.getInt("Autor")); System.out.println(); /* Cursor auf den nächsten Datensatz stellen */ resultSet.next(); } while (resultSet.isAfterLast() == false); } } } catch (SQLException e) { System.out.println("Fehler beim Durchgehen der Daten: " + e.getMessage()); }

Über die Methoden first, last und previous können Sie den Cursor übrigens auch auf den ersten, den letzten bzw. den vorherigen Datensatz setzen, was bei einer Anwendung mit einem Formular zum Editieren der Daten z. B. notwendig ist. Der Zugriff auf die Daten ist in der Praxis noch etwas komplexer, als ich es hier dargestellt habe. Sie müssen z. B. gegebenenfalls auf leere Felder reagieren. Die get-Methoden geben für leere Felder einen zum Datentyp passenden leeren Wert zurück (bei Zahlen ist das die Null). Nach dem Lesen können Sie über die Methode wasNull überprüfen, ob ein Feld leer war. Außerdem sollten Sie die Ausnahmebehandlung eventuell etwas verfeinern, damit Sie mehr Informationen erhalten, wenn beim Zugriff ein Fehler auftritt. Aber das überlasse ich Ihnen.

       

479

Sandini Bib

Daten bearbeiten Ein ResultSet-Objekt erlaubt nicht nur das einfache Auslesen der Daten. Sie können diese auch direkt bearbeiten. Zum Ändern eines Datensatzes schreiben Sie einfach Daten über verschiedene Methoden, die mit „update“ beginnen und die ähnlich den get-Methoden verschiedene Datentypen erlauben, in einzelne Felder. Diese Methoden schreiben die übergebenen Daten immer in den aktuellen Datensatz. Über die Methode moveToInsertRow können Sie aber auch einen neuen Datensatz erzeugen. Ich zeige hier nur, wie das prinzipiell funktioniert, ohne die notwendige Ausnahmebehandlung: /* Neuen Datensatz erzeugen */ resultSet.moveToInsertRow(); /* Datensatz beschreiben */ resultSet.updateString("ISBN", "3453099826"); resultSet.updateString("Titel", "Der tiefere Sinn des Labenz"); resultSet.updateInt("Autor", 1); /* Datenbank aktualisieren */ resultSet.insertRow();

Die Methode insertRow generiert aus den hinzugefügten Daten eine passende INSERT INTO-Anweisung und sendet diese an das DBMS. Falls dieses den Datensatz nicht anfügen kann, erzeugt insertRow eine Ausnahme. Auf eine ähnliche Weise können Sie den aktuellen Datensatz aktualisieren: /* Datensatz beschreiben */ resultSet.updateString("Verliehen_an", "Zaphod"); /* Datenbank aktualisieren */ resultSet.updateRow();

Der zu aktualisierende Datensatz muss dazu der aktuelle sein. Sie müssen den Cursor also zuvor auf den zu aktualisierenden Datensatz bewegen. updateRow erzeugt aus den Änderungen eine passende UPDATE-Anweisung. Dabei können natürlich wieder Fehler auftreten, die zu einer Ausnahme führen. Diese Art der Aktualisierung verwenden Sie idealerweise nur in Programme, die dem Benutzer Datensätze zur Bearbeitung in Formularen zur Verfügung stellen (ähnlich der Beispielanwendung aus Kapitel 8). Ansonsten sollten Sie Daten wenn möglich immer direkt über SQL aktualisieren, was aber auch für das Anfügen und Löschen gilt. Zum einen können Sie so gleich mehrere Datensätze in einem Rutsch verändern

480

      

Sandini Bib

oder löschen. Zum anderen ist die direkte Ausführung von SQL-Anweisungen immer wesentlich schneller, als wenn Sie dazu ein ResultSet-Objekt verwenden. Dabei sollten Sie aber auch bedenken, dass ein solchen Objekt Ihnen das manchmal nicht einfache Zusammensetzen einer SQL-Anweisung abnimmt. Löschen von Daten Als letztes Feature des ResultSet-Objekts zeige ich noch das Löschen. Dazu verwenden Sie die Methode deleteRow. Diese Methode löscht den aktuellen Datensatz (direkt auch in der Datenbank) und setzt den Cursor einen Datensatz weiter. Deshalb müssen Sie nach dem Löschen überprüfen, ob der Cursor nicht hinter dem letzten Datensatz steht (wenn der letzte gelöscht wurde). Dann können Sie den Cursor einfach auf den vorherigen Datensatz stellen. Hinzu kommt, dass Sie dann noch überprüfen müssen, ob das ResultSet-Objekt überhaupt noch Daten enthält. Das macht das Löschen nicht gerade einfach: /* Datensatz löschen */ resultSet.deleteRow(); /* Überprüfen, ob der Cursor nun hinter dem letzten Datensatz steht */ if (resultSet.isAfterLast()) { /* ResultSet zum vorherigen Datensatz bewegen und gleichzeitig überprüfen, ob das ResultSet nun leer ist */ if (resultSet.previous() == false) { System.out.println("Der letzte Datensatz wurde gelöscht"); } }

9.4.4 Daten über SQL direkt anfügen, ändern und löschen Zum Anfügen, Ändern und Löschen von Daten können Sie dem DBMS auch direkt SQL-Anweisungen übergeben. In vielen Fällen ist der direkte Aufruf effizienter und flexibler, als wenn Sie dazu ein ResultSetObjekt verwenden. Wenn Sie beispielsweise lediglich alle Bücher von Douglas Adams als verliehen kennzeichnen wollten, wäre es unsinnig, dafür ein ResultSet-Objekt zu erzeugen, die einzelnen Datensätze durchzugehen und der Reihe nach zu aktualisieren. Das können Sie wesentlich einfacher über eine UPDATE-Anweisung erledigen. Um eine SQLAnweisung an die Datenbank zu übergeben, verwenden Sie die executeUpdate-Methode. Das einzig Schwierige daran ist, die SQL-Anwei-

       

481

Sandini Bib

sung korrekt zusammenzusetzen. Das gilt besonders dann, wenn die Daten für die Anweisung vom Anwender eingegeben werden. Als Beispiel soll hier das Anfügen eines neuen Buchs dienen: String sql; /* Den Anwender die Daten eingeben lassen */ String isbn = null, titel = null, autor = null; try { java.io.DataInputStream in = new java.io.DataInputStream(System.in); System.out.print("ISBN: "); isbn = in.readLine(); System.out.print("Titel: "); titel = in.readLine(); System.out.print("Autor: "); autor = in.readLine(); sql = "INSERT INTO Buecher (ISBN, Titel, Autor) " + "VALUES ('" + isbn + "', " + "'" + titel + "', " + autor + ")"; statement.executeUpdate(sql); } catch (Exception e1) { System.out.println("Fehler beim Anfügen: " + e1.getMessage()); }

Das Schwierige daran ist, die Eingaben so in die SQL-Anweisung einzubauen, dass diese syntaktisch korrekt ist. Dabei müssen Sie besonders darauf achten, dass Strings innerhalb der Anweisung immer in Anführungszeichen oder Apostrophen eingefügt werden müssen. Ich verwende in Java lieber Apostrophe, weil Anführungszeichen nicht einfach zu handhaben sind (diese müssten in einem String über \" dargestellt werden, was das Ganze noch unübersichtlicher macht, als es bereits ist). Bei Dezimalzahlen müssen Sie darauf achten, dass Sie diese so formatieren, dass die englische Schreibweise verwendet wird. Datumswerte habe ich erst gar nicht berücksichtigt, weil diese immer in einem ganz speziellen Format angegeben werden müssen.

482

      

Sandini Bib

Was Sie ebenfalls beachten sollten, ist, dass Änderungen, die Sie über SQL direkt vornehmen, in einem gleichzeitig geöffneten ResultSetObjekt nicht sichtbar sind. Das ResultSet-Objekt hat seine Daten ja nur einmal abgefragt und arbeitet mit diesen Daten weiter. Lediglich Änderungen, die über das ResultSet-Objekt ausgeführt werden, sind auch in diesem Objekt sichtbar. In der Praxis ist das sogar ein echtes Problem: Wenn mehrere Anwender gleichzeitig auf eine Datenbank zugreifen (was immer möglich ist), kann es sein, dass ein anderer Anwender einen Datensatz, der in einem ResultSet-Objekt verwaltet wird, zwischenzeitlich ändert oder sogar löscht. Das Handling solcher Probleme ist nicht gerade trivial. Darauf gehe ich aufgrund der Komplexität aber nicht weiter ein.

9.4.5 Verbindung schließen Nach der Arbeit mit der Verbindung sollten Sie diese immer wieder schließen, damit alle (vom DBMS) eventuell belegten Ressourcen wieder freigegeben werden. Verwenden Sie dazu die close-Methode des Connection-Objekts. Das Schließen muss in einer Ausnahmebehandlung erfolgen. Mit dem Schließen der Verbindung beende ich auch das Beispielprogramm: 103 104 105 106 107 108 109 110 111 112 113 } 114 }

System.out.println("Schließe die Verbindung ..."); try { connection.close(); } catch (Exception e) { System.out.println("Fehler beim Schließen der " + "Verbindung: " + e.getMessage()); }

Und schließlich auch das Buch: buch.ende();

9.5

Zusammenfassung

In diesem Kapitel haben Sie zunächst gelernt, was eine Datenbank prinzipiell ist und wie ein Datenbanksystem aufgebaut ist. Sie kennen die grundsätzlichen Aufgaben eines Datenbankmanagementsystems und die wichtigsten Prinzipien von relationalen Datenbanken. Den Begriff

=XVDPPHQIDVVXQJ

483

Sandini Bib

„Tabelle“ bringen Sie in Verbindung mit einer Datenbank und Sie wissen auch, was Beziehungen zwischen Tabellen sind. Objektorientierte Datenbanken sind Ihnen noch fremd, Sie ahnen aber wahrscheinlich schon, dass die Arbeit mit solchen Datenbanken wesentlich einfacher ist als mit relationalen. Um Daten in relationalen Datenbanken erzeugen und bearbeiten zu können, sind Sie in der Lage, mit SQL Datenbanken zu erzeugen und Daten anzufügen, abzufragen, zu aktualisieren und zu löschen. Sie kennen zwar noch nicht alle Möglichkeiten von SQL, aber Sie wissen, worum es sich dabei handelt. Schließlich sind Sie auch in der Lage, eine Datenbank in einem Java-Programm zu bearbeiten, wobei Sie auch einige der erweiterten Features eines ResultSetObjekts kennen.

9.6

Fragen und Übungen

1. Was unterscheidet eine objektorientierte von einer relationalen

Datenbank? 2. Wozu setzen Sie SQL ein? 3. Welche Aufgabe übernimmt ein JDBC-Treiber? 4. Welcher Unterschied besteht zwischen der Arbeit mit einem Result-

Set-Objekt und dem direkten Ausführen von SQL-Anweisungen in

Java? 5. Programmieren Sie eine Anwendung, die dem Anwender erlaubt,

über ein Formular ein neues Buch anzufügen. Mehr soll diese Anwendung nicht bieten. 6. Programmieren Sie eine Anwendung, die es dem Anwender erlaubt,

die gespeicherten Bücher durchzugehen (ähnlich der Beispielanwendung aus Kapitel 8), zu ändern, zu löschen und neue Bücher anzufügen. 7. Erweitern Sie die Anwendung aus Aufgabe 6 so, dass zusätzlich das

Suchen nach Büchern möglich ist, die einen bestimmten Titel tragen. Der Anwender soll dabei nur einen Teil des Buchtitels eingeben können. Lesen Sie in der Dokumentation von MySQL dazu die Syntax des LIKE-Operators nach. 8. Erweitern Sie die Anwendung aus Aufgabe 7 so, dass zusätzlich das

Anfügen von neuen Autoren möglich ist. Viel Spaß dabei.

484

      

Sandini Bib

N

Nachwort

le

n e rn

Es hat sehr viel Spaß gemacht, dieses Buch zu schreiben. Viele Dinge waren auch für mich neu, besonders unter Java (meine Sprachen sind eher C# und Visual Basic). Ich hoffe, ich habe etwas von diesem Spaß im Buch weitergegeben. Auch wenn die Beispiele, besonders in den letzten beiden Kapiteln, vielleicht schon etwas komplex waren. Sehen Sie das Ganze aber so wie ich: Suchen Sie im Internet nach Lösungen, versuchen Sie nicht, zu tief in die einzelnen Themen einzusteigen und freuen Sie sich über die Erfolge. Viel Spaß also noch bei der weiteren Programmierung.

485

Sandini Bib

Sandini Bib

A

Anhang

le

n e rn

A.1 Lösungen zu den Fragen und Übungen A.1.1 Kapitel 1 1. Warum besteht häufig der Bedarf, Standardanwendungen durch in-

tegrierte Programme zu erweitern? Standardanwendungen sind zwar oft recht mächtig, können aber nie die zum Teil sehr speziellen Bedürfnisse aller Anwender bzw. Firmen abdecken. Besonders Firmen arbeiten häufig mit einer speziellen Geschäftslogik, die mit den eingebauten Möglichkeiten einer Standardanwendung nicht abgebildet werden kann. 2. Sie haben das Problem, eine Datenbank in Java ansprechen zu müs-

sen. Sie kennen Datenbanken, wissen aber nicht, welche Komponenten Sie einsetzen müssen und wie Sie diese programmieren. Wie finden Sie die benötigten Informationen? Wenn Sie ein gutes Buch zur Hand haben, lesen Sie einfach dort nach. Die Informationen in einem Buch (das möglichst nicht vom Hersteller der Programmiersprache stammt) sind meist sehr effizient. Wenn Sie kein Buch besitzen oder die Informationen nicht finden, können Sie auch in der Dokumentation der Programmiersprache nachschlagen, aber das ist oft eher ineffizient. Besser ist in diesem Fall, bei Google in normalen Webseiten und im Newsgroup-Archiv zu suchen. Wenn Sie die Lösung Ihres Problems dann immer noch nicht gefunden haben, können Sie schließlich noch eine Frage in eine Newsgroup mailen, die sich mit Java-Datenbanken beschäftigt.

     

487

Sandini Bib

A.1.2 Kapitel 2 1. Wie erzeugen Sie aus einem Java-Quelltext (mit der Endung .java) ein

ausführbares Programm? Dazu verwenden Sie den Java-Compiler javac, den Sie an der Konsole unter Übergabe des Namens der Quelltextdatei aufrufen. 2. Wie führen Sie ein Java-Programm (das in einer Datei mit der Endung

.class gespeichert ist) aus? Java-Programme führen Sie über den Java-Interpreter java aus. Rufen Sie diesen dazu an der Konsole unter Übergabe des Programmnamens (allerdings ohne Endung) auf. 3. Welche Vorteile bietet eine Entwicklungsumgebung wie beispielswei-

se Delphi oder Kylix gegenüber dem einfachen Entwickeln in einem Texteditor? Eine Entwicklungsumgebung wie beispielsweise Delphi oder Kylix besitzt gegenüber dem einfachen Entwickeln in einem Texteditor einige Vorteile. Neben einer wesentlich leichter lesbaren Darstellung des Quelltexts und vielen hilfreichen Tools fasst eine Entwicklungsumgebung alle Dateien eines Programms in einem Projekt zusammen, erlaubt das einfache Kompilieren und Testen des Programms und integriert meist auch einen umfangreichen Debugger. 4. Was sollten Sie beachten, wenn Sie ein neues Delphi/Kylix-

Programm speichern? Beim Speichern eines neuen Delphi/Kylix-Programms sollten Sie beachten, dass Sie alle Dateien des Programms in einem separaten Verzeichnis speichern, damit Sie diese ohne Probleme wiederfinden und zuordnen können. 5. Was ist ein Projekt?

Ein Projekt fasst alle Dateien, die zu einem Programm gehören, zusammen. Eine Entwicklungsumgebung erlaubt normalerweise über ein spezielles Projekt-Fenster den schnellen Zugriff auf diese Dateien und das Kompilieren aller Dateien zu einem Programm. 6. Nennen Sie zwei Unterschiede zwischen einer Konsolenanwendung

und einer Anwendung mit grafischer Oberfläche. Die zwei wichtigsten Unterschiede sind: • Eine Konsolenanwendung nimmt Ein- und Ausgaben an der Konsole vor. Eine normale Anwendung nimmt Eingaben in Steuerele-

488

$QKDQJ

Sandini Bib

menten in einem Fenster entgegen und gibt dort auch ihre Ausgaben aus, • Eine Konsolenanwendung startet oben im Quellcode und wird von oben nach unten abgearbeitet. Eine normale Anwendung wartet hingegen darauf, dass der Anwender durch die Betätigung eines Schalters oder durch andere Aktionen das Programm aufruft.

A.1.3 Kapitel 3 1. Warum kann ein Programm mathematische Berechnungen nur mit

einer eingeschränkten Genauigkeit ausführen? Zur Speicherung von Dezimalzahlen steht einem Programm nur eine beschränkte Menge an Bytes zur Verfügung. Die einzelnen Bits müssen nun so aufgeteilt werden, dass alle Vorkommaziffern gespeichert werden können. Für die Nachkommaziffern bleibt dann häufig nicht genügend Platz, sodass diese einfach ab einer bestimmten Stelle abgeschnitten werden. Bei Divisionen resultieren häufig Zahlen mit vielen Nachkommaziffern. Die eingeschränkte Speicherung führt dann immer zu einem etwas ungenauen Ergebnis. 2. Was unterscheidet einen ASCII-Zeichensatz vom Unicode-Zeichen-

satz? ASCII-Zeichen werden in einem Byte gespeichert, Unicode-Zeichen in zwei Byte. ASCII lässt deswegen nur 256 Zeichen zu, was mehrere verschiedene ASCII-Zeichensätze für unterschiedliche Regionen notwendig macht. Unicode kann hingegen 65535 Zeichen verwalten. Damit können fast alle Sprachen dieser Welt in einem einzigen Zeichensatz abgebildet werden. 3. Nennen Sie zwei Unterschiede zwischen einem Quellcode-Interpre-

ter und einem Maschinencode-Compiler. • Ein Quellcode-Interpreter interpretiert die einzelnen QuellcodeAnweisungen eines Quellcode-Programms und führt den so erzeugten Maschinencode direkt aus. Ein Maschinencode-Compiler übersetzt das gesamte Programm in eine ausführbare Datei. • Die von einem Quellcode-Interpreter ausgeführten Programme sind langsamer als in Maschinencode kompilierte Programme, weil das Interpretieren der einzelnen Quellcode-Anweisungen viel Zeit in Anspruch nimmt und auch bei wiederholten Anweisungen immer wieder neu ausgeführt wird. • Ein Quellcode-Interpreter benötigt zur Ausführung den Quellcode des Programms. Ein in Maschinencode kompiliertes Programm kann ohne den Quellcode ausgeführt werden.

     

489

Sandini Bib

4. Was unterscheidet ein Zwischencode- von einem Maschinencode-

Programm? Ein Maschinencode-Programm wird direkt über das Betriebssystem ausgeführt. Ein Zwischencode-Programm muss hingegen erst noch von einem Zwischencode-Interpreter oder Just-In-Time-Compiler in Maschinencode übersetzt werden. Deshalb kann ein ZwischencodeProgramm aber auch für die CPU des ausführenden Systems optimiert werden und ist deswegen nicht wesentlich langsamer und in einigen Situationen vielleicht auch schneller als ein Maschinencode-Programm. 5. Nennen Sie zwei Vorteile einer verteilten gegenüber einer normalen

Anwendung. • In einer verteilten Anwendung können viele Client-Anwendungen eine Server-Komponente nutzen. Muss die Komponente geändert werden, muss das nur ein einziges Mal ausgeführt werden. Die Clients bleiben im Idealfall unangetastet. • Da in verteilten Anwendungen häufig sehr viel Arbeit auf dem Server ausgeführt wird, können für die Clients einfache, rechenschwache Computer verwendet werden. • Verteilte Anwendungen erlauben eine sehr flexible (weil oft selbst programmierte) Verwaltung von Benutzerrechten. 6. Warum müssen Sie einem Computer absolut exakt mitteilen, was er

zu tun hat, wenn Sie ein Programm schreiben? Ein Computer kann vage Angaben zurzeit noch nicht wie ein Mensch interpretieren, also mit vorhandenem Wissen assoziieren, daraus neue Erkenntnisse gewinnen, durch Experimentieren vertiefen und das neue Wissen speichern. Ansätze dafür gibt es allerdings bereits im Bereich der künstlichen Intelligenz.

A.1.4 Kapitel 4 1. Wie viele verschiedene Speicherbereiche verwendet ein Programm

bei der Ausführung der folgenden Berechnung: i = (1 + 2) * 3;

i ist dabei eine Variable. Das Programm verwendet sechs Speicherbereiche. In drei Speicherbereichen werden die Konstanten verwaltet. Ein Bereich wird für die Addition benötigt, ein weiterer für die Multiplikation (also das Ergebnis). Das Ergebnis wird dann noch in die Variable i geschrieben, die ja auch einen Speicherbereich darstellt.

490

$QKDQJ

Sandini Bib

2. Wieso kann ein Short-Integer-Datentyp lediglich Zahlen im Bereich

von –32768 bis 32767 speichern? Ein Short-Integer-Datentyp belegt 16 Bit im Arbeitsspeicher. Da es sich um eine Zahl mit Vorzeichen handelt, wird das linke Bit für das Vorzeichen verwendet. Damit bleiben 15 Bit für die Speicherung übrig, also können Zahlen im Bereich von -215 (oder anders ausgedrückt 20 + 21 + ... + 214) bis 215-1 gespeichert werden. Die absolut gesehen kleinere positive Zahl resultiert aus der Tatsache, dass Zahlen mit Vorzeichen so gespeichert werden, dass die Zahl 0 nicht zweimal verwaltet wird. 3. Was sollten Sie immer beachten, wenn Sie mit den Datentypen Float

oder Double arbeiten? Diese Datentypen bieten nur eine eingeschränkte Genauigkeit. Float erlaubt nur maximal 6 bis 7 Dezimalziffern, Double 15 bis 16. Bei vielen Vorkommaziffern reduziert sich die Anzahl der möglichen Nachkommaziffern. Berechnungen mit diesen Datentypen sind also immer ungenau, wenn Dezimalstellen vorkommen. 4. Wieso können Sie mit Datumsdatentypen arithmetische Berechnun-

gen ausführen (z. B. einem Datum 100 Tage aufaddieren)? Datumsdatentypen sind eigentlich einfache Zahlen. Bei Java werden die Millisekunden gezählt, die seit dem 1.1.1970 01:00 vergangen sind. Bei Delphi und Kylix werden die Tage gezählt, die seit dem 30.12.1899 vergangen sind. 5. Wann muss ein Datentyp, der einem anderen Datentyp zugewiesen

wird, explizit konvertiert werden? Eine explizite Konvertierung ist dann notwendig, wenn beim Konvertieren Datenverluste auftreten können oder wenn die Konvertierung auch komplett fehlschlagen kann. 6. Warum ergibt die folgende Division in einem Java-Programm nicht

wie erwartet 0,25, sondern 0,0? int zahl1 = 1; int zahl2 = 4; double ergebnis = zahl1 / zahl2;

Werden Integerwerte dividiert, führt Java grundsätzlich eine Ganzzahldivision aus. Lediglich dann, wenn mindestens ein Fließkommawert in der Divisions-Operation vorkommt, dividiert ein JavaProgramm die Zahlen normal.

     

491

Sandini Bib

7. Welchen Sinn hat die Einteilung der Klassen der Java-Bibliothek in

einzelne Pakete? Die Einteilung in Pakete ermöglicht das einfache Finden einer gesuchten Funktionalität, indem Sie ausgehend von einem Paket, das vom Namen her zu passen scheint, weiter nach unten in der Hierarchie der Pakete suchen.

A.1.5 Kapitel 5 1. Welchen Datentyp besitzen Vergleichsausdrücke?

Vergleichsausdrücke besitzen immer den Typ boolean. 2. Warum ergibt der Vergleich "a" == "a" in Java false?

Wenn Sie in Java Strings miteinander vergleichen, vergleichen Sie in Wirklichkeit zwei Objekte. Ein Objektvergleich vergleicht nicht den Inhalt der Objekte, sondern die Referenzen, die im Programm auf die Objekte zeigen. Der Vergleich zweier Referenzen, die auf zwei separate Objekte zeigen, ergibt immer false. 3. Worauf müssen Sie immer achten, wenn Sie komplexe Vergleichsaus-

drücke formulieren, die mit Und-Verknüpfungen, Oder-Verknüpfungen und/oder Negierungen arbeiten? Sie sollten die einzelnen Teilausdrücke immer klammern. Damit beugen Sie potenziellen Fehlern vor, die durch die Priorität der logischen Operatoren (not vor and vor or) entstehen können. Außerdem vermeiden Sie in Sprachen, bei denen die Operatoren not, and und or eine Doppelbedeutung besitzen, dass der Compiler fälschlicherweise eine Bit-Operation ausführt. 4. Warum sollten Sie die Anweisungen innerhalb einer Schleife und ei-

ner Verzweigung immer etwas nach rechts einrücken? Damit erreichen Sie, dass Ihr Programm wesentlich übersichtlicher wird, als wenn Sie nicht einrücken. Das Verständnis des Programms fällt Ihnen und anderen Programmierern dann leichter. 5. Was unterscheidet eine kopfgesteuerte von einer fußgesteuerten

Schleife? Eine fußgesteuerte Schleife prüft die Schleifenbedingung erst im Fuß und wird deswegen im Gegensatz zur kopfgesteuerten Schleife mindestens einmal durchlaufen.

492

$QKDQJ

Sandini Bib

6. Welchen Wert besitzt die Variable i nach der Ausführung der folgen-

den Schleife? for i := 1 to 10 do begin writeln(i); end;

Nach der Ausführung der Schleife besitzt i den Wert 11. Das Programm hat nach dem letzten Durchlauf (i = 10) diese Variable noch um 1 hochgezählt und dann festgestellt, dass die Bedingung (i