PROMOD-2 / Ausgabe 2016 Ausgabe 2016 (BuggyVersion) MitMach-Skript/Materialien zur Vorlesung Prof. J. Anton Illik PROMOD-2 „Java: 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_v13_160113_students2print.docx 1 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 „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 Ergänzend werden in der Vorlesung weitere Inhalte vermittelt (bevorzugt aus dem Bereich „Projekt-Methodik“/SCRUM/QS- & Abnahme-Methodik, Projekt-Doku). Wird in der Vorlesung behandelt und ist prüfungsrelevant. Vorteil für Sie: Sie üben schon in der Vorlesung und prägen sich so die Inhalte ein. Tipp: Fertigen Sie ein Protokoll als Mitschrift an! Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 2 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Support im Internet: Unter https://www.youtube.com/user/illikhans finden Sie zu allen Kapiteln VideoVorschauen („ScriptPreviews“), mit der ein oder anderen inhaltlichen Motivation zu dem, was wir im Kolloquium besprechen und für die Projekte nützlich ist. Die „ScriptPreviews“ dienen der Vor- und Nachbereitung des Kolloquiums. Die ScriptPreview-Videos folgen dem Namens-Schema „PROMOD2_Episode#_ILLIK_Kap_#_ScriptPreview“ Kapitel-Nummer Episoden-Nummer „ECLIPSE_HandsOn_#_PROMOD1“ liefert Erläuterungen zur IDE Eclipse. Die Episoden erscheinen semesterbegleitend. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 3 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Inhaltsverzeichnis 1. Vererbung (Wiederholung & Ergänzung) ................................................................................. 11 1.1 Grundlagen ......................................................................................................................... 11 1.2 Die Klasse Object ............................................................................................................... 11 1.3 Konstruktoraufrufe: ............................................................................................................. 14 1.4 Vererbungsketten und Zuweisungskompatibilität: .............................................................. 15 1.5 Case-Study Objekt-Arrays, Zuweisungskompatibiliät, Operator instanceof ....................... 16 1.6 Case-Study Lösung: ........................................................................................................... 16 1.7 Polymorphie ....................................................................................................................... 18 1.7.1 Beispiel 1 – Methoden überschreiben: ........................................................................ 18 1.7.2 Beispiel 2 – "late binding – Polymorphismus": ............................................................. 19 1.8 Abstrakte Klassen und Methoden: ..................................................................................... 20 1.9 Finale Klassen: ................................................................................................................... 21 2. Packages (Selbststudium!) ...................................................................................................... 22 2.1 Grundlagen ......................................................................................................................... 22 2.2 Packages importieren: ........................................................................................................ 22 2.3 Packages definieren: .......................................................................................................... 23 2.4 Zugriffsrechte in Packages: ................................................................................................ 23 3. Interfaces (und Ergänzungen zu Klassen) ............................................................................... 26 3.1 Grundlagen ......................................................................................................................... 26 3.2 Adapterklassen ................................................................................................................... 29 3.3 Innere Klassen (auch: geschachtelte / eingebettete / nested class) .................................. 30 3.4 Lokale Klassen ................................................................................................................... 31 3.5 Anonyme Klassen .............................................................................................................. 31 3.6 Statische innere Klassen (static class) / statische innere Schnittstellen ............................ 32 4. Ein-/Ausgabe auf Konsole,Dateien u.a.(ADRELI_CON) .......................................................... 34 4.1 Grundlagen ......................................................................................................................... 34 4.2 Standard-Ein- und Ausgabe ............................................................................................... 35 4.3 Die Klasse File: .................................................................................................................. 37 4.4 Die Klasse RandomAccessFile: ......................................................................................... 40 5. Streams: (u.U. ADRELI_CON) ................................................................................................. 44 5.1 Grundlagen ......................................................................................................................... 44 5.2 Überblick (UNICODE-) Character-Stream-Klassen (Zeichendaten (Text) schreiben und Lesen) ......................................................................................................................................... 45 5.3 Character–Stream-Methoden: ............................................................................................ 46 5.3.1 Ausgabe-Streams ........................................................................................................ 46 5.3.2 Eingabe-Streams ......................................................................................................... 46 5.4 Beispiele/Anwendungen zu Character–Streams: ............................................................... 48 5.5 Überblick Byte-Stream-Klassen (auch: binäres Schreiben u. Lesen) ................................ 51 5.6 Byte–Stream-Methoden: .................................................................................................... 51 6. Multi-Threading (ADRELI_THREADS++) ................................................................................ 56 6.1 Grundlagen ......................................................................................................................... 56 6.2 Klasse Thread und Interface Runnable .............................................................................. 58 6.3 Zustände von Threads: ...................................................................................................... 62 6.3.1 Threads unterbrechen ................................................................................................. 62 6.3.2 Die Thread-Methoden yield(), sleep() und interrupt() .................................................. 62 6.3.3 Die Thread-Methode join() ........................................................................................... 63 6.3.4 Weitere Thread-Methoden (ggf. geerbt von class Object) ........................................... 64 6.3.5 Thread-Konstruktoren .................................................................................................. 64 6.4 Daemon-Threads ............................................................................................................... 66 6.5 Prioritäten von Threads und zugehörige Thread-Methoden: .............................................. 67 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 4 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 6.6 Gruppen von Threads ........................................................................................................ 67 6.7 Synchronisation und zugehörige Methoden (von class Object geerbt): ............................. 67 6.8 Datenaustausch zwischen Threads mit Pipes: ................................................................... 71 6.9 Weitere Synchronisationsmittel .......................................................................................... 74 6.10 Thread-Synchronisation mit CyclicBarrier ........................................................................ 75 6.11 Zusammenfassung Threads: ............................................................................................ 77 6.12 Zusammenfassung Pipes: ................................................................................................ 77 7. Exkurs: how to model an application? ..................................................................................... 78 7.1 Als Kompass/Leitplanke dienen Vorgehensmodelle .......................................................... 82 7.2 Ihre Einschätzung? Welches Vorgehensmodell ist wofür geeignet? .................................. 85 7.3 Und wie geht`s im Detail? .................................................................................................. 86 8. Netzwerkzugriff ........................................................................................................................ 87 8.1 Grundlagen ......................................................................................................................... 87 8.1.1 Protokolle ..................................................................................................................... 87 8.1.2 Adressierung: IP-Adresse und Port-Nummer .............................................................. 88 8.1.3 Die Datei hosts und DNS (Domain Name System) ..................................................... 90 8.2 Klassen für die Adressierung ............................................................................................. 91 8.2.1 Die Klasse InetAddress: (siehe API-Doku)This class represents an Internet Protocol (IP) address. ............................................................................................................................ 91 8.2.2 Die Klasse URL ........................................................................................................... 93 8.3 Socket-Programmierung .................................................................................................... 96 8.3.1 Client-Sockets: die Klasse socket („Kommunikationssocket “) .................................... 98 8.3.2 ServerSockets: die Klasse ServerSocket .................................................................... 99 8.3.3 Beispiel – Server-Sockets (server.java): .................................................................... 100 8.3.4 Beispiel – Client-Sockets (client.java): ....................................................................... 101 8.4 Was kommt den da im Strom? Sockets und Streams ...................................................... 102 9. RMI Remote Method Invocation (skip it) ................................................................................ 104 9.1 Grundlagen ....................................................................................................................... 104 9.2 Die Bestandteile einer RMI-Anwendung .......................................................................... 105 9.3 Das Ablaufschema eines entfernten Methodenaufrufs .................................................... 106 9.4 Die Hilfsmittel RMI-Compiler und RMI-Registry ............................................................... 107 9.5 Der RMISicherheitsmanager (RMISecurityManager) ....................................................... 108 9.6 Die Komponenten und Bestandteile auf der Server-Seite ................................................ 109 9.7 Die Komponenten und Bestandteile auf der Client-Seite ................................................. 110 9.8 Vollständiges RMI-Beispiel ............................................................................................... 110 9.9 Zusammenfassung ........................................................................................................... 114 9.10 Kontrollfragen (Technologien / RMI) .............................................................................. 115 10. AWT-Basics: Graphical User Interface ................................................................................ 116 10.1 Grundlagen ..................................................................................................................... 116 10.2 AWT – Klassenhierarchie ............................................................................................... 118 10.3 „Fenster“: von der Component über den Container zum Frame .................................... 119 10.4 Klasse Window ............................................................................................................... 120 10.5 Klasse Panel: (abgeleitet von Container) ....................................................................... 121 10.6 Klasse Frame: (abgeleitet von Window) ......................................................................... 121 10.7 "Fensterbausteine" und der Umgang damit (Erzeugung / Konfiguration / Anzeige) ..... 123 10.8 Anzeigen und schließen: ................................................................................................ 124 10.9 Bestandteile eines Fensters (class Frame) .................................................................... 125 10.9.1 Das Fenstersymbol/Icon: ......................................................................................... 126 10.9.2 Fensterposition und -Größe: .................................................................................... 127 10.9.3 Fensterfarben: ......................................................................................................... 128 10.9.4 Der Mouse-Cursor: .................................................................................................. 129 11. AWT-Events: Ereignisse und Event-Handling ..................................................................... 131 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 5 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 11.1 Grundlagen ..................................................................................................................... 131 11.2 Ereignisklassen .............................................................................................................. 133 11.3 Listener-Interfaces .......................................................................................................... 134 11.4 Listener bei Ereignisquellen registrieren ........................................................................ 134 11.5 Implementierungsmöglichkeiten: .................................................................................... 136 11.5.1 Verwendung von Adapterklassen: ........................................................................... 136 11.5.2 Listener Interface wird implementiert: ...................................................................... 136 11.6 Getrennter Code für GUI und Applikation ...................................................................... 136 11.7 Low-Level-Ereignisse (systemnahe Ereignisse / Einige Beispiele): ............................... 138 11.7.1 Component-Ereignisse ( Interface ComponentListener): ................................... 138 11.7.2 Window-Ereignisse ( Interface WindowListener): ............................................... 140 11.7.3 Focus-Ereignisse ( Interface FocusListener):..................................................... 142 11.7.4 Die Eventklasse Tastaturereignisse (Klasse KeyEvent) und das ListenerInterface KeyListener): ............................................................................................... 143 11.7.5 Die Eventklasse Klasse MouseEvent und die Listener-Interfaces MouseListener, MouseMotionListener : .......................................................................................................... 146 11.8 Zusammenfassung Ereignisse und ihre Behandlung ..................................................... 149 12. AWT-Graphics: Grafikprogrammierung mit AWT ................................................................. 153 12.1 Grundlagen ..................................................................................................................... 153 12.2 Der Grafikkontext / Die Klasse Graphics: .................................................................... 153 12.3 Die Methoden paint() und repaint(): ............................................................................... 154 12.4 Anzeige von Text: ........................................................................................................... 156 12.4.1 Die Klasse Font / einige Grundbegriffe .................................................................... 157 12.5 Farben / die Klasse Color ............................................................................................... 161 12.6 Weitere Methoden zum Zeichnen aus der Klasse Graphics .......................................... 163 12.7 Bitmaps anzeigen: (nur gif und jpeg-Format)/ Die Klasse Image ................................... 165 13. AWT-Komponenten: ............................................................................................................ 169 13.1 Grundlagen ..................................................................................................................... 169 13.2 Überblick über AWT-Komponenten: ............................................................................... 170 13.3 Anwendung / Umgang mit den Komponenten: ............................................................... 171 13.3.1 Die Klasse Label ...................................................................................................... 172 13.3.2 Die Klasse Button .................................................................................................... 172 13.3.3 Die Klasse Textfeld (abgeleitet von TextComponent) ............................................. 174 13.3.4 Die Klasse TextArea (abgeleitet von der Klasse TextComponent) .......................... 176 13.3.5 Die Klasse Choice und die Klasse List: ................................................................... 176 13.3.6 Die Klasse Checkbox: .............................................................................................. 178 13.3.7 Die Klasse CheckboxGroup: .................................................................................... 178 13.3.8 Die Klasse Scrollbar: ............................................................................................... 179 13.3.9 Die Klasse Scrollpane .............................................................................................. 181 13.4 Dialoge ........................................................................................................................... 181 13.4.1 Die Klasse Dialog: ................................................................................................... 181 13.4.2 Der Dateidialog (Klasse FileDialog) ................................................................... 182 13.5 Menüs: ............................................................................................................................ 184 13.5.1 Die Menüleisten (Klasse MenuBar) ......................................................................... 185 13.5.2 Die Menüs (Klasse Menu) ........................................................................................ 185 13.5.3 Die Menü-Einträge (Klasse MenuItem) .................................................................. 185 13.5.4 Kontextmenüs (Klasse PopupMenu) ....................................................................... 189 14. LayoutManager (ein Überblick) ............................................................................................ 192 14.1 Grundlagen ..................................................................................................................... 192 15. Elementare Dienste für JEE: JDBC ..................................................................................... 200 15.1 Hinführung ...................................................................................................................... 200 15.2 Grundlagen ..................................................................................................................... 200 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 6 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 15.3 JDBC als Basis für andere APIs: SQLJ und JDO .......................................................... 201 15.3.1 SQLJ ........................................................................................................................ 201 15.3.2 SQLJ im Vergleich zu JDBC .................................................................................... 202 15.3.3 SQLJ und der Übersetzungprozess ......................................................................... 203 15.3.4 Persistente Java Objekte: JDO Java Data Objects ................................................. 203 15.4 Architektur von JDBC ..................................................................................................... 203 15.5 Treibertypen ................................................................................................................... 204 15.6 Die Datenbank MySQL / Die DBMS Cloudscape / PointBase ....................................... 206 15.7 Aufbau einer Datenbankverbindung ............................................................................... 206 15.8 SQL-Anweisungen ausführen ........................................................................................ 210 15.9 Die Klasse Statement ..................................................................................................... 210 15.10 Klasse PreparedStatement .......................................................................................... 212 15.11 Klasse CallableStatement / StoredProcedure .............................................................. 213 15.12 Auswertung der Ergebnismengen ................................................................................ 213 15.12.1 Boolean und Integer als Rückgabewerte ............................................................... 214 15.12.2 ResultSet ............................................................................................................... 214 15.12.3 Konfigurieren der Ergebnismenge ......................................................................... 217 15.12.4 Nachladen von Datensätzen konfigurieren ............................................................ 217 15.12.5 Navigieren in der Ergebnismenge ......................................................................... 217 15.12.6 Werte der Ergebnismenge ermitteln ...................................................................... 217 15.12.7 Update der Ergebnismenge ................................................................................... 217 15.12.8 Nullwerte ................................................................................................................ 217 15.13 Auslesen von Metadaten .............................................................................................. 217 15.13.1 Datenbankinformationen ermitteln ......................................................................... 217 15.13.2 Informationen über eine Ergebnismenge ermittlen ................................................ 217 15.14 Transaktionen ............................................................................................................... 217 15.14.1 Manuelles und automatischs Commit .................................................................... 217 15.14.2 Isolationsebenen .................................................................................................... 217 15.14.3 Dirty Read .............................................................................................................. 217 15.14.4 Repeatabel Read ................................................................................................... 217 15.14.5 Phantom Read ....................................................................................................... 217 15.15 JDBC Option Package ................................................................................................. 217 15.15.1 DataSource ............................................................................................................ 217 15.15.2 Connection-Pooling ............................................................................................... 218 15.15.3 Verteilte Transaktionen .......................................................................................... 218 15.16 Datenbankzugriff über Applets ..................................................................................... 218 15.17 Debuggen von JDBC-Anwendungen ........................................................................... 218 15.18 CaseStudy Aufgabe (Aufgabe 1 von 030_JDBC) ........................................................ 218 15.19 CaseStudy Lösung ....................................................................................................... 219 15.20 Die Datenbank Derby ................................................................................................... 220 16. Promod Tipps ....................................................................................................................... 221 16.1 Methodik: Scrum ............................................................................................................ 221 17. Eclipse Tipps ........................................................................................................................ 221 17.1 Javadoc .......................................................................................................................... 221 18. JavaDoc - Tutorial ................................................................................................................ 221 18.1.1 Was ist JavaDoc? .................................................................................................... 221 18.1.2 Wie erstelle ich JavaDoc? ....................................................................................... 222 18.1.3 JavaDoc in den JBuilder einbinden ......................................................................... 222 18.1.4 Richtig dokumentieren & Tags ................................................................................. 223 18.1.5 Wichtige Optionen ................................................................................................... 223 18.1.6 Related Links ........................................................................................................... 224 19. Literatur (nehmen Sie jeweils die jüngste Ausgabe) ............................................................ 225 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 7 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 8 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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! Die Projekte, bzw. die Projektergebnisse dienen auch der Lernstantskontrolle! Deshalb unbedingt an den QS-Gesprächen teilnehmen! Der Modul Promod-2 ist als seminaristische Lehrveranstaltung organisiert. Im theoretischen Teil erfolgt 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 oder werden Ihnen auf einem anderen Medium übergeben. Projekte (die „Übungen“) werdem im Praktikumsblock durchgeführt. Die hierfür vorhandenen Aufgabenstellungen lösen die Praktikumsteilnehmer selbständig. Der Praktikumsblock gestaltet sich wie die Arbeit in einem realen Projekt – stellen Sie sich vor, Sie und Ihre Kommiliton/innen im Team sind Ihre eigene, neu gegründete Firma, und Sie bearbeiten die Projekte im Kundenauftrag. 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 SystemArchitekturen entwerfen und mit JAVA implementieren. Die Architekturen können multi-threaded sein und über eine GUI verfügen. Unsere Vorgehensweise orientiert sich an SCRUM. 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. Der Hands-On-Teil findet jeweils nach dem. Vorlesungsblock statt und hat mittel- oder unmittelbar etwas mit den Problemlösungen der Projektaufgaben zu tun. Der die Vorlesung begleitende Projektteil startet mit den Case Studies im 2. Block und setzt sich im 3. Block fort. Für den Projektteil gibt es eigene Projektaufgaben. Die Vorlesung orientiert sich an den Inhalten der 5 Adreli-Projekte. Das Skript lesen Sie am besten am Rechner mit geöffneter Entwicklungsumgebung (Eclipse) und den griffbereiten CaseStudies. Eclipse selbst ist nicht zentraler Gegenstand der Veranstaltung, wird aber doch hin und wieder thematisiert. Für die Auseinandersetzung mit Eclipse werden wir auf eine Reihe von Videos zurückgreifen. Sonstiges: Das Trichtersymbol (Bild rechts) macht auf Hinweise zur Entwicklungsumgebung aufmerksam. Sun Tutorials: Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 9 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 For the most accurate and up-to-date tutorials, please access the latest version from Oracle's official website for the Java SE Tutorials (Last Updated 03/02/20112), which can be found at: http://download.oracle.com/javase/tutorial. Tools & Server: • Ggf mehr dazu in Kolloquium Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 10 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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) String.toString() 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) Class getClass() Returns the runtime class of an object. void notify() Siehe Multi-Threading. void notifyAll() Siehe Multi-Threading. void wait () Siehe Multi-Threading. Object clone () Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 11 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 protected void finalize() 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_v13_160113_students2print.docx 12 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Beispiel: public class ObjektMethoden { 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())); } 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()); } } Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 13 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (Speicherplatz für) 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_v13_160113_students2print.docx 14 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 class computerverwaltung 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_v13_160113_students2print.docx 15 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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(); } Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 16 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 if (computer_array[counter] instanceof Server){ ((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); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 17 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 this.anzahlProz = anzahlProz; } 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_v13_160113_students2print.docx 18 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } 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_v13_160113_students2print.docx 19 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 20 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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; kein Binding-Verwaltung zur Laufzeit). 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 Methode 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_v13_160113_students2print.docx 21 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 Eine moderene Programmiersprache muss das Design (den Entwurf) eines Programmes mit 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 22 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Über eine import-Anweisung kann immer nur ein Package eingebunden werden. Besitzt ein Package weiterere Unterpackages, so müssen diese separart importiert werden. 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_calendar.getDate()); } } --------------------------------util.greg_calenadr.java----------------------------------------------------------package bsp_u_ueb_erstes_semester.woche_10_1gp_kap10_packages.util; public class greg_calendar { static String prefix; Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 23 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 24 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 25 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (= Methodensignaturen) 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 (= Schnittstelllen der Klasse / Protokoll der Klasse) besitzt. Methoden werden nur deklariert, nicht implementiert. Klassen können mehrere Interfaces implementieren (=> „Mehrfachvererbung“). D.h. sie implementieren die Methoden meherer Interfaces. Interfaces können von anderen Interfaces erben/abgeleitet werden. Interfaces werden mit dem Schlüsselwort "interface" eingerichtet Definition: [public] interface <Name> [extends <Interface>[,<Interface>]] { [public, static] final <Typ> Konstante; [public] <Typ> Methode(<Parameter>); } Im plem entierung: 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 26 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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. 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"); // } // unterschiedliche Implementierungen } // eines Interfaces // class TestClass2 implements PoliteObject { // public void sayHello() { // System.out.println("Guten Tag"); // } } 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(); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 27 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } // Datum1.java im default Package-------------------------------------------public class Datum1 implements Ausgabetypen // alle Ausgabetypen-Methoden { // müssen implementiert werden!!! private int tag, monat, jahr; public Datum1() { this(1, 1, 2002); } alle Interface-Methoden werden implementiert 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 28 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 { private Ausgabetypen[] at = new Ausgabetypen[4]; public Test10() { at[0] = new Datum1(4, 3, 2002); at[1] = new Zeit1(0, 12, 12); at[2] = new Datum1(8, 9, 2001); at[3] = new 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 von 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_v13_160113_students2print.docx 29 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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() {} // Leere Methoden, die im extend überlagert werden 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: Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 30 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 class aussen // Implementierungsdetails verborgen in inneren Klassen { class mitte // nur zur Verwendung in aussen { 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 Lokale Klassen sind Klassen, die in Methoden definiert werden. Quiz: Wann können lokale Klassen sinnvoll sein? => Wie eine innere Klasse kann eine lokale Klasse auf alle Methoden (hier nicht relevant) und Daten einer äusseren Klasse (hier eine Methode) zugreifen. Damit sind die Daten aus der Sicht der Methoden der inneren Klasse „global“. Das kann für die Implementierung von Hilfsmethoden sinnvoll und hilfreich sein. 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 31 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Beispiel – anonyme Klassen: // Ausgabetypen.java im default-Package------------------------------------------public interface Ausgabetypen { void ausgabe(); void ausgabeLang(); 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); 2 } Überschreibt }); leere Methode } public static void main(String[] args) { new Test14(); // Start via Konstruktor } aus der Adapterklasse 1 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_v13_160113_students2print.docx 32 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 33 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (random access) 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 (Java-)Applikationen und (Java-)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 InputStream/OutputStream) für Binär-I/O 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. Und last but not least: Mittels class PipedInputStream und class PipedOutputStream lassen sich (serializable) Objekte streamen. 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”) Während der Datenübertragung können Daten gefiltert (bearbeitet) werden Merkmale von wahlfreien Dateien/ Random-Access-Dateien: Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 34 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (in der Regel 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 Genaueres zu den Methoden siehe Class E/A-Beispiele, Methoden, wie bereits bekannt System.in.read() System.out.print() System.err.print() InputStream und Class Printstream 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_v13_160113_students2print.docx 35 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 "out"-Methoden aus class PrintStream 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() "read"-Methoden aus class InputStream Liefert nächstes Zeichen aus dem Eingabestrom als return read(byte[] b) 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 //(“skip over and discard”) 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_v13_160113_students2print.docx 36 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 File f3 = new File("tmp" + File.separatorChar + "file3.txt"); 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 Stammverzeich-nissen entsprechen Manipulstionsoperationen boolean createNewFile() Legt eine neue, leere Datei mit dem entsprechenden Namen an (RECHTE!!!) boolean delete() Versucht die Datei zu löschen, 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() 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" Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 37 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 boolean isDirectory() 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 APIDoku class File. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 38 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Beispiel: import java.io.*; public class FileInfo { public static void main(String[] args) { File verzX = new File("/temp/a/b/c/d"); // File verzX = new File("." + File.separatorChar + "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_v13_160113_students2print.docx 39 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 4.4 Die Klasse RandomAccessFile: Dateien mit wahlfreiem Zugriff – d.h. über geeignete Methoden kann auf jede gewünschte Dateiposition zugegriffen werden. 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“. Allerdings ist kein indizierter Zugriff möglich– wie bei Arrays üblich. Eine Random-Access-Datei ist eben doch kein Array. Der Seek-Pointer kann innerhalb der Datei beliebig auf jede Byte-Position 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 40 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 { public static void main(String[] args) { 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()); } } } Hinweis: System Independent Newline Characters Für Files: public static String newline = System.getProperty("line.separator"); Für die Konsole: ’\n’ ist für Konsolausgaben auf Windows, Linux, Mac gleichermaßen OK. 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 41 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 byte readByte() Reads a signed eight-bit value from this file. 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) 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) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 42 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 43 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 5. Streams: (u.U. 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_v13_160113_students2print.docx 44 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Die Hauptklassen von java.io: Object OutputStream InputStream Writer Reader File RA-File ... Byte-Stream Binäerdaten Lesen/Schreiben FlterOutputStream Character-Streams Zeichendaten Lesen/Schreiben ObjectStreamClass StreamTokenizer Siehe oben println() PrintStream FileDescriptor Siehe oben read() In Java werden generelle zwei Streamtypen unterschieden: Character Streams (zeichenartige Daten (Unicode Text)) und Byte Streams (binäre Daten, ASCII 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 JavaQuellcode ist ASCII-Code. 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 Wichtige Klassen - Writer-Ableitungen: (Character-) Klassen BufferedWriter CharArrayWriter OutputStreamWriter Was die Klasse leistet Gepuffertes Schreiben von Chars/Char-Arrays/Char-Strings. 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). Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 45 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 FilterWriter PipedWriter StringWriter PrintWriter Abstrakte Basisklasse der verschiedenen Implementierungen zum gefilterten Schreiben Ausgabestrom, geschrieben wird in eine Character-Pipe Ausgabestrom, der in einen String schreibt Ausgabestrom, der formatierte Objektrepräsentationen schreibt. Methoden dieser Klasse 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. Wichtige Klassen - Reader-Ableitungen: (Character-) Klasse 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) 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 boolean markSupported() Gibt Auskunft darüber, ob der Eingabestrom die Positonsmarkierung, und das Rücksetzen darauf, unterstützt int read() zur Extraktion(= EOF int read(char[] cbuf) zur Extraktion Lesen) genau eines Zeichens (16 Bit Character), -1 bei (= Lesen) einer Sequenz, beginnend ab der aktuellen Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 46 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Stromzeigerpositon int read(char[] cbuf, int off, int len) boolean ready() reset() long skip(long n) zur Lesen einer Sequenz der Länge len oder weniger von Zeichen, und Ablage in Array beginnend ab der Position off Lesetest. 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 47 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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, andernfalls das Zeichen // 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()); } String s; Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 48 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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) { String s = "Java macht Spass "; Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 49 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 // 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_v13_160113_students2print.docx 50 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 5.5 Überblick Byte-Stream-Klassen (auch: binäres Schreiben u. Lesen) ByteStreams verwenden für die Ein- und Ausgabe von Datenströmen 1-Byte Größen. Da externe Datensenken und 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. Wichtige Klassen - 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 (z.B. in eine File) geschrieben werden (Siehe API-Doku-Bsp.) … Wichtige Klassen - InputStream-Ableitungen: Klasse (Byte-) FileInputStream PipedInputStream FilterInputStream ByteArrayInputStream StringBufferInputStream SequenceInputStream ObjectInputStream … Was die Klasse leistet Byteweises lesen einer Datei Hochsprachliches Äquivallent den 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() Liefert die Anzahl Bytes die an der Eingabeschnittstelle zur Verfügung stehen. Diese Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 51 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (EOF), 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 eines Objekts (Binäre Ein-/Ausgabe) // Das Objekt aList wird in eine Datei geschrieben, // dann wird das Objekt aus der Datei geholt und // dem Objekt bList 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("Mueller","Susi"); Person pers_2=new Person("Beta","Rudolf"); 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_v13_160113_students2print.docx 52 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 //...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); // wir haben nur ein Objekt aList oos.writeObject(aList); // und schreiben dieses Objekt aur // den Stream oos.close(); } catch(IOException e){ } } //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @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_v13_160113_students2print.docx 53 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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. Beispiel 7: Nochmals Eingabe von der Konsole („quick and easy“ per API-Docu Siehe CaseStudy Scanner Klasse – Siehe dazu PROMOD1/kap10b_io_datei_ScannerPrintWriter import java.util.*; import java.io.*; public class FileIO { public static void main(String[] args) throws Exception{ Scanner sc = new Scanner(System.in); PrintWriter pw = new PrintWriter("personen.txt"); String myLine; for(int i = 0; i < 4; i++) { System.out.print("Eingabe Zeile "+i+": "); myLine = sc.nextLine(); pw.println(myLine); // pw.flush(); } pw.flush(); System.out.println("Ende der Eingabe."); } } Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 54 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 55 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 6. Multi-Threading (ADRELI_THREADS++) hier weiter 21.10.2015 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 (ggf. für die Thread-Kommunikation über Pipes) 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 (keine privillegierte Operation). Threads werden häufig auch als leichtgewichtige Prozesse < (lightweighted processes) 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 existieren, die sich keine gemeinsamen Objekte teilen. Anwendungsmöglichkeiten: Threads werden hauptsächlich angewandet 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“ macht die eigentliche Verarbeitung/Haupaufgabe („MVC-Ansatz“). 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). Das Hauptprogramm 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_v13_160113_students2print.docx 56 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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“) Exkurs: 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 (im Java-Programm) freiwillig ab. - nicht geeignet bei Real-Time-Multithreading für kritische Anwendungsprozesse, die deterministisches Scheduling verlangen Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 57 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 6.2 Klasse Thread und Interface Runnable Threads werden in Java durch die Klasse Thread und/oder 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() !! Parallelität t2.run(); // synchroner Start; Anomalie !! ein Nacheinander 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() // Das ist der Thread-Code { 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 } !! ein Nacheinander !! ein Nacheinander 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, so wird der Code wiedereintrittsfähig („reentrant“). Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 58 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Das Schlüsselwort „synchronized“ sorgt dafür, dass nur jeweils ein Thread durch seinen Code läuft und auf die gemeinsamen Daten (lokale Variablen) zugreift. Siehe auch Kapitel „Synchronisation“ weiter unten. Nach dem Start der Threads mit der Methode start() laufen die Threads asynchron. Beispiel 2 – ThreadDemo: Mehrere Aufgaben werden parallel ausgeführt / 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++; } } } Typischerweise ist in den ThreadAbleitungen nur Code 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 } // höher Prior wie MyThread2_ catch (InterruptedException ie) { Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 59 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 System.out.println(ie.getMessage()); } } } class MyThread2_ extends Thread { public void run() { int i = 0; try { while (i <= 50) { System.out.println("zweiter Thread"); this.sleep(100); // kooperatives Multithreading // niedriger Prior wie MyThread2_ 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 60 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 // 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”=Leerlauf; //besser t.join(); 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..."); } } 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 („concurrent“) 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, 8) oder Linux, MAC OS X, der Gesamtmaschinenzustand sich während des Ablaufs eines Thread beträchtlich unterscheiden kann vom Zustand während des Ablaufs eines anderen Threads!!!!!!!!!!!!!! Auf einem Laptop laufen durchaus über 100 Prozesse mit über 1000 Threads (siehe bspw. „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 Anwendungsfä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 1 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. (Auf Windows, Linux und Max OS X: selbstsynchronisierend) 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.” Siehe hierzu auch Kapitel 6.9 Weitere Synchronisationsmittel 1 Betriebssystemabhängig Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 61 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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/Dispatcher start() new ready to run Ende von run() Oder: freiwilliges Beenden, wenn Interrupted-Flag gesetzt. active running end yield() ERZEUGEN notify() notifyAll() blocked BEENDEN - 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 (das muss Programmierer/in selbst tun!!!!!), ob das Interrupted Flag gesetzt ist und damit der Thread terminieren soll. (Passt zur Philosopie „kooperatives Multithreading“.) 6.3.2 Die Thread-Methoden yield(), sleep() und interrupt() HINWEIS: Werden Ihre Threads nicht quasi-parallel abgearbeitet, sondern nacheinander, so basiert Ihre JavaImplementierung (betriebssystemabhängig!) 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_v13_160113_students2print.docx 62 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 static void yield() Der Thread gibt CPU freiwillig ab (und wird 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 „readyto-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 Thread-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, Siehe Torso: Class ManagerThread { .... Thread Producer = new ProducerThread(); Thread Consumer = new ConsumerThread(); Producer.start(); Consumer.start(); Producer.join(); // Manager-Thread wartet, bis der Producer-Thread fertig ist Consumer.join(); // Manager-Thread warten, bis der Consumer-Thread fertig ist System.out.println(„Producer & Consumer: beide fertig.“) Dadurch wird der Thread, der die Methode aufruft (im Beispiel oben der ManagerThread), in den Zustand „blocked“ versetzt, bis der Thread, dessen join()-Methode aufgerufen wird (oben Consumer und Producer), 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.) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 63 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 6.3.4 Weitere Thread-Methoden (ggf. geerbt von class 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. (See: Case-Study threads_sync/synchronisation.java) Aktiviert einen Thread, welcher duch den Aufruf von wait() in den Wartezustand versetzt wurde. void notify() 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 64 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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; 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) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 65 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 { } } } public void setDelay(int m) // -----------------------------------------------{ millis = m; } public int getTimerNr() // ---------------------------------------------------{ 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. Java-VM wird runtergefahren.) Standardmäßig laufen alle erzeugten Threads als User-Threads. Aus einem 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ück, 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? Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 66 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 6.5 Prioritäten von Threads und zugehörige Thread-Methoden: 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??) 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 und zugehörige Methoden (von class Object geerbt): 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 //CaseStudy sync_test { static int cnt = 0; // für die Threads sichtbar Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 67 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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(); } } 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 { private int private int private int private int private int Lager artikelnr; bestand = 0; zugang = 0; abgang = 0; aendeungsnr = 0; public Lager(int anr) { artikelnr = anr; } public int getBestand() { return bestand; } Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 68 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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; } 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_v13_160113_students2print.docx 69 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Beispiel – Synchronisation zusätzlich mit wait() und notify(): import java.util.Vector2; class { Cnt static Vector<Integer> cnt = new Vector<Integer>(); } class Thread1 extends Thread { private int i=0; public void run() { while (true) { // bei Bloecken muß hier ein Reference-Type // stehen -> sonst compile-error. synchronized(Cnt.cnt) {// kritischer Bereich / kritischer Block 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) //kritischer Bereich { 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();// fungiert als set()er-Thread Thread2 t2 = new Thread2();// fungiert als get()er-Thread t2.start(); t1.start(); // Überlegen Sie: was passiert, wenn in den Threads wait() und notify() // weggenommen werden? Warum sind diese Methoden zwingend notwendig? 2 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_v13_160113_students2print.docx 70 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 // Experimentieren Sie: mal mehr set()-er-, mal mehr get()-er-Threads… } } Hands On: Untersuchen und erläutern Sie das Beispiel „Synchronisation2“ im Hands-OnTeil. 6.8 Datenaustausch zwischen Threads mit Pipes: hier weiter 28.10.15 Voraussetzung: gemeinsamer (Prozess-)Adressraum (gemeinsame Objekte, Datenfelder) Für die Realisierung von Pipes gibt es jeweils eigene Stream-Klassen für die byte- und characterorientierte 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 automatisch (!) angehalten, wenn die Pipe voll ist § Konsument wird automatisch (!) 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 Ausgabestrom PipedOutputStream PipedReader PipedWriter Stream-Typ ByteStream (binäre Kommunikation und ASCII) CharacterStream (UNICODE; TextKommunikation) In die Pipes, bzw von den Pipes werden 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 cbuf starting at offset off to this piped output stream. void write(int c) Writes the specified char to the piped output stream. See next page: Die Erbschaft: (Siehe auch oben Kapitel “Streams”) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 71 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Die Erbschaft: (Siehe auch oben Kapitel “Streams”) Object OutputStream InputStream PipedOutputStream PipedInputStream Writer PipedWriter automatisch (!) Reader automatisch (!) automatisch (!) PipedReader automatisch (!) 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_v13_160113_students2print.docx 72 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 { PipedWriter pw; int x = 0; extends Thread 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 { hier wird synch pw.write(x);// dran denken, . System.out.println(this.getName() + "\t" + "produziert: " + x); } } catch(IOException io) { System.out.println("Produzent: Fehler beim Schreiben"); } } } class Konsument { PipedReader pr; extends Thread public Konsument (ThreadGroup group, PipedReader pr) { super(group, "Konsument"); this.pr = pr; Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 73 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } public void run() { while(isInterrupted() == false) { try { int x = pr.read(); //liefert -1 (= end-of-stream),wenn Produzent Pipe closed. im read() wird synch . // 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. 6.9 Weitere Synchronisationsmittel (opional) Weiterreichende Mittel für die Synchronisation stellt Java mittels der folgenden Klassen bereit: Class Semaphore: zählende Semaphore zur Verwaltung des Zugriffs auf Ressourcen Class CountDownLatch: “A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes” Class CyclicBarrier: Wartezeiten/Synchronisation wird auf der Basis bestimmter Arbeitsvolumen/Arbeitsaufträge implementiert Class Exchanger: “A synchronization point at which threads can pair and swap elements within pairs.” An den Synchronisationspunkten tauschen Threads Daten aus. Hinweis. Siehe hierzu API-Doku. Hinweis: Vergleiche dazu die Hinweise im Kapitel 6.2 Klasse Thread und Interface Runnable zu den Synchronisationsarten Zeitgesteuerte Synchronisation Ereignisgesteuerte Synchronisation Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 74 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Kommunikationsgesteuerte Synchronisation 6.10 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(); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 75 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } catch (BrokenBarrierException ex) { ex.printStackTrace(); } } } 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_v13_160113_students2print.docx 76 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 6.11 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.12 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 geeignet • Sockets • RMI • CORBA • Messages (JMS Java Message Queue Service) • Web-Services, • u.a. 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_v13_160113_students2print.docx 77 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 7. Exkurs: how to model an application? Oder anders gefragt: How to manage the project? Und wie sieht die Theorie dazu aus? 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/QS 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, der Klassenorganisation, 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 (= 1 Adreli-Projekt) 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“, „Extreme Programming“ und „SCRUM“. Mit ein zentraler Punkt ist das „divide and conquere“-/„divide et impera“-: „teile und herrsche“-Prinzip. (Zur Erinnerung: dieses Prinzip haben wir auch in PROMOD-1 für die Entwicklung von Algorithmen – im Zusammenhang mit der „Funktionalen Abstraktion“ - diskutiert.) . Abb.: Spiralmodell von Böhm Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 78 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Die (verborgene) Gesamtaufgabenstellung von PROMOD2 ist die Entwicklung einer 1. Datenbank-Applikation die 2. im Netz verteilt ist (Client-Sever, Web-Service, …) 3 3. mulituser-fähig . 4 4. skalierbar und 5 5. ausfallsicher 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(er) administrierbar, o damit auch hohes Risiko (möglicherweise Abbruch, hohe Korrektur-/Änderungskosten; 10x10x10Regel!). o .... 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 6 zuerst mit dem Applikationskern (dem Datenmodell 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. Danach wurden teilweise – für den Benutzer nicht sichtbar – weitere technisch Eigenschaften eingebaut. (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, Abschätzung von Zeitbedarf für die Implentierung (einer User-Funktionalität) Kostenschätzungen (i.d.R. -> (Zeit in Stunden) x Stundensatz. ) 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“: 3 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 je ein dedizierter Thread 5 Zum Beispiel durch Einsatz eines clusterfähigen DB-Servers (z.B. MySQL [MySQL Cluster integrates the standard 4 MySQL server with an in-memory clustered storage engine called NDB. Siehe: http://dev.mysql.com/doc/refman/5.0/en/mysqlcluster-overview.html ]) 6 Datenmodell und Funktionalität sind in unserem Fall zugegebenermaßen trivial. Dies ist aber so beabsichtigt, damit die „Vorgehensstruktur“ und die benutzten Java-APIs im Vordergrund stehen und gut sichtbar sind! Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 79 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 1. 2. 3. Im Einzelnen: • • • Check = neue API in separater Case-Study implentieren Test = die Case-Study studieren/modifizieren/testen Integrate = nach dem totalen Verständnis der Case-Study neue API in App einbauen Bottom-Up vorgehen: d.h. in einem separaten Java-File die API studieren (ausprobieren und testen analog zu den Case-Studies) 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! Integrieren: die funkionierenden Code-Sequenzen aus Ihrer Case-Study in die zu entwickelnde App übernehmen. Mehr dazu: Siehe Adreli-1-Pattern 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 (Hauptthread als “Controler” und zwei separate Threads für “Model” und “View”): Adreli_2_Thread Adreli_3_Net VIEW / UI-Thread migriert bei Adreli_3_Net in das Client-Programm CONTROLLER-Thread migriert bei Adreli_3_Net (Hauptthread) • z.T in den Client- und • z.T. in das Server-Prog. (Client und Server haben jeweils ihren eigenen CONTROLLER-Thread) MODEL-Thread migriert bei Adreli_3_Net (komplett) in das ServerProgramm (ggf. der einfachheithalber UI- und CONTROLLERModel-Thread zusammenfassen, da die GUI sehr primitiv ist) Kommunikation der Threads, lt. Aufgabenstellung, mittels Kommunikationsmittel Pipe. a) UI bleibt mit der switch-case-Konstruktion wie bisher bestehen b) „Trick 1“: File-I/O-Methoden (sind 3 Methoden) redurzieren sich auf die Pipe-Kommunikation zwischen UI-Thread (VIEW) und File-I/O-Thread (MODEL). Nun: MODEL-Thread schreibt auf und liest von CVSRAC-File c) Entweder: der bisherige Buffer (z.B. ArrayList oder besser Vector) muss in den char[]-Buffer, den z.B. PipedWriter.write(), bzw. PipedOutputStream.write() braucht, „umgeladen“ / konvertiert werden. d) Oder PIPE_FITTING: Mit ObjectOutputStream- und ObjektInputStream-Methoden können Objekte über eine Pipe (nur über PipedOutputStream/PipedInputStream NICHT 7 PipedWriter/PipedReader) gestreamt werden . Vergleiche Beispiel aus der API-Doku dazu: Schreibender THREAD: FileOutputStream fos = new FileOutputStream("personen.dat"); //durch pipe/socket ersetzen // später PipedOutputStream (und PipedInputStream im lesenden THREAD) // später ObjectOutputStream und ObjectInputStream im lesenden THREAD) // später OutputStream = <Socket>.getOutputStream() ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeInt(12345); oos.writeObject("Today"); oos.writeObject(new Date()); oos.close(); 7 Wäre für unsere Anwendung (Adreli_2) ideal! Warum? Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 80 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Lesender THREAD: FileInputStream fis = new FileInputStream("personen.dat"); // durch pipe/socket ersetzen // später PipedInputStream pis = new PipedInputStream(PipedOuputStream src); // später InputStream sis = <Socket>.getInputStream() ObjectInputStream ois = new ObjectInputStream(fis/pis/sis); int i = ois.readInt(); String today = (String) ois.readObject(); Date date = (Date) ois.readObject(); ois.close(); Mehr dazu: Siehe Adreli-2-Pattern Adreli_3_NetCom „How-To“ 4. Client-Programm schreiben (VIEW) § Mit User-Interaktion wie bisher (aus bestehenden Code-Teilen) § Mit Pufferung (ggf. so wie bisher) § Noch OHNE SOCKET-Kommunikation 5. Server-Programm schreiben (MODEL) § Mit File-I/O (aus bestehenden Code-Teilen) § Mit Pufferung (ggf. so wie bisher) § Noch OHNE SOCKET-Kommunikation Mehr dazu: Siehe Adreli-3-Pattern Adreli_4_GUI „How-To“ Adreli_5_JDBC „How-To“ Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 81 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 7.1 Als Kompass/Leitplanke dienen Vorgehensmodelle Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 82 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 83 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Rollen SCRUM-Modell Scrum Master Burn-down-chart Product Owner Sprint Backlog Release Backlog Product Backlog Development Team Pair-Programming 2 • • Sprint Planning Meeting Daily Scrum Zeremonien Review Meeting Prof. J. Anton Illik „SCRUMin unter 10 minutes...“ (YouTube) Video2brain, Udo Wiegärtner: Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 84 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 85 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 86 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 8. Netzwerkzugriff hier weiter 04.11.15 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 (TCP/IP-Protokoll-Stack). 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. Eine Protokollebene nutzt eine tieferliegende Schicht und reichen Ergebnisse an eine höhere Schicht weiter. 8 Das Protokoll, welches sich zur Kommunikation vernetzter Computer durchgesetzt 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. 8 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 (ca. bis Beginn der 1990er Jahre) seine eigene Protokoll Suite. Ab dann setzte sich TCP/IP durch. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 87 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 ISO Referenzmodell ursprüngliches ARPAnet Referenzmodell Application Presentation Session¥ Transport Prozesse / Applikationen 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 Protokolle IP Network Data Link Netzwerkschnittstelle Hardware Netz-Hardware UDP 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 (= Segmente; max 1500 Bytes) 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_v13_160113_students2print.docx 88 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 („Daemon“) 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-PeerVerbindung). 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). . Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 89 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (mittels IP-Nummer) über richtiger 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 HTTP Hypertext Transfer Protocol 80 TCP, UDP RFC1945, RFC2068 POP3 Post Office Protocol 110 TCP, UDP RFC1939 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. 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. Info: siehe auch: http://de.wikipedia.org/wiki/Liste_der_standardisierten_Ports 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 Windows 8 C:\windows\System32\drivers\etc Unter Linux/UNIX/MAC OS X 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 90 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 # # # # # # # # # # # # # # 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. 8.2 Klassen für die Adressierung 8.2.1 Die Klasse InetAddress: (siehe API-DokuThis 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 (IP-Nummer) des eigenen Computers (siehe Beispiel unten) liefert die Adresse (IP-Nummer) für den als Parameter genannten Host (siehe Beispiel unten) Alle Adressen (IP-Nummer) 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() 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 91 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 boolean isMulticastAddress() boolean isSiteLocalAddress() (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)"); 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){ } } Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 92 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 8.2.2 Die Klasse URL Ein URL = „Uniform Ressource Locator“ ist die Beschreibung einer Adresse (Quelle) im Internet / dient dazu, um eine Ressourcen im 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/Re ssource 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. Beispiel URL-String: 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 93 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 9 … und dann gab 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() 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: … try { 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 } } catch(MalformedURLRxception ex) { // ex handling } … 9 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_v13_160113_students2print.docx 94 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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]); // url-String mit Protokollangebe... 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_v13_160113_students2print.docx 95 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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) (11.11.2015) Der Server ServerSocket erzeugen Der Client (rein/pur) Socket erzeugen bind() auf Port-Nr. mit ServerSocket verbinden connect() (u.U. blockierend) auf Verbindung warten [u. U. mit Backlog dimensionieren] und bestätigen accept() (u.U. blockierend) Anfrage schreiben / Antwort lesen . Thread generieren Kann auch schon beim Erzeugen erledigt werden. Siehe API-Docu Verbindung abbauen close() lesen/schreiben (kooperieren über Sockets) ServerSocket schließen close() Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 96 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 97 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 („Kommunikationssocket “) 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 class 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 throws IOException getOutputStream() Liefert die Adresse des Host-Computers, zu dem die Verbindung besteht, das ist der Netzknoten zu dem der Client connect()ed 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 des Client zurück, mir der der Client-Socket verbunden ist. (Das Analogon im Server heist getLocalSocketAdress().) Gibt die Portnummer des Hosts zurück Erzeugt ein OutputStream-Objekt (das ist ein Byte-Stream) für das Übertragen von Daten zum Server ; mit write(byte[]...); 10 public InputStream getInputStream() throws IOException 10 auch: Stream- „Fitting“ um Ojekte zu streamen/writeObject() (serializable) Gibt ein InputStream-Objekt (das ist ein Byte- Siehe auch unten: Kapitel „Was kommten den da im Stream? Sockets und Streams“ Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 98 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Stream) zurück für das Lesen von Daten vom Server ; mit read(byte[]...); 11 auch: Stream- „Fitting“ um Ojekte zu streamen/readObject() (serializable) public int getSoTimeout() throws SocketException public int setSoTimeout(int timeout) throws SocketException 8.3.2 ServerSockets: die Klasse 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 an den Host. 0 entspricht unbegrenzter Wartezeit. 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 (=„Kommunikationssocket“). 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) 11 Server wartet auf Verbindungsanfragen am betreffenden Port. Kommt eine Verbindungsanforderung (connect()) von einem Client, so liefert die Methode das SocketObjekt 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 Siehe auch unten: Kapitel „Was kommten den da im Stream? Sockets und Streams“ Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 99 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 void bind(SocketAddress endpoint, int backlog) void close() InetAddress getInetAddress() int getLocalPort() int getSoTimeout() void setSoTimeout(int timeout) 8.3.3 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. 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 > 45678 ServerSocket myServerSocket = new ServerSocket ( 56789 ); // incl. bind() to port ServerSocket //*/ //* ALTERNATIVE B) Für INTERNET-Sockets *************************************** // class InetSocketAdress extends class SocketAdress InetSocketAddress myInetSocketAddress = new InetSocketAddress("localhost",56789); ServerSocket myServerSocket = new ServerSocket(); // unbound ServerSocket myServerSocket.bind(myInetSocketAddress); // we bind()it //*/ f or (; ; ) { // 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(); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 100 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } // 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"+ "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], 56789); // ODER; unconnected Socket kreieren und DANACH connect()en: InetSocketAddress serverInetSocketAddress = new InetSocketAddress(" localhost ", 56789 ); 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 do { } in = new BufferedReader( // zum Lesen von Console new InputStreamReader (System.in)); 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); } Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 101 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } Anmerkung: Dem Javaprogramm Kommandozeilenargumente mitgeben Textpad: „Konfiguration“ -> „Einstellungen“ -> „Extras“ -> „Java Programm starten“ -> „Parameterabfrage“ Eclipse: „Run“ -> Open Run Dialog bzw. „Run Configuration“ -> Tab „(x) Arguments“ 8.3.5 Was kommt den da im Strom? Sockets und Streams Byte-Streams, geliefert von den SocketMethoden getOutputStream() und getInputStream(). Was die Kommunikationspartner über die Sockets austauschen sind Was in den Streams „drinsteckt“, wissen die Kommunikationspartner (wie der Byte-Strom zu interpretieren ist): Wird Text über die Sockets übertragen, so wird der Stream in BufferedReader und BufferedWriter unter Zuhilfenahme der Bridge-Klassen OutputStreamWriter und InputStreamReader überführt, um dann z.B. mit write(char[]...) und mit read(char[]...) CharacterArrays zu schreiben und zu lesen. Werden binäre Daten über die Sockets übertragen, so wird der Stream in DataInputStream und DataOutputStream überführt, um dann z.B. mit write(byte[]...) und mit read(byte[]...) Einzelbyte oder Byte-Arrays zu schreiben und zu lesen. Bei serialisierten Objekten verwendet man dagegen ObjectInputStream und ObjectOutputStream. um dann z.B. mit writeObject(Object)und mit Object =readObject() Objekte zu schreiben und zu lesen. 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 c li en t Socket.c on ne c t(serverInetSocketAddress ); ClientSocket Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 102 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 ClientSocket ServerSocket Server (ggf. multi-threaded) ClientSocket = accept() f or (; ; ) ClientSocket 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_v13_160113_students2print.docx 103 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 RPCMechanismus (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 machine12) 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 Exceptions13, da ja beispielsweise der entfernte Methodenaufruf durch einen Verbindungszusammenbruch scheitern kann. 12 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. 13 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_v13_160113_students2print.docx 104 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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-Seite14, 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-Objekt15 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). 14 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. 15 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_v13_160113_students2print.docx 105 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Weitergabe Client Datenstrom weiterleiten Stub Aufruf einer Marshalling der entfernten Methode Parameter Aufruf der Methode Stub 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 RMI-Registry 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 J V M B Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 106 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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ückgabewerte16 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 RMI-Server-Host RMI-Client RMI-Registry Stub RMI-Server J V M A OS A Skeleton OS B J V M B Bild 4: RMI Gesamtablauf 9.4 Die Hilfsmittel RMI-Compiler und RMI-Registry Die Aufgabe des RMI-Compilers17 rmic ist die Erzeugung von Stub (und Skeleton) aus den Klassendateien. Für eine Klasse, die das Interface Remote implementiert, erzeugt der RMI16 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 17 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 107 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Compiler rmic die benötigten Stubs. Standardmäßig werden die erzeugten Stubs in demselben 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) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 108 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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. 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_v13_160113_students2print.docx 109 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 Stub18 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 18 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_v13_160113_students2print.docx 110 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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); A2: // Bind the remote object's stub in the registry Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 111 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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"; } } // end sayHello() Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 112 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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")); System.out.println (stub.sayHello("Deutsch")); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 113 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 114 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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: 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? Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 115 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 10. AWT-Basics: Graphical User Interface hier weiter Was Sie lernen: wer die wichtigen Mitglieder der AWT-Klassenhierarchie sind wie Fenster erzeugt und angezeigt werden wie Fenster geschlossen und gelöscht 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“ propagiert 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, low level, high level), welche durch Umgang mit Tastatur und Maus (z.B. Klicken auf Schaltfläche) oder Operationen auf Komponenten („Controls“, „Widgets“) 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, Controls bezeichntet. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 116 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (analog Mac OS X/Linux) 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 für Testausgaben. 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_v13_160113_students2print.docx 117 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 10.2 AWT – Klassenhierarchie java.lang.Object java.util.EventObject java.awt.AWTEvent java.awt.Component java.awt.Container Graphics s ...Choice ...Label ...Button ...List …Canvas java.awt.Dialog java.awt.Window javax.awt.Panel java.awt.Frame javax.applet.Applet …Scrollbar …Checkbox …TextComponet nt javax.swing.JDialog javax.swing.JFrame javax.swing.JComponent javax.swing.Label 20.05. Bild: Die AWT-Klassenhierarchie 19 java.applet.Applet javax.swing.JApplet (nonmenu-related Abstract Window Toolkit components) Im Folgenden betrachten wir zunächst das Wesentliche aus den Klassen Window, Frame, 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“ Und dann: die menu-related components: FileDialog, Dialog, beides Ableitungen von class Window; MenuBar, Menu, MenuItem, alle drei Klassen sind Ableitungen von Class MenuComponent; PopupMenu, ist Ableitung von class Menu; usw. 19 In dieser Klassenhierarchie fehlt komplett die Klasse java.awt.Menu. Siehe API Doku! Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 118 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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, o Eine Anwendung müsste die fehlenden Teile selbst zeichnen o Menü, Titelleiste. 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 o einer Gruppe. 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_v13_160113_students2print.docx 119 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 120 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Windows are capable of generating the following WindowEvents: WindowOpened, WindowClosed, WindowGainedFocus, WindowLostFocus. Windows können viele Listener zugeordnet werden (es gibt zahlreiche add..()-ListenerMethoden). Dazu kommen noch von class component geerbte Listener. Typisch für die ganze AWT-Hierarchie!!! 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. Panels können so gut wie keine Listener zugeordnet werden (es gibt so gut wie keine add..()Listener-Methoden. Siehe API-Doc!). Panels sind wichtige Strukturierungsmittel / Components zur Gliederung der Oberfläche. Case-Study: vergleiche in der API-Spezifikation die „Method Summary“ für die Klassen Component, 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 OS X 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 121 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Die meisten Methoden erbt die Klasse Frame von der Klasse Component, einige aber auch von den Klassen Container und Window. (Siehe API-Doku) Wichtige Methoden der Klasse Frame (…und ihrer Basisklassen) public public title) public public String getTitle () void setTitle (String isResizeable () Text in der Titelleiste Gibt den Fenstertitel zurück, bzw. setzt ihn Veränderbarkeit der Fenstergröße Fenstersymbol void remove (MenuComponent void setLocation (int x, s.u. 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. (Menüleisten sind damit austauschbar.) Die Methode remove() entfernt die verbundene Menüleiste. - siehe Beispiel unten - void setVisisble (Boolean s.u. - siehe Beispiel unten - void dispose () s.u. - siehe Beispiel unten - boolean 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 Fensterstatus (normal oder minimiert) Menüleiste è Weitere Methoden und Konstruktoren und Methoden siehe API – Documentation zum Package java.awt. Beispiel – Fenster erzeugen, Attribute setzen und anzeigen // Eclipse-Project: kap10_awt_basics_case_study_ 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"); // Text für die Titelleiste frame.setSize(300,200); // Fenstergrösse festlegen frame.setLocation(350,300); // Fensterposition festlegen frame.setBackground(Color.blue); // Fensterfarbe festlegen // teilweise von Component (setBackground(), setLocation()) // und von Window (setSize()) geerbt. // Dann Fenster anzeigen frame.setVisible(true); } } Objekte der Klasse Frame besitzten 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). Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 122 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Die Betätigung des Schließfeldes bleibt aber ohne Wirkung und muss erst programmiert werden. (Siehe Beispiel unten. 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 // Eclipse-Project: kap10_awt_basics_case_study_ 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 Konstruktor 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. (Zu class Frame, class WindowListener, class WindowAdapter: siehe API-Doku) Die Ereignisbehandlung ist hier mittels Verwendung einer Adapterklasse implementiert. (Methode „A“) -> Adapterklasse enthält alle Call-Back-Methoden -> NUR diejenige Adaptermethode, die gebraucht wird, muss redefiniert werden. (Siehe hierzu auch Fenster1_DoubleWindow_ClosingOnly.java) Wir werden später sehen, dass die Ereignisbehandlung auch durch die Implementierung eines ListenerInterfaces realisiert werden kann.(Methode „B“). Dann müssen aber ALLE Interface-Methoden implementiert wreden Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 123 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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). frame.setVisible(true) frame.setVisible(false) frame.dispose() //Fenster anzeigen //Fenster unsichtbar machen - Ressourcen noch belegt //Ressourcen freigeben Anwendung ist aber noch nicht beendet! o System.exit(status) è normal: status = 0!! Beispiel – Fenster verschwinden lassen // Eclipse-Project: kap10_awt_basics_case_study_ 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_v13_160113_students2print.docx 124 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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. (Mac OS: Titelleiste modifiziert) Fenstersymbol (icon) os-spez. 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 (siehe auch API-Doku): Eigenschaft Titel title Fenstersymbol icon Größe height x width (von Compontent geerbt) Position X,Y (von Compontent geerbt) Position und Größe 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) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 125 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 (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 – Der MAC zeigt keine Fenstersymbole/Icons. Vorgeschriebene Formate: GIF, JPEG oder PNG (implementierungsspezifisch). 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().getResource("./smile.gif"); // Icon laden (mit Methode aus der Klasse Toolkit)) Image icon = this.getToolkit().getImage(url); // Icon dem Fenster zuweisen frame.setIconImage(icon); getClass() ist eine Methode von class Object (returns the runtime class of this Object). getResource() ist eine Methode von class Class. Die class Class ist: Instances of the Hinweis 1: class Class represent classes and interfaces in a running Java application. oder Methode b): Image icon = this.getToolkit().getImage(“./smile.gif”); //keine ImageIcons auf MAC frame.setIconImage(icon); Hinweis 2: die Methode getTookit() (aus der Klasse Window) liefert ein Toolkit-Objekt. getImage() ist eine Toolkit-Methode. Class 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. (Toolkit wird gebraucht für den Zugriff auf die (unter AWT liegenden PeerKomponenten des nativen Systems….) 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) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 126 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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); }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); // zur Erinnerung: viele für Frames verwendete Meth. this.setLocation(350,300); // stammen von den Kl. Component,Container,Window … // 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 Hilfsklassen für Fensterposition und -Größe: hier weiter 02.12.2015 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 127 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 Hilfsklassen Fensterfarben: 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; additives Farbmodell) Einige Farben sind vordefiniert: z. B.: black, blue, cyan, gray, darkGray, lightGray, magenta, orange, pink, green, red, white, yellow. Color.blue // Farbe blau Color(int red, int green, int blue) // “Farbmischung” durch Angabe der // Intensität der RGB-Farbanteile // int-Werte von 0-255 Color(float red, float green, float blue) // float-Werte von 0-1 this.setBackground(new this.setBackground(new this.setBackground(new this.setBackground(new 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 128 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 10.9.4 Hilfsklassen Mouse-Cursor: Klasse 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. //Eclipse-Projekt: kap10_awt_basics_case_study_ 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_v13_160113_students2print.docx 129 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 130 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Nach dem Verständnis von Kapitel 11 ist unter Zuhilfename von Kapitel 13 „AWT Komponenten“ die GUI für Adreli machbar. 11. AWT-Events: Ereignisse und Event-Handling Was Sie lernen: was Ereignisse sind und wer oder was Ereignisse auslöst 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 via VM lfd. Appl. Weitere I/O HW 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 (Class Component und Ableitungen). Dies kann beispielsweise eine Schaltfläche (Class Button) sein, auf die mit der Maus geklickt wird. Oder etwa die Class Frame, auf dessen Schließbutton geklickt wird, usw. Ereignisziel / Ereignisempfänger: das sind Objekte, die das Ereignis empfangen à Listener empfangen die Ereignisse. (Für Objekte der Class Button kann das z.B. der MouseLisenter sein. Oder für Objekte der Class Frame z.B. der WindowListener u.v.a.m) Die Ereignissquelle muss den entsprechenden Listener registrieren, damit das Ereignis verarbeitet werden kann. (Für das Button-Objekte kommt z.B. die Methode addMouseListener() zum Einsatz, wenn Mouse-Events (siehe Class MouseEvent) empfangen werden sollen.) Ereignisobjekt: Wird dann zur Laufzeit ein Ereignis ausgelöst, so wird die zuständige („Callback“-)Methode des Listener-Interfaces aufgerufen. Der zuständigen Callback-Methode des Listener-Interfaces wird dann ein Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 131 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Ereignisobjekt als Parameter mitgegeben. Die Callback-Methode kann dann ggf. Attribute und Methoden des Ereignissobjekts für die Eventbehandlung nutzen. 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 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. Diese Methoden-Namen beginnen stets mit add...()) 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 Klicks (auf Button, der für 20 eine bestimmte Operation/Command steht; siehe Interface ActionListener) und einen für Mausaktivitäten (siehe Interface MouseListener). 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 (mit den „Callback”-Methoden 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_LowLevel.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. 20 So wird beispielsweise die Button-Beschriftung als Command-String gedeutet, der von der Class ActionEventMethode getActionCommand() geliefert wird... Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 132 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (4) TextEvent "low-level Event" (systemnahe Ereignisse / technische Events) (5) ComponentEvent (7) ContainerEvent (3) ItemEvent (6) FocusEvent InputEvent (10) MouseEvent (8) WindowEvent (9) KeyEvent Semantische Events: z. B. Klicken (mit oder ohne 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 in ein Textfeld. den Benutzer ausgelöst, z.B. ein Schaltflächenklick oder Eintrag 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. Systemnahe Auslösung (konkrete grafische Objekte aus der AWT-Fenster-Klassen-Hierarchie) 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_v13_160113_students2print.docx 133 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (6) FocusListener (7) ContainerListener (8) WindowsListener (9) KeyListener Listener werden mit entsprechenden add-Methoden bei der Ereignisquelle registriert. Welche add-Methoden (also: welche Listener) verfügbar sind, ist mittels APIBeschreibung der Ereignissquelle erkennbar. Siehe z.B. Class Frame als EreignissQuelle. Dort taucht neben vielen anderen add-Methoden auch die Methode addKeyListener() auf (geerbt von Class Component) Parameter für die add()-Methode ist, wie oben gesagt, das zu implementierende Listener-Interface Das Listener-Interface legt die Methoden fest, über die die Reaktion auf das Ereignis erfolgt. Beispiel - Interface KeyListener: public i nt er f ac e { public void public void public void } Ke y Li st en e r extends EventListener K ey Ty p ed (KeyEvent ev); K ey Pr e ss ed (KeyEvent ev); K ey Re l ea se d(KeyEvent ev); (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 („add....()“). è 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_v13_160113_students2print.docx 134 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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. (Im folgenden Bsp: nur Low Level Event MouseListener-Methoden im Einsatz) //Eclipse-Projekt: kap11_EventFenster_LowLevel import java.awt.*; import java.awt.event.*; public class EventFenster_LowLevel { 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); MouseListener (this); btn.add MouseListener this.add(btn); this.add } // ß Button registriert den WindowListener (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) {} public void mousePressed(MouseEvent me) {} Modell B) Alle Interfacemethoden (als “Callback-Methode) zu implementieren public void mouseEntered(MouseEvent me) {} public void mouseExited(MouseEvent me) {} public void ExitApp() { // Der eigentliche Applikations-Code------------------- Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 135 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 // hier könnten sich weitere Anweisungen befinden, // die beim Beenden der Anwendung ausgeführt werden System.exit(0); } } 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 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 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: // Eclipse-Projekt: kap11_awt_events_case_study_ 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); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 136 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 this.add(btn); this.addWindowListener(ereignisbehandl); } } //----------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_v13_160113_students2print.docx 137 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 11.7 Low-Level-Ereignisse (systemnahe Ereignisse / Einige Beispiele): 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 ( Interface 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 („Call-Back“-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() (Siehe: Hilfsmethoden in low-level-Event-Klassen) Beispiel – Eine low level Event-Behandlung; Reaktion auf ComponentEvent-Ereignisse //Eclipse-Projekt: kap11_awt_events_case_study_ 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() // schon bekannt { // Modell A public void windowClosing(WindowEvent e) // mehrere Adapter{ // methode überschreiben System.exit(0); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 138 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } // in der Folge u.U. weitere Adapterklassen: // public void windowIconified(WindowEvent e) // {…} // public void windowDeiconified(WindowEvent e) // {…} // public void windowActivated(WindowEvent e) // {…} // public void windowDeactivated(WindowEvent e) // {…} // USW., USW., ... }) ; this.addComponentListener(this); // neu this.setLayout(null); btn = new Button("ausblenden"); btn.setSize(90, 23); btn.setLocation((this.getWidth()-90)/2, //Button “einmitten” (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 red = (int)(this.getBounds().getX() % 254); // von ComponentListener red = (red > 0) ? red : -red; int green = (int)((this.getBounds().getX() + this.getBounds().getY()) % 254); green = (green > 0) ? green : -green; int blue = (int)(this.getBounds().getY() % 254); blue = (blue > 0) ? blue : -blue; this.setBackground(new Color(red, green, blue)); // Loca-spezif. Backgrnd } public void componentHidden (ComponentEvent ae) { System.out.println("Fenster wurde ausgeblendet"); } public void componentResized (ComponentEvent ae) { // Button neu “einmitten”” 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 } AbgeleiteterMouseListener class extends MouseAdapter //Modell C: Adapterntzg { // 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 Die gelb umrahmten Hilfsmethoden sind von der class Component geerbt. Die Hilfsmethode getComponent() von der class Container. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 139 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 11.7.2 Window-Ereignisse ( Interface 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 (per dispose()-Methode) …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 //Eclipse-Projekt: kap11_awt_events_case_study_ 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) Methoden { we.getWindow().dispose(); // alle Window-Listner- Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 140 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } public void windowClosed (WindowEvent we) { System.out.println("Fenster wurden 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); } } Hilfsmethoden aus Class WindowEvent bzw. Frame Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 141 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 11.7.3 Focus-Ereignisse ( Interface FocusListener ): In jeder Anwendung kann immer nur ein Fenster aktiv sein. In 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 sich 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_v13_160113_students2print.docx 142 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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“ einen Event 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“ (ausgefiltert) 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.4 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 ke als Argument übergeben. Klasse KeyEvent stellt Methoden zur Verfügung, über die ermittelt werden kann, welche Taste(n) Die betätigt wurde(n). public char getKeyChar() liefert Zeichen, das der Taste entspricht public int getKeyCode() Tastencode (Virtual Key Code, eine symbolische Konstante) public static String getKeyText() 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 Taste Virtual Key Code Taste Virtual Key Code Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 143 / 229 Taste 15.03.2016 PROMOD-2 / Ausgabe 2016 VK_A VK_B … VK_Z VK_0 VK_1 … VK_9 („getKeyChar()“) A B … Z 0 1 9 VK_Enter VK_F1 VK_F2 … VK_F12 VK_SPACE VK_END VK_HOME („getKeyChar()“) Enter F1 F2 VK_ESCAPE VK_INSERT VK_DELETE VK_TAB ESC EINFG 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 //Eclipse-Projekt: kap11_awt_events_case_study_ 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(); } // Keylistener-Methoden public void keyPressed (KeyEvent ke) { if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) System.exit(0); if(ke.getKeyChar() == 'o') { Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 144 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 if(ke.isAltDown()) ke.getComponent().setBackground(new Color(255,153,0)); else ke.getComponent().setBackground(Color.orange); } if(ke.getKeyChar() == 'g') { if(ke.isAltDown()) ke.getComponent().setBackground(new Color(0,153,0)); else // Keylistener-Methoden 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_v13_160113_students2print.docx 145 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 11.7.5 Die Eventklasse Klasse MouseEvent und die Listener-Interfaces MouseListener, MouseMotionListener : 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. Klasse MouseEvent Die stellt noch folgende Hilfs-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 //Eclipse-Projekt: kap11_awt_events_case_study_ 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_v13_160113_students2print.docx 146 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } 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); } // alle MouseListener// Methoden public void mousePressed (MouseEvent me) { distX = me.getX(); distY = me.getY(); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 147 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } // alle MouseMotion// 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_v13_160113_students2print.docx 148 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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) 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 ListenerInterfaces Ereignisklasse EventSource (konkrete grafische Objekte aus der AWT-FensterKlassen-Hierarchie) 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 mouseExited() – 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_v13_160113_students2print.docx 149 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 ActionEvent ActionListener actionPerformed() – Aktion wurde ausgeführt AdjustmentListene AdjustmentEvent z.B. Button, List, MenuItem, TextField Scrollbar r adjustmentValueChanged() – Wert wurde geändert ItemEvent Checkbox, List, ItemListener CheckBoxMenuIte m itemStateChanged() – Zustand wurde geändert TextEvent TextComponent TextListener 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_v13_160113_students2print.docx 150 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 151 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Ab hier (Kapitel 12 bis Kapitel 14) wird das Script hinsichtlich AWT als Beispielsammlung und CaseStudy-Pool genutzt: der Studierende nutzt es für das Selbststudium – das ein oder andere Beispiel wird u.U. auch im Kolloquium thematisiert. Beachten Sie: die Prüfungsrelevanz der Beispiele ist nach wie vor gegeben. Case-StudyPool (EINSCHUB: zunächst Kapitel 13. AWT-Komponeten: die wichtigen Window-Klassen Dialog / FileDialog, und der MenuComponent-Klassen Menu/MenuBar/MenuItem, PopUpMenu vorziehen. Diese AWT-Komponenten müssen ab Adreli_4 in Ihrer Softwarelösung verwendet werden.) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 152 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (class Graphics) 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, mit den Eigenschaften die im Graphics-Objekt eingestellt sind. 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.) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 153 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Gezeichnet mit Graphics.fillRect() und Graphics.fillOval(). Fenster wurde verkleinert und dann wieder vergrößert: Beispiel – Umgang mit dem Grafikkontext //Eclipse-Projekt: kap12_awt_graphics_case_study_ 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: Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 154 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 Class 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. Im Methodenrumpf von paint() wird von den oben behandelten Graphics-Methoden setColor(), fillRect(), drawRect()usw. (ca. 50 nicht geerbte Methoden), 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() //Eclipse-Projekt: kap12_awt_graphics_case_study_ 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 155 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (inneren) Fläche herauszufinden, auf die gezeichnet werden kann, steht die Methode Container.getInsets() zur Verfügung. Reterunwerttyp: class Insets . Enthält die int-Felder bottom, right, left, right und top. Siehe API-Doku. Beispiel – Die Positionierung im Fenster //Eclipse-Projekt: kap12_awt_graphics_case_study_ import java.awt.*; import java.awt.event.*; public class ZeichenFenster2 extends Frame { public static void main(String[] args) { ZeichenFenster2 fenster = new ZeichenFenster2(); } 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 156 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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-Doku(Java™ Font entnommen. Platform Standard Ed. 6) zur Klasse 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. 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 Schriftzeichens) 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. […] Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 157 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Quelle: Wikipedia 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 font) setFont(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 Font aus der System-Properties-Liste 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_v13_160113_students2print.docx 158 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 //Eclipse-Projekt: kap12_awt_graphics_case_study_ 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); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 159 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } } 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)); } } 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 160 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 //Eclipse-Projekt: kap12_awt_graphics_case_study_ 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); } }); } 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 161 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 werden 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 Funktion desktop Hintergrundfarbe des Desktops activeCaption Hintergrundfarbe der Titelleiste des aktuellen Fensters inactiveCaption Hintergrundfarbe der Titelleiste eines inaktiven Fensters activeCaptionText Schriftfarbe des Fenstertitels des aktiven Fensters inactiveCaptionText Schriftfarbe des Fenstertitels von inaktiven Fenstern window Hintergrundfarbe eines Fensters windowBorder Farbe des Fensterrahmens windowText Farbe der Schrift im Fenster menue Hintergrundfarbe des Menüs menueText Farbe der Schrift im Menü … Auszug der 26 definierten Systemfarben. Beispiel – Zeichnen mit Farben //Eclipse-Projekt: kap12_awt_graphics_case_study_ import java.awt.*; import java.awt.event.*; public class { int n = 1; Farbverlauf extends Frame // 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 162 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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) { i = i + 1; gruen = gruen + n; blau = blau - n; // rot bleibt konstant g.setColor(new Color(rot, gruen, blau)); //magenta g.drawLine(i, 0, i, hoehe); } while (blau < 256 - n) { i = i + 1; blau = blau + n; rot = rot - n; // gruen bleibt konstant g.setColor(new Color(rot, gruen, blau)); g.drawLine(i, 0, i, hoehe); } while (rot < 256 - n) { i = i + 1; gruen = gruen - n; rot = rot + n; // blau bleibt konstant g.setColor(new Color(rot, gruen, blau)); g.drawLine(i, 0, i, hoehe); } } } } 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) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 163 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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, int y, int width, int height, Boolean raised) raised == true = nach aussen public void fill3DRect(int x, int y, int width, int height, Boolean raised) 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: 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 x, int y, int width, int height, int startAngle, int endAngle) public abstract void fillArc(int x, int y, int width, int height, int startAngle, int endAngle) Beispiel – Nochmals: die Klasse Graphics und der Umgang mit Zeichenmethoden //Eclipse-Projekt: kap12_awt_graphics_case_study_ import java.awt.*; Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 164 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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); } } 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 165 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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.) Beispiel – Bitmaps anzeigen //Eclipse-Projekt: kap12_awt_graphics_case_study_ 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 wird erzeugt <<<<----------------------------- Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 166 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 } 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){} // 4 Zeichnen 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_v13_160113_students2print.docx 167 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 168 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 java.awt.Window javax.awt.Panel java.awt.Frame javax.applet.Applet …Scrollbar …Checkbox …TextComponet nt javax.swing.JDialog javax.swing.JFrame javax.swing.JComponent javax.swing.Label 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. Container sind Fenster (Window, Frame, Dialog), Panels und Applets (siehe Bild oben). Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 169 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 13.2 Überblick über AWT-Komponenten: Menüleiste (Menu, MenuBar, MenuItem) Text (Label) mehzeiliges Listenfeld (List) mehrzeiliges Listenfeld (TextArea) Titelleiste Leinwand Bildlaufleiste (scrollbar) (Canvas) einzeiliges Textfeld (TextField) Container (Panel) Schaltfläche (Button) Kontrollfeld (Checkbox) einzeiliges Listenfeld (choice) Überblick über die AWT-Komponenten, ihre Verwendung und die relevanten Listener: AWTKomponente Button Canvas Label Dialog, FileDialog TextField Choice Verwendung Listener 2 1 Ü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. Dialog und FileDialog werden als modale Fenster kreiert. Ausgestaltung des Fenster (mit Komponenten und den zugehörigen Listenern) obliegt beim Dialog dem User. Beim FileDialog ist die Ausgestaltung dagegen per default komplett, inclusive der zugehörigen Ereignisbehandlung. Über Textfelder werden Benutzereingaben und Benutzerausgaben realisiert, die nur eine Zeile benötigen. Ein Listenfeld dient der Auflistung und Auswahl vorgegebener Werte (EinfachSelektion). Die Werte können vom Benutzer ActionListener MouseListener KeyListener, MouseListener, MouseMotionListener u.v.a.m: siehe API-Doku „add“Methoden KeyListener MouseListener WindowListener WindowFocusListener TextListener ActionListener ComponentListener,ItemListener FocusListener 21 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_v13_160113_students2print.docx 170 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 weder verändert werden, noch können neue Werte hinzugefügt werden. List Mehrzeilige Listenfelder dienen ebenfalls der ItemListener Auflistung und Auswahl vorgegebener Werte. ActionListener Eine Mehrfachselektion ist möglich. TextArea Mehrzeilige Textfelder dienen der Eingabe TextListener und Anzeige längerer Texte. Checkbox Kontrollfelder (Checkboxen) eignen sich für ItemListener die Eingabe bzw. die Anzeige von Ja/NeinWerten durch das Setzen oder Entfernen der Markierung. CheckboxGroup Optionsfelder sind mehrere Kontrollfelder ItemListener (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 AdjustmentListener 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 ContainerListener Panel sind Container, die andere PropertyChangeListener Komponenten in sich aufnehmen können. Sie dienen der Gruppierung von Komponenten und haben eine besondere Bedeutung im Zusammenhang mit LayoutManagern. Scrollpane ContainerListener 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 mit beim Menüeintrag wird Eine Menüleiste wird direkt unter der Menu und Titelleiste in ein Fenster eingefügt. Sie kann ActionListener MenuItem registriert 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 beimMenüeintrag wird Ein Popup-Menü (auch als Kontext-Menü bezeichnet) wird durch Klicken mit der ActionListener registriert rechten Maustaste angezeigt. Allen Komponenten und dem Fenster selbst kann ein Popup-Menü hinzugefügt werden. xListener = Low Level Ereignis; yListener = Semantisches Ereignis; gelb markierte Felder: Muss-Komponenten im Adreli-Projekt. 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 171 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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. 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 172 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Beispiel – Buttons und Labels nutzen (UND: nur semantischer Event ActionListner-Meth. im Einsatz) //Eclipse-Projekt: kap13_awt_components_case_study_ 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); 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-Doku für die Klasse Button nach. Neben den hier vorgestellten ButtonMethoden gibt es zahlreiche weitere! Wieviel? Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 173 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 //Eclipse-Projekt: kap13_awt_components_case_study_ 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) { Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 174 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 EchoFenster fenster = new EchoFenster(); } public EchoFenster() { 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); this.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... this.add(txtEcho); btnLoeschen = new Button("loeschen"); ///////// 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); this.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(); //Cursor geht in’s Eingabetextfeld } 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_v13_160113_students2print.docx 175 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 176 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Beispiel – TextField-, List- und Choice-Komponenten //Eclipse-Projekt: kap13_awt_components_case_study_ 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)) { Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 177 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 lbl.setText("Doppelklick auf: " + lstEingaben.getSelectedItem()); } } 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_v13_160113_students2print.docx 178 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 hier weiter 09.12.2015 Beispiel – Anwendung der Klasse Scrollbar //Eclipse-Projekt: kap13_awt_components_case_study_ 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 (soll scrollable sein) 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); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 179 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 add("South", sbHor); // Gegenüber: North add("East", sbVert); // Gegenüber: West this.setVisible(true); 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) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 180 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 minimum maximum - the minimum value of the scroll bar - the maximum value of the scroll bar 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: d.h. ein anderes Fenster der Anwendung kann nicht aktiv werden, bis das Dialogfenster wieder geschlossen wird. Wird gesteuert mit dem 3. Konstruktor-Parameter (true/false) Siehe unten „MeinDialog.java“ 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 //Eclipse-Projekt: kap13_awt_components_case_study_ 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); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 181 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 btnDlg.addActionListener(this); this.setVisible(true); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); } } public void actionPerformed (ActionEvent ae) // ActionListener callback { 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); // Dialog-Fenster als modales Fenster! this.setSize(300, 130); this.setLocation(200, 150); this.setResizable(false); this.setLayout(null); Label lblMldg = new Label(mldg);//”Wollen Sie die Anwendung wirklich…” 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) // ActionListener callback { Object object = ae.getSource(); // geerbt von Class EventObject if(object.equals(btnOK)) antwort = true; setVisible(false); dispose(); } public boolean getAntwort() { return antwort; } } 13.4.2 Der Dateidialog (Klasse FileDialog ) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 182 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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. 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() setVisible() wird der Dateidialog angzeigt: 1. FileDialog dlg = new FileDialog(this,"Öffnen",FileDialog.LOAD); 2. dlg.show(); deprecated -> dlg.setVisible(true); oder 3. FileDialog dlg = new FileDialog(this,"Speichern",FileDialog.SAVE); 4. dlg.show(); deprecated -> dlg.setVisible (true); Ü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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 183 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 //Eclipse-Projekt: kap13_awt_components_case_study_ import java.awt.*; import java.awt.event.*; public class OpenDialogFenster extends Frame implements ActionListener { 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()) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 184 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Tastenkombinationen (MenuShortcut) 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 (OS-abhängig!) 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 MenuItem ) 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 185 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 o o o Erzeugt wird ein solches Objekt durch den Aufruf des Konstruktors der Klasse MenuShortcut. Als Parameter wird der Code der Taste übergeben, die in Verbindung mit der STRG-Taste den Shortcut ergibt. 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.) 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(); // geerbt von Class EventObject 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.) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 186 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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ü 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 //Eclipse-Projekt: kap13_awt_components_case_study_ 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); } }); } Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 187 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 public void actionPerformed(ActionEvent ae) //ActionListener cb { Object o = ae.getSource(); if(((MenuItem)o).getLabel().equals("Öffnen")) { 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) // ItemListener CB { 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 (für den Beenden-Dialog) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 188 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (Klasse PopupMenu) 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 (von Class Component geerbt). Siehe unten 1) (dafür ist KEIN addMouseListener() notwendig!) 2. Die Component-Methode processMouseEvent() muss überschrieben werden (von Class Component geerbt). 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_v13_160113_students2print.docx 189 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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); } }); // die folgenden 4 Schritte 1) – 4) konfigurieren das Popup-Menue // “Problem”: Kontextmenue ist an das Frame-Innere gebunden, nicht // an eine konkrete Component, wie z.B. an ein Menuitem... this.enableEvents(AWTEvent.MOUSE_EVENT_MASK); //1)Siehe o. die 4 Schritte } // von Class Component geerbt public void processMouseEvent(MouseEvent me) // 2)“alternative Mouse-Event-Behandlung { // von Class Component geerbt if(me.isPopupTrigger()) // 3) aus MouseEvent pmnu.show(me.getComponent(), me.getX(), me.getY()); // 4) aus PopupMenue } public void actionPerformed (ActionEvent ae) //ActionListener CB { 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"); } else if(((MenuItem)o).getLabel().equals("Datei Speichern")) { FileDialog dlg = new FileDialog(this, "Datei speichern unter", FileDialog.SAVE); Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 190 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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) // ItermListener CB { CheckboxMenuItem cmi = (CheckboxMenuItem) ie.getSource(); if(cmi.getLabel().equals("automatisch Speichern")) autosave = cmi.getState(); } } Bearbeiten Sie die Case Studies und stellen Sie die Benutzeroberflächen Für den Server und für den Client auf eine AWT-GUI um. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 191 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 14. LayoutManager (ein Überblick) ggf. erst in ADRELI_5_JDBC einbauen 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 (x,y) 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.) 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 192 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 193 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 194 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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); // zu add() geerbt von class Container 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_v13_160113_students2print.docx 195 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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, 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); 3); 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_v13_160113_students2print.docx 196 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 197 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 198 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 199 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 (Sem-Projekt 5. 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 in eine algorithmische Programmiersprache 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 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 (Java Code pur) 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 (Java) 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 (Java Server Pages)/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 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 (und weitere) zugreifen, ohne dass etwas im Code dafür geändert werden muss. Zusammen mit den Vorteilen der Programmiersprache Java ist Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 200 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 ein JDBC basiertes Java Programm also 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 (kurz: JDK) 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, („Model“) o darüber angesiedelt ist die Geschäftslogik-Ebene („Controller“) o und dann folgt die Präsentatinsebene. („View“) Datenebene i.d.R relationalen Datenbanken Die wird . durch den Einsatz von unterschiedlicher Hersteller (MySQL (-> SUN ->) Oracle, IBM, SyBase, …) realisiert (wenn strukturierte Daten vorliegen). Zur Anbindung von Java-Anwendungen und Java-Applets an relationale Datenbanksysteme wird über das JDBC-API eine (betriebssystem-)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 http://devapp.sun.com/product/jdbc/drivers http://www.oracle.com/technetwork/java/javase/jdbc/index.html (oder nach „jdbc driver“ googeln). Zum Zeitpunkt der Skripterstellung gab es 221 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 Java-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. (Mit Hilfe von Tags für SQL.) Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 201 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Beispielsweise kann eine Java Variable in einem SQL Befehl verwendet werden, um die Ergebnisse des SQL Befehls zu erhalten und weiter zu verarbeiten. 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 (runtime) 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 und damit auch ab dann fixiert (und zur Laufzeit nicht mehr änderbar.) 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 (mit weniger Notation wird mehr erreicht). 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_v13_160113_students2print.docx 202 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 22 23 Mit der Java Data Objects (JDO) API und Enterprise Java Beans (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. 22 23 Siehe http://java.sun.com/products/jdo Siehe http://java.sun.com/products/ejb Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 203 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 (u.U. ü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 24 Über die JDBC-ODBC-Brücke werden Datenbankverbindungen über die ODBC Schnittstelle 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. 24 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_v13_160113_students2print.docx 204 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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/SecurityManager), 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 einen 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 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 nativen Bibliotheken auf dem Client zu installieren. Wg. Java-Implementierung -> plattformunabhängig. 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.2010; nach der Übernahme durch Oracle wurde die Liste registrierter Treiben nicht mehr aktualisiert...(prüfen Sie das!) Die meisten Datenbankhersteller bieten entweder einen Typ-3- oder Typ-4-Treiber mit ihren Datenbanken an. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 205 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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. • Dieser Treiber ist ein Typ-4-Treiber. Als reine Java-Implementierung des MySQLProtokolls hängt der Treiber nicht ab von den MySQL-Client-Libraries und ist als Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 206 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Java-Implementierung auch plattformunabhängig. Eine eigene Serverschicht ist auf Seiten des MySQL-Servers auch nicht notwendig. • 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 MAC, Linux und auch für Windows nutzbar (eben plattformübergreifend). 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.16bin.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 JDK25 Lib (z.B. C:\Program Files\Java\jdk1.6.0_05\lib) kopieren. • 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:promod2“; 25 Auspacken mit dem Linux-Kommando tar –zxvf <myfile>.tar.gz Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 207 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Dabei wird auf eine lokale MySQL Datenbank mit dem Namen „promod2“ zugegriffen. Will man eine Verbindung zu einer externen Datenbank herstellen, so sähe die URL aus: String url = “jdbc:mysql://host:port/promod2“; ‚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 Werte26 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. 26 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_v13_160113_students2print.docx 208 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 209 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 210 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 die 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 PREIS ---------1.00 2.50 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 211 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 Birne 2.00 Falls keine spezielle Sortierung angegeben wurde, wird nach der ersten Spalte sortiert. 15.10 Klasse PreparedStatement27 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: 27 Das Statement wird auf Programmiersprach-Ebene vorbereitet und gespeichert. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 212 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 p_st.setString(1,“Banane“); p_st.setFloat(2, 2.50); p_st.executeUpdate(); Die ‚set’-Methoden gibt es für alle Arten von Parametertypen28. 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 / StoredProcedure29 Die dritte Möglichkeit in JDBC, einen SQL Befehl an eine Datenbank zu geben, ist das CallableStatement. Mit diesem kann man (in der DB) 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. 28 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. 29 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 213 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 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. Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 214 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 IT bei einem Commit-Befehl 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 API30. 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. 30 siehe http://java.sun.com/j2se/1.5.0/docs/api/java/sql/ResultSet.html Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 215 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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_v13_160113_students2print.docx 216 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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 <<<< end of adreli >>>>> 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 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 217 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 logischen Namen zugreifen. Es ist nicht mehr notwendig die genau URL der Datenbank zu kennen. 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 Vorname Max Philipp Maria Nachname Muster Mueller Durchschnitt Semester 4 5 4 Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 218 / 229 Note 1.3 2.0 3.0 15.03.2016 PROMOD-2 / Ausgabe 2016 135797 Klara Stein 4 4.7 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"); p_stmnt.setString(3,"Durchschnitt"); p_stmnt.setInt(4,4); p_stmnt.setDouble(5,3.0); if(p_stmnt.executeUpdate()> 0){ Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 219 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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."); } con.close(); } catch (SQLException e) { System.err.println("Ein Fehler ist aufgetreten: " + e); } } } //end 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_v13_160113_students2print.docx 220 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 1. Literatur (nehmen Sie jeweils die jüngste Ausgabe) Downloads: jdk-docs; langspec-3.0 (Sie finden die Dokumente auf der Oracle / Sun Microsystem Site) Pflichtlektüre (Kolloquium): Goll/Weiß/Müller: „Java als erste Programmiersprache – Vom Einsteiger zum Profi“, Verlag Teubner, Stuttgart/Leipzig/Wiesbaden, [u.U. jüngere Auflage] Empfohlene Lektüre (Kolloquium /Übungen): Christian Ullenboom: „Java ist auch eine Insel“, , geb., mit CD, 49,90 Euro, Galileo Computing, Bonn, [u.U. jüngere Auflage] 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, 7” [u.U. jüngere Auflage] 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_v13_160113_students2print.docx 225 / 229 15.03.2016 PROMOD-2 / Ausgabe 2016 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, 7” ” [u.U. jüngere Auflage] Weitere Projekt-Literatur Balzert, Helmut: Lehrbuch der Softwaretechnik – Entwurf, Implementierung, Installation und Betrieb Spektrum Akademischer Verlag, Heidelberg, 2011 [u.U. jüngere Auflage] Udo Wiegärtner, “Agile Softwareentwicklung mit SCRUM”, Video2Brain, 2013 - siehe auch Empfehlung aus den Kolloquium – *** Ende Skript *** Prof. Illik _PROMOD2_illik_02a_Java_SE_part02_v13_160113_students2print.docx 226 / 229 15.03.2016