Begleitmaterial zum Einführungspraktikum Java - November 2001 - Universität Essen Schützenbahn 70 45127 Essen Dozent: Dipl.-Wirt. Inform. Frank Lützenkirchen [email protected] 2 HRZ Einführungspraktikum Java 2 3 Themenübersicht I. - II. - Einführung und erste Schritte mit Java Entstehung von Java, Java heute, Eigenschaften von Java Realisierung der Plattformunabhängigkeit mit der Java Virtual Machine Verwendung des JDK, Applets und Applikationen Literatur, Software und Informationsquellen im Internet Grundlegende Sprachelemente Kommentare und Anweisungen, Variablen, Datentypen und Literale Operatoren, Arrays, Fallunterscheidungen und Schleifen Methoden schreiben, Arbeiten mit Zeichen und Zeichenketten III . Objektorientierte Programmierung mit Java - Klassen, Objekte, ihre Verwendung und ihr Lebenszyklus Klassen und Konstruktoren, Zugriffsschutz durch Modifier Klassen- und Instanzvariablen, Klassen- und Instanzmethoden Polymorphismus und Overloading, Vererbung und Overriding Abstrakte Klassen, Interfaces und Packages nutzen IV. Nützliches und Wissenswertes - V. - Die Klasse Objekt und Wrapper-Klassen, Casting und Konvertierung von Datentypen Datums- und Zeitwerte, Mathematische und Zufallsfunktionen Laufzeitfehler erzeugen und abfangen mit Exceptions Aus der Werkzeugsammlung: StringTokenizer, Vector, Stack und Hashtable Die Klassen System, Runtime und Process Applets, Grafik- und Soundausgabe, Ereignisse Applets schreiben und einsetzen, Appletmethoden Zeichnen mit der Klasse Graphics, Bilder, Farben und Zeichensätze einsetzen AudioClips abspielen und MediaTracker einsetzen Das Delegation Event Modell am Beispiel von Maus- und Tastaturereignissen VI. Graphische Benutzeroberflächen mit dem AWT - Das Abstract Windowing Toolkit und seine Funktionsweise Bausteine graphischer Oberflächen: Container, Komponenten und Layout-Manager Fenster und Menüs VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes - Arbeiten mit Ein- und Ausgabeströmen, Zugriff auf Dateien und Verzeichnisse Netzwerkfunktionalität von Java Einblicke in fortgeschrittene Techniken: Datenbankzugriffe mit JDBC, Grafische Oberflächen mit Swing, Java Servlets HRZ Einführungspraktikum Java 3 4 HRZ Einführungspraktikum Java 4 5 I. Einführung und erste Schritte mit Java HRZ Einführungspraktikum Java 5 I. Einführung und erste Schritte mit Java 6 1. Entstehung von Java 1991 Gründung des "Green Project" bei Sun Microsystem: Schaffung einer einfachen, objektorientierten Programmiersprache für Steuerungselektronik im Consumerbereich. Plattformunabhängigkeit und Stabilität spielen eine große Rolle. Das Resultat heißt "Oak". Sun gründet 1992 First Person, Inc. zur Vermarktung. Es entsteht ein tragbares Gerät mit dem Namen "*7" (Star seven), das über einen animierten "Assistenten" mit dem Namen "Duke" verfügt und eine Art Universalfernbedienung für Haushaltselektronik realisiert. Viele Verhandlungen, z. B. im Bereich Set-Top-Boxen, scheitern. Oak findet noch keinen Markt. Im Frühjahr 1994 wird First Person, Inc. aufgelöst. Im Juni 1993 wird die erste brauchbare Version eines Browsers für das World Wide Web mit dem Namen Mosaic bekannt. Das WWW verbreitet sich von nun an rasant. Ende 1994 kommt bei Sun die Idee auf, Oak als freie Programmiersprache für das World Wide Web umzuarbeiten. Ein einfacher Browser namens "WebRunner" kann im Herbst 1994 bereits kleine Oak-Applets ausführen. Anfang 1995 wird der Name "Java" gewählt, nach einer im Silicon Valley auch unter den Sun-Programmierern beliebte starke Bohnenkaffee-Sorte. Es wird viel Arbeit in die Bereiche Netzwerkfähigkeit und Sicherheit von Java gesteckt: Aus Oak wurde Java. Im März 1995 entdeckt Netscape-Gründer Marc Andreessen das Java-Projekt. Im Dezember 1995 ist die Netscape-Version 2.0 mit integrierter Java-Unterstützung verfügbar. Java Applets werden als erste Möglichkeit zur Realisierung dynamischer WebSeiten schnell populär. Im Frühjahr 1996 ist die erste Beta-Version des Java Development Kit (JDK) 1.0 im Internet frei erhältlich. Die kostenlose Java-Entwicklungsumgebung trägt erheblich zur schnellen Verbreitung der Programmiersprache bei. 2. Java heute Inzwischen ist Java als ernstzunehmende objektorientierte Programmiersprache etabliert und für nahezu alle gängigen Plattformen in aktuellen Versionen verfügbar. Auf der Client-Seite (Java Applets) hat Java in letzter Zeit deutlich an Bedeutung verloren. Wesentlicher Grund hierfür ist die mangelnde Browser-Unterstützung im Netscape Navigator und Internet-Explorer, die beide die aktuellsten Java-Versionen nur mit einem separat zu installierenden Java Plug-in von Sun unterstützen. Auf der Server-Seite hat sich Java deutlich stärker etabliert. Java Servlets und Java Server Pages lösen CGI-Anwendungen ab. Mächtige Klassenbibliotheken unterstützen aktuelle Standards, insbesondere Datenbankzugriffe mit JDBC, XML, LDAP, IMAP / POP3. Mit der Java 2 Micro Edition kehrt Java zu den ursprünglichen Anwendungsgebieten von Oak zurück. Es gibt Java-Umgebungen für Palm-Handhelds und ein erstes Javafähiges Handy von Motorola. Java verbreitet sich ebenfalls stark als System zur Programmierung von Chip-Karten (JavaCard). HRZ Einführungspraktikum Java 6 I. Einführung und erste Schritte mit Java 7 3. Die Plattformunabhängigkeit von Java Java-Quellcode besteht aus Dateien mit der Endung "*.java". Dieser Quellcode wird durch einen Compiler übersetzt. Es entsteht kein ausführbares Programm, sondern Java Bytecode mit der Endung "*.class". Der Bytecode stellt eine plattformunabhängige, vorkompilierte Form des Java-Codes dar. Er besteht aus einfachen, mit Maschinensprache vergleichbaren Anweisungen für einen virtuellen Computer, der Java Virtual Machine. Die Funktionsweise der Java Virtual Machine und der Bytecode-Anweisungen, die sie zu verarbeiten hat, ist genau definiert. Anbieter, die ihr System Java-fähig machen wollen, müssen eine entsprechende Virtual Machine für die jeweilige Plattform implementieren. Java Bytecode wird durch die Java Virtual Machine ausgeführt. Diese kann als eigenständiges Programm ausgelegt sein (z. B. java im JDK), Bestandteil eines WebBrowsers sein (z. B. integriert im Netscape Navigator) oder in Zukunft als Hardware in Form eines Prozessors z. B. in einer Waschmaschine existieren. Die Virtual Machine ist nicht nur ein virtueller Prozessor, sondern bietet auch eine virtuelle Betriebssystem-Umgebung nach, z. B. um Zugriffe auf Dateien und Verzeichnisse, das Fenstersystem der Plattform oder das Netzwerk zu ermöglichen. Die Virtual Machine kontrolliert die Zugriffe auf diese Ressourcen, was differenzierte Sicherheitsmechanismen ermöglicht. Der Sprachumfang des Java Bytecodes ist sehr gering. Er wird ergänzt durch eine Sammlung von Java-Klassenbibliotheken (Standard-Packages), die wiederum größtenteils in Java geschrieben sind und auf dem jeweiligen Laufzeitsystem der Virtual Machine vorausgesetzt werden können. Java-Quellcode *.java Compiler Java-Bytecode *.class Java Runtime Environment (java) Java Virtual Machine HRZ Einführungspraktikum Java Javaenabled Web-Browser ( Netscape, IE ) Java Virtual Machine 7 I. Einführung und erste Schritte mit Java 8 4. Wichtige Eigenschaften von Java Java ist streng genommen eine interpretierte Sprache. Übersetzte Java-Programme werden von einem Interpreter, der Java Virtual Machine, ausgeführt. Neuere JVMs verbessern allerdings inzwischen mit integriertem Just-in-Time-Compiler und dem HotSpot-Konzept die Performance gegenüber reinen Java-Interpretern erheblich. Java ist plattformunabhängig. Java-Anwendungen, die in 100 % reinem Java geschrieben sind, laufen ohne jegliche Veränderung auf allen Systemen, für die eine JavaLaufzeitumgebung zur Verfügung steht. Java ist durchgehend objektorientiert. Java-Code besteht aus Klassen, in denen Variablen und Methoden definiert sind. Objektorientierte Konzepte wie Vererbung, Overloading und Overriding werden unterstützt. Java ist relativ einfach zu erlernen. Java lehnt sich stark an C++ an, besitzt jedoch keine Zeiger und keine Mehrfachvererbung. Java ist recht übersichtlich strukturiert und hat eine einfache Syntax, daher weniger schwerverständlicher Code als z.B. in Perl oder C. Da Java neu entworfen wurde, gibt es weniger Altlasten als z. B.in C++. Java arbeitet in einer virtuellen Betriebssystemumgebung. Plattformunabhängige JavaAnwendungen laufen in einer plattformabhängigen Java Virtual Machine, die die Aufrufe des Java-Programms auf Aufrufe des jeweiligen Systems abbildet, z .B. den Zugriff auf Dateien. Java bietet integrierte Sicherheitsmechanismen. Die Java Virtual Machine überwacht zur Laufzeit den Zugriff auf das System und verhindert z. B., daß aus dem Netz geladene Java-Applets lokale Daten lesen können. Java ist robust. Laufzeitfehler können hierarchisch auf verschiedenen Ebenen vom Programmierer abgefangen, weitergeleitet oder selbst erzeugt werden. Java ist streng typisiert. Die Verwendung der Datentypen wird zur Übersetzungszeit genau überprüft. Der Wertebereich von Datentypen ist plattformübergreifend einheitlich definiert. Java ist multithreadingfähig. Java bietet Konstrukte, um in einer Anwendung verschiedene Teile parallel ausführen zu können. Java besitzt keine Mechanismen, um direkt auf Hardware, z. B. eine bestimmte Speicheradresse, zuzugreifen. Java verwaltet eigenständig den Arbeitsspeicher. Eine Garbage Collection wird von Java regelmäßig automatisch angestoßen, um den Speicher von nicht mehr benutzten Objekten zu säubern. Der Programmierer muß seine Objekte nicht mehr explizit freigeben. Java bietet eine integrierte Netzwerkunterstützung mit Klassen für Datenströme, URLs, Sockets etc zur Kommunikation auf der Basis der TCP/IP und UDP-Protokollfamilie. Java-Anwendungen sind klein. Das Java-Laufzeitsystem belegt nur einige hundert KByte. Da die Java-Bibliotheken auf dem Client vorausgesetzt werden können, müssen sie nicht zum übersetzten Programm dazugebunden werden. Die übertragene Datenmenge für ein einfaches Java-Applet ist daher vergleichsweise gering. Das Java 2 Software Development Kit und inzwischen auch integrierte graphische Entwicklungsumgebungen sind frei verfügbar, jedoch liegen das Copyright und alle Rechte an Java bei Sun Microsystems. Java ist also kein offener Standard, sondern ein herstellerabhängiges Produkt! HRZ Einführungspraktikum Java 8 I. Einführung und erste Schritte mit Java 9 5. Literatur und Software zu Java 5.1 Empfehlenswerte Literatur für Einsteiger Guido Krüger: Go To Java 2, Handbuch der Java-Programmierung Addison-Wesley, ISBN: 382731710X, DM 99,90 Gebundene Ausgabe - 1207 Seiten Bruce Eckel: Thinking in Java, Prentice Hall; ISBN: 0130273635, ca. DM 100,-, Taschenbuch kartoniert - 1128 Seiten 5.2 Literatur online und zum Download als HTML oder PDF Sun: The Java Tutorial - A practical guide for programmers: http://www.javasoft.com/docs/books/tutorial/ Gosling, James; Joy, Bill; Stelle, Guy: The Java Language Specification: http://www.javasoft.com/docs/books/jls/index.html Eckel, Bruce: Thinking in Java: http://www.bruceeckel.com/ Krüger, Guido: Go To Java 2: http://www.javabuch.de/ 5.3 Java-Entwicklungsumgebungen http://www.javasoft.com/j2se/ Das Java 2 Software Development Kit, Standard Edition, Version 1.3.1 ist eine reine Kommandozeilen-Umgebung zum Compilieren und Ausführen von Java-Anwendungen und wird von Sun zum freien Download für Windows, Linux und Solaris angeboten. http://www.jcreator.com/ JCreator ist eine graphische Entwicklungsumgebung (IDE) für Java, von der es auch eine kostenlose Freeware-Version (JCreator LE 2.0) gibt. Der Funktionsumfang beschränkt sich auf das Wesentliche, dafür ist das Programm aber auch schneller als Forte und JBuilder. http://www.borland.com/jbuilder/personal/ Die graphische Java-Entwicklungsumgebung Borland JBuilder 5.0 steht in der Personal Edition für Windows, Solaris und Linux frei zur Verfügung. http://www.sun.com/forte/ffj/ Die Java-IDE Forte for Java 3.0 ist eine weitere Alternative, die direkt von Sun angeboten wird. Die freie Version heisst hier Community Edition und steht ebensfalls für Windows, Solaris, Linux und als reine Java-Implementierung zur Verfügung. HRZ Einführungspraktikum Java 9 I. Einführung und erste Schritte mit Java 10 6. Das Java 2 SDK und Java-Versionen 6.1 JDK (Java Development Kit) bzw. SDK (Software Development Kit) Frei verfügbares Entwicklungssystem für Java, beinhaltet den Java-Compiler javac, den Java-Interpreter java zum Ausführen von Java-Applikationen, den appletviewer zum Ausführen von Java-Applets und weitere Tools Beinhaltet die Standard-Klassenbibliothek von Java inkl. Dokumentation und Quellcode Keine graphische Entwickler-Oberfläche, nur Kommandozeilen-Tools Windows-, Solaris- und Linux-Versionen werden von Sun entwickelt, weitere Versionen durch Drittanbieter portiert, z. B. von Apple für Mac, von IBM für AIX usw. Funktion eines Referenzsystems für den aktuellen Sprachumfang 6.2 Java-Versionen und Editionen Der aktuelle Sprachumfang von Java wird durch die Versionen des Java Development Kit (JDK/SDK) festgelegt. Die dritte Stelle der Versionsnummern dokumentiert den Stand von Bugfixes etc., aber keine Erweiterungen des Standard-Sprachumfangs: Java 1 und JDK 1.0.2: Wird von allen Java-fähigen Web-Browsern und Plattformen unterstützt, aus praktischer Sicht derzeit oft noch die wichtigste Java-Version für die Entwicklung von Java-Applets, die auf möglichst allen Browsern laufen sollen. Java 1.1 und JDK 1.1.8: Wird in vollem Umfang nur von den neuesten Browsern unterstützt, z .B. Netscape Communicator 4.7. Wesentliche Verbesserungen in Funktionalität (z. B. JDBC), Geschwindigkeit und Stabilität. Umbenennung des JDK nach der Version 1.1.8 in "Java 2 Software Development Kit" (Java 2 SDK). Der aktuelle Sprachumfang wird auf der Browser-Seite leider kaum unterstützt, auf der Server-Seite sind aktuelle Java-Versionen verfügbar. Netscape 6 ist derzeit der einzige Browser, der von der Installation an eine Java 2 Umgebung bieten kann. Andere Browser erfordern die separate Installation des Java Plug-in und spezielle HTML-Seiten zum Starten eines Applets mit diesem Plug-in. Aktuellste Versionen: Java 2 SDK 1.2.2 und 1.3.1. Version 1.4 befindet sich derzeit im Beta-Stadium. Alle aktuellen Java-Entwicklungsumgebungen und Standard-Bibliotheken sind Teil der "Java 2 Platform". In dieser werden nun verschiedene Editionen unterschieden: Java 2 Standard Edition: Entwicklung von Applets und Applikationen für Desktop-Clients / PC Gegenstand dieses Kurses Java 2 Enterprise Edition: Erweiterung der Standard Edition um zusätzliche Bibliotheken und standardisierte Schnittstellen, z. B. XML, Servlets/JSP, EMail, Verzeichnisdienste, Enterprise Java Beans Einsatz in Application Servern Java 2 Micro Edition: Eingeschränkter Sprachumfang, spezielle Entwicklungsumgebungen Spezialisierung auf Kleingeräte wie PDAs (Palm), Handys etc. HRZ Einführungspraktikum Java 10 I. Einführung und erste Schritte mit Java 11 7. Erste Schritte mit dem JDK bzw. SDK Welche Java-Version ist installiert? java –version Wie compiliert man eine Quellcode-Datei? javac HelloWorld.java Es entsteht eine Datei HelloWorld.class. Wie führt man eine Java Application aus? java HelloWorld Wie führt man ein Java Applet aus? ... durch Laden der HTML-Seite im Browser, die das Java Applet einbindet und aufruft! Die Umgebungsvariable CLASSPATH enthält eine Liste von Verzeichnissen, in denen die Java Laufzeitumgebung nach Class-Dateien sucht. Werden class-Dateien nicht gefunden, überprüfen Sie diese Variable und erweitern Sie sie gegebenenfalls, z. B. für Unix export CLASSPATH=.:~hrz120/myprojects/classes:$CLASSPATH Zusammengehörende Java-Class-Dateien einer Klassenbibliothek sind oft in einer JarDatei (Java Archive) zusammengefasst. Java Laufzeitumgebungen können Jar- und ZipDateien wie Verzeichnisse nach Class-Dateien durchsuchen. Jar- und Zip-Dateien werden wie Verzeichnisse im CLASSPATH eingetragen: export CLASSPATH=$CLASSPATH:/x/y/swingall.jar:/x/y/other.jar Jar-Dateien können mit dem Kommandozeilen-Tool jar bearbeitet werden, dessen Aufrufsyntax vom tar-Kommando übernommen ist: jar –vtf demo.jar zeigt den Inhalt einer Jar-Datei jar –xvf demo.jar packt den Inhalt einer Jar-Datei aus. jar –cvf demo.jar * erzeugt eine Jar-Datei mit allen Class-Dateien aus dem aktuellen Verzeichnis. HRZ Einführungspraktikum Java 11 I. Einführung und erste Schritte mit Java 12 8. Applets und Applications Java-Programme können in zwei verschiedenen Formen auftreten: Applications sind Java-Anwendungen, die wie gewöhnliche Anwendungen (d. h. ohne einen WWW-Browser) gestartet werden. Dazu benötigt man ein JavaLaufzeitsystem, das die Java-Applikation dann ausführt. Applets sind Java-Anwendungen, die in eine HTML-Seite eingebunden sind und mit dieser aus dem WWW heruntergeladen werden. Java-fähige Webbrowser führen dann das Applet aus und stellen es als Teil der WWW-Seite dar, sobald alle Bestandteile heruntergeladen wurden. Java Beans und Java Servlets sind weitere Erscheinungsformen von Java-Anwendungen. 8.1 Eine einfache Java-Application: HelloWorld.java public class HelloWorld { public static void main( String[] args ) { System.out.println( "Hello World!" ); } } Beachte: Java ist durchgehend objektorientiert, d. h. in Java gibt es auf der obersten Ebene nur Klassen. Auch eine Java-Anwendung besteht also mindestens aus einer Klasse. Mehr zu Klassen und Objektorientierung später. Der Dateiname muß exakt mit dem Klassennamen übereinstimmen (Hier also "HelloWorld.java" ). Jede (public-) Java-Klasse muß in einer eigenen Datei angegeben werden. Java-Applikationen müssen eine Methode main besitzen, die automatisch vom Laufzeitsystem aufgerufen wird, wenn man ihm diese Java-Klasse zur Ausführung übergibt. Mehr zu Methoden später. Die Methode main muß public und static deklariert sein String[] args ist ein Feld (Array) aus Zeichenketten, das eventuell mit dem Aufruf übergebenen Parameter aus der Kommandozeile aufnimmt. System.out ist ein Objekt, das den Standard-Ausgabestrom des Systems darstellt (also z. B. den Bildschirm oder die Java Console im Netscape Navigator) System.out besitzt eine Methode println, mit der man Zeichenketten auf dem Bildschirm ausgeben kann. Hier wird also die Zeichenkette "Hello World!" ausgegeben. Groß- und Kleinschreibung werden in Java genauestens unterschieden! Leerzeichen, Tabulatoren, Leerzeilen usw. werden von Java wie einfache Leerzeichen behandelt und können zur Strukturierung des Codes verwendet werden. HRZ Einführungspraktikum Java 12 I. Einführung und erste Schritte mit Java 13 8.2 Ein einfaches Java-Applet: HelloWorldApplet.java import java.applet.Applet; import java.awt.Graphics; public class HelloWorldApplet extends Applet { public void paint( Graphics g ) { g.drawString( "Hello World!", 20, 25 ); } } Hinweise: Der Dateiname dieses Quellcodes muß "HelloWorldApplet.java" lauten Mit der Anweisung import werden dem Java-System Sprachbestandteile bekanntgemacht, die nicht standardmäßig geladen sind. Hier werden die Klassen Applet und Graphics aus den Paketen java.applet und java.awt geladen. Standardmäßig sind nur die Klassen aus dem Paket java.lang bekannt. Mehr zu Packages später! extends Applet teilt Java mit, daß diese Klasse von der Klasse Applet abgeleitet ist, daß es sich also um ein Applet handelt. Mehr zu Vererbung später! Wenn Java-Applets, die in eine Webseite eingebunden wurden, geladen sind, ruft der Browser automatisch die Methode paint auf, um den Bereich des Applets auf dieser Seite zu "bemalen". Das System übergibt der Methode paint ein Objekt vom Typ Graphics, das die "Malfläche" repräsentiert. Das Objekt Graphics besitzt eine Methode drawString, um an den angegebenen Koordinaten eine Zeichenkette auszugeben. HRZ Einführungspraktikum Java 13 I. Einführung und erste Schritte mit Java 14 8.3 Aufruf eines Applets Java-Applets werden in eine HTML-Seite eingebunden und dann automatisch als Teil der Seite durch einen Java-fähigen Browser gestartet und angezeigt: Datei applet.html: <HTML> <HEAD> <TITLE> Ein einfaches Beispiel </TITLE> </HEAD> <BODY> <H1> Auf dieser Seite sehen Sie das Applet HelloWorld </H1> Etwas Text.<P> <APPLET CODE = "HelloWorldApplet.class" WIDTH = 100 HEIGHT = 50 > </APPLET> Noch etwas Text.<P> </BODY> </HTML> Beachte: Der Name der HTML-Seite ist unabhängig vom Namen des Applets. Eine HTML-Seite kann mehrere Applets gleichzeitig beinhalten. Das Applet wird durch das HTML-Tag <APPLET> aufgerufen und erscheint dann an dieser Stelle auf der Seite. Mehr dazu später! Die CODE-Anweisung teilt dem Webbrowser mit, welche Klassendatei er aus dem Netz laden und ausführen muß. Beachte, daß auch hier Groß- und Kleinschreibung relevant ist! Die Angaben WIDTH und HEIGHT geben den Platz an, der dem Applet auf der HTMLSeite eingeräumt wird. HRZ Einführungspraktikum Java 14 15 II. Grundlegende Sprachelemente HRZ Einführungspraktikum Java 15 II. Grundlegende Sprachelemente 16 1. Anweisungen und Kommentare 1.1 Anweisungen und Anweisungsblöcke Anweisungen enden immer mit einem Semikolon! Anweisungsblöcke werden mit geschweiften Klammern gebildet. Anweisungsblöcke können dort verwendet werden, wo sonst nur eine einzelne Anweisung von Java erwartet würde. 1.2 Kommentare Nach "//" wird der Rest der Zeile als Kommentar betrachtet Kommentarblöcke können durch "/* ... */" gebildet werden Javadoc-Kommentare sind durch "/** .. */" gekennzeichnet. Javadoc ist ein System, das aus einem derart kommentierten Quellcode automatisch eine QuelltextDokumentation im HTML-Format erzeugen kann und ist Bestandteil des JDK von Sun. Beispiel: a = b + c; // Hier wird was gerechnet /* Das Leben ist eher kurz als lang und wir stehen alle mittenmang. */ if ( a < 10 ) { d = a * 2; e = b + 1; } Beachte: Vorsicht beim Schachteln von Kommentaren! /* Das Leben ist eher kurz als lang /* Zeit ist ohnehin relativ, wie Einstein schon festgestellt hat. */ // An dieser Stelle haben wir jetzt ein Problem! und wir stehen alle mittenmang. */ HRZ Einführungspraktikum Java 16 II. Grundlegende Sprachelemente 17 2. Variablen und primitive Datentypen 2.1 Variablennamen Variablennamen dürfen mit einem Buchstaben, Unterstrich "_" oder Dollarzeichen "$" beginnen. Groß- und Kleinschreibung wird wie überall in Java auch bei Variablennamen unterschieden. Variablennamen dürfen sehr lang werden. Konvention ist, den ersten Buchstaben immer klein zu schreiben und folgende Worte mit einem Großbuchstaben zu beginnen, ohne "$" und "_" zu verwenden: boolean dieseVariableSpeichertObDiePersonVerheiratetIst; 2.2 Primitive Datentypen Datentyp boolean byte short int long float double char Wertebereich true oder false -128 bis 127 -32768 bis 32767 -231 bis +231 -263 bis +263 –1 32 Bit Gleitpunktzahl 64 Bit Gleitpunktzahl Ein einzelnes Zeichen Beachte: boolean ist ein eigener Datentyp mit den Literalen true und false! Die Wertebereiche sind auch auf verschiedenen Plattformen bei Java immer identisch! Zeichenketten (Strings) sind in Java Objekte, kein primitiver Datentyp! Eigene Datentypen können nicht definiert werden, jedoch eigene Objekte! 2.3 Variablen deklarieren int float boolean long schuhgroesse; meineGroesse; maennlich; meinWunschGehalt, deinWunschGehalt; Beachte: Variablendeklarationen können an beliebiger Stelle innerhalb einer Methode stehen, solange sie vor ihrer ersten Verwendung deklariert werden. Java kennt keine globalen Variablen. Java unterscheidet Klassen-, Instanz- und lokale Variablen (mehr dazu später). HRZ Einführungspraktikum Java 17 II. Grundlegende Sprachelemente 18 2.4 Variablen initialisieren: int schuhgroesse = 43; float meineGroesse = 1.76; boolean maennlich = true; int x, y, z = 5; int x = 5, y = 5, z = 5; // x und y nicht initialisiert! // so ist's recht! Beachte: Instanz- und Klassenvariablen bekommen default-Initialisierungen (0, false, null, '\0' ). Lokale Variablen sollten immer initialisiert werden. 2.5 Werte an Variablen zuweisen int int y = y = x = x = 5; y; 6; x + 3; y = 10; Beachte: Eine Zuweisung ist auch ein Ausdruck, dessen Wert der zugewiesenen Wert ist (siehe letzte Anweisung oben)! Dies ist eine potentielle Fehlerquelle, manchmal aber auch nützlich. 2.6 Variablen ausgeben (Beispiel) public class Test { public static void main( String[] args ) { int x = 5; double d = 10.357; System.out.println( x ); System.out.println( "x = " + x + " und d = " + d ); } } HRZ Einführungspraktikum Java 18 II. Grundlegende Sprachelemente 19 3. Literale int long int int x y z a = = = = 4; 4L; 0xC000; // hexadezimale Angabe mit 0x 0777; // oktale Angabe mit führender Null double d = 1.23; float f = 1.23F; double e = 1E-4; // 0.0001 boolean wahr = true; boolean falsch = false; char c = 'c'; char newline = '\n'; Zeichenliteral \n \t \b \r \f \\ \' \" \777 \xC000 \u1234 Beschreibung newline tab backspace return formfeed \ ' " Oktale Angabe Hexadezimale Angabe Unicode-Zeichensatz String undefinierterString = null; String einTitel = "Programmieren mit Java"; String leererString = ""; String text = "In M\u00fnchen sagt man \"O’zapft is!\""; \\ In München sagt man "O’zapft is!" Beachte: Zeichen und Zeichenketten werden in Java im 16-Bit-Unicode-Format verarbeitet und gespeichert. Das bedeutet, daß Sie z. B. auch hebräische oder andere Schriftzeichen bei der Ein- und Ausgabe von Datenströmen verwenden können. Die Verwendung von Unicode-Zeichen erfolgt mit \u (siehe oben), gefolgt vom Zahlencode des entsprechenden Zeichens. Ob die korrekte Ausgabe aller Unicode-Zeichen jedoch möglich ist, hängt vom Laufzeitsystem und den dort verfügbaren Zeichensätzen ab. Umlaut Unicode Ä \u00c4 ä \u00e4 HRZ Einführungspraktikum Java Ö \u00d6 ö \u00f6 Ü \u00dc ü \u00fc ß \00df 19 II. Grundlegende Sprachelemente 20 4. Operatoren 4.1 Arithmetische Operatoren x y z i = = = = 5 + 2; 3 – 2; 4 * 8 / 2; 47 % 11 // Modulo-Rest der Division: i = 3 4.2 Inkrementieren und Dekrementieren x = 1; x++; y = x++; // x = 1 // x = 2 // y = 2, x = 3 Postfix-Notation x = 1; y = ++x; // x = 1 // y = 2, x = 2 Prefix-Notation Analog arbeitet der Operator --. 4.3 Bitweise Operatoren & | ^ ~ << >> >>> AND OR XOR Komplementbildung Linksschieben Rechtsschieben Rechtsschieben, mit Null füllen 4.4 Zuweisungsoperatoren a = b = c = d = 5; a b c d e += -= *= /= %= 2; 3; 4; 5; 2; // // // // // a b c d e = = = = = a b c d e // a = ( b = ( c = ( d = 5 ) ) ); + – * / % 2; 3; 4; 5; 2; Analog arbeiten die Operatoren <<=, >>=, &=, |=, ^= HRZ Einführungspraktikum Java 20 II. Grundlegende Sprachelemente 21 4.5 Logische Operatoren & | ^ ! && || AND OR XOR NOT Beachte: Bei Verwendung der Form if ( AusdruckA & AusdruckB ) werden beide Ausdrücke ausgewertet und dann das Ergebnis gebildet. Bei Verwendung der Form if ( AusdruckA && AusdruckB ) wird zunächst AusdruckA ausgewertet. Wenn daraus allein schon das Endergebnis feststeht, wird AusdruckB nicht weiter beachtet. 4.6 Vergleichsoperatoren == != < > <= >= gleich ungleich kleiner als größer als kleiner oder gleich größer oder gleich Beachte: Zuweisungen mit =, Vergleiche mit == Häufiger Fehler, da Zuweisung auch als Wert interpretiert wird: boolean result = false; if( result = true ) ... // ist immer wahr -> result wird true zugewiesen, dann erst Vergleich! Abhilfe: if( true == result ) ... (Compiler würde meckern) oder if( result )... Bei Vergleich zwischen Objekten wird auf Identität geprüft, nicht auf inhaltliche Gleichheit: GregorianCalendar a = new GregorianCalendar( 2001, 5, 17 ); GregorianCalendar b = new GregorianCalendar( 2001, 5, 17 ); GregorianCalendar c = a; if ( a == b ) ... // false if ( a.equals( b ) )... // true ( Inhaltlicher Vergleich ) if ( a == c ) ... // true ( Identität ) HRZ Einführungspraktikum Java 21 II. Grundlegende Sprachelemente 22 5. Arrays Ein Feld (Array) speichert eine Liste von Werten Die Werte müssen alle vom gleichen Typ sein Der Typ ist ein beliebiger Objekttyp oder einfacher Datentyp Auf die Elemente kann über die Nummer (Index) des Elementes zugegriffen werden Die Größe des Arrays ist nicht variabel. 5.1 Deklarieren von Arrays int[] preise; String[] artikelNamen; 5.2 Erzeugen von Arrays preise = new int[ 10 ]; String[] artikelNamen = new String[ 10 ]; Beachte: Der Wert in den eckigen Klammern gibt die Anzahl der Elemente, nicht den höchsten Indexwert an! 5.3 Initialisieren von Arrays: String[] namen = { "Claudia", "Simone", "Eva" }; Hinweis: Array-Elemente werden default mit ( 0, false, '\0', null ) initialisiert. 5.4 Zugriff auf Array-Elemente: String[] namen = { "Claudia", "Simone", "Eva" }; namen[ 2 ] = "Marlene"; System.out.println( namen[ 0 ] ); // gibt "Claudia" aus Beachte: Das erste Element hat den Index Null! 5.5 Größe von Arrays feststellen: Die Größe eines Arrays läßt sich mit length prüfen: String[] namen = { "Claudia", "Simone", "Eva" }; int drei = namen.length; HRZ Einführungspraktikum Java 22 II. Grundlegende Sprachelemente 23 Beachte: Häufiger Schreibfehler: length ist keine Methode! String String[] int i1 = int i2 = a1 = "The quick brown fox jumps over the lazy dog."; a2 = { "Claudia", "Simone", "Eva" }; a1.length(); a2.length; // !!! 5.6 Mehrdimensionale Arrays In Java gibt es keine mehrdimensionalen Arrays, aber Arrays aus Arrays sind möglich: int[] werte1 = new int[ 2, 3 ] // geht nicht! int[][] werte2 = new int[ 2 ][ 3 ] // geht! werte[ 2 ][ 1 ] = 4711; 5.7 Arrays kopieren Arrays lassen sich mit der Methode arraycopy aus dem Paket System kopieren: System.arraycopy( Quelle, QStart, Ziel, ZStart, Anzahl ); Beispiel: int[] a = { 1, 2, 3, 4, 5 }; int[] b = new int[ 5 ]; b[ 0 ] = 9; b[ 1 ] = 10; System.arraycopy( a, 1, b, 2, 3 ); // b = { 9, 10, 2, 3, 4 } Beachte: In Java können Methoden auch beliebige Arrays von Objekten übergeben werden oder sie können solche erzeugen: public String[] getNames() { ... } Die Klasse Vector aus dem Paket java.util ermöglicht Arrays variabler Größe, bei denen die Elemente auch verschiedenen Typs sein können. Arrays werden wie Objekte als Referenz übergeben! Dies ist unbedingt zu beachten, wenn man z. B. ein Array bei einem Methodenaufruf übergibt: Manipulationen des Arrays innerhalb der Methode wirken sich dann auch auf die aufrufende Umgebung aus! HRZ Einführungspraktikum Java 23 II. Grundlegende Sprachelemente 24 6. Fallunterscheidungen 6.1 if-Anweisung if( Bedingung ) AnweisungA; else AnweisungB; if(Bedingung ) { AnweisungenA } else { AnweisungenB } Wenn die Bedingung erfüllt ist, wird der erste Anweisungsblock ausgeführt, sonst der zweite. Für Bedingung kann ein Vergleich, eine boolesche Variable oder ein Methodenaufruf verwendet werden, der einen boolean-Wert zurückgibt. Beachte: Auch wenn der if-Teil nur aus einer Anweisung besteht, kommt vor dem else-Teil ein Semikolon! Beispiel: if ( x < 5 ) System.out.println( "X ist kleiner als fünf" ); if ( y > 4 ) { a = y * 3 + 7; b = y + 2 - a; } else b = y + 1; Hinweis: Betrachte folgenden Fall: if ( x < 5 ) if ( x > 2 ) System.out.println( "2 < x < 5" ); else System.out.println( "x <= 2" ); Dieser Fall wird in der Literatur als "Dangling Else" bezeichnet. Java ordnet hier ein else immer dem letzten if zu. Ein abweichendes Verhalten läßt sich durch entsprechende Klammerung erreichen: if ( x < 5 ) { if ( x > 2 ) System.out.println( "2 < x < 5" ); } else System.out.println( "x >= 5" ); HRZ Einführungspraktikum Java 24 II. Grundlegende Sprachelemente 25 6.2 Bedingungsoperator ( Bedingung ? WertA : WertB ) Wenn die Bedingung erfüllt ist, hat dieser Ausdruck den WertA, sonst den WertB. Beispiel: int x = 5; int y = 8; int maximum = ( x > y ? x : y ); // maximum ist jetzt 8 Tip für später: Dieser Operator kann z. B. eine "NullPointerException" (Laufzeitfehler) verhindern: System.out.println( ( name == null ? "" : name ).length() ); 6.3 switch-Anweisung switch ( Ausdruck ) { case Wert1: Anweisung1; break; case Wert2: Anweisung2; break; default: Anweisung3; } if ( Ausdruck == Wert1 ) Anweisung1; else if ( Ausdruck == Wert2 ) Anweisung2; else Anweisung3; Die switch-Anweisung verhindert "else-if-Orgien" und das Wiederholen des Ausdrucks Der default-Teil wird ausgeführt, wenn keine der case-Fälle zutrifft. Er kann auch wegfallen. Beachte: Ausdruck darf nur ein primitiver Datentyp sein, dessen Casting nach int möglich ist, also z. B. long, int, char etc., nicht aber ein String! Ohne die Anweisung break; werden die folgenden Blöcke ebenfalls ausgeführt, bis zum nächsten break; (Potentielle Fehlerquelle). HRZ Einführungspraktikum Java 25 II. Grundlegende Sprachelemente 26 Beispiel: public class Test { public static void main( String[] args ) { char x = args[ 0 ].charAt( 0 ); // ersten Buschstaben des ersten Parameters ermitteln switch ( x ) { case 'a': case 'b': System.out.println("Das Zeichen ist ein a oder b"); break; case 'c': System.out.println("Das Zeichen ist ein c"); case 'd': System.out.println("Das Zeichen ist ein c oder d"); break; default: System.out.println("Kein a, b, c oder d"); } } } Aufruf: java Test c Ausgabe: Das Zeichen ist ein c Das Zeichen ist ein c oder d HRZ Einführungspraktikum Java 26 II. Grundlegende Sprachelemente 27 7. Schleifen 7.1 for-Schleifen for( Initialisierung; Bedingung; Inkrement ) { Anweisungen } Die Anweisung Initialisierung wird vor dem ersten Eintritt in den Schleifenblock ausgeführt. Die Anweisung Inkrement wird nach jedem Durchlauf des Schleifenblock ausgeführt. Vor jedem Durchlauf wird die Bedingung geprüft und, falls diese erfüllt ist, ein nochmaliger Durchlauf gestartet. Beispiel: for( int i = 0; i < 3; i++ ) { System.out.println( "Die Variable i hat den Wert " + i ); } Hinweis: In der Anweisung Initialisierung deklarierte Variablen sind nur lokal im Schleifenblock bekannt. Der Schleifenblock kann bei allen Schleifenarten auch nur aus einer einzelnen Anweisung ohne geschweifte Klammern bestehen Alle Elemente der Schleife können leer sein, wenn man sie nicht benötigt, also z. B. for( ; Bedingung; ) { Anweisungen } for( Initialisierung; Bedingung; Inkrement ); Beachte: Ein häufiger Fehler ist ein versehentliches Semikolon hinter der Klammer: for ( i = 0; i < 10; i++ ); System.out.println( "Oh, oh, bin nicht in der Schleife!" ); HRZ Einführungspraktikum Java 27 II. Grundlegende Sprachelemente 28 7.2 while-Schleifen while( Bedingung ) { Anweisungen } Vor jedem Schleifendurchlauf wird die Bedingung geprüft, auch vor dem ersten Eintritt in die Schleife Beispiel: int x = 20, y = 30; while ( Math.abs( x - y ) < 100 ) // Solange Differenz < 100 { System.out.println( "x=" + x + ", y=" + y ); x -= 10; y += 5; } 7.3 do-while-Schleifen do { Anweisungen } while( Bedingung ); Die Schleife wird mindestens einmal durchlaufen. Nach jedem Durchlauf wird die Bedingung geprüft und ggf. die Schleife verlassen. Beispiel: int x = 0; do { x = x * 2 + 1; System.out.println( x ); } while( x < 100 ); HRZ Einführungspraktikum Java 28 II. Grundlegende Sprachelemente 29 7.4 Schleifen unterbrechen Die Anweisung break; verläßt die gesamte Schleife und setzt die Programmausführung hinter der Schleife fort ( bei verschachtelten Schleifen wird also die nächsthöhere Ebene angesprungen) Die Anweisung continue; beendet nur den aktuellen Schleifendurchlauf und setzt die Schleife mit einem neuen Durchlauf fort. Beispiel: for( int i = 1; i < 100; i++ ) { if( i % 3 == 0 ) continue; System.out.println( "i ist: " + i ); System.out.println( "i zum Quadrat ist:" + i * i ); } 7.5 Schleifen benennen Schleifen können durch ein Label gefolgt von einem Doppelpunkt benannt werden: raushier: for( int i = 1; i < 10; i++ ) { while( irgendwasIstWahr ) { tuWas(); if ( etwasSchrecklichesIstPassiert ) break raushier; } } Beachte: Wo wir gerade bei Labeln sind: goto ist in Java ein reserviertes Schlüsselwort, wird aber nicht verwendet! Übung: Schreiben Sie ein Java-Programm, das mit Hilfe von zwei verschachtelten Schleifen alle Zahlenpaare größer als Null ausgibt, deren Summe kleiner als fünf ist. HRZ Einführungspraktikum Java 29 II. Grundlegende Sprachelemente 30 8. Methoden Methoden entsprechen den Prozeduren und Funktionen in anderen Programmiersprachen. 8.1 Methoden aufrufen Methoden werden über ihren Namen aufgerufen. Methoden gehören in der Regel zu einem Objekt. Der Name des Objektes wird dem Namen der Methode vorangestellt, gefolgt von einem Punkt. Beim Aufruf kann Ihnen eine Liste von Parametern mitgegeben werden, die aus Variablen oder Objekten besteht. Methoden können einen Variablenwert, ein Array, ein Objekt oder gar nichts zurückgeben. Beispiele: System.out.println( "Hallo Welt!" ); ... String name = "Willi Winzig"; int anzahlZeichen = name.length(); System.out.println( name.toUpperCase() ); System.out.println( "John Doe".length() ); // Ausgabe = 8 8.2 Methoden definieren Beispiele: public class MethodenTest { public static void sagHallo() { System.out.println( "Hallo Welt!" ); } public static void gruss( String name ) { System.out.println( "Hallo " + name + "!" ); } public static double mittelwert( double x, double y ) { double mw = ( x + y ) / 2; return mw; } public static void main( String[] args ) { sagHallo(); gruss( "Peter" ); double x = 7.8; double y = mittelwert( 3.5, x ); System.out.println( mittelwert( 10.2, 9.45 ) ); } } HRZ Einführungspraktikum Java 30 II. Grundlegende Sprachelemente 31 Allgemeine Syntax einer Methodendeklaration: Modifier Rückgabetyp Methodenname( Parameterliste ) { Anweisungen } Rückgabetyp legt den Datentyp fest, der nach Beendigung der Methode von dieser zurückgegeben wird. Methoden, die nichts zurückgeben, bekommen den Datentyp void. Methoden können auch Arrays oder Objekte zurückgeben Methoden ohne Rückgabe (void) heißen in anderen Programmiersprachen oft "Prozeduren". Methoden mit Rückgabe heißen in anderen Programmiersprachen oft "Funktionen". Parameterliste ist die Liste der Parameter, die der Methode mit übergeben werden, jeweils mit der Angabe des Datentyps und einem Variablennamen, getrennt durch Kommata. Die Parameterliste kann leer sein, die Klammern sind unbedingt anzugeben! Mit der Anweisung return wird die Methode verlassen und ein Wert zurückgegeben. Bei void-Methoden wird die return-Anweisung weggelassen. Methoden können in Java 1.0 nur innerhalb einer Klasse und außerhalb jeder anderen Methode deklariert werden. Ab Java 1.1 ist es mit "Inner Classes" möglich, in gewisser Weise Klassen und Methoden innerhalb anderer Klassen und Methoden zu deklarieren (mehr dazu später). 8.3 Parameterübergabe an Methoden, Rückgabe von Werten Bei einfachen Datentypen (int, double, boolean usw.) wird stets nur der Wert, nicht die Variable an sich übergeben (call by value). Bei Objekten und Arrays wird eine Referenz auf das Objekt, also an sich das Objekt selbst unter einem neuen Namen, übergeben (call by reference). Gleiches gilt für einen eventuell von der Methode zurückgegebenen return-Wert. Beispiel: public class Verdoppeln { public static int doppelt( int zahl ) { zahl = zahl * 2; // zahl ist hier eine lokale Variable! return zahl; } public static void main( String[] args ) { int zahl = 10; System.out.println( zahl ); System.out.println( doppelt( zahl ) ); System.out.println( zahl ); } // 10 // 20 // 10 } HRZ Einführungspraktikum Java 31 II. Grundlegende Sprachelemente 32 9. Arbeiten mit Zeichen und Zeichenketten 9.1 Die Klasse String Strings erzeugen String String char[] String // ... a = s = c = b = und new String(); "Mein Name ist Hase."; { 'T', 'i', 'n', 'a' }; new String( c ); weitere Konstruktoren Länge eines Strings String s = "Mein Name ist Hase."; int l = s.length(); // l = 19 Suchen in Strings String s = "Mein Name ist in Namibia bekannt."; int i = s.indexOf( "Nam" ); // i = 5 int j = s.indexOf( "Hund" ); // i = -1 int k = s.lastIndexOf( "Nam" ); // k = 17 int k = s.indexOf( "Nam", 10 ) // k = 17, ab 10. Zeichen Teile von Strings String char String String s c w y = = = = "Mein Name ist in Namibia s.charAt( 0 ); // c s.substring( 5, 9 ); // w s.substring( 17 ); // w bekannt."; = 'M' = "Name", Zeichen 5 – 8 = "Namibia bekannt." Veränderte Ausgabe von Strings String s = "Drei Chinesen mit dem Kontrabaß"; String t = s.replace('e','i').replace('o','i').replace('a','i'); // t = "Drii Chinisin mit dim Kintribiß" String c = "Monica loves Bill"; String d = c.toUpperCase(); // "MONICA LOVES BILL" String u = c.toLowerCase(); // "monica loves bill" Beachte: Instanzen von String sind nicht veränderlich, d. h. obige Methoden geben einen entsprechenden neuen String zurück, statt den alten zu verändern (Strings sind Objekte, nicht vergessen)! HRZ Einführungspraktikum Java 32 II. Grundlegende Sprachelemente 33 Vergleichen von Strings String a = "Inflationsgefahr"; String b = "Inflation"+a.charAt(9)+"Gefahr".toLowerCase(); String c = "infLATionsGefaHr"; if if if if if ( ( ( ( ( a == b ) ... // a.equals( b ) ) ... // a.equalsIgnoreCase( c ) ) ... // a.startsWith( "Inflation" ) ) ... // a.endsWith( "Ende" ) ) ... // false !!! true true true false String x = "Verona Feldbusch"; String y = "Morgens Aronal, abends Elmex" // Werbeeinnahmen! boolean b = x.regionMatches( 2, y, 9, 4 ); // "rona" -> true boolean ignoreCase = true; b = x.regionMatches( ignoreCase, 2, "ARONAL", 1, 4 ); // true String a1 = "Ameisenbär"; String a2 = "Braunbär"; int c = a1.compareTo( a2 ); // c < 0 // = 0 falls a1.equals( a2 ) // < 0 falls a1 lexikographisch vor a2 // > 0 falls a1 lexikographisch nach a2 Einfaches Verketten von Strings String String String String s t u v = = = = "Eins"; "Zwei"; s + t; s.concat( t ); Beachte: Viele Zeichenketten sollten besser mit Hilfe der Klasse StringBuffer verkettet werden!! length() liefert die Anzahl Zeichen, nicht die letzte Indexposition im String! substring( von, bis ) liefert den Teilstring bis vor dem Zeichen an der Position bis! replace() kann nur komplett alle Vorkommen eines Zeichens ersetzen, aber keine Teilstrings durch andere ersetzen! indexOf() etc. liefern bei erfolgloser Suche den Wert -1 Hinweis: Die Klasse String ist für statische Zeichenketten gedacht. StringBuffer realisieren dynamische, änderbare Zeichenketten. HRZ Einführungspraktikum Java 33 II. Grundlegende Sprachelemente 34 9.2 Die Klasse StringBuffer StringBuffer erzeugen StringBuffer st = new StringBuffer(); StringBuffer st = new StringBuffer( "Ein Text" ); Variablenwerte an den StringBuffer anhängen st.append( Variable ); Die Methode append existiert in Varianten für boolean, char, double, float, int, long und String Variablenwerte in den StringBuffer einfügen st.insert( Position, Variable ); Die Methode insert existiert in Varianten für boolean, char, double, float, int, long und String und fügt den Wert an der Stelle Position in den StringBuffer ein. Länge eines StringBuffers StringBuffer st = new StringBuffer( "Ein kurzer Text" ); int len = st.length(); // len = 15 st.setLength( len – 10 ); // st = "Ein k" Auf einzelne Zeichen zugreifen StringBuffer st = new StringBuffer( "Ein kurzer Text" ); char c = st.charAt( 1 ); // c = 'i' st.setCharAt( 13, 's' ); // "Ein kurzer Test" StringBuffer-Inhalt umdrehen: StringBuffer st = new StringBuffer( "Frank" ); st.reverse() System.out.println( st.toString() ); // Ausgabe "knarF" StringBuffer in String umwandeln String s = st.toString(); HRZ Einführungspraktikum Java 34 II. Grundlegende Sprachelemente 35 9.3 Die Klasse Character Die Klasse Character ist eine sogenannte Wrapper-Klasse für den einfachen Datentyp char (mehr zu Wrapper-Klassen später). Sie besitzt einige nützliche Methoden, um mit einzelnen Zeichen (Datentyp char) zu arbeiten. Zeichentyp bestimmen char c = 'a'; boolean b1 = Character.isDigit( c ); // true, falls c eine Ziffer ist boolean b2 = Character.isLetter( c ); // true, falls c ein Buchstabe ist boolean b3 = Character.isLetterOrDigit( c ); // ... boolean b4 = Character.isSpace( c ); // true, falls c Leerzeichen, \t, \n, \f, \r boolean b5 = Character.isJavaLetter( c ); // true, falls isLetter( c ) oder '$' oder '_' boolean b6 = Character.isJavaLetterOrDigit( c ); // ... boolean b7 = Character.isLowerCase( c ); // true, falls c ein Kleinbuchstabe ist boolean b8 = Character.isUpperCase( c ); // true, falls c ein Großbuchstabe ist Zeichen umwandeln char c char d char e char f String s = = = = = 'a'; 'B'; Character.toLowerCase( d ); // e = 'b' Character.toUpperCase( c ); // f = 'A' Character.toString( c ); // s = "a" HRZ Einführungspraktikum Java 35 II. Grundlegende Sprachelemente 36 HRZ Einführungspraktikum Java 36 37 III. Objektorientierte Programmierung mit Java HRZ Einführungspraktikum Java 37 III. Objektorientierte Programmierung mit Java 38 1. Objekte und Klassen Objekt besitzt Variablen (Attribute), die den Zustand des Objektes beschreiben. besitzt Methoden, über die auf das Objekt zugegriffen wird, die mit den Attributen arbeiten und die das Verhalten des Objektes festlegen. dadurch Verbindung der Daten und der Prozeduren, die mit diesen Daten arbeiten. Klasse Menge von gleichartigen Objekten Definition der Attribute und Methoden aller Objekte dieser Klasse Instanz (engl. instance, besser übersetzt als "Exemplar") Zur Laufzeit gebildetes konkretes Objekt aus einer Klasse Beispiele: Klasse: Attribute: Methoden: Instanz: Konto Kontostand, Kontoinhaber, Kontonummer einzahlen, abheben, überweisen Das Konto mit der Kontonr. 1234243 Klasse: Attribute: Methoden: Instanz: Point aus dem Paket java.awt x, y move, translate, ... Der Punkt ( 10, 45 ) Beachte: Jedes Java-Programm besteht aus einer Sammlung von Klassen, in denen Variablen und Methoden definiert werden Zur Laufzeit werden entsprechend den Anweisungen Instanzen von Objekten der Klassen erzeugt, die gegenseitig ihre Methoden aufrufen und ihre Variablen verwenden. Wichtige Ziele objektorientierter Programmierung: Anwendungssysteme als Abbildung der Objekte der realen Welt modellieren: Objekte Produkt, Kunde, Lieferant, Rechnung, Auftrag, ... Modellierung soll einfacher und korrekter werden Wiederverwendung von Code ermöglichen: Probleme nur einmal lösen! (Objekte in verschiedenen Anwendungen nutzen, durch Vererbung spezialisieren...) Auswirkungen von Änderungen lokal halten, auf Objekt beschränken (Pflegbarkeit des Codes erhöhen, Fehleranfälligkeit minimieren) HRZ Einführungspraktikum Java 38 III. Objektorientierte Programmierung mit Java 39 2. Lebenszyklus und Verwendung von Objekten 2.1 Objekte erzeugen Objekte werden durch Aufruf eines Konstruktors erzeugt. Dies geschieht durch Verwendung des Schlüsselwortes new. GregorianCalendar heute = new GregorianCalendar(); //parameterloser “Standardkonstruktor“ GregorianCalendar damals = new GregorianCalendar(1971,3,22); Point hier = new Point( 10, 45 ); Konstruktoren dienen dem Erzeugen von Objekten und legen dessen Anfangszustand fest. Konstruktoren werden durch new Klassenname( Parameter ) aufgerufen und geben ein Objekt dieser Klasse zurück. 2.2 Objekte verwenden Auf Variablen und Methoden eines Objektes wird durch Voranstellen des Objektnamens, gefolgt von einem Punkt, zugegriffen: import java.awt.Point; ... Point hier = new Point( 10, 45 ); hier.translate( 2, 0 ); // verschieben um 2 nach rechts System.out.println( "x = " + hier.x ); System.out.println( "y = " + hier.y ); hier.x = 8; hier.y = 20; // hier.move( 8, 20 ); täte dasselbe System.out.println( "x = " + hier.x ); System.out.println( "y = " + hier.y ); Objekte können als Parameter bei Methodenaufrufen übergeben werden und von Methoden als Rückgabewert zurückgegeben werden. Dabei wird nicht eine Kopie, sondern eine Referenz auf das Objekt übergeben, also quasi das Objekt selbst. 2.3 Objekte zerstören In Sprachen wie C++ ist es nötig, den von einem Objekt belegten Hauptspeicher wieder freizugeben, wenn das Objekt nicht mehr verwendet wird. Java entfernt selbständig Objekte aus dem Speicher, auf die keine Referenz mehr existiert (Garbage Collection). Dies geschieht verzögert in regelmäßigen Abständen, bei Speicherknappheit oder kann manuell durch Aufruf von System.gc(); angestoßen werden. HRZ Einführungspraktikum Java 39 III. Objektorientierte Programmierung mit Java 40 2.4 Packages verwenden Java-Klassen können zu Packages (Klassenbibliotheken) geordnet zusammengefaßt werden. Die wichtigsten Standard-Packages ab JDK 1.1 lauten: java.lang Klassen für grundlegende Sprachbestandteile java.math Klassen und Arithmetik für sehr große Zahlen java.net Netzwerkfunktionalität: URLs, Sockets, Datagramme java.io Ein-/Ausgabe über Datenströme und Dateien java.sql Datenbankzugriffe mit Java Database Connectivity (JDBC) java.text Internationalisierte, formatierte Datums- und Zahlenwerte java.applet Basis- und Hilfsklassen für Java Applets java.awt Abstract Windowing Toolkit: Fenster, Schaltflächen, ... java.awt.datatransfer Clipboard-Funktionen des AWT java.awt.event Ereignisverarbeitung java.rmi Remote Method Invocation (RMI) java.util Hilfsklassen und Utilities java.util.zip Klassen für ZIP / GZIP Datenströme Klassen aus dem Paket java.lang sind ohne import-Anweisung sofort verfügbar. Andere Klassen müssen im Kopf einer Klassendeklaration erst importiert werden: import java.awt.Graphics; import java.awt.*; // Einzelne Klasse, oder aber: // Alle Klassen aus java.awt public class HelloWorldApplet ... Die import-Anweisung macht lediglich dem System die Klassennamen des Paketes bekannt. Es werden aber nur die Klassen importiert, die zur Laufzeit auch wirklich verwendet werden, nicht automatisch alle Klassen des Paketes! Unterpakete müssen einzeln importiert werden, z. B. import java.awt.*; import java.awt.event.*; Alternativ kann der Name des Packages bei jeder Benutzung vorangestellt werden, z. B.: public void paint( java.awt.Graphics g ) { g.drawString( "Hello World", 20, 25 ); } Beachte: Mit dieser Form des Klassenaufrufs kann man das Problem umgehen, das entsteht, wenn man zwei Klassen mit dem gleichen Namen aus verschiedenen Paketen verwenden will. Es lassen sich eigene Packages erstellen (mehr dazu später). Es existiert eine Namenskonvention, die Konflikte bei Paketnamen vermeiden soll: Standardpakete aus dem Java-Sprachumfang beginnen mit java Java-Erweiterungen beginnen mit javax Pakete von Drittanbietern beginnen mit der Umkehrung des Domainnamens: www.ibm.com -> Paketnamen beginnen mit com.ibm HRZ Einführungspraktikum Java 40 III. Objektorientierte Programmierung mit Java 41 3. Eigene Klassen erstellen 3.1 Variablen, Konstruktoren und Methoden der Klasse deklarieren Jede (öffentliche) Klasse wird in einer eigenen Datei deklariert. Dateinamen und Klassennamen müssen sich entsprechen (class Konto in der Datei Konto.java). Die Reihenfolge der Deklarationen in der Klasse spielt keine Rolle: public class Klassenname { Variablendeklarationen ... Konstruktorendeklarationen... Methodendeklarationen ... in beliebiger Reihenfolge! } Beispiel: public class Konto { // Attribute: // Welche Werte beschreiben den Zustand dieses Kontos? public String kontoInhaber; public int kontoStand; public int kontoNr; // Konstruktor: wird aufgerufen bei "new Konto(...)" // Was geschieht beim Erzeugen eines neuen Kontos? public Konto( String derKontoInhaber, int dieKontoNr ) { kontoStand = 0; kontoInhaber = derKontoInhaber; kontoNr = dieKontoNr; } // Methoden: // Was kann ich mit einem Konto tun? public void einzahlen( int betrag ) { kontoStand += betrag; } public void abheben( int betrag ) { kontoStand -= betrag; } } Konstruktoren sind spezielle Methoden eines Objektes, die keinen Rückgabedatentyp und als Methodenname den Namen der Klasse besitzen. Sie werden aufgerufen, wenn man mit dem Schlüsselwort new eine neues Objekt erzeugt und dienen dazu, den Anfangszustand des neuen Objektes herzustellen. HRZ Einführungspraktikum Java 41 III. Objektorientierte Programmierung mit Java 42 3.2 Eigene Klassen verwenden: public class Bank { public static void main( String[] args ) { Konto meins = new Konto( "Willi Winzig", 1234823 ); Konto anderes; anderes = new Konto( "John Doe", 1234824 ); meins .einzahlen( anderes.einzahlen( meins .abheben ( meins .einzahlen( anderes.abheben ( 16500 23400 11200 13400 22300 ); // 165 DM ); ); ); ); System.out.println( meins .kontoStand ); System.out.println( anderes.kontoStand ); } } 3.3 Das Schlüsselwort this Ein Objekt kann auf seine eigenen Variablen und Methoden durch Voranstellen von this zugreifen. Insbesondere bei Namensgleichheit von Variablen des Objektes und lokalen Variablen der Methode ist dies sehr nützlich. Ein Objekt kann this auch verwenden, um eine Referenz auf sich selbst zu übergeben, z. B. mit return( this ); als Rückgabewert oder als Parameter in einem Methodenaufruf. Beispiel: Variante A: public Konto( String derKontoInhaber, int dieKontoNr ) { kontoStand = 0; kontoInhaber = derKontoInhaber; kontoNr = dieKontoNr; } Variante B (Verwendung von this): public Konto( String kontoInhaber, int kontoNr ) { this.kontoStand = 0; this.kontoInhaber = kontoInhaber; this.kontoNr = kontoNr; } HRZ Einführungspraktikum Java 42 III. Objektorientierte Programmierung mit Java 43 4. Zugriffsrechte steuern: Modifier public und private Das Problem: Konto meins = new Konto( "Willi Winzig", 1234823 ); meins.einzahlen ( 34500 ); meins.abheben ( 12000 ); meins.kontoStand = 100000; Mit Hilfe der Schlüsselwörter public und private kann man derartige Probleme vermeiden: public kennzeichnet, daß aus jeder beliebigen Klasse auf die so gekennzeichnete Variable oder Methode zugegriffen werden darf. private kennzeichnet, daß nur innerhalb der jeweiligen Klasse selbst auf die so gekennzeichnete Variable oder Methode zugegriffen werden darf. ... private int kontoStand; ... public void einzahlen( int betrag ) { if( betrag < 0 ) throw new IllegalArgumentException( "betrag < 0" ); kontoStand += betrag; } public void abheben( int betrag ) { if( ( betrag > kontoStand ) || ( betrag < 0 ) ) throw new IllegalArgumentException ( "betrag < 0 oder keine ausreichende Deckung" ); kontoStand -= betrag; } public int gibKontoStand() { return kontoStand; } Beachte: In den meisten Fällen sollten die Variablen eines Objektes aus oben aufgezeigten Gründen private deklariert werden und nur mit Zugriffsmethoden wie einzahlen() und abheben() kontrolliert veränderbar sein! Durch "Datenkapselung" und "Information Hiding" die Integrität und Fehlersicherheit der Objekte schützen! Das Objekt kann z. B. auch private-Methoden oder -Konstruktoren besitzen, die nur innerhalb des Objektes benutzt werden können und sollen. HRZ Einführungspraktikum Java 43 III. Objektorientierte Programmierung mit Java 44 5. Klassenvariablen, Klassenmethoden und Konstanten 5.1 Instanz- und Klassenvariablen Die bisher betrachteten Objektvariablen waren Instanzvariablen, d. h. jede Instanz der Klasse (also jedes Objekt) besitzt z. B. seine eigene Variable kontoStand. Durch Voranstellen des Schlüsselwortes static werden Klassenvariablen definiert. Klassenvariablen existieren nur einmal je Klasse. Alle Objekte dieser Klasse können auf deren Klassenvariablen zugreifen. Wird eine Klassenvariable geändert, "sehen" alle Objekte automatisch den geänderten Wert. Beispiel: public class Konto { public static int letzteKontoNr = 0; ... public Konto( String kontoInhaber ) { this.kontoStand = 0; this.kontoInhaber = kontoInhaber; this.kontoNr = ++letzteKontoNr; } public int gibKontoNr() { return kontoNr; } ... } Die Klassenvariable letzteKontoNr verwaltet die letzte vergebene Kontonummer. Der Konstruktor der Klasse erhöht jeweils diese Klassenvariable um eins. Die Klassenvariable muß sinnvollerweise initialisiert werden. Auf Klassenvariablen kann über den Namen der Klasse oder den Namen eines Objektes der Klasse zugegriffen werden: Konto meinKonto = new Konto( "Willi Winzig" ); System.out.println( meinKonto.letzteKontoNr ); System.out.println( Konto .letzteKontoNr ); Sinnvollerweise sollte obige Klassenvariable aber besser private deklariert sein! Beachte: Klassenvariablen sind nicht an die Existenz einer Instanz der Klasse gebunden, d. h. sie sind auch dann zugreifbar, wenn derzeit keine Instanz der Klasse existiert! HRZ Einführungspraktikum Java 44 III. Objektorientierte Programmierung mit Java 45 5.2 Konstanten deklarieren und verwenden Mit den Schlüsselwörtern final und static werden Konstanten deklariert, d.h. unveränderbare Variablen. Beispiel: public class Konto { public final static float EURO_DM_KURS = 1.95583F; ... } Verwendung in der Klasse Bank: public class Bank { public static void main( String[] args ) { int dm = 50; int euro = Math.round( dm / Konto.EURO_DM_KURS ); System.out.println( euro ); } } HRZ Einführungspraktikum Java 45 III. Objektorientierte Programmierung mit Java 46 5.3 Instanz- und Klassenmethoden Analog zu Klassenvariablen werden mit static Klassenmethoden definiert. Klassenmethoden sind nicht an die Existenz eines Objektes der Klasse gebunden, d. h. sie lassen sich auch dann aufrufen, wenn (noch) keine Instanz der Klasse existiert. Klassenmethoden dürfen daher nur auf Klassenvariablen oder andere Klassenmethoden der Klasse zugreifen, nicht auf Instanzvariablen oder -methoden. Klassenmethoden lassen sich durch Voranstellen des Klassennamens oder des Namens eines Objektes der Klasse aufrufen. Beispiel: public class Konto { public final static float EURO_DM_KURS = 1.95583F; public static int EUROnachDM( int betrag ) { return Math.round( EURO_DM_KURS * betrag ); } ... } Verwendung in der Klasse Bank: public class Bank { public static void main( String[] args ) { int euro = 30; int dm = Konto.EUROnachDM( euro ); System.out.println( dm ); } } Hinweise: Es können Klassen definiert werden, die nur aus Klassenvariablen und –methoden bestehen, d. h. es existieren keine Konstruktoren und keine Instanzen der Klasse. Mit Klassenvariablen und –methoden werden oft Hilfsklassen realisiert, z. B. die Klasse Math mit den (konstanten) Klassenvariablen PI und E und mathematischen Funktionen. Die Methode main einer Java-Anwendung ist eine Klassenmethode, da von der Klasse dieser Anwendung keine Instanz erzeugt wird, wenn die Anwendung gestartet wird. Übung: Erweitern Sie die Klasse um eine statische Methode zur Umrechnung von DM nach Euro. HRZ Einführungspraktikum Java 46 III. Objektorientierte Programmierung mit Java 47 6. Klasse Konto (Übersicht) public class Konto { public final static float EURO_DM_KURS = 1.95583F; public static int EUROnachDM( int betrag ) { return Math.round( EURO_DM_KURS * betrag ); } public static int DMnachEURO( int betrag ) { return Math.round( betrag / EURO_DM_KURS ); } private String kontoInhaber; private int kontoStand; private int kontoNr; public String gibKontoInhaber(){ return kontoInhaber; } public int gibKontoStand() { return kontoStand; } public int gibKontoNr() { return kontoNr; } private static int letzteKontoNr = 0; public Konto( String kontoInhaber ) { this.kontoStand = 0; this.kontoInhaber = kontoInhaber; this.kontoNr = ++letzteKontoNr; } public void einzahlen( int betrag ) { if( betrag < 0 ) throw new IllegalArgumentException( "betrag < 0" ); kontoStand += betrag; } public void abheben( int betrag ) { if( ( betrag > kontoStand ) || ( betrag < 0 ) ) throw new IllegalArgumentException ( "betrag < 0 oder keine ausreichende Deckung" ); kontoStand -= betrag; } } HRZ Einführungspraktikum Java 47 III. Objektorientierte Programmierung mit Java 48 7. Polymorphismus und Überladen 7.1 Methoden überladen In Java lassen sich Methoden definieren, die den gleichen Namen, aber eine unterschiedliche Parameterliste besitzen. Das Java-System unterscheidet beim Aufruf der Methode anhand der Datentypen der Parameterliste, welche der angegebenen Methoden auszuführen ist. Diese Eigenschaft objektorientierter Sprachen nennt man auch Überladen (Overloading) oder statischer Polymorphismus. Vergleiche auch z. B. die Definition der Methoden in der Klasse Math für verschiedene Datentypen int, long, float und double als Parameter! Beispiel: Zwei Varianten der Methode gibKontoStand(): public class Konto { ... public int gibKontoStand() { return kontoStand; } public int gibKontoStand( boolean inEuro ) { if( inEuro ) return DMnachEURO( kontoStand ); else return kontoStand; } ... } ... Konto k = new Konto( "Meier" ); k.einzahlen( 10000 ); int dm, euro; dm = k. gibKontoStand(); euro = k. gibKontoStand( true ); dm = k. gibKontoStand( false ); Beachte: In einigen Programmiersprachen ist das Überladen vordefinierter Operatoren ( z. B. "+") möglich. Dies ist in Java nicht möglich. Es ist nicht möglich, Methoden mit gleicher Parameterliste, aber verschiedenem Rückgabewert zu definieren! HRZ Einführungspraktikum Java 48 III. Objektorientierte Programmierung mit Java 49 Objektorientierte Denkweise: Wenn zwei Methoden das gleiche tun, sollten Sie auch gleich heißen. Vereinfache die Benutzung und ermögliche Wiederverwendung durch Flexibilität. Lasse nicht den Programmierer, sondern den Compiler oder das System zur Laufzeit entscheiden, welche Aktion die richtige ist, wenn bestimmte Parameter oder Objekte verwendet werden. 7.2 Konstruktoren überladen Oft werden auch Konstruktoren überladen. Die Klasse Konto kann z. B. alternative Konstruktoren anbieten: Beispiel: public class Konto { ... public Konto( String kontoInhaber ) { this.kontoStand = 0; this.kontoInhaber = kontoInhaber; this.kontoNr = ++letzteKontoNr; } public Konto( String kontoInhaber, int betrag ) { this.kontoStand = betrag; this.kontoInhaber = kontoInhaber; this.kontoNr = ++letzteKontoNr; } ... } Überladene Konstruktoren flexibilisieren die Verwendung der Klasse: Konto k1 = new Konto( "Meier" ); k1.einzahlen( 1000 ); Konto k2 = new Konto( "Schulze", 2000 ); HRZ Einführungspraktikum Java 49 III. Objektorientierte Programmierung mit Java 50 7.3 Überladene Konstruktoren aufrufen Mit this( Parameterliste ) kann ein überladener Konstruktor einen anderen Konstruktor der gleichen Klasse aufrufen. Die this()-Anweisung muß die erste Anweisung in diesem Konstruktor sein! Dieser Mechanismus wird oft verwendet, wenn ein Konstruktor einem bereits definierten entspricht, aber zusätzlich z. B. noch weitere Variablen setzt oder weitere Methoden aufruft. Objektorientierung: Greife auf Bestehendes zurück, löse ein Problem nur einmal. Beispiel: public class Konto { ... public Konto( String kontoInhaber ) { this( kontoInhaber, 0 ); } public Konto( String kontoInhaber, int betrag ) { this.kontoStand = betrag; this.kontoInhaber = kontoInhaber; this.kontoNr = ++letzteKontoNr; } ... } Hinweis zu Konstruktoren: Ein parameterloser Konstruktor wird Standardkonstruktor genannt. Er soll in der Regel eine Instanz erzeugen, bei der die Attribute auf bestimmte Standardwerte gesetzt werden. Andere Konstruktoren bieten dann die Möglichkeit, von den Standardvorgaben abzuweichen. Wird in einer Klasse kein Konstruktor deklariert, erzeugt das System automatisch einen Standardkonstruktor, der nichts tut (außer eine neue Instanz zurückzugeben). Will man dies verhindern, muß man mindestens einen Konstruktor deklarieren. Will man aber verhindern, daß überhaupt Instanzen der Klasse direkt erzeugt werden können, sollte man einen parameterlosen Standardkonstruktor deklarieren, der private ist. HRZ Einführungspraktikum Java 50 III. Objektorientierte Programmierung mit Java 51 8. Subklassen und Vererbung von Eigenschaften Objekte bzw. ihre Definition in Klassen können in einer Vererbungsbeziehung stehen, indem man eine neue Klasse von einer bereits existierenden Klasse ableitet. Eine Klasse bzw. ein Objekt erbt automatisch alle Eigenschaften (Variablen und Methoden) der Klasse, von der sie abgeleitet wird. Dies kann über mehrere Ebenen rekursiv erfolgen. Die erbende Klasse nennt man Subklasse, die vererbende Klasse Superklasse. Durch Vererbung müssen nur die Teile der Klasse neu angegeben werden, in denen sie sich von ihrer Superklasse unterscheidet. Beispiel: Superklasse: Konto Attribute: kontoStand, kontoNr, ... Methoden: einzahlen(), abheben(), ... Subklasse: Attribute: Methoden: Girokonto kontoStand, kontoNr, ... (geerbt), limit (neu) einzahlen(), abheben(), ... (geerbt), setzeLimit(), gibLimit() (neu) Durch das Schlüsselwort extends wird angezeigt, daß diese Klasse von einer anderen Klasse abgeleitet ist (d. h. von ihr erbt). In der Subklasse stehen automatisch alle Methoden und Variablen zur Verfügung, die in der Superklasse definiert wurden. Code-Beispiel (noch nicht vollständig!): public class Girokonto extends Konto { private int limit; public void setzeLimit( int limit ) { this.limit = limit; } public int gibLimit() { return limit; } } Objektorientierte Denkweise: Löse ein Problem möglichst generell und nur einmal in einer möglichst wiederverwendbaren Superklasse. Erweitere die Funktionalität durch Spezialisierung in Subklassen: Ein Girokonto ist eine besondere Art von Konto. Übung: Erzeugen Sie eine neue Klasse Girokonto, die von der Klasse Konto abgeleitet ist. HRZ Einführungspraktikum Java 51 III. Objektorientierte Programmierung mit Java 52 8.1 Konstruktoren und Vererbung Da Konstruktoren als Methodennamen den Namen ihrer Klasse tragen, können sie im obigen Sinne nicht vererbt werden. Konstruktoren werden für jede Klasse neu definiert und nicht wie andere Methoden vererbt. Ein Konstruktor der Subklasse kann (und sollte) jedoch einen Konstruktor der Superklasse über das Schlüsselwort super aufrufen, anstatt dessen Code in der Subklasse nochmals anzugeben. Fehlt die Angabe von super() in einem Konstruktor, wird beim Aufruf eines Konstruktors der Subklasse automatisch der sog. Standardkonstruktor (derjenige Konstruktor ohne Parameterliste: "super();" ) der Superklasse aufgerufen, falls er existiert. Beispiel: public class Girokonto extends Konto { ... public Girokonto( String kontoInhaber, int betrag, int limit ) { super( kontoInhaber, betrag ); setzeLimit( limit ); } ... } Übung: Ergänzen Sie die Klasse Girokonto um einen Konstruktor und schreiben Sie eine JavaAnwendung, die mit Konten und Girokonten arbeitet und Methoden dieser Klassen aufruft. Hinweis: In vielen objektorientierten Sprachen gibt es die Möglichkeit, Klassen zu erstellen, die von mehreren Klassen abgeleitet sind und die Eigenschaften aller dieser Klassen besitzen. Dies nennt man Mehrfachvererbung. Mehrfachvererbung ist in Java nicht möglich, da damit einige konzeptionelle Schwächen verbunden sind. Viele Probleme, bei denen man in anderen Sprachen Mehrfachvererbung verwenden würde, lassen sich in Java mit Hilfe von Interfaces lösen (mehr dazu später). Instanzen von Subklassen lassen sich wie Instanzen der Superklasse verwenden, z. B. bei der Übergabe als Parameter an eine Methode, die eine Instanz der Superklasse erwartet: Da ein Girokonto ein Konto ist, kann man eine Instanz von Girokonto überall dort verwenden, wo ein Konto erwartet wird: Konto irgendeins = new Girokonto( "Willi", 50000 ); HRZ Einführungspraktikum Java 52 III. Objektorientierte Programmierung mit Java 53 9. Überschreiben von Methoden und Konstruktoren 9.1 Variablenzugriffe und Methodenaufrufe bei Vererbung Hat man eine Instanz der Subklasse, kann auf alle Variablen zugegriffen und alle Methoden aufgerufen werden, die ihre Superklasse besitzt. Bei Vererbung über mehrere Ebenen wird der Aufruf einer Methode von unten nach oben so weit zur jeweiligen Superklasse durchgereicht, bis eine passende Methode in einer der Superklassen gefunden wird. 9.2 Überschreiben von Methoden Bei einem Methodenaufruf sucht Java zunächst in der jeweiligen Klasse nach einer Definition der Methode. Ist die Methode dort nicht definiert, wird die Anfrage nach oben in der Klassenhierarchie weitergereicht und die Superklasse dieser Klasse durchsucht. Auf diese Weise wird die Vererbung realisiert. Oft möchte man jedoch eine Methode, die bereits definiert war, neu definieren, um sie mit einer erweiterten Funktionalität zu versehen. Dies nennt man Überschreiben (Overriding). Die Methode muß dazu mit der gleichen Parameterliste wie in der Superklasse neu angegeben werden. Beispiel: Bei Girokonten darf das Konto bis zum Limit überzogen werden, daher muß die Methode abheben() überschrieben werden: public class Girokonto extends Konto { ... public void abheben( int betrag ) { if( betrag <= 0 ) throw new IllegalArgumentException ( "Betrag darf nicht <= 0 sein" ); if( betrag > kontoStand + limit ) throw new IllegalArgumentException ( "Sie sind leider zu arm" ); kontoStand -= betrag; } ... } Übung: Warum läßt sich das Beispiel mit der bisherigen Klasse Konto noch nicht übersetzen? HRZ Einführungspraktikum Java 53 III. Objektorientierte Programmierung mit Java 54 9.3 Die Zugriffsebene Protected Eine Subklasse kann nicht auf Methoden oder Variablen ihrer Superklasse zugreifen, die als private deklariert wurden, jedoch wie jede andere Klasse auf die public –Teile zugreifen. Methoden und Variablen, die (statt public oder private) als protected deklariert wurden, verhalten sich bezüglich ihrer Subklassen wie public, bezüglich aller anderen Klassen wie private deklariert. Beispiel: Damit die Methode abheben() in Girokonto direkt auf die Variable kontoStand der Klasse Konto zugreifen kann, muss diese als protected statt als private deklariert sein: public class Konto { protected String kontoInhaber; protected int kontoStand; protected int kontoNr; ... } 9.4 Überschriebene Methoden wieder aufrufen Innerhalb einer überschreibenden Methode kann über das Schlüsselwort "super." auf die Originalmethode zugegriffen werden: Beispiel: public class X { public void doSomething() { System.out.println( "Greetings from X" ); } } public class Y extends X { public void doSomething() { super.doSomething(); // Gibt "Greetings from X" aus System.out.println( "Greetings from Y" ); } } HRZ Einführungspraktikum Java 54 III. Objektorientierte Programmierung mit Java 55 9.5 Final-Methoden Methoden, die als final deklariert werden, können nicht von einer Subklasse überschrieben werden. Da der Compiler weiß, daß sich die Methode nicht mehr ändert, kann er ihren Code optimieren, so daß final-Methoden schneller ausgeführt werden. Aus diesem Grund sind viele Methoden der Java-Standardpakete final, was aber ihre Veränderung in eigenen Subklassen verhindert. Beispiel: Einige besonders zeitkritische Methoden der Klasse java.util.Vector sind final, damit sie schneller ausgeführt werden können. 9.6 Final-Klassen Klassen, die als final deklariert sind, dürfen nicht abgeleitet werden, d. h. von ihnen kann keine Unterklasse gebildet werden: public final class Name... Der Java-Compiler kann die Implementierung solcher Klassen optimieren, da er weiß, daß die Klasse nicht mehr verändert bzw. erweitert wird. Aus diesem Grunde sind einige JavaKlassen final. Beispiel: Die Klasse String ist leider final, da Zeichenkettenverarbeitung zeitaufwendig ist und die Java-Schöpfer durch die final-Deklaration die schnelle Implementierung von String durch die Laufzeitsysteme ermöglichen wollten. Man kann demnach nicht einfach eine Subklasse von String erstellen, die die Funktionalität erweitert, z. B. um eine Methode, die Teile von Strings durch andere ersetzen kann. HRZ Einführungspraktikum Java 55 III. Objektorientierte Programmierung mit Java 56 10. Abstrakte Klassen und Schnittstellen 10.1 Abstrakte Klassen Von Klassen, die als abstract deklariert werden, können keine Instanzen gebildet werden. Sie dienen lediglich als Vorlage für daraus abzuleitende Subklassen, die dann die in ihrer abstrakten Superklasse definierten Methoden und Variablen verwenden können. Abstrakte Klassen dürfen Methoden enthalten, die als abstract deklariert werden und von denen nur die Methodenunterschrift (d. h. Datentyp, Name, Parameterliste) angegeben wird. Nicht-abstrakte Subklassen dieser Klasse sind dann verpflichtet, diese Methoden zu implementieren. Subklassen abstrakter Klassen können wiederum abstrakt sein. Mit Hilfe abstrakter Klassen kann der Entwickler dafür sorgen, daß alle Unterklassen dieser Klasse gewisse Methoden zur Verfügung stellen müssen. Außerdem besitzen diese Subklassen damit eine gemeinsame Superklasse (die abstrakte Klasse). Obwohl diese abstrakt ist, läßt sich dieser Sachverhalt dann z. B. so ausnutzen, daß Objekte dieser abstrakten Klasse als Objekttypen in anderen Methoden anderer Klassen verwendet werden können. Da jede Subklasse auch dort verwendet werden kann, wo ein Objekt der Superklasse erwartet wird und sichergestellt ist, daß alle Subklassen die abstract definierten Methoden implementieren müssen, können so Methoden geschrieben werden, die automatisch mit allen Subklassen der abstrakten Klasse umgehen können. Beispiel aus der Java Klassenbibliothek: Vgl. die Definition der Klasse Number aus dem Paket java.lang, der abstrakten Superklasse der Wrapper-Klassen Integer, Long, Float und Double. Beispiel: public abstract class Konto // Abstrakte Klasse { ... // Abstrakte Methode public abstract int verfuegbarerBetrag(); public void abheben( int betrag ) { if( betrag < 0 ) throw new IllegalArgumentException( "Betrag < 0" ); if( betrag > verfuegbarerBetrag() ) throw new IllegalArgumentException( "Sorry, zu arm!" ); kontoStand -= betrag; } } (Fortsetzung folgende Seite) HRZ Einführungspraktikum Java 56 III. Objektorientierte Programmierung mit Java 57 Unterklassen von Konto implementieren die abstrakte Methode: public class Girokonto extends Konto { ... // Implementierung der abstrakten Methode: public int verfuegbarerBetrag() { return kontoStand + limit; } } public class Sparbuch extends Konto { ... // Implementierung der abstrakten Methode: public int verfuegbarerBetrag() { return kontoStand; } } Andere Klassen können nur Instanzen der nicht abstrakten Unterklassen bilden, können aber alle Methoden der Klasse Konto, auch auf die abstrakten, aufrufen: public class Bank { public static void main( String[] args ) { Sparbuch sb = new Sparbuch ( "Meier" ); Girokonto gk = new Girokonto( "Müller", 10000 ); bearbeiten( sb ); bearbeiten( gk ); } public static void bearbeiten( Konto k ) { int verfuegbar = k.verfuegbarerBetrag(); System.out.println( "Betrag: " + verfuegbar ); } } HRZ Einführungspraktikum Java 57 III. Objektorientierte Programmierung mit Java 58 10.2 Schnittstellen Oft ist die konkrete Klasse eines Objektes eigentlich unwichtig, wichtig ist nur, daß sie ein bestimmtes Verhalten zeigt, d. h. eine bestimmte Methode anbietet. Beim Sortieren eines Arrays ist es beispielsweise eigentlich egal, was in dem Array steht (Strings, Konten, Kunden, ...), solange zwei Array-Elemente sich vergleichen lassen, um die Sortierung durchführen zu können. Eine mögliche Lösung wäre, alle sortierbaren Klassen von einer abstrakten Superklasse Sortierbar abzuleiten, die eine Methode zum Vergleichen anbietet. Es wird so für alle Subklassen garantiert, daß sie eine Vergleichsmethode anbieten. Das ist jedoch keine gute Lösung: Kunde könnte z. B. eigentlich besser eine Subklasse von PersonenRolle sein, und Mehrfachvererbung ist in Java nicht erlaubt. Kunden und Konten sind auch nicht besonders ähnlich, die gemeinsame Superklasse erscheint sehr gekünstelt. Schnittstellen lösen dieses Problem. Eine Schnittstelle definiert eine Menge von Methoden, ohne deren Implementierung vorzugeben, ähnlich wie bei einer abstrakten Klasse. Beliebige Klassen können nun die Schnittstelle 'implementieren' und müssen dann die entsprechenden Methoden zur Verfügung stellen. Die Klassen und Schnittstellen müssen dann nicht mehr in einer Vererbungsbeziehung stehen. Man kann nun Programmcode schreiben, der mit beliebigen Klassen arbeitet, die eine bestimmte Schnittstelle implementieren, die Art der Klasse ist nun irrelevant. Das macht z. B. die Sortier-Funktion wiederverwendbar und sehr flexibel. Schnittstellen werden durch das Schlüsselwort interface (statt class) eingeleitet. Sie legen die Methoden fest, die alle Klassen anbieten müssen, die die Schnittstelle 'implementieren'. Beispiel: Die Schnittstelle Bewertbar definiert das Verhalten bewertbarer Objekte: public interface Bewertbar { public int gibWert(); } Beliebige Klassen implementieren die Schnittstelle: public abstract class Konto implements Bewertbar { ... public int gibWert() { return kontoStand; } } public class Auto implements Bewertbar { ... public int gibWert() { return kaufpreis * ( 20 – alterInJahren ) / 20; } } HRZ Einführungspraktikum Java 58 III. Objektorientierte Programmierung mit Java 59 Andere Klassen können gegen die Schnittstelle programmieren, ohne wissen zu müssen, welche konkreten Klassen zur Laufzeit diese Schnittstelle implementieren (vgl. unten die Methode berechneGesamtwert): public class Vermoegensverwaltung { public static void main( String[] args ) { Sparbuch sb = new Sparbuch( "Meier" ); sb.einzahlen( 30000 ); Auto porsche = new Auto(); Bewertbar[] dinge = new Bewertbar[ 2 ]; dinge[ 0 ] = sb; dinge[ 1 ] = porsche; int summe = berechneGesamtwert( dinge ); System.out.println( summe ); } public static int berechneGesamtwert( Bewertbar[] sachen ) { int summe = 0; for( int i = 0; i < sachen.length; i++ ) summe += sachen[ i ].gibWert(); return summe; } } HRZ Einführungspraktikum Java 59 III. Objektorientierte Programmierung mit Java 60 10.3 Beispiel: Sortieren eines Arrays: wiederverwendbare Implementierung Das Beispiel zeigt eine wiederverwendbare Implementierung einer Sortierfunktion für Arrays. Es sollen beliebige Arrays von Objekten beliebiger Klassen sortiert werden können. Vorausgesetzt wird nur, daß die Objekte eine Methode vergleiche() zur Verfügung stellen, anhand derer zwei Objekte miteinander verglichen werden können, um die korrekte Reihenfolge zu bestimmen. Die Methode vergleiche() soll je nachdem, welches Objekt als 'größer' betrachtet wird, eine Zahl größer, kleiner oder gleich null zurückgeben (vgl. die Methode compareTo() in der Klasse String, die analog arbeitet): Seien x und y zwei Instanzen der gleichen Klasse, dann soll der Aufruf x.vergleiche(y) einen int-Wert wie folgt zurückgeben: x.vergleiche( y ) x.vergleiche( y ) x.vergleiche( y ) ergibt einen Wert < 0 falls x < y ergibt einen Wert > 0 falls x > y ergibt 0 falls x = y Erstellen der Schnittstellendefinition in der Datei Vergleichbar.java: public interface Vergleichbar { public int vergleiche( Vergleichbar v ); } Die Klasse Konto implementiert die Schnittstelle Vergleichbar, d. h. sie stellt eine konkrete Implementierung für die in der Schnittstelle geforderte(n) Methode(n) zur Verfügung. Hier sollen Konto-Instanzen anhand ihres Kontostandes aufsteigend sortiert werden: public class Konto implements Vergleichbar { ... public int vergleiche( Vergleichbar v ) { Konto other = (Konto)v; return ( this.kontoStand - other.kontoStand ); } } Instanzen der Klasse Kunde sollen ebenfalls miteinander vergleichbar werden. Die Reihenfolge der Kunden soll anhand ihres Namens bestimmt werden (lexikographische Sortierung): HRZ Einführungspraktikum Java 60 III. Objektorientierte Programmierung mit Java 61 public class Kunde implements Vergleichbar { protected String name; // Der Name des Kunden public Kunde( String name ) { this.name = name; } public int vergleiche( Vergleichbar v ) { Kunde other = (Kunde)v; return ( this.name.compareTo( other.name ) ); } } Anwendungen können nun zu Sortierzwecken Kunden und Konten paarweise vergleichen und dabei einen einheitlichen Aufruf (die Methode der Schnittstelle) verwenden: public class Test { public static void main( String[] args ) { Konto ko1 = new Sparbuch( "Schmidt" ); Konto ko2 = new Sparbuch( "Kling" ); ko1.einzahlen( 20000 ); ko2.einzahlen( 30000 ); int vgl = ko1.vergleiche( ko2 ); System.out.println( vgl ); Vergleichbar ku1 = new Kunde( "Meier" ); Vergleichbar ku2 = new Kunde( "Müller" ); int vgl = ku1.vergleiche( ku2 ); System.out.println( vgl ); } } HRZ Einführungspraktikum Java 61 III. Objektorientierte Programmierung mit Java 62 Eine Klasse Sortierer, die Arrays von vergleichbaren Objekten sortiert, benutzt nur die Schnittstelle Vergleichbar, ohne etwas über die konkreten Klassen zu wissen, die diese Schnittstelle auf verschiedene Weise implementieren. Sie ist damit völlig unabhängig von einer bestimmten Klasse und wiederverwendbar. Die Klasse bietet eine Methode sortiere(), die ein Array von Instanzen einer die Schnittstelle Vergleichbar implementierenden Klasse mittels QuickSort-Algorithmus sortiert: public class Sortierer { public static void sortiere( Vergleichbar[] werte ) { quicksort( werte, 0, werte.length - 1 ); } protected static void quicksort( Vergleichbar[] werte, int lo0, int hi0 ) { if( hi0 > lo0 ) { int lo = lo0; int hi = hi0; int mid = ( lo0 + hi0 ) / 2; while( lo <= hi { while( ( lo < ( werte[ lo lo++; while( ( hi > ( werte[ hi hi--; ) hi0 ) && ].vergleiche( werte[ mid ] ) < 0 ) ) lo0 ) && ].vergleiche( werte[ mid ] ) > 0 ) ) if( lo <= hi ) { Vergleichbar temp = werte[ lo ]; werte[ lo ] = werte[ hi ]; werte[ hi ] = temp; lo++; hi--; } if( lo0 < hi ) quicksort( werte, lo0, hi ); if( lo < hi0 ) quicksort( werte, lo, hi0 ); } } } } Eine beliebige Anwendung kann nun Arrays von Kunden oder Konten sortieren: Kunde[] kunden = new Kunde[ 10 ]; Konto[] konten = new Konto[ 20 ]; ... // Arrays mit Kunden- und Konto-Instanzen füllen... Sortierer.sortiere( kunden ); Sortierer.sortiere( konten ); HRZ Einführungspraktikum Java 62 III. Objektorientierte Programmierung mit Java 63 Hinweise: Neue Klassen können später problemlos dem System hinzugefügt werden. Damit auch sie sortierbar sind, müssen sie nur ebenfalls das Interface Vergleichbar implementieren... Die Plattformunabhängigkeit von Java gründet sich an einigen Stellen auf Schnittstellen, insbes. im AWT (graphische Benutzeroberflächen in Java). Vgl. auch die Schnittstelle Enumeration aus dem Paket java.util und die Schnittstelle Runnable aus dem Paket java.lang! 10.4 Mehrfachvererbung und Implementierung mehrerer Schnittstellen Da Schnittstellen-Definitionen keine konkreten Implementierungen beinhalten, existieren hier keine Probleme bei Mehrfachvererbung, daher ist Mehrfachvererbung bei Schnittstellen erlaubt. public interface XundYundZ extends X, Y { public void doZ(); } Eine Klasse kann zwar nur eine Oberklasse besitzen, kann aber beliebig viele Schnittstellen implementieren: public class Girokonto extends Konto implements Bewertbar, Vergleichbar { ... } 10.5 Schnittstellen als Konstantensammlungen Schnittstellen werden auch oft verwendet, um so etwas wie 'globale Konstanten' zu definieren. Definiert man in einer Schnittstelle eine Konstante, so ist diese automatisch in jeder Klasse bekannt, die die Schnittstelle implementiert public interface FinanzKonstanten { public final static float EURO_DM_KURS = 1.95583F; } public class Konto implements FinanzKonstanten { public static int EUROnachDM( int betrag ) { return Math.round( EURO_DM_KURS * betrag ); } public static int DMnachEURO( int betrag ) { return Math.round( betrag / EURO_DM_KURS ); } ... } HRZ Einführungspraktikum Java 63 III. Objektorientierte Programmierung mit Java 64 11. Eigene Klassen zu Packages zusammenfassen 11.1 Packages definieren Mit Hilfe des Schlüsselwortes package kann man die Zugehörigkeit einer Klasse zu einem Package definieren. Damit lassen sich Klassen zu Bibliotheken zusammenfassen und übersichtlich verwalten und weitergeben. Namen von Packages werden per Konvention klein geschrieben. Die packageAnweisung muß die erste Anweisung im Quellcode sein: package finanzen; // hier kommen ggf. import-Anweisungen hin... public class Konto{ ... } package finanzen.wertpapiere; public class Aktie{ ... } Die Klassendateien und Quellcodedateien müssen sich dann in einem Unterverzeichnis befinden, das dem Namen des Package entspricht: Konto.java Aktie.java in in .\finanzen\Konto.java .\finanzen\wertpapiere\Aktie.java Die Packages lassen sich dann benutzen wie andere Packages auch: import finanzen.*; import finanzen.wertpapiere.*; public class Bank{ ... } Klassen die sich innerhalb des gleichen Paketes befinden, 'kennen' sich automatisch, es ist dann kein import nötig. Fehlt die Angabe einer Zugehörigkeit zu einem Package, wird das unbenannte 'Standardpaket' angenommen, das dem aktuellen Verzeichnis entspricht. Auch dann ist kein import nötig. HRZ Einführungspraktikum Java 64 III. Objektorientierte Programmierung mit Java 65 11.2 Zugriffsebenen und Packages Neben den Modifiern public, protected und private existiert noch eine vierte Zugriffsebene, die die Sichtbarkeit einer Methode oder Variable auf alle Klassen des gleichen Paketes beschränkt. Dazu wird jedoch nicht 'package' vor den Methodenoder Variablennamen geschrieben, sondern der Modifier weggelassen: public class xy { int gruppenschluessel = 4711; // kann nur aus Klassen dieses Packages benutzt werden void tuEtwasKritisches(); // darf nur aus Klassen dieses Packages aufgerufen werden } Auch den Zugriff auf ganze Klassen kann man entweder allen anderen Klassen erlauben public class x { ... } oder auf Klassen des gleichen Paketes beschränken. Nur diese können die Klasse dann benutzen: class y { ... } Klassen, die nicht public sind, müssen nicht in einer eigenen Quellcodedatei untergebracht sein, sie können sich eine Quellcodedatei mit anderen Klassen teilen. Nichtöffentliche Klassen mit Package-Zugriff ermöglichen es, die Komplexität einer Anwendung zu verbergen. Die import-Anweisung importiert nur public-Klassen, andere Klassen bleiben ihr verborgen. HRZ Einführungspraktikum Java 65 IV. Nützliches und Wissenswertes 66 HRZ Einführungspraktikum Java 66 IV. Nützliches und Wissenswertes 67 IV. Nützliches und Wissenswertes HRZ Einführungspraktikum Java 67 IV. Nützliches und Wissenswertes 68 1. Mathematische Funktionen Mathematische Funktionen werden durch die Klasse java.lang.Math abgedeckt. Da sie Teil des Paketes java.lang ist, ist sie standardmäßig bereits importiert. 1.1 Konstanten Klassenvariable Math.PI Math.E Wert als double ( 3.14159...) e als double (2.71828...) 1.2 Trigonometrische Funktionen Klassenmethode double Math.sin( double Radiant ) double Math.cos( double Radiant ) double Math.tan( double Radiant ) double Math.asin( double Radiant ) double Math.acos( double Radiant ) double Math.atan( double Radiant ) Funktion sinus cosinus tangens arcsinus arccosinus arctangens Beachte: Angabe als Radiant! Bei Angabe als Winkel diesen mit PI/180 multiplizieren! 1.3 Vergleiche Klassenmethode Math.max( Zahl1, Zahl2 ) Math.min( Zahl1, Zahl2 ) Funktion Max für int, long, float, double Min für int, long, float, double 1.4 Rundungsfunktionen Klassenmethode Math.round( Zahl ) double Math.rint( double Zahl ) double Math.floor( double Zahl ) double Math.ceil( double Zahl ) Math.abs( Zahl ) Math.IEEEremainder( Z1, Z2 ) HRZ Einführungspraktikum Java Funktion Auf- / Abrundung für float, double Ergebnis int bzw. long Auf- / Abrundung Nächstkleinere ganze Zahl Nächstgrößere ganze Zahl Absolutwert für int, long, float, double Rest der Division als double 68 IV. Nützliches und Wissenswertes 69 1.5 Exponentialfunktionen und Potenzen Klassenmethode double Math.pow( double d1, double d2) double Math.sqrt( double z ) double Math.exp( double z ) double Math.log( double z ) Funktion Potenz d1 hoch d2 Zweite Wurzel von z Potenz e hoch z Logarithmus naturalis von z 1.6 Zufallszahlen Klassenmethode double Math.random() Funktion Zufallszahl zwischen 0 und 1 Beachte: Zur Erzeugung von Zufallszahlenfolgen gibt es eine eigene Klasse Random im Paket java.util: import java.util.Random; ... Random r = new Random(); ... int i = r.nextInt(); // Nächste Zufallszahl long l = r.nextLong(); float f = r.nextFloat(); double d = r.nextDouble(); ... Random r1 = new Random(); // Systemzeit als Seed Random r2 = new Random( seed ); // seed ist vom Typ long ... r2.setSeed( seed ); // seed ist vom Typ long Beachte: Bei Verwendung identischer Seeds wird die gleiche Folge von Pseudo-Zufallszahlen erzeugt! 1.7 Große Zahlen Zum Arbeiten mit beliebig großen Zahlen stehen ab JDK 1.1 die Klassen BigDecimal und BigInteger aus dem Paket java.math zur Verfügung. Die Klassen bieten Methoden zum Rechnen mit solchen Zahlen an, die analog zu den mathematischen Standard-Operatoren der Sprache Java sind und diese für große Werte ersetzen. Außerdem sind alle relevanten Methoden aus der Klasse Math analog verfügbar. Weitere Details zu diesen Klassen finden sich in der API-Dokumentation! HRZ Einführungspraktikum Java 69 IV. Nützliches und Wissenswertes 70 2. Datums- und Zeitwerte 2.1 Einfache Zeitstempel Über die Funktion long jetzt = System.currentTimeMillis(); erhält man die seit Mitternacht am 1. Januar 1970, UTC vergangenen Millisekunden Darüber hinaus existieren die Klassen Date, Calendar und GregorianCalendar im Paket java.util, um mit Datumswerten zu arbeiten: 2.2 Die Klassen Date und Calendar Im JDK 1.0 diente die Klasse Date neben der Darstellung eines bestimmten Zeitpunktes auch dazu, diesen Zeitpunkt als ein bestimmtes Kalenderdatum in Tag / Monat / Jahr usw. zu interpretieren und zu verarbeiten. Diese Kalenderfunktionen waren nicht sehr gut auf internationale Anforderungen ausgelegt. Ab JDK 1.1 übernimmt die Klasse Calendar die Aufgabe, ein Datum innerhalb eines Kalendersystems darzustellen. Calendar ist eine abstrakte Klasse, derzeit einzig mitgelieferte konkrete Implementierung ist die Klasse GregorianCalendar. Jede Instanz von Calendar besitzt intern zwei Darstellungen eines Zeitpunktes: zum einen als Datum in Tag, Monat, Jahr usw. aufgeteilt und zum anderen als long-Wert (Anzahl Millisekunden seit 1.1.1970). Diese beiden Darstellungen sind auch getrennt manipulierbar und müssen synchron gehalten werden. 2.3 Datumswerte mit GregorianCalendar anlegen GregorianCalendar c; c c c c = = = = new new new new GregorianCalendar(); GregorianCalendar(1998,11,31 ); GregorianCalendar(1998,11,31,17,32); GregorianCalendar(1998,11,31,17,32,15); // // // // heute 31.12.1998 um 17:32 h und 15 Sek Beachte: Der Monat wird ab 0 gezählt ( Dezember = 11 )! Weitere Konstruktoren legen noch die Zeitzone, Sommer-/Winterzeitabweichung etc. fest. 2.4 Datumswerte vergleichen GregorianCalendar t1 = new GregorianCalendar( 1971, 3, 22 ); GregorianCalendar t2 = new GregorianCalendar( 1976, 9, 6 ); boolean b1 = t1.after ( t2 ); // false boolean b2 = t1.before( t2 ); // true boolean b3 = t1.equals( t2 ); // false HRZ Einführungspraktikum Java 70 IV. Nützliches und Wissenswertes 71 2.5 Datumsteile abfragen GregorianCalendar cal = new GregorianCalendar(); int era = cal.get( Calendar.ERA ); if( era == cal.AD ) System.out.println( "Anno Domini." ); int tag int monat int jahr = cal.get( Calendar.DATE ); = cal.get( Calendar.MONTH ); = cal.get( Calendar.YEAR ); int stunde1 = cal.get( Calendar.HOUR_OF_DAY ); int stunde2 = cal.get( Calendar.HOUR int amOrPm = cal.get( Calendar.AM_PM ); ); int minute = cal.get( Calendar.MINUTE ); int sekunde = cal.get( Calendar.SECOND ); int milli = cal.get( Calendar.MILLISECOND ); int wochej int wochem int tagj = cal.get( Calendar.WEEK_OF_YEAR ); = cal.get( Calendar.WEEK_OF_MONTH ); = cal.get( Calendar.DAY_OF_YEAR ); int moDiMi = cal.get( Calendar.DAY_OF_WEEK if( moDiMi == Calendar.SUNDAY ) ... ); ... und noch ein paar andere für Zeitzone, Sommerzeit etc., siehe API-Dokumentation! 2.6 Datumsteile setzen cal.set( Calendar.YEAR, 1976 ); cal.set( Calendar.MONTH, 9 ); ... Bei Verwendung älterer JDKs sollte man anschließend unbedingt cal.setTime( cal.getTime() ); aufrufen, wodurch die internen Werte für beide Darstellungen des Zeitpunktes synchronisiert werden. Dies behebt einen Bug in den älteren Java-Laufzeitumgebungen (JDK 1.1). 2.7 Datumswerte mit der Klasse DateFormat ausgeben Umfangreiche Möglichkeiten der Formatierung über die Klassen DateFormat und SimpleDateFormat aus dem Paket java.text (siehe API-Dokumentation): GregorianCalendar cal = new GregorianCalendar(); // jetzt DateFormat df = new SimpleDateFormat( "dd.MM.yyyy HH:mm" ); // DateFormat df = // DateFormat.getDateInstance(DateFormat.FULL, Locale.FRANCE); String zeit = df.format( cal.getTime() ); System.out.println( zeit ); HRZ Einführungspraktikum Java 71 IV. Nützliches und Wissenswertes 72 3. Die Klasse Object In Java sind alle Klassen direkt oder indirekt Subklassen der Klasse Object. Wenn für eine Klasse keine Superklasse angegeben ist, wird die Klasse von Object abgeleitet. Die Klasse Object wird oft dort eingesetzt, wo Methoden allgemein für die Verwendung mit ganz verschiedenen Objekten ausgelegt werden sollen. Da man eine Instanz einer Subklasse überall dort einsetzen darf, wo eine Instanz der Superklasse verwendet wird, kann man z. B. an eine Methode, die ein Object erwartet, jedes beliebige Objekt übergeben. Mit Hilfe des Operators instanceof läßt sich prüfen, ob ein Objekt eine Instanz einer bestimmten Klasse ist. Beispiel: public static boolean ersterWertKleiner( Object a, Object b ) { if ( a instanceOf String ) { String stra = (String)a; // 'Casting' ist hier nötig String strb = (String)b; return ( stra.compareTo( strb ) < 0 ); } else if ( a instanceof GregorianCalendar ) { GregorianCalendar g1 = (GregorianCalendar)a; GregorianCalendar g2 = (GregorianCalendar)b; return( g1.before( g2 ) ); } else ... } ... boolean b1 = ersterWertKleiner ( "Hund", "Hand" ); boolean b2 = ersterWertKleiner ( new GregorianCalendar( 1997, 7, 11 ), new GregorianCalendar( 1963, 1, 20 ) ); Mit Objekt.getClass() erhält man ein Objekt der Klasse Class, das Informationen über die Klasse des Objektes enthält. Einige nützliche Methoden: Object o = "Ein String-Objekt"; // oder sonst irgend etwas... Class c = o.getClass(); String klasse = c.getName(); // Name der Klasse Class s = c.getSuperclass(); // Superklasse der Klasse Object x = c.newInstance(); // Neue Instanz dieser Klasse, // funktioniert nur, falls es einen Standardkonstruktor gibt Ab JDK 1.1 bietet die Java Reflection API im Paket java.lang.reflect wesentlich weitreichendere Möglichkeiten (siehe API-Dokumentation) HRZ Einführungspraktikum Java 72 IV. Nützliches und Wissenswertes 73 4. Nützliche Werkzeuge für den Java-Programmierer 4.1 Die Klasse StringTokenizer Die Klasse StringTokenizer aus dem Paket java.util dient dazu, einen Teilstring in einzelne Elemente (Tokens) zu zerlegen. Die Elemente werden durch Trennzeichen voneinander getrennt. Die Menge der Trennzeichen wird dem Konstruktor als String mit übergeben. Beispiel: import java.util.StringTokenizer; public class Zerleger { public static void main( String[] args ) { String text = "Eins, zwei oder drei."; String trenn = " ,."; // Blank, Komma, Punkt sind Trenner StringTokenizer st = new StringTokenizer( text, trenn ); while ( st.hasMoreTokens() ) System.out.println( st.nextToken() ); } } // // // // // Ausgabe: Eins zwei oder drei Beachte: StringTokenizer erwartet als Trenner nur einzelne Zeichen, keine Zeichenketten! Weitere Konstruktoren st = // st = // // new StringTokenizer( text ); Leerzeichen, Tab, Newline als Trenner new StringTokenizer( text, delim, returnTokens ); falls der boolean-Wert returnTokens = true, dann werden die Trennertoken selbst auch mit zurückgegeben Weitere Methoden der Klasse StringTokenizer int countTokens() // Anzahl Token insgesamt String nextToken( String delim ) // Setzt gleichzeitig eine neue Menge von Trennzeichen HRZ Einführungspraktikum Java 73 IV. Nützliches und Wissenswertes 74 4.2 Wrapper-Klassen Viele nützliche Methoden der Klassen aus dem Paket java.util sind auf die Verwendung mit Objekten ausgelegt. Ein Problem dieser Methoden ist aber nun, daß ihnen nur Objekte, nicht aber Variablen einfacher Datentypen wie z. B. int übergeben werden können. Für derartige Fälle, in denen man einfache Datentypen wie Objekte behandeln will, stellt Java sogenannte Wrapper-Klassen zur Verfügung, die einfache Datentypen in ein Objekt "verpacken". Im JDK 1.0 existieren die Wrapper-Klassen Boolean, Character, Double, Float, Integer und Long für die jeweiligen primitiven Datentypen. Ab JDK 1.1 gibt es auch Wrapper-Klassen für Byte, Short und Void. Wrapper-Klassen sind auch nützlich, um Konvertierungen zwischen Datentypen auszuführen (mehr dazu später). Beispiel: (andere Wrapper-Klassen funktionieren analog...) int i = 3; Integer j = new Integer( i ); // Die 3 in ein Objekt verpackt int k = j.intValue(); // Wieder auspacken... Übung: Schauen Sie sich die Funktionsweise der Wrapper-Klassen in der API-Referenz an! 4.3 Die Klasse Stack Die Klasse Stack stellt einen Stapelspeicher für Objekte zur Verfügung (Last in, first out). Stack s = new Stack(); // Leeren Stapel anlegen ... s.push( new Integer( 1 ) ); // auf Stapel packen s.push( "Die Zwei" ); ... String s = (String)( s.pop() ); // vom Stapel entfernen Object alsNaechstesKommt = s.peek(); // vorausschauen! ... if ( s.empty() ) System.out.println( "nix mehr da!" ); HRZ Einführungspraktikum Java 74 IV. Nützliches und Wissenswertes 75 4.4 Die Klasse Vector Die Klasse Vector ist eine nützliche Alternative zu einem Array. In einem Vektor können beliebige Objekte abgelegt werden, die Länge des Vektors ist dynamisch veränderbar. Beispiel: Vector namen = new Vector(); namen.addElement( "Helmut" ); namen.addElement( "Otto" ); namen.addElement( "Willi" ); namen.removeElementAt( 1 ); namen.insertElementAt( "Anton", 0 ); String name = (String)( namen.elementAt( 1 ) ); Konstruktoren für Vektoren: new Vector(); new Vector( int initialCapacity ); new Vector( int initialCapacity, int capacityIncrement ); Den Inhalt des Vektors verändern: void addElement( Object obj ); // obj Hinten anhängen void insertElementAt( Object obj, int index ); // einfügen void setElementAt( Object obj, int index ); // austauschen void removeAllElements(); // alles löschen void removeElementAt( int index ); // an index löschen boolean removeElement( Object obj ); // obj entfernen Auf Elemente zugreifen: Object elementAt( int index ); Object firstElement(); Object lastElement(); // Element an Indexposition // Erstes Element // Letztes Element Den Vektor durchsuchen: boolean contains( Object obj ); int indexOf( Object obj ); int indexOf( Object obj, int index ); int lastIndexOf( Object obj ); int lastIndexOf( Object obj, int index ); // // // // // Ist obj drin? Suche vorwärts Suche ab index Suche rückwärts rückw. ab index Länge eines Vektors: int size(); // Anzahl Elemente im Vektor boolean isEmpty(); // Ist der Vektor leer? void setSize( int newSize ); // Vektor 'zurechtschneiden' HRZ Einführungspraktikum Java 75 IV. Nützliches und Wissenswertes 76 4.5 Die Klasse Hashtable Die Klasse Hashtable aus dem Paket java.util bietet die Funktionen einer Hashtabelle, in der Schlüssel auf Werte abgebildet werden. Um diese Abbildung zu ermöglichen, wird für jeden Schlüssel ein Hashcode benutzt. Als Schlüssel und Elemente der Hashtabelle können beliebige Objekte verwendet werden, die die Methoden equals und hashcode bereitstellen. Mit Hilfe von Wrapper-Klassen kann man auch primitive Datentypen benutzen. Beim Auffinden von Elementen anhand eines vorgegebenen Schlüssels wird dieser mit equals mit den Schlüsseln in der Tabelle verglichen, es wird also auf inhaltliche Gleichheit verglichen! Hashtable h = new Hashtable( 10 ); // neue Tabelle anlegen, voraussichtlich 10 Werte Wichtige Methoden: Object put( Object key, Object value ); // Einen Wert unter einem bestimmten Schlüssel ablegen Object get( Object key ); // In der Tabelle Wert zu diesem Schlüssel suchen Object remove( Object key ); // Wert unter diesem Schlüssel entfernen boolean contains ( Object value ); // Wert vorhanden? boolean containsKey( Object key ); // Schlüssel vorhanden? HRZ Einführungspraktikum Java 76 IV. Nützliches und Wissenswertes 77 Beispiel: import java.util.Hashtable; public class Buchstabierer { protected Hashtable buchstaben; public Buchstabierer() { buchstaben = new Hashtable( 26 ); buchstaben.put( "a", "Anton" ); buchstaben.put( "b", "Berta" ); buchstaben.put( "c", "Cesar" ); } // ... public void buchstabiere( String s ) { s = s.toLowerCase(); for ( int i = 0; i < s.length(); i++ ) { String c = s.substring( i, i + 1 ); if ( buchstaben.containsKey( c ) ) System.out.print( buchstaben.get( c ) ); } System.out.println(); } public static void main( String[] args ) { Buchstabierer bsb = new Buchstabierer(); for( int i = 0; i < args.length; i++ ) bsb.buchstabiere( args[ i ] ); } } Aufruf: java Buchstabierer Hallo ein Test HRZ Einführungspraktikum Java 77 IV. Nützliches und Wissenswertes 78 4.6 Die Klassen Runtime und Process Mit Hilfe der Klassen Runtime und Process lassen sich Kommandos aus Java heraus absetzen, z. B. um ein externes Programm zu starten: Runtime dieLaufzeitUmgebung = Runtime.getRuntime(); // sowas nennt man "Singleton": Es gibt nur eine Instanz! Process p = dieLaufzeitUmgebung.exec( "arbeiten.bat" ); // Es gibt Varianten zum gleichzeitigen Setzen von // Umgebungsvariablen, Übergeben von Argumenten... p.waitFor(); // Wartet auf Beendigung p.destroy(); // beendet den Prozeß int i = p.exitValue(); // der zurückgegebene Exit-Code Läuft ein Prozess, kann man auf seinen Eingabe-, Ausgabe- und Fehlerstrom zugreifen: InputStream is = p.getInputStream(); // Ausgabe von p InputStream es = p.getErrorStream(); // Fehler von p OutputStream os = p.getOutputStream(); // Eingabe an p HRZ Einführungspraktikum Java 78 IV. Nützliches und Wissenswertes 79 5. Konvertieren zwischen Datentypen und Objekten 5.1 Zwischen primitiven Datentypen konvertieren Im Gegensatz zu anderen Sprachen führt Java in der Regel keine automatische Typkonvertierung durch. Primitive Datentypen können durch Voranstellen von "(Datentyp)" konvertiert werden. Dieses Casting erzeugt einen neuen Wert, der ursprüngliche Wert wird nicht verändert! Casting von einem Datentyp zu einem anderen mit größerem Wertebereich (also z. B. von int nach long) ist nicht explizit nötig. Casting von einem Datentyp zu einem anderen mit kleinerem Wertebereich (also z. B. von double nach float) muß explizit erfolgen. Der Datentyp boolean kann nicht in andere Datentypen gecastet werden. Die int-Werte 0 und 1 können aber nach boolean gecastet werden. Da Casting eine sehr hohe Präzedenz hat, muß der zu castende Ausdruck geklammert werden, falls es sich nicht um eine einzelne Variable handelt. Beispiel: int i = (int)( 38.5 / 10.7 ); 5.2 Zwischen Objekten konvertieren Bei Casting zwischen Objekten müssen diese in einer Vererbungsbeziehung zueinander stehen. Casting von der Subklasse zur Superklasse ist nicht explizit nötig. Instanzen von Subklassen können überall dort verwendet werden, wo eine Instanz der Superklasse erwartet wird, d. h. Sie können z. B. einen String jeder Methode übergeben, die ein Object erwartet. Casting von der Superklasse zur Subklasse ist explizit durch Voranstellen von (Klassenname) nötig, da hier Information verloren gegangen ist. Beispiel: Vergleiche vorangegangene Beispiele zur Klasse Vector! 5.3 Objekte in Strings konvertieren Viele Klassen besitzen eine Methode toString(), die eine String-Darstellung des Objektes zurückgibt, z. B. die Wrapper-Klassen. Beispiel: Integer i = new Integer( 5 ); String s = i.toString(); HRZ Einführungspraktikum Java 79 IV. Nützliches und Wissenswertes 80 5.4 Primitive Datentypen in Strings konvertieren Die Klasse String besitzt die überladene Klassenmethode valueOf(), mit der die primitiven Datentypen boolean, double, float, int, long, char und char[]-Arrays in Strings konvertiert werden können. Beispiele: boolean b = true; double d = 28.425; String s = String.valueOf( b ); String t = String.valueOf( d ); 5.5 Strings in primitive Datentypen konvertieren Die Wrapper-Klassen Integer und Long besitzen Klassenmethoden parseInt() bzw. parseLong(), mit deren Hilfe Strings in int- und long-Werte konvertiert werden können. Dabei kann auch eine Basis (z. B. 2, 10, 16) angegeben werden. Die Wrapper-Klassen Boolean, Integer, Long, Float und Double besitzen Klassenmethoden valueOf(), mit deren Hilfe Strings in ein Objekt dieser Klasse und von dort aus in einen primitiven Datentyp konvertiert werden können. Beispiel: String s = "4711"; String t = "C000"; String u = "100101"; int i = Integer.parseInt( s ); int j = Integer.parseInt( t, 16 ); int k = Integer.parseInt( u, 2 ); 5.6 Wrapper-Objekte in primitive Datentypen konvertieren Alle Wrapper-Klassen besitzen Instanzmethoden, um sie zurück in die primitiven Datentypen zu konvertieren, die sie repräsentieren, z. B. booleanValue() in der Klasse Boolean, charValue() in der Klasse Character. Die Wrapper-Klassen Integer, Long, Float und Double besitzen alle jeweils die vier Instanzmethoden intValue(), longValue(), floatValue() und doubleValue(), über die diese Objekte in die entsprechenden primitiven Datentypen konvertiert werden können. Beispiel: String Double double float s d x y = = = = "153.92"; Double.valueOf( s ); d.doubleValue(); d.floatValue(); Hinweis: Die numerischen Wrapper-Klassen besitzen außerdem die Konstanten MAX_VALUE, MIN_VALUE, NEGATIVE_INFINITY und POSITIVE_INFINITY zur Ermittlung des Wertebereiches der Primitivtypen bzw. Darstellung von + / - . HRZ Einführungspraktikum Java 80 IV. Nützliches und Wissenswertes 81 6. Laufzeitfehler behandeln mit Exceptions 6.1 Grundlegendes über Laufzeitfehler Java bietet die Möglichkeit, nahezu alle auftretenden Laufzeitfehler abzufangen, zu behandeln, weiterzuleiten oder eigene Laufzeitfehler zu erzeugen. Laufzeitfehler, die nicht abgefangen werden, führen zum Abbruch des Programms oder zumindest (bei graphischen Benutzeroberflächen) zum Abbruch des aktuellen Threads. Laufzeitfehler sind in Java Instanzen der Klasse "Throwable" oder einer ihrer Unterklassen. Für jede Fehlerart gibt es eine spezielle Klasse. Bei Auftreten eines Fehlers erzeugt Java eine Instanz der jeweiligen Klasse. Dieses Objekt kann Informationen über den Fehler enthalten, z. B. den nicht gefundenen Dateinamen bei einer Instanz von "FileNotFoundException". Es gibt zwei Unterklassen von "Throwable": Die Klasse "Error" stellt schwerwiegende Fehler dar, die nicht abgefangen werden können. Die Klasse "Exception" stellt Laufzeitfehler dar, die man abfangen und behandeln kann. Die Klasse "Exception" besitzt wiederum viele Unterklassen, eine davon ist "RuntimeException". Instanzen von "RuntimeException" können, müssen aber nicht abgefangen werden, z. B. eine "ArithmeticException" bei einer möglichen Division durch Null. Instanzen von Klassen, die nicht Unterklassen von "RuntimeException" sind, müssen unbedingt abgefangen werden. Methodenaufrufe, die eine solche Exception eventuell auslösen, lassen sich nicht compilieren, wenn der aufrufende Code mögliche Laufzeitfehler nicht abfängt oder explizit weiterleitet. 6.2 Laufzeitfehler abfangen und behandeln Zum Abfangen einer Exception werden Code-Teile, die eventuell eine Exception erzeugen, in einen try-Block eingebetten, an dessen Ende eine eventuell auftretende Exception in einem catch-Block abgefangen und behandelt werden kann. Beispiel: public static void main( String[] args ) { String versuch = "Test"; try { versuch = args[ 0 ]; System.out.println( "Es hat funktioniert!" ); } catch( ArrayIndexOutOfBoundsException ex ) { System.out.println( "Ein Fehler ist aufgetreten." ); ex.printStackTrace(); // Zeige Position, an der der Fehler aufgetreten ist. System.out.println( ex.getClass().getName() ); System.out.println( ex.getMessage() ); } System.out.println( versuch ); } HRZ Einführungspraktikum Java 81 IV. Nützliches und Wissenswertes 82 Beachte: Es können mehrere catch-Blöcke angegeben werden, um verschiedene Klassen von Exceptions abzufangen, oder nur eine Superklasse (Exception) wird abgefangen und dann mit instanceOf überprüft, um welche als Exception es sich handelt. So kann die Klassenhierarchie der Exception-Arten genutzt werden. try{ ... } catch( ArithmeticException ex ){ catch( FileNotFoundException ex ){ ... } ... } try{ ...} catch ( Exception ex ) { if ( ex instanceof ArithmeticException ) { ... } else if ( ex instanceof FileNotFoundException ) { ... } } Beispiel: try{ Thread.sleep( 500 ); /* Schlafe 500 ms */ } catch( InterruptedException ex ){} // Fehler ignorieren 6.3 Laufzeitfehler weiterleiten Es gilt die catch-or-throw-Regel: Exceptions, die nicht RuntimeExceptions sind, müssen in der Methode, in der sie auftreten können, mit catch behandelt werden, oder die Methode hat eine entsprechende throws-Anweisung, die die Exception dann wiederum weitergibt, so daß sie weiter nach oben in der Aufrufhierarchie durchwandert. Dort muß sie wiederum behandelt oder weitergegeben (catch or throw) werden. Beispiel: public void a() throws IOException { FileInputStream fis = new FileInputStream( "test.xy" ); int i = fis.read(); System.out.println( i ); } public void b() throws IOException { a(); System.out.println( "Grüsse von b()!" ); } public void c() { try{ b(); } catch( IOException ex ) { System.out.println( "Da ging was schief." ); } } HRZ Einführungspraktikum Java 82 IV. Nützliches und Wissenswertes 83 6.4 Finally Hinter dem catch- oder try- Teil kann auch noch ein finally-Teil angegeben werden, der immer ausgeführt wird, egal, ob der try-Block normal beendet wurde oder eine Ausnahme erzeugt wurde: FileInputStream fis; try { fis = new FileInputStream( "test" ); int i = fis.read(); System.out.println( i ); } finally { if( fis != null ) fis.close(); } 6.5 Eigene Exceptions definieren Eigene Exceptions lassen sich durch Ableiten von der Klasse Exception (für unbedingt abzufangende Fehler) oder RuntimeException (für nicht zwingend abzufangende Fehler) definieren. Beispiel: public class UnsinnException extends RuntimeException { public UnsinnException( String nachricht ) { super( nachricht ); } } public class Textverarbeitung { public static void verarbeite( String s ) { if ( s.toLowerCase().indexOf( "unsinn" ) != -1 ) throw new UnsinnException ( "Unsinn wird nicht verarbeitet!" ); System.out.println( s.toUpperCase() ); } public static void main( String[] args ) { for( int i = 0; i < args.length; i++ ) { try{ verarbeite( args[ i ] ); } catch ( UnsinnException ex ) { System.out.println( "Fehler bei " + args[ i ] + ": " + ex.getMessage() ); } } } } HRZ Einführungspraktikum Java 83 IV. Nützliches und Wissenswertes 84 HRZ Einführungspraktikum Java 84 V. Applets, Grafik- und Soundausgabe, Ereignisse 85 V. Applets, Grafik- und Soundausgabe, Ereignisse HRZ Einführungspraktikum Java 85 V. Applets, Grafik- und Soundausgabe, Ereignisse 86 1. Applets Es gilt das "Sandkastenprinzip" bei heruntergeladenen Java-Applets: Das Applet kann in einem kontrollierten 'Sandkasten' spielen, der Java Virtual Machine des Browsers, darf diesen aber nicht 'verlassen'. Was aus dem Netz heruntergeladene Applets nicht dürfen: Zugriffe auf das Dateisystem des Clients (Schreiben, Lesen von Dateien) Kommunikation mit beliebigen Servern im Netz Programme starten oder Kommandos ausführen Zugriff z. B. auf die Windows Registry Was Applets dürfen: Kommunikation mit dem Heimat-Server über das Netz (z. B. über CGI-Aufrufe, FTP, HTTP, POP3, SMNP, SNMP, ...) Beachte: In einigen älteren Browsern haben Applets, die lokal von der Festplatte des Clients geladen werden, mehr Rechte als solche, die über das Netz geladen werden. Bei Verwendung der Microsoft-eigenen Java-Erweiterungen für ActiveX kann ein JavaApplet auf alle Windows-Ressourcen zugreifen. Was Applikationen dürfen: Alles, außer direkt auf Hardware zugreifen! Kommunikation mit beliebigen Servern über das Netz Neue Prozesse erstellen, Programme starten, Kommandos ausführen Native Code ausführen (Java Native Interface): C-Module einbinden Zugriff auf das Dateisystem Hinweis: Der Sprachumfang des JDK 1.1 und 1.2 bietet weiterführende Konzepte, z .B das Signieren von Applets. Digital signierten Applets kann man individuelle Rechte gewähren, z. B. auch den Zugriff auf das lokale Dateisystem des Clients. Leider verwenden Netscape, Microsoft und Sun hier jeweils unterschiedliche Signierungstechniken und APIs, so daß man je nach Browser noch unterschiedliche Verfahren anwenden muß. Die in der Praxis sinnvollste Lösung ist hier die Verwendung des Java Plug-ins. HRZ Einführungspraktikum Java 86 V. Applets, Grafik- und Soundausgabe, Ereignisse 87 1.1 Grundgerüst eines Applets import java.applet.Applet; import java.awt.Graphics; public class MyApplet extends Applet { public void init() { // Wird aufgerufen, wenn das Applet geladen wurde // Typische Aufgaben hier z. B. Parameter abfragen, // Grafik- und Sounddateien laden, ... } public void start() { // Wird aufgerufen nach init() oder wenn der Benutzer // zu dieser Webseite zurückkehrt // Aufgaben z. B. Netzverbindung aufbauen, ... } public void stop() { // Wird aufgerufen, wenn der Benutzer diese Webseite // verläßt. // Aufgaben z. B. Netzverbindung beenden, ... } public void destroy() { // Wird aufgerufen, wenn das Applet zerstört wird, // in der Regel bei Beenden des Browsers. // Wird nur selten angewandt. } public void paint( Graphics g ) { // Applet-Bereich auf der HTML-Seite zeichnen } } HRZ Einführungspraktikum Java 87 V. Applets, Grafik- und Soundausgabe, Ereignisse 88 1.2 Aufruf eines Applets innerhalb einer Webseite <HTML> <HEAD> <TITLE>Applet-Testseite</TITLE> </HEAD> <BODY> Hier kommt das Applet:<P> <APPLET CODE CODEBASE ARCHIVE WIDTH HEIGHT HSPACE VSPACE ALIGN = = = = = = = = "MyApplet.class" Name der Klassendatei "foo/bar/classes" Verzeichnis mit class-Dateien "MyAppletClasses.jar" Archivdatei mit *.class 300 Breite auf der Seite 200 Höhe auf der Seite 10 Horizontaler Abstand um das Applet 10 Vertikaler Abstand um das Applet LEFT Ausrichtung zum umgebenden Text, wie beim <IMG>-Tag in HTML > <PARAM NAME="user" VALUE="John Doe" > <PARAM NAME="font" VALUE="TimesRoman" > <PARAM NAME="size" VALUE="36" > Parameter Dieser Text erscheint, wenn der Browser kein Java versteht. </APPLET> <P>Hier kommt der Rest der Seite. </BODY> </HTML> Parameter können über das <PARAM>-Tag zwischen <APPLET> und </APPLET> übergeben werden. Beachte, daß Value immer eine Zeichenkette ist und im Gegensatz zu HTML allgemein die Groß-/Kleinschreibung beim Parameternamen wichtig ist! HRZ Einführungspraktikum Java 88 V. Applets, Grafik- und Soundausgabe, Ereignisse 89 1.3 Aufruf-Parameter aus der Webseite auslesen import java.applet.*; import java.awt.*; public class MyApplet extends Applet { protected Font f; protected String name; public void init() { // Parameter auslesen String userName = getParameter( "user" ); String fontName = getParameter( "font" ); String fontSize = getParameter( "size" ); // // if if if Wurden alle Parameter angegeben? Ansonsten Standardwerte nehmen: ( userName == null ) userName = "Nobody"; ( fontName == null ) fontName = "Courier"; ( fontSize == null ) fontSize = "12"; // fontSize von String nach int konvertieren: int fsize = Integer.parseInt( fontSize ); // Zeichensatz-Objekt erzeugen, Instanzvariablen setzen: f = new Font( fontName, Font.BOLD, fsize ); name = userName; } public void paint( Graphics g ) { g.setFont( f ); g.setColor( Color.blue ); g.drawString( "Hallo " + name + "!", 10, 50 ); } } HRZ Einführungspraktikum Java 89 V. Applets, Grafik- und Soundausgabe, Ereignisse 90 2. Zeichnen mit der Graphics-Klasse Das Graphics-Objekt repräsentiert den Zeichenbereich eines Applets. Im Koordinatensystem der Graphics-Klasse liegt der Punkt (0,0) oben links! Mit der Graphics-Klasse kann man folgende einfache Formen zeichnen: 2.1 Farben setzen und Text ausgeben public void paint( Graphics g ) { this.setBackground( Color.red ); Font f = new Font( "TimesRoman", Font.BOLD, 36 ); g.setFont( f ); g.setColor( Color.blue ); g.drawString( "Hallo!", 10, 50 ); g.setColor( Color.yellow ); g.drawString( "Wie geht's?", 10, 100 ); } Die Klasse java.awt.Color bietet weitreichende Methoden zum Umgang mit Farbwerten. Man kann z. B. eine beliebige Farbe durch Angabe ihrer Rot/Grün/BlauAnteile erzeugen: Color c = new Color( 100, 80, 140 ); g.setColor( c ); HRZ Einführungspraktikum Java 90 V. Applets, Grafik- und Soundausgabe, Ereignisse 91 2.2 Linien und Rechtecke zeichnen g.drawLine( 10, 10, 50, 50 ); // Linie von (10,10) nach (50,50) g.drawRect( 5, 10, 45, 50 ); // Rechteck oben links = (5,10) mit Breite 45 u. Höhe 50 g.fillRect( 5, 10, 45, 50 ); // Gefülltes Rechteck g.drawRoundRect( 5, 10, 45, 50, 10, 10 ); // mit gerundeten Ecken von 10*10 Pixeln im Quadrat g.fillRoundRect( 5, 10, 45, 50, 10, 10 ); // Gefülltes, gerundetes Rechteck g.draw3DRect( 10, // Schattiertes g.fill3DRect( 10, // Schattiertes 10, 30, 30, true ); Rechteck (3D-Look), Rechteck erhaben 10, 30, 30, false ); Rechteck (3D-Look), Rechteck vertieft Hinweis: Nicht gefüllte Rechtecke sind in Java immer 1 Pixel breiter und höher als gefüllte Rechtecke bei Verwendung der gleichen Parameter! 2.3 Polygone (Vielecke) zeichnen int x[] = { 10, 20, 15, 30, 45, 70 }; int y[] = { 10, 15, 30, 40, 30, 40 }; g.drawPolygon( x, y, x.length ); g.fillPolygon( x, y, x.length ); Polygon p = new Polygon( x, y, x.length ); p.addPoint( 10, 10 ); ... g.drawPolygon( p ); g.fillPolygon( p ); 2.4 Ovale, Kreise und Ellipsen zeichnen Kreise und Ellipsen werden durch das Rechteck definiert, das sie einschließt. g.drawOval( 10, 10, 30, 50 ); // Ellipse, die genau eingeschlossen wird durch das // Rechteck, das bei drawRect() mit den gleichen // Parametern entstehen würde g.fillOval( 25, 25, 40, 40 ); // Gefülltes Oval, sonst wie oben // Ist ein Kreis, da Breite = Höhe = 40 // Mitte ist demnach bei (45,45) und Radius = 20 HRZ Einführungspraktikum Java 91 V. Applets, Grafik- und Soundausgabe, Ereignisse 92 2.5 Kreis- und Ellipsenbögen zeichnen Die Parameter für Bögen lassen sich am einfachsten bestimmen, in dem man sich den Bogen zunächst als vollendete Ellipse vorstellt: g.drawOval( 25, 25, 40, 40 ); // Kreis aus dem obigen Beispiel Anschließend wird angegeben, ab welchem Gradwert der Bogen zu sehen sein soll: 0 Grad = 3 Uhr = Osten, 90 Grad = 12 Uhr = Norden usw. Außerdem muß angegeben werden, wieviel Grad der Bogen einnehmen soll: z. B. 180 Grad für eine halbe "Umrundung" gegen den Uhrzeigersinn, -90 Grad für ein Viertel im Uhrzeigersinn g.drawArc( 25, 25, 40, 40, 90, 90 ); // Der Teilbogen des obigen Kreises, der bei 12 Uhr beginnt // und bei 9 Uhr endet, also das linke obere Viertel Hinweis: Bei all diesen Methoden kann die Liniendicke nicht bestimmt werden! HRZ Einführungspraktikum Java 92 V. Applets, Grafik- und Soundausgabe, Ereignisse 93 3. Arbeiten mit Fonts 3.1 Font-Objekte erzeugen Zur Textausgabe in Applets lassen sich verschiedene Zeichensätze verwenden. Hierzu dient die Klasse Font. Der Font-Konstruktor erwartet als Parameter den Fontnamen, den Stil und die Schriftgröße in Punkt: Font f = new Font( "Serif", Font.BOLD, 12 ); Fontstile sind int-Werte, die als Konstanten in der Klasse Font definiert sind, z .B. Font.BOLD, Font.PLAIN, Font.ITALIC. Diese lassen sich kombinieren: Font f = new Font( "Monospaced", Font.BOLD + Font.ITALIC, 8 ); Beachte: Die Menge der unterstützten Zeichensätze ist systemabhängig! Um herauszufinden, welche Fonts vom Laufzeitsystem unterstützt werden, existiert die Methode getFontList() in der Klasse Toolkit des Paketes java.awt: String[] fontnames = Toolkit.getDefaultToolkit().getFontList(); In allen Java-Systemen sollten die Fonts "Serif", "SansSerif" und "Monospaced" unterstützt sein. Die Java-Laufzeitumgebung bildet diese symbolischen Fontnamen auf Systemfonts ab, unter Windows z. B. auf die True-Type-Schriften "Times New Roman", "Arial" und "Courier". Im JDK 1.0 hießen die Standardfonts (statt obiger Bezeichner) "TimesRoman", "Helvetica" und "Courier". HRZ Einführungspraktikum Java 93 V. Applets, Grafik- und Soundausgabe, Ereignisse 94 3.2 Zeichensätze verwenden: Font f1 = new Font( "TimesRoman", Font.BOLD, 36 ); g.setFont( f1 ); // g ist eine Graphics-Instanz g.drawString( "HRZ", 20, 20 ); Font f2 = g.getFont(); // Gerade aktiven Font ermitteln String name = f2.getName(); int size = f2.getSize(); int style = f2.getStyle(); // 0 = PLAIN, 1 = BOLD, // 2 = ITALIC, 3 = 1 + 2 :-) boolean b1 = f2.isPlain(); boolean b2 = f2.isBold(); boolean b3 = f2.isItalic(); 3.3 Größeninformationen über Zeichensätze Die Klasse FontMetrics enthält Größeninformationen über einen Font, z. B. über die Maße der Zeichen in einem Font. Damit läßt sich z. B. ermitteln, welche Breite ein Text mit einem bestimmten Font benötigt: // g ist eine Graphics-Instanz, z. B. aus paint( Graphics g ); // f2 ist eine Font-Instanz, z. B. erzeugt mit new Font(...) int breite = g.getFontMetrics( f2 ).stringWidth( "Hallo" ); Die wichtigsten Methoden der Klasse FontMetrics: public int charWidth( char c ); // Breite eines Zeichens public int StringWidth( String s ); // Breite eines Strings public public public public int int int int getAscent(); getDescent(); getHeight(); getLeading(); HRZ Einführungspraktikum Java // // // // Oberlänge, vgl. Buchstabe 'H' Unterlänge, vgl. Buchstabe 'j' Gesamte Höhe Zeilenabstand (ohne Font-Höhe) 94 V. Applets, Grafik- und Soundausgabe, Ereignisse 95 4. Verwenden von Bilddateien Java ist in der Lage, Grafikdateien vom Typ "GIF" und "JPEG" (die auch von HTML unterstützten Grafiktypen) direkt darzustellen. Diese werden durch die Klasse Image realisiert. 4.1 Laden von Images Die Klasse Applet besitzt eine Methode, um ein Image aus dem Netz nachzuladen. Ihr wird eine Instanz der Klasse URL übergeben, das die URL der Grafikdatei enthält: import java.net.URL; import java.awt.*; import java.applet.*; ... URL url1 = new URL("http://www.irgendwo.com/bild1.gif" ); Image bild1 = this.getImage( url1 ); Um die Position des Bildes auf dem Server relativ zur Position des Applets (d. h. der class-Datei) anzugeben, existiert eine Variante von getImage, die eine URL und einen dazu relativen Pfadnamen erwartet: // Position der Bilddatei relativ zur aufrufenden HTML-Seite URL url2 = this.getDocumentBase(); Image bild2 = this.getImage( url2, "bild2.jpg" ); // Position relativ zum Verzeichnis der class-Datei(en) URL url3 = this.getCodeBase(); Image bild3 = this.getImage( url3, "bilder/bild3.gif" ); Wird ein Bild nicht gefunden, liefert getImage() den Wert null zurück. In Java-Applications kann man Images über die Klasse Toolkit laden: URL url4 = new URL( "http://www.irgendwo.de/bild.gif" ); Image bild4 = Toolkit.getImage( url4 ); Image bild5 = Toolkit.getImage( "xy.gif" ); Ab JDK 1.1 kann man Images als Resource laden, d.h. sie werden wie die Klassen im Klassenpfad gesucht und automatisch geladen. Das geht im Applet in etwa wie folgt: URL url = getClass().getResource( "companylogo.gif" ); Image i = createImage( (ImageProducer) url.getContent() ); 4.2 Breite und Höhe eines Bildes bestimmen int bx = bild3.getWidth( this ); // Breite int by = bild3.getHeight( this ); // Höhe HRZ Einführungspraktikum Java 95 V. Applets, Grafik- und Soundausgabe, Ereignisse 96 4.3 Anzeigen von Images Die Klasse Graphics besitzt überladene Methoden drawImage(), um das Bild anzuzeigen. Die drawImage-Methoden liefern einen boolean-Wert zurück, der angibt, ob alle Bits des Bildes verfügbar waren. Der letzte Parameter von drawImage ist wieder der ImageObserver. boolean ok; ok = g.drawImage( bild1, 5, // Bild mit oberer linker ok = g.drawImage( bild2, 5, // Bild auf die Größe von 5, this ); Ecke bei (5,5) ausgeben 5, 100, 120, this ); 100 * 120 Pixeln skalieren GIF-Bilder erlauben es, sie so abzuspeichern, daß Punkte mit einer bestimmten Farbe als transparent angesehen werden, d. h. die Grafik ist an diesen Stellen "durchsichtig". Die obigen Methoden geben diese Bildpunkte ebenfalls transparent aus, d. h. schon in der Methode gezeichnete Dinge werden durch diese Bildpunkte nicht verändert. Alternativ kann man die transparenten Bildpunkte durch eine Farbe einfärben: ok = // ok = // g.drawImage( Transparente g.drawImage( Transparente bild1, Punkte bild2, Punkte 5, 5, Color.blue, this ); blau darstellen 5, 5, 100, 120, Color.blue, this ); blau darstellen und skalieren 4.4 Ladevorgänge überwachen mit der Klasse MediaTracker import java.applet.*; import java.awt.*; import java.net.*; public class ImageApplet extends Applet { protected Image bild; public void init() { URL dir = this.getDocumentBase(); bild = this.getImage( dir, "bild.jpg" ); MediaTracker mt = new MediaTracker( this ); mt.addImage( bild, 0 ); try{ mt.waitForAll(); } catch( Exception ex ) { System.out.println( "Fehler beim Laden des Bildes!"); } } public void paint( Graphics g ) { if( bild != null ) g.drawImage( bild, 0, 0, this ); } } HRZ Einführungspraktikum Java 96 V. Applets, Grafik- und Soundausgabe, Ereignisse 97 5. Ausgabe von Audiodateien Java unterstützt das direkte Abspielen von Audiodateien, die im AU-Format vorliegen. Dies ist ein Sun-eigenes Format, dessen Dateien zum Preis einer geringeren Tonqualität relativ klein sind. Im Internet findet man Konvertierungsprogramme, um z. B. WAV-Dateien in das AUFormat umzuwandeln. 5.1 Direktes Laden und Abspielen von Audiodateien Über die Methode play() der Klasse Applet lassen sich analog zum Vorgehen bei getImage() AU-Dateien herunterladen und unmittelbar abspielen: URL url1 = this.play( URL url2 = this.play( new URL( "http://www.irgendo.com/ton.au" ); url1); this.getDocumentBase(); url1, "sounds/spacemusic.au" ); 5.2 AudioClip-Instanzen erzeugen und abspielen Will man den Sound wiederholt verwenden, kann man analog zur Klasse Image Audiodateien als Instanzen der Schnittstelle AudioClip erzeugen: URL url3 = this.getDocumentBase(); AudioClip sound = this.getAudioClip( url3, "spacemusic.au" ); sound.play(); // einmaliges Abspielen sound.loop(); // wiederholtes Abspielen sound.stop(); // Abspielen stoppen Beispiel: Hintergrundmusik für die Webseite import java.applet.*; import java.net.*; public class AudioApplet extends Applet { protected AudioClip sound; public void init() { URL dir = this.getDocumentBase(); sound = this.getAudioClip( dir, "spacemusic.au" ); } public void start() { sound.loop(); } public void stop() { sound.stop(); } } HRZ Einführungspraktikum Java 97 V. Applets, Grafik- und Soundausgabe, Ereignisse 98 6. Auf Ereignisse reagieren Um eine graphische Oberfläche realisieren zu können, muß das System dem Programm eine Mitteilung senden, sobald bestimmte Ereignisse auftreten (Mausklick, Taste losgelassen, ...). In Java dient dazu der Event-Mechanismus, der durch die Klassen im Paket java.awt.event realisiert wird. Den verschiedenen Ereignissen entsprechen Klassen in diesem Paket, deren gemeinsame Superklasse die Klasse AWTEvent ist und die in einer Hierarchie zueinander stehen. Komponenten, die Ereignisse auslösen (also Event-Objekte erzeugen) werden Ereignisquellen genannt. Ein Button löst z. B. einen ActionEvent aus, wenn er gedrückt wird, eine Mausbewegung in einer Komponente löst einen MouseEvent aus. Man kann nun auf Ereignisse reagieren, indem man für bestimmte Ereignisse Ereignisempfänger (Event Listeners) bei der Komponente anmeldet. Ereignisempfänger sind Klassen, die ein zu einem Ereignistyp passendes Interface implementieren, dessen Methoden das System dann bei Auftreten des Ereignisses aufruft. Diese ListenerInterfaces sind Subklassen von EventListener. Dieses Modell wird ab JDK 1.1 verwendet und wird Delegation Event Model genannt, da es die Möglichkeit bietet, die Ereignisverarbeitung einer Komponenten beliebige andere Klassen zu delegieren. Beispiele anhand von Mausereignissen: Komponenten, z. B. Applets, lösen MouseEvents aus, wenn ein Mausereignis auftritt. Um Mausereignisse zu verarbeiten, registriert man bei der Komponente MouseListener oder MouseMotionListener, das sind Klassen, die das entsprechende Interface implementieren. Tritt das Ereignis auf, wird die entsprechende Methode der registrierten Listener aufgerufen und ihr eine Instanz der Klasse MouseEvent übergeben, die Details über das Ereignis enthält. Die Menge der registrierten Listener kann für eine Komponente über entsprechende add/remove-Anweisungen verändert werden. Es ist also auch möglich, mehr als einen Listener für ein bestimmtes Ereignis zu registrieren. HRZ Einführungspraktikum Java 98 V. Applets, Grafik- und Soundausgabe, Ereignisse 99 Bei einem Mausereignis ruft die Ereignisquelle z. B. die Methode mouseClicked() des MouseListeners auf: public void mouseClicked( MouseEvent e ) { int x = e.getX(); // Wo? int y = e.getY(); Point p = e.getPoint(); int clickCount = e.getClickCount(); // Doppelklick? int mods = e.getModifiers(); // Welche Maustaste? int buttonNr; if( ( mods & InputEvent.BUTTON3_MASK) != 0 ) buttonNr = 3; else if( ( mods & InputEvent.BUTTON2_MASK) != 0 ) buttonNr = 2; else if( ( mods & InputEvent.BUTTON1_MASK) != 0 ) buttonNr = 1; // Weitere Codezeilen, um auf das Ereignis zu reagieren... } HRZ Einführungspraktikum Java 99 V. Applets, Grafik- und Soundausgabe, Ereignisse 100 6.1 Die Komponente 'hört sich selbst zu' import java.awt.*; import java.awt.event.*; import java.applet.*; public class MausApplet extends Applet implements MouseListener, MouseMotionListener { public void init() { this.addMouseListener( this ); this.addMouseMotionListener( this ); } // Methoden des MouseListener Interfaces: public void mouseClicked( MouseEvent e ) { int x = e.getX(); int y = e.getY(); Graphics g = getGraphics(); g.drawOval( x - 2, y - 2, 4, 4 ); g.dispose(); } public void mousePressed( MouseEvent e ){} public void mouseReleased( MouseEvent e ){} public void mouseEntered( MouseEvent e ){} public void mouseExited( MouseEvent e ){} // Methoden des MouseMotionListener Interfaces: public void mouseDragged( MouseEvent e ){} public void mouseMoved( MouseEvent e ){} } HRZ Einführungspraktikum Java 100 V. Applets, Grafik- und Soundausgabe, Ereignisse 101 6.2 Eine andere Klasse hört zu: import java.awt.*; import java.awt.event.*; import java.applet.*; public class MausApplet extends Applet { public void init() { MouseListener ml = new MausLauscher(); this.addMouseListener( ml ); } } class MausLauscher implements MouseListener { ... // Methoden des MouseListener Interfaces: public void mousePressed( MouseEvent e ){} public void mouseReleased( MouseEvent e ){} ... } 6.3 Arbeiten mit Adapter-Klassen: Da Listener Interfaces sind, ist man gezwungen, alle Methoden des Interfaces zu implementieren, auch wenn wan vielleicht nur einige Methoden benötigt und nicht auf alle Ereignisse reagieren will. Daher stellt Java sogenannte Adapter-Klassen für die Listener zur Verfügung, die mehr als eine Methode anbieten. Diese Adapter-Klassen besitzen alle Methoden des entsprechenden Interfaces, die aber einfach nichts tun, also leer sind. Durch Ableiten einer Klasse von einem Adapter kann man die leeren Methoden so einfach erben, statt sie selbst noch einmal aufführen zu müssen: class MausLauscher extends MouseAdapter // bzw. MouseMotionAdapter bei Bedarf { // Überschreibe das leere mousePressed von MouseAdapter public void mousePressed( MouseEvent e ) { ... } // Der Rest wird geerbt. } HRZ Einführungspraktikum Java 101 V. Applets, Grafik- und Soundausgabe, Ereignisse 102 6.4 Arbeiten mit Inner Classes Ab JDK 1.1 hat man die Möglichkeit, eine Klasse innerhalb einer anderen Klasse zu definieren (Inner Class). Diese Klasse ist dann nur innerhalb der definierenden Klasse benutzbar und daher lokal. Instanzen einer Inner Class haben Zugriff auf die Methoden und Variablen der Klasse, der sie gehören, bzw. des Blockes, in dem sie definiert sind. Dieses Konstrukt wird sehr oft wie folgt zur Ereignisverarbeitung verwendet: import java.awt.*; import java.awt.event.*; import java.applet.*; public class MausApplet extends Applet { public void init() { this.addMouseListener( new LokalerMausLauscher() ); } class LokalerMausLauscher extends MouseAdapter { public void mousePressed( MouseEvent e ) { int x = e.getX(); int y = e.getY(); Graphics g = getGraphics(); g.fillRect( x, y, 2, 2 ); g.dispose(); } } } HRZ Einführungspraktikum Java 102 V. Applets, Grafik- und Soundausgabe, Ereignisse 103 6.5 Anonyme innere Klassen Eine Variante lokaler Klassen sind anonyme Klassen. Diese Klassen werden innerhalb einer Anweisung definiert, in einer Kombination aus new, der Angabe einer Oberklasse und der Definition der Variablen und Methoden der Klasse. Das sieht dann etwa wie folgt aus: import java.awt.*; import java.awt.event.*; import java.applet.*; public class MausApplet extends Applet { public void init() { this.addMouseListener( new MouseAdapter() // anonyme Subklasse von MouseAdapter { public void mousePressed( MouseEvent e ) { int x = e.getX(); int y = e.getY(); Graphics g = getGraphics(); g.fillRect( x, y, 2, 2 ); g.dispose(); } } ); // Ende Aufruf addMouseListener } } HRZ Einführungspraktikum Java 103 V. Applets, Grafik- und Soundausgabe, Ereignisse 104 6.6 Auf Tastaturereignisse reagieren Tastaturereignisse lösen in einer Komponente einen KeyEvent aus, wenn diese den Eingabefocus besitzt. Entsprechend existiert ein KeyAdapter, Methoden addKeyListener und removeKeyListener und ein KeyListener-Interface, das die folgenden Methoden besitzt: void keyTyped ( KeyEvent e ); // UniCode-Taste getippt void keyPressed ( KeyEvent e ); // Taste gedrückt void keyReleased( KeyEvent e ); // Taste losgelassen Eine Instanz von KeyEvent besitzt die folgenden Methoden: Jeder Taste ist ein Tastencode zugeordnet: int code = e.getKeyCode(); // Tastencode if( code = KeyEvent.VK_F1 ) System.out.println( "You pressed F1, need some help?" ); Die statische Methode getKeyText liefert die Beschriftung der Taste: String label = KeyEvent.getKeyText( code ); // label = "F1" Die statische Methode getKeyModifiersText liefert die Modifier-Label: String mods = KeyEvent.getKeyModifiersText( code ); // mods = "Ctrl+Shift" etc. Die Methode getKeyChar liefert das getippte Zeichen: if( code != KeyEvent.CHAR_UNDEFINED ) char c = e.getKeyChar(); // UniCode-Zeichen der Taste 6.6.1 Die Klasse InputEvent Die Klasse InputEvent ist die gemeinsame Basisklasse von MouseEvent und KeyEvent und besitzt noch einige weitere Methoden, die sich also sowohl bei MouseEvents als auch bei KeyEvents abfragen lassen: boolean boolean boolean boolean isShiftDown(); isAltDown(); isControlDown(); isMetaDown(); HRZ Einführungspraktikum Java // // // // true, true, true, true, falls falls falls falls Shift Alt CTRL Meta gedrückt gedrückt gedrückt gedrückt war war war war 104 VI. Graphische Benutzeroberflächen mit dem AWT 105 VI. Graphische Benutzeroberflächen mit dem AWT HRZ Einführungspraktikum Java 105 VI. Graphische Benutzeroberflächen mit dem AWT 106 1. Grundlagen graphischer Oberflächen Graphische Benutzeroberflächen können in Java mit Hilfe des Paketes java.awt erzeugt werden. Dieses Paket beinhaltet das Abstract Windowing Toolkit, das es ermöglicht, auf Elemente wie z. B. Buttons oder Checkboxes systemunabhängig zuzugreifen. Dies wird ermöglicht, indem für jedes Element einer Oberfläche (Fenster, Knopf, ...) eine sie abbildende Klasse existiert, die durch das Laufzeitsystem implementiert werden muß. Dies ist Aufgabe der Schnittstellensammlung im Paket java.awt.peer. Die Elemente der Oberfläche sehen daher immer so aus, wie sie in der Regel auch auf dem jeweiligen System erscheinen, abhängig davon, wo das Java-Programm ausgeführt wird. Sowohl Applets, als auch Applications können eine graphische Oberfläche besitzen, wobei Applets AWT-Grundelemente direkt im Appletbereich positionieren können, während Applications dazu erst ein Fenster erstellen müssen. 1.1 Die Bausteine des AWT Die Bausteine des AWT kann man in vier Kategorien aufteilen: Einfache Komponenten, wie z. B. Buttons, Checkboxes und Labels, mit deren Hilfe der Benutzer letztendlich das Programm bedient Container, wie z. B. Fenster , Panels und Applets, die die Aufgabe haben, die Komponenten in sich aufzunehmen. LayoutManager, die für die Ausrichtung und Anordnung von Komponenten in einem ihnen zugeordneten Container verantwortlich sind. Events, die von Komponenten erzeugt werden, wenn bestimmte Ereignisse wie z. B. das Drücken eines Buttons auftreten, und durch entsprechende EventListener verarbeitet werden können. Fast alle Elemente des AWT sind direkte oder indirekte Subklassen der abstrakten Klasse Component. Diese Klasse besitzt Methoden, die an jede Subklasse vererbt werden, z. B. die Funktionalität, Maus- und Tastaturereignisse auszulösen und angezeigt oder verborgen zu werden. Hier ein Ausschnitt aus der Klassenhierarchie: HRZ Einführungspraktikum Java 106 VI. Graphische Benutzeroberflächen mit dem AWT 107 1.2 Arbeiten mit einfachen Komponenten 1.2.1 Die Klasse Label Label dienen der Beschriftung von Elementen der Oberfläche, z. B. zur Beschriftung eines Texteingabefeldes: Label l1 Label l2 Label l3 // Mit = new Label(); // Leere Beschriftung (Platzhalter) = new Label( "ArtikelNr:" ); = new Label( "ArtikelNr:", Label.RIGHT ); Ausrichtung: LEFT, RIGHT, CENTER als int-Konstante String t = l1.getText(); l1.setText( "Eingabe:" ); // Beschriftung holen // Beschriftung setzen int i = l1.getAlignment(); // Ausrichtung holen l1.setAlignment( Label.CENTER ); // Ausrichtung setzen 1.2.2 Die Klasse Button Buttons realisieren Knöpfe, die gedrückt bzw. angeklickt werden können und dabei ein entsprechendes Ereignis auslösen: Button b = new Button( "Abbrechen" ); String t = b.getLabel(); b.setLabel( "Cancel" ); // Beschriftung holen // Beschriftung setzen 1.2.3 Die Klasse Checkbox Checkboxes sind Schalter-Felder, die durch Anklicken aktiviert oder deaktiviert werden können und eine eigene Beschriftung besitzen können: Checkbox c1 = new Checkbox(); // Unbeschriftet Checkbox c2 = new Checkbox( "mit Mayo, bitte!" ); String t = c1.getLabel(); c1.setLabel( "Mit Ketchup" ); // Beschriftung holen // Beschriftung setzen boolean angekreuzt = c1.getState(); // Status holen c1.setState( true ); // Status setzen HRZ Einführungspraktikum Java 107 VI. Graphische Benutzeroberflächen mit dem AWT 108 1.3 Arbeiten mit Containern Container-Instanzen haben die Aufgabe, Elemente der Oberfläche, wie z. B. Buttons in sich aufzunehmen. Container ist eine abstrakte Klasse. Entsprechend den verschiedenen Arten von Containern existieren die Subklassen Panel, ScrollPane (seit JDK 1.1) und Window. Die Klasse Window ist die Superklasse für Container, die auf dem Bildschirm bewegbar sind und in einem eigenen Fenster dargestellt werden. Es existieren die Subklassen Frame und Dialog. Die Klasse ScrollPane ist ein besonderer Container, der die in ihm enthaltene Komponente automatisch mit Scrollbalken versieht, um den sichtbaren Bereich der Komponente, z. B. eines Bildes, zu verändern. Die Klasse Panel ist ein Container, der im Gegensatz zu einem Window fest an einer bestimmten Position der Oberfläche fixiert ist. Da ein Container auch wie die einfachen Komponenten eine Subklasse von Component ist, lassen sich Panels und ScrollPanes ineinander verschachteln, was vielfältige Layout-Möglichkeiten bietet. Da jedes Panel seinen eigenen Layout-Manager besitzt, lassen sich so komplexe Anordnungen realisieren. Außerdem kann man durch Vererbung eigene Panel-Subklassen erzeugen und seine Oberflächen damit aus wiederverwendbaren Teilen zusammensetzen (Beispiele folgen). Die Klasse Applet ist eine Subklasse von Panel. Daher ist jedes Applet auch gleichzeitig ein Container, so daß sich in einem Applet auch AWT-Komponenten anordnen lassen. Applets besitzten gegenüber normalen Panels nur die bereits kennengelernten zusätzlichen Eigenschaften, wie z. B. mit dem Browser interagieren zu können( init(), start(), stop() usw.). Über die Methoden add() und remove() lassen sich Komponenten zu einem Container hinzufügen bzw. entfernen, so daß man auch dynamisch die Bestandteile der Oberfläche verändern kann. Beispiel: public class GUIApplet extends Applet { protected Button b; protected Checkbox c; public void init() { b = new Button( "Hit Me!" ); c = new Checkbox( "Diesen Unsinn nicht mehr anzeigen" ); add( b ); add( c ); } } Hinweis: Das Positionieren der Elemente ist Aufgabe eines sog. Layout-Managers. Mehr dazu später! HRZ Einführungspraktikum Java 108 VI. Graphische Benutzeroberflächen mit dem AWT 109 1.4 Auf Ereignisse in der Oberfläche reagieren 1.4.1 Auf Buttons reagieren Wenn ein Button angeklickt wird, wird ein ActionEvent erzeugt, den ein registrierter ActionListener in einer Methode actionPerformed() als Parameter erhält. public class Buttons extends Applet implements ActionListener { protected Button bBlau, bRot; public void init() { bBlau = new Button( "Blau" ); bRot = new Button( "Rot" ); add( bBlau ); add( bRot ); bBlau.addActionListener( this ); bRot .addActionListener( this ); } public void actionPerformed( ActionEvent e ) { Graphics g = getGraphics(); g.setColor( ( e.getSource() == bBlau ? Color.blue : Color.red ) ); g.drawRect( 0, 0, 100, 100 ); g.dispose(); } } 1.4.2 Auf Checkboxes unmittelbar reagieren public class Ankreuzen extends Applet implements ItemListener { protected Checkbox c; public void init() { c = new Checkbox( "Diesen Unsinn nicht mehr anzeigen" ); c.addItemListener( this ); add( c ); } public void itemStateChanged( ItemEvent e ) { System.out.println( "Zustand von c hat sich geändert" ); } } HRZ Einführungspraktikum Java 109 VI. Graphische Benutzeroberflächen mit dem AWT 110 1.5 Arbeiten mit Texteingabe-Komponenten 1.5.1 Die Klasse TextField Diese Klasse stellt ein einzeiliges Eingabefeld für Text zur Verfügung. Es ist möglich, statt der Eingabe ein anderes Zeichen als Echo auszugeben (sinnvoll bei Feldern, die z. B. Passwörter abfragen). Auch der Zugriff auf den markierten Teil der Eingabe ist möglich. TextField anlegen TextField TextField TextField TextField tname tname tname tname = = = = new new new new TextField(); // Leer TextField( 20 ); // Breite 20 TextField( "Willi" ); // Mit Vorgabe TextField( "Willi", 20 ); Echo-Zeichen setzen und abfragen TextField tpasswd = new TextField( 20 ); tpasswd.setEchoChar( '*' ); char c; if ( tpasswd.echoCharIsSet() ) c = tpasswd.getEchoChar(); Text abfragen und setzen String name = tname.getText(); tname.setText( "Hallo Welt!" ); Markierten Textteil abfragen und Markierungsbereich setzen String markiert = tname.getSelectedText(); int von = tname.getSelectionStart(); int bis = tname.getSelectionEnd(); tname.select( 0, 10 ); // Markiert die ersten 10 Zeichen tname.selectAll(); // Markiert alles Cursorposition abfragen und setzen int pos = tname.getCaretPosition(); // Cursor hinter den letzten Buchstaben setzen: tname.setCaretPosition( tname.getText().length() ); Textfeld aktivieren und deaktivieren tname.setEditable( false ); // Keine Eingabe möglich, grau if ( tname.isEditable() ) doSometing(); HRZ Einführungspraktikum Java 110 VI. Graphische Benutzeroberflächen mit dem AWT 111 1.5.2 Die Klasse TextArea Während ein TextField für eine einzeilige Information gedacht ist, stellt eine TextArea einen größeren, rechteckigen Bereich für Textein-/ausgabe zur Verfügung, der auch mit endsprechenden Scrollbalken ausgestattet ist. TextArea anlegen TextArea guestbook = new TextArea(); TextArea guestbook = new TextArea( "Mit Vorgabe" ); TextArea guestbook = new TextArea( 5, 50 ); // Zeilen, Sp. TextArea guestbook = new TextArea( "Mit Vorgabe", 5, 50 ); TextArea guestbook = new TextArea( "Mit Vorgabe", 5, 50, SCROLLBARS_HORIZONTAL_ONLY ); // etc., siehe API-Doku... Textinhalt verändern guestbook.append( "Killroy was here" ); guestbook.insert( "me too!", 30 ); // Text an Position 30 einfügen guestbook.replaceRange( "Test", 18, 24 ); // Zeichen 18 bis 23 durch "Test" ersetzen Weitere Methoden TextArea und TextField sind Subklassen von TextComponent. Aus diesem Grunde stehen die bereits zuvor bei TextField erklärten Methoden bis auf die Methoden zur Behandlung von Echo-Zeichen auch für eine TextArea zur Verfügung, denn sie werden aus TextComponent vererbt. Ereignisse bei Textkomponenten Wenn innerhalb einer TextComponent die Enter-Taste gedrückt wird, wird ein ActionEvent erzeugt. In diesem Fall liefert die Methode getActionCommand() der ActionEvent den eingegebenen Text, getSource() liefert die auslösende TextComponent (vgl. Button-Beispiel). Wenn sich der Text in einer TextComponent verändert, erzeugt dies jedesmal einen TextEvent. Als TextListener implementiert man die entsprechende Methode textValueChanged( TextEvent e); HRZ Einführungspraktikum Java 111 VI. Graphische Benutzeroberflächen mit dem AWT 112 2. Arbeiten mit Layout-Managern 2.1 Allgemeines zu Layout-Managern Jeder Container besitzt einen ihm zugeordneten Layout-Manager. Dieser hat die Aufgabe, die Komponenten in dem Container nach bestimmten Regeln anzuordnen und dabei auch ihre Größe festzulegen. Um eine plattformunabhängige Lösung zu ermöglichen, sind im Paket java.awt nur Layout-Manager zur Verfügung gestellt, die relative Positionierungsangaben erlauben. Es ist jedoch möglich, eigene Layout-Manager zu schreiben, die auch direkte Positionierungen an bestimmten Koordinaten erlauben. Borland JBuilder besitzt z. B. einen derartigen LayoutManager XYLayout. Java bietet die fünf Layout-Manager FlowLayout, BorderLayout, GridLayout, GridBagLayout und CardLayout. Durch Aufruf der Methode Container.setLayout() wird ein LayoutManager für einen Container festgelegt. Dazu wird eine Instanz des LayoutManagers erzeugt und als Parameter übergeben. Der LayoutManager interagiert selbständig mit dem Container, um das Layout durchzuführen. Beispiel: import java.awt.*; import java.applet.*; public class GUIApplet extends Applet { protected Button b; public void init() { setLayout( new BorderLayout() ); // Layout festlegen b = new Button( "Hit Me!" ); // Komponente erzeugen add( BorderLayout.CENTER, b ); // Komponente hinzufügen } } 2.2 FlowLayout FlowLayout ist das Standard-Layout der Container. In diesem sehr einfachen Layout werden die Komponenten bei jedem add() einfach der Reihe nach in einer Zeile hintereinander angeordnet, bis der rechte Rand erreicht ist. Dann erfolgt quasi ein Zeilenumbruch. Die Anordnung innerhalb der Zeilen kann wie folgt beeinflußt werden: setLayout( new FlowLayout() ); // Default: Zeilen zentrieren setLayout( new FlowLayout( Ausrichtung ) ); // FlowLayout.LEFT, FlowLayout.CENTER, FlowLayout.RIGHT setLayout( new FlowLayout( Ausrichtung, h, v ); // Zwischen den Komponenten h Pixel horizontal, v Pixel // vertikaler Abstand (Ohne Angabe jeweils 5 Pixel) HRZ Einführungspraktikum Java 112 VI. Graphische Benutzeroberflächen mit dem AWT 113 2.3 BorderLayout Dieses Layout ist gut geeignet, um bis zu fünf Komponenten in einem Container am linken, rechten, oberen und unteren Rand und im Zentrum anzuordnen. Der Platz für leere Teile des BorderLayouts darf beliebig schrumpfen, um für die anderen Komponenten Platz zu schaffen. setLayout( new BorderLayout() ); setLayout( new BorderLayout( 10, 10 ) ); // 10 * 10 Pixel Abstand zwischen den Bereichen Bei Aufruf von add() wird mit einer Richtungsangabe angezeigt, wo die Komponente platziert werden soll: add( BorderLayout.NORTH, new Label( "Hier ist oben" ) ); add( BorderLayout.CENTER, new Label( "Hier die Mitte" ) ); 2.4 GridLayout Bei einem GridLayout wird der Container-Bereich tabellenartig in ein Gitter unterteilt. Jede Zelle des Gitters ist gleich groß. setLayout( new GridLayout( 3, 2 ) ); // Gitter mit drei Zeilen und zwei Spalten setLayout( new GridLayout( 3, 2, 10, 10 ) ); // 10 * 10 Punkte Abstand zwischen den Zellen Beim Füllen des Containers wird oben links begonnen und bei jedem add() die jeweils nächste Zelle benutzt. Zellen lassen sich "überspringen", indem man in ihnen z. B. ein leeres Label einfügt. HRZ Einführungspraktikum Java 113 VI. Graphische Benutzeroberflächen mit dem AWT 114 2.5 GridBagLayout GridBagLayout ist der flexibelste Layout-Manager. Auch hier wird der Containerbereich in ein Gitter unterteilt, jedoch kann man einzelne Komponenten direkt in eine bestimmte Zelle positionieren und hat wesentlich mehr Steuerungsmöglichkeiten. Die Größe der einzelnen Zellen hängt von den verwendeten Komponenten und Parametern ab. Am ehesten ist das Verhalten eines GridBagLayouts mit dem einer HTML-Tabelle vergleichbar, was die Ausrichtungsmöglichkeiten und Reaktion auf nicht benutzte Zellen, Zeilen und Spalten und die Berechnung der Zellengrößen betrifft. Anordnung der Komponenten Die Anordnung der Komponenten erfolgt nicht direkt, sondern mit Hilfe eines zusätzlichen Objektes vom Typ GridBagConstraints. Zunächst muß eine Instanz von GridBagConstraints erzeugt werden. Anschließend werden die Instanzvariablen dieses Objektes angepaßt und es wird beim Aufruf von add() mit übergeben: public void init() { ... // Erzeuge und setze GridBagLayout GridBagLayout gbl = new GridBagLayout(); setLayout( gbl ); ... GridBagConstraints gbc = new GridBagConstraints(); ... // Setze Parameter für die Komponente gbc.fill = GridBagConstraints.BOTH; gbc.weightx = 1.0; gbc.gridx = 2; gbc.gridy = 1; // usw., siehe API-Dokumentation ... // Füge die Komponente mit diesen Parametern hinzu Button bOk = new Button( "Ok" ); gbl.setConstraints( bOk, gbc ); add( bOk ); ... } Die GridBagConstraints-Instanz muß nicht für jede hinzuzufügende Komponente neu erzeugt werden, sondern kann für alle Komponenten wiederverwendet werden, da der Layout-Manager sich beim Aufruf von setConstraints() 'Kopien' der gesetzten Werte macht: ... // Nach Einfügen des Buttons bOk geht es weiter: gbc.gridx = 3; Button bAbbr = new Button( "Abbrechen" ); gbl.setConstraints( bAbbr, gbc ); add( bAbbr ); HRZ Einführungspraktikum Java 114 VI. Graphische Benutzeroberflächen mit dem AWT 115 Es lassen sich die folgenden Instanzvariablen eines GridBagConstraints setzen: public int gridx; X-Koordinate der Zielzelle, die erste Zelle hat den Index 0, int-Wert oder GridBagConstraints.RELATIVE = hinter der zuvor hinzugefügten Komponente plazieren public int gridy; Entsprechend für die Y-Koordinate... public int gridwidth; Breite des Darstellungsbereiches, als Anzahl der überspannenden Zellen, int-Wert oder GridBagConstraints.REMAINDER = Komponente als letzte ihrer Zeile plazieren GridBagConstraints.RELATIVE = Komponente als vorletzte ihrer Zeile plazieren public int gridheight; Entsprechend für die Höhe des Darstellungsbereiches... public int fill; Anpassung der Komponentengröße an die ihres Darstellungsbereiches, falls dieser mehr Platz bietet, als eigentlich nötig ist: GridBagConstraints.NONE, HORIZONTAL, VERTICAL, BOTH public int anchor; Verankerung der Komponente in ihrem Darstellungsbereich, falls dieser mehr Platz bietet, als eigentlich nötig ist: GridBagConstraints.CENTER, NORTH, NORTHEAST, EAST, usw. public double weightx; public double weighty; Gewichtung dieser Komponente bei der Vergabe von zusätzlich verfügbarem Platz, relativ für die horizontale und vertikale Anpassung public int ipadx; public int ipady; Innerer leerer Rand um die Komponente in Pixeln, vergrößert ihre minimale Größe public Insets insets; Äußerer Mindestabstand zwischen Komponente u. dem Rand ihres Darstellungsbereiches Beispiel: HRZ Einführungspraktikum Java 115 VI. Graphische Benutzeroberflächen mit dem AWT 116 2.6 CardLayout CardLayout ist in gewisser Weise ein Sonderfall. Er teilt den Container in einer zusätzlichen Dimension auf und ermöglicht es, ähnlich wie unter einer Karteikarte die Komponenten abzulegen. Dies sind in der Regel Panels oder Canvas-Instanzen (mehr zu diesen Klassen später). Beim Aufruf der add-Methode wird der Name der Karteikarte mit übergeben. setLayout( new CardLayout() ); Panel one = new Panel(); // hier würde man noch Komponenten dem Panel one hinzufügen... // ... // Karte unter dem Namen "eins" hinzufügen: add( "eins", one ); Panel two = new Panel(); add( "zwei", two ); Panel three = new Panel(); add( "drei", three ); //... //... Es sind nicht alle Karten zeitgleich zu sehen, sondern es ist immer nur eine im Vordergrund. Die anderen Karten lassen sich dann vergleichbar mit einer Diaschau bei Bedarf in den Vordergrund bringen, es kann auch 'geblättert' werden: show( this, "zwei" ); // Karte "zwei" anzeigen show( this, "drei" ); // Karte "drei" anzeigen next( this ); // Nächste Karte anzeigen previous( this ); // Zurück zur vorhergehenden Karte first( this ); // Sprung zur ersten Karte last( this ); // Sprung zur letzten Karte Beachte: Auf der Oberfläche selbst sind keine Karteikartenreiter mit den entsprechenden Labels zu sehen! Die Labels dienen nur anwendungsintern der Unterteilung in verschiedene Bereiche. 2.7 Arbeiten ohne LayoutManager Mit setLayout( null ) kann man erreichen, daß für diesen Container kein LayoutManager verwendet wird. Man muß dann selbst die Größe und Position aller Komponenten im Container festlegen. Dies geschieht durch Aufruf der Methode setBounds( int x, int y, int width, int height) der Komponente: setLayout( null ); Button b = new Button( "Klick mich" ); b.setBounds( 20, 20, 100, 100 ); // Geerbt von Component add( b ); HRZ Einführungspraktikum Java 116 VI. Graphische Benutzeroberflächen mit dem AWT 117 3. Mehr über Komponenten 3.1 Allgemeine Eigenschaften von Komponenten Nahezu alle AWT-Klassen sind Subklassen der Klasse Component und erben damit deren Eigenschaften. Dazu gehört z. B. die Möglichkeit, Mauscursor, Standard-Font und Farben zu setzen etc. Auch die im Applet verwendeten Methoden paint(), repaint(), update() etc. sind von Component geerbt. Klassen können die Methode paint() überschreiben, um ihre Darstellung auf der Oberfläche zu zeichnen. Im Fall eines Containers wird zunächst dessen (standardmäßig leeres) paint() aufgerufen und danach erst die enthaltenen Komponenten gezeichnet. Überschreibt man paint() für einen Container (Applet-Beispiele!) und fügt dann Komponenten hinzu, überlagern diese also den durch paint() gezeichneten Hintergrund. Nützliche Methoden der Klasse Component: // c sei eine Komponente, z. B. ein Button c.setCursor( new Cursor( Cursor.WAIT_CURSOR ) ); // Eieruhr-Cursor als aktuellen Cursor setzen // DEFAULT, CROSSHAIR, MOVE, TEXT, WAIT sind möglich c.setBackground( Farbe ); c.setForeground( Farbe ); // Vorder- und Hintergrundfarbe c.setFont( Font ); // Standardfont setzen c.setVisible( boolean ); // Ein- und Ausblenden der Komponente c.setEnabled( boolean ); // Eingabemöglichkeit einstellen, z. B. bei einem Button // Setzen auf false verhindert die Auslösung eines Events 3.2 Ereignisverarbeitung von Komponenten Die bereits behandelten Key- und Mouse-Events stehen in gleicher Weise auch für jede andere Component zur Verfügung, so daß man sich dafür dort registrieren kann. Beim Verschieben, Ändern der Größe, Ein- und Ausblenden einer Komponente entsteht ein ComponentEvent, für den man sich bei der Komponente als ComponentListener registrieren lassen kann. ComponentListener implementieren die entsprechenden Methoden componentShown(), componentHidden(), componentMoved() und componentResized(). Durch Aufruf der Methode getComponent() des ComponentEvents kann man die auslösende Komponente ermitteln. Beim Wechsel des Eingabefocus entsteht in den betroffenen Komponenten ein FocusEvent, für den man sich als FocusListener registrieren kann. Die entsprechenden Methoden lauten focusGained() und focusLost(). Den übergebenen FocusEvent kann man mit getSource() nach der auslösenden Komponente fragen. HRZ Einführungspraktikum Java 117 VI. Graphische Benutzeroberflächen mit dem AWT 118 4. Mehr über Container 4.1 Container-Events Wird eine Komponente einem Container hinzugefügt oder weggenommen, wird ein ContainerEvent durch den Container erzeugt. Ein ContainerListener implementiert entsprechende Methoden componentAdded() und componentRemoved(), der ContainerEvent bietet die Methode getContainer() und die Methode getChild(), die die Komponente zurückliefert. 4.2 Verschachtelte Layouts mit Panels Mit Hilfe der Klasse Panel, die einen leeren, unbeweglichen Container darstellt, kann man besonders gut verschiedene Layouts miteinander kombinieren, indem man Panels innerhalb von anderen Panels erzeugt. Beispiel: import java.applet.*; import java.awt.*; public class PanelTest extends Applet { public void init() { setLayout( new GridLayout( 1, 2 ) ); Panel p1 = new Panel(); p1.setLayout( new BorderLayout() ); p1.add( "North", new Button( "p1-North" ) ); p1.add( "South", new Button( "p1-South" ) ); Panel p2 = new Panel(); p2.setLayout( new FlowLayout() ); p2.add( new Button( "p2-1" ) ); p2.add( new Button( "p2-2" ) ); p2.add( new Button( "p2-3" ) ); p1.add( "Center", p2 ); add( p1 ); add( new Button( "Test" ) ); } } HRZ Einführungspraktikum Java 118 VI. Graphische Benutzeroberflächen mit dem AWT 119 4.3 Die Klasse ScrollPane Ein ScrollPane ist ein spezieller Container, der genau eine Komponente aufnimmt und diese automatisch horizontal und vertikal scrollt. Die Komponente kann also größer sein als das ScrollPane, das sie aufnimmt. ScrollPane erzeugen ScrollPane sp = new ScrollPane(); ScrollPane sp = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS); // ALWAYS, NEVER oder AS_NEEDED angeben Panel content = new Panel(); ... // Panel content mit Komponenten füllen sp.add( content ); sp.setSize( 200, 100 ); // Breite und Höhe festlegen add( sp ); Programmgesteuert scrollen Dimension d = sp.getViewportSize(); // Dimensionen Ausschnitt sp.setScrollPosition( 50, 200 ); // Scrolle nach x, y Point p = sp.getScrollPosition(); // Aktuelle Position Schieberegler beeinflussen sp.getVAdjustable().setUnitIncrement( 1 ); // vertikal sp.getHAdjustable().setUnitIncrement( 2 ); // horizontal ... vergleiche Möglichkeiten im Interface Adjustable HRZ Einführungspraktikum Java 119 VI. Graphische Benutzeroberflächen mit dem AWT 120 5. Weitere einfache Komponenten 5.1 CheckboxGroup Checkbox-Objekte können zu einer CheckboxGroup zusammengefaßt werden. Im Unterschied zu einzeln verwendeten Checkboxen kann in einer CheckboxGroup immer nur eine der Checkboxen ausgewählt sein. Damit lassen sich also "RadioButtons" realisieren. CheckboxGroup anlegen Der CheckboxGroup-Instanz werden nacheinander die einzelnen Checkboxen zugeordnet, indem man für diese Checkboxen einen anderen Konstruktor wie folgt benutzt: Checkbox cbJa, cbNein, cbVielleicht, cbKeineAhnung; CheckboxGroup wahl = new CheckboxGroup(); ... cbJa = new Checkbox( "Ja", wahl, cbNein = new Checkbox( "Nein", wahl, cbVielleicht = new Checkbox( "Vielleicht", wahl, cbKeineAhnung = new Checkbox( "Keine Ahnung", wahl, add( add( add( add( cbJa cbNein cbVielleicht cbKeineAhnung false false true false ); ); ); ); ); ); ); ); Beachte: Eine der Checkboxen muß als Vorgabe aktiviert sein (vgl. cbVielleicht). Die CheckboxGroup selbst muß nicht dem Container hinzugefügt werden, somit lassen sich die Teile der Checkboxgroup frei und nicht zwingend beieinander positionieren. Die Klasse Checkbox besitzt Methoden getCheckboxGroup() und setCheckboxGroup(), mit denen man zusätzlich die Zugehörigkeit beeinflussen kann. Wird eine Auswahl gemacht, erzeugt die betroffene Checkbox einen ItemEvent, auf den man entsprechend reagieren kann (siehe vorne). Bestimmen und Setzen der ausgewählten Checkbox Checkbox gew = wahl.getSelectedCheckbox(); if ( gew == cbJa ) { ... } wahl.setSelectedCheckbox( cbNein ); HRZ Einführungspraktikum Java 120 VI. Graphische Benutzeroberflächen mit dem AWT 121 5.2 Choice Instanzen der Klasse Choice repräsentieren Auswahlfelder und werden je nach System als Popup- oder Dropdown-Liste dargestellt und dienen dazu, aus einer Liste einen Wert auszuwählen Choice anlegen Nach dem Erzeugen einer Choice-Instanz werden ihr mit addItem einzelne Listenwerte hinzugefügt, dann wird das Auswahlfeld dem Darstellungsbereich hinzugefügt: Choice cPizza = cPizza.addItem( cPizza.addItem( cPizza.addItem( cPizza.addItem( add( cPizza ); new Choice(); "Pizza Salami" "Pizza Tonno" "Pizza Hawaii" "Pizza Calzone" ); ); ); ); Werte hinzufügen und entfernen cPizza.insert( "Pizza Mista", 2 ); cPizza.remove( 1 ); cPizza.remove( "Pizza Salami" ); cPizza.removeAll(); Anzahl und Inhalte abfragen int String anzahl = cPizza.getItemCount(); p = cPizza.getItem( 2 ); Auswahl abfragen String pizzaName = cPizza.getSelectedItem(); int pizzaNr = cPizza.getSelectedIndex(); Auswahl setzen cPizza.select( "Pizza Hawaii" ); cPizza.select( 3 ); Auf Ereignisse reagieren Ebenso wie eine Checkbox sendet auch eine Choice bei einer Auswahl einen ItemEvent. Mit Hilfe der Methode getItemSelectable() des ItemEvents kann man in der entsprechenden Methode itemStateChanged() des ItemListeners die auslösende Choice ermitteln. HRZ Einführungspraktikum Java 121 VI. Graphische Benutzeroberflächen mit dem AWT 122 5.3 List List-Instanzen sind rechteckige Bereiche, in denen eine Liste dargestellt wird. Aus dieser können auch mehrere Werte ausgewählt werden. Ist die Liste länger als der Darstellungsbereich, werden automatisch Bildlaufleisten hinzugefügt. Liste anlegen Die Anzahl der am Bildschirm sichtbaren Zeilen kann im Konstruktor vorgegeben werden. Außerdem muß angegeben werden, ob jeweils nur ein Element (false) oder mehrere (true) ausgewählt werden dürfen: List plan = new List(); // Keine Mehrfachauswahl List plan = new List( 3, true ); // Mehrfachauswahl plan.addItem( plan.addItem( plan.addItem( plan.addItem( plan.addItem( add( plan ); "Merkur" "Venus" "Erde" "Mars" "Jupiter" ); ); ); ); ); Mit der Liste arbeiten List bietet sehr viele Methoden, die z. B. wie folgt eingesetzt werden können: plan.setMultipleMode( false ); if ( plan.isMultipleMode() ) ... plan.addItem( "Saturn" ); plan.addItem( "Pluto", 0 ); // Vorne einfügen plan.replaceItem( "Uranus", 2 ); // Nr. 2 ersetzen plan.delItem( 3 ); String s String sel String[] sels int isel int[] isels = = = = = plan.getItem( 1 ); plan.getSelectedItem(); plan.getSelectedItems(); plan.getSelectedIndex(); plan.getSelectedIndexes(); int num = plan.getItemCount(); ... und einige mehr, siehe API-Dokumentation! Auf Ereignisse reagieren Wie die Choice erzeugt auch die List entsprechende ItemEvents, wenn Items selektiert oder deselektiert werden. Ein Doppelklick auf einen Eintrag löst einen ActionEvent aus... HRZ Einführungspraktikum Java 122 VI. Graphische Benutzeroberflächen mit dem AWT 123 6. Frames und Dialoge 6.1 Die Klasse Frame Eigenständige Fenster sind im AWT Instanzen der Klasse Frame. Diese können dann aus einem Applet oder einer Application heraus erzeugt und angezeigt werden. Beispiel: import java.awt.*; public class FrameTester { public static void main( String[] args ) { Frame f = new Frame( "Fenstertitel" ); // Erzeugen // f.setTitle( String s ); // (Titel setzen) // String t = f.getTitle(); // (Titel holen) f.setSize( 300, 200 ); f.setLocation( 0, 0 ); //f.setIconImage( Image i ); f.setLayout( new FlowLayout() ); // // // // Fenstergröße Fensterposition (Icon des Fensters) Layout-Manager Button b = new Button( "Ok" ); //... f.add( b ); // Button erzeugen // Listener erzeugen... // Komponenten hinzuf. f.setVisible( true ); f.requestFocus(); // Fenster anzeigen // Focus erbitten try{ Thread.sleep( 10000 ); } // 10 Sek. warten catch( InterruptedException ex ){} f.setVisible( false ); f.dispose(); System.exit( 0 ); // Fenster ausblenden // Ressourcen freigeben // Exit-Code aus Anwendung zurückgeben } } Hinweis: Die Auflösung des Bildschirms läßt sich über die Klasse Toolkit ermitteln, so daß man z. B. den Frame als Vollbild oder zentriert anzeigen kann: Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); int w = d.width; int h = d.height; // Breite und Höhe in Pixeln HRZ Einführungspraktikum Java 123 VI. Graphische Benutzeroberflächen mit dem AWT 124 6.2 Auf Fensterereignisse reagieren Bei Auftreten entsprechender Ereignisse in Frame oder Dialog wird ein WindowEvent ausgelöst, den beim Fenster registrierte WindowListener bearbeiten. Es existieren folgende Methoden public void windowActivated( WindowEvent e ) // Fenster gerade neu erzeugt oder wieder im Vordergrund public void windowDeactivated( WindowEvent e ) // Fenster geht in den Hintergrund public void windowIconified( WindowEvent e ) // Fenster auf Icongröße verkleinert public void windowDeiconified( WindowEvent e ) // Fenster aus Icon wieder vergrößert public void windowOpened( WindowEvent e ) // Fenster wurde geöffnet public void windowClosing( WindowEvent e ) // Fenster soll geschlossen werden, z. B. mit ALT+F4 public void windowClosed( WindowEvent e ) // Fenster wurde geschlossen Java-Fenster lassen sich nicht automatisch schließen. Die Applikation muß das Ereignis windowClosing abfangen und das Fenster dann kontrolliert schließen, z. B. wie folgt: import java.awt.*; public class FrameTester { protected static Frame f; public static void main( String[] args ) { f = new Frame( "Fenstertitel" ); f.addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e ) { f.setVisible( false ); f.dispose(); System.exit( 0 ); } } ); f.setSize( 300, 200 ); f.setLocation( 0, 0 ); f.setVisible( true ); f.requestFocus(); } } Die Vorgehensweise ist für die Klassen Frame und Dialog identisch. HRZ Einführungspraktikum Java 124 VI. Graphische Benutzeroberflächen mit dem AWT 125 6.3 Die Klasse Dialog Neben Frames existiert noch eine weitere Fensterklasse, die Klasse Dialog. Sie realisiert ein Dialogfenster, z. B. einen Suchen-Dialog. Dialogfenster sind an einen Frame gebunden, der sie "besitzt", daher wird eine Referenz auf diesen Frame im Konstruktor mit übergeben. Dialogfenster können modal sein, d. h. sie blockieren den Eingabefocus, so daß solange der Dialog angezeigt wird, kein anderes Fenster aktiv sein kann. Beispiel: Dialog erzeugen Frame frame; // ... frame erzeugen usw. Dialog d1 = new Dialog( frame, false ); // nicht modal Dialog d2 = new Dialog( frame, "Suchen", true );// modal Modal-Modus setzen / ermitteln boolean modal = d1.isModal(); d2.setModal( false ); //Ist der Dialog modal? Größenänderung unterbinden / ermöglichen boolean res = d1.isResizable(); //Ist das Fenster veränderbar? d1.setResizable( false ); 6.4 Die Klasse FileDialog Java bietet einen plattformunabhängigen Zugriff auf den (plattformabhängigen) DateiDialog über die Klasse FileDialog. Beispiele: FileDialog dLoad = new FileDialog( frame, "Datei öffnen" ); dLoad.show(); // blockiert bis Auswahl erfolgt ist String datei = dLoad.getFile(); String verz = dLoad.getDirectory(); if( datei != null ) ... FileDialog dSave = new FileDialog( frame, "Datei speichern", FileDialog.SAVE ); // Man kann Vorgaben für Datei und Verzeichnis machen: // dSave.setFile( "img0000.gif" ); // dSave.setDirectory( "c:\\windows\\temp" ); dSave.show(); String datei = dSave.getFile(); String verz = dSave.getDirectory(); if( datei != null ) ... HRZ Einführungspraktikum Java 125 VI. Graphische Benutzeroberflächen mit dem AWT 126 7. Menüs und Menüleisten Die Klassen MenuBar, Menu, MenuItem und CheckboxMenuItem ermöglichen den Aufbau von Menüs in graphischen Oberflächen. Nur ein Frame darf eine MenuBar besitzen, kein Dialog, kein Applet usw.! 7.1 Menüs definieren MenuBar mb = new MenuBar(); Menu mDatei = new Menu( "Datei" ); mDatei.add( new MenuItem( "Neu" ) ); ... Menu mBearb = new Menu( "Bearbeiten" ); mBearb.add( new MenuItem( "Ausschneiden" ) ); mBearb.add( new MenuItem( "Kopieren" ) ); mBearb.add( new MenuItem( "Einfügen" ) ); mBearb.addSeparator(); mBearb.add( new CheckboxMenuItem( "AutoDelete an" ) ); mBearb.disable(); Menu mHilfe = new Menu( "?" ); mHilfe.add( new MenuItem( "Info" ) ); mb.add( mDatei ); mb.add( mBearb ); mb.setHelpMenu( mHilfe ); Frame f; ... // ... von irgedwo her f.setMenuBar( mb ); // Das geht nur in einem Frame... Hinweis: Da Menu eine Subklasse von MenuItem ist, ist jedes Menu also auch ein MenuItem, d. h. man kann also Untermenüs erzeugen... 7.2 Auf Menüereignisse reagieren Wenn der Benutzer einen Menüpunkt anklickt, wird ein ActionEvent durch das entsprechende MenuItem erzeugt und an die beim MenuItem registrierten ActionListener weitergeleitet. Für jedes relevante MenuItem muß man bei diesem einen Listener registrieren, d. h. innerhalb des gleichen Menüs können auch verschiedene Listener auf verschiedene MenuItems reagieren. In der Methode actionPerformed des Listeners kann der ActionEvent e untersucht werden: String menuItemLabel = e.getActionCommand(); MenuItem menuItem = (MenuItem)( e.getSource() ); HRZ Einführungspraktikum Java 126 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes 127 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes HRZ Einführungspraktikum Java 127 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes 128 1. Ein- und Ausgabeströme in Java Klassen für Ein-/Ausgabeoperationen sind im Paket java.io untergebracht. Ein- und Ausgabe in Java ist stromorientiert: Man liest aus einem Strom von Eingabedaten, der z. B. aus dem Netz oder aus einer Datei kommt und schreibt Daten in einen Ausgabestrom, der als Senke z. B. ebenfalls eine Netzverbindung, eine Datei o. ä. haben kann. Als Basis aller Ein- und Ausgabeströme existieren die Klassen InputStream und OutputStream für elementare, Byte-orientierte Ein- und Ausgabe. Der StandardEingabestrom System.in ist z. B. eine Instanz von InputStream. Auf diesen Basisströmen bauen spezialisiertere Stream-Klassen auf, z. B. PrintStream zur Ausgabe (System.out und System.err sind PrintStreams). Byteorientierte Ein- und Ausgabe mit InputStream und OutputStream Alle Basis-Ein- und Ausgabeströme besitzen Methoden read() und write() zum Lesen und Schreiben von Bytes. Mit den Unterklassen FileInputStream und FileOutputStream kann man z. B. Dateien byteweise lesen und schreiben: Beispiel: Kopieren einer Datei import java.io.*; public class CopyFile { public static void main( String[] args ) throws Exception { InputStream is = new FileInputStream ( args[ 0 ] ); OutputStream os = new FileOutputStream( args[ 1 ] ); byte[] buffer = new byte[4096]; // Puffer für 4 KByte int n; // Anzahl Bytes die gelesen wurden while( ( n = is.read( buffer, 0, buffer.length ) ) != -1 ) // Gelesene Bytes werden in das Array buffer geschrieben // Das Array wird ab Index 0 gefüllt // Es werden maximal buffer.length Bytes gelesen // Wenn der Eingabestrom beendet ist, gibt read –1 zurück os.write( buffer, 0, n ); // Schreibe n Bytes beginnend ab Index 0 aus dem Array // in den Ausgabestrom is.close(); os.close(); } } Aufruf: java CopyFile c:\x\y\quelle.dat d:\a\b\ziel.dat HRZ Einführungspraktikum Java 128 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes 129 Weitere interessante Klassen zur Ein- und Ausgabe auf Byte-Ebene Im Paket java.util.zip existieren Ein- und Ausgabeströme zum Lesen und Schreiben von ZIP- und GZIP-Strömen und -Dateien. Die Klassen DataInputStream und DataOutputStream ermöglichen es, einfache Java-Datentypen in einen Ausgabestrom zu schreiben bzw. wieder daraus zu lesen. Die Klasse RandomAccessFile ermöglicht statt einer sequentiellen Verarbeitung den wahlfreien Zugriff auf bestimmte Positionen einer Datei. Reader- und Writer-Klassen Da die Stream-Klassen auf der Basis von einzelnen Bytes arbeiten, sind sie nicht überall direkt dazu verwendbar, Zeichen und Strings zu lesen bzw. zu schreiben, da Zeichen ja Java-intern als UniCode-Zeichen in 16 Bit abgelegt sind, in einer Datei in der Regel aber als 8-Bit-Zeichen stehen. Daher hat man ab JDK 1.1 die Klassen für Byteströme um sogenannte Reader- und Writer-Klassen ergänzt, die aus einem Bytestrom einen Strom von UniCode-Zeichen machen und umgekehrt. Beim Lesen einer Datei wandelt ein Reader z. B. die 8-BitDarstellung der jeweiligen Codepage in die entsprechende 16-Bit-Darstellung des UniCode um. Dabei kann optional auch angegeben werden, in welcher Codepage die Daten codiert sind. OuputStreamWriter: Wandelt Character-Streams in Byte-Streams um FileWriter: Schreibt zeichenweise in eine Datei, Codepage kann z. B. auch angegeben werden. PrintWriter: Gibt alle Basis-Datentypen als Zeichenkette aus, z. B. System.out BufferedWriter: Puffert Ausgabeströme StringWriter: Schreibt Daten in einen StringBuffer InputStreamReader: Wandelt einen Byte-Stream in einen Character-Stream um FileReader: Liest aus einer Datei PushbackReader: Erlaubt es, gelesene Zeichen wieder in den Strom zurückzuschieben BufferedReader: Puffert Leseoperationen und liest komplette Zeilen LineNumberReader: BufferedReader, der zusätzlich Zeilen mitzählt StringReader: liest aus einem String Datenströme und Reader / Writer werden bei der Erzeugung mit new() oft ineinander verschachtelt (siehe unten) so daß der eine Strom jeweils auf den Funktionen eines darunterliegenden Stromes aufbaut. HRZ Einführungspraktikum Java 129 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes 130 Beispiel 1: Zeilenweises Ausgeben einer reinen Textdatei: import java.io.*; public class ReaderTest { public static void main( String[] args ) { try { BufferedReader br = new BufferedReader( new FileReader( "c:\\autoexec.bat" ) ); String line; while( ( line = br.readLine() ) != null ) System.out.println( line ); } catch( Exception ex ){ System.out.println( "Fehler!" ); } } } Beispiel 2: Ein paar Textzeilen in eine Datei schreiben: PrintWriter datei = new PrintWriter( new FileWriter( "index.html" ) ); datei.println( "<HTML><HEAD><TITLE>Ein Test</TITLE></HEAD>" ); datei.println( "<BODY> Hallo Welt! </BODY></HTML>" ); datei.close(); Beispiel 3: Lesen von der Standard-Eingabe: BufferedReader konsole = new BufferedReader( new InputStreamReader( System.in ) ); System.out.print( "Wie viele Waschmaschinen wollen Sie?" ); int anzahl = Integer.parseInt( konsole.readLine() ); System.out.println( "Menge: " + anzahl + " Stück" ); System.out.print( "Und wohin soll ich liefern?" ); String anschrift = konsole.readLine(); ... HRZ Einführungspraktikum Java 130 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes 131 2. Datei- und Verzeichnisinformationen Die Klasse File repräsentiert Informationen über eine Datei (nicht die Datei selbst). Klassenvariablen String vTrenner = File.separator; // Verzeichnistrenner ( Unix: "/", Windows: "\" ) hier String pTrenner = File.pathSeparator; // Pfadtrenner für Pfadlisten ( ":" bzw. ";" ) hier File-Instanz erzeugen File f = new File( dir, dateiname ); File f = new File( pfadname ); // "c:\temp\test.java" Informationen abfragen boolean b; b = f.canRead(); b = f.canWrite(); b = f.exists(); b = f.isAbsolute(); b = f.isDirectory(); b = f.isFile(); // // // // // // Lesender Zugriff möglich? Schreibender Zugriff möglich? Gibt es diese Datei / Verzeichnis? Ist die Pfadangabe absolut? Handelt es sich um ein Verzeichnis? oder um eine Datei? String s; s = f.getAbsolutePath(); s = f.getName(); s = f.getParent(); s = f.getPath(); // // // // long l; l = f.lastModified(); l = f.length(); // Letzte Änderung in ms seit 1.1.70 // Länge in Bytes Absoluter Pfad der Datei Datei- bzw. Verzeichnisname Pfad des Elternverzeichnisses Pfad der Datei Löschen und umbenennen boolean ok = f.delete(); boolean ok = f.renameTo( new File( "Neu.txt" ) ); Arbeiten mit Verzeichnissen String aktuelles = System.getProperty( "user.dir" ); // Aktuelles Arbeitsverzeichnis dieser Java Application String[] list = f.list(); // Verzeichnisinhalt String[] list = f.list( FilenameFilter ); // siehe Schnittstelle FilenameFilter in API-Docs!! boolean ok = f.mkdir(); // Erzeugt dieses Verzeichnis boolean ok = f.mkdirs(); // Erzeugt rekursiv Verzeichnis(se) HRZ Einführungspraktikum Java 131 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes 132 3. Netzwerkkommunikation mit Java Java bietet grundlegende Funktionen zur Arbeit in Netzen auf TCP/IP- und UDP-Basis, mit denen jedes darauf basierende höhere Protokoll implementiert werden kann (FTP, TELNET, HTTP, POP3, SMTP, NNTP, SNMP, ...). Es lassen sich Client- und Serversockets für TCP, Client- und Server-Datagramm-Sockets für UDP einrichten. Eine Klasse URL nimmt in ihren Instanzen URLs auf, die Klasse InetAddress nimmt in ihren Instanzen eine Hostadresse auf. Näheres in spezieller Literatur und in der API-Dokumentation! 3.1 Einfache Netzoperationen aus Applets heraus: this.getAppletContext().showDocument( eineURLInstanz ); // weist den Browser an, diese URL anzufordern. // Es gibt auch die Möglichkeit, ein target anzugeben... Instanzen vom Typ URL besitzen eine Methode openStream(). Diese liefert einen InputStream zurück, der dann wie jeder andere weiterverarbeitet werden kann: try { URL url = new URL("http://www.xy.com/irgendwas.txt" ) ; BufferedReader data = new BufferedReader( new InputStreamReader( url.openStream() ) ); String line; while ( ( line = data.readLine() ) != null ) System.out.println( line ); data.close(); } catch( Exception ex ) { System.out.println( "Ein Fehler ist aufgetreten." ); } 3.2 Kommunikation zwischen Applets: Applets der gleichen Seite können ihre public-Methoden untereinander aufrufen: Applet uhr = this.getAppletContext().getApplet( "Uhr" ); uhr.stop(); Der Name eines Applets kann im <APPLET>-Tag z. B. mit NAME="Uhr" definiert werden. Die Methode getApplets() liefert alternativ eine komplette Enumeration der Applets der Seite: Enumeration e = this.getAppletContext().getApplets(); while ( e.hasMoreElements() ) { Applet applet = (Applet)e.nextElement(); applet.doSomething(); } HRZ Einführungspraktikum Java 132 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes 133 4. Java Database Connectivity (JDBC) Verfügbar als Zusatzpaket für JDK 1.0, im Sprachumfang enthalten ab JDK 1.1 Java-API, um auf SQL-Ebene von Java aus auf beliebige relationale Datenbanken zuzugreifen. Die Schnittstelle ist dabei unabhängig von der Plattform und der Datenbank, der Java-Code muß bei einem Wechsel also nicht geändert werden. JDBC ist in Anlehnung an die Datenbank-Schnittstelle ODBC entwickelt worden. Eine JDBC-to-ODBC-Bridge-Treiber wird mit den JDBC-Klassen von Sun mitgeliefert. Klassen für Select-Abfragen, Cursors, Connections etc. sind verfügbar, die SQL-Datentypen werden einheitlich auf Java-Datentypen abgebildet. Nahezu alle Datenbanksysteme (Oracle, DB2, Access, ...) bieten inzwischen mächtige JDBC-Treiber. Es existieren verschiedene Treiber-Architekturen, im Idealfall ist der JDBC-Treiber selbst in Java geschrieben, so daß auf dem Client keine Installation von Treibern nötig ist, sondern der Download der Java-Klassen z. B. durch ein Applet ausreicht. Beispiel: import java.sql.*; public class Test { public static void main( String[] args ) throws Exception { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // JDBC-Treiber für ODBC-Verbindungen registrieren Connection c = DriverManager.getConnection( "jdbc:odbc:Bank;UID=Meier;PWD=MySecret" ); // Verbindungsaufbau zur ODBC-zugänglichen Datenbank // "Bank", User "Meier" mit Passwort "MySecret" Statement s = c.createStatement(); ResultSet r = s.executeQuery( "SELECT kontoNr, betrag FROM konten" ); // SQL-Select-Abfrage ausführen while ( r.next() ) // Für jeden Datensatz... { int betrag = r.getInt( "betrag" ); // Hole int-Wert über Namen der Spalte int nr = r.getInt( 0 ); // Hole int aus Spalte mit Index 0 (KontoNr) System.out.println( kontoNr + " / " + betrag ); } r.close(); c.close(); } } HRZ Einführungspraktikum Java 133 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes 134 5. Swing Verfügbar als Zusatzpaket für JDK 1.1, im Sprachumfang enthalten ab JDK 1.2 Mächtige Klassenbibliothek zur Erstellung graphischer Oberflächen mit Java Realisiert als Aufsatz auf das AWT, Alternative zu den AWT-Komponenten Verwendet keine native peers zur Darstellung, daher ist es einfacher, Oberflächen zu erzeugen, die auf jedem System gleich aussehen. Einige Stichwörter aus dem Leistungsumfang: Pluggable Look and Feel: Unix, Windows, Java, Mac, zur Laufzeit änderbar Drag and Drop zwischen Java-Elementen Unterstützung der Zwischenablage Grafische, animierte und roll-over-Buttons Tool Tips ('gelbe Kästchen') Fortschrittsbalken Tree Controls, auch mit selbstdefinierten Icons HTML Viewer –Komponente Komponenten für Tabellendarstellungen Toolbars Beispiele: Table Listbox Toolbar Tree Control HRZ Einführungspraktikum Java 134 VII. Ein- und Ausgabe, Netze, Dateien und Fortgeschrittenes 135 6. Java Servlets Java Servlets sind Java-Anwendungen, die im Prozess eines Web-Servers laufen, ähnlich wie Applets in einem Browser laufen. Ein Servlet-fähiger Web-Server besitzt dazu eine Java Virtual Machine, um Java Bytecode auszuführen und bietet eine standardisierte Servlet API, gegen die man Servlets programmieren kann. Nahezu alle gängigen Web-Server (Apache, Netscape, Domino, Microsoft) unterstützen inzwischen Servlets, oft sind zusätzlich Servlet Plug-Ins von Drittanbietern verfügbar. Vergleichbar mit CGI kann man einen HTTP Request an ein Servlet weiterleiten, das dann z. B. Daten aus einem HTML-Formular entgegennimmt und verarbeitet und dann als Antwort eine HTML-Seite erzeugt. Servlets können, abgesehen davon, daß sie keine graphische Oberfläche besitzen, den vollen Sprachumfang von Java nutzen, z. B. via JDBC eine Datenbank ansprechen. Servlets sind plattformunabhängig, man kann also das Servlet, einmal geschrieben, unverändert in einem anderen Web-Server auf einer anderen Plattform laufen lassen. Servlets sind schneller als CGI. Wenn ein Request bearbeitet werden soll, wird bei CGI ein neuer Prozess gestartet. Dieser Kontextwechsel zwischen den Prozessen kostet Zeit. Servlets laufen und bleiben nach dem Start im Web-Server-Prozess, der Wechsel ist nicht nötig, stattdessen wird für jeden Request einfach ein neuer Thread gestartet, der ihn bearbeitet. Dies geschieht automatisch und muß nicht programmiert werden. Die Java Servlet API bietet volle Unterstützung für das Bearbeiten von Cookies. Servlets besitzen ein integriertes Session Management, um Objekte in einer Session zu speichern. Wenn mehrere Requests vom gleichen Client bearbeitet werden, lassen sich so zwischen den einzelnen Requests Zustände erhalten, z. B. Login-Informationen. Dies wird dynamisch und automatisch entweder über Cookies oder URL Rewriting realisiert. Servlets können über eine URL von Browsern aufgerufen werden, in HTML-Seiten vergleichbar mit Server Side Includes eingebunden werden, oder sich gegenseitig aufrufen. Beispiel: Aufruf über http://www.bank.de/servlet/KontoServlet?kontoNr=12345 import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class KontoServlet extends HttpServlet { public void doGet( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException { ServletOutputStream out = res.getOutputStream(); res.setContentType("text/html"); int nr = Integer.parseInt(req.getParameter( "kontoNr" )); out.println( "<HEAD><TITLE>Ihr Konto:</TITLE></HEAD>" ); out.println( "<BODY>" ); out.println( "Ihr Kontostand:" ); out.println( Konto.gibKonto( nr ).gibKontoStand() ); out.println("</BODY>"); out.close(); } } HRZ Einführungspraktikum Java 135