Programmieren in Java Prof. Jürgen Sauer Programmieren in Java Skriptum zur Vorlesung im WS 2001/2002 1 Programmieren in Java 2 Programmieren in Java Inhaltsverzeichnis 0. Einführung in die Java-Programmierung 0.1 Ziel der Vorlesung "Programmieren" 0.2 Basis der Vorlesung: "Die Java-Maschine" 0.3 0.3.1 0.3.2 Der erste Versuch Das erste Programm Bestandteile eines Programms 1. Einführung in die Java-Programmierung 1.1 Übersicht zur Entwicklung der Programmiersprache Java 1.2 Was ist Java? 1.3 Einstieg in die Java-Programmierung 1.3.1 Die Software für die Java-Programmierung 1.3.2 Applets und Anwendungen 1.3.2.1 Entwickeln von Java-Anwendungen 1.3.2.2 Entwickeln von Java-Applets 1.4 Die Objektorientierung von Java 1.4.1 Grundlegende Konzepte 1.4.1.1 Zustand und Verhalten von Objekten, Klassen, Instanz- und Klassenvariable bzw. methoden 1.4.1.2 Superklassen und Subklassen, Vererbung und Klassenhierarchie 1.4.1.3 Referenzen und Referenztypen 1.4.1.4 Konvertieren von Objekten und Primitivtypen 1.4.1.5 Manipulieren von Objekten Vergleichen von Objekten Kopieren von Objekten 1.4.1.6 Lokale, innere und anonyme Klassen 1.4.1.7 Schnittstellen und anonyme Klassen 1.4.1.8 Polymorphismus und Binden 1.4.2 Klassen des Pakets java.lang 1.4.3 Ausnahmen und Ausnahmenbehandlung Ausnahmen Globales Exception Handling Die Fehlerklassen von Java Auslösen von Ausnahmen 1.4.4 Einfache Ein- und Ausgaben 2. Hauptbestandteile der Sprache 2.1 Token 3 Programmieren in Java 2.1.1 2.1.2 2.1.3 Schlüsselworte Bezeichner und Namenskonventionen Literale 1. Ganzzahlige Literale 2. Gleitpunktliterale 3. Boolesche Literale 4. Zeichenliterale 5. Zeichenkettenliterale 2.1.4 Trennzeichen 2.1.5 Operatoren 2.1.6 Kommentare, eingebettete Dokumentation 2.2 Typen 2.2.1 Primitive Datentypen 2.2.2 Operationen mit primitiven Datentypen 2.2.3 Datenfelder (Arrays) 2.2.3.1 Deklarieren, Erstellen von Array-Objekten 2.2.3.2 Zugriff auf Datenfeld-Elemente 2.2.3.3 Anwendungen mit eindimensionalen Feldern 2.2.3.4 Multidimensionale Datenfelder 2.2.3.5 Die Klasse Arrays 2.3 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 Ausdrücke Arithmetische Ausdrücke Bewertung von Ausdrücken Typkonvertierungen Vergleichsoperatoren Logische Ausdrücke 2.4 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.4.10 2.4.11 Anweisungen Blöcke und Anweisungen Leere Anweisungen Benannte Anweisungen Deklarationen Ausdrucksanweisungen Auswahlanweisungen Wiederholungsanweisungen Sprunganweisungen Synchronisationsanweisungen Schutzanweisungen Unerreichbare Anweisungen 2.5 Klassen 2.6 Methoden 2.6.1 Die Deklaration 2.6.2 Die Zugriffsspezifizierung 2.6.3 Die Methodenmodifizierer 2.6.4 Rückgabewerte von Methoden 2.6.5 Methodenname und Parameterliste 4 Programmieren in Java 2.6.6 Rekursion 2.6.6.1 Rekursive Funktionen 2.6.6.2 Rekursion und Iteration 3. 3.1 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 Grafische Benutzeroberflächen und Applets Ereignisbehandlung unter grafischen Benutzeroberflächen (Graphical User Interface) Gestaltung von grafischen Benutzeroberflächen mit Hilfe der AWT-Klassen Ereignisbehandlung unter grafischen Benutzeroberflächen Anwendung lokaler Klassen für die Ereignisbehandlung Externe- und interne Darstellung numerischer Werte Low-Level-Events 3.2 Kommunikation des Anwenders mit Programmen in GUI über Dialoge bzw. Menüs mit vordefinierten Dialogelementen 3.2.1 Dialoge 3.2.2 Menüs 3.3 Grundlagen der Applet-Erstellung 3.3.1 HTML-Grundlagen 3.3.2 Die interne Arbeitsweise eines Applets 3.3.3 „Multithreading“-fähige Applets 3.3.4 Animation in Applets 3.3.5 Das Laden und Anzeigen von Bildern 3.3.6 Das Laden und Anzeigen von Sound 3.3.7 Die Klasse JApplet 3.3.8 Das Interface AppletContext 3.4 Grafische Benutzeroberflächen mit Observable-Observer Die Klasse Observable Die Schnittstelle Observable Observable-Observer: Kopplung von Anwendung und GUI 3.5 Swing 3.6 JavaBeans 3.6.1 Wiederverwendbare Softwarekomponenten 3.6.2 Komponenten für JavaBeans 3.6.3 Enterprise JavaBeans 4. Grafik und Bildverarbeitung 4.1 4.1.1 4.1.2 4.1.3 Allgemeine Zeichenvorgänge Punkte, Linien, Kreise, Bögen, Polygone Farbangaben Textausgabe über den Zeichenmodus Die Klasse Font Die Klasse FontMetrics 4.1.4 Größenangaben 5 Programmieren in Java 4.1.5 Clipping-Operationen 4.2 Bildverarbeitung 4.2.1 Klassen zur Bildverarbeitung 4.2.2 Bildproduzenten und Bildkonsumenten 4.2.3 Bildfilter 4.2.4 Kopieren von Speicher in ein Bild und von Bildern in einen Speicher 4.2.4.1 Kopieren von Speicher in ein Bild 4.2.4.3 Kopieren von Bildern in einen Speicher 4.3 4.3.1 4.3.2 4.3.3 Grafikoperationen mit Java 2D Das zeichnen unter Java 2D API Eigenschaften geometrischer Objekte Affine Transformation 5. AWT 5.1 Bestandteile des AWT 5.2 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 Die AWT-Komponenten Schaltflächen (Buttons) Labels Kontrollkästchen und Optionsfelder Auswahlmenüs Listenfelder Textbereiche und Textfelder Schieber und Bildlaufleisten Zeichenbereiche 5.3 5.3.1 5.3.2 5.3.3 5.3.4 Container Panels Frames Menüs Dialoge 5.4 5.4.1 5.4.2 Die Layout-Manager Layout-Regeln Die einzelnen Layout-Manager 5.5 5.5.1 5.5.2 Die Event-Modelle 1.0 und 1.1 Der AWT-Handler 1.0 Das Event-Handling 1.1 5.6 Benutzerschnittstellen mit Swing 6. Utilities 6.1 Collections 6 Programmieren in Java 6.1.1 6.1.2 6.1.3 6.1.4 Die Klasse Vector Die Klasse Stack Die Klasse Hashtabelle Die Klasse Properties 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 Java 2 Collections Die Interfaces Collection, Iterator, Comperator Die Collections des Typs List Die Collections des Typs Set Die Collections des Typs Map Die Klasse Collection 6.3 Die Klasse StringTokenizer 6.4 Die Klasse Random 6.5 Datumsberechnungen 6.5.1 Die Klassen Date, Calendar, GregorianCalendar 6.5.2 Formatieren der Datumsangaben 7. Ein-/ Ausgabe 7.1 Die abstrakten Klassen InputStream und OutputStream 7.1.1 InputStream 7.1.2 OutputStream 7.2 Gefilterte Ströme 7.3 Die Klasse File 7.4 Die Klasse RandomAccessFile 7.5 Spezielle nützliche Ströme 7.6 7.6.1 7.6.2 7.6.3 7.6.4 Java 1.1 IO-Ströme Grundlagen Die abstrakte Klasse Reader und ihre Ableitungen Die abstrakte Klasse Writer und ihre Ableitungen Demonstrationsprogramm zur Ein-/Ausgabe (ab Java Version 1.1) 8. Serialisierung 8.1 8.1.1 8.1.2 8.1.3 Grundlagen Das Interface Serilizable Die Klasse ObjectOutputStream Die Klasse ObjectInputStream 8.2 Tiefe Objektkopien 7 Programmieren in Java 9. Netzwerkprogrammierung 9.1 9.1.1 9.1.2 9.1.3 Adressen, Ressourcen und URLs Die Klasse InetAdress Die Klasse URL Die Klasse URLConnection und abgeleitete Klassen 9.2 Kommunikation in Netzwerken 8 Programmieren in Java Literaturverzeichnis Alexander Newman u.a.: JAVA, Referenz und Anwendungen, 1996, Que, Haar bei München 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 ... 9 Programmieren in Java 10 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äzis 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 JavaSoft 1 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 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“. JavaProgramm Daten Eingabe (Tastatur) Externer Speicher Speicher Externer Java- Compiler System, QuellproSystemprogramme Laufzeit- Java - Java-Maschine gramme Javasystem Java- BytecodeDateien Ausgabe (Bildschirm) 12 Programmieren in Java 13 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 ... Die Methode ist für andere Klassen und Objekte verfügbar static ... Es handelt sich um eine Klassenmethode main() ... 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: 14 Programmieren in Java /* 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."); } } 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), Gleipunktzahlen (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 15 Programmieren in Java 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); } Vorrang 1 Operator ++ -+, ~ ! 2 3 4 (type) *, /, % +, + << >> >>> 5 <, <= >, >= 6 instanceof == != == != 7 8 9 10 11 12 13 & & ^ ^ | | && || ?= = *=, /=, +=, -= <<=, >>=, >>>=, &=, ^=, |= Operandentyp Arithmetisch Assoziation Operation R Pre- oder Post-Inkrement (unär) Arithmetisch R Preoder PostDekrement (unär) Arithmetisch R Unäres Plus, unäres Minus Integral R Bitweises Komplement (unär) boolean R Logisches Komplement (unär) irgendein R cast arithmetisch L Multiplikation, Division, Rest Arithmetisch L Addition, Subtraktion String L Verkettung Integral L Links-Shift Integral L Rechts-Shift mit Vorzeichen Integral L Rechts-Shift mit NullenNachziehen Arithmetisch L Kleiner als, kleiner als oder gleich Arithmetisch L Größer als, größer als oder gleich Objekt, Typ L Objekt?, Instanz? Primitiver Typ L Gleich (identische Werte) Primitiver Typ L Ungleich (verschiedene Werte) Objekt L Gleich (Referenz auf das gleiche Objekt) Objekt L Ungleich (Referenz auf verschiedene Objekte) Integral L Bitweises Und boolean L Logisches Und Integral L Bitweises Oder boolean L Logisches Oder (exklusiv) Integral L Bitweises Oder boolean L Logisches Oder (inklusiv) boolean L Konditionelles Und boolean L Konditionelles Oder boolean, irgendein, R Konditioneller (ternärer) irgendein Operator Variable, irgendein R Zuweisung Variable, irgendein R Zuweisung mit Operation 16 Programmieren in Java Abb. 0.3-1: Java-Operatoren 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“ 17 Programmieren in Java 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);). 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 Erstes.Programm.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. 18 Programmieren in Java Konsole: Arbeitsspeicher: Externspeicher javac ErstesProgramm.java ErstesProgramm.java javac ErstesProgramm.class java ErstesProgramm ErstesProgramm.class Java (Interpreter) "Das erste Programm der Vorlesung Programmieren in Java" Abb. 0.3-2: Ablaufplan ErstesProgramm 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 Ausdruck 4 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. 3 vgl. 2.4 vgl. 2.4.5 5 vgl. 2.4.6 6 vgl. 2.4.1 7 vgl. 2.6 4 19 Programmieren in Java - 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, saß 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. 8 vgl. 2.5 CallBack-Methoden sind von der abgeleiteteten Klasse zur Verfügung gestellte Methoden, die vom Browser bzw. Appletviewer aufgerufen werden 9 20 Programmieren in Java 21 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 Microsystems 10 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 Java 14 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 fogte 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 Elektronk (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 makierte 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 Loader (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 22 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 Entwicklungstolls 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 Hochleistungs-geschwindigkeits-Anwendungen 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 Klassem sind Teil des JDK, das darüber hinaus noch Klassen besitzt, die Funtionen 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. 23 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: 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 Adreßanfragen 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 24 Programmieren in Java - 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 Speicherbereich des Rechners22. Hinzu kommt auch noch die anschließende Sicherheitsüberprüfung durch den „Linker“. Der Linker ist ein Teil der Laufzeitumgebung, das die im System eingehenden Daten überprüft. - 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). Die komplette Java-Bytecode kann auf jedem Prozessor ausgeführt werden, der einen javafähigen Browser (bspw. die virtuelle Maschine von Java 24) 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. 22 Ungeprüfte Zugriffe auf Speicherbereiche des Rechners ermöglicht C/C++ (Zeigerarithmetik, implizite Deklaration) 23 Verschiedene Sicherheitslücken wurden aufgedeckt und wurden inzwischen beseitigt. 24 Die virtuelle Maschine wird auch als Java Interpreter oder Java Environment (Java-Laufzeitumgebung) bezeichnet 25 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 Internet 25 erhältlich. Nach der Installation hat das Java-Verzeichnis26 folgenden Inhalt27: Abb. 1.3-1: Inhalt des Java-Verzeichnisses Innerhalb des Java-Verzeichnisses folgendem Inhalt: Verzeichnis \bin \lib \include \demo \src befinden sich Unterverzeichnisse28 mit 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älz 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. 25 Das jeweils aktuelle JDK (aber auch ältere Versionen) können von den Sun-Microsytem-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 SunFTP-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 26 Programmieren in Java 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-95Anwender wird das Verzeichnis mit den Werkzeugen in der Pfadangabe der Datei „autoexec.bat“ eingetragen, z.B.: PATH c:\;C:\ORAWIN95\BIN;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% 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. 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. 27 Programmieren in Java 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-Dateii an einem anderen Ort gespeichert werden. 28 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 Quellcode 33 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 Befehlszeileneben 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. Initialiserung 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 Ausdrucke, 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]). ArrayIndizes 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 29 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“. 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“ bekannt. Man vergleicht dabei zunächst den ersten unter args[0] abgelegten Namen gegen alle weiteren im Datenfeld args abgelegten Namen. args “Juergen“ “Hubert“ “Josef“ “Liesel“ “Christian“ [0] [1] [2] [3] [4] Nach 4 Vergleichen hat das Datenfeld args folgende Gestalt angenommen: args [0] [1] [2] [3] [4] “Christian“ “Juergen“ “Josef“ “Liesel“ “Hubert“ 30 Programmieren in Java 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 “Christian“ “Hubert“ “Juergen“ “Liesel“ “Josef“ [0] [1] [2] [3] [4] „Christian“ und „Hubert“ sind jetzt richtig eingeordnet. Num 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 “Christian“ “Hubert“ “Josef“ “Liesel“ “Juergen“ [0] [1] [2] [3] [4] Der nächste Durchgang führt dann zum sortierten Datenfeld: args [0] [1] [2] [3] [4] “Christian“ “Hubert“ “Josef“ “Juergen“ “Liesel“ 2) Der soeben beschrieben 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++) { 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. 31 Programmieren in Java Die Vergleichsbedingung wird durch das Schlüsselwort if erzeugt: if (Ausdruck) { // Anweisung(en) } else { // Anweisung(en) } Eine if-Bedingung verwendet einen booeleschen 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 bestimmete Anweisung bzw. Anweisungen ausgeführt werden, wenn der boolesche 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 Programm mit Parametern führt zu: „Herzlich Willkommen Christian Hubert Josef Juergen Liesel“. 32 Programmieren in Java 4. Aufgabe: Die Argumentenliste args soll auf folgende Weise angezeigt werden: 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 Variable 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. DekrementOperationen 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. 33 Programmieren in Java 3) Der Quelltext zur Programmlösung ist dann: public class FloatDemo { public static void main(String args[]) { // 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); } } 34 Programmieren in Java 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 = 23.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. 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: 35 Programmieren in Java 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 HTMLDokument34 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 Dateiaufbereites (Editor): /* 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. 34 eine entsprechende Referenz innerhalb einer HTML-Seite mit einem speziellen Tag, dem <APPLET>-Tag erledigt die Einbettung in den Browser 36 Programmieren in Java Die folgende Abbildung zeigt die Modellierung35 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 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 35 Die Modellierung erfolgt nach den Regeln der Unified Modelling Language (UML). Die UML ist eine grafische, standardisierte Sprache zum Spezifizieren, Konstruieren, Visualisieren und Dokumentieren. 37 Programmieren in Java Linien36, von Ellipsen bzw. Kreisen37, von Rechtecken38 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 Abb. 1.3-4: Vererbungshierarchie von WillkommenApplet 36 public void drawLine(int x1, int y1, int x2, int y2); (x1,y1) bestimmt den Anfangspunkt, (x2,y2) bestimmt den Endpunkt der Linie. 37 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 38 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. 38 Programmieren in Java 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 Paketstruktur39 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. Die Attribute WIDTH und HEIGHT dienen zum Bestimmen der Größe des Applets. 39 Pakete werden in der UML als Akten mit Reitern dargestellt. Die gestrichelten Pfeile repräsentieren die Abhängigkeiten zwischen den Paketen. 39 Programmieren in Java 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 zugewiesen. Das neue Objekt wird anschließend der Instanzvariablen f zugewiesen und ist so der Methode paint() zugänglich: 40 Programmieren in Java 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.*;“40 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: //** ... */ 40 Diese Anweisung stellt alle Klassen des Pakets java.awt zur Verfügung 41 Programmieren in Java Grundsätzlich steht der Dokumentationskommentar vor Klassen, (Interfaces, Konstruktoren,) Methoden, z.B.41: /* 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 mu&szlig;. *** @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 Sun 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.: 41 PR13210 42 Programmieren in Java Abb.: 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. 43 Programmieren in Java import java.awt.*; import java.applet.*; 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 44 Programmieren in Java 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 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) { // .. } } 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. 45 Programmieren in Java 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 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 (Instanziierung42) 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 Aufruf des new-Operators erfordert43, erfolgt die Rückgabe von nicht mehr benötigtem Speicher automatisch44. 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 42 Eine Instanz einer Klasse ist ein (tatsächliches) Objekt (konkrete Darstellung) Ausnahmen: String-, Array-Literale 44 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 43 46 Programmieren in Java 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 Regeln45 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)46 Rückgabetypausdruck: Jede Methode ist typisiert. Der Typ einer Methode bestimt den Typ des Rückgabewerts. Dieser kann von einem beliebigen primitiven Typ47, einem Objekttyp oder vom Typ void sein. Methoden vom Typ void haben keinen Rückgabewert und dürfen nicht in Ausdrücken verwendet werden. Hat eine Methode einen Rückgabewert, dann kann sie mit der „return“Anweisung48 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. 45 vgl. 2.1.2 Bezeichner und Namenskonventionen vgl. 2.6.2 47 vgl. 2.2 48 vgl. 2.6.4 46 47 Programmieren in Java 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; } 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 JavaAnwendung49: 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 49 vgl. PR14101 48 Programmieren in Java 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()); } } 49 Programmieren in Java Einfache Typen51 und Referenztypen Einfache Typen 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 Typen52 sind in Java erst einmal keine Objekte. Java hat für alle einfachen Typen sog. Wrapper-Klassen 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. Java besitzt acht primitive Datentypen53: - 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 - 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 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‘); 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. 51 vgl. 2.2.1 vgl. 2.2.1 53 vgl. 2.2.1 52 50 Programmieren in Java 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.54: 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 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.: 54 PR14102 51 Programmieren in Java public Rechentafel() { this(0,1); } „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 ü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. 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 52 Programmieren in Java 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) { 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; } 53 Programmieren in Java 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. Klassenvariable und Klassenmethoden Die Klasse „Rechentafel"55 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; 55 PR14103 54 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“. 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 Konstanten56 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. 56 Das bedeutet: das Schlüsselwort final ist bei lokalen Variablen nicht erlaubt 55 Programmieren in Java Konstante bestehen aus einer Variablendeklaration, der das Schlüsselwort final vorangestellt ist, und ein Anfangswert zugewiesen ist, z.B.: final int LINKS = 0; Ü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. 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.57: 57 PR14103 56 Programmieren in Java public String toString() { return " Rechenobjekt# " + nummer + " Erster Operand: " + ersterOperand + " Zweiter Operand: " + zweiterOperand; } } 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. Dadurch wird bewirkt: Die abgeleitete Klasse erbt alle Eigenschaften der Basisklasse, d.h. alle Variablen und alle Methoden. 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 57 Programmieren in Java 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, 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" Objekte 58 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. 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; public double holeR(); public void setzeR(double r); public double umfang(); public double flaeche(); public String toString(); private double breite; private double hoehe; 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 58 vgl. PR14131 58 Programmieren in Java 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(); 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); } 59 Programmieren in Java // 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); } 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; } 60 Programmieren in Java 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; } } 61 Programmieren in Java 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); 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()); } } 62 Klassenhierarchie auf Programmieren in Java 1.4.1.3 Referenzen und Referenztypen 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 „==“59 bestätigt: Es handelt sich jetzt (nach der Zuweisung) um das gleiche Objekt, d.h.: die gleiche Referenz. 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. 1.4.1.4 Konvertieren von Objekten und Primitivtypen 59 Der Operator „==“ überprüft die Gleichheit der Referenzen. 63 Programmieren in Java „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. „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 64 Programmieren in Java 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“-Klasse60. 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)); System.out.println("a.equals(\"test\") " + a.equals("test")); System.out.println("c.equals(\"test\") " + c.equals("test")); } } // // // // // true false false true false // // // // // true true true true 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-Objekten61. public class Rechentafel implements Cloneable { // Instanzvariable private int ersterOperand = 0; 60 61 PR14110 PR14202 65 Programmieren in Java 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() { return ersterOperand + zweiterOperand; } public int subtrahiere() { return ersterOperand - zweiterOperand; } 66 Programmieren in Java 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; } } 67 Programmieren in Java 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"); 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. 68 Programmieren in Java 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()); } 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. 69 Programmieren in Java 1.4.1.6 Innere (lokale) und anonyme Klassen Lokale Klassen Seit JDK 1.1 kann man innerhalb einer bestehenden Klasse z.B. X eine neue Klasse z.B. Y definieren62. 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. 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. Dazu werden sie bei der Übergabe eines Objekts an eine Methode oder als Rückgabewert einer Methode innerhalb einer einzigen Anweisung definiert und instanziert. 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. 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. 62 Im JDK wird das Konzept als Inner Classes bezeichnet 70 Programmieren in Java 1.4.1.7 Schnittstellen und Pakete 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) definieren63. 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 extend meineSchnittstelle { // Abstrakte Methoden } "Interfaces" können benutzt werden, indem sie in einer Klasse implementiert werden, z.B.: public class MeineKlasse extend 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 + Metoden aus meinerSchnittstelle1 und meinerSchnittstelle2 */ } 63 Angaben zur Implementierung sind nicht möglich 71 Programmieren in Java 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 Implementierung eines Interface wird durch das Schlüsselwort „implements“ bei der Klassendefinition angezeigt. Bsp.64: Das folgende Interface65 mit dem Namen „Vergleich“ umfaßt Deklarationen für Vergleichsfunktionen. interface Vergleich { boolean kleinerAls(Object ls, Object rs); boolean kleinerAlsoderGleich(Object ls, Object rs); } Die Vergleichsfunktionen herangezogen werden: sollen zum Vergleichen aller möglichen Objekte 1. Verwendung des Interface „Vergleich“ zum Sortieren von Zeichenketten public class StringSortTest { static class StringVergleich implements Vergleich { public boolean kleinerAls(Object l, Object r) { return((String)l).toLowerCase().compareTo(((String)r).toLowerCase()) < 0; } public boolean kleinerAlsoderGleich(Object l, Object r) { return((String)l).toLowerCase().compareTo(((String)r).toLowerCase()) <= 0; } } // static StringVergleich sVergl = new StringVergleich(); public static void main(String args[]) { String x[] = { "Juergen","Christian","Bernd","Werner","Uwe", "Erich","Kurt","Karl","Emil","Ernst" }; Object o[] = new Object[x.length]; for (int i = 0; i < x.length; i++) { o[i] = (Object) x[i]; } // Ausgabe des noch nicht sortierten x System.out.println("Vor dem Sortieren:"); 64 65 PR14171 gespeichert in: Vergleich.java 72 Programmieren in Java for (int i = 0; i < x.length; i++) { System.out.println("x["+i+"] = " + x[i]); } // Aufruf der Sortieroutine Sort s = new Sort(new StringVergleich()); s.sort(o,x.length-1); // Gib das sortierte Feld x aus for (int i = 0; i < x.length; i++) { x[i] = (String) o[i]; } System.out.println(); System.out.println("Nach dem Sortieren:"); for (int i = 0; i < x.length; i++) { System.out.println("x["+i+"] = " + x[i]); } } } 2. Verwendung des Interface „Vergleich“ zum Sortieren von Zahlen public class ZahlenSortTest { static class ZahlenVergleich implements Vergleich { public boolean kleinerAls(Object l, Object r) { return (((Integer) l).intValue() < ((Integer) r).intValue()); } public boolean kleinerAlsoderGleich(Object l, Object r) { return (((Integer) l).intValue() <= ((Integer) r).intValue()); } } public static void main(String args[]) { int x[] = { 50, 70, 80, 10, 20, 30, 1, 2, 3, 99, 12, 11, 13}; Object o[] = new Object[x.length]; for (int i = 0; i < x.length; i++) { o[i] = new Integer(x[i]); } // Ausgabe des noch nicht sortierten x System.out.println("Vor dem Sortieren:"); for (int i = 0; i < x.length; i++) { System.out.println("x["+i+"] = " + x[i]); } // Aufruf der Sortieroutine Sort s = new Sort(new ZahlenVergleich()); s.sort(o,x.length-1); 73 Programmieren in Java // Gib das sortierte Feld x aus for (int i = 0; i < x.length; i++) { x[i] = ((Integer) o[i]).intValue();; } System.out.println(); System.out.println("Nach dem Sortieren:"); for (int i = 0; i < x.length; i++) { System.out.println("x["+i+"] = " + x[i]); } } } Die unter 1. und 2. angegebenen Klassen benutzen die Klasse Sort, die die eigentliche Funktion für das Sortieren enthält: public class Sort { private Vergleich vergleich; public Sort(Vergleich vergl) { vergleich = vergl; } public void sort(Object x[],int n) { bubbleSort(x,n); } public void bubbleSort(Object x[], int n) { for (int i = 0; i < x.length; i++) { for (int j = i + 1; j < x.length; j++) { if (!vergleich.kleinerAls(x[i],x[j])) { Object temp = x[i]; x[i] = x[j]; x[j] = temp; } } } } } 74 Programmieren in Java Konstanten. Neben abstrakten Methoden können Interfaces auch Konstanten (Variablen mit dem Attributen static und final) enthalten. Wenn eine Klasse ein solches Interface implementiert, erbt es gleichzeitig auch alle seine Konstanten. Ein Interface kann ausschließlich Konstanten enthalten. Pakete Definition 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 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-Klassenbibliothek66 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 Class String StringBuffer Thread ThreadGroup Throwable System Runtime Process Math Number Character Boolean 66 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. Für jede in java definierte Klasse gibt es eine Instanz von Class, die diese Klasse beschreibt Enthält Methoden zur Manipulation von Java-Zeichenketten Dient zum Erstellen von Java-Zeichenketten Stellt einen Ausführungs-Thread in einem Java-Programm dar. Jedes Programm kann mehrere Threads laufen lassen Ermöglicht die Verknüpfung von Threads untereinander. Einige Thread-Operationen können nur von Threads aus der gleichen ThreadGroup ausgeführt werden. 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. Stellt spezielle Utilities auf Systemebene zur Verfügung Enthält eine Vielzahl gleicher Funktionen wie System, behandelt aber auch das Laufen externer Programme Stellt ein externes Programm dar, das von einem Runtime-Objekt gestartet wurde. Stellt eine Reihe mathematischer Funktionen zur verfügung Ist die Basisklasse für Double,Float, Integer und Long (Objeckt-Wrapper) Ist ein Objekt-Wrapper für den datentyp char und enthält eine Reihe nützlicher zeichenorientierter Operationen Ist ein Objekt-Wrapper für den Datentyp boolean Die Klassenbibliothek des JDK befindet sich in einem Paket mit dem namen „java“. 75 Programmieren in Java ClassLoader SecurityManager Compiler Ermöglicht der Laufzeit-Umgebung von Java neue Klassen hinzuzufügen Legt die Sicherheits-Restriktionen der aktuellen Laufzeitumgebung fest. Viele JavaKlassen benutzen den Security-Manager zur Sicherstellung, daß eine Operation auch tatsächlich genehmigt ist. 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 - java.util - 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 erweitert67: Paket java.applet java.awt java.awt.datatranfer java.awt.event java.awt.image java.beans java.io java.lang java.lang.reflect 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 java.math java.net java.rmi 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) java.rmi.dgc java.rmi.registry java.rmi.server java.security java.security.aci java.security.interfaces 67 Zusätzlich 15 weitere Packages, etwa 500 Klassen und Schnittstellen 76 Programmieren in Java java.sql java.util java.util.zip Datenbankzugriff (JDBC) Diverse Utilities, Datenstrukturen JAR-Files, Kompression, Prüfsummen Abb.: Klassenbibliothek der Java-Version 1.1 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: - 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 import-Anweisung eingebunden, z.B.: import java util.*; ...... 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.*"68 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. 68 type import on demand, d.h.: Die Klasse wird erst dann in dem angegebenen Paket gesucht, wenn das Programm sie wirklich benötigt. 77 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.69: 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()); 69 Vgl. PR14181 78 Programmieren in Java /* (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 RechteckReferenz 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 Verbidung übernimmt das Laufzeitprogramm der Programmiersprache. Java unterstützt das Konzept des „Late Binding“. 79 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 ob) 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 Hashcode70: public native 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 71. Klonen von Objekten: protected native 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 native Class getClass() Benachrichtungsmechanismus. Hierfür gibt es die Methoden: public final native notify() bzw. public final native void notifyAll() und public final void wait() throws InterruptedException bzw. public final native void wait(long timeout) throws InterruptedException. Threads72 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 Dateinen 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. 1.4.2.2 Die Class-Klasse 70 Methoden, die mit dem Schlüsselwort native deklariert sind, sind nicht in Java (meistens iin C++) geschrieben. Normalerweise werden soche Methoden geschrieben, um etwas zu tun, was Java noch nicht kann: Zusammenarbeit mit einer besonderen Computer-Hardware oder einem Betriebssystem oder zur Leistungssteigerung 71 Die Klasse java.util.Hashtable bietet eine soche Datenstruktur an. 72 vgl. 1.4.2.4 80 Programmieren in Java 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 einegebunden 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 Field[] getFields() throws SecurityException // liefert ein Feld mit Field-Objekten73. Method[] getmethods() throws SecurityException // gibt ein Array von Method-Objekten74 zurück, die alle öffentlichen Methoden der Klasse/ // Schnittstelle beschreiben ... Abb.: Die Klasse Class 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 bezeichnet75. Das folgende Programmstück76 testet Objekte systematisch durch: 73 Objekten der Klasse java.lang.reflect.Field Objekte der Klasse java.lang.reflect.Method 75 Die Klasse java.lang.Class ist Baisis für Methoden, die Objekte vom Typ Constructor, Method, Field usw. zurückliefern. Die zugehörigen Klassen sind im Package java.lang.reflect definiert. 74 81 Programmieren in Java 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;“. 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. 76 vgl. PR14220 82 Programmieren in Java 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 aout public static Securitymanager getSecurityManager() public static void setSecurityManager(SecurityManager s) public static native long currentTimeMillis() public static Properties getProperties() public static void setProperties(Properties props) public static String getProperty(String key) public static String getenv(Strin name) // deprecated 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. 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(); 83 Programmieren in Java liefert das komplette Properties-Objekt mit den System-Properties. Bsp.77: 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 setError(PrintStream err); kann die Standardein- und Standardausgabe aus dem Programm heraus umgeleitet werden. public static void exit(int status) Mit System.exit 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. 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() 77 PR14231 84 Programmieren in Java 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 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. 85 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 native Thread currentThread() public static native void yield() public static native void sleep(long millis) throws InterruptedException public native synchronized void start() public void run() public final void stop() public final synchronized void stop(Throwable o) public void interrupt() public static boolean interrupted() public boolean isInterrupted() public void destroy() public final native boolean isAlive() public final void suspend() public final void resume() public final void setPriority(int newPriority) public final int getPriority() public String toString() Abb.: Thread-Klassen Methoden der Klasse Thread 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 die Methode run übertragen. start wird nach dem Start beendet, der Aufrufer kann parallel zum neu erzeugten Thread fortfahren. 86 Programmieren in Java Bsp.: Einfacher Thread, der in einer Endlosschleife einen Thread hochzählt. class MeinThread 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 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äuft78,) 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. Weitere Methoden public static void sleep(long millis) sorgt dafüt, daß der aktuelle Prozeß für die (in Millisekunden) angegebene Zeit unterbrochen wird. public static void sleep(long millis, int nanos) Die erzielbare Genauigkeit der Unterbrechungs-Wartezeit wird durch HardwareRestriktionen der Maschine begrent 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. 78 Beim Starten eines Java-Programms wird automatisch ein Thread für die Ausführung des Hauptprogramms angelegt. 87 Programmieren in Java 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 MeinThread1 t = new MeinThread1(); // Start vom erzeugten Thread t.start(); // Impliziter Aufruf der Methode run() try { Thread.sleep(2000); } catch(InterruptedException e) { } t.stop(); } } 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. 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. 88 Programmieren in Java 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 implementieren79. 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. Synchronisation nebenläufiger Prozesse 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 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 79 sogar die Klasse Thread selbst 89 Programmieren in Java 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 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. 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. Der Normalwert wird durch die Konstante Thread.NORM_PRIORITY festgelegt80. 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. 80 im aktuellen JDK 1, 5, 10 90 Programmieren in Java 1.4.2.5 Die Klassen String und StringBuffer Die Klasse String 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) throws StringIndexOutOfBoundsException 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 „StringIndexOutOfBoundsException“ erzeugt String substring(int Liefert den Teilstring, der an Position anfang beginnt und an anfang, int ende) 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 String trim() 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 einObjekt) equals(Object 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. boolean Testet, ob das String-Objekt mit der Zeichenkette s beginnt. startsWith(String s) Ist dies der Fall, dann gibt die Methode „true“ zurück, anderenfalls „false“. boolean endsWith(String s) int compareTo(String s) 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 91 Programmieren in Java die Methode den Rückgabewert 0. int regionMatches(int toffset, String other, int ooffset, int len) Suchen in Zeichenketten int indexOf(String s) int IndexOf(String vonIndex) s, 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. int 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() 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. String toUpperCase() 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. String replace(char Diese Methode führt eine zeichenweise Konvertierung des altesZeichen, char aktuellen String-Objekts durch. Jedes Auftreten von neuesZeichen) 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 static static static static static String String String String String String valueOf(boolean b) 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 Wrapper-Klassen bereitgestellt wird, in eine Zeichenkette um. 92 Programmieren in Java 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()); } } Die Klasse StringBuffer Die Klasse StringBuffer dient zur Implementierung veränderlicher Zeichenketten. Konstruktoren: StringBuffer() StringtBuffer(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) StringBuffer insert(int offset, 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. 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. 93 Programmieren in Java Verändern von Elementen: „void setCharAt(int index, char c) throws StringIndexOutOfBoundException“. Das an Position index stehende Zeichen wird durch c ersetzt. Falls StringBuffer zu kurz ist, löst die Methode eine Ausnahme des Typs StringIndexOutOfBoundException 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. 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 synchronized 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 6. Trigonometrische Funktionen public static double sin(double winkel) public static double cos(double winkel) Der Wert von winkel wird im Bogenmaß angegeben. 7. Mathematische Konstanten public double final double E; public static final double PI; 94 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 bouleanValue() public static Boolean valueOf(String s) public static boolean getBoolean(String name) public char charValue() public Character(char value) public int haschCode() 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 char toLowerCase(char ch) public static char toUpperCase(char ch) public static int digit(char ch, int radix) public static boolean isSpace(char ch) 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 95 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“)81 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 81 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. 96 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 static int parseInt(String s) throws NumberFormatException public static int parseInt(String s, int radix) throws NumberFormatException public static Integer valueOf(String s) throws NumberFormatException public static Integer valueOf(String 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. 97 Programmieren in Java 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 is Infinite(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 4. Die Klasse Double Sie hat dieselbe Funktionalität wie die Float-Klasse. 98 Programmieren in Java 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.82: 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); } } 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 82 PR14103 99 Programmieren in Java zahlreichen Standardausnahmen von Java stehen oder auch selbstdefinierte Ausnahmen. Das Ausnahme- / Fehlerobjekt besitzt einige nützliche Methoden, z.B. public String getMessage()83. 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(); } } } 83 Sie ist in der Klasse Throwable definiert und daher allen Exception-Objekten zugänglich. 100 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. 101 Programmieren in Java 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 müssen nicht extra abgefangen werden. „Errors“ sind systembedingt, „Exceptions“ dagegen programmbedingt und müssen behandelt 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 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 102 Programmieren in Java 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: - 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 aurgs[]) { 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 103 Programmieren in Java 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.84: class MeineAusnahme extends Exception { private int i; public MeineAusnahme() {} public MeineAusnahme(String nachricht) { 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(); } 84 Vgl. PR14140 104 Programmieren in Java 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) at AuswurfMeineAusnahme.main(AuswurfMeineAusnahme.java:50) a.wert() = 13 */ 105 Programmieren in Java 1.4.4 Einfache Ein-, Ausgaben Ein- und Ausgabeströme der Klasse System Die Klasse System enthält drei Datenströme: public static InputStream85 in public static PrintStream86 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 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“87 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.88: "Einlesen von zwei ganzen Zahlen". 85 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 87 Das grundlegende Konzept für Ein- und Ausgabe sind Datenströme, die im Paket „java.io“ versammelt sind. 88 Vgl. PR14150 86 106 Programmieren in Java 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); } } 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äts89 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 von den abstrakten Klassen Reader bzw. Writer90 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. 89 90 Hauptspeicher, Festplatte, Drucker, Netzwerk vgl. 7.6 107 Programmieren in Java Bsp.: Das folgende Programm liest eine Zeichenfolge aus einem Eingabestrom und schreibt die Zeichenfolge in einen Ausgabestrom 91 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: - 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(...) 91 vgl. PR14150 108 Programmieren in Java Eingabe und Ausgabe mit Dateien Zur Ein-, Ausgabe von bzw. aus Dateien stehen die aus den Klassen Reader bzw. Writer abgeleiteten Klassen FileReader bzw. FileWriter zur Verfügung. 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; } 109 Programmieren in Java void bereinigen() { // Freigabe der Syntax-Resourcen try { ein.close(); } catch(IOException a2) { System.out.println("ein.close() nicht erfolgreich"); } } } 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(); } } } Umlenken von Standardeingabe, Standardausgabe und Standardfehlerausgabe In Java 1.1 ermöglichen die folgenden Methoden das Umlenken: - setIn(InputStream) - setOut(PrintStream) - setErr(PrintStream) 110 Programmieren in Java Bsp.: Demonstration der Umlenkung von Standardeingabe und Standardausgabe in Java 1.192 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(); } catch(IOException a) { a.printStackTrace(); } } } 92 vgl. PR14150 111 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 Bezeichner, Identifiziere Literal Trennzeichen Operatoren Leeraum93 Kommentar Beschreibung Alle Wörter, die ein wesentlicher Teil der Java-Sprachdefinition sind Namen für Klassen,Objekte, Variablen, Konstanten, Methoden, etc.; Namen sind zusammengesetzt aus Unicode-Zeichen. An erster Stelle eines Bezeichners darf keine Zahl stehen. 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 Symbol zum Anzeigen für Trennungen und Zusammenfassungen von Code Zeichen bzw. Zeichenkombinationen zur Angabe einer auszuführenden Operation mit einer oder mehreren Variablen oder Konstanten Zeichen, die in beliebiger Anzahl und an jedem Ort zwischen Token mit Funktion zur übersichtlichen Gestaltung des Quellcodes plaziert werden können 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 Unicode94. Bei der Kompilierung wird der Quellcode automatisch in eine Folge von Unicode93 Technisch gesehen ist ein Leeraum kein Token Alle Literale (d.h. alle vorkommenden Zeichen (Zahlen, Buchstaben, Sonderzeichen, etc.) bestehen aus Unicode. 94 112 Programmieren in Java Zeichen 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. 113 Programmieren in Java 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. - 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. 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 0X95. 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. Die Zahlen 0 bis 15 werden durch die Buchstaben „A“ bis „F“ bzw. „a“ bis „f“ dargestellt (Groß- oder Kleinschreibung ist hier nicht relevant. 95 114 Programmieren in Java 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 sich um eine Zahl doppelter Genauigkeit handelt. Beispiele 0, 1, 2, ... , 9, 131140 0, 1, 1311, 41421, 9944, 718281828 E23, E-19, E6, e+307 f, F, d, D 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 } 3. Boolesche Literale Es gibt zwei Boolesche Literale: true und false. Es gibt keinen Nullwert und kein numerisches Äquivalent. 115 Programmieren in Java 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)96 - ein „u“ gefolgt von einer vierstelligen Serie von Hexadezimalziffern, die für ein nicht zeilenbeendendes Unicode-Zeichen stehen. Die vier Stellen der hexadezimalen Unicode-Darstellung (\u000 bis \uFFF) stehen für 65535 mögliche Kodierungen. 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 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. 96 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. 116 Programmieren in Java 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 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. 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 117 Programmieren in Java 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: + 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 Divisions- und Zuweisungsoperator Modulo- und Zuweisungsoperator Direkter Zuweisungsoperator Abb.: Die arithmetischen Zuweisungsoperatoren 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. 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 Bitweise Verschiebungsoperatoren 118 Programmieren in Java 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 Manipulieren97 import java.util.*; public class BitManipulation { public static void main(String[] args) { 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); 97 PR21502 119 Programmieren in Java 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(); } } 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 120 Programmieren in Java i >> 5, int: -45801399, binaer: 11111101010001010010000001001001 (~i) >> 5, int: 45801398, binaer: 00000010101110101101111110110110 i >>> 5, int: 88416329, binaer: 00000101010001010010000001001001 (~i) >>> 5, int: 45801398, binaer: 00000010101110101101111110110110 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 Vergleichsoperatoren Vergleichsoperatoren haben zwei Operanden und vergleichen diese (zwei Ganzzahlen oder zwei Gleitpunktzahlen)98. 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 98 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. 121 Programmieren in Java Die relationalen Operatoren „==“ und „!=“ arbeiten mit allen Objekten. Ihre Anwendung zeigt häufig ein „verwirrendes Ergebnis“, z.B. 99: 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()100 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 Logische Vergleichsoperatoren Die logischen Vergleichsoperatoren werden nur auf boolesche Operatoren angewandt und erzeugen nur boolesche Ergebnisse. Operator && || ! Beschreibung Logischer AND-Operator Logischer OR-Operator Logischer NOT-Operator Abb.: Logische Vergleichsoperatoren 99 Vgl. PR21500 Viele in der Java-Bibliorhek bereitgestellte Klassen implementieren die Methode equals() 100 122 Programmieren in Java 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 Datei101 oder des Pakets erzeugt. Tag @see [Klassenname] 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 @see Dieses Tag erzeugt einen Verweis („See also“-Link) in der HTML[Klassenname]#[Methodenname] Datei zu der Methode (innerhalb der mit Klassenname spezifizierten Klasse), die von Methodenname spezifiziert wird @param [Parametername] Dieses Tag dient zur Dokumentation der Parameter. Parametername ist selbsterklärend, und die Beschreibung [Beschreibung] spezifizeirt den Parameter. Geht die Beschreibung über eine Zeile hinaus, so wird dies in der nächsten Zeile fortgeführt. @version [Text] Mit diesem Tag kann die Version des Programms spezifiziert werden. @author [Name] Diese Tag fügt den Namen des Autors in die HTML-Datei ein @return [Beschreibung] Mit diesem Tag kann der wert beschrieben werden, der von einer Methode zurückgegeben wird @exception [Klassenname] Dieses Tag erzeugt einen Link auf Ausnahmen, die von der Klasse, die durch Klassenname spezifiziert ist, erzeugt werden. @deprecated [Ausdruck] 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, @since [Text] 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 dazugehörige Paketverzeichnis102 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 101 102 Beschreibung Diese Option veranlaßt javadoc, die version des JDK anzuzeigen. vgl. 1.3.2.2, 3. Aufgabe als Paketname zu verstehen, nicht als physikalisches Verzeichnis 123 Programmieren in Java -classpath [Verzeichnis] -v -verbose -d [Verzeichnis] -public -protected -private -package -Jflag -notree -doencoding [Name] -sourcepath [Pfad] 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 124 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 125 Programmieren in Java 2.2.2 Operationen mit primitiven Datentypen 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 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 126 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 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 Überlauf103 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 durch Null“ ergibt den Wert „NaN“ (keine Zahl). Ein Unterlauf104 gibt einen speziellen 103 d.h.: Größeres Ergebnis von einer Operation als durch den Wertebereich des jeweiligen Typs ausgedrückt werden kann. 104 d.h.: kleineres Ergebnis (- außer Null -) von einer Operation als durch den Wertebereich des jeweiligen Typs ausgedrückt werden kann. 127 Programmieren in Java Wert aus: „positiv oder negativ Null“. Dieser Wert kann mit Vergleichsoperatoren ausgewertet werden (bewirkt false). 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. 128 Programmieren in Java 2.2.3 Datenfelder (Arrays) Ein Datenfeld (Array) ist eine Ansammlung von Objekten eines bestimmten Typs105, 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 Referenzvariablen 106. 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. 105 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. 106 Es gibt drei Arten von Referenzvariablen: Klassen, Schnittstellen und Datenfelder 129 Programmieren in Java 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) { 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 130 Programmieren in Java 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) { 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. 131 Programmieren in Java 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 Fehler107. 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. 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. 107 Er weist auf eine „Ausnahme“ hin. 132 Programmieren in Java 3. Sortieren108 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: 108 import java.util.*; class BubbleSort { public static void main(String args[]) { int[] x; x = new int[10]; 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; PR22301 133 Programmieren in Java // 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]); } } } 2.2.3.4 Mehrdimensionale Datenfelder 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 4 Zeilen und 3 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] 134 M[0][4] M[1][4] M[2][4] M[3][4] Programmieren in Java 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}, {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(); } } } 135 Programmieren in Java Bsp.: Das 8-Damen-Problem109 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 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 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. 1 0 0 1 0 0 1 0 0 1 0 1 0 1 0 0 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 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 9 1 1 1 1 1 1 9 1 Vorschlag zur Implementierung: import java.util.*; 109 vgl. PR22305 136 Programmieren in Java 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; } for (int i = 0; i < 8; i++) { if (i != zeile) brett[i][spalte] = 1; } int lDzeile = zeile; int lDspalte = spalte; 137 Programmieren in Java // 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(); } 138 Programmieren in Java System.out.println(); int k = 0; for (int i = 0; i < brett.length; i++) for (int j = 0; j < brett[i].length; j++) { 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(); } } Multdimensionale 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)110. 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[]) { 110 Im Java werden multidimensionale Arrays streng genommen nicht unterstützt 139 Programmieren in Java // 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++) { 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 um Paket java.util mit nützlichen Methoden zum Zugriff auf Arrays: 140 Programmieren in Java public static void fill(int[] a, int wert) public static void 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. 141 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. 142 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 143 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.111: 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); } } 111 vgl. PR23301 144 Programmieren in Java 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. Bsp.112: 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); 112 PR23302 145 Programmieren in Java 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; } } 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. 146 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; 147 Programmieren in Java 2.4.5 Ausdrucksanweisungen In Java gibt es sieben verschiedene Arten von Ausdrucksanweisungen: Ausdrucksanweisung Zuordnung Prä-Inkrement Prae-Dekrement Post-Inkrement Post-Dekrement Methodenaufruf Zuweisungsausdruck Beispiel x=13; ++wert; --wert; 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 DekrementOperatoren113 // 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 */ 113 vgl. PR24500 148 Programmieren in Java 2.4.6 Auswahlanweisungen 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) anweisung114 if-else-Anweisungen 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-Anweisung115 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); „anweisung“ bedeutet: Eine einfache Anweisung, die nur von einem Semikolon abgeschlossen ist bzw. eine zusammengesetzte Folge von anweisungen, die von { ... } umschlossen ist. 115 Vgl. PR24601 114 149 Programmieren in Java if (i >= 0) { abs = i; } else { abs = -i; } System.out.println("abs = // if i = 13; abs = i; System.out.println("i = " if (abs < 0) { abs = -abs; } System.out.println("abs = i = -13; abs = i; System.out.println("i = " if (abs < 0) { abs = -abs; } System.out.println("abs = // Konditionaloperator i = -13; System.out.println("i = " abs = ( i < 0 ? -i : i); System.out.println("abs = i = 13; System.out.println("i = " abs = ( i < 0 ? -i : i); System.out.println("abs = " + abs); + i); " + abs); + i); " + abs); + i); " + abs); + i); " + abs); } } 150 Programmieren in Java 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 Werts116. „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-Anweisung117 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: System.out.print("A"); break; Integrale Werte sind bspw. vom Typ „int“, „char“. Nichtintegrale Typen. Z.B. float, String, müssen über eine Serie von „if“-Anweisungen „switch“ simulieren 117 vgl. PR24601 116 151 Programmieren in Java 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 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.118: 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; 118 PR24700 152 Programmieren in Java while (i <= absn) { aHochn *= a; i++; } if ( n < 0) { aHochn = 1.0 / aHochn; } System.out.println("aHochn = " + aHochn); } } 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.119: 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); } } for-Anweisung Sie besteht aus - for, gefolgt von optionalem Leerraum, gefolgt von einer öffnenden Klammer. 119 PR24700 153 Programmieren in Java - 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); } } 154 Programmieren in Java 2.4.8 Sprung-Anweisungen 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:“. 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.120: „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); } } } 120 PR24800 155 Programmieren in Java 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.121: 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; } 121 PR24800 156 Programmieren in Java if (i == 3) { ausgabe("break"); i++; break; } if (i == 7) { ausgabe("continue aussen"); 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 */ 157 Programmieren in Java 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"); 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 */ 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“. 158 Programmieren in Java Der Rückgabewert ist der Wert einer return-Anweisung. throw-Anweisung Eine throw-Anweisung erzeugt eine Laufzeit-Ausnahme. 2.4.9 Synchronisationsanweisungen Sie wird für den Umgang mit „Multithreading“ benutzt. 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 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 159 Programmieren in Java 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. Ö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. Der Klassenname Jede Klasse benötigt einen Namen. Superklasse und das Schlüsselwort extends Wird der Name einer Klasse nach dem Schlüsselwort extend 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. 160 Programmieren in Java 2.6 Methoden 2.6.1 Die Deklaration Die Deklarationen von Methoden haben folgendes Aussehen 122: Zugriffsspezifizierer Modifizierer Returnwert NameMethode(Parameter) throws Exceptionliste Methodenunterschrift123. 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 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. 122 123 Kursiv Geschriebenes ist optional Oft wird für denselben Zusammenhang der Begriff „methodensignatur“ verwendet. 161 Programmieren in Java 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 Datentyp 124 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. 124 Nicht nur primitive Datentypen, sondern auch komplexe Objekte. 162 Programmieren in Java 2.6.6 Rekursion 2.6.6.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. 2.6.6.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üu die Rekursion spricht: 1. Es gibt bestimmte rekursiv formulierte Algorithmen, die schneller oder wenigstens gleich schnell arbeiten als vergleichbare iterative. 2. 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. 163 Programmieren in Java 3. Grafische Benutzeroberflächen und Applets 3.1 Ereignisbehandlung unter grafischen Benutzeroberflächen Mit Hilfe einfacher Benutzerschnittstellen des Graphical User Interface (GUI)) lassen sich Ein- und Ausgaben125 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-, Mausund 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 125 Standardein- und Standardausgabe spielen in Java nur im Rahmen des „Debugging“ eine Rolle. 164 Programmieren in Java Component Container Panel Applet Window Frame Dialog FileDialog 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"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. Container { 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 synchronized void detFont(Font f) public Point getLocation() public Point getLocationOnScreen() public void setLoaction(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 Dimension getPreferredSize() public Dimension getMinimumSize() 165 Programmieren in Java public Dimension getMaximumSize() public void invalidate() public Graphics getGraphics() public FontMetrics getFontMetrics(Font font) public void paint(Graphics g) public void update(Graphics g) 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) public int checkImage(Image bild,ImageObeserver obs) public int checkImage(Image bild, int breite, int hoehe, ImageObserver obs) public synchronized void addComponentListener(ComponentListener l) public synchronized void removeComponentListener(ComponentListener l) public synchronized void addFocusListener(FocusListener l) public synchronized void removeFocusListener(FocusListener l) public synchronized void addKeyListener(KeyListener l) public synchronized void removeKeyListener(KeyListener l) public synchronized void addMouseListener(MouseListener l) public synchronized void removeMouseListener(MouseListener l) public synchronized void addMouseMotionListener(MouseMotionListener l) public synchronized void removeMouseMotionListener(MouseMotionListener l) public final void enableEvents(long eventsToEnable) public final void disableEvents(long eventsToDisable) public void processEvent(AWTEvent e) public 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 void addNotify() public void removeNotify() public boolean gotFocus(Event evt) public boolean lostFocus(Event evt, Object was) public void requestFocus() public synchronized void add(PopupMenu popup) public synchronized void remove(MenuComponent popup) public String toString() Abb.: Die Klasse Component 166 Programmieren in Java 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 Insets insets() public Component add(Component comp) public void remove(Component comp) public void removeAll() public LayoutManager getLayout() public void setLayout(LayoutManager mgr) ... Abb.: Die Klasse Container 167 Programmieren in Java 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 synchronized 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 synchronized void setResizable(boolen b) public void remove(MenuComponent m) public void dispose() Abb.: Die Klassen Frame und Dialog 168 << 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 synchronized void setResizable( boolean b) Programmieren in Java Bsp.126: 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[]) { 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 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. 126 PR14160 169 Programmieren in Java 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. Ereignistypen. Im JDK 1.1 werden Ereignistypen durch eine Hierarchie von Ereignisklassen repräsentiert, die alle aus der Klasse java.util.EventObject127 abgeleitet sind. 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. 127 Speichert das Objekt, das die Nachricht ausgelöst hat und gibt durch Aufruf von "public Object getSource()" das Objekt an. 170 Programmieren in Java 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. 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); 171 Programmieren in Java 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.128: Textfeldbearbeitung WindowAdapter mit "ActionListener-Interface" 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); 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); } }); 128 PR14160 172 und Programmieren in Java // 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 { public void actionPerformed(ActionEvent ae) { String s = eingabeTextFeld.getText(); ausgabeTextFeld.setText(s); } } 173 Programmieren in Java 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); } 174 Programmieren in Java public static void main(String args[]) { Vorl11a vorl11a = new Vorl11a(); vorl11a.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } } 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.: 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() { 175 Programmieren in Java 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:"); 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. 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 Bedeutung Eine Komponente wurde sichtbar Eine Komponente wurde unsichtbar Eine Komponente wurde verschoben 176 Programmieren in Java componentResized Die Größe der Komponente hat sich verändert Abb.: Übersicht zu den Methoden von ComponentListener 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 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 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. 177 Programmieren in Java Ereignismethode mousePressed mouseReleased mouseClicked 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. mouseEntered mouseExited 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. Weiterhin gibt es in MouseEvent die Methode 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 129geerbten Methoden public public public public boolean boolean boolean boolean isShiftDown(); isControlDown(); isMetaDown(); isAltDown(); zur Verfügung. MouseMotionEvents 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. 129 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 178 Programmieren in Java 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 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 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 Komonente 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. 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); 179 Programmieren in Java Die Taste, die gedrückt wurde, erhält man über die folgenden Methoden der Klasse KeyEvemt 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_F!" 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. getKeyCode Zeichentaste131: VK_UNDEFINED Funktionstaste132: 133 Zeichentaste: VK_... keyPressed Funktionstaste: VK_... keyTyped130 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(); 130 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 132 Dazu gehören bspw. F1, F2, Pos 1 aber auch die Umschalttasten: STRG, ALT, UMSCHALT 133 zeigt das Verhalten beim Aufruf von keyPressed 131 180 Programmieren in Java Bsp.: Das folgende Programm134 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()); } public void focusLost(FocusEvent e) { System.out.println(e.toString()); } } 134 vgl. PR14160 181 Programmieren in Java 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()); } } 182 Programmieren in Java 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); } } 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: public void processComponentEvent(ComponentEvent e) public void processFocusEvent(FocusEvent e) public void processKeyEvent(KeyEvent e) public void processMouseEvent(MouseEvent e) public void processMouseMoveEvent(MouseEvent e) public 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 183 Programmieren in Java 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. 6. Sie verfährt wieder so, wie unter den Ziffern 3 bis 5 beschrieben. Das wiederholt sich so lange, bis processEvent von ComponetEvent 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); } } 184 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 Dialoge135" 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 Icon136 zuordnen. Zuordnen eines Layoutmanagers. Es wird über die Methode "public void setLayout(LayoutManager mgr)" der Klasse Container realisiert. Java stellt fünf Layoutmanager137 bereit: FlowLayout BorderLayout, GridLayout 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.: 135 Modale Dialaloge verhindern die Interaktion des Anwenders mit anderen Fenstern der Anwendung bis zum Schließen des Dialogfensters. 136 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. 137 Vgl. 5.4.2 185 Programmieren in Java import java.awt.*; import java.awt.event.*; public class PR14171 extends Frame { public PR14171() { 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: 186 Programmieren in Java 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 Anpassung der Fenstergröße an den für die Darstellung der Dialogelemente erforderlichen Platz ausgeführt werden. Popup-Fenster der Klasse Dialog Die Klasse Dialog138 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.139: 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); } }); 138 139 vgl. 5.3.4 vgl. PR14170 187 Programmieren in Java 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); } } 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. 188 Programmieren in Java Dialog FileDialog public static final int LOAD public static final int SAVE << Konstruktor >> public FileDialog(Frame eltern) public FileDialog(Frame eltern, String titel) public FileDialog(Frame eltern, String titel, int mode) << Methoden >> public String getDirectory() public synchronized void setDirectory(String dir) public String getFile() public synchronized void setFile(String file) public FilenameFilter getFilenameFilter() public synchronized void setFilenameFilter(FilenameFilter filter) Abb.: Die Klasse FileDialog Bsp.: Laden und Speichern von Dateien mit Unterstützung durch ein Dateidialogfeld140. import java.awt.*; import java.awt.event.*; public class DateiDialog extends Frame { TextField dateiName = new TextField(); 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); } 140 PR14160 189 Programmieren in Java 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(); f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(250,110); f.setVisible(true); } } 190 Programmieren in Java 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: Labels141, Schaltflächen(Buttons)142, Kontrollkästchen und Optionsfelder143, Auswahlmenüs144, Listenfelder145, Textbereiche und Textfelder146, Schieberegler147. 141 vgl. 5.2.2 vgl. 5.2.1 143 vgl. 5.2.3 144 vgl. 5.2.4 145 vgl. 5.2.5 146 vgl. 5.2.6 147 vgl. 5.2.7 142 191 Programmieren in Java Das Dialogelement „Canvas“ 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.148: 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. 148 PR52900 192 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äge 149. 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. 149 vgl. 5.3.3 193 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(Stiing label, MenuShortcut s) << Methoden >> public void addNotify() public String getLabel() public synchronized void setLabel(String label) public boolean isEnabled() public synchronized void setEnabled(boolean b) public MenuShortcut getShortcut() public void setShortcut(MenuShortcut s) public final void enableEvents(long eventsToEnable) protected final void disableEvents(long eventsToEnable) public void setActionCommand(String kommando) public String getActionCommand() public synchronized void addActionListener(ActionListener l) public synchronized void removeActionListener(ActionListener l) proteced 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 countMenus() public Menu getMenu(int i) public synchronized 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) Menu 194 Programmieren in Java PopupMenu << Konstruktor >> public PopupMenu() public PopupMenu(String label) << Methoden >> public void addNotify() public void show(Component origin, int x, int y) Abb.: Die AWT-Menüklassen 195 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. 196 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 Verzeichnis150 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. 150 Pfadname, in dem sich die Klassen befinden 197 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 198 Programmieren in Java 1. Das Erstellen eines Applets Zum Erstellen eines Applet muß immer eine „Subklasse“ der Klasse Applet151 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 WebBrowser. 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. 151 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.* 199 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-Klasse152 in den Code der Applet-Subklasse importiert153 wird. Bsp.: „Aller Anfang ist schwer!. Dieser Spruch soll mit Hilfe eines Applet gezeigt werden.. Die zugehörige Quellcodedatei „AllerAnfangApplet.java“ umfaßt 154: 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); 152 Teil des Pakets java.awt Normalerweise geschieht dies über: import java.awt.Graphics 154 vgl. PR32101 153 200 Programmieren in Java // 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: <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: 201 Programmieren in Java 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 gezeichnet155 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 Zeichenbereich156 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: - 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.157: 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; 155 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. 156 Das ruft oft den unangenehmen Flimmereffekt bei schnellen Bildsequenzen hervor. 157 PR32305 202 Programmieren in Java // 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; // } } } 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. 203 Programmieren in Java // 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); } } Eine überschriebene update()-Methode kann den Flimmereffekt erheblich senken. 204 Programmieren in Java 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 aktualisiert158. 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 } } 158 vgl. PR42001 205 Programmieren in Java In der start()-Methode nimmt die while-Schleife alle Systemressourcen für sich in Anspruch (einschl. der Anzeige am Bildschirm). Deshalb funktioniert die 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) {} } } 206 Programmieren in Java // 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: // // // // 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() { // ... } } 207 Programmieren in Java 3.3.4 Animation in Applets Animationsschritte Eine Animation umfaßt in Java zwei Schritte: 1.Aufbau und Ausgabe eines Animationsrahmens (-fenster). 2. 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; 208 Programmieren in Java // Methoden public void init() { setBackground(Color.yellow); } 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 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()-Methode159 update() löscht den Bildschirm durch Füllen mit der aktuellen Hintergrundfarbe, setzt die aktuellen Farbe auf die Vordergrundfarbe und ruft anschließend paint() 159 vgl. PR32212 209 Programmieren in Java auf. Das Überschreiben von update() muß sicherstellen, daß so etwas Ähnliches geschieht. import java.awt.*; public class PendelAppl2 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; // 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; } } 210 Programmieren in Java 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); } public void paint(Graphics g) { xAlt = x; yAlt = y; } } 2. Double Buffering Mit „double buffering“160 wird eine zweite Oberfläche geschaffen, in der alles vorgezeichnet und dann auf einmal in die Zeichnungsoberfläche des Applet ausgegeben wird. 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; 160 211 Programmieren in Java // 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() { 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); } 212 Programmieren in Java 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; } } 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 URL161) - 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); } 161 Adressen im World Wide Web werden durch URL-Objekte repräsentiert. Die Klasse URL (Uniform Resource Locator) ist Teil des Pakets java.net 213 Programmieren in Java Bsp.162: 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); } } 3.3.6 Die Ausgabe von Sound Das JDK bietet Möglichkeiten zur Ausgabe von Sound 163 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. 162 vgl. PR33501 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 SunWelt 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). 163 214 Programmieren in Java Bsp.164: 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) {} } catch (MalformedURLException e) { System.out.println(e.toString()); } } } } 164 Vgl. PR33602, Aufruf z.B.: javac PR33602 file:///c:/winnt\media\ringin.wav 215 Programmieren in Java 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.Applet << Konstruktor >> public JPanel() << 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 216 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. 217 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 { public Observable(); public void addObserver(Observer o); public void deleteObserver(Observer o); public void deleteObservers(); public int countObservers); public void setChanged(); public 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. public synchronized 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 synchronized 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 synchronized boolean hasChanged() stellt fest, ob ein Objekt von Interesse modifiziert wurde. public synchronized void addObserver(Observer obs) Mit dieser Methode können Observer ihr Interesse an einem beliebigen Observable bekunden. public synchronized void deleteObserver (Observer obs) 218 Programmieren in Java Mit dieser Methode kann ein Observer die Überwachung der entsprechenden Observables beenden public synchronized void deleteObservers() löscht die Liste aller Observer public synchronized 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 Observable165 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.:166 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(); 165 Das Konzept für diese Klasse ist eine Leihgabe von Smalltalk und unter dem namen Model-View-Controller (MVC) bekannt. 166 PR34100 219 Programmieren in Java 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[]) { 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(); } } 220 Programmieren in Java private final boolean nextTo(OCBox b) { return Math.abs(x - b.x) <= 1 && Math.abs(y - b.y) <= 1; } } 221 Programmieren in Java 3.5 Swing Swing167 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. Swing-Komponenten werden auf gleiche Weise verwendet wie die Komponenten des AWT: Erzeugen einer Komponete über den Konstruktor, Aufruf der Methoden der Komponente. 3.6 Java Beans 3.6.1 Wiederverwendbare Softwarekomponenten Unter einer Software-Komponente168 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. 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 167 vgl. 5.6 Die Definition wurde zitiert nach: Griffel, Frank: Componentenware – Konzepte und Techniken eines Softwareparadigma, dpunkt-Verlag, 1998 168 222 Programmieren in Java 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“169) 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. 3.6.3 Enterprise Java Beans „Java Beans“ wurde als ein lokales Komponentenmodell170 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 169 - 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. 170D.h.: Die Kommunikation zwischen Komponenten findet auf einer maschine statt. 223 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. Die meisten Zeichenvorgänge werden jedoch in der paint()-Methode durchgeführt. Java-Applets zeichnen sich selbst neu nach Überschreiben der paint()-Methode. Es gibt drei verschieden Methoden171 zum Neuzeichnen: public void paint(Graphics g) public void repaint() public void update(Graphics g) 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 setXOR;ode(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 abtsract void clearRect(int x, int y, int width, int height) public abstract void drwaRoundRect(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, intx, int y) public abstract boolean drawImage(Image bild, int x, int y, ImageObserver observer) .... Abb.: Die Klasse Graphics 171 vgl. 3.2.2 224 Programmieren in Java Ein Graphics-Objekt erzeugt nicht der Entwickler, sondern er bekommt es dem Kontext gestellt, auf dem er zeichnen möchte. Die Klasse Graphics hat keinen öffentlichen Konstruktor. 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. Bsp.: Ein Applet mit zufällig verteilten Linien172 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(); } } 172 vgl. PR42005 225 Programmieren in Java 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. Zeichnen eines Rechtecks. Dafür gibt es die Methode: public abstract 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 void setColor(Color c). 226 Programmieren in Java Bsp.: Ein Applet mit zufällig verteilten Rechtecken173 // 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); } } 173 vgl. PR41103 227 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.174: 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); } } 174 PR41105 228 Programmieren in Java - Weitergabe einer Instanz der Polygon-Klasse, z.B.175: 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) { // 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), z.B.176: 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 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. java.awt.Polygon protected Rectangle bounds public int npoints public int[] xpoints 175 176 PR41105 PR41105 229 Programmieren in Java 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 bollean contains(int x, int y) ... public Rectange 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) 230 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ß Schwarz Grau Rot Grün Blau Yellow Magenta Cyan Rot-Anteil 255 0 127 255 0 0 255 255 0 Grün-Anteil 255 0 127 0 255 0 255 0 255 Blau-Anteil 255 0 127 0 0 255 0 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 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(); 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. 231 Programmieren in Java 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 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 FontKlasse177 und die Fontmetrics-Klasse178 beim Textzeichnen eine Rolle. Font public static final int PLAIN public static final int BOLD public static final int ITALIC public String name public int style public int size << 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 decode(String str) public static Font getFont(String nm, Font font) public int hashCode() public boolean equals(Object obj) public String toString() FontMetrics { abstract } public Font getFont() public int getLaeding() 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() Abb.: Die Klassen Font und FontMetrics 177 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. 178 232 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 fett179 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 void 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.: 180 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]); } } BOLD und ITALIC können auch gemeinsam verwendet werden, indem beide Konstanten mit „+“ zusammengefügt werden. 180 PR41300 179 233 Programmieren in Java 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 über die Grundlinie geht. Ascent (Überstand): damit ist gemeint, wie weit ein Buchstabe über die Grundlinie geht. Leading (Zeileabstand): Damit ist der Raum zwischen dem Descent eines Buchstabens und dem Ascent der nächsten Zeile gemeint. Methoden. Methode stringWidth() charWidth() getAscent() getDescent() getLeading() getHeight() 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 Gibt die Entfernung zwischen der Grundlinie und der unteren Grenze der Buchstaben aus (z.B. p und g) Gibt den Abstand zwischen dem Überstand einer Zeile und dem Überstand der nächsten Zeile aus gibt die Gesamthöhe der Schrift aus, d.h. die Summe von Überstand, Unterstand und Zeilenabstand Abb.: Fontmetrics-Methoden Bsp.181: 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; } // 181 PR41301 234 Programmieren in Java 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(); } int breite = getSize().width; int hoehe = getSize().height; spruchX = (breite - spruchBreite) / 2; spruchY = (hoehe + spruchAscent - spruchDescent) / 2; g.drawString(spruch,spruchX,spruchY); } } 235 Programmieren in Java 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 Rectange(Rectangle r) public Rectange (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 Rectange(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. 236 Programmieren in Java 4.1.5 Clipping-Operationen Alle primitiven Zeichenoperationen wirken auf den ganzen Bildschirm. Man kann dies durch das Setzen eines sog. Clipping-Bereichs einschränken, aus dem dann nicht mehr heraus gezeichnet werden kann. 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. 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-Bilder182 und JPEG-Bilder geladen werden. Jede Grafik wird als Exemplar der Klasse Image erzeugt. 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, ImgaObserver obs) public Image getScaledInstance(int vertikal, int horizontal, int modus) << interface >> 182 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). 237 Programmieren in Java 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 synchronized void addImage(Image bild, int id) public synchronized 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 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.ImageObeserver 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. 238 Programmieren in Java Bsp.: Ein Bildbetrachter183 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(); image = Toolkit.getDefaultToolkit().getImage(file); if (image != null) repaint(); } public static void main(String args[]) { new ImageViewer().show(); } } 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); 183 PR42300 239 Programmieren in Java „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.waitForIdD0). 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 symboloisiert wird: MediaTracker.ABORTED MediaTracker.COMPLETE MediaTracker.LOADING MediaTracker.ERRORED // // // // Das Das Das Das Laden von Bildern wurde abgebrochen Bilder wurden komplett geladen Bilder werden noch geladen 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 verwenden184. 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. 184 typischerweise einfache Zeichenroutinen, die das Bild auf dem Bildschirm anzeigen. 240 Programmieren in Java 4.2.3 Bildfilter ImageFilter liegen zwischen Produzenten und Bildinformationen und nehmen Einfluß auf die Größe. Subklassen der Klasse ImageFilter: Konsumenten, verändern BufferedImageFilter. Mit diesem Filter läßt sich ein Objekt vom Typ BufferedImageOp übergeben, mit dem unterschiedliche Manipulationen ermöglicht werden. BufferdImageOp ist eine Schnittstelle, die von AffineTransformOp, ConvolveOp, BandCombineOp, LookupOp implementiert wird. CropImageFilter. Bildteile werden herausgeschnitten. ReplicateScaleFilter. Zum Vergrößern / Verkleinern von Bildern. RGBImageFilter. Dieser allgemeine Filter ist für die eigene Filterklasse gedacht. Bsp.:185 Das folgende Applet nimmt ein Bild und setzt ihm ein CropImageFilter auf, damit es nur einen Teil des Bildes anzeigt. import java.awt.*; import java.awt.image.*; import java.applet.*; public class CropImage extends Applet { private Image originalImage; private Image croppedImage; private ImageFilter cropFilter; public void init() { // Beschaffe das Original originalImage = getImage(getDocumentBase(),"B01000800.jpg"); // Erzeuge einen Filter cropFilter = new CropImageFilter(0,0,480,360); // Erzeuge ein Bild, das die verkuerzte Version umfasst croppedImage = createImage(new FilteredImageSource( originalImage.getSource(),cropFilter)); } public void paint(Graphics g) { g.drawImage(originalImage,0,0,this); g.drawImage(croppedImage,480,0,this); } } 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. 185 PR42300 241 Programmieren in Java 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); } } Abb.: Darstellung von SpeicherBild 242 Programmieren in Java Die PixelGrabber-Klasse nimmt ein Bild und macht aus diesem ein Datenfeld mit ganzen Zahlen. Der PixelGrabber ist für Modifikationen bereits existierender Bilder nützlich. 4.2.4.2 Kopieren von Bildern in einen Speicher Die Klasse PixelGrabber nimmt ein Bild und macht aus diesem ein Datenfeld von Ganzzahlen. 243 Programmieren in Java 4.3 2D Java 2D Java ist Bestandteil der Java Foundation Classes (JFC), ein Teil dessen, was Sun Swing nennt und sämtliche Techniken umfaßt, die die GUI-Fähigkeit von Java ausmachen. Das neue Java-2D-API ist ein Satz von Klassen für erweiterte 2D-Grafik und die Bildverarbeitung. Die abstrakte Klasse Graphics2D erweitert die Funktionalität von Graphics um Transformationen, Linienstärke und –stil. Graphics { abstract } Graphics2D { abstract } public Stroke stroke // Definition von Linienstiften public Paint paint // Erweiterung des Farbmodells public AffineTransform transform // erlaubt Veränderungen der Form public Composite composite // gibt an, wie Objekte verschiedener Farben überlagert werden sollen, // erlaubt weiche Farbübergänge (Anti-Aliasing) .... << Konstruktor >> protected Grahics2D() << 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, ImageObsercer obs) public abstract void fill(Shape s) public abstract GraphicsConfiguration getDeviceConfiguration() public abstract FontrenderContext(getRenderContext() 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(Color c) public abstract setPaint(Paint paint) public abstract void setRenederingHint(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 244 Programmieren in Java 4.3.1 Das Zeichnen unter Java 2D-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) { 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. Zeichnen unter Java 2D-API Das Java 2D-API umfaßt zusätzliche unterstützende Features zur Spezifizierung von Zeichenstiften, komplexe Formen und diverse Zeichenprozesse. 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); } } Zur Definition des Rechtecks wird die neue Java 2D-API-Klasse GeneralPath (Bestandteil des geom-Pakets186) herangezogen. Das Erstellen von Standardformen 186 Graphics2D ist Bestandteil des Pakets java.awt 245 Programmieren in Java (z.B. Rechtecke Ellipsen, Bögen, Kurven) kann einfacher über diverse Subklassen der Klasse Shape (in Verbindung mit GeneralPath) erfolgen. 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 Java 2-D-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 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 3. Festlegen vom Aussehen der Form, des Textstrings oder des Bildes über passende Graphics2D-Methoden. 246 Programmieren in Java Java 2D API-Klassen187 Außer der Klasse java.awt.Graphics2D umfaßt Java 2D API Klassen in den Paketen java.awt.geom (geometrische Formen, Pfade und Transformationen), 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 CubicCurve2D188. 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 recteckige 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_BUT,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-BUT (keine Abschlußpunkte), CAP_ROUND (Anzeige von Kreisen an beiden Enden) und CAP_SQUARE auswählen. 187 PR43210 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. 188 247 Programmieren in Java 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). Das Objekt RenderingHints 4.3.3 Affine Transformation Eine affine Transformation eines Objekts ist eine Translation (Verschiebung), Rotation bzw. Scherung189. Bei diesen Transformationen bleiben parallele Linien nach der Translation auch parallel. Zur durchführung dieser Operationen existiert die Klasse AffineTransform. Dem Graphics2D-Kontext können diese Transformationen zugewiesen werden (z.B. über setTransform()). 4.3.4 Farbmanagement unter 2D-Java 4.3.5 2D-Bildverarbeitung 189 Ein Objekt wird geschert, wenn es entlang der Koordinatenachse verzogen wird. Im zweidimensionalen gibt es zwei Scherungsarten: Entlang der x-Achse und der y-Achse. 248 Programmieren in Java 249 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 Komponenten190 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 190 Java 1.2 und das Swing-Konzept erweitern diese Komponenten noch einmal um einen satz von neuen und ergänzenden Komponenten. 250 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 Applet191 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, 191 Abgeleitet von der Panel-Klasse 251 Programmieren in Java kann es in der Button-Klasse durch „public void setActionCommand(String kommando)“ geändert werden. Bsp.: Setzen von Hintergrundfarben nach verschiedenen Buttonklicks192 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); } }); 192 PR52105 252 Programmieren in Java 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(); } } 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, Objekct 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. 253 Programmieren in Java Bsp.: Setzen von Hintergrundfarben nach verschiedenen Buttonklicks193 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; } } 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 193 vgl. PR52105 254 Programmieren in Java wird) und einem Zustand (boolesche Variable, die angibt, ob die Box eingeschaltet wurde oder nicht194). 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 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. 194 Standardmäßig ist die Box ausgeschaltet (Wert ist false oder „off“). 255 Programmieren in Java 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. 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 256 Programmieren in Java 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“); } } } } 257 Programmieren in Java public static void main(String args[]) { PR52302 f = new PR52302(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); 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.195: 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; } 195 PR54305 258 Programmieren in Java if (welcheAuswahl.getLabel() == “Gelb“) { if (CheckboxStatus) setBackground(Color.yellow); return true; } return false; // keine der Optionen wird aufgefangen } } 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 getSelectionIndex() liefert den Index des ausgwä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 select(int) // Auswahl über den Index public 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. 259 Programmieren in Java 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( 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: 260 nahezu dieselbe Programmieren in Java 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() { 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); } } 261 Programmieren in Java 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: public TextField() erzeugt ein leeren Textfeld, in das der Anwender Text eingeben kann. public TextField(int breite) 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 breite) 262 Programmieren in Java 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 spalten) 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 getSelectEnd() kann die aktuelle Auswahl begefragt 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 mit die Veränderung von Text verhindern. Mit public void setEchoCharacter(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 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 textValueChanged mit einem „TextEvent“ als Parameter aufgerufen. „TextEvent“ ist aus „AWTEvent“ abgeleitet und erbt die Methoden getID, getSource. Typischerweise wird innerhalb von textValueChanged mit getSource das zugehörige Textfeld beschafft und mit getText auf seinen Inhalt 263 Programmieren in Java 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); } } 264 Programmieren in Java class B2A implements ActionListener { public void actionPerformed(ActionEvent e) { t1.setText(„Eingefuegt durch Button 2: „ + s); t1.setEditable(false); } } 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 scroll) erzeugt ein TextArea-Objekt, in dem über scroll die Ausstattung der TextArea mit Schieberegistern festgelegt werden kann. Dazu stellt TextArea die Konstanten SCROLLBARS_BOTH, SROLLBARS_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. 265 Programmieren in Java Bildlaufleisten können auch individuell erstellt werden und dienen zur (quasi-) analogen Anzeige bzw. Eingabe eines Werts aus einem vorgebenen Wertebereich. 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, int 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 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 Ereignisempfänger. Scrollbar sendet Adjustment-Ereignisse an seine Diese müssen das Interface AdjustmentListener 266 Programmieren in Java implementieren und sich durch Aufruf von public void addAdjustmentListener(AdjustmentListener l) registrieren. Das Adjustment-Ereignis führt im Ereignisempfänger zum Aufruf der Methode: 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(); } 267 Programmieren in Java 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()); } } 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()); } } 268 Programmieren in Java 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 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 269 Programmieren in Java 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(); } public void zeichnen() { zeichneObjekte = true; repaint(); } 270 Programmieren in Java 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() { 271 Programmieren in Java public void windowClosing(WindowEvent e) { System.exit(0); } }); 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(); } } 272 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 273 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()-Methode196. Mit public void hide()197 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. 196 Für show() aus dem Paket java.awt.Component gibt es als neue Variante die Methode setVisible(boolean) 197 hide() ist in java.awt.Frame als „deprecated“ deklariert. Statt dessen steht setVisible(boolean) zur Verfügung 274 Programmieren in Java 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: 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) Zugrff 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) 275 Programmieren in Java 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 addSeperator() // fügt den Seperator hinter dem zuletzt eingefügten Menüeintrag ein public void insertSeperator(int index) // Einfügepostion kann frei angegeben werden. 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); 276 Programmieren in Java // 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 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); 277 Programmieren in Java // 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“)) { 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 { 278 Programmieren in Java 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); } } 279 Programmieren in Java 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. Erzeugen. Ein modaler Dialog muß immer von der Klasse Dialog abgeleitet sein. Dialog bietet 4 Konstruktoren an public public public public Dialog(Frame Dialog(Frane 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 rezisable) public boolean isResizable() geschehen bzw. überprüft werden. 280 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. 2. Die Reihenfolge, in der die AWT-Komponenten in einen Container eingefügt werden. 3. Die Art des Layout-Managers. Das AWT umfaßt 5 verschiedene Typen von Layout-Managern, die die Oberfläche unterschiedlich aufgliedern: - 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()); 281 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) << Methoden >> public void addLayoutComponent(String name, Component comp) public int getAlignment() public void setAlignment(int align) .... 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); Button meinKnopf4 = new Button(“Pink“); add(meinKnopf4); Button meinKnopf5 = new Button(“Rosa“); add(meinKnopf5); Button meinKnopf6 = new Button(“Gelb“); add(meinKnopf6); Button meinKnopf7 = new Button(“Cyan“); 282 Programmieren in Java add(meinKnopf7); } } 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> Die BorderLayout-Klasse << interface >> LayoutManager 283 Programmieren in Java 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). 284 Programmieren in Java 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++)); 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: 285 Programmieren in Java 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 286 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 die Variablen gridwidth und gridheight bestimmen, wie viele Zellen hoch und breit eine Komponente sein sollte (Standardwert ist 1). Der Wert GridBagConstraint.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. „GridBagConstraint.BOTH“ sorgt für eine Streckung der Komponenten in beiden Richtungen so, daß sie den Anzeigebereich voll ausfüllen. 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 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) 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 isapdx an. Wieviel obeb und unten hinzukommen, speichert isapdy (isapdx, isapdy sind standardmäßig 0). gridwidth insets ipady fill gridheight ipadx Bsp.198: Plazieren von Schaltflächen import java.awt.*; import java.util.*; import java.applet.Applet; public class GridBagBsp extends Applet 198vgl. PR54205 287 Programmieren in Java { 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); // 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(); } 288 Programmieren in Java Die CardLayout-Klasse CardLayout199 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) festgelegt werden. Der Punkt (breite,hoehe) die Größe. (x,y) 199 bestimmt die Anfangsposition Das CardLayout ist als Notizbuchersatz durch die Einführung der Klasse JTabbedPane in Swing fast überflüssig geworden 289 und Programmieren in Java 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); } } } 290 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()-Methode200 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(); } 200 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, 291 Programmieren in Java // 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); } 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); } } } 292 Programmieren in Java 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 Labels201, der Elemente, des Inhalts der Komponenten202. Anstatt des zusätzlichen Arguments kann auch die Methode 203 getLabel verwendet werden. Behandlung von Fokus-Ereignissen Für die Ereignisse "Fokuserhalt" und "Fokusverlust" können die Methoden 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. 201 vgl. PR52105 Nicht vergessen: Das Argument in den richtigen Objekt-Typ zu casten 203 vgl. PR54305 202 293 Programmieren in Java 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. 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 294 Programmieren in Java // 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; } */ } 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 und „add“- bzw. Komponenten, die dieses Ereignis unterstützen Button, List, TextField, MenuItem und seine abkömmlinge einschl. CheckboxmenuItem, menu und PopupMenu Scrollbar, 295 Programmieren in Java AdjustmentListener addAdjustmentListener() removeAdjustmentListener() ComponenEvent ComponenListener addComponenListener() 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() TextEvent TextListener addTextListener() removeTextListener() 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, FrameLabel, 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 Alles was von TextComponent einschl. TextArea und TextField abgeleitet ist Abb.: Event- und Listener-Typen 296 Programmieren in Java 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, KexEvent, 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 297 Programmieren in Java Methoden für die Ereignisbehandlung 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. 298 Programmieren in Java Bsp.: Bearbeitung von zwei Schaltflächen und einem Textfeld 204 // Eine Anwendung und ein Applet import java.awt.*; import java.awt.event.*; import java.applet.*; public class SchalterApplet extends Applet { 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); } } 204 PR55202 299 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-Controller205-Ansatz, d.h. die konsequente Trennung der Daten und er 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 Klassen - ButtonModel EreignisKontrollFenster Menüs - ComboBoxModel behandlung Elemente - ListModel - TreeModel - ListDataEvent - JComponent - JFrame - JMenuBar - TableModel - ListSelectionEvent - JButton - JDialog - JMenuItem - TableColumnModel - TableModelEvent - JCheckBox - JPanel - JCheckbox- BoundesRangeModel - TreeModelEvent - JRadioButton - JApplet MenuItem - Renderer - TreeExpansionEvent - JLabel - JFileChooser - JRadioBox- ListcellRenderer - TreeSelectionEvent - JTextField - JColorChooser MenuItem - TableCellRenderer - CaretEvent - JPasswordField - JdesktopPane - JMenu - TableCellEditor - MenuEvent - JTextArea - JInternalPane - JPopupMenu - TreeCellRenderer - PopupMenuEvent - JComboBox - JOptionPane - JToolBar - TreeCellEditor - JList - JSrollPane - JSeparator - ComboBoxEditor - JScrollBar - JTabbedPane - TreeNode - JSlider - MutableTreeNode - JProgressbar - Border - JTree - Scrollable - JTable - MenuElement - Document - Caret ModelBorder Bilder Sonstiges - Style implementierung - StyleDocument - DesktopManager - AbstractlistModel - BorderFactory - ...... - ImageIcon - Icon - DefaultListmodel - TitleBorder - Buttongroup - SwingConstants - AbstractTableModel - LineBorder - KeyStroke - *Listener - ... - ... - .... 205 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. 300 Programmieren in Java javax.swing.JComponent ist von java.awt.Container abgeleitet. Als Container können alle JComponents insbesondere auch andere JComponents 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 Rectangle getBounds(Rectangle rv) protected Graphics getComponentGraphics (Grphics 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() 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 301 Programmieren in Java Swing-Komponenten sind Subklassen der Klasse JComponent. 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 umfaßt: - 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) zum Inhaltsbereich. Das JPanel-Objekt soll einziges Argument sein. Bsp.: Ein in eine Anwendung geschachteltes Applet, das die Besonderheiten von Swing beim Hinzufügen eines Component-Objekts zeigt. import import import import java.awt.*; java.awt.event.*; java.applet.*; javax.swing.*; public class JButtonDemo extends Applet { JButton b1 = new JButton("JButton1"); JButton b2 = new JButton("JButton2"); JTextField t = new JTextField(20); public void init() { ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { String name = ((JButton) e.getSource()).getText(); t.setText(name + " Gedrueckt"); } }; b1.addActionListener(al); add(b1); b2.addActionListener(al); add(b2); add(t); } public static void main(String args[]) { JButtonDemo applet = new JButtonDemo(); JFrame frame = new JFrame("JButtonDemo"); frame.addWindowListener(new WindowAdapter() { 302 Programmieren in Java public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.getContentPane().add(applet, BorderLayout.CENTER); frame.setSize(300,100); applet.init(); applet.start(); frame.setVisible(true); } } Die Swing-Klasse 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 Vorgabe beim Schließen eines Fensters. Beim Schließen eines Fensters verhält sich daher ein JFrame etwas anders als ein AWT-Frame. Beim JFrame verschwindet das Fenster in den Hintergrund. Dieses Verhalten kann mit der Funktion public setDefaultCloseOperation(int operation) Damit sich ein JFrame, wie ein Frame verhält, wird angegeben: setDefauftCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE) Neben DO_NOTHING_ON_CLOSE existieren drei weitere Konstanten (WindowConstants) HIDE_ON_CLOSE DISPOSE_ON_CLOSE EXIT_ON_CLOSE // verdeckt das fenster // // schliesst die anwendung Über public int getDefaultCloseOperation() kann die eingestellte Eigenschaft beim Schließen des Fensters bezogen werden. 303 Programmieren in Java 6. Utilities 6.1 Collections Collections sind Datenstrukturen zur Aufnahme und Verarbeitung von Datenmengen. Typische Collections sind Stacks, Queues, Deques, 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 Die Klasse Vector 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. 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. Anlegen eines neuen Vektors (Konstruktor): public Vector() Einfügen von Elementen: public void addElement(Object obj) // Anhängen an des Ende der bisher vorliegenden Liste von Elementen Eigenschaften: public final boolean isEmpty() // Prüfen, ob der Vektor leer ist public final int size() // bestimmt die Anzahl der Elemente 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. 304 Programmieren in Java Vector << Konstruktoren >> public Vector() public Vector(int startKapazitaet) public Vector(int startKapazitaet, int kapazitaetsSchrittweite) << Methoden >> public final synchronized Object(elentAt(int index) public final int size() public final synchronized Object firstElement() public final synchronized Object lastElement(); public final synchronized void insertElementAt(Object obj, int index) public final synchronized void setElementAt(Object obj, int index) public final synchronized copyInto(Objrct einArray[]) public final boolean contains(Object obj) public final int indexOf(Object obj) public final int lastIndexOf(Object obj) public final synchronized boolean removeElement(Object obj) public final synchronized void removeElementAt(int index) public final synchronized void removeAllElements() public final int capacity() // gibt an, wieviel Elemente im Vektor Patz haben // (, ohne daßautomatische Größenanpassung erfolgt) public synchronized Object clone() public final synchronized String toString() Abb.: Die Klasse Vector Zugriff auf Elemente: Für den sequentiellen Zugriff steht ein Iterator zur Verfügung. Wahlfreier Zugriff erfolgt über: public Object firstElement() throws ArrayIndexOutOfBoundException; public Object lastElement() throws ArrayIndexOutOfBoundException; 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. Ein Iterator wird in Java durch das Interface Enumeration bereitgestellt206. Das Interface Enumeration. public Interface Enumeration { public boolean hasMoreElements(); public Object nextElement() throws NoSuchElementException; // setzt den internen Zeiger auf das nächste Element } In der Klasse Vector liefert die Methode public Enumeration elements() einen Enumerator (Iterator) für alle Elemente, die sich in Vector befinden. 206 und wird deshalb in der Java-Welt Enumerator genannt. 305 Programmieren in Java 6.1.2 Die Klasse Stack 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 Test: public boolean empty() // bestimmt, ob der Stack leer ist Vector Stack public Object push(Object obj) public synchronized Object pop() public synchronized Object peek() public synchronized int search(Object obj) public boolean empty() Abb.: Die Klasse Stack Anwendung: „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: 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 306 Programmieren in Java 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 444 10 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 Testprogramm207 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(); } } 207 PR61210 307 Programmieren in Java 6.1.3 Die Klasse Hashtable Dictionary {abstract} public abstract Object put (Object key, Object value) publicc abstract Object get(Object key) public abstract Enumeration elements() public abstract Enumeration keys() public abstract int size() public absztract 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 synchronized boolean contains(Object wert) public synchronized boolean containsKey(Object key) public synchronized void clear() public synchronized Object clone() protected void rehash() public synchronized 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 synchronized void load(InputStream in) throws IOException // Hier muß ein InputStream übergeben werden, der die daten der // Property-Liste zur Verfügung stellt. public synchronizred void save(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 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 308 Programmieren in Java 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. Konstruktor. public Hashtable() Einfügen von Elementen. public Object put(Object key, Object wert) Der Auruf von „put“ fügt das Schlüssel-Werte-Paar (key,wert) in die Hashtable ein. Weder key noch wert dürfen dabei null sein. Falls bereits ein Wertepaar mit dem Schlüssel key vorliegt, wird der bisherige Wert gegen den neuen ausgetauscht, und put liefert in diesem Fall den Wert zurück, der bisher dem Schlüssel zugeordnet war Zugriff auf Elemente. public Object get(Object key) // get() erwartet ein Schlüsselobjekt // und liefert den dazu passenden Wert Zusätzlich gibt es noch: public boolean contains(Object wert) // Test auf einen bestimmten Wert public boolean containsKey(Object key) // Test auf einen bestimmten Schlüssel Iteratoren. In der Klasse HahTable gibt es zwei Iteratoren: public Enumeration elements() // Iterator für die Auflistung aller Werte der Hashtabelle. public Enumeration keys() // Iterator für die Auflistung aller Schlüssel Wie üblic liefern beide Iteratoren ein Objekt, welches das Interface Enumeration implementiert. Der Zugriff erfolgt daher mit Hilfe der Methoden hasMoreElements() und nextElement(). Bsp.: Hashtabelle zum Test der Zufälligkeit von Zufallszahlen der Klasse Math.random. import java.util.*; class Zaehler { int i = 1; public String toString() { return Integer.toString(i); } } 309 Programmieren in Java 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); } } 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. 6.1.4 Die Klasse Properties Die Klasse Properties ist aus HashTable abgeleitet und repräsentiert ein auf String-Paare spezialisiertes Dictionary, das es erlaubt, seinen Inhalt auf einem externen Datenträger zu speichern oder von dort zu laden. Ein solches Objekt wird auch als Property-Liste (oder Eigenschaftsliste) bezeichnet. 310 Programmieren in Java 6.1.5 Die Klasse BitSet Sie umfaßt eine Menge boolescher Flags, die mit logischen Operationen wie „and()“, „or()“, „xor()“ verknüpft werden können. BitSet public void and(BitSet bs) public void or(BitSet bs) public void xor(BitSet bs) public void clear(int index) public boolean get(int index) public void set(int index) public int size() Abb.: Die Klasse BitSet 6.2 Java 2 Collections 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. 311 Programmieren in Java 6.2.1 Das Interface Collection, das Interface Iterator und das Interface Comparator Das Interface Collection Das Interface Collection bildet die Basis aller Collection-Klasse und –Interfaces des JDK 1.2. << interface >> Collection public void clear(); public boolean add(Object o); public boolean addAll(Collection c); 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); public boolean contains(Object o); // Rückgabewert ist true, falls das vorgegebene Element gefunden werden konnte public boolean containsAll(Collection c); public boolean equals(Object o); public boolean isEmpty(); public int size(); public boolean retainAll(Collection c); public Iterator iterator(); publicObject [] toArray(); public Object [] toArray(Object [] a); public int hashCode(); public String toString() // Rückgabewert ist die Zeichenketten-Repräsentation der Kollektion. Abb.: Das Interface Collection 312 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(); // das zuletzt gelöschte Element wird anschließend mit remove() gelöscht 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 6.2.2 Die Collections des Typs List Collections des Typs List fassen eine Menge von Elementen zusammen, auf die sequentiell oder über Index (-positionen) zugegriffen werden kann. Wie Vektoren der Klasse Vector 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. 313 Programmieren in Java Das Interface List << 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, CallsCastException 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(); public int hashCode(); public Iterator iterator(); public ListIterator listIterator(): public ListIterator listIterator(int startIndex); public Object set(int index, Obeject element); public List subList(int fromIndex, int toIndex); public Object [] toArray(); public Object [] toArray(Object [] a); 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 UnsupportedException auslösen und diverse Methoden abstract deklariert sind. LinkedList realisiert die doppelt verkettete, lineare Liste ArrayList implementiert die Liste als Array von Elementen. 314 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 >> 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(); puplic 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(); 315 Programmieren in Java 6.2.3 Die Collections 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). 316 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 anfangskaazitaet); public HashSet(int anfangskapazitaet, int 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(); public Object fisrt(); public Object last(); TreeSet208 public TreeSet() public TreeSet(Collection collection); public TreeSet(Comparator vergleich); public TreeSet(SortedSet collection); 208 implementiert die sortierte Menge mit Hilfe der Klasse Treemap, verwendet einen Red-Black-Tree als Datenstruktur 317 Programmieren in Java 6.2.4 Die Collections 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 implemetierend 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(Collection 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); 318 Programmieren in Java 6.2.5 Die Klasse Collections Im Paket java.util gibt es die Klasse Collections mit einer großen Anzahl statischer Methoden zur Manipulation und Verarbeitung von Collections209, z.B. zum Durchsuchen, Sortieren, Kopieren und Synchronisieren von Collections. Sortieren und Suchen static void sort(List list); static void sort(List list, Comparator c); Mit Hilfe von sort() können beliebige Listen sortiert werden. Argumente sind vom Typ List und optional vom Typ Comparator. Fehlt die Angabe von Comparator, dann wird die Liste in ihrer natürlichen Ordnung sortiert. Alle Elemente müssen dann das Comparable-Interface implementiert haben und ohne Typfehler paarweise vergleichbar sein. Gemäß JDK-Dokumentation verwendet diese Methode ein modifiziertes Mischsortieren (MergeSort), das auch im „Worst.Case“ eine Laufzeit von N log( N ) hat. static int binarySearch(List list, Object key); static int binarySearch(List list, Object key, Comparator c); Für große Listen ist es sinnvoll, die Liste zu sortieren und anschließend „binäre Suche“ zu verwenden. Das gewünschte Element wird durch Intervallschachtelung mit fortgesetzter Teilung (Halbierung) der Intervallgröße immer weiter eingegrenzt, und das gesuchte Element ist spätestens nach log(N) Schritten gefunden. 209 Ebenso gibt es eine Hilfsklasse Arrays für Operationen mit Arrays (Feldern), vgl. 2.2.3.3 319 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 Whitespaces210 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 Satzes String satz = “Dies ist ein Satz“; StringTokenizer tokenizer = new StringTokenizer(satz); while (tokenizer.hasMoreTokens()) { System.out.println(tokenizer.nextToken()); } 210 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. 320 Programmieren in Java 6.4 Die Klasse Random Konstruktoren Random() // Erzeugt einen neuen Zufallszahlengenerator. Der Seed211 wird auf die aktuelle Zeit gesetzt. Random(long seed) // Erzeugt einen neuen Zufallszahlengenerator und benutzt den Parameter als Seed. Methoden 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.0212. 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. Startwert für jede Zufallszahl ist ein 48 Bit Seed. Das Woer „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 212 wird von Math.random() verwendet. 211 321 Programmieren in Java 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 übergebebe Objekt vom Typ Date, dann verhält sich die Funktion wie das zuvor // angegebene compareTo(). Anderenfalls wirft die Methdoe ein ClassCastException ... Abb.: Die Klasse Date Bsp.213: import java.util.Date; class DatumundZeit { public static void main(String args[]) { System.out.println(new Date() ); } } 213 PR65100 322 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. 323 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 Abb.: Konstanten aus der Klasse Calendar Bsp.: Ausgabe wichtiger Datumsfelder214 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 "); 214 PR65100 324 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 Programmieren in Java 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) { 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 staticTimeZone 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 Zeitzonen 215 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]); } } 215 Unter Windows mit dem JDK 1.3 sind dies 321 Zeitzonen, PR65100 325 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 final DateFormat getDateInstance() public final DateFormat getTimeInstance() public 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 lokal216) 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) 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 Sprachen217: Das folgende Programm erzeugt die Ausgabe für Deutschland und anschließend für Italien. 216 Unter Locale sind für einige Sprachen Konstanten vordefiniert: ENGLISH, FRENCH, GERMAN, ITALIAN, JAPANESE, KOREAN, CHINESE, SIMPLIFIED_CHINESE, TRADITIONAL_CHINESE 217 vgl. PR65200 326 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 SiimpleDateFormat(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 java.text.SimpleDateFormat (class 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. Symbol G y Bedeutung Ära Jahr Präsentation Text Nummer 327 Beispiel AD 1998 Programmieren in Java M MM MMM MMMM d h H m s S E EEEE D F w W a k K z ‘ ‘ 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. Wochr im Jahr Woche im Monat Am/pm-Txt Stunde am Tg (1-24) Stunde (0-11) Zeitzone Zeichen für Text Einzelnes Hochkomma Nummer Nummer Text Text Nummer Nummer Nummer Nummer Nummer Nummer Text Text Nummer Nummer Nummer 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 328 ‘ Programmieren in Java 7. Ein- und Ausgabe In Java werden Ein-, Ausgabeoperationen über sog. Datenströme218 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.io219 , das bei jedem Programm mit Ein-/Ausgabeoperationen importiert werden muß. Einfache Strom-Methoden erlauben nur das Versenden von Bytes mit Hilfe von Datenströmen220. 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. Der Begriff Strom kommt aus dem Betriebssystem Unix und bezieht sich auf „Pipes“. Eine Pipe ist ein nicht interprtierbarer 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. 219 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. 220 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. 218 329 Programmieren in Java ByteArrayInputStream FileInputStream DataInput BufferedInputSteam DataInputStram FilterInputStream InputStream Object PipedInputStream SequenceInputStream StringBufferedInputStream LineNumberInputSteam PushbackInputStream RandomAccessFile ByteArrayOutputStream FileOutputStram OutputStream FilterOutputStream DataOutput PipedOutputStream Throwable Exception IOException Abb.: java.io 330 BufferdOutputStream 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 331 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 FiIenputStream-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. PipedOutputStram. 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 Quellen zu einem Eingabestrom können sein: 1. Ein Array von Bytes 2. Ein Zeichenketten-Objekt 3. Eine Datei 4. Eine „Pipe“ 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“221 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)“ 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) 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 synchronized void mark(int readLimit) Die Methode markiert die aktuelle Position im Strom für eine spätere Rückkehr. public synchronized 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. 7.1.2 OutputStream 221 Rückgabe „-1“ bedeutet zum Unterschied von C nicht, daß ein Fehler aufgetreten ist. 332 Programmieren in Java 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 „Multuthreading“. 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) 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. 7.2 Gefilterte Ströme 333 Programmieren in Java 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: public FilterInputStream(InpuStream 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 334 Programmieren in Java 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) 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 222 boolean readBoolean() throws IOException byte readByte() throws IOException byte readUnsignedByte() throws IOException EOFException byte readChar() throws IOException byte readShort() throws IOException byte readUnsignedShort() throws IOException int readInt() throws IOException long readLong() throws IOException float readFloat() throws IOException double readDouble() throws IOException String readUTF() throws IOException222 UTF (Unicode Transmission Format) ist ein spezielles Format zum Kodieren von 16-Bit-Unicode-Werten 335 Programmieren in Java 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 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 void flush() void close() abstract void write(int b) void print(Object o) void print(String s) void print(char c) void print(char[] puffer) void print(char c) void print(int i) void print(float f) void print(double d) void println(Object f) 336 Programmieren in Java public public public public public public public public void void void void void void void void println(String s) println(char c) println(char[] puffer) println(char c) println(int i) println(float f) println(double d) println() 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() 337 Programmieren in Java 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 „..“. 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 accept, 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. Neben dem Zugriff auf Verzeichniseinträge gibt es Methoden 223 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. 7.4 Die RandomAccessFile-Klasse 223 Alle Methoden geben true zurück, wenn sie ihre Aufgabe erfolgreich ausführen konnten; anderenfalls geben sie false zurück 338 Programmieren in Java 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 IOException Bei der Übergabe des File-Objekts wird die durch dieses Objekt spezifizierte Datei geöffnet. public RandomAccessFile(String name, String mode) throws IOException 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 Dateiposition224 bestimmt werden. Weiterhin stehen lesende und schreibende Zugriffsmethoden zur Verfügung. 224 Der Dateipositionswert in den Methoden seek und getFilePointer ist die Anzahl der Bytes vom Anfang bis zum Ende der Datei. 339 Programmieren in Java RandomAccessFile <<Konstruktoren public randomAccessFile(File datei, String zugriffsmodus); // 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); << Methoden >> public long getFilePointer() publc void seek(long pos) // setzt den Dateizeiger – bezogen auf den Dateianfang – auf eine // bestimmte Stelle, an der der nächste Lese- und Schreibzugriff // stattfindet public void skipBytes(int n) public long length() public final boolean readBoolean(); public final byte readByte(); public final char readChar(); public final double readDouble(); public final float readFloat); public read final readInt(); public final long readLong(); public final short readShort(); public final String readUTF(); public final void readFully(byte b[]); public final void readFully(byte[], int off, int laenge); public final String readLine(); public final int readUnsignedByte(); public final int readUnsignedShort(); public int read(); public int read(byte b[]); public int read(byte b[], int off, int laenge); public final void writeBoolean(boolean w); public final void writeByte(int w); public final void writeBytes(String s); public final void writeBytes(String s); public final void writeChar(int w); public final void writeChar(String s); public final void writeDouble(double w); public final void writeFloat(float w); public final void writeInt(int w); public final void writeLong(int w); public final void writeShort(int w); public final void writeUTF(String str); public void write(int b); public void write(byte b[]); public void write(byte b[], int off, int laenge); Abb.: Die Klasse RandomAccessFile 340 Programmieren in Java 7.5 Die Klasse StreamTokenizer Die Klasse StreamTokenizer abrbeitet 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. 341 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 slahSlashComments(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 342 Programmieren in Java 7.6 Klassen für spezielle nützliche Ströme Die LineInputStream-Klasse Die SequenceInputStream-Klasse Die PushBackInputStream-Klasse 343 Programmieren in Java 7.6 Java 1.1 IO-Ströme 7.6.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. 344 Programmieren in Java 7.6.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 { public Reader(); publicc void close(); // schliesst den Eingabestrom public void mark(int readAheadlimit); // markiert eine bestimmte Position innerhalb eines Eingabestroms public boolean markSupported(); // überprüft, ob Markieren unterstützt wird public int read() // 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[]); // übernimmt eine Reihe von Zeichen in den als Parameter angegebenen // Array public int read(char cbuf[], int off, int len); // ü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); // überliest Zeichen im Eingabestrom public boolean ready() // liefert True, falls der nächste Aufruf von read erfolgen kann, ohne // daß die Eingabe beendet ist public void reset(); // 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. 345 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. 346 Programmieren in Java 7.6.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 void close(); // Schliessen des Ausgabestroms public void flush() // 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[]); // Varianten von write, die ein Array von Bytes oder ein String-Objekt // erwarten und dieses durch Aufruf von write() ausgeben abstact public void write(char einPuffer[], int off, int len); public void write(String str); public void write(String str, int off, int len); } 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. 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: 347 Programmieren in Java 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. 348 Programmieren in Java 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 BufferdWriter(Writer aus) public BufferedWriter(Writer aus, int groesse) 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) 349 Programmieren in Java 7.6.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"); } 350 Programmieren in Java // 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"); } // 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"); } } } 351 Programmieren in Java 8. Serialisierung 8.1 Grundlagen Serialisierung225 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. 225 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. 352 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 Datei226 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 ObjectOutput, ObjectStreamConstants) geschaffen. ObjectOutputStream implementiert das Interface ObjectOutput, z.B. die Funktion writeObject() zum Schreiben von Objekten. OutputStream { abstract } ObjectOutputStream << Konstruktoren > public ObjetOutputStream(OutputStrean 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[]) trows 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 Die Methode public final void witeObject(Object obj) throws IOException schreibt folgende Daten in den OutputStream: 226 Der Dateiname wird häufig so gewählt, daß er mit dem Präfix „ser“ endet. 353 Programmieren in Java - 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“. 354 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 > << Methoden >> public final Object readObject(Object obj) 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 ObjectInputStram Analog zum Interface ObjectOutput gibt es hier das Interface ObjectInput (interface java.io.ObjectInput extends DataInput) 355 Programmieren in Java << interface >> ObjectInput << Methoden >> public Object readObject() throws ClassNotFoubdException, IOException public int read() throws IOException // liest ein bayte 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 ObkectInput Das Deralisieren 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 hatte 227. 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(); } 227 Abgesehen von den nicht serialsierten Membervariablen des Typs static oder transient. 356 Programmieren in Java catch(ClassNotFoundException e) { System.err.println(e.toString()); } catch(IOException e) { System.err.println(e.toString()); } } } 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. 357 Programmieren in Java 9. Netzwerkprogrammierung Das Paket java.net enthält Klassen zur Programmierung von TCP/IP228Netzwerkzugriffen. 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 InetAdress Zur Adressierung von Rechnern im Netz wird die Klasse InetAdress des Pakets java.net verwendet. Eine InetAdress enthält sowohl eine IP-Adresse229 als auch den symbolischen Namen des jeweiligen Rechners230. Die beiden Bestandteile können mit den Methoden getHostName() und getHostAdress() abgefragt werden. Mit Hilfe von getAdress() kann die IP-Adresse auch direkt als byte-Array (mit 4 Elementen) beschafft werden. Zur Generierung eines InetAdress-Objekts stehen zur Verfügung: public static InetAdress getByname(String host) throws UnKnownHostException // erwatet einen String mit der IP-Adtesse oder dem Namen als Argument public static IntetAdress getLocalHost() throws UnKnownHostException // liefert ein InetAdress-Objekt für den eigenen Rechner 228 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) 229 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. 230 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. 358 Programmieren in Java 9.1.2 Die Klasse URL Im WWW werden Rtessourcen über URLs (Universal Ressource Locator) identifiziert. Eine URL besteht aus: einem Protokollnamen, z.B. http231 (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: //user232:password233@host234:port235/url-path236. 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. Feht 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. 231 Mit dem Hypertext Tranfer Protocol wird auf Inhalte des Web zugegriffen. Die URL für Dienste im Web beginnt mit http. 232 Optionaler Benutzername 233 optionales Paßwort, ein Paßwort ohne Benutzername kann nicht angegeben werden 234 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. 235 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. 236 Auf den server-namen 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. 359 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 // public URLConnection openConnection() throws IOException // 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 URL 360 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, FtpURLConnection oder MailtoURLConnection. URLConnection { abstract } << Methoden >> public Object getContent() throws IOException, UnkownException // liefert den Inhalt, auf den die URL verweist. 361 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. Sockets237 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. Berbindungsaufbau 2. Lesen bzw. Schreiben der Daten 3. Verbindungsabbau 237 Sockets wirden zu Beginn der achtziger Jahre für die Programmiersprache C entwickelt. 362