PROMOD-2a / SS10 Materialien zur Vorlesung im Sommersemester 2010 j.a.illik PROMOD-2 / Part 2 von 3 „Programmieren und Modellieren 2“ JAVA : SE advanced-1 / verteilte Applikationen (1. Das Skript ist durch die im Literaturverzeichnis genannte Pflichtlektüre zu ergänzen. 2. Achten Sie auf Ergänzungen und weitergehende Hinweise in der Vorlesung! Fertigen Sie eine Mitschrift an.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 1 / 210 23.03.2010 PROMOD-2a / SS10 „The capacity to learn is a gift. The ability to learn is a skill. The willingness to learn is a choice” Brian Herbert, Kevin J. Anderson: „Prelude to Dune – House Harkonnen” "Ich höre und ich vergesse. Ich sehe und ich erinnere mich. Ich tue und ich verstehe." Konfuzius Fertigen Sie ein Protokoll als Mitschrift an! Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 2 / 210 23.03.2010 PROMOD-2a / SS10 Inhaltsverzeichnis 1. Vererbung (Wiederholung & Ergänzung) ................................................................................. 8 1.1 Grundlagen ....................................................................................................................... 8 1.2 Die Klasse Object ............................................................................................................. 8 1.3 Konstruktoraufrufe: ......................................................................................................... 10 1.4 Vererbungsketten und Zuweisungskompatibilität: ........................................................... 11 1.5 Case-Study Objekt-Arrays, Zuweisungskompatibiliät, Operator instanceof .................... 12 1.6 Case-Study Lösung: ....................................................................................................... 12 1.7 Polymorphie .................................................................................................................... 14 1.7.1 Beispiel 1 – Methoden überschreiben: ..................................................................... 14 1.7.2 Beispiel 2 – "late binding – Polymorphismus": ......................................................... 15 1.8 Abstrakte Klassen und Methoden: .................................................................................. 16 1.9 Finale Klassen: ............................................................................................................... 17 2. Packages (Selbststudium!) .................................................................................................... 18 2.1 Grundlagen ..................................................................................................................... 18 2.2 Packages importieren: .................................................................................................... 18 2.3 Packages definieren: ...................................................................................................... 19 2.4 Zugriffsrechte in Packages:............................................................................................. 19 3. Interfaces (und Ergänzungen zu Klassen) ............................................................................. 21 3.1 Grundlagen ..................................................................................................................... 21 3.2 Adapterklassen ............................................................................................................... 24 3.3 Innere Klassen (auch: geschachtelte / eingebettete / nested class) ............................... 25 3.4 Lokale Klassen................................................................................................................ 26 3.5 Anonyme Klassen ........................................................................................................... 26 3.6 Statische innere Klassen (static class) / statische innere Schnittstellen ......................... 27 4. Ein-/Ausgabe auf Konsole,Dateien u.a.(ADRELI_CON) ........................................................ 28 4.1 Grundlagen ..................................................................................................................... 28 4.2 Standard-Ein- und Ausgabe ............................................................................................ 29 4.3 Die Klasse File: ............................................................................................................... 31 4.4 Die Klasse RandomAccessFile: ...................................................................................... 33 5. Streams: (ADRELI_CON) ...................................................................................................... 37 5.1 Grundlagen ..................................................................................................................... 37 5.2 Überblick (UNICODE-) Character-Stream-Klassen (Zeichendaten (Text) schreiben und Lesen)........................................................................................................................................ 38 5.3 Character–Stream-Methoden:......................................................................................... 39 5.3.1 Ausgabe-Streams .................................................................................................... 39 5.3.2 Eingabe-Streams ..................................................................................................... 39 5.4 Beispiele/Anwendungen zu Character–Streams: ............................................................ 40 5.5 Überblick Byte-Stream-Klassen (binäres Schreiben u. Lesen) ....................................... 43 5.6 Byte–Stream-Methoden: ................................................................................................. 43 6. Multi-Threading (ADRELI_THREADS) ................................................................................... 47 6.1 Grundlagen ..................................................................................................................... 47 6.2 Klasse Thread und Interface Runnable ........................................................................... 49 6.3 Zustände von Threads: ................................................................................................... 53 6.3.1 Threads unterbrechen.............................................................................................. 53 6.3.2 Die Methoden yield(), sleep() und interrupt() ........................................................... 53 6.3.3 Die Methode join() ................................................................................................... 54 6.3.4 Weitere Thread-Methode ......................................................................................... 54 6.3.5 Thread-Konstruktoren .............................................................................................. 55 6.4 Daemon-Threads ............................................................................................................ 57 6.5 Prioritäten von Threads: .................................................................................................. 57 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 3 / 210 23.03.2010 PROMOD-2a / SS10 6.6 Gruppen von Threads ..................................................................................................... 58 6.7 Synchronisation: ............................................................................................................. 58 6.8 Datenaustausch zwischen Threads mit Pipes:................................................................ 63 6.9 Thread-Synchronisation mit CyclicBarrier ....................................................................... 67 6.10 Zusammenfassung Threads: .......................................................................................... 69 6.11 Zusammenfassung Pipes:............................................................................................... 69 7. Exkurs: how to model an application? .................................................................................... 70 7.1 Als Kompass/Leitplanke dient ein Vorgehensmodell ...................................................... 72 7.2 Und wie geht`s im Detail? ............................................................................................... 76 8. Netzwerkzugriff ...................................................................................................................... 77 8.1 Grundlagen ..................................................................................................................... 77 8.1.1 Protokolle ................................................................................................................. 77 8.1.2 Adressierung: IP-Adresse und Port-Nummer........................................................... 78 8.1.3 Die Datei hosts und DNS (Domain Name System) .................................................. 80 8.2 Klassen für die Adressierung .......................................................................................... 81 8.2.1 Die Klasse InetAddress: (siehe API-Doku)This class represents an Internet Protocol (IP) address. .......................................................................................................................... 81 8.2.2 Die Klasse URL ....................................................................................................... 82 8.3 Socket-Programmierung ................................................................................................. 85 8.3.1 Client-Sockets: die Klasse socket ............................................................................ 86 8.3.2 ServerSockets: die Klasse ServerSocket................................................................. 87 8.3.3 Beispiel – Server-Sockets (server.java): .................................................................. 88 8.3.4 Beispiel – Client-Sockets (client.java): ..................................................................... 88 8.4 Was kommt den da im Strom? Sockets und Streams ..................................................... 89 9. RMI Remote Method Invocation ............................................................................................. 91 9.1 Grundlagen ..................................................................................................................... 91 9.2 Die Bestandteile einer RMI-Anwendung ......................................................................... 92 9.3 Das Ablaufschema eines entfernten Methodenaufrufs ................................................... 93 9.4 Die Hilfsmittel RMI-Compiler und RMI-Registry .............................................................. 94 9.5 Der Sicherheitsmanager (SecurityManager) ................................................................... 95 9.6 Die Komponenten und Bestandteile auf der Server-Seite ............................................... 96 9.7 Die Komponenten und Bestandteile auf der Client-Seite ................................................ 97 9.8 Vollständiges RMI-Beispiel ............................................................................................. 97 9.9 Zusammenfassung ....................................................................................................... 101 9.10 Kontrollfragen (Technologien / RMI) ............................................................................. 102 10. AWT-Basics: Graphical User Interface ............................................................................. 103 10.1 Grundlagen ................................................................................................................... 103 10.2 AWT – Klassenhierarchie ............................................................................................. 105 10.3 „Fenster“: von der Component über den Container zum Frame ................................... 106 10.4 Klasse Window ............................................................................................................. 107 10.5 Klasse Panel: (abgeleitet von Container) ...................................................................... 108 10.6 Klasse Frame: (abgeleitet von Window) ....................................................................... 108 10.7 "Fensterbausteine" und der Umgang damit (Erzeugung / Konfiguration / Anzeige) ..... 110 10.8 Anzeigen und schließen: ............................................................................................... 110 10.9 Bestandteile eines Fensters (class Frame) ................................................................... 112 10.9.1 Das Fenstersymbol/Icon: ....................................................................................... 113 10.9.2 Fensterposition und -Größe: .................................................................................. 114 10.9.3 Fensterfarben: ....................................................................................................... 114 10.9.4 Der Mouse-Cursor: ................................................................................................ 116 11. AWT-Events: Ereignisse und Event-Handling .................................................................. 118 11.1 Grundlagen ................................................................................................................... 118 11.2 Ereignisklassen ............................................................................................................. 120 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 4 / 210 23.03.2010 PROMOD-2a / SS10 11.3 Listener-Interfaces ........................................................................................................ 121 11.4 Listener bei Ereignisquellen registrieren ....................................................................... 121 11.5 Implementierungsmöglichkeiten:................................................................................... 123 11.5.1 1) Verwendung von Adapterklassen: ..................................................................... 123 11.5.2 2) Listener Interface wird implementiert: ................................................................ 123 11.6 Getrennter Code für GUI und Applikation ..................................................................... 123 11.7 Low-Level-Ereignisse (systemnahe Ereignisse): .......................................................... 125 11.7.1 Component-Ereignisse (die Klasse ComponentListener): ................................ 125 11.7.2 Window-Ereignisse (die Klasse WindowListener): ............................................ 127 11.7.3 Focus-Ereignisse (die Klasse FocusListener): ................................................. 129 11.7.4 Input-Ereignisse: .................................................................................................... 130 11.7.5 Die Eventklasse Tastaturereignisse (Klasse KeyEvent) und das ListenerInterface KeyListener):.............................................................................................. 130 11.7.6 Die Eventklasse Klasse MouseEvent und die Listener-Interfaces MouseListener, MouseMotionListener : ......................................................................................................... 133 11.8 Zusammenfassung Ereignisse und ihre Behandlung .................................................... 136 12. AWT-Graphics: Grafikprogrammierung mit AWT.............................................................. 138 12.1 Grundlagen ................................................................................................................... 138 12.2 Der Grafikkontext / Die Klasse Graphics: ...................................................................... 138 12.3 Die Methoden paint() und repaint(): .............................................................................. 139 12.4 Anzeige von Text: ......................................................................................................... 141 12.4.1 Die Klasse Font / einige Grundbegriffe .................................................................. 141 12.4.2 Die Klasse Font / Schriftarten ................................................................................ 143 12.5 Farben / die Klasse Color.............................................................................................. 146 12.6 Weitere Methoden zum Zeichnen aus der Klasse Graphics ......................................... 148 12.7 Bitmaps anzeigen: (nur gif und jpeg-Format)/ Die Klasse Image .................................. 150 13. AWT-Komponenten: ......................................................................................................... 153 13.1 Grundlagen ................................................................................................................... 153 13.2 Überblick über AWT-Komponenten: ............................................................................. 154 13.3 Anwendung / Umgang mit den Komponenten:.............................................................. 155 13.3.1 Die Klasse Label .................................................................................................... 155 13.3.2 Die Klasse Button .................................................................................................. 156 13.3.3 Die Klasse Textfeld (abgeleitet von TextComponent) ............................................ 158 13.3.4 Die Klasse TextArea (abgeleitet von der Klasse TextComponent) ........................ 160 13.3.5 Die Klasse Choice und die Klasse List:.................................................................. 160 13.3.6 Die Klasse Checkbox: ............................................................................................ 162 13.3.7 Die Klasse CheckboxGroup: .................................................................................. 162 13.3.8 Die Klasse Scrollbar:.............................................................................................. 163 13.3.9 Die Klasse Scrollpane ............................................................................................ 165 13.4 Dialoge .......................................................................................................................... 165 13.4.1 Die Klasse Dialog: ................................................................................................. 165 13.4.2 Der Dateidialog (Klasse FileDialog) ................................................................. 166 13.5 Menüs: .......................................................................................................................... 168 13.5.1 Die Menüleisten (Klasse MenuBar) ....................................................................... 169 13.5.2 Die Menüs (Klasse Menu) ...................................................................................... 169 13.5.3 Die Menü-Einträge (Klasse MenueItem) .............................................................. 169 13.5.4 Kontextmenüs (auch Popup-Menüs genannt)........................................................ 173 14. LayoutManager (ein Überblick)......................................................................................... 176 14.1 Grundlagen ................................................................................................................... 176 15. Elementare Dienste für JEE: JDBC .................................................................................. 184 15.1 Hinführung .................................................................................................................... 184 15.2 Grundlagen ................................................................................................................... 184 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 5 / 210 23.03.2010 PROMOD-2a / SS10 15.3 JDBC als Basis für andere APIs: SQLJ und JDO ......................................................... 185 15.3.1 SQLJ ...................................................................................................................... 185 15.3.2 SQLJ im Vergleich zu JDBC .................................................................................. 186 15.3.3 SQLJ und der Übersetzungprozess ....................................................................... 187 15.3.4 Persistente Java Objekte: JDO Java Data Objects ................................................ 187 15.4 Architektur von JDBC .................................................................................................... 187 15.5 Treibertypen .................................................................................................................. 188 15.6 Die Datenbank MySQL / Die DBMS Cloudscape / PointBase....................................... 190 15.7 Aufbau einer Datenbankverbindung .............................................................................. 190 15.8 SQL-Anweisungen ausführen ....................................................................................... 194 15.9 Die Klasse Statement ................................................................................................... 194 15.10 Klasse PreparedStatement........................................................................................ 196 15.11 Klasse CallableStatement / StoredProcedure ........................................................... 197 15.12 Auswertung der Ergebnismengen ............................................................................. 197 15.12.1 Boolean und Integer als Rückgabewerte............................................................ 198 15.12.2 ResultSet ............................................................................................................ 198 15.12.3 Konfigurieren der Ergebnismenge ...................................................................... 201 15.12.4 Nachladen von Datensätzen konfigurieren......................................................... 201 15.12.5 Navigieren in der Ergebnismenge ...................................................................... 201 15.12.6 Werte der Ergebnismenge ermitteln ................................................................... 201 15.12.7 Update der Ergebnismenge ............................................................................... 201 15.12.8 Nullwerte ............................................................................................................ 201 15.13 Auslesen von Metadaten ........................................................................................... 201 15.13.1 Datenbankinformationen ermitteln ..................................................................... 201 15.13.2 Informationen über eine Ergebnismenge ermittlen ............................................. 201 15.14 Transaktionen............................................................................................................ 201 15.14.1 Manuelles und automatischs Commit................................................................. 201 15.14.2 Isolationsebenen ................................................................................................ 201 15.14.3 Dirty Read .......................................................................................................... 201 15.14.4 Repeatabel Read ............................................................................................... 201 15.14.5 Phantom Read ................................................................................................... 201 15.15 JDBC Option Package .............................................................................................. 201 15.15.1 DataSource ........................................................................................................ 201 15.15.2 Connection-Pooling ............................................................................................ 202 15.15.3 Verteilte Transaktionen ...................................................................................... 202 15.16 Datenbankzugriff über Applets .................................................................................. 202 15.17 Debuggen von JDBC-Anwendungen ......................................................................... 202 15.18 CaseStudy Aufgabe (Aufgabe 1 von 030_JDBC) ...................................................... 202 15.19 CaseStudy Lösung .................................................................................................... 203 15.20 Die Datenbank Derby ................................................................................................ 204 16. Promod Tipps ................................................................................................................... 205 16.1 Methodik: Scrum ........................................................................................................... 205 17. Literatur (nehmen Sie jeweils die jüngste Ausgabe) ......................................................... 207 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 6 / 210 23.03.2010 PROMOD-2a / SS10 Didaktik, Methodik: Während der Vorlesung wird nicht der gesamte Stoff präsentiert. Bestimmte Abschnitte sind von den Studierenden eigenständig zu erarbeiten, insbesondere die Details! Der Modul Promod-2 ist als seminaristische Lehrveranstaltung organisiert. Im theoretischen Teil erfolgt wöchentlich in einem Vorlesungsblock eine Einführung in die Theorie und die Problematik. Grundlage des Vorlesungsblocks ist dieses Skript. Die Einsicht in die Theorie wird in einem unmittelbar daran anschliessenden Vertiefungsblock („HandsOn“-Teil) in die Praxis umgesetzt und validiert. Für diesen Praktikumsblock werden Case-Studies vorgegeben. Die Case-Studies sind teilweise in diesem Skript enthalten und teilweise auf dem Materialsserver. Projekte (und Übungen) werdem im Praktikumsblock durchgeführt. Die hierfür vorhandenen Aufgabenstellungen lösen die Praktikumsteilnehmer selbständig. Der Praktikumsblock ist tutoriell betreut. Im fortgeschrittenen Stadium der Lehrveranstaltung entwickeln die Studierenden eine zunehmende Selbständigkeit und erarbeiten selbständig vorgegebne Themen im Rahmen eines angeleiteten Selbststudiums. Ab etwa Mitte der Vorlesungszeit wird der Vorlesungsblock zum Praktikumsblock dazugeschlagen um die Projekte zu realisieren. Lernziel: Sie können datenbankbasierte, im LAN oder Internet verteilte System-Architekturen entwerfen und mit JAVA implementieren. Die Architekturen können multi-threaded sein und über eine GUI verfügen. Lehr- und Lernmaterialien: Das Pfeilsymbol (siehe Bild rechts) verweist auf „Case Studies“ für den „Hands-On“-Teil der Vorlesung. Zu einem Teil der Case Studies bekommen Sie Experimentalquellen, die Sie auf dem Materialserver finden. Der Hands-On-Teil findet jeweils im 2. Vorlesungsblock statt. Der die Vorlesung begleitende Übungsteil findet im 3. Block statt. Für den Übungsteil gibt es eigene Übungsaufgaben, die Sie auf dem Materialserver finden. Sonstiges: Das Trichtersymbol (Bild rechts) macht auf Hinweise zur Entwicklungsumgebung aufmerksam. Tools & Server: Bei den Tools & Servern gibt es Versionsabhängigkeiten! Mehr dazu in der Vorlesung. Java SE Java EE Eclipse Tomcat XAMP (siehe readme_d.txt) • Apache Web-Server • MySQL Datenbank • PHP Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 7 / 210 23.03.2010 PROMOD-2a / SS10 1. Vererbung (Wiederholung & Ergänzung) Was Sie lernen: was es mit der Klasse Object auf sich hat die Rolle von Konstruktoraufrufen in Ableitungen Zusammenhang von Vererbungsketten und Zuweisungskompatibiltät was early und late binding ist was abstrakte und finale Klassen sind Voraussetzungen: Arbeiten mit Klassen und Methoden 1.1 Grundlagen Bekannt: class subclass extends superclass { //zusätzliche Attribute //zusätzliche Methoden } nur eine Superklasse; keine Mehrfachvererbung! Æ nur additiv hinzufügen 1.2 Die Klasse Object Jede Klasse besitzt eine "unsichtbare" Superklasse "Object". Die Klasse Object ist die ultimative Basisklasse. Es bedarf jedoch keiner expliziten Vererbungsanweisung (... extends Object). class New [extends Object] { //Klasseninhalt } Die Klasse Object definiert mehrere Methoden, die allen Ableitungen, also allen Klassen zur Verfügung steht: boolean equals(Object o) Object clone() String.toString() Class getClass() void notify() void notifyAll() void wait() Protected void finalize() Methode prüft, ob Objekt identischen Inhalt wie anderes Objekt beinhaltet. Die Methode muss in abgeleiteten Klassen überschrieben (override) werden, wenn Sie auf inhaltliche Gleichheit eines Objekts testen möchten. Ansonsten wird überprüft, ob es sich um dieselbe Referenz auf das betreffende Objekt handelt. Erzeugt neues Objekt als exakte Kopie, jedoch vom Typ Object! (override) Liefert Stringrepräsentation des Objekts. (override) Returns the runtime class of an object. Siehe Multi-Threading. Siehe Multi-Threading. Siehe Multi-Threading. Called by the garbage collector (… System.gc() …) on an object when garbage collection determines that there are no more references to the object.(override) Umgedingt empfehlenswert: die API-Doku zu den Methoden lesen! (Insbesondere wg. „override“) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 8 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel: public class ObjektMethoden { public static void main(String[] args) { ObjektMethoden o1 = new ObjektMethoden(); ObjektMethoden o2 = new ObjektMethoden(); o1.setData("Neu"); if(!o1.equals(o2)) System.out.println("Nicht gleich!"); System.out.println(o1.toString()); System.out.println(o2.toString()); ObjektMethoden o3 = (ObjektMethoden)o1.clone(); System.out.println(o3.toString()); } private String data = "Objektdaten"; public String getData() { return data; } public void setData(String wert) { data = wert; } public String toString() { return data; } public Object clone() { ObjektMethoden om = new ObjektMethoden(); om.setData(data); return om; } } public boolean equals(Object o) { if(this == o) return true; if((o == null) || (this.getClass() != o.getClass())) return false; return (data.equals(((ObjektMethoden)o).getData())); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 9 / 210 23.03.2010 PROMOD-2a / SS10 1.3 Konstruktoraufrufe: Es können lediglich Methoden und Variablen vererbt werden. Konstruktoren können nicht vererbt werden. Allerdings kann der Konstruktor der Basisklasse aufgerufen werden. Beispiel: class Unterklasse extends Basisklasse { public Unterklasse() // Standardkonstruktor { super(); // Konstruktor der Superklasse wird aufgerufen //muss die erste Anweisung sein!! //Die Signatur ist wichtig!! . . . } } Sie können jeden beliebigen Konstruktor der Superklasse aufrufen: die Auswahl ergibt sich aus der Signatur. Aufruf von super() muss als erste Anweisung im Konstruktor stehen. Sie können nur den Konstruktor er unmittelbaren Superklasse aufrufen: super.super() ist nicht möchglich. Verwenden Sie keinen Aufruf von super(), so wird automatisch der Standardkonstruktor (auch DefaultKonstruktor genannt) der Superklasse als erste „unsichtbare“ Anweisung aufgerufen. Zur Erinnerung: Der Default-Konstruktor, der vom Compiler aufgerufen wird, allokiert ein Objekt, führt aber keine Initialisierungen durch: Der Standardkonstruktor ist ein Konstruktor ohne Parameter. (Hat eine Superklasse Konstruktoren, jedoch keinen Standardkonstruktor und der Konstruktor der Subklasse ruft keinen Konstruktor der Superklasse explizit auf, so meldet der Java-Compiler einen Fehler. Grund: Sobald nur ein einziger selbst geschriebener Konstruktor existiert – ob mit oder ohne Parameter – so ist der vom Compiler zur Verfügung gestellte Default-Konstruktor nicht mehr sichtbar. Beispiel: class Basis { protected int i; // in Ableitung zugreifbar public Basis(int i)//Konstruktor { this i = i; System.out.println("In Basisklasse"); } } class Unterklasse extends Basis { public Unterklasse(int i) // Konstruktor { super(i); // Basisklassen-Konstruktor-Aufruf System.out.println("In Unterklasse"); } } Um den Destruktor der Superklasse aufzurufen, kann die Anweisung super.finalize() verwendet werden. (Es erfolgt kein automatischer Aufruf von finalize() der Basisklasse.) Zur Erinnerung: die Java-Spezifikation garantiert nicht, dass die Methode finalize() zu einem bestimmten Zeitpunkt aufgerufen wird! Experimentieren Sie mit super.finalize() und Alternativen (z.B. dispose(); siehe: Promod1). Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 10 / 210 23.03.2010 PROMOD-2a / SS10 1.4 Vererbungsketten und Zuweisungskompatibilität: Wie bekannt: von einer Klasse können weitere Klassen abgeleitet werden, von denen wiederum Klassen abgeleitet werden: so entsteht eine Klassenhierarchie. In Java hat man von einer abgeleiteten Klasse immer nur Zugriff auf die nicht privaten Daten und Methoden der unmittelbaren Superklasse. Auf Elemente der Superklasse einer Superklasse hat man keinen direkten Zugriff: super.super.methode() geht nicht. Allerdings stehen alle Daten und Methoden zur Verfügung, die jeweils vererbt wurden. Innerhalb der Klassenhierarchie sind Objekte abgeleiteter Klassen immer zu Objektvariablen der Superklassen zuweisungskompatibel! Der Grund liegt darin, dass diese Objekte immer mindestens die Methoden und Daten der Superklasse besitzen und somit vollständig als ein solches Objekt funktionstüchtig sind. Wenn man jedoch einer Objektvariablen der Superklasse ein Objekt der Subklasse zuweist, fehlen die Methoden, die erst in der abgeleiteten (Sub-)Klasse definiert werden. (Muss auf diese Methoden der abgeleiteten Klassen zugegriffen werden, so ist ein Cast der Superklassen-Objektvariable notwendig.) Siehe Beispiel unten „instanceof“ Evaluieren Sie die oben genannten Punkte! GraphikObjekt Beispiel.: Kreis gefüllterKreis Beispiel: Rechteck gefülltesRechteck Kreis K = new gefuellterKreis(); . . . GrafikObject go [] = new GrafikObjekt[10]; go[0] = new Kreis(); go[1] = new Rechteck(); go[2] = new gefuellterKreis(); go[3] = new gefuelltesRechteck(); . . . Durch die Zuweisungskompatibilität können problemlos Typumwandlungen von der Subklasse in die Superklasse vorgenommen werden. Z. Beispiel: Kreis = (Kreis) gefuellterKreis(); Übung:Prüfen Sie, ob ein Cast in die umgekehrte Richtung möglich und sinnvoll ist. Demonstrieren Sie das Ergebnis an einem Programmbeispiel. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 11 / 210 23.03.2010 PROMOD-2a / SS10 1.5 Case-Study Objekt-Arrays, Zuweisungskompatibiliät, Operator instanceof (Klausur-Aufgabe PROMOD1 in WS0506) 1) Schreiben Sie eine übersetzungsfähige Java-Applikation, die folgende Aufgabenstellung löst: Im Rahmen eines Computerverwaltungssystems gilt es Informationen über Computer zu verarbeiten. Bei allen Computerarten sind folgende Daten von Bedeutung: Hauptspeicherkapazität und Plattenspeicherkapazität (in MB) und Prozessortyp (Intel oder AMD). Bei der Computerart „Laptop“ wird noch eine Angabe zum Gewicht (in Gramm) gebraucht. Bei der Computerart „Server“ ist die Anzahl von Prozessoren (in Stück) bedeutsam. A) Modellieren Sie ein geeignetes Java-Klassensystem (mit drei Klassen). Achten Sie darauf, dass die Attribute nicht ohne die dafür vorgesehenen Methoden manipulierbar sind: Stellen Sie für jedes Attribut eine separate read_in-Methode zur Verfügung, um das Attribut durch eine Keyboard-Eingabe zu füllen. Ebenso stellen Sie für jedes Attribut eine separate print_outMethode zur Verfügung, um den Attributinhalt auf der Konsole auszugeben. Für jede der drei Klassen bieten Sie Konstruktoren an, um bei der Instantiierung die Attribute nach Programmiererwunsch entsprechend zu initialisieren. (30 Punkte) B) Schreiben Sie eine main-Methode in der ein Array mit 3 Objekten angelegt wird. In die ArrayElemente müssen die Objekte der oben erwähnten drei Klassen passen: Legen Sie in das Array je ein Objekt der oben erwähnten Kategorien. Danach müssen alle Objekte im Array über das Keyboard gefüllt werden. Nachdem die Objekte im Array über das Keyboard gefüllt wurden, werden die Inhalte aller Objekte wieder auf den Bildschirm ausgegeben. Für das Füllen und Ausgeben des Array müssen Sie die dafür am besten geeignete Kontrollstruktur verwenden.(20 Punkte) 1.6 Case-Study Lösung: public class computerverwaltung { public static void main(String args[]){ System.out.println("Jetzt werden 3 Objekte in einem Array angelet."); Computer computer_array[] = new Computer[3]; computer_array[0] = new Computer(1000,2000,"intel"); computer_array[1] = new Laptop(2000,3000,"amd",3); computer_array[2] = new Server(3000,4000,"sparc",4); // Zuweisungskompat. for(int counter = 0; counter < 3; counter++) { computer_array[counter].read_in_Hauptspeicher(); computer_array[counter].read_in_Plattenspeicher(); computer_array[counter].read_in_Proztyp(); //statische Lösung; funktioniert, ist aber nicht wirklich brauchbar...: if (counter == 1){ ((Laptop)(computer_array[counter])).read_in_Gewicht(); } if (counter == 2){ ((Server)(computer_array[counter])).read_in_anzahlProz(); } } // end for() for(int counter = 0; counter <3; counter++) { computer_array[counter].print_out_Hauptspeicher(); computer_array[counter].print_out_Plattenspeicher(); computer_array[counter].print_out_Proztyp(); //dynamische Lösung mit instanceof-Operator: if (computer_array[counter] instanceof Laptop){ ((Laptop)(computer_array[counter])).print_out_Gewicht(); } if (computer_array[counter] instanceof Server){ Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 12 / 210 23.03.2010 PROMOD-2a / SS10 ((Server)(computer_array[counter])).print_out_anzahlProz(); } } // end for() }// end main() } // end class computerverwaltung class Computer { private int hauptspeicher; private int plattenspeicher; private String proztyp; Computer(int hauptspeicher, int plattenspeicher, String proztyp){ this.hauptspeicher = hauptspeicher; this.plattenspeicher = plattenspeicher; this.proztyp = proztyp; } public void read_in_Hauptspeicher(){ hauptspeicher = StdInput.readInt("Hauptspeicherkapazitaet: "); } } public void read_in_Plattenspeicher(){ plattenspeicher = StdInput.readInt("Plattenspeicherkapazitaet: "); public void read_in_Proztyp(){ proztyp = StdInput.readString("Prozessortyp: "); } public void print_out_Hauptspeicher(){ System.out.println("Hauptspeicherkapazitaet: "+hauptspeicher); } } public void print_out_Plattenspeicher(){ System.out.println("Plattenspeicherkapazitaet: "+plattenspeicher); public void print_out_Proztyp(){ System.out.println("Prozessortyp: "+proztyp); } } // end class Computer class Laptop extends Computer{ private int gewicht; Laptop(int hauptspeicher, int plattenspeicher, String proztyp, int gewicht){ super(hauptspeicher,plattenspeicher,proztyp); this.gewicht = gewicht; } public void read_in_Gewicht(){ gewicht = StdInput.readInt("Gewicht: "); } public void print_out_Gewicht(){ System.out.println("Gewicht: "+gewicht); } } // end class Laptop class Server extends Computer{ int anzahlProz; Server(int hauptspeicher, int plattenspeicher, String proztyp, int anzahlProz){ super(hauptspeicher, plattenspeicher, proztyp); this.anzahlProz = anzahlProz; Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 13 / 210 23.03.2010 PROMOD-2a / SS10 } public void read_in_anzahlProz(){ anzahlProz = StdInput.readInt("Anzahl Proz.: "); } public void print_out_anzahlProz(){ System.out.println("Anzahl Proz.: "+anzahlProz); } } // end class Server 1.7 Polymorphie Mit Polymorphy (aus dem Griechischen: Vielgestaltigkeit) ist die Fähigkeit eines Objekts gemeint, die richtige Methodenauswahl zu treffen. Wird eine Methode eines Objekts aufgerufen, so kann sich die Methodendefinition an folgenden Stellen stehen: o Die Klasse des Objekts hat die Methode definiert o Die Methode wurde von der Superklasse der Objektklasse übernommen (geerbt) o Die Methode existiert in der Superklasse, wurde aber in der davon abgeleiteten Klasse redefiniert / überschrieben Je nachdem, wann die Methodenauswahl erfolgt, spricht man von early binding oder late binding. o Beim early binding erfolgt die Methodenauswahl statisch zur Übersetzungszeit. o Beim late binding erfolgt die Methodenauswahl dynamisch zur Laufzeit. Hinweis: bei statischen (static), finalen (final) und privaten (private) Methoden kommt stets early binding zur Anwendung, da diese Methoden nicht überschrieben werden können (-> Performance). Beispiel: . . . GrafikObject go [] = new GrafikObjekt[10]; go[0] = new Kreis(); go[1] = new Rechteck(); go[2] = new gefuellterKreis(); go[3] = new gefuelltesRechteck(); . . . go[0].zeichne(); // early binding polymorphismus go[1].zeichne(); // early binding polymorphismus . . . Î hier: "early binding"-Polymorphismus: Bereits während der Compilezeit wird festgelegt, welche Ausprägung einer bestimmten Methode aufgerufen werden soll. Im Gegensatz hierzu gibt es auch noch "late binding-Polymorphismus". Hier wird erst bei der Laufzeit festgelegt, welche Ausprägung einer bestimmten Methode aufgerufen werden soll. 1.7.1 Beispiel 1 – Methoden überschreiben: class Basis { public String getName() { return "in Basisklasse"; } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 14 / 210 23.03.2010 PROMOD-2a / SS10 } class Unter extends Basis; { public String getName(); // Methode mit gleicher Signatur wie in Klasse Basis { super.getName(); // Aufruf der Methode der Superklasse return "in Unterklasse"; } } Zur Erinnerung: Es können keine Methoden einer Superklasse, die private, final oder static sind, überschrieben werden. 1.7.2 Beispiel 2 – "late binding – Polymorphismus": Das folgende Beispiel demonstriert das Wesen des late binding Polymorphismus: public class Polymorph { public static void main(String[] args) { Zahl werte[] = new Zahl[2]; // … erfolgt häuftig erst zur Laufzeit werte[0] = new DeutschZahl(); werte[1] = new EnglischZahl(); for(int i = 0; i < werte.length; i++) werte[i].druckeZahl(); } } // late binding Polymorphismus // Vergl. Bsp. “Computerverwaltung” class Zahl { protected double wert = 123456.78; public void druckeZahl() { System.out.println(wert); } } class DeutschZahl extends Zahl { public void druckeZahl() { System.out.println("123.456,78"); } } class EnglischZahl extends Zahl { public void druckeZahl() { System.out.println("123,456.78"); } } Übung: Erläutern Sie, warum late binding Polymorphismus für eine Programmiersprache eine weitere Steigerung der Flexibilität bedeutet. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 15 / 210 23.03.2010 PROMOD-2a / SS10 1.8 Abstrakte Klassen und Methoden: Abstrakte Klassen dienen als Vorlage für andere Klassen. Von abstrakten Klassen können keine Instanzen gebildet werden; nur Ableitungen sind möglich. Nicht alle Methoden einer abstrakten Klasse müssen leer sein, es können auch schon bereits voll ausgeprägte Methoden implementiert werden, dies ist jedoch nicht zwingend erforderlich. In d. R. besitzen abstrakte Klassen mindestens eine abstrakte Methode, die nur aus Methodendefinition (Signatur) ohne Rumpf besteht. Abstrakte Methoden werden mit dem Identifier "abstract" gekennzeichnet. Sinn und Zweck: Die Grundfunktionalität/Schnittstelle soll vorgegeben werden, jedoch erfolgt die Implementierung später. (Anwendung u.a. in der Design-Phase) Es ist möglich, Objektvariablen von der abstrakten Klasse zu erzeugen. Diesen Variablen können Objekten von Ableitungen zugewiesen werden. Beispiel: public class VorlageTest { public static void main(String[] args) { Vorlage v = new VorlageImpl(); } } abstract class Vorlage { public abstract String getName(); // abstrakte Methode } class VorlageImpl extends Vorlage { public String getName() { return "VorlageImpl"; } } Wird eine abstrakte Klasse definiert, die ausschliesslich abstrakte Methoden besitzt, so kann man stattdessen besser ein Interface einsetzen. Von Interfaces ist eine „Mehrfachvererbung“ möglich. (Siehe weiter unten Kapitel Interfaces.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 16 / 210 23.03.2010 PROMOD-2a / SS10 1.9 Finale Klassen: Von finalen Klassen kann keine Ableitung gemacht werden. Sie werden mit dem Attribut final erzeugt. Der Aufruf von finalen Klassen ist sehr schnell (wg. early binding). Typische Nutzung: für Hilfsklassen mit static-Methoden, die ohne Instanz einer Klasse aufgerufen werden können. Im Wesentlichen wird durch den Identifier "final" die Vererbungsfähigkeit verhindert. Dies widerspricht eigentlich zwar der Objektorientierung, wird aber z.B. aufgrund von Sicherheitsaspekten in Kauf genommen. Beispielsweise soll eine Passwortüberprüfung nicht einfach überschrieben werden können. Beispiel: public final class Formeln { public static int getKreisUmfang(int radius) { return (2 * java.lang.Math.PI * radius); } } Finale Klassen des JDK sind z.B. java.lang.String und java.lang.Math (Überprüfen Sie das.) Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 17 / 210 23.03.2010 PROMOD-2a / SS10 2. Packages (Selbststudium!) Was Sie lernen: warum in Java Packages verwendet werden wie Sie Packages definieren wie Sie Packages importieren/einbinden was es mit den Zugriffsrechten im Zusammenhang mit Packages auf sich hat Voraussetzungen: Erzeugen, Übersetzen und Ausführen von Java-Anwendungen 2.1 Grundlagen Moderene Programmiersprachen müssen das Design (den Entwurf) eines Programmes unterstützen. Hierfür sind Sprachmittel erforderlich, die es erlauben, ein Programm in Programmeinheiten zu unterteilen, um das Gesamtprogramm übersichtlich (u.a. wg. Wartbarkeit/Änderbarkeit) zu strukturieren. Solche Programmeinheiten sind in Java: Klassen, Interfaces, Threads und Packages. Betrachten wir zunächst Packages: Pakete gruppieren Klassen in einer Sammlung. Pakete eignen sich hervorragend, um die Programme zu organisieren. Packages lassen sich auch in Archiven (jar-Files im zip-Format) halten. Mit Packages werden Namensräume organisiert Für das Verwenden und Einbinden in ein Filesystem ist die Variable CLASSPATH relevant. Sie gibt den Pfad zum Paket-Startverzeichnis an. Andere Möglichkeit: jar-Files „lokal“ lassen (z.B. im src-Package) und in der Compiler-Option –classpath (durch „:“ separiert) auflisten. So können „External Libraries/JARs“ genau definiert eingebunden werden. In ECLIPSE: Project -> Properties -> Java Build Path -> Library -> Add JARs / Add Library / Add external JARs Der Zugriff auf eine Klasse erfolgt durch die Kombination des Package-Namens und des Klassennamens. Package-Namen setzen sich aus kleingeschriebenen Worten zusammen, die einen Pfad repräsentieren, deren Abschnitte durch Punkte voneinander getrennt werden. 2.2 Packages importieren: Man kann eine bestimmte Klasse oder ein gesamtes Paket importieren. Es ergeben sich keine negativen Auswirkungen auf die Kompilierzeit bzw. Codegröße, wenn alle Klassen eines Paketes importiert werden. Jedoch aus Gründen der Übersichtlichkeit bzw. der Eindeutigkeit bietet sich die explizite Importierung einzelner Klassen an. Die Importanweisung steht am Beginn der Quelldatei zwischen einer optionalen package-Anweisung und der ersten Klassendefinition. Über eine import-Anweisung kann immer nur ein Package eingebunden werden. Besitzt ein Package weiterere Unterpackages, so müssen diese separart importiert werden. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 18 / 210 23.03.2010 PROMOD-2a / SS10 Bsp: import java.util.*; import java.sql.*; import java.util.Date; . . . Î da die Klasse "Date" sowohl in java.util bzw. java.sql vorkommt, muss hier der Namenskonflikt mit einer expliziten Importanweisung aufgehoben werden. Ansonsten ergeben sich Fehler zur Kompilierzeit. 2.3 Packages definieren: Um Klassen in ein Paket aufzunehmen, muss der Name des Pakets am Beginn der Klasse angegeben werden. Dies steht noch vor dem eigentlichen Code: package <PackageName> Vor der Package-Anweisung dürfen sich nur Kommentare befinden. Wird keine Package-Anweisung in die Quelldatei aufgenommen, dann gehören die Klassen in dieser Quelldatei zum Standardpaket / Default-Package. Das Standardpaket hat keinen Paketnamen. Alle Klassen im aktuellen Verzeichnis gehören somit zusammen. Bsp: package my.package.proccon; . . //weiterer Quellcode 2.4 Zugriffsrechte in Packages: Für Pakete gelten nach wie vor die Zugriffsmodifizierer public, private und protected. Wird kein Zugriffsattribut angegeben so wird ein defaultmässiges Attribut ("package" oder "friendly") gesetzt, was den Zugriff aller Methoden im selben Paket möglich macht. Beispiel Package-Import und -Definition: import bsp_u_ueb_erstes_semester.woche_10_1gp_kap10_packages.util.*; public class PackageDemo { public static void main(String[] args) { greg_calendar.prefix = "Heute: "; System.out.println("Aufgepasst..."); System.out.println(greg_calendar.getDate()); // greg_calendar.prefix = "Heute ist der "; System.out.println(bsp_u_ueb_erstes_semester.woche_10_1gp_kap10_packages.util.greg_cal endar.getDate()); } } --------------------------------util.greg_calenadr.java----------------------------------------------------------package bsp_u_ueb_erstes_semester.woche_10_1gp_kap10_packages.util; public class greg_calendar { static String prefix; public static String getDate() { java.util.GregorianCalendar gc = new java.util.GregorianCalendar(); System.out.println("greeting from the long named package..."); return (gc.get(gc.DATE) + "." + (gc.get(gc.MONTH)+1) + "." + gc.get(gc.YEAR)); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 19 / 210 23.03.2010 PROMOD-2a / SS10 } } Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 20 / 210 23.03.2010 PROMOD-2a / SS10 3. Interfaces (und Ergänzungen zu Klassen) Was Sie lernen: wie Sie Interfaces definieren und verwenden was innere und anonyme Klassen sind was Adapterklassen sind und wozu sie verwendet werden Voraussetzungen: Klassen und Vererbung 3.1 Grundlagen Beim Entwurf von objektorientierten Systemen geht man über verschiedene Stufen der Abstraktion: Systemanalyse: „Welche Klassen werden benötigt und welche Beziehungen bestehen zwischen den Klassen?“ (Konzeptionelle Sicht: welche Klassen spielen eine Rolle („Klassenkandidatenkatalog“); in welcher Beziehung stehen die Klassen untereinander?). Entwurf/Design: „Welche Schnittstellen haben die Klassen?“ (Spezifizierende Sicht: hier kümmert man sich um die Schnittstellen der Klassen!) Implementierung: übernimmt die Ausprogrammierung der Schnittstellen (Interfaces) Æ Verfeinerung der Spezifikation. (Implementierende Sicht: hier werden die Schnittstellen implementiert!) Java unterstützt das „Programmieren im Großen“ (spezifizierende Sicht) mit dem Sprachmittel interface. Ein Interface definiert einen abstrakten Datentyp, der nur Konstanten und Methodensignaturen besitzt. Methoden werden nur deklariert, nicht implementiert. Klassen können mehrere Interfaces implementieren (=> „Mehrfachvererbung“). Interfaces werden mit dem Schlüsselwort "interface" eingerichtet Definition: [public] interface <Name> [extends <Interface>[,<Interface>]] { [public, static] final <Typ> Konstante; [public] <Typ> Methode(<Parameter>); } Implementierung: class <Name> implements <InterfaceName>, <InterfaceName> Alle Methoden in einem Interface sind automatisch abstract und public. Elemente von Interfaces können nicht protected oder private sein. Interfaces haben keine Konstruktoren/Destruktoren Es können aber Variablen vom Typ "Interface" definiert werden Interfacename Variable; Î hier können Objekte zugewiesen werden, deren Klasse das Interface implementiert. Über den Operator instanceof kann geprüft werden, ob ein Objekt ein Interface implementiert. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 21 / 210 23.03.2010 PROMOD-2a / SS10 Interfaces sind keine Klassen. Insbesondere kann man eine Schnittstelle nicht mit dem Operator new instantiieren. (Man spricht auch nicht von Ableitung, sondern von Implementierung). Beispiel 1 – Interface: (Phänomen: Mehrfachtypisierung) public class Interface { public static void main(String[] args) { TestClass1 tco1 = new TestClass1(); TestClass2 tco2 = new TestClass2(); tco1.sayHello(); /*1*/ tco2.sayHello(); /*1*/ if (tco1 instanceof TestClass1) /*2*/ // tco1 ein Klassen-Tpy System.out.println("tco1 is instance of TestClass1"); if (tco1 instanceof PoliteObject) /*2*/ // tco1 ein Interface-Typ System.out.println("tco1 is instance of PoliteObject"); } PoliteObject po; /*3*/ //Interface-Varable nimmt auch “Ableitungen” auf. po = tco1; /*4*/ // po referenziert TestClass1-Objekt po.sayHello(); po = tco2; /*5*/ // po referenziert TestClass2-Objekt po.sayHello(); } interface PoliteObject { public void sayHello(); } // nur Methoden-Signatur class TestClass1 implements PoliteObject { public void sayHello() { System.out.println("Hello World"); } } class TestClass2 implements PoliteObject { public void sayHello() { System.out.println("Guten Tag"); } } // // // // // // // // // unterschiedliche Implementierungen eines Interfaces Erläuterungen zum Beispiel: Das Beispiel definiert die Schnittstelle "PoliteObject" welche die Methode "sayHello()" anbietet. Die beiden Klassen "TestClass1" und "TestClass2" implementieren jeweils Methoden für die durch die Schnittstelle definierten Operationen. Der erste Anweisungsblock zeigt die (simple) Ausführung /*1*/ der genannten Methode. Die darauffolgende Codesequenz /*2*/ hebt den Aspekt der Mehrfachtypisierung hervor (Objekt von schnittstellen-implementierender Klasse ist sowohl Ausprägung der Klasse selbst (im Beispiel: TestClass1), als auch der Schnittstelle (PoliteObject). Abschließend wird eine Variable vom Typ der Schnittstelle deklariert /*3*/ und zunächst mit der Referenz auf ein Objekt der Klasse "TestClass1" belegt /*4*/. Der (statisch typsicher mögliche) Aufruf "sayHello()" ist auch hier möglich. Gleiche Bedingungen herrschen nach der Zuweisung eines Objektes vom Typ "TestClass2" an dieselbe Variable. Beispiel 2 – Interface: (Tpyische Verwendung eines Interfaces) // Ausgabetypen.java im default-Package-------------------------------------public interface Ausgabetypen { void ausgabe(); void ausgabeLang(); void ausgabeKurz(); } // Datum1.java im default Package-------------------------------------------public class Datum1 implements Ausgabetypen // alle Ausgabetypen-Methoden Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 22 / 210 23.03.2010 PROMOD-2a / SS10 { private int tag, monat, jahr; // müssen implementiert werden!!! alle Interface-Methoden werden implementiert public Datum1() { this(1, 1, 2002); } public Datum1(int tag, int monat, int jahr) { this.tag = tag; this.monat = monat; this.jahr = jahr; } public void ausgabe() { System.out.println("Datum: " + tag + "." + monat + "." + jahr); } public void ausgabeLang() { } System.out.println("Das gespeicherte Datum ist der " + tag + "." + monat + "." + jahr); public void ausgabeKurz() { System.out.println(tag + "." + monat + "." + jahr); } } // Zeit1.java im default-Package--------------------------------------------------public class Zeit1 implements Ausgabetypen { private int sekunde, minute, stunde; public Zeit1() { this(0, 0, 0); } public Zeit1(int sekunde, int minute, int stunde) { this.sekunde = sekunde; this.minute = minute; this.stunde = stunde; } alle Interface-Methoden werden implementiert public void ausgabe() { System.out.println("Zeit: " + sekunde + ":" + minute + ":" + stunde); } public void ausgabeLang() { System.out.println("Die gespeicherte Zeit ist " + sekunde + ":" + minute + ":" + stunde); } public void ausgabeKurz() { System.out.println(sekunde + ":" + minute + ":" + stunde); } } // Test1.java im default-Package----------------------------------------------------public class Test10 { private Ausgabetypen[] at = new Ausgabetypen[4]; public Test10() Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 23 / 210 23.03.2010 PROMOD-2a / SS10 { at[0] at[1] at[2] at[3] = = = = new new new new Datum1(4, 3, 2002); Zeit1(0, 12, 12); Datum1(8, 9, 2001); Zeit1(59, 59, 23); for(int i = 0; i < at.length; i++) if (at[0] instanceof Ausgabetypen) at[i].ausgabe(); } public static void main(String[] args) { new Test10(); // Start über Objektinstanziierung } } Übung: Fassen Sie die Unterschiede zwischen abstrakten Klassen und Interfaces zusammen. 3.2 Adapterklassen Eine Adapterklasse in Java ist eine Klasse, die ein vorgegebenes Interface mit leeren Methodenrümpfen implementiert, bzw. Rümpfen, die einen Standardwert zurückliefern. Adapterklassen können verwendet werden, wenn aus einem Interface lediglich ein Teil der Methoden benötigt wird, der Rest aber „uninteressant“ ist, (momentan) nicht gebraucht wird. In diesem Fall leitet man einfach eine neue Klasse aus der Adapterklasse ab, anstatt das zugehörige Interface zu implementieren, und überlagert die benötigten Methoden. Entwicklungsidee: stets zu Interfaces gleich die Adapterklassen mit anbieten….!!!! Hinweis: - wird vor allem bei der GUI-Programmierung verwendet. - Adapterklassen können auch weitere/zusätzliche Methoden anbieten. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 24 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Adapterklassen (vgl. Beispiel 2 oben): // AusgabetypenAdapter.java im default-Package------------------------------------------------------public class AusgabetypenAdapter implements Ausgabetypen // Adapter impl. Interface { public void ausgabe() {} public void ausgabeLang() {} public void ausgabeKurz() {} } // Datum2.java im default Package---------------------------------------------------------------public class Datum2 extends AusgabetypenAdapter // jetzt nur die gewünschten { // Methoden implementieren private int tag, monat, jahr; public Datum2() { this(1, 1, 2002); } public Datum2(int tag, int monat, int jahr) { this.tag = tag; this.monat = monat; this.jahr = jahr; } public void ausgabe() // die anderen Methoden bleiben wie sie sind { System.out.println("Datum: " + tag + "." + monat + "." + jahr); } } Übung: Machen Sie das Beispiel lauffähig durch die Implementierung der fehlenden Klassen. 3.3 Innere Klassen (auch: geschachtelte / eingebettete / nested class) Eine innere Klasse ist eine Klasse, die innerhalb einer anderen Klasse definiert ist. Aus inneren Klassen können auf alle Methoden und Daten der äußeren Klasse zugegriffen werden, auch wenn diese private sind. Innere Klassen sind den anderen Klassen des Pakets verborgen. (Weitere Stufe der Kapselung) Es können nur in der äußeren Klasse Objekte der inneren Klasse erzeugt werden ÆAusnahme: statische innere Klassen, von denen auch in anderen Klassen Objekte erzeugt werden können. Statische innere Klassen besitzen keine Referenz auf ein Objekt der äußeren Klasse. Für jede innere Klasse wird beim Compilieren eine eigene Klassendatei erzeugt. Diese wird so gekennzeichnet: outer$inner.class Der Klassenname nach außen lautet outer.inner Der Konstruktoraufruf sieht folgendermaßen aus: in outer: new inner(…) sonst: outer.new inner(…) Die bekannten Modifier public, private, protected, final und static können in inneren Klassen verwendet werden Beispiel – innere Klassen: class aussen // Implementierungsdetails verborgen in inneren Klassen { class mitte // nur zur Verwendung in aussen { Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 25 / 210 23.03.2010 PROMOD-2a / SS10 class innen // nur zur Verwendung in mitte { . . . } } } Hinweis/Motivation für innere Klassen: Innere Klassen sind sehr komfortabel, wenn man ereignisgesteuerte Programme schreibt (siehe weiter unten „Ereignisse und Event-Handling“). Anonyme innere Klassen bieten sich an, wenn man Callbacks en passant definieren möchte (siehe weiter unten „Ereignisse und Event-Handling“ z.B. bei der AWT-Programmierung). Ausserhalb dieser Anwendungsgebiete sieht man innere Klassen relativ selten, weil sie nicht im Brennpunkt des objektorientierten Modellierens liegen. a) Die typische Vorgehensweise ist der Entwurf „kleiner“ Klassen, die dann per Ableitung um weitere Daten und Methoden angereichert werden. Atypisch: komplexes im Inneren immer feiner zu modellieren. b) Innere Klassen kann man in anderen Klassen nicht nutzen, das möchte man aber im Sinne einer Wiederverwendung! 3.4 Lokale Klassen Siehe „Tiger“-Buch Seite (415 ff.) und andere Literatur. Übung: Beschreiben Sie was lokale Klassen sind. 3.5 Anonyme Klassen Anonyme Klassen sind spezielle Varianten von inneren Klassen. Ort der Verwendung: typischerweise auf Parameterposition von Methodenaufrufen. Anonyme Klassen besitzen keinen Namen. Es werden die Klassendeklaration und Objekterzeugung in einem Schritt zusammengefasst. Anonyme Klassen müssen von einer anderen Klasse abgeleitet werden oder ein Interface/Adapterklasse implementieren. Anonyme Klassen werden verwendet, wenn eine einzige Objektinstanz gebraucht wird. Meist auf der Parameterposition von Methoden. (z. B. bei Ausdrücken oder aber der GUI-Programmierung). o Statt eines Objektes wird über new und die nachfolgende Angabe eines Klassen- oder Interfacenamens die Erstellung sowie Implementierung einer Klasse mit Objekterzeugung verbunden. o Im runden Klammernpaar können Parameter für den Konstruktor übergeben werden (falls eine Superklasse vorhanden ist). o Bei Interfaces können keine Parameter übergeben werden, da sie keine Konstruktoren besitzen. o Das erzeugte Objekt wird der Methode als Parameter übergeben. Für anonyme Klassen wird beim Compilieren eine eigene Klassendatei erzeugt. Anonyme Klassen werden durchgezählt und mit dem $-Zeichen mit äußeren Klassennamen verbunden: outer$1.class Anonyme Klassen haben keine Konstruktoren. Beispiel – anonyme Klassen: // Ausgabetypen.java im default-Package------------------------------------------public interface Ausgabetypen { void ausgabe(); void ausgabeLang(); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 26 / 210 23.03.2010 PROMOD-2a / SS10 } void ausgabeKurz(); // AusgabetypenAdapter.java im default-Package-----------------------------------public class AusgabetypenAdapter implements Ausgabetypen { public void ausgabe() {} public void ausgabeLang() {} public void ausgabeKurz() {} } // Test14.java im default-Package-------------------------------------------------public class Test14 { private Ausgabe a = new Ausgabe(); private String s = "Anonym bleiben"; public Test14() // Konstruktor { a.drucke(new AusgabetypenAdapter() /// anonyme Klasse impl. Adapter /////// { /// Objekt wird erzeugt und der /////// public void ausgabe() /// Methode drucke() mitgegeben /////// { System.out.println(s); } Überschreibt }); leere Methode } aus der Adapterklasse public static void main(String[] args) { new Test14(); // Start via Konstruktor } class Ausgabe //////////////////////////////////////////////////////////////// { public void drucke(Ausgabetypen at) { at.ausgabe(); } } } Das oben dargestellte Beispiel wird uns als „Muster“ bei der AWT-Programmierung wieder begegnen! 3.6 Statische innere Klassen (static class) / statische innere Schnittstellen Bei inneren Klassen/geschachtelten Klassen kennt das eingeschlossene Objekt sein umgebendes Objekt und umgekehrt. Bei statisch geschachtelten Klassen gibt es diesen Bezug nicht: bei der statischen inneren Klasse braucht man keine Objekt von der äusseren Klasse, um ein Objekt der inneren Klasse zu erzeugen. Im äusseren Objekt können mehrere statische innere Objekte erzeugt werden. Übung: Überlegen Sie sich eine Beispiel, anhand dessen Sie den oben beschreibenen Sachverhalt experimentell studieren können. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 27 / 210 23.03.2010 PROMOD-2a / SS10 Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) 4. Ein-/Ausgabe auf Konsole,Dateien u.a.(ADRELI_CON) Was Sie lernen: wie Sie Ein- und Ausgabe über die Konsole und die Tastatur realisieren wie Sie mit Dateien (class File) arbeiten: die Schnittstelle zu Pfadnamen was Dateien mit wahlfreiem Zugriff sind und wie sie benutzt werden Voraussetzungen: Arbeiten mit Klassen und Methoden Arbeiten mit dem Exceptionhandling Im Grunde könnte das Kapitel auch „Persistierung – Erster Teil“ heissen: es gibt die zwei großen Persistenz-Konzepte, um Daten dauerhaft zu machen. Zum einen können dazu Dateien genutzt werden und zum zweiten Datenbanken. Beide Ansätze haben ihere Berechtigung und jeweilse ihre spezifischen Vor- und Nachteile. In diesem Kapitel kümmern wir uns um die ersten Schritte der Persistierung mit Hilfe der Dateien; hierfür gibt es mehrere Konzept: wir können Dateien als Random-Access-Files sehen oder auch als Streams. Die Streams wiederum sind selbst eine abstrahierende Schicht, hinter der sich neben Dateien auch Sockets, Pipes und Strings verbergen können. Auf für die Persistierung mit Datenbanken gibt es mehrere Java-Konzepte; diese behandeln wir in separaten Kapiteln. 4.1 Grundlagen Es ist zwischen Applikationen und Applets zu Unterscheiden. Applets haben i. d. R. keinen Zugriff auf Files des lokalen Rechners. I/O - Operationen laufen in Java über: 1.) Streams (Class Stream) für Binär- und und Zeichen-I/O 2.) Dateien (Random-Access-Files, Dateien für wahlfreien Zugriff; Class RandomAcessFile und zus. File) Merkmale von Streams: Besitzen Datenquelle (Source) und Datensenke (Destination). Ein Datenstrom kann von einer beliebigen Quelle kommen: Dateien, Netzwerkschnittstelle (Sockets: siehe Kapitel „Socket-Programmierung. Für Sockets gibt’s z.B. getInputStream() und getOutputStream()-Methoden, wenn es um Byte-Stream-Kommunikation (= binäre i/o) geht. (Prüfen Sie ein entsprechendes Methoden-Angebot für die Charakter-Stream-Kommunikation (= Zeicheni/o))! Pipes (für Pipes gibt es eigene Stream-Klassen, jeweils für die Byte- oder Character-Kommunikation), oder auch Strings (für Stings/Arrays gibt es jeweils eigene Stream-Klassen für die Byte- oder Charcter-Kommunikation) können Quellen sein. Das gleiche gilt für das Ziel des Datenstroms. Jeder Datenstrom kann nach dem gleichen Prinzip, mit den gleichen Methoden verarbeitet werden, egal woher der Strom kommt und für welches Ziel er bestimmt ist. Streams sind unidirektional (Monolog). Für einen Dialog zwischen zwei Kommunikationspartnern (Applikationen, Threads) werden zwei Streams benötigt. Streams unterliegen dem FIFO - Prinzip ("first in first out”) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 28 / 210 23.03.2010 PROMOD-2a / SS10 Während der Datenübertragung können Daten gefiltert (bearbeitet) werden Merkmale von wahlfreien Dateien: für den wahlfreien Zugriff ist die Quelle und das Ziel immer eine Datei. Dateien können sind bidirektional: sie können geschrieben und gelesen werden. Während der Übertragung der Daten ist keine Filterung möglich. Basis-Philosophie für den Umgang mit Streams und Dateien: Bevor Dateien und Streams verwendet werden können (Anwendung von E/A-Operationen) müssen sie eröffnet werden. Nicht mehr benötigte Dateien und Streams sollten wieder geschlossen werden (Methode close()). Das Öffnen von Dateien und Streams wird in Java üblicherweise über den Konstruktor der jeweiligen Klasse realisiert. Standard-Streams (siehe unten) werden automatisch geöffnet und geschlossen. 4.2 Standard-Ein- und Ausgabe Die Ein-/Ausgabe über die Standard-E/A-Geräte (meist Tastatur und Bildschirm) ist in der Klasse System implementiert. (Diese Klasse ist im Paket java.lang definiert; bei der Verwendung der Standard E/A ist also kein zusätzliches Paket einzubinden.) Die Standard-E/A arbeitet mit Objekten der Stream-Klassen InputStream und PrintStream. Stream-Typen Standard-Eingabestrom Standard-Ausgabestrom Standard-Fehlerausgabestrom Datenfelder-Felder der Klasse System static InputStream in static PrintStream out static PrintStream err Beispiele, Methoden System.in.read() System.out.print() System.err.print() Beispiel – I/O import java.io.*; public class IO { public static void main(String[] args) { byte [] b = new byte[1]; try { } } System.out.println("Ein Zeichen eingeben: "); System.in.read(b); System.out.println( (char) b[0] + " hat ASCII-Wert " + b[0]); catch (IOException ioe) { System.out.println(ioe.getMessage()); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 29 / 210 23.03.2010 PROMOD-2a / SS10 "out"-Methoden read() print(char c) / println(char c) print(double d) / println(double d) print(int i) / println(int i) print(Object o) / println() print(String s) / println() read(byte[] b) "read"-Methoden Liefert nächstes Zeichen aus dem Eingabestrom als return füllt ein Byte-Array mit maximal b.length ASCII-Zeichen read(byte[] b, int off, int len) - ,, - , jedoch mit Startpos. und Länge Methode zum den Eingabepuffer leeren: System.in.skip(System.in.available()); Beispiel: // // // // // // // PROMOD; Prof. Illik Gepufferte Ein-/Ausgabe Experimentieren Sie mit dem Programm und prüfen Sie, was passiert, wenn Sie die jeweiligen Zeilen mit der skip()-Methode auskommentieren. Erklärung! import java.io.*; public class StdIO { public static void main(String[] args) { int a; byte[] b = new byte[1]; byte[] line = new byte[81]; try { System.out.print("1. Bitte geben Sie ein Zeichen ein: "); a = System.in.read(); System.out.println("Zeichen "+ (char)a + " hat den ASCII-Code " + a + "!"); // Vergleiche ASCII-Code mit dem UNICODE (Unicode-Tabelle in Kap15) // www.unicode.org System.out.println("2. Bitte geben Sie ein Zeichen ein: "); //System.in.skip(System.in.available()); //Leeren des Tastaturpuffers System.in.read(b); System.out.println((char)b[0] + " hat den ASCII-Code " + b[0]); System.out.print("3. Bitte geben Sie noch ein Zeichen ein: "); System.in.skip(System.in.available()); System.in.read(b); System.out.println((char)b[0] + " hat den ASCII-Code " + b[0]); System.out.print("4. Geben Sie jetzt mehrere Zeichen ein: "); System.in.skip(System.in.available()); System.in.read(line); //for(int i=0;line[i] != '\u0000'; i++) for(int i = 0;line[i] != 0; i++) //for (int i = 0; i < line.length; i++) { System.out.print((char)line[i]); } } catch(IOException ioex) { System.out.println(ioex.getMessage()); } } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 30 / 210 23.03.2010 PROMOD-2a / SS10 4.3 Die Klasse File: Was die Klasse File ist: „An abstract representation of file and directory pathnames.“ (Zitat aus der File-API). Die Methoden der Klasse File sind auf Dateien (Files) und Verzeichnisse ausgerichtet. Z. B. Namen festlegen; Dateien anlegen, umbenennen, Attribute auflisten,... Die Klasse "File" besitzt drei verschiedene Konstruktoren: File(String path) File(String parentpath, String filename) File(File directory, String filename) Î Die Angabe des Pfads ist plattformabhängig, dass heißt, auf Windows-Rechnern trennt ein Backslash die Pfade (»temp\dasda«) und auf UNIX-Maschinen ein normaler Slash (»temp/dasda«). Glücklicherweise können wir die Klasse File nach dem separatorChar fragen. Î Ebenso wie bei den Pfadtrennern gibt es einen Unterschied in der Darstellung des Wurzelverzeichnisses (Root). Unter UNIX ist dies ein einzelner Slash (»/«), und unter Windows ist die Angabe des Laufwerks vor dem Doppelpunkt und dem Backslash-Zeichen gestellt (»D:\«) Î Eine absolute Pfadangabe wird inklusive der Wurzel angegeben. Bsp: C:\Programme\MyJavaProg\IO-Prog.jar Î Eine relative Pfadangabe enthält keine Wurzel uns ist relativ zum aktuellen Verzeichnis. Bsp: \MyJavaProg\IO.jar Î Siehe auch: UNC (Universal Naming Convention) für Microsoft Windows. Bsp – Pfadangaben unter Linux File f1 = new File("/tmp/file1.txt"); //absolute Pfadangabe inkl. Wurzel (/) File f2 = new File("tmp/file2.txt"); //relative Pfadangabe ohne Wurzel wichtige Methoden der Klasse File (Auszug): Pfad und Dateiangaben String getName() String getPath() String getParent() String getAbsolutPath() File [] listRoots() Gibt einen String zurück, der den Dateinamen des File-Objekts enthält Gibt einen String zurück, der den Pfadnamen der Datei enthält Gibt einen String zurück, der den übergeordneten Pfad des File-Objekts enthält Gibt einen String zurück, der den absoluten Pfadnamen enthält Gibt ein Array von File-Objekten zurück, die allen verfügbaren Stammverzeichnissen entsprechen Manipulstionsoperationen boolean createNewFile() Legt eine neue, leere Datei mit dem entsprechenden Namen an (RECHTE!!!) boolean delete() Versucht die Datei zu lösche, liefert im Erfolgsfall "true", andernfalls "false" boolean mkdir() Versucht ein Unterverzeichnis anzulegen, im Erfolgsfall "true", andernfalls "false" boolean renameTo(File f) Liefert "true", falls der Name entsprechend geändert wurde, andernfalls "false" boolean setReadOnly() Setzt das Dateiatribut »schreibgeschützt«. Im Erfolgsfall "true", andernfalls "false" Datei-und Verzeichnisinfos boolean canRead() boolean canWrite() boolean exists() boolean isDirectory() Gibt an, ob die Datei durch die aktuelle Anwendung gelesen werden kann Gibt an, ob die Datei beschreibbar oder nur lesbar ist Liefert "true", wenn die Datei oder das Verzeichnis existiert, andernfalls "false" Gibt "true" zurück, wenn das File-Obj. ein Verzeichnis repräsentiert, sonst "false" boolean isHidden() Prüft, ob das File-Objekt eine versteckte Datei oder Verzeichnis repräsentiert long lastModified() Gibt die Zeit der letzten Änderung in mSec als Unix-Timestamp zurück (1.1.1970) long length() Gibt die Länge der Datei in Byte zurück, oder 0, wenn die Datei nicht existiert string [] list() und FILE [] listfile()-Methoden listen die Verzeichnisinhalte auf. Siehe API-Doku class File. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 31 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel: import java.io.*; public class FileInfo { public static void main(String[] args) { File verzX = new File("/temp/a/b/c/d"); if(!verzX.exists()) { verzX.mkdir(); // +++++ wirft keine IOException -> siehe Doku! System.out.println("Verzeichnis " + verzX.getPath() + " angelegt: " + verzX.exists()); } File file = new File("/temp/test.txt"); if(! file.exists()) { try { file.createNewFile(); } catch(IOException e) { System.out.println("Die Datei konnte nicht erzeugt werden."); return; } System.out.println("Datei " + file.getName() + " angelegt: " + file.exists()); } if(file.isFile()) System.out.println(file.getName() + " ist eine Datei "); else System.out.println(file.getName() + " ist keine Datei "); if(file.isDirectory()) System.out.println(file.getName() + " ist ein Verzeichnis "); else System.out.println(file.getName() + " ist kein Verzeichnis "); System.out.println("Parent: " + file.getParent()); System.out.println("Parentdirectory: " + file.getParentFile()); System.out.println("Pfad (ohne Wurzel): " + file.getPath()); System.out.println("absoluter Pfad (mit Wurzel): " + file.getAbsolutePath()); System.out.println("Dateiattribute:"); System.out.println("lesen erlaubt: " + file.canRead()); System.out.println("schreiben erlaubt: " + file.canWrite()); System.out.println("versteckt: " + file.isHidden()); System.out.println(System.getProperty("user.dir")); // HINWEIS: // "user.dir" ist ein Key der eine System-Property beschreibt // Alle Keys sind in der Klasse System bei der Methode getProperties gelistet. } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 32 / 210 23.03.2010 PROMOD-2a / SS10 4.4 Die Klasse RandomAccessFile: Dateien mit wahlfreiem Zugriff Es gibt eine aktuelle Schreib- und Leseposition, welche durch den Schreib-Lese-Zeiger (Seek-Pointer) kennzeichnet (Siehe Bild an der Tafel.) Eine RandomAccess-Datei ist vorstellbar als ein großes Byte-„Array“ Der Seek-Pointer kann innerhalb der Datei beliebig verschoben werden Wenn eine RA-Datei eröffnet wird, muss sie bereits physisch existieren, ansonsten ergibt sich eine FileNotFound-Exception Wird eine RA-Datei zum Schreiben eröffnet, so wird sie als File angelegt, falls sie noch nicht existiert Eine RA-Datei kann nicht geleert werden. Hierzu muss sie gelöscht und anschließend wieder angelegt werden Methoden um den Seek-Pointer zu verstellen: Jedes Lesen und Schreiben versetzt den Seek-Pointer um die Anzahl der gelesenen/geschriebenen Bytes (impliziter Versatz in Richtung Dateiende) void seek(long pos) int skipBytes(int i) long getFilePointer() Setzt den Dateizeiger auf pos Bytes gerechnet vom Beginn der Datei Positioniert den Dateizeiger relativ zur aktuellen Position in Richtung Dateiende. Rückgabewert ist die Anzahl der übersprungenen Zeichen. Liefert die aktuelle Position des seek-pointers Die Klasse RandomAccessFile bietet für sämtliche primitiven Datentypen Methoden an, um entweder eine RA-Datei zu beschreiben oder auszulesen. Es gibt aber noch weitere Write- und Readmethoden. Hierzu gibt die Java-Doku nähere Informationen. Beispiel: import java.io.*; public class RandomAccessFileTest { public static void main(String[] args) { Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 33 / 210 23.03.2010 PROMOD-2a / SS10 } try { RandomAccessFile file = new RandomAccessFile("zzz_randfile.txt", "rw"); file.writeBytes("AAAAA\r\nBBBBB\r\nCCCCC\r\n"); // writeBytes() schreibt ASCII //file.writeChars("AAAAA\nBBBBB\nCCCCC\n"); // writeChars() schreibt UNICODE file.setLength(12); // File wird "abgeschnitten" nach 12. Zeichen file.seek(0); System.out.println("das erste Mal lesen"); for(int i = 1; i <= 3; i++) System.out.println(file.readLine()); file.writeBytes("DDDDD\nEEEEE\nFFFFF\n"); file.seek(0); System.out.println("Das zweite Mal lesen"); for(int i = 1; i <= 10; i++) System.out.println(file.readLine()); file.close(); } catch(FileNotFoundException fnfex) { System.out.println(fnfex.getMessage()); } catch(IOException ioex) { System.out.println(ioex.getMessage()); } } Aus der API-Beschreibung von class RandomAcessFile: Method Summary void close() Closes this random access file stream and releases any system resources associated with the stream. FileChannel getChannel() Returns the unique FileChannel object associated with this file. FileDescriptor getFD() Returns the opaque file descriptor object associated with this stream. long getFilePointer() Returns the current offset in this file. long length() Returns the length of this file. int read() Reads a byte of data from this file. int read(byte[] b) Reads up to b.length bytes of data from this file into an array of bytes. int read(byte[] b, int off, int len) Reads up to len bytes of data from this file into an array of bytes. boolean readBoolean() Reads a boolean from this file. byte readByte() Reads a signed eight-bit value from this file. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 34 / 210 23.03.2010 PROMOD-2a / SS10 char readChar() Reads a Unicode character from this file. double readDouble() Reads a double from this file. float readFloat() Reads a float from this file. void readFully(byte[] b) Reads b.length bytes from this file into the byte array, starting at the current file pointer. void readFully(byte[] b, int off, int len) Reads exactly len bytes from this file into the byte array, starting at the current file pointer. int readInt() Reads a signed 32-bit integer from this file. String readLine() Reads the next line of text from this file. long readLong() Reads a signed 64-bit integer from this file. short readShort() Reads a signed 16-bit number from this file. int readUnsignedByte() Reads an unsigned eight-bit number from this file. int readUnsignedShort() Reads an unsigned 16-bit number from this file. String readUTF() Reads in a string from this file. void seek(long pos) Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs. void setLength(long newLength) Sets the length of this file. int skipBytes(int n) Attempts to skip over n bytes of input discarding the skipped bytes. void write(byte[] b) Writes b.length bytes from the specified byte array to this file, starting at the current file pointer. void write(byte[] b, int off, int len) Writes len bytes from the specified byte array starting at offset off to this file. void write(int b) Writes the specified byte to this file. void writeBoolean(boolean v) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 35 / 210 23.03.2010 PROMOD-2a / SS10 Writes a boolean to the file as a one-byte value. void writeByte(int v) Writes a byte to the file as a one-byte value. void writeBytes(String s) Writes the string to the file as a sequence of bytes. void writeChar(int v) Writes a char to the file as a two-byte value, high byte first. void writeChars(String s) Writes a string to the file as a sequence of characters. void writeDouble(double v) Converts the double argument to a long using the doubleToLongBits method in class Double, and then writes that long value to the file as an eight-byte quantity, high byte first. void writeFloat(float v) Converts the float argument to an int using the floatToIntBits method in class Float, and then writes that int value to the file as a four-byte quantity, high byte first. void writeInt(int v) Writes an int to the file as four bytes, high byte first. void writeLong(long v) Writes a long to the file as eight bytes, high byte first. void writeShort(int v) Writes a short to the file as two bytes, high byte first. void writeUTF(String str) Writes a string to the file using UTF-8 encoding in a machine-independent manner. Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 36 / 210 23.03.2010 PROMOD-2a / SS10 Lecture -selfstudy- 5. Streams: (ADRELI_CON) Was Sie lernen: für welche Zwecke die verschiedenen Stream-Klassen eingesetzt werden was Byte-Streams (Binär-Streams) und Charakter-Streams (Zeichen-Streams) unterscheidet was gepufferte E/A bedeutet Voraussetzungen: Kenntnisse der Klasse File Arbeiten mit dem Exceptionhandling 5.1 Grundlagen Ein Datenstrom kann aus beliebiger Quelle stammen (engl. Source, Spring); siehe oben: Datei, Netz, … Ein Datenstrom kann in beliebige Senke (engl. Sink) gehen: dies kann wiederum seine ein Datei, ein Socket, eine Pipe, ein String. Ein Datenstrom ist aber immer unidirektional, geht also immer in eine Richtung Fehlerbehandlung Bei fast allen Operationen (z.B. beim Anlegen einer Datei, beim Öffnen, Lesen, Schreiben, …) können Fehler auftreten. Die Fehlerursachen hierfür können vielfältig sein: die Rechte für eine Operation fehlen, betroffene Datei existiert nicht, usw. Solche Fehler lösen eine Ausnahme vom Typ IOException aus. Aus diesem Grund müssen alle Operationen in einem try-catch-Block eingeschlossen sein. Tritt zum Beispiel beim Schreiben ein Fehler auf, so kann im catch-Block eine entsprechende Meldung ausgegeben und die Datei geschlossen werden. Gepufferte Ein- und Ausgabe Aus Gründen er Effizienz sind Ein- und Ausgaben meißt gepuffert. Das bedeuted, dass die Daten z.B. beim Lesen zunächst in einen Zwischenspeicher (Puffer) eingelesen und gesammelt werden. Von dort werden die Daten dann an ihr Ziel weitergeleitet, entweder o wenn der Puffer voll ist oder o wenn eine Methode zum Leeren des Puffers (flush()) aufgerufen wird oder o wenn das File geschlossen wird. So werden größere Mengen an Zeichen transportiert, was wesentlich effizienter ist als der direkte Einzelzeichenzugriff auf die vergleichsweise langsamen peripheren Ein-/Ausgabegeräte. Auch die Ein-/Ausgabe über die Standard-Geräte ist gepuffert. Dies ist daran erkennbar, dass erst mit dem Betätigen der RETURN-Taste die Daten gelesen werden. (Siehe oben, Kapitel „Standard-Ein-/Ausgabe.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 37 / 210 23.03.2010 PROMOD-2a / SS10 Die Hauptklassen von java.io: Object OutputStream InputStream Writer Byte-Stream Binäerdaten Lesen/Schreiben Reader File RA-File ... Character-Streams Zeichendaten Lesen/Schreiben ObjectStreamClass StreamTokenizer FileDescriptor In Java werden generelle zwei Streamtypen unterschieden: Character Streams (zeichenartige Daten (Text)) und Byte Streams (binäre Daten). Wie der Name schon andeutet, sind Byte Streams auf die Verarbeitung von beliebigen Byte-artigen Informationseinheiten -- und damit acht Bit große Einheiten -- beschränkt. Damit lassen sich ASCII-Daten verarbeiten, die historisch begründet, in sehr großer Anzahl vorliegen. Auch Java-Quellcode ist ASCIICode. Mit dem JDK v1.1 zusätzlich das Konzept der Character Streams eingeführt, die generell 16 Bit lange UNICODEZeichen (Character) bereitstellen. 5.2 Überblick (UNICODE-) Character-Stream-Klassen (Zeichendaten (Text) schreiben und Lesen) CharacterStreams verwenden den 16-Bit-Unicode und sind daher in Java für die Arbeit mit Strings und Zeichentypen besser geeignet als Byte-Streams. Abstrakte Klassen Writer Bridge-/Brücken-Klassen BufferedWriter CharArrayWriter OutputStreamWriter FilterWriter PipedWriter StringWriter PrintWriter FileWriter Reader BufferedReader CharArrayReader LineNumberR. InputStreamReader FilterReader FileReader PushBackR. PipedReader StringReader Writer-Ableitungen: Klasse (Character-) BufferedWriter CharArrayWriter OutputStreamWriter FilterWriter PipedWriter StringWriter PrintWriter Prof. Illik Was die Klasse leistet Gepuffertes Schreiben. Zeilenvorschub wird transparent plattformabhängig umgesetzt Zeichen werden in ein Char-Array geschrieben, das Array wächst automatisch Brücke zwischen Character- und Byte-Strömen; Unicode-Zeichen werden automatisch während des Schreibens in die entsprechende ASCII/Byte-Repräsentation umgesetzt. Abstrakte Klasse (für FileWriter). Abstrakte Basisklasse der verschiedenen Implementierungen zum gefilterten Schreiben Ausgabestrom über Character-Pipe Ausgabestrom, der in einen String schreibt Ausgabestrom, der formatierte Objektrepräsentationen schreibt. Methoden dieser Klasse PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 38 / 210 23.03.2010 PROMOD-2a / SS10 verursachen keine E/A-Ausnahmeereignisse HINWEIS: auf PrintWriter wird typ. mit den Methoden .print(), .println() geschrieben, während auf die anderen Steam-Objekt typ. mit .write() geschreiben wird. Reader-Ableitungen: Klasse (Character-) BufferedReader CharArrayReader InputStreamReader FilterReader PipedReader StringReader Was die Klasse leistet Liest Textinhalte gepuffert aus einem Character-Eingabestrom Einlesen von Unicode-Zeichen aus Character-Puffer Brücke zwischen Character- und Byte-Strömen; ASCII/Byte-Zeichen werden automatisch während des Lesens in die entsprechende Character-Repräsentation umgesetzt. Abstrakte Klasse (für FileReader). Abstrakte Basisklasse verschiedener filternder Eingabeströme. Konkrete Implementierungen werden durch die verschiedenen Subklassen zur Verfügung gestellt Unicode-Character Eingabestrom als Lese-Ende einer Character-Pipe Unicode-Character Eingabestrom, der aus einem String liest 5.3 Character–Stream-Methoden: 5.3.1 Ausgabe-Streams Generelle Methoden von java.io.Writer: void close() void flush() write(int) write(char[]) write(char[] cbuf, int off, int len) write(String) write(String str, int off, int len) 5.3.2 Schließt Ausgabestrom und gibt durch ihn belegte Systemressourcen frei Leert Ausgabestrom und schreibt alle Pufferbereiche Schreibt ein UNICODE-Zeichen Schreibt Charracter-Array Schreibt Teil der Länge len eines Character-Arrays beginnend ab Position off Schreibt String Schreibt Teil der Länge len eines Strings beginnend ab Position off Eingabe-Streams Generelle Methoden von java.io.Reader: void close() void mark(int r) boolean markSupported() int read() int read(char[] cbuf) int read(char[] cbuf, int off, intlen) boolean ready() reset() long skip(long n) Prof. Illik Schließt Ausgabestrom und gibt durch ihn belegte Systemressourcen frei Markiert gegenwärtige Position des Eingabezeigers. Ein nachfolgender Aufruf von reset versucht den Positionszeiger auf die Markierte Stelle zurückzusetzen, falls die aktuelle Position nicht weiter als readAheadLimit Zeichen entfernt liegt Gibt Auskunft darüber, ob der Eingabestrom die Positonsmarkierung, und das Rücksetzen darauf, unterstützt zur Extraktion genau eines Zeichens (16 Bit Character) zur Extraktion einer Sequenz, beginnend ab der aktuellen Stromzeigerpositon zur Extraktion einer Sequenz der Länge len oder weniger von Zeichen, und Ablage in Array beginnend ab der Position off Liefert dieser Aufruf true zurück, so liegen weitere Eingaben vor, die durch ein folgendes read gelesen werden können Versucht den Positionszeiger auf die durch die letzte gesetzte Markierung bezeichnete Stelle zurückzurücken Versucht n Zeichenpositionen zu überspringen. Die Anzahl der tatsächlich übersprungenen Positionen wird zurückgeliefert PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 39 / 210 23.03.2010 PROMOD-2a / SS10 5.4 Beispiele/Anwendungen zu Character–Streams: Beispiel 1: Anwendung Klassen FileWriter und FileReader import java.io.*; public class FileWriterTest { public static void main(String[] args) { String s = "Das ist eine Testausgabe\r\neines dreizeiligen Textes\r\n"; // Ausgabe in Datei try { FileWriter fw = new FileWriter("zzz_testFileWriter.txt"); // System.out.println(); fw.write(s); fw.close(); } catch(IOException io) { System.out.println(io.getMessage()); } // Einlesen und anzeigen // String lese = ""; StringBuffer lese = new StringBuffer(); int x = 0; } try { FileReader fr = new FileReader("zzz_testFileWriter.txt"); while((x = fr.read()) != -1) // -1 meldet EOF // lese += (char)x; lese.append((char)x); fr.close(); } catch(IOException io) { System.out.println(io.getMessage()); } System.out.println(lese); } Beispiel 2: Anwendung Klassen BufferedWriter und BufferedReader import uebungen.util.*; import java.io.*; public class BufferedWriterTest { public static void main(String[] args) { char[] cf = {'0','0','0','0','0','0','0','0','0','0'}; try { BufferedWriter bw = new BufferedWriter(new FileWriter("zzz_test.txt")); for(int i = 0; i < 750; i++) { cf[i%10]++; bw.write(cf); bw.newLine(); } bw.close(); } catch(IOException io) { System.out.println(io.getMessage()); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 40 / 210 23.03.2010 PROMOD-2a / SS10 } String s; BufferedReader br = null; try { br = new BufferedReader(new FileReader("zzz_test.txt")); try { while((s = br.readLine()) != null) { System.out.println(s); } } finally { if(br != null) br.close(); } } catch(IOException io) { System.out.println(io.getMessage()); } } Beispiel 3: Anwendung Klasse PrintWriter import java.io.*; public class PrintWriterTest { public static void main(String[] args) { try { // Ausgabe ins File PrintWriter pw= new PrintWriter(new FileWriter("zzz_test.txt"), true); // true = autoFlush // Ausgabe auf Konsole PrintWriter pw2 = new PrintWriter(System.out, true); // true = automatic flushing pw.println("Berechnung des Flächeninhalts für Kreise"); pw2.println("Berechnung des Flächeninhalts für Kreise"); for(int r = 1; r <= 100; r++) { pw.print("Radius "+r+": "); // auf PrintWriter-Objekte kann mit .print() pw2.print("Radius "+r+": "); // und .println() geschrieben werden pw.println(3.14 * r * r); // während auf andere “Schreib-“Streams mit pw2.println(3.14 * r * r); // write()-Methoden geschrieben wird } pw.close(); pw2.close(); } catch(IOException io) { System.out.println(io.getMessage()); } } } Beispiel 4: Anwendung Klasse StringWriter und StringReader import java.io.*; public class StringReaderWriterTest { public static void main(String[] args) { Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 41 / 210 23.03.2010 PROMOD-2a / SS10 String s = "Java macht Spass "; // arbeiten mit StringWriter System.out.println("Verwendung von StringWriter:"); String s1 = s; StringWriter sw = new StringWriter(); int l = s1.length(); for(int i = 0; i < l; i++) { s1 = s1.substring(0, s1.length()-1); sw.write(s1 + "\n"); } System.out.println(sw.toString()); // arbeiten mit StringReader System.out.println("Verwendung von StringReader:"); StringReader sr = new StringReader(s); int z; try { while((z = sr.read()) != -1) System.out.println((char)z); } catch(IOException _uh) { } } } Beispiel 5: Ausgabe auf Standard Out mit PrintLn import java.io.FileDescriptor; import java.io.FileWriter; import java.io.IOException; public class PrintLn { public static void main(String[] args) { FileWriter fw = null; try { fw = new FileWriter(FileDescriptor.out); for (int i=0; i < args.length; i++) fw.write (args[i]+" "); } catch (IOException ioe) { System.out.println("cannot open stdout!"); } finally { try { fw.close(); } catch (Exception e) { //ignore it } //catch } //finally } //main() } //class PrintLn Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 42 / 210 23.03.2010 PROMOD-2a / SS10 5.5 Überblick Byte-Stream-Klassen (binäres Schreiben u. Lesen) ByteStreams verwenden für die Ein- und Ausgabe von Datenströmen 1-Byte Größen. Da externe Datensenkenund Datenquellen (Dateien) byteweise arbeiten, sind grundsätzlich Bytestreams nötig, um in eine externe Datensenke zu schreiben und aus einer externen Datenquelle zu lesen. Deshalb gibt es in den CharacterStream-Klasssen auch keine Sink- und Spring-Stream-Klassen, die auf einer Datei als externe Datensenke/quelle arbeiten. OutputStream-Ableitungen: Klasse (Byte-) FileOutputStream PipedOutputStream FilterOutputStream ByteArrayOutputStream ObjectOutputStream … Was die Klasse leistet Schreibt Byte-artige Daten in Dateien oder durch Dateideskriptoren bezeichnete Ziele Hochsprachliches Äquivallent der Betriebssystem-Pipes (schreibend) Verleiht Ausgabestömen die Möglichkeit Ausgabedaten zu verändern. Einige konkrete Implementierungen sind durch die Subklassen gegeben Ausgabestrom, der in einen Byte-Array schreibt Schreibt Primitivtypen und Objekte. Ausschließlich Objekte, welche die java.ioSerializable-Schnittstelle implementieren können über diesen Mechanismus persistent geschrieben werden … InputStream-Ableitungen: Klasse (Byte-) FileInputStream PipedInputStream FilterInputStream ByteArrayInputStream StringBufferInputStream SequenceInputStream ObjectInputStream … Was die Klasse leistet Byteweises lesen einer Datei Hochsprachliches Äquivallent der Betriebssystem Pipes Gemeinsam mit anderen Eingabeströmen eingesetzt. Einsatzgebiet: Nachbearbeitung roher Eingaben. Die entsprechenden Subklassen stellen konkrete Implementierungen häufiger Einsatzfälle dar Lesen aus einem Byte Array deprecated!! Lesen aus String. Achtung: Diese Klasse geht von der (i. A. falschen) Entsprechung zwischen Bytes und Charactern aus. StringReader liefert für denselben Anwendungsfalle eine korrekte Implementierung Konkatenation von Eingebströmen; nach vollständigem Lesen des i-ten Eingebstroms wird der i+1-te angesprochen Lesen vollständig serialisierter Objekte, die die Schnittstellen java.io.Serializable oder java.io.Externalizable unterstützten … 5.6 Byte–Stream-Methoden: Generelle Methoden von java.io.OutputStream void close() void flush() void write(byte[] b) void write(byte[] b,int off,int len) void write(int) Schließt Ausgabestrom und gibt durch ihn belegte Systemressourcen frei Leert Ausgabestrom und schreibt alle Pufferbereiche Schreibt Byte-Array in Ausgabestrom Schreibt len Bytes eines Byte-Arrays ab Position off Schreibt Bytewert in Ausgabestrom Generelle Methoden von java.io.InputStream int available() Prof. Illik Liefert die Anzahl Bytes die an der Eingabeschnittstelle zur Verfügung stehen. Diese PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 43 / 210 23.03.2010 PROMOD-2a / SS10 void close() void mark(int) boolean markSupported() int read() int read(byte[]) int read(byte [] b,int off,int len) void reset() long skip(long n) Anzahl kann ohne Blockierung des Aufrufers gelesen werden Schließt Eingabestrom unter Freigabe der belegten Systemressourcen Markiert die gegenwärtige Position des Eingabezeigers im Eingabestrom; ein folgender Aufruf von reset setzt den Eingabezeiger wieder an die markierte Position zurück Gibt Auskunft darüber, ob der Eingabestrom die Positonsmarkierung, und das Rücksetzen darauf, unterstützt Ließt den nächsten Bytewert (>0 und <256) aus dem Eingabestrom. Ist das Ende des Eingabestromes erreicht, wird -1 retourniert (kein Ausnahmeereignis! Ließt eine Bytesequenz aus dem Eingabestrom, und legt sie im übergebenen Array ab. Die Anzahl der gelesenen Zeichen, bzw. -1 beim Erreichen den Eingabeendes, wird zurückgegeben Ließt Bytesequenz der Länge len und speichert sie ab Position off im übergebenen Array Setzt den Stromzeiger auf die Position der letzten vorhergehenden Markierung zurück, falls eine solche existiert Versucht den Stromzeiger um n Bytepositionen vorzurücken. Die Anzahl der tatsächlich übersprungenen Bytes wird zurückgegeben Nähere/weitere Informationen zu den vorgestellten Klassen, wie etwa Konstruktoraufbau und Methodenimplementationen lassen sich der aktuellen JAVA API-Dokumentation entnehmen. Die hauptsächliche Verwendung von Streams liegt vor allem zur Erstellen von Protokolldaten, also Logfiles, der Portierung von bspw. Datenbank-Export-Inhalten. Streams werden auch typischerweise überall dort verwendet, wo die Datenarchivierung in Datenbanken keinen Sinn macht, weil bspw. die typischen DB-Operationen keine Anwendung finden. Dies ist häufig im technischen Bereich der Fall: bspw. liegen Anweisungen für CNCMaschinen in Files vor. Beispiel 6: Ein-/Ausgabe eine Objekts (Binäre Ein-/Ausgabe) // Das Objekt bList wird in eine Datei geschrieben, // dann wird das Objekt aus der Datei geholt und // dem Objekt aList zugewiesen import import import import import import import java.io.FileInputStream; java.io.FileOutputStream; java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.io.Serializable; java.util.ArrayList; public class ObjectStreams_2 // <<-- { public static ArrayList<Person>aList= new ArrayList<Person>(); public static ArrayList<Person>bList = new ArrayList<Person>(); //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ public static void main (String [] args){ Person pers_1= new Person("Alpha","Nadine"); Person pers_2=new Person("Beta","Nanu"); Person pers_3=new Person("Gamma","Natascha"); // Objekt aList wird gefüllt aList.add(pers_3); aList.add(pers_2); aList.add(pers_1); //Ausgeben auf Stream //sichern(); sichern(aList); // Das Objekt aList ausgeben in Datei //Einlesen vom Stream / das File auslesen auslesen(); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 44 / 210 23.03.2010 PROMOD-2a / SS10 //...und ausgeben auf die Konsole // entweder for (Person buffer:bList) { // Objektinhalt auf Console ausgeben System.out.println(buffer.vorname); System.out.println(buffer.name); } // ...oder nicht ganz so kurz... /* Ob_St buffer; for (int i=0; i<3;i++) // nicht so gut wie oben { buffer = bList.get(i); System.out.println(buffer.vorname); System.out.println(buffer.name); } */ System.out.println("Ende Test..."); } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //public static void sichern() public static void sichern(ArrayList<Person> aList) { try{ FileOutputStream fos = new FileOutputStream("Datei.dat"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(aList); oos.close(); } catch(IOException e){ } // wir haben nur ein Objekt aList // und schreiben dieses Objekt aur // den Stream } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @SuppressWarnings("unchecked")//jai, nicht unbedingt notwendig; // nur Warning wird unterdrückt public static void auslesen() { try{ FileInputStream fis=new FileInputStream("Datei.dat"); //Binärfile ObjectInputStream ois= new ObjectInputStream(fis); try{ bList = (ArrayList<Person>)ois.readObject(); // cast von Object auf ArrayList<Ob_St> passt; // links und rechts gleicher Typ // Objekt wird geholt und bList zugewiesen } catch(ClassNotFoundException e){ System.exit(1); // jai } ois.close(); } catch(IOException e){ } } // end of auslesen() } // end of class ObjectStreams //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ class Person implements Serializable { String name; String vorname; Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 45 / 210 23.03.2010 PROMOD-2a / SS10 static final long serialVersionUID = 0L; public Person(String name,String vorname) { this.name=name; this.vorname=vorname; } } // end of class Person // Nicht optimal ist die Verteilung der Methoden!(Quick&Dirty Testbett // für Objekt-Ein-/Ausgabe. Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 46 / 210 23.03.2010 PROMOD-2a / SS10 6. Multi-Threading (ADRELI_THREADS++) Was Sie lernen: was Threads sind und wie sie aufgebaut sind die Eigenschaften und Methoden der Klasse Thread was unter Multi-Threading und Parallelität zu verstehen ist wie Threads miteinander kommunizieren können Voraussetzungen: Arbeiten mit Streams 6.1 Grundlagen Ein Prozess verfügt über einen eigenen Adressraum und beinhaltet ein in „Ausführung“ befindliches Programm. Ein gutes OS garantiert (mit Unterstützung durch die Hardware) die strenge Trennung der Prozessadressräume. Ein Prozess kann aus mehreren „Teilprozessen“ bestehen, die als Threads bezeichnet werden. Ein Thread ist also eine Untereinheit der OS-Verwaltungseinheit Prozeß (Task). Moderne OS sind in der Lage, mehrere Programme gleichzeitig auszuführen (Multi-Tasking / Multi-Programming / Multi-Processing / Concurrency/Concurrent Programming). Sind mehrere Threads gleichzeitig aktiv, so spricht man von Multi-Threading. Alle Threads arbeiten im selben Prozessadreßraum. Jeder Thread besitzt einen eigenen Stack. Jeder Thread bestitzt einen eigenen Programmzähler (PC, Program Counter). Jeder Thread kann weitere Threads erzeugen. Threads werden häufig auch als leichtgewichtige Prozesse bezeichnet. Threads können leicht auf gemeinsame Objekte und Variablen zugreifen. Hierbei besteht aber die Gefahr von inkonsistenten Objektzuständen. Als Lösung steht in Java die Synchronisation zur Verfügung. Allerdings können auch vollkommen voneinander getrennte Threads vorkommen, die sich keine gemeinsamen Objekte teilen. Anwendungsmöglichkeiten: Threads werden hauptsächlich angewandt um Parallelität bzw. Verteilung von Abläufen zu realisieren: o z.B. rechenintensive Algorithmen laufen in einem Hintergrund-Thread während die GUI in einem anderen Thread (im Vordergrund) läuft. o Z.B. mit einem Thread interagiert die Anwendung mit dem User („View“) mit einem anderen Thread wird die Kommunikation mit der Datenbasis („Modell“) abgewickelt. Hauptthread = „Controller“ („MVC“). Konstruktive Vorteile bietet die Zerlegung einer Gesamtapplikation in einen/mehrere Server-Thread/s und in einen/mehrere Client-Threads. Anmerkungen: Das Hauptprogramm läuft in einem Thread (Hauptthread, Steuerungsthread). Es wird erst beendet, wenn alle darin enthaltenen Threads beendet sind. Ausnahmen bilden sog. "Daemons". Dies sind Hintergrundthreads, auf die der Hauptthread nicht wartet. Wird der Hauptthread und alle User-Threads beendet, so werden dann auch die Daemons beendet, weil nichts mehr existiert, was auf das Ergebnis eines Hintergrundthreads wartet. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 47 / 210 23.03.2010 PROMOD-2a / SS10 Wird vom eingesetzten Betriebssystem die Thread-Verwaltung nicht unterstützt (kein natives Threading), so übernimmt die Virtual Machine die Threadverwaltung (Simulation): user-level-Threads mit Hilfe der POSIXThreadbibliothek. Dies ist i.a.R. bei Unix/Linux-Systemen der Fall. Threads sind prioritätenbehaftet und einem Scheduling ausgesetzt. Das Schedulingverfahren ist abhängig vom Betriebssystem und von der Thread-Implementierung (siehe unten „Thread-Implementierungen“) Thread-Implementierungen im Java-Umfeld: User-Level Threads: o Werden innerhalb eines Prozesses durch Verwendung einer Thread-Bibliothek erzeugt. o Vorteil: Erzeugung erfolgt ohne Intervention des Kernels -> höhere Performance. o Nachteil: u.U. unfaires Scheduling (weil der Prozess eine Zeitscheibe zugeordnet bekommt, unabhängig von der Anzahl der in ihm laufenden Threads). Wenn der Prozess über mehrere KernelLevel Treads verfügt, dann kann einem Prozess proportional zur Anzahl seiner Kernel-Level Threads eine größere Zeitscheibe zugeordnet werden. o Bei einem System-Call, der einen Prozeß blockiert, werden mit dem Prozeß alle User-Level-Threads blockiert. o Für User-Level Treads kann auf der Anwendungsebene ein eigener Scheduling-Algorithmus implementiert werden (was bei Kernel-Level Threads nicht geht, da sie ausschliesslich vom Kernel geschedult werden). Kernel-Level Threads: o Werden vom Kernel erzeugt (Kontetxt Switch) (siehe oben). o Vorteil: der Kernel kennt den Thread. Thread wird beim Scheduling berücksichtigt (siehe oben). o Vorteil: bei Mehrprozessorsystemen (Mehrkernsystemen) werden Kernel-Threads i.d.R. auf einzelne Prozessoren (Prozessorkerne) verteilt. Threads auf Hardwareebene: o Vorteil: Kontext-Switch sehr schnell. o Realisiert z.B. im Transputer mit Helios als Betriebssystem. o Intel Hyperthreading-Technologie: mehrere Threads können parallel von einem Prozessor ausgeführt werden. ÎJava favorisiert "kooperatives Multithreading". - d.h. der Programmierer gibt die CPU freiwillig ab. - nicht geeignet bei Real-Time-Multithreading für kritische Anwendungsprozesse, die deterministisches Scheduling verlangen Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 48 / 210 23.03.2010 PROMOD-2a / SS10 6.2 Klasse Thread und Interface Runnable Threads werden in Java durch die Klasse Thread und das Interface Runnable implementiert. In beiden Fällen wird der Thread-Body, also der parallel auszuführende Code, in Form der zu überlagernden Methode run() zur Verfügung gestellt. Diese Methode muss in der Klasse, die als Thread laufen soll überschrieben werden. Jeder Thread hat eine Priorität, welche die Reihenfolge der Abarbeitung der Threads innerhalb eines Prozesses beeinflusst. Siehe unten Kapitel „Prioritäten und Gruppen von Threads“ Beispiel 1: Erster Thread – alle berechnen Fakultäten Eine Aufgabe wird mehrfach parallel ausgeführt: public class AufrufErsterThread { public static void main(String[] args) { ErsterThread t1 = new ErsterThread(1, 12); ErsterThread t2 = new ErsterThread(2, 10); ErsterThread t3 = new ErsterThread(3, 7); t1.start(); // asynchroner Start von run()!! t2.run(); // synchroner Start t3.start(); while (t1.isAlive() || t2.isAlive() || t3.isAlive()) { System.out.println("Hauptprogramm ist aktiv"); } } } class ErsterThread extends Thread { int thread_nr, a; long fak = 1; // Daten für Threads sichtbar ErsterThread(int nummer, int fakultaet) { thread_nr = nummer; a = fakultaet; } } public synchronized void run() { for (int x = 1; x <= a; x++) { fak *= x; System.out.println("Thread " + thread_nr + ": " + x + "! = " + fak ); Thread.currentThread().yield(); // “unterbricht” den aktuellen Thread } // Siehe unten “Zustände von Threads” } // -> kooperatives Multithreading Hinweis: Wenn Objekte instanziiert werden, dann sind die Instanzvariablen jeweils objektspezifisch – nicht aber der Code! Der Code für die Methode run() ist zunächst nur einmal existent/im Hauptspeicher: Wird die Methode run() traditionell gestartet, so reicht dieses einmalige Vorhandensein für die sequentielle Abarbeitung der Methoden im Hauptthread. Dagegen sorgt der Start mit start() für das Einrichten eines eigenen Threads mit jeweils eigenem Programm Counter PC und eigenem Stack. Das Schlüsselwort „synchronized“ sorgt dafür, dass nur jeweils ein Thread durch seinen Code läuft und auf die gemeinsamen Daten zugreift. Siehe auch Kapitel „Synchronisation“ weiter unten. Nach dem Start der Threads mit der Methode start() laufen die Threads asynchron. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 49 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel 2 – ThreadDemo: Threads erledigen unterschiedliche Aufgaben: class MyThread1 extends Thread { public void run() { int i = 0; while (i <= 50) { System.out.println(i++); } } } class MyThread2 extends Thread { public void run() { int i = 0; while (i <= 50) { System.out.println("zweiter Thread"); i++; } } } public class ThreadDemo { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2(); t1.start(); t2.start(); } } Beispiel 3 – Thread mit sleep(): Hinweis: Experimentieren Sie mit unterschiedlichen sleep()-Zeiten. class MyThread1_ extends Thread { public void run() { int i = 0; try { while (i <= 50) { System.out.println(i++); this.sleep(100); // kooperatives Multithreading } } catch (InterruptedException ie) { System.out.println(ie.getMessage()); } } } class MyThread2_ extends Thread { public void run() Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 50 / 210 23.03.2010 PROMOD-2a / SS10 { int i = 0; try { while (i <= 50) { System.out.println("zweiter Thread"); this.sleep(200); i++; } } catch (InterruptedException ie) { System.out.println(ie.getMessage()); } } } public class ThreadSleep { public static void main(String[] args) throws InterruptedException { MyThread1_ t1 = new MyThread1_(); MyThread2_ t2 = new MyThread2_(); t1.start(); t2.start(); } } Beispiel 4 – Runnable: Es gibt noch eine weitere Möglichkeit Threads zu erschaffen, wie wir bereits wissen. Anstatt von der Klasse "Thread" zu erben kann man auch das Interface Runnable implementieren. Dies ist immer dann sinnvoll (und notwendig), wenn eine Klasse bereits von einer anderen erbt - und somit nicht mehr von der Klasse Thread erben kann (Java unterstützt ja keine Mehrfachvererbung). Das Interface Runnable enthält nur eine einzige Methoden-Deklaration, nämlich die der Methode run(). // class Bzzz extends Parent implements Runnable // Bsp. wäre realistischer, // aber länger class Bzzz implements Runnable { public void run() { int i = 0; while (i<100) { System.out.println(i++); } } } public class ThreadRunnable { public static void main(String args[]) { Bzzz b = new Bzzz(); // 1. Schritt: Objekt einer Klasse, die Runnable // implementiert Thread t = new Thread(b);// 2. Schritt: Objekt der o.g. Klasse wird dem // Konstruktor der Klasse Thread als Parameter // übergeben. („“Copy Initializer“) t.start(); // 3. Schritt: Starten des Threads while(t.isAlive()){} // Hauptthread ist “busy waiting” System.out.println("Thread ist beendet"); System.out.println("Bitte warten Sie noch 8 Sekunden..."); try { Thread.sleep(8000); } catch (InterruptedException e){} System.out.println("Danke..."); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 51 / 210 23.03.2010 PROMOD-2a / SS10 } Anmerkung zum Threading/zur Parallelisierung: Weiter oben haben wir schon gesehen, wenn Threads durch die Methode start() aktiviert werden, dann haben wir es zunächst mit einer asynchronen Parallelität zu tun. Das heißt, die Threads laufen parallel zueinander ab, ohne dass sie per se voneinander abhängig sind – soweit so gut. Dies bedeuted konkret aber auch, dass selbst inhaltlich gleichartige Threads sich u. U. in ihrem zeitlichen Ablaufverhalten unterscheiden. Warum? Antwort: Bedenken Sie, das in multithreadingfähigen Umgebungen, wie z.B. den Betriebssystem Windows (XP, Vista oder Windows 7) oder Linux, der Gesamtmaschinenzustand sich während des Ablaufs eines Thread beträchtlich unterscheiden kann vom Zustand während des Ablaufs eines anderen Threads. Auf meinem persönlichen Laptop laufen beispielsweise über 100 Prozesse mit über 1000 Threads (siehe „ProcessExplorer“ -> „View“ -> „System Information“; oder WindowsTaskmanager -> Prozesse -> Ansicht -> Treads). Je nachdem, was diese konkurrierenden Prozesse und Threads im Augenblick machen, beeinflusst dies auch das Zeitverhalten der Threads aus den Beispielprogrammen. Asynchron/Synchron: In vielen (Anwendungs-)Fällen ist die asynchrone Prallelität ausreichend: Threads werden gestarted (mit der Methode start()) und tun dann ihre Arbeit, bis sie terminieren oder zum terminieren aufgefordert werden (z.B. mit der Methode interrupt()). In anderen Anwendungfällen reicht diese asynchrone Parallelität nicht aus: die Threads müssen in einer aufeinander abgestimmten Art und Weise zusammenarbeiten. Dazu gehört, dass sie sich zeitlich oder über Ereignisse oder auch über einen Datenaustausch miteinander „synchronisieren“. Die „zeitgesteuerte Synchronisation“ kann z.B. mit der static-void-Methode sleep() bewerkstelligt werden. Die „ereignisgesteuerte Synchronisation“ kann z.B. über die Methoden void wait(), void notify(), join(), static void yield(), void interrupt() erfolgen Die „kommunikationsgesteuerte Synchronisation“ kann z.B. über einen Datenaustausch via Pipes und Sockets erfolgen. Pipes sind unter Umständen selbstsynchronisierend, was sich auch synchronisierend auf ihre Nutzer auswirkt. Mehr dazu siehe Kapitel „Datenaustausch zwischen Threads mit Pipes“: (siehe PipeAPI-Doku) “Whether or not a thread writing bytes to a pipe will block until another thread reads those bytes, or some previously-written bytes, from the pipe is system-dependent and therefore unspecified. Many pipe implementations will buffer up to a certain number of bytes between the sink and source channels, but such buffering should not be assumed.” Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 52 / 210 23.03.2010 PROMOD-2a / SS10 6.3 Zustände von Threads: Wie Prozesse haben auch Threads verschiedene Zustände, die sie einnehmen. Diese Zustände können durch Methoden der Klasse Thread hervorgerufen, bzw. geändert werden. Das folgende Bild gibt einen Überblick: Prozessorzuteilung durch Scheduling Ende von run() Oder: freiwilliges Beenden, wenn Interrupted-Flag gesetzt. start() new ready to run active running end yield() ERZEUGEN BEENDEN - notify() notifyAll() blocked Ressourcen fehlen (i/o) sleep() wait() join() LAUFEN (isAlive() = true) 6.3.1 Threads unterbrechen Why are Thread.stop(), Thread.suspend() and Thread.resume() deprecated? (Siehe API-Doku für Klassse Thread) Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked. (The monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any of the objects previously protected by these monitors were in an inconsistent state, other threads may now view these objects in an inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills threads silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days in the future. What should I use instead of Thread.stop()? Most uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. (This is the approach that JavaSoft's Tutorial has always recommended.) To ensure prompt communication of the stop-request, the variable must be volatile (or access to the variable must be synchronized). Dies bedeutet, dass ein Thread nur mit der unten vorgestellten Methode interrupt() zur Beendigung aufgefordert werden kann. Die Methode interrupt() setzt eine Interrupted-Flag. In der Methode run() wird am besten zyklisch (in einer Schleife) abgefragt, ob das Interrupted Flag gesetzt ist und damit der Thread terminieren soll. (Passt zur Philosopie „kooperatives Multithreading“.) 6.3.2 Die Methoden yield(), sleep() und interrupt() HINWEIS: Werden Ihre Threads nicht quasi-parallel abgearbeitet, sondern nacheinander, so basiert Ihre JavaImplementierung möglicherweise nicht auf einem Time-Slice-Scheduler (Zeitscheibenverfahren). Dieses Problem kann durch kooperatives Multitaskting gelöst werden: Threads müssen angewiesen werden, ihre Ausführung nach einer gewissen Zeit zu unterbrechen („sich kooperativ zu verhalten“). Dazu muss in einer Schleife eine yield() oder sleep()-Anweisung eingebaut werden. (Siehe hierzu auch das sleep()-Beispiel weiter oben.) sleep() und yield() sind static-Funktionen. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 53 / 210 23.03.2010 PROMOD-2a / SS10 static void yield() Der Thread wird zeitweilig unterbrochen (in den Zustand „ready-to-run“ versetzt bis ihn der Scheduler wieder aktiviert). Ein anderer Thread bekommt Prozessorzeit zugeteilt.(Siehe Beispiel 1 – Erster Thread) public static void sleep(long millis) throws InterruptedException public static void sleep(long millis, int nanos) throws InterruptedException Der Thread wird für die angegebene Zeit unterbrochen. Während der Ausführung der sleep()-Methode kann eine Exception vom Typ InterruptedException auftreten, die in einem try-catch-Block abgefangen werden muss. try { sleep(millis); } catch(InterruptedException ie) Auch die main()-Methode kann sleep() verwenden, um die Programmausführung zu unterbrechen. Dazu ist es nötig, dass die Klasse der main()-Methode von Tread erbt (Implementierung vom Interface Runnable reicht nicht). public void interrupt() Setzt das interrupted-Flag eines Threads und fordert damit den Thread auf sich zu beenden. (Versetzte sich ein Thread durch die Methoden sleep(), join() und wait() in den Zustand „blocked“, so kann dieser Thread mit der Methode interupt() vorzeitig wieder geweckt werden; er wird dann in den Zustand „ready-to-run“ versetzt. Damit werden also die blockierenden Methoden wie join() und sleep() beendet und die Methode run() macht mit dem catch-Konstrukt InterruptedException hinter der blockierenden Methode weiter.) public static boolean isInterrupted() Liefert den Wert des interrupted-Flags (verändert dieses nicht, im Gegensatz zur Methode interrupted()) public static boolean interrupted() Tests whether the current thread has been interrupted. The interrupted status of the thread is cleared by this method. In other words, if this method were to be called twice in succession, the second call would return false (unless the current thread were interrupted again, after the first call had cleared its interrupted status and before the second call had examined it) 6.3.3 Die Methode join() public final void join() throws InterruptedException public final void join(long millis) throws InterruptedException public final void join(long millis, int nanos) throws InterruptedException Die parameterlose join()-Methode wartet bis der angegebene Thread beendet ist. Ein Thread kann die Methode join() eines anderen Threads aufrufen. Dadurch wird der Thread, der die Methode aufruft, in den Zustand „blocked“ versetzt, bis der Thread, dessen join()-Methode aufgerufen wird, beendet ist. o Wird die Methode join() eines bereits beendeten Threads aufgerufen, so wird der aufrufende Thread nicht in den Zustand „blocked“ versetzt. Werden ein oder zwei Parameter angegeben, so wartet der aufrufende Thread höchstens die angegebene Zeit, bis er die Abarbeitung fortsetzt. Die Methode join() muss gegen eine Exception vom Typ InterruptedException abgesichert werden. (-> Aufruf von interrupt() für den in join() wartenden Thread.) 6.3.4 Weitere Thread-Methode (ggf. geerbt von Object) void wait() void wait(long millis, int nanos) Bewirkt, dass ein Thread wartet, bis entweder eine Benachrichtigung (notify()) eintrifft oder die angegebene Zeit abgelaufen ist, oder die Methode interrupt() für den Thread aufgerufen wird. HINWEIS: Diese Methode lässt sich nur aus einer synchronisierten Methode heraus aufrufen. Die Methode löst eine IllegalMonitorStateException aus, wenn der aktuelle Thread nicht der Besitzer der Objektsperre ist. wait() ist von der Klasse Object geerbt. void notify() (See: Case-Study threads_sync/synchronisation.java) Aktiviert einen Thread, welcher duch den Aufruf von wait() in den Wartezustand versetzt wurde. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 54 / 210 23.03.2010 PROMOD-2a / SS10 static Thread currentThread() Gibt eine Referenz auf den aktuell ausgeführten Thread zurück. void destroy() Zerstört den Thread ohne Aufräumarbeiten durchzurühren („Any monitors it has locked remain locked.“). string getName() Gibt den Namen des Threads zurück. void setName(String name) Umbenennung eines Threads: verändert den Thread-Namen in den als Parameter übergebenen Namen. boolean isAlive() Gibt true zurück, wenn der Thread läuft. 6.3.5 Thread-Konstruktoren zu Details siehe API-Beschreibung “Thread“. Constructor Summary Thread() Allocates a new Thread object. Thread(Runnable target) hiervon wird die run()-Methode aufgerufen Allocates a new Thread object. Thread(Runnable target, String name) Allocates a new Thread object. Thread(String name) Allocates a new Thread object. Thread(ThreadGroup group, Runnable target) Allocates a new Thread object. Thread(ThreadGroup group, Runnable target, String name) Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group. Thread(ThreadGroup group, Runnable target, String name, long stackSize) Allocates a new Thread object so that it has target as its run object, has the specified name as its name, belongs to the thread group referred to by group, and has the specified stack size. Thread(ThreadGroup group, String name) Allocates a new Thread object. Beispiel 5 – TestTimer: Case-Study-Aufgabe: Finden Sie raus, was Beispiel 5 macht! Siehe hierzu auch case_study_06.txt. public class TestTimer implements TimerListener { private Timer t1; private Timer t2; Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 55 / 210 23.03.2010 PROMOD-2a / SS10 int i = 1; public static void main(String[] args) // ----------------------------------{ TestTimer tt = new TestTimer(100); } public TestTimer(int t) // -------------------------------------------------{ t1 = new Timer(1, this); t2 = new Timer(2, this); t1.setDelay(t); t2.setDelay(t); // t1.setDaemon(true); // Siehe unten Kapitel Daemon-Threads // t2.setDaemon(true); t1.start(); t2.start(); System.out.println("2 Timer-Threads gestartet"); } public void timeBreak(Timer t) // -----------------------------------------{ if (i <= 20) { System.out.println("Timer-Thread "+t.getTimerNr()+" TimeBreak "+i++); } else { System.out.println("Timer-Thread "+t.getTimerNr()+" beendet"); t.stopThread(); } } } public class Timer extends Thread { TimerListener tlistener; int millis; int nr; boolean laeuft = true; Timer(int n, TimerListener tl) // --------------------------------------------{ tlistener = tl; nr = n; } public void run() // ---------------------------------------------------------{ while(laeuft) { try { sleep(millis); tlistener.timeBreak(this); } catch(InterruptedException ie) { } } } public void setDelay(int m) // -----------------------------------------------{ millis = m; } public int getTimerNr() // ---------------------------------------------------- Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 56 / 210 23.03.2010 PROMOD-2a / SS10 { return nr; } public boolean stopThread() // -----------------------------------------------{ return laeuft = false; } } interface TimerListener { public void timeBreak(Timer t); } 6.4 Daemon-Threads Es gibt zwei Arten von Threads: nämlich User-Threads und Daemon-Threads (typw. „Dienstleister“). Der Unterschied zwischen den beiden Thread-Arten besteht darin, dass Daemen-Threads im „Hintergrund“ laufen (d.h. u.a. keine Verbindung zur Standard-E/A haben), sich der Programmierer nicht darum kümmern muss, ob ein Daemon-Thread ordnungsgemäß beendet wird, da Daemon-Threads automatisch gestoppt werden, wenn alle anderen User-Threads eines Prozesses beendet sind. (Weil dann keine Clients mehr existieren, welche die Dienstleistungen der Daemons in Anspruch nehmen könnten. VM wird runtergefahren.) Standardmäßig laufen alle erzeugten Threads als User-Threads. Aus einem User-Thread wird ein Daemon-Thread durch den Aufruf der Methode setDaemon(true). Dieser Aufruf muss VOR dem Aufruf der start()-Methode für den entsprechenden Thread stehen. void setDaemon(boolean on) Kennzeichnet einen Thread als einen User-Thread (on == false) oder als einen Daemon-Thread (on == true). boolean isDaemon() Gibt true zurücke, wenn aufrufender Thread ein Daemon ist; andernfalls false. Beispiel – siehe oben Beispiel 5 TestTimer Lassen Sie im Beispiel 5 die beiden Timer-Threads als Deamons laufen. Was beobachten Sie? 6.5 Prioritäten von Threads: Jeder Java-Thread erbt seine Priorität von seinem übergeordneten Thread. Die Klasse Thread hat die Standardpriorität 5, die in der statischen Variable NORM_PRIORITY festgelegt ist. Die höchstmögliche Priorität ist MAX_PRIORITY: 10. Die niedrigste Priorität ist MIN_PRIORITY: 1. Die Zuteilung von Rechenzeit orientiert sich an Prioritäten. Der Thread mit der momentan höchsten Priorität läuft solange: - bis die Methode run() beendet ist - bis die Methode yield() (freiwillige Prozessoraufgabe) gerufen wird - bis ein Thread höherer Priorität gestartet wird - weiteres ist unbestimmt (insbesondere keine Aussage über eine Preemption…) (siehe auch „ThreadImplementierung im Java-Umfeld“ weiter oben“) Î Thread-Prioritäten sind in hohem Maße systemabhängig. Ein OS kann mehr oder weniger Prioritätsebenen haben. Die VM regelt, wie die Java-Prioritäten auf OS-Prioritätsebenen abgebildet werden. (WIE??) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 57 / 210 23.03.2010 PROMOD-2a / SS10 void setPriority(int newPriority) Ändert die Priorität auf den übergebenen Wert. int getPriority() Gibt die Priorität des Threads zurück. Wegen der oben erwähnten Systemabhängigkeit ist die Vergabe von Prioritäten mit Vorsicht anzuwenden. Soll die Abarbeitung von Threads exakt beeinflusst werden, ist es i.d.R. notwendig, einen eigenen Thread-Handler zu programmieren (für User-Level-Threads). 6.6 Gruppen von Threads Die Klasse ThreadGroup gestattet es Threads mit ähnlichen, zusammengehörenden Aufgaben wahlfrei zu gruppieren und zu strukturieren. Damit lassen sich threadspezifische Operationen gesammelt auf einer Gruppe von Threads ausführen. Bei der Erzeugung von Thread-Gruppen wird wie folgt vorgegangen: Erzeugen Sie ein Objekt der Klasse ThreadGroup. Verwenden Sie für die Erzeugung von Thread-Objekten einen Konstruktor, bei dem Sie als ersten Parameter die Thread-Gruppe übergeben, z.B.: o Thread(ThreadGroup group, String name) oder o Thread(ThreadGroup group, Runnable target) Über die Methoden der Klasse ThreadGroup können Sie nun alle Threads der Gruppe erreichen, z.B. unterbricht (genau genommen Unterbrechungsaufforderung) die folgende Anweisung alle Threads der Gruppe group: group.interrupt(); Beispiel – siehe unten Beispiel Thread-Kommunikation via Pipe 6.7 Synchronisation: Führen mehrere Threads Änderungen auf gemeinsamen Daten (Nutzung gemeinsamer Ressourcen) durch, so müssen sie synchronisiert werden, denn andernfalls können undefinierte Ergebnisse entstehen. Beispiel – mehrere Threads ohne Synchronisation: public class NoSynchronisation extends Thread { static int cnt = 0; // für die Threads sichtbar public void run() { while(cnt<100) { int i = cnt; // Kritischer Abschnitt Beginn try{ sleep(10);} // Simuliert zeitaufwendige Berechnungen catch(InterruptedException e){} i++; cnt=i; // Kritischer Abschnitt Ende System.out.println(i); } } public static void main(String[] args) { Thread t1 = new NoSynchronisation(); Thread t2 = new NoSynchronisation(); t1.start(); t2.start(); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 58 / 210 23.03.2010 PROMOD-2a / SS10 Die Ausgabe dieses Beispiels sollte eigentlich die Zahlen 1 bis 100 ausgeben, doch tatsächlich werden die Zahlen teils doppelt und teils auch in falscher Reihenfolge ausgegeben. Dies kommt dadurch zustande, weil beide Threads auf die static-Variable cnt zugreifen. Hinweis: Möglicherweise entdecken Sie auf Ihrem System keine der Anomalien. An welcher „Schraube“ in der run()-Methode müssen Sie drehen, um die Anomalie sichtbar zu machen? Lösung: Zur Synchronisation von Threads hat Java das Konzept des Monitors implementiert. Ein Monitor ist die Kapselung eines kritischen Bereichs (also eines Programmteils, der nur jeweils ausschliesslich von einem Thread durchlaufen werden darf) mit Hilfe einer automatisch verwalteten Sperre. Diese Sperre wird beim Betreten des Monitors gesetzt und beim Verlassen wieder zurückgenommen. Ist sie beim Eintritt in den Monitor bereits von einem anderen Thread gesetzt, so muß der aktuelle Thread warten, bis der Konkurrent die Sperre freigegeben und den Monitor verlassen hat. Das Monitor-Konzept wird mit Hilfe des in die Sprache integrierten Schlüsselworts synchronized realisiert. Durch synchronized kann entweder eine komplette Methode oder ein Block ({}) innerhalb einer Methode geschützt werden. Beispiel – Synchronisation des Zugriffs auf gemeinsame Ressourcen: In der unten gezeigten Lager-Klasse sind die Attribute bestand, zugang und abgang kritisch. Um ein Lagerobjekt konsistent zu halten, darf der Zugang zu diesen Daten (z.B. für eine weitere Write-/Read-Transaktion) erst dann freigegeben werden, wenn der komplette Datensatz (bestand, zugang und abgang) insgesamt aktualisiert wurde. public class Lager { private int artikelnr; private int bestand = 0; private int zugang = 0; private int abgang = 0; private int aendeungsnr = 0; public Lager(int anr) { artikelnr = anr; } public int getBestand() { return bestand; } public int getZugang() { return zugang; } public int getAbgang() { return abgang; } public int getAendeungsNr() { return aendeungsnr; } public synchronized void aenderungBestand(int x) { bestand+=x; aendeungsnr++; if (x > 0) zugang += x; else abgang -= x; } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 59 / 210 23.03.2010 PROMOD-2a / SS10 public int getNr() { return artikelnr; } } Übung: Siehe Übung zu diesem Kapitel; dort gilt es dieses Beipiel lauffähig zu machen. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 60 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Synchronisation zusätzlich mit wait() und notify(): import java.util.Vector1; class Cnt { static Vector cnt = new Vector(); } class Thread1 extends Thread { private int i=0; public void run() { while (true) { synchronized(Cnt.cnt) { Cnt.cnt.set(0,new Integer(i++)); try{Cnt.cnt.notify();} catch(IllegalMonitorStateException e){} } try {sleep(100);} catch(InterruptedException e) {} } } } class Thread2 extends Thread { public void run() { while (true) { synchronized(Cnt.cnt) { try{Cnt.cnt.wait();} catch (InterruptedException e) {} } System.out.println(Cnt.cnt.get(0)); } try{sleep(50);} catch (InterruptedException e) {} } } public class Synchronisation { public static void main(String[] args) { Cnt.cnt.add(new Integer(0)); Thread1 t1 = new Thread1(); Thread2 t2 = new Thread2(); t2.start(); t1.start(); } } 1 Zur Erinnerung an „Algorithmen & Datenstrukturen“ / Sie sollten folgende Collection-Typen kennen: Listen -> Vector, Stack, ArrayList, LinkedList Sets -> Collections ohne Duplikate: HashSet, TreeSet, Maps -> Schlüssel-Value-Paare: HashMap, TreeMap, Die Klasse Vector und ArrayList sind praktisch identisch bis auf den Unterschied, dass die Methoden der Klasse Vector synchronisiert sind. Siehe API-Doku! Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 61 / 210 23.03.2010 PROMOD-2a / SS10 Hands On: Untersuchen und erläutern Sie das Beispiel „Synchronisation2“ im Hands-OnTeil. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 62 / 210 23.03.2010 PROMOD-2a / SS10 6.8 Datenaustausch zwischen Threads mit Pipes: gemeinsamer Adressraum (gemeinsame Objekte, Datenfelder) Für die Realisierung von Pipes gibt es jeweils eigene Stream-Klassen für die byte- und character-orientierte Kommunikation Pipes sind stets Einweg-Kanäle: o unidirektionales Kommunikationsmittel Peer to Peer “read side” “write side” Client-Server Selbstsynchronisierend je nach Basis-OS! haben bestimmte Kapazität (häufig Vielfaches von 2KB, OS-abhängig) Produzent wird angehalten, wenn die Pipe voll ist Konsument wird angehalten, wenn die Pipe leer ist o FIFO-Prinzip o keine Seek-Operation - Wird ein Thread beendet, welcher liest und/oder schreibt, so erhält der Partnerthread die Exception "broken pipe" - Muss durch IOException abgesichert werden o - Siehe: Pipe-API-Doku: Whether or not a thread writing bytes to a pipe will block until another thread reads those bytes, or some previously-written bytes, from the pipe is system-dependent and therefore unspecified. Many pipe implementations will buffer up to a certain number of bytes between the sink and source channels, but such buffering should not be assumed. Zwei Klassen im Package java.io: Eingabestrom PipedInputStream PipedReader Ausgabestrom PipedOutputStream PipedWriter Stream-Typ ByteStream (binäre Kommunikation) CharacterStream (UNICODE; TextKommunikation) In die Pipes, bzw von den Pipes warden jeweils Einzelbyte/Einzelcharacter oder positionierte und dimensionierte Pufferinhalte geschrieben oder gelesen. Siehe z.B. API-Doku PipedWriter: void write(char[] cbuf, int off, int len) Writes len characters from the specified character array starting at offset off to this piped output stream. void write(int c) Writes the specified char to the piped output stream. Die Erbschaft: (Siehe auch oben Kapitel “Streams”) Object OutputStream InputStream PipedOutputStream Reader PipedWriter PipedInputStream Prof. Illik Writer PipedReader PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 63 / 210 23.03.2010 PROMOD-2a / SS10 Das Methoden-Angebot: Das Methoden-Angebot für die Pipes ist vergleichbar mit den Methoden in den Basisklassen: Für die lesenden Klassen: available(), close(), connect(), read(), receive(), ready() Für die schreibenden Klassen: close(), connect(), flush(), write() Details siehe API-Dokumentation. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 64 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Thread-Kommunikation via Pipe: import java.io.*; public class TestPipe { public static void main(String[] args) { try { // 1. Step: Prepare infrastructur to communicate PipedReader pr1 = new PipedReader(); // Character-Streams, UNICODE PipedWriter pw1 = new PipedWriter(); pw1.connect(pr1); // verbindet PipedWriter und PipedReader // 2. Step Instantiate Communicators and start() them ThreadGroup tg = new ThreadGroup("TG"); Produzent produzent = new Produzent (tg, pw1); // Schreibseite als Para Konsument verbraucher = new Konsument (tg,pr1); // Leseseite als Para produzent.start(); verbraucher.start(); Thread.sleep(20000); tg.interrupt(); // Operation auf der Thread-Gruppe } catch(InterruptedException ie){} catch(IOException io) { System.out.println("Fehler beim Verbinden der Pipe"); } } } class Produzent extends Thread { PipedWriter pw; int x = 0; public Produzent (ThreadGroup group, PipedWriter pw) { super(group, "Produzent"); this.pw = pw; } } public void run() { while(isInterrupted() == false) { // int x = (int)(100 * Math.random()); x++; try { pw.write(x); System.out.println(this.getName() + "\t" + "produziert: " + x); } catch(IOException io) { System.out.println("Produzent: Fehler beim Schreiben"); } } } class Konsument extends Thread { PipedReader pr; public Konsument (ThreadGroup group, PipedReader pr) { super(group, "Konsument"); this.pr = pr; Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 65 / 210 23.03.2010 PROMOD-2a / SS10 } public void run() { while(isInterrupted() == false) { try { int x = pr.read(); // liefert -1 (= end-of-stream), wenn Produent Pipe closed. System.out.println(this.getName() + "\t" + "entnimmt: " + x); } catch(IOException io) { System.out.println("Konsument: Fehler beim Lesen"); } } } } Hands On: • Untersuchen Sie, was passiert, wenn der Produzent kontinuierlich auf die Pipe schreibt, ohne dass ein Konsument aktiv ist (bzw. sich schon beendet hat). • Untersuchen Sie, was passiert, wenn der Produzent kontinuierlich „rasch“ auf die Pipe schreibt, aber der Konsument nur gelegentlich/“langsam“ von der Pipe liest. • Untersuchen Sie, was passiert, wenn der Konsument von einer Pipe lesen möchte, ohne dass ein Produzent aktiv ist (oder sich schon beendet hat). • Untersuchen Sie, was passiert, wenn der Konsument kontinuierlich/„schnell“ von der Pipe liest, der Produzent aber nur „gelegentlich“ etwas auf die Pipe schreibt. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 66 / 210 23.03.2010 PROMOD-2a / SS10 6.9 Thread-Synchronisation mit CyclicBarrier Siehe: Class CyclicBarrier http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/CyclicBarrier.html “A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released” Siehe auch Beispiel: http://programmingexamples.wikidot.com/cyclicbarrier A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. The barrier is called cyclic because it can be re-used after the waiting threads are released.[1] An example taken from [2] follows: import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierExample { private static int matrix[][] = { { 1 }, { 2, 2 }, { 3, 3, 3 }, { 4, 4, 4, 4 }, { 5, 5, 5, 5, 5 } }; private static int results[]; private static class Summer extends Thread { int row; CyclicBarrier barrier; Summer(CyclicBarrier barrier, int row) { this.barrier = barrier; this.row = row; } public void run() { int columns = matrix[row].length; int sum = 0; for (int i = 0; i < columns; i++) { sum += matrix[row][i]; } results[row] = sum; System.out.println("Results for row " + row + " are : " + sum); // wait for others try { barrier.await(); } catch (InterruptedException ex) { ex.printStackTrace(); } catch (BrokenBarrierException ex) { ex.printStackTrace(); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 67 / 210 23.03.2010 PROMOD-2a / SS10 } } public static void main(String args[]) { final int rows = matrix.length; results = new int[rows]; Runnable merger = new Runnable() { public void run() { int sum = 0; for (int i = 0; i < rows; i++) { sum += results[i]; } System.out.println("Results are: " + sum); } }; /* * public CyclicBarrier(int parties,Runnable barrierAction) * Creates a new CyclicBarrier that will trip when the given number * of parties (threads) are waiting upon it, and which will execute * the merger task when the barrier is tripped, performed * by the last thread entering the barrier. */ CyclicBarrier barrier = new CyclicBarrier(rows, merger); for (int i = 0; i < rows; i++) { new Summer(barrier, i).start(); } System.out.println("Waiting..."); } } Siehe auch „Getting to know Synchronizers”: http://java.sun.com/developer/JDCTechTips/2005/tt0216.html#1 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 68 / 210 23.03.2010 PROMOD-2a / SS10 6.10 Zusammenfassung Threads: Aus der API-Beschreibung entnommen: public class Thread extends Object implements Runnable A thread is a thread of execution in a program. The Java Virtual Machine allows an application to have multiple threads of execution running concurrently. Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority. Each thread may or may not also be marked as a daemon. When code running in some thread creates a new Thread object, the new thread has its priority initially set equal to the priority of the creating thread, and is a daemon thread if and only if the creating thread is a daemon. When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main() of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs: The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place. All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method. There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. 6.11 Zusammenfassung Pipes: Pipes sind ein streamorientiertes Kommunikationsmedium für die Inter-Thread-Kommunikation. Pipes können nur zwischen lokalen Thread verwendet werden. Pipes sind für eine methodenorientiert Zusammenarbeit zwischen Threads nicht so gut geeignet. Vorausschau: für die netzwerkweite Inter-Thread-Kommunikation (Programm-Kommunikation) sind die Sockets geeignet und RMI (und CORBA) und noch einiges mehr (Web-Services, …). Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 69 / 210 23.03.2010 PROMOD-2a / SS10 7. Exkurs: how to model an application? Oder anders gefragt: How to manage the project? Warum steht der Exkurs an dieser Stelle? Hätte man die „Theorie“ nicht vorher bringen können? Nun, das wäre sicher möglich gewesen – wäre dann allerdings eben Theorie geblieben. Nun ist die Situation die, dass Sie aus unserm Projektfahrplan (siehe unten) das ADRELI_CON Projekt und das Projekt ADRELI_THREADS++ bereits fertig haben. Durch die Präsentation der Projekte haben Sie auch konkret miterlebt, dass es teilweise ganz unterschiedliche Ansätze für die Umsetzung der Spezifikation gab. Manche Projektteams versuchten gleich von Anfang an ein MVCModell zu implementieren, andere haben eine monolithische Architektur bevorzugt – Hauptsache „es läuft“. Unterschiede gab es auch im Bereich der I/O, des Threadings, der Pipe-Kommunikation, usw.. Um das Verständnis für die Modellierungsproblematik zu sensibilisieren, sollen in diesem Kapitel einige Überlegungen diskutiert werden. ADRELI_CON ADRELI_THREADS++ ADRELI_NETCOM ADRELI_GUI ADRELI_JDBC Die ADRELI-Projekte Die Projektsequenz ermöglicht es, dass bestimmte Aspkete des Vorgehensmodells von Barry W. Boehm („Spiralmodell“, 1988) „erlebt“ werden können. Das Spiralmodell hat andere Vorgehensmodelle beinflußt, siehe dazu beispielsweise auch „Evolutionäre Softwareentwicklung“ und „Extreme Programming“. Mit ein zentraler Punkt ist das „divide et impera“: „teile und herrsche“. (Zur Erinnerung: dieses Prinzip haben wir auch in PROMOD-1 für die Entwicklung von Algorithmen – im Zusammenhang mit der „Funktionalen Abstraktion“ - diskutiert.) Die (verborgene) Gesamtaufgabenstellung von PROMOD2 ist die Entwicklung einer 1. Datenbank-Applikation die 2. im Netz verteilt ist (Client-Sever, Web-Service, …) 3. mulituser-fähig2. 4. skalierbar3 und 5. ausfallsicher4 ist. Diese Gesamtaufgabenstellung kann durch einen Gesamt-Entwurf gelöst werden. Die Konsequenz: hoher Designaufwand und („Design for the future“) lange Wartezeit bis zu einem testbaren ersten (Zwischen-)ergebnis. Auf der Managementseite: o schwer administrierbar, o damit auch hohes Risiko (möglicherweise Abbruch, hohe Korrektur-/Änderungskosten; 10x10x10Regel!). Die Projektsequenz (eine „Drehung“ in der Boehm’schen Spirale / bzw. ein „Sprint“ im Sinne von SCRUM) konzentierte sich auf jeweils dedizierte Probleme – ausgehend 1. von der (benutzergetriebenen) Applikationsfunktionalität (Datenmodell & Funktionalität) (Siehe oben Punkt 1) 2. hin zu fortgeschrittenen technischen Fähigkeiten der Applikation (Siehe oben die Punkte 2 bis 5) In der Retrospektive sollten Sie feststellen, dass Sie sich zuerst mit dem Applikationskern (dem Datenmodell5 und der Funktionalität) vertraut und sicher gemacht haben. Vorteil: der Applikationskern ist testbar, für die Weiterentwicklung stabilisiert und er ist auch vorführbar. 2 Nicht wirklich, da wir uns z.B. um keinerlei Rechteverwaltung kümmern. Am Ende ist der Server multi-threaded: um jeden Client kümmert sich ein dedizierter Thread 4 Zum Beispiel durch Einsatz eines clusterfähigen DB-Servers (z.B. MySQL [MySQL Cluster integrates the standard 3 MySQL server with an in-memory clustered storage engine called NDB. Siehe: http://dev.mysql.com/doc/refman/5.0/en/mysqlcluster-overview.html ]) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 70 / 210 23.03.2010 PROMOD-2a / SS10 Danach wurden teilweise – für den Benutzer nicht sichtbar – weitere technisch Eigenschaften implantiert (Threading, Pipes, Sockets, multi-threaded Server), die in Summe den Gesamt-Entwurf umgesetzt haben. Vorteil: wie oben. Jede „Spiraldrehung“/“Sprint“ ist testbar, stabilisiert sich und ist auch demonstrierbar. Die in den Teilprojekten verfassten UML-Diagramme und Source-Codes sollten den inkrementell zunehmenden Komplexitätsgrad verdeutlichen und für Sie greifbar (= kalkulierbar) machen, wo die Aufwände stecken. Solche Kenntnisse sind unerlässlich für die Abschätzung von Design-Entscheidungen und Kostenschätzungen. Adreli_1_Con „How-To“ Entwicklungsstruktur: Top-Down Entwurfsverfahren: Divide-and-Conquer 1. Basis-UI (= User Interface) realisieren; Menue zur Auswahl der Funkionalität 2. Business-Logik = Schleife und Verzweigung, je nach gewählter Funktion i.W.: Keyboard-Eingaben holen (Adressfelder) -> Puffern -> in File speichern (write) oder: Inhalt aus File holen (read) -> puffern -> auf Konsole anzeigen 3. Am besten zunächst für nur zwei Felder implementieren 4. Regex einbauen 5. Programm nun für alle Felder mit Regex implementieren 6. Regex perfektionieren Bei der Verwendung neuer API-Schnittstellen: „check – test – integrate“ 1. Bottom-Up vorgehen: d.h. in einem separaten Java-File die API studieren (ausprobieren und testen) 2. Dieses separate Java-File als Case-Study archivieren: so können Sie die Case-Study später in dem Maße erweitern, wie Sie die API-Schnittstelle weiter erforschen! 3. Integrieren: die funkionierenden Code-Sequenzen aus Ihrer Case-Study in die zu entwickelnde App übernehmen. Adreli_2_Thread++ „How-To“ 1) CaseStudies zu Thread und Runnable 2) Adreli_CON mit einem expliziten Thread implementieren/umschreiben 3) „Refactoring“ überlegen! 3a) Ziel: 2 – 3 Threads: GUI-Thread Client-Teil Model-Thread Client-Teil File I/O Thread Server-Teil (ggf. der einfachheithalber GUI- und Model-Thread zusammenfassen, da die GUI sehr primitiv ist) Kommunikation der Thread, lt. Aufgabenstellung, mittels Kommunikationsmittel Pipe. Konkret: a) Lassen Sie die GUI mit der switch-case-Konstruktion wie bisher bestehen b) „Trick 1“: File-I/O-Methoden (sind 3 Methoden) redurzieren sich nun auf die Pipe-Kommunikation zwischen GUI-Thread und File-I/O-Thread c) „Trick 2“: der bisherige Buffer (z.B. ArrayList) muss in den char[]-Buffer, den z.B. PipedWriter.write(), bzw. PipedOutputStream.write() braucht, „umgeladen“ / konvertiert werden. Adreli_3_NetCom „How-To“ Adreli_4_GUI „How-To“ Adreli_5_JDBC „How-To“ (dazu später mehr / hier weiter) 5 Datenmodell und Funktionalität sind in unserem Fall zugegebenermaßen trivial. Dies ist aber so beabsichtigt, damit die „Vorgehensstruktur“ und die benutzten Java-APIs gut sichtbar im Vordergrund stehen und gut sichtbar sind! Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 71 / 210 23.03.2010 PROMOD-2a / SS10 7.1 Als Kompass/Leitplanke dienen Vorgehensmodelle Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 72 / 210 23.03.2010 PROMOD-2a / SS10 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 73 / 210 23.03.2010 PROMOD-2a / SS10 Rollen SCRUM-Modell Scrum Master Burn-down-chart Product Owner Sprint Backlog Release Backlog Product Backlog Development Team Pair-Programming 2 Prof. Illik Sprint Planning Meeting Daily Scrum Zeremonien Review Meeting Prof. J. Anton Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 74 / 210 23.03.2010 PROMOD-2a / SS10 7.2 Ihre Einschätzung? Welches Vorgehensmodell ist wofür geeignet? Für sehr grosse Projekte gut geeignet: ???-Modell Für sehr kleine Projekte gut geeignet: ???-Modell Für kleine und mittlere Projekte gut geeignet: ???-Modell Für mittlere und größere Projekte gut geeignet: ???-Modell Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 75 / 210 23.03.2010 PROMOD-2a / SS10 7.3 Und wie geht`s im Detail? Siehe Artikel: Tom Gilb, „Evolutionäres Entwickeln“; tom_gilb_1987_scan0273.pfd Siehe Video: Hamid Shojaee, „SCRUM Agile Development in under 10 Minutes“ SCRUM_video_under_10_minutes.flv Siehe Artikel: Bernd Oestereich und Christian Weiss, „Wie werden große Softwareprojekte agiler?“ wum-009-0049-y_agil_grosse_sw_projekte_agiler.pdf aus Magazin „Wirtschaftsinformatik & Management“/Heft 02.2009 • • Den (historischen) Artikel von Tom Gilb und den Artikel von Bernd Oestereich und Christian Weiss finden Sie im Wintra. Das Video von Hamid Shojaee finden Sie bei YouTube Siehe Literartur: Siehe Kapitel “Algorithmen” Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 76 / 210 23.03.2010 PROMOD-2a / SS10 8. Netzwerkzugriff Was Sie lernen: wie mit Java auf das Netzwerk zugegriffen wird wie Client- und Server-Sockets programmiert werden Voraussetzungen: Arbeiten mit Threads 8.1 Grundlagen Als Netzwerke werden miteinander verbundene Computer bezeichnet, die miteinander kommunizieren können. Das bedeutendste Computernetz ist das Internet. Stehen die miteinander verbundenen Computer innerhalb eines Unternehmens, so spricht man von einem Intranet, wenn zum Aufbau des firmeninternen Computernetzes die Internet-Technologie verwendet wird. 8.1.1 Protokolle Um die komplexen Abläufe und Mechanismen der softwareseitigen Verbindung von Computern besser verstehen zu können, wurde unter dem Dach der ISO (International Organization for Standardization) das ISO/OSI-7Schichten-Modell (1983) definiert (siehe unten ISO-Referenzmodell). (OSI = Open System Internconnection). OSI-Paradigma: Zwei Netz-Computer können Informationen nur auf der gleichen Ebene (Schicht) austauschen. Beispielsweise wird eine E-Mail von der Anwenderschicht abgeschickt und kommt auf dem anderen Rechner in der Anwendungsschicht an. Für den Datenaustausch muss es bestimmte Vorschriften geben, die den Aufbau und Abbau der Verbindung und den Datenaustausch reglementieren. Diese (Umsetzung der Vorschriften in Software) werden Protokolle genannt. Protokolle nutzen eine tieferliegende Schicht und reichen Ergebnisse an eine höhere Schicht weiter. Das Protokoll, welches sich zur Kommunikation vernetzter Computer durchgesetzt6 hat, ist TCP/IP (Transmission Control Protocol / Internet Protocol). TCP/IP ist eine Sammlung von Protokollen, die entwickelt wurden, um Computeranwendern das kooperative Arbeiten in einem Netz von örtlich verteilten Rechnern (ohne Netzwerk-Zentrale) zu ermöglichen. Diese geographisch verteilten Computer sind üblicherweise Mitglieder lokaler Netzwerke. Sowohl im lokalen Netz wie auch in der „Zusammenfassung“ dieser lokalen Netze, dem sogenannten Internet, ist die TCP/IP-Protokoll-Suite die Software-Basis für die lokale und weltumspannende Kommunikation. Die TCP/IP-Protokoll-Suite wurde auf Anregung des US-amerikanischen Departement of Defence (DoD) ab dem Jahr 1962 entwickelt (Siehe hierzu auch http://www.illik.de -> „Online-Anhang Part 1 ->“ „Historischer Aufbruch: der Bau des Information Superhighway“ ). 6 Vor TCP/IP gab es zahlreiche proprietäre Protokolle. Von der IBM: SNA System Network Architecture; von Digital Equipment (kurz DEC): DNS Digital Network Architectur. Praktisch hatte jeder große Computer-Hersteller seine eigene Protokoll Suite. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 77 / 210 23.03.2010 PROMOD-2a / SS10 ISO Referenzmodell ursprüngliches ARPAnet Referenzmodell Application Presentation Prozesse / Applikationen Session Transport TCP/IP Schichten Beispiel für die SW einzelner Schichten Benutzerprogramme, Bibliotheken HTTP, FTP, SMTP, rcp, rlogin, … telnet, ftp, rlogin, rcp Socket SKT_stream TCP Host - Host IP Network Data Link Netzwerkschnittstelle Hardware Netz-Hardware UDP Protokolle ICMP Netzwerkschnittstelle Ethernet Driver Netz-HW Netz-Hardware Die Transport-Schicht (Transmission Level). TCP und UDP sind die am meisten verwendeten Protokolle in dieser Schicht. Die Protokolle dieser Schicht bieten Netzwerkdienste für Programme der Applikations-Schicht. Jedes dieser Protokolle hat eigene besondere Fähigkeiten. Beispiele für solche Fähigkeiten sind: o TCP: ist verbindungsorientiert und stellt eine sichere Punkt-zu-Punkt-Verbindung zwischen zwei Computern her. TCP gewährleistet einen fehlerfreien Datentransport. o UDP: ist verbindungslos. Hier muss das Anwendungsprogramm gewährleisten, dass die abgeschickten Pakete fehlerfrei beim Empfänger ankommen. Auch um die richtige Reihenfolge der Pakete muss sich das Anwendungsprogramm selbst kümmern. Verbindungsorientiert / verbindungslos: Bei der Verbindungsorientierung wird mit einem „Handshake“Verfahren gearbeitet. D.h. der empfangende Rechner bestätigt den Empfang eines/mehrerer Datenpakete. Bei der verbindungslosen Übertragung werden Datenpakete übertragen, ohne eine Rückantwort abzuwarten. Paket: Größere Datenmengen werden in mehrere kleine Pakete zerlegt und zwischen Computern übertragen. 8.1.2 Adressierung: IP-Adresse und Port-Nummer Voraussetzung für ein funktionierendes Computer-Netzwerkt ist eine eindeutige Adressierung der beteiligten Rechner. Unter TCP/IP wird eine 32-Bit-IP-Adresse verwendet (also 4 Byte bei IP Version 4; zum Beispiel 80.130.234.185 in Dezimalnotation). Bei IPv6 sind es 128-Bit-Adressen, sechzehn Byte; zum Beispiel 0588:2353:0000:0000:0000:0000:1428:57ab in hexadezimaler Notation). Das Paket java.net unterstützt seit JDK 1.4 auch IPv6 Internet-Adressen, vorausgesetzt dass das Host-Betriebssystem auch IPv6 untersützt. Die Adresse besteht aus der Netzwerkadresse und der Rechneradresse. Der Adressraum ist eingeteilt in verschieden Klassen. Siehe Bild unten. Beispiel für ein Klasse-C-Adresse: 217.89.65.160 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 78 / 210 23.03.2010 PROMOD-2a / SS10 Adressierung erfolgt über IP-Adressen und Port-Nummern. Es gibt eine spezielle IP-Adresse für den lokalen Arbeitsplatz: 127.0.0.1. Diese Adresse kann auch unterm dem Alias localhost angesprochen werden und referenziert den eigenen PC. Alle Adressen die mit 10. und 192.168. beginnen, sind für die Verwendung in privaten Netzen, wie z.B. dem Intranet, reserviert. Weiter Informationen hierzu siehe RFC1918 unter www.rfc.net. Oft werden Verbindungen zwischen zwei Computern als Client-Server-Verbindungen aufgebaut. Der Server wird dabei i.d.R gestartet, läuft im Hintergrund und stellt seine Services den anfragenden Clients zur Verfügung. Der als Client fungierende Computer nimmt Kontakt mit dem Server auf um einen Service des Servers in Anspruch zu nehmen. Ein Rechner kann gleichzeitig Client und Server sein (Peer-to-Peer-Verbindung). Die Portnummer adressiert einen Dienst/Service auf einem Server. Das IAB (Internet Activity Board / Internet Architecture Board http://www.iab.org bzw. die IANA Internet Assignd Numbers Authority ) regelt die Port-Zuordnung. Sog. "well-known port numbers" (21,23,25,80, .. usw. Siehe http://www.iana.org/assignments/port-numbers. Siehe auch Bild unten). Da auf einem Computer mehrere Serverprogramme laufen, die wiederum von mehreren Clients benutzt werden können, ist es notwendig, zur Unterscheidung der Serveranwendungen neben der IP-Adresse für den Verbindungsaufbau noch eine Port-Nummer anzugeben. Eine Client-Server-Verbindung kann nur aufgebaut werden, wenn die Client-Software den Server über die richtige Port-Nummer anspricht. Protokoll/Dienst ICMP/Ping Internet Control Message Protocol Port Nicht festgelegt Transportprotokoll TCP, UDP RFCs RFC792 FTP FileTransfer Protocol TELNET Terminal-Verbindung 21 TCP RFC959, RFC765 23 TCP RFC854 SMTP/MailServer Simple Mail Transfer Protocol 25 TCP RFC821 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 79 / 210 Erläuterung Protokoll zur Übermittlung von Kontroll-und Fehlermeldungen während der Datenübertragung Datenaustausch mit einem FTP-Server Protokoll zur Terminalemulation bei der Kommunikation mit einem anderen Computer. Einfaches PostÜbertragungsprotokoll für die Sendung von E-Mails im Internet. 23.03.2010 PROMOD-2a / SS10 HTTP Hypertext Transfer Protocol 80 TCP, UDP RFC1945, RFC2068 POP3 Post Office Protocol 110 TCP, UDP RFC1939 Protokoll zur Übertragung von Informationen (HTMLSeiten und ihre Inhalte) im Internet. Mail-TransferProtokoll zum Empfangen von EMails. Die Namen der Protokolle stimmen meist mit den Namen der Server/Services überein. Die Portnummer kann eine Zahl zwischen 0 und 65535 sein. Bei der Programmierung eigener Client-ServerVerbindungen dürfen bereits belegte Portnummern nicht verwendet werden. Dies sind i.a. alle Zahlen kleiner (vor einiger Zeit: 300) 50000 … am Besten auf der iana-Site nachsehen! Zahlen ab 50000 aufwärts verwenden. 8.1.3 Die Datei hosts und DNS (Domain Name System) Ist die Angabe von localhost beispielsweise nicht möglich, kann die Zuordnung eines Namens zu einer IPAdresse in der Datei hosts vorgenommen werden. In Windows XP Professional befindet sich die Datei z.B. unter C:\windows\i386. Unter Linux unter /etc. Beispiel - C:\windows\i386\hosts: # # # # # # # # # # # # # # # # # # # # Copyright (c) 1993-1999 Microsoft Corp. Dies ist eine HOSTS-Beispieldatei, die von Microsoft TCP/IP für Windows 2000 verwendet wird. Diese Datei enthält die Zuordnungen der IP-Adressen zu Hostnamen. Jeder Eintrag muss in einer eigenen Zeile stehen. Die IPAdresse sollte in der ersten Spalte gefolgt vom zugehörigen Hostnamen stehen. Die IP-Adresse und der Hostname müssen durch mindestens ein Leerzeichen getrennt sein. Zusätzliche Kommentare (so wie in dieser Datei) können in einzelnen Zeilen oder hinter dem Computernamen eingefügt werden, aber müssen mit dem Zeichen '#' eingegeben werden. Zum Beispiel: 102.54.94.97 38.25.63.10 rhino.acme.com x.acme.com # Quellserver # x-Clienthost 127.0.0.1 localhost In dieser Datei können weitere oft verwendete IP-Adressen symblischen Namen (Aliasnamen) zugeordnet werden. Auch im Internet werden symbolische Namen für die IP-Adressen der Zielrechner verwendet; diese Aliasnamen werden als Domain-Namen bezeichnet (www.illik.de). Der Domain-Name-Server (steht i.d.R. beim InternetProvider) wandelt den Domain-Namen in die IP-Adresse um und gibt sie z.B. dem Browser zurück. Damit kann die Verbindung zum Zielserver aufgebaut werden. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 80 / 210 23.03.2010 PROMOD-2a / SS10 8.2 Klassen für die Adressierung 8.2.1 Die Klasse InetAddress: (siehe API-Doku)This class represents an Internet Protocol (IP) address. Ein Objekt der Klasse InetAddress enthält den logischen Namen und die IP-Adresse des Zielrechners. Die Klasse InetAddress befindet sich im package java.net Methoden zur Erzeugung eines InetAddress-Objekts: static InetAddress getLocalHost() static InetAddress getByName(String host) static InetAddress[] getAllByName(String host) static InetAddress getByAddress(byte[] addr) static InetAddress getByAddress(String host, byte[] addr) Adresse des eigenen Computers (siehe Beispiel unten) liefert die Adresse für den als Parameter genannten Host (siehe Beispiel unten) Alle Adressen eines remote Hosts (siehe Beispiel unten) Liefert Adresse auf der Basis der übergebenen (raw) IPAdresse (siehe Methode unten getAddress()) Kreiert Adresse basierend auf der übergebenen IP-Nr. und dem Hostnamen. (Der Hostname wird nicht geprüft; kein Aufruf eines Namensdienstes.) …u.v.a.m. Weitere Methoden, um Informationen zum InetAddress-Objekt zu erhalten: String getHostName() String getHostAddress() byte[] getAddress() boolean isMulticastAddress() boolean isSiteLocalAddress() Liefert den logischen Namen der IP-Adresse zurück (siehe Beispiel unten) Liefert die IP-Adresse zurück (siehe Beispiel unten) Liefert IP-Adresse als Byte-Array zurück (Sprechweise (API-Doku): „raw IP address“) Liefert true zurück, wenn die IP-Adresse eine MulticastAdresse ist (kann gleichzeitig an mehrere Stationen Nachrichten verschicken). “Utility routine to check if the InetAddress is a site local address” …u.v.a.m. Siehe API-Beschreibung zu den Themen: Address types IP address scope Textual representation of IP addresses Host Name Resolution Inet Address Caching Beispiel - Umgang mit InetAddress-Methoden: import java.io.*; import java.net.*; public class InternetAdressen { public static void main (String [] args) { try { System.out.println("Meine Adresse (Rechnername/IP-Adresse): " + InetAddress.getLocalHost()); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.println ("Geben Sie einen Internet-Host-Namen ein (z.B. www.fhfurtwangen.de)"); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 81 / 210 23.03.2010 PROMOD-2a / SS10 while (true) { System.out.print ("Host-Name: "); String s = in.readLine(); if (s.equals("ende")) break; try { //InetAddress iAddr = InetAddress.getByName(s); InetAddress []iAddr = InetAddress.getAllByName(s); //System.out.println("IP-Adresse: " + iAddr.getHostAddress() + "\n"); for (int i = 0; i < iAddr.length; i++) { System.out.println("IP-Adresse: " + iAddr[i].getHostAddress() + "\n"); System.out.println("Rechnername: " + iAddr[i].getHostName() + "\n"); } } catch(UnknownHostException ex) { System.out.println ("Host konnte nicht gefunden werden" + "\n"); } } } } catch (Exception ex){ } } 8.2.2 Die Klasse URL Ein URL = „Uniform Ressource Locator“ ist eine Beschreibung einer Adresse (Quelle) im Internet / dient dazu, um Ressourcen in einem Netzwerk zu lokalisieren. Siehe API-Doku: „Class URL represents a Uniform Resource Locator, a pointer to a "resource" on the World Wide Web. A resource can be something as simple as a file or a directory, or it can be a reference to a more complicated object, such as a query to a database or to a search engine…..” Eine URL besteht aus mehreren Bestandteilen <Protokoll>://[Login[:Passwort]@]Rechnername.Domain[:Port]/Verzeichnis/Ressource o Die Java -Plattform unterstützt sowohl HTTP- als auch FTP-Ressourcen Die Klasse URL ermöglicht nicht nur den komfortablen Zugriff auf die oben genannten Teile der URL, sondern auch auf die hinter der URL stehende Ressource. So kann eine Datei durch Angabe der URL auch direkt geladen werden. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 82 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel: http://www.fh-furtwangen.de:80/user/derda/index.html Zugriff: Wenn man den Inhalt der Ressource abholen möchte, kann dies mit der Methode openStream() der Klasse URL erledigt werden. Siehe Beispiel weiter unten. Konstruktoren Für die Klasse URL gibt es sechs Konstruktoren. Hier zwei davon: URL(String spec) throws MalformedURLException URL(String protocol, Sting host, int port, String file) throws MalformedURLException … Der String spec enthält die URL Es kann die URL, in ihre Bestandteile zerlegt, übergeben werden. … und dann gab7 es noch URIs und URNs Das Paket java.net ab JDK 1.4 unterscheidet zwischen URLs und URIs (Uniform Ressource Identifiers). Eine URI ist ein syntaktisches Konstrukt, das die verschiedenen Teile der Zeichenfolge spezifiziert, die eine Webressource kennzeichnen. Eine URL ist eine spezielle Form eines URIs: die URL enthält ausreichend Informationen, um eine Ressource zu lokalisieren. URIs, wie beispielsweise [email protected] sind keine Lokatoren (Bezeichner für den Standort von Ressourcen) – der Identifier enthält keine Daten für die Lokalisierung. Ein solcher URI heisst URN (Uniform Ressource Name) In der Java-Bibliothek hat die Klasse URI keine Methoden für den Zugriff auf die Ressource, die der Bezeichner spezifiziert – ihre einzige Aufgabe ist das Parsing (Wir verfolgen das Thema hier nicht weiter. Siehe API-Doku und Literatur.) Wichtige Methoden der Klasse URL String getFile() String getHost() int getPort() String getProtocol() URLConnection openConnection() throws IOException InputStream openStream()throws IOException Boolean sameFile(URL ofher) Gibt den Datei-Namen, der in dem URL enthalten ist, zurück. Liefert den Host-Namen, der in dem URL enthalten ist. Liefert die Port-Nummer des URLs. Liefert das Protokoll des URLs. Öffnet eine neue Verbindung zu der URL-Adresse und gibt ein URLConnection-Objekt zurück. Öffnet eine neue Verbindung zu der URL-Adresse, von der gelesen werden soll, und gibt ein InputStreamObjekt zurück (remember: it’s a byte stream) (siehe Beispiel unten) Vergleicht zwei URLs in Bezug auf die URL-Adresse und gibt true zurück, wenn beide auf dieselbe Ressource verweisen. … Beispiel – Fragment URL-Methode openStream() 7 Anmerkung: Die früher übliche Unterscheidung zwischen URI, URL und URN ist nicht mehr relevant (vgl. [W3C01]). Wenn Sie den Eindruck vermitteln möchten, ganz besonders auf der Höhe der Zeit zu stehen, können Sie den Begriff „IRI” (Internationalized Ressource Identifier) verwenden. IRIs werden durch RFC 398 7(vgl. http://www.w3.org/International/iri-edit/]) definiert und sind im Prinzip URIs mit der Möglichkeit, alle Zeichen aus dem Unicode-Zeichensatz zu verwenden. Prüfen Sie den Sachverhalt hinsichtlich evtl. Auswirkungen auf Java! Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 83 / 210 23.03.2010 PROMOD-2a / SS10 Wenn man einfach den Inhalt der Ressource abholen (lesen) möchte, kann man dies mit Hilfe der Methode openStream(): diese Methode liefert ein InputStream-Objekt zurück, mit dem man den Inhalt der Ressource lesen kann: … URL url = new URL(urlString); … InputStream uin = url.openStream(); // stream of bytes BufferedReader in = new BufferedReader(new InputStreamReader(uin)); // bridge class String line; while ((line = in.readLine()) != null) // Zeilenweise lesen { // Zeile verarbeiten } … Hinweis: Benötigt man jedoch zusätzliche Angaben über die Ressource, muss auf die Klasse URLConnection zurückgegriffen werden. Diese Klasse bietet eine weitergehende Kontrolle über den Zugriff auf Web-Ressourcen; z.B. Setup-Parameter und Request-Properties können gesetzt werden. Siehe API-..\..\..\jdk-6u10docs\docs\api\index.htmlDoku und Literatur. Beispiel – URL-Methoden: (urls.java) import java.io.*; import java.net.*; public class URLs { public static void main (String [] args) { int l = 1000; byte[] is_byte = new byte[l]; try { URL url = new URL(args[0]); InputStream is = url.openStream(); System.out.println ("Inhalt von " + url.getProtocol()+ "://" + url.getHost() +": " + url.getPort()); while ((l = is.read(is_byte)) != -1) // Reads some number of bytes from the //input stream and stores them //into the buffer array { System.out.print(tochar(is_byte)); } } catch (MalformedURLException ex) { System.out.println ("falsche URL"); // Protocol - Domain - Port: http://www.ambit.de:80 } catch (IOException ex) { ex.printStackTrace(); } } private static char[] tochar(byte[] b) { char[] c = new char[b.length]; for (int i = 0; i < b.length; i++) c[i] = (char) ((b[i] & 0x7F) + (b[i] < 0 ? 128 : 0)); return c; } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 84 / 210 23.03.2010 PROMOD-2a / SS10 8.3 Socket-Programmierung Sockets wurden erstmalig in BSD-Unix („Berkeley System Distribution“) angeboten (Anfang der 1980er Jahre). Die Socket-Schnittstelle ist danach in allen bedeutenden Betriebssystemen implementiert worden. Die Socket-Schnittstelle dient der bidirektionalen Kommunikation zwischen zwei Computern im Netzwerk: mit den Methoden getOutputStream() und getInputStream() werden entsprechende Byte-Streams geöffnet. Die Datenübertragung über Sockets wird grob in drei Schritten vollzogen: 1. Zurest wird eine Punkt-zu-Punkt-Verbindung zwischen zwei Computern hergestellt. („unicast“) Dann werden die Daten übertragen; danach erfolgt der Verbindungsabbau. 2. Bei einer Client-Server-Verbindung wird dazu (vom Client) via IP-Nummer und Port-Nummer des Servers eine Verbindung zu diesem hergestellt. 3. Bei Peer-to-Peer-Verbindungen braucht auch der Server die IP- und Port-Daten vom Client. Es gibt Socket-Klassen für den Server (Klasse ServerSocket) und für den Client (Klasse Socket) Æ siehe API-Doku. Worin sich die Sockets unterscheiden? Siehe unten „Hinweis“ bei Client- und Server-Sockets. Schema für die Kontaktaufnahme und die Kommunikation zwischen Client und Server (vgl. unten public class server und public class client) Der Server ServerSocket erzeugen Der Client (rein/pur) Socket erzeugen bind() auf Port-Nr. mit Server verbinden connect() (u.U. blockierend) auf Verbindung warten [u. U. mit Backlog dimensionieren] und bestätigen accept() (u.U. blockierend) Anfrage schreiben / Antwort lesen . Kann auch schon beim Erzeugen erledigt werden. Siehe API-Docu Verbindung abbauen close() Thread generieren lesen/schreiben (kooperieren über Sockets) ServerSocket schließen close() Anmerkung: es gibt Stream-Sockets (= verbindungsorientierte TCP-Sockets) und Datagram-Sockets (verbindungslose UDP-Sockets). Wir betrachten im Folgenden nur die Stream-Sockets. Zu den DatagramSockets siehe Socket-API-Doku. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 85 / 210 23.03.2010 PROMOD-2a / SS10 Client und Server verwenden Sockets auf unterschiedliche Weise: Das Server-Programm verwendet einen Socket, der mit einem lokalen Port des Server-Rechners verbunden (Methode bind()) ist. Damit kann ein Client mit dem Server-Socket per connect() Verbindung aufnehmen. Das Client-Programm verwendet einen Socket, der eine Verbindung zu einem Port des Server-Rechners aufbaut (mit der Methode connect()). 8.3.1 Client-Sockets: die Klasse socket Hinweis: In der Klasse socket spielt stets die Adresse (in diversen Formen) des Servers eine Rolle. In der Klasse ServerSocket spielt nur der Port – über den der Service erreicht werden soll – eine Rolle. Siehe auch Hinweis zu ServerSocket. Konstruktoren der Klasse Socket: Socket(String host, int port) throws IOException Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException Socket(InetAddress address, int port) throws IOException Socket(InetAddress address, int port, InetAddress localAddr, Int localPort) throws IOException Als Parameter werden Host-Name und Port-Nummer des Servers angegeben. Creates a stream socket and connects it to the specified port number on the named host. Wie oben. Wie oben … vom eigenen Rechner / mit bind() darauf Wie oben, jedoch Host-Bezeichnung nicht über String sondern InetAddress-Objekt. Analog oben, mit bind() des Sockets auf locale Adresse / Port. Creates a socket and connects it to the specified remote address on the specified remote port. Wird der angegebene Zielrechner nicht gefunden, wird eine Exception vom Typ UnknownHostException (von IOException abgeleitet) ausgelöst. Wichtige Methoden der Klasse Socket Die Klasse Socket besitzt einige get- und set-Methoden zum Abfragen und Setzen der Verbindungseigenschaften. Die wichtigsten Methoden sind getOutputStream() und getInputStream(). Beide geben ein entsprechendes Byte-Stream-Objekt zurück, über das der Datenaustausch erfolgt, wenn die Verbindung erfolgreich eingerichtet werden konnte. (Siehe 7.4 „Was kommt den da im Strom?“: diese Streams werden dann ggf. noch überführt, je nach dem, was im Stream kommt (Text, binäre Daten/serialisierte Objekte). InetAddress getInetAddress() InetAddress getLocalAddress() int getPort() public OutputStream getOutputStream() throws IOException public InputStream getInputStream() throws IOException public int getSoTimeout() throws SocketException public int setSoTimeout(int timeout) 8 9 Liefert die Adresse des Host-Computers, zu dem die Verbindung besteht, also der Client connect()de ist. Wird die Methode im Server für den Server-Socket aufgerufen, so liefert sie die eigene Adresse (des Server Hosts). Gibt die lokale Adresse zurück, an die der Socket gebunden (bind()ed) ist. (Das Analogon im Server heist getLocalSocketAdress().) Gibt die Portnummer des Hosts zurück Erzeugt ein OutputStream-Objekt (das ist ein ByteStream) für das Übertragen von Daten zum Server8. Gibt ein InputStream-Objekt (das ist ein Byte-Stream) zurück für das Lesen von Daten vom Server9. Liefert die Zeit in ms, die eine Leseanforderung an den Host dauern darf, bevor eine InterruptedIOException ausgelöst wird (0 entspricht unbegrenzter Wartezeit). Setzt die Timeout-Zeit in ms für eine Leseanforderung Siehe auch unten: Kapitel „Was kommten den da im Stream? Sockets und Streams“ Siehe auch unten: Kapitel „Was kommten den da im Stream? Sockets und Streams“ Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 86 / 210 23.03.2010 PROMOD-2a / SS10 throws SocketException 8.3.2 an den Host. 0 entspricht unbegrenzter Wartezeit. ServerSockets: die Klasse ServerSocket Bevor ein Client die Kommunikation mit einem Server aufnehmen kann, muss natürlich der Server gestartet werden. Der Server bleibt dann in der accept()-Methode geblockt, bis sich ein Client bei ihm anmeldet. Hinweis 1: Beim ServerSocket spielt die IP-Addresse des Partners keine Rolle (vgl. oben Hinweis zum Client Socket.) Ursache: der ServerSocket wird nie als Erster initiativ! Die Initiative geht immer vom Client aus, deswegen muss dieser die Adresse des Severs wissen. Für die „Rückwärtskommunikation“ zum Client bekommt der Server einen geeigneten Client-Socket von der Methode accept() als Returnwert zur Verfügung gestellt. Für diesen Client Socket kann sich der Server mit getOutputStream() ein OutputStream Objekt besorgen, über dass er dem Client Infos zukommen lassen kann. Zusammenfassend kann man also sagen, der ServerSocket dient nur für die Kontaktaufnahmen mit dem Server. Die Kommunikation läuft über die Client Sockets. Hinweis 2: Wenn über einen einzigen Socket gelesen und geschrieben werden soll, geht das zwar, wie wir eben gesehen haben, ist jedoch „wenig komfortabel“/“flexibel“. Man muss sein eigenes „Protokoll“ implementieren, d.h. der Client meldet durch ein „over“, dass jetzt der Server ein Antwort (über den selben Socket) geben kann. Für eine echte Peer-to-Peer-Architektur würde man beiden Kommunikationspartner jeweils einen eignen ServerSocket und einen eigenen Client Socket spendieren. Konstruktoren der Klasse ServerSocket ServerSocket() ServerSocket(int port) ServerSocket(int port, int backlog) Creates an unbound server socket. Siehe Beispiel unten: ist dann noch mit bind() an einen Port zu binden. Creates a server socket, bound to the specified port. Mit dem zweiten Parameter kann die maximale Länge der Warteschlange bestimmt werden. Wird eine Verbindung an einem Socket mit voller Warteschlange angemeldet, so wird die Verbindung abgelehnt. Wichtige Methoden der Klasse ServerSocket Socket accept() void bind(SocketAddress endpoint) void bind(SocketAddress endpoint, int backlog) void close() InetAddress getInetAddress() int getLocalPort() int getSoTimeout() void setSoTimeout(int timeout) Prof. Illik Server wartet auf Verbindungsanfragen am betreffenden Port. Kommt eine Verbindungsanforderung von einem Client, so liefert die Methode das Socket-Objekt für die Client-Verbindung. (Wird die Verbindung vomClient beendet, so kann der Server einen weiteren Client bedienen.) Bindet ServerSocket an angegebene IP-Adresse und Port Bindet ServerSocket an angegebene IP-Adresse und Port. Dimensioniert backlog. Schließt den Server-Socket. Gibt die (eigene) Adresse zurück, mit der der Socket verbunden ist Gibt den Port zurück, mit dem der Server-Socket verbunden ist. Liefert die Timeout-Zeit in ms, für die ein Client-Socket den Server-Socket blockieren darf. Setzt die Timeout-Zeit in ms, die ein Client-Socket den Server blockieren darf. PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 87 / 210 23.03.2010 PROMOD-2a / SS10 8.3.3 Beispiel – Server-Sockets (server.java): import java.net.*; import java.io.*; import java.util.*; public class Server { public static void main (String [] args) throws IOException { System.out.println (">>> Server wird gestartet"); /* ALTERNATIVE A) Für LOKALE Sockets: **************************************** // The range for assigned ports managed by the IANA is 0-1023 // Creates ServerSocket bound to a specific (local) port; // Phantasie-Port > 45000 ServerSocket myServerSocket = new ServerSocket (45678); // bound() to port ServerSocket //*/ //* ALTERNATIVE B) Für INTERNET-Sockets *************************************** // class InetSocketAdress extends class SocketAdress InetSocketAddress myInetSocketAddress = new InetSocketAddress("localhost",45678); ServerSocket myServerSocket = new ServerSocket(); // unbound ServerSocket myServerSocket.bind(myInetSocketAddress); // we bind()it //*/ for(;;) { // start infinite loop Socket myClientSocket = myServerSocket.accept(); // accept() blocked und liefert Client-Socket // zurück; von dem kann gelesen werden, // wenn's ein schreibender Client ist } BufferedReader in = new BufferedReader(new InputStreamReader //bridge from byte streams to character streams to text (myClientSocket.getInputStream())); System.out.println ("--> Local Address angemeldet: " + myClientSocket.getLocalAddress()); // mit wem? System.out.println ("--> InetAddress angemeldet: " + myClientSocket.getInetAddress()); String s; try { while ((s = in.readLine()) != null) // Lesen vom Client-Socket System.out.println ("> " + s); // und Ausgabe des Gelesenen } catch (Exception e){} System.out.println ("<-- LocalAddress (address to which the socket is bound): " + myClientSocket.getLocalAddress()); System.out.println ("<-- InetAddress (addr to which the socket is connected): " + myClientSocket.getInetAddress()); myClientSocket.close(); } // end infinite loop } 8.3.4 Beispiel – Client-Sockets (client.java): import java.io.*; import java.net.*; public class Client { public static void main (String [] args) throws IOException { String s; // wird via Keyboard gefüllt und dann auf Socket geschreiben if (args.length != 1) { System.out.println ("falscher oder fehlender Parameter\n"+ Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 88 / 210 23.03.2010 PROMOD-2a / SS10 "Geben Sie als Parameter die Adresse des Servers an. Z.B. 'localhost'"); System.exit(1); } try { // ENTWEDER: Client Socket kreieren (UND gleich den Server connect()en) // Socket clientSocket = new Socket (args[0], 45678); // ODER; unconnected Socket kreieren und DANACH connect()en: InetSocketAddress serverInetSocketAddress = new InetSocketAddress("localhost",45678); Socket clientSocket = new Socket(); clientSocket.connect(serverInetSocketAddress); System.out.println ("Verbindung zu Server mit der Adresse " + clientSocket.getInetAddress() + " hergestellt."); // // // // // // client braucht in unserem Beispiel kein bind(), da er ja nicht über den/einen Port angesprochen werden soll; für peer-to-peer-Applikationen wirds gebraucht: InetSocketAddress myInetSocketAddress = new InetSocketAddress("localhost",45678); clientSocket.bind(myInetSocketAddress); BufferedWriter out = new BufferedWriter( // zum Schreiben auf Socket new OutputStreamWriter(clientSocket.getOutputStream())); // bridge from text to character streams to byte streams BufferedReader in = new BufferedReader( // zum Lesen von Console new InputStreamReader (System.in)); do { System.out.print ("Nachricht an Server: "); s = in.readLine(); // Lesen von Keyboard out.write (s); // Schreiben auf den Client-Socket out.newLine(); out.flush(); } while ( !s.equals ("ende") && clientSocket != null); in.close(); out.close(); } catch ( Exception e ) { e.printStackTrace(); System.exit(1); } } } Anmerkung: Dem Javaprogramm Kommandozeilenargumente mitgeben Textpad: Konfiguration -> Einstellungen -> Extras -> Java Programm starten ->Parameterabfrage Eclipse: Run -> Open Run Dialog 8.4 Was kommt den da im Strom? Sockets und Streams Was die Kommunikationspartner über die Sockets austauschen sind Byte-Streams, geliefert von den SocketMethoden getOutputStream() und getInputStream(). Was in den Streams „drinsteckt“, wissen die Kommunikationspartner: Wird Text über die Sockets übertragen, so wird der Stream in BufferedReader und BufferedWriter unter Zuhilfenahme der Bridge-Klassen OutputStreamWriter und InputStreamReader überführt. Werden binäre Daten über die Sockets übertragen, so wird der Stream in DataInputStream und DataOutputStream überführt. Bei serialisierten Objekten verwendet man dagegen ObjectInputStream und ObjectOutputStream. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 89 / 210 23.03.2010 PROMOD-2a / SS10 In unseren Beispielen oben übertragen wir Text über Sockets und nutzen deshalb die Bridge-Klassen Bild: ServerSockets und Client-Sockets. Über ServerSockets wird der Kontakt aufgenommung. Kommuniziert wird über die Client Sockets: Die Kommunikation in der Case Study ServerSocket Server clientSocket.connect(serverInetSocketAddress); ClientSocket ClientSocket ClientSocket ServerSocket ClientSocket Server (ggf. multi-threaded) ClientSocket = accept() for(;;) getInputStream() bzw. getOutputStream() gibt es nur für ClientSocket Client Sockets Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 90 / 210 23.03.2010 PROMOD-2a / SS10 9. RMI Remote Method Invocation Was Sie lernen: wie mit Java auf entfernte Objekte zugegriffen wird wie Client- und Server-Programme mittels verteilter Objekte programmiert werden Voraussetzungen: Arbeiten mit Interfaces Arbeiten mit den Tools o RMI-Registry-Tool: rmregistry o RMI-Compiler: rmic o Policytool für den Sicherheitsmanager: policytool (Hinweis: Das Kapitel basiert auf dem Buch J. Anton Illik: „Verteilte Systeme – Architekturen und SoftwareTechnologien“, Expertverlag, Stuttgart-Renningen, 2007. Im Buch wird die RMI-Variante unter Java VM-Version < 5 und JDK 1.4 beschrieben. Hier im Skript beschreiben wir „new RMI“ ab VM-Version 5 zusammen mit dem JDK 1.6.) 9.1 Grundlagen RMI (remote method invocation) ist die Java-Implementierung des „Remote Procedure Calls“ (RPC). Der Begriff „method invocation“ ist im Java-Umfeld natürlich treffender, da im Zusammenhang mit Java die Methoden eines entfernten Objekts aufgerufen werden. Abgesehen davon, sind viele Details, die im Zusammenhang mit dem Methodenaufruf stehen, wie beispielsweise Parameter- und Returnwert-Übergabe, mit den gleichen Problemen behaftet, wie beim RPC-Mechanismus (siehe Buch „Verteilte Systeme“, S. 94). Glücklicherweise passen damit auch die für den RPC kennengelernten Lösungsverfahren (wie z.B. der Einsatz von Stubs für das Marshalling/Unmarshalling). RMI impliziert also für die Entwicklung verteilter Anwendungen einen Entwurf, der auf die Verteilung von Objekten setzt, lokale Objekte kommunizieren mit entfernten Objekten. Das entfernte Objekt kann dabei entweder auf einem entfernten Rechner residieren oder aber auch auf dem gleichen Rechner gehostet werden wie das aufrufende Objekt – entscheidend dabei ist, dass das lokale und das entfernte Objekt in zwei verschiedenen virtuellen Maschinen (virtual machine10) laufen. Der Aufruf der Methode eines entfernten Objekts sieht dabei genauso aus, wie der Aufruf einer Methode von einem lokalen Objekt. Im Gegensatz zum lokalen Methodenaufruf gibt es für den entfernten Methodenaufruf allerdings mehr Exceptions11, da ja beispielsweise der entfernte Methodenaufruf durch einen Verbindungszusammenbruch scheitern kann. 10 Java-Programme werden vom Java-Compiler in den Byte-Code übersetzt, der vom Prozessor eines Computers nicht direkt ausgeführt wird. Die Virtual Machine ist die Ablaufumgebung für die in den Byte-Code übersetzten JavaProgramme. Diese Ablaufumgebung sorgt für die Interpretation des Byte-Codes. 11 Durch die Anzeige einer Exception signalisiert die Virtual Machine der Applikation eine Ausnahmesituation. Auf eine angezeigte Ausnahmesituation muss die Applikation reagieren, andernfalls wird die Applikation von der Virtual Machine abgebrochen.. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 91 / 210 23.03.2010 PROMOD-2a / SS10 Alle notwendigen RMI-Bestandteile (Klassen, Interfaces, RMI-Compiler, RMI-Registry, usw.) sind bereits in der Java Standard Edition JSE enthalten, so dass der Java-Entwickler bereits ohne den Einsatz von JEE (Java Enterprise Edition) einfache verteilte Applikationen entwickeln kann. Eine weitere Ursache, dass RMI bereits in JSE enthalten ist, liegt darin, dass für die Entwicklung verteilter Java-Applikationen auf der Basis von RMI keine Application Server notwendig sind, wie es sonst bei anderen JEE-Mechanismen (Enterprise Java Beans, Servlets, JSP) der Fall ist. 9.2 Die Bestandteile einer RMI-Anwendung Eine RMI Anwendung besteht im Wesentlichen aus folgenden Bestandteilen: • • • • • • • einem RMI-Client (= der Teil des Anwendungsprogramms, der die zentral vom RMIServer zur Verfügung gestellte Funktionaliät nutzt), Stub-Klassen auf der Client-Seite12, einem RMI-Server (= der Teil des Anwendungsprogramms, der den RMI-Clients zentrale Applikationsfunktionen zur Verfügung stellt), Skeleton-Klassen auf der Server-Seite (in den jüngeren Java-Releases ist das Skeleton identisch mit dem Stub; Stub auf der Server-Seite wird Skeleton genannt) und einer RMI-Registry (ist ein Service, bei dem der RMI-Server (zur Laufzeit) die Objekte registriert, die von RMI-Clients genutzt werden können). Das Programm rmiregistry liegt in java/bin des JDK. Wenn der RMI-Server (bzw. eines seiner bei der RMI-Registry registrierten Remote Objekte) nicht skalare Ergebnisse zurückliefern muss, kommt auch noch der RMISecurityManager ins Spiel (für den Austausch serialisierter Objekte). Der RMISecurityManager braucht dann ein Policy-File (dies heißt per default .java.policy, kann vom Entwickler aber auch anderst benannt werden) in dem die Grants (= die gewährten Zugriffsrechte) verzeichnet sind. Hergestellt, bzw. bearbeitet wird das Policy-File mit dem Policytool. Das Programm policytool liegt in java/bin des JDK. Auf der Client-Seite kümmert sich der Stub um die eigentliche Kommunikation mit der Serverseite. Ein Stub-Objekt ist ein client-seitiger Stellvertreter für das entfernte Objekt im RMIServer. Im RMI-Server steht dem Stub als konkreter Kommunikationspartner ein entsprechendes Skeleton-Objekt13 zur Verfügung. Ruft ein RMI-Client eine Methode von einem entfernten Objekt auf, so wird der Aufruf an das Stub-Objekt delegiert, das dann für die Weiterleitung des Methodenaufrufs an das entfernte Objekt sorgt. Dort kümmert sich als serverseitiger Vertreter für das aufrufende Objekt das Skeleton-Objekt um den Aufruf der gewünschten Methode. Stub und Skeleton übernehmen jeweils auf ihrer Seite die Abwicklung der Kommunikation (z.B. auch das Marshalling, bzw. Unmarshalling). 12 Es besteht auch die Möglichkeit, sich die vom Client benötigte Stub-Klassen dynamisch vom Server geben zu lassen. Diesen Ansatz werden wir hier zunächst nicht weiter verfolgen. 13 Das Skeleton-Objekt ist notwendig, wenn mit der Java-Entwicklungsversion JDK1.1 gearbeitet wird. In den Nachfolgeversionen wurde das Skeleton-Objekt auf der Serverseite durch das Stub-Objekt ersetzt, das auch auf der Client-Seite Verwendung findet. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 92 / 210 23.03.2010 PROMOD-2a / SS10 Weitergabe Client Datenstrom weiterleiten Stub Aufruf der Methode Stub Aufruf einer Marshalling der entfernten Methode Parameter Remote-Objekt Unmarshalling der Parameter Bild 1: Die Rolle von Stub und Skeleton Die Rolle der RMI-Registry Ein RMI-Client, der einen bestimmten Dienst aufrufen möchte, fragt zunächst bei der RMIRegistry nach, welches entfernte Objekt dafür das richtige ist und bekommt als Antwort eine entsprechende Objektreferenz auf das entfernte Objekt im Server zurück. Die Adresse der zuständigen RMI-Registry muss der Client kennen (URL + Port (1099 per default)). Die RMIRegistry muss auf demselben Rechner laufen, auf dem das Remote-Objekt ausgeführt wird. Der RMI-Server dient dazu, ein entferntes Objekt zu instantiieren und es unter einem bestimmten Dienstenamen in der RMI-Registry zu registrieren. Das Programm rmiregistry liegt in java/bin des JDK. Per default ist die RMI-Registry an den Port 1099 gebunden. 9.3 Das Ablaufschema eines entfernten Methodenaufrufs Im Folgenden geben wir schematisch den Ablauf eines entfernten Methodenaufrufs wieder und zeigen dabei, welche Rolle die einzelnen Bestandteile der RMI-Anwendung spielen. Danach besprechen wir die für die Herstellung notwendigen Werkzeuge. In einem abschließenden konkreten Beispiel werden die Details dann auf der Ebene des Codes beleuchtet. Wir gehen bei der Beschreibung unten davon aus, dass die verteilte RMI-Applikation hergestellt und bereits auf die entsprechenden Rechner verteilt ist. Beim Start der verteilten Applikation läuft dann folgendes ab. 1. Schritt: Der RMI-Server registriert ein entferntes Objekt bei der RMI-Registry unter einem eindeutigen Namen. (In unserem Beispiel unten ist die das Server-Programm HelloServer.java) RMI-Server-Host RMI-Registry RMI-Server Prof. Illik J V M B PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 93 / 210 23.03.2010 PROMOD-2a / SS10 Bild 2: Der RMI-Server registriert ein Objekt in der RMI-Registry 2. Schritt: Der Client (in unserem Beispiel heißt der Client HelloClient) schaut bei der RMIRegistry unter dem Namen (in unserem Beispiel lautet der Objekt-Name für das RemoteObjekt HelloImpl) nach und bekommt im positiven Fall eine Objektreferenz, die seinem RMI-Client-Host RMI-Server-Host RMI-Registry RMI-Client Remote Interface entsprechen muss (siehe Bild 3). Bild 3: Die RMI-Registry liefert die Objektreferenz zurück 3. Schritt: Der Client (HelloClient) ruft die gewünschte Methode aus der Objektreferenz auf. 4. Schritt: Der Server (HelloServer) gibt dem Client die Rückgabewerte14 dieses Aufrufs, oder der Client bekommt eine Fehlermeldung (bei einem Verbindungsabbruch, beispielsweise.) Das Bild 49 fasst die Abläufe nochmals insgesamt zusammen: RMI-Client-Host J V M RMI-Server-Host RMI-Client RMI-Registry Stub RMI-Server A OS A Skeleton J V M B OS B Bild 4: RMI Gesamtablauf 9.4 Die Hilfsmittel RMI-Compiler und RMI-Registry Die Aufgabe des RMI-Compilers15 rmic ist die Erzeugung von Stub (und Skeleton) aus den Klassendateien. Für eine Klasse, die das Interface Remote implementiert, erzeugt der RMICompiler rmic die benötigten Stubs. Standardmäßig werden die erzeugten Stubs in demselben 14 Ist der Rückgabewert skalar, so ist kein RMISecurityManager notwendig. Note: If the server needs to support clients running on pre-5.0 VMs, then a stub class for the remote object implementation class needs to be pregenerated using the rmic compiler, and that stub class needs to be made available for clients to download. See the codebase tutorial for more details. Siehe: http://java.sun.com/javase/6/docs/technotes/guides/rmi/index.html 15 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 94 / 210 23.03.2010 PROMOD-2a / SS10 Verzeichnis abgelegt, wie die zugrunde liegenden Klassendateien. Für den RMI-Compiler gibt es eine Reihe von Optionen; u.a. ist damit auch möglich, Stubs in anderen Verzeichnissen ablegen zu lassen. 9.5 Der RMISicherheitsmanager (RMISecurityManager) Der RMISecurityManager legt fest, welche Rechte „fremde“ Klassen (remote Klassen) an lokale Klassen haben. Um ein gewisses Maß an Sicherheit zu gewährleisten, sollte jedes RMI-ServerProgramm, einen RMISecurityManager haben, ansonsten können nur Klassen aus dem lokalen classpath verwendet werden. Um einem Programm die passenden Rechte zu geben, werden die zugesicherten Rechte in einer Rechte-Datei gesammelt, der Policy-Datei. Wenn die Laufzeitumgebung (virtual machine) gestartet wird, wird ein Verweis auf diese PolicyDatei mitgegeben (mit der Option –D, also für unser Beispiel: $java –D.java.policy HelloServer 1099), damit der Security-Manager Berechtigungen vergeben kann. Die Policy-Dateien bestehen aus einer Reihe von grant-Anweisungen. Das grafische Dienstprogramm policytool (siehe Bild 5: Das policytool) bietet die Möglichkeit, Applikationen und signierten Applets spezielle Rechte einzuräumen oder zu verweigern. Das Policy-Tool nimmt dem Entwickler die Arbeit ab, die Rechte-Dateien von Hand zu editieren. Bild 5: Das policytool (aus dem JDK-Verzeichnis java/bin) Nach dem Aufruf des Programms policytool öffnet sich ein Fenster, das uns einige Menüpunkte bereitstellt, über die man bestehende Rechte-Dateien editieren oder auch neue anlegen kann. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 95 / 210 23.03.2010 PROMOD-2a / SS10 Neue Einträge für die Zugeständnisse der Laufzeitumgebung an das Programm werden über das Menü Add Policy Entry („Richtlinieneintrag hinzufügen“) gemacht. Folgende Tabelle zeigt einige Permissions und ihre Bedeutungen Policy Permissions Bedeutung AllPermission Anwendung oder Applet dürfen alles FilePermission Zugriff auf Dateien und Verzeichnisse NetPermission Zugriff auf NetzwerkRessourcen PropertyPermission Zugriff auf Systemeigenschaften ReflectPermission Zugriff über Reflection auf andere Objekte RuntimePermission Einschränkungen von Laufzeitsystemen SecurityPermission allgemeines Sicherheitskonzept, etwa für den Zugriff auf Policies SerializablePermission Beschränkung der Serialisierung SocketPermission Spezielle Einschränkungen an Sockets Bild 6: Sicherheitseinstellungen (Permissions) Aus welchen Bestandteilen besteht nun der RMI-Client, bzw. der RMI-Server in unserem Beispiel? 9.6 Die Komponenten und Bestandteile auf der Server-Seite Auf der Server-Seite liegen: • Das Remote Interface des entfernten Objekts (remote Interface), welches das JavaInterface remote erweitert. (In unserem Beispiel unten das interface Hello; File: Hello.java) • Das entfernte Objekt steckt in unserem Beispiel in Server-Programm (File: HelloServer.java). • Stub und Skeleton für das entfernte Objekt ist das generierte File: HelloImpl_Stub.class. (Wird durch den RMI-Compiler rmic erzeugt: $rmic HelloImpl) • Der Server, der das entfernte Objekt instanziiert und bei der RMI-Registry anmeldet. In unserem Beispiel unten HelloServer; (File: HelloServer.java). • Die Policy-Datei. In unserem Beispiel unten die Datei .java.policy. (Per default heißt die Policy-Datei üblicherweise so und liegt üblicherweise im $HOME-Verzeichnis und enthält mehrere Policies). Bei uns liegt die Datei im Verzeichnis des Servers. Die Sicherheitseinstellungen müssen der laufenden VM ($java-Aufruf) bekannt sein, damit sie die Sicherheitsüberwachung machen kann. Die Policy-Datei kann im Verzeichnis des Servers liegen und muss mit der Option –D beim java-Aufruf mitgegeben werden. In unserem Beispiel: $java –D.java.policy HelloServer) • Die RMI-Registry. Wird auf der Server-Seite gestartet (manuell oder vom Betriebssystem als Service: $rmiregistry, bzw. unter Angabe des Ports, z.B. $rmiregistry 1099). Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 96 / 210 23.03.2010 PROMOD-2a / SS10 9.7 Die Komponenten und Bestandteile auf der Client-Seite Auf der Client-Seite liegen: • Das Interface des entfernten Objekts, welches das Java-Interface remote erweitert. (In unserem Beispiel unten das interface Hello) • Der Stub16 für das entfernte Objekt. In unserem Beispiel unten ist dies HelloImpl_Stub.class. (Ist ab Java VM-Version 5 nicht mehr notwendig.) • Der Client, der das entfernte Objekt nutzen möchte. In unserem Beispiel unten ist dies HelloClient. 9.8 Vollständiges RMI-Beispiel Im Folgenden Beispiel werden die oben genannten Bestandteile konkret vorgestellt und wichtige Details erläutert. Entfernte Methoden werden durch das Interface java.rmi.Remote definiert: // Java-Beispiel: Das remote Interface und die entfernte Methode sayHallo() import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote{ // Liegt auf der SERVERSEITE. // Liegt ebenfalls auf der CLIENTSEITE. public String sayHello (String language) throws RemoteException; } // das Interface gibt vor, wie die Signatur von sayHello() aussieht // sayHello() gibt einen String zurückgibt ♣ Bild 7: Das remote Interface Hello.java Das erweiterte remote-Interface wird dann implementiert um als entferntes Objekt genutzt zu werden. Server-Programme HelloServer.java implementieren also das Interface Remote und erweitern java.rmi.server.UnicastRemoteObject, welches die wichtigsten Methoden für die Verwendung von RMI bereitstellt. Bis jetzt haben wir: • • • Ein Remote-Objekt samt Interface (Hello), dessen Methoden von einer entfernten JVM aufgerufen werden können. Das Programm HelloServer.java meldet das Remote Object bei der Registry an. Ein Client-Programm, das einen solchen Aufruf vornimmt. Die Stub-Generierung mit rmic ist ab Java VM-Version 5 nicht mehr notwendig 16 Wenn der Stub zur Laufzeit nicht auf der Client-Seite vorhanden ist, wird er beim Name-Binding vom Server zurückgegeben. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 97 / 210 23.03.2010 PROMOD-2a / SS10 Für eine Klasse, die das Interface Remote implementiert, erzeugt der RMI-Compiler rmic die benötigten Stubs und Skeletons: $javac Hello.java $javac HelloClient.java // bis Version 5, danach entfällt der rmic-Einsatz $rmic HelloImpl # erzeugt HelloImpl_Stub.class // bis Version 5 Für Interessierte: Durch rmic -keepgenerated HelloImpl kann man die erzeugten Stubund Skel-Klassen als Java-Quelltext anschauen. Wie bekommt ein Client Zugriff auf ein Hello-Objekt? Wir wissen: Stub für Client und Server. • • • Der Server muss den Stub unter einem (eindeutigen) Namen bei einer rmiregistry anmelden. Diese rmiregistry muss auf demselben Rechner laufen, auf dem das Remote-Objekt (Stub) ausgeführt wird! Der Client muss 1. Über die Klasse LocateRegistry und die Methode getRegistry(host) sich auf dem Remote Host eine Referenz auf das Remote Object besorgen 2. den Namen des Objekts kennen, unter dem der Stub bei der Registry bekannt ist. // Java-Beispiel: Ein RMI-Server. File: HelloServer.java (Variante 1) import java.rmi.*; import java.rmi.server.*; import java.rmi.registry.*; public class HelloServer implements Hello { public HelloServer() {}; public String sayHello (String language) throws RemoteException { String lang = language.toLowerCase(); switch (lang.charAt (0)) { case'd':return "Hello!"; case'f':return "Salut!"; case'i':return "Ciao!"; default: return"Hi!"; } // end switch } // end sayHello() public static void main(String[] args) { try { HelloServer obj = new HelloServer(); Hello stub = (Hello)UnicastRemoteObject.exportObject(obj, 0); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 98 / 210 23.03.2010 PROMOD-2a / SS10 A2: // Bind the remote object's stub in the registry Registry registry = LocateRegistry.getRegistry(); registry.bind("Hello", stub); System.out.println("HelloServer ready."); } // end try catch (Exception e) { System.err.println("Server exception: " + e.toString()); e.printStackTrace(); } // end catch } //end main() } // end class HelloServer ♣ Bild 8: Die Klasse HelloServer.java (Variante 1) // Java-Beispiel: Ein RMI-Server. File: HelloServer.java (Variante 1) // import import import import import java.rmi.*; java.rmi.server.*; java.rmi.registry.*; java.io.*; java.net.*; public class HelloServer implements Hello { public HelloServer() {}; public String sayHello (String language) throws RemoteException { String lang = language.toLowerCase(); InetAddress serverInetAddr; // just to communicate the servers IP+name to the client String hostname; // just to communicate the servers IP+name to the client try { serverInetAddr = InetAddress.getLocalHost(); hostname = serverInetAddr.getHostAddress() + " " + serverInetAddr.getHostName(); System.out.println(hostname); switch (lang.charAt (0)) { case'd':return "Hello!" + " vom Server " + hostname; case'f':return "Salut!" + " vom Server " + hostname; case'i':return "Ciao!" + " vom Server " + hostname; default: return"Hi!"+ " vom Server " + hostname; } // end switch } catch (Exception ex){return "ERROR"; } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 99 / 210 23.03.2010 PROMOD-2a / SS10 } // end sayHello() public static void main(String[] args) { try { HelloServer obj = new HelloServer(); Hello stub = (Hello)UnicastRemoteObject.exportObject(obj, 0); // Bind the remote object's stub in the registry Registry registry = LocateRegistry.getRegistry(); registry.bind("Hello", stub); System.out.println("HelloServer ready."); } // end try catch (Exception e) { System.err.println("Server exception: " + e.toString()); e.printStackTrace(); } // end catch } //end main() } // end class HelloServer ♣ Bild 9: Die Klasse HelloServer.java (Variante 2: Server sendet seine IP und seinen Namen mit der Antwort zum Client) Erläuterungen zu den Marken im RMI-Server-Beispiel: A1: Ein RMISecurityManager wird eingerichtet (Siehe API-Doku: „RMI's class loader will not download any classes from remote locations if no security manager has been set”) (fehlt hier noch) A2: Aus der API-Doku: Note that a getRegistry call does not actually make a connection to the remote host. It simply creates a local reference to the remote registry and will succeed even if no registry is running on the remote host.“ U.U. würde der Server die Registry selbst starten. Siehe hier zu API-Doku. // Java-Beispiel: Ein RMI-Client import import import import java.rmi.*; java.rmi.server.*; java.rmi.registry.LocateRegistry; java.rmi.registry.Registry; public class HelloClient { private HelloClient() {}; static public void main (String[]args) { String host = (args.length<1) ? null :args[0]; try{ Registry registry = LocateRegistry.getRegistry(host); Hello stub = (Hello) registry.lookup("Hello"); System.out.println (stub.sayHello("Englisch")); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 100 / 210 23.03.2010 PROMOD-2a / SS10 System.out.println (stub.sayHello("Deutsch")); System.out.println (stub.sayHello("Italienisch")); System.out.println (stub.sayHello("Franzoesisch")); } catch (Exception e) { System.err.println ("HelloClient failed, caught exception" + e.getMessage()); System.err.println("Client exception: " + e.toString()); e.printStackTrace(); } } // end main() } // end class ♣ Bild 10: Die Klasse HelloClient.java Das Beispiel in Betrieb nehmen Insgesamt werden die Beispielprogramme übersetzt mit $javac Hello.java HelloServer.java HelloClient.java [Hinweis/Note: If the server needs to support clients running on pre-5.0 VMs, then a stub class for the remote object implementation class needs to be pregenerated using the rmic compiler, and that stub class needs to be made available for clients to download. See the codebase tutorial for more details.] Um das Beispiel auszuführen: • die Registry starten $rmiregistry oder via $start rmiregistry [To start the registry, run the rmiregistry command on the server's host. This command produces no output (when successful) and is typically run in the background. For more information, see the tools documentation for rmiregistry] • den Server starten $java HelloServer oder via $start java HelloServer • den Client starten $java HelloClient Siehe auch „Getting Started Using Java RMI“ http://java.sun.com/javase/6/docs/technotes/guides/rmi/index.html -> „Getting Started“ Hinweis 1: auf Windows 7 machte die Ausführung bisher hartnäckig Probleme! Hinweis 2: Auf Ubuntu 9.10 (Karmic Koala) läuft das Beispiel einwandfrei, +) wenn Client und Server gemeinsam auf der Ubuntu-Maschine gehostet sind, ++) wenn Client auf Ubuntu mit Server auf Vista zusammenarbeitet --) Es geht nicht: Server auf Ubuntu und Client auv Vista –> Client auf Vista crashed mit „connection refused“ 9.9 Zusammenfassung RMI ist die objektorientierte Variante von RPC und exklusiv auf Java abgestimmt. Daraus resultiert eine mangelnde Integrationsfähigkeit mit anderen Verteilungstechniken und Programmiersprachen. Aus der Sicht der Programmierung ist einer der wesentlichen Vorteile, dass bei nur geringem Initialisierungsaufwand lokale und entfernte Methodenaufrufe gleich gestaltet sind. RMI versteckt alle Details der Netzwerkkommunikation vor dem Entwickler. Dieser Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 101 / 210 23.03.2010 PROMOD-2a / SS10 muss sich weder mit Fragen des Datenaustauschs über Sockets, noch mit Fragen zum Protokoll auseinandersetzen. Allerdings gilt es zu beachten, dass auch der parallele, gleichzeitige Zugriff auf entfernte Objekte möglich ist und dass der Entwickler für solche Fälle die Zugriffssynchronisation selbst übernehmen muss. Die Interaktion zwischen Client und Server selbst läuft synchron ab: ein entferntes Objekt wartet, bis eine seiner Methoden aufgerufen wird. Während der Ausführung einer Methode wartet wiederum der aufrufende Client bis die Methode fertig ausgeführt ist. Um RMI in Betrieb zu nehmen und am Laufen zu erhalten sind einige wenige Werkzeuge und Dienste notwendig. Ein einfach strukturierter Namensdienst, die RMI-Registry, besorgt die (flache) Abbildung von Dienstenamen auf entfernte Objekte. (Der RMI-Compiler RMIC sorgt für die automatische Stub- und Skeleton Generierung aus den entsprechenden Klassendateien.) Java RMI kommt ohne Middleware aus, weil RMI, wie oben erwähnt, exklusiv auf Java abgestimmt ist. 9.10 Kontrollfragen (Technologien / RMI) Frage J1: Frage J2: Frage J3: Frage J4: Frage J5: Prof. Illik Worin besteht der konzeptionelle Unterschied zwischen RMI und RPC? Welche Aufgaben haben Client-Stub und Server-Skeleton im Rahmen von RMI zu lösen? Welche Rolle spielt die RMI-Registry? Welche Rolle spielt der RMI-Compiler? Was hat der Sicherheitsmanager bei RMI zu leisten? PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 102 / 210 23.03.2010 PROMOD-2a / SS10 10. AWT-Basics: Graphical User Interface Was Sie lernen: wer die wichtigen Mitglieder der AWT-Klassenhierarchie sind wie Fenster erzeugt und angezeigt werden wie Fenster geschlossen werden was Ereignisse sind wie und welche Ereignisse empfangen werden können Voraussetzungen: Arbeiten mit Klassen 10.1 Grundlagen AWT steht für "abstract windowing toolkit"; verfügbar seit Java Version 1.0 Notwendig : import java.awt.* (siehe API-Doku) AWT (package java.awt) nutzt das native GUI des darunterliegenden OS („heavyweight GUI components“), d.h. es nimmt das jeweilige native "look and feel" an - im Unterschied zu SWING („Bestandteil der JFC Java Foundation Classes“, package javax.swing), welches das native GUI nicht nutzt („lightweight GUI components“). Weitere Alternative: SWT („Standard Widget Toolkit“, IBM, 2001 / -> Eclipse), als „best of both worlds“ Das Peerkonzept des AWT: das Peer-Widget zeichnet sich selbst Funktionalität von AWT o Anzeigen von Bitmaps, Ausgaben von Sound o Zeichnen primitiver geometrischer Objekte (Linie, Kreis, Rechteck,...) und füllen dieser Objekte o Ausgabe von Schriften o bietet Komponenten für den Dialog mit dem User: Fenster, Schaltfläche, Textfelder, Menüs, … o Handling von Ereignissen (Events), welche durch Umgang mit Tastatur und Maus (z.B. Klicken auf Schaltfläche) oder Operationen auf Komponenten ausgelöst werden (z. B. Fenster schließen) Für die Darstellung grafischer Elemente, wie z.B. Schaltflächen, Textfelder, usw., werden spezielle Klassen verwendet Æ Java-Beans (Das sind Klassen, für die bestimmte syntaktische Regeln einzuhalten sind.) JavaBeans werden auch als Komponenten oder Widgets bezeichntet. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 103 / 210 23.03.2010 PROMOD-2a / SS10 Unterschied zwischen AWT und Swing: AWT nutzt nur die nativen Elemente, die auf allen Plattformen vorhanden sind, d.h. AWT-Komponenten sind auf diese überall verfügbaren nativen Elemente beschränkt. Die SWING-Bibliothekt ist umfangreicher, da sie nicht die nativen Elemente des Betriebssystems nutzt, sondern alle Komponenten selbst implementiert („Java zeichnet die Widgets selbst“). o Swing ist die GUI-Komponente in JFC Java Foundation Classes; im package javax.swing sind noch darüber hinaus gehende Funktionalitäten enthalten: Das Look & Feel ist zur Laufzeit veränderbar, ohne Neustart des Programmes Barrierefreier Zugang (zur Programmbedienung) wird unterstützt (alternative Interaktionstechniken für Menschen mit Handycap; Spracherkennung, Lesegeräte für Blinde, Braille-Schrift, etc.) Drag & Drop zum Datenaustausch zwischen Programmen (nicht notwendigerweise JavaProgramme) Sehr gute 2D Bibliotheken (vergleichbar mit dem Postscript-Ansatz) Gemeinsamkeit von AWT und Swing: Die zugrunde liegende Technik, wie die Ereignisbehandlung, Arbeiten mit Formen, Farben, Schriften und Umgang mit den Layoutmanagern (für die Anordnung der Elemente in einem Fenster) sind bei AWT und SWING gleich. Hinweis zur Übersetzung und Ausführung von GUI-basierenden Java-Programmen Bei Java-Installationen unter Windows gibt es im JDK zwei Java-Interpreter: java.exe zeigt ein Konsolfenster an. Über dieses Konsolfenster haben wir bisher die I/O getätigt javaw.exe zeigt kein Konsolfenster an. Ausgaben über System.out/err sind damit nicht mehr sichtbar. Fertig entwickelte und geteste GUI-basierte Java-Applikationen werden am Besten mit javaw.exe gestartet. Während der Entwicklung und des Tests ist aber java.exe nach wie vor nutzbar. Im Textpad muss die Nutzung von javaw.exe erst konfiguriert werden: „Konfiguration“ -> „Einstellungen“ -> „Extras“ -> „Hinzufügen“: jetzt alle Einstellungen so vornehmen wie in der Einstellung „Java Programm starten“ und statt java.exe nun javaw.exe einsetzen. Eclipse erkennt GUI-Programme und startet diese unmittelbar mit javaw.exe. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 104 / 210 23.03.2010 PROMOD-2a / SS10 10.2 AWT – Klassenhierarchie java.lang.Object java.util.EventObject java.awt.AWTEvent java.awt.Component java.awt.Container Graphics ...Choice ...Label ...Button ...List …Canvas java.awt.Dialog javax.swing.JDialog java.awt.Window javax.awt.Panel java.awt.Frame javax.applet.Applet javax.swing.JFrame javax.swing.JComponent javax.swing.Label …Scrollbar …Checkbox …TextComponet java.applet.Applet javax.swing.JApplet Bild: Die AWT-Klassenhierarchie17 Im Folgenden betrachten wir zunächst das Wesentliche aus den Klassen Frame, Window, Container und Component, soweit es notwendig ist, um mit Fenstern umzugehen. Danach machen wir einen Abstecher zur Grafikprogrammierung und der Klasse Graphics: Kapitel „Grafikprogrammierung...“ Im Anschluß daran kümmern wir uns um Komponenten, also die Klassen Button, Choice bis TextCompontent: Kapitel „AWT-Komponenten“ 17 In dieser Klassenhierarchie fehlt komplett die Klasse java.awt.Menu. Siehe API Doku! Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 105 / 210 23.03.2010 PROMOD-2a / SS10 10.3 „Fenster“: von der Component über den Container zum Frame Klasse Component: übernimmt/tätigt - Festlegen von Größe und Position einer Komponente (Schaltfläche, Eingabefeld,...) - Festlegen von Farbe und anderen grafischer Eigenschaften (Farbe, 2D, 3D,...) - Befähigt Komponenten Ereignisse zu empfangen Definition der Klasse Component aus der API Doku: A component is an object having a graphical representation that can be displayed on the screen and that can interact with the user. Examples of components are the buttons, checkboxes, and scrollbars of a typical graphical user interface. The Component class is the abstract superclass of the nonmenu-related Abstract Window Toolkit components. Class Component can also be extended directly to create a lightweight component. A lightweight component is a component that is not associated with a native opaque window. Klasse Container: (abgeleitet von Component) übernimmt/tätigt - Aufnahme von Komponenten... - Entfernen von Komponenten... - Anordnen von Komponenten unter Nutzung eines LayoutManagers Definition der Klasse Container aus der API Doku: A generic Abstract Window Toolkit (AWT) container object is a component that can contain other AWT components. Components added to a container are tracked in a list. The order of the list will define the components' front-toback stacking order within the container. If no index is specified when adding a component to a container, it will be added to the end of the list (and hence to the bottom of the stacking order). Klasse Window und Panel: (abgeleitet von Container) Die Klasse Window erzeugt Top-Level-Fenster ohne Rahmen, Menü, Titelleiste. o Eine Anwendung müsste die fehlenden Teile selbst zeichnen o Die Objekte der Klasse haben keine (wie bei Fenstern) übliche Funktionalität. Diese muss vollständig in der Anwenung implementiert werden. - Definition der Klasse Window aus der API Doku: A Window object is a top-level window with no borders and no menubar. The default layout for a window is BorderLayout. A window must have either a frame, a dialog, or another window defined as its owner when it's constructed. In a multi-screen environment, you can create a Window on a different screen device - Die Klasse Panel dient der Aufnahme anderer Komponenten, einschließlich anderer Panels. o Die Klasse dient also der Zusammenfassung von mehreren Komponenten zu einer Gruppe. o Für jedes Panel kann ein eigener LayoutManager zur Anordnung der Komponenten verwendet werden. Definition der Klasse Panel aus der API Doku: Panel is the simplest container class. A panel provides space in which an application can attach any other component, including other panels. The default layout manager for a panel is the FlowLayout layout manager. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 106 / 210 23.03.2010 PROMOD-2a / SS10 10.4 Klasse Window Siehe Java 2 Platform API Specification: public class Window extends Container implements Accessible A Window object is a top-level window with no borders and no menubar. The default layout for a window is BorderLayout. A window must have either a frame, dialog, or another window defined as its owner when it's constructed. In a virtual device multi-screen environment in which the desktop area could span multiple physical screen devices, the bounds of all configurations are relative to the virtual device coordinate system. The origin of the virtual-coordinate system is at the upper left-hand corner of the primary physical screen. Depending on the location of the primary screen in the virtual device, negative coordinates are possible, as shown in the following figure. The following code sets the location of a Window at (10, 10) relative to the origin of the physical screen of the corresponding GraphicsConfiguration. If the bounds of the GraphicsConfiguration is not taken into account, the Window location would be set at (10, 10) relative to the virtual-coordinate system and would appear on the primary physical screen, which might be different from the physical screen of the specified GraphicsConfiguration. Window w = new Window(Window owner, GraphicsConfiguration gc); Rectangle bounds = gc.getBounds(); w.setLocation(10 + bounds.x, 10 + bounds.y); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 107 / 210 23.03.2010 PROMOD-2a / SS10 Windows are capable of generating the following WindowEvents: WindowOpened, WindowClosed, WindowGainedFocus, WindowLostFocus. Windows können viele Listener zugeordnet warden (es gibt zahlreiche add..()-Listener-Methoden) 10.5 Klasse Panel: (abgeleitet von Container) Siehe Java 2 Platform API Specification: public class Panel extends Container implements Accessible Panel is the simplest container class. A panel provides space in which an application can attach any other component, including other panels. The default layout manager for a panel is the FlowLayout layout manager. …der dann mit einem LayoutManager verwaltet werden kann. Panel können keine Listener zugeordnet warden (es gibt keine add..()-Listener-Methoden) Case-Study: vergleiche in der API-Spezifikation die „Method Summary“ für die Klassen Window und Panel. 10.6 Klasse Frame: (abgeleitet von Window) Repräsentiert das Fenster, wie man es bspw. von MS Windows oder Linux/KDE/Gnome oder Mac kennt. o Besteht aus einer Titelleiste, einem Systemmenü einem Rahmen, Symbole zu minimieren, maximieren und schliessen. Konstruktoren der Klasse Frame public Frame() public Frame(String title) Ein Fenster mit leerer Titelleiste wird erzeugt. Ein Fenster mit entsprechend gefülltem Titel wird erzeugt. In der Klasse Frame befinden sich viele Attribute (Größe, Position, Farbe des Fensters), die mit entsprechenden get()- und set()-Methoden, die sich meist durch ihre Namen selbst erklären, gelesen bzw. gesetzt werden können. Die meisten Methoden erbt die Klasse Frame von der Klasse Component, einige aber auch von den Klassen Container und Window. (Sie API-Dokumentation) Wichtige Methoden der Klasse Frame (…und ihrer Basisklassen) public String getTitle() Prof. Illik Text in der Gibt den Fenstertitel zurück, PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 108 / 210 23.03.2010 PROMOD-2a / SS10 public void setTitle(String title) public boolean isResizeable() public void setResizable(boolean resizable) public Image getIconImage() public void setIconImage(Image image) public int getState() public void setState(int state) public public mb) public m) public int y) public b) public MenuBar getMenuBar() void setMenuBar(MenuBar Titelleiste bzw. setzt ihn Veränderbarkeit der Fenstergröße Fenstersymbol Hat der Parameter resizable den Wert true, so kann das Fenster in seiner Größe geändert werden, sonst nicht. Das Symbol, das beim Minimieren des Fensters angezeigt wird, kann abgefragt, bzw. gesetzt werden. (Das Minimieren von Fenstern wird nicht von allen OS unterstützt.) Der Status eines Fensters kann normal (Frame.NORMAL; Standard) oder minimiert (Frame.ICONIFIED) sein. Die mit dem Fenster verbundene Menüleiste wird zurückgegeben, bzw. gesetzt. Die Methode remove() entfernt die verbundene Menüleiste. Fensterstatus (normal oder minimiert) Menüleiste void remove(MenuComponent void setLocation(int x, s.u. - siehe Beispiel unten - void setVisisble(Boolean s.u. - siehe Beispiel unten - void dispose() s.u. - siehe Beispiel unten - Î Weitere Methoden und Konstruktoren und Methoden siehe API – Documentation zum Package java.awt. Beispiel – Fenster erzeugen, Attribute setzen und anzeigen // Fenster1.java import java.awt.*; public class Fenster1 { public static void main(String[] args) { Frame frame = new Frame(); // Erst Fenster “konfigurieren” frame.setTitle("Mein erstes Java-Fenster"); frame.setSize(300,200); frame.setLocation(350,300); frame.setBackground(Color.blue); } // // // // // Text für die Titelleiste Fenstergrösse festlegen Fensterposition festlegen Fensterfarbe festlegen von Component geerbt. // Dann Fenster anzeigen frame.setVisible(true); } Das Objekt der Klasse Frame besitzt schon einiges an Funktionalität, die Objekte der Klasse Window nicht haben: Das Minimier- und Maximierfeld funktioniert. Das Fenster kann verschoben werden. Das Fenster kann durch ziehen am Rahmen vergrößert / verkleinert werden. Die Titelleiste nimmt eine andere Farbe an, wenn das Fenster aktiviert oder deaktiviert wird (Werte im OS eingestellt). Die Betätigung des Schließfeldes bleibt aber ohne Wirkung und muss erst programmiert werden. (Siehe Beispiel unten.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 109 / 210 23.03.2010 PROMOD-2a / SS10 10.7 "Fensterbausteine" und der Umgang damit (Erzeugung / Konfiguration / Anzeige) Vorgehensweise: Fenster erzeugen, dann konfigurieren und danach anzeigen Im folgenden Beispiel wird die Initialisierung eines Frames von dessen Anzeige getrennt. Wichtige Fenster und Dialoge können auf dies Weise zuerst im Hintergrund erzeugt werden und dann bei Bedarf angezeigt werden. Beispiel – Fenster mit funktionierendem Schließfeld // FrameBeispiel.java import java.awt.*; import java.awt.event.*; public class FrameBeispiel extends Frame { Panel p = new Panel(); // Eine Panel wird im Frame eingefügt public FrameBeispiel() //Im Konstruktor wird lediglich die { // Konfiguration des Frames vorgenommen this.setSize(150,150); p.setBackground(Color.green); // Hintergrundfarbe des Panels ändern this.add(p); // Panel wird dem Frame zugeordnet this.addWindowListener(new WindowAdapter() // Listener beim Frame registrieren { public void windowClosing(WindowEvent we) {System.exit(0);} }); } public static void main(String [] args) { FrameBeispiel fB = new FrameBeispiel(); // Frame erzeugen via Konstrukor fB.setVisible(true); // Frame anzeigen } } Hinweise: Wir haben mit dem Beispiel etwas vorgegriffen. Dem Frame (Ereignisquelle) wird ein WindowListener zugeordnet (Ereignisempfänger), der für den Aufruf einer Call-Back-Methode verantwortlich ist, sobald bestimmte Ereignisse eintreffen. Die Call-Back-Methode sorgt dann für die richtige Reaktion auf das Ereignis. Diese Technik (Ereignisbehandlung; Event Delegation Model) werden wir in der Folge betrachten. Die Ereignisbehandlung ist hier mittels Verwendung einer Adapterklasse implementiert. (Methode „A“) Wir werden später sehen, dass die Ereignisbehandlung auch durch die Implementierung eines ListenerInterfaces realisiert werden kann.(Methode „B“) 10.8 Anzeigen und schließen: Bevor das Fenster auf dem physikalischen Schirm zur Anzeige gebracht wird (frame.setVisible(true)), sind entsprechende Ressourcen im Speicher allokiert. Nach frame.setVisible(false) sind die Ressourcen noch belegt. Erst nach frame.dispose() werden die belegten Ressourcen frei gegeben (um die sich dann der Garbage Collector kümmert). Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 110 / 210 23.03.2010 PROMOD-2a / SS10 frame.setVisible(true) frame.setVisible(false) frame.dispose() //Fenster anzeigen //Fenster unsichtbar machen //Ressourcen freigeben Anwendung ist aber noch nicht beendet! o System.exit(status) Î normal 0!! Beispiel – Fenster verschwinden lassen // Fenster2.java import java.awt.*; public class Fenster2 { public static void main(String[] args) { Frame frame = new Frame(); // erzeugen frame.setTitle("Mein erstes Java-Fenster"); // Konfigurieren frame.setSize(300,200); frame.setLocation(350,300); frame.setBackground(Color.blue); frame.setVisible(true); // anzeigen try{ Thread.currentThread().sleep(5000); }catch(Exception ex){} frame.setVisible(false); // … frame.dispose(); System.exit(0); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 111 / 210 23.03.2010 PROMOD-2a / SS10 10.9 Bestandteile eines Fensters (class Frame) Ein AWT-Fenster hat den im Folgenden gezeigten Grund-Aufbau. Auf den verschiedenen Plattformen unterscheidet sich das Erscheinungsbild geringfügig. Fenstersymbol (icon) Titel (title) in Titelleiste Minimier-, Maximier- und Schließfeld (beeinflusst state) Fensterposition (x,y) bezieht sich auf die linke obere Ecke Hintergrund (background) Vordergrund (foreground) Fensterhöhe (height) Cursor Fensterrahmen (border) Fensterbreite (width) In der folgenden Übersicht sind die Fensterbestandteile und die Methoden aufgelistet, über die ein Fenster konfiguriert werden kann: Eigenschaft Titel title Fenstersymbol icon Größe height x width (von Compontent geerbt) Position X,Y (von Compontent geerbt) Position und Größe Prof. Illik Setzen der Eigenschaft setTitle(String title) Lesen der Eigenschaft String getTitle() setIconImage(image i) Image getIconImage() setSize(int width, int height) Dimension getSize() int getHeight() int getWidth() setLocation(Point p) Point getLocation() setLocation(int x, int y) int getX() int getY() Rectangle getBounds() setBounds(Rectangle r) PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 112 / 210 23.03.2010 PROMOD-2a / SS10 (von Compontent geerbt) Vordergrundfarbe foreground (von Compontent geerbt) Hintergrundfarbe background (von Compontent geerbt) Cursor (von Window geerbt) setBounds(int x, int y, int width, int height) setForeground(Color c) Color getForeground() setBackground(Color c) Color getBackground() setCursor(Cursor c) Cursor getCursor() 10.9.1 Das Fenstersymbol/Icon: Das Bild für das Fenstersymbol sollte plattformspezifisch erstellt werden, da die Darstellung nicht auf allen Plattformen gleich ist. Vorgeschriebene Formate: GIF, JPEG oder PNG. Andere Formate werden nicht verstanden (siehe API-Doku). z. B. Windows 16 x 16 Pixel (small Icon); 32 x 32 Pixel (large Icon). Hinweis: JDK passt Größe automatisch an, wenn GIF / JPEG / PNG eine andere Auflösung haben. Bild am Besten ins Verzeichnis der class-Datei. (Siehe Case-Study.) Beispiel – Ein Icon nutzen entweder Methode a): // URL des Bildes laden URL url = this.getClass().getRessource("./smile.gif"); // Icon laden (mit Methode aus der Klasse Toolkit)) Image icon = this.getToolkit().getImage(url); // Icon dem Fenster zuweisen frame.setIconImage(icon); oder Methode b): Image icon = this.getToolkit().getImage(“./smile.gif”); frame.setIconImage(icon); Für Textpad: Die gif-Datei muss in diesem Beispiel in dem Verzeichnis liegen, in dem die class-Datei des Beispiels liegt. Wenn mit Eclipse gearbeitet wird gelten ggf. andere Ablageorte (z.B. Projektordner) Beispiel – Siehe fenster3.java mit Methode b) import java.awt.*; public class Fenster3 { public static void main(String[] args) { Frame frame = new Frame(); frame.setTitle("Java-Fenster mit Icon"); frame.setSize(300,200); frame.setLocation(350,300); frame.setBackground(Color.blue); Image icon = frame.getToolkit().getImage("smile.gif"); frame.setIconImage(icon); frame.setVisible(true); try{ Thread.sleep(10000); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 113 / 210 23.03.2010 PROMOD-2a / SS10 } }catch(Exception ex){} frame.setVisible(false); frame.dispose(); System.exit(1); } Beispiel – Siehe fenster4.java mit Methode a) import java.awt.*; import java.net.*; public class Fenster4 extends Frame { URL url; public static void main(String[] args) { Fenster4 fenster = new Fenster4(); } public Fenster4() { super("Java-Fenster mit Icon"); this.setSize(300,200); this.setLocation(350,300); … // im Programm unten komplettiert … // try { url = this.getClass().getResource("smile.gif"); } catch(Exception ex) { System.out.println(ex.getMessage()); } Image icon = this.getToolkit().getImage(url); this.setIconImage(icon); this.setVisible(true); try{ Thread.sleep(15000); }catch(Exception ex){} this.setVisible(false); this.dispose(); System.exit(1); } } 10.9.2 Fensterposition und -Größe: Die Position des Fensters bezieht sich immer auf die linke obere Ecke des Bildschirms. Die oben aufgeführten Methoden zur Festlegung von Position und Ausdehnung können Objekte der Klassen Point, Dimension und Rectangle als Parameter übergeben bekommen. Die Objekte dieser Klassen werden erzeugt, indem Werte für deren Attribute dem jeweiligen Konstruktor übergeben werden. Point(int x, int y) // Punkt x,y Dimension(int width, int height) // Breite, Höhe der Komponente Rectangle(int x, int y, int width, int height) // Punkt x, y, Breite, Höhe Z B.: Point p = new Point(10,10); setLocation(p); 10.9.3 Fensterfarben: Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 114 / 210 23.03.2010 PROMOD-2a / SS10 Zum Setzen von Vorder- und Hintergrundfarbe wird der entsprechenden Methode ein Color-Objekt übergeben. Die Klasse Color kapselt die Farben des RGB (Rot-Grün-Blau)-Farbmodells (Zahlenwerte von 0 - 255 geben Intensität des Farbanteils an) Einige Farben sind vordefiniert: z. B.: black, blue, cyan, grey, darkGrey, lightGrey, magenta, orange, pink, green, red, yellow. Color.blue // Farbe blau Color(int red, int green, int blue) this.setBackground(new this.setBackground(new this.setBackground(new this.setBackground(new Prof. Illik // “Farbmischung” durch Angabe der // Intensität der RGB-Farbanteile Color(255, 255, 255)); Color(0, 0, 0)); Color(255, 0, 0)); Color(200, 70, 150)); // // // // weißer Hintergrund schwarzer Hintergrund roter Hintergrund rosa Hintergrund PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 115 / 210 23.03.2010 PROMOD-2a / SS10 10.9.4 Der Mouse-Cursor: Der Mauszeiger (Cursor) kann innerhalb eines Fensters verschiedene Formen annehmen. Form wird durch eine symbolische Konstante repräsentiert Mit der Methode setCursor() lässt sich die Form des Cursors ändern. Symbolische Konstante DEFAULT_CURSOR CROSSHAIR_CURSOR HAND_CURSOR TEXT_CURSOR MOVE_CURSOR E_RESIZE_CURSOR W_RESIZE_CURSOR NE_RESIZE_CURSOR SW_RESIZE_CURSOR NW_RESIZE_CURSOR SE_RESIZE_CURSOR S_RESIZE_CURSOR N_RESIZE_CURSOR WAIT_CURSER CURSOR-Form Î Standardzeiger, meist als Pfeil Î Kreuz Î Handform Î Einfügemarke Î Positionsänderung Î Größenänderung Î Größenänderung Î Größenänderung Î Größenänderung Î Größenänderung Î Größenänderung Î Größenänderung Î Größenänderung Î Sanduhr / Wartezustand Aufgabe: Suchen Sie den passenden Cursor zu den oben genannten Cursorformen. import java.awt.*; import java.net.*; public class Fenster4 extends Frame { URL url; public static void main(String[] args) { Fenster4 fenster = new Fenster4(); } public Fenster4() { super("Java-Fenster mit Icon"); this.setSize(300,200); this.setLocation(350,300); this.setBackground(new Color(250,90,200)); this.setCursor(new Cursor(Cursor.HAND_CURSOR)); try { url = this.getClass().getResource("smile.gif"); } catch(Exception ex) { System.out.println(ex.getMessage()); } Image icon = this.getToolkit().getImage(url); this.setIconImage(icon); this.setVisible(true); try{ Thread.sleep(15000); }catch(Exception ex){} this.setVisible(false); this.dispose(); System.exit(1); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 116 / 210 23.03.2010 PROMOD-2a / SS10 Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 117 / 210 23.03.2010 PROMOD-2a / SS10 11. AWT-Events: Ereignisse und Event-Handling Was Sie lernen: was Ereignisse sind wie Ereignisse empfangen, weitergeleitet und ausgewertet werden welche Arten von Ereignissen es gibt Voraussetzungen: Grundlagen der Fensterprogrammierung Innere und anonyme Klassen 11.1 Grundlagen In der Programmierung grafischer Benutzeroberflächen stellt die Ereignisbehandlung (Event-Handling) ein zentrales Element dar. Das aktuelle Modell der Ereignisbehandlung unterscheidet sich von dem in Java 1.0. (Auf dieses ursprünglich implementierte Modell gehen wir nicht weiter ein.) Die Arbeitsweise der Ereignisbehandlung ist für AWT- und SWING-Komponenten nahezu identisch. Ereignismodell Java AWT Maus OS wartet Tastatur leitet Nachricht weiter lfd. Appl. In einem Programm mit GUI können Eingaben jeder Art Ereignisse auslösen. Sollen die Ereignisse etwas bewirken, so muss das entsprechend programmiert werden. In einem Java-Programm sind daran immer drei Objekte beteiligt: Ereignisquelle: das ist die Klasse, von der das Ereignis ausgeht. Dies kann beispielsweise eine Schaltfläche sein, auf die mit der Maus geklickt wird. Ereignisziel / Ereignisempfänger: das sind Objekte, das das Ereignis empfangen Æ Listener. Die Ereignissquelle muss den entsprechenden Listener registrieren, damit das Ereignis verarbeitet werden kann. Ereignisobjekt: Wird ein Ereignis ausgelöst, so wird die zuständige („Callback“-)Methode des Ereignisobjekts aufgerufen. Dieses Ereignisobjekt wird dem Listener als Parameter mitgegeben, wenn er der Ereignisquelle zugeordnet wird. Dieses Modell, bestehend aus Ereignisquelle, Ereigniszielen und Ereignisobjekt wird als " Event Delegation Model" bezeichnet ("Ereignisdelegationsmodell"). D. h. die Ereignisquelle leitet (delegiert) die Ereignisse an alle Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 118 / 210 23.03.2010 PROMOD-2a / SS10 registrierten Listener weiter. Der für das Ereignis zuständige Listener reagiert dann entsprechend darauf. (Ereignisquellen haben Methoden, mit denen man Ereignisempfänger für die Ereignisbehandlung registrieren kann.) Event-Delegation-Modell: mehrere Listener können bei einer Ereignisquelle registriert werden Ereignis z. B. Mausklick auf Button Programm registrieren Ereignisziel z. B. MouseListener Ereignisquelle z. B. Button Delegation registrieren Delegation Ereignisziel z. B. ActionListener Beispielsweise registriert eine Schaltfläche (Klasse Button) zwei Listener, einen für Mausklicks und einen für Mausbewegungen. Wenn die Ereignisquelle ein Ereignis empfängt, wird es an die Listener delegiert. Die Registrierung des Listeners bei der Ereignisquelle: Enthält „Event-Handler“ (A) Adapter oder (B) Interface (auch „Callback”-Methode genannt) EreignisQuellObjekt.addEreignisListener(EreignisBehandlungsObjekt); Das Ereignismodell erlaubt verschiedenen Möglichkeiten der Implementierung der Ereignisbehandlung, z.B: Modell A: die Verwendung von Adapterklassen, die schon eine (leere) Implementierung des Interfaces bereitstellen, so dass nur noch die Listener-Methoden implementiert werden müssen, die benötigt werden. Modell B: die Implementation eines Listener-Interfaces. In diesem Fall müssen alle Methoden des Interfaces implementiert werden. Im Beispiel unten (EventFenster.java) werden die beiden Methoden gegenübergestellt. Vorteile des Event-Delegation-Modells (ab Java-Version 1.1): Nur für (Ereignis-Quell-)Objekte, bei denen ein entsprechender Listener angemeldet ist, werden Nachrichten transportiert. Damit wird eine erhöhte Performance möglich. Durch diesen Ansatz wird die Trennung des Programm-Codes für die GUI vom Programm-Code der Anwendungslogik möglich. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 119 / 210 23.03.2010 PROMOD-2a / SS10 11.2 Ereignisklassen Die Klasse der AWT-Ereignisse sind im Package java.awt.event zusammengefasst. Ereignisobjekte kapseln Informationen über das Ereignis, das die Ereignisquelle an Ihre Empfänger schickt. Bei Bedarf kann dann die betroffene Listener-Methode das Ereignisobjekt analysieren und entsprechend darauf reagieren. "Semantische Events" java.util.EventObject java.awt.AWTEvent (1) Action Event (2) AdjustementEvent (3) ItemEvent "low-level Event" (systemnahe Ereignisse / technische Events) (5) ComponentEvent (7) ContainerEvent (6) FocusEvent (10) MouseEvent (4) TextEvent InputEvent (8) WindowEvent (9) KeyEvent Semantische Events: z. B. Klicken mit der Maus auf ein Button, Betätigung einer Taste. Für Semantische Events ist die Eventsource eine AWT-Komponente. Semantische Ereignisse werden für die Übertragung "höherwertiger" Ereignisse eingesetzt. z. B. Zustandsänderung einer Komponente, Ausführung einer Aktion. Ein semantisches Ereignis wird durch den Benutzer ausgelöst, z.B. ein Schaltflächenklick oder Eintrag in ein Textfeld. Low level Events sind an konkrete graphische Objekte wie Container (und Ableitungen; siehe Bild oben „Die AWT-Klassenhierarchie“), Mouse und Key gebunden . Für Low level Ereignisse ist die Eventsource ein konkretes grafisches Objekt aus der AWT-Fenster-Klassen-Hierarchie. Ein low level Ereignis ermöglicht die semantischen Ereignisse. Die Tabelle verdeutlicht nochmals den Unterschied zwischen semantischen Events und low level Events: Semantische Auslösung durch User-Interaktion mit einer AWT-Komponente Ereignissklassen ActionEvent Schaltflächenklick, Menüauswahl, Doppelklick auf Listeneintrag, Drücken der Return-Taste in einem Textfeld. AdjustmentEvent Benutzer ändert eine Bildlaufleiste. ItemEvent Benutzer trifft eine Auswahl aus einer Gruppe von Kontrollkästchen oder Listenelementen. TextEvent Inhalt eines Textfeldes oder Textbereichs wurde geändert. Auslösung (konkrete grafische Objekte aus der AWT-Fenster-Klassen-Hierarchie) Systemnahe Ereignissklassen CompontentEvent Größenänderung einer Komponente; Komponente wurde verschoben, angezeigt oder ausgeblendet; (Basisklasse für alle systemnahen Ereignisse). ContainerEvent Eine Komponente wurde hinzugefügt oder entfernt. FocusEvent Eine Komponente hat den Focus erhalten oder verloren. WindowEvent Fensterzustand hat sich geändert. MouseEvent Maustaste wurde gedrückt oder gelöst, die Maus bewegt oder gezogen. MouseWheelEvent Mausrädchen wurde gedreht. (Im JDK1.4 hinzugekommen; Ableitung von MouseEvent) KeyEvent Eine Taste wurde gedrückt oder gelöst. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 120 / 210 23.03.2010 PROMOD-2a / SS10 11.3 Listener-Interfaces Für jede Ereignisklasse gibt es ein Listener-Interface. (Für Maus-Ereignisse gibt es zwei Listener-Interfaces.) Listener werden für das Empfangen und Behandeln der Ereignisse benutzt. In java.util gib es das Interface EventListener, von dem alle AWT-Listener abgeleitet sind. java.util.EventListener (1) Action Listener (2) AdjustmentListener (3) ItemListener (4) TextListener (5) ComponentListener Das Listener-Interface legt Methoden fest, über die die Reaktion auf das Ereignis erfolgt. Als Parameter wird diesen Methoden das entsprechende Event-Objekt übergeben. (Siehe API-Doku zu Interface EventListener aus dem Package java.util) Beispiel - Interface KeyListener: public interface KeyListener extends EventListener { public void KeyTyped(KeyEvent ev); public void KeyPressed(KeyEvent ev); public void KeyReleased(KeyEvent ev); } (6) FocusListener (7) ContainerListener (8) WindowsListener (9) KeyListener (10) MouseListener (11) MouseMotionListener 11.4 Listener bei Ereignisquellen registrieren Die Registrierung des Listeners bei der Ereignisquelle: Enthält „Event-Handler“ (A) Adapter oder (B) Interface (auch „Callback”-Methode) …und u.U. weitere Daten zum Event EreignisQuellObjekt.addEreignisListener(EreignisBehandlungsObjekt); damit der Listener für die Behandlung des Ereignisses den zuständigen "Event-Handler“ / „Callback“-Methode aufrufen kann. In der Klasse Component und allen davon abgeleiteten Klassen ist für jeden Listenertyp eine Methode definiert, mit der der Listener registriert wird. Î setzt sich zusammen aus add und Name des Listeners (Beispiel: addKeyListener()) Beispiel – Eine erste Event-Behandlung Im Beispiel unten wird in einem Java-Fenster eine Schaltfläche angeordnet. Wenn der Benutzer auf die Schaltfläche klickt, soll das Fenster und die Anwendung geschlossen werden. Auch wenn das Schließfeld des Fensters betätigt wird, soll alles geschlossen werden. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 121 / 210 23.03.2010 PROMOD-2a / SS10 Im Beispielprogramm unten sind die oben erwähnten Methoden A (Verwendung von Adapterklassen für die Ereignisbehandlung) und Methode B (Implementation eines Listener-Interfaces) implementiert. //EventFenster.java import java.awt.*; import java.awt.event.*; public class EventFenster { public static void main(String[] args) { EFenster fenster = new EFenster(); fenster.setVisible(true); } } Die Klasse EventFenster ist Eventsource und Listener class EFenster extends Frame implements MouseListener { Button btn; public EFenster() { super("Bitte klicken"); this.setSize(180,100); this.setLocation(350,300); this.setBackground(new Color(50,200,255)); this.setLayout(null); btn = new Button("schliessen"); btn.setCursor(new Cursor(Cursor.HAND_CURSOR)); btn.setBounds(40, 50, 100, 23); btn.addMouseListener(this); // Å----------Button registriert den MouseListener this.add(btn); this.addWindowListener(new WindowAdapter() //Frame registriert den WindowListener { // anonyme Klasse public void windowClosing(WindowEvent we) { ExitApp(); } }); } // Implementierung des Interfaces MouseListener ------------------------------public void mouseClicked(MouseEvent me) { ExitApp(); Modell A) Nur eine Adaptermethode (als „Callback“-Methode) zu überschreiben. } public void mouseReleased(MouseEvent me) {} Modell B) Alle Interfacemethoden (als “Callback-Methode) zu implementieren public void mousePressed(MouseEvent me) {} public void mouseEntered(MouseEvent me) {} public void mouseExited(MouseEvent me) {} public void ExitApp() // Der eigentliche Applikations-Code------------------{ // hier könnten sich weitere Anweisungen befinden, // die beim Beenden der Anwendung ausgeführt werden System.exit(0); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 122 / 210 23.03.2010 PROMOD-2a / SS10 11.5 Implementierungsmöglichkeiten: Im Beispiel oben sind zwei verschieden Implementierungstechniken für die Ereignisbehandlung zu Einsatz gekommen. Hier nochmals eine Zusammenfassung. 11.5.1 1) Verwendung von Adapterklassen: Adapterklassen implementieren ein Interface und stellen für alle Methoden leere Rümpfe zur Verfügung. Besonders geeignet, wenn ein Interface viele Methoden enthält, aber nur wenige benötigt werden. Oder umgekehrt ausgedrückt: nur in diesen Fällen sollten Adapterklassen zum Einsatz kommen, sonst wird der Code leicht unübersichtlich. 11.5.2 2) Listener Interface wird implementiert: Alle Methoden des Listeners müssen aufgeführt werden. Relativ einfach zu implementieren, zu warten und zu erweitern. Nachteil u. U. viele leere Methoden. Vorteil: potentielle Funktionalität des Interfaces ist sichtbar Î Es ist eine Trennung von GUI und Applikationslogik möglich. Dadurch entstehen kleinere übersichtlichere Klassen. Andererseits ist ein Austausch der Programmlogik möglich, ohne dabei das GUI zu verändern. 11.6 Getrennter Code für GUI und Applikation Im obigen Beispiel EventFenster.java wurde schon versucht, den Code für die GUI vom Code für die Applikation zu trennen. So richtig zufrieden stellend ist die obige Lösung jedoch nicht, da der Gesamt-Code in einer einzigen Klasse (EFenster) untergebracht ist. Unten sehen Sie die besser organisierte Lösung: // EventDelegationFenster.java import java.awt.*; import java.awt.event.*; public class EventDelegationFenster { public static void main(String[] args) { EreignisBehandlung ereignisbehandlung = new EreignisBehandlung(); EDFenster fenster = new EDFenster(ereignisbehandlung); } } //----------------- GUI ---------------------------------------------------class EDFenster extends Frame { Button btn; public EDFenster(EreignisBehandlung ereignisbehandl) // Konstruktor mit EH { super("bitte klicken"); this.setSize(180,100); this.setLocation(350,300); this.setBackground(new Color(50,200,255)); this.setVisible(true); this.setLayout(null); btn = new Button("Schliessen"); btn.setCursor(new Cursor(Cursor.HAND_CURSOR)); btn.setBounds(40, 50, 100, 23); // x, y, width, height btn.addMouseListener(ereignisbehandl); this.add(btn); this.addWindowListener(ereignisbehandl); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 123 / 210 23.03.2010 PROMOD-2a / SS10 } } //----------Application Code in den Callback-Methoden der Listener-----------class EreignisBehandlung implements WindowListener, MouseListener { public void windowClosing(WindowEvent we) // WindowEvents----------------{ ende(); } public void windowOpened(WindowEvent we) {} public void windowIconified(WindowEvent we) {} public void windowDeiconified(WindowEvent we) {} public void windowActivated(WindowEvent we) {} public void windowDeactivated(WindowEvent we) {} public void windowClosed(WindowEvent we) {} public void mouseClicked(MouseEvent me) // MouseEvents-------------------{ ende(); } public void mouseReleased(MouseEvent me) {} public void mousePressed(MouseEvent me) {} public void mouseEntered(MouseEvent me) {} public void mouseExited(MouseEvent me) {} private void ende() // der eigentliche App Code ---------------------------{ System.exit(0); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 124 / 210 23.03.2010 PROMOD-2a / SS10 11.7 Low-Level-Ereignisse (systemnahe Ereignisse): Zur Erinnerung: Ein semantisches Ereignis wird durch AWT-Komponenten (Buttons, Choice, Label, usw.) ausgelöst, z.B. ein Schaltflächenklick oder Eintrag in ein Textfeld. Ein low level Ereignis ermöglicht die semantischen Ereignisse. Eventsource ist ein grafisches Objekt aus der AWT-Fenster-Klassen-Hierarchie, also Component, Container und Ableitungen davon 11.7.1 Component-Ereignisse (die Klasse ComponentListener): Die Objekte der Klasse Component und alle davon abgeleiteten Klassen können Component-Ereignisse empfangen. Sie müssen dazu den ComponentListener registrieren. Component-Ereignisse werden ausgelöst, wenn eine Komponente (Objekt der Klasse oder Ableitung davon)… …erzeugt wird oder von dem unsichtbaren in den sichtbaren Zustand versetzt wird (Z. B. Aufruf der Methode setVisible(true)) …unsichtbar gemacht wird (z.B. setVisible(false)) verschoben wird (per Maus oder setLocation()) …in ihrer Größe verändert wird (per Maus oder setSize()) Listener-Methode: componentShown() componentHidden() componentMoved() componentResized() Den genannten Listener-Methoden wird ein ComponentEvent-Objekt als Argument übergeben. Die Klasse ComponentEvent stellt die Methode getComponent() zur Verfügung, mit der ermittelt wird, welche Komponente das Ereignis ausgelöst hat. public Component getComponent() Beispiel – Eine low level Event-Behandlung; Reaktion auf ComponentEvent-Ereignis //ComponentEventFenster.java import java.awt.*; import java.awt.event.*; public class ComponentEventFenster { public static void main(String[] args) { CEFenster fenster = new CEFenster(); } } class CEFenster extends Frame implements ComponentListener { Button btn; public CEFenster() { super("jetzt wird's bunt"); this.setSize(300,200); this.setLocation(350,300); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); this.addComponentListener(this); this.setLayout(null); btn = new Button("ausblenden"); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 125 / 210 // // // // schon bekannt Modell A nur eine Adaptermethode überschreiben // neu 23.03.2010 PROMOD-2a / SS10 btn.setSize(90, 23); btn.setLocation((this.getWidth()-90)/2, (this.getHeight()-23)/2); btn.addMouseListener(new AbgeleiteterMouseListener()); this.add(btn); // Modell C, siehe unten this.setVisible(true); } public void componentMoved(ComponentEvent ae) // Modell B; Implementierung { // des Listener-Interfaces int r = (int)(this.getBounds().getX() % 254); // von ComponentListener r = (r > 0) ? r : -r; int g = (int)((this.getBounds().getX() + this.getBounds().getY()) % 254); g = (g > 0) ? g : -g; int b = (int)(this.getBounds().getY() % 254); b = (b > 0) ? b : -b; this.setBackground(new Color(r, g, b)); } public void componentHidden(ComponentEvent ae) { System.out.println("Fenster wurde ausgeblendet"); } public void componentResized(ComponentEvent ae) { btn.setLocation((ae.getComponent().getWidth()-btn.getWidth())/2, (ae.getComponent().getHeight()-btn.getHeight())/2); } public void componentShown(ComponentEvent ae) { // alle Component System.out.println("Fenster wird angezeigt"); // Listener-Methoden } } class AbgeleiteterMouseListener extends MouseAdapter // Methode C: Adapternutzung { // ohne anonyme Klasse public void mouseClicked(MouseEvent me) { me.getComponent().getParent().setVisible(false); try { Thread.sleep(1000); }catch(Exception e){} me.getComponent().getParent().setVisible(true); } } Parent des Buttons ist der Frame. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 126 / 210 23.03.2010 PROMOD-2a / SS10 11.7.2 Window-Ereignisse (die Klasse WindowListener): Window-Ereignisse treten ein, wenn sich der Zustand des Fensters ändert. Um auf diese Änderungen zu reagieren, muss der WindowListener implementiert werden. Windows-Ereignisse werden ausgelöst, wenn ein Fenster… …“geöffnet“ wurde (z.B. durch Methode setVisible(true)) …geschlossen wird (durch Schließfeld, Menü in der Titelleiste, ALT + F4) …geschlossen wurde …minimiert wurde (z.B. durch das Minimierfeld oder durch die Methode setState(Frame.ICONIFIED)) …wiederhergestellt wurde (via Icon anklicken, durch Methode setState(Frame.NORMAL) …in den Hintergrund gestellt wird (z.B. setVisible(false) oder Methode hide()) …erstellt wurde oder wieder aktiviert wird (durch Anklicken mit der Maus oder via Methode setVisible(true)) Listener-Methode: windowOpened() windowClosing() windowClosed() windowIconified() windowDeiconified() windowDeactivated() windowActivated() Den genannten Listener-Methoden wird ein WindowEvent-Objekt als Argument übergeben. Die Klasse WindowEvent stellt die Methode getWindow() zur Verfügung, mit der ermittelt wird, welches Fenster das Ereignis ausgelöst hat. public Window getWindow() Beispiel – Eine low level Event-Behandlung; Reaktion auf WindowEvent-Ereignis // WindowEventFenster.java import java.awt.*; import java.awt.event.*; public class WindowEventFenster { public static void main(String[] args) { WEFenster fenster1 = new WEFenster(200,200); WEFenster fenster2 = new WEFenster(300,300); } } class WEFenster extends Frame implements WindowListener { Button btn; public WEFenster(int x, int y) { super("ein ganz normales Fenster"); this.setSize(300,200); this.setLocation(x, y); this.addWindowListener(this); this.setVisible(true); } public void windowOpened(WindowEvent we) { we.getWindow().setBackground(Color.magenta); } public void windowClosing(WindowEvent we) // alle Window-Listner-Methoden { we.getWindow().dispose(); } public void windowClosed(WindowEvent we) { Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 127 / 210 23.03.2010 PROMOD-2a / SS10 System.out.println("Fenster wurde geschlossen"); System.exit(0); } } public void windowIconified(WindowEvent we) { ((Frame)we.getWindow()).setTitle("bin ganz klein"); } public void windowDeiconified(WindowEvent we) { ((Frame)we.getWindow()).setTitle("ein ganz normales Fenster"); } public void windowActivated(WindowEvent we) { we.getWindow().setBackground(Color.cyan); } public void windowDeactivated(WindowEvent we) { we.getWindow().setBackground(Color.lightGray); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 128 / 210 23.03.2010 PROMOD-2a / SS10 11.7.3 Focus-Ereignisse (die Klasse FocusListener): In jeder Anwendung kann immer nur ein Fenster aktiv sein. Auf einem Fenster kann auch nur eine Komponente aktiv sein. Diese Komponente besitzt den Focus. Dies ist durch verschiedene äußere Merkmale gekennzeichnet (Umrandung mit besonderm Aussehen (OS-abhängig), Ein Textfeld hat den Focus, wenn der Cursor blinkt,…). Focus-Ereignisse treten dann ein, wenn eine Komponente den Focus erhält oder verliert. Um auf diese Änderung zu reagieren, muss der FocusListener implementiert werden. Focus-Ereignisse werden ausgelöst, wenn eine Komponente… …den Focus erhält (durch Anklicken der Komponente mit der Maus, Tab-Taste, Methode requestFocus()) …den Focus verliert (durch Anklicken anderer Komponenten, Tab-Taste, requestFocus() einer anderen Komponente) Listener-Methode: focusGained() focusLost() Den genannten Listener-Methoden wird ein FocusEvent-Objekt als Argument übergeben. Eine Komponente kann mit der Methode requestFocus() den Focus für anfordern. Dazu muss die Komponente sichtbar sein. public void requestFocus() Mit der Methode transferFocus() kann der Focus an die nächste Komponente weitergereicht werden. Die Reihenfolge hängt davon ab, in welcher Reihenfolge die Komponenten zu dem Container-Objekt hinzugefügt wurden. public void transferFocus() Soll überprüft werden, ob eine Komponente den Focus hat, steht die Methode hasFocus() zur Verfügung. public void hasFocus() Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 129 / 210 23.03.2010 PROMOD-2a / SS10 11.7.4 Input-Ereignisse: Die InputEvent-Klasse ist die Oberklasse aller Eingabeereignisse (KeyEvent und MouseEvent, MouseWheelEvent). Sie stellt Methoden zur Verfügung, mit denen überprüft werden kann, ob Funktions- oder Umschalttasten betätigt wurden: boolean boolean boolean boolean boolean isAltDown() isAltGraphDown() isControlDown() isShiftDown() isMetaDown() Alt wurde gedrückt Alt Gr wurde gedrückt Strg wurde gedrückt Umschalttaste gedrückt rechte Maus-Taste gedrückt Die Klasse InputEvent stellt auch die Methode consume() zur Verfügung. Diese Methode „konsumiert“ eine Eingabe und verhindert dadurch das Weiterleiten der Eingabe. Die Methode kann dazu verwendet werden, um Zeichen „auszufiltern“, wenn nur bestimmte Zahlen und Buchstaben bei der Komponente ankommen sollen. public void consume() Ob eine Eingabe konsumiert wurde, kann mit der Methode isConsumed() festgestellt werden. public boolean isConsumed() Wenn der Zeitabstand zwischen zwei Eingaben (z.B. zwischen zwei Mausklicks) ermittelt werden soll, kann die Methode getWhen() eingesetzt werden. Die Methode liefert für ein Ereignis den zugehörigen Zeitstempel. public long getWhen() 11.7.5 Die Eventklasse Tastaturereignisse (Klasse KeyListener): KeyEvent) und das Listener-Interface Tastaturereignisse werden ausgelöst wenn eine Taste oder Tastenkombination betätigt wird. Um auf eine bestimmte Taste zu reagieren muss das KeyListener-Interface implementiert werden. Tastatur-Ereignisse werden ausgelöst, wenn eine Taste… …betätigt und wieder losgelassen wird …betätigt wird …losgelassen wurde Listener-Methode: keyTyped() keyPressed() keyReleased() Den genannten Listener-Methoden wird ein KeyEvent-Objekt als Argument übergeben. Die Klasse KeyEvent stellt Methoden zur Verfügung, über die ermittelt werden kann, welche Taste(n) betätigt wurde(n). public char getKeyChar() public int getKeyCode() public static String getKeyText() liefert Zeichen, das der Taste entspricht Tastencode (Virtual Key Code, eine symbolische Konstante) liefert Namen der Taste als Zeichenkette z.B. "ALT" System.out.println(ke.getKeyText(ke.getKeyCode())); keyTyped(): nur Unicode-Zeichen liefern einen Wert und können über getKeyChar() abgefragt werden. keyPressed(): alle Zeichen können abgefragt werden und über getKeyCode() lassen sich Tastaturcodes abfragen. Über den Virtual Key Code können auch die betätigten Steuerungstasten ermittelt werden. Einfacher ist es jedoch in den meisten Fällen, die von der Klasse InputEvent geerbten Methoden isAltDown(), isShiftDown(), usw. zu verwenden. Virtual Key Code VK_A VK_B Prof. Illik Taste A B Virtual Key Code VK_Enter VK_F1 Taste Enter F1 PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc Virtual Key Code VK_ESCAPE VK_INSERT 130 / 210 Taste ESC EINFG 23.03.2010 PROMOD-2a / SS10 … VK_Z VK_0 VK_1 … VK_9 … Z 0 1 9 VK_F2 … VK_F12 VK_SPACE VK_END VK_HOME F2 VK_DELETE VK_TAB ENTF TABULATOR F12 Leertaste Ende POS1 Welche Virtual Key Codes es noch gibt: siehe API-Doku „Field Summery“ in class KeyEvent. Beispiel – Eine low level Event-Behandlung; Reaktion auf Tastatur-Ereignis // KeyEventFenster.java import java.awt.*; import java.awt.event.*; public class KeyEventFenster { public static void main(String[] args) { KEFenster fenster = new KEFenster(); } } class KEFenster extends Frame implements KeyListener { Label lbl1, lbl2, lbl3; public KEFenster() { super("Farbe über Tastatur auswählen"); this.setSize(280,210); this.setLocation(350,300); this.setLayout(null); lbl1 = new Label("o = orange, Alt+o = dunkelorange");//statischer Text im Fenster lbl1.setBounds(20, 50, 190, 23); lbl1.setBackground(Color.orange); // Eine Komponente lbl1.setVisible(true); this.add(lbl1); lbl2 = new Label("g = grün, Alt+g = dunkelgrün"); lbl2.setBounds(40, 100, 190, 23); lbl2.setBackground(Color.green); this.add(lbl2); lbl2.setVisible(true); lbl3 = new Label("b = blau, Alt+b = dunkelblau"); lbl3.setBounds(60, 150, 190, 23); lbl3.setBackground(Color.cyan); this.add(lbl3); lbl3.setVisible(true); this.addKeyListener(this); // neu this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); this.setVisible(true); this.requestFocus(); } public void keyPressed(KeyEvent ke) { if (ke.getKeyCode() == KeyEvent.VK_END) System.exit(0); if(ke.getKeyChar() == 'o') { if(ke.isAltDown()) ke.getComponent().setBackground(new Color(255,153,0)); else ke.getComponent().setBackground(Color.orange); } if(ke.getKeyChar() == 'g') Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 131 / 210 23.03.2010 PROMOD-2a / SS10 { } if(ke.isAltDown()) ke.getComponent().setBackground(new Color(0,153,0)); else ke.getComponent().setBackground(Color.green); } if(ke.getKeyChar() == 'b') { if(ke.isAltDown()) ke.getComponent().setBackground(new Color(0,51,255)); else ke.getComponent().setBackground(Color.cyan); } public void keyReleased(KeyEvent ke) { if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) System.exit(0); } public void keyTyped(KeyEvent ke) { } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 132 / 210 23.03.2010 PROMOD-2a / SS10 MouseEvent und die Listener-Interfaces MouseListener, MouseMotionListener : 11.7.6 Die Eventklasse Klasse Mausereignissen treten immer dann ein, wenn eine Maustaste betätigt wird oder die Maus bewegt wird (mit oder ohne gedrückter Maustaste). Um auf diese Ereignise zu reagieren, muss das MouseMotionListenerInterface implementiert werden. Die Trennung von Maus-Ereignissen und Maus-Bewegungs-Ereignissen ist sinnvoll, da in vielen Programmen nur auf Mausklicks reagiert werden soll Mausbewegungen bringen eine Flut von Ereignissen mit sich (z.B. werden während einer Fenster-Verschiebung laufend Ereignisse ausgelöst), was die Programmperformance negativ beeinflusst. Mausereignisse: Zum Erkennen von Mausereignissen muss das MouseListener-Interface implementiert werden. Maus-Ereignisse werden ausgelöst, wenn… …Maustaste betätigt wird …Maustaste losgelassen wird …Maustaste geklickt wird (betätigt und losgelassen) …Maus in den Bereich einer Komponente bewegt wird, die das Klickereignis empfängt …Maus aus dem Bereich einer Komponente bewegt wird, die das Klickereignis empfängt Listener-Methode: mousePressed() mouseReleased() mouseClicked() mouseEntered() mouseExited() Mausbewegungsereignisse: Zum Erkennen von Mausbewegungsereignissen muss das MouseMotionListener-Interface implementiert werden. Mausbewegungsereignisse werden ausgelöst, wenn… …eine Komponente mit der Maus angeklickt wurde und bei gedrückter Maustaste verschoben wird …die Maus über eine Komponente hinweg bewegt wird, ohne dass eine Maustaste betätigt wird Listener-Methode: mouseDragged() mouseMoved() Den Methoden beider Listener wird ein MouseEvent-Objekt als Argument übergeben. Die Klasse MouseEvent stellt noch folgende Methoden zur Verfügung, über die die Position des Mauszeigers beim Klicken und die Anzahl der Klicks ermittelt werden kann. Point getPoint() int getX() int getY() int getClickCount Zur Ermittlung der Mauszeigerposition Zur Ermittlung der x-Mauszeigerposition Zur Ermittlung der y-Mauszeigerposition Anzahl er Mausklicks Beispiel – Eine low level Event-Behandlung; Reaktion auf Mausevents //MouseEventFenster.java import java.awt.*; import java.awt.event.*; public class MouseEventFenster { public static void main(String[] args) { MEFenster fenster = new MEFenster(); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 133 / 210 23.03.2010 PROMOD-2a / SS10 class MEFenster extends Frame implements MouseListener, MouseMotionListener { Button btn1, btn2, btn3; private int distX, distY; public MEFenster() { super("Klick-Fenster"); this.setSize(230,200); this.setLocation(400,300); this.setLayout(null); btn1 = new Button("Hintergrundfarbe pink"); btn1.setBounds(40, 50, 150, 23); btn1.addMouseListener(this); this.add(btn1); btn2 = new Button("schiebe mich"); btn2.setBounds(40, 100, 150, 23); btn2.addMouseMotionListener(this); btn2.addMouseListener(this); this.add(btn2); btn3 = new Button("gib mir einen Doppelklick"); btn3.setBounds(40, 150, 150, 23); btn3.addMouseListener(this); this.add(btn3); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); this.setVisible(true); } public void mouseClicked(MouseEvent me) { if(me.getComponent() == btn1) { me.getComponent().getParent().setBackground(Color.magenta); } } public void mouseReleased(MouseEvent me) { if((me.getComponent() == btn3) && (me.getClickCount() == 2)) { btn3.setLabel("Klick Klick"); try { Thread.sleep(1000); } catch(Exception ex) {} btn3.setLabel("gib mir einen Doppelklick"); } } public void mouseEntered(MouseEvent me) { me.getComponent().setBackground(Color.cyan); } public void mouseExited(MouseEvent me) { me.getComponent().setBackground(Color.lightGray); } public void mousePressed(MouseEvent me) { distX = me.getX(); distY = me.getY(); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc // alle MouseListener// Methoden // alle MouseMotion- 134 / 210 23.03.2010 PROMOD-2a / SS10 // Listener-Methoden public void mouseDragged(MouseEvent me) { btn2.setLocation((int)btn2.getLocation().getX() + me.getX() - distX, (int)btn2.getLocation().getY()+ me.getY() - distY); } public void mouseMoved(MouseEvent me) {} } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 135 / 210 23.03.2010 PROMOD-2a / SS10 11.8 Zusammenfassung Ereignisse und ihre Behandlung Einige der in der Tabelle unten genannten Low-level-Ereignisse haben wir in den Beispielen oben gesehen: Ereignis (low level Ereignisse) ListenerInterfaces Ereignisklasse EventSource (konkrete grafische Objekte aus der AWT-FensterKlassen-Hierarchie) Zustands- und Größenänderung einer Komponente Hinzufügen / Enterfernen einer Component / eines Containers Erhalten / Verlieren des Focus Fensterposititon und –größe ändern Tastendruck Mausbewegung Mausklicks Mausradbewegung Methoden des Listeners ComponentLIstener ComponentEvent Component componentHidden() – Komponente wurde verborgen componentMoved() – Komponente wurde bewegt componenResized() – Größe wurde geändert componentShown() – Komponente wurde sichtbar ContainerListener ContainerEvent Container componentAdded() – Komponente wurde hinzugefügt componentRemoved() – Komponente wurde entfernt FocusListener FocusEvent Component focusGained() – Komponente hat Focus erhalten focusLost() – Komponente hat Focus verloren WindowsListener WindowEvent Window windowActivated() – Fenster wurde aktiviert windowClosed() – Fenster wurde geschlossen windowClosing() – Fenster wird gerade geschlossen windowDeactivated() – Fenster wurde deaktiviert windowDeiconified() – Fenster wurde wieder hergestellt windowIconified() – Fenster wurde auf Symbolgröße minimiert windowOpened() – Fenster wurde geöffnet KeyListener KeyEvent Component keyPressed() – Taste wurde betätigt keyReleased() – Taste wurde losgelassen keyTyped() – Taste wird betätigt und wieder losgelassen MouseMotionListener MouseMotionEvent Component mouseDraged() – Maus wird mit gedrückter Taste über eine Komponente bewegt mouseMoved() – Maus wird ohne eine gedrückte Taste über eine Komponente bewegt MouseEvent Component MouseListener, mousePressed() – Maustaste wurde betätigt mouseReleased() – Maustaste wurde losgelassen mouseClicked() – Maustaste wurde betätigt und losgelassen mouseEntered() – Maustaste wurde in den Bereich einer Komponente bewegt mouseExite() – Maustaste wurde aus dem Bereich einer Komponente bewegt MouseWheelListener MouseWheelEvent Component mouseWheelMoved() – Mausrad wurde gedreht Da für die Low-Level-Ereignisse die Event-Source i.d.R. die Klasse Component ist, können die Low-Level-Listener auf (nahezu) alle Klassen der AWT-Hierarchie angewandt werden. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 136 / 210 23.03.2010 PROMOD-2a / SS10 Semantische Ereignisse werden uns in den Folgekapiteln begegnen; hier ein Überblick: Ereignis (semantische Ereignisse) Aktion auf Komponente (Choice, Label, Button, …) Verschieben Schieberegeler oder Bildlaufleiste Zustandsänderung Textänderung ListenerInterfaces Ereignisklasse EventSource (AWT Komponenten) Methoden des Listeners - Erläuterung ActionListener ActionEvent z.B. Button, List, MenuItem, TextField actionPerformed() – Aktion wurde ausgeführt AdjustmentListener AdjustmentEvent Scrollbar adjustmentValueChanged() – Wert wurde geändert ItemListener ItemEvent Checkbox, List, CheckBoxMenuItem itemStateChanged() – Zustgand wurde geändert TextListener TextEvent TextComponent textValueChanged() – Text wurde geändert Aufgabe: Im Progjekt ADRELI_4_GUI geben Sie dem bisherigen ADRELI-Programm eine AWT-Oberfläche. Dabei sollen verschiedene, in der Spalte „EventSource“ genannten AWT-Komponenten, verwendet werden. Beschreiben Sie hier, wie Sie mit Hilfe der Java-API-Docu vorgehen, um herauszufinden, auf welche Events mit welchen Methoden reagiert werden kann, ohne in der Tabelle oben nachzusehen. Also z.B. konkret: „Welche Events sind mit einem Button (als EventSource) auslösbar und mit welchen CallBack-Methoden kann darau reagiert werden?“. Notieren Sie Ihre Vorgehensweise unten! Nochmals: es geht also darum, dass Sie wissen, wie mit Hilfe der originalen Java-API-Docu – ohne die oben gezeigte Tabelle – die notwendigen Informationen für die GUI-Interaktionen zusammen gesucht werden können! Bearbeiten Sie die Case Studies (kap09_awt_event_case_study) und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 137 / 210 23.03.2010 PROMOD-2a / SS10 12. AWT-Graphics: Grafikprogrammierung mit AWT Was Sie lernen: wie auf AWT-Komponenten gezeichnet wird wie Text ausgegeben wird wie Bilder aus Dateien angezeigt werden können Voraussetzungen: Kenntniss der Fensterprogrammierung und Ereignisbehandlung 12.1 Grundlagen Die Klasse java.awt.Graphics kapselt einen graphischen Ausgabebereich und stellt u.a. Methoden zum Zeichnen von Linien, Kreisen, Rechtecken, usw. zur Verfügung. Auch die Darstellung von Schriften und das Anzeigen von Bildern gehören in den Bereich der Graphikprogrammierung. So speichert bspw. die Klasse Graphics Zeicheneinstellungen wie die aktuelle Schriftfarbe und die Schriftart. Anmerkung: Im Zusammenhang mit der Graphik und den Komponenten kommen jetzt auch semantische Ereigniss zum Einsatz. 12.2 Der Grafikkontext / Die Klasse Graphics: Unter dem Begriff Grafikkontext werden die aktuellen Einstellungen für die Zeichenfunktionen und die Textausgabe zusammengefasst: z. B. Zeichenfarbe, Schriftart, Zeichenbereich,… Der Grafikkontext stellt Methoden zur Verfügung… − zum Einstellen und Verändern der Grafikkontext-Eigenschaften − zum Zeichnen von Linien, Bögen u. a. primitiven geometrischen Formen − zum Füllen von Flächen − zur Textausgabe − zur Anzeige von Bildern Ermittelt wird der Grafikkontext eines Fensters und anderer AWT-Komponenten mit der ComponentMethode getGraphics() public Graphics getGraphics() Die Methode getGraphics() stammt von der Klasse Component. Die Methode kann für Objekte aller von Component abgeleiteten Klassen eingesetzt werden, wie z.B. Frame, Panel und den AWTKomponenten (z.B. Labels, Buttons, …). Die Methode getGraphics() liefert ein Graphics-Objekt (als Graphikkontext) zurück. Ruft man für dieses Objekt z.B. die Zeichenmethode drawRect() auf, so wird auf die entsprechende Komponente ein Rechteck gezeichnet. Hinweis: Wird auf diese Weise eine Graphik gezeichnet (siehe Beisiel unten: hier wird eine Graphik auf einen Button gezeichnet), so wird diese selbst gezeichnete Grafik gelöscht, wenn Teile davon verdeckt werden, z.B. durch ein anderes Fenster. (Probieren Sie’s mit dem unten gelisteten Programm aus.) Gezeichnet mit Graphics.fillRect() und Graphics.fillOval(). Fenster wurde verkleinert und Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 138 / 210 23.03.2010 PROMOD-2a / SS10 dann wieder vergrößert: Beispiel – Umgang mit dem Grafikkontext import java.awt.*; import java.awt.event.*; public class GrafikKontext extends Frame implements ActionListener { Button btn; public static void main(String[] args) { GrafikKontext fenster = new GrafikKontext(); } public GrafikKontext() { super("Zeichnen auf Schaltflächen"); this.setSize(260,180); this.setLocation(100,100); this.setBackground(new Color(50,200,255)); this.setLayout(null); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); btn = new Button("Klicken Sie hier"); btn.setSize(200, 100); btn.setLocation(30, 50); this.add(btn); Der Grafikkontext btn.addActionListener(this); vom Button this.setVisible(true); } public void actionPerformed(ActionEvent ev) // wird aufgerufen nach Betätigung der { // Schaltfläche Graphics g_btn = btn.getGraphics(); g_btn.setColor(Color.blue); g_btn.fillRect(10, 10, 100, 50); g_btn.setColor(Color.red); g_btn.fillOval(70, 40, 120, 50); } } 12.3 Die Methoden paint() und repaint(): Die paint()-Methode wird in der Component-Klasse definiert: public void paint(Graphics g) Aufgerufen wird paint() immer dann, wenn die Komponente das erste Mal gezeichnet wird oder wenn ein erneutes Zeichnen erfoderlich ist. Um mit der paint()-Methode auf einer Komponente zu zeichnen, brauchen Sie eine Klasse, die von Compontent abgeleitet ist und in der die Methode paint() überschreiben wurde. Zum Beispiel kann man eine neue Klasse von Frame ableiten, deren Methode paint() überschreiben und so in das Fenster zeichnen. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 139 / 210 23.03.2010 PROMOD-2a / SS10 Im Methodenrumpf von paint() wird von den oben behandelten Graphics-Methoden setColor(), fillRect(), usw., Gebrauch gemacht. Die Methode paint() wird automatisch aufgerufen, wenn ein Fenster verborgen war und (wieder) sichtbar wird. (z.B. bei setVisible(true)) Siehe Beispiel ZeichenFenster1 unten. ÎAuch Teile einer Komponente lassen sich "wieder zeichnen"!! Siehe AWT-Doku zu den Methoden Compontent.repaint() Beispiel – Die Methode paint() import java.awt.*; import java.awt.event.*; public class ZeichenFenster1 extends Frame { public static void main(String[] args) { ZeichenFenster1 fenster = new ZeichenFenster1(); } public ZeichenFenster1() { super("Zeichenfenster 1"); this.setSize(250,140); this.setLocation(100,100); this.setBackground(new Color(50,200,255)); this.setVisible(true); this.setLayout(null); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void paint(Graphics g) { g.setColor(Color.yellow); g.fillRect(45, 55, 150, 50); g.setColor(Color.blue); g.drawString("gefülltes Rechteck",60,80); } } Die Positionierung der Graphik erfolgt ausgehend von der linken oberen Ecke (0,0) der Komponente, auf die gezeichnet wird. Die Maßeinheit ist Pixel. Jeder Zeichen- und Textausgabemethode muss die Position auf diese Weise übergeben werden. Wenn im Fenster gezeichnet wird, ist zu beachten, dass ein Fenster einen Rahmen, eine Titelleiste und u. U. eine Menüzeile besitzt. Um die wahre Größe der Fläche herauszufinden, auf die gezeichnet werden kann, steht die Methode Container.getInsets() zur Verfügung. Beispiel – Die Positionierung im Fenster import java.awt.*; import java.awt.event.*; public class ZeichenFenster2 extends Frame { public static void main(String[] args) { ZeichenFenster2 fenster = new ZeichenFenster2(); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 140 / 210 23.03.2010 PROMOD-2a / SS10 } } public ZeichenFenster2() { super("Zeichenfenster2"); this.setSize(300,140); this.setLocation(100,100); this.setBackground(new Color(50,200,255)); this.setVisible(true); this.setLayout(null); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void paint(Graphics g) { g.setColor(Color.white); g.drawRect(this.getInsets().left + 20, this.getInsets().top + 20, (int)this.getSize().getWidth() - this.getInsets().left this.getInsets().right - 40, (int)this.getSize().getHeight() - this.getInsets().top – this.getInsets().bottom - 40 ); } Die Methode getSize() stammt von Component und liefert einen Returnwert vom Typ Dimension (enthält die zwei Felder Witdth und Height). Siehe API-Doku. 12.4 Anzeige von Text: Für die Ausgabe von Text bietet die Klasse Graphics mehrere Methoden: public void drawString(String str, int x, int y); public void drawChars(char[] data, int offset, int length, int x, int y); public void drawBytes(byte[] data, int offset, int length, int x, int y); drawstring() gibt die übergebene Zeichenkette auf der Kompontente aus. Es wird nur soviel Text geschreiben, wie in den Bereich passt. Der Rest wird abgeschnitten. drawChars() und drawBytes() schreiben Zeichen (UNICODE), bzw. Bytes (ASCII) eines Arrays auf die Komponente. Der Parameter offset legt die Startposition fest. Der Prameter length gibt an, wie viele Zeichen, bzw. Bytes aus dem Array zu schreiben sind. Die Parameter x und y bezeichnen die Position der linken Seite der Grundlinie. 12.4.1 Die Klasse Font / einige Grundbegriffe Die hier kurz skizzierten Grundbegriffe sind der API-Dokumentation (Java™ Platform Standard Ed. 6) zur Klasse Font entnommen. The Font class represents fonts, which are used to render text in a visible way. A font provides the information needed to map sequences of characters to sequences of glyphs and to render sequences of glyphs on Graphics and Component objects. […] 12.4.1.1 Characters and Glyphs A character is a symbol that represents an item such as a letter, a digit, or punctuation in an abstract way. For example, 'g', LATIN SMALL LETTER G, is a character. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 141 / 210 23.03.2010 PROMOD-2a / SS10 A glyph is a shape used to render a character or a sequence of characters. In simple writing systems, such as Latin, typically one glyph represents one character. In general, however, characters and glyphs do not have one-to-one correspondence. For example, the character 'á' LATIN SMALL LETTER A WITH ACUTE, can be represented by two glyphs: one for 'a' and one for '´'. […] Font („Abbildungsvorschrift“, „digitale Umsetzung“) „Character“ (semantisches Konezpt) „Glyphe“ (graphische Darstellung eines Schriftzeichen) Bild: Zusammenhang zwischen Character, Font und Glyphe 12.4.1.2 Physical and Logical Fonts The Java Platform distinguishes between two kinds of fonts: physical fonts and logical fonts. 1. Physical fonts are the actual font libraries containing glyph data and tables to map from character sequences to glyph sequences, using a font technology such as TrueType or PostScript Type 1. All implementations of the Java Platform must support TrueType fonts; support for other font technologies is implementation dependent. […] 2. Logical fonts are the five font families defined by the Java platform which must be supported by any Java runtime environment: Serif, SansSerif, Monospaced, Dialog, and DialogInput. These logical fonts are not actual font libraries. Instead, the logical font names are mapped to physical fonts by the Java runtime environment. The mapping is implementation and usually locale dependent, so the look and the metrics provided by them vary. Typically, each logical font name maps to several physical fonts in order to cover a large range of characters. Peered AWT components, such as Label and TextField, can only use logical fonts. […] 12.4.1.3 Font Faces and Names A Font can have many faces, such as heavy, medium, oblique („kursiv“), gothic („Fraktur“) and regular. All of these faces have similar typographic design. There are three different names that you can get from a Font object. The logical font name is simply the name that was used to construct the font. The font face name, or just font name for short, is the name of a particular font face, like Helvetica Bold. The family name is the name of the font family that determines the typographic design across several faces, like Helvetica. […] Quelle: Wikipedia Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 142 / 210 23.03.2010 PROMOD-2a / SS10 12.4.2 Die Klasse Font / Schriftarten Wenn keine Schriftart festgelegt wird, so nutzt Java eine systemabhängige Standardschrift (Dialog.plain) Soll eine andere Schriftart verwendet werden, so ist ein Font-Objekt zu verwenden Dieses Font-Objekt wird dem Grafikkontext/einem Graphics-Objekt mit setFont(Font font) übergeben public abstract void setFont(Font font) Font setzten Bezüglich der Font-Eigenschaftgen (Attribute & Methoden) siehe API-Beschreibung class Font. Methoden um Font-Einstellungen zu bekommen sind im Folgenden aufgelistet. public public public public public Font getFont() String getFontName() String getFamily() int getStyle() int getSize() gibt den Font zurück gibt Font- -Namen zurück gibt Font-Family-Namen zurück gibt Schriftstil zurück (BOLD, PLAIN, ITALIC,…) gibt Schriftgröße zurück Konstruktor: public Font(String Name, int Style, int Size) Konstruktor Der erste Parameter ist der Name der Schriftart. Auf allen Systemen sollten die Schriften Dialog, DialogInput, Monospaced(Courier), Serif(Times Roman), Sans Serif(Arial) und Symbol verfügbar sein. Der zweite Parameter kennzeichnet den Schriftstil. Der Schriftstil kann BOLD (fett), PLAIN (normal) und ITALIC (kuriv) sein. Die Schriftstile sind symbolsiche Konstanten, die in der Klasse Font vereinbart sind. Es können auch zwei Schriftstile miteinander verknüft werden: z.B. Font.BOLD + Font.ITALIC. Der dritte Parameter spezifiziert die Schriftgröße. Die Schriftgröße kann durch eine Integer-Zahl angegeben werden. Die Maßeinheit ist Pixel. Beispiel – Der Umgang mit Schriftarten; Methoden der Klasse Font Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 143 / 210 23.03.2010 PROMOD-2a / SS10 import java.awt.*; import java.awt.event.*; public class ZeichenFenster3 extends Frame { public static void main(String[] args) { ZeichenFenster3 fenster = new ZeichenFenster3(); } public ZeichenFenster3() { super("Zeichenfenster3"); this.setSize(700,300); this.setLocation(100,100); this.setBackground(new Color(50,200,255)); this.setVisible(true); this.setLayout(null); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void paint(Graphics g) { g.drawString("Das ist ein Beispieltext",40,50); g.drawLine(40, 50, 170, 50); g.drawString(g.getFont().getFontName(),40,80); Font font = new Font("Monospaced", Font.BOLD, 12); g.setFont(font); g.drawString(""+g.getFont().toString(),40,110); g.drawString(""+g.getFont(),40,140); g.setFont(new Font("Serif", Font.BOLD, 12)); g.drawString(""+g.getFont(),40,170); g.setFont(new Font("SansSerif", Font.PLAIN, 12)); g.drawString(""+g.getFont(),40,200); g.setFont(new Font("Dialog", Font.ITALIC, 12)); g.drawString(""+g.getFont(),40,230); g.setFont(new Font("DialogInput", Font.BOLD + Font.ITALIC, 12)); g.drawString(""+g.getFont(),40,260); } } Welche Schriften sind verfügber? Welche Schriftarten aktuell verfügbar sind, kann über die Klasse GraphicsEnvironment abgefragt werden. Mit der Methode getLocalGraphicsEnvironment() wird ein Objekt dieser Klasse erzeugt. Die Methode getAvailableFontFamilyNames() liefert die Namen aller verfügbaren Fonts in ein String-Array zurück. public void paint(Graphics g) { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); String[] fonts = ge.getAvailableFontFamilyNames(); int anz = fonts.length; for (int i = 0; i < anz; i++) { g.setFont(new Font(fonts[i],Font.PLAIN, 10)); g.drawString(fonts[i], 20, 40 + (i * 13)); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 144 / 210 23.03.2010 PROMOD-2a / SS10 Grundaubau des Schriftsatzes (Selbstudium) Wenn ein Text an eine bestimmte Stelle in einem Fenster positioniert werden soll, wird i.d.R. die Breite, die der auszugebenden Text einnehmen wird, benötigt. Diese Information kann über die Klasse FontMetrics abgerufen werden. Um die Informationen dieser Klasse richtig nutzen zu können, muss man den Grundaufbau eines Schriftsatzes kennen. Das Bild unten nennt die wichtigsten Begriffe des Schriftsatzes. Die englischen Begriffe entsprechen den Methodennamen der Klasse FontMetrics. Mit den folgenden FontMetrics-Methoden lassen sich die Schrift-Attribute ermittlen: public public public public int int int int getHeight() getAscent() getDescent() getMaxDescent() public int getLeading() public int StringWidth(String s) public int charWidth(int char) liefert Zeilenhöhe (Gesamthöhe) liefert Oberlänge (ascent) liefert Unterlänge (descent) liefert Größe der Unterlänge zurück,die maximal von einem Zeichen erreicht werden kann liefert den Durchschuss (leading, Zeilenabstand) liefert die Breite, die der String einnimmt (mit eingestelltem Font) liefert die Zeichenbreite Beispiel – Der Umgang mit Schriftarten; Methoden der Klasse FontMetrics import java.awt.*; import java.awt.event.*; public class ZeichenFenster5 extends Frame { public static void main(String[] args) { ZeichenFenster5 fenster = new ZeichenFenster5(); } public ZeichenFenster5() { super("Zeichenfenster5"); this.setSize(350,130); this.setLocation(100,100); this.setBackground(new Color(50,200,255)); this.setVisible(true); this.setLayout(null); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 145 / 210 23.03.2010 PROMOD-2a / SS10 } }); } public void paint(Graphics g) { String s = "Dieser String steht in der Mitte des Fensters"; int breite = getSize().width - getInsets().left - getInsets().right; int hoehe = getSize().height - getInsets().bottom - getInsets().top; Font font = new Font("Serif", Font.BOLD + Font.ITALIC, 15); FontMetrics fmetrics = getFontMetrics(font); int zeilenMitte = fmetrics.getAscent() (fmetrics.getAscent() + fmetrics.getDescent()) / 2; g.setFont(font); g.setColor(SystemColor.windowText); g.drawString(s, getInsets().left + (breite - fmetrics.stringWidth(s)) / 2, getInsets().top + hoehe / 2 + zeilenMitte); } 12.5 Farben / die Klasse Color Java verwendet das RGB-Modell Die Klasse Color kapselt die Farben des RGB (Rot-Grün-Blau)-Farbmodells (Zahlenwerte von 0 – 255 (int) oder von 0.0 bis 1.0 (float)) geben Intensität des Farbanteils an) Einige Farben sind vordefiniert: z. B.: black, blue, cyan, gray, darkGray, lightGrey, magenta, orange, pink, green, red, yellow, Bei der Erzeugung eines Color-Objects wird die Farbe festgelegt: entweder über drei Integer-Werte, die in einem Wertebereich von 0 bis 255 liegen, oder durch float-Zahlen im Wertebereich zwischen 0.0 und 1.0. Color(int r, int g, int b) Î Werte 0 -255 oder aber Color(float r, float g, float b) Î Werte von 0.0 - 1.0 z.B.: Color Color Color Color Color c c c c c = = = = = new new new new new Color(255,255,255); // weiß Color(0,0,0); // schwarz Color(255,0,0); // rot Color(0,255,0); // grün Color(0,0,255); // blau Die Farbe für das nächste Grafikobjekt wird mit setColor(Color c) gesetzt public abstract void setColor(Color c) Farbe, in der das nächste Graphikobjekt gezeichnet warden soll Die Abfrage der aktuellen Zeichenfarbe erfolgt mit public abstract Color getColor() Von der Klasse Color ist die Klasse SystemColor abgeleitet und kann ebenfalls für Farbeinstellungen verwendet werden. Vorteil: Werden Systemfarben geändert, so werden auch diese Farben dynamisch angepasst: Bereich desktop activeCaption inactiveCaption activeCaptionText inactiveCaptionText window windowBorder windowText menue Prof. Illik Funktion Hintergrundfarbe des Desktops Hintergrundfarbe der Titelleiste des aktuellen Fensters Hintergrundfarbe der Titelleiste eines inaktiven Fensters Schriftfarbe des Fenstertitels des aktiven Fensters Schriftfarbe des Fenstertitels von inaktiven Fenstern Hintergrundfarbe eines Fensters Farbe des Fensterrahmens Farbe der Schrift im Fenster Hintergrundfarbe des Menüs PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 146 / 210 23.03.2010 PROMOD-2a / SS10 menueText Farbe der Schrift im Menü … Auszug der 26 definierten Systemfarben. Beispiel – Zeichnen mit Farben import java.awt.*; import java.awt.event.*; public class Farbverlauf extends Frame { int n = 1; // ganze Zahl die den Abstand // im Spektrum angibt public static void main(String[] args) { Farbverlauf fenster = new Farbverlauf(); } public Farbverlauf() { super("Farbverlauf"); this.setSize(350,130); this.setLocation(100,100); this.setVisible(true); this.setLayout(null); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void paint(Graphics g) { int rot = 255; // int blau = 255; // rgb-Kombination für Magenta int gruen = 0; // int breite = getWidth()- getInsets().left - getInsets().right; int hoehe = getHeight() - getInsets().bottom; int i = 0; while (i < breite) { while (gruen < 256 - n) //magenta - rot - gelb { i = i + 1; gruen = gruen + n; blau = blau - n; g.setColor(new Color(rot, gruen, blau)); //magenta g.drawLine(i, 0, i, hoehe); } while (blau < 256 - n) //gelb - grün - cyan { i = i + 1; blau = blau + n; rot = rot - n; g.setColor(new Color(rot, gruen, blau)); g.drawLine(i, 0, i, hoehe); } while (rot < 256 - n) //cyan - blau - magenta { i = i + 1; gruen = gruen - n; rot = rot + n; g.setColor(new Color(rot, gruen, blau)); g.drawLine(i, 0, i, hoehe); } } } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 147 / 210 23.03.2010 PROMOD-2a / SS10 12.6 Weitere Methoden zum Zeichnen aus der Klasse Graphics Die Klasse Graphics stellt Methoden zum Zeichnen einfacher geometrischer Figuren bereit. Dazu gehören Linien, Rechtecke, Polygone, Kreise und Kreisbögen. Linien: Mit einer Pixelstärke von einem Pixel. Dicke Linien entweder durch Zusammensetzung mehrerer 1-Pixel-Linien oder aber durch ein "schmales" Rechteck. Die Linie wird als Verbindung zwischen zwei Punkt-Koordinaten gezogen. Liegt ein Punkt ausserhalb des Fensters, wird die Linie am Fensterrand abgeschnitten, ohne dass dies ein Fehler wäre. Nur durchgezogene Linien. Eine einfache Möglichkeit für das Zeichnen breiter Linien oder Linien mit einem anderen Muster (gepunktet, usw.) existiert nicht. public abstact void drawLine(int x1, int y1, int x2, int y2) x1, y1 steht für den Startpunkt und x2, y2 steht für den Endpunkt. Ungefüllte Rechtecke: Es lassen sich Rechtecke ohne und mit runden Ecken darstellen. x und y kennzeichnen den linken oberen Startpunkt. width stellt die Breite und height die Höhe des Rechtecks dar. Bei Rechtecken mit runden Ecken stellen die letzten beiden Parameter den horizontalen und vertikalen Durchmesser es Eckbogens dar. public void drawRect(int x, int y, int width, int height) public abstract void drawRoundRect(int x, int y, int width, int heigt, int arcWidth, int arcHeight) Gefüllte Rechtecke: Rechtecke ohne und mit runden Ecken lassen sich auch als gefüllte Rechtecke darstellen: public abstract void fillRect(int x, int y, int width, int height) public abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) 3D Rechtecke: 3D-Rechtecke erheben sich positive nach vorne (Parameter raised = true) oder netagiv nach hinten (Parameter raised = false) aus der Ebene. public void draw3DRect(int x, raised public void fill3DRect(int x, raised int y, int width, int height, Boolean raised) == true = nach aussen int y, int width, int height, Boolean raised) == false= nach innen Polygone: Polygone sind Figuren aus mehreren Linien. Wird ein nicht geschlossenes Polygon angegeben, so wird automatisch der letzte Punkt mit dem ersten verbunden. public public public public abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) void drawPolygon(Polygon p) abstract void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) void fillPolygon(Polygon p) Polygon(int[] xPoints, int[] yPoints, int nPoints) PolyLine: Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 148 / 210 23.03.2010 PROMOD-2a / SS10 Offener Polygonzug. Der Anfangs- und Endpunkt wird nicht miteinander verbunden. public abstract void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) Kreise, Ellipsen: In AWT werden Kreise und Ellipsen über die gleichen Methoden gezeichnet, da Kreise spezielle Ellipsen sind. public abstract void drawOval(int x, int y, int width, int height) public abstract void fillOval(int x, int y, int width, int height) Die x und y-Koordinaten geben den linken oberen Eckpunkt des Rechtecks an, das den Kreis aufnimmt. Der Parameter width spezifiert die horizontale Ausdehnung und der Parameter height die vertikale Dimension. Bogen: Als Bogen wird ein Teilstück eines Kreises bzw. einer Ellipse verstanden. Beschrieben wird der Bogen wie der Kreis (auf dem er liegt) mit zusätzlicher Angabe von Start- und Endwinkel. public abstract void drawArc(int int public abstract void fillArc(int int x, int y, int width, int height, int startAngle, endAngle) x, int y, int width, int height, int startAngle, endAngle) Beispiel – Nochmals: die Klasse Graphics und der Umgang mit Zeichenmethoden import java.awt.*; import java.awt.event.*; public class ZeichenFenster6 extends Frame { public static void main(String[] args) { ZeichenFenster6 fenster = new ZeichenFenster6(); } public ZeichenFenster6() { super("Zeichenfenster 6"); this.setSize(450,140); this.setLocation(100,100); this.setVisible(true); this.setLayout(null); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void paint(Graphics g) { g.setColor(Color.yellow); g.fillRect(45, 55, 100, 70); g.setColor(Color.black); g.drawOval(45, 55, 100, 70); g.setColor(Color.yellow); g.fillRect(175, 55, 100, 70); g.setColor(Color.black); g.drawArc(175, 55, 100, 70, 0, 140); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 149 / 210 23.03.2010 PROMOD-2a / SS10 12.7 Bitmaps anzeigen: (nur gif und jpeg-Format)/ Die Klasse Image Bitmaps werden in Java durch die Klasse Image repräsentiert. Bitmaps müssen zuerst geladen werden und können dann angezeigt werden. Unterstützt werden die Grafikformate gif und jpeg. Über das JAI Java Advanced Imaging API stehen weiter Grafikformate zur Verfügung (siehe: http://java.sun.com/products/java-media/jai/index.html) Das Laden einer Bitmap kann mit Hilfe der Methode getImage() durchgeführt werden. public abstract Image getImage(String filename) // Name der Bilddatei public abstract Image getImage(URL url) // URL der Bilddatei 1. Laden: URL url = this.getClass().getResource("smile.gif") Image img = this.getToolkit().getImage(url) oder Image icon = this.getToolkit().getImage(“./smile.gif”); oder alternative: Image icon = Toolkit.getDefaultToolkit().getImage(“./smile.gif”); (Hinweis 1: die Methode getTookit() (aus der Klasse Window) liefert ein Toolkit-Objekt. Hinweis 2: Klasse Toolkit: This class is the abstract superclass of all actual implementations of the Abstract Window Toolkit. Subclasses of Toolkit are used to bind the various components to particular native toolkit implementations. … Some methods defined by Toolkit query the native operating system directly.) 2. Anzeigen: Angezeigt wird ein Bild mit der Methode drawImage() aus der Klasse Graphics. public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer) public abstract boolean drawImage(Image img,int x, int y, int width, int height ImageObserver observer) public abstract boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) drawImage() ist sehr polymorph: es gibt sechs verschiedene Parameterlisten. In jedem Fall werden den Methoden das Image-Objekt und die linke obere Ecke als Startposition für das Bild übergeben. Als letzter Parameter kann ein ImageObserver-Objekt übergeben werden, das die Informationen zum Bild enthält, z.B. den ladezustand. Dieser Parameter kann null sein. werden Breite und Höhe angegeben, so wird das Bild in diese Ausmaße hinein abgebildet. Dabei kann es vergrößert, verkleinert, verzerrt werden. Für transparente Bilder kann der Parameter bgcolor zur Bestimmung der Hintergrundfarbe angegeben werden. In bestimmten Anwendungen kann es erforderlich sein, den Ladevorgang des Bildes zu beenden, bevor im Programm weitergemacht werden kann. Dies ist z.B. notwendig, wenn die Größe des Bildes für die weiteren Positionierung benötigt wird. Diese Aufgabe übernimmt die Klasse MediaTracker. Für die Umsetzung sind die folgenden drei Schritte notwendig (siehe Beispiel class JavaTasse): 1. Über den Konstruktor wird ein Objekt der Klasse MediaTracker erzeugt. 2. Durch die Methode addImage(Image image, int id) wir das betreffende Image-Objekt dem MediaTracker bekannt gemacht. Dabei bekommt es die angegebene ID-Nummer zugewiesen. Diese ID ist frei wählbar. 3. Nach dem Aufruf der Methode getImage() wird die Methode waitForID(int id) der Klasse MediaTracker aufgerufen. Damit wird solange gewartet, bis der Ladevorgang des Images mit der angegebenen ID abgeschlossen ist. (Mit der Methode waitForAll() können mehrere Bilder auf einmal überwacht werden.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 150 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Bitmaps anzeigen import java.awt.*; import java.awt.event.*; import java.net.*; public class JavaTasse extends Frame { public static void main(String[] args) { JavaTasse fenster = new JavaTasse(); } public JavaTasse() { super("Heisse Tasse"); this.setSize(330,200); this.setLocation(100,100); this.setVisible(true); this.setLayout(null); this.setBackground(Color.yellow); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void paint(Graphics g) { URL url = null; // 1 Objekt der Klasse MediaTracker erzeugt <<<<------------------------------MediaTracker tracker = new MediaTracker(this); try { url = this.getClass().getResource("HeisseTasse.gif"); } catch(Exception ex) { System.out.println(ex.getMessage()); } Image img = getToolkit().getImage(url); // 2 Image dem Mediatracker bekannt machen <<<<-------------------------tracker.addImage(img, 1); try { // 3 warten tracker.waitForID(1); <<<<<--------------------------------------------}catch(InterruptedException ex){} g.drawImage(img, 20, 30, this); g.drawImage(img, 120, 30, 60, 140, this); g.drawImage(img, 230, 30, Color.green, this); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 151 / 210 23.03.2010 PROMOD-2a / SS10 Bearbeiten Sie die Case Studies (kap10_awt_graphics_case_studies) und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 152 / 210 23.03.2010 PROMOD-2a / SS10 13. AWT-Komponenten: Was Sie lernen: welche AWT-Komponenten es gibt wie AWT-Komponenten definiert und verwendet werden wie (Mensch-Maschinen-) Dialoge erstellt werden wie Menüs erzeugt werden Voraussetzungen: Fensterprogrammierung Grafikprogrammierung Eventhandling 13.1 Grundlagen Für die Gestaltung vonFenstern stellt AWT verschiedene Komponenten (Oberflächenelemente) bereit. java.lang.Object java.util.EventObject java.awt.AWTEvent java.awt.Component java.awt.Container ...Choice ...Label ...Button ...List …Canvas java.awt.Dialog javax.swing.JDialog java.awt.Window javax.awt.Panel java.awt.Frame javax.applet.Applet javax.swing.JFrame javax.swing.JComponent javax.swing.Label …Scrollbar …Checkbox …TextComponet java.applet.Applet javax.swing.JApplet Die Eigenschaften und Funktionsweisen einer Komponente werden jeweils in einer Klasse gekapselt. Die Klassen sind alle direkt oder indirekt von der Klasse Component abeleitet. Dadurch haben sie gemeinsame Eigenschaften, wie z.B. Größe, Position und Farbe. Die Komponenten besitzen Methoden, um Listener zu registrieren; damit können Sie Events verarbeiten. (Semantische Events.) Komponenten können direkt auf dem Fenster (i.d. R. Frame) angeordnet werden. Wenn eine Gruppierung von Komponenten gewünscht wird, können diese zusammengehörigen Komponenten in einem Panel angeordnet werden. In einem Fenster (i.d.R. Frame) lassen sich dann mehrer Panels (mit jeweils mehreren Komponenten) anordnen. Diese Vorgehensweise hat den Vorteil, dass sich alle Komponenten eines Panels mit einer Anweisung ein- und ausblenden lassen. Auch bei der Arbeit mit Layout-Managern, die eine automatische Anordnung der Komponente im Container bewirken, werden die Komponenten häufig auf Panels platziert. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 153 / 210 23.03.2010 PROMOD-2a / SS10 Container sind Fenster (Window, Frame, Dialog), Panels und Applets (siehe Bild oben). 13.2 Überblick über AWT-Komponenten: Menüleiste (Menu, MenuBar, MenuItem) Text (Label) mehzeiliges Listenfeld (List) mehrzeiliges Listenfeld (TextArea) Titelleiste Leinwand Bildlaufleiste (scrollbar) (Canvas) einzeilige Textfeld (TextField) Container (Panel) Schaltfläche (Button) Kontrollfeld (Checkbox) einzeiliges Listenfeld (choice) Überblick über die AWT-Komponenten, ihre Verwendung und die relevanten Listener: AWT-Komponente Button Canvas Label TextField Choice List TextArea Checkbox Verwendung Über Schaltflächen kann der Benutzer das Programm steuern. Durch das Betätigen der Schaltfläche wird das Programm angewiesen, bestimmte Aktionen auszuführen. API-DOCU: A Canvas component represents a blank rectangular area of the screen onto which the application can draw or from which the application can trap input events from the user. Ein Label dient der Anzeige von Text, den er Benutzer nicht ändern kann. Labels werden als Beschriftung von Textfeldern verwendet. Über Textfelder werden Benutzereingaben und Benutzerausgaben realisiert, die nur eine Zeile benötigen. Ein Listenfeld dient der Auflistung und Auswahl vorgegebener Werte (Einfach-Selektion). Die Werte können vom Benutzer weder verändert werden, noch können neue Werte hinzugefügt werden. Mehrzeilige Listenfelder dienen ebenfalls der Auflistung und Auswahl vorgegebener Werte. Eine Mehrfachselektion ist möglich. Mehrzeilige Textfelder dienen der Eingabe und Anzeige längerer Texte. Kontrollfelder (Checkboxen) eignen sich für die Eingabe bzw. die Anzeige von Ja/Nein-Werten durch Listener18 ActionListener MouseListener KeyListener, MouseListener, MouseMotionListener u.v.a.m: siehe API-Doku „add“-Methoden KeyListener MouseListener TextListener ActionListener ItemListener ItemListener ActionListener TextListener ItemListener 18 Hier sind nicht immer alle Listener aufgelistet! Im Einzelfall also in der API-Docu der AWT-Komponentenklasse nachsehen!!!! An den „add“-Methoden ist erkennbar, welche Listener zugeodnet werden können. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 154 / 210 23.03.2010 PROMOD-2a / SS10 das Setzen oder Entfernen der Markierung. Optionsfelder sind mehrere Kontrollfelder (Checkbox), die zu einer Gruppe (CheckboxGroup) zusammengefasst sind. Von den Optionsfeldern einer Gruppe kann nur jeweils eins aktiv sein, die Optionen schließen sich gegenseitig aus. Scrollbar Bildlaufleisten dienen zum Blättern (Scrollen), z.B. wenn der Inhalt eines Fensters nicht vollständig angezeigt werden kann. Der eigenliche Scrollvorgang muss jedoch manuell durchgeführt werden. Panel Panel sind Container, die andere Komponenten in sich aufnehmen können. Sie dienen der Gruppierung von Komponenten und haben eine besondere Bedeutung im Zusammenhang mit LayoutManagern. Scrollpane Diese Komponente dient er Aufnahme einer anderen Komponente, die durch ihre Größe auf dem verfügbaren Platz nicht vollständig angezeigt werden kann. Mit den beiden Bildlaufleisten lassen sich die verdeckten Bereiche der Komponente sichtbar machen. MenuBar Eine Menüleiste wird direkt unter der Titelleiste in ein Fenster eingefügt. Sie kann mehrere Menüs besitzen, die wiederum Menüeinträge und Untermenüs beinhalten. Die Menüeinträge dienen der Auswahl bestimmter Aktivitäten es Programms. PopupMenu Ein Popup-Menü (auch als Kontext-Menü bezeichnet) wird durch Klicken mit der rechten Maustaste angezeigt. Allen Komponenten und dem Fenster selbst kann ein Popup-Menü hinzugefügt werden. xListener = Low Level Ereignis; yListener = Semantisches Ereignis CheckboxGroup ItemListener AdjustmentListener ContainerListener ContainerListener beim Menüeintrag wird ActionListener registriert beimMenüeintrag wird ActionListener registriert 13.3 Anwendung / Umgang mit den Komponenten: Die typische Verwendung von AWT-Komponenten vollzieht sich in drei Schritten: 1. Objekt der Komponenten-Klasse erzeugen 2. Eigenschaften der Komponenten festlegen 3. Komponenten-Objekt zum Container (Fenster, Panel,…) hinzufügen. Dazu wird die Methode add() des Containers verwendet. Hinweis: in den folgenden Beispielen wird für die Anordnung der Komponenten kein LayoutManager verwendet. Die Komponenten werden mit der Methode setLocation() direkt an einer bestimmten Position fixiert. Es sollten Namenskonventionen beachtet werden, damit eine eindeutige Komponentenzuordnung die Lesbarkeit erleichtert. Klasse Button Label Textfield Choice List Präfix btn lbl txt choice lst Klasse Textarea Checkbox Scrollbar Panel Menue Präfix ta cb sb pnl mnu 13.3.1 Die Klasse Label Ein Label dient der Anzeige von Text, den er Benutzer nicht ändern kann. Ein Label wird für die Beschriftung von Text-, Options- und Kontrollfeldern benutzt werden. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 155 / 210 23.03.2010 PROMOD-2a / SS10 Ein Label wird über einen der folgenden drei Konstruktoren erzeugt: public Label() public Label(String text) public Label(String text, int alignment) Label ohne Beschriftung Label mit Beschriftung Label mit Beschriftung und Ausrichtung Für das Alignement stehen symbolischen Konstanten bereit: o Label.LEFT o Label.RIGHT o Label.CENTER Text und Alignment des Labels lassen sich auch dynamisch zur Laufzeit mit folgenden Methoden ändern, bzw. abfragen: o void setText(String text) o void setAlignment(int alignment) o String getText() o int getAlignment() 13.3.2 Die Klasse Button Objekte der Klasse Button repräsentieren Schaltflächen. Über Schaltflächen kann der Benutzer das Programm steuern. Durch das Betätigen der Schaltfläche wird das Programm angewiesen, bestimmte Aktionen auszuführen. Zwei Konstruktoren stehen zur Verfügung: public Button() public Button(String text) Schaltfläche ohne Beschriftung Schaltfläche mitBeschriftung Schaltflächen werden dem Benutzer angeboten, um bestimmte Aktionen auszuführen. Die Betätigung der Schaltfläche löst ein ActionEvent-Ereignis aus. Um das Ereignis auszuwerten wird wie folgt vorgegangen: 1. Implementieren Sie das Interface ActionListener. 2. Überlagern Sie die Methode actionPerformed() in der betreffenden Klasse (es ist die einzige Methode des Interfaces). 3. In der Methode actionPerformed() wird die Aktion für die jeweilige Schaltfläche implementiert. Hinweis: sind mehrer Schaltflächen in einem Container enthalten, so kann mit der Methode getActionCommand() ermittelt werden, welche der Schaltflächen das Ereignis ausgelöst hat. public String getActionCommand() void setActionCommand(String command) Liefert Zeichenkette, mit der die Schaltfläche beschriftet ist (sofern nicht mit setActionCommand() ein anderer Text als Action-Kommando gesetzt wurde). Setzt den Kommandonamen, der von getActionCommand() zurückgeliefert wird. Beispiel – Buttons und Labels nutzen import java.awt.*; import java.awt.event.*; public class ButtonFenster extends Frame implements ActionListener { Button btnOk, btnAbbrechen; Label lblUeberschrift, lblMeldung; public static void main(String[] args) { ButtonFenster fenster = new ButtonFenster(); } public ButtonFenster() { super("Betätigen Sie eine Schaltfläche"); this.setSize(250, 120); this.setLocation(100, 100); this.setBackground(Color.cyan); this.setLayout(null); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 156 / 210 23.03.2010 PROMOD-2a / SS10 lblUeberschrift = new Label("Schaltflächen-Test"); ////////// Label lblUeberschrift.setFont(new Font("Serif", Font.BOLD+Font.ITALIC, 20)); lblUeberschrift.setSize(200, 30); lblUeberschrift.setLocation(40, 25); this.add(lblUeberschrift); btnOk = new Button("OK"); ////////////////////////////// Button btnOk.setSize(80, 23); btnOk.setLocation(40, 60); this.add(btnOk); btnOk.addActionListener(this); // besitzt Methode actionPerformed() btnAbbrechen = new Button("Abbrechen"); ///////////////////// Button btnAbbrechen.setSize(80, 23); btnAbbrechen.setLocation(130, 60); this.add(btnAbbrechen); btnAbbrechen.addActionListener(this); lblMeldung = new Label("keine Schaltfläche betätigt", Label.CENTER); //// lblMeldung.setSize(170, 23); lblMeldung.setLocation(40, 85); this.add(lblMeldung); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void actionPerformed(ActionEvent ae) { if(ae.getActionCommand().equals("OK")) lblMeldung.setText("OK wurde betätigt"); else lblMeldung.setText("Abbrechen wurde betätigt"); } } Aufgabe: Schauen Sie in der API-Dokumentation für die Klasse Button nach. Neben den hier vorgestellten Button-Methoden gibt es zahlreiche weitere! Wieviel? Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 157 / 210 23.03.2010 PROMOD-2a / SS10 13.3.3 Die Klasse Textfeld (abgeleitet von TextComponent) Über Textfelder werden Benutzereingaben und Benutzerausgaben realisiert, die nur eine Zeile benötigen. Im Gegensatz zum Label kann er Benutzer die Zeichenketten ändern bzw. neue Zeichenketten eingeben. Textfelder besitzen Methoden um Zeichenketten zu setzen und abzufragen um die Editierbarkeit der Zeichenkette einzustellen (zulassen, verbieten) um die Editierbarkeit der Zeichenkette abzufragen um die Curserposition in der Zeichenkette abzufragen und zu setzen um Teile in der Zeichenkette zu markieren, bzw. die Position der Markierung abzufragen um verdeckte Eingaben zu ermöglichen (z.B. für die Passworteingabe) String getText() void setText(String t) void setEditable(boolean b) boolean isEditable() int getCaretPosition() void setCaretPosition(int position) void select(int selectionStart, int selectionEnd) void selectAll() void setSelectionStart(int selectionStart) int getSelectionEnd() void setEchoChar(char c) char getEchoChar() boolean echoCharIsSet() Zeichenkette abfragen Zeichenkette setzen Editierbarkeit erlauben, bzw. verbieten Editierbarkeit abfragen Cursorposition in der Zeichenkette abfragen Cursorposition in der Zeichenkette ändern Textmarkierung setzen gesamte Zeichenkette markieren setzt den Markierungsanfang auf die Startposition liefert die Entposition des markierten Texts setzt den für das Echo zu nutzenden Buchstaben liefert den für das Echo zu nutzenden Buchstaben testet, ob ein Echo-Zeichen gesetzt ist Wird vom Benutzer ein Zeichen in das Textfeld eingegeben, so wird ein TextEvent-Ereignis ausgelöst. o Um ein solches Ereignis abzufangen, muss ein TextListener-Interface für das Textfeld mit der Methode addTextListener() registriert werden. o Tritt das Ereignis ein, so wird die Methode textValueChanged(TextEvent event) aufgerufen Betätigt der Benutzer die Enter-Taste, so wird ein ActionEvent-Ereignis ausgelöst. Beispiel: TextField tf1, tf2, tf3, tf4; // a blank text field tf1 = new TextField(); // blank field of 20 columns tf2 = new TextField("", 20); // predefined text displayed tf3 = new TextField("Hello!"); // predefined text in 30 columns tf4 = new TextField("Hello", 30); Beispiel – TextFields nutzen import java.awt.*; import java.awt.event.*; public class EchoFenster extends Frame implements ActionListener, TextListener { TextField txtEingabe, txtEcho; Button btnLoeschen; Label lblMldg; public static void main(String[] args) { EchoFenster fenster = new EchoFenster(); } public EchoFenster() Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 158 / 210 23.03.2010 PROMOD-2a / SS10 { super("EchoFenster"); this.setSize(480, 140); this.setLocation(100, 100); this.setBackground(Color.cyan); this.setLayout(null); txtEingabe = new TextField(); ///////////////// TextField 1 txtEingabe.setSize(300, 23); txtEingabe.setLocation(40, 50); add(txtEingabe); txtEingabe.addTextListener(this); // für Reaktion auf Zeicheneingabe txtEingabe.addActionListener(this); // für Reaktion auf ENTER-Taste txtEcho = new TextField(); ///////////////// TextField 2 txtEcho.setSize(300, 23); txtEcho.setLocation(40, 100); txtEcho.setEditable(false); // damit erübrigen sich Listener... add(txtEcho); btnLoeschen = new Button("löschen"); ///////// Button btnLoeschen.setSize(100, 23); btnLoeschen.setLocation(360, 75); this.add(btnLoeschen); btnLoeschen.addActionListener(this); lblMldg = new Label(); ////////////////// Label lblMldg.setSize(200,23); lblMldg.setLocation(40, 75); add(lblMldg); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void actionPerformed(ActionEvent ae) // wird vom ActionListener gerufen { Object object = ae.getSource(); if(object.equals(btnLoeschen)) { txtEingabe.setText(""); txtEingabe.requestFocus(); } else if(object.equals(txtEingabe)) { txtEingabe.selectAll(); } } public void textValueChanged(TextEvent te) // wird vom TextListener gerufen { txtEcho.setText(txtEingabe.getText()); lblMldg.setText("aktuelle Cursorposition " + txtEingabe.getCaretPosition()); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 159 / 210 23.03.2010 PROMOD-2a / SS10 13.3.4 Die Klasse TextArea (abgeleitet von der Klasse TextComponent) Mehrzeilige Textfelder dienen der Eingabe und Anzeige längerer Texte. Mehrzeilige Textfelder sind standardmäßig mit Bildlaufleisten (wenn der Text breiter oder länger als die Textarea ist) ausgerüstet. Da die Klasse TextArea wie die Klasse TextField von der Klasse TextComponent abeleitet ist, besitzen beide Klassen gemeinsame Methoden, wie z. B. getText(), setText(), getCaretPosition(), getSelectedText() und setEditable(). TextAreas besitzen zusätzliche Methoden um Texte einzufügen und anzuhängen um Teile von Texten zu ersetzen um die Anzahl von Zeilen und Spalten zu ermitteln bzw. zu ändern um Größeninformationen zu liefern um zu ermitteln, welche Rollbalken genutzt werden Methode void insert(String str, int pos) void append(String str) void replaceRange(String str, int start, int end) int getRows(e) void setRows(int rows) int getColumns() void setColumns(int columns) Dimension getMinimumSize() Dimension getPreferredSize() int getScrollbarVisibility() Wirkung Zeichenkette str an der Position pos einfügen Zeichenkette str anhängen Zeichenkette von start bis end durch str ersetzten liefert die aktuelle Zeilenanzahl setzt die Zeilenzahl auf rows liefert die aktuelle Spaltenanzahl setzt die Spaltenzahl auf columns liefert minimale Größe liefert bevorzugte Größe ermittlet, welcher Rollbalken genutzt wird Wird vom Benutzer ein Zeichen in das Textfeld eingegeben, so wird auch hier ein TextEvent-Ereignis ausgelöst. (Durch die Betätigung der Enter-Taste wird jedoch kein ActionEvent ausgelöst wie beim TextField.) 13.3.5 Die Klasse Choice und die Klasse List: Die Klassen Choice und List repräsentieren einzeilige, bzw. mehrzeilige Listenfelder. Ein Listenfeld dient der Auflistung und Auswahl vorgegebener Werte. Die Werte können vom Benutzer weder verändert werden, noch kann der Benutzer neue Werte hinzugefügen. Der Programmierer kann Werte über die add()-Methode zur Laufzeit den Listenfeldern hinzufügen. Die Einträge werden dann in der Reihenfolge, in der sie hinzugefügt wurden, angezeigt. Eine Möglichkeit die Anzeigenreihenfolge nachträglich zu ändern gibt es nicht. Für Choice und List stehen u.a. folgende Methoden zur Verfügung: Methode Wirkung String getSelectedItem() liefert den ausgewählten Entrag int getSelectedIndex() liefert den Indes des ausgewählten Eintrags void select(int pos) programmgesteuerte Auswahl eines Eintrags void select(String str) programmgesteuerte Auswahl eines Eintrags Wird vom Benutzer ein Eintrag aus dem Listfeld ausgewählt, so wird ein ItemEvent-Ereignis ausgelöst. Um ein solches Ereignis abzufangen, muss ein ItemListener-Interface implementiert werden. Beim Eintreten des Ereignisses wird die Methode itemStateChanged() aufgerufen. Mehrzeilige Listenfelder (Klasse List) bieten mehr Funktionalität als einzeilige Listen (Klasse choice): Wahlweise kann eine Mehrfachselektion zugelassen werden (2. Parameter des Konstruktors). Elemente können dynamisch entfernt oder geändert werden (Methoden deItem(), remove(), replaceItem()). Ein ActionEvent-Ereignis wird ausgelöst, wenn ein Doppelklick auf den Eintrag erfolgt. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 160 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – TextField-, List- und Choice-Komponenten import java.awt.*; import java.awt.event.*; public class ListenFenster extends Frame implements ActionListener, ItemListener { Button btnUebernehmen; TextField txtEingabe; List lstEingaben; Choice choiceEingaben; Label lbl; public static void main(String[] args) { ListenFenster fenster = new ListenFenster(); } public ListenFenster() { super("Funktionsweise von Listenfeldern"); this.setSize(400, 160); this.setLocation(100, 100); this.setBackground(Color.cyan); this.setLayout(null); txtEingabe = new TextField(""); txtEingabe.setSize(100, 23); txtEingabe.setLocation(40, 50); add(txtEingabe); btnUebernehmen = new Button("übernehmen"); btnUebernehmen.setSize(100, 23); btnUebernehmen.setLocation(40, 90); add(btnUebernehmen); btnUebernehmen.addActionListener(this); lstEingaben = new List(); lstEingaben.setSize(100, 65); lstEingaben.setLocation(160, 50); lstEingaben.addItemListener(this); lstEingaben.addActionListener(this); add(lstEingaben); choiceEingaben = new Choice(); choiceEingaben.setSize(100, 23); choiceEingaben.setLocation(280, 50); choiceEingaben.addItemListener(this); add(choiceEingaben); lbl = new Label(""); lbl.setSize(400, 23); lbl.setLocation(40, 120); this.add(lbl); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void actionPerformed(ActionEvent ae) //wird vom ActionListener gerufen { Object object = ae.getSource(); if (object.equals(btnUebernehmen)) { lstEingaben.add(txtEingabe.getText()); choiceEingaben.add(txtEingabe.getText()); txtEingabe.setText(""); txtEingabe.requestFocus(); } else if(object.equals(lstEingaben)) { lbl.setText("Doppelklick auf: " + lstEingaben.getSelectedItem()); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 161 / 210 23.03.2010 PROMOD-2a / SS10 public void itemStateChanged(ItemEvent ie) // wird vom ItemListener gerufen { Object object = ie.getSource(); if (object.equals(choiceEingaben)) { lstEingaben.select(choiceEingaben.getSelectedIndex()); lbl.setText("aus Choice wurde ausgewählt: " + choiceEingaben.getSelectedItem()); } else if(object.equals(lstEingaben)) { choiceEingaben.select(lstEingaben.getSelectedIndex()); lbl.setText("aus List wurde ausgewählt: " + lstEingaben.getSelectedItem()); } } } 13.3.6 Die Klasse Checkbox: Kontrollfelder (Checkboxen) eignen sich für die Eingabe bzw. die Anzeige von Ja/Nein-Werten durch das Setzen oder Entfernen der Markierung. Die Darstellung OS-abhängig: z.B. ist der True-Wert unter Windows ein Haken im Kontrollfeld und unter Linux ist es ein hervorgehobenes Quadrat. Beschriftung neben dem Kontrollfeld erfolgt mit setLabel() oder wird dem Konstruktor mitgegeben: cbBuch = new Checkbox(); cbBuch.setLabel("Buch"); cbCD = new Checkbox("CD", true); Den aktuellen Zustand eines Kontrollfelds liefert die Methode boolean getState() Kontrollfelder werden einzeln ausgewertet, d.h. das Setzen ihrer Werte hat keinen Einfluss auf die Werte anderer Kontrollfelder. Durch Ändern des Wertes wird ItemEvent-Ereignis wie beim Listenfeld ausgelöst. 13.3.7 Die Klasse CheckboxGroup: Zur Realisierung von Optionsfeldern, bei denen von mehreren Werten jeweils nur einer ausgewählt werden kann. Ein Optionsfeld wird durch eine Checkbox dargestellt, die zu einer CheckBoxGroup gehört. Vorgehensweise: o Vor der Definition der Optionsfelder (Checkboxen) wird die CheckBoxGroup deklariert. o Im Konstruktor der CheckBox wird als zweiter, bzw. dritter Parameter der Name der zugehörigen CheckBoxGroup übergeben. o Der Status der einzelnen Checkboxen muss in diesem Fall angegeben werden. Beispiel: cbg = new CheckboxGroup(); cbOne = new Checkbox("one", cgb, true); cbTwo = new Checkbox("two", cbg, false); cbThree = new Checkbox("three", cbg, false); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 162 / 210 23.03.2010 PROMOD-2a / SS10 13.3.8 Die Klasse Scrollbar: Bildlaufleisten dienen zum Blättern (Scrollen), z.B. wenn der Inhalt eines Fensters nicht vollständig angezeigt werden kann. Der eigenliche Scrollvorgang muss jedoch manuell durchgeführt werden. Standardmäßig wird eine vertikale Bildlaufleiste erzeugt. Die Ausrichtung der Bildlaufleiste kann aber im Konstruktor durch die symbolischen Konstanten Scrollbar.HORIZONTAL, bzw. Scrollbar.VERTICAL geändert werden. Die Beziehung zwischen der Position des Bildlauffeldes und dem Verschieben des Fensterinhalts muss vom Programm aus gesteuert werden. Festgelegt werden muss dazu der maximale Wert es Bildlauffeldes (Methode void setMaximum(int newMax)) der minimale Wert des Bildlauffeldes (Methode void setMinimum(int newMin)) Bildlauffeld die Anfangsposition des Bildlauffeldes (Methode void setValue(int newVal)) Schaltflächen scrollbar, bubble (Die hier skizzierten Werte können auch dem Konstruktor mitgegeben werden.) Klickt der Benutzer auf einen beliebigen Teil der Scrollbar, so wird ein AdjustmentEvent-Ereignis ausgelöst. Der AdjustmentListener muss implementiert werden, wenn dieses Ereignis empfangen werden soll. Die Klasse Scrollbar stellt die Methode addAdjustmentListener zur Registrierung es Listeners bereit. Beim Auftreten des Ereignisses wird die Methode void adjustmentValueChanged(adjustmentEvent e) aufgerufen. Darin wird die Anpassung des Bildausschnitts odes des Wertes durchgeführt, für den die entsprechende Bildlaufleiste zuständig ist. Über verschiedene Methoden lässt sich herausfinden, welcher Bereich der Bildlaufleiste angeklickt wurde: eine der beiden Schaltflächen an den Enden der Bildlaufleiste oder der Bereich zwischen Schaltfläche und Bildlauffeld oder das Bildlauffeld Beispiel – Anwendung der Klasse Scrollbar import java.awt.*; import java.awt.event.*; public class ScrollFenster extends Frame implements AdjustmentListener { Scrollbar sbHor, sbVert; Panel1 pnl; int x = 0; int y = 0; public static void main(String[] args) { ScrollFenster fenster = new ScrollFenster(); } public ScrollFenster() { super("ScrollFenster"); this.setSize(200, 150); this.setLocation(100, 100); this.setBackground(Color.cyan); this.setLayout(new BorderLayout()); pnl = new Panel1(); pnl.setSize(300, 400); // Panel größer als Fenster pnl.setBackground(Color.pink); add(pnl); sbHor = new Scrollbar(Scrollbar.HORIZONTAL, 1, 25, 1, pnl.getWidth()); // s. u. sbVert = new Scrollbar(Scrollbar.VERTICAL, 1, 70, 1, pnl.getHeight()); // s. u. sbHor.addAdjustmentListener(this); sbVert.addAdjustmentListener(this); add("South", sbHor); add("East", sbVert); this.setVisible(true); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 163 / 210 23.03.2010 PROMOD-2a / SS10 this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } } public void adjustmentValueChanged(AdjustmentEvent ae) // wird vom AdjstmentListener { // gerufen int i = 1; Object a = ae.getSource(); if(a.equals(sbHor)) { i = sbHor.getValue() - x; pnl.setSize((int) (pnl.getWidth() + i), pnl.getSize().height); pnl.setLocation((int) - sbHor.getValue() + this.getInsets().left, (int) pnl.getLocation().getY()); x = sbHor.getValue(); } else { i = sbVert.getValue() - y; pnl.setSize(pnl.getSize().width, (int) (pnl.getHeight() + i)); pnl.setLocation((int) pnl.getLocation().getX(), (int) - sbVert.getValue() + this.getInsets().top); y = sbVert.getValue(); } } class Panel1 extends Panel { public Panel1(LayoutManager layout) { super(layout); } public Panel1() { super(); } public void paint(Graphics g) { g.setColor(Color.yellow); g.fillRect(25,35,200,300); g.setColor(Color.blue); g.fillOval(70,70,130,200); } } Erläuterung zum Konstruktor: public Scrollbar(int orientation, int value, int visible, int minimum, int maximum) throws HeadlessException Parameters: orientation - indicates the orientation of the scroll bar. (Orientierung) value - the initial value of the scroll bar (Startposition) visible - the visible amount of the scroll bar, typically represented by the size of the bubble (Größe) minimum - the minimum value of the scroll bar maximum - the maximum value of the scroll bar Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 164 / 210 23.03.2010 PROMOD-2a / SS10 13.3.9 Die Klasse Scrollpane Die Klasse Scrollpane ist einem Panel ähnlich, kann aber nur eine Komponente aufnehmen. Besitzt standardmäßig zwei Bildlaufleisten. Diese Komponente wird eingesetzt, wenn eine andere Komponente auf Grund ihrer Größe auf dem verfügbaren Platz nicht vollständig angezeigt werden kann. 13.4 Dialoge Ein "Dialog" ist ein Top-Level-Fenster Liefert Meldung oder Frage an den User und wartet auf dessen Reaktion 13.4.1 Die Klasse Dialog: Ein Dialog ist i.d.R. ein "modales" Fenster: ein anderes Fenster der Anwendung kann nicht aktiv werden, bis das Dialogfenster wieder geschlossen wird. Dialog-Fenster ist einem Owner-Fenster/Vater-Fenster (hierarchisch übergeordnetes Fenster) zugeordnet o Dialog(Frame owner, boolean modal) o Dialog(Frame owner, String title, boolean modal) Die Klasse Dialog wird i.d.R. für Meldungen oder zum Einstellen von Optionen eingesetzt. Üblicherweise können diese Dialoge nicht in ihrer Größe verändert werden. Über die Methode void setResizable(boolean resizable) kann die Größenänderung unterbunden werden. Um ein Dialogfenster zu erzeugen, muss eine neue Klasse erstellt weren, die von der Klasse Dialog abgeleitet wird. Die Komponenten, die in dem Dialogfenster angezeigt werden sollen, müssen vom Programm selbst positioniert werden. Das gleiche gilt für die Reaktionen auf die Betätigung dieser Komponenten. Beispiel – Anwendung der Klasse Dialog import java.awt.*; import java.awt.event.*; public class DialogFenster extends Frame implements ActionListener { Button btnDlg; public static void main(String[] args) { DialogFenster fenster = new DialogFenster(); } public DialogFenster() { super("DialogFenster"); this.setSize(200, 130); this.setLocation(100, 100); this.setBackground(Color.lightGray); this.setLayout(null); btnDlg = new Button("Beenden?"); btnDlg.setSize(100, 23); btnDlg.setLocation(50, 60); this.add(btnDlg); btnDlg.addActionListener(this); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 165 / 210 23.03.2010 PROMOD-2a / SS10 } }); public void actionPerformed(ActionEvent ae) // wird vom ActionListener gerufen { MeinDialog dlg = new MeinDialog(this, "Ende-Dialog", "Wollen Sie die Anwendung wirklich beenden?"); if(dlg.getAntwort()) System.exit(0); } } import java.awt.*; import java.awt.event.*; public class MeinDialog extends Dialog implements ActionListener { Button btnOK, btnAbbrechen; private boolean antwort = false; public MeinDialog(Frame owner, String titel, String mldg) { super(owner, titel, true); this.setSize(300, 130); this.setLocation(200, 150); this.setResizable(false); this.setLayout(null); Label lblMldg = new Label(mldg); lblMldg.setSize(260, 25); lblMldg.setLocation(20, 40); add(lblMldg); btnOK = new Button("OK"); btnOK.setSize(70, 25); btnOK.setLocation(70, 80); this.add(btnOK); btnOK.addActionListener(this); btnAbbrechen = new Button("Abbrechen"); btnAbbrechen.setSize(70, 25); btnAbbrechen.setLocation(160, 80); this.add(btnAbbrechen); btnAbbrechen.addActionListener(this); this.setVisible(true); } public void actionPerformed(ActionEvent ae) { Object object = ae.getSource(); if(object.equals(btnOK)) antwort = true; setVisible(false); dispose(); } public boolean getAntwort() { return antwort; } // wird vom ActionListener gerufen } 13.4.2 Der Dateidialog (Klasse FileDialog) Im Package java.awt steht ein vordefinierter Dialog zur Verfügung, der die Auswahl von Dateinamen und Verzeichnissen ermöglicht, um Dateien zu lesen oder zu schreiben. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 166 / 210 23.03.2010 PROMOD-2a / SS10 Die Klasse FileDialog ist von der Klasse Dialog abgeleitet. Erzeugt wird der Dataiedialog mit einem von drei zur Verfügung stehenden Konstruktoren. FileDialog (Frame parent) Dialogfenster zum Dateiladen FileDialog(Frame parent, String title) Dialogfenster zum Dateiladen mit Titel FileDialog(Frame parent, String title, int mode) Dialogfenster mit Titel zum Laden oder Speichern (FileDialog.LOAD, FileDialog.SAVE) Durch show() wird der Dateidialog angzeigt: 1. FileDialog dlg = new FileDialog(this,"Öffnen",FileDialog.LOAD); 2. dlg.show(); oder 3. FileDialog dlg = new FileDialog(this,"Speichern",FileDialog.SAVE); 4. dlg.show(); Über die folgenden Methoden kann auf den Namen der Datei, bzw. des Verzeichnisses zugegriffen werden. (Achtung! Die Dateiverwaltung ist stark OS-spezifisch: Aufbau von Dateinamen, Aufbau der Verzeichnisstruktur, u.a.m.) public String getDirectory() Liefert das ausgewählte Verzeichnis als String zurück. public void setDirectroy(String dir) Setzt das Verzeichnis auf den als String mitgegebenen Namen. public String getFile() Liefert den ausgewählten Dateinamen als String zurück. public void setFile(String file) Setzt den Dateinamen auf den als String mitgegebenen Namen. Vor Aufruf des Dialogs kann über die set-Methoden ein Verzeichnis-Name und ein File-Name voreingestellt werden. Die get-Methoden werden nach Beendigung des Dialogs aufgerufen und liefern den ausgewählten Verzeichnisnamen und den Dateinamen. Das tatsächliche Öffnen, Lesen und Schreiben muss dann im Programm noch vorgenommen werden. Beispiel: Siehe weiter unten MenueFenster.java Beispiel – Anwendung der Klasse FileDialog import java.awt.*; import java.awt.event.*; public class OpenDialogFenster extends Frame implements ActionListener { Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 167 / 210 23.03.2010 PROMOD-2a / SS10 } Button btnOeffnen; Label lbl; public static void main(String[] args) { OpenDialogFenster fenster = new OpenDialogFenster(); } public OpenDialogFenster() { super("DialogFenster"); this.setSize(400, 130); this.setLocation(100, 100); this.setBackground(Color.lightGray); this.setLayout(null); btnOeffnen = new Button("Öffnen"); btnOeffnen.setSize(100, 23); btnOeffnen.setLocation(40, 60); this.add(btnOeffnen); btnOeffnen.addActionListener(this); lbl = new Label(); lbl.setSize(360, 23); lbl.setLocation(20, 90); this.add(lbl); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void actionPerformed(ActionEvent ae) { FileDialog dlg = new FileDialog(this, "Öffnen", FileDialog.LOAD); dlg.setVisible(true); lbl.setText("Datei " + dlg.getDirectory() + dlg.getFile() + " ausgewählt"); } 13.5 Menüs: Menüs sollen die Funktionalität der Anwendung übersichtlich und sinnvoll gruppieren. Menüs bestehen aus einer Menüleiste, die bestimmte Menüpunkte (Menüs) enthält. Menüs werden durch Anklicken geöffnet und enthalten wiederum Menüeinträge und Untermenüs. Einzelne Bestandteile von Menüs sind Klassen Menü (Menu) Menüleiste (MenuBar) Menüeintrag (MenuItem) (CheckboxMenuItem) Trennstrich (Separator) (Menu.addSeparator()) Tastenkombinationen (MenuShortcut) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 168 / 210 23.03.2010 PROMOD-2a / SS10 1. 2. 3. 4. 5. Die Erstellung eines Menüs erfolgt in folgenden Schritten Menüleiste erstellen und dem Fenster hinzufügen Dann ein Menü erstellen und der Menüleiste hinzufügen Dann Menüeintrag erzeugen un dem Menü hinzufügen Schritt 3 so lange wiederholen, bis alle Menüeinträge für ein Menü erstellt sind ab Schritt 2 so lange wiederholen, bis alle Menüs (Sub- und Popup-Menüs) erstellt sind 13.5.1 Die Menüleisten (Klasse MenuBar) Die Menüleiste wird über den parameterlosen Konstruktor der Klasse MenuBar erzeugt: Konstruktor: MenuBar mnuBar = new MenuBar(); this.setMenuBar(mnuBar); // Methode der Klasse Frame Wird automatisch unter der Titelleiste positioniert Die Menüleiste ist zunächst leer Menüs hinzufügen Æ add() Menüs wegnehmen Æ remove() Anzeige in der Reihenfolge des Hinzufügens 13.5.2 Die Menüs (Klasse Menu) Ein Menü gruppiert ein oder mehrere Menüeinträge. Dem Konstruktor wird die Beschriftung des Menüs als Parameter mitgegeben: Menu mnu = new Menu("Datei"); mnuBar.add(mnu); Durch die Methode add() der Klasse MenuBar wird das Menü zur Menüleiste hinzugefügt. Das Menü ist zunächst leer. Menüeinträge hinzufügen Æ add(), oder Æ insert() Menüeinträge wegnehmen Æ remove() Trennstriche zwischen den Menüpunkten werden mit addSeparator() oder insertSeparator() eingefügt. Anmerkung: durch add() wird die Reihenfolge der Menüeinträge definiert. Durch insert() kann ein Menüeintrag an bestimmter Indexposition eingefügt werden. (Erster Menüeintrag hat den Index 0.) 13.5.3 Die Menü-Einträge (Klasse MenueItem) Menüeinträge erzeugen Die Beschriftung eines Menüeitrags wird dem Konstruktor als Parameter übergeben: MenuItem mi = new MenuItem("Öffnen"); mnu.add(mi); Als zweiter Parameter im Konstruktor kann eine Tastenkombination (als Shortcut zum Aktiviern des Menüpunkts) übergeben werden. o Diese Tastenkombination wird in Form eines Objekts der Klasse MenuShortcut definiert. o Erzeugt wird ein solches Objekt durch den Aufruf des Konstruktors der Klasse MenuShortcut. o Als Parameter wird der Code der Taste übergeben, die in Verbindung mit der STRG-Taste den Shortcut ergibt. o Für den Tastencode können die symbolischen Konstanten der Klasse KeyEvent verwendet werden („virtual key code“). (Java lässt für Shortcats nur alphanumerische Tasten zu.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 169 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel: MenuItem mi = new MenuItem(„Öffnen“, new MenuShortcut(KeyEvent.VK_O)); Wählt er Benutzer einen Menüeintrag aus, so wird ein ActionEvent-Ereignis ausgelöst. Soll das Ereignis für den Menüeintrag verarbeitet werden, so muss der ActionListener registriert werden. o mi.addActionListener(this); o In der Methode actionPerformed() erfolgt die Verarbeitung aller ActionEvent-Ereignisse, die auch durch eine Schaltfläche oder ein Listenfeld ausgelöst werden können. o Es gilt zunächst herauszufinden, welches Objekt das Ereignis ausgelöst hat. o Die Methode getLabel() liefert die Beschriftung des Menüeintrags. o Besser: alternativ liefert die Methode getSource() die Ereignisquelle. Beispiel: public void actionPerformed(ActionEvent ae) { Object o = actionevent.getSource(); if (((MenuItem)o).getLabel() == “Öffnen”) . . . Menüeinträge besitzen die Eigenschaft enabled, diese Eigenschaft gibt an, ob der Menüeintrag ausgewählt werden darf oder nicht. Inaktive Menüeinträge werden grau dargestellt und lassen sich nicht aktivieren. Über die Methoden setEnabled() bzw. isEnabled() kann der Wert gesetzt bzw. abgefragt werden. o mi.setEnabled(false); Menüeinträge markieren Markierungen in Menüeinträgen werden für das Setzen von Optionen verwendet. Ist die Menüoption gesetzt,so ist der Menüeintrag links neben der Beschriftung durch eine (OS-abhänige) Markierung gekennzeichnet (Häckchen, erhabenes Quadrat, …) Zur Erzeugung optionaler Menüeinträge wird die Klase checkboxMenuItem verwendet (abgeleitet von MenuItem) Die Klasse hat zusätzliche Methoden, um die Statusinformationen zu verarbeiten: void setState(boolean b) Setzt den Status der Option auf den übergebenen Wert b. boolean getState() Liefert den aktuellen Status zurück. Wird ein optonaler Menüeintrag ausgewählt, so wird ein ItemEvent-Ereignis ausgelöst (wie bei Checkboxen oder Listenfeldern). o Ist die Verarbeitung des Ereignisses notwendig, so muss der ItemEvent-Listener beim Menüeintrag registriert werden. Untermenüs Für die Erzeugung des Untermenüs wird dem Menü statt eines Menüeintags ein weiteres Menü hinzugefügt. An diese Menü werden dann die Menüeinträge angehängt. (Siehe Beispiel unten.) mnuBar = new MenuBar(); this.setMenuBar(mnuBar); // Menüleiste erzeugen // und zum Fenster hinzufügen mnu = new Menu("Grafik"); // Menü „Graphik“ erzeugen mnuBar.add(mnu); // und zur Menüleiste hinzufügen mnu2 = new Menu("Farbe"); // Unter-Menü “Farbe” erzeugen mnu.add(mnu2); // und zum Menü Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 170 / 210 23.03.2010 PROMOD-2a / SS10 mi = new MenuItem("rot"); // 1. Menüeintrag für das Unter-Menü “Farbe” erzeugen mnu2.add(mi); // und zum Unter-Menü hinzufügen Beispiel – Anwendung von Menüs import java.awt.*; import java.awt.event.*; import java.io.*; public class MenueFenster extends Frame implements ActionListener, ItemListener { Label lbl; boolean autosave = true; public static void main(String[] args) { MenueFenster fenster = new MenueFenster(); } public MenueFenster() { super("Fenster mit Menü"); this.setSize(400, 130); this.setLocation(100, 100); this.setLayout(null); MenuBar mnuBar = new MenuBar(); this.setMenuBar(mnuBar); Menu mnu = new Menu("Datei"); //----- erstes Meneue ------------------mnuBar.add(mnu); MenuItem mnuItem = new MenuItem("Öffnen", new MenuShortcut(KeyEvent.VK_O)); mnuItem.addActionListener(this); mnu.add(mnuItem); mnuItem = new MenuItem("Speichern", new MenuShortcut(KeyEvent.VK_S)); mnuItem.addActionListener(this); mnu.add(mnuItem); mnu.addSeparator(); CheckboxMenuItem cbmnuItem = new CheckboxMenuItem("automatisch Speichern", autosave); cbmnuItem.addItemListener(this); mnu.add(cbmnuItem); mnu.addSeparator(); mnuItem = new MenuItem("Beenden"); mnuItem.addActionListener(this); mnu.add(mnuItem); mnu = new Menu("Info"); //---------- zweites Menue ------------------mnuBar.add(mnu); mnuItem = new MenuItem("Info", new MenuShortcut(KeyEvent.VK_I)); mnuItem.addActionListener(this); mnu.add(mnuItem); cbmnuItem = new CheckboxMenuItem("Hintergrund weiß", true); cbmnuItem.addItemListener(this); mnu.add(cbmnuItem); lbl = new Label(); lbl.setSize(360, 23); lbl.setLocation(20, 90); this.add(lbl); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void actionPerformed(ActionEvent ae) { Object o = ae.getSource(); if(((MenuItem)o).getLabel().equals("Öffnen")) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 171 / 210 23.03.2010 PROMOD-2a / SS10 { FileDialog dlg = new FileDialog(this, "Datei öffnen", FileDialog.LOAD); dlg.setVisible(true); lbl.setText("Datei " + dlg.getDirectory() + dlg.getFile() + " ausgewählt"); } else if(((MenuItem)o).getLabel().equals("Speichern")) { FileDialog dlg = new FileDialog(this, "Datei speichern unter", FileDialog.SAVE); dlg.show(); lbl.setText("speichen unter " + dlg.getDirectory() + dlg.getFile()); } else if(((MenuItem)o).getLabel().equals("Beenden")) { MeinDialog dlg = new MeinDialog(this, "Ende-Dialog", "Wollen Sie die Anwendung wirklich beenden?"); if(dlg.getAntwort()) System.exit(0); } else if(((MenuItem)o).getLabel().equals("Info")) { MeinDialog dlg = new MeinDialog(this, "Info", "Dieses Programm enthält ein Menü"); } } } public void itemStateChanged(ItemEvent ie) { CheckboxMenuItem cmi = (CheckboxMenuItem) ie.getSource(); if(cmi.getLabel().equals("Hintergrund weiß")) if (cmi.getState()) setBackground(Color.white); else setBackground(Color.lightGray); else if(cmi.getLabel().equals("automatisch Speichern")) autosave = cmi.getState(); } Zugehörige Klasse MeinDialog Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 172 / 210 23.03.2010 PROMOD-2a / SS10 import java.awt.*; import java.awt.event.*; public class MeinDialog extends Dialog implements ActionListener { Button btnOK, btnAbbrechen; private boolean antwort = false; public MeinDialog(Frame owner, String titel, String mldg) { super(owner, titel, true); this.setSize(300, 130); this.setLocation(200, 150); this.setResizable(false); this.setLayout(null); Label lblMldg = new Label(mldg); lblMldg.setSize(260, 25); lblMldg.setLocation(20, 40); add(lblMldg); btnOK = new Button("OK"); btnOK.setSize(70, 25); btnOK.setLocation(70, 80); this.add(btnOK); btnOK.addActionListener(this); btnAbbrechen = new Button("Abbrechen"); btnAbbrechen.setSize(70, 25); btnAbbrechen.setLocation(160, 80); this.add(btnAbbrechen); btnAbbrechen.addActionListener(this); this.setVisible(true); } public void actionPerformed(ActionEvent ae) { Object object = ae.getSource(); if(object.equals(btnOK)) antwort = true; setVisible(false); dispose(); } public boolean getAntwort() { return antwort; } } 13.5.4 Kontextmenüs (auch Popup-Menüs genannt) Kontextmenüs erscheinen, wenn mit der rechten Maustaste auf eine Komponente bzw. ein Fenster geklickt wird. (Achtung: in Abhängigkeit vom Betriebssystem kann der „popUpTrigger“ auch die mittlere Maustaste sein!) Erzeugt wird ein Kontextmenü durch die Klasse PopupMenu, die von der Klasse Menu abgeleitet ist. Es sind für die Kontext-Menüerstellung die gleichen Schritte notwendig, wie für die Erstellung eines Menüs (ohne Menüleiste). Die Erstellung eines Kontextmenüs erfolgt in folgenden Schritten 1. Die Komponente, an die das Kontextmenü durch die Methode add() gebunden ist, muss Mausereignisse empfangen können. Dazu wird die Methode enableEvents() aufgerufen. Siehe unten 1) 2. Die Component-Methode processMouseEvent() muss überschrieben werden. Siehe unten 2) 3. Durch die MouseEvent-Methode isPopUpTrigger() muss abgefragt werden, ob es sich um das Ereignis handelt, welches zum Anzeigen des Kontextmenüs führen soll. Siehe unten 3) 4. Das Kontextmenü wird durch den Aufruf der Methode show() angezeigt. Siehe unten 4) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 173 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Anwendung von Kontextmenüs import java.awt.*; import java.awt.event.*; import java.io.*; public class KontextmenueFenster extends Frame implements ActionListener, ItemListener { PopupMenu pmnu; boolean autosave = true; Label lbl; public static void main(String[] args) { KontextmenueFenster fenster = new KontextmenueFenster(); } public KontextmenueFenster() { super("Fenster mit Kontextmenü"); this.setSize(400, 130); this.setLocation(100, 100); this.setLayout(null); pmnu = new PopupMenu(); this.add(pmnu); MenuItem mnuItem = new MenuItem("Datei Öffnen"); mnuItem.addActionListener(this); pmnu.add(mnuItem); mnuItem = new MenuItem("Datei Speichern"); mnuItem.addActionListener(this); pmnu.add(mnuItem); pmnu.addSeparator(); CheckboxMenuItem cbmnuItem = new CheckboxMenuItem("automatisch Speichern", autosave); cbmnuItem.addItemListener(this); pmnu.add(cbmnuItem); pmnu.addSeparator(); mnuItem = new MenuItem("Beenden"); mnuItem.addActionListener(this); pmnu.add(mnuItem); lbl = new Label(); lbl.setSize(360, 23); lbl.setLocation(20, 90); add(lbl); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); this.enableEvents(AWTEvent.MOUSE_EVENT_MASK); // 1) Siehe oben die 4 Schritte } public void processMouseEvent(MouseEvent me) { if(me.isPopupTrigger()) pmnu.show(me.getComponent(), me.getX(), me.getY()); // 2) // 3) // 4) } public void actionPerformed(ActionEvent ae) { Object o = ae.getSource(); if(((MenuItem)o).getLabel().equals("Datei Öffnen")) { FileDialog dlg = new FileDialog(this, "Datei öffnen", FileDialog.LOAD); dlg.setVisible(true); lbl.setText("Datei " + dlg.getDirectory() + dlg.getFile() + " ausgewählt"); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 174 / 210 23.03.2010 PROMOD-2a / SS10 else if(((MenuItem)o).getLabel().equals("Datei Speichern")) { FileDialog dlg = new FileDialog(this, "Datei speichern unter", FileDialog.SAVE); dlg.setVisible(true); lbl.setText("speichen unter " + dlg.getDirectory() + dlg.getFile()); } else if(((MenuItem)o).getLabel().equals("Beenden")) { MeinDialog dlg = new MeinDialog(this, "Ende-Dialog", "Wollen Sie die Anwendung wirklich beenden?"); if(dlg.getAntwort()) System.exit(0); } else if(((MenuItem)o).getLabel().equals("Info")) { MeinDialog dlg = new MeinDialog(this, "Info", "Dieses Programm enthält ein Menü"); } } } public void itemStateChanged(ItemEvent ie) { CheckboxMenuItem cmi = (CheckboxMenuItem) ie.getSource(); if(cmi.getLabel().equals("automatisch Speichern")) autosave = cmi.getState(); } Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 175 / 210 23.03.2010 PROMOD-2a / SS10 14. LayoutManager (ein Überblick) Was Sie lernen: welche Aufgaben LayoutManager erfüllen welche LayoutManager es gibt wie LayoutManager eingesetzt werden Voraussetzungen: Fensterprogrammierung Umgang mit AWT-Komponenten 14.1 Grundlagen Die Komponenten von AWT bzw. von Swing können durch direkte Angabe der Position und Größe im Fenster platziert warden (so genanntes Null-Layout). Das Null-Layout ist sinnvoll, wenn das Programm nur unter einem Betriebssystem laufen soll oder eine exakte Positionierung erforderlich ist. Bei einem Einsatz unter anderem Betriebssystem können sich Verschiebungen und Größenänderungen ergeben. Aus diesem Grund wurde in Java für die Anordnung der Komponenten in einem Fenster bzw. Container ein besonderes Konzept entwickelt: die LayoutManager. LayoutManager haben folgende Eigenschaften: o Man kann jeder Container-Komponente einen LayoutManager zuordnen. o Die Positionierung und Festlegung der Größe der Komponente erfolgt über den LayoutManager auf allen Plattformen in der gleichen Art und Weise. Die Größe der Komponenten wird vom LayoutManager festgelegt. Einige LayoutManager greifen auf Eigenschaften der Komponenten zurück (über die Component-Methoden getPreferedSize(), getMinimumSize(), getMaximumSize()). Die Position und Größe der Komponenten wird ggf. an die Fenstergröße angepasst, wenn diese verändert wird. Container können mehrfach verschachtelt werden. Da jeder Container einen eigenen LayoutManager besitzt, ist somit eine vielfältige Oberflächengestaltung möglich. Um LayoutManager einzusetzten, muss das Packet java.awt.* importiert werden. Hinweis: In manchen Programmierumgebungen (z. B. JBuilder) stehen noch weitere LayoutManager zur Verfügung. Hierbei handelt es sich um proprietäre LayoutManger, die nicht zum Sprachumfang gehören. Layout-Manager FlowLayout (Für Panels und der davon abgeleiteten Klasse Applet ist das FlowLayout voreingestellt.) BorderLayout (Für Fenster (Frame, Window) ist das BorderLayout voreingestellt.) Prof. Illik Erläuterung Ordnet die hinzugefügten Komponenten nebeneinander in ihrer bevorzugten Größe (getPreferedSize()) an. Passt eine neue Komponente nicht mehr in eine Zeile, sow wird eine neue Zeile begonnen. Ordnet die hinzugefügte Komponente an den Außenseiten und in der Mitte des Containers an. Dadurch können maximal fünf Komponenten platziert werden. Beispiel PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 176 / 210 23.03.2010 PROMOD-2a / SS10 GridLayout Ordnet die hinzugefügten Komponenten in einem Gitter mit vorgegebener Anzahl von Spalten und Zeilen an. GridBagLayout Ordnet die hinzugefügten Komponenten in einem Gitter an. Eine Komponente kann mehrere Zellen eines Gitters einnehmen. Das Gitter passt sich ggf. an. CardLayout BoxLayout Die eigentliche Ausrichtigung der Komponenten erfolgt über das GridBagConstraints Objekt, welches beim Hinzufügen zum Container anzugeben ist. Ordnet die hinzugefügten Komponenten übereinander in Form eines Stapels an. Alle unteren Komponenten werden durch die oberste verdeckt. Über Methoden der Klasse kann jeweils die nächste oder vorherige Komponente angezeigt werden. Ordnet die hinzugefügten Komponenten wahlweise in einer Zeile oder Spalte im Container an. Die voreingestellte maximale Größe der Komponentewird nicht geändert. OverlayLayout Dieser Manager wurde für Swing entwickelt, kann aber auch im AWT eingesetzt werden. Dazu muss das Package javax.swing.* importiert werden. Nur für Swing. SpringLayout Nur für Swing. Damit können mehrere Componenten übereinander angeordnet werden. Durchscheinend. … added in JDK version 1.4 to support layout in GUI builders….very low-level and as such you really should only use it with a GUI builder…. Siehe auch: http://java.sun.com/docs/books/tutorial/uiswing/layout/ Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 177 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Das FlowLayout import java.awt.*; import java.awt.event.*; public class FlowLayoutFenster extends Frame { Button btn1, btn2, btn3; } public static void main(String[] args) { FlowLayoutFenster fenster = new FlowLayoutFenster(); } public FlowLayoutFenster() { super("Anordnung im FlowLayout"); this.setSize(250, 80); this.setLocation(100, 100); this.setBackground(Color.cyan); this.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10)); btn1 = new Button("Nr. 1"); this.add(btn1); btn2 = new Button("Nr. 2"); this.add(btn2); btn3 = new Button("Nr. 3"); this.add(btn3); pack(); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 178 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Das BorderLayout import java.awt.*; import java.awt.event.*; public class BorderLayoutFenster extends Frame { Button btn1, btn2, btn3, btn4, btn5; public static void main(String[] args) { BorderLayoutFenster fenster = new BorderLayoutFenster(); } public BorderLayoutFenster() { super("Anordnung im BorderLayout"); this.setSize(250, 100); this.setLocation(100, 100); this.setBackground(Color.cyan); this.setLayout(new BorderLayout()); btn1 = new Button("Norden"); this.add("North", btn1); btn2 = new Button("Westen"); this.add("West", btn2); btn3 = new Button("Osten"); this.add(btn3, BorderLayout.EAST); btn4 = new Button("Süden"); this.add(btn4, BorderLayout.SOUTH); btn5 = new Button("Zentrum"); this.add("Center", btn5); pack(); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 179 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Das GridLayout import java.awt.*; import java.awt.event.*; public class GridLayoutFenster extends Frame { Button btn1, btn2, btn3, btn4, btn5, btn6; public static void main(String[] args) { GridLayoutFenster fenster = new GridLayoutFenster(); } public GridLayoutFenster() { super("Anordnung im GridLayout"); this.setSize(250, 100); this.setLocation(100, 100); this.setBackground(Color.cyan); GridLayout gl = new GridLayout(0, 3); this.setLayout(gl); btn1 = new Button("Nr. 1"); this.add(btn1); btn2 = new Button("Nr. 2"); this.add(btn2); btn3 = new Button("Nr. 3"); this.add(btn3); btn4 = new Button("Nr. 4"); this.add(btn4); btn5 = new Button("Nr. 5"); this.add(btn5); btn6 = new Button("Nr. 6"); this.add(btn6); } pack(); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); gl.setColumns(2); pack(); } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 180 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Das GridBagLayout import java.awt.*; import java.awt.event.*; public class GridBagLayoutFenster extends Frame { Button btn1, btn2, btn3, btn4, btn5, btn6; public static void main(String[] args) { GridBagLayoutFenster fenster = new GridBagLayoutFenster(); } public GridBagLayoutFenster() { super("Anordnung im GridBagLayout"); this.setSize(250, 100); this.setLocation(100, 100); this.setBackground(Color.cyan); GridBagLayout gbl = new GridBagLayout(); this.setLayout(gbl); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(1, 1, 1, 1); gbc.fill = GridBagConstraints.BOTH; gbc.weightx = 1; gbc.weighty = 1; btn1 = new Button("Nr. 1"); gbc.gridwidth = 1; gbc.gridheight = 1; gbc.gridx = 0; gbc.gridy = 0; gbl.setConstraints(btn1, gbc); this.add(btn1); btn2 = new Button("Nr. 2"); gbc.gridwidth = 1; gbc.gridheight = 1; gbc.gridx = 0; gbc.gridy = 1; gbl.setConstraints(btn2, gbc); this.add(btn2); btn3 = new Button("Nr. 3"); gbc.gridwidth = 1; gbc.gridheight = 2; gbc.gridx = 1; gbc.gridy = 0; gbc.weightx = 2; gbl.setConstraints(btn3, gbc); this.add(btn3); btn4 = new Button("Nr. 4"); gbc.gridwidth = 2; gbc.gridheight = 1; gbc.gridx = 0; gbc.gridy = 2; gbc.weightx = 1; gbl.setConstraints(btn4, gbc); this.add(btn4); btn5 = new Button("Nr. 5"); gbc.gridwidth = 1; gbc.gridheight = 1; gbc.gridx = 2; gbc.gridy = 0; gbl.setConstraints(btn5, gbc); this.add(btn5); btn6 = new Button("Nr. 6"); gbc.gridwidth = 1; gbc.gridheight = 2; gbc.gridx = 2; gbc.gridy = 1; gbl.setConstraints(btn6, gbc); this.add(btn6); //pack(); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 181 / 210 23.03.2010 PROMOD-2a / SS10 this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } } Beispiel – Das CardLayout import java.awt.*; import java.awt.event.*; public class CardLayoutFenster extends Frame implements ActionListener { Button btn1, btn2, btn3, btn4; CardLayout cl = new CardLayout(); public static void main(String[] args) { CardLayoutFenster fenster = new CardLayoutFenster(); } public CardLayoutFenster() { super("Anordnung im CardLayout"); this.setSize(250, 100); this.setLocation(100, 100); this.setBackground(Color.cyan); this.setLayout(cl); btn1 = new Button("Nr. 1"); this.add("First", btn1); btn1.addActionListener(this); btn2 = new Button("Nr. 2"); this.add("Second", btn2); btn2.addActionListener(this); btn3 = new Button("Nr. 3"); this.add("Third", btn3); btn3.addActionListener(this); btn4 = new Button("Nr. 4"); this.add("Fourth",btn4); btn4.addActionListener(this); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } public void actionPerformed(ActionEvent ae) { cl.next(this); } } Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 182 / 210 23.03.2010 PROMOD-2a / SS10 Beispiel – Das BoxLayout import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BoxLayoutFenster extends JFrame { JButton btn1, btn2, btn3; public static void main(String[] args) { BoxLayoutFenster fenster = new BoxLayoutFenster(); } public BoxLayoutFenster() { super("Anordnung im BoxLayout"); this.getContentPane().setLayout(new BorderLayout()); this.setSize(280, 90); this.setLocation(100, 100); this.setBackground(Color.cyan); JPanel p = new JPanel(); p.setBackground(Color.yellow); this.getContentPane().add(p); BoxLayout bl = new BoxLayout(p, BoxLayout.X_AXIS); p.setLayout(bl); btn1 = new JButton("Nr. 1"); btn1.setAlignmentY(1.0f); p.add(btn1); p.add(Box.createHorizontalGlue()); btn2 = new JButton("Nummer 2"); btn2.setAlignmentY(0.5f); p.add(btn2); p.add(Box.createHorizontalGlue()); btn3 = new JButton("Nr. 3"); btn3.setAlignmentY(0.0f); p.add(btn3); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } } Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.) Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 183 / 210 23.03.2010 PROMOD-2a / SS10 (Sem-Projekt 3. JDBC) 15. Elementare Dienste für JEE: JDBC Was Sie lernen: wie die Architektur von JDBC aussieht wie Sie über Java auf Datenbanken zugreifen wie Anfragen an eine Datenbank gerichtet und Ergebnisse ausgewertet werden Voraussetzungen: Java Fundamentals Datenbank-Grundlagen Literatur: Siehe Literaturverzeichnis Goll/Weiß/Müller: “Tigerbuch” Wutka: J2EE Developer’s Guide http://knol.google.com/k/j-anton-illik/jdbc-ein-elementarer-dienst-fr-jee-how/2rmlvpldf2pey/2# 15.1 Hinführung Die Notwendigkeit, die SQL-Sprache einzubetten, ergibt sich daraus, dass es sich bei SQL um eine rudimentäre (non computational complete) Programmiersprache handelt, die allein nicht ausreicht, um komplexe Datenbankanwendungen zu schreiben. Eine Möglichkeit, Datenbankanwendungen zu schreiben, bietet die Einbettung der SQL-Sprache in prozedurale und objektorientierte Programmiersprachen wie wie C/C++/C# und Java. Man spricht dann von Embedded SQL. Dabei wird ein Vorübersetzer/Präprozessor verwendet, der jede SQL-Anweisung übersetzt. Nach der Vorübersetzerphase wird ein Programm mit einem einheitlichen Code erzeugt, das anschließend übersetzt werden kann. (Die Programmiersprache, in die SQL eingebettet wird, in unserem Fall Java, wird Host-Sprache genannt.) Eine andere Möglichkeit der SQL-Nutzung in Java läuft über eine Aufrufschnittstelle (Call Library Interface): hier werden native Klassen und Methoden der Sprache speziell für den Umgang mit einer Datenbank zur Verfügung gestellt. Für die Nutzen von Datenbanken aus einer Programmiersprache wie Java gibt es also zwei verschiedene Ansätze: den Embedded SQL Ansatz SQLJ und den CLI (Call Library Interface) Ansatz von JDBC. Wir betrachten zunächst den CLI-Ansatz von JDBC. 15.2 Grundlagen Einer (von mehreren) elementaren Java-Dienst für JavaEE ist JDBC. Im JavaEE-Umfeld wird JDBC an verschiedenen Stellen genutzt: z.B. in JSP/Servlets und insbesondere in EJBs (Enterprise Java Beans). JDBC, Java Database Connectivity, ist ist eine Java API und Teil des Java JDK. Es wird dazu benutzt Daten aus einer beliebigen Tabellenstruktur herauszulesen. Dabei sind nicht nur Datenbanktabellen gemeint, sondern auch auch normale Dateien oder Datenquellen, die Tabellenstrukturen aufweisen. In erster Linie bietet es aber die Möglichkeit, insbesondere Datenbankapplikationen komplett in Java zu schreiben. JDBC wird sehr oft dazu benutzt, SQL Befehle an die Datenbank weiterzuleiten. Der große Vorteil dabei ist, dass es genügt, ein (einziges) Programm für alle Arten von Datenbanken zu schreiben. Mit JDBC kann also ein einzelnes Programm z.B. auf eine Oracle-, eine IBM DB2- oder eine MySQL-Datenbank zugreifen, ohne dass etwas im Code dafür geändert werden muss. Zusammen mit den Vorteilen der Programmiersprache Java ist ein JDBC Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 184 / 210 23.03.2010 PROMOD-2a / SS10 basiertes Java Programm in der Lage, Clienten auf einer beliebigen Plattform (egal ob beispielsweise Windows, Macintosh oder eine UNIX Maschine) mit einer beliebigen Datenbank zu verbinden. Die JDBC API besteht eigentlich aus zwei Teilen. Das Kern-JDBC (java.sql.*) wird mit dem Java Standard Development Kit geliefert. JavaEE entält zusätzlich das optionale JDBC-Paket javax.sql.*, das einige zusätzliche serverseitige Features enthält, die in der JavaEE-Entwicklung häufiger gebraucht werden, vor allem im Bereiche der Enterprise JavaBeans. Schlagworte hierfür: o o o o DataSource, Connection-Pooling, verteilte Transaktionen, RowSets (erweitert das ResultSet-Interface) Moderne Software-Architekturen bestehen aus mindestens drei Ebenen: o Der Hardware am nächsten ist die Datenebene, o darüber angesiedelt ist die Geschäftslogik-Ebene o und dann folgt die Präsentatinsebene. Die Dateneben wird i.d.R. durch den Einsatz von relationalen Datenbanken unterschiedlicher Hersteller (MySQL (-> SUN ->) Oracle, IBM, SyBase, …) realisiert. Zur Anbindung von Java-Anwendungen und Java-Applets an relationale Datenbanksysteme wird über das JDBC-API eine plattform- und datenbankunabhängige Schnittstelle bereitgestellt. Die Datenbankhersteller können über JDBC-Treiber eine Anbindung an ihr DBMS anbieten. Eine Liste der verfügbaren JDBC-Treibern finden Sie unter http://servlet.java.sun.com/product/jdbc/drivers oder http://developers.sun.com/product/jdbc/drivers (oder nach „jdbc driver“ googeln). Zum Zeitpunkt der Skripterstellung gab es über 220 verfügbare Treiber. Um Daten einer Datenbank abzulegen, abzufragen und zu ändern wird die strukturierte Abfragesprache SQL (Structured Query Language) eingesetzt. Für die Abfragesprach SQL wurden mehrere Standards und Funktionsumfänge definiert. Der SQL ANSI 92 ENTRY LEVEL wurde als Grundlage für JDBC festgelegt. Damit ist sichergestellt, dass ein standardisierter Teil von SQL-Anweisungen für alle JDBC-Treiber verwendet werden kann. Die Java-Aufrufschnittstelle von SQL basiert auf dem Standard X/Open SQL CLI (Call Level Interface). Das heißt, Sie verwenden in Ihrem Programm SQL-Anweisungen in Form von Zeichenketten (Strings), die an die betreffenden Methoden der Datenbanktreiber weitergegeben werden. Werden Daten zurückgegeben (Ergebnismenge), können Sie auf diese über andere Methoden zugriffen. Im Fall von EJBs wird JDBC auf zwei Arten genutzt: Zum einen können direkte JDBC-Aufrufe in Session Beans und Bean Managed Persistance Entity Beans benutzt werden, zum anderen kann der EJB-Container intern JDBC zur Speicherung von Daten aus Container Managed Persitance Entity Beans verwenden. JDBC ist zwar ein typischer Weg, Daten aus EJBs heraus persistent abzulegen, aber es ist nicht der einzige Weg. Beispielsweise sind auch indirekte Zugriffe auf Datenhaltungssysteme über CORBA, JMS (Java Messaging Service) oder JCA (Java Connector Architecture) möglich. 15.3 JDBC als Basis für andere APIs: SQLJ und JDO Die JDBC API kann zum einen ohne andere APIs zur Erstellung von Datenbank-Applikationen benutzt werden. Andererseits ist JDBC zugleich Grundlage für eine Reihe von anderen APIs, die JDBC erweitern und spezialisieren. Diesbezüglich stellen wir kurz SQLJ und JDO vor. 15.3.1 SQLJ Insbesondere im Bereich SQL und JDBC wurde der Standard SQLJ aus einem Konsortium von Oracle, IBM, Sun und anderen Firmen geschaffen, um Embedded SQL in Java zu integrieren. Während der Kompilierung lassen sich (direkte) SQL Anweisungen mit Java Anweisungen kombinieren. Beispielsweise kann eine Java Variable in einem SQL Befehl verwendet werden, um die Ergebnisse des SQL Befehls zu erhalten und weiter zu verarbeiten. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 185 / 210 23.03.2010 PROMOD-2a / SS10 15.3.2 SQLJ im Vergleich zu JDBC Der wichtigste Unterschied zwischen den beiden Sprachen ist, dass SQLJ eine statische und JDBC eine dynamische Sprache ist. Eine Sprache wird dynamisch genannt, falls der Datenbankprogrammierer damit Datenbankobjekte abfragen und ändern kann, die erst zur Ablaufzeit bekannt sind. Das gilt nicht nur für Datenbankobjekte, sondern auch für ganze SQL-Anweisungen. Im Unterschied zu JDBC, kann SQLJ nur statisch verwendet werden, d.h. alle SQL-Anweisungen und alle in ihnen verwendeten Datenbankobjekte (bis eventuell auf die Werte der Host-Variablen) sind zur Übersetzungszeit bekannt. Obwohl eine dynamische Sprache flexibler als eine statische ist, hat SQLJ auch Vorteile im Vergleich zu JDBC. Diese sind: SQLJ befindet sich auf einem höherem logischen Niveau/hat eine grössere Aggregationsdichte als JDBC. Damit wird ein Programm kompakter/kürzer. SQLJ hat eine einfachere Syntax als JDBC SQLJ kann die Syntax- und Semantikanalyse zur Übersetzungszeit durchführen. Im Unterschied zu JDBC, wo alle SQL-Anweisungen, die in einem JDBC-Programm in Methodenaufrufen versteckt sind, ist jede SQL-Anweisung in einem SQLJ-Programm in ihrer ursprünglichen Form dargestellt und dadurch gleich erkennbar. Aus diesem Grund bezeichnet man SQLJ als logisch höhere Programmiersprache (als JDBC). Das folgende Beispiel verdeutlicht diesen Sachverhalt. Zunächst geben wir kurz einen Ausschnitt aus einem JDBC-Programm wieder, in dem eine SELECT-Anweisung mit Hilfe der preparedStatement()-Methode vorbereitet wird und danach die Spaltenwerte den entsprechenden Variablen zugewiesen werden. java.sql.PreparedStatement ps = con.preparedStatement(„SELECT address FROM emp WHERE name=?“); .... ps.setString(1,name); java.sql.ResultSet names = ps.executeQuery(); names.next(); addr = names.getString(1); names.close(); Um dieselbe Funktionalität mit Hilfe von SQLJ zu implementieren, reicht eine einzige Programmzeile aus: #sql (con) {SELECT address INTO :addr FROM emp WHERE name= :name}; Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 186 / 210 23.03.2010 PROMOD-2a / SS10 15.3.3 SQLJ und der Übersetzungprozess SQLJ Quelleprogramm: Java-Programm mit eingebettetem SQL SQLJ – Übersetzer (auch Präprozessor oder Vorübersetzer genannt) Java-Programm mit JDBC-Aufrufen Java-Compiler Java-Byte-Code mit JDBC-Methodenaufrufen JDBC-Teiber DB Bild: Übersetzung eines SQLJ-Programms 15.3.4 Persistente Java Objekte: JDO Java Data Objects Mit der Java Data Objects19 (JDO) API und Enterprise Java Beans20 (EJB) ist es möglich, Daten aus einem Datenspeicher mit Java Objekten zu verknüpfen und Java Objekte dauerhaft (persistent) in einem Datenspeicher aufzubewahren. Im Falle von JDBC können beispielsweise ganze Reihen oder Spalten von Datenbanken, die mit JDBC ausgelesen wurden, als Objekte gespeichert werden. Mit JDO ist es ausserdem möglich, auch Datenbanken anzusprechen, die nicht auf SQL basieren. Dadurch kann der Java Code unabhängig von der Datenbanksprache geschrieben werden. JDO kann also mit JDBC relationelle Datenbanken erreichen. Mit der eigenen Abfragesprache JDOQL kann JDO bei relationellen Datenbanken diese Befehle direkt mithilfe von JDBC in SQL Abfragen umwandeln. JDO lässt generelle Java Objekte fortbestehen, wohingegen EJB Entity-Beans verwendet, um Abfragen und dergleichen zu speichern. 15.4 Architektur von JDBC Eine Java-Anwendung oder ein Java-Applet lädt im ersten Schritt mit Hilfe eines Treibermanagers einen JDBCTreiber. Über diesen wird dann eine Verbindung zu einer Datenbank hergestellt. 19 20 Siehe http://java.sun.com/products/jdo Siehe http://java.sun.com/products/ejb Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 187 / 210 23.03.2010 PROMOD-2a / SS10 Je nach dem Typ des verwendeten JDBC-Treibers können zur Kommunikation mit der Datenbank noch weitere Komponenten benötigt werden, z.B. ein ODBC-Treiber. Diese JDBC-Treiberkombination kommuniziert (über das verwendete Netzwerk) mit dem Datenbankserver (DBMS) Der DBMS-Server reguliert den eigentlichen Zugriff auf die Datenbank.Dabei kann ein Datenbankserver auch mehrere Datenbanken verwalten. 15.5 Treibertypen Eine zentrale Rolle spielen die Datenbanktreiber. Auch wenn nicht direkt mit JDBC programmiert wird, sondern SQLJ oder eine zugekaufte Zurgriffsschicht oder ein EJB-Applikationsserver verwendet wird, ist immer eine JDBC-Treiber erforderlich: den auch SQLJ, die Zugriffschichten oder EJB-Applikationsserver setzen irgendwann auf der JDBC-Schnittstelle auf. Die Tabelle unten beschreibt die verschiedenen Treibertypen. Die Treibertypen im Überblick: 1. Typ 1: JDBC / ODBC Brücke 2. Typ 2: Native API Treiber 3. Typ 3: JDBC Middelware Treiber 4. Typ 4: Native Protocol Treiber Treibertyp Class-1-Teiber JDBC-ODBC-Bridge Eigenschaften Über die JDBC-ODBC-Brücke werden Datenbankverbindungen über die ODBC21Schnittstelle aufgebaut. Dazu werden die Anweisungen von JDBC an die betreffende Funktion des ODBCTreibers weitergegeben, der im nativen Code des Betriebssystems auf dem Client vorliegt und die eigentliche Verbindung zur Datenbank oder dem Datenbankserver herstellt. Der ODBC-Treiber nutzt das CLI des Herstellers. D.h. die nativen (ODBC-) Bibliotheken des Datenbanksystems für den Datenbankzugriff müssen auf dem Cient installiert werde. 21 ODBC Open Database Connectivity wurde ursprünglich von Microsoft auf Basis des Call Level Interface von X/Open und ISO/IEC entwickelt, ist aber inzwischen auch von anderen Softwareherstellern übernommen worden. Mit ODBC kann auf jede lokale oder ferne Datenquelle zugegriffen werden. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 188 / 210 23.03.2010 PROMOD-2a / SS10 Die Bridge ist nur einmal vorhanden. Für jedes Datenbanksystem ist jedoch ein ODBCTreiber erforderlich. ODBC-Treiber sind für die meisten Datenbanksysteme verfügbar und werden hauptsächlich beim Zugriff auf Datenressourcen unter Windows eingesetzt. Nachteilig ist die zusätzliche Installation und Konfiguration des ODBC-Treibers, die auf dem Client erfolgen muss. Applets können diese Schnittstelle nur begrenzt nutzen (über das Security API), da sie standardmäßig keinen Zugriff auf die lokalen Ressourcen des Clients haben. Class-2-Treiber JDBC-Native-API-partialJava-Treiber (CLI des Herstellers) Class-3-Treiber JDBC-net-pure-JavaTreiber Dieser Treibertyp ist sinnvoll für den Test, nicht aber für den operativen Einsatz. JDBC-Aufrufe werden hier auf native Anweisungen des Datenbank-Clients abgebildet, der die Kommunikation zum Datenbankserver herstellt. Dazu müssen zusätzlich die nativen Bibliotheken des Datenbanksystems für den Datenbankzugriff auf dem Client installiert werden. Der Zugriff ist hier sehr schnell. Der JDBC-Treiber verwendet jedoch nativen Code (den des DBMS-Client-Treibers) und ist damit selbst nicht plattformunabhängig. Hierbei handelt es sich um einen plattformunabhängigen Java-Treiber. Die Anweisungen werden von Java über das verwendete Netzwerkprotokoll (datenbankunabhängig) an einem auf dem Datenbankserver vorhandenen JDBCServer übergeben und von diesem in Datenbankanweisungen übersetzt. Der JDBC-Treiber enthält zusätzlich die Client-Funktionalität des DBMS. Es sind keine zusätzlichen nativen Bibliotheken auf dem Client erforderlich, sodass er einfach gewartet werden kann Class-4-Treiber Native-pure-JavaProtokolltreiber Im Falle eines Applets wird der Datenbanktreiber gegebenenfalls erst auf den Client übertragen. In dieser Treibervariante erfolgt eine direkte Kommunikation (datenbankherstellerspezifisches Protokoll) des JDBC-Treibers mit dem DBMS. Dabei wird auf dem DBMS die gleiche Struktur wie bei Class-1- und Class-2-Treibern verwendet. Es sind keine zusätzlichen Bibliotheken auf dem Client zu installieren. Eine Liste der momentan verfügbaren 221 JDBC-Treiber findet sich auf der folgenden Web-Site http://developers.sun.com/product/jdbc/drivers (ggf. nach „jdbc driver“ googlen). Stand: 19.06.2009 Die meisten Datenbankhersteller bieten entweder einen Typ-3- oder Typ-4-Treiber mit ihren Datenbanken an. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 189 / 210 23.03.2010 PROMOD-2a / SS10 Hier ein Überblick JDBC/ODBC-Bridge eigene Serverschicht CLI des Herstellers 15.6 Die Datenbank MySQL / Die DBMS Cloudscape / PointBase Empfehlung: in MySQL einarbeiten! Wenn Zeit/Muse/Intersse ggf. in Cloudscape/PointBase hineinschnuppern. Cloudscape/PointBase sind Datenbanken, die von Sun mit dem SUN-JEEServer zur Verfügung gestellt werden/wurden. Die Beispiele in der Sun-Doku beziehen sich (teilweise) darauf. Download und Installation Sonstige Hinweise Î Siehe Literatur und Internet 15.7 Aufbau einer Datenbankverbindung Der Verbindungsaufbau zu einer Datenbank ist der erste Schritt in einer JDBC Datenbank Anwendung. Dies geschieht üblicherweise mit der Klasse DriverManager (Siehe API-Doku: „The basic service for managing a set of JDBC drivers“). Es sind drei Schritte nötig, um mit DriverManager eine Verbindung zu einer Datenquelle herzustellen: Das Downloaden und Installieren des Treibers Im Programm: das dynamische Laden des Treibers zur Laufzeit und Im Programm: der eigentliche Verbindungsaufbau. • Im Folgenden benutzen wir den Treiber Connector/J von MySQL. • Prof. Illik Dieser Treiber ist ein Typ-4-Treiber. Als reine Java-Implementierung des MySQLProtokolls hängt der Treiber nicht ab von den MySQL-Client-Libraries. PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 190 / 210 23.03.2010 PROMOD-2a / SS10 • Die zur Zeit aktuelle Version ist unter http://dev.mysql.com/downloads/connector/j/5.1.html zu finden. • Als Java-Implementierung ist der Treiber sowohl für Linux wie auch für Windows nutzbar. Geliefert wird ein zip-File oder ein tar.gz-File. In beiden Fällen sind die Sources und die Binaries enthalten. Nach dem Entpacken findet man den Treiber unter dem Namen mysql-connector-java-5.1.7-bin.jar • Der Treiber muss im CLASSPATH des nutzenden Programmes liegen und auch zur Laufzeit verfügbar sein. Zur Laufzeit wird der Treiber wie folgt geladen: Class.forName(“com.mysql.jdbc.Driver“); //Lädt die Treiber-Klassen-Lib • Für den Test unter Eclipse kopieren Sie den DB-Treiber mysql-connector-java-5.1.7bin.jar mit in das Eclipse-Verzeichnis „src“. Andere potenzielle Ablageorte für den DB-Treiber • • Für lokale Java-Applikationen: Das dynamische Laden eines Treibers zeigt JDBC, zu welcher Datenbank oder Datenquelle es sich verbinden soll: Treiber also in die JRE-Lib (z.B. C:\Program Files\Java\jre6\lib\ext) und in die JDKLib (z.B. C:\Program Files\Java\jdk1.6.0_05\lib) kopieren.22 • Besser ist es allerdings jar-Files „lokal“ zu lassen und über die Compiler-Optionen –classpath (durch „:“ separiert) oder über Tool-/IDE-Optionen einzubinden. Oder: • Den kompletten Pfad inclusive jar-File in die Umgebungsvariable CLASSPATH aufnehmen. (Eclipse: „Project“ -> „Properties“ -> „Build Path“ -> „Libraries“ -> „Add External Libraries“ -> zum jar-File navigieren -> aufnehmen.) Für Tomcat-Programme: (Servlets/JSP) wird der $CATALINA_HOME/common/lib (Tomcat 5) hinterlegt. Treiber unter $CATALINA_HOME/lib (Tomcat 6) bzw. Der Pfad des Connector/J Treibers in der JAR-Lib lautet „com.mysql.jdbc.Driver“. Jeder Treiber für eine Datenbank hat einen eigenen Pfad, mit dem dieser aufgerufen werden muss. Eine Instanziierung des Treibers oder eine direkte Verknüpfung oder Registrierung mit der Klasse DriverManager ist nicht nötig, da dies dies automatisch geschieht. Nach der obigen Zeile Programmcode ist der Treiber einsatzbereit und eine Verbindung kann hergestellt werden. • Der Verbindungsaufbau geschieht genauso einfach wie Schritt eins: Connection con = DriverManager.getConnection(String url, String username, String passwort); Die Methode getConnection() liefert eine ein Connection Objekt zurück, mit welchem man später SQL Befehle ausführen kann. Die URL (englisch: Uniform Resource Locators) ist ein String, der zur Bestimmung der Datenbank dient. Eine typische URL sieht folgendermaßen aus: String url = “jdbc:mysql:test“; Dabei wird auf eine lokale MySQL Datenbank mit dem Namen „test“ zugegriffen. Will man eine Verbindung zu einer externen Datenbank herstellen, so sähe die URL aus: 22 Auspacken mit dem Linux-Kommando tar –zxvf <myfile>.tar.gz Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 191 / 210 23.03.2010 PROMOD-2a / SS10 String url = “jdbc:mysql://host:port/test“; ‚Host’ und ‚port’ müssen dabei ersetzt werden. Der Benutzername und Passwort in der getConnection() Methode sind selbst erklärend. Es gibt noch zwei weitere Varianten dieser Methode: DriverManager.getConnection(String url); Hierbei wird kein Benutzername und Passwort benötigt, wenn man eine Verbindung zu einer Datenbank aufbaut. DriverManager.getConnection(String url, Properties info); ‚Properties’ ist eine Erweiterung von java.util.HashTable, welche eine Reihe von Einstellungen für die Datenbankverbindung enthalten. In der unteren Tabelle werden einige der möglichen Werte23 vorgestellt. Name Username Passwort autoReconnect useSSL requireSSL allowMultiQuerries Beschreibung Der Benutzername für die Datenbank Das zugehörige Passwort für den Benutzernamen Soll die Verbindung wiederhergestellt werden, falls sie getrennt wurde? Soll eine sichere Verbindung mit SSL verwendet werden? Soll eine SSL gesicherte Verbindung vorausgesetzt sein, falls useSSL = true? Erlaubt das Versenden mehrerer SQLBefehle in einem Befehlsobjekt, die durch ‚;’ getrennt werden. Defaultwert false false false false Tabelle 1 Mögliche Einträge in Properties Beim Verbindungsaufbau können natürlich auch Fehler auftreten. Diese werden durch die Ausnahmebehandlung (englisch: exception handling) ‚SQLException’ abgefangen. ‚SQLException’ wird bei allen Fehlern ausgelöst, die im Zusammenhang mit JDBC und SQL stehen. Mit dem DriverManager können mehrere Connections zu Datenbanken betrieben werden. Innerhalb einer Connection können mehrere Statements zur (sukzessiven) Ausführung einer oder mehrerer SQL-Anweisungen verwendet werden. Die Ergebnisse solcher SQL-Anweisungen werden als Instanzen der Klasse ResultSet zurückgegeben. 23 Eine komplette Liste findet sich unter http://dev.mysql.com/doc/connector/j/en/cj-configuration-properties.html Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 192 / 210 23.03.2010 PROMOD-2a / SS10 Am Ende einer Datenbankanwendung sollte immer die Verbindung wieder geschlossen werden, damit keine offenen Verbindungen bei der Datenbank zurückbleiben. Dies geschieht mit einer einfachen close-Methode: con.close(); Zusammenfassung Aufbau einer Datenbankverbindung: Zur Herstellung einer Datenbankverbindung über JDBC sind folgende Schritte zu erledigen: 1. Installation eines Datenbankservers (lokal oder im LAN) 2. Installation eines JDBC-Treibers 3. Laden eines Treibermanagers mit dem ensprechenden JDBC-Treiber 4. Herstellen einer Verbindung zur Datenbank 5. SQL-Anweisungen an die Datenbank richten o … Zum Bearbeiten der Struktur der Datenbank (Tabellen anlegen,…) o … Zum Abfragen / Selektieren von Daten. Es wird eine Ergebnismenge geliefert. o … Zum Bearbeiten von Daten (ändern, löschen, einfügen). Es wird keine Ereignismenge geliefert. 6. Gegebenenfalls von der SQL-Anweisung zurückgegebene Ergebnismenge bearbeiten. Treiber laden DriverManager Connection PreparedStatement Statement CallableStatement ResultSet Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 193 / 210 23.03.2010 PROMOD-2a / SS10 Die Abbildung visualisiert das Zusammenspiel der einzelnen Interfaces und Klassen. Der JDBC-Treiber wird unabhäng als Klasse geladen. Wenn über den Treibermanager eine Datenbankverbindung hergestellt wird (Interface Connection), wird auf den bereits geladenen Treiber zurückgegriffen. Über eine Datenbankverbindung werden Anweisungen an diese gesendet (Interfaces CallableStatement, PreparedStatement, Statement). Zwei dieser Anweisungen liefern eine Ergebnismenge (Interface Result) zurück. 15.8 SQL-Anweisungen ausführen Nachdem eine Verbindung zu einer Datenbank hergestellt wurde, können nun SQL Anweisungen (englisch: statements) verschickt werden. Diese können auf verschiedene Arten erstellt werden. In den folgenden drei Unterkapiteln werden diese jeweils gesondert behandelt. 15.9 Die Klasse Statement Dies ist die Standard Klasse für SQL Anweisungen. Ein solches Statement wird von dem Verbindungsobjekt erstellt: Statement st = con.createStatement(); Es gibt drei verschiedene Methoden, um ein Statement Objekt zu kreieren. Methodenname createStatement() createStatement(int resultSetType, int resultSetConcurrency) createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) Beschreibung Die Standardmethode ohne jegliche Parameter Die Integerparameter sind für das ResultSet Objekt, welches die Ergebnisse eines Queries liefert (siehe „Ereignisse anzeigen“). Drei Parameter bestimmen die Einstellungen für das Rückgabeobjekt. Siehe Kapitel 15.12.2 ResultSet Tabelle 2 Statement Objekt erzeugen Mit diesem Objekt ‚st’ lassen sich nun Anweisungen abschicken. Es ist nicht nötig, bei jedem SQL Befehl ein neues Objekt zu kreieren. Die Anweisungen werden mit ‚execute’ Methoden an die Datenbank gesendet. Drei Methoden-Typen werden unterschieden: Erstens: boolean execute(String sql); ‚sql’ ist die SQL Anweisung, die an die Datenbank geschickt werden soll und mehrere Rückgabeobjekte in Form von ResultSet entstehen lassen kann. Als Rückgabewert liefert die Methode ein boolean zurück, welches ‚true’ ist, wenn das erste Rückgabeobjekt vom Typ ResultSet ist. Zweitens: int executeUpdate(String sql); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 194 / 210 23.03.2010 PROMOD-2a / SS10 Im Gegensatz zur ersten Methode werden hier nur bestimmte SQL Befehle ausgeführt. ‚INSERT’, ‚UPDATE’, ‚DELETE’ oder eine SQL Anweisung, die keinen Rückgabewert besitzt, können hier ausgeführt werden. Trotzdem liefert diese Methode etwas zurück. Bei ‚INSERT’, ‚UPDATE’ oder ‚DELETE’ Befehlen wird Reihenanzahl angegeben oder der Wert ist 0, wenn eine andere Anweisung ausgeführt wurde. Drittens: ResultSet executeQuery(String sql); Typischerweise werden ‚SELECT’ Anweisungen mit dieser Methode verschickt. Das Rückgabeobjekt ist vom Typ ResultSet, welches in einem späteren Kapitel näher erläutert wird. Beispielanwendung: Hat man die Datenbank ‚Test’ und man möchte die Tabelle ‚Obst’ anlegen, so kann man dies folgendermaßen machen. Eine Verbindung zu der Datenbank besteht bereits. Statement stmt = con.createStatement(); stmt.executeUpdate(“CREATE TABLE OBST (NAME VARCHAR(32), PREIS FLOAT)“); Die Tabelle ‚Obst’ hat zwei Attribute, Name und Preis. Name ist vom Type VARCHAR und kann 32 Zeichen lang sein. Preis hingegen ist ein Fliesskommawert. Natürlich lassen sich die Befehle vorher in einer String-Variablen speichern und dann in das Statement Objekt einzufügen: String createObstTable = “CREATE TABLE OBST (NAME VARCHAR(32), PREIS FLOAT)”; stmt.executeUpdate(createObstTable); Um die Datenbank zu füllen, verwendet man folgenden Befehl: stmt.executeUpdate(“INSERT INTO OBST VALUES (’Banane’, 2.50)”); stmt.executeUpdate(“INSERT INTO OBST VALUES (’Apfel’, 1.00)”); stmt.executeUpdate(“INSERT INTO OBST VALUES (’Birne’, 2.00)”); Wie man bisher sehen kann, sind die Befehle aufgebaut wie „normale“ SQL Anweisungen für eine Datenbank. In einem letzten Schritt kann man sich die Ergebnisse noch anzeigen lassen. Das wird so realisiert: String getDataFromTable = “SELECT * FROM OBST“; System.out.println(stmt.executeQuery(getDataFromTable)); Das Ergebnis hierzu sähe auf der Konsole wie folgt aus: NAME ----------Apfel Banane Prof. Illik PREIS ---------1.00 2.50 PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 195 / 210 23.03.2010 PROMOD-2a / SS10 Birne 2.00 Falls keine spezielle Sortierung angegeben wurde, wird nach der ersten Spalte sortiert. 15.10 Klasse PreparedStatement24 PreparedStatements sind abgeleitet vom Interface Statement. Sie werden oftmals dann eingesetzt, wenn ein Statement Objekt mehrmals verwendet werden soll. Wie der Name es vermuten lässt, werden die Statements vorpräpariert, das heißt vorkompiliert. SQL Anweisungen, die Variablen verwenden, sind das bevorzugte Einsatzgebiet. Es können natürlich auch SQL Befehle ohne Variablen benutzt werden. Der große Vorteil von einem PreparedStatement kommt erst dann zur Geltung, wenn der gleiche SQL Befehl mit unterschiedlichen Werten ausgeführt wird. Ein PreparedStatemsent Objekt kann auf mehrere Arten erzeugt werden. Dabei ist auch wieder ein Verbindungsobjekt vonnöten. Methodenname prepareStatement(String sql) prepareStatement(String sql, int autoGeneratedKeys) prepareStatement(String sql, int[] columnIndexes) prepareStatement(String sql, String[] columnNames) prepareStatement(String sql, int resultSetType, Beschreibung ‚sql’ ist der SQL Befehle, der ein oder mehrere ‚?’ als Parameter enthalten kann. Sollen automatisch generierte Schlüssel zurückgegeben werden oder nicht: Statement.RETURN_GENERATED_KEYS = ja Statement.NO_GENERATED_KEYS = nein Werden Reihen hinzugefügt, bestimmt das Array ‚columnIndex’, welche Spalten zurückgegeben werden. Das Prinzip der Methode ist dasselbe wie bei der vorherigen, nur dass die Spaltennamen als Strings übergeben werden Beide Integervariablen setzen Parameter des zurückgegebenen ResultSet (siehe Kapitel 15.12.2) Objektes. int resultSetConcurrency) prepareStatement(String sql, int resultSetType, Wie bei der obigen Methode werden Einstellungen für das Rückgabeobjekt getätigt. int resultSetConcurrency, int resultSetHoldability) Tabelle 3 PreparedStatement Objekt erzeugen Ein typisches PreparedStatement kann folgendermaßen aussehen. Es wird dabei das obige Beispiel weitergeführt: PreparedStatement p_st = con.prepareStatement(“INSERT INTO OBST VALUES(?, ?)“); Dabei sind die ‚?’ Parameter, die beliebig gesetzt werden können. In den Beispielen für Statement könnte man so schreiben: 24 Das Statement wird auf Programmiersprach-Ebene vorbereitet und gespeichert. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 196 / 210 23.03.2010 PROMOD-2a / SS10 p_st.setString(1,“Banane“); p_st.setFloat(2, 2.50); p_st.executeUpdate(); Die ‚set’-Methoden gibt es für alle Arten von Parametertypen25. Es werden immer zwei Parameter übergeben. Der erste sagt aus, welches ‚?’ ersetzt werden soll. Der zweite beinhaltet den zu ersetzenden Wert. Schließlich wird der Befehl mit einer ‚execute’-Methode ausgeführt. 15.11 Klasse CallableStatement / StoredProcedure26 Die dritte Möglichkeit in JDBC, einen SQL Befehl an eine Datenbank zu geben, ist das CallableStatement. Mit diesem kann man vorgespeicherte Prozeduren und Anweisungen ausführen. Solche vorgefertigten Skripte können eine Reihe von SQL Befehlen enthalten, die dann in der Datenbank gespeichert werden, um sie jederzeit aufrufen zu können. Eine vorgefertigte Prozedur kann man auch mit JDBC erstellen: String storedProcedure = “CREATE PROCEDURE ZEIGE_OBST AS “+“ SELECT * FROM OBST WHERE PRICE > 1.50“; Statement st = con.createStatement(); st.executeUpdate(storedProcedure); Dieser SQL Befehl ist nun in der Datenbank unter dem Namen „ZEIGE_OBST“ gespeichert. Aufgerufen wird dieser mit einem CallableStatement: CallableStatement c_st = con.prepareCall(“{ ZEIGE_OBST }“); ResultSet rs = c_st.executeUpdate(); Der Treiber übersetzt diese Syntax in ein für die Datenbank verständliches SQL, welche dann den die gespeicherte Prozedur ausführt. Das Ergebnis dieser vorgefertigten Prozedur wird in unserem Beispiel wie folgt aussehen: NAME ----------Banane Birne PREIS ---------2.50 2.00 15.12 Auswertung der Ergebnismengen Bei jeder SQL Anweisung gibt es Rückgabewerte. Es ist egal, ob man einen Befehl wie UPDATE oder INSERT, die nur Daten in der Datenbank verändern und nicht abrufen oder ein SELECT Statement ausführt, welches eine ganze Reihe von Rückgabewerten beinhalten kann. In den nächsten Unterkapiteln werden die verschiedenen Arten von Rückgabewerten gesondert behandelt. 25 Nachzulesen in http://java.sun.com/j2se/1.5.0/docs/api/java/sql/PreparedStatement.html Im Gegensatz zum Prepared Statement wird die Stored Procedure in der DB gespeichert – nicht auf Ebene der Programmiersprache. 26 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 197 / 210 23.03.2010 PROMOD-2a / SS10 15.12.1 Boolean und Integer als Rückgabewerte Ein boolean oder ein Integer werden von Methoden zurückgegeben, wenn entweder die Methode execute() oder executeUpdate() ausgeführt wurde. Typisch dafür sind die drei Anweisungen UPDATE, INSERT oder DELETE. Bei solchen Statements ist es nur wichtig zu wissen, ob der Befehl erfolgreich durchgeführt wurde oder fehlgeschlagen ist. Ist er fehlgeschlagen, wird eine SQLException ausgelöst. Bei einem erfolgreichen Durchführen wird je nach Methode ein bool`scher Wert oder ein Integer zurückgegeben. Bei jeder der drei Möglichkeiten Statement, PreparedStatement und CallableStatement gibt es Methoden, die nur einen solchen Rückgabewert besitzen: boolean execute(); int executeUpdate(); Dies sind zwei solche Methoden. Bei der oberen wird ein ‚true’ zurückgegeben, falls der SQL Befehl ein RowSet Objekt zurückgibt. Bei einer Update Methode wird die Anzahl der Reihen als Rückgabewert gesetzt. Löscht man mit einem DELETE Statement drei Reihen, so erhält man von der Methode ebenfalls den Wert drei. 15.12.2 ResultSet Ein Objekt vom Interface ResultSet wird üblicherweise bei SQL Anweisungen erstellt, die eine Datenbank Abfrage sind. Der SELECT Befehl ist die wohl am häufigsten verwendete Anweisung. Ein ResultSet kann auf verschiedene Weise konfiguriert werden. In der Standardeinstellung kann das ResultSet reihenweise ausgelesen werden, beginnend mit der ersten Reihe. Mit der Erstellung eines Statements, egal welcher Art, kann das ResultSet gestaltet werden. In der folgenden Tabelle werden die einzelnen Einstellungsmöglichkeiten beschrieben. Als Wiederholung wird eine Methode nun aufgezeigt, welche die verschiedenen Parameter für das ResultSet beinhaltet: Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability); Diese drei Übergabewerte können mit verschiedenen Konstanten aus dem Interface ResultSet besetzt werden: Parametertyp resultSetType Konstante in ResultSet ResultSet.TYPE_FORWARD_ONLY ResultSet.TYPE_SCROLL_INSENSITIVE Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc Beschreibung Das ResultSet darf nur vorwärts gelesen werden. Während ein ResultSet Objekt geöffnet ist, reagiert es nicht auf Veränderungen, die an diesem vollzogen werden. 198 / 210 23.03.2010 PROMOD-2a / SS10 ResultSet.TYPE_SCROLL_SENSITIVE resultSetConcurrency resultSetHoldability Während ein ResultSet Objekt geöffnet ist, reagiert es auf Veränderungen. ResultSet.CONCUR_READ_ONLY Das ResultSet darf nicht verändert werden. ResultSet.CONCUR_UPDATABLE Das ResultSet darf verändert werden. ResultSet.HOLD_CURSORS_OVER_COMM Das ResultSet Objekt wird nicht bei einem Commit-Befehl IT geschlossen, sondern ist für weitere Anweisungen verfügbar. ResultSet.CLOSE_CURSORS_AT_COMMIT Das ResultSet wird bei einem Commit-Befehl geschlossen. Tabelle 4: ResultSet Konstanten Nun gibt es mehrere Möglichkeiten, sich die Ergebnisse mit einem ResultSet anzeigen zu lassen. Will man den kompletten Rückgabewert auf einen Schlag, so kann man einfach das Objekt als Ganzes ausgeben. Beispielsweise könnte dies so aussehen: ResultSet rs = stmt.executeQuery(“SELECT Name, Preis FROM Obst“); System.out.println(rs); Das Ergebnis zeigt alle Daten der Tabelle Obst an: NAME ----------Apfel Banane Birne PREIS ---------1.00 2.50 2.00 Es ist aber auch möglich, die Werte einzeln abzurufen. Statt der Ausgabe des gesamten Objektes auf einmal, kann man jede Reihe einzelnen auswählen: while (rs.next()) { String name = rs.getString("Name"); float preis = rs.getFloat("Preis"); System.out.println("Ein(e) " + name + " kostet " + preis); } Die while-Schleife wird solange durchlaufen, wie es Reihen in dem ResultSet Objekt gibt. Das Ergebnis sieht wiederum ähnlich zu dem oberen aus: Ein(e) Apfel kostet 1.00 Ein(e) Banane kostet 2.50 Ein(e) Birne kostet 2.00 Ähnlich wie bei PreparedStatement gibt es eine Reihe von Datentypen, die aufgerufen werden können. Eine aktuelle Liste dieser ‚getter’-Methoden gibt es in der Java JDK API27. In einem ResultSet kann man auch zu einer bestimmten Reihe springen. Es gibt verschiedene Methoden dafür, die in der nächsten Tabelle vorgestellt werden. 27 siehe http://java.sun.com/j2se/1.5.0/docs/api/java/sql/ResultSet.html Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 199 / 210 23.03.2010 PROMOD-2a / SS10 Methode boolean absolute(int row); Beschreibung boolean first(); Springt zu der angegebenen Reihe. Ist diese außerhalb des Objektes, wird ‚false’ zurückgegeben. Damit kann man Reihen vor- und zurückgehen. Auch hier wird ‚false’ zurückgegeben, falls die Reihe nicht in dem Objekt liegt. Springt eine Reihe zurück. Der Rückgabewert ist ‚true , wenn die Reihe noch innerhalb des Objektes liegt. Springt eine Reihe vor. Die Art des Rückgabewertes ist dieselbe wie in der vorherigen Methode. Der Zeiger geht zurück zur ersten Reihe. boolean last(); Das Objekt ist nun in der letzten Reihe. void afterLast(); Mit dieser Methode springt man zu der Stelle nach der letzten Reihe. Mit dieser Methode springt man zu der Stelle vor der esten Reihe. boolean relative(int row); boolean previous(); boolean next(); void beforeFirst(); Tabelle 5 Methoden, um eine Reihe in einem ResultSet auszuwählen Ein ResultSet Objekt lässt sich auch bearbeiten. Dafür gibt es eine Reihe von ‚update’-Methoden. (Siehe API Beschreibung). Natürlich wird dabei nur der Wert innerhalb des Objektes geändert, nicht aber der zugrundeliegende Wert in der Datenbank. Will man beispielsweise den Preis des Apfels verändern, so kann man dies folgendermaßen machen: while (rs.next()) { String name = rs.getString("Name"); float preis = rs.getFloat("Preis"); if(name.equals("Apfel") { System.out.println("Ein " + name + " kostet " + preis); Rs.updateFloat(1,1.75); System.out.println("Ein " + name + " kostet nun" + preis); } } Das Ergebnis sieht wie folgt aus: Ein Apfel kostet 1.00 Ein Apfel kostet nun 1.75 Die Möglichkeit, den Datenbankeintrag doch mit diesem neuen Wert zu versehen, besteht mit dieser Methode: void updateRow(); Ein vorzeitiges Schließen des ResultSet Objektes, um keine weiteren Daten in das Objekt einfließen zu lassen, ist mit einer simplen close()-Methode möglich. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 200 / 210 23.03.2010 PROMOD-2a / SS10 Siehe Literatur und zu folgenden Themen: 15.12.3 Konfigurieren der Ergebnismenge 15.12.4 Nachladen von Datensätzen konfigurieren 15.12.5 Navigieren in der Ergebnismenge 15.12.6 Werte der Ergebnismenge ermitteln 15.12.7 Update der Ergebnismenge 15.12.8 Nullwerte 15.13 Auslesen von Metadaten Für Werkzeuge, die mit beliebigen Datenbankstrukturen arbeiten, sind Metadaten unerlässlich. Metadaten, auch Metainformationen genannt, sind Daten, die Datenbankstrukturen und deren Eigenschaften beschreiben. Eine Metainformation ist beispeilsweise die Auskunft, welche Tabellen sich in einer Datenbank befinden oder welche Attribute in einem ResultSet enthalten sind. JDBC stellt zwei Klassen bereit, mit denen Metadaten abgefragt werden können: ResultSetMetaData liefert Informationen über ein bestimmtes ResultSet. DataBaseMetaData liefert Informationen zu Datenbanken. 15.13.1 Datenbankinformationen ermitteln 15.13.2 Informationen über eine Ergebnismenge ermittlen 15.14 Transaktionen 15.14.1 Manuelles und automatischs Commit 15.14.2 Isolationsebenen 15.14.3 Dirty Read 15.14.4 Repeatabel Read 15.14.5 Phantom Read 15.15 JDBC Option Package 15.15.1 DataSource Ein DataSource-Objekt repräsentiert eine Datenbank oder eine beliebige andere Datenquelle und beinhaltet die wichtigsten Informationen darüber. Verschiedene Datenquellen können so im gleichen Programm abstrakt gehandabt werden. Für den Client sehen diese Datenquellen gleich aus. Ein DataSource-Objekt wird in der Regel von einem separaten Programm erzeugt und beim Naming-Service registriert. Clients können dann im Netzwerk auf diese Datenquelle über ihren logischen Namen zugreifen. Es ist nicht mehr notwendig die genau URL der Datenbank zu kennen. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 201 / 210 23.03.2010 PROMOD-2a / SS10 15.15.2 Connection-Pooling Der Aufbau von Datenbankverbindungen kostet Performance. Durch das Verwalten von geöffneten Verbindungen in einem Pool können diese Kosten reduziert werden. 15.15.3 Verteilte Transaktionen Das XA-Protokoll (ein X/Open Standard) wird verwendet, wenn mehrere Datenbanken oder allgemeiner Datenquellen innerhalb einer Transaktion angesprochen werden sollen. Denkbar ist beispielsweise die Anbindung eines Altsystems, das parallel weiter betrieben wird und damit auch die Änderungen erhalten muss. Für die Implementierung verteilter Transaktionen ist die XASchnittstelle mit dem dem spezifizierten Zwei-Phasen-Commit notwendige Voraussetzung. 15.16 Datenbankzugriff über Applets 15.17 Debuggen von JDBC-Anwendungen 15.18 CaseStudy Aufgabe (Aufgabe 1 von 030_JDBC) Wenn Sie mit Eclipse arbeiten und falls noch nicht geschehen, erstellen sie ein neues Eclipse Projekt. Achten sie darauf, dass der JDBC Treiber für die MySQL Datenbank richtig in das Projekt eingebunden ist. Erstellen sie eine neue Java Datei und folgen sie den Anweisungen: 1. Laden sie den Datenbanktreiber und erstellen sie eine Verbindung zur Datenbank ‚promod2’ her. Achten sie gegebenenfalls darauf, dass die Datenbank auf einem externen Server liegen könnte und sich somit die URL ändern wird. 2. Sie sollen mithilfe eines Statement Objektes eine neue Tabelle ‚semester’ erstellen. Folgende Attribute soll die Tabelle haben: • Die Matrikelnummer als INTEGER Typ • Vorname als VARCHAR Typ mit der Länge 32 • Dasselbe für den Nachnamen • Die Semesterstufe als INTEGER Typ • Die Note als DOUBLE Typ 3. Überprüfen sie mit einer Ausgabe auf die Konsole, ob die Tabelle korrekt erstellt wurde. Erklären sie, warum der Rückgabewert entweder false oder 0 sein muss. 4. Füllen sie die Tabelle mit Daten. Verwenden sie dazu einmal ein normales Statement und für die restlichen PreparedStatements. Setzen sie dabei die Vorzüge eines PreparedStatement ein. Diese Daten sollten in der Tabelle zu finden sein: Matrikelnummer 111222 123456 987654 135797 Prof. Illik Vorname Max Philipp Maria Klara Nachname Muster Mueller Durchschnitt Stein Semester 4 5 4 4 PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 202 / 210 Note 1.3 2.0 3.0 4.7 23.03.2010 PROMOD-2a / SS10 5. Wenn sie die Methode executeUpdate() verwenden, zeigen sie den Unterschied zu Punkt 3 auf und erklären sie ihn. 6. Schließen sie die Verbindung korrekt. 15.19 CaseStudy Lösung import import import import import java.sql.Connection; java.sql.DriverManager; java.sql.PreparedStatement; java.sql.SQLException; java.sql.Statement; public class Loesung_jdbc_1 { public static void main(String[] args) { try { // Laden des Treibers Class.forName("com.mysql.jdbc.Driver").newInstance(); } catch (Exception e) { System.err.println("Treiber konnte nicht geladen werden: " + e); } try { String url = "jdbc:mysql://127.0.0.1/promod2"; /////////////<<<<-------Connection con = DriverManager.getConnection(url, "benutzer","passwort"); Statement stmnt = con.createStatement(); String createTable = + + + "CREATE TABLE semester" "(matrikelnummer INTEGER, vorname VARCHAR(32), " "nachname VARCHAR(32), semester INTEGER, " "note DOUBLE)"; if (stmnt.executeUpdate(createTable) == 0) { System.out.println("Tabelle semester wurde erfolgreich erstellt." + " Der Rueckgabewert ist 0, weil ein CREATE " + "Statement nichts zurueckgibt."); } String insertData = "INSERT INTO semester(matrikelnummer, vorname, " + "nachname, semester, note) " + "VALUES(111222, 'Max', 'Muster', 4, 1.3)"; if(stmnt.executeUpdate(insertData) > 0 ){ System.out.println("Daten wurden korrekt in der DB gespeichert." + " Der Unterschied zum CREATE Statement liegt darin, " + "das 1 Zeile der Tabelle hinzugefuegt" + " und dieser Wert zurueckgegeben wurde."); } String preparedSt = "INSERT INTO semester(matrikelnummer, vorname, " + "nachname, semester, note) " + "VALUES(?, ?, ?, ?, ?)"; PreparedStatement p_stmnt = con.prepareStatement(preparedSt); p_stmnt.setInt(1,123456); p_stmnt.setString(2,"Philipp"); p_stmnt.setString(3,"Mueller"); p_stmnt.setInt(4,5); p_stmnt.setDouble(5,2.0); if(p_stmnt.executeUpdate()> 0){ System.out.println("Daten wurden korrekt in der DB gespeichert."); } p_stmnt.setInt(1,987654); p_stmnt.setString(2,"Maria"); Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 203 / 210 23.03.2010 PROMOD-2a / SS10 p_stmnt.setString(3,"Durchschnitt"); p_stmnt.setInt(4,4); p_stmnt.setDouble(5,3.0); if(p_stmnt.executeUpdate()> 0){ System.out.println("Daten wurden korrekt in der DB gespeichert."); } p_stmnt.setInt(1,135797); p_stmnt.setString(2,"Klara"); p_stmnt.setString(3,"Stein"); p_stmnt.setInt(4,4); p_stmnt.setDouble(5,4.7); if(p_stmnt.executeUpdate()> 0){ System.out.println("Daten wurden korrekt in der DB gespeichert."); } } } //end con.close(); } catch (SQLException e) { System.err.println("Ein Fehler ist aufgetreten: " + e); } 15.20 Die Datenbank Derby Seit Java 6 (Jahr 2005) ist die Datenbank „Derby“ Bestandteil von Java. Derby wurde ursprünglich unter dem Namen Cloudscape von IBM entwickelt. Der Computerkonzern überließ den Quellcode der Apache Foundation, die die Datenbank unter dem neuen Namen als OpenSource-Projekt weiterentwickelt. WORKSHOP: Bearbeiten Sie die 2. Aufgabe der Case Studie 030_JDBC. Nachdem die „Inbetriebnahme“ Ihrer JDBC-Lösung geklappt hat, entwickeln Sie diese weiter. Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 204 / 210 23.03.2010 PROMOD-2a / SS10 16. Promod Tipps 16.1 Methodik: Scrum “Programmieren und Modellieren (=PROMOD) ohne Tools und Methodik is nix.” Diese Erkenntnis ist nicht gerade neu. Promod Tipp macht Tools und Methoden schmackhaft. Heute gehts um Scrum. Scrum gehört zu den agilen Methoden, die auf einem iterativen Ansatz basieren. Die Wurzeln von Scrum reichen zurück auf Veröffentlichungen von Hirotaka Takeuchi und Ikujiro Nonaka (es geht um eine Methode zur Beschleunigung von kommerziellen Produktentwicklungen) aus dem Jahr 1986 bzw. in das Jahr 1991, in dem DeGrace und Stahl in ihrem Werk “Wiked Problems, Righteour Solutions” den Ansatz von Takeuchi und Nonaka als als “Scrum Approach” bezeichneten. Beiträge von Ken Schwaber und Jeff Sutherland runden die Methode ab. Infortainment: Scrum http://www.illik-online.com/wordpress_blogpod/?p=115 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 205 / 210 23.03.2010 PROMOD-2a / SS10 *** Ende Skript *** Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 206 / 210 23.03.2010 PROMOD-2a / SS10 17. Literatur (nehmen Sie jeweils die jüngste Ausgabe) Downloads (Vorlesung): jdk-6u10-docs; java_ee_javadocs; langspec-3.0 (Sie finden die Dokumente auf der Sun Microsystem Site) Pflichtlektüre (Vorlesung): Goll/Weiß/Müller: „Java als erste Programmiersprache – Vom Einsteiger zum Profi“, Verlag Teubner, Stuttgart/Leipzig/Wiesbaden, Empfohlene Lektüre (Vorlesung/Übungen): Christian Ullenboom: „Java ist auch eine Insel“, , geb., mit CD, 49,90 Euro, Galileo Computing, Bonn, Cay S. Horstmann, Gary Cornell „Core Java. Band 1 – Grundlagen“ Markt+Technik Verlag, München, Die englische Version ist “revised and updated vor Java SE 6” Weitere empfohlene Literatur (Übungen/Projekte ) Verteilte Systeme: J. Anton Illik “Verteilte Systeme. Architekturen und Software-Technologien“ Expert Verlag, Renningen, 2007 ISBN 978-3-8169-2730-3 Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 207 / 210 23.03.2010 PROMOD-2a / SS10 Johannes Nowak „Fortgeschrittene Programmierung mit Java 5“ dpunkt Verlag, Heidelberg, Cay S. Horstmann, Gary Cornell „Core Java. Band 2– Expertenwissen“ Markt+Technik Verlag, München, Die englische Version ist “revised and updated vor Java SE 6” Mark Wutka „J2EE Developer’s Guide“ Markt+Technik Verlag, München, Michael Kroll, Stefan Haustein „J2ME Developer’s Guide“ Markt+Technik Verlag, München, Gottfried Wolmeringer “Profikurs Eclipse 3“ Vieweg Verlag, Wiesbaden, Volker Turau, Krister Saleck, Christopher Lenz „Web-basierte Anwendungen entwickeln mit JSP2. Einsatz von JSTL, Struts, JSF, JDBC, JDO, JCA” d-punkt Verlag, Heidelberg Weitere Projekt-Literatur zu den Tools: Jakarta Struts: James Turner, Kevin Bedall “Struts” Addison-Wesley; James Goodwill „Mastering Jakarta-Struts“ Wiley, Behandelt jedoch nur Version 1.0! Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 208 / 210 23.03.2010 PROMOD-2a / SS10 Ted Husted “Struts in Action” Manning; James Holmes; “Struts – The Complete Reference” McGraw-Hill Osborne; Tomcat: “Professional Apache Tomcat 5” Wrox-Press; I CVS: Karl Fogel, Moshe Bar; “Open Source-Projekte mit CVS” MITP Offizielles Manual von Per Cederqvist https://www.cvshome.org/docs/manual/ ANT: Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 209 / 210 23.03.2010 PROMOD-2a / SS10 Bernd Matzke; „Ant – Das Java Build Tool in der Praxis“ Addison-Wesley; - siehe auch Empfehlung aus der Vorlesung – Prof. Illik PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc 210 / 210 23.03.2010