386 111 3MB
German Pages 734 Year 2006
Java ist auch eine Insel http://kickme.to/tiger/
Christian Ullenboom
J a va ist a uc h e ine I nse l Ein Kurs in Java und objektorientierter Programmierung
Mit Programmen für die Java 2 Plattform
1
ii
18
Schon wieder eine neue Sprache?
23
1.1
Historischer Hintergrund ......................................................................................... 24
1.2 1.2.1 1.2.2
Eigenschaften von Java ........................................................................................... 25 Die virtuelle Maschine ............................................................................................ 25 Konzepte einer modernen Programmiersprache ..................................................... 26
1.3
Java in Vergleich zu anderen Sprachen................................................................... 29
1.4
Die Rolle von Java im Web..................................................................................... 30
1.5
Aufkommen von Stand-Alone-Applikationen ........................................................ 30
1.6
Entwicklungsumgebungen ...................................................................................... 31
1.7 1.7.1
Erstes Programm compilieren und testen ................................................................ 34 Häufige Compiler- und Interpreterprobleme........................................................... 36
2
• • • • • •
Vorwort
Sprachbeschreibung
38
2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6
Grundlagen der Sprache Java .................................................................................. 38 Textcodierung durch Unicode-Zeichen................................................................... 38 Kommentare ............................................................................................................ 39 Bezeichner ............................................................................................................... 40 Reservierte Schlüsselwörter .................................................................................... 40 Token....................................................................................................................... 41 Semantik .................................................................................................................. 41
2.2 2.2.1 2.2.1 2.2.1 2.2.2
Anweisungen und Programme ................................................................................ 41 Programme .............................................................................................................. 42 Funktionsaufrufe als Anweisung............................................................................. 42 Die leere Anweisung ............................................................................................... 43 Der Block................................................................................................................. 43
2.3 2.3.1 2.3.2 2.3.3 2.3.1 2.3.2 2.3.3 2.3.4
Datentypen............................................................................................................... 44 Primitive Datentypen............................................................................................... 44 Wahrheitswerte........................................................................................................ 45 Variablendeklarationen............................................................................................ 45 Ganzzahlige Datentypen.......................................................................................... 46 Die Fließkommazahlen............................................................................................ 46 Zeichen .................................................................................................................... 47 Automatische Anpassung der Größe bei Zuweisungen........................................... 47
2.4 2.4.1 2.4.2
Ausdrücke................................................................................................................ 48 Operatoren ............................................................................................................... 48 Division und Modulo............................................................................................... 50
2.5 2.5.1 2.5.2
Verzweigungen........................................................................................................ 52 Die ›if‹ und ›if/else‹ Anweisung.............................................................................. 52 ›switch‹ bietet die Alternative ................................................................................. 55
2.6
Schleifen .................................................................................................................. 58
2.6.1 2.6.2 2.6.3 2.6.4
Die ›while‹ Schleife................................................................................................. 58 Die ›do/while‹ Schleife............................................................................................ 59 Die ›for‹ Schleife..................................................................................................... 60 Multilevel break und continue................................................................................. 63
2.7 2.7.1
Methoden einer Klasse ............................................................................................ 63 Rekursive Funktionen.............................................................................................. 65
2.8 2.8.1 2.8.2 2.8.1 2.8.2 2.8.3 2.8.4
Noch mehr Operatoren ............................................................................................ 68 Bit-Operationen ....................................................................................................... 68 Die Verschiebe-Operatoren ..................................................................................... 69 Anwendung für Bitoperatoren und Shift ................................................................. 71 Unterklassen prüfen................................................................................................. 74 Der Bedingungsoperator.......................................................................................... 74 Überladenes Plus für Strings ................................................................................... 76
3
Klassen und Objekte
77
3.1 3.1.1 3.1.2
Objektorientierte Programmierung.......................................................................... 77 Warum überhaupt OOP? ......................................................................................... 77 Modularität und Wiederverwertbarkeit ................................................................... 78
3.2 3.2.1 3.2.2
Klassen benutzen ..................................................................................................... 78 Anlegen eines Exemplars einer Klasse.................................................................... 79 Zugriff auf Variablen und Funktionen mit dem Punkt ............................................ 80
3.3 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5
Eigene Klassen definieren ....................................................................................... 81 Argumentübergabe .................................................................................................. 82 Lokale Variablen ..................................................................................................... 83 Die this-Referenz..................................................................................................... 83 Initialisierung von lokalen Variablen ...................................................................... 84 Privatsphäre ............................................................................................................. 84
3.4 3.4.1 3.4.2 3.4.3 3.4.4 3.4.1
Statische Methoden und Variablen.......................................................................... 86 Statische Variablen .................................................................................................. 86 Statische Methoden ................................................................................................. 86 Konstanten mit dem Schlüsselwort final bei Variablen .......................................... 87 Der Einstiegspunkt für das Laufzeitzeitsystem ....................................................... 87 Der Rückgabewert von main() ................................................................................ 89
3.5
Methoden überladen ................................................................................................ 89
3.6 3.6.1 3.6.2
Objekte anlegen und zerstören ................................................................................ 90 Die Erschaffung von Objekten ................................................................................ 90 Zerstörung eines Objekts durch den Müllaufsammler ............................................ 92
3.7
Gegenseitige Abhängigkeiten von Klassen ............................................................. 93
3.8 3.8.1
Vererbung ................................................................................................................ 94 Methoden überschreiben ......................................................................................... 96
3.9 3.9.1 3.9.2 3.9.3
Abstrakte Klassen und Interfaces ............................................................................ 96 Abstrakte Klassen .................................................................................................... 96 Schnittestellen (Interfaces) ...................................................................................... 98 Erweitern von Interfaces – Subinterfaces.............................................................. 100 • • • • • •
iii
3.9.4
Statische Initialisierung einer Schnittstelle ........................................................... 100
3.10 3.10.1 3.10.1
Innere Klassen ....................................................................................................... 102 Implementierung einer verketteten Liste............................................................... 102 Funktionszeiger ..................................................................................................... 104
3.11
Pakete .................................................................................................................... 105
3.12 3.12.1 3.12.2 3.12.3 3.12.4 3.12.5 3.12.6 3.12.7
Arrays .................................................................................................................... 106 Deklaration und Initialisierung.............................................................................. 107 Zugriff auf die Elemente ....................................................................................... 108 Arrays und Objekte................................................................................................ 109 Mehrdimensionale Arrays ..................................................................................... 110 Anonyme Felder .................................................................................................... 112 Die Wahrheit über die Array-Initialisierung ......................................................... 113 Arrays kopieren und füllen.................................................................................... 114
4
Exceptions 4.1 4.1.1 4.1.2 4.1.3 4.1.4
Problembereiche einzäunen................................................................................... 116 Exceptions in Java ................................................................................................. 116 Alles geht als Exception durch .............................................................................. 117 Die Throws-Klausel............................................................................................... 118 Abschließende Arbeiten mit finally....................................................................... 118
4.2 4.2.1 4.2.1
Exceptions sind nur Objekte.................................................................................. 119 Auswerfen von Exceptions.................................................................................... 119 Neue Exception-Klassen definieren ...................................................................... 120
4.3
Die Exception-Objekte in Java .............................................................................. 120
4.4
Ein Assert in Java .................................................................................................. 122
4.5 4.5.1
Sicherheitsfragen mit dem SecurityManager klären ............................................. 126 Programm beenden ................................................................................................ 126
5
• • iv •• • •
116
Die Funktionsbibliothek
127
5.1
Die Java-Klassenphilosophie................................................................................. 127
5.2 5.2.1
Die unterste Klasse Object .................................................................................... 128 Aufbau der Klasse Object...................................................................................... 128
5.3 5.3.1 5.3.2 5.3.3 5.3.4 5.3.1 5.3.1
Wrapper-Klassen (auch Ummantelungs-Klassen) ................................................ 132 Die Character Klasse ............................................................................................. 132 Die Boolean Klasse ............................................................................................... 133 Die Number Klasse................................................................................................ 134 Methoden der Wrapper-Klassen............................................................................ 135 Unterschiedliche Ausgabeformate......................................................................... 138 Zahlenbereiche kennen.......................................................................................... 139
5.4
Ausführung von Programmen in der Klasse Runtime........................................... 140
5.5 5.5.1
Compilieren von Klassen ...................................................................................... 140 Vorcompilierung durch einen JIT ......................................................................... 140
5.5.2
6
Der Sun Compiler.................................................................................................. 141
Der Umgang mit Zeichenketten
143
6.1 6.1.1 6.1.2
Strings und deren Anwendung .............................................................................. 143 Wie die String-Klasse benutzt wird....................................................................... 143 String-Objekte verraten viel .................................................................................. 145
6.2
Teile im String ersetzen......................................................................................... 146
6.3
Zeichenkodierungen umwandeln........................................................................... 147
6.4
Durchlaufen eines Strings mit der Klasse StringCharacterIterator ....................... 147
6.5
Der Soundex Code................................................................................................. 152
6.6
Sprachabhängiges Vergleichen mit der Collator-Klasse ....................................... 155
6.7 6.7.1 6.7.2
Die Klasse StringTokenizer................................................................................... 157 Ein Wortzerleger mit Geschwindigkeitsbetrachtung............................................. 159 Automatischer Wortumbruch ................................................................................ 161
6.8
StreamTokenizer.................................................................................................... 162
6.9 6.9.1 6.9.2
Formatieren mit Format Objekten ......................................................................... 165 Ausgaben formatieren ........................................................................................... 166 Dezimalzeichen formatieren.................................................................................. 169
6.10
Reguläre Ausdrücke in Java mit gnu.regexp ......................................................... 170
7
Mathematisches
173
7.1
Arithmetik in Java ................................................................................................. 173
7.2
Die Funktionen der Math Bibliothek..................................................................... 174
7.3
Die Random-Klasse............................................................................................... 175
7.4 7.4.1 7.4.2 7.4.1
Große Zahlen ......................................................................................................... 176 Die Klasse BigInteger............................................................................................ 176 Ganz lange Fakultäten ........................................................................................... 178 Konstruktoren und BigInteger Objekte verschieden erzeugen.............................. 178
7.5
Probleme mit Java und der Mathematik ................................................................ 180
7.6
Das Java Matrix Paket Jama.................................................................................. 181
8
Raum und Zeit
183
8.1
Wichtige Datums Klassen im Überblick ............................................................... 183
8.2 8.2.1
Die Klasse Date ..................................................................................................... 183 Zeitmessung und Profiling .................................................................................... 186
8.3
Die abstrakte Klasse Calendar............................................................................... 187
8.4 8.4.1
Der Gregorianische Kalender ................................................................................ 188 Zeitzonen durch die Klasse TimeZone repräsentiert............................................. 192 • • • • • •
v
8.5 8.5.1 8.5.1 8.5.1
9
Threads
203
9.1 9.1.1
Prozesse und Threads ............................................................................................ 203 Wie parallele Programme die Geschwindigkeit heben ......................................... 204
9.2 9.2.1
Die Zustände eines Threads................................................................................... 205 Erzeugen eines Threads ......................................................................................... 205
9.3 9.3.1 9.3.1
Threads schlafen .................................................................................................... 208 Das Ende eines Threads......................................................................................... 210 Arbeit niederlegen und wieder aufnehmen............................................................ 212
9.4
Priorität .................................................................................................................. 212
9.5
Dämonen................................................................................................................ 214
9.6
Kooperative und Nicht-Kooperative Threads ....................................................... 214
9.7
Synchronisation ..................................................................................................... 215
9.8
Beispiel Producer/Consumer ................................................................................. 216
9.9
Nachrichtenaustausch zwischen zwei Threads...................................................... 218
9.10
Erzeugte Threads zählen........................................................................................ 218
9.11
Grenzen von Threads............................................................................................. 220
9.12
Finde alle Threads, die gerade laufen.................................................................... 220
9.13 9.13.1 9.13.1
Gruppen von Threads in einer ThreadGroup......................................................... 221 Threads in einer ThreadGroup anlegen ................................................................. 224 Thread und ThreadGroup Methoden im Vergleich ............................................... 226
9.14
Einen Abbruch der virtuellen Maschine erkennen ................................................ 227
10
• • vi •• • •
Formatieren der Datumsangaben........................................................................... 193 Mit DateFormat und SimpleDateFormat formatieren ........................................... 194 Parsen von Texten ................................................................................................. 200 Parsen und Formatieren ab bestimmten Positionen............................................... 202
Datenstrukturen und Algorithmen
229
10.1 10.1.1
Mit einem Iterator durch die Daten wandern ........................................................ 229 Bauernregeln aufzählen ......................................................................................... 230
10.2
Dynamische Datenstrukturen ................................................................................ 231
10.3 10.3.1 10.3.1 10.3.1
Die Vector Klasse.................................................................................................. 232 Ein Polymorphie-Beispiel zur Klasse Vector........................................................ 235 Eine Aufzählung und gleichzeitiges Verändern .................................................... 237 Die Funktionalität eines Vectors erweitern ........................................................... 238
10.4 10.4.1 10.4.2
Stack, der Stapel .................................................................................................... 239 Das oberste Stackelement duplizieren................................................................... 239 Ein Stack ist ein Vector – Aha! ............................................................................. 241
10.5
Die Klasse BitSet für Bitmengen .......................................................................... 241
10.6 10.6.1 10.6.2 10.6.3 10.6.4 10.6.5 10.6.6
Die Klasse Hashtable............................................................................................. 243 Ein Hashtable Objekt erzeugen ............................................................................. 243 Einfügen und Abfragen der Datenstruktur ............................................................ 244 Die Arbeitsweise einer Hashtabelle....................................................................... 245 Klonen ................................................................................................................... 246 Aufzählen der Elemente ........................................................................................ 247 Ausgabe der Hashtabelle und Gleichheitstest ....................................................... 248
10.7
Die abstrakte Klasse Dictionary ............................................................................ 249
10.8
Die Properties Klasse ............................................................................................ 250
10.9
Queue, die Schlange .............................................................................................. 254
10.10 10.10.1 10.10.2 10.10.3 10.10.4 10.10.5 10.10.1 10.10.2
Die Collection API ................................................................................................ 256 Die Schnittstelle Collection................................................................................... 256 Schnittstellen, die Collection erweitern................................................................. 257 Abstrakte Collection Klassen als Basisklassen ..................................................... 258 Konkrete Container-Klassen ................................................................................. 259 Das erste Collection Programm............................................................................. 259 Iteratoren................................................................................................................ 260 Der Comperator ..................................................................................................... 261
10.11 10.11.1 10.11.1 10.11.1
Listen ..................................................................................................................... 262 AbstractList ........................................................................................................... 263 ArrayList................................................................................................................ 266 LinkedList.............................................................................................................. 267
10.12 10.12.1 10.12.2 10.12.3 10.12.1
Algorithmen........................................................................................................... 267 Datenmanipulation ................................................................................................ 268 Größte und kleinste Werte einer Collection finden............................................... 270 Sortieren ................................................................................................................ 271 Elemente in der Collection suchen ........................................................................ 273
10.13
Typsichere Datenstrukturen................................................................................... 273
10.14 10.14.1 10.14.2 10.14.1
Ein Design-Pattern durch Beobachten von Änderungen....................................... 275 Design Pattern ....................................................................................................... 275 Das Beobachter-Pattern (Observer/Observable) ................................................... 275 Compiler mit generischen Klassen: Pizza ............................................................. 278
11
Datenströme und Dateien
279
11.1 11.1.1
Übersicht über wichtigste Stream- und Writer/Reader-Klassen ........................... 280 Die abstrakten Basisklassen .................................................................................. 281
11.2 11.2.1 11.2.2 11.2.1 11.2.2 11.2.1 11.2.1 11.2.1
Ein- und Ausgabeklassen Input/OutputStream ..................................................... 281 Die Klasse OutputStream ...................................................................................... 282 Ein Datenschlucker................................................................................................ 283 Die Eingabeklasse InputStream............................................................................. 284 Anwenden der Klasse FileInputStream ................................................................. 284 Anwendung der FileOutputStream Klasse ............................................................ 285 Kopieren von Dateien............................................................................................ 286 Daten filtern durch FilterInputStream/FilterOutputStream ................................... 287 • • • vii • • •
11.2.2 11.2.3 11.2.1 11.2.2
Der besonderer Filter PrintStream......................................................................... 289 System Ein- und Ausgabe und Input- PrintStreams .............................................. 290 Bytes in den Strom mit ByteArrayOutputStream.................................................. 294 Die SequenceInputStream Klasse.......................................................................... 295
11.3 11.3.1 11.3.2 11.3.3 11.3.1
Die Writer Klassen ................................................................................................ 297 Die abstrakte Klasse Writer................................................................................... 298 Datenkonvertierung durch den OutputStreamWriter ............................................ 299 In Dateien schreiben mit der Klasse FileWriter .................................................... 301 StringWriter und CharArrayWriter ....................................................................... 302
11.4 11.4.1 11.4.1 11.4.2
Erweitern der Writer Funktionalität ...................................................................... 304 Gepufferte Ausgabe durch BufferedWriter ........................................................... 304 Ausgabemöglichkeiten durch PrintWriter erweitern............................................. 306 Daten mit FilterWriter filtern ................................................................................ 308
11.5 11.5.1 11.5.2 11.5.3 11.5.1 11.5.1 11.5.2 11.5.1 11.5.1 11.5.1
Die Reader Klassen ............................................................................................... 313 Die abstrakte Basisklasse Reader .......................................................................... 313 Automatische Konvertierungen mit dem InputStreamReader .............................. 315 Dateien lesen mit der Klasse FileReader............................................................... 316 StringReader und CharArrayReader...................................................................... 317 Schachteln von Eingabe-Streams .......................................................................... 318 Gepufferte Eingaben mit der Klasse BufferedReader ........................................... 318 LineNumberReader zählt automatisch Zeilen mit................................................. 320 Eingaben filtern mit der Klasse FilterReader ........................................................ 321 Daten wieder zurück in den Eingabestrom mit PushbackReader.......................... 323
11.6 11.6.1 11.6.2 11.6.1 11.6.1 11.6.1
Dateien................................................................................................................... 326 Das File Objekt...................................................................................................... 326 Die Wurzel aller Verzeichnisse ............................................................................. 328 Verzeichnisse listen und Dateien filtern................................................................ 329 Änderungsdatum einer Datei................................................................................. 332 Dateien mit wahlfreiem Zugriff (Random-Access-Files)...................................... 333
11.7 11.7.1 11.7.1
Datenkompression ................................................................................................. 336 Datenströme komprimieren ................................................................................... 337 ZIP Dateien............................................................................................................ 340
11.8 11.8.1 11.8.2 11.8.1
Prüfsummen........................................................................................................... 347 Die Schnittstelle Checksum................................................................................... 347 Die Klasse CRC32................................................................................................. 348 Die Adler-32 Klasse .............................................................................................. 350
11.9 11.9.1 11.9.2 11.9.3 11.9.4 11.9.5
Persistente Objekte und Serialisierung.................................................................. 351 Objekte speichern .................................................................................................. 351 Objekte lesen ......................................................................................................... 353 Das Inferface Serializable...................................................................................... 354 Beispiele aus den Standardklassen ........................................................................ 356 Wie funktioniert Serialisierung?............................................................................ 361
12
Grafikprogrammierung mit dem AWT 12.1
• • viii •• • •
364
Fenster (Windows) unter grafischen Oberflächen................................................. 364
12.1.1
Fenster öffnen........................................................................................................ 364
12.2 12.2.1
Grundlegendes zum Zeichnen ............................................................................... 367 Die paint() Methode .............................................................................................. 367
12.3
Punkte und Linien.................................................................................................. 369
12.4
Rechtecke aller Art ................................................................................................ 370
12.5
Alles was rund ist .................................................................................................. 371
12.6 12.6.1 12.6.2 12.6.1
Linenzüge sind Polygone und Poylines................................................................. 372 Die Polygon-Klasse ............................................................................................... 373 N-Ecke zeichnen.................................................................................................... 374 Vollschlanke Linien zeichnen ............................................................................... 376
12.7 12.7.1 12.7.2 12.7.1 12.7.2
Zeichenketten schreiben ........................................................................................ 377 Einen neuen Zeichensatz bestimmen..................................................................... 377 Zeichensätze des Systems ermitteln ...................................................................... 378 Die Klasse FontMetrics ......................................................................................... 380 Logische und native Fontnamen in font.properties ............................................... 381
12.8
Clipping-Operationen ............................................................................................ 384
12.9 12.9.1 12.9.1 12.9.2 12.9.3 12.9.1 12.9.2 12.9.1 12.9.2
Farben .................................................................................................................... 387 Zufällige Farbblöcke zeichnen .............................................................................. 388 Farbbereiche zurückgeben..................................................................................... 389 Vordefinierte Farben ............................................................................................. 390 Farben aus Hexadezimalzahlen erzeugen.............................................................. 390 Einen helleren und dunkleren Farbton wählen...................................................... 392 Farben nach Namen auswählen ............................................................................. 393 Farbmodelle HSB und RGB.................................................................................. 395 Die Farben des Systems......................................................................................... 395
12.10 12.10.1 12.10.1 12.10.2 12.10.3
Bilder anzeigen und Grafiken verwalten............................................................... 400 Die Grafik zeichnen............................................................................................... 402 Grafiken zentrieren ................................................................................................ 404 Laden von Bildern mit dem MediaTracker beobachten ........................................ 404 Kein Flackern durch Double-Buffering................................................................. 409
12.11 12.11.1 12.11.2 12.11.3 12.11.4 12.11.1
Von Produzenten, Konsumenten und Beobachtern............................................... 411 Producer und Consumer für Bilder........................................................................ 411 Beispiel für die Übermittlung von Daten .............................................................. 412 Ein PPM Grafik Lader als ImageConsumer.......................................................... 414 Bilder selbst erstellen ............................................................................................ 415 Die Bildinformationen wieder auslesen ................................................................ 419
12.12 12.12.1 12.12.2 12.12.3
Alles wird bunt mit Farbmodellen......................................................................... 421 Die abstrakte Klasse ColorModel.......................................................................... 422 Farbwerte im Pixel mit der Klasse DirectColorModel.......................................... 424 Die Klasse IndexColorModel ................................................................................ 425
12.13 12.13.1 12.13.1 12.13.2
Grafiken speichern................................................................................................. 428 Bilder im GIF-Format speichern ........................................................................... 428 Gif Speichern mit dem ACME Paket .................................................................... 430 JPEG Dateien mit dem Sun Paket schreiben......................................................... 430 • • • • • •
ix
12.13.1 Java Image Management Interface (Jimi) ............................................................. 433 12.14
Programmicon setzen ............................................................................................ 434
12.15 Filter ...................................................................................................................... 435 12.15.1 Tansparenz............................................................................................................. 435 12.16
Drucken der Fensterinhalte.................................................................................... 436
12.17 12.17.1 12.17.1 12.17.2 12.17.3
Java 2D API........................................................................................................... 437 Grafische Objekte zeichnen................................................................................... 437 Geometrische Objekte durch Shape gekennzeichnet ............................................ 439 Eigenschaften Geometrischer Objekte .................................................................. 440 Transformationen mit dem AffineTransform Objekt ............................................ 443
12.18
Graphic Layers Framework................................................................................... 444
12.19
Grafikverarbeitung ohne grafische Oberfläche ..................................................... 444
13
x
• • • • • •
Komponenten und Container
446
13.1 13.1.1 13.1.1
Das Toolkit ............................................................................................................ 447 Einen Hinweis beepen ........................................................................................... 447 Größe und Position des Fensters verändern .......................................................... 448
13.2 13.2.1
Es tut sich sich was. Ereignisse beim AWT .......................................................... 449 Das Fenster schließen ............................................................................................ 449
13.3 13.3.1 13.3.2 13.3.3 13.3.1 13.3.2 13.3.1 13.3.1 13.3.2 13.3.1 13.3.1 13.3.1 13.3.2 13.3.3 13.3.1 13.3.2 13.3.1 13.3.1 13.3.2
Komponenten ........................................................................................................ 452 Die Basis aller Komponenten: Die Klasse Components ....................................... 452 Ereignisse der Komponenten................................................................................. 453 Proportionales Vergrößern eines Fensters............................................................. 453 Hinzufügen von Komponenten.............................................................................. 455 Ein Informationstext – Der Label.......................................................................... 455 Eine Schaltfläche (Button) .................................................................................... 458 Der aufmerksame ActionListener.......................................................................... 459 Horizontale und vertikale Balken – Der Scrollbar ................................................ 461 Ein Auswahlmenü – Das Choice-Menü ................................................................ 465 Einer aus vielen – Kontrollfelder (Checkbox)....................................................... 468 Ereignisse über ItemListener................................................................................. 470 Optionsfelder ......................................................................................................... 470 List-Boxen ............................................................................................................. 471 Texteingabe in einer Eingabezeile......................................................................... 474 Mehrzeilige Textfelder .......................................................................................... 474 Menüs .................................................................................................................... 476 Popup-Menüs......................................................................................................... 483 Ereignissverarbeitung auf unterster Ebene ............................................................ 483
13.4 13.4.1 13.4.1 13.4.1
Alles Auslegungssache: Die Layout-Manager ...................................................... 484 FlowLayout............................................................................................................ 484 BorderLayout......................................................................................................... 486 GridLayout ............................................................................................................ 487
13.5 13.5.1
Dialoge .................................................................................................................. 489 Der Dateiauswahl-Dialog ...................................................................................... 489
13.6
14
Die Zwischenablage (Clipboard)........................................................................... 491
Let’s Swing
494
14.1
Java Foundation Classes........................................................................................ 494
14.2
Das Model-View-Controller Konzept ................................................................... 495
14.3 14.3.1
Der Inhalt einer Zeichenfläche, JPanel.................................................................. 496 Das Swing Gerüst für weitere Programme ............................................................ 497
14.4
JLabel .................................................................................................................... 498
14.5 14.5.1 14.5.1
Die Klasse ImageIcon............................................................................................ 499 Die Schnittstelle Icon ............................................................................................ 500 Was Icon und Image verbindet.............................................................................. 501
14.6 14.6.1 14.6.2 14.6.1 14.6.2 14.6.3
Die Schaltflächen von Swing ................................................................................ 502 JButton................................................................................................................... 502 AbstractButton....................................................................................................... 503 JToggleButton ....................................................................................................... 504 JCheckBox............................................................................................................. 504 Radiogruppen ........................................................................................................ 505
14.7
Tooltips.................................................................................................................. 505
14.8
JScrollBar .............................................................................................................. 506
14.9
JSlider .................................................................................................................... 506
14.10
Der Fortschrittsbalken JProgressBar ..................................................................... 507
14.11
JComboBox ........................................................................................................... 508
14.12
Symbolleisten alias Toolbars................................................................................. 509
14.13 Dialoge .................................................................................................................. 510 14.13.1 Der Farbauswahl-Dialog JColorChooser .............................................................. 510 14.14 Texteingaben ......................................................................................................... 510 14.14.1 JPasswordField ...................................................................................................... 510 14.14.2 Die Editor Klasse JEditorPane .............................................................................. 511 14.15
Das Java Look&Feel ............................................................................................. 512
14.16 Migration ............................................................................................................... 512 14.16.1 Pakete an verschiedenen Orten.............................................................................. 512
15
Java Media
514
16
Netzwerkprogrammierung
516
16.1 16.1.1
Grundlegende Begriffe .......................................................................................... 516 Internet Standards und RFC .................................................................................. 517
16.2 16.2.1 16.2.1
URL Verbindungen ............................................................................................... 517 URL Objekte erzeugen .......................................................................................... 518 Informationen über eine URL ............................................................................... 521 • • • • • •
xi
• • xii •• • •
16.2.1
Der Zugriff auf die Daten über die Klasse URL ................................................... 523
16.3 16.3.1 16.3.2 16.3.3
Die Klasse URLConnection .................................................................................. 525 Methoden und Anwendung von URLConnection................................................. 525 Protokoll- und Content-Handler ............................................................................ 526 Im Detail: Von URL zu URLConnection.............................................................. 528
16.4 16.4.1 16.4.2 16.4.1
Das Common Gateway Interface........................................................................... 529 Parameter für ein CGI-Programm ......................................................................... 529 Codieren der Parameter für CGI Programme........................................................ 530 Eine Suchmaschine ansprechen............................................................................. 531
16.5
Hostadresse und IP-Adressen ................................................................................ 532
16.6 16.6.1 16.6.2 16.6.3 16.6.4 16.6.5
Socketprogrammierung ......................................................................................... 535 Das Netzwerk ist der Computer ............................................................................ 535 Standarddienste unter Windows nachinstallieren.................................................. 536 Streamsockets ........................................................................................................ 537 Informationen über den Socket ............................................................................. 539 Ein kleines Ping – lebt der Rechner noch? ............................................................ 539
16.7 16.7.1
Client/Server-Kommunikation .............................................................................. 540 Ein Multiplikations-Server .................................................................................... 541
16.8 16.8.1
Weitere Systemdienste .......................................................................................... 542 Mit telnet an den Ports horchen............................................................................. 542
16.9
Das File Transfer Protocol (FTP) .......................................................................... 543
16.10 16.10.1 16.10.2 16.10.3 16.10.4
E-Mail verschicken................................................................................................ 544 Wie eine E-Mail um die Welt geht........................................................................ 544 Übertragungsprotokolle ......................................................................................... 544 Das Simple Mail Transfer Protocol ....................................................................... 547 Demoprogramm, welches eine E-Mail abschickt.................................................. 550
16.11 16.11.1 16.11.2 16.11.3
Arbeitsweise eines Web-Servers ........................................................................... 551 Das Hypertext Transfer Protocol (HTTP) ............................................................. 551 Anfragen an den Server ......................................................................................... 552 Die Antworten vom Server.................................................................................... 554
16.12 16.12.1 16.12.2 16.12.3 16.12.1 16.12.2 16.12.1 16.12.1
Datagramsockets.................................................................................................... 557 Die Klasse DatagramSocket .................................................................................. 559 Datagramme und die Klasse DatagramPacket ...................................................... 559 Auf ein einkommendes Paket warten .................................................................... 560 Ein Paket zum Senden vorbereiten........................................................................ 561 Methoden der Klasse DatagramPacket.................................................................. 562 Das Paket senden................................................................................................... 562 Die Zeitdienste und ein eigener Server und Client................................................ 563
16.13
Internet Control Message Protocol (ICMP) .......................................................... 566
16.14
Multicast-Kommunikation .................................................................................... 566
17
Verteilte Anwendungen mit RMI und Corba
567
18
Datenbankmanagement mit JDBC
569
18.1
JDBC: Der Zugriff auf Datenbanken über Java .................................................... 569
18.2
Das relationale Modell .......................................................................................... 570
18.3 18.3.1 18.3.2
Die Rolle von SQL ................................................................................................ 570 Ein Rundgang durch SQL Anfragen ..................................................................... 571 Datenabfrage mit der Data Query Language (DQL)............................................. 572
18.4 18.4.1 18.4.2 18.4.3
Die quasi-freie Datenbank mSQL ......................................................................... 574 Leistung von mSQL .............................................................................................. 574 mSQL unter Windows einsetzen ........................................................................... 574 mSQL starten und benutzen .................................................................................. 575
18.5
Die Windows Datenbank Microsoft-Access ......................................................... 576
18.6 18.6.1 18.6.2 18.6.3 18.6.4 18.6.5
Datenbanktreiber für den Zugriff .......................................................................... 577 Lösungen für JDBC............................................................................................... 577 Die JDBC-ODBC Bridge ...................................................................................... 580 ODBC einrichten und Access damit verwenden ................................................... 580 Der Java Datenbanktreiber mSQL-JDBC ............................................................. 582 Den mSQL-JDBC Treiber installieren .................................................................. 582
18.7
Eine Beispiel-Abfrage ........................................................................................... 583
18.8 18.8.1 18.8.2 18.8.3 18.8.4 18.8.5 18.8.6
Mit Java an eine Datenbank andocken .................................................................. 584 Der Treibermanager............................................................................................... 584 Eine Aufzählung aller Treiber ............................................................................... 584 Log-Informationen ................................................................................................ 585 Den Treiber laden .................................................................................................. 586 Wie Treiber programmiert sind ............................................................................. 586 Verbindung zur Datenbank.................................................................................... 588
18.9 18.9.1 18.9.2
Datenbankabfragen................................................................................................ 590 Abfragen über das Statement Objekt..................................................................... 590 Ergebnisse einer Abfrage im ResultSet................................................................. 591
18.10
Java und SQL Datentypen ..................................................................................... 592
18.11
Elemente einer Datenbank hinzufügen.................................................................. 595
18.12 MetaDaten ............................................................................................................. 595 18.12.1 Metadaten über die Tabelle ................................................................................... 595 18.12.2 Informationen über die Datenbank........................................................................ 598 18.13
19
Exception Typen von JDBC.................................................................................. 599
Applets
600
19.1
Das erste Hallo-Applet .......................................................................................... 601
19.2 19.2.1
Parameter an das Applet übergeben ...................................................................... 602 Vom Applet den Browerinhalt ändern .................................................................. 602 • • • xiii • • •
19.2.2 19.2.3
Woher wurde das Applet geladen.......................................................................... 603 Was ein Applet alles darf ...................................................................................... 603
19.3 19.3.1 19.3.1 19.3.1
Musik in einem Applet .......................................................................................... 604 Fest verdrahtete Musikdatei .................................................................................. 604 Variable Musikdatei über einen Parameter ........................................................... 605 WAV und MIDI-Dateien abspielen....................................................................... 605
19.4 19.4.1 19.4.2 19.4.1
Browserabhängiges Verhalten............................................................................... 606 Java im Browser aktiviert? .................................................................................... 606 Läuft das Applet unter Netscape oder Microsoft Explorer?.................................. 607 Datenaustausch zwischen Applets und Java Skripten ........................................... 608
19.5
Applets und Applikationen kombinieren............................................................... 608
19.6
Datenaustausch zwischen Applets......................................................................... 608
20
Reflection
612
20.1
Einfach mal reinschauen........................................................................................ 612
20.2 20.2.1 20.2.2 20.2.1 20.2.1 20.2.1 20.2.1 20.2.2 20.2.1 20.2.1
Mit dem Class Objekt etwas über Klassen erfahren.............................................. 612 An ein Class Objekt kommen................................................................................ 612 Was das Class Objekt beschreibt........................................................................... 614 Der Name der Klasse............................................................................................. 616 Superklassen und zu implementierende Schnittstellen finden .............................. 617 Implementierte Interfaces einer Klasse/eines Inferfaces ....................................... 618 Modifizierer und die Klasse Modifier ................................................................... 619 Die Attribute einer Klasse ..................................................................................... 620 Methoden............................................................................................................... 623 Konstruktoren einer Klasse ................................................................................... 625
20.3 20.3.1 20.3.1
Objekte manipulieren ............................................................................................ 626 Objekte erzeugen ................................................................................................... 626 Variablen setzen .................................................................................................... 629
20.4 20.4.1
Methoden aufrufen ................................................................................................ 631 Dynamische Methodenaufrufe bei festen Methoden beschleunigen..................... 631
20.5
Ein größeres Beispiel............................................................................................. 633
21
Komponenten durch Bohnen
638
22
Sicherheitskonzepte
639
• • xiv •• • •
22.1
Der Sandkasten (Sandbox) .................................................................................... 639
22.2 22.2.1 22.2.2 22.2.3 22.2.4 22.2.1
Sicherheitsmanager (Security Manager) ............................................................... 639 Der Sicherheitsmanager bei Applets ..................................................................... 640 Sicherheitsmanager aktivieren............................................................................... 643 Der Sicherheitsmanager in den Java Bibliotheken ................................................ 643 Ein eigener Sicherheitsberater............................................................................... 644 Übersicht über die Methoden ................................................................................ 647
22.3 22.3.1
Klassenlader (Class Loader).................................................................................. 650 Wie die Klasse mit dem main() heißt .................................................................... 650
22.4 22.4.1 22.4.2 22.4.3 22.4.4
Digitale Unterschriften .......................................................................................... 651 Die MDx Reihe...................................................................................................... 652 Secure Hash Algorithm (SHA).............................................................................. 652 Mit der Security API einen Fingerabdruck berechnen .......................................... 653 Die Klasse MessageDigest .................................................................................... 653
22.5
Zertifikate .............................................................................................................. 656
23
Die Java Virtuelle Maschine 23.1 23.1.1 23.1.2
24
659
Format der Klassendatei ........................................................................................ 662 Constant Pool......................................................................................................... 663 Attribute einer Klasse ............................................................................................ 664
Die Werkzeuge des JDK
665
24.1
Die Werkzeuge im Überblick ................................................................................ 665
24.2
Der Compiler ›javac‹............................................................................................. 665
24.3 24.3.1 24.3.2 24.3.3
Das Archivformat Jar ............................................................................................ 666 Das Dienstprogramm Jar benutzen........................................................................ 667 Das Manifest.......................................................................................................... 669 Jar Archive für Applets und Applikation .............................................................. 669
24.4 24.4.1 24.4.2 24.4.3 24.4.4
Mit Doclets Javaprogramme dokumentieren......................................................... 671 Mit JavaDoc Dokumentationen erstellen .............................................................. 671 Wie JavaDoc benutzt wird..................................................................................... 672 Doclets programmieren ......................................................................................... 672 Das Standard-Doclet.............................................................................................. 674
25
Zusatzprogramme für die Java-Umgebung
680
25.1 25.1.1 25.1.2 25.1.3
Konverter von Java nach C.................................................................................... 680 Toba....................................................................................................................... 680 Arbeitsweise von Toba .......................................................................................... 681 Abstriche des Konverters ...................................................................................... 681
25.2
Der alternative Java-Bytecode-Compiler ›guavac‹ ............................................... 682
25.3
Die alternative JVM ›Kaffe‹.................................................................................. 683
25.4 25.4.1 25.4.2
Decompiler ............................................................................................................ 684 Jad, ein scheller Decompiler.................................................................................. 685 SourceAgain .......................................................................................................... 687
25.5
Obfuscate Programm ............................................................................................. 687
25.6
Source-Code Verschönerer (Beautifier) ................................................................ 687
• • • xv • • •
26
Style-Guides
689
26.1
Programmierrichtlinien.......................................................................................... 689
26.2
Allgemeine Richtlinien.......................................................................................... 690
26.3 26.3.1 26.3.2
Quellcode kommentieren....................................................................................... 690 Bemerkungen für JavaDoc .................................................................................... 692 Gotcha Schlüsselwörter ......................................................................................... 693
26.4 26.4.1 26.4.2
Bezeichnernamen .................................................................................................. 694 Ungarische Notation.............................................................................................. 694 Vorschlag für die Namensgebung ......................................................................... 694
26.5 26.5.1 26.5.2 26.5.3 26.5.4
Formatierung ......................................................................................................... 695 Einrücken von Programmcode – die Vergangenheit............................................. 696 Verbundene Ausdrücke ......................................................................................... 696 Kontrollierter Datenfluss ....................................................................................... 697 Funktionen............................................................................................................. 698
26.6
Ausdrücke.............................................................................................................. 699
26.7 26.7.1 26.7.2
Anweisungen ......................................................................................................... 700 Schleifen ................................................................................................................ 700 Switch, Case und Durchfallen ............................................................................... 702
26.8
Klassen .................................................................................................................. 703
26.9 26.9.1
Zugriffsrechte ........................................................................................................ 703 Accessors/Zugriffsmethoden................................................................................. 704
A
Die Java Grammatik
705
26.10
Die lexikalische Struktur ....................................................................................... 705
26.11
Typen, Werte und Variablen ................................................................................. 705
26.12
Bezeichner ............................................................................................................. 706
26.13
Pakete .................................................................................................................... 706
26.14
Produktionen für die LALR(1) Grammatik........................................................... 707
26.15 26.15.1 26.15.2 26.15.3 26.15.4 26.15.5
Klassen .................................................................................................................. 707 Klassendeklaration ................................................................................................ 707 Attribute................................................................................................................. 708 Methoden............................................................................................................... 708 Statische Initialisierungen ..................................................................................... 709 Konstruktoren ........................................................................................................ 709
26.16 Schnittstellen ......................................................................................................... 709 26.16.1 Schnittstellendefinitionen ...................................................................................... 709 26.16.2 Felder..................................................................................................................... 710
• • xvi •• • •
26.17
Blöcke und Anweisungen...................................................................................... 710
26.18
Ausdrücke.............................................................................................................. 713
B
Quellenverzeichnis
717
• • • xvii • • •
Vorwort Mancher glaubt schon darum höflich zu sein, weil er sich überhaupt noch der Worte und nicht der Fäuste bedient. – Hebbel
Java ist auch eine I nsel Java wurde am 23. Mai 1995 auf der SunWorld in San Francisco als neue Programmiersprache vorgestellt. Sie gibt uns elegante Programmiermöglichkeiten; nichts Neues, aber so gut verpackt und verkauft, dass sie angenehm und flüssig zu Programmieren ist. Dieses Tutorial beschäftigt sich in 26 Kapiteln mit Java, den Klassen, der Design-Philosophie und der Programmierung.
I nhalt Kapitel 1: Schon wieder eine neue Sprache? Kapitel 2: Sprachbeschreibung. Kapitel 3: Klassen und Objekte. Kapitel 4: Exceptions. Kapitel 5: Die Funktionsbibliothek. Kapitel 6: Der Umgang mit Zeichenketten. Kapitel 7: Mathematisches. Kapitel 8: Raum und Zeit. Kapitel 9: Threads. Kapitel 10: Algorithmen und Datenstrukturen. Kapitel 11: Datenströme und Dateien. Kapitel 12: Oberflächenprogrammierung mit dem AWT. Kapitel 13: Let’s Swing. Kapitel 14: Grafikprogrammierung. Kapitel 15: Java Media. Kapitel 16: Netzwerkprogrammierung. Kapitel 17: Verteilte Anwendungen mit RMI und CORBA. Kapitel 18: Datenbankmanagement mit JDBC. Kapitel 19: Applets. Kapitel 20: Reflection. Kapitel 21: Komponenten durch Bohnen. Kapitel 22: Sicherheitskonzepte. Kapitel 23: Die Java Virtuelle Maschine. Kapitel 24: Die Werkzeuge des JDK. Kapitel 25: Zusatzprogramme für die Java-Umgebung. Kapitel 26: Style-Guide. Anhang A: Die Java Grammatik Anhang B: Quellenverzeichnis
• • 18 •• • •
Konvent ionen In diesem Buch werden folgende Konventionen verwendet: Listings und Methoden sind in n i c h t p r o p o r t i o n a l e r Schrift gesetzt. Bei Methodennamen folgt immer ein Klammerpaar. Die Parameter sind nicht immer aufgeführt. Neu eingeführte Begriffe sind kursiv gesetzt und der Index verweist genau auf diese Stelle. Des weiteren sind Dateinamen und Dateiendungen (.txt) kursiv. Internetadressen sind unterstrichen. Komplette Programmlistings sind wie folgt aufgebaut: Quellcode 0.0
Javaprogrammname.java
c l a s s Tr a l l a l a ..
Der Quellcode gehört somit zur Klasse Javaprogrammname.java. Methoden oder Konstruktoren werden in einer speziellen Auflistung aufgeführt, die ein leichtes Finden erlauben. Im Rechteck steht der voll qualifizierte Klassen- beziehungsweise Schnittstellenname. In nachfolgenden Zeilen sind geerbte Oberklassen und implementierte Schnittstellen aufgeführt. a b s t r a c t c l a s s j a v a . t e x t . Da t e Fo r ma t e x t e n d s Fo r ma t i mp l e me n t s Cl o n e a b l e Ÿ Da t e p a r s e ( St r i n g ) t h r o ws Pa r s e Ex c e p t i o n
Parst einen Datum- oder einen Zeit-String. Da jede Klasse, die keine direkte Oberklasse hat, automatisch von Ob j e c t erbt, ist diese nicht extra angegeben.
Mot ivat ion für das Buch – Oder warum es noch ein Java Buch gibt ... Die Beschäftigung mit Java hängt eng mit einer universitären Pflichtveranstaltung zusammen; meiner Projektgruppe zur objektorientierten Dialogspezifikation um 1997. Und weil ich die Teilnehmer davon überzeugen wollte, Java als Programmiersprache einzusetzen (und nicht Objective-C), arbeitete ich meinen ersten Foliensatz für den Seminarvortrag aus. Dieser wurde auch die Basis für meine Schulungsunterlagen, die ich in fast 40 Kursen immer weiter verbessert habe. Als ich dann noch die Seminararbeit schreiben musste, sind die geplanten Seminarseiten schon in ein kleines Buch ausgeartet. Es kam sogar dazu, dass die sogenannte Seminararbeit schon sehr viele Seiten fasste und nachher die jetzige Einleitung mehr oder weniger zur Seminararbeit verwurstet wurde. Zumal das Tutorial zwischendurch immer dicker geworden ist. Dass es mich über die universitäre Pflicht hinaus zum Schreiben treibt, ist nur eine Lern-Strategie. Wenn ich mich in neue Gebiete einarbeite, dann lese ich erst einmal quantitativ auf Masse und beginne dann Zusammenfassungen zu schreiben. Und erst beim Schreiben wird mir erst richtig bewusst, was ich nicht weiß. Dann zeigt sich auch meine Liebe für’s Detail. Das Lernen durch Schreiben hat mir auch bei einem anderen Buch sehr geholfen, das leider nicht veröffentlicht wurde.1 Es ist ein Assembler Buch für den MC680x0 im Amiga. Das waren auch um die 800 Seiten,
• • • 19 • • •
aber die Verlage konnten mir nur sagen, dass die Zeit des Amigas vorbei ist. (Komisch, woher die das wussten?) Die Prognosen für Java stehen schon besser, denn der Einsatz von Java in der Wirtschaft fängt gerade erst richtig an. Und dann gibt es das Buch vielleicht doch mal zu kaufen... Nun freue ich mich, dass das Tutorial so gut angenommen wurde und jetzt erfreulich detailreich ist. Mittlerweise hat mir die Arbeit mit Java auch diverse Dozenten-Stellen verschafft – so ein Buch ist prima Werbung ;-) Es freut mich auch, dass ausgewählte Kapitel des Tutorials (welches ich immer ›Insel‹ nenne) mittlerweile auch als Schulungsunterlage Verwendung findet.
Und für wen ist j et zt das Tut orial? Aus der oberen Beschreibung folgt, dass ich das Buch nicht für eine bestimmte Zielgruppe entwikkele. Es ist für mich entstanden, um Java gut kennenzulernen. Das heißt aber lange noch nicht, dass es für alle anderen Leser völlig unbrauchbar ist, denn Anfänger sowie Fortgeschrittene können von dem Buch profitieren. Die Vorraussetzungen sind Kenntnis einer imperativen Programmiersprache und Verständnis für objektorientierte Technologien. An einigen Stellen werden Verweise auf C(++) gezogen, diese sind aber nicht wesentlich für das Verständnis, sondern dienen nur zum Vergleich. Programmiereinsteiger, die keine Programmiererfahrung besitzen, sollten ein zusätzliches Buch verwenden. Erfahrenere Programmierer können die ersten Kapitel überspringen und routinierte Softwareentwickler beginnen erst in der hinteren Hälfte.
Wird sich das Buch in seinem Aufbau noch ändern? Nach drei Jahren Arbeit mit Java und Schulungen setze ich im Buch andere Schwerpunkte, so dass sich die Insel zu einem didaktisch aufbereiteten Lehrbuch entwickeln wird, dass von seinem Schwierigkeitsgrad nach hinten immer komplexer wird. Die unterschiedlichen Kapitel sollten prinzipiell für Lernende geeignet sein, und anschließende Unterkapitel sind für erfahrene Programmierer oder Informatiker. Das Tutorial ist nicht als Nachschlagewerk gedacht. Besonders der Neuling wird an einigen Stellen den sequenziellen Pfad verlassen müssen, da spezielle Kapitel mehr Hintergrundinformationen und Vertrautheit mit Programmiersprachen fordert. In den zukünftigen Versionen werden diese vertiefenden Stelle besonders gekennzeichnet sein.
Welche Soft ware wir nut zen Als ich 1997 mit Java begann kamen die ersten Javaversionen von Schöpfer Sun auf den Markt. Bis dahin hat sich die Versionsspirale von 1.0 bis aktuell 1.3 gedreht. Als Grundlage dient daher das Java Developing Kit (kurz JDK) als Referenzimplementierung. Das JDK lässt sich unter http:// www.javasoft.com/products/ beziehen. Das Paket besteht im Wesentlichen aus einem Compiler, Interpreter und einer Online-Hilfe im HTML- oder Win-Help Format. Das JDK ist für die Plattformen Windows und Solaris erhältlich. Mittlerweile gibt es von Javasoft auch eine Implementierung für Linux, die an einigen Stellen auf der Open-Source-Implementierung Kaffe basiert – für das Sun viel Schelte einstecken musste. Mehr dazu unter http://www.blackdown.org. Eine grafische Entwicklungsoberfläche (IDE) ist nicht Teil des Paketes. Ich stütze mich auch nicht auf einen Hersteller, da der Markt zu dynamisch ist und die Hersteller verschiedene Entwicklergruppen ansprechen. Die Programme lassen sich mit einem einfachen ASCII-Texteditor eingeben und dann auf der Kommandozeile übersetzen. Diese Form der Entwicklung ist allerdings nicht mehr zeitgemäß, so dass ein Aufsatz die Programmerstellung vereinfacht. Ich habe mit Kawa und CodeGuide gute Erfahrun1. Damals habe ich mit zwei Freunden gewettet, dass ich für das Assembler-Buch innerhalb eines Jahres einen Verleger finde. (So richtig habe ich auch nicht gesucht.) Als Wetteinsatz hatte ich ein freies Burger-Essen bei McDonalds versprochen. Die Wette habe ich natürlich verloren. Doch auf dem Weg nach Hause hatten beide etwa 5 Burger, genug Eis und Kirschtaschen gegessen und ich musste zwischendurch an einer Straße anhalten. So hatte auch ich meinen Spaß. Beide haben danach etwa ein halbes Jahr keine Burger mehr gegessen. • • 20 •• • •
gen gemacht. Eine Übersicht über Entwicklungswerkzeuge findet der Leser im ersten Kapitel. Für die Entwicklung von Applets ist ein Browser mit Javaunterstützung wichtig. Zum Testen lässt sich der Appletviewer aus dem JDK verwenden. Besonders für Javabuch Autoren stellt sich die Frage, welche JDK-Version und damit, welche Bibliotheken beschrieben werden sollen. Ich habe das Problem so gelöst, dass immer die Möglichkeiten des neusten JDK (also zur Zeit 1.3) genutzt werden. Das wirft an einigen Stellen Probleme auf, beispielsweise dann, wenn eine kommerziellen Entwicklungsumgebung genutzt werden soll, oder wenn Appltes entwickelt werden. Kommerzielle IDEs hinken dem JDK immer etwas hinterher und die Webbrowser, die Applets darstellen, verstehen nicht immer die aktuellen Versionen. Hier ist der Stand von 1.1 schon gut! Dennoch ist es für mich eine Frage der Zeit, bis sich auch die Neuerungen durchsetzen und so ist es schwer zu entscheiden was den nun alt ist oder nicht. Wenn ich Anmerkungen im Text anheften würde, bliebe die Frage offen, wann ich diese streichen sollte. Galt vor einm Jahr noch 1.1 als Novum, ist dies heute 1.3. Die Leser finden im Buch nur das, was aktuelle möglich ist und nur aus historischen Gründen Verweise auf frühere Lösungen. Das gilt etwa für die Ereignisbehandlung oder den inneren Klassen. Für die Didaktik ist die Versionsfrage auch unerheblich und Softwarewickler werden die Online-Dokumentationen konsultieren.
Danksagungen Ich würde gerne einem großen Softwarehaus meinen Dank aussprechen; doch leider gibt es keinen Grund dafür. Mit einer Textverarbeitung ist es wie mit Menschen – irgendwie hat doch jeder noch mal eine zweite Chance. Auch eine Textverarbeitung. Klappt irgend etwas einmal nicht, nun gut, vielleicht geht es auf einem anderen Weg. Auch meiner Ex-Pommes-Bude nebenan habe ich schon viele Chancen gegeben – und nichts. Die Pommes blieben weich und pampig. Die Konsequenz ist: Ich gehe nicht mehr hin. Genauso ist es mit Microsoft Word oder Adobe FrameMaker. Einst war ich von FrameMaker so begeistert, doch das hielt nur einen Monat. Die Texterfassung ist umständlich und so ging ich zu Word 7 über. Damals waren es schon etwa 40 Seiten mit Vorlagen. Das Konvertieren ging schnell in drei Tagen über die Bühne. Als ich dann – aus Gründen, die mir heute nicht mehr bekannt sind1 – zu Word 8 überging, ging das Konvertieren schon wieder los. Ich war geblendet von den Funktionen und Spielereien. Die Ernüchterung kam zwei Monate später. Mein Dokument war auf die Größe von 100 Seiten angeschwollen und Filialdokumente machten Sinn. Doch plötzlich fehlte eine Datei, andere waren defekt und Word wollte einfach nicht ohne eine Fehlermeldung die Filialdokumente laden. Sie waren aus unerfindlichen Gründen als fehlerhaft markiert und auch die Anweisung, alles zu kopieren und in ein neues Dokument zu packen, machten sie nicht wieder einsatzbereit. Da ist auch das plötzliche Weiß werden des gesamten Textes unter Word 7 noch harmlos dagegen. Als anschließend Word noch anfing meine Absatzvorlagen heiter durcheinanderzubringen und auch nach Ändern, Speichern immer noch die gleichen Effekte zeigte, war es soweit: Word 8 musste weg. Also wieder zurück zu Word 7? Ja! Also RTF, Absatzvorlagen wieder umdefinieren, altes Filialdokument wieder einsetzen. Die Zeit, die ich für Umformatierungen und Konvertierungen verliere, ist weg und das einzige was ich gelernt habe ich: »Sei vorsichtig bei einem MS-Produkt«! Aber, erzähl’ ich damit jemanden etwas Neues? Nun, ich darf es eigentlich gar nicht erwähnen, aber ich bin doch schon wieder bei FrameMaker gelandet. Was sonst?2 Das Programm eignet sich zwar nicht zur Texterfassung, jedoch ist der Satz sehr gut, die Darstellung von Bild und Text überzeugend schnell, und für einen möglichen späteren Druck sehr entgegenkommend. Die Texterfassung läuft nun über Word 2000 – mit roten Kringeln und neuer Rechtschreibung – und dann setze ich die Textpassagen über die Zwischenablage, Format-gesäubert von UltraEdit, in FrameMaker ein. Dort lassen sich auch über 700 Seiten mit Bildern und Tabellen ohne Seitenneuberechnung schnell scrollen. So sollte das immer sein. Mich beein1. Versionsfanatismus? 2. Ja, OK, TeX ist auch eine Lösung. • • • 21 • • •
druckt in diesem Zusammenhang immer eine Textverarbeitung auf dem Arcon Achimedes. Sie stellt den Text beim Verschieben eines Bildes mit automatischer Neuberechnung des Textflusses pixelgenau da. Warum habe ich das in einer PC-Textverarbeitung noch nicht gesehen?
Echt e Danksagungen Ein Professor in einem Seminar meinte zur Bewertung von Seminararbeiten, dass erst das Gute und dann das Schlechte zu sagen ist. (Das war besonders unglücklich, wenn es zu einigen Seminararbeiten gar nichts positives zu sagen gab...) Daher das Gute: Ich danke besonders Henryk Plötz und Michael Schulze für ihre Arbeit. Sie haben sich die Mühe gemacht hat, die ersten Kapitel sehr intensiv zu lesen und zu bewerten. Weiterer Dank für Hinweise gehen an verschiedene treue Leser, deren Namen aufzulisten viel Platz kosten würde. Ich danke auch den vielen Buch- und Artikelautoren für ihre interessanten Werke. Nun leider noch etwas Bedauerliches. Das Tutorial wird pro Woche 200 bis 300 mal vom Server http://Java-Tutor.com/javabuch geladen. Diese Tatsache ist allein noch nicht bedauerlich, doch was ich vermisse, sind die Vorschläge, wie sich die Qualität des Buches heben lässt. (Ich kann mir irgendwie nicht vorstellen, dass der Inhalt 100 Prozent perfekt ist.) Dass verleitet mich zu der Annahme, dass einige Leser Internet-Konsumenten der Sorte sind, die alles, was nichts kostet, kopieren und sammeln. Das Buch kostet zwar nichts und ist frei, doch es ist nicht geschenkt. Als Gegenleistung wünsche ich mir folgendes:
Feedback Auch wenn ich (oder meine Mithelfer) die Kapitel noch so sorgfältig durchgegangen bin, kann ich nicht ausschließen, dass noch Unstimmigkeiten vorhanden sind. Wer Anmerkungen, Hinweise, Korrekturen oder Fragen zu bestimmten Punkten hat, der sollte sich nicht scheuen, mir eine E-Mail (unter der Adresse [email protected]) zu senden. Ich bin für Anregung und Kritik stets dankbar. Und jetzt wünsche ich viel Spaß beim Lesen und Lernen von Java! Paderborn1, 20. April 2k Christian U. Ullenboom
1. Na ja, Heidelberg, New York oder Tokio würde sich besser machen... • • 22 •• • •
KAPITEL
1 Schon wieder eine neue Sprache? Wir produzieren heute Informationen in Massen, wie früher Autos. – John Naisbitt
Java ist mittlerweile ein Modewort geworden und liegt in aller Munde. Doch nicht so sehr, weil Java eine schöne Insel1, eine reizvolle Wandfarbe oder eine Pinte mit brasilianischen Rhythmen in Paris ist, sondern vielmehr, weil Java eine neue Programmiersprache ist, mit der ›modern‹ programmiert werden kann. Wer heute nicht mindestens schon einmal was von Java gehört hat, scheint megaout2. In Java ist viel hineingeredet worden, es wurde als Lösung für alle Softwareprobleme in den Himmel gehoben und als unbrauchbar verdammt. Java ist Philosophie und Innovation gleichzeitig – ein verworrenes Thema. Doch warum ist Java so populär? Nach einer Umfrage, die zur 27. ACM3 SIGCSE-Konferenz im März '97 in Kalifornien abgehalten wurde, ergab die Auswertung von 75 Befragten folgende prozentuale Verteilung: Lobenswerte Eigenschaft von Java
%
Programme sind im Netz ladbar
51
Java ist plattformunabhängig
43
Ist sicherer als C++
21
Die Sprache ist einfach ›In‹
16
Entfernt unsichere Eigenschaften von C++
11
Compiler und VM von Sun frei
11
Erlaubt die Erstellung von grafischen Benutzungsschnittstellen
9
Tabelle: Welche Eigenschaften an Java wie geschätzt werden. 1. Die Insel Java ist die kleinste der Sudaninseln in Indonesien mit etwa 88,4 Millionen Einwohnern. Hörer der ›Drei Fragezeichen‹ verbinden die Insel vermutlich noch mit Schätzen... 2. Und die, die in Bewerbungen sieben Jahre Java-Erfahrungen angeben, ohnehin. 3. ACM ist die Abkürzung für ›American Computation and Mathematics‹. Die Gesellschaft verbreitet Schriften über aktuelle Forschung in der Informatik und Mathematik. • • • 23 • • •
Allgemeine Ablehnung gegenüber C++
9
Ist ein richtiges Produkt zur richtigen Zeit
9
Sprache mit durchdachten Elementen
8
Qualität der Bibliotheken
7
Elegante Speicherverwaltung mit Garbage-Collector
5
Applikation- und Appletfähigkeit
4
Unterstützt Threads
3
Ermöglicht verteiltes Rechnen
3
Exception-Verwaltung
0
Tabelle: Welche Eigenschaften an Java wie geschätzt werden. Und als ob es nicht schon genug Programmiersprachen gibt! Prof. Parnes, der sich in einer Untersuchung mit dem Einsatz von Programmiersprachen beschäftigte, nimmt an, dass sich unter 1700 Dissertationen in der Informatik etwa 700 mit neuen Programmiersprachen beschäftigen. Dann muss Java schon einiges zu bieten haben! Im ersten Kapitel sollen daher kurz die wesentlichen Konzepte der ›Internetprogrammiersprache‹1 vorgestellt werden.
1.1 Hist orischer Hint ergrund In den siebziger Jahren wollte Bill Joy eine Programmiersprache schaffen, die alle Vorteile von MESA und C vereinigt. Diesen Wunsch konnte sich Joy zunächst nicht erfüllten und erst am Anfang der neunziger Jahre schrieb er den Artikel ›Further‹, wie eine neue objektorientierte Sprache aussehen könnte; sie sollte in den Grundzügen auf C++ aufbauen. Erst später ist ihm bewusst geworden, dass C++ als Basissprache ungeeignet und für große Programme unhandlich ist. Zu dieser Zeit arbeitete James Gosling an dem SGML-Editor ›Imagination‹. Er entwickelte in C++ und war auch mit dieser Sprache nicht zufrieden, aus diesem Unmut entstand die neue Sprache Oak. Der Name fiel Gosling ein, als er aus dem Fenster seines Arbeitsplatzes schaute – er sah eine Eiche (engl. Oak). Doch vielleicht ist das auch nur eine Legende. Patrick Naughton startete im Dezember 1990 das ›Green‹-Projekt, in das Gosling und Mike Sheridan involviert waren. Die Idee hinter dem Grün war die Entwicklung von Software für interaktives Fernsehen und andere Geräte der Konsumelektronik. Bestandteile dieses Projekts waren das Betriebssystem Green-OS, James Interpreter Oak und einige Hardwarekomponenten. Joy zeigte den Mitgliedern des Green-Projekts seinem ›Further‹-Aufsatz und begann mit der Implementierung einer grafischen Benutzeroberfläche. Gosling schrieb den Originalcompiler in C und anschließend entwarfen Naughton, Gosling und Sheridan den Runtime-Interpreter ebenfalls in C – die Sprache C++ kam nie zum Einsatz. Oak führte die ersten Programme im August 1991 aus. So entwickelte das Green-Dream-Team ein Gerät mit der Bezeichnung *7 (Star Seven), das sie im Herbst 1992 intern vorstellten. Sun-Chef Scott McNealy war von *7 beeindruckt und aus dem Team wurde im November die Firma First Person, Inc. Nun ging es um die Vermarktung von Star Seven.
1. Dass das Internet schon in den Alltag eingezogen ist, sehen wir schon an der Musik. So singt etwa Hot’n Juicy im Lied »I’m horney tonight« darüber, den Angebeteten über das Internet zu erreichen. Keine Spur mehr von rein wissenschaftlicher Anwendung. • • 24 •• • •
Anfang 1993 hörte das Team von einer Anfrage von Time-Warner, die ein System für Set-TopBoxen brauchten. (Set-Top-Boxen sind elektronische Konsumgeräte.) First Person richtete den Blick vom Consumer-Markt auf die Set-Top-Boxen. Leider zeigte sich Time-Warner später nicht mehr interessiert, aber First Person entwickelte (sich) weiter. Nach vielen Richtungswechseln konzentrierte sich die Entwicklung auf das World Wide Web (kurz Web genannt, selten W3). Die Programmiersprache sollte Programmcode über das Netzwerk empfangen können und auch fehlerhafte Programme tolerieren. Damit konnten die meisten Konzepte aus C/C++ schon abgehakt werden – Pointer, die wild den Speicher beschreiben, sind ein Beispiel. Die Mitglieder des ursprünglichen Projektteams erkannten, das Oak alle Eigenschaften aufwies, die nötig waren, um es im Web einzusetzen – perfekt, obwohl ursprünglich für einen ganz anderen Zweck entwickelt. Die Sprache Oak bekam den Namen ›Java‹. Patrick Naughton führte den Prototypen des Browsers ›WebRunner‹ vor, der an einem Wochenende entstanden sein soll. Nach etwas Überarbeitung von Jonathan Payne wurde der Browser ›HotJava‹ getauft und im Mai auf der SunWorld '95 der Öffentlichkeit vorgestellt. Zunächst konnten sich nur wenige Anwender mit HotJava anfreunden. So war es großes Glück, dass Netscape sich entschied, die Java-Technologie zu lizenzieren. Sie wurde in der Version 2.0 des Netscape Navigators implementiert. Der Navigator kam im Dezember 1995 auf den Markt. Im Januar 1996 wurde das JDK 1.0 freigegeben, was den Programmierern die erste Möglichkeit gab, Java Applikationen und Web-Applets (Applet: ›A Mini Application‹) zu programmieren. Kurz vor der Fertigstellung des JDK 1.0 gründeten die verbliebenen Mitgliedern des Green-Teams die Firma JavaSoft. Und so begann der Siegeslauf...
1.2 Eigenschaft en von Java Java ist eine objektorientierte Programmiersprache, die sich durch einige zentrale Eigenschaften auszeichnet. Diese machen sie universell einsetzbar und für die Industrie als robuste Programmiersprache interessant. Da Java objektorientiert ist, spiegelt es den Wunsch der Entwickler wieder, moderne und wiederverwertbare Softwarekomponenten zu programmieren.
1.2.1 Die virt uelle Maschine Zunächst einmal ist Java eine Programmiersprache wie jede andere auch. Nur im Gegensatz zu herkömmlichen Programmiersprachen, die Maschinencode für eine spezielle Plattform generieren, erzeugt der Java-Compiler Programmcode für eine virtuelle Maschine, den sogenannten Bytecode. Diese virtuelle Maschine1 ist ein einfacher Prozessor, der mittlerweile auch in Silizium gegossen wurde – diese Entwicklung verfolgt verstärkt Sun, beziehungsweise die Lizenznehmer. Der Prototyp dieses Prozessors (genannt PicoJava) ist mittlerweile verfügbar und findet bald Einzug in sogenannte Network-Computer. Das sind Computer ohne bewegliche Peripherie wie Festplatten, die als Terminal am Netz hängen. Bei der Entwicklung des Prozessors stand nicht die maximale Geschwindigkeit im Vordergrund, sondern die Kosten pro Chip, um ihn in jedes Haushaltsgerät einzubauen. Damit aber der Programmcode der virtuellen Maschine ausgeführt werden kann, muss ein Interpreter die Befehlsfolgen dekodieren und ausführen. Somit ist Java eine compilierte aber auch interpretierte Programmiersprache – von der Hardwaremethode einmal abgesehen. Der Compiler, der von Sun selbst in Java geschrieben ist, generiert den Bytecode. Doch nicht nur die Programmiersprache Java erstellt Bytecode, zur Zeit laufen von verschiedenen Herstellen Entwicklungen von C und ADA-Compilern, die Bytecode erstellen. Die Entwicklergruppe von EIFFEL unter der Leitung von Bertrand Meyer wird in den nächsten Versionen J-Code unterstützen. Ebenso gibt es eine 1. Auch die erhabene OO-Sprache Smalltalk bedient sich einer Virtuellen Maschine. • • • 25 • • •
Scheme-Umgebung, die komplett in Java programmiert ist. Der Compiler erstellt für den Lisp-Dialekt ebenfalls Java-Bytecode. Mittlerweile ist Java nicht nur interpretierte Sprache, sondern auch interpretierende Sprache zugleich. Das zeigen unterschiedliche Computer- und Prozessor-Emulations-Programme.1 Nach der Übersetzungsphase führt die Laufzeitumgebung (auch Run-Time-Interpreter genannt), die Java Virtuelle Maschine, den Bytecode aus2. Das Interpretieren bereitet noch Geschwindigkeitsprobleme, da das Erkennen, Dekodieren und Ausführen der Befehle Zeit kostet. Im Schnitt sind Java-Programme drei bis zehn mal langsamer als C oder C++ Programme. Die Technik der Just-In-Time (JIT) Compiler3 minimiert das Problem. Ein JIT-Compiler beschleunigt die Ausführung der Programme, indem die Programmanweisungen der virtuellen Maschine auf die physikalische übersetzt werden. Es steht anschließend ein auf die Architektur angepasstes Programm im Speicher, welches ohne Interpretation schnell ausgeführt wird. Auch Netscape übernahm im Windows-Communicator4 4.0 einen JIT (den von Symantec) um an Geschwindigkeit zuzulegen – obwohl diese Variante noch nicht den gesamten 1.1 Standard beherrscht. (Erst in der Version 4.06 von Netscape kam die volle Unterstützung für 1.1.) Mit diesem Trick ist die Laufzeit zwar in vielen Fällen immer noch unter C, aber der Abstand ist geringer. Nur durch den Einsatz der PicoJava-Prozessoren lassen sich um 50 mal schnellere Ausführungszeiten erzielen5.
1.2.2 Konzept e einer m odernen Program m iersprache Im Entwurf von Java wurde Sicherheit gefordert. Die bedeutet unter anderem eine sichere Syntax.
Kein Präprozessor Einen Präprozessor gibt es in Java nicht und entsprechend keine Header-Dateien. Diese sind in Java nicht nötig, da der Compiler die Signaturen aus den Klassendateien liest. Ein schmutziger Trick wie #de f i ne pr i va t e publ i c # i n c l u d e " a l l e s Mo e g l i c h e "
oder Makros, die Fehler durch doppelte Auswertung erzeugen, sind damit von vorne herein ausgeschlossen. Leider ist damit auch bedingte Compilierung mit # i f d e f nicht möglich. Dies führt vereinzelt dazu, dass ein externer Präprozessor den Quellcode bearbeitet.
1. Dies beweist Hob, ein portabler ZX-Spectrum Emulator, der komplett in Java geschrieben ist. Auf der Web-Seite http://www.engis.co.uk/stuff/hob/ gibt noch viele Spiele dazu, die als Applet ausprobiert werden können. 2. Die Idee des Bytecodes (Framemaker schlägt hier als Korrekturvorschlag ›Bote Gottes‹ vor) ist schon alt. Die Firma Datapoint schuf um 1970 die Programmiersprache PL/B, die Programme auf Bytecode abbildet. Auch verwendet die Orginalimplementation von UCSD-Pascal, etwa Anfang 1980, einen Zwischenencode – kurz pcode. 3. Diese Idee ist auch schon alt: HP hatte um 1970 JIT-Compiler für BASIC-Maschinen. 4. Netscape hört es gar nicht gerne, wenn der Web-Browser als Navigator bezeichnet wird. Hier im Tutorial verwenden wir dies allerdings synonym. Die Firma verseht den Communicator als Web-Lösung, die nicht nur aus einem Web-Browser besteht. Es wird gemunkelt, dass Mitarbeiter aus der Firma rausfliegen, wenn sie das Wort Navigator nur in den Mund nehmen... 5. Es ist schon paradox eine plattformunabhängige Sprache vorzuschlagen und dann einen Prozessor zu entwicklen, der anschließend das Problem der langsamen Ausführung löst. • • 26 •• • •
Überladene Operat oren Wenn wir Operatoren (zu deutsch Rechenzeichen), wie das Plus- oder Minuszeichen verwenden und damit Ausdrücke zusammenfügen, machen wir dies meistens mit bekannten Rechengrößen. So fügt ein Plus zwei Ganzzahlen, aber auch zwei Fließkommazahlen (Gleitkommazahlen) zusammen. Einige Programmiersprachen – unter ihnen meistens Skriptsprachen – erlauben auch das ›Rechnen‹ mit Zeichenketten, mit einem Plus können diese beispielsweise zusammengefügt werden. Die meisten Programmiersprachen erlauben es jedoch nicht, die Operatoren mit neuer Bedeutung zu versehen und mit Objekten zu verknüpfen. In C++ jedoch ist das Überladen von Operatoren möglich. Diese ist praktisch bei Rechnungen mit komplexen Objekten, da dort nicht über die Methoden umständliche Verbindungen geschaffen werden, sondern über ein Operatorzeichen angenehm kurze. Obwohl zu Weilen ganz praktisch – das Standardbeispiel sind Objekte von komplexen Zahlen und Brüche – verführt die Möglichkeit Operatoren zu überladen oft zu unsinnigem Gebrauch. In Java ist daher das Überladen der Operatoren bisher nicht möglich. Es kann aber gut sein, dass dies sich in der Zukunft ändert. Der einzige überladene Operator in Java ist das Pluszeichen bei Strings. Zeichenketten können damit leicht zusammengesetzt werden. Informatiker verwenden in dem Zusammenhang auch gerne das Wort Konkatenation (selten Katenation). Bei einem String "Hallo" und " du da" ist "Hallo du da" die Konkatenation der Zeichenketten.
Ausnahm enbehandlung Java unterstützt ein modernes System um mit Laufzeitfehlern umzugehen. In der Programmiersprache wurden Exceptions eingeführt: Objekte die zur Laufzeit generiert werden und einen Fehler anzeigen. Diese Problemstellen können durch Programmkonstrukte gekapselt werden. Die Lösung ist in vielen Fällen sauberer als die mit Rückgabewerten und unleserlichen Ausdrücken im Programmfluss. In C++ gibt es ebenso Exceptions, diese werden aber bisher wenig benutzt. Aus Geschwindigkeitsgründen wird die Überwachung von Array-Grenzen (engl. RangeChecking) in C(++)1 nicht durchgeführt. Und der fehlerhafte Zugriff auf das Element n + 1 eines Feldes der Größe n kann zweierlei bewirken: Ein Zugriffsfehler tritt auf, oder, viel schlimmer, andere Daten werden beim Schreibzugriff überschrieben und der Fehler ist nicht nachvollziehbar. Schon in PASCAL wurde eine Grenzüberwachung mit eincompiliert. Das Laufzeitsystem von Java überprüft automatisch die Grenzen eines Arrays. Diese Überwachungen können nicht, wie diverse PASCAL-Compilern erlauben, abgeschaltet werden, sondern sind immer dann eingebaut, wenn nicht schon im Voraus aus den Schleifenzählern ersichtlich ist, dass keine Überschreitung möglich ist.
Zeiger und Referenzen In Java gibt es keine Zeiger (engl. Pointer), wie sie aus anderen Programmiersprachen bekannt und gefürchtet sind. Da eine objektorientierte Programmiersprache aber ohne Zeiger nicht funktioniert, werden Referenzen eingeführt, eine sichere Version des Pointers. Eine Referenz ist ein stark typisierter Zeiger, der seinen Typ nicht ändern kann. Dass so etwas in C++ leicht möglich ist, zeigt das folgende Beispiel. # i n c l u d e # i n c l u d e c l a s s Ga n z _ u n s i c h e r { publ i c : 1.
In C++ ließe sich eine Variante mit einem überladenen Operator denken. • • • 27 • • •
Ga n z _ u n s i c h e r ( ) { s t r c p y ( p a s s wo r t , " g e h e i m" ) ; } pr i va t e : c h a r p a s s wo r t [ 1 0 0 ] ; };
i n t ma i n ( ) { Ga n z _ u n s i c h e r g l e i c h _ p a s s i e r t s ; c h a r * b o e s e wi c h t = ( c h a r * ) &g l e i c h _ p a s s i e r t s ; c o u t 1 ) { v e r s e t z e Tu r m( n - 1 , k u p f e r , g o l d , s i l b e r ) ; b e we g e Sc h e i b e ( n , k u p f e r , g o l d ) ; v e r s e t z e Tu r m( n - 1 , s i l b e r , k u p f e r , g o l d ) ; } el s e { b e we g e Sc h e i b e ( n , k u p f e r , g o l d ) ; } } p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { v e r s e t z e Tu r m( 4 , " Ku p f e r " , " Si l b e r " , " Go l d " ) ; } • • • 67 • • •
}
Starten wir das Programm mit 4 Scheiben, so bekommen wir folgende Ausgabe: Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i Sc h e i
be be be be be be be be be be be be be be be
1 2 1 3 1 2 1 4 1 2 1 3 1 2 1
von von von von von von von von von von von von von von von
Ku p f e r n a c h Si l b e r Ku p f e r n a c h Go l d Si l b e r n a c h Go l d Ku p f e r n a c h Si l b e r Go l d n a c h Ku p f e r Go l d n a c h Si l b e r Ku p f e r n a c h Si l b e r Ku p f e r n a c h Go l d Si l b e r n a c h Go l d Si l b e r n a c h Ku p f e r Go l d n a c h Ku p f e r Si l b e r n a c h Go l d Ku p f e r n a c h Si l b e r Ku p f e r n a c h Go l d Si l b e r n a c h Go l d
Schon bei vier Scheiben haben wir 15 Bewegungen. Nun wollen wir uns die Komplexität bei n Porphyrscheiben überlegen. Bei einer Scheibe haben wir nur eine Bewegung zu machen. Bei zwei Scheiben aber schon doppelt so viele wie vorher und noch einen zusätzlich. Formaler: 1. S1 = 1 2. S2 = 1 + 2S1 = 3 3. S3 = 1 + 2S2 = 7 Führen wir die Berechung induktiv fort, so folgt für Sn, dass 2n - 1 Schritte auszuführen sind, um n Scheiben zu bewegen. Nehmen wir an, unser Prozessor arbeitet mit 100 MIPS, also 100 Millionen Operationen pro Sekunde, ergibt sich für n = 100 eine Zeit von 4*1013 Jahren (etwa 20000 geologische Erdzeitalter). An diesem Beispiel wird uns wie beim Beispiel mit der Ackermann-Funktion deutlich: Die Funktionen sind im Prinzip berechenbar, nur praktisch ist so ein Algorithmus nicht.
2.8 Noch m ehr Operat oren 2.8.1 Bit - Operat ionen Mit Bit-Operatoren lassen sich Binäroperationen auf Operanden durchführen. Zu den Bit-Operationen zählen Verknüpfungen, Schiebeoperationen und das Komplement. Durch die bitweisen Operatoren können die einzeln Bits abgefragt und manipuliert werden. Als Verknüpfungen bietet Java die folgenden Bit-Operatoren an. Operator Bezeichnung
Funktion
~
Komplement
Alle Bits von a werden invertiert.
|
bitweises Oder
Die Bits von a und b werden einzeln Oder-verknüpft.
Tabelle: Bit-Operatoren in Java • • 68 •• • •
Operator Bezeichnung
Funktion
&
bitweises Und
Die Bits von a und b werden einzelnd Und-verknüpft.
^
bitweises exklusives Oder
Die Bits von a und b werden einzelnd ExklusivOder verknüpft.
Tabelle: Bit-Operatoren in Java Betrachen wir allgemein die binäre Verknüpfung a # b . Bei der binären bitweisen Und-Verknüpfung mit & gilt für jedes Bit: Nur wenn beide Operanden a und b Eins sind, dann ist auch das Ergebnis Eins. Bei der Oder-Verknüpfung mit | muss nur einer der Operanden Eins sein, damit das Ergebnis Eins ist. Bei einem Exklusiven Oder ist das Ergebnis 1, wenn nur genau einer der Operanden Eins ist. Sie beide Null oder Eins, ist das Ergebnis 0. Dies entspricht einer binären Addition oder Subtraktion. Die Bit-Operatoren lassen sich zusammen mit den Shift-Operatoren gut dazu verwenden, ein Bit zu setzen, respektive herauszufinden, ob ein Bit gesetzt ist. Betrachten wir folgende Funktionen, die ein bestimmtes Bit setzten, abfragen, invertieren und löschen. In dem Beispiel sehen wir eine Anwendung aller Operatoren. i n t s e t Bi t ( i n t n , i n t p o s ) { r e t u r n n | ( 1 >-Operator verschiebt eine Variable (Bitmuster) bitweise um n Schritte nach rechts ohne das Vorzeichen der Variablen zu berücksichtigen (vorzeichenloser Rechtsshift). So werden auf der linken Seite (MSB) nur Nullen eingeschoben; das Vorzeichen wird mitgeschoben. Bei einer positiven Zahl hat dies keinerlei Auswirkungen und das Verhalten ist wie beim >>-Operator. Nur bei einer negativen Zahl ändert sich der Wert wie das Beispiel zeigt. Quellcode 2.h
ShiftRightDemo.java
c l a s s Sh i f t Ri g h t De mo { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { i nt i , j ; i = 64; j = i >>> 1 ; Sy s t e m. o u t . p r i n t l n ( " 6 4 >>> 1 = " + j ) ; i = - 64; j = i >>> 1 ; Sy s t e m. o u t . p r i n t l n ( " - 6 4 >>> 1 = " + j ) ; } }
Die Ausgabe ist für den negativen Operanden am Spannensten. 6 4 >>> 1 = 3 2 - 6 4 >>> 1 = 2 1 4 7 4 8 3 6 1 6
Ein Auch in Assembler gibt es 2 Gruppen von Schiebeoperatoren: Die arithmetischen Schiebebefehle (SAL und SAR), die das Vorzeichen des Operanden beachten, und die logischen Schiebebefehle (SHL und SHR), die den Operanden ohne Beachtung eines etwaigen Vorzeichen schieben. Die Befehle SAL und SHL haben die gleiche Wirkung. So ist >>> der Bit-Operator, in dem das Vorzeichen nicht beachtet wird. (wie SHR in Assembler). Auch spricht hier der Grund gegen den Bit-Operator > 1 ) >>> 2 ) >>> 4 ) >>> 8 ) >>> 1 6 )
+ + + + +
(n (n (n (n (n
& & & & &
0x55555555) ; 0x33333333) ; 0 x 0 F0 F0 F0 F) ; 0 x 0 0 FF0 0 FF) ; 0 x 0 0 0 0 FFFF) ;
Der Algorithmus hat immer dieselbe Laufzeit – egal wie viele der 32 Bits gesetzt sind. Hier stecken aber sehr viele versteckte Operationen. Zwei Und-Verknüpfungen, eine Addition, ein Shift und eine Zuweisung. Bei 5 Zeilen sind dies 25 Operationen, oder in Java Bytecode 49 Anweisungen. Dies steht 5 Operationen in der Schleife gegenüber, beziehungsweise 8 Java Bytecode Anweisungen. Die Schleifen-Variante ist demnach nur dann schneller, wenn weniger als 5 Bits im Muster sind. Für mehr lohnt sich der feste Algorithmus.
Haben wir eine Zweierpot enz als Zahl? Jetzt, wo wir uns schon mit Bits angewärmt haben, wollen wir uns noch zwei weiteren Problemen widmen, die indirekt mit der Anzahl der Bits verbunden sind. Zunächst ist gefragt, ob eine gegebene Zahl eine direkte Zweierpotenz ist, die Zahl also x2 ist, etwa 1, 2, 4, 8, 16, 32, 64. Dies können wir mit dem oben besprochenen Algorithmus leicht lösen. Wir bestimmten erst die Anzahl der Bits und anschließend testen wir, ob dies genau Eins ist. Doch das geht auch schneller, denn der obere Algorithmus geht ja bei einem Ergebnis ungleich Null weiter. Bei nur einer gesetzten Eins muss also das Ergebnis direkt Null ergeben. Die oberen Zeilen können also verkürzt werden zu folgender Funktion. • • 72 •• • •
s t a t i c b o o l e a n i s p o w2 ( i n t x ) { r e t u r n ( x & ( x - 1 ) ) == 0 ; }
Gerade oder ungerade Anzahl von Bit s m it Xor Beim zweiten Problem haben wir eine Anzahl von Bits gegeben und wir sollen herausfinden, ob diese eine gerade oder ungerade Parität ergeben. Eine ungerade Parität ergibt sich aus einer ungeraden Anzahl von Einsen. Hier gibt es wieder eine naive und eine intelligente Lösung. Beginnen wir bei der naiven, die ausnutzt, was wir von ein paar Minuten gemacht haben: Bits zählen. Der Gedanke liegt nahe, die Anzahl einfach Modulo Zwei zu nehmen. Das geht, dann ist jedoch die Laufzeit unnötig hoch, da wir erst die Anzahl berechnen und dann die Summe wieder beschneiden müssen. Es geht auch intelligenter. Dazu schauen wir uns noch einmal die Bitfolge an: 01100100 10110100
Wir haben hier die 16 Bits einer Ganzzahl i n t . Das Leerzeichen erscheint wie ein Tippfehler, doch dies ist gewollt, da wir hier eine Separation des i n t in zwei Hälften vornehmen. Betrachten wir nun die beiden Hälften getrennt. 01100100 besitzt drei Einsen, hat also die Parität 1. 10110100 besitzt vier Einsen, besitzt also die Parität 0. Zusammen ist 0+1 = 1, also ist die Gesamtparität auch Eins. Wollen wir einen schnellen Algorithmus programmieren, dann könnten wir diese Zusammensetzung ausnutzen. Die Zusammenfassung der Parität mit Plus lässt sich auch anders schreiben, nämlich durch eine Xor-Verknüpfung. 01100100 ^ 10110100 ---------11010000
Dies funktioniert, da Xor wie eine Addition mit Übertrag definiert ist. So wird Eins plus Null zu einer Eins und zwei Einsen heben sich gegenseitig auf. Die Parität ist ungerade, wenn wir eine ungerade Anzahl von Einsen haben. Die obige Anweisung ließe sich also in Java Programmcode wie folgt zusammenbauen: x = x ^ x >> 8 ;
Wir schieben die links stehenden acht Bit nach rechts und Xor-Verknüpfen dies mit dem Rest. Nach der Xor-Operation steht in den unteren 8 Bits die Parität kodiert. Die oberen 8 Bits werden nun nicht weiter benötigt. Die Verschiebung und Verknüpfung war nur ein Schritt. Wir haben jetzt die Parität eines 16 Bit Wortes auf die Betrachtung der Parität eines 8 Bit Wortes reduziert. Nun können wir aber auch diese 8 Bit in zwei Vier-Bit Blöcke aufteilen und diese wiederum Xor-Verknüpfen. Und den komprimieren wir den Vier-Bit Block auf einen Zwei-Bit Block. Anschließend bleibt nur noch eine einfache Rechtsverschiebung. Nun haben wir die Parität im letzten Bit sitzen. Steht hier eine Eins, dann ist die Parität ungerade. Der folgende Programmcode zeigt die p a r i t y ( ) Methode für ein 32 Bit Integer. s t a t i c bool e a n pa r i t y( i nt x ) { • • • 73 • • •
x x x x x
^= ^= ^= ^= ^=
x x x x x
>> >> >> >> >>
16; 8; 4; 2; 1;
r e t u r n ( x & 1 ) == 0 ; }
Die Laufzeit ergibt sich somit aus drei (Shift, Xor, Zuweisung) mal fünf Anweisungen plus zwei Anweisungen für den Test auf wahr oder falsch. Damit ergeben sich 17 Anweisungen für die Parität. Für drei gesetzte Bits wäre sogar noch der Erst-Zählen-Dann-Modulo2-Ansatz konkurrenzfähig.
2.8.2 Unt erklassen prüfen In Java haben die Entwickler einen Operator im Sprachwortschatz aufgenommen, mit dem Klassen auf ihre Verwandtschaft geprüft werden können. Mit dem Operator i n s t a n c e o f kann zur Laufzeit festgestellt werden, ob ein definiertes Exemplar eine Unterklasse einer anderen Klasse ist. Dies ist sinnvoll, denn durch objektorientiertes Programmieren werden laufend Basisobjekte definiert und erweitert. Grund für die Einführung war auch, dass es keine generischen Typen in Java gibt und Daten, die aus einer Datenstruktur kommen, automatisch vom Typ der Basisklasse sind. bool e a n b; St r i n g s t r = " To l l " ; b = ( s t r i n s t a n c e o f St r i n g ) ; b = ( s t r i n s t a n c e o f Ob j e c t ) ; b = ( s t r i n s t a n c e o f Da t e ) ;
/ / wa h r / / wa h r / / nö
Deklariert ist eine Variable s t r , als Objekt vom Typ St r i n g . Da die Operation i n s t a n c e o f reflexiv ist, ist der erste Ausdruck auf jeden Fall wahr. Für den zweiten Fall gilt: Alle Objekte gehen irgendwie aus Ob j e c t hervor und sind somit logischerweise Erweiterungen. Im dritten Fall ist Da t e keine Basisklasse für St r i n g , der Ausdruck ist falsch. Der i n s t a n c e o f -Operator ist transitiv, da für drei Klassen A, B und C und Objekten a , b von entsprechenden Typen für a i n s t a n c e o f B und b i n s t a n c e o f C gilt: a i n s t a n c e o f C.
2.8.3 Der Bedingungsoperat or In Java gibt es ebenso wie in C(++) einen Operator, der drei Operanden benutzt. Dies ist der Bedingungsoperator, der auch Konditional-Operator, ternärer Operator beziehungsweise trinärer Operator genannt wird. Er erlaubt es, den Wert eines Ausdrucks von einer Bedingung abhängig zu machen, ohne dass dazu eine if-Anweisung verwendet werden muss. Die Operanden sind durch ? bzw. : voneinander getrennt. Co n d i t i o n a l Or Ex p r e s s i o n ? Ex p r e s s i o n : Co n d i t i o n a l Ex p r e s s i o n
• • 74 •• • •
Der erste Ausdruck muss vom Typ b o o l e a n sein und bestimmt, ob das Ergebnis Ex p r e s s i o n oder Co n d i t i o n a l Ex p r e s s i o n ist. Der Bedingungsoperator kann eingesetzt werden, wenn der zweite und dritte Operand einen nummerischen Typ, boolschen Typ, Referenz-Typ oder n u l l -Typ besitzt. Der Aufruf von Methoden, die demnach v o i d zurückgeben, ist nicht gestattet. Eine Anwendung für den trinären Operator ist oft eine Zuweisung an eine Variable. va r i a bl e = be di ngung ? a us dr uc k1 : a us dr uc k2;
Der Wert der Variablen wird jetzt in Abhängigkeit der Bedingung gesetzt. Ist sie erfüllt, dann erhält die Variable den Wert des ersten Ausdruckes, andernfalls wird der Wert des zweiten Ausdruckes zugewiesen. So etwa für ein Maximum. x = ( a > b ) ? 0 : 1;
Dies entspricht beim herkömmlichen Einsatz für if/else: if ( a > b ) x = 0; el s e x = 1;
Beispiele Der Bedingungsoperator findet sich häufig in kleinen Funktionen. Dazu einige Beispiele. Um das Maximum oder Minimum zweier Ganzzahlen zurückzugeben definieren wir: i n t mi n ( i n t a , i n t b ) { r e t u r n a < b ? a : b ; } i n t ma x ( i n t a , i n t b ) { r e t u r n a > b ? a : b ; }
Der Absolutwert einer Zahl wird zurückgegeben durch x >= 0 ? x : - x
Der Groß-/Kleinbuchstabe soll zurückgegeben werden. i s l o we r ( c ) ? ( c - ' a ' +' A' i s u p p e r ( c ) ? ( c - ' A' +' a '
) : c ) : c
Es soll eine Zahl n , die zwischen 0 und 15 liegt zur Hexadezimalzahl konvertiert werden. ( n < 10) ? ( ' 0'
+ n) : ( ' a '
- 10 + n )
Einige Fallen Die Anwendung des trinären Operators führt schnell zu schlecht lesbaren Programmen und sollte daher vorsichtig eingesetzt werden. In C(++) führt die unbeabsichtigte Mehrfachauswertung in Makros zu schwer findbaren Fehlern. Gut, dass uns das in Java nicht passieren kann. Durch ausreichende Klammerung muss sichergestellt werden, dass die Ausdrücke auch in der beabsichtigten Reihenfolge ausgewertet werden. Zu allem ist der Bedingungsoperator im Gegensatz zu allen anderen Operatoren rechtsassoziativ. Die Anweisung • • • 75 • • •
b1 ? a 1 : b2 ? a 2 : a 3
ist demnach gleichbedeutend mit b1 ? a 1 : ( b2 ? a 2 : a 3 )
2.8.4 Überladenes Plus für St rings Obwohl sich in Java die Operatoren fast alle auf primitive Datentypen beziehen, gibt es doch eine bemerkenswerte Verwendung des Plus-Operators. Das spezielle Objekt St r i n g kann mit dem Plus-Operator mit anderen Strings verbunden werden. Strings wurden in Java eingeführt, da Verwaltung von Zeichenketten oft in Programmen vorkommen. In dem Kapitel über die verschiedenen Klassen wird String noch ewas präziser dargestellt, insbesondere die Gründe dargelegt, die für die Einführung eines String-Objekts auf der einen Seite aber auch für die Sonderbehandlung in der Sprachdefinition führten. Quellcode 2.h
HelloName.java
/ / Ei n Kl e i n e s , Tr a l l a l a ' - Pr o g r a mm i n J a v a cl as s { publ { // St
He l l o Na me i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) Zwe i St r i n g s d e k l a r i e r e n r i n g n a me , i nt r o;
/ / Er s e t z e " Ul l i " d u r c h d e i n e n Na me n n a me = " Ul l i " ; / / Nu n d i e Au s g a b e i n t r o = " Tr i Tr a Tr a l l a l a " + " \ " Tr u l l a l a \ " \ n , s a g e " + n a me ; Sy s t e m. o u t . p r i n t l n ( i n t r o ) ; } }
Nachdem ein String i n t r o durch verschiedene Objekte zusammengesetzt wurde, läuft die Ausgabe auf dem Bildschirm über die Funktion p r i n t l n ( ) ab. Die Funktion schreibt den String auf die Konsolenausgabe und setzt hinter die Zeile noch einen Zeilenvorschub. Das Objekt Sy s t e m. o u t definiert den Ausgabekanal.
• • 76 •• • •
KAPITEL
3 Klassen und Obj ekt e Nichts auf der Welt ist so gerecht verteilt wie der Verstand. Jeder glaubt er hätte genug davon.
3.1 Obj ekt orient iert e Program m ierung Da Java eine objektorientierte Programmiersprache ist, müssen die Paradigmen der Konzepte bekannt sein. Erstaunlicherweise ist dies nicht viel, denn Objektorientiertes Programmieren basiert nur auf einigen wenigen Ideen, die zu beachten sind. Dann wird OOP nicht zum Verhängnis und der Vorteil gegenüber modularem Programmieren kann ausgeschöpft werden. Bjarne Stroustrup (Schöpfer von C++, von seinen Freunden auch Stumpy genannt) sagte treffend über den Vergleich von C und C++: »C makes it easy to shoot yourself in the foot, C++ makes it harder, but when you do, it blows always your whole leg.«
H e r k u n f t de r OO- Spr a ch e n Jav a ist nat ür lich nicht die er st e OO- Spr ache, auch nicht C+ + . Als k lassisch sind Sm allt alk und insbesonder e Sim ula- 67 – die Säule alle OOSpr achen – anzusehen. Die eingeführ t en Konzept e sind bis heut e ak t uell und v iele Gr ößen der OOP bilden v ier Pr inzipien: Abst r ak t ion, Kapselung, Ver er bung und Polym or phie 1 .
3.1.1 Warum überhaupt OOP? In der frühen Softwareentwicklung haben sich zwei Modelle zum Entwurf von Programmen herausgebildet: Top-Down- und Bottom-Up-Analyse. Beide beschreiben eine Möglichkeit, Software durch schrittweises Verfeinern zu entwerfen. Bei der Top-Down-Analyse steht das Gesamtprogramm im Mittelpunkt und es wird nach den Funktionen gefragt, um diese an der oberen Stelle benötigte Funktionalität implementieren zu können. Ein Beispiel: Es soll ein Fahrplanauskunftprogramm geschrieben werden. An oberster Stelle verwenden wir drei Funktionen, die das Programm initialisieren, die Bildschirmmaske aufbauen und die Benutzereingaben entgegennehmen. Anschließend modellieren wir diese drei Funktionen um weitere Funktionen, beispielsweise im 1. Keine Sorge, alle vier Grundsäulen werden in den nächsten Kapiteln ausführlich beschrieben! • • • 77 • • •
Unterprogramm Initialisieren: Speicher beschaffen, Informationsdatei laden, Informationen in Datenstrukturen umsortieren. Jede dieser Funktionen wird weiterhin verfeinert, bis die gewünschte Funktionalität erreicht ist. Der Top-Down-Ansatz eignet sich somit nur für Systeme, deren unteren Stufen nacheinander entwickelt werden. Denn ohne den unteren Teil ist das gesamte Programm nicht lauffähig. Sofort wird das Problem sichtbar: Es muss von vornherein klar sein, welche Funktionen die Software hat und alle Funktionen müssen bekannt sein. Eine Modularisierung in Teilaufgaben ist schwer möglich. Diese Analyse-Technik krankt, schauen wir uns daher den Bottom-UpEntwurf an. Dieser Entwurf geht genau von der anderen Seite. Wir entwickeln erst die Komponenten der unteren Stufe und vereinigen sie dann zu einem Modul höherer Abstraktion. Problem: Diese Technik eignet sich nur dann gut zur Entwicklung, wenn die unteren Stufen tatsächlich eigenständig lauffähig sind. Beide Methoden sind nicht wirklich befriedigend und so wurden Mischformen geschaffen. Diese waren aber auch nur durchschnittlich in ihrer Fähigkeit, die Softwareprodukte zu gliedern. Objektorientierte Programmierung wird als Schlüssel zur zukünftigen Softwareentwicklung angesehen und erweitert die Leistungsfähigkeit der Analyse-Technik Bottom-Up und Top-Down. Nach der Frage, welche Faktoren zum Umdenken von prozeduraler nach objektorientierte Programmierung führten, lassen sich im wesentlichen drei Eigenschaften aufzählen. n In einem Programm stehen die Daten im Vordergrund. n Funktionen sind kurzlebiger als Daten. n Jede Software ist unzähligen Änderungen unterworfen Die Objekt-Orientierte Programmierung versucht nun diese drei Punkte am besten zu beachten und führt Verfahren ein, diese Problematik zu entschärfen. Stehen die Daten im Vordergrund, so müssen wir weniger in Funktionen denken, sondern in Objekten, die die Daten beschreiben. Da Funktionen kurzlebiger als die Daten sind, koppeln wir doch einfach an die Daten die Funktionen. Und damit Änderungen gut möglich sind, kapseln wir die Funktionen soweit von den Daten, dass sie allgemein angewendet werden können. Wir sehen schon an dieser kurzen Beschreibung, dass ein Objekt immer im Mittelpunkt steht. Und das ist kurz gesagt Objekt-Orientierte Programmierung – alles dreht sich um das Objekt.
3.1.2 Modularit ät und Wiederverwert barkeit Die Objekt-Orientierte Programmierung stellt zwei Konzepte in den Mittelpunkt des Software-Entwurfs: Wiederverwertbarkeit (das Problem ist jedem bekannt: Programmieren wiederholt sich an allen Stellen, kann das Neuschreiben nicht vermieden werden?) und Modularität. Bei der Wiederverwendung geht es darum, die Bauteile Objektorientierter Systeme, die Klassen, zu nutzen. Daher wollen wir nun erst einmal Klassen verwenden. Im zweiten Schritt werden wir dann eigene Klassen programmieren. Dann kümmern wir uns um Modularität, also wie Klassen in Verbänden gehalten werden.
3.2 Klassen benut zen Klassen sind wichtigstes Merkmal objektorientierte Programme. Eine Klasse bildet den Bauplan für konkrete Objekte – jedes Objekt ist Exemplar (auch Instanz1 oder Ausprägung) einer Klasse. Eine Klasse definiert 1. Ich möchte gerne das Wort ›Instanz‹ vermeiden und verwende dafür durchgängig im Tutorial das Wort ›Exemplar‹. Anstelle von ›instanziieren‹ tritt das einfache Wort ›erzeugen‹. • • 78 •• • •
n Attribute (Variablen, auch Felder1 genannt). n Operationen (Methoden2, die Funktionen einer Klasse) genannt, und n weitere Klassen (innere Klassen). Attribute und Operationen nennen sich auch Eigenschaften eines Objekts.
3.2.1 Anlegen eines Exem plars einer Klasse Bevor wir uns mit eigenen Klassen beschäftigen, wollen wir ein paar Klassen aus den Standardbibliotheken kennenlernen. Eine dieser Klassen ist Po i n t . Sie beschreibt einen zweidimensionalen Punkt mit den Koordinaten x und y und einige Funktionen, mit denen sich Punkt-Objekte anlegen und verändern lassen. Aus den Klassen werden zur Laufzeit Exemplare, die Po i n t Objekte, erzeugt; eine Klasse beschreibt also, wie ein Objekt aussehen wird. In einer Mengen/ElementeBeziehung gesprochen: Elemente werden zu Objekten und Mengen zu Klassen. Wir verbinden nun einen Variablennamen mit der Klasse und erstellen somit eine Referenz auf ein Po i n t Objekt. Po i n t p ;
Vergleichen wir dies mit einer bekannten Variablendeklaration einer Ganzzahl i nt i ;
so können wir uns dies gut merken, denn links steht der Typ und rechts der Name des Objekts, der Variablenname. Im Beispiel deklarieren wir eine Variable p und teilen dem Compiler mit, dass diese Referenz vom Typ Po i n t ist. Die Referenz ist bei Objektvariablen anfangs mit Null initialisiert. Durch die Deklaration einer Variablen mit dem Typ einer Klasse, wird noch kein Exemplar erzeugt, dazu müssen wir mit dem n e w Schlüsselwort ein Objekt erzeugen. Die Klammern müssen immer hinter dem n e w stehen. Wir werden später sehen, dass hier ein spezieller Methodenaufruf stattfindet, wo wir auch Werte übergeben können. p = n e w Po i n t ( ) ;
Das tatsächliche Objekt wird erst dynamisch, also zur Laufzeit, mit n e w erzeugt. Damit stellt das System Speicher bereit und bildet die zum Objekt zugehörigen Speicherzugriffsoperationen auf diesen ausgezeichneten Block ab. In den einzelnen Zeilen zur Deklaration der Variablen und der Objekterzeugung lassen sich die Variablen und Objekte, wie bei der Deklaration primitiver Datentypen, auch gleich initialisieren. doubl e pi = 3. 1415926535; Po i n t p = n e w Po i n t ( ) ;
1. Den Begriff ›Feld‹ benutze ich im Folgenden nicht. Er bleibt für Arrays reserviert. 2. In C++ auch Memberfunktionen genannt. • • • 79 • • •
3.2.2 Zugriff auf Variablen und Funkt ionen m it dem Punkt Die in einer Klasse definierten Variablen werden Exemplarvariablen (auch Objekt-, Instanz- oder Ausprägungsvariablen) genannt. Wird ein Objekt erschaffen, dann hat es seinen eigenen Satz von Exemplarvariablen, sollen sich mehrere Objekte eine Variable teilen, so ist dies explizit anzugeben. Objekte einer Klasse unterscheiden sich damit nur durch die Werte ihrer Variablen, die einen Zustand bilden. Ist das Objekt angelegt, wird auf die Methoden oder Variablen mit einem Punkt zugegriffen. Der Punkt (auch Selektor genannt) steht zwischen der Referenz und der Objekt-Eigenschaft. Folgenden Zeilen erzeugen einen Punkt und weisen den Objektvariablen Werte zu. Po i n t p = n e w Po i n t ( ) ; p. x = 12; p. y = 45;
Der Typ links vom Punkt muss immer eine Referenz darstellen. So funktioniert auch folgendes: n e w Po i n t ( ) . x = 1 ;
Dies ist allerdings unsinnig, da zwar das Objekt erzeugt und ein Attribut gesetzt wird, anschließend aber der Garbage-Collector das Objekt wieder wegräumt. Für einen Methodenaufruf kann dies schon sinniger sein. Ein Methodenaufruf gestaltet sich genau so einfach. Hinter der Referenz und dem Punkt folgt der Methodenname. Das nachfolgende Beispiel ist lauffähig und bindet gleich noch die Po i n t Klasse an, die sich in einem besonderem Paket befindet. Quellcode 3.b
MyPoint.java
i mp o r t j a v a . a wt . Po i n t ; c l a s s My Po i n t { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Po i n t p = n e w Po i n t ( ) ; p. x = p. y = 12; p . mo v e ( - 3 , 2 ) ; Sy s t e m. o u t . p r i n t l n ( p . t o St r i n g ( ) ) ; // //
a l t e r na t i v Sy s t e m. o u t . p r i n t l n ( p ) ; }
}
Die letzte Anweisung ist gültig, da p r i n t l n ( ) bei einem Objekt automatisch die t o St r i n g ( ) Methode aufruft.
• • 80 •• • •
3.3 Eigene Klassen definieren Die Deklaration einer Klasse wird durch das Schlüsselwort c l a s s eingeleitet. Hier ein Beispiel der Klasse So c k e . Diese einfache Klasse definiert Daten und Methoden. Die Signatur einer Methode bestimmt ihren Namen, ihren Rückgabewert und ihre Paramterliste. Die So c k e Klasse speichert wesentliche Attribute, die einer Socke zugeordnet werden. Zu unserer Sockenklasse wollen wir ein konkretes Java-Programm angeben. Eine Klasse So c k e definiert g e wi c h t und f a r b e und die andere Klasse erzeugt in der ma i n ( ) Funktion das So c k e Objekt. Wir erkennen am Schlüsselwort p r i v a t e , dass es Daten geben kann, die nach außen nicht sichtbar sein sollen. Innerhalb der Klasse lässt sich das Attribut selbstverständlich verwenden. (Wer sonst?) Quellcode 3.c
SockeDemo.java
c l a s s So c k e { p u b l i c St r i n g f a r b e ; publ i c i nt g e wi c h t ; voi d t r oc kne ( ) { i s t Tr o c k e n = t r u e ; } v o i d wa s c h e ( ) { i s t Tr o c k e n = f a l s e ; } v o i d i s t Tr o c k e n ( ) { r e t u r n i s t Tr o c k e n ; }
p r i v a t e b o o l e a n i s t Tr o c k e n ; } c l a s s So c k e De mo { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { So c k e s t i n k i ; s t i nki s t i nki . s t i nki . s t i nki . Sy s t e m.
= n e w So c k e ( ) ; f a r be = " r ot " ; g e wi c h t = 5 6 5 ; wa s c h e ( ) ; o u t . p r i n t l n ( " I s t d i e So c k e t r o c k e n ? " + s t i n k i . i s t Tr o c k e n ( ) ) ;
} }
• • • 81 • • •
Die angegebene Klasse enthält die Methode t r o c k n e ( ) und zwei Variablen. Existiert das Objekt So c k e und wird zum Waschen aufgefordert, dann schickt das erfragende Objekt eine Nachricht (auch Botschaft) an So c k e . In der Konsolenausgabe erfahren wir dann über i s t Tr o c k e n ( ) , ob die Socke feucht ist oder nicht. i s t Tr o c k e n ( ) gibt ein b o o l e a n 1 zurück. Damit kapselt die Methode die private Variable i s t Tr o c k e n , auf der kein Zugriff von außen möglich ist. Das Beispiel zeigt auch, dass eine Attribut und eine Methode den gleichen Namen besitzen kann. Im herkömmlichen Sinne wird der Begriff Nachricht2 eher als Verschicken einer Botschaft eines dynamisches Objekts verwendet und Funktionsaufrufe als statisch angesehen. Hier ist allerdings der Begriff der Funktion nicht so statisch zu sehen. Auch unter Java stehen Funktionen nicht zwangsläufig zur Compilezeit fest. Nachrichten an Objekte werden in Java vollständig über Funktionen realisiert; Selektoren3 stehen nicht zur Verfügung.
Met hodenaufrufe Alle Attribute und Operationen einer Klasse sind in der Klasse selbst sichtbar. Das heißt, innerhalb einer Klasse werden die Exemplarvariablen und Funktionen mit ihrem Namen verwendet. Somit greift die Funktion t r o c k n e n ( ) direkt auf die möglichen Attribute zu. Wird eine Methode aufgerufen und gibt sie einen Wert zurück, so kann dieser ein Primitiver Datentyp, ein Referenz-Typ oder ein v o i d -Typ sein.
An z a h l de r Pa r a m e t e r I m Gegensat z zu C( + + ) m uss beim Aufr uf der Funk t ion die Anzahl der Par am et er ex ak t st im m en. Diese sind fest und folglich t y psicher . Ein Nacht eil ist dies nicht , denn über ladene Funk t ionen m achen den Einsat z v on v ar iablen Par am et er n fast über flüssig.
3.3.1 Argum ent übergabe In Java werden alle Datentypen als Wert übergeben (engl. Call by Value). Für primitive Datentypen bedeutet dies, sie werden als lokale Variable ins Unterprogramm eingebracht. Objekte werden nicht kopiert, ihre Referenz wird übergeben. c l a s s So c k e { v o i d f u n c ( i n t z a h l , Ob j e k t o b j ) { ... } } c l a s s Ma i n { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) 1. In reinen objektorientierten Programmiersprachen wie Smalltalk oder EIFFEL würde der Rückgabewert immer ein Objekt sein. 2. In Objective-C wird durchgängiger von Nachrichten gesprochen, die Syntax unterstützt dies auch. In C++, ein recht undynamisches System, werden weiterhin die Wörter Methoden und Member-Funktionen verwerdet. 3. Selektoren entsprechen in Sprachen wie Smalltalk oder Objektive-C Funktionsaufrufen in Java. Die Besonderheit hierbei ist: Botschaften werden mit Namen benannt. Am Beispiel Smalltalk: 17388 gcd: 1251. Dem Adressaten 17388 wird das durch den Selektor gcd bestimmte Objekt 1251 zugesandt – das Ergebnis ist das Objekt 9. • • 82 •• • •
{ So c k e s = n e w So c k e ( ) ; So c k e t = n e w So c k e ( ) ; i nt n = 67; s . f unc ( n, t ) ; } }
Wird die Variable n der Funktion f u n c ( ) übergeben, so gibt es nur Veränderungen in dieser Methode an der lokalen Variablen z a h l . Eine Veränderung von n tritt somit nicht nach außen und bleibt lokal. (In C(++) wäre dies möglich und es nennt sich dort ›call by reference‹). Werden jedoch Objektereferenzen übergeben, so führen Attributänderungen zu Änderungen im Objekt. Zeigt die Referenz o b j auf ein Socken-Objekt t , findet jede Änderung dort statt. Andernfalls müsste eine Kopie des Objekts angelegt werden, was aber nicht der Fall ist. (In Java kann auch kein Copy-Konstruktor definiert werden.)
3.3.2 Lokale Variablen Variablen können in der Klasse oder in der Funktion deklariert werden. Globale Variablen, die für alle Funktionen sichtbar sind, gibt es in Java nicht. Eine globale Variable müsste in einer Klasse definiert werden, die dann alle Klassen übernehmen. Häufiger sind jedoch lokale Variablen, die innerhalb von Methoden deklariert werden. Diese haben einen beschränkten Wirkungsbereich. Sie sind nur in dem Block gültig, in dem sie definiert wurden. Da ein Block immer mit geschweiften Klammern angegeben wird, erzeugen wir durch die Zeilen { i nt i ; { i nt j ; } }
zwei Blöcke, die ineinander geschachtelt sind. Zu jeder Zeit können Blöcke definiert werden. Außerhalb des Blocks sind deklarierte Variablen nicht sichtbar. Nach Abschluss des inneres Blocks, der j deklariert, ist ein Zugriff auf j nicht mehr mögich; auf i ist der Zugriff weiterhin erlaubt. Falls Objekte im Block angelegt wurden, wird der GC diese wieder freigeben, falls keine zusätzliche Referenz besteht.
3.3.3 Die t his- Referenz Hat eine lokale Variable den gleichen Namen wie eine Exemplarvariable, so verdeckt sie diese. Das heißt aber nicht, dass auf die Exemplarvariable nicht mehr zugegriffen werden kann. Zum Einsatz kommt der t h i s -Zeiger1, ein Zeiger, der immer auf die Klasse zeigt. Mit diesem t h i s -Zeiger kann auf das aktuelles Objekt zugegriffen werden (t h i s -Referenz) und entsprechend mit dem PunktOperator die Variable ausgelesen werden. Häufiger Einsatzort sind Funktionsparameter, die genauso genannt werden wie die Exemplar-Variablen, um damit eine starke Zugehörigkeit auszudrücken. 1. In C++ existiert ebenfalls ein this-Zeigerm, der die gleiche Funktion erfüllt. In Objective-C entspricht dieser Zeiger dem ›self‹. • • • 83 • • •
cl as s T { i nt x; T( i n t x ) { Sy s t e m. o u t . p r i n t l n ( t h i s . x , x ) ; t hi s . x = x; } }
In der Klasse T wird mit der Funktion T – es handelt sich in einem Falle um einen Konstruktor der Klasse – ein Wert übergeben.
3.3.4 I nit ialisierung von lokalen Variablen Während Objekt-Variablen automatisch mit einem Null-Wert initialisiert werden, geschieht dies bei lokalen Variablen nicht. Das heißt, der Programmierer muss sich selber um die Initialisierung kümmern. Häufig passieren Fehler bei falsch angewendeten Konditionen, wie das folgendes Beispiel demonstriert. voi d t e s t ( ) { i n t n e n e , wi l l i Wu r m; n e n e += 1 ; / / Co mp i l e r f e h l e r n e n e = 1 ; n e n e += 1 ; / / i s t OK i f ( n e n e == 1 ) wi l l i Wu r m = 2 ; wi l l i Wu r m += 1 ; }
Die beiden lokalen Variablen n e n e und wi l l i Wu r m werden nicht automatisch mit Null initialisiert – so wie dies für Eigenschaften-Variablen autoamtisch geschieht. So kommt es bei der Inkrementierung von n e n e zu einem Compilerfehler. Denn dazu ist erst eine Lesezugriff auf die Variable nötig um dann den Wert Eins zu addieren. Die erste Referenz muss aber eine Zuweisung sein. Oftmals gibt es jedoch bei Zuweisungen unter Konditionen Probleme. Würde in der i f -Abfrage wi l l i Wu r m nicht mit Zwei gesetzt, so wäre nur unter der Bedingung n e n e gleich Eins ein Lesezugriff auf wi l l i Wu r m nötig. Da diese Variable jedoch vorher nicht gesetzt wurde, ergäbe sich das oben angesprochene Problem.
3.3.5 Privat sphäre Innerhalb einer Klasse sind alle Funktionen und Attribute für die Methoden sichtbar. Damit die Daten einer Klasse vor externem Zugriff geschützt sind und Methoden nicht von außen aufgerufen werden können, unterbindet das Schlüsselwort p r i v a t e allen von außen zugreifenden Klassen den Zugriff. Als Beispiel definieren wir uns eine Klasse Pa s s wo r d mit dem privaten Element p a s s .
• • 84 •• • •
c l a s s Pa s s wo r d { p r i v a t e St r i n g p a s s ; }
Eine Klasse j will nun auf das Passwort zugreifen: c l a s s Pa s s De mo { Pa s s wo r d p wd = n e w Pa s s wo r d ( ) ; Sy s t e m. o u t . p r i n t l n ( p wd . p a s s ) ;
/ / Co mp i l e r f e h l e r
}
Die Klasse Pa s s wo r d enthält den privaten String p a s s und dieser kann nicht referenziert werden. Der Compiler erkennt zur Compile- bzw. Laufzeit Verstöße und meldet diese. Allerdings wäre es maches mal besser, wenn der Compiler uns nicht verraten würde, dass das Element privat ist, sondern einfach nur melden würde, dass es dieses Element nicht gibt. Private Funktionen und Variablen dienen in erster Linie dazu, den Klassen Modularisierungsmöglichkeiten zu geben, die von außen nicht sichbar sein müssen. Zwecks Strukturierung werden Teilaufgaben in Funktionen gegliedert, die aber von außen nie alleine aufgerufen werden dürfen. Da die Implementierung versteckt wird und der Programmierer vielleicht nur eine Zugriffsfunktion sieht, wird auch der Terminus ›Data Hiding‹ verwendet. Zum Beispiel ein Radio. Von außen bietet es die Funktionen a n ( ) , a u s ( ) , l a u t e r ( ) , l e i s e r ( ) an, aber wie es ein Radio zum Musikspielen bringt ist eine ganz andere Frage, die wir lieber nicht beantwortet wissen wollen. Dem unerlaubten Zugriff steht der freie Zugriff auf Funktionen und Variablen entgegen. Eingeleitet wird dieser durch das Schlüsselwort p u b l i c . Damit ist von außen jederzeit Zugriff möglich. Wird weder p u b l i c , p r o t e c t e d noch p r i v a t e verwendet, so ist ein Zugriff von außen nur innerhalb des Paketes möglich. Von außen ist der Zugriff dann untersagt, quasi ein eingebautes p r i v a t e . Der Einsatz von p r i v a t e zeigt sich besonders beim Ableiten von Funktionen. Denn werden Bezeichner nicht mit p r i v a t e gekennzeichnet, dann wird der Zugriff auch auf diese Funktionen in der Subklasse erlaubt. Das soll aber nicht immer sein, private Informationen sollen auch manche Subklasse interessieren. Dazu wiederum dient das Schlüsselwort p r o t e c t e d . Damit sind Mitglieder einer Klasse für die Unterklasse sichtbar sowie im gesamten Paket. 1
Zusammenfassend:
1. Die mit p u b l i c deklarierten Methoden und Variablen sind überall dort sichtbar, wo auch die Klasse verfügbar ist. Natürlich kann auch eine erweiternde Klasse auf alle Elemente zugreifen. 2. p r i v a t e : Die Methoden und Variablen sind nur innerhalb der Klasse sichtbar. Auch wenn diese Klasse erweitert wird, die Elemente sind nicht sichtbar. 3. p r o t e c t e d : Wird eine Klasse erweitert, so sind die mit p r o t e c t e d deklarierten Variablen und Methoden in der Unterklasse sichtbar, aber nicht außerhalb. Zudem gilt die Erweiterung, dass alle Klassen im gleichen Paket auch den Zugriff bekommen. Ein Paket ist ein Verzeichnis mit Klassen. Der Einsatz der Schlüsselworte p u b l i c , p r i v a t e und p r o t e c t e d sollte überlegt erfolgen und objektorientierte Programmierung zeichnet sich durch durchdachten Einsatz von Klassen und deren Beziehungen aus. Am Besten ist die einschränkendste Beschreibung. Also nie mehr Öffentlichkeit als notwendig. 1. Nur in diesem Punkt weicht protected von der C++ gewohnten Definition ab. • • • 85 • • •
3.4 St at ische Met hoden und Variablen Exemplar-Variablen sind eng mit ihrer Klasse verbunden. Wird ein Objekt erschaffen, dann operieren alle Funktionen auf einem eigenen Satz von Variablen. Ändert ein Objekt den Datenbestand, so hat dies keine Auswirkungen auf die Daten der anderen Objekte.
3.4.1 St at ische Variablen Müssen sich aber erzeugte Objekte gemeinsam Variablen aus der Klasse teilen, so werden die Variablen mit s t a t i c gekennzeichnet; diese Variablen werden auch Statische Variablen genannt. Statische Variablen werden oft verwendet, wenn Objekte über eine gemeinsame Variable kommunizieren. c l a s s So c k e { s t a t i c i nt a nz a hl ; s t a t i c d o u b l e wi t z Fa k t o r ( d o u b l e I Q ) { ... } }
Die Variable a n z a h l kann nun von jedem Objekt der Klasse So c k e verwendet werden, aber eine Änderung ist für alle Objete sichtbar. In einer statischen Variablen kann beispielsweise festgehalten werden, wie oft ein Klasse erzeugt wurde. Für Socken wäre interessant, dass sie testen können, wieviele Socken es sonst gibt. Dabei wird lediglich im Konstruktor die Variable a n z a h l um eine Stelle erhöht. Statische Variablen können als Klassenvariablen angesehen werden. Vergleichbares findet sich bei der Programmiersprache Smalltalk, nicht jedoch bei Objective-C – dort müssen globale Variablen verwendet werden. Da die Variable für alle Objekte immer die gleiche ist, sind statische Variablen so etwas wie globale Variablen.
3.4.2 St at ische Met hoden Auch Funktionen können mit s t a t i c gekennzeichnet werden. Dies hat einen einfachen Grund: Um auf Variablen oder Memberfunktion zuzugreifen, wird kein Objekt benötigt, im obigen Fall p , um auf die Werte zuzugreifen. St a t i c De mo . wi e Of t Be n u t z t =+ 1 ; Sy s t e m. o u t . p r i n l n ( " I c h b i n s o t o l l : " + St a t i c De mo . wi t z Fa k t o r ( 1 0 0 ) ) ;
Das heißt, statische Methoden und Variablen existieren ohne Exemplar eines Objekts. Für deren Einsatz sprechen verschiedene Gründe: n Sie können überall aufgerufen werden, wo der Klassenname verfügbar ist. n Sinnvoll für Utility-Funktionen, die unabhängig von Objekten sind. Ein gutes Beispiel ist die Klasse Ma t h . Die Funktionen daraus müssen immer benutzbar sein, ein Objekt vom Typ Ma t h braucht nicht erzeugt zu werden. Von jeder Stelle im Programmcode kann aber die Wurzelfuktion mit Ma t h . s q r t ( ) aufgerufen werden.
• • 86 •• • •
3.4.3 Konst ant en m it dem Schlüsselwort final bei Variablen Statische Variablen werden auch verwendet, um Konstanten zu definieren. Dazu dient zusätzlich das Schlüsselwort f i n a l . Damit wird dem Compiler mitgeteilt, dass mit dieser Variablen oder Funktion nichts mehr passieren darf. Für Variablen bedeutet dies: Es sind Konstanten, jeder Schreibzugriff wäre ein Fehler und für Klassen: Diese Klasse kann nicht Basisklasse einer anderen sein, sie kann demnach nicht erweitert werden. c l a s s Ra d i o { s t a t i c f i n a l f l o a t SWF3 = 1 0 1 . 1 0 F, SDR3 = 9 9 . 9 0 F, BFBS = 1 0 3 . 0 F; }
In der Klasse Ra d i o werden drei Konstanten definiert. Es ist eine gute Idee, Konstanten groß zu schreiben, um deren Bedeutung hervorzuheben. Finale Variablen müssen nicht zwingend bei ihrer Definition einen Wert zugewiesen bekommen. Dies kann auch genau einmal im Programmcode geschehen. Folgende ist gültig: f i na l i nt a ; ... a = 2;
In der Vergangenheit gab es beim Javac und Jikes-Compiler Fehler, so dass sie mehrfache Zuweisung erlaubten.1
3.4.4 Der Einst iegspunkt für das Laufzeit zeit syst em Wie in C und C++ gibt es eine ausgezeichnete Funktion ma i n ( ) , die angesprungen wird, wenn die vom Laufzeitsystem angesprochene Klasse erzeugt wird. Folgendes kleines Programm verdeutlicht die Signatur der ma i n ( ) Funktion: Quellcode 3.d
Toll.java
c l a s s To l l { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Sy s t e m. o u t . p r i n t l n ( " J a v a i s t t o l l " ) ; } }
Die ma i n ( ) Funktion ist für alle zugänglich (p u b l i c ) und auf jeden Fall statisch (s t a t i c ) zu deklarieren. Stimmt die Signatur nicht überein – es wird kein String sondern ein Stringfeld als Argument verlangt – dann wird diese Funktion nicht als Ansprungsfunktion von der virtuellen Maschine erkannt.
1. Ein Beispiel, welches den Fehler reproduziert, findet der Leser auf http://java-tutor.com/faq.html. • • • 87 • • •
N a m e d e s Pr og r a m m s I m Gegensat z zu C/ C+ + st eht im Ar gum ent Null nicht der Dat einam e – der j a unt er Jav a der Klassennam e ist –, sonder n der er st e Über gabepar am et er .
Die Anzahl der Param et er Eine besondere Variable für die Anzahl der Parameter ist natürlich nicht von nöten, da das StringArray-Objekt selbst weiß, wieviel Parameter es enthält. Im nächsten Quellcode können wir bei der Ausführung hinter dem Klassennamen noch einen Namen übergeben. Dieser wird dann auf der Shell ausgegeben. Quellcode 3.d
Hello.java
c l a s s He l l o { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { i f ( a r gs . l e ngt h > 0 ) { Sy s t e m. o u t . p r i n t l n ( " Ha l l o " + a r g s [ 0 ] ) ; } } }
Wir müssen eine Schleife verwenden, um alle Namen auszugeben. Quellcode 3.d
LiebtHamster.java
c l a s s Li e b t Ha ms t e r { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { i f ( a r g s . l e n g t h == 0 ) Sy s t e m. o u t . p r i n t l n ( " Wa s ! ! Ke i n e r l i e b t k l e i n e Ha ms t e r ? " ) ; el s e { Sy s t e m. o u t . p r i n t ( " Li e b t k l e i n e Ha ms t e r : " ) ; i nt i = 0; wh i l e ( i < a r g s . l e n g t h ) Sy s t e m. o u t . p r i n t ( a r g s [ i ++] + " " ) ; Sy s t e m. o u t . p r i n t l n ( ) ; } } }
• • 88 •• • •
Die Modifizierer von m ain( ) Die Signatur der ma i n ( ) Methode ist immer mit den Modifizierern p u b l i c , s t a t i c und v o i d anzugeben. Dass die Methode statisch ist, muss gelten, da auch ohne Exemplar der Klasse ein Funktionsaufruf möglich sein soll. Doch die Sichtbarkeit p u b l i c muss nicht zwingend gelten, da die JVM auch eine Applikation mit einer privaten ma i n ( ) Methode finden könnte. Hier ist einzig und allein die Durchgängigkeit im Design der Grund. Die Idee ist, dass von außerhalb einer Klasse und auch außerhalb des Paketes auf die Methode ma i n ( ) ein Zugriff möglich sein soll. Und dieser externe Zugriff ist eben nur mit p u b l i c erreichbar. Eine Ausnahme bei der speziellen Methode ma i n ( ) wäre also denkbar, jedoch nicht sauber. Wer jedoch gegen die Regel verstößt und p u b l i c weglässt, wird merken, dass auch ohne p u b l i c sich ein Programm compilieren und auch nutzen lässt. Dann gelten aber die Beschränkungen des Paketes und ein Zugriff von einem anderen Verzeichnis ist untersagt.
3.4.1 Der Rückgabewert von m ain( ) Der Rückgabewert v o i d ist sicherlich diskussionswürdig, da die Sprachentwerfer auch hätten fordern können, dass ein Programm immer für die Shell einen Wert zurückgibt. Da jedoch nicht wie in C(++) der Rückgawert i n t oder v o i d ist, lassen sich Rückgabewerte nicht über ein r e t u r n übermitteln, sondern ausschließlich über eine spezielle Funktion e x i t ( ) im Paket System. f i n a l c l a s s j a v a . l a n g . Sy s t e m Ÿ s t a t i c voi d e xi t ( i nt s t a t us ) Beendet die aktuelle JVM und gibt das Argument der Methode als Statuswert zurück. Ein Wert
ungleich Null zeigt einen Fehler an. Also ist der Rückgabewert beim normalen fehlerfreien Verlassen Null. Eine Se c u r i t y Ex c e p t i o n wird geworfen, falls sich der aktuelle Thread nicht mit dem Status beenden lässt.
3.5 Met hoden überladen Fehlende variable Parameterlisten, werden durch die Möglichkeit der überladenen Methoden nahezu unnötig.
ü be r la de n e M e t h ode n Eine über ladene Met hoden ist eine Funkt ion m it gleichem Nam en aber v er schiedenen Par am et er n.
Das bekannte p r i n t ( ) ist eine überladene Funktion, die etwa wie folgt definiert ist: c l a s s Pr i n t St { voi d pr i nt voi d pr i nt voi d pr i nt }
r eam ( Ob j e c t a r g ) { . . . } ( St r i n g a r g ) { . . . } ( c ha r [ ] a r g ) { . . . }
• • • 89 • • •
Wird nun die Funktion p r i n t ( ) mit irgendeinem Typ aufgerufen, dann wird die am besten passende Funktion rausgesucht. Versucht der Programmierer beispielsweise die Ausgabe eines Objekts Da t e , dann stellt sich die Frage, welche Methode sich darum kümmert. Glücklicherweise ist die Antwort nicht schwierig, denn es existiert auf jeden Fall eine Print-Methode, welche Objekte ausgibt. Und da auch Da t e , wie auch alle anderen Klassen, eine Subklasse von Ob j e c t ist, wird diese p r i n t ( ) Funktion gewählt. Natürlich kann nicht erwartet werden, dass das Datum in einem ausgewählten Format ausgeben wird, jedoch wird eine Ausgabe auf dem Schirm sichtbar. Denn jedes Objekt kann sich durch den Namen identifizieren und dieser würde in dem Falle ausgegeben. Obwohl es sich so anhört, als ob immer die Funktion mit dem Parameter Objekt aufgerufen wird, wenn der Datentyp nicht angepasst werden kann, ist dies nicht ganz richtig. Wenn der Compiler keine passende Klasse findet, dann wird das nächste Objekt im Ableitungsbaum gesucht, für die in unserem Falle eine Ausgabefunktion existiert.
3.6 Obj ekt e anlegen und zerst ören Die Objekte werden, soweit nicht durch f i n a l ausgeschaltet, mit dem n e w-Operator angelegt. Der Speicher wird dabei auf dem System-Heap reserviert1, das Laufzeitsystem übernimmt diese Aufgabe. Wird das Objekt nicht mehr referenziert, so räumt der GC in bestimmten Abständen auf und gibt den Speicher an das Laufzeitsystem zurück.
3.6.1 Die Erschaffung von Obj ekt en Bei der Erschaffung eines Objekts sollen oftmals Initialisierungen durchgeführt und damit die Klasse in einen Anfangszustand gesetzt werden. Dazu dienen Konstruktoren. Der Konstruktor ist eine besondere Funktion, die optional auch Übergabeparameter erlaubt. Da mitunter mehrere Konstruktoren mit unterschiedlichen Namen vorkommen, ist die Funktion damit oft überladen. Der Einsatz von Konstruktoren bietet verschiedene Vorteile: n Einige Klassen beinhalten Variablen, die ohne vorherige Zuweisung bzw. Initialisierung keinen Sinn machen würden. n Einige Klassen verwalten Daten und bereiten spezielle Datenstrutkuren vor. Konstruktoren werden ausgeführt, nachdem die Objektvariablen intialisiert sind. c l a s s So c k e { So c k e ( ) { / * Hi e r wi r d wa s e r z e u g t * / } So c k e ( St r i n g f a r b e ) { / * Er z e u g e mi t Fa r b e * / } So c k e ( St r i n g f a r b e , i n t g r ö ß e ) { / * Er z e u g e mi t z we i * / } }
Ein Konstruktor ohne Argumente ist der Standard-Konstruktor (auch Default-Konstruktor, selten No-Arg-Konstruktor). Im Beispiel ist der erste Konstruktor der Standard-Konstruktor. Wenn es in unseren Klassen keinen Konstruktor gibt, so wird automatsich ein Standard-Konstruktor angelegt. Wenn es jedoch mindestens einen parametrisierten Konstruktor gibt, wird dieser Standard-Konstruktor nicht mehr automatisch angelegt. Wollen wir daher ein Objekt einfach mit n e w Ob j e k t ( ) erzeugen, so müssen wir den Standard-Konstruktor per Hand hinzufügen. Dass der Standard-Konstruktor dann nicht mehr angelegt wird, hat einen guten Grund. Denn andernfalls ließe sich ein Objekt anlegen, ohne das wichtige Variablen initialisiert wurden. 1. Ich vermeide hier die Wörer alloziert oder allokiert. • • 90 •• • •
Ein Konstruktor wird bei der Erschaffung eines Objekts durch den n e w-Operator ausgelöst. So erzeugt So c k e o mi = n e w So c k e ( " o r a n g e " ) ;
ein Objekt vom Typ So c k e . Die Laufzeitumgebung von Java reserviert soviel Speicher, dass ein Objekt vom Typ So c k e dort Platz hat, ruft den Konstruktor auf und gibt eine Referenz auf das Objekt zurück; die hier im Beispiel der Variablen o mi zugewiesen wird. Kann das System nicht genügend Speicher bereitstellen, so wird der GC aufgerufen und kann dieser keinen freien Platz besorgen, generiert die Laufzeitumgebung einen Ou t Of Me mo r y Er r o r . (Wohlgemerkt einen Fehler und keine Exception! Ein Fehler kann nicht aufgefangen werden.) Im obengenannten Beispiel wird nicht der Standard-Konstruktor aufgerufen, sondern einer, der einen String als Parameter akzeptiert. Welcher der Konstruktoren nun schließlich aufgerufen wird, ist zur Laufzeit bekannt. Der Compiler erkennt den passenden Konstruktor, wie die passende Methode, an den Typen der Parameter. Mitunter werden zwar verschiedene Konstruktoren verwendet aber in einem Konstruktor verbirgt sich die tatsächliche Initialisierung des Objekts. Ein Konstruktor möchte daher einen anderer Konstruktor derselben Klasse – nicht den der Oberklasse – aufrufen, um nicht gleichen Programmcode ausprogrammieren zu müssen. Dazu dient wieder das Schlüsselwort t h i s . c l a s s So c k e { St r i n g f a r b e ; So c k e ( St r i n g f a r b e ) { t hi s . f a r be = f a r be ; } So c k e ( ) { t h i s ( " s c h wa r z " ) ; }
/ / t h i s i s t h i e r d i e Re f e r e n z
/ / t h i s ( ) a n Ko n s t r u k t o r we i t e r
}
Die Klasse So c k e besitzt zwei Konstruktoren, den Standard-Konstruktor und einen Ein-ParameterKonstruktor. Wird ein neues Objekt mit n e w So c k e ( ) aufgebaut, wird der Standard-Konstruktor aufgerufen und ihm anschließend die Farbe Schwarz im parametrisierten Konstruktur übergeben – Standardsocken sind einfach schwarz. Natürlich stellt sich die Frage, warum wir denn einen zweiten Aufruf starten. Viel einfacher wäre doch für den Standard-Konstruktor folgendes: So c k e ( ) { f a r b e = " s c h wa r z " ; }
Das ist in der Tat weniger und auch vermutlich schneller, doch diese Implementierung hat einen großen Nachteil. Nehmen wir an, wie hätte 10 Konstruktoren für alle erdenklichen Fälle in genau diesem Stil implementiert. Kommt das Unerhoffte, dass wir auf einmal in jedem Konstruktor etwas initialisieren müssen, so muss der Programmcode, etwa ein Aufruf der Methode i n i t ( ) , in jedem • • • 91 • • •
der Konstruktoren gesetzt werden. Dieses Problem umgeben wir einfach, in dem wir die Arbeit auf einen speziellen Konstruktor verschieben. Ändert sich nun das Programm in der Weise, dass überall beim Initialisieren Programmcode ausgeführt werden muss, ändern wir nun eine Zeile in dem konkreten von allen benutzen Konstruktor und für uns fällt wenig Änderungsarbeit an. Aus softwaretechnischen Gesichtspunkten ein großer Vorteil. Überall in den Java Bibliotheken lässt sich diese Technik wiedererkennen. Ein schönen einfaches Beispiel ist etwa die Po i n t Klasse. Der Aufruf von t h i s ( ) muss in der erster Zeile stehen! Auch können als Parameter von t h i s ( ) keine Exemplarvariablen übergeben werden. Möglich sind aber statische finale Variablen. c l a s s So c k e { f i n a l i n t r i n g e l An z a h l = 4 ; s t a t i c f i n a l i n t RI NGEL_ ANZAHL = 4 ; So c k e ( St r i n g g , i n t a n z Ri n g ) { . . . } So c k e ( St r i n g f ) { // t h i s ( f , r i n g e l An z a h l ) ; t h i s ( f , RI NGEL_ ANZAHL ) ; } }
/ / ni c ht e r l a ubt / / da s ge ht s t a t t de s s e n
Da Exemplarvariablen bis zu einem bestimmten Punkt noch nicht initialisiert sind, lässt uns der Compiler nicht darauf zugreifen – nur statische Exemplarvariablen sind als Übergabeparameter erlaubt.
3.6.2 Zerst örung eines Obj ekt s durch den Müllaufsam m ler Glücklicherweise werden wir beim Programmieren von der lästigen Aufgabe befreit, Speicher von Objekten freizugeben. Wird ein Objekt nicht mehr referenziert, dann wird der Garbage Collector1 (kurz GC) aufgerufen, und dieser kümmert sich um alles weitere – der Entwicklungsprozess wird dadurch natürlich vereinfacht. Der Einsatz eines GCs verhindert zwei große Probleme: n Ein Objekt kann gelöscht werden, aber die Referenz existiert noch (engl. dangling pointer). n Kein Zeiger verweist auf ein bestimmtes Objekt, dieses existiert aber noch im Speicher (engl. memory leaks). Dem GC wird es leicht gemacht, wenn o b j e c t = n u l l gesetzt wird, denn dann weiß der GC, dass zumindest eine Verweis weniger auf das Objekt existiert. War es die letzte Referenz, kann der GC dieses Objekt entfernen.
D e st r u k t or e n Einen Dest uk t or , so w ie in C+ + , gibt es in Jav a nicht . Wohl k önnen w ir eine Funk t ion f i n a l i z e ( ) auspr ogr am m ier en, in der Aufr äum ar beit en er ledigt w er den. Die Met hode er bt j ede Klasse v on Ob j e c t . I m Gegensat z zu C+ + m it einer m anuellen Fr eigabe ist aller dings in Jav a k eine Aussage über den Zeit punk t m achbar ,
1. Lange Tradition hat der Garbage Collector unter LISP und unter Smalltalk. • • 92 •• • •
an dem die Rout ine aufger ufen w ir d – dies ist v on der I m plem ent ier ung des GCs abhängig. Es k ann auch sein, dass f i n a l i z e ( ) über haupt nicht aufger ufen w ir d. Dies k ann dann passier en, w enn die VM genug Speicher hat und dann beendet w ir d.
Der GC erscheint hier als ominöses Ding, welches clever die Objekte verwaltet. Doch was ist der GC? Implementiert ist er als Thread in niedriger Priorität, der laut einer Netzaussage etwa 3% der Rechenleistung benötigt. Er verwaltet eine Liste der Objekte und in regelmäßigen Abständen werden nicht benötigte Objekte markiert und entfernt. Effiziente GCs sind noch Teil der Forschung, Sun verwendet jedoch einen sehr einfachen Algorithmus, der unter dem Namen ›Mark and Sweep‹ bekannt ist1. Das Markieren der nicht mehr verwendeten Objekte nimmt jedoch die meiste Zeit in Anspruch. In der Implementierung des GCs unterscheiden sich auch die Java-Interpreter der verschiedenen Anbieter. So verwendet die VM von Microsoft eine effizientere Strategie zum Erkennen und Entfernen der Objekte. Sie verwenden einen modifizierten ›Stop and Copy‹ Algorithmus, der schneller ist, als die gewöhnlichen GC-Strategien. Somit wirbt Microsoft nicht ohne Recht damit, dass ihre VM einen Geschwindigkeitsvorteil Faktor 2-4 gegenüber der Sun-Implementierung besitzt (natürlich nicht immer so 100% kompatibel). Insbesondere ist das Anlegen von Objekten bei Microsofts VM flott. Mittlerweile ist auch das Anlegen von Objekten unter der Java VM von Sun dank der Hot-Spot Technologie schneller geworden. Hot-Spot ist seit Java 1.3 fester Bestandteil des JDK. Moderne Laufzeitumgebungen entscheiden den Vergleich mit Microsoft für sich, da die JVM der Redmonder zur Zeit nicht weiter entwickelt wird.
3.7 Gegenseit ige Abhängigkeit en von Klassen In Java brauchen wir uns keine Gedanken um die Reihenfolge der Deklarationen zu machen. Wo es in anderen Sprachen genau auf die Reihenfolge ankommt, kann in Java eine Klasse eine andere benutzen auch wenn diese erst später implementiert ist. In C ist dies ein bekanntes Problemfeld. Wir wollen eine Funktion nutzen, müssen diese aber vor den Aufruf stellen (oder mit e x t e r n und solchen widerlichen Konstruktionen). Noch schlimmer ist dies bei verketteten Listen und ähnlichen Datenstrukturen. Dann wird dort erst deklariert (zum Bespiel c l a s s To l l ) und später definiert und implementiert. In Java können wir uns ganz und gar auf den Compiler verlassen – es ist seine Aufgabe mit den Abhängigkeiten zurechtzukommen. Ein gewisses Problem bereiten die Abhängigkeiten dennoch, zum Beispiel das eines Angestelltenverhältnisses. Jeder Arbeiter hat einen Vorarbeiter aber auch ein Vorarbeiter ist ein Arbeiter. Wie sollen nun die Klassen implementiert werden? Definieren wir die Arbeiter-Klasse zuerst, kann der Vorarbeiter sie erweitern. Aber dann kennt der Arbeiter noch keinen Vorabeiter! Dieses Problem ist glücklicherweise nur in C oder C++ problematisch. In Java hilft uns der Compiler, denn dieser schaut während des Compile-Vorgangs in die Dateien der importierten Pakete und auch in der eigenen Datei etwas vorraus. Das Arbeiter/Vorarbeiter-Problem kann auf zwei Wegen gelöst werden: Der erste Weg: Jede Klasse wird in eine Datei gekapselt. So etwa die Datei Ar b e i t e r . j a v a und eine Datei Vo r a r b e i t e r . j a v a oder beide Klassen in der Datei, die die Klassen wie folgt implementieren: c l a s s Ar b e i t e r { 1.
Der Garbage Collector von VisualWorks Smalltalk gehört mit zu den besten Implementierungen. Bei einem direkten Vergleich von VisualWorks und SUNs JVM ist die JVM beim anlegen und entfernen von 100000 Objekten etwa fünfmal langsamer als VisualWorks. Auch VisualWorks benutzt einen weiterentwickelten Stop and Copy Algorithmus. • • • 93 • • •
Vo r a b e i t e r v o r a r b e i t e r ; / / wa s e i n e n Ar b e i t e r s o a u s z e i c h n e t }
Im Falle der Dateitrennung wird der der Compiler in die Datei schauen und im anderen Fall wird der Compiler die andere Klasse in der gleichen Datei finden. c l a s s Vo r a r b e i t e r e x t e n d s Ar b e i t e r { / / u n d h i e r , wa s e r a l l e s me h r h a t }
3.8 Vererbung Die Klassen in Java sind in Hierarchien geordnet. Von Ob j e c t erben automatisch alle Klassen, direkt oder indirekt. Eine neu definierte Klasse kann durch das Schlüsselwort e x t e n d s eine Klasse erweitern. Sie wird dann zur Unter- oder Subklasse. Die erweiterte Klasse hießt Oberklasse (auch Superklasse). Durch den Vererbungsmechanismus werden alle sichtbaren Eigenschaften der Oberklasse auf die Unterklasse übertragen. In Java ist auf direktem Weg nur die Einfachvererbung1 (engl. Single Inheritance) erlaubt. In der Einfachvererbung kann eine Klasse lediglich eine andere erweitern. In Programmiersprachen wie C++ können auch mehrere Klassen zu einer neuen verbunden werden. Dies bringt aber einige Probleme mit sich, die in Java vermieden werden. Wir wollen nun eine Klassenhierarchie für Chipstüten aufbauen. Die Hierarchie geht von oben nach unten von der Superklasse zur Subklasse. Da die Klasse Ob j e c t die Basisklasse aller anderen Klasse ist, wird sie in dem Baum mit aufgeführt. Der Graph zeigt die Klassenaufteilung. Die Klasse Pl a s t i k erbt automatisch alles von Ob j e c t , Tüte erbt alle Eigenschaften von Pl a s t i k und letztendlich übernimmt Ch i p s Tü t e alle Eigenschaften von Tü t e . c l a s s Pl a s t i k { St r i n g f a r b e ; f l oa t e l a s t i z i t ä t ; f l o a t g e wi c h t ( f l o a t r a w ) { . . . } } c l a s s Tü t e e x t e n d s Pl a s t i k { f l o a t v o l u me n ; . . . b o o l e a n t ü t e Ge f ü l l t ( ) { . . . } } c l a s s Ch i p s Tü t e e x t e n d s Tü t e { St r i n g h e r s t e l l e r ; }
1. In Java kann dies durch den Einsatz von Interfaces umgangen werden. In Smalltalk ist dies ein großer Streitpunkt, denn diese Sprache erlaubt nur Einfachvererbung – zur Verärgerung einiger Programmierer. • • 94 •• • •
Eine kleine Klasse Pl a s t i k deklariert zwei Attribute und eine Funktion, die einen Wert berechnet. Wird die Unterklasse Pl a s t i k nun zu Tüte erweitert, so kann das Objekt Ch i p s Tü t e problemlos auf Variablen wie f a r b e , e l a s t i z i t ä t , v o l u me n sowie Memberfunktionen zugreifen. Die Vererbung kann durch p r i v a t e eingeschränkt werden. Eine Subklasse erbt dann alles von einer Superklasse, was nicht p r i v a t e ist. Zusätzlich kommt zu p r i v a t e noch eine Sonderform p r o t e c t e d hinzu. Hier kann auch eine Unterklasse alle Eigenschaften sehen. Nur von außen sind die Eigenschaften privat. Eine Ausnahme bilden jedoch Klassen, die im gleichen Paket sind.
Aut om at ische Anpassung Das folgende Beispiel zeigt, dass auch eine Unterklasse einer Superklasse zugewiesen werden kann: Ch i p s Tü t e a l d i = n e w Ch i p s Tü t e ( ) ; a l di . f a r be = " r ot " ; a l d i . v o l u me n = 2 0 0 ; Pl a s t i k t ü t c h e n = a l d i ; Sy s t e m. o u t . p r i n t l n ( t ü t c h e n . f a r b e ) ;
/ / i s t r ot
Ein Exemplar a l d i vom Typ Ch i p s Tü t e wird erzeugt und dessen Farbe und Volumen gesetzt. Zusätzlich schaffen wir uns eine neue Variable t ü t c h e n vom Typ Pl a s t i k . Da Ch i p s Tü t e die Klasse Tü t e erweitert, ist Ch i p s Tü t e mächtiger. Jedoch kann t ü t c h e n gleich a l d i gesetzt und dessen Farbe ausgegeben werden. Auf den ersten Blick erscheint das nicht sonderlich sinnvoll, erfüllt aber einen Zweck: t ü t c h e n übernimmt alles vom mächtigerem a l d i , verzichtet aber auf alle anderen Informationen die eine Chipstüte noch bietet. In der Anwendung ist dies ein mächtiges Konzept. Es kann eine Basisklasse geschaffen werden und verschiedene Klassen erweitern diesese. Da alle abgeleiteten Objekte die Grundfunktionalität der Subklasse besitzen, liefert die Eigenschaftet der Basisklasse einen gemeinsame Nenner.
Explizit e Anpassung durch einen Cast Doch auch durch einen expliziten Cast können Objekte zugewiesen werden: Ch i p s Tü t e l i d l = n e w Ch i p s Tü t e ( ) ; a l di = l i dl ; a l di = t üt c he n; a l d i = ( Ch i p s Tü t e ) t ü t c h e n ;
/ / Co mp i l e r f e h l e r , i n k o mp a t i b l e r Ty p / / d a s i s t OK
In diesem Fall wird ein neues Objekt l i d l erzeugt und a l d i zugewiesen. Dies ist in Ordnung, da beide Objekte vom gleichen Typ sind. Die zweite Zeile ist schon nicht mehr korrekt, da beide Objekte unterschiedlich sind und auch Zuweisung nicht automatisch angepasst wird. Es kommt zu einem Compilerfehler. Es ist aber möglich, dass Objekt t ü t c h e n durch einen Typecast in eine Ch i p s Tü t e umzuwandeln. Dann kann diese auch a l d i zugewiesen werden. Dieser Fall ist genau der entgegengesetzte zum Beispiel davor. Nun wird aus dem t ü t c h e n eine Ch i p s Tü t e gemacht. Da t ü t c h e n aber weniger kann, fehlen mitunter einige Informationen, die aber nachgetragen werden können, wenn mit a l d i weitergearbeitet wird.
• • • 95 • • •
3.8.1 Met hoden überschreiben Vererbt eine Klasse ihre Methoden einer anderen, so kann diese die Methode neu implementieren. Somit bieten sich generell zwei Möglichkeiten an: Methoden einer Unterklasse können n überladen (die Methode trägt den gleichen Namen wie eine Methode aus einer Unterklasse, hat aber verschiedene Parameter) oder n überschrieben (die Methode besitzt nicht nur den gleichen Namen, sondern auch die gleichen Parameter) werden. Wird die Signatur eines Funktionsblockes beim Überschreiben nicht aufmerksam genug beachtet, wird unbeabsichtigt eine Methode überladen. Dieser Fehler ist schwer zu finden. c l a s s Si l i z i u m { b o o l e a n i s Te u e r ( ) { r e t u r n f a l s e ; } } c l a s s I C e x t e n d s Si l i z i u m { b o o l e a n i s Te u e r ( ) { r e t u r n t r u e ; } }
Wird mit I C OP = n e w I C; ein neues Objekt angelegt, so ruft ruft OP. i s Te u e r ( ) nun die Methode von I C auf, die den Wert t r u e zurückgibt. Zusammenfassend können wir sagen, dass eine Klasse ihr Erbe durch vier Techniken erweitern kann; durch: n Hinzufügen neuer Variablen n Hinzufügen neuer Methoden n Überladen gererbter Methoden n Überschreiben ererbter Methoden
3.9 Abst rakt e Klassen und I nt erfaces Nicht immer soll eine Klasse sofort ausprogrammiert werden. In Java gibt es dazu zwei Konzepte: Abstrakte Klassen und Interfaces.
3.9.1 Abst rakt e Klassen Eine abstrakte Klasse1 definiert lediglich den Prototypen, die Implementierung folgt an anderer Stelle. Oftmals besitzen abstrakte Klassen auch gar keine Implementierung, nämlich dann, wenn andere Klassen abstrakte Klassen erweitern und die Funktionen überschreiben. Obwohl eine abstrakte Klasse nichts enthalten muss, können jedoch Methoden oder Variablen enthalten sein. Diese aber zu einer kompletten Klasse zu erweitern ist nicht sinnvoll, denn abstrakte Klassen können nicht erzeugt werden. 1. Wahrend in Java eine Klasse abstract definiert wird, wird in EIFFEL eine Unterprogramm als deferred gekennzeichnet. Das heisst also, die Implementierung wird aufgeschoben. • • 96 •• • •
a b s t r a c t c l a s s Ma t e r i a l { a b s t r a c t i n t g e wi c h t ( ) ; }
Das Schlüsselwort a b s t r a c t leitet die Definition einer abstrakten Klasse ein. Eine Klasse kann ebenso abstrakt sein wie eine Methode. Eine abstrakte Methode muss mit einem Semikolon abgeschlossen werden. Ist einmal eine Methode abstrakt, so ist es auch automatisch die Klasse. Vergessen wir aber einmal das Schlüsselwort a b s t r a c t bei einer solchen Klassen, so bekommen wir einen Compilerfehler. Versuchen wir ein Exemplar einer abstrakten Klasse zu erzeugen, so bekommen wir ebenfalls einen Compilerfehler. Auch ein indirekter Weg über die Cl a s s Methode n e wI n s t a n c e ( ) bringt uns nicht zum Ziel, sondern nur eine I n s t a n t i a t i o n Ex c e p t i o n ein.
Vererben von abst rakt en Met hoden Wenn wir von einer Klassen abstrakte Methoden erben, so haben wir zwei Möglichkeiten. Wir implementieren diese Methode und dann kann die Klasse korrekt angelegt werden oder wir überschreiben sie nicht. Wenn wir es nicht machen, dann verbleibt aber eine abstrakte Methode und unsere Klasse muss wiederum abstrakt sein. a b s t r a c t c l a s s Ti e r { i nt a l t e r = - 1; v o i d a l t e r Se t z e n ( i n t a ) { a l t e r = a ; } a b s t r a c t b o o l e a n i s t Sä u g e r ( ) ; a bs t r a c t voi d a us ga be ( ) ; } a b s t r a c t c l a s s Sä u g e t i e r e x t e n d s Ti e r { b o o l e a n i s t Sä u g e r ( ) { r e t u r n t r u e ; } } c l a s s Me n s c h e x t e n d s Sä u g e t i e r { v o i d a u s g a b e ( ) { Sy s t e m. o u t . p r i n t l n ( " I c h b i n e i n Me n s c h " ) ; } c l a s s De l f i n e x t e n d s Sä u g e t i e r { v o i d a u s g a b e ( ) { Sy s t e m. o u t . p r i n t l n ( " I c h b i n e i n De l f i n " ) ; } a u s g a b e ( ) ist eine Methode, die für die jeweiligen Implementierungen eine kurze Meldung auf
dem Schirm gibt. Da alle erweiternden Klassen jeweils andere Zeichenketten ausgeben, setzen wir die Methode a b s t r a c t . Damit muss aber auch die Klasse Ti e r abstrakt sein. In der ersten Ableitung Sä u g e t i e r können wir nun beliebiges hinzufügen, aber wir implementieren a u s g e b e n ( ) wieder nicht. Also muss auch Sä u g e t i e r wieder a b s t r a c t sein. Die Dritte Klasse ist nun Me n s c h . Sie erweitert Sä u g e t i e r und liefert eine Implementierung für a u s g a b e ( ) . Damit muss sie nicht abstrakt sein. Es ist allerdings ohne weiteres möglich, einem Ti e r Objekt eine Referenz eines Me n s c h bzw. Sä u g e t i e r Objekts zuzuweisen. Also ist also folgendes richtig. • • • 97 • • •
Ti e r m = n e w Me n s c h ( ) , d = n e w De l f i n ( ) ;
Wird ein Me n s c h oder De l f i n Objekt erzeugt, so wird der Konstruktor dieser Klassen aufgerufen. Dieser bewirkt aber einen Aufruf des Konstruktors der Superklasse. Und obwohl diese a b s t r a c t ist, besitzt sie wie alle anderen Klassen einen Standard-Konstruktor. Des weiteren werden beim Aufruf von Me n s c h ( ) auch noch die Attribute initialisiert, so dass a l t e r auf - 1 gesetzt wird. Rufen wir nun a u s g a b e ( ) auf, so kommt der passende String auf den Schirm: m. a u s g a b e ( ) ; d. a us ga be ( ) ;
liefert I c h b i n e i n Me n s c h I c h b i n e i n De l f i n
3.9.2 Schnit t est ellen ( I nt erfaces) Eine Schnittstelle (engl. Interface) (äquivalent zu denen in Objective-C) enthält keine Implementierungen, sondern nur Prototypen der Methoden. Anders gesagt: Sie sind pure abstrakte Klassen. Obwohl keine Funktionen ausprogrammiert werden, sind s t a t i c f i n a l Variablen in einer Schnittstelle erlaubt. Möchte eine Klasse eine Schnittstelle verwenden, so folgt hinter dem Klassennnamen das Schlüsselwort i mp l e me n t s . Eine Klasse kann mehrere Schnittstellen implementieren. Das folgende Beispiel definiert die Schnittstelle Ma t e r i a l mit drei Konstanten. Daneben gibt es eine nicht ausprogrammierte Funktion b e r e c h n e Ge wi c h t ( ) . Diese wird nicht als a b s t r a c t gekennzeichnet, obwohl sie es eigentlich sein könnte. i n t e r f a c e Ma t e r i a l { s t a t i c f i n a l i n t RUND = 0 , ECKI G = 1 , OVAL = 2 ; i n t b e r e c h n e Ge wi c h t ( ) ; } c l a s s Me t a l l i mp l e me n t s Ma t e r i a l { i n t b e r e c h n e Ge wi c h t ( ) { . . . } } c l a s s Pl a s t i k i mp l e me n t s Ma t e r i a l { i n t b e r e c h n e Ge wi c h t ( ) { . . . } } c l a s s Ho l z i mp l e me n t s Ma t e r i a l { i n t b e r e c h n e Ge wi c h t ( ) { . . . } }
• • 98 •• • •
Verschiedene Klassen implementieren Ma t e r i a l . Alle Klassen definieren dabei die Funktion b e r e c h n e Ge wi c h t ( ) anders. Dies ist einleuchtend, denn alle Materialien haben ein unterschiedliches Gewicht.
Spr e ch w e ise Klassen er ben v on Klassen. Klassen im plem ent ier en Schnit t st ellen.
Ein Polym orphie Beispiel m it Schnit t st ellen An diese Stelle sei noch einmal an die Möglichkeit erinnert, Funktionen auf Objekte auszuführen, die eine gemeinsame Basis haben. So könnte zwar Me t a l l , Pl a s t i k oder Ho l z einen ganz eigenen Satz von Funktionen bieten, aber alle verstehen die Nachricht b e r e c h n e Ge wi c h t ( ) . Das anschließende Beispiel zeigt eine Anwendung dieser kleinsten gemeinsamen Methodenbasis: Die Callbacks. Sie sind besonders dazu geeignet, Referenzen zu übergeben. i n t e r f a c e At o m { i n t wi e v i e l e Ne u t r o n e n ( ) ; } c l a s s Wa s s e r s t o f f i mp l e me n t s At o m { i n t wi e v i e l e Ne u t r o n e n ( ) { r e t u r n 0 ; } } c l a s s He l i u m i mp l e me n t s At o m { i n t wi e v i e l e Ne u t r o n e n ( ) { r e t u r n 2 ; } } c l a s s Sa u e r s t o f f i mp l e me n t s At o m { i n t wi e v i e l e Ne u t r o n e n ( ) { r e t u r n 8 ; } } c l a s s Ge mi s c h { At o m s t o f f ; Ge mi s c h ( At o m a ) { s t of f = a ; } publ i c i nt ne ut r one nI nf o( ) { r e t u r n s t o f f . wi e v i e l e Ne u t r o n e n ( ) ; } }
Im Beispiel implementieren verschiedene Stoffe das Interface At o m. Jeder Stoff kann aber über die Methode n e u t r o n e n I n f o über seine Anzahl von Neutronen informieren. Das Bindeglied der Kette ist das Objekt Ge mi s c h . Diesem wird beim Konstruktor ein allgemeines Atom übergeben. Ge mi s c h bezieht sich nur auf diese Funktionen, was die Elemente sonst noch für Methoden imple• • • 99 • • •
mentieren, kann Ge mi s c h nicht wissen. Ge mi s c h sichert sich die Referenz in einer lokalen Variablen a . Wird mit u . n e u t r o n e n I n f o auf die Funktion von Ge mi s c h Bezug genommen, so wird die Methode des Objekts aufgerufen, dessen Referenz in a gesichert war. Ge mi s c h c ha r
u; a t o mKü r z e l ;
/ / s e t z e a t o mKü r z e l mi t i r g e n d e i n e m We r t . s wi t c h ( a t o mKü r z e l ) { c a s e ' h ' : u = n e w Ge mi s c h ( n e w He l i u m( ) ) ; br e a k; c a s e ' s ' : u = n e w Ge mi s c h ( n e w Sa u e r s t o f f ( ) ) ; br e a k; d e f a u l t : u = n e w Ge mi s c h ( n e w Wa s s e r s t o f f ( ) ) ; } Sy s t e m. o u t . p r i n t l n ( u . n e u t r o n e n I n f o ( ) ) ;
3.9.3 Erweit ern von I nt erfaces – Subint erfaces Ein Subinterface ist eine Erweiterung eines anderen Interfaces. Ein Beispiel: i n t e r f a c e Sc h ö n e s At o m e x t e n d s At o m { i nt ä s t he t i k( ) ; }
Eine Klasse, die nun Sc h ö n e s At o m implemtiert, muss die Methoden von beiden Klassen implementieren, demzufolge die Methode ä s t h e t i k ( ) und die Methode, die in At o m abstrakt abgegeben wurde – es bleibt a n z a h l Ne u t r o n e n ( ) .
3.9.4 St at ische I nit ialisierung einer Schnit t st elle Wir wollen nun eine Möglichkeit kennenlernen, wie statische Initialisierung auch in Schnittstellen möglich wird. Denn standardmäßig ist dies nicht von der Sprache unterstützt, die virtuelle Maschine aber kennt eine solche Möglichkeit. Aber dies jetzt in Java Bytecode abzubilden wird dem Aufwand nicht gerecht. Somit muss für nachfolgendes schnell aufgeschriebenes eine neue Variante gesucht werden. publ i c { publ i publ i publ i
i n t e r f a c e Nu mb e r s c s t a t i c f i n a l i n t ONE = 1 ; c s t a t i c f i n a l i n t TWO = 2 ; c s t a t i c f i n a l i n t THREE = 3 ;
p u b l i c s t a t i c f i n a l Ha s h t a b l e n a me s = n e w Ha s h t a b l e ( ) ; • • 100 •• • •
s t at i c { ^ / / Hi e r g i b t ' s d e n Co mp i l e r f e h l e r / / " I nt e r f a c e s c a n' t ha ve s t a t i c i ni t i a l i z e r s . " n a me s . p u t ( n e w I n t e g e r ( ONE) , " o n e " ) ; n a me s . p u t ( n e w I n t e g e r ( TWO) , " t wo " ) ; n a me s . p u t ( n e w I n t e g e r ( THREE) , " t h r e e " ) ; } }
Es ist nun angenehm, wenn die Hashtabelle vorher initialisiert werden könnte. Denn hier handelt es sich ja im weitesten Sinne auch um Konstanten. Der Trick, wie dieses Problem gelöst werden kann, liegt in der Einführung einer inneren Klasse, die wir Cl I n i t nennen wollen. Innerhalb dieser Klasse setzen wir nun den Initialisierungsblock. Anschließend muss nur noch eine Dummy Variable gesetzt werden, damit der Initialisierungsblock in der Klasse auch ausgeführt wird. Dazu definieren wir eine Variable c l i n i t . Sie wird s t a t i c f i n a l , also somit eine echte Konstante. s t a t i c f i n a l Cl I n i t c l i n i t = n e w Cl I n i t ( ) ; s t a t i c f i n a l Cl I n i t { Cl I n i t ( ) { } s t at i c { .... } }
Innerhalb von s t a t i c { } lässt sich auf die Hashtable der äußeren Klasse zugreifen und somit auch die Werte setzen. Ohne die Erzeugung des clinit Objekts geht es nicht, denn andernfalls würde sonst die Klasse Clinit nicht initialisiert werden. Somit fügt sich für die Hashtable folgendes zusammen: i mp o r t j a v a . u t i l . Ha s h t a b l e ; publ i c { publ i publ i publ i
i n t e r f a c e Nu mb e r s c s t a t i c f i n a l i n t ONE = 1 ; c s t a t i c f i n a l i n t TWO = 2 ; c s t a t i c f i n a l i n t THREE = 3 ;
p u b l i c s t a t i c f i n a l Ha s h t a b l e n a me s = n e w Ha s h t a b l e ( ) ; s t a t i c f i n a l Cl I n i t c l i n i t = n e w Cl I n i t ( ) ; s t a t i c f i n a l Cl I n i t { Cl I n i t ( ) { } s t at i c { n a me s . p u t ( n e w I n t e g e r ( ONE) , " o n e " ) ; n a me s . p u t ( n e w I n t e g e r ( TWO) , " t wo " ) ; n a me s . p u t ( n e w I n t e g e r ( THREE) , " t h r e e " ) ; } • • • 101 • • •
} }
Und das Programm kann nun alle Elemente wie folgt nutzen: c l a s s Us e Nu mb e r s i mp l e me n t s Nu mb e r s { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Sy s t e m. o u t . p r i n t l n ( " THREE i s " + n a me s . g e t ( n e w I n t e g e r ( THREE) ) ) ; } }
3.10 I nnere Klassen Eine der größten Veränderungen, die der Sprachstandard seit 1.1 erfahren hat, war die Einführung von inneren Klassen (engl. inner classes). Mit dieser Sprachergänzung wurde es möglich, neue Klassen innerhalb von bestehenden Klassen zu deklarieren. Optisch sieht das so aus, wie eine Variablen- oder Methoden- Definition. Als zusätzliche Erweiterung kamen lokale Klassen und als Sonderfall anonyme Klassen hinzu. Sie werden innerhalb eines normalen Programmablaufes formuliert. Im folgenden wollen wir alle 4 neuen Typen an einem Beispiel beschreiben.
Geschacht elt e Klassen und Schnit t st ellen Eine geschachtelte Klasse oder Schnittstelle ist statisch in einer anderen Klasse oder in einem anderen Interface definiert. Per Definition ist eine innere Klasse immer statisch, das heißt, unbewusst steht immer der Modifizierer s t a t i c davor. Dies ist genau so wie die Definition einer statischen Methode oder einer statischen Variable. Die innere Klasse oder das Interface verhält sich bis auf einen kleinen Unterschied bei der Namensvergabe wie herkömmliche Klassen. Wird zum Beispiel in der Klasse v e r k e t t e Li s t e das Interface Element definiert, so könnten wir auf dieses Interface mittels v e r k e t t e t e Li s t e . El e me n t zugreifen.
Mit gliedsklasse Eine Mitgliedsklasse ist wie eine innere Klasse definiert, jedoch nicht mit dem Modifizierer s t a t i c . So verhält sich eine Mitgliedsklasse in vielen Fällen wie Exemplarvariablen und Methoden. Mitgliedsklassen können auf alle Variablen und Methoden der oberen Klasse zugreifen inklusive der privaten Variablen und Methoden. Dies führte zu heftigen Debatten bei der Sprachdefinition.
3.10.1 I m plem ent ierung einer verket t et en List e Verkette Listen gibt es in Java seit der Java2 Plattform, so dass wir eigentlich nicht auf die Implementierung schauen müssten. Doch da dies für viele Leser noch ein Geheimnis ist, wie die Pointer abgebildet werden, schauen wir uns eine einfache Implementierung an. Zunächst benötigen wir eine Zelle, die Daten und eine Referenz auf das folgende Objekt speichert. Die Zelle wird durch die Klasse Cell modelliert. c l a s s Li n k e d Li s t { p r i v a t e c l a s s Ce l l • • 102 •• • •
{ Ob j e c t d a t a ; Ce l l n e x t ; p u b l i c Ce l l ( Ob j e c t o ) { da t a = o; } } p r i v a t e Ce l l h e a d , t a i l ; p u b l i c v o i d a d d ( Ob j e c t o ) { Ce l l n = n e w Ce l l ( o ) ; i f ( t a i l == n u l l ) he a d = t a i l = n; el s e { t a i l . ne xt = n; t a i l = n; } } p u b l i c St r i n g t o St r i n g ( ) { St r i n g s = " " ; Ce l l c e l l = h e a d ; wh i l e ( c e l l ! = n u l l ) { s = s + c e l l . da t a + " " ; c e l l = c e l l . ne xt ; } r e t ur n s ; } }
Eine Liste besteht nun aus einer Menge von Ce l l Elementen. Da diese Objekte fest mit der Liste verbunden sind, ist hier der Einsatz von geschachtelten Klassen sinnvoll. Die Liste selbst benötigt aber nur einen Verweis auf den Kopf (erstes Element) und auf das Ende (letztes Element zum Einfügen). Um nun ein Element in dieser Liste hinzuzufügen, erzeugen wir zunächst eine neue Zelle n . Ist t a i l und h e a d gleich n u l l heißt dies, dass es noch keine Elemente in der Liste gibt. Demnach legen wir beide Referenzen auf das neue Objekt. Werden nun später Elemente eingefügt, hängen sie sich hinter t a i l . Wenn es nun schon Elemente in der Liste gab, dann ist t a i l nicht gleich n u l l und es zeigt auf das letzte Element. Seine n e x t Referenz zeigt auf n u l l und wird dann mit einem neuen Wert belegt, nämlich mit dem des neu beschafften Objekts n . Nun hängt es in der Liste drin und das Ende muss noch angezeigt werden. Daher legen wir die Referent t a i l auch noch auf das neue Objekt.
• • • 103 • • •
Quellcode 3.j
LinkedListDemo.java
p u b l i c c l a s s Li n k e d Li s t De mo { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Li n k e d Li s t l = n e w Li n k e d Li s t ( ) ; l . a d d ( " Ha l l o " ) ; l . a d d ( " Ot t o " ) ; Sy s t e m. o u t . p r i n t l n ( l ) ; } }
3.10.1 Funkt ionszeiger Das folgende Beispiel implementiert Funktionszeiger über Interfaces. Das Interface Fu n c t i o n definiert eine Funktion c a l c , die von zwei Prozeduren ausprogrammiert wird. Wir benutzen als Testprogramme zwei innere Klassen, die im Interface eingebettet sind. Quellcode 3.j
Function.java
p u b l i c i n t e r f a c e Fu n c t i o n { publ i c voi d c a l c ( i nt num ) ; c l a s s Fu n c t i o n On e i mp l e me n t s Fu n c t i o n { publ i c voi d c a l c ( i nt num ) { Sy s t e m. o u t . p r i n t l n ( " Fu n k t i o n e i n s g i b t mi r " + ( n u m* 2 ) ) ; } } c l a s s Fu n c t i o n Two i mp l e me n t s Fu n c t i o n { publ i c voi d c a l c ( i nt num ) { Sy s t e m. o u t . p r i n t l n ( " Fu n k t i o n z we i g i b t mi r " + ( n u m* 4 ) ) ; } } }
Die beiden Funktionen Fu n c t i o n On e und Fu n c t i o n Two implementieren Fu n c t i o n jeweils so, dass c a l c die als Parameter übergeben Zahl mit zwei bzw. vier multipliziert ausgibt. Eine Klasse Fu n c t i o n Te s t sortiert beide Funktionen in ein Feld f u n c ein und ruft die beiden Funktionen anschließend auf. Quellcode 3.j
FunctionTest.java
p u b l i c c l a s s Fu n c t i o n Te s t { • • 104 •• • •
f i n a l i n t MAX = 2 ; f i n a l Fu n c t i o n [ ] f u n c = n e w Fu n c t i o n [ MAX] ; / / Co n s t r u c t o r Fu n c t i o n Te s t ( ) { f u n c [ 0 ] = n e w Fu n c t i o n . Fu n c t i o n On e ( ) ; f u n c [ 1 ] = n e w Fu n c t i o n . Fu n c t i o n Two ( ) ; } voi d c a l c ( i nt i ) { ( ( Fu n c t i o n ) f u n c [ 0 ] ) . c a l c ( i ) ; ( ( Fu n c t i o n ) f u n c [ 1 ] ) . c a l c ( i ) ; } / / Ma i n p r o g r a m p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Fu n c t i o n Te s t f t = n e w Fu n c t i o n Te s t ( ) ; f t . c a l c ( 42 ) ; } }
Für Callbacks sind innere Klassen besonders gut geeignet, denn für die abstrakten Klassen muss nicht jedesmal eine neue Klassendatei angelegt werden. So ist für das oben abgegebene Beispiel durchaus denkbar, in die Klasse Fu n c t i o n Te s t die abstrakte Klasse f u n c t i o n sowie die Implementierungen f u n c t i o n On e und f u n c t i o n Two einzubetten, obwohl zwei Klassen Fu n c t i o n Te s t und Fu n c t i o n schon weniger Quellcodezeilen ergeben.
3.11 Paket e Ein Paket ist eine Gruppe von verbundenen Klassen, die sich normalerweise1 in einem Verzeichnis befinden. Diesen Verzeichnisnamen gibt ein Paketname an. pa c ka ge s üßi gke i t e n; c l a s s Zu c k e r { ... } p u b l i c c l a s s Sc h o k o l a d e e x t e n d s Zu c k e r { ... } 1. Ich schreibe normalerweise, da die Paketstruktur nicht zwingend auf Verzeichnisse abgebildet werden muss. Pakete könnten beispielsweise vom Klassenlader aus einer Datenbank aus Relation mit den Namen ausgelesen werden. • • • 105 • • •
Alle Klassen, die in dieser Datei implementiert werden, gehören zum Paket s ü ß i g k e i t e n . Die Zugehörigkeit wird durch das Schlüsselwort p a c k a g e ausgedrückt. Um die Pakete zu nutzen, wird innerhalb einer Klasse mit i mp o r t auf die Klassen im Paket aufmerksam gemacht. Importiert ein Paket ein anderes, so können die Klassen schnell referenziert werden. pa ka ge l e c ke r e i e n; i mp o r t s ü ß i g k e i t e n . Sc h o k o l a d e ; c l a s s We i h n a c h t s ma n n { Sc h o k o l a d e s ; / / s o n s t s ü ß i g k e i t e n . Sc h o k o l a d e }
Damit nicht alle Klassen eines Paketes einzeln aufgeführt werden müssen, lässt sich mit dem Sternchen als eine Art Wildcard auf alle p u b l i c Klassen zugreifen. Häufig Gebrauch machen davon Programmen mit grafischer Oberfläche gemacht, in den ersten Zeilen findet sich dann: i mp o r t j a v a . a wt . *
In den ersten Java-Versionen ließ sich auch etwa i mp o r t
j a v a . a wt ;
anstatt i mp o r t
j a v a . a wt . * ; schreiben.
Natürlich müssen wir diesen i mp o r t nicht schreiben. Er dient lediglich als Abkürzung für die Klassenbezeichnung. Auch sagt ein i mp o r t nichts darüber aus, ob die Klassen dort jemals gebraucht werden. Pakete sind oft in Hierarchien geordnet. Dies wird auch durch die Darstellung in der Verzeichnisstruktur deutlich. Daher gehören zu einem Paket oft verschiedene Unterpakete. Es werden durch i mp o r t j a v a . * nicht automatisch alle Klassen eingebunden. Die i mp o r t -Anweisung bezieht sich nur auf ein Verzeichnis und nicht auf alle Unterverzeichnisse mit.
3.12 Arrays Ein Array (auch Feld oder Vektor) ist ein spezieller Datentyp, der die Werte unter einem Namen mit einem Index anspricht. Er ist vergleichbar mit einem Setzkasten, wo ein Platz (etwa für Schlümpfe) immer Variablen eines gleichen Typs (nur Schlümpfe und keine Pokemons) aufnimmt. Normalerweise liegen die Komponenten im Speicher hintereinander, doch dies ist ein Implementierungsdetail, welches in Java unwichtig ist. Jedes Array beinhaltet Objekte eines bestimmten Datentyps. Dies können sein: n Elementare Datentypen wie int, byte, long, usw. n Referenzen auf andere Objekte. n Referenzen auf andere Arrays.
• • 106 •• • •
3.12.1 Deklarat ion und I nit ialisierung Eine Array-Variablendeklaration ist mit einer gewöhnlichen Deklaration zu vergleichen, nur dass nach dem Datentyp – oder auch der Variablen – die Zeichen ›[ ‹ und ›] ‹ gesetzt werden müssen. Uns ist es freigestellt welche Schreibweise wir wählen. Hauptsache es kommen überhaupt Klammern dahin – doch wie bei der gesamten Programmierung sollte konsistent vorgegangen werden, eimal so, einmal so, behindert die schnelle Wahrnehmung von Programmquelltext. i nt [ ] s c ha c h; i n t a u c h Sc h a c h [ ] ; Bu t t o n [ ] r e c h n e r ;
So ganz ohne Unterschied ist die Deklaration nicht. Das zeigt sich spätestens, wenn mehr als eine Variable deklariert wird. Die Klammern können zum einen Teil des Typs sein und einmal Teil der Variablen. Ist er Teil des Typs, so sind alle deklarierten Variablen ein Feld. Es entspricht demnach i nt [ ]
p r i ms , ma t r i x [ ] , 3 d ma t r i x [ ] [ ] ;
der Deklaration i n t p r i ms [ ] , ma t r i x [ ] [ ] , 3 d ma t r i x [ ] [ ] [ ] ;
Hier ist doppelt Vorsicht verboten, denn der ein oder andere wollte vielleicht nur schreiben: i n t [ ] p r i ms , i ;
und wollte ausdrücken, dass i eine normale Ganzzahlvariable ist. Hier würde der Compiler jetzt annehmen, dass i ein Feld ist und eine Zuweisung der Art i =2 gnadenlos ablehnen. Und jetzt ist nicht direkt ersichtlich, wo der Fehler ist. Ich empfehle daher, die Klammern hinter den Bezeichner zu setzen. Aus diesem Grunde ist es auch ungünstig unter C(++) das Sternchen für den Zeiger direkt an den Datentyp zu packen.1
Arrays anlegen Ein Array mit einer bestimmten Größe muss mit dem n e w-Operator erzeugt werden. Das Anlegen der Variablen alleine erzeugt noch kein Feld mit eine bestimmten Länge. In Java ist das Anlegen des Feldes genauso dynamisch wie die Objekterzeugung. Dies drückt auch der n e w-Operator aus. Die Länge des Feldes wird in eckigen Klammern angegeben. Hier kann ein beliebiger Integer-Wert stehen, auch eine Variable. Wenn eine Variable a r r a y Of I n t s definiert wurde, dann erzeugt folgende Zeile das Array-Objekt für 100 Elemente. a r r a y Of I n t s = n e w i n t [ 1 0 0 ] ;
Die Deklaration ist auch zusammen mit der Zuweisung möglich, zum Beispiel für Gleitkommazahlen. d o u b l e a r r a y Of Do u b l e s [ ] = n e w d o u b l e [ 1 0 0 ] ;
1. Dies kommt in C++ aber wieder in Mode und macht auch Sinn. Doch dies ist eine andere Geschichte, die an anderer Stelle erzählt werden muss. • • • 107 • • •
Der Datentyp muss keiner elementarer sein. Auch ein Array von Objekten kann deklariert werden. Dieses Array besteht dann aus Referenzen auf die Objekte. Die Größe errechnet sich demnach aus der Größe des Feldes mal dem Speicherbedarf einer Referenz. Nur das Array selbst wird angelegt, nicht aber die Objekte, die das Array aufnehmen soll. Dies lässt sich einfach damit begründen, dass der Compiler auch gar nicht wüsste, welchen Konstruktor er aufrufen sollte. Bu t t o n t a s t e [ ] = n e w Bu t t o n [ 9 ] ;
Hier wird Platz für 9 Tasten gemacht, aber kein Button-Objekt wird angelegt. Das Feld besteht aus neun Referenzen. Später würde das Feld etwa mit t a s t e [ 0 ] = n e w Bu t t o n ( " 1 " ) gefüllt. Standardmäßig werden die Array-Elemente mit n u l l oder einem mit 0 vergleichbarern Typ initialisiert. Jedoch können wir, wie im folgenden Beispiel gezeigt, die Einträge direkt mit einem Wert belegen. i n t p r i mi Mä u s c h e n [ ] = { 1 , 2 , 3 , 5 , 7 , 7 +4 , } ; St r i n g s u b s t a n t i v e [ ] = { " Ha u s " , " Ma u s " , t r a n s l a t o r . t o Ge r ma n ( " d o g " ) ; }
In diesem Fall ist die Anzahl der Elemente im Feld gleich der Anzahl Tupel, die in der Aufzählung genannt sind. Es ist nicht möglich (wie in C(++)) das Feld mit einer bestimmten Größe zu initialisieren und dann einige wenige Werte vorher zuzuweisen. Innerhalb der Aufzählung kann als letztes ein Komma stehen. Dies wird, wie bei p r i mi Mä u s c h e n demonstriert, ignoriert.
St rings und Felder Ein Feld von Char-Zeichen ist nicht mit einem Strings vergleichbar. Die Klasse St r i n g bietet jedoch einen Konstruktor an, so dass aus einem Feld mit Zeichen ein String-Objekt erzeugt werden kann. Alle Zeichen des Feldes werden kopiert, so dass anschließend Feld und String keine Verbindung mehr besitzen. Das heißt auch, falls sich das Feld ändert, ändert sich der String nicht automatisch mit. Dies liegt auch daran, dass wir String-Objekte nicht ändern können. Mit der Methode t o Ch a r Ar r a y ( ) können wir einen String in ein Char-Feld konvertieren.
3.12.2 Zugriff auf die Elem ent e Die Anzahl der Elemente, die ein Array aufnehmen kann, wird auch als Dimension bzw. Länge bezeichnet. In Java beginnt ein Array, ähnlich wie in C(++), bei 0 (und nicht bei 1 wie in Pascal). Die Größe lässt sich später nicht mehr ändern. Beginnt sie bei 0, so ist der letzten Index die um 1 verkleinerte Länge des Arrays. Der Zugriff auf die Elemente eines Feldes erfolgt mit Hilfe der eckigen Klammern [ ] . Bei einem Array A der Länge n ist demnach A[ 0 ] bis A[ n - 1 ] erlaubt. Ein Beispiel: doubl e x[ ] = ne w doubl e [ 10] ; f o r ( i n t i = 0 ; i < 1 0 ; i ++ ) x[ i ] = 2*i ; • • 108 •• • •
Innerhalb der Klammern steht ein Ganzzahl-Ausdruck, der sich zur Laufzeit berechnen lassen muss. Long-Werte sowie Gleitkommazahlen sind nicht möglich. Bei Long-Werten wäre der Wertebereich zu groß, denn ein i n t umfasst ja schon 4 Bytes. Bei Gleitkommazahlen bleibt die Frage nach der Zugriffstechnik. Hier müssten wir den Wert auf ein Intervall runterrechnen. Liegt etwa eine Fließkommazahl f im Intervall von 0 bis 1 und haben die Werte eine Genauigkeit von einem Tausendstel, so ließe sich für ein Feld a für eine Ausgabe schreiben: f = 0. 01; out ( a [ ( i nt ) ( f *1000 ) ] )
/ / i m I nt e r va l l von 0 bi s 1
Fehler bei Feldern Beim Zugriff auf das Feld können drei Fehler auftreten. Zunächst einmal kann das Array-Objekt fehlen, so dass die Referenzierung fehlschlägt. Etwa bei folgendem Fall, bei dem der Compiler auch nicht meckert. i nt f e l d[ ] ; f e l d[ 1] = 1;
Die Strafe ist eine Nu l l Po i n t e r Ex c e p t i o n . Der zweite und dritte Fehler liegt im Index begründet. Dieser könnte negativ oder über der maximalen Länge liegen. Jeder Zugriff auf das Feld wird zur Laufzeit getestet. Auch bei Operationen, die für den Compiler entscheidbar wären, wird dieser Weg eingeschlagen. Etwa für nachfolgende Zeilen: i nt f e l d[ ] = ne w i nt [ 100] ; f e l d[ 100] = 100;
Hier könnte der Compiler theoretisch Alarm schlagen, macht er aber nicht. Dies hängt damit zusammen, dass Feldoperationen über die Array-Klasse abgewickelt werden, und die Klammern nur eine andere Form eines Methodenaufrufes sind. So ist der Zugriff auf Elemente mit einem ungültigen Index syntaktisch völlig in Ordnung. Ist der Index negativ1 oder zu hoch, dann hagelt es eine I n d e x Ou t Of Bo u n d Exception. Dieser kann, muss aber nicht, abgefangen werden. Falls nicht, dann geht der Fehler zum Laufzeitsystem hoch und das Programm bricht ab.
3.12.3 Arrays und Obj ekt e Arrays sind spezielle Objekte, die geordnete Elemente enthalten. Obwohl ein Array Ähnlichkeit zu Objekten hat, gibt es doch einige wichtige Unterschiede: n Eine Array-Klasse wird automatisch generiert, wenn ein Array-Typ deklariert wird. n Mit den Operatoren [ ] kann auf Array-Elemente über Index zugegriffen werden. n Eine spezielle Form des n e w-Operators erzeugt ein Exemplar des Arrays.
1. Ganz anders verhält sich da Perl. Dort wird ein negativer Index dazu verwendet, ein Feldelement relativ zum letzten Array-Eintrag anzusprechen. Und auch bei C ist ein negativer Index duchaus möglich und praktisch. • • • 109 • • •
Das Verhalten eines Arrays kann durch ein eigenes Objekt simuliert werden. Dies ist aber komplizierter als gleich ein Array zu nehmen, denn da Operatoren nicht überladen werden können ist ein anderer Zugriff auf die Elemente nötig, über Memberfunktionen der Klasse. Für die Java Virtuelle Maschine gibt es eine Klasse Ar r a y , die aber nicht für Programmierer zugänglich ist.
Die Länge des Arrays Die Anzahl der Elemente, folglich die Länge des Arrays, ist in der frei zugänglichen Variablen l e n g t h gesichert. l e n g t h ist eine p u b l i c f i n a l i n t Variable, die entweder positiv oder Null ist. c ha r a l pha be t [ ] = ne w c ha r [ 26] ; i n t a u c h Wi r k l i c h Se c h s u n d z wa n z i g = a l p h a b e t . l e n g t h ; p u b l i c ma i n ( St r i n g a r g s [ ] ) { i n t n o Ar g s = a r g s . l e n g t h ; }
3.12.4 Mehrdim ensionale Arrays Java definiert mehrdimensionale Arrays durch Arrays von Arrays. Sie können etwa für die Darstellung von mathematischen Matrizen oder Bildpunkten Verwendung finden. Ein 2dim Feld mit dem Platz für 4 Reihen von 8 Elementen definiert sich einfach über folgende Zeile. b y t e A[ ] [ ] = n e w b y t e [ 4 ] [ 8 ] ;
Zwei alternative (aber nicht unbedingt glücklichere) Deklaration sind byt e [ ] [ ] A = ne w byt e [ 4] [ 8] ; byt e [ ] A [ ] = ne w byt e [ 4] [ 8] ;
Einzelne Elemente werden mit A[ i ] [ j ] angesprochen.1 Der Zugriff erfolgt mit so vielen Klammerpaaren, wie die Dimension des Arrays angibt. Obwohl mehrdimensionale Felder im Prinzip Felder in Feldern sind, lassen sie sich leicht deklarieren.
Nicht - recht eckige Felder Da in Java multidimensionale Arrays als Arrays von Arrays implementiert sind, müssen diese nicht zwingend rechteckig sein. Jede Zeile im Feld kann eine eigene Größe haben. Im nachfolgenden Beispiel haben die Zeilen der Matrix Mdie Länge 1, 2 und 3. So entsteht ein dreieckiges Array. i n t M[ ] [ ] =n e w i n t [ 3 ] [ ] ; f o r ( i n t i =0 ; i = 0 ; i n d e x - - ) { • • 356 •• • •
Ha s h t a b l e En t r y e n t r y = t a b l e [ i n d e x ] ; wh i l e ( e n t r y ! = n u l l ) { s . wr i t e Ob j e c t ( e n t r y . k e y ) ; s . wr i t e Ob j e c t ( e n t r y . v a l u e ) ; e nt r y = e nt r y. ne xt ; } } } p r i v a t e s y n c h r o n i z e d v o i d r e a d Ob j e c t ( j a v a . i o . Ob j e c t I n p u t St r e a m s ) t h r o ws I OEx c e p t i o n , Cl a s s No t Fo u n d Ex c e p t i o n { / / Li e ß l e n g t h , t h r e s h o l d a n d l o a d f a c t o r s . d e f a u l t Re a d Ob j e c t ( ) ; / / Li e ß Lä n g e d e s Ar r a y s u n d An z a h l d e r El e me n t e i nt or i gl e ngt h = s . r e a dI nt ( ) ; i n t e l e me n t s = s . r e a d I n t ( ) ; / / Be r e c h n e n e u e Gr ö ß e , d i e e t wa 5 % ü b e r d e r a l t e n Gr ö ß e l i e g t i n t l e n g t h = ( i n t ) ( e l e me n t s * l o a d Fa c t o r ) + ( e l e me n t s / 2 0 ) + 3 ; i f ( l e n g t h > e l e me n t s && ( l e n g t h & 1 ) == 0 ) l e ngt h- - ; i f ( o r i g l e n g t h > 0 && l e n g t h > o r i g l e n g t h ) l e ngt h = or i gl e ngt h; t a b l e = n e w Ha s h t a b l e En t r y [ l e n g t h ] ; c ount = 0; / / Li e ß a l l e El e me n t e mi t k e y / v a l u e f o r ( ; e l e me n t s > 0 ; e l e me n t s - - ) { Ob j e c t k e y = s . r e a d Ob j e c t ( ) ; Ob j e c t v a l u e = s . r e a d Ob j e c t ( ) ; put ( ke y, va l ue ) ; } } }
In diesem Beispiel wurden also wr i t e Ob j e k t ( ) und r e a d Ob j e c t ( ) so implementiert, dass alle zusätzlichen Informationen geschrieben worden sind.
Der But t on in der AWT- Klasse Die meisten Java-Klassen benötigen diese zusätzlichen Funktionen nicht und es reicht aus, die Attribute zu schreiben, um das Objekt später wieder zu rekonstruieren. Im j a v a . a wt . * Paket aber müssen die meisten Objekte neue Schreib-/Lesefunktionen implementieren. Eine Schwierigkeit kommt daher, da sich Peer-Klassen nicht serialisieren lassen. Schauen wir nun in ein paar Klassen hinein – zunächst in einen Button.
• • • 357 • • •
p u b l i c c l a s s Bu t t o n e x t e n d s Co mp o n e n t { St r i n g l a b e l ; St r i n g a c t i o n Co mma n d ; t r a n s i e n t Ac t i o n Li s t e n e r a c t i o n Li s t e n e r ; p r i v a t e s t a t i c f i n a l St r i n g b a s e = " b u t t o n " ; p r i v a t e s t a t i c i n t n a me Co u n t e r = 0 ; ... p r i v a t e v o i d wr i t e Ob j e c t ( Ob j e c t Ou t p u t St r e a m s ) t h r o ws I OEx c e p t i o n { s . d e f a u l t Wr i t e Ob j e c t ( ) ; AWTEv e n t Mu l t i c a s t e r . s a v e ( s , a c t i o n Li s t e n e r K, a c t i o n Li s t e n e r ) ; s . wr i t e Ob j e c t ( n u l l ) ; } p r i v a t e v o i d r e a d Ob j e c t ( Ob j e c t I n p u t St r e a m s ) t h r o ws Cl a s s No t Fo u n d Ex c e p t i o n , I OEx c e p t i o n { ... } }
Eine besondere Schwierigkeit stellen die Events dar. Sie müssen gesichert werden und dazu die Bezüge zu den Aktionen. c l a s s j a v a . a wt . AWTEv e n t Mu l t i c a s t e r i mp l e me n t s Co mp o n e n t Li s t e n e r , Co n t a i n e r Li s t e n e r , Fo c u s Li s t e n e r , Ke y Li s t e n e r , Mo u s e Li s t e n e r , Mo u s e Mo t i o n Li s t e n e r , Wi n d o wLi s t e n e r , Ac t i o n Li s t e n e r , I t e mLi s t e n e r , Ad j u s t me n t Li s t e n e r , Te x t Li s t e n e r , I n p u t Me t h o d Li s t e n e r Ÿ p r o t e c t e d s t a t i c v o i d s a v e ( Ob j e c t Ou t p u t St r e a m s , St r i n g k , Ev e n t Li s t e n e r l ) t h r o ws I OEx c e p t i o n Speichert zu einem EventListener (Ev e n t Li s t e n e r ist ein Interface) die Verbindungen. Sie können dann später mit a d d Ac t i o n Li s t e n e r ( ) wieder rekonstruiert werden.
Leider ist SUN mit der Dokumentation dieser Funktion etwas sparsam gewesen. Wir kommen somit nicht drum herum in die Implementation hineinzuschauen: p r o t e c t e d s t a t i c v o i d s a v e ( Ob j e c t Ou t p u t St r e a m s , St r i n g k , Ev e n t Li s t e n e r l ) t h r o ws I OEx c e p t i o n { i f ( l == n u l l ) { r e t ur n; } e l s e i f ( l i n s t a n c e o f AWTEv e n t Mu l t i c a s t e r ) { ( ( AWTEv e n t Mu l t i c a s t e r ) l ) . s a v e I n t e r n a l ( s , k ) ; • • 358 •• • •
} e l s e i f ( l i n s t a n c e o f Se r i a l i z a b l e ) { s . wr i t e Ob j e c t ( k ) ; s . wr i t e Ob j e c t ( l ) ; } }
Wir sehen: Ist kein Ev e n t Li s t e n e r installiert, so ist nichts zu schreiben. Implementiert dieser das Se r i a l i z a b l e Interface, so schreiben wir den Namen. Ist l ein AWTEv e n t Mu l t i c a s t e r , dann wird die s a v e I n t e r n a l ( ) Methode gerufen. In wr i t e Ob j e c t ( ) vom Bu t t o n werden wir den String a c t i o n Li s t e n e r K vergeblich suchen, dieser ist in Co mp o n e n t deklariert, dort steht dann folgendes: / ** I nt e r na l , c ons t a nt s f or s e r i a l i z a t i on */ f i n a l s t a t i c St r i n g a c t i o n Li s t e n e r K = " a c t i o n L" ;
War l ein AWTEv e n t Mu l t i c a s t e r , so werden in s a v e ( ) während der Serialisierung alle Bezüge des Ev e n t Li s t e n e r s serialisiert. In der wr i t e Ob j e c t ( ) Methode sehen wir weiterhin: Anschließend wird die Reihe mit einer Null abgeschlossen. Nun ist es an der Zeit sich r e a d Ob j e c t ( ) vom Button vorzunehmen: p r i v a t e v o i d r e a d Ob j e c t ( Ob j e c t I n p u t St r e a m s ) t h r o ws Cl a s s No t Fo u n d Ex c e p t i o n , I OEx c e p t i o n { s . d e f a u l t Re a d Ob j e c t ( ) ; Ob j e c t k e y Or Nu l l ; wh i l e ( n u l l ! = ( k e y Or Nu l l = s . r e a d Ob j e c t ( ) ) ) { St r i n g k e y = ( ( St r i n g ) k e y Or Nu l l ) . i n t e r n ( ) ; i f ( a c t i o n Li s t e n e r K == k e y ) a d d Ac t i o n Li s t e n e r ( ( Ac t i o n Li s t e n e r ) ( s . r e a d Ob j e c t ( ) ) ) ; e l s e / / s ki p va l ue f or unr e c ogni z e d ke y s . r e a d Ob j e c t ( ) ; } }
Zunächst liest ein r e a d Ob j e c t ( ) die Variablen l a b e l und a c t i o n Co mma n d . Anschließend lesen wir solange ein Ob j e c t , bis dieses gleich Null ist – in wr i t e Ob j e c t ( ) haben wir die Liste der Events mit Null abgeschlossen. In der Schleife erzeugen wir dann einen String k e y . Wir müssen an dieser Stelle noch genauer auf die Methode i n t e r n ( ) eingehen, denn sie garantiert uns, das die Strings identisch sind. c l a s s j a v a . l a n g . St r i n g i mp l e me n t s Se r i a l i z a b l e , Co mp a r a b l e Ÿ p u b l i c n a t i v e St r i n g i n t e r n ( )
Alle Konstanten werden in der JVM in einem Pool verwaltet. Genauso die Strings. Diese Methode liefert nun eine identische Repräsentation des String-Objekts. Denn sind zwei Strings
• • • 359 • • •
in ihrem Wert gleich – also s . e q u a l s ( t ) für zwei Strings s und t – sind noch lange nich ihre Zeiger gleich. Allerdings sorgt i n t e r n ( ) dafür, dass s . i n t e r n ( ) == t . i n t e r n ( ) ist. Das Wissen um die Funktionsweise von i n t e r n ( ) hilft uns nun, den nachfolgenden String-Vergleich zu verstehen, der sonst falsch ist. Wir vergleichen, ob wir im Dateistrom die Zeichenkette ›actionL‹ – ausgedrückt durch die String-Konstante a c t i o n Li s t e n e r K in Co mp o n e n t – vorliegen haben. Wenn ja, dann können wir das nächste Objekt auslesen, und es als Ac t i o n Li s t e n e r zu unserem Button zufügen.
Menüs serialisieren Eine Menüzeile (Klasse Me n u Ba r ) eignet sich auch prima zum Serialisieren. Schauen wir uns den Quellcode an (der im Vergleich zum Button herrlich einfach ist): p r i v a t e v o i d wr i t e Ob j e c t ( j a v a . i o . Ob j e c t Ou t p u t St r e a m s ) t h r o ws j a v a . l a n g . Cl a s s No t Fo u n d Ex c e p t i o n , j a v a . i o . I OEx c e p t i o n { s . d e f a u l t Wr i t e Ob j e c t ( ) ; } p r i v a t e v o i d r e a d Ob j e c t ( j a v a . i o . Ob j e c t I n p u t St r e a m s ) t h r o ws j a v a . l a n g . Cl a s s No t Fo u n d Ex c e p t i o n , j a v a . i o . I OEx c e p t i o n { s . d e f a u l t Re a d Ob j e c t ( ) ; f o r ( i n t i = 0 ; i < me n u s . s i z e ( ) ; i ++) { Me n u m = ( Me n u ) me n u s . e l e me n t At ( i ) ; m. p a r e n t = t h i s ; } } wr i t e Ob j e c t ( ) schreibt seine Attribute (im wesentlichen den Vektor me n u ) einfach in den Strom. Die Objekte in me n u wissen wiederum wie sie sich zu schreiben haben. Beim Lesen wird dieser Vektor auch wieder gelesen und gefüllt, jedoch müssen wir den p a r e n t -Zeiger auf die eigene Klasse setzen. p a r e n t ist eine transiente Variable in der Co n t a i n e r -Klasse.
Im übrigen ist der Quellcode der Klasse Me n u fast derselbe wie der in Me n u Ba r . wr i t e Ob j e c t ( ) unterscheidet sich nicht von dem in Me n u Ba r , nur in r e a d Ob j e c t ( ) wird dann über i t e m iteriert: f o r ( i n t i = 0 ; i < i t e ms . s i z e ( ) ; i ++) { Me n u I t e m i t e m = ( Me n u I t e m) i t e ms . e l e me n t At ( i ) ; i t e m. p a r e n t = t h i s ; }
Natürlich müssen wir sofort in Me n u I t e m hineinschauen und herausfinden, ob es dort auch so einfach aussieht. Aber leider werden wir enttäuscht, denn dort haben wir es wieder mit Objekten zu tun, die Events auslösen können, also Buttons usw. Glücklicherweise sehen in Me n u I t e m aber die Methoden zum Auslesen und Schreiben genauso aus wie beim Bu t t o n .
• • 360 •• • •
11.9.5 Wie funkt ioniert Serialisierung? Java bietet mit der Serialisierung eine entgegenkommende Technik, um Objekte zu sichern. Die Sicherung erfolgt dabei in einen Datenstrom, der also an eine Datei oder an eine Netzwerkverbindung verknüpft sein kann. Dabei muss die Schreibmethode (und dies ist wr i t e Ob j e c t ( ) der Klasse Ob j e c t Ou t p u t St r e a m) aber genau wissen, welches Objekt schon geschrieben wurde und welches nicht. Es ist einleuchtend, dass bei komplexen Bäumen mit Mehrfachverweisen nicht zigmal alle Objekte gesichert werden. Jedes Objekt hat während der Serialisierung ein eindeutiges Handle. Geht nur die wr i t e Ob j e c t ( ) durch den Objektbaum, so schaut der Algorithmus nach, ob ein Objekt des Handles schon gesichert wurde. Wenn, dann ist nichts zu sichern. Genau dieser Teil des Quellcodes ist unten abgedruckt. Es ist ein Ausschnitt aus wi r t e Ob j e k t ( ) . / / I f t he a l t e r na t e obj e c t i s a l r e a dy / / s e r i a l i z e d j u s t r e me mb e r t h e r e p l a c e me n t i f ( s e r i a l i z e Nu l l An d Re p e a t ( a l t o b j ) ) { a d d Re p l a c e me n t ( o b j , a l t o b j ) ; r e t ur n; }
Es überprüft die Methode s e r i a l i z e Nu l l An d Re p e a t ( Ob j e c t ) , ob ein Objekt schon gesichert wurde. Wenn, dann wird lediglich die Referenz auf dieses Objekt gespeichert, nicht das Objekt selbst. Diese Funktion muss also herausfinden, dass das Objekt überhaupt schon gespeichert wurde, und anschließend muss es die Referenz in den Datenstrom schreiben. Ist die Referenz n u l l , also ein Sonderfall, so wird einfach eine spezielle Kennung (TC_ NULL aus dem Interface Ob j e c t St r e a mCo n s t a n t s ) geschrieben. Ist die Referenz nicht n u l l , so wird nach der Kennung gesucht, und diese folgt dann hinter der Kennung TC_ REFERENCE. Nachfolgend der entsprechende Teil aus s e r i a l i z e Nu l l An d Re p e a t ( Ob j e c t o b j ) . / * Lo o k t o s e e i f t h i s o b j e c t h a s a l r e a d y b e e n r e p l a c e d . * I f s o , p r o c e e d u s i n g t h e r e p l a c e me n t o b j e c t . */ i f ( r e p l a c e Ob j e c t s ! = n u l l ) { o b j = l o o k u p Re p l a c e ( o b j ) ; } i n t h a n d l e = f i n d Wi r e Of f s e t ( o b j ) ; i f ( h a n d l e >= 0 ) { / * Ad d a r e f e r e n c e t o t h e s t r e a m * / wr i t e Co d e ( TC_ REFERENCE) ; wr i t e I n t ( h a n d l e + b a s e Wi r e Ha n d l e ) ; r e t ur n t r ue ; } r e t ur n f a l s e ; / / not s e r i a l i z e d, i t s up t o t he c a l l e r
Die Methode f i n d Wi r e Of f s e t ( Ob j e c t ) liefert nun das Handle für das Objekt zurück. Dieses Handle ergibt sich aus einer Hashfunktion. Hier sind verschiedene Variablen in der Klasse reserviert. Die Dokumentation ist ausführlich genug: /* * * * *
Ob j e c t r e f e r e n c e s a r e ma p p e d t o t h e wi r e h a n d l e s t h r o u g h a h a s h t a b l e Wi r e Ha n d l e s a r e i n t e g e r s g e n e r a t e d b y t h e Ob j e c t Ou t p u t St r e a m, t h e y n e e d o n l y b e u n i q u e wi t h i n a s t r e a m. Ob j e c t s a r e a s s i g n e d s e q u e n t i a l h a n d l e s s t o r e d i n wi r e Ha n d l e 2 Ob j e c t . Th e h a n d l e f o r a n o b j e c t i s i t s i n d e x i n wi r e Ha n d l e 2 Ob j e c t . • • • 361 • • •
* Ob j * Th e * -1 */ pr i va t pr i va t pr i va t pr i va t
e c t wi t h t h e " s a me " h a s h c o d e a r e c h a i n e d u s i n g wi r e Ha s h 2 Ha n d l e . h a s h c o d e o f o b j e c t s i s u s e d t o i n d e x t h r o u g h t h e wi r e Ha s h 2 Ha n d l e . i s t h e ma r k e r f o r u n u s e d c e l l s i n wi r e Ne x t Ha n d l e e e e e
Ob j i nt i nt i nt
e c t [ ] wi r e Ha n d l e 2 Ob j e c t ; [ ] wi r e Ne x t Ha n d l e ; [ ] wi r e Ha s h 2 Ha n d l e ; n e x t Wi r e Of f s e t ;
Es bildet Sy s t e m. i d e n t i t y Ha s h Co d e ( Ob j e c t ) den Hashwert eines Objekts. Anschließend wird in einer ausprogrammierten Hashtabelle das Handle ermittelt. /* * Lo c a t e a n d r e t u r n i f f o u n d t h e h a n d l e f o r t h e s p e c i f i e d o b j e c t . * - 1 i s r e t ur ne d i f t he obj e c t doe s not oc c ur i n t he a r r a y of * k n o wn o b j e c t s . */ p r i v a t e i n t f i n d Wi r e Of f s e t ( Ob j e c t o b j ) { i n t h a s h = Sy s t e m. i d e n t i t y Ha s h Co d e ( o b j ) ; i n t i n d e x = ( h a s h & 0 x 7 FFFFFFF) % wi r e Ha s h 2 Ha n d l e . l e n g t h ; f o r ( i n t h a n d l e = wi r e Ha s h 2 Ha n d l e [ i n d e x ] ; h a n d l e >= 0 ; h a n d l e = wi r e Ne x t Ha n d l e [ h a n d l e ] ) { i f ( wi r e Ha n d l e 2 Ob j e c t [ h a n d l e ] == o b j ) r e t ur n ha ndl e ; } r e t ur n - 1; }
Nun bleibt lediglich die Frage, an welcher Stelle denn die Hashtabelle aufgebaut wird. Nun muss ja jedes Objekt, was sich schreibt, eine Information ablegen, dass es schon gesichert wurde. Dazu wird in wr i t e Ob j e k t ( ) die Methode a s s i g n Wi r e Of f s e t ( Ob j e c t ) verwendet. Immer dann, wenn ein Objekt in den Stream gesetzt wird, so wird auch a s s i g n Wi r e Of f s e t ( ) aufgerufen. Bleibt nur noch eine Frage zu klären: Wie findet wr i t e Ob j e k t ( ) die zu schreibenden Attribute überhaupt? Hier verrichtet o u t p u t Ob j e c t ( o b j ) den Dienst. Die Methode testet, ob das Objekt überhaupt serialisierbar ist, andernfalls wirft es eine No t Se r i a l i z a b l e Ex c e p t i o n , sammelt alle Superklassen in einem Stack, um auch diese später zu schreiben. Alle sich auf dem Stack befindenden Objekte müssen nun geschrieben werden. Dies erledigt d e f a u l t Wr i t e Ob j e c t ( ) . Die Klasse Ob j e c t St r e a mCl a s s verfügt über eine Methode g e t Fi e l d s No Co p y ( ) , die ein Array vom Typ Ob j e c t St r e a mFi e l d zurückgibt. Dort befinden sich nun alle Attribute. o u t p u t Cl a s s Fi e l d s ( ) holt sich mittels g e t Ty p e Co d e ( ) den Typ der Variablen und schreibt ihn. Ein Auszug der Methode für das Schreiben eines Bytes: s wi t c h ( f i e l d s [ i ] . g e t Ty p e Co d e ( ) ) { c a s e ' B' : b y t e b y t e Va l u e = f i e l d s [ i ] . g e t Fi e l d ( ) . g e t By t e ( o ) ; wr i t e By t e ( b y t e Va l u e ) ; br e a k; ... } • • 362 •• • •
Spannend wird es nun, wenn eine Objektbeziehung vorliegt. Wieder ein Blick in den Quellcode: c a s e ' L' : Ob j e c t o b j e c t Va l u e = f i e l d s [ i ] . g e t Fi e l d ( ) . g e t ( o ) ; wr i t e Ob j e c t ( o b j e c t Va l u e ) ; br e a k;
Hier wird, genauso wie wir es auch machen, wr i t e Ob j e c t ( ) benutzt.
• • • 363 • • •
KAPITEL
12 Grafikprogram m ierung m it dem AWT Die meiste Gefahr geht nicht von den Erfahrungen aus, die man machen muss, sondern von denen, die man nicht machen darf. – Hellmut Walters
Das AWT definiert einen Satz von plattformunabhängigen Widgets (Window Elements) – etwa Schaltflächen (engl. Buttons), Textfelder und Menüs – sowie Containern, die die Gruppierung der Elemente erlauben und grafischen Grundprimitiven wie Zeichenstifte. Leider ist das AWT teilweise so einfach, dass eine professionelle Oberfläche nur mit Mühe zu erstellen ist. Für die Abkürzung ›AWT‹ sind mittlerweise viele Erklärungen im Umlauf: Abstract Window Toolkit (so sollte es eigentlich heißen), Awfull Window Toolkit (kommt der Sache schon ganz nahe), Another Window Toolkit, Awkward Window Toolkit, Annoying Window Toolkit, Advanced Window Toolkit oder Applet Window Toolkit. In den nächsten Kapiteln wollen wir uns näher mit dem AWT auseinander setzten. Wir wollen zuerst die grafischen Primitiven nutzen und später über die Widgets sprechen.
12.1 Fenst er ( Windows) unt er grafischen Oberflächen Der Anfang aller Programme unter einer grafischen Benutzeroberfläche ist das Fenster (engl. Window). Wir müssen uns daher erst mit den Fenstern beschäftigen, bis wir auf den Inhalt näher eingehen können. Das Fenster dient auch zur Grundlage von Dialogen, spezielle Fenster, die entweder modal oder nicht-modal arbeiten können.
12.1.1 Fenst er öffnen Damit wir unter Java ein Fenster öffnen können, müssen wir zunächst einmal das a wt -Paket mit einbinden. Dann können wir eine Klasse Fr a me und deren Methoden nutzen. Das Listing ist sehr kurz:
• • 364 •• • •
Quellcode 12.a
Hello.java
i mp o r t j a v a . a wt . Fr a me ; p u b l i c c l a s s He l l o Fr a me { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Fr a me f = n e w Fr a me ( " Da s Fe n s t e r z u r We l t " ) ; f . s e t Si z e ( 3 0 0 , 2 0 0 ) ; f . s h o w( ) ; } }
In unserem Hauptprogramm erzeugen wir ein Fr a me Objekt.
Fe h le r m e ldu n g: Wenn w ir eine Applikat ion m it einem Fenst er st ar t en und die Meldung j a v a . l a n g . I n t e r n a l Er r o r : u n s u p p o r t e d s c r e e n d e p t h er scheint , so benut zen w ir m ehr Far ben, als unser Bildschir m unt er st üt zt . Mit w eniger als 16 Far ben hat die Jav a Um gebung Schw ier igk eit en.
Mehr zur Klasse Fram e Neben dem Standardkonstruktor existiert noch ein weiterer, bei dem wir den Namen in der Titelleiste noch bestimmen können, wie im Beispiel geschehen. c l a s s j a v a . a wt . Fr a me e x t e n d s Wi n d o w i mp l e me n t s Me n u Co n t a i n e r Ÿ Fr a me ( )
Erzeugt ein neues Fr a me Objekt, welches am Anfang unsichtbar ist. Ÿ Fr a me ( St r i n g ) Erzeugt ein neues Fr a me Objekt, mit einem Fenster-Titel, welches am Anfang unsichtbar ist. Ÿ v o i d s e t Ti t l e ( St r i n g )
Setzt den Titel des Fensters außerhalb des Konstruktors.
• • • 365 • • •
Abbildung 3: Das erste Fenster Damit ist das Fenster vorbereitet, aber noch nicht sichtbar. Es wird erst sichtbar, wenn wir die s h o w( ) Methode aufrufen. Alternativ funktioniert s e t Vi s i b l e ( t r u e ) . Da sich die Fr a me Klasse direkt von Wi n d o w ableitet – ein Frame ist ein Window mit Titelleiste –, besitzt Fr a me keine eigene s h o w( ) Funktion. c l a s s j a v a . a wt . Wi n d o w e x t e n d s Co n t a i n e r Ÿ v o i d s h o w( )
Zeigt das Fenster an. Liegt es im Hintergrund, so wird es wieder in den Vordergrund geholt. Ÿ b o o l e a n i s Sh o wi n g ( ) t r u e , wenn sich das Fenster auf dem Bildschirm befindet. Ÿ v o i d t o Ba c k ( )
Das Fenster wird als letztes in die Fensterreihenfolge eingereiht. Ein anderes Fenster wird somit sichtbar. Ÿ v o i d t o Fr o n t ( )
Platziert das Fenster als erstes in der Darstellung aller Fenster auf dem Schirm.
Haupt program m von Fram e ableit en Wir können unsere neue Klasse auch direkt von Fr a me ableiten. Dann ist es uns gestattet, auf die Funktionen der Klasse Fr a me direkt zuzugreifen, zum Beispiel auf s e t Si z e ( ) . Im Hauptprogramm erzeugen wir über den Konstruktor dann das Fenster. Der Konstruktor ruft über die s u p e r ( ) Funktion den Konstruktor von Fr a me auf (da wir Fr a me ja einfach beerben). In den nachfolgenden Programmen werden wir immer diese Methode verwenden. Quellcode 12.a
SinWin.java
i mp o r t j a v a . a wt . Fr a me ; p u b l i c c l a s s Si n Wi n e x t e n d s Fr a me { p u b l i c Si n Wi n ( i n t x , i n t y ) { • • 366 •• • •
s u p e r ( " Ha l l o " ) ; s e t Si z e ( x , y ) ; } p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Si n Wi n wi n 1 = n e w Si n Wi n ( 1 0 0 , 2 0 0 ) ; wi n 1 . s h o w( ) ; Si n Wi n wi n 2 = n e w Si n Wi n ( 3 0 0 , 3 0 0 ) ; wi n 2 . s h o w( ) ; } }
Nachdem im Konstruktor dann das Fenster erzeugt wurde, ändern wir die Größe. Im Hauptprogramm erzeugen wir zwei Fenster wi n 1 und wi n 2 , die beide Exemplare der eigenen Klasse sind. Die s h o w( ) Methode ist natürlich an ein Objekt gebunden. Wir werden vielleicht die Idee bekommen, folgendes Programmsegment auszuprobieren – auf den ersten Blick liegt es ja nahe: p u b l i c c l a s s So Ni c h t e x t e n d s Fr a me { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { s u p e r ( " Ha l l o " ) ; s e t Si z e ( 3 0 0 , 3 0 0 ) ; s h o w( ) ; } }
Das erste Problem liegt bei s u p e r ( ) ; es darf nur in Konstruktoren aufgerufen werden, aber nicht in ganz normalen Funktionen, wie ma i n ( ) . (Wir haben sicherlich noch im Hinterkopf, dass s u p e r ( ) nur in der ersten Zeile eines Konstruktors stehen darf.) Das nächste Problem sind die Funktionen s e t Si z e ( ) und s h o w( ) selber. ma i n ( ) ist statisch, das heißt, alle Funktionen müssen statisch sein oder sich auf erzeugte Objekte beziehen. s e t Si z e ( ) bzw. s h o w( ) sind aber keine statische Methoden der Klasse j a wa . a wt . Co mp o n e n t , bzw. j a v a . a wt . Wi n d o w, sondern werden dynamisch gebunden. Da, wie ma i n ( ) es möchte, keine statischen Referenzen erstellt werden können, ist das Programm somit total falsch.
12.2 Grundlegendes zum Zeichnen Nachdem wir ein Fenster öffnen können, wollen wir etwas in den Fensterinhalt schreiben. In den nächsten Abschnitten beschäftigen wir und auch intensiver mit den Zeichenmöglichkeiten.
12.2.1 Die paint ( ) Met hode Als einleitendes Beispiel soll nun genügen, einen Text zu platzieren. Dazu implementieren wir die Funktion p a i n t ( ) der Fr a me Klasse. Die Co mp o n e n t Klasse definiert u p d a t e ( ) abstrakt. Indem wir sie implementieren wird der gewünschte Inhalt immer dann gezeichnet, wenn das Fenster neu aufgebaut wird, oder wir von außen r e p a i n t ( ) oder u p d a t e ( ) aufrufen. • • • 367 • • •
Quellcode 12.b
Biene.java
i mp o r t j a v a . a wt . * ; i mp o r t j a v a . a wt . e v e n t . * ; p u b l i c c l a s s Bi e n e e x t e n d s Fr a me { p u b l i c Bi e n e ( ) { s e t Si z e ( 5 0 0 , 1 0 0 ) ; a d d Wi n d o wLi s t e n e r ( n e w Wi n d o wAd a p t e r ( ) { p u b l i c v o i d wi n d o wCl o s i n g ( Wi n d o wEv e n t e ) { Sy s t e m. e x i t ( 0 ) ; } }) ; } p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { g . d r a wSt r i n g ( " \ " Ma j a , wo b i s t d u ? \ " ( Mi t t e r me i e r ) " , 100, 60 ) ; } p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Bi e n e ma y a = n e w Bi e n e ( ) . s h o w( ) ; } }
Abbildung 4: Ein Fenster mit gezeichnetem Inhalt Ein spezieller Wert wird in der p a i n t ( ) Methode übergeben – der Grafikkontext, ein Objekt vom Typ Gr a p h i c s . Dieses Gr a p h i c s -Objekt besitzt verschiedene Methoden (Linie, Kreis, Oval, Rechteck, String uvm.), womit wir an die Objekte der Oberfläche gelangen und darauf zeichnen können. Auch dann, wenn die Objekte nicht direkt sichtbar sind. Bei jeder Zeichenoperation muss der Grafikkontext angeben werden, denn in dieses Objekt hält Buch über mehrere Sachen: n Die Komponente, auf der zu zeichnen ist (hier erst einmal das rohe Fenster). n Koordinaten des Bildbereiches und des Clipping-Bereiches. n Der aktuelle Clip-Bereich, und Font, die aktuelle Farbe. n Die Pixeloperation (XOR oder Paint). n Die Funktion, mit der die Farbe verknüpft wird.
• • 368 •• • •
Wir können nur in der p a i n t ( ) Methode auf das Gr a p h i c s -Objekt zugreifen. Diese wiederum wird immer dann aufgerufen, wenn die Komponente neu gezeichet werden muss. Dies nutzen wir dafür, um einen Text zu schreiben. Leicht ist zu entnehmen, dass d r a wSt r i n g ( n a me , x - Ac h s e , y - Ac h s e ) einen Text in den Zeichenbereich des Grafikkontextes schreibt. Im Folgenden werden wir noch weitere Funktionen kennenlernen.
12.3 Punkt e und Linien Die grafischen Objekte werden in einem Koordinaten-System platziert, welches seine Ursprungskoordinaten – also (0,0) – links oben definiert. Die Angabe ist absolut zum Fensterrahmen. Wählen wir die Koordinate auf der Y-Achse klein, so kann es vorkommen, das wir nichts mehr sehen, denn das Objekt wandert in die Bildschirmleiste. Gelegentlich mischt sich die Umgangssprache mit der Sprache der Mathematik und Computergrafik, so dass wir noch einmal die wichtigsten Begriffe aufzählen:
Punkt e Ein Punkt ist durch zwei oder mehrere Koordinaten gekennzeichnet, ganz nach seinen Dimensionen, in denen er sich befindet. Da er, so kennen wir ihn aus der Mathematik, keine Ausdehnung hat, dürfen wir ihn eigentlich gar nicht sehen. In Java gibt es keine Funktion, mit der Punkte gezeichnet werden. Diese können nur durch einen Linienbefehl erzeugt werden.
Pixel Das Wort Pixel ist eine Abkürzung für ›Picture Element‹. Ein Pixel beschreibt einen physikalischen Punkt auf dem Bildschirm und ist daher nicht zu verwechseln mit einem Punkt (obwohl umgangssprachlich keine feste Trennung existiert). Pixel besitzen ebenfalls wie Punkte Koordinaten und wird ein grafisches Objekt gezeichnet, so werden die entsprechenden Punkte auf dem Bildschirm gesetzt. Die Anzahl der Pixel auf dem Monitor ist beschränkt, unter einer Auflösung von 1024 x 768 ›Punkten‹ sind dies also 786.432 Pixel, die einzeln zu setzen sind. Einen Pixel zu setzen heißt aber nichts anderes als ihm eine andere Farbe zu geben.
Linien Auch bei Linien müssen wir uns von der Vorstellung trennen, die uns die Analytische Geometrie vermittelt. Denn dort ist eine Linie als kürzeste Verbindung zwischen zwei Punkten definiert – so sagt es Euklid. Da sie Ein-Dimensional sind besitzen sie dementsprechend eine Länge aus unendlich vielen Punkten aber keine Dicke. Auf dem Bildschirm besteht eine Linie nur aus endlich vielen Punkten und wenn eine Linie gezeichnet wird, dann werden Pixel gesetzt, die nahe an der wirklichen Linie sind. Die Punkte müssen passend in ein Raster gesetzt werden und so passiert es, dass die Linie in Stücke zerbrochen wird. Dieses Problem gibt es bei allen grafischen Operationen, da von Fließkommawerten eine Abbildung auf Ganzzahlen, in unserem Fall absolute Koordinaten des Bildschirmes, gemacht werden müssen. Eine bessere Darstellung der Linien und Kurven ist durch
• • • 369 • • •
›antialiasing‹ zu erreichen. Dies ist eine Art weichzeichnen nicht nur mit einer Farbe, sondern mit Abstufungen, so dass die Qualität am Bildschirm wesentlich besser ist. Auch bei Zeichensätzen am Bildschirm ist eine gute Verbesserung der Lesbarkeit zu erzielen. c l a s s j a v a . a wt . Gr a p h i c s Ÿ v o i d d r a wLi n e ( i n t x 1 , i n t y 1 , i n t x 2 , i n t y 2 )
Zeichnet eine Linie zwischen den Koordinaten (x1,y1) und (x2,y2) in der Vordergrundfarbe. Ÿ v o i d d r a wLi n e ( i n t x , i n t y , i n t x , i n t y )
Setzt einen Punkt an die Stelle (x,y).
12.4 Recht ecke aller Art Im Folgenden wollen wir nur die p a i n t ( ) Methode mit etwas Leben füllen. Zunächst ein Blick auf die Funktionen, die uns Rechtecke zeichnen lässt. Die Rückgabewerte sind immer v o i d . Es ist nicht so, als dass die Funktionen mitteilen, ob auch ein tatsächlicher Zeichenbereich gefüllt werden konnte. Liegen die Koordinaten und das zu zeichnende Objekt nicht im Sichtfenster, so passiert einfach gar nichts. c l a s s j a v a . a wt . Gr a p h i c s Ÿ d r a wRe c t ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t )
Zeichnet ein Rechteck in der Vordergrundfarbe. Das Rechteck ist (wi d t h + 1) Pixel breit und (h e i g h t + 1) Pixel hoch. Ÿ f i l l Re c t ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t )
Zeichnet ein gefülltes Rechteck in der Vordergrundfarbe. Das Rechteck ist (wi d t h + 1) Pixel breit und (h e i g h t + 1) Pixel hoch. Ÿ d r a wRo u n d Re c t ( i n t x , y , i n t wi d t h , h e i g h t , i n t a r c Wi d t h , a r c He i g h t )
Zeichnet ein abgerundetes Rechteck in der Vordergrundfarbe. Das Rechteck ist (wi d t h + 1) Pixel breit und (h e i g h t + 1) Pixel hoch. a r c Wi d t h gibt den horizontalen und a r c He i g h t den vertikalen Durchmesser der Kreisbögen der Ränder an. Ÿ f i l l Ro u n d Re c t ( i n t x , y , i n t wi d t h , h e i g h t , i n t a r c Wi d t h , a r c He i g h t ) Wie d r a wRo u n d Re c t ( ) , nur gefüllt. Ÿ d r a w3 DRe c t ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t , b o o l e a n r a i s e d )
Zeichnet ein dreidimensional angedeutetes Rechteck in der Vordergrundfarbe. Der Parameter r a i s e d gibt an, ob das Rechteck über der Fläche oder in die Fläche hinein wirken soll. Die Farben für den Effekt werden aus der Vordergrundfarben gewonnen. Ÿ f i l l 3 DRe c t ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t , b o o l e a n r a i s e d ) Wie d r a w3 Dr e c t ( ) , nur gefüllt.
Die Implementierung einiger Routinen können wir uns im Paket j a v a . a wt . Gr a p h i c s anschauen. So finden wir dort beispielsweise d r a wRe c t ( ) : p u b l i c v o i d d r a wRe c t ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t ) • • 370 •• • •
{ i f ( ( wi d t h < 0 ) | | r e t ur n; }
( he i ght < 0) ) {
i f ( h e i g h t == 0 | | wi d r a wLi n e ( x , y , x + } el s e { d r a wLi n e ( x , y , x + d r a wLi n e ( x + wi d t h , d r a wLi n e ( x + wi d t h , d r a wLi n e ( x , y + h e i }
d t h == 0 ) { wi d t h , y + h e i g h t ) ; wi d t h y, x + y + he i ght , x,
1, y) ; wi d t h , y + h e i g h t - 1 ) ; ght , x + 1, y + he i ght ) ; y + 1) ;
}
Neben den anderen beiden Funktion d r a w3 DRe c t ( ) und f i l l 3 DRe c t ( ) sind dies aber die einzigen ausprogrammierten Routinen. Die restlichen Methoden werden von der konkreten Gr a p h i c s Klasse der darunter liegenden Plattform implementiert.
12.5 Alles was rund ist Die Gr a p h i c s Klasse stellt vier Methoden zum Zeichnen von Ovalen und Kreisbögen bereit. Gefüllte und nicht gefüllte Ellipsen sind immer in einem Rechteck eingepasst. c l a s s j a v a . a wt . Gr a p h i c s Ÿ d r a wOv a l ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t )
Zeichnet ein Oval in der Vordergrundfarbe, welches die Ausmaße eines Rechteckes hat. Das Oval hat eine Größe von (wi d t h + 1) Pixeln in der Breite und (h e i g h t + 1) Pixel in der Höhe. Ÿ f i l l Ov a l ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t ) Wie d r a wOv a l ( ) , nur gefüllt. Ÿ v o i d d r a wAr c ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t , i n t s t a r t An g l e , i n t a r c An g l e )
Zeichnet einen Kreisbogen. Null Grad liegt in der 3 Uhr Position. Bei einem Aufruf mit den Winkel-Parametern 0, 270 wird ein Kreisbogen gezeichnet, bei dem 90 Grad im unteren rechnten Bereich nicht gezeichnet sind. Ÿ v o i d f i l l Ar c ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t , i n t s t a r t An g l e , i n t a r c An g l e ) Wie d r a wAr c ( ) , nur gefüllt.
Und so sieht ein Beispiel für die Methoden in der Praxis aus: publ { g. g. g. g. g.
i c v o i d p a i n t ( Gr a p h i c s g ) d r a wRe c t ( 1 0 , 1 0 , 8 0 , 3 0 ) ; d r a wRo u n d Re c t ( 1 1 0 , 1 0 , 8 0 , 3 0 , 1 5 , 1 5 ) ; d r a w3 DRe c t ( 2 1 0 , 1 0 , 8 0 , 3 0 , t r u e ) ; d r a w3 DRe c t ( 2 1 0 , 6 0 , 8 0 , 3 0 , f a l s e ) ; d r a wOv a l ( 1 0 , 1 1 0 , 8 0 , 3 0 ) ; • • • 371 • • •
g. g. g. g. g. g.
s et fil fil fil fil fil
Co l o r ( Co l o r . r e d ) ; l Re c t ( 1 0 , 1 0 , 8 0 , 3 0 ) ; l Ro u n d Re c t ( 1 1 0 , 1 0 , 8 0 , 3 0 , 1 5 , 1 5 ) ; l 3 DRe c t ( 2 1 0 , 1 0 , 8 0 , 3 0 , t r u e ) ; l 3 DRe c t ( 2 1 0 , 6 0 , 8 0 , 3 0 , f a l s e ) ; l Ov a l ( 1 0 , 1 1 0 , 8 0 , 3 0 ) ;
}
Mit g . s e t Co l o r ( Co l o r . r e d ) setzen wir die Farbe, also die Farbe des Zeichenstiftes, auf rot. Mit Farben geht es im übernächsten Kapitel weiter.
Eine Kreis und Ellipsen Klasse Bei der Methode d r a wOv a l ( ) müssen wir immer daran denken, dass die Ellipse, oder im Spezialfall der Kreis, in ein Rechteck mit Startkoordinaten und mit Breite und Höhe gezeichnet wird. Dies ist nicht immer die natürliche Vorstellung einer Ellipse. Daher packen wir das ganze in eine Klasse El l i p s e und geben ihr eine p a i n t ( ) Methode. Quellcode 12.e
Ellipse.java
i mp o r t j a v a . a wt . * ; c l a s s El l i p s e { i nt x, y, r x, r y; El l i p s e ( i n t x , i n t y , i n t r x , i n t r y ) { t hi s . x = x; t hi s . y = y; t hi s . r x = r x; t hi s . r y = r y; } v o i d d r a w( Gr a p h i c s g ) { g . d r a wOv a l ( x - r x , y - r y , r x +r x , r y +r y ) ; } }
12.6 Linenzüge sind Polygone und Poylines Eine Polyline besteht aus einer Menge von Linen, die einen Linienzug beschreiben. Dieser Linienzug muss nicht geschlossen sein. Ist er allerdings geschlossen, so sprechen wir von einem Polygon. In Java gibt es verschiedenen Möglichkeiten, Polygone und Polylines zu zeichnen. Zunächst einmal über ein Koordinatenfeld. c l a s s j a v a . a wt . Gr a p h i c s Ÿ v o i d d r a wPo l y l i n e ( i n t x Po i n t s [ ] , i n t y Po i n t s [ ] , i n t n Po i n t s )
Zeichnet einen Linenzug durch die gegebenen Koordinaten in der Vordergrundfarbe. Die Figur ist nicht automatisch geschlossen, wenn nicht die Start- und Endkoordinaten gleich sind. Mit n Po i n t kontrollieren wir die Anzahl der gezeichneten Linien.
• • 372 •• • •
Ÿ d r a wPo l y g o n ( i n t x Po i n t s [ ] , i n t y Po i n t s [ ] , i n t n Po i n t s ) Zeichnet wie d r a wPo l y l i n e ( ) einen Linienzug, schließt diesen aber immer gleich, indem die erste Koordinate mit der Koordinate n Po i n t s verbunden wird. Ÿ v o i d f i l l Po l y g o n ( i n t x Po i n t s [ ] , i n t y Po i n t s [ ] , i n t n Po i n t s )
Füllt das Polygon nach der Gerade/Ungerade-Regel aus.
Fü lle n v on Poly lin e s Es er scheint einleucht end, dass eine Polyline nicht gefüllt w er den k ann da sie offen ist . Som it gibt es die Funk t ion f i l l Po l y l i n e ( ) nicht .
12.6.1 Die Polygon- Klasse Neben der Möglichkeit, die Linenzüge durch Koordinatenfelder zu beschreiben, gibt es in Java die Polygon-Klasse Po l y g o n . Sie ist einer Erweiterung des Interfaces Sh a p e . Sie ist aber minimal, lediglich die Methode g e t Bo u n d s ( ) wird implementiert. Ein Polygon-Objekt verwaltet eigenständig seine Koordinaten und von außen können wir Elemente hinzunehmen. Mit der mächtigen Methode c o n t a i n s ( ) können wir herausfinden, ob ein Punkt in dem von der Polyline ausgezeichneten Fläche liegt. Doch zunächst müssen wir ein Polyline-Objekt erzeugen. Dazu dienen zwei Konstruktoren: c l a s s j a v a . a wt . Po l y g o n i mp l e me n t s Sh a p e , Se r i a l i z a b l e Ÿ Po l y g o n ( )
Erzeugt ein Polygon-Objekt ohne Koordinaten. Ÿ Po l y g o n ( i n t x p o i n t s [ ] , i n t y p o i n t s [ ] , i n t n p o i n t s )
Erzeugt ein Polygon mit den angegebenen Koordinaten. Nun können wir Punkte hinzufügen und Anfragen an das Po l y g o n Objekt stellen: Ÿ Re c t a n g l e g e t Bo u n d s ( )
Gibt die Bounding-Box der Figur zurück. Sie beschreibt die Ausmaße, wie das Objekt in einem Rechteck liegen würde. Ein Re c t a n g l e Objekt besitzt die Variablen h e i g h t (Höhe des Rechteckes), wi d t h (Breite des Rechteckes), x (x-Koordinate) und y (y -Koordinate des Rechteckes). Mit verschiedenen Funktionen lassen sich Rechtecke zusammenfassen und schneiden. Ÿ v o i d a d d Po i n t ( i n t x , i n t y )
Die Koordinate (x,y) wird hinzugefügt. Die Grenzen (engl. Boundings) werden automatisch aktualisiert. Ÿ bool e a n c ont a i ns ( i nt x, i nt y ) Liefer t r u e , wenn der Punkt (x,y) im Polygon liegt. Es wird ein Gerade/Ungerade-Algorithmus
verwendet, um dies herauszufinden.
• • • 373 • • •
Ÿ b o o l e a n c o n t a i n s ( Po i n t p ) Liefert t r u e , wenn der Punkt p im Polygon liegt. Ein Po i n t Objekt besitzt die Attribute x und y für die Koordinaten.
c l a s s j a v a . a wt . Gr a p h i c s Das erzeugte Polygon können wir mit speziellen Methoden, natürlich aus Gr a p h i c s , zeichnen. Ÿ v o i d d r a wPo l y g o n ( Po l y g o n )
Zeichnet das Polygon in der Vordergrundfarbe. Ÿ v o i d f i l l Po l y g o n ( Po l y g o n )
Zeichnet ein gefülltes Polygon.
12.6.2 N- Ecke zeichnen Bisher gibt es im Gr a p h i c s -Paket keine Funktion, um regelmäßige n-Ecke zu zeichnen. So eine Funktion ist aber leicht und schnell programmiert. Wir teilen dazu einfach einen Kreis in n Teile auf, und berechnen die x- und y-Koordinate der Punkte auf dem Kreis. Diese Punkte fügen wir einem Po l y g o n Objekt mittels der a d d Po i n t ( ) Methode zu. Eine private Funktion d r a wNe c k ( ) übernimmt diese Polygon-Erstellung. Der letzte Parameter der Funktion ist ein Wahrheitswert, der bestimmt, ob das n-Eck gefüllt werden soll oder nicht. Nun kann mit zwei öffentliche Funktionen ein nicht gefülltes bzw. gefülltes n-Eck gezeichnet werden.
Abbildung 5: Ein gefülltes 12-Eck Quellcode 12.f
nEck.java
i mp o r t j a v a . a wt . * ; i mp o r t j a v a . a wt . e v e n t . * ; p u b l i c c l a s s n Ec k e x t e n d s Fr a me { p u b l i c n Ec k ( ) { s e t Si z e ( 2 0 0 , 2 0 0 ) ; a d d Wi n d o wLi s t e n e r ( n e w Wi n d o wAd a p t e r ( ) { p u b l i c v o i d wi n d o wCl o s i n g ( Wi n d o wEv e n t e ) { Sy s t e m. e x i t ( 0 ) ; } }) ; • • 374 •• • •
} p r i v a t e v o i d d r a wNe c k ( Gr a p h i c s g , i n t x , i n t y , i n t r , i nt n, bool e a n f i l l e d ) { Po l y g o n p = n e w Po l y g o n ( ) ; f o r ( i n t i = 0 ; i < n ; i ++ ) p . a d d Po i n t ( ( i n t ) ( x + r * Ma t h . c o s ( i * 2 * Ma t h . PI / n ) ) , ( i n t ) ( y + r * Ma t h . s i n ( i * 2 * Ma t h . PI / n ) ) ) ; i f ( f i l l e d == t r u e ) g . f i l l Po l y g o n ( p ) ; el s e g . d r a wPo l y g o n ( p ) ; } / ** * Dr a ws a n - Ec k p o l y g o n wi t h t h e g i v e n p a r a mt e r */ p u b l i c v o i d d r a wNe c k ( Gr a p h i c s g , i n t x , i n t y , i n t r , i n t n ) { d r a wNe c k ( g , x , y , r , n , f a l s e ) ; } / ** * Dr a ws a f i l l e d n - Ec k p o l y g o n wi t h t h e g i v e n p a r a mt e r */ p u b l i c v o i d f i l l Ne c k ( Gr a p h i c s g , i n t x , i n t y , i n t r , i n t n ) { d r a wNe c k ( g , x , y , r , n , t r u e ) ; } p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { f i l l Ne c k ( g , 1 0 0 , 1 0 0 , 5 0 , 6 ) ; d r a wNe c k ( g , 1 0 0 , 1 0 0 , 6 0 , 6 ) ; } p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { n Ec k p o l y = n e w n Ec k ( ) ; p o l y . s h o w( ) ; } }
• • • 375 • • •
12.6.1 Vollschlanke Linien zeichnen In Zeichenprogramme und grafischen Präsentationen besteht häufig die Notwendigkeit die sonst nur so dünnen Standard-Linien etwas aufzupusten. Es sind also dickere Linien erwünscht und dies führt zu vielfältigen Problemen, die spontan nicht so sichtbar werden. Zunächst die Frage nach der Zeichentechnik. Die erste Möglichkeit ist, mehrere Linien übereinander zu zeichnen. Dieser Ansatz ist auf den ersten Blick der einfachste, doch der zweite Blick auf die Grafik zeigt, dass einige Löcher entstehen; die Linien sind nicht genau übereinander. Dies liegt an den Rechenfehlern der Linienfunktion. Diese Lösung scheidet somit aus, und wir entscheiden und für einen Linienzug, der gefüllt wird. Dies bleibt der einzige Ausweg, nur, diese Lösung ist nicht besonders schnell. Denn erst muss der Linienzug gezeichnet werden und anschließend folgt eine kostspielige Füllfunktion. Doch dann gibt es keine Probleme mit Löchern. Etwaige Schwierigkeiten, wie etwa eine 2 Pixel hoher Polygonzug, in dem eigentlich kein Platz mehr ist, muss auf die Leistungsfähigkeit der Füll-Methode verlagert werden. Das zweite Problem betrifft ist das Ende der Linien. Sollen diese abgerundet, spitz wie ein Pfeil oder wie eine Rampe aussehen? Oder soll die Linie, die dann einfach wie ein gedrehtes Rechteck aussehen? Ein Blick in die Grafikbibliotheken von Windows oder X11 zeigt, dass hier viele Arten existieren. Unsere folgende Funktion ist aber sehr einfach gebaut. Sie rundet nicht ab, sondern zeichnet das gedrehtes Rechteck. Eine dritte Unsicherheit ist bei der Definition der Endpunkte. Ist eine Linie 10 Pixel breit, so muss sichergestellt werden, wo denn der Startpunkt liegt. Liegt er in der Mitte oder, wenn etwa die Ränder mit einer Spitze gezeichnet sind, an diesen Punkten. Da unsere Methode sehr einfach ist, kümmern wir uns nicht darum und die Endpunkte liegen mittig. publ i c s t a t i c voi d d r a wTh i c k Li n e ( i n t x , i n t y , i n t x 2 , i n t y 2 , i n t t h i c k n e s s , Gr a p h i c s g ) { i n t b = Ma t h . r o u n d ( t h i c k n e s s / 2 ) , d e l t a x , d e l t a y ; doubl e a ngl e ; / / i f ( y 2 ==y ) a l p h a = 0 ; e l s e a n g l e = Ma t h . a t a n ( ( d o u b l e ) ( ( y 2 - y ) / ( x 2 - x ) ) ) ; d e l t a y = ( i n t ) Ma t h . r o u n d ( ( Ma t h . c o s ( a n g l e ) * b ) ) ; d e l t a x = ( i n t ) Ma t h . r o u n d ( ( Ma t h . s i n ( a n g l e ) * b ) ) ; Po l y g o n p = n e w Po l y g o n ( ) ; p. p. p. p.
a d d Po i a d d Po i a d d Po i a d d Po i
nt nt nt nt
( ( ( (
x- de l t x +d e l t x 2 +d e l x2- de l
a x, a x, t a x, t a x,
y +d e l t a y ) ; y- de l t a y ) ; y2- de l t a y ) ; y 2 +d e l t a y ) ;
g . f i l l Po l y g o n ( p ) ; }
Aus der Beschreibung am Anfang geht hervor, dass das Zeichnen von dicken Linien mit den gewünschten Zusätzen wie Ränder keine triviale Aufgabe ist. Schön ist, dass sich unter der Java 2 Plattform die Java 2D API um diese Aufgabe kümmert.
• • 376 •• • •
12.7 Zeichenket t en schreiben Die Methode, mit der Zeichen in verschiedenen Zeichensätzen (engl. Fonts) auf die Zeichenfläche gebracht werden, heißt d r a wSt r i n g ( ) . Diese Funktion besitzt drei Parameter: Zu schreibende Zeichenkette, x-Koordinate und y-Koordinate. d r a wSt r i n g ( ) zeichnet im aktuell eingestellten Zeichensatz und die Grundlinie (engl. Baseline) befindet sich auf der übergebenden y-Position. c l a s s j a v a . a wt . Gr a p h i c s Ÿ v o i d d r a wSt r i n g ( St r i n g , i n t x , i n t y )
Schreibt einen String in der aktuellen Farbe und dem aktuellen Zeichensatz. Die x und y-Werte bestimmen die Startpunkte der Grundlinie. Ÿ v o i d d r a wCh a r s ( c h a r d a t a [ ] , i n t o f f s e t , i n t l e n g t h , i n t x , i n t y )
Schreibt die Zeichenkette und bezieht die Daten aus einem Char-Feld. Ÿ v o i d d r a wBy t e s ( b y t e d a t a [ ] , i n t o f f s e t , i n t l e n g t h , i n t x , i n t y )
Schreibt die Zeichenkette und bezieht die Daten aus einem Byte-Feld.
12.7.1 Einen neuen Zeichensat z best im m en Die Funktion d r a wSt r i n g ( ) zeichnet immer im aktuellen Zeichensatz und um diesen zu ändern benutzen wir eine Funktion s e t Fo n t ( ) . Der Übergabeparameter ist ein Fo n t -Objekt, welches wir erst erzeugen müssen. Der Konstruktor von Fo n t ist durch verschiedene Parameter definiert. c l a s s j a v a . a wt . Fo n t i mp l e me n t s Se r i a l i z a b l e Ÿ Fo n t ( St r i n g Na me , i n t St i l , i n t Gr ö ß e ) Erzeugt ein Fo n t -Objekt. Na me
Die Namen des Zeichensatzes können von System zu System unterschiedlich sein. Unter WinNT, MacOs, Linux, Solaris und IRIX sind jedenfalls die Zeichensätze Mo n o s p a c e d (früher Co u r i e r ), Di a l o g , Sa n s Se r i f (früher He l v e t i c a ) und Se r i f (früher Ti me s Ro ma n ) erlaubt, unter MacOs kommt noch der Zeichensatz Ge n e v a hinzu. Vor Java 1.1 gab es noch den Zeichensatz Sy mb o l (bzw. Za p f Di n g b a t s ), der aber durch die Unicode-Zeichen abgedeckt wird.
St i l
Das Fo n t -Objekt definiert drei Konstanten, um die Schriftart fett und kursiv darzustellen. Die symbolischen Werte sind: Fo n t . PLAI N, Fo n t . BOLD und für einen nicht ausgezeichneten Schriftsatz Fo n t . PLAI N. Die Attribute können mit dem binären Oder verbunden werden, ein fetter und kursiver Zeichensatz, ist durch Fo n t . BOLD | Fo n t . I TALI C zu erreichen.
Gr ö ß e
Eine Angabe in Punkten, wie groß die Schrift sein soll. Ein Punkt entspricht etwa 1/72 Zoll (etwa 0,376 mm).
Ein üblicher Konstruktor ist zum Beispiel:
• • • 377 • • •
n e w Fo n t ( " Se r i f " , Fo n t . PLAI N, 1 4 )
und häufig wird dieser sofort in s e t Fo n t ( ) genutzt, so wie s e t Fo n t ( n e w Fo n t ( " Se r i f " , Fo n t . BOLD, 2 0 ) ) ;
12.7.2 Zeichensät ze des Syst em s erm it t eln Die Umsetzung der Namen auf die verschiedenen Rechnerplattformen übernimmt Java, so heißt Helvetica unter Windows Arial (aber mit den selben Laufweiten). Der Grund dafür liegt bei den Herstellern der Zeichensätze. Denn diese sind nicht frei und der Name Helvetica ist von Adobe geschützt. Doch auch unter X11 heißt Helvetica nicht Helvetica. Da die verschiedenen ZeichensatzHersteller den Namen Helvetica aber kaufen können, ist der Original-Zeichensatz unter X11 AdobeHelvetica. Die Firma Adobe war so gnädig und hat die Zeichensätze als Type-1 Schriftarten beigelegt. Type-1 Schriftarten sind unter X11 relativ neu, denn erst als von IBM der Server-Zusatz programmiert wurde, konnten Type-1 Schriften benutzt werden. Vorher wurden die Anwender immer mit kleinen Klötzen abgefertigt, wenn die Schriftgröße einmal zu hoch gewählt wurde. Leider ist dies bei einigen Zeichensätzen immer noch der Fall. Selbst Star-Office unter X11 hat darunter zu kämpfen. Und wir auch, verlangen wir einen Zeichensatz, der nur als Bitmap in den Standardgrößen gerastert ist. Um herauszufinden, welche Zeichensätze auf einem System installiert sind, kann die g e t Fo n t Li s t ( ) Methode der Klasse To o l k i t bemüht werden.
a b s t r a c t c l a s s j a v a . a wt . To o l k i t Ÿ St r i n g [ ] g e t Fo n t Li s t ( )
Gibt die Namen der verfügbaren Zeichensätze zurück. Ÿ Fo n t Me t r i c s g e t Fo n t Me t r i c s ( Fo n t )
Gibt die Font-Metriken des Bildschirm-Zeichensatzes zurück. Folgendes Codesegment zeigt die Implementierung einer Schleife, das alle Zeichensatznamen ausgibt. Wir müssen kein Fenster geöffnet haben, um die Zeichensätze abzurufen. Quellcode 12.g
ListFont.java
i mp o r t j a v a . a wt . * ; c l a s s Li s t Fo n t { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { / / h e r k ö mml i c h St r i n g f o n t s [ ] = To o l k i t . g e t De f a u l t To o l k i t ( ) . g e t Fo n t Li s t ( ) ; f o r ( i n t i = 0 ; i < f o n t s . l e n g t h ; i ++ ) Sy s t e m. o u t . p r i n t l n ( f o n t s [ i ] ) ;
• • 378 •• • •
Sy s t e m. o u t . p r i n t l n ( ) ; / / Se i t 1 . 2 St r i n g a l l _ f o n t s [ ] = Gr a p h i c s En v i r o n me n t . g e t Lo c a l Gr a p h i c s En v i r o n me n t ( ) . g e t Av a i l a b l e Fo n t Fa mi l y Na me s ( ) ; f o r ( i n t i = 0 ; i < a l l _ f o n t s . l e n g t h ; i ++ ) Sy s t e m. o u t . p r i n t l n ( a l l _ f o n t s [ i ] ) ; } }
Ein neuer Weg Neben der g e t Fo n t Li s t ( ) Methode des To o l k i t ist seit Java 1.2 eine weitere Methode hinzugekommen: g e t Av a i l a b l e Fo n t Fa mi l y Na me s ( ) . Sie ist auf einem Gr a p h i c s En v i r o n me n t definiert und dies ist eine Ausprägung einer grafischen Oberfläche oder eines Druckers. Die Methode g e t Av a i l a b l e Fo n t Fa mi l y Na me s ( ) lässt sich auf einer Lokalen aufrufen. Da jedes Fo n t Objekt die t o St r i n g ( ) Methode passend implementiert, sehen wir den Namen der Zeichensätze. So folgt nach dem Aufruf des Programms (jedenfalls bei mir) die Ausgabe für den ersten Teil: Di a l o g Sa n s Se r i f Se r i f Mo n o s p a c e He l v e t i c a Ti me s Ro ma n Co u r i e r Di a l o g I n p u t Za p f Di n g b a t s
Die Funktion g e t To o l k i t ( ) gehört zur Klasse Fr a me – sie erbt die Methode von Co mp o n e n t – , so dass wir nicht zwingend die Funktion To o l k i t . g e t De f a u l t To o l k i t ( ) von der statischen Klasse To o l k i t verwenden müssen. St r i n g f o n t s [ ] = g e t To o l k i t ( ) . g e t Fo n t Li s t ( ) ;
a b s t r a c t c l a s s j a v a . a wt . Co mp o n e n t i mp l e me n t s I ma g e Ob s e r v e r , Me n u Co n t a i n e r , Se r i a l i z a b l e Ÿ To o l k i t g e t To o l k i t ( )
Gibt den Toolkit des Fensters zurück.
• • • 379 • • •
Der akt uell verwendet e Zeichensat z Ist im Programm lediglich der aktuell verwendete Zeichensazt gefragt, können wir g e t Fo n t ( ) von der Gr a p h i c s Klasse nutzen. c l a s s j a v a . a wt . Gr a p h i c s Ÿ Fo n t g e t Fo n t ( )
Liefert den aktuellen Zeichensatz.
12.7.1 Die Klasse Font Met rics Jedes Fo n t Objekt beinhaltet lediglich Information über Schriftsatzfamilie, Schriftsatznamen, Größe und Stil. Sie bietet keinen Zugriff auf Abmessungen des Zeichensatzes. Um diese Daten aufzuspüren, erzeugen wir ein Fo n t Me t r i c s Objekt. Es verwaltet metrische Informationen, die mit einer Schriftart verbunden ist. Dazu gehören Ober- und Unterlänge, Schrifthöhe und Zeilenabstand. Um das Fo n t Me t r i c Objekt des aktuellen Grafikkontextes zu nutzen, findet sich eine Methode g e t Fo n t ( ) . Diese Methode ist aber von Gr a p h i c s und nicht zu verwechseln mit der g e t Fo n t ( ) Methode von Fo n t Me t r i c s , die das gleiche macht aber in einem anderem Objekt liegt. In der p a i n t ( ) Methode kann also mittels Fo n t Me t r i c s f m = g e t Fo n t Me t r i c s ( g e t Fo n t ( ) ) ;
auf die Metriken des aktuellen Zeichensatzes zugegriffen werden. c l a s s j a v a . a wt . Gr a p h i c s Ÿ Fo n t Me t r i c s g e t Fo n t Me t r i c s ( )
Liefert die Font-Metriken zum aktuellen Zeichensatz. Ÿ Fo n t Me t r i c s g e t Fo n t Me t r i c s ( Fo n t f ) Liefert die Font-Metriken für den Zeichensatz f .
Die Klasse Fo n t Me t r i c s bietet die folgenden Methoden an, wobei sich alle Angaben auf das jeweilige Zeichensatzobjekt beziehen. Beziehen sich die Rückgabeparameter auf die Zeichengröße, so ist die Angabe immer in Punkten. c l a s s j a v a . a wt . Fo n t Me t r i c s i mp l e me n t s Se r i a l i z a b l e Ÿ i n t b y t e s Wi d t h ( b y t e [ ] , i n t , i n t ) i n t c h a r s Wi d t h ( c h a r [ ] , i n t , i n t )
Gibt die Breite aller Zeichen des Feldes zurück. Ÿ i n t c h a r Wi d t h ( i n t | c h a r )
Liefert die Breite zu einem Zeichen.
• • 380 •• • •
Ÿ i n t g e t As c e n t ( )
Gibt den Abstand von der Grundlinie zur oberen Grenze (Oberlänge genannt) zurück. Ÿ i n t g e t De s c e n t ( )
Gibt den Abstand von der Grundlinie zur unteren Grenze (Unterlänge) zurück. Ÿ i n t g e t Fo n t ( )
Liefert aktuellen Zeichensatz. Ÿ i n t g e t He i g h t ( )
Gibt die Schrifthöhe einer Textzeile in Pixel zurück. Sie berechnet sich aus Zeilendurchschuss + Oberlänge + Unterlänge. Ÿ i n t g e t Le a d i n g ( )
Gibt Zwischenraum zweier Zeilen zurück. Ÿ i n t g e t Ma x Ad v a n c e ( )
Liefert die Breite des breitesten Zeichens. Ÿ i n t g e t Ma x As c e n t ( )
Liefert das Maximum aller Oberlängen in Pixeln. Ÿ i n t g e t Ma x De s c e n t ( )
Liefert das Maximum aller Unterlängen in Pixeln. Vor Java 1.1: g e t Ma x De c e n t ( ) . Ÿ i n t [ ] g e t Wi d t h s ( )
Liefert in einem Ganzzahlfeld die Breiten der Zeichen zurück. Das Feld ist 256 Elemente groß. Ÿ i n t s t r i n g Wi d t h ( St r i n g )
Gibt die Breite der Zeichenkette zurück, wenn diese gezeichnet würde.
Einen St ring unt erst reichen Wir wollen nun s t r i n g Wi d t h ( ) benutzen, um unterstrichenen Text darzustellen. Dafür gibt es keine Standardfunktion. Aber schreiben wir uns einfach eine Methode, die die Koordinaten sowie den String annimmt Die Methode d r a wUn d e r l i n e d St r i n g ( ) schreibt mit d r a wSt r i n g ( ) die Zeichenkette. d r a wLi n e ( ) bekommt die Breite der Linie durch die Breite der Zeichenkette. Die Linie ist zwei Punkte unter der Baseline. Natürlich achtet so eine kleine Funktion nicht auf das Aussparen von Buchstaben, die unter der Baseline liegen. Die Buchstaben ›y‹ oder ›q‹ sind dann unten gnadenlos durchgestrichen. d r a wUn d e r l i n e d St r i n g ( Gr a p h i c s g , i n t x , i n t y , St r i n g s ) { g . d r a wSt r i n g ( s , 1 0 , 1 0 ) ; g . d r a wLi n e ( x , y +2 , x +g e t Fo n t Me t r i c s ( g e t Fo n t ( ) ) . s t r i n g Wi d t h ( s ) , y +2 ) ; }
12.7.2 Logische und nat ive Font nam en in font .propert ies Wir haben bei der Benutzung der Zeichensätze für das AWT bisher nur mit den logischen Namen Dialog, SansSerif, Serif, Monospaced und DialogInput gearbeitet. Die logischen Fontnamen müssen jedoch unter einem Betriebssystem in native Zeichensätze übersetzt werden. Zur Übersetzung dient die Datei font.properties, die im Unterverzeichnis jre\lib der Installation des JDK steht. (Das Verzeichnis wird aus der Umgebungsvariablen ›java.home‹ genommen.) Diese Datei wird dann von der internen Java Klasse WFo n t Pe e r gelesen und vorverarbeitet. WFo n t Pe e r erweitert die • • • 381 • • •
abstrakte Klasse Pl a t f o r mFo n t und ist die Implementierung in der Windows Welt. WFo n t Pe e r ist im Gegensatz zur Klasse Pl a t f o r mFo n t klein, die einzige Funktionalität, die sie hinzufügt ist, dass sie die passenden Konverter zur Laufzeit lädt. PlatformFont enthält eine native Methode i n i t I Ds ( ) , die dann unter dem Sun JDK in der C++ Klasse Awt Fo n t programmiert ist. Bei einer Zeichensatzanfrage, die wir hier nicht diskutieren, kann dann später der passende Font geladen werden, der dann durch die Datei festgelegt ist. Der Aufbau der font.properties Datei erinnert an die Notation einer Properties Datei. Sie gliedert sich in fünf große Bereiche (und noch ein paar kleine Definitionen am Rande): n Zuweisen der logischen zu den nativen Zeichensätzen mit Kodierung n Zeichensatz Aliasnamen n Übertragung der alten Zeichensatznamen n Definition der Umwandlungsroutinen n Ausschluss von Bereichen Es beginnt mit einer Liste von Fontdefinitionen. Zu einem Font wird in den Stilen normal, fett, kursiv und fett kursiv ein zugehöriger Zeichensatz definiert. Exemplarisch ist der Teil der Datei am Zeichensatz dialog wiedergeben. d i a l o g . 0 =Ar i a l , ANSI _ CHARSET d i a l o g . 1 =Wi n g Di n g s , SYMBOL_ CHARSET, NEED_ CONVERTED d i a l o g . 2 =Sy mb o l , SYMBOL_ CHARSET, NEED_ CONVERTED
Der logische Name dialog ist definiert als Paar mit dem Namen des nativen Zeichensatzes und der Kodierung. Für dialog ist demnach unter Windows der Zeichensatz Arial eingestellt und die Zeichenkodierung ist ANSI_CHARSET. Java definiert noch weitere Kodierungen, darunter gehören DEFAULT_CHARSET, SYMBOL_CHARSET , SHIFTJIS_CHARSET, GB2312_CHARSET, HANGEUL_CHARSET, CHINESEBIG5_CHARSET, OEM_CHARSET, JOHAB_CHARSET, HEBREW_CHARSET , ARABIC_CHARSET, GREEK_CHARSET, TURKISH_CHARSET, VIETNAMESE_CHARSET, THAI_CHARSET, EASTEUROPE_CHARSET, RUSSIAN_CHARSET, MAC_CHARSET und BALTIC_CHARSET. So ist für die russische Windows Variante die Zeichensatz-Datei ›font.properties.ru‹ zuständig und dort findet sich für d i a l o g folgende Zeile: d i a l o g . 0 =Ar i a l , RUSSI AN_ CHARSET
Beziehungsweise für das Chinesische Windows in ›font.properties.zh.NT4.0‹: d i a l o g . p l a i n . 1 =\ u 5 b 8 b \ u 4 f 5 3 , GB2 3 1 2 _ CHARSET
Die beiden Unicode Zeichen \ u 5 b 8 b \ u 4 f 5 3 beziehen sich auf den Chinesischen Zeichensatz mit dem Dateinamen ›SIMSUN.TTC‹. Sollte in der Datei ein falscher Zeichensatz definiert werden, so wird dieser als ANSI_CHARSET angenommen. Und so beginnt die Datei ›font.properties‹ mit: # @( # ) f o n t . p r o p e r t i e s 1 . 8 9 8 / 0 6 / 2 6 # # AWT Fo n t d e f a u l t Pr o p e r t i e s f o r Wi n d o ws # d i a l o g . 0 =Ar i a l , ANSI _ CHARSET d i a l o g . 1 =Wi n g Di n g s , SYMBOL_ CHARSET, NEED_ CONVERTED • • 382 •• • •
d i a l o g . 2 =Sy mb o l , SYMBOL_ CHARSET, NEED_ CONVERTED . . . n u n f o l g e n d i e De f i n i t i o n e n f ü r d i a l o g , d i a l o g i n p u t , s e r i f , s a n s s e r i f , mo n o s p a c e d
Die Java Klasse Pl a t f o r mFo n t muss nun also entscheiden, wie der Dateiname heißt, der die Zeichensatzdefinitionen enthält. Dazu baut der einen Namen aus font.propoperies + language + region + encoding zusammen, wobei l a n g u a g e = Sy s t e m. g e t Pr o p e r t y ( " u s e r . l a n g u a g e " , " e n " ) ; r e g i o n = Sy s t e m. g e t Pr o p e r t y ( " u s e r . r e g i o n " ) ; e n c o d i n g = Sy s t e m. g e t Pr o p e r t y ( " f i l e . e n c o d i n g " ) ;
optionale Strings sind. Für unsere europäische Java-Version dürfte die Datei einfach ›font.properites‹ heißen. Neben diesen Festlegungen für die neuen Zeichensätze sind zur Haltung der Kompatibilität noch Übersetzungen für die alten Fonts in der font.properties Datei angegeben – diese sind allerdings auskommentiert, denn Pl a t f o r mFo n t fügt diese automatisch zu einem Properties Objekt hinzu und lädt dann die Beschreibungsdatei. # f o r b a c k wo r d c o mp a t i b i l i t y t i me s r o ma n . 0 =Ti me s Ne w Ro ma n , ANSI _ CHARSET h e l v e t i c a . 0 =Ar i a l , ANSI _ CHARSET c o u r i e r . 0 =Co u r i e r Ne w, ANSI _ CHARSET z a p f d i n g b a t s . 0 =Wi n g Di n g s , SYMBOL_ CHARSET
Anschließend folgt noch eine Festlegung der physikalischen Zeichensatznamen unter Windows. # f o n t f i l e n a me s f o r r e d u c e d i n i t i a l i z a t i o n t i me f i l e n a me . Ar i a l =ARI AL. TTF ... f i l e n a me . Ti me s _ Ne w_ Ro ma n _ Bo l d _ I t a l i c =TI MESBI . TTF f i l e n a me . Wi n g Di n g s =WI NGDI NG. TTF f i l e n a me . Sy mb o l =SYMBOL. TTF
Nun die # DEFAULT f o n t d e f i n i t i o n # d e f a u l t . c h a r =2 7 5 1 # St a t i c Fo n t Ch a r s e t i n f o . # f o n t c h a r s e t . d i a l o g . 1 =s u n . a wt . wi n d o ws . Ch a r To By t e Wi n g Di n g s f o n t c h a r s e t . d i a l o g . 2 =s u n . a wt . Ch a r To By t e Sy mb o l . . . n u n f o l g e n d i e De f i n i t i o n e n f ü r f o n t c h a r s e t . d i a l o g i n p u t , f o n t c h a r s e t . s e r i f , f o n t c h a r s e t . s a n s s e r i f u n d f o n t c h a r s e t . mo n o s p a c e d
• • • 383 • • •
Definiert werden durch die Zuweisung Konvertierungsklassen. Ch a r To By t e Wi n g Di n g s und Ch a r To By t e Sy mb o l sind beides Erweiterungen von Ch a r To By t e I SO8 8 5 9 _ 1 (der wiederum ein Ch a r To By t e Co n v e r t e r – abstrakt – ist), einer Klasse, die für die Konvertierung von Kodierungen zuständig ist. Es folgt noch eine Bereichsdefinition, wo keine Zeichen definiert sind. # Ex c l u s i o n Ra n g e i n f o . # e x c l u s i o n . d i a l o g . 0 =0 1 0 0 - f f f f e x c l u s i o n . d i a l o g i n p u t . 0 =0 1 0 0 - f f f f e x c l u s i o n . s e r i f . 0 =0 1 0 0 - f f f f e x c l u s i o n . s a n s s e r i f . 0 =0 1 0 0 - f f f f e x c l u s i o n . mo n o s p a c e d . 0 =0 1 0 0 - f f f f # c ha r s e t f or t e xt i nput # i n p u t t e x t c h a r s e t =ANSI _ CHARSET
Wenn nun ein Font benötigt wird, so wird ein Pl a t f o r mFo n t Objekt erzeugt. Jedes Pl a t f o r mFo n t Objekt steht für einen Zeichensatz. In einem Vector wird nun ein Fo n t De s c r i p t o r verwaltet, der die einzelnen Ausprägungen sichert. Dies speichert Name des Zeichensatzes, ein Ch a r To By t e Co n v e r t e r Objekt und die Bereichsdefinition.
12.8 Clipping- Operat ionen Alle primitiven Zeichenoperationen wirken sich auf den gesamten Bildschirm aus und sind nicht auch Bereiche eingeschränkt. Wenn wir dies erreichen wollen, setzen wir einen sogenannten Clipping-Bereich, aus dem dann nicht mehr heraus gezeichnet wird. Leider war in der Vergangenheit die Implementierung dieses Clippings immer etwas anfällig gegen Fehler, so dass eine falsche Zeichnung durchaus vorkommen kann. Wer da auf Nummer sicher gehen möchte, sollte ein Offscreen-Bild anlegen, die Operationen in dieses Image machen und dann das Bild zeichnen. Doch bleiben wir beim herkömmlichen Clipping. Dies ist eine Eigenschaft des aktuellen Gr a p h i c Objekts. Mit der Methode c l i p Re c t ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t ) lässt sich dieser Bereich einschränken. Dann erfolgen alle Operationen in diesem Bereich. Das folgende Programm erzeugt zwei Clipping-Bereiche und füllt einen sehr großen Bereich aus, der aber nicht sichtbar ist. Quellcode 12.h
ClipDemo.java
i mp o r t j a v a . a wt . * ; p u b l i c c l a s s Cl i p De mo e x t e n d s Fr a me { p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { Gr a p h i c s g c o p y = g . c r e a t e ( ) ; / / Cl i p p i n g a u f • • 384 •• • •
g. g. g. g. g.
c l i p Re c t ( s e t Co l o r ( f i l l Re c t ( s e t Co l o r ( d r a wOv a l (
100, 100, 100, 100 ) ; Co l o r . o r a n g e ) ; 0, 0, 500, 500 ) ; Co l o r . b l a c k ) ; 150, 100, 100, 100 ) ;
/ / Zwe i t e r Cl i p p i n g Be r e i c h g . c l i p Re c t ( 2 5 0 , 2 5 0 , 5 0 , 5 0 ) ; g . s e t Co l o r ( Co l o r . b l u e ) ; g . f i l l Re c t ( 0 , 0 , 5 0 0 0 , 5 0 0 0 ) ; / / Di e u r s p r ü n g l i c h e Gr ö ß e z u r ü c k s e t z e n g c o p y . s e t Co l o r ( Co l o r . y e l l o w ) ; g c o p y . f i l l Re c t ( 5 0 , 5 0 , 2 0 , 5 0 ) ; gc opy. di s pos e ( ) ; } publ i { Cl i c d. c d. }
c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) p De mo c d = n e w Cl i p De mo ( ) ; s e t Si z e ( 4 0 0 , 4 0 0 ) ; s h o w( ) ;
}
Den alt en Zust and für den Graphics wieder herst ellen Für die Zeichenoperationen im Clipping-Bereich gibt es noch eine alternative Implementierung. Diese verzichtet auf die Kopie des Grafikkontextes mittels c r e a t e ( ) am Anfang und setzt zum Schluss vor die Stelle von g c o p y ein g e t Gr a p h i c s ( ) mit dem sich der alte Kontext wieder herstellen lässt. Dann können wir wieder mit g . d r a wXXX( ) arbeiten und g c o p y ist überflüssig.
Alt ernat ive Form en Mit s e t Cl i p ( ) können alternativ zu den rechteckigen Formen auch beliebige Sh a p e Objekte die Clipping-Form vorgeben. Nachfolgende p a i n t ( ) Methode benutzt als Beschnitt ein Dreieck. p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { Po l y g o n p = n e w Po l y g o n ( ne w i nt [ ] {200, 100, 300}, ne w i nt [ ] {100, 300, 300}, 3 ); g . s e t Cl i p ( p ) ; g . s e t Co l o r ( Co l o r . o r a n g e ) ; g . f i l l Re c t ( 0 , 0 , 5 0 0 , 5 0 0 ) ; • • • 385 • • •
}
Bei alten Implementierungen funktioniert dies nicht. Auf der Konsole erscheint dann eine Fehlermeldung der Art: j a v a . l a n g . I l l e g a l Ar g u me n t Ex c e p t i o n : s e t Cl i p ( Sh a p e ) o n l y s u p p o r t s Re c t a ngl e obj e c t s
Verdeckt e Bereiche und schnelles Bildschirm erneuern Clipping-Bereiche sind nicht nur zum Einschränken der primitiv-Operationen sinnvoll. Bei Bereichsüberdeckungen in Fenster liefern sie wertvolle Informationen über den neuzuzeichnenden Bereich. Bei einer guten Applikation wird nur der Teil wirklich neugezeichnet, der auch überdeckt wurde. So lässt sich Rechenzeit sparen. Wir schauen uns dies einmal in einem Beispiel und Ablauf an: p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { Re c t a n g l e r = g . g e t Cl i p Bo u n d s ( ) ; Sy s t e m. o u t . p r i n t l n ( r ) ; }
Das Programm erzeugt etwa j j j j j j j j j j j j j j j
a va . a va . a va . a va . a va . a va . a va . a va . a va . a va . a va . a va . a va . a va . a va .
a wt a wt a wt a wt a wt a wt a wt a wt a wt a wt a wt a wt a wt a wt a wt
. . . . . . . . . . . . . . .
Re c t Re c t Re c t Re c t Re c t Re c t Re c t Re c t Re c t Re c t Re c t Re c t Re c t Re c t Re c t
a ngl a ngl a ngl a ngl a ngl a ngl a ngl a ngl a ngl a ngl a ngl a ngl a ngl a ngl a ngl
e [ x =4 , y =2 3 , wi d t h =3 9 2 , h e i g h t =3 7 3 ] e [ x =1 0 4 , y =8 7 , wi d t h =2 9 2 , h e i g h t =3 0 9 ] e [ x =1 0 4 , y =8 7 , wi d t h =2 8 6 , h e i g h t =2 1 1 ] e [ x =1 0 4 , y =8 7 , wi d t h =2 4 3 , h e i g h t =1 9 6 ] e [ x =1 0 4 , y =8 7 , wi d t h =2 2 1 , h e i g h t =2 1 9 ] e [ x =1 0 1 , y =8 9 , wi d t h =2 2 1 , h e i g h t =2 1 9 ] e [ x =7 5 , y =9 9 , wi d t h =2 3 5 , h e i g h t =2 2 9 ] e [ x =6 5 , y =1 2 1 , wi d t h =2 2 1 , h e i g h t =2 1 9 ] e [ x =5 7 , y =1 3 1 , wi d t h =2 2 5 , h e i g h t =2 2 2 ] e [ x =5 4 , y =1 3 6 , wi d t h =2 2 1 , h e i g h t =2 1 9 ] e [ x =2 5 5 , y =1 5 1 , wi d t h =1 , h e i g h t =2 1 9 ] e [ x =3 4 , y =1 5 1 , wi d t h =2 2 1 , h e i g h t =2 1 9 ] e [ x =1 0 2 , y =1 6 3 , wi d t h =2 2 1 , h e i g h t =2 1 9 ] e [ x =1 8 6 , y =1 7 9 , wi d t h =2 1 0 , h e i g h t =2 1 7 ] e [ x =3 6 6 , y =2 2 6 , wi d t h =3 0 , h e i g h t =1 7 0 ]
Hieraus lassen sich verschiedene Fensteroperationen ableiten. Ich habe ein fremdes Fenster über das Java Fenster geschoben und dann das fremde Fenster verkleinert. Die Re c t a n g l e Informationen geben Aufschluss über die Größe der neuzuzeichnenden Bereiche. Haben wir schon daran gedacht, die Information in einem I ma g e Objekt abzulegen, lässt sich wunderbar d r a wI ma g e ( I ma g e i mg , i n t d x 1 , i n t d y 1 , i n t d x 2 , i n t d y 2 , i n t s x 1 , i n t s y 1 , i n t s x 2 , i n t s y 2 , I ma g e Ob s e r v e r o b s e r v e r ) nutzen. Hier müssen wir die Werte aus dem Re c t a n g l e auslesen und in d r a wI ma g e ( ) übertragen. g e t Cl i p Bo u n d s ( ) liefert ein Re c t a n g l e Objekte, dessen Werte für d r a wI ma g e ( ) nötig sind. Da jedoch auch beliebige Formen nötig sind, liefert hier g e t Cl i p ( ) ein Shape Objekt. g e t Cl i p Re c t ( ) ist die veraltete Methode zu g e t Cl i p Bo u n d s ( ) , sonst aber identisch. Die Methode g e t Cl i p Bo u n d s ( Re c t a n g l e ) –
• • 386 •• • •
einig der wenigen nicht-abstrakten Methoden in Gr a p h i c s – legt die Informationen im übergebenen Re c t a n g e Objekt ab, welches auch zurückgeliefert wird. Sie ruft nur g e t Cl i p Bo u n d s ( ) auf und überträgt die vier Attribute in das Rechteck.
12.9 Farben Der Einsatz von Farben in Java-Programmen, ist Dank der Co l o r -Klasse einfach. Die Klasse stellt eine Vielzahl von Routinen zur Verfügung, mit denen Color-Objekte erzeugt und manipuliert werden können. c l a s s j a v a . a wt . Co l o r i mp l e me n t s Pa i n t , Se r i a l i z a b l e Ÿ Co l o r ( f l o a t r , f l o a t g , f l o a t b )
Erzeugt ein Color-Objekt mit den Grundfarben Rot, Grün und Blau. Die Werte müssen im Bereich 0.0 bis 1.0 sein. Ÿ Co l o r ( i n t r , i n t g , i n t b ) Erzeugt ein Co l o r -Objekt mit den Grundfarben Rot, Grün und Blau. Die Werte müssen im
Bereich 0 bis 255 liegen. Ÿ Co l o r ( i n t r g b )
Erzeugt ein Color-Objekt aus dem r g b -Wert, der die Farben Rot, Grün und Blau kodiert. Der Rot-Anteil befindet sich unter den Bits 16 bis 23, der Grünanteil in 8 bis 15 und der Blauanteil in 0 bis 7. Da ein Integer immer 32 Bit breit ist, ist jede Farbe durch ein Byte (8 Bit) repräsentiert. Eine private Funktion t e s t Co l o r Va l u e Ra n g e ( ) der Co l o r -Klasse überprüft, ob die Werte tatsächlich zwischen 0,0 und 1,0 (erster Fall) oder zwischen 0 und 255 (zweiter Fall) liegen. Wenn nicht, wird eine I l l e g a l Ar g u me n t Ex c e p t i o n ausgelöst. Im dritten Fall ist es egal, ob in den oberen acht Bit der Integer-Zahl die Farbinformationen. Sonstige Werte werden einfach nicht betrachtet und mit einem Alpha-Wert gleich 255 überschrieben, So zeigt es auch der Einzeiler aus dem Quelltext. p u b l i c Co l o r ( i n t r g b ) { va l ue = 0xf f 000000 | r gb; }
c l a s s j a v a . a wt . Gr a p h i c s Ÿ v o i d s e t Co l o r ( Co l o r )
Setzt die aktuelle Farbe, die dann von den Zeichenfunktionen umgesetzt werden. Ÿ Co l o r g e t Co l o r ( )
Liefert die aktuelle Farbe. Ÿ v o i d s e t XORMo d e ( Co l o r ) Setzt die Pixel-Operation auf XOR. Abwechselnde Punkte werden in der aktuellen Farbe und der mit dieser Funktion gesetzten XOR-Farbe gesetzt.
• • • 387 • • •
D ie m e n sch lich e Fa r bw a h r n e h m u n g Wir Menschen unt er schieden Far ben nach dr ei Eigenschaft en: Far bt on, Helligk eit und Sät t igung. Der Mensch k ann et w a 200 Far bt öne unt er scheiden. Diese w er den dur ch die Wellenlänge des Licht es best im m t . Die Licht int ensit ät und Em pfindlichk eit unser er Rezept or en lässt uns et w a 500 Helligk eit sst ufen unt er scheiden. Bei der Sät t igung handelt es sich um eine Mischung m it w eißem Licht . Hier er k ennt en w ir et w a 20 St ufen. Dam it k ann unser v isuelles Sy st em et w a zw ei Millionen ( 200x 500x 20) Far bnuancen unt er scheiden.
12.9.1 Zufällige Farbblöcke zeichnen Um einmal die Möglichkeiten der Farbgestaltung zu beobachten, betrachten wir die Ausgabe eines Programms, welches Rechtecke mit wahllosen Farben anzeigt. Quellcode 12.i
ColorBox.java
i mp o r t j a v a . a wt . * ; i mp o r t j a v a . a wt . e v e n t . * ; p u b l i c c l a s s Co l o r Bo x e x t e n d s Fr a me { p u b l i c Co l o r Bo x ( ) { s u p e r ( " Ne o p l a s t i z i s mu s " ) ; s e t Si z e ( 3 0 0 , 3 0 0 ) ; a d d Wi n d o wLi s t e n e r ( n e w Wi n d o wAd a p t e r ( ) { p u b l i c v o i d wi n d o wCl o s i n g ( Wi n d o wEv e n t e ) { Sy s t e m. e x i t ( 0 ) ; } }) ; } f i n a l p r i v a t e i n t r a n d o m( ) { r e t u r n ( i n t ) ( Ma t h . r a n d o m( ) * 2 5 6 ) ; } p u b l i c v o i d p a i n t ( Gr a p h i c s g r ) { f o r ( i n t y = 2 0 ; y < g e t Si z e ( ) . h e i g h t - 2 5 ; y += 3 0 ) f o r ( i n t x = 4 0 ; x < g e t Si z e ( ) . wi d t h - 2 5 ; x += 3 0 ) { i n t r = r a n d o m( ) , g = r a n d o m( ) , b = r a n d o m( ) ; gr . gr . gr . gr . } }
• • 388 •• • •
s e t Co l o r ( f i l l Re c t ( s e t Co l o r ( d r a wRe c t (
n e w Co l o r ( r , g , b ) ) ; y, x, 25, 25) ; Co l o r . b l a c k ) ; y- 1, x- 1, 25, 25 ) ;
p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { n e w Co l o r Bo x ( ) . s h o w( ) ; } }
Abbildung 6: Programmierter Neoplastizismus Das Fenster der Applikation hat eine gewisse Größe, die wir mit s i z e ( ) in der Höhe und Breite abfragen. Anschließend erzeugen wir Blöcke, die mit einer zufälligen Farbe gefüllt sind. f i l l Re c t ( ) übernimmt diese Aufgabe. Da die gefüllten Rechtecke immer in der Vordergrundfarben gezeichnet werden, setzen wir den Zeichenstift durch die Funktion s e t Co l o r ( ) , die natürlich Element-Funktion von j a v a . a wt . Gr a p h i c s ist. Entsprechend gibt es eine korrespondierende Funktion g e t Co l o r ( ) , die die aktuelle Vordergrundfarbe als Co l o r Objekt zurückgibt. Diese Funktion darf nicht mit den Funktionen g e t Co l o r ( St r i n g ) beziehungsweise g e t Co l o r ( St r i n g , Co l o r ) aus der Co l o r -Klasse verwechselt werden.
12.9.1 Farbbereiche zurückgeben Mitunter müssen wir den umgekehrten Weg gehen und von einem gegebenen Co l o r Objekt wieder an die Rot/Grün/Blau-Anteile kommen. Dies ist einfach, jedoch bietet die Funktionsbibliothek entsprechendes c l a s s j a v a . a wt . Co l o r i mp l e me n t s Pa i n t , Se r i a l i z a b l e Ÿ i n t g e t Re d ( ) , i n t g e t Gr e e n ( ) , i n t g e t Bl u e ( )
Liefert Rot, Grün und Blau-Anteil des Farb-Objekts. Ÿ i n t g e t RGB( ) Gibt die RGB-Farbe als Ganzzahl kodiert zurück. • • • 389 • • •
12.9.2 Vordefiniert e Farben Wenn wir Farben benutzen wollen, dann sind schon viele Werte vordefiniert (wie im vorausgehenden Beispiel die Farbe Rot). Weitere sind: b l a c k , b l u e , c y a n , d a r k Gr a y , g r a y , g r e e n , l i g h t Gr a y , ma g e n t a , o r a n g e , p i n k , wh i t e und y e l l o w. In der Klasse j a wa . a wt . Co l o r sind dazu viele Zeilen der Form / ** * Th e c o l o r wh i t e . */ p u b l i c f i n a l s t a t i c Co l o r wh i t e = n e w Co l o r ( 2 5 5 , 2 5 5 , 2 5 5 ) ;
platziert. Nachfolgend zeigt die Tabelle die Wertbelegung für die Farbtupel. Farbname
Rot
Grün
Blau
wh i t e
255
255
255
bl a c k
0
0
0
l i g h t Gr a y
192
192
192
d a r k Gr a y
128
128
128
r ed
255
0
0
gr e e n
0
255
0
bl ue
0
0
255
ye l l ow
255
255
0
Pu r p l e
255
0
255
Tabelle: Farbanteile für die vordefinierten Standardfarben
12.9.3 Farben aus Hexadezim alzahlen erzeugen Um eine Farbbeschreibung im hexadezimalen Format in einzelne Farbkomponenten der Co l o r Klasse zu zerlegen, also zum Beispiel von FFFFFF nach (255,255,255), gibt es zwei einfache und elegante Wege: Zum einen über die Über die Wrapper-Klasse I n t e g e r . Die folgende Zeile erzeugt aus dem String c o l o r He x St r i n g ein Co l o r Objekt. Co l o r c o l o r = n e w Co l o r ( I n t e g e r . p a r s e I n t ( c o l o r He x St r i n g , 1 6 ) ) ;
Eine andere Möglichkeit ist noch viel eleganter, denn es stellt uns die Co l o r -Klasse eine einfache Routine bereit: Co l o r c o l o r = Co l o r . d e c o d e ( " # " + c o l o r He x St r i n g ) ; d e c o d e ( St r i n g ) verlangt eine 24-Bit-Integer-Zahl als String codiert. Durch das Hash-Symbol
und dem Plus erzeugen wir ein String-Objekt, welches als Hexadezimalzahl bewertet wird. c l a s s j a v a . a wt . Co l o r i mp l e me n t s Pa i n t , Se r i a l i z a b l e
• • 390 •• • •
Ÿ Co l o r d e c o d e ( St r i n g ) t h r o ws Nu mb e r Fo r ma t Ex c e p t i o n
Liefert die Farbe vom übergebenen String. Die Zeichenkette ist als 24-Bit Integer kodiert. Nun wertet d e c o d e ( ) den String aus, indem wiederum die d e c o d e ( ) Funktion der I n t e g e r Klasse aufgerufen wird. Aus diesem Rückgabewert wird dann wiederum das Co l o r -Objekt aufgebaut. Wo jetzt der Algorithmus schon beschrieben wurde, können wir einen Blick auf die Implementierung werfen: publ i I nt i nt r et }
c s t a t i c Co l o r d e c o d e ( St r i n g n m) t h r o ws Nu mb e r Fo r ma t Ex c e p t i o n { e g e r i n t v a l = I n t e g e r . d e c o d e ( n m) ; i = i n t v a l . i n t Va l u e ( ) ; u r n n e w Co l o r ( ( i >> 1 6 ) & 0 x FF, ( i >> 8 ) & 0 x FF, i & 0 x FF) ;
Dort sehen wir, dass bei falschen Werten eine Nu mb e r Fo r ma t Ex c e p t i o n ausgelöst wird. Diese Exception kommt von der d e c o d e ( ) Funktion der Integer-Klasse. Die Implementierung verrät uns die Arbeitsweise, und zeigt uns auf, dass wir auch aus Okalziffern ein Color-Objekt erzeugen könnten. Oder aber aus einem String, der nicht mit dem Hash-Zeichen, sondern mit dem gewohnten Präfix 0 x beginnt. p u b l i c s t a t i c I n t e g e r d e c o d e ( St r i n g n m) t h r o ws Nu mb e r Fo r ma t Ex c e p t i o n { i f ( n m. s t a r t s Wi t h ( " 0 x " ) ) { r e t u r n I n t e g e r . v a l u e Of ( n m. s u b s t r i n g ( 2 ) , 1 6 ) ; } i f ( n m. s t a r t s Wi t h ( " # " ) ) { r e t u r n I n t e g e r . v a l u e Of ( n m. s u b s t r i n g ( 1 ) , 1 6 ) ; } i f ( n m. s t a r t s Wi t h ( " 0 " ) && n m. l e n g t h ( ) > 1 ) { r e t u r n I n t e g e r . v a l u e Of ( n m. s u b s t r i n g ( 1 ) , 8 ) ; } r e t u r n I n t e g e r . v a l u e Of ( n m) ; }
Farben Hexadezim al oder als Tripel Es ist nur ein kleiner Schritt von der Farbangabe in Hex-Code und in Rot/Grün/Blau zu einer Methode, die einem String ansieht, was für eine Farbdefinition dieser audrückt. Der String kodiert die hexadezimale Farbangabe als »#rrggbb« und die dezimale Angabe in der Form »r,g,b«. Unsere Methode g e t Co l o r ( ) gibt bei einer ungültigen Farbdefinition null zurück. Quellcode 12.i
getColorTester.java
i mp o r t j a v a . a wt . * ; i mp o r t j a v a . u t i l . * ; c l a s s g e t Co l o r Te s t e r { p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Sy s t e m. o u t . p r i n t l n ( g e t Co l o r ( " # a e a 4 d d " ) ) ; Sy s t e m. o u t . p r i n t l n ( g e t Co l o r ( " 1 2 , 4 , 5 5 " ) ) ; }
• • • 391 • • •
p u b l i c s t a t i c Co l o r g e t Co l o r ( St r i n g t e x t ) { St r i n g To k e n i z e r s t = n e w St r i n g To k e n i z e r ( t e x t , " , " ) ; i n t n u mb e r Of To k e n s = s t . c o u n t To k e n s ( ) ; i f ( n u mb e r Of To k e n s == 1 ) { St r i n g t o k e n = s t . n e x t To k e n ( ) ; i f ( ( t o k e n . c h a r At ( 0 ) == ' # ' ) && ( t o k e n . try { r e t u r n Co l o r . d e c o d e ( t o k e n ) ; } c a t c h ( Nu mb e r Fo r ma t Ex c e p t i o n e ) { } } } e l s e i f ( n u mb e r Of To k e n s == 3 ) { try { r e t u r n n e w Co l o r ( I n t e g e r . p a r s e I n t ( s t . I nt e ge r . pa r s e I nt ( s t . I nt e ge r . pa r s e I nt ( s t . } c a t c h ( Nu mb e r Fo r ma t Ex c e p t i o n e ) { } } r e t ur n nul l ;
l e n g t h ( ) == 7 ) ) {
n e x t To k e n ( ) ) , n e x t To k e n ( ) ) , n e x t To k e n ( ) ) ) ;
} }
Diese Methode ist zum Beispiel sinnvoll, damit ein Applet aus einem Parameter die Hintergrundfarbe auslesen kann. Im Applet-Tag kann folgendes stehen:
Zu Erinnerung: Um aus einem Applet den Parameter auszulesen nutzen wir die g e t Pa r a me t e r ( ) Methode mit einem String-Argument.
12.9.1 Einen helleren und dunkleren Farbt on wählen Zwei besondere Funktionen sind b r i g h t e r ( ) und d a r k e r ( ) . Sie liefern ein Farb-Objekt zurück, welches jeweils eine Farb-Nuance heller bzw. dunkler ist. Die Implementierung von d r a w3 DRe c t ( ) zeigt den Einsatz der Funktionen. p u b l i c v o i d d r a w3 DRe c t ( i n t x , i n t y , i n t wi d t h , i n t h e i g h t , bool e a n r a i s e d) { Co l o r c = g e t Co l o r ( ) ; Co l o r b r i g h t e r = c . b r i g h t e r ( ) ; Co l o r d a r k e r = c . d a r k e r ( ) ; s e t Co l o r ( r a i s e d ? b r i g h t e r : d a r k e r ) ; d r a wLi n e ( x , y , x , y + h e i g h t ) ; • • 392 •• • •
d r a wLi s e t Co l d r a wLi d r a wLi s e t Co l
n e ( x + 1 , y , x + wi d t or ( r a i s e d ? da r ke r : ne ( x + 1, y + he i ght , n e ( x + wi d t h , y , x + or ( c ) ;
h - 1, y) ; br i ght e r ) ; x + wi d t h , y + h e i g h t ) ; wi d t h , y + h e i g h t - 1 ) ;
}
Wie viele anderen Funktionen aus der Co l o r -Klasse sind die Routinen sichtbar implementiert also nicht nativ: / ** * Re t u r n s a b r i g h t e r v e r s i o n o f t */ p u b l i c Co l o r b r i g h t e r ( ) { r e t u r n n e w Co l o r ( Ma t h . mi n ( ( i n t Ma t h . mi n ( ( i n t Ma t h . mi n ( ( i n t } / ** * Re t u r n s a d a r k e r v e r s i o n o f t */ p u b l i c Co l o r d a r k e r ( ) { r e t u r n n e w Co l o r ( Ma t h . ma x ( ( i Ma t h . ma x ( ( i Ma t h . ma x ( ( i }
hi s c ol or .
) ( g e t Re d ( ) * ( 1 / FACTOR) ) , 2 5 5 ) , ) ( g e t Gr e e n ( ) * ( 1 / FACTOR) ) , 2 5 5 ) , ) ( g e t Bl u e ( ) * ( 1 / FACTOR) ) , 2 5 5 ) ) ;
hi s c ol or .
n t ) ( g e t Re d ( ) * FACTOR) , 0 ) , n t ) ( g e t Gr e e n ( ) * FACTOR) , 0 ) , n t ) ( g e t Bl u e ( ) * FACTOR) , 0 ) ) ;
FACTOR ist eine Konstante, die durch p r i v a t e s t a t i c f i n a l d o u b l e FACTOR = 0 . 7 ;
festgelegt ist. Sie lässt sich also nicht ändern. c l a s s j a v a . a wt . Co l o r i mp l e me n t s Pa i n t , Se r i a l i z a b l e Ÿ Co l o r b r i g h t e r ( )
Gibt einen helleren Farbton zurück. Ÿ Co l o r d a r k e r ( )
Gibt einen dunkleren Farbton zurück.
12.9.2 Farben nach Nam en auswählen Programme, die Farben benutzen, sind geläufig und oft ist die Farbgebung nicht progammierbar sondern kann vom Benutzer individuell besetzt werden. So setzt in einer HTML-Seite beispielsweise die Hintergrundfarbe die Variable BGCOLOR. Sie enthält eine Zeichenkette, die entweder den Farbnamen enthält oder eine Hexadezimalzahl kennzeichnet, die mit Rot-, Grün- und Blau-Werten die Farbe kodiert. Eine Klasse, mit einer Methode, die Farbnamen erkennt und ein Co l o r -Objekt zurückgeben ist schnell programmiert. Als Ergänzung soll eine weitere Funktion ausprogrammiert werden, die eine Zeichenkette entgegennimmt, erkennt ob die erste Ziffer ein Hash-Symbol ist und • • • 393 • • •
dann die Zahl auswertet. Beginnt der String nicht mit einem Hash, so wird überprüft, ob es ein Farbstring ist. Zusätzlich kann hinter den Farbnamen noch die Kennung ›bright‹ (oder ›light‹) bwz. ›dark‹ stehen, die den Farbton dann noch um eine Nuance aufhellen oder abdunkeln. Wir programmieren die zwei statischen Methoden aus: Quellcode 12.i
ColorParser.java
i mp o r t j a v a . a wt . * ; c l a s s Co l o r Pa r s e r { p u b l i c s t a t i c Co l o r p a r s e Co l o r ( St r i n g s ) { i f ( s . e q u a l s I g n o r e Ca s e ( " b l a c k " ) ) r e t u r n Co l o r . b l a c k ; i f ( s . e q u a l s I g n o r e Ca s e ( " b l u e " ) ) r e t u r n Co l o r . b l u e ; i f ( s . e q u a l s I g n o r e Ca s e ( " c y a n " ) ) r e t u r n Co l o r . c y a n ; i f ( s . e q u a l s I g n o r e Ca s e ( " d a r k Gr a y " ) ) r e t u r n Co l o r . d a r k Gr a y ; i f ( s . e q u a l s I g n o r e Ca s e ( " g r a y " ) ) r e t u r n Co l o r . g r a y ; i f ( s . e q u a l s I g n o r e Ca s e ( " g r e e n " ) ) r e t u r n Co l o r . g r e e n ; i f ( s . e q u a l s I g n o r e Ca s e ( " l i g h t Gr a y " ) ) r e t u r n Co l o r . l i g h t Gr a y ; i f ( s . e q u a l s I g n o r e Ca s e ( " ma g e n t a " ) ) r e t u r n Co l o r . ma g e n t a ; i f ( s . e q u a l s I g n o r e Ca s e ( " o r a n g e " ) ) r e t u r n Co l o r . o r a n g e ; i f ( s . e q u a l s I g n o r e Ca s e ( " p i n k " ) ) r e t u r n Co l o r . p i n k ; i f ( s . e q u a l s I g n o r e Ca s e ( " r e d " ) ) r e t u r n Co l o r . r e d ; i f ( s . e q u a l s I g n o r e Ca s e ( " wh i t e " ) ) r e t u r n Co l o r . wh i t e ; i f ( s . e q u a l s I g n o r e Ca s e ( " y e l l o w" ) ) r e t u r n Co l o r . y e l l o w; r e t ur n nul l ; } p u b l i c s t a t i c Co l o r p a r s e Co mp l e x Co l o r ( St r i n g s ) { i f ( s . s t a r t s Wi t h ( " # " ) ) { try { r e t u r n Co l o r . d e c o d e ( s ) ; } c a t c h ( Nu mb e r Fo r ma t Ex c e p t i o n e ) { r e t u r n n u l l ; } } Co l o r c o l o r = p a r s e Co l o r ( s ) ; i f ( c ol or ! = nul l ) r e t ur n c ol or ; i f ( s . s ubs t r i ng( 0, 6 ) . e qua l i f ( ( c o l o r = p a r s e Co l o r ( s . r e t ur n c ol or . br i ght e r ( ) ; } e l s e i f ( s . s ubs t r i ng( 0, 5 ) . i f ( ( c o l o r = p a r s e Co l o r ( s . r e t ur n c ol or . br i ght e r ( ) ; } e l s e i f ( s . s ubs t r i ng( 0, 4 ) . i f ( ( c o l o r = p a r s e Co l o r ( s . r e t ur n c ol or . da r ke r ( ) ; } • • 394 •• • •
s I g n o r e Ca s e ( " b r i g h t " ) ) { s ubs t r i ng( 6) ) ) ! = nul l )
e q u a l s I g n o r e Ca s e ( " l i g h t " ) ) { s ubs t r i ng( 5 ) ) ) ! = nul l )
e q u a l s I g n o r e Ca s e ( " d a r k " ) ) { s ubs t r i ng( 4) ) ) ! = nul l )
r e t ur n nul l ; }
/ / Co l o r n o t f o u n d
}
12.9.1 Farbm odelle HSB und RGB Zwei Farbmodelle sind in der Computergrafik geläufig. Das RGB-Modell, wo die Farben durch einen Rot/Grün/Blau-Anteil definiert werden und ein HSB-Modell, welches die Farben durch einen Grundton (Hue), Farbsättigung (Saturation) und Helligkeit (Brightness) definieren. Die Farbmodelle können die gleichen Farben beschreiben und umgerechnet werden. c l a s s j a v a . a wt . Co l o r i mp l e me n t s Pa i n t , Se r i a l i z a b l e Ÿ s t a t i c i n t HSBt o RGB( f l o a t h u e , f l o a t s a t u r a t i o n , f l o a t b r i g h t n e s s ) Aus HSB-kodierten Farbwert wird ein RBG-Farbwert gemacht. Ÿ s t a t i c f l o a t [ ] RGBt o HSB( i n t r , i n t g , i n t b , f l o a t h s b v a l s [ ] ) Verlangt ein Array h s b v a l s zur Aufnahme von HSB, in dem die Werte gespeichert werden sollen. Das Array kann n u l l sein und wird somit angelegt. Das Feld wird zurückgegeben. Ÿ s t a t i c Co l o r g e t HSBCo l o r ( f l o a t h , f l o a t s , f l o a t b ) Um Co l o r Objekte aus einem HSB-Modell zu erzeugen kann die Funktion genutzt werden.
Die Implementierung von g e t HSBCo l o r ( ) ist ein Witz: p u b l i c s t a t i c Co l o r g e t HSBCo l o r ( f l o a t h , f l o a t s , f l o a t b ) { r e t u r n n e w Co l o r ( HSBt o RGB( h , s , b ) ) ; }
12.9.2 Die Farben des Syst em s Bei eigenen Java-Programmen ist es wichtig, diese sich so perfekt wie möglich in die Reihe der anderen Programme einzureihen ohne großartig aufzufallen. Dazu muss ein Fenster die globalen Einstellungen wie den Zeichensatz und die Farben kennen. Für die Systemfarben gibt es die Klasse Sy s t e mCo l o r , welche alle Farben einer Grafischen Oberfläche auf symbolische Konstanten abbildet. Besonders praktisch ist dies bei Änderungen von Farben während der Laufzeit. Über diese Klasse können immer die aktuellen Werte eingeholt werden, denn ändert sich beispielsweise die Hintergrundfarbe der Laufleisten, so ändert sich damit auch der RGB-Wert mit. Die Systemfarben sind Konstanten von Sy s t e mCo l o r und werden mit der Funktion g e t RGB( ) in eine Ganzzahl umgewandelt. Die Klasse definiert folgende statische finale Variablen. c l a s s j a v a . a wt . Sy s t e mCo l o r i mp l e me n t s Se r i a l i z a b l e
• • • 395 • • •
SystemColor
Welche Farbe anspricht
de s kt op
Farbe des Desktop-Hintergrundes
a c t i v e Ca p t i o n
Hintergrundfarben für Text im Fensterrahmen
a c t i v e Ca p t i o n Te x t
Farbe für Text im Fensterrahmen
a c t i v e Ca p t i o n Bo r d e r
Rahmenfarbe für Text im Fensterrahmen
i n a c t i v e Ca p t i o n
Hintergrundfarbe für inaktiven Text im Fensterrahmen
i n a c t i v e Ca p t i o n Te x t
Farbe für inaktiven Text im Fensterrahmen
i n a c t i v e Ca p t i o n Bo r d e r
Rahmenfarbe für inaktiven Text im Fensterrahmen
wi n d o w
Hintergrundfarbe der Fenster
wi n d o wBo r d e r
Rahmenfarbe der Fenster
wi n d o wTe x t
Textfarbe für Fenster
me n u
Hintergrundfarbe für Menüs
me n u Te x t
Textfarbe für Menüs
t e xt
Hintergrundfarbe für Textkomponenten
t e x t Te x t
Textfarbe für Textkomponenten
t e x t Hi g h l i g h t
Hintergrundfarbe für hervorgehobenen Text
t e x t Hi g h l i g h t Te x t
Farbe des Textes wenn dieser hervorgehoben ist
t e x t I n a c t i v e Te x t
Farbe für inaktiven Text
c ont r ol
Hintergrundfarbe für Kontroll-Objekte
c o n t r o l Te x t
Textfarbe für Kontroll-Objekte
c o n t r o l Hi g h l i g h t
Normale Farbe, mit der Kontroll-Objekte hervorgehoben werden
c o n t r o l Lt Hi g h l i g h t
Hellere Farbe, mit der Kontroll-Objekte hervorgehoben werden
c o n t r o l Sh a d o w
Normale Hintergrundfarbe für Kontroll-Objekte
c o n t r o l Dk Sh a d o w
Dunklerer Schatten für Kontroll-Objekte
s c r ol l ba r
Hintergrundfarbe der Schieberegler
i nf o
Hintergrundfarbe der Hilfe
i n f o Te x t
Textfarbe der Hilfe
Tabelle: Konstanten der Systemfarben Um die System-Farbe in eine brauchbare Varibale zu konvertieren gibt es die g e t RGB( ) Funktion. So erzeugen wir mit n e w Co l o r ( ( Sy s t e mCo l o r . wi n d o w) . g e t RGB( ) )
• • 396 •• • •
einfach ein Co l o r Objekt in der Farbe des Fensters. f i n a l c l a s s j a v a . a wt . Sy s t e mCo l o r i mp l e me n t s Se r i a l i z a b l e Ÿ i n t g e t RGB( ) Liefert den RGB-Wert der Systemfarbe als Ganzzahl kodiert.
Zuordung der Farben unt er Windows Werden die Farben vom System nicht zugewiesen, so werden vordefinierten Werte gesetzt. Folgende Einteilung wird unter Windows unternommen und beibehalten, wenn dies nicht vom System überschrieben wird. Farbe
Initialisierte Farbe
de s kt op
n e w Co l o r ( 0 , 9 2 , 9 2 ) ;
a c t i v e Ca p t i o n
n e w Co l o r ( 0 , 0 , 1 2 8 ) ;
a c t i v e Ca p t i o n Te x t
Co l o r . wh i t e ;
a c t i v e Ca p t i o n Bo r d e r
Co l o r . l i g h t Gr a y ;
i n a c t i v e Ca p t i o n
Co l o r . g r a y ;
i n a c t i v e Ca p t i o n Te x t
Co l o r . l i g h t Gr a y ;
i n a c t i v e Ca p t i o n Bo r d e r
Co l o r . l i g h t Gr a y ;
wi n d o w
Co l o r . wh i t e ;
wi n d o wBo r d e r
Co l o r . b l a c k ;
wi n d o wTe x t
Co l o r . b l a c k ;
me n u
Co l o r . l i g h t Gr a y ;
me n u Te x t
Co l o r . b l a c k ;
t e xt
Co l o r . l i g h t Gr a y ;
t e x t Te x t
Co l o r . b l a c k ;
t e x t Hi g h l i g h t
n e w Co l o r ( 0 , 0 , 1 2 8 ) ;
t e x t Hi g h l i g h t Te x t
Co l o r . wh i t e ;
t e x t I n a c t i v e Te x t
Co l o r . g r a y ;
c ont r ol
Co l o r . l i g h t Gr a y ;
c o n t r o l Te x t
Co l o r . b l a c k ;
c o n t r o l Hi g h l i g h t
Co l o r . wh i t e ;
c o n t r o l Lt Hi g h l i g h t
n e w Co l o r ( 2 2 4 , 2 2 4 , 2 2 4 ) ;
c o n t r o l Sh a d o w
Co l o r . g r a y ;
c o n t r o l Dk Sh a d o w
Co l o r . b l a c k ;
s c r ol l ba r
n e w Co l o r ( 2 2 4 , 2 2 4 , 2 2 4 ) ;
Tabelle: Systemfarben
• • • 397 • • •
i nf o
n e w Co l o r ( 2 2 4 , 2 2 4 , 0 ) ;
i n f o Te x t
Co l o r . b l a c k ;
Tabelle: Systemfarben Um zu sehen, welche Farben auf dem laufenden System aktiv sind, formulieren wir ein Programm, welches eine kleine Textzeile in der jeweiligen Farbe angibt. Da wir auf die internen Daten nicht zugreifen können, müssen wir ein Farbfeld mit Sy s t e mCo l o r Objekten aufbauen.
Abbildung 7: Die System-Farben unter einer Windows-Konfiguration Quellcode 12.i
SystemColors.java
i mp o r t j a v a . a wt . * ; i mp o r t j a v a . a wt . e v e n t . * ; c l a s s Sy s t e mCo l o r s e x t e n d s Fr a me { p r i v a t e St r i n g s y s t e mCo l o r St r i n g [ ] = { " d e s k t o p " , " a c t i v e Ca p t i o n " , " a c t i v e Ca p t i o n Te x t " , " a c t i v e Ca p t i o n Bo r d e r " , " i n a c t i v e Ca p t i o n " , " i n a c t i v e Ca p t i o n Te x t " , " i n a c t i v e Ca p t i o n Bo r d e r " , " wi n d o w" , " wi n d o wTe x t " , " me n u " , " me n u Te x t " , " t e x t " , " t e x t Te x t " , " t e x t Hi g h l i g h t " , " t e x t Hi g h l i g h t Te x t " , " t e x t I n a c t i v e Te x t " , " c o n t r o l " , " c o n t r o l Te x t " , " c o n t r o l Hi g h l i g h t " , " c o n t r o l Lt Hi g h l i g h t " , " c o n t r o l Sh a d o w" , • • 398 •• • •
" c o n t r o l Dk Sh a d o w" , " s c r o l l b a r " , " i n f o " , " i n f o Te x t " }; pr i va t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t Sy s t };
e Sy s t e mCo l o r s y s t e mCo l o r [ ] = { e mCo l o r . d e s k t o p , e mCo l o r . a c t i v e Ca p t i o n , e mCo l o r . a c t i v e Ca p t i o n Te x t , e mCo l o r . a c t i v e Ca p t i o n Bo r d e r , e mCo l o r . i n a c t i v e Ca p t i o n , e mCo l o r . i n a c t i v e Ca p t i o n Te x t , e mCo l o r . i n a c t i v e Ca p t i o n Bo r d e r , e mCo l o r . wi n d o w, e mCo l o r . wi n d o wTe x t , e mCo l o r . me n u , e mCo l o r . me n u Te x t , e mCo l o r . t e x t , e mCo l o r . t e x t Te x t , e mCo l o r . t e x t Hi g h l i g h t , e mCo l o r . t e x t Hi g h l i g h t Te x t , e mCo l o r . t e x t I n a c t i v e Te x t , e mCo l o r . c o n t r o l , e mCo l o r . c o n t r o l Te x t , e mCo l o r . c o n t r o l Hi g h l i g h t , e mCo l o r . c o n t r o l Lt Hi g h l i g h t , e mCo l o r . c o n t r o l Sh a d o w, e mCo l o r . c o n t r o l Dk Sh a d o w, e mCo l o r . s c r o l l b a r , e mCo l o r . i n f o , e mCo l o r . i n f o Te x t
p u b l i c Sy s t e mCo l o r s ( ) { s e t Si z e ( 2 0 0 , 4 0 0 ) ; a d d Wi n d o wLi s t e n e r ( n e w Wi n d o wAd a p t e r ( ) { p u b l i c v o i d wi n d o wCl o s i n g ( Wi n d o wEv e n t e ) { Sy s t e m. e x i t ( 0 ) ; } }) ; } p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { g . s e t Fo n t ( n e w Fo n t ( " Di a l o g " , Fo n t . BOLD, 1 2 ) ) ; f o r ( i n t i =0 ; i < s y s t e mCo l o r St r i n g . l e n g t h ; i ++ ) { g . s e t Co l o r ( n e w Co l o r ( s y s t e mCo l o r [ i ] . g e t RGB( ) ) ) ; g . d r a wSt r i n g ( s y s t e mCo l o r St r i n g [ i ] , 2 0 , 4 0 +( i * 1 3 ) ) ; } } p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Sy s t e mCo l o r s c = n e w Sy s t e mCo l o r s ( ) ; c . s h o w( ) ; } • • • 399 • • •
}
12.10 Bilder anzeigen und Grafiken verwalt en Bilder sind neben Text das wichtigste visuelle Gestaltungsmerkmal. In Java können Grafiken an verschiedenen Stellen eingebunden werden. So zum Beispiel als Grafiken in Zeichengebieten (Canvas) oder als Icons in Buttons, die angeklickt werden und ihre Form ändern. Über Java können GIFBilder und JPEG-Bilder geladen werden.
GI F u n d JPEG Das GI F- For m at ( Gr aphics I nt er change For m at ) ist ein k om pr im ier endes Ver fahr en, w elches 1987 v on Com puSer v e- Bet r eiber n zum Aust ausch v on Bilder n ent w ick elt w ur de. GI F- Bilder k önnen bis zu 1600 x 1600 Punk t e um fassen. Die Kom pr im ier ung nach einem v er änder t en LZW 1 - Pack v er fahr en nim m t k einen Einfluss auf die Bildqualit ät ( sie ist v er lusst fr ei) . Jedes GI F- Bild k ann aus m ax im al 256 Far ben best ehen – bei einer Palet t e aus 16,7 Millionen Far ben. Nach dem St andar d v on 1989 k önnen m ehr er e GI F- Bilder in einer Dat ei gespeicht w er den. JPEGBilder dagegen sind in der Regel v er lust behaft et und das Kom pr im ier v er fahr en speicher t die Bilder m it einer 24- Bit Far bpalet t e. Der Kom pr im ier ungsfakt or kann pr ozent ual eingest ellt w er den.
Jede Grafik wird als Exemplar der Klasse I ma g e erzeugt. Um aber ein Grafik Objekt erst einmal zu bekommen gibt es zwei grundlegende Verfahren: Laden eines Bildes von einem Applet und Laden eines Bildes aus einer Applikation. In beiden Fällen wird g e t I ma g e ( ) verwendet, eine Methode, die mehrfach überladen ist, um uns verschiedene Möglichkeiten an die Hand zu geben, I ma g e Objekte zu erzeugen.
Bilder in Applikat ionen Grafiken in einer Applikation werden über die Klasse To o l k i t eingebunden. Der Konstruktor kann einerseits eine URL beinhalten oder eine Pfadangabe zu der Grafikdatei a b s t r a c t c l a s s j a v a . a wt . To o l k i t Ÿ I ma g e g e t I ma g e ( St r i n g )
Das Bild wird durch eine Pfadangabe überliefert. Ÿ I ma g e g e t I ma g e ( URL ) Das Bild wird durch die URL angegeben.
Folgendes Beispiel verdeutlicht die Arbeistsweise: I ma g e p i c = g e t To o l k i t ( ) . g e t I ma g e ( " h a n s wu r s t " ) ;
Ein I ma g e Objekt wird erzeugt und das Objekt mit der Datei h a n s wu r s t in Verbindung gebracht. Die Formulierung lässt: »Laden der Datei nicht zu«, denn die Grafik wird erst aus der Datei bzw. dem Netz geladen, wenn der erste Zeichenaufruf stattfindet. Somit schützt uns die Bibliothek vor unvorhersehbaren Ladevorgängen für Bilder, die spät oder gar nicht genutzt sind. 1. Benannt nach den Erfindern Lempel, Ziv und Welch. • • 400 •• • •
Da die g e t I ma g e ( ) Funktion einmal für URLs und Strings definiert ist, ist vor folgendem Konstrukt natürlich nur zu warnen: g e t I ma g e ( " h t t p : / / h o s t n a me / g r a f i k " ) ;
Gewiss führt es zum gnadenlosen Fehler, denn eine Datei mit dem Namen h t t p : / / h o s t n a me / g r a f i k gibt es nicht! Korrekt heißt es: g e t I ma g e ( n e w URL( " h t t p : / / h o s t n a me / g r a f i k " ) ) ;
Bilder in Applet s Die Applet-Klasse kennt ebenso zwei Methoden g e t I ma g e ( ) , die wiederum die entsprechenden Methoden aus der Klasse Ap p l e t Co n t e x t aufrufen. i n t e r f a c e j a v a . a p p l e t . Ap p l e t Co n t e x t Ÿ I ma g e g e t I ma g e ( URL ) Das Bild wird durch die URL angegeben.
Müssen wir in einem Applet die Grafik relativ angeben, uns fehlt aber der aktuelle Bezugspunkt, so hilft uns die Funktion g e t Co d e Ba s e ( ) weiter, die uns die relative Adresse des Applets übergibt. (Mit g e t Do c u me n t Ba s e ( ) bekommen wir die URL des HTML-Dokumentes, unter der das Applet eingebunden ist.)
Bilder aus dem Cache nehm en Eine Webcam erzeugt kontinuierlich neue Bilder. Sollen diese in einem Applet präsentiert werden, so ergibt sich das Problem, dass ein erneuter Aufruf von g e t I ma g e ( ) lediglich das alte Bild liefert. Dies liegt an der Verwaltung der I ma g e Objekte, denn sie werden in einem Cache gehalten. Für sie gibt es keinen GC, der die Entscheidung fällt: Das Bild ist alt. Da hilft die Methode f l u s h ( ) der I ma g e Klasse. Sie löscht das Bild aus der interne Liste. Eine erneute Aufforderung zum Laden bringt also das gewünschte Ergebnis. a b s t r a c t c l a s s j a v a . a wt . I ma g e Ÿ a bs t r a c t voi d f l us h( )
Gibt die für das Image belegten Ressourcen frei.
• • • 401 • • •
Spe ich e r spa r e n I m age Obj ek t e w er den nicht aut om at isch fr eigegeben. flush( ) ent sor gt diese Bilder und m acht w ieder Speicher fr ei und den Rechner w ieder schneller .
12.10.1 Die Grafik zeichnen Die Grafik wird durch die Funktion d r a wI ma g e ( ) gezeichnet. Wie erwähnt wird sie, falls noch nicht vorhanden, vom Netz oder Dateisystem geladen. Das folgende Programmlisting zeigt eine einfache Applikation mit einer Menüleiste, die über ein Dateiauswahldialog eine Grafik lädt. Die Größe des Fensters wird auf die Größe der Grafik gesetzt.
Abbildung 8: Ein einfacher Bildbetrachter mit Dateiauswahldialog Quellcode 12.j
ImageViewer.java
i mp o r t j a v a . a wt . * ; i mp o r t j a v a . a wt . e v e n t . * ; p u b l i c c l a s s I ma g e Vi e we r e x t e n d s Fr a me i mp l e me n t s Ac t i o n Li s t e n e r { p u b l i c I ma g e Vi e we r ( ) { s e t Ti t l e ( " Bi l d b e t r a c h t e r " ) ; / / Ko n s t r u i e r e d i e Me n ü z e i l e Me n u Ba r mb a r = n e w Me n u Ba r ( ) ; Me n u me n u = n e w Me n u ( " Da t e i " ) ; • • 402 •• • •
Me n u I t e m me n u i t e m = n e w Me n u I t e m( " Öf f n e n " , n e w Me n u Sh o r t c u t ( ( i n t ) ' O' ) ) ; me n u i t e m. a d d Ac t i o n Li s t e n e r ( t h i s ) ; me n u . a d d ( me n u i t e m ) ; mb a r . a d d ( me n u ) ; s e t Me n u Ba r ( mb a r ) ;
/ / Da s Fe n s t e r mi t X s c h l i e ß e n f r a me = t h i s ; a d d Wi n d o wLi s t e n e r ( n e w Wi n d o wAd a p t e r ( ) { p u b l i c v o i d wi n d o wCl o s i n g ( Wi n d o wEv e n t e ) { Sy s t e m. e x i t ( 0 ) ; } } ); s e t Si z e ( 6 0 0 , 4 0 0 ) ; } p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { i f ( i ma g e ! = n u l l ) { g . d r a wI ma g e ( i ma g e , 0 , 0 , t h i s ) ; s e t Si z e ( i ma g e . g e t Wi d t h ( t h i s ) , i ma g e . g e t He i g h t ( t h i s ) ) ; } } p u b l i c v o i d a c t i o n Pe r f o r me d ( Ac t i o n Ev e n t e ) { Fi l e Di a l o g d = n e w Fi l e Di a l o g ( f r a me , " Öf f n e Gr a f i k d a t e i " , Fi l e Di a l o g . LOAD ) ; d . s e t Fi l e ( " * . j p g ; * . g i f " ) ; d . s h o w( ) ; St r i n g f i l e = d . g e t Di r e c t o r y ( ) + d . g e t Fi l e ( ) ; i ma g e = To o l k i t . g e t De f a u l t To o l k i t ( ) . g e t I ma g e ( f i l e ) ; i f ( i ma g e ! = n u l l ) r e pa i nt ( ) ; } p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { n e w I ma g e Vi e we r ( ) . s h o w( ) ; } p r i v a t e I ma g e i ma g e ; p r i v a t e Fr a me f r a me ; }
• • • 403 • • •
12.10.1 Grafiken zent rieren Eine Funktion zum Zentrieren einer Grafik braucht neben der Grafik als Image und dem Gr a p h i c s noch die Komponente, auf der die Grafik gezeichnet wird. Über die g e t Si z e ( ) Funktion des Co mp o n e n t Objekts kommen wir an die Breite und Höhe der Zeichenfläche. Wir holen uns die Hintergrundfarbe und füllen die Zeichenfläche mit dieser, anschließend positionieren wir das Bild in der Mitte, indem wir die Breite/Höhe des Bildes von der Breite/Höhe der Zeichenfläche subtrahieren und anschießend durch Zwei teilen. publ c e nt { g. Di g. g.
i c s t a t i c voi d e r I ma g e ( Gr a p h i c s g , Co mp o n e n t c o mp o n e n t , I ma g e i ma g e ) s e t Co l o r ( c o mp o n e n t . g e t Ba c k g r o u n d ( ) ) ; me n s i o n d = c o mp o n e n t . s i z e ( ) ; f i l l Re c t ( 0 , 0 , d . wi d t h , d . h e i g h t ) ; d r a wI ma g e ( i ma g e , ( d . wi d t h - i ma g e . g e t Wi d t h ( n u l l ) ) / 2 , ( d . h e i g h t - i ma g e . g e t He i g h t ( n u l l ) ) / 2 , nul l ) ;
}
12.10.2 Laden von Bildern m it dem MediaTracker beobacht en Das Laden von Bildern mittels g e t I ma g e ( ) wird dann vom System angeregt, wenn das Bild zum ersten Mal benötigt wird. Diese Technik ist zwar ganz schön und entzerrt den Netzwerktransfer, ist aber für einige grafische Einsätze ungeeignet. Nehmen wir zum Beispiel eine Animation, dann können wir nicht erwarten, erst dann die Animation im vollen Ablauf zu sehen, wenn wir nacheinander alle Bilder im Aufbauprozess sehen konnten. Daher ist es zu Wünschen, die Bilder erst einmal alle laden zu können, bevor sie angezeigt werden. Die Klasse Me d i a Tr a c k e r ist eine Hilfsklasse, mit der wir den Ladeprozess von Media-Objekten, bisher nur Bilder, beobachten können. Um den Überwachungsprozess zu starten, werden die Media-Objekte dem Me d i a Tr a c k e r zur Beobachtung übergeben. Neben dieser Stärke besitzt die Klasse noch weitere Vorteile gegenüber der herkömmlichen Methode: n Bilder können in Gruppen organisiert werden n Bilder könenn synchron oder asynchron geladen werden n Die Bilder-Gruppen können unabhängig geladen werden
Ein MediaTracker Obj ekt erzeugen Um ein Me d i a Tr a c k e r Objekt zu erzeugen, rufen wir seinen Konstruktor mit einem einzigen Parameter vom Typ Co mp o n e n t auf. Me d i a Tr a c k e r t r a c k e r = n e w Me d i a Tr a c k e r ( t h i s ) ;
Wenn wir Ap p l e t oder Fr a me erweitern kann dies – so wie im Beispiel – der t h i s -Zeiger sein. Diese zeigt aber schon die Einschränkung der Klasse auf das Laden von Bildern, denn was hat eine Musik schon mit einer Komponente zu tun?
• • 404 •• • •
Bilder beobacht en Nachdem ein Me d i a Tr a c k e r Objekt erzeugt ist, fügt die a d d I ma g e ( I ma g e ) Methode ein Bild in eine Warteliste ein. Eine weitere überladene Methode a d d I ma g e ( I ma g e , Gr u p p e I D) erlaubt die Angabe einer Gruppe. Dieser Identifier entspricht gleichzeitig einer Priorität, in der die Bilder geholt werden. Gehören also Bilder zu einer gleichen Gruppe ist die Priorität immer dieselbe. Bilder mit einer niedrigeren Gruppennummer werden mit einer niedrigen Priorität geholt als Bilder mit einer höheren ID. Eine dritte Methode von a d d I ma g e ( ) erlaubt die Angabe einer Skalierungsgröße. Nach dieser wird das geladene Bild dann skaliert und eingefügt. Schauen wir uns einmal eine typische Programmsequenz an, die ein Hintergrundbild, sowie einige animierte Bilder dem Medien-Überwacher überreichen. I ma g e b g = g e t I ma g e ( " b a c k g r o u n d . g i f " ) , a n i m[ ] = n e w I ma g e [ MAX_ ANI M] ; Me d i a Tr a c k e r t r a c k e r = n e w Me d i a Tr a c k e r ( t h i s ) ; t r a c k e r . a d d I ma g e ( b g , 0 ) ; f o r ( i n t i = 0 ; i < MAX_ ANI M; i ++ ) { a n i m[ i ] = g e t I ma g e ( g e t Do c u me n t Ba s e ( ) , " a n i m" +i +" . g i f " ) ; t r a c k e r . a d d I ma g e ( a n i m[ i ] , 1 ) ; }
Das Hintergrundbild wird dem Me d i a Tr a c k e Objekt hinzugefügt. Die ID, also die Gruppe, ist 0. Das Bildarray a n i m[ ] wird genauso gefüllt und überwacht. Die ID des Feldes ist 1. Also gehören alle Bilder dieser Animation zu eine weiteren Gruppe. Um den Ladeprozess anzustoßen benutzen wir eine der Methoden wa i t Fo r Al l ( ) oder wa i t Fo r I D( ) . Die wa i t Fo r I D( ) Methode wird benutzt, um Bilder mit einer betimmten Gruppe zu laden. Die Gruppenummer muss natürlich dieselbe vergebene Nummer sein, die bei der a d d I ma g e ( ) Methode verwendet wurde. Beide Methoden arbeiten synchron, bleiben also solange in der Methode, bis alle Bilder geladen wurden oder ein Fehler bzw. eine Unterbrechung auftrat. Da dies also das ganze restliche Programm blockieren würde, werden diese Ladeoperationen gerne in Threads gesetzt. Wie diese Methoden in einem Thread verwendet werden, zeigt das folgende Programmsegment. Der Block ist idealerweise in einer r u n ( ) Methode platziert – oder, bei einem Applet, in der i n i t ( ) Methode. try { t r a c k e r . wa i t Fo r I D( 0 ) ; t r a c k e r . wa i t Fo r I D( 1 ) ; } c a t c h ( I n t e r r u p t e d Ex c e p t i o n e ) { r e t u r n ; }
Die wa i t Fo r I D( ) Methode wirft einen Fehler, falls sie beim Ladevorgang unterbrochen wurde. Daher müssen wir unsere Operationen in einen try/catch-Block setzen. Während das Bild geladen wird, können wir seinen Ladezustand mit den Methoden c h e c k I D( ) überprüfen. c h e c k I D( ) bekommt als ersten Parameter eine Gruppe zugeordnet und überprüft dann, ob die Bilder, die mit der Gruppe verbunden sind, geladen wurden. Wenn ja, gibt die Methode t r u e zurück, auch dann wenn der Prozess fehlerhaft oder abgebrochen wurde. Ist der Ladeprozess noch nicht gestartet, dann veranlasst c h e c k I D( Gr u p p e ) dies nicht. Um dieses Verhalten zu steuern regt die überladene Funktion c h e c k I D( Gr u p p e , t r u e ) das Laden an. Beide geben f a l s e zurück, falls der Ladeprozess noch nicht beendet ist.
• • • 405 • • •
Eine weitere Überprüfungsfunktion ist c h e c k Al l ( ) . Diese arbeitet wie c h e c k I D( ) , nur, dass sie auf alle Bilder in allen Gruppen achtet und nicht auf die ID angewiesen ist. Ebenfalls wie c h e k k I D( ) gibt es c h e c k Al l ( ) in zwei Varianten. Die zweite startet den Ladeprozess, falls die Bilder noch nicht angestoßen wurden, sich zu laden. Die Me d i a Tr a c k e r -Klasse verfügt über vier Konstanten, die verschiedene Flags vertreten, um den Status des Objekts zu erfragen. Einige der Methoden geben diese Konstanten ebenso zurück. Konstante
Bedeutung
LOADI NG
Ein Medien-Objekt wird gerade geladen.
ABORTED
Das Laden eines Objekts wurde unterbrochen.
ERRORED
Ein Fehler trat während des Ladens auf
COMPLETE
Das Medien-Objekt wurde erfolgreich geladen.
Tabelle: Flags der Klasse MediaTracker Mit s t a t u s I D( ) verbunden, welches ja den Zustand des Ladens überwacht, können wir leicht die Fälle rausfinden, wo das Bild erfolgreich bzw. nicht erfolgreich geladen werden konnte. Dazu UndVerknüpfen wir einfach die Konstante mit dem Rückgabewert von s t a t u s Al l ( ) oder s t a t u s I D( ) . i f ( ( t r a c k e r . s t a t u s Al l ( ) & Me d i a Tr a c k e r . ERRORED) ! = 0 ) { / / Fe h l e r !
Wie wir sehen können wir durch solche Zeilen leicht herausfinden, ob bestimmte Bilder schon geladen sind. Me d i a Tr a c k e r . COMPLETE sagt uns ja und wenn ein Fehler auftrat, dann ist der Rückgabewert Me d i a Tr a c k e r . ERRORED. Wir wollen diese Flags nun verwenden, um in einer p a i n t ( ) Methode das Vorhandensein von Bildern zu überprüfen und wenn möglich diese dann anzuzeigen. Erinnern wir uns daran, dass in der Gruppe 0 ein Hintergrundbild lag und in Gruppe 1 die zu animierenden Bilder. Wenn ein Fehler auftritt zeichnen wir ein rotes Rechteck auf die Zeichenfläche und signalisieren damit, dass was nicht funktionierte. p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { i f ( t r a c k e r . s t a t u s I D( 0 , t r u e ) & Me d i a Tr a c k e r . ERRORED ) { g . s e t Co l o r ( Co l o r . r e d ) ; g . f i l l Re c t ( 0 , 0 , s i z e ( ) . wi d t h , s i z e ( ) . h e i g h t ) ; r e t ur n; } g . d r a wI ma g e ( b g , 0 , 0 , t h i s ) ; i f ( ( t r a c k e r . s t a t u s I D( 1 ) & Me d i a Tr a c k e r . COMPLETE) ! = 0 ) { g . d r a wI ma g e ( a n i m[ c o u n t e r %MAX_ ANI M] , 5 0 , 5 0 , t h i s ) ; } }
c l a s s j a v a . a wt . Me d i a Tr a c k e r i mp l e me n t s Se r i a l i z a b l e
• • 406 •• • •
Ÿ Me d i a Tr a c k e r ( Co mp o n e n t ) Erzeugt einen Me d i a Tr a c k e r auf einer Komponente, auf der das Bild möglicherweise
angezeigt wird. Ÿ v o i d a d d I ma g e ( I ma g e i ma g e , i n t i d )
Fügt ein Bild nicht skaliert der Ladeliste hinzu. Ruft a d d I ma g e ( i ma g e , i d , - 1 , - 1 ) auf. Ÿ v o i d a d d I ma g e ( I ma g e i ma g e , i n t i d , i n t w, i n t h )
Fügt ein skaliertes Bild der Ladeliste hinzu. Soll ein Bild in einer Richtung nicht skaliert werden, ist -1 einzutragen. Ÿ p u b l i c b o o l e a n c h e c k Al l ( ) Überprüft, ob alle vom Me d i a Tr a c k e r überwachten Medien geladen worden sind. Falls der
Ladeprozess noch nicht angestoßen wurde wird dieser auch nicht initiiert. Ÿ b o o l e a n c h e c k Al l ( b o o l e a n l o a d ) Überprüft, ob alle vom Me d i a Tr a c k e r überwachten Medien geladen worden sind. Falls der
Ladeprozess noch nicht angestoßen wurde, wird dieser dazu angeregt. Ÿ b o o l e a n i s Er r o r An y ( ) t r u e , wenn eines der überwachten Bilder einen Fehler beim Laden verursachte. Ÿ Ob j e c t [ ] g e t Er r o r s An y ( )
Liefert eine Liste aller Objekte, die einen Fehler aufweisen. n u l l , wenn alle korrekt geladen wurden. Ÿ v o i d wa i t Fo r Al l ( ) t h r o ws I n t e r r u p t e d Ex c e p t i o n Das Laden aller vom Me d i a Tr a c k e r überwachten Bilder wird angestoßen und es wird solange
gewartet, bis alles geladen wurde, oder ein Fehler beim Laden oder Skalieren auftrat. Ÿ b o o l e a n wa i t Fo r Al l ( l o n g ms ) t h r o ws I n t e r r u p t e d Ex c e p t i o n
Startet den Ladeprozess. Die Funktion kehrt erst dann zurück, wenn alle Bilder geladen wurden oder die Zeit überschritten wurde. t r u e , wenn alle korrekt geladen wurden. Ÿ i n t s t a t u s Al l ( b o o l e a n l o a d )
Liefert einen Oder-Verknüpften Wert der Flags LOADI NG, ABORTED, ERRORED und COMPLETE. Der Ladeprozess wird bei l o a d auf t r u e gestartet. Ÿ b o o l e a n c h e c k I D( i n t i d )
Überprüft, ob alle Bilder, die mit der ID i d verbunden sind, geladen wurden. Der Ladeprozess wird mit diese Methode nicht angestoßen. Liefert t r u e , wenn alle Bilder geladen sind, oder ein Fehler auftrat. Ÿ b o o l e a n c h e c k I D( i n t i d , b o o l e a n l o a d ) Wie c h e c k I D( i n t i d ) , nur, dass die Bilder geladen werden, die bisher noch nicht geladen
wurden. Ÿ b o o l e a n i s Er r o r I D( i n t i d )
Liefert der Fehler-Status von allen Bilder mit der ID i d . t r u e , wenn eines der Bilder beim Laden einen Fehler aufwies. Ÿ Ob j e c t [ ] g e t Er r o r s I D( i n t i d )
Liefert eine Liste aller Medien, die einen Fehler aufweisen. Ÿ v o i d wa i t Fo r I D( i n t i d ) t h r o ws I n t e r r u p t e d Ex c e p t i o n Startet den Ladeprozess für die gegebene ID. Die Methode wartet solange, bis alle Bilder
geladen sind. Beim Fehler oder Abbruch wird angenommen, dass aller Bilder ordentlich geladen wurden. Ÿ b o o l e a n wa i t Fo r I D( i n t i d , l o n g ms ) t h r o ws I n t e r r u p t e d Ex c e p t i o n Wie wa i t Fo r I D( ) , nur stoppt der Ladeprozess nach der festen Anzahl Millisekunden. • • • 407 • • •
Ÿ i n t s t a t u s I D( i n t i d , b o o l e a n l o a d ) Liefert einen Oder-Verknüpften Wert der Flags LOADI NG, ABORTED, ERRORED und COMPLETE. Ein noch nicht geladenes Bild hat den Status 0. Ist der Parameter l o a d gleich t r u e , dann
werden die Bilder geladen, die bisher nocht nicht geladen wurden. Ÿ v o i d r e mo v e I ma g e ( I ma g e i ma g e )
Entfernt ein Bild von der Liste der Medien-Elemente. Dabei werden alle Objekte, die sich nur in der Skalierung unterscheiden, entfernt. Ÿ p u b l i c v o i d r e mo v e I ma g e ( I ma g e i ma g e , i n t i d ) Entfernt das Bild mit der ID i d von der Liste der Mendien-Elemente. Auch die Objekte werden
dabei entfernt, wo sich die Bilder nur in der Skalierung unterscheiden. Ÿ p u b l i c v o i d r e mo v e I ma g e ( I ma g e i ma g e , i n t i d , i n t wi d t h , i n t h e i g h t ) Entfernt ein Bild mit den vorgegebenen Ausmaßen und der ID i d von der Liste der Medien-
Elemente. Doppelte Elemente werden ebenso gelöscht.
Die I m plem ent ierung von MediaTracker Es ist nun interessant zu Beobachten, wie die Klasse Me d i a Tr a c k e r implementiert ist. Sie verwaltet intern die Medien-Objekte in einer verkettete Liste. Da sie offen für alle Medien-Typen ist (aber bisher nur für Bilder umgesetzt ist), nimmt die Liste allgemeine Me d i a En t r y Objekte auf. Me d i a En t r y ist eine abstrakte Klasse und gibt einige Methoden vor, um alle erdenklichen Medientypen aufzunehmen. Die meisten der Funktionen dienen dafür, die Elemente in die Liste zu setzen. Einige der Funktionen sind abstrakt, genau die, die auf spezielle Medien gehen, und andere ausprogrammiert, genau die, die die Liste verwalten. Ÿ Me d i a En t r y ( Me d i a Tr a c k e r mt , i n t i d ) { . . . } Ÿ a b s t r a c t Ob j e c t g e t Me d i a ( ) ; Ÿ s t a t i c Me d i a En t r y i n s e r t ( Me d i a En t r y h e a d , Me d i a En t r y me ) { . . . } Ÿ a b s t r a c t v o i d s t a r t Lo a d ( ) ; Ÿ voi d c a nc e l ( ) { . . . } Ÿ s y n c h r o n i z e d i n t g e t St a t u s ( b o o l e a n l o a d , b o o l e a n v e r i f y ) { . . . } Ÿ v o i d s e t St a t u s ( i n t f l a g ) { . . . }
Ein paar Konstanten werden aus Me d i a Tr a c k e r übernommen. Dies sind LOADI NG, ABORTED, ERRORED, COMPLETE. Zwei weitere Konstanten setzen sich aus den anderen zusammen: LOADSTARTED = ( LOADI NG | ERRORED | COMPLETE) und DONE = ( ABORTED | ERRORED | COMPLETE) . Nun benutzt der Me d i a Tr a c k e r aber keine abstrakten Klassen. Vielmehr gibt es von der abstrakten Klasse Me d i a En t r y eine konkrete Implementierung und dies ist I ma g e Me d i a En t r y . Sie verwaltet Image-Objekte und implementiert neben dem Interface Se r i a l i z a b l e auch den I ma g e Ob s e r v e r . Die Methode aus dem I ma g e Ob s e r v e r , die zu implementieren ist, heißt: b o o l e a n i ma g e Up d a t e ( I ma g e i mg , i n t f l a g s , i n t x , i n t y , i n t w, i n t h )
Schauen wir in die a d d I ma g e ( ) Methode vom Me d i a Tr a c k e r hinein, wie ein Element in die Liste eingefügt wird: h e a d = Me d i a En t r y . i n s e r t ( h e a d , n e w I ma g e Me d i a En t r y ( t h i s , i ma g e , i d , w, h ) ) ; • • 408 •• • •
Zunächst wird ein neues I ma g e Me d i a En t r y -Objekt mit dem Zeiger auf den Me d i a Tr a c k e r , dem zu ladenden Bild (i ma g e ), ID und Ausmaßen erzeugt. Dann fügt die statische Methode i n s e r t ( ) der abstrakten Klasse Me d i a En t r y dieses Element in die Listen-Klasse hinzu. Nun wollen wir ergründen, warum wir dem Konstruktor der Me d i a Tr a c k e r -Klasse eine Komponente übergeben mussten. Diese Komponente – abgelegt als Exemplarvariable t a r g e t . Keiner der Methoden von Me d i a Tr a c k e r braucht dies; eigentlich klar. Doch beim Laden des Bildes durch die Klasse I ma g e Me d i a En t r y wird eine Co mp o n e n t verlangt. Die beiden Funktionen sind g e t St a t u s ( ) und s t a r t Lo a d ( ) . Denn genau an diesen Stellen muss der Status des Bildladens zurückgegeben werden beziehungsweise das Laden begonnen werden. Und dies macht p r e p a r e I ma g e ( ) bzw. c h e c k I ma g e ( ) , und beides sind nun mal Methoden von Co mp o n e n t . Doch diese beiden Methoden brauchen nun mal einen I ma g e Ob s e r v e r . Also implementiert auch I ma g e Me d i a En t r y das Interface des I ma g e Ob s e r v e r s. Stellt sich nur die Frage, warum dieser überhaupt implementiert werden muss. Dies ist aber ganz einfach: Die Methode g e t St a t u s ( ) ruft c h e c k I ma g e ( ) auf um den Status den Bildes zu holen, s t a r t Lo a d ( ) nutzt p r e p a r e I ma g e ( ) um das Bild zu laden und, was noch übrigbleibt, i ma g e Up d a t e ( ) aus dem I ma g e Ob s e r v e r , der dann mittels s e t St a t u s ( ) die Flags setzt. Denn dies ist der einzige, der den Ladevorgang überwacht, also ist er der einzige, der den Status ändern kann. Überlegen wir uns, was passieren müsste, damit neue Medienelemente hinzugefügt werden könnten. Zuerst einmal sollte ein neuer Konstruktor her, einer, der keine Komponenten verlangt. Dann kann eine neue Ableitung von Me d i a En t r y ein neues Medien-Objekt aufnehmen. Jetzt sind lediglich die Methoden g e t Me d i a ( ) und s t a r t Lo a d ( ) zu implementieren und fertig ist der neue Me d i a Tr a c k e r .
12.10.3 Kein Flackern durch Double- Buffering Zeichen wir komplexe Grafiken, dann fällt beim Ablauf des Programms deutlich auf, dass der Zeichenvorgang durch Flackern gestört ist. Dieses Flackern tritt in zwei Fällen auf. n Wenn wir Bildschirminhalte verschieben und Teile verdeckt werden, muss über die u p d a t e ( ) und p a i n t ( ) Methode der verdeckte Bildausschnittneu aufgebaut werden. n In der p a i n t ( ) Methode kommen oft rechenintensive Zeichenoperationen vor und das Bild muss mittels der Grafikoperationen neu aufgebaut werden. Zeichnen wir ein Dreieck, so müssen wie drei Linien zeichnen. Aber während die Linien gezeichnet werden, fährt der Rasterstrahl mehrmals über den Schirm und bei jedem Rasterdurchlauf sehen wir ein neues Bild, welches immer einen Teil mehr von sich preisgibt. Bei aufwändigen Zeichenoperationen sind nun viele Rasterstrahldurchläufe nötig bis das Bild komplett ist.
D ou ble - Bu ffe r in g Eine einfache und elegant e Met hode, diesem Flack er n zu ent k om m en, ist die Technik des Double- Buffer ing. Eine zw eit e Zeichenebene w ir d angelegt und auf dieser dann gezeichnet . I st die Zeichnung k om plet t w ir d sie zur passenden Zeit in den sicht bar en Ber eich hineink opier t .
Über Double-Buffering vermeiden wir zusätzliche Zeichenoperationen auf der sichtbaren Fläche, in dem wir alle Operationen auf einem Hintergrundbild durchführen. Immer dann, wenn das Bild, beispielsweise eine Konstruktionszeichnung, fertig ist, kopieren wir das Bild in den Vordergrund. Dann kann nur noch bei dieser Kopiermethode Flackern auftreten. Glücklicherweise ist das Zeichnen auf Hintergrundbildern nicht schwieriger als auf Vordergrundbildern, denn die Operationen sind auf beliebigen I ma g e s erlaubt. • • • 409 • • •
Zunächst benötigen wir einen Offscreen-Buffer für Grafik als I ma g e Objekt, auf dem wir die Zeichenoperationen angewenden. Zum Beispiel durch die folgenen Zeilen: Gr a p h i c s o f f s c r e e n Gr a p h i c s ; I ma g e o f f s c r e e n I ma g e ;
Innerhalb der p a i n t ( ) Methode – oder bei einem Applet gerne in der i n i t ( ) Funktion – erzeugen wir die Zeichenfläche mit der Funktion c r e a t e I ma g e ( ) . Die Größe der Fläche muss übergeben werden, wir können aber über die g e t Si z e ( ) Methode, die alle von Co mp o n e n t abgeleiteten Objekte implementieren, erfragen. Neben dem Bild müssen wir noch das Gr a p h i c s -Objekt initialisieren: o f f s c r e e n I ma g e = c r e a t e I ma g e ( 4 0 0 , 4 0 0 ) ; o f f s c r e e n Gr a p h i c s = o f f s c r e e n I ma g e . g e t Gr a p h i c s ( ) ;
Wo wir vorher innerhalb der p a i n t ( ) Methoden immer die Grafikoperationen mit dem Gr a p h i c s g der Methode p a i n t ( ) benutzten, ersetzen wir dieses g durch o f f s c r e e n Gr a p h i c s . Unsere Zeichenoperationen verschieben wie von der p a i n t ( ) Methode in eine eigene Methode, zum Beispiel o f f Pa i n t ( ) . So werden die drei Linien in der p a i n t ( ) Methode publ { g. g. g. }
i c v o i d p a i n t ( Gr a p h i c s g ) d r a wLi n e ( 1 0 , 2 0 , 1 0 0 , 2 0 0 ) ; d r a wLi n e ( 1 0 0 , 2 0 0 , 6 0 , 1 0 0 ) ; d r a wLi n e ( 6 0 , 1 0 0 , 1 0 , 2 0 ) ;
zu p r i v a t e v o i d o f f Pa i n t ( ) { o f f s c r e e n Gr a p h i c s . d r a wLi n e ( 1 0 , 2 0 , 1 0 0 , 2 0 0 ) ; o f f s c r e e n Gr a p h i c s . d r a wLi n e ( 1 0 0 , 2 0 0 , 6 0 , 1 0 0 ) ; o f f s c r e e n Gr a p h i c s . d r a wLi n e ( 6 0 , 1 0 0 , 1 0 , 2 0 ) ; }
Die Urimplementation der u p d a t e ( ) Methode ist so programmiert, dass sie den Bildschirm löscht und anschließend p a i n t ( ) aufruft. Genauer: Der Code der u p d a t e ( ) Methode ist in Co mp o n e n t durch den Zweizeiler p u b l i c v o i d u p d a t e ( Gr a p h i c s g ) { c l e a r Ba c k g r o u n d ( ) ; pa i nt ( g ) ; }
gegeben. c l e a r Ba c k g r o u n d ( ) zeichnet ein gefülltes Rechteck in der Hintergrundfarben über die Zeichenfläche. Auch dieses Löschen ist für das Flackern verantwortlich. Es macht aber Sinn, aus der u p d a t e ( ) Methode sofort p a i n t ( ) aufzurufen. Die meisten Applikationen Überschreiben daher die Implementierung von u p d a t e ( ) . p u b l i c v o i d u p d a t e ( Gr a p h i c s g ) { pa i nt ( g ) ; • • 410 •• • •
}
Somit fällt das lästige und zeitkostende Bildschirmlöschen weg. Da in unserer p a i n t ( ) Methode ohnehin das gesamte Rechteck gezeichnet wird können keine Bereiche ungeschrieben bleiben. Der Code der p a i n t ( ) Methode ist daher nicht mehr spektakulär. Wir haben die Grafik im Hintergrund aufgebaut und sie muss nun in den eigentlichen Zeichenbereich mit d r a wI ma g e ( ) kopiert werden. Aus p a i n t ( ) heraus haben wir den aktuellen Gr a p h i c -Kontext g und dann zeichnet p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { i f ( o f f s c r e e n I ma g e ! = n u l l ) g . d r a wI ma g e ( o f f s c r e e n I ma g e , 0 , 0 , t h i s ) ; }
des Bild. Wohlbemerkt ist dieser Funktionsaufruf der einzige in p a i n t ( ) .
12.11 Von Produzent en, Konsum ent en und Beobacht ern Bisher kamen die angezeigten Grafiken irgendwie vom Datenträger auf den Schirm. Im Folgenden wollen wir dies etwas präziser betrachten. Schon an den verschiedensten Stellen haben wir von der Eigenschaft der d r a wI ma g e ( ) Methode gesprochen, erst bei der ersten Benutzung das Bild zu laden. Die Image Klasse versteckt dabei jedes Detail des Ladevorganges und die Methode d r a wI ma g e ( ) zeichnete. In Java kommt hinter den Kulissen ein Modell zur Tragen, welches komplex aber auch sehr leistungsfähig ist. Es ist das Modell vom Erzeuger (engl. Producer) und Verbraucher (engl. Consumer). Ein Beispiel aus der realen Welt: Lakritze wird von Haribo produziert und von mir konsumiert. Oder etwas technischer: Ein Objekt, welches vom Netzwerk eine Grafik holt oder auch ein Objekt, welches aus einem Array mit Farbinformationen das Bild aufbaut. Und der Consumer ist die Zeichenfunktion, die das Bild darstellten möchte.
12.11.1 Producer und Consum er für Bilder Ein besonderer Produzent, der sich um alles kümmert was das Bilderzeugen angeht, ist der Image Producer. Im Gegensatz dazu sind es die Image Consumer, die etwaige Bilddaten benutzen. Zu diesen Bild Konsumenten zählen in der Regel Low-Level Zeichenroutinen, die auch die Grafik auf den Schirm bringen. In der Bibliothek von Java ist die Aufgabe der Bild Produzenten und Konsumenten durch die Schnittstelle I ma g e Pr o d u c e r und I ma g e Co n s u me r abgebildet. Das Interface I ma g e Pr o d u c e r beschreibt Methoden, um Pixel eines Bildes bereitzustellen. Klassen, die nun die Schnittstelle implementieren, stellen somit die Bildinformationen einer speziellen Quelle da. Die Klasse Me mo r y I ma g e So u r c e ist eine vorgefertigte Klasse, die I ma g e Pr o d u c e r implementiert. Sie produziert Bildinformationen aus einem Array von Pixeln, die im Speicher gehalten werden. Im Gegenzug beschreibt die Schnittstelle I ma g e Co n s u me r Methoden, die einem Objekt den Zugriff auf die Bilddaten des Produzenten erlauben. Objekte, die ImageConsumer implementieren, hängen somit immer an einem Bilderzeuger. Der Produzent liefert die Daten über Methoden zum Konsumenten, in dem spezielle – im Interface I ma g e Co n s u me r vorgeschriebene – Methoden aufgerufen werden.
• • • 411 • • •
12.11.2 Beispiel für die Überm it t lung von Dat en Damit für uns das Verfahren deutlich wird, beschreiben wir zunächst das Prinzip der Übermittlung von Daten vom Produzenten zum Konsumenten an einem Beispiel. Wir entwickeln eine Klasse Produzent mit einer Methode b e g i n n e ( ) und eine Klasse Ko n s u me n t , der vom Produzenten Daten haben möchte. Wenn der Produzent etwas für den Konsumenten erzeugen soll, dann ruft der Konsument die e r z e u g e Fü r ( ) Routine mit einem Verweis auf sich auf. Danach ruft der Konsument die Funktion b e g i n n e ( ) auf. Über diesen Verweis an e r z e u g e Fü r ( ) weiß dann der Produzent, an wen er die Daten schicken muss. Nach dem Aufruf von b e g i n n e ( ) sendet der Produzent an alle Konsumenten die Daten, in dem er die Methode b r i e f k a s t e n ( ) aller Konsumenten aufruft und somit die Daten abliefert. c l a s s Ko n s u me n t { i r g e n d wo ( ) { Pr o d u z e n t n u d e l n n u d e l n . e r z e u g e Fü r ( t h i s ) nude l n. be gi nne ( ) } br i e f ka s t e n( i nt da t a ) { a u s g a b e ( " I c h h a b e e i n " + d a t a + " b e k o mme n " ) } } c l a s s Pr o d u z e n t { e r z e u g e Fü r ( Ko n s u me n t e i n Ko n s u me n t ) { me r k e s i c h a l l e Ko n s u me n t e n i n e i n e r Li s t e } be gi nne ( ) { d a t a = e r z e u g e Da t u m( ) f ü r a l l e i n t e r e s s i e r t e n Ko n s u me t e n k o n s u me n t . b r i e f k a s t e n ( d a t a ) } }
Wie der I m ageProducer dem I m ageConsum er die Dat en beschreibt Das Interface I ma g e Pr o d u c e r benutzt die Methode s e t Pi x e l s ( ) im I ma g e Co n s u me r um das Bild dem Konsumenten zu beschreiben. Ein gutes Beispiel für das Modell ist das Laden eines Bildes über ein Netzwerk. So verlangt etwa die Zeichenfunktion d r a wI ma g e ( ) das Bild. Nehmen wir eine konkrete Klasse an, die ein Bild laden kann. Diese implementiert natürlich dann das Interface I ma g e Pr o d u c e r . Zunächst beginnt dann die Klasse mit dem Lesevorgang, in dem sie eine Netzwerkverbindung aufbaut und einen Kommunikationskanal öffnet. Das erste was das Programm dann vom Server liest ist die Breite und Höhe des Bildes. Seine Informationen über die Dimension
• • 412 •• • •
berichtet sie dem Konsumenten mit der Methode s e t Di me n s i o n s ( ) . Uns sollte bewusst sein, dass es zu einem Produzenten durchaus mehrere Konsumenten geben kann. Korrekter hieße das: Die Information über die Dimension wird zu allen horchenden Konsumenten gebracht. Als nächstes liest der Produzent die Farbinformationen für das Bild. Über die Farbtabelle findet er heraus, welches Farbmodell das Bild benutzt. Dies teilt er über den Aufruf von s e t Co l o r Mo d e l ( ) jeden Consumer mit. Danach lassen sich die Pixel des Bildes übertragen. Die verschieden Formate nutzen dabei allerdings unterschiedliche Techniken. Sie heißen Hints. Die Übermittlung der Hints an den Consumer geschieht mit der Methode s e t Hi n t s ( ) . Jeder Consumer kann daraufhin seine Handhabung mit den Bildpunkten optimieren. So könnte etwa ein Konsument, der ein Bild skalieren soll, genau in dem Moment die Bildzeile skalieren und die Werte neu berechnen, während der Produzent eine Zeile erzeugt. Mögliche Werte für die Hints sind: a b s t r a c t i n t e r f a c e j a v a . a wt . i ma g e . I ma g e Co n s u me r Ÿ I ma g e Co n s u me r . TOPDOWNLEFTRI GHT
Die Pixellieferung ist von oben nach unten und von links nach rechts. Ÿ I ma g e Co n s u me r . COMPLETESCANLI NES
Mehrere Zeilen (Scanlinles) bauen das Bild auf. Eine Scanline besteht aus mehreren Pixels die dann in einem Rutsch anliegen. Es wird also sooft s e t Pi x e l s ( ) aufgerufen wie es Bildzeilen gibt. Ÿ I ma g e Co n s u me r . SI NGLEPASS
Die Pixel des gesamten Bildes können wir nach einem Aufruf von s e t Pi x e l s ( ) erwarten. Niemals liefern mehrere Aufrufe dieselben Bildinformationen. Ein progressive JPEG Bild fällt nicht in diese Kategorie, da es ja in mehreren Durchläufen erst komplett vorliegt. Ÿ I ma g e Co n s u me r . SI NGLEFRAME
Das Bild besteht aus genau einem statischen Bild. Ein Programm, welches also nicht schrittweise Zeilen zur Verfügung stellt, benutzt dieses Flag. Der Consumer ruft also einmal setPixels() vom Producer auf und danach steht das Bild bereit. Ein Bild aus eine Videoquelle würde, da es sich immer wieder ändert, niemals SI NGLEFRAME sein. Ÿ I ma g e Co n s u me r . RANDOMPI XELORDER
Die Bildpunkte kommen in beliebiger Reihenfolge an. Der ImageConsumer kann somit keine Optimierung vornehmen, die von der Reihenfolge der Pixel abhängt. Ohne Bestätigung einer anderen Reihenfolge müssen wir von RANDOMPI XELORDER ausgehen. Erst nach Abschluss durch einen Aufruf von i ma g e Co mp l e t e ( ) - siehe unten – lässt sich mit dem Bild weiterarbeiten. Nun kann der Producer anfangen mittels s e t Pi x e l s ( ) Pixel zu produzieren. Da der Poducer die s e t Pi x e l s ( ) Methode aufruft, die im Consumer implementiert ist, wird der Kosument dementsprechend all den Programmcode enthalten, der die Bildinformationen benötigt. Wir erinnern uns entsprechend an die Methode b r i e f k a s t e n ( ) von unserem ersten Beispiel. Wir haben damals nur das erlangte Datum ausgegeben. Ein wirklicher Konsument allerdings sammelt sich alle Daten bis das Bild geladen ist, und verwendet es dann weiter, in dem er es zum Beispiel anzeigt. In der Regel ist erst nach vielen Aufrufen das Bild aufgebaut, genau dann, wenn der Consumer jeweils nur eine Zeile des Bildes liefert. Es kann aber auch nur ein Aufruf genügen, nämlich genau dann, wenn das Bild in einem Rutsch geliefert wird (I ma g e Co n s u me r . SI NGLEPASS ). Nachdem das Bild geladen ist, ruft der Producer die i ma g e Co mp l e t e ( ) Methode für den Konsumenten auf, um anzuzeigen, dass das Bild geladen ist. Nun sind also keine Aufrufe mehr für s e t Pi x e l s ( ) möglich um das Bild vollständig zu erhalten. Der Methode i ma g e Co mp l e t e ( ) wird • • • 413 • • •
immer ein Parameter übergeben und für ein gültig Bild ist der Parameter I ma g e Co n s u me r . STATI CI MAGEDONE. Auch Multi-Frames-Images (etwa Animated GIF) ist dies gestatte und zeigt an, dass das letzte Bild der Sequenz geladen ist. Besteht das Bild aus mehreren Teilen, es folgen aber noch weitere Frames, ist der Parameter SI NGLEFRAMEDONE. Hier zeigt SI NGLEFRAMEDONE also nur den Abschluss eines Einzelbildes an. Über s e t Hi n t s ( ) ist dann aber schon ein Multi-Frame angekündigt gewesen. Mehrere Fehler können beim Produzieren auftreten. Es zeigt I MAGEERROR bzw. I MAGEABORT an, dass ein schwerer Fehler auftrat und das Bild nicht erzeugt werden konnte. Die Unterscheidung der beiden Fehlerquellen ist nicht eindeutig. a b s t r a c t i n t e r f a c e j a v a . a wt . i ma g e . I ma g e Co n s u me r Ÿ v o i d i ma g e Co mp l e t e ( i n t s t a t u s ) Wird aufgerufen, wenn der I ma g e Pr o d u c e r alle Daten abgeliefert hat. Auch, wenn ein
einzelner Rahmen einer Multi-Frame Animation beendet ist oder ein Fehler auftrat. Ÿ v o i d s e t Co l o r Mo d e l ( Co l o r Mo d e l mo d e l ) Das ColorModel bestimmt, wie s e t Pi x e l s ( ) die Pixelinformationen wertet. Ÿ v o i d s e t Di me n s i o n s ( i n t wi d t h , i n t h e i g h t )
Die Ausmaße der Bildquelle. Ÿ v o i d s e t Hi n t s ( i n t h i n t f l a g s )
Reihenfolge der Bildinformationen. Ÿ v o i d s e t Pi x e l s ( i n t x , i n t y , i n t w, i n t h , Co l o r Mo d e l mo d e l , byt e [ ] pi xe l s , i nt of f , i nt s c a ns i z e )
Die Bildpunkte werden durch einen oder mehrer Aufrufe der Funktion überliefert. Ÿ v o i d s e t Pi x e l s ( i n t x , i n t y , i n t w, i n t h , Co l o r Mo d e l mo d e l , i nt [ ] pi xe l s , i nt of f , i nt s c a ns i z e )
Die Bildpunkte werden durch einen oder mehrer Aufrufe der Funktion überliefert. Ÿ v o i d s e t Pr o p e r t i e s ( Ha s h t a b l e p r o p s )
Setzt eine Liste von Eigenschaften, die mit dem Bild verbunden sind. Dies kann etwa eine Zeichenkette über den Bilderzeuger sein, die Geschwindigkeit eines Bildaufbaus oder die Information, wieviel Konsumenten an einem Produzenten hängen können.
12.11.3 Ein PPM Grafik Lader als I m ageConsum er Um ein Beispiel für einen ImageConsumer anzuführen wollen wir uns kurz mit dem Portable Pixmap (PPM) Dateiformat beschäftigen. PPM ist ein Teil der Extended Portable Bitmap Utilities (PBMPLUS). Es dient als Speicherformat zum Ablegen der Farbinformationen in Bitmapdaten, welche vom Tool PBMPLUS erzeugt werden. Die erzeugten Bildinformationen sind entweder als ASCII Werte angegeben (Ascii encoded) oder binär kodiert (Binary encoded). Wir betrachten einmal den Kopf einer ASCII kodierten Datei. P3 # Cr e a t e d b y Pa i n t Sh o p Pr o 339 338 255 41 88 46 35 83 43 42 89 53 41 90 58 28 76 50 24 72 50 34 77 58 35 76 60 • • 414 •• • •
38 77 59 63 100 82 53 91 66 21 62 30 37 82 43 30 79 32 51 104 50 67 121 69 ... 30 21 12 23 14 5 23 14 5 34 25 16 28 19 10 25 16 7 34 25 16 27 18 9 21 14 6 33 26 18 36 29 21
Das Kürzel P3 zeigt die ASCII Kodierung der Daten an. Es folgt ein Kommentar, welches mit dem Hash Symbol beginnt. Dieses Kommentar gilt für die ganze Zeile. Es folgen die Ausmaße der Grafik, zuerst die Breite und dann die Höhe. Unser Bild des Malers Macke hat die Ausdehnung von 339 Pixeln in der Breite und 338 Pixeln in der Höhe. Das nachfolgende Wort gibt die Farbtiefe an, die im Beispiel bei 255 liegt (ordinal gezählt). Es folgen die Farbwerte der einzelnen Pixel; als Rot, Grün, Blau Tupel. Der erste Farbton setzt sich somit aus den Komponenten 41, 88, 46 zusammen. Für jede der 338 Zeilen sind 339 Farbwerte im Spiel. Sie müssen nicht in einer physikalischen Zeile in der Datei kodiert werden. Paint Shop Pro trennt die Zeilen alle mit einem Return und einem Zeilenvorschub, also mit zwei Zeichen (0x0d, 0x0a). Dies ist aber nicht vorgeschrieben. Anderes ist die binäre Kodierung der PPM-Dateien. Ein Ausschnitt aus der Datei mit dem gleichen Bild: P6 # Cr e a t e d b y Pa i n t Sh o p Pr o 339 338 255 ) X. # S+* Y5 ) Z: L2 H2 " M: # L- %R+- O 3 h 2 Cy E# U/ H| X; u M&d ;
Wieder sind Ausmaße und Farbtiefe deutlich zu erkennen. Das erste Wort ist allerdings P6 und nicht mehr P3. P6 zeigt dem einlesenden Programm an, dass nun hinter der Farbtiefe nicht mehr mit ASCII-Zahlenwerten zu rechnen ist. Vielmehr folgen Binärzahlen und wiederum drei für einen Pixel, aufgegliedert nach Rot, Grün und Blau. Eine Applikation, die dies nun behandeln möchte, muss beide Verfahren beherrschen und kann am ersten Wort erkennen, worauf sie sich einzustellen hat. Da das Datenvolumen bei ASCII Kodierten Daten viel höher als bei der binären Speicherung ist, sollte dies das vorherrschende Format sein. Die Größen der Macke Bilder im Überblick: MakkeAscii.ppm mit 1.041 KB und MackeBin.ppm mit 336 KB. Beim Laden fällt dies besonders auf, denn die ASCII-Werte müssen erst konvertiert werden. So stehen auch die Ladenzeiten krass gegenüber: ASCII kodiert 6360 ms und binär kodiert 170 ms. Somit ist die Binär-Variante fast 40 mal schneller. Ein Programm zum Einlesen findet der Leser in den Quellcodedateien. Der Kern basiert auf einer Implementierung von John Zukowski, vorgestellt im Buch ›Java AWT Reference‹. Die Idee vom Consumer findet hier Anwendung.
12.11.4 Bilder selbst erst ellen Bisher haben wir über unsere bekannten Zeichenfunktionen wie d r a wLi n e ( ) und so weiter auf die Oberfläche gezeichnet. Die p a i n t ( ) Methode gab uns den Grafikkontext in die Hand, mit dem wir die Operation durchführen konnten. Nun kann es aber von Vorteil sein, wenn wir direkt in eine Zeichenfläche malen könnten und nich immer über die Elementarfunktionen gehen müssten. Es ist intuitiv klar, dass dieser Weg bei bestimmten Grafikoperationen schneller ist. So können wir nicht existierende Grafikfunktionen – beispielsweise eine ›weiche Linie‹ – durch Punktoperationen direkt auf dem Raster durchführen, ohne immer die d r a wLi n e ( ) und s e t Co l o r ( ) Funktionen für einen Punkt zu bemühen. Wesentlich schneller sind wir wieder mit Bildern im Hintergrund, so wie wir im letzten Abschnitt flackerfreie Bilder produzierten.
• • • 415 • • •
Was wir dazu brauchen ist eine Klasse aus dem a wt . i ma g e -Paket, Me mo r y I ma g e So u r c e . Quellcode 12.k
MemImage.java
i mp o r t j a v a . a wt . * ; i mp o r t j a v a . a wt . i ma g e . * ; c l a s s Me mI ma g e e x t { f i na l s t a t i c i nt f i na l s t a t i c i nt f i na l s t a t i c i nt i n t i ma g e Da t a [ ] a, a, a, a, a, a, a, a, a, a, a, a, a, a, a , a , a , a , a , b, b, a , a , a , b, b, b, c , a , a , b, b, c , c , c , a , b, b, c , c , b, b, a , b, c , b, b, b, b, b, b, c , b, b, b, b, b, c , b, b, b, b, b, b, c , b, b, b, b, b, b, c , b, b, b, b, b, b, c , b, b, b, b, b, b, c , b, b, b, b, b, b, b, c , b, b, b, b, a , b, c , b, b, b, c , a , b, b, c , c , b, c , a , a , b, b, c , c , b, a , a , a , b, b, b, c , a , a , a , a , a , b, b, a, a, a, a, a, a, a, a, a, a, a, a, a, a, };
e n d s Fr a me a = Co l b = Co l c = Co l = { a , a , a , b, b, b, b, b, b, c , c , c , c , b, b, c , b, b, c , c , b, b, c , c , b, b, c , c , b, b, b, c , b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, c , c , b, b, c , c , c , b, c , c , c , b, c, c, c, c, c, c, c, c, b, c , c , c , b, b, b, b, a , a , a , b,
o r . wh i t e . g e t RGB( ) ; o r . b l a c k . g e t RGB( ) ; o r . y e l l o w. g e t RGB( ) ; b, b, c, c, c, c, c, c, b, b, b, b, b, b, c, c, c, c, c, b, b,
b, c, c, c, c, c, c, b, b, b, b, b, b, b, c, c, c, c, c, c, b,
b, c, b, b, b, b, b, b, b, b, b, b, b, b, b, c, c, c, c, c, b,
b, c, c, b, c, b, b, b, b, b, b, b, b, b, b, b, c, c, c, c, b,
b, c, c, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, c, c, b,
b, c, c, b, c, b, b, b, b, b, b, b, b, b, b, b, c, c, c, c, b,
b, c, b, b, b, b, b, b, b, b, b, b, b, b, b, c, c, c, c, c, b,
b, c, c, c, c, c, c, b, b, b, b, b, b, b, c, c, c, c, c, c, b,
b, b, c, c, c, c, c, c, b, b, b, b, b, b, c, c, c, c, c, b, b,
b, b, c, c, c, c, c, c, b, b, b, b, b, b, b, b, c, c, c, b, b,
a, b, c, b, c, c, c, b, b, b, b, b, b, b, c, c, c, c, c, b, a,
a, b, c, b, b, b, b, b, b, b, b, b, b, c, c, c, c, c, c, b, a,
a, b, b, c, b, b, b, b, b, b, b, b, b, c, c, c, c, c, b, b, a,
a, a, b, c, c, b, b, b, b, b, b, b, b, b, c, c, b, c, b, a, a,
a, a, b, b, c, b, b, b, b, b, b, b, b, b, b, b, c, b, b, a, a,
a, a, a, b, c, c, b, b, b, b, b, b, b, b, b, c, c, b, a, a, a,
I ma g e i c o n ; Me mI ma g e ( ) { s u p e r ( " Mi t f r e u n d l i c h e r Un t e r s t u e t z u n g v o n . . . " ) ; i c o n = c r e a t e I ma g e ( n e w Me mo r y I ma g e So u r c e ( 3 2 , 2 1 , i ma g e Da t a , 0 , 3 2 ) ) ; s e t Ba c k g r o u n d ( Co l o r . wh i t e ) ; s e t Si z e ( 3 0 0 , 3 0 0 ) ; s h o w( ) ; } p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { g . d r a wI ma g e ( i c o n , 1 0 0 , 1 0 0 , 6 4 , 6 4 , t h i s ) ; } p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Me mI ma g e mi = n e w Me mI ma g e ( ) ; } • • 416 •• • •
a, a, a, b, b, c, b, b, b, b, b, b, b, b, b, c, b, b, a, a, a,
a, a, a, a, b, b, c, c, b, b, b, b, b, c, c, b, b, a, a, a, a,
a, a, a, a, a, b, b, b, c, c, c, c, c, b, b, b, a, a, a, a, a,
a, a, a, a, a, a, a, b, b, b, b, b, b, b, a, a, a, a, a, a, a,
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a
}
Bildpunkt e ansprechen Zunächst wird das Bild im Speicher, dass heißt in einem Integer-Feld, gezeichnet. So bereiten die drei Zeilen i nt br e i t e = 100; i nt höhe = 100; i nt pi xe l s [ ] = ne w i nt [ br e i t e * höhe ] ;
ein Ganzzahl-Array mit 100 mal 100 Bildpunkten vor. Da die Farben durch die Grundfarben Rot, Grün und Blau in den Abstufungfen 0-255 kodiert werden können an bestimmten Stellen einer 24Bit-Zahl einen Farbwert repräsentieren, lässt sich einfach durch p i x e l s [ x * wi d t h + y ] = ( g 1 6 ) & 0 x f f ; >> 8 ) & 0 x f f ; ) & 0xf f ;
c l a s s j a v a . a wt . i ma g e . Pi x e l Gr a b b e r i mp l e me n t s I ma g e Co n s u me r Ÿ Pi x e l Gr a b b e r ( I ma g e , i n t x , i n t y , i n t Br e i t e , i n t Hö h e , i n t Fe l d [ ] , i n t Ve r s c h i e b u n g , i n t Sc a n s i z e ) Erzeugt ein Pi x e l Gr a b b e r -Objekt, welches ein Rechteck von RGB-Farben aus dem Feld holt. Das Rechteck ist durch die Ausmaße x , y , Br e i t e , Hö h e beschrieben. Die Farben für einen Punkt (i,j) sind im Feld an der Position (j - y ) * Sc a n s i z e + (i - x ) + Ve r s c h i e b u n g . Mit der Umwandlung wird noch nicht begonnen. Sie muss mit der Funktion g r a b Pi x l e s anregt
werden. Ÿ b o o l e a n g r a b Pi x e l s ( ) t h r o ws I n t e r r u p t e d Ex c e p t i o n Die Werte von einem I ma g e oder I ma g e Pr o d u c e r werden geholt. Da das Kodieren einige Zeit in Anspruch nimmt, kann die Funktion von außen unterbrochen werden. Daher ist eine t r y Anweisung notwending, die I n t e r r u p t e d Ex c e p t i o n abfängt. Ging alles gut, wird t r u e
zurückgegeben. Ÿ i n t g e t He i g h t ( )
Liefert die Höhe des Pixelfeldes. Ist die Höhe nicht verfügbar, ist das Ergebnis - 1 . Ÿ i n t g e t Wi d t h ( )
Liefert die Breite eines Pixelfeldes – ist diese nicht verfügbar, ist das Ergebnis - 1 .
Ein Grabber Beispiel Das nachfolgende Programm lädt ein Bild und gibt die Farbinformationen – also die Anteile Rot, Grün, Blau – auf der Konole aus. Dabei müssen wir nur in das Bild klicken. Um das Bild zu laden, nutzen wir eine Methode aus dem Swing, die später noch genauer erklärt wird. An dieser Stelle implementieren wir auch eine einfache Ereignis-Behandlung. Die Implementierung ist zawr nicht mehr zeitgemäß, dafür aber einfach. Quellcode 12.k
DuAlterGrabber.java
i mp o r t j a v a x . s wi n g . * ; i mp o r t j a v a . a wt . * ; i mp o r t j a v a . a wt . i ma g e . * ; c l a s s Du Al t e r Gr a b b e r e x t e n d s Fr a me { I ma g e i ma g e ; i n t wi d t h , h e i g h t ; i nt pi xe l s [ ] ; Du Al t e r Gr a b b e r ( ) { i ma g e = n e w I ma g e I c o n ( " Ma c k e . j p g " ) . g e t I ma g e ( ) ; • • 420 •• • •
wi d t h = i ma g e . g e t Wi d t h ( t h i s ) ; h e i g h t = i ma g e . g e t He i g h t ( t h i s ) ; p i x e l s = n e w i n t [ wi d t h * h e i g h t ] ; Pi x e l Gr a b b e r g r a b b e r = n e w Pi x e l Gr a b b e r ( i ma g e , 0 , 0 , wi d t h , h e i g h t , p i x e l s , 0 , wi d t h ) ; try { g r a b b e r . g r a b Pi x e l s ( ) ; } c a t c h ( I n t e r r u p t e d Ex c e p t i o n e ) { Sy s t e m. e r r . p r i n t l n ( " Er r o r g e t t i n g p i x e l s " ) ; } s e t Si z e ( wi d t h , h e i g h t ) ; } p u b l i c b o o l e a n h a n d l e Ev e n t ( Ev e n t e ) { i f ( e . i d == Ev e n t . MOUSE_ DOWN ) { i n t p i x e l = p i x e l s [ e . y * wi d t h + e . x ] ; i n t a l p h a = ( p i x e l >> 2 4 ) & 0 x f f ; i nt r e d = ( p i x e l >> 1 6 ) & 0 x f f ; i n t g r e e n = ( p i x e l >> 8 ) & 0 x f f ; i nt bl ue = ( pi xe l ) & 0xf f ; Sy s t e m. o u t . p r i n t l n ( " R=" +r e d + " G=" +g r e e n + " B=" +b l u e ) ; } r e t ur n f a l s e ; } p u b l i c v o i d p a i n t ( Gr a p h i c s g ) { i f ( i ma g e ! = n u l l ) g . d r a wI ma g e ( i ma g e , 0 , 0 , t h i s ) ; } p u b l i c s t a t i c v o i d ma i n ( St r i n g a r g s [ ] ) { Fr a me f = n e w Du Al t e r Gr a b b e r ( ) ; f . s h o w( ) ; } }
12.12 Alles wird bunt m it Farbm odellen Als wir uns mit dem Produzenten- und Konsumenten-Modell bei Image Objekten beschäftigt haben, standen die Daten über die Pixel immer in einem Byte- oder Integer-Feld. Eher übersprungen wurde das Farbmodell bei Me mo r y I ma g e So u r c e ( ) und einem c r e a t e I ma g e ( ) . Die Einträge der Felder sind Pixel und die Werte standen für Farbinformationen, genauer gesagt für Rot, Grün und Blau. Wir haben uns bisher wenig über Gedanken über das Format gemacht und haben • • • 421 • • •
stillschweigend angenommen, dass diese in 24 Bit abgelegt sein müssen. Dies muss jedoch nicht so sein und die Interpretation der Farbwerte in einem Informationswort bestimmt ein Farbmodell. Für Farbmodelle gibt es in Java die Klasse Co l o r Mo d e l . Mit der Klasse lassen sich dann aus einem Pixel die roten, grünen, blauen und transparenten Anteile bestimmen. Der transparente Teil, auch Alpha-Komponente genannt, bestimmt, in welcher Intensität die Farbinformationen wirken. AlphaWerte lassen sich nur in Zusammenhang mit Bildern anwenden. Mit der Graphics Klasse lässt sich ein Alpha-Wert nicht einstellen, der dann alte Zeichenoperationen beeinflusst. Bei den Farbmodellen ist der Anteil der Transparenz genauso wie ein Farbwert 8 Bit lang. Ein Wert von 255 sagt aus, dass der Farbwert zu 100% sichtbar ist. Ist der Wert 0, so ist die Farbe nicht zu sehen. Java macht das Programmierleben so plattformunabhängig wie möglich. Bei wenig oder vielen Farben auf der Zielplattform wird eine optimale Annäherung an unsere Wunschfarben errechnet. So können wir alles in 24-Bit Farbtiefe errechnen und die Dislay-Komponente sucht die wichtigsten Farben heraus und fasst Gruppen ähnlicher Farben zusammen.
12.12.1 Die abst rakt e Klasse ColorModel Die abstrakte Klasse Co l o r Mo d e l beschreibt alle Methoden für konkrete Farbklassen, so dass die Informationen über die Farbwerte und Transparenz erreichbar sind. Obwohl die Klasse abstrakt ist, besitzt sie zwei Konstruktoren, die von den Unterklassen benutzt werden. Direkte Unterklassen sind Co mp o n e n t Co l o r Mo d e l , I n d e x Co l o r Mo d e l und Pa c k e d Co l o r Mo d e l . a b s t r a c t c l a s s j a v a . a wt . i ma g e . Co l o r Mo d e l i mp l e me n t s Tr a n s p a r e n c y Ÿ Co l o r Mo d e l ( i n t p i x e l _ b i t s , i n t [ ] b i t s , Co l o r Sp a c e c s p a c e , b o o l e a n h a s Al p h a , b o o l e a n i s Al p h a Pr e mu l t i p l i e d , i n t t r a n s p a r e n c y , i n t t r a n s f e r Ty p e ) Ÿ Co l o r Mo d e l ( i n t b i t s )
Der zweite Konstruktor ist praktisch, da dieser nur die Farbtiefe in Bits erwartet. Diese abstrakte Klasse besitzt jedoch die Fabrik-Methode (die also statisch ist) g e t RGBd e f a u l t ( ) , die ein Co l o r Mo d e l Objekt zurückliefert. Das Standard-Farbmodell, auch sRGB genannt, ist ein Farbmodell, welches die Werte als 24 Bit Tupel mit den Komponenten Alpha, Rot, Grün und Blau hält. Dieses Farbmodell lässt sich etwa für ein Memory-Image einsetzen. Der erste Konstuktor ist noch leistungsfähiger und ist erst seit Java 1.2 dabei. Mit seiner Hilfe muss ein Farbwert nicht zwingend in einem Integer kodiert sein. Die Methode g e t Pi x e l Si z e ( ) liefert die Farbtiefe eines Farbmodells. Das Standard-Modell besitzt eine Tiefe von 32 Bit (24 für die Farben und dann noch der Alpha-Kanal). So ergibt auch die folgende Zeile als Anwort auf die Frage nach der Anzahl Farben im Standard-Modell 32: Sy s t e m. o u t . p r i n t l n ( Co l o r Mo d e l . g e t RGBd e f a u l t ( ) . g e t Pi x e l Si z e ( ) ) ;
Die Hauptaufgabe einer Farb-Modell-Klasse ist die Auswertung der Farbinformationen aus einem Speicherwort. Mit drei Methoden lassen sich die verschiedenen Farben auslesen. g e t Re d ( i n t p i x e l ) , g e t Gr e e n ( i n t p i x e l ) , g e t Bl u e ( i n t p i x e l ) . Zusätzlich kommt noch g e t Al p h a ( i n t p i x e l ) hinzu. Jede dieser Methoden ist abstrakt und liefert eine Ganzzahl mit dem Farbwert zurück. Wie wir später sehen werden ist das einfachste Modell das, was wir bisher immer benutzt haben. Dieses liest nämlich genau von den Stellen 24, 16 und 8 die Farbwerte aus. Da die Methoden abstrakt sind, müssen Unterklassen dieses Verhalten programmieren. • • 422 •• • •
Eine weitere Methode ist g e t RGB( ) , welche ein int mit allen Farben im entsprechenden Farbformat zurückliefert. Die Implementierung basiert auf den Anfrage-Methoden. p u b l i c i n t g e t RGB( i n t p i x e l ) { r e t u r n ( g e t Al p h a ( p i x e l ) > 8 ) ; }
Wir sehen auch an g e t Re d ( ) , dass der Pixel auch direkt ein Index für das private Feld r g b ist. Hieraus ergibt sich auch die Konsequenz, wenn der Index über die Feldgröße läuft. f i n a l p u b l i c i n t g e t Re d ( i n t p i x e l ) { r e t u r n ( r g b [ p i x e l ] >> 1 6 ) & 0 x f f ; }
Werfen wir nun einen Blick auf ein Programm, welches ein Bytefeld erzeugt und aus sechs Farben die Pixel in das Feld schreibt. Zum Schluss konvertieren wir das Bytefeld mit einem Me mo r y I ma g e So u r c e in ein Image Objekt. Für diese Klasse können wir ein I n d e x Co l o r Mo d e l angeben, dass dann folgendes Format hat: Co l o r Mo d e l c m = I n d e x Co l o r Mo d e l ( 8 , c o l o r Cn t , r , g , b ) ;
Hier handelt es sich um ein Farbmodell mit 8 Bits und 6 Farben. Die folgenden Werte zeigen auf die drei Felder mit den Farbwerten. Anschließend erzeugt c r e a t e I ma g e ( ) mit diesem Farbmodell das I ma g e Objekt. I ma g e i = c r e a t e I ma g e ( n e w Me mo r y I ma g e So u r c e ( w, h , c m, p i x e l s , 0 , w) ) ;
Quellcode 12.l
IndexColorModelDemo.java
i mp o r t j a v a . a wt . * ; i mp o r t j a v a . a wt . i ma g e . * ; • • 426 •• • •
p u b l i c c l a s s I n d e x Co l o r Mo d e l De mo e x t e n d s Fr a me { I ma g e i ; s t a t i c i nt w = 400, h = 400; i n t p i x e l s [ ] = n e w i n t [ w* h ] ; Co l o r c o l o r s [ ] = { Co l o r . r e d , Co l o r . o r a n g e , Co l o r . y e l l o w, Co l o r . g r e e n , Co l o r . b l u e , Co l o r . ma g e n t a };
I n d e x Co l o r Mo d e l De mo ( ) { i n t c o l o r Cn t = c o l o r s . l e n g t h ; b y t e r [ ] = n e w b y t e [ c o l o r Cn t ] , g [ ] = n e w b y t e [ c o l o r Cn t ] , b [ ] = n e w b y t e [ c o l o r Cn t ] ;
f or ( i { r[i] g[ i ] b[ i ] }
n t i =0 ; i