Programmieren in Java Prof. Jürgen Sauer Programmieren in Java Skriptum zur Vorlesung im WS 2000/2001 1 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 Der erste Versuch 0.3.1 Das erste Programm 0.3.2 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 Programmieren in Java 2. Hauptbestandteile der Sprache 2.1 Token 2.1.1 Schlüsselworte 2.1.2 Bezeichner und Namenskonventionen 2.1.3 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 3 Programmieren in Java 2.6.3 Die Methodenmodifizierer 2.6.4 Rückgabewerte von Methoden 2.6.5 Methodenname und Parameterliste 2.6.6 Überladen und Überschreiben von Methoden 3. 3.1 Grafische Benutzeroberflächen und Applets Ereignisbehandlung unter grafischen Benutzeroberflächen (Graphical User Interface) 1. Gestaltung von grafischen Benutzeroberflächen mit Hilfe der AWT-Klassen 2. Ereignisbehandlung unter grafischen Benutzeroberflächen 3. Anwendung lokaler Klassen für die Ereignisbehandlung 4. Externe- und interne Darstellung numerischer Werte 5. 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-Clips Sound-Ausgabe in Applets Sound-Ausgabe in Applikationen 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 4. Grafik und Bildverarbeitung 4.1 Allgemeine Zeichenvorgänge 4.1.1 Punkte, Linien, Kreise, Bögen 4.1.2 Farbangaben 4.1.3 Textausgabe über den Zeichenmodus Die Klasse Font Die Klasse FontMetrics 4.1.4 Die Java-Zeichenmodi 4 Programmieren in Java 4.2 Das Zeichnen von Bildern 4.2.1 Laden und Anzeigen einer Bitmap 4.2.2 Die Klasse MediaTracker 4.2.3 Entwickling einer eigenen Bitmap-Komponente 4.2.4 Überspielen einer Folge von Bitmaps 4.3 Grafikoperationen mit Java 2D 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 Container 5.3.1 Panels 5.3.2 Frames 5.3.3 Menüs 5.3.4 Dialoge 5.4 Die Layout-Manager 5.4.1 Layout-Regeln 5.4.2 Die einzelnen Layout-Manager 5.5 Die Event-Modelle 1.0 und 1.1 5.5.1 Der AWT-Handler 1.0 5.5.2 Das Event-Handling 1.1 5.6 Benutzerschnittstellen mit Swing 6. Utilities 6.1 6.1.1 6.1.2 6.1.3 6.1.4 Collections Die Klasse Vector Die Klasse Stack Die Klasse Hashtabelle Die Klasse Bitset 6.2 Java 2 Collections 5 Programmieren in Java 6.3 6.4 6.5 Die Klasse StringTokenizer Die Klasse Random Die Klassen Date, Calendar 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 9. Netzwerkprogrammierung 6 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 ... 7 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 JavaSoft1 halten permanent die aktuellsten Informationen im Internet bereit. 0.2 Java-Maschine und Programmiersystem Die Java-Maschine2 nutzt die Möglichkeiten des Universalrechners. Die Eingabemöglichkeit dient zum Einschreiben von Daten und Programmen in den Speicher der Zentraleinheit (central processing unit, CPU). Die Zentraleinheit verarbeitet die eingegebenen Daten nach den Anweisungen des eingelesenen Programms, das zur Ausführung in binäre Form gebracht wurde. Das Steuerwerk überwacht die Ausführung des Programms (Entschlüsseluung der binär dargestellten Anweisungen). Das eigentliche Abarbeiten der Programmanweisungen (arithmetische, logische Operatoren) in der gegebenen Reihenfolge übernimmt das Rechenwerk. Daten, Programme, die im Hauptspeicher keinen Platz finden, sind auf externen Speichermedien ausgelagert (Magnetband, Magnetplatte, Floppy Disc). 1 http://java.sun.com Die virtuelle Maschine von Java (Java VM) ist wirklich eine Maschine, d.h. Hardware, die bestimmte Operationscodes (Maschinenanweisungen) interpretieren kann. Es wurden bzw. werdem Chips entwickelt, die diese Maschine in Hardware implementieren. Im Regelfall geschieht aber eine Emulation durch das Laufzeitsystem auf der jeweiligen Zielplattform. 2 8 Programmieren in Java Eingabeeinheit Hauptspeicher Externer Speicher Ausgabeeinheit Steuerwerk 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“. Java-Programm Daten Eingabe (Tastatur) Externer Speicher System, Systemprogramme Java-Maschine Ausgabe (Bildschirm) 9 Externer Speicher Java-Compiler Java-Quellprogramme Java-Laufzeitsystem Java-BytecodeDateien Programmieren in Java 0.3 Der erste Versuch 0.3.1 Das erste Programm Quelltext: import java.lang.*; /* ErstesProgramm ist eine Applikation, die den einfachen Gebrauch von Zeichenketten aufzeigt */ public class ErstesProgramm extends Object { // Beginn der Ausfuehrung vom Programm public static void main(String args[]) { // Ausgabe auf das Standard-Ausgabegeraet System.out.println( "Das erste Programm der Vorlesung Programmieren in Java."); } } Das Programm umfaßt zwei Teile: eine Klassendefinition und einen Programmabschnittt, der durch die Methode main() bestimmt ist. Jede Java-Anwendung besteht aus mehreren Klassen. Die Klasse, die die Ausgangsbasis für Java-Programme ist, muß die main()-Methode benutzen: public static void main(Strig 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: /* 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."); } } 10 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 Ausgabe 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 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); } 11 Programmieren in Java 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 Abb. 0.3-1: Java-Operatoren 12 Programmieren in Java System.out.println(s); ist eine Anweisung. Anweisungen stehen für eine einzelne Aktion, die in einem Java-Programm ausgeführt wird. Jede Anweisung wird mit einem Strichpunkt „;“ abgeschlossen. Einige Anweisungen erzeugen einen Wert, wie das bspw. beim Addieren zweier Zahlen der Fall ist. Derartige Anweisungen werden als Ausdrücke bezeichnet. Ein Ausdruck ist eine Anweisung, die als Ergebnis einen (Rückgabe-) Wert produziert. Dieser Wert kann zur späteren Verwendung im Programm gespeichert, direkt in einer anderen Anweisung verwendet oder überhaupt nicht beachtet werden. Ausdrücke können Konstanten, Variablen, Operatoren beinhalten. Ausdrücke sind vielfach mit unären und binären Operatoren verknüpft. Trennzeichen: Das sind Symbole und Zusammenfassungen von Quellcode: ( ) { } [ ] ;. ,. „(“: wird sowohl zum Öffnen einer Parameterliste für eine Methode als auch zur Festlegung der Priorität für Operationen in einem Ausdruck benutzt. „)“: wird sowohl zum Schließen einer Parameterliste für eine Methode als auch zur Festlegung der Priorität für Operationen in einem Ausdruck benutzt. Bsp.: public static void main(String args[]) „{“: wird zu Beginn eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt. „}“:wird an das Ende eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt. Bsp.: Methoden werden in Java durch einen Anweisungsblock bestimmt. Ein Anweisungsblock besteht in der Regel aus einer Reihe von Anweisungen, die von geschweiften Klammern umschlossen sind. public static void main(String args[]) { String s; s = "Das erste Programm der Vorlesung Programmieren in Java."; System.out.println(s); } „[“: steht für eine Ausdruck, der als Index für ein Datenfeld (Array) steht. „]“: folgt einem Ausdruck, der als Index für ein Datenfeld steht. Bsp.: String args[] = {“Juergen“, “Hubert“, “Josef“, “Liesel“, “Christian“}; definiert einen Array mit 5 Komponenten, die alle den gleichen Typ besitzen Datenfelder (Arrays) dienen zum Speichern von Elementen, die alle denselben Typ aufweisen. Jedem Element wird innerhalb des Datenfelds ein eigener Speicherplatz zugewiesen. Dieser Speicherplatz ist für den leichten Zugriff durchnumeriert. [0] [1] [2] [3] [4] “Juergen“ “Hubert“ “Josef“ “Liesel“ “Christian“ Auf den Wert einer Komponenten kann über den Array-Namen (z.B. args), gefolgt von einem Index in eckigen Klammern (z.B. [0]) zugegriffen werden. Mit args[0] = “Roland“; kann dem ersten Element des Array args ein neuer Wert zugewiesen werden. „;“: dient zum Beenden einer Anweisung. „,“: wird häufig als Begrenzer (z.B. in einer Parameterliste) benutzt. „.“: wird als Dezimalpunkt als auch zum Trennen solcher Dinge wie Paketnamen von Klassennamen oder Variablennamen benutzt (z.B. System.out.println(s);). Standardmäßig haben Klassen Zugriff auf die Klassen im Paket „java.lang“. Zur 13 Programmieren in Java 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. Konsole: Arbeitsspeicher: javac ErstesProgramm.java Externspeicher 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 14 Programmieren in Java 0.3.2 Bestandteile eines Programms Wesentliche Programmelemente in Java sind: - Anweisungen Anweisungen3 gehören zu den elementaren ausführbaren Programmelementen. Eine Anweisung kann eine Deklaration enthalten, einen Ausdruck4 auswerten oder den Programmablauf5 (Auswahl-, Iterations-, Sprung-Anweisungen und return-, throw-Anweisung) steuern. - Blöcke Ein Block6 ist eine Zusammenstellung von Anweisungen, die nacheinander ausgeführt werden. Ein Block kann eigene Variable definieren, die nur innerhalb des Blocks sichtbar sind. Sie werden beim Aufruf des Blocks angelegt und beim Verlassen des Blocks zerstört. Innerhalb eines Blocks sind nur die lokalen Variablen des Blocks und die lokalen Variablen des umgebenden Blocks bzw. der umgebenden Methode sichtbar. Nach außen stellt sich der Block als eine einzige Anweisung dar. - Methoden Methoden7 unterscheiden sich von Blöcken folgendermaßen: -- Sie haben einen Namen und können von verschiedenen Stellen des Programms aufgerufen werden. -- Sie sind parametrisierbar -- Sie können einen Rückgabewert besitzen. Methoden werden in Java immer lokal zu einer Klasse definiert. - Klassen Sie8 enthalten Variablen zur Beschreibung des Zustands von Objekten und Methoden zur Beschreibung des Verhaltens von Objekten. - Schnittstellen Eine Schnittstelle (Interface) ist eine Sammlung von Methoden, die einen Namen besitzen, aber nicht implementiert sind. Ein Klasse kann beliebig viele Schnittstellen implementieren. Dadurch wird die Klasse zur Implementierung der Methode gezwungen, deren Namen von der Schnittstelle definiert wurden. Falls zwei unterschiedliche Klassen, dieselbe Schnittstelle implementieren, können beide auf Aufrufe der Methode, die in der Schnittstelle definiert sind, reagieren. Allerdings kann die Reaktion auf diese Methodenaufrufe bei einzelnen Klassen total unterschiedlich sein. - Pakete Ein Paket ist eine Sammlung von Klassen. Jede Klasse in Java gehört zu einem Paket. Pakete ermöglichen, 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. 3 vgl. 2.4 vgl. 2.4.5 5 vgl. 2.4.6 6 vgl. 2.4.1 7 vgl. 2.6 8 vgl. 2.5 9 CallBack-Methoden sind von der abgeleiteteten Klasse zur Verfügung gestellte Methoden, die vom Browser bzw. Appletviewer aufgerufen werden 4 15 Programmieren in Java 1. Einführung in die Java-Programmierung 1.1 Übersicht zur Entwicklung der Programmiersprache Java Java ist in den Entwicklungslaboren der amerikanischen Firma Sun Microsystems10 entstanden. Man entschied sich bei Sun zur Realisierung eines im Jahre 1990 begonnenen Projekts11 für eine neue Programmiersprache, da bisher entwickelte Programme mit vorliegenden Programmiersprachen zu große Schwächen zeigten. Der erste Versuch war nur bedingt erfolgreich. Lediglich der damals im Internet verbreitete Mosaic-Browser12 wurde zu einer Zielplattform der neuen Programmiersprache13, die Ende 1994 für das Internet umgearbeitet wurde und über das Netz frei und umsonst verteilt wurde. 1995 wurde die neue Programmiersprache mit dem Namen Java14 der InternetÖffentlichkeit in Kombination mit einem Browser, HotJava, präsentiert. HotJava war die erste komplexe und vollständig in Java geschriebene Anwendung, der erste Java-fähige Browser und damit die Präsentationsform für die ersten Java-Applets. Außerdem war dieser Browser eine wesentliche Ergänzung des ersten JavaEntwicklungstools von Sun – das Java Develelopment Kit (JDK 1.0). Ein kommerzielles Produkt, der Java Workshop15, wurde kurz nach der Präsentation von JDK 1.0 bereitgestellt. Natürlich gab es im JDK noch diverse Kinderkrankheiten. Im zweiten Quartal 1997 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 16 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. 17 Programmieren in Java Systemen befinden. Objekte können mit Java-Programmen über URLs20 vom gesamten Web genauso wie auf dem lokalen Rechner geöffnet und bearbeitet werden. Wichtige Teile der Anwendung bzw. der Daten können lokal vorhanden sein, andere werden bei Bedarf geladen. - Java ist interpretiert. Ein gewisser Teil des Java-Codes (ca. 20 %) werden vom Container, dem Browser interpretiert. Der Java-Quellcode wird mit dem Java-Compiler in Bytecode (architekturneutrales Object-Code-Format) kompiliert. Bytecode ist nicht lauffähig, bis er von der Java-Laufzeitumgebung21 interpretiert wird. Java-Compiler (Pentium) Java-Interpreter (Pentium) Java-Code Java-Compiler (SPARC) Java-Interpreter (SPARC) Abb. 1.2-1: - Java ist stabil, d.h. zuverlässig. Die Stabilität einer Programmiersprache zeigt sich darin, daß während der Kompilierungsphase der gößte Teil der Datenüberprüfung ausgeführt werden kann. Java ist stark typisiert. Damit können Fehler früh gefunden werden. Ein weiteres Stabilitätskriterium von Java-Programmen ist die eingebaute Begrenzung der Zugriffsmöglichkeiten auf den Hinzu kommt auch noch die anschließende Speicherbereich des Rechners 22. Sicherheitsüberprüfung durch den „Linker“. Der Linker ist ein Teil der Laufzeitumgebung, das die im System eingehenden Daten überprüft. 20 Rechner sind über IP-Nummern bzw. über Alias-Namen (Domain-Name-System, DNS) im Internet eindeutig adressiert. Innerhalb des Internet müssen aber auch alle Daten und Programme über unverwechselbare InternetAdressen bestimmt sein. Der Name dieser Internet-Adressen für konkrete 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 22 Ungeprüfte Zugriffe auf Speicherbereiche des Rechners ermöglicht C/C++ (Zeigerarithmetik, implizite Deklaration) 18 Programmieren in Java - Java gilt als sicher. In dieser Hinsicht muß sich Java allerdings noch bewähren23. - Java ist auf verschieden Systemen mit unterschiedlichen Prozessoren und BetriebssystemArchitekturen lauffähig (architekturneutral). Die komplette Java-Bytecode kann auf jedem Prozessor ausgeführt werden, der einen javafähigen Browser (bspw. die virtuelle Maschine von Java24) unterstützt. Plattformunabhängiger Binärcode wird allerdings nicht erzeugt, sondern Java-Bytecode wird während der Laufzeit in systemeigenen Maschinencode übertragen (interpretiert). - Java unterstützt „Multithreading“. „Multthreading bedeutet: Mehrere Aufgaben oder Prozesse können gleichzeitig ausgeführt werden. Nicht die quasi gleichzeitige Ausführung mehrerer Programme (Multitasking), sondern die gleichzeitige, parallele Ausführung von einzelnen Programmschritten (oder zusammenhängenden Prozessen) ist „Multithreading“. Das kann bedeuten: Innerhalb eines Programms können mehrere Dinge gleichzeitig geschehen, mehrere Faden / Threads eines Programms können gleichzeitig verfolgt und abgearbeitet werden. 23 Verschiedene Sicherheitslücken wurden aufgedeckt und wurden inzwischen beseitigt. Die virtuelle Maschine wird auch als Java Interpreter oder Java Environment (Java-Laufzeitumgebung) bezeichnet 24 19 Programmieren in Java 1.3 Einstieg in die Java-Programmierung 1.3.1 Die Software für die Java-Programmierung Das JAVA Development Kit (JDK) enthält die Entwicklungsumgebung zum Schreiben von Java Programmen. Das JDK ist für Sun-SPARC-Systeme mit Solaris 2.2 (oder höher) sowie für Windows NT und Windows 95 über das Internet25 erhältlich. Nach der Installation hat das Java-Verzeichnis26 folgenden Inhalt27: Abb. 1.3-1: Inhalt des Java-Verzeichnisses Innerhalb des Java-Verzeichnisses 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 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 20 Programmieren in Java mit angelegt. Darin befinden sich die entkomprimierten Java-Dateien, die sonst nur im gepackten Zustand (Datei SRC.ZIP) vorhanden sind. Es ist sinnvoll, die in der JDK-Umgebung verfügbaren Werkzeuge von allen Verzeichnissen aus zugänglich zu machen. Für Windows-NT und Windows-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% 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. 21 Programmieren in Java 1.3.2 Applets und Anwendungen Java Programme werden in zwei Hauptanwendungs-Gruppen gegliedert: Applets und Anwendungen. Applets sind Java-Programme, die über das WWW heruntergeladen und von einem Web-Browser auf dem Rechner des Anwenders ausgeführt werden. Applets können nur auf einem javafähigen Browser ausgeführt werden bzw. mit einem Tool des JDK, dem Appletviewer, gesichtet werden. Java-Anwendungen sind allgemeine, in der Java-Sprache geschriebene Programme. Zum Ausführen von Java-Anwendungen ist kein Browser nötig. 1.3.2.1 Entwicklung von Java-Anwendungen 1. Aufgabe: Erstelle eine Anwendung, die den Text „Willkommen in der JavaWelt“ ausgibt. Lösungsschritte: 1) Erstelle die folgende Datei mit dem Namen „Willkommen.java“ mit Hilfe eines Dateiaufbereiters (Editor): class Willkommen { public static void main(String args[]) { System.out.println("Willkommen in der Java-Welt!"); } } Das Programm umfaßt zwei Teile: eine Klassendefinition und ein Programmabschnitt, der unter main() angegeben ist. 2) Speichern der Datei mit dem Namen „Willkommen.java“ unter einem beliebigen Verzeichnis31. 3) Aufruf des Java-Übersetzers über die folgende Befehlszeileneingabe: javac [optionen] dateiname. optionen .... bestimmen das Verhalten vom Compiler dateiname ... Name der Datei mit dem Java-Quellcode. „javac“ fordert, daß der Quelltext in Dateien steht, deren Dateiname die Extension „.java“ besitzt. „javac“ kompiliert den Quellcode in Java-Bytecode und speichert seine Ausgabe in einer Datei mit dem Namen „dateiname.class“. Standardmäßig werden .classDateien im gleichen Verzeichnis wie die .java-Quelldatei erzeugt32. Im vorliegenden Fall ist der Aufruf: javac Willkommen.java. 4) Ausführen der Bytecode-Datei „Willkommen.class“ mit dem Java-Interpreter java: „java Willkommen“. Der im JDK enthaltene Interpreter heißt „java“. Falls alles richtig gelaufen ist, erscheint in der letzten (Ausgabe-) Zeile: Willkommen in der Java-Welt!. 31 32 Vgl. PR13210 Mit der Option „-d“ im javac-Aufruf kann die .class-Dateii an einem anderen Ort gespeichert werden. 22 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 23 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 [0] [1] [2] [3] [4] “Juergen“ “Hubert“ “Josef“ “Liesel“ “Christian“ Nach 4 Vergleichen hat das Datenfeld args folgende Gestalt angenommen: args [0] [1] [2] [3] [4] “Christian“ “Juergen“ “Josef“ “Liesel“ “Hubert“ Die erste Position (args[0]) ist im Datenfeld damit schon richtig eingeordnet. Danach muß jetzt der Name an der 2. Position ([1]) mit den übrigen Namen des Datenfelds verglichen werden. Nach 3 Vergleichen zeigt sich folgendes Bild: 24 Programmieren in Java args [0] [1] [2] [3] [4] “Christian“ “Hubert“ “Juergen“ “Liesel“ “Josef“ „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 [0] [1] [2] [3] [4] “Christian“ “Hubert“ “Josef“ “Liesel“ “Juergen“ Der nächste Durchgang führt dann zum sortierten Datenfeld: args [0] [1] [2] [3] [4] “Christian“ “Hubert“ “Josef“ “Juergen“ “Liesel“ 2) Der soeben 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. 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 25 Programmieren in Java 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“. 26 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. 27 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); } } 4) Der Aufruf java FloatDemo 17 4 führt dann zu folgenden Ausgabe: x = 17.0 x = 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 = 10.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 28 Programmieren in Java 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: 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()); } } } 29 Programmieren in Java 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 demnach nötig, das Applet in einem HTML-Dokument34 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. 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 34 eine entsprechende Referenz innerhalb einer HTML-Seite mit einem speziellen Tag, dem <APPLET>-Tag erledigt die Einbettung in den Browser 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. 30 Programmieren in Java 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 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. 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. 31 Programmieren in Java 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: Verrebungshierarchie von WillkommenApplet Die Beziehung zwischen ImageObserver und Component ist eine Schnittstelle. ImageObserver wird von Component implementiert. WillkommenApplet arbeitet mit den Klassen Applet und Graphics unmittelbar zusammen. Diese beiden Klassen bilden lediglich einen kleinen Ausschnitt aus der Bibliothek mit vordefinierten Java-Klassen. Die Verwaltung dieser Klassen und Schnittstellen organisiert Java in mehreren verschiedenen Paketen. Das Wurzelpaket in der Java-Umgebung heißt java. In dieses Paket sind mehrere weitere Pakete geschachtelt, die wiederum andere Pakete, Schnittstellen und Klassen enthalten. Object existiert im Paket lang, Panel, Container, Component existieren im Paket awt, und die Klasse Applet im Paket applet. Die Schnittstelle ImageObserver existiert im Paket image, das liegt wiederum im Paket awt (qualifizierter Name: java.awt.ImageObeserver). Die Paketstruktur39 kann in einem Klassendiagramm visualisiert werden: 39 Pakete werden in der UML als Akten mit Reitern dargestellt. Die gestrichelten Pfeile repräsentieren die Abhängigkeiten zwischen den Paketen. 32 Programmieren in Java 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. 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. 33 Programmieren in Java 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: 34 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.*;“1 schreiben. 2) Kompiliere die Datei „WillkommenApplet.java“ in eine „.class“-Datei. 3) Ausführung des Applet, z.B. über den Aufruf appletviewer WillkommenApplet.html. 3. Aufgabe: Ü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. 40 Diese Anweisung stellt alle Klassen des Pakets java.awt zur Verfügung 35 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 36 Programmieren in Java 4. 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. 37 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 (Instanziierung41) 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 erfordert42, erfolgt die Rückgabe von nicht mehr benötigtem Speicher automatisch43. 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 41 Eine Instanz einer Klasse ist ein (tatsächliches) Objekt (konkrete Darstellung) Ausnahmen: String-, Array-Literale 43 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 42 38 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 Regeln44 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)45 Rückgabetypausdruck: Jede Methode ist typisiert. Der Typ einer Methode bestimt den Typ des Rückgabewerts. Dieser kann von einem beliebigen primitiven Typ46, 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“Anweisung47 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. 44 vgl. 2.1.2 Bezeichner und Namenskonventionen vgl. 2.6.2 46 vgl. 2.2 47 vgl. 2.6.4 45 39 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 JavaAnwendung1: 2. Die Klasse Rechentafeltest zum Überprüfen der Klasse Rechentafel Die Klasse Rechentafeltest ist durch die Anwendung der Klasse Rechentafel bestimmt. Sie enthält die main()-Methode. Im Mittelpunkt der Anwendung steht die Anweisung: Rechentafel einRechenobjekt = new Rechentafel(); Sie erzeugt eine Instanz der Klasse Rechentafel und speichert eine Referenz darauf in der Variablen „einRechenobjekt“. In jeder objektorientierten 48 vgl. PR14101 40 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()); } } Einfache Typen50 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 Typen51 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 Datentypen52: 50 vgl. 2.2.1 vgl. 2.2.1 52 vgl. 2.2.1 51 41 Programmieren in Java - 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 15 -2 31 -2 63 -2 IEEE 754 IEEE 754 - Maximum 16 Unicode 2 -1 +128 15 +2 -1 31 +2 -1 63 +2 -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. 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. 42 Programmieren in Java Konstruktoren können aber auch – wie normale Dateien – Parameter übergeben bekommen, z.B.53: 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.: 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). 53 PR14102 43 Programmieren in Java 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 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: 44 Programmieren in Java 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; } 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; } } 45 Programmieren in Java 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"54 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; 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.: 54 PR14103 46 Programmieren in Java // 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 Konstanten55 gesetzt werden. Beim Bezug einer Variablen in einer Methodendefinition sucht Java zuerst eine Definition dieser Variablen im aktuellen Bereich, dann durchsucht es die äußeren Bereiche bis zur Definition der aktuellen Methode. Ist die gesuchte Größe keine lokale Variable, sucht Java nach einer Definition dieser Variablen als Instanzvariable in der aktuellen Klasse und zum Schluß in der Superklasse. Konstanten sind Speicherbereiche mit Werten, die sich nie ändern. In Java können solche Konstanten ausschließlich für Instanz- und Klassenvariable erstellt werden. Konstante bestehen aus einer Variablendeklaration, der das Schlüsselwort final vorangestellt ist, und ein Anfangswert zugewiesen ist, z.B.: final int LINKS = 0; 55 Das bedeutet: das Schlüsselwort final ist bei lokalen Variablen nicht erlaubt 47 Programmieren in Java Ü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.56: public String toString() { return " Rechenobjekt# " + nummer + " Erster Operand: " + ersterOperand + " Zweiter Operand: " + zweiterOperand; } } 56 PR14103 48 Programmieren in Java 1.4.1.2 Superklassen und Subklassen, Vererbung und Klassenhierarchie Definitionen Klassen können sich auf andere Klassen beziehen. Ausgangspunkt ist eine Superklasse, von der Subklassen (Unter-) abgeleitet sein können. Jede Subklasse erbt den Zustand (über die Variablen-Deklarationen) und das Verhalten der Superklasse. Darüber hinaus können Subklassen eigene Variable und Methoden hinzufügen. Superklassen, Subklassen bilden eine mehrstufige Hierarchie. In Java sind alle Klassen Ableitungen einer einzigen obersten Klasse – der Klasse Object. Sie stellt die allgemeinste Klasse in der Hierarchie dar und legt die Verhaltensweisen und Attribute, die an alle Klassen in der Java-Klassenbibliothek vererbt werden, fest. Vererbung bedeutet, daß alle Klassen in eine strikte Hierarchie eingeordnet sind und etwas von übergeordneten Klassen erben. Was kann von übergeordneten Klassen vererbt werden? Beim Erstellen einer neuen Instanz erhält man ein Exemplar jeder Variablen, die in der aktuellen Klasse definiert ist. Zusätzlich wird ein Exemplar für jede Variable bereitgestellt, die sich in den Superklassen der aktuellen Klasse befindet. Bei der Methodenauswahl haben neue Objekte Zugang zu allen Methodennamen ihrer Klasse und deren Superklasse. Methodennamen werden dynamisch beim Aufruf einer Methode gewählt. Das bedeutet: Java prüft zuerst die Klasse des Objekts auf Definition der betreffenden Methode. Ist sie nicht in der Klasse des Objekts definiert, sucht Java in der Superklasse dieser Klasse usw. aufwärts in der Hierarchie bis die Definition der Methode gefunden wird. Ist in einer Subklasse eine Methode mit gleichem Namen, gleicher Anzahl und gleichem Typ der Argumente wie in der Superklasse definiert, dann wird die Methodendefinition, die zuerst (von unten nach oben in der Hierarchie) gefunden wird, ausgeführt. Methoden der abgeleiteten Klasse überdecken die Methoden der Superklasse (Overriding beim Überschreiben / Überdefinieren). Zum Ableiten einer Klasse aus einer bestehenden Klasse ist im Kopf der Klasse mit Hilfe des Schlüsselworts „extends“ ein Verweis auf die Basisklasse anzugeben. 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 einer Klassenhierarchie verwendet, z.B. für die Zusammenfassung von zwei oder mehr Klassen. 49 Programmieren in Java 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" Objekte57 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. 57 vgl. PR14131 50 Programmieren in Java GeomObjekt private double x; private double y; public double holeX(); public void setzeX(double x); public double holeY(); public void setzeY(double y); Kreis private double r; public double holeR(); public void setzeR(double r); public double umfang(); public double flaeche(); public String toString(); Rechteck 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 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(); 51 Programmieren in Java public abstract double flaeche(); } import java.lang.*; public class Kreis extends GeomObjekt { // Instanzvariable private double r; // Radius // Konstruktoren public Kreis() { this(0.0,0.0,1.0); } public Kreis(double r) { this(0.0,0.0,r); } public Kreis(double x, double y, double r) { super(); super.setzeX(x); super.setzeY(y); this.setzeR(r); } // Instanzmethoden public double holeR() { return r; } public void setzeR(double r) { this.r = (r >= 0.0 ? r : -r); } public double umfang() { double u = 2 * r * Math.PI; return u; } public double flaeche() { return r * r * Math.PI; } public String toString() { return "Kreis: (" + holeX() + ", " + holeY() + ") " + "r = " + r; } } import java.lang.*; public class Rechteck extends GeomObjekt { // Instanzvariable private double breite, hoehe; // Konstruktoren public Rechteck() { this(0.0,0.0,1.0,1.0); } public Rechteck(double breite, double hoehe) { this(0.0,0.0,breite,hoehe); } public Rechteck(double x, double y, double breite, double hoehe) { 52 Programmieren in Java super(); super.setzeX(x); super.setzeY(y); this.setzeBreite(breite); this.setzeHoehe(hoehe); } // Instanzmethoden public double holeBreite() { return breite; } public void setzeBreite(double breite) { this.breite = ( breite >= 0 ? breite : -breite); } public double holeHoehe() { return hoehe; } public void setzeHoehe(double hoehe) { this.hoehe = ( hoehe >= 0 ? hoehe : -hoehe); } public double umfang() { return 2 * breite + 2 * hoehe; } public double flaeche() { return breite * hoehe; } public String toString() { return "Rechteck: (" + holeX() + ", " + holeY() + ") " + "breite = " + breite + " hoehe = " + hoehe; } } Die Klasse "Test" Funktionsfähigkeit: überprüft die vorliegende import java.lang.*; public class Test extends Object { public static void main(String args[]) { // Erzeuge einen Kreis Kreis k1 = new Kreis(); System.out.println(k1.toString()); double umfang; umfang = k1.umfang(); System.out.println("Umfang = " + umfang); System.out.println("Flaeche = " + k1.flaeche()); // Erzeuge einen anderen Kreises Kreis k2 = new Kreis(1.0,1.0,2.0); System.out.println(k2.toString()); umfang = k2.umfang(); System.out.println("Umfang = " + umfang); System.out.println("Flaeche = " + k2.flaeche()); // Erzeuge einen dritten Kreis Kreis k3 = new Kreis(-2.0); System.out.println(k3.toString()); umfang = k3.umfang(); System.out.println("Umfang = " + umfang); 53 Klassenhierarchie auf Programmieren in Java System.out.println("Flaeche = " + k3.flaeche()); // Erzeugen eines Rechteck Rechteck r1 = new Rechteck(2.0,2.0); System.out.println(r1.toString()); umfang = r1.umfang(); System.out.println("Umfang = " + umfang); System.out.println("Flaeche = " + r1.flaeche()); // Erzeugen eines weiteren Rechtecks Rechteck r2 = new Rechteck(2.0,2.0,2.0,3.0); System.out.println(r2.toString()); umfang = r2.umfang(); System.out.println("Umfang = " + umfang); System.out.println("Flaeche = " + r2.flaeche()); } } 1.4.1.3 Referenzen 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 „==“58 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 58 Der Operator „==“ überprüft die Gleichheit der Referenzen. 54 Programmieren in Java 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 „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. 55 Programmieren in Java Konvertieren von Primitivtypen in Objekte und umgekehrt Primitive Typen und Objekte sind in Java zwei völlig verschiedene Dinge. Konvertierung und automatisches Casting sind nicht möglich. Allerdings enthält das Paket „java.lang“ mehrere Subklassen, die je einem primitiven Datentyp entsprechen: Integer, Float, Boolean usw. Mit den in diesen Klassen definierten Klassenmethoden kann mit Hilfe von new für alle Primitivtypen eine „Art Objekt“ erstellt werden, z.B.: „Integer intObjekt = new Integer(13);“. Mit intValue() wird ein primitiver „int“-Wert aus einem Integer-Objekt extrahiert, z.B.: int ganzeZahl = intObjekt.intValue(); 1.4.1.5 Manipulieren von Objekten Vergleichen von Objekten Die relationalen Operatoren funktionieren in der Regel nur mit Primitivtypen, nicht mit Objekten. Die Ausnahme zu dieser Regel bilden die Operatoren „==“ und „!=“. Die Operatoren können für Objekte benutzt werden und prüfen, ob sich zwei Operanden auf genau das gleiche Objekt beziehen. Java besitzt kein Konzept für das Überladen (Overloading) von Operatoren. Zum Vergleichen von Instanzen eigendefinierter Klassen müssen Sondermethoden in die Klasse einbezogen werden. Bsp.: Gleichheitstest mit Objekten der „String“-Klasse59. 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")); } } 59 PR14110 56 // // // // // true false false true false // // // // // true true true true true Programmieren in Java 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-Objekten60. public class Rechentafel implements Cloneable { // Instanzvariable private int ersterOperand = 0; private int zweiterOperand = 0; private int nummer; // Identifikationsnummer // Klassenvariable static int anzRechenobjekte = 0; // Konstruktoren Rechentafel() { this(0,1); } Rechentafel(int ersterOperand, int zweiterOperand) { super(); this.ersterOperand = ersterOperand; this.zweiterOperand = zweiterOperand; nummer = ++anzRechenobjekte; } // Kopierkonstruktor Rechentafel(Rechentafel rechenObjekt) { this(rechenObjekt.ersterOperand,rechenObjekt.zweiterOperand); } // Operationen public int holersterOperand() { return ersterOperand; } public void setzersterOperand(int ersterOperand) { this.ersterOperand = ersterOperand; } public int holzweiterOperand() { return zweiterOperand; } public void setzweiterOperand(int zweiterOperand) { this.zweiterOperand = zweiterOperand; } public int addiere() { return ersterOperand + zweiterOperand; } public int subtrahiere() { 60 PR14202 57 Programmieren in Java return ersterOperand - zweiterOperand; } public int multipliziere() { return ersterOperand * zweiterOperand; } public int dividiere() { // ganzzahlige Division try { /* Innerhalb des try-Blocks werden diejenigen kritischen Aktionen durchgefuehrt, die Ausnahmen erzeugen koennen */ return (ersterOperand / zweiterOperand); } catch(java.lang.ArithmeticException a) { System.out.println(" Fehler: Division durch Null"); System.out.println("Divisor wird auf 1 gesetzt!"); zweiterOperand = 1; return (ersterOperand / zweiterOperand); } } public String toString() { return "Rechenobjekt# " + nummer + " Erster Operand: " + ersterOperand + " Zweiter Operand: " + zweiterOperand; } public Object clone() { Object o = null; try { o = super.clone(); } catch(CloneNotSupportedException e) { System.out.println("Objekt kann nicht geklont werden"); } return o; } } Diese Methode kann dann über ein in der Klasse Rechentafeltest instanziertes Objekt der Klasse Rechentafel aufgerufen werden: import java.lang.*; class Rechentafeltest extends Object { public static void main(String argv[]) { // Erzeugen Instanz der Klasse Rechentafel Rechentafel erstesRechenobjekt = new Rechentafel(3,2); System.out.println("Eine Instanz der Klasse " + erstesRechenobjekt.getClass().getName() + " wurde erzeugt."); System.out.println(erstesRechenobjekt.toString()); // Klonen eines Rechenobjekts System.out.println("Klonen des Objekts"); Rechentafel zweitesRechenobjekt = (Rechentafel) erstesRechenobjekt.clone(); System.out.println(zweitesRechenobjekt.toString()); if (erstesRechenobjekt == zweitesRechenobjekt) System.out.println("Erstes Objekt == Zweites Objekt"); else 58 Programmieren in Java System.out.println("Erstes Objekt != Zweites Objekt"); // Manipulation System.out.println( "Aenderungen am Original bleiben unberuecksichtigt"); erstesRechenobjekt.setzersterOperand(7); erstesRechenobjekt.setzweiterOperand(5); System.out.println(erstesRechenobjekt.toString()); System.out.println(zweitesRechenobjekt.toString()); // Zuweisung System.out.println("Zuweisen des Objekts"); Rechentafel drittesRechenobjekt; drittesRechenobjekt = erstesRechenobjekt; System.out.println(drittesRechenobjekt.toString()); if (erstesRechenobjekt == drittesRechenobjekt) System.out.println("Erstes Objekt == Drittes Objekt"); else System.out.println("Erstes Objekt != Drittes Objekt"); // Manipulieren System.out.println("Aenderungen beziehen sich auch auf die Kopie"); erstesRechenobjekt.setzersterOperand(2); erstesRechenobjekt.setzweiterOperand(3); System.out.println(erstesRechenobjekt.toString()); System.out.println(drittesRechenobjekt.toString()); // Kopierkonstruktor System.out.println("Arbeiten mit einem Kopierkonstruktor"); Rechentafel viertesRechenobjekt = new Rechentafel(erstesRechenobjekt); System.out.println(viertesRechenobjekt.toString()); } } Der Test zeigt: Das geklonte Objekt enthält die gleichen Werte. Bestimmen der Klasse eines Objekts Die Klasse Class stellt über die Methode getname() den Namen einer Klasse zur Verfügung: public String getName() Bsp.: Der folgende Aufruf Rechentafel einRechenobjekt = new Rechentafel(3,2); System.out.print("Eine Instanz der Klasse "); einRechenobjekt.zeigeKlasse(); betimmt den Namen der Klasse von „einRechenobjekt“. Die Methode „zeigeKlasse“ enthält die Methode getName() der Klasse Class. Das Objekt, dessen Klassenzugehörigkeit bestimmt werden soll ruft die Methode getClass() der Klasse Object auf. Das Ergebnis dieser Methode ist ein Class-Objekt, das die Methode getName() kennt. „getName()“ gibt die Zeichenkette aus. void zeigeKlasse() { System.out.print(this.getClass().getName()); } 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; 59 Programmieren in Java der Operator instanceof kann auch als Schnittstelle benutzt werden. Falls ein Objekt eine Schnittstelle implementiert, gibt instanceof mit dem Schnittstellennamen auf der rechten Seite true aus. 1.4.1.6 Innere (lokale) und anonyme Klassen Lokale Klassen Seit JDK 1.1 kann man innerhalb einer bestehenden Klasse z.B. X eine neue Klasse z.B. Y definieren61. 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. 61 Im JDK wird das Konzept als Inner Classes bezeichnet 60 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) definieren62. 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 */ } 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. 62 Angaben zur Implementierung sind nicht möglich 61 Programmieren in Java Bsp.63: Das folgende Interface64 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:"); 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]); } } } 63 64 PR14171 gespeichert in: Vergleich.java 62 Programmieren in Java 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); // 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]); } } } 63 Programmieren in Java 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; } } } } } 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). 64 Programmieren in Java Die Java-Klassenbibliothek65 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 ClassLoader SecurityManager Compiler 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 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. 65 Die Klassenbibliothek des JDK befindet sich in einem Paket mit dem namen „java“. 65 Programmieren in Java - 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 erweitert66: Paket java.applet java.awt java.awt.datatranfer java.awt.event java.awt.image java.beans java.io java.lang java.lang.reflect java.math java.net java.rmi java.rmi.dgc java.rmi.registry java.rmi.server java.security java.security.aci java.security.interfaces java.sql java.util java.util.zip Bedeutung Applets Abstract Window Toolkit ClipBoard-Funktionalität (Copy / Paste) AWT Event-handling Bildanzeige Java Beans Ein- und Ausgabe, Streams Elementare Sprachunterstützung Introspektion, besserer Zugriff auf Klassen durch Debugger und Inspektoren Netzzugriffe Remote Method Invocation, Zugriff auf Objekte in anderen virtuellen Maschinen RMI Distributed Garbage Collection Verwaltet Datenbank, die RMI-Verbindungen koordiniert RMI-Server Sicherheit durch digitale Signaturen, Schlüssel Access Control Lists Digital Signature Algorithm (DAS-Klassen) Datenbankzugriff (JDBC) Diverse Utilities, Datenstrukturen JAR-Files, Kompression, Prüfsummen 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: 66 Zusätzlich 15 weitere Packages, etwa 500 Klassen und Schnittstellen 66 Programmieren in Java -- Mit "import paket.Klasse" wird genau eine Klasse importiert, alle anderen Klassen des Pakets bleiben verborgen --Mit "import paket.*"67 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. 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.68: 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()); 67 type import on demand, d.h.: Die Klasse wird erst dann in dem angegebenen Paket gesucht, wenn das Programm sie wirklich benötigt. 68 Vgl. PR14181 67 Programmieren in Java /* o ist ein Object. Object enthaelt eine Definition von toString(), daher akzeptiert der Java-Compiler (zur Uebersetzungszeit) o.toString(). */ // System.out.println("Umfang: " + o.umfang()); /* o ist ein Object. Object enthaelt oder erbt keine Definition von umfang(), daher akzeptiert der JavaCompiler (zur Uebersetzungszeit) o.umfang() nicht. */ // System.out.println("Umfang: " + (Rechteck) o.umfang()); /* (Rechteck) o.umfang() akzeptiert der Java-Compiler (zur Uebesersetzungszeit) aus dem gleichen Grund wie zuvor nicht. Die Schreibweise (Rechteck) o.umfang() bedeutet auch: Der Rueckgabewert von o.umfang() soll als 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“. 68 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. Zeichenkettendarstellung von Objekten: public String toString() Darüber kann ein Objekt an einen Ausgabestrom übergeben werden. Klonen von Objekten: protected Object clone() throws CloneNotSupportedException, OutOfMemoryError erzeugt ein exaktes Duplikat eines Objekts. Ein Objekt kann nur geklont werden, falls es die CloneableSchnittstelle unterstützt. 1.4.2.2 Die Class-Klasse Sie enthält Informationen, die eine Java-Klasse beschreiben. In Java hat jede Klasse eine entsprechende Instanz von Class. Konstruktor. Es gibt keinen öffentlichen Konstruktor für das Class-Objekt. Class-Instanzen. Eine Class-Instanz kann auf drei unterschiedliche Arten erhalten werden: - public final native Class getClass() ermittelt die Class-Instanz für ein Objekt. Bsp.: void ausgabeClassName(Object obj) { System.out.println(“Die Klasse von “ + obj + “: “ + obj.getClass.getName()); } public static Class forName(String className) throws ClassNotFoundException gibt die Instanz von Class wieder, die zu className gehört. - Laden einer neuen Klasse durch Verwendung eines eigenen ClassLoader-Objekts Informationen über eine Klasse können bezogen werden über public String getName() gibt den Namen der Klasse zurück. public boolean isInterface() gibt true aus, falls es sich um eine Schnittstelle handelt. public Class getSuperClass() gibt die Superklasse der Klasse an. public Class[] getInterfaces() gibt ein Datenfeld aus, das Class-Instanzen für jede Schnittstelle enthält, die diese Klasse unterstützt. public ClassLoader getClassLoader() gibt die Instanz von ClassLoader an, die dafür verantwortlich ist, daß diese Klasse in die Laufzeit-Umgebung geladen wird. 69 Programmieren in Java 1.4.2.3 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. 70 Programmieren in Java Die Methode public static Properties getProperties(); liefert das komplette Properties-Objekt mit den System-Properties. Bsp.69: 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. 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 dabei 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() liefert die Anzahl der Millisekunden, die zum Zeitpunkt des Aufrufs seit Mitternacht des 1.1.1970 vergangen sind. 69 PR14231 71 Programmieren in Java 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. 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. 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 72 Programmieren in Java einen einzigen Vordergrund-Thread besitzt, (nämlich den, in dem main() läuft70,) 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. 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 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. 70 Beim Starten eines Java-Programms wird automatisch ein Thread für die Ausführung des Hauptprogramms angelegt. 73 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 implementieren71. 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) 71 sogar die Klasse Thread selbst 74 Programmieren in Java Prozeß aus der Warteliste des Objekts, stellt die (temporär) aufgehobenen Sperren wieder her und führt ihn dem normalen Scheduling zu. „wait()“ und „notify()“ sind damit für elementare Synchronisationsaufgaben geeignet, bei denen es weniger auf die Kommunikation als auf die Steuerung zeitlicher Abläufe ankommt 75 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 76 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. 77 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. 78 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; 79 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. 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“)72 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() 72 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. 80 Programmieren in Java Objekt-Wrapper-Varianten von true und false public final static Boolean FALSE; public final static Boolean TRUE; Die Klasse Number 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. 81 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. 82 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.73: 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 73 PR14103 83 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()74. 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(); } } } 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 { ......... } } 74 Sie ist in der Klasse Throwable definiert und daher allen Exception-Objekten zugänglich. 84 Programmieren in Java Hier wird dem Compiler mitgeteilt, daß der vorliegende Code eine Ausnahme („MeineAusnahme“) erzeugen könnte. Falls in der mit „throws“ gekennzeichneten Methode eine Ausnahmesituation auftritt, dann wird die aufrufende Methode nach einer Ausnahmebehandlung durchsucht. Enthält die aufrufende Methode eine Ausnahmebehandlungsmethode, wird mit dieser die Ausnahme bearbeitet. Ist keine Routine vorhanden, wird deren aufrufende Methode durchsucht und so fort. Die Ausnahme wird von der Hierarchieebene, wo sie aufgetreten ist, jeweils eine Hierarchieebene weiter nach oben gereicht. Wird die Ausnahme nirgends aufgefangen, dann bricht der JavaInterpreter normalerweise die Ausführung des Programms ab. Das Auslösen einer Ausnahme wird im Java-Sprachgebrauch als „throwing“ bezeichnet, das Behandeln einer Ausnahme wird „catching“ genannt. Das Grundprinzip des Exception-Mechanismus: - Ein Laufzeitfehler oder eine vom Entwickler gewollte Bedingung löst eine Ausnahme aus. - Diese kann entweder vom Programmteil, in dem sie angelegt wurde, behandelt werden, oder sie kann weitergegeben werden. - Wird die Ausnahme weitergegeben, so hat der Empfänger erneut die Möglichkeit sie entweder zu behandeln oder selbst weiterzugeben. - Wird die Ausnahme von keinem Programmteil behandelt, so führt sie zum Abbruch eines Programms, und zur Ausgabe einer Fehlermeldung. Auswertungen, die Ausnahmen auslösen, können entweder direkt in eine tryKlausel mit optionalen catch-Anweisungen (Behandlung vor Ort) eingefaßt oder über die throws-Klausel an den übergeordneten Programmblock weitergeleitet werden. Falls weder die „Behandlung vor Ort“ noch eine throws-Klausel verwendet wird, liefert der Compiler bereits bei der Übersetzung eine Fehlermeldung. Eine Ausnahme bildet die Klasse RuntimeException einschl. ihrer Subklassen. Für sie ist weder die Fehlerbehandlung noch Weiterleitung mit der throws-Klausel notwendig. Die Fehlerklassen von Java Zur Behandlung von Fehlern und Ausnahmen besitzt die Klasse Throwable zwei Subklassen: die Klassen Exception und Error. In beiden Ausnahmeklassen sind die wichtigsten Ausnahmen und Fehler der Java-Laufzeitbibliothek bereits enthalten. Ausnahmen der Klasse Error und ihrer Klasse RuntimeError 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 85 Programmieren in Java 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 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. 86 Programmieren in Java 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 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.75: 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; } } 75 Vgl. PR14140 87 Programmieren in Java public class AuswurfMeineAusnahme { public static void f() throws MeineAusnahme { System.out.println("Auswurf von MeineAusnahme aus f()"); throw new MeineAusnahme(); } public static void g() throws MeineAusnahme { System.out.println("Auswurf von MeineAusnahme aus g()"); throw new MeineAusnahme("Ursprung in g()"); } public static void h() throws MeineAusnahme { System.out.println("Auswurf von MeineAusnahme aus h()"); throw new MeineAusnahme("Ursprung in h()",13); } public static void main(String args[]) { try { f(); } catch (MeineAusnahme a) { a.printStackTrace(); } try { g(); } catch (MeineAusnahme a) { a.printStackTrace(); } try { h(); } catch (MeineAusnahme a) { a.printStackTrace(); System.out.println("a.wert() = " + a.wert()); } } } /* Ausgabe: Auswurf vom MeineAusnahme aus f() MeineAusnahme at java.lang.Throwable.<init>(Compiled Code) at java.lang.Exception.<init>(Compiled Code) at MeineAusnahme.<init>(AuswurfMeineAusnahme.java:4) at AuswurMeineAusnahme.f(Vererbung.java:21) at AuswurfmeineAusnahme.main(Vererbung.java:36) Auswurf von MeineAusnahme aus g() MeineAusnahme: Ursprung in g() at java.lang.Throwable.<init>(Compiled Code) at java.lang.Exception.<init>(Compiled Code) at MeineAusnahme.<init>(AuswurfMeineAusnahme.java:7) at AuswurfMeineAusnahme.g(AuswurfMeineAusnahme.java:26) at AuswurfMeineAusnahme.main(AuswurfMeineAusnahme.java:43) Auswurf von MeineAusnahme aus h() MeineAusnahme: Ursprung in h() at java.lang.Throwable.<init>(Compiled Code) at java.lang.Exception.<init>(Compiled Code) at MeineAusnahme.<init>(AuswurfMeineAusnahme.java:11) at AuswurfMeineAusnahme.g(AuswurfMeineAusnahme.java:31) at AuswurfMeineAusnahme.main(AuswurfMeineAusnahme.java:50) a.wert() = 13 */ 88 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 InputStream76 in public static PrintStream77 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. 76 77 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 89 Programmieren in Java 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“78 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.79: "Einlesen von zwei ganzen Zahlen". import java.io.*; public class EingZweiZahlen { // Einlesen von zwei ganzen Zahlen public static void main(String args[]) throws IOException { int a, b, c; BufferedReader ein = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Bitte a eingeben: "); a = Integer.parseInt(ein.readLine()); System.out.print("Bitte b eingeben: "); b = Integer.parseInt(ein.readLine()); c = a + b; System.out.println("a + b = " + c); } } 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äts80 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. Writer81 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. Bsp.: Das folgende Programm liest eine Zeichenfolge aus einem Eingabestrom und schreibt die Zeichenfolge in einen Ausgabestrom82 78 Das grundlegende Konzept für Ein- und Ausgabe sind Datenströme, die im Paket „java.io“ versammelt sind. 79 Vgl. PR14150 80 Hauptspeicher, Festplatte, Drucker, Netzwerk 81 vgl. 7.6 82 vgl. PR14150 90 Programmieren in Java 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 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; } void bereinigen() { // Freigabe der Syntax-Resourcen try { ein.close(); } catch(IOException a2) { System.out.println("ein.close() nicht erfolgreich"); } } } 92 Programmieren in Java 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) Bsp.: Demonstration der Umlenkung von Standardeingabe und Standardausgabe in Java 1.183 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(); } } } 83 vgl. PR14150 93 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 Leeraum84 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 Unicode85. Bei der Kompilierung wird der Quellcode automatisch in eine Folge von Unicode84 Technisch gesehen ist ein Leeraum kein Token Alle Literale (d.h. alle vorkommenden Zeichen (Zahlen, Buchstaben, Sonderzeichen, etc.) bestehen aus Unicode. 85 94 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 Abb.: Reservierte Java Schlüsselworte 95 case continue false generic instanceof null public switch true cast default final goto int operator rest synchronized try Programmieren in Java 2.1.2 Bezeichner und Namenskonventionen Bezeichner bzw. Identifizierer (identifier) sind Worte, die vom Programmierer gewählt, zu Token werden und die Variablen, Konstanten, Klassen, Objekte, Beschriftungen und Methoden darstellen. Bezeichner dürfen nicht mit JavaSchlüsselworten identisch sein. Es gibt in Java einige Regeln zur Namensvergabe, die unbedingt eingehalten werden müssen: - Bezeichner bestehen aus Unicode-Buchstaben und Zahlen in unbeschränkter Länge. - Das erste Zeichen eines Bezeichners muß ein Buchstabe, der Unterstrich „_“ oder das Zeichen „$“ sein. Alle folgenden Zeichen sind entweder Buchstaben oder Zahlen. - Zwei Token gelten nur dann als derselbe Bezeichner, wenn sie dieselbe Länge haben, und jedes Zeichen im ersten Token genau mit dem korrespondierenden Zeichen des zweiten Token übereinstimmt, d.h. den vollkommen identischen Unicode-Wert hat. Java unterscheidet Groß- und Kleinschreibung. Es hat sich eingebürgert, einige Namenskonventionen in Java einzuhalten: - Man sollte möglichst „sprechende“ Bezeichner wählen (Ausnahme sind Schleifen, wo für Zählvariablen meistens nur ein Buchstabe (z.B. i, j) verwendet wird. - Konstanten (Elemente mit dem „Modifizierer“ „final“) sollten vollständig groß geschrieben werden. - 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 0X86. 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. 86 Die Zahlen 0 bis 15 werden durch die Buchstaben „A“ bis „F“ bzw. „a“ bis „f“ dargestellt (Groß- oder Kleinschreibung ist hier nicht relevant. 96 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. 97 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)87 - 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. 87 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. 98 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 99 Programmieren in Java 2.1.5 Operatoren Operatoren geben an, welche Operation mit einem oder mehreren gegebenen Operanden durchgeführt werden soll. Es gibt in Java 37 Zeichensequenzen, die Token darstellen, welche als Operatoren benutzt werden. 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 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 100 Programmieren in Java /= %= = 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 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 Manipulieren88 import java.util.*; public class BitManipulation { public static void main(String[] args) { 88 PR21502 101 Programmieren in Java Random rand = new Random(); int i = rand.nextInt(); int j = rand.nextInt(); pBinInt("-1",-1); pBinInt("+1",+1); int maxpos = 2147483647; pBinInt("maxpos", maxpos); int maxneg = -2147483648; pBinInt("maxneg",maxneg); pBinInt("i",i); pBinInt("~i",~i); pBinInt("-i",-i); pBinInt("j",j); pBinInt("i & j",i & j); pBinInt("i | j",i | j); pBinInt("i ^ j",i ^ j); pBinInt("i << 5",i << 5); pBinInt("i >> 5",i >> 5); pBinInt("(~i) >> 5",(~i) >> 5); pBinInt("i >>> 5",i >>> 5); pBinInt("(~i) >>> 5", (~i) >>> 5); long l = rand.nextLong(); long m = rand.nextLong(); pBinLong("-1L",-1L); pBinLong("+1L",+1L); long ll = 9223372036854775807L; pBinLong("maxpos", ll); long lln = -9223372036854775807L; pBinLong("maxneg",lln); pBinLong("l",l); pBinLong("~l",~l); pBinLong("-l",-l); pBinLong("m",m); pBinLong("l & m",l & m); pBinLong("l | m",l | m); pBinLong("l ^ m",l ^ m); pBinLong("l << 5",l << 5); pBinLong("l >> 5",l >> 5); pBinLong("(~l) >> 5",(~l) >> 5); pBinLong("l >>> 5",l >>> 5); pBinLong("(~l) >>> 5", (~l) >>> 5); } static void pBinInt(String s, int i) { System.out.println(s + ", int: " + i + ", binaer: "); System.out.print(" "); for (int j = 31; j >= 0; j--) if (((1 << j) & i) != 0) System.out.print("1"); else System.out.print("0"); System.out.println(); } static void pBinLong(String s, long l) { System.out.println(s + ", long: " + l + ", binaer: "); System.out.print(" "); for (int j = 63; j >= 0; j--) if (((1L << j) & l) != 0) System.out.print("1"); else System.out.print("0"); System.out.println(); } } 102 Programmieren in Java Die Ausgabe zu dieser Anwendung zeigt für den Bereich „int“ jeweils die interne Zahlendarstellung: -1, int: -1, binaer: 11111111111111111111111111111111 +1, int: 1, binaer: 00000000000000000000000000000001 maxpos, int: 2147483647, binaer: 01111111111111111111111111111111 maxneg, int: -2147483648, binaer: 10000000000000000000000000000000 i, int: -1465644750, binaer: 10101000101001000000100100110010 ~i, int: 1465644749, binaer: 01010111010110111111011011001101 -i, int: 1465644750, binaer: 01010111010110111111011011001110 j, int: -1273964081, binaer: 10110100000100001101100111001111 i & j, int: -1610610430, binaer: 10100000000000000000100100000010 i | j, int: -1128998401, binaer: 10111100101101001101100111111111 i ^ j, int: 481612029, binaer: 00011100101101001101000011111101 i << 5, int: 344008256, binaer: 00010100100000010010011001000000 i >> 5, int: -45801399, binaer: 11111101010001010010000001001001 (~i) >> 5, int: 45801398, binaer: 00000010101110101101111110110110 i >>> 5, int: 88416329, binaer: 00000101010001010010000001001001 (~i) >>> 5, int: 45801398, binaer: 00000010101110101101111110110110 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 103 Programmieren in Java Vergleichsoperatoren Vergleichsoperatoren haben zwei Operanden und vergleichen diese (zwei Ganzzahlen oder zwei Gleitpunktzahlen)1. Als Rückgabewert der Operation entsteht ein boolescher Wert (true oder false). Operator == != < > <= >= Bedeutung Gleichheitsoperator Ungleichheitsoperator Kleiner-als-Operator Größer-als-Operator Kleiner-als-oder-gleich-Operator Größer-als-oder-gleich-Operator Abb.: Die Java-Vergleichsoperatoren Die relationalen Operatoren „==“ und „!=“ arbeiten mit allen Objekten. Ihre Anwendung zeigt häufig ein „verwirrendes Ergebnis“, z.B.90: 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()91 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)); } } 89 // true 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. 90 Vgl. PR21500 91 Viele in der Java-Bibliorhek bereitgestellte Klassen implementieren die Methode equals() 104 Programmieren in Java 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 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 beginnen mit /* und enden mit */. Sie können sich über mehrere Zeilen erstrecken. - 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. 105 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 106 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 107 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 Überlauf92 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 Unterlauf93 gibt einen speziellen 92 d.h.: Größeres Ergebnis von einer Operation als durch den Wertebereich des jeweiligen Typs ausgedrückt werden kann. 93 d.h.: kleineres Ergebnis (- außer Null -) von einer Operation als durch den Wertebereich des jeweiligen Typs ausgedrückt werden kann. 108 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. 109 Programmieren in Java 2.2.3 Datenfelder (Arrays) Ein Datenfeld (Array) ist eine Ansammlung von Objekten eines bestimmten Typs1, die über einen laufenden Index adressierbar sind. Gegenüber „normalen Objekten“ haben Arrays zwei wesentliche Einschränkungen: 1. Arrays haben keine Konstruktoren. Statt dessen wird der Operator new mit spezieller Syntax aufgerufen. 2. Es können keine Subklassen eines Datenfelds definiert werden. Datenfelder gehören zu den Referenzvariablen1. Arrays können jeden Wertetyp (primitiver Typ oder Objekt) enthalten, jedoch können in einem Array keine unterschiedlichen Typen gespeichert werden. Arrays in Java sind Objekte und unterscheiden sich von Datenfeldern in anderen Programmiersprachen durch folgende Merkmale: - Array-Variable sind Referenzen - Arrays besitzen Methoden und Instanz-Variable - Arrays werden zur Laufzeit erzeugt 2.2.3.1 Deklarieren, Erstellen von Array-Objekten In Java umfaßt das Erstellen eines Datenfelds drei Schritte: 1. Deklaration einer Variablen zur Aufnahme eines Datenfelds. Array-Variable zeigen den Objekttyp an, den das Datenfeld aufnimmt und den namen des Arrays, gefolgt von leeren eckigen Klammern „[]“, z.B: int x[]. Alternativ dazu kann eine Array-Variable auch so (Klammern nach dem Typ) festgelegt sein, z.B. int[] x. 2. Erstellen eines neuen Array-Objekts und Zuweisen einer Array-Variablen. Das kann erfolgen - mit new, z.B. String[] namen = new String[10]; Hier wird ein neues Datenfeld von Strings mit 10 Elementen erstellt. Beim Erstellen eines Array-Objekts mit new ist anzugeben, wieviele Elemente das Array aufnehmen soll. Alle Elemente des Array werden automatisch initialisiert (0 für numerische Datenfelder, false für boolesche, ‘\0‘ für Zeichen-Arrays und „null“ für alles andere. Bsp.: Erzeugen von Datenfeldern mit „new“ a) Aufnahme von Werten primitiver Typen import java.util.*; public class FeldPrim { static Random rand = new Random(); static int zRand(int mod) { return Math.abs(rand.nextInt()) % mod; } public static void main(String[] args) 94 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. 95 Es gibt drei Arten von Referenzvariablen: Klassen, Schnittstellen und Datenfelder 110 Programmieren in Java { int[] a; // Die Groesse des Array wird zur Laufzeit // zufaellig bestimmt a = new int[zRand(20)]; ausgabe("Laenge von a = " + a.length); for (int i = 0; i < a.length; i++) ausgabe("a[" + i + "] = " + a[i]); } static void ausgabe(String s) { System.out.println(s); } } b) Aufnahme von Objekten Ein Datenfeld, dessen Komponenten keine primitiven Typen aufnehmen sollen, muß immer mit „new“ gefüllt werden. import java.util.*; public class FeldObj { static Random rand = new Random(); static int zRand(int mod) { return Math.abs(rand.nextInt()) % mod; } public static void main(String[] args) { Integer[] a; // Die Groesse des Array wird zur Laufzeit // zufaellig bestimmt a = new Integer[zRand(20)]; ausgabe("Laenge von a = " + a.length); for (int i = 0; i < a.length; i++) { a[i] = new Integer(zRand(500)); ausgabe("a[" + i + "] = " + a[i]); } } static void ausgabe(String s) { System.out.println(s); } } c) Variable Argumente Das zweite Format in der Feldinitialisierung bestimmt eine bequeme syntaktische Form für den „Aufruf von Methoden“, die den gleichen Effekt besitzt wie die „Argumentenliste in C“. Über eine solche Argumentenliste kann eine unbekannte Menge von Argumenten eines unbekannten Typs behandelt werden. Da alle Klassen von der Klasse Object abstammen, kann eine Methode mit einem Datenfeld als Argument verwendet werden, das aus Objekten vom Typ Object besteht, z.B. class A { int i; } public class VarArgs { static void f(Object[] x) { 111 Programmieren in Java for (int i = 0; i < x.length; i++) System.out.println(x[i]); } public static void main(String[] args) { f(new Object[] { new Integer(40), new VarArgs(), new Float(3.14), new Double(11.11) }); f(new Object[] { "eins", "zwei", "drei" }); f(new Object[] { new A(), new A(), new A() }); } } - durch direktes Initialisieren des Array-Inhalts, z.B.: String[] namen = {“juergen“, “bernd“, “liesel“, “dieter“, “hans“, “vera“, “christian“, “theo“, “emil“, “karl“}; Alle Elemente der in der geschweiften Klammer stehenden Elemente müssen vom gleichen Typ sein 3. Speichern von Elementen im Array Zur Speicherung eines Werts in einem Array, wird der Array-Ausdruck „subscript“ benutzt, z.B.: x[subscript] . „subscript“ ist die Stelle (Position) für den Zugriff auf eine Datenfeld-Element. Das Array-Subskript muß vom Typ int sein. Wegen der vom Compiler vorgenommenen automatischen Typenkonvertierungen sind auch short, byte, char zulässig. Indexausdrücke werden vom Laufzeitsystem auf Einhaltung der Array-Grenzen überprüft. 2.2.3.2 Zugriff auf Datenfeld-Elemente, Ändern von Datenfeld-Elementen Datenfeld-Subskripte beginnen mit 0. Alle Array-Subskripte werden beim Kompilieren geprüft, ob sie sich innerhalb der Grenzem des Array befinden (Größer als 0, kleiner als die Länge des Datenfelds). String ort[] = new String[10]; ort[10] = “Dinkesbuehl“; ist falsch (Fehler beim Kompilieren). Das in „ort“ gespeicherte Array hat nur 10 ab 0 numerierte Elemente. Wird das Array-Subskript zur Laufzeit berechnet und resultiert daraus ein Wert außerhalb der Array-Grenzen, dann erzeugt der Java-Interpreter einen Fehler96. 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. 96 Er weist auf eine „Ausnahme“ hin. 112 Programmieren in Java 2.2.3.3 Anwendungen mit eindimensionaler Datenfeldern 1. Sammeln Datenfelder sind die wohl nützlichsten Objekte in Java, mit denen Objekte in leicht zugänglichen Listen gesammelt werden können. 2. Suchen a) sequentielle Suche b) binäre Suche 3. Sortieren97 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 97 PR22301 113 Programmieren in Java Vorschlag für die Implementierung: 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; // 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]); } } } 114 Programmieren in Java 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] M[0][4] M[1][4] M[2][4] M[3][4] Zweidimensionale Datenfelder werden, wie das folgende Beispiel zeigt, auf gleiche Weise erstellt und initialisiert wie eindimensionale Felder. class Einheitsmatrix { publich static void main(String args[]) { double[][] EM; EM = new double[4][4]; for (int zeile = 0; zeile < 4; zeile++) { for (int spalte = 0; spalte < 4; spalte++) { if (zeile != spalte) { EM[zeile][spalte] = 0.0; } else { EM[zeile][spalte] = 1.0; } } } } } 115 Programmieren in Java 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(); } } } Bsp.: Das 8-Damen-Problem98 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 98 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 vgl. PR22305 116 Programmieren in Java 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.*; 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); 117 Programmieren in Java 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; // 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++) 118 Programmieren in Java { for (int j = 0; j < 8; j++) { System.out.print(brett[i][j] + " "); } System.out.println(); } System.out.println(); int k = 0; for (int i = 0; i < brett.length; i++) for (int j = 0; j < brett[i].length; j++) { 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)99. Bsp.: Mehrdimensionale Felder import java.util.*; public class MehrdimFeld { static Random rand = new Random(); static int zRand(int mod) { return Math.abs(rand.nextInt()) % mod; } public static void main(String args[]) { // 1. Bsp: Erzeugen eines mehrdimensionalen Felds 99 Im Java werden multidimensionale Arrays streng genommen nicht unterstützt 119 Programmieren in Java // 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); } } 120 Programmieren in Java 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: 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) 121 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. 122 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 123 Programmieren in Java Abb.: Casting-Operatoren 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.100: Konvertieren primitiver Typen public class KonvPrim { public static void main(String args[]) { char z = ’a’; System.out.println("char z = ’" + z + ’\’’); System.out.println("Unicode von z: " + (int) z); int i = 17; System.out.println("int i = " + i); long l = 4L; System.out.println("long l = " + l); double d = 17.3; System.out.println("double d = " + d); float f = 4.5F; System.out.println("float f = " + f); double sd; float sf; long sl; int si; sd = i + l + d + f; System.out.println("sd = i + l + d + f = " + sd); // sf = i + l + d + f; /* Inkompatibler Typ! */ // System.out.println("sf = i + l + d + f = " + sf); sf = (float) (i + l + d + f); // sl = i + l + d + f; /* Inkompatibler Typ! */ // System.out.println("sl = i + l + d + f = " + sl); sl = i + l + (long) d + (long) f; System.out.println("sl = l + i + (long) d + (long) f = " + sl); sl = (long) (i + l + d + f); System.out.println("sl = (long) (i + l + d + f) = " + sl); // si = i + l + d + f; /* Inkompatibler Typ! */ // System.out.println("si = i + l + d + f = " + si); si = i + (int) l + (int) d + (int) f; System.out.println("sl = l + (int) i + (int) d + (int) f = " + sl); si = (int) (i + l + d + f); System.out.println("sl = (int) (i + l + d + f) = " + si); } } Konvertieren von Objekten. Mit Einschränkungen lassen sich Klasseninstanzen in Instanzen anderer Klassen konvertieren. Die Klassen müssen allerdings durch Vererbung miteinander verbunden sein. Allgemein gilt: Ein Objekt einer Klasse kann auf seine Superklasse festgelegt werden. Spezifische Informationen der Subklasse gehen dabei verloren. Das Konvertieren erfolgt immer nach folgender Form: (Klassenname) Objekt. Bsp.101: Konvertieren Datentyp Object 100 vgl. PR23301 124 Programmieren in Java import java.applet.Applet; import java.awt.*; import java.util.Vector; public class MittleresDrittel extends Applet { int appletHoehe; int appletBreite; Vector endpunkt = new Vector(); public void init() { Dimension d = getSize(); appletHoehe = d.height - 1; appletBreite = d.width - 1; // Anhaengen an Liste endpunkt.addElement(new Float(0.0f)); endpunkt.addElement(new Float(1.0f)); } public void paint(Graphics g) { float x1, x2; Float tempFloat; g.setColor(Color.yellow); g.fillRect(0,0,appletBreite,appletHoehe); g.setColor(Color.black); for (int i = 0; i < appletHoehe; i+=5) { // Zeichne die Linien for (int j = 0; j < endpunkt.size(); j+=2) { tempFloat = (Float) endpunkt.elementAt(j); x1 = tempFloat.floatValue(); tempFloat = (Float) endpunkt.elementAt(j+1); x2 = tempFloat.floatValue(); g.drawLine(Math.round(x1 * appletBreite),i, Math.round(x2 * appletBreite),i); } // Entferne das mittlere Drittel aus den Linien schneideausSegment(); tempFloat = (Float) endpunkt.elementAt(0); x1 = tempFloat.floatValue(); System.out.println(x1); tempFloat = (Float) endpunkt.elementAt(1); x2 = tempFloat.floatValue(); System.out.println(x2); if (Math.round(x1*appletBreite) == Math.round(x2*appletBreite)) break; } } public void schneideausSegment() { int index = 0; float luecke; Float tempFloat1, tempFloat2; int stop = endpunkt.size(); for (int i = 0; i < stop; i += 2) { schneideausMittleresDrittel(index,index+1); index += 4; } } public void schneideausMittleresDrittel(int links, int rechts) { float luecke; 101 PR23302 125 Programmieren in Java 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. 126 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; 127 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 DekrementOperatoren102 // 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 */ 102 vgl. PR24500 128 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) anweisung103 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-Anweisung104 import java.lang.*; public class IfDemo extends Object { public static void main(String[] args) { int i; int abs; // if - else i = 13; System.out.println("i = " + i); if (i >= 0) { abs = i; } else { abs = -i; } System.out.println("abs = " + abs); i = -13; System.out.println("i = " + i); if (i >= 0) { abs = i; } else { abs = -i; } System.out.println("abs = " + abs); 103 „anweisung“ bedeutet: Eine einfache Anweisung, die nur von einem Semikolon abgeschlossen ist bzw. eine zusammengesetzte Folge von anweisungen, die von { ... } umschlossen ist. 104 Vgl. PR24601 129 Programmieren in Java // 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 = + i); " + abs); + i); " + abs); + i); " + abs); + i); " + abs); } } 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 Werts 105. „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. 105 Integrale Werte sind bspw. vom Typ „int“, „char“. Nichtintegrale Typen. Z.B. float, String, müssen über eine Serie von „if“-Anweisungen „switch“ simulieren 130 Programmieren in Java Bsp.: Demonstrationsprogramm zur switch-Anweisung106 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; 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 106 vgl. PR24601 131 Programmieren in Java nächste Anweisung nach der Unteranweisung oder nach dem Block der whileAnweisung weitergegeben. while (boolescher_Ausdruck) anweisung Bsp.107: public class WhileDemo extends Object { public static void main(String args[]) { double a = 2.0; int n = 10; System.out.println("a = " + a); System.out.println("n = " + n); int absn = (n < 0) ? -n : n; double aHochn = 1.0; int i = 1; while (i <= absn) { aHochn *= a; i++; } if ( n < 0) { 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.108: 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 107 108 PR24700 PR24700 132 Programmieren in Java { 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. - 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. 133 Programmieren in Java 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); } } 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). 134 Programmieren in Java Bsp.109: „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); } } } 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.110: Marken im Zusammenhang mit „for“- bzw. „while“-Schleifen 109 PR24800 135 Programmieren in Java 1. Marken im Zusammenhang mit „for“ public class MarkiertesFor { public static void main(String[] args) { int i = 0; aussen: // hier soll keine Anweisung stehen for (; true; ) // Endlos-Schleife { innen: // hier soll keine Anweisung stehen for (; i < 10; i++) { ausgabe("i = " + i); if (i == 2) { ausgabe("continue"); continue; } if (i == 3) { ausgabe("break"); i++; break; } if (i == 7) { ausgabe("continue aussen"); 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 110 PR24800 136 Programmieren in Java i = 6 continue innen i = 7 continue innen i = 8 break aussen */ 2. Marken im Zusammenhang mit „while“ public class MarkiertesWhile { public static void main(String[] args) { int i = 0; aussen: while (true) { ausgabe("Aeussere While-Schleife"); while (true) { i++; ausgabe("i = " + i); if (i == 1) { ausgabe("continue"); continue; } if (i == 3) { ausgabe("continue aussen"); 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 */ 137 Programmieren in Java return-Anweisung Alle Funktionen haben einen eindeutigen Typ, der gleichzeitig Typ des Rückgabewerts ist. Mögliche Typen für die Rückgabe sind primitive Typen, Datenfelder (Arrays), Klassen, Schnittstellen. Es gibt einen speziellen Typ für eine Funktion ohne Rückgabewert: „void“. Der Rückgabewert ist der Wert einer return-Anweisung. 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. 138 Programmieren in Java 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 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. 139 Programmieren in Java 2.6 Methoden 2.6.1 Die Deklaration Die Deklarationen von Methoden haben folgendes Aussehen 111: Zugriffsspezifizierer Modifizierer Returnwert NameMethode(Parameter) throws Exceptionliste Methodenunterschrift112. 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. 111 112 Kursiv Geschriebenes ist optional Oft wird für denselben Zusammenhang der Begriff „methodensignatur“ verwendet. 140 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 Datentyp113 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. 113 Nicht nur primitive Datentypen, sondern auch komplexe Objekte. 141 Programmieren in Java 3. Grafische Benutzeroberflächen und Applets 3.1 Ereignisbehandlung unter grafischen Benutzeroberflächen Mit Hilfe einfacher Benutzerschnittstellen Graphical User Interface (GUI)) lassen sich Ein- und Ausgaben114 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 Component Container Panel 114 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 Standardein- und Standardausgabe spielen in Java nur im Rahmen des „Debugging“ eine Rolle. 142 Programmieren in Java Applet Window Frame Dialog FileDialog 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 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. Bsp.115: 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: 115 des vorliegenden PR14160 143 Programms zeigt das folgende Programmieren in Java Das vorliegende Beispiel zeigt noch einige Mängel: - Zwar kann in das für die Eingabe vorgesehene Textfeld ein String eingegeben werden, das zweite Textfeld ist allerdings nicht zugänglich. Es passiert somit nicht gerade viel. Es werden noch keine Ereignisse aufgefangen und behandelt. - Noch nicht einmal kann das Fenster geschlossen werden, und die Applikation muß (nach dem Schließen des Frame) mit "CTRL-C" explizit abgebrochen werden. 3.1.2 Ereignisbehandlung unter grafischen Benutzeroberflächen Im Mittelpunkt der Programmierung unter einer GUI steht die Kommunikation zwischen System und Anwendungsprogramm. Die Anwendung wird über alle Arten von Ereignissen und Zustandsänderungen vom System durch Versenden von Nachrichten (z.B. über Mausklick, Tastatureingaben, Veränderungen an Größe und Lage der Fenster) informiert. Die Reaktion auf die Nachrichten erfolgt in spezielllen Ereignisempfängern (EventListeners), die das zum Ereignis passende EmpfängerInterface implementieren. Damit ein Ereignisempfänger Nachrichten einer bestimmten Ereignisquelle erhält, muß er sich bei der Quelle registrieren lassen, d.h.: Es muß eine EventListener-Klasse geschrieben, instanziert und bei der Ereignisquelle registriert werden. Zum Empfang von Nachrichten muß ein Objekt eine Reihe von Methoden implementieren, die von der Nachrichtenquelle, bei der es sich registriert hat, aufgerufen werden können. Die Ereignisempfänger stellen diese Methoden durch Implementierung von Interfaces bereit, die aus der Klasse EventListener des Pakets java.util abgeleitet sind. Ereignistypen. Im JDK 1.1 werden Ereignistypen durch eine Hierarchie von Ereignisklassen repräsentiert, die alle aus der Klasse java.util.EventObject116 abgeleitet sind. 116 Speichert das Objekt, das die Nachricht ausgelöst hat und gibt durch Aufruf von "public Object getSource()" das Objekt an. 144 Programmieren in Java EventObject AWTEvent ComponentEvent FocusEvent InputEvent KeyEvent ActionEvent AdjustmentEvent ContainerEvent ItemEvent TextEvent WindowEvent MouseEvent Abb.: Spezifische Ereignisklassen Die Hierarchie der AWT-spezifischen Ereignisklassen beginnt mit der Klasse AWTEvent und befindet sich im Paket java.awt. AWTEvent ist Superklasse aller Ereignisklassen des AWT, die sich im Paket java.awt.event befinden. Dieses Paket ist in jede Klasse einzubeziehen, die sich mit dem Event-Handling von AWTAnwendungen beschäftigt. EventListener-Interface. Je Ereignisklasse gibt es ein EventListener-Interface. Es definiert eine seperate Methode für jede Ereignisart der Ereignisklasse. So besitzt bspw. das Interface MouseListener die Methoden mouseClicked, mouseEntered, mouseExited, mousePressed und mouseReleased, die beim Eintreffen des jeweiligen Ereignis aufgerufen werden. EventListener FocusListener ActionListener AdjustementListener ItemListener KeyListener MouseListener MouseMotionListener ComponentListener ContainerListener WindowListener Abb.: Hierarchie der EventListener-Interfaces Jede der Methoden eines Listener-Interface enthält als einziges Argument ein Objekt vom zugehörigen Ereignistyp. Alle Methoden sind vom Typ void. 145 Programmieren in Java 3.1.3 Anwendung lokaler Klassen für die Ereignisbehandlung Im GUI-Objekt, das einen Event-Handler benötigt wird eine lokale Klasse zur Implementierung des passenden Interface angegeben. Lokale Klassen werden lokal zu einer anderen Klasse erzeugt. Sie sind nur innerhalb dieser Klasse definiert und sichtbar. Objekte der lokalen Klasse können nur aus der erzeugenden Klasse produziert werden. Die lokale Klasse kann aber auf alle Instanzmerkmale der erzeugenden Klasse zugreifen. Eine Variante lokale Klassen sind anonyme Klassen. Sie werden ebenfalls lokal zu einer anderen Klasse erzeugt, kommen aber ohne Klassennamen aus. Dazu werden sie bei der Übergabe eines Objekts an eine Methode oder als Rückgabewert einer Methode innerhalb einer einzigen Anwendung definiert und instanziert. Damit einer anonymen Klasse überhaupt eine sinnvolle Aufgabe zugeführt werden kann, muß sie aus einer anderen Klasse abgeleitet sein oder ein bestehendes Interface implementieren. Bsp.: ActionListener-Interface für die Übernahme eines Textfeld-Inhalts in ein Textfeld zur Ausgabe Falls das Textfeld für die Ausgabe den im Textfeld für die Eingabe angegebenen Text wiedergeben soll, muß ein ActionListener (eine zusätzlich innere, anonyme Klasse) mit der Methode public void actionPerformed (ActionEvent a) definiert werden. Diese Methode umfaßt Aufrufe der Methoden getText() und setText() der Klasse TextField. Darüber kann in das Eingabetextfeld eingegebener Text in das Ausgabefeld ausgegeben werden. Da die "Methode actionPerformed" der anonymen inneren Klasse auf "eingabeTextFeld" bzw. "ausgabeTextFeld" zugreift, ist außerhalb von main() anzugeben: private static TextField eingabeTextFeld = new TextField(20); private static TextField ausgabeTextFeld = new TextField(20); Zum Schließen des Fensters wird über eine anonyme innere Klasse ein WindowAdapter (eine Art WindowListener) mit der Methode "public void windowClosing(WindowEvent e)" definiert und an den "Frame" eingekettet. Wird der Frame geschlossen, dann wird diese Methode aufgerufen, die über System.exit(0) die Applikation beendet. Eine Adapterklasse implementiert ein Interface mit mehreren Mehoden und erlaubt es somit abgeleiteten Klassen, nur noch die Methoden zu überlagern, die tatsächlich von Interesse sind. Passende Adapterklasssen stellt das Paket java.awt.event bereit, z.B. FocusAdapter, KeyAdapter, MouseAdapter, MouseMotionAdapter, ComponentAdapter, ContainerAdapter, WindowAdapter. Die Registrierung erfolgt mit der Methode addWindowListener, die in den Klassen Dialog und Frame zur Verfügung steht. Bsp.117: 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 117 PR14160 146 und Programmieren in Java 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); } }); // 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); } 147 Programmieren in Java class TFL implements ActionListener { public void actionPerformed(ActionEvent ae) { String s = eingabeTextFeld.getText(); ausgabeTextFeld.setText(s); } } public static void main(String args[]) { Vorl11b vorl11b = new Vorl11b(); vorl11b.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } } Schließlich kann das Interface auch direkt implementiert Zweckmäßigerweise ist dann die Anwendung eine Subklasse von Frame. werden. import java.lang.*; import java.awt.*; import java.awt.event.*; public class Vorl11a extends Frame implements ActionListener { private static Label eingabeTextFeldLabel = new Label("Eingabestring:"); private static TextField eingabeTextFeld = new TextField(20); private static Label ausgabeTextFeldLabel = new Label("Ausgabestring:"); private static TextField ausgabeTextFeld = new TextField(20); public Vorl11a() { eingabeTextFeld.setEditable(true); ausgabeTextFeld.setEditable(false); Panel panel = new Panel(); panel.add(eingabeTextFeldLabel); panel.add(eingabeTextFeld); panel.add(ausgabeTextFeldLabel); panel.add(ausgabeTextFeld); eingabeTextFeld.addActionListener(this); add(panel); pack(); setVisible(true); } public void actionPerformed(ActionEvent ae) { System.out.println(ae.getActionCommand()); String s = eingabeTextFeld.getText(); ausgabeTextFeld.setText(s); } public static void main(String args[]) { Vorl11a vorl11a = new Vorl11a(); vorl11a.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } } 148 Programmieren in Java 3.1.4 Externe und interne Darstellung von numerischen Werten Numerische Werte werden - extern (z.B. im Textfeld eines GUI) durch Zeichenketten dargestellt - intern als Werte von einem der elementaren Datentypen byte, short, int, long, float oder double oder als Objekte von einem der Referenzdatentypen (WrapperKlassen) Byte, Short, Integer, Long oder Double. Diese Klassen stellen Methoden zur Umwandlung von numerischen Werten in Zeichenketten und umgekehrt zur Verfügung. Da nicht jede beliebige Zeichenkette als interne Darstellung einer Zahl interpretiert werden kann, kann beim Versuch der Umwandlung eine NumberFormatExpression auftreten, die normalerweise mit einer try-catch-Anweisung entsprechend behandelt werden sollte. Bsp.: Einlesen und Ausgeben von numersichen Werten import java.lang.*; import java.awt.*; import java.awt.event.*; public class EchoZahl extends Object { // Einlesen und Ausgeben von Zeichenketten ueber // eine grafische Benutzeroberflaeche private static TextField eingabeTextFeld = new TextField(20); private static TextField ausgabeTextFeld = new TextField(20); public static void main(String args[]) { Frame fenster = new Frame("Echo"); fenster.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // TextField eingabeTextFeld = new TextField(20); Label eingabeTextFeldLabel = new Label("Zahleneingabe:"); eingabeTextFeld.setEditable(true); eingabeTextFeld.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { Double dRef = null; String s = eingabeTextFeld.getText(); try { dRef = new Double(s); } catch (NumberFormatException nfe) { System.err.println(nfe.toString()); } double d = dRef.doubleValue(); d++; String s1 = Double.toString(d); String s2 = String.valueOf(d); ausgabeTextFeld.setText(s2); } }); // TextField ausgabeTextFeld = new TextField(20); 149 Programmieren in Java 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 componentResized Bedeutung Eine Komponente wurde sichtbar Eine Komponente wurde unsichtbar Eine Komponente wurde verschoben Die Größe der Komponente hat sich verändert Abb.: Übersicht zu den Methoden von ComponentListener 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. 150 Programmieren in Java 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. Ereignismethode mousePressed mouseReleased mouseClicked mouseEntered mouseExited Bedeutung Die Maustaste wurde gedrückt Die gedrückte Maustaste wurde losgelassen Eine Maustaste wurde gedrückt und wieder losgelassen. Die Methode wird nach mouseReleased aufgerufen. Der Mauszeiger wurde in den Client-Bereich der auslösenden Komponente hineinbewegt Der Mauszeiger wurde aus dem Client-Bereich der auslösenden Komponente herausbewegt. Abb.: Übersicht zu den Methoden von MouseListener Die Ermittlung der Position des Mauszeigers kann über public int getX(); // liefert die X-Koordinate public int getY(); // liefert die Y-Koordinate public Point getPoint(); /* liefert X- und Y-Koordinate des Punkts, an dem sich der Mauszeiger beim Auftreten des Ereignisses befindet */ ermittelt werden. Koordinatenwerte werden relativ zum Ursprung der auslösenden Komponente angegeben. 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 151 Programmieren in Java 118geerbten public public public public Methoden 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. 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 118 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 152 Programmieren in Java Ein Empfänger für Key-Events muß das Interface KeyListener implementieren und bekommt Events des Typs KeyEvent übergeben. KeyEvent erweitert die Klasse InputEvent, die aus ComponentEvent abgeleitet ist, und stellt neben getID, getSource eine Reihe von Methoden zur Erkennung und Berabeitung von Tastaturcodes zur Verfügung. Die Registrierung erfolgt mit der Methode public void addKeyListener (KeyListener l), die auf allen Objekten des Typs Component oder daraus abgeleiteter Klassen zur Verfügung steht. Das Interface KeyListener definiert drei unterschiedliche Methoden: public abstract void keyTyped(KeyEvent e); public abstract void keyPressed(KeyEvent e); public abstract void keyReleased(KeyEvent e); Die Taste, die gedrückt wurde, erhält man über die folgenden Methoden der Klasse 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 Zeichentaste120: VK_UNDEFINED Funktionstaste121: 122 Zeichentaste: VK_... keyPressed Funktionstaste: VK_... keyTyped119 getKeyChar Zeichentaste: Taste als char Funktionstaste: Zeichentaste: Taste als char Funktionstaste: CHAR_UNDEFINED Rückgabecode bei Tastatur-Ereignissen 119 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 121 Dazu gehören bspw. F1, F2, Pos 1 aber auch die Umschalttasten: STRG, ALT, UMSCHALT 122 zeigt das Verhalten beim Aufruf von keyPressed 120 153 Programmieren in Java Zusätzlich stehen fogende aus InputEvent geerbten Methoden zur Verfügung: public public public public boolean boolean boolean boolean isShiftDown(); isControlDown(); isMetaDown(); isAltDown(); Bsp.: Das folgende Programm123 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()); } } class KL implements KeyListener { public void keyPressed(KeyEvent e) { System.out.println(e.toString()); } public void keyReleased(KeyEvent e) { System.out.println(e.toString()); } 123 vgl. PR14160 154 Programmieren in Java public void keyTyped(KeyEvent e) { System.out.println(e.toString()); } } class ML implements MouseListener { public void mouseClicked(MouseEvent e) { // requestFocus(); System.out.println(e.toString()); } public void mousePressed(MouseEvent e) { System.out.println(e.toString()); } public void mouseReleased(MouseEvent e) { System.out.println(e.toString()); } public void mouseEntered(MouseEvent e) { System.out.println(e.toString()); } public void mouseExited(MouseEvent e) { System.out.println(e.toString()); } } class MML implements MouseMotionListener { public void mouseDragged(MouseEvent e) { System.out.println(e.toString()); } public void mouseMoved(MouseEvent e) { System.out.println(e.toString()); } } public static void main(String[] args) { PR14166 f = new PR14166(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(200,200); f.setVisible(true); } } Die Lösung zu dieser Aufgabe kann auch über public void processEvent(AWTEvent e) erhalten werden. 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: 155 Programmieren in Java 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. 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); } } 156 Programmieren in Java 3.2 Kommunikation Anwender – Programm über Dialoge bzw. Menüs mit vor- oder freidefinierten Dialogelementen 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 Dialoge124" 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 Icon125 zuordnen. Zuordnen eines Layoutmanagers. Es wird über die Methode "public void setLayout(LayoutManager mgr)" der Klasse Container realisiert. Java stellt fünf Layoutmanager126 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.: import java.awt.*; import java.awt.event.*; public class PR14171 extends Frame { public PR14171() 124 Modale Dialaloge verhindern die Interaktion des Anwenders mit anderen Fenstern der Anwendung bis zum Schließen des Dialogfensters. 125 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. 126 Vgl. 5.4.2 157 Programmieren in Java { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { setVisible(false); dispose(); System.exit(0); } }); // Layout festelegen und Komponenten hinzufuegen int i = 0; Panel p1 = new Panel(); p1.setLayout(new GridLayout(3,1)); p1.add(new Button("Schaltflaeche " + ++i)); p1.add(new Button("Schaltflaeche " + ++i)); p1.add(new Button("Schaltflaeche " + ++i)); Panel p2 = new Panel(); p2.setLayout(new BorderLayout()); p2.add("North",new Button("Schaltflaeche " + ++i)); p2.add("South",new Button("Schaltflaeche " + ++i)); p2.add("West",new Button("Schaltflaeche " + ++i)); p2.add("East",new Button("Schaltflaeche " + ++i)); p2.add("Center",new Button("Schaltflaeche " + ++i)); // Hauptfenster setLayout(new GridLayout(1,2)); add(p1); add(p2); pack(); } public static void main(String args[]) { PR14171 f = new PR14171(); f.setVisible(true); } } Das vorliegende Programm zeigt nach dem Aufruf das folgende Fenster: 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. 158 Programmieren in Java Popup-Fenster der Klasse Dialog Die Klasse Dialog127 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.128: import java.awt.*; import java.awt.event.*; public class InfoDialog extends Dialog { protected Button schalter; protected Label label; public InfoDialog(Frame eltern, String titel, String nachricht) { super(eltern,titel,false); this.setLayout(new BorderLayout(15,15)); label = new Label(nachricht,Label.CENTER); this.add("Center",label); schalter = new Button("Okay"); schalter.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dispose(); setVisible(false); } }); Panel p = new Panel(); p.setLayout(new FlowLayout(FlowLayout.CENTER,15,15)); p.add(schalter); this.add("South",p); this.pack(); } public static void main(String args[]) { Frame f = new Frame("InfoDialog-Test"); f.setSize(300,100); f.setVisible(true); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { // dispose(); // setVisible(false); System.exit(0); } }); InfoDialog dlg = new InfoDialog(f,"Dialog-Demo", "Diese Demo wurde nach einer Vorlage von" + "David Flanagan geschrieben"); dlg.setVisible(true); } } Dialogfenster sind Übergangsfenster. Sie dienen dazu, den Benutzer über Ereignisse zu informieren oder Eingabe vom Benutzer anzufordern. Im Gegensatz zu 127 128 vgl. 5.3.4 vgl. PR14170 159 Programmieren in Java 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. Bsp.: Laden und Speichern von Dateien mit Unterstützung durch ein Dateidialogfeld. 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); } 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 160 Programmieren in Java 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); } } 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. 161 Programmieren in Java 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: Labels129, Schaltflächen(Buttons)130, Kontrollkästchen und Optionsfelder131, Auswahlmenüs132, Listenfelder133, Textbereiche und Textfelder134, Schieberegler135. Das freidefinierte 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 129 vgl. 5.2.2 vgl. 5.2.1 131 vgl. 5.2.3 132 vgl. 5.2.4 133 vgl. 5.2.5 134 vgl. 5.2.6 135 vgl. 5.2.7 130 162 Programmieren in Java übergebenen Graphics-Objekts entspricht dabei der linken oberen Ecke des Ausgabebereichs. Bsp.136: 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. 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äge137. 136 137 PR52900 vgl. 5.3.3 163 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. 164 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 Verzeichnis138 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. 138 Pfadname, in dem sich die Klassen befinden 165 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. 1. Das Erstellen eines Applets Zum Erstellen eines Applet muß immer eine „Subklasse“ der Klasse Applet139 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.: Lebenszklus eines Applet Die Methode init() wird nach dem Laden des Applet ausgeführt. Sie dient zur Initialisierung. 139 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.* 166 Programmieren in Java 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. 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-Klasse140 in den Code der Applet-Subklasse importiert141 wird. Bsp.: „Aller Anfang ist schwer!. Dieser Spruch soll mit Hilfe eines Applet gezeigt werden.. Die zugehörige Quellcodedatei „AllerAnfangApplet.java“ umfaßt142: import java.applet.*; import java.awt.*; public class AllerAnfangApplet extends Applet { Font f; String spruch; // public void init() { f = new Font(„Helvetica“,Font.BOLD,24); this.spruch = „Aller Anfang ist schwer!“; } // public void paint(Graphics g) { // Oval mit Farbe yellow g.setColor(Color.yellow); g.fillOval(10,10,330,100); // Roter Rahmen; da Java keine Linienbreite kennt, // wird die Linienbreite durrch 4 Ovale, (die sich // um Pixelbreite unterscheiden,) simuliert g.setColor(Color.red); 140 Teil des Pakets java.awt Normalerweise geschieht dies über: import java.awt.Graphics 142 vgl. PR32101 141 167 Programmieren in Java 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: 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 gezeichnet143 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. 143 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. 168 Programmieren in Java 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 Zeichenbereich144 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.145: Ein Applet zum Zeichnen von Punkten an den Stellen, an denen eine Maustaste gedrückt wurde. // Import der Pakete import java.awt.*; import java.awt.event.*; // Top-Level Klassen-Deklaration des Applets public class MausDownPunktApplet extends java.applet.Applet { // Variablen-Deklarationen private int mausX, mausY; // private boolean mausKlick = false; // Methoden, die ueberschrieben werden public void init() { setBackground(Color.yellow); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { mausX = e.getX(); mausY = e.getY(); repaint(); } }); } /* public boolean mouseDown(Event e, int x, int y) { mausX = x; mausY = y; mausKlick = true; repaint(); return true; } */ 144 145 Das ruft oft den unangenehmen Flimmereffekt bei schnellen Bildsequenzen hervor. PR32305 169 Programmieren in Java // 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. // 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; // } } 170 Programmieren in Java public void update(Graphics g) { paint(g); } } Eine überschriebene update()-Methode kann den Flimmereffekt erheblich senken. 3.3.3 „Multithreading“-fähige Applets Mit Threads können in Java Applets so erstellt werden, daß alle oder auch einzelnen Codeteile in ihrem eigenen Thread laufen, ohne andere Teile des Systems zu beeinflussen. Ein Applet kann im wesentlichen über vier Schritte Multithreading-fähig gemacht werden: 1. Erweitern der Unterschrift des Applets um implements Runnable 2. Hinzufügen einer Instanzvariablen, die den Thread des Applet enthält 3. Reduktion der start()-Methode, so daß sie außer dem Start des Threads keine weiteren Threads enthält 4. Hinzufügen der run()-Methode, die den eigentlichen Code enthält, den das Applet ausführen soll. Bsp.: Ein Applet zur Anzeige von Datum und Uhrzeit, jede Sekunde wird aktualisiert146. 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 } } 146 vgl. PR42001 171 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) {} } } // 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 } } 172 Programmieren in Java 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() { // ... } } 173 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; // Methoden public void init() { setBackground(Color.yellow); } public void start() { if (faden == null) 174 Programmieren in Java { 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 due 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()-Methode147 update() löscht den Bildschirm durch Füllen mit der aktuellen Hintergrundfarbe, setzt die aktuellen Farbe auf die Vordergrundfarbe und ruft anschließend paint() auf. Das Überschreiben von update() muß sicherstellen, daß so etwas Ähnliches geschieht. 147 vgl. PR32212 175 Programmieren in Java 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; } } 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); 176 Programmieren in Java paint(g); } public void paint(Graphics g) { xAlt = x; yAlt = y; } } 2. Double Buffering Mit „double buffering“148 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; // 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; } } 148 177 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) { paint(g); } public void paint(Graphics g) { backgroundGraphics.setColor(Color.white); backgroundGraphics.drawLine(xStart,yStart,xAlt,yAlt); backgroundGraphics.fillOval(xAlt-d/2,yAlt-d/2,d,d); backgroundGraphics.setColor(Color.blue); backgroundGraphics.drawLine(xStart,yStart,x,y); backgroundGraphics.setColor(Color.red); backgroundGraphics.fillOval(x-d/2,y-d/2,d,d); g.drawImage(backgroundImage,0,0,this); xAlt = x; yAlt = y; } } 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 URL149) - 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: 149 Adressen im World Wide Web werden durch URL-Objekte repräsentiert. Die Klasse URL (Uniform Resource Locator) ist Teil des Pakets java.net 178 Programmieren in Java - das Image-Objekt, das angezeigt werden soll - die x- und y-Koordinate - das Schlüsselwort this Mit paint() kann das Bild zur Anzeige gebracht werden: public paint(Graphigs g) { g.drawImage(imageObjekt, xKoord, yKoord, this); } Bsp.150: 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 Sound-Ausgabe in Applets Das JDK bietet Möglichkeiten zur Ausgabe von Sound 151 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ß. 150 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). 151 179 Programmieren in Java public getAudioClip(URL url, String name) Hier wird ein Objekt der Klasse AudioClip beschafft, das dann abgespielt werden kann. AudioClip stellt drei Methoden zur Verfügung: public void play() startet die zuvor geladene Sound-Datei und spielt sie genau einmal ab. public void loop() startet die zuvor geladene Sound-Datei und spielt den Sound in einer Endlosschleife immer wieder ab. public void stop() Darüber kann die zuvor mit loop() iniziierte Schleife beendet werden. Bsp.152: 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()); } } } } Sound-Ausgabe in Applikationen 152 Vgl. PR33602 180 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. 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() 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() public synchronized void deleteObserver (Observer ob) public synchronized void deleteObservers() public synchronized int countObservers() liefert die Anzahl der für in Observable registrierten Observer 181 Programmieren in Java Die Schnittstelle Observable Jede Klasse, die Mitteilungen über Modifikationen eines Observable bekommen möchte, muß das Observer-Interface implementieren. Dieses interfave 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 182 Programmieren in Java 3.5 Swing Swing153 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 JavaBeans ist das Komponentenmodell von Java. Diese Komponenten haben eine genauere und für Programmierwerkzeuge verständlichere Schnittstellendefintion als Java-Klassen. Die zur Beschreibung und Analyse benötigten Interfaces und Klassen stehen im Package java.beans. Sie arbeiten sehr eng mit den Klassen zur Laufzeitanalyse von Java-Klassen im Paket java.lang.reflect zusammen. Die Schnittstellen von JavaBeans bezeichnet man als Features. Sie besteht aus drei Teilen: JavaBeans Properties Methoden Ereignisse Abb.: Die JavaBean-Features 1. Properties sind die öffentlichen 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ß. 2. Die öffentlichen Methoden, die eine Bean ausführen kann 3. Ein JBean erzeugr Ereignisse, z.B. dann, wenn sich ihre Properties ändern. Diese drei Arten von Features sind im Interface BeanInfo dokumentiert. 153 vgl. 5.6 183 Programmieren in Java 4. Grafik und Animation 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 Methoden154 zum Neuzeichnen des Applet: public void paint(Graphics g) public void repaint() public void update(Graphics g) 4.1 Allgemeine Zeichenvorgänge 4.1.1 Punkte, Linien, Kreise, Bögen 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. 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 Linien155 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 154 155 vgl. 3.2.2 vgl. PR42005 184 Programmieren in Java public void init() { setBackground(Color.lightGray); } public void start() { if (faden == null) { faden = new Thread(this); faden.start(); } } public void stop() { if (faden != null) { faden.stop(); faden = null; } } public void run() { // Ausfuehrung des Applet while (true) { x1 = (int) (Math.random() * this.size().width); x2 = (int) (Math.random() * this.size().width); y1 = (int) (Math.random() * this.size().height); y2 = (int) (Math.random() * this.size().height); rot = (float) Math.random(); gruen = (float) Math.random(); blau = (float) Math.random(); linienFarbe = new Color(rot,gruen,blau); repaint(); // Aufruf der repaint()-Methode try {faden.sleep(1000); } // Pause von 1000 Millisekunden catch(InterruptedException e) {} } } public void paint(Graphics g) { g.setColor(linienFarbe); g.drawLine(x1,y1,x2,y2); } } Die Methode drawLine zeichnet Linien mit einer Dicke von einem Pixel. 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). Bsp.: Ein Applet mit zufällig verteilten Rechtecken156 // zeichne Rechtecke import java.applet.*; import java.awt.*; public class RechteckeAppl3 extends Applet 156 vgl. PR41103 185 Programmieren in Java { // 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); } } 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. 186 Programmieren in Java 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.157: import java.awt.*; public class ZeichnePolyAppl1 extends java.applet.Applet { // Instanzvariable // Definition des Felds mit den x-Koordinaten int xKoord[] = {20,50,70,40,20,20}; // Definition des Felds mit den y-Koordinaten int yKoord[] = {30,10,20,70,50,30}; // Methoden public void init() { setBackground(Color.yellow); } public void paint(Graphics g) { // Zeichne ein 5-Eck g.setColor(Color.red); g.drawPolygon(xKoord,yKoord,6); } } - Weitergabe einer Instanz der Polygon-Klasse, z.B.158: 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.159: import java.awt.*; public class ZeichnePolyAppl3 extends java.applet.Applet { 157 PR41105 PR41105 159 PR41105 158 187 Programmieren in Java // 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); } } 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) 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 Abb.: Gebräuchliche Farbwerte RGB-Werte) 188 Blau-Anteil 255 0 127 0 0 255 0 255 255 Programmieren in Java 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) 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. 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 FontKlasse160 und die Fontmetrics-Klasse161 beim Textzeichnen eine Rolle. Die Klasse Font 160 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. 161 189 Programmieren in Java 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 ind 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 fett162 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 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.163: 162 BOLD und ITALIC können auch gemeinsam verwendet werden, indem beide Konstanten mit „+“ zusammengefügt werden. 163 PR41301 190 Programmieren in Java import java.applet.*; import java.awt.*; public class AllAnfAppl extends Applet { private static final String spruch = "Aller Anfang ist schwer!"; private Font font; private FontMetrics fontMetrics; private int spruchBreite; private int spruchAscent; private int spruchDescent; private int spruchX; private int spruchY; // Methoden public void init() { setBackground(Color.white); font = new Font("Helvetica",Font.BOLD,24); fontMetrics = null; } // public void paint(Graphics g) { // Oval mit Farbe yellow g.setColor(Color.yellow); g.fillOval(0,0,getSize().width,getSize().height); // Roter Rahmen; da Java keine Linienbreite kennt, // wird die Linienbreite durrch 4 Ovale, (die sich // um Pixelbreite unterscheiden,) simuliert g.setColor(Color.red); g.drawOval( 3, 3,getSize().width-6,getSize().height-6); g.drawOval( 2, 2,getSize().width-4,getSize().height-4); g.drawOval( 1, 1,getSize().width-2,getSize().height-2); g.drawOval( 0, 0,getSize().width,getSize().height); g.setColor(Color.black); g.setFont(font); if (fontMetrics == null) { fontMetrics = g.getFontMetrics(); spruchBreite = fontMetrics.stringWidth(spruch); spruchAscent = fontMetrics.getAscent(); spruchDescent = fontMetrics.getDescent(); } int breite = getSize().width; int hoehe = getSize().height; spruchX = (breite - spruchBreite) / 2; spruchY = (hoehe + spruchAscent - spruchDescent) / 2; g.drawString(spruch,spruchX,spruchY); } } 191 Programmieren in Java 4.1.4 Die Java-Zeichenmodi Die Graphics-Klasse verfügt über zwei verschiedene Modi zum Zeichnen: den PaintModus und den XOR-Modus. 4.2 Das Zeichnen von Bildern (Bitmaps) 4.2.1 Kopieren von Speicher in ein Bild Ein möglicher Typ für das Produzieren von Bildern ist ein Datenfeld mit ganzen Zahlen, die für die farbe eines jeden Pixels stehen. Möglich wird das durch die Klasse MemoryImageSource. Bsp.: Das folgende Applet erzeugt ein Speicherbild, eine MemoryImageSource und zeichnet das Bild im Zeichenbereich. import java.applet.*; import java.awt.*; import java.awt.image.*; /* das Applet zeichnet ein Image und benutzt dazu einen Array mit Pixeln */ public class SpeicherBild extends Applet { private final static int b = Color.blue.getRGB(); private final static int r = Color.red.getRGB(); private final static int g = Color.green.getRGB(); int pixels[] = { b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, g, g, g, g, g, g, b, b, b, b, g, g, g, g, g, g, b, b, b, b, g, g, r, r, g, g, b, b, b, b, g, g, r, r, g, g, b, b, b, b, g, g, g, g, g, g, b, b, b, b, g, g, g, g, g, g, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b }; Image meinBild; public void init() { /* Erzeigen des Bilds aus dem Array pixels. Die Pixels werden zeilenweise von Postion 0 aus dem Array gelesen. eine Zeile umfasst 10 Postionen. */ meinBild = createImage( new MemoryImageSource(10,10,pixels,0,10)); } public void paint(Graphics g) { // Zeichen das Bild. // Breite und Hoehe des Bilds werden 10fach vergroessert g.drawImage(meinBild,0,0,100,100,this); } } 192 Programmieren in Java Abb.: Darstellung von SpeicherBild 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.2 Laden und Anzeigen einer Bitmap Das Anzeigen einer Bitmap kann in zwei Schritte unterteilt werden: - Das Laden der Bitmap von einem externen Speichermedium oder aus dem Netz - Die eigentliche Ausgabe auf dem Bildschirm Laden von Bitmaps. Es erfogt mit der Methode getImage, die es in verschiedenen Varianten164 gibt: 4.2.3 Die Klasse Mediatracker 4.2.4 Entwicklung einer eigenen Bitmap-Componente 4.2.5 Abspielen einer Foge von Bitmaps 164 Sie unterscheiden sich dadurch, aus welcher Quelle sie die Bitmap-datei laden. 193 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. 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()-Mthode in ein Graphics2DObjekt 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 194 Programmieren in Java path.lineTo(500.0f,400.0f); path.lineTo(500.0f,300.0f); path.lineTo(300.0f,300.0f); path.closePath(); // Fuellen der Form g2d.fill(path); // // // // untere rechte Ecke obere rechte Ecke obere linke Ecke Schliessen des Rechtecks } } Zur Definition des Rechtecks wird die neue Java 2D-API-Klasse GeneralPath (Bestandteil des geom-Pakets165) herangezogen. Das Erstellen von Standardformen (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. 3. Festlegen vom Aussehen der Form, des Textstrings oder des Bildes über passende Graphics2D-Methoden. Java 2D API-Klassen 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). 4.3.3 Farbmanagement unter 2D-Java 4.3.4 2D-Bildverarbeitung 4.3.5 Rendering 165 Graphics2D ist Bestandteil des Pakets java.awt 195 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 Komponenten166 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 166 Java 1.2 und das Swing-Konzept erweitern diese Komponenten noch einmal um einen satz von neuen und ergänzenden Komponenten. 196 Programmieren in Java 5.2.1 Schaltflächen (Buttons) 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 Applet167 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, kann es in der Button-Klasse durch „public void setActionCommand(String kommando)“ geändert werden. Bsp.: Setzen von Hintergrundfarben nach verschiedenen Buttonklicks168 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) { 167 168 Abgeleitet von der Panel-Klasse PR52105 197 Programmieren in Java setBackground(Color.blue); } }); schalt3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setBackground(Color.green); } }); schalt4.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setBackground(Color.yellow); } }); schalt5.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setBackground(Color.black); } }); this.setLayout(new BorderLayout(15,15)); Panel p = new Panel(); p.setLayout(new FlowLayout(FlowLayout.CENTER,15,15)); p.add(schalt1); p.add(schalt2); p.add(schalt3); p.add(schalt4); p.add(schalt5); this.add("South",p); // this.pack(); } } 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. Bsp.: Setzen von Hintergrundfarben nach verschiedenen Buttonklicks169 169 vgl. PR52105 198 Programmieren in Java 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 199 Programmieren in Java Erzeugen von Kontrollkästchen. Ein Kontrollkästchen (Checkbutton / Checkbox) hat zwei Bestandteile: ein Label (Text, der neben dem Kontrollkästchen angelegt wird) und einem Zustand (boolesche Variable, die angibt, ob die Box eingeschaltet wurde oder nicht170). 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. 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()); 170 Standardmäßig ist die Box ausgeschaltet (Wert ist false oder „off“). 200 Programmieren in Java 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 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); 201 Programmieren in Java Checkbox cb1 = new Checkbox(„rot“,cbg,false); Checkbox cb2 = new Checkbox(„blau“,cbg,false); Checkbox cb3 = new Checkbox(„gruen“,cbg,false); cb1.addItemListener(new CBL()); cb2.addItemListener(new CBL()); cb3.addItemListener(new CBL()); add(cb1); add(cb2); add(cb3); } public class CBL implements ItemListener { public void itemStateChanged(ItemEvent e) { Checkbox cb = (Checkbox) e.getItemSelectable(); if (cb.getLabel() == „rot“) { if (cb.getState()) { System.out.println(„rot“); } } if (cb.getLabel() == „blau“) { if (cb.getState()) { System.out.println(„blau“); } } if (cb.getLabel() == „gruen“) { if (cb.getState()) { System.out.println(„gruen“); } } } } public static void main(String args[]) { PR52302 f = new PR52302(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); 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.171: import java.awt.*; public class CheckAktApplet extends java.applet.Applet { CheckboxGroup meineCheckboxGruppe = new CheckboxGroup(); public void init() 171 PR54305 202 Programmieren in Java { add(new Checkbox(„Blau“,meineCheckboxGruppe,false)); add(new Checkbox(„Rot“,meineCheckboxGruppe,false)); add(new Checkbox(„Gruen“,meineCheckboxGruppe,false)); add(new Checkbox(„Gelb“,meineCheckboxGruppe,true)); // Hintergrundfarbe passend zur Voreinstellung setBackground(Color.yellow); } public boolean action(Event e, Object welcheAktion) { // Test auf Kontrollkaestchen oder Radiobutton if (!(e.target instanceof Checkbox)) return false; // Instanz der Ereignisse Checkbox welcheAuswahl = (Checkbox) e.target; // Status des Radiobuttons zur Kontrolle boolean CheckboxStatus = welcheAuswahl.getState(); // Auswahl Blau if (welcheAuswahl.getLabel() == „Blau“) { if (CheckboxStatus) setBackground(Color.blue); return true; } // Auswahl Rot if (welcheAuswahl.getLabel() == „Rot“) { if (CheckboxStatus) setBackground(Color.red); return true; } // Auswahl Gruen if (welcheAuswahl.getLabel() == „Gruen“) { if (CheckboxStatus) setBackground(Color.green); return true; } if (welcheAuswahl.getLabel() == „Gelb“) { if (CheckboxStatus) setBackground(Color.yellow); return true; } return false; // keine der Optionen wird aufgefangen } } 203 Programmieren in Java 5.2.4 Auswahlmenüs Es handelt sich um Popup- bzw. Pulldown-Menüs, die sich beim Anklicken öffnen und mehrere auszuwählende Optionen anzeigen, von denen dann eine Option ausgewählt werden kann. Konstruktor: public Choice() // erstellt ein Choice-Objekt mit einer leeren Liste. Bearbeiten der Liste (Combobox) zum Auswahlmenü: public void addItem(String item) Jeder Aufruf von addItem hängt das übergebende Element „item“ an das Ende der Liste an. Die Elemente werden in der Reihenfolge der Aufrufe von addItem eingefügt. Zugriff auf die Elemente der Combobox: public int 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. 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() 204 Programmieren in Java { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setBackground(Color.lightGray); f.setVisible(true); } } 5.2.5 Listenfelder Eine Instanz der Klasse List ist eine listenartige Darstellung von Werten, aus denen Anwender einen oder mehrere auswählen kann. Anders als ein Choice-Element ist ein Element der Klasse List ständig in voller Größe auf dem Bildschirm sichtbar. Erzeugen von Listenfeldern. Public List() // legt eine leere Liste an, deren dargestellte Größe durch den // Layoutmanager begrenzt wird. public List(int groesse) // Der Parameter groesse legt die Anzahl der angezeigten Zeilen fest. public List(int groesse, boolean multiselect) // ist multiselect „true“, kann der Anwender nicht nur ein Element, sondern //auch andere Elemente auswählen Mehrfachauswahl ausgewählter Elemente. „List“ bietet Funktionalität wie Choice an. Zusätzlich stehen zur Verfügung: nahezu dieselbe public int[] getSelectedIndexes() // liefert einen Array mit den Indexpositionen der ausgewählten // Elemente. public String[] getSelectedItems() // liefert eine Liste der Werte. Auswahl einzelner Elemente. Public void select(int index) public void deselect(int index) Entfernen, Verändern von Elementen. Public void delItem(int index) // löscht das Element an der Position index public void remove(int index) // löscht das Element an der Position index public void replaceItem(String neuerWert,int index) // ersetzt das Element an der Position index durch die Postion // neuerWert. Item- und Action-Ereignisse. Ein Listenelement sendet Item- und ActionEreignisse. Ein Action-Ereignis wird generiert, wenn ein Listenelement durch Doppelklick ausgewählt wurde. Ein Item-Ereignis wird ausgelöst, nachdem in der Liste ein Element ausgewählt wurde. Bsp.: import java.awt.*; import java.awt.event.*; public class PR14177 extends Frame { String[] farben = {„rot“,“blau“,“gruen“,“pink“,“orange“, „gelb“,“cyan“}; List lst = new List(6,true); public PR14177() { 205 Programmieren in Java setLayout(new FlowLayout()); for (int i = 0; i < 7; i++) lst.addItem(farben[i]); add(lst); lst.addItemListener(new ILL()); lst.addActionListener(new ALL()); pack(); } public class ILL implements ItemListener { public void itemStateChanged(ItemEvent e) { List lst = (List) e.getItemSelectable(); String str = lst.getSelectedItem(); int pos = ((Integer) e.getItem()).intValue(); System.out.println(„event.getSelectedItem: „ + str + „ „ + pos); } } class ALL implements ActionListener { public void actionPerformed(ActionEvent e) { Object obj = e.getSource(); if (obj instanceof List) { System.out.println(„Listenfeld-Aktion: „ + e.getActionCommand()); } } } public static void main(String args[]) { PR14177 f = new PR14177(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { // f.setVisible(false); System.exit(0); } }); f.setBackground(Color.lightGray); // f.setSize(100,160); f.setVisible(true); } } 5.2.6 Textbereiche und Textfelder Das AWT verfügt über Klassen zur Eingabe von Text (Klasse TextField für Textfelder und Klasse TextArea für Textbereiche). Textfelder begrenzen die Menge des einzugebenden Textes und können nicht gescrollt werden. In Textbereichen können mehrere Zeilen verarbeitet werden, außerdem sind Textbereiche scrollbar. TextField Ein TextField dient zur Darstellung von Text. Anwender und Programm können den dargestellten Text einlesen und verändern. Erzeugen von Textfeldern. Es stehen folgende Konstruktoren zur Verfügung: 206 Programmieren in Java public TextField() erzeugt ein leeren Textfeld, in das der Anwender Text eingeben kann. Public TextField(int greite) 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) 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 207 Programmieren in Java 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 zugegriffen. Bsp.: import java.awt.*; import java.awt.event.*; public class PR14170 extends Frame { Button b1 = new Button(„Hole Text“), b2 = new Button(„Setze Text“); TextField t1 = new TextField(30), t2 = new TextField(30), t3 = new TextField(30); String s = new String(); public PR14170() { setLayout(new FlowLayout()); b1.addActionListener(new B1A()); b2.addActionListener(new B2A()); t1.addTextListener(new T1()); t1.addActionListener(new T1A()); add(b1); add(b2); add(t1); add(t2); add(t3); } class T1 implements TextListener { public void textValueChanged(TextEvent e) { t2.setText(t1.getText()); } } class T1A implements ActionListener { private int zaehler = 0; public void actionPerformed(ActionEvent e) { t3.setText(„t1 ActionEvent „ + zaehler++); } } class B1A implements ActionListener { public void actionPerformed(ActionEvent e) { s = t1.getSelectedText(); if (s.length() == 0) s = t1.getText(); t1.setEditable(true); } } class B2A implements ActionListener { public void actionPerformed(ActionEvent e) { 208 Programmieren in Java 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. 209 Programmieren in Java 5.2.7 Schieber und Bildlaufleisten Listen und Textbereiche besitzen standardmäßig die Eigenschaft bei Bedarf nicht in den Anzeigebereich passenden Inhalt über Bildlaufleisten und Schieber zu scrollen. Bildlaufleisten können auch individuell erstellt werden und dienen zur (quasi-) analogen Anzeige bzw. Eingabe eines Werts aus einem vorgebenen Wertebereich. 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() 210 Programmieren in Java Ereignisse. Ein Scrollbar sendet Adjustment-Ereignisse an seine Ereignisempfänger. Diese müssen das Interface AdjustmentListener implementieren und sich durch Aufruf von public void addAdjustmentListener(AdjustmentListener l) registrieren. Das Adjustment-Ereignis führt im Ereignisempfänger zum Aufruf der Methode: 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 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 Bsp.: import java.awt.*; import java.awt.event.*; public class PR14175 extends Frame { public PR14175() { Panel p = new Panel(); p.setLayout(new BorderLayout()); Scrollbar hsb = new Scrollbar(Scrollbar.HORIZONTAL,1,10,1,100); hsb.addAdjustmentListener(new HsbAL()); p.add(„South“,hsb); Scrollbar vsb = new Scrollbar(Scrollbar.VERTICAL,1,10,1,100); vsb.addAdjustmentListener(new VsbAL()); p.add(„East“,vsb); add(p); pack(); } public class HsbAL implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent e) { switch(e.getAdjustmentType()) { case AdjustmentEvent.UNIT_INCREMENT: System.out.println(„Adjustment.UNIT_INCREMENT“); break; case AdjustmentEvent.UNIT_DECREMENT: System.out.println(„Adjustment.UNIT_DECREMENT“); break; case AdjustmentEvent.BLOCK_INCREMENT: System.out.println(„Adjustment.BLOCK_INCREMENT“); break; case AdjustmentEvent.BLOCK_DECREMENT: System.out.println(„Adjustment.BLOCK_DECREMENT“); break; 211 Programmieren in Java 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()); } } 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 212 Programmieren in Java ein ScrollPane automatisch die erforderlichen Schieberegister Dialogelemente horizontal und vertikal verschieben zu können. Konstruktoren. ein, um 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 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() 213 Programmieren in Java { zeichneObjekte = false; repaint(); } public void zeichnen() { zeichneObjekte = true; repaint(); } public void paint(Graphics g) { System.out.println( „Methode paint wurde aufgerufen, zeichneObjekte = „ + zeichneObjekte); if (zeichneObjekte) { // Individuelle Auspraegung der Zeichnung int r = 8; int i, j; int x, y; g.setColor(Color.red); for (i=1; i<=10; ++i) { x = 100 – r * i; y = (int) (40 + (i – 1) * 1.7321 * r); for (j=1; j<=i; ++j) { g.fillOval(x,y,2*r,2*r); x += 2 * r; } } // g.drawLine(0,0,100,100); // g.setColor(Color.blue); // g.drawString(„Hallo I2A und I2B“,100,100); // g.setColor(Color.red); // g.draw3Drect(150,150,50,50,true); } } } Die Klasse ZeichneGUI benutzt die Klasse ZeichneCanvas. Sie bezieht die Zeichenfläche in eine grafische Benutzeroberfläche mit Schaltknöpfen und Textfeldern ein. import java.awt.*; import java.awt.event.*; public class ZeichneGUI extends Frame implements MouseMotionListener { // Instanzvariable private TextField xFeld; private TextField yFeld; private Button zeichne; private Button bereinige; private ZeichneCanvas zeichenflaeche; // Konstruktoren public ZeichneGUI() { this(200,200); } public ZeichneGUI(int breite,int hoehe) { super(); setTitle(„Demonstration Zeichenflaeche“); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } 214 Programmieren in Java }); setLayout(new BorderLayout()); Panel panelN = new Panel(); panelN.add(new Label(„x: „)); xFeld = new TextField(5); xFeld.setEditable(false); panelN.add(xFeld); panelN.add(new Label(„y: „)); yFeld = new TextField(5); yFeld.setEditable(false); panelN.add(yFeld); add(„North“,panelN); zeichenflaeche = new ZeichneCanvas(breite,hoehe); zeichenflaeche.addMouseMotionListener(this); add(„Center“,zeichenflaeche); Panel panelS = new Panel(); zeichne = new Button(„Zeichne“); zeichne.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { zeichenflaeche.zeichnen(); } }); panelS.add(zeichne); bereinige = new Button(„Bereinige“); bereinige.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { zeichenflaeche.bereinigen(); } }); panelS.add(bereinige); add(„South“,panelS); pack(); setVisible(true); } // Implementierung des MouseMotionListener-Interface public void mouseMoved(MouseEvent e) { xFeld.setText(„ „ + e.getX()); yFeld.setText(„ „ + e.getY()); } public void mouseDragged(MouseEvent e) { xFeld.setText(„ „ + e.getX()); yFeld.setText(„ „ + e.getY()); } // Start der GUI public static void main(String args[]) { ZeichneGUI gui = new ZeichneGUI(); } } 215 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 216 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()-Methode172. Mit public void hide()173 kann der Frame wieder unsichtbar gemacht werden. Über public void dispose() wird der Frame beendet. Mit public boolean isShowing() kann geprüft werden, ob das Fenster bereits angezeigt ist. Standardlayout für Fenster: Border-Layout. Hinzufügen von Komponenten: Rahmen sind Container wie Panels, andere Komponenten können mit der add()-Methode hinzugefügt werden. 5.3.3 Menüs Jedes Fenster / Frame kann eine eigene Menüleiste besitzen. Jede Menüleiste kann mehrere Menüs enthalten und jedes Menü beliebige Einträge. Java unterstützt die Konstruktion von Menüs durch eine Reihe speziell dafür vorgesehenen Klassen: Klasse MenuBar Menu MenuItem CheckboxmenuItem Bedeutung Beschreibt die Menüzeile (-leiste)eines Fensters Beschreibet ein einzelnes, der in der Menüzeile enthaltenen Menüs Bildet die vom Anwender auswählbaren Einträge innerhalb der Menüs Die Menüleiste Sie beschreibt das Hauptmenü eines Fensters. Sie befindet sich unterhalb der Titelleiste am oberen Rand des Fensters und zeigt die Namen der darin enthaltenen Menüs an. Eine Menüleiste wird durch Instanzen der Klasse MenuBar erzeugt: 172 Für show() aus dem Paket java.awt.Component gibt es als neue Variante die Methode setVisible(boolean) 173 hide() ist in java.awt.Frame als „deprecated“ deklariert. Statt dessen steht setVisible(boolean) zur Verfügung 217 Programmieren in Java Konstruktor. Public MenuBar() Einfügen von Menüs. Public void add(Menu m). Entfernen von bestehenden Menüs. Public void remove(int index) public void remove(MenuComponent m) 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) 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) 218 Programmieren in Java // 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); // 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 219 Programmieren in Java Menu d = new Menu(„Datei“); MenuItem[] datei = { // new MenuItem(„Open“), // menu shortcut new MenuItem(„Exit“,new MenuShortcut(KeyEvent.VK_E)) }; ML ml = new ML(); datei[0].setActionCommand(„Open“); datei[0].addActionListener(ml); datei[1].setActionCommand(„Exit“); datei[1].addActionListener(ml); for (int i = 0; i < datei.length; i++) d.add(datei[i]); mb.add(d); // CheckboxMenuItem Menu s = new Menu(„Sicherheit“); CheckboxMenuItem[] sicherung = { new CheckboxMenuItem(„Wache“), new CheckboxMenuItem(„Versteck“) }; CMIL cmil = new CMIL(); sicherung[0].setActionCommand(„Wache“); sicherung[0].addItemListener(cmil); sicherung[1].setActionCommand(„Versteck“); sicherung[1].addItemListener(cmil); for (int i = 0; i < sicherung.length; i++) s.add(sicherung[i]); d.add(s); // Hilfsmenue Menu meineHilfe = new Menu(„Hilfe“); mb.setHelpMenu(meineHilfe); // Eintraege im Hilfsmenue meineHilfe.add(new MenuItem(„Info“)); meineHilfe.add(new MenuItem(„Hilfe“)); // Textfeld initialisieren // t.setEditable(false); // add(t,BorderLayout.CENTER); } class ML implements ActionListener { public void actionPerformed(ActionEvent e) { MenuItem ziel = (MenuItem) e.getSource(); String aktionsKommando = ziel.getActionCommand(); if (aktionsKommando.equals(„Open“)) { System.out.println(„Oeffnen“); } else if (aktionsKommando.equals(„Exit“)) { dispatchEvent(new WindowEvent(PR53331.this, WindowEvent.WINDOW_CLOSING)); } } } class CMIL implements ItemListener { public void itemStateChanged(ItemEvent e) { CheckboxMenuItem ziel = (CheckboxMenuItem) e.getSource(); String aktionsKommando = ziel.getActionCommand(); if (aktionsKommando.equals(„Wache“)) { System.out.println(„Wache „ + ziel.getState()); 220 Programmieren in Java } else if (aktionsKommando.equals(„Versteck“)) { System.out.println(„Versteck „ + ziel.getState()); } } } class RotL implements ActionListener { public void actionPerformed(ActionEvent e) { setBackground(Color.red); } } class BlauL implements ActionListener { public void actionPerformed(ActionEvent e) { setBackground(Color.blue); } } class GruenL implements ActionListener { public void actionPerformed(ActionEvent e) { setBackground(Color.green); } } class GelbL implements ActionListener { public void actionPerformed(ActionEvent e) { setBackground(Color.yellow); } } class FarbeL implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println(„Im Untermenue wurde Farbe ausgewaehlt“); } } class MonoL implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println(„Im Untermenue wurde Mono gewaehlt“); } } public static void main(String args[]) { PR53331 f = new PR53331(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(300,200); f.setVisible(true); } } 221 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. 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. 222 Programmieren in Java 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()); 5.4.2 Die einzelnen Layout-Manager Die FlowLayout-Klasse 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“); 223 Programmieren in Java add(meinKnopf5); Button meinKnopf6 = new Button(„Gelb“); add(meinKnopf6); Button meinKnopf7 = new Button(„Cyan“); 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> 224 Programmieren in Java Die BorderLayout-Klasse Die BorderLayout-Klasse unterteilt einen Container in fünf Bereiche: North, South, East, West, Center. Falls einem Container Komponenten hinzugefügt werden, geschieht das über ein spezielles Exemplar der add()-Methode ( mit einem dieser fünf Bereichsnamen). Bsp.: BorderLayout-Applet, das einem Applet einige Schaltfläche hinzufügt. Der Quellcode ist import java.awt.*; import java.applet.*; public class BorderLayoutTestApplet extends Applet { public void init() { int i = 0; setBackground(Color.yellow); setLayout(new BorderLayout()); add(„North“,new Button(„Schaltflaeche „ + i++)); add(„South“,new Button(„Schaltflaeche „ + i++)); add(„East“,new Button(„Schaltflaeche „ + i++)); add(„West“,new Button(„Schaltflaeche „ + i++)); add(„Center“,new Button(„Schaltflaeche „ + i++)); } } Für das Applet wurde folgende Größe vereinbart: <HTML> <HEAD> <TITLE>FlowLayout-Test</TITLE> </HEAD> <BODY> <APPLET CODE=“BorderLayoutTestApplet.class“ WIDTH=500 HEIGHT=100> </APPLET> </BODY> </HTML> Das führt zu dem folgenden Layout: Die GridLayout-Klasse Diese Klasse teilt den Container in ein Gitter mit gleich großen Zellen ein. Hinzugefügte Komponenten werden von links nach rechts angeordnet, begonnen wird bei den linken, oberen Zellen. Bsp.: Plazieren von Schaltflächen Der folgende Quellcode 225 Programmieren in Java 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 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: 226 Programmieren in Java - - - - - 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). Bsp.174: Plazieren von Schaltflächen import java.awt.*; import java.util.*; import java.applet.Applet; public class GridBagBsp extends Applet { protected void macheSchalter(String name, GridBagLayout gridbag, GridBagConstraints c) { Button schalter = new Button(name); gridbag.setConstraints(schalter, c); add(schalter); } public void init() { GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); setFont(new Font(„Helvetica“, Font.PLAIN, 14)); setLayout(gridbag); c.fill = GridBagConstraints.BOTH; c.weightx = 1.0; macheSchalter(„Schaltflaeche1“, gridbag, c); macheSchalter(„Schaltflaeche2“, gridbag, c); macheSchalter(„Schaltflaeche3“, gridbag, c); // letzter Schalter in der Zeile: c.gridwidth = GridBagConstraints.REMAINDER; macheSchalter(„Schaltflaeche4“, gridbag, c); // neue Zeile, weightx wiederherstellen: c.weightx = 0.0; macheSchalter(„Schaltflaeche5“, gridbag, c); // vorletztes Element der Zeile: c.gridwidth = GridBagConstraints.RELATIVE; macheSchalter(„Schaltflaeche6“, gridbag, c); // letztes Element: c.gridwidth = GridBagConstraints.REMAINDER; macheSchalter(„Schaltflaeche7“, gridbag, c); // neue Zeile, Wert wiederherstellen: c.gridwidth = 1; c.gridheight = 2; c.weighty = 1.0; 174vgl. PR54205 227 Programmieren in Java macheSchalter(„Schaltflaeche8“, gridbag, c); c.weighty = 0.0; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; macheSchalter(„Schaltflaeche9“, gridbag, c); macheSchalter(„Schaltflaeche10“, gridbag, c); resize(300, 100); } public static void main(String args[]) { Frame f = new Frame(„GridBag Layout Beispiel“); GridBagBsp bsp = new GridBagBsp(); bsp.init(); f.add(„Center“, bsp); f.pack(); f.resize(f.preferredSize()); f.show(); } Die CardLayout-Klasse 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) 228 bestimmt die Anfangsposition 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); } } } 229 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()-Methode175 ist unter dem AWT-Event-Handler die allgemeinste Art, wie das AWT auf irgendwelche Ereignisse eingeht, die auf der Benutzeroberfläche stattfinden. Ereignisse werden innerhalb der handleEvent()Methode interpretiert und dann gezielt passende Methoden aufgerufen. Damit die handleEvent()-Funktion nicht zu groß und unübersichtlich wird, ruft handleEvent() mehrere Hilfsfunktionen (mouseEnter, keydown, action, ... ) auf, die die jeweils zugeordneten Events bearbeiten. Die Verarbeitung von Ereignissen stützt sich auf die Klasse Event. Die Instanzvariable „id“ der Klasse Event enthält die Art des Ereignisses und liefert alle notwendigen Informationen. Das Attribut „target“ enthält die Information, welches Objekt das Ereignis ausgelöst hat. Bsp.: Darstellung einer Datei im Textbereich eines Fensters (Frame) import java.awt.*; import java.io.*; public class DateiDarsteller extends Frame { // Instanzvariable String dateiName; // Konstruktoren public DateiDarsteller() throws IOException { super("Dateidarsteller "); MenuBar menue = new MenuBar(); Menu m = new Menu("Datei"); m.add(new MenuItem("Oeffnen")); m.add(new MenuItem("Schliessen")); menue.add(m); // Installiere diesen Menuebalken setMenuBar(menue); this.show(); } // Methoden public void dateiDarstellen() throws IOException { try { File d = new File(dateiName); int groesse = (int) d.length(); int anzZeich = 0; FileInputStream ein = new FileInputStream(d); byte[] daten = new byte[groesse]; while (anzZeich < groesse) anzZeich += ein.read(daten,anzZeich,groesse); String s = new String(daten,0); TextArea textBereich = new TextArea(s,24,80); textBereich.setFont(new Font("Helvetiva",Font.PLAIN,10)); textBereich.setEditable(false); this.add("Center",textBereich); 175 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, 230 Programmieren in Java } catch (IOException e) { } this.show(); } public boolean handleEvent(Event e) { if ((e.id == Event.ACTION_EVENT) && (e.target instanceof MenuItem)) { if (((MenuItem) e.target).getLabel().equals("Oeffnen")) { FileDialog fd = new FileDialog(this,"Dateidialog"); fd.show(); dateiName = fd.getFile(); System.out.println("Dateiname: " + dateiName); try { this.dateiDarstellen(); } catch (IOException a ) { } return true; } if (((MenuItem) e.target).getLabel().equals("Schliessen")) { this.hide(); this.dispose(); System.exit(0); return true; } } return false; } public static void main(String argv[]) throws IOException { try { DateiDarsteller DD = new DateiDarsteller(); } catch (IOException e) { System.out.println(e); } } } Behandlung von Aktionsereignissen Aktionsereignisse werden über public boolean action(Event e, Object arg) behandelt. Zuerst muß innerhalb der methode überprüft werden, welche Komponente der Benutzeroberfläche die Aktion erzeugt hat. Zur vereinfachten Bearbeitung beinhaltet das Event-Objekt, das beim Aufruf von action() erhalten wird, eine "target"-Instanzvariable, die eine Referenz zu dem Objekt, das das Ereignis aufgenommen hat, enthält. Mit dem instanceof-Operator kann dann bestimmt werden, von welcher Komponente das Ereignis ausging. Das zweite Argument dient zum Bestimmen des Labels176, der Elemente, des Inhalts der Komponenten177. Anstatt des zusätzlichen Arguments kann auch die Methode178 getLabel verwendet werden. Behandlung von Fokus-Ereignissen Für die Ereignisse "Fokuserhalt" und "Fokusverlust" können die Methoden 176 vgl. PR52105 Nicht vergessen: Das Argument in den richtigen Objekt-Typ zu casten 178 vgl. PR54305 177 231 Programmieren in Java public boolean getFocus(Enet evt, Object arg) bzw. public boolean lostFocus(Event evt, Object arg) verwendet werden. Ereignisse von Listenfeldern Listenfelder erzeugen drei verschiedene Ereignisarten: Auswahl bzw. Abwahl eines Eintrags in der Liste bzw. Doppelklick auf einen Eintrag. Ein Doppelklick auf einen Eintrag kann mit der Methode action bearbeitet werden. Auswahl und Abwahl eines Listeneintrags kann mit "handleEvent" und Überprüfen auf die Ereignisse mit LIST_SELECT und LIST_DESELECT erfolgen. 5.5.2 Das Event-Handling in Java 1.1 bzw. Java 1.2 Im neuen Ereignismodell java.util.EventObject. java.awt.AWTEvent. erben die Ereignisklassen von der AWT-Events erben von der Klasse Klasse EventListener Falls eine Klasse im Ereignisbehandlungsmodell von Java 1.2 aus ein Ereignis reagieren will, muß diese eine Schnittstelle implementieren, die dieses Ereignis verarbeitet. Diese Schnittstellen werden als EventListener bezeichnet. Jeder Listener behandelt eine bestimmte Ereignisart. Eine Klasse kann so viele Listener implementieren, wie benötigt werden. Die folgenden EventListener stehen zur Verfügung: ActionListener AdjustmentListener FocusListener ItemListener KeyListener MouseListener MouseMotionListener WindowListener Aktionsereignisse, die durch den Benutzer ausgelöst werden, z.B. Klick auf eine Schaltfläche Ereignisse, die erzeugt werden, wenn werte einer Komponente eingestellt werden (z.B. Bewegung eines Schiebers einer Bildlaufleiste) Ereignisse, die erzeugt werden, wenn eine Komponente, z.B. ein textfeld, den eingabefokus erhält oder verliert. Ereignisse, die erzeugt werden, wenn ein Element, z.B. ein Kontrollkästchen, verändert wurde Tastaturereignisse, die bei Tastatureingaben erzeugt werden. Mausereignisse, die erzeugt werden, wenn mit der maus geklickt wird, die Maus in den Bereich einer Komponente eintritt bzw. diese wieder verläßt Mausereignisse, die die Bewegung einer Maus über eine Komponente verfolgen Ereignisse, die von Fenstern erzeugt werden Das Paket java.awt.event beinhaltet alle elementaren EventListener. Über import java.awt.event.* erfolgt das Importieren in die Anwendungen. 232 Programmieren in Java Event-Handling mit Hilfe lokaler Klasse Für die Ereignisbehandlung werden lokale Klassen herangezogen. Im JDK 1.0 wurden Klassen nur auf Paketen definiert, eine Schachtelung war nicht möglich. Im JDK 1.1 kann innerhalb bestehender Klassen eine neue Klasse definiert werden (Konzept der „Inner Classes“), die nur innerhalb der bestehenden Klasse sichtbar ist. Objektinstanzen der inneren Klasse können nur aus der umfassenden Klasse heraus erzeugt werden. Allerdings kann die innere Klasse auf die Instanzvariablen der äußeren Klasse zugreifen. Mit Hilfe lokaler Klassen werden die benötigten EventListener implementiert. Dazu wird in dem GUI-Objekt, das einen Event-Handler benötigt, eine lokale Klasse definiert (und aus einer passenden AdapterKlasse abgeleitet). Ein einführendes Bsp.: Event-Handling nach der Bearbeitung von Schaltflächen des AWT in Version 1.0 und 1.1 // Einfangen von Klicks auf Schaltflaechen import java.awt.*; import java.awt.event.*; import java.applet.*; public class { Button sch1 Button sch2 public void { SchalterAppl extends Applet = new Button("Schaltflaeche 1"); = new Button("Schaltflaeche 2"); init() sch1.addActionListener(new Sch1()); sch2.addActionListener(new Sch2()); add(sch1); add(sch2); } class Sch1 implements ActionListener { public void actionPerformed(ActionEvent e) { getAppletContext().showStatus("Schaltflaeche 1"); } } class Sch2 implements ActionListener { public void actionPerformed(ActionEvent e) { getAppletContext().showStatus("Schaltflaeche 2"); } } /* public boolean action(Event e, Object welcheAktion) { if (e.target.equals(sch1)) getAppletContext().showStatus("Schaltflaeche 1"); else if (e.target.equals(sch2)) getAppletContext().showStatus("Schaltflaeche 2"); else return super.action(e,welcheAktion); return true; } */ } 233 Programmieren in Java Einfügen / Entfernen passender Listener Das neue Event-Modell von Java 1.1 bzw. 1.2 hat mit dem JDK 1.0 offensichtlich nur noch wenig gemeinsam. Alle AWT-Komponenten haben im neuen AWT-EventHandling „addXXXListener()“und „removeXXXListener()“-Methoden erhalten. „XXX“ beschreibt den Typ des Events. Die folgende Tabelle zeigt Ereignisse, „Listeners“, Methoden und Komponenten, die über addXXXListener() bzw. removeXXXListener() auf spezifische Ereignisse reagieren: Event, „listener interface“ „remove“-Methoden ActionEvent ActionListener addActionListener() removeActionListener() AdjustmentEvent AdjustmentListener addAdjustmentListener() removeAdjustmentListener() 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 und „add“- bzw. Komponenten, die dieses Ereignis unterstützen Button, List, TextField, MenuItem und seine abkömmlinge einschl. CheckboxmenuItem, menu und PopupMenu Scrollbar, alles was mit der Erzeugung vom „Adjustable“Interface zu tun hat Component und seine Abkömmlinge, einschl. Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, TextField Container und seine Abkömmlinge, einschl. Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame Component und seine Abkömmlinge einschl. Button, Canvas, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, 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 234 Programmieren in Java addTextListener() removeTextListener() Abb.: Event- und Listener-Typen Jeder Komponenten-Typ unterstützt nur bestimmte Ereignis-Typen: Komponenten-Typ Adjustable Applet Button Canvas Checkbox CheckboxMenuItem Choice Component Container Dialog FileDialog Frame Label List Menu MenuItem Panel PopupMenu Scrollbar ScrollPane TextArea TextComponent TextField Window Ereignis, das durch die Komponente unterstützt wird AdjustmentEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ItemEvent, FocusEvent, 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 Methoden für die Ereignisbehandlung Da nun bekannt ist, welche Ereignisse eine bestimmte Komponente unterstützt, kann das zugehörige „Listener“-Interface angegeben werden: 235 Programmieren in Java „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. 236 Programmieren in Java Bsp.: Bearbeitung von zwei Schaltflächen und einem Textfeld179 // 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); } } 179 PR55202 237 Programmieren in Java 5.6 Benutzerschnittstellen mit Swing Swing-Komponenten sind Subklassen der Klasse JComponent. 5.6.1 Komponenten und Container 238 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 Mngenoperationen 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 implementiert. Zudem gibt es jeweils Implementierungen. So gibt es bspw. Implementierungsvarianten LinkedList bzw. AbstractList. und den angegebenen Namen eine oder mehrere konkrete für das Interface List die die abstrakte Implementierung 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. 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() throws ArrayIndexOutOfBoundException; 239 Programmieren in Java 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 bereitgestellt180. 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 für alle Elemente, die sich in Vector befinden, z.B.: 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 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 180 und wird deshalb in der Java-Welt Enumerator genannt. 240 Programmieren in Java 72 10 = 1 ⋅ 64 + 0 ⋅ 16 + 2 ⋅ 4 + 0 = 1020 4 5310 = 1 ⋅ 32 + 1 ⋅ 16 + 0 ⋅ 8 + 1 ⋅ 4 + 0 ⋅ 2 + 1 = 1101012 Mit einem Stapel läßt sich die Umrechnung folgendermaßen unterstützen: 6 leerer Stapel n = 355310 7 7 4 4 4 1 1 1 1 n%8=1 n/8=444 n%8=4 n/8=55 n%8=7 n/8=6 n = 444 10 n = 5510 n = 610 n%8=6 n/6=0 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 Testprogramm181 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(); } } 181 PR61210 241 Programmieren in Java 6.1.3 Die Klasse Hashtable Die Klasse Hashtable ist eine Konkretisierung der abstrakten Klasse Dictionary. Diese Klasse beschreibt einen assoziativen Speicher, der Schlüssel auf Werte abbildet und über den Schlüsselbegriff einen effizienten Zugriff auf den Wert ermöglicht. 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 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); } } 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); } } 242 Programmieren in Java 6.1.4 Die Klasse Properties Die Klasse Properties ist aus HashTable abgeleitet. 6.1.5 Die Klasse BitSet 6.2 Java 2 Collections 6.2.1 Die Collection des Typs List 6.3 Die Klasse StringTokenizer 6.4 Die Klasse Random 6.5 Die Klassen Date, Calendar 243 Programmieren in Java 7. Ein- und Ausgabe In Java werden Ein-, Ausgabeoperationen über sog. Datenströme182 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.io183 , das bei jedem Programm mit Ein-/Ausgabeoperationen importiert werden muß. Einfache Strom-Methoden erlauben nur das Versenden von Bytes mit Hilfe von Datenströmen184. 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. 182 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. 183 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. 184 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. 244 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 BufferdOutputStream DataOutputStream PrintStream EOFException FileNotFoundException InterruptedIOException UTFDataFormatException Abb.: java.io 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 ByteArrayInputStream StringBufferInputStream Funktion Argument für den Konstruktor Nutzungsmöglichkeit Ein Puffer im arbeitsspeicher Der Puffer aus dem Bytes geholt wird ald Eingabestrom benutzt. werden. Als Datenquelle. Verbunden mit einem FileInputStream-Objekt kann daraus ein nützliches Interface gestaltet werden. Konvertiert einen String in einen Ein String. Die zugrundeliegende InputStream Implementierung benutzt einen StringBuffer. Als Datenquelle. Verbunden mit einem FiIenputStream-Objekt 245 Programmieren in Java 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. 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. Abb.: Eingabestrom-Typen Quellen zu einem Eingabestrom können sein: 1. Ein Array von Bytes 2. Ein Zeichenketten-Objekt 3. Eine Datei 4. Eine „Pipe“ 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“185 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 185 Rückgabe „-1“ bedeutet zum Unterschied von C nicht, daß ein Fehler aufgetreten ist. 246 Programmieren in Java 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 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 247 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. Programmieren in Java 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 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 Abb.: „FilterInputStream“-Typen 248 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 Programmieren in Java Nützliche Subklassen von FilterOutputStream sind: Klasse DataOutputStream PrintStream BufferedOutputStream Funktion Konstruktor-Argumente Nutzungsmöglichkeit Ausgabe primitiver Typen OutputStream Erlaubt das Schreiben primitiver Typen Produziert formatierte Ausgabe. OutputStream Formatierte Ausgabe Gepufferte Ausgabe, der Puffer OutputStream kann mit flush() geleert werden Gepufferte Ausgabe Abb.: „FilterOutputStream“-Typen Gepufferte Ströme Gepufferte Ströme werden mit der Klasse BufferedInputStream für die Eingabe und BufferedOutputStream für die Ausgabe realisiert: public BufferedInputStream(InputStream ein) public BufferedInputStream(InputStream ein, int puffergroesse) public BufferedOutputStream(OutputStream aus) public BufferedOutputStream(OutputStream aus, int puffergroesse) 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: 249 Programmieren in Java public public public public public public public public public public public 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 IOException186 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) 186 UTF (Unicode Transmission Format) ist ein spezielles Format zum Kodieren von 16-Bit-Unicode-Werten 250 Programmieren in Java public PrintStream(OutputStream aus, boolean autoFlush) Methode für die Ausgabe von Objekten. public public public public public public public public public public public public public public public public public public public public void 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) void println(String s) void println(char c) void println(char[] puffer) void println(char c) void println(int i) void println(float f) void println(double d) void println() 251 Programmieren in Java 7.3 Die Klasse File Ein File-Objekt kann sich auf eine Datei oder ein Verzeichnis beziehen und läßt sich auf verschiedene Arten erstellen. public File(String pfadname) erstellt eine File-Instanz, die in pfadname angegeben wurde. public File(String pfadname, String dateiname) erstellt eine File-Instanz, die sich aus dem in dateiname angegebenen Dateinamen und dem in pfadnamen aungegebenen Pfad zusammensetzt. public File(File verzeichnis, String dateiname) erstellt eine File-Instanz, die sich aus dem in dateiname angegebenen Dateinamen und dem in verzeichnis angegebenen Verzeichnis zusammensetzt. Die Klasse File umfaßt Methoden zum Berabeiten von Dateien bzw. Verzeichnissen. Die Methoden public boolean isFile() public boolean isDirectory überprüfen, ob eine Datei oder ein Verzeichnis vorliegt. Mit public boolean canRead() public boolean canWrite() wird Lese- bzw. Schreiberlaubnis überprüft. Die Methode public long lastModified() zeigt an, wann die Datei bzw. das Verzeichnis geändert wurde. Mit public boolean exists wird die physikalische Existenz einer Datei oder eines Verzeichnisses überprüft. Über public String getName() wird der name einer Datei bzw. eines Verzeichnisses ermittelt. 7.4 Die RandomAccessFile-Klasse Für den Zugriff auf Random-Access-Dateien stellt das Paket java.io die Klasse RandomAccessFile zur Verfügung. Es kann nur auf Dateien zugegriffen werden, auch das durch die „Streams“ realisierte Filter-Konzept gibt es in Random-AccessDateien nicht. Öffnen, Neuanlegen und Schließen. Das Öffnen von Random-Access-Dateien erfolgt mit den Konstruktoren: public RandomAccessFile(File datei, String mode) throws IOException Bei der Übergabe des File-Objekts wird die durch dieses Objekt spezifizierte Datei geöffnet. 252 Programmieren in Java 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 Dateiposition187 bestimmt werden. 7.5 Spezielle nützliche Ströme Die LineInputStream-Klasse Die SequenceInputStream-Klasse Die PushBackInputStream-Klasse Die StreamTokenizer-Klasse. Sie zerlegt einen Zeichenstrom in einen Tokenstrom188. Die Klasse StreamTokenizer ist nicht von den Klassen InputStream und OutputStream abgeleitet. Da die Klasse mit InputStream-Objekten arbeitet, gehört sie zum IO-Bereich. Die Klasse StreamTokenizer zerlegt einen InputStream in eine Folge von Token (Textbereiche, die durch beliebige Zeichen eigener Wahl abgegrenzt sind). Falls ein Token bspw. einem Wort entsprechen soll, dann ist es begrenzt durch leerzeichen und durch Interpunktionszeichen. 187 Der Dateipositionswert in den Methoden seek und getFilePointer ist die Anzahl der Beytes vom anfang bis zum ende der Datei. 188 Token: einzelne Wörter bzw. Satzzeichen 253 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. 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. Einige Klassen bleiben von der Umstellung unberührt: DataOutputStream, File, RandomAccessFile, SequenceInputStream. 254 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 Die abstrakte Basisklasse konvertiert Byte- in Character-Streams. 255 Programmieren in Java FileReader Sie ermöglicht die Eingabe einer Datei. 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. 256 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 Konvertzierung zwischen Character- und ByteStreams. FileWriter 257 Programmieren in Java ermöglicht die Ausgabe in eine Datei. Sie implementiert die abstrakten Eigenschaften von Writer. Die Klasse FileWriter bietet 4 Konstruktoren für das Öffnen von Dateien an: public FileWriter(String dateiName) throws IOException Falls dateiName eine bereits vorhandene Datei bezeichnet, wird sie geöffnet und ihr bisheriger Inhalt gelöscht, anderenfalls wird eine neue Datei mit diesem Namen angelegt. Sukzessive Aufrufe von write schreiben dann in diese Datei. public FileWriter(String dateiName, boolean append) throws IOException Wird der Parameter append mit dem Wert true an den Konstruktor übergeben, dann werden die Ausgabezeichen an die Datei angehängt, falls sie bereits existiert. public FileWriter (File datei) throws IOException public FileWriter (FileDescriptor fd) Parameter ist hier ein File-Objekt bzw. ein FileDescriptor-Objekt Die Klasse StringWriter Sie besitzt zwei Konstruktoren: public StringWriter() bestimmt einen StringWriter in der Standardgröße eines StringBuffer-Objekts. Der interne Puffer wächst automatisch, falls fortgesetzte Aufrufe von „write“ das eroderlich machen public StringWriter(int initialGroesse) Zugriffe: Die schreibenden Zugriffe auf die Puffer erfolgen mit den von Writer bekannten „write“-Methoden. Für den Zugriff auf den Inhalt des Puffers gibt es die Methoden public StringBuffer getBuffer() und public String toString(). CharArrayWriter Sie schreibt Zeichen in ein Character-Array, das automatisch bei fortgesetzten Aufrufen von „write“ erweitert wird. Die Klasse besitzt zwei Konstruktoren: public CharArrayWriter() public CharArrayWriter(int initialGroesse) Zugriffe: Schreibende Zugriffe erfolgen mit den von „Writer“ bekannten „write“Methoden. Für den Zugriff auf das Array gibt es die Methoden public String toString() und public char[] toCharArray(). Mit der Methode public void reset() wird der interne Puffer geleert. Die Größe des Zeichen-Array wird über public int size() ermittelt. Durch Aufruf von public void writeTo(Writer aus) throws IOException kann der Inhalt des Array an einen anderen Writer übergeben werden und so mit einem einzigen Funktionsaufruf in eine Datei geschrieben werden. BufferedWriter Die Klasse puffert „Stream“-Ausgaben über einen internen Puffer, in dem die Ausgaben von write zwischengespeichert werden. Falls der Puffer gefüllt ist oder die Methode flush aufgerufen wird, werden alle gepufferten Ausgaben in den „Stream“ geschrieben. Das Puffern der Ausgabe ist immer dann nützlich, wenn die Ausgabe in eine Datei geschreiben werden soll. BufferedWriter besitzt zwei Konstruktoren: public 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. 258 Programmieren in Java 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) 259 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"); } // 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"); } 260 Programmieren in Java // 5. Speichern und Wiedergewinnen von Daten try { DataOutputStream aus2 = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("Daten.txt"))); aus2.writeDouble(3.14159); aus2.writeBytes("Das war Pi"); aus2.close(); DataInputStream ein5 = new DataInputStream( new BufferedInputStream( new FileInputStream("Daten.txt"))); BufferedReader ein5br = new BufferedReader( new InputStreamReader(ein5)); System.out.println(ein5.readDouble()); System.out.println(ein5br.readLine()); } catch(EOFException e) { System.out.println("Ende des Stroms"); } } catch(FileNotFoundException e) { System.out.println("Datei nicht gefunden: " + args[1]); } catch(IOException e) { System.out.println("IO Exception"); } } } 8. Serialisierung Serialisierung189 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. 9. Netzwerkprogrammierung 189 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. 261