Programmieren in Java Prof. Jürgen Sauer Programmieren in Java Skriptum zur Vorlesung im WS 1999/2000 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 0.3.1 0.3.2 Der erste Versuch Das erste Programm Bestandteile eines Programms 1. Einführung in die Java-Programmierung 1.1 Übersicht zur Entwicklung der Programmiersprache Java 1.2 Was ist Java? 1.3 Einstieg in die Java-Programmierung 1.3.1 Die Software für die Java-Programmierung 1.3.2 Applets und Anwendungen 1.3.2.1 Entwickeln einer Java-Anwendung 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 2.1.1 2.1.2 2.1.3 Token Schlüsselworte Bezeichner und Namenskonventionen Literale 1. Ganzzahlige Literale 2. Gleitpunktliterale 3. Boolesche Literale 4. Zeichenliterale 5. Zeichenkettenliterale 2.1.4 Trennzeichen 2.1.5 Operatoren 2.1.6 Kommentare, eingebettete Dokumentation 2.2 Typen 2.2.1 Primitive Datentypen 2.2.2 Opeartionen 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.3 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 Ausdrücke Arithmetische Ausdrücke Bewertung von Ausdrücken Typkonvertierungen Vergleichsoperatoren Logische Ausdrücke 2.4 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.4.10 2.4.11 Anweisungen Blöcke und Anweisungen Leere Anweisungen Benannte Anweisungen Deklarationen Ausdrucksanweisungen Auswahlanweisungen Wiederholungsanweisungen Sprunganweisungen Synchronisationsanweisungen Schutzanweisungen Unerreichbare Anweisungen 2.5 Klassen 2.6 Methoden 2.6.1 Die Deklaration 2.6.2 Die Zugriffsspezifizierung 2.6.3 Die Methodenmodifizierer 3 Programmieren in Java 2.6.4 2.6.5 2.6.6 Rückgabewerte von Methoden Methodenname und Parameterliste Überladen und Überschreiben von Methoden 3. Das Arbeiten mit Objekten 3.1 Ereignisbehandlung unter grafischen Benutzeroberflächen (Graphical User Interface) 1. Gestaltung von grafischen benutzeroberflächen mit Hilfe der AWT-Klassen 2. Ereignisbehandlung unter grafischen benutzroberflä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.3 Grundlagen der Applet-Erstellung 3.3.1 Die interne Arbeitsweise eines Applets Applet-Methoden Methoden zur Ereignisbehandlung in Applets „Multithreading“-fähige Applets 3.3.2 Die Applet-Klasse 3.4 Kollektionen (Container) Die Klasse Vector Die Klasse Stack Die Klasse Hashtabelle Die Klasse Bitset 4. Grafik und Animation 4.1 4.1.1 4.1.2 4.1.3 Allgemeine Zeichenvorgänge Punkte, Linien, Kreise, Bögen Farbangaben Textausgabe über den Zeichenmodus Die Klasse Font Die Klasse FontMetrics 4.1.4 Die Java-Zeichenmodi 4.1.5 Das Zeichnen von Bildern 4.2 4.2.1 4.2.2 4.2.3 Animation Aufbau eines Animationsrahmens Abspielen einer Animation Reduktion von Flimmereffekten in Animationen 4.3 Grafikoperationen mit Java 2D 4 Programmieren in Java 5. AWT 5.1 Bestandteile des AWT 5.2 5.2.1 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 Die AWT-Komponenten Schaltflächen (Buttons) Labels Kontrollkästchen und Optionsfelder Auswahlmenüs Listenfelder Textbereiche und Textfelder Schieber und Bildlaufleisten Zeichenbereiche 5.3 5.3.1 5.3.2 5.3.3 5.3.4 Container Panels Frames Menüs Dialoge 5.4 Die Layout-Manager 5.4.1 5.4.2 Layout-Regeln Die einzelnen Layout-Manager 5.5 5.5.1 5.5.2 Die Event-Modelle 1.0 und 1.1 Der AWT-Handler 1.0 Das Event-Handling 1.1 6. Multithreading 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 Java 1.1 IO-Ströme Grundlagen 5 Programmieren in Java 7.6.2 7.6.3 7.6.4 Die abstrakte Klasse Reader und ihre Ableitungen Die abstrakte Klasse Writer und ihre Ableitungen Demonstrationsprogramm zur Ein-/Ausgabe (ab Java Version 1.1) 8. 6 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 Überlegungungen (Arbeitsanweisungen) eines Programmierers einfach und anschaulich darstellen kann. Java wurde ab 1991 bei Sun Microsystems entwickelt. Verantwortlich für Java ist JavaSoft, eine Tochterfirma von Sun Microsystems. Java und JavaSoft halten permanent die aktuellsten Informationen im Internet bereit. 0.2 Java-Maschine und Programmiersystem Die Java-Maschine 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). 7 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) 8 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 Ausgangsbais 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."); } } 9 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); } 10 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.: Java-Operatoren 11 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);). 12 Programmieren in Java Standardmäßig haben Klassen Zugriff auf die Klassen im Paket „java.lang“. Zur Bezugnahme auf eine Klasse, die sich nicht in java.lang befindet, sind alle Pakete, in denen sich die Klasse befindet, anzugeben, z.B. java.awt.Color // bezieht sich auf die Klasse Color im Paket awt, // das sich im Paket java befindet. Leerraumzeichen: Sie können in beliebiger Anzahl und an jedem Ort zwischen Token (, die eine Funktion besitzen) zur übersichtlichen Gestaltung des Quellcodes plaziert werden. Solche Zeichen sind bspw.: Space, Tab, Zeilenende, Formularvorschub Kommentar: Er wird vom Compiler ignoriert. Man unterscheidet den Kommentar bis zum Zeilenende „//“ und den eingebetteten Kommentar „/* ... */“, z.B.: /* ErstesProgramm ist eine Applikation, die den einfachen Gebrauch von Zeichenketten aufzeigt */ Übersetzung und Ausführung. Befindet sich der Quelltext zum Programm in einer Datei mit dem Namen ErstesProgramm.java, dann kann dieses Programm durch Aufruf des JavaCompilers javac in ablauffähigen Bytecode übersetzt werden. Das geschieht über das folgende Systemkommando: javac 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. 13 Programmieren in Java Konsole: Arbeitsspeicher: javac ErstesProgramm.java Externspeicher ErstesProgramm.java javac ErstesProgramm.class java ErstesProgramm ErstesProgramm.class Java (-Interpreter) "Das erste Programm der Verlesung Programmieren in Java" 14 Programmieren in Java 0.3.2 Bestandteile eines Programms Wesentliche Programmelemente in Java sind: - Anweisungen Anweisungen1 gehören zu den elementaren ausführbaren Programmelementen. Eine Anweisung kann eine Deklaration enthalten, einen Ausdruck 2 auswerten oder den Programmablauf3 (Auswahl-, Iterations-, Sprung-Anweisungen und return-, throw-Anweisung) steuern. - Blöcke Ein Block4 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 Methoden5 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 Sie6 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-Methoden7 auf. 1 vgl. 2.4 vgl. 2.4.5 3 vgl. 2.4.6 4 vgl. 2.4.1 5 vgl. 2.6 6 vgl. 2.5 7 CallBack-Methoden sind von der abgeleiteteten Klasse zur Verfügung gestellte Methoden, die vom Browser bzw. Appletviewer aufgerufen werden 2 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 Microsystems 8 entstanden. Man entschied sich bei Sun zur Realisierung eines im Jahre 1990 begonnenen Projekts9 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-Browser10 wurde zu einer Zielplattform der neuen Programmiersprache11, die Ende 1994 für das Internet umgearbeitet wurde und über das Netz frei und umsonst verteilt wurde. 1995 wurde die neue Programmiersprache mit dem Namen Java 12 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 Workshop13, 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 8 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) 10 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. 11 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. 12 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) 13 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. 9 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 214 eingeführt. 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 ist15 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 Java16 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)17. - 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 Systemen befinden. Objekte können mit Java-Programmen über URLs18 vom gesamten Web 14 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 15 Offizielle Definition von Sun Microsystems 16 vgl. 2.3 17 vgl. 2.4. 18 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). 17 Programmieren in Java 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-Laufzeitumgebung19 interpretiert wird. Java-Compiler (Pentium) Java-Interpreter (Pentium) Java-Code Java-Compiler (SPARC) Java-Interpreter (SPARC) Abb.: - Java ist stabil, d.h. zuverlässig. Die Stabilität einer Programmiersprache zeigt sich darin, daß während der Kompilierungsphase der gößte Teil der Datenüberprüfung ausgeführt werden kann. Java ist stark typisiert. Damit können Fehler früh gefunden werden. Ein weiteres Stabilitätskriterium von Java-Programmen ist die eingebaute Begrenzung der Zugriffsmöglichkeiten auf den Speicherbereich des Rechners20. Hinzu kommt auch noch die anschließende Sicherheitsüberprüfung durch den „Linker“. Der Linker ist ein Teil der Laufzeitumgebung, das die im System eingehenden Daten überprüft. - Java gilt als sicher. In dieser Hinsicht muß sich Java allerdings noch bewähren21. - 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 Java22) 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 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. 19 Normalerweise durch einen Java-Browser 20 Ungeprüfte Zugriffe auf Speicherbereiche des Rechners ermöglicht C/C++ (Zeigerarithmetik, implizite Deklaration) 21 Verschiedene Sicherheitslücken wurden aufgedeckt und wurden inzwischen beseitigt. 22 Die virtuelle Maschine wird auch als Java Interpreter oder Java Environment (Java-Laufzeitumgebung) bezeichnet 18 Programmieren in Java Programms können mehrere Dinge gleichzeitig geschehen, mehrere Faden / Threads eines Programms können gleichzeitig verfolgt und abgearbeitet werden. 1.3 Einstieg in die Java-Programmierung 1.3.1 Die Software für die Java-Programmierung Das JAVA Development Kit (JDK) enthält die Entwicklungsumgebung zum Schreiben von Java Programmen. Das JDK ist für Sun-SPARC-Systeme mit Solaris 2.2 (oder höher) sowie für Windows NT und Windows 95 über das Internet 23 erhältlich. Nach der Installation hat das Java-Verzeichnis24 folgenden Inhalt25: Abb.: Inhalt des Java-Verzeichnisses Innerhalb des Java-Verzeichnisses befinden sich Unterverzeichnisse26 mit folgendem Inhalt: Verzeichnis \bin \lib \include Inhalt In diesem Verzeichnis befinden sich die JDK-Programme In diesem Verzeichnis befinden sich defaultmäßig die Standard-Java-Klassen des JDK27 In diesem Verzeichnis befinden sich diverse Header-Dateien für die gemeinsame 23 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. 24 Erzeugt von der Entpackungsroutine des JDK 25 Das Verzeichnis „meinepr“ dient zur Aufnahme von Projekten und zählt nicht standardmäßig zum Inhalt eines Java-Verzeichnisses 26 unter Windows 27 Im wesentlichen ist das die Datei classes.zip 19 Programmieren in Java \demo \src Verwendung von Java und C/C++ Das Demo-Verzeichnis einthälz Beispielprogramme Falls „Java-Source“ im InstallShield ausgewählt wurde, wird dieses Verzeichnis mit angelegt. Darin befinden sich die entkomprimierten Java-Dateien, die sonst nur im gepackten Zustand (Datei SRC.ZIP) vorhanden sind. 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-Umgebungsvariable28 „autoexec.bat“ so vorliegen: sollte unter Windows in der Datei set classpath=c:\jdk1.1.6\lib\classes.zip; 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!"); } } 28 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. 20 Programmieren in Java 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 Verzeichnis29. 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 erzeugt30. 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!. 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 31 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 } 29 Vgl. PR13210 Mit der Option „-d“ im javac-Aufruf kann die .class-Dateii an einem anderen Ort gespeichert werden. 31 Vgl. PR13210 30 21 Programmieren in Java 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). 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. 22 Programmieren in Java args “Juergen“ “Hubert“ “Josef“ “Liesel“ “Christian“ [0] [1] [2] [3] [4] Nach 4 Vergleichen hat das Datenfeld args folgende Gestalt angenommen: args “Christian“ “Juergen“ “Josef“ “Liesel“ “Hubert“ [0] [1] [2] [3] [4] Die erste Position (args[0]) ist im Datenfeld damit schon richtig eingeordnet. Danach muß jetzt der Name an der 2. Position ([1]) mit den übrigen Namen des Datenfelds verglichen werden. Nach 3 Vergleichen zeigt sich folgendes Bild: args “Christian“ “Hubert“ “Juergen“ “Liesel“ “Josef“ [0] [1] [2] [3] [4] „Christian“ und „Hubert“ sind jetzt richtig eingeordnet. Num muß der Name an der dritten Position mit allen noch verbliebenen Namen auf Position 4 und 5 noch verglichen werden. Das Resultat der Vergleiche zeigt: args “Christian“ “Hubert“ “Josef“ “Liesel“ “Juergen“ [0] [1] [2] [3] [4] Der nächste Durchgang führt dann zum sortierten Datenfeld: args [0] [1] [2] [3] [4] “Christian“ “Hubert“ “Josef“ “Juergen“ “Liesel“ 2) Der soeben beschrieben Sortieralgorithmus muß in Java-Programmcode abgebildet werden. Es ist leicht erkennbar, daß hier zwei verschachtelte forSchleifen die Sortierung erreichen können. for (i = 0; i < args.length; i++) { for (j = i + 1; j < args.length; j++) { if (args[i].compareTo(args[j]) > 0) { // Tauschen String temp = args[i]; // lokale Variable 23 Programmieren in Java 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 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“. 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“ 24 Programmieren in Java 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. 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 + - * / 25 Programmieren in Java 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 = " + z = ++x; System.out.println("Nach z = ++x: z = " + z + ", 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 + " y = " + y); z = (float) (1.0f / 0.0f); System.out.println("z = (float) (1.0f / 0.0f) = " + z); x); x); = " x = " } } 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 = 24.0, x = 21.0 y = 2.0 x = 21.0 y = 2.0 Nach z = x + y * ++y: z = 27.0, x = 21.0 y = 3.0 z = (float) (1.0f / 0.0f) = Infinity Das zuletzt angegebenen Resultat zeigt die Zuordnung "Infinity" falls eine Zahl ungleich Null durch Null geteilt wird. 1.0f und 0.0f sind Gleitpunkt-Literale. Da "Infinity" als sehr groß interpretiert (Typ double) wird, muß hier in den Typ float konvertiert werden. 5. 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 26 Programmieren in Java „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 a b"); System.out.println(e.getMessage()); } } } 1.3.2.2 Entwicklung von Java-Applets Die Entwicklung eines Applets unterscheidet sich von einer Anwendung, weil JavaApplets in einer Web-Seite mit anderen Seitenelementen zusammen ausgeführt werden. Zur Ausführung eines Applets ist es demnach nötig, das Applet in einem HTML-Dokument32 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); } } 32 eine entsprechende Referenz innerhalb einer HTML-Seite mit einem speziellen Tag, dem <APPLET>-Tag erledigt die Einbettung in den Browser 27 Programmieren in Java 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 Modellierung33 des vorliegenden Quellcodes: WillkommenApplet paint() g.drawString (“Herzlich Willkommen in der Java-Welt!“,5,25) Abb.: Klassendiagramm zu WillkommenApplet Die Klasse WillkommenApplet läßt sich grafisch als rechteckiges Symbol darstellen. Die paint()-Methode wird ohne formale Parameter beschrieben, ihre Implementierung wird durch die beigefügte Notiz gezeigt. Die unmittelbare Superklasse wird im Quelltext direkt in der „extends“-Klausel angegeben. „extends java.applet.Applet“ bestimmt das die angegebene Applet-Klasse von der Applet-Klasse des Abstract Windowing Toolkit (AWT) abgeleitet ist. Der andere Teil der Klassendefinition enthält das Schlüsselwort „public“ mit der Bedeutung: Die Klasse ist nach dem Laden für das gesamte JavaSystem verfügbar. Applets müssen „public“ deklariert werden. Die folgende Abbildung zeigt die Beziehungen der Klasse WillkommenApplet zu ihren unmittelbaren Nachbarn: 33 Die Modellierung erfolgt nach den Regeln der Unified Modelling Language (UML). Die UML ist eine grafische, standardisierte Sprache zum Spezifizieren, Konstruieren, Visualisieren und Dokumentieren. 28 Programmieren in Java Applet WillkommenApplet paint() Graphics Abb.: 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 Linien34, von Ellipsen bzw. Kreisen35, von Rechtecken36 und anderen Formen zur Verfügung. In der paint()-Methode wurde hier der String “Herzlich Willkommen in der Java Welt!“ bei den (x,y)-Koordinaten (5,25) ausgegeben. Der Ursprung des Koordinatensystems liegt in der linken oberen Ecke des Darstellungsbereichs. Der String wird in einer defaultmäßig festgelegten Schrift und Farbe angezeigt. Die Untersuchung der Java-Bibliotheken zu Applet und Graphics zeigt: Die beiden Klassen sind Teil einer größeren Hierarchie. Verfolgt man die von Applet erweiterten und implementierten Klassen, dann kann man das folgende Klassendiagramm erhalten: 34 public void drawLine(int x1, int y1, int x2, int y2); (x1,y1) bestimmt den Anfangspunkt, (x2,y2) bestimmt den Endpunkt der Linie. 35 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 36 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. 29 Programmieren in Java Object Component ImageObserver Container Panel Applet WillkommenApplet Abb.: 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 Schittstelle ImageObserver existiert im Paket image, das liegt wiederum im Paket awt (qualifierter Name: java.awt.ImageObeserver). Die Paketstruktur kann über ein Klassendiagramm visualisiert werden. Die Paketstruktur37 kann in einem Klassendiagramm visualisiert werden: java WillkommenApplet applet awt lang Abb.: Paketstruktur im WillkommenApplet 37 Pakete werden in der UML als Akten mit Reitern dargestellt. Die gestrichelten Pfeile repräsentieren die Abhängigkeiten zwischen den Paketen. 30 Programmieren in Java 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. WillkommenApplet.java WillkommenApplet.class WillkommenApplet.html Abb. 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.: Darstellung des Fensters mit dem Appletviewer 31 Programmieren in Java 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("Times Roman",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 „Times Roman“, fett in „12-Punkt“ zugewiesen. Das neue Objekt wird anschließend der Instanzvariablen f zugewiesen und ist so der Methode paint() zugänglich: public void paint (Graphics g) { // Mitteilung: Die Schrift zur Anzeige von Text befindet sich in der // Instanzvariablen f g.setFont(f); // Mitteilung: Die Farbe fuer die Ausgabe ist gelb g.setColor(Color.yellow); // Die glebe Farbe dient zum Fuellen eines Rahmens fuer den Text, der // aus einem Rechteck mit abgerundeten Ecken besteht g.fillRoundRect(0,0,225,30,10,10); // Mitteilung: die Farbe fuer Textausgaben ist // eine Instanz der Klasse Color fuer die Farbe rot g.setColor(Color.red); // Mitteilung: Text wird in festgelegter Schrift und Farbe bei den // (x,y)-Koordinaten (5,25) ausgegeben. g.drawString("Herzlich Willkommen in der Java Welt!",5,25); } Die Klassen Font und Color werden über die folgenden import-Anweisungen bereitgestellt: import java.awt.Graphics; import java.awt.Font; import java.awt.Color; Dafür kann man auch „import java.awt.*;“38 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 38 Diese Anweisung stellt alle Klassen des Pakets java.awt zur Verfügung 32 Programmieren in Java Ereignisse vom System und liefert Ergebnisse zurück. Beim Start eines Applet ruft der Webbrowser die Methode init() des Appletobjekts auf. Beim Beenden eines Applets wird die Methode destroy() aufgerufen. Init() und destroy() werden im Lebenszyklus eines Applet genau einmal ausgeführt, nämlich beim Erzeugen bzw. beim Beenden eines Objekts. Zusätzlich sind zwei weitere Methoden start() und stop() vorgesehen, die an verschiedenen Stellen zur Aktivierung aufgerufen werden können. Wählt ein Anwender bei gestartetem Applet z.B. im Browser eine andere Internetseite an, so ruft der Browser die stop()-Routine auf und hält das Applet an, bis der Anwender wieder auf die Appletseite zurückkehrt. Dann ruft der Browser die start()-Routine zum ordnungsgemäßen Fortsetzen des Applet auf. import java.awt.*; import java.applet.*; 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); } } 33 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 (Instanziierung 39) 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 erfordert40, erfolgt die Rückgabe von nicht mehr benötigtem Speicher automatisch41. 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 39 Eine Instanz einer Klasse ist ein (tatsächliches) Objekt (konkrete Darstellung) Ausnahmen: String-, Array-Literale 41 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 40 34 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 Regeln42 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 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)43 Rückgabetypausdruck: Jede Methode ist typisiert. Der Typ einer Methode bestimt den Typ des Rückgabewerts. Dieser kann von einem beliebigen primitiven Typ44, 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“Anweisung45 einen Wert an den Aufrufer zurückgeben. Parameterliste enthält optional Argumente und hat folgende Struktur: Datentyp variablenname, ..... Die Anzahl der Parameter ist beliebig und kann Null sein. In Java wird jede selbstdefinierte Klasse mit Hilfe des Operators new instanziert. Mit Ausnahme von Zeichenketten (Strings) und Datenfeldern (Arrays), bei denen der Compiler auch Literale zur Objekterzeugung bereitstellt, gilt dies für alle vordefinierten Klassen der Java-Bibliothek. Der Aufruf einer Methode erfolgt ähnlich der Verwendung einer Instanzvariablen in „Punktnotation“. Zur Unterscheidung von einem Variablenzugriff müssen zusätzlich die Parameter in Klammern angegeben werden, selbst wenn die Parameter-Liste leer ist. Ein praktisches Beispiel 1. Erstellen der Klasse Rechentafel zum Rechnen mit ganzen Zahlen Die grundlegende Klassendefinition ist: class Rechentafel{ } 42 vgl. 2.1.2 Bezeichner und Namenskonventionen vgl. 2.6.2 44 vgl. 2.2 45 vgl. 2.6.4 43 35 Programmieren in Java 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 JavaAnwendung46: 2. Die Klasse Rechentafeltest zum Überprüfen der Klasse Rechentafel Die Klasse Rechentafeltest ist durch die Anwendung der Klasse Rechentafel bestimmt. Sie enthält die main()-Methode. Im Mittelpunkt der Anwendung steht die Anweisung: Rechentafel einRechenobjekt = new Rechentafel(); Sie erzeugt eine Instanz der Klasse Rechentafel und speichert eine Referenz darauf in der Variablen „einRechenobjekt“. In jeder objektorientierten Programmiersprache lassen sich spezielle Methoden definieren, die bei der Initialisierung eines Objekts aufgerufen werden. In Java werden Konstruktoren als Methoden ohne Rückgabewert definiert, die den Namen der Klasse erhalten, zu der 46 vgl. PR14101 36 Programmieren in Java 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 Typen48 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 Typen49 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 Datentypen50: - vier Ganzzahltypen mit unterschiedlichen Wertebereichen 48 vgl. 2.2.1 vgl. 2.2.1 50 vgl. 2.2.1 49 37 Programmieren in Java - zwei Gleitpunktzahlentypen mit unterschiedlichen Wertebereichen nach IEEE Standard of Binary Floating Point Arithmetic. - einen Zeichentyp Primitiver Typ boolean char byte short int long float double void Größe 1-Bit 16-Bit 8-Bit 16-Bit 32-Bit 64-Bit 32-Bit 64-Bit - Minimum Unicode 0 -128 -215 -231 -263 IEEE 754 IEEE 754 - Maximum Unicode 216-1 +128 +215-1 +231-1 +263-1 IEEE 754 IEEE 754 - Wrapper-Klasse Boolean Character Byte Short Integer Long Float Double Void Den primitiven Typen sind „Wrapper“-Klassen zugeordnet, die ein nicht primitives Objekt auf dem „Heap“ zur Darstellung des primitiven Typs erzeugen, z.B.: char zeichen = ‘x‘; Character zeichenDarstellung = new Character(zeichen) bzw. Character zeichenDarstellung = new Character(‘x‘); Referenztypen Neben den primitiven Typen gibt es noch 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 Gewöhnlich stellt man „explizit“ einen „default“-Konstruktor zur Verfügung. Dieser parameterlose Konstruktor überlagert den implizit bereitgestellten „default“Konstruktor. Er wird dann bei allen parameterlosen Instanzierungen verwendet. Konstruktoren können aber auch – wie normale Dateien – Parameter übergeben bekommen, z.B.: 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 38 Programmieren in Java 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 den Parameter this aufgerufen werden 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). 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 Zugriff nur innerhalb einer Klasse Zugriff innerhalb eines Pakets Zugriff innerhalb eines Pakets oder von Subklassen in einem anderen 39 Programmieren in Java public 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. Abhlife verspricht hier das Prinzip der Datenkapselung (data hiding, data encapsulation). Instanzvariable werden als „private“ erklärt, d.h.: Zugriff auf diese Instanzvariable nur innerhalb der Klasse. Von außerhalb kann nur indirekt über das Aufrufen von Methoden, die als „public“ erklärt sind, auf die Instanzvariablen zugegriffen werden. Deshalb sollte auch prinzipiell für jede Instanzvariable eine entsprechende „get“- und eine „set“-Methode („hole“ bzw. „setze“) zur Verfügung gestellt werden, die jeweils „public“ erklärt werden. Bsp.: Die Klasse „Rechentafel“ nimmt dann folgenden Gestalt an: class Rechentafel extends Object { // Instanzvariable private int ersterOperand = 0; private int zweiterOperand = 0; // Konstruktoren public Rechentafel() { this(0,1); } public Rechentafel(int ersterOperand, int zweiterOperand) { 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; } 40 Programmieren in Java public int subtrahiere() { return ersterOperand - zweiterOperand; } public int multipliziere() { return ersterOperand * zweiterOperand; } public int dividiere() { // ganzzahlige Division if (zweiterOperand == 0) { System.out.println("Fehler: Division durch Null"); System.out.println("Divisor wird auf 1 gesetzt!"); zweiterOperand = 1; } return ersterOperand / zweiterOperand; } } Das Beispiel zeigt folgende Empfehlungen für die Vergabemöglichkeit von Zugriffsrechten: Klassen Instanzvariable Instanzkonstanten Instanzmethoden public private public public, falls ein Zugriff von außen erforderlich und sinnvoll ist. private, falls es sich um klaseninterne Hilfsmethoden handelt. Analoge Überlegungen gelten auch für Klassenvariable und -methoden. Klassenvariable und Klassenmethoden Die Klasse „Rechentafel" 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. 41 Programmieren in Java 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.: // 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 Konstanten51 gesetzt werden. 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. 51 Das bedeutet: das Schlüsselwort final ist bei lokalen Variablen nicht erlaubt 42 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. Überladen kann. 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 Inforamtion ü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. Überschreiben einer Oberklassen-Methode: 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. public String toString() { return " Rechenobjekt# " + nummer + " Erster Operand: " + ersterOperand + " Zweiter Operand: " + zweiterOperand; } } 1.4.1.2 Superklassen und Subklassen, Vererbung und Klassenhierarchie Definitionen Klassen können sich auf andere Klassen beziehen. Ausgangspunkt ist eine Superklasse, von der Subklassen (Unter-) abgeleitet sein können. Jede Subklasse erbt den Zustand (über die Variablen-Deklarationen) und das Verhalten der Superklasse. Darüber hinaus können Subklassen eigene Variable und Methoden hinzufügen. Superklassen, Subklassen bilden eine mehrstufige Hierarchie. In Java sind alle Klassen Ableitungen einer einzigen obersten Klasse – der Klasse Object. Sie stellt die allgemeinste Klasse in der Hierarchie dar und legt die Verhaltensweisen und Attribute, die an alle Klassen in der Java-Klassenbibliothek vererbt werden, fest. Vererbung bedeutet, daß alle Klassen in eine strikte Hierarchie eingeordnet sind und etwas von übergeordneten Klassen erben. Was kann von übergeordneten Klassen vererbt werden? Beim Erstellen einer neuen Instanz erhält man ein Exemplar jeder Variablen, die in der aktuellen Klasse definiert ist. Zusätzlich wird ein Exemplar für jede Variable bereitgestellt, die sich in den Superklassen der aktuellen Klasse befindet. Bei der Methodenauswahl haben neue Objekte Zugang zu allen 43 Programmieren in Java 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. Auch eine abstrakte Methode ist zunächst einmal durch das reservierte Wort "abstract" erklärt. Eine abstrakte Methode besteht nur aus dem Methodenkopf, anstelle des Methodenrumpfs (der Methodendefinition) steht nur das "Semikolon", z.B. public abstract String toString();. Enthält eine Klasse mindestens eine abstrakte Methode, so wird automatisch die gesamte Klasse zu einer abstrakten Klasse. Abstrakte Klassen enthalten keine Konstruktoren. Sie können zwar Konstruktoren aufnehmen, allerdings führt jeder explizite Versuch zur Erzeugung eines Objekts einer abstrakten Klasse zu einer Fehlermeldung. Abstrakte Methoden stellen eine Schnittstellenbeschreibung dar, die der Programmierer einer Subklasse zu definieren hat. Ein konkrete Subklasse einer abstrakten Klasse muß alle abstrakten Methoden der Superklasse(n) implementieren. Beispiel: Eine Klassenhierarchie für "geometrische" Objekte. Das folgende Klassendiagramm zeigt die hierarchische Struktur von Klassen, die "geometrische Objekte" beschreiben. All diesen geometrischen Objekten ist gemeinsam: Sie werden surch einen Bezugspunkt (x,y) fixiert. DieGemeinsamkeiten sind in der abstrakten Klasse "Geomobjekt" zusammengefaßt. 44 Programmieren in Java GeomObjekt private double x; private double y; public double holeX(); public void setzeX(double x); public double holeY(); public void setzeY(double y); Kreis Rechteck private double r; private double breite; private double hoehe; public double holeR(); public void setzeR(double r); public double umfang(); public double flaeche(); publicc String toString(); public double holeBreite(); public void setzeBreite(double breite) public double holeHoehe(double hoehe); public void setzeHoehe(double hoehe); public double umfang(); public double flaeche(); public String toString(); Ausgangspunkt ist die abstrakte Klasse "GeomObjekt". Dort ist der allen Objekten zugeordnete Bezugspunkt fixiert. Subklassen (z.B. Kreis und Rechteck) erben diesen Bezugspunkt und die zum Zugriff auf ihn bereitgestellte Methoden. Außerdem führen die Subklassen die nötigen Spezialisierungen auf das jeweilige geometrische Objekt durch. import java.lang.*; public abstract class GeomObjekt extends Object { // Instanzvariable private double x, y; // Bezugspunkt geom. Objekte // Instanzmethoden public double holeX() { return x; } public void setzeX(double x) { this.x = x; } public double holeY() { return y; } public void setzeY(double y) { this.y = y; } // Abstrakte Methoden public abstract String toString(); public abstract double umfang(); 45 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) { 46 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); 47 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 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 „==“ bestätigt: Es handelt sich jetzt (nach der Zuweisung) um das gleiche Objekt. 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. 48 Programmieren in Java 1.4.1.4 Konvertieren von Objekten und Primitivtypen „Casting“ ist ein Mechanismus zum Konvertieren des Werts eines Objekts oder eines primitiven Typs in einen anderen Typ. „Casting“ ergibt ein neues Objekt oder einen neuen Wert und wirkt sich nicht auf das ursprüngliche Objekt bzw. den ursprünglichen Wert aus. Konvertieren von Primitivtypen Ist der angestrebte Typ „größer“ als der zu konvertierende Typ, ist häufig kein explizites „Casting“ nötig. Ein „Byte“ oder ein Zeichen kann automatisch z.B. als „int“, ein „int“ als „long“, ein „int“ als „float“ behandelt werden. Es gehen beim Konvertieren des Werts keine Informationen verloren, weil der größere Typ mehr Genauigkeit bietet als der kleinere. Ist der angestrebte Typ „kleiner“ als der zu konvertierende Typ, ist „Casting“ nötig. Explizites Casting sieht so aus: (typname) wert „typname“ ist der Name des Typs, auf den konvertiert wird, „wert“ ist ein Ausdruck, der den zu konvertierenden Wert ergibt, z.B.: „(int) x/y;“. Dieser Ausdruck teilt x durch y und wandelt dann das Ergebnis in „int“ um. Da Casting eine höhere Priorität als Arithmetik hat, müssen die Klammern angegeben werden. Konvertieren von Objekten Mit einer Einschränkung können auch Klasseninstanzen in Instanzen anderer Klassen konvertiert werden. Die betroffenen Klassen müssen durch Vererbung miteinander verbunden sein, d.h.: Ein Objekt kann nur in eine Instanz der Sub- oder Superklassen konvertiert werden, nicht aber in eine beliebige Klasse. Durch Konvertieren eines Objekts in eine Instanz der Superklasse gehen Informationen, die die Subklasse bereitgestellt hat, verloren. Spezifisches Casting ist erforderlich: (klassename) objekt. „klassename“ ist der Name der Klasse, in die das Objekt konvertiert werden soll. „objekt“ ist eine Referenz auf das zu konvertierende Objekt. Konvertieren von Primitivtypen in Objekte und umgekehrt Primitive Typen und Objekte sind in Java zwei völlig verschiedene Dinge. Konvertierung und automatisches Casting sind nicht möglich. Allerdings enthält das Paket „java.lang“ mehrere Subklassen, die je einem primitiven Datentyp entsprechen: Integer, Float, Boolean usw. Mit den in diesen Klassen definierten Klassenmethoden kann mit Hilfe von new für alle Primitivtypen eine „Art Objekt“ erstellt werden, z.B.: „Integer intObjekt = new Integer(13);“. Mit intValue() wird ein primitiver „int“-Wert aus einem Integer-Objekt extrahiert, z.B.: int ganzeZahl = intObjekt.intValue(); 49 Programmieren in Java 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“-Klasse. Die String-Klasse definiert die Methode equals(), die jeden Zeichen der Zeichenkette prüft und „true“ ausgibt, falls die beiden Zeichenketten den gleichen Wert haben. Nach dem Operator „==“ sind die beiden „String“-Objekte nicht gleich, weil sie zwar den gleichen Inhalt haben, aber nicht die gleichen Objekte sind. // Besonderheiten im Zusammenhang mit String-Objekten final class StringManipulationen { public static void main(String args[]) { String a = "test"; String b = "test"; String c = new String("test"); String d = new String("test"); // Díe folgenden Anweisungen geben wahr oder falsch // an die Konsole aus System.out.println("a == b " + (a==b)); System.out.println("c == d " + (c==d)); System.out.println("a == c " + (a==c)); System.out.println("a == \"test\" " + (a=="test")); System.out.println("c == \"test\" " + (c=="test")); System.out.println(); System.out.println("a.equals(b) " + a.equals(b)); System.out.println("c.equals(d) " + c.equals(d)); System.out.println("a.equals(c) " + a.equals(c)); System.out.println("a.equals(\"test\") " + a.equals("test")); System.out.println("c.equals(\"test\") " + c.equals("test")); } } // // // // // true false false true false // // // // // true true true true true Kopieren von Objekten Die clone()-Methode erzeugt ein Duplikat als Kopie eines Objekts. Damit ein Objekt geklont werden kann, muß es die „Cloneable“-Schnittstelle unterstützen. Die Cloneable-Schnittstelle im Paket java.lang hat selbst keine Methoden, sie dient lediglich als Indikator dafür, daß ein Objekt geklont werden kann. Das Format für die clone()-Methode ist: protected Object clone() throws CloneNotSupportedException Bsp.: Die Klasse Rechentafel erhält eine clone()-Methode zum Klonen von Rechentafel-Objekten public class Rechentafel implements Cloneable { 50 Programmieren in Java // 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() { 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; 51 Programmieren in Java 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 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); 52 Programmieren in Java 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; 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 definieren52. 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. 52 Im JDK wird das Konzept als Inner Classes bezeichnet 53 Programmieren in Java 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 implemntieren 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. 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) definieren53. 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 { 53 Angaben zur Implementierung sind nicht möglich 54 Programmieren in Java // Abstrakte Methoden } bzw. public interface meineSpezialschnittstelle extend meineSchnittstelle { // Abstarkte 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. Bsp.: interface Kaempfer { void kaempfen(); } interface Schwimmer { void schwimmen(); } interface Flieger { void fliegen(); } class Kampfhandlung { public void kaempfen() {} } class Held extends Kampfhandlung implements Kaempfer, Schwimmer, Flieger { public void schwimmen() {} public void fliegen() {} } public class Abenteuer { static void t(Kaempfer x) { x.kaempfen(); } static void u(Schwimmer x) { x.schwimmen(); } static void v(Flieger x) { x.fliegen(); } 55 Programmieren in Java static void w(Kampfhandlung x) { x.kaempfen(); } public static void main(String args[]) { Held i = new Held(); t(i); u(i); v(i); w(i); } } Konstanten Neben abstrakten Methoden können Interfaces auch Konstanten (Variablen mit dem Attributen static und final) enthalten. Wenn eine Klasse ein solches Interface implementiert, erbt es gleichzeitig auch alle seine Konstanten. Ein Interface kann ausschließlich Konstanten enthalten. Pakete Definition Pakete sind in Java Zusammenfassungen von Klassen und Schnittstellen. Sie entsprechen in Java den Bibliotheken anderer Programmiersprachen. Pakete ermöglichen, daß modulare Klassengruppen nur verfügbar sind, wenn sie gebraucht werden. Potentielle Konflikte zwischen Klassenamen in unterschiedlichen Klassengruppen können dadurch vermieden werden. Für Methoden und Variablen innerhalb von Paketen besteht eine Zugriffsschutzschablone. Jedes Paket ist gegenüber anderen, nicht zum Paket zählenden Klassen abgeschirmt. Klassen, Methoden oder Variablen sind nur sichtbar für Klassen im gleichen Paket. Klassen, die ohne „package“ Anweisung definiert sind, werden vom Compiler in ein „Standardpaket“ gestellt. Voraussetzung dafür ist: Die „.java“- und „.class“-Dateien dieses Pakets befinden sich im aktuellen Verzeichnis (oder in einen darunter liegenden Klassenverzeichnis). Die Java-Klassenbibliothek54 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 54 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. Die Klassenbibliothek des JDK befindet sich in einem Paket mit dem namen „java“. 56 Programmieren in Java Throwable System Runtime Process Math Number Character Boolean ClassLoader SecurityManager Compiler 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 Einthät 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 neie 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. Eröglicht, falls vorhanden, den Zugriff auf den "just-in-time" Compiler Abb.: Klassen des java.lang-Pakets Zusätzlich enthält das java.lang-Paket noch zwei Schnittstellen: Cloneable Runnable Muß von einem anderen Objekt implementiert werden, das dann geklont oder kopiert werden kann Wird zusammen mit der Thread-Klasse benutzt, um die aufgerufene Methode zu definieren, wenn ein Thread gestartet wird. Abb.: Schnittstellen im java.lang-Paket - java.util - java.io: Klassen zum Lesen und Schreiben von Datenströmen und zum Handhaben von Dateien. - java.net: Klassen zur Netzunterstützung, z.B. socket und URL (eine Klasse zum Darstellen von Referenzen auf Dokumente im World Wide Web). - java.awt (Abstract Windowing Toolkit): Klassen zum Implementieren einer grafischen Benutzeroberfläche. Das Paket enthält auch eine Klasse für Grafik (java.awt.Graphics) und Bildverarbeitung (java.awt.Image). - java.applet: Klassen zum Implementieren von Applets, z.B. die Klasse Applet. Die Java Version 1.1 hat die Klassenbibliothek umfassend erweitert55: Paket java.applet java.awt java.awt.datatranfer java.awt.event java.awt.image java.beans java.io java.lang java.lang.reflect 55 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 Zusätzlich 15 weitere Packages, etwa 500 Klassen und Schnittstellen 57 Programmieren in Java Debugger und Inspektoren java.math java.net java.rmi java.rmi.dgc java.rmi.registry java.rmi.server java.security java.security.aci java.security.intefaces java.sql java.util java.util.zip 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: -- Mit "import paket.Klasse" wird genau eine Klasse importiert, alle anderen Klassen des Pakets bleiben verborgen --Mit "import paket.*"56 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 Polymorphismaus Definition 56 type import on demand, d.h.: Die Klasse wird erst dann in dem angegebenen Paket gesucht, wenn das Programm sie wirklich benötigt. 58 Programmieren in Java 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. 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 Veerbung 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“. 59 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. 60 Programmieren in Java 1.4.2.3 Die Klasse System 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. 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 61 Programmieren in Java 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 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 62 Programmieren in Java 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 String valueOf(boolean b) static String valueOf(char z) Die „valueOf“-Methoden sind als Klassenmethoden implementiert und können ohen String-Objekt aufgerufen werden. Da sie in der Regel zur Erzeugung von Strings verwendet werden, ist das sinnvoll. Ein Aufruf von „valueOf“ wandelt ein primitives Objekt mit Hilfe der Methode „toString()“, die von der zugehörigen WrapperKlassen bereitgestellt wird, in eine Zeichenkette um. 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 Carbage Collector freigibt. 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. 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. 63 Programmieren in Java 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; 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 Beschreibung Eine numerische Zahl zwischen 0 und 9 Ein Buchstabe des Alphabets Ein alphabetischer Buchstabe oder eine numerische Zahl 64 Programmieren in Java isLowerCase isUpperCase isJavaLetter isJavaLetterOrDigit isSpace 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 isTitleCase 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“)57 wieder. Falls ein Zeichen keinem Wert in der angegebenen Zahlendarstellung entspricht, wird „-1“ zurückgegeben. public static char forDigit(int digit, int radix) Konvertiert einen numerischen Wert in ein Zeichen, das diesen Wert in einer bestimmten Zahlenbasis darstellt, z.B. Character.forDigit(6,8) ergibt 6 und Character.forDigit(12,16) ergibt "c“. Die Klasse Boolean Diese Klasse ist der Objekt-Wrapper für den primitiven Typ boolean. Konstruktoren: public Boolean(boolean wert) public Boolean(String str) Beschaffung eines booleschen Werts, der in einem Objekt abgelegt ist: public boolean booleanValue() Objekt-Wrapper-Varianten von true und false public final static Boolean FALSE; public final static Boolean TRUE; Die Klasse Number 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 57 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. 65 Programmieren in Java 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. 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. 66 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.58: 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 58 PR14103 67 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()59. 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 { ......... } 59 Sie ist in der Klasse Throwable definiert und daher allen Exception-Objekten zugänglich. 68 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 69 Programmieren in Java den Zugriff auf Datenfelder. Teilweise sind sie der Klasse „Error“ (oder ihrer Subklasse RuntimeError) zugeordnet und müssen nicht extra abgefangen und dokumentiert werden. Sind Ausnahmen der Klasse Exception zugeordnet, dann müssen sie behandelt werden (z.B. alle Ausnahmen von java.io). Die Klasse RuntimeException ist die Superklasse für die Behandlung aller Laufzeitfehler, die behandelt werden können aber nicht müssen (die Entscheidung liegt beim Programmierer). UnknownError VirtualMachineError OutOfMemoryError InternalError Error AWTError LinkageError Object Throwable ClassNotFoundException Exception RuntimeException EOFException IOException FileNotFoundException InterruptedException UTFDataFormatException Abb.: Fehlerklassenhierarchie Auslösen von Ausnahmen Java verfügt in seinen Standardbibliotheken über zahlreiche vorgefertigte Ausnahmen für fast alle Standardsitiuationen (z.B. Dateioperationen auf der Basis der IO-Exception). Selbstdefinierte Ausnahmen benötigen eine Subklasse von Exception. Das Auslösen dieser selbstdefinierten Ausnahmen erfolgt über die throwAnweisung: throw AusnahmeObjekt. Die Behandlung selbstausgelöseter Ausnahmen erfolgt nach den üblichen Regeln: - Suche nach einem Ausnahmen-Handler in den umgebenden Blöcken - Bei erfolgloser Suche Weitergabe des Fehlers an den Aufrufer. Wird die Ausnahme nicht innerhalb derselben Methode behandelt, dann ist sie mit Hilfe der "throws"-Klausel zu deklarieren und „weiter oben“ in der Aufrufkette zu behandeln. Bsp.: public class AusnahmeMethoden { public static void main(String aurgs[]) { try { 70 Programmieren in Java 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.60: class MeineAusnahme extends Exception { private int i; public MeineAusnahme() {} public MeineAusnahme(String nachricht) { super(nachricht); } public MeineAusnahme(String nachricht, int x) { super(nachricht); i = x; } public int wert() { return i; } } public class AuswurfMeineAusnahme { public static void f() throws MeineAusnahme { System.out.println("Auswurf von MeineAusnahme aus f()"); throw new MeineAusnahme(); } 60 Vgl. PR14140 71 Programmieren in Java 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 */ 72 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 InputStream61 in public static PrintStream62 out public static PrintStream err // Standardeingabe // Standardausgabe // Standardfehlerausgabe Diese Datenströme dienen zum Lesen bzw. Schreiben in das Fenster von dem die Anwendung gestartet wurde. Die Verwendung des Stroms System.in innerhalb von Applets ist aber nicht sinnvoll, weil die verschieden Browser diesen Datenstrom unterschiedlich behandeln. Die Ströme System.out und System.err sendet Netscape an das Fenster der Java-Konsole, der Appletviewer gibt sie in das Fenster weiter, aus dem der Appletviewer gestartet wurde, Einfache Ausgaben auf das Standard-Ausgabegerät Mit Hilfe des Kommandos „System.out.println“, das als einziges Argument eine Zeichenkette erwartet, können einfache Ausgaben auf den Bildschirm geschrieben werden. Mit Hilfe des Plus-Operators „+“ können Zeichenketten und numerische Argumente verknüpft werden, so daß man neben Text auch Zahlen ausgeben kann. Einfache Eingaben vom Standard-Eingabegerät Leider ist es etwas komplizierter Daten zeichenweise von der Tastatur zu lesen. Es steht zwar ein vordefinierter Eingabestrom System.in zur Verfügung, der aber nicht die eingelesenen Zeichen primitiver Typen konvertieren kann. Statt dessen muß eine Instanz der Klasse „InputStreamReader“63 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. 64: "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("Bitta a eingeben: "); a = Integer.parseInt(ein.readLine()); System.out.print("Bitte b eingeben: "); 61 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 63 Das grundlegende Konzept für Ein- und Ausgabe sind Datenströme, die im Paket „java.io“ versammelt sind. 64 Vgl. PR14150 62 73 Programmieren in Java 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äts65 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. Writer66 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 Ausgabestrom 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); 65 66 Hauptspeicher, Festplatte, Drucker, Netzwerk Vgl. 7.6 74 Programmieren in Java } } } 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(...) 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) 75 Programmieren in Java { 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"); } } } 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.1 import java.io.*; class Umlenken { public static void main(String args[]) { try { BufferedInputStream ein = 76 Programmieren in Java 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(); } } } 77 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 Leeraum67 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 Unicode68. Bei der Kompilierung wird der Quellcode automatisch in eine Folge von Unicode67 Technisch gesehen ist ein Leeraum kein Token Alle Literale (d.h. alle vorkommenden Zeichen (Zahlen, Buchstaben, Sonderzeichen, etc.) bestehen aus Unicode. 68 78 Programmieren in Java Zeichen transformiert. Eine ASCII-Kodierung wird einfach mit Voranstellen der Zeichenfolge „\u00“ und folgender Hexadezimalzahl in das passende UnicodeZeichen übersetzt. Dies definiert in Java eine Escape-Sequenz, mit der alle UnicodeZeichen verschlüsselt werden können. Durch die „Escape-Sequenz-Darstellung“ des Quelltextes in Java ist die Verwendung von Umlauten und anderen Sonderzeichen in Bezeichnern möglich. Zwei Bezeichner gelten in Java als identisch, wenn ihre Unicode-Darstellung übereinstimmend ist. Deshalb muß auch in Java unbedingt zwischen Groß- und Kleinschreibung unterschieden werden. 2.1.1 Schlüsselworte Es gibt bestimmte Buchstabenkombinationen, die in Java eine besondere Bedeutung haben. Diese Buchstabenfolgen werden in Java Schlüsselworte genannt abstract catch do finally if interface outer return this var boolean char double float implements long package short throw void break class else for import native private static throws volatile byte const extends future inner new protected super transient while case continue false generic instanceof null public switch true cast default final goto int operator rest synchronized try Abb.: Reservierte Java Schlüsselworte 2.1.2 Bezeichner und Namenskonventionen Bezeichner bzw. Identifizierer (identifier) sind Worte, die vom Programmierer gewählt, zu Token werden und die Variablen, Konstanten, Klassen, Objekte, Beschriftungen und Methoden darstellen. Bezeichner dürfen nicht mit JavaSchlüsselworten identisch sein. Es gibt in Java einige Regeln zur Namensvergabe, die unbedingt eingehalten werden müssen: - Bezeichner bestehen aus Unicode-Buchstaben und Zahlen in unbeschränkter Länge. - Das erste Zeichen eines Bezeichners muß ein Buchstabe, der Unterstrich „_“ oder das Zeichen „$“ sein. Alle folgenden Zeichen sind entweder Buchstaben oder Zahlen. - Zwei Token gelten nur dann als derselbe Bezeichner, wenn sie dieselbe Länge haben, und jedes Zeichen im ersten Token genau mit dem korrespondierenden Zeichen des zweiten Token übereinstimmt, d.h. den vollkommen identischen Unicode-Wert hat. Java unterscheidet Groß- und Kleinschreibung. 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. 79 Programmieren in Java - Identifizierer von Klassen sollten mit einem Großbuchstaben beginnen und anschließend klein geschrieben werden. Wenn sich ein Bezeichner aus mehreren Wörtern zusammensetzt, dürfen diese nicht getrennt werden. Die jeweiligen Anfangsbuchstaben innerhalb des gesamten Bezeichners werden jedoch groß geschrieben. - Die Identifizierer von Variablen, Methoden und Elementen beginnen mit Kleinbuchstaben und werden auch anschließend klein geschrieben. Wenn ein Bezeichner sich aus mehreren Wörtern zusammensetzt, dürfen diese nicht getrennt werden. Die jeweiligen Anfangsbuchstaben der folgenden Wörter können jedoch innerhalb des Gesamtbezeichners groß geschrieben werden. 2.1.3 Literale Literale sind Token, die für zu speichernde Werte der Datentypen byte, short, int, long, float, double, boolean und char stehen. Literale werden auch zur Darstellung von Werten benutzt, die in Zeichenketten gespeichert werden. Es gibt: Boolesche Literale, Zeichenliterale, Zeichenkettenliterale, Gleitpunktliterale, ganzzahlige Literale. 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 0X69. Oktale Darstellungen beginnen mit einer führenden Null. Negativen Ganzzahlen wird ein Minuszeichen vorangestellt. Ganzzahlige Literale haben gemäß Voreinstellung den Typ „int“. Durch Anhängen vom „l“ bzw. „L“ kann man jedoch explizit den Typ „long“ wählen. 2. 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 Die Zahlen 0 bis 15 werden durch die Buchstaben „A“ bis „F“ bzw. „a“ bis „f“ dargestellt (Groß- oder Kleinschreibung ist hier nicht relevant. 69 80 Programmieren in Java 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. 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)70 - 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 70 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. 81 Programmieren in Java 5. Zeichenkettenliterale Zeichenkettenliterale sind aus mehreren Zeichenliteralen zusammengesetzte Ketten (Strings). Bei Zeichenkettenliteralen werden null oder mehr Zeichen in Anführungszeichen (“)dargestellt. Java erzeugt Zeichenketten als Instanz der Klasse String. Damit stehen alle Methoden der Klasse String zur Manipulation einer Zeichenkette zur Verfügung. Zeichenkettenliterale stehen zwischen zwei Anführungszeichen (“) und können Steuerzeichen wie Tabulatoren, Zeilenvorschübe, nichtdruckbare Unicode-Zeichen und druckbare Unicode-Spezialzeichen enthalten. Es kann sich auch um EscapeSequenzen handeln. Beide Anführungszeichen müssen in derselben Zeile des Quellcodes stehen. Jedes String-Literal ist eine Referenz auf ein Objekt der Klasse String. Falls der Compiler beim Übergeben des Quelltextes ein string-Literal findet, erzeut er ein neues String-Objekt und verwendet es anstelle des Literals. In Java ist der Operator + auch auf Strings definiert. Auf zwei String-Objekte angewendet, liefert er die Verkettung beider Objekte. 2.1.4 Trennzeichen Trennnzeichen sind Token, die aus einem einzigen Zeichen bestehen und andere Token trennen. Java kennt 9 Trennzeichen: ( ) { } [ ] ; , . Wird sowohl zum Öffnen einer Parameterliste für eine Methode als auch zur Festlegung eines Vorrangs für Operationen in einem Ausdruck benutzt. Wird sowohl zum Schließen einer Parameterliste für eine Methode als auch zur Festlegung eines Vorrangs für Operationen in einem Ausdruck benutzt Wird zu Beginn eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt Wird an das Ende eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt Steht vor einem Ausdruck, der als Index für ein Datenfeld (Array) steht Folgt einem Ausdruck, der als Index für ein Datenfeld dient Dient sowohl zum Beenden einer Ausdrucksanweisung als auch zum Trennen der Teile einer forAnweisung. wird in vielen Zusammenhängen als Begrenzer verwendet Wird sowohl als Dezimalpunkt als auch zum Trennen solcher Dinge wie Paketnamen von Klassennamen oder Variablennamen benutzt. Abb.: Die Java-Trennzeichen 82 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 83 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 Manipulieren71 import java.util.*; public class BitManipulation { public static void main(String[] args) { 71 PR21502 84 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(); } } 85 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 86 Programmieren in Java Vergleichsoperatoren Vergleichsoperatoren haben zwei Operanden und vergleichen diese (zwei Ganzzahlen oder zwei Gleitpunktzahlen)72. 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. 73: 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()74 bereit, z.B.: public class EqualsMethode { public static void main(String[] args) { Integer i1 = new Integer(13); Integer i2 = new Integer(13); System.out.println(i1.equals(i2)); } } // true Logische Vergleichsoperatoren Die logischen Vergleichsoperatoren werden nur auf boolesche Operatoren angewandt und erzeugen nur boolesche Ergebnisse. Operator && || Beschreibung Logischer AND-Operator Logischer OR-Operator 72 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. 73 Vgl. PR21500 74 Viele in der Java-Bibliorhek bereitgestellte Klassen implementieren die Methode equals() 87 Programmieren in Java ! Logischer NOT-Operator Abb.: Logische Vergleichsoperatoren 2.1.6 Kommentare, eingebettete Dokumentation Es gibt drei Arten von Kommentaren - Einzeilige Kommentare beginnen mit // und enden mit dem Ende der Zeile - Mehrzeilige Kommentare 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. Dokumentationskommentare Sie dienen dazu, Programme im Quelltext zu kommentieren. 88 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 89 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 90 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 Überlauf75 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 Unterlauf 76 gibt einen speziellen 75 d.h.: Größeres Ergebnis von einer Operation als durch den Wertebereich des jeweiligen Typs ausgedrückt werden kann. 76 d.h.: kleineres Ergebnis (- außer Null -) von einer Operation als durch den Wertebereich des jeweiligen Typs ausgedrückt werden kann. 91 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. 92 Programmieren in Java 2.2.3 Datenfelder (Arrays) Ein Datenfeld (Array) ist eine Ansammlung von Objekten eines bestimmten Typs 77, die über einen laufenden Index adressierbar sind. Gegenüber „normalen Objekten“ haben Arrays zwei wesentliche Einschränkungen: 1. Arrays haben keine Konstruktoren. Statt dessen wird der Operator new mit spezieller Syntax aufgerufen. 2. Es können keine Subklassen eines Datenfelds definiert werden. Datenfelder gehören zu den Referenzvariablen 78. Arrays können jeden Wertetyp (primitiver Typ oder Objekt) enthalten, jedoch können in einem Array unterschiedliche 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) 77 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. 78 Es gibt drei Arten von Referenzvariablen: Klassen, Schnittstellen und Datenfelder 93 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) { 94 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 Fehler79. Die Länge eines Datenfelds kann mit der Instanzvariablen length getestet werden, z.B.: int laenge = ort.length;. Zum Zuweisen eines Werts wird hinter dem Zugriffsausdruck die Zuweisungsanweisung gestellt. Falls einem Array-Element ein Wert zugewiesen wird, dann wird auf das betreffende Objekt eine Referenz erzeugt. Datenfelder mit primitiven Typen (z.B. int, float) kopieren die Werte von einem Element in ein anderes. 2.2.3.3 Anwendungen mit eindimensionaler Datenfeldern 79 Er weist auf eine „Ausnahme“ hin. 95 Programmieren in Java 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. Sortieren80 80 PR22301 96 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; } } } } } 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++) 97 Programmieren in Java { for (int j = 0; j < a[i].length; j++) { System.out.print(a[i][j] + " "); } System.out.println(); } } } 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)81. Bsp.: Mehrdimensionale Felder import java.util.*; public class MehrdimFeld { static Random rand = new Random(); static int zRand(int mod) { return Math.abs(rand.nextInt()) % mod; } public static void main(String args[]) { // 1. Bsp: Erzeugen eines mehrdimensionalen Felds // mit Werten primitiver Typen int[][] a1 = { { 1, 2, 3, }, { 4, 5, 6, }, }; for (int i = 0; i < a1.length; i++) for (int j = 0; j < a1[i].length; j++) ausgabe("a1[" + i + "][" + j + "] = " + a1[i][j]); // 2. Bsp.: Dreidimensionales Feld mit fester Laenge int[][][] a2 = new int[2][2][4]; for (int i = 0; i < a2.length; i++) for (int j = 0; j < a2[i].length; j++) for (int k = 0; k < a2[i][j].length;k++) ausgabe("a2[" + i + "][" + j + "][" + k + "] = " + a2[i][j][k]); // 3. Bsp.: Dreidimensionales Feld mit variablen Laengen int[][][] a3 = new int[zRand(7)][][]; for (int i = 0; i < a3.length; i++) { a3[i] = new int[zRand(5)][]; for (int j = 0; j < a3[i].length; j++) { 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) }, 81 Im Java werden multidimensionale Arrays streng genommen nicht unterstützt 98 Programmieren in Java { 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); } } 99 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. 100 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 101 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. 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. 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. 102 Programmieren in Java 2.4 Anweisungen 2.4.1 Blöcke und Anweisungen Methoden und statische Initialisatoren werden in Java durch Anweisungsblöcke definiert. Ein Anweisungsblock besteht in der Regel aus einer Reihe von Anweisungen, die in geschweiften Klammern stehen. Das bedeutet: Es können in diesem Block lokale Variablen deklariert werden, die außerhalb des Blocks nicht verfügbar sind, und deren Existenz erlischt, wenn der Block ausgeführt wurde. 2.4.2 Leere Anweisungen In Java können leere Anweisungen erstellt werden. Für den Compiler sieht eine leere Anweisung nur wie ein zusätzliches Semikolon aus. 2.4.3 Benannte Anweisungen Jede Anweisung darf in Java eine „Benennung“ haben. Die „Benennung“ hat die gleichen Eigenschaften wie jeder andere Bezeichner. Die Reichweite der „Benennung“ erstreckt sich über den ganzen Block. Der Benennung folgt ein Doppelpunkt. „Benennungen“ werden nur von den Sprunganweisungen break und continue benutzt. 2.4.4 Deklarationen Eine Deklarationsanweisung definiert eine Variable, egal ob Klasse, Schnittstelle, Datenfeld, Objekt oder primitiver Typ. Das Format einer solchen Anweisung hängt davon ab, welcher der 5 verschiedenen Formen deklariert wird. Typname variablenName; bzw. Typname variablenName = initialerWert; 2.4.5 Ausdrucksanweisungen In Java gibt es sieben verschiedene Arten von Ausdrucksanweisungen: Ausdrucksanweisung Zuordnung Prä-Inkrement Prae-Dekrement Beispiel x=13; ++wert; --wert; 103 Programmieren in Java Post-Inkrement Post-Dekrement Methodenaufruf Zuweisungsausdruck wert++; wert--; System.out.println(“Aller Anfang ist schwer!“); byte x = new byte; Abb.: Die sieben Ausdrucksanweisungen in Java Bsp.: Demonstrationsprogramm zur Wirkungsweise der Inkrement- und DekrementOperatoren82 // Demonstration der Operatoren ++ und -public class AutoInkr { public static void main(String[] args) { int i = 1; ausgabe("i: " + i); ausgabe("++i: " + ++i); // Pre-Inkrement ausgabe("i++: " + i++); // Post-Inkrement ausgabe("i: " + i); ausgabe("--i: " + --i); // Pre-Dekrement ausgabe("i--: " + i--); // Post-Inkrement ausgabe("i: " + i); } static void ausgabe(String s) { System.out.println(s); } } /* Ausgabe i: 1 ++i: 2 i++: 2 i: 3 --i: 2 i--: 2 i: 1 */ 2.4.6 Auswahlanweisungen 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) anweisung83 if-else-Anweisungen 82 vgl. PR24500 „anweisung“ bedeutet: Eine einfache Anweisung, die nur von einem Semikolon abgeschlossen ist bzw. eine zusammengesetzte Folge von anweisungen, die von { ... } umschlossen ist. 83 104 Programmieren in Java Ergänzt if um einen else-Teil. Dieser else-Teil reicht die Kontrolle an eine Anweisung oder Block weiter, wenn der boolesche Wert im if-Teil der Anweisung false war. if (boolscher_Ausdruck) anweisung else anweisung Bsp.: Demonstrationsprogramm zur if-Anweisung84 import java.lang.*; public class IfDemo extends Object { public static void main(String[] args) { int i; int abs; // if - else i = 13; System.out.println("i = " + i); if (i >= 0) { abs = i; } else { abs = -i; } System.out.println("abs = " + abs); i = -13; System.out.println("i = " + i); if (i >= 0) { abs = i; } else { abs = -i; } System.out.println("abs = " + abs); // if i = 13; abs = i; System.out.println("i = " + i); if (abs < 0) { abs = -abs; } System.out.println("abs = " + abs); i = -13; abs = i; System.out.println("i = " + i); if (abs < 0) { abs = -abs; } System.out.println("abs = " + abs); // Konditionaloperator i = -13; System.out.println("i = " + i); abs = ( i < 0 ? -i : i); 84 Vgl. PR24601 105 Programmieren in Java System.out.println("abs = " + abs); i = 13; System.out.println("i = " + i); abs = ( i < 0 ? -i : i); System.out.println("abs = " + 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 Werts85. „switch“ vergleicht das Ergebnis einer Auswertung vom integralen Selektor mit jedem integralen Wert. Falls ein passender Wert gefunden wird, wird die zugehörige Anweisung (einfach oder zusammengesetzt) ausgeführt. Anderenfalls kommt es zur Ausführung der bei „default“ angegebenen Anweisung. Die „break“-Anweisung ist optional. Fehlt sie, dann werden die folgenden Anweisungen ausgeführt, bis ein „break“ auftritt. Bsp.: Demonstrationsprogramm zur switch-Anweisung86 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; Integrale Werte sind bspw. vom Typ „int“, „char“. Nichtintegrale Typen. Z.B. float, String, müssen über eine Serie von „if“-Anweisungen „switch“ simulieren 86 vgl. PR24601 85 106 Programmieren in Java 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 nächste Anweisung nach der Unteranweisung oder nach dem Block der whileAnweisung weitergegeben. while (boolescher_Ausdruck) anweisung Bsp.: 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; 107 Programmieren in Java } 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.: public class DoDemo extends Object { public static void main(String args[]) { double a = 2.0; int n = 10; System.out.println("a = " + a); System.out.println("n = " + n); int absn = (n < 0) ? -n : n; double aHochn = 1.0; int i = 1; do { if (n == 0) { break; } aHochn *= a; i++; } while (i <= absn); if ( n < 0) { aHochn = 1.0 / aHochn; } System.out.println("aHochn = " + aHochn); } } for-Anweisung Sie besteht aus - for, gefolgt von optionalem Leerraum, gefolgt von einer öffnenden Klammer. - 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. 108 Programmieren in Java - Testteil. Enthält eine boolesche Variable oder einen Ausdruck, der einmal je Schleife neu bewertet wird. Falls der Ausdruck false wird, geht die Kontrolle zur nächsten Anweisung nach der for-Anweisung und ihrer Unteranweisung oder ihrem Block weiter. Die Zuweisungen werden nur einmal vor der ersten Wiederholung der Unteranweisung oder des Blocks gemacht. - Inkrementteil. Enthält eine durch Kommata getrennte Reihe von Ausdrücken, die einmal je Durchlauf der Schleife bewertet werden. Dieser Teil wird gewöhnlich dazu verwendet, einen Index, der im Testteil überprüft wird, zu inkrementieren. Am Ende des Teils steht ein Semikolon. Jeder der drei Ausdrücke kann entfallen. for (initialisierung; boolescher_Ausdruck; naechster_Schritt) anweisung Bsp.: public class ForDemo extends Object { public static void main(String args[]) { double a = 2.0; int n = 10; System.out.println("a = " + a); System.out.println("n = " + n); int absn = (n < 0) ? -n : n; double aHochn = 1.0; for (int i = 1; i <= absn; i++) { aHochn *= a; } if ( n < 0) { aHochn = 1.0 / aHochn; } System.out.println("aHochn = " + aHochn); } } 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 109 Programmieren in Java Der aktuelle Schleifendurchlauf wird unterbrochen. Es wird zum Anfang der Schleife zurückgekehrt, falls hinter continue kein „Bezeichner“ steht. Anderenfalls wird zu einer äußeren Schleife zurückgekehrt, die eine Markierung (label) gleichen Namens enthält. Eine continue-Anweisung darf nur in einem Unterweisungsblock einer Iterationsanweisung stehen (while, do oder for). Bsp.: „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. 110 Programmieren in Java 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.: Marken im Zusammenhang mit „for“- bzw. „while“-Schleifen 1. Marken im Zusammenhang mit „for“ public class MarkiertesFor { public static void main(String[] args) { int i = 0; aussen: // hier soll keine Anweisung stehen for (; true; ) // Endlos-Schleife { innen: // hier soll keine Anweisung stehen for (; i < 10; i++) { ausgabe("i = " + i); if (i == 2) { ausgabe("continue"); continue; } if (i == 3) { ausgabe("break"); i++; break; } if (i == 7) { ausgabe("continue aussen"); 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 111 Programmieren in Java continue innen i = 5 continue innen i = 6 continue innen i = 7 continue innen i = 8 break aussen */ 2. Marken im Zusammenhang mit „while“ public class MarkiertesWhile { public static void main(String[] args) { int i = 0; aussen: while (true) { ausgabe("Aeussere While-Schleife"); while (true) { i++; ausgabe("i = " + i); if (i == 1) { ausgabe("continue"); continue; } if (i == 3) { ausgabe("continue aussen"); 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 112 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. 113 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 114 Programmieren in Java Veränderungen möglich sind. Die neue Klasse erbt alle Daten und Methoden, die in der Originalklasse definiert wurden. 2.6 Methoden 2.6.1 Die Deklaration Die Deklarationen von Methoden haben folgendes Aussehen87: Zugriffsspezifizierer Modifizierer Returnwert NameMethode(Parameter) throws Exceptionliste Methodenunterschrift88. 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 87 88 Kursiv Geschriebenes ist optional Oft wird für denselben Zusammenhang der Begriff „methodensignatur“ verwendet. 115 Programmieren in Java dieser Methode überschreiben. Methoden, die auf keinem Fall geändert werden sollen, sollten deshalb immer als final deklariert werden. Native Methoden – der Modifizierer native. Native Methoden sind Methoden, die nicht in Java geschrieben sind, aber dennoch innerhalb von Java verwendet werden sollen. Der Modifizierer native wird vor der Methode deklariert, der Body (Körper) der Methode wird durch ein Semikolon ersetzt. Methoden synchronisieren – der Modifizierer synchronized. Wird das Schlüsselwort sychronized vor eine Methodendeklaration gesetzt, dann werden Datenverletzungen verhindert, die entstehen können, wenn zwei Methoden gleichzeitig versuchen auf dieselben Daten zuzugreifen. 2.6.4 Rückgabewerte von Methoden Rückgabewerte vonJava-Methoden können von jedem erlaubten Datentyp 89 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. 89 Nicht nur primitive Datentypen, sondern auch komplexe Objekte. 116 Programmieren in Java 3. Das Arbeiten mit Objekten und Klassen 3.1 Ereignisbehandlung unter grafischen Benutzeroberflächen Mit Hilfe einfacher Benutzerschnittstellen Graphical User Interface (GUI)) lassen sich Ein- und Ausgaben90 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 90 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. 117 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.: 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 ineinem Panel, das selbst wiederum einziges Objekt in einem Fenster (Frame) mit dem Titel „Echo“ ist. import java.lang.*; import java.awt.*; public class EchoMitBeno { // Einlesen und Ausgeben von Zeichenketten ueber // eine grafische Benutzeroberflaeche public static void main(String args[]) { TextField eingabeTextFeld = new TextField(20); Label eingabeTextFeldLabel = new Label("Eingabestring:"); eingabeTextFeld.setEditable(true); TextField ausgabeTextFeld = new TextField(20); Label ausgabeTextFeldLabel = new Label("Ausgabestring:"); ausgabeTextFeld.setEditable(false); Panel panel = new Panel(); panel.add(eingabeTextFeldLabel); panel.add(eingabeTextFeld); panel.add(ausgabeTextFeldLabel); panel.add(ausgabeTextFeld); Frame fenster = new Frame("Echo"); fenster.add(panel); fenster.pack(); fenster.setVisible(true); } } Die Implementierung Ausgabefenster: des vorliegenden 118 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.EventObject91 abgeleitet sind. EventObject AWTEvent ComponentEvent FocusEvent InputEvent KeyEvent ActionEvent AdjustmentEvent ContainerEvent ItemEvent TextEvent WindowEvent MouseEvent Abb.: Spezifische Ereignisklassen Die Hierarchie der AWT-spezifischen Ereignisklassen beginnt mit der Klasse AWTEvent und befindet sich im Paket java.awt. AWTEvent ist Superklasse aller 91 Speichert das Objekt, das die Nachricht ausgelöst hat und gibt durch Aufruf von "public Object getSource()" das Objekt an. 119 Programmieren in Java 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. 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. 120 Programmieren in Java 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.: Textfeldbearbeitung WindowAdapter mit "ActionListener-Interface" import java.lang.*; import java.awt.*; import java.awt.event.*; public class EchoMitBeno extends Object { // Einlesen und Ausgeben von Zeichenketten ueber // eine grafische Benutzeroberflaeche private static TextField eingabeTextFeld = new TextField(20); private static TextField ausgabeTextFeld = new TextField(20); public static void main(String args[]) { Frame fenster = new Frame("Echo"); fenster.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // TextField eingabeTextFeld = new TextField(20); Label eingabeTextFeldLabel = new Label("Eingabestring:"); eingabeTextFeld.setEditable(true); eingabeTextFeld.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { String s = eingabeTextFeld.getText(); ausgabeTextFeld.setText(s); } }); // 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); 121 und Programmieren in Java panel.add(ausgabeTextFeld); // Frame fenster = new Frame("Echo"); fenster.add(panel); fenster.pack(); fenster.setVisible(true); } } 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(); 122 Programmieren in Java d++; String s1 = Double.toString(d); String s2 = String.valueOf(d); ausgabeTextFeld.setText(s2); } }); // TextField ausgabeTextFeld = new TextField(20); Label ausgabeTextFeldLabel = new Label("Ausgabestring:"); ausgabeTextFeld.setEditable(false); Panel panel= new Panel(); panel.add(eingabeTextFeldLabel); panel.add(eingabeTextFeld); panel.add(ausgabeTextFeldLabel); panel.add(ausgabeTextFeld); // Frame fenster = new Frame("Echo"); fenster.add(panel); fenster.pack(); fenster.setVisible(true); } } 3.1.5 Low-Level-Events Von der Klasse ComponenEvent sind Event-Klassen für "Low-Level"-Ereignisse abgeleitet. Sie sind für den Transfer von elementaren Nachrichten zuständig, die von Fenstern und Dialogelementen stammen. Component-Events Wird eine Komponente verschoben oder ihre Größe bzw. ihr Anzeigezustand verändert, dann wird ein Componen-Event generiert. Da Fenster und alle Dialogelemente aus der Klasse Component abgeleitet sind, gelten die hier angegebenen Ereignisse für nahezu alle GUI-Elemente. Der Empfänger für ComponentEvent muß das Interface ComponentListener implementieren und bekommt Events des Typs ComponentEvent übergeben. ComponentEvent erweiter AWTEvent und stellt neben getID, getSource die Methode public Component getComponent() bereit, mit der die Komponente ermittelt werden kann, die die Nachricht ausgelöst hat. Die Registrierung der Empfängerklasse erfolgt mit public void addComponentListener(ComponentListener l). Ereignismethode componentShown componentHidden componentMoved 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 123 Programmieren in Java getWindow() zur Verfügung, mit der das Fenster ermittelt werden kann, das die Nachricht ausgelöst hat. Die Registrierung der Empfängerklasse erfolgt über die Methode public void addWindowListener(WindowListener l) , die in den Klassen Dialog und Frame zur Verfügung steht. Ereignismethode windowActivated windowClosed windowClosing windowDeactivated windowDeiconified windowIconified windowOpened Bedeutung Das Fenster wurde aktiviert. Die Methode wird nach dem Erstellen des Fensters aufgerufen und wenn ein Fenster, das im Hintergrund stand, erneut in den Vordergrund gelangt. Das Fenster wurde geschlossen Das Fenster soll geschlossen werden. Diese Methode wird aufgerufen, wenn der Anwender das Fenster über die TitelLeiste, das Systemmenü oder die Tastenkombination ALT+F4 schließen will. Die Anwendung hat den Code bereit zu stellen, der das Fenster schließt. Standardmäßig reagiert das Programm nicht auf diese Benutzeraktionen Das Fenster wurde deaktiviert, also in den Hintergrung gestellt Das Fenster wurde wiederhergestellt, nachdem es zuvor auf Symbolgröße verkleinert wurde Das Fenster wurde auf Symbolgröße verkleinert Das Fenster wurde geöffnet. Abb.: Übersicht zu den Methoden von WindowListener Mouse-Events Ein Mouse-Event entsteht, wenn der Anwender (innerhalb der Client-Area des Fensters) eine der Maustasten drückt oder losläßt. Dabei reagiert das Programm sowohl auf Klicks der linken als auch der ( - falls vorhanden -) der rechten Maustaste und zeigt an, welche der Umschalttasten STRG, ALT, UMSCHALT oder META während des Mausklicks gedrückt waren. Es ist möglich zwischen einfachen oder doppelten Mausklicks zu unterscheiden. Ein Empfänger für Mouse-Events erweitert die Klasse InputEvent und stellt neben getID, getSource eine Reihe zusätzlicher Methoden bereit. Die Registrierung der Empfängerklasse erfolgt mit public void addMouseListener(MouseListener l), die in allen Klassen zur Verfügung steht, die aus Component abgeleitet sind. 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. 124 Programmieren in Java 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 92geerbten Methoden public public public public boolean boolean boolean boolean isShiftDown(); isControlDown(); isMetaDown(); isAltDown(); zur Verfügung. MouseMotionEvents Sie geben Auskunft über die Bewegung des Mauszeigers. Ein Empfänger für "MouseMotionEvents" muß das Interface MouseMotionListener implementieren. Es wird mit public void addMouseMotionListener(MouseListener l) registriert. Die Methode steht allen Objekten der Klasse Component oder daraus abgeleiteteter Klassen zur Verfügung. Die Methoden von MouseMotionListener bekommen Events des Typs MouseEvent übergeben. Damit stehen diesselben Methoden wie bei MouseEvent zur Verfügung. 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 void focusGained(FocusEvent e) // Aufruf, wenn die Komponente den Fokus erhält public abstract void focusLost(FocusEvent e) 92 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 125 Programmieren in Java // Aufruf, wenn die Komonente den Fokus abgibt Über die Methode public void requestFocus() kann eine Komponente den Fokus für sich selbst beanspruchen bzw. ihn einer anderen Komponenten zuweisen. Key-Events Ein Empfänger für Key-Events muß das Interface KeyListener implementieren und bekommt Events des Typs KeyEvent übergeben. KeyEvent erweitert die Klasse InputEvent, die aus ComponentEvent abgeleitet ist, und stellt neben getID, getSource eine Reihe von Methoden zur Erkennung und Berabeitung von Tastaturcodes zur Verfügung. Die Registrierung erfolgt mit der Methode public void addKeyListener (KeyListener l), die auf allen Objekten des Typs Component oder daraus abgeleiteter Klassen zur Verfügung steht. Das Interface KeyListener definiert drei unterschiedliche Methoden: public abstract void keyTyped(KeyEvent e); public abstract void keyPressed(KeyEvent e); public abstract void keyReleased(KeyEvent e); 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. keyTyped93 getKeyCode Zeichentaste94: VK_UNDEFINED Funktionstaste95: - 93 getKeyChar Zeichentaste: Taste als char Funktionstaste: - 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 94 126 Programmieren in Java keyPressed96 Zeichentaste: VK_... Funktionstaste: VK_... Zeichentaste: Taste als char Funktionstaste: CHAR_UNDEFINED Rückgabecode bei Tastatur-Ereignissen Zusätzlich stehen fogende aus InputEvent geerbten Methoden zur Verfügung: public public public public boolean boolean boolean boolean isShiftDown(); isControlDown(); isMetaDown(); isAltDown(); Bsp.: Das folgende Programm 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) { 95 96 Dazu gehören bspw. F1, F2, Pos 1 aber auch die Umschalttasten: STRG, ALT, UMSCHALT zeigt das Verhalten beim Aufruf von keyPressed 127 Programmieren in Java System.out.println(e.toString()); } public void keyReleased(KeyEvent e) { System.out.println(e.toString()); } public void keyTyped(KeyEvent e) { System.out.println(e.toString()); } } class ML implements MouseListener { public void mouseClicked(MouseEvent e) { // requestFocus(); System.out.println(e.toString()); } public void mousePressed(MouseEvent e) { System.out.println(e.toString()); } public void mouseReleased(MouseEvent e) { System.out.println(e.toString()); } public void mouseEntered(MouseEvent e) { System.out.println(e.toString()); } public void mouseExited(MouseEvent e) { System.out.println(e.toString()); } } class MML implements MouseMotionListener { public void mouseDragged(MouseEvent e) { System.out.println(e.toString()); } public void mouseMoved(MouseEvent e) { System.out.println(e.toString()); } } public static void main(String[] args) { PR14166 f = new PR14166(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(200,200); f.setVisible(true); } } Die Lösung zu dieser Aufgabe kann auch über public void processEvent(AWTEvent e) 128 Programmieren in Java 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: 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 PR14167 extends Frame { Display ausgabe = new Display(); PR14167() { // 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) { PR14167 f = new PR14167(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } 129 Programmieren in Java }); f.setSize(200,200); f.setVisible(true); } } 130 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 Dialoge97" 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 Icon 98 zuordnen. Zuordnen eines Layoutmanagers. Es wird über die Methode "public void setLayout(LayoutManager mgr)" der Klasse Container realisiert. Java stellt fünf Layoutmanager99 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() 97 Modale Dialaloge verhindern die Interaktion des Anwenders mit anderen Fenstern der Anwendung bis zum Schließen des Dialogfensters. 98 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. 99 Vgl. 5.4.2 131 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. 132 Programmieren in Java Modale Dialoge Die Klasse Dialog100 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. Dateidialog Für den Zugriff auf das Dateisystem verwendet man zweckmäßigerweise das Dateidialogfeld. 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(""); } 100 Vgl. 5.3.4 133 Programmieren in Java } } class SichernL implements ActionListener { public void actionPerformed(ActionEvent e) { FileDialog d = new FileDialog(DateiDialog.this, "Welche Datei soll gesichert werden?", FileDialog.SAVE); d.setFile("*.java"); d.setDirectory("."); // Aktuelles Verzeichnis d.show(); String datei = "*.*"; if ((datei = d.getFile()) != null) { dateiName.setText(datei); verzeichnis.setText(d.getDirectory()); } else { dateiName.setText("Cancel wurde gedrueckt!"); verzeichnis.setText(""); } } } public static void main(String args[]) { Frame f = new DateiDialog(); f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(250,110); f.setVisible(true); } } 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, besizen Größe und Position und sind in der Lage, Nachrichten zu empfangen und zu bearbeiten. 134 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: Labels101, Schaltflächen(Buttons)102, Kontrollkästchen und Optionsfelder103, Auswahlmenüs104, Listenfelder105, Textbereiche und Textfelder106, Schieberegler107, 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 Die in der Klasse Canvas definierte Methode paint(Graphics g) füllt die Zeichenfläche lediglich mit Hintergrundfarbe. Subklassen, die wirklich zeichnen, sind von Canvas abgeleitete Klassen, deren paint-Methode alle zu zeichnenden 101 vgl. 5.2.2 vgl. 5.2.1 103 vgl. 5.2.3 104 vgl. 5.2.4 105 vgl. 5.2.5 106 vgl. 5.2.6 107 vgl. 5.2.7 102 135 Programmieren in Java Objekte zu jedem Zeitpunkt neu zeichnen können. Durch Überlagerung von public void paint(Graphics g) sorgt eine Canvas-Komponente für die Darstellung auf dem Bildschirm. Der Punkt (0,0) des übergebenen Graphics-Objekts entspricht dabei der linken oberen Ecke des Ausgabebereichs. 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äge 108. 108 vgl. 5.3.3 136 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 Darstellungssoftwre (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. Das Grundgerüst einer HTML-Seite Eine HTML-Seite wird immer in die Anweisung <html> am Anfang und </html> am Ende eingeschlossen. 137 Programmieren in Java 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 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 Applet109 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 109 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.* 138 Programmieren in Java ü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. 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-Klasse110 in den Code der Applet-Subklasse importiert111 wird. Bsp.: „Aller Anfang ist schwer!. Dieser Spruch soll mit Hilfe eines Applet gezeigt werden.. Die zugehörige Quellcodedatei „AllerAnfangApplet.java“ umfaßt 112: 110 Teil des Pakets java.awt Normalerweise geschieht dies über: import java.awt.Graphics 112 vgl. PR32101 111 139 Programmieren in Java import java.applet.*; import java.awt.*; public class AllerAnfangApplet extends Applet { Font f; String spruch; // public void init() { f = new Font("Helvetica",Font.BOLD,24); this.spruch = "Aller Anfang ist schwer!"; } // public void paint(Graphics g) { // Oval mit Farbe yellow g.setColor(Color.yellow); g.fillOval(10,10,330,100); // Roter Rahmen; da Java keine Linienbreite kennt, // wird die Linienbreite durrch 4 Ovale, (die sich // um Pixelbreite unterscheiden,) simuliert g.setColor(Color.red); g.drawOval(10,10,330,100); g.drawOval( 9, 9,332,102); g.drawOval( 8, 8,334,104); g.drawOval( 7, 7,336,106); g.setColor(Color.black); g.setFont(f); g.drawString(this.spruch,40,70); } } Die zugehörige HTML-Datei AllerAnfangApplet.html umfaßt: <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: 140 Programmieren in Java Java-Applets zeichnen sich durch Überschreiben der „paint“-Methode selbst. Wie wird die „paint“-Methode aufgerufen? Es gibt drei verschiedene Methoden zum Neuzeichnen eines Applet: public void paint(Graphics g) Sie zeichnet tatsächlich die Grafik des Applets in den Zeichenbereich. Sie wird immer aufgerufen, wenn ein Applet neu gezeichnet113 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. public void update(Graphics g) Sie wird von repaint() aufgerufen. Die „update“-Methode löscht den vollständigen Zeichenbereich114 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. 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 Methoden zur Verfügung. So kann bspw. das Drücken einer Maustaste über die Methode public boolean mouseDown(Event e, int x, int y) Aktionen auslösen. Allerdings muß dann die Methode mouseDown() überschrieben werden. „Event“ ist die Ereignisklasse, die Informationen über das Ereignis enthält. „x“ und „y“ sind die Koordinaten, bei denen die Maustaste gedrückt wurde. Bsp.115: Ein Applet zum Zeichnen von Punkten an den Stellen, an denen eine Maustaste gedrückt wurde. 113 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. 114 Das ruft oft den unangenehmen Flimmereffekt bei schnellen Bildsequenzen hervor. 115 PR32305 141 Programmieren in Java // Import der Pakete import java.awt.*; // 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); } public boolean mouseDown(Event e, int x, int y) { mausX = x; mausY = y; mausKlick = true; repaint(); return true; } // Ausgabemethode public void paint(Graphics g) { g.setColor(Color.blue); if (mausKlick) { g.fillOval(mausX,mausY,20,20); mausKlick = false; } } } Nach jedem Mausklick wird ein blauer Punkt in die Zeichenfläche gebracht. Das Bild wird neu gezeichnet. Die repaint()-Methode ruft vor der Ausführung der paint()-Methode die update()-Methode auf, die anschließend paint() aufruft. „update()“ leert in Originalform den Anzeigebereich des Applets. Wird update() überschrieben, z.B. durch public void update(Graphics g) { paint(g); } wird der Anzeigebereich nicht mehr geleert. // Import der Pakete import java.awt.*; // 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); } public boolean mouseDown(Event e, int x, int y) { mausX = x; mausY = y; mausKlick = true; repaint(); return true; } // Ausgabemethode public void paint(Graphics g) 142 Programmieren in Java { g.setColor(Color.blue); if (mausKlick) { g.fillOval(mausX,mausY,20,20); mausKlick = false; } } public void update(Graphics g) { paint(g); } } Eine überschriebene update()-Methode kann den Flimmereffekt erheblich senken. Mausaktionen: Es gibt sechs mögliche Mausaktionen: - Die Maustaste wird gedrückt - Die Maustaste wird wieder losgelassen - Die Maus wird mit gedrückter Maustaste bewegt (Drag) - Die Maus wird mit losgelassener Maustaste bewegt - Der Mauszeiger verläßt den Bereich des Applets - Der Mauszeiger kommt (wieder) in den Bereich des Applets Für jedes Ereignis stellt Java eine Methode116 zur Verfügung, die zur Applet-Klasse gehört: public public public public public public boolean boolean boolean boolean boolean boolean mouseDown(Event e, int x, int y) mouseUp(Event e, int x, int y) mouseEnter(Event e, int x, int y) mouseExit(Event e, int x, int y) mouseDrag(Event e, int x, int y) mouseMove(Event e, int x, int y) 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 aktualisiert117. Nach den bisher vorliegenden Erkenntnissen müßte das zugehörige Applet folgende Gestalt haben: import java.awt.Graphics; import java.awt.Font; Die hier angegebenen Methoden werden von Sun als „depricated“ (veraltet) bezeichnet, da sie aus dem Eventmodell 1.0 stammen. Sie sind aberlange noch nicht überflüssig. 117 vgl. PR42001 116 143 Programmieren in Java import java.util.Date; // // Top Level Deklaration des Applets // public class DigitalUhr extends java.applet.Applet { // Variablen-Deklaration Font einFomt = new Font("Tmes Roman",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(einFomt); // Setzen des aktuellen Font g.drawString(datum.toString(),10,50); // Ausgabe Datum // Da paint() wiederholt mit jeweils dem aktuellen Wert von // "datum" aufgerufen wird, wird die Zeichenkette jede Sekunde //zur Ausgabe des neuen Datums aufgerufen } } In der start()-Methode nimmt die while-Schleife alle Systemressourcen für sich in Anspruch (einschl. der Anzeige am Bildschirm). Deshalb funktioniert die 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 einFomt = new Font("Tmes Roman",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) { 144 Programmieren in Java faden.stop(); faden = null; } } public void run() { // 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(einFomt); // 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 } } 3.3.4 Eine Applet-Schablone 3.3.5 Die Applet-Klasse Die Methoden der Applet-Klasse erlauben Applets - die Handhabung von Parametern über die Methoden: public String getParameter(String name) public String[][]getParameterInfo() - Das Importieren von Bildern Dazu werden folgende Methoden aus dem Image/Graphics-Paket genutzt: public Image getImage(URL url, String name) Die Methode lädt Bilddateien in ein Applet. - das Importieren und Abspielen von Sound-Clips - die Interaktion des Applets mit der Umgebung (Browser, Appletviewer) und Verwaltung des Lebenszyklus eines Applets 145 Programmieren in Java 3.4 Behälter (Container, Collection) 146 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 Methoden118 zum Neuzeichnen des Applet: public void paint(Graphicds 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 Linien119 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 118 119 vgl. 3.2.2 vgl. PR42005 147 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 Rechtecken120 // zeichne Rechtecke import java.applet.*; import java.awt.*; public class RechteckeAppl3 extends Applet 120 vgl. PR41103 148 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. 149 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.121: 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.122: 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.123: import java.awt.*; public class ZeichnePolyAppl3 extends java.applet.Applet { 121 PR41105 PR41105 123 PR41105 122 150 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) vertikalen 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 151 Blau-Anteil 255 0 127 0 0 255 0 255 255 Programmieren in Java 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 FontKlasse124 und die Fontmetrics-Klasse125 beim Textzeichnen eine Rolle. 4.1.4 Die Java-Zeichenmodi 124 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. 125 152 Programmieren in Java Die Graphics-Klasse verfügt über zwei verschiedene Modi zum Zeichnen: den Paint-Modus und den XOR-Modus. 4.1.5 Das Zeichnen von Bildern Es umfaßt die Schritte: Das Laden von Bildern und das Ausgeben von Bildern. Das Laden von Bildern mit der Methode: public Image getImage(URL url). Die Methode126 lädt ein Bild und erstellt automatisch eine Instanz der Image-Klasse. Das Anzeigen von Bildern mit der drawImage()-Methode 126 Die Methode gehört zur Applet-Klasse 153 Programmieren in Java 4.2 Animation 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. 4.2.1 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 4.2.2 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() { 154 Programmieren in Java if (faden == null) { faden = new Thread(this); faden.start(); } } public void stop() { if (faden != null) { faden.stop(); faden = null; } } public void run() { while (true) { x = xStart + (int)(r * Math.sin(theta)); y = yStart + (int)(r * Math.cos(theta)); if ((theta >= thetaMax) | (theta <= thetaMin)) wechsel = -wechsel; theta += wechsel; repaint(); try { Thread.sleep(10); } catch(InterruptedException e) {} } } public void paint(Graphics g) { g.setColor(Color.blue); g.drawLine(xStart,yStart,x,y); g.setColor(Color.red); g.fillOval(x-d/2,y-d/2,d,d); } } 4.2.3 Reduktion von Flimmereffekten in Animationen Überschreiben der update()-Methode 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; 155 Programmieren in Java int xAlt, yAlt; // Methoden public void init() { setBackground(Color.white); } public void start() { if (faden == null) { faden = new Thread(this); faden.start(); } } public void stop() { if (faden != null) { faden.stop(); faden = null; } } public void run() { while (true) { x = xStart + (int)(r * Math.sin(theta)); y = yStart + (int)(r * Math.cos(theta)); if ((theta >= thetaMax) | (theta <= thetaMin)) wechsel = -wechsel; theta += wechsel; repaint(); try { Thread.sleep(10); } catch(InterruptedException e) {} } } public void update(Graphics g) { g.setColor(Color.Yellow); g.drawLine(xStart,yStart,xAlt,yAlt); g.fillOval(xAlt-d/2,yAlt-d/2,d,d); g.setColor(Color.blue); g.drawLine(xStart,yStart,x,y); g.setColor(Color.red); g.fillOval(x-d/2,y-d/2,d,d); paint(g); } public void paint(Graphics g) { xAlt = x; yAlt = y; } } Double Buffering Mit „double buffering“ wird eine zweite Oberfläche geschaffen, in der alles vorgezeichnet und dann auf einmal in die Zeichnungsoberfläche des Applet ausgegeben wird. 156 Programmieren in Java import java.awt.*; public class Pendel extends java.applet.Applet implements Runnable { // Instanzvariable // Position vom Zentrum des schwingenden Pendels int x, y; // double thetaMax = (double) 0.35; double thetaMin = (double) -0.35; // Die initiale Position vom Pendel double theta = (double) 0.; // double wechsel = (double) 0.01; // int xStart = 150, yStart = 20; // Radius des Pendels double r = (double) 200; // Durchmesser des Balls int d = 20; Thread faden; int xAlt, yAlt; Image backgroundImage; Graphics backgroundGraphics; // Methoden public void init() { setBackground(Color.white); backgroundImage = createImage(this.size().width, this.size().height); backgroundGraphics = backgroundImage.getGraphics(); } public void start() { if (faden == null) { faden = new Thread(this); faden.start(); } } public void stop() { if (faden != null) { faden.stop(); faden = null; } } public void run() { 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) { 157 Programmieren in Java 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; } } 4.3 Das neue 2D API 158 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 Komponenten127 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 127 Java 1.2 und das Swing-Konzept erweitern diese Komponenten noch einmal um einen satz von neuen und ergänzenden Komponenten. 159 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 Applet128 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. 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) 128 Abgeleitet von der Panel-Klasse 160 Programmieren in Java 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 Buttonklicks 129 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() 129 vgl. PR52105 161 Programmieren in Java // Ausrichten der Textzeiele 5.2.3 Kontrollkästchen und Optionsfelder Erzeugen von Kontrollkästchen. Ein Kontrollkästchen (Checkbutton / Checkbox) hat zwei Bestandteile: ein Label (Text, der neben dem Kontrollkästchen angelegt wird) und einem Zustand (boolesche Variable, die angibt, ob die Box eingeschaltet wurde oder nicht130). 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"), 130 Standardmäßig ist die Box ausgeschaltet (Wert ist false oder „off“). 162 Programmieren in Java cb2 = new Checkbox("Checkbox 2",true), cb3 = new Checkbox("Checkbox_3",false); public PR52301() { setLayout(new GridLayout(3,1)); setBackground(Color.lightGray); cb1.addItemListener(new CBL()); cb2.addItemListener(new CBL()); cb3.addItemListener(new CBL()); add(cb1); add(cb2); add(cb3); } public class CBL implements ItemListener { public void itemStateChanged(ItemEvent e) { Checkbox cb = (Checkbox) e.getItemSelectable(); System.out.println(cb.getLabel() + ": " + cb.getState()); } } public static void main(String args[]) { PR52301 f = new PR52301(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(100,150); f.setVisible(true); } } Defintion zur CheckboxGroup. Eine CheckboxGroup ist die Java-Variante einer Gruppe von Radiobuttons (Optionsfelder), von den genau immer einer aktiviert wird. Wird eine anderer Button aktiviert, so änder er seinen internen Status auf "true" und der zuvor gesetzte wird "false". Eine CheckboxGroup ist nichts Anderes als eine Checkbox, deren CheckboxGroup-Parameter gesetzt ist. Erzeugen von Optionsfeldern (Radiobuttons). Erforderlich ist zunächst eine Checkbox-Gruppe, die über die betreffenden Konstruktoren erstellt werden kann. Dieser Gruppe werden die einzelnen Optionsfelder hinzugefügt. Plazieren von Optionsfeldern in einem Container. Auch Optionsfelder müssen wie alle Komponenten nach der Erzeugung in einen Container gebracht werden. Das geschieht über die Methode add(). Setzen und Überprüfen von Optionsfeldern. Die Methoden getState() und getLabel() funktionieren auch bei Optionsfeldern. Zum Zugriff auf die Gruppe eines Optionsfelds und zum Ändern gibt es die Methoden: public CheckboxGroup getCheckboxGroup() public void setCheckboxGroup(CheckboxGroup g) Zum Holen und Setzen des momentan ausgewählten Optionsfelds dienen public Checkbox getCurrent() public void setCurrent(Checkbox box) Bsp.: import java.awt.*; import java.awt.event.*; public class PR52302 extends Frame 163 Programmieren in Java { CheckboxGroup cbg = new CheckboxGroup(); public PR52302() { setLayout(new GridLayout(3,1)); setBackground(Color.lightGray); Checkbox cb1 = new Checkbox("rot",cbg,false); Checkbox cb2 = new Checkbox("blau",cbg,false); Checkbox cb3 = new Checkbox("gruen",cbg,false); cb1.addItemListener(new CBL()); cb2.addItemListener(new CBL()); cb3.addItemListener(new CBL()); add(cb1); add(cb2); add(cb3); } public class CBL implements ItemListener { public void itemStateChanged(ItemEvent e) { Checkbox cb = (Checkbox) e.getItemSelectable(); if (cb.getLabel() == "rot") { if (cb.getState()) { System.out.println("rot"); } } if (cb.getLabel() == "blau") { if (cb.getState()) { System.out.println("blau"); } } if (cb.getLabel() == "gruen") { if (cb.getState()) { System.out.println("gruen"); } } } } public static void main(String args[]) { PR52302 f = new PR52302(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); 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. 164 Programmieren in Java Bsp.131: import java.awt.*; public class CheckAktApplet extends java.applet.Applet { CheckboxGroup meineCheckboxGruppe = new CheckboxGroup(); public void init() { add(new Checkbox("Blau",meineCheckboxGruppe,false)); add(new Checkbox("Rot",meineCheckboxGruppe,false)); add(new Checkbox("Gruen",meineCheckboxGruppe,false)); add(new Checkbox("Gelb",meineCheckboxGruppe,true)); // Hintergrundfarbe passend zur Voreinstellung setBackground(Color.yellow); } public boolean action(Event e, Object welcheAktion) { // Test auf Kontrollkaestchen oder Radiobutton if (!(e.target instanceof Checkbox)) return false; // Instanz der Ereignisse Checkbox welcheAuswahl = (Checkbox) e.target; // Status des Radiobuttons zur Kontrolle boolean CheckboxStatus = welcheAuswahl.getState(); // Auswahl Blau if (welcheAuswahl.getLabel() == "Blau") { if (CheckboxStatus) setBackground(Color.blue); return true; } // Auswahl Rot if (welcheAuswahl.getLabel() == "Rot") { if (CheckboxStatus) setBackground(Color.red); return true; } // Auswahl Gruen if (welcheAuswahl.getLabel() == "Gruen") { if (CheckboxStatus) setBackground(Color.green); return true; } if (welcheAuswahl.getLabel() == "Gelb") { if (CheckboxStatus) setBackground(Color.yellow); return true; } return false; // keine der Optionen wird aufgefangen } } 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. 131 PR54305 165 Programmieren in Java 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() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setBackground(Color.lightGray); 166 Programmieren in Java 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() { 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(); 167 Programmieren in Java } 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: 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) 168 Programmieren in Java Ü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 Columns() 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. Berabeiten von Textfeldern. Über public void setEditable(boolean erlaubt) // Aufruf mit Parameter false unterbindet weitere Eingaben bzw. public boolean isEditable() // Abfrage des aktuellen Status kann mit die Veränderung von Text verhindern. Mit public void setEchoCharacter(char z) (Übergabe eines Zeichens, das beim Tastendruck anstelle des vom anwender eingegebenen Zeichens ausgegeben wird) bzw. public char getEchochar() besteht die Möglichkeit verdeckter Eingaben (z.B. für Paßwörter). Generieren eines Action-Event erfolgt beim Drücken der "Enter-"Taste innerhalb des Textfelds. Die Methode getActionCommand des Action-Events liefert den Inhalt des Textfelds. Generieren eines Text-Ereignisses bei jeder Änderung im Textfeld. Ein Empfänger kann mit der Methode addTextListener vom Textfeld registriert werden. Als Argument wird ein Objekt erwartet, das das Interface TextListener implementiert. Beim Auftreten eines Text-Ereignisses wird vom TextListener die Methode textValueChanged mit einem "TextEvent" als Parameter aufgerufen. "TextEvent" ist aus "AWTEvent" abgeleitet und erbt die Methoden getID, 169 Programmieren in Java 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) { t1.setText("Eingefuegt durch Button 2: " + s); t1.setEditable(false); } } public static void main(String args[]) { PR14170 f = new PR14170(); 170 Programmieren in Java f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(300,200); f.setBackground(Color.lightGray); f.setVisible(true); } } TextArea Mit dieser Klasse können mehrzeilige Textfelder erzeugt werden. Zusätzlich kann der Text in allen Richtungen "scrollen", so daß auch größere Texte bequem bearbeitet werden können. Konstruktoren. public TextArea() erzeugt ein leeres TextArea-Objekt in einer vom System vorgegebenen Größe. public TextArea(int zeilen, int spalten) erzeugt eine leeres TextArea-Objekt mit einer bestimmten Anzahl von sichtbaren Zeilen und Spalten. public TextArea(String text, int zeilen, int spalten) erzeugt ein TextArea-Objekt mit Text. public TextArea(String text, int zeilen, int spalten, int scroll) erzeugt ein TextArea-Objekt, in dem über scroll die Ausstattung der TextArea mit Schieberegistern festgelegt werden kann. Dazu stellt TextArea die Konstanten SCROLLBARS_BOTH, SROLLBARS_NONE, SCROLLBARS_VERTICAL_ONLY, SCROLLBARS_HORIZONTAL_ONLY zur Verfügung, die als Argumente übergeben werden können. Zusätzliche in der Klasse bereitgestellte Methoden zum Verändern von Teilen des Texts. Neben den bereits in der Klasse TextField angegebenen Methoden stehen zur Verfügung: public void insert(String str, int pos) verändert die Zeichenkette str ab Position pos. Der dahinter stehende Text wird entsprechend nach hinten verschoben. public void append(String str) hängt den über str übergebenen Text hinten an. public void replaceRange(String text, int start, int ende) ersetzt den Text zwischen start und end durch den String text. Senden von Text-Events. Ein Objekt der Klasse TextArea sendet Text-Events, so wie es bei TextField beschrieben wurde. 5.2.7 Schieber und Bildlaufleisten Listen und Textbereiche besitzen standardmäßig die Eigenschaft bei Bedarf nicht in den Anzeigebereich passenden Inhalt über Bildlaufleisten und Schieber zu scrollen. 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 171 Programmieren in Java einer Positionierung des Schiebers. Zur Änderung des aktuellen Werts einer Bildlaufleiste können drei verschieden Teile der Bildlaufleiste verwendet werden: - Pfeile an beiden Enden zum Erhöhen / Erniedrigen des Werts um eine Einheit (standardmäßig 1) - Ein Bereich in der Mitte zum Erhöhen / Erniedrigen eines Werts um je eine größere Einheit (standardmäßig 10) - Ein Bildlauffeld (Schieber) in der Mitte, der den momentan gewählten Wert anzeigt. Durch Verschieben des Bildlauffelds mit dem Mauszeiger kann innerhalb der Bildlaufleiste ein anderer Wert gewählt werden. Nötig ist nur die Festlegung eines Höchst- und Mindestwerts für die Bildlaufleiste. Der Rest wird von Java erledigt. Konstruktoren. public Scrollbar() erzeugt einen vertikalen Schieberegler mit 0,0 als anfänglichen Höchst-, Mindestwert. public Scrollbar(int orientierung) "orientierung" bezeichnet die Ausrichtung und kann über Scrollbar.HORIZONTAL bzw. Scrollbar.VERTICAL festgelegt werden. public Scollbar(int orientierung, int wert, int bh, int minimum, int maximum) orientierung...bestimmt die Ausrichtung des Schiebereglers wert...Anfangswert des Schiebereglers (Wert zwischen dem Höchst- und Mindestwert bh...Breite bzw. Höhe der dem Schieberegler zugeteilten Box (Seite) minimum, maximum...bezeichnet den Mindest-, Höchstwert des Schiebereglers Methoden der Scrollbar-Klasse versorgen die Handhabung der Werte des Schiebereglers: Methode public public public public public public Bedeutung Zugriff auf den aktuellen Wert des Schiebereglers Setzt den aktuellen Wert des Schiebereglers Bestimmt die Ausrichtung des Schiebereglers Zugriff auf den Mindestwert Zugriff auf den Höchstwert Zugriff der Parameter, die die Stärke der Veränderung des Werts beim Klicken auf die Buttons bzw. die Schaltfläche zwischen Schieber und Button bestimmen. public void setUnitIncrement(int l) Veränderung der Parameter, die die Stärke der Veränderung des Werts beim Klicken auf die Buttons zwischen Schieber und Button bestimmen public int getBlockIncrement() Zugriff der Parameter, die die Stärke der Veränderung des Werts beim Klicken auf die Buttons bzw. die Schaltfläche zwischen Schieber und Button bestimmen public void setBlockIncrement(int l) Veränderung der Parameter, die die Stärke der Veränderung des Werts beim Klicken auf die Schaltfläche zwischen Schieber und Button bestimmen int getValue() void setValue(int wert) int getOrientation() int getMinimum() int getMaximum() int getUnitIncrement() Ereignisse. Ein 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. 172 Programmieren in Java Konstanten, die von getAdjustmentType Bedeutung zurückgegeben werden AdjustmentEvent.UNIT_INCREMENT Der Wert durch Klicken eines "Button" um eine Einheit erhöht. AdjustmentEvent.UNIT_DECREMENT Der Wert durch Klicken eines "Button" um eine Einheit erniedrigt. AdjustmentEvent.BLOCK_INCREMENT AdjustmentEvent.BLOCK_DECREMENT AdjustmentEvent.TRACK Bsp.: Der Wert wurde durch Klicken der Schaltfläche zwischen Button und Schieber um eine Seite erhöht Der Wert wurde durch Klicken der Schaltfläche zwischen Button und Schieber um eine Seite vermindert Der Wert wurde durch Ziehen des Schiebers verändert import java.awt.*; import java.awt.event.*; public class PR14175 extends Frame { public PR14175() { Panel p = new Panel(); p.setLayout(new BorderLayout()); Scrollbar hsb = new Scrollbar(Scrollbar.HORIZONTAL,1,10,1,100); hsb.addAdjustmentListener(new HsbAL()); p.add("South",hsb); Scrollbar vsb = new Scrollbar(Scrollbar.VERTICAL,1,10,1,100); vsb.addAdjustmentListener(new VsbAL()); p.add("East",vsb); add(p); pack(); } public class HsbAL implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent e) { switch(e.getAdjustmentType()) { case AdjustmentEvent.UNIT_INCREMENT: System.out.println("Adjustment.UNIT_INCREMENT"); break; case AdjustmentEvent.UNIT_DECREMENT: System.out.println("Adjustment.UNIT_DECREMENT"); break; case AdjustmentEvent.BLOCK_INCREMENT: System.out.println("Adjustment.BLOCK_INCREMENT"); break; case AdjustmentEvent.BLOCK_DECREMENT: System.out.println("Adjustment.BLOCK_DECREMENT"); break; case AdjustmentEvent.TRACK: System.out.println("Adjustment.TRACK"); break; } System.out.println("Wert: " + e.getValue()); } } public class VsbAL implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent e) { switch(e.getAdjustmentType()) { case AdjustmentEvent.UNIT_INCREMENT: 173 Programmieren in Java System.out.println("Adjustment.UNIT_INCREMENT"); break; case AdjustmentEvent.UNIT_DECREMENT: System.out.println("Adjustment.UNIT_DECREMENT"); break; case AdjustmentEvent.BLOCK_INCREMENT: System.out.println("Adjustment.BLOCK_INCREMENT"); break; case AdjustmentEvent.BLOCK_DECREMENT: System.out.println("Adjustment.BLOCK_DECREMENT"); break; case AdjustmentEvent.TRACK: System.out.println("Adjustment.TRACK"); break; } System.out.println("Wert: " + e.getValue()); } } public static void main(String args[]) { PR14175 f = new PR14175(); f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setBackground(Color.lightGray); f.setVisible(true); } } 5.2.8 ScrollPane Ein ScrollPane ist ein Container für automatisches horizontales und vertikales Scrolling. ScrollPane unterscheidet sich durch zwei Eigenschaften von einem gewöhnlichen Panel: - Es kann genau ein Dialogelement aufnehmen und benötigt keinen eigenen Layoutmanager - Es ist in der lage, eine virtuelle Ausgabefläche zu verwalten, die größer ist als die auf dem Bildschirm zur Verfügung stehende Ausgabefläche. Die von einem ScrollPane angezeigte Komponente arbeitet mit einer virtuellen Ausgabefläche ( und merkt nichts von evtl. Größenbeschränkungen auf dem Bildschirm). Falls die benötigte Ausgabefläche größer ist als die anzeigbare, blendet ein ScrollPane automatisch die erforderlichen Schieberegister ein, um Dialogelemente horizontal und vertikal verschieben zu können. Konstruktoren. public ScrollPane() public ScrollPane(int scrollbarDisplayPolicy) "srollbarDisplayPolicy" definiert die Strategie zur Anzeige der Schieberegler entsprechend den in der folgende Liste aufgelisteten Konstanten: ScrollPane.SCROLLBARS_AS_NEEDED ScrollPane.SCROLLBARS_ALWAYS 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 174 Programmieren in Java ScrollPane.SCROLLBARS_NEVER Die Schieberegler werden nie angezeigt, der Bildschirm kann nur vom Programm aus verschoben werden 5.2.9 Zeichenbereiche Ein Canvas ist ein frei definiertes Dialogelement, das in der Grundversion praktisch keine Funktionalität zur Verfügung stellt. Damit ein Canvas etwas Sinnvolles tun kann, muß daraus eine eigene Klasse abgeleitet werden. Konstruktor: public Canvas() Die Methode paint(Graphics g). Durch Überlagerung von public void paint(Graphics g) sorgt eine Canvas-Komponente für die Darstellung auf dem Bildschirm. Die vom System bereitgestellte Version zeichnet nur die Ausgabefläche in der aktuellen Hintergrundfarbe. Eine überlagerte Version kann hier natürlich ein beliebig komplexes Darstellungsverhalten erzielen. Der Punkt (0,0) des übergebenen Graphics-Objekts entspricht dabei der linken oberen Ecke des Ausgabebereichs. Ereignisse. Da die Klasse Canvas aus Component abgeleitet ist, bekommt ein Canvas-Objekt alle Ereignisse zugestellt, die auch an eine Komponente gehen. Hierzu zählen Tastatur-, Maus-, Mausbewegungs-, Fokusund Komponentenereignisse. Bsp.: Die von Canvas abgeleitete Klasse ZeichneCanvas import java.awt.*; import java.awt.event.*; public class ZeichneCanvas extends Canvas { // Instanzvariable public boolean zeichneObjekte; private Font font; // Konstruktoren public ZeichneCanvas() { this(400,400); } public ZeichneCanvas(int breite,int hoehe) { super(); setSize(breite,hoehe); setBackground(Color.white); bereinigen(); font = new Font("Helvetica",Font.BOLD,24); } // Instanzmethoden public void bereinigen() { zeichneObjekte = false; repaint(); } public void zeichnen() { zeichneObjekte = true; repaint(); } public void paint(Graphics g) { System.out.println( "Methode paint wurde aufgerufen, zeichneObjekte = " + zeichneObjekte); 175 Programmieren in Java 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); } }); 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); 176 Programmieren in Java 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(); } } 177 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 gesetzt wird. Wird die Einstellung nicht geändert, werden für Vordergrund- und Hintergrundfarbe die unter Windows eingestellten Standardfarben verwendet. Mit 178 Programmieren in Java 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()-Methode132. Mit public void hide()133 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: Konstruktor. public MenuBar() Einfügen von Menüs. public void add(Menu m). Entfernen von bestehenden Menüs. public void remove(int index) 132 Für show() aus dem Paket java.awt.Component gibt es als neue Variante die Methode setVisible(boolean) 133 hide() ist in java.awt.Frame als „deprecated“ deklariert. Statt dessen steht setVisible(boolean) zur Verfügung 179 Programmieren in Java 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) // Einfügepostion kann frei angegeben werden. Zugriff auf Menüeintrag. public MenuItem getItem(int index) Anzahl der Einträge. public int getItemCount() 180 Programmieren in Java 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 Menu d = new Menu("Datei"); MenuItem[] datei = { // new MenuItem("Open"), 181 Programmieren in Java // 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()); } else if (aktionsKommando.equals("Versteck")) { System.out.println("Versteck " + ziel.getState()); } 182 Programmieren in Java } } 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); } } 183 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. 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: 184 Programmieren in Java - 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"); 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> 185 Programmieren in Java <APPLET CODE="FlowLayoutTestApplet.class" WIDTH=100 HEIGHT=120> </APPLET> </BODY> </HTML> zur folgenden Darstellung Eine Veränderung in der Größe des Applet macht den zeilenweisen Aufbau deutlich: <HTML> <HEAD> <TITLE>FlowLayout-Test</TITLE> </HEAD> <BODY> <APPLET CODE="FlowLayoutTestApplet.class" WIDTH=300 HEIGHT=100> </APPLET> </BODY> </HTML> Die BorderLayout-Klasse 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() { 186 Programmieren in Java int i = 0; setBackground(Color.yellow); setLayout(new BorderLayout()); add("North",new Button("Schaltflaeche " add("South",new Button("Schaltflaeche " add("East",new Button("Schaltflaeche " add("West",new Button("Schaltflaeche " add("Center",new Button("Schaltflaeche " + + + + + i++)); i++)); i++)); i++)); i++)); } } Für das Applet wurde folgende Größe vereinbart: <HTML> <HEAD> <TITLE>FlowLayout-Test</TITLE> </HEAD> <BODY> <APPLET CODE="BorderLayoutTestApplet.class" WIDTH=500 HEIGHT=100> </APPLET> </BODY> </HTML> Das führt zu dem folgenden Layout: Die GridLayout-Klasse Diese Klasse teilt den Container in ein Gitter mit gleich großen Zellen ein. Hinzugefügte Komponenten werden von links nach rechts angeordnet, begonnen wird bei den linken, oberen Zellen. Bsp.: Plazieren von Schaltflächen Der folgende Quellcode import java.awt.*; import java.applet.*; public class GridLayoutTestAppl extends Applet { public void init() { setLayout(new GridLayout(7,3)); for (int i = 0; i < 20; i++) add(new Button("Schaltflaeche " + i)); } } führt über die folgende Größe im Applet-Tag <HTML> <HEAD> <TITLE>FlowLayout-Test</TITLE> </HEAD> <BODY> 187 Programmieren in Java <APPLET CODE="GridLayoutTestAppl.class" WIDTH=300 HEIGHT=200> </APPLET> </BODY> </HTML> zu der folgenden Darstellung Die GridBagLayout-Klasse und die GridBagConstraint-Klasse Im Gegensatz zur GridLayout-Klasse entscheidet hier der Layout-Manager über die Anzahl der Zeilen und Spalten. Die GridBagLayout-Klasse ermöglicht einer Komponente auch die Belegung von mehr als einer Zelle. Der gesamte Bereich, den die Komponente einnimmt, wird „display area“ genannt. Bevor einem Container eine Komponente hinzugefügt wird, müssen GridBagLayout Vorschläge unterbreitet werden, wo die Komponente hingestellt werden soll. Die GridBagConstraint-Klasse besitzt verschiedene Variable zur Steuerung der Anordnung der Komponenten: - die Variablen gridx und gridy (Koordinaten der Zelle in der die nächste Komponente plaziert werden soll). Die obere linke Ecke von GridBagLayout liegt bei (0,0), der Standardwert ist GridBagConstraints.RELATIVE - die Variablen gridwidth und gridheight bestimmen, wie viele Zellen hoch und breit eine Komponente sein sollte (Standardwert ist 1). Der Wert GridBagConstraint.REMAINDER für gridwidth bestimmt, daß eine Komponente die letzte in der Spalte sein sollte. - Die Variable fill gibt an, welche Dimension einer Komponente sich verändern soll, wenn eine Komponente kleiner als der Anzeigebereich ist. Gültige Werte sind: NONE, BOTH, HORIZONTAL und VERTICAL. "GridBagConstraint.BOTH" sorgt für eine Streckung der Komponenten in beiden Richtungen so, daß sie den Anzeigebereich voll ausfüllen. - Die Variable weightx und weighty definieren die Gewichtung für freien Raum innerhalb eines Containers und bestimmen die relative Größe der Komponenten. Ein Komponente mit einem Wert von 2.0 für weightx nimmt doppelt soviel Platz in horizontaler Richtung wie eine Komponente mit dem Wert 1.0 für weightx. Bsp.134: Plazieren von Schaltflächen import java.awt.*; import java.util.*; import java.applet.Applet; 134vgl. PR54205 188 Programmieren in Java 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; 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(); } 189 Programmieren in Java 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) 190 bestimmt die Anfangsposition und Programmieren in Java 5.5 Die Event-Modelle 1.0 und 1.1 5.5.1 Der AWT-Handler 1.0 Die Methode handleEvent Die handleEvent()-Methode135 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); 135 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, 191 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 Labels136, der Elemente, des Inhalts der Komponenten137. Anstatt des zusätzlichen Arguments kann auch die Methode 138 getLabel verwendet werden. Behandlung von Fokus-Ereignissen Für die Ereignisse "Fokuserhalt" und "Fokusverlust" können die Methoden 136 vgl. PR52105 Nicht vergessen: Das Argument in den richtigen Objekt-Typ zu casten 138 vgl. PR54305 137 192 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. 193 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; } */ 194 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 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 195 Programmieren in Java TextListener addTextListener() removeTextListener() und TextField abgeleitet ist Abb.: Event- und Listener-Typen Jeder Komponenten-Typ unterstützt nur bestimmte Ereignis-Typen: Komponenten-Typ Adjustable Applet Button Canvas Checkbox CheckboxMenuItem Choice Component Container Dialog FileDialog Frame Label List Menu MenuItem Panel PopupMenu Scrollbar ScrollPane TextArea TextComponent TextField Window Ereignis, das durch die Komponente unterstützt wird AdjustmentEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ItemEvent, FocusEvent, 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 196 Programmieren in Java Da nun bekannt ist, welche Ereignisse eine bestimmte Komponente unterstützt, kann das zugehörige „Listener“-Interface angegeben werden: „Listener“-Interface ActionListener AdjustmentListener ComponentListener ComponentAdapter ContainerListener ContainerAdapter FocusListener FocusAdapter KeyListener KeyAdapter MouseListener MouseAdapter MouseMotionListener MouseMotionAdapter WindowListener Windowadapter ItemListener TextListener Methoden actionPerformed(ActionEvent) adjustmentValueChanged( AdjustmentEvent) componentHidden(ComponentEvent) componentShown(ComponentEvent) componentMoved(ComponentEvent) componentResized(ComponentEvent) componentAdded(ContainerEvent) componentRemoved(ContainerEvent) focusGained(FocusEvent) focusLost(FocusEvent) keyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent) mouseClicked(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent) mouseDragged(MouseEvent) mouseMoved(MouseEvent) windowOpened(Windowevent) windowClosing(WindowEvent) windowClosed(WindosEvent) windowActivated(WindowEvent) windowDeactivated(WindowEvent) windowIconified(WindowEvent) windowDeiconified(WindowEvent) ItemStateChanged(ItemEvent) textValueChanged(TextEvent) Abb.: Listener-Interface Adapter besorgen Default-Methoden für jede Methode im Interface. Nur die Methode, die geändert wird, muß überschrieben werden. Komponenten des AWT in Fenstern bzw. Applets unter Java 1.1 Die in den folgenden Programmen verwendeten Komponenten können in einem Fenster (Frame) über die Instanz eines Applet dargestellt werden. Das Programm enthält deshalb zusätzlich zu den für Applets nötigen Methoden eine main()Methode, die eine Instanz eines Applets innerhalb eines Frame aufbaut. Bsp.: Bearbeitung von zwei Schaltflächen und einem Textfeld139 // Eine Anwendung und ein Applet import java.awt.*; import java.awt.event.*; import java.applet.*; public class SchalterApplet extends Applet { 139 PR55202 197 Programmieren in Java Button sch1 = new Button("Schaltflaeche 1"); Button sch2 = new Button("Schaltflaeche 2"); TextField t = new TextField(20); public void init() { sch1.addActionListener(new Sch1()); sch2.addActionListener(new Sch2()); add(sch1); add(sch2); add(t); } class Sch1 implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Schaltflaeche 1"); } } class Sch2 implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Schaltflaeche 2"); } } // Schliessen der Applikation static class WL extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } // main()-Methode fuer die Applikation public static void main(String[] args) { SchalterApplet applet = new SchalterApplet(); Frame einRahmen = new Frame("Schalterdarstellung"); einRahmen.addWindowListener(new WL()); einRahmen.add(applet,BorderLayout.CENTER); einRahmen.setSize(300,200); applet.init(); applet.start(); einRahmen.setVisible(true); } } 198 Programmieren in Java 6. Multithreading Beim Ablauf eines Programms wird mit der Ausführung seiner Initialisierungsprozedur begonnen, werden Methoden aufgerufen und mit der Ausführung und Verarbeitung fortgefahren, bis das Ende des Programms erreicht ist. Das Programm nutzt einen einzigen Thread. Ein Thread ist für das Programm ein Kontrollpunkt. Multithreading ermöglicht die Ausführung mehrerer verschiedener Threads gleichzeitig im gleichen Programm ohne gegenseitige Beeinträchtigung. Multithreading bedeutet: Gleichzeitige parallele Ausführung von mehreren einzelnen Programmschritten. Innerhalb eines Programms können mehrere Dinge gleichzeitig geschehen, mehrere „Fäden“ eines Programms können gleichzeitig verfolgt und abgearbeitet werden. Java hat Threads direkt in die Sprache integriert. Threads werden durch die Klasse Thread und das Interface Runnable implementiert. In beiden Fällen wird der parallel auszuführende Code durch die überlagerte Methode „run“ zur Verfügung gestellt. Die Kommunikation kann durch Zugriff auf Instanz- / Klassenvariablen oder durch den Aufruf beliebiger Methoden, die innerhalb von „run“ sichtbar sind, erfolgen. Zur Synchronisation stellt Java das aus der Betriebssystemtheorie bekannte Konzept des Monitors zur Verfügung. Darüber können kritische Abschnitte innerhalb der korrekt geklammerten Programmfragmente und Methoden gekapselt und so der Zugriff auf gemeinsam benutzte Datenstrukturen koordiniert werden. Java bietet Funktionen zur Verarbeitung von Threads an: Zusammenfassen von Threads zu Gruppen, Priorisieren von Threads, Ermitteln von Eigenschaften zu Threads. Das Scheduling kann dabei wahlweise140 unterbrechend oder nicht unterbrechend implementiert sein. 140 Die Sprachspezifikation legt dies nicht eindeutig fest 199 Programmieren in Java 7. Ein- und Ausgabe In Java werden Ein-, Ausgabeoperationen über sog. Datenströme 141 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.io142 , das bei jedem Programm mit Ein-/Ausgabeoperationen importiert werden muß. Einfache Strom-Methoden erlauben nur das Versenden von Bytes mit Hilfe von Datenströmen143. Ein nicht interpretierbarer Bytestrom kann von jeder beliebigen Quelle kommen. Quelle bzw. Ziel des Stroms sind völlig verschiedene Erzeuger bzw. Verbraucher von Bytes. Allgemeine Methoden, die von jeder beliebigen Quelle lesen können, akzeptieren ein Stromargument zur Bezeichnung der Quelle. Allgemeine Methoden zum Schreiben akzeptieren einen Strom zur Zielbestimmung. Filter haben zwei Stromargumente. Sie lesen vom ersten, verarbeiten die Daten und Schreiben ins zweite. Zum Senden/Empfangen verschiedener Datentypen gibt es die Schnittstellen DataInput und DataOutput. Sie legen Methoden zum Senden/Empfangen anderer Java-Datentypen fest. Mit Hilfe der Schnittstellen ObjectInput und ObjectOutput lassen sich ganze Objekte über einen Strom senden. Alle Methoden, die sich mit Ein- und Ausgabeoperationen beschäftigen, werden in der Regel mit throws IOException abgesichert. Diese Subklasse von Exception enthält alle potentiellen I/O-Fehler, die bei der Verwendung von Datenströmen auftreten können. Die I/O-Exceptions können direkt mit einem try-catch-Block aufgefangen oder an übergeordnete Methoden weitergegeben werden. Der Begriff Strom kommt aus dem Betriebssystem Unix und bezieht sich auf „Pipes“. Eine Pipe ist ein nicht interprtierbarer Strom von Bytes, der zur Kommunikation zwischen Programmen (bzw. „gegabelten Kopien“ eines Programms) oder zum Lesen und Schreiben von verschiedenen Geräten und Dateien benutzt wird. Ein Strom ist ein Kommunikationspfad zwischen der Quelle und dem Ziel eines Informationsblocks. 142 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. 143 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. 141 200 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 201 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“144 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 144 Rückgabe „-1“ bedeutet zum Unterschied von C nicht, daß ein Fehler aufgetreten ist. 202 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 203 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 204 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: 205 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 IOException145 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) 145 UTF (Unicode Transmission Format) ist ein spezielles Format zum Kodieren von 16-Bit-Unicode-Werten 206 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() 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 207 Programmieren in Java 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. 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 Dateiposition bestimmt werden. 208 Programmieren in Java 7.5 Spezielle nützliche Ströme Die LineInputStream-Klasse Die SequenceInputStream-Klasse Die PushBackInputStream-Klasse Die StreamTokenizer-Klasse. Tokenstrom146. 146 Sie zerlegt Token: einzelne Wörter bzw. Satzzeichen 209 einen Zeichenstrom in einen 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. 210 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); // 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. FileReader 211 Programmieren in Java 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. 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. LineNumerReader 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. 212 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 213 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. 214 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) 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(); 215 Programmieren in Java // 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"); } // 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) 216 Programmieren in Java { System.out.println("IO Exception"); } } } 217