Programmieren in Java - oth

Transcription

Programmieren in Java - oth
Programmieren in Java
Prof. Jürgen Sauer
Programmieren in Java
Skriptum zur Vorlesung im WS 2004/2005
1
Programmieren in Java
Inhaltsverzeichnis
0. ÜBERSICHT.....................................................................................................................................11
0.1 Ziel der Vorlesung "Programmieren" ...................................................................................................... 11
0.2 Java-Maschine und Programmiersystem ................................................................................................. 11
0.3 Der erste Versuch........................................................................................................................................ 13
0.3.1 Das erste Programm ............................................................................................................................... 13
0.3.2 Bestandteile eines Programms ............................................................................................................... 19
1. EINFÜHRUNG IN DIE JAVA-PROGRAMMIERUNG......................................................................21
1.1 Übersicht zur Entwicklung der Programmiersprache Java ................................................................... 21
1.2 Was ist Java?............................................................................................................................................... 22
1.3 Einstieg in die Java-Programmierung ...................................................................................................... 25
1.3.1 Die Software für die Java-Programmierung .......................................................................................... 25
1.3.2 Applets und Anwendungen.................................................................................................................... 27
1.3.2.1 Entwicklung von Java-Anwendungen ............................................................................................ 27
1.3.2.2 Entwicklung von Java-Applets ....................................................................................................... 34
1.3.3 Programmablaufpläne und Strukturierte Programmierung .................................................................... 43
1.3.3.1 Darstellung von Algorithmen durch Programmablaufpläne ........................................................... 43
1.3.3.2 Darstellung von Algorithmen mit Struktogrammen........................................................................ 46
1.3.4 Datentyp und Datenstruktur ................................................................................................................... 52
1.4 Die Objektorientierung von Java .............................................................................................................. 55
1.4.1 Grundlegende Konzepte......................................................................................................................... 55
1.4.1.1 Zustand und Verhalten von Objekten, Klassen, Instanz- und Klassen-Variable bzw. -Methoden . 55
1.4.1.1.1 Die Abbildung von Zustand bzw. Verhalten in Instanzvariable bzw. Instanzmethoden ......... 55
1.4.1.1.2 Einfache Typen, Referenztypen, Automatisches Boxen .......................................................... 59
1.4.1.1.3 Konstruktoren .......................................................................................................................... 61
1.4.1.1.4 Zugriffsrechte auf Klassen, Variable und Methoden ............................................................... 62
1.4.1.1.5 Klassenvariable und Klassenmethoden.................................................................................... 64
1.4.1.1.6 Lokale Variable und Konstanten.............................................................................................. 65
1.4.1.1.7 Überladen von Methoden......................................................................................................... 66
1.4.1.2 Superklassen und Subklassen, Vererbung und Klassenhierarchie .................................................. 67
1.4.1.3 Referenzen, Referenztypen und Aufzählungstyp............................................................................ 72
1.4.1.4 Konvertieren von Objekten und Primitivtypen ............................................................................... 75
1.4.1.5 Manipulieren von Objekten ............................................................................................................ 76
1.4.1.6 Innere (lokale) und anonyme Klassen............................................................................................. 80
1.4.1.7 Schnittstellen und Pakete ................................................................................................................ 82
1.4.1.7.1 Schnittstellen............................................................................................................................ 82
1.4.1.7.2 Pakete....................................................................................................................................... 84
1.4.1.8 Polymorphismus und Binden .......................................................................................................... 88
1.4.2 Klassen des Pakets java.lang.................................................................................................................. 90
1.4.2.1 Die Klasse Object............................................................................................................................ 90
1.4.2.3 Die Klasse System .......................................................................................................................... 93
1.4.2.4 Multithreading................................................................................................................................. 95
1.4.2.4.1. Die Klasse Thread................................................................................................................... 95
1.4.2.4.2 Das Interface Runnable ...................................................................................................... 102
1.4.2.4.3. Prozesse und Threads in Java................................................................................................ 104
1.4.2.4.4 Synchronisation nebenläufiger Prozesse................................................................................ 106
1.4.2.5 Die Klassen String und StringBuffer ............................................................................................ 112
3
Programmieren in Java
1.4.2.5.1 Die Klasse String............................................................................................................... 112
1.4.2.5.2 Die Klasse StringBuffer ......................................................................................................... 116
1.4.2.6 Die Math-Klasse ........................................................................................................................... 117
1.4.2.7 Object-Wrapper-Klassen............................................................................................................... 118
1.4.3 Ausnahmen und Ausnahmenbehandlung............................................................................................. 121
1.4.4 Ein-, Ausgaben..................................................................................................................................... 128
1.4.4.1 Ein- und Ausgabeströme der Klasse System................................................................................. 128
1.4.4.2 Datenströme .................................................................................................................................. 129
1.4.4.3 Eingabe und Ausgabe mit Dateien................................................................................................ 131
1.4.4.4 Verwalten von Dateien und Verzeichnissen durch die Klasse File............................................... 137
1.4.4.5 Filtern mit den Klasse FilterReader und FilterWriter ................................................................... 137
1.4.4.6 Der Dateiauswahl-Dialog.............................................................................................................. 141
2. HAUPTBESTANDTEILE DER SPRACHE ................................................................................... 143
2.1 Token.......................................................................................................................................................... 143
2.1.1 Schlüsselworte ..................................................................................................................................... 144
2.1.2 Bezeichner und Namenskonventionen................................................................................................. 144
2.1.3 Literale ................................................................................................................................................. 145
2.1.3.1 Ganzzahlige Literale ..................................................................................................................... 145
2.1.3.2 Gleitpunktliterale .......................................................................................................................... 145
2.1.3.3 Boolesche Literale......................................................................................................................... 146
2.1.3.4 Zeichenliterale............................................................................................................................... 146
2.1.3.5 Zeichenkettenliterale ..................................................................................................................... 147
2.1.4 Trennzeichen........................................................................................................................................ 147
2.1.5 Operatoren ........................................................................................................................................... 148
2.1.5.1 Arithmetische Operatoren ............................................................................................................. 148
2.1.5.1.1 Einstellige arithmetische Operatoren ..................................................................................... 148
2.1.5.1.2 Arithmetische Zuweisungsoperatoren.................................................................................... 148
2.1.5.2 Inkrement- / Dekrement-Operatoren............................................................................................. 149
2.1.5.3 Bitweise arithmetische Operatoren ............................................................................................... 149
2.1.5.4 Bitweise Verschiebungsoperatoren............................................................................................... 149
2.1.5.5 Bitweise Zuweisungsoperatoren ................................................................................................... 151
2.1.5.6 Vergleichsoperatoren .................................................................................................................... 152
2.1.5.7 Logische Vergleichsoperatoren..................................................................................................... 152
2.1.6 Kommentare, eingebettete Dokumentation.......................................................................................... 153
2.2 Typen.......................................................................................................................................................... 155
2.2.1 Primitive Datentypen ........................................................................................................................... 155
2.2.2 Operationen mit primitiven Datentypen............................................................................................... 156
2.2.2.1 Operationen mit booleschen Variablen ......................................................................................... 156
2.2.2.2 Operationen mit Zeichenvariablen ................................................................................................ 156
2.2.2.3 Operationen mit Gleitpunktzahlen ................................................................................................ 157
2.2.2.4 Operationen mit ganzzahligen Variablen (bzw. ganzzahligen Ausdrücken) ................................ 158
2.2.3 Datenfelder (Arrays) ............................................................................................................................ 159
2.2.3.1 Deklarieren, Erstellen von Array-Objekten .................................................................................. 159
2.2.3.2 Zugriff auf Datenfeld-Elemente, Ändern von Datenfeld-Elementen ............................................ 161
2.2.3.3 Anwendungen mit eindimensionaler Datenfeldern....................................................................... 162
2.2.3.4 Mehrdimensionale Datenfelder..................................................................................................... 164
2.2.3.5 Die Klasse Arrays ......................................................................................................................... 170
2.3 Ausdrücke.................................................................................................................................................. 171
2.3.1 Arithmetische Ausdrücke..................................................................................................................... 171
2.3.2 Bewertung von Ausdrücken................................................................................................................. 171
2.3.3 Typkonvertierungen............................................................................................................................. 172
2.3.4 Vergleichsoperatoren ........................................................................................................................... 175
2.3.5 Logische Ausdrücke............................................................................................................................. 175
2.4 Anweisungen.............................................................................................................................................. 176
2.4.1 Blöcke und Anweisungen .................................................................................................................... 176
4
Programmieren in Java
2.4.2 Leere Anweisungen ............................................................................................................................. 176
2.4.3 Benannte Anweisungen ....................................................................................................................... 176
2.4.4 Deklarationen....................................................................................................................................... 176
2.4.5 Ausdrucksanweisungen........................................................................................................................ 176
2.4.6 Auswahlanweisungen .......................................................................................................................... 177
2.4.6.1 if-Anweisungen............................................................................................................................. 177
2.4.6.2 if-else-Anweisungen ..................................................................................................................... 177
2.4.6.3 switch-Anweisungen..................................................................................................................... 179
2.4.7 Iterationsanweisungen.......................................................................................................................... 180
2.4.7.1 while-Anweisung .......................................................................................................................... 180
2.4.7.2 do-Anweisung ............................................................................................................................... 181
2.4.7.3 for-Anweisung .............................................................................................................................. 182
2.4.7.4 Die erweiterte Form der for-Schleife in Java 1.5 .......................................................................... 183
2.4.8 Sprung-Anweisungen........................................................................................................................... 184
2.4.8.1 break-Anweisung .......................................................................................................................... 184
2.4.8.2 continue-Anweisung ..................................................................................................................... 184
2.4.8.3 Marken (labels) ............................................................................................................................. 185
2.4.8.4 return-Anweisung ......................................................................................................................... 187
2.4.8.5 throw-Anweisung.......................................................................................................................... 187
2.4.9 Synchronisationsanweisungen ............................................................................................................. 187
2.4.10 Schutzanweisungen............................................................................................................................ 188
2.4.11 Unerreichbare Anweisungen.............................................................................................................. 188
2.5 Klassen ....................................................................................................................................................... 188
2.5.1 Deklaration........................................................................................................................................... 188
2.5.2 Generische Klassen und generische Schnittstellen .............................................................................. 189
2.5.2.1 Generische Typen ......................................................................................................................... 189
2.5.2.1.1 Typvariable ............................................................................................................................ 189
2.5.2.1.2 Vererbung .............................................................................................................................. 192
2.5.2.1.3 Einschränkung der Typvariablen ........................................................................................... 193
2.5.2.2 Generische Schnittstellen .............................................................................................................. 194
2.6 Methoden ................................................................................................................................................... 196
2.6.1 Die Deklaration.................................................................................................................................... 196
2.6.2 Die Zugriffsspezifizierung ................................................................................................................... 196
2.6.3 Die Methodenmodifizierer ................................................................................................................... 197
2.6.4 Rückgabewerte von Methoden............................................................................................................. 197
2.6.5 Methodenname und Parameterliste ...................................................................................................... 197
2.6.6 Variable Parameteranzahl .................................................................................................................... 198
2.6.7 Rekursion ............................................................................................................................................. 198
2.6.7.1 Rekursive Funktionen ................................................................................................................... 198
2.6.7.2 Rekursion und Iteration................................................................................................................. 199
3. GRAFISCHE BENUTZEROBERFLÄCHEN UND APPLETS ...................................................... 201
3.1 Ereignisse in grafischen Benutzeroberflächen ....................................................................................... 201
3.1.1 Gestaltung von GUI mit Hilfe der AWT-Klassen................................................................................ 201
3.1.2 Ereignisbehandlung unter grafischen Benutzeroberflächen................................................................. 205
3.1.3 Anwendung lokaler Klassen für die Ereignisbehandlung.................................................................... 208
3.1.5 Low-Level-Events................................................................................................................................ 213
3.1.5.1 Component-Events........................................................................................................................ 213
3.1.5.2 Window-Events............................................................................................................................. 213
3.1.5.3 Mouse-Events ............................................................................................................................... 214
3.1.5.4 MouseMotion-Events.................................................................................................................... 215
3.1.5.5 Fokus-Events................................................................................................................................. 215
3.1.5.6 Key-Events.................................................................................................................................... 216
3.1.6 Der Weg eines Ereignisses................................................................................................................... 219
3.2 Kommunikation Anwender – Programm über Dialoge bzw. Menüs ................................................... 221
3.2.1 Dialoge................................................................................................................................................. 221
5
Programmieren in Java
3.2.2 Menüs................................................................................................................................................... 228
3.2.3 Popup-Menüs ....................................................................................................................................... 230
3.3 Grundlagen der Applet-Erstellung.......................................................................................................... 232
3.3.1 HTML-Grundlagen .............................................................................................................................. 232
3.3.2 Die interne Arbeitsweise eines Applets ............................................................................................... 234
3.3.3 „Multithreading“-fähige Applets ......................................................................................................... 240
3.3.4 Animation in Applets ........................................................................................................................... 243
3.3.5 Das Laden und Anzeigen von Bildern ................................................................................................. 248
3.3.7 Die Klasse JApplet............................................................................................................................... 250
3.3.8 Das Interface AppletContext................................................................................................................ 251
3.4 Grafische Benutzeroberfläche mit Observable-Observer ..................................................................... 252
3.5 Swing .......................................................................................................................................................... 255
3.6 Java Beans ................................................................................................................................................. 256
3.6.1 Wiederverwendbare Softwarekomponenten ........................................................................................ 256
3.6.3 Enterprise Java Beans .......................................................................................................................... 258
4. GRAFIK UND BILDVERARBEITUNG ......................................................................................... 259
4.1 Allgemeine Zeichenvorgänge ................................................................................................................... 260
4.1.1 Punkte, Linien, Kreise, Bögen, Polygone ............................................................................................ 260
4.1.2 Farbangaben......................................................................................................................................... 266
4.1.3 Textausgabe über den Zeichen-Modus ................................................................................................ 267
4.1.4 Grössenangaben ................................................................................................................................... 270
4.1.5 Clipping-Operationen .......................................................................................................................... 271
4.2 Bildverarbeitung ....................................................................................................................................... 271
4.2.1 Klassen zur Bildverarbeitung............................................................................................................... 272
4.2.2 Bildproduzenten und Bildkonsumenten............................................................................................... 276
4.2.3 Bildfilter............................................................................................................................................... 277
4.2.4 Kopieren von Speicher in ein Bild bzw. von Bildern in einen Speicher.............................................. 281
4.2.4.1 Kopieren von Speicher in ein Bild ................................................................................................ 281
4.2.4.2 Kopieren von Bildern in einen Speicher ....................................................................................... 282
4.3 Java 2D....................................................................................................................................................... 284
4.3.1 Das Zeichnen unter Java2D API.......................................................................................................... 285
4.3.3 Koordinatensysteme und Transformation ............................................................................................ 295
4.3.4 Java2D-Pipeline ................................................................................................................................... 300
4.3.5 Farbmanagement unter Java 2D........................................................................................................... 301
4.3.6 Text und Fonts unter Java2D ............................................................................................................... 307
4.3.7 2D Bildverarbeitung............................................................................................................................. 309
4.3.8 Animationen mit Java2D ..................................................................................................................... 310
5. AWT............................................................................................................................................... 311
5.1 Bestandteile des AWT............................................................................................................................... 311
5.2 Die AWT-Komponenten........................................................................................................................... 311
5.2.1 Schaltflächen (Buttons)........................................................................................................................ 312
5.2.2 Labels................................................................................................................................................... 315
5.2.3 Kontrollkästchen und Optionsfelder .................................................................................................... 315
5.2.4 Auswahlmenüs..................................................................................................................................... 319
5.2.5 Listenfelder .......................................................................................................................................... 320
5.2.6 Textbereiche und Textfelder ................................................................................................................ 321
5.2.7 Schieber und Bildlaufleisten ................................................................................................................ 324
5.2.8 ScrollPane ............................................................................................................................................ 327
6
Programmieren in Java
5.2.9 Zeichenbereiche ................................................................................................................................... 328
5.3 Container ................................................................................................................................................... 331
5.3.1 Panels ................................................................................................................................................... 331
5.3.2 Frames.................................................................................................................................................. 331
5.3.3 Menüs................................................................................................................................................... 332
5.3.4 Dialoge................................................................................................................................................. 336
5.4 Die Layout-Manager................................................................................................................................. 338
5.4.1 Layout-Regeln...................................................................................................................................... 338
5.4.2 Die einzelnen Layout-Manager............................................................................................................ 339
5.5 Die Event-Modelle 1.0 und 1.1 ................................................................................................................. 347
5.5.1 Der AWT-Handler 1.0 ......................................................................................................................... 347
5.5.2 Das Event-Handling in Java 1.1 bzw. Java 1.2.................................................................................... 349
5.6 Benutzerschnittstellen mit Swing............................................................................................................. 355
6. UTILITIES...................................................................................................................................... 421
6.1 Kollektionen (Collections) ........................................................................................................................ 421
6.1.1 Durchwandern von Datenstrukturen mit Iteratoren ............................................................................. 421
6.1.2 Die Klasse Vector ................................................................................................................................ 422
6.1.3 Die Klasse Stack .................................................................................................................................. 424
6.1.4 Die Klasse Bitset für Bitmengen.......................................................................................................... 427
6.1.5 Die Klasse Hashtable und assoziative Speicher................................................................................... 428
6.1.6 Die abstrakte Klasse Dictionary........................................................................................................... 432
6.1.7 Die Klasse Properties ........................................................................................................................... 433
6.2 Collection API ........................................................................................................................................... 434
6.2.1 Die Schnittstellen Collection, Iterator, Comparator............................................................................. 434
6.2.2 Die Behälterklassen und Schnittstellen des Typs List ......................................................................... 436
6.2.3 Behälterklassen des Typs Set............................................................................................................... 440
6.2.4 Behälterklassen des Typs Map............................................................................................................. 443
6.2.5 Algorithmen ......................................................................................................................................... 445
6.2.5.1 Datenmanipulation ........................................................................................................................ 445
6.2.5.2 Größter und kleinster Wert einer Collection................................................................................. 446
6.2.5.3 Sortieren........................................................................................................................................ 446
6.2.5.4 Suchen von Elementen.................................................................................................................. 448
6.2.6 Generics ............................................................................................................................................... 449
6.2.6.1 Sammlungsklassen ........................................................................................................................ 449
6.2.6.2 Generische Methoden ................................................................................................................... 450
6.2.6.3 Iteration ......................................................................................................................................... 452
6.3 Die Klasse StringTokenizer...................................................................................................................... 455
6.4 Die Klasse Random ................................................................................................................................... 456
6.5 Datumsberechnungen ............................................................................................................................... 457
6.5.1 Die Klassen Date, Calendar, GregorianCalendar................................................................................. 457
6.5.2 Formatieren der Datumsangaben ......................................................................................................... 461
6.6 Formatieren mit Format-Objekten ......................................................................................................... 463
6.6.1 Die Klasse Numberformat für die Ausgabe von Prozenten, Zahlen, Währungen ............................... 464
6.6.2 Ausgabe formatieren mit MessageFormat ........................................................................................... 464
6.6.3 Dezimalzahlenformatierung mit DecimalFormat................................................................................. 464
6.6.4 Formatieren mit format()...................................................................................................................... 465
7. EIN- UND AUSGABE ................................................................................................................... 467
7
Programmieren in Java
7.1 Die abstrakten Klassen InputStream und OutputStream..................................................................... 469
7.1.1 InputStream.......................................................................................................................................... 469
7.1.2 OutputStream ....................................................................................................................................... 471
7.2 Gefilterte Ströme....................................................................................................................................... 472
7.3 Die Klasse File ........................................................................................................................................... 475
7.4 Die RandomAccessFile-Klasse ................................................................................................................. 477
7.5 Die Klasse StreamTokenizer .................................................................................................................... 479
7.6 Klassen für spezielle nützliche Ströme .................................................................................................... 482
7.7 Java 1.1 IO-Ströme ................................................................................................................................... 483
7.7.1 Grundlagen........................................................................................................................................... 483
7.7.2 Die abstrakte Klassen Reader und ihre Ableitungen............................................................................ 484
7.7.3 Die abstrakte Klasse Writer und ihre Ableitungen .............................................................................. 486
7.7.4 Demonstrationsprogramm zur Ein-/ Ausgabe ab Java Version 1.1 ..................................................... 489
8. SERIALISIERUNG ........................................................................................................................ 491
8.1 Grundlagen................................................................................................................................................ 491
8.1.1 Das Interface Serializable .................................................................................................................... 491
8.1.2 Die Klasse ObjectOutputStream .......................................................................................................... 492
8.1.3 Die Klasse ObjectInputStream............................................................................................................. 494
8.2 Tiefe Objektkopien ................................................................................................................................... 496
9. NETZWERKPROGRAMMIERUNG .............................................................................................. 497
9.1 Adressen, Ressourcen und URLs............................................................................................................. 497
9.1.1 Die Klasse InetAddress........................................................................................................................ 497
9.1.2 Die Klasse URL ................................................................................................................................... 498
9.1.3 Die Klasse URLConnection und abgeleitete Klassen .......................................................................... 502
9.2 Kommunikation in Netzwerken............................................................................................................... 503
8
Programmieren in Java
Literaturverzeichnis
Alexander Newman u.a.: JAVA, Referenz und Anwendungen, 1996, Haar bei München : Que
Ralph Steyer: JAVA 1.2, Kompendium, 1998, Markt & Technik, Haar bei München
Laura Lemay u. Roger Cadenhead; JAVA 2 in 21 Tagen, 1999, Markt & Technik, Haar bei
München
Guide Krüger: Go To Java 2, 1999, Addison-Wesley, München ...
David Flanagan: Java in a Nutshell, 1996, O’Reilly&Associates Inc, Bonn ...
Florian Hawlitzek: Java 2, 2000, Addison-Wesley, München ...
Christian Ullenboom: Java ist auch eine Insel, GalileoPress, Bonn 2002
9
Programmieren in Java
0. Übersicht
0.1 Ziel der Vorlesung "Programmieren"
„Vermittlung grundlegender Kenntnisse, die zum Lösen von Problemen mit einer
Rechenanlage (Computer, Rechner) nötig sind.“
Die Rechenanlage ist „irgendeine“ (vom „Jumbo“ bis zum Personal Computer) und
muß
- eine Eingabemöglichkeit für Ziffern, Buchstaben und Sonderzeichen besitzen (Tastatur)
- programmierbar sein
- eine Ausgabemöglichkeit haben (Bildschirm)
Zum Erwerb grundlegender Kenntnisse für das Lösen von Problemen auf einem
Universalrechner ist
- zu zeigen, wie man von der Problemstellung zum Lösungsverfahren (Algorithmus) kommt. Alle
Probleme, für die ein Lösungweg genügend präzise formuliert werden kann, sind über den Rechner
lösbar.
- zu üben: Der Umgang mit den Algorithmen. Dazu wird die Programmiersprache Java benutzt, in der
die Algorithmen formuliert werden. Der in Java formulierte Lösungsweg (Algorithmus) ist das JavaProgramm (Anwendung, Applet), der Rechner auf dem das Programm ausführbar ist, ist die JavaMaschine. Java ist eine Sprache, die die Arbeitsanweisungen eines Programmierers einfach und
anschaulich darstellen kann.
Java wurde ab 1991 bei Sun Microsystems entwickelt. Verantwortlich für Java ist
JavaSoft, eine Tochterfirma von Sun Microsystems. Java und JavaSoft1 halten
permanent die aktuellsten Informationen im Internet bereit.
0.2 Java-Maschine und Programmiersystem
Die Java-Maschine2 nutzt die Möglichkeiten des Universalrechners.
Die Eingabemöglichkeit dient zum Einschreiben von Daten und Programmen in den
Speicher der Zentraleinheit (central processing unit, CPU). Die Zentraleinheit
verarbeitet die eingegebenen Daten nach den Anweisungen des eingelesenen
Programms, das zur Ausführung in binäre Form gebracht wurde. Das Steuerwerk
überwacht die Ausführung des Programms (Entschlüsseluung der binär dargestellten
Anweisungen).
Das
eigentliche
Abarbeiten
der
Programmanweisungen
(arithmetische, logische Operatoren) in der gegebenen Reihenfolge übernimmt das
Rechenwerk. Daten, Programme, die im Hauptspeicher keinen Platz finden, sind auf
externen Speichermedien ausgelagert (Magnetband, Magnetplatte, Floppy Disc).
1
http://java.sun.com
Die virtuelle Maschine von Java (Java VM) ist wirklich eine Maschine, d.h. Hardware, die bestimmte
Operationscodes (Maschinenanweisungen) interpretieren kann. Es wurden bzw. werdem Chips entwickelt, die
diese Maschine in Hardware implementieren. Im Regelfall geschieht aber eine Emulation durch das
Laufzeitsystem auf der jeweiligen Zielplattform.
2
11
Programmieren in Java
Eingabeeinheit
Hauptspeicher
Externer Speicher
Steuerwerk
Ausgabeeinheit
Rechenwerk
Zentraleinheit
Abb. 0.2-1: Prinzipieller Aufbau eines Universalrechners
Der Universalrechner ist vielfältig anwendbar. Welche Anwendungsmöglichkeit (z.B.
Java-Maschine) gewünscht wird, gibt der Benutzer durch Systemkommandos
bekannt. Diese Kommandos werden von Systemprogrammen entschlüsselt und
anschließend interpretiert. Eines dieser Kommandos verwandelt, falls die dazu
nötigen Programme vorhanden sind, den Universalrechner in eine Java-Maschine.
Für die Java-Maschine sind danach die Java-Programme des Benutzers die Quelle
aller Erkenntnisse.
Quellprogramme werden über die Tastatur eingegeben und auf externen Speicher
abgelegt. Zur Eingabe, Korrektur und Änderung stellt das System ein Programm mit
dem Namen „Editor" zur Verfügung. Das Speichern der eingegebenen bzw.
geänderten Programme auf Externspeicher übernimmt das Systemprogramm
„Dateiverwaltung“.
Java-Programm
Daten
Eingabe (Tastatur)
Externer Speicher
System,
Systemprogramme
Java-Maschine
Ausgabe (Bildschirm)
12
Externer Speicher
Java-Compiler
Java-Quellprogramme
Java-Laufzeitsystem
Java-BytecodeDateien
Programmieren in Java
0.3 Der erste Versuch
0.3.1 Das erste Programm
Quelltext:
import java.lang.*;
/* ErstesProgramm ist eine Applikation, die den einfachen
Gebrauch von Zeichenketten aufzeigt */
public class ErstesProgramm extends Object
{
// Beginn der Ausfuehrung vom Programm
public static void main(String args[])
{
// Ausgabe auf das Standard-Ausgabegeraet
System.out.println(
"Das erste Programm der Vorlesung Programmieren in Java.");
}
}
Das Programm umfaßt zwei Teile: eine Klassendefinition und einen
Programmabschnittt, der durch die Methode main() bestimmt ist.
Jede Java-Anwendung besteht aus mehreren Klassen. Die Klasse, die die
Ausgangsbasis für Java-Programme ist, muß die main()-Methode benutzen:
public static void main(String args[]) { ... }.
Es bedeuten:
public ...
static ...
main() ...
Die Methode ist für andere Klassen und Objekte verfügbar
Es handelt sich um eine Klassenmethode
Die Funktion main() hat einen Parameter vom Typ Zeichenkette
(String).
Dieser
Parameter
dient
zur
Aufnahme
von
Befehlszeilenargumente.
Argumente,
die
an
Java-Programme
übergeben werden, werden zu Zeichenketten konvertiert. In JavaProgrammen ist main(), wie in C/C++-Programmen, die erste
Routine des Programms, die ausgeführt wird.
Durch die „import“-Anweisung können Entwickler Klassen verwenden, die in
anderen Dateien definiert sind. Compiler bzw. Interpreter greifen auf die Dateien zu.
Über „import“ wird bestimmt, wo diese Dateien liegen. Java importiert immer das
Paket „java.lang“, denn hier ist die Klasse Object enthalten, von der alle JavaKlassen abgeleitet sind. Das Paket „java.lang“, die unmittelbare Ableitung von der
Klasse Object werden in Java per default bereitgestellt. Die diesbezüglichen
Angaben im Programm können entfallen, der Quelltext kann deshalb auch folgende
Gestalt annehmen:
/* ErstesProgramm ist eine Applikation, die den einfachen
Gebrauch von Zeichenketten aufzeigt */
public class ErstesProgramm
{
// Beginn der Ausfuehrung vom Programm
public static void main(String args[])
{
// Ausgabe auf das Standard-Ausgabegeraet
System.out.println(
"Das erste Programm der Vorlesung Programmieren in Java.");
}
}
13
Programmieren in Java
Token
Die Ausdrucksformen der Sprache Java werden Token genannt. In derartige für die
Sprache Java sinnvolle Einheiten muß sich der Quelltext eines Java-Programms
zerlegen lassen. Es gibt in Java fünf Arten von Token: Bezeichner oder Identifizierer,
Schlüsselworte, Literale, Operatoren, Trennzeichen.
Bezeichner, Identifizierer: Darunter versteht man die Benennungen (Namen) für
Klassen, Objekte, Variable, Methoden. Namen sind zusammengesetzt aus UnicodeZeichen. Java benutzt den 16-Bit-Unicode-Zeichensatz, dessen erste 256 Zeichen
dem normalen ASCII-Zeichensatz entsprechen (Byte 1 ist immer auf 0 gesetzt). Im
vorliegenden Programm sind z.B. Bezeichner: ErstesProgramm, main, System.
Java unterscheidet Groß-/ Kleinschreibung. Namen bestehen in der Regel aus
alphabetischen Zeichen und Dezimalziffern. An der ersten Stelle darf keine Zahl
stehen.
Schlüsselwörter:
Das sind Wörter, die ein wesentlicher Teil der JavaSprachdefinition sind, z.B.: public, class, void, String.
Literale: mit einem Literal können Variablen und Konstanten bestimmte Werte
zugewiesen werden. Literale können annehmen: numerische Werte (z.B. 13),
boolesche Werte (true bzw. false), Zeichen (z.B. ‘A‘) und Zeichenketten (z.B.
"Das erste Programm der Vorlesung Programmieren in Java.").
Operatoren: Das sind Zeichen bzw. Zeichenkombinationen zur Angabe einer
auszuführenden Operation mit einer oder mehreren Variablen oder Konstanten
(Operanden), z.B. + - / <<< >>>.
Variable sind Arbeitsspeicherstellen, an denen Informationen gespeichert werden
können. Zur Deklaration erhält eine Variable Namen (Bezeichner) und Typ
zugeordnet. Der Typ kennzeichnet die Art der Information, die die Variable
aufnehmen kann, z.B.:
int i;
// zur Aufnahme ganzer Zahlen
String s; // zur Aufnahme von Zeichenketten
In Java unterscheidet man: elementare (primitive Typen) und benutzerdefinierte
Typen (einschl. der vom System bereitgestellten Klassen: String und Array). Es
gibt acht primitive Typen zum Speichern von ganzen Zahlen (byte, short, int,
long), Gleitpunktzahlen (float, double), Zeichen (char) und booleschen Werten
(boolean).
Sobald die Variable deklariert wurde, kann ihr über den Zuweisungsoperator („=“) ein
Wert zugewiesen werden, z.B. innerhalb der main()-Methode
public static void main(String args[])
{
String s;
// Deklaration der Variablen s zur Aufnahme von Zeichenketten
// Zuweisung
s = "Das erste Programm der Vorlesung Programmieren in Java."
// Aufruf der Methode println() der Klasse System, die sich im Paket
// java.lang befindet
System.out.println(s);
}
14
Programmieren in Java
Vorrang
1
2
3
4
5
6
7
8
9
10
11
12
13
Operator
++
Operandentyp
Arithmetisch
--
Arithmetisch
+, -
Arithmetisch
~
Integral
!
boolean
(type)
*, /, %
irgendein
arithmetisch
+, +
<<
>>
Arithmetisch
String
Integral
Integral
>>>
Integral
<, <=
Arithmetisch
>, >=
Arithmetisch
instanceof
==
!=
Objekt, Typ
Primitiver Typ
Primitiver Typ
==
Objekt
!=
Objekt
&
&
^
^
Integral
boolean
Integral
boolean
|
|
&&
||
?=
Integral
boolean
boolean
boolean
boolean,
irgendein,
irgendein
Variable, irgendein R
Variable, irgendein R
=
*=, /=,
+=, -=
<<=, >>=,
>>>=,
&=, ^=,
|=
Abb. 0.3-1: Java-Operatoren
15
Assoziation Operation
R
Pre- oder Post-Inkrement
(unär)
R
Pre- oder PostDekrement (unär)
R
Unäres Plus, unäres
Minus
R
Bitweises Komplement
(unär)
R
Logisches Komplement
(unär)
R
cast
L
Multiplikation, Division,
Rest
L
Addition, Subtraktion
L
Verkettung
L
Links-Shift
L
Rechts-Shift mit
Vorzeichen
L
Rechts-Shift mit NullenNachziehen
L
Kleiner als, kleiner als
oder gleich
L
Größer als, größer als
oder gleich
L
Objekt?, Instanz?
L
Gleich (identische Werte)
L
Ungleich (verschiedene
Werte)
L
Gleich (Referenz auf das
gleiche Objekt)
L
Ungleich (Referenz auf
verschiedene Objekte)
L
Bitweises Und
L
Logisches Und
L
Bitweises Oder
L
Logisches Oder
(exklusiv)
L
Bitweises Oder
L
Logisches Oder (inklusiv)
L
Konditionelles Und
L
Konditionelles Oder
R
Konditioneller (ternärer)
Operator
Zuweisung
Zuweisung mit Operation
Programmieren in Java
System.out.println(s); ist eine Anweisung. Anweisungen stehen für eine
einzelne Aktion, die in einem Java-Programm ausgeführt wird. Jede Anweisung wird
mit einem Strichpunkt „;“ abgeschlossen. Einige Anweisungen erzeugen einen Wert,
wie das bspw. beim Addieren zweier Zahlen der Fall ist. Derartige Anweisungen
werden als Ausdrücke bezeichnet.
Ein Ausdruck ist eine Anweisung, die als Ergebnis einen (Rückgabe-) Wert
produziert. Dieser Wert kann zur späteren Verwendung im Programm gespeichert,
direkt in einer anderen Anweisung verwendet oder überhaupt nicht beachtet werden.
Ausdrücke können Konstanten, Variablen, Operatoren beinhalten. Ausdrücke sind
vielfach mit unären und binären Operatoren verknüpft.
Trennzeichen: Das sind Symbole und Zusammenfassungen von Quellcode: ( ) {
} [ ] ;. ,.
„(“: wird sowohl zum Öffnen einer Parameterliste für eine Methode als auch zur
Festlegung der Priorität für Operationen in einem Ausdruck benutzt.
„)“: wird sowohl zum Schließen einer Parameterliste für eine Methode als auch zur
Festlegung der Priorität für Operationen in einem Ausdruck benutzt.
Bsp.: public static void main(String args[])
„{“: wird zu Beginn eines Blocks mit Anweisungen oder einer Initialisierungsliste
gesetzt.
„}“:wird an das Ende eines Blocks mit Anweisungen oder einer Initialisierungsliste
gesetzt.
Bsp.: Methoden werden in Java durch einen Anweisungsblock bestimmt. Ein Anweisungsblock
besteht in der Regel aus einer Reihe von Anweisungen, die von geschweiften Klammern umschlossen
sind.
public static void main(String args[])
{
String s;
s = "Das erste Programm der Vorlesung Programmieren in Java.";
System.out.println(s);
}
„[“: steht für eine Ausdruck, der als Index für ein Datenfeld (Array) steht.
„]“: folgt einem Ausdruck, der als Index für ein Datenfeld steht.
Bsp.: String args[] = {"Juergen", "Hubert", "Josef", "Liesel", "Christian"};
definiert einen Array mit 5 Komponenten, die alle den gleichen Typ besitzen.
Datenfelder (Arrays) dienen zum Speichern von Elementen, die alle denselben Typ aufweisen.
Jedem Element wird innerhalb des Datenfelds ein eigener Speicherplatz zugewiesen. Dieser
Speicherplatz ist für den leichten Zugriff durchnumeriert.
[0]
[1]
[2]
[3]
[4]
"Juergen"
"Hubert"
"Josef"
"Liesel"
"Christian"
Auf den Wert einer Komponenten kann über den Array-Namen (z.B. args), gefolgt von einem Index in
eckigen Klammern (z.B. [0]) zugegriffen werden. Mit
args[0] = "Roland";
kann dem ersten Element des Array args ein neuer Wert zugewiesen werden.
„;“: dient zum Beenden einer Anweisung.
„,“: wird häufig als Begrenzer (z.B. in einer Parameterliste) benutzt.
„.“: wird als Dezimalpunkt als auch zum Trennen solcher Dinge wie Paketnamen von
Klassennamen oder Variablennamen benutzt (z.B. System.out.println(s);).
16
Programmieren in Java
Standardmäßig haben Klassen Zugriff auf die Klassen im Paket „java.lang“. Zur
Bezugnahme auf eine Klasse, die sich nicht in java.lang befindet, sind alle Pakete,
in denen sich die Klasse befindet, anzugeben, z.B.
java.awt.Color // bezieht sich auf die Klasse Color im Paket awt,
// das sich im Paket java befindet.
Leerraumzeichen: Sie können in beliebiger Anzahl und an jedem Ort zwischen Token
(die eine Funktion besitzen) zur übersichtlichen Gestaltung des Quellcodes plaziert
werden. Solche Zeichen sind bspw.: Space, Tab, Zeilenende, Formularvorschub
Kommentar: Er wird vom Compiler ignoriert. Man unterscheidet den Kommentar bis
zum Zeilenende „//“ und den eingebetteten Kommentar „/* ... */“, z.B.:
/* ErstesProgramm ist eine Applikation, die den einfachen
Gebrauch von Zeichenketten aufzeigt */
Übersetzung und Ausführung.
Befindet sich der Quelltext zum Programm in einer Datei mit dem Namen
ErstesProgramm.java, dann kann dieses Programm durch Aufruf des JavaCompilers javac in ablauffähigen Bytecode übersetzt werden. Das geschieht über
das folgende Systemkommando:
javac ErstesProgramm.java
Den Byte-Code, den der Java-Compiler in der Datei ErstesProgramm.class
hinterlegt hat, kann über das Kommando
java ErstesProgramm
zur Ausführung gebracht werden.
17
Programmieren in Java
Konsole:
Arbeitsspeicher:
javac ErstesProgramm.java
Externspeicher
ErstesProgramm.java
ErstesProgramm.class
java ErstesProgramm
ErstesProgramm.class
Java (-Interpreter)
"Das erste Programm der Vorlesung Programmieren in Java"
Abb. 0.3-2: Ablaufplan ErstesProgramm
18
Programmieren in Java
0.3.2 Bestandteile eines Programms
Wesentliche Programmelemente in Java sind:
- Anweisungen
Anweisungen3 gehören zu den elementaren ausführbaren Programmelementen. Eine Anweisung
kann eine Deklaration enthalten, einen Ausdruck4 auswerten oder den Programmablauf5 (Auswahl-,
Iterations-, Sprung-Anweisungen und return-, throw-Anweisung) steuern.
- Blöcke
Ein Block6 ist eine Zusammenstellung von Anweisungen, die nacheinander ausgeführt werden. Ein
Block kann eigene Variable definieren, die nur innerhalb des Blocks sichtbar sind. Sie werden beim
Aufruf des Blocks angelegt und beim Verlassen des Blocks zerstört. Innerhalb eines Blocks sind nur
die lokalen Variablen des Blocks und die lokalen Variablen des umgebenden Blocks bzw. der
umgebenden Methode sichtbar. Nach außen stellt sich der Block als eine einzige Anweisung dar.
- Methoden
Methoden7 unterscheiden sich von Blöcken folgendermaßen:
-- Sie haben einen Namen und können von verschiedenen Stellen des Programms aufgerufen
werden.
-- Sie sind parametrisierbar
-- Sie können einen Rückgabewert besitzen.
Methoden werden in Java immer lokal zu einer Klasse definiert.
- Klassen
Sie8 enthalten Variablen zur Beschreibung des Zustands von Objekten und Methoden zur
Beschreibung des Verhaltens von Objekten.
- Schnittstellen
Eine Schnittstelle (Interface) ist eine Sammlung von Methoden, die einen Namen besitzen, aber
nicht implementiert sind. Ein Klasse kann beliebig viele Schnittstellen implementieren. Dadurch wird
die Klasse zur Implementierung der Methode gezwungen, deren Namen von der Schnittstelle
definiert wurden. Falls zwei unterschiedliche Klassen, dieselbe Schnittstelle implementieren, können
beide auf Aufrufe der Methode, die in der Schnittstelle definiert sind, reagieren. Allerdings kann die
Reaktion auf diese Methodenaufrufe bei einzelnen Klassen total unterschiedlich sein.
- Pakete
Ein Paket ist eine Sammlung von Klassen. Jede Klasse in Java gehört zu einem Paket. Pakete
ermöglichen, daß Sammlungen von Klassen bei Bedarf verfügbar sind. Die Klassenbibliotheken
befinden sich in einem Paket mit dem Namen „java“. Dieses Paket beinhaltet Pakete, die spezielle
Bestandteile der Sprache Java, z.B. Dateieingabe und Datenausgabe, Multimedia, etc. definieren.
Standardmäßig haben Klassen der Anwender nur Zugrifff auf Klassen im Paket „java.lang“
(Standard-Feature). Klassen irgendeines anderen Pakets müssen importiert werden.
- Applikationen (Anwendungen)
Anwendungen (Applikationen) bilden die eigenständigen Programme. Sie benötigen zur Ausführung
keinen Browser, sondern nur den Java-Interpreter und die .class-Dateien der verwendeten
Klassen.
- Applets
Applets sind ebenfalls lauffähige Java-Programme. Sie werden aus einer HTML-Seite aufgerufen
und benötigen zur Ausführung einen Web-Browser (oder ein Werkzeug wie den Appletviewer).
Applets müssen von der Klasse Applet abgeleitet und nach den Regeln dieser Klasse aufgebaut
sein. Zum Starten des Programms erzeugt der Browser eine Instanz der abgeleiteten Klasse und
ruft eine Reihe vordefinierter Callback-Methoden9 auf.
3
vgl. 2.4
vgl. 2.4.5
5 vgl. 2.4.6
6 vgl. 2.4.1
7 vgl. 2.6
8 vgl. 2.5
9 CallBack-Methoden sind von der abgeleiteteten Klasse zur Verfügung gestellte Methoden, die vom Browser
bzw. Appletviewer aufgerufen werden
4
19
Programmieren in Java
20
Programmieren in Java
1. Einführung in die Java-Programmierung
1.1 Übersicht zur Entwicklung der Programmiersprache Java
Java ist in den Entwicklungslaboren der amerikanischen Firma Sun Microsystems10
entstanden. Man entschied sich bei Sun zur Realisierung eines im Jahre 1990
begonnenen Projekts11 für eine neue Programmiersprache, da bisher entwickelte
Programme mit vorliegenden Programmiersprachen zu große Schwächen zeigten.
Der erste Versuch war nur bedingt erfolgreich. Lediglich der damals im Internet
verbreitete Mosaic-Browser12 wurde zu einer Zielplattform der neuen
Programmiersprache13, die Ende 1994 für das Internet umgearbeitet wurde und über
das Netz frei und umsonst verteilt wurde.
1995 wurde die neue Programmiersprache mit dem Namen Java14 der InternetÖffentlichkeit in Kombination mit einem Browser, HotJava, präsentiert. HotJava
war die erste komplexe und vollständig in Java geschriebene Anwendung, der erste
Java-fähige Browser und damit die Präsentationsform für die ersten Java-Applets.
Außerdem war dieser Browser eine wesentliche Ergänzung des ersten JavaEntwicklungstools von Sun – das Java Develelopment Kit (JDK 1.0). Ein
kommerzielles Produkt, der Java Workshop15, wurde kurz nach der Präsentation von
JDK 1.0 bereitgestellt.
Natürlich gab es im JDK noch diverse Kinderkrankheiten. Im zweiten Quartal 1997
folgte deshalb nach einigen Zwischenversionen die Version 1.1 des JDK. Parallel zur
10
Sun ist eine der führenden Hersteller von Workstations
Entwicklung eines vollkommen neuen, plattformunabhängigen Betriebssystems für den „Consumerbereich
der allgemeinen Elektronik (Telefone, Videorecorder, Waschmaschinen, Kaffemaschinen; eigentlich alle
elektrischen Maschinen, die Daten benötigen)
12 der erste WWW-Browser mit einer grafischen Benutzeroberfläche. WWW steht für World Wide Web und ist
inzwischen die wichtigste Stütze im Internet. Das WWW ist im wesentlichen durch sog. Hypertexte aufgebaut,
die mit der Sprache HTML entwickelt wurden und werden. Ein Hypertext ist im wesentlichen ein ASCII-Text,
der durch maskierte Wörter (sog. Hyperlinks) zu weiteren Seiten führt. Hypertext ist eigentlich nur ein Text mit
Verweisen auf andere Texte. Der Verweis auf den weiterführenden Text kann aktiviert werden (z.B. durch
Mausklick), und es wird zu dem gewünschten Text verzweigt.
Das Hypertext Transfer Protocol (HTTP) dient zur Übertragung von Informationen aus dem WWW. HTTP ist
ein objektorientiertes Protokoll (TCP/IP-Programm) zur einfachen Übertragung von Hypertext-Dokumenten
zwischen Client und Server. Client-Programme, die HTTP benutzen, werden (in der Regel) als Web-Browser,
Server-Programme als Web-Server bezeichnet. Der Browser schickt an den Server die Aufforderung eine
bestimmte HTML-Seite zu übertragen. Falls er in dieser Seite weitere Verweise (z.B. auf Bilder) entdeckt,
schickt er weitere Übertragungswünsche hinterher. Das Besorgen der gewünschten Dokumente erfolgt über ein
einheitliches Adressierungsschema, dem Uniform Resource Locater (URL), durch den Internet-Standort und die
Art der zu übertragenden Information identifiziert werden.
13 Dem WWW mit dem bis zu diesem Zeitpunkt realisierten Stand der HTML fehlten: dreidimensionale
Darstellung der Objekte, eine bewegte Animation und eine Möglichkeit zur vernünftigen Interaktion mit dem
Anwender. Deshalb waren hier die Multimedia- und Interaktionseigenschaften der neuen Programmiersprache
besonders erfolgreich.
14 verantwortlich für Java ist die Firma JavaSoft – eine Tochterfirma von Sun Microsystems. Sun bzw. JavaSoft
halten im Internet permanent die aktuellste Information von Java bereit. Einige der Informationen findet man
bereits auf der Einstiegseite von Sun (http://java.sun.com), andere Informationen bekommt man von der
Neuigkeitenseite (http://java.sun.com/nav/new/index.html)
15 mit Test- und Debug-Möglichkeiten, einem Referenzcompiler, einer integrierten Entwicklungsumgebung mit
Editor, Browser, Project-, Portfolio- und Build-Manager, Debugger, Project-Tester und Online-Hilfe. Der
Workshop geht mit der Version 2.0 inzwischen in eine neue Phase zur Unterstützung des neuen Java.
11
21
Programmieren in Java
1.1.x-Version gab es auch einen neuen Hot-Java-Browser zur Unterstützung der
neuen 1.1-API-Funktionen.
Java 1.2 ist die neueste Version. In Verbindung mit dem JDK 1.2 wurde der Begriff
Java 216 eingeführt. Inzwischen gibt es schon die Version 1.3, die nur wenige neue
Programmierschnittstellen bietet, dafür aber eine komfortablere und schnellere
Laufzeitumgebung.
Seit Version 1.2 ist Java in verschiedenen Ausgaben (Editionen) erhältlich. Sun hat
drei verschiedene Editionen mit unterschiedlicher Ausrichtung definiert:
1. Standard-Edition für den Desktop Client oder PC
2. Enterprise-Edition für Application Server
3. Micro-Edition für Kleingeräte.
Am weitesten entwickelt ist die Standard-Edition.
Neben dem JDK gibt es zahlreiche kommerzielle Entwicklungstools für JavaProgrammierer, z. B.: Symantec Visual Café, Borland JBuilder, SuperCade, National
Intelligence Roaster, SunSoft Java Workshop.
1.2 Was ist Java?
Java ist17 eine einfache, objektorientierte, dezentrale, interpretierte, stabil laufende,
sichere, architekturneutrale, portierbare und dynamische Sprache, die
Hochleistungsgeschwindigkeitsanwendungen und Multithreading unterstützt.
- Java ist einfach, obwohl es sehr nahe an der ziemlich komplizierten C/C++-Syntax entworfen wurde.
Die komplexen Teile von C/C++ wurden jedoch aus Java ausgeschlossen. In Java gibt es keine
Zeiger (Pointer) und auch keine Zeiger-Arithmetik. Strings und Arrays sind echte Objekte. Die
Speicherverwaltung erfolgt weitgehend automatisch.
Die Ausdrücke in Java18 entsprechen aber weitgehend denen von C/C++. Java besitzt eine „if“Anweisung, eine „while“-, „do“- und „for“-Schleife und eine „switch“-Anweisung. Es gibt die von C
bekannten „break“- und „continue“-Anweisungen (in normaler und mit einem „Label“ versehenen
Form)19.
- Java ist klein. Eine der ursprünglichen Ziele von Java war die Erleichterung der Software-Entwicklung für
kleine Rechner.
- Java ist objektorientiert. Es gehört zu einer Familie von Sprachen, die Daten als Objekte definieren
und Methoden zur Bearbeitung dieser Objekte verwenden. Das objektorientierte Konzept von Java
hat viel von C++ geerbt, aber auch Konzepte anderer objektorientierter Sprachen wurden
übernommen. Wie die meisten objektorientierten Sprachen umfaßt Java eine umfangreiche
Klassenbibliothek, die grundlegende Datentypen, Systemein- und Systemausgabe und andere
Hilfsmittel (utilities) bietet. Die grundlegenden Klassen sind Teil des JDK, das darüber hinaus noch
Klassen besitzt, die Funktionen im Zusammenhang mit in Netzwerken üblichen Internet-Protokollen
und Benutzeroberflächen unterstützen. Da diese Klassenbibliotheken in Java geschrieben sind, sind
sie, wie alle Java-Anwendungen, auf alle Plattformen portierbar.
- Java ist dezentral und erfüllt eine wesentliche Eigenschaft von Client/Server-Anwendungen. Die
Fähigkeit der Verteilung von Informationen für die Berechnung der Daten. „Dezentral“ beschreibt die
Beziehung von Systemobjekten: Es ist gleichgültig, ob die Objekte sich auf lokalen oder entfernten
16
Die exakte Bezeichnung für das JDK 1.2 ist Java 2 JDK v1.2. Nähere Information enthält die Webseite:
http://java.sun.com/products/jdk/1.2/java2.html
17 Offizielle Definition von Sun Microsystems
18 vgl. 2.3
19 vgl. 2.4.
22
Programmieren in Java
Systemen befinden. Objekte können mit Java-Programmen über URLs20 vom gesamten Web
genauso wie auf dem lokalen Rechner geöffnet und bearbeitet werden. Wichtige Teile der
Anwendung bzw. der Daten können lokal vorhanden sein, andere werden bei Bedarf geladen.
- Java ist interpretiert. Ein gewisser Teil des Java-Codes (ca. 20 %) werden vom Container, dem
Browser interpretiert. Der Java-Quellcode wird mit dem Java-Compiler in Bytecode
(architekturneutrales Object-Code-Format) kompiliert. Bytecode ist nicht lauffähig, bis er von der
Java-Laufzeitumgebung21 interpretiert wird.
Java-Compiler (Pentium)
Java-Interpreter (Pentium)
Java-Code
Java-Compiler (SPARC)
Java-Interpreter (SPARC)
Abb. 1.2-1:
- Java ist stabil, d.h. zuverlässig. Die Stabilität einer Programmiersprache zeigt sich darin, daß
während der Kompilierungsphase der gößte Teil der Datenüberprüfung ausgeführt werden kann.
Java ist stark typisiert. Damit können Fehler früh gefunden werden. Ein weiteres Stabilitätskriterium
von Java-Programmen ist die eingebaute Begrenzung der Zugriffsmöglichkeiten auf den
Hinzu
kommt
auch
noch
die
anschließende
Speicherbereich
des
Rechners22.
Sicherheitsüberprüfung durch den „Linker“. Der Linker ist ein Teil der Laufzeitumgebung, das die im
System eingehenden Daten überprüft.
20
Rechner sind über IP-Nummern bzw. über Alias-Namen (Domain-Name-System, DNS) im Internet eindeutig
adressiert. Innerhalb des Internet müssen aber auch alle Daten und Programme über unverwechselbare InternetAdressen bestimmt sein. Der Name dieser Internet-Adressen für konkrete Adressanfragen an Dokumente im
Internet lautet URL und steht für Uniform Resource Locator („einheitliches Adressierungsschema für
Objekte“ im Internet). „Einheitlich“ deshalb, weil mit einer URL sowohl die verschiedenen Dienste (WWW,
FTP, Gopher usw.) als auch Rechner oder Dokumente beschrieben werden können. Der Begriff „Objekt“ steht
bspw. für Datei, Text, Videos, Sounds usw., also für ziemlich alles, was sich im Netz befindet. Die exakte
Schreibweise einer URL ist je nach Dienstprotokoll etwas unterschiedlich, sieht jedoch in der Regel so aus:
Dienstprotokoll://host.domain:port/pfad/datei
Dienstprotokoll ist bspw.: http, ftp, gopher, news, mailto, wais. Danach folgen fast immer Doppelpunkt und
zwei Slashes (Ausnahme: mailto).
Mit hosts.domain wird ein Rechner im Internet adressiert. Dabei kann die IP-Nummer des Rechner angegeben
werden (unüblich). Häufiger nimmt man dafür den DNS-Namen (in der Form
host.{localDomain}.SecondLevelDomain.TopLevelDomain.
Auf einem Internet-Rechner kann unter einem Namen eine ganze Reihe von verschiedenen Diensten parallel
betrieben werden (z.B. FTP-Server, HTTP-Server). Zum Erreichen des gewünschten Dienstes auf dem
ausgewählten Rechner benötigt man den sog. Port. Ports sind numerische Werte (zwischen 0 und 1023). In der
Regel hat jeder Internet-Dienst einen Default-Wert, der immer verwendet wird, wenn kein Port explizit
angegeben ist. Der Port für einen HTTP-Server ist immer „80“, ein FTP-Server hat immer den Port „21“.
Das genaue Objekt verbirgt sich hinter der Angabe /pfad/datei.
21 Normalerweise durch einen Java-Browser
22 Ungeprüfte Zugriffe auf Speicherbereiche des Rechners ermöglicht C/C++ (Zeigerarithmetik, implizite
Deklaration)
23
Programmieren in Java
- Java gilt als sicher. In dieser Hinsicht muß sich Java allerdings noch bewähren23.
- Java ist auf verschieden Systemen mit unterschiedlichen Prozessoren und Betriebssystemarchitekturen lauffähig (architekturneutral). Der komplette Java-Bytecode kann auf jedem Prozessor
ausgeführt werden, der einen javafähigen Browser (bspw. die virtuelle Maschine von Java24)
unterstützt. Plattformunabhängiger Binärcode wird allerdings nicht erzeugt, sondern Java-Bytecode
wird während der Laufzeit in systemeigenen Maschinencode übertragen (interpretiert).
- Java unterstützt „Multithreading“. „Multthreading bedeutet: Mehrere Aufgaben oder Prozesse können
gleichzeitig ausgeführt werden. Nicht die quasi gleichzeitige Ausführung mehrerer Programme
(Multitasking), sondern die gleichzeitige, parallele Ausführung von einzelnen Programmschritten
(oder zusammenhängenden Prozessen) ist „Multithreading“. Das kann bedeuten: Innerhalb eines
Programms können mehrere Dinge gleichzeitig geschehen, mehrere Faden / Threads eines
Programms können gleichzeitig verfolgt und abgearbeitet werden.
23
Verschiedene Sicherheitslücken wurden aufgedeckt und wurden inzwischen beseitigt.
Die virtuelle Maschine wird auch als Java Interpreter oder Java Environment (Java-Laufzeitumgebung)
bezeichnet
24
24
Programmieren in Java
1.3 Einstieg in die Java-Programmierung
1.3.1 Die Software für die Java-Programmierung
Das JAVA Development Kit (JDK) enthält die Entwicklungsumgebung zum Schreiben
von Java Programmen. Das JDK ist für Sun-SPARC-Systeme mit Solaris 2.2 (oder
höher) sowie für Windows NT und Windows 95 über das Internet25 erhältlich.
Nach der Installation hat das Java-Verzeichnis26 folgenden Inhalt27:
Abb. 1.3-1: Inhalt des Java-Verzeichnisses
Innerhalb des Java-Verzeichnisses befinden sich Unterverzeichnisse28 mit folgendem
Inhalt:
Verzeichnis
\bin
\lib
\include
\demo
\src
Inhalt
In diesem Verzeichnis befinden sich die JDK-Programme
In diesem Verzeichnis befinden sich defaultmäßig die Standard-Java-Klassen
des JDK29
In diesem Verzeichnis befinden sich diverse Header-Dateien für die gemeinsame
Verwendung von Java und C/C++
Das Demo-Verzeichnis einthält Beispielprogramme
Falls „Java-Source“ im InstallShield ausgewählt wurde, wird dieses Verzeichnis
mit angelegt. Darin befinden sich die entkomprimierten Java-Dateien, die sonst
nur im gepackten Zustand (Datei SRC.ZIP) vorhanden sind.
Es ist sinnvoll, die in der JDK-Umgebung verfügbaren Werkzeuge von allen
Verzeichnissen aus zugänglich zu machen. Für Windows-NT und Windows-9525
Das jeweils aktuelle JDK (aber auch ältere Versionen) können von den Sun-Microsystem-Webseiten bzw. der
JavaSoft-Homepage geladen werden (http://www.sun.com bzw. http://www.javasoft.com/ ). Genutzt werden
kann auch die JavaSoft-WWW-Download-Seite für das jeweilige JDK (http://java.sun.com.products/jdk/x.x ,
die Angabe x.x ist durch die gewünschte Versionsnummer zu ersetzen). Auch eine FTP-Download, z.B. vom
Sun-FTP-Server (ftp.javasoft.com) ist möglich.
26 Erzeugt von der Entpackungsroutine des JDK
27 Das Verzeichnis „meinepr“ dient zur Aufnahme von Projekten und zählt nicht standardmäßig zum Inhalt
eines Java-Verzeichnisses
28 unter Windows
29 Im wesentlichen ist das die Datei classes.zip
25
Programmieren in Java
Anwender wird das Verzeichnis mit den Werkzeugen in der Pfadangabe der Datei
„autoexec.bat“ eingetragen, z.B.:
PATH c:\;c:\windows;c:\windows\command;c:\jdk1.1.6\bin;c:\jdk1.1.6\include;
Die CLASSPATH-Umgebungsvariable30
„autoexec.bat“ so vorliegen:
sollte
unter
Windows
in
der
Datei
set classpath=c:\jdk1.1.6\lib\classes.zip;
Im JDK 1.2 wird die CLASSPATH-Umgebungsvariable nicht mehr benötigt. Ist
allerdings eine CLASSPATH-Variable gesetzt, so wird sie auch verwendet.
Nach der Installation des JDK 1.2 bzw. JDK 1.3 muß das Verzeichnis
\jdk1.3\bin in den Suchpfad ausführbarer Dateien eingetragen werden. Das kann
direkt in der autoexec.bat durch Modifikation der PATH-Anweisung, mit Hilfe einer
Batch-Datei oder direkt erledigt werden, z.B.:
set PATH=d:\jdk1.3\bin;%PATH%
30 Umgebungsvariable sind Einstellungen, mit denen die HotJava- und Java-Interpreter-Umgebungen des
Systems spezifiziert werden. Sie werden z.B. unter Windows im allg. auf Befehlszeilenebene oder in der
„autoexec.bat“ mit der Anweisung „SET [Umgebungsvariable]= ....“ gesetzt.
CLASSPATH ist die wichtigste der Umgebungsvariablen von Java. Mit dieser Umgebungsvariablen wird
bestimmt, woher die Systemklassen importiert werden.
26
Programmieren in Java
1.3.2 Applets und Anwendungen
Java Programme werden in zwei Hauptanwendungs-Gruppen gegliedert: Applets
und Anwendungen.
Applets sind Java-Programme, die über das WWW heruntergeladen und von einem
Web-Browser auf dem Rechner des Anwenders ausgeführt werden. Applets können
nur auf einem javafähigen Browser ausgeführt werden bzw. mit einem Tool des JDK,
dem Appletviewer, gesichtet werden.
Java-Anwendungen sind allgemeine, in der Java-Sprache geschriebene Programme. Zum Ausführen von Java-Anwendungen ist kein Browser nötig.
1.3.2.1 Entwicklung von Java-Anwendungen
1. Aufgabe: Erstelle eine Anwendung, die den Text „Willkommen in der JavaWelt“ ausgibt.
Lösungsschritte:
1) Erstelle die folgende Datei mit dem Namen „Willkommen.java“ mit Hilfe eines
Dateiaufbereiters (Editor):
class Willkommen
{
public static void main(String args[])
{
System.out.println("Willkommen in der Java-Welt!");
}
}
Das Programm umfaßt zwei Teile: eine Klassendefinition und ein
Programmabschnitt, der unter main() angegeben ist.
2) Speichern der Datei mit dem Namen „Willkommen.java“ unter einem beliebigen
Verzeichnis31.
3) Aufruf des Java-Übersetzers über die folgende Befehlszeileneingabe:
javac [optionen] dateiname.
optionen .... bestimmen das Verhalten vom Compiler
dateiname ... Name der Datei mit dem Java-Quellcode. "javac" fordert, daß
der Quelltext in Dateien steht, deren Dateiname die Extension ".java"
besitzt.
„javac“ kompiliert den Quellcode in Java-Bytecode und speichert seine Ausgabe in
einer Datei mit dem Namen „dateiname.class“. Standardmäßig werden .classDateien im gleichen Verzeichnis wie die .java-Quelldatei erzeugt32. Im vorliegenden
Fall ist der Aufruf: javac Willkommen.java.
4) Ausführen der Bytecode-Datei „Willkommen.class“ mit dem Java-Interpreter
java: „java Willkommen“. Der im JDK enthaltene Interpreter heißt „java“. Falls
alles richtig gelaufen ist, erscheint in der letzten (Ausgabe-) Zeile: Willkommen in
der Java-Welt!.
31
32
Vgl. pr13210
Mit der Option "-d" im javac-Aufruf kann die .class-Datei an einem anderen Ort gespeichert werden.
27
Programmieren in Java
2. Aufgabe: Erstelle eine Anwendung, die den Text „Herzlich Willkommen
Juergen Hubert Josef Liesel“ ausgibt. Die angebenen Namen
sollen auf Befehlszeilenebene als Parameter eingeben werden.
Lösungsschritte:
1) Erstellen einer Quelle (Datei), die folgenden Quellcode33 enthält:
class WillkommensGruss
{
public static void main(String args[])
{
System.out.print("Herzlich Willkommen ");
System.out.print(args[0]);
}
}
2) Speichern der Datei, Übersetzen und Aufruf des Programm mit dem Parameter
Juergen führt zu: „Herzlich Willkommen Juergen“.
Argumente auf Befehlszeilenebene werden in den Zeichenketten-Array „String
args[]“ aufgenommen. „args“ ist der Name des Zeichenketten-Arrays, das die
Argumentenliste enthält und dem Programm immer zur Verfügung steht. Hier wurde
nur das erste Argument der Liste (args[0])ausgegeben. Sollen alle Argumente, die
auf der Befehlszeilenebene eingegeben wurden, ausgegeben werden, dann ist die
Liste komponentenweise mit einer Zählschleife abzuarbeiten. Die Zählschleife (forSchleife) sieht in Java so aus:
for (Initialisierung; Test; Inkrement)
{
// Anweisungen
}
Initialisierung ist ein Ausdruck, der den Beginn der Zählschleife einleitet, z.B.
Initialisierung eines Schleifenindex (z.B. int i = 0;). Die Variablen, die in diesem
Teil der Schleife deklariert werden, sind lokal (in Bezug auf die Schleife). Das
bedeutet: Sie gehören zur Schleife und existieren nicht mehr nach der vollständigen
Ausführung der Schleife. Man kann in diesem Bereich mehr als eine Variable
initialisieren (durch Angabe mehrerer durch Kommas getrennte Ausdrücke, z.B.
int i = 0, int j = 10).
Test ist ein Ausdruck, der nach jeder Iteration der Schleife ausgeführt wird. Der Test
muß ein boolescher Ausdruck oder eine Funktion sein, die einen booleschen Wert
zurückgibt (true oder false). Ergibt der Test true, wird die Schleife ausgeführt. Sobald
er false ergibt, wird die Schleifenausführung angehalten.
Inkrement ist ein beliebiger Audruck oder Funktionsaufruf. Üblicherweise wird er
verwendet, um den Wert des Schleifenindex näher an den Endwert zu bringen und
damit für Beendigung der Schleife zu sorgen. Wie im Initialisierungsbereich kann im
Inkrement-Bereich mehr als ein Ausdruck untergebracht sein, falls die einzelnen
Ausdrücke mit Kommas voneinander getrennt sind.
Auf den Wert eines Elements in einem Array wird über den Namen des Array, gefolgt
von einem Index in eckigen Klammern zugegriffen (z.B. args[i]). Array-Indizes
beginnen mit 0. Alle Array-Indizes werden geprüft, um sicher zu stellen, daß sie sich
innerhalb der Grenzen des Array befinden, wie sie bei der Erzeugung des Arrays
festgelegt wurden. Die Länge des Array kann im Programm mit der Instanzvariablen
length getestet werden (z.B. args.length).
33
Vgl. pr13210
28
Programmieren in Java
3) Erweitern der Quellcode-Datei um eine Zählschleife, die die Argumentenliste
abarbeitet:
class WillkommensGruss
{
public static void main(String args[])
{
int i; // Lokale Variable
System.out.print("Herzlich Willkommen ");
for (i=0; i < args.length;i++)
{
System.out.print(args[i] + " ");
}
}
}
„args.length“ bestimmt die Länge der Argumentenliste.
4) Speichern der Datei, Übersetzen und Aufruf des Programm mit Parametern führt
zu: „Herzlich Willkommen Juergen Liesel Vera Christian Hubert“.
Auffällig ist: i spielt hier eigentlich gar keine große Rolle, es hat lediglich als Index
seine Berechtigung. Nur damit lässt sich das Element an einer bestimmten Stelle im
Array ansprechen. Da dieses Durchlaufen von Arrays häufig ist , haben die
Entwickler von Sun seit Java 1.5 eine Abkürzung für soche Iterationen in die Sprache
eingeführt:
for (Typ bezeichner : Feld)
...
Diese erweiterte for-Schleife löst sich vom Index und erfragt jedes Elemment des
Feldes. "WillkommensGruss" lässt sich dann so formulieren:
class WillkommensGruss
{
public static void main(String args[])
{
System.out.print("Herzlich Willkommen ");
for (String arg : args)
{
System.out.print(arg + " ");
}
System.out.println();
}
}
Die "for"-Schleife ist folgendermaßen zu lesen: "Für jedes Element arg vom Typ
String in args tue ... ". Ein Variablenname wie i für den Scheifenindex ist nicht mehr
nötig, denn der Index ist nicht sichtbar. Intern setzt der Compiler diese erweiterte
for-Schleife ganz klassisch um, sodass der Bytecode unter beiden Varianten gleich
ist.
3. Aufgabe: Die auf Befehlszeilenebene eingegebenen Namen sollen sortiert
werden.
So
soll
die
Eingabe
der
Befehlszeile
„java
WillkommensGruss
Juergen
Hubert
Josef
Liesel
Christian“ zu folgender Ausgabe führen: „Herzlich Willkommen
Christian Hubert Josef Juergen Liesel“.
Lösungsschritte:
1) Gesucht ist ein Algorithmus, der die über die Befehlszeile eingegebenen Namen
sortiert. Ein einfacher Sortieralgorithmus ist unter dem Namen „Bubble-Sort“
29
Programmieren in Java
bekannt. Man vergleicht dabei zunächst den ersten unter args[0] abgelegten Namen
gegen alle weiteren im Datenfeld args abgelegten Namen.
args
[0]
[1]
[2]
[3]
[4]
"Juergen"
"Hubert"
"Josef"
"Liesel"
"Christian"
Nach 4 Vergleichen hat das Datenfeld args folgende Gestalt angenommen:
args
[0]
[1]
[2]
[3]
[4]
"Christian"
"Juergen"
"Josef"
"Liesel"
"Hubert"
Die erste Position (args[0]) ist im Datenfeld damit schon richtig eingeordnet.
Danach muß jetzt der Name an der 2. Position ([1]) mit den übrigen Namen des
Datenfelds verglichen werden. Nach 3 Vergleichen zeigt sich folgendes Bild:
args
[0]
[1]
[2]
[3]
[4]
"Christian"
"Hubert"
"Juergen"
"Liesel"
"Josef"
„Christian“ und „Hubert“ sind jetzt richtig eingeordnet. Nun muß der Name an der
dritten Position mit allen noch verbliebenen Namen auf Position 4 und 5 noch
verglichen werden. Das Resultat der Vergleiche zeigt:
args
[0]
[1]
[2]
[3]
[4]
"Christian"
"Hubert"
"Josef"
"Liesel"
"Juergen"
Der nächste Durchgang führt dann zum sortierten Datenfeld:
args
[0]
[1]
[2]
[3]
[4]
"Christian"
"Hubert"
"Josef"
"Juergen"
"Liesel"
2) Der soeben beschriebene Sortieralgorithmus muß in Java-Programmcode
abgebildet werden. Es ist leicht erkennbar, daß hier zwei verschachtelte forSchleifen die Sortierung erreichen können.
for (i = 0; i < args.length; i++)
{
for (j = i + 1; j < args.length; j++)
{
30
Programmieren in Java
if (args[i].compareTo(args[j]) > 0)
{ // Tauschen
String temp = args[i]; // lokale Variable
args[i] = args[j];
args[j] = temp;
}
}
}
Zeichenketten werden über die Methode „compareTo“ der Klasse String
verglichen. Generell stehen die Vergleichsoperatoren (== != < <= > >=) nur für
das Vergleichen von Zahlen zur Verfügung.
Die Vergleichsbedingung wird durch das Schlüsselwort if erzeugt:
if (Ausdruck)
{
// Anweisung(en)
}
else {
// Anweisung(en)
}
Eine if-Bedingung verwendet einen boolschen Ausdruck für die Entscheidung, ob
eine Anweisung ausgeführt werden soll. Die Anweisung wird ausgeführt, wenn der
Ausdruck den Wert true zurückliefert. Falls gewünscht wird, daß eine bestimmte
Anweisung bzw. Anweisungen ausgeführt werden, wenn der boolsche Ausdruck
false zurückliefert, ist dieser Anweisungsblock durch das Schlüsselwort else
einzuleiten.
3) Das vollständige Programm umfaßt folgenden Quelltext:
class WillkommensGruss
{
public static void main(String args[])
{
int i, j; // Lokale Variable
System.out.print("Herzlich Willkommen ");
for (i = 0; i < args.length; i++)
{
for (j = i + 1; j < args.length; j++)
{
if (args[i].compareTo(args[j]) > 0)
{
String temp = args[i];
args[i] = args[j];
args[j] = temp;
}
}
}
for (i = 0; i < args.length; i++)
{
System.out.print(args[i] + " ");
}
System.out.println();
}
}
4) Speichern der Datei, Übersetzen und Aufruf des Programms mit Parametern führt
zu: „Herzlich Willkommen Christian Hubert Josef Juergen Liesel“.
4. Aufgabe: Die Argumentenliste args soll auf folgende Weise angezeigt werden:
31
Programmieren in Java
args[0]
args[1]
args[2]
args[3]
args[4]
=
=
=
=
=
"Christian"
"Hubert"
"Josef"
"Juergen"
"Liesel"
Lösungsschritte:
1) System.out.println() bzw. System.out.print() erwarten ein einziges
Argument innerhalb der Klammern. Sollen, wie hier gewünscht, mehrere Variablen,
vom Typ String oder Zeichenkettenliterale, Argument für println() sein, dann
können diese Elemente mit dem Verkettungsoperator „+“ zu einem einzigen String
oder
Zeichenkettenliteral
verknüpft
werden.
Der
Umgang
mit
dem
Verkettungsoperator ist in Java einfach, da er alle Variablentypen und Objektwerte
wie Strings behandelt. Sobald ein Teil einer Verkettung ein String oder ein StringLiteral
ist,
werden
alle
Operatoren
wie
Strings
behandelt,
z.B.:
System.out.println("1 + 2 = " + 3);.
2) Die Ausgabe kann über die folgende Anweisungen so erfolgen
for (i = 0; i < args.length; i++)
{
System.out.println("args[" + i + "]
}
System.out.println();
= \"" + args[i] + "\"");
Die Zeichen für doppelte Anführungszeichen(") und Backslash(\) müssen durch die
Verwendung von sog. Escape-Sequenzen (\" und \\) dargestellt werden. Die
beiden Anführungszeichen, die das Literal umschliessen, müssen in derselben Zeile
des Quellcodes stehen. Eine neue Zeile kann innerhalb eines Literals durch die
Escape-Sequenz \n erreicht werden.
5. Aufgabe: Zwei Gleitpunktzahlen sollen über die Befehlszeile eingelesen werden.
Anschließend sollen mit diesen beiden Zahlen alle zugelassenen,
binären arithmetischen Operationen für Gleitpunktzahlen (des primitiven
Datentyps float) und unäre Inkrement- bzw. Dekrement-Operationen
ausgeführt werden.
Lösungsschritte:
1) Die Übergabe der beiden Zahlen über die Befehlsargumentenliste erfordert die
Konvertierung des Typs String in einen Typ der Klasse Float. Das konvertierte
Datum kann anschließend einer Variablen des primitiven Typs float zugewiesen
werden.
float x = Float.valueOf(args[0]).floatValue();
float y = Float.valueOf(args[1]).floatValue();
2) Für ganze Zahlen und Gleitpunktzahlen sind Additions- (+), Subtraktions- (-),
Multiplikations- (*) und Divisionsopertoren (/) definiert. Es gibt außerdem für unäre
Arithmetik noch Inkrement- und Dekrementoperatoren zur Manipulation des Werts
von Variablen.
3) Der Quelltext zur Programmlösung34 ist dann:
public class FloatDemo
{
public static void main(String args[])
{
34
FloatDemo.java aus pr13210
32
Programmieren in Java
}
}
// Konvertieren
float x = Float.valueOf(args[0]).floatValue();
float y = Float.valueOf(args[1]).floatValue();
// Ausgabe der ueber die Befehlszeile eingegebenen Zahlen
System.out.println("x = " + x);
System.out.println("y = " + y);
// Binaere Arithmetik mit den Operatoren + - * /
float z;
z = x + y;
System.out.println("z = x + y = " + z);
z = x - y;
System.out.println("z = x - y = " + z);
z = x * y;
System.out.println("z = x * y = " + z);
z = x / y;
System.out.println("z = x / y = " + z);
// Unaere Arithmetik mit Inkrement- / Dekrementoperator
x++;
System.out.println("Nach x++: x = " + x);
y--;
System.out.println("Nach y--: y = " + y);
z = x++;
System.out.println("Nach z = x++: z = " + z + ", x = " + x);
z = ++x;
System.out.println("Nach z = ++x: z = " + z + ", x = " + x);
System.out.println("x = " + x);
System.out.println("y = " + y);
z = ++x + y--;
System.out.println("nach z = ++x + y--: z = " + z + ", x = "
+ x + " y = " + y);
System.out.println("x = " + x);
System.out.println("y = " + y);
z = x + y * ++y;
System.out.println("nach z = x + y * ++y: z = " + z + ", x = "
+ x + " y = " + y);
z = (float) (1.0f / 0.0f);
System.out.println("z = (float) (1.0f / 0.0f) = " + z);
4) Der Aufruf java FloatDemo 17 4 führt dann zu folgenden Ausgabe:
x = 17.0
y = 4.0
z = x + y = 21.0
z = x - y = 13.0
z = x * y = 68.0
z = x / y = 4.25
Nach x++: x = 18.0
Nach y--: y = 3.0
Nach z = x++: z = 18.0, x = 19.0
Nach z = ++x: x = 20.0, x = 20.0
x = 20.0
y = 3.0
Nach z = ++x + y--: z = 24.0, x = 21.0 y = 2.0
x = 21.0
y = 2.0
Nach z = x + y * ++y: z = 27.0, x = 21.0 y = 3.0
z = (float) (1.0f / 0.0f) = Infinity
Das zuletzt angegebenen Resultat zeigt die Zuordnung "Infinity" falls eine Zahl
ungleich Null durch Null geteilt wird. 1.0f und 0.0f sind Gleitpunkt-Literale. Da
"Infinity" als sehr groß interpretiert (Typ double) wird, muß hier in den Typ
float konvertiert werden.
33
Programmieren in Java
Die Anwendung zeigt außerdem die Anwendungsweise der Inkrement- und
Dekrementoperatoren in Präfix- und Postfix-Schreibweise. Steht der Operator vor der
Variablen (z.B. ++x), dann wird er angewendet, bevor der Wert der Variablen in eine
Anweisung eingesetzt wird. Im umgekehrten Fall (z.B. x++) wird die Variable zuerst
in einer Anweisung benutzt und dann erhöht.
6. Aufgabe: In der 5. Aufgabe führt der Aufruf java FloatDemo zu einem Fehler.
Dieser Fehler soll aufgefangen werden.
Lösungsschritte:
1)
Java
verfügt
zur
Behandlung
von
Fehlern
eine
spezielle
Ausnahmenbehandlungsroutine (exception handling) den „try-catch“-Block. Im
„try“-Block wird der normale Ablauf behandelt. Im „catch“-Block erfolgt die
Behandlung der Ausnahmen. Die Verzweigung in den „catch“-Block erfolgt beim
Auftreten einer Ausnahme automatisch, der „try“-Block bleibt dabei unberührt.
2) Der Quelltext nimmt nach dem Einfügen eines „try-catch“-Blocks folgende
Gestalt an:
public class FloatDemo
{
public static void main(String args[])
{
try {
// Konvertieren
................
}
catch(Exception e)
{
System.out.println("Fehler bei der Eingabe: java FloatDemo a b");
System.out.println(e.getMessage());
}
}
}
1.3.2.2 Entwicklung von Java-Applets
Die Entwicklung eines Applets unterscheidet sich von einer Anwendung, weil JavaApplets in einer Web-Seite mit anderen Seitenelementen zusammen ausgeführt
werden. Zur Ausführung eines Applets ist es nötig, das Applet in einem HTMLDokument35 einzubetten. In diesem HTML-Dokument werden dem javafähigen
Browser die Informationen mitgeteilt, die er zur Ausführung des Applets benötigt. Der
Browser lädt die Klassendatei und führt das Applet automatisch aus.
Aus der Sicht des Programmierers ist das Applet eine Klasse, die von der AppletKlasse abgeleitet wird.
1. Aufgabe: Erstelle ein Applet, das in einem Fenster den Text „Herzlich
Willkommen in der Java-Welt“ ausgibt
Lösungsschritte:
1) Erstelle die folgende Datei mit dem Namen „WillkommenApplet.java“ mit Hilfe
eines Dateiaufbereiters (Editor):
35 eine entsprechende Referenz innerhalb einer HTML-Seite mit einem speziellen Tag, dem <APPLET>-Tag
erledigt die Einbettung in den Browser
34
Programmieren in Java
/* Das erste Java-Applet */
import java.awt.Graphics;
public class WillkommenApplet extends java.applet.Applet
{
public void paint (Graphics g)
{
g.drawString("Herzlich willkommen in der Java Welt!",5,25);
}
}
Durch die „import“-Anweisung können Entwickler Klassen verwenden, die in
anderen Dateien definiert sind. Compiler bzw. Interpreter greifen auf die classDateien zu. Über „import“ wird bestimmt, wo diese Dateien liegen. Java importiert
immer das Paket „java.lang“, denn hier ist die Klasse Object enthalten, von der
alle Java-Klassen abgeleitet sind. Die Graphics-Klasse enthält Methoden zum
Zeichnen von Textzeichen und Zeichenketten. Mit der „drawstring“-Methode der
Graphics-Klasse können Textzeichen auf den Bildschirm gemalt werden.
Die folgende Abbildung zeigt die Modellierung36 des vorliegenden Quellcodes:
WillkommenApplet
paint()
g.drawString
(“Herzlich Willkommen in der Java-Welt!“,5,25)
Abb. 1.3-2: Klassendiagramm zu WillkommenApplet
Die Klasse WillkommenApplet läßt sich grafisch als rechteckiges Symbol
darstellen. Die paint()-Methode wird ohne formale Parameter beschrieben, ihre
Implementierung wird durch die beigefügte Notiz gezeigt.
Die unmittelbare Superklasse wird im Quelltext direkt in der „extends“-Klausel
angegeben. „extends java.applet.Applet“ bestimmt das die angegebene
Applet-Klasse von der Applet-Klasse des Abstract Windowing Toolkit (AWT)
abgeleitet ist. Der andere Teil der Klassendefinition enthält das Schlüsselwort
„public“ mit der Bedeutung: Die Klasse ist nach dem Laden für das gesamte JavaSystem verfügbar. Applets müssen „public“ deklariert werden.
Die folgende Abbildung zeigt die Beziehungen der Klasse WillkommenApplet zu
ihren unmittelbaren Nachbarn:
Applet
WillkommenApplet
paint()
Graphics
36 Die Modellierung erfolgt nach den Regeln der Unified Modelling Language (UML). Die UML ist eine
grafische, standardisierte Sprache zum Spezifizieren, Konstruieren, Visualisieren und Dokumentieren.
35
Programmieren in Java
Abb. 1.3-3: Die unmittelbare Umgebung von WillkommenApplet
Die gerichtete Linie mit der unausgefüllten Pfeilspitze von WillkommenApplet zu
Applet repräsentiert eine Generalisierung, d.h.: WillkommenApplet ist eine
Unterklasse
von
Applet.
Der
gestrichelte
Pfeil
repräsentiert
eine
Abhängigkeitbeziehung: WillkommenApplet verwendet Graphics.
Ein eigenes, vom Benutzer erstelltes Applet überschreibt gewöhnlich Methoden, die
in der Superklasse Applet definiert sind. Diese Methoden übernehmen Aufgaben zur
Initialisierung des Applet vor der Ausführung (public void start()), zur
Reaktion auf Mauseingaben, zum Anhalten des Applet (public void stop())
und zu Aufräumungsarbeiten (public void destroy()), wenn das Applet
beendet wird. Eine dieser Methoden ist paint(), die sich um die Anzeige des
Applet in einer Webseite kümmert. Die paint()-Methode besitzt ein einziges
Argument, eine Instanz der Klasse Graphics. Die Klasse Graphics stellt
Verhaltensweisen zur Darstellung von Schriften, Farben, zum Zeichnen von Linien37,
von Ellipsen bzw. Kreisen38, von Rechtecken39 und anderen Formen zur Verfügung.
In der paint()-Methode wurde hier der String "Herzlich Willkommen in der
Java Welt!" bei den (x,y)-Koordinaten (5,25) ausgegeben. Der Ursprung des
Koordinatensystems liegt in der linken oberen Ecke des Darstellungsbereichs. Der
String wird in einer defaultmäßig festgelegten Schrift und Farbe angezeigt.
Die Untersuchung der Java-Bibliotheken zu Applet und Graphics zeigt: Die beiden
Klassen sind Teil einer größeren Hierarchie. Verfolgt man die von Applet erweiterten
und implementierten Klassen, dann kann man das folgende Klassendiagramm
erhalten:
Object
Component
ImageObserver
Container
Panel
Applet
WillkommenApplet
37
public void drawLine(int x1, int y1, int x2, int y2); (x1,y1) bestimmt den
Anfangspunkt, (x2,y2) bestimmt den Endpunkt der Linie.
38 public void drawOval(int x, int y, int width, int height); (x,y) gibt die
Koordinaten der oberen linken Ecke des die Ellipse umschreibenden Rechtecks mit der Höhe height und der
Breite width am
39 public void drawRect(int x, int y, int width, int height); bestimmt die obere
linke Ecke des Rechtecks, (width, height) legen Breite und Höhe des Rechtecks fest.
36
Programmieren in Java
Abb. 1.3-4: Vererbungshierarchie von WillkommenApplet
Die Beziehung zwischen ImageObserver und Component ist eine Schnittstelle.
ImageObserver wird von Component implementiert.
WillkommenApplet arbeitet mit den Klassen Applet und Graphics unmittelbar
zusammen. Diese beiden Klassen bilden lediglich einen kleinen Ausschnitt aus der
Bibliothek mit vordefinierten Java-Klassen. Die Verwaltung dieser Klassen und
Schnittstellen organisiert Java in mehreren verschiedenen Paketen. Das Wurzelpaket
in der Java-Umgebung heißt java. In dieses Paket sind mehrere weitere Pakete
geschachtelt, die wiederum andere Pakete, Schnittstellen und Klassen enthalten.
Object existiert im Paket lang, Panel, Container, Component existieren im
Paket awt, und die Klasse Applet im Paket applet. Die Schnittstelle
ImageObserver existiert im Paket image, das liegt wiederum im Paket awt
(qualifizierter Name: java.awt.ImageObeserver). Die Paketstruktur40 kann in
einem Klassendiagramm visualisiert werden:
java
WillkommenApplet
applet
awt
lang
Abb. 1.3-5: Paketstruktur im WillkommenApplet
2) Speichern der Datei mit dem Namen „WillkommenApplet.java“ unter einem
beliebigen Verzeichnis.
3) Aufruf des Java-Übersetzers über die folgende Befehlszeileneingabe: „javac
WillkommenApplet.java“. Der Compiler gibt bei erfolgreicher Übersetzung keine
Rückmeldung. Zwei Dateien müssen nach erfolgreicher Übersetzung vorliegen:
„WillkommenApplet.java“ und „WillkommenApplet.class“.
4) Erstellen der folgenden HTML-Datei „WillkommenApplet.html“, anschließend
Speichern dieser Datei.
<HTML>
<HEAD>
<TITLE>Seid gegruesst!</TITLE>
</HEAD>
<BODY>
<P>Mein Java Applet sagt:<BR>
<APPLET CODE="WillkommenApplet.class" WIDTH=200 HEIGHT=50>
</APPLET>
</BODY>
</HTML>
Der Bezugspunkt in HTML-Dateien auf ein Applet erfolgt mit dem <Applet>-Tag. Das
Code-Attribut dient zur Angabe von dem Namen der Klasse, die das Applet enthält.
40 Pakete werden in der UML als Akten mit Reitern dargestellt. Die gestrichelten Pfeile repräsentieren die
Abhängigkeiten zwischen den Paketen.
37
Programmieren in Java
Die Attribute WIDTH und HEIGHT dienen zum Bestimmen der Größe des Applets.
Der Browser benutzt diese Werte zur Zuteilung des Raums, der für das Applet auf
der Seite freigehalten werden muß. Hier wurde eine Box mit einer Breite von 200 und
einer Höhe von 50 Pixeln definiert.
WillkommenApplet ist als Applet implementiert. Es kann nie isoliert stehen, sondern
ist normalerweise Teil einer Webseite.
WillkommenApplet.java
WillkommenApplet.class
WillkommenApplet.html
Abb. 1.3-6: Die Komponenten von WillkommenApplet
5) Ausführung des Applet mit einem javafähigen Web-Browser bzw. dem
Appletviewer, z.B. mit dem Aufruf „appletviewer WillkommenApplet.html“. Das
Resultat müßte so aussehen:
Abb. 1.3-7: Darstellung des Fensters mit dem Appletviewer
In einem Browser würde zusätzlich der Text rund um das Applet („Mein Java
Applet sagt:“) gezeigt werden.
2. Aufgabe: Verändern von Schrift und Farbe für den Text „Herzlich Willkommen
in der Java-Welt“.
Lösungsschritte:
1) Erweitere die Datei mit dem Namen „WillkommenApplet.java“ mit Hilfe eines
Dateiaufbereites (Editor).
Die erste Erweiterung soll die Schrift verändern, in der der Text ausgegeben wird. Es
wird ein Objekt der Klasse java.awt.Font über folgende Anweisung erzeugt: Font f
= new Font("TimesRoman",Font.BOLD,12);
Objekte der Klasse Font dienen zum Bereitstellen verschiedener Schriftarten für die
Methode drawString() und repräsentieren den Namen, den Stil und die Größe
einer Schrift. Mit einem spezifischen Objekt der Klasse Font kann man eine Schrift
aufrufen, die sich von der standardmäßig in Applets benutzten Schrift unterscheidet.
Dem Font-Objekt wird hier die Schrift „TimesRoman“, fett in „12-Punkt“ Größe
38
Programmieren in Java
zugewiesen. Das neue Objekt wird anschließend der Instanzvariablen f zugewiesen
und ist so der Methode paint() zugänglich:
public void paint (Graphics g)
{
// Mitteilung: Die Schrift zur Anzeige von Text befindet sich in der
// Instanzvariablen f
g.setFont(f);
// Mitteilung: Die Farbe fuer die Ausgabe ist gelb
g.setColor(Color.yellow);
// Die glebe Farbe dient zum Fuellen eines Rahmens fuer den Text, der
// aus einem Rechteck mit abgerundeten Ecken besteht
g.fillRoundRect(0,0,225,30,10,10);
// Mitteilung: die Farbe fuer Textausgaben ist
// eine Instanz der Klasse Color fuer die Farbe rot
g.setColor(Color.red);
// Mitteilung: Text wird in festgelegter Schrift und Farbe bei den
// (x,y)-Koordinaten (5,25) ausgegeben.
g.drawString("Herzlich Willkommen in der Java Welt!",5,25);
}
Die Klassen Font und Color werden über die folgenden import-Anweisungen
bereitgestellt:
import java.awt.Graphics;
import java.awt.Font;
import java.awt.Color;
Dafür kann man auch „import java.awt.*;“1 schreiben.
2) Kompiliere die Datei „WillkommenApplet.java“ in eine „.class“-Datei.
3) Ausführung des Applet, z.B. über den Aufruf appletviewer
WillkommenApplet.html.
3. Aufgabe: Erweitere das Applet der 1. Aufgabe um einen Dokumentationskommentar
Java kennt verschiedene Kommentarzeichen
- für die interne Dokumentation: // und die Kombination /* ... */
- für die externe Dokumentation: //** ... */
Grundsätzlich steht der Dokumentationskommentar vor Klassen, (Interfaces,
Konstruktoren,) Methoden, z.B.42:
/* Das erste Java Applet */
import java.awt.Graphics;
/** Dieses Applet demonstriert den Grundaufbau von Applets.
*** @author Juergen Sauer
*** @version WS 2001/2002
*** @see WillkommenApplet01
*/
public class WillkommenApplet01 extends java.applet.Applet
{
/** Die Methode paint ist von Component geerbt
*** Sie wird aufgerufen, wenn das Applet neu gezeichnet werden muss;.
41
42
Diese Anweisung stellt alle Klassen des Pakets java.awt zur Verfügung
pr13210
39
Programmieren in Java
*** @param g Eine Referenz eines Grafikobjekts
*/
public void paint (Graphics g)
{
g.drawString("Herzlich Willkommen in der Java Welt!",5,25);
}
}
Das Programm, das einen derartigen Quellcode zu einer HTML-Dokumentation
verarbeitet, heißt javadoc.exe und ist im Java-SDK von Sun43 enthalten.
Aufrufsyntax: javadoc [flags] [klasse|package]
Für „flags“ kann * oder folgende Schalter angegeben werden:
-version
-author
-sourcepath
-classpath
-doctype
-noindex
-notree
@version berücksichtigen
@author berücksichtigen
Suchpfad für Klassen
Synonym für sourcepath
Ausgabetyp (Standard: html)
Kein Verzeichnis aller Konstruktoren und Methoden erzeugen
Keine Java-Klassenhierarchie erzeugen
Falls das vorliegende Beispiel mit javadoc –author –version –notree –
noindex WillkommenApplet01.java aufgerufen wird, erzeugt das Programm
eine Datei WillkommenApplet01.html, die Auswertungen der Java-API zeigt und
den Kommentar, z. B.:
43
siehe auch: http://java.sun.com/javadoc/
40
Programmieren in Java
Abb. 1.3-8: Ausschnitt zum über javadoc erzeugten Dokument
4. Aufgabe: Überwachen des Lebenszyklus eines Applet.
Ein Applet führt keine Aktionen aus eigener Initiative aus, sondern empfängt
Ereignisse vom System und liefert Ergebnisse zurück. Beim Start eines Applet ruft
der Webbrowser die Methode init() des Appletobjekts auf. Beim Beenden eines
Applets wird die Methode destroy() aufgerufen. init() und destroy() werden im
Lebenszyklus eines Applet genau einmal ausgeführt, nämlich beim Erzeugen bzw.
beim Beenden eines Objekts. Zusätzlich sind zwei weitere Methoden start() und
stop() vorgesehen, die an verschiedenen Stellen zur Aktivierung aufgerufen werden
können. Wählt ein Anwender bei gestartetem Applet z.B. im Browser eine andere
Internetseite an, so ruft der Browser die stop()-Routine auf und hält das Applet an,
bis der Anwender wieder auf die Appletseite zurückkehrt. Dann ruft der Browser die
start()-Routine zum ordnungsgemäßen Fortsetzen des Applet auf.
import java.awt.*;
import java.applet.*;
41
Programmieren in Java
public class AppletDemo extends Applet
{
// Instanzvariable
String s;
int
initialisierungen = 0;
int
startpunkte
= 0;
int
haltepunkte
= 0;
// Methoden
public void init() { initialisierungen++; }
public void start() { startpunkte++; }
public void stop() { haltepunkte++; }
public void paint(Graphics g)
{
// Zur Ausgabe einer geeigneten Nachricht über das Verhalten
// des Applet wird der String s mit Hilfe des Opertors + aus
// Teilstrings zusammengesetzt. Ganze Zahlen werden dabei in Zei// chenketten konvertiert.
s = "Initialisierungen: " + initialisierungen +
", Startpunkte: " + startpunkte +
", Haltepunkte: " + haltepunkte;
g.drawString(s,10,10);
}
}
Browser
öffnet
<<new>>
Webseite
:Applet
init()
start()
paint()
verlässt
stop()
Browser
schliesst
destroy()
Browser
Abb.: Lebenszyklus eines Applets
5. Aufgabe: Entwickle aus der vorliegenden Aufgabe eine allgemeine AppletSchablone, die als Vorlage für das Erstellen von Applets dienen kann.
Das folgende Gerüst beschreibt ein Muster für alle Applets:
//
//
//
//
//
//
Name der Klasse:
Beschreibung:
Import der Pakete
import java.lang.*;
import java.applet.*;
import java.awt.*;
// Top-Level-Klassen-Deklaration bzw. Definition des Applets
public Klassenname extends java.applet.Applet
42
Programmieren in Java
{
// Variablen-Deklarationen bzw. Definitionen
// ...
// Eigene Methoden
// ...
// Methoden, die ueberschrieben werden
//
public void init()
{
// ...
}
public void start()
{
// ...
}
public void stop()
{
// ...
}
public void destroy()
{
// ...
}
// Optional: die Ausgabemethode
public void paint(Graphics g)
{
// ..
}
}
Ein Applet erbt Methoden und Variablen der Applet-Klasse. Die Applet-Klasse erbt
wiederum
von
einer
Reihe
anderer
Klassen.
Eine
Klasse,
die
java.applet.Applet erweitert, kann Variablen und Methoden aus
java.lang.Object, java.awt.Component, java.awt.Container sowie
java.awt.Panel verwenden.
1.3.3 Programmablaufpläne und Strukturierte Programmierung
1.3.3.1 Darstellung von Algorithmen durch Programmablaufpläne
Komposition
Aus einer Anweisungsfolge44 (mit n ∈ N ) „anweisung1 anweisung2 ... anweisungn“
konstuiert man durch Klammerung mit { und } eine Anweisung, die Komposition der
Anweisungen der Folge
{ anweisung1 anweisung2 ... anweisungn }
Die Anweisungen werden nacheinander in der angegebenen Reihenfolge ausgeführt.
Die Klammern umschließen einen Block, in dem Variablen neu bzw. lokal vereinbart
werden können.
44
vgl. 2.4.1
43
Programmieren in Java
In Programmabläufplänen werden Anweisungen in Anweisungsknoten (Symbol:
Kästchen) dargestellt.
Abb.: Anweisungsknoten
Neben Anweisungsknoten gibt es in Prgrammablaufplänen Testknoten. Sie werden
mit einem Rhombus symbolisiert. Testknoten enthalten Tests. Ist die Bedingung
erfüllt bestimmt der mit „true“ bewertete Pfeil den nächsten Knoten. Anderenfalls der
mit „false“ bewertete.
Abb.: Testknoten
Daneben gibt es noch initale und finale Knoten:
Abb.: Initialer bzw. finaler Knoten
Bedingte Anweisung45
Java-Notation: if (bedingung) anweisung
Programmablaufplan:
true
false
Abb.: Bedingte Anweisung
Verzweigungsanweisung
Java-Notation: if (bedingung) anweisung1 else anweisung2
45
vgl. 2.4.6
44
Programmieren in Java
Programmablaufplan:
true
false
Abb.: Verzweigungsannweisung
Endgeprüfte Schleife46
Java-Notation: // Vorbedingung
do
anweisung
// Invariante
while (bedingung)
// Nachbedingung
Programmablaufplan:
true
false
Abb.: Endgeprüfte Schleife
Anfangsgeprüfte Schleife47
Java-Notation: // Vorbedingung
while (bedingung)
// Invariante
anweisung
// Nachbedingung
46
47
vgl. 2.4.7
vgl. 2.4.7
45
Programmieren in Java
Programmablaufplan:
false
true
Abb.: Anfangsgeprüfte Schleife
Bsp.: Flussdiagramm zur Berechnung des größten gemeinsamen Teilers von m und
n.
Start
x=m
y=n
T
F
x == y
z=x
x>y
Ende
x=x–y
y=y-x
1.3.3.2 Darstellung von Algorithmen mit Struktogrammen
Jedes Programm kann mit drei Grundstrukturen realisiert werden:
- Sequenz (Komposition)
Bei einer Sequenz werden die Anweisungen nacheinander, der Reihe nach, durchgeführt.
- Fallunterscheidung
Hier wird aufgrund einer Bedingung einer von 2 Fällen ausgewertet.
- Schleife
46
Programmieren in Java
Bei einer Schleife wird eine Verarbeitung wiederholt durchgeführt, solange eine Bedingung erfüllt ist.
Programme, die aus diesen Grundstrukturen aufgebaut sind, nennt man strukturierte
Programme48. Zum Programmentwurf nach der Methode der "strukturierten
Programmierung" gehört das Prinzip der schrittweisen Verfeinerung (stepwise
refinement). Man beginnt mit einem Grobansatz und verfeinert schrittweise49. Dabei
wird die Gesamtaufgabe in Teilaufgaben aufgeteilt, die unabhängig voneinander
entwickelt werden können (Modularisierung).
1. Die Sequenz (Komposition)
Aus einer Anweisungsfolge50 (mit n ∈ N ) „anweisung1 anweisung2 ... anweisungn“
konstuiert man durch Klammerung mit { und } eine Anweisung, die Komposition der
Anweisungen der Folge
{ anweisung1 anweisung2 ... anweisungn }
Die Anweisungen werden nacheinander in der angegebenen Reihenfolge ausgeführt.
Die Klammern umschließen einen Block, in dem Variablen neu bzw. lokal vereinbart
werden können.
In Programmabläufplänen werden Anweisungen in Anweisungsknoten (Symbol:
Kästchen) dargestellt.
Abb.: Anweisungsknoten
2. Die Selektion (Fallunterscheidung)
Bei einer Sektion wird aufgrund einer Bedingung entschieden51, welches der beiden
Anweisungen durchgeführt wird, z.B.:
if (n > 0)
System.out.println("positiv");
else
System.out.println("kleiner oder gleich 0");
48
Entscheidender Punkt: Verbannung des Sprungbefehls "goto" aus den Programmen (zentrale Ursache für
undurchsichtige Programme).
49 Top Down Prinzip
50 vgl. 2.4.1
51 vgl. 2.4.6
47
Programmieren in Java
Allgemeines Format
true
if (bedingung)
statementA;
else
statemebtB;
false
statementA
statementB
Abb.: Graf. Darstellung eines Struktugramms für eine Selektion
In einem if-Statement, kann der else-Teil wegfallen. Falls in if- oder im elseZweig mehrere Anweisungen durchzuführen sind, müssen diese mit geschweiften
Klammern als zusammengesetztes Statement geschrieben werden.
Aufbau von Bedingungen (Boolesche Ausdrücke)52: Die in Fallunterscheidungen
auftretende Bedingung ist ein Boolescher Ausdruck, d.h. ein Ausdruck mit den
möglichen Werten true oder false. Boolesche Ausdrücke sind aufgebaut aus:
- Vergleichsoperatoren
<
<=
>
>=
==
!=
kleiner
kleiner oder gleich
groesser
groesser oder gleich
gleich
ungleich
- logischen Verknüpfungen
&&
||
!
Und-Verknüpfung
Oder-Verknüpfung
Negation
- Klammern ()
Typen-Konversionen bei Vergleichen: Bei Vergleichen werden (wie in algebraischen
Ausdrücken) automatisch erforderliche Typen-Konversionen durchgeführt, sodass
"int" mit "float" usw. miteinander verglichen werden kann.
Verschachtelte if-Statements: In verschachtelten if-Anweisungen bezieht sich ein
"else" immer auf das nächste "if".
true
n>0
false
n == 0
true
false
"positiv"
"null"
"negativ"
int n;
if ( n > 0)
System.out.println("positiv");
else
if (n == 0)
System.out.println("null");
else
System.out.println("negativ");
Das switch-Statement: Für Mehrfach-Fallunterscheidungen, welche aufgrund eines
ganzzahligen Werts erfolgen, stellt Java ein übersichtliches Statement zur
Verfügung, das switch-Statement53, z.B.:
52
53
vgl. 2.3.5
vgl. 2.4.6
48
Programmieren in Java
int wochentag;
switch (wochentag)
{
case 0: System.out.println("Sonntag");
break;
case 1: System.out.println("Montag");
break;
case 2: System.out.println("Dienstag"),
break;
…
case 6: System.out.println("Samstag");
break;;
default: System.out.println("ungueltiger Tag");
}
3. Schleifen
Man unterscheidet drei Arten von Schleifen.
- "while"-Schleife54
Bei einer while-Schleife wird die Aktion wiederholt durchgeführt, solange eine bestimmte Bedingung
erfüllt ist (Fortsetzungsbedingung). Die Bedingung wird bei jedem Durchgang vor der Durchführung
der Aktion geprüft.
- Zählschleifen ("for"-Schleifen)
Bei einer Zählschleife wird ein Zähler (die sog. Laufvariable) verwendet. Die Aktion der Schleife wird
für jeden Wert der Laufvariablen von einem Anfangswert bis (inklusive) einem Endwert durchgeführt.
- "do-while"-Schleifen
Diese Schleifen funktionieren wie die "while"-Schleifen mit dem Unterschied, dass die
Fortsetzungsbedingung bei jedem Durchgang nach der Aktion geprüft wird.
1. while-Schleifen (Anfangsgeprüfte Schleifen)
solange bedingung erfuellt
while (bedingung)
{
statement;
…
…
statement;
….
…
}
statement
....
statement
....
statement
2. for-Schleifen55, z.B.:
int i;
for (i = 0; i < 100; i++)
{
54
55
vgl. 2.4.7
vgl. 2.4.7
49
Programmieren in Java
System.out.print("Durchlauf: ");
System.out.println(i);
}
Lokale Laufvariablen: Im Initialisierungs-Statement einer for-Anweisung kann eine
Laufvariable direkt durch eine vorangestellte Typangabe definiert werden.
Bsp.: Berechnung der ersten 100000 Glieder der Harmonischen Reihe
double summe = 0;
for (int i = 0; i < 100000; i++)
summe += 1.0 / i;
System.out.println("Summe: " + summe);
3. do-while-Schleifen56 (Endgeprüfte Scheifen)
do
{
statement;
....
statement;
} while (bedingung)
statement;
…
…
statement;
solange bedingung erfuellt
Bei einer do-while-Anweisung werden die Anweisungen der Schleife mindestens
einmal durchgeführt.
Bsp.: Ausgabe der Ziffern einer nicht negativen, ganzen Zahl
do
{
System.out.println(n % 10);
n = n / 10;
} while (n > 0);
4. Beenden eines Programms
Mit dem Statement System.exit(0) kann ein Programm an einer beliebigen Stelle
beendet werden. Dabei wird ein Return-Code ausgegeben (0 für normale Ende).
Dieser Code kann bei Bedarf auf Betriebssystem-Ebene verwertet werden.
5. Bsp.:
1. BubbleSort
56
vgl. 2.4.7
50
Programmieren in Java
swapped = false
swapped = false
Schleife über Arrayposition i von 0 bis Arraylänge - 1
array[i] > array[i+1]
swap(a,i,i+1)
swapped = true
swapped?
2. Suche nach dem ersten Vorkommen von Musterzeichenfolge pattern[0..m-1]
in Textzeichenfolge text[0..n-1]
Ansatz: Ab jeder Position i prügfen, ob der Test text[i..i + (m – 1)] mit dem
Muster pattern übereinstimmt.
letztes-bearbeitungswürdiges-Zeichen = textLaenge - musterLaenge
Schleife über alle bearbeitungswürdigen Zeichenpositionen i im Text
String substr = Substring im Text von Postion i bis i + Musterlänge
substr stimmt mit Muster überein
return: i
return: -1
Implementierung:
public static int search(String text, String pattern)
{
final int last = text.length() - pattern.length();
for (int i = 0; i <= last; i++)
{
String substr = text.substring(i,i+pattern.length());
if (substr.equals(pattern)) return i;
}
return -1;
}
51
Programmieren in Java
1.3.4 Datentyp und Datenstruktur
Zum Umgang eines Programms mit Werten benötigt man Variable. Eine Variable ist
ein Bezeichnung, unter der Daten im Speicher gehalten werden. Variable müssen vor
der ersten Verwendung vereinbart (deklariert) werden und einen bestimmten
Datentyp besitzen, z.B.:
double a = 2.7;
double b = 3.5;
"a" und "b" tragen jeweils ihren eigenen Wert in sich und sind Variable von einem primitiven
(elementaren, skalaren) Datentyp.
Werte von einem primitiven Typ
- werden bereits bei der Variablendefintion angelegt
- haben eine Literaldarstellung
- können Parameter- und Rückgabewert bei Methoden sein
- können mit Operatoren verknüpft werden.
Java besitzt acht primitive Datentypen57:
- vier Ganzzahltypen mit unterschiedlichen Wertebereichen
- zwei Gleitpunktzahlentypen mit unterschiedlichen Wertebereichen nach IEEE Standard of Binary Floating
Point Arithmetic.
- einen Zeichentyp
Primitiver Typ
boolean
char
byte
short
int
long
float
double
void
Größe
1-Bit
16-Bit
8-Bit
16-Bit
32-Bit
64-Bit
32-Bit
64-Bit
-
Minimum
Unicode 0
-128
-215
-231
-263
IEEE 754
IEEE 754
-
Abb.:
Datentyp
ist bestimmt durch
-
eine Menge von Daten (Werte)
eine Menge von Operationen auf diesen Daten
Datenstruktur
ist ein Datentyp mit folgenden Eigenschaften
1. Die besteht aus mehreren Datenelementen. Diese können
57
vgl. 2.2.1
52
Maximum
Unicode 216-1
+128
+215-1
+231-1
+263-1
IEEE 754
IEEE 754
-
Wrapper-Klasse
Boolean
Character
Byte
Short
Integer
Long
Float
Double
Void
Programmieren in Java
-
atomare Datentypen oder
selbst Datenstrukturen sein
2. Sie setzt die Elemente durch eine Menge von Regeln (eine Struktur) in eine Beziehung (Relation).
Exemplare (Instanzen, Objekte) von einem Referenzdatentyp müssen (fast immer)
explizit mit new und einem Konstruktor angefordert werden.
Elementare Strukturrelationen:
Menge
lineare Struktur (gerichtete 1:1-Relation)
Baum (hierarchisch)
(gerichtete 1 : n – Relation)
Graph (Netzwerk)
( n : m Relation)
Datenstrukturen dienen zur Organisation von Daten, um bestimmte Operationen
(effizient) zu unterstützen. Einfache Datenstrukturen, die von Programmiersprachen
unterstützt werden, sind vor allem: Arrays (Felder), Records (Datensätze), Sets, Files
(Dateien). In Java sind es: Array und File.
Array: Ein Array (Reihung) ist eine Datenstruktur fester Größe, die aus Elementen
gleichen Typs aufgebaut ist. Diese sind über Indizes zugreifbar. Indizes stehen in
einer 1:1 Relation zu den Elementen: Jeder Indexwert identifiziert genau eine
Komponente.
Für Arrays in Java58 gilt bzgl.
-
-
Deklaration von Referenzvariablen auf Arrays, z.B.: int einFeld[]; int[]
aucheinFeld;
Speicheranforderung. Es gibt mehrere Möglichkeiten
- mit new, z.B. int [] feld = new int[20]
- Initialisierung mit Literalen, z.B.: int [] feld = {5,23,2};
- Zuweisung (ohne erneutes Bereitstellen von Speicher), z.B. int [] einAnderesFeld = feld;
Zugriff auf Feldelement, z.B feld[index]
Files (Dateien): Eine Datei ist eine geordnete endliche Folge eines gegebenen
Datentyps, wobei auf alle Elemente sequentiell zugegriffen werden kann:
58
alle Elemente sind vom gleichen Typ
vgl. 2.2.3
53
Programmieren in Java
-
Struktur zwischen den Elementenm ist linear
Das Lesen aus einer Datei und Schreiben in eine Datei erfolgt in Java59 mit den
Klassen FileReader bzw. FileWriter. FileReader ist ein Datei-Strom und eine
Subklasse von InputStreamReader. Die Angabe von Dateien kann über Objekte
der Klasse File60 oder (plattformspezifische) Dateinamen als Strings erfolgen.
Bsp.61:
import java.io.*;
public class Kopie
{
public static void main(String [] args) throws IOException
{
File eingabeDatei = new File(".","Kopie.java");
File ausgabeDatei = new File("Kopie.txt");
FileReader ein
= new FileReader(eingabeDatei);
FileWriter aus
= new FileWriter(ausgabeDatei);
int c;
while ((c = ein.read()) != -1) aus.write(c);
ein.close(); aus.close();
}
}
Beliebige Reader-Objekte können in einen BufferedReader gesteckt werden,
z.B. auch FileReader-Objekte. (anolog gilt das für Writer bzw. Byte-Ströme):
Bsp.62:
import java.io.*;
public class BufferedDemo
{
public static void main(String [] args) throws IOException
{
File eingabeDatei = new File(".","BufferedDEmo.java");
File ausgabeDatei = new File("BufferedDemo.txt");
BufferedReader ein
=
new BufferedReader(new FileReader(eingabeDatei));
BufferedWriter aus
=
new BufferedWriter(new FileWriter(ausgabeDatei));
String s;
while ((s = ein.readLine()) != null)
{
aus.write(s);
aus.write('\n');
}
ein.close(); aus.close();
}
}
Strukturierte zusammengesetze Datentypen sind in Java fast ausschließlich
Referenzdatentypen (Klassen, Interfaces, Felder (Array in Java)). Objekte von einem
Referenzdatentyp
-
müssen (fast immer) explizit mit new angefordert werden
können Parameter- und Rückgabewert bei Methoden sein
können „Trägerobjekt“ eines Methodcenaufrufs sein
59
vgl. 7.7 bzw. 1.4.4.3
vgl. 1.4.4.4
61 pr13400
62 pr13400
60
54
Programmieren in Java
Wird ein Objekt eines Referenzdatentyps als Methodenparameter verwendet, wird
eine Kopie der Referenz übergeben. In der Methode kann das Objekt im Gegendatz
zu primitiven Datentypen63) geändert werden, diese Änderung wirkt auch nach
außen.
Basis-Datenstrukturen in Java: Java bietet mit dem Java Collection Framework64
eine Bibliothek mit in der Praxis häufig verwendeten Basis-Datenstrukturen an.
1.4 Die Objektorientierung von Java
1.4.1 Grundlegende Konzepte
1.4.1.1 Zustand und Verhalten von Objekten, Klassen, Instanz- und Klassen-Variable
bzw. -Methoden
1.4.1.1.1 Die Abbildung von Zustand bzw. Verhalten in Instanzvariable bzw.
Instanzmethoden
Objekte sind die Schlüssel zum Verständnis der objektorientierten Technologie.
Objekte sind Gegenstände des täglichen Lebens: der Schreibtisch, das Skript, die
Vorlesung. All diese Objekte der realen Welt haben Zustand und Verhalten. Auch
Software-Objekte haben Zustand und Verhalten. Der Zustand wird in Variablen
festgehalten, das Verhalten der Objekte beschreiben Methoden. Die Variablen
bilden den Kern der Objekte. Methoden schirmen den Objektkern von anderen
Objekten des Programms ab (Kapselung). Software-Objekte kommunizieren und
verkehren über Nachrichen (Botschaften) miteinander. Das sendende Objekt schickt
dem Zielobjekt eine Aufforderung, eine bestimmte Methode auszuführen. Das
Zielobjekt versteht (hoffentlich) die Aufforderung und reagiert mit der zugehörigen
Methode. Die genaue formale Schreibweise solcher Botschaften in objektorientierten
Sprachen ist im Detail verschieden, jedoch wird meistens folgende Form verwendet:
Empfänger.Methodenname(Argument).
„Argument“
ist
in
dem
Botschaftsausdruck ein Übergabeparameter für die Methode.
In der realen Welt existieren häufig Objekte der gleichen Art. Sie werden über einen
Prototyp, eine Klasse, zusammengefaßt. Eine Klassendefinition in Java wird durch
das Schlüsselwort „class“ eingeleitet. Anschließend folgt innerhalb von
geschweiften
Klammern
eine
beliebige
Anzahl
an
Variablenund
Methodendefinitionen. Zum Anlegen eines Objekts einer Klasse (Instanziierung65)
muß eine Variable vom Typ der Klasse deklariert und mit Hilfe des new-Operators
ein neu erzeugtes Objekt zugewiesen werden. Das Speicher-Management in Java
erfolgt automatisch. Während das Erzeugen von Objekten immer einen expliziten
63
Wird in der Methode der Wert des Parameters mit primitivem Datentyp geändert, dann dringt dies nicht nach
außen
64 vgl. 6.2
65 Eine Instanz einer Klasse ist ein (tatsächliches) Objekt (konkrete Darstellung)
55
Programmieren in Java
Aufruf des new-Operators erfordert66, erfolgt die Rückgabe von nicht mehr
benötigtem Speicher automatisch67.
Das Schreiben eines Programms besteht damit aus Entwurf und Zusammenstellung
von Klassen. Klassenbibliotheken (Sammlung von Klassen) stellen Lösungen für
grundlegende Programmieraufgaben bereit.
Zustand, Aussehen und andere Qualitäten eines Objekts (Attribute) werden durch
Variable definiert. Da jede Instanz einer Klasse verschiedene Werte für ihre
Variablen haben kann, spricht man von Instanzvariablen. Zusätzlich gibt es noch
Klassenvariable, die die Klasse selbst und alle ihre Instanzen betreffen. Werte von
Klassenvariablen werden direkt in der Klasse gespeichert. Der Zustand wird in
Variablen festgehalten und zeigt den momentanen Stand der Objektstruktur an, d.h.
die in den einzelnen Bestandteilen des Objekts enthaltenen Informationen und
Daten. Abhängig vom Detaillierungsgrad kann die Notation für eine Variable den
Namen, den Datentyp und den voreingestellten Wert zeigen:
Sichtbarkeit Typ Name = voreingestellter_Wert;
Sichtbarkeit: öffentlich (public), geschützt (protected) oder privat (private)
Typ: Datentyp
Name: eine nach bestimmten Regeln68 gebildete Zeichenkette
Nach der Initialisierung haben alle Variablen des Objekts zunächst Standardwerte
(voreingestellte Werte). Der Zugriff auf sie erfolgt mit Hilfe der Punktnotation:
Objekt.Variable.
Zur Bezugnahme auf das aktuelle Objekt dient das Schlüsselwort this. Es kann an
jeder beliebigen Stelle angegeben werden, an der das Objekt erscheinen kann, z.B.
in einer Punktnotation zum Verweis auf Instanzvariablen des Objekts oder als
Argument für eine Methode oder als Ausgabewert der aktuellen Methoden. In vielen
Fällen kann das Schlüsselwort this entfallen. Das hängt davon ab, ob es Variablen
mit gleichem Namen im lokalen Bereich gibt.
Zur Definition des Verhaltens von Objekten dienen Methoden. Methoden sind
Funktionen, die innerhalb von Klassen definiert werden und auf Klasseninstanzen
angewandt werden. Methoden wirken sich aber nicht nur auf ein Objekt aus. Objekte
kommunizieren auch miteinander durch Methoden. Eine Klasse oder ein Objekt kann
Methoden einer anderen Klasse oder eines anderen Objekts aufrufen, um
Änderungen in der Umgebung mitzuteilen oder ein Objekt aufzufordern, seinen
Zustand zu ändern. Instanzmethoden (Operationen, Services) werden auf eine
Instanz angewandt, Klassenmethoden beziehen sich auf eine Klasse.
Klassenmethoden können nur mit Klassenvariablen arbeiten.
Die Beschreibung der Operationen (Nachrichten, Methoden) erfolgt nach dem
folgenden Schema:
Sichbarkeit Rückgabetypausdruck Name(Parameterliste)
Sichtbarkeit: öffentlich (public), geschützt (protected), privat (private)69
Rückgabetypausdruck: Jede Methode ist typisiert. Der Typ einer Methode bestimt den Typ des
Rückgabewerts. Dieser kann von einem beliebigen primitiven Typ70, einem Objekttyp oder vom Typ
void sein. Methoden vom Typ void haben keinen Rückgabewert und dürfen nicht in Ausdrücken
66
Ausnahmen: String-, Array-Literale
Ein Garbage-Collector (niedrigpriorisierte Hintergrundprozeß) sucht in regelmäßigen Abständen nach nicht
mehr referenzierten Objekten und gibt den durch sie belegten Speicher an das Laufzeitsystem zurück
68 vgl. 2.1.2 Bezeichner und Namenskonventionen
69 vgl. 2.6.2
70 vgl. 2.2
67
56
Programmieren in Java
verwendet werden. Hat eine Methode einen Rückgabewert, dann kann sie mit der „return“Anweisung71 einen Wert an den Aufrufer zurückgeben.
Parameterliste enthält optional Argumente und hat folgende Struktur: Datentyp
variablenname, ..... Die Anzahl der Parameter ist beliebig und kann Null sein.
In Java wird jede selbstdefinierte Klasse mit Hilfe des Operators new instanziert. Mit
Ausnahme von Zeichenketten (Strings) und Datenfeldern (Arrays), bei denen der
Compiler auch Literale zur Objekterzeugung bereitstellt, gilt dies für alle
vordefinierten Klassen der Java-Bibliothek.
Der Aufruf einer Methode erfolgt ähnlich der Verwendung einer Instanzvariablen in
„Punktnotation“. Zur Unterscheidung von einem Variablenzugriff müssen zusätzlich
die Parameter in Klammern angegeben werden, selbst wenn die Parameter-Liste leer
ist.
Ein praktisches Beispiel:
1. Erstellen der Klasse Rechentafel zum Rechnen mit ganzen Zahlen
Die grundlegende Klassendefinition ist: class Rechentafel{ }
Das ist eine Java-Klasse (in einfachster Form), selbstverständlich passiert hier noch
nicht viel. Damit etwas Aktion stattfinden kann, müssen Zustand und Verhalten
diverser Rechentafel-Objekte festgehalten werden können.
Der Zustand wird in Variablen gespeichert. In Rechentafel-Objekten gibt es zwei
Operanden zur Aufnahme ganzzahliger Werte für die Berechnung und eine Variable
zur Aufnahme des Resultats nach der Berechnung. Operanden und Resultat sind in
der Klasse Rechentafel Instanzvariable, da jedes Rechentafel-Objekt dafür
verschiedene Werte haben kann. Folgende Instanzvariable (mit Datentyp int)
werden in den durch „{ }“markierten Klassenkörper eingetragen:
// Instanzvariable
int ersterOperand = 0;
int zweiterOperand = 0;
Da keine spezifische Angabe zur Sichtbarkeit vorliegen, haben Objekte auf die
Variablen Zugriff, die aus Klassen innerhalb des Klassenverzeichnisses gebildet
werden.
Rechentafel-Objekte sollten „Rechnen“ können. Dieses Verhalten bedeutet: Die
vorliegenden Instanzvariablen sind durch Instanzmethoden zu ergänzen, z.B.:
// Operationen
int addiere()
{
return ersterOperand + zweiterOperand;
}
int subtrahiere()
{
return ersterOperand - zweiterOperand;
}
int multipliziere()
{
return ersterOperand * zweiterOperand;
}
int dividiere()
{
// ganzzahlige Division
return ersterOperand / zweiterOperand;
71
vgl. 2.6.4
57
Programmieren in Java
}
Die Klasse ist damit für Rechentafel-Objekte vollständig bereitgestellt. Allerdings wird
erst nach dem Hinzufügen der main()-Methode aus dieser Klasse eine JavaAnwendung1:
2. Die Klasse Rechentafeltest zum Überprüfen der Klasse Rechentafel
Die Klasse Rechentafeltest ist durch die Anwendung der Klasse Rechentafel
bestimmt. Sie enthält die main()-Methode. Im Mittelpunkt der Anwendung steht die
Anweisung:
Rechentafel einRechenobjekt = new Rechentafel();
Sie erzeugt eine Instanz der Klasse Rechentafel und speichert eine Referenz
darauf in der Variablen „einRechenobjekt“. In jeder objektorientierten
Programmiersprache lassen sich spezielle Methoden definieren, die bei der
Initialisierung eines Objekts aufgerufen werden. In Java werden Konstruktoren als
Methoden ohne Rückgabewert definiert, die den Namen der Klasse erhalten, zu der
sie gehören. Falls eine Klasse keinen expliziten Konstruktor besitzt, wird ein
parameterloser „default“-Konstruktor aufgerufen (, der hier zusammen mit dem
Operator new verwendet wird).
import java.lang.*;
class Rechentafeltest extends Object
{
// Zur Ausführung mit dem Java-Interpreter wird eine main()-Methode
// benoetigt
public static void main(String argv[])
{
// Erzeugen einer Instanz der Klasse Rechentafel
Rechentafel einRechenobjekt = new Rechentafel();
// Setzen der Instanzvariablen
einRechenobjekt.ersterOperand = 3;
einRechenobjekt.zweiterOperand = 2;
// Test mit einem Rechenobjekt der Klasse Rechentafel
System.out.println("Test mit einem Rechentafel-Objekt");
// Aufruf der Methoden und Ausgabe des jeweiligen Resultats
System.out.print("Addition ");
System.out.println("von " + einRechenobjekt.ersterOperand + " und "
+ einRechenobjekt.zweiterOperand + " ist "
+ einRechenobjekt.addiere());
System.out.print("Subtraktion ");
System.out.println("von " + einRechenobjekt.ersterOperand + " und "
+ einRechenobjekt.zweiterOperand + " ist "
+ einRechenobjekt.subtrahiere());
System.out.print("Multiplikation ");
System.out.println("von " + einRechenobjekt.ersterOperand + " und "
+ einRechenobjekt.zweiterOperand + " ist "
+ einRechenobjekt.multipliziere());
System.out.print("Division ");
System.out.println("von " + einRechenobjekt.ersterOperand + " und "
+ einRechenobjekt.zweiterOperand + " ist "
+ einRechenobjekt.dividiere());
}
}
72
vgl. pr14101
58
Programmieren in Java
1.4.1.1.2 Einfache Typen74, Referenztypen, Automatisches Boxen
Einfache Typen und Wrapper-Klassen
Jedes Java-Programm besteht aus einer Sammlung von Klassen. Der vollständige
Code von Java wird in Klassen eingeteilt. Es gibt davon nur eine Ausnahme:
Boolesche Operatoren, Zahlen und andere einfache Typen75 sind in Java erst einmal
keine Objekte. Java hat für alle einfachen Typen sog. Wrapper-Klassen76
implementiert. Ein Wrapper-Klasse ist eine spezielle Klasse, die eine
Objektschnittstelle für die in Java verschiedenen primitiven Typen darstellt. Über
Wrapper-Objekte können alle einfachen Typen wie Klassen behandelt werden.
Den primitiven Typen sind „Wrapper“-Klassen zugeordnet, die ein nicht primitives
Objekt auf dem „Heap“ zur Darstellung des primitiven Typs erzeugen, z.B.:
char zeichen = 'x';
Character zeichenDarstellung = new Character(zeichen)
bzw.
Character zeichenDarstellung = new Character('x');
PrimitivTyp
boolean
byte
short
int
long
float
double
void77
Wrapper-Typ
Boolean
Byte
Short
Integer
Long
Float
Double
Void78
Wrapper-Klassen erlauben es, Daten eines primitiven Typs als Objekt zu speichern.
Objekte dieser Klassen sind nicht modifizierbar. Ist einmal ein Integer-Objekt mit
einer bestimmten Zahl erzeugt, lässt sich die in diesem Objekt erzeugte Zahl nicht
mehr modifizieren.
Alle Wrapper-Typen bieten bestimmte Service-Methoden an.
Wichtige und gebrächliche Servive-Methoden einschl. weiterer Merkmale:
-
-
Alle Wrapper-Typen sind final deklariert und können nicht weiter spezialisiert werden.
...Value(), z.B. booleanValue(), byteValue(), shortValue(), intValue(),
longValue(), floatValue(), doubleValue(). All diese Methoden geben den intern
gekapselten Wert unverändert zurück. Darüber hinaus existieren Methoden zur Ausgabe
eines numerischen Wrapper-Typs in einen beliebigen numerischen Primitivtyp79.
Konstruktoren erlauben die Erzeugung eines Wrappertyps aus einem Ausdruck vom Typ des
gekapselten Typs oder aus einem String.
compareTo() vergleicht den gekapselten zweier Wrappertyp-Objekte.
74
vgl. 2.2.1
vgl. 2.2.1
76 vgl. 1.4.2.7
77 Dieser Typ ist nicht als Datentyp für Variable und Attribute verfügbar, kann nur als Rückgabetyp von
Operationen spezifiziert werden.
78 kann nicht instanziiert werden
79 ersetzen die explizite Typumwandlung
75
59
Programmieren in Java
-
parse...() Methoden, z.B. parseByte(String s), parseByte(String s, int
radix), parseInt(String s), parseInt(String s, int radix), erzeugen aus
Zeichenketten den jeweiligen Primitivtyp.
valueOf(). Für alle Wrapper-Typen integraler Primitivtypen (byte, char, long, int, short) ist
mit der valueOf()-Methode eine (Factory-) Methode zur Erzeugung eines neuen WrapperObjekts aus einer Zeichenkette definiert.
die üblichen arithmetischen Operationen stehen zur Verknüpfung von Wrapperobjekten nicht
zur Verfügung (kein Operator overloading).
Referenztypen
Neben den primitiven Typen gibt es die Referenztypen. Dazu gehören: Objekte der
benutzerdefinierten und aus vom System bereitgestellten Klassen, der Klassen
String und Array (Datenfeld). Weiterhin gibt es die vordefinierte Konstante
„null“, die eine leere Referenz bezeichnet.
„String“ und „Array“ weisen einige Besonderheiten aus:
- Für Strings und Arrays kennt der Compiler Literale, die einen expliziten Aufruf des Operator new
überflüssig machen
- Arrays sind klassenlose Objekte. Sie können ausschließlich vom Compiler erzeugt werden, besitzen
aber keine explizite Klassendefinition. Sie werden dennoch vom Laufzeitsystem wie normale
Objeklte behandelt.
- Die Klasse String ist zwar im JDK vorhanden. Der Compiler hat aber Kenntnis über den inneren
Aufbau von Strings und generiert bei Anzeigeoperationen Code, der auf Methoden der Klassen
String und StringBuffer zugreift.
Automatisches Boxen
Boxing. Beim Speichern eines Datums von einem primitiven Typ in einer Variablen,
die nur Objekte speichern kann, ist kapseln in einem Objekt der entsprechenden
(Wrapper-) Klasse nötig (boxing). Es kam zu Konstruktoraufrufen dieser Klassen.
Unboxing. Sollte später mit Operatoren auf den Zahlen, die durch gekapselte Objekte
ausgedrückt wurden, gerechnet werden, so war der primitive Wert mit einem
Methodenaufruf aus dem Objekt wieder zu extrahieren (unboxing). Es kam zu
Aufrufen wie intValue() im Code80.
Bsp.: Manuelles Verpacken und Auspacken primitiver Daten
public class ManuellesBoxen
{
public static void main(String [] args)
{
int i1 = 42;
Object o = new Integer(i1);
System.out.println(o);
Integer i2 = new Integer(17);
Integer i3 = new Integer(4);
int i4 = 21;
System.out.println((i2.intValue() + i3.intValue()) * 14);
}
}
Mit Java 1.5 können die primitiven Typen mit ihren entsprechenden KLassen
synonym verwendet werden. Nach außen hin werden die primitiven Typen auch
Objekttypen. Der Übersetzer nimmt die notwendigen boxing- und unboxingOperationen vor.
80
vgl. 1.4.2.7
60
Programmieren in Java
Bsp.81: Automatic Boxing
public class AutomatischesBoxen
{
public static void main(String [] args)
{
int i1 = 42;
Object o = i1;
System.out.println(o);
Integer i2 = 17;
Integer i3 = 4;
int i4 = 21;
System.out.println((i2 + i3) * 14);
}
}
1.4.1.1.3 Konstruktoren
Eine „Constructor“-Methode bestimmt, wie ein Objekt initialisiert wird. Konstruktoren
haben immer den gleichen Namen wie die Klasse und besitzen keine „return“Anweisung. Java erledigt beim Aufruf eines Konstruktors folgende Aufgaben:
- Speicherzuweisung für das Objekt
- Initialisieung der Instanzvariablen des Objekts auf ihre Anfangswerte oder einen Default-Wert (0 bei
Zahlen, „null“ bei Objekten, „false“ bei booleschen Operatoren.
- Aufruf der Konstruktor-Methode der Klasse
Gewöhnlich stellt man „explizit“ einen „default“-Konstruktor zur Verfügung. Dieser
parameterlose Konstruktor überlagert den implizit bereitgestellten „default“Konstruktor. Er wird dann bei allen parameterlosen Instanzierungen verwendet.
Konstruktoren können aber auch – wie normale Dateien – Parameter übergeben
bekommen, z.B.82:
public Rechentafel(int ersterOperand, int zweiterOperand)
{
super(); // Aufruf des Default-Konstruktors der Superklasse
this.ersterOperand = ersterOperand;
this.zweiterOperand = zweiterOperand;
}
super() bestimmt einen Aufruf des „default“-Konstruktors der eindeutig
bestimmten „Superklasse“. „super(...)“ darf nur als erste Anweisung eines
Konstruktors auftreten. Generell bezeichnet das reservierte Wort „super“, die nach
„extends“ benannte Superklasse.
Häufig sind die Parameter Anfangswerte für Instanzvariablen und haben oft den
gleichen Namen wie die entsprechenden Instanzvariablen. In diesen Fällen löst die
Verwendung von bsp. this.ersterOperand
= ersterOperand; derartige
Namenskonflikte auf.
Bei „this“ handelt es sich um einen Zeiger, der beim Anlegen eines Objekts
automatisch generiert wird. „this“ ist eine Referenzvariable, die auf das aktuelle
Objekt zeigt und zum Ansprechen der eigenen Methoden und Instanzvariablen dient.
Der „this“-Zeiger ist auch explizit verfügbar und kann wie eine ganz normale
81
82
vgl. pr14112
pr14102
61
Programmieren in Java
Objektvariable verwendet werden. Er wird als versteckter Parameter an jede nicht
statische Methode übergeben.
Konstruktoren können in Java verkettet aufgerufen werden, d.h. sie können sich
gegenseitig aufrufen. Der aufrufende Konstruktor wird dabei als normale Methode
angesehen, die über this einen weiteren Konstruktor der aktuellen Klasse aufrufen
kann, z.B.:
public Rechentafel()
{
this(0,1);
}
1.4.1.1.4 Zugriffsrechte auf Klassen, Variable und Methoden
Es gibt in Java insgesamt 4 Zugriffsrechte:
private
Ohne Schlüsselwort
protected
public
Zugriff nur innerhalb einer Klasse
Zugriff innerhalb eines Pakets
Zugriff innerhalb eines Pakets oder von Subklassen in einem anderen
Paket
Zugriff von überall
Mit Voranstellen des Schlüsselworts „public“ können alle Klassen, Variablen /
Konstanten und Methoden für einen beliebigen Zugriff eingerichtet werden. Eine
derartige Möglichkeit, die in etwa der Zugriffsmöglichkeit globaler Variablen in
konventionellen Programmiersprachen entspricht, ist insbesondere bei komplexen
Programm-Systemen gefährlich. Es ist nicht sichergestellt, daß zu jedem Zeitpunkt
die Werte der Instanzvariablen von Objekten bestimmte Bedingungen erfüllen.
Möglich ist bspw. die Anweisung
einRechenobjekt.zweiterOperand = 0;
in der Klasse „Rechentafeltest“. Mit Sicherheit führt diese Anweisung bei der
Ausführung einer Division auf einen Divisionsfehler.
Abhilfe verspricht hier das Prinzip der Datenkapselung (data hiding, data
encapsulation). Instanzvariable werden als „private“ erklärt, d.h.: Zugriff auf diese
Instanzvariable nur innerhalb der Klasse. Von außerhalb kann nur indirekt über das
Aufrufen von Methoden, die als „public“ erklärt sind, auf die Instanzvariablen
zugegriffen werden. Deshalb sollte auch prinzipiell für jede Instanzvariable eine
entsprechende „get“- und eine „set“-Methode („hole“ bzw. „setze“) zur Verfügung
gestellt werden, die jeweils „public“ erklärt werden.
Bsp.: Die Klasse „Rechentafel“ nimmt dann folgenden Gestalt an:
class Rechentafel extends Object
{
// Instanzvariable
private int ersterOperand = 0;
private int zweiterOperand = 0;
// Konstruktoren
public Rechentafel()
{
this(0,1);
}
public Rechentafel(int ersterOperand, int zweiterOperand)
62
Programmieren in Java
{
super();
this.ersterOperand = ersterOperand;
this.zweiterOperand = zweiterOperand;
}
// Operationen
public int holersterOperand()
{
return ersterOperand;
}
public void setzersterOperand(int ersterOperand)
{
this.ersterOperand = ersterOperand;
}
public int holzweiterOperand()
{
return zweiterOperand;
}
public void setzweiterOperand(int zweiterOperand)
{
this.zweiterOperand = zweiterOperand;
}
public int addiere()
{
return ersterOperand + zweiterOperand;
}
public int subtrahiere()
{
return ersterOperand - zweiterOperand;
}
public int multipliziere()
{
return ersterOperand * zweiterOperand;
}
public int dividiere()
{
// ganzzahlige Division
if (zweiterOperand == 0)
{
System.out.println("Fehler: Division durch Null");
System.out.println("Divisor wird auf 1 gesetzt!");
zweiterOperand = 1;
}
return ersterOperand / zweiterOperand;
}
}
Das Beispiel zeigt folgende Empfehlungen für die Vergabemöglichkeit von Zugriffsrechten:
Klassen
Instanzvariable
Instanzkonstanten
Instanzmethoden
public
private
public
public, falls ein Zugriff von außen erforderlich und sinnvoll ist.
private, falls es sich um klaseninterne Hilfsmethoden handelt.
Analoge Überlegungen gelten auch für Klassenvariable und -methoden.
„Freundliche“ Klassen und „freundliche“ Methoden
„Rechentafel“ ist eine „freundliche“ Klasse. Der voreingestellte „Defaultstatus“
einer Klasse ist immer „freundlich“ und wird dann verwendet, wenn keine Angaben
63
Programmieren in Java
über die Sichtbarkeit (Spezifizierer, Modifizierer) am Beginn der Klassendefinition
vorliegen.
Die freundliche Grundeinstellung aller Klassen bedeutet: Diese Klasse kann von
anderen Klassen nur innerhalb desselben Pakets benutzt werden. Das PaketKonzept von Java faßt mehrere Klassen zu einem Paket über die Anweisung
„package“ zusammen. Durch die Anweisung „import“ werden einzelne Pakete
dann in einem Programm verfügbar gemacht. Klassen, die ohne „package“Anweisung definiert werden, werden vom Compiler in ein Standardpaket gestellt. Die
„.java“- und „.class“-Dateien dieses Pakets befinden sich im aktuellen
Verzeichnis oder im darunterliegenden Verzeichnis.
Mit dem Voranstellen von „public“ vor die Klassendeklaration wird eine Klasse als
„öffentlich“ deklariert. Dies bedeutet: Alle Objekte haben Zugriff auf „public“Klassen (nicht nur die des eigenen Pakets).
Der voreingestellte Defaultstaus einer Methode ist immer freundlich und wird immer
dann verwendet, wenn keine explizite Angabe zur Sichtbarkeit am Anfang der
Methodendeklaration vorliegt. Die freundliche Grundeinstellung aller Methoden
bedeutet: Die Methoden können sowohl innerhalb der Klasse als auch innerhalb des
zugehörigen Pakets benutzt werden.
1.4.1.1.5 Klassenvariable und Klassenmethoden
Die Klasse „Rechentafel"83 wurde zusätzlich erweitert um
// Klassenvariable
static int anzRechenobjekte = 0;
Das reservierte Wort static macht Variable und Methoden (wie bspw. main()) zu
Klassenvariablen bzw. Klassenmethoden.
Klassenvariable werden in der Klasse definiert und gespeichert. Deshalb wirken sich
ihre Werte auf die Klasse und all ihre Instanzen aus. Jede Instanz hat Zugang zu der
Klassenvariablen, jedoch gibt es für alle Instanzen dieser Variablen nur einen Wert.
Durch Änderung des Werts ändern sich die Werte aller Instanzen der betreffenden
Klasse.
Die folgende Erweiterung des Konstruktors der Klasse „Rechentafel“ berücksichtigt
das spezielle Verhalten von Klassenvariablen:
public Rechentafel(int ersterOperand, int zweiterOperand)
{
super();
this.ersterOperand = ersterOperand;
this.zweiterOperand = zweiterOperand;
nummer = ++anzRechenobjekte;
}
„nummer“ ist eine Instanzvariable, „anzRechenObjekte“ ist die Klassenvariable.
Das reservierte Wort final macht Variablen zu Größen, denen nur einmal bei der
Deklaration ein Wert zugewiesen werden kann, d.h. sie werden dadurch zu
Konstanten, z.B.: public static final double PI = 3.1415926535897932846;
83
pr14103
64
Programmieren in Java
Klassenmethoden wirken sich wie Klassenvariable auf die ganze Klasse, nicht auf
einzelne Instanzen aus. Klassenmethoden sind nützlich zum Zusammenfassen
allgemeiner Methoden an einer Stelle der Klasse. So umfaßt die Math-Klasse
zahlreiche mathematische Funktionen in der Form von Klassenmethoden. Es gibt
keine Instanzen der Klasse Math.
Auch rekurvive Programme benutzen Klassenmethoden, z.B.:
// Berechnung der Fakultaet
public static long fakultaet(int n)
{
if (n < 0)
{ return -1; }
else if (n == 0)
{ return 1; }
else
{ return n * fakultaet(n-1); }
}
Der Aufruf einer Klassenmethode kann im Rahmen der Punktnotation aber auch
direkt erfolgen, z.B.:
long resultat = fakultaet(i);
bzw.
long resultat = Rechentafel.fakultaet(i);
unter der Voraussetzung: Die Klassenmethode „fakultaet“ befindet sich in der Klasse
„Rechentafel“.
1.4.1.1.6 Lokale Variable und Konstanten
Lokale Variable werden innerhalb von Methodendefinitionen deklariert und können
nur dort verwendet werden. Es gibt auch auf Blöcke beschränkte lokale Variablen.
Die Deklaration einer lokalen Variablen gilt in Java als ausführbare Anweisung. Sie
darf überall dort erfolgen, wo eine Anweisung verwendet werden darf. Die
Sichtbarkeit einer lokalen Variablen erstreckt sich von der Deklaration bis zum Ende
des umschließenden Blocks.
Lokale Variablen existieren nur solange im Speicher, wie die Methode oder der Block
existiert. Lokale Variable müssen unbedingt ein Wert zugewiesen bekommen, bevor
sie benutzt werden können. Instanz- und Klassenvariable haben einen
typspezifischen Defaultwert. Auf lokale Variable kann direkt und nicht über die
Punktnotation zugegriffen werden. Lokale Variable können auch nicht als
Konstanten84 gesetzt werden.
Beim Bezug einer Variablen in einer Methodendefinition sucht Java zuerst eine
Definition dieser Variablen im aktuellen Bereich, dann durchsucht es die äußeren
Bereiche bis zur Definition der aktuellen Methode. Ist die gesuchte Größe keine
lokale Variable, sucht Java nach einer Definition dieser Variablen als Instanzvariable
in der aktuellen Klasse und zum Schluß in der Superklasse.
Konstanten sind Speicherbereiche mit Werten, die sich nie ändern. In Java können
solche Konstanten ausschließlich für Instanz- und Klassenvariable erstellt werden.
Konstante bestehen aus einer Variablendeklaration, der das Schlüsselwort final
vorangestellt ist, und ein Anfangswert zugewiesen ist, z.B.: final int LINKS =
0;
84
Das bedeutet: das Schlüsselwort final ist bei lokalen Variablen nicht erlaubt
65
Programmieren in Java
1.4.1.1.7 Überladen von Methoden
In Java ist es erlaubt, Methoden zu überladen, d.h. innerhalb einer Klasse zwei
unterschiedliche Methoden mit denselben Namen zu definieren. Der Compiler
unterscheidet die verschiedenen Varianten anhand Anzahl und Typisierung der
Parameter. Es ist nicht erlaubt, zwei Methoden mit exakt demselben Namen und
identischer Parameterliste zu definieren. Es werden auch zwei Methoden, die sich
nur durch den Typ ihres Rückgabewerts unterscheiden als gleich angesehen.
Der Compiler kann die Namen in allen drei Fällen unterscheiden, denn er arbeitet mit
der Signatur der Methode. Darunter versteht man ihren internen Namen. Dieser setzt
sich aus dem nach außen sichtbaren Namen und zusätzlich kodierter Information
über Reihenfolge und die Typen der formalen Parameter zusammen.
Überladen kann bedeuten, daß bei Namensgleichheit von Methoden in „Superklasse“
und abgeleiteter Klasse die Methode der abgeleitetem Klasse, die der Superklasse
überdeckt. Es wird aber vorausgesetzt, daß sich die Parameter signifikant
unterscheiden, sonst handelt es sich bei der Konstellation Superklasse – Subklasse
um den Vorgang des Überschreibens. Soll die Originalmethode aufgerufen werden,
dann wird das Schlüsselwort „super“ benutzt. Damit wird der Methodenaufruf in der
Hierarchie nach oben weitergegeben.
Überschreiben einer Oberklassen-Methode: Zum Überschreiben einer Methode wird
eine Methode erstellt, die den gleichen Namen, Ausgabetyp und die gleiche
Parameterliste wie eine Methode der Superklasse besitzt. Da Java die erste
Methodendefinition ausführt, die es findet und die in Namen, Ausgabetyp und
Parameterliste übereinstimmt, wird die ursprüngliche Methodendefinition dadurch
verborgen. Klassen-Methoden werden nicht überschrieben, sondern überdeckt, da
sie (z. B. Klasse.methode(), Superklasse.methode()) in jedem Fall verschiedene
Namen haben.
Konstruktor-Methoden können „technisch“ nicht überschrieben werden. Da sie den
gleichen Namen wie die aktuelle Klasse haben, werden Konstruktoren nicht vererbt,
sondern immer neu erstellt. Wird ein Konstruktor einer bestimmten Klasse
aufgerufen, wird gleichzeitig auch der Konstruktor aller Superklassen aktiviert, so daß
alle Teile einer Klasse initialisiert werden. Ein spezielles Überschreiben kann über
super(arg1, arg2, ...) ermöglicht werden.
Die Methode toString() ist eine in Java häufig verwendete Methode.
„toString()“ wird als weitere Instanzmethode der Klasse Rechentafel definiert und
überschreibt die Methode gleichen Namens, die in der Oberklasse Object definiert
ist, z.B.85:
public String toString()
{
return " Rechenobjekt# " + nummer + " Erster Operand: "
+ ersterOperand + " Zweiter Operand: " + zweiterOperand;
}
}
85
pr14103
66
Programmieren in Java
1.4.1.2 Superklassen und Subklassen, Vererbung und Klassenhierarchie
Definitionen
Klassen können sich auf andere Klassen beziehen. Ausgangspunkt ist eine
Superklasse, von der Subklassen (Unter-) abgeleitet sein können. Jede Subklasse
erbt den Zustand (über die Variablen-Deklarationen) und das Verhalten der
Superklasse. Darüber hinaus können Subklassen eigene Variable und Methoden
hinzufügen. Superklassen, Subklassen bilden eine mehrstufige Hierarchie. In Java
sind alle Klassen Ableitungen einer einzigen obersten Klasse – der Klasse Object.
Sie stellt die allgemeinste Klasse in der Hierarchie dar und legt die Verhaltensweisen
und Attribute, die an alle Klassen in der Java-Klassenbibliothek vererbt werden, fest.
Vererbung bedeutet, daß alle Klassen in eine strikte Hierarchie eingeordnet sind und
etwas von übergeordneten Klassen erben. Was kann von übergeordneten Klassen
vererbt werden? Beim Erstellen einer neuen Instanz erhält man ein Exemplar jeder
Variablen, die in der aktuellen Klasse definiert ist. Zusätzlich wird ein Exemplar für
jede Variable bereitgestellt, die sich in den Superklassen der aktuellen Klasse
befindet. Bei der Methodenauswahl haben neue Objekte Zugang zu allen
Methodennamen ihrer Klasse und deren Superklasse. Methodennamen werden
dynamisch beim Aufruf einer Methode gewählt. Das bedeutet: Java prüft zuerst die
Klasse des Objekts auf Definition der betreffenden Methode. Ist sie nicht in der
Klasse des Objekts definiert, sucht Java in der Superklasse dieser Klasse usw.
aufwärts in der Hierarchie bis die Definition der Methode gefunden wird.
Ist in einer Subklasse eine Methode mit gleichem Namen, gleicher Anzahl und
gleichem Typ der Argumente wie in der Superklasse definiert, dann wird die
Methodendefinition, die zuerst (von unten nach oben in der Hierarchie) gefunden
wird, ausgeführt. Methoden der abgeleiteten Klasse überdecken die Methoden der
Superklasse (Overriding beim Überschreiben / Überdefinieren).
Zum Ableiten einer Klasse aus einer bestehenden Klasse ist im Kopf der Klasse mit
Hilfe des Schlüsselworts „extends“ ein Verweis auf die Basisklasse anzugeben.
Wird der Name einer Klasse nach dem Schlüsselwort extends angegeben, dann
wird damit diese Klasse als Superklasse spezifiziert, auf der eine neue Klasse
aufbaut. Durch Erweiterung der Superklasse entsteht eine neue Kopie dieser Klasse,
an der Veränderungen möglich sind. Die neue Klasse erbt alle Daten und Methoden,
die in der Originalklasse definiert wurden. Durch Hinzufügen neuer Elemente oder
Überladen der vorhandenen kann die Funktionalität der abgeleiteten Klasse erweitert
werden.
Die Vererbung einer Klasse kann beliebig tief geschachtelt werden. Eine abgeleitete
Klasse erbt dabei jeweils die Eigenschaften der unmittelbaren „Superklasse“, die
ihrerseits die Eigenschaften ihrer unmittelbaren „Superklasse“ erbt.
Superklassen können abstrakte Klassen mit generischem Verhalten sein. Von einer
abstrakten Klasse wird nie eine direkte Instanz benötigt. Sie dient nur zu
Verweiszwecken. Abstrakte Klassen dürfen keine Implementierung einer Methode
enthalten und sind damit auch nicht instanziierbar. Java charakterisiert abstrakte
Klassen mit dem Schlüsselwort abstract. Abstrakte Klassen werden zum Aufbau
einer Klassenhierarchie verwendet, z.B. für die Zusammenfassung von zwei oder
mehr Klassen.
Auch eine abstrakte Methode ist zunächst einmal durch das reservierte Wort
"abstract" erklärt. Eine abstrakte Methode besteht nur aus dem Methodenkopf,
67
Programmieren in Java
anstelle des Methodenrumpfs (der Methodendefinition) steht nur das "Semikolon",
z.B. public abstract String toString();.
Enthält eine Klasse mindestens eine abstrakte Methode, so wird automatisch die
gesamte Klasse zu einer abstrakten Klasse. Abstrakte Klassen enthalten keine
Konstruktoren. Sie können zwar Konstruktoren aufnehmen, allerdings führt jeder
explizite Versuch zur Erzeugung eines Objekts einer abstrakten Klasse zu einer
Fehlermeldung. Abstrakte Methoden stellen eine Schnittstellenbeschreibung dar, die
der Programmierer einer Subklasse zu definieren hat. Ein konkrete Subklasse einer
abstrakten Klasse muß alle abstrakten Methoden der Superklasse(n)
implementieren.
Beispiel: Eine Klassenhierarchie für "geometrische" Objekte86
Das folgende Klassendiagramm zeigt die hierarchische Struktur von Klassen, die
"geometrische Objekte" beschreiben. All diesen geometrischen Objekten ist
gemeinsam: Sie werden durch einen Bezugspunkt (x,y) fixiert. DieGemeinsamkeiten
sind in der abstrakten Klasse "Geomobjekt" zusammengefaßt.
86
vgl. pr14131
68
Programmieren in Java
GeomObjekt
private double x;
private double y;
public double holeX();
public void setzeX(double x);
public double holeY();
public void setzeY(double y);
Kreis
Rechteck
private double r;
private double breite;
private double hoehe;
public double holeR();
public void setzeR(double r);
public double umfang();
public double flaeche();
public String toString();
public double holeBreite();
public void setzeBreite(double breite)
public double holeHoehe(double hoehe);
public void setzeHoehe(double hoehe);
public double umfang();
public double flaeche();
public String toString();
Ausgangspunkt ist die abstrakte Klasse "GeomObjekt". Dort ist der allen Objekten
zugeordnete Bezugspunkt fixiert. Subklassen (z.B. Kreis und Rechteck) erben diesen
Bezugspunkt und die zum Zugriff auf ihn bereitgestellte Methoden. Außerdem führen
die Subklassen die nötigen Spezialisierungen auf das jeweilige geometrische Objekt
durch.
import java.lang.*;
public abstract class GeomObjekt extends Object
{
// Instanzvariable
private double x, y;
// Bezugspunkt geom. Objekte
// Instanzmethoden
public double holeX()
{
return x;
}
public void setzeX(double x)
{
this.x = x;
}
public double holeY()
{
return y;
}
public void setzeY(double y)
{
this.y = y;
}
// Abstrakte Methoden
public abstract String toString();
69
Programmieren in Java
public abstract double umfang();
public abstract double flaeche();
}
import java.lang.*;
public class Kreis extends GeomObjekt
{
// Instanzvariable
private double r;
// Radius
// Konstruktoren
public Kreis()
{
this(0.0,0.0,1.0);
}
public Kreis(double r)
{
this(0.0,0.0,r);
}
public Kreis(double x, double y, double r)
{
super();
super.setzeX(x);
super.setzeY(y);
this.setzeR(r);
}
// Instanzmethoden
public double holeR()
{
return r;
}
public void setzeR(double r)
{
this.r = (r >= 0.0 ? r : -r);
}
public double umfang()
{
double u = 2 * r * Math.PI;
return u;
}
public double flaeche()
{
return r * r * Math.PI;
}
public String toString()
{
return "Kreis: (" + holeX() + ", " + holeY() +
") " + "r = " + r;
}
}
import java.lang.*;
public class Rechteck extends GeomObjekt
{
// Instanzvariable
private double breite, hoehe;
// Konstruktoren
public Rechteck()
{
this(0.0,0.0,1.0,1.0);
}
public Rechteck(double breite, double hoehe)
{
this(0.0,0.0,breite,hoehe);
}
70
Programmieren in Java
public Rechteck(double x, double y, double breite, double hoehe)
{
super();
super.setzeX(x);
super.setzeY(y);
this.setzeBreite(breite);
this.setzeHoehe(hoehe);
}
// Instanzmethoden
public double holeBreite()
{
return breite;
}
public void setzeBreite(double breite)
{
this.breite = ( breite >= 0 ? breite : -breite);
}
public double holeHoehe()
{
return hoehe;
}
public void setzeHoehe(double hoehe)
{
this.hoehe = ( hoehe >= 0 ? hoehe : -hoehe);
}
public double umfang()
{
return 2 * breite + 2 * hoehe;
}
public double flaeche()
{
return breite * hoehe;
}
public String toString()
{
return "Rechteck: (" + holeX() + ", " + holeY()
+ ") " + "breite = " + breite +
" hoehe = " + hoehe;
}
}
Die Klasse "Test"
Funktionsfähigkeit:
überprüft
die
vorliegende
import java.lang.*;
public class Test extends Object
{
public static void main(String args[])
{
// Erzeuge einen Kreis
Kreis k1 = new Kreis();
System.out.println(k1.toString());
double umfang;
umfang = k1.umfang();
System.out.println("Umfang = " + umfang);
System.out.println("Flaeche = " + k1.flaeche());
// Erzeuge einen anderen Kreises
Kreis k2 = new Kreis(1.0,1.0,2.0);
System.out.println(k2.toString());
umfang = k2.umfang();
System.out.println("Umfang = " + umfang);
System.out.println("Flaeche = " + k2.flaeche());
// Erzeuge einen dritten Kreis
Kreis k3 = new Kreis(-2.0);
71
Klassenhierarchie
auf
Programmieren in Java
System.out.println(k3.toString());
umfang = k3.umfang();
System.out.println("Umfang = " + umfang);
System.out.println("Flaeche = " + k3.flaeche());
// Erzeugen eines Rechteck
Rechteck r1 = new Rechteck(2.0,2.0);
System.out.println(r1.toString());
umfang = r1.umfang();
System.out.println("Umfang = " + umfang);
System.out.println("Flaeche = " + r1.flaeche());
// Erzeugen eines weiteren Rechtecks
Rechteck r2 = new Rechteck(2.0,2.0,2.0,3.0);
System.out.println(r2.toString());
umfang = r2.umfang();
System.out.println("Umfang = " + umfang);
System.out.println("Flaeche = " + r2.flaeche());
}
}
1.4.1.3 Referenzen, Referenztypen und Aufzählungstyp
Referenzen
Java erlaubt die Definition von Klassen, aus denen Objekte erzeugt werden können.
Objekte können als Referenztypen behandelt werden, die von Variablen angelegt
und verwendet werden.
Beim Zuweisen von Variablen für Objekte bzw. beim Weiterreichen von Objekten als
Argumente an Methoden werden Referenzen auf diese Objekte festgelegt.
Bsp.: Referenzen auf Objekte in der Klasse Rechentafel bzw. Rechentafeltest
System.out.println("Referenzen auf Objekte");
// Referenzen auf Objekte
Rechentafel rechenObj1 = new Rechentafel();
rechenObj1.ersterOperand = 200;
rechenObj1.zweiterOperand = 300;
Rechentafel rechenObj3 = new Rechentafel(300,200);
System.out.println("Operanden von Instanz 1: " +
rechenObj1.ersterOperand + ", " + rechenObj1.zweiterOperand);
System.out.println("Operanden von Instanz 2 vor Zuweisung: " +
rechenObj3.ersterOperand + ", " + rechenObj3.zweiterOperand);
rechenObj3 = rechenObj1;
System.out.println("Operanden von Instanz 2 nach Zuweisung: " +
rechenObj3.ersterOperand + ", " + rechenObj3.zweiterOperand);
// Test auf Gleichheit
if (rechenObj1 == rechenObj3)
{
System.out.println("Die beiden Objekte sind tatsaechlich gleich");
}
Der Test mit „==“87 bestätigt: Es handelt sich jetzt (nach der Zuweisung) um das
gleiche Objekt, d.h.: die gleiche Referenz.
Die „null“-Referenz. In Java gibt es das spezielle Literal null. Es zeigt an, daß
eine Referenz auf kein Objekt verweist. Der Wert ist nur für Referenzen vorgesehen
und kann nicht in einen primitiven Typ umgewandelt werden. Die „null“-Referenz ist
typenlos, d.h.: Sie kann jedem Objekt zugewiesen werden, z.B.:
87
Der Operator „==“ überprüft die Gleichheit der Referenzen.
72
Programmieren in Java
Point p = null;
String s = null;
Da sich hinter „null“ kein Objekt verbirgt, kann darüber auch kein Objekt aufgerufen
werden.
Java verfügt über ein automatisches Speichermanagement. Deshalb braucht der
Java-Programmierer sich nicht um die Rückgabe von Speicher zu kümmern, der von
Referenzvariablen belegt ist. Ein mit niederer Priorität im Hintergrund arbeitender
„Garbage Collector“ sucht ständig nach Objekten, die nicht mehr referenziert werden,
um den belegten Speicher freizugeben.
Finalizer-Methoden. Sie werden aufgerufen, kurz bevor das Objekt im Papierkob
landet und sein Speicher freigegeben wird. Zum Erstellen einer Finalizer-Methode
dient der folgende Eintrag in der Klassendefinition void finalize(){ ... }. Im
Körper dieser Methode können alle möglichen Reinigungsprozeduren stehen, die das
Objekt ausführen soll. Der Aufruf der Methode finalize() bewirkt nicht unmittelbar
die Ablage im Papierkorb. Nur durch das Entfernen aller Referenzen auf das Objekt
wird das Objekt zum Löschen markiert.
Referenztypen
Java kennt zwei Arten von Typen: einfache Typen und Referenztypen. Einfache
Typen sind: boolean, byte, short, int, long, char, float und double.
Klassen und Arrays sind Referenztypen. Ein Referenzwert enthält eine Referenz auf
die Daten eines Objekts bzw. Arrays. Referenztypen müssen explizit mit Hilfe des
Operators new erzeugt werden.
Aufzählungstyp
Mit Java 1.5 wurde ein Aufzählungstyp in Java integriert. Syntaktisch erscheint dieser
Typ wie eine Klasse, die statt des Schlüsselworts class das Schlüsselwort enum
hat.
Konzeptionell sind Aufzählungstypen Variablen und Attributen gleichgestellt.
public, protected, private88 static89 final90 enum identifier { value }
Im einfachsten Anwendungsfall besteht die Definition eines Aufzählungstyps aus der
durch Kommata voneinander getrennten vollständigen Aufzählung aller Werte.
Bsp.91:
public class PR14132
{
enum jahreszeiten { winter, fruehling, sommer, herbst };
public static void main(String args[])
{
jahreszeiten ende = jahreszeiten.winter;
if (ende == jahreszeiten.winter)
88
einer aus public, protecetd, private
optional
90 optional
91 pr14132
89
73
Programmieren in Java
System.out.println("Es ist " + ende);
jahreszeiten kalt = jahreszeiten.winter;
}
}
Die Verwendung von Aufzählungstypen entspricht der von primitiven Typen. Aus
diesem Grund ist keine Anforderung von Speicherplatz durch new erforderlich92.
Im Gegensatz zu Ausprägungen von Primitivtypen besitzen Aufzählungstypen jedoch
keinen Vorgabewert mit dem sie standardmäßig initialisiert werden. Die Verwendung
einer nicht initialisierten Ausprägung eines Aufzählungstypen führt zu einem
Übersetzungsfehler93.
Identisch zu den Primitivtypen besitzen Aufzählungstypen keine Identität. Zwei mit
demselben Wert belegte Aufzählungen werden als identisch betrachtet.
Aufzählungstypen lassen sich klassenartig ausbauen, z.B.94:
public enum Tage
{
montag, dienstag, mittwoch, donnerstag,
freitag, samstag, sonntag;
public boolean istWerktag()
{
switch(this)
{
case sonntag :
case samstag : return false;
default
: return true;
}
}
public static void main(String [] args)
{
Tage tag = freitag;
System.out.println(tag);
System.out.println(tag.ordinal());
System.out.println(tag.istWerktag());
System.out.println(sonntag.istWerktag());
}
}
Aufzählungsklassen enthalten in einer Reihung alle Werte der Aufzählung, über die
mit der neuen for-Schleife95 iteriert werden kann, z.B.:
public class IterTage
{
public static void main(String [] args)
{
for (Tage tag : Tage.values())
System.out.println(tag.ordinal() + ": " + tag);
}
}
Die beiden letzten Programme zeigen die Ausgabe:
92 Der Übersetzer verhindert die Instanziierung des Aufzählungstyps mit new und bricht beim Versuch mit der
Fehlermeldung enum types may not be instantiated.
93 variable ... might not have been initialized
94 pr14132
95 vgl. 2.4.7.4
74
Programmieren in Java
1.4.1.4 Konvertieren von Objekten und Primitivtypen
„Casting“ ist ein Mechanismus zum Konvertieren des Werts eines Objekts oder eines
primitiven Typs in einen anderen Typ. „Casting“ ergibt ein neues Objekt oder einen
neuen Wert und wirkt sich nicht auf das ursprüngliche Objekt bzw. den
ursprünglichen Wert aus.
Konvertieren von Primitivtypen
Ist der angestrebte Typ „größer“ als der zu konvertierende Typ, ist häufig kein
explizites „Casting“ nötig. Ein „Byte“ oder ein Zeichen kann automatisch z.B. als
„int“, ein „int“ als „long“, ein „int“ als „float“ behandelt werden. Es gehen beim
Konvertieren des Werts keine Informationen verloren, weil der größere Typ mehr
Genauigkeit bietet als der kleinere.
Ist der angestrebte Typ „kleiner“ als der zu konvertierende Typ, ist „Casting“ nötig.
Explizites Casting sieht so aus: (typname) wert
„typname“ ist der Name des Typs, auf den konvertiert wird, „wert“ ist ein Ausdruck,
der den zu konvertierenden Wert ergibt, z.B.: „(int) x/y;“. Dieser Ausdruck teilt x
durch y und wandelt dann das Ergebnis in „int“ um. Da Casting eine höhere
Priorität als Arithmetik hat, müssen die Klammern angegeben werden.
Konvertieren von Objekten
Mit einer Einschränkung können auch Klasseninstanzen in Instanzen anderer
Klassen konvertiert werden. Die betroffenen Klassen müssen durch Vererbung
miteinander verbunden sein, d.h.: Ein Objekt kann nur in eine Instanz der Sub- oder
Superklassen konvertiert werden, nicht aber in eine beliebige Klasse.
Durch Konvertieren eines Objekts in eine Instanz der Superklasse gehen
Informationen, die die Subklasse bereitgestellt hat, verloren. Spezifisches Casting ist
erforderlich: (klassename) objekt.
„klassename“ ist der Name der Klasse, in die das Objekt konvertiert werden soll.
75
Programmieren in Java
„objekt“ ist eine Referenz auf das zu konvertierende Objekt.
Konvertieren von Primitivtypen in Objekte und umgekehrt
Primitive Typen und Objekte sind in Java zwei völlig verschiedene Dinge.
Konvertierung und automatisches Casting sind nicht möglich. Allerdings enthält das
Paket „java.lang“ mehrere Subklassen, die je einem primitiven Datentyp entsprechen:
Integer, Float, Boolean usw. Mit den in diesen Klassen definierten Klassenmethoden
kann mit Hilfe von new für alle Primitivtypen eine „Art Objekt“ erstellt werden, z.B.:
„Integer intObjekt = new Integer(13);“. Mit intValue() wird ein
primitiver „int“-Wert aus einem Integer-Objekt extrahiert, z.B.: int ganzeZahl =
intObjekt.intValue();
1.4.1.5 Manipulieren von Objekten
Vergleichen von Objekten
Die relationalen Operatoren funktionieren in der Regel nur mit Primitivtypen, nicht mit
Objekten. Die Ausnahme zu dieser Regel bilden die Operatoren „==“ und „!=“. Die
Operatoren können für Objekte benutzt werden und prüfen, ob sich zwei Operanden
auf genau das gleiche Objekt beziehen.
Java besitzt kein Konzept für das Überladen (Overloading) von Operatoren. Zum
Vergleichen von Instanzen eigendefinierter Klassen müssen Sondermethoden in die
Klasse einbezogen werden.
Bsp.: Gleichheitstest mit Objekten der „String“-Klasse96.
Die String-Klasse definiert die Methode equals(), die jeden Zeichen der
Zeichenkette prüft und „true“ ausgibt, falls die beiden Zeichenketten den
gleichen Wert haben. Nach dem Operator „==“ sind die beiden „String“Objekte nicht gleich, weil sie zwar den gleichen Inhalt haben, aber nicht die
gleichen Objekte sind.
// Besonderheiten im Zusammenhang mit String-Objekten
final class StringManipulationen
{
public static void main(String args[])
{
String a = "test";
String b = "test";
String c = new String("test");
String d = new String("test");
// Díe folgenden Anweisungen geben wahr oder falsch
// an die Konsole aus
System.out.println("a == b " + (a==b));
System.out.println("c == d " + (c==d));
System.out.println("a == c " + (a==c));
System.out.println("a == \"test\" " + (a=="test"));
System.out.println("c == \"test\" " + (c=="test"));
System.out.println();
System.out.println("a.equals(b) " + a.equals(b));
System.out.println("c.equals(d) " + c.equals(d));
System.out.println("a.equals(c) " + a.equals(c));
96
pr14110
76
//
//
//
//
//
true
false
false
true
false
// true
// true
// true
Programmieren in Java
System.out.println("a.equals(\"test\") " + a.equals("test")); // true
System.out.println("c.equals(\"test\") " + c.equals("test")); // true
}
}
Kopieren von Objekten
Die clone()-Methode erzeugt ein Duplikat als Kopie eines Objekts. Damit ein
Objekt geklont werden kann, muß es die „Cloneable“-Schnittstelle unterstützen.
Die Cloneable-Schnittstelle im Paket java.lang hat selbst keine Methoden, sie
dient lediglich als Indikator dafür, daß ein Objekt geklont werden kann. Das Format
für die clone()-Methode ist: protected
Object
clone()
throws
CloneNotSupportedException
Bsp.: Die Klasse Rechentafel erhält eine clone()-Methode zum Klonen von
Rechentafel-Objekten97.
public class Rechentafel implements Cloneable
{
// Instanzvariable
private int ersterOperand = 0;
private int zweiterOperand = 0;
private int nummer;
// Identifikationsnummer
// Klassenvariable
static int anzRechenobjekte = 0;
// Konstruktoren
Rechentafel()
{
this(0,1);
}
Rechentafel(int ersterOperand, int zweiterOperand)
{
super();
this.ersterOperand = ersterOperand;
this.zweiterOperand = zweiterOperand;
nummer = ++anzRechenobjekte;
}
// Kopierkonstruktor
Rechentafel(Rechentafel rechenObjekt)
{
this(rechenObjekt.ersterOperand,rechenObjekt.zweiterOperand);
}
// Operationen
public int holersterOperand()
{
return ersterOperand;
}
public void setzersterOperand(int ersterOperand)
{
this.ersterOperand = ersterOperand;
}
public int holzweiterOperand()
{
return zweiterOperand;
}
public void setzweiterOperand(int zweiterOperand)
{
this.zweiterOperand = zweiterOperand;
}
public int addiere()
97
pr14202
77
Programmieren in Java
{
return ersterOperand + zweiterOperand;
}
public int subtrahiere()
{
return ersterOperand - zweiterOperand;
}
public int multipliziere()
{
return ersterOperand * zweiterOperand;
}
public int dividiere()
{
// ganzzahlige Division
try {
/* Innerhalb des try-Blocks werden diejenigen
kritischen Aktionen durchgefuehrt, die
Ausnahmen erzeugen koennen
*/
return (ersterOperand / zweiterOperand);
}
catch(java.lang.ArithmeticException a)
{
System.out.println(" Fehler: Division durch Null");
System.out.println("Divisor wird auf 1 gesetzt!");
zweiterOperand = 1;
return (ersterOperand / zweiterOperand);
}
}
public String toString()
{
return "Rechenobjekt# " + nummer + " Erster Operand: "
+ ersterOperand + " Zweiter Operand: " + zweiterOperand;
}
public Object clone()
{
Object o = null;
try {
o = super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println("Objekt kann nicht geklont werden");
}
return o;
}
}
Diese Methode kann dann über ein in der Klasse Rechentafeltest instanziertes
Objekt der Klasse Rechentafel aufgerufen werden:
import java.lang.*;
class Rechentafeltest extends Object
{
public static void main(String argv[])
{
// Erzeugen Instanz der Klasse Rechentafel
Rechentafel erstesRechenobjekt = new Rechentafel(3,2);
System.out.println("Eine Instanz der Klasse " +
erstesRechenobjekt.getClass().getName() +
" wurde erzeugt.");
System.out.println(erstesRechenobjekt.toString());
// Klonen eines Rechenobjekts
System.out.println("Klonen des Objekts");
78
Programmieren in Java
Rechentafel zweitesRechenobjekt = (Rechentafel)
erstesRechenobjekt.clone();
System.out.println(zweitesRechenobjekt.toString());
if (erstesRechenobjekt == zweitesRechenobjekt)
System.out.println("Erstes Objekt == Zweites Objekt");
else
System.out.println("Erstes Objekt != Zweites Objekt");
// Manipulation
System.out.println(
"Aenderungen am Original bleiben unberuecksichtigt");
erstesRechenobjekt.setzersterOperand(7);
erstesRechenobjekt.setzweiterOperand(5);
System.out.println(erstesRechenobjekt.toString());
System.out.println(zweitesRechenobjekt.toString());
// Zuweisung
System.out.println("Zuweisen des Objekts");
Rechentafel drittesRechenobjekt;
drittesRechenobjekt = erstesRechenobjekt;
System.out.println(drittesRechenobjekt.toString());
if (erstesRechenobjekt == drittesRechenobjekt)
System.out.println("Erstes Objekt == Drittes Objekt");
else
System.out.println("Erstes Objekt != Drittes Objekt");
// Manipulieren
System.out.println("Aenderungen beziehen sich auch auf die Kopie");
erstesRechenobjekt.setzersterOperand(2);
erstesRechenobjekt.setzweiterOperand(3);
System.out.println(erstesRechenobjekt.toString());
System.out.println(drittesRechenobjekt.toString());
// Kopierkonstruktor
System.out.println("Arbeiten mit einem Kopierkonstruktor");
Rechentafel viertesRechenobjekt = new Rechentafel(erstesRechenobjekt);
System.out.println(viertesRechenobjekt.toString());
}
}
Der Test zeigt: Das geklonte Objekt enthält die gleichen Werte.
Bestimmen der Klasse eines Objekts
Die Klasse Class stellt über die Methode getName() den Namen einer Klasse zur
Verfügung: public String getName()
Bsp.: Der folgende Aufruf
Rechentafel einRechenobjekt = new
Rechentafel(3,2);
System.out.print("Eine Instanz der Klasse ");
einRechenobjekt.zeigeKlasse();
betimmt den Namen der Klasse von „einRechenobjekt“. Die Methode „zeigeKlasse“
enthält die Methode getName() der Klasse Class. Das Objekt, dessen
Klassenzugehörigkeit bestimmt werden soll ruft die Methode getClass() der Klasse
Object auf. Das Ergebnis dieser Methode ist ein Class-Objekt, das die Methode
getName() kennt. „getName()“ gibt die Zeichenkette aus.
void zeigeKlasse()
{
System.out.print(this.getClass().getName());
}
79
Programmieren in Java
Ein anderen Test bietet der Operator „instanceof“. Er hat zwei Operanden: Ein
Objekt links und den Namen der Klasse rechts. Der Ausdruck gibt true oder false
aus, je nach dem, ob das Objekt eine Instanz der benannten Klasse ist, z.B.:
if (!(anderesObj instanceof Rechentafel)) return false;
der Operator instanceof kann auch als Schnittstelle benutzt werden. Falls ein
Objekt eine Schnittstelle implementiert, gibt instanceof mit dem
Schnittstellennamen auf der rechten Seite true aus.
1.4.1.6 Innere (lokale) und anonyme Klassen
Bisher gab es nur Klassen, die entweder in Paketen organisiert waren oder sich in
einem Verzeichnis befanden. Diese Form von Klassen heißen „Top-Level“-Klassen.
Klassen können aber auch in andere Klassen aufgenommen werden. Das nennt sich
dann „innere Klassen“.
class Aussen
{
...
class Innen
{
...
}
...
}
Lokale Klassen
Seit JDK 1.1 kann man innerhalb einer bestehenden Klasse z.B. X eine neue Klasse
z.B. Y definieren98. Diese Klasse ist nur innerhalb von X sichtbar. Objekte von Y
können damit nur aus X erzeugt werden. Y kann aber auf alle Instanzmerkmale von X
zugreifen. Bei der Instanzierung wird (neben einem impliziten this-Zeiger) ein
weiterer Verweis auf die erzeugende Instanz der umschließenden Klasse übergeben,
der es ermöglicht, auf sie zuzugreifen. Für innere Klassen wird eine eigenständige
Datei mit der Namenskonvention UmgebendeKlasse$innereKllasse erzeugt.
Die Anwendung lokaler Klassen für die Ereignisbehandlung besteht darin, mit ihrer
Hilfe die benötigten EventListener zu implementieren. Dazu wird das GUI-Objekt, das
einen Event-Handler benötigt, eine lokale Klasse definiert und aus einer passenden
Adapter-Klasse abgeleitet. Es braucht nicht mehr das gesamte Interface
implementiert zu werden, ( denn die Methodenrümpfe werden ja aus der
Adapterklasse geerbt,) sondern lediglich die tatsächlich benötigetn Methoden.
Anonyme Klassen
Eine Variante der lokalen Klassen sind anonyme Klassen. Sie werden ebenfalls lokal
zu einer anderen Klasse erzeugt, kommen aber ohne Klassennamen aus.
98
Im JDK wird das Konzept als Inner Classes bezeichnet
80
Programmieren in Java
Klassendefinition: new BasisKlasse(parameter) { /* inner class
methods and data */ }
Damit eine anonyme Klasse überhaupt irgendeiner sinnvollen Aufgabe zugeführt
werden kann, muß sie aus einer anderen Klasse abgeleitet sein oder ein
bestehendes Interface implementieren.
Bsp.99:
public class Anonymous
{
private TestClass test()
{
return new TestClass()
{
public void hello()
{ System.out.println("overridden test!"); } //hello()
}; //class TestClass
} //test()
public static void main(String[] args)
{
TestClass myRV;
myRV = (new Anonymous()).test();
myRV.hello();
System.out.println( myRV instanceof TestClass);
System.out.println( myRV.getClass().getName() );
} //end main()
} //class Anonymous
class TestClass
{
public void hello()
{
System.out.println("original");
} //hello()
} //class TestClass
Adapterklassen
Eine Adapterklasse implementiert ein vorgegebenes Interface mit leeren
Methodenrümpfen. Adapterklassen können verwendet werden, wenn aus einem
Interface lediglich ein Teil der Methoden benötigt wird, der Rest aber uninteressant
ist. In diesem Fall leitet man eine neue Klasse aus der Adapterklasse ab, anstatt das
zugehörige Interface zu implementieren und überlagert die benötigten Methoden. Alle
übrigen Methoden des Interfaces werden von der Basisklasse zur Verfügung gestellt.
Zu jedem der Low-Level-Ereignisempfänger stellt java.awt.event eine passende
Adapterklasse zur Verfügung.
99
pr14160
81
Programmieren in Java
1.4.1.7 Schnittstellen und Pakete
1.4.1.7.1 Schnittstellen
Definition
Eine Schnittstelle ist in Java eine Sammlung von Methodennamen ohne konkrete
Definition. Klassen haben die Möglichkeit zur Definition eines Objekts, Schnittstellen
können lediglich ein paar abstrakte Methoden und Konstanten (finale Daten)
definieren100.
Schnittstellen bilden in Java den Ersatz für Mehrfachvererbung. Eine Klasse kann
demnach nur eine Superklasse, jedoch dafür mehrere Schnittstellen haben.
Ein Schnittstelle („Interface“) ist eine besondere Form der Klasse, die ausschließlich
abstrakte Methoden und Konstanten enthält. Anstelle von class dient zur Definition
eines „Interface“ das Schlüsselwort „interface“.
"Interfaces" werden formal wie Klassen definiert, z.B.:
public interface meineSchnittstelle
{
// Abstrakte Methoden
}
bzw.
public interface meineSpezialschnittstelle extends meineSchnittstelle
{
// Abstrakte Methoden
}
"Interfaces" können benutzt werden, indem sie in einer Klasse implementiert werden,
z.B.:
public class MeineKlasse extends Object implements meineSchnittstelle
{
/* Normale Klassendefinition + Methoden aus meineSchnittstelle */
}
Eine Klasse kann mehrere Schnittstellen implementieren, z.B.
public class MeineKlasse extends Object
implements meineSchnittstelle1, meineSchnittstelle2
{
/* Normale Klassendefinition + Methoden aus meinerSchnittstelle1
und meinerSchnittstelle2 */
}
Verwendung
Bei der Vererbung von Klassen spricht man von Ableitung, bei Interfaces nennt man
es Implementierung. Durch Implementieren einer Schnittstelle verpflichtet sich die
Klasse, alle Methoden, die im Interface definiert sind, zu implementieren. Die
100
Angaben zur Implementierung sind nicht möglich
82
Programmieren in Java
Implementierung eines Interface wird durch das Schlüsselwort „implements“ bei der
Klassendefinition angezeigt.
1. Bsp.: Natürliche Ordnungsbeziehungen sind in Java über das Interface
Comparable eingefangen:
/**************** Comparable.java *****************************
/** Das Interface deklariert eine Methode anhand der sich das
* das aufgerufene Objekt mit dem uebergebenen vergleicht.
* Fuer jeden vom Objekt abgeleiteten Datentyp muss eine solche
* Vergleichsklasse implementiert werden.
* Die Methode erzeugt eine Fehlermeldung, wenn "a" ein Objekt
* einer anderen Klasse als dieses Objekt ist.
*
* int compareTo(Comparable a)
*
liefert
0, wenn this == 0
*
liefert < 0, wenn this < a
*
liefert > 0, wenn this > a
*
*/
public interface Comparable
{
public int compareTo(Comparable a);
}
Zahlreiche von Java bereitgestellte Referenztypen (Klassen) haben dieses Interface
implementiert. Deshalb sortiert die nachfolgenden Sortierroutine (Sort) String- und
Zahlen-Objekte, wie der Aufrufe aus SortTest beweisen.
public class Sort
{
public void sort(Comparable x[])
{
bubbleSort(x);
}
public void bubbleSort(Comparable x[])
{
for (int i = 0; i < x.length; i++)
{
for (int j = i + 1; j < x.length; j++)
{
if (x[i].compareTo(x[j]) > 0)
{
Comparable temp = x[i];
x[i] = x[j];
x[j] = temp;
}
}
}
}
}
public class SortTest
{
public static void main(String args[])
{
String sa[] = {
"Juergen","Christian","Bernd","Werner","Uwe",
"Erich","Kurt","Karl","Emil","Ernst"
};
// Ausgabe des noch nicht sortierten x
System.out.println("Vor dem Sortieren:");
for (int i = 0; i < sa.length; i++)
83
Programmieren in Java
{
System.out.println("sa["+i+"] = " + sa[i]);
}
// Aufruf der Sortieroutine
Sort s = new Sort();
s.sort(sa);
// Gib das sortierte Feld x aus
System.out.println();
System.out.println("Nach dem Sortieren:");
for (int i = 0; i < sa.length; i++)
{
System.out.println("sa["+i+"] = " + sa[i]);
}
System.out.println();
int za[] = { 50, 70, 80, 10, 20, 30, 1, 2, 3, 99, 12, 11, 13};
Integer o[] = new Integer[za.length];
for (int i = 0; i < za.length; i++)
{
o[i] = new Integer(za[i]);
}
// Ausgabe des noch nicht sortierten za
System.out.println("Vor dem Sortieren:");
for (int i = 0; i < za.length; i++)
{
System.out.println("za["+i+"] = " + za[i]);
}
// Aufruf der Sortieroutine
Sort zs = new Sort();
zs.sort(o);
// Gib das sortierte Feld x aus
for (int i = 0; i < za.length; i++)
{
za[i] = ((Integer) o[i]).intValue();;
}
System.out.println();
System.out.println("Nach dem Sortieren:");
for (int i = 0; i < za.length; i++)
{
System.out.println("za["+i+"] = " + za[i]);
}
}
}
2. Bsp.101: Erweiterte Form der for-Schleife for {Type identifier : expr) {
... } in Java 1. 5
1.4.1.7.2 Pakete
Definition: package packageName; // Am Anfang der Quelldatei
Pakete sind in Java Zusammenfassungen von Klassen und Schnittstellen. Sie
entsprechen in Java den Bibliotheken anderer Programmiersprachen. Pakete
ermöglichen, daß modulare Klassengruppen nur verfügbar sind, wenn sie gebraucht
werden. Potentielle Konflikte zwischen Klassenamen in unterschiedlichen
Klassengruppen können dadurch vermieden werden.
Für Methoden und Variablen innerhalb von Paketen besteht eine
Zugriffsschutzschablone. Jedes Paket ist gegenüber anderen, nicht zum Paket
101
vgl. 2.4.7.4
84
Programmieren in Java
zählenden Klassen abgeschirmt. Klassen, Methoden oder Variablen sind nur sichtbar
für Klassen im gleichen Paket. Klassen, die ohne „package“ Anweisung definiert sind,
werden vom Compiler in ein „Standardpaket“ gestellt. Voraussetzung dafür ist: Die
„.java“- und „.class“-Dateien dieses Pakets befinden sich im aktuellen Verzeichnis
(oder in einen darunter liegenden Klassenverzeichnis).
Die Java-Klassenbibliothek102 von Java 1.0 enthält folgende Pakete:
- java.lang: Klassen, die unmittelbar zur Sprache gehören. Das Paket umfaßt u.a.
die Klassen Object, String, System, außerdem die Sonderklassen für die
Primitivtypen (Integer, Character, Float, etc.)
Object
Aus dieser Klasse leiten sich alle weiteren Klassen ab. Ohne explizite Angabe der Klasse,
die eine neue Klasse erweitern soll, erweitert die neue Klasse die Object-Klasse. Die
Klasse Object ist die Basis-Klasse jeder anderen Klasse in Java. Sie definiert Methoden,
die von allen Klasse in Java unterstützt werden.
Class
Für jede in Java definierte Klasse gibt es eine Instanz von Class, die diese Klasse
beschreibt
String
Enthält Methoden zur Manipulation von Java-Zeichenketten
StringBuffer
Dient zum Erstellen von Java-Zeichenketten
Thread
Stellt einen Ausführungs-Thread in einem Java-Programm dar. Jedes Programm kann
mehrere Threads laufen lassen
ThreadGroup
Ermöglicht die Verknüpfung von Threads untereinander. Einige Thread-Operationen
können nur von Threads aus der gleichen ThreadGroup ausgeführt werden.
Throwable
Ist die Basisklasse für Ausnahmen. Jedes Objekt, das mit der "catch"-Anweisung
gefangen oder mit der "throw"-Anweisung verworfen wird, muß eine Subklasse von
Throwable sein.
System
Stellt spezielle Utilities auf Systemebene zur Verfügung
Runtime
Enthält eine Vielzahl gleicher Funktionen wie System, behandelt aber auch das Laufen
externer Programme
Process
Stellt ein externes Programm dar, das von einem Runtime-Objekt gestartet wurde.
Math
Stellt eine Reihe mathematischer Funktionen zur verfügung
Number
Ist die Basisklasse für Double,Float, Integer und Long (Objeckt-Wrapper)
Character
Ist ein Objekt-Wrapper für den datentyp char und enthält eine Reihe nützlicher
zeichenorientierter Operationen
Boolean
Ist ein Objekt-Wrapper für den Datentyp boolean
ClassLoader
Ermöglicht der Laufzeit-Umgebung von Java neue Klassen hinzuzufügen
SecurityManager Legt die Sicherheits-Restriktionen der aktuellen Laufzeitumgebung fest. Viele JavaKlassen benutzen den Security-Manager zur Sicherstellung, daß eine Operation auch
tatsächlich genehmigt ist.
Compiler
Ermöglicht, falls vorhanden, den Zugriff auf den "just-in-time" Compiler
Abb.: Klassen des java.lang-Pakets
Zusätzlich enthält das java.lang-Paket noch zwei Schnittstellen:
Cloneable
Runnable
Muß von einem anderen Objekt implementiert werden, das dann geklont oder kopiert
werden kann
Wird zusammen mit der Thread-Klasse benutzt, um die aufgerufene Methode zu
definieren, wenn ein Thread gestartet wird.
Abb.: Schnittstellen im java.lang-Paket
-
102
java.util
Die Klassenbibliothek des JDK befindet sich in einem Paket mit dem namen „java“.
85
Programmieren in Java
-
java.io: Klassen zum Lesen und Schreiben von Datenströmen und zum
Handhaben von Dateien.
-
java.net: Klassen zur Netzunterstützung, z.B. socket und URL (eine Klasse
zum Darstellen von Referenzen auf Dokumente im World Wide Web).
-
java.awt (Abstract Windowing Toolkit): Klassen zum Implementieren einer
grafischen Benutzeroberfläche. Das Paket enthält auch eine Klasse für Grafik
(java.awt.Graphics) und Bildverarbeitung (java.awt.Image).
-
java.applet: Klassen zum Implementieren von Applets, z.B. die Klasse
Applet.
Die Java Version 1.1 hat die Klassenbibliothek umfassend erweitert103:
Paket
Java.applet
Java.awt
Java.awt.datatranfer
Java.awt.event
Java.awt.image
Java.beans
Java.io
Java.lang
Java.lang.reflect
Java.math
Java.net
Java.rmi
Java.rmi.dgc
Java.rmi.registry
Java.rmi.server
Java.security
Java.security.aci
Java.security.interfaces
Java.sql
Java.util
Java.util.zip
Abb.: Klassenbibliothek der Java-Version 1.1
Bedeutung
Applets
Abstract Window Toolkit
ClipBoard-Funktionalität (Copy / Paste)
AWT Event-handling
Bildanzeige
Java Beans
Ein- und Ausgabe, Streams
Elementare Sprachunterstützung
Introspektion, besserer Zugriff auf Klassen durch
Debugger und Inspektoren
Netzzugriffe
Remote Method Invocation, Zugriff auf Objekte in
anderen virtuellen Maschinen
RMI Distributed Garbage Collection
Verwaltet Datenbank, die RMI-Verbindungen
koordiniert
RMI-Server
Sicherheit durch digitale Signaturen, Schlüssel
Access Control Lists
Digital Signature Algorithm (DAS-Klassen)
Datenbankzugriff (JDBC)
Diverse Utilities, Datenstrukturen
JAR-Files, Kompression, Prüfsummen
Verwendung
Jede Klasse ist Bestandteil eines Pakets. Der vollständige Name einer Klasse
besteht aus dem Namen des Pakets, danach kommt der ein Punkt, gefolgt von dem
eigentlichen Namen der Klasse.
Zur Verwendung einer Klasse muß angegeben werden, in welchem Paket sie liegt.
Hier gibt es zwei unterschiedliche Möglichkeiten:
-
103
Die Klasse wird über ihren vollen (qualifizieren) Namen angesprochen, z.B.
java.util.Date d = new java.util.Date();
Am Anfang des Klassenprogramms werden die gewünschten Klassen mit Hilfe der importAnweisung eingebunden, z.B.:
import java util.*;
Zusätzlich 15 weitere Packages, etwa 500 Klassen und Schnittstellen
86
Programmieren in Java
-
-
......
Date d = new Date();
Die import-Anweisung gibt es in unterschiedlichen Ausprägungen:
Mit "import paket.Klasse" wird genau eine Klasse importiert, alle anderen Klassen des
Pakets bleiben verborgen
Mit "import paket.*"104 können alle Klassen des angegebenen Pakets auf einmal importiert
werden.
Standardmäßig haben Java-Klassen Zugang zu den in java.lang befindlichen
Klassen. Klassen aus anderen Paketen müssen explizit über den Paketnamen
einbezogen oder in die Quelldatei importiert werden.
Statische Imports
Mit der Überarbeitung zur Sprachversion 1.5 wurden durch die statischen Importe
eine abkürzende Schreibweise für importierte statische Methoden etabliert:
Durch die zusätzliche Angabe des Schlüsselworts static vor der vollqualifizierte
Klassenidentifikation stehen als Resultate alle als statisch deklarierten Methoden
zum direkten Aufruf zur Verfügung.
Bsp.: Über statische Importe kann die Funktion max() aus Math importiert werden,
so dass der Klassenname entfallen kann
import static java.lang.Math.max;
class StatischesImport
{
static int max3(int a, int b, int c)
{
return max(a, max(b,c));
}
}
104 type import on demand, d.h.: Die Klasse wird erst dann in dem angegebenen Paket gesucht, wenn das
Programm sie wirklich benötigt.
87
Programmieren in Java
1.4.1.8 Polymorphismus und Binden
Polymorphismus
Definition
Polymorphismus ist die Eigenschaft von Objekten, mehreren Klassen (und nicht nur
genau einer Klasse) anzugehören, d.h. je nach Zusammenhang in unterschiedlicher
Gestalt (polymorph) aufzutreten. Java ist polymorph.
Bsp.105: Das folgende Programm Poly ist eine Anwendung zu der bereits
bekannten Klassenhierarchie für geometrische Objekte. Es behandelt
Erscheinungsformen des Polymorphismus.
import java.lang.*;
public class Poly extends Object
{
public static void main(String args[])
{
Kreis k = new Kreis(0.0,1.0,2.0);
Rechteck r = new Rechteck(0.0,1.0,2.0,2.0);
// GeomObjekt geom = new GeomObjekt();
/* unzulaessig, da GeomObjekt abstrakt ist */
Object o = new Object();
// Folgende Zuweisungen sind zulaessig
GeomObjekt geom1 = k;
GeomObjekt geom2 = new Kreis(1.0,1.0,1.0);
GeomObjekt geom3 = r;
GeomObjekt geom4 = new Rechteck(1.0,1.0,1.0,4.0);
o = k;
// Aufruf der Methoden toString() und umfang()
// ueber geom1
System.out.println(geom1.toString());
System.out.println("Umfang: " + geom1.umfang());
/* geom1 ist ein GeomObjekt. GeomObjekt enthaelt eine
(abstrakte) Definition von toString() und von umfang().
Zur Laufzeit ist der Wert von geom1 eine Referenz auf
ein Objekt der Klasse Kreis, daher werden die im Kreis
definierten (allgemeiner: die in Kreis definierten
bzw. ererbten) Methoden toString() und umfang() ausgefuehrt.
*/
// Aufruf der Methoden toString() und umfang() ueber o
// System.out.println(o.toString());
/* o ist ein Object. Object enthaelt eine Definition von
toString(), daher akzeptiert der Java-Compiler
(zur Uebersetzungszeit) o.toString().
*/
// System.out.println("Umfang: " + o.umfang());
/* o ist ein Object. Object enthaelt oder erbt keine
Definition von umfang(), daher akzeptiert der JavaCompiler (zur Uebersetzungszeit) o.umfang() nicht.
*/
// System.out.println("Umfang: " + (Rechteck) o.umfang());
/* (Rechteck) o.umfang() akzeptiert der Java-Compiler
(zur Uebesersetzungszeit) aus dem gleichen Grund wie
zuvor nicht.
Die Schreibweise (Rechteck) o.umfang() bedeutet auch:
Der Rueckgabewert von o.umfang() soll als Rechteck105
Vgl. pr14181
88
Programmieren in Java
Referenz interpretiert werden.
*/
System.out.println("Umfang: " + ((Rechteck) o).umfang());
/* o ist ein Object, (Rechteck o) bedeutet: o soll als
Rechteck interprtiert werden. Fuer Rechteck-Objekte
ist umfang() erkaert, der Java-Compiler akzeptiert
(zur Uebersetzungszeit) ((Rechteck o).umfang().
Zur Laufzeit wird die in Recteck definierte
(allgemeiner: die in Recheck definierte bzw. ererbte)
Methode umfang() ausgefuehrt.
*/
}
}
Binden
Das Schema einer Botschaft wird aus "Empfänger Methode Argument" gebildet.
Darüber soll eine Nachricht die physikalische Adresse eines Objekts im Speicher
finden. Trotz Polymorphismus und Vererbung ist der Methodenname (oft. Selektor
genannt) nur eine Verknüpfung zum Programmcode einer Methode. Vor der
Ausführung einer Methode muß daher eine genaue Zuordnung zwischen dem
Selektor und der physikalischen Adresse des tatsächlichen Programmcodes erfolgen
(Binden oder Linken).
In der objektorientierten Programmierung unterscheidet man: frühes und spätes
Binden:
Frühes Binden: Ein Compiler ordnet schon zum Zeitpunkt der Übersetzung des Programms die
tatsächliche, physikalische Adresse der Methode dem Methodenaufruf zu.
Spätes Binden: Hier wir erst zur Laufzeit des Programms die tatsächliche Verknüpfung zwischen
Selektor und Code hergestellt. Die richtige Verbindung übernimmt das Laufzeitprogramm der
Programmiersprache.
Java unterstützt das Konzept des „Late Binding“.
89
Programmieren in Java
1.4.2 Klassen des Pakets java.lang
1.4.2.1 Die Klasse Object
Die Object-Klasse ist die Basisklasse für jede andere Klasse in Java. Sie definiert
Methoden, die von allen Klassen in Java unterstützt werden.
Test auf Gleichheit von Objekten: public boolean equals(Object obj)
Darüber kann ermittelt werden, ob zwei Objekte gleich sind. Subklassen
überschreiben diese Methode zur Durchführung eines inhaltlichen Vergleichs. In
jeder Klasse ist die Methode auch gut aufgehoben, denn nur ein Objekt weiß, wann
es mit einem anderen gleich ist. Ob zwei Referenzen auf das gleiche Objekt zeigen,
läßt sich durch den Vergleichsoperator == feststellen.
Zeichenkettendarstellung von Objekten: public String toString()
Darüber kann ein Objekt an einen Ausgabestrom übergeben werden.
Berechnung von Hashcode: public int hashCode() berechnet den
numerischen Wert, der als Index zur Speicherung eines Objekts in einer Hashtabelle
verwendet werden kann.
Hashsodes werden zur Speicherung von Elementen in Hashtabellen. Das sind
Datenstrukturen, die eine effizienten Zugriff auf ihre Elemente erlauben.
Klonen
von
Objekten:
protected
Object
clone()
throws
CloneNotSupportedException, OutOfMemoryError erzeugt ein exaktes
Duplikat eines Objekts. Ein Objekt kann nur geklont werden, falls es die CloneableSchnittstelle unterstützt.
Fragen nach dem Namen der Objektklasse: public final Class getClass()
Benachrichtungsmechanismus. Hierfür gibt es die Methoden: public final void
notify() bzw. public final void notifyAll() und public final void
wait() throws InterruptedException bzw. public final void
wait(long timeout) throws InterruptedException. Threads106 können
miteinander kommunizieren und gegenseitig auf sich warten. Object definiert fünf
Versionen von wait() und notify() zur Synchronisation von Threads.
Aufräumen: protected void finalize() throws Throwable. Eine spezielle
Methode finalize() wird immer dann aufgerufen, wenn der Garbage Collector
das Objekt entfernen möchte. Objekte sollten diese Funktion überschreiben, wenn
sie bspw. noch Dateien schliessen müssen.
Objektidentifikation: public String toString(). Jedes Objekt sollte sich durch
die Methode toString() mit einer Zeichenkette identifizieren. Die Zeichenkette gibt
die Inhalt der Attribute an. Neue Objekte sollten diese Methode überschreiben. Ist
dies nicht der Fall, gelangt das zugehörige Programm zur Standardimplementierung
in Object.
106
vgl. 1.4.2.4
90
Programmieren in Java
1.4.2.2 Die Class-Klasse
Diese Klasse stellt Methoden zur Abfrage von Eigenschaften von Klassen zur
Verfügung und erlaubt es, Klassen dynamisch zu laden und Instanzen dynamisch zu
erzeugen. In Java hat jede Klasse eine entsprechende Instanz von Class. In Java
liegen Klassen selbst als Objekt vor. Neben normalen Klassen werden auch
Interfaces über Class-Objekte abgelegt. Für ein bestimmtes Objekt kann man auf
mehreren Wegen das zugehörige Class-Objekt in Erfahrung bringen. Für ein
bestimmtes Objekt kann man auf mehreren Wegen das zugehörige Class-Objekt in
Erfahrung bringen:
- Ist ein Exemplar der Klasse verfügbar, dann ruft man die Methode getClass() von
java.lang.Object auf. Man erhält die zugehörige Klasse.
- Ist die Klasse bekannt, besteht aber zusätzlich Interesse an den Vorfahren, dann kann man das mit
getSuperClass() herausfinden
- Ist die Klasse nicht zur Compilezeit aber zur Laufzeit bekannt, dann hilft die Klassenmethode
forName() weiter.
Ein Klassenobjekt kann eine Schnittstelle, eine Klasse, einen primitiven Datentyp
oder auch ein Array beschreiben.
Class
public static Class forName(String className) throws ClassNotFoundException
// liefert das Klassen-Objekt mit dem vollqualifizierten Namen der Klasse oder der Schnittstelle
// className. Das Metaobjekt wird, falls es nicht von der VM eingebunden ist, gesucht und
// geladen. Der Rückgabewert ist nicht null, falls es nicht gefunden werden konnte, sondern eine
// ClassNotFoundException.
public String getName()
// liefert den voll qualifizierten Namen der Klasse, der Schittstelle, des Arrays oder des
// primitiven Datentyps des Class-Objekts
public Class getSuperclass()
// liefert die Superklasse des Klassen-Objekts.
public boolean isInterface()
// liefert true, falls das Class-Objekt ein Interface beschreibt
public boolean isArray()
// liefert true, falls das Class-Objekt einen Array beschreibt
public boolean isPrimitive()
// testet, ob das Interface einen primitiven Datentyp beschreibt
public int getModifiers()
// liefert die Modifizierer für eine Klasse oder ein Interface
public Field[] getFields() throws SecurityException
// liefert ein Feld mit Field-Objekten107.
public Method[] getMethods() throws SecurityException
// gibt ein Array von Method-Objekten108 zurück, die alle öffentlichen Methoden der Klasse/
// Schnittstelle beschreiben
...
Abb.: Die Klasse Class
107
108
Objekten der Klasse java.lang.reflect.Field
Objekte der Klasse java.lang.reflect.Method
91
Programmieren in Java
Class besitzt Methoden, die hauptsächlich zur Beschreibung der Schnittstellen einer
Klasse und zur Einordnung in die Klassenhierarchie dienen. Diese abstrakte und
dynamische Dokumentation von Klassen wird als Reflection bezeichnet109.
Das folgende Programmstück110 testet Objekte systematisch durch:
import java.util.*;
class CheckClassTyp
{
public static void main(String args[])
{
Class observer
= Observer.class;
Class observable = Observable.class;
Class array = (new int[2][3][4]).getClass();
Class primitive = Integer.TYPE;
checkClassType(observer);
checkClassType(observable);
checkClassType(array);
checkClassType(primitive);
}
static void checkClassType(Class c)
{
String name = c.getName();
if (c.isArray())
System.out.println(name + " ist ein Array.");
else if (c.isPrimitive())
System.out.println(name + " ist ein primitiver Datentyp.");
else if (c.isInterface())
System.out.println(name + " ist ein Schnittstelle.");
else
System.out.println(name + " ist eine Klasse.");
}
}
Liegt der Name der Klasse als Class-Objekt vor, kann man zur Laufzeit den voll
qualifizierten Namen über die Methode getName() ausgeben.
import java.util.Date;
class BspName
{
public static void main(String [] args)
{
Date d = new Date();
Class c = d.getClass();
String s = c.getName();
System.out.println(s);
System.out.println((new int[2][3][4]).getClass().getName());
}
}
Arrays werden über getName() mit führenden „[“ kodiert. Jede Klammer beschreibt
die Tiefe des Arrays. Nach der Klammer folgt in kodierter Form, welchen Typ das
Array speichert. So liefert System.out.println((new int [2] [3] [4] ).
getClass().getName());den String „[[[I“. Nimmt das Array Objekte auf, wird dies
mit
einem
„LKlassenname;“
kodiert.
So
ergibt
(new
Object[3]).getClass().getName() den String „[Ljava.lang.Object;“.
109
Die Klasse java.lang.Class ist Basis für Methoden, die Objekte vom Typ Constructor, Method, Field usw.
zurückliefern. Die zugehörigen Klassen sind im Package java.lang.reflect definiert.
110 vgl. pr14220
92
Programmieren in Java
1.4.2.3 Die Klasse System
Die Klasse System besteht nur aus Klassenvariablen und –methoden und kann nicht
instanziiert werden. Sie stellt die Verbindung zum Betriebssystem her und erlaubt
bspw. den Zugriff auf die Standardein- / Standardausgabe.
System
public static final InputStream in
public static final PrintStream out
public static final PrintStream err
public static void setIn(Inputstream in)
public static void setOut(PrintStream out)
public static void setErr(PrintStream err)
public static Securitymanager getSecurityManager()
public static void setSecurityManager(SecurityManager s)
public static long currentTimeMillis()
public static Properties getProperties()
public static void setProperties(Properties props)
public static String getProperty(String key)
public static void exit(int status)
public static void gc()
public static void load(String filename)
Abb.: Die Klasse System
System-Properties
Properties sind Listen von Eigenschaften, die Programmen vom Java-Laufzeitsystem
zur Verfügung gestellt werden. Jede Eigenschaft besitzt einen Name, unter dem auf
sie zugegriffen werden kann.
Property
java.version
java.vendor
java.vendor.url
java.home
java.class.version
java.class.path
os.name
os.arch
os.version
file.seperator
path.seperator
line.seperator
user.name
user.home
user.dir
Bedeutung
Java-Versionsnummer
Herstellerspez. Zeichenkette
URL (Internet-Link)
Installationsverzeichnis
Versionsnummer der Java-Klassenbibliothek
Aktueller Klassenpfad
Name des Betriebssystems
Betriebssystem-Architektur
Versionsnummer des Betriebssystems
Trennzeichen für die Bestandteile des Pfadnamens
Trennzeichen für die Laufwerksangabe eines Pfadnamens
Zeichenkette für Zeilenumschaltung
Name des angemeldeten Benutzers
Home-Verzeichnis
Aktuelles Arbeitsverzeichnis
Für den Zugriff auf diese Eigenschaften steht die Klasse Properties aus dem
Paket java.util zur Verfügung. Sie erzeugt Property-Listen. Die Klasse
Properties ist eine Ableitung der Klasse HashTable und stellt darüber eine
Tabelle mit Schlüssel/Wertepaaren zur Verfügung.
93
Programmieren in Java
Für den Zugriff auf einzelne Properties reicht meistens die Klassenmethode der
Klasse System aus:
public static String getProperty(String key)
liefert die Eigenschaft zu dem key in Form einer Zeichenkette. Falls keine Eigenschaft unter diesem
Namen (key) gefunden wird, wird „null“ zurückgegeben.
Die Methode
public static Properties getProperties();
liefert das komplette Properties-Objekt mit den System-Properties.
Bsp.111: Ausgabe einer Liste aller System-Properties auf dem Bildschirm
import java.lang.*;
import java.util.*;
class EigenschaftsTest extends Object
{
public static void main(String argv[])
{
System.out.println(new Date());
// System-Eigenschaften
Properties p = System.getProperties();
p.list(System.out);
System.out.println("Speicher-Verwendung:");
Runtime rt = Runtime.getRuntime();
System.out.println("Totaler Speicher = " + rt.totalMemory() +
" Freier Speicher = " + rt.freeMemory());
}
}
in, err und out
out ist eine statische Variable vom Typ PrintStream, die beim Starten des System
so initialisiert wird, daß ihre Ausgabe auf die Standardausgabe geleitet wird.
Analog zu out gibt es err und in. Dabei dient err zur Ausgabe von
Fehlermeldungen, in ist ein Standardeingabekanal, der zur Eingabe von der
Tastatur verwendet wird.
Mit Hilfe von
public static void setIn(InputStream in);
public static void setOut(PrintStream out);
public static void setErr(PrintStream err);
kann die Standardein- und Standardausgabe aus dem Programm heraus umgeleitet
werden.
public static void exit(int status)
Mit System.exit(status) wird das laufende Programm beendet. „status“ dient
als Fehleranzeige und wird als Exitcode an den Aufrufer des Programms
zurückgegeben. Gemäß Konvention zeigt ein Wert größer oder gleich 1 einen Fehler
während der Programmausführung an, 0 signalisiert ein fehlerfreies Programmende.
111
pr14231
94
Programmieren in Java
public static void gc()
führt einen expliziten Aufruf des Garbage Collector durch. Dieser sucht nach
freiem Speicher und gibt ihn dann an das Laufzeitsystem zurück. Normalerweise ist
ein aufruf dieser Methode nicht erforderlich, denn der Garbage Collector läuft als
niedrig priorisierter Thread im Hintergrund.
public static long currentTimeMillis()
liefert die Anzahl der Millisekunden, die zum Zeitpunkt des Aufrufs seit Mitternacht
des 1.1.1970 vergangen sind. Ob tatsächlich eine Auflösung von einer Millisekunde
erreicht wird, ist von der Java-Implementierung abhängig. In PC-basierten Systemen
orientiert sie sich meistens an der Auflösung des „System-Timer“.
1.4.2.4 Multithreading
1.4.2.4.1. Die Klasse Thread
Threads werden in Java durch die Klasse Thread und das Interface Runnable
implementiert. In beiden Fällen wird der Thread-Körper (, also der parallel
auszuführende Code) in Form der zu überlagernden Methode run bereitgestellt. Die
Kommunikation kann durch Zugriff auf die Instanz- oder Klassenvariablen oder durch
Aufruf beliebiger Methoden, die innerhalb von run sichtbar sind, erfolgen. Ein
Thread stellt einen einzigen Ablauf von Ausführungen in einem Java-Programm dar.
Die Klasse Thread ist Bestandteil des Pakets java.lang und steht damit allen
Anwendungen zur Verfügung. Über die Klasse Thread hat Java das
(Betriebssystem-) Konzept der Nebenläufigkeit implementiert. Mit Nebenläufigkeit
bezeichnet man die Fähigkeit eines Systems zwei oder mehr Vorgänge gleichzeitig
oder quasi gleichzeitig ausführen zu lassen. Ein Thread ist ein eigenständiges
Programmfragment, das parallel zu einem anderen Thread laufen kann.
95
Programmieren in Java
<< interface >>
Runnable
public abstract void run()
Thread
public static final int MIN_PRIORITY
public static final int NORM_PRIORITY
public static final int MAX_PRIORITY
<< Konstruktor >>
public Thread()
public Thread(Runnable target)
<< Methoden >>
public static Thread currentThread()
public static void yield()
public static void sleep(long millis) throws InterruptedException
public void start()
public void run()
public final void stop()
//deprecated
public final void stop(Throwable obj)
//deprecated
public void interrupt()
public static boolean interrupted()
public boolean isInterrupted()
public void destroy()
public final boolean isAlive()
public final void suspend()
//deprecated
public final void resume()
//deprecated
public final void setPriority(int newPriority)
public final int getPriority()
...
public String toString()
Abb.: Thread-Klassen
Methoden der Klasse Thread
public void run()
Zum Erzeugen eines Thread muß eine eigene Klasse aus der Klasse Thread
abgeleitet, und die Methode run überlagert werden. Mit Hilfe des Aufrufs der
Methode start wird der Thread gestartet, die weitere Ausführung wird an run()
übertragen. „start()“ wird nach dem Start beendet, der Aufrufer kann parallel zum
neu erzeugten Thread fortfahren.
Bsp.: Einfacher Thread, der in einer Endlosschleife einen Thread hochzählt.
class MeinThread extends Thread
{
public void run()
{
int i = 0;
while (true)
96
Programmieren in Java
{
// Endlosschleife, Abbruch durch Druecken von Strg+C
System.out.println(i++);
}
}
public static void main(String args[])
{
// Erzeugen einer Instanz
MeinThread t = new MeinThread();
// Start vom erzeugten Thread
t.start();
// Impliziter Aufruf der Methode run()
}
}
Der (quasi) parallel auszuführende Code wird durch die überlagerte Methode run
bereitgestellt. Die Kommunikation kann durch Zugriff auf Instanz- und
Klassenvariablen oder durch Aufruf beliebiger Methoden erfolgen, die innerhalb von
run() sichtbar sind.
Eine Java-Applikation wird immer dann beendet, wenn der letzte Thread beendet
wurde(, der kein Hintergrund-Thread (Dämon) ist). Da ein einfaches Programm nur
einen einzigen Vordergrund-Thread besitzt, (nämlich den, in dem main() läuft112,)
wird es beendet, wenn main() beendet wird. Das Beispielprogramm erzeugt einen
zusätzlichen Vordergrund-Thread und kann erst dann beendet werden, wenn dieser
Thread beendet wurde.
public static void sleep(long millis) throws InterruptedException
sorgt dafür, daß der aktuelle Prozeß für die (in Millisekunden) angegebene Zeit
unterbrochen wird.
public static void sleep(long millis, int nanos) throws
InterruptedException
Die erzielbare Genauigkeit der Unterbrechungs-Wartezeit wird durch HardwareRestriktionen der Maschine begrenzt
public final boolean isAlive()
gibt true zurück, wenn der aktuelle Thread gestartet, aber noch nicht beendet
wurde.
Beendet wird ein Thread, wenn das Ende der run-Methode erreicht ist, oder wenn
die Methode stop aufgerufen wurde.
Bsp.: Beenden des Thread nach 2 Sekunden durch Aufruf von stop
class MeinThread1 extends Thread
{
public void run()
{
int i = 0;
while (true)
{
// Endlosschleife, Abbruch durch Druecken von Strg+C
System.out.println(i++);
}
}
public static void main(String args[])
{
// Erzeugen einer Instanz
112 Beim Starten eines Java-Programms wird automatisch ein Thread für die Ausführung des Hauptprogramms
angelegt.
97
Programmieren in Java
}
}
MeinThread1 t = new MeinThread1();
// Start vom erzeugten Thread
t.start();
// Impliziter Aufruf der Methode run()
try {
Thread.sleep(2000);
}
catch(InterruptedException e)
{ }
t.stop();
Mit dem JDK 1.2 wurde stop() als deprecated markiert, da es nicht vorhersehbar
und auch nicht definiert ist, an welcher Stelle ein Thread beim Aufruf von stop()
abgebrochen wird.
Ersatz für stop(): Man kann ein Flag für Threadabbruch setzen und dieses zyklisch
in der Methode run des Thread abfragen. Falls dieses gesetzt ist, können alle
Ressourcen freigegeben und der Thread durch reguläres Beenden von run() (bpw.
mit Hilfe von return) beendet werden. Das Flag darf allerdings nicht zu oft aber
auch nicht zu selten abgefragt werden.
Bsp.113:
class MeinThread extends Thread
{
boolean ok = true;
public void run()
{
int i = 0;
while (ok)
System.out.println(i++);
}
}
public class MeinThreadTest
{
public static void main(String [] args)
{
MeinThread t = new MeinThread();
t.start();
try {
Thread.sleep(2000);
}
catch (InterruptedException e)
{ }
t.ok = false;
}
}
Es gibt in der Klasse Thread einige Methoden, die einen solchen Mechanismus
standardmäßig unterstützen.
public void interrupt()
setzt ein Flag, das eine Unterbrechungsanforderung signalisiert.
public boolean isInterrupted()
stellt fest, ob das Abbruchflag gesetzt wurde und der Thread beendet wurde.
public static boolean interrupted()
113
pr14240
98
Programmieren in Java
stellt den Status des Abbruchflag beim aktuellen Thread fest und setzt zusätzlich das
Abbruchflag auf seinen Initialisierungswert zurück.
Bsp.114:
public class PR14240 extends Thread
{
int zaehler = 0;
public void run()
{
while (true)
{
if (isInterrupted())
{
break;
}
printLine(++zaehler);
}
}
private void printLine(int cnt)
{
//Zeile ausgeben
System.out.print(zaehler + ": ");
for (int i = 0; i < 30; ++i)
{
System.out.print(i == zaehler % 30 ? "* " : ". ");
}
System.out.println();
// 100 ms. warten
try {
Thread.sleep(100);
}
catch (InterruptedException e)
{
interrupt();
}
}
public static void main(String[] args)
{
PR14240 th = new PR14240();
{
//Thread starten
th.start();
//2 Sekunden warten
try {
Thread.sleep(2000);
}
catch (InterruptedException e) { }
//Thread unterbrechen
th.interrupt();
}
}
}
Das Programm gibt zunächst über einen separaten Thread zunächst Textzeilen aus.
Wahrscheinlich erfolgt der Aufruf von interrupt() während des Aufrufs von
sleep(), da die Pause nach der Bildschirmausgabe vermulich länger dauert als die
Bildschirmausgabe.
Ist
dies
der
Fall
wird
sleep()
mit
einer
InterruptedException abgebrochen. Das Abbruchflag wird zurückgesetzt und
der Aufruf von interrupt() damit wirkungslos. Da aber in der catch-Klausel
114
vgl. pr14240
99
Programmieren in Java
interrupt() erneut aufgerufen wird, wird das Flag wieder gesetzt und run() die
Abbruchaufforderung signalisiert.
public final void join() throws InterruptedException
Mit Hilfe dieser Methode kann gezielt auf die Beendigung eines Thread gewartet
werden. So etwas ist sinnvoll, wenn zeitaufwendige Operationen vor dem Aufruf
einer Folgeoperation noch beendet werden müssen. Der zeitaufwendigsten
Operation ist ein eigener Thread zugeordnet.
public static void yield()
“Freiwilliges” Angebot des laufenden Thread, den Prozessor abzugeben.
Unterbrechen eines Thread: Mit Hilfe der Methoden suspend bzw. resume ist es
möglich einen Thread vorübergehend zu unterbrechen bzw. fortzusetzen. Durch
suspend() wird die Ausführung unterbrochen, durch resume() wird der Thread
(genauer: die Methode run) an der Stelle fortgesetzt, an der die Unterbrechung
erfolgte. Sowohl suspend() als auch resume() sind aber deprecated und sollten
nicht mehr verwendet werden.
Verwaltung von Threads
Threads besitzen administrative Eigenschaften, die sich in zwei Gruppen einteilen
lassen:
- Priorität und Name
- Thread-Gruppen
Priorität, Name. Jeder Thread hat einen eindeutigen Namen. Ohne explizite Vorgabe
erfolgt die Vergabe des Namens automatisch in der Form “Thread-“+n , wobei n
ein int ist. Mit public void setName(String name) kann einem Thread ein
Name zugewiesen werden. Mit public String getName() kann der Name eines
Thread abgefragt werden. public static Thread currentThread() liefert
den Zugriff auf den aktuellen Thread, der die Methode gerade ausführt, z.B.:
public class ThreadTest
{
public static void main(String args[])
{
System.out.println("THREAD: " + Thread.currentThread() );
}
}
/* Ausgabe am Bildschirm: THREAD: Thread[main,5,main]
Die Ausgabe umfasst : Name, Priorität und Gruppe
*/
Neben dem Namen besitzt ein Thread noch eine Priorität. Die Priorität steuert der
Scheduler in der Weise, daß bei Vorhandensein mehrerer Threads diejenigen mit
höherer Priorität vor denen mit niedrigerer Priorität ausgeführt werden. Beim Anlegen
eines neuen Thread bekommt dieser die Priorität des aktuellen Thread. Mit public
void setPriority(int neuePrioritaet) kann die neue Priorität gesetzt und
mit public int getPriority() abgefragt werden. Als Parameter kann an
setPriority() ein Wert übergeben werden, der zwischen den Konstanten
Thread.MIN_PRIORITY und Thread.MAX_PRIORITY liegt. Java bietet 10
100
Programmieren in Java
Prioritätsstufen
zwischen
1
(Thread.MIN_PRIORITY)
und
10
(Thread.MAX_PRIORITY) an. Der Normalwert wird durch die Konstante
Thread.NORM_PRIORITY festgelegt115.
Thread-Gruppen. Sie verwalten Informationen für eine ganze Gruppe und bieten
Methoden an, die auf alle Threads einer Gruppe angewendet werden können. In der
Klassenbibliothek gibt es dafür eine eigende ThreadGroup.
ThreadGroup
<< Konstruktoren >>
public ThreadGroup(String name)
public ThreadGroup(ThreadGroup parent, String name)
<< Methoden >>
public final String getName();
public final ThreadGroup getParent()
public int activeCount()
public int activeGroupCount()
public int enumerate(Thread[] list)
public enumerate(ThreadGroup[] list)
public void interrupt();
public boolean isDaemon()
public void setDaemon(boolean daemon)
public final int getMaxPriority()
public final void setMaxPriority()
Abb.: Die Klasse TheadGroup
Die Klasse ThreadGroup stellt zur Verwaltung mehrerer Threads Methoden bereit.
Sie besitzt eine Default-Priorität, kann alle Threads anhalten und kann sie als
Daemons ablaufen lassen. Threads werden durch Konstruktoren einer Gruppe
zugeordnet. Thread-Gruppen können auch ineinander verschachtelt werden.
Daemon-Threads. Es gibt Aufgaben, die zur Laufzeit des Programms zu erledigen
sind, aber nie terminieren, z.B. der Garbage Collector. Da die virtuelle Maschine erst
beendet wird, wenn kein Java-Thread mehr aktiv ist, müssen am Ende der
Anwendung alle Threads angehalten werden. Es wäre aber lästig (und manchmal
schwierig), die Hintergrund-Threads explizit zu beenden. Hierfür existiert das
Konzept der Daemon-Threads. Da sich die virtuelle Maschine beendet, wenn nur
noch Daemon-Threads laufen, brauch die Beendigung dieser Threads nicht weiter
berücksichtigt werden. Ein Thread ist ein Daemon, wenn ihm das durch
public final void setDaemon(boolean on)
mitgeteilt wurde. Die Methode
public final boolean isDaemon()
gibt Auskunft über die Art eines Thread.
115
im aktuellen JDK 1, 5, 10
101
Programmieren in Java
1.4.2.4.2 Das Interface Runnable
Nicht immer ist es möglich, eine Klasse, die als Thread laufen soll, von Thread
abzuleiten. Ein Thread kann deshalb auch über Implementierung des Interface
Runnable erzeugt werden. Runnable enthält nur eine einzige Deklaration nämlich
die der Methode run:
public interface Runnable
{
public abstract void run();
}
Implementierung von Runnable: Jede Klasse, deren Instanzen als Thread laufen,
muß das Interface Runnable implementieren116. Eine nicht von Thread abgeleitete
Instanz kann nach folgenden Implementierungsschritten als Thread laufen:
- Erzeugen eines neuen Thread-Objekts
- Übergabe des Objekts, das parallel ausgeführt werden soll, an den Konstruktor
- Aufruf der Methode start() des Thread-Objekts
Über start() startet das Thread-Objekt die run-Metode des übergebenen Objekts
(das sie durch die Übergabe an den Konstruktor kennt). Da dieses Objekt das
Interface Runnable implementiert, ist garantiert, daß eine geeignete Methode run
zur Verfügung steht.
Bsp.117: ThreadDemo
Thread
Runnable
<< interface >>
MAX_PRIORITY
….
sleep()
run()
start()
join()
….
ErsterThread
run()
ZweiterThread
runs
run()
class ErsterThread extends Thread
{
public void run()
{
for (int i = 0; i < 10; i++)
try {
Thread.sleep( Math.round(1000 * Math.random()) );
System.out.println(this + " " + i);
}
116
117
sogar die Klasse Thread selbst
vgl. pr14240
102
Programmieren in Java
catch (InterruptedException e)
{ System.err.println(e); }
}
}
class ZweiterThread implements Runnable
{
public void run()
{
for (int i = 0; i < 10; i++)
try {
Thread.sleep( Math.round(1000 * Math.random()) );
System.out.println(Thread.currentThread().toString() + " " + i);
}
catch (InterruptedException e)
{ System.err.println(e); }
}
}
public class ThreadDemo
{
public static void main(String [] args)
{
ErsterThread thread1 = new ErsterThread();
thread1.start();
Thread thread2 = new Thread(new ZweiterThread());
thread2.start();
try {
thread1.join();
thread2.join();
}
catch (InterruptedException e)
{ System.err.println(e); }
}
}
Abb.: Definition von Threads
Die Klasse Thread implementiert Runnable. Dort ist die Methode run() deklariert. Mit der Methode
start() wird ein Thread gestartet, in dem run() aufgerufen wird. „join()“ wartet auf die Beendigung
des Thread.
ErsterThread ist Subklasse von Thread. „run()“ von Thread wird überschrieben.
„sleep()“ legt einen Thread für eine bestimmte Zeit (in Millisekunden) schlafen. Hier wird zufällig
eine Zeitspanne zwischen 0 und 1000 ms gewartet und dann der Scheifenzählerwert ausgegeben.
103
Programmieren in Java
start thread2
start thread1
join thread1
join thread2
main
thread1
thread2
Zeit
Abb.: Zusammenspiel von Threads in ThreadDemo
ZweiterThread implementiert Runnable. Hier muß explizit eine Instanz von Thread erzeugt
werden.
Die Instanz von ZweiterThread wird einem Thread-Objekt übergeben, der die run()-Methode
betreibt.
ErsterThread kann sich selber schlafen legen. ZweiterThread legt die benutzte Instanz von Thread
schlafen.
Bei Programmstart existiert immer automatisch ein ErsterThread, dessen Ausführung in der
main()-Methode beginnt. Dieser main-Thread startet nun zwei weitere Threads und wartet (join())
auf das Ende dieser Threads.
Bei jedem Ablauf ergibt sich nicht-deterministisch eine andere Reihenfolge der Ausgaben
1.4.2.4.3. Prozesse und Threads in Java
Ein Java-Programm besitzt in der Regel eine Klasse mit einer statischen Methode
main().
Bei jedem Start eines Java-Programms erzeugt das Betriebssystem einen Prozeß
und startet die virtuelle Maschine (VM). Die VM erzeugt einen Haupt-Thread (main
thread) auch Programmfaden, Kontrollfluß oder leichtgewichtiger Prozeß genannrt.
Der Main-Thread führt daraufhin die Methode main() aus.
Eine Methode im Quelltext besteht aus einer Sequenz von Anweisungen. Beim
Übersetzen des Quelltextes, wird jede Anweisung in mehrere elementare
Teilanweisungen (byte codes) für die VM zerlegt. Ein Java-Thread führt diese
einzelnen elementaren Anweisungen einer Methode (z.B. main()) und die
Anweisungen der darin enthaltenen Methoden squentiell aus.
Java-Prozesse werden auf Betriebssystem-Prozesse abgebildet, d.h.: Auch JavaThreads können auf Betriebssystem-Threads abgebildet werden. Man spricht von
einer virtuellen Maschine, die native Threads unterstützt.
104
Programmieren in Java
Anwendung_1
Anwendung_2
Betriebssystem
Java_VM_1
Java_VM_2
VM
glob.
Variable
Prozess_1
lok. Variable
Prozess_2
lok. Variable
Prozess_3
lok. Variable
Abb. Betriebssystem - VM
Die Abbildung der Java-Prozesse auf BS-Prozesse hängt von der Implementierung
der VM ab. Es gibt Implementierungen
- die das Scheduling der VM-Prozesse übernehmen
- die das Scheduling dem darunterliegenden BS überlassen
"scheduling strategies". Threads benötigen für ihre Ausführung einen Prozessor.
Viele Java-Anwendungen unterstützen nur einen Prozessor. Bei nur einem
Prozessor kann nur ein Thread aktiv sein. Der Thread wird dem Prozessor zugeteilt.
Man spricht in diesem Zusammenhang von Scheduling. Die Zuteilungsstrategie
beeinflusst das Verhalten des Systems bei mehreren Threads. Folgende Zustände
eines Thread können unterschieden werden:
- Initialzustand
Der Thread wurde erzeugt, führt aber die noch keine Methode aus, d.h. start() wurde noch nicht
aufgerufen.
- Lauffähig
Ein Thread geht vom Initialzustand mit start() in den Zustand "lauffähig" über.
- Aktiv
Ein Thread wird aktiv, wenn er von der VM aus der Menge der lauffähigen Threads ausgewählt wird.
In diesem Zustand führt der Thread tatsächlich Code aus. Der Thread wird von der VM dazu entweder
in den Zustand lauffähig zurückgesetzt oder er kommt in den Zustand "blockiert".
- Blockiert
Ein Thread ist blockiert, wenn er auf den exklusiven Zugriff auf eines bei synchronized
angegebenen Objekts oder auf das Ergebnis mit wait() wartet.
Ist der Thread blockiert, dann geht er in den Zustand "lauffähig" über, falls ein anderer Thread mit
einem notify() oder notifyAll() das Ergebnis meldet und daraufhin die VM des blockierten
Threads auswählt.
- Endzustand.
Der Thread ist in seinem Endzustand, falls run() ausgeführt wurde oder mit einer Ausnahme
beendet wurde.
105
Programmieren in Java
Auswahl durch VM
nach notify()
lauffähig
blockiert
notify(), notifyAll()
Initial
run()
Ende
sleep() oder
Auswahl durch VM
in synchronized()
aktiv
warten auf Zugriff
Ende von run() oder unbehandelte
Ausnahme
Abb.: Automatendarstellung der Übergänge zwischen den Zuständen eines Thread
start()
1.4.2.4.4 Synchronisation nebenläufiger Prozesse
Einführendes Beispiel. Mehrere Threads mit gemeinsamer Variablen.
public class OhneSynchroTest extends Thread
{
static int zaehler = 0;
public static void main(String args [])
{
Thread t1 = new OhneSynchroTest();
Thread t2 = new OhneSynchroTest();
t1.start();
t2.start();
}
static void schreibe()
{
System.out.print(zaehler + " ");
zaehler++;
if (zaehler > 25) System.exit(0);
}
public void run()
{
while (true) schreibe();
}
}
Die beiden Threads versuchen den Zählerstand zu inkrementieren. Ziel ist, dass beide gemeinsam
zaehler von 0 auf 25 hochzählen.
Auszüge aus einer möglichen Ausgabe:
Abb.: Mögliche Ausgabe vom nicht synchronisierten Programm OhneSynchroTest
Die Ausgabe von OhneSynchroTest ist nicht plausibel.
106
Programmieren in Java
Offensichtlich ist es notwendig, beide Threads zu koordinieren. Sie müssen so
synchronisiert werden, dass nur ein Thread das Prüfen der Bedingung und das
Inkrementieren atomar, d.h. vollständig ohne Unterbrechung ausführt.
Hinweis. Wie immer bei fehlender Synchronisation muß der im Beispiel gezeigte
Fehler nicht zwingend auftreten und kann lange Zeit völlig unbemerkt bleiben. Je
nachdem, wann die Threads aktiv sind bzw. bei einem Prozessor sich abwechseln,
ergeben sich unterschiedliche Resultate118.
Synchronisation paralleler Prozesse in Java. Mehrere Threads können nur über
gemeinsame Variable kommunizieren. Folgende Konflikte können dabei auftreten:
- Gleichzeitiges Lesen und Schreiben
Ein Thread liest und ein anderer ändert gerade das Objekt, ist aber noch nicht fertig.
- Gleichzeitiges Schreiben
Mehrere Threads versuchen das Objekt gleichzeitig zu ändern.
Rufen mehrere Threads Methoden gleichzeitig auf, dann kann ohne Synchronisation
ein Zwischenzustand einer Methode den Ablauf einer anderen Methode
beeinflussen. Zur Vermeidung von Konflikten gibt es mehrere Möglichkeiten:
- Schreiben nicht zulassen
Eine Klasse wird so definiert, dass eine Änderung der Attribute nach dem Erzeugen von Objekten
nicht mehr möglich ist (immutable class). Bei Java können bspw. mehrere Threads problemlos
auf ein gleiches Objekt der Klasse java.lang.String zugreifen, da ein String nicht mehr
geändert werden kann. Jede Methode der Klasse produziert bei einer Änderung des Werts ein
neues String Objekt.119
- Lesen und Schreiben synchronisieren
Für einen Bereich, in den gemeinsame Variable geschrieben oder gelesen werden, benötigt jeder
Thread eine Sperre bevor er diesen Bereich ausführt. Danach gibt er die Sperre wieder frei. Mit
diesem gegenseitigen Ausschluß (mutually exclusive lock) lassen sich Zugriffe auf Objekte so
koordinieren, dass Konflikte vermieden werden.
In Java müssen alle Bereiche, in dem gemeinsame Variable geschrieben oder
gelesen werden, explizit mit dem Schlüsselwort synchronized zur Vermeidung von
Konflikten versehen werden.
synchronized(O)
{
S
}
O ist ein beliebiger Ausdruck, der ein Java-Objekt liefert, und S eine Menge von Anweisungen.
Erreicht ein Thread die synchronized Anweisung, dann gelten folgende Regeln:
- Der Thread versucht exklusiven Zugriff auf das Objekt O zu erhalten.
- Wenn kein anderer Thread den exklusiven Zugriff auf das Objekt hat, geling dies.
- Besitzt ein anderer Thread den exklusiven Zugriff auf das Objekt, dann wartet der Thread, bis das
Objekt O wieder freigegeben wird. Es können mehrere Threads auf die Freigabe warten.
- Erhält ein Thread den exklusiven Zugriff auf O, dann tritt er in den Anweisungsblock ein.
- Verläßt der Thread den Anweisungsblock, in dem er den exklusiven Zugriff erhalten hat, dann gibt er
das Objekt wieder frei.
118
Dies macht es extrem schwierig in einem fertigen System fehlerhafte Code-Stellen zu finden. Vor allem kann
allein das Suchen des Fehlers mit Hilfe vom Debugger den Fehler verschwinden lassen. Durch das Beobachten
mit dem Debugger wird das Abwechseln von Threads beeinflusst.
119 Für eine performante Änderung eines String steht die Klasse java.lang.StringBuffer zur
Verfügung, diese ist aber nicht sicher für die gleichzeitige Verwendung durch mehrere Threads (thread safe).
107
Programmieren in Java
Wenn eine Ausnahme (exception) auftritt, die nicht innerhalb eines
Anweisungsblocks abgefangen wird, dann wird der Ausnahmeblock automatisch
verlassen und dabei auch die Sperre wieder freigegeben.
Verschachteln von synchronisierten Anweisungsblöcken ist möglich, auch wenn
mehrmals mit dem gleichen Objekt synchronisiert wird. Erst beim Verlassen des
äußeren Anweisungsblocks wird das Objekt freigegeben.
Kritischer Bereich. In das letzte Beispiel kann zum Synchronisieren das
Schlüsselwort synchronized eingesetzt werden. Prüfung und Inkrementierung
stellen einen kritischen Bereich dar. Das ist ein Bereich, in dem Konflikte auftreten
können.
Den kritischen Bereich kann man auch in einer eigenen Methode zusammen fassen.
public class SynchroTest extends Thread
{
static int zaehler = 0;
public static void main(String args [])
{
Thread t1 = new SynchroTest();
Thread t2 = new SynchroTest();
t1.start();
t2.start();
}
static synchronized void schreibe()
{
System.out.print(zaehler + " ");
zaehler++;
if (zaehler > 25) System.exit(0);
}
}
public void run()
{
while (true) schreibe();
}
Abb.: Mögliche Ausgabe von OhneSynchroTest bzw. SynchroTest
Es wird gewährleistet, dass nur ein Thread gleichzeitig in genau dieser Methode aktiv
sein kann. Es wird nicht gewährleistet, dass kein anderer Thread gleichzeitig in einer
anderen Methode aktiv ist, d. h. zaehler kann in einer anderen Methode weiterhin
gleichzeitig geändert werden. Das bedeudet auch:
"Lesende Methoden müssen synchronisiert werden".
Bsp.: Einführen der Methode getZaehler(), die den Zählerstand zurückliefert
static long getZaehler()
{
return zaehler;
}
108
Programmieren in Java
Die Methode getZaehler() kann aus folgenden Gründen ein fehlerhaftes Ergebnis haben:
- getZaehler() kann ausgeführt werden, während irgendeine andere auch synchronisierte
Methode (z.B. inkrementieren()) aus einer Subklasse heraus ausgeführt wird. Diese andere
Methode könnte zaehler zwischenzeitlich auf –1 setzen und erst am Ende einen korrekten Wert
zuweisen. Die Methode getZaehler() könnte zwischenzeitlich den unsinnigen Wert –1 liefern.
- Auch wenn keiner den Wert zwischenzeitlich auf –1 setzt, kann getZaehler() einen falschen Wert
liefern. Java garantiert, dass alle Operationen auf primitiven Typen atomar ausgeführt werden,
außer für double und long. Das bedeutet: Die Ausführung von ++zaehler könnte in zwei
Schritten ablaufen. Zwischen den beiden Schritten könnte ein Schrittwechsel auftreten. Der Wert
von zaehler nach dem 1. Schritt ist nicht spezifiziert.
Aus diesen Gründen muß getZaehler() synchronisiert werden:
static synchronized long getZaehler()
{
return zaehler;
}
Synchronisierte Methoden und Vererbung. Die Synchronisation gehört in Java nur
zur Implementierung und wird nicht vererbt. Daher kann auch eine InterfaceDefinition nicht das Schlüsselwort synchronized enthalten.
Synchronisation statischer Methoden. In Java gehört zu jeder Klasse eine, vom
System erzeugte Instanz der Klasse java.lang.Class. Diese Instanz beschreibt
den Aufbau der Klasse und macht dies zur Laufzeit verfügbar. Der Klassenname
gefogt von ".class" liefert die Instanz. Wenn eine statische Methode als
synchronized gekennzeichnet ist, entspricht dies einer Synchronisierung mit der
zu dieser Instanz gehörenden Instanz der Klasse java.lang.Class120.
Bsp.:
public class Zaehler
{
private static long maximum = 1000;
public static synchronized long getMaximum()
{
return maximum;
}
…
}
Die Methode getMaximum() entspricht folgender Implementierung:
public static long getMaximum()
{
synchronized(Zaehler.class)
{
return maximum;
}
}
Bewachte kritische Bereiche. Bisher konnte ein Thread nur auf eine einzige
Bedingung warten und zwar darauf, ob ein Zugriff zur Verfügung steht oder nicht.
Manchmal möchte man komplexere zustandabhängige Bedingungen formulieren, auf
deren Erfüllung man wartet. Das könnte man so beschreiben: await E then S
endwait, wobei E ein boolscher Ausdruck (Wächter) und S eine Menge von
120
vgl. 1.4.2.2
109
Programmieren in Java
Anweisungen ist. Sobald E erfüllt ist, wird S exklusiv von einem einzigen Thread
ausgeführt.
public class ClassMitGurdedSuspension
{
…
private synchronized void awaitConditionE()
{
while (!E)
{
try {
wait();
}
catch (InterruptedException ex) { }
}
}
public void synchronized guardedStatementS
{
awaitConditionE();
S
}
}
Die folgenden Methoden der Klasse Object übernehmen "Wächter"-Funktionen:
public final void wait()
throws InterruptedException
public final void wait(long timeout)
throws InterruptedException
public final void wait(long timeout,int nanos) throws InterruptedException
- Der aktuelle Thread wird auf wartend gesetzt und in einen mit dem Objekt verknüpften Wartebereich
gestellt
- Der exklusive Zugriff, den der Thread hat, wird freigegeben. Das ist Voraussetzung dafür, dass
andere Threads überhaupt notify() bzw. notifyAll() aufrufen können.
- Es existiert noch die Möglichkeit bei wait() die maximale Wartezeit anzugeben. Ist diese
verstrichen, dann wird automatisch für diesen Thread wie bei notify() verfahren.
public final void notify()
- Wenn wartende Threads existieren, dann wird nichtdeterministisch einer ausgewählt und dieser aus
dem Wartebereich entfernt
- Der ausgewählte Thread versucht Zugriff auf das Objekt zu bekommen. Der ausgewählte Thread
kann den Zugriff nur dann bekommen, wenn der Thread der notify() aufgerufen hat und noch im
Besitz des Zugriffs, diesen freigibt, und kein anderer Thread den Zugriff erhält.
- Erhält der ausgewählte Thread später den Zugriff, dann setzt er die Ausführung mit dem auf wait()
folgenden Code fort.
public final void notifyAll()
- Verläuft wie notify() mit dem Unterschied, daß alle wartenden Threads aus dem Wartebereich
entfernt werden und wieder versuchen die Sperre zu bekommen.
Nur wenn ein Thread den exklusiven Zugriff auf ein Objekt hat, kann er dessen
wait(), notify(), oder notifyAll() aufrufen. Ansonsten wird eine
IllegalMonitorStateException vom Laufzeitsystem erzeugt.
Wenn eine Unterbrechung während eines wait() passiert (z.B. durch Aufruf von
interrupt() der Klasse Thread, dann wird wie bei notify() verfahren. Die
Ausführung wird aber fortgefahren mit dem Werfen einer Ausnahme der Klasse
InterruptedException.
Das Ziel ist es mit wait() auf ein Ereignis zu warten. Das Ereignis wird durch das
Objekt oder einen Zustand des Objekts repräsentiert. Mit notify() wird dann das
Eintreten des Ereignisses mitgeteilt.
Das Monitor-Konzept in Java. Zur Synchronisation nebenläufiger Prozesse hat Java
das Konzept des Monitors implementiert. Ein Monitor ist die Kapselung eines
kritischen Bereichs (also eines Programmteils, der nur von jeweils einem Prozeß zur
110
Programmieren in Java
aktuellen Zeit durchlaufen werden darf) mit Hilfe einer automatisch gesetzten Sperre.
Die Sperre wird beim Eintritt in den Monitor gesetzt und beim Verlassen wieder
zurückgenommen. Ist sie beim Eintritt in den Monitor bereits von einem anderen
Prozeß gesetzt, dann muß der aktuelle Prozeß warten, bis der Konkurent die Sperre
freigegeben und den Monitor verlassen hat.
Das Monitor-Konzept wird mit Hilfe des Schlüsselworts synchronized realisiert.
Durch synchronized kann entweder eine komplette Methode oder ein Block
innerhalb einer Methode geschützt werden. Der Eintritt in den so deklarierten Monitor
wird als Sperre der this-Pointer verwendet, anderenfalls ist die Objektvariable
explizit anzugeben.
Neben dem Monitorkonzept stehen mit den Methoden wait() und notify() der
Klasse Object noch weitere Synchronisationsprimitive zur Verfügung. Zusätzlich zu
der bereits erwähnten Sperre, die einem Objekt zugeordnet ist, besitzt ein Objekt
eine Warteliste. Dabei handelt es sich um eine (möglicherweise leere) Menge von
Threads, die von einem Scheduler unterbrochen wurden und auf Ereignisse warten,
um fortgesetzt werden zu können.
wait() und notify() dürfen nur aufgerufen werden, wenn das Objekt bereits
gesperrt ist, also nur innerhalb eines synchronized-Blocks. Ein Aufruf von wait()
nimmt die bereits gewährten Sperren (temporär) zurück und stellt dem Prozeß, der
den Aufruf von wait() verursachte, in die Warteliste des Objekts. Dadurch wird er
unterbrochen. Ein Aufruf von notify() entfernt einen (beliebigen) Prozeß aus der
Warteliste des Objekts, stellt die (temporär) aufgehobenen Sperren wieder her und
führt ihn dem normalen Scheduling zu. „wait()“ und „notify()“ sind damit für
elementare Synchronisationsaufgaben geeignet, bei denen es weniger auf die
Kommunikation als auf die Steuerung zeitlicher Abläufe ankommt . Zusätzlich bietet
die Klasse Object auch noch die Methode notifyAll(). Der Unterschied zu
notify() ist, dass alle wartenden Threads „aufgeweckt“ (reaktiviert werden, bei
notify() nur ein einziger.
Threadkommunikation
Verschiedene Threads können auf verschiedenen
austauschen: Streams, gemeinsam genutzte Objekte,
beiden zuletzt angegebenen Alternativen basieren darauf,
einen gemeinsamen Adressraum (Heap) teilen. Somit
Objekte diesselben Objekte im Speicher ansprechen.
111
Wegen effizient Daten
statische Variablen. Die
dass verschieden Threads
können mehrere Thread-
Programmieren in Java
1.4.2.5 Die Klassen String und StringBuffer
Ein String ist eine Sammluung von Zeichen, die im Speicher geordnet abgelegt
werden. Die Zeichen setzen sich aus einem Zeichensatz zusammen, der in Java
dem Unicode-Standard entspricht. In Java ist eine Symbiose zwischen String als
Objekt und String als eingebautem Datentyp vorgenommen worden. Möglich ist eine
Zuweisung von String-Literalen an Objekte und die Verknüpfung mehrerer Strings zu
einem.
1.4.2.5.1 Die Klasse String
Die Klasse String repräsentiert Zeichenketten, die sich nicht ändern.
Konstruktoren:
String()
String(String wert)
String(char[] wert)
Erzeugt ein leeres String-Objekt
Erzeugt einen neuen String durch Duplizierung eines bereits
vorhandenen.
Erzeugt einen neuen String aus einem vorhandenen ZeichenArray.
Zeichenextraktion
char charAt(int index)
String substring(int
anfang, int ende)
String trim()
Liefert das Zeichen an Position index. Dabei hat das erste
Element eines String den Wert 0 und das letzte den Index
„length()-1“. Falls der String kürzer als „index + 1“ ist, wird eine
Ausnahme des Typs „IndexOutOfBoundsException“ erzeugt
Liefert den Teilstring, der an Position anfang beginnt und an
Position ende endet. Wie bei allen Zugriffen über einen
numerischen Index beginnt hier das Zählen bei 0. „ende“
verweist auf das erste Zeichen hinter dem zu extrahierenden
Teilstring. Der Rückgabewert ist also die Zeichenkette, die von
Indexposition anfang bis zur Indexpostion „ende –1“ reicht
Liefert den String, der entsteht, wenn auf beiden Seiten der
Zeichenkette jeweils alle zusammenhängenden Leerzeichen
entfernt werden. Dabei werden alle Zeichen, die einen Code
kleiner 32 haben, als Leerzeichen angesehen. „trim“ entfernt
immer Leerzeichen auf beiden Seiten. Da die Klasse String als
final definiert wurde, gibt es keine Möglichkeit, entsprechende
Methoden nachzurüsten, z.B. für das rechtsbündige oder
linksbündige Entfernen von Leerzeichen.
Länge der Zeichenkette: int length() liefert die aktuelle Länge des StringObjekts. Ist der Rückgabewert 0, dann ist der String leer. Wird ein Wert „n“ größer 0
zurückgegeben, dann enthält der String „n“ Zeichen, die an den Indexpositionen 0 bis
„n-1“ liegen.
Vergleichen von Zeichenketten:
boolean equals(Object
einObjekt)
Liefert true, wenn das aktuelle Objekt und einObjekt
identisch sind. „equals“ testet auf inhaltlliche Gleichheit und
nicht darauf, ob beide Strings dasselbe Objekt referenzieren.
Neben „equals“ gibt es die Methode equalsIgnoreCase, die
evtl. vorhandene Unterschiede in der Groß- und
Kleinschreibung beider Zeichenketten ignoriert.
112
Programmieren in Java
boolean
startsWith(String s)
Testet, ob das String-Objekt mit der Zeichenkette s beginnt.
Ist dies der Fall, dann gibt die Methode „true“ zurück,
anderenfalls „false“.
boolean endsWith(String
s)
int compareTo(String s)
boolean regionMatches(int
toffset, String other, int
ooffset, int len)
Führt einen lexikalischen Vergleich der beiden Strings durch.
Bei einem lexikalischen Vergleich werden die Zeichen
paarweise von links nach rechts verglichen. Tritt ein
Unterschied auf oder ist einer der beiden Strings beendet,
wird das Ergebnis ermittelt. Ist das aktuelle String-Objekt
kleiner als s, wird ein negativer Wert zurückgegeben. Ist er
größer, wird ein positiver Wert zurückgegeben. Bei Gleichheit
liefert die Methode den Rückgabewert 0.
Testet, ob 2 String-Abschnitte gleich sind.
Suchen in Zeichenketten
int indexOf(String s)
int IndexOf(String s, int
vonIndex)
int lastIndexOf(String s)
Sucht das erste Vorkommen der Zeichenkette s
innerhalb des String-Objekts. Wird s gefunden, liefert
die
Methode
den
Index
des
ersten
übereinstimmenden Zeichens zurück, andernfalls wird
–1 zurückgegeben.
Die Methode gibt es auch in einer anderen Version,
die einen Parameter vom Typ char akzeptiert. In
diesem Fall sucht sie nach dem Auftreten des ersten
angegebenen Zeichens.
Diese Methode arbeitet wie die vorige, beginnt mit der
Suche aber erst ab Position vonIndex. Wird s
beginnend ab dieser Position gefunden, liefert die
Methode den Index des ersten übereinstimmenden
Zeichens, andernfalls –1.
Eine Variante dieser Methode erwartet anstelle eines
String-Parameters ein Argument vom Typ char.
Sucht nach dem letzten Vorkommen des Teilstrings s
im aktuellen String-Objekt- Wird „s“ gefunden, liefert
die
Methode
den
Index
des
ersten
übereinstimmenden Zeichens, andernfalls –1. Eine
Variante dieser Methode erwartet ein Argument des
Typs char.
Ersetzen von Zeichenketten
String toLowerCase()
String toUpperCase()
String replace(char
altesZeichen, char
neuesZeichen)
Liefert den String zurück, der entsteht, wenn alle Zeichen in
Kleinbuchstaben umgewandelt werden. Besitzt der String keine
umwandelbaren Zeichen, dann wird der Original-string
zurückgegeben.
Liefert den String zurück, der entsteht, wenn alle Zeichen in
Großbuchstaben umgewandelt werden. Besitzt der String keine
umwandelbaren Zeichen, wird der Original-String zurückgegeben.
Diese Methode führt eine zeichenweise Konvertierung des
aktuellen
String-Objekts
durch.
Jedes
Auftreten
von
altesZeichen wird durch neuesZeichen ersetzt. Es gibt in
Java keine Methode, die das Ersetzen von Teilstrings durch
andere Teilstrings (von möglicherweise unterschiedlicher Länge)
ermöglicht. „replace“ ist das einzige Mittel für Ersetzungen.
Konvertierungsfunktionen:
static String valueOf(boolean b)
113
Programmieren in Java
static
static
static
static
static
String
String
String
String
String
valueOf(char z)
valueOf(int i)
valueOf(long l)
valueOf(float f)
valueOf(double d)
Die „valueOf“-Methoden sind als Klassenmethoden implementiert und können ohne
String-Objekt aufgerufen werden. Da sie in der Regel zur Erzeugung von Strings
verwendet werden, ist das sinnvoll. Ein Aufruf von „valueOf“ wandelt ein primitives
Objekt mit Hilfe der Methode „toString()“, die von der zugehörigen WrapperKlassen bereitgestellt wird, in eine Zeichenkette um.
Formatieren mit format(). Die Klasse final class java.lang.String implements
CharSequence, Comparable<String>, Serializable stellt mit der statischen Funktion
format() eine Methode bereit, Zeichenketten nach einer Vorlage zu formatieren
static String format(Locale l, String format, Object ... args)
liefert einen formatierten String, der aus der gewünschten Sprache, dem String und Argumenten
hervorgeht.
static String format(String format,Object ... args)
liefert einen formatierten String, der aus dem format-String und den Argumenten hervorgeht.
Der String format nennt sich Format-String. Er enthält neben dem auszugebenden
Zeichen sogenannte Format-Spezifierer, die dem Formatierer darüber Auskunft
geben, wie das Argument formatiert werden soll. "%s" steht für eine unformatierte
Ausgabe eines Strings.
Weitere Eigenschaften: Die Klasse String ist final. Von einer String-Klasse können
keine neuen Klassen abgeleitet werden.
String-Objekte sind nicht dynamisch. Es werden durch die Klasse String keine
dynamischen Zeichenketten implementiert. Nach der Initialisierung eines String bleibt
dessen Länge konstant, z.B.:
String s = "Hallo Welt";
s = substring(0,5);
Die „substring“-Methode erzeugt eine Kopie, die mit dem gewünschten Inhalt gefüllt
wird. Diese gibt sie an den Aufrufer zurück, der das Ergebnis erneut s zuweist und
damit die Originalinstanz für den Garbage Collector freigibt.
Bsp.: Anwendung von Methoden der String-Klasse
public class PruefeString
{
public static void main(String args[])
{
String str = "Java ist die beste Programmiersprache, " +
"die Studenten der Informatik erlernen sollten.";
System.out.println("Der String ist: " + str);
System.out.println("Laenge des String: " + str.length());
System.out.println("Das Zeichen an Position 7: " + str.charAt(7));
System.out.println("Der Substring von 24 bis 31: " +
str.substring(24,31));
System.out.println("Der Index zum Zeichen I: " + str.indexOf('I'));
System.out.println("Der Index zum Anfang von "
+ "Substring \"beste\": " + str.indexOf("beste"));
System.out.println("Der String in Großbuchstaben: " + str.toUpperCase());
}
}
114
Programmieren in Java
115
Programmieren in Java
1.4.2.5.2 Die Klasse StringBuffer
Die Klasse StringBuffer dient zur Implementierung veränderlicher Zeichenketten.
Die Methoden der Klasse StringBuffer dienen hauptsächlich dem Anhängen und
Einfügen von Texten. Dabei wird der benötigte Speicherplatz automatisch der Größe
angepaßt.
Konstruktoren:
StringBuffer()
StringBuffer(String s)
Erzeugt einen leeren StringBuffer
Erzeugt ein neues StringBuffer-Objekt,
Zeichenkette s ist.
das
eine
Kopie
der
Einfügen von Elementen
StringBuffer append(String s)
Hängt String s an das Ende des StringBuffer-Objekts.
Zurückgegeben wird das verlängerte StringBufferObjekt. Zusätzlich gibt es die Methode append in
Varianten für das Anhängen aller Arten von primitiven
Typen. Anstelle eines String-Objekts wird hier der
entsprechende primitive Typ übergeben, in einen String
konvertiert und an das Ende des Objekts angehängt.
StringBuffer insert(int offset, String s) Fügt den String s an die Position offset in den
aktuellen StringBuffer ein. Zurückgegeben wuird das
verlängerte StringBuffer-Objekt. Auch diese Methode
gibt es für primitive Typen. Der anstelle eines String
übergebene Wert wird zunächst in einen String
konvertiert und dann an die gewünschte Stelle
eingefügt.
Verändern von Elementen: „void setCharAt(int index, char c) throws
IndexOutOfBoundsException“. Das an Position index stehende Zeichen wird
durch c ersetzt. Falls StringBuffer zu kurz ist, löst die Methode eine Ausnahme
des Typs IndexOutOfBoundsException aus.
Länge eines String-Objekts: int length() liefert die Länge des Objekts (Anzahl
der Zeichen, die zum Zeitpunkt des Aufrufs in dem StringBuffer enthalten sind).
Konvertieren in einen String: String toString(). Nachdem die Konstruktion
eines StringBuffer-Objekts abgeschlossen ist, kann es mit Hilfe dieser Methode
in einen String verwandelt werden. Die Methode legt dabei keine Kopie des
StringBuffer-Objekts an, sondern liefert einen Zeiger auf den internen Zeichenpuffer.
Erst wenn der StringBuffer erneut verändert werden soll, wird tatsächlich eine Kopie
erzeugt.
116
Programmieren in Java
1.4.2.6 Die Math-Klasse
Die Math-Klasse enthält nützliche numerische Konstanten und Funktionen:
1. Die Funktionen „min“ und „max“.
2. Der absolute Betrag
3. Zufallszahlen
Die Klasse Math unterstützt die „random“-Methode: public static double random(). Die
„random“-Methode gibt eine Zahl zwischen 0.0 und 1.0 aus.
4. Runden
Runden einer Zahl vom Typ float: public static int round(float a). Gerundet wird auf
die nächste ganze Zahl (z.B. 5.4 auf 5, 5.5. auf 6)
Runden einer Zahl vom Typ double: public static long round(double a)
Abrunden: public static double floor(double a). Math.floor rundet immer ab, z.B.
ergibt Math.floor(4.99) die Zahl 4.0.
Aufrunden: public static double ceil(double a). ceil runder immer auf, z.B.:
Math.ceil(4.01) ergibt 5.0.
Runden auf die nächste ganze Zahl: public static double rint(double a).
5. Exponenten und Logarithmen
x
Bestimmen des Expoentialwerts e : public static double exp(double x)
Bestimmen des natürlichen algorithmus ln(x ) : public static double log(double x)
6. Trigonometrische Funktionen
public static double sin(double winkel)
public static double cos(double winkel)
Der Wert von winkel wird im Bogenmaß angegeben.
7. Potenzen, Quadratwurzel
y
Bestimmen vom Wert der Potenz x : public static double pow(double x, double y)
Bestimmen der Quadratwurzel von x: public static double sqrt(double x)
8. Mathematische Konstanten
public static final double E;
public static final double PI;
117
Programmieren in Java
1.4.2.7 Object-Wrapper-Klassen
Die Object-Wrapper-Klassen sind Wrapper für primitive Typen. Zusätzlich besitzen
sie Methoden zur Umwandlung von Zeichenketten in die verschiedenen Datentypen.
Boolean
Character
public static final Boolean TRUE
public static final Boolean FALSE
public static final char MIN_VALUE
public staic final char MAX_VALUE
public boolean booleanValue()
public static Boolean valueOf(String s)
public static boolean getBoolean(String name)
public char charValue()
public Character(char value)
public int hashCode()
public boolean equals(Object obj)
public String toString()
public static boolean isLowerCase(char ch)
public static boolean isUpperCase(char ch)
public static boolean isDigit(char ch)
public static boolean isLetter(char ch)
public static boolean isLetterOrDigit(char ch)
public static boolean isJavaLetter(char ch)
public static boolean isJavaLetterOrDigit(char c)
public static boolean isSpace(char ch)
public static boolean isTitleCase(char ch)
public static char toLowerCase(char ch)
public static char toUpperCase(char ch)
public static int digit(char ch, int radix)
public static int getNumericValue(char ch)
Number
{ abstract }
public abstract int intValue()
public abstract long longValue()
public abstract float floatValue()
public abstract double doubleValue()
public byte byteValue()
public short shortValue()
Byte
Short
Integer
Long
Abb.: Wrapper-Klassen
118
Float
Double
Programmieren in Java
Die Klasse Character
Konstruktor:
Ermitteln vom „char“-Wert: public char charValue()
Klassifizieren von Zeichen:
Methode
isDigit
isLetter
isLetterOrDigit
isLowerCase
isUpperCase
isJavaLetter
isJavaLetterOrDigit
isSpace
isTitleCase
Beschreibung
Eine numerische Zahl zwischen 0 und 9
Ein Buchstabe des Alphabets
Ein alphabetischer Buchstabe oder eine numerische Zahl
Ein großgeschriebener alphabetischer Buchstabe
Ein Buchstabe, ‚$‘ oder ‚_‘
Ein Buchstabe, eine Zahl, $ oder _
Eine Leerstelle, eine neue Zeile, ein Return, ein Tab oder ein
Formularvorschub
Spezielle zweibuchstabige groß- und kleingeschriebene Buchstaben
Jede dieser Klassifikationsmerkmale gibt „true“ zurück, wenn das betreffende
Zeichen zur Klassifikation gehört.
Groß- und kleingeschriebene Variante eines Buchstabens:
public static char toUppercase(char zeichen)
public static char toLowerCase(char zeichen)
Konvertierungsmethoden zur Umwandlung von Zahlen in Zeichenketten und
Zeichenketten in Zahlen:
public static int digit(char zeichen, int radix)
gibt den numerischen Wert eines Zeichens der angegebenen Zahlendarstellung
(„radix“)121 wieder. Falls ein Zeichen keinem Wert in der angegebenen
Zahlendarstellung entspricht, wird „-1“ zurückgegeben.
public static char forDigit(int digit, int radix)
Konvertiert einen numerischen Wert in ein Zeichen, das diesen Wert in einer
bestimmten Zahlenbasis darstellt, z.B. Character.forDigit(6,8) ergibt 6 und
Character.forDigit(12,16) ergibt "c“.
Die Klasse Boolean
Diese Klasse ist der Objekt-Wrapper für den primitiven Typ boolean.
Konstruktoren:
public Boolean(boolean wert)
public Boolean(String str)
Beschaffung eines booleschen Werts, der in einem Objekt abgelegt ist:
public boolean booleanValue()
Objekt-Wrapper-Varianten von true und false
public final static Boolean FALSE;
public final static Boolean TRUE;
Die Klasse Number
121 z.B. 10 für dezimal, 8 für oktal, 16 für hexadezimal. Zahlenbasis kann Werte von 2
(Character.MIN_RADIX) bir 32 (Character.MAX_RADIX) umfassen.
119
Programmieren in Java
Die Objekt-Wrapper der Typen int, long, float und double sind Subklassen der
abstrakten Klasse Number. Vier Methoden sind für die Konvertierung einer Zahl in
einen bestimmten primitiven Typ zuständig:
public
public
public
public
abstract
abstract
abstract
abstract
int intValue();
long longValue();
float floatValue();
double doubleValue()
1. Die Klasse Integer
Konstruktoren:
public Integer(int wert)
public Integer(String s) throws NumberFormatException
Die Zahlenbasis (Radix) ist 10. Falls die Zeichenkette nicht numerische Zeichen
enthält, wird die Ausnahme NumberFormatException ausgeworfen.
Umwandeln Zeichenkette in ganze Zahlen:
public
public
public
public
static
static
static
static
int parseInt(String s)
int parseInt(String s,
Integer valueOf(String
Integer valueOf(String
throws NumberFormatException
int radix) throws NumberFormatException
s) throws NumberFormatException
s, int radix) throws NumberFormatException
Der Unterschied zwischen parseInt und valueOf liegt darin: parseInt gibt ein
„int“ zurück, valueOf ein Integer.
Konvertieren einer ganzen Zahl in eine Zeichenkette:
public static String toString(int i)
public static String toString(int i, int radix)
Die beiden Methoden dürfen nicht mit der Instanz-Methode toString verwechselt
werden, die für alle Subklassen von Objekt definiert ist.
Die Konstanten Integer.Min_VALUE und Integer.MAX_VALUE:
public final static int MIN_VALUE
public final static int MAX_VALUE
2. Die Klasse Long
Sie ist weitgehend mit der Integer-Klasse identisch. Allerdings dient sie als Wrapper
für long-Werte.
3. Die Klasse Float
Sie enthält einen Objekt-Wrapper für den Datentyp float.
Konstruktoren:
public Float(float wert)
public Float(double wert)
public Float(String s) throws NumberFormatException
Konvertierung von bzw. zu Zeichenketten:
public static Float valueOf(String s) throws NumberFormatException
public static String toString(float f)
Überprüfen auf „Unendlich“ bzw. auf „keine Zahl“:
public static boolean isInfinite(float f)
public static boolean isNaN(float f)
// Instanz-Variante
public boolean isNaN()
// Instanz-Variante
Konstanten:
public
public
public
public
final
final
final
final
static
static
static
static
float
float
float
float
MIN_VALUE
MAX_VALUE
NEGATIVE_INFINITY
NaN
120
Programmieren in Java
4. Die Klasse Double
Sie hat dieselbe Funktionalität wie die Float-Klasse.
1.4.3 Ausnahmen und Ausnahmenbehandlung
Ausnahmen
Eine Ausnahme (Exception) ist eine unerwünschte Erscheinung. Eine derartige
Situation (z.B. Division durch Null) ist in einem Programm gegebenenfalls durch eine
Fehlerbehandlungsroutine aufzufangen.
Es gibt unterschiedliche Möglichkeiten zur Fehlerbehandlung:
- die individuelle programmierte Fehlerbehandlung, z.B.:
public int dividiere()
{
// ganzzahlige Division
if (zweiterOperand == 0)
{
System.out.println("Fehler: Division durch Null");
System.out.println("Divisor wird auf 1 gesetzt!");
zweiterOperand = 1;
}
return ersterOperand / zweiterOperand;
}
- die von Java bereitgestellten Fehlerbehandlungen
Ausnahmen sind in Java Objekte, die erzeugt werden, wenn eine
Ausnahmebedingung vorliegt. Diese Objekte werden von der Methode an das
aufrufende Programm zurückgegeben und müssen behandelt werden. Das
Behandeln von Ausnahmen wird als Auffangen bezeichnet. Das Auffangen wird
durch Plazieren von Anweisungen, die Ausnahmen erzeugen können, in eine trycatch Block erreicht, z.B.122:
public int dividiere()
{
// ganzzahlige Division
try {
/* Innerhalb des try-Blocks werden diejenigen
kritischen Aktionen durchgefuehrt, die
Ausnahmen erzeugen koennen
*/
return (ersterOperand / zweiterOperand);
}
catch(java.lang.ArithmeticException a)
{
System.out.println(" Fehler: Division durch Null");
System.out.println("Divisor wird auf 1 gesetzt!");
zweiterOperand = 1;
return (ersterOperand / zweiterOperand);
}
}
122
pr14103
121
Programmieren in Java
In der catch-Klausel wird die Art der aufzufangenden Ausnahme definiert. Dort ist
ein formaler Parameter angegeben, der beim Auftreten der Ausnahme ein
Fehlerobjekt übernehmen soll. Fehlerobjekte sind Instanzen der Klasse Throwable
(oder eine ihrer Unterklassen). Sie werden vom Aufrufer der Ausnahme erzeugt und
als Parameter an die catch-Klausel übergeben. Das Fehlerobjekt enthält
Informationen über die Art der aufgetretenen Ausnahme. Es kann dort eine der
zahlreichen Standardausnahmen von Java stehen oder auch selbstdefinierte
Ausnahmen. Das Ausnahme- / Fehlerobjekt besitzt einige nützliche Methoden, z.B.
public String getMessage()123. Diese Methode gibt Fehlermeldungen zurück.
public void printStackTrace(). Diese Methode druckt einen Auszug aus
dem Laufzeit-Stack.
Am Ende eines try-Blocks können beliebig viele „catch“-Klauseln stehen, so daß
unterschiedliche Arten von Ausnahmen behandelt werden können.
Mit Hilfe der finally-Klausel (letzter Bestandteil einer try-catch-Anweisung)
kann ein Programmfragment definiert werden, das immer ausgeführt wird, wenn die
zugehörige try-Klausel betreten wurde.
Bsp.: Rückgabe von „Resourcen“ in der finally-Klausel
In dem folgenden Programm stellt die finally-Klausel sicher, daß der Schalter
tatsächlich nach dem Ende der Bearbeitung im try-catch-Block ausgeschaltet ist.
class Schalter
{
boolean zustand = false;
boolean lies() { return zustand; }
void an() { zustand = true; }
void aus() { zustand = false; }
}
public class AnAusSchalter
{
static Schalter schalter = new Schalter();
public static void main(String args[])
{
try
{
schalter.an();
// hier kann sich Quellcode befinden, der
// Ausnahmen ausloest
}
catch(NullPointerException a)
{
System.out.println("NullPointerException");
}
catch(IllegalArgumentException a)
{
System.out.println("IllegalArgumentException");
}
finally
{
schalter.aus();
}
}
}
123
Sie ist in der Klasse Throwable definiert und daher allen Exception-Objekten zugänglich.
122
Programmieren in Java
Globales Exception-Handling
Java realisiert das globale Handling von Ausnahmen über die optionale Erweiterung
einer Methodendeklaration mit der „throws“-Klausel, z.B.:
public class MeineAusnahmenKlasse
{
public void eineAusnahmenMethode() throws MeineAusnahme
{
.........
}
}
Hier wird dem Compiler mitgeteilt, daß der vorliegende Code eine Ausnahme
(„MeineAusnahme“) erzeugen könnte.
Falls in der mit „throws“ gekennzeichneten Methode eine Ausnahmesituation
auftritt, dann wird die aufrufende Methode nach einer Ausnahmebehandlung
durchsucht. Enthält die aufrufende Methode eine Ausnahmebehandlungsmethode,
wird mit dieser die Ausnahme bearbeitet. Ist keine Routine vorhanden, wird deren
aufrufende Methode durchsucht und so fort. Die Ausnahme wird von der
Hierarchieebene, wo sie aufgetreten ist, jeweils eine Hierarchieebene weiter nach
oben gereicht. Wird die Ausnahme nirgends aufgefangen, dann bricht der JavaInterpreter normalerweise die Ausführung des Programms ab.
Das Auslösen einer Ausnahme wird im Java-Sprachgebrauch als „throwing“
bezeichnet, das Behandeln einer Ausnahme wird „catching“ genannt.
Das Grundprinzip des Exception-Mechanismus:
- Ein Laufzeitfehler oder eine vom Entwickler gewollte Bedingung löst eine Ausnahme aus.
- Diese kann entweder vom Programmteil, in dem sie angelegt wurde, behandelt werden, oder sie kann
weitergegeben werden.
- Wird die Ausnahme weitergegeben, so hat der Empfänger erneut die Möglichkeit sie entweder zu behandeln
oder selbst weiterzugeben.
- Wird die Ausnahme von keinem Programmteil behandelt, so führt sie zum Abbruch eines Programms, und zur
Ausgabe einer Fehlermeldung.
Auswertungen, die Ausnahmen auslösen, können entweder direkt in eine tryKlausel mit optionalen catch-Anweisungen (Behandlung vor Ort) eingefaßt oder
über die throws-Klausel an den übergeordneten Programmblock weitergeleitet
werden. Falls weder die „Behandlung vor Ort“ noch eine throws-Klausel verwendet
wird, liefert der Compiler bereits bei der Übersetzung eine Fehlermeldung. Eine
Ausnahme bildet die Klasse RuntimeException einschl. ihrer Subklassen. Für sie
ist weder die Fehlerbehandlung noch Weiterleitung mit der throws-Klausel
notwendig.
Die Fehlerklassen von Java
Zur Behandlung von Fehlern und Ausnahmen besitzt die Klasse Throwable zwei
Subklassen: die Klassen Exception und Error. In beiden Ausnahmeklassen sind
die wichtigsten Ausnahmen und Fehler der Java-Laufzeitbibliothek bereits enthalten.
Ausnahmen der Klasse Error und ihrer Klasse RuntimeError (sowie
RuntimeException) müssen nicht extra abgefangen werden. „Errors“ sind
systembedingt, „Exceptions“ dagegen programmbedingt und müssen behandelt
123
Programmieren in Java
werden. Es gibt fünf Typen von Ausnahmen, die in einer throws-Klausel aufgelistet
werden müssen:
ClassNotFoundException
IllegalAccessException
InstantiationException
InterruptedException
NoSuchMethodException
Hinzu kommen verschiedene weitere Ausnahmen aus den Java-Paketen. So gibt es
spezielle Fehlerklassen für die Ein- und Ausgabe, die Netzwerkkommunikation oder
den Zugriff auf Datenfelder. Teilweise sind sie der Klasse „Error“ (oder ihrer
Subklasse RuntimeError) zugeordnet und müssen nicht extra abgefangen und
dokumentiert werden. Sind Ausnahmen der Klasse Exception (Ausnahme:
RuntimeException) zugeordnet, dann müssen sie behandelt werden (z.B. alle
Ausnahmen von java.io).
Die Klasse RuntimeException ist die Superklasse für die Behandlung aller
Laufzeitfehler, die behandelt werden können aber nicht müssen (die Entscheidung
liegt beim Programmierer).
UnknownError
VirtualMachineError
OutOfMemoryError
InternalError
Error
AWTError
LinkageError
Object
Throwable
ClassNotFoundException
Exception
RuntimeException
EOFException
IOException
FileNotFoundException
InterruptedException
UTFDataFormatException
Abb.: Fehlerklassenhierarchie
Auslösen von Ausnahmen
Java verfügt in seinen Standardbibliotheken über zahlreiche vorgefertigte
Ausnahmen für fast alle Standardsitiuationen (z.B. Dateioperationen auf der Basis
der IO-Exception). Selbstdefinierte Ausnahmen benötigen eine Subklasse von
Exception. Das Auslösen dieser selbstdefinierten Ausnahmen erfolgt über die throwAnweisung: throw AusnahmeObjekt.
Die Behandlung selbstausgelöseter Ausnahmen erfolgt nach den üblichen Regeln:
124
Programmieren in Java
- Suche nach einem Ausnahmen-Handler in den umgebenden Blöcken
- Bei erfolgloser Suche Weitergabe des Fehlers an den Aufrufer.
Wird die Ausnahme nicht innerhalb derselben Methode behandelt, dann ist sie mit
Hilfe der "throws"-Klausel zu deklarieren und „weiter oben“ in der Aufrufkette zu
behandeln.
Bsp.:
public class AusnahmeMethoden
{
public static void main(String args[])
{
try
{
throw new Exception("Hier ist meine Ausnahme");
}
catch (Exception a)
{
System.out.println("Eingefangene Ausnahme");
System.out.println("a.getMessage(): " + a.getMessage());
System.out.println("a.toString(): " + a.toString());
System.out.println("a.printStackTrace(): ");
a.printStackTrace();
}
}
}
/* Ausgabe:
Eingefangene Ausnahme
a.getMessage(): Hier ist meine Ausnahme
a.toString(): java.lang.Exception: Hier ist meine Ausnahme
a.printStackTrace():
java.lang.Exception: Hier ist meine Ausnahme
at AusnahmeMethoden.main(AusnahmeMethoden.java)
*/
Die throw-Anweisung besitzt Merkmale einer Sprunganweisung. Sie unterbricht das
Programm und verzweigt unmittelbar zur umgebenden catch-Klausel. Gibt es keine
catch-Klausel, wird der Fehler weitergegeben. Tritt die Ausnahme innerhalb einer
try-catch-Anweisung mit einer finally-Klausel auf, wird diese noch vor der
Weitergabe ausgeführt.
Auch das „Wiederauswerfen“ einer Ausnahme ist möglich, insbesondere dann, falls
eine Ausnahme eingefangen wurde, sie aber in einem größeren Zusammenhang
behandelt werden soll
catch (Exception a)
{
System.out.println("Eine Ausnahme wurde eingefangen");
throw a;
}
Eigene, d.h. benutzerdefinierte Ausnahmen können direkt von der Klasse
Exception abgeleitet werden, z.B.124:
class MeineAusnahme extends Exception
{
private int i;
public MeineAusnahme() {}
public MeineAusnahme(String nachricht)
124
Vgl. pr14140
125
Programmieren in Java
{
super(nachricht);
}
public MeineAusnahme(String nachricht, int x)
{
super(nachricht);
i = x;
}
public int wert() { return i; }
}
public class AuswurfMeineAusnahme
{
public static void f() throws MeineAusnahme
{
System.out.println("Auswurf von MeineAusnahme aus f()");
throw new MeineAusnahme();
}
public static void g() throws MeineAusnahme
{
System.out.println("Auswurf von MeineAusnahme aus g()");
throw new MeineAusnahme("Ursprung in g()");
}
public static void h() throws MeineAusnahme
{
System.out.println("Auswurf von MeineAusnahme aus h()");
throw new MeineAusnahme("Ursprung in h()",13);
}
public static void main(String args[])
{
try { f(); }
catch (MeineAusnahme a)
{
a.printStackTrace();
}
try { g(); }
catch (MeineAusnahme a)
{ a.printStackTrace(); }
try { h(); }
catch (MeineAusnahme a)
{
a.printStackTrace();
System.out.println("a.wert() = " + a.wert());
}
}
}
/* Ausgabe:
Auswurf vom MeineAusnahme aus f()
MeineAusnahme
at java.lang.Throwable.<init>(Compiled Code)
at java.lang.Exception.<init>(Compiled Code)
at MeineAusnahme.<init>(AuswurfMeineAusnahme.java:4)
at AuswurMeineAusnahme.f(Vererbung.java:21)
at AuswurfmeineAusnahme.main(Vererbung.java:36)
Auswurf von MeineAusnahme aus g()
MeineAusnahme: Ursprung in g()
at java.lang.Throwable.<init>(Compiled Code)
at java.lang.Exception.<init>(Compiled Code)
at MeineAusnahme.<init>(AuswurfMeineAusnahme.java:7)
at AuswurfMeineAusnahme.g(AuswurfMeineAusnahme.java:26)
at AuswurfMeineAusnahme.main(AuswurfMeineAusnahme.java:43)
Auswurf von MeineAusnahme aus h()
MeineAusnahme: Ursprung in h()
at java.lang.Throwable.<init>(Compiled Code)
at java.lang.Exception.<init>(Compiled Code)
at MeineAusnahme.<init>(AuswurfMeineAusnahme.java:11)
at AuswurfMeineAusnahme.g(AuswurfMeineAusnahme.java:31)
126
Programmieren in Java
at AuswurfMeineAusnahme.main(AuswurfMeineAusnahme.java:50)
a.wert() = 13
*/
127
Programmieren in Java
1.4.4 Ein-, Ausgaben
Quelle
Eingabestrom
Programm write()
read()
Ausgabestrom
Programm
Senke
Abb.: Input und Output Stream
Einlesen von Daten aus einer externen Quelle (source) oder Ausgabe zu einer
externen Senke (sink).
Daten können aus verschiedenen Quellen stammen: Tastatur, Datei, Speicher,
Internet, anderes Programm.
Daten können von verschiedener Art sein: Zeichen, Objekte, Bilder, ....
Daten können zu verschiedenen Ausgabeeinheiten gehen: Bildschirm, Datei,
Drucker, Speicher, Internet, anderes Programm.
Daten werden generell sequentiell gelesen oder geschrieben
1.4.4.1 Ein- und Ausgabeströme der Klasse System
Die Klasse System enthält drei Datenströme:
public static InputStream125 in
public static PrintStream126 out
public static PrintStream err
// Standardeingabe
// Standardausgabe
// Standardfehlerausgabe
Diese Datenströme dienen zum Lesen bzw. Schreiben in das Fenster von dem die
Anwendung gestartet wurde. Die Verwendung des Stroms System.in innerhalb von
Applets ist aber nicht sinnvoll, weil die verschieden Browser diesen Datenstrom
unterschiedlich behandeln. Die Ströme System.out und System.err sendet
Netscape an das Fenster der Java-Konsole, der Appletviewer gibt sie in das Fenster
weiter, aus dem der Appletviewer gestartet wurde,
Einfache Ausgaben auf das Standard-Ausgabegerät
Mit Hilfe des Kommandos „System.out.println“, das als einziges Argument eine
Zeichenkette erwartet, können einfache Ausgaben auf den Bildschirm geschrieben
werden. Mit Hilfe des Plus-Operators „+“ können Zeichenketten und numerische
Argumente verknüpft werden, so daß man neben Text auch Zahlen ausgeben kann.
Einfache Eingaben vom Standard-Eingabegerät
125
126
Mit der Klasse InputStream können Leseoperationen eines Bytestroms verwirklicht werden, vgl. 7.1.1
Die Klasse PrintStream bezieht sich auf die I/O von Java 1.0
128
Programmieren in Java
Leider ist es etwas komplizierter Daten zeichenweise von der Tastatur zu lesen. Es
steht zwar ein vordefinierter Eingabestrom System.in zur Verfügung, der aber nicht
die eingelesenen Zeichen primitiver Typen konvertieren kann. Statt dessen muß eine
Instanz der Klasse „InputStreamReader“127 und daraus ein „BufferedReader“
erzeugt werden. Dieser kann zum Lesen einer zeilenweisen Eingabe mit
Umwandlung des Ergebnisses in einen primitiven Typ verwendet werden, z.B.128:
"Einlesen von zwei ganzen Zahlen".
import java.io.*;
public class EingZweiZahlen
{
// Einlesen von zwei ganzen Zahlen
public static void main(String args[]) throws IOException
{
int a, b, c;
BufferedReader ein = new BufferedReader(new
InputStreamReader(System.in));
System.out.print("Bitte a eingeben: ");
a = Integer.parseInt(ein.readLine());
System.out.print("Bitte b eingeben: ");
b = Integer.parseInt(ein.readLine());
c = a + b;
System.out.println("a + b = " + c);
}
}
1.4.4.2 Datenströme
Das grundlegende Modell für Ein- und Ausgabe sind Datenströme (CharacterStreams), die im Paket "java.io" versammelt sind. Das Datenstrom-Modell geht
davon aus, daß
- Programme Zeichenfolgen aus einem Datenstrom lesen bzw. in einen Datenstrom schreiben
- ein Datenstrom unabhängig von den Fähigkeiten des jeweiligen I/O-Geräts129 organisiert werden
muß.
Streams können auch verkettet werden und verschachtelt werden. Das Schachteln
erlaubt die Konstruktion von Filtern, die bei der Ein-/Ausgabe bestimmte
Zusatzfunktionen übernehmen, z.B. das Puffern von Zeichen, das Zählen von
Zeichen oder die Interpretation binärer Dateien.
Alle Klassen zur Datenein- bzw. Datenausgabe befinden sich im Paket java.io und
sind für Zeichenströme130 von den abstrakten Klassen Reader bzw. Writer131
abgeleitet. Allgemeine Fehlanzeige im Paket java.io ist die Ausnahme
IOException, die deshalb auch von einer Vielzahl von Methoden ausgelöst werden
kann und dabei sehr unterschiedliche Bedeutung annehmen kann.
127 Das grundlegende Konzept für Ein- und Ausgabe sind Datenströme, die im Paket „java.io“ versammelt
sind.
128 Vgl. pr14150
129 Hauptspeicher, Festplatte, Drucker, Netzwerk
130 Für Byte-Ströme gibt es die Klassen InputStream / OutputStream einschl. zahlreicher Subklassen
131 vgl. 7.6
129
Programmieren in Java
Reader
InputStreamReader
BufferedReader
FilterReader
FileReader
Writer
OutputStreamWriter
BufferedWriter
FilterWriter
FileWriter
Abb. Subklassen von Reader / Writer
Bsp.: Das folgende Programm liest eine Zeichenfolge aus einem Eingabestrom und
schreibt die Zeichenfolge in einen Ausgabestrom132
import java.io.*;
public class EchoEinAus extends Object
{
public static void main(String args[])
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader
ein = new BufferedReader(isr);
String eingabeZeile = null;
// OutputStreamWriter osw = new OutputStreamWriter(System.out);
PrintWriter aus
= new PrintWriter(System.out);
while (true)
{
aus.print("Gib eine Zeichenkette ein: ");
aus.flush();
try {
eingabeZeile = ein.readLine();
}
catch (IOException e)
{
aus.println(e.toString());
System.exit(0);
}
if (eingabeZeile == null)
{
aus.println();
aus.flush();
break;
}
aus.println(eingabeZeile);
}
}
}
Für die Eingabe wurde herangezogen:
132
vgl. pr14150
130
Programmieren in Java
- InputStream in der Klasse System aus dem Paket java.lang
- die abstrakte Klasse InputStream des Pakets "java.io"
- die Klasse InputStreamReader des Pakets "java.io"
- die Methode "String readLine()" mit der Klasse BufferedReader des Pakets java.io
Für die Ausgabe kann ebenso verfahren werden. Man zieht in diesem
Zusammenhang zur Ausgabe heran:
- die Klasse PrintStream (Subklasse der abstrakten Klasse OutputStream)
- die Klasse BufferedWriter (Subklasse von Writer). Es gibt aber kein Gegenstück zu "String
readLine()".
- die Klasse PrintWriter (Subklasse von Writer) für println(...)
1.4.4.3 Eingabe und Ausgabe mit Dateien
Dateien dienen zur Ablage von Daten auf externen Speichern. Dateien werden zur
besseren Übersicht auf externen Speichern in sog. Verzeichnissen gespeichert – der
Zugriff auf eine Datei ist somit über einen Verzeichnispfad und über den Dateinamen
möglich. Unter der Vielzahl verschiedener Dateiformate sind besonders
hervorzuheben: Text- und Binärdateien.
Byte-weises Schreiben und Lesen von (Binär-) Dateien
Zum (Byte-weisen) Schreiben und Lesen von Dateien werden verwendet: Die
Klassen FileOutputStream und FileInputStream.
1. Bsp.: Ausgabe einer Binärdatei
import java.io.*;
public class pr14444a
{
public static void main (String args[])
{
File f = new File("datei.bin");
try {
DataOutputStream raus = new DataOutputStream(
new FileOutputStream(f));
for (int i=1;i<=10;i++)
raus.writeInt(i*i);
}
catch(IOException e)
{
System.err.println(e.toString());
}
}
}
2. Bsp.: Eingabe einer Binärdatei
import java.io.*;
public class pr14444b
{
public static void main (String args[])
{
File f = new File("datei.bin");
131
Programmieren in Java
try {
DataInputStream rein = new DataInputStream(
new FileInputStream(f));
for (int i=1;i<=10;i++)
{
int zahl = rein.readInt();
System.out.println(zahl);
}
}
catch (IOException e)
{ System.err.println(e.toString()); }
}
}
Verarbeiten von 16-Bit Unicode Zeichen mit den Klassen FileReader und FileWriter
Eingabe von Textdateien
1. Bsp.: Einlesen einer Eingabe-Datei
import java.io.*;
public class EingabeDatei extends Object
{
private BufferedReader ein;
EingabeDatei(String dName) throws Exception
{
try {
ein = new BufferedReader(new FileReader(dName));
}
catch (FileNotFoundException a)
{
// Der Konstruktor von FileReader war nicht erfolgreich
System.out.println("Kann nicht geoeffnet werden: " + dName);
throw a;
}
catch (Exception a)
{
// Alle anderen Ausnahmen schliessen die Datei
try {
ein.close();
}
catch (IOException a1)
{
System.out.println("ein.close() nicht erfolgreich");
}
throw a;
}
finally { /* nicht geeignet fuer close() */ }
}
String holeZeile() throws IOException
{
String s;
try {
s = ein.readLine();
}
catch (IOException a)
{
System.out.println("readLine() nicht erfolgreich");
s = "Fehlanzeige";
}
return s;
}
void bereinigen()
132
Programmieren in Java
{
// Freigabe der Syntax-Resourcen
try {
ein.close();
}
catch(IOException a2)
{
System.out.println("ein.close() nicht erfolgreich");
}
}
}
2. Bsp.: Das folgende Programm bearbeitet die Eingabedatei.
import java.io.*;
public class BearbeiteEingabeDatei extends Object
{
public static void main(String args[])
{
try {
// Erzeugen einer Instanz
EingabeDatei ein =
new EingabeDatei("BearbeiteEingabeDatei.java");
String s;
int i = 1;
while ((s = ein.holeZeile()) != null)
System.out.println("" + i++ + ": " + s);
ein.bereinigen();
}
catch (Exception a)
{
System.out.println("Eingefangen in main(), a.printStackTrace()");
a.printStackTrace();
}
}
}
Das Beispiel zeigt: Eingabeströme können verschachtelt werden. Folgende Klassen
stehen dafür zur Verfügung (, die im Konstruktor einen Reader erwarten):
BufferedReader, LineNumberReader, FilterReader und PushbackReader.
Ein BufferedReader puffert Daten in einem 8192 Bytes großen Zwischenspeicher.
Dadurch müssen weniger Zugriffe auf den Datenträger gemacht werden. Zusätzlich
stellt BufferedReader die Methode public String readLine() zur
Verfügung, die eine komplette Textzeile einliest und als String an den Aufrufer
zurückgibt. „null“ wird zurückgegeben, wenn der Stream am Ende ist.
Ein LineNumberReader liest die Eingabezeilen und zählt gleichzeitig die Zeilen, die
gelesen wurden. Mit public int getLineNumber() bzw. public void
setLineNumber(int zeilenNummer) läßt sich auf die die Zeilennummern
zugreifen.
Ausgabe mit Dateien
Die Ausgabe in Dateien wird unterstützt durch die Klasse FileWriter.
FileWriter ist die einzige Subklasse von OutputStreamWriter. Jeder
OutputStreamWriter agiert mir einem CharToByteConverter und konvertiert
so Zeichenströme in Byteströme.
import java.io.*;
public class CharArrayWriterDemo
{
133
Programmieren in Java
public static void main(String args[])
{
String s;
try
{
PrintWriter pw = new PrintWriter(new BufferedWriter(
new FileWriter("charArrayWriterDemoPuffer.txt")));
for (int i = 1; i < 1000; i++) pw.println("Zeile " + i);
pw.close();
}
catch (IOException e)
{ System.out.println("Konnte Datei nicht erstellen"); }
}
}
In Java gibt es drei Klassen BufferedWriter (mit standardmäßig 8192 Bytes
großen Puffer), PrintWriter, FilterWriter, die einen Writer im Konstruktor
erlauben und ihre Ausgabe dorthin weiterleiten. Zu Beginn des vorliegenden
Programms133 wird ein FileWriter erstellt. Anschließend erzeugt man einen
BufferedWriter, der die Daten, die in die Datei geschrieben werden, sammelt.
Dieser BufferedWriter wird zu einem PrintWriter erweitert. Da ein
PrintWriter Schreibfunktionen besitzt, ist man auf write()-Methoden nicht mehr
angewiesen, sondern kann die komfortablen print()-Funktionen nutzen.
Umlenken von Standardeingabe, Standardausgabe und Standardfehlerausgabe
In Java 1.1 ermöglichen die folgenden Methoden das Umlenken:
- setIn(InputStream)
- setOut(PrintStream)
- setErr(PrintStream)
Bsp.: Demonstration der Umlenkung von Standardeingabe und Standardausgabe in
Java 1.1134
import java.io.*;
class Umlenken
{
public static void main(String args[])
{
try
{
BufferedInputStream ein =
new BufferedInputStream(new FileInputStream("Umlenken.java"));
// produziert "deprecation"-Nachrichten, System.setOut() und
// System.setErr() erfordern ein PrintStream-Objekt als Argument
PrintStream aus = new PrintStream(
new BufferedOutputStream(
new FileOutputStream("test.aus")));
System.setIn(ein); System.setOut(aus); System.setErr(aus);
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
String s;
while ((s = br.readLine()) != null)
System.out.println(s);
aus.close();
133
134
vgl. pr14330
vgl. pr14150
134
Programmieren in Java
}
catch(IOException a)
{
a.printStackTrace();
}
}
}
Random Access Files135
werden über die Klasse Random Access File realisiert. Beim Konstruktor
RandomAccessFile(String,Zugriffsmodus) wird neben dem Dateinamen
auch die Zugriffsart angegegeben ("r" nur für Lesezugriffe, "rw" zum Lesen und
Schreiben). Mit der Methode seek(long) kann der Dateizeiger (FilePointer) gesetzt
werden, getFilePointer() liefert die aktuelle Position des FilePointer.
Weiterhin stehen zur Verfügung: length(), close(), readLine(), read(), readByte(),
readShort(), readInt(), readLong(), readFloat(), readDouble(), readChar(),
readBoolean() und die analogen write-Methoden.
Bsp.: Ein- und Ausgabe von und nach Dateien mit wahlfreiem Zugriff136
1. Erzeugen der Datei rafrw (Datei mit wahlfreiem Zugriff). Auf rafrw werden ganze Zahlen
geschrieben.
2. Erzeugen der Datei rafr (Datei mit wahlfreiem Zugriff), von der ganze Zahlen gelesen werden.
import java.io.*;
public class RandomAccessTest
{
public static void main(String[] args)
throws java.io.IOException
{
BufferedReader br = new BufferedReader
(new InputStreamReader(System.in));
int intLength = 4; // Laenge der Darstellung von ganzen Zahlen
// updating part
try
{
RandomAccessFile rafrw = new RandomAccessFile("testfile", "rw");
System.out.print("\nAendern eines Datensatzes der Datei\n");
// Datensatznummer erfragen und Dateizeiger setzen und anzeigen
System.out.print("Datei geoeffnet. ");
System.out.print("Dateizeiger="+rafrw.getFilePointer()
+" DateiLaenge="+rafrw.length()+" Byte(s)\n");
System.out.print("\n? Welchen Datensatz aendern = ");
long pos = intLength * (new Integer(br.readLine()).intValue());
rafrw.seek(pos);
System.out.print( "Dateizeiger="+rafrw.getFilePointer()+"\n");
// Falls vorhanden, Datensatz anzeigen
if (pos < rafrw.length())
{
// Datensatz anzeigen, Dateizeiger wiederherstellen
System.out.print("Datensatz = "+rafrw.readInt()+"\n");
rafrw.seek(pos);
}
else
System.out.print("Aha, Datensatz soll angefuegt werden.\n");
// Neuen Datensatz einlesen
System.out.print("\n? Neuer Datensatz = ");
135
136
vgl. 7.4
vgl. pr14444
135
Programmieren in Java
}
int val = new Integer(br.readLine()).intValue();
// Neuen Datensatz in die Datei schreiben, Dateizeiger anzeigen
rafrw.writeInt(val);
System.out.print( "Nach dem Schreiben: Dateizeiger="
+rafrw.getFilePointer()+"\n");
// test outpout part
RandomAccessFile rafr = new RandomAccessFile("testfile", "r");
System.out.print("\nVoila, die geaenderte Datei\n");
// Wiedergabe der Datensaetze aus der Datei
// nach der Veraenderung
int i;
try
{
while (true)
{
i = rafr.readInt();
System.out.print(i+" ");
}
} catch (EOFException e) {}
rafrw.close();
rafr.close();
} catch (IOException e)
{
System.err.println("RandomAccessTest: " + e);
}
System.out.print("\n");
}
136
Programmieren in Java
1.4.4.4 Verwalten von Dateien und Verzeichnissen durch die Klasse File
Informationen über eine Datei erhält man über ein File-Objekt von class File
java.io.File implements Serializable, Comparable. Ein File-Objekt
repräsentiert eine Datei oder ein Verzeichnis auf dem Dateisystem. Der Verweis auf
Datei bzw. Verzeichnis wird durch einen Pfadnamen spezifiziert. Pfadnamen137
können absolut oder relativ zum aktuellen Verzeichnis angegeben werden.
Dateieigenschaften und Dateiattribute können über zahlreiche Methoden der Klasse
File138 gesetzt bzw. werden.
Bsp.:
import java.io.*;
// import java.nio.charset.*;
public class FileInfo
{
public static void main(String[] args)
{
File f = new File("./FileInfo.java");
System.out.println("The length of " + f.getName() +
" is " + f.length() + " bytes.");
try {
System.out.println(f.getName() + " is in the " +
f.getCanonicalPath() + " directory.");
}
catch (IOException e) { System.out.println("exception");}
}
}
Weitere wichtige Methoden der Klasse File139 sind: exit(), list(), delete(),
length(), mkdir(), renameTo(File ...), isDirectory(), isFile().
1.4.4.5 Filtern mit den Klasse FilterReader und FilterWriter
Die abstrakten Filterklassen von java.io erlauben beim Lesen und Schreiben
zusätzliche Operationen auszuführen.
Bsp.: Filtern von Textdateien mit der Extension .java aus dem aktuellen Verzeichnis
/*
* Auflisten des Inhalts vom aktuellen Verzeichnis und Filtern
*/
import java.io.*;
class DateiFilter implements FilenameFilter
{
public boolean accept(File f,String s)
{
if (s.toLowerCase().endsWith(".java"))
return true;
return false;
}
}
137
Pfadnamen sind plattformabhängig:
Bei Windows-Rechnern trennt ein BackSlash die Pfade
Bei Unix-Rechnern trennt ein Slash die Pfade.
138 vgl. 7.3
139 vgl. 7.3
137
Programmieren in Java
public class Verzeichnistest
{
public static void main(String args[])
{
String nameVerz = System.getProperty("user.dir");
File userdir = new File(nameVerz);
// System.out.println(System.getProperty("user.dir"));
String eintraege [] = userdir.list();
System.out.println("Alle Eintraege aus dem Verzeichnis " + nameVerz);
for (int i = 0;i < eintraege.length; i++)
System.out.println(eintraege[i]);
System.out.println("Alle Eintraege aus dem Verzeichnis mit Endung .java "
+ nameVerz);
eintraege = userdir.list(new DateiFilter());
// Arbeitet wie die Methode list(), nur filtert ein spezielles FileName// Filter-Objekt bestimmte Namen heraus
for (int i = 0;i < eintraege.length; i++)
System.out.println(eintraege[i]);
}
}
Eingabefilter
Die Klasse FilterReader erhält den eigentlichen Reader über den Konstruktor und
speichert ihn als Membervariable.
Bsp.140: Die Klasse CaseFilter implementiert eine erweiterte Funktionalität für
read(). Beim Einlesen werden Kleinbuchstaben in Großbuchstaben umgewandelt.
import java.io.*;
public class CaseFilter extends FilterReader
{
public CaseFilter(Reader f)
{
super(f);
}
public int read() throws IOException
{
int zeichen = super.read();
if (Character.isLowerCase((char) zeichen))
return Character.toUpperCase((char) zeichen);
else return zeichen;
}
}
In der Klasse DekoStream wird nun das read() eines CaseFilter-Objekts
verwendet. Direkt beim Einlesen wird der Text formatiert.
import java.io.*;
public class DekoStream
{
public DekoStream()
{
String s = readNormal("DekoStream.java");
System.out.println(s);
s = readFilter("DekoStream.java");
System.out.println(s);
}
140
vgl. pr14341
138
Programmieren in Java
private String readNormal(String fl)
{
StringBuffer s = new StringBuffer();
try {
FileReader fread = new FileReader(fl);
int zeichen;
while ((zeichen = fread.read()) != -1)
s.append((char) zeichen);
fread.close();
}
catch(IOException e) { System.err.println(e); }
return s.toString();
}
private String readFilter(String fl)
{
StringBuffer s = new StringBuffer();
try {
FileReader fread = new FileReader(fl);
CaseFilter ff = new CaseFilter(fread);
/* Die CaseFilter-Klasse implementiert eine erweiterte
Funktionalitaet fuer read(). Beim Einlesen werden
Kleinbuchstaben in Grossbuchstaben umgewandelt */
int zeichen;
while ((zeichen = ff.read()) != -1)
s.append((char) zeichen);
ff.close();
}
catch(IOException e) { System.err.println(e); }
return s.toString();
}
public static void main(String [] args)
{
new DekoStream();
}
}
Ab JDK 1.1 gibt es einen vordefinierten Eingabefilter PushBackReader, der aus
FilterReader abgeleitet ist. Ein PushBackReader erweitert die Klasse
FilterReader um einen ein Byte großen PushBack-Puffer. Dieser erlaubt einer
Anwendung, das zuletzt gelesenen Zeichen wieder in den Eingabestrom zurück zu
schieben. Der nächste Lesezugriff liest dann nicht das folgende Zeichen im
Eingabestrom, sondern das gerade zurückgelesene Zeichen.
Ausgabefilter
Die Architektur der Writer-Klassen macht es möglich eigene Filter zu konstruieren.
Das kann bspw. durch Überlagern der Klasse Writer141 geschehen. Der offizielle
Weg besteht allerdings in der Überlagerung der abstrakten Klasse FilterWriter.
FilterWriter besitzt ein internes Writer-Objekt out, das bei der Initialisierung an
den Konstruktor übergeben wird. Zusätzlich überlagert es drei der vier writeMethoden, um die Ausgabe auf out umzuleiten.
Damit ein eigener FilterWriter erzeugt werden kann, muß beachtet werden:
- die eigene Klasse leitet sich von FilterWriter ab
- der Konstruktor bekommt als Parameter ein Writer-Objekt und ruft mit super(out) den Konstruktor
der Superklasse auf. Das ist die Klasse FilterWriter.
141
wie es bspw. bei PrintWriter und BufferedWriter realisier wurde.
139
Programmieren in Java
- Überlagern der drei write() Methoden, Ausführung der Filterfunktionen, Weitergabe der wahren
Daten an den Writer
Bsp.142: Konvertieren aller Zeichen in Großbuchstaben innerhalb eines Streams.
import java.io.*;
class UpCaseWriter extends FilterWriter
{
public UpCaseWriter(Writer out)
{
super(out);
}
public void write(int c) throws IOException
{
super.write(Character.toUpperCase((char) c));
}
public void write(char cbuff[], int off, int len)
throws IOException
{
for (int i = 0; i < len; i++)
write(cbuff[off + i]);
}
public void write(String str, int off, int len)
throws IOException
{
write(str.toCharArray(),off,len);
}
}
Test:
import java.io.*;
public class PR14441
{
public static void main(String args[])
{
PrintWriter f;
String s = " und diese Zeichenkette auch";
try {
f = new PrintWriter(
new UpCaseWriter(new FileWriter("upcase.txt")));
f.println("Diese Zeile wird gross geschrieben");
/* Die drei write() Methoden werden in UpCaseWriter
do überlagert, dass zunächst Ausgabezeichen in
Grossschrift konvertiert und anschliessend die
passende Superklassen-Methode aufgerufen wird, um
die Datei an den internen Writer out zu uebergeben
*/
// Test write(int);
f.println();
// Test write(String)
f.write(s); f.println();
// Test von write(String,int,int)
f.write(s,0,23); f.println();
// Test von write(char[],int,int)
f.write(s.toCharArray(),0,10);
f.println();
f.close();
}
catch(IOException e)
{
System.out.println("Fehler beim Erstellen der Datei");
}
142
vgl. pr14341
140
Programmieren in Java
}
}
1.4.4.6 Der Dateiauswahl-Dialog
Eine spezielle Klasse FileDialog (class java.awt.FileDialog extends
Dialog) zeigt einen vom Betriebssystem unabhängigen Dateiauswahl-Dialog. Er
kann für das Speichern und Öffnen konfiguriert sein. Er lassen sich die Pfade und ein
FilenameFilter setzen. Erst nach dem Schließen und Beenden mit dem OKButton stehen ausgewählte Dateien zur Verfügung:
Bsp.:
import java.awt.*;
public class DateiAuswahl extends Frame
{
public static void main(String args [])
{
Frame f = new Frame();
// ein Objekt von Frame wird dem FileDialog mit auf den
// Weg gegeben
FileDialog d = new FileDialog(f,"oeffne was",FileDialog.LOAD);
d.setFile("*.java");
d.show();
String datei = d.getDirectory() + d.getFile();
System.out.println(datei);
}
}
141
Programmieren in Java
2. Hauptbestandteile der Sprache
2.1 Token
Token bedeuten in Java dasselbe wie Worte und Zeichensetzung für die
menschliche Sprache. Wenn Java den Quellcode mit dem Java-Compiler (javac)
kompiliert, findet eine Zerlegung des Quellcodes in kleine Bestandteile, Symbole
oder Token genannt, statt. Token müssen sich in sinnvolle Einheiten und gültige
Arten einordnen lassen. Das übernimmt der sog. Parser des javac, der Quelltext in
Bytecode übersetzt und zusätzlich noch Leerzeichen und Kommentare (aus dem
Quelltext) entfernt.
Aus Token setzen sich die Ausdrucksformen von Java zusammen. Es gibt in Java
fünf Arten von Token: Bezeichner oder Identifizierer, Schlüsselwörter, Literale,
Operatoren, Trennzeichen.
Token
Schlüsselworte
Beschreibung
Alle Wörter, die ein wesentlicher Teil der
Java-Sprachdefinition sind
Bezeichner, Identifiziere Namen für Klassen,Objekte, Variablen,
Konstanten, Methoden, etc.; Namen sind
zusammengesetzt aus Unicode-Zeichen.
An erster Stelle eines Bezeichners darf
keine Zahl stehen.
Literal
Mit einem Literal können Variablen und
Konstanten
bestimmte
Werte
zugewiesen
werden.
Die
können
sämtliche in der Java-Sprachdefinition
erlaubte Arten von Werten (numerische
Werte, boolesche Werte, Zeichen,
Zeichenketten) sein
Trennzeichen
Symbol zum Anzeigen für Trennungen
und Zusammenfassungen von Code
Operatoren
Zeichen bzw. Zeichenkombinationen zur
Angabe einer auszuführenden Operation
mit einer oder mehreren Variablen oder
Konstanten
Zeichen, die in beliebiger Anzahl und an
Leeraum143
jedem Ort zwischen Token mit Funktion
zur übersichtlichen Gestaltung des
Quellcodes plaziert werden können
Kommentar
Wird vom Compiler ignoriert. Eine
Besonderheit
ist
der
„javadoc“Kommentar,
der
vom
JavaDokumentations-Tool ausgewertet wird.
Bsp.
Public, class, static, void, String,
else, if, this, etc.
“Hallo Java“, 13, false, true
(){}[];.,
+ - * / , >>> <<<
Space,
Tab,
Formularvorschub
Zeilenende,
// .. Kommentar bis zum Zeilenende
/* Eingebetter Kommentar*/
//* javadoc-Kommentar */
Abb.: Die Java-Token
Java benutzt den 16-Bit-Unicode-Zeichensatz. Die Unicode-Spezifikation ist sehr
umfangreich, die ersten 256 Zeichen entsprechen aber dem normalen ASCIIZeichensatz (Byte 1 ist immer auf 0 gesetzt). Der Java-Compiler erwartet Unicode144.
143
Technisch gesehen ist ein Leeraum kein Token
Alle Literale (d.h. alle vorkommenden Zeichen (Zahlen, Buchstaben, Sonderzeichen, etc.) bestehen aus
Unicode.
144
143
Programmieren in Java
Bei der Kompilierung wird der Quellcode automatisch in eine Folge von UnicodeZeichen transformiert. Eine ASCII-Kodierung wird einfach mit Voranstellen der
Zeichenfolge „\u00“ und folgender Hexadezimalzahl in das passende UnicodeZeichen übersetzt. Dies definiert in Java eine Escape-Sequenz, mit der alle UnicodeZeichen verschlüsselt werden können. Durch die „Escape-Sequenz-Darstellung“ des
Quelltextes in Java ist die Verwendung von Umlauten und anderen Sonderzeichen in
Bezeichnern möglich.
Zwei Bezeichner gelten in Java als identisch, wenn ihre Unicode-Darstellung
übereinstimmend ist. Deshalb muß auch in Java unbedingt zwischen Groß- und
Kleinschreibung unterschieden werden.
2.1.1 Schlüsselworte
Es gibt bestimmte Buchstabenkombinationen, die in Java eine besondere Bedeutung
haben. Diese Buchstabenfolgen werden in Java Schlüsselworte genannt
abstract
catch
do
finally
if
interface
outer
return
this
var
boolean
char
double
float
implements
long
package
short
throw
void
Break
Class
Else
For
Import
Native
Private
Static
Throws
Volatile
byte
const
extends
future
inner
new
protected
super
transient
while
case
continue
false
generic
instanceof
null
public
switch
true
cast
default
final
goto
int
operator
rest
synchronized
try
Abb.: Reservierte Java Schlüsselworte
2.1.2 Bezeichner und Namenskonventionen
Bezeichner bzw. Identifizierer (identifier) sind Worte, die vom Programmierer
gewählt, zu Token werden und die Variablen, Konstanten, Klassen, Objekte,
Beschriftungen und Methoden darstellen. Bezeichner dürfen nicht mit JavaSchlüsselworten identisch sein.
Es gibt in Java einige Regeln zur Namensvergabe, die unbedingt eingehalten werden
müssen:
-
Bezeichner bestehen aus Unicode-Buchstaben und Zahlen in unbeschränkter Länge.
Das erste Zeichen eines Bezeichners muß ein Buchstabe, der Unterstrich „_“ oder das Zeichen „$“ sein.
Alle folgenden Zeichen sind entweder Buchstaben oder Zahlen.
Zwei Token gelten nur dann als derselbe Bezeichner, wenn sie dieselbe Länge haben, und jedes Zeichen im
ersten Token genau mit dem korrespondierenden Zeichen des zweiten Token übereinstimmt, d.h. den
vollkommen identischen Unicode-Wert hat. Java unterscheidet Groß- und Kleinschreibung.
Es hat sich eingebürgert, einige Namenskonventionen in Java einzuhalten:
-
Man sollte möglichst „sprechende“ Bezeichner wählen (Ausnahme sind Schleifen, wo für Zählvariablen
meistens nur ein Buchstabe (z.B. i, j) verwendet wird.
Konstanten (Elemente mit dem „Modifizierer“ „final“) sollten vollständig groß geschrieben werden.
144
Programmieren in Java
-
-
Identifizierer von Klassen sollten mit einem Großbuchstaben beginnen und anschließend klein geschrieben
werden. Wenn sich ein Bezeichner aus mehreren Wörtern zusammensetzt, dürfen diese nicht getrennt
werden. Die jeweiligen Anfangsbuchstaben innerhalb des gesamten Bezeichners werden jedoch groß
geschrieben.
Die Identifizierer von Variablen, Methoden und Elementen beginnen mit Kleinbuchstaben und werden auch
anschließend klein geschrieben. Wenn ein Bezeichner sich aus mehreren Wörtern zusammensetzt, dürfen
diese nicht getrennt werden. Die jeweiligen Anfangsbuchstaben der folgenden Wörter können jedoch
innerhalb des Gesamtbezeichners groß geschrieben werden.
2.1.3 Literale
Literale sind Token, die für zu speichernde Werte der Datentypen byte, short, int,
long, float, double, boolean und char stehen. Literale werden auch zur
Darstellung von Werten benutzt, die in Zeichenketten gespeichert werden.
Es gibt: Boolesche Literale, Zeichenliterale, Zeichenkettenliterale, Gleitpunktliterale,
ganzzahlige Literale.
2.1.3.1 Ganzzahlige Literale
Die Datentypen „int“ und „long“ können dezimal, hexadezimal und oktal
beschrieben werden. Die Voreinstellung ist dezimal und gilt immer dann, wenn Werte
ohne weitere Änderung dargestellt werden. Hexadezimale Darstellung beginnt immer
mit der Sequenz 0x oder 0X145. Oktale Darstellungen beginnen mit einer führenden
Null. Negativen Ganzzahlen wird ein Minuszeichen vorangestellt. Ganzzahlige
Literale haben gemäß Voreinstellung den Typ „int“. Durch Anhängen vom „l“ bzw.
„L“ kann man jedoch explizit den Typ „long“ wählen.
2.1.3.2 Gleitpunktliterale
Gleitpunktliterale bestehen aus mehreren Teilen. Sie erscheinen in folgender
Reihenfolge:
Teil
Ganzzahliger Teil
Dezimalpunkt
Gebrochener Teil
Exponent
Typensuffix
Nötig?
Nicht, wenn der gebrochene Teil
vorhanden ist
Nicht, wenn ein Exponent vorhanden
ist. Muß vorliegen, wenn es einen
Dezimalpunkt gibt
Darf nicht vorhanden sein, wenn es
keinen Dezimalpunkt gibt. Muß
vorhanden sein, wenn es keinen
ganzzahligen Teil gibt
Nur, wenn es keinen Dezimalpunkt gibt
Nein. Bei Abwesenheit eines
Typensuffix wird angenommen, daß es
Beispiele
0, 1, 2, ... , 9, 131140
0, 1, 1311, 41421, 9944, 718281828
E23, E-19, E6, e+307
f, F, d, D
145 Die Zahlen 0 bis 15 werden durch die Buchstaben „A“ bis „F“ bzw. „a“ bis „f“ dargestellt (Groß- oder
Kleinschreibung ist hier nicht relevant.
145
Programmieren in Java
sich um eine Zahl doppelter
Genauigkeit handelt.
Bsp.: Typensuffix für Ganzzahlen bzw. Gleitpunktzahlen
class Literale
{
char c = 0xffff;
// max. hex. Wert fuer char
byte b = 0x7f;
// max. hex. Wert fuer byte
short s = 0x7fff;
// max. hex. Wert fuer short
int i1 = 0x2f;
// hexadezimal in Kleinbuchstaben
int i2 = 0X2F;
// hexadezimal in Grossbuchstaben
// hexadeimale und oktale Werte mit long
long n1 = 200L;
// "long"-Suffix
long n2 = 200l;
// "long"-suffix
long n3 = 200;
// float- und double-Literale
float f1 = 1;
float f2 = 1F;
// "float"-Suffix
float f3 = 1f;
// "float"-Suffix
float f4 = 1e-45f;
// wissenschaftl. Schreibweise
float f5 = 1e+9f;
// "float"-Suffix
double d1 = 1d;
// "double"-Suffix
double d2 = 1D;
// "double"-Suffix;
double d3 = 47e47d;
// wissenschaftl. Schreibweise
}
2.1.3.3 Boolesche Literale
Es gibt zwei Boolesche Literale: true und false. Es gibt keinen Nullwert und kein
numerisches Äquivalent.
2.1.3.4 Zeichenliterale
Sie werden in einzelne, hochgestellte Anführungszeichen gesetzt. Als einzelne
Zeichen gelten alle druckbaren Zeichen mit Ausnahme des Bindestrichs „-“ und des
Backslash „\“. Bei Escape-Zeichenliteralen folgen den Anführungszeichen der
Backslash und dem Backslash eine der folgenden Zeichen:
- b, t, n, f, r, <<, ‚ oder \
- eine Serie von Oktalziffern (dreistellig)146
- ein „u“ gefolgt von einer vierstelligen Serie von Hexadezimalziffern, die für ein nicht
zeilenbeendendes Unicode-Zeichen stehen. Die vier Stellen der hexadezimalen UnicodeDarstellung (\u000 bis \uFFF) stehen für 65535 mögliche Kodierungen.
146 Die als oktale Escape-Literale bezeichneten Zeichenliterale können zur Darstellung aller Unicode-Werte von
„\u0000“ bis „\u00ff“ benutzt werden. Bei oktaler Darstellung ist diese Darstellung auf „\000“ bis auf „\u377“
begrenzt.
146
Programmieren in Java
Escape-Literal
‘\b‘
‘\t‘
‘\n‘
‘\f‘
‘\r‘
‘\“
‘\‘
‘\\‘
Unicode-Steuersequenz
\u0008
\u0009
\u000a
\u000c
\u000d
\u0022
\u0027
\u005c
Oktal-Sequenz
\010
\011
\012
\014
\015
\042
\047
\134
Bedeutung
Backspace
Tab
Neue Zeile
Formularvorschub
Return
Doppeltes Anführungszeichen
Einfaches Anführungszeichen
Backslash
Abb.: Escape-Literale
2.1.3.5 Zeichenkettenliterale
Zeichenkettenliterale sind aus mehreren Zeichenliteralen zusammengesetzte Ketten
(Strings). Bei Zeichenkettenliteralen werden null oder mehr Zeichen in Anführungszeichen (“)dargestellt. Java erzeugt Zeichenketten als Instanz der Klasse String.
Damit stehen alle Methoden der Klasse String zur Manipulation einer Zeichenkette
zur Verfügung.
Zeichenkettenliterale stehen zwischen zwei Anführungszeichen (“) und können
Steuerzeichen wie Tabulatoren, Zeilenvorschübe, nichtdruckbare Unicode-Zeichen
und druckbare Unicode-Spezialzeichen enthalten. Es kann sich auch um EscapeSequenzen handeln. Beide Anführungszeichen müssen in derselben Zeile des
Quellcodes stehen.
Jedes String-Literal ist eine Referenz auf ein Objekt der Klasse String. Falls der
Compiler beim Übergeben des Quelltextes ein string-Literal findet, erzeut er ein
neues String-Objekt und verwendet es anstelle des Literals.
In Java ist der Operator + auch auf Strings definiert. Auf zwei String-Objekte angewendet, liefert er die Verkettung beider Objekte.
2.1.4 Trennzeichen
Trennnzeichen sind Token, die aus einem einzigen Zeichen bestehen und andere
Token trennen. Java kennt 9 Trennzeichen:
(
)
{
}
[
]
;
,
.
Wird sowohl zum Öffnen einer Parameterliste für eine Methode als auch zur Festlegung eines
Vorrangs für Operationen in einem Ausdruck benutzt.
Wird sowohl zum Schließen einer Parameterliste für eine Methode als auch zur Festlegung
eines Vorrangs für Operationen in einem Ausdruck benutzt
Wird zu Beginn eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt
Wird an das Ende eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt
Steht vor einem Ausdruck, der als Index für ein Datenfeld (Array) steht
Folgt einem Ausdruck, der als Index für ein Datenfeld dient
Dient sowohl zum Beenden einer Ausdrucksanweisung als auch zum Trennen der Teile einer forAnweisung.
wird in vielen Zusammenhängen als Begrenzer verwendet
Wird sowohl als Dezimalpunkt als auch zum Trennen solcher Dinge wie Paketnamen von
Klassennamen oder Variablennamen benutzt.
Abb.: Die Java-Trennzeichen
147
Programmieren in Java
2.1.5 Operatoren
Operatoren geben an, welche Operation mit einem oder mehreren gegebenen
Operanden durchgeführt werden soll. Es gibt in Java 37 Zeichensequenzen, die
Token darstellen, welche als Operatoren benutzt werden.
2.1.5.1 Arithmetische Operatoren
Arithmetische Operatoren benutzen nur zwei Operanden. Diese sind entweder
ganzzahlige Werte oder Gleitpunktzahlen. Als Rückgabe einer arithmetischen
Operation erhält man einen neuen Wert, dessen Datentyp sich folgendermaßen
ergibt:
- Zwei ganzzahlige Datentypen (byte, short, int oder long) als Operanden ergeben immer einen
ganzzahligen Datentyp als Ergebnis. Dabei kann als Datentyp int oder long entstehen, byte und
short sind nicht möglich. Der Datentyp long entsteht nur, wenn einer der beiden Operanden
bereits vom Datentyp long war oder das Ergebnis von der Größe her nur als long dargestellt
werden kann.
- Zwei Gleitpunktzahlentypen als Operanden ergeben immer einen Gleitpunktzahlentyp als Ergebnis.
Die Anzahl der Stellen des Ergebnisses ist immer das Maximum der Stellenanzahl der beiden
Operanden.
- Falls die Operanden ein ganzzahliger Typ und eine Gleitpunktzahlentyp sind, dann ist das Ergebnis
immer ein Gleitpunktzahlentyp.
Operator
+
*
/
%
Bedeutung
Additionsoperator
Subtraktionsoperator
Multiplikationsoperator
Divisionsoperator
Modulo-Operator
Beispiel
13 + 11
13 - 11
13 * 11
13 / 11
13 % 11
Abb.: Die arithmetischen Java-Operatoren
2.1.5.1.1 Einstellige arithmetische Operatoren
Es gibt zwei einstellige (d.h. mit nur einem Operanden) arithmetische Operatoren in
Java:
- Einstellige arithmetische Negierung: - Das Gegenteil der arithmetischen Negierung: +
2.1.5.1.2 Arithmetische Zuweisungsoperatoren
Neben dem direkten Zuweisungsoperator gibt es die arithmetischen
Zuweisungsoperatoren. Diese sind eigentlich nur eine Abkürzung für arithmetische
Operationen mit ganzen Zahlen und Gleitpunktzahlen. Das Ergebnis einer
Zuweisung über einen arithmetischen Zuweisungsoperator steht immer auf der linken
Seite.
Operator
+=
-=
*=
Bedeutung
Additions- und Zuweisungsoperator
Subtraktions- und Zuweisungsoperator
Multiplikations- und Zuweisungsoperator
148
Programmieren in Java
/=
%=
=
Divisions- und Zuweisungsoperator
Modulo- und Zuweisungsoperator
Direkter Zuweisungsoperator
Abb.: Die arithmetischen Zuweisungsoperatoren
2.1.5.2 Inkrement- / Dekrement-Operatoren
Inkrement- und Dekrement-Operatoren sind einstellige Operatoren und werden nur in
Verbindung mit einem ganzzahligen Wert oder einer Gleitpunktzahl benutzt.
Der Inkrement-Operator (++) erhöht den Wert des Operanden um 1. Steht der
Operator vor dem Operanden, erfolgt die Erhöhung des Werts, bevor der Wert dem
Operanden zugewiesen wird. Wenn er hinter dem Operanden steht, erfolgt die
Erhöhung, nachdem der Wert bereits zugewiesen wurde.
Der Dekrement-Operator (--) erniedrigt den Wert des Operanden um 1. Steht der
Operator vor dem Operanden, erfolgt die Erniedrigung des Werts, bevor der Wert
dem Operanden zugewiesen wird. Wenn er hinter dem Operanden steht, erfolgt die
Erniedrigung, nachdem der Wert bereits zugewiesen wurde.
2.1.5.3 Bitweise arithmetische Operatoren
Bitweise Arithmetik wird im wesentlichen zum Setzen und Testen einzelner Bits und
Kombinationen einzelner Bits innerhalb einer Variablen benutzt.
Operator
&
|
^
~
Beschreibung
Bitweiser AND-Operator
Bitweiser OR-Operator
Bitweiser XOR-Operator
Bitweiser Komplement-Operator
Abb.: Bitoperatoren in Java
2.1.5.4 Bitweise Verschiebungsoperatoren
Bitweise Verschiebungsoperatoren verschieben die Bits in einer ganzen Zahl.
Operator
<<
>>
>>>
Beschreibung
Operator für bitweise Verschiebung nach links
Operator für bitweise Verschiebung nach rechts
Operator für binäre Verschiebung nach rechts mit Füllnullen
Abb.: Die bitweisen Verschiebungsoperatoren
Bsp.: Bitweises Manipulieren147
import java.util.*;
public class BitManipulation
{
public static void main(String[] args)
147
pr21502
149
Programmieren in Java
{
Random rand = new Random();
int i = rand.nextInt();
int j = rand.nextInt();
pBinInt("-1",-1);
pBinInt("+1",+1);
int maxpos = 2147483647;
pBinInt("maxpos", maxpos);
int maxneg = -2147483648;
pBinInt("maxneg",maxneg);
pBinInt("i",i);
pBinInt("~i",~i);
pBinInt("-i",-i);
pBinInt("j",j);
pBinInt("i & j",i & j);
pBinInt("i | j",i | j);
pBinInt("i ^ j",i ^ j);
pBinInt("i << 5",i << 5);
pBinInt("i >> 5",i >> 5);
pBinInt("(~i) >> 5",(~i) >> 5);
pBinInt("i >>> 5",i >>> 5);
pBinInt("(~i) >>> 5", (~i) >>> 5);
long l = rand.nextLong();
long m = rand.nextLong();
pBinLong("-1L",-1L);
pBinLong("+1L",+1L);
long ll = 9223372036854775807L;
pBinLong("maxpos", ll);
long lln = -9223372036854775807L;
pBinLong("maxneg",lln);
pBinLong("l",l);
pBinLong("~l",~l);
pBinLong("-l",-l);
pBinLong("m",m);
pBinLong("l & m",l & m);
pBinLong("l | m",l | m);
pBinLong("l ^ m",l ^ m);
pBinLong("l << 5",l << 5);
pBinLong("l >> 5",l >> 5);
pBinLong("(~l) >> 5",(~l) >> 5);
pBinLong("l >>> 5",l >>> 5);
pBinLong("(~l) >>> 5", (~l) >>> 5);
}
static void pBinInt(String s, int i)
{
System.out.println(s + ", int: " + i + ", binaer: ");
System.out.print("
");
for (int j = 31; j >= 0; j--)
if (((1 << j) & i) != 0)
System.out.print("1");
else
System.out.print("0");
System.out.println();
}
static void pBinLong(String s, long l)
{
System.out.println(s + ", long: " + l + ", binaer: ");
System.out.print("
");
for (int j = 63; j >= 0; j--)
if (((1L << j) & l) != 0)
System.out.print("1");
else
System.out.print("0");
System.out.println();
}
}
150
Programmieren in Java
Die Ausgabe zu dieser Anwendung zeigt für den Bereich „int“ jeweils die interne
Zahlendarstellung:
-1, int: -1, binaer:
11111111111111111111111111111111
+1, int: 1, binaer:
00000000000000000000000000000001
maxpos, int: 2147483647, binaer:
01111111111111111111111111111111
maxneg, int: -2147483648, binaer:
10000000000000000000000000000000
i, int: -1465644750, binaer:
10101000101001000000100100110010
~i, int: 1465644749, binaer:
01010111010110111111011011001101
-i, int: 1465644750, binaer:
01010111010110111111011011001110
j, int: -1273964081, binaer:
10110100000100001101100111001111
i & j, int: -1610610430, binaer:
10100000000000000000100100000010
i | j, int: -1128998401, binaer:
10111100101101001101100111111111
i ^ j, int: 481612029, binaer:
00011100101101001101000011111101
i << 5, int: 344008256, binaer:
00010100100000010010011001000000
i >> 5, int: -45801399, binaer:
11111101010001010010000001001001
(~i) >> 5, int: 45801398, binaer:
00000010101110101101111110110110
i >>> 5, int: 88416329, binaer:
00000101010001010010000001001001
(~i) >>> 5, int: 45801398, binaer:
00000010101110101101111110110110
2.1.5.5 Bitweise Zuweisungsoperatoren
Bitweise Zuweisungsoperatoren verwenden einen Wert, führen eine entsprechende
bitweise Operation mit dem zweiten Operanden durch und legen das Ergebnis als
Inhalt des ersten Operanden ab.
Operator
&=
|=
^=
<<=
>>=
>>>=
Beschreibung
Bitweiser AND-Zuweisungsoperator
Bitweiser OR-Zuweisungsoperator
Bitweiser XOR-Zuweisungsoperator
Zuweisungsoperator für die bitweise Verschiebung nach links
Zuweisungsoperator für die bitweise Verschiebung nach rechts
Zuweisungsoperator für die bitweise Verschiebung nach rechts mit Füllnullen
Abb.: Die bitweisen Zuweisungsoperatoren
151
Programmieren in Java
2.1.5.6 Vergleichsoperatoren
Vergleichsoperatoren haben zwei Operanden und vergleichen diese (zwei
Ganzzahlen oder zwei Gleitpunktzahlen)1. Als Rückgabewert der Operation entsteht
ein boolescher Wert (true oder false).
Operator
==
!=
<
>
<=
>=
Bedeutung
Gleichheitsoperator
Ungleichheitsoperator
Kleiner-als-Operator
Größer-als-Operator
Kleiner-als-oder-gleich-Operator
Größer-als-oder-gleich-Operator
Abb.: Die Java-Vergleichsoperatoren
Die relationalen Operatoren „==“ und „!=“ arbeiten mit allen Objekten. Ihre
Anwendung zeigt häufig ein „verwirrendes Ergebnis“, z.B.149:
public class GleichheitsTest
{
public static void main(String[] args)
{
Integer i1 = new Integer(13);
Integer i2 = new Integer(13);
System.out.println(i1 == i2); // false
System.out.println(i1 != i2); // true
}
}
Der Inhalt der Objekte ist zwar gleich, die Referenzen auf die Objekte sind nicht
gleich. Falls der aktuelle Infalt auf Gleichheit überprüft werden soll, steht die Methode
equals()150 bereit, z.B.:
public class EqualsMethode
{
public static void main(String[] args)
{
Integer i1 = new Integer(13);
Integer i2 = new Integer(13);
System.out.println(i1.equals(i2));
}
}
// true
2.1.5.7 Logische Vergleichsoperatoren
Die logischen Vergleichsoperatoren werden nur auf boolesche Operatoren
angewandt und erzeugen nur boolesche Ergebnisse.
Operator
&&
||
Beschreibung
Logischer AND-Operator
Logischer OR-Operator
148
Die Werte zweier Variablen vom Typ char können ebenfalls mit den Vergleichsoperatoren verglichen
werden. Es werden die Variablen vom Typ char bei der Verwendung eines Vergleichsoperators wie ganze 16Bit-Zahlen entsprechend ihrer Unicode-Codierung behandelt.
149 Vgl. pr21500
150 Viele in der Java-Bibliorhek bereitgestellte Klassen implementieren die Methode equals()
152
Programmieren in Java
!
Logischer NOT-Operator
Abb.: Logische Vergleichsoperatoren
2.1.6 Kommentare, eingebettete Dokumentation
Es gibt drei Arten von Kommentaren
- Einzeilige Kommentare beginnen mit // und enden mit dem Ende der Zeile
- Mehrzeilige Kommentare (Blockkommentare) beginnen mit /* und enden mit */. Sie können sich über
mehrere Zeilen erstrecken.. Blockkommentare dürfen nicht geschachtelt sein.
- Dokumentationskommentare beginnen mit /** und enden mit */. Sie können sich ebenfalls über
mehrere Zeilen erstrecken und sind spezielle Kommentare, die für das javadoc-System benutzt
werden. Javadoc dient zum Erzeugen von API-Dokumentationen aus anderem Java-Quellcode.
Das JDK verfügt über das Dokumentations-Tool javadoc, das auf der Basis von
speziellen Kommando-Tags innerhalb einer Java-Quelltextdatei eine HTML-Datei als
API-Dokumentation der angegebenen Datei151 oder des Pakets erzeugt.
Tag
@see [Klassenname]
@see
[Klassenname]#[Methodenname]
@param [Parametername]
[Beschreibung]
@version [Text]
@author [Name]
@return [Beschreibung]
@exception [Klassenname]
@deprecated [Ausdruck]
@since [Text]
Beschreibung
Dieses tag erzeugt einen Verweis in der HTML-Datei zu der
Klasse, die von Klassenname spezifiziert wird. Dabei kann der
Klassenname auch eine vollständige Pfadangabe sein
Dieses Tag erzeugt einen Verweis („See also“-Link) in der HTMLDatei zu der Methode (innerhalb der mit Klassenname
spezifizierten Klasse), die von Methodenname spezifiziert wird
Dieses Tag dient zur Dokumentation der Parameter.
Parametername ist selbsterklärend, und die Beschreibung
spezifizeirt den Parameter. Geht die Beschreibung über eine Zeile
hinaus, so wird dies in der nächsten Zeile fortgeführt.
Mit diesem Tag kann die Version des Programms spezifiziert
werden.
Diese Tag fügt den Namen des Autors in die HTML-Datei ein
Mit diesem Tag kann der wert beschrieben werden, der von einer
Methode zurückgegeben wird
Dieses Tag erzeugt einen Link auf Ausnahmen, die von der
Klasse, die durch Klassenname spezifiziert ist, erzeugt werden.
Neu seit Version 1.1. Das Tag markiert eine Klasse, ein Interface,
ein Feld oder eine Methode als nicht verwendbar in weiteren
Anwendungen´. Bereits existierender Code wird trotz dieser
kennzeichnung weiter kompiliert und laufen, aber der Compiler
wird eine Warnung generieren,
Spezifiziert, wann das Release erstellt wurde.
Abb.: Die javadoc-Tags
Die Syntax von javadoc:
javadoc [Optionen] [Dateiname]
javadoc [Klassenname] [Paketname]
Falls ein Paketname angegeben wurde, dann dokumentiert javadoc alle JavaQuelldateien innerhalb des aktuellen Verzeichnisses und anschließend das
151
vgl. 1.3.2.2, 3. Aufgabe
153
Programmieren in Java
dazugehörige Paketverzeichnis152 relatin zum CLASSPATH. Für jede Klasse wird ein
eigenes HTML-Dokument erzeugt und für die Klassen innerhalb eines Pakets ein
HTML-Index generiert.
Option
-version
-classpath
[Verzeichnis]
-v
-verbose
-d [Verzeichnis]
-public
-protected
-private
-package
-Jflag
-notree
-doencoding [Name]
-sourcepath [Pfad]
Beschreibung
Diese Option veranlaßt javadoc, die version des JDK anzuzeigen.
Diese Angabe korrespondiert wie bei den anderen JDK-Komponenten mit der
Umgebungsvariablen CLASSPATH und teilt in diesem Fall javadoc mit, in
welchen Verzeichnissen nach den Quelltext-Dateien zu suchen ist. Die
Verzeichnisse werden in der Unix-Syntax durch Doppelpunkte getrennt,
Windows benutzt das Semikolon. Die angegebenen Pfade werden in der
Reihenfolge ihres Auftretens durchsucht.
Identisch mit der Option –y
Mit dieser Option wird javadoc gezeigt, in welchem Verzeichnis sich das HTMLDokument zur Dokumentation nach der Generierung befinden soll.
Normalerweise wird das aktuelle Verzeichnis verwendet.
Zeigt nur die öffentlichen elemente an
Zeigt die geschützten und die öffentlichen Klassen und Methoden an
Zeigt alle Klassen und Methoden an
Zeigt die Packages, die geschützten und öffentlichen Klassen und Methoden an
Gibt ein Flag direkt an das Runtime-System weiter
Wenn diese Option gesetzt ist, wird keine Klassenhierarchie generiert.
Gibt den Suchpfad für zu dokumentierende Quelldateien an. Beeinflußt das
laden von Klassendateien nicht. Wird die Option nicht verwendet, dokumentiert
javadoc defaultmäßig nur die im aktuellen verzeichnis vorhandenen Klassen
und Packages im Tree-Bereich der Dokumentation. Sollen Klassen und
Packages in anderen verzeichnissen mit dokumentiert werden, müssen diese
hier angegeben werden.
Abb.: Die javadoc-Optionen
152
als Paketname zu verstehen, nicht als physikalisches Verzeichnis
154
Programmieren in Java
2.2 Typen
Ein Typ gibt in einer Computersprache an, wie etwas (z.B. eine Variable) im Speicher
des Rechners dargestellt wird. In Java gibt es vier verschiedene Arten von Typen:
„primitive Datentypen“, Datenfelder (Arrays) , Klassen und Schnittstellen
2.2.1 Primitive Datentypen
Java besitzt 8 primitive Datentypen.
Bezeichnung
byte
Typ
8 Bits
short
16 Bits
int
32 Bits
long
64 Bits
float
32 Bits
double
64 Bits
char
16 Bits
boolean
1 Bit
Beschreibung
Kleinster Wertebereich. Mit Vorzeichen. Wird zum Darstellen von
Ganzzahlwerten (ganzzahliges Zweierkomplement) von (-2 hoch 7 = 128) bis (+2 hoch 7 = 128) verwendet.
Wertebereich mit Vorzeichen. Wird zum Darstellen von
Ganzzahlwerten (ganzzahliges Zweierkomplement) von (-2 hoch 15 =
-32167) bis (+2 hoch 15 - 1 = 32.167) verwendet.
Standardwertebereich. Wird zum Darstellen von Ganzzahlwerten
(ganzzahliges Zweierkomplement) von (-2 hoch 31 = -2.147.483.648)
bis (+2 hoch 31 = 2.147.483.647) verwendet.
Größter Wertebereich. Wird zum Darstellen von Ganzzahlwerten
(ganzzahliges Zweierkomplement) von (-2 hoch 63) bis (+2 hoch 63)
verwendet.
Kürzester Wertebereich mit Vorzeichen zur Darstellung von
Gleitpunktzahlenwerten. Dies entspricht Gleitpunktzahlen mit
einfacher Genauigkeit, die den IEEE-754-1985-Standard benutzen.
Es existiert ein Literal zur Darstellung von plus/minus unendlich sowie
der Wert NaN (Not a Number) zur Darstellung von nicht definierten
Ergebnissen
Größter Wertebereich mit Vorzeichen zur Darstellung von
Gleitpunktzahlenwerten. Der Wertebereich liegt zwischen +/-10 hoch
317. Auch diese Gleitpunktzahlen benutzen den IEEE-754-1985Standard. Es existiert wie beim Typ float ein Literal zur Darstellung
von plus/minus unendlich sowie der Wert NaN (Not a Number) zur
Darstellung von nicht definierten Ergebnissen
Darstellung eines Zeichens des Unicode-Zeichensatzes. Zur
Darstellung von alphanumerischen Zeichen wird dieselbe Kodierung
wie beim ASCII-Zeichensatz verwendet, aber das höchste Bit ist auf 0
gesetzt. Der Datentyp ist als einziger primitiver Java-Datentyp
vorzeichenlos. Der Maximalwert ist \uFFFF.
Einer char-Variablen kann, da sie 2 Bytes lang ist, eine Ganzzahl
zwischen 0 und 65535 ohne Konvertierung zugewiesen werden. Die
Umkehrung – also die Zuweisung von char-Zeichen an andere
Datentypen – funktioniert nur für int problemlos. Für andere
Datentypen ist keine direkte Zuweisung möglich.
Der boolschesche Datentyp umfaßt die Werte: true oder false
(Defaultwert). Logische Vergleiche sind in Java vom Typ boolean.
Auch boolesche Variablen sind dem Datentyp boolean zugeordnet.
Werte vom Typ boolean sind zu allen anderen Datentypen
inkompatibel und lassen sich nicht durch Casting in andere Typen
überführen.
Abb.: Primitive Datentypen der Java-Sprache
155
Programmieren in Java
2.2.2 Operationen mit primitiven Datentypen
2.2.2.1 Operationen mit booleschen Variablen
Operation
=
Name
Zuweisung
==
Gleichheit
!=
Ungleichheit
!
&
|
^
Logisches NOT
AND
OR
XOR
&&
Logisches AND
||
Logisches OR
?:
if-then-else
Bedeutung
Einer booleschen Variable wird der Wert true oder false
zugewiesen
Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide boolesche
Operanden denselben Wert (true oder false) haben.
Anderenfalls wird false zurückgegeben.
Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide boolesche
Operanden unterschiedliche Werte (true oder false)
haben. Anderenfalls wird false zurückgeben.
Falls der Operand false ist, wird true zurückgegeben
Rückgabewert ist true, falls beide Operanden true sind
Rückgabewert ist false, falls beide Operanden false sind.
Rückgabewert ist true, falls genau ein Operand true ist
(exklusives Oder)
Rückgabe von true nur dann, wenn die beiden Operanden
true sind.
Rückgabe von false nur dann, wenn beide Operanden
false sind.
Diese Operation benötigt einen booleschen Ausdruck vor dem
Fragezeichen. Falls er true ist, wird der Wert vor dem
Doppelpunkt zurückgegeben, ansonsten der Wert hinter dem
Doppelpunkt.
Abb.: Operationen mit booleschen Variablen
2.2.2.2 Operationen mit Zeichenvariablen
Zeichenvariablen können Operanden in jeder ganzzahligen Operation sein und
werden wie ganze 16-Bit-Zahlen ohne Vorzeichen behandelt.
Operation
=
==
Name
!=
Ungleichheit
<,<=,>,>=
+,-
Relational
Vorzeichen
+,-,*,/
Binäre Arithmetik
+=,-=,*=,/=
Zuweisung
++,--
Binäre Arithmetik
Bedeutung
Einer Zeichenvariablen wird ein Wert zugewiesen
Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden
denselben Wert (Unicode-Werte stimmen überein) haben.
Anderenfalls wird false zurückgegeben.
Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide boolesche
Operanden unterschiedliche Werte ( Bezogen auf die
Unicode-Darstellung) haben. Anderenfalls wird false
zurückgeben
Operatoren zum Vergleich innerhalb einer Kontrollstruktur.
Vorzeichenoperatoren bei einem Operanden
Die Zeichenvariablen gehen in die Berechnung mit ihren
Unicode-Werten ein.
Additions-,
Subtraktions-,
Multiplikations-,
DivisionsZuweisungen
Inkrement- und Dekrement-Operatoren für den Unicode-Wert
von Zeichenvariablen
156
Programmieren in Java
<<,>>,>>>
<<=,>>=,
>>>=
~
&
|
^
&=,|=,^=
Verschiebung
Bitweise Verschiebeoperatoren: Operatoren für bitweises
Verschieben nach links, für bitweises Verschieben nach
rechts und für das bitweise Verschieben nach rechts mit
Füllnullen.
Verschiebung
und Bitweise Verschiebungs- und Zuweisungsoperatoren (nach
Zuweisung
links, nach rechts und nach rechts mit Füllnullen
Bitweises NOT
Einstellige bitweise Komplementbildung. Wenn ein Zeichen
komplementiert wird, dann werden alle seine Bits invertiert.
Bitweises AND
Falls AND mit 2 Zeichenvariablen benutzt wird und das
Ergebnis in einem dritten Zeichen abgelegt wird, dann hat das
resultierende Zeichen nur für die Bits den Eintrag 1, wenn alle
beiden Operanden an der gleichen Stelle Bits mit dem Wert 1
hatten
Bitweises OR
Falls OR mit 2 Zeichenvariablen benutzt wird und das
Ergebnis in einem dritten Zeichen abgelegt wird, dann hat das
resultierende Zeichen nur für die Bits den Eintrag 1, wenn
einer der Operanden an dieser Position eine 1 hatte
Bitweises exklusives Falls XOR mit 2 Zeichenvariablen benutzt wird und das
OR
Resultat in einem 3. Zeichen abgelegt wird, dann hat das
resultierende Zeichen nur für die Bits den Eintrag 1, wenn das
zugehörige Bit in genau einem der beiden Operanden gesetzt
ist.
Bitweise Zuweisung
Bitweise AND-, OR-, exklusive OR (XOR)- und
Zuweisungsoperatoren
Abb.: Operationen mit Zeichenvariablen
2.2.2.3 Operationen mit Gleitpunktzahlen
Operation
=,+=,-=,/=
==
Name
Zuweisung
Gleichheit
!=
Ungleichheit
<,<=,>,>=
+,+,-,*,/
Relational
Vorzeichen
Binäre Arithmetik
+=,-=,*=,/=
Zuweisung
++,--
Binäre Arithmetik
Bedeutung
Einer Gleitpunktzahl wird ein Wert zugewiesen
Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden
denselben
Wert
haben.
Anderenfalls
wird
false
zurückgegeben
Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden
unterschiedliche Werte haben. Anderenfalls wird false
zurückgeben
Operatoren zum Vergleich innerhalb einer Kontrollstruktur
Vorzeichenoperatoren bei einem Operanden
Additions-,
Subtraktions-,
Multiplikations-,
DivisionsOperatoren
Additions-,
Subtraktions-,
Multiplikations-,
DivisionsZuweisungen
Inkrement- und Dekrementoperatoren für den Wert der
Variablen.
Abb.: Operationen mit den Typen float und double
Java erzeugt keine Ausnahmen bei der Benutzung der Gleitpunktarithmetik. Ein
Überlauf153 oder das Teilen aller möglichen Zahlen (außer „Null durch Null“) führt zur
Ausgabe von positiven bzw. negativen „unendlichen“ Werten. Das Teilen von „Null
153
d.h.: Größeres Ergebnis von einer Operation als durch den Wertebereich des jeweiligen Typs ausgedrückt
werden kann.
157
Programmieren in Java
durch Null“ ergibt den Wert „NaN“ (keine Zahl). Ein Unterlauf154 gibt einen speziellen
Wert aus: „positiv oder negativ Null“. Dieser Wert kann mit Vergleichsoperatoren
ausgewertet werden (bewirkt false).
2.2.2.4 Operationen mit ganzzahligen Variablen (bzw. ganzzahligen Ausdrücken)
Operation
=,+=,-=,/=
==
!=
<,<=,>,>=
+,+,-,*,/
+=,-=,*=,/=
++,-<<,>>,>>>
<<=,>>=,
>>>=
~
&
|
^
&=,|=,^=
Name
Zuweisung
Gleichheit
Bedeutung
Einer ganzzahligen Variablen wird ein Wert zugewiesen
Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden
denselben
Wert
haben.
Anderenfalls
wird
false
zurückgegeben
Ungleichheit
Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden
unterschiedliche Werte haben. Anderenfalls wird false
zurückgeben
Relational
Weitere Operatoren zum Vergleich innerhalb einer
Kontrollstruktur
Vorzeichen
Vorzeichenoperatoren bei einem Operanden
Binäre Arithmetik
Additions-,
Subtraktions-,
Multiplikations-,
DivisionsOperatoren
Zuweisung
Additions-,
Subtraktions-,
Multiplikations-,
DivisionsZuweisungen
Binäre Arithmetik
Additions-,
Subtraktions-,
Multiplikations-,
DivisionsOperatoren
Bitweise Verschiebeoperatoren: Operatoren für bitweises
Verschieben nach links, für bitweises Verschieben nach
rechts und für das bitweise Verschieben nach rechts mit
Füllnullen
Bitweise Verschiebungs- und Zuweisungsoperatoren (nach
links, nach rechts und nach rechts mit Füllnullen
Bitweises NOT
Einstellige bitweise Operation
Bitweises AND
Falls AND mit 2 Ganzzahlen benutzt wird und das Ergebnis in
einem dritten Ganzzahl abgelegt wird, dann hat die
resultierende Ganzzahl nur für die Bits den Eintrag 1, wenn
alle beiden Operanden an der gleichen Stelle Bits mit dem
Wert 1 hatten
Bitweises OR
Falls OR mit 2 Ganzzahlen benutzt wird und das Ergebnis in
einer dritten Ganzzahl abgelegt wird, dann hat die
resultierende Ganzzahl nur für die Bits den Eintrag 1, wenn
einer der Operanden an dieser Position eine 1 hatte
Biweises exklusives Falls XOR mit 2 Ganzzahlen benutzt wird und das Resultat in
OR
einer 3. Ganzzahl abgelegt wird, dann hat die resultierende
Ganzzahl nur für die Bits den Eintrag 1, wenn das zugehörige
Bit in genau einem der beiden Operanden gesetzt ist.
Bitweise Zuweisung
Inkrement- und Dekrementoperatoren für den Wert der
Variablen
Abb.: Operationen mit ganzzahligen Variablen
Ganzzahlige Variable werden in Java als „Zweierkomplement“-Zahlen mit Vorzeichen
verwendet.
154 d.h.: kleineres Ergebnis (- außer Null -) von einer Operation als durch den Wertebereich des jeweiligen Typs
ausgedrückt werden kann.
158
Programmieren in Java
2.2.3 Datenfelder (Arrays)
Ein Datenfeld (Array) ist eine Ansammlung von Objekten eines bestimmten Typs1,
die über einen laufenden Index adressierbar sind. Gegenüber „normalen Objekten“
haben Arrays zwei wesentliche Einschränkungen:
1. Arrays haben keine Konstruktoren. Statt dessen wird der Operator new mit spezieller Syntax
aufgerufen.
2. Es können keine Subklassen eines Datenfelds definiert werden.
Datenfelder gehören zu den Referenzvariablen1. Arrays können jeden Wertetyp
(primitiver Typ oder Objekt) enthalten, jedoch können in einem Array keine
unterschiedlichen Typen gespeichert werden.
Arrays in Java sind Objekte und unterscheiden sich von Datenfeldern in anderen
Programmiersprachen durch folgende Merkmale:
- Array-Variable sind Referenzen
- Arrays besitzen Methoden und Instanz-Variable
- Arrays werden zur Laufzeit erzeugt
2.2.3.1 Deklarieren, Erstellen von Array-Objekten
In Java umfaßt das Erstellen eines Datenfelds drei Schritte:
1. Deklaration einer Variablen zur Aufnahme eines Datenfelds.
Array-Variable zeigen den Objekttyp an, den das Datenfeld aufnimmt und den namen
des Arrays, gefolgt von leeren eckigen Klammern „[]“, z.B: int x[]. Alternativ
dazu kann eine Array-Variable auch so (Klammern nach dem Typ) festgelegt sein,
z.B. int[] x.
2. Erstellen eines neuen Array-Objekts und Zuweisen einer Array-Variablen.
Das kann erfolgen
- mit new, z.B. String[] namen = new String[10];
Hier wird ein neues Datenfeld von Strings mit 10 Elementen erstellt. Beim Erstellen
eines Array-Objekts mit new ist anzugeben, wieviele Elemente das Array
aufnehmen soll. Alle Elemente des Array werden automatisch initialisiert (0 für
numerische Datenfelder, false für boolesche, ‘\0‘ für Zeichen-Arrays und „null“
für alles andere.
Bsp.: Erzeugen von Datenfeldern mit „new“
a) Aufnahme von Werten primitiver Typen
import java.util.*;
public class FeldPrim
{
static Random rand = new Random();
static int zRand(int mod)
{
return Math.abs(rand.nextInt()) % mod;
}
public static void main(String[] args)
155
Es kann sich dabei um primitive Variablentypen (byte, char, short, int, long, float, double, boolean), aber
auch um andere Datenfelder (verschachtelte Arrays) oder Objekte handeln.
156 Es gibt drei Arten von Referenzvariablen: Klassen, Schnittstellen und Datenfelder
159
Programmieren in Java
{
int[] a;
// Die Groesse des Array wird zur Laufzeit
// zufaellig bestimmt
a = new int[zRand(20)];
ausgabe("Laenge von a = " + a.length);
for (int i = 0; i < a.length; i++)
ausgabe("a[" + i + "] = " + a[i]);
}
static void ausgabe(String s)
{
System.out.println(s);
}
}
b) Aufnahme von Objekten
Ein Datenfeld, dessen Komponenten keine primitiven Typen aufnehmen
sollen, muß immer mit „new“ gefüllt werden.
import java.util.*;
public class FeldObj
{
static Random rand = new Random();
static int zRand(int mod)
{
return Math.abs(rand.nextInt()) % mod;
}
public static void main(String[] args)
{
Integer[] a;
// Die Groesse des Array wird zur Laufzeit
// zufaellig bestimmt
a = new Integer[zRand(20)];
ausgabe("Laenge von a = " + a.length);
for (int i = 0; i < a.length; i++)
{
a[i] = new Integer(zRand(500));
ausgabe("a[" + i + "] = " + a[i]);
}
}
static void ausgabe(String s)
{
System.out.println(s);
}
}
c) Variable Argumente
Das zweite Format in der Feldinitialisierung bestimmt eine bequeme
syntaktische Form für den „Aufruf von Methoden“, die den gleichen Effekt
besitzt wie die „Argumentenliste in C“. Über eine solche Argumentenliste
kann eine unbekannte Menge von Argumenten eines unbekannten Typs
behandelt werden. Da alle Klassen von der Klasse Object abstammen,
kann eine Methode mit einem Datenfeld als Argument verwendet werden,
das aus Objekten vom Typ Object besteht, z.B.
class A
{
int i;
}
public class VarArgs
{
static void f(Object[] x)
160
Programmieren in Java
{
for (int i = 0; i < x.length; i++)
System.out.println(x[i]);
}
public static void main(String[] args)
{
f(new Object[] {
new Integer(40), new VarArgs(),
new Float(3.14), new Double(11.11)
});
f(new Object[] { "eins", "zwei", "drei" });
f(new Object[] { new A(), new A(), new A() });
}
}
- durch direktes Initialisieren des Array-Inhalts, z.B.: String[] namen =
{"juergen", "bernd", "liesel", "dieter", "hans", "vera",
"christian", "theo", "emil", "karl"};
Alle Elemente der in der geschweiften Klammer stehenden Elemente müssen vom
gleichen Typ sein
3. Speichern von Elementen im Array
Zur Speicherung eines Werts in einem Array, wird der Array-Ausdruck „subscript“
benutzt, z.B.: x[subscript] . „subscript“ ist die Stelle (Position) für den Zugriff
auf eine Datenfeld-Element. Das Array-Subskript muß vom Typ int sein. Wegen der
vom Compiler vorgenommenen automatischen Typenkonvertierungen sind auch
short, byte, char zulässig. Indexausdrücke werden vom Laufzeitsystem auf
Einhaltung der Array-Grenzen überprüft.
2.2.3.2 Zugriff auf Datenfeld-Elemente, Ändern von Datenfeld-Elementen
Datenfeld-Subskripte beginnen mit 0. Alle Array-Subskripte werden beim Kompilieren
geprüft, ob sie sich innerhalb der Grenzem des Array befinden (Größer als 0, kleiner
als die Länge des Datenfelds).
String ort[] = new String[10];
ort[10] = "Dinkesbuehl";
ist falsch (Fehler beim Kompilieren). Das in „ort“ gespeicherte Array hat nur 10 ab 0
numerierte Elemente.
Wird das Array-Subskript zur Laufzeit berechnet und resultiert daraus ein Wert
außerhalb der Array-Grenzen, dann erzeugt der Java-Interpreter einen Fehler157. Die
Länge eines Datenfelds kann mit der Instanzvariablen length getestet werden, z.B.:
int laenge = ort.length;.
Zum Zuweisen eines Werts wird hinter dem Zugriffsausdruck die
Zuweisungsanweisung gestellt. Falls einem Array-Element ein Wert zugewiesen
wird, dann wird auf das betreffende Objekt eine Referenz erzeugt. Datenfelder mit
primitiven Typen (z.B. int, float) kopieren die Werte von einem Element in ein
anderes.
157
Er weist auf eine „Ausnahme“ hin.
161
Programmieren in Java
2.2.3.3 Anwendungen mit eindimensionaler Datenfeldern
1. Sammeln
Datenfelder sind die wohl nützlichsten Objekte in Java, mit denen Objekte in leicht
zugänglichen Listen gesammelt werden können.
2. Suchen
a) sequentielle Suche
Aufgabenstellung: Gegeben ist eine ganze Zahl (Schlüssel schl) und ein Feld (Array) mit
ganzzahligen Werten, die in diesem Array beliebig verteilt sind. Such die Indexposition, für die gilt
„a[i] == schl“. Ist die Suche ergebnislos, dann gib „Nicht Gefunden“ zurück.
b) binäre Suche
Aufgabenstellung: Gegeben ist eine ganze Zahl (Schlüssel schl) und ein Array a mit ganzzahligen
Werten, die in diesem Feld in aufsteigender Folge vorliegen. Suche die Indexposition, für die gilt:
a[i] == schl“. Ist die Suche ergebnislos, dann gib „Nicht Gefunden“ zurück.
Lösungsverfahren: Es wird geprüft, ob der gesuchte Schlüssel sich in der Mitte des Array befindet.
Falls dies nicht der Fall ist, wird „schl < a[mitte]“ im linken Teil (von der Mitte aus gesehen) und
für „schl > a[mitte]“ im rechten Teil des Array gesucht. Diese Strategie kann fortgesetzt werden,
bis die Suche erfolgreich war oder der Schlüssel nicht gefunden wurde.
3. Sortieren158
Aufgabenstellung: Schreibe ein Programm, das die in einem Datenfeld („array“) gespeicherten ganzen
Zahlen nach einem einfachen Sortierverfahren („Sortieren durch Austauschen, Bubble Sort“) in
aufsteigende Sortierreihenfolge bringt.
Lösungsverfahren (Algorithmus): Man kann jeweils den ersten Schlüssel gegen alle weiteren
Schlüssel im Arbeitsspeicherfeld vergleichen und so an der ersten Position den kleinsten Schlüssel
nach (N-1) Vergleichen ermitteln. Danach vergleicht man jeweils den Schlüssel aus der zweiten
Position mit allen nachfolgenden Schlüsslwerten. Nach (N-2) Vergleichen steht der zweitkleinste
Sachlüssel an der zweiten Position. Bei Fortsetzung dieser Verfahrensweise ist schließlich nur noch
ein einziger Schlüssel vorhanden, der dann auf der richtigen, der letzten Position steht.
Bsp.:
1
37 22 18 9 9
22 37 37 37 37
18 18 22 22 22
9
9 9 18 18
25 25 25 25 25
2
9
22
37
18
25
3
9
18
37
22
25
9
18
37
22
25
9
18
22
37
25
4
9
18
22
37
25
9
18
22
25
37
Abb.: Tabelle mit 5 Schlüsseln zur Demonstration des Bubble-Sort
Vorschlag für die Implementierung:
158
import java.util.*;
class BubbleSort
{
public static void main(String args[])
{
int[] x;
x = new int[10];
pr22301
162
Programmieren in Java
Random zufallsZahl = new Random();
// Initialisieren des Array x
for (int i = 0; i < 10; i++)
{
x[i] = zufallsZahl.nextInt();
}
// Ausgabe des noch nicht sortierten x
System.out.println("Vor dem Sortieren:");
for (int i = 0; i < 10; i++)
{
System.out.println("x["+i+"] = " + x[i]);
}
boolean sortiert = false;
// Sortiere das Feld x
while (!sortiert)
{
sortiert = true;
for (int i=0; i < 9; i++)
{
if (x[i] > x[i+1])
{
int temp = x[i];
x[i] = x[i+1];
x[i+1] = temp;
sortiert = false;
}
}
}
// Gib das sortierte Feld x aus
System.out.println();
System.out.println("Nach dem Sortieren:");
for (int i = 0; i < 10; i++) {
System.out.println("x["+i+"] = " + x[i]);
}
}
}
163
Programmieren in Java
2.2.3.4 Mehrdimensionale Datenfelder
Java definiert mehrdimensionale Arrays durch Arrays von Arrays.
Zweidimensionale Datenfelder
Ein zweidimensionale Feld entspricht einer zweidimensionalen Wertetabelle, z.B.:
Z0
Z1
Z2
Z3
Z4
S0 S1 S2 S3
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
Das vorliegende Datenfeld umfaßt 5 Zeilen und 4 Spalten. Ein Datenfeld „M“ mit 4
Zeilen und 5 Spalten ist folgendermaßen aufgebaut.
M[0][0]
M[1][0]
M[2][0]
M[3][0]
M[0][1]
M[1][1]
M[2][1]
M[3][1]
M[0][2]
M[1][2]
M[2][2]
M[3][2]
M[0][3]
M[1][3]
M[2][3]
M[3][3]
M[0][4]
M[1][4]
M[2][4]
M[3][4]
Zweidimensionale Datenfelder werden, wie das folgende Beispiel zeigt, auf gleiche
Weise erstellt und initialisiert wie eindimensionale Felder.
class Einheitsmatrix
{
publich static void main(String args[])
{
double[][] EM;
EM = new double[4][4];
for (int zeile = 0; zeile < 4; zeile++)
{
for (int spalte = 0; spalte < 4; spalte++)
{
if (zeile != spalte)
{
EM[zeile][spalte] = 0.0;
}
else {
EM[zeile][spalte] = 1.0;
}
}
}
}
}
Da mehrdimensionale Arrays als geschachtelte Arrays gespeichert werden, ist es
möglich, „nicht-rechteckige“ Arrays zu erzeugen, z.B.:
public class KeinRechteckFeld
{
public static void main(String args[])
{
int a[][] = { {0},
164
Programmieren in Java
{1,2},
{3,4,5},
{6,7,8,9}
};
for (int i = 0; i < a.length; i++)
{
for (int j = 0; j < a[i].length; j++)
{
System.out.print(a[i][j] + " ");
}
System.out.println();
}
}
}
Beispiele
1. Das 8-Damen-Problem159
Aufgabenstellung: Acht Damen sollen so auf einem Schachbrett positioniert werden, daß sie sich nicht
schlagen können, d.h.: Zwei Damen stehen nie in derselben Zeile oder Spalte oder Diagonale.
Algorithmus zur Lösung: Zu Beginn wird ein zweidimensionales Feld mit 0 gefüllt.
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
Danach wird zufällig im zweidimensionalen Feld eine „Dame“ gesetzt. Das Setzen der Dame wird
durch eine 9 markiert.
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
9
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
Durch das Positionieren der Dame sind Positionierungen von weiteren Damen in derselben Zeile und
Spalte wie die soeben positionierte Dame, aber auch in den zugehörigen Diagonalen nicht erlaubt.
Die nicht mehr zulässigen Positionen werden mit 1 markiert.
0
1
0
1
0
1
0
0
159
0
0
1
1
1
0
0
0
1
1
1
9
1
1
1
1
0
0
1
1
1
0
0
0
0
1
0
1
0
1
0
0
1
0
0
1
0
0
1
0
0
0
0
1
0
0
0
1
0
0
0
1
0
0
0
0
vgl. pr22305
165
Programmieren in Java
Damen dürfen jetzt nur noch auf die mit 0 markierten Komponenten des zweidimensionalen Felds
gebracht werden. Das geschieht solange bis alle Komponenten des zweidimensionalen Felds mit 1
oder 9 gefüllt sind. Wurden beim Füllen des Felds insgesamt 8 Damen positioniert, dann wurde eine
Lösung erreicht, die so aussehen könnte:
1
1
1
1
9
1
1
1
1
9
1
1
1
1
1
1
1
1
1
9
1
1
1
1
1
1
1
1
1
9
1
1
1
1
1
1
1
1
1
9
1
1
9
1
1
1
1
1
9
1
1
1
1
1
1
1
1
1
1
1
1
1
9
1
Vorschlag zur Implementierung:
import java.util.*;
public class Damen
{
// Konstanten
final int N = 8;
// Variable
boolean fuellen;
boolean ausgeben;
Random rand = new Random();
int zeile, spalte;
int[][] brett;
int aktZahl = 0;
int[] positionen = new int[N];
// Methoden
int zRand(int mod)
{
return Math.abs(rand.nextInt() % mod);
}
public void initialisiere()
{
fuellen = false;
ausgeben = false;
aktZahl = 0;
brett = new int[N][N];
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
{
brett[i][j] = 0;
}
}
public void nimmPosition()
{
do {
zeile = zRand(N);
// System.out.println(zeile);
spalte = zRand(N);
// System.out.println(spalte);
} while (brett[zeile][spalte] != 0);
brett[zeile][spalte] = 9;
aktZahl++;
}
public void bewertePosition()
{
for (int i = 0; i < 8; i++)
{
if (i != spalte) brett[zeile][i] = 1;
}
166
Programmieren in Java
for (int i = 0; i < 8; i++)
{
if (i != zeile) brett[i][spalte] = 1;
}
int lDzeile = zeile;
int lDspalte = spalte;
// Beruecksichtigung der nach links laufenden Diagonale
while ((lDzeile > 0) && (lDspalte > 0))
{
lDzeile--;
lDspalte--;
}
do {
if ((lDzeile != zeile) && (lDspalte != spalte))
{
brett[lDzeile][lDspalte] = 1;
}
lDzeile++;
lDspalte++;
} while ((lDzeile < 8) && (lDspalte < 8));
// ausgabe();
int rDzeile = zeile;
int rDspalte = spalte;
// Beruecksichtiung der nach rechts laufenden Diagonale
while ((rDzeile > 0) && (rDspalte < 7))
{
rDzeile--;
rDspalte++;
}
do {
if ((rDzeile != zeile) && (rDspalte != spalte))
{
brett[rDzeile][rDspalte] = 1;
}
rDzeile++;
rDspalte--;
} while ((rDzeile < 8) && (rDspalte >= 0));
}
public void pruefe()
{
int nullen = 0;
int anzDamen = 0;
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
{
if (brett[i][j] == 0) nullen++;
if (brett[i][j] == 9) anzDamen++;
}
if (nullen == 0)
fuellen = true;
if (anzDamen == 8) ausgeben = true;
}
public void ausgabe()
{
System.out.println();
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
System.out.print(brett[i][j] + " ");
}
System.out.println();
}
System.out.println();
int k = 0;
for (int i = 0; i < brett.length; i++)
for (int j = 0; j < brett[i].length; j++)
167
Programmieren in Java
{
if (brett[i][j] == 9) positionen[k++] = j;
}
for (k = 0; k < N; k++)
System.out.print(positionen[k]+" ");
System.out.println();
}
public void erzeugeDamen()
{
do
{
initialisiere();
do {
nimmPosition();
// ausgabe();
bewertePosition();
// ausgabe();
if (aktZahl > 4) pruefe();
} while (!fuellen);
} while (!ausgeben);
ausgabe();
}
}
public class Damentest
{
public static void main(String args[])
{
Damen d = new Damen();
d.erzeugeDamen();
}
}
2. Pascal’sches Dreieck
1
1
1
1
1
1
1
2
3
4
5
6
1
1
3
6
10
15
1
4
10
20
1
5
15
1
6
1
Das Pascal'sche Dreieck wird mit Hilfe eines nicht rechteckigen Arrays abgebildet.
Für die ersten Elemente kann geschrieben werden:
int element[][] = {
{1},
{1,1},
{1,2,1}
};
In der folgenden Implementierung wird zu jeder Ebene dynamisch ein Feld mit der
passenden Länge hinzugefügt.
Bsp:160
public class Pascal
160
vgl. pr22331, Pascal.java
168
Programmieren in Java
{
public static void main(String args[])
{
int dreieck[][] = new int[7][];
for (int i = 0; i < dreieck.length; i++)
{
dreieck[i] = new int[i+1];
for (int j = 0; j <= i; j++)
{
if ((j == 0) || (j == i))
dreieck[i][j] = 1;
else
dreieck[i][j] = dreieck[i - 1][j- 1] + dreieck[i - 1][j];
System.out.print(dreieck[i][j] + " ");
}
System.out.println();
}
}
}
Multidimensionale Datenfelder
Zweidimensionale Datenfelder bilden nicht das Ende. Java kann Felder mit 3, 4 oder
mehr Dimensionen unterstützen. Es handelt sich dabei allerdings um ein Array mit
Arrays (die wiederum Arrays enthalten können usw. über beliebig viele
Dimensionen)161.
Bsp.: Mehrdimensionale Felder
import java.util.*;
public class MehrdimFeld
{
static Random rand = new Random();
static int zRand(int mod)
{
return Math.abs(rand.nextInt()) % mod;
}
public static void main(String args[])
{
// 1. Bsp: Erzeugen eines mehrdimensionalen Felds
// mit Werten primitiver Typen
int[][] a1 = {
{ 1, 2, 3, },
{ 4, 5, 6, },
};
for (int i = 0; i < a1.length; i++)
for (int j = 0; j < a1[i].length; j++)
ausgabe("a1[" + i + "][" + j + "] = " + a1[i][j]);
// 2. Bsp.: Dreidimensionales Feld mit fester Laenge
int[][][] a2 = new int[2][2][4];
for (int i = 0; i < a2.length; i++)
for (int j = 0; j < a2[i].length; j++)
for (int k = 0; k < a2[i][j].length;k++)
ausgabe("a2[" + i + "][" + j + "][" + k + "] = "
+ a2[i][j][k]);
// 3. Bsp.: Dreidimensionales Feld mit variablen Laengen
int[][][] a3 = new int[zRand(7)][][];
for (int i = 0; i < a3.length; i++)
{
a3[i] = new int[zRand(5)][];
for (int j = 0; j < a3[i].length; j++)
161
Im Java werden multidimensionale Arrays streng genommen nicht unterstützt
169
Programmieren in Java
{
a3[i][j] = new int[zRand(5)];
}
}
for (int i = 0; i < a3.length; i++)
for (int j = 0; j < a3[i].length; j++)
for (int k = 0; k < a3[i][j].length;k++)
ausgabe("a3[" + i + "][" + j + "][" + k + "] = "
+ a3[i][j][k]);
// Mehrdimensionales Feld mit nicht primitiven Objekten
Integer[][] a4 = {
{ new Integer(1), new Integer(2) },
{ new Integer(3), new Integer(4) },
{ new Integer(5), new Integer(6) },
};
for (int i = 0; i < a4.length; i++)
for (int j = 0; j < a4[i].length; j++)
ausgabe("a4[" + i + "][" + j + "] = " + a4[i][j]);
// Array mit nicht primitiven Objekten der stueckweise
// aufgebaut wird
Integer[][] a5;
a5 = new Integer[3][];
for (int i = 0; i < a5.length; i++)
{
a5[i] = new Integer[3];
for (int j = 0; j < a5[i].length; j++)
a5[i][j] = new Integer(i * j);
}
for (int i = 0; i < a5.length; i++)
for (int j = 0; j < a5[i].length; j++)
ausgabe("a5[" + i + "][" + j + "] = " + a5[i][j]);
}
static void ausgabe(String s)
{
System.out.println(s);
}
}
2.2.3.5 Die Klasse Arrays
Seit dem JDK1.2 gibt es die Klasse Arrays im Paket java.util mit nützlichen
Methoden zum Zugriff auf Arrays:
public static void fill(int[] a, int wert)
public static int binarySearch(int[] a, int schl)
public static void sort(int[] a)
public static boolean equals(int[] a1, int [] a2)
Diese Methoden stehen auch in vergleichbaren Versionen für andere primitive
Datentypen zur Verfügung.
170
Programmieren in Java
2.3 Ausdrücke
Ein Ausdruck ist das Ergebnis einer Verknüpfung von Operanden und Operatoren
nach den syntaktischen Regeln der Sprache. Ausdrücke werden üblicherweise zur
Durchführung von Operationen (Manipulationen) an Variablen oder Werten
verwendet.
Ausdrücke gehören zu den kleinsten ausführbaren Einheiten eines Programms. Sie
dienen zur Verzuweisung an Variable, zur Durchführung numerischer berechnungen
und zur Formulierung logischer Bedingungen.
Ein Ausdruck besteht immer aus mindestens einem Operanden, auf dem der
Operator angewandt wird. Nach dem Typ der Operanden unterscheidet man
numerische, relationale, logische, bitweise Operatoren. Jeder Ausdruck hat einen
Rückgabewert, der durch die Anwendung des Operators auf die Operanden entsteht.
Der Typ des Rückgabewerts bestimmt sich aus den Typen der Operanden und der
Art des verwendeten Operators.
2.3.1 Arithmetische Ausdrücke
Jede Programmiersprache hat einen Mechanismus für arithmetische Berechnungen.
In Java werden solche Berechnungen in arithmetischen Ausdrücken durchgeführt.
2.3.2 Bewertung von Ausdrücken
Bei der Bewertung von Ausdrücken spielen Operatorassoziativität, Operatorvorrang
und Bewertungsreihenfolge eine Rolle.
Operatorassoziativität
Alle arithmetischen Operatoren assoziieren von links nach rechts, d.h.: Falls derselbe
Operator in einem Ausdruck mehr als einmal vorkommt, dann wird der am weitesten
links stehende zuerst bewertet, gefolgt von dem rechts daneben stehenden, usw. Die
Assoziativitätsregel bestimmt, wie Kombinationen des gleichen Operators bewertet
werden können.
Priorität
Java hält sich, wie die grundlegende Arithmetik, strikt an die Reglen der
Vorrangigkeit. Die multiplikativen Operatoren (*, / und %) haben Vorrang vor den
additiven Operatoren (+ und -). Immer wenn die Bewertungsreihenfolge von
Operatoren in einem Ausdruck geändert werden soll, müssen Klammern benutzt
werden. Jeder Ausdruck in Klammern wird zuerst bewertet. Der Vorrang der
einstelligen arithmetischen Operatoren steht über allen anderen arithmetischen
Operatoren.
171
Programmieren in Java
Bewertungsreihenfolge
Die Vorrangregeln helfen bei der Bewertung, welche Operatoren in einem Ausdruck
zuerst benutzt werden und welche Operanden zu welchen Operatoren gehören. Die
Regeln für die Bewertungsreihenfolge helfen festzulegen, wann welche Operanden
bewertet werden. Die drei folgenden Regeln bestimmen, wie ein Ausdruck bewertet
wird:
-
Bei allen binären Operatoren wird der linke Operand vor dem rechten bewertet.
Zuerst werden die Operanden, danach die Operatoren bewertet.
Falls mehrere Parameter, die durch Kommata voneinander getrennt sind, durch einen Methodenaufruf zur
Verfügung gestellt werden, werden diese Parameter von links nach rechts bewertet.
2.3.3 Typkonvertierungen
Java ist eine typisierte Sprache. Es finden gründliche Typüberprüfungen statt, und es
gelten strikte Beschränkungen für die Konvertierung von Werten eines Typs zu
einem anderen. Unter „Casting“ versteht man die Umwandlung von einem Datentyp
in einen anderen.
Java unterstützt explizite Konvertierungen und „ad hoc“-Konvertierungen.
Ad-hoc-Konvertierungen. Hier gibt es folgende Regeln für numerische Datentypen:
- Bei Operationen mit ausschl. ganzzahligen Operanden wird, falls einer der beiden Operanden den
Datentyp long hat, der andere ebenfalls zu long konvertiert; ansonsten werden beide Operanden
zu int konvertiert. Das Ergebnis ist dann ebenfalls vom Typ int. Ist allerdings der ausgegebene
Wert zu groß, um im Wertebereich von int dargestellt zu werden, wird der Typ long verwendet.
- Bei Operationen mit wenigstens einem Gleitpunkt-Operanden wird, wenn einer der Operanden den
Datentyp double hat, der andere ebenfalls zu double konvertiert. Das Ergebnis ist dann ebenfalls
vom Datentyp double, anderenfalls werden beide Operanden zum Datentyp float konvertiert.
Das Ergebnis ist ebenfalls vom Typ float.
Explizite Konvertierungen. Sie sind immer nötig, wenn eine Umwandlung in einen
anderen Datentyp gewünscht wird und diese nicht „ad hoc“ eintritt. „Casting“ muß
dann angewendet werden. Der zugehörige (Festlegungs-)Operator besteht aus
einem Typnamen in runden Klammern. Er ist ein einstelliger Operator mit hoher
Priorität und steht vor seinen Operanden. Er hat immer die folgende Form:
„(Datentyp) Wert“. Der (Festlegungs-)Operator bestimmt den Wert seines
Operanden, der auf den in Klammern bezeichneten Typ festgelegt wird. Es gibt
folgende Casting-Operatoren:
Operator
(byte)
Beispiel
(byte) (x/y)
(short)
(int)
(long)
(float)
(double)
(char)
(boolean)
(short) x
(int) (x/y)
(long) x
(float) x
(double) x
(char) x
(boolean) 0
Erläuterung
Wandelt das Errgebnis von x/y in einen Wert vom Datentyp byte
um
Wandelt x in einen Wert vom Datentyp short um
Wandelt das Ergebnis von x/y in einen Wert vom Datentyp int
Wandelt x in einen Wert vom Datentyp long um
Wandelt x in einen Wert vom Datentyp float um
Wandelt x in einen Wert vom Datentyp double um
Wandelt x in einen Wert vom Datentyp char um
Wandelt 0 in einen booleschen Datentyp um
Abb.: Casting-Operatoren
172
Programmieren in Java
Casting hat eine höhere Priorität als Arithmetik. Deshalb müssen arithmetische
Operationen in Verbindung mit Casting in Klammern gesetzt werden
Nicht alle Konvertierungen sind möglich. Variable eines arithmetischen Typs können
auf jeden anderen arithmetischen Typ festgelegt werden. Boolesche Werte können
nicht auf irgendeinen anderen Wert festgelegt werden. Die Umkehrung funktioniert
mit Einschränkungen: 0 und 1 lassen sich in boolesche Werte konvertieren.
Bsp.162: Konvertieren primitiver Typen
public class KonvPrim
{
public static void main(String args[])
{
char z = 'a';
System.out.println("char z = '" + z + '\'');
System.out.println("Unicode von z: " + (int) z);
int i = 17;
System.out.println("int i = " + i);
long l = 4L;
System.out.println("long l = " + l);
double d = 17.3;
System.out.println("double d = " + d);
float f = 4.5F;
System.out.println("float f = " + f);
double sd;
float sf;
long
sl;
int si;
sd = i + l + d + f;
System.out.println("sd = i + l + d + f = " + sd);
// sf = i + l + d + f;
/* Inkompatibler Typ! */
// System.out.println("sf = i + l + d + f = " + sf);
sf = (float) (i + l + d + f);
// sl = i + l + d + f;
/* Inkompatibler Typ! */
// System.out.println("sl = i + l + d + f = " + sl);
sl = i + l + (long) d + (long) f;
System.out.println("sl = l + i + (long) d + (long) f = " + sl);
sl = (long) (i + l + d + f);
System.out.println("sl = (long) (i + l + d + f) = " + sl);
// si = i + l + d + f;
/* Inkompatibler Typ! */
// System.out.println("si = i + l + d + f = " + si);
si = i + (int) l + (int) d + (int) f;
System.out.println("sl = l + (int) i + (int) d + (int) f = " + sl);
si = (int) (i + l + d + f);
System.out.println("sl = (int) (i + l + d + f) = " + si);
}
}
Konvertieren von Objekten. Mit Einschränkungen lassen sich Klasseninstanzen in
Instanzen anderer Klassen konvertieren. Die Klassen müssen allerdings durch
Vererbung miteinander verbunden sein. Allgemein gilt: Ein Objekt einer Klasse kann
auf seine Superklasse festgelegt werden. Spezifische Informationen der Subklasse
gehen dabei verloren. Das Konvertieren erfolgt immer nach folgender Form:
(Klassenname) Objekt.
162
vgl. pr23301
173
Programmieren in Java
Bsp.163: Konvertieren Datentyp Object
import java.applet.Applet;
import java.awt.*;
import java.util.Vector;
public class MittleresDrittel extends Applet
{
int appletHoehe;
int appletBreite;
Vector endpunkt = new Vector();
public void init()
{
Dimension d = getSize();
appletHoehe = d.height - 1;
appletBreite = d.width - 1;
// Anhaengen an Liste
endpunkt.addElement(new Float(0.0f));
endpunkt.addElement(new Float(1.0f));
}
public void paint(Graphics g)
{
float x1, x2;
Float tempFloat;
g.setColor(Color.yellow);
g.fillRect(0,0,appletBreite,appletHoehe);
g.setColor(Color.black);
for (int i = 0; i < appletHoehe; i+=5)
{
// Zeichne die Linien
for (int j = 0; j < endpunkt.size(); j+=2)
{
tempFloat = (Float) endpunkt.elementAt(j);
x1
= tempFloat.floatValue();
tempFloat = (Float) endpunkt.elementAt(j+1);
x2
= tempFloat.floatValue();
g.drawLine(Math.round(x1 * appletBreite),i,
Math.round(x2 * appletBreite),i);
}
// Entferne das mittlere Drittel aus den Linien
schneideausSegment();
tempFloat = (Float) endpunkt.elementAt(0);
x1 = tempFloat.floatValue();
System.out.println(x1);
tempFloat = (Float) endpunkt.elementAt(1);
x2 = tempFloat.floatValue();
System.out.println(x2);
if (Math.round(x1*appletBreite) ==
Math.round(x2*appletBreite)) break;
}
}
public void schneideausSegment()
{
int index = 0;
float luecke;
Float tempFloat1, tempFloat2;
int stop = endpunkt.size();
for (int i = 0; i < stop; i += 2)
{
schneideausMittleresDrittel(index,index+1);
index += 4;
}
}
163
pr23302
174
Programmieren in Java
public void schneideausMittleresDrittel(int links, int rechts)
{
float luecke;
float x1, x2;
Float tempFloat1, tempFloat2;
// Zugriff an der angegebenen Position
tempFloat1 = (Float) endpunkt.elementAt(links);
tempFloat2 = (Float) endpunkt.elementAt(rechts);
luecke = tempFloat2.floatValue() - tempFloat1.floatValue();
x1 = tempFloat1.floatValue() + luecke/3.0f;
x2 = tempFloat2.floatValue() - luecke/3.0f;
// Einfuegen an der angegebenen Stelle in der Liste
endpunkt.insertElementAt(new Float(x2),rechts);
endpunkt.insertElementAt(new Float(x1),rechts);
}
}
2.3.4 Vergleichsoperatoren
Java hat eine Menge von Operatoren für das Vergleichen von zwei oder mehr
Größen. Diese Operatoren lassen sich aufteilen in
-
relationale Operatoren. Sie sind zum Ordnen von Größen bestimmt, ob etwa ein Wert größer oder
kleiner als ein anderer ist.
Gleichheitsoperatoren. Sie sagen nur aus, ob zwei Werte gleich sind
2.3.5 Logische Ausdrücke
Logische Operationen können auf zwei verschiedene Arten ausgeführt werden:
-
Short-turn-Operatoren (logisches AND && und logisches OR ||). Sie operieren nur mit booleschen
Variablen.
Bitweise Operatoren. Sie operieren mit jedem Bit zweier ganzzahliger Operanden.
175
Programmieren in Java
2.4 Anweisungen
2.4.1 Blöcke und Anweisungen
Methoden und statische Initialisatoren werden in Java durch Anweisungsblöcke
definiert. Ein Anweisungsblock besteht in der Regel aus einer Reihe von
Anweisungen, die in geschweiften Klammern stehen. Das bedeutet: Es können in
diesem Block lokale Variablen deklariert werden, die außerhalb des Blocks nicht
verfügbar sind, und deren Existenz erlischt, wenn der Block ausgeführt wurde.
2.4.2 Leere Anweisungen
In Java können leere Anweisungen erstellt werden. Für den Compiler sieht eine leere
Anweisung nur wie ein zusätzliches Semikolon aus.
2.4.3 Benannte Anweisungen
Jede Anweisung darf in Java eine „Benennung“ haben. Die „Benennung“ hat die
gleichen Eigenschaften wie jeder andere Bezeichner. Die Reichweite der
„Benennung“ erstreckt sich über den ganzen Block. Der Benennung folgt ein
Doppelpunkt.
„Benennungen“ werden nur von den Sprunganweisungen break und continue
benutzt.
2.4.4 Deklarationen
Eine Deklarationsanweisung definiert eine Variable, egal ob Klasse, Schnittstelle,
Datenfeld, Objekt oder primitiver Typ. Das Format einer solchen Anweisung hängt
davon ab, welcher der 5 verschiedenen Formen deklariert wird.
Typname variablenName;
bzw.
Typname variablenName = initialerWert;
2.4.5 Ausdrucksanweisungen
In Java gibt es sieben verschiedene Arten von Ausdrucksanweisungen:
Ausdrucksanweisung
Zuordnung
Prä-Inkrement
Prae-Dekrement
Beispiel
x=13;
++wert;
--wert;
176
Programmieren in Java
Post-Inkrement
Post-Dekrement
Methodenaufruf
Zuweisungsausdruck
wert++;
wert--;
System.out.println("Aller Anfang ist schwer!");
byte x = new byte;
Abb.: Die sieben Ausdrucksanweisungen in Java
Bsp.: Demonstrationsprogramm zur Wirkungsweise der Inkrement- und DekrementOperatoren164
// Demonstration der Operatoren ++ und -public class AutoInkr
{
public static void main(String[] args)
{
int i = 1;
ausgabe("i: " + i);
ausgabe("++i: " + ++i); // Pre-Inkrement
ausgabe("i++: " + i++); // Post-Inkrement
ausgabe("i: " + i);
ausgabe("--i: " + --i); // Pre-Dekrement
ausgabe("i--: " + i--); // Post-Inkrement
ausgabe("i: " + i);
}
static void ausgabe(String s)
{
System.out.println(s);
}
}
/* Ausgabe
i: 1
++i: 2
i++: 2
i: 3
--i: 2
i--: 2
i: 1
*/
2.4.6 Auswahlanweisungen
2.4.6.1 if-Anweisungen
Eine if-Anweisung testet eine boolesche Variable oder einen Ausdruck zur
Feststellung, ob eine Anweisung oder ein Anweisungsblock ausgeführt werden soll.
Hat die boolesche Variable den Wert true, wird der Block ausgeführt. Falls nicht,
springt die Programmkontrolle zur nächsten Anweísung hinter dem Block.
if (boolscher_Ausdruck)
anweisung165
2.4.6.2 if-else-Anweisungen
164
vgl. pr24500
„anweisung“ bedeutet: Eine einfache Anweisung, die nur von einem Semikolon abgeschlossen ist bzw.
eine zusammengesetzte Folge von anweisungen, die von { ... } umschlossen ist.
165
177
Programmieren in Java
Ergänzt if um einen else-Teil. Dieser else-Teil reicht die Kontrolle an eine
Anweisung oder Block weiter, wenn der boolesche Wert im if-Teil der Anweisung
false war.
if (boolscher_Ausdruck)
anweisung
else
anweisung
Bsp.: Demonstrationsprogramm zur if-Anweisung166
import java.lang.*;
public class IfDemo extends Object
{
public static void main(String[] args)
{
int i;
int abs;
// if - else
i = 13;
System.out.println("i = " + i);
if (i >= 0)
{
abs = i;
}
else {
abs = -i;
}
System.out.println("abs = " + abs);
i = -13;
System.out.println("i = " + i);
if (i >= 0)
{
abs = i;
}
else {
abs = -i;
}
System.out.println("abs = " + abs);
// if
i = 13;
abs = i;
System.out.println("i = " + i);
if (abs < 0)
{
abs = -abs;
}
System.out.println("abs = " + abs);
i = -13;
abs = i;
System.out.println("i = " + i);
if (abs < 0)
{
abs = -abs;
}
System.out.println("abs = " + abs);
// Konditionaloperator
i = -13;
System.out.println("i = " + i);
abs = ( i < 0 ? -i : i);
System.out.println("abs = " + abs);
166
Vgl. pr24601
178
Programmieren in Java
i = 13;
System.out.println("i = " + i);
abs = ( i < 0 ? -i : i);
System.out.println("abs = " + abs);
}
}
2.4.6.3 switch-Anweisungen
Eine switch-Anweisung ermöglicht die Weitergabe des Kontrollflusses an eine von
vielen Anweisungen in ihrem Block mit Unteranweisungen (ist abhängig vom Wert
des Ausdrucks in der switch-Anweisung).
switch (integraler_Selektor)
{
case integraler_Wert1: anweisung; break;
case integraler_Wert2: anweisung; break;
case integraler_Wert3: anweisung; break;
.....
default: anweisung;
}
Ein integraler_Selektor ist ein Ausdruck zur Produktion eines integralen Werts167.
„switch“ vergleicht das Ergebnis einer Auswertung vom integralen Selektor mit jedem
integralen Wert. Falls ein passender Wert gefunden wird, wird die zugehörige
Anweisung (einfach oder zusammengesetzt) ausgeführt. Anderenfalls kommt es zur
Ausführung der bei „default“ angegebenen Anweisung. Die „break“-Anweisung ist
optional. Fehlt sie, dann werden die folgenden Anweisungen ausgeführt, bis ein
„break“ auftritt.
Bsp.: Demonstrationsprogramm zur switch-Anweisung168
import java.lang.*;
public class SwitchDemo extends Object
{
public static void main(String[] args)
{
int dezimal = 10;
System.out.print("dezimal = " + dezimal + " hex: ");
switch (dezimal)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
System.out.print("" + dezimal);
break;
case 10:
167
Integrale Werte sind bspw. vom Typ „int“, „char“. Nichtintegrale Typen. Z.B. float, String, müssen über eine
Serie von „if“-Anweisungen „switch“ simulieren
168 vgl. pr24601
179
Programmieren in Java
System.out.print("A");
break;
case 11:
System.out.print("B");
break;
case 12:
System.out.print("C");
break;
case 13:
System.out.print("D");
break;
case 14:
System.out.print("E");
break;
case 15:
System.out.print("F");
break;
default:
System.out.print("Keine gueltige Hex-Ziffer");
break;
}
System.out.println();
}
}
2.4.7 Iterationsanweisungen
2.4.7.1 while-Anweisung
Die while-Anweisung testet eine boolesche Variable oder einen Ausdruck. Ist er
true, wird die Unteranweisung oder der Block solange ausgeführt, bis sich der Wert
false einstellt. Ist die Variable oder der Ausdruck false, wird die Kontrolle an die
nächste Anweisung nach der Unteranweisung oder nach dem Block der whileAnweisung weitergegeben.
while (boolescher_Ausdruck)
anweisung
Bsp.169:
public class WhileDemo extends Object
{
public static void main(String args[])
{
double a = 2.0;
int
n = 10;
System.out.println("a = " + a);
System.out.println("n = " + n);
int absn = (n < 0) ? -n : n;
double aHochn = 1.0;
int i = 1;
while (i <= absn)
{
aHochn *= a;
i++;
}
if ( n < 0)
169
pr24700
180
Programmieren in Java
{
aHochn = 1.0 / aHochn;
}
System.out.println("aHochn = " + aHochn);
}
}
2.4.7.2 do-Anweisung
Die do-Anweisung testet eine boolesche Variable oder einen Ausdruck. Solange
dieser den Wert true hat, wird die Unteranweisung oder der Block ausgeführt. Erst
wenn die boolesche Variable oder der Ausdruck den Wert false hat, wird die
Wiederholung eingestellt und die Schleife verlassen. Der Code-Block innerhalb der
do-Anweisung wird auf jeden Fall mindestens einmal ausgeführt.
do
anweisung
while (boolescher_Ausdruck)
Bsp.170:
public class DoDemo extends Object
{
public static void main(String args[])
{
double a = 2.0;
int
n = 10;
System.out.println("a = " + a);
System.out.println("n = " + n);
int absn = (n < 0) ? -n : n;
double aHochn = 1.0;
int i = 1;
do
{
if (n == 0)
{
break;
}
aHochn *= a;
i++;
} while (i <= absn);
if ( n < 0)
{
aHochn = 1.0 / aHochn;
}
System.out.println("aHochn = " + aHochn);
}
}
170
pr24700
181
Programmieren in Java
2.4.7.3 for-Anweisung
Sie besteht aus
- for, gefolgt von optionalem Leerraum, gefolgt von einer öffnenden Klammer.
- Initialisierungsteil. Er enthält eine durch Kommata getrente Reihe von
Deklarations- und Zuweisungsanweisungen, die durch ein Semikolon beendet
wird. Die Deklarationen haben nur Gültigkeit für den Bereich der for-Anweisung
und ihrer Unteranweisungen. Die Zuweisungen werden nur einmal vor der esrten
Wiederholung der Unteranweisung oder des Blocks gemacht.
- Testteil. Enthält eine boolesche Variable oder einen Ausdruck, der einmal je
Schleife neu bewertet wird. Falls der Ausdruck false wird, geht die Kontrolle zur
nächsten Anweisung nach der for-Anweisung und ihrer Unteranweisung oder
ihrem Block weiter. Die Zuweisungen werden nur einmal vor der ersten
Wiederholung der Unteranweisung oder des Blocks gemacht.
- Inkrementteil. Enthält eine durch Kommata getrennte Reihe von Ausdrücken, die
einmal je Durchlauf der Schleife bewertet werden. Dieser Teil wird gewöhnlich
dazu verwendet, einen Index, der im Testteil überprüft wird, zu inkrementieren.
Am Ende des Teils steht ein Semikolon.
Jeder der drei Ausdrücke kann entfallen.
for (initialisierung; boolescher_Ausdruck; naechster_Schritt)
anweisung
Bsp.: public class ForDemo extends Object
{
public static void main(String args[])
{
double a = 2.0;
int
n = 10;
System.out.println("a = " + a);
System.out.println("n = " + n);
int absn = (n < 0) ? -n : n;
double aHochn = 1.0;
for (int i = 1; i <= absn; i++)
{
aHochn *= a;
}
if ( n < 0)
{
aHochn = 1.0 / aHochn;
}
System.out.println("aHochn = " + aHochn);
}
}
182
Programmieren in Java
2.4.7.4 Die erweiterte Form der for-Schleife in Java 1.5
for (Type identifier : expr) { body }
Dieser Konstrukt wird so gelesen: "Für jedes identifier des Typs Type in expr
führe aus".
Das erweiterte for möchte links vom Doppelpunkt ein Feld (Array) oder Objekt, das
vom Typ Iterable ist. Das Interface Iterable schreibt nur die Existenz einer
Funktion iterator() vor, die einen java.lang.SimpleIterator liefert. Der
konkrete SimpleIterator muß nur die Methoden hasNext() und next()
implementieren, um das nächste Element in der Aufzählung zu beschaffen und das
Ende anzuzeigen. Ein SimpleIterator ist ein Iterator171 ohne remove().
Bsp.:
import java.util.ArrayList;
import java.util.Iterator;
public class ForLoopTest
{
public static void main(String[] args)
{
double[] array = {2.5, 5.2, 7.9, 4.3, 2.0, 4.1, 7.3, 0.1, 2.6};
// Einfache Iteration vorwaerts durch die Schleife
for(double d: array) { System.out.println(d); }
System.out.println("---------------------");
// Das folgende arbeitet mit allem, das das Interface Iterable
// implementiert, z.B mit Collections wie ArrayList
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(7); list.add(15); list.add(-67);
for(Integer number : list) { System.out.println(number); }
System.out.println("---------------------");
// Es unterstuetzt auch Autoboxing
for(int item: list) { System.out.println(item); }
System.out.println("---------------------");
}
}
Test:
Abb.:
171
vgl. 6.2.1
183
Programmieren in Java
2.4.8 Sprung-Anweisungen
2.4.8.1 break-Anweisung
Unteranweisungsblöcke von Schleifen und switch-Anweisungen können durch
Verwendung einer break-Anweisung verlassen werden. Eine unbezeichnete breakAnweisung springt zur nächsten Zeile nach der aktuellen (innersten) Wiederholungsund switch-Anweisung. Mit einer bezeichneten break-Anweisung am Anfang einer
Schleife kann an eine Anweisung mit dieser Bezeichnung in der derzeitigen Methode
gesprungen werden. Am Anfangsteil der Schleife muß ein Label (eine Bezeichnung)
mit einem Doppelpunkt stehen, z.B.: „label1:“.
2.4.8.2 continue-Anweisung
Der aktuelle Schleifendurchlauf wird unterbrochen. Es wird zum Anfang der Schleife
zurückgekehrt, falls hinter continue kein „Bezeichner“ steht. Anderenfalls wird zu
einer äußeren Schleife zurückgekehrt, die eine Markierung (label) gleichen
Namens enthält.
Eine continue-Anweisung darf nur in einem Unterweisungsblock einer
Iterationsanweisung stehen (while, do oder for).
Bsp.172: „break“ und „continue“ innerhalb von „for“- bzw. „while“-Schleifen
public class BreakundContinue
{
public static void main(String[] args)
{
for (int i = 0; i < 100; i++)
{
if (i == 13) break;
// raus aus der Schleife
if (i % 9 != 0) continue; // naechster Schleifendurchlauf
System.out.println(i);
}
int i = 0;
// eine "unendliche Schleife
while (true)
{
i++;
int j = i * 27;
if (j == 1269) break;
// raus aus der Schleife
if (i % 10 != 0) continue; // zurueck an den Anfang der Schleife
System.out.println(i);
}
}
}
172
pr24800
184
Programmieren in Java
2.4.8.3 Marken (labels)
Eine Markierung bzw. ein „label“ bewirkt in Verbindung mit der „break“- bzw.
„continue“-Anweisung eine Unterbrechnug des Schleifendurchgangs und einen
Sprung zu der dem „label“ folgenden Anweisung. Sinnvollerweise steht dann ein
„label“ am Anfang der Schleifen, z.B.:
label1:
äußere_Iteration
{
innere_Iteration
{
// ...
break;
// ...
continue;
// ...
continue label1;
// ...
break label1;
}
}
// 1. Fall
// 2. Fall
// 3. Fall
// 4. Fall
Im 1. Fall bricht „break“ aus der inneren Iteration heraus und führt in die äußere
Iteration. Im 2. Fall verzweigt „continue“ auf den Anfang der inneren Iteration. Im 3.
Fall führt „continue label1“ aus der inneren und äußeren Iteration heraus auf
„label1“. Die Iteration wird mit dem Anfang der äußeren Iteration fortgesetzt. Im 4.
Fall bricht „break label1“ aus der inneren Iteration heraus auf label1. Es kommt
aber nicht zu einem erneuten Eintritt in die Iterationen, sondern zu einem Ausbruch
aus beiden Iterationen.
Bsp.173: Marken im Zusammenhang mit „for“- bzw. „while“-Schleifen
1. Marken im Zusammenhang mit „for“
public class MarkiertesFor
{
public static void main(String[] args)
{
int i = 0;
aussen:
// hier soll keine Anweisung stehen
for (; true; )
// Endlos-Schleife
{
innen:
// hier soll keine Anweisung stehen
for (; i < 10; i++)
{
ausgabe("i = " + i);
if (i == 2)
{
ausgabe("continue");
continue;
}
if (i == 3)
{
ausgabe("break");
i++;
break;
}
if (i == 7)
{
ausgabe("continue aussen");
173
pr24800
185
Programmieren in Java
i++;
continue aussen;
}
if (i == 8)
{
ausgabe("break aussen");
break aussen;
}
for (int k = 0; k < 5; k++)
{
if (k == 3)
{
ausgabe("continue innen");
continue innen;
}
}
}
}
}
static void ausgabe(String s)
{
System.out.println(s);
}
}
/* Ausgabe
i = 0
continue innen
i = 1
continue innen
i = 2
continue
i = 3
break
i = 4
continue innen
i = 5
continue innen
i = 6
continue innen
i = 7
continue innen
i = 8
break aussen
*/
2. Marken im Zusammenhang mit „while“
public class MarkiertesWhile
{
public static void main(String[] args)
{
int i = 0;
aussen:
while (true)
{
ausgabe("Aeussere While-Schleife");
while (true)
{
i++;
ausgabe("i = " + i);
if (i == 1)
{
ausgabe("continue");
continue;
}
if (i == 3)
{
ausgabe("continue aussen");
186
Programmieren in Java
continue aussen;
}
if (i == 5)
{
ausgabe("break");
break;
}
if (i == 7)
{
ausgabe("break aussen");
break aussen;
}
}
}
}
static void ausgabe(String s)
{
System.out.println(s);
}
}
/* Ausgabe
Aeussere While-Schleife
i = 1
continue
i = 2
i = 3
continue aussen
Aeussere While-Schleife
i = 4
i = 5
break
Aeussere While-Schleife
i = 6
i = 7
break aussen
*/
2.4.8.4 return-Anweisung
Alle Funktionen haben einen eindeutigen Typ, der gleichzeitig Typ des
Rückgabewerts ist. Mögliche Typen für die Rückgabe sind primitive Typen,
Datenfelder (Arrays), Klassen, Schnittstellen. Es gibt einen speziellen Typ für eine
Funktion ohne Rückgabewert: „void“.
Der Rückgabewert ist der Wert einer return-Anweisung.
2.4.8.5 throw-Anweisung
Eine throw-Anweisung erzeugt eine Laufzeit-Ausnahme.
2.4.9 Synchronisationsanweisungen
Sie wird für den Umgang mit „Multithreading“ benutzt.
187
Programmieren in Java
2.4.10 Schutzanweisungen
Java kennt drei Schutzanweisungen: try, catch, finally. Sie werden zur
Handhabung von Ausnahmen in einer Methode benutzt, die eine Ausnahmesituation
hervorrufen kann.
2.4.11 Unerreichbare Anweisungen
Es ist das Schreiben einer Methode mit Codezeilen möglich, die nie erreicht werden
können, z.B. Zeilen zwischen einer bedingungslosen return-Anweisung und der
nächsten Bezeichnung oder dem Ende eines Blocks. Solche Anweisungen erzeugen
einen Fehler beim Kompilieren.
2.5 Klassen
2.5.1 Deklaration
Klassen definieren Zustand und Verhalten von Objekten. Jedes Java-Programm
besteht aus einer Sammlung von Klassen. Alle Klassen in Java haben eine
gemeinsame Oberklasse, die Klasse Object. Auch Java selbst (als
Entwicklungsplattform) ist aus Klassen aufgebaut, die mit dem JDK frei verfügbar
sind. Das eigentliche RUNTIME-Modul besteht aus der Datei Java Core Classes
(classes.zip), die normalerweise nicht entpackt wird und im Unterverzeichnis
\lib des JDK vohanden ist. Die Datei enthält den vollständigen kompilierten Code
von Java.
Jede Klasse besteht formal aus zwei Teilen: der Deklaration und dem Body (Körper).
Generell haben Klassendeklarationen folgendes Format:
Modifizierer class NeueKlasse extends NameSuperklasse
implements NameSchnittstelle
Es gibt vier Eigenschaften einer Klasse, die in einer Deklaration definiert werden
können: Modifizierer, Klassenname, Superklasse, Schnittstellen
Modifizierer
Sie stehen am Beginn der Klassendeklaration und legen fest, wie die Klasse
während der weiteren Entwicklung gehandhabt werden kann. Klassen haben einen
voreingestellten, „freundlichen“ Defaultstatus. Er wird immer dann verwendet, wenn
kein Modifizierer am Anfang einer Klassendefinition steht. „Freundlich“ bedeutet: Die
Klasse darf erweitert und von anderen Klassen benutzt werden, aber nur von
Objekten innerhalb desselben Pakets. Die Grundeinstellung bezieht sich also auf die
Sichtbarkeit von anderen Klassen und deren Objekten. Falls davon abgewichen
werden soll, ist einer der folgenden Modifizierer zu verwenden: public , final,
abstract.
188
Programmieren in Java
Öffentliche Klassen – der Modifizierer public. Eine Klasse wird als öffentlich
deklariert, wenn man den Modifizierer public vor die Klassendeklaration setzt. Alle
Objekte dürfen auf public-Klassen zugreifen, d.h.: Sie können von allen Objekten
benutzt und erweitert werden, ganz egal zu welchem Paket sie gehören.. Die
Deklaration einer öffentlichen Klasse muß immer identisch sein mit dem Namen,
unter dem die Quelle dieser Datei gespeichert ist.
Finale Klassen – der Modifizierer final. Finale Klassen dürfen keine Subklassen
haben. Der Modifizierer final muß am Beginn der Klassendeklaration gesetzt sein.
Abstrakte Klassen – der Modifizierer abstract. Von einer derartig beschriebenen
Klasse wird nie eine direkte Instanz benötigt und kann auch nie eine Instanz gebildet
werden. Sie darf keine Implementierung einer Methode enthalten. In einer abstrakten
Klasse gibt es mindestens eine nicht vollständig angegebene Methode.
2.5.2 Generische Klassen und generische Schnittstellen
2.5.2.1 Generische Typen
2.5.2.1.1 Typvariable
Mit Java 1.5 wird das Typsystem auf generische Typen erweitert. Die Idee für
generische Typen ist es eine Klasse zu schreiben, die für verschiedene Typen als
Inhalt zu benutzen ist. Die Speicherung beliebiger Objekte kann Java bis zur Version
1.4 nur in Feldern vom Typ Object vollziehen. Allerdings geht damit die typische
statische Typinformation verloren. Dynamische Typzusicherung ist in diesem Fall für
weitere sinnvolle Nutzung der Objekte unerläßich. Dynamische Typzusicherung kann
aber zu Laufzeitfehlern führen:
Bsp.174:
class AlteSchachtel
{
Object inhalt;
AlteSchachtel(Object inhalt)
{
this.inhalt = inhalt;
}
}
public class AnwAlteSchachtel
{
public static void main(String args[])
{
AlteSchachtel b = new AlteSchachtel("Hallo");
String s = (String) b.inhalt;
System.out.println(s.toUpperCase());
System.out.println(((String) s).toUpperCase());
}
}
Wann auch immer mit inhalt gearbeitet wird, eine Typzusicherung während der Laufzeit ist
durchzuführen. Das kann zu Laufzeitfehlern führen. So übersetzt das folgende Programm fehlerfrei
gibt aber einen Laufzeitfehler.
174
pr25210
189
Programmieren in Java
public class AnwAlteSchachtelFehler
{
public static void main(String args[])
{
AlteSchachtel b = new AlteSchachtel(new Integer(42));
String s = (String) b.inhalt;
System.out.println(s.toUpperCase());
}
}
Das vorliegende Bsp. zeigt: Sobald der Typ Object benutzt wird gibt es einen
Verlust an Typsicherheit, sobald der Typ Object benutzt wird.
Wie kann man Klassen schreiben, die statische Typsicherheit garantieren? Im
vorliegenden Beispiel braucht man nur jedes Auftreten des Typs Object durch einen
Variablennamen zu ersetzen. Die Variable ist eine Typvariable, sie steht für einen
beliebigen Typ. Dem Klassennamen wird zusätzlich in der Klassendefinition, in
spitzen Klammern eingeschlossen, hinzugefügt, dass diese Klasse eine Typvariable
benutzt.
class Schachtel <elementType>
{
elementType inhalt;
Schachtel(elementType inhalt)
{
this.inhalt = inhalt;
}
}
Die Typvariable elementType ist allqualifiziert. Für jeden Typ elementType kann die
Klasse Schachtel verwendet werden, z.B.:
Schachtel<String> zur Speicherung von Strings
Schachtel<Integer> zur Speicherung von Integer-Objekte
Bsp.175:
public class AnwSchachtel
{
public static void main(String args[])
{
Schachtel<String> b1 = new Schachtel<String>("Hallo");
String s = b1.inhalt;
System.out.println(s.toUpperCase());
System.out.println(b1.inhalt.toUpperCase());
Schachtel<Integer> b2 = new Schachtel<Integer>(new Integer(42));
System.out.println(b2.inhalt.intValue());
}
}
175
pr25210
190
Programmieren in Java
Da mit generischen Typen keine Typzusicherungen mehr vorzunehmen sind, gibt es
hier auch keine dynamischen Typfehler mehr. Laufzeitfehler, wie sie ohne die
"generische Schachtel" aufgetreten sind, werden jetzt bereits zur Übersetzungszeit
entdeckt.
Bsp.176:
public class AnwSchachtelFehler
{
public static void main(String args[])
{
Schachtel<String> b = new Schachtel<String>(new Integer(42));
String s = b.inhalt;
System.out.println(s.toUpperCase());
}
}
AnwSchachtelFehler.java: 5 cannot find symbol
Kovarianz gegen Kontravarianz: Das folgende Programm führt auf einen Fehler
class Kontra
{
public static void main(String [] args)
{
Schachtel<Object> s = new Schachtel<String>("Hallo");
}
}
Eine Schachtel<String> ist keine Schachtel<Object>. Der Grund dafür ist,
dass bestimmte Laufzeitfehler vermieden werden sollen. Betrachtet man ein Objekt
des
Typs
Schachtel<String>
über
eine
Referenz
des
Typs
Schachtel<Object>, dann können in dem Feld inhalt beliebige Objekte
gespeichert werden. Die Referenz über den Typ Box<String> geht davon aus,
dass in inhalt nur Stringobjekte gespeichert werden.
Reihungen (arrays) verhalten sich in Java anders. Bei Reihungen ist die
entsprechende Zuweisung erlaubt. Eine Reihung von Stringobjekten darf einer
Reihung beliebiger Objekte zugewiesen werden. Dann kann es bei der Benutzung
einer Reihung von Objekten zu einem Laufzeitfehler kommen.
176
pr25210
191
Programmieren in Java
2.5.2.1.2 Vererbung
Von generischen Klassen lassen sich Subklassen definieren. Diese Subklassen
können, müssen aber nicht selbst generische Klassen sein.
Bsp.177: Erweiterung der Schachtelklasse, so dass 2 Objekte gespeichert werden
können.
class Paar <at,bt> extends Schachtel<at>
{
bt zweites;
Paar(at x , bt y)
{
super(x); public class AnwPaar
{
public static void main(String[] args)
{
Paar <String, Integer> p =
new Paar<String,Integer>("Hallo",new Integer(40));
System.out.println(p);
System.out.println(p.inhalt.toUpperCase());
System.out.println(p.zweites.intValue() + 2);
}
}
Die Klasse Paar hat zwei Typvariablen. Instanzen von Paar müssen angeben von welchem Typ die
beiden zu speichernden Objekte sein sollen.
public class AnwPaar
{
public static void main(String[] args)
{
Paar <String, Integer> p =
new Paar<String,Integer>("Hallo",new Integer(40));
System.out.println(p);
System.out.println(p.inhalt.toUpperCase());
System.out.println(p.zweites.intValue() + 2);
}
}
Es können auch Subklassen durch Zusammenfassen mehrerer Typvariablen gebildet
werden, z.B.178:
class UniPaar<at > extends Paar<at,at>
{
UniPaar(at x,at y)
{
super(x,y);
}
void tausch()
{
final at z = zweites;
zweites = inhalt;
inhalt = z;
}
}
Wie man sieht, sind Typvariablen wie bisherige Typen zu benutzen. Sie können als Typ für lokale
Variable oder Parameter genutzt werden.
177
178
pr25210
pr25210
192
Programmieren in Java
public class AnwUniPaar
{
public static void main(String [] args)
{
UniPaar<String> p = new UniPaar<String>("welt","hallo");
System.out.println(p);
p.tausch();
System.out.println(p);
}
}
Man kann auch Subklassen einer generischen Klasse bilden, die nicht mehr
generisch ist. So lässt sich bspw.179 die Klasse Schachtel zu einer Klasse erweitern,
in der nur noch Stringobjekte verpackt werden dürfen:
class StringSchachtel extends Schachtel<String>
{
StringSchachtel(String x)
{
super(x);
}
}
Diese Klasse kann nun vollkommen ohne spitze Klammern benutzt werden:
public class AnwStringSchachtel
{
public static void main(String args [])
{
Schachtel b = new StringSchachtel("Hallo");
System.out.println(b.inhalt);
}
}
2.5.2.1.3 Einschränkung der Typvariablen
Bei der Definition einer Schablone können Typen eingeschränkt werden. Es kann
eingeschränkt werden, dass eine Typvariable nicht für alle Typen ersetzt werden
darf, sonder nur für bestimmte Typen. Somit kann bspw. vorgeschrieben werden,
dass der Typ eine konkrete Schnittstelle verwenden muß.
Bsp.180: Erweiterung der Klasse Schachtel mit einer set()-Methode, die nur einen
neuen Wert in das entsprechende Objekt speichert, wenn es größer ist als das
bereits gespeicherte Objekt.
Hierzu müssen die zu speichernden Objekte in einer Ordnungsrelation vergleichbar sein, was in Java
über die Implementierung der Schnittstelle Comparable ausgedrückt wird.
class SammleMaxAlt
{
private Comparable wert;
SammleMaxAlt(Comparable x)
{ wert = x; }
void setWert(Comparable x)
{ if (wert.compareTo(x) < 40) wert = x; }
Comparable getWert() { return wert; }
179
180
pr25210
pr25210
193
Programmieren in Java
}
Die Klasse SammleMaxAlt ist in der Lage beliebige Objekte, die die Schnittstelle Comparable
implementieren, zu speichern. Es besteht aber folgendes Problem: Greift man auf das gespeicherte
Objekt mit der Methode getWert() erneut zu, dann ist der genaue Wert dieses Objekts nicht mehr
bekannt. Evtl. ist eine dynmische Typzusicherung notwendig, die zu Laufzeitfehlern führen kann.
Die generischen Typen von Java 1.5 können dieses Problem beheben, indem man den allgemeinen
Typ Object durch eine Typvariable ersetzt, die Subtypen der Schnittstelle Comparable sind. Dies
wird durch eine zusätzliche extends-Klausel für die Typvariable angegeben.
class SammleMax <elementType extends Comparable>
{
private elementType wert;
SammleMax(elementType x) { wert = x; }
void setWert(elementType x)
{
if (wert.compareTo(x) < 0) wert = x;
}
elementType getWert() { return wert; }
}
Für die Benutzung der Klasse ist jetzt für jede konkrete Instanz der konkrete Typ des gespeicherten
Objekts anzugeben. Die Methode getWert() liefert als Rückgabetyp nicht ein allgemeines Objekt
des Typs Comparable, sondern exakt ein Typ des Instanztyps.
class AnwSammleMax
{
public static void main(String [] args)
{
SammleMax<String> sm = new SammleMax<String>("Brecht");
sm.setWert("Calderon");
sm.setWert("Shakespeare");
sm.setWert("Goethe");
sm.setWert("Schiller");
System.out.println(sm.getWert().toUpperCase());
}
}
Wie man in der letzten Zeile sieht, entfällt wieder die dynamische Typzusicherung.
2.5.2.2 Generische Schnittstellen
Generische Typen erlauben es, den Typ Object in Typsignaturen zu eliminieren.
Der Typ Object ist als schlecht anzusehen, denn er ist gleichbedeutend damit, dass
keine Information über einen konkreten Typ während der Übersetzungszeit zur
Verfügung steht. In herkömmlichen Java ist in APIs von Bibliotheken der Typ Object
allgegenwärtig. Sogar in der Klasse Object selbst findet man diesen Typ in
Signaturen, z.B. in der Methode equals(). Prinzipiell kann deshalb ein Objekt mit
Objekten jedes beliebigen Typs verglichen werden. Häufig will man aber nur gleiche
Typen miteinander vergleichen. Generische Typen erlauben es, allgemein eine
Gleichheitsmethode zu definieren, in der nur Objekte gleichen Typs miteinander
verglichen werden können.
Generische Typen erweitern sich ohne Umstände auf Schnittstellen. Speziell für
Gleichheit könnte eine derartige Schnittstelle so aussehen:
interface EQ<otherType>
{ public boolean eq(otherType other); }
194
Programmieren in Java
Bsp.: Äpfel mit Äpfel vergleichen
Dazu definiert man in der implements-Klausel, dass EQ<Apfel> implementiert wird.
class Apfel implements EQ<Apfel>
{
String typ;
Apfel(String typ)
{
this.typ=typ;
}
public boolean eq(Apfel other)
{
return this.typ.equals(other.typ);
}
}
Jetzt können erst einmal "Äpfel" mit "Äpfel" verglichen werden.
class TestEq
{
public static void main(String [] args)
{
Apfel a1 = new Apfel("Golden Delicious");
Apfel a2 = new Apfel("Macintosh");
System.out.println(a1.eq(a2));
System.out.println(a1.eq(a1));
}
}
Der folgende Vergleich führt zu einem Fehler beim Übersetzen181:
class TesteEqError
{
public static void main(String []args)
{
Apfel a = new Apfel("Golden Delicious");
Birne b = new Birne("williams");
System.out.println(a.equals(b));
System.out.println(a.eq(b));
}
}
Die Schnittstelle EQ wird nun auch für die Klasse Birne implementiert
class Birne implements EQ<Birne>
{
String typ;
Birne(String typ)
{
this.typ=typ;
}
public boolean eq(Birne other)
{
return this.typ.equals(other.typ);
}
}
Während des statischen Typchecks wird überprüft, ob Äpfel mit Äpfeln und Birne mit Birnen
verglichen werden. Der Versuch Äpfel mit Birnen zu vergleichen, führt zu einem Typfehler.
181
eq(Apfel) in Apfel cannot be applied to (Birne)
195
Programmieren in Java
Der statische TypCheck stellt auch sicher, dass eine generische Schnittstelle mit der korrekten
Typsignatur implementiert wird. Der Versuch eine "Birnenklasse" zu schreiben, die eine Gleichheit mit
Äpfeln implementieren soll, dann aber die Methode "eq" mit dem Parametertyp "Birne" zu
implemntieren, führt zu einer Fehlermeldung182:
class BirneError implements EQ<Apfel>
{
String typ;
BirneError(String typ)
{
this.typ=typ;
}
public boolean eq(Birne other)
{
return this.typ.equals(other.typ);
}
}
2.6 Methoden
2.6.1 Die Deklaration
Die Deklarationen von Methoden haben folgendes Aussehen183:
Zugriffsspezifizierer Modifizierer Returnwert NameMethode(Parameter)
throws Exceptionliste
Methodenunterschrift184. Unter Unterschrift einer Methode versteht man eine
Kombination aus Teilen der Definition: dem Namen der Methode, dem Rückgabetyp
und den verschiedenen Parametern.
2.6.2 Die Zugriffsspezifizierung
Freundliche Methoden – der voreingestellte Defaultstatus.
Öffentliche Methoden – der Zugriffsspezifizierer public.
Geschützt Methoden – der Zugriffsspezifizierer protected.
Prvate Methoden – der Zugriffsspezifizierer private.
Privat geschützt – die Zugriffsspezifizierer private und protected in Kombination.
Methoden, die als private protected deklariert wurden, sind sowohl für eine
Klasse als auch für eine Subklasse vefügbar, aber nicht für den Rest des Pakets
oder auch für Klassen außerhalb des Pakets. Das bedeutet: Subklassen einer
182
BirneFehler is not abstract and does not override abstract method
eq(Apfel) in EQ
183 Kursiv Geschriebenes ist optional
184 Oft wird für denselben Zusammenhang der Begriff „Methodensignatur“ verwendet.
196
Programmieren in Java
gegebenen Klasse können Methoden, die private protected deklariert wurden
aufrufen. Instanzen der Subklasse können dies aber nicht.
2.6.3 Die Methodenmodifizierer
Klassenmethoden – der Modifizierer static.
Abstrakte Methoden – der Modifizierer abstract
Finale Methoden – der Modifizierer final. Das Schlüsselwort final vor einer
Methodendeklaration verhindert, daß irgendwelche Subklassen die derzeitige Klasse
dieser Methode überschreiben. Methoden, die auf keinem Fall geändert werden
sollen, sollten deshalb immer als final deklariert werden.
Native Methoden – der Modifizierer native. Native Methoden sind Methoden, die
nicht in Java geschrieben sind, aber dennoch innerhalb von Java verwendet werden
sollen. Der Modifizierer native wird vor der Methode deklariert, der Body (Körper) der
Methode wird durch ein Semikolon ersetzt.
Methoden synchronisieren – der Modifizierer synchronized. Wird das
Schlüsselwort sychronized vor eine Methodendeklaration gesetzt, dann werden
Datenverletzungen verhindert, die entstehen können, wenn zwei Methoden
gleichzeitig versuchen auf dieselben Daten zuzugreifen.
2.6.4 Rückgabewerte von Methoden
Rückgabewerte vonJava-Methoden können von jedem erlaubten Datentyp185 sein.
Eine Methode muß immer einen Wert zurückgeben (und zwar genau den Datentyp,
der in der Deklaration angegeben wurde), es sei denn, sie wurde mit void deklariert.
Die Methode hat dann keinen Rückgabewert.
2.6.5 Methodenname und Parameterliste
Bzgl. der Methodennamen gelten die gleichen Regeln wie bei allen Token.
Eine Parameterliste hat folgende Struktur: Datentyp variablenname, Datentyp
variablenname, .... . Die Anzahl der Parameter ist beliebig und kann Null sein.
185
Nicht nur primitive Datentypen, sondern auch komplexe Objekte.
197
Programmieren in Java
2.6.6 Variable Parameteranzahl
Mit Java 1.5 können Methoden mit einer variablen Anzahl von Parametern definiert
werden. Sie werden durch Punkte nach dem Paramtertyp in der Signatur
gekennzeichnet. Damit wird angezeigt: Es kann eine beliebige Anzahl dieser
Parameter bei einem Methodenaufruf geben.
Bsp.:
public class VarParams
{
public static String append(String ... argumente)
{
String result = "";
for (String a : argumente)
result += a;
return result;
}
public static void main(String [] args)
{
System.out.println(append("Hallo"," ","Welt"));
}
}
2.6.7 Rekursion
2.6.7.1 Rekursive Funktionen
Eine Funktion ist rekursiv, falls die Ausführung des Rumpfs der Funktion wiederum
zum Aufruf der Funktion führt. Man unterscheidet
Direkte Rekursion (Eine Funktion ruft sich selbst im Rumpf auf)
und
Indirekte Rekursion (Der rekursive Aufruf befidet sich nicht im Funktionsrumpf. So ruft bspw. eine
Funktion A eine Funktion B auf und diese startet in ihrem Rumpf die Funktion A).
Wie Wiederholungsanweisungen neigen auch rekursive Funktionen zur Gefahr nicht
abbrechbarer Berechnungen. Eine Terminierung ist unbedingt erforderlich. Das
geschieht über eine Bedingung von der der rekursive Aufruf abhängt..
Bei jeder rekursiven Anwendung wird ein Satz lokaler, gebundener Variablen kreiert.
Sie haben zwar denselben Namen wie Objekte des vorangegengenen Aufrufs der
Prozedur, besitzen aber verschiedene Werte. Die Namen beziehen sich immer auf
die zuletzt erzeugten Variablen.
198
Programmieren in Java
2.6.7.2 Rekursion und Iteration
Man kann zeigen, daß sich jeder rekursive Algorithmus in einen iterativen
umwandeln läßt. Da die Iteration in den meisten Fällen wesentlich effizienter ist als
die die Rekursion, ist die Frage berechtigt, weshalb man überhaupt die Rekusion
verwendet. Für die Rekursion spricht:
1.
2.
Es gibt bestimmte rekursiv formulierte Algorithmen, die schneller oder wenigstens gleich
schnell
arbeiten als vergleichbare iterative.
Es lassen sich viele Probleme rekursiv „sehr einfach“ lösen.
Rekursiv formulierte Algorithmen bieten sich insbesondere an, wenn das
zugrundeliegende Problem oder die zu behandelnden Daten rekursiv definiert sind.
Das ist aber noch keine Garantie dafür, daß ein rekursiver Algorithmus auch der
beste Weg zur Lösung des Problems ist.
199
Programmieren in Java
3. Grafische Benutzeroberflächen und Applets
3.1 Ereignisse in grafischen Benutzeroberflächen
Mit Hilfe einfacher Benutzerschnittstellen des Graphical User Interface (GUI))
lassen sich Ein- und Ausgaben186 bequem gestalten. Generell sollte Ein-/Ausgabe in
Java über ein „GUI“ in Abhängigkeit von den vom Betriebssystem registrierten
Ereignisarten und Zustandsänderungen erfolgen.
3.1.1 Gestaltung von GUI mit Hilfe der AWT-Klassen
Java enthält ein einfach zu bedienendes System für Gestaltung grafische
Benutzeroberflächen: das Abstract Windowing Toolkit (AWT). Die Fähigkeiten
des AWT umfassen:
Grafische Operationen zum Zeichnen von Linien oder Füllen von Flächen und zur Ausgabe von
Text
Methode zur Steuerung des Programmablaufs auf der Basis von Nachrichten für Tatstatur-,
Maus- und Fensterereignisse.
Dialogelemente zur Kommunikation mit dem Anwender und Funktion zum Design von
Dialogboxen.
Grafikfunktionen zur Darstellung von Bitmaps und Ausgabe von "Sound".
-
Zum Einbinden der Grafikfähigkeiten dient die Anweisung
import java.awt.*;
zu Beginn der Klassendefinition. Danach stehen alle Klassen aus dem Paket
java.awt zur Verfügung.
Zur Ausgabe von grafischen Elementen benötigt eine Anwendung ein Fenster, das
eine Applikation (im Gegensatz zu einem Applet) selbst erzeugen muß. Das AWT
enthält verschiedene Fensterklassen:
Panel
Component
Applet
Container
Window
Dialog
FileDialog
Frame
Component
Container
186
Abstrakte Klasse mit der Aufgabe: Repäsentation von Programmelementen, die
eine Größe und Position haben und die auf eine Vielzahl von Ereignissen
reagieren können bzw. Ereignisse senden können
Abstrakte Klasse mit der Aufgabe: Aufnahme von Komponenten innerhalb anderer
Komponenten. Container stellt für das Hinzufügen bzw. Entfernen von
Komponenten Methoden bereit und realisiert mit Hilfe von "Layout-Manager"-
Standardein- und Standardausgabe spielen in Java nur im Rahmen des „Debugging“ eine Rolle.
201
Programmieren in Java
Panel
Applet
Window
Frame
Dialog
FileDialog
Klassen Positionierung und Anordnung von Komponenten.
Ist die konkrete Klasse mit den Eigenschaften von Component und Container. Sie
erbt alle Eigenschaften von Container, kann Komponenten aufnehmen und mit
Hilfe des Layoutmanagers auf dem Bildschirm anordnen.
Erweitert die Funktionalität der Klasse Applet um Methoden, die für das Ausführen
von Applets von Bedeutung sind. Damit entsteht ein Programmelement, das eine
Größe und eine Position hat, auf Ereignisse reagieren kann und in der Lage ist,
weitere Komponenten aufzunehmen.
Bestimmt ein Top-Level-Window ohne Rahmen, Titelleiste und Menü. Sie ist für
Anwendungen geeignet, die Rahmenelemente selbst zeichnen oder volle Kontrolle
über das gesamte Fenster benötigen.
Repräsentiert ein Top-Level-Window mit Rahmen, Titelleiste und optionalem
Menü.
Realisiert modale und nicht modale Dialoge.
Stellt ein Standard-Dateidialog des jeweiligen Systems bereit. Dieser kann beim
Laden oder Speichern einer datei zur Eingabe oder zur Auswahl eines
Dateinamens verwendet werden.
Abb. Fensterklassen-Hierarchie
Alle Fensterklassen sind von einer gemeinsamen abstrakten Basisklasse abgeleitet,
die die Funktionalität für Aussehen, Positionierung, Ausgabe und Laufzeitverhalten(
Fokus, Mauszeiger, Sichtbarkeit) bereitstellt.
Component
{ abstract }
public String getName()
public void setName(String name)
public Container getParent()
public boolean contains(int x, int y)
public boolean isVisible()
public void setVisible(boolean b)
public boolean isEnabled()
public void setEnabled(boolean b)
public boolean isShowing()
public Color getForeground()
public void setForeground(Color c)
public Color getBackground()
public void setBackground(Color c)
public Font getFont()
public void setFont(Font f)
public Point getLocation()
public Point getLocationOnScreen()
public void setLocation(int x, int y)
public void setLocation(Point p)
public Dimension getSize()
public void setSize(int breite, int hoehe)
public void setSize(Dimension d)
public Rectangle getBounds()
public void setBounds(int x, int y, int breite, int hoehe)
public void setBounds(Rectangle r)
public Graphics getGraphics()
public FontMetrics getFontMetrics(Font font)
public void paintAll(Graphics g)
public void repaint()
public boolean imageUpdate(Image bild, int flags, int x, int y, int breite, int hoehe)
public Image createImage(ImageProducer erz)
public Image createImage(int breite, int hoehe)
public boolean prepareImage(Image bild, ImageObserver obs)
public boolean prepareImage(Image bild, int breite, int hoehe, ImageObserver obs)
202
Programmieren in Java
public int checkImage(Image bild, ImageObserver obs)
public int checkImage(Image bild, int breite, int hoehe, ImageObserver obs)
public void addComponentListener(ComponentListener l)
public void removeComponentListener(ComponentListener l)
public void addFocusListener(FocusListener l)
public void removeFocusListener(FocusListener l)
public void addKeyListener(KeyListener l)
public void removeKeyListener(KeyListener l)
public void addMouseListener(MouseListener l)
public void removeMouseListener(MouseListener l)
public void addMouseMotionListener(MouseMotionListener l)
public void removeMouseMotionListener(MouseMotionListener l)
public final void enableEvents(long eventsToEnable)
public final void disableEvents(long eventsToDisable)
protected void processComponentEvent(ComponentEvent e)
protected void processFocusEvent(FocusEvent e)
protected void processKeyEvent(KeyEvent e)
protected void processMouseEvent(MouseEvent e
protected void processMouseMotionEvent(MouseEvent e)
public boolean gotFocus(Event evt) //deprecated, -> processFocusEvent(FocusEvent e)
public boolean lostFocus(Event evt, Object was)
public void requestFocus()
public void add(PopupMenu popup)
public void remove(MenuComponent popup)
public String toString()
……
…….
…….
…….
…….
Abb.: Die Klasse Component
Container sind Fenster oder Fensterbereiche, in denen andere Kontrollelemente
platziert werden können.
Component
Container
{abstract}
public int countComponents()
public Component getComponent(int n)
public Components[] getComponents()
public Insets getInsets()
public Component add(Component comp)
public void remove(Component comp)
public void removeAll()
public LayoutManager getLayout()
public void setLayout(LayoutManager mgr)
public Dimension getPreferredSize()
public Dimension getMinimumSize()
public Dimension getMaximumSize()
public void invalidate()
public void paint(Graphics g) //container
public void update(Graphics g) //container
protected void processEvent(AWTEvent e) //container
public void addNotify() //container
203
Programmieren in Java
public void removeNotify() //container
Abb.: Die Klasse Container
Zur Anzeige eines Fensters auf dem Bildschirm muß eine der Fensterklassen,
Window, Frame, Dialog, Applet, FileDialog instanziert werden. Zum Ableiten
einer eigenen Fensterklasse wird in der Regel die Klasse Frame oder Dialog
verwendet, die beibe aus der Klasse Window abgeleitet sind.
Container
Window
<< Methoden >>
public void show()
public boolean isShowing()
public void dispose()
public void pack()
public void toFront()
public void toBack()
public void addWindowListener(WindowListener l)
Frame
Dialog
public static final int DEFAULT_CURSOR
<< Konstruktor >>
public Frame()
public Frame(String titel)
<< Methoden >>
public String getTitle()
public void setTitle(String titel)
public MenuBar getMenuBar()
public void setmenuBar(MenuBar mb)
public boolean isResizable()
public void setResizable(boolean b)
public void remove(MenuComponent m)
<< Konstruktor >>
public Dialog(Frame eltern)
public Dialog(Frame eltern, boolean modal)
<< Methoden >>
public boolean isModal()
public void setModal(boolean b)
public void show()
public boolean isResizable()
public void setResizable(boolean b)
Abb.: Die Klassen Frame und Dialog
Bsp.187: Ein-, Ausgabe über Textfelder in einem ersten GUI
Ein erstes GUI soll aus einem editierbaren und einem nicht editierbaren Textfeld bestehen.
Den beiden Textfeldern soll jeweils ein Label mit der Beschriftung „Eingabestring:“ bzw.
„Ausgabestring“ zugeordnet sein. Diese Komponenten sollen automatisch von links nach
rechts und von oben nach unten angeordnet werden in einem Panel, das selbst wiederum
einziges Objekt in einem Fenster (Frame) mit dem Titel „Echo“ ist.
import java.lang.*;
import java.awt.*;
public class EchoMitBeno
{
// Einlesen und Ausgeben von Zeichenketten ueber
// eine grafische Benutzeroberflaeche
public static void main(String args[])
187
pr14160
204
Programmieren in Java
{
TextField eingabeTextFeld = new TextField(20);
Label eingabeTextFeldLabel = new Label("Eingabestring:");
eingabeTextFeld.setEditable(true);
TextField ausgabeTextFeld = new TextField(20);
Label ausgabeTextFeldLabel = new Label("Ausgabestring:");
ausgabeTextFeld.setEditable(false);
Panel panel = new Panel();
panel.add(eingabeTextFeldLabel);
panel.add(eingabeTextFeld);
panel.add(ausgabeTextFeldLabel);
panel.add(ausgabeTextFeld);
Frame fenster = new Frame("Echo");
fenster.add(panel);
fenster.pack();
fenster.setVisible(true);
}
}
Die Implementierung
Ausgabefenster:
des
vorliegenden
Programms
zeigt
das
folgende
Abb.:
Das vorliegende Beispiel zeigt noch einige Mängel:
- Zwar kann in das für die Eingabe vorgesehene Textfeld ein String eingegeben werden, das zweite
Textfeld ist allerdings nicht zugänglich. Es passiert somit nicht gerade viel. Es werden noch keine
Ereignisse aufgefangen und behandelt.
- Noch nicht einmal kann das Fenster geschlossen werden, und die Applikation muß (nach dem
Schließen des Frame) mit "CTRL-C" explizit abgebrochen werden.
3.1.2 Ereignisbehandlung unter grafischen Benutzeroberflächen
Im Mittelpunkt der Programmierung unter einer GUI steht die Kommunikation
zwischen System und Anwendungsprogramm. Die Anwendung wird über alle Arten
von Ereignissen und Zustandsänderungen vom System durch Versenden von
Nachrichten (z.B. über Mausklick, Tastatureingaben, Veränderungen an Größe und
Lage der Fenster) informiert. Die Reaktion auf die Nachrichten erfolgt in spezielllen
Ereignisempfängern (EventListeners), die das zum Ereignis passende EmpfängerInterface implementieren. Damit ein Ereignisempfänger Nachrichten einer
bestimmten Ereignisquelle erhält, muß er sich bei der Quelle registrieren lassen, d.h.:
Es muß eine EventListener-Klasse geschrieben, instanziert und bei der
Ereignisquelle registriert werden. Zum Empfang von Nachrichten muß ein Objekt
eine Reihe von Methoden implementieren, die von der Nachrichtenquelle, bei der es
sich registriert hat, aufgerufen werden können. Die Ereignisempfänger stellen diese
Methoden durch Implementierung von Interfaces bereit, die aus der Klasse
EventListener des Pakets java.util abgeleitet sind.
205
Programmieren in Java
Ereignistypen. Im JDK 1.1 werden Ereignistypen durch eine Hierarchie von
Ereignisklassen repräsentiert, die alle aus der Klasse java.util.EventObject188
abgeleitet sind.
188 Speichert das Objekt, das die Nachricht ausgelöst hat und gibt durch Aufruf von "public Object getSource()"
das Objekt an.
206
Programmieren in Java
EventObject
AWTEvent
ComponentEvent
FocusEvent
InputEvent
KeyEvent
ActionEvent
AdjustmentEvent
ContainerEvent
ItemEvent
TextEvent
WindowEvent
MouseEvent
Abb.: Spezifische Ereignisklassen
Die Hierarchie der AWT-spezifischen Ereignisklassen beginnt mit der Klasse
AWTEvent und befindet sich im Paket java.awt. AWTEvent ist Superklasse aller
Ereignisklassen des AWT, die sich im Paket java.awt.event befinden. Dieses
Paket ist in jede Klasse einzubeziehen, die sich mit dem Event-Handling von AWTAnwendungen beschäftigt.
EventListener-Interface. Je Ereignisklasse gibt es ein EventListener-Interface. Es
definiert eine seperate Methode für jede Ereignisart der Ereignisklasse. So besitzt
bspw. das Interface MouseListener die Methoden mouseClicked,
mouseEntered, mouseExited, mousePressed und mouseReleased, die beim
Eintreffen des jeweiligen Ereignis aufgerufen werden.
EventListener
FocusListener
ActionListener
AdjustementListener ItemListener
KeyListener
MouseListener
MouseMotionListener
ComponentListener
ContainerListener
WindowListener
Abb.: Hierarchie der EventListener-Interfaces
Jede der Methoden eines Listener-Interface enthält als einziges Argument ein Objekt
vom zugehörigen Ereignistyp. Alle Methoden sind vom Typ void.
207
Programmieren in Java
3.1.3 Anwendung lokaler Klassen für die Ereignisbehandlung
Im GUI-Objekt, das einen Event-Handler benötigt wird eine lokale Klasse zur
Implementierung des passenden Interface angegeben. Lokale Klassen werden lokal
zu einer anderen Klasse erzeugt. Sie sind nur innerhalb dieser Klasse definiert und
sichtbar. Objekte der lokalen Klasse können nur aus der erzeugenden Klasse
produziert werden. Die lokale Klasse kann aber auf alle Instanzmerkmale der
erzeugenden Klasse zugreifen.
Eine Variante lokale Klassen sind anonyme Klassen. Sie werden ebenfalls lokal zu
einer anderen Klasse erzeugt, kommen aber ohne Klassennamen aus. Dazu werden
sie bei der Übergabe eines Objekts an eine Methode oder als Rückgabewert einer
Methode innerhalb einer einzigen Anwendung definiert und instanziert. Damit einer
anonymen Klasse überhaupt eine sinnvolle Aufgabe zugeführt werden kann, muß sie
aus einer anderen Klasse abgeleitet sein oder ein bestehendes Interface
implementieren.
Bsp.: ActionListener-Interface für die Übernahme eines Textfeld-Inhalts in ein
Textfeld zur Ausgabe
Falls das Textfeld für die Ausgabe den im Textfeld für die Eingabe angegebenen Text
wiedergeben soll, muß ein ActionListener (eine zusätzlich innere, anonyme Klasse) mit der
Methode public void actionPerformed (ActionEvent a) definiert werden. Diese
Methode umfaßt Aufrufe der Methoden getText() und setText() der Klasse TextField.
Darüber kann in das Eingabetextfeld eingegebener Text in das Ausgabefeld ausgegeben
werden. Da die "Methode actionPerformed" der anonymen inneren Klasse auf
"eingabeTextFeld" bzw. "ausgabeTextFeld" zugreift, ist außerhalb von main()
anzugeben:
private static TextField eingabeTextFeld = new TextField(20);
private static TextField ausgabeTextFeld = new TextField(20);
Zum Schließen des Fensters wird über eine anonyme innere Klasse ein
WindowAdapter (eine Art WindowListener) mit der Methode "public void
windowClosing(WindowEvent e)" definiert und an den "Frame" eingekettet.
Wird der Frame geschlossen, dann wird diese Methode aufgerufen, die über
System.exit(0) die Applikation beendet.
Eine Adapterklasse implementiert ein Interface mit mehreren Mehoden und erlaubt
es somit abgeleiteten Klassen, nur noch die Methoden zu überlagern, die tatsächlich
von Interesse sind. Passende Adapterklasssen stellt das Paket java.awt.event bereit,
z.B. FocusAdapter, KeyAdapter, MouseAdapter, MouseMotionAdapter,
ComponentAdapter, ContainerAdapter, WindowAdapter. Die Registrierung
erfolgt mit der Methode addWindowListener, die in den Klassen Dialog und
Frame zur Verfügung steht.
Bsp.189:
Textfeldbearbeitung
mit
"ActionListener-Interface"
und
WindowAdapter
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
public class EchoMitBeno extends Object
{
// Einlesen und Ausgeben von Zeichenketten ueber
// eine grafische Benutzeroberflaeche
private static TextField eingabeTextFeld = new TextField(20);
private static TextField ausgabeTextFeld = new TextField(20);
189
pr14160
208
Programmieren in Java
public static void main(String args[])
{
Frame fenster = new Frame("Echo");
fenster.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
// TextField eingabeTextFeld = new TextField(20);
Label eingabeTextFeldLabel = new Label("Eingabestring:");
eingabeTextFeld.setEditable(true);
eingabeTextFeld.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
String s = eingabeTextFeld.getText();
ausgabeTextFeld.setText(s);
}
});
// TextField ausgabeTextFeld = new TextField(20);
Label ausgabeTextFeldLabel = new Label("Ausgabestring:");
ausgabeTextFeld.setEditable(false);
Panel panel = new Panel();
panel.add(eingabeTextFeldLabel);
panel.add(eingabeTextFeld);
panel.add(ausgabeTextFeldLabel);
panel.add(ausgabeTextFeld);
// Frame fenster = new Frame("Echo");
fenster.add(panel);
fenster.pack();
fenster.setVisible(true);
}
}
Alternativ zur vorliegenden Realisierung der Ereignisbehandlung über eine anonyme
Klasse, kann auch ein Listener über eine lokale Klasse implementiert werden.
Gewöhnlich ist die Anwendung eine Subklasse von Frame.
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
public class Vorl11b extends Frame
{
private static Label eingabeTextFeldLabel = new Label("Eingabestring:");
private static TextField eingabeTextFeld = new TextField(20);
private static Label ausgabeTextFeldLabel = new Label("Ausgabestring:");
private static TextField ausgabeTextFeld = new TextField(20);
public Vorl11b()
{
eingabeTextFeld.setEditable(true);
ausgabeTextFeld.setEditable(false);
Panel panel = new Panel();
panel.add(eingabeTextFeldLabel);
panel.add(eingabeTextFeld);
panel.add(ausgabeTextFeldLabel);
panel.add(ausgabeTextFeld);
eingabeTextFeld.addActionListener(new TFL());
add(panel);
pack();
setVisible(true);
}
class TFL implements ActionListener
209
Programmieren in Java
{
public void actionPerformed(ActionEvent ae)
{
String s = eingabeTextFeld.getText();
ausgabeTextFeld.setText(s);
}
}
public static void main(String args[])
{
Vorl11b vorl11b = new Vorl11b();
vorl11b.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
}
Schließlich
kann
das
Interface
auch
direkt
implementiert
Zweckmäßigerweise ist dann die Anwendung eine Subklasse von Frame.
werden.
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
public class Vorl11a extends Frame implements ActionListener
{
private static Label eingabeTextFeldLabel = new Label("Eingabestring:");
private static TextField eingabeTextFeld = new TextField(20);
private static Label ausgabeTextFeldLabel = new Label("Ausgabestring:");
private static TextField ausgabeTextFeld = new TextField(20);
public Vorl11a()
{
eingabeTextFeld.setEditable(true);
ausgabeTextFeld.setEditable(false);
Panel panel = new Panel();
panel.add(eingabeTextFeldLabel); panel.add(eingabeTextFeld);
panel.add(ausgabeTextFeldLabel); panel.add(ausgabeTextFeld);
eingabeTextFeld.addActionListener(this);
add(panel);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent ae)
{
System.out.println(ae.getActionCommand());
String s = eingabeTextFeld.getText();
ausgabeTextFeld.setText(s);
}
public static void main(String args[])
{
Vorl11a vorl11a = new Vorl11a();
vorl11a.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
}
210
Programmieren in Java
211
Programmieren in Java
3.1.4 Externe und interne Darstellung von numerischen Werten
Numerische Werte werden
- extern (z.B. im Textfeld eines GUI) durch Zeichenketten dargestellt
- intern als Werte von einem der elementaren Datentypen byte, short, int, long,
float oder double oder als Objekte von einem der Referenzdatentypen (WrapperKlassen) Byte, Short, Integer, Long oder Double. Diese Klassen stellen
Methoden zur Umwandlung von numerischen Werten in Zeichenketten und
umgekehrt zur Verfügung. Da nicht jede beliebige Zeichenkette als interne
Darstellung einer Zahl interpretiert werden kann, kann beim Versuch der
Umwandlung eine NumberFormatExpression auftreten, die normalerweise mit
einer try-catch-Anweisung entsprechend behandelt werden sollte.
Bsp.190: Einlesen und Ausgeben von numersichen Werten
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
public class EchoZahl extends Object
{
// Einlesen und Ausgeben von Zeichenketten ueber
// eine grafische Benutzeroberflaeche
private static TextField eingabeTextFeld = new TextField(20);
private static TextField ausgabeTextFeld = new TextField(20);
public static void main(String args[])
{
Frame fenster = new Frame("Echo");
fenster.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
// TextField eingabeTextFeld = new TextField(20);
Label eingabeTextFeldLabel = new Label("Zahleneingabe:");
eingabeTextFeld.setEditable(true);
eingabeTextFeld.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
Double dRef = null;
String s = eingabeTextFeld.getText();
try {
dRef = new Double(s);
}
catch (NumberFormatException nfe)
{
System.err.println(nfe.toString());
}
double d = dRef.doubleValue();
d++;
String s1 = Double.toString(d);
String s2 = String.valueOf(d);
ausgabeTextFeld.setText(s2);
}
});
// TextField ausgabeTextFeld = new TextField(20);
Label ausgabeTextFeldLabel = new Label("Ausgabestring:");
190
pr14160
212
Programmieren in Java
ausgabeTextFeld.setEditable(false);
Panel panel= new Panel();
panel.add(eingabeTextFeldLabel);
panel.add(eingabeTextFeld);
panel.add(ausgabeTextFeldLabel);
panel.add(ausgabeTextFeld);
// Frame fenster = new Frame("Echo");
fenster.add(panel);
fenster.pack();
fenster.setVisible(true);
}
}
3.1.5 Low-Level-Events
Von der Klasse ComponenEvent sind Event-Klassen für "Low-Level"-Ereignisse
abgeleitet. Sie sind für den Transfer von elementaren Nachrichten zuständig, die von
Fenstern und Dialogelementen stammen.
3.1.5.1 Component-Events
Wird eine Komponente verschoben oder ihre Größe bzw. ihr Anzeigezustand
verändert, dann wird ein Componen-Event generiert. Da Fenster und alle
Dialogelemente aus der Klasse Component abgeleitet sind, gelten die hier
angegebenen Ereignisse für nahezu alle GUI-Elemente.
Der Empfänger für ComponentEvent muß das Interface ComponentListener
implementieren und bekommt Events des Typs ComponentEvent übergeben.
ComponentEvent erweiter AWTEvent und stellt neben getID, getSource die
Methode public Component getComponent() bereit, mit der die Komponente
ermittelt werden kann, die die Nachricht ausgelöst hat. Die Registrierung der
Empfängerklasse erfolgt mit
public void addComponentListener(ComponentListener l).
Ereignismethode
componentShown
componentHidden
componentMoved
componentResized
Bedeutung
Eine Komponente wurde sichtbar
Eine Komponente wurde unsichtbar
Eine Komponente wurde verschoben
Die Größe der Komponente hat sich verändert
Abb.: Übersicht zu den Methoden von ComponentListener
3.1.5.2 Window-Events
Ein WindowEvent wird generiert, falls sich am Status eines Fensters eine Änderung
ergeben hat, die für das Anwenderprogramm interessant sein könnte. Ein Empfänger
für Window-Events muß das Interface WindowListener implementieren.
WindowEvent stellt neben getID, getSource die Methode public Window
getWindow() zur Verfügung, mit der das Fenster ermittelt werden kann, das die
Nachricht ausgelöst hat. Die Registrierung der Empfängerklasse erfolgt über die
213
Programmieren in Java
Methode public void addWindowListener(WindowListener l) , die in den
Klassen Dialog und Frame zur Verfügung steht.
Ereignismethode
windowActivated
windowClosed
windowClosing
windowDeactivated
windowDeiconified
windowIconified
windowOpened
Bedeutung
Das Fenster wurde aktiviert. Die Methode wird nach dem Erstellen des
Fensters aufgerufen und wenn ein Fenster, das im Hintergrund stand,
erneut in den Vordergrund gelangt.
Das Fenster wurde geschlossen
Das Fenster soll geschlossen werden. Diese Methode wird aufgerufen,
wenn der Anwender das Fenster über die TitelLeiste, das Systemmenü
oder die Tastenkombination ALT+F4 schließen will. Die Anwendung hat
den Code bereit zu stellen, der das Fenster schließt. Standardmäßig
reagiert das Programm nicht auf diese Benutzeraktionen
Das Fenster wurde deaktiviert, also in den Hintergrung gestellt
Das Fenster wurde wiederhergestellt, nachdem es zuvor auf
Symbolgröße verkleinert wurde
Das Fenster wurde auf Symbolgröße verkleinert
Das Fenster wurde geöffnet.
Abb.: Übersicht zu den Methoden von WindowListener
3.1.5.3 Mouse-Events
Ein Mouse-Event entsteht, wenn der Anwender (innerhalb der Client-Area des
Fensters) eine der Maustasten drückt oder losläßt. Dabei reagiert das Programm
sowohl auf Klicks der linken als auch der ( - falls vorhanden -) der rechten Maustaste
und zeigt an, welche der Umschalttasten STRG, ALT, UMSCHALT oder META
während des Mausklicks gedrückt waren. Es ist möglich zwischen einfachen oder
doppelten Mausklicks zu unterscheiden. Ein Empfänger für Mouse-Events erweitert
die Klasse InputEvent und stellt neben getID, getSource eine Reihe
zusätzlicher Methoden bereit. Die Registrierung der Empfängerklasse erfolgt mit
public void addMouseListener(MouseListener l), die in allen Klassen
zur Verfügung steht, die aus Component abgeleitet sind.
Ereignismethode
mousePressed
mouseReleased
mouseClicked
mouseEntered
mouseExited
Bedeutung
Die Maustaste wurde gedrückt
Die gedrückte Maustaste wurde losgelassen
Eine Maustaste wurde gedrückt und wieder losgelassen. Die Methode wird
nach mouseReleased aufgerufen.
Der Mauszeiger wurde in den Client-Bereich der auslösenden
Komponente hineinbewegt
Der Mauszeiger wurde aus dem Client-Bereich der auslösenden
Komponente herausbewegt.
Abb.: Übersicht zu den Methoden von MouseListener
Die Ermittlung der Position des Mauszeigers kann über
public int getX(); // liefert die X-Koordinate
public int getY(); // liefert die Y-Koordinate
public Point getPoint(); /* liefert X- und Y-Koordinate des Punkts, an dem
sich der Mauszeiger beim Auftreten des Ereignisses befindet */
ermittelt werden. Koordinatenwerte werden relativ zum Ursprung der auslösenden
Komponente angegeben.
214
Programmieren in Java
Weiterhin
gibt
es
in
MouseEvent
die
Methode
public
boolean
isPopupTrigger(). Darüber kann abgefragt werden, ob das Klickereignis den
Ausruf eines Popup-Menüs anzeigen soll. Die Methode public int
getClickCount() liefert die Anzahl der Mausklicks.
Für die Beabeitung von Mouse-Events stehen außerdem die aus InputEvent
191geerbten Methoden
public
public
public
public
boolean
boolean
boolean
boolean
isShiftDown();
isControlDown();
isMetaDown();
isAltDown();
zur Verfügung.
3.1.5.4 MouseMotion-Events
Sie geben Auskunft über die Bewegung des Mauszeigers. Ein Empfänger für
"MouseMotionEvents" muß das Interface MouseMotionListener implementieren.
Es wird mit public void addMouseMotionListener(MouseListener l)
registriert. Die Methode steht allen Objekten der Klasse Component oder daraus
abgeleiteteter Klassen zur Verfügung. Die Methoden von MouseMotionListener
bekommen Events des Typs MouseEvent übergeben. Damit stehen diesselben
Methoden wie bei MouseEvent zur Verfügung.
Das Interface MouseMotionListener definiert
public abstract void mouseMoved(MouseEvent e);
// Aufruf bei Bewegung einer Maus ohne Drücken der Maustaste
public abstract void mouseDragged(MouseEvent e);
/* Aufruf bei Bewegung der Maus und gedrückter rechter
Maustaste */
oder
linker
3.1.5.5 Fokus-Events
Der Fokus zeigt an, welches Fenster Tastatureingaben erhält. Sind mehrere Fenster
gleichzeitig geöffnet, so kann immer nur eines von ihnen den Fokus beanspruchen.
Sind auf einem aktiven Fenster mehrere Dialogelemente aktiv, so kann ebenfalls nur
eines davon den Fokus erhalten, denn jedes Dialogelement wird ebenfalls durch ein
(meist unsichtbares) Fenster dargestellt.
Ein Empfänger für Fokus-Events muß das Interface FocusListener
implementieren und bekommt Events des Typs FocusEvent übergeben.
FocusEvent erweitert ComponentEvent und stellt neben getID(), getSource()
die Methode public boolean isTemporary() bereit. Sie zeigt an, ob der
Fokuswechsel temporär oder permanent ist. Die Registrierung von Focus-Events
erfolgt über public void addFocusListener(FocusListener l), die allen
Objekten des Typs Component oder daraus abgeleiteten Objekten zur Verfügung
191 InputEvent ist Basisklasse von MouseEvent und KeyEvent. Sie stellt Methoden bereit, die
allgemeine Informationen über den Zustand der Umschalttasten STRG, ALT, UMSCHALT oder META zum
Zeitpunkt des Ereignisses liefern
215
Programmieren in Java
steht, Das Interface FocusListener enthält zwei unterschiedliche Methoden:
public abstract
// Aufruf, wenn
public abstract
// Aufruf, wenn
void focusGained(FocusEvent e)
die Komponente den Fokus erhält
void focusLost(FocusEvent e)
die Komponente den Fokus abgibt
Über die Methode public void requestFocus() kann eine Komponente den
Fokus für sich selbst beanspruchen bzw. ihn einer anderen Komponenten zuweisen.
3.1.5.6 Key-Events
Ein Empfänger für Key-Events muß das Interface KeyListener implementieren und
bekommt Events des Typs KeyEvent übergeben. KeyEvent erweitert die Klasse
InputEvent, die aus ComponentEvent abgeleitet ist, und stellt neben getID,
getSource eine Reihe von Methoden zur Erkennung und Berabeitung von
Tastaturcodes zur Verfügung. Die Registrierung erfolgt mit der Methode public
void addKeyListener (KeyListener l), die auf allen Objekten des Typs
Component oder daraus abgeleiteter Klassen zur Verfügung steht. Das Interface
KeyListener definiert drei unterschiedliche Methoden:
public abstract void keyTyped(KeyEvent e);
public abstract void keyPressed(KeyEvent e);
public abstract void keyReleased(KeyEvent e);
Die Taste, die gedrückt wurde, erhält man über die folgenden Methoden der Klasse
KeyEvent bereitgestellt:
public int getKeyCode()
liefert virtuelle Tastencodes, die in KeyEvent als symbolische Konstanten definiert wurden.Hier wird
beim Drücken der Taste A immer der Code VK_A geliefert, unabhängig davon, ob UMSCHALT
gedrückt wurde oder nicht.
Symbolischer Name
VK_0..VK_9
VK_A..VK_Z
VK_ENTER
VK_SPACE
VK_TAB
VK_ESCAPE
VK_BACK_SPACE
VK_F1..VK_F12
VK_HOME, VK_END
VK_PAGE_UP, VK_PAGE_DOWN
VK_DOWN, VK_UP
VK_LEFT, VK_RIGHT
VK_INSERT, VK_DELETE
Bedeutung
0..9
A..Z
Enter
Leertaste
Tabulator
Escape
Rückschritt
Die Funktionstasten F1 .. F12
Home, End
Bild hoch, Bild runter
Cursor hoch, Cursor runter
Cursor links, Cursor rechts
Einfg, Entf
Abb.: Tabelle der virtuellen Key-Codes
public char getKeyChar()
liefert das Zeichen, das der gedrückten Zeichentste entspricht, z.B. "a", wenn Taste A gedrückt wurde,
und "A", wenn die Tastenkombination UMSCHALT + A gedrückt wurde. Funktionstasten werden nicht
übertragen. Der Rückgabewert ist hier KeyEvent.CHAR_UNDEFINED.
216
Programmieren in Java
keyTyped192
keyPressed195
getKeyCode()
Zeichentaste193: VK_UNDEFINED
Funktionstaste194: Zeichentaste: VK_...
Funktionstaste: VK_...
getKeyChar()
Zeichentaste: Taste als char
Funktionstaste: Zeichentaste: Taste als char
Funktionstaste: CHAR_UNDEFINED
Rückgabecode bei Tastatur-Ereignissen
Zusätzlich stehen fogende aus InputEvent geerbten Methoden zur Verfügung:
public
public
public
public
boolean
boolean
boolean
boolean
isShiftDown();
isControlDown();
isMetaDown();
isAltDown();
Bsp.: Das folgende Programm196 schreibt alle möglichen Ereignisse zu Low-LevelEvents auf.
import java.awt.*;
import java.awt.event.*;
public class PR14166 extends Frame
{
PR14166()
{
addComponentListener(new CL());
addFocusListener(new FL());
addKeyListener(new KL());
addMouseListener(new ML());
addMouseMotionListener(new MML());
}
class CL implements ComponentListener
{
public void componentMoved(ComponentEvent e)
{
System.out.println(e.toString());
}
public void componentResized(ComponentEvent e)
{
System.out.println(e.toString());
}
public void componentHidden(ComponentEvent e)
{
System.out.println(e.toString());
}
public void componentShown(ComponentEvent e)
{
System.out.println(e.toString());
}
}
class FL implements FocusListener
{
public void focusGained(FocusEvent e)
{
System.out.println(e.toString());
}
192
zeigt das Verhalten beim Aufruf der Listener-Methode keyTyped
Tasten, mit denen Buchstaben, Ziffern oder sonst. Unicode-Zeichen eingegeben werden, wie z.B. a, A, 1, 2,
% aber auch ESC, SPACE, TAB
194 Dazu gehören bspw. F1, F2, Pos 1 aber auch die Umschalttasten: STRG, ALT, UMSCHALT
195 zeigt das Verhalten beim Aufruf von keyPressed
196 vgl. pr14160
193
217
Programmieren in Java
public void focusLost(FocusEvent e)
{
System.out.println(e.toString());
}
}
class KL implements KeyListener
{
public void keyPressed(KeyEvent e)
{
System.out.println(e.toString());
}
public void keyReleased(KeyEvent e)
{
System.out.println(e.toString());
}
public void keyTyped(KeyEvent e)
{
System.out.println(e.toString());
}
}
class ML implements MouseListener
{
public void mouseClicked(MouseEvent e)
{
// requestFocus();
System.out.println(e.toString());
}
public void mousePressed(MouseEvent e)
{ System.out.println(e.toString()); }
public void mouseReleased(MouseEvent e)
{ System.out.println(e.toString()); }
public void mouseEntered(MouseEvent e)
{ System.out.println(e.toString()); }
public void mouseExited(MouseEvent e)
{ System.out.println(e.toString()); }
}
class MML implements MouseMotionListener
{
public void mouseDragged(MouseEvent e)
{ System.out.println(e.toString()); }
public void mouseMoved(MouseEvent e)
{ System.out.println(e.toString()); }
}
public static void main(String[] args)
{
PR14166 f = new PR14166();
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.setSize(200,200);
f.setVisible(true);
}
}
218
Programmieren in Java
3.1.6 Der Weg eines Ereignisses
Jede Ereignisquelle besitzt eine Reihe von Methoden, die für das Aufbereiten und
Verteilen der Nachricht zuständig sind. Beim Weiterreichen einer Nachricht wird
innerhalb der Nachrichtenquelle die Methode processEvent aufgerufen. Diese
verteilt die Nachricht anhand ihres Typs an spezialisierte Methoden, deren Name
sich nach dem Typ der zugehörigen Ereignisquelle richtet:
protected void processComponentEvent(ComponentEvent e)
protected void processFocusEvent(FocusEvent e)
protected void processKeyEvent(KeyEvent e)
protected void processMouseEvent(MouseEvent e)
protected void processMouseMotionEvent(MouseEvent e)
protected void processActionEvent(ActionEvent e)
Abb.: Spezialisierte Methoden für das Event-Handling
"processEvent" bzw. die spezialisierten Methoden für das Event-Handling werden
nur aufgerufen, wenn der entsprechende Ereignistyp für diese Ereignisquelle aktiviert
wurde. Dies geschieht in folgenden Fällen:
-
Ein passender Ereignisempfänger wurde über die zugehörige addEventListener-Methode
registriert.
Ein Ereignistyp wurde explizit durch Aufruf der Methode protected final void
enableEvents(long eventsToEnable) aktiviert. Die Methode erwartet eine Maske, die
durch eine bitweise Oder-Verknüpfung passender Konstanten aus der Klasse AWTEvent
zusammengesetzt werden kann. Folgende Bitmasken akzeptiert diese Methode:
ACTION_EVENT_MASK
ADJUSTMENT_EVENT_MASK
COMPONENT_EVENT_MASK
CONTAINER_EVENT_MASK
FOCUS_EVENT_MASK
ITEM_EVENT_MASK
KEY_EVENT_MASK
MOUSE_EVENT_MASK
MOUSE_MOTION_EVENT_MASK
TEXT_EVENT_MASK
WINDOW_EVENT_MASK
Die Namen dieser Bitmasken verweisen auf die Namen der Ereignisklassen, für
deren Objekte sie den Ereignisfilter durchlässig schalten. Sollen mehrere aktiviert
werden, dann ist diese Kombination durch Addition bzw. bitweises Oder möglich,
z.B.:
enableEvents(ACTION_EVENT_MASK + WINDOW_EVENT_MASK)
bzw.
enableEvents(ACTION_EVENT_MASK | WINDOW_EVENT_MASK)
Sobald ein soches Ereignis erzeugt wird, sieht sein Weg dann folgendermaßen aus:
1. Die Methode processEvent der eigenen Klasse erhält das Ereignis
2. Nach der Ereignisverarbeitung wird die Methode der Subklasse aufgerufen
3. Diese prüft den Ereignistyp und ruft dann typspezifische process...Event-Methoden auf, z.B.
processActionEvent, processFocusEvent.
4. Diese prüfen, ob Listener registriert sind, und rufen sie gegebenenfalls auf.
5. Nach diesen Aufrufen wird die Methode processEvent der nächsten Superklasse aufgerufen.
219
Programmieren in Java
6. Sie verfährt wieder so, wie unter den Ziffern 3 bis 5 beschrieben. Das wiederholt sich so lange, bis
processEvent von ComponentEvent durchlaufen wird.
Bsp.: Das folgende Programm schreibt alle möglichen Ereignisse zu Low-LevelEvents auf.
import java.awt.*;
import java.awt.event.*;
public class PR14168 extends Frame
{
PR14168()
{
// Bekanntmachen der im Programm zu bearbeitenden Ereignisse
enableEvents(
AWTEvent.COMPONENT_EVENT_MASK |
AWTEvent.FOCUS_EVENT_MASK |
AWTEvent.KEY_EVENT_MASK |
AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK |
AWTEvent.CONTAINER_EVENT_MASK);
}
public void processEvent(AWTEvent e)
{
System.out.println(e.toString());
super.processEvent(e);
}
public static void main(String[] args)
{
PR14168 f = new PR14168();
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.setSize(200,200);
f.setVisible(true);
}
}
220
Programmieren in Java
3.2 Kommunikation Anwender – Programm über Dialoge bzw.
Menüs
3.2.1 Dialoge
Der Anwender verkehrt im Rahmen grafischer Benutzeroberflächen über vom
Programm vorgegebene Dialoge. Diese bestehen aus einem Fenster und einer
Reihe von Dialogelementen (z.B. Textfelder, Buttons, Listboxen) zur Darstellung und
Erfassung programmspezifischer Daten. Das Design der Dialoge wird von
Layoutmangern unterstützt, die sich um Größe und Anordnung der einzelnen
Dialogelemente kümmern.
Erstellen eines Dialogs
Dafür sind 4 Schritte nötig:
-
Anlegen eines Fensters
Zuordnen eine Layoutmanagers
Einfügen von Dialogelementen
Anzeigen des Fensters
Anlegen eines Fensters. Ein Dialogfenster kann wahlweise aus der Klasse Frame
oder Dialog abgeleitet werden. "Dialog" erlaubt "modale Dialoge197" und
verhindert das Verändern der Fenstergröße durch den Anwender. Im Gegensatz zum
Frame kann ein "Dialog"-Fenster keine Menüleiste erzeugen und dem Fenster kein
Icon198 zuordnen.
Zuordnen eines Layoutmanagers. Es wird über die Methode "public void
setLayout(LayoutManager mgr)" der Klasse Container realisiert. Java stellt fünf
bereit:
FlowLayout
BorderLayout,
GridLayout
Layoutmanager199
GridBagLayout und CardLayout.
Neben den Fähigkeiten eines Layoutmanagers bestimmt in der Regel die
Reihenfolge der Aufrufe von der "add"-Methode des Fensters die tatsächliche
Anordnung der Komponenten auf dem Bildschirm.
Schachteln von Layoutmanagern. Dazu wird an die Stelle, die das Sublayout erhalten
soll, einfach ein Objekt der Klasse Panel eingefügt, das einen eigenen
Layoutmanager erhält. Dieses Panel kann mit Dialogelementen bestückt werden, die
entsprechend dem zugeordneten Sublayout formatiert werden, z.B.:
import java.awt.*;
import java.awt.event.*;
public class PR14171 extends Frame
{
public PR14171()
{
197
Modale Dialaloge verhindern die Interaktion des Anwenders mit anderen Fenstern der Anwendung bis zum
Schließen des Dialogfensters.
198 Falls ein Fenster (unter Windows) minimiert wird, zeigt es ein Icon an. Mit Hilfe eines Doppelklicks auf das
Icon kann eine ursprüngliche Größe des Fensters wiederhergestellt werden. Mit Hlife der Methode "public
void setIconImage(Image bild)" kann einem Fenster ein Icon zugeordnet werden, das beim
Minimieren angezeigt wird.
199 Vgl. 5.4.2
221
Programmieren in Java
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
setVisible(false);
dispose();
System.exit(0);
}
});
// Layout festelegen und Komponenten hinzufuegen
int i = 0;
Panel p1 = new Panel();
p1.setLayout(new GridLayout(3,1));
p1.add(new Button("Schaltflaeche " + ++i));
p1.add(new Button("Schaltflaeche " + ++i));
p1.add(new Button("Schaltflaeche " + ++i));
Panel p2 = new Panel();
p2.setLayout(new BorderLayout());
p2.add("North",new Button("Schaltflaeche " + ++i));
p2.add("South",new Button("Schaltflaeche " + ++i));
p2.add("West",new Button("Schaltflaeche " + ++i));
p2.add("East",new Button("Schaltflaeche " + ++i));
p2.add("Center",new Button("Schaltflaeche " + ++i));
// Hauptfenster
setLayout(new GridLayout(1,2));
add(p1);
add(p2);
pack();
}
public static void main(String args[])
{
PR14171 f = new PR14171();
f.setVisible(true);
}
}
Das vorliegende Programm zeigt nach dem Aufruf das folgende Fenster:
Abb.:
Einfügen von Dialogelementen. Es erfolgt über
public Component add(Component komponente)
public Component add(Component komponente, int pos)
public Component add (String name, Component komponente)
// erwartet einen String-Parameter, der bei bestimmten Layout-Managern
// (z.B. BorderLayout) Informationen zur Positionierung der Elemente ("bei
// BorderLayout: "South", "East", "West", "North", "Center") angibt.
der Klasse Container. Mit "public void remove(Component komponente)"
können bereits an das Fenster übergebene Komponenten gelöscht werden.
Anzeigen eines Dialogfensters. Es erfolgt durch einen Aufruf von "setVisible".
Zweckmäßig sollte zuvor "public void pack()" der Klasse Window zur
222
Programmieren in Java
Anpassung der Fenstergröße an den für die Darstellung der Dialogelemente
erforderlichen Platz ausgeführt werden.
Popup-Fenster der Klasse Dialog
Die Klasse Dialog200 stellt ein Popup-Fenster bereit und ermöglicht die Erzeugung
"modaler" bzw. "nicht modaler" Dialoge. "Modal" bedeutet: Das Dialogfeld blockiert
andere Fenster, während es angezeigt wird, z.B.201:
import java.awt.*;
import java.awt.event.*;
public class InfoDialog extends Dialog
{
protected Button schalter;
protected Label label;
public InfoDialog(Frame eltern, String titel, String nachricht)
{
super(eltern,titel,false);
this.setLayout(new BorderLayout(15,15));
label = new Label(nachricht,Label.CENTER);
this.add("Center",label);
schalter = new Button("Okay");
schalter.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
dispose();
setVisible(false);
}
});
Panel p = new Panel();
p.setLayout(new FlowLayout(FlowLayout.CENTER,15,15));
p.add(schalter);
this.add("South",p);
this.pack();
}
public static void main(String args[])
{
Frame f = new Frame("InfoDialog-Test");
f.setSize(300,100);
f.setVisible(true);
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
// dispose();
// setVisible(false);
System.exit(0);
}
});
InfoDialog dlg = new InfoDialog(f,"Dialog-Demo",
"Diese Demo wurde nach einer Vorlage von"
+ "David Flanagan geschrieben");
dlg.setVisible(true);
}
}
200
201
vgl. 5.3.4
vgl. pr14170
223
Programmieren in Java
Dialogfenster sind Übergangsfenster. Sie dienen dazu, den Benutzer über Ereignisse
zu informieren oder Eingabe vom Benutzer anzufordern. Im Gegensatz zu Frames
haben Dialogfelder im allg. keine Titelleiste oder Schaltfläche zum Schließen des
Fensters.
Ein Dialogfenster ist, wie ein Frame, ein Panel, in dem Komponenten der
Benutzeroberfläche angeordnet, gezeichnet sowie Grafikoperationen ausgeführt
werden können.
Mit der Klasse FileDialog kann ein plattformspezifischer Dateidialog erzeugt
werden.
Dialog
FileDialog
public static final int LOAD
public static final int SAVE
<< Konstruktor >>
public FileDialog(Frame eltern)
// erzeugt Datei-Dialog ohne Titel zum Öffnen einer Datei
public FileDialog(Frame eltern, String titel)
//erzeugt Dialog zum Öffnen einer Datei mit einem Titel im
// Fensterrahmen
public FileDialog(Frame eltern, String titel, int mode)
// erzeugt Dialog mit einem Titel im Fensterrahmen. Da der
// Dialog sowohl für Sichern und Laden ausgelegt ist, sind
// weder LOAD oder SAVE mit anzugeben.
<< Methoden >>
public String getDirectory()
// liefert das Verzeichnis des Dialogs
public synchronized void setDirectory(String dir)
// setzt das Verzeichnis, das zu Beginn ausgegeben werden soll
public String getFile()
// liefert die ausgewählte datei des Dialogs
public synchronized void setFile(String file)
// Datei, die zu Beginn angezeigt wird
public FilenameFilter getFilenameFilter()
// liefert FilenameFilter.-Objekt zurück
public synchronized void setFilenameFilter(FilenameFilter filter)
// setzt den FilenameFilter
Abb.: Die Klasse FileDialog
Vererbungskette. FileDialog leitet sich von Dialog ab. Dialog ist eine Ableitung
von Window. Window ist ein Container und dieser wiederum ein Component.
Bsp.: Laden und Speichern von Dateien mit Unterstützung durch ein Dateidialogfeld202.
import java.awt.*;
import java.awt.event.*;
public class DateiDialog extends Frame
{
TextField dateiName = new TextField();
202
pr14160
224
Programmieren in Java
TextField verzeichnis = new TextField();
Button oeffnen = new Button("Oeffnen");
Button sichern = new Button("Sichern");
public DateiDialog()
{
setTitle("Dateidialog-Test");
Panel p = new Panel();
p.setLayout(new FlowLayout());
oeffnen.addActionListener(new OeffnenL());
p.add(oeffnen);
sichern.addActionListener(new SichernL());
p.add(sichern);
add(p,BorderLayout.SOUTH);
verzeichnis.setEditable(false);
dateiName.setEditable(false);
p = new Panel();
p.setLayout(new GridLayout(2,1));
p.add(dateiName); p.add(verzeichnis);
add(p,BorderLayout.NORTH);
}
class OeffnenL implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
FileDialog d = new FileDialog(DateiDialog.this,
"Welche Datei soll geoeffnet werden?");
d.setFile("*.java");
d.setDirectory(".");
// Aktuelles Verzeichnis
d.show();
String datei = "*.*";
if ((datei = d.getFile()) != null)
{
dateiName.setText(datei);
verzeichnis.setText(d.getDirectory());
}
else {
dateiName.setText("Cancel wurde gedrueckt!");
verzeichnis.setText("");
}
}
}
class SichernL implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
FileDialog d = new FileDialog(DateiDialog.this,
"Welche Datei soll gesichert werden?",
FileDialog.SAVE);
d.setFile("*.java");
d.setDirectory(".");
// Aktuelles Verzeichnis
d.show();
String datei = "*.*";
if ((datei = d.getFile()) != null)
{
dateiName.setText(datei);
verzeichnis.setText(d.getDirectory());
}
else {
dateiName.setText("Cancel wurde gedrueckt!");
verzeichnis.setText("");
}
}
}
public static void main(String args[])
{
Frame f = new DateiDialog();
225
Programmieren in Java
f.addWindowListener(
new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.setSize(250,110); f.setVisible(true);
}
}
Bei Applets ist es vom Browser abhängig, ob Instanzen von FileDialog eingesetzt
werden können. Die meisten Browser erzeugen lediglich einen Fehler.
Vordefinierte Dialogelemente
Jedes Dialogelement wird in Java durch eine eigene Klasse repräsentiert. Zur
Aufnahme eines Dialogelements in einen Dialog wird eine neue Instanz der
gewünschten Klasse angelegt und das resultierende Element mit "add" in den Dialog
eingefügt. Alle Dialogelemente sind aus der Klasse Component abgeleitet. Sie
verfügen über die grundlegenden Eigenschaften eines Fensters, besitzen Größe und
Position und sind in der Lage, Nachrichten zu empfangen und zu bearbeiten.
Component
Button
TextComponent
TextField
Container
TextArea
Menu
Panel
Applet
MenuComponent
MenuBar
Checkbox
MenuItem
Window
Frame
Dialog
Abb.: Komponenten des AWT
Vordefinierte Dialogelemente unter den Komponenten des AWT sind:
Labels203, Schaltflächen(Buttons)204, Kontrollkästchen und Optionsfelder205,
Auswahlmenüs206, Listenfelder207, Textbereiche und Textfelder208, Schieberegler209.
Das Dialogelement „Canvas“
203
vgl. 5.2.2
vgl. 5.2.1
205 vgl. 5.2.3
206 vgl. 5.2.4
207 vgl. 5.2.5
208 vgl. 5.2.6
209 vgl. 5.2.7
204
226
Programmieren in Java
Die meisten AWT-Komponenten bieten Möglichkeiten für die Ausführung von
Zeichenoperationen. Zeichenbereiche sind Komponenten, die nur speziell zum
Zeichnen ausgerichtet sind. Zeichenbereiche können keine anderen Komponenten
enthalten, akzeptieren aber Ereignisse. Außerdem können sie Animationen und
Bilder anzeigen. Zum Erstellen eines Zeichenbereichs wird die Klasse Canvas
benutzt. Das eigentliche Zeichnen erfolgt in der Methode paint(Graphics g).
Diese Methode wird immer dann aufgerufen, wenn die Zeichenfäche neu gezeichnet
werden muß. Typische Situationen, in denen paint(Graphics g) aufgerufen wird,
sind:
- Der die Zeichenfläche umfassende Rahmen (Frame) wird ertmals erstellt
- Die Methode repaint() wird explizit aufgerufen
- Die Zeichenfläche wurde teilweise oder ganz von anderen Fenstern überdeckt
- Die Größe der Zeichenfläche hat sich verändert
Durch Überlagerung von public void paint(Graphics g) sorgt eine CanvasKomponente für die Darstellung auf dem Bildschirm. Der Punkt (0,0) des
übergebenen Graphics-Objekts entspricht dabei der linken oberen Ecke des
Ausgabebereichs.
Bsp.210:
import java.awt.*;
public class KreuzHaar extends java.applet.Applet
{
GridLayout g
= new GridLayout(1,1);
MeinCanvas can = new MeinCanvas();
public void init()
{
setLayout(g);
add(can);
}
}
class MeinCanvas extends java.awt.Canvas
{
public void paint(Graphics g)
{
int x = getSize().width / 2;
int y = getSize().height / 2;
g.setColor(Color.black);
g.drawLine(x-10,y,x-2,y);
g.drawLine(x+10,y,x+2,y);
g.drawLine(x,y-10,x,y-2);
g.drawLine(x,y+10,x,y+2);
}
}
Da die Klasse Canvas aus Component abgeleitet ist, bekommt ein Canvas-Objekt
alle Ereignisse zugestellt, die auch an eine Komponente gehen. Hierzu zählen:
Tastatur-, Maus-, Mausbewegungs-, Fokus- und Komponentenereignisse.
210
pr52900
227
Programmieren in Java
3.2.2 Menüs
Jedes Fenster / Frame kann eine eigene Menüleiste besitzen. Jede Menüleiste kann
mehrere Menüs enthalten und jedes Menü beliebige Einträge211.
Menüs dienen zur Auswahl von Funktionen und Optionen in Anwendungen. Menüs
können entweder in der Form von Menubars an der Oberseite von Fenstern oder als
Popupmenüs an einer beliebigen Stelle auftreten.
Ein Menü ist entweder in einer Menüleiste eines Frame-Objekts eingebettet (Klasse
MenuBar) oder erscheint auf Abruf als Kontextmenü (Klasse PopupMenu). Damit
sind entweder Untermenüs (Klasse Menu) oder Menüeinträge (Klasse MenuItem)
enthalten.
Jedes Menü besitzt ein „label“ und ein „aktionKommando“ (ein String, der das Menü
beim ActionEvent kennzeichnet und der ausgelöst wird, wenn der Eintrag
angeklickt wird). Für Menüs können Shortcuts, also Tastaturkürzel, angegeben
werden.
Ein MenuItem ist ein Eintrag in einem Menü, der sich bei Selektion einer Option
ändert (CheckboxMenuItem) oder ein Kommando absetzt.
211
vgl. 5.3.3
228
Programmieren in Java
MenuComponent
{ abstract }
public String getName()
public void setName(String name)
public MenuContainer getParent()
public Font getFont()
public void setFont(Font f)
public final void dispatchEvent(AWTEvent e)
protected void processEvent(AWTEvent e)
public String toString()
MenuItem
MenuBar
<< Konstruktoren >>
public MenuItem()
public MenuItem(String label)
public MenuItem(String label, MenuShortcut s)
<< Methoden >>
public void addNotify()
public String getLabel()
public void setLabel(String label)
public boolean isEnabled()
public void setEnabled(boolean b)
public MenuShortcut getShortcut()
public void setShortcut(MenuShortcut s)
protected final void enableEvents(long eventsToEnable)
protected final void disableEvents(long eventsToEnable)
public void setActionCommand(String kommando)
public String getActionCommand()
public void addActionListener(ActionListener l)
public void removeActionListener(ActionListener l)
protected void processEvent(AWTEvent e)
protected processActionEvent(ActionEvent e)
...
CheckboxMenuItem
<< Konstruktor >>
public MenuBar()
<< Methoden >>
public void addNotify()
public void removeNotify()
public Menu getHelpMenu()
public void setHelpMenu(Menu m)
public Menu add(Menu m)
public void remove
(MenuComponent m)
public int getMenuCount()
public Menu getMenu(int i)
public Enumeration shortcuts()
public MenuItem
getShortcutMenuItem
(MenuShortcut s)
<< interface >>
MenuContainer
Menu
<< Konstruktoren >>
public CheckboxMenuItem()
public CheckboxMenuItem(String label)
public CheckboxMenuItem(String label,
boolean state)
<< Methoden >>
public void addItemListener(ItemListener l)
public void addNotify()
public boolean getState()
protected void processEvent(AWTEvent e)
...
<< Konstruktoren >>
public Menu()
public Menu(String label)
<< Methoden >>
public MenuItem add(MenuItem m)
public void add(String label)
public void addNotify()
public MenuItem getItem(int index)
public void insert(MenuItem mi, int index)
public void insert(String label,int index)
...
229
Programmieren in Java
3.2.3 Popup-Menüs
Popup-Menüs sind nicht wie normale Menüs an eine bestimmte Position gebunden,
sondern tretem meistens dort auf, wo der Benutzer mit der rechten Maustaste
geklickt hat. Für Popup-Menüs ist die Klasse Popup-Menu zuständig. Menüeinträge
können mit der Klasse Menu erzeugt werden.
Menu
PopupMenu
<< Konstruktor >>
public PopupMenu()
// erzeugt ein Popup-Menu
public PopupMenu(String label)
<< Methoden >>
public void addNotify()
public void show(Component origin, int x, int y)
// läßt das Popup-Menu auf der Komponenten
// origin an der Position x, y aufspringen.
Abb.: Die AWT-Menüklasse PopupMenu
Bsp.: 212
import java.awt.*;
import java.awt.event.*;
public class PopMenDemo extends Frame
{
PopupMenu popmen = new PopupMenu();
public PopMenDemo()
{
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
popmen.add(new MenuItem("Eintrag 1"));
popmen.addSeparator();
popmen.add(new MenuItem("Eintrag 2"));
popmen.add(new MenuItem("Eintrag 3"));
popmen.add(new MenuItem("Eintrag 4"));
add(popmen);
popmen.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println(e.getActionCommand() + " wurde gedrueckt");
}
});
addMouseListener(new MouseAdapter()
212
pr53330, PopMenDemo.java
230
Programmieren in Java
{
public void mouseClicked(MouseEvent m)
{
if (m.getModifiers() == m.BUTTON3_MASK)
popmen.show(PopMenDemo.this,m.getX(),m.getY());
}
});
setSize(300,300); setVisible(true);
}
public static void main(String args[])
{ new PopMenDemo().show(); }
}
231
Programmieren in Java
3.3 Grundlagen der Applet-Erstellung
3.3.1 HTML-Grundlagen
Die Einbindung von Java-Applets in HTML-Seiten
Java-Applets werden innerhalb von HTML-Seiten über Referenzen eingebunden.
Referenzen können Überschriften, Texte, Kapitel, Unterkapitel, Absätze, Grafiken
bzw. Links zu anderen Seiten sein. HTML ist eine Dokumentationsbeschreibungssprache mit der logische Strukturen eines Dokuments beschrieben werden. Über
Klartext (ASCII-Text) gibt ein Dokumentenformat Empfehlungen an eine
Darstellungssoftware (Browser) zur Darstellung der Dokumentstruktur.
Außerdem wird beschrieben, welche Funktionalität wie auszuführen ist, damit sie
dem geplanten Layout und der vorgegebenen Funktionalität entspricht. Es gibt keine
verbindliche Darstellungsvorschrift, deshalb kann die Ausführung von HTML-Seiten
in verschiedenen Browsern oft unterschiedlich anfallen.
Die Basis von HTML
Die Sprache HTML baut auf der Sprache SGML auf. HTML ist die Abkürzung für
Hypertext Markup Language und wurde aus der in der ISO-Norm 87779:1986
festgeschriebenen Spache SGML (Structured Generalized Markup Language)
entwickelt. Ein in HTML geschiebenes Dokument kann außer Text, Grafiken und
multimediale Elemente (Sound, Video, usw.) enthalten (Referenz auf Grafik- oder
Multimedia-Dateien). Weiterhin können mit gewissen Einschränkungen über HTML
Datenbankabfragen formuliert, Resultate optisch aufbereitet und Menüstrukturen
aufgebaut werden.
Die Normung der HTML realisiert und kontrolliert das World Wide Web Consortium
(W3C) mit Sitz in Genf. Ende 1997 kam es zu zur offiziellen Verabschiedung des
Standards.
HTML-Steueranweisungen
Tags. Alle HTML-Steueranweisungen werden in sog. Tags geschrieben, die von
spitzen Klammern (< >) begrenzt sind. Man unterscheidet zwischen Einleitungs- und
Abschluß-Tags. Die Abschluß-Tags sind beinahe identisch mit dem Einleitungs-Tag.
Sie besitzen lediglich zusätzlich einen Slash (/) nach dem „<“-Zeichen.
Groß- und Kleinschreibung spielt bei HTML-Steueranweisungen keine Rolle.
HTML-Seiten haben zwingend die Extension .htm oder .html.
232
Programmieren in Java
Das Grundgerüst einer HTML-Seite
Eine HTML-Seite wird immer in die Anweisung <html> am Anfang und </html> am
Ende eingeschlossen.
Konkrete Referenzierung eines Java Applet
Die konkrete Referenzierung eines Java-Applet wird mit dem Applet-Tag <applet
...> eingeleitet. Zwischen dem einleitenden Tag und dem abschließenden AppletTag (</applet>) können benötigte Parameter oder beliebiger Text eingegeben
werden. Die einfachste Form der Applet-Referenz. Ohne irgendwelche Parameter
wird ein Java-Applet eingebunden mit
<APPLET CODE = "klassenelement" WIDTH = Wert HEIGHT = Wert>
</APPLET>
klassenelement: Applet-Klasse
WIDTH = Wert: Breite des Applet in Pixel
HEIGHT = Wert: Höhe des Applet in Pixel
Das <APPLET>-Tag erzeugt keinen Absatz, deshalb sollte es in einem allgemeinen
Text-Tag stehen, z.B. <P> oder in einem Überschriften-Tag (<H1>, <H2> usw.).
Optionale Parameter des <Applet>-Tag
Parameter
CODEBASE
ARCHIVE
OBJECT
ALT
NAME
ALIGN
VSPACE
HSPACE
Bedeutung
Hier kann ein alternatives Verzeichnis213 für das Laden von Klassendateien
angegeben werden. Fehlt diese Angabe, wird das Dokumentenverzeichnis
genommen.
Angabe des JAR-Archivs, aus dem die Klassendateien und sonstige Resourcen
des Applet genommen werden
Name der Datei, die den serialisierten Inhalt des Applet enthält.
Alternativer Text für Browser, die das Applet verstehen, aber Java nicht
unterstützen
Eindeutiger Name für das Applet. Er kann zur Unterscheidung mehrerer
kommunizierender Applets auf einer Web-Seite verwendet werden.
Vertikale Anordnung des Applets in einer Textzeile. Hier kann einer der Werte
left, right, top, texttop, middle, absmiddle, baseline, bottom, absbottom
angegeben werden.
Rand über und unter dem Applet
Rand links oder rechts vom Applet
Neben den Parametern des „Applet-Tag“ können auch Parameter an das Applet
selbst übergeben werden. Jeder Parameter kann durch ein <PARAM>-Tag über zwei
Attribute für Name (name) und Wert (value) festgelegt werden. Das <PARAM>-Tag
steht zwischen einem öffnenden und schließenden <APPLET>-Tag.
Die Parameter werden beim Laden an das Applet weitergereicht. Innerhalb des
Applets können sie mit der Methode public String getParameter(String
name) abgefragt werden.
213
Pfadname, in dem sich die Klassen befinden
233
Programmieren in Java
3.3.2 Die interne Arbeitsweise eines Applets
Ein Java-Applet besitzt im Gegensatz zu einer Java-Anwendung keine main()Methode, die beim Laden gestartet wird und das Programm solange am Leben hält,
bis es der Benutzer beendet. In einem Applet sorgen vier Methoden dafür, daß sich
das Applet in seiner Umgebung korrekt verhält.
Panel
Applet
<< Konstruktor >>
public Applet()
<< Methoden >>
public void destroy()
public AppletContext getAppletContext()
public String getAppletInfo()
public AudioClip getAudioClip(URL url)
public AudioClip getAudioClip(URL url,String name)
public URL getCodeBase()
public URL getDocumentBase()
public Image getImage(URL url)
public Image getImage(URL url, String name)
public Locale getLocale()
public String getParameter(String name)
public String[][] getParameterInfo()
public void init()
public boolean isActive()
public void play(URL url)
public void play(URL url, String name)
public void resize(Dimension d)
public void resize(int width, int height)
public void setStub(AppletStub stub)
public void showStatus(String msg)
public void start()
public void stop()
…..
Abb. Die Klasse Applet
234
Programmieren in Java
1. Das Erstellen eines Applets
Zum Erstellen eines Applet muß immer eine „Subklasse“ der Klasse Applet214
erzeugt werden. Java setzt voraus, daß eine Applet-Subklasse public deklariert
wurde. Erkennt Java ein Applet auf einer Web-Seite, dann wird die AppletAusgangsklasse und die Hilfsklasse, die diese erste Klasse evtl. benutzt, über das
Netz geladen. Java erstellt eine Instanz dieser Klasse, alle systembezogenenen
Methoden werden an diese Instanz geschickt. Mehrere Applets auf der gleichen oder
auf unterschiedlichen Seiten verwenden andere Instanzen, so daß sich jedes Applet
auf dem gleichen System evtl. anders verhält.
2. Applet-Methoden
Applets können zahlreiche, unterschiedliche Aktivitäten umfassen, die verschiedenen
wichtigen Ereignissen im Lebenszyklus eines Applet entsprechen, z.B. Initialisieren,
Zeichnen, Mausereignisse. Jeder Aktivität ist eine entsprechende Methode
zugeordnet, d.h.: Falls eine Ereignis stattfindet, ruft der Browser (oder ein Java
ähnliches Werkzeug) diese spezifische Methode auf.
Zum Reagieren auf solche Ereignisse sind bestimmte Verhaltensweisen vorzusehen.
Das geschieht durch Überschreiben der jeweiligen Methode in der Applet-Subklasse.
Unterschiedliche Applet-Verhalten bedeutet: Jeweils andere Methoden müssen
überschieben werden. Die folgenden Methoden bestimmen den Lebenszyklus eines
Applet:
Rückkehr zur HTML-Seite
init()
start()
stop()
destroy()
Verlassen der HTML-Seite
Abb.: Methoden im Lebenszklus eines Applet
Die Methode init() wird nach dem Laden des Applet ausgeführt. Sie dient zur
Initialisierung.
Die Methode start() wird automatisch nach Aufruf der Methode init()
aufgerufen bzw. dann, wenn das Applet in den Zustand „aktiv“ versetzt wird. Applets
können in zwei Zuständen sein: aktiv und inaktiv. Nach dem Laden eines Applets ist
dieses zunächst inaktiv. Das Applet wechselt in den Zustand aktiv, wenn es erstmalig
auf dem Bildschirm erscheint. Von dort aus wechselt es seinen Zustand zwischen
aktiv und inaktiv. Wodurch dieser Zustandswechsel genau ausgelöst wird, ist
abhängig vom Kontext des Applel, d.h. in der Regel vom verwendetet Web-Browser.
Die Methode stop() wird aufgerufen, wenn die HTML-Seite, in der das Applet
eingebunden ist, verlassen wird bzw. das Applet in den Zustand inaktiv versetzt wird.
214 Alle Applets müssen java.applet.Applet in die Datei mit der Definition der Applet-Klasse importieren.
java.applet.* vollzieht das Einbunden von java.applet.Applet automatisch. Fast alle Applets (die mit grafischen
Schnittstellen) benötigen auch java.awt.*
235
Programmieren in Java
Die Methode destroy() zerstört das Applet, nachdem es gestoppt wurde und der
Kontext des Applet sich für eine Zerstörung entscheidet. Die Methode destroy()
sorgt dafür, daß alle vom Applet belegte Ressourcen wieder freigegeben werden.
Vorhandene, vom Applet erzeugte Threads werden ebenfalls zerstört.
Die paint()-Methode wird in Java immer aufgerufen, wenn ein Applet gezeichnet
werden muß, z.B. bein erstmaligen Zeichnen des Applet, beim Verschieben des
Applet-Fenster, beim Überlagern des Applet-Fenster durch ein anderes Fenster. Die
paint()-Methode hat folgende Gestalt:
public void paint(Graphics g)
{
....
}
paint() besitzt ein Argument: eine Instanz der Klasse Graphics. Dieses Objekt
wird vom Browser erstellt und an paint() abgegeben. Es muß sichergestellt sein,
daß die Graphics-Klasse215 in den Code der Applet-Subklasse importiert216
wird.
Bsp.: „Aller Anfang ist schwer!. Dieser Spruch soll mit Hilfe eines Applet gezeigt
werden.. Die zugehörige Quellcodedatei „AllerAnfangApplet.java“ umfaßt217:
import java.applet.*;
import java.awt.*;
public class AllerAnfangApplet extends Applet
{
Font f;
String spruch;
//
public void init()
{
f = new Font("Helvetica",Font.BOLD,24);
this.spruch = "Aller Anfang ist schwer!";
}
//
public void paint(Graphics g)
{
// Oval mit Farbe yellow
g.setColor(Color.yellow);
g.fillOval(10,10,330,100);
// Roter Rahmen; da Java keine Linienbreite kennt,
// wird die Linienbreite durrch 4 Ovale, (die sich
// um Pixelbreite unterscheiden,) simuliert
g.setColor(Color.red);
g.drawOval(10,10,330,100);
g.drawOval( 9, 9,332,102);
g.drawOval( 8, 8,334,104);
g.drawOval( 7, 7,336,106);
g.setColor(Color.black);
g.setFont(f);
g.drawString(this.spruch,40,70);
}
}
Die zugehörige HTML-Datei AllerAnfangApplet.html umfaßt:
215
Teil des Pakets java.awt
Normalerweise geschieht dies über: import java.awt.Graphics
217 vgl. pr32101
216
236
Programmieren in Java
<HTML>
<HEAD>
<TITEL>Hallo!</TITLE>
<BODY>
<CENTER>
<APPLET CODE="AllerAnfangApplet.class" WIDTH=350 HEIGHT=125>
</APPLET>
</CENTER>
</BODY>
</HEAD>
</HTML>
In einem Browser führt das zu der folgenden Darstellung:
Java-Applets zeichnen sich durch Überschreiben der „paint“-Methode selbst. Wie
wird die „paint“-Methode aufgerufen?
Es gibt drei verschiedene Methoden zum Neuzeichnen eines Applet:
public void paint(Graphics g)
Sie zeichnet tatsächlich die Grafik des Applets in den Zeichenbereich. Sie wird immer aufgerufen,
wenn ein Applet neu gezeichnet218 werden muß. Das in der „paint“-Methode abgebene GraphicsObjekt enthält den Grafikstatus, d.h. die aktuellen Merkmale der Zeichnungsoberfläche.
public void repaint()
Sie kann jederzeit aufgerufen werden, wann auch immer das Applet neu gezeichnet werden muß. Sie
ist der Auslöser, die „paint“-Methode sobald wie möglich aufzurufen und das Applet neu zu
zeichnen. Sollten die repaint()-Anweisungen schneller ablaufen, als Java diese verarbeiten kann,
werden evtl. einige übersprungen. In vielen Fällen ist die Verzögerung zwischen dem Aufruf von
repaint() und der eigentlichen Aktualisierung des Fensters vernachlässigbar.
public void update(Graphics g)
Sie wird von repaint() aufgerufen. Die „update“-Methode löscht den vollständigen
Zeichenbereich219 und ruft anschließend die „paint“-Methode auf, die dann das Applet vollständig
neu zeichnet. Der Aufruf der „paint“-Methode erfolgt also nicht direkt über die „repaint“-Methode,
sondern indirekt über die „update“-Methode.
Applets werden aus Sicherheitsgründen gewissen Einschränkungen unterworfen:
218
Dies ist immer beim ersten Aufruf des Applets der Fall, aber auch jedesmal dann, wenn das Applet-Fenster
verschoben oder zwischenzeitlich von einem anderen Fenster überlagert wurde.
219 Das ruft oft den unangenehmen Flimmereffekt bei schnellen Bildsequenzen hervor.
237
Programmieren in Java
-
-
Applets können das Dateisystem des Bernutzers nicht lesen und nicht beschreiben, abgesehen
von bestimmten Verzeichnissen (die vom Benutzer durch eine Zugriffskontrolliste, die
standardäßig leer ist, bestimmt werden). Einige Browser lassen keinerlei Schreib- und Leseaktion
des Applets auf dem Client zu.
Applets können auf dem Client keinerlei Programme ausführen.
Applets können normalerweise nur mit dem System kommunizieren, auf denen sie gespeichert
sind.
Applets können keine keine nativen Programme der lokalten Plattform laden, auch keine
gemeinsame Bibliotheken (wie DLL’s).
3. Methoden zur Ereignisbehandlung in Applets
Ein Applet kann auch auf Ereignisse wie Mausbewegungen reagieren. Für soche
Ereignisse (z.B. Drücken der Maustaste) stellt Java Ereignisbehandlungs-Methoden
des JDK 1.0 bzw. JDK 1.1 zur Verfügung.
Bsp.220: Ein Applet zum Zeichnen von Punkten an den Stellen, an denen eine
Maustaste gedrückt wurde.
// Import der Pakete
import java.awt.*;
import java.awt.event.*;
// Top-Level Klassen-Deklaration des Applets
public class MausDownPunktApplet extends java.applet.Applet
{
// Variablen-Deklarationen
private int mausX, mausY;
// private boolean mausKlick = false;
// Methoden, die ueberschrieben werden
public void init()
{
setBackground(Color.yellow);
addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
mausX = e.getX(); mausY = e.getY();
repaint();
}
});
}
/*
public boolean mouseDown(Event e, int x, int y)
{
mausX = x; mausY = y;
mausKlick = true;
repaint();
return true;
}
*/
// Ausgabemethode
public void paint(Graphics g)
{
g.setColor(Color.blue);
// if (mausKlick)
// {
g.fillOval(mausX,mausY,20,20);
// mausKlick = false;
// }
220
pr32305
238
Programmieren in Java
}
}
Nach jedem Mausklick wird ein blauer Punkt in die Zeichenfläche gebracht.
Das Bild wird neu gezeichnet. Die repaint()-Methode ruft vor der
Ausführung der paint()-Methode die update()-Methode auf, die
anschließend paint() aufruft. „update()“ leert in Originalform den
Anzeigebereich des Applets. Wird update() überschrieben, z.B. durch
public void update(Graphics g)
{
paint(g);
}
entfällt das Leeren des Anzeigebereichs.
// Import der Pakete
import java.awt.*;
import java.awt.event.*;
// Top-Level Klassen-Deklaration des Applets
public class MausDownPunkteApplet extends java.applet.Applet
{
// Variablen-Deklarationen
private int mausX, mausY;
// private boolean mausKlick = false;
// Methoden, die ueberschrieben werden
public void init()
{
setBackground(Color.yellow);
addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
mausX = e.getX(); mausY = e.getY();
repaint();
}
});
}
/*
public boolean mouseDown(Event e, int x, int y)
{
mausX = x; mausY = y;
mausKlick = true;
repaint();
return true;
}
*/
// Ausgabemethode
public void paint(Graphics g)
{
g.setColor(Color.blue);
// if (mausKlick)
// {
g.fillOval(mausX,mausY,20,20);
// mausKlick = false;
// }
}
public void update(Graphics g)
{
paint(g);
}
}
239
Programmieren in Java
Eine überschriebene update()-Methode kann den Flimmereffekt erheblich senken.
3.3.3 „Multithreading“-fähige Applets
Mit Threads können in Java Applets so erstellt werden, daß alle oder auch einzelnen
Codeteile in ihrem eigenen Thread laufen, ohne andere Teile des Systems zu
beeinflussen. Ein Applet kann im wesentlichen über vier Schritte
Multithreading-fähig gemacht werden:
1. Erweitern der Unterschrift des Applets um implements Runnable
2. Hinzufügen einer Instanzvariablen, die den Thread des Applet enthält
3. Reduktion der start()-Methode, so daß sie außer dem Start des Threads keine weiteren Threads
enthält
4. Hinzufügen der run()-Methode, die den eigentlichen Code enthält, den das Applet ausführen soll.
Bsp.: Ein Applet zur Anzeige von Datum und Uhrzeit, jede Sekunde wird
aktualisiert221. Nach den bisher vorliegenden Erkenntnissen müßte das
zugehörige Applet folgende Gestalt haben:
import java.awt.Graphics;
import java.awt.Font;
import java.util.Date;
//
// Top Level Deklaration des Applets
//
public class DigitalUhr extends java.applet.Applet
{
// Variablen-Deklaration
Font einFont = new Font("TimesRoman",Font.BOLD,24);
Date datum;
// Eigene Methoden
// Methoden, die ueberschrieben werden
public void start()
{
// Ausfuehrung des Applet
while (true)
{
datum = new Date();
repaint(); // Aufruf der repaint()-Methode
try {Thread.sleep(1000); } // Pause von 1000 Millisekunden
catch(InterruptedException e) {}
}
}
// Optional, aber sehr wahrscheinlich – die Ausgabemethode
public void paint(Graphics g)
{
g.setFont(einFont); // Setzen des aktuellen Font
g.drawString(datum.toString(),10,50); // Ausgabe Datum
// Da paint() wiederholt mit jeweils dem aktuellen Wert von
// „datum“ aufgerufen wird, wird die Zeichenkette jede Sekunde
//zur Ausgabe des neuen Datums aufgerufen
}
}
In der start()-Methode nimmt die while-Schleife alle Systemressourcen für
sich in Anspruch (einschl. der Anzeige am Bildschirm). Deshalb funktioniert die
221
vgl. pr42001
240
Programmieren in Java
digitale Uhr nicht. Außerdem kann das Applet nicht gestoppt werden, da die
stop()-Methode nicht aufgerufen werden kann. Die Lösung des Problems liegt
im erneuten Schreiben des Applets mit Threads. Das Applet muß dazu mit den
vorgegebenen vier Arbeitsschritten erweitert werden.
import
import
import
//
// Top
//
public
java.awt.Graphics;
java.awt.Font;
java.util.Date;
Level Deklaration des Applets
class DigitalThreadUhr extends java.applet.Applet
implements Runnable
{
// Variablen-Deklaration
Font einFont = new Font("TimesRoman",Font.BOLD,24);
Date datum;
Thread faden;
// Eigene Methoden
// Methoden, die ueberschrieben werden
public void start()
{
if (faden == null)
{
faden = new Thread(this);
faden.start();
}
}
public void stop()
{
if (faden != null)
{
faden.stop();
faden = null;
}
}
public void run()
{
// Ausfuehrung des Applet, hier findet die Animation statt
while (true)
{
datum = new Date();
repaint(); // Aufruf der repaint()-Methode
try {Thread.sleep(1000); } // Pause von 1000 Millisekunden
catch(InterruptedException e) {}
}
}
// Optional, aber sehr wahrscheinlich – die Ausgabemethode
public void paint(Graphics g)
{
g.setFont(einFont); // Setzen des aktuellen Font
g.drawString(datum.toString(),10,50); // Ausgabe Datum
// Da paint() wiederholt mit jeweils dem aktuellen Wert von
// "datum" aufgerufen wird, wird die Zeichenkette jede Sekunde
// zur Ausgabe des neuen Datums aufgerufen
}
}
Das folgende Gerüst umfaßt ein Muster für „multithreading“-fähige Applets:
// Name der Klasse:
// Beschreibung:
241
Programmieren in Java
//
//
//
//
Import
import
import
import
der Pakete
java.lang.*;
java.applet.*;
java.awt.*;
// Top-Level-Klassen-Deklaration bzw. Definition des Applets
public Klassenname extends java.applet.Applet
{
// Variablen-Deklarationen bzw. Definitionen
// ...
// Eigene Methoden
// ...
// Methoden, die ueberschrieben werden
//
public void init()
{
// ...
}
public void start()
{
// ...
}
public void stop()
{
// ...
}
public void destroy()
{
// ...
}
// Optional: die Ausgabemethode
public void paint(Graphics g)
{
// ..
}
// Bei Multithreading: Verwendung der run-Methode
public void run()
{
// ...
}
}
242
Programmieren in Java
3.3.4 Animation in Applets
Animationsschritte
Eine Animation umfaßt in Java zwei Schritte:
1.
2.
Aufbau und Ausgabe eines Animationsrahmens (-fenster).
Entsprechende häufige Wiederholung der Zeichnung, um den Eindruck von Bewegung zu
vermitteln (Abspielen einer Animation).
Aufbau eines Animationsrahmens
Dazu gehört alles das, was die Animation vorbereitet, z.B.:
-
Ermitteln der Größe des Ausgabebereichs
Positionieren der Animation
Erstellen oder Laden der einzelnen Animationsbilder
Aufbau von einzelnen Animationssequenzen
Abspielen einer Animation
Die paint()-Methode wird von Java aufgerufen, wenn ein Applet gezeichnet
werden muß. Java kann aber auch aufgefordert werden, ein Bild zu einem
bestimmten Zeitpunkt nachzuzeichnen. Tut man das wiederholt und schnell genug
mit der repaint()-Methode, dann entsteht eine Animation. repaint() ist eine
Anfrage an Java, das Applet so schnell wie möglich zu zeichnen.
import java.awt.*;
import java.util.*;
public class PendelAppl1 extends java.applet.Applet implements Runnable
{
// Instanzvariable
// Position vom Zentrum des schwingenden Pendels
int x, y;
//
double thetaMax = (double) 0.35;
double thetaMin = (double) –0.35;
// Die initiale Position vom Pendel
double theta = (double) 0.;
//
double wechsel = (double) 0.01;
//
int xStart = 150, yStart = 20;
// Radius des Pendels
double r = (double) 200;
// Durchmesser des Balls
int d = 20;
Thread faden;
// Methoden
public void init()
{
setBackground(Color.yellow);
}
public void start()
{
243
Programmieren in Java
if (faden == null)
{
faden = new Thread(this);
faden.start();
}
}
public void stop()
{
if (faden != null)
{
faden.stop();
faden = null;
}
}
public void run()
{
while (true)
{
x = xStart + (int)(r * Math.sin(theta));
y = yStart + (int)(r * Math.cos(theta));
if ((theta >= thetaMax) | (theta <= thetaMin))
wechsel = -wechsel;
theta += wechsel;
repaint();
try { Thread.sleep(10); }
catch(InterruptedException e) {}
}
}
public void paint(Graphics g)
{
g.setColor(Color.blue);
g.drawLine(xStart,yStart,x,y);
g.setColor(Color.red);
g.fillOval(x-d/2,y-d/2,d,d);
}
}
Reduktion von Flimmereffekten in Animationen
Die Methode update() ist die Ursache für das Flimmer-Problem. Da das AppletFenster zwischen den Einzelbildern gelöscht wird, springen die Bereiche des AppletFenster, die sich ändern, kurz zwischen dem Zustand Löschen und Neuzeichnen hin
und her, d.h. sie flimmern. Zwei Verfahrensweisen können das Flimmern von JavaApplets einschränken:
-
Überschreiben der update-Methode so, daß sie entweder den Bildschirm nicht löscht oder nur
Teile löscht, die geändert wurden.
Überschreiben der Methoden update() und paint(), Verwenden doppelter Pufferung.
1. Überschreiben der update()-Methode222
update() löscht den Bildschirm durch Füllen mit der aktuellen Hintergrundfarbe,
setzt die aktuellen Farbe auf die Vordergrundfarbe und ruft anschließend paint()
auf. Das Überschreiben von update() muß sicherstellen, daß so etwas Ähnliches
geschieht.
import java.awt.*;
public class PendelAppl2 extends java.applet.Applet implements Runnable
{
222
vgl. pr32212
244
Programmieren in Java
// Instanzvariable
// Position vom Zentrum des schwingenden Pendels
int x, y;
//
double thetaMax = (double) 0.35;
double thetaMin = (double) –0.35;
// Die initiale Position vom Pendel
double theta = (double) 0.;
//
double wechsel = (double) 0.01;
//
int xStart = 150, yStart = 20;
// Radius des Pendels
double r = (double) 200;
// Durchmesser des Balls
int d = 20;
Thread faden;
int xAlt, yAlt;
// Methoden
public void init()
{
setBackground(Color.white);
}
public void start()
{
if (faden == null)
{
faden = new Thread(this);
faden.start();
}
}
public void stop()
{
if (faden != null)
{
faden.stop();
faden = null;
}
}
public void run()
{
while (true)
{
x = xStart + (int)(r * Math.sin(theta));
y = yStart + (int)(r * Math.cos(theta));
if ((theta >= thetaMax) | (theta <= thetaMin))
wechsel = -wechsel;
theta += wechsel;
repaint();
try { Thread.sleep(10); }
catch(InterruptedException e) {}
}
}
public void update(Graphics g)
{
g.setColor(Color.Yellow);
g.drawLine(xStart,yStart,xAlt,yAlt);
g.fillOval(xAlt-d/2,yAlt-d/2,d,d);
g.setColor(Color.blue);
g.drawLine(xStart,yStart,x,y);
g.setColor(Color.red);
g.fillOval(x-d/2,y-d/2,d,d);
paint(g);
}
245
Programmieren in Java
public void paint(Graphics g)
{
xAlt = x;
yAlt = y;
}
}
2. Double Buffering
Mit „double buffering“ wird eine zweite Oberfläche geschaffen, in der alles
vorgezeichnet und dann auf einmal in die Zeichnungsoberfläche des Applet
ausgegeben wird.
Bsp.223:
import java.awt.*;
public class Pendel extends java.applet.Applet implements Runnable
{
// Instanzvariable
// Position vom Zentrum des schwingenden Pendels
int x, y;
//
double thetaMax = (double) 0.35;
double thetaMin = (double) –0.35;
// Die initiale Position vom Pendel
double theta = (double) 0.;
//
double wechsel = (double) 0.01;
//
int xStart = 150, yStart = 20;
// Radius des Pendels
double r = (double) 200;
// Durchmesser des Balls
int d = 20;
Thread faden;
int xAlt, yAlt;
Image backgroundImage;
Graphics backgroundGraphics;
// Methoden
public void init()
{
setBackground(Color.white);
backgroundImage = createImage(this.size().width,
this.size().height);
backgroundGraphics = backgroundImage.getGraphics();
}
public void start()
{
if (faden == null)
{
faden = new Thread(this);
faden.start();
}
}
public void stop()
{
if (faden != null)
{
faden.stop();
faden = null;
}
}
public void run()
{
223
pr32213, Pendel.java
246
Programmieren in Java
while (true)
{
x = xStart + (int)(r * Math.sin(theta));
y = yStart + (int)(r * Math.cos(theta));
if ((theta >= thetaMax) | (theta <= thetaMin))
wechsel = -wechsel;
theta += wechsel;
repaint();
try { Thread.sleep(10); }
catch(InterruptedException e) {}
}
}
public void update(Graphics g)
{
paint(g);
}
public void paint(Graphics g)
{
backgroundGraphics.setColor(Color.white);
backgroundGraphics.drawLine(xStart,yStart,xAlt,yAlt);
backgroundGraphics.fillOval(xAlt-d/2,yAlt-d/2,d,d);
backgroundGraphics.setColor(Color.blue);
backgroundGraphics.drawLine(xStart,yStart,x,y);
backgroundGraphics.setColor(Color.red);
backgroundGraphics.fillOval(x-d/2,y-d/2,d,d);
g.drawImage(backgroundImage,0,0,this);
xAlt = x;
yAlt = y;
}
}
247
Programmieren in Java
3.3.5 Das Laden und Anzeigen von Bildern
Den Umgang mit Bildern ermöglicht die Klasse Image des Pakets java.awt. In
einem Applet können Methoden der Klassen Applet und Graphics zum Laden und
Anzeigen von Bildern herangezogen werden. Bilder werden als seperate Dateien
außerhalb der .class-Dateien von Java gespeichert. Falls die Image-Klasse
verwendet wird, muß das Bild im Format .GIF oder .JPG vorliegen.
Laden von Bildern. Es erfolgt mit der Methode getImage()aus der Applet-Klasse,
die mit einem oder zwei Argumenten aufgerufen werden kann:
-
Aufruf von getImage mit einem Argument (ein Objekt vom Typ URL224)
Aufruf mit zwei Argumenten (Basis URL des Bilds (URL-Objekt)) und ein String, der den relativen
Pfad oder den Dateinamen des aktuellen Bilds angibt.
Die Klasse Applet besitzt zwei Methoden zum Erzeugen einer Basis-URL ohne
Angaben fester Adressen im Programm:
-
die Methode getDocumentBase() gibt ein URL-Objekt zurück, das den Ordner (das
Verzeichnis) repräsentiert, die die Webseite mit dem Applet enthält.
die Methode getCodeBase() gibt ein Verzeichnis (Ordner) zurück, das das Verzeichnis
repräsentiert, in dem sich die .class-Datei der Hauptklasse des Applet befindet.
Ausgabe von Bildern. Mit der Methode drawImage() der Graphics-Klasse kann ein
Bild, das in ein Image-Objekt geladen wurde, angezeigt werden. drawImage() hat 4
Argumente:
-
das Image-Objekt, das angezeigt werden soll
die x- und y-Koordinate
das Schlüsselwort this
Mit paint() kann das Bild zur Anzeige gebracht werden:
public paint(Graphigs g)
{
g.drawImage(imageObjekt, xKoord, yKoord, this);
}
Bsp.225:
import java.awt.*;
import java.applet.*;
public class ZeichneBild extends Applet
{
private Image bild;
public void init()
{
bild = getImage(getDocumentBase(),"B04240900.jpg");
resize(250, 200);
}
public void paint(Graphics g)
{
int xPos = 10;
g.drawImage(bild,xPos,10,this);
}
}
224
Adressen im World Wide Web werden durch URL-Objekte repräsentiert. Die Klasse URL (Uniform
Resource Locator) ist Teil des Pakets java.net
225 vgl. pr33501
248
Programmieren in Java
3.3.6 Die Ausgabe von Sound
Das JDK bietet Möglichkeiten zur Ausgabe von Sound226 an. Die Ausgabe von
Sound kann über zwei Methoden der Klasse Applet erfolgen:
public void play(URL url)
public void play(URL url, String name)
hier kann entweder die URL einer Sound-Datei oder die Kombination von
Verzeichnis-URL und Dateinamen angegeben werden. Die Übergabe der URLs
geschieht über die Applet-Methoden:
public URL getCodeBase()
public URL getDocumentbase()
Die Methoden liefern eine URL des Verzeichnisses, aus dem das Applet gestartet wurde bzw. in dem
die aktuelle HTML-Seite liegt. Der nachteil dieser Vorgehensweise ist, daß die Sound-datei bei jedem
Aufruf neu geladen werden muß.
public getAudioClip(URL url, String name)
Hier wird ein Objekt der Klasse AudioClip beschafft, das dann abgespielt werden
kann. AudioClip stellt drei Methoden zur Verfügung:
public void play()
startet die zuvor geladene Sound-Datei und spielt sie genau einmal ab.
public void loop()
startet die zuvor geladene Sound-Datei und spielt den Sound in einer Endlosschleife
immer wieder ab.
public void stop()
Darüber kann die zuvor mit loop() iniziierte Schleife beendet werden.
Bsp.227:
import java.net.*;
import java.applet.*;
public class PR33602
{
public static void main(String args[])
{
if (args.length >= 1)
{
try {
URL url = new URL(args[0]);
AudioClip clip = Applet.newAudioClip(url);
clip.play();
try {
Thread.sleep(10000);
}
catch (InterruptedException e)
{}
}
226
Das JDK 1.2 ermöglicht die Soundausgabe in Applets und Applikationen. Frühere Versionen gestatten
Soundausgabe nur für Applets. Die Ausgabe war auf Sound beschränkt, die im AU-Format (stammt aus der
Sun-Welt und legt ein Sample im Format 8 Bit Mono, Sampling-Rate 8 kHz, µ -lawKompression ab)
vorliegen mußte. Seit dem JDK 1.2 werden auch die Sample-Formate WAV und AIFF sowie die Midi-Formate
Typ 0 und Typ 1 und RMF unterstützt. Zudem gibt es einige Shareware- oder Freeware-Tools, die zwischen
verschieden Formaten konvertieren können (z.B. CoolEdit oder Gold-Wave).
227 Vgl. pr33602, Aufruf z.B.: java PR33602 file:///c:/winnt\media\ringin.wav
249
Programmieren in Java
}
}
}
catch (MalformedURLException e)
{
System.out.println(e.toString());
}
3.3.7 Die Klasse JApplet
Diese Klasse befindet sich im Paket javax.swing, gehört aber logisch zu den
Applets. Sie erweitert die Applets um typische Swing-Eigenschaften.
java.awt.Panel
java.applet.Applet
javax.swing.JApplet
<< Konstruktor >>
public JApplet()
<< Methoden >>
public void setJMenuBar(JMenuBar menuBar)
public JMenuBar getJMenuBar()
public void setLayout(LayoutManager manager)
public Container getContentPane()
public void setContentPane(Container contentPane)
....
Abb. Die Klasse JApplet
250
Programmieren in Java
3.3.8 Das Interface AppletContext
Die Referenz vom Typ des Interface AppletContext dient vorwiegend zur
Kommunikation zwischen Applet und dem Webbrowser bzw. anderen Applets.
<< interface >>
AppletContext
public Applet getApplet(String name)
public Enumeration getApplets()
public AudioClip getAudioClip(URL url)
public Image getImage(URL url)
public void showDocument(URL url)
public void showDocument(URL url, String target)
public void showStatus(String status)
Abb.: Das Interface AppletContext
Mit Hilfe von z.B. getAppletContext().getApplets() kann auf andere Applets
Bezug genommen werden. Die Methode showDocument() lädt eine Webseite in den
Browser, auf dem das Applet läuft.
251
Programmieren in Java
3.4 Grafische Benutzeroberfläche mit Observable-Observer
Das Zusammenwirken und die Kommunikation zwischen Awendung und grafischer
Benutzeroberfläche sollte folgendermaßen gestaltet sein:
-
Anwendung und grafische Benutzeroberfläche sollen möglichst unabhängig voneinander sein, in
jedem Fall aber sauber voneinander getrennt sein (Anwendungsklasse, GUI-Klasse).
Jedes GUI-Objekt muß seinen Anwendungsfall kennen, um desssen Methoden aufrufen zu
können
Eigentlich müßte umgekehrt jedes Anwendungsobjekt „sein GUI-Objekt“ kennen, z.B. zur Anzeige
der Anfangswerte
Das Hauptprogramm „degeneriert“ zur Erzeugung eines Anwendungs- und GUI-Objekts.
Die Klasse Observable
In Java muß das Interesse an einem Objekt durch eine eigene Klasse ausgedrückt
werden. Die eigene Klasse muß von der Klasse Observable abgeleitet sein. Die
Observable-Klasse erlaubt es einem Objekt, andere Objekte zu informieren, wenn es
eine Änderung erfährt.
public class java.util.Observable
{
Observable();
public void addObserver(Observer o);
public void deleteObserver(Observer o);
public void deleteObservers();
public int countObservers);
protected void setChanged();
portected void clearChanged();
public boolean hasChanged();
public void notifyObservers();
public void notifyObservers(Object arg);
}
Methoden: Die wichtigsten Methoden beim Erzeugen einer Subklasse von
Observable sind „setChanged“ und „notifyObservers“. Die „setChanged“Methode markiert, daß Observable verändert wurde. Beim Aufruf von
„notifyObservers“ werden die Observer benachrichtigt.
protected void setChanged()
setzt ein internes Flag für die Modifikation (wird von „notifyObservers“ verwendet). Es wird
automatisch gelöscht, wenn „notifyObservers“ aufgerufen wird, kann aber auch manuell mit der
„clearChanged“-Methode gelöscht werden.
protected void clearChanged()
public void notifyObservers()
prüft, ob das changed-Flag gesetzt wurde. Ist dies nicht der Fall, kann keine Mitteilung über eine
Änderung versendet werden.
public void notifyObservers(Object arg)
Das Argument kann zur Übergabe zusätzlicher Information über die Modifikation dienen. Ohne
Parameter entspricht der Aufruf einem Aufruf mit dem Argument Null.
public boolean hasChanged()
stellt fest, ob ein Objekt von Interesse modifiziert wurde.
public void addObserver(Observer obs)
252
Programmieren in Java
Mit dieser Methode können Observer ihr Interesse an einem beliebigen Observable bekunden.
public void deleteObserver (Observer obs)
Mit dieser Methode kann ein Observer die Überwachung der entsprechenden Observables beenden
public void deleteObservers()
löscht die Liste aller Observer
public int countObservers()
liefert die Anzahl der für in Observable registrierten Observer
Das Interface Observer
Jede Klasse, die Mitteilungen über Modifikationen eines Observable bekommen
möchte, muß das Observer-Interface implementieren. Dieses Interface besteht aus
einer einzigen Methode:
public abstract void update(Observable obs, Object arg)
Sie wird bei einer Objektmodifikation aufgerufen. „obs“ ist das Observable, das
soeben geändert wurde. Wurde „notifyObservers“ ohne Argument aufgerufen,
dann ist arg Null.
Observable-Observer: Kopplung von Anwendung und GUI
Die Klasse Observable228 erlaubt einem Objekt, Informationen an andere Objekte
zu geben, wenn es eine Änderung erfährt. Beim Erstellen von Benutzeroberflächen
kann es verschiedene Möglichkeiten zur Änderung eines Datenobjekts geben. Eine
solche Änderung kann wiederum die Aktualisierung verschiedener Teile einer
Anzeige zur Folge haben.
Bsp.:229
import java.awt.*;
import java.awt.event.*;
import java.util.*;
class BoxObservable extends Observable
{
public void notifyObservers(Object b)
{
setChanged();
super.notifyObservers(b);
}
}
public class BoxObserver extends Frame
{
Observable notifier = new BoxObservable();
public BoxObserver(int grid)
{
setTitle("Demonstration Observer");
setLayout(new GridLayout(grid,grid));
for (int x = 0; x < grid; x++)
for (int y = 0; y < grid; y++)
add(new OCBox(x, y, notifier));
}
public static void main(String args[])
228
Das Konzept für diese Klasse ist eine Leihgabe von Smalltalk und unter dem namen Model-View-Controller
(MVC) bekannt.
229 pr34100
253
Programmieren in Java
{
int grid = 8;
if (args.length > 0)
grid = Integer.parseInt(args[0]);
Frame f = new BoxObserver(grid);
f.setSize(500,400); f.setVisible(true);
f.addWindowListener(
new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
}
}
class OCBox extends Canvas
implements Observer
{
Observable notifier;
int x, y;
Color fFarbe = neueFarbe();
static final Color[] farben = {
Color.black, Color.blue, Color.cyan,
Color.darkGray, Color.gray, Color.green,
Color.lightGray, Color.magenta, Color.orange,
Color.pink, Color.red, Color.white, Color.yellow
};
OCBox(int x, int y, Observable notifier)
{
this.x = x; this.y = y;
notifier.addObserver(this); this.notifier = notifier;
addMouseListener(new ML());
}
static final Color neueFarbe()
{
return farben[(int) (Math.random() * farben.length)];
}
public void paint(Graphics g)
{
g.setColor(fFarbe); Dimension d = getSize();
g.fillRect(0, 0, d.width, d.height);
}
class ML extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{ notifier.notifyObservers(OCBox.this); }
}
public void update(Observable o, Object arg)
{
OCBox clicked = (OCBox) arg;
if (nextTo(clicked))
{ fFarbe = clicked.fFarbe; repaint(); }
}
private final boolean nextTo(OCBox b)
{
return Math.abs(x - b.x) <= 1 && Math.abs(y - b.y) <= 1;
}
}
254
Programmieren in Java
3.5 Swing
Die grundlegende GUI-Programmierung in Java wird durch die Klassenbibliothek
AWT (Abstrach Window Toolkit) ermöglicht. Dabei werden in Wirklichkeit die
graphischen Elemente nicht durch die AWT komplett realisiert, sondern die
Erzeugung wird im wesentlichen an die jeweilige Plattform (Windows, Solaris, Mac)
delegiert. Das im Hintergrund erzeugte und arbeitende Element wird dann als „Peer“Komponente bezeichnet. Die Klassenbibliothek kapselt den Zugriff auf diese
Elemente und erleichtert damit auch teilweise die Handhabung einzelner Objekte.
1996 erstellte Netscape eine GUI-Bibliothek „Internet Foundation Classes“ mit
komplett anderem Einsatz. Die Peer-Funktionalität wird auf ein Minimum reduziert:
Ein Fenster zu erstellen, darauf zu zeichnen und dieses wieder zu schliessen.
Dadurch wird eine gemeinsame Basis für alle Elemente der Benutzeroberfläche
geschaffen, auf der aufbauend alle GUI-Elemente wie Schaltflächen, Listen, Menüs
usw. gezeichnet werden konnten. Die komplette Funktionalität muß dann für alle
Komponenten „nachgebaut“ werden, mit dem Vorteil einer einheitlchen,
plattformübergreifenden Lösung. Diese Lösung wurde gemeinsam von Netscape und
Sun ausgebaut und verbessert, wodurch eine nicht peerbasierte GUI-Bibliothek als
Teil der Java Foundation Classes (JFC) für die Benutzeroberfläche mit dem Namen
Swing entstanden ist.
Swing230 ist eine Erweiterung des Abstract Windowing Toolkit, vollständig in
Java 2 integriert und bietet eine wesentlich verbesserte Funktionalität. Alle Elemente
von Swing sind Bestandteile des Pakets javax.swing, die über import
javax.swing.* in Anwendungen einbezogen werden kann.
Die grafische Benutzeroberfläche für Java Programme "Swing" umfasst die
folgenden drei wesentlichen Konzepte:
- Komponenten (Schnittstellenelemente wie z.B. Buttons, Menüs, ...). Swing-Komponenten werden
auf gleiche Weise verwendet wie die Komponenten des AWT: Erzeugen einer Komponente über den
Konstruktor, Aufruf der Methoden der Komponente.
- Container (Behälter, die mit Komponenten gefüllt werden, z.B. Fenster, Dialoge, Panels,...).
- Events (Nachrichten über Benutzeraktionen mit Maus, Tastatur bzw. Veränderung von
Komponenten oder Systemstatus (Zeitintervall vergangen, Netzwerkanfragen, ...).
230
vgl. 5.6
255
Programmieren in Java
3.6 Java Beans
3.6.1 Wiederverwendbare Softwarekomponenten
Unter einer Software-Komponente231 versteht man:
Eine Komponente ist ein Stück Software, das klein genug ist, um es in einem Stück zu erzeugen und
pflegen zu können, groß genug ist, um eine sinnvoll einsetzbare Funktionalität zu bieten und eine
individuelle Unterstützung zu rechtfertigen, sowie mit standardisierten Schnittstelle ausgestattet ist,
um mit anderen Komponenten zusammen zu arbeiten.
Zu den grundlegenden Eigenschaften einer Softwarekomponente zählen:
- eine Softwarekomponente ist ein ausführbares Programm
- sie realisiert eine klar abgegrenzte Programmfunktionalität (z.B. ein GUI-Control oder ein
Berechnungsmodul)
- ihre Implementierung ist nach außen nicht transparent (black-box-Modell)
- sie verfügt über öffentliche Schnittstellen, über die auf die Funktionalität der Komponente zugegriffen
werden kann (Eigenschaften, Methoden, Ereignisse).
- eine Softwarekomponente lässt sich in umfangreichere Programme integrieren
- im Unterschied zu Objekten sind Komponenten auf eine abstrakten Ebene angesiedelt und nur lose
miteinandet verkoppelt. Das soll vor allem die einfache Wiederverwendbarkeit von Komponenten
gewährleisten
Bisher gibt es keine einheitliche Entwicklungsmethode für component ware. Es
existieren eine Reihe konkurrierender Komponentenarchitekturen, u.a.
- Microsoft ActiveX/COM/DCOM als Weiterentwicklung von OLE(object linking and embedding)
- Java Beans
- CORBA Business Objects
Sie sind z.T. untereinander interoperable, d.h. ein Java Beans kann als CORBAfähiges Objekt ausgestattet sein oder im Rahmen von ActiveX eingesetzt werden
(über eine ActiveX-Bridge, die das Bean als Actve-X-Komponente verpackt).
Für die Komponentenentwicklung von einzelnen Beans stehen folgende
Funktionalitäten bereit:
- Eigenschaften (properties) und Anpassung (customization) von Komponenten
- Ereignisverarbeitung, um unterschiedliche Komponenten miteinander zu verknüpfen und sie
kommunizieren zu lassen
- Introspektion (introspection), so dass ein Entwicklungswerkzeug erkennen kann, wie ein Bean
arbeitet.
- Persistenz und Verpacken von Komponenten, so dass in einem Entwicklungswerkzeug angepasstes
bzw. modifiziertes Bean in einem neuen Zustand gespeichert werden kann.
Die wichtigsten Merkmale eines Beans sind:
- seine Eigenschaften (properties)
- seine Methoden, die von anderen Komponenten aufgerufen werden können.
- Ereignisse / Nachrichen, die es auslöst.
231 Die Definition wurde zitiert nach: Griffel, Frank: Componentenware – Konzepte und Techniken eines
Softwareparadigma, dpunkt-Verlag, 1998
256
Programmieren in Java
3.6.2 Komponenten für Java: Beans
JavaBeans ist das Komponentenmodell von Java. Diese Komponenten haben eine
genauere und für Programmierwerkzeuge verständlichere Schnittstellendefintion als
Java-Klassen. Die zur Beschreibung und Analyse benötigten Interfaces und Klassen
stehen im Package java.beans. Sie arbeiten sehr eng mit den Klassen zur
Laufzeitanalyse von Java-Klassen im Paket java.lang.reflect zusammen. Die
Schnittstellen von JavaBeans bezeichnet man als Features. Sie besteht aus drei
Teilen:
JavaBeans
Properties
Methoden
Ereignisse
Abb.: Die JavaBean-Features
1. Properties sind die öffentlichen, mit Namen versehenen Attribute oder Eigenschaften. Über diese
Properties sind Beans an die Vorstellungen des Programmierers anpassbar, ohne daß er er den
Quellcode ändern und darauf irgendein Zugriff haben muß. Die Implementierung von Properties
erfolgt durch entsprechende lesende und schreibende Methoden, die einer Namenskonvention
(„Design-Pattern“232) gehorchen. Die Java-Beans-Architektur unterscheidet zwischen sog.
Indexed, Bound und Constraint Properties.
2. Die öffentlichen Methoden, die eine Bean ausführen kann
3. Ein JavaBean erzeugt Ereignisse, z.B. dann, wenn sich ihre Properties ändern.
Diese drei Arten von Features sind im Interface BeanInfo dokumentiert.
Das Paket java.beans enthält folgende Klassen, Schnittstellen und Ausnahmen:
AppletInitalizer
BeanInfo
Customizer
DesignMode
PropertyChangeListener
PropertyEditor
VetoableChangeListener
Visibility
Methoden für die Initialisierung von Beans, die auch Applets sind
Methoden für die Beschreibung eines Beans, z.B.
getBeanDescriptor()
Schnittstelle für Klassen, die der Anpassung von Beans dienen
(Bean-Editoren, Bean-Customizer)
Methoden, die feststellen, ob sich ein Bean in einer Design- oder
Lauffzeitumgebung befindet
Lauscherschnittstelle, die Eigenschaftsänderungsnachrichten
abfängt
Methoden für Klassen, die das Editieren von Beans bzw. ihrer
Eigenschaften erlauben.
Lauscherschnittstelle für sog. "constraint properties", d.h.
Eigenschaften deren Änderung von "externen Faktoren" abhängt,
d.h. gegen deren Änderung von anderen Komponenten ein Veto
eingelegt werden kann.
Methoden zur Feststellung, ob ein Bean in einem GUI ausgeführt
wird.
Abb.: Schnittstellen in java.beans
232
-
Unter Design Pattern sind einfache Muster auf der Basis von Methodennamen und Signaturen gemeint, z.B.
Ein Property mit dem Namen Color, für das sowohl schreibende als auch lesende Zugriffe erlaubt sind wird
durch Implementierung der Methoden „public Color getColor()“ und „public void setColor()“ dargestellt.
Das Entwurfsmuster für das Verbinden eines Event-Handlers mit einer Event-Source besteht aus einer
Methode, die mit „set“ beginnt und mit „Listener“ endet und ein Argument besitzt. Außerdem muß eine
Vererbung vom EventListener-Interface vorhanden sein.
257
Programmieren in Java
BeanDescriptor
Beans
EventSetDescriptor
FeatureDescriptor
Introspector
MethodDescriptor
ParameterDescriptor
PropertyChangeEvent
PropertyChangeSupport
PropertyDescriptor
PropertyEditorManager
PropertyEditorSupport
SimpleBeanInfo
VetoableChangeSupport
Klasse für den Zugriff auf allgemeine Eigenschaften einer
Komponente
Hilfsklasse mit nützlichen Methoden um Informationen über ein
Bean zu erhalten (isDesignTime(), isGuiAvailable(), etc.)
beschreibt Ereignistypen, die von einem Bean ausgelöst werden.
Superklasse aller Deskriptor-Klassen für Beans mit generischen
Informationsmethoden (z.B. getNames(), attributeNames(),
getValues(), etc.).
Zugriff auf Beaneigenschaften zur Design-Zeit durch Methoden wie
z.B. getBeanInfo().
beschrebt eine Mrethode eines Java Bean, die von außen
zugänglich ist
zusätzliche Information über Parameter (d.h. mehr als das, was
man über die reflection-Methoden ermitteln kann
Nachrichtenklasse für die Verarbeitung von Eigenschaftsänderungsachrichten
Hilfsklasse für die Verarbeitung von Eigenschaftsänderungsnachrichten
beschreibt die Eigenschaft eines Bean, die von außen zugänglich
ist.
ist beim Aufbau eines geeigneten Editor für ein Bean behilflich
Hilfsklasse für den Aufbau von Editoren
Hilfsklasse für die Generierung bon BeanInfo-Objekten
Hilfsklasse für die Verarbeitung von Eigenschaften, gegen die ein
Veto möglich ist.
Abb.: Klassen in java.beans
IntrospectionException
PropertyVetoException
tritt bei Fehlern während der Introspektion eines Beans auf
tritt auf, falls eine Eigenschaft eines Bean auf einen nicht
akzeptablen Wert geändert werden soll
Abb.: Ausnahmen in java.beans
3.6.3 Enterprise Java Beans
„Java Beans“ wurde als ein lokales Komponentenmodell233 entworfen. Das JavaBeans-Modell sieht aber auch die Nutzung entfernter Ressourcen vor: Enterprise
Java Beans. Damit werden Anbindungen zur folgenden Application Interfaces
möglich:
- Java Database Connectivity -API: Es gestattet den Zugriff auf relationale Datenbanken.
- Remote Method Invocation: RMI stellt ein API zur Verfügung, mit dessen Hilfe die Kommunikation
zweier Komponenten über Adreß- oder Maschinenräume hinweg möglich ist. RMI ist eine Art
objektorientierter RPC-Mechanismus (Remote Procedure Call), der speziell für Java entwickelt
wurde.
- CORBA
233D.h.:
Die Kommunikation zwischen Komponenten findet auf einer Maschine statt.
258
Programmieren in Java
4. Grafik und Bildverarbeitung
Mit einer Instanz von Graphics kann gezeichnet werden, z.B.:
Graphics meineGrafik;
meineGrafik = getGraphics();
meineGrafik.drawString("mache irgendwas",20,40);
Die in der Klasse Component als public Graphics getGraphics() definierte
Methode gibt den „Graphics“-Kontext von der Komponente zurück bzw. Null, wenn
die Komponente keinen aktuellen Grafikbezug hat. Bei jeder Zeichenoperation muß
der grafische Kontext angegeben werden, denn dieses Objekt verwaltet:
- die Komponente, auf der zu zeichnen ist.
- Koordinaten des Bildbereichs und des Clipping-Bereichs.
- der aktuelle Clip-Bereich und Font, die aktuelle Farbe
- die Pixeloperation (XOR oder Paint)
- Composite (Überschreibmodus/Alphakanal)
Die meisten Zeichenvorgänge werden in der paint()-Methode durchgeführt. JavaApplets zeichnen sich selbst neu nach Überschreiben der paint()-Methode. Es gibt
drei verschiedene Methoden234 zum Neuzeichnen:
public void paint(Graphics g)
public void repaint()
public void update(Graphics g)
Ein Graphics-Objekt erzeugt nicht der Entwickler, sondern er bekommt den Kontext
gestellt, auf dem er zeichnen möchte. Die Klasse Graphics hat keinen öffentlichen
Konstruktor.
234
vgl. 3.2.2
259
Programmieren in Java
Graphics
{ abstract }
.........
<< Konstruktor >>
protected Graphics()
<< Methoden >>
public abstract Graphics create()
public Graphics create(int x, int y, int width, int height)
public abstract void translate(int x, int y)
public abstract Color getColor()
public abstract void setColor(Color farbe)
public abstract void setXORMode(Color farbe)
public abstract Font getFont()
public abstract void setFont(Font f)
public FontMetrics getFontMetrics()
public abstract Rectangle getClipBounds()
public abstract void clipRect(int x, int y, int width, int height)
public abstract void setClip(int x, int y, int width, int height)
public abstract Shape getClip()
public abstract void drawLine(int x1, int y1, int x2, int y2)
public abstract fillRect(int x, int y, int width, int height)
public void drawRect(int x, int y, int width, int height)
public abstract void clearRect(int x, int y, int width, int height)
public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
public abstract fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
public abstract void drawOval(int x, int y, int width, int height)
public abstract void fillOval(int x, int y, int width, int height)
public abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
public abstract void drawString(String str, int x, int y)
public abstract boolean drawImage(Image bild, int x, int y, ImageObserver observer)
Abb.: Die Klasse Graphics
4.1 Allgemeine Zeichenvorgänge
4.1.1 Punkte, Linien, Kreise, Bögen, Polygone
Das Koordinatensystem. Der Ausgangspunkt (0,0) des Java-Koordinatensystems
ist die obere linke Ecke. Von dieser Stelle führen positive x-Werte nach rechts und
positive y-Werte nach unten. Die Angaben der Koordinaten erfolgen in Pixel. Alle
Pixelwerte sind Ganzzahlen.
Punkte. Ein Punkt ist durch zwei oder mehr Koordinaten gekennzeichnet. In Java gibt
es keine Funktion, mit der Punkte gezeichnet werden. Diese können nur durch
Liniebefehle erzeugt werden.
Zeichnen einer Linie. Es geschieht mit der Methode: public abstract void
drawLine(int x1, int y1, int x2, int y2). (x1,y1) bestimmt den
Anfangspunkt, (x2,y2) bestimmt den Endpunkt der Linie.
260
Programmieren in Java
Bsp.: Ein Applet mit zufällig verteilten Linien235
import java.awt.*;
// Top Level Deklaration des Applets
public class LinienApplet extends java.applet.Applet
implements Runnable
{
// Variablen-Deklaration
int x1 = 0;
int x2 = 0;
int y1 = 0;
int y2 = 0;
float rot, gruen, blau;
Color linienFarbe;
// Eigene Methoden
// Methoden, die ueberschrieben werden
public void init()
{
setBackground(Color.lightGray);
}
public void start()
{
if (faden == null)
{
faden = new Thread(this);
faden.start();
}
}
public void stop()
{
if (faden != null)
{
faden.stop();
faden = null;
}
}
public void run()
{
// Ausfuehrung des Applet
while (true)
{
x1 = (int) (Math.random() * this.size().width);
x2 = (int) (Math.random() * this.size().width);
y1 = (int) (Math.random() * this.size().height);
y2 = (int) (Math.random() * this.size().height);
rot = (float) Math.random();
gruen = (float) Math.random();
blau = (float) Math.random();
linienFarbe = new Color(rot,gruen,blau);
repaint(); // Aufruf der repaint()-Methode
try {faden.sleep(1000); } // Pause von 1000 Millisekunden
catch(InterruptedException e) {}
}
}
public void paint(Graphics g)
{
g.setColor(linienFarbe);
g.drawLine(x1,y1,x2,y2);
}
}
Die Methode drawLine zeichnet Linien mit einer Dicke von einem Pixel.
235
vgl. pr42005
261
Programmieren in Java
Zeichnen eines Rechtecks. Dafür gibt es die Methode: public void
drawRect(int x, int y, int width, int height). (x1,y1) bestimmt die
obere linke Ecke eines Rechtecks, (width, height) legen Breite und Höhe des
Rechtecks fest.
Zeichnen eines gefüllten Rechtecks. Es wird ermöglicht durch die Methode: public
abstract void fillRect(int x, int y, int width, int height). Die
Farbe, mit der das Rechteck gefüllt werden soll, kann mit der folgenden Methode
gesetzt werden: public abstract void setColor(Color c).
Bsp.: Ein Applet mit zufällig verteilten Rechtecken236
// zeichne Rechtecke
import java.applet.*;
import java.awt.*;
public class RechteckeAppl3 extends Applet
{
// Instanzvariable
int appletHoehe;
int appletBreite;
int rechteckHoehe;
int rechteckBreite;
int rechteckTop;
int rechteckLinks;
Color rechteckFarbe;
int anzRechtecke = 100;
// Methoden
public void init()
{
Dimension d = size();
appletHoehe = d.height;
appletBreite = d.width;
repaint();
}
public void paint(Graphics g)
{
setBackground(Color.white);
g.setColor(Color.black);
g.drawRect(0,0,appletBreite – 1,appletHoehe – 1);
for (int i = 0; i < anzRechtecke; i++)
{
rechteckTop
= bestimmeZufallszahl(appletHoehe);
rechteckLinks = bestimmeZufallszahl(appletBreite);
rechteckHoehe = bestimmeZufallszahl(
appletHoehe - rechteckTop);
rechteckBreite = bestimmeZufallszahl(
appletBreite – rechteckLinks);
rechteckFarbe = new Color(bestimmeZufallszahl(255),
bestimmeZufallszahl(255),
bestimmeZufallszahl(255));
g.setColor(rechteckFarbe);
g.fillRect(rechteckLinks,rechteckTop,rechteckBreite-1,
rechteckHoehe – 1);
}
}
private int bestimmeZufallszahl(int bereich)
{
double ausgangsgroesse;
ausgangsgroesse = Math.random();
return (int) (ausgangsgroesse * bereich);
}
}
236
vgl. pr41103
262
Programmieren in Java
Löschen eines Rechtecks. Das übernimmt die Methode public abstract void
clearRect(int x, int y, int width, int height).
Kopieren eines Rechtecks. Dafür gibt es die Methode public abstract void
copyArea(int x, int y, int width, int height, int dx, int dy).
Zeichnen eines 3D-Rechtecks. Es erfolgt mit Hilfe der Methode public void
draw3DRect(int x, int y, int width, int height, boolean
raised).
Zeichnen eines gefüllten 3D-Rechtecks.
Zeichnen abgerundeter Rechtecke. Sie können gezeichnet werden mit public
abstract void drawRoundRect(int x, int y, int width, int
height, int arcWidth, int arcHeight). „arcWidth“ bestimmt den Winkel
der Abrundung auf der horizontalen, „arcHeight“ den Winkel auf der vertikalen
Ebene. Je größer die Winkel sind, desto stärker gerundet erscheint das Rechteck.
Zeichnen abgerundeter, gefüllter Rechtecke. Dafür existiert die Methode public
abstract void fillRoundRect(int x, int y, int width, int
height, int arcWidth, int arcHeight).
Zeichnen von Polygonen. Hierfür kann die Methode public abstract void
drawPolygon(int[]
xPunkte,
int[]
yPunkte,
int
nPunkte)
herangezogen werden. Es gibt zwei Möglichkeiten beim Zeichnen von Polygonen:
- Weitergabe der beiden Datenbereiche (Arrays) mit den x- und y-Koordinaten der Punkte, z.B.237:
import java.awt.*;
public class ZeichnePolyAppl1 extends java.applet.Applet
{
// Instanzvariable
// Definition des Felds mit den x-Koordinaten
int xKoord[] = {20,50,70,40,20,20};
// Definition des Felds mit den y-Koordinaten
int yKoord[] = {30,10,20,70,50,30};
// Methoden
public void init() { setBackground(Color.yellow); }
public void paint(Graphics g)
{
// Zeichne ein 5-Eck
g.setColor(Color.red);
g.drawPolygon(xKoord,yKoord,6);
}
}
- Weitergabe einer Instanz der Polygon-Klasse, z.B.238:
import java.awt.*;
public class ZeichnePolyAppl2 extends java.applet.Applet
{
// Instanzvariable
// Definition des Felds mit den x-Koordinaten
int xKoord[] = {20,50,70,40,20,20};
// Definition des Felds mit den y-Koordinaten
int yKoord[] = {30,10,20,70,50,30};
// Anzahl Ecken
int anzEcken = xKoord.length;
// Methoden
public void init() { setBackground(Color.yellow); }
public void paint(Graphics g)
237
238
pr41105
pr41105
263
Programmieren in Java
{
// Zeichne ein 5-Eck
g.setColor(Color.red);
Polygon poly = new Polygon(xKoord,yKoord, anzEcken);
g.drawPolygon(poly);
}
}
Das Zeichnen von gefüllten Polygonen. Dazu dient die Methode public abstract
void fillPolygon(int[] xPunkte, int[] yPunkte, int nPunkte).
Bsp.239:
import java.awt.*;
public class ZeichnePolyAppl3 extends java.applet.Applet
{
// Instanzvariable
// Definition des Felds mit den x-Koordinaten
int xKoord[] = {20,50,70,40,20};
// Definition des Felds mit den y-Koordinaten
int yKoord[] = {30,10,20,70,50};
// Methoden
public void init() { setBackground(Color.yellow); }
public void paint(Graphics g)
{
// Zeichne ein 5-Eck
g.setColor(Color.red);
g.fillPolygon(xKoord,yKoord,5);
}
}
Zeichnen eines Linienzuges mit der Methode abstract void drawPolygon(int
xPunkte[], int yPunkte[], int nPunkte) durch die gegebenen
Koordinaten in der Vordergrundfarbe. Die Figur wird dabei nicht geschlossen, wenn
nicht Strat- und Endkoordinaten gleich sind. Mit nPunkte werden die Anzahl der
gezeichneten Linien kontrolliert.
Die Klasse Polygon. Sie ist eine Erweiterung des Interface Shape . Ein PolygonObjekt verwaltet eigenständig seine Koordinaten, von außen können Elemente
aufgenommen werden.
239
pr41105
264
Programmieren in Java
java.awt.Polygon
protected Rectangle bounds
public int npoints
public int[] xpoints
public int[] ypoints
<< Konstruktor >>
public Polygon()
public Polygon(int[] xpoints, int[] ypoints, int npoints)
<< Methoden >>
public void addPoint(int x, int y)
public boolean contains(double x, double y)
public boolean contains(double x, double y, double w, double h)
public boolean contains(int x, int y)
...
public Rectangle getBounds();
...
public boolean inside(int x, int y)
...
public void translate(int deltaX, int deltaY)
Abb.: Die Klasse Polygon
Das Zeichnen von Kreisen und Ellipsen. Es erfolgt über die Methode public
abstract void drawOval(int x, int y, int width, int height).
(x,y) gibt die Koordinaten der oberen linken Ecke des umschreibenden Rechtecks
an.
Das Zeichnen von gefüllten Kreisen und Ellipsen. Es erfolgt über die Methode
public abstract void fillOval(int x, int y, int width, int
height)
Das Zeichnen von Bögen. Java stellt die folgende Methode dafür zur Verfügung:
public abstract void drawArc(int x, int y, int breite, int
hoehe, int startWinkel, int bogenWinkel). „startWinkel“ bestimmt
den Anfangswinkel von einer (gedachten) horizontalen Mittellinie aus gesehen, ab
dem der Bogen gezeichnet werden soll. „bogenWinkel“ legt fest, wie weit der
Bogen ab dem Startpunkt gezeichnet wird und in welche Richtung er geht. Die
positive Richtung in Java ist entgegen dem Uhrzeigersinn.
Das Zeichnen von gefüllten Bögen. Es erfolgt über die Methode public abstract
void fillArc(int x, int y, int breite, int hoehe, int
startWinkel, int bogenWinkel)
Das Zeichnen von Zeichenketten erfolgt mit Hilfe der Methoden
public abstract void drawString(String str, int x, int y)
public void drawChars(char[] daten, int offset, int length, int x, int y)
public void drawBytes(byte[] daten, int offset, int length, int x, int y)
265
Programmieren in Java
4.1.2 Farbangaben
Java setzt Farben aus sog. Primärfarben (Rot, Grün, Blau) des Lichts zusammen
(RGB-Modell). Eine Farbe im RGB-Modell wird durch die Angabe, wieviel rotes,
grünes und blaues Licht in der Farbe enthalten sind, bestimmt. Dies kann entweder
mit einer Ganzzahl zwischen 0 und 255 oder einer Gleitpunktzahl zwischen 0.0 und
1.0 geschehen.
Farbe
Weiß (white)
Hellgrau (lightGray)
Grau (gray)
Dunkelgrau (darkGray)
Schwarz (black)
Rot (rot)
Grün (green)
Blau (blue)
Gelb (yellow)
Orange (orange)
Pink (pink)
Magenta (magenta)
Cyan (cyan)
Rot-Anteil
255
192
128
64
0
255
0
0
255
255
255
255
0
Grün-Anteil
255
192
128
64
0
0
255
0
255
200
175
0
255
Blau-Anteil
255
192
128
64
0
0
0
255
0
0
175
255
255
Abb.: Gebräuchliche Farbwerte RGB-Werte)
Neben dem RGB-Farbmodell unterstützt Java auch das HSB-Farbmodell. Dieses
stellt eine Farbe durch die drei Parameter Farbton, Intensität und Helligkeit dar. Die
Farbmodelle können die gleichen Farben beschreiben und umgerechnet werden.
Die Color-Klasse. Auf drei Arten kann eine Farbe erzeugt werden:
public Color(int red, int green, int blue)
Damit wird eine Farbe mit Rot-, Grün- und Blau-Werten zwischen 0 und 255 erzeugt.
public Color(int rgb)
erzeugt ein Color-Objekt aus dem rgb-Wert, der die Farben rot, grün, blau kodiert. Der Rot-Anteil
befindet sich in den Bits 16 bis 23, der Grünanteil in 8 bis 15 und auch der Blauanteil in 0 bis 7. Jede
Farbe ist durch ein Byte repräsentiert.
public Color(float red, float green, float blue)
Eine gängige Farbe kann schneller über die Standardfarbobjekte der in der Color-Klasse definierten
verschiedenen Klassenvariablen gewonnen werden, z.B.:
public
public
public
public
public
public
public
public
public
public
public
public
public
static
static
static
static
static
static
static
static
static
static
static
static
static
Color
Color
Color
Color
Color
Color
Color
Color
Color
Color
Color
Color
Color
white;
lightGray;
gray;
darkGray;
black;
red;
blue;
green;
yellow;
magenta;
cyan;
orange;
pink;
Ermitteln der RGB-Werte von einem bestimmten Farbobjekt. Es erfolgt über
public int getRed();
public int getGreen();
266
Programmieren in Java
public int getBlue();
Setzen von Farben. Es wird möglich durch die Methode: public abstract void
setColor(Color c). Der Parameter bestimmt das gewünschte Farbobjekt.
Setzen von Hintergrundfarben. Normalerweise ist die Hintergrundfarbe eines Applets
weiß oder dunkelgrau (je nach Container). Individuell kann die Hintergrundfarbe
eines Applets gesetzt werden durch: public void setBackground(Color c).
Parameter ist das gewünschte Farbobjekt.
Setzen von Vordergrundfarben. Falls die Farbe für alle Zeichenobjekte innerhalb
eines Applets pauschal festgesetzt werden soll, dann kann die Methode public
void setForeground(Color c) verwendet werden.
4.1.3 Textausgabe über den Zeichen-Modus
Die Graphics-Klasse enthält auch Methoden zum Zeichnen von Textzeichen und
Zeichenketten (z.B. die drawString() Methode). Zusätzlich spielen die FontKlasse240 und die Fontmetrics-Klasse241 beim Textzeichnen eine Rolle.
Font
FontMetrics
{ abstract }
public static final int PLAIN
public static final int BOLD
public static final int ITALIC
protected String name
protected int style
protected int size
public Font getFont()
public int getLeading()
public int getAscent()
public int getDescent()
public int getHeight()
public int getMaxAscent()
public int getMaxDescent()
public int getMaxAdvance()
public int charWidth(int zeichen)
public int charWidth(char zeichen)
public int stringWidth(String str)
public int[] getWidths()
public String toString()
<< Konstruktoren >>
public Font(String name, int style, int size)
<< Methoden >>
public String getName()
public int getStyle()
public int getSize()
public boolean isPlain()
public boolean isBold()
public boolean isItalic()
public static Font getFont(String nm)
public static Font getFont(String nm, Font font)
public static Font decode(String str)
public int hashCode()
public boolean equals(Object obj)
public String toString()
Abb.: Die Klassen Font und FontMetrics
240
Die Font-Klasse stellt bestimmte Fonts dar (Name, Stil, Fontgröße)
Die Fontmetrics-Klasse enthält Informationen über den Font wie tatsächliche Höhe und Breite eines
bestimmten Zeichens.
241
267
Programmieren in Java
Die Klasse Font
Texte werden in einem standarmäßig bereitgestellten Font ausgegeben. Soll ein
anderer Font zur Textausgabe verwendet werden, so muß ein Objekt der Klasse
Font erzeugt und dem verwendeten Graphics-Objekt zugewiesen werden.
Das Erzeugen neuer Font-Objekte wird über die Parameter name, style und size
des Konstruktors der Klasse Font gesteuert:
public Font(String name, int style, int size)
name: Name des gewünschten Font. In allen Java-Systemen sollen „SansSerif“, „Serif“, und
„Monospaced“ unterstützt werden. Unter Windows werden die Standardnamen auf die „True-TypeFonts“ „Arial“, „TimesNewRoman“ und „CourierNew“ abgebildet.
style: Auswahl der Ausprägung (fett, kursiv)
Name
Font.PLAIN
Font.BOLD
Font.ITALIC
Wert
0
1
2
Bedeutung
Standard-Font
fett242
kursiv
size: Angabe der Größe der gewünschten Schriftart in Pixel (Punktgrößen)
public void setFont(Font font) wird zum Eintragen des Font-Objekts in den
Grafikkontext verwendet.
public Font getFont() ermittelt den aktuellen Font.
Die Ermittlung, welche Zeichensätze auf einem System installiert sind, kann über
Toolkit.getDefaultToolkit().getFontList()
erfolgen.
String[]
getFontList() gibt die Namen der verfügbaren Zeichensätze zurück, z.B.:243
import java.awt.*;
class ListFont
{
public static void main(String args[])
{
// herkoemmliche Methode zur Ermittlung der
// Schriftarten in einem Java-System
String fonts[] = Toolkit.getDefaultToolkit().getFontList();
for (int i = 0; i < fonts.length; i++)
System.out.println(fonts[i]);
}
}
Die Klasse FontMetrics
Diese Klasse bietet Methoden zur Bestimmung der Größe der angezeigten Zeichen
in der festgelegten Schrift an.
Begriffe für Schriften und Text.
Baseline (Grundlinie): Damit ist die imaginäre Linie gemeint, auf der der Text steht.
Descent (Unterstand): Damit ist gemeint, wie weit ein Buchstabe unter die Grundlinie geht.
Ascent (Überstand): Damit ist gemeint, wie weit ein Buchstabe über die Grundlinie geht.
242
BOLD und ITALIC können auch gemeinsam verwendet werden, indem beide Konstanten mit „+“
zusammengefügt werden.
243 pr41300
268
Programmieren in Java
Leading (Zeileabstand): Damit ist der Raum zwischen dem Descent eines Buchstabens und dem
Ascent der nächsten Zeile gemeint.
Methoden.
Methode
stringWidth()
charWidth()
getAscent()
Aktion
Gibt die volle Breite einer Zeichenmkette in Pixel aus
Gibt die Breite eines bestimmten zeichens aus
Gibt die Entfernung zwischen der Grundlinie und der oberen Grenze der Buchstaben
aus
getDescent()
Gibt die Entfernung zwischen der Grundlinie und der unteren Grenze der
Buchstaben aus (z.B. p und g)
getLeading()
Gibt den Abstand zwischen dem Überstand einer Zeile und dem Überstand der
nächsten Zeile aus
getHeight()
gibt die Gesamthöhe der Schrift aus, d.h. die Summe von Überstand, Unterstand
und Zeilenabstand
Abb.: Fontmetrics-Methoden
Bsp.244:
import java.applet.*;
import java.awt.*;
public class AllAnfAppl extends Applet
{
private static final String spruch = "Aller Anfang ist schwer!";
private Font font;
private FontMetrics fontMetrics;
private int spruchBreite;
private int spruchAscent;
private int spruchDescent;
private int spruchX;
private int spruchY;
// Methoden
public void init()
{
setBackground(Color.white);
font = new Font("Helvetica",Font.BOLD,24);
fontMetrics = null;
}
//
public void paint(Graphics g)
{
// Oval mit Farbe yellow
g.setColor(Color.yellow);
g.fillOval(0,0,getSize().width,getSize().height);
// Roter Rahmen; da Java keine Linienbreite kennt,
// wird die Linienbreite durrch 4 Ovale, (die sich
// um Pixelbreite unterscheiden,) simuliert
g.setColor(Color.red);
g.drawOval( 3, 3,getSize().width-6,getSize().height-6);
g.drawOval( 2, 2,getSize().width-4,getSize().height-4);
g.drawOval( 1, 1,getSize().width-2,getSize().height-2);
g.drawOval( 0, 0,getSize().width,getSize().height);
g.setColor(Color.black);
g.setFont(font);
if (fontMetrics == null)
{
fontMetrics
= g.getFontMetrics();
spruchBreite = fontMetrics.stringWidth(spruch);
spruchAscent = fontMetrics.getAscent();
spruchDescent = fontMetrics.getDescent();
244
pr41301
269
Programmieren in Java
}
int breite = getSize().width;
int hoehe = getSize().height;
spruchX = (breite - spruchBreite) / 2;
spruchY = (hoehe + spruchAscent - spruchDescent) / 2;
g.drawString(spruch,spruchX,spruchY);
}
}
4.1.4 Grössenangaben
Für die meisten Größenangaben werden int-Werte benutzt. Darüber hinaus gibt es
auch alterantive Formulierungsmöglichkeiten mit den Klassen Dimension, Point
und Rectangle.
<< interface >>
Shape
Point
public int x
public int y
Rectangle
public int y
public int y
public int width
public int height
<< Konstruktoren >>
public Rectangle(Rectangle r)
public Rectangle(int x, int y, int width, int height)
public Rectangle(int width, int height)
public Rectangle(Point p, Dimension d)
public Rectangle(Point p)
public Rectangle(Dimension d)
<< Methoden >>
public Rectangle getBounds()
public void setBounds(int x,int y, int width, int height)
public void setBounds(Rectangle r)
public Point getLocation()
public void setLocation(Point p)
public void setLocation((int x, int y)
public void translate(int x, int y)
public Dimension getSize()
public void setSize(int width, int height)
public boolean contains(Point p)
public boolean contains(int x, int y)
public boolean isEmpty()
public int hashCode()
public boolean equals(Object obj)
public String toString()
<< Konstruktoren >>
public Point()
public Point(Point p)
public Point(int x, int y)
<< Methoden >>
public Point getLocation()
public void setLocation(Point p)
public void setLocation(int x, int y)
public void move(int x, int y)
public void translate(int x, int y)
public int hashCode()
public boolean equals(Object obj)
public String toString()
Dimension
public int width
public int height
<< Konstruktoren >>
public Dimension()
public Dimension(Dimension d)
public Dimension(int width, int height)
<< Methoden >>
public Dimension getSize()
public void setSize(Dimension d)
public boolean equals(Object obj)
public String toString()
Abb.: Die Klassen Dimension und Rectangle
Das Interface Shape wird von den bisher bekannten geometrischen Formen (z.B.
Rectangle, Polygon) implementiert und außerdem von den neuen geometrischen
Formen im Paket java.awt.geom.
270
Programmieren in Java
4.1.5 Clipping-Operationen
Jeder Grafikkontext hat eine zugeordnete Clipping-Region, die dazu dient, die
Ausgabe auf einen bestimmten Bereich einzugrenzen. Clipping ist eine Eigenschaft
des aktuellen „Graphic“ Objekts. Mit der Methode clipRect(int x, int y, int
breite, int hoehe) läßt sich der Bereich einschränken, dann erfolgen alle
Operationen in diesem Bereich. Mit
public abstract void setClip(int x, int y, int width, int height)
public abstract void setClip(Shape clip)
kann die Clipping-Region auf einen beliebigen Bereich innerhalb des aktuelle
Fensters ausgedehnt werden. Die zweite Version von setClip() erlaubt die
Übergabe eines Objekts, die das Shape-Interface implementiert.
4.2 Bildverarbeitung
Bilder sind neben Text das wichtigste visuelle Gestaltungsmerkmal. In Java können
Grafiken an verschiedenen Stellen eingebunden werden, z.B. als Grafiken in
Zeichenflächen (Canvas) oder als Icons in Buttons. Über Java können GIF-Bilder245
und JPEG-Bilder geladen werden.
Jede Grafik wird als Exemplar der Klasse Image erzeugt. Image dient zur
Implementierung von:
- ImageProducer für die Erzeugung von Bildern auf der Grundlage von Bildpunkten.
- ImageConsumer für die Darstellung von Bildern als Interpretation der gespeicherten Bildpunkte.
- ImageFilter für die Filterung des Bildes insbesondere hinsichtlich vorgebbarer Farbmodelle und
Bildeigenschaften (mit der speziellen Subklasse RGBImageFilter).
- ImageObserver bspw. für die Beobachtung, ob ein Bild vollständig geladen ist u.ä.m. Zur
vollständigen Kontrolle des Ladens eines Bilds stellt java.awt. die Klasse MediaTracker bereit.
Bilder einer Applikation werden über die Klasse Toolkit eingebunden.
Bsp.: Anfordern eines Bilds in einer Applikation.
Image pic = Toolkit.getDefaultToolkit().getImage("");
Zur Anzeige eines Bilds in Originalgröße wird die Methode drawImage() mit
folgenden Argumenten aufgerufen:
- Das Image-Objekt, das angezeigt werden soll.
- die x-Koordinate
- die y-Koordinate
- Das Schlüsselwort this.
245 Das GIF-Format (Graphics Interchange Format) ist ein komprimierendes Verfahren, das 1987 von
CompuServe-Betreibern zum Austausch von Bildern entwickelt wurde. GIF-Bilder können bis zu 1600 mal
1600 Punkte umfassen. Die Komprimierung (nach dem LZW-Verfahren) ist verlustfrei. Jedes GIF-Bild kann aus
maximal 256 Farben bestehen (bei einer Palette von 16,7 Millionen Farben).
271
Programmieren in Java
4.2.1 Klassen zur Bildverarbeitung
Toolkit
{ abstract }
public abstract Image getImage(String dateiName)
public abstract Image getImage(Url url)
public abstract Image createImage(ImageProducer erzeuger)
public abstract boolean prepareImage(Image bild, int breite, int hoehe,ImageObserver observer)
public abstract int checkImage(Image bild, int breite, int hoehe, ImageObserver observer)
....
Image
{ abstract }
public abstract int getHeight(ImageObserver obs)
public abstract int getWidth(ImageObserver obs)
public abstract ImageProducer getSource()
public abstract Graphics getGraphics()
public abstract void flush()
public abstract Object getProperty(String attribut, ImageObserver obs)
public Image getScaledInstance(int breite, int hoehe, int modus)
<< interface >>
ImageObserver
public static final int ALLBITS
public static final int SOMEBITS
public static final int ABORT
public static final int ERROR
public static final int PROPERTIES
public abstract boolean imageUpdate(Image bild, int infoFlags, int x, int y, int breite, int hoehe)
MediaTracker
public static final int ABORTED
public static final int COMPLETE
public static final int ERRORED
public static final int LOADING
<< Konstruktor >>
public MediaTracker(Component comp)
<< Methoden >>
public void addImage(Image bild, int id)
public void addImage(Image bild, int id, int breite, int hoehe)
public boolean checkAll()
public void waitForAll() throws InterruptedException
public boolean checkID(int id)
public void waitForID(int id) throws InterruptedException
...
Abb.: Image-Klassen
272
Programmieren in Java
Die Klassen zur Bildverarbeitung befinden sich im Package java.awt.image. Mit
Hilfe der Klasse java.awt.Toolkit kann ein Bild aus einer angegebenen Quelle
mit getImage() geladen werden. Dabei wird ein Objekt vom Typ
java.awt.Image zurückgegeben. Der Ladezustand eines Objekts (z.B eines Bilds)
kann überwacht werden, wenn die zum Objekt zugehörige Klasse das Interface
java.awt.image.ImageObserver implementiert. Das tun alle AWT- und SwingKontrollelemente. Zur vereinfachten Benutzung beim Laden mehrerer Bilder gibt es
die Klasse java.awt.MediaTracker. Die weiteren Klassen im Paket
java.util.image ermöglichen Skalierung, Rotation, Filtern und andere
Veränderungen der Bilder. Als Bildformate werden GIF und JPG unterstützt.
In Java 2 kamen Klassen zum pixelorientierten Erzeugen und Bearbeiten von Bildern
im Paket java.awt.image und dem neuen java.awt.image.renderable
hinzu.
Bsp.: Ein Bildbetrachter246
import java.awt.*;
import java.awt.event.*;
public class ImageViewer extends Frame implements ActionListener
{
private Image image;
private Frame frame;
public ImageViewer()
{
setTitle("Bildbetrachter");
// konstruiere die Menuezeile
MenuBar mbar = new MenuBar();
Menu menue = new Menu("Datei");
MenuItem menueItem = new MenuItem("Oeffnen", new MenuShortcut((int)
'O'));
menueItem.addActionListener(this);
menue.add(menueItem);
mbar.add(menue);
setMenuBar(mbar);
// Schliessen des Fenster mit X
frame = this;
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
setSize(600,400);
}
public void paint(Graphics g)
{
if (image != null)
{
g.drawImage(image,0,0,this);
setSize(image.getWidth(this),image.getHeight(this));
}
}
public void actionPerformed(ActionEvent e)
{
FileDialog d = new FileDialog(frame,"Oeffne Grafikdatei",
FileDialog.LOAD);
d.setFile("*.jpg;*gif");
d.show();
String file = d.getDirectory() + d.getFile();
246
pr42300
273
Programmieren in Java
image = Toolkit.getDefaultToolkit().getImage(file);
if (image != null) repaint();
}
public static void main(String args[])
{
new ImageViewer().show();
}
}
Ein Bild in einer anderen Größe kann durch eine erweiterte Version von
drawImage() angezeigt werden, falls folgende Parameter angegeben werden:
- Das Imgae-Objekt, das angezeigt werden soll
- die x-Koordinate
- die y-Koordinate
- die Breite
- die Höhe
- das Schlüsselwort this
Zwei Methoden der Image-Klasse sind zur Anzeige eines Bilds, das nich in
Originalgröße gezeigt werden soll, hilfreich:
- getHeight() gibt die Höhe des Bilds zurück.
- getWidth() gibt die Breite des Bilds zurück.
Bsp.247: Verkleinern der Originalgröße eines Bilds (Haus Kirchplatz 7) um 25 bzw. 50
Prozent
import java.awt.Graphics;
import java.awt.Image;
public class Kirchplatz7 extends java.applet.Applet
{
Image whig;
public void init()
{
whig = getImage(getCodeBase(),"B01000800.jpg");
}
public void paint(Graphics g)
{
int iBreite = whig.getWidth(this);
int iHoehe
= whig.getHeight(this);
int xPos = 10;
// 25%
g.drawImage(whig, xPos, 10, iBreite / 4, iHoehe / 4, this);
// 50%
xPos += (iBreite / 4) + 10;
g.drawImage(whig, xPos, 10, iBreite / 2, iHoehe / 2, this);
}
}
247
vgl. pr42300
274
Programmieren in Java
Abb.: Das Haus "Am Kirchplatz 7". Links ist ein Viertel, rechts die Hälfte der Originalgröße dargestellt.
Mit der Klasse MediaTracker kann festgestellt werden, ob ein Bild für die Anzeige
bereit ist. Ein MediaTracker, z.B. für ein Applet, kann erzeugt werden über
MediaTracker meinTracker = new MediaTracker(this);
„this“ bezieht sich auf das Applet. Ein Bild wird angezeigt über die Methode
getImage(), z.B. über Image bild = getImage(getDocumentBase(),"B......jpg").
Der Mediatracker wird angewiesen, dieses Bild über
meinTracker.addImage(bild,0)
zu beobachten. Mit waitForID() kann gewartet werden, bis das Bild fertig geladen
wirde, z.B. meinTracker.waitForID(0). Mit waitForAll() kann gewartet
werden, bis alle Bilder geladen sind. Falls nicht die ganze Zeit gewartet werden soll,
bis das Bild geladen ist, kann der Ladevorgang mit der Methode statusID()
überwacht werden. Beim Aufruf von statusID() wird eine Identifikation (ID)
übergeben, für die ein Status angelegt werden soll, und ein boolscher Operator mit
einer Angabe, ob der Ladevorgang für das Bild gestartet werden soll oder nicht, z.B.:
meinTracker.statusID(0,true)
Soll der Status aller Bilder überprüft werden, dann erfolgt der Aufruf
meinTracker.statusAll(true).
Die
Methoden
statusID()
bzw.
statusAll() geben eine ganze Zahl zurück, die durch folgende Flags symbolisiert
wird:
MediaTracker.ABORTED
MediaTracker.COMPLETE
// Das Laden von Bildern wurde abgebrochen
// Das Bilder wurden komplett geladen
275
Programmieren in Java
MediaTracker.LOADING
MediaTracker.ERRORED
// Das Bilder werden noch geladen
// Das Laden von Bildern ist fehlerhaft
Mit checkID() bzw. checkALL() kann überprüft werden, ob ein Bild bzw. alle
Bilder geladen wurde(n):
public
public
public
public
boolean
boolean
boolean
boolean
checkAll()
checkAll(boolean startLoading)
checkID(int id)
checkID(int id,boolean startLoading)
Falls „startLoading“ true ist, wird der Ladevorgang für alle Bilder, die noch nicht
geladen sind, gestartet.
4.2.2 Bildproduzenten und Bildkonsumenten
Bildproduzenten sind die Quellen für Bilddateien. Bildkonsumenten sind Objekte, die
Bilder verwenden248. In Java ist die Aufgabe von Bildproduzenten bzw.
Bildkonsumenten durch die Schnittstelle ImageProducer und ImageConsumer
abgebildet.
Das Interface ImageProducer beschreibt Methoden zum Bereitstellen von Pixeln
eines Bildes. Klassen, die die Schnittstelle implementieren, stellen Bildinformationen
einer speziellen Quelle dar. So ist die Klasse MemoryImageSource eine
vorgefertigte Klasse, die ImageProducer implementiert. Sie produziert
Bildinformationen aus einem Array von Pixeln, die im Speicher gehalten werden.
Die Schnittstelle ImageConsumer beschreibt Methoden, die einem Objekt den
Zugriff auf die Bilddatei des Produzenten erlauben.
Das Produzenten/Konsumenten-Modell verwendet eine ColorModel-Klasse. Bilder,
die zwischen Produzenten und Konsumenten ausgetauscht werden, werden aus
Datenfeldern von Ganzzahlen erstellt. Jede Ganzzahl steht für die Farbe eines
Pixels. Die ColorModel-Klasse verfügt über Methoden zum Herausziehen von roten,
grünen, blauen und „alpha“-Komponenten.
Die „alpha“-Komponente steht für die Transparenz einer Farbe. Ein „alpha“-Wert von
255 bedeutet, daß die Farbe vollkommen lichtundurchlässig ist. Ein „alpha“-Wert von
0 zeigt an, daß die Farbe vollständig transparent ist.
Das Standard-Farbmodell ist das RGB-Default-Modell mit dem die 4
Farbkomponenten in eine Form „0xaarrggbb“ gebracht werden. Die 8 Bit ganz links
sind der alpha-Wert, die nächsten 8 Bit sind die „Rot“-Komponente, danach kommt
die „Grün“-Komponente und zum Schluß kommen 8 Bits für Blau.
Bsp.: Eine Farbe von „0x12345678“ würde eine „alpha“-Komponente von 0x12
(ziemlich transparent) haben, eine Rotkomponente von 0x34, eine Grünkomponente
von 0x56 und eine Blaukomponente von 0x78.
248
typischerweise einfache Zeichenroutinen, die das Bild auf dem Bildschirm anzeigen.
276
Programmieren in Java
4.2.3 Bildfilter
ImageFilter liegen zwischen Produzenten und
Bildinformationen und nehmen Einfluß auf die Größe.
Konsumenten,
verändern
Subklassen der Klasse ImageFilter:
BufferedImageFilter. Mit diesem Filter läßt sich ein Objekt vom Typ BufferedImageOp
übergeben, mit dem unterschiedliche Manipulationen ermöglicht werden. BufferedImageOp ist eine
Schnittstelle, die von AffineTransformOp, ConvolveOp, LookupOp implementiert wird.
CropImageFilter. Bildteile werden herausgeschnitten.
ReplicateScaleFilter. Zum Vergrößern / Verkleinern von Bildern.
RGBImageFilter. Dieser allgemeine Filter ist für eine eigene, um RGBFilter erweiterte Filterklasse
gedacht.
Bilder skalieren mit getScaledInstance()
Die Methode public
Image
getScaledInstance(int
width,int
height,int hints) liefert ein skaliertes Bild mit den neuen Ausmaßen width
und height, hints gibt einen Skalierungsfaktor als Konstante an.
getScaledInstance() greift auf die Filterklassen AreaAveragingScaleFilter
und ReplicateScaleFilter zu. Sie berechnen jeweils das neue Bild über den
Bildproduzenten. Bei einer Vergrößerung werden die Pixel einer Zeile und Spalte
einfach verdoppelt. Bei einer Verkleinerung werden einfach Reihen und Spalten
weggelassen249.
Beim Vergrößern oder Verkleinern kommt es zu Pixelfehlern und die Frage, wie Pixel
vergrößert werden, beeinflusst das Endergebnis und die Geschwindigkeit..
getScaledInstance() verlangt nicht nur Breite und Höhe, sondern auch eine
Konstante für die Art der Skalierung. Der Skalierungsparameter bestimmt den
Algorithmus.
Skalierungsparameter
SCALE_DEFAULT
SCALE_FAST
SCALE_SMOOTH
SCALE_REPLICATE
SCALE_AREA_AVERAGING
verwendet einen Standard-Skalierungs-Algorithmus
verwendet einen Skalierungs-Algorithmus, der mehr Wert auf
Geschwindigkeit als auf Glätte des Bildes legt
verwendet einen Algorithmus mit guter Bildqualität und legt wenig
Wert auf Geschwindigkeit
benutzt für den Skalierungs-Algorithmus den ReplicateScaleFilter
verwendet den AreaAveragingScaleFilter
Abb.: Skalierungsparameter für getScaledInstance()
Bsp.250:
import
import
import
import
import
public
{
java.awt.*;
java.awt.event.*;
java.awt.image.*;
java.applet.*;
javax.swing.*;
class ScaledImage extends Applet
249
Mit einem AreaAveragingScaleFilter erhält man die besseren Resultate, da Pixel nicht einfach kopiert
werden, sondern eingefügte Pixel aus der Mittelwertberechnung bestimmt werden. Der Algorithmus heißt auch
nearest neighbor algorithm.
250 pr42300
277
Programmieren in Java
private Image originalImage;
private Image scaledImage;
public void init()
{
originalImage = new ImageIcon("B01000800.jpg").getImage();
int prozent = 50;
scaledImage = originalImage.getScaledInstance(
(originalImage.getWidth(this) * prozent)/100,
(originalImage.getHeight(this)*prozent)/100,
Image.SCALE_SMOOTH );
}
public void paint(Graphics g)
{
g.drawImage(scaledImage ,5,0,this);
}
}
Bildteile ausschneiden mit dem CropImageFilter
Für die Anwendung eines Filters wird die Klasse FilteredImageSource
herangezogen. Im Konstruktor von FilteredImageSource wird das Bild und der
Filter angegeben.
Bsp.:251 Das folgende Applet nimmt das Bild aus der vorstehenden Abbildung und
setzt ihm ein CropImageFilter auf, damit es nur einen Teil des Bildes anzeigt.
import
import
import
import
import
public
{
251
java.awt.*;
java.awt.event.*;
java.awt.image.*;
java.applet.*;
javax.swing.*;
class ScaledCropImage extends Applet
pr42300
278
Programmieren in Java
private Image originalImage;
private Image scaledImage;
private Image croppedImage;
private ImageFilter cropFilter;
public void init()
{
originalImage = new ImageIcon("B01000800.jpg").getImage();
int prozent = 50;
scaledImage = originalImage.getScaledInstance(
(originalImage.getWidth(this) * prozent)/100,
(originalImage.getHeight(this)*prozent)/100,
Image.SCALE_SMOOTH );
cropFilter = new CropImageFilter(0,0,240,200);
croppedImage = createImage(new
FilteredImageSource(scaledImage.getSource(),cropFilter));
}
public void paint(Graphics g)
{
g.drawImage(croppedImage ,3,0,this);
}
}
Manipulation über Farbfilter
Die folgende Filterklasse ist abgeleitet von RGBImageFilter. Sie umfaßt
filterRGB() und zwingt einem Bild einen Grauschleier auf.
class GrayFilter extends RGBImageFilter
{
public GrayFilter() {
canFilterIndexColorModel = true;
}
}
public int filterRGB(int x, int
int a = rgb & 0xff000000;
int r = (((rgb & 0xff0000) +
int g = (((rgb & 0x00ff00) +
int b = (((rgb & 0x0000ff) +
return a | r | g | b;
}
y, int rgb) {
0x180000)/3) & 0xff0000;
0x018000)/3) & 0x00ff00;
0x000180)/3) & 0x0000ff;
279
Programmieren in Java
Bsp.252: Das folgende Applet benutzt das Bild der vorstehenden Abbildung und färbt
es mit Hilfe der vorstehenden Filterklasse GrayFilter grau ein.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.applet.*;
import javax.swing.*;
public class ScaledCropGrayImage extends Applet
{
private Image originalImage;
private Image scaledImage;
private Image croppedImage;
private Image grauImage;
private ImageFilter cropFilter;
private ImageFilter grauFilter;
public void init()
{
originalImage = new ImageIcon("B01000800.jpg").getImage();
int prozent = 50;
scaledImage = originalImage.getScaledInstance(
(originalImage.getWidth(this) * prozent)/100,
(originalImage.getHeight(this)*prozent)/100,
Image.SCALE_SMOOTH );
cropFilter = new CropImageFilter(0,0,240,200);
croppedImage = createImage(new
FilteredImageSource(scaledImage.getSource(),cropFilter));
grauFilter = new GrayFilter();
ImageProducer erzeuger = new
FilteredImageSource(croppedImage.getSource(),grauFilter);
grauImage = this.createImage(erzeuger);
}
public void paint(Graphics g)
{
g.drawImage(grauImage,3,0,this);
g.drawImage(croppedImage ,250,0,this);
}
}
252
vgl. pr42300
280
Programmieren in Java
4.2.4 Kopieren von Speicher in ein Bild bzw. von Bildern in einen Speicher
4.2.4.1 Kopieren von Speicher in ein Bild
Ein möglicher Typ für das Produzieren von Bildern ist ein Datenfeld mit ganzen
Zahlen, die für die Farbe eines jeden Pixels stehen. Möglich wird das durch die
Klasse MemoryImageSource.
Bsp.: Das folgende Applet erzeugt ein Speicherbild, eine MemoryImageSource und
zeichnet das Bild im Zeichenbereich.
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
/* das Applet zeichnet ein Image und benutzt dazu
einen Array mit Pixeln
*/
public class SpeicherBild extends Applet
{
private final static int b = Color.blue.getRGB();
private final static int r = Color.red.getRGB();
private final static int g = Color.green.getRGB();
int pixels[] = {
b, b, b, b, b, b, b, b, b, b,
b, b, b, b, b, b, b, b, b, b,
b, b, g, g, g, g, g, g, b, b,
b, b, g, g, g, g, g, g, b, b,
b, b, g, g, r, r, g, g, b, b,
b, b, g, g, r, r, g, g, b, b,
b, b, g, g, g, g, g, g, b, b,
b, b, g, g, g, g, g, g, b, b,
b, b, b, b, b, b, b, b, b, b,
b, b, b, b, b, b, b, b, b, b
};
Image meinBild;
public void init()
{
/* Erzeigen des Bilds aus dem Array pixels.
Die Pixels werden zeilenweise von Postion 0
aus dem Array gelesen. eine Zeile umfasst 10
Postionen.
*/
meinBild = createImage(
new MemoryImageSource(10,10,pixels,0,10));
}
public void paint(Graphics g)
{
// Zeichen das Bild.
// Breite und Hoehe des Bilds werden 10fach vergroessert
g.drawImage(meinBild,0,0,100,100,this);
}
}
281
Programmieren in Java
Abb.: Darstellung von SpeicherBild
4.2.4.2 Kopieren von Bildern in einen Speicher
Die PixelGrabber-Klasse nimmt ein Bild und macht aus diesem ein Datenfeld mit
ganzen Zahlen. Ein PixelGrabber wird auf ein Image-Objekt aufgesetzt und füllt ein
Ganzzahl-Feld mit den Farbwerten, die die Anteile der Farben rot, grün und blau
enthalten.
Der PixelGrabber ist für Modifikationen bereits existierender Bilder nützlich.
class java.awt.image.PixelGrabber implements ImageConsumer
Konstruktor.
public PixelGrabber(Image img, int x, int y, int breite, int hoehe, int
feld[], int verschiebung, int scanSize)
erzeugt ein PixelGrabber-Objekt, das ein Rechteck von RGB-Farben aus dem Feld holt. Das
Rechteck ist durch die Maße x, y, breite, hoehe beschrieben. Die Farbe für einen Punkt (i,j) sind im
Feld an der Position (j-y)*scanSize+(i-x)+verschiebung. Mit der Umwandlung wird noch
nicht begonnen. Sie muß mit der Funktion grabPixels() angeregt werden.
Methoden.
public boolean grabPixels() throws InterruptedException
Die Werte von einem Image oder ImageProducer werden geholt. Die Funktion kann von außen
unterbrochen werden.
public int getHeight()
liefert die Höhe des Pixelfelds. Ist die Höhe nicht verfügbar, dann ist das Ergebnis –1.
public int getWidth() liefert die Breite des Pixelfelds.
Bsp.: Das nachfolgende Bild lädt ein Bild und gibt die Farbinformationen auf der
Konsole aus.
import
import
import
import
javax.swing.*;
java.awt.*;
java.awt.event.*;
java.awt.image.*;
public class PR42400 extends Frame
{
Image bild;
int breite, hoehe;
int pixels[];
public PR42400()
{
282
Programmieren in Java
/*
bild = Toolkit.getDefaultToolkit().getImage(
"d:\\jdk1.3.1\\pgj\\progr\\pr42400\\B01000800.jpg");
*/
bild = new ImageIcon("B01000800.jpg").getImage();
if (bild == null) System.out.println("Kein Bild");
breite = bild.getWidth(this);
System.out.println(breite);
hoehe = bild.getHeight(this);
pixels = new int [breite * hoehe];
PixelGrabber grabber = new PixelGrabber(bild,0,0,breite,hoehe,
pixels,0,breite);
try {
grabber.grabPixels();
}
catch(InterruptedException e)
{
System.err.println("Fehler beim Holen der Pixel");
}
setSize(breite,hoehe);
repaint();
addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent m)
{
int pixel = pixels[breite * m.getY() + m.getX()];
int alpha = (pixel >> 24) & 0xff;
int rot
= (pixel >> 16) & 0xff;
int gruen = (pixel >> 8) & 0xff;
int blau = (pixel)
& 0xff;
System.out.println("R=" +rot+ " G=" +gruen+ "B=" +blau);
}
});
}
public void paint(Graphics g)
{
if (bild != null)
g.drawImage(bild,0,0,this);
}
public static void main(String args[])
{
Frame f = new PR42400();
f.show();
}
}
283
Programmieren in Java
4.3 Java 2D
Überblick
Java 2D ist Bestandteil der Java Foundation Classes (JFC)253, ein Teil dessen, was
Sun Swing nennt und sämtliche Techniken umfaßt, die die GUI-Fähigkeit von Java
ausmachen. Das neue Java2D-API ist ein Satz von Klassen für erweiterte 2D-Grafik
und die Bildverarbeitung. Java 2D unterstützt:
-
Shapes (Formen): Linien, Polygonzüge, Rechtecke, Ellipsen, parametrische Kurven, etc.
Strokes (Stricharten): Striche verschiedener Dicke und Farbe zum Zeichnen von Shapes,
ausgezogen oder aus Punkten, etc., zusammengesetzt sptze oder abgerundete Ecken.
Filling (Ausfüllen): geschlossene Polygonzüge oder Kurven bilden Figuren, die mit Farben,
Mustern, Texturen, etc. (Paints) ausgefüllt werden können.
Antialiasing: Geometrische Formen ergeben auf dem Rasterschirm oft "unschöne Treppeneffekte"
(Aliasing). Mit Antaliasing-Techniken können diese Effekte i. allg. gemildert werden.
Images (Rasterbilder), z.B. eingescannte Fotografien.
Text: Unterstützung verschiedener Zeichensätze (Fonts) und Darstellungsarten (Text Layout).
Transformations: Translation (Verschieben), Rotation, Skalierung (Vergrössern, Verkleinern),
Verzerrung, ets. von Grafiken, Text und Bildern (Images)
Clipping (Wegschneiden, Kappen): z.B. hat in einem Fenster nur ein Teil einer Grafik Platz, dann
wird der Rest entfernt ("weggeschnitten").
Composites: Bilder, Figuren, etc. werden übereinander gelegt. Wie werden die einzelnen
schichten gewichtet, z.B. bzgl. der Durchsichtigkeit (Transparenz)?
Color: Einsatz von Farben, verschiedene Farbmodelle verwendbar.
Image Processing (Bildverarbeitung): z.B. Verschärfen, Farbkorrekturen, etc.
Printing (Drucken): Ausgabe aller Java 2D Grafiken auf einem Drucker, Steuerungsmöglichkeiten,
etc.
Rendering Modell
Der Kern von Java 2D ist die Klasse java.awt.Graphics2D. Sie ist eine Erweiterung
der AWT-Klasse und stellt die Implementierung des Rendering-Modells von Java 2D
dar.
Wie wird Grafik auf ein Ausgabegerät (Bildschirm, Drucker, Puffer) abgebildet /
übertragen?
1. Jedes Graphics2D-Objekt ist mit einem Ausgabeziel verknüpft, das festlegt, wohin gerendert wird.
Die Eigenschaften des Ausgabeziels, wie z.B. Pixelformat und Auflösung werden durch ein
GraphicsConfiguration-Objekt beschieben
2. Rendering:
(a) Durch Setzen von Attributen wird der grafische Kontext254 festgelegt.
(b) Das darzustellende Grafikobjekt (Shape, Text oder Bild) wird ausgewählt.
(c) Das Rendering wird durchgeführt.
Da die meisten Methoden den Zugriff auf ein allgemeines Graphics-Objekt liefern ist
eine Typumwandlung (Cast) nach Graphics2D nötig.
253 JFC ist eine Erweiterung des Abstract Windowing Toolkit (AWT): AWT = UIToolkit (Swing) + Drawing
Toolkit (2D API)
254 Der grafische Kontext kann durch die folgende Attribute festgelegt werden: Font (Schriftart), Stroke
(Strichart), Paint (Füllfarbe, Textur), Composite (Vermischen von mehreren Grafikobjekten), Rendering Hints
(Antialiasing, Dithering, etc.), Clipping (Beschränkung des Rendering auf einen bestimmten Ausschnitt),
Transform (Affine Transformation)
284
Programmieren in Java
Graphics
{ abstract }
Graphics2D
{ abstract }
Stroke stroke
// Definition von Linienstiften
Paint paint
// Erweiterung des Farbmodells
AffineTransform transform // erlaubt Veränderungen der Form
Composite composite
// gibt an, wie Objekte verschiedener Farben überlagert werden
// sollen, erlaubt weiche Farbübergänge (Anti-Aliasing)
<< Konstruktor >>
protected Graphics2D()
<< Methoden >>
public abstract void addRenderingHints(Map hints)
public abstract void clip(Shape s)
public abstract draw(Shape s)
public abstract void drawImage(Image img, AffineTransform xForm, ImageObserver obs)
public abstract void fill(Shape s)
public abstract GraphicsConfiguration getDeviceConfiguration()
public abstract FontRenderContext getFontRenderContext()
public abstract Paint getPaint()
public abstract Object getRenderingHint(RenderingHints.Key hintKey)
public abstract Stroke getStroke()
public abstract AffineTransform getTransform()
public abstract void rotate(double theta)
public abstract void scale(double sx, double sy)
public abstract void setComposite(Composite comp)
public abstract setPaint(Paint paint)
public abstract void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
public abstract void setTransform(AffineTransform Tx)
public abstract void transform(AffineTransform Tx)
public abstract void translate(double tx, double ty)
public abstract void translate(int x, int y)
...
Abb.: Die Klasse Graphics2D
4.3.1 Das Zeichnen unter Java2D API
Zeichnen unter dem AWT Drawing Model
Dieses Modell wurde bisher zum Zeichnen verwendet, z. B.:
import java.awt.*;
import java.applet.*;
public class MaleGefRechteck extends Applet
{
// Zeichnen unter dem AWT Drawing Model
public void paint(Graphics g)
285
Programmieren in Java
{
g.setColor(Color.red);
g.fillRect(300,300,200,100);
}
}
Die Vorgehensweise beim Bearbeiten für jede Komponente kann aus diesem Bsp.
unmittelbar abgeleitet werden:
1. Spezifikation der notwendigen Atribute für die zu zeichnende Form, z.B. die Farbe mit „setColor“.
2. Definition der zu zeichnenden Form
3. Festlegen des genauen Aussehens der Form, z.B. über eine passende Grafikmethode.
Das AWT biete einfache grafische Funktionen an. Diese AWT-Funktionen besitzen
aber
- einen niedrigen Abstraktionsgrad und keine objektorientierte Repräsentation von grafischen
Objekten.
- diskrete Fenster- statt kontinuierlicher Weltkoordinaten
- ein "schwaches" Modell zur Abbildung von Farben und Farbverläufen.
Zeichnen mit Java2D
Das Java2D API umfaßt zusätzliche unterstützende Features zur Spezifizierung von
Zeichenstiften, komplexen Formen und diversen Zeichenprozessen. Zur Nutzung
dieser Möglichkeit muß der Graphics-Parameter der paint()-Methode in ein
Graphics2D-Objekt gebracht werden („gecastet“) werden, z.B.:
import java.awt.*;
import java.applet.*;
import java.awt.geom.*;
public class MaleGefRechteck extends Applet
{
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
// Spezifikation der Attribute
g2d.setColor(Color.red);
// Definition der Form (Verwende Even-Odd-Regel)
GeneralPath path = new GeneralPath();
path.moveTo(300.0f,400.0f); // untere linke Ecke
path.lineTo(500.0f,400.0f); // untere rechte Ecke
path.lineTo(500.0f,300.0f); // obere rechte Ecke
path.lineTo(300.0f,300.0f); // obere linke Ecke
path.closePath();
// Schliessen des Rechtecks
// Fuellen der Form
g2d.fill(path);
}
}
Eine Polygon-Klasse wie unter dem AWT gibt es in dem 2D-API nicht. Hier wird ein
neuer Weg eingeschlagen, der über die Klasse GeneralPath geht. Damit lassen
sich beliebige Formen bilden. Dem Pfad werden verschiedene Punkte hinzugefügt,
die dann verbunden werden. Die Punkte müssen nicht zwingend, wie bei einem
Polygon mit Linien verbunden werden, sondern lassen sich auch durch quadratische
oder kubische Kurven verbinden. Da Graphics2D eine Unterklasse von Graphics ist,
lassen sich natürlich alle AWT-Operatoren weiter verwenden.
286
Programmieren in Java
Bsp.: Aufbau eines "roten Polygons" aus einem Pfad255
import java.awt.*;
import java.applet.*;
import java.awt.geom.*;
public class PolyRot extends Applet
{
public void paint(Graphics g)
{
// Erzeuegen eines Graphics2D-Objekt
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
// Definition der Form
GeneralPath einPfad = new GeneralPath();
// Startpunkt festlegen (Ursprung der sichtbaren
// Zeichenflaeche des Fensters
einPfad.moveTo(getInsets().left,getInsets().top);
for (int i = 1, j = 10;i <= 10; i++, j--)
{
einPfad.lineTo(getSize().width/j,getSize().height/i);
einPfad.lineTo(getSize().width/j,getSize().height/j);
}
// der Pfad wird geschlossen
einPfad.closePath();
// Zeichnen (blau) des Umrisses vom PfadObjekt
// und Fuellen mit rot
g2d.setColor(Color.blue);
g2d.draw(einPfad);
g2d.setColor(Color.red);
g2d.fill(einPfad);
}
}
Abb.: Ein rotes Polygon
Im einfachsten Fall erzeugt man einen Pfad, indem man ein Objekt der Klasse
GeneralPath instanziert und festlegt, welche Punkte zu diesem Pfad gehören
sollen. Dies kann durch einen Aufruf der Methoden von GeneralPath geschehen.
Man verfolgt einen virtuellen Pfad in der Zeichenfläche und generiert dabei die
Punkte, die zum Pfad gehören sollen.
255
vgl.PR43100
287
Programmieren in Java
Verfahrensweise beim Zeichnen mit den Java 2D-API-Klassen
1. Spezifikation der notwendigen beschreibenden Attribute
2. Definition der Form, eines Textstrings oder eines Bilds.
Das Java2D-API behandelt Positionsangeben (Pfade), Texte und Bilder gleichartig.
Sie können rotiert, skaliert, verzerrt und mit diversen Methoden zusammengesetzt
werden. Das „Shape“-Interface definiert einen ganzen Satz von Methoden zur
Beschreibung von geometrischen PATH-Objekten. GeneralPath ist eine
Implementation vom Shape-Interface, das zur Definition von beliebig komplexen
Formen (zusammengestzt aus Linien- und Kurvensegmenten) verwendet werden
kann.
<< interface >>
java.awt.Shape
java.awt.Polygon
java.awt.geom.Line2D
java.awt.geom.GeneralPath
java.awt.geom.Area
java.awt.geom.RectangularShape
java.awt.geom.Arc2D
java.awt.geom.Ellipse2D
java.awt.geom.Rectangle2D
Abb.: das Interface Shape und implementierende Klassen
draw() aus der Klasse Graphics2D nimmt ein Shape-Objekt und zeichnet es.
Shape-Objekte sind Linien, Polygone oder auch Kurven.
abstract public void draw(Shape s) zeichnet die Forrm im aktuellen
Graphics2D-Kontext.
Bsp.: Ein Kreis innerhalb von einem Kasten.256
import
import
import
import
javax.swing.*;
java.awt.*;
java.awt.event.*;
java.awt.geom.*;
public class ShapeBsp extends JPanel
{
private Ellipse2D.Double kreis
= new Ellipse2D.Double(10,10,350,350);
private Rectangle2D.Double quadrat
= new Rectangle2D.Double(10,10,350,350);
public void paintComponent(Graphics g)
{
clear(g);
Graphics2D g2d = (Graphics2D) g;
256
vgl. PR43100
288
Programmieren in Java
g2d.fill(kreis);
g2d.draw(quadrat);
}
protected void clear(Graphics g)
{
super.paintComponent(g);
// loescht die Pixelmap des Bildschirms
// per Default gibt es Double Buffering
}
protected Ellipse2D.Double getCircle()
{
return (kreis);
}
public static void main(String[] args)
{
ShapeBsp jp = new ShapeBsp();
String titel = jp.getClass().toString();
if (titel.indexOf("class") != -1)
titel = titel.substring(6);
JFrame frame = new JFrame(titel);
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
frame.getContentPane().add(jp,BorderLayout.CENTER);
frame.setSize(380,400);
frame.setVisible(true);
}
}
3. Festlegen vom Aussehen der Form, des Textstrings oder des Bildes über
passende Graphics2D-Methoden.
289
Programmieren in Java
290
Programmieren in Java
Geometrische Objekte
Java2D unterscheidet folgende geometrische Objekte:
- Shapes: beliebige offene oder geschlossene Figuren, zusammengesetzt aus Folgen von Lininen und
Kurvensegmenten.
- Text: Text in vorhert festgelegtem Zeichensatz (Zeichen sind als Shapes definiert)
- Image (rechteckiger Ausschnitt einer Bitmap).
Jedem geometrischen Objekt (Punkt, Klasse, Rechteck) ist ein Java-Klasse
zugeordnet. Das konkrete Aussehen des Objekts (Farbe, Umriss) wird erst bei der
Darstellung festgelegt. Die Darstellung erfolgt über einen Rasterisierer257, der
Kompositionsvorschriften
für
kontinuierliche
geometrische
Objekte
in
Weltkoordinaten über ein diskretes Raster umsetzt.
Allgemeine Vorgehensweise
1. Setzen von Graphikattributen (rendering attributes) wie Linientyp, Füllmuster (auch
komplexe Gradienten).
2. Definition der Form, einer Zeichenkette (auszugebender Text) oder eines Bilds.
3. Anwendung einer Transformation (z.B. Bild verzerren oder Filtern, Text drehen
etc.).
4. Ausgabe eines graphischen Elements (z.B. durch die Graphics2D-Methoden
draw(), drawImage(), drawRectangle() oder fill().
Die eigentliche Ausgabe erfolgt in 4 Schritten:
1. Das Objekt wird in Grafikprimitive umgewandelt und auf den Koordinatenraum des Ausgabegerätes
(device space) abgebildet.
- Handelt es sich um eine Form (Shape), so werden die in der Form enthaltenen Graphikelemente
bestimmt.
- Handelt es sich um einen Text, wird die Form der Buchstaben (Glyphen) aus der Schriftinformation
bestimmt und in einen Umriß (outline) umgewandelt, der sich als Shape-Objekt beschreiben lässt.
- Bei Bildern (images) wird ihre Bounding Box in Device-Koordinaten umgewandelt (unter
Verwendung der im Programm angegebenen Transformationen des Graphics2D-Kontextes)
2. Der aktuelle clipping path schränkt die Darstellungsoperation (rendering operation) ein. Ein
clipping path kann jede Form annehmen, die sich durch ein Shape-Objekt beschreiben lässt.
Normalerweise handelt es sich um den Bereich der gerade tatsächlich gezeichnet werden kann (z.B.
sichtbare Fläche eines Fensters).
3. Die Ausgabefarbe wird bestimmt (aus den Bilddaten bei Bildern, aus dem aktuellen Paint- oder
Color-Objekt in allen anderen Fällen.
4. Die Farbe wird aus das auszugebende Objekt angewandt.
Java 2D API-Klassen258
Außer der Klasse java.awt.Graphics2D umfaßt Java 2D API Klassen in den
Paketen java.awt.geom (geometrische Formen, Pfade und Transformationen),
257
Die kontinuierlichen Shapes (z.B. ein Kreis) werden vom Rasterisierer in diskrete Rasterbelegungen
umgewandelt. Man unterscheidet Rasterisierung mit oder ohne Aliasing:
258 pr43210
291
Programmieren in Java
java.awt.font, java.awt.color (Farbräume), java.awt.image und
java.awt.image.renderable (Bitmaps und Filter) sowie java.awt.print
(Drucken).
Die Klassen für geometrische Objekte, die sich von der Klasse Shape ableiten, sind
Polygon, RectangularShape, Rectangle, Area, QuadCurve2D und
CubicCurve2D259. Eine besondere Klasse, die auch von Shape abgeleitet ist, heißt
GeneralPath. Damit lassen sich mehrere Objekte einer Figur zusammensetzten.
Die Klasse Rectangle2D, RoundRectangle2D, Aread2D und Ellipse2D erben
von der Klasse RectangularShape und sind Objekte, die durch eine rechteckige
Box umgeben sind.
4.3.2 Eigenschaften geometrischer Objekte
Dicke und Art der Linien bestimmen
Die Methode setStroke() kann definieren
- die Dicke (width)
- die Eigenschaft wie ein Liniensegment beginnt und abschließt
- die Art, wie sich die Linien verbinden
- ein Linien-Pattern (dash attributes)
Unterstützt wird setStroke() durch die Schnittstelle Stroke, die konkret durch
BasicStroke implementiert wird, z.B.
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
BasicStroke pen =
new BasicStroke(2.0f,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND)
g2.setStroke(pen);
}
Für BasicStroke gibt es 9 Konstruktoren. Der hier verwendete Konstruktor von
BasicStroke har drei Argumente:
- einen float-Wert, der die Linienstärke angibt
- einen int-Wert, der die Art des Linienendes festlegt
- einen int-Wert, der den Stil des Verbindungsstücks zwischen zwei Liniensegmenten festlegt
Besonders bei breiten Linie ist es interessant, wie die Linie endet. Hier läßt sich aus
CAP-BUTT (keine Abschlußpunkte), CAP_ROUND (Anzeige von Kreisen an beiden
Enden) und CAP_SQUARE auswählen.
Die möglichen Stile für Verbindungselemente sind JOIN_MITER (Abschluß von
Linien so, daß sie senkrecht aufeinander stehen), JOIN_ROUND (Abschluß der Linien
durch abgerundete Ecken) und JOIN-BEVEL (Eine Linie wird zwischen den äußeren
Eckpunkten gezogen).
259 Beschreibung quadratischer und kubischer Kurvensegmente. Das sind Kurven, die durch zwei Endpunkte
und durch Kontrollpunkte dazwischen gegeben sind. Kubische Kurvensegmente werden auch Bézier-Kurven
genannt.
292
Programmieren in Java
Bsp260.: Das folgende Applet zeigt Verbindungselemente zwischen Linien
import java.awt.*;
import java.applet.*;
import java.awt.geom.*;
public class StrokeDemo extends Applet
{
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
BasicStroke pen1 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
g2d.setStroke(pen1);
g2d.draw(new Line2D.Float(10f,10f,210f,10f));
BasicStroke pen2 = new BasicStroke(10.0f,BasicStroke.CAP_ROUND,
BasicStroke.JOIN_MITER);
g2d.setStroke(pen2);
g2d.draw(new Line2D.Float(10f,30f,210f,30f));
BasicStroke pen3 = new BasicStroke(10.0f,BasicStroke.CAP_SQUARE,
BasicStroke.JOIN_MITER);
g2d.setStroke(pen3);
g2d.draw(new Line2D.Float(10f,50f,210f,50f));
BasicStroke pen4 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
g2d.setStroke(pen4);
g2d.draw(new Line2D.Float(10f,70f,100f,70f));
g2d.draw(new Line2D.Float(100f,70f,100f,120f));
g2d.draw(new Line2D.Float(100f,120f,10f,120f));
g2d.draw(new Line2D.Float(10f,120f,10f,70f));
BasicStroke pen5 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL);
g2d.setStroke(pen5);
g2d.draw(new Line2D.Float(120f,70f,210f,70f));
g2d.draw(new Line2D.Float(210f,70f,210f,120f));
g2d.draw(new Line2D.Float(210f,120f,120f,120f));
g2d.draw(new Line2D.Float(120f,120f,120f,70f));
BasicStroke pen6 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
g2d.setStroke(pen6);
g2d.draw(new Line2D.Float(30f,140f,10f,160f));
g2d.draw(new Line2D.Float(30f,140f,50f,160f));
BasicStroke pen7 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL);
g2d.setStroke(pen7);
g2d.draw(new Line2D.Float(90f,140f,70f,160f));
g2d.draw(new Line2D.Float(90f,140f,110f,160f));
BasicStroke pen8 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND);
g2d.setStroke(pen8);
g2d.draw(new Line2D.Float(150f,140f,130f,160f));
g2d.draw(new Line2D.Float(150f,140f,170f,160f));
float dash[] = {10, 2};
BasicStroke pen9 = new BasicStroke(2.0f,BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
1,
dash,0);
g2d.setStroke(pen9);
g2d.draw(new Rectangle2D.Float(10f,180f,50f,50f));
BasicStroke pen10 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT,
260
pr43100
293
Programmieren in Java
BasicStroke.JOIN_ROUND);
g2d.setStroke(pen10);
g2d.draw(new Rectangle2D.Float(10f,250f,60f,70f));
BasicStroke pen11 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL);
g2d.setStroke(pen11);
g2d.draw(new Rectangle2D.Float(90f,250f,60f,70f));
BasicStroke pen12 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
g2d.setStroke(pen11);
g2d.draw(new Rectangle2D.Float(170f,250f,60f,70f));
}
}
Abb.
294
Programmieren in Java
4.3.3 Koordinatensysteme und Transformation
Das Java 2D-API unterscheidet zwischen
- dem user coordinate space (Koordinatenraum des Benutzers)
und
- dem device coordinate space (Koordinatensystem des Ausgabegeräts)
Zu den möglichen Transformationen gehören
- die Drehung (rotate(), setToRotation())
- die Skalierung (scale(), setToScale())
- die Verschiebung (translate(), setToTranslation())
- die Verzerrung (shear(), setToShear())
Das folgende Beispiel261 zeigt ein Rechteck, das zunächst im Ursprung des
Koordinatensystems (Linke obere Ecke) gezeichnet wird. Anschließend wird der
Koordinatenraum durch sukzessive Anwendung von Transformationen
- verschoben
- verschoben und gedreht
- verschoben, gedreht und skaliert
- verschoben, gedreht, skaliert und verzerrt
import
import
import
import
javax.swing.*;
java.awt.*;
java.awt.event.*;
java.awt.geom.*;
public class TransformBsp extends JPanel
{
public void paintComponent(Graphics g)
{
clear(g);
Graphics2D g2d = (Graphics2D) g;
Rectangle einRechteck =
new Rectangle(getInsets().left,
getInsets().top,50,50);
g2d.draw(einRechteck);
// neues Transformationsmodell erzeugen
AffineTransform dieTransformation = new AffineTransform();
// Koordinatenraum um 50 x-, 50 y-Einheiten verschieben
dieTransformation.translate(50.0,50.0);
g2d.transform(dieTransformation);
g2d.setColor(Color.lightGray);
g2d.fill(einRechteck);
// Koordinatenraum um 30 Grad gegen der Uhrzeigersinn drehen
dieTransformation.rotate(-Math.PI/12.0);
g2d.transform(dieTransformation);
g2d.setColor(g2d.getColor().darker());
g2d.fill(einRechteck);
// Koordinatenraum um die Faktoren 1.33 * x, 0.66 * y skalieren
dieTransformation.scale(1.33f,0.66f);
g2d.transform(dieTransformation);
g2d.setColor(Color.black);
g2d.fill(einRechteck);
// Koordinatensystem um die Faktoren 0.1 * x, 0.1 * y verzerren
261
vgl. pr43100
295
Programmieren in Java
dieTransformation.shear(0.1f,0.1f);
g2d.transform(dieTransformation);
g2d.setColor(Color.black);
g2d.fill(einRechteck);
}
protected void clear(Graphics g)
{
super.paintComponent(g);
// loescht die Pixelmap des Bildschirms
// per Default gibt es Double Buffering
}
public static void main(String[] args)
{
TransformBsp tbsp = new TransformBsp();
String titel = tbsp.getClass().toString();
if (titel.indexOf("class") != -1)
titel = titel.substring(6);
JFrame frame = new JFrame(titel);
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
frame.getContentPane().add(tbsp,BorderLayout.CENTER);
frame.setSize(380,250);
frame.setVisible(true);
}
}
Abb.: Transformation eines Rechtecks.
Nach jeder Transformation wird das Rechteck neu gezeichnet.
Affine Transformationen. Sie können durch einfache Transformationsmatrizen
beschrieben werden, z.B. Translation eines Punktes (x,y) um (dx,dy):
 1 0 dx   x   x + dx 

   

 0 1 dy  ⋅  y  =  y + dy 
0 0 1   1  1 

   

296
Programmieren in Java
Bsp.262: Das Haus des Nikolaus.
Gegeben ist die folgende paint()- Methode
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
// Definition der Form (Verwende Even-Odd-Regel)
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
path.moveTo(-25,50);
path.lineTo(-25.0f,0.0f); path.lineTo(0.0f,-30.0f);
path.lineTo(25.0f,0.0f);
path.lineTo(-25.0f,0.0f);
path.lineTo(25.0f,50.0f);
path.lineTo(-25.0f,50.0f);
path.lineTo(25.0f,0.0f);
path.lineTo(25.0f,50.0f);
path.closePath();
// Schliessen
g2d.draw(path);
}
Die Implementierung dieser paint()-Methode zeigt das folgende Fenster an:
Eine Translation um (100,100) führt zu dem folgenden Fenster:
Diese Form wurde durch Implementieren der folgende paint()-Methode erreicht:
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
// Definition der Form (Verwende Even-Odd-Regel)
262
pr43100
297
Programmieren in Java
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
path.moveTo(-25,50);
path.lineTo(-25.0f,0.0f); path.lineTo(0.0f,-30.0f);
path.lineTo(25.0f,0.0f);
path.lineTo(-25.0f,0.0f);
path.lineTo(25.0f,50.0f);
path.lineTo(-25.0f,50.0f);
path.lineTo(25.0f,0.0f);
path.lineTo(25.0f,50.0f);
path.closePath();
// Schliessen
// Transformation
AffineTransform at = new AffineTransform();
at.setToTranslation(100.0f,100.0f);
g2d.transform(at);
g2d.draw(path);
}
Zusätzlich zur Translation soll das Haus noch um 45 Grad im Uhrzeigersinn gedreht werden. Dabei
muß unterschieden werden zwischen dem Setzen einer einer Affinen Transformation und dem
Erweitern einer Affinen Transformation.
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
// Definition der Form (Verwende Even-Odd-Regel)
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
path.moveTo(-25,50);
path.lineTo(-25.0f,0.0f); path.lineTo(0.0f,-30.0f);
path.lineTo(25.0f,0.0f);
path.lineTo(-25.0f,0.0f);
path.lineTo(25.0f,50.0f);
path.lineTo(-25.0f,50.0f);
path.lineTo(25.0f,0.0f);
path.lineTo(25.0f,50.0f);
path.closePath();
// Schliessen
// Transformation
AffineTransform at = new AffineTransform();
// Setzen der affinen Transformation
at.setToTranslation(100.0f,100.0f);
// Erweitern der affinen Transformation
at.rotate(Math.PI/4);
g2d.transform(at);
g2d.draw(path);
}
Die Geometrie vom Haus des Nikolaus soll vergrößert werden.
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
// Definition der Form (Verwende Even-Odd-Regel)
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
298
Programmieren in Java
path.moveTo(-25,50);
path.lineTo(-25.0f,0.0f); path.lineTo(0.0f,-30.0f);
path.lineTo(25.0f,0.0f);
path.lineTo(-25.0f,0.0f);
path.lineTo(25.0f,50.0f);
path.lineTo(-25.0f,50.0f);
path.lineTo(25.0f,0.0f);
path.lineTo(25.0f,50.0f);
path.closePath();
// Schliessen
// Transformation
AffineTransform at = new AffineTransform();
// Setzen der affinen Transformation
at.setToTranslation(100.0f,100.0f);
// Erweitern der affinen Transformation
at.rotate(Math.PI/4);
// Skalieren
at.scale(1.5f,1.5f);
g2d.transform(at);
// Zeichnen
g2d.draw(path);
}
Das Fenster zeigt: Die Skalierung in Java2D beeinflusst auch die Stroke- und Paint-Eigenschaften,
d.h. bei einer Vergrößerung wird auch die Liniendicke größer.
Die vollständige Transformation kann durch dir folgendeTransformationsmatrix beschrieben werden
(Kompostion von Transformationen):
 1 0 dx   cos α

 
 0 1 dy  ⋅  0
0 0 1   0

 
0
sin α
0
0   sx 0 0 
 

0  ⋅  0 sy 0  ⋅ geom _ objekt
1   0 0 1 
299
Programmieren in Java
4.3.4 Java2D-Pipeline
Java2D stellt geometische Objekte über eine "Render-"Pipeline dar:
Transformation
fill()
Shapes
draw()
Stroke
Text
drawStr()
Font
Images
drawImage()
Rasterisierer
Clipping
Hints
Rendering Hints
Paint
Composition
Output Device
Abb.: Java2D-Pipeline
Geometrische Objekte können neben ihrer Form (Shape, z.B. Rechteck) durch drei
weitere Parameter beschrieben werden: Translation, Rotation, Skalierung. Die
Anwendung dieser Parameter auf ein geometrisches Objekt wird als Transformation
bezeichnet.
Kontinuierliche Shapes werden vom Rasterisierer in diskrete Rasterbelegungen
umgewandelt. Man unterscheidet Rasterisierung mit oder ohne Aliasing:
- Aliasing: Raster-Pixel wird als belegt gekennzeichnet, wenn das Objekt meht als x% des Pixels
belegt
- Antialiasing: dem Pixel wird der Belegungsfaktor x zugewiesen und als Transparenzwert interpretiert.
Rendering Hints sind Hinweise oder Wünsche an Java2D, welche Qualität bzw.
welche Performance man gerne erreichen würde. Java2D entscheidet dann selber,
welche dieser Wünsche erfüllt werden (können).
Die Ausgabe von Java2D erfolgt typischerweise in ein Fenster. Dieses Fenster zeigt
nur einen Ausschnitt der potentiell unbegrenzten (virtuellen) Java2D-Zeichenfläche.
Gemalt werden soll natürlich nur das, was auch tatsächlich in diesem sichtbaren
Ausschnitt liegt. Dieses Zuschneiden auf einen Bereich wird als Clipping bezeichnet.
Java2D erlaubt neben dem normalen Clipping auf einem rechteckigen (Fenster-)
Bereich auch das Clipping auf beliebige Java2D-Flächen.
Im Paint-Schritt werden Farbe und Transparenz der Pixel festgelegt. Dazu gehört
u.a.:
- die Ausprägung der Umrisslinie (durchgehend, gestrichelt, …)
- die Art der Füllung (feste Farbe, Farbverlauf, Muster, Bitmap, …)
Falls ein Image gemalt werden soll, entfällt dieser Schritt, da alle Informationen
implizit im Image enthalten sind.
300
Programmieren in Java
Java2D erweitert das Hinzufügen von Objekten um sog. Kompostionsregeln
(bekannt als Porter-Duff-Regeln). Neben der Porter-Duff-Komposition, die ein
Source-Objekt mit der gesamten Zeichenfläche verknüpft, gibt es noch die sog.
CAG-Komposition (Constructive Area Geometry). CAG verknüpft jeweils 2 (CAG-)
Objekte zu einem neuen CAG-Objekt.
4.3.5 Farbmanagement unter Java 2D
Die Klasse Color mit dem Interface Paint
Ein Objekt der Klasse Color stellt eine Farbe im RGB-Farbschema dar. Color
implementiert das neue Interface Paint (Zeichenfarbe).
<< interface >>
Paint
Color
GradientPaint
public static Color black;
public static Color blue;
public static Color cyan;
…
public static Color white;
public static Color yellow;
<< Konstruktoren >>
public Color(float r, float g, float b)
public Color(int rgb)
public Color(int rgba,boolean hasalppha)
public Color(int r,int g,int b)
public Color(int r,intg,int b,int a)
<< Methoden >>
public Color brighter()
public Color darker()
public boolean equals()
public int getAlpha()
public int getBlue()
…
public static Color getColor(String nm)
public static getHSBColor(float h,float s,
float b)
public int getRGB()
public int getTransparency()
public static int HSBtoRGB(float hue,
float saturation,float brightness)
public static float RGBtoHSB(int r,int g,int b,
float[] hsbwerte)
public String toString()
301
TexturePaint
Programmieren in Java
Abb.: Die Klasse Color
Farbübergänge
Java 2D erlaubt die Verwendung von Farbübergängen (Gradienten) und Mustern zur
Füllung beliebiger Formen. Dazu existieren zwei Klassen, die die Schnittstelle Paint
implementieren:
- GradientPaint (repäsentiert einen glatten Übergang von einer Farbe zu einer anderen)
- TexturePaint für Füllmuster auf der Basis einer Bilddatei, die das Muster vorgibt.
public class GradientPaint extends Object implements Paint
Konstruktoren.
public GradientPaint(float x1,float y1,Color c1,float x2,float y2,Color c2)
public GradientPaint(float x1,float y1,Color c1,float x2,float y2,
Color c2,boolean cyclic)
// c1 ist die Farbe am ersten Punkt(x1,y1), c2 ist die Farbe am zweiten Punkt (x2,y2). cyclic ist true,
// falls der Übergang zwischen den Farben wiederholt werden soll
Der aktuelle Gradient wird über setPaint() des Graphics2D-Kontextes gesetzt.
Bsp.263: Ein Kreis wird mit einem Farbübergang ausgestattet.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
public class GradientPaintBsp extends JPanel
{
private Ellipse2D.Double kreis
= new Ellipse2D.Double(10,10,350,350);
private GradientPaint gradient
= new GradientPaint(0,0,Color.red,175,175,Color.yellow,true);
public void paintComponent(Graphics g)
{
clear(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setPaint(gradient);
g2d.fill(getCircle());
g2d.setPaint(Color.black);
g2d.draw(getCircle());
}
protected void clear(Graphics g)
{
super.paintComponent(g);
// loescht die Pixelmap des Bildschirms
// per Default gibt es Double Buffering
}
protected Ellipse2D.Double getCircle()
{
return (kreis);
}
public static void main(String[] args)
{
GradientPaintBsp gbsp = new GradientPaintBsp();
String titel = gbsp.getClass().toString();
if (titel.indexOf("class") != -1)
263
pr43100
302
Programmieren in Java
titel = titel.substring(6);
JFrame frame = new JFrame(titel);
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
frame.getContentPane().add(gbsp,BorderLayout.CENTER);
frame.setSize(380,400);
frame.setVisible(true);
}
}
Abb.: Ausstatten einer Form mit einem Farbgradienten
public class TexturePaint extends Object implements Paint
Konstruktor.
public TexturePaint(BufferedImage texture,Rectangle2D anchor)
// ein Bild wird über eine Form gedeckt.
Methoden.
public PaintContext createContext(ColorModel cm, Rectangle deviceBounds,
Rectangle2D userBounds, AffineTransform xForm, Rendering hints)
public BufferedImage getImage()
public int getTransparency()
Bsp.264: Ein Kreis wird mit einem Füllmuster abgedeckt.
264
pr43100
303
Programmieren in Java
import
import
import
import
import
javax.swing.*;
java.awt.*;
java.awt.event.*;
java.awt.geom.*;
java.awt.image.*;
public class TexturePaintBsp extends JPanel
{
private Ellipse2D.Double kreis
= new Ellipse2D.Double(10,10,350,350);
public void paintComponent(Graphics g)
{
clear(g);
Graphics2D g2d = (Graphics2D) g;
// g2d.fill(kreis);
// g2d.draw(quadrat);
BufferedImage buffImage =
new BufferedImage(10,10,BufferedImage.TYPE_INT_RGB);
Graphics2D gg = buffImage.createGraphics();
gg.setColor(Color.yellow);
gg.fillRect(0,0,10,10);
gg.setColor( Color.black ); // draw in black
gg.drawRect(1,1,6,6);
// draw a rectangle
gg.setColor(Color.blue );
// draw in blue
gg.fillRect(1,1,3,3);
// draw a filled rectangle
gg.setColor(Color.red );
// draw in red
gg.fillRect(4,4,3,3);
// draw a filled rectangle
g2d.fill(
new RoundRectangle2D.Double(
155, 30, 75, 100, 50, 50 ));
// paint buffImage onto the Frame
g2d.setPaint(new TexturePaint(buffImage, new Rectangle(10,10)));
g2d.fill(getCircle());
g2d.setPaint(Color.black);
g2d.draw(getCircle());
}
protected void clear(Graphics g)
{
super.paintComponent(g);
// loescht die Pixelmap des Bildschirms
// per Default gibt es Double Buffering
}
protected Ellipse2D.Double getCircle()
{
return (kreis);
}
public static void main(String[] args)
{
TexturePaintBsp tbsp = new TexturePaintBsp();
String titel = tbsp.getClass().toString();
if (titel.indexOf("class") != -1)
titel = titel.substring(6);
JFrame frame = new JFrame(titel);
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
frame.getContentPane().add(tbsp,BorderLayout.CENTER);
frame.setSize(380,400);
frame.setVisible(true);
}
}
304
Programmieren in Java
Abb.: Füllen einer Form mit einem Füllmuster aus einer Bilddatei
Transparentes Zeichnen mit Hilfe der Klasse AlphaComposite
Für jede geometrische Form kann ein Transparenzwert ("Alphakanal") angegeben
werden, der angibt, wie stark "deckend" gezeichnet werden soll. Dazu instanziert
man ein Objekt von AlphaComposite und setzt den entsprechenden
Zeichenparameter mit der Methode setComposite() des Graphics2D-Kontextes.
Bsp.265: Ein rotes, halbtransparentes Rechteck liegt über einem Muster.
import java.awt.*;
import java.applet.*;
import java.awt.geom.*;
public class PolyTransp extends Applet
{
public void paint(Graphics g)
{
// Erzeuegen eines Graphics2D-Objekt
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
// Definition der Form
GeneralPath einPfad = new GeneralPath();
// Startpunkt festlegen (Ursprung der sichtbaren
// Zeichenflaeche des Fensters
einPfad.moveTo(getInsets().left,getInsets().top);
for (int i = 1, j = 10;i <= 10; i++, j--)
{
einPfad.lineTo(getSize().width/j,getSize().height/i);
einPfad.lineTo(getSize().width/j,getSize().height/j);
265
vgl. pr43100
305
Programmieren in Java
}
// der Pfad wird geschlossen
einPfad.closePath();
// Zeichnen (blau) des Umrisses vom PfadObjekt
g2d.setColor(Color.blue);
g2d.draw(einPfad);
// neues Gradientenobjekt
GradientPaint derFarbGradient =
new GradientPaint(getInsets().left,
// Startpunkt
getInsets().top,
// Gradient
new Color(255,255,255),
// Farbe
getSize().width,
// Endpunkt
getSize().height,
// Gradient
new Color(0,0,0));
// Endfarbe
// Gradient festlegen
g2d.setPaint(derFarbGradient);
// Pfad fuellen
g2d.fill(einPfad);
// Farbe festlegen (verdraengt den Gradienten)
g2d.setColor(Color.red);
// Instanz eines Alphakanals laden (Wert: 50 Prozent = 0.5)
AlphaComposite transparenzModus =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.5f);
// Transparenzwert festlegen
g2d.setComposite(transparenzModus);
// Zeichnen eines Rechtecks (50 Pixel Rand an allen Seiten)
g2d.fillRect(getInsets().left + 50,
getInsets().top + 50,
getSize().width - getInsets().right - 100,
getSize().height - getInsets().bottom - 100);
// g2d.setColor(Color.red);
// g2d.fill(einPfad);
}
}
Abb.: Transparente Überlagerung von Formen
Wenn zwei einander überlappende Formen dargestellt werden sollen, muß geklärt
werden, wie die Objekte sich zueinander verhalten sollen. Hierzu gibt es die
Composite- und CompositeContext-Interfaces. Eine Implementierung dazu ist
die AlphaComposite-Klasse, in der die SRC_OVER-Regel die meistgenutzte ist.
306
Programmieren in Java
4.3.6 Text und Fonts unter Java2D
Character, Glyphs266 und Fonts
Ein String lässt sich auf der Ebene der einzelnen Zeichen auf vielfältige Art und
Weise manipulieren, z.B. durch
- verschiedene Formatierungen
- Ausrichtung
- Position
- Schriftart
- Metrik
- Größe
- Umrisse
Jedes Font-Objekt umfasst diese Attribute, die direkt über von der Font-Klasse zur
Verfügung gestellte Methoden ansprechbar sind.
Java2D enthält eine erweiterte Font-Klasse. die erheblich weitgehendere
Möglichkeiten zur Kontrolle von Schriftarten bereitstellt.
Bsp.267: Das folgende Programm listet die von Java2D bereitgestellten Fonts auf.
import java.awt.*;
/** Lists the names of all available fonts. */
public class ListFonts
{
public static void main(String[] args)
{
GraphicsEnvironment env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fontNames = env.getAvailableFontFamilyNames();
System.out.println("Available Fonts:");
for(int i = 0; i < fontNames.length; i++)
System.out.println(" " + fontNames[i]);
}
}
Transformations- und Zeichentechnik
Die Transformations- und Zeichentechnik von Java2D kann auch auf Textstrings
angewendet werden.
Bsp.268: Rotation eines Textstrings
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
public class RotationBsp extends JPanel
{
private Ellipse2D.Double kreis
= new Ellipse2D.Double(10,10,350,350);
private GradientPaint gradient
= new GradientPaint(0,0,Color.red,175,175,Color.yellow,true);
private Color[] farben = { Color.white, Color.black };
public RotationBsp()
266
Die Vereinigung von dem Zeichen selbst und seinem exakten Aussehen wird Glyph genannt
pr43100
268 pr43100
267
307
Programmieren in Java
}
{
GraphicsEnvironment env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
env.getAvailableFontFamilyNames();
setFont(new Font("Book Antiqua", Font.PLAIN, 90));
}
protected void clear(Graphics g)
{
super.paintComponent(g);
// loescht die Pixelmap des Bildschirms
// per Default gibt es Double Buffering
}
protected Ellipse2D.Double getCircle()
{
return (kreis);
}
public void paintComponent(Graphics g)
{
clear(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setPaint(gradient);
g2d.fill(getCircle());
g2d.setPaint(Color.black);
g2d.draw(getCircle());
g2d.translate(185.0,185.0);
for (int i = 0; i < 16; i++)
{
g2d.rotate(Math.PI/8.0);
g2d.setPaint(farben[i%2]);
g2d.drawString("Java",0,0);
}
}
public static void main(String[] args)
{
RotationBsp rotationBsp = new RotationBsp();
String titel = rotationBsp.getClass().toString();
if (titel.indexOf("class") != -1)
titel = titel.substring(6);
JFrame frame = new JFrame(titel);
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
frame.getContentPane().add(rotationBsp,BorderLayout.CENTER);
frame.setSize(380,400);
frame.setVisible(true);
}
308
Programmieren in Java
Abb.:
4.3.7 2D Bildverarbeitung
Zeichnen eines Bilds.
Mit Hilfe der Graphics2d-Methode
public boolean drawImage(BufferedImage image, BufferedImageOp filter,
int left, int top)
kann ein BufferedImage ab der oberen linken Ecke (left,top) gezeichnet
werden.
Erstellen eines BufferedImage aus einem Image-File.
public static BufferedImage getBufferedImage(String imageFile, Component c)
{
Image image = c.getToolkit().getImage(imageFile);
MediaTracker tracker = new MediaTracker(c);
// nutzt MediaTracker
tracker.addImage(image,0);
try {
tracker.waitForAll();
}
catch (InterruptedException ie) { }
BufferedImage bufferedImage = new BufferedImage(image.getWidth(c),
image.getHeight(c),BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bufferedImage.createGraphics();
g2d.drawImage(image,0,0,c);
return bufferedImage);
}
309
Programmieren in Java
BufferedImage-Objekte
Ein BufferedImage-Objekt besteht aus einem Raster-Objekt und einem ColorModelObjekt.
Der Raster kümmert sich um die Bildverwaltung, wobei der DataBuffer die Bilddaten
enthält und das SampleModel angibt, wie die Bilddaten zu interpretien sind.
Das ColorModel gibt an, wie die Pixeldaten in Bezug auf ihre Farben ausgewertet
werden sollen. Ein BufferedImage kann als OffScreen-Puffer dienen. Damit kann
man das Objekt schon im Speicher rendern, bevor man das Bild oder nur den
benötigten Ausschnitt auf den Bildschirm kopiert.
4.3.8 Animationen mit Java2D
Man kann mit Java2D einfache Animationen erstellen, z.B.:
310
Programmieren in Java
5. AWT
5.1 Bestandteile des AWT
In Java wird die Kommunikation mit dem Anwender hauptsächlich über ein eigenes
Konzept realisiert – dem Abstract Windowing Toolkit. Das AWT besitzt zur
Kommunikation mit dem Anwender ein Application Programming Interface (API).
Darüber können allgemeine Komponenten der Benutzeroberfläche, z.B.
Schaltflächen oder Menüs, plattformunabhängig genutzt werden.
5.2 Die AWT-Komponenten
Die AWT stellt Komponenten für den Anwender bereit. Die von Anfang an
vorhandenen Komponenten269 des AWT sind: Schaltflächen (Buttons), Labels,
Kontrollkästchen
(Checkbuttons),
Optionsfelder
(Radiobuttons),
Listen,
Auswahlfelder, Textfelder, Menüs, Zeichenbereiche.
Zusätzlich gibt es Container, in die die Komponenten zum Erstellen vollständiger und
sinnvoller Anwendungsschnittstellen integriert sein müssen. Die Container im AWT
sind Fenster, Panels, Frames, Dialoge.
Ein weiterer Bestandteil der AWT sind Layout-Manager. Ein Layout-Manager ist in
jedem Container enthalten. Er findet – angepaßt an die jeweilige Situation –
automatisch heraus, an welche Stelle die Komponenten am besten passen. Der AWT
stellt 5 verschiedene Typen von Layout-Managern zur Verfügung: Flow-, Border-,
Grid-, Gridbag-Layout.
Schließlich stellt AWT die Mittel zur Reaktion auf Ereignisse zur Verfügung, die vom
Anwender über Komponenten ausgelöst werden können.
Die Wurzel fast aller AWT-Komponenten ist die Klasse Component. Sie enthält die
grundlegenden Anzeige- und Event-Handling-Funktionen.
Component
Canvas
Container
Panel
Applet
TextComponent
Window
Frame
Button
TextField
Dialog
Abb. AWT-Klassenhierarchie
269 Java 1.2 und das Swing-Konzept erweitern diese Komponenten noch einmal um einen satz von neuen und
ergänzenden Komponenten.
311
Programmieren in Java
5.2.1 Schaltflächen (Buttons)
Component
Button
<< Konstruktoren >>
public Button()
public Button(String label)
<< Methoden >>
public void addActionListener(ActionListener l)
public void addNotify()
public String getActionCommand()
public String getLabel()
protected String paramString()
protected void processActionEvent(ActionEvent e)
protected void processEvent(AWTEvent e)
public void removeActionListener(ActionListener l)
public void setActionCommand(String kommando)
public void setLabel(String label)
Abb. Die Klasse Button
Erzeugen.
Schaltflächen
(Buttons)
sind
Elemente
einer
grafischen
Benutzeroberfläche, die auf Knopfdruck Aktionen in der Fensterklasse auslösen. Mit
den folgenden Konstruktoren kann eine Schaltfläche erstellt werden:
-
Button() erzeugt eine leere Schaltfläche ohne Beschriftung
Button(String label) erzeugt eine Schaltfläche mit der durch das String-Objekt
bezeichneten Beschriftung.
In einem Applet270 reicht dafür aus: add(new Button("Auf los geht's
los!"));.
Beschriftungen einer Schaltfläche können dynamisch zur Laufzeit gesetzt und wieder
verändert werden. Dazu dient die Methode: public void setLabel(String
label). Welche Beschriftung die Schaltfläche angenommen hat, ermittelt die
Methode public String getLabel();.
Reaktionen auf die Betätigung von Schaltflächen (JDK 1.1). Falls ein Button gedrückt
wird, sendet es ein Action-Event an seine Ereignisempänger. Diese müssen das
Interface ActionListener implementieren und sich durch den Aufruf von
„addActionListener“ registrieren:
public void addActionListener(ActionListener l)
public void removeActionListener(ActionListener l)
Das Action-Event führt im Ereignisempfänger zum Aufruf der Methode public
void
actionPerformed(ActionEvent
ae)
,die
die
Methode
getActionCommand aufrufen kann, mit der die Beschriftung des Button abgefragt
werden kann. Falls das Action-Kommando nicht mit der Beschriftung identisch ist,
270
Abgeleitet von der Panel-Klasse
312
Programmieren in Java
kann es in der Button-Klasse durch „public void setActionCommand(String
kommando)“ geändert werden.
Bsp.: Setzen von Hintergrundfarben nach verschiedenen Buttonklicks271
import java.awt.*;
import java.awt.event.*;
public class SchaltApplet extends java.applet.Applet
{
private Button schalt1 = new Button("Rot");
private Button schalt2 = new Button("Blau");
private Button schalt3 = new Button("Gruen");
private Button schalt4 = new Button("Gelb");
private Button schalt5 = new Button("Schwarz");
public void init()
{
setBackground(Color.white);
schalt1.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
setBackground(Color.red);
}
});
schalt2.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
setBackground(Color.blue);
}
});
schalt3.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
setBackground(Color.green);
}
});
schalt4.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
setBackground(Color.yellow);
}
});
schalt5.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
setBackground(Color.black);
}
});
this.setLayout(new BorderLayout(15,15));
Panel p = new Panel();
p.setLayout(new FlowLayout(FlowLayout.CENTER,15,15));
p.add(schalt1);
p.add(schalt2);
p.add(schalt3);
p.add(schalt4);
p.add(schalt5);
this.add("South",p);
// this.pack();
}
}
271
pr52105
313
Programmieren in Java
Reaktionen auf die Betätigung von Schaltflächen (JDK 1.0). Die Komponenten
innerhalb der AWT-Oberfläche besitzen eine action()-Methode, die aufgerufen
wird, wenn bei einer Komponente eine Aktion ausgführt wurde. Beim Betätigen
(Auslösen) einer Schaltfläche wird die action()-Methode aufgerufen. Die
action()-Methode gehört zu den Ereignisbehandlungsmethoden des Event
Handling. Die Syntax der action()-Methode ist bei allen Komponenten identisch:
public boolean action(Event ereignis, Object welcheAktion).
Ereignis: Ereignis, das bei der Komponente aufgetreten ist
welcheAktion: steht für das, was geschehen ist
Bei Schaltflächen ist die Art der Aktion (welcheAktion) ganz einfach über das Label
(Beschriftung) der Schaltfläche auszuwerten, die ausgelöst wurde. Der „Event“Parameter enthält spezifische Informationen über die Aktion, z.B.:
event.target (Komponente, bei der die Aktion eingetreten ist)
event.when (Zeitpunkt, zu dem die Aktion geschehen ist)
Mit dem instanceof-Operator kann die event.target-Variable überprüft werden,
ob die Aktion auch für das Objekt erfolgt, das gewünscht wird.
Bsp.: Setzen von Hintergrundfarben nach verschiedenen Buttonklicks272
import java.awt.*;
public class KnopfAktApplet extends java.applet.Applet
{
public void init()
{
setBackground(Color.white); this.setLayout(new BorderLayout(15,15));
Panel p = new Panel();
p.setLayout(new FlowLayout(FlowLayout.CENTER,15,15));
p.add(new Button("Rot"));
p.add(new Button("Blau"));
p.add(new Button("Gruen"));
p.add(new Button("Gelb"));
p.add(new Button("Schwarz")); this.add("South",p);
}
public boolean action(Event e, Object arg)
{
// Test auf Schaltflaechenaktion
if (e.target instanceof Button) aendereFarbe((String) arg);
return true;
}
void aendereFarbe(String knopfName)
{
// Aendern der Hintergrundfarbe
if (knopfName.equals("Rot"))
setBackground(Color.red);
else if (knopfName.equals("Blau"))
setBackground(Color.blue);
else if (knopfName.equals("Gruen"))
setBackground(Color.green);
else if (knopfName.equals("Gelb"))
setBackground(Color.yellow);
else if (knopfName.equals("Schwarz")) setBackground(Color.black);
else;
}
}
272
vgl. pr52105
314
Programmieren in Java
5.2.2 Labels
Labels sind Zeichenketten zur Beschriftung anderer Komponenten der
Benutzeroberfläche. Ein Label umfaßt eine Zeile Text, die auf dem Bildschirm
angezeigt wird und vom Programm veränder werden kann.
Konstruktoren.
public Label()
public Label(String text)
public Label(String text, int ausrichten)
Der Parameter „ausrichten“ bestimmt die Ausrichtung des Texts. Hier kann eine der Konstanten
Label.LEFT, Label.RIGHT, Label.CENTER übergeben werden.
Methoden.
public void setText(String text)
// Zugriff auf die Textzeile des Label
public String getText()
// Zugriff auf die Textzeile des Label
public void setAlignment(int ausrichten)
// Ausrichten der Textzeile
public int getAlignment()
// Ausrichten der Textzeiele
5.2.3 Kontrollkästchen und Optionsfelder
Erzeugen von Kontrollkästchen. Ein Kontrollkästchen (Checkbutton / Checkbox)
hat zwei Bestandteile: ein Label (Text, der neben dem Kontrollkästchen angelegt
wird) und einem Zustand (boolesche Variable, die angibt, ob die Box eingeschaltet
wurde oder nicht273). Zum Erzeugen von Kontrollkästchen gibt es 5 Konstruktoren
-
-
Checkbox()
(Kontrollkästchen ohne Label)
Checkbox(String label)
(Kontrollkästchen mit Beschriftung)
Checkbox(String label, boolean zustand)
Checkbox mit Vorgabe des Anfangszustands
Checkbox(String label, CheckboxGroup cbg, boolean zustand)
(Kontrollkästchen, das jenach gesetzter boolescher Zustandsvariable vorselektiert (true) ist oder
nicht (false). Das mittlere Argument ist bei Kontrollkästchen immer auf Null gesetzt. Falls das nicht
der Fall ist, dient der Parameter cbg zum Gruppieren von Radiobuttons.
Checkbox(String label, boolean zustand, CheckboxGroup cbg)
Plazieren von Kontrollkästchen in einem Container. Es erfolgt über die Methode
add(), z.B.: add(new Checkbox("Java"));.
Überprüfen und Setzen von Kontrollkästchen. Mit der Methode public boolean
getState() kann überprüft werden, ob ein Kontrollkästchen angeglickt wurde. Die
Methode public void setState(boolean zustand) setzt den Status (true
für gesetzt). Die getlabel()-Methode fragt das Label des jeweiligen
Kontrollkästchens ab.
Reaktion auf Zustandsänderungen. Eine Checkbox generiert ein Item-Event, wenn
die Markierung vom Anwender gesetzt oder zurückgenommen wird. Zur Reaktion auf
dieses Event ist durch den Aufruf von addItemListener ein Objekt, das das
Interface ItemListener implementiert, bei der Checkbox zu registrieren. In
273
Standardmäßig ist die Box ausgeschaltet (Wert ist false oder „off“).
315
Programmieren in Java
diesem Fall wird ein Ereignisempfänger die Methode itemStateChanged mit einem
Argument vom Typ ItemEvent aufgerufen:
public abstract void itemStateChanged(ItemEvent e)
Über das ItemEvent kann die Methode getItemSelectable aufgerufen werden,
mit der ermittelt werden kann, durch welche Checkbox das Ereignis ausgelöst wurde:
public ItemSelectable getItemSelectable().
Der Rückgabewert kann in ein Objekt des Typs Checkbox konvertiert werden. Durch
Aufruf von getState() kann der aktuelle Zustand bestimmt werden.
Bsp.:
import java.awt.*;
import java.awt.event.*;
public class PR52301 extends Frame
{
Checkbox
cb1 = new Checkbox("Checkbox 1"),
cb2 = new Checkbox("Checkbox 2",true),
cb3 = new Checkbox("Checkbox_3",false);
public PR52301()
{
setLayout(new GridLayout(3,1));
setBackground(Color.lightGray);
cb1.addItemListener(new CBL());
cb2.addItemListener(new CBL());
cb3.addItemListener(new CBL());
add(cb1);
add(cb2);
add(cb3);
}
public class CBL implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{
Checkbox cb = (Checkbox) e.getItemSelectable();
System.out.println(cb.getLabel() + ": " + cb.getState());
}
}
public static void main(String args[])
{
PR52301 f = new PR52301();
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
f.setSize(100,150); f.setVisible(true);
}
}
Defintion zur CheckboxGroup. Eine CheckboxGroup ist die Java-Variante einer
Gruppe von Radiobuttons (Optionsfelder), von den genau immer einer aktiviert wird.
Wird eine anderer Button aktiviert, so änder er seinen internen Status auf „true“ und
der zuvor gesetzte wird „false“. Eine CheckboxGroup ist nichts Anderes als eine
Checkbox, deren CheckboxGroup-Parameter gesetzt ist.
Erzeugen von Optionsfeldern (Radiobuttons). Erforderlich ist zunächst eine
Checkbox-Gruppe, die über die betreffenden Konstruktoren erstellt werden kann.
Dieser Gruppe werden die einzelnen Optionsfelder hinzugefügt.
316
Programmieren in Java
Plazieren von Optionsfeldern in einem Container. Auch Optionsfelder müssen wie
alle Komponenten nach der Erzeugung in einen Container gebracht werden. Das
geschieht über die Methode add().
Setzen und Überprüfen von Optionsfeldern. Die Methoden getState() und
getLabel() funktionieren auch bei Optionsfeldern. Zum Zugriff auf die Gruppe
eines Optionsfelds und zum Ändern gibt es die Methoden:
public CheckboxGroup getCheckboxGroup()
public void setCheckboxGroup(CheckboxGroup g)
Zum Holen und Setzen des momentan ausgewählten Optionsfelds dienen
public Checkbox getCurrent()
public void setCurrent(Checkbox box)
Bsp.:
import java.awt.*;
import java.awt.event.*;
public class PR52302 extends Frame
{
CheckboxGroup cbg = new CheckboxGroup();
public PR52302()
{
setLayout(new GridLayout(3,1));
setBackground(Color.lightGray);
Checkbox cb1 = new Checkbox("rot",cbg,false);
Checkbox cb2 = new Checkbox("blau",cbg,false);
Checkbox cb3 = new Checkbox("gruen",cbg,false);
cb1.addItemListener(new CBL());
cb2.addItemListener(new CBL());
cb3.addItemListener(new CBL());
add(cb1); add(cb2); add(cb3);
}
public class CBL implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{
Checkbox cb = (Checkbox) e.getItemSelectable();
if (cb.getLabel() == "rot")
{
if (cb.getState())
{ System.out.println("rot"); }
}
if (cb.getLabel() == "blau")
{
if (cb.getState())
{ System.out.println("blau"); }
}
if (cb.getLabel() == "gruen")
{
if (cb.getState())
{ System.out.println("gruen"); }
}
}
}
public static void main(String args[])
{
PR52302 f = new PR52302();
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
317
Programmieren in Java
f.setSize(100,150);
}
}
f.setVisible(true);
Reaktionen auf Kontrollfelder und Radiobuttons im JDK1.0. Kontrollfelder und
Radiobuttons verfügen wie alle Komponenten über die action()-Methode. Sie wird
bei einer Auswahl aufgerufen, sobald ein Feld selektiert wird.
Bsp.274:
import java.awt.*;
public class CheckAktApplet extends java.applet.Applet
{
CheckboxGroup meineCheckboxGruppe = new CheckboxGroup();
public void init()
{
add(new Checkbox("Blau",meineCheckboxGruppe,false));
add(new Checkbox("Rot",meineCheckboxGruppe,false));
add(new Checkbox("Gruen",meineCheckboxGruppe,false));
add(new Checkbox("Gelb",meineCheckboxGruppe,true));
// Hintergrundfarbe passend zur Voreinstellung
setBackground(Color.yellow);
}
public boolean action(Event e, Object welcheAktion)
{
// Test auf Kontrollkaestchen oder Radiobutton
if (!(e.target instanceof Checkbox))
return false;
// Instanz der Ereignisse
Checkbox welcheAuswahl = (Checkbox) e.target;
// Status des Radiobuttons zur Kontrolle
boolean CheckboxStatus = welcheAuswahl.getState();
// Auswahl Blau
if (welcheAuswahl.getLabel() == "Blau")
{
if (CheckboxStatus) setBackground(Color.blue); return true;
}
// Auswahl Rot
if (welcheAuswahl.getLabel() == "Rot")
{
if (CheckboxStatus) setBackground(Color.red);
return true;
}
// Auswahl Gruen
if (welcheAuswahl.getLabel() == "Gruen")
{
if (CheckboxStatus) setBackground(Color.green); return true;
}
if (welcheAuswahl.getLabel() == "Gelb")
{
if (CheckboxStatus) setBackground(Color.yellow);
return true;
}
return false; // keine der Optionen wird aufgefangen
}
}
274
pr54305
318
Programmieren in Java
5.2.4 Auswahlmenüs
Es handelt sich um Popup- bzw. Pulldown-Menüs, die sich beim Anklicken öffnen
und mehrere auszuwählende Optionen anzeigen, von denen dann eine Option
ausgewählt werden kann.
Konstruktor: public Choice()
// erstellt ein Choice-Objekt mit einer leeren Liste.
Bearbeiten der Liste (Combobox) zum Auswahlmenü: public
void
addItem(String item) Jeder Aufruf von addItem hängt das übergebende
Element „item“ an das Ende der Liste an. Die Elemente werden in der Reihenfolge
der Aufrufe von addItem eingefügt.
Zugriff auf die Elemente der Combobox: public int getSelectedIndex()
liefert den Index des ausgewählten Elements. Durch Übergabe dieses Werts (an
„getItem“ kann das aktuelle Element beschafft werden: public String
getItem(int n). Über public String getSelectedItem() kann ohne
Kenntnis der internen Nummer (Index) das ausgewählte Element beschafft werden.
Programmseitige Auswahl eines Elements:
public void select(int)
// Auswahl über den Index
public void select(String s)
// Auswahl über den angegebenen Wert von String
Ereignisbehandlung. Ebenso wie eine Checkbox sendet auch ein Choice-Element
„Item“-Ereignisse, wenn ein Element ausgewählt wurde. Zur Reaktion auf dieses
Element muß ein ItemListener durch Aufruf von public
void
addItemListener(ItemListener l) registriert sein. Ein „ItemEvent“ wird
immer dann gesendet, wenn ein Element ausgewählt wurde. In diesem Fall wird ein
Ereignisempfänger die Methode itemStateChanged mit einem Argument vom Typ
ItemEvent
aufrufen:
public
abstract
void
itemStateChanged(ItemEvent e). Das „ItemEvent“ stellt die Methode
public ItemSelectable getItemSelectable() zur Verfügung, mit der
ermittelt werden kann, durch welches Choice-Element das Ereignis ausgewählt
wurde. Zusätzlich gibt es public Object getItem(), die das ausgewählte
Element als String zur Verfügung stellt.
Bsp.:
import java.awt.*;
import java.awt.event.*;
public class PR14174 extends Frame
{
TextField t
= new TextField(30);
Choice auswahl = new Choice();
public PR14174()
{
auswahl.addItem("rot");
auswahl.addItem("blau");
auswahl.addItem("gruen");
auswahl.addItem("pink");
auswahl.addItem("orange");
auswahl.addItem("gelb");
auswahl.addItem("cyan");
add(auswahl);
pack();
}
public static void main(String args[])
{
PR14174 f = new PR14174();
f.addWindowListener(
319
Programmieren in Java
}
new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
f.setBackground(Color.lightGray);
f.setVisible(true);
}
5.2.5 Listenfelder
Eine Instanz der Klasse List ist eine listenartige Darstellung von Werten, aus denen
Anwender einen oder mehrere auswählen kann. Anders als ein Choice-Element ist
ein Element der Klasse List ständig in voller Größe auf dem Bildschirm sichtbar.
Erzeugen von Listenfeldern.
public List()
// legt eine leere Liste an, deren dargestellte Größe durch den
// Layoutmanager begrenzt wird.
public List(int groesse)
// Der Parameter groesse legt die Anzahl der angezeigten Zeilen fest.
public List(int groesse, boolean multiselect)
// ist multiselect "true", kann der Anwender nicht nur ein Element,
// sondern auch andere Elemente auswählen
Mehrfachauswahl ausgewählter Elemente. „List“ bietet
Funktionalität wie Choice an. Zusätzlich stehen zur Verfügung:
nahezu
dieselbe
public int[] getSelectedIndexes()
// liefert einen Array mit den Indexpositionen der ausgewählten
// Elemente.
public String[] getSelectedItems()
// liefert eine Liste der Werte.
Auswahl einzelner Elemente.
public void select(int index)
public void deselect(int index)
Entfernen, Verändern von Elementen.
public void delItem(int index)
// löscht das Element an der Position index
public void remove(int index)
// löscht das Element an der Position index
public void replaceItem(String neuerWert,int index)
// ersetzt das Element an der Position index durch die Postion
// neuerWert.
Item- und Action-Ereignisse. Ein Listenelement sendet Item- und ActionEreignisse. Ein Action-Ereignis wird generiert, wenn ein Listenelement durch
Doppelklick ausgewählt wurde. Ein Item-Ereignis wird ausgelöst, nachdem in der
Liste ein Element ausgewählt wurde.
Bsp.:
import java.awt.*;
import java.awt.event.*;
public class PR14177 extends Frame
{
String[] farben = {"rot","blau","gruen","pink","orange",
"gelb","cyan"};
List lst = new List(6,true);
public PR14177()
320
Programmieren in Java
{
setLayout(new FlowLayout());
for (int i = 0; i < 7; i++)
lst.addItem(farben[i]);
add(lst);
lst.addItemListener(new ILL());
lst.addActionListener(new ALL());
pack();
}
public class ILL implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{
List lst = (List) e.getItemSelectable();
String str = lst.getSelectedItem();
int pos = ((Integer) e.getItem()).intValue();
System.out.println("event.getSelectedItem: " + str + " " + pos);
}
}
class ALL implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
Object obj = e.getSource();
if (obj instanceof List)
{
System.out.println("Listenfeld-Aktion: " + e.getActionCommand());
}
}
}
public static void main(String args[])
{
PR14177 f = new PR14177();
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
// f.setVisible(false);
System.exit(0);
}
});
f.setBackground(Color.lightGray);
// f.setSize(100,160);
f.setVisible(true);
}
}
5.2.6 Textbereiche und Textfelder
Das AWT verfügt über Klassen zur Eingabe von Text (Klasse TextField für
Textfelder und Klasse TextArea für Textbereiche). Textfelder begrenzen die Menge
des einzugebenden Textes und können nicht gescrollt werden. In Textbereichen
können mehrere Zeilen verarbeitet werden, außerdem sind Textbereiche scrollbar.
TextField
Ein TextField dient zur Darstellung von Text. Anwender und Programm können den
dargestellten Text einlesen und verändern.
Erzeugen von Textfeldern. Es stehen folgende Konstruktoren zur Verfügung:
321
Programmieren in Java
public TextField()
erzeugt ein leeren Textfeld, in das der Anwender Text eingeben kann.
public TextField(int anzSpalten)
Erzeugt ein leeres Textfeld bestimmter Breite. Die Anzahl einzugebender Zeichen ist nicht begrenzt.
Ist der Text länger, so scrollt er innerhalb des Textfelds
public TextField(String text)
Über den Parameter text kann eine Zeichenkette vorgegeben werden, die beim aufruf des Textfelds
vorgelegt wird.
public TextField(String text, int anzSpalten)
Methoden zum Zugriff bzw. Verändern von Textfeldern.
public String getText();
public void setText(String text)
Mit
public int getColumns()
kann die Anzahl der darstellbaren Zeichen eines Textfelds abgefragt und mit
public void setColumns(int anzSpalten)
verändert werden.
Markieren von Text.
public void selectAll()
//markiert den kompletten Bereich
public void select(int erstes, int letztes)
//markiert den Bereich von „erstes“ bis „letztes“. Das „Zählen“ beginnt bei 0.
Mit
public int getSelectionStart()
bzw.
public int getSelectionEnd()
kann die aktuelle Auswahl abgefragt werden. Mit
public int getCaretPosition()
und
public void setCaretPosition(int position)
kann auf die aktuelle Cursorposition zugegriffen werden.
Bearbeiten von Textfeldern.
Über
public void setEditable(boolean erlaubt)
// Aufruf mit Parameter false unterbindet weitere Eingaben
bzw.
public boolean isEditable()
// Abfrage des aktuellen Status
kann man die Veränderung von Text verhindern. Mit
public void setEchoChar(char z)
(Übergabe eines Zeichens, das beim Tastendruck anstelle des vom anwender eingegebenen
Zeichens ausgegeben wird)
bzw.
public char getEchoChar()
besteht die Möglichkeit verdeckter Eingaben (z.B. für Paßwörter).
Generieren eines Action-Event erfolgt beim Drücken der Enter-Taste innerhalb des
Textfelds. Die Methode String getActionCommand() des Action-Events liefert
den Inhalt des Textfelds.
Generieren eines Text-Ereignisses bei jeder Änderung im Textfeld. Ein Empfänger
kann mit der Methode addTextListener vom Textfeld registriert werden. Als
Argument wird ein Objekt erwartet, das das Interface TextListener implementiert.
Beim Auftreten eines Text-Ereignisses wird vom TextListener die Methode
322
Programmieren in Java
textValueChanged mit einem „TextEvent“ als Parameter aufgerufen.
„TextEvent“ ist aus „AWTEvent“ abgeleitet und erbt die Methoden getID(),
getSource().
Typischerweise
wird
innerhalb
von
public
void
textValueChanged(TextEvent e) mit getSource() das zugehörige Textfeld
beschafft und mit getText() auf seinen Inhalt zugegriffen.
Bsp.:
import java.awt.*;
import java.awt.event.*;
public class PR14170 extends Frame
{
Button
b1 = new Button("Hole Text"),
b2 = new Button("Setze Text");
TextField
t1 = new TextField(30),
t2 = new TextField(30),
t3 = new TextField(30);
String s = new String();
public PR14170()
{
setLayout(new FlowLayout());
b1.addActionListener(new B1A());
b2.addActionListener(new B2A());
t1.addTextListener(new T1());
t1.addActionListener(new T1A());
add(b1);
add(b2);
add(t1);
add(t2);
add(t3);
}
class T1 implements TextListener
{
public void textValueChanged(TextEvent e)
{
t2.setText(t1.getText());
}
}
class T1A implements ActionListener
{
private int zaehler = 0;
public void actionPerformed(ActionEvent e)
{
t3.setText("t1 ActionEvent " + zaehler++);
}
}
class B1A implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
s = t1.getSelectedText();
if (s.length() == 0) s = t1.getText();
t1.setEditable(true);
}
}
class B2A implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
t1.setText("Eingefuegt durch Button 2: " + s);
t1.setEditable(false);
}
323
Programmieren in Java
}
public static void main(String args[])
{
PR14170 f = new PR14170();
f.addWindowListener(
new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.setSize(300,200);
f.setBackground(Color.lightGray);
f.setVisible(true);
}
}
TextArea
Mit dieser Klasse können mehrzeilige Textfelder erzeugt werden. Zusätzlich kann der
Text in allen Richtungen „scrollen“, so daß auch größere Texte bequem bearbeitet
werden können.
Konstruktoren.
public TextArea()
//erzeugt ein leeres TextArea-Objekt in einer vom System vorgegebenen Größe.
public TextArea(int zeilen, int spalten)
//erzeugt eine leeres TextArea-Objekt mit einer bestimmten Anzahl von sichtbaren Zeilen
//und Spalten.
public TextArea(String text, int zeilen, int spalten)
//erzeugt ein TextArea-Objekt mit Text.
public TextArea(String text, int zeilen, int spalten, int scrollbars)
//erzeugt ein TextArea-Objekt, in dem über scrollbars die Ausstattung der TextArea mit
//Schieberegistern festgelegt werden kann. Dazu stellt TextArea die Konstanten
//SCROLLBARS_BOTH, SCROLLBARS_NONE, SCROLLBARS_VERTICAL_ONLY,
//SCROLLBARS_HORIZONTAL_ONLY zur Verfügung, die als Argumente übergeben werden können.
Zusätzliche in der Klasse bereitgestellte Methoden zum Verändern von Teilen des
Texts. Neben den bereits in der Klasse TextField angegebenen Methoden stehen zur
Verfügung:
public void insert(String str, int pos)
//verändert die Zeichenkette str ab Position pos. Der dahinter stehende Text wird entsprechend
//nach hinten verschoben.
public void append(String str)
//hängt den über str übergebenen Text hinten an.
public void replaceRange(String text, int start, int ende)
//ersetzt den Text zwischen start und end durch den String text.
Senden von Text-Events. Ein Objekt der Klasse TextArea sendet Text-Events,
so wie es bei TextField beschrieben wurde.
5.2.7 Schieber und Bildlaufleisten
Listen und Textbereiche besitzen standardmäßig die Eigenschaft bei Bedarf nicht in
den Anzeigebereich passenden Inhalt über Bildlaufleisten und Schieber zu scrollen.
Bildlaufleisten können auch individuell erstellt werden und dienen zur (quasi-)
analogen Anzeige bzw. Eingabe eines Werts aus einem vorgebenen Wertebereich.
324
Programmieren in Java
Der Schieberegler kann horizotal und vertikal angeordnet werden und besitzt einen
Schieber, dessen Größe veränderlich ist. Der interne Wert eines Schiebereglers und
die Anzeigeposition seines Schiebers sind untrennbar miteinander verbunden. Ändert
der Anwender die Position des Schiebers, ändert sich automatisch auch sein interner
Wert. Wird vom Programm der Wert verändert, führt das automatisch zu einer
Positionierung des Schiebers. Zur Änderung des aktuellen Werts einer Bildlaufleiste
können drei verschieden Teile der Bildlaufleiste verwendet werden:
-
Pfeile an beiden Enden zum Erhöhen / Erniedrigen des Werts um eine Einheit (standardmäßig 1)
Ein Bereich in der Mitte zum Erhöhen / Erniedrigen eines Werts um je eine größere Einheit
(standardmäßig 10)
Ein Bildlauffeld (Schieber) in der Mitte, der den momentan gewählten Wert anzeigt. Durch
Verschieben des Bildlauffelds mit dem Mauszeiger kann innerhalb der Bildlaufleiste ein anderer
Wert gewählt werden.
Nötig ist nur die Festlegung eines Höchst- und Mindestwerts für die Bildlaufleiste. Der
Rest wird von Java erledigt.
Konstruktoren.
public Scrollbar()
//erzeugt einen vertikalen Schieberegler mit 0,0 als anfänglichen Höchst-, Mindestwert.
public Scrollbar(int orientierung)
//orientierung bezeichnet die Ausrichtung und kann über Scrollbar.HORIZONTAL bzw.
//Scrollbar.VERTICAL festgelegt werden.
public Scollbar(int orientierung, int wert, int bh, int minimum,
maximum)
//orientierung...bestimmt die Ausrichtung des Schiebereglers
//wert...Anfangswert des Schiebereglers (Wert zwischen dem Höchst- und Mindestwert
//bh...Breite bzw. Höhe der dem Schieberegler zugeteilten Box (Seite)
//minimum, maximum...bezeichnet den Mindest-, Höchstwert des Schiebereglers
int
Methoden der Scrollbar-Klasse versorgen die Handhabung der Werte des
Schiebereglers:
Methode
public
public
public
public
public
public
Bedeutung
Zugriff auf den aktuellen Wert des Schiebereglers
Setzt den aktuellen Wert des Schiebereglers
Bestimmt die Ausrichtung des Schiebereglers
Zugriff auf den Mindestwert
Zugriff auf den Höchstwert
Zugriff der Parameter, die die Stärke der Veränderung des
Werts beim Klicken auf die Buttons bzw. die Schaltfläche
zwischen Schieber und Button bestimmen.
public void setUnitIncrement(int l) Veränderung der Parameter, die die Stärke der
Veränderung des Werts beim Klicken auf die Buttons
zwischen Schieber und Button bestimmen
public int getBlockIncrement()
Zugriff der Parameter, die die Stärke der Veränderung des
Werts beim Klicken auf die Buttons bzw. die Schaltfläche
zwischen Schieber und Button bestimmen
public void setBlockIncrement(int l) Veränderung der Parameter, die die Stärke der
Veränderung des Werts beim Klicken auf die Schaltfläche
zwischen Schieber und Button bestimmen
int getValue()
void setValue(int wert)
int getOrientation()
int getMinimum()
int getMaximum()
int getUnitIncrement()
Ereignisse.
Ein
Scrollbar
sendet
Adjustment-Ereignisse
an
seine
Ereignisempfänger. Diese müssen das Interface AdjustmentListener
implementieren
und
sich
durch
Aufruf
von
public
void
addAdjustmentListener(AdjustmentListener
l)
registrieren.
Das
Adjustment-Ereignis führt im Ereignisempfänger zum Aufruf der Methode:
325
Programmieren in Java
public abstract void adjustmentValueChanged(AdjustmentEvent e),
die ein AdjustmentEvent übergeben bekommt. Dieses besitzt die Metode
getAdjustable(), mit der der auslösende Scrollbar bestimmt werden kann.
Zusätzlich gibt es die Methode getAdjustmentType(), die Auskunft darüber gibt,
welche Benutzeraktion zur Auslösung des Ereignisses führte.
Konstanten, die von getAdjustmentType Bedeutung
zurückgegeben werden
AdjustmentEvent.UNIT_INCREMENT
Der Wert durch Klicken eines „Button“ um eine
Einheit erhöht.
AdjustmentEvent.UNIT_DECREMENT
Der Wert durch Klicken eines „Button“ um eine
Einheit erniedrigt.
AdjustmentEvent.BLOCK_INCREMENT
AdjustmentEvent.BLOCK_DECREMENT
AdjustmentEvent.TRACK
Bsp.:
Der Wert wurde durch Klicken der Schaltfläche
zwischen Button und Schieber um eine Seite erhöht
Der Wert wurde durch Klicken der Schaltfläche
zwischen Button und Schieber um eine Seite
vermindert
Der Wert wurde durch Ziehen des Schiebers
verändert
import java.awt.*;
import java.awt.event.*;
public class PR14175 extends Frame
{
public PR14175()
{
Panel p = new Panel();
p.setLayout(new BorderLayout());
Scrollbar hsb = new Scrollbar(Scrollbar.HORIZONTAL,1,10,1,100);
hsb.addAdjustmentListener(new HsbAL());
p.add("South",hsb);
Scrollbar vsb = new Scrollbar(Scrollbar.VERTICAL,1,10,1,100);
vsb.addAdjustmentListener(new VsbAL());
p.add("East",vsb);
add(p);
pack();
}
public class HsbAL implements AdjustmentListener
{
public void adjustmentValueChanged(AdjustmentEvent e)
{
switch(e.getAdjustmentType())
{
case AdjustmentEvent.UNIT_INCREMENT:
System.out.println("Adjustment.UNIT_INCREMENT");
break;
case AdjustmentEvent.UNIT_DECREMENT:
System.out.println("Adjustment.UNIT_DECREMENT");
break;
case AdjustmentEvent.BLOCK_INCREMENT:
System.out.println("Adjustment.BLOCK_INCREMENT");
break;
case AdjustmentEvent.BLOCK_DECREMENT:
System.out.println("Adjustment.BLOCK_DECREMENT");
break;
case AdjustmentEvent.TRACK:
System.out.println("Adjustment.TRACK");
break;
}
System.out.println("Wert: " + e.getValue());
}
326
Programmieren in Java
}
public class VsbAL implements AdjustmentListener
{
public void adjustmentValueChanged(AdjustmentEvent e)
{
switch(e.getAdjustmentType())
{
case AdjustmentEvent.UNIT_INCREMENT:
System.out.println("Adjustment.UNIT_INCREMENT");
break;
case AdjustmentEvent.UNIT_DECREMENT:
System.out.println("Adjustment.UNIT_DECREMENT");
break;
case AdjustmentEvent.BLOCK_INCREMENT:
System.out.println("Adjustment.BLOCK_INCREMENT");
break;
case AdjustmentEvent.BLOCK_DECREMENT:
System.out.println("Adjustment.BLOCK_DECREMENT");
break;
case AdjustmentEvent.TRACK:
System.out.println("Adjustment.TRACK");
break;
}
System.out.println("Wert: " + e.getValue());
}
}
public static void main(String args[])
{
PR14175 f = new PR14175();
f.addWindowListener(
new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
f.setBackground(Color.lightGray);
f.setVisible(true);
}
}
5.2.8 ScrollPane
Ein ScrollPane ist ein Container für automatisches horizontales und vertikales
Scrolling. ScrollPane unterscheidet sich durch zwei Eigenschaften von einem
gewöhnlichen Panel:
-
Es kann genau ein Dialogelement aufnehmen und benötigt keinen eigenen Layoutmanager
Es ist in der Lage, eine virtuelle Ausgabefläche zu verwalten, die größer ist als die auf dem
Bildschirm zur Verfügung stehende Ausgabefläche.
Die von einem ScrollPane angezeigte Komponente arbeitet mit einer virtuellen
Ausgabefläche (und merkt nichts von evtl. Größenbeschränkungen auf dem
Bildschirm). Falls die benötigte Ausgabefläche größer ist als die anzeigbare, blendet
ein ScrollPane automatisch die erforderlichen Schieberegister ein, um
Dialogelemente horizontal und vertikal verschieben zu können.
Konstruktoren.
public ScrollPane()
public ScrollPane(int scrollbarDisplayPolicy)
//srollbarDisplayPolicy definiert die Strategie zur Anzeige der Schieberegler entsprechend
327
Programmieren in Java
//den in der folgende Liste aufgelisteten Konstanten:
ScrollPane.SCROLLBARS_AS_NEEDED
ScrollPane.SCROLLBARS_ALWAYS
ScrollPane.SCROLLBARS_NEVER
Die Schieberegler werden genau dann angezeigt,
wenn es erforderlich ist, d.h.: Es wird mehr Platz
benötigt, als für sie zur Anzeige zur Verfügung
steht.
Die Schieberegler werden immer angezeigt
Die Schieberegler werden nie angezeigt, der
Bildschirm kann nur vom Programm aus verschoben
werden
5.2.9 Zeichenbereiche
Ein Canvas ist ein frei definiertes Dialogelement, das in der Grundversion praktisch
keine Funktionalität zur Verfügung stellt. Damit ein Canvas etwas Sinnvolles tun
kann, muß daraus eine eigene Klasse abgeleitet werden.
Konstruktor:
public Canvas()
Die Methode paint(Graphics g). Durch Überlagerung von public void
paint(Graphics g) sorgt eine Canvas-Komponente für die Darstellung auf dem
Bildschirm. Die vom System bereitgestellte Version zeichnet nur die Ausgabefläche
in der aktuellen Hintergrundfarbe. Eine überlagerte Version kann hier natürlich ein
beliebig komplexes Darstellungsverhalten erzielen. Der Punkt (0,0) des übergebenen
Graphics-Objekts entspricht dabei der linken oberen Ecke des Ausgabebereichs.
Ereignisse. Da die Klasse Canvas aus Component abgeleitet ist, bekommt ein
Canvas-Objekt alle Ereignisse zugestellt, die auch an eine Komponente gehen.
Hierzu
zählen
Tastatur-,
Maus-,
Mausbewegungs-,
Fokusund
Komponentenereignisse.
Bsp.:
Die von Canvas abgeleitete Klasse ZeichneCanvas
import java.awt.*;
import java.awt.event.*;
public class ZeichneCanvas extends Canvas
{
// Instanzvariable
public boolean zeichneObjekte;
private Font font;
// Konstruktoren
public ZeichneCanvas()
{
this(400,400);
}
public ZeichneCanvas(int breite,int hoehe)
{
super();
setSize(breite,hoehe);
setBackground(Color.white);
bereinigen();
font = new Font("Helvetica",Font.BOLD,24);
}
// Instanzmethoden
public void bereinigen()
{
zeichneObjekte = false;
repaint();
328
Programmieren in Java
}
public void zeichnen()
{
zeichneObjekte = true;
repaint();
}
public void paint(Graphics g)
{
System.out.println(
"Methode paint wurde aufgerufen, zeichneObjekte = "
+ zeichneObjekte);
if (zeichneObjekte)
{
// Individuelle Auspraegung der Zeichnung
int r = 8;
int i, j;
int x, y;
g.setColor(Color.red);
for (i=1; i<=10; ++i)
{
x = 100 – r * i;
y = (int) (40 + (i – 1) * 1.7321 * r);
for (j=1; j<=i; ++j)
{
g.fillOval(x,y,2*r,2*r);
x += 2 * r;
}
}
// g.drawLine(0,0,100,100);
// g.setColor(Color.blue);
// g.drawString("Hallo I2A und I2B",100,100);
// g.setColor(Color.red);
// g.draw3Drect(150,150,50,50,true);
}
}
}
Die Klasse ZeichneGUI benutzt die Klasse ZeichneCanvas. Sie bezieht die
Zeichenfläche in eine grafische Benutzeroberfläche mit Schaltknöpfen und
Textfeldern ein.
import java.awt.*;
import java.awt.event.*;
public class ZeichneGUI extends Frame
implements MouseMotionListener
{
// Instanzvariable
private TextField xFeld;
private TextField yFeld;
private Button zeichne;
private Button bereinige;
private ZeichneCanvas zeichenflaeche;
// Konstruktoren
public ZeichneGUI()
{
this(200,200);
}
public ZeichneGUI(int breite,int hoehe)
{
super(); setTitle("Demonstration Zeichenflaeche");
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
329
Programmieren in Java
setLayout(new BorderLayout());
Panel panelN = new Panel(); panelN.add(new Label(„x: „));
xFeld = new TextField(5); xFeld.setEditable(false);
panelN.add(xFeld); panelN.add(new Label("y: "));
yFeld = new TextField(5); yFeld.setEditable(false);
panelN.add(yFeld);
add("North",panelN);
zeichenflaeche = new ZeichneCanvas(breite,hoehe);
zeichenflaeche.addMouseMotionListener(this);
add("Center",zeichenflaeche); Panel panelS = new Panel();
zeichne = new Button("Zeichne");
zeichne.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
zeichenflaeche.zeichnen();
}
});
panelS.add(zeichne);
bereinige = new Button("Bereinige");
bereinige.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
zeichenflaeche.bereinigen();
}
});
panelS.add(bereinige); add("South",panelS);
pack();
setVisible(true);
}
// Implementierung des MouseMotionListener-Interface
public void mouseMoved(MouseEvent e)
{
xFeld.setText(" " + e.getX()); yFeld.setText(" " + e.getY());
}
public void mouseDragged(MouseEvent e)
{
xFeld.setText(" " + e.getX()); yFeld.setText(" " + e.getY());
}
// Start der GUI
public static void main(String args[])
{
ZeichneGUI gui = new ZeichneGUI();
}
}
330
Programmieren in Java
5.3 Container
Container sind allgemeine Komponenten, die andere Komponenten oder auch
andere Container enthalten.
5.3.1 Panels
Eine sehr häufig vorkommende Container-Form ist das sog. Panel, das einen
Container darstellt, der am Bildschirm dargestellt werden kann. Ein Panel ist ein
„reiner“ Container, kein eigenes Fenster. Sein einziger Zweck ist es, Komponenten in
einem Fenster anzuordnen.
Erzeugen eines Panels. Das Erzeugen erfolgt mit Hilfe des Konstruktors „Panel()“,
z.B.: Panel meinPanel = new Panel();
Hinzufügen von einem Panel in einen anderen Container. Ein Panel kann bspw. mit
add(meinPanel) in ein Applet eingebaut werden.
Verschachteln von Panels. Panels lassen sich in beliebig vielen Ebenen
verschachteln. Sinn machen solche verschachtelten Panels vor allem in Verbindung
mit den verschiedenen Layouts.
5.3.2 Frames
Ein Frame ist ein voll funktionsfähiges Fenster mit eigenem Titel und Icon. Frames
können Pulldown-Menüs haben und verschieden gestaltete Mauszeiger verwenden.
Erzeugen von Frames. Mit der Frame-Klasse kann ein funktionsfähiges Fenster mit
Menüleiste erstellt werden. Zum Erzeugen von Frames stehen verschiedene
Konstruktoren zur Verfügung:
public Frame()
//Damit kann ein Frame erzeugt werden, der zu Beginn nicht sichtbar ist und keinen Titel hat.
public Frame(String titel)
//Diese Version vergibt einen Titel bei der Erzeugung, das Frame ist zu Beginn nicht sichtbar.
Verändern der Titelzeile. Sie läßt sich in den Klassen Frame und Dialog mit den
Methoden
public void setTitle(String titel)
// ändert die Beschriftung der Titelleiste in titel
public String getTitle()
// Abfrage der Titelleiste
beeinflussen.
Einstellen des Standard-Font. Ein Fenster hat einen Standard-Font, der zur Ausgabe
der Schrift verwendet wird, (wenn nicht im Grafik-Kontext ein anderer Font
ausgewählt wird). Der Standard-Font eines Fensters wird mit „public void
setFont (Font f)“ der Klasse Component eingestellt.
Einstellen von Vorder- Und Hintergrundfarbe. Die Vordergrundfarbe dient zur
Ausgabe von Grafik- und Textobjekten, wenn im Grafik-Kontext keine andere Farbe
331
Programmieren in Java
gesetzt wird. Wird die Einstellung nicht geändert, werden für Vordergrund- und
Hintergrundfarbe die unter Windows eingestellten Standardfarben verwendet. Mit
public void setBackground(Color farbe)
public void setForeground(Color farbe)
können Vordergrund- und Hintergrundfarbe des Fensters geändert werden.
Größe und Position eines Fensters können bestimmt werden über
public void setSize(int breite, int hoehe)
// verändert die Größe des Fensters auf den Wert (breite, hoehe)
public void setSize(Dimension d)
public void setBounds(int x, int y, int breite, int hoehe)
// positioniert ein Fenster der Größe (breite,hoehe) an die Position (x,y)
public void setBounds(Rectangle r)
public void setLocation(int x, int y)
// bewegt die linke obere Ecke an die Bildschirmposition (x,y)
public void setLocation(Point p);
Anzeigen, Wegblenden und Löschen von Frames. Die Konstruktoren haben das
Frame unsichtbar gelassen. Frames müssen vor Gebrauch sichtbar gemacht
werden. Der 1. Schritt besteht darin, einen Frame eine Größe mit der Methode
public void resize(int breite /* Breite vom Frame in Pixel */,
int hoehe /* Hoehe in Pixel */) zu geben. Sichtbar wird der Frame über
die show()-Methode275. Mit public void hide()276 kann der Frame wieder
unsichtbar gemacht werden. Über public void dispose() wird der Frame
beendet. Mit public boolean isShowing() kann geprüft werden, ob das
Fenster bereits angezeigt ist.
Standardlayout für Fenster: Border-Layout.
Hinzufügen von Komponenten: Rahmen sind Container wie Panels, andere
Komponenten können mit der add()-Methode hinzugefügt werden.
5.3.3 Menüs
Jedes Fenster / Frame kann eine eigene Menüleiste besitzen. Jede Menüleiste kann
mehrere Menüs enthalten und jedes Menü beliebige Einträge. Java unterstützt die
Konstruktion von Menüs durch eine Reihe speziell dafür vorgesehenen Klassen:
Klasse
MenuBar
Menu
MenuItem
CheckboxMenuItem
Bedeutung
Beschreibt die Menüzeile (-leiste)eines Fensters
Beschreibet ein einzelnes, der in der Menüzeile enthaltenen Menüs
Bildet die vom Anwender auswählbaren Einträge innerhalb der Menüs
Die Menüleiste
Sie beschreibt das Hauptmenü eines Fensters. Sie befindet sich unterhalb der
Titelleiste am oberen Rand des Fensters und zeigt die Namen der darin enthaltenen
Menüs an. Eine Menüleiste wird durch Instanzen der Klasse MenuBar erzeugt:
275
Für show() aus dem Paket java.awt.Component gibt es als neue Variante die Methode
setVisible(boolean)
276 hide() ist in java.awt.Frame als „deprecated“ deklariert. Statt dessen steht setVisible(boolean) zur
Verfügung
332
Programmieren in Java
Konstruktor. public MenuBar()
Einfügen von Menüs. public void add(Menu m).
Entfernen von bestehenden Menüs. public void remove(int index)
public void remove(MenuComponent m)
Zugriff auf ein beliebiges Menü. public Menu getMenu(int index).
getMenu liefert das Menüobjekt, das sich an der Position mit dem angegebenen
Index befindet. Zum Binden einer Menüleiste an ein Fenster mit dem angegebenen
Index (index) befindet.
Binden einer Menüleiste an einen Frame. public void setMenuBar(MenuBar
mb) .Durch Aufruf dieser Methode wird die angegebene Menüleiste im Fenster
angezeigt und beim Auswählen des Menüpunkts werden Nachrichen ausgelöst und
an das Fenster gesendet. Die Fensterklasse kann diese Nachrichen durch das
Registrieren eines Objekts vom Typ ActionListener bearbeiten.
Menüs.
Sie bilden die Bestandteile einer Menüleiste und werden durch Instanzen der Klasse
Menu repräsentiert.
Konstruktor. public Menu(String label)
// label gibt den Namen des Menüs an
Standardhilfe-Menü: public void setHelpMenu(Menu m) erzeugt ein spezielles
Standardhilfemenü.
Menüeinträge
Einfache Menüeinträge sind die elementaren Bestandteile eines Menüs. Sie besitzen
einen Text, mit dem sie dem Anwender die dahinterstehende Funktion anzeigen.
Wenn der zugehörige Menüpunkt aufgerufen wird, sendet das Programm eine
Nachricht an das zugehörige Fenster, die dann zum Aufruf der entsprechenden
Methode führt.
Erzeugen von Menüeinträgen: public MenuItem(String label). „label“ ist
der Name des Menüeintrags.
Zugriff auf den Namen bzw. Setzen des Namens eines Menüeintrags:
public String getLabel()
public void setLabel(String label)
Neben dem Namen besitzt ein Menüeintrag eine interne Zustandsvariable. Sie zeigt
an, ob der Menüeintrag aktiv ist oder nicht. Nur ein aktiver Eintrag kann vom
Anwender ausgewählt werden und so eine Nachricht auslösen. Nach dem Aufruf des
Konstruktors ist ein Menüeintrag zunächst aktiviert. Er kann durch public void
setEnabled(boolean b) mit Parameterwert „false“ deaktiviert und mit
Parameterwert true aktiviert werden. Über public boolean isEnabled() kann
der aktuelle Zustand abgefragt werden.
Methoden für die Bearbeitung von Menüeinträgen.
public
public
public
public
void
void
void
void
add(MenuItem m)
add(String label)
remove(int index)
remove(MenuComponent Item)
Seperatoren für Menüeinträge. Ein Seperator wird als waagrechter Strich zur
Trennung der Menüeinträge angezeigt:
public void addSeparator()
// fügt den Separator hinter dem zuletzt eingefügten Menüeintrag ein
public void insertSeparator(int index)
// Einfügepostion kann frei angegeben werden.
333
Programmieren in Java
Zugriff auf Menüeintrag. public MenuItem getItem(int index)
Anzahl der Einträge. public int getItemCount()
Die Klasse CheckboxMenuItem. Sie ist aus MenuItem abgeleitet und besitzt
zusätzlich eine interne Zustandsvariable, die zwischen true und false umgeschaltet
werden kann. Die visuelle Darstellung der Zustandsvariablen erfolgt durch Anfügen
oder Entfernen eines „Häkchens“ neben dem Menüeintrag.
Die Instanzierung eines CkeckboxMenuItem erfogt wie bei einem MenuItem.
Zusätzlich stehen die beiden Methoden setState() und getState() zum Setzen
und Abfragen des Zustands zur Verfügung:
public void setState(boolean status)
public boolean getState()
Menüaktionen: Ein Menü macht nur Sinn, wenn damit eine Aktion ausgelöst werden
kann. Diese Aktion kann wie jede andere Aktion mit der Methode actionPerformed
behandelt werden.
Bsp.: Zusammenstellung der wesentlichen Elemente zur Gestaltung von Menüs
import java.awt.*;
import java.awt.event.*;
public class PR53331 extends Frame
{
// TextField t = new TextField("Keine Anzeige",30);
public PR53331()
{
// Menueleiste erzeugen
MenuBar mb = new MenuBar();
// Menueleiste an Rahmen verankern
setMenuBar(mb);
// Menuepunkt erzeugen
Menu m1 = new Menu("Farben"); // Menuepunkt 1
// Ein Untermenue erzeugen
// Menueeintraege in der Menuezeile plazieren
mb.add(m1);
// Eintraege in Menuepunkt 1
MenuItem rot = new MenuItem("Rot");
m1.add(rot);
rot.addActionListener(new RotL());
MenuItem blau = new MenuItem("Blau");
m1.add(blau);
blau.addActionListener(new BlauL());
MenuItem gruen = new MenuItem("Gruen");
m1.add(gruen);
gruen.addActionListener(new GruenL());
MenuItem gelb = new MenuItem("Gelb");
m1.add(gelb);
gelb.addActionListener(new GelbL());
// Untermenue erzeugen
Menu unterMenue = new Menu("Farbanpassung");
// Trennlinie
MenuItem trennLinie = new MenuItem("-");
m1.add(trennLinie);
// Untermenue hinzufuegen
m1.add(unterMenue);
MenuItem farbe = new MenuItem("Farbe");
MenuItem mono = new MenuItem("Mono");
farbe.addActionListener(new FarbeL());
mono.addActionListener(new MonoL());
unterMenue.add(farbe);
unterMenue.add(mono);
// Menue zur Dateibearbeitung
334
Programmieren in Java
Menu d = new Menu("Datei");
MenuItem[] datei =
{
//
new MenuItem("Open"),
// menu shortcut
new MenuItem("Exit",new MenuShortcut(KeyEvent.VK_E))
};
ML ml = new ML();
datei[0].setActionCommand("Open");
datei[0].addActionListener(ml);
datei[1].setActionCommand("Exit");
datei[1].addActionListener(ml);
for (int i = 0; i < datei.length; i++)
d.add(datei[i]);
mb.add(d);
// CheckboxMenuItem
Menu s = new Menu("Sicherheit");
CheckboxMenuItem[] sicherung;
{
new CheckboxMenuItem("Wache"),
new CheckboxMenuItem("Versteck")
};
CMIL cmil = new CMIL();
sicherung[0].setActionCommand("Wache");
sicherung[0].addItemListener(cmil);
sicherung[1].setActionCommand("Versteck");
sicherung[1].addItemListener(cmil);
for (int i = 0; i < sicherung.length; i++)
s.add(sicherung[i]);
d.add(s);
// Hilfsmenue
Menu meineHilfe = new Menu("Hilfe");
mb.setHelpMenu(meineHilfe);
// Eintraege im Hilfsmenue
meineHilfe.add(new MenuItem("Info"));
meineHilfe.add(new MenuItem("Hilfe"));
// Textfeld initialisieren
// t.setEditable(false);
// add(t,BorderLayout.CENTER);
}
class ML implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
MenuItem ziel
= (MenuItem) e.getSource();
String aktionsKommando = ziel.getActionCommand();
if (aktionsKommando.equals("Open"))
{
System.out.println("Oeffnen");
}
else if (aktionsKommando.equals("Exit"))
{
dispatchEvent(new WindowEvent(PR53331.this,
WindowEvent.WINDOW_CLOSING));
}
}
}
class CMIL implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{
CheckboxMenuItem ziel = (CheckboxMenuItem) e.getSource();
String aktionsKommando = ziel.getActionCommand();
if (aktionsKommando.equals("Wache"))
{
335
Programmieren in Java
System.out.println("Wache " + ziel.getState());
}
else if (aktionsKommando.equals("Versteck"))
{
System.out.println("Versteck " + ziel.getState());
}
}
}
class RotL implements ActionListener
{
public void actionPerformed(ActionEvent e)
{ setBackground(Color.red); }
}
class BlauL implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
setBackground(Color.blue);
}
}
class GruenL implements ActionListener
{
public void actionPerformed(ActionEvent e)
{ setBackground(Color.green); }
}
class GelbL implements ActionListener
{
public void actionPerformed(ActionEvent e)
{ setBackground(Color.yellow); }
}
class FarbeL implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
System.out.println("Im Untermenue wurde Farbe ausgewaehlt");
}
}
class MonoL implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
System.out.println("Im Untermenue wurde Mono gewaehlt");
}
}
public static void main(String args[])
{
PR53331 f = new PR53331();
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
f.setSize(300,200); f.setVisible(true);
}
}
5.3.4 Dialoge
Unter einem Dialog versteht man ein Popup-Fenster. Dialoge werden entweder
„modal“ oder „non-modal“ erzeugt. „modal“ bedeutet: Das Dialogfeld blockiert andere
Fenster, wenn es angezeigt wird.
336
Programmieren in Java
Erzeugen. Ein modaler Dialog muß immer von der Klasse Dialog abgeleitet sein.
Dialog bietet 4 Konstruktoren an
public
public
public
public
Dialog(Frame
Dialog(Frame
Dialog(Frame
Dialog(Frame
eltern)
eltern, boolean modal)
eltern, String titel)
eltern, String titel, boolean modal)
Der Parameter modal entscheidet, ob der Dialog modal wird oder nicht.
Zugriff auf modale bzw. „nicht modale“ Eigenschaft. Dafür gibt es die Methoden
public boolean isModal()
// Rückgabewert ist true, falls der Dialog modal ist. Anderenfalls ist er
// false.
public void setModal(boolean b)
Unterbinden der Veränderbarkeit der Größe eines Fensters. Das kann über
public void setResizable(boolean resizable)
public boolean isResizable()
geschehen bzw. überprüft werden.
337
Programmieren in Java
5.4 Die Layout-Manager
Das AWT-System kümmert sich weitgehend selbstständig um Größenanpassung
und Postionierung von Komponenten auf der Oberfläche. Je nach Plattform und
Bedingungen werden Komponenten in die Oberfläche optimal angepaßt. Über
Layout-Manager kann das AWT angewiesen werden, wo Komponenten im Verhältnis
zu anderen Komponenten stehen sollen. Der Layout-Manager bestimmt nach
gewissen Regeln, an welche Stelle die Komponenten am besten passen und ermittelt
die optimale Größe der Komponenten.
5.4.1 Layout-Regeln
Drei Aspekte bestimmen das Aussehen einer AWT-Oberfläche:
1.
Die Plattform und die Bedingungen, die unter dieser Plattform vorliegen. Java
ist plattformunabhängig, daher kann der Programmierer hier keine Aussagen
machen.
Die Reihenfolge, in der die AWT-Komponenten in einen Container eingefügt
werden.
Die Art des Layout-Managers. Das AWT umfaßt 5 verschiedene Typen von
Layout-Managern, die die Oberfläche unterschiedlich aufgliedern:
2.
3.
-
Flow-Layout
Grid-Layout
Border-Layout
Card-Layout
GridBag-Layout
Jedes Panel kann einen eigenen Layout-Manager verwenden.
Erstellen eines Layout-Managers. Zum Erstellen eines Layout-Managers für einen
Panel kann ( in der init()-Methode) die Methode public
void
setLayout(LayoutManager mgr) verwendet werden, z.B.: setLayout(new
FlowLayout());
338
Programmieren in Java
5.4.2 Die einzelnen Layout-Manager
Die FlowLayout-Klasse
<< interface >>
LayoutManager
FlowLayout
public static int CENTER
public static int LEADING
public static int LEFT
public static int RIGHT
public static int TRAILING
<< Konstruktoren >>
public FlowLayout()
public FlowLayout(int align)
public FlowLayout(int align, int hgap, int vgap)
//erzeugt ein FlowLayout mit angegebenen Alignment und einem
//horizontalen bzw. vertikalen Rand
<< Methoden >>
public void addLayoutComponent(String name, Component comp)
public int getAlignment()
//liefert das Alignment des FlowLayout-Managers
public void setAlignment(int align)
//setzt das Alignment mit Hilfe der Konstanten
//FlowLayout.RIGHT, FlowLayout.LEFT, FlowLayout.CENTER
...
Abb. Die Klasse FlowLayout
FlowLayout ordnet Komponenten von links nach rechts an, bis keine weiteren
Komponenten mehr in die Zeile passen. Dann geht es zur nächsten Zeile und bewegt
sich wieder von links nach rechts. Die Standard-.Ausrichtung für ein FlowLayout ist
zentriert.
Der FlowLayout-Manager ist der Standard-Layout-Manager für alle Applets.
Bsp.: Plazierung von Schaltflächen.
Die Komponenten werden in der Reihenfolge, in der sie in den Container eingefügt
werden, spaltenweise von links nach rechts eingeordnet, bis keine weiteren
Komponenten mehr in eine Zeile passen. So führt der folgende Quellcode
import java.awt.*;
public class FlowLayoutTestApplet extends java.applet.Applet
{
public void init()
{
setBackground(Color.yellow);
setLayout(new FlowLayout());
Button meinKnopf1 = new Button("Rot");
add(meinKnopf1);
Button meinKnopf2 = new Button("Blau");
add(meinKnopf2);
Button meinKnopf3 = new Button("Gruen");
add(meinKnopf3);
339
Programmieren in Java
Button meinKnopf4
add(meinKnopf4);
Button meinKnopf5
add(meinKnopf5);
Button meinKnopf6
add(meinKnopf6);
Button meinKnopf7
add(meinKnopf7);
}
}
= new Button("Pink");
= new Button("Rosa");
= new Button("Gelb");
= new Button("Cyan");
mit den im Applet-Tag angegebenen Abmessungen
<HTML>
<HEAD>
<TITLE>FlowLayout-Test</TITLE>
</HEAD>
<BODY>
<APPLET CODE="FlowLayoutTestApplet.class" WIDTH=100 HEIGHT=120>
</APPLET>
</BODY>
</HTML>
zur folgenden Darstellung
Eine Veränderung in der Größe des Applet macht den zeilenweisen Aufbau deutlich:
<HTML>
<HEAD>
<TITLE>FlowLayout-Test</TITLE>
</HEAD>
<BODY>
<APPLET CODE="FlowLayoutTestApplet.class" WIDTH=300 HEIGHT=100>
</APPLET>
</BODY>
</HTML>
340
Programmieren in Java
Die BorderLayout-Klasse
<< interface >>
LayoutManager
BorderLayout
public static String CENTER
public static String NORTH
public static String EAST
public static String SOUTH
public static String WEST
<< Konstruktoren >>
public BorderLayout()
public BorderLayout(int hgap, int vgap)
<< Methoden >>
public void addLayoutComponent(Component comp, Object constraints)
public void layoutContainer(Container target)
public Dimension preferredLayoutSize(Container target)
public void removeLayoutComponent(Component comp)
...
Abb. Die Klasse BorderLayout
Die BorderLayout-Klasse unterteilt einen Container in fünf Bereiche: North, South,
East, West, Center. Falls einem Container Komponenten hinzugefügt werden,
geschieht das über ein spezielles Exemplar der add()-Methode ( mit einem dieser
fünf Bereichsnamen).
Bsp.: BorderLayout-Applet, das einem Applet einige Schaltfläche hinzufügt.
Der Quellcode ist
import java.awt.*;
import java.applet.*;
public class BorderLayoutTestApplet extends Applet
{
public void init()
{
int i = 0;
setBackground(Color.yellow);
setLayout(new BorderLayout());
add("North",new Button("Schaltflaeche " + i++));
add("South",new Button("Schaltflaeche " + i++));
add("East", new Button("Schaltflaeche "
+ i++));
341
Programmieren in Java
add("West", new Button("Schaltflaeche "
+ i++));
add("Center",new Button("Schaltflaeche " + i++));
}
}
Für das Applet wurde folgende Größe vereinbart:
<HTML>
<HEAD>
<TITLE>FlowLayout-Test</TITLE>
</HEAD>
<BODY>
<APPLET CODE="BorderLayoutTestApplet.class" WIDTH=500 HEIGHT=100>
</APPLET>
</BODY>
</HTML>
Das führt zu dem folgenden Layout:
Die GridLayout-Klasse
Diese Klasse teilt den Container in ein Gitter mit gleich großen Zellen ein.
Hinzugefügte Komponenten werden von links nach rechts angeordnet, begonnen
wird bei den linken, oberen Zellen.
Bsp.: Plazieren von Schaltflächen
Der folgende Quellcode
import java.awt.*;
import java.applet.*;
public class GridLayoutTestAppl extends Applet
{
public void init()
{
setLayout(new GridLayout(7,3));
for (int i = 0; i < 20; i++)
add(new Button("Schaltflaeche " + i));
}
}
führt über die folgende Größe im Applet-Tag
<HTML>
<HEAD>
<TITLE>FlowLayout-Test</TITLE>
</HEAD>
<BODY>
<APPLET CODE="GridLayoutTestAppl.class" WIDTH=300 HEIGHT=200>
</APPLET>
</BODY>
</HTML>
zu der folgenden Darstellung
342
Programmieren in Java
Die GridBagLayout-Klasse und die GridBagConstraint-Klasse
Die Klasse GridBagLayout ermöglicht die Anordnung der Komponenten in einem
rasterähnlichen Layout. Zusätzlich zum GridLayout kann kontrolliert werden: Die
Werte einzelner Zellen im Raster, die Proportionen zwischen Zeilen und Spalten
sowie die Anordnung von Komponenten innerhalb der Zellen im Raster. Zur
Erstellung eines GridLayout dienen zwei Klassen:
- GridBagLayout, die den Layoutmanager bereitstellt
- GridBagConstraints, die die Eigenschaften jeder Komponente im Raster bestimmt.
Die GridBagLayout-Klasse ermöglicht einer Komponente auch die Belegung von
mehr als einer Zelle. Der gesamte Bereich, den die Komponente einnimmt, wird
„display area“ genannt. Bevor einem Container eine Komponente hinzugefügt
wird, müssen GridBagLayout Vorschläge unterbreitet werden, wo die Komponente
hingestellt werden soll. Die GridBagConstraint-Klasse besitzt verschiedene
Variable zur Steuerung der Anordnung der Komponenten:
-
-
-
-
-
die Variablen gridx und gridy (Koordinaten der Zelle in der die nächste Komponente plaziert
werden soll). Die obere linke Ecke von GridBagLayout liegt bei (0,0), der Standardwert ist
GridBagConstraints.RELATIVE. Für gridx ist das die erste rechte Zelle neben der letzten
hinzugefügten Komponente. Für gridy ist es genau die Zelle, die unterhalb der letzten
Komponente hinzugefügt wurde.
die Variablen gridwidth und gridheight bestimmen, wie viele Zellen hoch und breit eine
Komponente sein sollte (Standardwert ist 1). Der Wert GridBagConstraints.REMAINDER für
gridwidth bestimmt, daß eine Komponente die letzte in der Spalte sein sollte.
Die Variable fill gibt an, welche Dimension einer Komponente sich verändern soll, wenn eine
Komponente kleiner als der Anzeigebereich ist. Gültige Werte sind: NONE, BOTH, HORIZONTAL
und VERTICAL. „GridBagConstraints.BOTH“ sorgt für eine Streckung der Komponenten in
beiden Richtungen so, daß sie den Anzeigebereich voll ausfüllen.
anchor wird eingesetzt, wenn die Komponente kleiner als ihr Anzeigebereich ist. Sie zeigt an, wo
die Komponente innerhalb des Anzeigebereichs plaziert werden soll. Der Standardwert ist
„GridBagConstraints.CENTER“. Er gibt an, welche Komponente im „Zentrum des
Anzeigebereichs“ stehen soll. Die anderen Komponenten sind Kompasspunkte:
GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST etc.
Die Variable weightx und weighty definieren die Gewichtung für freien Raum innerhalb eines
Containers und bestimmen die relative Größe der Komponenten. Ein Komponente mit einem Wert
343
Programmieren in Java
-
von 2.0 für weightx nimmt doppelt soviel Platz in horizontaler Richtung wie eine Komponente mit
dem Wert 1.0 für weightx.
Durch „insets“ (Objekte der Klasse Insets)277 wird der freizuhaltende Rand zwischen
Komponente und Gitter festgelegt. Die Standardwerte für alle 4 Seiten sind Null.
Die Elemente können zum Teil auch vergrößert werden. Die Pixel, die rechts und links zuzufügen
sind, gibt ipadx an. Wieviel oben und unten hinzukommt, speichert ipady. (ipadx, ipady sind
standardmäßig 0).
gridwidth
insets
ipady
fill
gridheight
ipadx
Bsp.278: Plazieren von Schaltflächen
import java.awt.*;
import java.util.*;
import java.applet.Applet;
public class GridBagBsp extends Applet
{
protected void macheSchalter(String name,
GridBagLayout gridbag,
GridBagConstraints c)
{
Button schalter = new Button(name);
gridbag.setConstraints(schalter, c);
add(schalter);
}
public void init()
{
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setFont(new Font("Helvetica", Font.PLAIN, 14));
setLayout(gridbag);
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
macheSchalter("Schaltflaeche1", gridbag, c);
macheSchalter("Schaltflaeche2", gridbag, c);
macheSchalter("Schaltflaeche3", gridbag, c);
// letzter Schalter in der Zeile:
c.gridwidth = GridBagConstraints.REMAINDER;
macheSchalter("Schaltflaeche4", gridbag, c);
// neue Zeile, weightx wiederherstellen:
c.weightx = 0.0;
macheSchalter("Schaltflaeche5", gridbag, c);
// vorletztes Element der Zeile:
c.gridwidth = GridBagConstraints.RELATIVE;
macheSchalter("Schaltflaeche6", gridbag, c);
// letztes Element:
c.gridwidth = GridBagConstraints.REMAINDER;
macheSchalter("Schaltflaeche7", gridbag, c);
277
Beim Erstellen eines neuen Layout-Managers erzeugte horizontale und vertikale Abstände dienen zur
Bestimmung des Platzes zwischen Komponenten in einem Panel. Die Klasse Insets bietet Eckeinsetzwerte für
oben, unten, links und rechts an, die dann verwendet werden, wenn das Panel gezeichnet wird.
278vgl. pr54205
344
Programmieren in Java
// neue Zeile, Wert wiederherstellen:
c.gridwidth = 1;
c.gridheight = 2;
c.weighty = 1.0;
macheSchalter("Schaltflaeche8", gridbag, c);
c.weighty = 0.0;
c.gridwidth = GridBagConstraints.REMAINDER;
c.gridheight = 1;
macheSchalter("Schaltflaeche9", gridbag, c);
macheSchalter("Schaltflaeche10", gridbag, c);
resize(300, 100);
}
public static void main(String args[])
{
Frame f = new Frame("GridBag Layout Beispiel");
GridBagBsp bsp = new GridBagBsp();
bsp.init();
f.add("Center", bsp);
f.pack();
f.resize(f.preferredSize());
f.show();
}
Die CardLayout-Klasse
CardLayout279 plaziert die Elemente auf sich gegenseitig verdeckende Karten.
Jeweils eine dieser Karten kann sichtbar gemacht werden.
Das Null-Layout
Ein Aufruf der Methode „setLayout“ mit dem Argument null erzeugt ein NullLayout. In diesem Fall verwendet das Fenster keinen Layoutmanager, sondern
überläßt die Positionierung der Komponenten der Anwendung. Je Komponente sind
drei Schritte auszuführen
-
Erzeugen der Komponente
Festlegung von Größe und Position der Komponente
Übergabe der Komponente an das Fenster.
Größe und Position des Dialogelements können mit
public void setBounds(int x, int y, int breite, int hoehe)
279 Das CardLayout ist als Notizbuchersatz durch die Einführung der Klasse JTabbedPane in Swing fast
überflüssig geworden
345
Programmieren in Java
festgelegt werden. Der Punkt
(breite,hoehe) die Größe.
(x,y)
bestimmt
die
Bsp.:
import java.awt.*;
import java.awt.event.*;
public class NL extends Frame
{
public static void main(String args[])
{
NL f = new NL();
f.setVisible(true);
}
public NL()
{
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
setVisible(false);
dispose();
System.exit(0);
}
});
setSize(300,250);
setLayout(null);
for (int i = 0; i < 5; i++)
{
Button schalter = new Button("Schalter" + (i+1));
schalter.setBounds(10+i*35,40+i*35,100,30);
add(schalter);
}
}
}
346
Anfangsposition
und
Programmieren in Java
5.5 Die Event-Modelle 1.0 und 1.1
5.5.1 Der AWT-Handler 1.0
Die Methode handleEvent
Die handleEvent()-Methode280 ist unter dem AWT-Event-Handler die allgemeinste
Art, wie das AWT auf irgendwelche Ereignisse eingeht, die auf der
Benutzeroberfläche stattfinden. Ereignisse werden innerhalb der handleEvent()Methode interpretiert und dann gezielt passende Methoden aufgerufen. Damit die
handleEvent()-Funktion nicht zu groß und unübersichtlich wird, ruft
handleEvent() mehrere Hilfsfunktionen (mouseEnter, keydown, action, ... )
auf, die die jeweils zugeordneten Events bearbeiten. Die Verarbeitung von
Ereignissen stützt sich auf die Klasse Event. Die Instanzvariable „id“ der Klasse
Event enthält die Art des Ereignisses und liefert alle notwendigen Informationen. Das
Attribut „target“ enthält die Information, welches Objekt das Ereignis ausgelöst hat.
Bsp.: Darstellung einer Datei im Textbereich eines Fensters (Frame)
import java.awt.*;
import java.io.*;
public class DateiDarsteller extends Frame
{
// Instanzvariable
String dateiName;
// Konstruktoren
public DateiDarsteller() throws IOException
{
super("Dateidarsteller ");
MenuBar menue = new MenuBar();
Menu m = new Menu("Datei");
m.add(new MenuItem("Oeffnen"));
m.add(new MenuItem("Schliessen"));
menue.add(m);
// Installiere diesen Menuebalken
setMenuBar(menue);
this.show();
}
// Methoden
public void dateiDarstellen() throws IOException
{
try {
File d = new File(dateiName);
int groesse = (int) d.length();
int anzZeich = 0;
FileInputStream ein = new FileInputStream(d);
byte[] daten = new byte[groesse];
while (anzZeich < groesse)
anzZeich += ein.read(daten,anzZeich,groesse);
String s = new String(daten,0);
TextArea textBereich = new TextArea(s,24,80);
textBereich.setFont(new Font("Helvetiva",Font.PLAIN,10));
textBereich.setEditable(false);
this.add("Center",textBereich);
}
280
Die Methode ist in der Klasse Component definiert, von der die Klasse java.applet.Applet
abgeleitet ist. Dadurch steht die Methode allen Applets zur Verfügung,
347
Programmieren in Java
catch (IOException e) { }
this.show();
}
public boolean handleEvent(Event e)
{
if ((e.id == Event.ACTION_EVENT) && (e.target instanceof
MenuItem))
{
if (((MenuItem) e.target).getLabel().equals("Oeffnen"))
{
FileDialog fd = new FileDialog(this,"Dateidialog");
fd.show();
dateiName = fd.getFile();
System.out.println("Dateiname: " + dateiName);
try { this.dateiDarstellen(); }
catch (IOException a ) { }
return true;
}
if (((MenuItem) e.target).getLabel().equals("Schliessen"))
{
this.hide();
this.dispose();
System.exit(0);
return true; }
}
return false;
}
public static void main(String argv[]) throws IOException
{
try {
DateiDarsteller DD = new DateiDarsteller();
}
catch (IOException e) { System.out.println(e); }
}
}
Behandlung von Aktionsereignissen
Aktionsereignisse werden über public boolean action(Event e, Object
arg) behandelt. Zuerst muß innerhalb der methode überprüft werden, welche
Komponente der Benutzeroberfläche die Aktion erzeugt hat. Zur vereinfachten
Bearbeitung beinhaltet das Event-Objekt, das beim Aufruf von action() erhalten
wird, eine "target"-Instanzvariable, die eine Referenz zu dem Objekt, das das
Ereignis aufgenommen hat, enthält. Mit dem instanceof-Operator kann dann
bestimmt werden, von welcher Komponente das Ereignis ausging. Das zweite
Argument dient zum Bestimmen des Labels281, der Elemente, des Inhalts der
Komponenten282. Anstatt des zusätzlichen Arguments kann auch die Methode283
getLabel verwendet werden.
Behandlung von Fokus-Ereignissen
Für die Ereignisse "Fokuserhalt" und "Fokusverlust" können die Methoden
281
vgl. pr52105
Nicht vergessen: Das Argument in den richtigen Objekt-Typ zu casten
283 vgl. pr54305
282
348
Programmieren in Java
public boolean getFocus(Enet evt, Object arg)
bzw.
public boolean lostFocus(Event evt, Object arg)
verwendet werden.
Ereignisse von Listenfeldern
Listenfelder erzeugen drei verschiedene Ereignisarten: Auswahl bzw. Abwahl eines
Eintrags in der Liste bzw. Doppelklick auf einen Eintrag. Ein Doppelklick auf einen
Eintrag kann mit der Methode action bearbeitet werden. Auswahl und Abwahl eines
Listeneintrags kann mit "handleEvent" und Überprüfen auf die Ereignisse mit
LIST_SELECT und LIST_DESELECT erfolgen.
5.5.2 Das Event-Handling in Java 1.1 bzw. Java 1.2
Im neuen Ereignismodell
java.util.EventObject.
java.awt.AWTEvent.
erben die Ereignisklassen von der
AWT-Events
erben
von
der
Klasse
Klasse
EventListener
Falls eine Klasse im Ereignisbehandlungsmodell von Java 1.2 aus ein Ereignis
reagieren will, muß diese eine Schnittstelle implementieren, die dieses Ereignis
verarbeitet. Diese Schnittstellen werden als EventListener bezeichnet. Jeder
Listener behandelt eine bestimmte Ereignisart. Eine Klasse kann so viele
Listener implementieren, wie benötigt werden. Die folgenden EventListener
stehen zur Verfügung:
ActionListener
AdjustmentListener
FocusListener
ItemListener
KeyListener
MouseListener
MouseMotionListener
WindowListener
Aktionsereignisse, die durch den Benutzer ausgelöst werden, z.B.
Klick auf eine Schaltfläche
Ereignisse, die erzeugt werden, wenn werte einer Komponente
eingestellt werden (z.B. Bewegung eines Schiebers einer
Bildlaufleiste)
Ereignisse, die erzeugt werden, wenn eine Komponente, z.B. ein
textfeld, den eingabefokus erhält oder verliert.
Ereignisse, die erzeugt werden, wenn ein Element, z.B. ein
Kontrollkästchen, verändert wurde
Tastaturereignisse, die bei Tastatureingaben erzeugt werden.
Mausereignisse, die erzeugt werden, wenn mit der maus geklickt
wird, die Maus in den Bereich einer Komponente eintritt bzw. diese
wieder verläßt
Mausereignisse, die die Bewegung einer Maus über eine
Komponente verfolgen
Ereignisse, die von Fenstern erzeugt werden
Das Paket java.awt.event beinhaltet alle elementaren EventListener. Über
import java.awt.event.* erfolgt das Importieren in die Anwendungen.
349
Programmieren in Java
Event-Handling mit Hilfe lokaler Klasse
Für die Ereignisbehandlung werden lokale Klassen herangezogen. Im JDK 1.0
wurden Klassen nur auf Paketen definiert, eine Schachtelung war nicht möglich. Im
JDK 1.1 kann innerhalb bestehender Klassen eine neue Klasse definiert werden
(Konzept der „Inner Classes“), die nur innerhalb der bestehenden Klasse sichtbar ist.
Objektinstanzen der inneren Klasse können nur aus der umfassenden Klasse heraus
erzeugt werden. Allerdings kann die innere Klasse auf die Instanzvariablen der
äußeren Klasse zugreifen.
Mit Hilfe lokaler Klassen werden die benötigten EventListener implementiert. Dazu
wird in dem GUI-Objekt, das einen Event-Handler benötigt, eine lokale Klasse
definiert (und aus einer passenden AdapterKlasse abgeleitet).
Ein einführendes Bsp.: Event-Handling nach der Bearbeitung von Schaltflächen des
AWT in Version 1.0 und 1.1
// Einfangen von Klicks auf Schaltflaechen
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class
{
Button sch1
Button sch2
public void
{
SchalterAppl extends Applet
= new Button("Schaltflaeche 1");
= new Button("Schaltflaeche 2");
init()
sch1.addActionListener(new Sch1());
sch2.addActionListener(new Sch2());
add(sch1);
add(sch2);
}
class Sch1 implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
getAppletContext().showStatus("Schaltflaeche 1");
}
}
class Sch2 implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
getAppletContext().showStatus("Schaltflaeche 2");
}
}
/*
public boolean action(Event e, Object welcheAktion)
{
if (e.target.equals(sch1))
getAppletContext().showStatus("Schaltflaeche 1");
else if (e.target.equals(sch2))
getAppletContext().showStatus("Schaltflaeche 2");
else
return super.action(e,welcheAktion);
return true;
}
350
Programmieren in Java
*/
}
Einfügen / Entfernen passender Listener
Das neue Event-Modell von Java 1.1 bzw. 1.2 hat mit dem JDK 1.0 offensichtlich nur
noch wenig gemeinsam. Alle AWT-Komponenten haben im neuen AWT-EventHandling
„addXXXListener()“und
„removeXXXListener()“-Methoden
erhalten. „XXX“ beschreibt den Typ des Events. Die folgende Tabelle zeigt
Ereignisse, „Listeners“, Methoden und Komponenten, die über addXXXListener()
bzw. removeXXXListener() auf spezifische Ereignisse reagieren:
Event, „listener interface“
„remove“-Methoden
ActionEvent
ActionListener
addActionListener()
removeActionListener()
AdjustmentEvent
AdjustmentListener
addAdjustmentListener()
removeAdjustmentListener()
ComponentEvent
ComponentListener
addComponentListener()
removeComponentListener()
ContainerEvent
ContainerListener
addContainerListener()
removeContainerListener()
FocusEvent
FocusListener
addFocusListener()
removeFocusListener()
KeyEvent
KeyListener
addKeyListener()
removeKeyListener()
MouseEvent
MouseListener
addMouseListener()
removeMouseListener()
MouseEvent
MouseMotionListener
addMouseMotionListener()
removeMouseMotionListener()
WindowEvent
WindowListener
addWindowListener()
removeWindowListener()
ItemEvent
ItemListener
addItemListener()
removeItemListener()
und
„add“-
bzw. Komponenten, die dieses Ereignis unterstützen
Button, List, TextField, MenuItem und seine
Abkömmlinge einschl. CheckboxMenuItem, Menu
und PopupMenu
Scrollbar,
alles was mit der Erzeugung vom „Adjustable“Interface zu tun hat
Component und seine Abkömmlinge, einschl.
Button, Canvas, Checkbox, Choice, Container,
Panel, Applet, ScrollPane, Window, Dialog,
FileDialog, Frame, Label, List, Scrollbar,
TextArea, TextField
Container und seine Abkömmlinge, einschl.
Panel, Applet, ScrollPane, Window, Dialog,
FileDialog, Frame
Component und seine Abkömmlinge einschl.
Button, Canvas, Container, Panel, Applet,
ScrollPane, Window, Dialog, FileDialog, Frame,
Label, List, Scrollbar, TextArea, TextField
Component und seine Abkömmlinge einschl.
Button, Canvas, Checkbox, Choice, Container,
Panel, Applet, ScrollPane, Window, Dialog,
FileDialog, Frame, Label, List, Scrollbar,
TextArea, TextField
Component und seine Abkömmlinge einschl.
Button, Canvas, Checkbox, Choice, Container,
Panel, Applet, ScrollPane, Window, Dialog,
FileDialog, Frame, Label, List, Scrollbar,
TextArea, TextField
Component und seine Abkömmlinge einschl.
Button, Canvas, Checkbox, Choice, Container,
Panel, Applet, ScrollPane, Window, Dialog,
FileDialog, Frame, Label, List, Scrollbar,
TextArea, TextField
Window und seine Abkömmlinge einschl. Dialog,
FileDialog und Frame
Checkbox, CkeckboxMenuItem, Choice, List und
alles,
was
das
ItemSelectable
Interface
implementiert
351
Programmieren in Java
TextEvent
TextListener
addTextListener()
removeTextListener()
Alles was von TextComponent einschl. TextArea
und TextField abgeleitet ist
Abb.: Event- und Listener-Typen
Jeder Komponenten-Typ unterstützt nur bestimmte Ereignis-Typen:
Komponenten-Typ
Adjustable
Applet
Button
Canvas
Checkbox
CheckboxMenuItem
Choice
Component
Container
Dialog
FileDialog
Frame
Label
List
Menu
MenuItem
Panel
PopupMenu
Scrollbar
ScrollPane
TextArea
TextComponent
TextField
Window
Ereignis, das durch die Komponente unterstützt
wird
AdjustmentEvent
ContainerEvent,
FocusEvent,
KeyEvent,
MouseEvent, ComponentEvent
ActionEvent,
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
ItemEvent, FocusEvent, KeyEvent, MouseEvent,
ComponentEvent
ActionEvent, ItemEvent
ItemEvent, FocusEvent, KeyEvent, MouseEvent,
ComponentEvent
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
ContainerEvent,
FocusEvent,
KeyEvent,
MouseEvent, ComponentEvent
ContainerEvent,
WindowEvent,
FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
ContainerEvent,
WindowEvent,
FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
ContainerEvent,
WindowEvent,
FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
ActionEvent,
FocusEvent,
KeyEvent,
MouseEvent, ItemEvent, ComponentEvent
ActionEvent
ActionEvent
ContainerEvent,
FocusEvent,
KeyEvent,
MouseEvent, ComponentEvent
ActionEvent
AdjustmentEvent,
FocusEvent,
KeyEvent,
MouseEvent, ComponentEvent
ContainerEvent,
FocusEvent,
KeyEvent,
MouseEvent, ComponentEvent
TextEvent, FocusEvent, KeyEvent, MouseEvent,
ComponentEvent
TextEvent, FocusEvent, KeyEvent, MouseEvent,
ComponentEvent
ActionEvent, TextEvent, FocusEvent, KeyEvent,
MouseEvent, ComponentEvent
ContainerEvent,
WindowEvent,
FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
Abb.: Komponenten-Typ und Ereignisse
Methoden für die Ereignisbehandlung
352
Programmieren in Java
Da nun bekannt ist, welche Ereignisse eine bestimmte Komponente unterstützt, kann
das zugehörige „Listener“-Interface angegeben werden:
„Listener“-Interface
ActionListener
AdjustmentListener
ComponentListener
ComponentAdapter
ContainerListener
ContainerAdapter
FocusListener
FocusAdapter
KeyListener
KeyAdapter
MouseListener
MouseAdapter
MouseMotionListener
MouseMotionAdapter
WindowListener
Windowadapter
ItemListener
TextListener
Methoden
actionPerformed(ActionEvent)
AdjustmentValueChanged(
AdjustmentEvent)
componentHidden(ComponentEvent)
componentShown(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)
focusGained(FocusEvent)
focusLost(FocusEvent)
keyPressed(KeyEvent)
keyReleased(KeyEvent)
keyTyped(KeyEvent)
mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
mouseDragged(MouseEvent)
mouseMoved(MouseEvent)
windowOpened(WindowEvent)
windowClosing(WindowEvent)
windowClosed(WindosEvent)
windowActivated(WindowEvent)
windowDeactivated(WindowEvent)
windowIconified(WindowEvent)
windowDeiconified(WindowEvent)
itemStateChanged(ItemEvent)
textValueChanged(TextEvent)
Abb.: Listener-Interface
Adapter besorgen Default-Methoden für jede Methode im Interface. Nur die Methode,
die geändert wird, muß überschrieben werden.
Komponenten des AWT in Fenstern bzw. Applets unter Java 1.1
Die in den folgenden Programmen verwendeten Komponenten können in einem
Fenster (Frame) über die Instanz eines Applet dargestellt werden. Das Programm
enthält deshalb zusätzlich zu den für Applets nötigen Methoden eine main()Methode, die eine Instanz eines Applets innerhalb eines Frame aufbaut.
Bsp.: Bearbeitung von zwei Schaltflächen und einem Textfeld284
// Eine Anwendung und ein Applet
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class SchalterApplet extends Applet
284
pr55202
353
Programmieren in Java
{
Button sch1 = new Button("Schaltflaeche 1");
Button sch2 = new Button("Schaltflaeche 2");
TextField t = new TextField(20);
public void init()
{
sch1.addActionListener(new Sch1());
sch2.addActionListener(new Sch2());
add(sch1);
add(sch2);
add(t);
}
class Sch1 implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
t.setText("Schaltflaeche 1");
}
}
class Sch2 implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
t.setText("Schaltflaeche 2");
}
}
// Schliessen der Applikation
static class WL extends WindowAdapter
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
}
// main()-Methode fuer die Applikation
public static void main(String[] args)
{
SchalterApplet applet = new SchalterApplet();
Frame einRahmen = new Frame("Schalterdarstellung");
einRahmen.addWindowListener(new WL());
einRahmen.add(applet,BorderLayout.CENTER);
einRahmen.setSize(300,200);
applet.init();
applet.start();
einRahmen.setVisible(true);
}
}
354
Programmieren in Java
5.6 Benutzerschnittstellen mit Swing
Die Oberflächentechnik „Swing“ (Paket: javax.swing) von Java 2 umfaßt eine
Vielzahl mächtiger Klassen (, z.B. Tabellen, Baumansichten). Swing benutzt den
Model-View-Controller285-Ansatz, d.h. die konsequente Trennung der Daten und der
Darstellung. Swing-Elemente fallen in zwei Gruppen:
- Fenster, die direkt vom Betriebssystem dargestellt werden, z.B. JFrame, JDialog oder JApplet.
Diese sind von den jeweiligen AWT-Komponenten abgeleitet.
- Kontrollelemente, die von Java selbst gerendert werden. Diese sind Subklassen von JComponent.
javax.swing, javax.swing.event, javax.swing.table, javax.swing.tree, javax.swing.text
Interfaces
- ButtonModel
- ComboBoxModel
- ListModel
- TreeModel
- TableModel
- TableColumnModel
- BoundedRangeModel
- Renderer
- ListCellRenderer
- TableCellRenderer
- TableCellEditor
- TreeCellRenderer
- TreeCellEditor
- ComboBoxEditor
- TreeNode
- MutableTreeNode
- Border
- Scrollable
- MenuElement
- Document
- Caret
- Style
- StyledDocument
- DesktopManager
- Icon
- SwingConstants
- *Listener
Klassen
Ereignisbehandlung
KontrollElemente
Fenster
- ListDataEvent
- JFrame
- JComponent
- ListSelectionEvent - JButton
- JDialog
- TableModelEvent
- JPanel
- JCheckBox
- TreeModelEvent
- JRadioButton - JApplet
- TreeExpansionEvent - JLabel
- JFileChooser
- TreeSelectionEvent - JTextField
- JColorChooser
- CaretEvent
- JPasswordField - JDesktopPane
- MenuEvent
- JTextArea
- JInternalFrame
- PopupMenuEvent
- JComboBox
- JOptionPane
- JList
- JScrollPane
- JScrollBar
- JTabbedPane
- JSlider
- JProgressBar
- JTree
- JTable
Modelimplementierung
Border
- AbstractListModel
- DefaultListModel
- AbstractTableModel
- ...
285
- BorderFactory
- TitledBorder
- LineBorder
- ...
Bilder
Menüs
- JMenuBar
- JMenuItem
- JCheckboxMenuItem
- JRadioBoxMenuItem
- JMenu
- JPopupMenu
- JToolBar
- JSeparator
Sonstiges
- ...... - ImageIcon
-ButtonGroup
- KeyStroke
- ...
Beim Model-View-Controller-Entwurfsmuster (MVC) unterscheidet man drei Arten von
Applikationselementen:
Model (Datenmodell)
Im Datenmodell werden Zustand und Funktionalität gespeichert. Es kann Anfragen vom View über seinen
Zustand beantworten und Zustandsänderungen vom Controller oder anderen Objekten verarbeiten.
View (Darstellung „look“)
Die Ansicht weiß nichts oder nur sehr wenig (über das Datenmodell und stellt lediglich die Daten dar).
Controller (Steuerung „feel“)
Die Steuerung stellt ein Modell der realen Welt dar und reagiert auf Benutzereingaben von Maus, Tastatur und
anderen Eingabegeräten.
355
Programmieren in Java
5.6.1 Die Komponentenstruktur
javax.swing.JComponent ist von java.awt.Container abgeleitet.
Container können alle JComponents auch andere Komponenten enthalten.
Container
JComponent
public void addAncestorListener(AncestorListener l)
public void addNotify()
...
public boolean contains(int x, int y)
public JToolTip createToolTip()
...
public float getAlignmentX()
public float getAlignmentY()
...
public Border getBorder()
public void setBorder(Border border)
public Rectangle getBounds(Rectangle rv)
protected Graphics getComponentGraphics (Graphics g)
...
public Graphics getGraphics()
public int getHeight()
public Insets getInsets()
...
public Point getLocation(Point rv)
public Dimension getMaximumSize()
public Dimension getMinimumSize()
...
…
public Dimension getSize(Dimension rv)
public Point getToolTipLocation(MouseEvent event)
public String getToolTipText()
public String getToolTipText(MouseEvent event)
...
public int getWidth()
public int getX()
public int getY()
...
public void paint(Graphics g)
...
public void repaint(Rectangle r)
...
public void setToolTipText(String text)
protected void setUI(ComponentUI newUI)
public void setVisible(boolean aFlag)
...
public void update(Graphics g)
public void updateUI()
…
Abb. Die Klasse JComponent
356
Als
Programmieren in Java
Viele der Swing-Komponenten sind direkt oder indirekt aus der Klasse JComponent
abgeleitet
Instanzen von JComponent können ausgestattet werden mit Umrandungen,
Tooltips, transparentem Hintergrund, Doppelpufferung, etc.
Umrandungen
Instanzen von JComponent können über die Methode setBorder() Umrandungen
erhalten.
public void setBorder(Border border)
Border ist ein Interface, zu dem es verschiedene Implementierungen gibt. Die
wichtigsten sind:
Klassenname
EmptyBorder
LineBorder
BevelBorder
EtchedBorder
CompoundBorder
TitledBorder
Beschreibung
Unsichtbarer Rand mit einstellbarer Dicke
Einfache Linie mit einstellbarer Farbe und Dicke
Erhabener und vertiefter 3D-Effekt
Eingelassene Line mit 3D-Effekt
Aus 2 anderen Umrandungen zusammengesetzt
Umrandung mit einem eingeketteten Text
Abb.: Border-Implementierungen
Die Klassen besitzen sehr unterschiedliche Konstruktoren, mit denen ihre jeweiligen
Eigenschaften festgelegt werden. Border-Instanzen können mit new über die Klasse
BorderFactory
mit
den
Methoden
createEmptyBorder(),
createLineBorder(), usw. erzeugt werden.
Bsp.286:
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.border.*;
public class Raender extends JPanel
{
public Raender()
{
setLayout(new GridLayout(2,4));
add(showBorder(new TitledBorder("Titel")));
add(showBorder(new EtchedBorder()));
add(showBorder(new LineBorder(Color.blue)));
add(showBorder(new MatteBorder(5,5,30,30,Color.green)));
add(showBorder(new BevelBorder(BevelBorder.RAISED)));
add(showBorder(new SoftBevelBorder(BevelBorder.LOWERED)));
add(showBorder(new CompoundBorder(new EtchedBorder(),
new LineBorder(Color.red))));
}
static JPanel showBorder(Border b)
{
JPanel jp = new JPanel();
jp.setLayout(new BorderLayout());
String nm = b.getClass().toString();
nm = nm.substring(nm.lastIndexOf('.')+1);
jp.add(new JLabel(nm,JLabel.CENTER),BorderLayout.CENTER);
286
pr56001
357
Programmieren in Java
jp.setBorder(b);
return jp;
}
public static void main(String args[])
{
JFrame frame = new JFrame("Begrenzungen");
// frame.addWindowListener(new WindowClosingAdapter(true));
frame.getContentPane().add(new Raender(),BorderLayout.CENTER);
frame.setLocation(100,100);
frame.setSize(500,300);
frame.pack();
/* Die Methode pack() sorgt fuer die Reduzierung der
Groesse des Frame, dass alle Komponenten gerade darin
noch Platz finden
*/
frame.setVisible(true);
/* Macht den Frame sichtbar */
}
}
Abb.
Tooltips
JComponent kann Komponenten ein Tooltip-Text zuweisen. Dieser wird angezeigt,
wenn die Maus über das Dialogelement bewegt und dort angehalten wird. TooltipTexte werden mit setToolTipText() zugewiesen bzw. mit getToolTipText()
abgefragt:
public void setToolTipText(String text)
public void getToolTipText()
Das Fenster mit dem Hilfetext ist immer im Swing-Fenster und nie außerhalb. Passt
die Hilfe nicht auf das Fenster wird sie gar nicht dargestellt, und die Komponente
macht durch "Flackern" auf sie aufmerksam
Bsp.287:
import java.awt.*;
import javax.swing.*;
public class TooltipDemo
{
public static void main(String args [])
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String text = "<html>Ich brauche Hilfe.<p>Tempo</html>";
JButton button = new JButton(text);
String hilfe = "<html>Hier ist sie, die <b>Hilfe:</b>" +
"<ul><li>Cool bleiben<li>Handbuch lesen</ul></html>";
287
pr56001
358
Programmieren in Java
button.setToolTipText(hilfe);
frame.getContentPane().add(button);
frame.setSize(250, 250);
frame.show();
}
}
Abb.: Tooltip-Demonstration
Bildschirmdarstellung der Komponenten
Normalerweise braucht eine GUI-Anwendung sich um die konkrete Darstellung nicht
zu kümmern. Das ändert sich, wenn eine eigene Komponente entwickelt werden soll.
Bei AWT-Anwendungen wurde dies aus Canvas abgeleitet, in überlagerten Varianten
von paint() oder update() wurde die nötige Bildschirmausgabe bereit gestellt.
Die Methode paint() hat in JComponent eine recht aufwendige Implementierung
und wird normalerweise nicht mehr überlagert. Im Prinzip ruft sie nacheinander ihre
Methoden paintComponent(), paintBorder() (Zeichnen der Umrandung) und
paintChildren() (Zeichnen der Dialogelemente) auf. Für die Darstellung der
eigenen Komponenten ist
protected void paintComponent(Graphics g)
zuständig. In JComponent wird jeder Aufruf von paintComponent() an das
ComponentUI delegiert. Jede Swing-Komponente besitzt ein ComponentUI, das je
Look and Feel unterschiedlich sein kann. Eine selbstdefinierte Komponente muß also
entweder für jedes Look and Feel ein passendes ComponentUI zur Verfügung
stellen oder die Bildschirmdarstellung durch Überlagern von paintComponent()
selbst erledigen.
Doppelpufferung
Swing-Komponenten, die aus JComponent abgeleitet sind, können automatisch
doppelgepuffert werden. Dazu ist lediglich der Aufruf von
public void setDoubleBuffered(boolean einFlag)
mit Übergabe von "true" erforderlich. Mit
359
Programmieren in Java
public boolean isDoubleBuffered()
kann der aktuelle Zustand abgefragt werden.
Größenangaben
In der Klasse Component sind die Methoden getMinimumSize(),
getPreferredSize(), getMaximumSize() definiert. Sie werden in abgeleiteten
Klassen überlagert, um dem Layoutmanager die minimale, optimale, maximale
Größe mitzuteilen. In JComponent gibt es zusätzlich
public void setMinimumSize(Dimension minimumSize)
public void setPreferredSize(Dimension preferredSize)
public void setMaximumSize(Dimension maximumSize)
zur Veränderung der Größenangaben bestehender Komponenten.
Invalidierung / Validierung
Zur Mitteilung an den Container, dass das Layout seiner Komponenten komplett
aufgebaut werden soll, ist validate() aufzurufen.
public void validate()
Zuvor muß der Container, auf dem der Aufruf erfolgte, zuvor mit
public void invalidate()
als ungültig deklariert sein.
In JComponent gibt es zusätzlich revalidate(), das die beiden Schritte
nacheinander durchführt.
360
Programmieren in Java
5.6.2 Fenster
Container
Window
JComponent
Frame
Dialog
JFrame
JDialog
Container contentPane
Int defaultCloseOperation
<< Konstruktoren >>
public JFrame()
public JFrame(String titel)
<< Konstruktoren >>
public JDialog()
Abb.: Die Klassen JFrame und JDialog
Die Swing-Klassen für Hauptfenster (JFrame) und Dialogboxen (JDialog) sind
direkt von den AWT-Klassen Frame und Dialog abgeleitet und erben all ihre
Methoden. Der Hauptunterschied ist, daß keine Kontrollelemente mehr direkt auf das
Fenster platziert werden, sondern auf einen Client-Container (mit dem Namen
„ContentPane“). Nun können beide Klassen Menüleisten aufnehmen (Typ
JMenuBar) und besitzen Vorgaben beim Schließen eines Fensters.
5.6.2.1 JFrame
class
javax.swing.Jframe
extends
Accessible, RootPaneContainer
Frame
implements
WindowConstants,
JFrame dient für den Ersatz der schwergewichtigen Komponente Frame und ist
Ausgangsbasis für die meisten Swing-Oberflächen. Die Klasse JFrame erbt die
wichtigsten Methoden zum Administrieren von Rahmen von ihren Vorgängern. Die
wichtigste Methode ist die von Component geerbte Methode setVisible(true)
(oder show()), da Fenster standardmäßig nicht sichtbar sind. „dispose()“ macht
Fenster unsichbar und gibt damit alle verbundenen Ressourcen frei.
Ein JFrame ist ein vollwertiges Fenster. Es besitzt einen Rahmen mit Titelleiste und
u.U.a. eine MenuBar (Attribut JMenuBar). „resizable()“ gibt an, ob der Benutzer
die Größe eines Fensters verändern kann und iconImage() dient zur Festlegung
eines Symbols, falls das Fenster den Zustand (state) Frame.ICONIFIED
annimmt.
361
Programmieren in Java
Unterschied zum AWT-Frame. Mit frame.add(komponente) wird in einem AWTProgramm ein Objekt zu einem Fenster hinzugefügt. Bei einem JFrame muß das
Component-Objekt erst zu der Zeichenfläche, die ContentPane genammt wird,
zugefügt werden, z.B.:
Container con = frame.getContentPane();
con.add(komponente);
Das läßt sich abkürzen zu: frame.getContentPane().add(komponente)
Auch beim Schließen eines Fensters verhält sich ein JFrame etwas anders als ein
AWT-Frame. Beim JFrame verschwindet das Fenster in den Hintergrund. Dieses
Verhalten kann mit der Funktion „void setDefaultCloseOperation(int
operation)“
geändert
werden.
So
verhält
sich
bspw.
mit
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE)
ein JFrame wie ein Frame. Jetzt lässt sich mit Hilfe eines WindowListener-Objekts
auf windowClosing() reagieren. Neben DO_NOTHING_ON_CLOSE existieren drei
weitere Konstanten:
WindowConstants.HIDE_ON_CLOSE
Das Fenster wird automatisch verdeckt, nachdem die WindowListener aufgerufen werden (Standard).
WindowConstants.DISPOSE_ON_CLOSE
Bewirkt die Freigabe der Ressourcen, nachdem die Listener abgearbeitet sind und das Fenster
geschlossen wird.
WindowConstants.EXIT_ON_CLOSE
Ruft System.exit() auf und schließt die Anwendung.
„getDefaultCloseOperation()“ liefert die eingestellte Eigenschaft beim
Schließen des Fensters.
Das Applikationsgerüst. Für das Einfügen in einen Swing-Frame ist folgendes
Applikationsgerüst zweckmäßig: Container und Komponenten müssen in einem
Zwischen-Container mit dem Namen „ContentPane“ (Inhaltsbereich) eingefügt
werden. Der JFrame ist in verschieden Bereiche (panes) unterteilt. Der
Hauptbereich ist der Inhaltsbereich, der die gesamte Fläche des Frame repräsentiert,
in den Komponenten eingefügt werden können. Das Erzeugen einer Komponenten in
den Inhaltsbereich umfasst:
-
Erzeugen eines JPanel-Objekts
Einfügen aller Komponenten in das JPanel-Objekt über dessen add (Component)-Methode
Implementieren des JPanel-Objekts über die Methode setContentPane(Container).
Bsp.288:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Rahmenwerk extends JFrame
{
public Rahmenwerk()
{
super("Anwendung - Titel");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocation(100,100);
setSize(300,200);
pack();
288
pr56110
362
Programmieren in Java
/* pack() sorgt fuer die Reduzierung der
Groesse des Frame, dass alle Komponenten gerade darin
noch Platz finden
*/
setVisible(true);
/* Macht den Frame sichtbar */
}
public static void main(String args[])
{
JFrame frame = new Rahmenwerk();
}
}
Layout. LayouManager verwalten die Anordnung der Komponenten in einem
Container. Das Layout wird durch die Methode setLayout(LayoutManager lm)
angegeben.
Bsp.289: Anwendung des LayoutManagers BorderLayout
Das Programm fügt in das JFrame ein Texfeld mit einem Scrollbalken, einem JColorChooser und 2
"Karteikarten mit jeweils einer Tabelle" ein.
Das Textfeld wird durch das Anlegen eines JTextArea-Objekts erzeugt. Durch Übergabe dieses
Objekts an den Konstruktor eines JScrollPane-Objekts wird das Textfeld mit einem Scrollbalken auf
der rechten Seite ausgestattet.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Pr56352 extends JFrame
{
Color farbe;
JColorChooser jcc;
JButton jw;
public static void main(String args[])
{
Pr56352 pr56352 = new Pr56352();
}
public Pr56352()
{
super("BorderLayout");
JTextArea ja = new JTextArea("Norden");
JScrollPane jsp =
new JScrollPane(ja, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
jsp.setPreferredSize(new Dimension((int) this.getWidth(),100));
jcc = new JColorChooser();
jw = new JButton("Westen");
jw.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
farbe = jcc.getColor();
jw.setBackground(farbe);
}
});
JTabbedPane jtp = new JTabbedPane(2);
JTable jt1 = new JTable(13,2);
JTable jt2 = new JTable(10,1);
jt2.setBackground(Color.green);
jtp.addTab("2",jt1);
jtp.addTab("1",jt2);
JButton js = new JButton("Sueden");
289
vgl. pr56352 in pr56350
363
Programmieren in Java
}
}
js.setBackground(Color.yellow);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
} );
getContentPane().setLayout(new BorderLayout());
getContentPane().add(BorderLayout.NORTH,jsp);
getContentPane().add(BorderLayout.WEST,jw);
getContentPane().add(BorderLayout.EAST,jtp);
getContentPane().add(BorderLayout.CENTER,jcc);
getContentPane().add(BorderLayout.SOUTH,js);
setSize(700,400);
setVisible(true);
Abb. Grafische Benutzroberfläche, die mit BorderLayout erzeugt wurde
364
Programmieren in Java
Interne Struktur von JFrame. Die interne Struktur weist mehrere übereinanderliegende Bereiche auf, denen verschiedene Aufgaben zugeteilt wurden. Im
wesentlichen sind dies:
- Wurzelbereich
- Schichtbereich
(dient hauptsächlich zur Darstellung von Menüs)
- Inhaltsbereich
(Container für die meisten visuellen Elemente, kann mit getContentPane() angefordert werden)
- Glasbereich
(Komponenten, die in den Glasbereich eingefügt werden, erscheinen im Vordergrund und
überdecken die darunter liegenden Schichten. Diese Eigenschaft ist vor allem für die Darstellung
von PopUp-Menüs von Bedeutung.)
JFrame
JRoot
JLayeredPane
Inhaltsbereich
Glasbereich
Abb.: Interne Struktur von JFrame
Die Hauptkomponente wird als RootPane bezeichnet und ist vom Typ JRootPane.
Eine RootPane enthält folgende Komponenten:
- eine aus Container abgeleitete GlassPane
- eine aus JLayeredPane abgeleitete LayeredPane.
Eine LayeredPane enthält ihrerseits zwei Unterkomponenten:
- eine aus Container abgeleitete ContentPane
- eine aus JMenuBar abgeleitete Menüleiste.
Die RootPane und mit ihr die darin enthaltene GlassPane, LayeredPane und
ContentPane werden beim Anlegen des Fensters automatisch erzeugt (nur die
Menüleiste bleibt standardmäßig leer). Außerdem implementieren alle Hauptfenster
das Interface RootPaneContainer, das den Zugriff auf die RootPane vereinfacht.
Einige seiner Methoden sind:
public
public
public
public
JRootPane getRootPane()
Container getContentPane()
JLayeredPane getLayeredPane()
Component getClassPane()
Neben den “getter”-Methoden gibt es auch „setter“-Methoden, die den strukturellen
Aufbau der RootPane vollständig verändern können.
365
Programmieren in Java
5.6.2.2 JWindow
JWindow ist aus Window abgeleitet und dient dazu, ein rahmenloses Fenster zu
erzeugen, das an beliebiger Stelle und in beliebiger Größe auf dem Bildschirm
platziert werden kann.
5.6.2.3 JDialog, JFileChooser, JColorChooser
JDialog
Mit der aus Dialog abgeleiteten Klasse JDialog stehen auch im Swing Dialogfenster
zur Verfügung. Dialogfenster unterscheiden sich von Hauptfenstern, dass sie kein
Menü und nur eingeschränkte Systemfunktionen besitzen. Sie besitzen denselben
strukturellen Aufbau wie JFrame und JWindow und implementieren ebenfalls das
Interface RootPaneContainer. Auch hier erfolgt das Hinzufügen und Anordnen
von Komponenten nicht auf dem Fenster selbst, sondern auf seiner ContentPane.
Die (wichigsten) Konstruktoren sind:
public
public
public
public
JDialog(Frame
JDialog(Frame
JDialog(Frame
JDialog(Frame
owner)
owner, bollean modal)
owner, String titel)
owner, String titel, boolean modal)
Als "owner" sollte der Aufrufer das Fenster übergeben, zu dem der Dialog logisch
gehört. Alle Konstruktoren gibt es auch owner vom Typ Dialog. Wahlweise kann ein
JDialog auch ohne owner konstruiert werden (mit dem parameterlosen
Konstruktor), doch kann es dann u.U. Fokusprobleme beim Wechsel zwischen den
Anwendungen geben.
Modaler bzw. nicht-modaler Dialog. Bei einem modalen Dialog wird der Aufruf von
show() (bzw. setVisible(true)) erst dann beendet, wenn der Dialog
geschlossen wurde. Bei einem nicht-modalen Dialog fährt das Programm dagegen
unmittelbar nach der nächsten Anweisung hinter show() fort.
JFileChooser
Diese Klasse ermöglicht einen Standarddialog, mit dessen Hilfe komfortable und
Betriebssystem unabhängige Datei- und Verzeichnisauswahl möglich ist.
JColorChooser
Diese Klasse erlaubt eine einfache, visuelle und kontextunabhängige Auswahl von
Farbinformationen.
366
Programmieren in Java
Bsp.290: JColorChooserTest
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JColorChooserTest extends JFrame
implements ActionListener
{
public JColorChooserTest()
{
super("Using JColorChooser");
// WindowUtilities.setNativeLookAndFeel();
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
Container content = getContentPane();
content.setBackground(Color.white);
content.setLayout(new FlowLayout());
JButton colorButton
= new JButton("Choose Background Color");
colorButton.addActionListener(this);
content.add(colorButton);
setSize(300, 100);
setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
// Args are parent component, title, initial color
Color bgColor
= JColorChooser.showDialog(this,
"Choose Background Color",
getBackground());
if (bgColor != null)
getContentPane().setBackground(bgColor);
}
public static void main(String[] args)
{
new JColorChooserTest();
}
}
Nach Drücken des Button "ChooseBackgroundColor" erscheint
290
pr56370
367
Programmieren in Java
zur Auswahl der gewünschten Farbe, die hier zum Füllen des Hintergrunds benutzt wird.
Mit dem Dialog lassen sich Farben in drei unterschiedlichen Dialogen auswählen.
Der Benutzer hat die Auswahl unter den vordefinierten Farben, HSB-Werten und
RGB-Werten. Der Farbauswahl-Dialog erscheint auf dem Bildschirm nach dem Aufruf
JColorChooser.showDialog() mit drei Argumenten:
einem Component-Objekt, auf dem gezeichnet werden kann
dem Titel
einer Anfangsfarbe
Rückgabewert ist nach Beenden des Dialogs die ausgewählte Farbe (bei Abbruch:
null).
5.6.2.4 JOptionPane
Die Klasse javax.swing.JOptionPane ist in der Lage, einfache Dialoge, die
lediglich ein Icon, einen Text oder ein Eingabefeld und eine Anzahl der Schaltflächen
"Yes", "No" und "Cancel" enthalten, mit einem einzigen Aufruf einer statischen
Methode zu erzeugen.
368
Programmieren in Java
5.6.2.5 JInternalFrame
Bei vielen Programmen gibt es ein einziges Hauptfenster und zahlreiche geöffnete
Kindfenster innerhalb des Hauptfensters. Diese unter Windows als MDI (Multiple
Document Interface) bezeichnete Technik ist mittlerweise weit verbreitet.
In Swing können MDI-Anwendungen entwickelt werden. Dazu werden zwei Arten von
Komponenten benötigt:
- das als Desktop bezeichnete Hauptfenster der Anwendung
- das Subfenster der Anwendung
5.6.2.6 JPanel und JLayeredPane
JPanel
Ein JPanel ist ein Fensterbereich, in dem andere Kontrollelemente platziert werden
können (Container). Hauptaufgabe ist die Gestaltung und Gliederung von
Benutzeroberflächen. JPanel ist die Basisklasse für GUI-Container, die nicht
Hauptfenster sind. Standardmäßig ist einem JPanel ein FlowLayout als
LayoutManager zugeordnet. Neben den geerbten Methoden von JComponent,
Container, Component und Object kommen keine nenneswerten Funktionen
hinzu.
Konstruktoren.
public JPanel()
// erzeugt ein neues JPanel mit Double-Buffering
public JPanel(boolean isDoubleBuffered)
// erzeugt ein neues JPanel mit FlowLayout und der angegebenen Puffer-Strategie
public JPanel(LayoutManager layout)
// erzeugt einen JPanel mit Double-Buffering und dem angegebenen Layout-Manager
public JPanel(LayoutManager layout, boolean isDoubleBuffered)
// erzeugt ein neues JPanel mit dem angegebenen LayoutManager und der angegebenen Puffer// Strategie
JLayeredPane
JLayeredPane fügt den Dialogen eine dritte Dimension hinzu und ermöglicht mit
Hilfe eines Layerkonzepts die kontrollierte Anordnung von Komponenten
übereinander. JLayeredPane ist die Elternklasse von JDesktopPane und wichtiger
Bestandteil der Struktur von Hauptfenstern.
369
Programmieren in Java
5.6.3 Menüs
5.6.3.1 Grundlagen
Menüs dienen zur Auswahl von Funktionen und Optionen in Anwendungen. Sie
können entweder an der Oberseite von Fenstern oder als PopUp-Menüs an einer
beliebigen Stelle auftauchen.
JMenuBar
In Swing können alle Hauptfenster mit Ausnahme von JWindow eine Menüleiste
haben.
JMenu
Die einzelnen Menüs einer Menüleiste sind Instanzen der Klasse JMenu, die aus
JMenuItem abgeleitet ist.
JMenuItem
Die Klasse JMenuItem repräsentiert Menüeinträge, also Elemente, die sich in einem
Menu befinden. Dabei handelt es sich um Texte, die wahlweise mit einem Icon oder
mit einem Häkchen versehen werden können.
5.6.3.2 Kontextmenüs
370
Programmieren in Java
JComponent
AbstractButton
JMenuItem
<< Konstruktoren >>
public JMenuItem()
public JMenuItem(Icon icon)
public JMenuItem(String text)
public JMenuItem(String text, Icon icon)
public JMenuItem(String text, int mnemonic)
<< Methoden >>
public void addMenuDragMouseListener(MenuDragMouseListener l)
public void addMenuKeyListener(MenuKeyListener l)
public KeyStroke getAccelerator()
…
public MenuKeyListener [] getMenuKeyListeners()
public MenuElement [] getSubElements()
…
public void init(String text, Icon icon)
…
public void removeKeyListener(MenuKeyListener l)
public void setAccelerator(KeyStroke keyStroke)
…
public void setEnabled(boolean b)
public void setUI(MenuItem ui)
public void updateUI()
JMenu
JCheckBoxMenuItem
<< Konstruktoren >>
public JMenu()
public JMenu(Action a)
public JMenu(String s)
public JMenu(String s, boolean b)
<< Methoden >>
public JMenuItem add(Action a)
public JMenuItem add(JMenuItem menuItem)
public JMenuItem add(String s)
public addmenuListener(MenuListener l)
public void addSeparator()
…
public void doClick(int pressTime)
…
public JMenuItem getItem(int pos)
public int getItemCount()
…
public JMenuItem insert(Action a, int pos)
Abb.: Die Swing-Menüklassen
371
JRadioButtonMenuItem
Programmieren in Java
JComponent
JMenuBar
JPopupMenu
<< Konstruktor >>
public JMenuBar()
<< Methoden >>
public void add(JMenu c)
public void addNotify()
…
public JMenu getMenu(int index)
…
public MenuElement [] getSubElements()
public MenuBarUI getUI()
…
public boolean isSelected()
…
public void setHelpMenu(JMenu menu)
…
public void setUI(MenuBarUI ui)
public void updateUI()
<< Konstruktor >>
public JPopupMenu()
public JPopupMenu(String label)
<< Methoden >>
public JMenuItem add(Action a)
public JMenuItem add(JMenuItem m)
public JMenuItem add(String s)
372
Programmieren in Java
5.6.4 Die Container-Klassen JToolBar, JSplitPane, JScrollPane,
JTabbedPane
JToolBar
Mit JToolBar können Werkzeugleisten erstellt werden, die Buttons enthalten.
JSplitPane
Das Kontrollelment teilt eine Fläche in zwei Teile.
JScrollPane
Der
Container
JScrollPane
ermöglicht
die
Anzeige
eines
Komponentenausschnitts. In Verbindung mit den Klassen JTextArea und JList
besteht die Ausgabe von JScrollPane darin, Dialogelemente, die zu groß für den zur
Verfügung stehenden Platz sind, mit Hilfe eines verschiebbaren Fensters
ausschnittsweise sichtbar zu machen.
Konstruktoren.
public JScrollPane(Component view)
public JScrollPane(Component view, int vsbPolicy, int hsbPolicy)
Die Argumente vsbPolicy und hsbPolicy geben an, wann ein horizintaler bzw. vertikaler
Schieberegler eingeblendet wird. Es können folgende Werte angegeben werden:
Konstante
VERTICAL_SCROLLBAR_NEVER
VERTICAL_SCROLLBAR_ALLWAYS
VERTICAL_SCROLLBAR_AS_NEEDED
HORIZONTAL_SCROLLBAR_NEVER
HORIZONTAL_SCROLLBAR_ALLWAYS
HORIZONTAL_SCROLLBAR_AS_NEEDED
Bedeutung
Der vertikale Schieberegler wird nie angezeigt.
Der vertikale Schieberegler wird immer angezeigt.
Der vertikale Schieberegler wird nur angezeigt,
wenn er benötigt wird
Der horizontale Schieberegler wird nie angezeigt
Der horizontale Schieberegler wird immer
angezeigt
Der horizontale Schieberegler wird nur angezeigt,
wenn er benötigt wird
Falls die Argumente vsbPolicy und hsbPolicy nicht angegeben werden, blendet JScrollPane
die Schieberegler nur dann ein, wenn sie wirklich benötigt werden.
Methoden.
public void setColumnHeaderView(Component view)
Angabe einer Komponente für den Spaltenkopf. Sie wird über dem eigentlichen Dialogelement
angezeigt, bei horozontalen Bewegungen mit diesem verschoben. Bei vertikalen
Schieberbewegungen bleiben sie dagegen am Platz.
public void setRowheaderView(Component view)
Angabe eines Zeilenkopfs links neben der eigentlichen Komponente, wird bei vertikalen Bewegungen
verschoben, behält bei horizontalen Bewegungen seinen Platz bei.
373
Programmieren in Java
public void setCorner(String key, Component corner)
Ein Dialogelement kann in eine beliebige der vier ungenutzten Ecken291 einer JScrollPane plaziert
werden. Der Parameter key gibt dabei an, welche Ecke belegt werden soll. Argument kann einer der
Konstanten LOWER_LEFT_CORNER, LOWER_RIGHT_CORNER, UPPER_LEFT_CORNER,
UPPER_RIGHT_CORNER sein.
Spaltenheader
Eckfläche
Eckfläche
ViewPort
Zeilenheader
Eckfläche
Vertikaler
Schieberegler
Eckfläche
Horizontaler
Schieberegler
Abb.: Aufbau einer JScrollpane
Der sichtbare Ausschnitt heißt ViewPort und wird durch folgende Methoden eingestell:
public void setViewPort(JViewPort viewport)
public void setViewPortView(Component view)
eingestellt.
JTabbedPane
Ein JTabbedPane erlaubt die Darstellung mehrerer Container, die über Kartenreiter
zugänglich sind.
291 u.U. stehen Eckflächen nicht zur Verfügung. Die beiden Ecken auf der linken Seite sind bspw. nur
vorhanden, wenn ein Zeilenkopf eingeblendet wurde. Die rechte obere ist nur vorhanden, wenn ein vertikaler
Schieberegler eingeblendet wurde, und die rechte untere erfordert sogar die Anwesenheit beider Schieberegler.
Auch kann die Anwendung praktisch keinen Einfluß auf die Größe der Ecken nehmen. Diese wird
ausschließlich die Ausdehnung der Schieberegler und des Zeilenkopfes bestimmt.
374
Programmieren in Java
5.6.5 Swing-Komponenten
5.6.5.1 Label und Textfelder
JLabel
Ein JLabel ist ein Dialogelement zur Anzeige und Beschriftung innerhalb eines GUIComtainers, besitzt Text und Icon, die in beliebiger Anordnung dargestellt werden
können. Die beiden Elemente sind optional, ein JLabel kann auch nur ein Icon
enthalten. Auf Benutzereingeben reagiert ein JLabel nicht. In der Swing-Bibliothek
gibt es mit dem Interface Icon bzw. der Klasse ImageIcon Unterstützung zur
Bildverarbeitung, wobei der Ladevorgang mit Hilfe von Mediatracker intern erledigt
wird.
Konstruktoren.
public JLabel(String text)
public JLabel(Icon image)
public JLabel(String text, Icon icon,int horizontalAlignment)
// Der Parameter horizontalAlignment gibt an, wie das Label horizontal plaziert werden soll, falls
// links und rechts mehr Platz als erforderlich zur Verfügung steht. Hier kann eine der Konstanten
// LEFT, CENTER oder RIGHT aus dem Interface SwingConstants angegeben werden.
Methoden.
public void setHorizonatTextPosition(int textPosition)
public void getHorizontalTextPosition()
public void setHorizontalAlignment(int alignment)
// horizontale Ausrichtung, mögliche Parameter: LEFT, CENTER, RIGHT
public int getHorizontalAlignment()
public void setVerticalAlignment(int alignment)
// vertikale Ausrichtung, mögliche Parameter: TOP, CENTER, BOTTOM
public int getVerticalAlignment()
Anzeige von Bildern (im Gegensatz zur AWT-Implementierung). Angegeben werden
zur Anzeige von Bildern Icon-Objekte. Die Klasse ImageIcon implemntiert die IconSchnittstelle.
interface java.awt.swing.Icon
public int getIconWidth()
// liefert die Breite eines Icons
public int getIconHeight()
// liefert die Höhe eines Icons
public void paintIcon(Component c, Graphics g, unt x, int y)
// zeichnet das Icon an die angegebene Position.
Die Klasse ImageIcon implementiert die Icon-Schnittstelle und nutzt die Klasse
Image (und deren verwandte Klassen. Ein ImageIcon ist serialisierbar
(implementiert das Interface Serializable). Eine Referenz auf das Image-Objekt kann
durch den Aufruf von getImage() der Klasse Toolkit hergestellt werden. Mit der
Methode protected void loadImage(Image image) wartet man dann mit
Hilfe eines Mediatrackers auf das Bild. Dabei setzt der Mediatracker Höhe und
375
Programmieren in Java
Breite, die sich über Icon-Methoden abfragen lassen. Zur Serialisierbarkeit sind die
Methoden readObject() und writeObject() implementiert. Im zugehörigen
Dateistrom befinden sich Breite und Höhe und anschließend ein Integer-Feld mit
Pixelwerten. Bei readObject() liest s.readObject() – wobei s der aktuelle
ObjectInputStream ist -, das Feld wieder ein. Über die Toolkit-Funktion
createImage() wird die Klasse MemoryImage genutzt, um das Feld wieder bzu
einem Image-Objekt zu konvertieren. Umgekehrt geht es genauso einfach:
writeObject() schreibt Breite und Höhe und anschließend das Integer-Feld, das
es über ein PixelGrabber bekommen hat.
Die ImageIcon-Klasse kann somit das Laden von Icon-Objekten mit einer
Programmzeilen beschreiben:
Image image = new ImageIcon(("bild.jpg").getImage())
Da ein Image immer wieder in ein ImageIcon umgewandelt werden kann, eignet
sich die Klasse hervorragend zum Laden und Speichern.
Bsp.292: Einige Labels mit unterschiedlichen Eigenschaften
import java.awt.*;
import javax.swing.*;
class LabelDemo extends JFrame
{
public LabelDemo()
{
super("JLabel Demo");
setSize(600, 100);
JPanel content = (JPanel) getContentPane();
content.setLayout(new GridLayout(1, 4, 4, 4));
JLabel label = new JLabel();
label.setText("JLabel");
label.setBackground(Color.white);
content.add(label);
label = new JLabel("JLabel",SwingConstants.CENTER);
label.setOpaque(true);
label.setBackground(Color.white);
content.add(label);
label = new JLabel("JLabel");
label.setFont(new Font("Helvetica", Font.BOLD, 18));
label.setOpaque(true);
label.setBackground(Color.white);
content.add(label);
ImageIcon image = new ImageIcon("flight.gif");
label = new JLabel("JLabel", image,SwingConstants.RIGHT);
label.setVerticalTextPosition(SwingConstants.TOP);
label.setOpaque(true);
label.setBackground(Color.white);
content.add(label);
setVisible(true);
}
public static void main(String args[])
{
new LabelDemo();
}
}
292
pr56360
376
Programmieren in Java
Abb.: Demonstration zu den Klassen JLabel und IconImage
JTextField
Ein JTextField ist ein einzeiliges Eingabefeld.
Konstruktoren.
public JTextField(int columns)
// ein leeres Textfeld mit der angegebenen Anzahl Spalten
public JTextField(String text)
// erzeugt ein Textfeld mit angegebenem Text
public JTextField(String text,int columns)
// erzeugt ein Textfeld nach Vorgabe von Spaltenanzahl und Text
// Die Spaltenanzahl wird zur Berechnung der Breite der Textfelder verwendet
Methoden.
public String getText()
public void setText()
public String getSelectedText()
public int getSelectionStart()
public void setSelectionStart()
public void setSelectionEnd(int selectionEnd)
public int getCaretPosition()
public void setCaretPosition(int pos)
public void moveCaretPosition(int pos)
// verändert die Position der Einfügemarke und markiert dabei den Bereich zwischen alter und
// neuer Position. Für alle Positionsangaben gilt, dass der Platz vor dem ersten Zeichen die
// Position 0, und der nach dem letzten Textzeichen die Position Länge des Texts hat
Listener.
public void addActionListener(ActionListener l)
// wird immer aufgerufen, wenn im Eingabefeld ENTER gedrückt wird.
public void addCaretListener(CaretListener l)
// wir aufgerufen, wenn sich die Position der Einfügemarke geändert hat
Bsp.293:
import javax.swing.*;
import java.awt.*;
public class JTextFieldTest extends JFrame
{
public JTextFieldTest()
{
super("JTextField Test");
getContentPane().setLayout(new FlowLayout());
JTextField textField1 = new JTextField("m",1);
JTextField textField2 = new JTextField("mm",2);
JTextField textField3 = new JTextField("mmm",3);
JTextField textField4 = new JTextField("mmmm",4);
293
pr56370
377
Programmieren in Java
JTextField textField5 = new JTextField("mmmmm",5);
JTextField textField6 = new JTextField("mmmmmm",6);
JTextField textField7 = new JTextField("mmmmmmm",7);
JTextField textField8 = new JTextField("mmmmmmmm",8);
JTextField textField9 = new JTextField("mmmmmmmmm",9);
JTextField textField10 = new JTextField("mmmmmmmmmm",10);
JTextField textField11 = new JTextField("mmmmmmmmmmm",11);
JTextField textField12 = new JTextField("mmmmmmmmmmmm",12);
JTextField textField13 = new JTextField("mmmmmmmmmmmmm",13);
JTextField textField14 = new JTextField("mmmmmmmmmmmmmm",14);
getContentPane().add(textField1);
getContentPane().add(textField2);
getContentPane().add(textField3);
getContentPane().add(textField4);
getContentPane().add(textField5);
getContentPane().add(textField6);
getContentPane().add(textField7);
getContentPane().add(textField8);
getContentPane().add(textField9);
getContentPane().add(textField10);
getContentPane().add(textField11);
getContentPane().add(textField12);
getContentPane().add(textField13);
getContentPane().add(textField14);
setSize(300,170);
setVisible(true);
}
public static void main(String argv[])
{
new JTextFieldTest();
}
}
Abb.: Demonstration zur Klasse JTextField
JTextArea
JTextArea ist eine Komponente zur Anzeige und Eingabe von mehrzeiligen Texten.
Wie die AWT-Klasse TextArea dient sie dazu, unformatierte Texte zu bearbeiten.
Diese können Zeilenumbrüche und Tabulatoren, nicht aber unterschiedliche
Schriften, Farben oder grafische Elemente enthalten294.
Konstruktoren.
public JTextArea(String text)
public JTextArea(int rows, int columns)
public JTextArea(String text,int rows,int columns)
294
Für diesen Zweck gibt es die Klassen JEditorPane und JTextPane
378
Programmieren in Java
Methoden.
public int getLineCount()
// liefert Anzahl der Zeilen
public int getLineStartOffset(int line)
public int getLineEndOffset(int line)
// Zu einer beliebigen Zeile kann Anfangs- und Endoffset bestimmt werden.
public int getLineOfOffset(int offset)
// ermittelt die Nummer einer Zeile, wenn der Offset eines darin enthaltenen Zeichens bekannt ist.
public void setTabSize(int size)
public int getTabSize()
// Zugriff auf die Tabulatorwerte (standardmäßig mit Wert 8295)
public void setLineWrap(boolean wrap)
// Festlegung, ob zu breite Spalten automatisch unterbrochen werden sollen
public void setWrapStyleWord(boolean word)
// Umbruch an Wortgrenzen oder mitten im Wort
Automatisches Scrollen. JTextArea kann Text nicht automatisch scrollen. Wird das
gewünscht, muß das Textfeld in einer Komponente von JScrollPane eingebettet
werden.
Bsp.296:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class BspScrolling extends JFrame
{
public BspScrolling()
{
super();
JScrollPane scroller = new JScrollPane();
getContentPane().add(scroller);
StringBuffer bigBuffer = new StringBuffer();
for (int i = 0; i < 50; i++)
{
bigBuffer.append(Integer.toString(i));
bigBuffer.append(' ');
}
JLabel longLabel = new JLabel(bigBuffer.toString());
scroller.getViewport().add(longLabel);
}
public static void main(String [] args)
{
BspScrolling einFrame = new BspScrolling();
einFrame.setVisible(true);
}
}
JEditorPane und JTextPane
Die Klasse JEditorPane und deren Subklasse JTextPane erlauben verschiedene
Typen von Text darzustellen. Die beiden Klassen unterstützen HTML und Rich Text
Format (RTF). Eigene Implementierungen lassen sich hinzufügen (sog. Editor-Kits).
Der Editor stellt Text dar, der ihm mit
295
Zur Umrechnung in Bildschirmpixel wird er mit der Breite des breitesten Buchstabens im aktuellen Font
multipliziert.
296 pr56110
379
Programmieren in Java
public void setContentType(String type)
übergeben wird. Ohne eigene Erweiterung sind text/html (Standard),
text/plain und text/rtf erlaubt297. Wird mit Standardkonstruktor gearbeitet,
dann kann mit
public void setPage(String url)
bzw.
public void setPage(URL page)
ein URL-Objekt oder ein String eine Seite belegen. Auch
public void setText(String t)
erlaubt Setzen des spezifizierten Inhalts von Text ohne Textkomponente.
Konstruktoren.
public
public
public
public
JEditorPane()
JEditorPane(String url)
JEditorPane(String type,String text)
JEditorPane(URL initialPage)
Bsp.298:Anzeige einer HTML-Seite
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PR56510 extends Frame
{
String url = "file:///d:/dok/pgj/ws02/progr/pr56510/Skript48.html";
public PR56510()
{
try {
JEditorPane htmlPane = new JEditorPane(url);
htmlPane.setEditable(false);
add(new JScrollPane(htmlPane));
}
catch(IOException e) { System.out.println("Error" + url);};
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0);}
});
setSize(300,200);
setVisible(true);
}
public static void main(String args[])
{
}
}
PR56510 pr56510 = new PR56510();
297
Soll nur Text ohne Formatierungen und ohne Attribute dargestellt werden, lässt sich gleich TextField
verwenden.
298 pr56510
380
Programmieren in Java
JTextComponent
JTextArea
JEditorPane
<< Konstruktoren >>
public JTextArea()
public JTextArea (Document doc,
String text,
int rows, int columns)
public JTextArea(int rows,int columns)
public JTextArea(String text)
public JTextArea(String text,
int rows, int columns)
<< Methoden >>
public void append(String str)
…
public int getColumns()
…
public int getColumnCount()
…
public int getRows()
…
public void insert(String str, int pos)
…
public void replaceRange(String str,int start,int end)
public void setColumns(int columns)
public void setFont(Font f)
…
public void setRows(int rows)
<< Konstruktoren >>
public JEditorPane()
public JEditorPane(String type, String
text)
public JEditorPane(URL initialPage)
…
<< Methoden >>
public String getContentType()
public EditorKit getEditorKit()
…
public URL getPage()
public Dimension getPreferedSize()
…
public String getText()
…
public void setPage(String url)
public void setPage(URL page)
public void setText(String t)
JTextPane
<< Konstruktoren >>
public JTextPane()
public JTextPane(StyledDocument doc)
<< Methoden >>
public Style addStyle(String nm, Style
parent)
…
public String getUIClassID()
public void setStyleDocument(
StyledDocument doc)
Abb.: Die Klassen JTextArea und JTextPane
381
Programmieren in Java
5.6.5.2 Schaltflächen
Die Klasse JButton und das Interface ButtonModel
Buttons (Schaltflächen) sind rechteckige Knöpfe, die eine Aktion auslösen, wenn der
Benutzer sie anklickt.
Die visuelle Komponente wird durch die Klasse JButton repräsentiert, die das
Modell und die Ansicht speichert. Gleichzeitig dient die Klasse als Vermittler beim
Zugriff auf das Modell oder bei Anforderungen an die Ansicht. Das jeweilige Modell
ist durch eine gleichnamige Schnittstelle implementiert. So definiert z.B. die
Schnittstelle ButtonModel den Zustand der Schaltflächen-Klassen mit den
Zugriffsmethoden:
public String getActionCommand()
// liefert den Namen des Aktionsfeldes
public boolean isArmed()
public boolean isEnabled()
public boolean isPressed()
public boolean isRollover()
public boolean isSelected()
Der Zugriff auf das Modell der Komponente muß mit Hilfe der Methode
public ButtonModel getModel()
der Klasse JButton angefordert werden bzw. kann mit
public void setModel(ButtonModel newModel)
der Klasse JButton angefordert werden. In Standardfällen ist der direkte Zugriff auf
das Modell nicht erforderlich, da die Klasse JButton über Zugriffsmethoden auf die
meisten Inhalte des Modells verfügt und diese kapselt. Alle Modelle mit
"Schaltflächen"-Funktionalität (Schaltflächen, Optionsfelder, Kontrollkästchen)
können dasselbe Modell verwenden.
382
Programmieren in Java
JComponent
AbstractButton
protected ActionListener actionListener
protected ChangeEvent changeEvent
protected ChangeListener changeListener
protected ItemListener itemListener
public void addActionListener(ActionListener l)
public void addChangeListener (ChangeListener l)
public void addItemListener(ItemListener l)
…
public void doClick()
…
public int getHorizontalTextPosition()
public int getHorizontalAlignment()
public int getVerticalAlignment()
…
public Icon getIcon()
…
public Insets getMargin()
public int getMnemonic()
public ButtonModel getModel()
…
public Icon getSelectedIcon()
public String getText()
public ButtonUI getUI();
…
public void setUI(ButtonUI ui)
…
public void updateUI()
JButton
<< Konstruktoren >>
public JButton()
public JButton(String text)
public JButton(Icon icon)
public JButton(String text,Icon icon)
<< Methoden >>
…
public boolean isDefaultButton()
…
public void removeNotify()
…
Abb.: Die Klasse JButton
383
Programmieren in Java
Die Klassen JCheckBox und JRadioButton
In Swing werden runde und eckige Optionsfelder nicht mehr in einer Klasse
zusammengefasst, sondern durch die Klassen JCheckBox und JRadioButton
dargestellt.
JCheckBox
Die Klasse JCheckBox stellet einen Button dar, der vom Anwender wahlweise anoder ausgeschaltet werden kann. JCheckBox ist von der Klasse JToggleButton
abgeleitet, die als Abstraktion von Buttons, die ihren Zustand ändern können, auch
Basisklasse von JRadioButton ist.
JCheckBox kann eine textuelle Beschriftung oder ein Icon oder beides enthalten.
Konstruktoren.
public JCheckBox(String text)
public JCheckBox(String text,boolean selected)
// Mit dem Parameter "selected" kann bereits bei der Instanzierung angegeben werden,
// ob die Checkbox aktiviert oder deaktiviert sein soll.
Methoden.
public void getSelected(boolean b)
public boolean isSelected()
Wie ein JButton sendet eine JCheckBox bei jeder Bestätigung ein ActionEvent
an registrierte Listener. Zudem wird bei Zustandsänderungen ein ItemEvent
versendet, auf das ein ItemListener reagieren kann.
JRadioButton
Die Klasse JRadioButton stellt eine Button dar, der wahlweise an- und
ausgeschaltet werden kann. Anders als bei der JCheckBox ist in einer Gruppe von
Radiobuttons allerdings immer nur ein Button zur Zeit aktiviert, alle anderen sind
deaktiviert.
Konstruktoren.
public JRadioButton(String text)
public JRadioButton(String text, boolean selected)
Methoden.
public void add(AbstractButton b)
public ButtonModel getSelection()
public Enumeration getElements()
// liefert alle Buttons der Gruppe
384
Programmieren in Java
AbstractButton
JToggleButton
ButtonGroup
protected Vector Buttons
<< Konstruktor >>
public ButtonGroup()
<< Methoden >>
public void add(AbstractButton b)
public int getButtonCount()
public Enumeration getElement()
public ButtonModel getSelection()
public boolean isSelected(ButtonModel m)
public void remove(AbstractButton b)
public void setSelected(ButtonModel m,
boolean b)
JCheckBox
<< Konstruktoren >>
public JCheckBox()
public JCheckBox(String text))
public JCheckBox(String text,
boolean selected)
<< Methoden >>
…
JRadioButton
<< Konstruktoren >>
public JRadioButton(String text)
public JRadionButton(String text,
boolean selected)
<< Methoden >>
…
Abb. JCheckBox und JRadioButton
385
Programmieren in Java
5.6.5.3 Listen und Comboboxen
JList
JList ist ein typisches Beispiel für das Model-View-Controller-Prinzip. Dabei stehen
für die View (JList) mehrere Modelle zur Verfügung. Am wichtigsten ist das
Datenmodell vom Typ ListModel, das Auswahlmodell (ListSelectionModel)
und die Klassen für die Darstellung der Einträge (ListCellRenderer).
Konstruktoren: JList verfügt über Konstruktoren, die eine Liste aus einem Vector
oder Array299 erzeugen. Da die Daten nicht direkt in der Liste gespeichert werden,
sondern in einem separaten Vektor, ist jederzeit einZugriff auf diese Daten möglich.
Die Daten können also, nachdem sie in der Liste angezeigt wurden, nachträglich
verändert werden. Das Problem ist dabei, dass die Komponenten von der Änderung
nichts erfahren, da ein sehr triviales Modell, ohne jegliche Vorkehrungen oder
Benachrichtigungen im Fall einer Änderung, durch den Konstruktor verwendet wurde.
Darstellung und Auswahl der in der Liste enthaltenen Elemente: Die Anzahl der
Elemente, die gleichzeitig angezeigt werden sollen, kann mit der Methode
public void setVisibleRowCount(int setVisibleRowCount)
eingestellt werden. Die Listenanzeige ist aber völlig von anderen Mechanismen wie
z.B. Bildlauf getrennt, d.h. ein Listenfeld führt keinen automatischen Bildlauf durch.
Zur Realisierung eines Bildlaufbereichs muß ein JScrollPane-Objekt mit der Liste
als Parameter erzeugt werden und diese dann anschließend einem Panel
hinzugefügt werden.
Entfernen, Modifizieren von Einträgen in bzw. aus der Liste: Das Listenobjekt selbst
verfügt über keine Methoden, die es erlauben, einen neuen Eintrag in die List
einzufügen. Daten werden nicht in die Liste selbst gespeichert, sondern werden über
eine Modell-Schnittstelle ListModel verwaltet. Das ListModel besitzt folgende
Methode, mit deren Hilfe auf die Daten zugegriffen werden kann:
public void getElementAt(int index)
public int getSize()
Es besteht die Möglichkeit, die Dateninhalte, statt sie in der Liste selbst zu speichern
oder aus einem Vektor zu lesen, mit Hilfe einer Neudefinition von getElementAt()
und getSize() z.B. online zu berechnen oder aus einer Datenbank anzufordern.
Die zur Anzeige benötigten Inhalte werden dann dynamisch durch die Liste vom
vorhandenen ListModel über den zugehörigen Index angefordert.
Mit Hilfe der Methode
public void addDataListener(ListDataListener l)
kann ein Listener zum Modell hinzugefügt werden, der dafür Sorge tragen kann, dass
eine Benachrichtigung der Listener-Komponente stattfindet, falls ein Element
modifiziert wurde. Da die Liste nichts über die Elemente, die angezeigt werden
sollen, weiß, muß für jedes Element Breite und Höhe neu berechnet werden. Mit Hilfe
von
public void setFixedCellHeight(int height)
299
Übergabe beim Erstellen des Elements
386
Programmieren in Java
public void setFixedCellWidth(int width)
der Klasse JList können konstante Abmaße der darzustellenden Elemente definiert
werden, wodurch die Berechnung bei gleichartigen Elementen, was die
überwiegende Mehrheit der Fälle sein dürfte, entfallen kann.
Dynamische Speicherung der Objekte im Modell durch Einsatz von DefaultListModel:
Das DefaulListModel verfügt über die Methoden
public void addElement(Object element)
public boolean removeElement(Object obj)
, die zum Einfügen und Entfernen von Listenelementen eingesetzt werden können.
Darstellung beliebiger, benutzerdefinierter Zeichnungen und Objekte: Hierzu muß ein
Listenzellen-Renderer definiert werden. Dieser kann dafür Sorge tragen, dass
entsprechende Elemente benutzerdefiniert innerhalb der Liste gezeichnet werden.
Ein Listenzellen-Renderer kann eine beliebige Klasse sein, die eine Schnittstelle mit
public Component getListCellRendererComponent(JList list,Object value,
int index, boolean isSelected,boolean hasFocus)
implementiert. Die Schnittstelle gibt ein Objekt vom Typ Component zurückgeben,
das wiederum die Methoden paintComponent() und getPreferredSize()
implementiert. paintComponent() sorgt für das Zeichnen der Komponente,
getPreferredSize() ist für das Berechnen der Abmaße erforderlich, falls keine
feste Größen definiert wurden.
387
Programmieren in Java
JComponent
JList
...
<< Konstruktoren >>
public JList()
public JList(ListModel dataModel)
public JList(Object[] listData)
public JList(Vector listData)
<< Methoden >>
public void addListSelectionListener(ListSelectionListener l)
public void addSelectionInterval(int anchor,int lead)
public void clearSelection()
protected ListSelectionModel createSelectionModel()
…
public AccessibleContext getAccessibleContext()
public int getAnchorSelectionIndex()
public Rectangle getCellBounds(int index0,int index1)
public ListCellRenderer getCellRenderer()
…
public void getSelectionMode(int selectionMode)
public int getSelectedIndex()
public int [] getSelectedIndices()
public Object getSelectedValue()
public Object [] getSelectedValues()
public boolean isSelectedIndex(int index)
…
public void ListSelectionListener[] getSelectionListeners()
public int getMaxSelectionIndex()
public int getMinSelectionIndex()
public ListModel getModel()
…
public ListUI getUI()
…
public String paramString()
…
public void setListData(Object[] listData)
public void setListData(Vector listData)
public void setModel(ListModel model)
…
public void setSelectionModel(ListSelectionModel selectionModel)
public void setUI(ListUI ui)
public void setValueIsAdjusting(boolean b)
public void setVisibleRowCount(int visibleRowCount)
public void updateUI()
Abb.: Die Klasse JList
388
Programmieren in Java
<< interface >>
ListModel
public void addListDataListener(ListDataListener l)
public void setUI(ListUI ui)
public void setValueIsAdjusting(boolean b)
public void setVisibleRowCount(int visibleRowCount)
public void updateUI()
AbstractListModel
{ abstract }
public void addListDataListener(ListDataListener l)
…
public void removeDataListener(ListDataListener l)
DefaultListModell
<< Konstruktoren >>
public DefaultListModel()
<< Methoden >>
public void add(int index,Object element)
public void addElement(Object obj)
public void clear()
public void contains(Object elem)
public void copyInto(Object[] anArray)
public Object elementAt(int index)
public Enumeration elements()
…
public Object get(int index)
public Object getElementAt(int index)
…
public void insertElementAt(Object obj,int index)
public boolean isEmpty()
public Object lastElement()
…
public Object remove(int index)
public void removeAllElements()
…
public Object set(int index,Object element)
public void setElementAt(Object obj,int index)
public voiud setSize(int newSize)
public int size()
public Object[] toArray()
public String toString()
public void trimToSize()
389
Programmieren in Java
<< interface >>
ListSelectionModel
public void addListSelectionListener(ListSelectionListener x)
public void addSelectionInterval(int index0,int index1)
public void clearSelection()
public int getAnchorSelectionIndex()
public int getMaxSelectionIndex()
public int getMinSelectionIndex()
public int getSelectionMode()
…
public boolean isSelectionEmpty()
…
public void setSelectionMode()
public void setValueIsAdjusting(boolean valueIsAdjusting)
DefaultListSelectionModel
…
protected EventListenerList listenerList
<< Konstruktoren >>
public DefaultListSelectionModel()
<< Methoden >>
public void addListSelectionListener(ListSelectionListener l)
public void addSelectionInterval(int index0,int index1)
public void clearSelection()
public Object clone()
…
public String toString()
<< interface >>
ListCellRenderer
public Componet getListCellRendererComponent(JList list,Object value,
int index, boolean isSelected,boolean hasFocus)
DefaultListCellRenderer
<< Konstruktor >>
public DefaultListCellRenderer()
<< Methoden >>
public void firePropertyChange(String propertyName,boolean oldValue,boolean newValue)
…
public void validate()
390
Programmieren in Java
JComboBox
Die JComboBox teilt dasselbe Darstellungsmodell (ListCellRenderer) mit der
JList. Das Datenmodell ComboBoxModel ist von ListModel abgeleitet und
erweitert es um Methoden zum Hinzufügen und Löschen von Einträgen. In einer
JComboBox kann immer nur ein Eintrag ausgewählt sein.
5.6.5.4 Quasi analoge Komponenten
In Swing gibt es drei Kontrollelemente zur Eingabe und Visualisierung von
Zahlenwerten aus einem festgelegten Wertbereich: JScrollBar (ein einfacher
Scrollbalken), JSlider (ein komplexer Schieberegler mit Skala), JProgressBar
(eine Fortschrittsanzeige). Alle drei unterstützen das BoundedRangeModelInterface, das den Zugriff auf die Attribute value, minimum, maximum, extent bzw.
valueAdjusting (ein Flag, das anzeigt, ob der Wert gerade eingestellt wird) und
die Implementierung von ChangeEvents sicherstellt.
<< interface >>
BoundedRangeModel
public void addChangeListener(ChangeListener x)
public int getExtent()
public int getMaximum()
public int getMinimum()
public int getValue()
public boolean getValueIsAdjusting()
public void removeChangeListener(ChangeListener x)
public void setExtent(int newExtent)
public void setMaximum(int newMaximum)
public void setRangeProperties(int value, int extent, int min,
int max, boolean adjusting)
public void setValue(int newValue)
public void setValueIsAdjusting(boolean b)
DefaultBoundedRangeModel
Abb.: Interface BoundedRangeModel
JScrollBar
Attrribute. Die Klasse JScrollBar besitzt zusätzlich die Attribute unitIncrement für
die Schrittweite, wenn auf die Pfeiltatsen gedrückt wird (Standard ist 1) und
blockIncrement für einen Mausklickj auf den weißen Hintergrung des
Scrollbalkens (Standard ist 10).
Konstruktor. Der wichtigste Konstruktor ist
public JScrollBar(int orientation,
int value,
int extent,
int min, int
// Ausrichtung des Schiebereglers
// Anfangswert des Schiebers
// Ausdehnung des Schiebereglers
max)
391
Programmieren in Java
Mit orientation wird die Ausrichtung (HORIZONTAL oder VERTICAL) des Schiebereglers
festgelegt, min gibt den kleinsten, max den größten möglichen Wert an.
Mit extent wird die Ausdehnung des Schiebers festgelegt. Sie muß mindestens eins, kann auch
größer sein.
value ist der Anfangswert des Schiebers. Er muß zwischen min und max – extent liegen.
Methoden.
public int getMaximum()
public void setMaximum(int maximum)
public int getVisibleAmount()
// liefert die Ausdehnung des Schiebers
public void setVisibleAmount(int extent)
public int getValue()
public void setValue(int value)
public int getUnitIncrement()
// gibt an, um welchen Betrag der Wert des Schiebereglers sich verändert, wenn der Anwender
// einen Pfeilbutton betätigt
public void setUnitIncrement(int unitIncrement)
public int getBlockIncremnent()
// ermittelt den Betrag der Änderung, wenn zwischen Schieber und Pfeiltatsen geklickt wird.
public void setBlockIncrement(int blockIncrement)
Ereignisbehandlung. Aus Kompatibilitätsgründen zur AWT-Scrollbar implementiert
die Klasse das Interface Adjustable und unterstützt den AdjustmentListener.
Wird ein Wert einer JScrollBar verändert, sendet sie ein AdjustmentEvent an
registrierte Listener. Diese müssen das Interface AdjustmentListener
implementieren und werden durch addjustmentListener() registiert. Mit
getValueAdjusting() kann festgestellt werden, auf welche Weise der Wert
verändert wird. Rückgabe von true erfolgt dann, wenn der Anwender den Schieber
betätigt. false wird zurückgegeben, wenn die Änderung durch einen Mausklick auf
einen Button oder auf die Fläche zwischen Button und Schieber ausgelöst wird.
JSlider
Wichtiger (konzeptioneller) Unterschied zum JScrollbar:
1. Ein JSlider kann eine Anzeigeskala mit grober und fiener Beschriftung haben.
2. Ein JSlider kennt keine unterschiedlichen Schiebergrößen. Ihre Ausdehung ist immer 1.
Die Klasse JSlider ist ein Schieberegler mit optionaler Skala. Die Skalenstriche
nennen sich Ticks und existieren in zwei Längen: majorTicks, minorTicks.
Wichtigster Konstruktor.
public JSlider(int orientation, int min, int max, int value)
Methoden.
public int getMaximum()
public void setMaximum(int maximum)
public int getMinimum()
public void setMinimum(int minimum)
392
Programmieren in Java
public int getValue()
public void setValue(int value)
public int getMajorTickSpacing()
// Vorgabe des Abstands der großen Markierung
public void setMajorTockSpacing(int n)
public int getMinorTickSpacing()
public void setMinorTickSpacing(int n)
// Vorgabe des Abstands der kleinen Markierung
public void setPaintTicks(boolen b)
// Anzeige der Anzeigeskala durch Übergabe von true
public void setPaintLabels(boolean b)
// Anzeige der Beschriftung durch Übergabe von true
Ereignisbehandlung. Ein JSlider sendet ein ChangeEvent, wenn sein Wert
verändert wird. Zur Reaktion auf dieses Ereignis muß das Interface
ChangeListener implementiert sein, und das zu implementierende Objekt durch
den Aufruf addChangeListener() registriert sein. Mit getValueIsAdjusting()
kann festgestellt werden, ob die Änderung Bestandteil einer Kette von Änderungen
sit oder ob sie einzeln aufgetreten ist.
Bsp.300:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JSliders extends JFrame
{
public JSliders()
{
super("Using JSlider");
// WindowUtilities.setNativeLookAndFeel();
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
});
Container content = getContentPane();
content.setBackground(Color.white);
JSlider slider1 = new JSlider();
slider1.setBorder(BorderFactory.createTitledBorder
("JSlider without Tick Marks"));
content.add(slider1, BorderLayout.NORTH);
JSlider slider2 = new JSlider();
slider2.setBorder(BorderFactory.createTitledBorder
("JSlider with Tick Marks"));
slider2.setMajorTickSpacing(20);
slider2.setMinorTickSpacing(5);
slider2.setPaintTicks(true);
content.add(slider2, BorderLayout.CENTER);
JSlider slider3 = new JSlider();
slider3.setBorder(BorderFactory.createTitledBorder
("JSlider with Tick Marks & Labels"));
slider3.setMajorTickSpacing(20);
slider3.setMinorTickSpacing(5);
slider3.setPaintTicks(true);
slider3.setPaintLabels(true);
content.add(slider3, BorderLayout.SOUTH);
pack();
300
vgl. pr56370
393
Programmieren in Java
setVisible(true);
}
public static void main(String[] args)
{
new JSliders();
}
}
JProgressBar
Die Klasse JProgressBar zeigt einen im Programm berechneten Fortschritt eines
Vorgangs an. Im Fortschritts-Balken kann ein Text angezeigt werden
Konstruktoren.
public JProgressBar(int orient)
public JProgressBar(int min, int max)
public JProgressBar(int orient,int min,int max)
Methoden.
public void setStringPainted(boolean b)
// Standardmäßig wird die Fortschrittsanzeige ohne Beschriftung dargestellt. Durch Aufruf von
// setStringPainted() mit Übergabe true kann ein prozentualer Fortschrittswert angezeigt
// werden.
public void setValue(int n)
// Veränderung der grafischen darstellung des Fortschritts
public int getValue()
// Abfrage des Werts des grafischen Fortschritts
Bsp.301:
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.event.*;
public class JProgressBarDemo extends JFrame
{
protected int m_min = 0;
protected int m_max = 100;
protected int m_counter = 0;
protected JProgressBar jpb;
public JProgressBarDemo()
{
301
pr56370
394
Programmieren in Java
super("JProgressBar Demo");
setSize(300,50);
UIManager.put("ProgressBar.selectionBackground", Color.black);
UIManager.put("ProgressBar.selectionForeground", Color.white);
UIManager.put("ProgressBar.foreground", new Color(8,32,128));
jpb = new JProgressBar();
jpb.setMinimum(m_min);
jpb.setMaximum(m_max);
jpb.setStringPainted(true);
JButton start = new JButton("Start");
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Thread runner = new Thread() {
public void run() {
m_counter = m_min;
while (m_counter <= m_max) {
Runnable runme = new Runnable() {
public void run() {
jpb.setValue(m_counter);
}
};
SwingUtilities.invokeLater(runme);
m_counter++;
try {
Thread.sleep(100);
}
catch (Exception ex) {}
}
}
};
runner.start();
}
});
getContentPane().add(jpb, BorderLayout.CENTER);
getContentPane().add(start, BorderLayout.WEST);
WindowListener wndCloser = new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{ System.exit(0); }
};
addWindowListener(wndCloser);
setVisible(true);
}
}
public static void main(String[] args)
{ new JProgressBarDemo(); }
395
Programmieren in Java
JComponente
JScrollBar
JProgressBar
protected int blockIncrement
protected BoundRangeModel model
protected int orientation
protected int unitIncrement
protected ChangeEvent changeEvent
protected ChangeListener changeListener
protected BoundRangeModel model
protected int orientation
protected boolean paintBorder
protected boolean paintString
protected String progressString
<< Konstruktor >>
public JScrollBar()
public JScrollBar(int orientation)
public JScrollBar(int orientation,
int value, int extent,
int min, int max)
<< Methoden >>
public void AddAdjustmentListener(
AdjustmentListener l)
….
public int getBlockIncrement()
public int getMaximum()
public Dimension getMaximumSize()
…
<< Konstruktor >>
public JProgressBar()
…
<< Methoden >>
public void addChangeListener(
ChangeListener l)
protected ChangeListener
createChangeListener
…
JSlider
public ChangeEvent changeEvent
public ChangeListener changeListener
<< Konstruktor >>
public JSlider()
<< Methoden >>
…
Abb.: Die Klassen JProgressBar, JScrollBar und JSlider
396
Programmieren in Java
5.6.5.5 Tabellen
Das Modell für JTable muß das Interface TableModel implementieren. Die Klasse
AbstractTableModel kann als Ableitungsbasis für die konkrete Datenklasse
verwendet werden. Die Klasse DefaultTableModel enthält eine Implementierung
aus ineinander verschachtelten Vector-Objekten.
Konstruktoren.
public JTable(Object[][] rowData,Object ColumnNanes)
Im zweidimensionalen Array enthält die erste Dimension die Zeilen, die zweite die Spalten. Zur
Darstellung in der Tabelle werden die Arrayelemente mit toString() in Strings umgewandelt. Das 2.
Array enthält ein Array mit Strings, die als Spaltenköpfe angezeigt werden.
public JTable(Vector rowData,Vector columnNames)
Daten und Spalten werden in einem Vector übergeben. Der Datenvektor rowData muß für jede Zeile
einen Subvektor mit den Datenelementen der Zeile enthalten.
public JTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm)
Das TableModel stellt Daten zur Verfügung.
Das TableColumnModel definiert die Spalten.
Das ListSelectionModel ist für die Auswahl von Tabellenelelementen zuständig.
Bsp.: Erzeugen einer einfachen Tabelle
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SimpleTable extends JFrame
{
public static final String[][] DATEN =
{
{"Uebungsblatt 1", "Binaere Baumknoten in Java"},
{"Uebungsblatt 1a", "Baumansichten der Java Foundation Classes"},
{"Uebungsblatt 2", "Verarbeitung binaerer Suchbaumknoten"},
{"Uebungsblatt 3", "Die generische Klasse BinaererSuchbaum in Java"},
{"Uebungsblatt 4", "Die generische Klasse AvlBaum in Java"},
{"Uebungsblatt 5", "Perfekt ausgeglichene Baeume"},
{"Uebungsblatt 6", "Rot-Schwarz-Baeume"},
{"Uebungsblatt 7", "AA-Baeume"},
{"Uebungsblatt 8", "Splay-Baeume"},
{"Uebungsblatt 9", "Bayer-Baeume"},
{"Uebungsplatt 10", "Auf Platte / Diskette gespeicherte Bayer-Bäume"},
{"Uebungsblatt 11", "Hash-Tabellen"},
{"Uebungsblatt 12", "Heap-Algorithmen"},
{"Uebungsblatt 13", "Vorrangwarteschlangen"},
{"Uebungsblatt 14", "Binomial Queue, Fibonacci Heap"},
{"Uebungsblatt 15", "Backtracking-Algorithmen"},
{"Uebungsblatt 16",
"Ansichten der Java Foundation Classes JList und JTable"},
{"Uebungsblatt 17", "Skip Lists"}
};
public static final String[] COLHEADS =
{
"Ausgabe", "Titelthema"
};
// Konstruktor
public SimpleTable()
{
super("Uebersicht zu den Uebungen in AD");
JTable table = new JTable(DATEN, COLHEADS);
Container cp = getContentPane();
cp.add(new JLabel("Uebungen zu AD im WS 02"), "North");
cp.add(new JScrollPane(table), "Center");
}
397
Programmieren in Java
public static void main(String[] args)
{
SimpleTable frame = new SimpleTable();
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
frame.setLocation(100, 100);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Das Programm erzeugt die folgende Tabelle:
Abb.: Tabelle, die von SimpleTable erzeugt wird
Konfiguration der Tabelle.
Mit public void setRowHeight(int rowHeight) wird die Gesamthöhe einer Zeile festgelegt.
Alle Zeilen sind dabei gleich hoch.
Mit public void setRowMargin(int rowMartgin) wird der am oberen Rand und unteren
Rand jeder Zeile frei bleibende Platz bestimmt. Der für den Inhalt der Zelle verfügbare Platz ergibt
sich aus der Zellenhöhe minus oberem und unterem Rand. Durch Aufruf von public void
setIntercellSpacing(Dimension newSpacing) kann (zusammen mit dem vertikalen) auch
der horizontale Rand der Zellenelemente festgelegt werden.
Standardmäßig werden die Zellen einer JTable mit senkrechten und waagrechten Begrenzungslinien
voneinander getrennt. Mit public void setShowGrid(boolean b) können beide Linienarten
zugleich an- oder ausgeschaltet werden. Sollen die horizontalen oder vertikalen Linien separat
aktiviert oder deaktiviert werden, können
public void setShowHorizontalLines(boolean b)
public void setShowVerticalLines(boolean b)
verwendet werden. Das Verändern der Zellen ist in begrenzter Weise mit folgenden Methoden
möglich:
public void setGridColor(Color newColor)
// verändert die Farbe, mit der Gitterlinien angezeigt werden
public void setSelectionForeground(Color selectionForeground)
public void setSelectionBackground(Color selectionBackground)
Das Verhalten der Tabelle, nachdem die Breite einer einzelnen Spalte verändert wurde, bestimmt
public void setAutoResize(int mode)
398
Programmieren in Java
Der dadurch frei werdende oder zusätzlich benötigte Platz kann auf unterschiedliche Weise den
übrigen Spalten zugeordnet werden. Der Parameter mode kann folgende Werte annehmen:
Modus
AUTO_RESIZE_OFF
Bedeutung
keine automatische Größenanpassung der Spalten.
Wurde die Tabelle in JScrollPane verpackt, bekommt
sie nötigenfalls einen horizontale Schieberegler.
AUTO_RESIZE_LAST_COLUMN
Die letzte Spalte wird zum Größenausgleich
verwendet. Dadurch reduziert sich der Platz für die
letzte Spalte, wenn eine andere Spalte vergrößert
wird, und er erhöht sich, wenn sie verkleinert wird.
AUTO_RESIZE_NEXT_COLUMN
Die recht neben der modifizierten Spalte liegende
Spalte wird zum Größenausgleich verwendet.
AUTO_RESIZE_SUBSEQUENT_COLUMNS Die Größenänderung wird gleichmäßig auf alle
nachfolgenden Spalten verteilt.
AUTO_RESIZE_ALL_COLUMNS
Die Größenänderung wird auf alle Spalten der Tabelle
verteilt.
Selektieren von Elementen
Selektionsmodi: Die folgenden Methoden der Klasse JTable regeln die Selektion:
public void setRowSelectionAllowed(boolean flag)
Zeilenweise Selektion nach Aufruf dieser Methode mit true als Argument.
public void setColumnSelectionAllowed(boolean flag)
Spaltenweise Selektion nach Aufruf dieser Methode mit true als Argument.
Durch Übergabe von false können Selektionsmarken ausgeschaltet werden und nur noch einzelne
Zellen selektiert werden. Standardmäßig kann zeilen-, aber nicht spaltenweise selektiert werden.
public void setSelectionMode(int selectionMode)
legt fest, ob ein einzelnes Element, ein zusammenhängender Bereich oder mehrere Bereiche
selektiert
werden
können.
Hier
ist
eine
der
Konstanten
SINGLE_SELECTION,
SINGLE_INTERVAL_SELECTION,
oder
MULTIPLE_INTERNAL_SELECTION
der
Klasse
ListSelectionModel zu übergeben.
public void setSelectionEnabled(boolean flag)
Der Aufruf dieser Methode mit true als Argument bewirkt: Zeile und Spalten können gleichzeitig
markiert und so zusammenhängende rechteckige Bereiche von Zellen (einschl. einer einzelnen)
selektiert werden.
Abfragen der Selektion. Die folgenden Methoden der Klasse JTable bestimmen, welche Elemente
selektiert wurden:
public int getSelectionRow()
public int getSelectionColumn()
Die beiden Methoden liefern die selektierte Zeile bzw. Spalte, wenn der Selektionsmodus
SINGLE_SELECTION ist. Die 1. Zeile und Spalte haben jeweils den Index 0. Erlaubt der aktuelle
Selektionsmodus das Selektieren ganzer Zeilen und Spalten, dann impliziert das Ergebnis: Alle
Elemente dieser Zeile bzw. Spalte sind selektiert. Falls keine Elemente selektiert sind, wird –1
zurückgegeben.
public int [] getSelectedRows()
public int [] getSelectedCloumns()
Falls einer der Mehrfachselektionsmodi aktiviert ist, können über diese beiden Methoden Arrays mit
allen selektierten Zeilen und Spalten beschafft werden. Falls keine Elemente selektiert sind, wird ein
leeres Array zurückgegeben.
Verändern der Selektion. JTable stellt Methoden bereit mit denen die Selektion programmgesteuert
verändert werden kann:
public void selectAll()
public void clearSelection()
public void setRowSelectionIntervall(int index0, int index1)
399
Programmieren in Java
Markieren eines zusammenhängenden Bereichs von Zeilen
public void addRowSelectionIntervall(int index0, int index1)
Hinzufügen eines zusammenhängenden Bereichs zur aktuellen Selektion
public void removeRowSelectionIntervall(int index0, int index1)
Entferenen eines zusammenhängenden Bereichs aus der aktuellen Selektion
public void setColumnSelectionIntervall(int index0, int index1)
public void addColumnSelectionIntervall(int index0, int index1)
public void removeColumnSelectionIntervall(int index0, int index1)
Zugriff auf den Inhalt der Tabelle
Daten in der Tabelle. Unabhängig von der aktuellen Selektion kann auf den Inhalt der Tabelle
zugegriffen werden.
pulic int getRowCount()
public int getColumnCount()
liefern die aktuelle Zeilen- bzw. Spaltenanzahl der Tabelle.
Editieren von Tabellenelementen. Nach einem Doppelklick auf eine Zelle kann der Anwender die in
diesem Element enthaltenen Daten verändern. JTable besitzt eigene Methoden, mit denen abgefragt
werden kann, ob und in welcher Zelle die Tabelle gerade editiert wird.
Das Tabellenmodell. Ein eigenes Tabellenmodell muß das Interface TableModel
implementieren und bei der Instanzierung an den Konstruktor der JTable
übergeben. Wahlweise kann nach Instanzierung auf das Modell mit folgenden
Methoden der Klasse JTable zugegriffen werden:
public void setModel(TableModel newModel)
public TableModel getModel()
Das Interface TableModel definiert folgende Methoden:
public
public
public
public
public
public
public
public
void addTableModelListener(TableModelListener l)
Class getColumnClass(int columnIndex)
int getColumnCount()
int getRowCount()
Object getValueAt(int rowIndex,int columnIndex)
boolean isCellEditable(int rowIndex,int columnIndex)
void removeTableModelLister(TableModelListener l)
void setValueAt(Object aValue,int rowIndex,int columnIndex)
Das Spaltenmodell. Das Spaltenmodell einer JTable muß das Interface
TableColumnModel aus dem Paket javax.swing.table implementieren. Es wird bei
der Instanzierung einer JTable an den Konstruktor übergeben. Ohne weitere
Ableitung kann die Standard-Implementierung DefaultTableColumnModel
verwendet werden. Sie stellt Methoden zum Hinzufügen bzw. Entfernen von
Spaltenobjekten (Typ TableColumn) bereit.
public void addColumn(TableColumn aColumn)
public void removeColumn(TableColumn aColumn)
Bsp.:
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.table.*;
public class SimpleTable2 extends JFrame
{
400
Programmieren in Java
public static final String[][] DATEN =
{
{"Uebungsblatt 1", "Binaere Baumknoten in Java"},
{"Uebungsblatt 1a", "Baumansichten der Java Foundation Classes"},
{"Uebungsblatt 2", "Verarbeitung binaerer Suchbaumknoten"},
{"Uebungsblatt 3", "Die generische Klasse BinaererSuchbaum in Java"},
{"Uebungsblatt 4", "Die generische Klasse AvlBaum in Java"},
{"Uebungsblatt 5", "Perfekt ausgeglichene Baeume"},
{"Uebungsblatt 6", "Rot-Schwarz-Baeume"},
{"Uebungsblatt 7", "AA-Baeume"},
{"Uebungsblatt 8", "Splay-Baeume"},
{"Uebungsblatt 9", "Bayer-Baeume"},
{"Uebungsplatt 10", "Auf Platte / Diskette gespeicherte Bayer-Bäume"},
{"Uebungsblatt 11", "Hash-Tabellen"},
{"Uebungsblatt 12", "Heap-Algorithmen"},
{"Uebungsblatt 13", "Vorrangwarteschlangen"},
{"Uebungsblatt 14", "Binomial Queue, Fibonacci Heap"},
{"Uebungsblatt 15", "Backtracking-Algorithmen"},
{"Uebungsblatt 16",
"Ansichten der Java Foundation Classes JList und JTable"},
{"Uebungsblatt 17", "Skip Lists"}
};
public static final String[] COLHEADS =
{
"Ausgabe", "Titelthema"
};
// Konstruktor
public SimpleTable2()
{
super("Uebersicht zu den Uebungen in AD");
//Spaltenmodell erzeugen
DefaultTableColumnModel cm = new DefaultTableColumnModel();
for (int i = 0; i < COLHEADS.length; i++)
{
TableColumn col = new TableColumn(i, i == 2 ? 150 : 60);
col.setHeaderValue(COLHEADS[i]);
cm.addColumn(col);
}
//Tabellenmodell erzeugen
TableModel tm = new AbstractTableModel()
{
public int getRowCount()
{
return DATEN.length;
}
public int getColumnCount()
{
return DATEN[0].length;
}
public Object getValueAt(int row, int column)
{
return DATEN[row][column];
}
};
JTable table = new JTable(tm, cm);
Container cp = getContentPane();
cp.add(new JLabel("Uebungen zu AD im WS 02"), "North");
cp.add(new JScrollPane(table), "Center");
}
public static void main(String[] args)
{
SimpleTable2 frame = new SimpleTable2();
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
401
Programmieren in Java
{
System.exit(0);
}
});
frame.setLocation(100, 100);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Rendering302 der Zellen
public TableCellRenderer getDefaultRenderer(Class columnClass)
public void setDefaultCellRenderer(Class columnClass,
TableCellRenderer renderer)
Sofern nicht in den Tabellenspalten ein eigener Renderer bestimmt ist, ist der
Standard-Renderer für die Darstellung aller Tabellenelemente zuständig. Es muß das
Interface TableCellRenderer implementieren, das nur eine einzige Methode enthält.
public Component getTableCellRendererComponent(
JTable table,Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column)
Diese Methode arbeitet als Factory-Methode und wird nur dann aufgerufen, wenn zur
Darstellung einer Zelle ein Renderer benötigt wird.
Standardmäßig wird als Renderer eine Instanz der Klasse DefaultTableCellRenderer
verwendet. Sie ist eine Ableitung von JLabel mit deren Hilfe Farbe, Hintergrund und
das Look-and-Feel der Tabelle an die Erfordernisse der jeweiligen Zelle angepasst
werden.
Reaktion auf Ereignisse. JTable generiert eine Vielzahl vom Ereignissen, um
registrierte Listener über Änderungen des Tabellenzustands zu informieren. Will ein
Objekt beispielsweise darüber informiert werden, daß sich die Selektion geändert
hat, muß es zwei ListSelectionListener registrieren. Einer davon wird auf dem
Selektionsmodell registriert, das mit getSelectionModel() ermittelt werden kann.
Da dieser nur Informationen über Änderungen an der Zeilenselektion versendet, muß
ein zweiter Listener auf dem Modell für die Spaltenselektion registriert werden. Es
kann durch Aufruf von getColumnModel() beschafft werden, und auf sein
Selektionsmodell kann ebenfalls mit getSelectionModel() zugegriffen werden.
Bei jeder Änderung der Selektion wird nun valueChanged() aufgerufen und kann
mit Hilfe der oben erläuterten Methoden herausfinden, welche Zeilen und Spalten
selektiert sind.
Die Tabelle informiert auch über Änderungen ihrer Daten. Dazu muß auf dem
Tabellenmodell (das mit getModel() beschafft wird) durch Aufruf von
addTableModelListener() ein TableModelListener registriert werden. Bei
jeder Änderung des Modells wird dann dessen Methode tableChanged()
aufgerufen.
Schließlich können alle in den Superklassen von JTable definierten Listener
registriert werden.
302 Vorgang, der dafür sorgt, dass die Zellen auf dem Bildschirm dargestellt werden. Die dafür verantwortlichen
Komponenten werden als Renderer bezeichnet.
402
Programmieren in Java
JComponent
JTable
...
protected TableCellEditor cellEditor
protected boolean cellSelectionEnabled
protected TableCloumnModel columnModel
protected TableModel dataModel
…
protected Color selectionBackground
potected Color selectionForeground
protected ListSelectionModel selectionModel
protected boolean showHorizontalLines
protected boolean showVerticalLines
<< Konstruktoren >>
public JTable()
public JTable(int numRows, int numColumns)
public JTable(Object[][] neuDaten,Object [] columnNames)
public JTable(TableModel dm)
public JTable(TableModel dm, TableColumnModel cm)
public JTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm)
public JTable(Vector rowData, Vector columnNames)
<< Methoden >>
public void addColumn(TableColumn aColumn)
…
public void addNotify()
…
public void clearSelection()
public void columnAdded(TableColumnModelEvent e)
public int columnAtPoint(Point point)
…
public void doLayout()
public boolean editCellAt(int row, int column)
…
public TableCellEditor getCellEditor()
public TableCellEditor getCellEditor(int row,int column)
public Rectangle getCellRect(int row,int column,boolean includeSpacing)
public TableCellRenderer getCellRenderer(int row,int column)
public TableColumn getColumnClass(int column)
public int getColumnCount()
public TableColumnModel getColumnModel()
public String columnName(int column)
public boolean getSelectionEnabled()
public Class getColumnClass(int column)
public int getColumnCount()
public TableColumnModel getColumnModel()
public String getColumnName(int column)
public boolean getColumnSelectionAllowed()
…
403
Programmieren in Java
public TableModel getModel()
public int getRowCount()
public int getRowHeight()
public int getRowHeight(int row)
…
public ListSelectionModel getSelectionModel()
public boolean getShowHorizontalLines()
public boolean getShowVerticalLines()
…
public JTableHeader getTableHeader()
…
public TableUI getUI()
…
public Object getValueAt(int row,int column)
…
public boolean isCellEditable(int row,int column)
…
public String paramString()
…
public void selectAll()
…
public void setModel(TableModel dataModel)
…
public void setRowHeight(int rowHeight)
public void setRowHeight(int row,int rowHeight)
…
public void setUI(TableUI ui)
…
public void tableChanged(TableMoveEvent e)
…
public void updateUI()
public void valueChanged(ListSelectionEvent e)
Abb.: Die Klasse JTable
404
Programmieren in Java
<< interface >>
TableModel
public void addTableModelListener(TableModelListener l)
public Class getColumnClass(int columnIndex)
public int getColumnCount()
public int getRowCount()
public Object getValueAt(int rowIndex,int columnIndex)
public boolean isCellEditable(int rowIndex,int columnIndex)
public void removeTableModelLister(TableModelListener l)
public void setValueAt(Object aValue,int rowIndex,int columnIndex)
AbstractTableModel
{ abstract }
…
public int findColumn(String columnName)
public void fireTableCellUpdated(int row,int column)
public void fireTableChanged(TableModelEvent e)
public void fireTableChanged()
…
public EventListener[] getListeners(Class listenerType)
public TableModelListener[] getTableModelListener()
…
DefaultTableModell
proteced Vector columnIdentifiers
protected Vector dataVector
<< Konstruktoren >>
public DefaultTableModel()
public DefaultTableModel(int rowCount,int ColumnCount)
public DefaultTableModel(Object[] columnNames,int rowCount)
public DefaultTableModel(Vector columnNames,int rowCount)
public DefaultTableModel(Vector data,Vector columnNames)
<< Methoden >>
public void addColumn(Object columnName)
public void addColumn(Object columnName,Object[] columnData)
public void addColumn(Object columnName,Vector columnData)
public void addRow(Object[] rowData)
public void addRow(Vector data)
…
public int getColumnCount()
public String getColumnName()
public Vector getDataVector()
public int getRowCount()
public Object getValueAt(int row,int column)
public void insertRow(int row,Object[] rowData)
public void insertRow(int row,Vector rowData)
…
public void setDateVector(Object[][] dataVector,Object[] columnIdentifiers)
public setDataVector(Vector dataVector,Vector columnIdentifiers)
405
Programmieren in Java
public void setNumRows(int rowCount)
public void setRowCount(int rowCount)
public void setValueAt(Object aValue,int row,int column)
<< interface >>
TableCellEditor
public Component getTableCellEditorComponent(JTable table,Object value,
boolean isSelected,int row,int column)
DefaultCellEditor
…
protected JComponent editorComponent
<< Konstruktoren >>
public DefaultCellEditor(JCheckBox checkBox)
public DefaultCellEditor(JComboBox checkBox)
public DefaultCellEditor(JTextField textField)
<< Methoden >>
…
public Object getCellEditorValue()
…
public Component getComponent()
…
public boolean isCellEditable(EventObject anEvent)
…
<< interface >>
TableCellRenderer
public Componet getTableCellRendererComponent(JTable table,Object value,
boolean isSelected, boolean hasFocus,int row,int column)
DefaultTableCellRenderer
<< Konstruktor >>
public DefaultTableCellRenderer()
<< Methoden >>
public void firePropertyChange(String propertyName,boolean oldValue,boolean newValue)
…
public void validate()
406
Programmieren in Java
5.6.5.6 Hierarchische Strukturen
Die Klasse JTree dient zur Darstellung, Navigation und Berabeitung baumartiger,
hierarchischer Strukturen. Die Baumdarstellung basiert auf einem hierarchischen
Datenmodell. Jede Modellklasse zur Baumdarstellung muß sich auf das Interface
TreeModel beziehen. JTree besitzt dafür Konstruktoren bzw. die Zugriffsmethoden
getModel() und setModel(). Die einzelnen Baumknoten müssen die Interfaces
TreeNode
oder
MutableTreeNode
implementieren.
Die
Klasse
DefaulMutableTreeNode enthält eine universelle Implementierung (inklusive
Navigationsmethoden) für Baumknoten
Erzeugen eines Baums. Liegt ein geeignetes Datenmodell vor, ist das Instanzieren
eines JTree einfach. Die beiden wichtigsten Konstruktoren der Klasse JTree sind:
public JTree(TreeModel neuesModell)
// erwartet wird ein vordefiniertes TreeModel zur Darstellung der Baumelemente. Ein TreeModel
// kapselt alle relevanten Informationen über die Baumstruktur. Es liefert auf Anfrage die Wurzel
// des Baums, stellt Informationen über einen bestimmten Knoten bereit oder liefert dessen
// Nachfolgeknoten
public JTree(TreeNode wurzel)
// Die Wurzel wird automatisch in ein geeignetes TreeModel eingebettet. Der JTree erfragt die zur
// Darstellung der Navigation erforderlichen Daten immer beim TreeModel. Das über wurzel
// Baumwurzel) instanzierte DefaultTreeModel kann diese Informationen aus den Knoten und den
// darin gespeicherten Verweisen auf ihre Nachfolgeknoten entnehmen.
Zugriffsmethoden auf das Datenmodell.
public TreeModel getModel()
public void setModel(TreeModel neuesModell)
Anzeige bzw. Unterdrücken der Wurzel bei der Baumdarstellung. Eine wichtige
Konfigurationsoption regelt, ob die Wurzel des Baums bei seiner Darstellung
angezeigt oder unterdrückt werden soll. Auf sie kann mit den Methoden
public void setRootVisible(boolean rootVisible)
public boolean isRootVisible()
zugegriffen werden.
Zur Beschriftung eines Knotens bei der visuellen Darstellung verwendet ein JTree
die Methode toString().
Abfragen zur Selektion von Baumknoten. JTree stellt eine Reihe von Methoden für
Abfragen bereit, ob und welche Knoten selektiert sind, z.B.:
public TreePath getSelectionPath()
// ermittelt das selektierte Element. Bei aktivierter Mehrfachselektion liefert die Methode das erste aller
// selektierten Elemente. Falls kein Knoten selektiert ist, wird null zurückgeliefert.
public TreePath[] getSelectionPaths()
// zurückgegeben wird ein Array mit allen selektierten Knoten
public TreePath getLeadSelectionPath()
// liefert das markierte Element
Die angegebenen Methoden liefern Objekte der Klasse TreePath. Diese Klasse
beschreibt einen Knoten im Baum über den Pfad, der von der Wurzel aus beschritten
werden muß, um zu den Knoten zu gelangen. Mit
public Object getLastSelectedPathComponent()
407
Programmieren in Java
der Klasse TreePath kann das letzte Element dieses Pfads bestimmt werden. Mit
public Object[] getPath()
der Klasse TreePath kann der vollständige Pfad ermittelt werden. An erster Stelle
liegt die Wurzel des Baums, an letzter Stelle das selektierte Element. Solle ermittelt
werden, ob und welche Elemente im Baum selektiert sind, können die Methoden
public boolean isSelectionEmpty()
public boolean isPathSelected(TreePath pfad)
aufgerufen werden.
Verändern der Selektion. Die Selektion kann programmgesteuert verändert werden:
public void clearSelection()
// Löschen der Selektion
public void addSelectionPath(TreePath path)
// Erweitern der Selektion um ein einzelnes Element
public void addSelectionPaths(TreePath [] pfade)
// Erweitern der Selektion um eine Menge Knoten
public void setSelectionPath(TreePath path)
// Selektion der als Argument übergebenen Knoten)
public void setSelectionPaths(TreePath[] paths)
// Selektion der als Argument übergebenen Knoten
Zur unmittelbaren Reaktion auf Änderungen der Selektion durch den Anwender kann
ein
TreeSelectionListener
instanziert
werden
(Registrierung
mit
addTreeSelectionListener() beim JTree). Bei jeder Selektionsänderung wird
public void valueChanged(TreeSelectionEvent event)
aufgerufen. "event" stellt
public TreePath getOldLeadSelectionPath()
public TreePath getNewLeadSelectionPath()
zur Verfügung, um auf den vorherigen und aktuellen Selektionspfad zuzugreifen.
Öffnen und Schließen der Knoten. Der Anwender kann die Knoten mit Maus- oder
Tastaturkommandos öffnen oder schließen. Dadurch werden Nachfolgeknoten
sichtbar oder versteckt.
public boolean isExpanded(TreePath pfad)
// liefert true, wenn der Knoten geöffnet ist
public boolean isCollapsed()
// liefert true, wenn der Knoten geschlossen ist
public boolean hasBeenExpanded(TreePath path)
// gibt an, ob der Knoten überhaupt schon einmal geöffnet wurde
public boolean isVisible(TreePath pfad)
// liefert true, wenn der Knoten sichtbar ist, d.h. all seine Elternknoten geöffnet sind
public void makeVisible(TreePath pfad)
// Sichtbar Machen eines Knoten
public void expandPath(TreePath pfad)
// Öffnen eines Knoten
public void collapsePath(TreePath pfad)
// Schließen eines Knoten
Verändern der Baumstruktur. Es können neue Knoten eingefügt, bestehende entfernt
oder vorhandene modifiziert werden.
408
Programmieren in Java
Interfaces zur Modellierung und Sicht auf einen Baum: Drei Interfaces erlauben
Modellierung und Sicht auf einen Baum: TreeModel, TreeSelectionModel,
TreeCellRenderer.
Ein viertes Interface TreeNode beschreibt, was in jedem Knoten des Baums
dargestellt wird.
TreeModel
Dieses Interface beschreibt das einem JTree zugeordnete Datenmodell. Das
TreeModel-Interface spezifiziert, wie ein Baum (über eine Datenstruktur) abgebildet
wird.
public
public
public
public
public
Object getChild(Object parent, int index)
int getChildCount(Object parent)
int getIndexOfChild(Object parent,Object child)
Object getRoot()
Object isLeaf(Object node)
Drei weitere Methoden
public void addTreeModelListener(TreeModelListener l)
public void removeTreeModelListener(TreeMoldelListener l)
public void valueForPathChanged(TreePath pfad, Object neuerWert)
behandeln Hinzufügen, Entfernen und die Kentnisnahme von Event-Listeners. Diese
Listeners bemerken Änderungen im TreeModel nach Empfang von TreeModelEventNachrichten.
Ein Objekt, das diese Methoden definiert, kann Modell für einen JTree sein. Die
DefaultTreeModel-Klasse ist eine einfache Implementierung des TreeModel,
das explizit TreeNode- und MutableTreeNode-Objekte benutzt.
TreeNode
JTree-Objekte werden aus TreeNode-Objekten gebildet (einfache Repräsentationen
eines Baumknoten). Die wichtigsten Methoden von TreeNode sind:
public int getChildCount()
// ermittelt die Anzahl der Nachfolgeknoten. Sie werden von 0 an durchnummeriert.
public TreeNode getChildAt(int childIndex)
// liefert einen beliebigen Nachfolgeknoten
public TreeNode getParent()
// Ein Knoten kennt seinen Vaterknoten, der mit getParent() ermittelt werden kann
public boolean isLeaf()
// Abfrage, ob ein Knoten ein Blatt ist oder weitere Nachfolgeknoten enthält
Die Klasse DefaultMutableTreeNode definiert Methoden zum Anschauen und
Manipulieren von Baumknoten. Diese Klasse implementiert das Interface
MutableTreeNode (Extension des Interface TreeNode).
Anwendungsbezogene Informationen werden bereits dem Konstruktor übergeben:
public DefaultMutableTreeNode(Object benutzerObjekt)
409
Programmieren in Java
DefaultMutableTreeNode hat Methoden zum Einfügen und Löschen von Knoten
implementiert:
public void add(MutableTreeNode neuesKind)
// ein Kindknoten wird an das Ende der Liste der Nachfolgeknoten angefügt
public void insert(MutableTreeNode neuesKind, int KindIndex)
// Einfügen eines neuen Kindknoten an beliebiger Stelle
public void remove(int kindIndex)
// Entfernen eines beliebigen Knoten
public void removeAllChildren()
// Entfernen aller Kindknoten
/* Zugriff auf Infornationen, die in einem benutzerObjekt gehalten werden */
public void setUserObject(Object benutzerObjekt)
public Object getUserObject()
// benutzerObjekt ist auch der Lieferant von Knotenbeschriftungen. Jeder Aufruf von toString()
// wird an benutzerObjekt weitergeleitet
TreeSelectionModel
Diese Interface spezifiziert, wie ein Benutzer einen Pfad bestimmter Objekte
auswählen kann. JTree benutzt dieses Modell zur Auswahl von Regeln.
DefaultTreeSelectionModel
ist
eine
Implementierung
von
TreeSelectionModel.
TreeSelectionModel steuert das Selektieren von Knoten. Die Zugriffsmethoden
auf TreeSelectionModel sind:
public void setSelectionModel(TreeSelectionModel selectionModel)
public TreeSelectionModel getSelectionModel()
Standardmäßig erlaubt ein JTree das Selektieren mehrerer Knoten. Soll die
Selektionsmöglichkeit auf einen Knoten beschränk werden, muß ein eigenes
TreeSelectionModel an setSelectionModel() übergeben werden. Dazu
kann eine Instanz der Klasse DefaultTreeSelectionModel erzeugt werden und
durch Aufruf von
public void setSelectionMode(int mode)
und Übergabe einer der Konstanten
SINGLE_TREE_SELECTION
CONTIGUOUS_TREE_SELECTION
DISCONTIGUOUS_TREE_SELECTION
konfiguriert werden.
TreeCellRenderer
Dieses Interface wird zur visuellen Repräsentation von Baumknoten benutzt. Es
umfasst nur eine Methode.
Anwendungen
410
Programmieren in Java
1. Ein Baum, der aus DefaultMutableTreeNodes aufgebaut ist303.
import java.awt.*;
import javax.swing.*;
import javax.swing.tree.*;
public class SimpleTree extends JFrame
{
public SimpleTree()
{
super("Creating a Simple JTree");
// WindowUtilities.setNativeLookAndFeel();
/* try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
}
catch(Exception e)
{
System.out.println("Error setting native L&F: " + e);
} */
// addWindowListener(new ExitListener());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container content = getContentPane();
Object[] hierarchy =
{ "javax.swing",
"javax.swing.border",
"javax.swing.colorchooser",
"javax.swing.event",
"javax.swing.filechooser",
new Object[] { "javax.swing.plaf",
"javax.swing.plaf.basic",
"javax.swing.plaf.metal",
"javax.swing.plaf.multi" },
"javax.swing.table",
new Object[] { "javax.swing.text",
new Object[] { "javax.swing.text.html",
"javax.swing.text.html.parser" },
"javax.swing.text.rtf" },
"javax.swing.tree",
"javax.swing.undo" };
DefaultMutableTreeNode root = processHierarchy(hierarchy);
JTree tree = new JTree(root);
content.add(new JScrollPane(tree), BorderLayout.CENTER);
setSize(275, 300);
setVisible(true);
}
public static void main(String[] args)
{
new SimpleTree();
}
/** Small routine that will make node out of the first entry
* in the array, then make nodes out of subsequent entries
* and make them child nodes of the first one. The process is
* repeated recursively for entries that are arrays.
*/
private DefaultMutableTreeNode
processHierarchy(Object[] hierarchy)
{
DefaultMutableTreeNode node =
new DefaultMutableTreeNode(hierarchy[0]);
DefaultMutableTreeNode child;
for(int i=1; i<hierarchy.length; i++)
{
303
vgl. pr56570
411
Programmieren in Java
Object nodeSpecifier = hierarchy[i];
if (nodeSpecifier instanceof Object[])
// Knoten mit Nachfolgeknoten
child = processHierarchy((Object[])nodeSpecifier);
else
child = new DefaultMutableTreeNode(nodeSpecifier);
// Blatt
node.add(child);
}
return(node);
}
}
Swing besitzt einen Manager für Benutzerschnittstellen, der das "Look and Feel"
(Java L&F) von Komponenten kontrolliert. Das Management des Look and Feel wird
von der Klasse UIManager übernommen. Folgende Auswahlmöglichkeiten stehen in
Abhängigkeit von der Java-Entwicklungsumgebung zur Verfügung:
- das Windows-95- und Windows-NT-Look-and-Feel
- das Motif-x-Windows-System-Look-and-Feel
- Metal
Die
Auswahl
des
Look-and-Feel
erfolgt
mit
der
Methode
getSystemLookAndFeelClassName() als Argument für die Methode
setLookandFeel. Es werden unterschiedliche Ergebnisse auf unterschiedlichen
Betriebssystemen erzeugt.
412
Programmieren in Java
413
Programmieren in Java
JComponent
JTree
...
protected TreeCellEditor cellEditor
protected TreeCellRendrer cellRenderer
…
protected boolean rootVisible
protected TreeSelectionModel selectionModel
…
protected TreeModel treeModel
protected TreeModellListener treeModelListener
…
<< Konstruktoren >>
JTree()
JTree(Hashtable wert)
JTree(TreeModel neuesModel)
JTree(TreeNode root)
JTree(Vector wert)
JTree(Object[] wert)
…
<< Methoden >>
…
public void addSelectionPath(TreePath pfad)
public void addSelectionPaths(TreePath[] pfade)
public TreePath getSelectionPath()
// ermittelt das ausgewählte Element
public TreePath[] getSelectionPaths()
public void setSelectionPath(TreePath pfad)
public void setSelectionPaths(TreePath[] pfade)
public TreePath getLeadSelectionPath()
// liefert das markierte Element
…
public void setRootVisible(boolean rootVisible)
public boolean isRootVisible()
public TreeModel getModel()
public void setModel(TreeModel neuesModel)
public TreeCellRenderer getCellRenderer ()
public void setCellRenderer(TreeCellrenderer x)
…
public void setUI(TreeUI ui)
public void updateUI()
…
public void setSelectionModel(TreeSelectionModel selectionModel)
…
Abb.: Die Klasse JTree
414
Programmieren in Java
<< interface >>
TreeModel
public Object getRoot()
public Object getChild(Object parent, int index)
public boolean isLeaf(Object knoten)
public void valueForPathChanged(TreePath pfad,Object neuerWert)
public int getIndexOfChild(Object parent, Object child)
public void addTreeModelListener(TreeModelListener l)
public void removeTreeListener(TreeModelListener l)
DefaultTreeModell
…
proteced TreeNode root
protected EventListenerList listenerList
<< Konstruktoren >>
public DefaultTreeModel()
public DefaultTreeModel(TreeNode root,boolean asksAllowsChildren)
<< Metoden >>
public void addTreeModelListener l)
public void removeTreeModelListener(TreeModelListener l)
…
public Object getChild(Object parent, int index)
public int getChildCount(Object parent)
public int getIndexOfChild(Object parent,Object child)
…
public TreeNode[] getPathToRoot(TreeNode einKnoten)
…
public Object getRoot()
….
public void insertInfo(MutableTreeNode neuesKind, MutableTreeNode
parent,int index)
public boolean isLeaf(Object knoten)
public void nodeChanged(TreeNode knoten)
…
public void removeNodeFromParent(MutableTreeNode knoten)
…
public void setRoot(TreeNode root)
public void valueForPathChanged(TreePath path,Object neuerWert)
415
Programmieren in Java
<< interface >>
TreeSelectionModel
static int CONTIGUOUS_TREE_SELECTION
static int DISCONTIGUOUS_TREE_SELECTION
static int SINGLE_TREE_SELECTION
public void setSelectionMode(int node)
public int setSelectionPath(TreePath path)
public void setSelectionPaths(TreePath [] paths)
public void addSelectionPath(TreePath path)
public void addSelectionPaths(TreePath [] paths)
public void removeSelectionPath(TreePath path)
public void removeSelectionPaths(TreePaths [] paths)
public TreePath getSelectionPath()
public TreePath[] getSelectionPaths()
public int getSelectionCount()
public boolean isPathSelected(TreePath path)
public boolean isSelectionEmpty()
public void clearSelection()
public int [] getSelectionRows()
public boolean isRowSelected(int row)
public void resetRowSelection()
public int getLeadSelecttionRow()
public TreePath getSelectionPath()
public void addPropertyChangeListener(PropertyChangeListener listener)
DefaultTreeSelectionModel
...
protected int leadIndex
...
protected TreePath [] selection
…
static String SELECTION_MODE_PROPERTY
protected int selectionMode
…
<< Konstruktor >>
public DefaultTreeSelectionModel()
<< Methoden >>
public void addSelectionPath(TreePath path)
public void addSelectionPaths(TreePath [] paths)
public void removeSelectionPath(TreePath path)
…
public void addTreeSelectionListener(TreeSelectionListener x)
public void removeTreeSelectionListener(TreeSelectionListener x)
…
public void updateLeadIndex()
public void insureUniqueness
public String toString()
public Object clone() throws CloneNotSupportedException
416
Programmieren in Java
<< interface >>
TreeNode
public Enumertion children()
public boolean getAllowsChildren()
public TreeNode getChildAt(int childIndex)
public int getChildCount()
public int getIndex(TreeNode node)
public TreeNode getParent()
public boolean isLeaf()
<< interface >>
MutableTreeNode
public void insert(MutableTreeNode child,int index)
public void remove(int index)
public void remove(MutableTreeNode node)
public void removeFromParent()
public void setParent(MutableTreeNode newParent)
public void setUserObject(Object objekt)
DefaultMutableTreeNode
….
protected MutableTreeNode parent
<< Konstruktoren >>
DefaultMutableTreeNode()
DefaultMutableTreeNode(Object objekt)
DefaultMutableTreeNode(Object benutzerObjekt)
<< Methoden >>
public void add(MutableTreeNode neuesKind)
public Enumeartion breadFirstEnumeration()
…
public Enumeration depthFirstEnumeration()
…
public TreeNode getChildAfter(TreeNode einKind)
…
public TreeNode getChildBefore(TreeNode einKind)
…
public int getDepth()
…
public int getLevel()
…
public TreeNode getPath()
protected TreeNode[] getPathToRoot(TreeNode einKnoten, int tiefe)
…
public TreeNode getRoot()
…
public boolean isRoot()
…
public void removeAllChildren()
…
public String toString()
417
Programmieren in Java
<< interface >>
TreeCellEditor
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected,
Boolean expanded, boolean leaf, int row)
DefaultTreeCellEditor
protected Color borderSelectionColor
protected booleab canEdit
protected Component editingComponent
protected Container editingContainer
protected Icob editingIcon
protected Font font
protected TreePath lstPath
…
protected Timer timer
protected JTree tree
<< Konstruktoren >>
public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer)
public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer,TreeCellEditor editor)
<< Methoden >>
public void setBorderSelectionColor(Color newColor)
public Color getBorderSelectionColor()
public void setFont(Font font)
public Font getFont()
…
418
Programmieren in Java
<< interface >>
TreeCellRenderer
public Component getTreeCellRendererComponent( JTree tree, Object value, boolean selected,
boolean expanded, bollean leaf, int row,
boolean lostFocus)
DefaultTreeCellRenderer
protected Color backGroundSelectionColor
protected Color borderSelectionColor
protected Icon closedIcon
protected boolean hasFocus
protected Icon leafIcon
protected Icon openIcon
protected boolean selected
protected Color textNonSelectionColor
protected Color textSelectionColor
<< Konstruktor >>
public TreeCellRenderer()
<< Methoden >>
public void setFont(Font font)
public void setLeafIcon(Icon newIcon)
public void setOpenIcon(Icon newIcon)
public void setTextNonSelectionColor(Color newColor)
public void setTextSelectionColor(Color newColor)
public void validate()
419
Programmieren in Java
5.6.6 Ereignisbehandlung unter Swing
420
Programmieren in Java
6. Utilities
6.1 Kollektionen (Collections)
Kollektionen (Collections) sind Datenstrukturen zur Aufnahme und Verarbeitung von
Datenmengen. Typische Collections sind Stacks, Queues, Priority Queues, Listen
oder Trees (Bäume).
In Java existieren seit der Version 1.0 die Collections Vector, Stack, Hashtable
und Bitset. Die Kritik an diesem Collection-Konzept führte zu einer Sammlung von
20 Klassen und Interfaces im Paket java.util des JDK 1.2. Im Wesentlichen sind
dies: Set, List und Map.
-
Eine List ist eine beliebig große Liste von Elementen beliebigen Typs, auf die wahlfrei und
sequentiell zugegriffen werden kann
Ein Set ist eine Menge von Elementen (ohne Duplikate), auf die mit typischen Mengenoperationen
zugegriffen werden kann.
Eine Map ist eine Abbildung von Elementen eines Typs auf Elemente eines anderen Typs (Menge
zusammengehöriger Paare).
Jede dieser Grundformen ist als Interface und den angegebenen Namen
implementiert. Zudem gibt es jeweils eine oder mehrere konkrete
Implementierungen. So gibt es bspw. für das Interface List die
Implementierungsvarianten LinkedList bzw. die abstrakte Implementierung
AbstractList.
6.1.1 Durchwandern von Datenstrukturen mit Iteratoren
Bei Datenstrukturen gibt es eine Möglichkeit, gespeicherte Daten unabhängig von
der Implementierung immer mit der gleichen Technik abzufragen. Bei
Datenstrukturen handelt es sich meistens um Daten in Listen, Bäumen oder
ähnlichem und oft wird nur die Frage nach der Zugehörigkeit eines Worts zum
Datenbestand gestellt (z.B. „Gehört das Wort dazu?“). Auch die Möglichkeit Daten in
irgendeiner Form aufzuzählen, ist eine häufig gestellte Aufgabe. Hierfür bieten sich
Iteratoren an. In Java umfaßt das Interface Enumeration die beiden Funktionen
hasMoreElements() und nextElement(), mit denen durch eine Datenstruktur
iteriert werden kann.
public interface Enumeration
{
public boolean hasMoreElements();
// Test, ob noch ein weiteres Element aufgezählt werden kann
public Object nextElement() throws NoSuchElementException;
/* setzt den internen Zeiger auf das nächste Element, d. h. liefert das
das nächste Element der Enumertion zurück. Diese Funktion kann eine
NoSuchException auslösen, wenn nextElement() aufgerufen wird, obwohl
hasMoreElements() unwahr ist
*/
}
421
Programmieren in Java
Die Aufzählung erfolgt meistens über
for (Enumeration e = ds.elements(); e.hasMoreElements(); )
System.out.println(e.nextElements());
Die Datenstruktur ds besitzt eine Methode elements(), die ein Enumeration-Objekt
zurückgibt, das die Aufzählung erlaubt.
6.1.2 Die Klasse Vector
class java.util.Vector extends AbstractList
implements List, Cloneable, Collection,
Serializable
Die Klasse Vector beschreibt ein Array mit variabler Länge. Objekte der Klasse
Vector sind Repräsentationen einer linearen Liste. Die Liste kann Elemente
beliebigen Typs enthalten, ihre Länge ist zur Laufzeit veränderbar (Array mit variabler
Länge). Vector erlaubt das Einfügen von Elementen an beliebiger Stelle, bietet
sequentiellen und wahlfreien Zugriff auf die Elemente. Das JDK realisiert Vector als
Array von Elementen des Typs Object. Der Zugriff auf Elemente erfolgt über
Indizes. Es wird dazu aber kein Operator [], sondern es werden Methoden benutzt,
die einen Index als Parameter annehmen.
Anlegen eines neuen Vektors (Konstruktor): public Vector()
public Vector(int initialCapacity, int capacityIncrement)
// Ein Vector vergrößert sich automatisch, falls mehr Elemente aufgenommen werden, als
// ursprünglich vorgesehen (Resizing). Dabei sollen initialCapacity und capacityIncrement
// passend gewählt werden.
Einfügen von Elementen: public void addElement(Object obj)
// Anhängen an des Ende der bisher vorliegenden Liste von Elementen
Eigenschaften: public boolean isEmpty()
// Prüfen, ob der Vektor leer ist
public int size()
// bestimmt die Anzahl der Elemente
public int capacity()
// bestimmt die interne Größe des Arrays. Sie kann mit ensureCapacity() geändert
// werden
Einfügen an beliebiger Stelle innerhalb der Liste:
public void insertElementAt(Object obj, int index) throws
ArrayIndexOutOfBoundsException
// fügt obj an die Position index in den "Vector" ein.
Zugriff auf Elemente: Für den sequentiellen Zugriff steht ein Iterator zur Verfügung.
Wahlfreier Zugriff erfolgt über:
public Object firstElement() throws NoSuchElementException;
public Object lastElement() throws NoSuchElementException;
public Object elementAt(int index) throws ArrayIndexOutOfBoundException;
firstElement() liefert das erste, lastElement() das letzte Element- Mit
elementAt() wird auf das Element an der Position index zugegriffen. Alle 3
Methoden verursachen eine Ausnahme, wenn das gewünschte Element nicht
vorhanden ist.
Arbeitsweise des internen Arrays. Der Vector vergrößert sich automatisch, falls mehr
Elemente aufgenommen werden. Die Operation heißt Resizing.
422
Programmieren in Java
Die Größe des Felds. Mit capacity() erhält man die interne Größe des Arrays. Sie kann mit
ensureCapacity() geändert werden. ensureCapacity(int minimumCapacity) bewirkt bei
einem Vector, daß er mindestens minCapacity Elemente aufnehmen soll.
Der Vektor verkleinert nicht die aktuelle Kapazität, falls sie schon höher als minCapacity ist. Zur
Veränderung dieser Größe, dient die Methode trimToSize(). Sie reduziert die Kapazität des
Vectors auf die Anzahl der Elemente, die gerade im Vector sind.
Die Anzahl der Elemente kann über die Methode size() erfragt werden. Sie kann über
setSize(int newSize) geändert werden. Ist die neue Größe kleiner als die alte, so werden die
Elemente am Ende des Vectors abgeschnitten. Ist newSize größer als die alte Größe, werden die
neu angelegten Elemente mit null initialisiert.
Bereitstellen des Interface Enumerartion. In der Klasse Vector liefert die Methode public
Enumeration elements() einen Enumerator (Iterator) für alle Elemente, die sich in Vector befinden.
Vector
<< Konstruktoren >>
public Vector()
// Ein Vector in der Anfangsgröße von 10 Elementen wird angelegt
public Vector(int startKapazitaet)
// Ein Vector enthält Platz für startKapazitaet Elemente
public Vector(int startKapazitaet, int kapazitaetsSchrittweite)
<< Methoden >>
public Object elementAt(int index)
// Das an der Stelle index befindliche Objekt wird zurückgegeben
public int size()
public Object firstElement()
public Object lastElement();
public void insertElementAt(Object obj, int index)
// fügt Object obj an index ein und verschiebt die anderen Elemente
public void setElementAt(Object obj, int index)
public copyInto(Object einArray[])
// kopiert die Elemente des Vektors in das Array einArray
// Falls das bereitgestellte Objektfeld nicht so groß ist wie der Vektor,
// dann tritt eine IndexOutOfBoundsException auf
public boolean contains(Object obj)
// sucht das Element, liefert true zurück wenn o im Vector vorkommt
public int indexOf(Object obj)
// sucht im Vector nach dem Objekt obj. Falls obj nicht in der Liste ist, wird
// -1 übergeben
public int lastIndexOf(Object obj)
public boolean removeElement(Object obj)
// entfernt obj aus der Liste. Konnte es entfernt werden, wird true
// zurückgeliefert
public void removeElementAt(int index)
// entfernt das Element an Stelle index
public void removeAllElements()
// löscht alle Elemente
public int capacity()
// gibt an, wieviel Elemente im Vektor Patz haben
// (, ohne daßautomatische Größenanpassung erfolgt)
public Object clone()
// Implementierung der clone()-methode von Object, d.h. eine Referenz
// des kopierten Feldes wird zurückgegeben. Die Kopie ist flach.
public String toString()
Abb.: Die Klasse Vector
423
Programmieren in Java
6.1.3 Die Klasse Stack
class java.util.Stack extends Vector
Ein Stack ist eine nach dem LIFO-Prinzip arbeitende Datenstruktur. Elemente
werden vorn (am vorderen Ende der Liste) eingefügt und von dort auch wieder
entnommen. In Java ist ein Stack eine Ableitung von Vector mit neuen
Zugriffsfunktionen für die Implementierung des typischen Verhaltens von einem
Stack.
Konstruktor: public Stack();
Hinzufügen neuer Elemente: public Object push(Object item);
Zugriff auf das oberste Element:
public Object pop();
// Zugriff und Entfernen des obersten Element
public Object peek()
// Zugriff auf das oberste Element
Suche im Stack: public int search(Object o)
// Suche nach beliebigem Element,
// Rueckgabewert: Distanz zwischen gefundenem und
//
obersten Stack-Element bzw. –1,
//
falls das Element nicht da ist.
Test:public boolean empty()
// bestimmt, ob der Stack leer ist
Vector
Stack
public Stack()
public Object push(Object obj)
public Object pop()
public Object peek()
public int search(Object obj)
public boolean empty()
Abb.: Die Klasse Stack
Anwendungen:
1. Umrechnen von Dezimalzahlen in andere Basisdarstellungen
Aufgabenstellung: Defaultmäßig werden Zahlen dezimal ausgegeben. Ein Stapel, der Ganzzahlen
aufnimmt, kann dazu verwendet werden, Zahlen bezogen auf eine andere Basis als 10 darzustellen.
Die Funktionsweise der Umrechnung von Dezimalzahlen in eine Basis eines anderen Zahlensystem
zeigen die folgenden Beispiele:
424
Programmieren in Java
2810 = 3 ⋅ 8 + 4 = 34 8
72 10 = 1 ⋅ 64 + 0 ⋅ 16 + 2 ⋅ 4 + 0 = 1020 4
5310 = 1 ⋅ 32 + 1 ⋅ 16 + 0 ⋅ 8 + 1 ⋅ 4 + 0 ⋅ 2 + 1 = 1101012
Mit einem Stapel läßt sich die Umrechnung folgendermaßen unterstützen:
6
1
leerer Stapel
n = 355310
7
7
4
4
4
1
1
1
n%8=1
n/8=444
n%8=4
n/8=55
n%8=7
n/8=6
n%8=6
n/6=0
n = 44410
n = 5510
n = 610
n = 010
Abb.: Umrechnung von 355310 in 67418 mit Hilfe eines Stapel
Algorithmus zur Lösung der Aufgabe:
1) Die am weitesten rechts stehende Ziffer von n ist n%b. Sie ist auf dem Stapel abzulegen.
2) Die restlichen Ziffern von n sind bestimmt durch n/b. Die Zahl n wird ersetzt durch n/b.
3) Wiederhole die Arbeitsschritte 1) und 2) bis keine signifikanten Ziffern mehr übrig bleiben.
4) Die Darstellung der Zahl in der neuen Basis ist aus dem Stapel abzulesen. Der Stapel ist zu diesem
Zweck zu entleeren.
Implementierung: Das folgende kleine Testprogramm304 realisiert den Algorithmus und benutzt dazu
eine Instanz von Stack.
import java.util.*;
public class PR61210
{
public static void main(String[] args)
{
int zahl = 3553;
// Dezimalzahl
int b
= 8;
// Basis
Stack s = new Stack();
// Stapel
do
{
s.push(new Integer(zahl % b));
zahl /= b;
} while (zahl != 0);
while (!s.empty())
{
System.out.print(s.pop());
}
System.out.println();
}
}
304
pr61210
425
Programmieren in Java
2. Einlesen einer Folge ganzer Zahlen, Ausgabe der Zahlen in umgekehrter
Reihenfolge305
import java.io.*;
import java.util.*;
public class Reverse
{
public static void main (String args[]) throws IOException
{
int a[]=new int[20];
//Stapel
Stack s = new Stack();
// Einlesen der ganzen Zahlen, Aufnahme der Zahlen in einen ganzzahligen
// Array, Aufnahme der im ganzzahligen Array gespeicherten Zahlen in
// einen Stapel
BufferedReader ein=new BufferedReader(new InputStreamReader(System.in));
for ( int i=0;i<a.length;i++)
{
a[i]=Integer.parseInt(ein.readLine());
s.push(new Integer (a[i]));
}
//Entleeren des Stapels mit Ausgabe auf die Java-Konsole
while ( !s.empty() )// 1 Punkt
{
System.out.print(" "+((Integer)s.pop()).intValue());
}
System.out.println( );
}
}
Ein Stack ist ein Vector. Die Vector-Klasse wird von der Klasse Stack erweitert. Das
ist sicherlich nicht immer besonders sinnvoll. Funktionen, die im Gegensatz zur
Leistungsfähigkeit eines Stapels stehen sind add(), addAll(), addElement(),
capacity(), clear(), clone(), contains(), copyInto(), elementAt(), ....
.
305
vgl. pr62100
426
Programmieren in Java
6.1.4 Die Klasse Bitset für Bitmengen
class java.util.BitSet implements Cloneable, Serializable
Die Klasse Bitset bietet komfortable Möglichkeiten zur bitweisen Manipulation von
Daten.
Bitset anlegen und füllen. Mit zwei Methoden lassen sich die Bits des Bitsets leicht
ändern: set(int bitNummer) und clear(int bitNummer).
Mengenorintierte Operationen. Das Bitset erlaubt mengenorientierte Operationen mit
einer weiteren Menge.
BitSet
public void and(BitSet bs)
public void or(BitSet bs)
public void xor(BitSet bs)
public void andNot(Bitset set)
// löscht alle Bits im Bitset, dessen Bit in set gesetzt sind
public void clear(int index)
// Löscht ein Bit. Ist der Index negativ, kommt es
// zur Auslösung von IndexOutOfBoundsException
public void set(int index)
// Setzt ein Bit. Ist der Index negativ, kommt es
// zur Auslösung von IndexOutOfBoundsException
public boolean get(int index)
// liefert den Wert des Felds am übergebenen Index,
// kann IndexOutOfBoundsException auslösen.
public int size()
public boolean equals(Object o)
// Vergleicht sich mit einem anderen Bitset-Objekt o.
Abb.: Die Klasse BitSet
Bsp.306: Anwendung der Klasse BitSet
Primzahlen
bei der Konstruktion einer Menge von
import java.util.*;
public class Primtest
{
final static int MAXPRIM = 30;
public static void main(String args[])
{
BitSet b = new BitSet();
for (int i = 2; i <= MAXPRIM; i++)
{
boolean ok = true;
for (int j = 2; j < i; j++)
if (b.get(j) && (i % j) == 0)
{ ok = false; break; }
if (ok) b.set(i);
}
for (int i = 1; i < MAXPRIM; i++)
{
if (b.get(i)) System.out.print(i + " ");
}
System.out.println();
306
pr61310
427
Programmieren in Java
}
}
6.1.5 Die Klasse Hashtable und assoziative Speicher
Eine Hashtabelle (Hashtable) ist ein assoziativer Speicher, der Schlüssel (keys) mit
Werten verknüpft. Die Datenstruktur ist mit einem Wörterbuch vergleichbar. Die
Hashtabelle arbeitet mit Schlüssel/Werte Paaren. Aus dem Schlüssel wird nach einer
Funktion – der sog. Hashfunktion – ein Hashcode berechnet. Dieser dient als Index
für ein internes Array. Dieses Array hat zu Anfang ein feste Grösse. Leider hat dieses
Technik einen entscheidenden Nachteil. Besitzen zwei Wörter denselben Hashcode,
dann kommt es zu einer Kollision. Auf ihn muß die Datenstruktur vorbereitet sein.
Hier gibt es verschiedene Lösungsansätze. Die unter Java implementierte Variante
benutzt eine verkettete Liste (separate Chaining). Falls eine Kollision auftritt, so wird
der Hashcode beibehalten und der Schlüssel bzw. Wert in einem Listenelement an
den vorhandenen Eintrag angehängt. Wenn allerdings irgendwann einmal eine Liste
durchsucht werden muß, dann wird die Datenstruktur langsam. Ein Maß für den
Füllgrad ist der Füllfaktor (Load Factor). Dieser liegt zwischen 0 und 100 %. 0
bedeutet: kein Listenelement wird verwendet. 100 % bedeutet: Es ist kein Platz mehr
im Array und es werden nur noch Listen für alle zukommenden Werte erweitert. Der
Füllfaktor sollte für effiziente Anwendungen nicht höher als 75% sein. Ist ein
Füllfaktor nicht explizit angegeben, dann wird die Hashtabelle „rehashed“, wenn mehr
als 75% aller Plätze besetzt sind.
class java.util.Hashtable extends Dictionary implements Map,
Cloneable, Serializable
Erzeugen von einem Objekt der Klasse Hashtable:
public Hashtable()
/* Die Hashtabelle enthält eine Kapazität von 11 Einträgen und einen Füllfaktor von 75 % */
public Hashtable(int initialCapacity)
/* erzeugt eine Hashtabelle mit einer vorgebenen Kapazität und dem Füllfaktor 0.75 */
public Hashtable(int initialCapacity, float loadFactor)
/* erzeugt eine Hashtabelle mit einer vorgebenen Kapazität und dem angegebenen Füllfaktor */
Daten einfügen: public Object put(Object key, Object value)
/* speichert den Schlüssel und den Wert in der Hashtabelle. Falls sich zu dem
Schlüssel schon ein Eintrag in der Hashtabelle befand, so wird dieser
zurückgegeben. Anderenfalls ist der Rückgabewert null. Die Methode ist
vorgegeben vom Interface Map. Es überschreibt die Methode von der Superklasse
Dictionary. */
Daten holen: public Object get(Object key)
Schlüssel entfernen. public Object remove(Object key)
Löschen der Werte. public void clear()
Test. public boolean containsKey(Object key)
// Test auf einen bestimmten Schlüssel
public boolean containsValue(Object value)
// Test auf einen bestimmten Wert
Aufzählen der Elemente. Mit keys() und elements() bietet die Hashtabelle zwei
Methoden an, die eine Aufzählung zurückgeben:
public Enumeration keys()
// liefert eine Aufzählung aller Schlüssel, überschreibt keys() in Dictionary.
428
Programmieren in Java
public Enumeration elements()
// liefert eine Aufzählung der Werte, überschreibt elements() in Dictionary
Wie üblich liefern beide Iteratoren ein Objekt, welches das Interface Enumeration
implementiert. Der Zugriff erfolgt daher mit Hilfe der Methoden hasMoreElements()
und nextElement().
Dictionary
{abstract}
public abstract Object put (Object key, Object value)
public abstract Object get(Object key)
public abstract Enumeration elements()
public abstract Enumeration keys()
public abstract int size()
public abstract boolean isEmpty()
public abstract Object remove(Object key)
Hashtable
Map
<< Konstruktor >>
public Hashtable(int initialKapazitaet)
public Hashtable(int initialKapazitaet, float Ladefaktor)
public Hashtable()
<< Methoden >>
public boolean contains(Object wert)
public boolean containsKey(Object key)
public void clear()
public Object clone()
protected void rehash()
public String toString()
Properties
<< Konstruktor >>
public Properties()
// legt einen leeren Container an
public Properties(Properties defaults)
// füllt eine Property-Liste mit den angegebenen Default-Werten
<< Methoden >>
public String getProperty(String key)
public String getProperty(String key, String defaultKey)
public void load(InputStream in) throws IOException
// Hier muß ein InputStream übergeben werden, der die daten der
// Property-Liste zur Verfügung stellt.
public void store(OutputStream out, String header)
public void list(PrintStream out)
public void list(PrintWriter out)
public Enumeration propertyNames()
// beschafft ein Enumerations-Objekt mit denen Eigenschaften
// der Property-Liste aufgezählt werden können
429
Programmieren in Java
Abb.: Die Klassen Hashtable und Properties
Die Klasse Hashtable ist eine Konkretisierung der abstrakten Klasse Dictionary.
Diese Klasse beschreibt einen assoziativen Speicher, der Schlüssel auf Werte
abbildet und über den Schlüsselbegriff einen effizienten Zugriff auf den Wert
ermöglicht. Einfügen und der Zugriff auf Schlüssel erfolgt nicht auf der Basis des
Operators „==“, sondern mit Hilfe der Methode „equals“. Schlüssel müssen daher
lediglich inhaltlich gleich sein, um als identisch angesehen zu werden.
Beispiele.
1. Hashtabelle zum Test der von Zufallszahlen der Methode Math.random()307.
import java.util.*;
class Zaehler
{
int i = 1;
public String toString()
{
return Integer.toString(i);
}
}
public class Statistik
{
public static void main(String args[])
{
Hashtable h = new Hashtable();
for (int i = 0; i < 10000; i++)
{
// Erzeuge eine Zahl zwischen 0 und 20
Integer r = new Integer((int)(Math.random() * 20));
if (h.containsKey(r))
((Zaehler) h.get(r)).i++;
else h.put(r,new Zaehler());
}
System.out.println(h);
}
}
2. Hashtabelle für direkten Zugriff auf Daten308
import java.io.*;
import java.util.*;
public class HashTabTest
{
public static void main(String [ ] args)
{
// Map map = new HashMap();
Hashtable h = new Hashtable();
String eingabeZeile
= null;
BufferedReader eingabe = null;
try {
eingabe = new BufferedReader(
new FileReader("eing.txt"));
}
catch (FileNotFoundException io)
307
308
pr61310
pr61310
430
Programmieren in Java
{
System.out.println("Fehler beim Einlesen!");
}
try {
while ( (eingabeZeile = eingabe.readLine() ) != null)
{
StringTokenizer str = new StringTokenizer(eingabeZeile);
if (eingabeZeile.equals("")) break;
String key
= str.nextToken();
String daten = str.nextToken();
System.out.println(key);
h.put(key,daten);
// map.put(key,daten);
}
}
catch (IOException ioe)
{
System.out.println("Eingefangen in main()");
}
try {
eingabe.close();
}
catch(IOException e)
{
System.out.println(e);
}
System.out.println("Uebersicht zur Hash-Tabelle");
// System.out.println(map);
System.out.println(h);
//h.printHashTabelle();
System.out.println("Abfragen bzw. Modifikationen");
// Wiederauffinden
String eingabeKey = null;
BufferedReader ein = new BufferedReader(
new InputStreamReader(System.in));
System.out.println("Wiederauffinden von Elementen");
while (true)
{
try {
System.out.print("Bitte Schluessel eingeben, ! bedeutet Ende: ");
eingabeKey = ein.readLine();
// System.out.println(eingabeKey);
if (eingabeKey.equals("!")) break;
String eintr = (String) h.get(eingabeKey);
// String eintr = (String) map.get(eingabeKey);
if (eintr == null)
System.out.println("Kein Eintrag!");
else
{
System.out.println(eintr);
System.out.println("Soll dieser Eintrag geloescht werden? ");
String antwort = ein.readLine();
// System.out.println(antwort);
if ((antwort.equals("j")) || (antwort.equals("J")))
{
// System.out.println("Eintrag wird entfernt!");
h.remove(eingabeKey);
// map.remove(eingabeKey);
}
}
}
catch(IOException ioe)
{
System.out.println(eingabeKey +
" konnte nicht korrekt eingelesen werden!");
}
431
Programmieren in Java
}
System.out.println(h);
// System.out.println(map);
// System.out.println("Sortierte Tabelle");
// Map sortedMap = new TreeMap(map);
// System.out.println(sortedMap);
}
}
Die Klasse Hashtable benutzt das Verfahren der Schlüsseltransformation (HashFunktion) zur Abbildung von Schlüsseln auf Indexpostionen eines Arrays. Die
Kapazität der Hash-Tabelle gibt die Anzahl der Elemente an, die insgesamt
untergebracht werden können. Der Ladefaktor zeigt an, bei welchem Füllungsgrad
die Hash-Tabelle vergrößert werden muß. Das Vergrößern erfolgt automatisch, falls
die Anzahl der Elemente innerhalb der Tabelle größer ist als das Produkt aus
Kapazität und Ladefaktor. Seit dem JDK 1.2 darf der Ladefaktor auch größer als 1
sein. In diesem Fall wird die Hash-Tabelle erst dann vergrößert, wenn der
Füllungsgrad größer als 100% ist und bereits ein Teil der Elemente in den
Überlaufbereichen untergebracht wurde.
Die Klasse Hashtable ist eine besondere Klasse für Wörterbücher. Ein Wörterbuch
ist eine Datenstruktur, die Elemente miteinander assoziiert. Das Wörterbuchproblem
ist das Problem, wie aus dem Schlüssel möglichst schnell der zugehörige Wert
konstruiert wird. Die Lösung des Problems ist: Der Schlüssel wird als Zahl kodiert
(Hashcode) und dient in einem Array als Index. An einem Index hängen dann noch
die Werte mit gleichem Hashcode als Liste an.
6.1.6 Die abstrakte Klasse Dictionary
Die Klasse Dictionary ist eine abstrakte Klasse, die Methoden anbietet, wie
Objekte (also Schlüssel und Wert) miteinander assoziiert werden:
public abstract Object put(Object key, Object value)
// fügt den Schlüssel key mit dem verbundenen Wert value in das Wörterbuch
// ein
public abstract Object get(Object key)
//
//
//
//
liefert das zu key gehörende Objekt zurück. Falls kein Wert mit dem
Schlüssel verbunden ist, so liefert get() eine null. Eine null als
Schlüssel oder Wert kann nicht eingesetz werden. In put() würde das zu
einer NullPointerException führen.
public abstract Object remove(Object key)
// entfernt ein Schlüssel/Wertepaar aus dem Wörterbuch. Zurückgegeben wird
// der assoziierte Wert.
public abstract boolean isEmpty()
// true, falls keine Werte im Wörterbuch
public int size()
gibt zurück, wie viele Elemente aktuell im Wörterbuch sind.
public abstract Enumeration keys()
// liefert eine Enumeration für alle Schlüssel
public abstract Enumeration elements()
// liefert eine Enumeration über alle Werte.
432
Programmieren in Java
6.1.7 Die Klasse Properties
Die Properties Klasse ist eine Erweiterung von Hashtable. Ein Properties Objekt
erweitert die Hashtable um die Möglichkeit, sich unter einem wohldefinierten Format
über einen Strom zu laden und zu speichern.
Erzeugen. public Properties()
// erzeugt ein leeres Propertes Objekt ohne Worte.
public Properties(Properties p)
// erzeugt ein leeres Properties Objekt mit Standard-werten aus den
// übergebenen Properties
Laden einer Properties-Liste. public void load(InputStream is)
Speichern einer Properties-Liste. public void store(OutputStream os,
String kennung). An den Kopf der Datei wird eine kennung geschrieben (die im
2. Argument angegeben ist). Die Kennung darf „null“ sein.
Enumeration. public void Enumeration propertyNames()
Zeichenketten suchen. Die Methode getProperty(String s).
public String getProperty(String s)
// sucht in den Properties nach der Zeichenkette
public String getProperty(String key, String default)
// sucht in den Properties nach der Zeichenkette key. Ist dieser nicht
// vorhanden, wird der String default zurückgegeben
Eigenschaften ausgeben. Die Methode list() wandert durch die Daten und gibt sie
auf einem PrintWriter aus:
public void list(PrintWriter pw)
// listet die Properties auf dem PrintWriter aus.
Bsp.: Systemeigenschaften der Java-Umgebung
import java.util.*;
import java.io.*;
public class SaveProp
{
public static void main(String args [])
{
try {
Properties p1 = System.getProperties();
FileOutputStream propAusFile = new
FileOutputStream("properties.txt");
p1.store(propAusFile,null);
propAusFile.close();
Properties p2 = new Properties();
FileInputStream eing = new FileInputStream("properties.txt");
p2.load(eing);
p2.list(System.out);
}
catch(IOException e) { System.err.println(e);}
}
}
433
Programmieren in Java
6.2 Collection API
Die Java 2 Plattform hat Java erweitert um das Collection API. Anstatt Collection
kann man auch Container (Behälter) sagen. Ein Container ist ein Objekt, das
wiederum Objekte aufnimmt und die Verantwortung für die Elemente übernimmt. Im
„util“-Paket befinden sich sechs Schnittstellen, die grundlegende Eigenschaften
der Containerklassen definieren.
Das in Java 1.2 enthaltene Collections Framework beinhaltet im Wesentlichen drei
Grundformen: Set, List und Map. Jede dieser Grudformen ist als Interface
implementiert. Die Interfaces List und Set sind direkt aus Collection abgeleitet. Es
gibt auch noch eine abstrakte Implementierung des Interface, mit dessen Hilfe das
Erstellen eigener Collections erleichtert wird. Bei allen Collections, die das Interface
Collection implementieren, kann ein Iterator zum Durchlaufen der Elemente mit der
Methode „iterator()“ beschafft werden.
Zusätzlich fordert die JDK 1.2-Spezifikation für jede Collection-Klasse zwei
Konstruktoren:
- Einen parameterlosen Konstruktor zum Anlegen einer neuen Collection.
- Ein mit einem einzigen Collection-Argument ausgestatteter Konstruktor, der eine neue Collection anlegt und
mit den Elementen der als Argument übergebenen Collection auffüllt.
6.2.1 Die Schnittstellen Collection, Iterator, Comparator
Das Interface Collection bildet die Basis der Collection-Klasse und –Interfaces des
JDK 1.2. Alle Behälterklassen implementieren das Collection Interface und geben
den Klassen damit einen äußeren Rahmen.
434
Programmieren in Java
Das Interface Collection
<< interface >>
Collection
public void clear();
// Optional: Löscht alle Elemente in dem Container. Eird dies vom Container nicht unterstützt,
// kommt es zur UnsupportedOperationException
public boolean add(Object o);
// Optional: Fügt ein Objekt dem Container hinzu und gibt true zurück, falls sich das Element
// einfügen läßt. Gibt false zurück, falls schon ein Objektwert vorhanden ist und doppelte Werte
// nicht erlaubt sind.
public boolean addAll(Collection c);
// fügt alle Elemente der Collection c dem Container hinzu
public boolean remove(Object o);
// Entfernen einer einzelnen Instanz. Rückgabewert ist true, wenn das Element gefunden und
// entfernt werden konnte
public boolean removeAll(Collection c);
// Oprtional: Entfernt alle Objekte der Collection c aus dem Container
public boolean contains(Object o);
// liefert true, falls der Container das Element enthält
// Rückgabewert ist true, falls das vorgegebene Element gefunden werden konnte
public boolean containsAll(Collection c);
// liefert true, falls der Container alle Elemente der Collection c enthält.
public boolean equals(Object o);
// vergleicht das angegebene Objekt mit dem Container, ob die gleichen Elemente vorkommen.
public boolean isEmpty();
// liefert true, falls der Container keine Elemente enthält
public int size();
// gibt die Größe des Containers zurück
public boolean retainAll(Collection c);
public Iterator iterator();
public Object[] toArray();
// gibt ein Array mit Elementen des Containers zurück
public Object[] toArray(Object[] a);
public int hashCode();
// liefert den Hashwert des Containers
public String toString()
// Rückgabewert ist die Zeichenketten-Repräsentation der Kollektion.
Abb.: Das Interface Collection
Die abstrakte Basisklasse AbstractCollection implementiert die Methoden des
Interface Collection (ohne iterator() und size()). AbstractCollection
ist die Basisklasse von AbstractList und AbstractSet.
435
Programmieren in Java
Das Interface Iterator
<< interface >>
Iterator
public boolean hasNext();
// gibt true zurück, wenn der Iterator mindestens ein weiteres Element enthält.
public Object next();
// liefert das nächste Element bzw. löst eine Ausnahme des Typs NoSuchElementException
// aus, wenn es keine weiteren Elemente gibt
public void remove();
// entfernt das Element, das der Iterator bei next() geliefert hat.
Abb.: Das Interface Iterator
Bei allen Collections, die das Interface Collection implementieren, kann ein Iterator
zum Durchlaufen der Elemente mit der Methode „iterator()“ beschafft werden.
Das Interface Comparator
Vergleiche zwischen Objekten werden mit speziellen Objekten vorgenommen, den
Comparatoren. Ein konkreter Comparator implementiert die folgende Schnittstelle.
<< interface >>
java.util.Comparator
public int compare(Object o1, Object o2)
// vergleicht 2 Argumente auf ihre Ordnung
public boolean equals(Object arg)
// testet, ob zwei Objekte bzgl. des Comparator-Objekts gleich sind
Abb. Das Interface Comparator
6.2.2 Die Behälterklassen und Schnittstellen des Typs List
Behälterklassen des Typs List fassen eine Menge von Elementen zusammen, auf die
sequentiell oder über Index (-positionen) zugegriffen werden kann. Wie Vektoren der
Klasse Vector309 hat das erste Element den Index 0 und das letzte den Index
„size() – 1“. Es ist möglich an einer beliebigen Stelle ein Element einzufügen
oder zu löschen. Die weiter hinten stehenden Elemente werden dann entsprechend
weiter nach rechts bzw. nach links verschoben.
309
seit Java 1.2 implementiert die Klasse Vector die Schnittstelle List
436
Programmieren in Java
Das Interface List310
<< interface >>
List
public void add(int index, Object element);
// Einfügen eines Elements an der durch Index spezifizierten Position
public boolean add(Object o);
// Anhängen eines Elements ans Ende der Liste
// Rückgabewert ist true, falls die Liste durch den Aufruf von add verändert wurde. Er ist false,
// wenn die Liste nicht verändert wurde. Das kann bspw. der Fall sein, wenn die Liste keine
// Duplikate erlaubt und ein bereits vorhandenes Element noch einmal eingefügt werden soll.
// Konnte das Element aus einem anderen Grund nicht eingefügt werden, wird eine Ausnahme
// des Typs UnsupportedOperationException, ClassCastException oder IllegalArgumentException
// ausgelöst
public boolean addAll(Collection c);
// Einfügen einer vollständigen Collection in die Liste. Der Rückgabewert ist true, falls die Liste
// durch den Ausfruf von add veränder wurde
public boolean addAll(int index, Collection c)
public void clear();
public boolean equals(Object object);
public boolean contains(Object element);
public boolean containsAll(Collection collection);
public Object remove(int index)
public boolean remove(Object element);
public boolean removeAll(Collection c);
// Alle Elemente werden gelöscht, die auch in der als Argument angebenen
// Collection enthalten sind.
public boolean retainAll(Collection c);
// löscht alle Elemente außer den in der Argument-Collection enthaltenen
public Object get(int index);
public int hashCode();
public Iterator iterator();
public ListIterator listIterator():
public ListIterator listIterator(int startIndex);
public Object set(int index, Object element);
public List subList(int fromIndex, int toIndex);
public Object[] toArray();
public Object[] toArray(Object[] a);
Abb.: Das Interface List
Auf die Elemente einer Liste läßt sich mit einem Index zugreifen und nach Elementen
läßt sich mit linearem Aufwand suchen. Doppelte Elemente sind erlaubt. Die
Schnittstelle List, die in ArrayList und LinkedList eine Implementierung findet, erlaubt
sequentiellen Zugriff auf die gespeicherten gespeicherten Elemente. Das Interface
List wird im JDK von verschiedenen Klassen implementiert:
AbstractList
ist eine abstrakte Basisklasse (für eigene List-Implementierungen), bei der alle
Methoden die Ausnahme UnsupportedOperationException auslösen und diverse
Methoden
abstract
deklariert
sind.
Die
direkten
Subklassen
sind
310 Da ebenfalls das AWT-Paket eine Klasse mit gleichen Namen verwendet, muß der voll qualifizierte Name in
Anwendungen benutzt werden.
437
Programmieren in Java
AbstractSequentialList,
ArrayList
und
Vector.java.util.
AbstractList implementiert bereits viele Methoden für die beiden ListenKlassen311:
abstract class AbstractList extends AbstractCollection implements List
<< Methoden >>
public void add(int index, Object element)
// Optional: Fügt ein Objekt an der spezifizierten stelle ein
public boolean add(Object o)
// Optional: Fügt das Element am Ende an
public boolean addAll(int index, Collection c)
// Optional: Fügt alle Elemente der Collection ein
public void clear()
// Optional: Löscht alle Elemente
public boolean equals(Object o)
// vergleicht die Liste mit dem Objekt
public abstract Object get(int index)
// liefert das Element an dieser Stelle
int hashCode()
// liefert HashCode der Liste
int indexOf(Object o)
// liefert Position des ersten Vorkommens für o oder –1,
// wenn das Element nicht existiert.
Iterator iterator()
// liefert den Iterator. Überschreibt die Methode AbstractCollection,
// obwohl es auch listIterator() für die spezielle Liste gibt. Die Methode
// ruft aber listIterator() auf und gibt ein ListIterator-Objekt zurück
Object remove(int index)
// löscht ein Element an Position index.
protected void removeRange(int fromIndex, int toIndex)
// löscht Teil der Liste von fromIndex bis toIndex. fromIndex wird mitgelöscht,
// toIndex nicht.
public Object set(int index, Object element)
// Optional. Ersetzt das Element an der Stelle index mit element.
public List subList(int fromIndex, int toIndex)
// liefert Teil einer Liste fromIndex (einschließlich) bis toIndex (nicht mehr dabei)
Abb.: Die abstrakte Klasse AbstractList
AbstractSequentialList
bereitet die Klasse LinkedList darauf vor, die Elemente in einer Liste zu
verwalten und nicht wie ArrayList in einem internen Array.
LinkedList
realisiert die doppelt verkettete, lineare Liste und implementiert List.
ArrayList
implementiert die Liste als Feld von Elementen und implementiert List. Da
ArrayList ein Feld ist, ist der Zugriff auf ein spezielles Element sehr schnell. Eine
LinkedList muß aufwendiger durchsucht werden. Die verkettete Liste ist aber
deutlich im Vorteil, wenn Elemente gelöscht oder eingefügt werden.
311 Der Aufruf einer optionalen Methode, die von der Subklasse nicht implementiert wird, führt zur
UnsupportedOperationException.
438
Programmieren in Java
<< interface >>
Collection
<< interface >>
List
LinkedList
ArrayList
Vector
<< Konstruktor >>
<< Konstruktor >>
public LinkedList();
public ArrayList();
public LinkedList(Collection collection); public ArrayList(Collection collection);
public ArrayList(int anfangsKapazitaet);
<< Methoden >>
public void addFirst(Object object);
public void addLast(Object object);
public Object getFirst();
public Object getLast();
public Object removeFirst();
public Object removeLast();
<< Methoden >>
protected void removeRange
(int fromIndex, int toIndex)
// löscht Teil der Liste von
// fromIndex bis toIndex. fromIndex wird
// mitgelöscht, toIndex nicht.
Abb. LinkedList, ArrayList, Vector
Das Interface ListIterator
<< interface >>
ListIterator
public boolean hasPrevious();
// bestimmt, ob es vor der aktuellen Position ein weiteres Element gibt, der Zugriff ist mit
// previous möglich
public boolean hasNext();
public Object next();
public Object previous();
public int nextIndex();
public int previousIndex();
public void add(Object o);
// Einfügen eines neuen Elements an der Stelle der Liste, die unmittelbar vor dem nächsten
// Element des Iterators liegt
public void set(Object o);
// erlaubt, das durch den letzten Aufruf von next() bzw. previous() beschaffene Element zu
// ersetzen
public void remove();
Abb.: Das Interface ListIterator
ListIterator ist eine Erweiterung von Iterator. Die Schnittstelle fügt noch
Methoden hinzu, damit an aktueller Stelle auch Elemente eingefügt werden können.
Mit einem ListIterator läßt sich rückwärts laufen und auf das vorgehende
Element zugreifen.
439
Programmieren in Java
6.2.3 Behälterklassen des Typs Set
Ein Set ist eine Menge, in der keine doppelten Einträge vorkommen können. Set hat
die gleichen Methoden wie Collection. Standard-Implementierung für Set sind das
unsortierte HashSet (Array mit veränderlicher Größe) und das sortierte TreeSet
(Binärbaum).
Bsp.
import java.util.*;
public class SetBeispiel
{
public static void main(String args [])
{
Set set = new HashSet();
set.add("Gerhard");
set.add("Thomas");
set.add("Michael");
set.add("Peter");
set.add("Christian");
set.add("Valentina");
System.out.println(set);
Set sortedSet = new TreeSet(set);
System.out.println(sortedSet);
}
}
440
Programmieren in Java
<< interface >>
Collection
<< interface >>
Set
public boolean add(Object element);
public boolean addAll(Collection collection);
public void clear();
public boolean equals(Object object);
public boolean contains(Object element);
public boolean containsAll(Collection collection);
public int hashCode();
public Iterator iterator();
public boolean remove(Object element);
public boolean removeAll(Collection collection);
public boolean retainAll(Collection collection);
public int size();
public Object[] toArray();
public Object[] toArray(Object[] a);
HashSet
public HashSet();
public HashSet(Collection collection);
public HashSet(int anfangskapazitaet);
public HashSet(int anfangskapazitaet,
float ladefaktor);
<< interface >>
SortedSet
public Object first();
public Object last();
public SortedSet headSet(Object toElement);
public SortedSet subSet(Object fromElement,
Object toElement);
public SortedSet tailSet(Object fromElement);
public Comparator comparator();
TreeSet312
public TreeSet()
public TreeSet(Collection collection);
public TreeSet(Comparator vergleich);
public TreeSet(SortedSet collection);
Abb.:
312
implementiert die sortierte Menge mit Hilfe der Klasse TreeMap, verwendet einen Red-Black-Tree als
Datenstruktur
441
Programmieren in Java
Die Schnittstelle Set
ist eine im mathematischen Sinne definierte Menge von Objekten. Die Reihenfolge
wird durch das Einfügen festgelegt. Wie von mathematischen Mengen bekannt, darf
ein Set keine doppelten Elemente enthalten. Besondere Beachtung muß Objekten
geschenkt werden, die ihren Wert nachträglich ändern. Die kann ein Set nicht
kontrollieren. Eine Menge kann sich nicht selbst als Element enthalten.
Zwei Klassen ergeben sich aus Set: die abstrakte Klasse AbstractSet und die
konkrete Klasse HashSet.
Die Schnittstelle SortedSet
erweitert Set so, daß Elemente sortiert ausgelesen werden können. Das
Sortierkriterium wird durch die Hilfsklasse Comparator gesetzt.
442
Programmieren in Java
6.2.4 Behälterklassen des Typs Map
Ein Map ist eine Menge von Elementen, auf die über Schlüssel zugegriffen wird.
Jedem Schlüssel (key) ist genau ein Wert (value) zugeordnet. StandardImplementierungem sind HashMap, HashTable und TreeMap.
Interface Map, SortedMap und implemetierende Klassen
<< interface >>
Collection
<< interface >>
Map
public void clear();
public boolean containsKey(Object key);
public boolean containsValue(Object value);
public Set entrySet();
public Object get(Object key);
public boolean isEmpty();
public Set keySet();
public Object remove(Object key);
public int size();
public Collection values();
HashMap
public HashMap();
public HashMap(Map collection);
public HashMap(int anfangskapazitaet);
public HashMap(int anfangskapazitaet,
int ladefaktor);
<< interface >>
SortedMap
public Comparator comparator();
public Object firstKey();
public Object lastKey();
public SortedMap headMap(Object toKey);
public SortedMap subMap(Object fromKey,
Object toKey);
public SortedMap tailMap(Object fromKey);
Hashtable
TreeMap
public TreeMap();
public TreeMap(Map collection);
public TreeMap(Comparator vergleich);
public TreeMap(SortedMap collection);
Abb.:
Die Schnittstelle Map
443
Programmieren in Java
Eine Klasse, die Map implementiert, behandelt einen assoziativen Speicher. Dieser
verbindet einen Schlüssel mit einem Wert. Die Klasse Hashtable erbt von Map.
Map ist für die implementierenden Klassen AbstractMap, HashMap, Hashtable,
RenderingHints, WeakHashMap und Attributes das, was die abstrakte Klasse
Dictionary für die Klasse Hashtable ist.
Die Schnittstelle SortedMap
Eine Map kann mit Hilfe eines Kriteriums sortiert werden und nennt sich dann
SortedMap. SortedMap erweitert direkt Map. Das Sortierkriterium wird mit einem
speziellen Objekt, das sich Comparator nennt, gesetzt. Damit besitzt auch der
assoziative Speicher über einen Iterator eine Reihenfolge. Nur die konkrete Klasse
TreeMap implementiert bisher eine SortedMap.
Die abstrakte Klasse AbstractMap
implementiert die Schnittstelle Map.
Die konkrete Klasse HashMap
implementiert einen assoziativen Speicher, erweitert die Klasse AbstractMap und
implementiert die Schnittstelle Map.
Die konkrete Klasse TreeMap
Erweitert AbstractMap und implementiert SortedMap. Ein Objekt von TreeMap hält
Elemente in einem Baum sortiert.
444
Programmieren in Java
6.2.5 Algorithmen
Die Wahl einer geeigneten Datenstruktur ist der erste Schritt. Im zweiten Schritt
müssen die Algorithmen implementiert werden. Die Java Bibliothek hilft mit einigen
Standardalgorithmen weiter. Dazu zählen Funktionen zum Sortieren und Suchen in
Containern und das Füllen von Containern. Zum flexiblen Einsatz dieser Funktionen
haben die Java-Entwickler die Klasse Collections bereitgestellt. Collections bietet
Algorithmen statischer Funktionen an, die als Parameter ein Collection Objekt
erwarten. Leider sind viele Algorithmen nur auf List Objekte definiert, z.B.
public static void shuffle(List list)
// würfelt die Werte einer Liste durcheinander
Bsp.
import java.util.*;
public class VectorShuffle
{
public static void main(String args[])
{
Vector v = new Vector();
for (int i = 0; i < 10; i++)
v.add(new Integer(i));
Collections.shuffle(v);
System.out.println(v);
}
}
public static void shuffle(List list, Random rnd)
// würfelt die werte der Liste durcheinander und benutzt dabei den Random Generator rnd.
Nur die Methoden min() und max() arbeiten auf allgemeinen Collection-Objekten.
6.2.5.1 Datenmanipulation
Daten umdrehen. Die Methode reverse() dreht die Werte einer Liste um. Die
Laufzeit ist linear zu der Anzahl der Elemente.
public static void reverse(List l)
// dreht die Elemente in der Liste um
Listen füllen. Mit der fill()-Methode läßt sich eine Liste in linearer Zeit belegen.
Nützlich ist dies, wenn eine Liste mit Werten initialisiert werden muß.
public static void fill (List l, Object o)
// füllt eine Liste mit dem Element o
Daten zwischen Listen kopieren. Die Methode copy(List quelle, List ziel)
kopiert alle Elemente von quelle in die Liste ziel und überschreibt dabei
Elemente, die evtl. an dieser Stelle liegen.
public static void copy(List quelle, List ziel)
// kopiert Elemente von quelle nach ziel. Ist ziel zu klein, gibt es eine IndexOutOfBoundsException
445
Programmieren in Java
6.2.5.2 Größter und kleinster Wert einer Collection
Die Methoden min() und max() suchen das größte und kleinste Element einer
Collection. Die Laufzeit ist linear zur Größe der Collection. Die Methoden machen
keinen Unterschied, ob die Liste schon sortiert ist oder nicht.
public static Object min(Collection c)
//
public static Object max(Collection c)
/* Falls min() bzw. max() auf ein Collection-Objekt angewendet wird, erfolgt die Bestimmung des
Minimums bzw. Maximums nach der Methode compareTo der Comparable Schnittstelle. Byte,
Character, Double, File, Float, Long, Short, String, Integer, BigInteger, ObjectStreamField, Date und
Calendar haben diese Schnittstelle implementiert. Lassen sich die Daten nicht vergleichen, dann gibt
es eine ClassCastException
*/
public static Object min(Collection c, Comparator vergl)
//
public static Object max(Collection c, Comparator vergl)
6.2.5.3 Sortieren
Die Collection Klasse bietet zwei sort() Methoden an, die die Elemente einer Liste
stabil313 sortieren. Die Methode sort() sortiert die Elemente in ihrer natürlichen
Ordnung, z.B.:
- Zahlen nach der Größe (13 < 40)
- Zeichenketten alphanumerisch (Juergen < Robert < Ulli)
Eine zweite überladene Form von sort() arbeitet mit einem speziellen Comparator
Objekt, das zwei Objekte mit der Methode compare() vergleicht.
public static void sort(List liste)
// sortiert die Liste
public static void sort(List liste, Comparator c)
// sortiert die Liste mit dem Comparator c
Die Sortierfunktion arbeitet nur mit List-Objekten. „sort()“ gibt es aber auch in der
Klasse Arrays.
Bsp.: Das folgende Programm sortiert eine Reihe von Zeichenketten in aufsteigender
Folge. Es nutzt die Methode Arrays.asList() zur Konstruktion einer Liste aus
einem Array314.
import java.util.*;
public class CollectionsSortDemo
{
public static void main(String args[])
{
String feld[] =
{ "Regina","Angela","Michaela","Maria","Josepha",
"Amalia","Vera","Valentina","Daniela","Saida",
313
Stabile Sortieralgorithmen beachten die Reihenfolge von gleichen Elementen, z.B. beim Sortieren von
Nachrichten in einem Email-Programm, zuerst nach dem Datum und anschließend nach dem Sender, soll die
Liste innerhalb des Datum sortiert bleiben.
314 Leider gibt es keinen Konstruktor für ArrayList, der einen Array mit Zeichenketten zuläßt.
446
Programmieren in Java
"Linda","Elisa"
};
List l = Arrays.asList(feld);
Collections.sort(l);
System.out.println(l);
}
}
Die Java Bibliothek bietet nicht viel zur Umwandlung von Feldern („Array“) in
dynamische Datenstrukturen. Eine Ausnahme bildet die Hilfsklasse Arrays, die die
Methode asList() anbietet. Die Behälterklassen ArrayList und LinkedList werden
über asList() nicht unterstützt, d.h. Über asList() wird zwar eine interne Klasse
ArrayList benutzt, die eine Erweiterung von AbstractList ist, aber nur das
notwendigste implementiert.
Sortieralgorithmus. Es handelt sich um einen optimierten „Merge-Sort“. Seine
Laufzeit beträgt N ⋅ log( N ) .
Die sort() Methode arbeitet mit der toArray() Funktion der Klasse List. Damit
werden die Elemente der Liste in einem Feld (Array) abgelegt. Schließlich wird die
sort() Methode der Klasse Arrays genutzt und mit einem ListIterator wieder
in die Liste eingefügt.
Daten in umgekehrter Reihenfolge sortieren. Das wird über ein spezielles
Comparator-Objekt geregelt, das von Collections über die Methode
reverseOrder() angefordert werden kann.
Bsp.:
import java.util.*;
public class CollectionsReverseSortDemo
{
public static void main(String args[])
{
Vector v = new Vector();
for (int i = 0; i < 10; i++)
{
v.add(new Double(Math.random()));
}
Comparator comparator = Collections.reverseOrder();
Collections.sort(v,comparator);
System.out.println(v);
}
}
Eine andere Möglichkeit für umgekehrt sortierte Listen besteht darin, erst die Liste
mit sort() zu sortieren und anschließend mit reverse() umzudrehen.
447
Programmieren in Java
6.2.5.4 Suchen von Elementen
Die Behälterklassen enthalten die Methode contains(), mit der sich Elemente
suchen lassen. Für sortierte Listen gibt es eine wesentlich schnellere Suchmethode:
binarySearch():
public static int binarySearch(List liste, Object key)
// sucht ein Element in der Liste. Gibt die Position zurück oder ein Wert kleiner 0,
// falls key nicht in der Liste ist.
public static int binarySearch(List liste, Object key, Comparator c)
// Sucht ein Element mit Hilfe des Comparator Objekts in der Liste. Gibt die Position zurück oder
// einen Wert kleiner als 0, falls der key nicht in der Liste ist.
Bsp.: Das folgende Programm sortiert (zufällig ermittelte) Daten und bestimmt Daten
in Listen mit Hilfe der binären Suche.
import java.util.*;
public class ListSort
{
public static void main(String [] args)
{
final int GR = 20;
// Verwenden einer natuerliche Ordnung
List a = new ArrayList();
for (int i = 0; i < GR; i++)
a.add(new VglClass((int)(Math.random() * 100)));
Collections.sort(a);
Object finde = a.get(GR / 2);
int ort = Collections.binarySearch(a,finde);
System.out.println("Ort von " + finde + " = " + ort);
// Verwenden eines Comparator
List b = new ArrayList();
// Bestimmt zufaellig Zeichenketten der Laenge 4
for (int i = 0; i < GR; i++)
b.add(Felder.randString(4));
// Instanz fuer den Comparator
AlphaVgl av = new AlphaVgl();
// Sortieren
Collections.sort(b,av);
// Binaere Suche
finde = b.get(GR / 2);
ort = Collections.binarySearch(b,finde,av);
System.out.println(b);
System.out.println("Ort von " + finde + " = " + ort);
}
}
448
Programmieren in Java
6.2.6 Generics
6.2.6.1 Sammlungsklassen
Mit Java 1.5 wurde durch Einführung der sog. Generics das Feld der Templatebasierten Metaprogrammierung eröffnet. Das sinnvollste Einsatzfeld dieses
Mechanismus bildet die Anwendung auf die vorhandenen Klassen der Collection API
zur Realisierung typsicherer Objektsammlungen.
Allgemeine Syntax: Nachstellung des durch spitze Winkellammern eingeschlossenen
Typnamens nach dem Namen der so typisierten Sammlung, z.B.
Definition einer Liste, die ausschließlich Objekte des Typs String enthält: List<String>
Bsp.315:
import java.util.*;
import java.util.List;
class ListTest
{
public static void main(String [] args)
{
List<String> xs = new ArrayList<String>();
xs.add("Shakespeare");
xs.add("Schiller");
xs.add("Goethe");
xs.add("Brecht");
xs.add("Thomas Mann");
String x = xs.get(3);
System.out.println(xs);
}
}
Test:
Aus Kompatiblitätsgründen mit bestehendem Code können generische Klassen auch
weiter hin ohne konkrete Angabe des Typparameters benutzt werden. Während der
Übersetzung wird in diesen Fällen eine Warnung ausgegeben.
import java.util.*;
import java.util.List;
class WarnTest
{
public static void main(String [] args)
{
List xs = new ArrayList<String>();
xs.add("Shakespeare");
xs.add("Schiller");
xs.add("Goethe");
315
pr66100
449
Programmieren in Java
xs.add("Brecht");
xs.add("Thomas Mann");
System.out.println(xs);
}
}
Konventionsgemäß erlaubt Java ausschließlich die Festlegung von Klassen als
Inhaltstypen von Objektsammlungen. Primitivtypen sind von der Verwendung
ausgeschlossen.
6.2.6.2 Generische Methoden
Generische Typen sind nicht an einen objektorientierten Kontext gebunden. In Java
verlässt man den objektorientierten Kontext in statischen Methoden. Statische
Methoden sind nicht an ein Objekt gebunden und lassen sich generisch in Java
definieren. Hierzu ist vor der Methodensignatur in spitzen Klammern eine Liste der
für die statischen Methoden benutzten Typvariablen anzugeben.
Bsp.: Generische Methoden der Klasse TestGenericBinaerBaumKnoten
Die folgende Darstellung zeigt die Definition einer generischen Klasse für Knoten in einem binären
Suchbaums mit einer Typvariablen, die für alle Subtypen der Schnittstelle Comparable steht.
// Elementarer Knoten eines binaeren Baums, der nicht ausgeglichen ist
class BinaerBaumknoten<T extends Comparable>
{
// Instanzvariable
protected BinaerBaumknoten<T> links;
protected BinaerBaumknoten<T> rechts;
public
T daten;
// linker Teilbaum
// rechter Teilbaum
// Dateninhalt der Knoten
// Konstruktor
public BinaerBaumknoten(T datenElement)
{
this(datenElement, null, null );
}
public BinaerBaumknoten(T datenElement,
BinaerBaumknoten<T> l,
BinaerBaumknoten<T> r)
{
daten
= datenElement;
links
= l;
rechts
= r;
}
public void insert (T x)
{
if (x.compareTo(daten) > 0)
// dann rechts
{
if (rechts == null) rechts = new BinaerBaumknoten<T>(x);
else rechts.insert(x);
}
else // sonst links
{
if (links == null) links = new BinaerBaumknoten<T> (x);
else links.insert(x);
}
}
public BinaerBaumknoten<T> getLinks()
{
return links;
450
Programmieren in Java
}
public BinaerBaumknoten<T> getRechts()
{
return rechts;
}
}
Die Klasse TestGenericBinaerBaumKnoten erzeugt einen binären Baum mit Hilfe der
vorliegenden Klasse BinaerBaumknoten. Die Knoten des binären Baums werden über zahlreiche
generische Methoden durchlaufen.
public class TestGenericBinaerBaumKnoten
{
public static void main (String args[])
{
BinaerBaumknoten<Integer> baum = null;
/*
for (int i = 0; i < 20; i++) // 20 Zusfallsstrings speichern
{
String s = "Zufallszahl " + (int)(Math.random() * 100);
if (baum == null) baum = new BinaerBaumknoten(s);
}
print(baum); // Sortiert wieder ausdrucken
*/
for (int i = 0; i < 10; i++)
{
// Erzeuge eine Zahl zwischen 0 und 100
Integer r = new Integer((int)(Math.random()*100));
if (baum == null) baum = new BinaerBaumknoten<Integer>(r);
else baum.insert(r);
}
System.out.println("Inorder-Durchlauf");
print(baum);
System.out.println();
System.out.println("Baumdarstellung um 90 Grad versetzt");
ausgBinaerBaum(baum,0);
System.out.print("Kleinster Wert: ");
System.out.print(((Integer)(findeMin(baum))).intValue());
System.out.println();
System.out.print("Groesster Wert: ");
System.out.print(((Integer)(findeMax(baum))).intValue());
System.out.println();
}
// Generische Methoden
public static <T extends Comparable>
void print (BinaerBaumknoten<? extends T> baum)
// Rekursive Druckfunktion
{
if (baum == null) return;
print(baum.getLinks());
System.out.print(baum.daten + " ");
print(baum.getRechts());
}
public static <T extends Comparable>
void ausgBinaerBaum(BinaerBaumknoten<T> b, int stufe)
{
if (b != null)
{
ausgBinaerBaum(b.getRechts(), stufe + 1);
for (int i = 0; i < stufe; i++)
{
System.out.print("
");
}
System.out.println(b.daten);
ausgBinaerBaum(b.getLinks(), stufe + 1);
451
Programmieren in Java
}
}
public static <T extends Comparable> T findeMin(BinaerBaumknoten<T> b)
{
return datenZugriff( findMin(b) );
}
public static <T extends Comparable> T findeMax(BinaerBaumknoten<T> b)
{
return datenZugriff( findMax(b) );
}
public static <T extends Comparable> T datenZugriff(BinaerBaumknoten<T> b)
{
return b == null ? null : b.daten;
}
public static <T extends Comparable>
BinaerBaumknoten<T> findMin(BinaerBaumknoten<T> b)
{
if (b == null) return null;
else if (b.getLinks() == null) return b;
return findMin(b.getLinks());
}
public static <T extends Comparable>
BinaerBaumknoten<T> findMax(BinaerBaumknoten<T> b)
{
if (b != null)
while (b.getRechts() != null)
b = b.getRechts();
return b;
}
}
6.2.6.3 Iteration
In typischer Weise wird in einem Programm über die Elemente eines Sammlungstyps
iteriert oder über alle Elemente eines Array (Reihung). Java kennt dafür
verschiedenene Schleifenkonstrukte. Leider kannte Java bis zur Version 1.4 kein
eigenes Schleifenkonstrukt, das bequem eine Iteration über die Elemente einer
Sammlung (Collection) ausdrücken konnte.
Bsp.316:
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
class AlteIteration
{
public static void main(String [] args)
{
String [] ar =
{ "Brecht", "Schiller", "Goethe", "Shakespeare", "Thomas Mann" };
List xs = new ArrayList();
for (int i = 0;i < ar.length; i++)
{
final String s = ar[i];
xs.add(s);
}
for (Iterator it = xs.iterator(); it.hasNext();)
{
final String s = (String) it.next();
316
pr66100
452
Programmieren in Java
}
}
}
System.out.println(s.toUpperCase());
Mit Java 1.5 gibt es endlich die Möglichkeit, direkt zu bestimmen: "Mache für alle
Elemente im nachfolgenden Sammlungsobjekt etwas ...". Vorgesehen ist dafür eine
Kontrollstrukturanweisung317 von folgender Form:
for (Type identifier expr) { body }
Dieser Konstrukt wird so gelesen: "Für jedes identifier des Typs Type in expr
führe aus". Die Kontrollstrukturanweisung gilt für alle Objekte vom Typ der
Schnittstelle Iterable, wozu auf jedem Fall alle Datenstrukturen (einschl. Array)
zählen.
Bsp.318:
import java.util.List;
import java.util.ArrayList;
class NeueIteration
{
public static void main(String [] args)
{
String [] ar =
{ "Brecht", "Schiller", "Goethe", "Shakespeare", "Thomas Mann" };
List<String> xs = new ArrayList<String>();
for (String s:ar) xs.add(s);
for (String s:xs) System.out.println(s.toUpperCase());
}
}
Definition der Schnittstelle Iterable: public interface Iterable<T>
{ SimpleIterator<T> iterator();}
Iterable schreibt die Existenz einer Funktion iterator() vor, die einen
java.lang.SimpleIterator liefert. SimpleIterator ist ein Iterator319 ohne
remove():
public interface SimpleIterator<T>
{
boolean hasNext();
T next();
}
Der konkrete SimpleIterator muß nur die Methoden hasNext() und next()
implementieren, um das nächste Element in der Aufzählung zu beschaffen und das
Ende anzuzeigen.
java.util.Iterator() implementiert seit Java 1.5 den SimpleIterator:
public interface Iterator<E> extends SimpleIterator<E>
{
boolean hasNext();
E next();
void remove();
}
317
vgl. 2.4.7
pr66100
319 vgl. 6.2.1
318
453
Programmieren in Java
Das bekannte Programmierer-Idiom
for (Iterator i = c.iterator(); i.hasNext)
{
Typ o = (Typ) i.next();
}
kann durch das erweiterte for erfolgreich abgekürzt werden:
for (i : c)
{
Typ o = (Typ) i;
}
Bsp.320:
import java.util.Iterator;
import java.util.StringTokenizer;
class WordIterable implements Iterable, Iterator
{
private StringTokenizer st;
public WordIterable( String s )
{
st = new StringTokenizer( s );
}
// Methode vom Iterable
public Iterator iterator()
{
return this;
}
// Methoden vom Iterator
public boolean hasNext()
{
return st.hasMoreTokens();
}
public Object next()
{
return st.nextToken();
}
public void remove() { }
}
public class WordIterableDemo
{
public static void main( String args[] )
{
String s =
"Am Anfang war das Wort - am Ende die Phrase. (Stanislaw Jerzy Lec)";
for ( Object word : new WordIterable(s) )
System.out.println( word );
}
}
320
pr66100
454
Programmieren in Java
6.3 Die Klasse StringTokenizer
Die StringTokenizer-Klasse unterstützt das Aufteilen eines Strings in Teilworte bzw.
Zeichen, sog. Tokens. Tokens werden mit Hilfe von Trennzeichen erkannt, die ein
Token vom anderen trennen. Ein Token ist eine Zeichenkette, in der keine
Trennzeichen vorkommen. In der Regel werden Whitespaces321 als Trennzeichen
verwendet. Der StringTokenizer ist nicht an bestimmte Trennzeichen gebunden, sie
können vielmehr frei gewählt werden. Nur in der Voreinstellung sind Tabulator,
Leerzeichen und Zeilentrenner die Delimiter.
Konstruktor: public StringTokenizer(String str)
Ein StringTokenizer mit Whitespaces als Trennzeichen wird erzeugt. Der zu
untersuchende String ist str. Sollen andere Zeichen als Trennzeichen angegeben
werden, so müssen diese ebenfalls an den Konstruktor übergeben werden:
public StringTokenizer(String str, String delimiters)
Methoden:
public String nextToken() throws NoSuchElementException
gibt das jeweils nächste Token an
public boolean hasMoreTokens()
gibt Auskunft darüber, ob es noch weitere Tokens gibt.
public int countTokens()
gibt Auskunft darüber, wie viele Tokens in einem String vorhanden sind.
public String nextToken(String neueDelimiter)
bewirkt nach einem Aufruf die Veränderung der Trennzeichen.
Das Enumerations-Interface ist in die StringTokenizer-Klasse implementiert:
public boolean hasMoreElements()
{
return hasMoreTokens();
}
public Object nextElement()
{
return nextToken();
}
Bsp.: Ausgabe der Worte eines Satzes322
import java.util.StringTokenizer;
import java.io.*;
public class StringTokenizerTest
{
public static void main(String[] args)
{
System.out.println("\nStringTokenizerTest\n");
System.out.println("Gib einige Saetze fuer den Test an.");
System.out.println("Terminiere ueber eine leere Zeile");
boolean moreInput;
do {
System.out.print("> ");
String eingabeZeile = null;
BufferedReader eingabe = null;
eingabe = new BufferedReader(new InputStreamReader(System.in));
try {
321
Zu den Whitespaces gehören Leerzeichen, Tabulator, Zeilenvorschub und Zeilenumbruch. Wird beim
Erzeugen eines Tokenizer keine Trennzeichen-Zeichenkette angegeben, so werden Whitespaces als
Trennzeichen angenommen.
322 vgl. pr66100
455
Programmieren in Java
}
}
eingabeZeile =eingabe.readLine();
}
catch(IOException e) { System.out.println(e); }
if (eingabeZeile.equals("")) break;
StringTokenizer st = new StringTokenizer(eingabeZeile);
if (st.hasMoreTokens())
{
moreInput = true;
String wort = st.nextToken();
System.out.print(wort);
while (st.hasMoreTokens())
{
System.out.print(" / ");
System.out.print(st.nextToken());
}
System.out.println();
}
else { moreInput = false; }
} while (moreInput);
6.4 Die Klasse Random
Konstruktoren
Random()
// Erzeugt einen neuen Zufallszahlengenerator. Der Seed323 wird auf die aktuelle Zeit gesetzt.
Random(long seed)
// Erzeugt einen neuen Zufallszahlengenerator und benutzt den Parameter als Seed.
Methoden
public void setSeed(long seed)
// setzt den Zufallswert neu
Die Random-Klasse kann Zufallszahlen in vier verschiedenen Datentypen erzeugen:
public int nextInt()
// liefert die nächste Zufallszahl
public long nextLong()
// liefert die nächste Zufallszahö
public float nextFloat()
// liefert die nächste Zufallszahl zwischen 0.0 und 1.0
public double nextDouble()
erzeugt eine Zufallszahl im double-Format zwischen 0.0 und 1.0324.
323
Startwert für jede Zufallszahl ist ein 48 Bit Seed. Das Wort „Seed“ kommt vom englischen Wort für Samen
und deutet an, daß es bei der Generierung von Zufalsszahlen wie bei Pflanzen einen Samen gibt, der zu
Nachkommen führt. Aus diesem Startwert ermitteln sich anschließend die anderen Zahlen durch lineare
Kongruenzen. Dadurch sind die Zahlen nicht wirklich zufällig, sie gehorchen einem mathematischen Verfahren
324 wird von Math.random() verwendet.
456
Programmieren in Java
6.5 Datumsberechnungen
6.5.1 Die Klassen Date, Calendar, GregorianCalendar
Die Klasse Date
Die Date-Klasse repräsentiert ein bestimmtes Datum bzw. einen bestimmten
Zeitpunkt. Ausgangspunkt ist der 1. Januar 1970 0:00 h Greenwich-Zeit.
Date
<< Konstruktoren >>
public Date()
// erzeugt eine Datums-Angabe und initialisiert es mit der Zeit, die bei der Erzeugung gelesen
// wurde
public Date(long d)
// erzeugt ein Datums-Objekt und initialisiertes mit der angegebenen Anzahl von Millisekunden
// seit dem 1. Januar 1970, 00:00:00 GMT
<< Methoden >>
public long getTime()
// liefert die Anzahl der Millisekunden seit dem 1. Januar 1970, 00:00:00 GMT zurück
public void setTime(long t)
// setzt die Anzahl Millisekunden des Datums-Objekts neu. Die akuelle Zeit kann über
// today.setTime(new java.util.Date()) gesetzt werden.
public boolean before(Date d)
public boolean after(Date d)
// testet, ob das eigene Datum vor / nach dem Datum des Parameters ist. „true“, wenn getTime() für
// beide den gleichen Wert ergibt und der Parameter nicht null ist.
public int compareTo(Date d)
// vergleicht zwei Datum-Objekte und gibt 0 zurück, falls beide die gleiche Zeit repräsentieren.
//
public int compareTo(Object o)
// Ist das übergebene Objekt vom Typ Date, dann verhält sich die Funktion wie das zuvor
// angegebene compareTo(). Anderenfalls wirft die Methode eine ClassCastException
...
Abb.: Die Klasse Date
Bsp.325:
import java.util.Date;
class DatumundZeit
{
public static void main(String args[])
{
System.out.println(new Date() );
}
}
325
pr65100
457
Programmieren in Java
Die Klasse Calendar
(abstract class java.util.Calendar implements Serializable, Cloneable)
Sie wird eingesetzt zur Konvertierung zwischen verschiedenen Datumsformaten. Die
Klasse Calendar besitzt nur einige statisch Funktionen, z.B. getInstance(). Mit
der Klassenmethode getInstance() bekommt man ein nutzbares Format:
public static Calendar getInstance()
liefert einen Calendar in der Ausprägung von GregorianCalendar mit der dargestellten Zeitzone und
Lokalisierung.
Die Klasse GregorianCalendar
Die abstrakte Klasse Calendar wird durch die Klasse GregorianCalendar
implementiert. In der gegenwärtigen Implementierung deckt die Klasse den Zeitraum
von 4716 v. Chr. bis zum Jahre 5.000.000.
GregorianCalendar
<< Konstruktoren >>
public GregorianCalendar()
// erzeugt ein standardmäßiges GregorianCalendar-Objekt mit der aktuellen Zeit in der
// voreingestellten Zeitzone und Lokalisierung
public GregorianCalendar(int jahr, int monat, int tag)
// erzeugt ein GregorianCalendar-Objekt in der voreingestellten Zeitzone und Lokalisierung. Das
// Datum wird durch jahr, monat (0 <= monat <= 11) und tag festgelegt
public GregorianCalendar(int jahr, int monat, int tag, int stunde, int minute)
public GregorianCalendar(int jahr, int monat, int tag, int stunde, int minute, int sekunde)
<< Methoden >>
public final void set(int feld, int wert)
// setzt das Feld feld mit dem Wert wert
public final void set(int jahr, int monat, int tag)
// setzt die Werte für Jahr, Monat, Tag
public final void set(int jahr, int monat, int tag, int stunde, int minute)
public final void set(int jahr, int monat, int tag, int stunde, int minute, int sekunde)
public final int get(int feld)
// liefert den Wert für das Feld feld
...
Abb.: Die Klasse GregorianCalendar (class java.util.GregorianCalendar extends Calendar)
Das Abfragen und Setzen von Datumselementen erfolgt mit den überladenen
Methoden set() und get(). Beide erwarten als ersten Parameter einen
Feldbezeichner. Er gibt an, auf welches Datum / Zeitfeld zugegriffen werden soll. Die
get()-Methoden liefern den Inhalt des angegebenen Felds. Die set()-Methoden
schreiben den als zweiten Parameter übergebenen Wert in das Feld. Die
nachfolgende Tabelle zeigt eine Übersicht der Feldbezeichner und der
Wertebereiche.
458
Programmieren in Java
Feldbezeichner
ERA
YEAR
MONTH
DAY_OF_MONTH
WEEK_OF_YEAR
WEEK_OF_MONTH
DAY_OF_YEAR
DAY_OF_WEEK
Minimalwert
0(BC)
1
0
1
1
1
1
1
Maximalwert
1(AD)
5000000
11
31
54
6
366
7
DAY_OF_WEEK_IN_MONTH
HOUR
HOUR_OF_DAY
MINUTE
SECOND
MILLISECOND
AM_PM
ZONE_OFFSET
-1
0
0
0
0
0
0
-12*60*60*1000
6
12
23
59
59
999
1
12*60*60*1000
DST_OFFSET
0
1*60*60*1000
Erklärung
Datum vor oder nach Christi
Jahr
Monat
Tag
Woche
Woche des Monats
Tag des Jahres
Tag der Woche (1 = Sonntag, 7 =
Samstag
Tag der Woche im Monat
Stunde von 12
Stunde von 24
Minute
Sekunden
Millisekunden
Vor 12, nach 12
Zeitzonenabweichnung
in
Millisekunden
Sommerzeitabweichnung
in
Millisekunden
Abb.: Konstanten aus der Klasse Calendar
Bsp.: Ausgabe wichtiger Datumsfelder326
import java.util.*;
public class DateDemo
{
public static void main(String args[])
{
GregorianCalendar cal = new GregorianCalendar();
cal.setTimeZone(TimeZone.getTimeZone("ECT"));
cal.setTime(cal.getTime());
printCalendar(cal);
cal.set(Calendar.DATE,12);
cal.set(Calendar.MONTH,Calendar.MARCH);
cal.set(Calendar.YEAR,1973);
printCalendar(cal);
}
public static void printCalendar(Calendar cal)
{
String wochenTag = (new String[]{"Sonntag",
"Montag","Dienstag","Mittwoch","Donnerstag",
"Freitag","Samstag"})
[cal.get(Calendar.DAY_OF_WEEK)];
System.out.println(wochenTag + ", " +
cal.get(Calendar.DATE) + "." +
(cal.get(Calendar.MONTH)+1) + "." +
cal.get(Calendar.YEAR) + ", " +
cal.get(Calendar.HOUR_OF_DAY) + ":" +
o(cal.get(Calendar.MINUTE)) + ":" +
o(cal.get(Calendar.SECOND)) + " und " +
cal.get(Calendar.MILLISECOND) + " ms ");
System.out.println("Es ist die " +
cal.get(Calendar.WEEK_OF_YEAR) + ". Woche im Jahr und ");
System.out.println(cal.get(Calendar.WEEK_OF_MONTH) +
". Woche im Monat\n");
}
public static String o(int i)
326
pr65100
459
Programmieren in Java
{
return (i >= 10) ? Integer.toString(i) : "0" + i;
}
}
Die Klasse TimeZone (abstract class java.util.TimeZone implements
Serializable, Cloneable)
Die Klasse TimeZone repräsentiert eine Zeitzone inklusive Zeitverschiebung und
benutzt dazu folgende statische Methoden:
public static TimeZone getDefault()
// gibt die Zeitzone für die Umgebung zurück
public static String[] getAvailableIDs()
// liefert alle verfügbaren IDs
public static TimeZone getTimeZone(String ID)
// liefert die Zeitzone für eine gegebene ID
Bsp.: Gib eine Aufzählung aller unterstützten Zeitzonen327 an.
import java.util.*;
public class AlleZonen
{
public static void main(String args[])
{
String s[] = TimeZone.getAvailableIDs();
Arrays.sort(s);
for (int i = 0; i < s.length; i++)
System.out.println(s[i]);
}
}
327
Unter Windows mit dem JDK 1.3 sind dies 321 Zeitzonen, pr65100
460
Programmieren in Java
6.5.2 Formatieren der Datumsangaben
Die Klasse DateFormat
DateFormat
<< Methoden>>
public final String format(Date d)
// formatiert das Datum in einen Datum / Zeit-String
public static final DateFormat getDateInstance()
public static final DateFormat getTimeInstance()
public static final DateFormat getDateTimeInstance()
// liefert einen Datum/Zeit-Formatierer mit dem vorgegebenen stil aus der Standardumgebung
public static final DateFormat getDateInstance(int dateStil)
public static final DateFormat getTimeInstance(int stil)
// liefert einen Datum/Zeit-Formatierer mit den Stil stil und der Standardsprache
public static final DateFormat getDateInstance(int stil, Locale lokal328)
public static final DateFormat getTimeInstance(int stil, Locale lokal)
// liefert einen Datum/Zeit-Formatierer und der Sprache lokal
public static final DateFormat getDateTimeInstance(int dateStil, int timeStil)
// gibt einen Datum/Zeit-Formatierer für die gesetzte Sprache im angegebenen
// Formatierungsstil zurück
public static final DateFormat getDateTimeInstance(int dateStil, int timeStil, Locale lokal)
// gibt ein Datum/Zeit-Formatierer für die Sprache lokal im angegebenen Formatierungsstil zurück
public Date parse(String fs) throws ParseException
// „parst“ ein Datum oder einen Zeit-String
...
Abb.: Die Klasse DateFormat
(abstract class java.text.DateFormat extends Format implements Cloneable, Serializable
Die Klasse DateFormat bietet die Methoden getDateInstance(),
getTimeInstance(),
getDateTimeInstance()
mit
den
Paramtern
DateFormat.SHORT,
DateFormat.MEDIUM,
DateFormat.LONG,
DateFormat.FULL an, die die Zeit bzw. das Datum auf vier verschiedene Arten
formatieren:
Konstante
SHORT
MEDIUM
LONG
FULL
Beispiel für das Datum
29.9.01
29.9.2001
22. September 2001
Samstag, 22. September 2001
Beispiel für die Zeit
21:51
21:51:45
21:53:20 GMT+02:00
21:53 Uhr GMT+02:00
Abb.: Konstanten aus DateFormat
Da DateFormat abstrakt ist, ist erst die aus DateFormat abgeleitete Klasse
SimpleDateFormat einsatzbereit.
Bsp. mit Sprachen329: Das folgende Programm erzeugt die Ausgabe für Deutschland
und anschließend für Italien.
328 Unter Locale sind für einige Sprachen Konstanten vordefiniert: ENGLISH, FRENCH, GERMAN,
ITALIAN, JAPANESE, KOREAN, CHINESE, SIMPLIFIED_CHINESE, TRADITIONAL_CHINESE
461
Programmieren in Java
import java.util.*;
import java.text.*;
public class SimpleDateFormatierer
{
public static void main(String args[])
{
Calendar cal;
cal = new GregorianCalendar(TimeZone.getTimeZone("ECT") );
cal.setTime(cal.getTime());
DateFormat format;
format = DateFormat.getDateTimeInstance(
DateFormat.FULL, DateFormat.MEDIUM);
System.out.println(format.format(cal.getTime() ) );
format = DateFormat.getDateTimeInstance(
DateFormat.FULL, DateFormat.MEDIUM, Locale.ITALY);
System.out.println(format.format(cal.getTime() ) );
}
}
Die Klasse SimpleDateFormat
Zum Formatieren eines Datum-Objekts muß zunächst ein Objekt von
SimpleDateFormat
erzeugt
werden.
Dieses
bekommt
dann
evtl.
Formatierungsanweisungen (über eine andere Methode oder über einen weiteren
Konstruktor) mit und formatiert dann mit der format() das Datum.
SimpleDateFormat
<< Konstruktoren >>
public SimpleDateFormat()
// erzeugt eine neues SimpleDateFormat-Objekt in der eingestellten Sprache
public SimpleDateFormat(String fs)
// erzeugt ein SimpleDateFormat-Objekt mit dem vorgegebenen Formatierungsstring in der
// voreingestellten Sprache
public SimpleDateFormat(String fs, Locale loc)
// erzeugt ein SimpleDateFormat-Objekt mit dem vorgebenen Formatierungsstring in der Sprache
// loc.
<< Methoden >>
public void applyPattern(String fs)
// setze den Formatieruns-String
public void applyLocalizedPattern(String pattern)
// sertzt den Formatierungs-string für die gegebenen Sprache
public String toPattern()
// liefert den Formatierungsstring
public String toLocalizedPattern()
// liefert den übersetzten Formatierungsstring
Abb.: Die Klasse
DateFormat)
SimpleDateFormat
(class
java.text.SimpleDateFormat
extends
Zur individuellen Anpassung der Ausgabe kann ein Formatierungsstring angegeben
werden. Diese Formatierungsanweisung wird entweder dem Konstruktor übergeben
oder kann nachträglich mit der Methode applyPattern() geändert werden.
329
vgl. pr65200
462
Programmieren in Java
Symbol
G
Y
M
MM
MMM
MMMM
d
h
H
m
s
S
E
EEEE
D
F
w
W
a
k
K
Z
‘
‘'
Bedeutung
Ära
Jahr
Monat im Jahr
Monat im Jahr m. Null
Monat im Jahr (kurz)
Monat im Jahr (lang)
Tag im Monat
Stunde (1-12)
Stunde am Tag (0-23)
Minute der Stunde
Sekunde der Minute
Millisekunde
Tag der Woche (kurz)
Tag der Woche (lang)
Tag im Jahr
Tag der Woche i. Mon.
Woche im Jahr
Woche im Monat
am/pm-Text
Stunde am Tg (1-24)
Stunde (0-11)
Zeitzone
Zeichen für Text
Einzelnes Hochkomma
Präsentation
Text
Nummer
Nummer
Nummer
Text
Text
Nummer
Nummer
Nummer
Nummer
Nummer
Nummer
Text
Text
Nummer
Nummer
Nummer
Beispiel
AD
1998
7
07
Sep
September
26
9
0
13
22
257
Mi
Mittwoch
304
3
13
Text
Nummer
Nummer
Text
Trennzeichen
Literal
AM
24
0
GMT+02:00
‘
Abb.: Symbole im Formatstring zur Steuerung der Ausgabe
6.6 Formatieren mit Format-Objekten
Zahlen, Datumsangaben und Text können auf verschiedene Art und Weise formatiert
werden. Unter Java wird das Formatierungsverhalten in einer abstrakten Klasse
Format (abstract class java.text.Format implements Serializable,
Cloneable) fixiert. Format stellt die Methoden format() und parseObject()
bereit. Jede Zeichenkette, die vom Format-Objekt erzeugt wurde, ist auch mit dem
Parser wieder einlesbar.
Im JDK erweitern drei Klassen Format: DateFormat, MessageFormat und
NumberFormat. Sie übernehmen die Ein-/Ausgabe für das Datum, allgemeine
Botschaften (Nachrichten) und Zahlen. Jede der Klassen implementiert die Methoden
zur Ausgabe format() und zum Erkennen parseObject(). In Java 5 realisiert die
format()-Funktion eine Ausgabe, so wie sie unter der Programmiersprache C mit
printf() gesetzt wurde.
463
Programmieren in Java
6.6.1 Die Klasse Numberformat für die Ausgabe von Prozenten, Zahlen,
Währungen
Für die häufigsten Ausgaben als Prozentzahlen oder als Währungszahlen gibt es die
speziellen
statischen
Funktionen
getPercentInstance(),
getNumberInstance(),
getIntegerInstance()
und
getCurrencyInstance()330.
6.6.2 Ausgabe formatieren mit MessageFormat
MessageFormat ist eine konkrete Unterklasse der abstrakten Klasse Format. Sie
dient dazu, Nachrichten sprachunabhängig zu erzeugen.
6.6.3 Dezimalzahlenformatierung mit DecimalFormat
Die Klasse DecimalFormat dient zur formatierten Ausgabe von ganzen Zahlen.
Dem Konstruktor wird ein Formatierungs- String übergeben. Die Formatierung erfolgt
unter Berücksichtigung der eingestellten Sprache.
Bsp.: Die Klasse DecimalFormatTest331
import java.text.*;
public class DecimalFormatTest
{
public static void main(String args[])
{
double d = 12345.67890;
DecimalFormat df = new DecimalFormat("###,##0.00");
System.out.println(df.format(d));
}
}
Symbol
0
#
.
,
;
%
%%
-X
'
Bedeutung
repräsentiert eine Zahl
eine Zahl. Ist an dieser Stelle keine Zahl angegeben, dann bleibt die Stelle leer
trennt Vor- und Nachkommastellen
gruppiert die Zahl (Eine Gruppe ist so graß wie der Abstand von ',' zu '.'.
Trennzeichen für mehrere Formate
Standardzeichen für negatives Präfix
die Zahl wird mit 100 multipliziert und als Prozent ausgewiesen
genau wie Prozent nur mit Promille
Nationales Währungssymbol
Internationales Währungssymbol
alle andere Zeichen können normal benutzt werden
Ausmarkieren von speziellen Symbolen (im Präfix oder Suffix benutzt).
Abb.: Formatierungsanweisungen für DecimalFormat
330
331
jeweils in der parmeterlosen Variante und in der Variante mit einem Locale-Objekt.
vgl. pr65200
464
Programmieren in Java
6.6.4 Formatieren mit format()
Die
Klasse
final
class
java.lang.String
implements
CharSequence,
Comparable<String>, Serializable stellt mit der statischen Funktion format() eine
Methode bereit, Zeichenketten nach einer Vorlage zu formatieren
static String format(Locale l, String format, Object ... args)
liefert einen formatierten String, der aus der gewünschten Sprache, dem String und Argumenten
hervorgeht.
static String format(String format,Object ... args)
liefert einen formatierten String, der aus dem format-String und den Argumenten hervorgeht.
Der String format nennt sich Format-String. Er enthält neben dem auszugebenden
Zeichen sogenannte Format-Spezifierer, die dem Formatierer darüber Auskunft
geben, wie das Argument formatiert werden soll. "%s" steht für eine unformatierte
Ausgabe eines Strings.
Die Anzahl der Format-Spezifierer ist groß und vielfältig. Intern übernehmen diese
Aufgabe java.util.Formatter, die sich auch direkt verwenden lassen.
Praktischerweise ist das Formatieren und Ausgaben zu einer neuen Funktion
printf() in den PrintWriter und PrintStream332 gewandert
332
das System.out-Objekt ist vom Typ PrintStream
465
Programmieren in Java
7. Ein- und Ausgabe
In Java werden Ein-, Ausgabeoperationen über sog. Datenströme333 realisiert. Es
stehen zur Behandlung des Datenstrommodells zwei abstrakte Klassen zur
Verfügung: InputStream und OutputStream. Die beiden Klassen gehören zu
dem Paket java.io334 , das bei jedem Programm mit Ein-/Ausgabeoperationen
importiert werden muß.
Einfache Strom-Methoden erlauben nur das Versenden von Bytes mit Hilfe von
Datenströmen335. Ein nicht interpretierbarer Bytestrom kann von jeder beliebigen
Quelle kommen. Quelle bzw. Ziel des Stroms sind völlig verschiedene Erzeuger bzw.
Verbraucher von Bytes.
Allgemeine Methoden, die von jeder beliebigen Quelle lesen können, akzeptieren ein
Stromargument zur Bezeichnung der Quelle. Allgemeine Methoden zum Schreiben
akzeptieren einen Strom zur Zielbestimmung. Filter haben zwei Stromargumente. Sie
lesen vom ersten, verarbeiten die Daten und Schreiben ins zweite.
Zum Senden/Empfangen verschiedener Datentypen gibt es die Schnittstellen
DataInput und DataOutput. Sie legen Methoden zum Senden/Empfangen
anderer Java-Datentypen fest. Mit Hilfe der Schnittstellen ObjectInput und
ObjectOutput lassen sich ganze Objekte über einen Strom senden.
Alle Methoden, die sich mit Ein- und Ausgabeoperationen beschäftigen, werden in
der Regel mit throws IOException abgesichert. Diese Subklasse von Exception
enthält alle potentiellen I/O-Fehler, die bei der Verwendung von Datenströmen
auftreten können.
Die I/O-Exceptions können direkt mit einem try-catch-Block aufgefangen oder an
übergeordnete Methoden weitergegeben werden.
333
Der Begriff Strom kommt aus dem Betriebssystem Unix und bezieht sich auf „Pipes“. Eine Pipe ist ein nicht
interpretierbarer Strom von Bytes, der zur Kommunikation zwischen Programmen (bzw. „gegabelten Kopien“
eines Programms) oder zum Lesen und Schreiben von verschiedenen Geräten und Dateien benutzt wird.
Ein Strom ist ein Kommunikationspfad zwischen der Quelle und dem Ziel eines Informationsblocks.
334 Die Klasse java.io enthält eine große Anzahl von Klassen zur Ein-/Ausgabe. Die meisten dieser Klassen
leiten sich von InputStream bzw. OutputStream ab.
335 Ein Strom von Bytes kann mit einem Wasserstrom verglichen werden. Wird aus einem Strom Wasser
entnommen, dann wird er als Eingabestrom benutzt. Wird in einen Strom Wasser geschüttet, dann wird er als
Ausgabestrom verwendet. Die Verbindung von Strömen kann mit dem Verbinden von Wasserschläuchen
verglichen werden.
467
Programmieren in Java
ByteArrayInputStream
FileInputStream
DataInput
BufferedInputStream
DataInputStream
FilterInputStream
InputStream
Object
PipedInputStream
SequenceInputStream
StringBufferedInputStream
LineNumberInputStream
PushbackInputStream
RandomAccessFile
ByteArrayOutputStream
FileOutputStream
OutputStream
FilterOutputStream
DataOutput
PipedOutputStream
Throwable
Exception
IOException
Abb.: java.io
468
BufferedOutputStream
DataOutputStream
PrintStream
EOFException
FileNotFoundException
InterruptedIOException
UTFDataFormatException
Programmieren in Java
7.1 Die abstrakten Klassen InputStream und OutputStream
7.1.1 InputStream
Aufgabenbeschreibung
Mit dieser Klasse können Leseoperationen eines Bytestroms verwirklicht werden.
Woher die Bytes kommen und wie sie befördert werden, spielt keine Rolle. Sie
müssen dem einlesenden Objekt nur zur Verfügung stehen. Die Aufgabe der Klasse
InputStream ist die Repräsentation von Klassen, die Eingaben aus verschiedenen
Quellen produzieren.
Klasse
Funktion
ByteArrayInputStream
Ein Puffer im arbeitsspeicher
wird ald Eingabestrom benutzt.
StringBufferInputStream
Konvertiert einen String in einen
InputStream
FileInputStream
Dient zum Lesen aus einer Datei
PipedInputStream
Produziert
Daten
für
den
PipedOutputStream (implentiert
das „Pipe“-Konzept.
SequenceInputStream
Konvertiert zwei oder mehrere
„InputStream“-Objekte in einen
einzelnen InputStream.
Abb.: Eingabestrom-Typen
Quellen zu einem Eingabestrom können sein:
1. Ein Array von Bytes
2. Ein Zeichenketten-Objekt
3. Eine Datei
4. Eine „Pipe“
469
Argument für den Konstruktor
Nutzungsmöglichkeit
Der Puffer aus dem Bytes geholt
werden.
Als Datenquelle. Verbunden mit
einem
FileInputStream-Objekt
kann daraus ein nützliches
Interface gestaltet werden.
Ein String. Die zugrundeliegende
Implementierung benutzt einen
StringBuffer.
Als Datenquelle. Verbunden mit
einem
FiIeInputStream-Objekt
kann daraus ein nützliches
Interface gestaltet werden.
Ein String der den Dateinamen
enthält oder ein File- bzw.
FileDescriptor-Objekt
Als Datenquelle. Verbunden mit
einem FilterInputStream-Objekt
kann daraus ein nützliches
Interface gestaltet werden.
PipedOutputStream.
Als
Datenquelle
im
Multithreading. Verbunden mit
einem FilterInputStream-Objekt
kann daraus ein nützliches
Interface gestaltet werden.
Zwei InputStream-Objekte oder
eine Enumeration für einen
Container von InputStreamObjekten.
Als Datenquelle. Verbunden mit
einem FilterInputStream-Objekt
kann daraus ein nützliches
Interface gestaltet werden.
Programmieren in Java
5. Eine Folge von Strömen, die zu einem umfassenden, einzelnen Strom gesammelt werden können.
6. Anders beschaffene Quellen, z.B. eine Internet-Verbindung
Die „read“-Methoden
Zum Einlesen von Datenströmen gibt es die „read“-Methode. Die einfachste Form
ist: public abstract int read() throws IOException. Ein einzelnes Byte
wird aus dem Eingabestrom gelesen und ausgegeben. Falls der Bytestrom das Ende
erreicht hat, wird „-1“336 ausgageben. Die Methode führt ein blockierendes Lesen
aus, d.h.: Es wird auf Daten gewartet, falls sie nicht unmittelbar zur Verfügung
stehen.
Die Methode public int read(byte[] bytes) throws IOException füllt
ein Datenfeld mit Bytes, die aus einem Strom gelesen wurden und gibt die Anzahl
Bytes zurück. Bei Bedarf (z.B. im Datenstrom sind nicht genügend Bytes vorhanden)
werden weniger Bytes gelesen, als das Datenfeld aufnehmen kann.
public int read(byte[] bytes, int offset, int length) throws
IOException füllt ein Datenfeld ab Position „offset“ mit bis zu length Bytes aus
dem Strom. Es wird entweder die Anzahl der gelesenen Bytes oder –1 für das
Dateiende ausgegeben.
Weitere nützliche Methoden
public int available throws IOException
Allen „read“-Methoden ist gemeinsam: Sie warten auf das Ende aller angeforderten
Eingaben. Es kann dabei zu längeren Blockaden beim Einlesen von Daten kommen.
Die Methode „available“ gibt die Anzahl Bytes zurück, die ohne Blockieren
gelesen werden kann.
public long skip(long n) throws IOException
Die „skip“-Methode benutzt „read“ zum Überspringen und blockiert deshalb unter
den gleichen Bedingungen wie „read“. Die gibt die Anzahl Bytes zurück, die sie
übersprungen hat, oder „-1“, falls das Ende des Stroms erreicht wurde
public boolean markSupported()
Die Methode gibt „true“ zurück, falls der Sttrom Markierungen unterstützt.
public void mark(int readLimit)
Die Methode markiert die aktuelle Position im Strom für eine spätere Rückkehr.
public void reset() throws IOException
Mit dieser Methode kann der Strom auf die Markierung zurücksetzen.
public void close() throws IOException
Diese Methode schließt die Verarbeitung von einem Strom. Die meisten Ströme
werden automatisch bei der Garbage Collection geschlossen.
336
Rückgabe „-1“ bedeutet zum Unterschied von C nicht, daß ein Fehler aufgetreten ist.
470
Programmieren in Java
7.1.2 OutputStream
Aufgabenbeschreibung
Der Ausgabestrom ist ein Empfänger von Daten. Man findet Ausgabeströme fast nur
in Verbindung mit Eingabeströmen. Die Aufgabe der Klasse OutputStream ist die
Repräsentation von Klassen zur Festlegung der Datensenke. Das kann ein „Array
von Bytes“, eine Datei oder eine „Pipe“ sein.
Klasse
Funktion
ByteArrayOutputStream
Kreiert einen Arbeits-speicherPuffer.
Alle
vom
Strom
gesendeten Daten werden hier
plaziert.
FileOutputStream
Zum Schreiben einer Datei
PipedOutputStream
Information zum Schreiben ist
Eingabe für den assoziierten
PipedInputStream
Argumente für den Konstruktur
Nutzungsmöglichkeit
Optional: zu initialisierende
Puffergröße
Zum Bestimmen des Datenziels.
Verbunden
mit
einem
FilterOutputStream-Objekt kann
daraus ein nützliches Interface
gestaltet werden.
Eine Zeichenkette, die den
Namen der Datei repräsentiert ,
bzw.
ein
Fileoder
FileDescriptor-Objekt.
Zum
Bestimmen
eines
Datenziels.
Verbunden
mit
einem FileOutputStream-Objekt
kann daraus ein nützliches
Interface gestaltet werden.
PipedInputStream
Zum Bestimmen des datenziels
für „Multithreading“. Verbunden
mit einem FilterOutputStreamObjekt
kann
daraus
ein
nützliches Interface gestaltet
werden.
Abb.: Ausgabestrom-Typen
Die „write“-Methoden
public abstract void write(int b) throws IOException
Die Methode schreibt ein einzelnes Byte in den Ausgabestrom.
public void write(byte[] bytes) throws IOException
Die Methode schreibt den Inhalt des Datenfelds bytes in den Ausgabestrom.
public void write(byte[] bytes, int offset, int length) throws
IOException
Die Methode schreibt „length“ Bytes ab Position „offset“ aus dem Datenfeld
bytes.
public void flush() throws IOException
Diese Methode leert einen Ausgabestrom
public void close() throws IOException
Auch Ausgabeströme sollten am Ende einer Verarbeitung geschlossen werden.
471
Programmieren in Java
7.2 Gefilterte Ströme
Die Klassen FilterInputStream und FilterOutputStream zum Verknüpfen
von Strömen.
Diese Klassen ermöglichen das Verknüpfen von Strömen, keine neuen Methoden.
Der große Vorteil liegt in der Verbindung mit einem anderen Strom. Die
Konstruktoren für den FilterInputStream und den FilterOutputStream
haben deshalb InputStream- und OutputStream-Objekte als Parameter:
protected FilterInputStream(InputStream ein)
public FilterOutpuStream(OutputStream aus)
Nützliche Subklassen von FilterInputStream sind:
Klasse
Funktion
DataInputStream
Ermöglicht das Einlesen von
primitiven Typen
BufferedInputStream
Gepuffertes Einlesen
LineNumberInputStream
Verfolgt die zeilennummer im
Eingabestrom. Die Methode
getLineNumber()
und
setLineNumber(int)
können
verwendet werden
Hat
einen
Byte
großen InputStream
„Pushback“-Puffer, das letzte Braucht der Java-Compiler
gelesene Zeichen kann in den
strom zurückgelegt werden.
PushbackInputStream
Konstruktor-Argumente
Nutzungsmöglichkeit
InputStream
Enthält ein Interface für das
lesen primitiver Typen
InputStream
mit
optionale
Angabe der Puffergröße
InputStream
Fügt eine Zeilennummerierung
hinzu
Abb.: „FilterInputStream“-Typen
Nützliche Subklassen von FilterOutputStream sind:
Klasse
DataOutputStream
PrintStream
BufferedOutputStream
Funktion
Konstruktor-Argumente
Nutzungsmöglichkeit
Ausgabe primitiver Typen
OutputStream
Erlaubt das Schreiben primitiver
Typen
Produziert formatierte Ausgabe. OutputStream
Formatierte Ausgabe
Gepufferte Ausgabe, der Puffer OutputStream
kann mit flush() geleert werden
Gepufferte Ausgabe
Abb.: „FilterOutputStream“-Typen
Gepufferte Ströme
Gepufferte Ströme werden mit der Klasse BufferedInputStream für die Eingabe und
BufferedOutputStream für die Ausgabe realisiert:
public BufferedInputStream(InputStream ein)
public BufferedInputStream(InputStream ein, int puffergroesse)
public BufferedOutputStream(OutputStream aus)
public BufferedOutputStream(OutputStream aus, int puffergroesse)
472
Programmieren in Java
Die Klasse BufferedInputStream implementiert die vollen Fähigkeiten der
InputStream-Methoden, besitzt aber zusätzlich einen gepufferten Byte-Array
(Cache für weitere Leseoperationen).
Die Klasse BufferedOutputStream implementiert die vollen Fähigkeiten der
OutputStream-Methoden, besitzt aber zusätzlich einen gepufferten Byte-Array.
Die BufferedInputStream-Klasse versucht mit einem einzigen read-Aufruf soviel
Daten wie möglich in ihre Puffer einzulesen. Die BufferedOutputStream-Klasse
ruft nur dann die write-Methode auf, wenn ihr Puffer voll ist oder wenn flush
aufgerufen wird.
Datenströme
Die Filter DataInputStream und DataOutputStream sind nützliche Filter des
java.io-Pakets. Sie ermöglichen Schreiben und Lesen primitiver Typen in Java auf
maschinenunabhängige Weise.
DataInputStream implementiert eine DataInput-Schnittstelle. Diese Schnittstelle
definiert Methoden zum Lesen von primitiven Datentypen in Java (sowie noch ein
paar weitere Methoden).
DataOutputStream implementiert eine DataOutput-Schnittstelle mit AusgabeMethoden, die den Eingabe-Methoden der DataInput-Schnittstelle entsprechen.
Die Konstruktoren zu den DataInputStream- und DataOutputStream-Klassen
sind Stromfilter-Konstruktoren, die den zu filternden Strom als Parameter verwenden:
public DataInputStream (InputStream ein)
public DataOutputStream(OutputStream aus)
Die Schnittstelle DataInput. Sie enthält Methoden zum Lesen primitiver Typen:
public
public
public
public
public
public
public
public
public
public
public
boolean readBoolean() throws EOFException, IOException
byte readByte() throws EOFException, IOException
int readUnsignedByte() throws EOFException, IOException
char readChar() throws EOFException, IOException
short readShort() throws EOFException, IOException
int readUnsignedShort() throws EOFException, IOException
int readInt() throws EOFException, IOException
long readLong() throws EOFException, IOException
float readFloat() throws EOFException, IOException
double readDouble() throws EOFException, IOException
String readUTF() throws EOFException, IOException337,
UTFDataFormatException
Ausgeworfene Ausnahmen sind vom Typ IOException oder EOFException.
EOFException wird ausgeworfen, wenn das Ende des Stroms erreicht wird. Die
Ausnahme läßt sich überall einsetzen, wo bisher auf „-1“ überprüft wurde.
Zum Lesen einer durch Zeilenumschaltung (\r, \n) begrenzten Zeile aus einer
Textdateigibt es die Methode: public String readLine() throws
IOException. „\r, \n, \r\n“ werden entfernt, bevor die Zeile als Zeichenkette
wiedergegeben wird.
Bsp.: Einlesen von der Standardeingabe und Ausgabe
337
UTF (Unicode Transmission Format) ist ein spezielles Format zum Kodieren von 16-Bit-Unicode-Werten
473
Programmieren in Java
Mit der Methode public int skipBytes(int anzahlBytes) wird eine Anzahl
Bytes übersprungen.
Die Schnittstelle DataOutput. Duch diese Schnittstelle sind folgende Methoden zur
Ausgabe primitiver Typen definiert:
public
public
public
public
public
public
public
public
void
void
void
void
void
void
void
void
writeBoolean(boolean b) throws IOException
writeByte(int b) throws IOException
writeChar(int c) throws IOException
writeShort(int c) throws IOException
writeInt(int i) throws IOException
writeFloat(float f) throws IOException
writeDouble(double d) throws IOException
writeUTF(String s) throws IOException
Mit den Methoden
public void writeBytes(String s) throws IOException
public void writeChars(String s) throws IOException
kann eine Zeichenkette als eine Reihe von Bytes oder Zeichen abgelegt werden.
Die PrintStream-Klasse
Der „System.out“-Strom mit den Methoden „System.out.print“ bzw.
„System.out.println“ ist eine Instanz von PrintStream. „System.in“ ist ein
InputStream.
Erstellen eines „PrintStream“-Objekts. Das PrintStream-Objekt muß in einen
existierenden Ausgabestrom angehangen werden. Über einen optionalen Parameter
kann automatisch die Methode flush() aufgerufen werden:
public PrintStream(OutputStream aus)
public PrintStream(OutputStream aus, boolean autoFlush)
Methode für die Ausgabe von Objekten.
public
public
public
public
public
public
public
public
public
public
public
public
public
public
public
public
public
public
public
public
void
void
void
void
void
void
void
void
void
void
void
void
void
void
void
void
void
void
void
void
flush()
close()
write(int b)
print(Object o)
print(String s)
print(boolean c)
print(char[] puffer)
print(char c)
print(int i)
print(float f)
print(double d)
println(Object f)
println(String s)
println(boolean c)
println(char[] puffer)
println(char c)
println(int i)
println(float f)
println(double d)
println()
474
Programmieren in Java
7.3 Die Klasse File
Ein File-Objekt kann sich auf eine Datei oder ein Verzeichnis beziehen und läßt
sich auf verschiedene Arten erstellen.
public File(String pfadname)
erstellt eine File-Instanz, die in pfadname angegeben wurde.
public File(String pfadname, String dateiname)
erstellt eine File-Instanz, die sich aus dem in dateiname angegebenen Dateinamen und dem in
pfadnamen aungegebenen Pfad zusammensetzt.
public File(File verzeichnis, String dateiname)
erstellt eine File-Instanz, die sich aus dem in dateiname angegebenen Dateinamen und dem in
verzeichnis angegebenen Verzeichnis zusammensetzt.
Die Klasse File (class java.io.File implements Serializable,
Comparable) umfaßt Methoden zum Bearbeiten von Dateien bzw. Verzeichnissen.
Nachdem ein File-Objekt erzeugt wurde, können Methoden zum Zugriff auf die
einzelnen Bestandteile des Dateinamens aufgerufen werden:
public String getName();
// liefert den Namen der Datei
public String getPath();
// liefert den kompletten Namen (Datei oder Verzeichnis)
public String getAbsolutePath();
// liefert den absoluten Pfadnamen für das File-Objekt
public String getParent();
Die Methoden
public boolean isFile()
public boolean isDirectory()
überprüfen, ob eine Datei oder ein Verzeichnis vorliegt. Mit
public boolean canRead()
public boolean canWrite()
wird Lese- bzw. Schreiberlaubnis überprüft. Die Methode
public long lastModified()
zeigt an, wann die Datei bzw. das Verzeichnis geändert wurde. Mit
public boolean exists()
wird die physikalische Existenz einer Datei oder eines Verzeichnisses überprüft. Über
public String getName()
wird der Name einer Datei bzw. eines Verzeichnisses ermittelt.
Wurde ein File-Objekt für ein Verzeichnis konstruiert, stehen folgende Methoden zur
Verfügung:
public String[] list();
liefert einen Array von Strings, der für jeden gefundenen Verzeichniseintrag ein
Element enthält. Die Liste enthält den Namen aller Dateien und Unterverzeichnisse
mit Ausnahme von „.“ und „..“. Die einfache Funktion list() liefert nur relative
475
Programmieren in Java
Pfade (Dateiname oder Verzeichnisname). Komplette File-Objekte beschafft die
Methode listFiles), die ihre vollständige Pfadangabe kennen. Der vollständige Pfad
kann dann mit getName() erfragt werden.
In einer Variante von list() kann die Auswahl der Verzeichniseinträge
eingeschränkt werden. Es muß aber dann ein Objekt übergeben werden, das das
Interface FilenameFilter implementiert. Dieses besitzt die Methode accept338,
die für jede gefundene Datei aufgerufen wird und entscheidet, ob sie in die Liste
aufgenommen werden soll oder nicht. Zusätzlich gibt es die statische Methode
listRoots, mit der eine Liste der Wurzeln verfügbarer Dateisysteme beschafft
werden kann.
public static File[] listRoots()
liefert die verfügbaren Wurzeln der Dateisysteme oder null, falls diese nicht
festgestellt werden konnten.
Neben dem Zugriff auf Verzeichniseinträge gibt es Methoden339 für Löschen,
Umbenennen von Dateien bzw. Verzeichnissen und für das Neuanlegen von
Verzeichnissen.
public boolean mkdir()
// Anlegen des spezifizierten Verzeichnisses
public boolean mkdirs()
// auch "Vater"-Verzeichnisse werden automatisch angelegt.
public boolean renameTo(File ziel)
public boolean delete();
// löscht die durch das File-Objekt bezeichnete Datei
Da es bei URL-Objekten häufig vorkommt, daß eine Datei die Basis ist, wurde ab
Version 1.2 von Java die Methode toURL() aufgenommen:
public URL toURL() throws MalformedURLException
// liefert ein URL-Objekt vom File-Objekt
Es muß ein File-Objekt erzeugt werden. Anschließend erzeugt toURL() ein URLObjekt, das das Protokoll „file“ trägt und danach eine absolute Pfadangabe zur Datei
bzw. zum Verzeichnis enthält.
338
public boolean accept(File verzeichnis, String dateiName)
Alle Methoden geben true zurück, wenn sie ihre Aufgabe erfolgreich ausführen konnten; anderenfalls geben
sie false zurück
339
476
Programmieren in Java
7.4 Die RandomAccessFile-Klasse
Für den Zugriff auf Random-Access-Dateien stellt das Paket java.io die Klasse
RandomAccessFile zur Verfügung. Es kann nur auf Dateien zugegriffen werden,
auch das durch die „Streams“ realisierte Filter-Konzept gibt es in Random-AccessDateien nicht.
Öffnen, Neuanlegen und Schließen. Das Öffnen von Random-Access-Dateien erfolgt
mit den Konstruktoren:
public
RandomAccessFile(File
datei,
String
mode)
throws
FileNotFoundException
Bei der Übergabe des File-Objekts wird die durch dieses Objekt spezifizierte Datei
geöffnet.
public RandomAccessFile(String name, String mode) throws
FileNotFoundException
Bei der Übergabe des String-Parametres „name“ wird die Datei mit diesem Namen
geöffnet. Der zweite Parameter mode bestimmt die Art des Zugriffs:
- „r“: Öffnen nur zum Lesen
- „rw“: Öffnen zum Schreiben und Lesen. Ein reiner Schreibmodus wird nicht
unterstützt.
Es gibt in der Klasse RandomAccessFile keine explizite Differenzierung zwischen
Öffnen der Datei und Neuanlegen. Implizit gilt: Eine Datei wird neu angelegt, wenn
beim Öffnen im Modus „w“ nicht vorhanden ist. Existiert die Datei bereits, wird sie
unverändert geöffnet, und es gibt keine Möglichkeit, ihren Inhalt zu löschen oder die
Dateilänge auf einen bestimmten Wert zusetzen.
Das Schliessen erfolgt durch Aufruf der parameterlosen Methode close.
Positionieren des Dateizeigers. Jeder Schreib- und Lesezugriff erfolgt an der
Position, die durch den aktuellen Inhalt des Satzzeigers bestimmt wird und
positioniert den Zeiger um die Anzahl gelesener bzw. geschriebener Bytes weiter.
Die Klasse RandomAccessFile stellt eine Reihe von Methoden zum Zugriff auf den
Satzzeiger zur Verfügung.
Die Methode seek. Die RandomAccessFile-Klasse besitzt alle Methoden, die in
den Schnittstellen DataInput und DataOutput verfügbar sind. Mit public void
seek(long dateiPosition) throws IOException kann man an jede
beliebige Position der Datei springen. Mit der Methode public long
getFilePointer() throws IOException kann die derzeitige Dateiposition340
bestimmt werden.
Weiterhin stehen lesende und schreibende Zugriffsmethoden zur Verfügung.
340
Der Dateipositionswert in den Methoden seek und getFilePointer() ist die Anzahl der Bytes vom
Anfang bis zum Ende der Datei.
477
Programmieren in Java
RandomAccessFile
<<Konstruktoren>>
public RandomAccessFile(File datei, String zugriffsmodus) throws FileNotFoundException;
// erzeugt einen Random-Access-Datei-Strom vom File-Objekt.
// Ob aus der datei gelesen oder geschrieben wird, bestimmt
// ein String, der den Modus angibt: „r“, „w“ oder „rw“.
public RandomAccessFile(String datei, String zugriffsmodus) throws FileNotFoundException;
<< Methoden >>
public long getFilePointer() throws IOException;
publc void seek(long pos) throws IOException;
// setzt den Dateizeiger – bezogen auf den Dateianfang – auf eine
// bestimmte Stelle, an der der nächste Lese- und Schreibzugriff
// stattfindet
public int skipBytes(int n) throws IOException;
public long length() throws IOException;
public final boolean readBoolean() throws IOException;
public final byte readByte() throws IOException;
public final char readChar() throws IOException;
public final double readDouble() throws IOException;
public final float readFloat() throws IOException;
public final int readInt() throws IOException;
public final long readLong() throws IOException;
public final short readShort() throws IOException;
public final String readUTF() throws IOException;
public final void readFully(byte b[]) throws IOException;
public final void readFully(byte[], int off, int laenge) throws IOException;
public final String readLine() throws IOException;
public final int readUnsignedByte() throws IOException;
public final int readUnsignedShort() throws IOException;
public int read() throws IOException;
public int read(byte b[]) throws IOException;
public int read(byte b[], int off, int laenge) throws IOException;
public final void writeBoolean(boolean w) throws IOException;
public final void writeByte(int w) throws IOException;
public final void writeBytes(String s) throws IOException;
public final void writeChar(int w) throws IOException;
public final void writeChars(String s) throws IOException;
public final void writeDouble(double w) throws IOException;
public final void writeFloat(float w) throws IOException;
public final void writeInt(int w) throws IOException;
public final void writeLong(long w) throws IOException;
public final void writeShort(int w) throws IOException;
public final void writeUTF(String str) throws IOException;
public void write(int b) throws IOException;
public void write(byte b[]) throws IOException;
public void write(byte b[], int off, int laenge) throws IOException;
Abb.: Die Klasse RandomAccessFile
478
Programmieren in Java
7.5 Die Klasse StreamTokenizer
Die Klasse StreamTokenizer arbeitet im Gegensatz zum StringTokenizer aus
dem Paket java.util auf einem Datenstrom, genauer auf einem Reader. Sie
beachtet keine Unicode-Eingabe, sondern nur Zeichen aus dem Bereich von \u0000
bid \u00FF. Wahrend des Parsens werden bestimmte Merkmale aus dem Text
erkannt, so u. a. Identifier (etwa Schlüsselworte), Zahlen, Strings in
Anführungszeichen und verschiedene Kommentararten (C-Stil oder C++-Stil). Der
Erkennungsvorgang wird anhand einer Syntaxtabelle überprüft. Diese Tabelle enthält
bspw. die Zeichen, die ein Schlüsselwort identifizieren oder die Zeichen, die
Trennzeichen sind. Jedes gelesene Zeichen wird dann keinem, einem oder mehreren
Attributen zugeordnet. Diese Attribute fallen in die Kategorie Trennzeichen,
alphanumerische Zeichen, Zahlen, Hochkomma bzw. Anführungszeichen.
Zur Benutzung der Klasse wird zunächst ein StreamTokenizer-Objekt erzeugt und
dann die Syntaxtabellen initialisiert. Das Überlesen von Kommentarzeilen wird durch
st.slashSlashComments(true);
st.slashStarComments(true);
// Kommentar
/* Kommentart */
gesteuert. Die erste Methode überliest im Eingabestrom alle Zeichen bis zum Return.
Die zweite Methode überliest alles bis zum Stern / Slash.
Beim Lesen des Datenstroms mit nextToken() kann über bestimmte Flags erfragt
werden, ob im Stream ein Wort (TT_WORD) eine Zahl (TT_NUMBER), das Ende der
Datei (TT_EOF) oder das Ende der Zeile (TT_EOL) vorliegt. Wichtig ist
eolIsSignificant(true) zu setzen, da andernfalls der StreamTokenizer nie ein
TT_EOL findet. Wurde ein Wort erkannt, dann werden alle Zeichen in
Kleinbuchstaben konvertiert.
Bsp.341: StreamTokenizerDemo.java
import java.io.*;
public class StreamTokenizerDemo
{
public static void main(String args[]) throws IOException
{
String fn = "StreamTokenizerDemo.java";
StreamTokenizer st = new StreamTokenizer(new FileReader(fn));
st.slashSlashComments(true);
st.ordinaryChar('/');
st.parseNumbers();
st.eolIsSignificant(true);
int tval;
while ((tval = st.nextToken()) != st.TT_EOF)
{
if (tval == st.TT_NUMBER)
System.out.println("Nummer: " + st.nval);
else if (tval == st.TT_WORD)
System.out.println("Wort: " + st.sval);
else if (tval == st.TT_EOL)
System.out.println("Ende der Zeile");
else System.out.println("Zeichen: " + (char) st.ttype);
}
}
}
341
vgl. pr75100
479
Programmieren in Java
480
Programmieren in Java
class java.io.StreamTokenizer
<< Konstruktor >>
StreamTokenizer(Reader r)
<< Methoden >>
public void resetSyntax
// Reinitialisiert die Syntaxtabelle des Tokenizers, so dass kein Zeichen eine Soderstellung
// geniesst.
public void wordChars(int low, int hi)
// Zeichen im Bereich von low bis hi werden als Trennzeichen erkannt
public void whitespaceChars(int low, int hi)
// Zeichen im Bereich von low bis hi werden als Trennzeichen erkannt.
public void ordinaryChars(int low, int hi)
// Zeichen im Bereich von low bis hi geniessen keine Sonderbehandlung, werden als
// normale zeichen behandelt.
public void ordinaryChar(int ch)
// Das Zeichen besitzt keine zusätzliche Funktion, ist bspw. kein Kommentarzeichen,
// trennsymbol oder Nummernzeichen
public void parseNumbers()
// Nummern sollen vom Tokenizer erkannt werden. In der Syntaxtabelle gelten die 12
// Zeichen 0, 1, 2, 3, 4, 5, 6., 7, 8, 9, ., - als numerisch. Liegt ein Gleipunktzahl an,
// so wird der Vorkommateil in nval abgelegt und das Token ergibt im Attribut ttype
// den Wert TT_NUMBER
public void commentChar(int ch)
// Gibt das Zeichen an, das einen einzeiligen Kommentar einleitet
public void slashStarComments(boolean flag)
// Der Tokenizer soll Kommentare im C-Stil (/* .. */) erkennen oder nicht
public void slashSlashComments(boolean flag)
// Der Tokenizer soll Kommentare im C++-Stil (// ..) erkennen oder nicht
public void lowerCaseMode(boolean fl)
// liegt in ttype ein Token vom Typ TT_WORD vor, so wird das automatisch in
// Kleinschreibweise konvertiert, falls fi gleich true ist
public int nextToken() throws IOException
// liefert das nächste Token im Strom. Der Typ des Token wird im Attribut ttype hinterlegt.
// Zusätzliche Informationen befinden sich im Attribut nval (Nummer) oder sval (Zeichenkette).
// In der Regel wird solange geparst, bis das Token TT_EOF zurückgegeben wird.
public void pushBack()
// Der Aufruf von nextToken() liefert den aktuellen wert im Attribut ttype und ändert nval oder sval
// nicht
public int lineno()
// liefert die Zeilennummer
Abb.: Die Klasse StreamTokenizer
481
Programmieren in Java
7.6 Klassen für spezielle nützliche Ströme
Die LineInputStream-Klasse
Die SequenceInputStream-Klasse
Die PushbackInputStream-Klasse
482
Programmieren in Java
7.7 Java 1.1 IO-Ströme
7.7.1 Grundlagen
Bis zur Version 1.0 des JDK gab es nur Byte-Streams in Java. Das ergab
Schwierigkeiten bei der Umwandlung zwischen Bytes und 16 Bit langen UnicodeZeichen, die innerhalb von Java benutzt werden. Im JDK 1.1. wurden daher
„Character“-Streams auf der Basis von 16 Bit langen Unicode-Zeichen eingeführt.
Die Kompatibilität wurde durch Brückenklassen gewährleistet, die „Character“Streams in „Byte“-Streams überführen (und umgekehrt).
Quellen und Senken: „Java 1.0“-Klassen
InputStream
OutputStream
FileInputStream
FileOutputStream
StringBufferInputStream
ByteArrayInputStream
ByteArrayOutputStream
PipedInputStream
PipedOutputStream
Korrespondierende Klassen in Java 1.1
Reader
Konverter: InputStreamReader
Writer
Konverter: OutputStreamWriter
FileReader
FileWriter
StringReader
StringWriter
CharArrayReader
CharArrayWriter
PipedReader
PipedWriter
Abb. Wichtige Klassen zur Eingabe
In Filterklassen kann nur eine etwas gröbere Zuordnung von Klassen aus Java 1.0 zu
Java 1.1 angegeben werden. So ist „BufferedOutputStream“ eine Subklasse von
FilterOutputStream, BufferedWriter ist dagegen keine Subklasse von
FilterWriter.
Filter: Klassen in Java 1.0
FilterInputStream
FilterOutputStream
BufferedInputStream
BufferedOutputStream
DataInputStream
PrintStream
LineNumberInputStream
StreamTokenizer
PushbackInputStream
Korrespondierende Klassen in Java 1.1
FilterReader
FilterWriter (abstrakte Klasse ohne Subklassen)
BufferedReader (mit readLine)
BufferedWriter
DataInputStream, bei readLine() BufferedReader
verwenden
PrintWriter
LineNumberReader
StreamTokenizer
PushbackReader
Abb. Wichtige Klassen zur Ausgabe
Einige Klassen bleiben von der Umstellung unberührt: DataOutputStream, File,
RandomAccessFile, SequenceInputStream.
483
Programmieren in Java
7.7.2 Die abstrakte Klassen Reader und ihre Ableitungen
Basis aller sequentiellen Eingaben ist die abstrakte Klasse Reader, die eine
Schnittstelle für streambasierte Eingaben zur Verfügung stellt:
public abstract class java.io.Reader
{
protected Reader();
public abstract void close() throws IOException;
// schliesst den Eingabestrom
public void mark(int readAheadlimit) throws IOException;
// markiert eine bestimmte Position innerhalb eines Eingabestroms
public boolean markSupported();
// überprüft, ob Markieren unterstützt wird
public int read() throws IOException;
// liest das nächste Zeichen aus dem Eingabestrom und liefert es als
// "int". Der Rückgabewert –1 kennzeichnet das Ende des Eingabestroms
public int read(char cbuf[]) throws IOException;
// übernimmt eine Reihe von Zeichen in den als Parameter angegebenen
// Array
public abstract int read(char cbuf[], int off, int len) throws IOException;
// übernimmt eine Reihe von zeichen in einen Bereich des Puffers, der
// durch Versatz (Offset) und die gewünschte Länge gekennzeichnet ist
public long skip(long n) throws IOException;
// überliest Zeichen im Eingabestrom
public boolean ready() throws IOException;
// liefert True, falls der nächste Aufruf von read erfolgen kann, ohne
// daß die Eingabe beendet ist
public void reset() throws IOException;
// setzt den Lesezeiger an die markierte Stelle zurück
}
Von der abstrakten Basisklasse Reader können keine Instanzen abgeleitet werden.
Es gibt eine Reihe aus Reader abgeleitete Klassen, die die Verbindung zur
konkreten Eingabe bzw. zum konkreten Eingabegerät herstellen.
Klasse
InputStreamReader
FileReader
PushbackReader
BufferedReader
LineNumberReader
StringReader
CharArrayReader
PipedReader
Bedeutung
Abstrakte Klasse für alle Reader, die einen ByteStrom in einen Character-Strom umwandeln
Konkrete Ableitung von InputStreamReader zum
Einlesen einer Datei
Eingabefilter mit der Möglichkeit zur Rückgabe
von Zeichen
Reader zur Eingabepufferung und zum Lesen
kompletter Zeilen
Ableitung von BufferedReader mit der Fähigkeit
zum Zählen von Zeilen
Reader zum Einlesen von Zeichen aus einem
String
Reader zum Einlesen von Zeichen aus einem
Zeichen-Array
Reader zum Einlesen von Zeichen aus einem
PipedWriter
Abb.: Von Reader abgeleitete Klassen
InputStreamReader
Diese Klasse konvertiert Byte- in Character-Streams.
484
Programmieren in Java
FileReader
Sie ermöglicht die Eingabe einer Datei. Die Klasse FileReader geht direkt aus einem
InputStreamReader hervor.
Konstruktoren. Mit ihnen ermöglicht FileReader das Öffnen von Dateien:
public FileReader(String dateiName) throws FileNotFoundException;
Bei Übergabe der Zeichenkette dateiName wird die Datei mit dem angegebenen
Namen zum Lesen geöffnet. Falls sie nicht vorhanden ist, kommt es zur Ausnahme
des Typs FileNotFoundException
public FileReader(File datei) throws FileNotFoundException;
erwartet ein File-Objekt zur Spezifikation einer zu öffnenden Datei.
public FileReader(FileDescriptor fd);
erwartet ein File-Deskriptor-Objekt, das eine bereits geöffnete Datei angibt.
StringReader
Diese Klasse erlaubt das Lesen von Zeichen aus einem String.
CharArrayReader
Diese Klasse erlaubt das Lesen von Zeichen aus einen „Zeichen-Array“
BufferedReader
Diese Klasse dient zur Pufferung von Eingaben. BufferedReader implementiert die
vollen Fähigkeiten der Methoden von Reader. Diese Klasse verwendet dazu
gepufferte Zeichen-Arrays.
Konstruktoren: public BufferedReader(Reader ein)
public BufferedReader(Reader ein, int gr)
Der erste Parameter ist ein Reader-Objekt, auf das ein BufferedReader aufgesetzt
werden soll. Der optionale Parameter gr gibt die Größe des internen Puffer an. Fehlt
er, so wird eine für die meisten Situationen angemessene Standardeinstellung
verwendet.
Zeilenweises Lesen: public String readLine throws IOException.
Rückgabewert ist ein String mit dem Zeicheninhalt (ohne Begrenzungszeichen) oder
„null“, falls das Ende von „Stream“ erreicht wurde.
LineNumberReader
Diese Klasse ist eine Ableitung von BufferedReader, die zusätzlich noch die
Anzahl der Eingabezeichen beim Einlesen zählen kann. Mit „public int
getLineNumber()“ wird der aktuelle Stand des Zeilenzählers abgefragt. Mit
public void setLineNumber(int LineNumber) kann der aktuelle Stand des
Zeilenzählers verändert werden.
485
Programmieren in Java
7.7.3 Die abstrakte Klasse Writer und ihre Ableitungen
Basis aller sequentiellen Eingaben ist die Klasse Writer, die eine Schnittstelle für
„stream“-basierte Ausgaben zur Verügung stellt:
public abstract class java.io.Writer
{
protected Writer();
// Oeffnen und Vorbereiten des Ausgabestroms
public abstract void close() throws IOException;
// Schliessen des Ausgabestroms
public abstract void flush() throws IOException;
// Leeren der Ausgabe von einem gepufferten Cache
public void write(int c);
// Grundform mit einem einzigen Ausgabeparameter vom typ int,
// der als Byte in den Ausgabestrom geschrieben wird
public void write(char einPuffer[]) throws IOException;
// Varianten von write, die ein Array von Bytes oder ein String-Objekt
// erwarten und dieses durch Aufruf von write() ausgeben
public abstract void write(char einPuffer[], int off, int len) throws
IOException;
public void write(String str) throws IOException;
public void write(String str, int off, int len) throws IOException;
}
Von der abstrakten Basisklasse Writer können keine Instanzen abgeleitet werden.
Es gibt eine Reihe aus „Writer“ abgeleitete Klassen, die die Verbindung zur
konkreten Ausgabe(gerät) herstellen:
Klasse
OutputStreamWriter
FileWriter
FilterWriter
PrintWriter
BufferedWriter
StringWriter
CharArrayWriter
PipedWriter
Bedeutung
Abstrakte Basisklasse für alle Writer, die einen Character-Stream in
einen Byte-Stream umwandeln
Konkrete Ableitung von OutputStreamWriter zur Ausgabe einer Datei
Abstrakte Basisklasse für die Konstruktion von Ausgabefiltern
Ausgabe aller Basistypen im Textformat
Writer mit Ausgabepufferung
Writer zur Ausgabe in einem String
Writer zur Ausgabe im Zeichen-Array
Writer zur Ausgabe in einem PipedReader
Abb.: Aus Writer abgeleitete Klassen
Von den abgeleiteten Klassen wird erwartet: Überlagerung der Methoden flush(),
close() und write(char einPuffer[], int offset, int laenge).
Zun Schachteln von Ausgabe-Streams gibt es die aus Writer abgeleiteten Klassen:
BufferedWriter, PrintWriter und FilterWriter. Falls die "write"-Methode
eines derart geschachtelten "Writer" aufgerufen wird, gibt sie Daten nicht mehr
direkt an den internen "Writer", sondern führt Filterfunktionen aus.
OutputStreamWriter
Diese Klasse übernimmt eine Konvertierung zwischen Character- und Byte-Streams.
486
Programmieren in Java
FileWriter
ermöglicht die Ausgabe in eine Datei. Sie implementiert die abstrakten Eigenschaften
von Writer. Die Klasse FileWriter bietet 4 Konstruktoren für das Öffnen von
Dateien an:
public FileWriter(String dateiName) throws IOException
Falls dateiName eine bereits vorhandene Datei bezeichnet, wird sie geöffnet und ihr bisheriger Inhalt
gelöscht, anderenfalls wird eine neue Datei mit diesem Namen angelegt. Sukzessive Aufrufe von write
schreiben dann in diese Datei.
public FileWriter(String dateiName, boolean append) throws IOException
Wird der Parameter append mit dem Wert true an den Konstruktor übergeben, dann werden die
Ausgabezeichen an die Datei angehängt, falls sie bereits existiert.
public FileWriter (File datei) throws IOException
public FileWriter (FileDescriptor fd)
Parameter ist hier ein File-Objekt bzw. ein FileDescriptor-Objekt
Die Klasse StringWriter
Sie besitzt zwei Konstruktoren:
public StringWriter()
bestimmt einen StringWriter in der Standardgröße eines StringBuffer-Objekts. Der interne Puffer
wächst automatisch, falls fortgesetzte Aufrufe von „write“ das eroderlich machen
public StringWriter(int initialGroesse)
Zugriffe: Die schreibenden Zugriffe auf die Puffer erfolgen mit den von Writer
bekannten „write“-Methoden. Für den Zugriff auf den Inhalt des Puffers gibt es die
Methoden public StringBuffer getBuffer() und public String
toString().
CharArrayWriter
Sie schreibt Zeichen in ein Character-Array, das automatisch bei fortgesetzten
Aufrufen von „write“ erweitert wird. Die Klasse besitzt zwei Konstruktoren:
public CharArrayWriter()
public CharArrayWriter(int initialGroesse)
Zugriffe: Schreibende Zugriffe erfolgen mit den von „Writer“ bekannten „write“Methoden. Für den Zugriff auf das Array gibt es die Methoden public String
toString() und public char[] toCharArray(). Mit der Methode public
void reset() wird der interne Puffer geleert. Die Größe des Zeichen-Array wird
über public int size() ermittelt. Durch Aufruf von public void
writeTo(Writer aus) throws IOException kann der Inhalt des Array an
einen anderen Writer übergeben werden und so mit einem einzigen
Funktionsaufruf in eine Datei geschrieben werden.
BufferedWriter
Die Klasse puffert „Stream“-Ausgaben über einen internen Puffer, in dem die
Ausgaben von write zwischengespeichert werden. Falls der Puffer gefüllt ist oder die
Methode flush aufgerufen wird, werden alle gepufferten Ausgaben in den „Stream“
geschrieben. Das Puffern der Ausgabe ist immer dann nützlich, wenn die Ausgabe in
eine Datei geschreiben werden soll. BufferedWriter besitzt zwei Konstruktoren:
public BufferedWriter(Writer aus)
public BufferedWriter(Writer aus, int groesse)
487
Programmieren in Java
In beiden Fällen wird ein existierender Writer übergeben, an den alle gepufferten
Ausgaben weitergereicht werden. Ist die Größe des Puffers nicht explizit angegeben,
legt BufferedWriter einen Standardpuffer an.
Die „write“-Methoden von BufferedWriter entsprechen denen der Klasse
Writer. Zusätzlich gibt es eine Methode newLine mit der eine Zeileumschaltung in
den Stream geschrieben werden kann.
PrintWriter
Diese Klasse stellt Ausgabemethoden für alle primitiven Datentypen und für
Objektgruppen zur Verfügung.
Konstruktoren.
public PrintWriter(Writer aus)
instanziert ein PrintWriter-Objekt durch Übergabe eines Writer-Objekts, auf das die Ausgabe
umgelenkt werden soll.
public PrintWriter(Writer aus, boolean autoflush)
Mit dem Parameter autoflush wird angegeben, ob nach der Ausgabe „einer Zeilenschaltung“
automatisch die Methode flush() aufgerufen werden soll.
Ausgabe primitiver Typen (und Objekttypen): Sie wird realisiert über eine Reihe
überladener Methoden mit dem Namen „print“. Zusätzlich gibt es alle Methoden in
der Variante println, bei der automatisch an das Ende der Ausgabe ein
Zeilenvorschub (Zeilenumschaltung) angehängt wird. Println() existiert auch
parameterlos zur Ausgabe einer einzelnen Zeilenumschaltung.
public
public
public
public
public
public
public
public
public
void
void
void
void
void
void
void
void
void
print(boolean b)
print(char z)
print(char s[])
print(double d)
print(float f)
print(int i)
print(long l)
print(Object obj)
print(String s)
488
Programmieren in Java
7.7.4 Demonstrationsprogramm zur Ein-/ Ausgabe ab Java Version 1.1
Das Demonstrationsprogramm soll folgende Ein-/Ausgabe zeigen:
1. Zeilenweises Lesen
2. Eingabe vom Speicher
3. Formatierte Speicher-Eingabe
4. Zeilennummerierung und Dateiausgabe
5. Speichern und Wiedergewinnen von Daten
import java.io.*;
public class EinAusDemo
{
public static void main(String[] args)
{
try
{
// 1. Zeilenweises Lesen
BufferedReader ein1 = new BufferedReader(
new FileReader(args[0]));
String s1, s2 = new String();
while ((s1 = ein1.readLine()) != null)
s2 += s1 + '\n';
ein1.close();
// Lesen Standardeingabe
BufferedReader stdin = new BufferedReader(
new InputStreamReader(System.in));
System.out.print("Gib eine Zeile an: ");
System.out.println(stdin.readLine());
// 2. Eingabe vom Speicher
StringReader ein2 = new StringReader(s2);
int zch;
while ((zch=ein2.read()) != -1)
System.out.print((char) zch);
// 3. Formatierte Speicher-eingabe
try
{
DataInputStream ein3 = new DataInputStream(
new StringBufferInputStream(s2));
while (true)
System.out.print((char) ein3.readByte());
}
catch(EOFException e)
{
System.out.println("Ende des Stroms");
}
// 4. Zeilennummerierung und Datei-Ausgabe
try
{
LineNumberReader zn = new LineNumberReader(
new StringReader(s2));
// BufferedReader ein4 = new BufferedReader(zn);
PrintWriter aus1 = new PrintWriter(
new BufferedWriter(
new FileWriter("EinAusDemo.aus")));
while ((s1 = zn.readLine()) != null)
aus1.println("/* " + zn.getLineNumber() + " */" + s1);
// System.out.println("/* " + zn.getLineNumber() + " */" + s1);
aus1.close();
}
catch(EOFException e)
{
System.out.println("Ende des Stroms");
489
Programmieren in Java
}
// 5. Speichern und Wiedergewinnen von Daten
try
{
DataOutputStream aus2 = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("Daten.txt")));
aus2.writeDouble(3.14159);
aus2.writeBytes("Das war Pi");
aus2.close();
DataInputStream ein5 = new DataInputStream(
new BufferedInputStream(
new FileInputStream("Daten.txt")));
BufferedReader ein5br = new BufferedReader(
new InputStreamReader(ein5));
System.out.println(ein5.readDouble());
System.out.println(ein5br.readLine());
}
catch(EOFException e)
{
System.out.println("Ende des Stroms");
}
}
catch(FileNotFoundException e)
{
System.out.println("Datei nicht gefunden: " + args[1]);
}
catch(IOException e)
{
System.out.println("IO Exception");
}
}
}
490
Programmieren in Java
8. Serialisierung
8.1 Grundlagen
Serialisierung342 ist die Fähigkeit ein Objekt, das im Hauptspeicher der Anwendung
existiert, in ein Format zu konvertieren, das es erlaubt, das Objekt in eine Datei zu
schreiben oder über eine Netzwerkverbindung zu transportieren. Auch der
umgekehrte Weg gehört dazu: Rekonstruktion eines in serialisierter Form
vorliegenden Objekts in das interne Format der laufenden Java-Maschine.
8.1.1 Das Interface Serializable
Serialisierbare Objekte können in Dateien gespeichert oder über Netzwerke
übertragen werden. Dazu müssen sie in ein Byteformat umgewandelt werden
können. Das Markierungsinterface Serializable kennzeichnet eine Klasse als
serialisierbar. Ähnlich wie bei Cloneable sind dazu keine Methoden zu
implementieren, es müssen aber auch alle referenzierenden Klassen serialisierbar
sein. Falls dies nicht der Fall ist, wird eine NotSerializableException erzeugt.
Voraussetzung für das Serialisieren ist die Implementierung der Schnittstelle
Serializable. Das Interface enthält keine Implementierung, sondern dient nur
dazu, durch die Implementierungs-Hierarchie die Fähigkeit zum Schreiben
anzuzeigen.
342 häufig auch mit dem Begriff Persistenz gleichgesetzt. Persistenz bezeichnet das dauerhafte Speichern von
Daten auf einem externen Datenträger, so daß sie auch nach dem Beenden des Programms erhalten bleiben.
491
Programmieren in Java
8.1.2 Die Klasse ObjectOutputStream
Zur Serialisierung eines Objekts ( - oder allgemeiner Daten bzw. Primitive - ) benötigt
man einen OutputStream. Am besten eignet sich ein FileOutputStream dafür,
da meistens die Daten in einer Datei343 gesichert werden sollen. FileOutputStream
erweitert die Klasse OutputStream. Eine Verbindung zwischen Datei und ObjektStrom
wird
durch
die
Klasse
ObjectOutputStream
(class
java.io.ObjectOutputStream
extends
OutputStream
implements
DataOutput,
ObjectOutput,
ObjectStreamConstants)
geschaffen.
ObjectOutputStream implementiert das Interface ObjectOutput, z.B. die
Funktion writeObject() zum Schreiben von Objekten.
OutputStream
{ abstract }
ObjectOutputStream
<< Konstruktoren >
public ObjectOutputStream(OutputStream out) throws IOException
// erzeugt einen ObjectOutputStream, der in den angegebenen OutputStream
// schreibt
<< Methoden >>
public final void writeObject(Object obj) throws IOException
// schreibt das Objekt. Die implementierende Klasse weiß, wie
// das Objekt zu schreiben ist
public void write(int b) throws IOException
// Ein Bayte wird geschrieben
public void write(byte b[]) throws IOException
// schreibt ein Array von Bytes
...
public void writeBytes(String daten) throws IOException;
public void writeChars(String daten) throws IOException;
public void writeByte(int daten) throws IOException;
public void writeShort(int daten) throws IOException;
public void writeChar(int daten) throws IOException;
public void writeInt(int daten) throws IOException;
public void writeLong(long daten) throws IOException;
public void writeFloat(float daten) throws IOException;
public void writeDouble(double daten) throws IOException;
...
public void flush() throws IOException
// Noch gepufferte Daten werden geschrieben
public void close() throws IOException
// Stream wird geschlossen.
...
Abb.: Die Klasse OblectOutputStream einschl. einiger Methoden des Interface ObjectOutput
343
Der Dateiname wird häufig so gewählt, daß er mit dem Präfix „ser“ endet.
492
Programmieren in Java
Die Methode public final void writeObject(Object obj) throws
IOException schreibt folgende Daten in den OutputStream:
- Die Klasse des als Argument übergebenen Objekts
- Die Signatur der Klasse
- Alle nicht statischen, nicht transienten Membervariablen des übergebenen Objekts einschl. der aus
Elternklassen geerbten Membervariablen.
Bsp.: Serialisierung eines „Zeit-“ Objekts. Das Zeit-Objekt ist eine Instanz der
Klasse „Zeit“, die eine Uhrzeit (Stunde, Minute) kapselt.
import java.io.*;
public class Zeit implements Serializable
{
private int stunde;
private int minute;
public Zeit(int stunde, int minute)
{
this.stunde = stunde;
this.minute = minute;
}
public String toString()
{
return stunde + ":" + minute;
}
}
Mit Hilfe eines Objekts vom Typ ObjectOutputStream kann ein Time-Objekt
serialisiert werden.
import java.io.*;
import java.util.*;
public class Pr81100
{
public static void main(String args[])
{
try {
FileOutputStream fs = new FileOutputStream("test1.ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
Zeit z = new Zeit(10,20);
os.writeObject(z);
os.close();
}
catch(IOException e)
{ System.err.println(e.toString()); }
}
}
Nach dem Schließen des Streams steht das serialisierte Objekt in „test1.ser“.
493
Programmieren in Java
8.1.3 Die Klasse ObjectInputStream
An einen Eingebestrom kommt man über einen InputStream. Da die Daten häufig
aus einer Datei kommen ist dies häufig ein FileInputStream, der mit einem
ObjectInputStream verknüpft wird. Die Methode readObject() liest das
Objekt, findet heraus, was für ein Typ es ist und holt, falls nötig, auch noch Objekte,
auf die verwiesen wird.
InputStream
{ abstract }
ObjectInputStream
<< Konstruktoren >
public ObjectInputStream(InputStream in) throws IOException,
StreamCorruptedException
<< Methoden >>
public final Object readObject() throws OptionalDataException
ClassNotFoundException, IOException
// Liest ein Objekt und gibt es zurück
public boolean readBoolean() throws IOException
public byte readByte(i) throws IOException
public short readShort() throws IOException
public char readChar() throws IOException
public int readInt() throws IOException
public long readLong() throws IOException
public float readFloat() throws IOException
public double readDouble() throws IOException
. ...
Abb.: Die Klasse ObjectInputStream
Analog zum Interface ObjectOutput gibt es hier das Interface ObjectInput
(interface java.io.ObjectInput extends DataInput)
494
Programmieren in Java
<< interface >>
ObjectInput
<< Methoden >>
public Object readObject() throws ClassNotFoundException, IOException
public int read() throws IOException
// liest ein byte aus dem datenstrom. Dieses ist –1, wenn das Ende erreicht ist
public int read(byte b[]) throws IOException
// liest ein Array in den Puffer. Das Ende wird durch –1 angezeigt
public int read(byte b[], int off, int laenge) throws IOException
// liest ein Array von Bytes in den Puffer b an die Stelle off genau len Bytes
public long skip (long n) throws IOException
// überspringt n Bytes im Eingabestrom.
public int available() throws IOException
// Gibt die Anzahl der Zeichen zurück, die ohne Blockade gelesen werden
public void close() throws IOException
// schließt den Eingabestrom
...
Abb.: Das Interface ObjectInput
Das Deserialisieren kann mann sich etwa so vorstellen:
1. Anlegen einen neuen Objekts des zu serialisierenden Typs, Vorbelegen der Membervariablen mit
Defaultwerten, Aufruf des Defaultkonstruktor der ersten nicht serialisierbaren Superklasse.
2. Lesen der serialsierten Daten und Zuweisen der Daten zu den entsprechenden Membervariablen
des angelegten Objekts.
Das durch Deserialisieren erzeugte Objekt hat anschließend dieselbe Struktur und
denselben Zustand, den das serialsierte Objekt hatte344. Da der Rückgabewert von
readObject() vom Typ Object ist, muß das erzeugte Objekt in den tatsächlichen
Typ (oder eine seiner Oberklassen) umgewandelt werden.
Bsp.: Deserialisieren des im vorherigen Beispiel serialisierten und in die Datei
„test1.ser“ geschriebenen Zeit-Objekts.
import java.io.*;
import java.util.*;
public class Pr81101
{
public static void main(String args[])
{
try {
FileInputStream fs
= new FileInputStream("test1.ser");
ObjectInputStream is = new ObjectInputStream(fs);
Zeit z = (Zeit) is.readObject();
System.out.println(z.toString());
is.close();
}
catch(ClassNotFoundException e)
{ System.err.println(e.toString()); }
catch(IOException e)
{ System.err.println(e.toString()); }
}
}
344
Abgesehen von den nicht serialsierten Membervariablen des Typs static oder transient.
495
Programmieren in Java
Beim Deserialisieren von Objekten können einige Fehler passieren. Damit ein Aufruf
von readObject() erfolgreich ist, müssen mehrere Kriterien erfüllt sein:
- das nächste Element des Eingabestroms ist tatsächlich ein Objekt (kein primitiver Typ).
- Das Objekt muß vollständig und fehlerfrei aus der Eingabedatei lesen lasse.
- Es muß eine Konvertierung auf den gewünschten Typ erlauben, also entweder zu derselben oder
einer daraus abgeleiteten Klasse gehören.
- Der Bytecode für die Klasse des zu serialsierenden Objekts muß vorhanden sein. Er wird beim
Serialisieren nicht mitgespeichert, sondern muß dem Empfängerstrom wie üblich als kompilierter
Bytecode zur Verfügung stehen.
- Die Klasseninformation des serialisierten Objekts und die im deserialisierenden Programm als
Bytecode vorhandene Klasse müssen zueinander kompatibel sein.
8.2 Tiefe Objektkopien
Klassen könen die Methode clone() überschreiben und so eine Kopie der Werte
liefern. Die Standarimplementierung ist jedoch so festgelegt, daß die Kopie flach ist.
Das bedeutet: Referenzen auf Objekte, die vom klonenden Objekt ausgehen, werden
beibehalten und diese Objekte nicht extra kopiert.
Eine tiefe Kopie kann folgendermaßen erzeugt werden: Das zu klonende Objekt ist
zu serialisieren und dann wieder auszupacken. Die zu klonenden Objekte müssen
nur das Serializable-Interface implementieren.
496
Programmieren in Java
9. Netzwerkprogrammierung
Das Paket java.net enthält Klassen zur Programmierung von TCP/IP345Netzwerkzugriffen. Dieses (Internet-) Protokoll stellt die Basis aller Java-Netzzugriffe
dar. Darauf aufbauend gibt es weiter entwickelte Netzwerkprotokolle wie RMI für
entfernte Methodenaufrufe und verteilte Anwendungen und Enterprise
JavaBeans für verteilte Objekte.
9.1 Adressen, Ressourcen und URLs
9.1.1 Die Klasse InetAddress
Zur Adressierung von Rechnern im Netz wird die Klasse InetAddress des Pakets
java.net verwendet. Eine InetAddress enthält sowohl eine IP-Adresse346 als
auch den symbolischen Namen des jeweiligen Rechners347. Die beiden Bestandteile
können mit den Methoden getHostName() und getHostAddress() abgefragt
werden. Mit Hilfe von getAddress() kann die IP-Adresse auch direkt als „byte“Array (mit 4 Elementen) beschafft werden.
Zur Generierung eines InetAddress-Objekts stehen zur Verfügung:
public static InetAddress getByName(String host) throws
UnknownHostException
// erwartet einen String mit der IP-Adtesse oder dem Namen als Argument
public static InetAddress getLocalHost() throws UnknownHostException
// liefert ein InetAdress-Objekt für den eigenen Rechner
Das folgende Programm348 ermittelt zu einer IP-Adresse den symbolischen Namen
des zufehörigen Rechners bzw. zum symbolischen Namen eine IP-Adresse,
import java.net.*;
public class WerBinIch
{
public static void main(String args[])
{
if (args.length != 1)
{
System.err.println("Verwendung: java WerBinIch <host>");
System.exit(1);
}
try {
345
Als Protokoll bezeichnet man die Menge aller Regeln, die nötig sind, um einen kontrollierten und
eindeutigen Verbindungsaufbau, Datenaustausch und Verbindungsabbau gewährleisten zu können. Die derzeit
in Java verfügbareb Netzwerkfähigkeiten basieren auf den Internet-Protokollen TCP/IP (bzw. TCP/UDP)
346 IP steht für Internet Protocol. Die 32-Bit-lange IP-Adresse besteht aus einer Netzwerk-ID und einer Host-ID.
Die Host-ID gibt die Bezeichnung des Rechners innerhalb seines eigenen Netzwerks an, die Netwerk-ID liefert
die Bezeichnung des Rechners.
347 Anstelle der IP-Adresse können bei Anwendungsprotokollen symbolische Namen verwendet werden. Sie
werden mit Hilfe von Namen-Servern in die zugehörige IP-Adresse übersetzt , bevor die Verbindung aufgebaut
wird. Das Domain Name System (DNS) ordnet numerischen IP-Adressen sprechende Namen zu.
348 vgl. pr91100
497
Programmieren in Java
// Hole die angeforderte Adresse
InetAddress addr = InetAddress.getByName(args[0]);
System.out.println(addr.getHostName());
System.out.println(addr.getHostAddress());
}
catch(Exception e)
{
System.err.println(e.toString());
System.exit(1);
}
}
}
9.1.2 Die Klasse URL
Im WWW werden Ressourcen über URLs (Universal Ressource Locator) identifiziert.
Eine URL besteht aus:
einem Protokollnamen, z.B. http349 (HTML), file (lokale Dteien), ftp (Dateitransfer), rmi (Remote
Method Invocation), inop(Inter ORB Protocol) oder jdbc (Java Database Connectivity), dem ein
Doppelpunkt und
zwei Schrägstriche folgen. Nach dem Doppel-Slash kommt die Angabe:
//user350:password351@host352:port353/url-path354. Einige Teile können bei einer URL
ausgelassen werden. So sind „user:password@“, „password“, „port“ und „/url-path“ optional.
Sind Benutzername und Paßwort gegeben, so folgt ein „At“-Zeichen @. Paßwort und Benutzername
durfen nicht Doppelpunkt, At-Zeichen oder Slash enthalten. Fehlt die Angabe des Rechners, wird der
aktuelle Rechner (localhost) benutzt, bei Ports bekannte Standardnummern. Schließlich folgt eine
Bezeichnung der Ressource, typischerweise unter Angabe eines Pfads.
Java implementiert das Konzept eines Uniform Resource Locator durch eine eigene
Klasse URL, die sich im Paket java.net befindet. Aus einer gegebenen
Zeichenreihe erstellt Java einen geeigneten URL, den Man zum Einrichten einer
URLConnection nutzen kann. Die Verbindung ermöglicht eine Interaktion, die vom
Protokoll der betreffenden Ressource definiert wird.
Erzeugen von URL-Objekten. Am einfachsten ist es, über eine String-Repräsentation
der URL-Adresse zu gehen, z.B.: URL fhURL = new URL("http://www.fh-
regensburg.de/");
Diese URL wurde mit dem Konstruktor
public URL(String urlAddr) throws MalformedURLException
erzeugt. Ein anderer Konstruktor ist
349
Mit dem Hypertext Tranfer Protocol wird auf Inhalte des Web zugegriffen. Die URL für Dienste im Web
beginnt mit http.
350 Optionaler Benutzername
351 optionales Paßwort, ein Paßwort ohne Benutzername kann nicht angegeben werden
352 Auf die Angabe des Protokolls folgt in der Regel der Name der Domäne oder die IP-Adresse des Servers.
Name und IP-Adresse sind in der Regel gleichwertig, da von einem besonderen Dienst der Name in eine IPAdresse umgesetzt wird.
353 Eine Verbindung zu einem Rechner geschieht immer durch eine Art Tür, die Port genannt wird. Die PortNummer läßt den Server die Dienste kategorisieren. Jeder Dienst bekommt eine andere Portnummer, damit sie
sich unterscheiden lassen. Normalerweise horcht der HTTP-Server auf Port 80.
354 Auf den Servernamen folgt die Angabe der Datei, auf die über HTTP oder FTP zugegriffen werden soll. Da
sie in einem Verzeichnis liegt, beschreibt url-path den Weg zur Datei. Ist keine Datei vorhanden und endet die
Angabe der URL mit einem Slash „/“, dann versucht der Web-Server auf eine der Dateien index.html bzw.
index.htm zuzugreifen.
498
Programmieren in Java
public URL(URL urlObj, String … ) throws MalformedURLException
// erzeugt relativ zur URL ein neues URL-Objekt.
Bsp.: Zugiff auf die Homepage der FH Regensburg
import java.net.*;
import java.io.*;
public class OeffneURLStrom
{
public static void main(String args[])
{
try {
String s;
URL fhURL = new URL("http://www.fh-regensburg.de/");
BufferedReader ein = new BufferedReader(
new InputStreamReader(fhURL.openStream()));
while ((s = ein.readLine()) != null)
System.out.println(s);
ein.close();
}
catch(MalformedURLException e)
{ System.out.println("MalformedURLException: " + e); }
catch(IOException e)
{ System.out.println("IOException: " + e); }
}
}
Die Klasse URL besitzt auch Konstruktoren, die die Angabe der Komponenten von
der Adresse (also Zugriffsart, Hostname und Dateiadresse getrennt) akzeptieren:
public URL(String protocol, String host, int port, String file) throws
MalformedURLException
public URL(String protocol, String host, String file) throws
MalformedURLException
Jeder der Konstruktoren wirft eine MalformedURLException, wenn der Parameter
im Konstruktor entweder null ist oder er ein unbekanntes Protokoll beschreibt.
Zugriff auf Daten über eine URL. Es gibt zwei Möglichkeiten über eine URL bzw. über
eine URLConnection. Beide Wege benutzen Streams. Jedes URL-Objekt besitzt die
Methode openStream(), die einen InputStream zum Weiterverarbeiten liefert.
final InputStream openStream() throws IOException
// öffnet eine Verbindung zum Server und liefert einen InputStream zurück
URLConnection openConnection() throws IOException
// liefert ein URLConnection-Objekt, das die Verbindung zum entfernten
// Server vertritt. openConnection() wird vom Protokoll-Handler immer
// dann aufgerufen, wenn eine neue Verbindung geöffnet wird.
Verweist die URL auf eine Textdatei, dann erweitert man den InputStream zu
einem BufferedReader, da dieser die readLine()-Methode besitzt.
Bsp.: Eine Antwort (HTML-Seite) auf eine Suchfrage
import java.io.*;
import java.net.*;
public class GoogleSucher
{
public static void main(String args[]) throws IOException,
MalformedURLException
{
499
Programmieren in Java
String s = "";
if (args.length == 0) s = "Stephan Meier in Regensburg";
else
for (int i = 0; i < args.length; i++) s += args[i] + " ";
s.trim();
s = "p=" + URLEncoder.encode(s);
// Methoden der Klasse URLEncoder machen aus einem String eine URL
System.out.println(s);
URL u = new URL("http://de.google.yahoo.com/bin/query_de?" + s);
BufferedReader ein = new BufferedReader(
new InputStreamReader(u.openStream()));
String zeile, antwort = null;
while ((zeile = ein.readLine()) != null) antwort += zeile + "\n";
System.out.print(antwort);
}
}
URLs in Applets. Die Klasse Applet hat zwei Methoden mit denen man eine BasisURL erzeugen kann, ohne eine feste Adresse im Programm anzugeben:
- Die Methode getDocumentBase() gibt ein URL-Objekt zurück, welches das Verzeichnis
repräsentiert, das die Webseite mit dem Applet enthält.
- Die Methode getCodeBase() gibt ein URL-Objekt zurück, das den Ordner repräsentiert, in dem
sich die .class-Datei der Hauptklasse des Applet befindet.
Die Applet-Klasse bietet eine Methode mit dem Namen getImage() an, mit der ein
Bild in ein Image-Objekt geladen werden kann. Es gibt zwei Möglichkeiten, diese
Methode zu verwenden:
- Die Methode getImage(), aufgerufen mit einem Argument (ein Objekt vom Typ URL), lädt das Bild
mit dieser URL
- Die Methode getImage(), aufgerufen mit zwei Argumenten (der Basis-URL und einem String, der
den relativen Pfad oder Dateinamen des aktuellen Bilds ausgibt).
Bsp.:
import java.applet.Applet;
import java.awt.*;
import java.net.*;
public class AppletURL extends Applet
{
Image bild;
public void init()
{
URL u1 = getDocumentBase(); System.out.println(u1);
try { URL u2 = new URL(u1,"images/scratch1.gif");
System.out.println(u2);
bild = getImage(getCodeBase(),"images/B04240900.jpg");
}
catch (MalformedURLException e) { System.out.println(e); }
}
public void paint(Graphics schirm)
{
int iBreite = bild.getWidth(this); int iHoehe = bild.getHeight(this);
schirm.drawImage(bild,10,10,iBreite/4,iHoehe/4,this);
}
}
500
Programmieren in Java
URL
<< Konstruktoren >>
public URL(String url) throws MalformedURLException
public URL(String protokoll, String host, String pfad) throws MalformedURLException
public URL(String protololl, String host, int portnummer, String pfad)
throws MalformedURLException
// erzeugt ein URL-Objekt mit gegebenen Protokoll, Hostnamen, Portnummer und Datei
// Ist die Portnummer –1, so wird der Standard-Port verwendet, z:B für WWW Port 80
<< Methoden >>
public final Object getContent() throws IOException
//
public final InputStream openStream() throws IOException
// öffnet eine Verbindung zum Server und liefert einen Inputstream zurück. Die Me// thode ist eine Ankürzung für openConnection.getInputStream()
public URLConnection openConnection() throws IOException
// liefert ein URLConnection-Objekt, das die Verbindung zum entfernten Objekt
// vertritt.. openConnection() wird vom Protokoll-Handler immer dann aufgerufen,
// wenn eine neue Verbindung geöffnet wird.
public String getProtocol()
// liefert das Protokoll der URL
public String getHost()
// liefert den Hostnamen der URL, falls das möglich ist.
// Für das Protokoll „file“ ist das ein leerer String
public int getPort()
// liefert die Portnummer. Ist sie nicht gesetzt, liefert die Methode eine –1.
public String getFile()
// gibt den Dateinamen der URL zurück
Abb.: Die Klasse java.net.URL
URL toURL() throws MalformedURLException
liefert ein URL-Objekt zu einem File-Objekt. Es muß ein File-Objekt erzeugt sein,
anschließend erzeugt toURL() ein URL-Objekt (mit dem Protokoll "file" und den
absoluten Pfadangaben zur Datei bzw. zum Verzeichnis).
501
Programmieren in Java
9.1.3 Die Klasse URLConnection und abgeleitete Klassen
Eine URL-Connection erhält man durch Aufruf der Methode openConnection()
eines URL-Objekts. Mit diesem Objekt lassen sich Inhalte der Ressource lesen sowie
Informationen über die Art des Objekts ermitteln. Je nach Art des Protokolls wird die
abstrakte Klasse URLConnection durch verschieden konkrete Klassen
implementiert,
z.B.
HttpURLConnection,
AppletResourceConnection,
FileURLConnection oder FtpURLConnection. Die Subklassen implementieren
die Protokolle, mit denen die Verbindung zum Inhalt aufgebaut wird. Subklassen
bedienen sich dabei der Klasse URLStreamHandler mit den der eigentliche Inhalt
ausgelesen wird.
URLConnection
{ abstract }
<< Konstruktor >>
protected URLConnection(URL url)
<< Methoden >>
public Object getContent() throws IOException, UnkownServiceException
// liefert den Inhalt, auf den die URL verweist.
Der Inhalt eines URL-Objekts lässt sich mit getContent() vom Server beschaffen.
Für
HTML-Dateien
liefert
getContent()
ein
Objekt
vom
Typ
sun.net.www.MeteredStream,
für
normale
Textdateien
ein
sun.www.content.txt.PlainTextInputStream-Objekt. Für Texte und HTMLSeiten kann man mit Hilfe eines InputStream-Objekts die Datei (zeilenweise) lesen.
Mit
Object o = u.getContent();
System.out.println("Ich erhielt " + o.getClass().getName());
kann festgestellt werden, was für ein Handler-Objekt eine URL-Klasse für den
Datenstrom einsetzt. getContent() erkennt anhand der Endung bzw. den ersten
Bytes den Typ der Datei. Dann konvertiert der Content Handler die Bytes seines
Datenstroms in ein Java Objekt.
502
Programmieren in Java
9.2 Kommunikation in Netzwerken
Das Common GatewayInterface (CGI)
CGI ist die Beschreibung einer Schnittstelle über die externe Programme mit
Informations-Servern, meistens Web-Servern, Daten austauschen. CGI-Programme
können in allen möglichen Programmiersprachen verfaßt sein. Häufig sind es Shelloder Perl-Skripte.
Die CGI-Programme werden von einem Browser durch eine URL angesprochen. Der
Browser baut eine Verbindung zum Server auf. Dieser erkennt an Hand des Pfads
der URL, ob es sich um eine normale Web-Seite oder um ein Skript handelt. Falls es
ein Skript ist, dann führt der Server das Skript aus, das eine HTML-Datei erzeugt.
Diese wird übertragen und im Browser dargestellt.
Sockets357
Als Socket bezeichnet man ein streambasierte Programmierschnittstelle zur
Kommunikation zweier Rechner in einem TCP/IP-Netz. Das Übertragen von Daten
über eine Socket-Verbindung besteht aus:
1. Verbindungsaufbau
2. Lesen bzw. Schreiben der Daten
3. Verbindungsabbau
Werden Rechner verbunden, so implementiert jeder Rechner einen Socket.
Derjenige, der Daten empfängt (Client), öffnet eine Socket-Verbindung zum Horchen
und derjenige, der sendet, öffnet eine Verbindung zum Senden (Server). Damit der
Empfänger den Sender auch hören kann, muß dieser durch eine eindeutige Adresse
als Server ausgemacht werden. Er bekommt also eine IP-Adresse im Netz und eine
ebenso eindeutige Port-Adresse. Der Port ist so etwas wie eine Zimmernummer: Die
Adresse bleibt dieselbe, aber in jedem Zimmer sitzt einer und macht seine Aufgaben.
Für jeden Dienst (Service), den ein Server bereitstellt, gibt es einen Port358. Eine
Port-Nummer ist eine 16-Bit-Zahl und in die Gruppen System und Benutzer eingeteilt.
Sockets aus der Sicht einer Client- bzw. Server-Anwendung werden durch die beiden
Klassen Socket und ServerSocket repräsentiert.
Bsp359.:
1. Ein einfacher Server, der alles „nachplappert“, was der Client sendet.
2. Ein einfacher Client, der Zeilen zum Server sendet und einliest
357
Sockets wurden zu Beginn der achtziger Jahre für die Programmiersprache C entwickelt.
Diese Adressen werden von der IANA (Internet Assigned Number Authority) vergeben. Die IANA ist der
zentrale Koordinator für die IP-Adressen, Domain Namen, MIME Typen und viele andere Parameter, u.a.a.
Portnummern.
359 pr92100
358
503