Document

Werbung
PROMOD-2a / SS10
Materialien zur Vorlesung im Sommersemester 2010
j.a.illik
PROMOD-2 / Part 2 von 3
„Programmieren und Modellieren 2“
JAVA
: SE advanced-1 / verteilte Applikationen
(1. Das Skript ist durch die im Literaturverzeichnis
genannte Pflichtlektüre zu ergänzen.
2. Achten Sie auf Ergänzungen und
weitergehende Hinweise in der Vorlesung!
Fertigen Sie eine Mitschrift an.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
1 / 210
23.03.2010
PROMOD-2a / SS10
„The capacity to learn is a gift.
The ability to learn is a skill.
The willingness to learn is a choice”
Brian Herbert, Kevin J. Anderson:
„Prelude to Dune – House Harkonnen”
"Ich höre und ich vergesse.
Ich sehe und ich erinnere mich.
Ich tue und ich verstehe."
Konfuzius
Fertigen Sie ein Protokoll als Mitschrift an!
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
2 / 210
23.03.2010
PROMOD-2a / SS10
Inhaltsverzeichnis
1. Vererbung (Wiederholung & Ergänzung) ................................................................................. 8 1.1 Grundlagen ....................................................................................................................... 8 1.2 Die Klasse Object ............................................................................................................. 8 1.3 Konstruktoraufrufe: ......................................................................................................... 10 1.4 Vererbungsketten und Zuweisungskompatibilität: ........................................................... 11 1.5 Case-Study Objekt-Arrays, Zuweisungskompatibiliät, Operator instanceof .................... 12 1.6 Case-Study Lösung: ....................................................................................................... 12 1.7 Polymorphie .................................................................................................................... 14 1.7.1 Beispiel 1 – Methoden überschreiben: ..................................................................... 14 1.7.2 Beispiel 2 – "late binding – Polymorphismus": ......................................................... 15 1.8 Abstrakte Klassen und Methoden: .................................................................................. 16 1.9 Finale Klassen: ............................................................................................................... 17 2. Packages (Selbststudium!) .................................................................................................... 18 2.1 Grundlagen ..................................................................................................................... 18 2.2 Packages importieren: .................................................................................................... 18 2.3 Packages definieren: ...................................................................................................... 19 2.4 Zugriffsrechte in Packages:............................................................................................. 19 3. Interfaces (und Ergänzungen zu Klassen) ............................................................................. 21 3.1 Grundlagen ..................................................................................................................... 21 3.2 Adapterklassen ............................................................................................................... 24 3.3 Innere Klassen (auch: geschachtelte / eingebettete / nested class) ............................... 25 3.4 Lokale Klassen................................................................................................................ 26 3.5 Anonyme Klassen ........................................................................................................... 26 3.6 Statische innere Klassen (static class) / statische innere Schnittstellen ......................... 27 4. Ein-/Ausgabe auf Konsole,Dateien u.a.(ADRELI_CON) ........................................................ 28 4.1 Grundlagen ..................................................................................................................... 28 4.2 Standard-Ein- und Ausgabe ............................................................................................ 29 4.3 Die Klasse File: ............................................................................................................... 31 4.4 Die Klasse RandomAccessFile: ...................................................................................... 33 5. Streams: (ADRELI_CON) ...................................................................................................... 37 5.1 Grundlagen ..................................................................................................................... 37 5.2 Überblick (UNICODE-) Character-Stream-Klassen (Zeichendaten (Text) schreiben und
Lesen)........................................................................................................................................ 38 5.3 Character–Stream-Methoden:......................................................................................... 39 5.3.1 Ausgabe-Streams .................................................................................................... 39 5.3.2 Eingabe-Streams ..................................................................................................... 39 5.4 Beispiele/Anwendungen zu Character–Streams: ............................................................ 40 5.5 Überblick Byte-Stream-Klassen (binäres Schreiben u. Lesen) ....................................... 43 5.6 Byte–Stream-Methoden: ................................................................................................. 43 6. Multi-Threading (ADRELI_THREADS) ................................................................................... 47 6.1 Grundlagen ..................................................................................................................... 47 6.2 Klasse Thread und Interface Runnable ........................................................................... 49 6.3 Zustände von Threads: ................................................................................................... 53 6.3.1 Threads unterbrechen.............................................................................................. 53 6.3.2 Die Methoden yield(), sleep() und interrupt() ........................................................... 53 6.3.3 Die Methode join() ................................................................................................... 54 6.3.4 Weitere Thread-Methode ......................................................................................... 54 6.3.5 Thread-Konstruktoren .............................................................................................. 55 6.4 Daemon-Threads ............................................................................................................ 57 6.5 Prioritäten von Threads: .................................................................................................. 57 Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
3 / 210
23.03.2010
PROMOD-2a / SS10
6.6 Gruppen von Threads ..................................................................................................... 58 6.7 Synchronisation: ............................................................................................................. 58 6.8 Datenaustausch zwischen Threads mit Pipes:................................................................ 63 6.9 Thread-Synchronisation mit CyclicBarrier ....................................................................... 67 6.10 Zusammenfassung Threads: .......................................................................................... 69 6.11 Zusammenfassung Pipes:............................................................................................... 69 7. Exkurs: how to model an application? .................................................................................... 70 7.1 Als Kompass/Leitplanke dient ein Vorgehensmodell ...................................................... 72 7.2 Und wie geht`s im Detail? ............................................................................................... 76 8. Netzwerkzugriff ...................................................................................................................... 77 8.1 Grundlagen ..................................................................................................................... 77 8.1.1 Protokolle ................................................................................................................. 77 8.1.2 Adressierung: IP-Adresse und Port-Nummer........................................................... 78 8.1.3 Die Datei hosts und DNS (Domain Name System) .................................................. 80 8.2 Klassen für die Adressierung .......................................................................................... 81 8.2.1 Die Klasse InetAddress: (siehe API-Doku)This class represents an Internet Protocol
(IP) address. .......................................................................................................................... 81 8.2.2 Die Klasse URL ....................................................................................................... 82 8.3 Socket-Programmierung ................................................................................................. 85 8.3.1 Client-Sockets: die Klasse socket ............................................................................ 86 8.3.2 ServerSockets: die Klasse ServerSocket................................................................. 87 8.3.3 Beispiel – Server-Sockets (server.java): .................................................................. 88 8.3.4 Beispiel – Client-Sockets (client.java): ..................................................................... 88 8.4 Was kommt den da im Strom? Sockets und Streams ..................................................... 89 9. RMI Remote Method Invocation ............................................................................................. 91 9.1 Grundlagen ..................................................................................................................... 91 9.2 Die Bestandteile einer RMI-Anwendung ......................................................................... 92 9.3 Das Ablaufschema eines entfernten Methodenaufrufs ................................................... 93 9.4 Die Hilfsmittel RMI-Compiler und RMI-Registry .............................................................. 94 9.5 Der Sicherheitsmanager (SecurityManager) ................................................................... 95 9.6 Die Komponenten und Bestandteile auf der Server-Seite ............................................... 96 9.7 Die Komponenten und Bestandteile auf der Client-Seite ................................................ 97 9.8 Vollständiges RMI-Beispiel ............................................................................................. 97 9.9 Zusammenfassung ....................................................................................................... 101 9.10 Kontrollfragen (Technologien / RMI) ............................................................................. 102 10. AWT-Basics: Graphical User Interface ............................................................................. 103 10.1 Grundlagen ................................................................................................................... 103 10.2 AWT – Klassenhierarchie ............................................................................................. 105 10.3 „Fenster“: von der Component über den Container zum Frame ................................... 106 10.4 Klasse Window ............................................................................................................. 107 10.5 Klasse Panel: (abgeleitet von Container) ...................................................................... 108 10.6 Klasse Frame: (abgeleitet von Window) ....................................................................... 108 10.7 "Fensterbausteine" und der Umgang damit (Erzeugung / Konfiguration / Anzeige) ..... 110 10.8 Anzeigen und schließen: ............................................................................................... 110 10.9 Bestandteile eines Fensters (class Frame) ................................................................... 112 10.9.1 Das Fenstersymbol/Icon: ....................................................................................... 113 10.9.2 Fensterposition und -Größe: .................................................................................. 114 10.9.3 Fensterfarben: ....................................................................................................... 114 10.9.4 Der Mouse-Cursor: ................................................................................................ 116 11. AWT-Events: Ereignisse und Event-Handling .................................................................. 118 11.1 Grundlagen ................................................................................................................... 118 11.2 Ereignisklassen ............................................................................................................. 120 Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
4 / 210
23.03.2010
PROMOD-2a / SS10
11.3 Listener-Interfaces ........................................................................................................ 121 11.4 Listener bei Ereignisquellen registrieren ....................................................................... 121 11.5 Implementierungsmöglichkeiten:................................................................................... 123 11.5.1 1) Verwendung von Adapterklassen: ..................................................................... 123 11.5.2 2) Listener Interface wird implementiert: ................................................................ 123 11.6 Getrennter Code für GUI und Applikation ..................................................................... 123 11.7 Low-Level-Ereignisse (systemnahe Ereignisse): .......................................................... 125 11.7.1 Component-Ereignisse (die Klasse ComponentListener): ................................ 125 11.7.2 Window-Ereignisse (die Klasse WindowListener): ............................................ 127 11.7.3 Focus-Ereignisse (die Klasse FocusListener): ................................................. 129 11.7.4 Input-Ereignisse: .................................................................................................... 130 11.7.5 Die Eventklasse Tastaturereignisse (Klasse KeyEvent) und das ListenerInterface KeyListener):.............................................................................................. 130 11.7.6 Die Eventklasse Klasse MouseEvent und die Listener-Interfaces MouseListener,
MouseMotionListener : ......................................................................................................... 133 11.8 Zusammenfassung Ereignisse und ihre Behandlung .................................................... 136 12. AWT-Graphics: Grafikprogrammierung mit AWT.............................................................. 138 12.1 Grundlagen ................................................................................................................... 138 12.2 Der Grafikkontext / Die Klasse Graphics: ...................................................................... 138 12.3 Die Methoden paint() und repaint(): .............................................................................. 139 12.4 Anzeige von Text: ......................................................................................................... 141 12.4.1 Die Klasse Font / einige Grundbegriffe .................................................................. 141 12.4.2 Die Klasse Font / Schriftarten ................................................................................ 143 12.5 Farben / die Klasse Color.............................................................................................. 146 12.6 Weitere Methoden zum Zeichnen aus der Klasse Graphics ......................................... 148 12.7 Bitmaps anzeigen: (nur gif und jpeg-Format)/ Die Klasse Image .................................. 150 13. AWT-Komponenten: ......................................................................................................... 153 13.1 Grundlagen ................................................................................................................... 153 13.2 Überblick über AWT-Komponenten: ............................................................................. 154 13.3 Anwendung / Umgang mit den Komponenten:.............................................................. 155 13.3.1 Die Klasse Label .................................................................................................... 155 13.3.2 Die Klasse Button .................................................................................................. 156 13.3.3 Die Klasse Textfeld (abgeleitet von TextComponent) ............................................ 158 13.3.4 Die Klasse TextArea (abgeleitet von der Klasse TextComponent) ........................ 160 13.3.5 Die Klasse Choice und die Klasse List:.................................................................. 160 13.3.6 Die Klasse Checkbox: ............................................................................................ 162 13.3.7 Die Klasse CheckboxGroup: .................................................................................. 162 13.3.8 Die Klasse Scrollbar:.............................................................................................. 163 13.3.9 Die Klasse Scrollpane ............................................................................................ 165 13.4 Dialoge .......................................................................................................................... 165 13.4.1 Die Klasse Dialog: ................................................................................................. 165 13.4.2 Der Dateidialog (Klasse FileDialog) ................................................................. 166 13.5 Menüs: .......................................................................................................................... 168 13.5.1 Die Menüleisten (Klasse MenuBar) ....................................................................... 169 13.5.2 Die Menüs (Klasse Menu) ...................................................................................... 169 13.5.3 Die Menü-Einträge (Klasse MenueItem) .............................................................. 169 13.5.4 Kontextmenüs (auch Popup-Menüs genannt)........................................................ 173 14. LayoutManager (ein Überblick)......................................................................................... 176 14.1 Grundlagen ................................................................................................................... 176 15. Elementare Dienste für JEE: JDBC .................................................................................. 184 15.1 Hinführung .................................................................................................................... 184 15.2 Grundlagen ................................................................................................................... 184 Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
5 / 210
23.03.2010
PROMOD-2a / SS10
15.3 JDBC als Basis für andere APIs: SQLJ und JDO ......................................................... 185 15.3.1 SQLJ ...................................................................................................................... 185 15.3.2 SQLJ im Vergleich zu JDBC .................................................................................. 186 15.3.3 SQLJ und der Übersetzungprozess ....................................................................... 187 15.3.4 Persistente Java Objekte: JDO Java Data Objects ................................................ 187 15.4 Architektur von JDBC .................................................................................................... 187 15.5 Treibertypen .................................................................................................................. 188 15.6 Die Datenbank MySQL / Die DBMS Cloudscape / PointBase....................................... 190 15.7 Aufbau einer Datenbankverbindung .............................................................................. 190 15.8 SQL-Anweisungen ausführen ....................................................................................... 194 15.9 Die Klasse Statement ................................................................................................... 194 15.10 Klasse PreparedStatement........................................................................................ 196 15.11 Klasse CallableStatement / StoredProcedure ........................................................... 197 15.12 Auswertung der Ergebnismengen ............................................................................. 197 15.12.1 Boolean und Integer als Rückgabewerte............................................................ 198 15.12.2 ResultSet ............................................................................................................ 198 15.12.3 Konfigurieren der Ergebnismenge ...................................................................... 201 15.12.4 Nachladen von Datensätzen konfigurieren......................................................... 201 15.12.5 Navigieren in der Ergebnismenge ...................................................................... 201 15.12.6 Werte der Ergebnismenge ermitteln ................................................................... 201 15.12.7 Update der Ergebnismenge ............................................................................... 201 15.12.8 Nullwerte ............................................................................................................ 201 15.13 Auslesen von Metadaten ........................................................................................... 201 15.13.1 Datenbankinformationen ermitteln ..................................................................... 201 15.13.2 Informationen über eine Ergebnismenge ermittlen ............................................. 201 15.14 Transaktionen............................................................................................................ 201 15.14.1 Manuelles und automatischs Commit................................................................. 201 15.14.2 Isolationsebenen ................................................................................................ 201 15.14.3 Dirty Read .......................................................................................................... 201 15.14.4 Repeatabel Read ............................................................................................... 201 15.14.5 Phantom Read ................................................................................................... 201 15.15 JDBC Option Package .............................................................................................. 201 15.15.1 DataSource ........................................................................................................ 201 15.15.2 Connection-Pooling ............................................................................................ 202 15.15.3 Verteilte Transaktionen ...................................................................................... 202 15.16 Datenbankzugriff über Applets .................................................................................. 202 15.17 Debuggen von JDBC-Anwendungen ......................................................................... 202 15.18 CaseStudy Aufgabe (Aufgabe 1 von 030_JDBC) ...................................................... 202 15.19 CaseStudy Lösung .................................................................................................... 203 15.20 Die Datenbank Derby ................................................................................................ 204 16. Promod Tipps ................................................................................................................... 205 16.1 Methodik: Scrum ........................................................................................................... 205 17. Literatur (nehmen Sie jeweils die jüngste Ausgabe) ......................................................... 207 Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
6 / 210
23.03.2010
PROMOD-2a / SS10
Didaktik, Methodik:
Während der Vorlesung wird nicht der gesamte Stoff präsentiert. Bestimmte Abschnitte sind von den
Studierenden eigenständig zu erarbeiten, insbesondere die Details!
Der Modul Promod-2 ist als seminaristische Lehrveranstaltung organisiert.
Im theoretischen Teil erfolgt wöchentlich in einem Vorlesungsblock eine Einführung in die Theorie und die
Problematik. Grundlage des Vorlesungsblocks ist dieses Skript.
Die Einsicht in die Theorie wird in einem unmittelbar daran anschliessenden Vertiefungsblock („HandsOn“-Teil) in die Praxis umgesetzt und validiert. Für diesen Praktikumsblock werden Case-Studies
vorgegeben. Die Case-Studies sind teilweise in diesem Skript enthalten und teilweise auf dem
Materialsserver.
Projekte (und Übungen) werdem im Praktikumsblock durchgeführt. Die hierfür vorhandenen
Aufgabenstellungen lösen die Praktikumsteilnehmer selbständig. Der Praktikumsblock ist tutoriell betreut.
Im fortgeschrittenen Stadium der Lehrveranstaltung entwickeln die Studierenden eine zunehmende
Selbständigkeit und erarbeiten selbständig vorgegebne Themen im Rahmen eines angeleiteten
Selbststudiums.
Ab etwa Mitte der Vorlesungszeit wird der Vorlesungsblock zum Praktikumsblock dazugeschlagen um die
Projekte zu realisieren.
Lernziel: Sie können datenbankbasierte, im LAN oder Internet
verteilte System-Architekturen entwerfen und mit JAVA
implementieren. Die Architekturen können multi-threaded sein
und über eine GUI verfügen.
Lehr- und Lernmaterialien:
Das Pfeilsymbol (siehe Bild rechts) verweist auf „Case Studies“ für den „Hands-On“-Teil der
Vorlesung. Zu einem Teil der Case Studies bekommen Sie Experimentalquellen, die Sie auf dem
Materialserver finden. Der Hands-On-Teil findet jeweils im 2. Vorlesungsblock statt.
Der die Vorlesung begleitende Übungsteil findet im 3. Block statt. Für den Übungsteil gibt es eigene
Übungsaufgaben, die Sie auf dem Materialserver finden.
Sonstiges:
Das Trichtersymbol (Bild rechts) macht auf Hinweise zur Entwicklungsumgebung aufmerksam.
Tools & Server:
Bei den Tools & Servern gibt es Versionsabhängigkeiten! Mehr dazu in der Vorlesung.
Java SE
Java EE
Eclipse
Tomcat
XAMP (siehe readme_d.txt)
• Apache Web-Server
• MySQL Datenbank
• PHP
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
7 / 210
23.03.2010
PROMOD-2a / SS10
1. Vererbung (Wiederholung & Ergänzung)
Was Sie lernen:
was es mit der Klasse Object auf sich hat
die Rolle von Konstruktoraufrufen in Ableitungen
Zusammenhang von Vererbungsketten und Zuweisungskompatibiltät
was early und late binding ist
was abstrakte und finale Klassen sind
Voraussetzungen:
Arbeiten mit Klassen und Methoden
1.1 Grundlagen
Bekannt:
class subclass extends superclass
{
//zusätzliche Attribute
//zusätzliche Methoden
}
nur eine Superklasse;
keine Mehrfachvererbung!
Æ nur additiv hinzufügen
1.2 Die Klasse Object
Jede Klasse besitzt eine "unsichtbare" Superklasse "Object". Die Klasse Object ist die ultimative Basisklasse. Es
bedarf jedoch keiner expliziten Vererbungsanweisung (... extends Object).
class New [extends Object]
{
//Klasseninhalt
}
Die Klasse Object definiert mehrere Methoden, die allen Ableitungen, also allen Klassen zur Verfügung steht:
boolean equals(Object o)
Object clone()
String.toString()
Class getClass()
void notify()
void notifyAll()
void wait()
Protected void finalize()
Methode prüft, ob Objekt identischen Inhalt wie anderes
Objekt beinhaltet.
Die Methode muss in abgeleiteten Klassen überschrieben
(override) werden, wenn Sie auf inhaltliche Gleichheit eines
Objekts testen möchten. Ansonsten wird überprüft, ob es
sich um dieselbe Referenz auf das betreffende Objekt
handelt.
Erzeugt neues Objekt als exakte Kopie, jedoch vom Typ
Object! (override)
Liefert Stringrepräsentation des Objekts. (override)
Returns the runtime class of an object.
Siehe Multi-Threading.
Siehe Multi-Threading.
Siehe Multi-Threading.
Called by the garbage collector (… System.gc() …) on an
object when garbage collection determines that there are no
more references to the object.(override)
Umgedingt empfehlenswert: die API-Doku zu den Methoden lesen! (Insbesondere wg. „override“)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
8 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel:
public class ObjektMethoden
{
public static void main(String[] args)
{
ObjektMethoden o1 = new ObjektMethoden();
ObjektMethoden o2 = new ObjektMethoden();
o1.setData("Neu");
if(!o1.equals(o2))
System.out.println("Nicht gleich!");
System.out.println(o1.toString());
System.out.println(o2.toString());
ObjektMethoden o3 = (ObjektMethoden)o1.clone();
System.out.println(o3.toString());
}
private String data = "Objektdaten";
public String getData()
{
return data;
}
public void setData(String wert)
{
data = wert;
}
public String toString()
{
return data;
}
public Object clone()
{
ObjektMethoden om = new ObjektMethoden();
om.setData(data);
return om;
}
}
public boolean equals(Object o)
{
if(this == o)
return true;
if((o == null) || (this.getClass() != o.getClass()))
return false;
return (data.equals(((ObjektMethoden)o).getData()));
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
9 / 210
23.03.2010
PROMOD-2a / SS10
1.3 Konstruktoraufrufe:
Es können lediglich Methoden und Variablen vererbt werden. Konstruktoren können nicht vererbt werden.
Allerdings kann der Konstruktor der Basisklasse aufgerufen werden.
Beispiel:
class Unterklasse extends Basisklasse
{
public Unterklasse() // Standardkonstruktor
{
super(); // Konstruktor der Superklasse wird aufgerufen
//muss die erste Anweisung sein!!
//Die Signatur ist wichtig!!
.
.
.
}
}
Sie können jeden beliebigen Konstruktor der Superklasse aufrufen: die Auswahl ergibt sich aus der Signatur.
Aufruf von super() muss als erste Anweisung im Konstruktor stehen.
Sie können nur den Konstruktor er unmittelbaren Superklasse aufrufen: super.super() ist nicht möchglich.
Verwenden Sie keinen Aufruf von super(), so wird automatisch der Standardkonstruktor (auch DefaultKonstruktor genannt) der Superklasse als erste „unsichtbare“ Anweisung aufgerufen. Zur Erinnerung: Der
Default-Konstruktor, der vom Compiler aufgerufen wird, allokiert ein Objekt, führt aber keine Initialisierungen
durch: Der Standardkonstruktor ist ein Konstruktor ohne Parameter. (Hat eine Superklasse Konstruktoren,
jedoch keinen Standardkonstruktor und der Konstruktor der Subklasse ruft keinen Konstruktor der Superklasse
explizit auf, so meldet der Java-Compiler einen Fehler. Grund:
Sobald nur ein einziger selbst geschriebener Konstruktor existiert – ob mit oder ohne Parameter – so ist
der vom Compiler zur Verfügung gestellte Default-Konstruktor nicht mehr sichtbar.
Beispiel:
class Basis
{
protected int i; // in Ableitung zugreifbar
public Basis(int i)//Konstruktor
{
this i = i;
System.out.println("In Basisklasse");
}
}
class Unterklasse extends Basis
{
public Unterklasse(int i) // Konstruktor
{
super(i);
// Basisklassen-Konstruktor-Aufruf
System.out.println("In Unterklasse");
}
}
Um den Destruktor der Superklasse aufzurufen, kann die Anweisung super.finalize()
verwendet werden. (Es erfolgt kein automatischer Aufruf von finalize() der
Basisklasse.) Zur Erinnerung: die Java-Spezifikation garantiert nicht, dass die
Methode finalize() zu einem bestimmten Zeitpunkt aufgerufen wird!
Experimentieren Sie mit super.finalize() und Alternativen (z.B. dispose();
siehe: Promod1).
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
10 / 210
23.03.2010
PROMOD-2a / SS10
1.4 Vererbungsketten und Zuweisungskompatibilität:
Wie bekannt: von einer Klasse können weitere Klassen abgeleitet werden, von denen wiederum Klassen
abgeleitet werden: so entsteht eine Klassenhierarchie.
In Java hat man von einer abgeleiteten Klasse immer nur Zugriff auf die nicht privaten Daten und Methoden der
unmittelbaren Superklasse. Auf Elemente der Superklasse einer Superklasse hat man keinen direkten Zugriff:
super.super.methode() geht nicht. Allerdings stehen alle Daten und Methoden zur Verfügung, die jeweils
vererbt wurden.
Innerhalb der Klassenhierarchie sind Objekte abgeleiteter Klassen immer zu Objektvariablen der Superklassen
zuweisungskompatibel!
Der Grund liegt darin, dass diese Objekte immer mindestens die Methoden und Daten der Superklasse besitzen
und somit vollständig als ein solches Objekt funktionstüchtig sind.
Wenn man jedoch einer Objektvariablen der Superklasse ein Objekt der Subklasse zuweist, fehlen die Methoden,
die erst in der abgeleiteten (Sub-)Klasse definiert werden. (Muss auf diese Methoden der abgeleiteten Klassen
zugegriffen werden, so ist ein Cast der Superklassen-Objektvariable notwendig.) Siehe Beispiel unten
„instanceof“
Evaluieren Sie die oben genannten Punkte!
GraphikObjekt
Beispiel.:
Kreis
gefüllterKreis
Beispiel:
Rechteck
gefülltesRechteck
Kreis K = new gefuellterKreis();
.
.
.
GrafikObject go [] = new GrafikObjekt[10];
go[0] = new Kreis();
go[1] = new Rechteck();
go[2] = new gefuellterKreis();
go[3] = new gefuelltesRechteck();
.
.
.
Durch die Zuweisungskompatibilität können problemlos Typumwandlungen von der Subklasse
in die Superklasse vorgenommen werden. Z. Beispiel:
Kreis = (Kreis) gefuellterKreis();
Übung:Prüfen Sie, ob ein Cast in die umgekehrte Richtung möglich und sinnvoll
ist. Demonstrieren Sie das Ergebnis an einem Programmbeispiel.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
11 / 210
23.03.2010
PROMOD-2a / SS10
1.5 Case-Study Objekt-Arrays, Zuweisungskompatibiliät, Operator instanceof
(Klausur-Aufgabe PROMOD1 in WS0506)
1) Schreiben Sie eine übersetzungsfähige Java-Applikation, die folgende Aufgabenstellung löst:
Im Rahmen eines Computerverwaltungssystems gilt es Informationen über Computer zu
verarbeiten. Bei allen Computerarten sind folgende Daten von Bedeutung:
Hauptspeicherkapazität und Plattenspeicherkapazität (in MB) und Prozessortyp (Intel oder AMD).
Bei der Computerart „Laptop“ wird noch eine Angabe zum Gewicht (in Gramm) gebraucht. Bei der
Computerart „Server“ ist die Anzahl von Prozessoren (in Stück) bedeutsam.
A) Modellieren Sie ein geeignetes Java-Klassensystem (mit drei Klassen). Achten Sie darauf,
dass die Attribute nicht ohne die dafür vorgesehenen Methoden manipulierbar sind: Stellen Sie für
jedes Attribut eine separate read_in-Methode zur Verfügung, um das Attribut durch eine
Keyboard-Eingabe zu füllen. Ebenso stellen Sie für jedes Attribut eine separate print_outMethode zur Verfügung, um den Attributinhalt auf der Konsole auszugeben. Für jede der drei
Klassen bieten Sie Konstruktoren an, um bei der Instantiierung die Attribute nach
Programmiererwunsch entsprechend zu initialisieren. (30 Punkte)
B) Schreiben Sie eine main-Methode in der ein Array mit 3 Objekten angelegt wird. In die ArrayElemente müssen die Objekte der oben erwähnten drei Klassen passen: Legen Sie in das Array
je ein Objekt der oben erwähnten Kategorien. Danach müssen alle Objekte im Array über das
Keyboard gefüllt werden. Nachdem die Objekte im Array über das Keyboard gefüllt wurden,
werden die Inhalte aller Objekte wieder auf den Bildschirm ausgegeben. Für das Füllen und
Ausgeben des Array müssen Sie die dafür am besten geeignete Kontrollstruktur verwenden.(20
Punkte)
1.6 Case-Study Lösung:
public class computerverwaltung {
public static void main(String args[]){
System.out.println("Jetzt werden 3 Objekte in einem Array angelet.");
Computer computer_array[] = new Computer[3];
computer_array[0] = new Computer(1000,2000,"intel");
computer_array[1] = new Laptop(2000,3000,"amd",3);
computer_array[2] = new Server(3000,4000,"sparc",4);
// Zuweisungskompat.
for(int counter = 0; counter < 3; counter++) {
computer_array[counter].read_in_Hauptspeicher();
computer_array[counter].read_in_Plattenspeicher();
computer_array[counter].read_in_Proztyp();
//statische Lösung; funktioniert, ist aber nicht wirklich brauchbar...:
if (counter == 1){
((Laptop)(computer_array[counter])).read_in_Gewicht();
}
if (counter == 2){
((Server)(computer_array[counter])).read_in_anzahlProz();
}
} // end for()
for(int counter = 0; counter <3; counter++) {
computer_array[counter].print_out_Hauptspeicher();
computer_array[counter].print_out_Plattenspeicher();
computer_array[counter].print_out_Proztyp();
//dynamische Lösung mit instanceof-Operator:
if (computer_array[counter] instanceof Laptop){
((Laptop)(computer_array[counter])).print_out_Gewicht();
}
if (computer_array[counter] instanceof Server){
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
12 / 210
23.03.2010
PROMOD-2a / SS10
((Server)(computer_array[counter])).print_out_anzahlProz();
}
} // end for()
}// end main()
} // end class computerverwaltung
class Computer {
private int hauptspeicher;
private int plattenspeicher;
private String proztyp;
Computer(int hauptspeicher, int plattenspeicher, String proztyp){
this.hauptspeicher = hauptspeicher;
this.plattenspeicher = plattenspeicher;
this.proztyp = proztyp;
}
public void read_in_Hauptspeicher(){
hauptspeicher = StdInput.readInt("Hauptspeicherkapazitaet: ");
}
}
public void read_in_Plattenspeicher(){
plattenspeicher = StdInput.readInt("Plattenspeicherkapazitaet: ");
public void read_in_Proztyp(){
proztyp = StdInput.readString("Prozessortyp: ");
}
public void print_out_Hauptspeicher(){
System.out.println("Hauptspeicherkapazitaet: "+hauptspeicher);
}
}
public void print_out_Plattenspeicher(){
System.out.println("Plattenspeicherkapazitaet: "+plattenspeicher);
public void print_out_Proztyp(){
System.out.println("Prozessortyp: "+proztyp);
}
} // end class Computer
class Laptop extends Computer{
private int gewicht;
Laptop(int hauptspeicher, int plattenspeicher, String proztyp, int gewicht){
super(hauptspeicher,plattenspeicher,proztyp);
this.gewicht = gewicht;
}
public void read_in_Gewicht(){
gewicht = StdInput.readInt("Gewicht: ");
}
public void print_out_Gewicht(){
System.out.println("Gewicht: "+gewicht);
}
} // end class Laptop
class Server extends Computer{
int anzahlProz;
Server(int hauptspeicher, int plattenspeicher, String proztyp, int anzahlProz){
super(hauptspeicher, plattenspeicher, proztyp);
this.anzahlProz = anzahlProz;
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
13 / 210
23.03.2010
PROMOD-2a / SS10
}
public void read_in_anzahlProz(){
anzahlProz = StdInput.readInt("Anzahl Proz.: ");
}
public void print_out_anzahlProz(){
System.out.println("Anzahl Proz.: "+anzahlProz);
}
} // end class Server
1.7 Polymorphie
Mit Polymorphy (aus dem Griechischen: Vielgestaltigkeit) ist die Fähigkeit eines Objekts gemeint, die richtige
Methodenauswahl zu treffen.
Wird eine Methode eines Objekts aufgerufen, so kann sich die Methodendefinition an folgenden Stellen stehen:
o Die Klasse des Objekts hat die Methode definiert
o Die Methode wurde von der Superklasse der Objektklasse übernommen (geerbt)
o Die Methode existiert in der Superklasse, wurde aber in der davon abgeleiteten Klasse redefiniert /
überschrieben
Je nachdem, wann die Methodenauswahl erfolgt, spricht man von early binding oder late binding.
o Beim early binding erfolgt die Methodenauswahl statisch zur Übersetzungszeit.
o Beim late binding erfolgt die Methodenauswahl dynamisch zur Laufzeit.
Hinweis: bei statischen (static), finalen (final) und privaten (private) Methoden kommt stets early binding zur
Anwendung, da diese Methoden nicht überschrieben werden können (-> Performance).
Beispiel:
.
.
.
GrafikObject go [] = new GrafikObjekt[10];
go[0] = new Kreis();
go[1] = new Rechteck();
go[2] = new gefuellterKreis();
go[3] = new gefuelltesRechteck();
.
.
.
go[0].zeichne(); // early binding polymorphismus
go[1].zeichne(); // early binding polymorphismus
.
.
.
Î hier: "early binding"-Polymorphismus: Bereits während der Compilezeit wird festgelegt, welche Ausprägung einer
bestimmten Methode aufgerufen werden soll.
Im Gegensatz hierzu gibt es auch noch "late binding-Polymorphismus". Hier wird erst bei der Laufzeit festgelegt,
welche Ausprägung einer bestimmten Methode aufgerufen werden soll.
1.7.1
Beispiel 1 – Methoden überschreiben:
class Basis
{
public String getName()
{
return "in Basisklasse";
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
14 / 210
23.03.2010
PROMOD-2a / SS10
}
class Unter extends Basis;
{
public String getName(); // Methode mit gleicher Signatur wie in Klasse Basis
{
super.getName();
// Aufruf der Methode der Superklasse
return "in Unterklasse";
}
}
Zur Erinnerung: Es können keine Methoden einer Superklasse, die private, final oder static sind, überschrieben
werden.
1.7.2
Beispiel 2 – "late binding – Polymorphismus":
Das folgende Beispiel demonstriert das Wesen des late binding Polymorphismus:
public class Polymorph
{
public static void main(String[] args)
{
Zahl werte[] = new Zahl[2];
// … erfolgt häuftig erst zur Laufzeit
werte[0] = new DeutschZahl();
werte[1] = new EnglischZahl();
for(int i = 0; i < werte.length; i++)
werte[i].druckeZahl();
}
}
// late binding Polymorphismus
// Vergl. Bsp. “Computerverwaltung”
class Zahl
{
protected double wert = 123456.78;
public void druckeZahl()
{
System.out.println(wert);
}
}
class DeutschZahl extends Zahl
{
public void druckeZahl()
{
System.out.println("123.456,78");
}
}
class EnglischZahl extends Zahl
{
public void druckeZahl()
{
System.out.println("123,456.78");
}
}
Übung: Erläutern Sie, warum late binding Polymorphismus für eine Programmiersprache eine weitere Steigerung der
Flexibilität bedeutet.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
15 / 210
23.03.2010
PROMOD-2a / SS10
1.8 Abstrakte Klassen und Methoden:
Abstrakte Klassen dienen als Vorlage für andere Klassen.
Von abstrakten Klassen können keine Instanzen gebildet werden; nur Ableitungen sind möglich.
Nicht alle Methoden einer abstrakten Klasse müssen leer sein, es können auch schon bereits voll ausgeprägte
Methoden implementiert werden, dies ist jedoch nicht zwingend erforderlich. In d. R. besitzen abstrakte Klassen
mindestens eine abstrakte Methode, die nur aus Methodendefinition (Signatur) ohne Rumpf besteht.
Abstrakte Methoden werden mit dem Identifier "abstract" gekennzeichnet.
Sinn und Zweck: Die Grundfunktionalität/Schnittstelle soll vorgegeben werden, jedoch erfolgt die
Implementierung später. (Anwendung u.a. in der Design-Phase)
Es ist möglich, Objektvariablen von der abstrakten Klasse zu erzeugen. Diesen Variablen können Objekten von
Ableitungen zugewiesen werden.
Beispiel:
public class VorlageTest
{
public static void main(String[] args)
{
Vorlage v = new VorlageImpl();
}
}
abstract class Vorlage
{
public abstract String getName(); // abstrakte Methode
}
class VorlageImpl extends Vorlage
{
public String getName()
{
return "VorlageImpl";
}
}
Wird eine abstrakte Klasse definiert, die ausschliesslich abstrakte Methoden besitzt, so kann man stattdessen
besser ein Interface einsetzen. Von Interfaces ist eine „Mehrfachvererbung“ möglich. (Siehe weiter unten Kapitel
Interfaces.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
16 / 210
23.03.2010
PROMOD-2a / SS10
1.9 Finale Klassen:
Von finalen Klassen kann keine Ableitung gemacht werden.
Sie werden mit dem Attribut final erzeugt.
Der Aufruf von finalen Klassen ist sehr schnell (wg. early binding).
Typische Nutzung: für Hilfsklassen mit static-Methoden, die ohne Instanz einer Klasse aufgerufen werden
können.
Im Wesentlichen wird durch den Identifier "final" die Vererbungsfähigkeit verhindert. Dies widerspricht
eigentlich zwar der Objektorientierung, wird aber z.B. aufgrund von Sicherheitsaspekten in Kauf genommen.
Beispielsweise soll eine Passwortüberprüfung nicht einfach überschrieben werden können.
Beispiel:
public final class Formeln
{
public static int getKreisUmfang(int radius)
{
return (2 * java.lang.Math.PI * radius);
}
}
Finale Klassen des JDK sind z.B. java.lang.String und java.lang.Math (Überprüfen
Sie das.)
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu
diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
17 / 210
23.03.2010
PROMOD-2a / SS10
2. Packages (Selbststudium!)
Was Sie lernen:
warum in Java Packages verwendet werden
wie Sie Packages definieren
wie Sie Packages importieren/einbinden
was es mit den Zugriffsrechten im Zusammenhang mit Packages auf sich hat
Voraussetzungen:
Erzeugen, Übersetzen und Ausführen von Java-Anwendungen
2.1 Grundlagen
Moderene Programmiersprachen müssen das Design (den Entwurf) eines Programmes unterstützen. Hierfür sind
Sprachmittel erforderlich, die es erlauben, ein Programm in Programmeinheiten zu unterteilen, um das
Gesamtprogramm übersichtlich (u.a. wg. Wartbarkeit/Änderbarkeit) zu strukturieren.
Solche Programmeinheiten sind in Java: Klassen, Interfaces, Threads und Packages.
Betrachten wir zunächst Packages:
Pakete gruppieren Klassen in einer Sammlung. Pakete eignen sich hervorragend, um die Programme zu
organisieren. Packages lassen sich auch in Archiven (jar-Files im zip-Format) halten.
Mit Packages werden Namensräume organisiert
Für das Verwenden und Einbinden in ein Filesystem ist die Variable CLASSPATH relevant. Sie gibt den Pfad
zum Paket-Startverzeichnis an.
Andere Möglichkeit: jar-Files „lokal“ lassen (z.B. im src-Package) und in der Compiler-Option –classpath
(durch „:“ separiert) auflisten. So können „External Libraries/JARs“ genau definiert eingebunden werden.
In ECLIPSE: Project -> Properties -> Java Build Path -> Library -> Add JARs / Add Library / Add external
JARs
Der Zugriff auf eine Klasse erfolgt durch die Kombination des Package-Namens und des Klassennamens.
Package-Namen setzen sich aus kleingeschriebenen Worten zusammen, die einen Pfad repräsentieren, deren
Abschnitte durch Punkte voneinander getrennt werden.
2.2 Packages importieren:
Man kann eine bestimmte Klasse oder ein gesamtes Paket importieren.
Es ergeben sich keine negativen Auswirkungen auf die Kompilierzeit bzw. Codegröße, wenn alle Klassen
eines Paketes importiert werden. Jedoch aus Gründen der Übersichtlichkeit bzw. der Eindeutigkeit bietet sich
die explizite Importierung einzelner Klassen an.
Die Importanweisung steht am Beginn der Quelldatei zwischen einer optionalen package-Anweisung und der
ersten Klassendefinition.
Über eine import-Anweisung kann immer nur ein Package eingebunden werden. Besitzt ein Package weiterere
Unterpackages, so müssen diese separart importiert werden.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
18 / 210
23.03.2010
PROMOD-2a / SS10
Bsp:
import java.util.*;
import java.sql.*;
import java.util.Date;
.
.
.
Î da die Klasse "Date" sowohl in java.util bzw. java.sql vorkommt, muss hier der Namenskonflikt mit einer
expliziten Importanweisung aufgehoben werden. Ansonsten ergeben sich Fehler zur Kompilierzeit.
2.3 Packages definieren:
Um Klassen in ein Paket aufzunehmen, muss der Name des Pakets am Beginn der Klasse angegeben werden.
Dies steht noch vor dem eigentlichen Code: package <PackageName>
Vor der Package-Anweisung dürfen sich nur Kommentare befinden.
Wird keine Package-Anweisung in die Quelldatei aufgenommen, dann gehören die Klassen in dieser Quelldatei
zum Standardpaket / Default-Package. Das Standardpaket hat keinen Paketnamen. Alle Klassen im aktuellen
Verzeichnis gehören somit zusammen.
Bsp:
package my.package.proccon;
.
.
//weiterer Quellcode
2.4 Zugriffsrechte in Packages:
Für Pakete gelten nach wie vor die Zugriffsmodifizierer public, private und protected. Wird
kein Zugriffsattribut angegeben so wird ein defaultmässiges Attribut ("package" oder
"friendly") gesetzt, was den Zugriff aller Methoden im selben Paket möglich macht.
Beispiel Package-Import und -Definition:
import bsp_u_ueb_erstes_semester.woche_10_1gp_kap10_packages.util.*;
public class PackageDemo
{
public static void main(String[] args)
{
greg_calendar.prefix = "Heute: ";
System.out.println("Aufgepasst...");
System.out.println(greg_calendar.getDate());
// greg_calendar.prefix = "Heute ist der ";
System.out.println(bsp_u_ueb_erstes_semester.woche_10_1gp_kap10_packages.util.greg_cal
endar.getDate());
}
}
--------------------------------util.greg_calenadr.java----------------------------------------------------------package bsp_u_ueb_erstes_semester.woche_10_1gp_kap10_packages.util;
public class greg_calendar
{
static String prefix;
public static String getDate()
{
java.util.GregorianCalendar gc = new java.util.GregorianCalendar();
System.out.println("greeting from the long named package...");
return (gc.get(gc.DATE) + "." + (gc.get(gc.MONTH)+1) + "." + gc.get(gc.YEAR));
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
19 / 210
23.03.2010
PROMOD-2a / SS10
}
}
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu
diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
20 / 210
23.03.2010
PROMOD-2a / SS10
3. Interfaces (und Ergänzungen zu Klassen)
Was Sie lernen:
wie Sie Interfaces definieren und verwenden
was innere und anonyme Klassen sind
was Adapterklassen sind und wozu sie verwendet werden
Voraussetzungen:
Klassen und Vererbung
3.1 Grundlagen
Beim Entwurf von objektorientierten Systemen geht man über verschiedene Stufen der Abstraktion:
Systemanalyse: „Welche Klassen werden benötigt und welche Beziehungen bestehen zwischen den Klassen?“
(Konzeptionelle Sicht: welche Klassen spielen eine Rolle („Klassenkandidatenkatalog“); in welcher Beziehung
stehen die Klassen untereinander?).
Entwurf/Design: „Welche Schnittstellen haben die Klassen?“ (Spezifizierende Sicht: hier kümmert man sich um
die Schnittstellen der Klassen!)
Implementierung: übernimmt die Ausprogrammierung der Schnittstellen (Interfaces) Æ Verfeinerung der
Spezifikation. (Implementierende Sicht: hier werden die Schnittstellen implementiert!)
Java unterstützt das „Programmieren im Großen“ (spezifizierende Sicht) mit dem Sprachmittel interface.
Ein Interface definiert einen abstrakten Datentyp, der nur Konstanten und Methodensignaturen besitzt.
Methoden werden nur deklariert, nicht implementiert.
Klassen können mehrere Interfaces implementieren (=> „Mehrfachvererbung“).
Interfaces werden mit dem Schlüsselwort "interface" eingerichtet
Definition:
[public] interface <Name> [extends <Interface>[,<Interface>]]
{
[public, static] final <Typ> Konstante;
[public] <Typ> Methode(<Parameter>);
}
Implementierung:
class <Name> implements <InterfaceName>, <InterfaceName>
Alle Methoden in einem Interface sind automatisch abstract und public.
Elemente von Interfaces können nicht protected oder private sein.
Interfaces haben keine Konstruktoren/Destruktoren
Es können aber Variablen vom Typ "Interface" definiert werden Interfacename Variable; Î hier können
Objekte zugewiesen werden, deren Klasse das Interface implementiert.
Über den Operator instanceof kann geprüft werden, ob ein Objekt ein Interface implementiert.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
21 / 210
23.03.2010
PROMOD-2a / SS10
Interfaces sind keine Klassen. Insbesondere kann man eine Schnittstelle nicht mit dem Operator new
instantiieren. (Man spricht auch nicht von Ableitung, sondern von Implementierung).
Beispiel 1 – Interface: (Phänomen: Mehrfachtypisierung)
public class Interface {
public static void main(String[] args) {
TestClass1 tco1 = new TestClass1();
TestClass2 tco2 = new TestClass2();
tco1.sayHello(); /*1*/
tco2.sayHello(); /*1*/
if (tco1 instanceof TestClass1) /*2*/ // tco1 ein Klassen-Tpy
System.out.println("tco1 is instance of TestClass1");
if (tco1 instanceof PoliteObject) /*2*/ // tco1 ein Interface-Typ
System.out.println("tco1 is instance of PoliteObject");
}
PoliteObject po; /*3*/ //Interface-Varable nimmt auch “Ableitungen” auf.
po = tco1;
/*4*/ // po referenziert TestClass1-Objekt
po.sayHello();
po = tco2;
/*5*/ // po referenziert TestClass2-Objekt
po.sayHello();
}
interface PoliteObject {
public void sayHello();
}
// nur Methoden-Signatur
class TestClass1 implements PoliteObject {
public void sayHello() {
System.out.println("Hello World");
}
}
class TestClass2 implements PoliteObject {
public void sayHello() {
System.out.println("Guten Tag");
}
}
//
//
//
//
//
//
//
//
//
unterschiedliche Implementierungen
eines Interfaces
Erläuterungen zum Beispiel: Das Beispiel definiert die Schnittstelle "PoliteObject" welche die Methode "sayHello()"
anbietet. Die beiden Klassen "TestClass1" und "TestClass2" implementieren jeweils Methoden für die durch die
Schnittstelle definierten Operationen. Der erste Anweisungsblock zeigt die (simple) Ausführung /*1*/ der genannten
Methode. Die darauffolgende Codesequenz /*2*/ hebt den Aspekt der Mehrfachtypisierung hervor (Objekt von
schnittstellen-implementierender Klasse ist sowohl Ausprägung der Klasse selbst (im Beispiel: TestClass1), als auch
der Schnittstelle (PoliteObject). Abschließend wird eine Variable vom Typ der Schnittstelle deklariert /*3*/ und
zunächst mit der Referenz auf ein Objekt der Klasse "TestClass1" belegt /*4*/. Der (statisch typsicher mögliche)
Aufruf "sayHello()" ist auch hier möglich. Gleiche Bedingungen herrschen nach der Zuweisung eines Objektes vom
Typ "TestClass2" an dieselbe Variable.
Beispiel 2 – Interface: (Tpyische Verwendung eines Interfaces)
// Ausgabetypen.java im default-Package-------------------------------------public interface Ausgabetypen
{
void ausgabe();
void ausgabeLang();
void ausgabeKurz();
}
// Datum1.java im default Package-------------------------------------------public class Datum1 implements Ausgabetypen // alle Ausgabetypen-Methoden
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
22 / 210
23.03.2010
PROMOD-2a / SS10
{
private int tag, monat, jahr;
// müssen implementiert werden!!!
alle Interface-Methoden
werden implementiert
public Datum1()
{
this(1, 1, 2002);
}
public Datum1(int tag, int monat, int jahr)
{
this.tag = tag;
this.monat = monat;
this.jahr = jahr;
}
public void ausgabe()
{
System.out.println("Datum: " + tag + "." + monat + "." + jahr);
}
public void ausgabeLang()
{
}
System.out.println("Das gespeicherte Datum ist der " + tag + "." + monat + "." + jahr);
public void ausgabeKurz()
{
System.out.println(tag + "." + monat + "." + jahr);
}
}
// Zeit1.java im default-Package--------------------------------------------------public class Zeit1 implements Ausgabetypen
{
private int sekunde, minute, stunde;
public Zeit1()
{
this(0, 0, 0);
}
public Zeit1(int sekunde, int minute, int stunde)
{
this.sekunde = sekunde;
this.minute = minute;
this.stunde = stunde;
}
alle Interface-Methoden
werden implementiert
public void ausgabe()
{
System.out.println("Zeit: " + sekunde + ":" + minute + ":" + stunde);
}
public void ausgabeLang()
{
System.out.println("Die gespeicherte Zeit ist " + sekunde + ":" + minute + ":" +
stunde);
}
public void ausgabeKurz()
{
System.out.println(sekunde + ":" + minute + ":" + stunde);
}
}
// Test1.java im default-Package----------------------------------------------------public class Test10
{
private Ausgabetypen[] at = new Ausgabetypen[4];
public Test10()
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
23 / 210
23.03.2010
PROMOD-2a / SS10
{
at[0]
at[1]
at[2]
at[3]
=
=
=
=
new
new
new
new
Datum1(4, 3, 2002);
Zeit1(0, 12, 12);
Datum1(8, 9, 2001);
Zeit1(59, 59, 23);
for(int i = 0; i < at.length; i++)
if (at[0] instanceof Ausgabetypen)
at[i].ausgabe();
}
public static void main(String[] args)
{
new Test10(); // Start über Objektinstanziierung
}
}
Übung: Fassen Sie die Unterschiede zwischen abstrakten Klassen und Interfaces zusammen.
3.2 Adapterklassen
Eine Adapterklasse in Java ist eine Klasse, die ein vorgegebenes Interface mit leeren Methodenrümpfen
implementiert, bzw. Rümpfen, die einen Standardwert zurückliefern.
Adapterklassen können verwendet werden, wenn aus einem Interface lediglich ein Teil der Methoden benötigt
wird, der Rest aber „uninteressant“ ist, (momentan) nicht gebraucht wird.
In diesem Fall leitet man einfach eine neue Klasse aus der Adapterklasse ab, anstatt das zugehörige Interface zu
implementieren, und überlagert die benötigten Methoden.
Entwicklungsidee: stets zu Interfaces gleich die Adapterklassen mit anbieten….!!!!
Hinweis:
- wird vor allem bei der GUI-Programmierung verwendet.
- Adapterklassen können auch weitere/zusätzliche Methoden anbieten.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
24 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Adapterklassen (vgl. Beispiel 2 oben):
// AusgabetypenAdapter.java im default-Package------------------------------------------------------public class AusgabetypenAdapter implements Ausgabetypen // Adapter impl. Interface
{
public void ausgabe() {}
public void ausgabeLang() {}
public void ausgabeKurz() {}
}
// Datum2.java im default Package---------------------------------------------------------------public class Datum2 extends AusgabetypenAdapter // jetzt nur die gewünschten
{
// Methoden implementieren
private int tag, monat, jahr;
public Datum2()
{
this(1, 1, 2002);
}
public Datum2(int tag, int monat, int jahr)
{
this.tag = tag;
this.monat = monat;
this.jahr = jahr;
}
public void ausgabe() // die anderen Methoden bleiben wie sie sind
{
System.out.println("Datum: " + tag + "." + monat + "." + jahr);
}
}
Übung: Machen Sie das Beispiel lauffähig durch die Implementierung der fehlenden Klassen.
3.3 Innere Klassen (auch: geschachtelte / eingebettete / nested class)
Eine innere Klasse ist eine Klasse, die innerhalb einer anderen Klasse definiert ist.
Aus inneren Klassen können auf alle Methoden und Daten der äußeren Klasse zugegriffen werden, auch
wenn diese private sind.
Innere Klassen sind den anderen Klassen des Pakets verborgen. (Weitere Stufe der Kapselung)
Es können nur in der äußeren Klasse Objekte der inneren Klasse erzeugt werden
ÆAusnahme: statische innere Klassen, von denen auch in anderen Klassen Objekte erzeugt werden können.
Statische innere Klassen besitzen keine Referenz auf ein Objekt der äußeren Klasse.
Für jede innere Klasse wird beim Compilieren eine eigene Klassendatei erzeugt. Diese wird so gekennzeichnet:
outer$inner.class
Der Klassenname nach außen lautet outer.inner
Der Konstruktoraufruf sieht folgendermaßen aus:
in outer:
new inner(…)
sonst:
outer.new inner(…)
Die bekannten Modifier public, private, protected, final und static können in inneren Klassen
verwendet werden
Beispiel – innere Klassen:
class aussen // Implementierungsdetails verborgen in inneren Klassen
{
class mitte // nur zur Verwendung in aussen
{
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
25 / 210
23.03.2010
PROMOD-2a / SS10
class innen // nur zur Verwendung in mitte
{
.
.
.
}
}
}
Hinweis/Motivation für innere Klassen:
Innere Klassen sind sehr komfortabel, wenn man ereignisgesteuerte Programme schreibt (siehe weiter unten
„Ereignisse und Event-Handling“).
Anonyme innere Klassen bieten sich an, wenn man Callbacks en passant definieren möchte (siehe weiter
unten „Ereignisse und Event-Handling“ z.B. bei der AWT-Programmierung).
Ausserhalb dieser Anwendungsgebiete sieht man innere Klassen relativ selten, weil sie nicht im Brennpunkt des
objektorientierten Modellierens liegen. a) Die typische Vorgehensweise ist der Entwurf „kleiner“ Klassen, die dann
per Ableitung um weitere Daten und Methoden angereichert werden. Atypisch: komplexes im Inneren immer feiner zu
modellieren. b) Innere Klassen kann man in anderen Klassen nicht nutzen, das möchte man aber im Sinne einer
Wiederverwendung!
3.4 Lokale Klassen
Siehe „Tiger“-Buch Seite (415 ff.) und andere Literatur.
Übung: Beschreiben Sie was lokale Klassen sind.
3.5 Anonyme Klassen
Anonyme Klassen sind spezielle Varianten von inneren Klassen. Ort der Verwendung: typischerweise auf
Parameterposition von Methodenaufrufen.
Anonyme Klassen besitzen keinen Namen. Es werden die Klassendeklaration und Objekterzeugung in einem
Schritt zusammengefasst.
Anonyme Klassen müssen von einer anderen Klasse abgeleitet werden oder ein Interface/Adapterklasse
implementieren.
Anonyme Klassen werden verwendet, wenn eine einzige Objektinstanz gebraucht wird. Meist auf der
Parameterposition von Methoden. (z. B. bei Ausdrücken oder aber der GUI-Programmierung).
o Statt eines Objektes wird über new und die nachfolgende Angabe eines Klassen- oder
Interfacenamens die Erstellung sowie Implementierung einer Klasse mit Objekterzeugung
verbunden.
o Im runden Klammernpaar können Parameter für den Konstruktor übergeben werden (falls eine
Superklasse vorhanden ist).
o Bei Interfaces können keine Parameter übergeben werden, da sie keine Konstruktoren besitzen.
o Das erzeugte Objekt wird der Methode als Parameter übergeben.
Für anonyme Klassen wird beim Compilieren eine eigene Klassendatei erzeugt. Anonyme Klassen werden
durchgezählt und mit dem $-Zeichen mit äußeren Klassennamen verbunden: outer$1.class
Anonyme Klassen haben keine Konstruktoren.
Beispiel – anonyme Klassen:
// Ausgabetypen.java im default-Package------------------------------------------public interface Ausgabetypen
{
void ausgabe();
void ausgabeLang();
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
26 / 210
23.03.2010
PROMOD-2a / SS10
}
void ausgabeKurz();
// AusgabetypenAdapter.java im default-Package-----------------------------------public class AusgabetypenAdapter implements Ausgabetypen
{
public void ausgabe() {}
public void ausgabeLang() {}
public void ausgabeKurz() {}
}
// Test14.java im default-Package-------------------------------------------------public class Test14
{
private Ausgabe a = new Ausgabe();
private String s = "Anonym bleiben";
public Test14() // Konstruktor
{
a.drucke(new AusgabetypenAdapter() /// anonyme Klasse impl. Adapter ///////
{
/// Objekt wird erzeugt und der
///////
public void ausgabe()
/// Methode drucke() mitgegeben
///////
{
System.out.println(s);
}
Überschreibt
});
leere Methode
}
aus der
Adapterklasse
public static void main(String[] args)
{
new Test14(); // Start via Konstruktor
}
class Ausgabe ////////////////////////////////////////////////////////////////
{
public void drucke(Ausgabetypen at)
{
at.ausgabe();
}
}
}
Das oben dargestellte Beispiel wird uns als „Muster“ bei der AWT-Programmierung wieder begegnen!
3.6 Statische innere Klassen (static class) / statische innere Schnittstellen
Bei inneren Klassen/geschachtelten Klassen kennt das eingeschlossene Objekt sein umgebendes Objekt und
umgekehrt. Bei statisch geschachtelten Klassen gibt es diesen Bezug nicht: bei der statischen inneren Klasse
braucht man keine Objekt von der äusseren Klasse, um ein Objekt der inneren Klasse zu erzeugen.
Im äusseren Objekt können mehrere statische innere Objekte erzeugt werden.
Übung: Überlegen Sie sich eine Beispiel, anhand dessen Sie den oben beschreibenen Sachverhalt
experimentell studieren können.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
27 / 210
23.03.2010
PROMOD-2a / SS10
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu diesem Kapitel!
(Siehe Unterlagen hierzu auf dem Materialserver.)
4. Ein-/Ausgabe auf Konsole,Dateien u.a.(ADRELI_CON)
Was Sie lernen:
wie Sie Ein- und Ausgabe über die Konsole und die Tastatur realisieren
wie Sie mit Dateien (class File) arbeiten: die Schnittstelle zu Pfadnamen
was Dateien mit wahlfreiem Zugriff sind und wie sie benutzt werden
Voraussetzungen:
Arbeiten mit Klassen und Methoden
Arbeiten mit dem Exceptionhandling
Im Grunde könnte das Kapitel auch „Persistierung – Erster Teil“ heissen: es gibt die zwei großen
Persistenz-Konzepte, um Daten dauerhaft zu machen.
Zum einen können dazu Dateien genutzt werden und
zum zweiten Datenbanken. Beide Ansätze haben ihere Berechtigung und jeweilse ihre
spezifischen Vor- und Nachteile.
In diesem Kapitel kümmern wir uns um die ersten Schritte der Persistierung mit Hilfe der Dateien; hierfür gibt
es mehrere Konzept: wir können Dateien als Random-Access-Files sehen oder auch als Streams.
Die Streams wiederum sind selbst eine abstrahierende Schicht, hinter der sich neben
Dateien auch Sockets, Pipes und Strings verbergen können.
Auf für die Persistierung mit Datenbanken gibt es mehrere Java-Konzepte; diese behandeln wir in
separaten Kapiteln.
4.1 Grundlagen
Es ist zwischen Applikationen und Applets zu Unterscheiden. Applets haben i. d. R. keinen Zugriff auf Files des
lokalen Rechners.
I/O - Operationen laufen in Java über:
1.) Streams (Class Stream) für Binär- und und Zeichen-I/O
2.) Dateien (Random-Access-Files, Dateien für wahlfreien Zugriff; Class RandomAcessFile und zus. File)
Merkmale von Streams:
Besitzen Datenquelle (Source) und Datensenke (Destination). Ein Datenstrom kann von einer beliebigen Quelle
kommen: Dateien, Netzwerkschnittstelle (Sockets: siehe Kapitel „Socket-Programmierung. Für Sockets gibt’s
z.B. getInputStream() und getOutputStream()-Methoden, wenn es um Byte-Stream-Kommunikation (= binäre
i/o) geht. (Prüfen Sie ein entsprechendes Methoden-Angebot für die Charakter-Stream-Kommunikation (= Zeicheni/o))!
Pipes (für Pipes gibt es eigene Stream-Klassen, jeweils für die Byte- oder Character-Kommunikation), oder auch
Strings (für Stings/Arrays gibt es jeweils eigene Stream-Klassen für die Byte- oder Charcter-Kommunikation)
können Quellen sein. Das gleiche gilt für das Ziel des Datenstroms.
Jeder Datenstrom kann nach dem gleichen Prinzip, mit den gleichen Methoden verarbeitet werden, egal woher
der Strom kommt und für welches Ziel er bestimmt ist.
Streams sind unidirektional (Monolog). Für einen Dialog zwischen zwei Kommunikationspartnern (Applikationen,
Threads) werden zwei Streams benötigt.
Streams unterliegen dem FIFO - Prinzip ("first in first out”)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
28 / 210
23.03.2010
PROMOD-2a / SS10
Während der Datenübertragung können Daten gefiltert (bearbeitet) werden
Merkmale von wahlfreien Dateien:
für den wahlfreien Zugriff ist die Quelle und das Ziel immer eine Datei.
Dateien können sind bidirektional: sie können geschrieben und gelesen werden.
Während der Übertragung der Daten ist keine Filterung möglich.
Basis-Philosophie für den Umgang mit Streams und Dateien:
Bevor Dateien und Streams verwendet werden können (Anwendung von E/A-Operationen) müssen sie eröffnet
werden.
Nicht mehr benötigte Dateien und Streams sollten wieder geschlossen werden (Methode close()).
Das Öffnen von Dateien und Streams wird in Java üblicherweise über den Konstruktor der jeweiligen Klasse
realisiert.
Standard-Streams (siehe unten) werden automatisch geöffnet und geschlossen.
4.2 Standard-Ein- und Ausgabe
Die Ein-/Ausgabe über die Standard-E/A-Geräte (meist Tastatur und Bildschirm) ist in der Klasse System
implementiert. (Diese Klasse ist im Paket java.lang definiert; bei der Verwendung der Standard E/A ist also kein
zusätzliches Paket einzubinden.)
Die Standard-E/A arbeitet mit Objekten der Stream-Klassen InputStream und PrintStream.
Stream-Typen
Standard-Eingabestrom
Standard-Ausgabestrom
Standard-Fehlerausgabestrom
Datenfelder-Felder der Klasse
System
static InputStream in
static PrintStream out
static PrintStream err
Beispiele, Methoden
System.in.read()
System.out.print()
System.err.print()
Beispiel – I/O
import java.io.*;
public class IO
{
public static void main(String[] args)
{
byte [] b = new byte[1];
try
{
}
}
System.out.println("Ein Zeichen eingeben: ");
System.in.read(b);
System.out.println( (char) b[0] + " hat ASCII-Wert " + b[0]);
catch (IOException ioe)
{
System.out.println(ioe.getMessage());
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
29 / 210
23.03.2010
PROMOD-2a / SS10
"out"-Methoden
read()
print(char c)
/ println(char c)
print(double d) / println(double d)
print(int i)
/ println(int i)
print(Object o) / println()
print(String s) / println()
read(byte[] b)
"read"-Methoden
Liefert nächstes Zeichen aus
dem Eingabestrom als return
füllt ein Byte-Array mit maximal
b.length ASCII-Zeichen
read(byte[] b, int off,
int len)
- ,, - , jedoch mit Startpos. und
Länge
Methode zum den Eingabepuffer leeren: System.in.skip(System.in.available());
Beispiel:
//
//
//
//
//
//
//
PROMOD; Prof. Illik
Gepufferte Ein-/Ausgabe
Experimentieren Sie mit
dem Programm und prüfen Sie,
was passiert, wenn Sie die jeweiligen
Zeilen mit der skip()-Methode
auskommentieren. Erklärung!
import java.io.*;
public class StdIO
{
public static void main(String[] args)
{
int a;
byte[] b = new byte[1];
byte[] line = new byte[81];
try
{
System.out.print("1. Bitte geben Sie ein Zeichen ein: ");
a = System.in.read();
System.out.println("Zeichen "+ (char)a + " hat den ASCII-Code " + a + "!");
// Vergleiche ASCII-Code mit dem UNICODE (Unicode-Tabelle in Kap15)
// www.unicode.org
System.out.println("2. Bitte geben Sie ein Zeichen ein: ");
//System.in.skip(System.in.available()); //Leeren des Tastaturpuffers
System.in.read(b);
System.out.println((char)b[0] + " hat den ASCII-Code " + b[0]);
System.out.print("3. Bitte geben Sie noch ein Zeichen ein: ");
System.in.skip(System.in.available());
System.in.read(b);
System.out.println((char)b[0] + " hat den ASCII-Code " + b[0]);
System.out.print("4. Geben Sie jetzt mehrere Zeichen ein: ");
System.in.skip(System.in.available());
System.in.read(line);
//for(int i=0;line[i] != '\u0000'; i++)
for(int i = 0;line[i] != 0; i++)
//for (int i = 0; i < line.length; i++)
{
System.out.print((char)line[i]);
}
}
catch(IOException ioex)
{
System.out.println(ioex.getMessage());
}
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
30 / 210
23.03.2010
PROMOD-2a / SS10
4.3 Die Klasse File:
Was die Klasse File ist: „An abstract representation of file and directory pathnames.“ (Zitat aus der File-API).
Die Methoden der Klasse File sind auf Dateien (Files) und Verzeichnisse ausgerichtet. Z. B. Namen festlegen;
Dateien anlegen, umbenennen, Attribute auflisten,...
Die Klasse "File" besitzt drei verschiedene Konstruktoren:
File(String path)
File(String parentpath, String filename)
File(File directory, String filename)
Î Die Angabe des Pfads ist plattformabhängig, dass heißt, auf Windows-Rechnern trennt ein Backslash die
Pfade (»temp\dasda«) und auf UNIX-Maschinen ein normaler Slash (»temp/dasda«). Glücklicherweise können wir
die Klasse File nach dem separatorChar fragen.
Î Ebenso wie bei den Pfadtrennern gibt es einen Unterschied in der Darstellung des Wurzelverzeichnisses (Root).
Unter UNIX ist dies ein einzelner Slash (»/«), und unter Windows ist die Angabe des Laufwerks vor dem Doppelpunkt
und dem Backslash-Zeichen gestellt (»D:\«)
Î Eine absolute Pfadangabe wird inklusive der Wurzel angegeben. Bsp: C:\Programme\MyJavaProg\IO-Prog.jar
Î Eine relative Pfadangabe enthält keine Wurzel uns ist relativ zum aktuellen Verzeichnis. Bsp: \MyJavaProg\IO.jar
Î Siehe auch: UNC (Universal Naming Convention) für Microsoft Windows.
Bsp – Pfadangaben unter Linux
File f1 = new File("/tmp/file1.txt"); //absolute Pfadangabe inkl. Wurzel (/)
File f2 = new File("tmp/file2.txt"); //relative Pfadangabe ohne Wurzel
wichtige Methoden der Klasse File (Auszug):
Pfad und Dateiangaben
String getName()
String getPath()
String getParent()
String getAbsolutPath()
File [] listRoots()
Gibt einen String zurück, der den Dateinamen des File-Objekts enthält
Gibt einen String zurück, der den Pfadnamen der Datei enthält
Gibt einen String zurück, der den übergeordneten Pfad des File-Objekts enthält
Gibt einen String zurück, der den absoluten Pfadnamen enthält
Gibt ein Array von File-Objekten zurück, die allen verfügbaren Stammverzeichnissen entsprechen
Manipulstionsoperationen
boolean createNewFile()
Legt eine neue, leere Datei mit dem entsprechenden Namen an (RECHTE!!!)
boolean delete()
Versucht die Datei zu lösche, liefert im Erfolgsfall "true", andernfalls "false"
boolean mkdir()
Versucht ein Unterverzeichnis anzulegen, im Erfolgsfall "true", andernfalls
"false"
boolean renameTo(File f)
Liefert "true", falls der Name entsprechend geändert wurde, andernfalls "false"
boolean setReadOnly()
Setzt das Dateiatribut »schreibgeschützt«. Im Erfolgsfall "true", andernfalls
"false"
Datei-und
Verzeichnisinfos
boolean canRead()
boolean canWrite()
boolean exists()
boolean isDirectory()
Gibt an, ob die Datei durch die aktuelle Anwendung gelesen werden kann
Gibt an, ob die Datei beschreibbar oder nur lesbar ist
Liefert "true", wenn die Datei oder das Verzeichnis existiert, andernfalls "false"
Gibt "true" zurück, wenn das File-Obj. ein Verzeichnis repräsentiert, sonst
"false"
boolean isHidden()
Prüft, ob das File-Objekt eine versteckte Datei oder Verzeichnis repräsentiert
long lastModified()
Gibt die Zeit der letzten Änderung in mSec als Unix-Timestamp zurück
(1.1.1970)
long length()
Gibt die Länge der Datei in Byte zurück, oder 0, wenn die Datei nicht existiert
string [] list() und FILE [] listfile()-Methoden listen die Verzeichnisinhalte auf. Siehe API-Doku
class File.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
31 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel:
import java.io.*;
public class FileInfo
{
public static void main(String[] args)
{
File verzX = new File("/temp/a/b/c/d");
if(!verzX.exists())
{
verzX.mkdir(); // +++++ wirft keine IOException -> siehe Doku!
System.out.println("Verzeichnis " + verzX.getPath() + " angelegt: " +
verzX.exists());
}
File file = new File("/temp/test.txt");
if(! file.exists())
{
try
{
file.createNewFile();
}
catch(IOException e)
{
System.out.println("Die Datei konnte nicht erzeugt werden.");
return;
}
System.out.println("Datei " + file.getName() + " angelegt: " + file.exists());
}
if(file.isFile())
System.out.println(file.getName() + " ist eine Datei ");
else
System.out.println(file.getName() + " ist keine Datei ");
if(file.isDirectory())
System.out.println(file.getName() + " ist ein Verzeichnis ");
else
System.out.println(file.getName() + " ist kein Verzeichnis ");
System.out.println("Parent: " + file.getParent());
System.out.println("Parentdirectory: " + file.getParentFile());
System.out.println("Pfad (ohne Wurzel): " + file.getPath());
System.out.println("absoluter Pfad (mit Wurzel): " + file.getAbsolutePath());
System.out.println("Dateiattribute:");
System.out.println("lesen erlaubt: " + file.canRead());
System.out.println("schreiben erlaubt: " + file.canWrite());
System.out.println("versteckt: " + file.isHidden());
System.out.println(System.getProperty("user.dir"));
// HINWEIS:
// "user.dir" ist ein Key der eine System-Property beschreibt
// Alle Keys sind in der Klasse System bei der Methode getProperties gelistet.
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
32 / 210
23.03.2010
PROMOD-2a / SS10
4.4 Die Klasse RandomAccessFile:
Dateien mit wahlfreiem Zugriff
Es gibt eine aktuelle Schreib- und Leseposition, welche durch den Schreib-Lese-Zeiger (Seek-Pointer)
kennzeichnet (Siehe Bild an der Tafel.)
Eine RandomAccess-Datei ist vorstellbar als ein großes Byte-„Array“
Der Seek-Pointer kann innerhalb der Datei beliebig verschoben werden
Wenn eine RA-Datei eröffnet wird, muss sie bereits physisch existieren, ansonsten ergibt sich eine
FileNotFound-Exception
Wird eine RA-Datei zum Schreiben eröffnet, so wird sie als File angelegt, falls sie noch nicht existiert
Eine RA-Datei kann nicht geleert werden. Hierzu muss sie gelöscht und anschließend wieder angelegt werden
Methoden um den Seek-Pointer zu verstellen:
Jedes Lesen und Schreiben versetzt den Seek-Pointer um die Anzahl der gelesenen/geschriebenen Bytes (impliziter
Versatz in Richtung Dateiende)
void seek(long pos)
int skipBytes(int i)
long getFilePointer()
Setzt den Dateizeiger auf pos Bytes gerechnet vom Beginn der Datei
Positioniert den Dateizeiger relativ zur aktuellen Position in Richtung Dateiende.
Rückgabewert ist die Anzahl der übersprungenen Zeichen.
Liefert die aktuelle Position des seek-pointers
Die Klasse RandomAccessFile bietet für sämtliche primitiven Datentypen Methoden an, um entweder eine RA-Datei
zu beschreiben oder auszulesen. Es gibt aber noch weitere Write- und Readmethoden. Hierzu gibt die Java-Doku
nähere Informationen.
Beispiel:
import java.io.*;
public class RandomAccessFileTest
{
public static void main(String[] args)
{
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
33 / 210
23.03.2010
PROMOD-2a / SS10
}
try
{
RandomAccessFile file = new RandomAccessFile("zzz_randfile.txt", "rw");
file.writeBytes("AAAAA\r\nBBBBB\r\nCCCCC\r\n"); // writeBytes() schreibt ASCII
//file.writeChars("AAAAA\nBBBBB\nCCCCC\n"); // writeChars() schreibt UNICODE
file.setLength(12); // File wird "abgeschnitten" nach 12. Zeichen
file.seek(0);
System.out.println("das erste Mal lesen");
for(int i = 1; i <= 3; i++)
System.out.println(file.readLine());
file.writeBytes("DDDDD\nEEEEE\nFFFFF\n");
file.seek(0);
System.out.println("Das zweite Mal lesen");
for(int i = 1; i <= 10; i++)
System.out.println(file.readLine());
file.close();
}
catch(FileNotFoundException fnfex)
{
System.out.println(fnfex.getMessage());
}
catch(IOException ioex)
{
System.out.println(ioex.getMessage());
}
}
Aus der API-Beschreibung von class RandomAcessFile:
Method Summary
void close()
Closes this random access file stream and releases any system resources associated with
the stream.
FileChannel getChannel()
Returns the unique FileChannel object associated with this file.
FileDescriptor getFD()
Returns the opaque file descriptor object associated with this stream.
long getFilePointer()
Returns the current offset in this file.
long length()
Returns the length of this file.
int read()
Reads a byte of data from this file.
int read(byte[] b)
Reads up to b.length bytes of data from this file into an array of bytes.
int read(byte[] b, int off, int len)
Reads up to len bytes of data from this file into an array of bytes.
boolean readBoolean()
Reads a boolean from this file.
byte readByte()
Reads a signed eight-bit value from this file.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
34 / 210
23.03.2010
PROMOD-2a / SS10
char readChar()
Reads a Unicode character from this file.
double readDouble()
Reads a double from this file.
float readFloat()
Reads a float from this file.
void readFully(byte[] b)
Reads b.length bytes from this file into the byte array, starting at the current file pointer.
void readFully(byte[] b, int off, int len)
Reads exactly len bytes from this file into the byte array, starting at the current file pointer.
int readInt()
Reads a signed 32-bit integer from this file.
String readLine()
Reads the next line of text from this file.
long readLong()
Reads a signed 64-bit integer from this file.
short readShort()
Reads a signed 16-bit number from this file.
int readUnsignedByte()
Reads an unsigned eight-bit number from this file.
int readUnsignedShort()
Reads an unsigned 16-bit number from this file.
String readUTF()
Reads in a string from this file.
void seek(long pos)
Sets the file-pointer offset, measured from the beginning of this file, at which the next read
or write occurs.
void setLength(long newLength)
Sets the length of this file.
int skipBytes(int n)
Attempts to skip over n bytes of input discarding the skipped bytes.
void write(byte[] b)
Writes b.length bytes from the specified byte array to this file, starting at the current file
pointer.
void write(byte[] b, int off, int len)
Writes len bytes from the specified byte array starting at offset off to this file.
void write(int b)
Writes the specified byte to this file.
void writeBoolean(boolean v)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
35 / 210
23.03.2010
PROMOD-2a / SS10
Writes a boolean to the file as a one-byte value.
void writeByte(int v)
Writes a byte to the file as a one-byte value.
void writeBytes(String s)
Writes the string to the file as a sequence of bytes.
void writeChar(int v)
Writes a char to the file as a two-byte value, high byte first.
void writeChars(String s)
Writes a string to the file as a sequence of characters.
void writeDouble(double v)
Converts the double argument to a long using the doubleToLongBits method in class
Double, and then writes that long value to the file as an eight-byte quantity, high byte first.
void writeFloat(float v)
Converts the float argument to an int using the floatToIntBits method in class Float, and
then writes that int value to the file as a four-byte quantity, high byte first.
void writeInt(int v)
Writes an int to the file as four bytes, high byte first.
void writeLong(long v)
Writes a long to the file as eight bytes, high byte first.
void writeShort(int v)
Writes a short to the file as two bytes, high byte first.
void writeUTF(String str)
Writes a string to the file using UTF-8 encoding in a machine-independent manner.
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu
diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
36 / 210
23.03.2010
PROMOD-2a / SS10
Lecture
-selfstudy-
5. Streams: (ADRELI_CON)
Was Sie lernen:
für welche Zwecke die verschiedenen Stream-Klassen eingesetzt werden
was Byte-Streams (Binär-Streams) und Charakter-Streams (Zeichen-Streams)
unterscheidet
was gepufferte E/A bedeutet
Voraussetzungen:
Kenntnisse der Klasse File
Arbeiten mit dem Exceptionhandling
5.1 Grundlagen
Ein Datenstrom kann aus beliebiger Quelle stammen (engl. Source, Spring); siehe oben: Datei, Netz, …
Ein Datenstrom kann in beliebige Senke (engl. Sink) gehen: dies kann wiederum seine ein Datei, ein Socket,
eine Pipe, ein String.
Ein Datenstrom ist aber immer unidirektional, geht also immer in eine Richtung
Fehlerbehandlung
Bei fast allen Operationen (z.B. beim Anlegen einer Datei, beim Öffnen, Lesen, Schreiben, …) können Fehler
auftreten. Die Fehlerursachen hierfür können vielfältig sein: die Rechte für eine Operation fehlen, betroffene Datei
existiert nicht, usw.
Solche Fehler lösen eine Ausnahme vom Typ IOException aus. Aus diesem Grund müssen alle Operationen in
einem try-catch-Block eingeschlossen sein.
Tritt zum Beispiel beim Schreiben ein Fehler auf, so kann im catch-Block eine entsprechende Meldung
ausgegeben und die Datei geschlossen werden.
Gepufferte Ein- und Ausgabe
Aus Gründen er Effizienz sind Ein- und Ausgaben meißt gepuffert. Das bedeuted, dass die Daten z.B. beim
Lesen zunächst in einen Zwischenspeicher (Puffer) eingelesen und gesammelt werden. Von dort werden die
Daten dann an ihr Ziel weitergeleitet, entweder
o wenn der Puffer voll ist oder
o wenn eine Methode zum Leeren des Puffers (flush()) aufgerufen wird oder
o wenn das File geschlossen wird.
So werden größere Mengen an Zeichen transportiert, was wesentlich effizienter ist als der direkte
Einzelzeichenzugriff auf die vergleichsweise langsamen peripheren Ein-/Ausgabegeräte.
Auch die Ein-/Ausgabe über die Standard-Geräte ist gepuffert. Dies ist daran erkennbar, dass erst mit dem
Betätigen der RETURN-Taste die Daten gelesen werden. (Siehe oben, Kapitel „Standard-Ein-/Ausgabe.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
37 / 210
23.03.2010
PROMOD-2a / SS10
Die Hauptklassen von java.io:
Object
OutputStream
InputStream
Writer
Byte-Stream
Binäerdaten Lesen/Schreiben
Reader
File
RA-File
...
Character-Streams
Zeichendaten Lesen/Schreiben
ObjectStreamClass
StreamTokenizer
FileDescriptor
In Java werden generelle zwei Streamtypen unterschieden: Character Streams (zeichenartige Daten (Text)) und
Byte Streams (binäre Daten). Wie der Name schon andeutet, sind Byte Streams auf die Verarbeitung von
beliebigen Byte-artigen Informationseinheiten -- und damit acht Bit große Einheiten -- beschränkt. Damit lassen sich
ASCII-Daten verarbeiten, die historisch begründet, in sehr großer Anzahl vorliegen. Auch Java-Quellcode ist ASCIICode.
Mit dem JDK v1.1 zusätzlich das Konzept der Character Streams eingeführt, die generell 16 Bit lange UNICODEZeichen (Character) bereitstellen.
5.2 Überblick (UNICODE-) Character-Stream-Klassen (Zeichendaten
(Text) schreiben und Lesen)
CharacterStreams verwenden den 16-Bit-Unicode und sind daher in Java für die Arbeit mit Strings und
Zeichentypen besser geeignet als Byte-Streams.
Abstrakte Klassen
Writer
Bridge-/Brücken-Klassen
BufferedWriter
CharArrayWriter
OutputStreamWriter
FilterWriter
PipedWriter
StringWriter
PrintWriter
FileWriter
Reader
BufferedReader
CharArrayReader
LineNumberR.
InputStreamReader
FilterReader
FileReader
PushBackR.
PipedReader
StringReader
Writer-Ableitungen:
Klasse (Character-)
BufferedWriter
CharArrayWriter
OutputStreamWriter
FilterWriter
PipedWriter
StringWriter
PrintWriter
Prof. Illik
Was die Klasse leistet
Gepuffertes Schreiben. Zeilenvorschub wird transparent plattformabhängig umgesetzt
Zeichen werden in ein Char-Array geschrieben, das Array wächst automatisch
Brücke zwischen Character- und Byte-Strömen; Unicode-Zeichen werden
automatisch während des Schreibens in die entsprechende ASCII/Byte-Repräsentation
umgesetzt. Abstrakte Klasse (für FileWriter).
Abstrakte Basisklasse der verschiedenen Implementierungen zum gefilterten Schreiben
Ausgabestrom über Character-Pipe
Ausgabestrom, der in einen String schreibt
Ausgabestrom, der formatierte Objektrepräsentationen schreibt. Methoden dieser Klasse
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
38 / 210
23.03.2010
PROMOD-2a / SS10
verursachen keine E/A-Ausnahmeereignisse
HINWEIS: auf PrintWriter wird typ. mit den Methoden .print(), .println() geschrieben,
während auf die anderen Steam-Objekt typ. mit .write() geschreiben wird.
Reader-Ableitungen:
Klasse (Character-)
BufferedReader
CharArrayReader
InputStreamReader
FilterReader
PipedReader
StringReader
Was die Klasse leistet
Liest Textinhalte gepuffert aus einem Character-Eingabestrom
Einlesen von Unicode-Zeichen aus Character-Puffer
Brücke zwischen Character- und Byte-Strömen; ASCII/Byte-Zeichen werden
automatisch während des Lesens in die entsprechende Character-Repräsentation
umgesetzt.
Abstrakte Klasse (für FileReader).
Abstrakte Basisklasse verschiedener filternder Eingabeströme. Konkrete
Implementierungen werden durch die verschiedenen Subklassen zur Verfügung gestellt
Unicode-Character Eingabestrom als Lese-Ende einer Character-Pipe
Unicode-Character Eingabestrom, der aus einem String liest
5.3 Character–Stream-Methoden:
5.3.1
Ausgabe-Streams
Generelle Methoden von java.io.Writer:
void close()
void flush()
write(int)
write(char[])
write(char[] cbuf,
int off, int len)
write(String)
write(String str,
int off, int len)
5.3.2
Schließt Ausgabestrom und gibt durch ihn belegte Systemressourcen frei
Leert Ausgabestrom und schreibt alle Pufferbereiche
Schreibt ein UNICODE-Zeichen
Schreibt Charracter-Array
Schreibt Teil der Länge len eines Character-Arrays beginnend ab Position off
Schreibt String
Schreibt Teil der Länge len eines Strings beginnend ab Position off
Eingabe-Streams
Generelle Methoden von java.io.Reader:
void close()
void mark(int r)
boolean
markSupported()
int read()
int read(char[]
cbuf)
int read(char[]
cbuf, int off,
intlen)
boolean ready()
reset()
long skip(long n)
Prof. Illik
Schließt Ausgabestrom und gibt durch ihn belegte Systemressourcen frei
Markiert gegenwärtige Position des Eingabezeigers. Ein nachfolgender Aufruf von reset
versucht den Positionszeiger auf die Markierte Stelle zurückzusetzen, falls die aktuelle
Position nicht weiter als readAheadLimit Zeichen entfernt liegt
Gibt Auskunft darüber, ob der Eingabestrom die Positonsmarkierung, und das
Rücksetzen darauf, unterstützt
zur Extraktion genau eines Zeichens (16 Bit Character)
zur Extraktion einer Sequenz, beginnend ab der aktuellen Stromzeigerpositon
zur Extraktion einer Sequenz der Länge len oder weniger von Zeichen, und Ablage in
Array beginnend ab der Position off
Liefert dieser Aufruf true zurück, so liegen weitere Eingaben vor, die durch ein folgendes
read gelesen werden können
Versucht den Positionszeiger auf die durch die letzte gesetzte Markierung bezeichnete
Stelle zurückzurücken
Versucht n Zeichenpositionen zu überspringen. Die Anzahl der tatsächlich
übersprungenen Positionen wird zurückgeliefert
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
39 / 210
23.03.2010
PROMOD-2a / SS10
5.4 Beispiele/Anwendungen zu Character–Streams:
Beispiel 1: Anwendung Klassen FileWriter und FileReader
import java.io.*;
public class FileWriterTest
{
public static void main(String[] args)
{
String s = "Das ist eine Testausgabe\r\neines dreizeiligen Textes\r\n";
// Ausgabe in Datei
try
{
FileWriter fw = new FileWriter("zzz_testFileWriter.txt");
// System.out.println();
fw.write(s);
fw.close();
}
catch(IOException io)
{
System.out.println(io.getMessage());
}
// Einlesen und anzeigen
// String lese = "";
StringBuffer lese = new StringBuffer();
int x = 0;
}
try
{
FileReader fr = new FileReader("zzz_testFileWriter.txt");
while((x = fr.read()) != -1) // -1 meldet EOF
// lese += (char)x;
lese.append((char)x);
fr.close();
}
catch(IOException io)
{
System.out.println(io.getMessage());
}
System.out.println(lese);
}
Beispiel 2: Anwendung Klassen BufferedWriter und BufferedReader
import uebungen.util.*;
import java.io.*;
public class BufferedWriterTest
{
public static void main(String[] args)
{
char[] cf = {'0','0','0','0','0','0','0','0','0','0'};
try
{
BufferedWriter bw = new BufferedWriter(new FileWriter("zzz_test.txt"));
for(int i = 0; i < 750; i++)
{
cf[i%10]++;
bw.write(cf);
bw.newLine();
}
bw.close();
}
catch(IOException io)
{
System.out.println(io.getMessage());
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
40 / 210
23.03.2010
PROMOD-2a / SS10
}
String s;
BufferedReader br = null;
try
{
br = new BufferedReader(new FileReader("zzz_test.txt"));
try
{
while((s = br.readLine()) != null)
{
System.out.println(s);
}
}
finally
{
if(br != null)
br.close();
}
}
catch(IOException io)
{
System.out.println(io.getMessage());
}
}
Beispiel 3: Anwendung Klasse PrintWriter
import java.io.*;
public class PrintWriterTest
{
public static void main(String[] args)
{
try
{
// Ausgabe ins File
PrintWriter pw= new PrintWriter(new FileWriter("zzz_test.txt"), true);
// true = autoFlush
// Ausgabe auf Konsole
PrintWriter pw2 = new PrintWriter(System.out, true);
// true = automatic flushing
pw.println("Berechnung des Flächeninhalts für Kreise");
pw2.println("Berechnung des Flächeninhalts für Kreise");
for(int r = 1; r <= 100; r++)
{
pw.print("Radius "+r+": "); // auf PrintWriter-Objekte kann mit .print()
pw2.print("Radius "+r+": "); // und .println() geschrieben werden
pw.println(3.14 * r * r);
// während auf andere “Schreib-“Streams mit
pw2.println(3.14 * r * r);
// write()-Methoden geschrieben wird
}
pw.close();
pw2.close();
}
catch(IOException io)
{
System.out.println(io.getMessage());
}
}
}
Beispiel 4: Anwendung Klasse StringWriter und StringReader
import java.io.*;
public class StringReaderWriterTest
{
public static void main(String[] args)
{
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
41 / 210
23.03.2010
PROMOD-2a / SS10
String s = "Java macht Spass ";
// arbeiten mit StringWriter
System.out.println("Verwendung von StringWriter:");
String s1 = s;
StringWriter sw = new StringWriter();
int l = s1.length();
for(int i = 0; i < l; i++)
{
s1 = s1.substring(0, s1.length()-1);
sw.write(s1 + "\n");
}
System.out.println(sw.toString());
// arbeiten mit StringReader
System.out.println("Verwendung von StringReader:");
StringReader sr = new StringReader(s);
int z;
try
{
while((z = sr.read()) != -1)
System.out.println((char)z);
}
catch(IOException _uh)
{
}
}
}
Beispiel 5: Ausgabe auf Standard Out mit PrintLn
import java.io.FileDescriptor;
import java.io.FileWriter;
import java.io.IOException;
public class PrintLn
{
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter(FileDescriptor.out);
for (int i=0; i < args.length; i++)
fw.write (args[i]+" ");
} catch (IOException ioe) {
System.out.println("cannot open stdout!");
} finally {
try {
fw.close();
} catch (Exception e) {
//ignore it
} //catch
} //finally
} //main()
} //class PrintLn
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
42 / 210
23.03.2010
PROMOD-2a / SS10
5.5 Überblick Byte-Stream-Klassen (binäres Schreiben u. Lesen)
ByteStreams verwenden für die Ein- und Ausgabe von Datenströmen 1-Byte Größen.
Da externe Datensenkenund Datenquellen (Dateien) byteweise arbeiten, sind grundsätzlich Bytestreams nötig,
um in eine externe Datensenke zu schreiben und aus einer externen Datenquelle zu lesen.
Deshalb gibt es in den CharacterStream-Klasssen auch keine Sink- und Spring-Stream-Klassen, die auf einer
Datei als externe Datensenke/quelle arbeiten.
OutputStream-Ableitungen:
Klasse (Byte-)
FileOutputStream
PipedOutputStream
FilterOutputStream
ByteArrayOutputStream
ObjectOutputStream
…
Was die Klasse leistet
Schreibt Byte-artige Daten in Dateien oder durch Dateideskriptoren bezeichnete Ziele
Hochsprachliches Äquivallent der Betriebssystem-Pipes (schreibend)
Verleiht Ausgabestömen die Möglichkeit Ausgabedaten zu verändern. Einige konkrete
Implementierungen sind durch die Subklassen gegeben
Ausgabestrom, der in einen Byte-Array schreibt
Schreibt Primitivtypen und Objekte. Ausschließlich Objekte, welche die java.ioSerializable-Schnittstelle implementieren können über diesen Mechanismus persistent
geschrieben werden
…
InputStream-Ableitungen:
Klasse (Byte-)
FileInputStream
PipedInputStream
FilterInputStream
ByteArrayInputStream
StringBufferInputStream
SequenceInputStream
ObjectInputStream
…
Was die Klasse leistet
Byteweises lesen einer Datei
Hochsprachliches Äquivallent der Betriebssystem Pipes
Gemeinsam mit anderen Eingabeströmen eingesetzt. Einsatzgebiet:
Nachbearbeitung roher Eingaben. Die entsprechenden Subklassen stellen konkrete
Implementierungen häufiger Einsatzfälle dar
Lesen aus einem Byte Array
deprecated!! Lesen aus String. Achtung: Diese Klasse geht von der (i. A. falschen)
Entsprechung zwischen Bytes und Charactern aus. StringReader liefert für
denselben Anwendungsfalle eine korrekte Implementierung
Konkatenation von Eingebströmen; nach vollständigem Lesen des i-ten
Eingebstroms wird der i+1-te angesprochen
Lesen vollständig serialisierter Objekte, die die Schnittstellen java.io.Serializable
oder java.io.Externalizable unterstützten
…
5.6 Byte–Stream-Methoden:
Generelle Methoden von java.io.OutputStream
void close()
void flush()
void write(byte[]
b)
void write(byte[]
b,int off,int len)
void write(int)
Schließt Ausgabestrom und gibt durch ihn belegte Systemressourcen frei
Leert Ausgabestrom und schreibt alle Pufferbereiche
Schreibt Byte-Array in Ausgabestrom
Schreibt len Bytes eines Byte-Arrays ab Position off
Schreibt Bytewert in Ausgabestrom
Generelle Methoden von java.io.InputStream
int available()
Prof. Illik
Liefert die Anzahl Bytes die an der Eingabeschnittstelle zur Verfügung stehen. Diese
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
43 / 210
23.03.2010
PROMOD-2a / SS10
void close()
void mark(int)
boolean
markSupported()
int read()
int read(byte[])
int read(byte []
b,int off,int len)
void reset()
long skip(long n)
Anzahl kann ohne Blockierung des Aufrufers gelesen werden
Schließt Eingabestrom unter Freigabe der belegten Systemressourcen
Markiert die gegenwärtige Position des Eingabezeigers im Eingabestrom; ein folgender
Aufruf von reset setzt den Eingabezeiger wieder an die markierte Position zurück
Gibt Auskunft darüber, ob der Eingabestrom die Positonsmarkierung, und das
Rücksetzen darauf, unterstützt
Ließt den nächsten Bytewert (>0 und <256) aus dem Eingabestrom. Ist das Ende des
Eingabestromes erreicht, wird -1 retourniert (kein Ausnahmeereignis!
Ließt eine Bytesequenz aus dem Eingabestrom, und legt sie im übergebenen Array ab.
Die Anzahl der gelesenen Zeichen, bzw. -1 beim Erreichen den Eingabeendes, wird
zurückgegeben
Ließt Bytesequenz der Länge len und speichert sie ab Position off im übergebenen Array
Setzt den Stromzeiger auf die Position der letzten vorhergehenden Markierung zurück,
falls eine solche existiert
Versucht den Stromzeiger um n Bytepositionen vorzurücken. Die Anzahl der tatsächlich
übersprungenen Bytes wird zurückgegeben
Nähere/weitere Informationen zu den vorgestellten Klassen, wie etwa Konstruktoraufbau und
Methodenimplementationen lassen sich der aktuellen JAVA API-Dokumentation entnehmen.
Die hauptsächliche Verwendung von Streams liegt vor allem zur Erstellen von Protokolldaten, also Logfiles, der
Portierung von bspw. Datenbank-Export-Inhalten. Streams werden auch typischerweise überall dort verwendet,
wo die Datenarchivierung in Datenbanken keinen Sinn macht, weil bspw. die typischen DB-Operationen keine
Anwendung finden. Dies ist häufig im technischen Bereich der Fall: bspw. liegen Anweisungen für CNCMaschinen in Files vor.
Beispiel 6: Ein-/Ausgabe eine Objekts (Binäre Ein-/Ausgabe)
// Das Objekt bList wird in eine Datei geschrieben,
// dann wird das Objekt aus der Datei geholt und
// dem Objekt aList zugewiesen
import
import
import
import
import
import
import
java.io.FileInputStream;
java.io.FileOutputStream;
java.io.IOException;
java.io.ObjectInputStream;
java.io.ObjectOutputStream;
java.io.Serializable;
java.util.ArrayList;
public class ObjectStreams_2
// <<--
{
public static ArrayList<Person>aList= new ArrayList<Person>();
public static ArrayList<Person>bList = new ArrayList<Person>();
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
public static void main (String [] args){
Person pers_1= new Person("Alpha","Nadine");
Person pers_2=new Person("Beta","Nanu");
Person pers_3=new Person("Gamma","Natascha");
// Objekt aList wird gefüllt
aList.add(pers_3);
aList.add(pers_2);
aList.add(pers_1);
//Ausgeben auf Stream
//sichern();
sichern(aList);
// Das Objekt aList ausgeben in Datei
//Einlesen vom Stream / das File auslesen
auslesen();
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
44 / 210
23.03.2010
PROMOD-2a / SS10
//...und ausgeben auf die Konsole
// entweder
for (Person buffer:bList)
{
// Objektinhalt auf Console ausgeben
System.out.println(buffer.vorname);
System.out.println(buffer.name);
}
// ...oder nicht ganz so kurz...
/*
Ob_St buffer;
for (int i=0; i<3;i++)
// nicht so gut wie oben
{
buffer = bList.get(i);
System.out.println(buffer.vorname);
System.out.println(buffer.name);
}
*/
System.out.println("Ende Test...");
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//public static void sichern()
public static void sichern(ArrayList<Person> aList)
{
try{
FileOutputStream fos = new FileOutputStream("Datei.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(aList);
oos.close();
}
catch(IOException e){
}
// wir haben nur ein Objekt aList
// und schreiben dieses Objekt aur
// den Stream
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@SuppressWarnings("unchecked")//jai, nicht unbedingt notwendig;
// nur Warning wird unterdrückt
public static void auslesen()
{
try{
FileInputStream fis=new FileInputStream("Datei.dat"); //Binärfile
ObjectInputStream ois= new ObjectInputStream(fis);
try{
bList = (ArrayList<Person>)ois.readObject();
// cast von Object auf ArrayList<Ob_St> passt;
// links und rechts gleicher Typ
// Objekt wird geholt und bList zugewiesen
} catch(ClassNotFoundException e){
System.exit(1); // jai
}
ois.close();
} catch(IOException e){
}
} // end of auslesen()
} // end of class ObjectStreams
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class Person implements Serializable {
String name;
String vorname;
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
45 / 210
23.03.2010
PROMOD-2a / SS10
static final long serialVersionUID = 0L;
public Person(String name,String vorname)
{
this.name=name;
this.vorname=vorname;
}
} // end of class Person
// Nicht optimal ist die Verteilung der Methoden!(Quick&Dirty Testbett
// für Objekt-Ein-/Ausgabe.
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu
diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
46 / 210
23.03.2010
PROMOD-2a / SS10
6. Multi-Threading (ADRELI_THREADS++)
Was Sie lernen:
was Threads sind und wie sie aufgebaut sind
die Eigenschaften und Methoden der Klasse Thread
was unter Multi-Threading und Parallelität zu verstehen ist
wie Threads miteinander kommunizieren können
Voraussetzungen:
Arbeiten mit Streams
6.1 Grundlagen
Ein Prozess verfügt über einen eigenen Adressraum und beinhaltet ein in „Ausführung“ befindliches
Programm. Ein gutes OS garantiert (mit Unterstützung durch die Hardware) die strenge Trennung der
Prozessadressräume.
Ein Prozess kann aus mehreren „Teilprozessen“ bestehen, die als Threads bezeichnet werden.
Ein Thread ist also eine Untereinheit der OS-Verwaltungseinheit Prozeß (Task). Moderne OS sind in der Lage,
mehrere Programme gleichzeitig auszuführen (Multi-Tasking / Multi-Programming / Multi-Processing /
Concurrency/Concurrent Programming).
Sind mehrere Threads gleichzeitig aktiv, so spricht man von Multi-Threading.
Alle Threads arbeiten im selben Prozessadreßraum.
Jeder Thread besitzt einen eigenen Stack.
Jeder Thread bestitzt einen eigenen Programmzähler (PC, Program Counter).
Jeder Thread kann weitere Threads erzeugen.
Threads werden häufig auch als leichtgewichtige Prozesse bezeichnet.
Threads können leicht auf gemeinsame Objekte und Variablen zugreifen. Hierbei besteht aber die Gefahr von
inkonsistenten Objektzuständen. Als Lösung steht in Java die Synchronisation zur Verfügung. Allerdings können
auch vollkommen voneinander getrennte Threads vorkommen, die sich keine gemeinsamen Objekte teilen.
Anwendungsmöglichkeiten:
Threads werden hauptsächlich angewandt um Parallelität bzw. Verteilung von Abläufen zu realisieren:
o z.B. rechenintensive Algorithmen laufen in einem Hintergrund-Thread während die GUI in einem
anderen Thread (im Vordergrund) läuft.
o Z.B. mit einem Thread interagiert die Anwendung mit dem User („View“) mit einem anderen Thread
wird die Kommunikation mit der Datenbasis („Modell“) abgewickelt. Hauptthread = „Controller“
(„MVC“).
Konstruktive Vorteile bietet die Zerlegung einer Gesamtapplikation in einen/mehrere Server-Thread/s und in
einen/mehrere Client-Threads.
Anmerkungen:
Das Hauptprogramm läuft in einem Thread (Hauptthread, Steuerungsthread). Es wird erst beendet, wenn alle
darin enthaltenen Threads beendet sind. Ausnahmen bilden sog. "Daemons". Dies sind Hintergrundthreads, auf
die der Hauptthread nicht wartet. Wird der Hauptthread und alle User-Threads beendet, so werden dann auch
die Daemons beendet, weil nichts mehr existiert, was auf das Ergebnis eines Hintergrundthreads wartet.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
47 / 210
23.03.2010
PROMOD-2a / SS10
Wird vom eingesetzten Betriebssystem die Thread-Verwaltung nicht unterstützt (kein natives Threading), so
übernimmt die Virtual Machine die Threadverwaltung (Simulation): user-level-Threads mit Hilfe der POSIXThreadbibliothek. Dies ist i.a.R. bei Unix/Linux-Systemen der Fall.
Threads sind prioritätenbehaftet und einem Scheduling ausgesetzt. Das Schedulingverfahren ist abhängig
vom Betriebssystem und von der Thread-Implementierung (siehe unten „Thread-Implementierungen“)
Thread-Implementierungen im Java-Umfeld:
User-Level Threads:
o Werden innerhalb eines Prozesses durch Verwendung einer Thread-Bibliothek erzeugt.
o Vorteil: Erzeugung erfolgt ohne Intervention des Kernels -> höhere Performance.
o Nachteil: u.U. unfaires Scheduling (weil der Prozess eine Zeitscheibe zugeordnet bekommt,
unabhängig von der Anzahl der in ihm laufenden Threads). Wenn der Prozess über mehrere KernelLevel Treads verfügt, dann kann einem Prozess proportional zur Anzahl seiner Kernel-Level Threads
eine größere Zeitscheibe zugeordnet werden.
o Bei einem System-Call, der einen Prozeß blockiert, werden mit dem Prozeß alle User-Level-Threads
blockiert.
o Für User-Level Treads kann auf der Anwendungsebene ein eigener Scheduling-Algorithmus
implementiert werden (was bei Kernel-Level Threads nicht geht, da sie ausschliesslich vom Kernel
geschedult werden).
Kernel-Level Threads:
o Werden vom Kernel erzeugt (Kontetxt Switch) (siehe oben).
o Vorteil: der Kernel kennt den Thread. Thread wird beim Scheduling berücksichtigt (siehe oben).
o Vorteil: bei Mehrprozessorsystemen (Mehrkernsystemen) werden Kernel-Threads i.d.R. auf einzelne
Prozessoren (Prozessorkerne) verteilt.
Threads auf Hardwareebene:
o Vorteil: Kontext-Switch sehr schnell.
o Realisiert z.B. im Transputer mit Helios als Betriebssystem.
o Intel Hyperthreading-Technologie: mehrere Threads können parallel von einem Prozessor ausgeführt
werden.
ÎJava favorisiert "kooperatives Multithreading".
- d.h. der Programmierer gibt die CPU freiwillig ab.
- nicht geeignet bei Real-Time-Multithreading für kritische Anwendungsprozesse, die deterministisches
Scheduling verlangen
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
48 / 210
23.03.2010
PROMOD-2a / SS10
6.2 Klasse Thread und Interface Runnable
Threads werden in Java durch die Klasse Thread und das Interface Runnable implementiert.
In beiden Fällen wird der Thread-Body, also der parallel auszuführende Code, in Form der zu überlagernden
Methode run() zur Verfügung gestellt. Diese Methode muss in der Klasse, die als Thread laufen soll
überschrieben werden.
Jeder Thread hat eine Priorität, welche die Reihenfolge der Abarbeitung der Threads innerhalb eines Prozesses
beeinflusst. Siehe unten Kapitel „Prioritäten und Gruppen von Threads“
Beispiel 1: Erster Thread – alle berechnen Fakultäten
Eine Aufgabe wird mehrfach parallel ausgeführt:
public class AufrufErsterThread
{
public static void main(String[] args)
{
ErsterThread t1 = new ErsterThread(1, 12);
ErsterThread t2 = new ErsterThread(2, 10);
ErsterThread t3 = new ErsterThread(3, 7);
t1.start(); // asynchroner Start von run()!!
t2.run();
// synchroner Start
t3.start();
while (t1.isAlive() || t2.isAlive() || t3.isAlive())
{
System.out.println("Hauptprogramm ist aktiv");
}
}
}
class ErsterThread extends Thread
{
int thread_nr, a;
long fak = 1;
// Daten für Threads sichtbar
ErsterThread(int nummer, int fakultaet)
{
thread_nr = nummer;
a = fakultaet;
}
}
public synchronized void run()
{
for (int x = 1; x <= a; x++)
{
fak *= x;
System.out.println("Thread " + thread_nr + ":
" + x + "! = " + fak );
Thread.currentThread().yield();
// “unterbricht” den aktuellen Thread
}
// Siehe unten “Zustände von Threads”
}
// -> kooperatives Multithreading
Hinweis: Wenn Objekte instanziiert werden, dann sind die Instanzvariablen jeweils objektspezifisch –
nicht aber der Code! Der Code für die Methode run() ist zunächst nur einmal existent/im Hauptspeicher:
Wird die Methode run() traditionell gestartet, so reicht dieses einmalige Vorhandensein für die sequentielle
Abarbeitung der Methoden im Hauptthread.
Dagegen sorgt der Start mit start() für das Einrichten eines eigenen Threads mit jeweils eigenem Programm
Counter PC und eigenem Stack.
Das Schlüsselwort „synchronized“ sorgt dafür, dass nur jeweils ein Thread durch seinen Code läuft
und auf die gemeinsamen Daten zugreift. Siehe auch Kapitel „Synchronisation“ weiter unten.
Nach dem Start der Threads mit der Methode start() laufen die Threads asynchron.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
49 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel 2 – ThreadDemo:
Threads erledigen unterschiedliche Aufgaben:
class MyThread1 extends Thread
{
public void run()
{
int i = 0;
while (i <= 50)
{
System.out.println(i++);
}
}
}
class MyThread2 extends Thread
{
public void run()
{
int i = 0;
while (i <= 50)
{
System.out.println("zweiter Thread");
i++;
}
}
}
public class ThreadDemo
{
public static void main(String[] args)
{
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.start();
t2.start();
}
}
Beispiel 3 – Thread mit sleep():
Hinweis: Experimentieren Sie mit unterschiedlichen sleep()-Zeiten.
class MyThread1_ extends Thread
{
public void run()
{
int i = 0;
try
{
while (i <= 50)
{
System.out.println(i++);
this.sleep(100);
// kooperatives Multithreading
}
}
catch (InterruptedException ie)
{
System.out.println(ie.getMessage());
}
}
}
class MyThread2_ extends Thread
{
public void run()
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
50 / 210
23.03.2010
PROMOD-2a / SS10
{
int i = 0;
try
{
while (i <= 50)
{
System.out.println("zweiter Thread");
this.sleep(200);
i++;
}
}
catch (InterruptedException ie)
{
System.out.println(ie.getMessage());
}
}
}
public class ThreadSleep
{
public static void main(String[] args) throws InterruptedException
{
MyThread1_ t1 = new MyThread1_();
MyThread2_ t2 = new MyThread2_();
t1.start();
t2.start();
}
}
Beispiel 4 – Runnable:
Es gibt noch eine weitere Möglichkeit Threads zu erschaffen, wie wir bereits wissen. Anstatt von der Klasse
"Thread" zu erben kann man auch das Interface Runnable implementieren.
Dies ist immer dann sinnvoll (und notwendig), wenn eine Klasse bereits von einer anderen erbt - und somit nicht
mehr von der Klasse Thread erben kann (Java unterstützt ja keine Mehrfachvererbung). Das Interface Runnable
enthält nur eine einzige Methoden-Deklaration, nämlich die der Methode run().
// class Bzzz extends Parent implements Runnable // Bsp. wäre realistischer,
// aber länger
class Bzzz implements Runnable
{
public void run()
{
int i = 0;
while (i<100)
{
System.out.println(i++);
}
}
}
public class ThreadRunnable
{
public static void main(String args[])
{
Bzzz b = new Bzzz();
// 1. Schritt: Objekt einer Klasse, die Runnable
// implementiert
Thread t = new Thread(b);// 2. Schritt: Objekt der o.g. Klasse wird dem
// Konstruktor der Klasse Thread als Parameter
// übergeben. („“Copy Initializer“)
t.start();
// 3. Schritt: Starten des Threads
while(t.isAlive()){}
// Hauptthread ist “busy waiting”
System.out.println("Thread ist beendet");
System.out.println("Bitte warten Sie noch 8 Sekunden...");
try { Thread.sleep(8000); }
catch (InterruptedException e){}
System.out.println("Danke...");
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
51 / 210
23.03.2010
PROMOD-2a / SS10
}
Anmerkung zum Threading/zur Parallelisierung: Weiter oben haben wir schon gesehen, wenn Threads durch
die Methode start() aktiviert werden, dann haben wir es zunächst mit einer asynchronen Parallelität zu
tun. Das heißt, die Threads laufen parallel zueinander ab, ohne dass sie per se voneinander abhängig sind –
soweit so gut. Dies bedeuted konkret aber auch, dass selbst inhaltlich gleichartige Threads sich u. U. in ihrem
zeitlichen Ablaufverhalten unterscheiden. Warum?
Antwort: Bedenken Sie, das in multithreadingfähigen Umgebungen, wie z.B. den Betriebssystem Windows
(XP, Vista oder Windows 7) oder Linux, der Gesamtmaschinenzustand sich während des Ablaufs eines
Thread beträchtlich unterscheiden kann vom Zustand während des Ablaufs eines anderen Threads. Auf
meinem persönlichen Laptop laufen beispielsweise über 100 Prozesse mit über 1000 Threads (siehe
„ProcessExplorer“ -> „View“ -> „System Information“; oder WindowsTaskmanager -> Prozesse -> Ansicht ->
Treads). Je nachdem, was diese konkurrierenden Prozesse und Threads im Augenblick machen, beeinflusst
dies auch das Zeitverhalten der Threads aus den Beispielprogrammen.
Asynchron/Synchron: In vielen (Anwendungs-)Fällen ist die asynchrone Prallelität ausreichend: Threads
werden gestarted (mit der Methode start()) und tun dann ihre Arbeit, bis sie terminieren oder zum
terminieren aufgefordert werden (z.B. mit der Methode interrupt()). In anderen Anwendungfällen reicht
diese asynchrone Parallelität nicht aus: die Threads müssen in einer aufeinander abgestimmten Art und Weise
zusammenarbeiten. Dazu gehört, dass sie sich zeitlich oder über Ereignisse oder auch über einen
Datenaustausch miteinander „synchronisieren“.
Die „zeitgesteuerte Synchronisation“ kann z.B. mit der static-void-Methode sleep() bewerkstelligt
werden.
Die „ereignisgesteuerte Synchronisation“ kann z.B. über die Methoden void wait(), void
notify(), join(), static void yield(), void interrupt() erfolgen
Die „kommunikationsgesteuerte Synchronisation“ kann z.B. über einen Datenaustausch via Pipes und
Sockets erfolgen. Pipes sind unter Umständen selbstsynchronisierend, was sich auch synchronisierend
auf ihre Nutzer auswirkt. Mehr dazu siehe Kapitel „Datenaustausch zwischen Threads mit Pipes“: (siehe PipeAPI-Doku) “Whether or not a thread writing bytes to a pipe will block until another thread reads those bytes, or
some previously-written bytes, from the pipe is system-dependent and therefore unspecified. Many pipe
implementations will buffer up to a certain number of bytes between the sink and source channels, but such
buffering should not be assumed.”
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
52 / 210
23.03.2010
PROMOD-2a / SS10
6.3 Zustände von Threads:
Wie Prozesse haben auch Threads verschiedene Zustände, die sie einnehmen.
Diese Zustände können durch Methoden der Klasse Thread hervorgerufen, bzw. geändert werden. Das folgende
Bild gibt einen Überblick:
Prozessorzuteilung durch
Scheduling
Ende von run() Oder:
freiwilliges Beenden,
wenn Interrupted-Flag
gesetzt.
start()
new
ready to
run
active
running
end
yield()
ERZEUGEN
BEENDEN
-
notify()
notifyAll()
blocked
Ressourcen fehlen (i/o)
sleep()
wait()
join()
LAUFEN (isAlive() = true)
6.3.1
Threads unterbrechen
Why are Thread.stop(), Thread.suspend() and Thread.resume() deprecated? (Siehe API-Doku für Klassse
Thread)
Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked. (The
monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any of the objects previously
protected by these monitors were in an inconsistent state, other threads may now view these objects in an
inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary
behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other
unchecked exceptions, ThreadDeath kills threads silently; thus, the user has no warning that his program may be
corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days
in the future.
What should I use instead of Thread.stop()?
Most uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread
should stop running. The target thread should check this variable regularly, and return from its run method in an
orderly fashion if the variable indicates that it is to stop running. (This is the approach that JavaSoft's Tutorial has
always recommended.) To ensure prompt communication of the stop-request, the variable must be volatile (or
access to the variable must be synchronized).
Dies bedeutet, dass ein Thread nur mit der unten vorgestellten Methode interrupt() zur Beendigung aufgefordert
werden kann. Die Methode interrupt() setzt eine Interrupted-Flag. In der Methode run() wird am besten zyklisch
(in einer Schleife) abgefragt, ob das Interrupted Flag gesetzt ist und damit der Thread terminieren soll. (Passt zur
Philosopie „kooperatives Multithreading“.)
6.3.2
Die Methoden yield(), sleep() und interrupt()
HINWEIS: Werden Ihre Threads nicht quasi-parallel abgearbeitet, sondern nacheinander, so basiert Ihre JavaImplementierung möglicherweise nicht auf einem Time-Slice-Scheduler (Zeitscheibenverfahren). Dieses Problem
kann durch kooperatives Multitaskting gelöst werden: Threads müssen angewiesen werden, ihre Ausführung nach
einer gewissen Zeit zu unterbrechen („sich kooperativ zu verhalten“). Dazu muss in einer Schleife eine yield() oder
sleep()-Anweisung eingebaut werden. (Siehe hierzu auch das sleep()-Beispiel weiter oben.) sleep() und yield() sind
static-Funktionen.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
53 / 210
23.03.2010
PROMOD-2a / SS10
static void yield()
Der Thread wird zeitweilig unterbrochen (in den Zustand „ready-to-run“ versetzt bis ihn der Scheduler wieder
aktiviert). Ein anderer Thread bekommt Prozessorzeit zugeteilt.(Siehe Beispiel 1 – Erster Thread)
public static void sleep(long millis) throws InterruptedException
public static void sleep(long millis, int nanos) throws InterruptedException
Der Thread wird für die angegebene Zeit unterbrochen.
Während der Ausführung der sleep()-Methode kann eine Exception vom Typ InterruptedException auftreten,
die in einem try-catch-Block abgefangen werden muss.
try
{
sleep(millis);
}
catch(InterruptedException ie)
Auch die main()-Methode kann sleep() verwenden, um die Programmausführung zu unterbrechen. Dazu ist es
nötig, dass die Klasse der main()-Methode von Tread erbt (Implementierung vom Interface Runnable reicht
nicht).
public void interrupt()
Setzt das interrupted-Flag eines Threads und fordert damit den Thread auf sich zu beenden. (Versetzte sich ein
Thread durch die Methoden sleep(), join() und wait() in den Zustand „blocked“, so kann dieser Thread mit
der Methode interupt() vorzeitig wieder geweckt werden; er wird dann in den Zustand „ready-to-run“
versetzt. Damit werden also die blockierenden Methoden wie join() und sleep() beendet und die Methode
run() macht mit dem catch-Konstrukt InterruptedException hinter der blockierenden Methode weiter.)
public static boolean isInterrupted()
Liefert den Wert des interrupted-Flags (verändert dieses nicht, im Gegensatz zur Methode interrupted())
public static boolean interrupted()
Tests whether the current thread has been interrupted. The interrupted status of the thread is cleared by this
method. In other words, if this method were to be called twice in succession, the second call would return false
(unless the current thread were interrupted again, after the first call had cleared its interrupted status and before
the second call had examined it)
6.3.3
Die Methode join()
public final void join() throws InterruptedException
public final void join(long millis) throws InterruptedException
public final void join(long millis, int nanos) throws InterruptedException
Die parameterlose join()-Methode wartet bis der angegebene Thread beendet ist. Ein Thread kann die
Methode join() eines anderen Threads aufrufen. Dadurch wird der Thread, der die Methode aufruft, in den
Zustand „blocked“ versetzt, bis der Thread, dessen join()-Methode aufgerufen wird, beendet ist.
o Wird die Methode join() eines bereits beendeten Threads aufgerufen, so wird der aufrufende
Thread nicht in den Zustand „blocked“ versetzt.
Werden ein oder zwei Parameter angegeben, so wartet der aufrufende Thread höchstens die angegebene Zeit,
bis er die Abarbeitung fortsetzt.
Die Methode join() muss gegen eine Exception vom Typ InterruptedException abgesichert werden. (->
Aufruf von interrupt() für den in join() wartenden Thread.)
6.3.4
Weitere Thread-Methode (ggf. geerbt von Object)
void wait()
void wait(long millis, int nanos)
Bewirkt, dass ein Thread wartet, bis entweder eine Benachrichtigung (notify()) eintrifft oder die angegebene
Zeit abgelaufen ist, oder die Methode interrupt() für den Thread aufgerufen wird. HINWEIS: Diese Methode
lässt sich nur aus einer synchronisierten Methode heraus aufrufen.
Die Methode löst eine IllegalMonitorStateException aus, wenn der aktuelle Thread nicht der Besitzer der
Objektsperre ist.
wait() ist von der Klasse Object geerbt.
void notify()
(See: Case-Study threads_sync/synchronisation.java)
Aktiviert einen Thread, welcher duch den Aufruf von wait() in den Wartezustand versetzt wurde.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
54 / 210
23.03.2010
PROMOD-2a / SS10
static Thread currentThread()
Gibt eine Referenz auf den aktuell ausgeführten Thread zurück.
void destroy()
Zerstört den Thread ohne Aufräumarbeiten durchzurühren („Any monitors it has locked remain locked.“).
string getName()
Gibt den Namen des Threads zurück.
void setName(String name)
Umbenennung eines Threads: verändert den Thread-Namen in den als Parameter übergebenen Namen.
boolean isAlive()
Gibt true zurück, wenn der Thread läuft.
6.3.5
Thread-Konstruktoren
zu Details siehe API-Beschreibung “Thread“.
Constructor Summary
Thread()
Allocates a new Thread object.
Thread(Runnable target)
hiervon wird die run()-Methode aufgerufen
Allocates a new Thread object.
Thread(Runnable target, String name)
Allocates a new Thread object.
Thread(String name)
Allocates a new Thread object.
Thread(ThreadGroup group, Runnable target)
Allocates a new Thread object.
Thread(ThreadGroup group, Runnable target, String name)
Allocates a new Thread object so that it has target as its run object, has the specified name
as its name, and belongs to the thread group referred to by group.
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
Allocates a new Thread object so that it has target as its run object, has the specified name
as its name, belongs to the thread group referred to by group, and has the specified stack size.
Thread(ThreadGroup group, String name)
Allocates a new Thread object.
Beispiel 5 – TestTimer:
Case-Study-Aufgabe: Finden Sie raus, was Beispiel 5 macht! Siehe hierzu auch
case_study_06.txt.
public class TestTimer implements TimerListener
{
private Timer t1;
private Timer t2;
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
55 / 210
23.03.2010
PROMOD-2a / SS10
int i = 1;
public static void main(String[] args) // ----------------------------------{
TestTimer tt = new TestTimer(100);
}
public TestTimer(int t) // -------------------------------------------------{
t1 = new Timer(1, this);
t2 = new Timer(2, this);
t1.setDelay(t);
t2.setDelay(t);
// t1.setDaemon(true); // Siehe unten Kapitel Daemon-Threads
// t2.setDaemon(true);
t1.start();
t2.start();
System.out.println("2 Timer-Threads gestartet");
}
public void timeBreak(Timer t) // -----------------------------------------{
if (i <= 20)
{
System.out.println("Timer-Thread "+t.getTimerNr()+" TimeBreak
"+i++);
}
else
{
System.out.println("Timer-Thread "+t.getTimerNr()+" beendet");
t.stopThread();
}
}
}
public class Timer extends Thread
{
TimerListener tlistener;
int millis;
int nr;
boolean laeuft = true;
Timer(int n, TimerListener tl) // --------------------------------------------{
tlistener = tl;
nr = n;
}
public void run() // ---------------------------------------------------------{
while(laeuft)
{
try
{
sleep(millis);
tlistener.timeBreak(this);
}
catch(InterruptedException ie)
{
}
}
}
public void setDelay(int m) // -----------------------------------------------{
millis = m;
}
public int getTimerNr() // ----------------------------------------------------
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
56 / 210
23.03.2010
PROMOD-2a / SS10
{
return nr;
}
public boolean stopThread() // -----------------------------------------------{
return laeuft = false;
}
}
interface TimerListener
{
public void timeBreak(Timer t);
}
6.4 Daemon-Threads
Es gibt zwei Arten von Threads: nämlich User-Threads und Daemon-Threads (typw. „Dienstleister“). Der
Unterschied zwischen den beiden Thread-Arten besteht darin, dass
Daemen-Threads im „Hintergrund“ laufen (d.h. u.a. keine Verbindung zur Standard-E/A haben),
sich der Programmierer nicht darum kümmern muss, ob ein Daemon-Thread ordnungsgemäß beendet wird, da
Daemon-Threads automatisch gestoppt werden, wenn alle anderen User-Threads eines Prozesses
beendet sind. (Weil dann keine Clients mehr existieren, welche die Dienstleistungen der Daemons in Anspruch
nehmen könnten. VM wird runtergefahren.)
Standardmäßig laufen alle erzeugten Threads als User-Threads.
Aus einem User-Thread wird ein Daemon-Thread durch den Aufruf der Methode setDaemon(true). Dieser
Aufruf muss VOR dem Aufruf der start()-Methode für den entsprechenden Thread stehen.
void setDaemon(boolean on)
Kennzeichnet einen Thread als einen User-Thread (on == false) oder als einen Daemon-Thread (on ==
true).
boolean isDaemon()
Gibt true zurücke, wenn aufrufender Thread ein Daemon ist; andernfalls false.
Beispiel – siehe oben Beispiel 5 TestTimer
Lassen Sie im Beispiel 5 die beiden Timer-Threads als Deamons laufen. Was beobachten Sie?
6.5 Prioritäten von Threads:
Jeder Java-Thread erbt seine Priorität von seinem übergeordneten Thread.
Die Klasse Thread hat die Standardpriorität 5, die in der statischen Variable NORM_PRIORITY festgelegt ist.
Die höchstmögliche Priorität ist MAX_PRIORITY: 10.
Die niedrigste Priorität ist MIN_PRIORITY: 1.
Die Zuteilung von Rechenzeit orientiert sich an Prioritäten. Der Thread mit der momentan höchsten Priorität
läuft solange:
- bis die Methode run() beendet ist
- bis die Methode yield() (freiwillige Prozessoraufgabe) gerufen wird
- bis ein Thread höherer Priorität gestartet wird
- weiteres ist unbestimmt (insbesondere keine Aussage über eine Preemption…) (siehe auch „ThreadImplementierung im Java-Umfeld“ weiter oben“)
Î Thread-Prioritäten sind in hohem Maße systemabhängig. Ein OS kann mehr oder weniger Prioritätsebenen
haben. Die VM regelt, wie die Java-Prioritäten auf OS-Prioritätsebenen abgebildet werden. (WIE??)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
57 / 210
23.03.2010
PROMOD-2a / SS10
void setPriority(int newPriority)
Ändert die Priorität auf den übergebenen Wert.
int getPriority()
Gibt die Priorität des Threads zurück.
Wegen der oben erwähnten Systemabhängigkeit ist die Vergabe von Prioritäten mit Vorsicht anzuwenden. Soll die
Abarbeitung von Threads exakt beeinflusst werden, ist es i.d.R. notwendig, einen eigenen Thread-Handler zu
programmieren (für User-Level-Threads).
6.6 Gruppen von Threads
Die Klasse ThreadGroup gestattet es Threads mit ähnlichen, zusammengehörenden Aufgaben wahlfrei zu
gruppieren und zu strukturieren. Damit lassen sich threadspezifische Operationen gesammelt auf einer Gruppe von
Threads ausführen.
Bei der Erzeugung von Thread-Gruppen wird wie folgt vorgegangen:
Erzeugen Sie ein Objekt der Klasse ThreadGroup.
Verwenden Sie für die Erzeugung von Thread-Objekten einen Konstruktor, bei dem Sie als ersten Parameter die
Thread-Gruppe übergeben, z.B.:
o Thread(ThreadGroup group, String name) oder
o Thread(ThreadGroup group, Runnable target)
Über die Methoden der Klasse ThreadGroup können Sie nun alle Threads der Gruppe erreichen, z.B.
unterbricht (genau genommen Unterbrechungsaufforderung) die folgende Anweisung alle Threads der Gruppe
group: group.interrupt();
Beispiel – siehe unten Beispiel Thread-Kommunikation via Pipe
6.7 Synchronisation:
Führen mehrere Threads Änderungen auf gemeinsamen Daten (Nutzung gemeinsamer Ressourcen) durch, so
müssen sie synchronisiert werden, denn andernfalls können undefinierte Ergebnisse entstehen.
Beispiel – mehrere Threads ohne Synchronisation:
public class NoSynchronisation extends Thread
{
static int cnt = 0;
// für die Threads sichtbar
public void run()
{
while(cnt<100)
{
int i = cnt; // Kritischer Abschnitt Beginn
try{ sleep(10);} // Simuliert zeitaufwendige Berechnungen
catch(InterruptedException e){}
i++;
cnt=i; // Kritischer Abschnitt Ende
System.out.println(i);
}
}
public static void main(String[] args)
{
Thread t1 = new NoSynchronisation();
Thread t2 = new NoSynchronisation();
t1.start();
t2.start();
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
58 / 210
23.03.2010
PROMOD-2a / SS10
Die Ausgabe dieses Beispiels sollte eigentlich die Zahlen 1 bis 100 ausgeben, doch tatsächlich werden die Zahlen
teils doppelt und teils auch in falscher Reihenfolge ausgegeben. Dies kommt dadurch zustande, weil beide
Threads auf die static-Variable cnt zugreifen.
Hinweis: Möglicherweise entdecken Sie auf Ihrem System keine der Anomalien. An welcher „Schraube“ in der
run()-Methode müssen Sie drehen, um die Anomalie sichtbar zu machen?
Lösung:
Zur Synchronisation von Threads hat Java das Konzept des Monitors implementiert. Ein Monitor ist die
Kapselung eines kritischen Bereichs (also eines Programmteils, der nur jeweils ausschliesslich von einem
Thread durchlaufen werden darf) mit Hilfe einer automatisch verwalteten Sperre. Diese Sperre wird beim
Betreten des Monitors gesetzt und beim Verlassen wieder zurückgenommen. Ist sie beim Eintritt in den Monitor
bereits von einem anderen Thread gesetzt, so muß der aktuelle Thread warten, bis der Konkurrent die Sperre
freigegeben und den Monitor verlassen hat.
Das Monitor-Konzept wird mit Hilfe des in die Sprache integrierten Schlüsselworts synchronized realisiert.
Durch synchronized kann entweder eine komplette Methode oder ein Block ({}) innerhalb einer Methode
geschützt werden.
Beispiel – Synchronisation des Zugriffs auf gemeinsame Ressourcen:
In der unten gezeigten Lager-Klasse sind die Attribute bestand, zugang und abgang kritisch. Um ein Lagerobjekt
konsistent zu halten, darf der Zugang zu diesen Daten (z.B. für eine weitere Write-/Read-Transaktion) erst dann
freigegeben werden, wenn der komplette Datensatz (bestand, zugang und abgang) insgesamt aktualisiert wurde.
public class Lager
{
private int artikelnr;
private int bestand = 0;
private int zugang = 0;
private int abgang = 0;
private int aendeungsnr = 0;
public Lager(int anr)
{
artikelnr = anr;
}
public int getBestand()
{
return bestand;
}
public int getZugang()
{
return zugang;
}
public int getAbgang()
{
return abgang;
}
public int getAendeungsNr()
{
return aendeungsnr;
}
public synchronized void aenderungBestand(int x)
{
bestand+=x;
aendeungsnr++;
if (x > 0)
zugang += x;
else
abgang -= x;
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
59 / 210
23.03.2010
PROMOD-2a / SS10
public int getNr()
{
return artikelnr;
}
}
Übung: Siehe Übung zu diesem Kapitel; dort gilt es dieses Beipiel lauffähig zu machen.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
60 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Synchronisation zusätzlich mit wait() und notify():
import java.util.Vector1;
class Cnt
{
static Vector cnt = new Vector();
}
class Thread1 extends Thread
{
private int i=0;
public void run()
{
while (true)
{
synchronized(Cnt.cnt)
{
Cnt.cnt.set(0,new Integer(i++));
try{Cnt.cnt.notify();}
catch(IllegalMonitorStateException e){}
}
try {sleep(100);} catch(InterruptedException e) {}
}
}
}
class Thread2 extends Thread
{
public void run()
{
while (true)
{
synchronized(Cnt.cnt)
{
try{Cnt.cnt.wait();}
catch (InterruptedException e) {}
}
System.out.println(Cnt.cnt.get(0));
}
try{sleep(50);} catch (InterruptedException e) {}
}
}
public class Synchronisation
{
public static void main(String[] args)
{
Cnt.cnt.add(new Integer(0));
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t2.start();
t1.start();
}
}
1
Zur Erinnerung an „Algorithmen & Datenstrukturen“ / Sie sollten folgende Collection-Typen kennen:
Listen -> Vector, Stack, ArrayList, LinkedList
Sets -> Collections ohne Duplikate: HashSet, TreeSet,
Maps -> Schlüssel-Value-Paare: HashMap, TreeMap,
Die Klasse Vector und ArrayList sind praktisch identisch bis auf den Unterschied, dass die Methoden der Klasse
Vector synchronisiert sind. Siehe API-Doku!
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
61 / 210
23.03.2010
PROMOD-2a / SS10
Hands On: Untersuchen und erläutern Sie das Beispiel „Synchronisation2“ im Hands-OnTeil.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
62 / 210
23.03.2010
PROMOD-2a / SS10
6.8 Datenaustausch zwischen Threads mit Pipes:
gemeinsamer Adressraum (gemeinsame Objekte, Datenfelder)
Für die Realisierung von Pipes gibt es jeweils eigene Stream-Klassen für die byte- und character-orientierte
Kommunikation
Pipes sind stets Einweg-Kanäle:
o unidirektionales Kommunikationsmittel
Peer to Peer
“read side”
“write side”
Client-Server
Selbstsynchronisierend je nach Basis-OS!
ƒ haben bestimmte Kapazität (häufig Vielfaches von 2KB, OS-abhängig)
ƒ Produzent wird angehalten, wenn die Pipe voll ist
ƒ Konsument wird angehalten, wenn die Pipe leer ist
o FIFO-Prinzip
o keine Seek-Operation
- Wird ein Thread beendet, welcher liest und/oder schreibt, so erhält der Partnerthread die Exception "broken pipe"
- Muss durch IOException abgesichert werden
o
- Siehe: Pipe-API-Doku: Whether or not a thread writing bytes to a pipe will block until another thread reads those bytes, or some
previously-written bytes, from the pipe is system-dependent and therefore unspecified. Many pipe implementations will buffer up to
a certain number of bytes between the sink and source channels, but such buffering should not be assumed.
Zwei Klassen im Package java.io:
Eingabestrom
PipedInputStream
PipedReader
Ausgabestrom
PipedOutputStream
PipedWriter
Stream-Typ
ByteStream (binäre Kommunikation)
CharacterStream (UNICODE; TextKommunikation)
In die Pipes, bzw von den Pipes warden jeweils Einzelbyte/Einzelcharacter oder positionierte und
dimensionierte Pufferinhalte geschrieben oder gelesen. Siehe z.B. API-Doku PipedWriter:
void write(char[] cbuf, int off, int len)
Writes len characters from the specified character array starting at offset off to this
piped output stream.
void write(int c)
Writes the specified char to the piped output stream.
Die Erbschaft: (Siehe auch oben Kapitel “Streams”)
Object
OutputStream
InputStream
PipedOutputStream
Reader
PipedWriter
PipedInputStream
Prof. Illik
Writer
PipedReader
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
63 / 210
23.03.2010
PROMOD-2a / SS10
Das Methoden-Angebot: Das Methoden-Angebot für die Pipes ist vergleichbar mit den Methoden in den
Basisklassen:
Für die lesenden Klassen: available(), close(), connect(), read(), receive(), ready()
Für die schreibenden Klassen: close(), connect(), flush(), write()
Details siehe API-Dokumentation.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
64 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Thread-Kommunikation via Pipe:
import java.io.*;
public class TestPipe
{
public static void main(String[] args)
{
try
{ // 1. Step: Prepare infrastructur to communicate
PipedReader pr1 = new PipedReader(); // Character-Streams, UNICODE
PipedWriter pw1 = new PipedWriter();
pw1.connect(pr1); // verbindet PipedWriter und PipedReader
// 2. Step Instantiate Communicators and start() them
ThreadGroup tg = new ThreadGroup("TG");
Produzent produzent = new Produzent (tg, pw1); // Schreibseite als Para
Konsument verbraucher = new Konsument (tg,pr1); // Leseseite als Para
produzent.start();
verbraucher.start();
Thread.sleep(20000);
tg.interrupt();
// Operation auf der Thread-Gruppe
}
catch(InterruptedException ie){}
catch(IOException io)
{
System.out.println("Fehler beim Verbinden der Pipe");
}
}
}
class Produzent extends Thread
{
PipedWriter pw;
int x = 0;
public Produzent (ThreadGroup group, PipedWriter pw)
{
super(group, "Produzent");
this.pw = pw;
}
}
public void run()
{
while(isInterrupted() == false)
{
// int x = (int)(100 * Math.random());
x++;
try
{
pw.write(x);
System.out.println(this.getName() + "\t" + "produziert: " + x);
}
catch(IOException io)
{
System.out.println("Produzent: Fehler beim Schreiben");
}
}
}
class Konsument extends Thread
{
PipedReader pr;
public Konsument (ThreadGroup group, PipedReader pr)
{
super(group, "Konsument");
this.pr = pr;
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
65 / 210
23.03.2010
PROMOD-2a / SS10
}
public void run()
{
while(isInterrupted() == false)
{
try
{
int x = pr.read(); // liefert -1 (= end-of-stream), wenn Produent Pipe closed.
System.out.println(this.getName() + "\t" + "entnimmt: " + x);
}
catch(IOException io)
{
System.out.println("Konsument: Fehler beim Lesen");
}
}
}
}
Hands On:
•
Untersuchen Sie, was passiert, wenn der Produzent kontinuierlich auf die Pipe schreibt,
ohne dass ein Konsument aktiv ist (bzw. sich schon beendet hat).
•
Untersuchen Sie, was passiert, wenn der Produzent kontinuierlich „rasch“ auf die Pipe
schreibt, aber der Konsument nur gelegentlich/“langsam“ von der Pipe liest.
•
Untersuchen Sie, was passiert, wenn der Konsument von einer Pipe lesen möchte, ohne
dass ein Produzent aktiv ist (oder sich schon beendet hat).
•
Untersuchen Sie, was passiert, wenn der Konsument kontinuierlich/„schnell“ von der Pipe
liest, der Produzent aber nur „gelegentlich“ etwas auf die Pipe schreibt.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
66 / 210
23.03.2010
PROMOD-2a / SS10
6.9 Thread-Synchronisation mit CyclicBarrier
Siehe: Class CyclicBarrier http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/CyclicBarrier.html
“A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are
useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is
called cyclic because it can be re-used after the waiting threads are released”
Siehe auch Beispiel: http://programmingexamples.wikidot.com/cyclicbarrier
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. The
barrier is called cyclic because it can be re-used after the waiting threads are released.[1]
An example taken from [2] follows:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample
{
private static int matrix[][] =
{
{ 1 },
{ 2, 2 },
{ 3, 3, 3 },
{ 4, 4, 4, 4 },
{ 5, 5, 5, 5, 5 } };
private static int results[];
private static class Summer extends Thread
{
int row;
CyclicBarrier barrier;
Summer(CyclicBarrier barrier, int row)
{
this.barrier = barrier;
this.row = row;
}
public void run()
{
int columns = matrix[row].length;
int sum = 0;
for (int i = 0; i < columns; i++)
{
sum += matrix[row][i];
}
results[row] = sum;
System.out.println("Results for row " + row + " are : " + sum);
// wait for others
try
{
barrier.await();
} catch (InterruptedException ex)
{
ex.printStackTrace();
} catch (BrokenBarrierException ex)
{
ex.printStackTrace();
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
67 / 210
23.03.2010
PROMOD-2a / SS10
}
}
public static void main(String args[])
{
final int rows = matrix.length;
results = new int[rows];
Runnable merger = new Runnable()
{
public void run()
{
int sum = 0;
for (int i = 0; i < rows; i++)
{
sum += results[i];
}
System.out.println("Results are: " + sum);
}
};
/*
* public CyclicBarrier(int parties,Runnable barrierAction)
* Creates a new CyclicBarrier that will trip when the given number
* of parties (threads) are waiting upon it, and which will execute
* the merger task when the barrier is tripped, performed
* by the last thread entering the barrier.
*/
CyclicBarrier barrier = new CyclicBarrier(rows, merger);
for (int i = 0; i < rows; i++)
{
new Summer(barrier, i).start();
}
System.out.println("Waiting...");
}
}
Siehe auch „Getting to know Synchronizers”:
http://java.sun.com/developer/JDCTechTips/2005/tt0216.html#1
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
68 / 210
23.03.2010
PROMOD-2a / SS10
6.10 Zusammenfassung Threads:
Aus der API-Beschreibung entnommen:
public class Thread
extends Object
implements Runnable
A thread is a thread of execution in a program. The Java Virtual Machine allows an application to have multiple
threads of execution running concurrently.
Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority.
Each thread may or may not also be marked as a daemon.
When code running in some thread creates a new Thread object, the new thread has its priority initially set equal
to the priority of the creating thread,
and is a daemon thread if and only if the creating thread is a daemon.
When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method
named main() of some designated class). The Java Virtual Machine continues to execute threads until either of the
following occurs:
The exit method of class Runtime has been called and the security manager has permitted the exit operation to
take place.
All threads that are not daemon threads have died, either by returning from the call to the run method or by
throwing an exception that propagates beyond the run method.
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This
subclass should override the run method of class Thread. An instance of the subclass can then be allocated and
started.
The other way to create a thread is to declare a class that implements the Runnable interface. That class then
implements the run method. An instance of the class can then be allocated, passed as an argument when creating
Thread, and started.
6.11 Zusammenfassung Pipes:
Pipes sind ein streamorientiertes Kommunikationsmedium für die Inter-Thread-Kommunikation.
Pipes können nur zwischen lokalen Thread verwendet werden.
Pipes sind für eine methodenorientiert Zusammenarbeit zwischen Threads nicht so gut geeignet.
Vorausschau: für die netzwerkweite Inter-Thread-Kommunikation (Programm-Kommunikation) sind die
Sockets geeignet und RMI (und CORBA) und noch einiges mehr (Web-Services, …).
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu
diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
69 / 210
23.03.2010
PROMOD-2a / SS10
7. Exkurs: how to model an application?
Oder anders gefragt: How to manage the project?
Warum steht der Exkurs an dieser Stelle? Hätte man die „Theorie“ nicht vorher bringen können? Nun, das wäre
sicher möglich gewesen – wäre dann allerdings eben Theorie geblieben. Nun ist die Situation die, dass Sie aus
unserm Projektfahrplan (siehe unten) das ADRELI_CON Projekt und das Projekt ADRELI_THREADS++ bereits
fertig haben.
Durch die Präsentation der Projekte haben Sie auch konkret miterlebt, dass es teilweise ganz unterschiedliche
Ansätze für die Umsetzung der Spezifikation gab. Manche Projektteams versuchten gleich von Anfang an ein MVCModell zu implementieren, andere haben eine monolithische Architektur bevorzugt – Hauptsache „es läuft“.
Unterschiede gab es auch im Bereich der I/O, des Threadings, der Pipe-Kommunikation, usw..
Um das Verständnis für die Modellierungsproblematik zu sensibilisieren, sollen in diesem Kapitel einige
Überlegungen diskutiert werden.
ADRELI_CON
ADRELI_THREADS++
ADRELI_NETCOM
ADRELI_GUI
ADRELI_JDBC
Die ADRELI-Projekte
Die Projektsequenz ermöglicht es, dass bestimmte Aspkete des Vorgehensmodells von Barry W. Boehm
(„Spiralmodell“, 1988) „erlebt“ werden können. Das Spiralmodell hat andere Vorgehensmodelle beinflußt, siehe
dazu beispielsweise auch „Evolutionäre Softwareentwicklung“ und „Extreme Programming“. Mit ein zentraler
Punkt ist das „divide et impera“: „teile und herrsche“. (Zur Erinnerung: dieses Prinzip haben wir auch in PROMOD-1
für die Entwicklung von Algorithmen – im Zusammenhang mit der „Funktionalen Abstraktion“ - diskutiert.)
Die (verborgene) Gesamtaufgabenstellung von PROMOD2 ist die Entwicklung einer
1. Datenbank-Applikation die
2. im Netz verteilt ist (Client-Sever, Web-Service, …)
3. mulituser-fähig2.
4. skalierbar3 und
5. ausfallsicher4 ist.
Diese Gesamtaufgabenstellung kann durch einen Gesamt-Entwurf gelöst werden. Die Konsequenz:
hoher Designaufwand und („Design for the future“)
lange Wartezeit bis zu einem testbaren ersten (Zwischen-)ergebnis.
Auf der Managementseite:
o schwer administrierbar,
o damit auch hohes Risiko (möglicherweise Abbruch, hohe Korrektur-/Änderungskosten; 10x10x10Regel!).
Die Projektsequenz (eine „Drehung“ in der Boehm’schen Spirale / bzw. ein „Sprint“ im Sinne von SCRUM)
konzentierte sich auf jeweils dedizierte Probleme – ausgehend
1. von der (benutzergetriebenen) Applikationsfunktionalität (Datenmodell & Funktionalität) (Siehe oben Punkt 1)
2. hin zu fortgeschrittenen technischen Fähigkeiten der Applikation (Siehe oben die Punkte 2 bis 5)
In der Retrospektive sollten Sie feststellen, dass Sie sich
zuerst mit dem Applikationskern (dem Datenmodell5 und der Funktionalität) vertraut und sicher gemacht
haben. Vorteil: der Applikationskern ist testbar, für die Weiterentwicklung stabilisiert und er ist auch
vorführbar.
2
Nicht wirklich, da wir uns z.B. um keinerlei Rechteverwaltung kümmern.
Am Ende ist der Server multi-threaded: um jeden Client kümmert sich ein dedizierter Thread
4
Zum Beispiel durch Einsatz eines clusterfähigen DB-Servers (z.B. MySQL [MySQL Cluster integrates the standard
3
MySQL server with an in-memory clustered storage engine called NDB. Siehe: http://dev.mysql.com/doc/refman/5.0/en/mysqlcluster-overview.html ])
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
70 / 210
23.03.2010
PROMOD-2a / SS10
Danach wurden teilweise – für den Benutzer nicht sichtbar – weitere technisch Eigenschaften implantiert
(Threading, Pipes, Sockets, multi-threaded Server), die in Summe den Gesamt-Entwurf umgesetzt haben.
Vorteil: wie oben. Jede „Spiraldrehung“/“Sprint“ ist testbar, stabilisiert sich und ist auch demonstrierbar.
Die in den Teilprojekten
verfassten UML-Diagramme und
Source-Codes
sollten den inkrementell zunehmenden Komplexitätsgrad verdeutlichen und für Sie greifbar (= kalkulierbar)
machen, wo die Aufwände stecken. Solche Kenntnisse sind unerlässlich für die
Abschätzung von Design-Entscheidungen und
Kostenschätzungen.
Adreli_1_Con „How-To“
Entwicklungsstruktur: Top-Down
Entwurfsverfahren: Divide-and-Conquer
1. Basis-UI (= User Interface) realisieren; Menue zur Auswahl der Funkionalität
2. Business-Logik = Schleife und Verzweigung, je nach gewählter Funktion
ƒ i.W.: Keyboard-Eingaben holen (Adressfelder) -> Puffern -> in File speichern (write)
ƒ oder: Inhalt aus File holen (read) -> puffern -> auf Konsole anzeigen
3. Am besten zunächst für nur zwei Felder implementieren
4. Regex einbauen
5. Programm nun für alle Felder mit Regex implementieren
6. Regex perfektionieren
Bei der Verwendung neuer API-Schnittstellen: „check – test – integrate“
1. Bottom-Up vorgehen: d.h. in einem separaten Java-File die API studieren (ausprobieren und testen)
2. Dieses separate Java-File als Case-Study archivieren: so können Sie die Case-Study später in dem
Maße erweitern, wie Sie die API-Schnittstelle weiter erforschen!
3. Integrieren: die funkionierenden Code-Sequenzen aus Ihrer Case-Study in die zu entwickelnde App
übernehmen.
Adreli_2_Thread++ „How-To“
1) CaseStudies zu Thread und Runnable
2) Adreli_CON mit einem expliziten Thread implementieren/umschreiben
3) „Refactoring“ überlegen!
3a) Ziel: 2 – 3 Threads:
GUI-Thread
Client-Teil
Model-Thread
Client-Teil
File I/O Thread
Server-Teil
(ggf. der einfachheithalber GUI- und Model-Thread zusammenfassen, da die GUI sehr primitiv ist)
Kommunikation der Thread, lt. Aufgabenstellung, mittels Kommunikationsmittel Pipe.
Konkret:
a) Lassen Sie die GUI mit der switch-case-Konstruktion wie bisher bestehen
b) „Trick 1“: File-I/O-Methoden (sind 3 Methoden) redurzieren sich nun auf die Pipe-Kommunikation
zwischen GUI-Thread und File-I/O-Thread
c) „Trick 2“: der bisherige Buffer (z.B. ArrayList) muss in den char[]-Buffer, den z.B.
PipedWriter.write(), bzw. PipedOutputStream.write() braucht, „umgeladen“ / konvertiert
werden.
Adreli_3_NetCom „How-To“
Adreli_4_GUI „How-To“
Adreli_5_JDBC „How-To“
(dazu später mehr / hier weiter)
5
Datenmodell und Funktionalität sind in unserem Fall zugegebenermaßen trivial. Dies ist aber so beabsichtigt, damit
die „Vorgehensstruktur“ und die benutzten Java-APIs gut sichtbar im Vordergrund stehen und gut sichtbar sind!
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
71 / 210
23.03.2010
PROMOD-2a / SS10
7.1 Als Kompass/Leitplanke dienen Vorgehensmodelle
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
72 / 210
23.03.2010
PROMOD-2a / SS10
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
73 / 210
23.03.2010
PROMOD-2a / SS10
Rollen
SCRUM-Modell
Scrum Master
Burn-down-chart
Product
Owner
Sprint Backlog
Release Backlog
Product
Backlog
Development
Team
Pair-Programming
2
Prof. Illik
Sprint Planning
Meeting
Daily Scrum
Zeremonien
Review
Meeting
Prof. J. Anton Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
74 / 210
23.03.2010
PROMOD-2a / SS10
7.2 Ihre Einschätzung? Welches Vorgehensmodell ist wofür geeignet?
Für sehr grosse Projekte gut geeignet:
???-Modell
Für sehr kleine Projekte gut geeignet:
???-Modell
Für kleine und mittlere Projekte gut geeignet:
???-Modell
Für mittlere und größere Projekte gut geeignet:
???-Modell
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
75 / 210
23.03.2010
PROMOD-2a / SS10
7.3 Und wie geht`s im Detail?
Siehe Artikel: Tom Gilb, „Evolutionäres Entwickeln“;
tom_gilb_1987_scan0273.pfd
Siehe Video:
Hamid Shojaee, „SCRUM Agile Development in under 10 Minutes“
SCRUM_video_under_10_minutes.flv
Siehe Artikel: Bernd Oestereich und Christian Weiss,
„Wie werden große Softwareprojekte agiler?“
wum-009-0049-y_agil_grosse_sw_projekte_agiler.pdf
aus Magazin „Wirtschaftsinformatik & Management“/Heft 02.2009
•
•
Den (historischen) Artikel von Tom Gilb und den Artikel von Bernd Oestereich und
Christian Weiss finden Sie im Wintra.
Das Video von Hamid Shojaee finden Sie bei YouTube
Siehe Literartur:
Siehe Kapitel “Algorithmen”
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
76 / 210
23.03.2010
PROMOD-2a / SS10
8. Netzwerkzugriff
Was Sie lernen:
wie mit Java auf das Netzwerk zugegriffen wird
wie Client- und Server-Sockets programmiert werden
Voraussetzungen:
Arbeiten mit Threads
8.1 Grundlagen
Als Netzwerke werden miteinander verbundene Computer bezeichnet, die miteinander kommunizieren
können. Das bedeutendste Computernetz ist das Internet. Stehen die miteinander verbundenen Computer
innerhalb eines Unternehmens, so spricht man von einem Intranet, wenn zum Aufbau des firmeninternen
Computernetzes die Internet-Technologie verwendet wird.
8.1.1
Protokolle
Um die komplexen Abläufe und Mechanismen der softwareseitigen Verbindung von Computern besser verstehen
zu können, wurde unter dem Dach der ISO (International Organization for Standardization) das ISO/OSI-7Schichten-Modell (1983) definiert (siehe unten ISO-Referenzmodell). (OSI = Open System Internconnection).
OSI-Paradigma: Zwei Netz-Computer können Informationen nur auf der gleichen Ebene (Schicht)
austauschen. Beispielsweise wird eine E-Mail von der Anwenderschicht abgeschickt und kommt auf dem anderen
Rechner in der Anwendungsschicht an. Für den Datenaustausch muss es bestimmte Vorschriften geben, die den
Aufbau und Abbau der Verbindung und den Datenaustausch reglementieren. Diese (Umsetzung der Vorschriften
in Software) werden Protokolle genannt. Protokolle nutzen eine tieferliegende Schicht und reichen Ergebnisse
an eine höhere Schicht weiter.
Das Protokoll, welches sich zur Kommunikation vernetzter Computer durchgesetzt6 hat, ist TCP/IP
(Transmission Control Protocol / Internet Protocol). TCP/IP ist eine Sammlung von Protokollen, die
entwickelt wurden, um Computeranwendern das kooperative Arbeiten in einem Netz von örtlich verteilten
Rechnern (ohne Netzwerk-Zentrale) zu ermöglichen. Diese geographisch verteilten Computer sind üblicherweise
Mitglieder lokaler Netzwerke. Sowohl im lokalen Netz wie auch in der „Zusammenfassung“ dieser lokalen Netze,
dem sogenannten Internet, ist die TCP/IP-Protokoll-Suite die Software-Basis für die lokale und weltumspannende
Kommunikation.
Die TCP/IP-Protokoll-Suite wurde auf Anregung des US-amerikanischen Departement of Defence (DoD) ab dem
Jahr 1962 entwickelt (Siehe hierzu auch http://www.illik.de -> „Online-Anhang Part 1 ->“ „Historischer
Aufbruch: der Bau des Information Superhighway“ ).
6
Vor TCP/IP gab es zahlreiche proprietäre Protokolle. Von der IBM: SNA System Network Architecture; von Digital
Equipment (kurz DEC): DNS Digital Network Architectur. Praktisch hatte jeder große Computer-Hersteller seine
eigene Protokoll Suite.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
77 / 210
23.03.2010
PROMOD-2a / SS10
ISO Referenzmodell
ursprüngliches
ARPAnet
Referenzmodell
Application
Presentation
Prozesse
/
Applikationen
Session
Transport
TCP/IP
Schichten
Beispiel für die SW
einzelner Schichten
Benutzerprogramme,
Bibliotheken
HTTP, FTP, SMTP,
rcp,
rlogin,
…
telnet,
ftp, rlogin,
rcp
Socket
SKT_stream
TCP
Host - Host
IP
Network
Data Link
Netzwerkschnittstelle
Hardware
Netz-Hardware
UDP
Protokolle
ICMP
Netzwerkschnittstelle
Ethernet Driver
Netz-HW
Netz-Hardware
Die Transport-Schicht (Transmission Level). TCP und UDP sind die am meisten verwendeten Protokolle in
dieser Schicht. Die Protokolle dieser Schicht bieten Netzwerkdienste für Programme der Applikations-Schicht.
Jedes dieser Protokolle hat eigene besondere Fähigkeiten. Beispiele für solche Fähigkeiten sind:
o
TCP: ist verbindungsorientiert und stellt eine sichere Punkt-zu-Punkt-Verbindung zwischen zwei
Computern her. TCP gewährleistet einen fehlerfreien Datentransport.
o
UDP: ist verbindungslos. Hier muss das Anwendungsprogramm gewährleisten, dass die
abgeschickten Pakete fehlerfrei beim Empfänger ankommen. Auch um die richtige Reihenfolge
der Pakete muss sich das Anwendungsprogramm selbst kümmern.
Verbindungsorientiert / verbindungslos: Bei der Verbindungsorientierung wird mit einem „Handshake“Verfahren gearbeitet. D.h. der empfangende Rechner bestätigt den Empfang eines/mehrerer Datenpakete. Bei
der verbindungslosen Übertragung werden Datenpakete übertragen, ohne eine Rückantwort abzuwarten.
Paket: Größere Datenmengen werden in mehrere kleine Pakete zerlegt und zwischen Computern übertragen.
8.1.2
Adressierung: IP-Adresse und Port-Nummer
Voraussetzung für ein funktionierendes Computer-Netzwerkt ist eine eindeutige Adressierung der beteiligten
Rechner.
Unter TCP/IP wird eine 32-Bit-IP-Adresse verwendet (also 4 Byte bei IP Version 4; zum Beispiel 80.130.234.185
in Dezimalnotation). Bei IPv6 sind es 128-Bit-Adressen, sechzehn Byte; zum Beispiel
0588:2353:0000:0000:0000:0000:1428:57ab in hexadezimaler Notation). Das Paket java.net unterstützt seit
JDK 1.4 auch IPv6 Internet-Adressen, vorausgesetzt dass das Host-Betriebssystem auch IPv6 untersützt.
Die Adresse besteht aus der Netzwerkadresse und der Rechneradresse. Der Adressraum ist eingeteilt in
verschieden Klassen. Siehe Bild unten. Beispiel für ein Klasse-C-Adresse: 217.89.65.160
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
78 / 210
23.03.2010
PROMOD-2a / SS10
Adressierung erfolgt über IP-Adressen und Port-Nummern.
Es gibt eine spezielle IP-Adresse für den lokalen Arbeitsplatz: 127.0.0.1. Diese Adresse kann auch unterm dem
Alias localhost angesprochen werden und referenziert den eigenen PC.
Alle Adressen die mit 10. und 192.168. beginnen, sind für die Verwendung in privaten Netzen, wie z.B. dem
Intranet, reserviert. Weiter Informationen hierzu siehe RFC1918 unter www.rfc.net.
Oft werden Verbindungen zwischen zwei Computern als Client-Server-Verbindungen aufgebaut. Der Server
wird dabei i.d.R gestartet, läuft im Hintergrund und stellt seine Services den anfragenden Clients zur Verfügung.
Der als Client fungierende Computer nimmt Kontakt mit dem Server auf um einen Service des Servers in
Anspruch zu nehmen. Ein Rechner kann gleichzeitig Client und Server sein (Peer-to-Peer-Verbindung).
Die Portnummer adressiert einen Dienst/Service auf einem Server. Das IAB (Internet Activity Board /
Internet Architecture Board http://www.iab.org bzw. die IANA Internet Assignd Numbers Authority ) regelt
die Port-Zuordnung. Sog. "well-known port numbers" (21,23,25,80, .. usw. Siehe
http://www.iana.org/assignments/port-numbers. Siehe auch Bild unten).
Da auf einem Computer mehrere Serverprogramme laufen, die wiederum von mehreren Clients benutzt werden
können, ist es notwendig, zur Unterscheidung der Serveranwendungen neben der IP-Adresse für den
Verbindungsaufbau noch eine Port-Nummer anzugeben. Eine Client-Server-Verbindung kann nur aufgebaut
werden, wenn die Client-Software den Server über die richtige Port-Nummer anspricht.
Protokoll/Dienst
ICMP/Ping
Internet Control
Message Protocol
Port
Nicht festgelegt
Transportprotokoll
TCP, UDP
RFCs
RFC792
FTP FileTransfer
Protocol
TELNET
Terminal-Verbindung
21
TCP
RFC959, RFC765
23
TCP
RFC854
SMTP/MailServer Simple Mail
Transfer Protocol
25
TCP
RFC821
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
79 / 210
Erläuterung
Protokoll zur
Übermittlung von
Kontroll-und
Fehlermeldungen
während der
Datenübertragung
Datenaustausch mit
einem FTP-Server
Protokoll zur
Terminalemulation
bei der
Kommunikation mit
einem anderen
Computer.
Einfaches PostÜbertragungsprotokoll
für die Sendung von
E-Mails im Internet.
23.03.2010
PROMOD-2a / SS10
HTTP Hypertext
Transfer Protocol
80
TCP, UDP
RFC1945, RFC2068
POP3 Post
Office Protocol
110
TCP, UDP
RFC1939
Protokoll zur
Übertragung von
Informationen (HTMLSeiten und ihre
Inhalte) im Internet.
Mail-TransferProtokoll zum
Empfangen von EMails.
Die Namen der Protokolle stimmen meist mit den Namen der Server/Services überein.
Die Portnummer kann eine Zahl zwischen 0 und 65535 sein. Bei der Programmierung eigener Client-ServerVerbindungen dürfen bereits belegte Portnummern nicht verwendet werden. Dies sind i.a. alle Zahlen kleiner
(vor einiger Zeit: 300) 50000 … am Besten auf der iana-Site nachsehen! Zahlen ab 50000 aufwärts
verwenden.
8.1.3
Die Datei hosts und DNS (Domain Name System)
Ist die Angabe von localhost beispielsweise nicht möglich, kann die Zuordnung eines Namens zu einer IPAdresse in der Datei hosts vorgenommen werden. In Windows XP Professional befindet sich die Datei z.B.
unter C:\windows\i386. Unter Linux unter /etc.
Beispiel - C:\windows\i386\hosts:
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
Copyright (c) 1993-1999 Microsoft Corp.
Dies ist eine HOSTS-Beispieldatei, die von Microsoft TCP/IP
für Windows 2000 verwendet wird.
Diese Datei enthält die Zuordnungen der IP-Adressen zu Hostnamen.
Jeder Eintrag muss in einer eigenen Zeile stehen. Die IPAdresse sollte in der ersten Spalte gefolgt vom zugehörigen
Hostnamen stehen.
Die IP-Adresse und der Hostname müssen durch mindestens ein
Leerzeichen getrennt sein.
Zusätzliche Kommentare (so wie in dieser Datei) können in
einzelnen Zeilen oder hinter dem Computernamen eingefügt werden,
aber müssen mit dem Zeichen '#' eingegeben werden.
Zum Beispiel:
102.54.94.97
38.25.63.10
rhino.acme.com
x.acme.com
# Quellserver
# x-Clienthost
127.0.0.1 localhost
In dieser Datei können weitere oft verwendete IP-Adressen symblischen Namen (Aliasnamen) zugeordnet werden.
Auch im Internet werden symbolische Namen für die IP-Adressen der Zielrechner verwendet; diese Aliasnamen
werden als Domain-Namen bezeichnet (www.illik.de). Der Domain-Name-Server (steht i.d.R. beim InternetProvider) wandelt den Domain-Namen in die IP-Adresse um und gibt sie z.B. dem Browser zurück. Damit kann
die Verbindung zum Zielserver aufgebaut werden.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
80 / 210
23.03.2010
PROMOD-2a / SS10
8.2 Klassen für die Adressierung
8.2.1
Die Klasse InetAddress: (siehe API-Doku)This class represents an Internet Protocol (IP) address.
Ein Objekt der Klasse InetAddress enthält den logischen Namen und die IP-Adresse des Zielrechners.
Die Klasse InetAddress befindet sich im package java.net
Methoden zur Erzeugung eines InetAddress-Objekts:
static InetAddress getLocalHost()
static InetAddress getByName(String host)
static InetAddress[] getAllByName(String
host)
static InetAddress getByAddress(byte[]
addr)
static InetAddress getByAddress(String
host, byte[] addr)
Adresse des eigenen Computers
(siehe Beispiel unten)
liefert die Adresse für den als Parameter genannten Host
(siehe Beispiel unten)
Alle Adressen eines remote Hosts
(siehe Beispiel unten)
Liefert Adresse auf der Basis der übergebenen (raw) IPAdresse (siehe Methode unten getAddress())
Kreiert Adresse basierend auf der übergebenen IP-Nr.
und dem Hostnamen. (Der Hostname wird nicht geprüft;
kein Aufruf eines Namensdienstes.)
…u.v.a.m.
Weitere Methoden, um Informationen zum InetAddress-Objekt zu erhalten:
String getHostName()
String getHostAddress()
byte[] getAddress()
boolean
isMulticastAddress()
boolean
isSiteLocalAddress()
Liefert den logischen Namen der IP-Adresse zurück
(siehe Beispiel unten)
Liefert die IP-Adresse zurück
(siehe Beispiel unten)
Liefert IP-Adresse als Byte-Array zurück (Sprechweise
(API-Doku): „raw IP address“)
Liefert true zurück, wenn die IP-Adresse eine MulticastAdresse ist (kann gleichzeitig an mehrere Stationen
Nachrichten verschicken).
“Utility routine to check if the InetAddress is a site local
address”
…u.v.a.m.
Siehe API-Beschreibung zu den Themen:
Address types
IP address scope
Textual representation of IP addresses
Host Name Resolution
Inet Address Caching
Beispiel - Umgang mit InetAddress-Methoden:
import java.io.*;
import java.net.*;
public class InternetAdressen
{
public static void main (String [] args)
{
try
{ System.out.println("Meine Adresse (Rechnername/IP-Adresse): " +
InetAddress.getLocalHost());
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.println ("Geben Sie einen Internet-Host-Namen ein (z.B. www.fhfurtwangen.de)");
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
81 / 210
23.03.2010
PROMOD-2a / SS10
while (true)
{
System.out.print ("Host-Name: ");
String s = in.readLine();
if (s.equals("ende")) break;
try
{
//InetAddress iAddr = InetAddress.getByName(s);
InetAddress []iAddr = InetAddress.getAllByName(s);
//System.out.println("IP-Adresse:
" + iAddr.getHostAddress() + "\n");
for (int i = 0; i < iAddr.length; i++) {
System.out.println("IP-Adresse: " + iAddr[i].getHostAddress() + "\n");
System.out.println("Rechnername: " + iAddr[i].getHostName() + "\n");
}
}
catch(UnknownHostException ex)
{
System.out.println ("Host konnte nicht gefunden werden" + "\n");
}
}
}
}
catch (Exception ex){ }
}
8.2.2
Die Klasse URL
Ein URL = „Uniform Ressource Locator“ ist eine Beschreibung einer Adresse (Quelle) im Internet / dient dazu, um
Ressourcen in einem Netzwerk zu lokalisieren.
Siehe API-Doku: „Class URL represents a Uniform Resource Locator, a pointer to a "resource" on the
World Wide Web. A resource can be something as simple as a file or a directory, or it can be a reference to a
more complicated object, such as a query to a database or to a search engine…..”
Eine URL besteht aus mehreren Bestandteilen
<Protokoll>://[Login[:Passwort]@]Rechnername.Domain[:Port]/Verzeichnis/Ressource
o Die Java -Plattform unterstützt sowohl HTTP- als auch FTP-Ressourcen
Die Klasse URL ermöglicht nicht nur den komfortablen Zugriff auf die oben genannten Teile der URL, sondern
auch auf die hinter der URL stehende Ressource. So kann eine Datei durch Angabe der URL auch direkt
geladen werden.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
82 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel: http://www.fh-furtwangen.de:80/user/derda/index.html
Zugriff: Wenn man den Inhalt der Ressource abholen möchte, kann dies mit der Methode openStream() der
Klasse URL erledigt werden. Siehe Beispiel weiter unten.
Konstruktoren
Für die Klasse URL gibt es sechs Konstruktoren. Hier zwei davon:
URL(String spec) throws MalformedURLException
URL(String protocol,
Sting host,
int port,
String file) throws MalformedURLException
…
Der String spec enthält die URL
Es kann die URL, in ihre Bestandteile zerlegt,
übergeben werden.
… und dann gab7 es noch URIs und URNs
Das Paket java.net ab JDK 1.4 unterscheidet zwischen URLs und URIs (Uniform Ressource Identifiers). Eine
URI ist ein syntaktisches Konstrukt, das die verschiedenen Teile der Zeichenfolge spezifiziert, die eine
Webressource kennzeichnen. Eine URL ist eine spezielle Form eines URIs: die URL enthält ausreichend
Informationen, um eine Ressource zu lokalisieren.
URIs, wie beispielsweise [email protected] sind keine Lokatoren (Bezeichner für den Standort von Ressourcen) –
der Identifier enthält keine Daten für die Lokalisierung. Ein solcher URI heisst URN (Uniform Ressource
Name)
In der Java-Bibliothek hat die Klasse URI keine Methoden für den Zugriff auf die Ressource, die der
Bezeichner spezifiziert – ihre einzige Aufgabe ist das Parsing (Wir verfolgen das Thema hier nicht weiter. Siehe
API-Doku und Literatur.)
Wichtige Methoden der Klasse URL
String getFile()
String getHost()
int getPort()
String getProtocol()
URLConnection openConnection() throws
IOException
InputStream openStream()throws IOException
Boolean sameFile(URL ofher)
Gibt den Datei-Namen, der in dem URL enthalten ist,
zurück.
Liefert den Host-Namen, der in dem URL enthalten ist.
Liefert die Port-Nummer des URLs.
Liefert das Protokoll des URLs.
Öffnet eine neue Verbindung zu der URL-Adresse und
gibt ein URLConnection-Objekt zurück.
Öffnet eine neue Verbindung zu der URL-Adresse, von
der gelesen werden soll, und gibt ein InputStreamObjekt zurück (remember: it’s a byte stream)
(siehe Beispiel unten)
Vergleicht zwei URLs in Bezug auf die URL-Adresse und
gibt true zurück, wenn beide auf dieselbe Ressource
verweisen.
…
Beispiel – Fragment URL-Methode openStream()
7
Anmerkung: Die früher übliche Unterscheidung zwischen URI, URL und URN ist nicht mehr relevant (vgl.
[W3C01]). Wenn Sie den Eindruck vermitteln möchten, ganz besonders auf der Höhe der Zeit zu stehen, können Sie
den Begriff „IRI” (Internationalized Ressource Identifier) verwenden. IRIs werden durch RFC 398 7(vgl.
http://www.w3.org/International/iri-edit/]) definiert und sind im Prinzip URIs mit der Möglichkeit, alle Zeichen aus dem
Unicode-Zeichensatz zu verwenden. Prüfen Sie den Sachverhalt hinsichtlich evtl. Auswirkungen auf Java!
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
83 / 210
23.03.2010
PROMOD-2a / SS10
Wenn man einfach den Inhalt der Ressource abholen (lesen) möchte, kann man dies mit Hilfe der Methode
openStream(): diese Methode liefert ein InputStream-Objekt zurück, mit dem man den Inhalt der Ressource lesen
kann:
…
URL url = new URL(urlString);
…
InputStream uin = url.openStream(); // stream of bytes
BufferedReader in = new BufferedReader(new InputStreamReader(uin)); // bridge class
String line;
while ((line = in.readLine()) != null) // Zeilenweise lesen
{
// Zeile verarbeiten
}
…
Hinweis: Benötigt man jedoch zusätzliche Angaben über die Ressource, muss auf die Klasse URLConnection
zurückgegriffen werden. Diese Klasse bietet eine weitergehende Kontrolle über den Zugriff auf Web-Ressourcen;
z.B. Setup-Parameter und Request-Properties können gesetzt werden. Siehe API-..\..\..\jdk-6u10docs\docs\api\index.htmlDoku und Literatur.
Beispiel – URL-Methoden: (urls.java)
import java.io.*;
import java.net.*;
public class URLs
{
public static void main (String [] args)
{
int l = 1000;
byte[] is_byte = new byte[l];
try
{
URL url = new URL(args[0]);
InputStream is = url.openStream();
System.out.println ("Inhalt von " + url.getProtocol()+ "://" +
url.getHost() +": " + url.getPort());
while ((l = is.read(is_byte)) != -1) // Reads some number of bytes from the
//input stream and stores them
//into the buffer array
{
System.out.print(tochar(is_byte));
}
}
catch (MalformedURLException ex)
{
System.out.println ("falsche URL"); // Protocol - Domain - Port:
http://www.ambit.de:80
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
private static char[] tochar(byte[] b)
{
char[] c = new char[b.length];
for (int i = 0; i < b.length; i++)
c[i] = (char) ((b[i] & 0x7F) + (b[i] < 0 ? 128 : 0));
return c;
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
84 / 210
23.03.2010
PROMOD-2a / SS10
8.3 Socket-Programmierung
Sockets wurden erstmalig in BSD-Unix („Berkeley System Distribution“) angeboten (Anfang der 1980er Jahre).
Die Socket-Schnittstelle ist danach in allen bedeutenden Betriebssystemen implementiert worden.
Die Socket-Schnittstelle dient der bidirektionalen Kommunikation zwischen zwei Computern im Netzwerk: mit
den Methoden getOutputStream() und getInputStream() werden entsprechende Byte-Streams geöffnet.
Die Datenübertragung über Sockets wird grob in drei Schritten vollzogen:
1. Zurest wird eine
Punkt-zu-Punkt-Verbindung zwischen zwei Computern hergestellt. („unicast“)
Dann werden die Daten übertragen;
danach erfolgt der Verbindungsabbau.
2. Bei einer Client-Server-Verbindung wird dazu (vom Client) via IP-Nummer und Port-Nummer des
Servers eine Verbindung zu diesem hergestellt.
3. Bei Peer-to-Peer-Verbindungen braucht auch der Server die IP- und Port-Daten vom Client.
Es gibt Socket-Klassen für den Server (Klasse ServerSocket) und für den Client (Klasse Socket) Æ siehe
API-Doku. Worin sich die Sockets unterscheiden? Siehe unten „Hinweis“ bei Client- und Server-Sockets.
Schema für die Kontaktaufnahme und die Kommunikation zwischen Client und Server (vgl. unten public
class server und public class client)
Der Server
ServerSocket erzeugen
Der Client (rein/pur)
Socket erzeugen
bind() auf Port-Nr.
mit Server verbinden
connect()
(u.U. blockierend)
auf Verbindung warten [u.
U. mit Backlog
dimensionieren] und
bestätigen accept()
(u.U. blockierend)
Anfrage schreiben
/
Antwort lesen
.
Kann auch
schon beim
Erzeugen
erledigt werden.
Siehe API-Docu
Verbindung abbauen
close()
Thread generieren
lesen/schreiben
(kooperieren über
Sockets)
ServerSocket schließen
close()
Anmerkung: es gibt Stream-Sockets (= verbindungsorientierte TCP-Sockets) und Datagram-Sockets
(verbindungslose UDP-Sockets). Wir betrachten im Folgenden nur die Stream-Sockets. Zu den DatagramSockets siehe Socket-API-Doku.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
85 / 210
23.03.2010
PROMOD-2a / SS10
Client und Server verwenden Sockets auf unterschiedliche Weise:
Das Server-Programm verwendet einen Socket, der mit einem lokalen Port des Server-Rechners verbunden
(Methode bind()) ist. Damit kann ein Client mit dem Server-Socket per connect() Verbindung
aufnehmen.
Das Client-Programm verwendet einen Socket, der eine Verbindung zu einem Port des Server-Rechners
aufbaut (mit der Methode connect()).
8.3.1
Client-Sockets: die Klasse socket
Hinweis: In der Klasse socket spielt stets die Adresse (in diversen Formen) des Servers eine Rolle. In der Klasse
ServerSocket spielt nur der Port – über den der Service erreicht werden soll – eine Rolle. Siehe auch Hinweis zu
ServerSocket.
Konstruktoren der Klasse Socket:
Socket(String host,
int port) throws IOException
Socket(String host,
int port,
InetAddress localAddr,
int localPort) throws IOException
Socket(InetAddress address,
int port) throws IOException
Socket(InetAddress address,
int port,
InetAddress localAddr,
Int localPort) throws IOException
Als Parameter werden Host-Name und
Port-Nummer des Servers angegeben.
Creates a stream socket and connects it to the specified
port number on the named host.
Wie oben.
Wie oben
… vom eigenen Rechner / mit bind() darauf
Wie oben, jedoch Host-Bezeichnung nicht über String
sondern InetAddress-Objekt.
Analog oben, mit bind() des Sockets auf locale Adresse
/ Port.
Creates a socket and connects it to the specified remote
address on the specified remote port.
Wird der angegebene Zielrechner nicht gefunden, wird eine Exception vom Typ UnknownHostException (von
IOException abgeleitet) ausgelöst.
Wichtige Methoden der Klasse Socket
Die Klasse Socket besitzt einige get- und set-Methoden zum Abfragen und Setzen der Verbindungseigenschaften.
Die wichtigsten Methoden sind getOutputStream() und getInputStream(). Beide geben ein entsprechendes
Byte-Stream-Objekt zurück, über das der Datenaustausch erfolgt, wenn die Verbindung erfolgreich eingerichtet
werden konnte. (Siehe 7.4 „Was kommt den da im Strom?“: diese Streams werden dann ggf. noch überführt, je
nach dem, was im Stream kommt (Text, binäre Daten/serialisierte Objekte).
InetAddress getInetAddress()
InetAddress getLocalAddress()
int getPort()
public OutputStream getOutputStream()
throws IOException
public InputStream getInputStream()
throws IOException
public int getSoTimeout() throws
SocketException
public int setSoTimeout(int timeout)
8
9
Liefert die Adresse des Host-Computers, zu dem die
Verbindung besteht, also der Client connect()de ist.
Wird die Methode im Server für den Server-Socket
aufgerufen, so liefert sie die eigene Adresse (des Server
Hosts).
Gibt die lokale Adresse zurück, an die der Socket
gebunden (bind()ed) ist. (Das Analogon im Server
heist getLocalSocketAdress().)
Gibt die Portnummer des Hosts zurück
Erzeugt ein OutputStream-Objekt (das ist ein ByteStream) für das Übertragen von Daten zum Server8.
Gibt ein InputStream-Objekt (das ist ein Byte-Stream)
zurück für das Lesen von Daten vom Server9.
Liefert die Zeit in ms, die eine Leseanforderung an den
Host dauern darf, bevor eine InterruptedIOException
ausgelöst wird (0 entspricht unbegrenzter Wartezeit).
Setzt die Timeout-Zeit in ms für eine Leseanforderung
Siehe auch unten: Kapitel „Was kommten den da im Stream? Sockets und Streams“
Siehe auch unten: Kapitel „Was kommten den da im Stream? Sockets und Streams“
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
86 / 210
23.03.2010
PROMOD-2a / SS10
throws SocketException
8.3.2
an den Host. 0 entspricht unbegrenzter Wartezeit.
ServerSockets: die Klasse ServerSocket
Bevor ein Client die Kommunikation mit einem Server aufnehmen kann, muss natürlich der Server gestartet werden.
Der Server bleibt dann in der accept()-Methode geblockt, bis sich ein Client bei ihm anmeldet.
Hinweis 1:
Beim ServerSocket spielt die IP-Addresse des Partners keine Rolle (vgl. oben Hinweis zum Client Socket.)
Ursache: der ServerSocket wird nie als Erster initiativ! Die Initiative geht immer vom Client aus,
deswegen muss dieser die Adresse des Severs wissen.
Für die „Rückwärtskommunikation“ zum Client bekommt der Server einen geeigneten Client-Socket von
der Methode accept() als Returnwert zur Verfügung gestellt. Für diesen Client Socket kann sich der Server
mit getOutputStream() ein OutputStream Objekt besorgen, über dass er dem Client Infos zukommen
lassen kann.
Zusammenfassend kann man also sagen,
der ServerSocket dient nur für die Kontaktaufnahmen mit dem Server.
Die Kommunikation läuft über die Client Sockets.
Hinweis 2:
Wenn über einen einzigen Socket gelesen und geschrieben werden soll, geht das zwar, wie wir eben
gesehen haben, ist jedoch „wenig komfortabel“/“flexibel“.
Man muss sein eigenes „Protokoll“ implementieren, d.h. der Client meldet durch ein „over“, dass jetzt der
Server ein Antwort (über den selben Socket) geben kann.
Für eine echte Peer-to-Peer-Architektur würde man beiden Kommunikationspartner jeweils einen eignen
ServerSocket und einen eigenen Client Socket spendieren.
Konstruktoren der Klasse ServerSocket
ServerSocket()
ServerSocket(int port)
ServerSocket(int port, int backlog)
Creates an unbound server socket. Siehe Beispiel unten:
ist dann noch mit bind() an einen Port zu binden.
Creates a server socket, bound to the specified port.
Mit dem zweiten Parameter kann die maximale Länge
der Warteschlange bestimmt werden. Wird eine
Verbindung an einem Socket mit voller Warteschlange
angemeldet, so wird die Verbindung abgelehnt.
Wichtige Methoden der Klasse ServerSocket
Socket accept()
void bind(SocketAddress endpoint)
void bind(SocketAddress endpoint, int backlog)
void close()
InetAddress getInetAddress()
int getLocalPort()
int getSoTimeout()
void setSoTimeout(int timeout)
Prof. Illik
Server wartet auf Verbindungsanfragen am betreffenden
Port. Kommt eine Verbindungsanforderung von einem
Client, so liefert die Methode das Socket-Objekt für die
Client-Verbindung. (Wird die Verbindung vomClient
beendet, so kann der Server einen weiteren Client
bedienen.)
Bindet ServerSocket an angegebene IP-Adresse und
Port
Bindet ServerSocket an angegebene IP-Adresse und
Port. Dimensioniert backlog.
Schließt den Server-Socket.
Gibt die (eigene) Adresse zurück, mit der der Socket
verbunden ist
Gibt den Port zurück, mit dem der Server-Socket
verbunden ist.
Liefert die Timeout-Zeit in ms, für die ein Client-Socket
den Server-Socket blockieren darf.
Setzt die Timeout-Zeit in ms, die ein Client-Socket den
Server blockieren darf.
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
87 / 210
23.03.2010
PROMOD-2a / SS10
8.3.3
Beispiel – Server-Sockets (server.java):
import java.net.*;
import java.io.*;
import java.util.*;
public class Server
{
public static void main (String [] args) throws IOException
{
System.out.println (">>> Server wird gestartet");
/* ALTERNATIVE A) Für LOKALE Sockets: ****************************************
// The range for assigned ports managed by the IANA is 0-1023
// Creates ServerSocket bound to a specific (local) port;
// Phantasie-Port > 45000
ServerSocket myServerSocket = new ServerSocket (45678);
// bound() to port ServerSocket
//*/
//* ALTERNATIVE B) Für INTERNET-Sockets ***************************************
// class InetSocketAdress extends class SocketAdress
InetSocketAddress myInetSocketAddress = new InetSocketAddress("localhost",45678);
ServerSocket myServerSocket = new ServerSocket(); // unbound ServerSocket
myServerSocket.bind(myInetSocketAddress);
// we bind()it
//*/
for(;;)
{ // start infinite loop
Socket myClientSocket = myServerSocket.accept();
// accept() blocked und liefert Client-Socket
// zurück; von dem kann gelesen werden,
// wenn's ein schreibender Client ist
}
BufferedReader in = new BufferedReader(new InputStreamReader
//bridge from byte streams to character streams to text
(myClientSocket.getInputStream()));
System.out.println ("--> Local Address angemeldet: "
+ myClientSocket.getLocalAddress()); // mit wem?
System.out.println ("--> InetAddress angemeldet: "
+ myClientSocket.getInetAddress());
String s;
try
{
while ((s = in.readLine()) != null)
// Lesen vom Client-Socket
System.out.println ("> " + s);
// und Ausgabe des Gelesenen
}
catch (Exception e){}
System.out.println ("<-- LocalAddress (address to which the socket is bound): " +
myClientSocket.getLocalAddress());
System.out.println ("<-- InetAddress (addr to which the socket is connected): " +
myClientSocket.getInetAddress());
myClientSocket.close();
} // end infinite loop
}
8.3.4
Beispiel – Client-Sockets (client.java):
import java.io.*;
import java.net.*;
public class Client
{
public static void main (String [] args) throws IOException
{
String s; // wird via Keyboard gefüllt und dann auf Socket geschreiben
if (args.length != 1)
{
System.out.println ("falscher oder fehlender Parameter\n"+
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
88 / 210
23.03.2010
PROMOD-2a / SS10
"Geben Sie als Parameter die Adresse des Servers an. Z.B.
'localhost'");
System.exit(1);
}
try
{
// ENTWEDER: Client Socket kreieren (UND gleich den Server connect()en)
// Socket clientSocket = new Socket (args[0], 45678);
// ODER; unconnected Socket kreieren und DANACH connect()en:
InetSocketAddress serverInetSocketAddress =
new InetSocketAddress("localhost",45678);
Socket clientSocket = new Socket();
clientSocket.connect(serverInetSocketAddress);
System.out.println ("Verbindung zu Server mit der Adresse "
+ clientSocket.getInetAddress()
+ " hergestellt.");
//
//
//
//
//
//
client braucht in unserem Beispiel kein bind(), da er ja nicht über
den/einen Port angesprochen
werden soll; für peer-to-peer-Applikationen wirds gebraucht:
InetSocketAddress myInetSocketAddress =
new InetSocketAddress("localhost",45678);
clientSocket.bind(myInetSocketAddress);
BufferedWriter out = new BufferedWriter( // zum Schreiben auf Socket
new OutputStreamWriter(clientSocket.getOutputStream()));
// bridge from text to character streams to byte streams
BufferedReader in = new BufferedReader( // zum Lesen von Console
new InputStreamReader (System.in));
do
{
System.out.print ("Nachricht an Server: ");
s = in.readLine(); // Lesen von Keyboard
out.write (s);
// Schreiben auf den Client-Socket
out.newLine();
out.flush();
}
while ( !s.equals ("ende") && clientSocket != null);
in.close();
out.close();
}
catch ( Exception e )
{
e.printStackTrace();
System.exit(1);
}
}
}
Anmerkung: Dem Javaprogramm Kommandozeilenargumente mitgeben
Textpad: Konfiguration -> Einstellungen -> Extras -> Java Programm starten ->Parameterabfrage
Eclipse: Run -> Open Run Dialog
8.4 Was kommt den da im Strom? Sockets und Streams
Was die Kommunikationspartner über die Sockets austauschen sind Byte-Streams, geliefert von den SocketMethoden getOutputStream() und getInputStream().
Was in den Streams „drinsteckt“, wissen die Kommunikationspartner:
Wird Text über die Sockets übertragen, so wird der Stream in BufferedReader und BufferedWriter unter
Zuhilfenahme der Bridge-Klassen OutputStreamWriter und InputStreamReader überführt.
Werden binäre Daten über die Sockets übertragen, so wird der Stream in DataInputStream und
DataOutputStream überführt.
Bei serialisierten Objekten verwendet man dagegen ObjectInputStream und ObjectOutputStream.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
89 / 210
23.03.2010
PROMOD-2a / SS10
In unseren Beispielen oben übertragen wir Text über Sockets und nutzen deshalb die Bridge-Klassen
Bild: ServerSockets und Client-Sockets.
Über ServerSockets wird der Kontakt aufgenommung. Kommuniziert wird über die Client Sockets:
Die Kommunikation in der Case Study
ServerSocket
Server
clientSocket.connect(serverInetSocketAddress);
ClientSocket
ClientSocket
ClientSocket
ServerSocket
ClientSocket
Server
(ggf. multi-threaded)
ClientSocket = accept()
for(;;)
getInputStream() bzw.
getOutputStream()
gibt es nur für
ClientSocket
Client Sockets
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu
diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
90 / 210
23.03.2010
PROMOD-2a / SS10
9. RMI Remote Method Invocation
Was Sie lernen:
wie mit Java auf entfernte Objekte zugegriffen wird
wie Client- und Server-Programme mittels verteilter Objekte programmiert werden
Voraussetzungen:
Arbeiten mit Interfaces
Arbeiten mit den Tools
o RMI-Registry-Tool: rmregistry
o RMI-Compiler: rmic
o Policytool für den Sicherheitsmanager: policytool
(Hinweis: Das Kapitel basiert auf dem Buch J. Anton Illik: „Verteilte Systeme – Architekturen und SoftwareTechnologien“, Expertverlag, Stuttgart-Renningen, 2007.
Im Buch wird die RMI-Variante unter Java VM-Version < 5 und JDK 1.4 beschrieben. Hier im
Skript beschreiben wir „new RMI“ ab VM-Version 5 zusammen mit dem JDK 1.6.)
9.1 Grundlagen
RMI (remote method invocation) ist die Java-Implementierung des „Remote
Procedure Calls“ (RPC). Der Begriff „method invocation“ ist im Java-Umfeld
natürlich treffender, da im Zusammenhang mit Java die Methoden eines
entfernten Objekts aufgerufen werden. Abgesehen davon, sind viele Details,
die im Zusammenhang mit dem Methodenaufruf stehen, wie beispielsweise Parameter- und
Returnwert-Übergabe, mit den gleichen Problemen behaftet, wie beim RPC-Mechanismus (siehe
Buch „Verteilte Systeme“, S. 94). Glücklicherweise passen damit auch die für den RPC
kennengelernten Lösungsverfahren (wie z.B. der Einsatz von Stubs für das
Marshalling/Unmarshalling).
RMI impliziert also für die Entwicklung verteilter Anwendungen einen Entwurf, der auf die
Verteilung von Objekten setzt, lokale Objekte kommunizieren mit entfernten Objekten.
Das entfernte Objekt kann dabei entweder auf einem entfernten Rechner residieren oder aber
auch auf dem gleichen Rechner gehostet werden wie das aufrufende Objekt – entscheidend dabei
ist, dass das lokale und das entfernte Objekt in zwei verschiedenen virtuellen Maschinen
(virtual machine10) laufen.
Der Aufruf der Methode eines entfernten Objekts sieht dabei genauso aus, wie der Aufruf einer
Methode von einem lokalen Objekt. Im Gegensatz zum lokalen Methodenaufruf gibt es für den
entfernten Methodenaufruf allerdings mehr Exceptions11, da ja beispielsweise der entfernte
Methodenaufruf durch einen Verbindungszusammenbruch scheitern kann.
10
Java-Programme werden vom Java-Compiler in den Byte-Code übersetzt, der vom Prozessor eines Computers
nicht direkt ausgeführt wird. Die Virtual Machine ist die Ablaufumgebung für die in den Byte-Code übersetzten JavaProgramme. Diese Ablaufumgebung sorgt für die Interpretation des Byte-Codes.
11
Durch die Anzeige einer Exception signalisiert die Virtual Machine der Applikation eine Ausnahmesituation. Auf
eine angezeigte Ausnahmesituation muss die Applikation reagieren, andernfalls wird die Applikation von der Virtual
Machine abgebrochen..
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
91 / 210
23.03.2010
PROMOD-2a / SS10
Alle notwendigen RMI-Bestandteile (Klassen, Interfaces, RMI-Compiler, RMI-Registry, usw.) sind
bereits in der Java Standard Edition JSE enthalten, so dass der Java-Entwickler bereits ohne
den Einsatz von JEE (Java Enterprise Edition) einfache verteilte Applikationen entwickeln kann.
Eine weitere Ursache, dass RMI bereits in JSE enthalten ist, liegt darin, dass für die Entwicklung
verteilter Java-Applikationen auf der Basis von RMI keine Application Server notwendig sind,
wie es sonst bei anderen JEE-Mechanismen (Enterprise Java Beans, Servlets, JSP) der Fall ist.
9.2 Die Bestandteile einer RMI-Anwendung
Eine RMI Anwendung besteht im Wesentlichen aus folgenden Bestandteilen:
•
•
•
•
•
•
•
einem RMI-Client (= der Teil des Anwendungsprogramms, der die zentral vom RMIServer zur Verfügung gestellte Funktionaliät nutzt),
Stub-Klassen auf der Client-Seite12,
einem RMI-Server (= der Teil des Anwendungsprogramms, der den RMI-Clients
zentrale Applikationsfunktionen zur Verfügung stellt),
Skeleton-Klassen auf der Server-Seite (in den jüngeren Java-Releases ist das Skeleton
identisch mit dem Stub; Stub auf der Server-Seite wird Skeleton genannt) und
einer RMI-Registry (ist ein Service, bei dem der RMI-Server (zur Laufzeit) die Objekte
registriert, die von RMI-Clients genutzt werden können). Das Programm rmiregistry liegt in
java/bin des JDK.
Wenn der RMI-Server (bzw. eines seiner bei der RMI-Registry registrierten Remote
Objekte) nicht skalare Ergebnisse zurückliefern muss, kommt auch noch der
RMISecurityManager ins Spiel (für den Austausch serialisierter Objekte).
Der RMISecurityManager braucht dann ein Policy-File (dies heißt per default .java.policy,
kann vom Entwickler aber auch anderst benannt werden) in dem die Grants (= die
gewährten Zugriffsrechte) verzeichnet sind. Hergestellt, bzw. bearbeitet wird das Policy-File
mit dem Policytool. Das Programm policytool liegt in java/bin des JDK.
Auf der Client-Seite kümmert sich der Stub um die eigentliche Kommunikation mit der
Serverseite. Ein Stub-Objekt ist ein client-seitiger Stellvertreter für das entfernte Objekt im RMIServer. Im RMI-Server steht dem Stub als konkreter Kommunikationspartner ein entsprechendes
Skeleton-Objekt13 zur Verfügung.
Ruft ein RMI-Client eine Methode von einem entfernten Objekt auf, so wird der Aufruf an das
Stub-Objekt delegiert, das dann für die Weiterleitung des Methodenaufrufs an das entfernte
Objekt sorgt. Dort kümmert sich als serverseitiger Vertreter für das aufrufende Objekt das
Skeleton-Objekt um den Aufruf der gewünschten Methode. Stub und Skeleton übernehmen
jeweils auf ihrer Seite die Abwicklung der Kommunikation (z.B. auch das Marshalling, bzw.
Unmarshalling).
12
Es besteht auch die Möglichkeit, sich die vom Client benötigte Stub-Klassen dynamisch vom Server geben zu
lassen. Diesen Ansatz werden wir hier zunächst nicht weiter verfolgen.
13
Das Skeleton-Objekt ist notwendig, wenn mit der Java-Entwicklungsversion JDK1.1 gearbeitet wird. In den
Nachfolgeversionen wurde das Skeleton-Objekt auf der Serverseite durch das Stub-Objekt ersetzt, das auch auf der
Client-Seite Verwendung findet.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
92 / 210
23.03.2010
PROMOD-2a / SS10
Weitergabe
Client
Datenstrom weiterleiten
Stub
Aufruf der Methode
Stub
Aufruf einer
Marshalling der
entfernten Methode
Parameter
Remote-Objekt
Unmarshalling der
Parameter
Bild 1: Die Rolle von Stub und Skeleton
Die Rolle der RMI-Registry
Ein RMI-Client, der einen bestimmten Dienst aufrufen möchte, fragt zunächst bei der RMIRegistry nach, welches entfernte Objekt dafür das richtige ist und bekommt als Antwort eine
entsprechende Objektreferenz auf das entfernte Objekt im Server zurück. Die Adresse der
zuständigen RMI-Registry muss der Client kennen (URL + Port (1099 per default)). Die RMIRegistry muss auf demselben Rechner laufen, auf dem das Remote-Objekt ausgeführt wird.
Der RMI-Server dient dazu, ein entferntes Objekt zu instantiieren und es unter einem bestimmten
Dienstenamen in der RMI-Registry zu registrieren. Das Programm rmiregistry liegt in java/bin
des JDK. Per default ist die RMI-Registry an den Port 1099 gebunden.
9.3 Das Ablaufschema eines entfernten Methodenaufrufs
Im Folgenden geben wir schematisch den Ablauf eines entfernten Methodenaufrufs wieder und
zeigen dabei, welche Rolle die einzelnen Bestandteile der RMI-Anwendung spielen. Danach
besprechen wir die für die Herstellung notwendigen Werkzeuge. In einem abschließenden
konkreten Beispiel werden die Details dann auf der Ebene des Codes beleuchtet.
Wir gehen bei der Beschreibung unten davon aus, dass die verteilte RMI-Applikation hergestellt
und bereits auf die entsprechenden Rechner verteilt ist. Beim Start der verteilten Applikation läuft
dann folgendes ab.
1. Schritt: Der RMI-Server registriert ein entferntes Objekt bei der RMI-Registry unter einem
eindeutigen Namen. (In unserem Beispiel unten ist die das Server-Programm
HelloServer.java)
RMI-Server-Host
RMI-Registry
RMI-Server
Prof. Illik
J
V
M
B
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
93 / 210
23.03.2010
PROMOD-2a / SS10
Bild 2: Der RMI-Server registriert ein Objekt in der RMI-Registry
2. Schritt: Der Client (in unserem Beispiel heißt der Client HelloClient) schaut bei der RMIRegistry unter dem Namen (in unserem Beispiel lautet der Objekt-Name für das RemoteObjekt HelloImpl) nach und bekommt im positiven Fall eine Objektreferenz, die seinem
RMI-Client-Host
RMI-Server-Host
RMI-Registry
RMI-Client
Remote Interface entsprechen muss (siehe Bild 3).
Bild 3: Die RMI-Registry liefert die Objektreferenz zurück
3. Schritt: Der Client (HelloClient) ruft die gewünschte Methode aus der Objektreferenz auf.
4. Schritt: Der Server (HelloServer) gibt dem Client die Rückgabewerte14 dieses Aufrufs, oder
der Client bekommt eine Fehlermeldung (bei einem Verbindungsabbruch, beispielsweise.)
Das Bild 49 fasst die Abläufe nochmals insgesamt zusammen:
RMI-Client-Host
J
V
M
RMI-Server-Host
RMI-Client
RMI-Registry
Stub
RMI-Server
A
OS A
Skeleton
J
V
M
B
OS B
Bild 4: RMI Gesamtablauf
9.4 Die Hilfsmittel RMI-Compiler und RMI-Registry
Die Aufgabe des RMI-Compilers15 rmic ist die Erzeugung von Stub (und Skeleton) aus den
Klassendateien. Für eine Klasse, die das Interface Remote implementiert, erzeugt der RMICompiler rmic die benötigten Stubs. Standardmäßig werden die erzeugten Stubs in demselben
14
Ist der Rückgabewert skalar, so ist kein RMISecurityManager notwendig.
Note: If the server needs to support clients running on pre-5.0 VMs, then a stub class for the remote object
implementation class needs to be pregenerated using the rmic compiler, and that stub class needs to be made
available for clients to download. See the codebase tutorial for more details. Siehe:
http://java.sun.com/javase/6/docs/technotes/guides/rmi/index.html
15
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
94 / 210
23.03.2010
PROMOD-2a / SS10
Verzeichnis abgelegt, wie die zugrunde liegenden Klassendateien. Für den RMI-Compiler gibt es
eine Reihe von Optionen; u.a. ist damit auch möglich, Stubs in anderen Verzeichnissen ablegen
zu lassen.
9.5 Der RMISicherheitsmanager (RMISecurityManager)
Der RMISecurityManager legt fest, welche Rechte „fremde“ Klassen (remote Klassen) an lokale
Klassen haben. Um ein gewisses Maß an Sicherheit zu gewährleisten, sollte jedes RMI-ServerProgramm, einen RMISecurityManager haben, ansonsten können nur Klassen aus dem lokalen
classpath verwendet werden.
Um einem Programm die passenden Rechte zu geben, werden die zugesicherten Rechte in einer
Rechte-Datei gesammelt, der Policy-Datei.
Wenn die Laufzeitumgebung (virtual machine) gestartet wird, wird ein Verweis auf diese PolicyDatei mitgegeben (mit der Option –D, also für unser Beispiel: $java –D.java.policy HelloServer
1099), damit der Security-Manager Berechtigungen vergeben kann. Die Policy-Dateien bestehen
aus einer Reihe von grant-Anweisungen.
Das grafische Dienstprogramm policytool (siehe Bild 5: Das policytool) bietet die Möglichkeit,
Applikationen und signierten Applets spezielle Rechte einzuräumen oder zu verweigern. Das
Policy-Tool nimmt dem Entwickler die Arbeit ab, die Rechte-Dateien von Hand zu editieren.
Bild 5: Das policytool (aus dem JDK-Verzeichnis java/bin)
Nach dem Aufruf des Programms policytool öffnet sich ein Fenster, das uns einige Menüpunkte
bereitstellt, über die man bestehende Rechte-Dateien editieren oder auch neue anlegen kann.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
95 / 210
23.03.2010
PROMOD-2a / SS10
Neue Einträge für die Zugeständnisse der Laufzeitumgebung an das Programm werden über das
Menü Add Policy Entry („Richtlinieneintrag hinzufügen“) gemacht. Folgende Tabelle zeigt einige
Permissions und ihre Bedeutungen
Policy Permissions
Bedeutung
AllPermission
Anwendung oder Applet dürfen alles
FilePermission
Zugriff auf Dateien und Verzeichnisse
NetPermission
Zugriff auf NetzwerkRessourcen
PropertyPermission
Zugriff auf Systemeigenschaften
ReflectPermission
Zugriff über Reflection auf andere Objekte
RuntimePermission
Einschränkungen von Laufzeitsystemen
SecurityPermission
allgemeines Sicherheitskonzept, etwa für den
Zugriff auf Policies
SerializablePermission Beschränkung der Serialisierung
SocketPermission
Spezielle Einschränkungen an Sockets
Bild 6: Sicherheitseinstellungen (Permissions)
Aus welchen Bestandteilen besteht nun der RMI-Client, bzw. der RMI-Server in unserem
Beispiel?
9.6 Die Komponenten und Bestandteile auf der Server-Seite
Auf der Server-Seite liegen:
• Das Remote Interface des entfernten Objekts (remote Interface), welches das JavaInterface remote erweitert. (In unserem Beispiel unten das interface Hello; File:
Hello.java)
• Das entfernte Objekt steckt in unserem Beispiel in Server-Programm (File:
HelloServer.java).
• Stub und Skeleton für das entfernte Objekt ist das generierte File:
HelloImpl_Stub.class. (Wird durch den RMI-Compiler rmic erzeugt: $rmic HelloImpl)
• Der Server, der das entfernte Objekt instanziiert und bei der RMI-Registry anmeldet. In
unserem Beispiel unten HelloServer; (File: HelloServer.java).
• Die Policy-Datei. In unserem Beispiel unten die Datei .java.policy. (Per default heißt
die Policy-Datei üblicherweise so und liegt üblicherweise im $HOME-Verzeichnis und
enthält mehrere Policies). Bei uns liegt die Datei im Verzeichnis des Servers. Die
Sicherheitseinstellungen müssen der laufenden VM ($java-Aufruf) bekannt sein, damit sie
die Sicherheitsüberwachung machen kann. Die Policy-Datei kann im Verzeichnis des
Servers liegen und muss mit der Option –D beim java-Aufruf mitgegeben werden. In
unserem Beispiel: $java –D.java.policy HelloServer)
• Die RMI-Registry. Wird auf der Server-Seite gestartet (manuell oder vom Betriebssystem
als Service: $rmiregistry, bzw. unter Angabe des Ports, z.B. $rmiregistry 1099).
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
96 / 210
23.03.2010
PROMOD-2a / SS10
9.7 Die Komponenten und Bestandteile auf der Client-Seite
Auf der Client-Seite liegen:
• Das Interface des entfernten Objekts, welches das Java-Interface remote erweitert. (In
unserem Beispiel unten das interface Hello)
• Der Stub16 für das entfernte Objekt. In unserem Beispiel unten ist dies
HelloImpl_Stub.class. (Ist ab Java VM-Version 5 nicht mehr notwendig.)
• Der Client, der das entfernte Objekt nutzen möchte. In unserem Beispiel unten ist dies
HelloClient.
9.8 Vollständiges RMI-Beispiel
Im Folgenden Beispiel werden die oben genannten Bestandteile konkret vorgestellt und wichtige
Details erläutert.
Entfernte Methoden werden durch das Interface java.rmi.Remote definiert:
// Java-Beispiel: Das remote Interface und die entfernte Methode sayHallo()
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote{ // Liegt auf der SERVERSEITE.
// Liegt ebenfalls auf der CLIENTSEITE.
public String sayHello (String language) throws RemoteException;
}
// das Interface gibt vor, wie die Signatur von sayHello() aussieht
// sayHello() gibt einen String zurückgibt
♣
Bild 7: Das remote Interface Hello.java
Das erweiterte remote-Interface wird dann implementiert um als entferntes Objekt genutzt zu
werden.
Server-Programme HelloServer.java implementieren also das Interface Remote und erweitern
java.rmi.server.UnicastRemoteObject, welches die wichtigsten Methoden für die Verwendung
von RMI bereitstellt.
Bis jetzt haben wir:
•
•
•
Ein Remote-Objekt samt Interface (Hello), dessen Methoden von einer entfernten
JVM aufgerufen werden können. Das Programm HelloServer.java meldet das
Remote Object bei der Registry an.
Ein Client-Programm, das einen solchen Aufruf vornimmt.
Die Stub-Generierung mit rmic ist ab Java VM-Version 5 nicht mehr notwendig
16
Wenn der Stub zur Laufzeit nicht auf der Client-Seite vorhanden ist, wird er beim Name-Binding vom Server
zurückgegeben.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
97 / 210
23.03.2010
PROMOD-2a / SS10
Für eine Klasse, die das Interface Remote implementiert, erzeugt der RMI-Compiler rmic die
benötigten Stubs und Skeletons:
$javac Hello.java
$javac HelloClient.java
// bis Version 5, danach entfällt der rmic-Einsatz
$rmic HelloImpl # erzeugt HelloImpl_Stub.class // bis Version 5
Für Interessierte: Durch rmic -keepgenerated HelloImpl kann man die erzeugten Stubund Skel-Klassen als Java-Quelltext anschauen.
Wie bekommt ein Client Zugriff auf ein Hello-Objekt?
Wir wissen: Stub für Client und Server.
•
•
•
Der Server muss den Stub unter einem (eindeutigen) Namen bei einer rmiregistry
anmelden.
Diese rmiregistry muss auf demselben Rechner laufen, auf dem das Remote-Objekt
(Stub) ausgeführt wird!
Der Client muss
1. Über die Klasse LocateRegistry und die Methode getRegistry(host) sich auf
dem Remote Host eine Referenz auf das Remote Object besorgen
2. den Namen des Objekts kennen, unter dem der Stub bei der Registry bekannt
ist.
// Java-Beispiel: Ein RMI-Server. File: HelloServer.java (Variante 1)
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
public class HelloServer implements Hello {
public HelloServer() {};
public String sayHello (String language) throws RemoteException
{
String lang = language.toLowerCase();
switch (lang.charAt (0))
{
case'd':return "Hello!";
case'f':return "Salut!";
case'i':return "Ciao!";
default: return"Hi!";
} // end switch
} // end sayHello()
public static void main(String[] args)
{
try {
HelloServer obj = new HelloServer();
Hello stub = (Hello)UnicastRemoteObject.exportObject(obj, 0);
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
98 / 210
23.03.2010
PROMOD-2a / SS10
A2:
// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.getRegistry();
registry.bind("Hello", stub);
System.out.println("HelloServer ready.");
} // end try
catch (Exception e)
{
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
} // end catch
} //end main()
} // end class HelloServer
♣
Bild 8: Die Klasse HelloServer.java (Variante 1)
// Java-Beispiel: Ein RMI-Server. File: HelloServer.java (Variante 1)
//
import
import
import
import
import
java.rmi.*;
java.rmi.server.*;
java.rmi.registry.*;
java.io.*;
java.net.*;
public class HelloServer implements Hello {
public HelloServer() {};
public String sayHello (String language) throws RemoteException
{
String lang = language.toLowerCase();
InetAddress serverInetAddr;
// just to communicate the servers IP+name to the client
String hostname;
// just to communicate the servers IP+name to the client
try {
serverInetAddr = InetAddress.getLocalHost();
hostname = serverInetAddr.getHostAddress()
+ " "
+ serverInetAddr.getHostName();
System.out.println(hostname);
switch (lang.charAt (0))
{
case'd':return "Hello!" + " vom Server " + hostname;
case'f':return "Salut!" + " vom Server " + hostname;
case'i':return "Ciao!" + " vom Server " + hostname;
default: return"Hi!"+ " vom Server " + hostname;
} // end switch
}
catch (Exception ex){return "ERROR"; }
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
99 / 210
23.03.2010
PROMOD-2a / SS10
} // end sayHello()
public static void main(String[] args)
{
try {
HelloServer obj = new HelloServer();
Hello stub = (Hello)UnicastRemoteObject.exportObject(obj, 0);
// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.getRegistry();
registry.bind("Hello", stub);
System.out.println("HelloServer ready.");
} // end try
catch (Exception e)
{
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
} // end catch
} //end main()
} // end class HelloServer
♣
Bild 9: Die Klasse HelloServer.java (Variante 2: Server sendet seine IP und seinen Namen mit der Antwort zum Client)
Erläuterungen zu den Marken im RMI-Server-Beispiel:
A1: Ein RMISecurityManager wird eingerichtet (Siehe API-Doku: „RMI's class loader will not
download any classes from remote locations if no security manager has been set”) (fehlt hier
noch)
A2: Aus der API-Doku: Note that a getRegistry call does not actually make a connection to the
remote host. It simply creates a local reference to the remote registry and will succeed even
if no registry is running on the remote host.“ U.U. würde der Server die Registry selbst
starten. Siehe hier zu API-Doku.
// Java-Beispiel: Ein RMI-Client
import
import
import
import
java.rmi.*;
java.rmi.server.*;
java.rmi.registry.LocateRegistry;
java.rmi.registry.Registry;
public class HelloClient {
private HelloClient() {};
static public void main (String[]args)
{
String host = (args.length<1) ? null :args[0];
try{
Registry registry = LocateRegistry.getRegistry(host);
Hello stub = (Hello) registry.lookup("Hello");
System.out.println (stub.sayHello("Englisch"));
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
100 / 210
23.03.2010
PROMOD-2a / SS10
System.out.println (stub.sayHello("Deutsch"));
System.out.println (stub.sayHello("Italienisch"));
System.out.println (stub.sayHello("Franzoesisch"));
}
catch (Exception e)
{
System.err.println ("HelloClient failed, caught exception" +
e.getMessage());
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
} // end main()
} // end class
♣
Bild 10: Die Klasse HelloClient.java
Das Beispiel in Betrieb nehmen
Insgesamt werden die Beispielprogramme übersetzt mit
$javac Hello.java HelloServer.java HelloClient.java
[Hinweis/Note: If the server needs to support clients running on pre-5.0 VMs, then a stub class for the remote object
implementation class needs to be pregenerated using the rmic compiler, and that stub class needs to be made available for clients
to download. See the codebase tutorial for more details.]
Um das Beispiel auszuführen:
• die Registry starten $rmiregistry oder via $start rmiregistry
[To start the registry, run the rmiregistry command on the server's host. This command produces no output (when
successful) and is typically run in the background. For more information, see the tools documentation for rmiregistry]
• den Server starten $java HelloServer
oder via $start java HelloServer
• den Client starten $java HelloClient
Siehe auch
„Getting Started Using Java RMI“
http://java.sun.com/javase/6/docs/technotes/guides/rmi/index.html -> „Getting Started“
Hinweis 1: auf Windows 7 machte die Ausführung bisher hartnäckig Probleme!
Hinweis 2: Auf Ubuntu 9.10 (Karmic Koala) läuft das Beispiel einwandfrei,
+) wenn Client und Server gemeinsam auf der Ubuntu-Maschine gehostet sind,
++) wenn Client auf Ubuntu mit Server auf Vista zusammenarbeitet
--) Es geht nicht: Server auf Ubuntu und Client auv Vista –> Client auf Vista crashed
mit „connection refused“
9.9 Zusammenfassung
RMI ist die objektorientierte Variante von RPC und exklusiv auf Java abgestimmt. Daraus
resultiert eine mangelnde Integrationsfähigkeit mit anderen Verteilungstechniken und
Programmiersprachen. Aus der Sicht der Programmierung ist einer der wesentlichen Vorteile,
dass bei nur geringem Initialisierungsaufwand lokale und entfernte Methodenaufrufe gleich
gestaltet sind. RMI versteckt alle Details der Netzwerkkommunikation vor dem Entwickler. Dieser
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
101 / 210
23.03.2010
PROMOD-2a / SS10
muss sich weder mit Fragen des Datenaustauschs über Sockets, noch mit Fragen zum Protokoll
auseinandersetzen.
Allerdings gilt es zu beachten, dass auch der parallele, gleichzeitige Zugriff auf entfernte Objekte
möglich ist und dass der Entwickler für solche Fälle die Zugriffssynchronisation selbst
übernehmen muss. Die Interaktion zwischen Client und Server selbst läuft synchron ab: ein
entferntes Objekt wartet, bis eine seiner Methoden aufgerufen wird. Während der Ausführung
einer Methode wartet wiederum der aufrufende Client bis die Methode fertig ausgeführt ist.
Um RMI in Betrieb zu nehmen und am Laufen zu erhalten sind einige wenige Werkzeuge und
Dienste notwendig. Ein einfach strukturierter Namensdienst, die RMI-Registry, besorgt die
(flache) Abbildung von Dienstenamen auf entfernte Objekte. (Der RMI-Compiler RMIC sorgt für
die automatische Stub- und Skeleton Generierung aus den entsprechenden Klassendateien.)
Java RMI kommt ohne Middleware aus, weil RMI, wie oben erwähnt, exklusiv auf Java
abgestimmt ist.
9.10 Kontrollfragen (Technologien / RMI)
Frage J1:
Frage J2:
Frage J3:
Frage J4:
Frage J5:
Prof. Illik
Worin besteht der konzeptionelle Unterschied zwischen RMI und RPC?
Welche Aufgaben haben Client-Stub und Server-Skeleton im Rahmen von RMI zu
lösen?
Welche Rolle spielt die RMI-Registry?
Welche Rolle spielt der RMI-Compiler?
Was hat der Sicherheitsmanager bei RMI zu leisten?
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
102 / 210
23.03.2010
PROMOD-2a / SS10
10. AWT-Basics: Graphical User Interface
Was Sie lernen:
wer die wichtigen Mitglieder der AWT-Klassenhierarchie sind
wie Fenster erzeugt und angezeigt werden
wie Fenster geschlossen werden
was Ereignisse sind
wie und welche Ereignisse empfangen werden können
Voraussetzungen:
Arbeiten mit Klassen
10.1 Grundlagen
AWT steht für "abstract windowing toolkit"; verfügbar seit Java Version 1.0
Notwendig : import java.awt.* (siehe API-Doku)
AWT (package java.awt) nutzt das native GUI des darunterliegenden OS („heavyweight GUI components“),
d.h. es nimmt das jeweilige native "look and feel" an - im Unterschied zu SWING („Bestandteil der JFC Java
Foundation Classes“, package javax.swing), welches das native GUI nicht nutzt („lightweight GUI
components“). Weitere Alternative: SWT („Standard Widget Toolkit“, IBM, 2001 / -> Eclipse), als „best of both
worlds“
Das Peerkonzept des AWT: das Peer-Widget zeichnet sich selbst
Funktionalität von AWT
o
Anzeigen von Bitmaps, Ausgaben von Sound
o
Zeichnen primitiver geometrischer Objekte (Linie, Kreis, Rechteck,...) und füllen dieser Objekte
o
Ausgabe von Schriften
o
bietet Komponenten für den Dialog mit dem User: Fenster, Schaltfläche, Textfelder, Menüs, …
o
Handling von Ereignissen (Events), welche durch Umgang mit Tastatur und Maus (z.B. Klicken auf
Schaltfläche) oder Operationen auf Komponenten ausgelöst werden (z. B. Fenster schließen)
Für die Darstellung grafischer Elemente, wie z.B. Schaltflächen, Textfelder, usw., werden spezielle Klassen
verwendet Æ Java-Beans (Das sind Klassen, für die bestimmte syntaktische Regeln einzuhalten sind.) JavaBeans werden auch als Komponenten oder Widgets bezeichntet.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
103 / 210
23.03.2010
PROMOD-2a / SS10
Unterschied zwischen AWT und Swing: AWT nutzt nur die nativen Elemente, die auf allen Plattformen
vorhanden sind, d.h. AWT-Komponenten sind auf diese überall verfügbaren nativen Elemente beschränkt. Die
SWING-Bibliothekt ist umfangreicher, da sie nicht die nativen Elemente des Betriebssystems nutzt, sondern alle
Komponenten selbst implementiert („Java zeichnet die Widgets selbst“).
o
Swing ist die GUI-Komponente in JFC Java Foundation Classes; im package javax.swing sind
noch darüber hinaus gehende Funktionalitäten enthalten:
ƒ Das Look & Feel ist zur Laufzeit veränderbar, ohne Neustart des Programmes
ƒ Barrierefreier Zugang (zur Programmbedienung) wird unterstützt (alternative
Interaktionstechniken für Menschen mit Handycap; Spracherkennung, Lesegeräte für Blinde,
Braille-Schrift, etc.)
ƒ Drag & Drop zum Datenaustausch zwischen Programmen (nicht notwendigerweise JavaProgramme)
ƒ Sehr gute 2D Bibliotheken (vergleichbar mit dem Postscript-Ansatz)
Gemeinsamkeit von AWT und Swing: Die zugrunde liegende Technik, wie die Ereignisbehandlung, Arbeiten
mit Formen, Farben, Schriften und Umgang mit den Layoutmanagern (für die Anordnung der Elemente in
einem Fenster) sind bei AWT und SWING gleich.
Hinweis zur Übersetzung und Ausführung von GUI-basierenden Java-Programmen
Bei Java-Installationen unter Windows gibt es im JDK zwei Java-Interpreter:
java.exe zeigt ein Konsolfenster an. Über dieses Konsolfenster haben wir bisher die I/O getätigt
javaw.exe zeigt kein Konsolfenster an. Ausgaben über System.out/err sind damit nicht mehr sichtbar.
Fertig entwickelte und geteste GUI-basierte Java-Applikationen werden am Besten mit javaw.exe gestartet. Während
der Entwicklung und des Tests ist aber java.exe nach wie vor nutzbar.
Im Textpad muss die Nutzung von javaw.exe erst konfiguriert werden: „Konfiguration“ -> „Einstellungen“ ->
„Extras“ -> „Hinzufügen“: jetzt alle Einstellungen so vornehmen wie in der Einstellung „Java Programm
starten“ und statt java.exe nun javaw.exe einsetzen. Eclipse erkennt GUI-Programme und startet diese
unmittelbar mit javaw.exe.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
104 / 210
23.03.2010
PROMOD-2a / SS10
10.2 AWT – Klassenhierarchie
java.lang.Object
java.util.EventObject
java.awt.AWTEvent
java.awt.Component
java.awt.Container
Graphics
...Choice
...Label
...Button
...List
…Canvas
java.awt.Dialog
javax.swing.JDialog
java.awt.Window
javax.awt.Panel
java.awt.Frame
javax.applet.Applet
javax.swing.JFrame
javax.swing.JComponent
javax.swing.Label
…Scrollbar
…Checkbox
…TextComponet
java.applet.Applet
javax.swing.JApplet
Bild: Die AWT-Klassenhierarchie17
Im Folgenden betrachten wir zunächst das Wesentliche aus den Klassen Frame, Window, Container und
Component, soweit es notwendig ist, um mit Fenstern umzugehen.
Danach machen wir einen Abstecher zur Grafikprogrammierung und der Klasse Graphics:
Kapitel „Grafikprogrammierung...“
Im Anschluß daran kümmern wir uns um Komponenten, also die Klassen Button, Choice bis TextCompontent:
Kapitel „AWT-Komponenten“
17
In dieser Klassenhierarchie fehlt komplett die Klasse java.awt.Menu. Siehe API Doku!
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
105 / 210
23.03.2010
PROMOD-2a / SS10
10.3 „Fenster“: von der Component über den Container zum Frame
Klasse Component:
übernimmt/tätigt
- Festlegen von Größe und Position einer Komponente (Schaltfläche, Eingabefeld,...)
- Festlegen von Farbe und anderen grafischer Eigenschaften (Farbe, 2D, 3D,...)
- Befähigt Komponenten Ereignisse zu empfangen
Definition der Klasse Component aus der API Doku:
A component is an object having a graphical representation that can be displayed on the screen and that can
interact with the user. Examples of components are the buttons, checkboxes, and scrollbars of a typical
graphical user interface.
The Component class is the abstract superclass of the nonmenu-related Abstract Window Toolkit
components. Class Component can also be extended directly to create a lightweight component. A lightweight
component is a component that is not associated with a native opaque window.
Klasse Container: (abgeleitet von Component)
übernimmt/tätigt
- Aufnahme von Komponenten...
- Entfernen von Komponenten...
- Anordnen von Komponenten unter Nutzung eines LayoutManagers
Definition der Klasse Container aus der API Doku:
A generic Abstract Window Toolkit (AWT) container object is a component that can contain other AWT
components.
Components added to a container are tracked in a list. The order of the list will define the components' front-toback stacking order within the container. If no index is specified when adding a component to a container, it will
be added to the end of the list (and hence to the bottom of the stacking order).
Klasse Window und Panel: (abgeleitet von Container)
Die Klasse Window erzeugt Top-Level-Fenster ohne Rahmen, Menü, Titelleiste.
o Eine Anwendung müsste die fehlenden Teile selbst zeichnen
o Die Objekte der Klasse haben keine (wie bei Fenstern) übliche Funktionalität. Diese muss
vollständig in der Anwenung implementiert werden.
-
Definition der Klasse Window aus der API Doku:
A Window object is a top-level window with no borders and no menubar. The default layout for a window is
BorderLayout.
A window must have either a frame, a dialog, or another window defined as its owner when it's constructed.
In a multi-screen environment, you can create a Window on a different screen device
-
Die Klasse Panel dient der Aufnahme anderer Komponenten, einschließlich anderer Panels.
o Die Klasse dient also der Zusammenfassung von mehreren Komponenten zu einer Gruppe.
o Für jedes Panel kann ein eigener LayoutManager zur Anordnung der Komponenten verwendet
werden.
Definition der Klasse Panel aus der API Doku:
Panel is the simplest container class. A panel provides space in which an application can attach any other
component, including other panels.
The default layout manager for a panel is the FlowLayout layout manager.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
106 / 210
23.03.2010
PROMOD-2a / SS10
10.4 Klasse Window
Siehe Java 2 Platform API Specification:
public class Window
extends Container
implements Accessible
A Window object is a top-level window with no borders and no menubar. The default
layout for a window is BorderLayout.
A window must have either a frame, dialog, or another window defined as its
owner when it's constructed.
In a virtual device multi-screen environment in which the desktop area could span
multiple physical screen devices, the bounds of all configurations are relative to the
virtual device coordinate system. The origin of the virtual-coordinate system is at the
upper left-hand corner of the primary physical screen. Depending on the location of
the primary screen in the virtual device, negative coordinates are possible, as shown
in the following figure.
The following code sets the location of a Window at (10, 10) relative to the origin of
the physical screen of the corresponding GraphicsConfiguration. If the bounds of the
GraphicsConfiguration is not taken into account, the Window location would be set at (10,
10) relative to the virtual-coordinate system and would appear on the primary
physical screen, which might be different from the physical screen of the specified
GraphicsConfiguration.
Window w = new Window(Window owner, GraphicsConfiguration gc);
Rectangle bounds = gc.getBounds();
w.setLocation(10 + bounds.x, 10 + bounds.y);
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
107 / 210
23.03.2010
PROMOD-2a / SS10
Windows are capable of generating the following WindowEvents: WindowOpened,
WindowClosed, WindowGainedFocus, WindowLostFocus.
Windows können viele Listener zugeordnet warden (es gibt zahlreiche add..()-Listener-Methoden)
10.5 Klasse Panel: (abgeleitet von Container)
Siehe Java 2 Platform API Specification:
public class Panel
extends Container
implements Accessible
Panel is the simplest container class. A panel provides space in which an
application can attach any other component, including other panels.
The default layout manager for a panel is the FlowLayout layout manager.
…der dann mit einem LayoutManager verwaltet werden kann.
Panel können keine Listener zugeordnet warden (es gibt keine add..()-Listener-Methoden)
Case-Study: vergleiche in der API-Spezifikation die „Method Summary“ für
die Klassen Window und Panel.
10.6 Klasse Frame: (abgeleitet von Window)
Repräsentiert das Fenster, wie man es bspw. von MS Windows oder
Linux/KDE/Gnome oder Mac kennt.
o Besteht aus
ƒ einer Titelleiste,
ƒ einem Systemmenü
ƒ einem Rahmen,
ƒ Symbole zu minimieren, maximieren und schliessen.
Konstruktoren der Klasse Frame
public Frame()
public Frame(String title)
Ein Fenster mit leerer Titelleiste wird erzeugt.
Ein Fenster mit entsprechend gefülltem Titel wird
erzeugt.
In der Klasse Frame befinden sich viele Attribute (Größe, Position, Farbe des Fensters), die mit entsprechenden
get()- und set()-Methoden, die sich meist durch ihre Namen selbst erklären, gelesen bzw. gesetzt werden können.
Die meisten Methoden erbt die Klasse Frame von der Klasse Component, einige aber auch von den Klassen
Container und Window. (Sie API-Dokumentation)
Wichtige Methoden der Klasse Frame (…und ihrer Basisklassen)
public String getTitle()
Prof. Illik
Text in der
Gibt den Fenstertitel zurück,
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
108 / 210
23.03.2010
PROMOD-2a / SS10
public void setTitle(String
title)
public boolean isResizeable()
public void setResizable(boolean
resizable)
public Image getIconImage()
public void setIconImage(Image
image)
public int getState()
public void setState(int state)
public
public
mb)
public
m)
public
int y)
public
b)
public
MenuBar getMenuBar()
void setMenuBar(MenuBar
Titelleiste
bzw. setzt ihn
Veränderbarkeit
der
Fenstergröße
Fenstersymbol
Hat der Parameter resizable den Wert true, so
kann das Fenster in seiner Größe geändert
werden, sonst nicht.
Das Symbol, das beim Minimieren des Fensters
angezeigt wird, kann abgefragt, bzw. gesetzt
werden. (Das Minimieren von Fenstern wird nicht
von allen OS unterstützt.)
Der Status eines Fensters kann normal
(Frame.NORMAL; Standard) oder minimiert
(Frame.ICONIFIED) sein.
Die mit dem Fenster verbundene Menüleiste wird
zurückgegeben, bzw. gesetzt.
Die Methode remove() entfernt die verbundene
Menüleiste.
Fensterstatus
(normal oder
minimiert)
Menüleiste
void remove(MenuComponent
void setLocation(int x,
s.u.
- siehe Beispiel unten -
void setVisisble(Boolean
s.u.
- siehe Beispiel unten -
void dispose()
s.u.
- siehe Beispiel unten -
Î Weitere Methoden und Konstruktoren und Methoden siehe API – Documentation zum Package java.awt.
Beispiel – Fenster erzeugen, Attribute setzen und anzeigen
// Fenster1.java
import java.awt.*;
public class Fenster1
{
public static void main(String[] args)
{
Frame frame = new Frame();
// Erst Fenster “konfigurieren”
frame.setTitle("Mein erstes Java-Fenster");
frame.setSize(300,200);
frame.setLocation(350,300);
frame.setBackground(Color.blue);
}
//
//
//
//
//
Text für die Titelleiste
Fenstergrösse festlegen
Fensterposition festlegen
Fensterfarbe festlegen
von Component geerbt.
// Dann Fenster anzeigen
frame.setVisible(true);
}
Das Objekt der Klasse Frame besitzt schon einiges an Funktionalität, die Objekte der Klasse Window nicht
haben:
Das Minimier- und Maximierfeld funktioniert.
Das Fenster kann verschoben werden.
Das Fenster kann durch ziehen am Rahmen vergrößert / verkleinert werden.
Die Titelleiste nimmt eine andere Farbe an, wenn das Fenster aktiviert oder deaktiviert wird (Werte im OS
eingestellt).
Die Betätigung des Schließfeldes bleibt aber ohne Wirkung und muss erst programmiert werden. (Siehe
Beispiel unten.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
109 / 210
23.03.2010
PROMOD-2a / SS10
10.7 "Fensterbausteine" und der Umgang damit (Erzeugung / Konfiguration /
Anzeige)
Vorgehensweise: Fenster erzeugen, dann konfigurieren und danach anzeigen
Im folgenden Beispiel wird
die Initialisierung eines Frames von
dessen Anzeige getrennt.
Wichtige Fenster und Dialoge können auf dies Weise zuerst im Hintergrund erzeugt werden und dann bei Bedarf
angezeigt werden.
Beispiel – Fenster mit funktionierendem Schließfeld
// FrameBeispiel.java
import java.awt.*;
import java.awt.event.*;
public class FrameBeispiel extends Frame
{
Panel p = new Panel(); // Eine Panel wird im Frame eingefügt
public FrameBeispiel()
//Im Konstruktor wird lediglich die
{
// Konfiguration des Frames vorgenommen
this.setSize(150,150);
p.setBackground(Color.green); // Hintergrundfarbe des Panels ändern
this.add(p);
// Panel wird dem Frame zugeordnet
this.addWindowListener(new WindowAdapter() // Listener beim Frame registrieren
{
public void windowClosing(WindowEvent we)
{System.exit(0);}
});
}
public static void main(String [] args)
{
FrameBeispiel fB = new FrameBeispiel(); // Frame erzeugen via Konstrukor
fB.setVisible(true);
// Frame anzeigen
}
}
Hinweise:
Wir haben mit dem Beispiel etwas vorgegriffen. Dem Frame (Ereignisquelle) wird ein WindowListener
zugeordnet (Ereignisempfänger), der für den Aufruf einer Call-Back-Methode verantwortlich ist, sobald
bestimmte Ereignisse eintreffen. Die Call-Back-Methode sorgt dann für die richtige Reaktion auf das Ereignis.
Diese Technik (Ereignisbehandlung; Event Delegation Model) werden wir in der Folge betrachten.
Die Ereignisbehandlung ist hier mittels Verwendung einer Adapterklasse implementiert. (Methode „A“)
Wir werden später sehen, dass die Ereignisbehandlung auch durch die Implementierung eines ListenerInterfaces realisiert werden kann.(Methode „B“)
10.8 Anzeigen und schließen:
Bevor das Fenster auf dem physikalischen Schirm zur Anzeige gebracht wird (frame.setVisible(true)),
sind entsprechende Ressourcen im Speicher allokiert. Nach frame.setVisible(false) sind die
Ressourcen noch belegt. Erst nach frame.dispose() werden die belegten Ressourcen frei gegeben (um
die sich dann der Garbage Collector kümmert).
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
110 / 210
23.03.2010
PROMOD-2a / SS10
frame.setVisible(true)
frame.setVisible(false)
frame.dispose()
//Fenster anzeigen
//Fenster unsichtbar machen
//Ressourcen freigeben
Anwendung ist aber noch nicht beendet!
o System.exit(status) Î normal 0!!
Beispiel – Fenster verschwinden lassen
// Fenster2.java
import java.awt.*;
public class Fenster2
{
public static void main(String[] args)
{
Frame frame = new Frame();
// erzeugen
frame.setTitle("Mein erstes Java-Fenster"); // Konfigurieren
frame.setSize(300,200);
frame.setLocation(350,300);
frame.setBackground(Color.blue);
frame.setVisible(true);
// anzeigen
try{
Thread.currentThread().sleep(5000);
}catch(Exception ex){}
frame.setVisible(false);
// …
frame.dispose();
System.exit(0);
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
111 / 210
23.03.2010
PROMOD-2a / SS10
10.9 Bestandteile eines Fensters (class Frame)
Ein AWT-Fenster hat den im Folgenden gezeigten Grund-Aufbau. Auf den verschiedenen Plattformen
unterscheidet sich das Erscheinungsbild geringfügig.
Fenstersymbol
(icon)
Titel (title) in
Titelleiste
Minimier-, Maximier- und
Schließfeld (beeinflusst state)
Fensterposition (x,y)
bezieht sich auf die
linke obere Ecke
Hintergrund
(background)
Vordergrund
(foreground)
Fensterhöhe
(height)
Cursor
Fensterrahmen
(border)
Fensterbreite
(width)
In der folgenden Übersicht sind die Fensterbestandteile und die Methoden aufgelistet, über die ein Fenster
konfiguriert werden kann:
Eigenschaft
Titel
title
Fenstersymbol
icon
Größe
height x width
(von Compontent
geerbt)
Position
X,Y
(von Compontent
geerbt)
Position und Größe
Prof. Illik
Setzen der Eigenschaft
setTitle(String title)
Lesen der Eigenschaft
String getTitle()
setIconImage(image i)
Image getIconImage()
setSize(int width, int height)
Dimension getSize()
int getHeight()
int getWidth()
setLocation(Point p)
Point getLocation()
setLocation(int x, int y)
int getX()
int getY()
Rectangle getBounds()
setBounds(Rectangle r)
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
112 / 210
23.03.2010
PROMOD-2a / SS10
(von Compontent
geerbt)
Vordergrundfarbe
foreground
(von Compontent
geerbt)
Hintergrundfarbe
background
(von Compontent
geerbt)
Cursor
(von Window
geerbt)
setBounds(int x, int y, int width,
int height)
setForeground(Color c)
Color getForeground()
setBackground(Color c)
Color getBackground()
setCursor(Cursor c)
Cursor getCursor()
10.9.1 Das Fenstersymbol/Icon:
Das Bild für das Fenstersymbol sollte plattformspezifisch erstellt werden, da die Darstellung nicht auf allen
Plattformen gleich ist.
Vorgeschriebene Formate: GIF, JPEG oder PNG. Andere Formate werden nicht verstanden (siehe API-Doku).
z. B. Windows 16 x 16 Pixel (small Icon); 32 x 32 Pixel (large Icon).
Hinweis: JDK passt Größe automatisch an, wenn GIF / JPEG / PNG eine andere Auflösung haben.
Bild am Besten ins Verzeichnis der class-Datei. (Siehe Case-Study.)
Beispiel – Ein Icon nutzen
entweder Methode a):
// URL des Bildes laden
URL url = this.getClass().getRessource("./smile.gif");
// Icon laden (mit Methode aus der Klasse Toolkit))
Image icon = this.getToolkit().getImage(url);
// Icon dem Fenster zuweisen
frame.setIconImage(icon);
oder Methode b):
Image icon = this.getToolkit().getImage(“./smile.gif”);
frame.setIconImage(icon);
Für Textpad: Die gif-Datei muss in diesem Beispiel in dem Verzeichnis liegen, in dem die class-Datei des
Beispiels liegt.
Wenn mit Eclipse gearbeitet wird gelten ggf. andere Ablageorte (z.B. Projektordner)
Beispiel – Siehe fenster3.java mit Methode b)
import java.awt.*;
public class Fenster3
{
public static void main(String[] args)
{
Frame frame = new Frame();
frame.setTitle("Java-Fenster mit Icon");
frame.setSize(300,200);
frame.setLocation(350,300);
frame.setBackground(Color.blue);
Image icon = frame.getToolkit().getImage("smile.gif");
frame.setIconImage(icon);
frame.setVisible(true);
try{
Thread.sleep(10000);
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
113 / 210
23.03.2010
PROMOD-2a / SS10
}
}catch(Exception ex){}
frame.setVisible(false);
frame.dispose();
System.exit(1);
}
Beispiel – Siehe fenster4.java mit Methode a)
import java.awt.*;
import java.net.*;
public class Fenster4 extends Frame
{
URL url;
public static void main(String[] args)
{
Fenster4 fenster = new Fenster4();
}
public Fenster4()
{
super("Java-Fenster mit Icon");
this.setSize(300,200);
this.setLocation(350,300);
…
// im Programm unten komplettiert
…
//
try
{
url = this.getClass().getResource("smile.gif");
}
catch(Exception ex)
{
System.out.println(ex.getMessage());
}
Image icon = this.getToolkit().getImage(url);
this.setIconImage(icon);
this.setVisible(true);
try{
Thread.sleep(15000);
}catch(Exception ex){}
this.setVisible(false);
this.dispose();
System.exit(1);
}
}
10.9.2 Fensterposition und -Größe:
Die Position des Fensters bezieht sich immer auf die linke obere Ecke des Bildschirms. Die oben aufgeführten
Methoden zur Festlegung von Position und Ausdehnung können Objekte der Klassen Point, Dimension
und Rectangle als Parameter übergeben bekommen.
Die Objekte dieser Klassen werden erzeugt, indem Werte für deren Attribute dem jeweiligen Konstruktor
übergeben werden.
Point(int x, int y)
// Punkt x,y
Dimension(int width, int height)
// Breite, Höhe der Komponente
Rectangle(int x, int y, int width, int height) // Punkt x, y, Breite, Höhe
Z B.:
Point p = new Point(10,10);
setLocation(p);
10.9.3 Fensterfarben:
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
114 / 210
23.03.2010
PROMOD-2a / SS10
Zum Setzen von Vorder- und Hintergrundfarbe wird der entsprechenden Methode ein Color-Objekt übergeben.
Die Klasse Color kapselt die Farben des RGB (Rot-Grün-Blau)-Farbmodells (Zahlenwerte von 0 - 255 geben
Intensität des Farbanteils an)
Einige Farben sind vordefiniert: z. B.: black, blue, cyan, grey, darkGrey, lightGrey, magenta, orange,
pink, green, red, yellow.
Color.blue // Farbe blau
Color(int red, int green, int blue)
this.setBackground(new
this.setBackground(new
this.setBackground(new
this.setBackground(new
Prof. Illik
// “Farbmischung” durch Angabe der
// Intensität der RGB-Farbanteile
Color(255, 255, 255));
Color(0, 0, 0));
Color(255, 0, 0));
Color(200, 70, 150));
//
//
//
//
weißer Hintergrund
schwarzer Hintergrund
roter Hintergrund
rosa Hintergrund
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
115 / 210
23.03.2010
PROMOD-2a / SS10
10.9.4 Der Mouse-Cursor:
Der Mauszeiger (Cursor) kann innerhalb eines Fensters verschiedene Formen annehmen.
Form wird durch eine symbolische Konstante repräsentiert
Mit der Methode setCursor() lässt sich die Form des Cursors
ändern.
Symbolische Konstante
DEFAULT_CURSOR
CROSSHAIR_CURSOR
HAND_CURSOR
TEXT_CURSOR
MOVE_CURSOR
E_RESIZE_CURSOR
W_RESIZE_CURSOR
NE_RESIZE_CURSOR
SW_RESIZE_CURSOR
NW_RESIZE_CURSOR
SE_RESIZE_CURSOR
S_RESIZE_CURSOR
N_RESIZE_CURSOR
WAIT_CURSER
CURSOR-Form
Î Standardzeiger, meist als Pfeil
Î Kreuz
Î Handform
Î Einfügemarke
Î Positionsänderung
Î Größenänderung
Î Größenänderung
Î Größenänderung
Î Größenänderung
Î Größenänderung
Î Größenänderung
Î Größenänderung
Î Größenänderung
Î Sanduhr / Wartezustand
Aufgabe: Suchen Sie den
passenden Cursor
zu den oben genannten
Cursorformen.
import java.awt.*;
import java.net.*;
public class Fenster4 extends Frame
{
URL url;
public static void main(String[] args)
{
Fenster4 fenster = new Fenster4();
}
public Fenster4()
{
super("Java-Fenster mit Icon");
this.setSize(300,200);
this.setLocation(350,300);
this.setBackground(new Color(250,90,200));
this.setCursor(new Cursor(Cursor.HAND_CURSOR));
try
{
url = this.getClass().getResource("smile.gif");
}
catch(Exception ex)
{
System.out.println(ex.getMessage());
}
Image icon = this.getToolkit().getImage(url);
this.setIconImage(icon);
this.setVisible(true);
try{
Thread.sleep(15000);
}catch(Exception ex){}
this.setVisible(false);
this.dispose();
System.exit(1);
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
116 / 210
23.03.2010
PROMOD-2a / SS10
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu
diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
117 / 210
23.03.2010
PROMOD-2a / SS10
11. AWT-Events: Ereignisse und Event-Handling
Was Sie lernen:
was Ereignisse sind
wie Ereignisse empfangen, weitergeleitet und ausgewertet werden
welche Arten von Ereignissen es gibt
Voraussetzungen:
Grundlagen der Fensterprogrammierung
Innere und anonyme Klassen
11.1 Grundlagen
In der Programmierung grafischer Benutzeroberflächen stellt die Ereignisbehandlung (Event-Handling) ein
zentrales Element dar.
Das aktuelle Modell der Ereignisbehandlung unterscheidet sich von dem in Java 1.0. (Auf dieses ursprünglich
implementierte Modell gehen wir nicht weiter ein.)
Die Arbeitsweise der Ereignisbehandlung ist für AWT- und SWING-Komponenten nahezu identisch.
Ereignismodell Java AWT
Maus
OS
wartet
Tastatur
leitet
Nachricht
weiter
lfd.
Appl.
In einem Programm mit GUI können Eingaben jeder Art Ereignisse auslösen. Sollen die Ereignisse etwas
bewirken, so muss das entsprechend programmiert werden. In einem Java-Programm sind daran immer drei
Objekte beteiligt:
Ereignisquelle: das ist die Klasse, von der das Ereignis ausgeht. Dies kann beispielsweise eine Schaltfläche
sein, auf die mit der Maus geklickt wird.
Ereignisziel / Ereignisempfänger: das sind Objekte, das das Ereignis empfangen Æ Listener.
Die Ereignissquelle muss den entsprechenden Listener registrieren, damit das Ereignis verarbeitet werden
kann.
Ereignisobjekt: Wird ein Ereignis ausgelöst, so wird die zuständige („Callback“-)Methode des Ereignisobjekts
aufgerufen. Dieses Ereignisobjekt wird dem Listener als Parameter mitgegeben, wenn er der Ereignisquelle
zugeordnet wird.
Dieses Modell, bestehend aus Ereignisquelle, Ereigniszielen und Ereignisobjekt wird als " Event Delegation Model"
bezeichnet ("Ereignisdelegationsmodell"). D. h. die Ereignisquelle leitet (delegiert) die Ereignisse an alle
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
118 / 210
23.03.2010
PROMOD-2a / SS10
registrierten Listener weiter. Der für das Ereignis zuständige Listener reagiert dann entsprechend darauf.
(Ereignisquellen haben Methoden, mit denen man Ereignisempfänger für die Ereignisbehandlung registrieren kann.)
Event-Delegation-Modell: mehrere Listener können bei einer Ereignisquelle registriert werden
Ereignis
z. B. Mausklick auf
Button
Programm
registrieren
Ereignisziel
z. B.
MouseListener
Ereignisquelle
z. B. Button
Delegation
registrieren
Delegation
Ereignisziel
z. B. ActionListener
Beispielsweise registriert eine Schaltfläche (Klasse Button) zwei Listener, einen für Mausklicks und einen für
Mausbewegungen. Wenn die Ereignisquelle ein Ereignis empfängt, wird es an die Listener delegiert.
Die Registrierung des Listeners bei der Ereignisquelle:
Enthält „Event-Handler“ (A) Adapter oder
(B) Interface (auch „Callback”-Methode
genannt)
EreignisQuellObjekt.addEreignisListener(EreignisBehandlungsObjekt);
Das Ereignismodell erlaubt verschiedenen Möglichkeiten der Implementierung der Ereignisbehandlung, z.B:
Modell A: die Verwendung von Adapterklassen, die schon eine (leere) Implementierung des Interfaces
bereitstellen, so dass nur noch die Listener-Methoden implementiert werden müssen, die benötigt werden.
Modell B: die Implementation eines Listener-Interfaces. In diesem Fall müssen alle Methoden des Interfaces
implementiert werden.
Im Beispiel unten (EventFenster.java) werden die beiden Methoden gegenübergestellt.
Vorteile des Event-Delegation-Modells (ab Java-Version 1.1):
Nur für (Ereignis-Quell-)Objekte, bei denen ein entsprechender Listener angemeldet ist, werden Nachrichten
transportiert. Damit wird eine erhöhte Performance möglich.
Durch diesen Ansatz wird die Trennung des Programm-Codes für die GUI vom Programm-Code der
Anwendungslogik möglich.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
119 / 210
23.03.2010
PROMOD-2a / SS10
11.2 Ereignisklassen
Die Klasse der AWT-Ereignisse sind im Package java.awt.event zusammengefasst.
Ereignisobjekte kapseln Informationen über das Ereignis, das die Ereignisquelle an Ihre Empfänger schickt.
Bei Bedarf kann dann die betroffene Listener-Methode das Ereignisobjekt analysieren und entsprechend
darauf reagieren.
"Semantische Events"
java.util.EventObject
java.awt.AWTEvent
(1) Action Event
(2) AdjustementEvent
(3) ItemEvent
"low-level Event"
(systemnahe Ereignisse / technische Events)
(5) ComponentEvent
(7) ContainerEvent
(6) FocusEvent
(10) MouseEvent
(4) TextEvent
InputEvent
(8) WindowEvent
(9) KeyEvent
Semantische Events: z. B. Klicken mit der Maus auf ein Button, Betätigung einer Taste. Für Semantische
Events ist die Eventsource eine AWT-Komponente.
Semantische Ereignisse werden für die Übertragung "höherwertiger" Ereignisse eingesetzt. z. B. Zustandsänderung einer Komponente, Ausführung einer Aktion.
Ein semantisches Ereignis wird durch den Benutzer ausgelöst, z.B. ein Schaltflächenklick oder Eintrag in ein
Textfeld.
Low level Events sind an konkrete graphische Objekte wie Container (und Ableitungen; siehe Bild oben „Die
AWT-Klassenhierarchie“), Mouse und Key gebunden . Für Low level Ereignisse ist die Eventsource ein
konkretes grafisches Objekt aus der AWT-Fenster-Klassen-Hierarchie.
Ein low level Ereignis ermöglicht die semantischen Ereignisse.
Die Tabelle verdeutlicht nochmals den Unterschied zwischen semantischen Events und low level Events:
Semantische
Auslösung durch User-Interaktion mit einer AWT-Komponente
Ereignissklassen
ActionEvent
Schaltflächenklick, Menüauswahl, Doppelklick auf Listeneintrag, Drücken der
Return-Taste in einem Textfeld.
AdjustmentEvent
Benutzer ändert eine Bildlaufleiste.
ItemEvent
Benutzer trifft eine Auswahl aus einer Gruppe von Kontrollkästchen oder
Listenelementen.
TextEvent
Inhalt eines Textfeldes oder Textbereichs wurde geändert.
Auslösung (konkrete grafische Objekte aus der AWT-Fenster-Klassen-Hierarchie)
Systemnahe
Ereignissklassen
CompontentEvent
Größenänderung einer Komponente; Komponente wurde verschoben, angezeigt
oder ausgeblendet; (Basisklasse für alle systemnahen Ereignisse).
ContainerEvent
Eine Komponente wurde hinzugefügt oder entfernt.
FocusEvent
Eine Komponente hat den Focus erhalten oder verloren.
WindowEvent
Fensterzustand hat sich geändert.
MouseEvent
Maustaste wurde gedrückt oder gelöst, die Maus bewegt oder gezogen.
MouseWheelEvent
Mausrädchen wurde gedreht. (Im JDK1.4 hinzugekommen; Ableitung von
MouseEvent)
KeyEvent
Eine Taste wurde gedrückt oder gelöst.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
120 / 210
23.03.2010
PROMOD-2a / SS10
11.3 Listener-Interfaces
Für jede Ereignisklasse gibt es ein Listener-Interface. (Für Maus-Ereignisse gibt es zwei Listener-Interfaces.)
Listener werden für das Empfangen und Behandeln der Ereignisse benutzt.
In java.util gib es das Interface EventListener, von dem alle AWT-Listener abgeleitet sind.
java.util.EventListener
(1) Action Listener
(2) AdjustmentListener
(3) ItemListener
(4) TextListener
(5) ComponentListener
Das Listener-Interface legt Methoden fest, über die die Reaktion auf das Ereignis
erfolgt. Als Parameter wird diesen Methoden das entsprechende Event-Objekt
übergeben. (Siehe API-Doku zu Interface EventListener aus dem Package
java.util)
Beispiel - Interface KeyListener:
public interface KeyListener extends EventListener
{
public void KeyTyped(KeyEvent ev);
public void KeyPressed(KeyEvent ev);
public void KeyReleased(KeyEvent ev);
}
(6) FocusListener
(7) ContainerListener
(8) WindowsListener
(9) KeyListener
(10) MouseListener
(11) MouseMotionListener
11.4 Listener bei Ereignisquellen registrieren
Die Registrierung des Listeners bei der Ereignisquelle:
Enthält „Event-Handler“ (A) Adapter oder
(B) Interface (auch „Callback”-Methode)
…und u.U. weitere Daten zum Event
EreignisQuellObjekt.addEreignisListener(EreignisBehandlungsObjekt);
damit der Listener für die Behandlung des Ereignisses den zuständigen "Event-Handler“ / „Callback“-Methode
aufrufen kann.
In der Klasse Component und allen davon abgeleiteten Klassen ist für jeden Listenertyp eine Methode definiert,
mit der der Listener registriert wird.
Î setzt sich zusammen aus add und Name des Listeners (Beispiel: addKeyListener())
Beispiel – Eine erste Event-Behandlung
Im Beispiel unten wird in einem Java-Fenster eine Schaltfläche angeordnet. Wenn der Benutzer auf die
Schaltfläche klickt, soll das Fenster und die Anwendung geschlossen werden.
Auch wenn das Schließfeld des Fensters betätigt wird, soll alles geschlossen werden.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
121 / 210
23.03.2010
PROMOD-2a / SS10
Im Beispielprogramm unten sind die oben erwähnten Methoden A (Verwendung von Adapterklassen für die
Ereignisbehandlung) und Methode B (Implementation eines Listener-Interfaces) implementiert.
//EventFenster.java
import java.awt.*;
import java.awt.event.*;
public class EventFenster
{
public static void main(String[] args)
{
EFenster fenster = new EFenster();
fenster.setVisible(true);
}
}
Die Klasse EventFenster ist
Eventsource und Listener
class EFenster extends Frame implements MouseListener
{
Button btn;
public EFenster()
{
super("Bitte klicken");
this.setSize(180,100);
this.setLocation(350,300);
this.setBackground(new Color(50,200,255));
this.setLayout(null);
btn = new Button("schliessen");
btn.setCursor(new Cursor(Cursor.HAND_CURSOR));
btn.setBounds(40, 50, 100, 23);
btn.addMouseListener(this); // Å----------Button registriert den MouseListener
this.add(btn);
this.addWindowListener(new WindowAdapter() //Frame registriert den WindowListener
{ // anonyme Klasse
public void windowClosing(WindowEvent we)
{
ExitApp();
}
});
}
// Implementierung des Interfaces MouseListener ------------------------------public void mouseClicked(MouseEvent me)
{
ExitApp();
Modell A)
Nur eine Adaptermethode
(als „Callback“-Methode)
zu überschreiben.
}
public void mouseReleased(MouseEvent me)
{}
Modell B)
Alle Interfacemethoden
(als “Callback-Methode)
zu implementieren
public void mousePressed(MouseEvent me)
{}
public void mouseEntered(MouseEvent me)
{}
public void mouseExited(MouseEvent me)
{}
public void ExitApp()
// Der eigentliche Applikations-Code------------------{
// hier könnten sich weitere Anweisungen befinden,
// die beim Beenden der Anwendung ausgeführt werden
System.exit(0);
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
122 / 210
23.03.2010
PROMOD-2a / SS10
11.5 Implementierungsmöglichkeiten:
Im Beispiel oben sind zwei verschieden Implementierungstechniken für die Ereignisbehandlung zu Einsatz
gekommen. Hier nochmals eine Zusammenfassung.
11.5.1 1) Verwendung von Adapterklassen:
Adapterklassen implementieren ein Interface und stellen für alle Methoden leere Rümpfe zur Verfügung.
Besonders geeignet, wenn ein Interface viele Methoden enthält, aber nur wenige benötigt werden. Oder
umgekehrt ausgedrückt: nur in diesen Fällen sollten Adapterklassen zum Einsatz kommen, sonst wird der Code
leicht unübersichtlich.
11.5.2 2) Listener Interface wird implementiert:
Alle Methoden des Listeners müssen aufgeführt werden.
Relativ einfach zu implementieren, zu warten und zu erweitern.
Nachteil u. U. viele leere Methoden.
Vorteil: potentielle Funktionalität des Interfaces ist sichtbar
Î Es ist eine Trennung von GUI und Applikationslogik möglich. Dadurch entstehen kleinere übersichtlichere Klassen.
Andererseits ist ein Austausch der Programmlogik möglich, ohne dabei das GUI zu verändern.
11.6 Getrennter Code für GUI und Applikation
Im obigen Beispiel EventFenster.java wurde schon versucht, den Code für die GUI vom Code für die Applikation
zu trennen. So richtig zufrieden stellend ist die obige Lösung jedoch nicht, da der Gesamt-Code in einer
einzigen Klasse (EFenster) untergebracht ist.
Unten sehen Sie die besser organisierte Lösung:
// EventDelegationFenster.java
import java.awt.*;
import java.awt.event.*;
public class EventDelegationFenster
{
public static void main(String[] args)
{
EreignisBehandlung ereignisbehandlung = new EreignisBehandlung();
EDFenster fenster = new EDFenster(ereignisbehandlung);
}
}
//----------------- GUI ---------------------------------------------------class EDFenster extends Frame
{
Button btn;
public EDFenster(EreignisBehandlung ereignisbehandl) // Konstruktor mit EH
{
super("bitte klicken");
this.setSize(180,100);
this.setLocation(350,300);
this.setBackground(new Color(50,200,255));
this.setVisible(true);
this.setLayout(null);
btn = new Button("Schliessen");
btn.setCursor(new Cursor(Cursor.HAND_CURSOR));
btn.setBounds(40, 50, 100, 23);
// x, y, width, height
btn.addMouseListener(ereignisbehandl);
this.add(btn);
this.addWindowListener(ereignisbehandl);
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
123 / 210
23.03.2010
PROMOD-2a / SS10
}
}
//----------Application Code in den Callback-Methoden der Listener-----------class EreignisBehandlung implements WindowListener, MouseListener
{
public void windowClosing(WindowEvent we) // WindowEvents----------------{
ende();
}
public void windowOpened(WindowEvent we)
{}
public void windowIconified(WindowEvent we)
{}
public void windowDeiconified(WindowEvent we)
{}
public void windowActivated(WindowEvent we)
{}
public void windowDeactivated(WindowEvent we)
{}
public void windowClosed(WindowEvent we)
{}
public void mouseClicked(MouseEvent me) // MouseEvents-------------------{
ende();
}
public void mouseReleased(MouseEvent me)
{}
public void mousePressed(MouseEvent me)
{}
public void mouseEntered(MouseEvent me)
{}
public void mouseExited(MouseEvent me)
{}
private void ende() // der eigentliche App Code ---------------------------{
System.exit(0);
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
124 / 210
23.03.2010
PROMOD-2a / SS10
11.7 Low-Level-Ereignisse (systemnahe Ereignisse):
Zur Erinnerung:
Ein semantisches Ereignis wird durch AWT-Komponenten (Buttons, Choice, Label, usw.) ausgelöst, z.B. ein
Schaltflächenklick oder Eintrag in ein Textfeld.
Ein low level Ereignis ermöglicht die semantischen Ereignisse. Eventsource ist ein grafisches Objekt aus der
AWT-Fenster-Klassen-Hierarchie, also Component, Container und Ableitungen davon
11.7.1 Component-Ereignisse (die Klasse
ComponentListener):
Die Objekte der Klasse Component und alle davon abgeleiteten Klassen können Component-Ereignisse empfangen.
Sie müssen dazu den ComponentListener registrieren.
Component-Ereignisse werden ausgelöst, wenn eine
Komponente (Objekt der Klasse oder Ableitung
davon)…
…erzeugt wird oder von dem unsichtbaren in den
sichtbaren Zustand versetzt wird (Z. B. Aufruf der
Methode setVisible(true))
…unsichtbar gemacht wird (z.B. setVisible(false))
verschoben wird (per Maus oder setLocation())
…in ihrer Größe verändert wird (per Maus oder
setSize())
Listener-Methode:
componentShown()
componentHidden()
componentMoved()
componentResized()
Den genannten Listener-Methoden wird ein ComponentEvent-Objekt als Argument übergeben.
Die Klasse ComponentEvent stellt die Methode getComponent() zur Verfügung, mit der ermittelt wird,
welche Komponente das Ereignis ausgelöst hat.
public Component getComponent()
Beispiel – Eine low level Event-Behandlung; Reaktion auf ComponentEvent-Ereignis
//ComponentEventFenster.java
import java.awt.*;
import java.awt.event.*;
public class ComponentEventFenster
{
public static void main(String[] args)
{
CEFenster fenster = new CEFenster();
}
}
class CEFenster extends Frame implements ComponentListener
{
Button btn;
public CEFenster()
{
super("jetzt wird's bunt");
this.setSize(300,200);
this.setLocation(350,300);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
this.addComponentListener(this);
this.setLayout(null);
btn = new Button("ausblenden");
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
125 / 210
//
//
//
//
schon bekannt
Modell A
nur eine Adaptermethode überschreiben
// neu
23.03.2010
PROMOD-2a / SS10
btn.setSize(90, 23);
btn.setLocation((this.getWidth()-90)/2,
(this.getHeight()-23)/2);
btn.addMouseListener(new AbgeleiteterMouseListener());
this.add(btn);
// Modell C, siehe unten
this.setVisible(true);
}
public void componentMoved(ComponentEvent ae)
// Modell B; Implementierung
{
// des Listener-Interfaces
int r = (int)(this.getBounds().getX() % 254);
// von ComponentListener
r = (r > 0) ? r : -r;
int g = (int)((this.getBounds().getX() + this.getBounds().getY()) % 254);
g = (g > 0) ? g : -g;
int b = (int)(this.getBounds().getY() % 254);
b = (b > 0) ? b : -b;
this.setBackground(new Color(r, g, b));
}
public void componentHidden(ComponentEvent ae)
{
System.out.println("Fenster wurde ausgeblendet");
}
public void componentResized(ComponentEvent ae)
{
btn.setLocation((ae.getComponent().getWidth()-btn.getWidth())/2,
(ae.getComponent().getHeight()-btn.getHeight())/2);
}
public void componentShown(ComponentEvent ae)
{
// alle Component
System.out.println("Fenster wird angezeigt");
// Listener-Methoden
}
}
class AbgeleiteterMouseListener extends MouseAdapter // Methode C: Adapternutzung
{
// ohne anonyme Klasse
public void mouseClicked(MouseEvent me)
{
me.getComponent().getParent().setVisible(false);
try
{
Thread.sleep(1000);
}catch(Exception e){}
me.getComponent().getParent().setVisible(true);
}
}
Parent des Buttons ist der Frame.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
126 / 210
23.03.2010
PROMOD-2a / SS10
11.7.2 Window-Ereignisse (die Klasse
WindowListener):
Window-Ereignisse treten ein, wenn sich der Zustand des Fensters ändert. Um auf diese Änderungen zu
reagieren, muss der WindowListener implementiert werden.
Windows-Ereignisse werden ausgelöst, wenn ein
Fenster…
…“geöffnet“ wurde (z.B. durch Methode
setVisible(true))
…geschlossen wird (durch Schließfeld, Menü in der
Titelleiste, ALT + F4)
…geschlossen wurde
…minimiert wurde (z.B. durch das Minimierfeld oder
durch die Methode setState(Frame.ICONIFIED))
…wiederhergestellt wurde (via Icon anklicken, durch
Methode setState(Frame.NORMAL)
…in den Hintergrund gestellt wird (z.B.
setVisible(false) oder Methode hide())
…erstellt wurde oder wieder aktiviert wird (durch
Anklicken mit der Maus oder via Methode
setVisible(true))
Listener-Methode:
windowOpened()
windowClosing()
windowClosed()
windowIconified()
windowDeiconified()
windowDeactivated()
windowActivated()
Den genannten Listener-Methoden wird ein WindowEvent-Objekt als Argument übergeben.
Die Klasse WindowEvent stellt die Methode getWindow() zur Verfügung, mit der ermittelt wird, welches
Fenster das Ereignis ausgelöst hat.
public Window getWindow()
Beispiel – Eine low level Event-Behandlung; Reaktion auf WindowEvent-Ereignis
// WindowEventFenster.java
import java.awt.*;
import java.awt.event.*;
public class WindowEventFenster
{
public static void main(String[] args)
{
WEFenster fenster1 = new WEFenster(200,200);
WEFenster fenster2 = new WEFenster(300,300);
}
}
class WEFenster extends Frame implements WindowListener
{
Button btn;
public WEFenster(int x, int y)
{
super("ein ganz normales Fenster");
this.setSize(300,200);
this.setLocation(x, y);
this.addWindowListener(this);
this.setVisible(true);
}
public void windowOpened(WindowEvent we)
{
we.getWindow().setBackground(Color.magenta);
}
public void windowClosing(WindowEvent we)
// alle Window-Listner-Methoden
{
we.getWindow().dispose();
}
public void windowClosed(WindowEvent we)
{
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
127 / 210
23.03.2010
PROMOD-2a / SS10
System.out.println("Fenster wurde geschlossen");
System.exit(0);
}
}
public void windowIconified(WindowEvent we)
{
((Frame)we.getWindow()).setTitle("bin ganz klein");
}
public void windowDeiconified(WindowEvent we)
{
((Frame)we.getWindow()).setTitle("ein ganz normales Fenster");
}
public void windowActivated(WindowEvent we)
{
we.getWindow().setBackground(Color.cyan);
}
public void windowDeactivated(WindowEvent we)
{
we.getWindow().setBackground(Color.lightGray);
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
128 / 210
23.03.2010
PROMOD-2a / SS10
11.7.3 Focus-Ereignisse (die Klasse
FocusListener):
In jeder Anwendung kann immer nur ein Fenster aktiv sein.
Auf einem Fenster kann auch nur eine Komponente aktiv sein. Diese Komponente besitzt den Focus. Dies ist
durch verschiedene äußere Merkmale gekennzeichnet (Umrandung mit besonderm Aussehen (OS-abhängig),
Ein Textfeld hat den Focus, wenn der Cursor blinkt,…).
Focus-Ereignisse treten dann ein, wenn eine Komponente den Focus erhält oder verliert. Um auf diese
Änderung zu reagieren, muss der FocusListener implementiert werden.
Focus-Ereignisse werden ausgelöst, wenn eine
Komponente…
…den Focus erhält (durch Anklicken der Komponente
mit der Maus, Tab-Taste, Methode requestFocus())
…den Focus verliert (durch Anklicken anderer
Komponenten, Tab-Taste, requestFocus() einer
anderen Komponente)
Listener-Methode:
focusGained()
focusLost()
Den genannten Listener-Methoden wird ein FocusEvent-Objekt als Argument übergeben.
Eine Komponente kann mit der Methode requestFocus() den Focus für anfordern. Dazu muss die
Komponente sichtbar sein.
public void requestFocus()
Mit der Methode transferFocus() kann der Focus an die nächste Komponente weitergereicht werden. Die
Reihenfolge hängt davon ab, in welcher Reihenfolge die Komponenten zu dem Container-Objekt hinzugefügt
wurden.
public void transferFocus()
Soll überprüft werden, ob eine Komponente den Focus hat, steht die Methode hasFocus() zur Verfügung.
public void hasFocus()
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
129 / 210
23.03.2010
PROMOD-2a / SS10
11.7.4 Input-Ereignisse:
Die InputEvent-Klasse ist die Oberklasse aller Eingabeereignisse (KeyEvent und MouseEvent,
MouseWheelEvent). Sie stellt Methoden zur Verfügung, mit denen überprüft werden kann, ob Funktions- oder
Umschalttasten betätigt wurden:
boolean
boolean
boolean
boolean
boolean
isAltDown()
isAltGraphDown()
isControlDown()
isShiftDown()
isMetaDown()
Alt wurde gedrückt
Alt Gr wurde gedrückt
Strg wurde gedrückt
Umschalttaste gedrückt
rechte Maus-Taste gedrückt
Die Klasse InputEvent stellt auch die Methode consume() zur Verfügung. Diese Methode „konsumiert“ eine
Eingabe und verhindert dadurch das Weiterleiten der Eingabe. Die Methode kann dazu verwendet werden, um
Zeichen „auszufiltern“, wenn nur bestimmte Zahlen und Buchstaben bei der Komponente ankommen sollen.
public void consume()
Ob eine Eingabe konsumiert wurde, kann mit der Methode isConsumed() festgestellt werden.
public boolean isConsumed()
Wenn der Zeitabstand zwischen zwei Eingaben (z.B. zwischen zwei Mausklicks) ermittelt werden soll, kann die
Methode getWhen() eingesetzt werden. Die Methode liefert für ein Ereignis den zugehörigen Zeitstempel.
public long getWhen()
11.7.5 Die Eventklasse Tastaturereignisse (Klasse
KeyListener):
KeyEvent)
und das Listener-Interface
Tastaturereignisse werden ausgelöst wenn eine Taste oder Tastenkombination betätigt wird. Um auf eine bestimmte
Taste zu reagieren muss das KeyListener-Interface implementiert werden.
Tastatur-Ereignisse werden ausgelöst, wenn eine
Taste…
…betätigt und wieder losgelassen wird
…betätigt wird
…losgelassen wurde
Listener-Methode:
keyTyped()
keyPressed()
keyReleased()
Den genannten Listener-Methoden wird ein KeyEvent-Objekt als Argument übergeben.
Die Klasse KeyEvent stellt Methoden zur Verfügung, über die ermittelt werden kann, welche Taste(n) betätigt
wurde(n).
public char getKeyChar()
public int getKeyCode()
public static String getKeyText()
liefert Zeichen, das der Taste entspricht
Tastencode (Virtual Key Code, eine symbolische Konstante)
liefert Namen der Taste als Zeichenkette z.B. "ALT"
System.out.println(ke.getKeyText(ke.getKeyCode()));
keyTyped(): nur Unicode-Zeichen liefern einen Wert und können über getKeyChar() abgefragt werden.
keyPressed(): alle Zeichen können abgefragt werden und über getKeyCode() lassen sich Tastaturcodes
abfragen.
Über den Virtual Key Code können auch die betätigten Steuerungstasten ermittelt werden. Einfacher ist es jedoch in
den meisten Fällen, die von der Klasse InputEvent geerbten Methoden isAltDown(), isShiftDown(), usw. zu
verwenden.
Virtual Key Code
VK_A
VK_B
Prof. Illik
Taste
A
B
Virtual Key Code
VK_Enter
VK_F1
Taste
Enter
F1
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
Virtual Key Code
VK_ESCAPE
VK_INSERT
130 / 210
Taste
ESC
EINFG
23.03.2010
PROMOD-2a / SS10
…
VK_Z
VK_0
VK_1
…
VK_9
…
Z
0
1
9
VK_F2
…
VK_F12
VK_SPACE
VK_END
VK_HOME
F2
VK_DELETE
VK_TAB
ENTF
TABULATOR
F12
Leertaste
Ende
POS1
Welche Virtual Key Codes es noch gibt: siehe API-Doku „Field Summery“ in class KeyEvent.
Beispiel – Eine low level Event-Behandlung; Reaktion auf Tastatur-Ereignis
// KeyEventFenster.java
import java.awt.*;
import java.awt.event.*;
public class KeyEventFenster
{
public static void main(String[] args)
{
KEFenster fenster = new KEFenster();
}
}
class KEFenster extends Frame implements KeyListener
{
Label lbl1, lbl2, lbl3;
public KEFenster()
{
super("Farbe über Tastatur auswählen");
this.setSize(280,210);
this.setLocation(350,300);
this.setLayout(null);
lbl1 = new Label("o = orange, Alt+o = dunkelorange");//statischer Text im Fenster
lbl1.setBounds(20, 50, 190, 23);
lbl1.setBackground(Color.orange);
// Eine Komponente
lbl1.setVisible(true);
this.add(lbl1);
lbl2 = new Label("g = grün, Alt+g = dunkelgrün");
lbl2.setBounds(40, 100, 190, 23);
lbl2.setBackground(Color.green);
this.add(lbl2);
lbl2.setVisible(true);
lbl3 = new Label("b = blau, Alt+b = dunkelblau");
lbl3.setBounds(60, 150, 190, 23);
lbl3.setBackground(Color.cyan);
this.add(lbl3);
lbl3.setVisible(true);
this.addKeyListener(this);
// neu
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
this.setVisible(true);
this.requestFocus();
}
public void keyPressed(KeyEvent ke)
{
if (ke.getKeyCode() == KeyEvent.VK_END)
System.exit(0);
if(ke.getKeyChar() == 'o')
{
if(ke.isAltDown())
ke.getComponent().setBackground(new Color(255,153,0));
else
ke.getComponent().setBackground(Color.orange);
}
if(ke.getKeyChar() == 'g')
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
131 / 210
23.03.2010
PROMOD-2a / SS10
{
}
if(ke.isAltDown())
ke.getComponent().setBackground(new Color(0,153,0));
else
ke.getComponent().setBackground(Color.green);
}
if(ke.getKeyChar() == 'b')
{
if(ke.isAltDown())
ke.getComponent().setBackground(new Color(0,51,255));
else
ke.getComponent().setBackground(Color.cyan);
}
public void keyReleased(KeyEvent ke)
{
if (ke.getKeyCode() == KeyEvent.VK_ESCAPE)
System.exit(0);
}
public void keyTyped(KeyEvent ke)
{
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
132 / 210
23.03.2010
PROMOD-2a / SS10
MouseEvent und die Listener-Interfaces MouseListener,
MouseMotionListener :
11.7.6 Die Eventklasse Klasse
Mausereignissen treten immer dann ein, wenn eine Maustaste betätigt wird oder die Maus bewegt wird (mit
oder ohne gedrückter Maustaste). Um auf diese Ereignise zu reagieren, muss das MouseMotionListenerInterface implementiert werden.
Die Trennung von Maus-Ereignissen und Maus-Bewegungs-Ereignissen ist sinnvoll, da in vielen
Programmen nur auf Mausklicks reagiert werden soll
Mausbewegungen bringen eine Flut von Ereignissen mit sich (z.B. werden während einer Fenster-Verschiebung
laufend Ereignisse ausgelöst), was die Programmperformance negativ beeinflusst.
Mausereignisse:
Zum Erkennen von Mausereignissen muss das MouseListener-Interface implementiert werden.
Maus-Ereignisse werden ausgelöst, wenn…
…Maustaste betätigt wird
…Maustaste losgelassen wird
…Maustaste geklickt wird (betätigt und losgelassen)
…Maus in den Bereich einer Komponente bewegt
wird, die das Klickereignis empfängt
…Maus aus dem Bereich einer Komponente bewegt
wird, die das Klickereignis empfängt
Listener-Methode:
mousePressed()
mouseReleased()
mouseClicked()
mouseEntered()
mouseExited()
Mausbewegungsereignisse:
Zum Erkennen von Mausbewegungsereignissen muss das MouseMotionListener-Interface implementiert werden.
Mausbewegungsereignisse werden ausgelöst,
wenn…
…eine Komponente mit der Maus angeklickt wurde
und bei gedrückter Maustaste verschoben wird
…die Maus über eine Komponente hinweg bewegt
wird, ohne dass eine Maustaste betätigt wird
Listener-Methode:
mouseDragged()
mouseMoved()
Den Methoden beider Listener wird ein MouseEvent-Objekt als Argument übergeben.
Die Klasse MouseEvent stellt noch folgende Methoden zur Verfügung, über die die Position des Mauszeigers beim
Klicken und die Anzahl der Klicks ermittelt werden kann.
Point getPoint()
int getX()
int getY()
int getClickCount
Zur Ermittlung der Mauszeigerposition
Zur Ermittlung der x-Mauszeigerposition
Zur Ermittlung der y-Mauszeigerposition
Anzahl er Mausklicks
Beispiel – Eine low level Event-Behandlung; Reaktion auf Mausevents
//MouseEventFenster.java
import java.awt.*;
import java.awt.event.*;
public class MouseEventFenster
{
public static void main(String[] args)
{
MEFenster fenster = new MEFenster();
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
133 / 210
23.03.2010
PROMOD-2a / SS10
class MEFenster extends Frame implements MouseListener, MouseMotionListener
{
Button btn1, btn2, btn3;
private int distX, distY;
public MEFenster()
{
super("Klick-Fenster");
this.setSize(230,200);
this.setLocation(400,300);
this.setLayout(null);
btn1 = new Button("Hintergrundfarbe pink");
btn1.setBounds(40, 50, 150, 23);
btn1.addMouseListener(this);
this.add(btn1);
btn2 = new Button("schiebe mich");
btn2.setBounds(40, 100, 150, 23);
btn2.addMouseMotionListener(this);
btn2.addMouseListener(this);
this.add(btn2);
btn3 = new Button("gib mir einen Doppelklick");
btn3.setBounds(40, 150, 150, 23);
btn3.addMouseListener(this);
this.add(btn3);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
this.setVisible(true);
}
public void mouseClicked(MouseEvent me)
{
if(me.getComponent() == btn1)
{
me.getComponent().getParent().setBackground(Color.magenta);
}
}
public void mouseReleased(MouseEvent me)
{
if((me.getComponent() == btn3) && (me.getClickCount() == 2))
{
btn3.setLabel("Klick Klick");
try
{
Thread.sleep(1000);
}
catch(Exception ex)
{}
btn3.setLabel("gib mir einen Doppelklick");
}
}
public void mouseEntered(MouseEvent me)
{
me.getComponent().setBackground(Color.cyan);
}
public void mouseExited(MouseEvent me)
{
me.getComponent().setBackground(Color.lightGray);
}
public void mousePressed(MouseEvent me)
{
distX = me.getX();
distY = me.getY();
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
// alle MouseListener// Methoden
// alle MouseMotion-
134 / 210
23.03.2010
PROMOD-2a / SS10
// Listener-Methoden
public void mouseDragged(MouseEvent me)
{
btn2.setLocation((int)btn2.getLocation().getX() + me.getX() - distX,
(int)btn2.getLocation().getY()+ me.getY() - distY);
}
public void mouseMoved(MouseEvent me)
{}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
135 / 210
23.03.2010
PROMOD-2a / SS10
11.8 Zusammenfassung Ereignisse und ihre Behandlung
Einige der in der Tabelle unten genannten Low-level-Ereignisse haben wir in den Beispielen
oben gesehen:
Ereignis
(low level
Ereignisse)
ListenerInterfaces
Ereignisklasse
EventSource
(konkrete grafische Objekte
aus der AWT-FensterKlassen-Hierarchie)
Zustands- und
Größenänderung
einer Komponente
Hinzufügen /
Enterfernen einer
Component / eines
Containers
Erhalten / Verlieren
des Focus
Fensterposititon und
–größe ändern
Tastendruck
Mausbewegung
Mausklicks
Mausradbewegung
Methoden des Listeners
ComponentLIstener
ComponentEvent
Component
componentHidden() – Komponente wurde verborgen
componentMoved() – Komponente wurde bewegt
componenResized() – Größe wurde geändert
componentShown() – Komponente wurde sichtbar
ContainerListener
ContainerEvent
Container
componentAdded() – Komponente wurde hinzugefügt
componentRemoved() – Komponente wurde entfernt
FocusListener
FocusEvent
Component
focusGained() – Komponente hat Focus erhalten
focusLost() – Komponente hat Focus verloren
WindowsListener
WindowEvent
Window
windowActivated() – Fenster wurde aktiviert
windowClosed() – Fenster wurde geschlossen
windowClosing() – Fenster wird gerade geschlossen
windowDeactivated() – Fenster wurde deaktiviert
windowDeiconified() – Fenster wurde wieder hergestellt
windowIconified() – Fenster wurde auf Symbolgröße minimiert
windowOpened() – Fenster wurde geöffnet
KeyListener
KeyEvent
Component
keyPressed() – Taste wurde betätigt
keyReleased() – Taste wurde losgelassen
keyTyped() – Taste wird betätigt und wieder losgelassen
MouseMotionListener MouseMotionEvent Component
mouseDraged() – Maus wird mit gedrückter Taste über
eine Komponente bewegt
mouseMoved() – Maus wird ohne eine gedrückte Taste über
eine Komponente bewegt
MouseEvent
Component
MouseListener,
mousePressed() – Maustaste wurde betätigt
mouseReleased() – Maustaste wurde losgelassen
mouseClicked() – Maustaste wurde betätigt und losgelassen
mouseEntered() – Maustaste wurde in den Bereich einer
Komponente bewegt
mouseExite() – Maustaste wurde aus dem Bereich einer
Komponente bewegt
MouseWheelListener
MouseWheelEvent
Component
mouseWheelMoved() – Mausrad wurde gedreht
Da für die Low-Level-Ereignisse die Event-Source i.d.R. die Klasse Component ist, können die
Low-Level-Listener auf (nahezu) alle Klassen der AWT-Hierarchie angewandt werden.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
136 / 210
23.03.2010
PROMOD-2a / SS10
Semantische Ereignisse werden uns in den Folgekapiteln begegnen; hier ein Überblick:
Ereignis
(semantische
Ereignisse)
Aktion auf
Komponente
(Choice, Label,
Button, …)
Verschieben
Schieberegeler
oder Bildlaufleiste
Zustandsänderung
Textänderung
ListenerInterfaces
Ereignisklasse
EventSource
(AWT Komponenten)
Methoden des Listeners - Erläuterung
ActionListener
ActionEvent
z.B. Button, List,
MenuItem,
TextField
actionPerformed() – Aktion wurde ausgeführt
AdjustmentListener AdjustmentEvent Scrollbar
adjustmentValueChanged() – Wert wurde geändert
ItemListener
ItemEvent
Checkbox, List,
CheckBoxMenuItem
itemStateChanged() – Zustgand wurde geändert
TextListener
TextEvent
TextComponent
textValueChanged() – Text wurde geändert
Aufgabe:
Im Progjekt ADRELI_4_GUI geben Sie dem bisherigen ADRELI-Programm eine AWT-Oberfläche. Dabei sollen
verschiedene, in der Spalte „EventSource“ genannten AWT-Komponenten, verwendet werden.
Beschreiben Sie hier, wie Sie mit Hilfe der Java-API-Docu vorgehen, um herauszufinden, auf welche Events
mit welchen Methoden reagiert werden kann, ohne in der Tabelle oben nachzusehen.
Also z.B. konkret: „Welche Events sind mit einem Button (als EventSource) auslösbar und mit welchen CallBack-Methoden kann darau reagiert werden?“. Notieren Sie Ihre Vorgehensweise unten!
Nochmals: es geht also darum, dass Sie wissen, wie mit Hilfe der originalen Java-API-Docu – ohne die oben
gezeigte Tabelle – die notwendigen Informationen für die GUI-Interaktionen zusammen gesucht werden können!
Bearbeiten Sie die Case Studies (kap09_awt_event_case_study) und lösen
Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem
Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
137 / 210
23.03.2010
PROMOD-2a / SS10
12. AWT-Graphics: Grafikprogrammierung mit AWT
Was Sie lernen:
wie auf AWT-Komponenten gezeichnet wird
wie Text ausgegeben wird
wie Bilder aus Dateien angezeigt werden können
Voraussetzungen:
Kenntniss der Fensterprogrammierung und Ereignisbehandlung
12.1 Grundlagen
Die Klasse java.awt.Graphics kapselt einen graphischen Ausgabebereich und stellt u.a. Methoden zum
Zeichnen von Linien, Kreisen, Rechtecken, usw. zur Verfügung. Auch die Darstellung von Schriften und das
Anzeigen von Bildern gehören in den Bereich der Graphikprogrammierung. So speichert bspw. die Klasse
Graphics Zeicheneinstellungen wie die aktuelle Schriftfarbe und die Schriftart.
Anmerkung: Im Zusammenhang mit der Graphik und den Komponenten kommen jetzt auch semantische
Ereigniss zum Einsatz.
12.2 Der Grafikkontext / Die Klasse Graphics:
Unter dem Begriff Grafikkontext werden die aktuellen Einstellungen für die Zeichenfunktionen und die
Textausgabe zusammengefasst: z. B. Zeichenfarbe, Schriftart, Zeichenbereich,…
Der Grafikkontext stellt Methoden zur Verfügung…
− zum Einstellen und Verändern der Grafikkontext-Eigenschaften
− zum Zeichnen von Linien, Bögen u. a. primitiven geometrischen Formen
− zum Füllen von Flächen
− zur Textausgabe
− zur Anzeige von Bildern
Ermittelt wird der Grafikkontext eines Fensters und anderer AWT-Komponenten mit der ComponentMethode getGraphics()
public Graphics getGraphics()
Die Methode getGraphics() stammt von der Klasse Component. Die Methode kann für Objekte aller
von Component abgeleiteten Klassen eingesetzt werden, wie z.B. Frame, Panel und den AWTKomponenten (z.B. Labels, Buttons, …).
Die Methode getGraphics() liefert ein Graphics-Objekt (als Graphikkontext) zurück. Ruft man für
dieses Objekt z.B. die Zeichenmethode drawRect() auf, so wird auf die entsprechende Komponente ein
Rechteck gezeichnet.
Hinweis: Wird auf diese Weise eine Graphik gezeichnet (siehe Beisiel unten: hier wird eine Graphik auf einen Button
gezeichnet), so wird diese selbst gezeichnete Grafik gelöscht, wenn Teile davon verdeckt werden, z.B. durch ein
anderes Fenster. (Probieren Sie’s mit dem unten gelisteten Programm aus.)
Gezeichnet mit Graphics.fillRect() und
Graphics.fillOval().
Fenster wurde verkleinert und
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
138 / 210
23.03.2010
PROMOD-2a / SS10
dann wieder vergrößert:
Beispiel – Umgang mit dem Grafikkontext
import java.awt.*;
import java.awt.event.*;
public class GrafikKontext extends Frame implements ActionListener
{
Button btn;
public static void main(String[] args)
{
GrafikKontext fenster = new GrafikKontext();
}
public GrafikKontext()
{
super("Zeichnen auf Schaltflächen");
this.setSize(260,180);
this.setLocation(100,100);
this.setBackground(new Color(50,200,255));
this.setLayout(null);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
btn = new Button("Klicken Sie hier");
btn.setSize(200, 100);
btn.setLocation(30, 50);
this.add(btn);
Der Grafikkontext
btn.addActionListener(this);
vom Button
this.setVisible(true);
}
public void actionPerformed(ActionEvent ev) // wird aufgerufen nach Betätigung der
{
// Schaltfläche
Graphics g_btn = btn.getGraphics();
g_btn.setColor(Color.blue);
g_btn.fillRect(10, 10, 100, 50);
g_btn.setColor(Color.red);
g_btn.fillOval(70, 40, 120, 50);
}
}
12.3 Die Methoden paint() und repaint():
Die paint()-Methode wird in der Component-Klasse definiert:
public void paint(Graphics g)
Aufgerufen wird paint() immer dann, wenn die
Komponente das erste Mal gezeichnet wird oder wenn
ein erneutes Zeichnen erfoderlich ist.
Um mit der paint()-Methode auf einer Komponente zu zeichnen, brauchen Sie eine Klasse, die von
Compontent abgeleitet ist und in der die Methode paint() überschreiben wurde.
Zum Beispiel kann man eine neue Klasse von Frame ableiten, deren Methode paint() überschreiben und so in
das Fenster zeichnen.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
139 / 210
23.03.2010
PROMOD-2a / SS10
Im Methodenrumpf von paint() wird von den oben behandelten Graphics-Methoden setColor(), fillRect(),
usw., Gebrauch gemacht.
Die Methode paint() wird automatisch aufgerufen, wenn ein Fenster verborgen war und (wieder) sichtbar
wird. (z.B. bei setVisible(true)) Siehe Beispiel ZeichenFenster1 unten.
ÎAuch Teile einer Komponente lassen sich "wieder zeichnen"!! Siehe AWT-Doku zu den Methoden
Compontent.repaint()
Beispiel – Die Methode paint()
import java.awt.*;
import java.awt.event.*;
public class ZeichenFenster1 extends Frame
{
public static void main(String[] args)
{
ZeichenFenster1 fenster = new ZeichenFenster1();
}
public ZeichenFenster1()
{
super("Zeichenfenster 1");
this.setSize(250,140);
this.setLocation(100,100);
this.setBackground(new Color(50,200,255));
this.setVisible(true);
this.setLayout(null);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void paint(Graphics g)
{
g.setColor(Color.yellow);
g.fillRect(45, 55, 150, 50);
g.setColor(Color.blue);
g.drawString("gefülltes Rechteck",60,80);
}
}
Die Positionierung der Graphik erfolgt ausgehend von der linken oberen Ecke (0,0) der Komponente, auf die
gezeichnet wird. Die Maßeinheit ist Pixel.
Jeder Zeichen- und Textausgabemethode muss die Position auf diese Weise übergeben werden.
Wenn im Fenster gezeichnet wird, ist zu beachten, dass ein Fenster einen Rahmen, eine Titelleiste und u. U.
eine Menüzeile besitzt.
Um die wahre Größe der Fläche herauszufinden, auf die gezeichnet werden kann, steht die Methode
Container.getInsets() zur Verfügung.
Beispiel – Die Positionierung im Fenster
import java.awt.*;
import java.awt.event.*;
public class ZeichenFenster2 extends Frame
{
public static void main(String[] args)
{
ZeichenFenster2 fenster = new ZeichenFenster2();
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
140 / 210
23.03.2010
PROMOD-2a / SS10
}
}
public ZeichenFenster2()
{
super("Zeichenfenster2");
this.setSize(300,140);
this.setLocation(100,100);
this.setBackground(new Color(50,200,255));
this.setVisible(true);
this.setLayout(null);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void paint(Graphics g)
{
g.setColor(Color.white);
g.drawRect(this.getInsets().left + 20,
this.getInsets().top + 20,
(int)this.getSize().getWidth() - this.getInsets().left this.getInsets().right - 40,
(int)this.getSize().getHeight() - this.getInsets().top –
this.getInsets().bottom - 40
);
}
Die Methode getSize() stammt von Component und liefert einen Returnwert vom Typ
Dimension (enthält die zwei Felder Witdth und Height). Siehe API-Doku.
12.4 Anzeige von Text:
Für die Ausgabe von Text bietet die Klasse Graphics mehrere Methoden:
public void drawString(String str, int x, int y);
public void drawChars(char[] data, int offset, int length, int x, int y);
public void drawBytes(byte[] data, int offset, int length, int x, int y);
drawstring() gibt die übergebene Zeichenkette auf der Kompontente aus. Es wird nur soviel Text geschreiben,
wie in den Bereich passt. Der Rest wird abgeschnitten.
drawChars() und drawBytes() schreiben Zeichen (UNICODE), bzw. Bytes (ASCII) eines Arrays auf die
Komponente. Der Parameter offset legt die Startposition fest. Der Prameter length gibt an, wie viele
Zeichen, bzw. Bytes aus dem Array zu schreiben sind.
Die Parameter x und y bezeichnen die Position der linken Seite der Grundlinie.
12.4.1 Die Klasse Font / einige Grundbegriffe
Die hier kurz skizzierten Grundbegriffe sind der API-Dokumentation (Java™ Platform Standard Ed. 6) zur
Klasse Font entnommen.
The Font class represents fonts, which are used to render text in a visible way. A font provides the information
needed to map sequences of characters to sequences of glyphs and to render sequences of glyphs on Graphics
and Component objects. […]
12.4.1.1
Characters and Glyphs
A character is a symbol that represents an item such as a letter, a digit, or punctuation in an abstract way. For
example, 'g', LATIN SMALL LETTER G, is a character.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
141 / 210
23.03.2010
PROMOD-2a / SS10
A glyph is a shape used to render a character or a sequence of characters. In simple writing systems, such as Latin,
typically one glyph represents one character. In general, however, characters and glyphs do not have one-to-one
correspondence. For example, the character 'á' LATIN SMALL LETTER A WITH ACUTE, can be represented by two
glyphs: one for 'a' and one for '´'. […]
Font („Abbildungsvorschrift“, „digitale Umsetzung“)
„Character“ (semantisches Konezpt)
„Glyphe“ (graphische Darstellung eines Schriftzeichen)
Bild: Zusammenhang zwischen Character, Font und Glyphe
12.4.1.2
Physical and Logical Fonts
The Java Platform distinguishes between two kinds of fonts: physical fonts and logical fonts.
1. Physical fonts are the actual font libraries containing glyph data and tables to map from character
sequences to glyph sequences, using a font technology such as TrueType or PostScript Type 1. All
implementations of the Java Platform must support TrueType fonts; support for other font technologies is
implementation dependent. […]
2. Logical fonts are the five font families defined by the Java platform which must be supported by any
Java runtime environment: Serif, SansSerif, Monospaced, Dialog, and DialogInput. These logical fonts
are not actual font libraries. Instead, the logical font names are mapped to physical fonts by the Java
runtime environment. The mapping is implementation and usually locale dependent, so the look and the
metrics provided by them vary. Typically, each logical font name maps to several physical fonts in order to
cover a large range of characters.
Peered AWT components, such as Label and TextField, can only use logical fonts. […]
12.4.1.3
Font Faces and Names
A Font can have many faces, such as heavy, medium, oblique („kursiv“), gothic („Fraktur“) and regular. All of
these faces have similar typographic design.
There are three different names that you can get from a Font object.
The logical font name is simply the name that was used to construct the font.
The font face name, or just font name for short, is the name of a particular font face, like Helvetica Bold.
The family name is the name of the font family that determines the typographic design across several faces,
like Helvetica. […]
Quelle: Wikipedia
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
142 / 210
23.03.2010
PROMOD-2a / SS10
12.4.2 Die Klasse Font / Schriftarten
Wenn keine Schriftart festgelegt wird, so nutzt Java eine systemabhängige Standardschrift
(Dialog.plain)
Soll eine andere Schriftart verwendet werden, so ist ein Font-Objekt zu verwenden
Dieses Font-Objekt wird dem Grafikkontext/einem Graphics-Objekt mit setFont(Font font) übergeben
public abstract void setFont(Font font)
Font setzten
Bezüglich der Font-Eigenschaftgen (Attribute & Methoden) siehe API-Beschreibung class Font.
Methoden um Font-Einstellungen zu bekommen sind im Folgenden aufgelistet.
public
public
public
public
public
Font getFont()
String getFontName()
String getFamily()
int getStyle()
int getSize()
gibt den Font zurück
gibt Font- -Namen zurück
gibt Font-Family-Namen zurück
gibt Schriftstil zurück (BOLD, PLAIN, ITALIC,…)
gibt Schriftgröße zurück
Konstruktor:
public Font(String Name, int Style, int Size)
Konstruktor
Der erste Parameter ist der Name der Schriftart. Auf allen Systemen sollten die Schriften Dialog,
DialogInput, Monospaced(Courier), Serif(Times Roman), Sans Serif(Arial) und Symbol verfügbar sein.
Der zweite Parameter kennzeichnet den Schriftstil. Der Schriftstil kann BOLD (fett), PLAIN (normal) und
ITALIC (kuriv) sein. Die Schriftstile sind symbolsiche Konstanten, die in der Klasse Font vereinbart sind. Es
können auch zwei Schriftstile miteinander verknüft werden: z.B. Font.BOLD + Font.ITALIC.
Der dritte Parameter spezifiziert die Schriftgröße. Die Schriftgröße kann durch eine Integer-Zahl angegeben
werden. Die Maßeinheit ist Pixel.
Beispiel – Der Umgang mit Schriftarten; Methoden der Klasse Font
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
143 / 210
23.03.2010
PROMOD-2a / SS10
import java.awt.*;
import java.awt.event.*;
public class ZeichenFenster3 extends Frame
{
public static void main(String[] args)
{
ZeichenFenster3 fenster = new ZeichenFenster3();
}
public ZeichenFenster3()
{
super("Zeichenfenster3");
this.setSize(700,300);
this.setLocation(100,100);
this.setBackground(new Color(50,200,255));
this.setVisible(true);
this.setLayout(null);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void paint(Graphics g)
{
g.drawString("Das ist ein Beispieltext",40,50);
g.drawLine(40, 50, 170, 50);
g.drawString(g.getFont().getFontName(),40,80);
Font font = new Font("Monospaced", Font.BOLD, 12);
g.setFont(font);
g.drawString(""+g.getFont().toString(),40,110);
g.drawString(""+g.getFont(),40,140);
g.setFont(new Font("Serif", Font.BOLD, 12));
g.drawString(""+g.getFont(),40,170);
g.setFont(new Font("SansSerif", Font.PLAIN, 12));
g.drawString(""+g.getFont(),40,200);
g.setFont(new Font("Dialog", Font.ITALIC, 12));
g.drawString(""+g.getFont(),40,230);
g.setFont(new Font("DialogInput", Font.BOLD +
Font.ITALIC, 12));
g.drawString(""+g.getFont(),40,260);
}
}
Welche Schriften sind verfügber?
Welche Schriftarten aktuell verfügbar sind, kann über die Klasse
GraphicsEnvironment abgefragt werden. Mit der Methode
getLocalGraphicsEnvironment() wird ein Objekt dieser Klasse erzeugt.
Die Methode getAvailableFontFamilyNames() liefert die Namen aller
verfügbaren Fonts in ein String-Array zurück.
public void paint(Graphics g)
{
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fonts = ge.getAvailableFontFamilyNames();
int anz = fonts.length;
for (int i = 0; i < anz; i++)
{
g.setFont(new Font(fonts[i],Font.PLAIN, 10));
g.drawString(fonts[i], 20, 40 + (i * 13));
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
144 / 210
23.03.2010
PROMOD-2a / SS10
Grundaubau des Schriftsatzes (Selbstudium)
Wenn ein Text an eine bestimmte Stelle in einem Fenster positioniert werden soll, wird i.d.R. die Breite, die der
auszugebenden Text einnehmen wird, benötigt. Diese Information kann über die Klasse FontMetrics abgerufen
werden.
Um die Informationen dieser Klasse richtig nutzen zu können, muss man den Grundaufbau eines Schriftsatzes
kennen. Das Bild unten nennt die wichtigsten Begriffe des Schriftsatzes. Die englischen Begriffe entsprechen den
Methodennamen der Klasse FontMetrics.
Mit den folgenden FontMetrics-Methoden lassen sich die Schrift-Attribute ermittlen:
public
public
public
public
int
int
int
int
getHeight()
getAscent()
getDescent()
getMaxDescent()
public int getLeading()
public int StringWidth(String s)
public int charWidth(int char)
liefert Zeilenhöhe (Gesamthöhe)
liefert Oberlänge (ascent)
liefert Unterlänge (descent)
liefert Größe der Unterlänge zurück,die maximal von einem
Zeichen erreicht werden kann
liefert den Durchschuss (leading, Zeilenabstand)
liefert die Breite, die der String einnimmt (mit eingestelltem Font)
liefert die Zeichenbreite
Beispiel – Der Umgang mit Schriftarten; Methoden der Klasse FontMetrics
import java.awt.*;
import java.awt.event.*;
public class ZeichenFenster5 extends
Frame
{
public static void main(String[]
args)
{
ZeichenFenster5 fenster = new ZeichenFenster5();
}
public ZeichenFenster5()
{
super("Zeichenfenster5");
this.setSize(350,130);
this.setLocation(100,100);
this.setBackground(new Color(50,200,255));
this.setVisible(true);
this.setLayout(null);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
145 / 210
23.03.2010
PROMOD-2a / SS10
}
});
}
public void paint(Graphics g)
{
String s = "Dieser String steht in der Mitte des Fensters";
int breite = getSize().width - getInsets().left - getInsets().right;
int hoehe = getSize().height - getInsets().bottom - getInsets().top;
Font font = new Font("Serif", Font.BOLD + Font.ITALIC, 15);
FontMetrics fmetrics = getFontMetrics(font);
int zeilenMitte = fmetrics.getAscent() (fmetrics.getAscent() + fmetrics.getDescent()) / 2;
g.setFont(font);
g.setColor(SystemColor.windowText);
g.drawString(s, getInsets().left + (breite - fmetrics.stringWidth(s)) / 2,
getInsets().top + hoehe / 2 + zeilenMitte);
}
12.5 Farben / die Klasse Color
Java verwendet das RGB-Modell
Die Klasse Color kapselt die Farben des RGB (Rot-Grün-Blau)-Farbmodells (Zahlenwerte von 0 – 255 (int)
oder von 0.0 bis 1.0 (float)) geben Intensität des Farbanteils an)
Einige Farben sind vordefiniert: z. B.: black, blue, cyan, gray, darkGray, lightGrey, magenta, orange,
pink, green, red, yellow,
Bei der Erzeugung eines Color-Objects wird die Farbe festgelegt: entweder über drei Integer-Werte, die in einem
Wertebereich von 0 bis 255 liegen, oder durch float-Zahlen im Wertebereich zwischen 0.0 und 1.0.
Color(int r, int g, int b) Î Werte 0 -255 oder aber
Color(float r, float g, float b) Î Werte von 0.0 - 1.0
z.B.:
Color
Color
Color
Color
Color
c
c
c
c
c
=
=
=
=
=
new
new
new
new
new
Color(255,255,255); // weiß
Color(0,0,0); // schwarz
Color(255,0,0); // rot
Color(0,255,0); // grün
Color(0,0,255); // blau
Die Farbe für das nächste Grafikobjekt wird mit setColor(Color c) gesetzt
public abstract void setColor(Color c)
Farbe, in der das nächste Graphikobjekt gezeichnet
warden soll
Die Abfrage der aktuellen Zeichenfarbe erfolgt mit public abstract Color getColor()
Von der Klasse Color ist die Klasse SystemColor abgeleitet und kann ebenfalls für Farbeinstellungen verwendet
werden. Vorteil: Werden Systemfarben geändert, so werden auch diese Farben dynamisch angepasst:
Bereich
desktop
activeCaption
inactiveCaption
activeCaptionText
inactiveCaptionText
window
windowBorder
windowText
menue
Prof. Illik
Funktion
Hintergrundfarbe des Desktops
Hintergrundfarbe der Titelleiste des aktuellen Fensters
Hintergrundfarbe der Titelleiste eines inaktiven Fensters
Schriftfarbe des Fenstertitels des aktiven Fensters
Schriftfarbe des Fenstertitels von inaktiven Fenstern
Hintergrundfarbe eines Fensters
Farbe des Fensterrahmens
Farbe der Schrift im Fenster
Hintergrundfarbe des Menüs
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
146 / 210
23.03.2010
PROMOD-2a / SS10
menueText
Farbe der Schrift im Menü
…
Auszug der 26 definierten Systemfarben.
Beispiel – Zeichnen mit Farben
import java.awt.*;
import java.awt.event.*;
public class Farbverlauf extends Frame
{
int n = 1; // ganze Zahl die den Abstand
// im Spektrum angibt
public static void main(String[] args)
{
Farbverlauf fenster = new Farbverlauf();
}
public Farbverlauf()
{
super("Farbverlauf");
this.setSize(350,130);
this.setLocation(100,100);
this.setVisible(true);
this.setLayout(null);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void paint(Graphics g)
{
int rot = 255;
//
int blau = 255;
// rgb-Kombination für Magenta
int gruen = 0;
//
int breite = getWidth()- getInsets().left - getInsets().right;
int hoehe = getHeight() - getInsets().bottom;
int i = 0;
while (i < breite)
{
while (gruen < 256 - n)
//magenta - rot - gelb
{
i = i + 1;
gruen = gruen + n;
blau = blau - n;
g.setColor(new Color(rot, gruen, blau)); //magenta
g.drawLine(i, 0, i, hoehe);
}
while (blau < 256 - n)
//gelb - grün - cyan
{
i = i + 1;
blau = blau + n;
rot = rot - n;
g.setColor(new Color(rot, gruen, blau));
g.drawLine(i, 0, i, hoehe);
}
while (rot < 256 - n)
//cyan - blau - magenta
{
i = i + 1;
gruen = gruen - n;
rot = rot + n;
g.setColor(new Color(rot, gruen, blau));
g.drawLine(i, 0, i, hoehe);
}
}
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
147 / 210
23.03.2010
PROMOD-2a / SS10
12.6 Weitere Methoden zum Zeichnen aus der Klasse Graphics
Die Klasse Graphics stellt Methoden zum Zeichnen einfacher geometrischer Figuren bereit. Dazu gehören Linien,
Rechtecke, Polygone, Kreise und Kreisbögen.
Linien:
Mit einer Pixelstärke von einem Pixel.
Dicke Linien entweder durch Zusammensetzung mehrerer 1-Pixel-Linien oder aber durch ein "schmales"
Rechteck.
Die Linie wird als Verbindung zwischen zwei Punkt-Koordinaten gezogen.
Liegt ein Punkt ausserhalb des Fensters, wird die Linie am Fensterrand abgeschnitten, ohne dass dies ein Fehler
wäre.
Nur durchgezogene Linien.
Eine einfache Möglichkeit für das Zeichnen breiter Linien oder Linien mit einem anderen Muster (gepunktet, usw.)
existiert nicht.
public abstact void drawLine(int x1, int y1, int x2, int y2)
x1, y1 steht für den Startpunkt und x2, y2 steht für den Endpunkt.
Ungefüllte Rechtecke:
Es lassen sich Rechtecke ohne und mit runden Ecken darstellen. x und y kennzeichnen den linken oberen
Startpunkt. width stellt die Breite und height die Höhe des Rechtecks dar.
Bei Rechtecken mit runden Ecken stellen die letzten beiden Parameter den horizontalen und vertikalen
Durchmesser es Eckbogens dar.
public void drawRect(int x, int y, int width, int height)
public abstract void drawRoundRect(int x, int y, int width, int heigt, int arcWidth,
int arcHeight)
Gefüllte Rechtecke:
Rechtecke ohne und mit runden Ecken lassen sich auch als gefüllte Rechtecke darstellen:
public abstract void fillRect(int x, int y, int width, int height)
public abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth,
int arcHeight)
3D Rechtecke:
3D-Rechtecke erheben sich positive nach vorne (Parameter raised = true) oder netagiv nach hinten (Parameter
raised = false) aus der Ebene.
public void draw3DRect(int x,
raised
public void fill3DRect(int x,
raised
int y, int width, int height, Boolean raised)
== true = nach aussen
int y, int width, int height, Boolean raised)
== false= nach innen
Polygone:
Polygone sind Figuren aus mehreren Linien. Wird ein nicht geschlossenes Polygon angegeben, so wird automatisch
der letzte Punkt mit dem ersten verbunden.
public
public
public
public
abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
void drawPolygon(Polygon p)
abstract void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
void fillPolygon(Polygon p)
Polygon(int[] xPoints, int[] yPoints, int nPoints)
PolyLine:
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
148 / 210
23.03.2010
PROMOD-2a / SS10
Offener Polygonzug. Der Anfangs- und Endpunkt wird nicht miteinander verbunden.
public abstract void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
Kreise, Ellipsen:
In AWT werden Kreise und Ellipsen über die gleichen Methoden gezeichnet, da Kreise spezielle Ellipsen sind.
public abstract void drawOval(int x, int y, int width, int height)
public abstract void fillOval(int x, int y, int width, int height)
Die x und y-Koordinaten geben den linken oberen Eckpunkt des Rechtecks an, das den
Kreis aufnimmt. Der Parameter width spezifiert die horizontale Ausdehnung und der
Parameter height die vertikale Dimension.
Bogen:
Als Bogen wird ein Teilstück eines Kreises bzw. einer Ellipse verstanden. Beschrieben wird der Bogen wie der Kreis
(auf dem er liegt) mit zusätzlicher Angabe von Start- und Endwinkel.
public abstract void drawArc(int
int
public abstract void fillArc(int
int
x, int y, int width, int height, int startAngle,
endAngle)
x, int y, int width, int height, int startAngle,
endAngle)
Beispiel – Nochmals: die Klasse Graphics und der Umgang mit Zeichenmethoden
import java.awt.*;
import java.awt.event.*;
public class ZeichenFenster6 extends Frame
{
public static void main(String[] args)
{
ZeichenFenster6 fenster = new ZeichenFenster6();
}
public ZeichenFenster6()
{
super("Zeichenfenster 6");
this.setSize(450,140);
this.setLocation(100,100);
this.setVisible(true);
this.setLayout(null);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void paint(Graphics g)
{
g.setColor(Color.yellow);
g.fillRect(45, 55, 100, 70);
g.setColor(Color.black);
g.drawOval(45, 55, 100, 70);
g.setColor(Color.yellow);
g.fillRect(175, 55, 100, 70);
g.setColor(Color.black);
g.drawArc(175, 55, 100, 70, 0, 140);
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
149 / 210
23.03.2010
PROMOD-2a / SS10
12.7 Bitmaps anzeigen: (nur gif und jpeg-Format)/ Die Klasse Image
Bitmaps werden in Java durch die Klasse Image repräsentiert. Bitmaps müssen
zuerst geladen werden und können
dann angezeigt werden.
Unterstützt werden die Grafikformate gif und jpeg. Über das JAI Java Advanced Imaging API stehen weiter
Grafikformate zur Verfügung (siehe: http://java.sun.com/products/java-media/jai/index.html)
Das Laden einer Bitmap kann mit Hilfe der Methode getImage() durchgeführt werden.
public abstract Image getImage(String filename) // Name der Bilddatei
public abstract Image getImage(URL url)
// URL der Bilddatei
1. Laden:
URL url = this.getClass().getResource("smile.gif")
Image img = this.getToolkit().getImage(url)
oder
Image icon = this.getToolkit().getImage(“./smile.gif”);
oder alternative:
Image icon = Toolkit.getDefaultToolkit().getImage(“./smile.gif”);
(Hinweis 1: die Methode getTookit() (aus der Klasse Window) liefert ein Toolkit-Objekt.
Hinweis 2: Klasse Toolkit: This class is the abstract superclass of all actual implementations of the Abstract
Window Toolkit. Subclasses of Toolkit are used to bind the various components to particular native toolkit
implementations. … Some methods defined by Toolkit query the native operating system directly.)
2. Anzeigen:
Angezeigt wird ein Bild mit der Methode drawImage() aus der Klasse Graphics.
public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer)
public abstract boolean drawImage(Image img,int x, int y, int width, int height
ImageObserver observer)
public abstract boolean drawImage(Image img, int x, int y, Color bgcolor,
ImageObserver observer)
drawImage() ist sehr polymorph: es gibt sechs verschiedene Parameterlisten.
In jedem Fall werden den Methoden das Image-Objekt und die linke obere Ecke als Startposition für das Bild
übergeben.
Als letzter Parameter kann ein ImageObserver-Objekt übergeben werden, das die Informationen zum Bild
enthält, z.B. den ladezustand. Dieser Parameter kann null sein.
werden Breite und Höhe angegeben, so wird das Bild in diese Ausmaße hinein abgebildet. Dabei kann es
vergrößert, verkleinert, verzerrt werden.
Für transparente Bilder kann der Parameter bgcolor zur Bestimmung der Hintergrundfarbe angegeben
werden.
In bestimmten Anwendungen kann es erforderlich sein, den Ladevorgang des Bildes zu beenden, bevor im
Programm weitergemacht werden kann. Dies ist z.B. notwendig, wenn die Größe des Bildes für die weiteren
Positionierung benötigt wird. Diese Aufgabe übernimmt die Klasse MediaTracker.
Für die Umsetzung sind die folgenden drei Schritte notwendig (siehe Beispiel class JavaTasse):
1. Über den Konstruktor wird ein Objekt der Klasse MediaTracker erzeugt.
2. Durch die Methode addImage(Image image, int id) wir das betreffende Image-Objekt dem
MediaTracker bekannt gemacht. Dabei bekommt es die angegebene ID-Nummer zugewiesen. Diese ID ist
frei wählbar.
3. Nach dem Aufruf der Methode getImage() wird die Methode waitForID(int id) der Klasse
MediaTracker aufgerufen. Damit wird solange gewartet, bis der Ladevorgang des Images mit der
angegebenen ID abgeschlossen ist. (Mit der Methode waitForAll() können mehrere Bilder auf einmal
überwacht werden.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
150 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Bitmaps anzeigen
import java.awt.*;
import java.awt.event.*;
import java.net.*;
public class JavaTasse extends Frame
{
public static void main(String[] args)
{
JavaTasse fenster = new JavaTasse();
}
public JavaTasse()
{
super("Heisse Tasse");
this.setSize(330,200);
this.setLocation(100,100);
this.setVisible(true);
this.setLayout(null);
this.setBackground(Color.yellow);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void paint(Graphics g)
{
URL url = null;
// 1 Objekt der Klasse MediaTracker erzeugt <<<<------------------------------MediaTracker tracker = new MediaTracker(this);
try
{
url = this.getClass().getResource("HeisseTasse.gif");
}
catch(Exception ex)
{
System.out.println(ex.getMessage());
}
Image img = getToolkit().getImage(url);
// 2 Image dem Mediatracker bekannt machen <<<<-------------------------tracker.addImage(img, 1);
try
{ // 3 warten
tracker.waitForID(1); <<<<<--------------------------------------------}catch(InterruptedException ex){}
g.drawImage(img, 20, 30, this);
g.drawImage(img, 120, 30, 60, 140, this);
g.drawImage(img, 230, 30, Color.green, this);
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
151 / 210
23.03.2010
PROMOD-2a / SS10
Bearbeiten Sie die Case Studies (kap10_awt_graphics_case_studies) und lösen
Sie die Übungsaufgeben zu diesem Kapitel! (Siehe Unterlagen hierzu auf dem
Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
152 / 210
23.03.2010
PROMOD-2a / SS10
13. AWT-Komponenten:
Was Sie lernen:
welche AWT-Komponenten es gibt
wie AWT-Komponenten definiert und verwendet werden
wie (Mensch-Maschinen-) Dialoge erstellt werden
wie Menüs erzeugt werden
Voraussetzungen:
Fensterprogrammierung
Grafikprogrammierung
Eventhandling
13.1 Grundlagen
Für die Gestaltung vonFenstern stellt AWT verschiedene Komponenten (Oberflächenelemente) bereit.
java.lang.Object
java.util.EventObject
java.awt.AWTEvent
java.awt.Component
java.awt.Container
...Choice
...Label
...Button
...List
…Canvas
java.awt.Dialog
javax.swing.JDialog
java.awt.Window
javax.awt.Panel
java.awt.Frame
javax.applet.Applet
javax.swing.JFrame
javax.swing.JComponent
javax.swing.Label
…Scrollbar
…Checkbox
…TextComponet
java.applet.Applet
javax.swing.JApplet
Die Eigenschaften und Funktionsweisen einer Komponente werden jeweils in einer Klasse gekapselt. Die Klassen
sind alle direkt oder indirekt von der Klasse Component abeleitet. Dadurch haben sie gemeinsame
Eigenschaften, wie z.B. Größe, Position und Farbe. Die Komponenten besitzen Methoden, um Listener zu
registrieren; damit können Sie Events verarbeiten. (Semantische Events.)
Komponenten können direkt auf dem Fenster (i.d. R. Frame) angeordnet werden. Wenn eine Gruppierung von
Komponenten gewünscht wird, können diese zusammengehörigen Komponenten in einem Panel angeordnet
werden. In einem Fenster (i.d.R. Frame) lassen sich dann mehrer Panels (mit jeweils mehreren Komponenten)
anordnen.
Diese Vorgehensweise hat den Vorteil, dass sich alle Komponenten eines Panels mit einer Anweisung ein- und
ausblenden lassen.
Auch bei der Arbeit mit Layout-Managern, die eine automatische Anordnung der Komponente im Container
bewirken, werden die Komponenten häufig auf Panels platziert.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
153 / 210
23.03.2010
PROMOD-2a / SS10
Container sind Fenster (Window, Frame, Dialog), Panels und Applets (siehe Bild oben).
13.2 Überblick über AWT-Komponenten:
Menüleiste (Menu, MenuBar, MenuItem)
Text (Label) mehzeiliges Listenfeld (List)
mehrzeiliges Listenfeld (TextArea)
Titelleiste
Leinwand
Bildlaufleiste
(scrollbar)
(Canvas)
einzeilige Textfeld
(TextField)
Container
(Panel)
Schaltfläche (Button)
Kontrollfeld (Checkbox) einzeiliges Listenfeld (choice)
Überblick über die AWT-Komponenten, ihre Verwendung und die relevanten Listener:
AWT-Komponente
Button
Canvas
Label
TextField
Choice
List
TextArea
Checkbox
Verwendung
Über Schaltflächen kann der Benutzer das Programm
steuern. Durch das Betätigen der Schaltfläche wird
das Programm angewiesen, bestimmte Aktionen
auszuführen.
API-DOCU: A Canvas component represents a blank
rectangular area of the screen onto which the
application can draw or from which the application can
trap input events from the user.
Ein Label dient der Anzeige von Text, den er Benutzer
nicht ändern kann. Labels werden als Beschriftung von
Textfeldern verwendet.
Über Textfelder werden Benutzereingaben und
Benutzerausgaben realisiert, die nur eine Zeile
benötigen.
Ein Listenfeld dient der Auflistung und Auswahl
vorgegebener Werte (Einfach-Selektion). Die Werte
können vom Benutzer weder verändert werden, noch
können neue Werte hinzugefügt werden.
Mehrzeilige Listenfelder dienen ebenfalls der Auflistung
und Auswahl vorgegebener Werte. Eine
Mehrfachselektion ist möglich.
Mehrzeilige Textfelder dienen der Eingabe und
Anzeige längerer Texte.
Kontrollfelder (Checkboxen) eignen sich für die
Eingabe bzw. die Anzeige von Ja/Nein-Werten durch
Listener18
ActionListener
MouseListener
KeyListener, MouseListener,
MouseMotionListener
u.v.a.m: siehe API-Doku
„add“-Methoden
KeyListener
MouseListener
TextListener
ActionListener
ItemListener
ItemListener
ActionListener
TextListener
ItemListener
18
Hier sind nicht immer alle Listener aufgelistet! Im Einzelfall also in der API-Docu der AWT-Komponentenklasse
nachsehen!!!! An den „add“-Methoden ist erkennbar, welche Listener zugeodnet werden können.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
154 / 210
23.03.2010
PROMOD-2a / SS10
das Setzen oder Entfernen der Markierung.
Optionsfelder sind mehrere Kontrollfelder
(Checkbox), die zu einer Gruppe (CheckboxGroup)
zusammengefasst sind. Von den Optionsfeldern einer
Gruppe kann nur jeweils eins aktiv sein, die Optionen
schließen sich gegenseitig aus.
Scrollbar
Bildlaufleisten dienen zum Blättern (Scrollen), z.B.
wenn der Inhalt eines Fensters nicht vollständig
angezeigt werden kann. Der eigenliche Scrollvorgang
muss jedoch manuell durchgeführt werden.
Panel
Panel sind Container, die andere Komponenten in
sich aufnehmen können. Sie dienen der Gruppierung
von Komponenten und haben eine besondere
Bedeutung im Zusammenhang mit
LayoutManagern.
Scrollpane
Diese Komponente dient er Aufnahme einer anderen
Komponente, die durch ihre Größe auf dem
verfügbaren Platz nicht vollständig angezeigt werden
kann. Mit den beiden Bildlaufleisten lassen sich die
verdeckten Bereiche der Komponente sichtbar
machen.
MenuBar
Eine Menüleiste wird direkt unter der Titelleiste in
ein Fenster eingefügt. Sie kann mehrere Menüs
besitzen, die wiederum Menüeinträge und
Untermenüs beinhalten. Die Menüeinträge dienen der
Auswahl bestimmter Aktivitäten es Programms.
PopupMenu
Ein Popup-Menü (auch als Kontext-Menü bezeichnet)
wird durch Klicken mit der rechten Maustaste
angezeigt. Allen Komponenten und dem Fenster selbst
kann ein Popup-Menü hinzugefügt werden.
xListener = Low Level Ereignis; yListener = Semantisches Ereignis
CheckboxGroup
ItemListener
AdjustmentListener
ContainerListener
ContainerListener
beim Menüeintrag wird
ActionListener
registriert
beimMenüeintrag wird
ActionListener
registriert
13.3 Anwendung / Umgang mit den Komponenten:
Die typische Verwendung von AWT-Komponenten vollzieht sich in drei Schritten:
1. Objekt der Komponenten-Klasse erzeugen
2. Eigenschaften der Komponenten festlegen
3. Komponenten-Objekt zum Container (Fenster, Panel,…) hinzufügen. Dazu wird die Methode
add() des Containers verwendet.
Hinweis: in den folgenden Beispielen wird für die Anordnung der Komponenten kein LayoutManager verwendet.
Die Komponenten werden mit der Methode setLocation() direkt an einer bestimmten Position fixiert.
Es sollten Namenskonventionen beachtet werden, damit eine eindeutige Komponentenzuordnung die
Lesbarkeit erleichtert.
Klasse
Button
Label
Textfield
Choice
List
Präfix
btn
lbl
txt
choice
lst
Klasse
Textarea
Checkbox
Scrollbar
Panel
Menue
Präfix
ta
cb
sb
pnl
mnu
13.3.1 Die Klasse Label
Ein Label dient der Anzeige von Text, den er Benutzer nicht ändern kann.
Ein Label wird für die Beschriftung von Text-, Options- und Kontrollfeldern benutzt werden.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
155 / 210
23.03.2010
PROMOD-2a / SS10
Ein Label wird über einen der folgenden drei Konstruktoren erzeugt:
public Label()
public Label(String text)
public Label(String text, int alignment)
Label ohne Beschriftung
Label mit Beschriftung
Label mit Beschriftung und Ausrichtung
Für das Alignement stehen symbolischen Konstanten bereit:
o Label.LEFT
o Label.RIGHT
o Label.CENTER
Text und Alignment des Labels lassen sich auch dynamisch zur Laufzeit mit folgenden Methoden ändern, bzw.
abfragen:
o void setText(String text)
o void setAlignment(int alignment)
o String getText()
o int getAlignment()
13.3.2 Die Klasse Button
Objekte der Klasse Button repräsentieren Schaltflächen. Über Schaltflächen kann der Benutzer das Programm
steuern. Durch das Betätigen der Schaltfläche wird das Programm angewiesen, bestimmte Aktionen auszuführen.
Zwei Konstruktoren stehen zur Verfügung:
public Button()
public Button(String text)
Schaltfläche ohne Beschriftung
Schaltfläche mitBeschriftung
Schaltflächen werden dem Benutzer angeboten, um bestimmte Aktionen auszuführen. Die Betätigung der
Schaltfläche löst ein ActionEvent-Ereignis aus.
Um das Ereignis auszuwerten wird wie folgt vorgegangen:
1. Implementieren Sie das Interface ActionListener.
2. Überlagern Sie die Methode actionPerformed() in der betreffenden Klasse (es ist die einzige Methode
des Interfaces).
3. In der Methode actionPerformed() wird die Aktion für die jeweilige Schaltfläche implementiert.
Hinweis: sind mehrer Schaltflächen in einem Container enthalten, so kann mit der Methode getActionCommand()
ermittelt werden, welche der Schaltflächen das Ereignis ausgelöst hat.
public String getActionCommand()
void setActionCommand(String command)
Liefert Zeichenkette, mit der die Schaltfläche beschriftet
ist (sofern nicht mit setActionCommand() ein anderer
Text als Action-Kommando gesetzt wurde).
Setzt den Kommandonamen, der von
getActionCommand() zurückgeliefert wird.
Beispiel – Buttons und Labels nutzen
import java.awt.*;
import java.awt.event.*;
public class ButtonFenster extends Frame implements ActionListener
{
Button btnOk, btnAbbrechen;
Label lblUeberschrift, lblMeldung;
public static void main(String[] args)
{
ButtonFenster fenster = new ButtonFenster();
}
public ButtonFenster()
{
super("Betätigen Sie eine Schaltfläche");
this.setSize(250, 120);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
this.setLayout(null);
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
156 / 210
23.03.2010
PROMOD-2a / SS10
lblUeberschrift = new Label("Schaltflächen-Test"); ////////// Label
lblUeberschrift.setFont(new Font("Serif", Font.BOLD+Font.ITALIC, 20));
lblUeberschrift.setSize(200, 30);
lblUeberschrift.setLocation(40, 25);
this.add(lblUeberschrift);
btnOk = new Button("OK");
////////////////////////////// Button
btnOk.setSize(80, 23);
btnOk.setLocation(40, 60);
this.add(btnOk);
btnOk.addActionListener(this);
// besitzt Methode actionPerformed()
btnAbbrechen = new Button("Abbrechen"); ///////////////////// Button
btnAbbrechen.setSize(80, 23);
btnAbbrechen.setLocation(130, 60);
this.add(btnAbbrechen);
btnAbbrechen.addActionListener(this);
lblMeldung = new Label("keine Schaltfläche betätigt", Label.CENTER); ////
lblMeldung.setSize(170, 23);
lblMeldung.setLocation(40, 85);
this.add(lblMeldung);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void actionPerformed(ActionEvent ae)
{
if(ae.getActionCommand().equals("OK"))
lblMeldung.setText("OK wurde betätigt");
else
lblMeldung.setText("Abbrechen wurde betätigt");
}
}
Aufgabe: Schauen Sie in der API-Dokumentation für die Klasse Button nach. Neben den hier vorgestellten
Button-Methoden gibt es zahlreiche weitere! Wieviel?
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
157 / 210
23.03.2010
PROMOD-2a / SS10
13.3.3 Die Klasse Textfeld (abgeleitet von TextComponent)
Über Textfelder werden Benutzereingaben und Benutzerausgaben realisiert, die nur eine Zeile benötigen. Im
Gegensatz zum Label kann er Benutzer die Zeichenketten ändern bzw. neue Zeichenketten eingeben.
Textfelder besitzen Methoden
um Zeichenketten zu setzen und abzufragen
um die Editierbarkeit der Zeichenkette einzustellen (zulassen, verbieten)
um die Editierbarkeit der Zeichenkette abzufragen
um die Curserposition in der Zeichenkette abzufragen und zu setzen
um Teile in der Zeichenkette zu markieren, bzw. die Position der Markierung abzufragen
um verdeckte Eingaben zu ermöglichen (z.B. für die Passworteingabe)
String getText()
void setText(String t)
void setEditable(boolean b)
boolean isEditable()
int getCaretPosition()
void setCaretPosition(int position)
void select(int selectionStart, int
selectionEnd)
void selectAll()
void setSelectionStart(int selectionStart)
int getSelectionEnd()
void setEchoChar(char c)
char getEchoChar()
boolean echoCharIsSet()
Zeichenkette abfragen
Zeichenkette setzen
Editierbarkeit erlauben, bzw. verbieten
Editierbarkeit abfragen
Cursorposition in der Zeichenkette abfragen
Cursorposition in der Zeichenkette ändern
Textmarkierung setzen
gesamte Zeichenkette markieren
setzt den Markierungsanfang auf die Startposition
liefert die Entposition des markierten Texts
setzt den für das Echo zu nutzenden Buchstaben
liefert den für das Echo zu nutzenden Buchstaben
testet, ob ein Echo-Zeichen gesetzt ist
Wird vom Benutzer ein Zeichen in das Textfeld eingegeben, so wird ein TextEvent-Ereignis ausgelöst.
o Um ein solches Ereignis abzufangen, muss ein TextListener-Interface für das Textfeld mit der
Methode addTextListener() registriert werden.
o Tritt das Ereignis ein, so wird die Methode textValueChanged(TextEvent event) aufgerufen
Betätigt der Benutzer die Enter-Taste, so wird ein ActionEvent-Ereignis ausgelöst.
Beispiel:
TextField tf1, tf2, tf3, tf4;
// a blank text field
tf1 = new TextField();
// blank field of 20 columns
tf2 = new TextField("", 20);
// predefined text displayed
tf3 = new TextField("Hello!");
// predefined text in 30 columns
tf4 = new TextField("Hello", 30);
Beispiel – TextFields nutzen
import java.awt.*;
import java.awt.event.*;
public class EchoFenster extends Frame implements ActionListener, TextListener
{
TextField txtEingabe, txtEcho;
Button btnLoeschen;
Label lblMldg;
public static void main(String[] args)
{
EchoFenster fenster = new EchoFenster();
}
public EchoFenster()
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
158 / 210
23.03.2010
PROMOD-2a / SS10
{
super("EchoFenster");
this.setSize(480, 140);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
this.setLayout(null);
txtEingabe = new TextField(); ///////////////// TextField 1
txtEingabe.setSize(300, 23);
txtEingabe.setLocation(40, 50);
add(txtEingabe);
txtEingabe.addTextListener(this);
// für Reaktion auf Zeicheneingabe
txtEingabe.addActionListener(this);
// für Reaktion auf ENTER-Taste
txtEcho = new TextField();
///////////////// TextField 2
txtEcho.setSize(300, 23);
txtEcho.setLocation(40, 100);
txtEcho.setEditable(false);
// damit erübrigen sich Listener...
add(txtEcho);
btnLoeschen = new Button("löschen"); ///////// Button
btnLoeschen.setSize(100, 23);
btnLoeschen.setLocation(360, 75);
this.add(btnLoeschen);
btnLoeschen.addActionListener(this);
lblMldg = new Label();
////////////////// Label
lblMldg.setSize(200,23);
lblMldg.setLocation(40, 75);
add(lblMldg);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void actionPerformed(ActionEvent ae) // wird vom ActionListener gerufen
{
Object object = ae.getSource();
if(object.equals(btnLoeschen))
{
txtEingabe.setText("");
txtEingabe.requestFocus();
}
else if(object.equals(txtEingabe))
{
txtEingabe.selectAll();
}
}
public void textValueChanged(TextEvent te) // wird vom TextListener gerufen
{
txtEcho.setText(txtEingabe.getText());
lblMldg.setText("aktuelle Cursorposition " + txtEingabe.getCaretPosition());
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
159 / 210
23.03.2010
PROMOD-2a / SS10
13.3.4 Die Klasse TextArea (abgeleitet von der Klasse TextComponent)
Mehrzeilige Textfelder dienen der Eingabe und Anzeige längerer Texte. Mehrzeilige Textfelder sind
standardmäßig mit Bildlaufleisten (wenn der Text breiter oder länger als die Textarea ist) ausgerüstet.
Da die Klasse TextArea wie die Klasse TextField von der Klasse TextComponent abeleitet ist, besitzen
beide Klassen gemeinsame Methoden, wie z. B. getText(), setText(), getCaretPosition(),
getSelectedText() und setEditable().
TextAreas besitzen zusätzliche Methoden
um Texte einzufügen und anzuhängen
um Teile von Texten zu ersetzen
um die Anzahl von Zeilen und Spalten zu ermitteln bzw. zu ändern
um Größeninformationen zu liefern
um zu ermitteln, welche Rollbalken genutzt werden
Methode
void insert(String str, int pos)
void append(String str)
void replaceRange(String str, int start,
int end)
int getRows(e)
void setRows(int rows)
int getColumns()
void setColumns(int columns)
Dimension getMinimumSize()
Dimension getPreferredSize()
int getScrollbarVisibility()
Wirkung
Zeichenkette str an der Position pos einfügen
Zeichenkette str anhängen
Zeichenkette von start bis end durch str ersetzten
liefert die aktuelle Zeilenanzahl
setzt die Zeilenzahl auf rows
liefert die aktuelle Spaltenanzahl
setzt die Spaltenzahl auf columns
liefert minimale Größe
liefert bevorzugte Größe
ermittlet, welcher Rollbalken genutzt wird
Wird vom Benutzer ein Zeichen in das Textfeld eingegeben, so wird auch hier ein TextEvent-Ereignis
ausgelöst. (Durch die Betätigung der Enter-Taste wird jedoch kein ActionEvent ausgelöst wie beim TextField.)
13.3.5 Die Klasse Choice und die Klasse List:
Die Klassen Choice und List repräsentieren einzeilige, bzw. mehrzeilige Listenfelder. Ein Listenfeld dient der
Auflistung und Auswahl vorgegebener Werte. Die Werte können vom Benutzer weder verändert werden, noch
kann der Benutzer neue Werte hinzugefügen.
Der Programmierer kann Werte über die add()-Methode zur Laufzeit den Listenfeldern hinzufügen. Die
Einträge werden dann in der Reihenfolge, in der sie hinzugefügt wurden, angezeigt. Eine Möglichkeit die
Anzeigenreihenfolge nachträglich zu ändern gibt es nicht.
Für Choice und List stehen u.a. folgende Methoden zur Verfügung:
Methode
Wirkung
String getSelectedItem()
liefert den ausgewählten Entrag
int getSelectedIndex()
liefert den Indes des ausgewählten Eintrags
void select(int pos)
programmgesteuerte Auswahl eines Eintrags
void select(String str)
programmgesteuerte Auswahl eines Eintrags
Wird vom Benutzer ein Eintrag aus dem Listfeld ausgewählt, so wird ein ItemEvent-Ereignis ausgelöst.
Um ein solches Ereignis abzufangen, muss ein ItemListener-Interface implementiert werden.
Beim Eintreten des Ereignisses wird die Methode itemStateChanged() aufgerufen.
Mehrzeilige Listenfelder (Klasse List) bieten mehr Funktionalität als einzeilige Listen (Klasse choice):
Wahlweise kann eine Mehrfachselektion zugelassen werden (2. Parameter des Konstruktors).
Elemente können dynamisch entfernt oder geändert werden (Methoden deItem(), remove(), replaceItem()).
Ein ActionEvent-Ereignis wird ausgelöst, wenn ein Doppelklick auf den Eintrag erfolgt.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
160 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – TextField-, List- und Choice-Komponenten
import java.awt.*;
import java.awt.event.*;
public class ListenFenster extends Frame implements ActionListener, ItemListener
{
Button btnUebernehmen;
TextField txtEingabe;
List lstEingaben;
Choice choiceEingaben;
Label lbl;
public static void main(String[] args)
{
ListenFenster fenster = new
ListenFenster();
}
public ListenFenster()
{
super("Funktionsweise von Listenfeldern");
this.setSize(400, 160);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
this.setLayout(null);
txtEingabe = new TextField("");
txtEingabe.setSize(100, 23);
txtEingabe.setLocation(40, 50);
add(txtEingabe);
btnUebernehmen = new Button("übernehmen");
btnUebernehmen.setSize(100, 23);
btnUebernehmen.setLocation(40, 90);
add(btnUebernehmen);
btnUebernehmen.addActionListener(this);
lstEingaben = new List();
lstEingaben.setSize(100, 65);
lstEingaben.setLocation(160, 50);
lstEingaben.addItemListener(this);
lstEingaben.addActionListener(this);
add(lstEingaben);
choiceEingaben = new Choice();
choiceEingaben.setSize(100, 23);
choiceEingaben.setLocation(280, 50);
choiceEingaben.addItemListener(this);
add(choiceEingaben);
lbl = new Label("");
lbl.setSize(400, 23);
lbl.setLocation(40, 120);
this.add(lbl);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void actionPerformed(ActionEvent ae) //wird vom ActionListener gerufen
{
Object object = ae.getSource();
if (object.equals(btnUebernehmen))
{
lstEingaben.add(txtEingabe.getText());
choiceEingaben.add(txtEingabe.getText());
txtEingabe.setText("");
txtEingabe.requestFocus();
}
else if(object.equals(lstEingaben))
{
lbl.setText("Doppelklick auf: " + lstEingaben.getSelectedItem());
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
161 / 210
23.03.2010
PROMOD-2a / SS10
public void itemStateChanged(ItemEvent ie) // wird vom ItemListener gerufen
{
Object object = ie.getSource();
if (object.equals(choiceEingaben))
{
lstEingaben.select(choiceEingaben.getSelectedIndex());
lbl.setText("aus Choice wurde ausgewählt: " + choiceEingaben.getSelectedItem());
}
else if(object.equals(lstEingaben))
{
choiceEingaben.select(lstEingaben.getSelectedIndex());
lbl.setText("aus List wurde ausgewählt: " + lstEingaben.getSelectedItem());
}
}
}
13.3.6 Die Klasse Checkbox:
Kontrollfelder (Checkboxen) eignen sich für die Eingabe bzw. die Anzeige von Ja/Nein-Werten durch das
Setzen oder Entfernen der Markierung. Die Darstellung OS-abhängig: z.B. ist der True-Wert unter Windows ein
Haken im Kontrollfeld und unter Linux ist es ein hervorgehobenes Quadrat.
Beschriftung neben dem Kontrollfeld erfolgt mit setLabel() oder wird dem Konstruktor mitgegeben:
cbBuch = new Checkbox();
cbBuch.setLabel("Buch");
cbCD = new Checkbox("CD", true);
Den aktuellen Zustand eines Kontrollfelds liefert die Methode boolean getState()
Kontrollfelder werden einzeln ausgewertet, d.h. das Setzen ihrer Werte hat keinen Einfluss auf die Werte anderer
Kontrollfelder.
Durch Ändern des Wertes wird ItemEvent-Ereignis wie beim Listenfeld ausgelöst.
13.3.7 Die Klasse CheckboxGroup:
Zur Realisierung von Optionsfeldern, bei denen von mehreren Werten jeweils nur einer ausgewählt werden
kann. Ein Optionsfeld wird durch eine Checkbox dargestellt, die zu einer CheckBoxGroup gehört.
Vorgehensweise:
o Vor der Definition der Optionsfelder (Checkboxen) wird die CheckBoxGroup deklariert.
o Im Konstruktor der CheckBox wird als zweiter, bzw. dritter Parameter der Name der zugehörigen
CheckBoxGroup übergeben.
o Der Status der einzelnen Checkboxen muss in diesem Fall angegeben werden.
Beispiel:
cbg = new CheckboxGroup();
cbOne = new Checkbox("one", cgb, true);
cbTwo = new Checkbox("two", cbg, false);
cbThree = new Checkbox("three", cbg, false);
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
162 / 210
23.03.2010
PROMOD-2a / SS10
13.3.8 Die Klasse Scrollbar:
Bildlaufleisten dienen zum Blättern (Scrollen), z.B. wenn der Inhalt eines Fensters nicht vollständig angezeigt
werden kann. Der eigenliche Scrollvorgang muss jedoch manuell durchgeführt werden.
Standardmäßig wird eine vertikale Bildlaufleiste erzeugt. Die Ausrichtung
der Bildlaufleiste kann aber im Konstruktor durch die symbolischen
Konstanten Scrollbar.HORIZONTAL, bzw. Scrollbar.VERTICAL
geändert werden.
Die Beziehung zwischen der Position des Bildlauffeldes und dem Verschieben des Fensterinhalts muss vom
Programm aus gesteuert werden. Festgelegt werden muss dazu
der maximale Wert es Bildlauffeldes (Methode void setMaximum(int newMax))
der minimale Wert des Bildlauffeldes (Methode void setMinimum(int newMin))
Bildlauffeld
die Anfangsposition des Bildlauffeldes (Methode void setValue(int newVal)) Schaltflächen scrollbar, bubble
(Die hier skizzierten Werte können auch dem Konstruktor mitgegeben werden.)
Klickt der Benutzer auf einen beliebigen Teil der Scrollbar, so wird ein AdjustmentEvent-Ereignis ausgelöst.
Der AdjustmentListener muss implementiert werden, wenn dieses Ereignis empfangen werden soll. Die
Klasse Scrollbar stellt die Methode addAdjustmentListener zur Registrierung es Listeners bereit.
Beim Auftreten des Ereignisses wird die Methode void adjustmentValueChanged(adjustmentEvent e)
aufgerufen. Darin wird die Anpassung des Bildausschnitts odes des Wertes durchgeführt, für den die
entsprechende Bildlaufleiste zuständig ist.
Über verschiedene Methoden lässt sich herausfinden, welcher Bereich der Bildlaufleiste angeklickt wurde:
eine der beiden Schaltflächen an den Enden der Bildlaufleiste oder
der Bereich zwischen Schaltfläche und Bildlauffeld oder
das Bildlauffeld
Beispiel – Anwendung der Klasse Scrollbar
import java.awt.*;
import java.awt.event.*;
public class ScrollFenster extends Frame implements AdjustmentListener
{
Scrollbar sbHor, sbVert;
Panel1 pnl;
int x = 0;
int y = 0;
public static void main(String[] args)
{
ScrollFenster fenster = new ScrollFenster();
}
public ScrollFenster()
{
super("ScrollFenster");
this.setSize(200, 150);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
this.setLayout(new BorderLayout());
pnl = new Panel1();
pnl.setSize(300, 400); // Panel größer als Fenster
pnl.setBackground(Color.pink);
add(pnl);
sbHor = new Scrollbar(Scrollbar.HORIZONTAL, 1, 25, 1, pnl.getWidth()); // s. u.
sbVert = new Scrollbar(Scrollbar.VERTICAL, 1, 70, 1, pnl.getHeight()); // s. u.
sbHor.addAdjustmentListener(this);
sbVert.addAdjustmentListener(this);
add("South", sbHor);
add("East", sbVert);
this.setVisible(true);
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
163 / 210
23.03.2010
PROMOD-2a / SS10
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
}
public void adjustmentValueChanged(AdjustmentEvent ae) // wird vom AdjstmentListener
{
// gerufen
int i = 1;
Object a = ae.getSource();
if(a.equals(sbHor))
{
i = sbHor.getValue() - x;
pnl.setSize((int) (pnl.getWidth() + i), pnl.getSize().height);
pnl.setLocation((int) - sbHor.getValue() +
this.getInsets().left,
(int) pnl.getLocation().getY());
x = sbHor.getValue();
}
else
{
i = sbVert.getValue() - y;
pnl.setSize(pnl.getSize().width, (int) (pnl.getHeight() + i));
pnl.setLocation((int) pnl.getLocation().getX(),
(int) - sbVert.getValue() +
this.getInsets().top);
y = sbVert.getValue();
}
}
class Panel1 extends Panel
{
public Panel1(LayoutManager layout)
{
super(layout);
}
public Panel1()
{
super();
}
public void paint(Graphics g)
{
g.setColor(Color.yellow);
g.fillRect(25,35,200,300);
g.setColor(Color.blue);
g.fillOval(70,70,130,200);
}
}
Erläuterung zum Konstruktor:
public Scrollbar(int orientation,
int value,
int visible,
int minimum,
int maximum)
throws HeadlessException
Parameters:
orientation - indicates the orientation of the scroll bar. (Orientierung)
value - the initial value of the scroll bar (Startposition)
visible - the visible amount of the scroll bar, typically represented by the size of the bubble (Größe)
minimum - the minimum value of the scroll bar
maximum - the maximum value of the scroll bar
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
164 / 210
23.03.2010
PROMOD-2a / SS10
13.3.9 Die Klasse Scrollpane
Die Klasse Scrollpane ist einem Panel ähnlich, kann aber nur eine Komponente aufnehmen.
Besitzt standardmäßig zwei Bildlaufleisten.
Diese Komponente wird eingesetzt, wenn eine andere Komponente auf Grund ihrer Größe auf dem verfügbaren
Platz nicht vollständig angezeigt werden kann.
13.4 Dialoge
Ein "Dialog" ist ein Top-Level-Fenster
Liefert Meldung oder Frage an den User und wartet auf dessen Reaktion
13.4.1 Die Klasse Dialog:
Ein Dialog ist i.d.R. ein "modales" Fenster: ein anderes Fenster der Anwendung kann nicht aktiv werden, bis
das Dialogfenster wieder geschlossen wird.
Dialog-Fenster ist einem Owner-Fenster/Vater-Fenster (hierarchisch übergeordnetes Fenster) zugeordnet
o Dialog(Frame owner, boolean modal)
o Dialog(Frame owner, String title, boolean modal)
Die Klasse Dialog wird i.d.R. für Meldungen oder zum Einstellen von Optionen eingesetzt. Üblicherweise
können diese Dialoge nicht in ihrer Größe verändert werden. Über die Methode void setResizable(boolean
resizable) kann die Größenänderung unterbunden werden.
Um ein Dialogfenster zu erzeugen, muss eine neue Klasse erstellt weren, die von der Klasse Dialog abgeleitet
wird. Die Komponenten, die in dem Dialogfenster angezeigt werden sollen, müssen vom Programm selbst
positioniert werden. Das gleiche gilt für die Reaktionen auf die Betätigung dieser Komponenten.
Beispiel – Anwendung der Klasse Dialog
import java.awt.*;
import java.awt.event.*;
public class DialogFenster extends Frame implements ActionListener
{
Button btnDlg;
public static void main(String[] args)
{
DialogFenster fenster = new DialogFenster();
}
public DialogFenster()
{
super("DialogFenster");
this.setSize(200, 130);
this.setLocation(100, 100);
this.setBackground(Color.lightGray);
this.setLayout(null);
btnDlg = new Button("Beenden?");
btnDlg.setSize(100, 23);
btnDlg.setLocation(50, 60);
this.add(btnDlg);
btnDlg.addActionListener(this);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
165 / 210
23.03.2010
PROMOD-2a / SS10
}
});
public void actionPerformed(ActionEvent ae) // wird vom ActionListener gerufen
{
MeinDialog dlg = new MeinDialog(this,
"Ende-Dialog",
"Wollen Sie die Anwendung wirklich beenden?");
if(dlg.getAntwort())
System.exit(0);
}
}
import java.awt.*;
import java.awt.event.*;
public class MeinDialog extends Dialog implements ActionListener
{
Button btnOK, btnAbbrechen;
private boolean antwort = false;
public MeinDialog(Frame owner, String titel, String mldg)
{
super(owner, titel, true);
this.setSize(300, 130);
this.setLocation(200, 150);
this.setResizable(false);
this.setLayout(null);
Label lblMldg = new Label(mldg);
lblMldg.setSize(260, 25);
lblMldg.setLocation(20, 40);
add(lblMldg);
btnOK = new Button("OK");
btnOK.setSize(70, 25);
btnOK.setLocation(70, 80);
this.add(btnOK);
btnOK.addActionListener(this);
btnAbbrechen = new Button("Abbrechen");
btnAbbrechen.setSize(70, 25);
btnAbbrechen.setLocation(160, 80);
this.add(btnAbbrechen);
btnAbbrechen.addActionListener(this);
this.setVisible(true);
}
public void actionPerformed(ActionEvent ae)
{
Object object = ae.getSource();
if(object.equals(btnOK))
antwort = true;
setVisible(false);
dispose();
}
public boolean getAntwort()
{
return antwort;
}
// wird vom ActionListener gerufen
}
13.4.2 Der Dateidialog (Klasse FileDialog)
Im Package java.awt steht ein vordefinierter Dialog zur Verfügung, der die Auswahl von Dateinamen und
Verzeichnissen ermöglicht, um Dateien zu lesen oder zu schreiben.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
166 / 210
23.03.2010
PROMOD-2a / SS10
Die Klasse FileDialog ist von der Klasse Dialog abgeleitet.
Erzeugt wird der Dataiedialog mit einem von drei zur Verfügung stehenden Konstruktoren.
FileDialog (Frame parent)
Dialogfenster zum Dateiladen
FileDialog(Frame parent, String title)
Dialogfenster zum Dateiladen mit Titel
FileDialog(Frame parent, String title, int mode) Dialogfenster mit Titel zum Laden oder
Speichern (FileDialog.LOAD,
FileDialog.SAVE)
Durch show() wird der Dateidialog angzeigt:
1. FileDialog dlg = new FileDialog(this,"Öffnen",FileDialog.LOAD);
2. dlg.show();
oder
3. FileDialog dlg = new FileDialog(this,"Speichern",FileDialog.SAVE);
4. dlg.show();
Über die folgenden Methoden kann auf den Namen der Datei, bzw. des Verzeichnisses zugegriffen werden.
(Achtung! Die Dateiverwaltung ist stark OS-spezifisch: Aufbau von Dateinamen, Aufbau der Verzeichnisstruktur,
u.a.m.)
public String getDirectory()
Liefert das ausgewählte Verzeichnis als String zurück.
public void setDirectroy(String dir)
Setzt das Verzeichnis auf den als String mitgegebenen
Namen.
public String getFile()
Liefert den ausgewählten Dateinamen als String zurück.
public void setFile(String file)
Setzt den Dateinamen auf den als String mitgegebenen
Namen.
Vor Aufruf des Dialogs kann über die set-Methoden ein Verzeichnis-Name und ein File-Name voreingestellt
werden.
Die get-Methoden werden nach Beendigung des Dialogs aufgerufen und liefern den ausgewählten
Verzeichnisnamen und den Dateinamen.
Das tatsächliche Öffnen, Lesen und Schreiben muss dann im Programm noch vorgenommen werden.
Beispiel: Siehe weiter unten MenueFenster.java
Beispiel – Anwendung der Klasse FileDialog
import java.awt.*;
import java.awt.event.*;
public class OpenDialogFenster extends Frame implements ActionListener
{
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
167 / 210
23.03.2010
PROMOD-2a / SS10
}
Button btnOeffnen;
Label lbl;
public static void main(String[] args)
{
OpenDialogFenster fenster = new OpenDialogFenster();
}
public OpenDialogFenster()
{
super("DialogFenster");
this.setSize(400, 130);
this.setLocation(100, 100);
this.setBackground(Color.lightGray);
this.setLayout(null);
btnOeffnen = new Button("Öffnen");
btnOeffnen.setSize(100, 23);
btnOeffnen.setLocation(40, 60);
this.add(btnOeffnen);
btnOeffnen.addActionListener(this);
lbl = new Label();
lbl.setSize(360, 23);
lbl.setLocation(20, 90);
this.add(lbl);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void actionPerformed(ActionEvent ae)
{
FileDialog dlg = new FileDialog(this, "Öffnen", FileDialog.LOAD);
dlg.setVisible(true);
lbl.setText("Datei " + dlg.getDirectory() + dlg.getFile() + " ausgewählt");
}
13.5 Menüs:
Menüs sollen die Funktionalität der Anwendung übersichtlich und sinnvoll gruppieren.
Menüs bestehen aus einer Menüleiste, die bestimmte Menüpunkte (Menüs) enthält.
Menüs werden durch Anklicken geöffnet und enthalten wiederum Menüeinträge und Untermenüs.
Einzelne Bestandteile von Menüs sind Klassen
Menü (Menu)
Menüleiste (MenuBar)
Menüeintrag (MenuItem)
(CheckboxMenuItem)
Trennstrich (Separator)
(Menu.addSeparator())
Tastenkombinationen (MenuShortcut)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
168 / 210
23.03.2010
PROMOD-2a / SS10
1.
2.
3.
4.
5.
Die Erstellung eines Menüs erfolgt in folgenden Schritten
Menüleiste erstellen und dem Fenster hinzufügen
Dann ein Menü erstellen und der Menüleiste hinzufügen
Dann Menüeintrag erzeugen un dem Menü hinzufügen
Schritt 3 so lange wiederholen, bis alle Menüeinträge für ein Menü erstellt sind
ab Schritt 2 so lange wiederholen, bis alle Menüs (Sub- und Popup-Menüs) erstellt sind
13.5.1 Die Menüleisten (Klasse MenuBar)
Die Menüleiste wird über den parameterlosen Konstruktor der Klasse MenuBar erzeugt:
Konstruktor:
MenuBar mnuBar = new MenuBar();
this.setMenuBar(mnuBar); // Methode der Klasse Frame
Wird automatisch unter der Titelleiste positioniert
Die Menüleiste ist zunächst leer
Menüs hinzufügen Æ add()
Menüs wegnehmen Æ remove()
Anzeige in der Reihenfolge des Hinzufügens
13.5.2 Die Menüs (Klasse Menu)
Ein Menü gruppiert ein oder mehrere Menüeinträge.
Dem Konstruktor wird die Beschriftung des Menüs als Parameter mitgegeben:
Menu mnu = new Menu("Datei");
mnuBar.add(mnu);
Durch die Methode add() der Klasse MenuBar wird das Menü zur Menüleiste hinzugefügt.
Das Menü ist zunächst leer.
Menüeinträge hinzufügen Æ add(), oder Æ insert()
Menüeinträge wegnehmen Æ remove()
Trennstriche zwischen den Menüpunkten werden mit addSeparator() oder insertSeparator() eingefügt.
Anmerkung: durch add() wird die Reihenfolge der Menüeinträge definiert. Durch insert() kann ein Menüeintrag
an bestimmter Indexposition eingefügt werden. (Erster Menüeintrag hat den Index 0.)
13.5.3 Die Menü-Einträge (Klasse MenueItem)
Menüeinträge erzeugen
Die Beschriftung eines Menüeitrags wird dem Konstruktor als Parameter übergeben:
MenuItem mi = new MenuItem("Öffnen");
mnu.add(mi);
Als zweiter Parameter im Konstruktor kann eine Tastenkombination (als Shortcut zum Aktiviern des
Menüpunkts) übergeben werden.
o Diese Tastenkombination wird in Form eines Objekts der Klasse MenuShortcut definiert.
o Erzeugt wird ein solches Objekt durch den Aufruf des Konstruktors der Klasse MenuShortcut.
o Als Parameter wird der Code der Taste übergeben, die in Verbindung mit der STRG-Taste den Shortcut
ergibt.
o Für den Tastencode können die symbolischen Konstanten der Klasse KeyEvent verwendet werden
(„virtual key code“). (Java lässt für Shortcats nur alphanumerische Tasten zu.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
169 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel: MenuItem mi = new MenuItem(„Öffnen“, new MenuShortcut(KeyEvent.VK_O));
Wählt er Benutzer einen Menüeintrag aus, so wird ein ActionEvent-Ereignis ausgelöst. Soll das Ereignis für
den Menüeintrag verarbeitet werden, so muss der ActionListener registriert werden.
o mi.addActionListener(this);
o In der Methode actionPerformed() erfolgt die Verarbeitung aller ActionEvent-Ereignisse, die
auch durch eine Schaltfläche oder ein Listenfeld ausgelöst werden können.
o Es gilt zunächst herauszufinden, welches Objekt das Ereignis ausgelöst hat.
o Die Methode getLabel() liefert die Beschriftung des Menüeintrags.
o Besser: alternativ liefert die Methode getSource() die Ereignisquelle.
Beispiel:
public void actionPerformed(ActionEvent ae)
{
Object o = actionevent.getSource();
if (((MenuItem)o).getLabel() == “Öffnen”)
. . .
Menüeinträge besitzen die Eigenschaft enabled, diese Eigenschaft gibt an, ob der Menüeintrag ausgewählt
werden darf oder nicht.
Inaktive Menüeinträge werden grau dargestellt und lassen sich nicht aktivieren.
Über die Methoden setEnabled() bzw. isEnabled() kann der Wert gesetzt bzw. abgefragt werden.
o mi.setEnabled(false);
Menüeinträge markieren
Markierungen in Menüeinträgen werden für das Setzen von Optionen verwendet.
Ist die Menüoption gesetzt,so ist der Menüeintrag links neben der Beschriftung durch eine (OS-abhänige)
Markierung gekennzeichnet (Häckchen, erhabenes Quadrat, …)
Zur Erzeugung optionaler Menüeinträge wird die Klase checkboxMenuItem verwendet (abgeleitet von
MenuItem)
Die Klasse hat zusätzliche Methoden, um die Statusinformationen zu verarbeiten:
void setState(boolean b)
Setzt den Status der Option auf den übergebenen Wert b.
boolean getState()
Liefert den aktuellen Status zurück.
Wird ein optonaler Menüeintrag ausgewählt, so wird ein ItemEvent-Ereignis ausgelöst (wie bei Checkboxen
oder Listenfeldern).
o Ist die Verarbeitung des Ereignisses notwendig, so muss der ItemEvent-Listener beim Menüeintrag
registriert werden.
Untermenüs
Für die Erzeugung des Untermenüs wird dem Menü statt eines Menüeintags ein weiteres Menü hinzugefügt.
An diese Menü werden dann die Menüeinträge angehängt. (Siehe Beispiel unten.)
mnuBar = new MenuBar();
this.setMenuBar(mnuBar);
// Menüleiste erzeugen
// und zum Fenster hinzufügen
mnu = new Menu("Grafik"); // Menü „Graphik“ erzeugen
mnuBar.add(mnu);
// und zur Menüleiste hinzufügen
mnu2 = new Menu("Farbe"); // Unter-Menü “Farbe” erzeugen
mnu.add(mnu2);
// und zum Menü
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
170 / 210
23.03.2010
PROMOD-2a / SS10
mi = new MenuItem("rot"); // 1. Menüeintrag für das Unter-Menü “Farbe” erzeugen
mnu2.add(mi);
// und zum Unter-Menü hinzufügen
Beispiel – Anwendung von Menüs
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class MenueFenster extends Frame implements ActionListener, ItemListener
{
Label lbl;
boolean autosave = true;
public static void main(String[] args)
{
MenueFenster fenster = new MenueFenster();
}
public MenueFenster()
{
super("Fenster mit Menü");
this.setSize(400, 130);
this.setLocation(100, 100);
this.setLayout(null);
MenuBar mnuBar = new MenuBar();
this.setMenuBar(mnuBar);
Menu mnu = new Menu("Datei"); //----- erstes Meneue ------------------mnuBar.add(mnu);
MenuItem mnuItem = new MenuItem("Öffnen", new MenuShortcut(KeyEvent.VK_O));
mnuItem.addActionListener(this);
mnu.add(mnuItem);
mnuItem = new MenuItem("Speichern", new MenuShortcut(KeyEvent.VK_S));
mnuItem.addActionListener(this);
mnu.add(mnuItem);
mnu.addSeparator();
CheckboxMenuItem cbmnuItem = new CheckboxMenuItem("automatisch Speichern",
autosave);
cbmnuItem.addItemListener(this);
mnu.add(cbmnuItem);
mnu.addSeparator();
mnuItem = new MenuItem("Beenden");
mnuItem.addActionListener(this);
mnu.add(mnuItem);
mnu = new Menu("Info"); //---------- zweites Menue ------------------mnuBar.add(mnu);
mnuItem = new MenuItem("Info", new MenuShortcut(KeyEvent.VK_I));
mnuItem.addActionListener(this);
mnu.add(mnuItem);
cbmnuItem = new CheckboxMenuItem("Hintergrund weiß", true);
cbmnuItem.addItemListener(this);
mnu.add(cbmnuItem);
lbl = new Label();
lbl.setSize(360, 23);
lbl.setLocation(20, 90);
this.add(lbl);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void actionPerformed(ActionEvent ae)
{
Object o = ae.getSource();
if(((MenuItem)o).getLabel().equals("Öffnen"))
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
171 / 210
23.03.2010
PROMOD-2a / SS10
{
FileDialog dlg = new FileDialog(this, "Datei öffnen", FileDialog.LOAD);
dlg.setVisible(true);
lbl.setText("Datei " + dlg.getDirectory() + dlg.getFile() + " ausgewählt");
}
else if(((MenuItem)o).getLabel().equals("Speichern"))
{
FileDialog dlg = new FileDialog(this, "Datei speichern unter", FileDialog.SAVE);
dlg.show();
lbl.setText("speichen unter " + dlg.getDirectory() + dlg.getFile());
}
else if(((MenuItem)o).getLabel().equals("Beenden"))
{
MeinDialog dlg = new MeinDialog(this, "Ende-Dialog",
"Wollen Sie die Anwendung wirklich beenden?");
if(dlg.getAntwort())
System.exit(0);
}
else if(((MenuItem)o).getLabel().equals("Info"))
{
MeinDialog dlg = new MeinDialog(this, "Info",
"Dieses Programm enthält ein Menü");
}
}
}
public void itemStateChanged(ItemEvent ie)
{
CheckboxMenuItem cmi = (CheckboxMenuItem) ie.getSource();
if(cmi.getLabel().equals("Hintergrund weiß"))
if (cmi.getState())
setBackground(Color.white);
else
setBackground(Color.lightGray);
else if(cmi.getLabel().equals("automatisch Speichern"))
autosave = cmi.getState();
}
Zugehörige Klasse MeinDialog
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
172 / 210
23.03.2010
PROMOD-2a / SS10
import java.awt.*;
import java.awt.event.*;
public class MeinDialog extends Dialog implements ActionListener
{
Button btnOK, btnAbbrechen;
private boolean antwort = false;
public MeinDialog(Frame owner, String titel, String mldg)
{
super(owner, titel, true);
this.setSize(300, 130);
this.setLocation(200, 150);
this.setResizable(false);
this.setLayout(null);
Label lblMldg = new Label(mldg);
lblMldg.setSize(260, 25);
lblMldg.setLocation(20, 40);
add(lblMldg);
btnOK = new Button("OK");
btnOK.setSize(70, 25);
btnOK.setLocation(70, 80);
this.add(btnOK);
btnOK.addActionListener(this);
btnAbbrechen = new Button("Abbrechen");
btnAbbrechen.setSize(70, 25);
btnAbbrechen.setLocation(160, 80);
this.add(btnAbbrechen);
btnAbbrechen.addActionListener(this);
this.setVisible(true);
}
public void actionPerformed(ActionEvent ae)
{
Object object = ae.getSource();
if(object.equals(btnOK))
antwort = true;
setVisible(false);
dispose();
}
public boolean getAntwort()
{
return antwort;
}
}
13.5.4 Kontextmenüs (auch Popup-Menüs genannt)
Kontextmenüs erscheinen, wenn mit der rechten Maustaste auf eine Komponente bzw. ein Fenster geklickt
wird. (Achtung: in Abhängigkeit vom
Betriebssystem kann der „popUpTrigger“
auch die mittlere Maustaste sein!)
Erzeugt wird ein Kontextmenü durch die
Klasse PopupMenu, die von der Klasse
Menu abgeleitet ist.
Es sind für die Kontext-Menüerstellung die
gleichen Schritte notwendig, wie für die
Erstellung eines Menüs (ohne Menüleiste).
Die Erstellung eines Kontextmenüs erfolgt in folgenden Schritten
1. Die Komponente, an die das Kontextmenü durch die Methode add() gebunden ist, muss
Mausereignisse empfangen können. Dazu wird die Methode enableEvents() aufgerufen. Siehe
unten 1)
2. Die Component-Methode processMouseEvent() muss überschrieben werden. Siehe unten 2)
3. Durch die MouseEvent-Methode isPopUpTrigger() muss abgefragt werden, ob es sich um das
Ereignis handelt, welches zum Anzeigen des Kontextmenüs führen soll. Siehe unten 3)
4. Das Kontextmenü wird durch den Aufruf der Methode show() angezeigt. Siehe unten 4)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
173 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Anwendung von Kontextmenüs
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class KontextmenueFenster extends Frame implements ActionListener, ItemListener
{
PopupMenu pmnu;
boolean autosave = true;
Label lbl;
public static void main(String[] args)
{
KontextmenueFenster fenster = new KontextmenueFenster();
}
public KontextmenueFenster()
{
super("Fenster mit Kontextmenü");
this.setSize(400, 130);
this.setLocation(100, 100);
this.setLayout(null);
pmnu = new PopupMenu();
this.add(pmnu);
MenuItem mnuItem = new MenuItem("Datei Öffnen");
mnuItem.addActionListener(this);
pmnu.add(mnuItem);
mnuItem = new MenuItem("Datei Speichern");
mnuItem.addActionListener(this);
pmnu.add(mnuItem);
pmnu.addSeparator();
CheckboxMenuItem cbmnuItem = new CheckboxMenuItem("automatisch Speichern",
autosave);
cbmnuItem.addItemListener(this);
pmnu.add(cbmnuItem);
pmnu.addSeparator();
mnuItem = new MenuItem("Beenden");
mnuItem.addActionListener(this);
pmnu.add(mnuItem);
lbl = new Label();
lbl.setSize(360, 23);
lbl.setLocation(20, 90);
add(lbl);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
this.enableEvents(AWTEvent.MOUSE_EVENT_MASK); // 1) Siehe oben die 4 Schritte
}
public void processMouseEvent(MouseEvent me)
{
if(me.isPopupTrigger())
pmnu.show(me.getComponent(), me.getX(), me.getY());
// 2)
// 3)
// 4)
}
public void actionPerformed(ActionEvent ae)
{
Object o = ae.getSource();
if(((MenuItem)o).getLabel().equals("Datei Öffnen"))
{
FileDialog dlg = new FileDialog(this, "Datei öffnen", FileDialog.LOAD);
dlg.setVisible(true);
lbl.setText("Datei " + dlg.getDirectory() + dlg.getFile() + " ausgewählt");
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
174 / 210
23.03.2010
PROMOD-2a / SS10
else if(((MenuItem)o).getLabel().equals("Datei Speichern"))
{
FileDialog dlg = new FileDialog(this, "Datei speichern unter", FileDialog.SAVE);
dlg.setVisible(true);
lbl.setText("speichen unter " + dlg.getDirectory() + dlg.getFile());
}
else if(((MenuItem)o).getLabel().equals("Beenden"))
{
MeinDialog dlg = new MeinDialog(this, "Ende-Dialog",
"Wollen Sie die Anwendung wirklich beenden?");
if(dlg.getAntwort())
System.exit(0);
}
else if(((MenuItem)o).getLabel().equals("Info"))
{
MeinDialog dlg = new MeinDialog(this, "Info",
"Dieses Programm enthält ein Menü");
}
}
}
public void itemStateChanged(ItemEvent ie)
{
CheckboxMenuItem cmi = (CheckboxMenuItem) ie.getSource();
if(cmi.getLabel().equals("automatisch Speichern"))
autosave = cmi.getState();
}
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu
diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
175 / 210
23.03.2010
PROMOD-2a / SS10
14. LayoutManager (ein Überblick)
Was Sie lernen:
welche Aufgaben LayoutManager erfüllen
welche LayoutManager es gibt
wie LayoutManager eingesetzt werden
Voraussetzungen:
Fensterprogrammierung
Umgang mit AWT-Komponenten
14.1 Grundlagen
Die Komponenten von AWT bzw. von Swing können durch direkte Angabe der Position und Größe im Fenster
platziert warden (so genanntes Null-Layout).
Das Null-Layout ist sinnvoll, wenn das Programm nur unter einem Betriebssystem laufen soll oder eine exakte
Positionierung erforderlich ist.
Bei einem Einsatz unter anderem Betriebssystem können sich Verschiebungen und Größenänderungen
ergeben. Aus diesem Grund wurde in Java für die Anordnung der Komponenten in einem Fenster bzw. Container
ein besonderes Konzept entwickelt: die LayoutManager.
LayoutManager haben folgende Eigenschaften:
o Man kann jeder Container-Komponente einen LayoutManager zuordnen.
o Die Positionierung und Festlegung der Größe der Komponente erfolgt über den LayoutManager
auf allen Plattformen in der gleichen Art und Weise.
ƒ Die Größe der Komponenten wird vom LayoutManager festgelegt. Einige LayoutManager
greifen auf Eigenschaften der Komponenten zurück (über die Component-Methoden
getPreferedSize(), getMinimumSize(), getMaximumSize()).
Die Position und Größe der Komponenten wird ggf. an die Fenstergröße angepasst, wenn diese verändert
wird.
Container können mehrfach verschachtelt werden. Da jeder Container einen eigenen LayoutManager besitzt,
ist somit eine vielfältige Oberflächengestaltung möglich.
Um LayoutManager einzusetzten, muss das Packet java.awt.* importiert werden.
Hinweis: In manchen Programmierumgebungen (z. B. JBuilder) stehen noch weitere LayoutManager zur Verfügung.
Hierbei handelt es sich um proprietäre LayoutManger, die nicht zum Sprachumfang gehören.
Layout-Manager
FlowLayout
(Für Panels und der davon
abgeleiteten Klasse Applet ist das
FlowLayout voreingestellt.)
BorderLayout
(Für Fenster (Frame, Window) ist das
BorderLayout voreingestellt.)
Prof. Illik
Erläuterung
Ordnet die hinzugefügten
Komponenten nebeneinander in
ihrer bevorzugten Größe
(getPreferedSize()) an. Passt
eine neue Komponente nicht
mehr in eine Zeile, sow wird eine
neue Zeile begonnen.
Ordnet die hinzugefügte
Komponente an den
Außenseiten und in der Mitte
des Containers an. Dadurch
können maximal fünf
Komponenten platziert werden.
Beispiel
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
176 / 210
23.03.2010
PROMOD-2a / SS10
GridLayout
Ordnet die hinzugefügten
Komponenten in einem Gitter mit
vorgegebener Anzahl von
Spalten und Zeilen an.
GridBagLayout
Ordnet die hinzugefügten
Komponenten in einem Gitter
an. Eine Komponente kann
mehrere Zellen eines Gitters
einnehmen. Das Gitter passt
sich ggf. an.
CardLayout
BoxLayout
Die eigentliche Ausrichtigung der
Komponenten erfolgt über das
GridBagConstraints Objekt,
welches beim Hinzufügen zum
Container anzugeben ist.
Ordnet die hinzugefügten
Komponenten übereinander in
Form eines Stapels an. Alle
unteren Komponenten werden
durch die oberste verdeckt. Über
Methoden der Klasse kann
jeweils die nächste oder
vorherige Komponente
angezeigt werden.
Ordnet die hinzugefügten
Komponenten wahlweise in einer
Zeile oder Spalte im Container
an. Die voreingestellte maximale
Größe der Komponentewird
nicht geändert.
OverlayLayout
Dieser Manager wurde für Swing
entwickelt, kann aber auch im
AWT eingesetzt werden. Dazu
muss das Package
javax.swing.* importiert
werden.
Nur für Swing.
SpringLayout
Nur für Swing.
Damit können mehrere Componenten
übereinander angeordnet werden.
Durchscheinend.
… added in JDK version 1.4 to support layout
in GUI builders….very low-level and as such
you really should only use it with a GUI
builder….
Siehe auch: http://java.sun.com/docs/books/tutorial/uiswing/layout/
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
177 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Das FlowLayout
import java.awt.*;
import java.awt.event.*;
public class FlowLayoutFenster extends Frame
{
Button btn1, btn2, btn3;
}
public static void main(String[] args)
{
FlowLayoutFenster fenster = new FlowLayoutFenster();
}
public FlowLayoutFenster()
{
super("Anordnung im FlowLayout");
this.setSize(250, 80);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
this.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10));
btn1 = new Button("Nr. 1");
this.add(btn1);
btn2 = new Button("Nr. 2");
this.add(btn2);
btn3 = new Button("Nr. 3");
this.add(btn3);
pack();
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
178 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Das BorderLayout
import java.awt.*;
import java.awt.event.*;
public class BorderLayoutFenster extends Frame
{
Button btn1, btn2, btn3, btn4, btn5;
public static void main(String[] args)
{
BorderLayoutFenster fenster = new BorderLayoutFenster();
}
public BorderLayoutFenster()
{
super("Anordnung im BorderLayout");
this.setSize(250, 100);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
this.setLayout(new BorderLayout());
btn1 = new Button("Norden");
this.add("North", btn1);
btn2 = new Button("Westen");
this.add("West", btn2);
btn3 = new Button("Osten");
this.add(btn3, BorderLayout.EAST);
btn4 = new Button("Süden");
this.add(btn4, BorderLayout.SOUTH);
btn5 = new Button("Zentrum");
this.add("Center", btn5);
pack();
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
179 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Das GridLayout
import java.awt.*;
import java.awt.event.*;
public class GridLayoutFenster extends Frame
{
Button btn1, btn2, btn3, btn4, btn5, btn6;
public static void main(String[] args)
{
GridLayoutFenster fenster = new GridLayoutFenster();
}
public GridLayoutFenster()
{
super("Anordnung im GridLayout");
this.setSize(250, 100);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
GridLayout gl = new GridLayout(0, 3);
this.setLayout(gl);
btn1 = new Button("Nr. 1");
this.add(btn1);
btn2 = new Button("Nr. 2");
this.add(btn2);
btn3 = new Button("Nr. 3");
this.add(btn3);
btn4 = new Button("Nr. 4");
this.add(btn4);
btn5 = new Button("Nr. 5");
this.add(btn5);
btn6 = new Button("Nr. 6");
this.add(btn6);
}
pack();
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
gl.setColumns(2);
pack();
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
180 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Das GridBagLayout
import java.awt.*;
import java.awt.event.*;
public class GridBagLayoutFenster extends Frame
{
Button btn1, btn2, btn3, btn4, btn5, btn6;
public static void main(String[] args)
{
GridBagLayoutFenster fenster = new GridBagLayoutFenster();
}
public GridBagLayoutFenster()
{
super("Anordnung im GridBagLayout");
this.setSize(250, 100);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
GridBagLayout gbl = new GridBagLayout();
this.setLayout(gbl);
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(1, 1, 1, 1);
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 1;
btn1 = new Button("Nr. 1");
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.gridx = 0;
gbc.gridy = 0;
gbl.setConstraints(btn1, gbc);
this.add(btn1);
btn2 = new Button("Nr. 2");
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.gridx = 0;
gbc.gridy = 1;
gbl.setConstraints(btn2, gbc);
this.add(btn2);
btn3 = new Button("Nr. 3");
gbc.gridwidth = 1;
gbc.gridheight = 2;
gbc.gridx = 1;
gbc.gridy = 0;
gbc.weightx = 2;
gbl.setConstraints(btn3, gbc);
this.add(btn3);
btn4 = new Button("Nr. 4");
gbc.gridwidth = 2;
gbc.gridheight = 1;
gbc.gridx = 0;
gbc.gridy = 2;
gbc.weightx = 1;
gbl.setConstraints(btn4, gbc);
this.add(btn4);
btn5 = new Button("Nr. 5");
gbc.gridwidth = 1;
gbc.gridheight = 1;
gbc.gridx = 2;
gbc.gridy = 0;
gbl.setConstraints(btn5, gbc);
this.add(btn5);
btn6 = new Button("Nr. 6");
gbc.gridwidth = 1;
gbc.gridheight = 2;
gbc.gridx = 2;
gbc.gridy = 1;
gbl.setConstraints(btn6, gbc);
this.add(btn6);
//pack();
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
181 / 210
23.03.2010
PROMOD-2a / SS10
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
}
Beispiel – Das CardLayout
import java.awt.*;
import java.awt.event.*;
public class CardLayoutFenster extends Frame implements ActionListener
{
Button btn1, btn2, btn3, btn4;
CardLayout cl = new CardLayout();
public static void main(String[] args)
{
CardLayoutFenster fenster = new CardLayoutFenster();
}
public CardLayoutFenster()
{
super("Anordnung im CardLayout");
this.setSize(250, 100);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
this.setLayout(cl);
btn1 = new Button("Nr. 1");
this.add("First", btn1);
btn1.addActionListener(this);
btn2 = new Button("Nr. 2");
this.add("Second", btn2);
btn2.addActionListener(this);
btn3 = new Button("Nr. 3");
this.add("Third", btn3);
btn3.addActionListener(this);
btn4 = new Button("Nr. 4");
this.add("Fourth",btn4);
btn4.addActionListener(this);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
public void actionPerformed(ActionEvent ae)
{
cl.next(this);
}
}
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
182 / 210
23.03.2010
PROMOD-2a / SS10
Beispiel – Das BoxLayout
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class BoxLayoutFenster extends JFrame
{
JButton btn1, btn2, btn3;
public static void main(String[] args)
{
BoxLayoutFenster fenster = new BoxLayoutFenster();
}
public BoxLayoutFenster()
{
super("Anordnung im BoxLayout");
this.getContentPane().setLayout(new BorderLayout());
this.setSize(280, 90);
this.setLocation(100, 100);
this.setBackground(Color.cyan);
JPanel p = new JPanel();
p.setBackground(Color.yellow);
this.getContentPane().add(p);
BoxLayout bl = new BoxLayout(p, BoxLayout.X_AXIS);
p.setLayout(bl);
btn1 = new JButton("Nr. 1");
btn1.setAlignmentY(1.0f);
p.add(btn1);
p.add(Box.createHorizontalGlue());
btn2 = new JButton("Nummer 2");
btn2.setAlignmentY(0.5f);
p.add(btn2);
p.add(Box.createHorizontalGlue());
btn3 = new JButton("Nr. 3");
btn3.setAlignmentY(0.0f);
p.add(btn3);
this.setVisible(true);
this.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we)
{
System.exit(0);
}
});
}
}
Bearbeiten Sie die Case Studies und lösen Sie die Übungsaufgeben zu
diesem Kapitel! (Siehe Unterlagen hierzu auf dem Materialserver.)
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
183 / 210
23.03.2010
PROMOD-2a / SS10
(Sem-Projekt 3. JDBC)
15. Elementare Dienste für JEE: JDBC
Was Sie lernen:
wie die Architektur von JDBC aussieht
wie Sie über Java auf Datenbanken zugreifen
wie Anfragen an eine Datenbank gerichtet und Ergebnisse ausgewertet werden
Voraussetzungen:
Java Fundamentals
Datenbank-Grundlagen
Literatur: Siehe Literaturverzeichnis
Goll/Weiß/Müller: “Tigerbuch”
Wutka: J2EE Developer’s Guide
http://knol.google.com/k/j-anton-illik/jdbc-ein-elementarer-dienst-fr-jee-how/2rmlvpldf2pey/2#
15.1 Hinführung
Die Notwendigkeit, die SQL-Sprache einzubetten, ergibt sich daraus, dass es sich bei SQL um eine rudimentäre
(non computational complete) Programmiersprache handelt, die allein nicht ausreicht, um komplexe
Datenbankanwendungen zu schreiben.
Eine Möglichkeit, Datenbankanwendungen zu schreiben, bietet die Einbettung der SQL-Sprache in prozedurale
und objektorientierte Programmiersprachen wie wie C/C++/C# und Java. Man spricht dann von Embedded SQL.
Dabei wird ein Vorübersetzer/Präprozessor verwendet, der jede SQL-Anweisung übersetzt. Nach der
Vorübersetzerphase wird ein Programm mit einem einheitlichen Code erzeugt, das anschließend übersetzt
werden kann. (Die Programmiersprache, in die SQL eingebettet wird, in unserem Fall Java, wird Host-Sprache
genannt.)
Eine andere Möglichkeit der SQL-Nutzung in Java läuft über eine Aufrufschnittstelle (Call Library Interface):
hier werden native Klassen und Methoden der Sprache speziell für den Umgang mit einer Datenbank zur
Verfügung gestellt.
Für die Nutzen von Datenbanken aus einer Programmiersprache wie Java gibt es also zwei verschiedene Ansätze:
den Embedded SQL Ansatz SQLJ und
den CLI (Call Library Interface) Ansatz von JDBC.
Wir betrachten zunächst den CLI-Ansatz von JDBC.
15.2 Grundlagen
Einer (von mehreren) elementaren Java-Dienst für JavaEE ist JDBC. Im JavaEE-Umfeld wird JDBC an
verschiedenen Stellen genutzt: z.B. in JSP/Servlets und insbesondere in EJBs (Enterprise Java Beans).
JDBC, Java Database Connectivity, ist ist eine Java API und Teil des Java JDK. Es wird dazu benutzt Daten
aus einer beliebigen Tabellenstruktur herauszulesen. Dabei sind nicht nur Datenbanktabellen gemeint,
sondern auch auch normale Dateien oder Datenquellen, die Tabellenstrukturen aufweisen. In erster Linie
bietet es aber die Möglichkeit, insbesondere Datenbankapplikationen komplett in Java zu schreiben. JDBC wird
sehr oft dazu benutzt, SQL Befehle an die Datenbank weiterzuleiten. Der große Vorteil dabei ist, dass es genügt,
ein (einziges) Programm für alle Arten von Datenbanken zu schreiben. Mit JDBC kann also ein einzelnes
Programm z.B. auf eine Oracle-, eine IBM DB2- oder eine MySQL-Datenbank zugreifen, ohne dass etwas im
Code dafür geändert werden muss. Zusammen mit den Vorteilen der Programmiersprache Java ist ein JDBC
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
184 / 210
23.03.2010
PROMOD-2a / SS10
basiertes Java Programm in der Lage, Clienten auf einer beliebigen Plattform (egal ob beispielsweise Windows,
Macintosh oder eine UNIX Maschine) mit einer beliebigen Datenbank zu verbinden.
Die JDBC API besteht eigentlich aus zwei Teilen. Das Kern-JDBC (java.sql.*) wird mit dem Java Standard
Development Kit geliefert. JavaEE entält zusätzlich das optionale JDBC-Paket javax.sql.*, das einige
zusätzliche serverseitige Features enthält, die in der JavaEE-Entwicklung häufiger gebraucht werden, vor allem
im Bereiche der Enterprise JavaBeans. Schlagworte hierfür:
o
o
o
o
DataSource,
Connection-Pooling,
verteilte Transaktionen,
RowSets (erweitert das ResultSet-Interface)
Moderne Software-Architekturen bestehen aus mindestens drei Ebenen:
o Der Hardware am nächsten ist die Datenebene,
o darüber angesiedelt ist die Geschäftslogik-Ebene
o und dann folgt die Präsentatinsebene.
Die Dateneben wird i.d.R. durch den Einsatz von relationalen Datenbanken unterschiedlicher Hersteller
(MySQL (-> SUN ->) Oracle, IBM, SyBase, …) realisiert.
Zur Anbindung von Java-Anwendungen und Java-Applets an relationale Datenbanksysteme wird über das
JDBC-API eine plattform- und datenbankunabhängige Schnittstelle bereitgestellt. Die Datenbankhersteller
können über JDBC-Treiber eine Anbindung an ihr DBMS anbieten.
Eine Liste der verfügbaren JDBC-Treibern finden Sie unter http://servlet.java.sun.com/product/jdbc/drivers oder
http://developers.sun.com/product/jdbc/drivers (oder nach „jdbc driver“ googeln). Zum Zeitpunkt der
Skripterstellung gab es über 220 verfügbare Treiber.
Um Daten einer Datenbank abzulegen, abzufragen und zu ändern wird die strukturierte Abfragesprache SQL
(Structured Query Language) eingesetzt. Für die Abfragesprach SQL wurden mehrere Standards und
Funktionsumfänge definiert. Der SQL ANSI 92 ENTRY LEVEL wurde als Grundlage für JDBC festgelegt. Damit
ist sichergestellt, dass ein standardisierter Teil von SQL-Anweisungen für alle JDBC-Treiber verwendet werden
kann.
Die Java-Aufrufschnittstelle von SQL basiert auf dem Standard X/Open SQL CLI (Call Level Interface). Das
heißt, Sie verwenden in Ihrem Programm SQL-Anweisungen in Form von Zeichenketten (Strings), die an die
betreffenden Methoden der Datenbanktreiber weitergegeben werden. Werden Daten zurückgegeben
(Ergebnismenge), können Sie auf diese über andere Methoden zugriffen.
Im Fall von EJBs wird JDBC auf zwei Arten genutzt: Zum einen können direkte JDBC-Aufrufe in Session Beans
und Bean Managed Persistance Entity Beans benutzt werden, zum anderen kann der EJB-Container intern
JDBC zur Speicherung von Daten aus Container Managed Persitance Entity Beans verwenden. JDBC ist zwar
ein typischer Weg, Daten aus EJBs heraus persistent abzulegen, aber es ist nicht der einzige Weg.
Beispielsweise sind auch indirekte Zugriffe auf Datenhaltungssysteme über CORBA, JMS (Java Messaging
Service) oder JCA (Java Connector Architecture) möglich.
15.3 JDBC als Basis für andere APIs: SQLJ und JDO
Die JDBC API kann zum einen ohne andere APIs zur Erstellung von Datenbank-Applikationen benutzt werden.
Andererseits ist JDBC zugleich Grundlage für eine Reihe von anderen APIs, die JDBC erweitern und
spezialisieren. Diesbezüglich stellen wir kurz SQLJ und JDO vor.
15.3.1 SQLJ
Insbesondere im Bereich SQL und JDBC wurde der Standard SQLJ aus einem Konsortium von Oracle, IBM, Sun
und anderen Firmen geschaffen, um Embedded SQL in Java zu integrieren.
Während der Kompilierung lassen sich (direkte) SQL Anweisungen mit Java Anweisungen kombinieren.
Beispielsweise kann eine Java Variable in einem SQL Befehl verwendet werden, um die Ergebnisse des SQL
Befehls zu erhalten und weiter zu verarbeiten.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
185 / 210
23.03.2010
PROMOD-2a / SS10
15.3.2 SQLJ im Vergleich zu JDBC
Der wichtigste Unterschied zwischen den beiden Sprachen ist, dass SQLJ eine statische und JDBC eine
dynamische Sprache ist.
Eine Sprache wird dynamisch genannt, falls der Datenbankprogrammierer damit Datenbankobjekte abfragen
und ändern kann, die erst zur Ablaufzeit bekannt sind.
Das gilt nicht nur für Datenbankobjekte, sondern auch für ganze SQL-Anweisungen.
Im Unterschied zu JDBC, kann SQLJ nur statisch verwendet werden, d.h. alle SQL-Anweisungen und alle in ihnen
verwendeten Datenbankobjekte (bis eventuell auf die Werte der Host-Variablen) sind zur Übersetzungszeit bekannt.
Obwohl eine dynamische Sprache flexibler als eine statische ist, hat SQLJ auch Vorteile im Vergleich zu JDBC.
Diese sind:
SQLJ befindet sich auf einem höherem logischen Niveau/hat eine grössere Aggregationsdichte als JDBC.
Damit wird ein Programm kompakter/kürzer.
SQLJ hat eine einfachere Syntax als JDBC
SQLJ kann die Syntax- und Semantikanalyse zur Übersetzungszeit durchführen.
Im Unterschied zu JDBC, wo alle SQL-Anweisungen, die in einem JDBC-Programm in Methodenaufrufen versteckt
sind, ist jede SQL-Anweisung in einem SQLJ-Programm in ihrer ursprünglichen Form dargestellt und dadurch
gleich erkennbar. Aus diesem Grund bezeichnet man SQLJ als logisch höhere Programmiersprache (als JDBC).
Das folgende Beispiel verdeutlicht diesen Sachverhalt.
Zunächst geben wir kurz einen Ausschnitt aus einem JDBC-Programm wieder, in dem eine SELECT-Anweisung mit
Hilfe der preparedStatement()-Methode vorbereitet wird und danach die Spaltenwerte den entsprechenden
Variablen zugewiesen werden.
java.sql.PreparedStatement ps =
con.preparedStatement(„SELECT address FROM emp WHERE name=?“);
....
ps.setString(1,name);
java.sql.ResultSet names = ps.executeQuery();
names.next();
addr = names.getString(1);
names.close();
Um dieselbe Funktionalität mit Hilfe von SQLJ zu implementieren, reicht eine einzige Programmzeile aus:
#sql (con) {SELECT address INTO :addr FROM emp WHERE name= :name};
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
186 / 210
23.03.2010
PROMOD-2a / SS10
15.3.3 SQLJ und der Übersetzungprozess
SQLJ Quelleprogramm: Java-Programm mit eingebettetem SQL
SQLJ – Übersetzer (auch Präprozessor oder Vorübersetzer genannt)
Java-Programm mit JDBC-Aufrufen
Java-Compiler
Java-Byte-Code mit JDBC-Methodenaufrufen
JDBC-Teiber
DB
Bild: Übersetzung eines SQLJ-Programms
15.3.4 Persistente Java Objekte: JDO Java Data Objects
Mit der Java Data Objects19 (JDO) API und Enterprise Java Beans20 (EJB) ist es möglich, Daten aus einem
Datenspeicher mit Java Objekten zu verknüpfen und Java Objekte dauerhaft (persistent) in einem Datenspeicher
aufzubewahren. Im Falle von JDBC können beispielsweise ganze Reihen oder Spalten von Datenbanken, die mit
JDBC ausgelesen wurden, als Objekte gespeichert werden. Mit JDO ist es ausserdem möglich, auch Datenbanken
anzusprechen, die nicht auf SQL basieren. Dadurch kann der Java Code unabhängig von der
Datenbanksprache geschrieben werden. JDO kann also mit JDBC relationelle Datenbanken erreichen. Mit der
eigenen Abfragesprache JDOQL kann JDO bei relationellen Datenbanken diese Befehle direkt mithilfe von JDBC in
SQL Abfragen umwandeln. JDO lässt generelle Java Objekte fortbestehen, wohingegen EJB Entity-Beans verwendet,
um Abfragen und dergleichen zu speichern.
15.4 Architektur von JDBC
Eine Java-Anwendung oder ein Java-Applet lädt im ersten Schritt mit Hilfe eines Treibermanagers einen JDBCTreiber.
Über diesen wird dann eine Verbindung zu einer Datenbank hergestellt.
19
20
Siehe http://java.sun.com/products/jdo
Siehe http://java.sun.com/products/ejb
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
187 / 210
23.03.2010
PROMOD-2a / SS10
Je nach dem Typ des verwendeten JDBC-Treibers können zur Kommunikation mit der Datenbank noch weitere
Komponenten benötigt werden, z.B. ein ODBC-Treiber.
Diese JDBC-Treiberkombination kommuniziert (über das verwendete Netzwerk) mit dem Datenbankserver
(DBMS)
Der DBMS-Server reguliert den eigentlichen Zugriff auf die Datenbank.Dabei kann ein Datenbankserver auch
mehrere Datenbanken verwalten.
15.5 Treibertypen
Eine zentrale Rolle spielen die Datenbanktreiber. Auch wenn nicht direkt mit JDBC programmiert wird, sondern
SQLJ oder eine zugekaufte Zurgriffsschicht oder ein EJB-Applikationsserver verwendet wird, ist immer eine
JDBC-Treiber erforderlich: den auch SQLJ, die Zugriffschichten oder EJB-Applikationsserver setzen irgendwann auf
der JDBC-Schnittstelle auf. Die Tabelle unten beschreibt die verschiedenen Treibertypen.
Die Treibertypen im Überblick:
1. Typ 1: JDBC / ODBC Brücke
2. Typ 2: Native API Treiber
3. Typ 3: JDBC Middelware Treiber
4. Typ 4: Native Protocol Treiber
Treibertyp
Class-1-Teiber
JDBC-ODBC-Bridge
Eigenschaften
Über die JDBC-ODBC-Brücke werden Datenbankverbindungen über die ODBC21Schnittstelle aufgebaut.
Dazu werden die Anweisungen von JDBC an die betreffende Funktion des ODBCTreibers weitergegeben, der im nativen Code des Betriebssystems auf dem Client
vorliegt und die eigentliche Verbindung zur Datenbank oder dem Datenbankserver
herstellt. Der ODBC-Treiber nutzt das CLI des Herstellers.
D.h. die nativen (ODBC-) Bibliotheken des Datenbanksystems für den
Datenbankzugriff müssen auf dem Cient installiert werde.
21
ODBC Open Database Connectivity wurde ursprünglich von Microsoft auf Basis des Call Level Interface von
X/Open und ISO/IEC entwickelt, ist aber inzwischen auch von anderen Softwareherstellern übernommen worden. Mit
ODBC kann auf jede lokale oder ferne Datenquelle zugegriffen werden.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
188 / 210
23.03.2010
PROMOD-2a / SS10
Die Bridge ist nur einmal vorhanden. Für jedes Datenbanksystem ist jedoch ein ODBCTreiber erforderlich. ODBC-Treiber sind für die meisten Datenbanksysteme verfügbar
und werden hauptsächlich beim Zugriff auf Datenressourcen unter Windows eingesetzt.
Nachteilig ist die zusätzliche Installation und Konfiguration des ODBC-Treibers, die
auf dem Client erfolgen muss. Applets können diese Schnittstelle nur begrenzt
nutzen (über das Security API), da sie standardmäßig keinen Zugriff auf die lokalen
Ressourcen des Clients haben.
Class-2-Treiber
JDBC-Native-API-partialJava-Treiber (CLI des
Herstellers)
Class-3-Treiber
JDBC-net-pure-JavaTreiber
Dieser Treibertyp ist sinnvoll für den Test, nicht aber für den operativen Einsatz.
JDBC-Aufrufe werden hier auf native Anweisungen des Datenbank-Clients abgebildet,
der die Kommunikation zum Datenbankserver herstellt.
Dazu müssen zusätzlich die nativen Bibliotheken des Datenbanksystems für den
Datenbankzugriff auf dem Client installiert werden.
Der Zugriff ist hier sehr schnell. Der JDBC-Treiber verwendet jedoch nativen Code (den
des DBMS-Client-Treibers) und ist damit selbst nicht plattformunabhängig.
Hierbei handelt es sich um einen plattformunabhängigen Java-Treiber. Die
Anweisungen werden von Java über das verwendete Netzwerkprotokoll
(datenbankunabhängig) an einem auf dem Datenbankserver vorhandenen JDBCServer übergeben und von diesem in Datenbankanweisungen übersetzt.
Der JDBC-Treiber enthält zusätzlich die Client-Funktionalität des DBMS. Es sind
keine zusätzlichen nativen Bibliotheken auf dem Client erforderlich, sodass er
einfach gewartet werden kann
Class-4-Treiber
Native-pure-JavaProtokolltreiber
Im Falle eines Applets wird der Datenbanktreiber gegebenenfalls erst auf den Client
übertragen.
In dieser Treibervariante erfolgt eine direkte Kommunikation (datenbankherstellerspezifisches Protokoll) des JDBC-Treibers mit dem DBMS. Dabei wird auf dem
DBMS die gleiche Struktur wie bei Class-1- und Class-2-Treibern verwendet.
Es sind keine zusätzlichen Bibliotheken auf dem Client zu installieren.
Eine Liste der momentan verfügbaren 221 JDBC-Treiber findet sich auf der folgenden Web-Site
http://developers.sun.com/product/jdbc/drivers (ggf. nach „jdbc driver“ googlen). Stand: 19.06.2009
Die meisten Datenbankhersteller bieten entweder einen Typ-3- oder Typ-4-Treiber mit ihren Datenbanken an.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
189 / 210
23.03.2010
PROMOD-2a / SS10
Hier ein Überblick
JDBC/ODBC-Bridge
eigene Serverschicht
CLI des Herstellers
15.6 Die Datenbank MySQL / Die DBMS Cloudscape / PointBase
Empfehlung: in MySQL einarbeiten! Wenn Zeit/Muse/Intersse ggf. in Cloudscape/PointBase
hineinschnuppern. Cloudscape/PointBase sind Datenbanken, die von Sun mit dem SUN-JEEServer zur Verfügung gestellt werden/wurden. Die Beispiele in der Sun-Doku beziehen sich
(teilweise) darauf.
Download und Installation
Sonstige Hinweise
Î Siehe Literatur und Internet
15.7 Aufbau einer Datenbankverbindung
Der Verbindungsaufbau zu einer Datenbank ist der erste Schritt in einer JDBC Datenbank
Anwendung. Dies geschieht üblicherweise mit der Klasse DriverManager (Siehe API-Doku:
„The basic service for managing a set of JDBC drivers“). Es sind drei Schritte nötig, um mit
DriverManager eine Verbindung zu einer Datenquelle herzustellen:
Das Downloaden und Installieren des Treibers
Im Programm: das dynamische Laden des Treibers zur Laufzeit und
Im Programm: der eigentliche Verbindungsaufbau.
•
Im Folgenden benutzen wir den Treiber Connector/J von MySQL.
•
Prof. Illik
Dieser Treiber ist ein Typ-4-Treiber. Als reine Java-Implementierung des MySQLProtokolls hängt der Treiber nicht ab von den MySQL-Client-Libraries.
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
190 / 210
23.03.2010
PROMOD-2a / SS10
•
Die zur Zeit aktuelle Version ist unter
http://dev.mysql.com/downloads/connector/j/5.1.html
zu finden.
•
Als Java-Implementierung ist der Treiber sowohl für Linux wie auch für Windows
nutzbar. Geliefert wird ein zip-File oder ein tar.gz-File. In beiden Fällen sind die
Sources und die Binaries enthalten. Nach dem Entpacken findet man den Treiber
unter dem Namen mysql-connector-java-5.1.7-bin.jar
•
Der Treiber muss im CLASSPATH des nutzenden Programmes liegen und auch zur
Laufzeit verfügbar sein. Zur Laufzeit wird der Treiber wie folgt geladen:
Class.forName(“com.mysql.jdbc.Driver“); //Lädt die Treiber-Klassen-Lib
•
Für den Test unter Eclipse kopieren Sie den DB-Treiber mysql-connector-java-5.1.7bin.jar mit in das Eclipse-Verzeichnis „src“.
Andere potenzielle Ablageorte für den DB-Treiber
•
•
Für lokale Java-Applikationen: Das dynamische Laden eines Treibers zeigt JDBC, zu welcher Datenbank oder
Datenquelle es sich verbinden soll: Treiber also in die JRE-Lib (z.B. C:\Program Files\Java\jre6\lib\ext) und in die JDKLib (z.B. C:\Program Files\Java\jdk1.6.0_05\lib) kopieren.22
•
Besser ist es allerdings jar-Files „lokal“ zu lassen und über die Compiler-Optionen –classpath (durch „:“ separiert)
oder über Tool-/IDE-Optionen einzubinden. Oder:
•
Den kompletten Pfad inclusive jar-File in die Umgebungsvariable CLASSPATH aufnehmen. (Eclipse: „Project“ ->
„Properties“ -> „Build Path“ -> „Libraries“ -> „Add External Libraries“ -> zum jar-File navigieren -> aufnehmen.)
Für Tomcat-Programme: (Servlets/JSP) wird der
$CATALINA_HOME/common/lib (Tomcat 5) hinterlegt.
Treiber
unter
$CATALINA_HOME/lib
(Tomcat
6)
bzw.
Der Pfad des Connector/J Treibers in der JAR-Lib lautet „com.mysql.jdbc.Driver“. Jeder
Treiber für eine Datenbank hat einen eigenen Pfad, mit dem dieser aufgerufen werden
muss. Eine Instanziierung des Treibers oder eine direkte Verknüpfung oder Registrierung
mit der Klasse DriverManager ist nicht nötig, da dies dies automatisch geschieht. Nach
der obigen Zeile Programmcode ist der Treiber einsatzbereit und eine Verbindung kann
hergestellt werden.
•
Der Verbindungsaufbau geschieht genauso einfach wie Schritt eins:
Connection con = DriverManager.getConnection(String url,
String username,
String passwort);
Die Methode getConnection() liefert eine ein Connection Objekt zurück, mit welchem
man später SQL Befehle ausführen kann. Die URL (englisch: Uniform Resource Locators)
ist ein String, der zur Bestimmung der Datenbank dient. Eine typische URL sieht
folgendermaßen aus:
String url = “jdbc:mysql:test“;
Dabei wird auf eine lokale MySQL Datenbank mit dem Namen „test“ zugegriffen. Will
man eine Verbindung zu einer externen Datenbank herstellen, so sähe die URL aus:
22
Auspacken mit dem Linux-Kommando tar –zxvf <myfile>.tar.gz
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
191 / 210
23.03.2010
PROMOD-2a / SS10
String url = “jdbc:mysql://host:port/test“;
‚Host’ und ‚port’ müssen dabei ersetzt werden. Der Benutzername und Passwort in der
getConnection() Methode sind selbst erklärend.
Es gibt noch zwei weitere Varianten dieser Methode:
DriverManager.getConnection(String url);
Hierbei wird kein Benutzername und Passwort benötigt, wenn man eine Verbindung zu
einer Datenbank aufbaut.
DriverManager.getConnection(String url, Properties info);
‚Properties’ ist eine Erweiterung von java.util.HashTable, welche eine Reihe von
Einstellungen für die Datenbankverbindung enthalten. In der unteren Tabelle werden
einige der möglichen Werte23 vorgestellt.
Name
Username
Passwort
autoReconnect
useSSL
requireSSL
allowMultiQuerries
Beschreibung
Der Benutzername für die Datenbank
Das zugehörige Passwort für den
Benutzernamen
Soll die Verbindung wiederhergestellt
werden, falls sie getrennt wurde?
Soll eine sichere Verbindung mit SSL
verwendet werden?
Soll eine SSL gesicherte Verbindung
vorausgesetzt sein, falls useSSL = true?
Erlaubt das Versenden mehrerer SQLBefehle in einem Befehlsobjekt, die durch ‚;’
getrennt werden.
Defaultwert
false
false
false
false
Tabelle 1 Mögliche Einträge in Properties
Beim Verbindungsaufbau können natürlich auch Fehler auftreten. Diese werden durch die
Ausnahmebehandlung (englisch: exception handling) ‚SQLException’ abgefangen.
‚SQLException’ wird bei allen Fehlern ausgelöst, die im Zusammenhang mit JDBC und SQL
stehen.
Mit dem DriverManager können mehrere Connections zu Datenbanken betrieben werden.
Innerhalb einer Connection können mehrere Statements zur (sukzessiven) Ausführung einer oder
mehrerer SQL-Anweisungen verwendet werden. Die Ergebnisse solcher SQL-Anweisungen
werden als Instanzen der Klasse ResultSet zurückgegeben.
23
Eine komplette Liste findet sich unter http://dev.mysql.com/doc/connector/j/en/cj-configuration-properties.html
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
192 / 210
23.03.2010
PROMOD-2a / SS10
Am Ende einer Datenbankanwendung sollte immer die Verbindung wieder geschlossen
werden, damit keine offenen Verbindungen bei der Datenbank zurückbleiben. Dies geschieht mit
einer einfachen close-Methode:
con.close();
Zusammenfassung Aufbau einer Datenbankverbindung:
Zur Herstellung einer Datenbankverbindung über JDBC sind folgende Schritte zu erledigen:
1. Installation eines Datenbankservers (lokal oder im LAN)
2. Installation eines JDBC-Treibers
3. Laden eines Treibermanagers mit dem ensprechenden JDBC-Treiber
4. Herstellen einer Verbindung zur Datenbank
5. SQL-Anweisungen an die Datenbank richten
o … Zum Bearbeiten der Struktur der Datenbank (Tabellen anlegen,…)
o … Zum Abfragen / Selektieren von Daten. Es wird eine Ergebnismenge geliefert.
o … Zum Bearbeiten von Daten (ändern, löschen, einfügen). Es wird keine Ereignismenge
geliefert.
6. Gegebenenfalls von der SQL-Anweisung zurückgegebene Ergebnismenge bearbeiten.
Treiber laden
DriverManager
Connection
PreparedStatement
Statement
CallableStatement
ResultSet
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
193 / 210
23.03.2010
PROMOD-2a / SS10
Die Abbildung visualisiert das Zusammenspiel der einzelnen Interfaces und Klassen. Der JDBC-Treiber wird
unabhäng als Klasse geladen. Wenn über den Treibermanager eine Datenbankverbindung hergestellt wird (Interface
Connection), wird auf den bereits geladenen Treiber zurückgegriffen.
Über eine Datenbankverbindung werden Anweisungen an diese gesendet (Interfaces CallableStatement,
PreparedStatement, Statement). Zwei dieser Anweisungen liefern eine Ergebnismenge (Interface Result)
zurück.
15.8 SQL-Anweisungen ausführen
Nachdem eine Verbindung zu einer Datenbank hergestellt wurde, können nun SQL Anweisungen
(englisch: statements) verschickt werden. Diese können auf verschiedene Arten erstellt werden. In
den folgenden drei Unterkapiteln werden diese jeweils gesondert behandelt.
15.9 Die Klasse Statement
Dies ist die Standard Klasse für SQL Anweisungen. Ein solches Statement wird von dem
Verbindungsobjekt erstellt:
Statement st = con.createStatement();
Es gibt drei verschiedene Methoden, um ein Statement Objekt zu kreieren.
Methodenname
createStatement()
createStatement(int resultSetType,
int resultSetConcurrency)
createStatement(int resultSetType,
int resultSetConcurrency,
int resultSetHoldability)
Beschreibung
Die Standardmethode ohne jegliche
Parameter
Die Integerparameter sind für das
ResultSet Objekt, welches die
Ergebnisse eines Queries liefert
(siehe „Ereignisse anzeigen“).
Drei Parameter bestimmen die
Einstellungen für das
Rückgabeobjekt. Siehe Kapitel
15.12.2 ResultSet
Tabelle 2 Statement Objekt erzeugen
Mit diesem Objekt ‚st’ lassen sich nun Anweisungen abschicken. Es ist nicht nötig, bei jedem SQL
Befehl ein neues Objekt zu kreieren. Die Anweisungen werden mit ‚execute’ Methoden an die
Datenbank gesendet. Drei Methoden-Typen werden unterschieden:
Erstens:
boolean execute(String sql);
‚sql’ ist die SQL Anweisung, die an die Datenbank geschickt werden soll und mehrere
Rückgabeobjekte in Form von ResultSet entstehen lassen kann. Als Rückgabewert liefert
die Methode ein boolean zurück, welches ‚true’ ist, wenn das erste Rückgabeobjekt vom
Typ ResultSet ist.
Zweitens:
int executeUpdate(String sql);
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
194 / 210
23.03.2010
PROMOD-2a / SS10
Im Gegensatz zur ersten Methode werden hier nur bestimmte SQL Befehle ausgeführt.
‚INSERT’, ‚UPDATE’, ‚DELETE’ oder eine SQL Anweisung, die keinen Rückgabewert
besitzt, können hier ausgeführt werden. Trotzdem liefert diese Methode etwas zurück. Bei
‚INSERT’, ‚UPDATE’ oder ‚DELETE’ Befehlen wird Reihenanzahl angegeben oder der
Wert ist 0, wenn eine andere Anweisung ausgeführt wurde.
Drittens:
ResultSet executeQuery(String sql);
Typischerweise werden ‚SELECT’ Anweisungen mit dieser Methode verschickt. Das
Rückgabeobjekt ist vom Typ ResultSet, welches in einem späteren Kapitel näher erläutert
wird.
Beispielanwendung:
Hat man die Datenbank ‚Test’ und man möchte die Tabelle ‚Obst’ anlegen, so kann man dies
folgendermaßen machen. Eine Verbindung zu der Datenbank besteht bereits.
Statement stmt = con.createStatement();
stmt.executeUpdate(“CREATE TABLE OBST (NAME VARCHAR(32),
PREIS FLOAT)“);
Die Tabelle ‚Obst’ hat zwei Attribute, Name und Preis. Name ist vom Type VARCHAR und kann
32 Zeichen lang sein. Preis hingegen ist ein Fliesskommawert. Natürlich lassen sich die Befehle
vorher in einer String-Variablen speichern und dann in das Statement Objekt einzufügen:
String createObstTable = “CREATE TABLE OBST (NAME
VARCHAR(32), PREIS FLOAT)”;
stmt.executeUpdate(createObstTable);
Um die Datenbank zu füllen, verwendet man folgenden Befehl:
stmt.executeUpdate(“INSERT INTO OBST VALUES (’Banane’, 2.50)”);
stmt.executeUpdate(“INSERT INTO OBST VALUES (’Apfel’, 1.00)”);
stmt.executeUpdate(“INSERT INTO OBST VALUES (’Birne’, 2.00)”);
Wie man bisher sehen kann, sind die Befehle aufgebaut wie „normale“ SQL Anweisungen für eine
Datenbank.
In einem letzten Schritt kann man sich die Ergebnisse noch anzeigen lassen. Das wird so
realisiert:
String getDataFromTable = “SELECT * FROM OBST“;
System.out.println(stmt.executeQuery(getDataFromTable));
Das Ergebnis hierzu sähe auf der Konsole wie folgt aus:
NAME
----------Apfel
Banane
Prof. Illik
PREIS
---------1.00
2.50
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
195 / 210
23.03.2010
PROMOD-2a / SS10
Birne
2.00
Falls keine spezielle Sortierung angegeben wurde, wird nach der ersten Spalte sortiert.
15.10 Klasse PreparedStatement24
PreparedStatements sind abgeleitet vom Interface Statement. Sie werden oftmals dann
eingesetzt, wenn ein Statement Objekt mehrmals verwendet werden soll. Wie der Name es
vermuten lässt, werden die Statements vorpräpariert, das heißt vorkompiliert. SQL Anweisungen, die Variablen verwenden, sind das bevorzugte Einsatzgebiet. Es können natürlich
auch SQL Befehle ohne Variablen benutzt werden. Der große Vorteil von einem PreparedStatement kommt erst dann zur Geltung, wenn der gleiche SQL Befehl mit unterschiedlichen
Werten ausgeführt wird. Ein PreparedStatemsent Objekt kann auf mehrere Arten erzeugt werden.
Dabei ist auch wieder ein Verbindungsobjekt vonnöten.
Methodenname
prepareStatement(String sql)
prepareStatement(String sql,
int autoGeneratedKeys)
prepareStatement(String sql,
int[] columnIndexes)
prepareStatement(String sql,
String[] columnNames)
prepareStatement(String sql,
int resultSetType,
Beschreibung
‚sql’ ist der SQL Befehle, der ein oder mehrere
‚?’ als Parameter enthalten kann.
Sollen automatisch generierte Schlüssel
zurückgegeben werden oder nicht:
Statement.RETURN_GENERATED_KEYS = ja
Statement.NO_GENERATED_KEYS = nein
Werden Reihen hinzugefügt, bestimmt das Array
‚columnIndex’, welche Spalten zurückgegeben
werden.
Das Prinzip der Methode ist dasselbe wie bei der
vorherigen, nur dass die Spaltennamen als
Strings übergeben werden
Beide Integervariablen setzen Parameter des
zurückgegebenen ResultSet (siehe Kapitel
15.12.2) Objektes.
int resultSetConcurrency)
prepareStatement(String sql,
int resultSetType,
Wie bei der obigen Methode werden
Einstellungen für das Rückgabeobjekt getätigt.
int resultSetConcurrency,
int resultSetHoldability)
Tabelle 3 PreparedStatement Objekt erzeugen
Ein typisches PreparedStatement kann folgendermaßen aussehen. Es wird dabei das obige
Beispiel weitergeführt:
PreparedStatement p_st = con.prepareStatement(“INSERT INTO
OBST VALUES(?, ?)“);
Dabei sind die ‚?’ Parameter, die beliebig gesetzt werden können. In den Beispielen für Statement
könnte man so schreiben:
24
Das Statement wird auf Programmiersprach-Ebene vorbereitet und gespeichert.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
196 / 210
23.03.2010
PROMOD-2a / SS10
p_st.setString(1,“Banane“);
p_st.setFloat(2, 2.50);
p_st.executeUpdate();
Die ‚set’-Methoden gibt es für alle Arten von Parametertypen25. Es werden immer zwei Parameter
übergeben. Der erste sagt aus, welches ‚?’ ersetzt werden soll. Der zweite beinhaltet den zu
ersetzenden Wert. Schließlich wird der Befehl mit einer ‚execute’-Methode ausgeführt.
15.11 Klasse CallableStatement / StoredProcedure26
Die dritte Möglichkeit in JDBC, einen SQL Befehl an eine Datenbank zu geben, ist das
CallableStatement. Mit diesem kann man vorgespeicherte Prozeduren und Anweisungen
ausführen. Solche vorgefertigten Skripte können eine Reihe von SQL Befehlen enthalten, die
dann in der Datenbank gespeichert werden, um sie jederzeit aufrufen zu können. Eine
vorgefertigte Prozedur kann man auch mit JDBC erstellen:
String storedProcedure = “CREATE PROCEDURE ZEIGE_OBST AS
“+“ SELECT * FROM OBST WHERE
PRICE > 1.50“;
Statement st = con.createStatement();
st.executeUpdate(storedProcedure);
Dieser SQL Befehl ist nun in der Datenbank unter dem Namen „ZEIGE_OBST“ gespeichert.
Aufgerufen wird dieser mit einem CallableStatement:
CallableStatement c_st = con.prepareCall(“{ ZEIGE_OBST }“);
ResultSet rs = c_st.executeUpdate();
Der Treiber übersetzt diese Syntax in ein für die Datenbank verständliches SQL, welche dann den
die gespeicherte Prozedur ausführt. Das Ergebnis dieser vorgefertigten Prozedur wird in unserem
Beispiel wie folgt aussehen:
NAME
----------Banane
Birne
PREIS
---------2.50
2.00
15.12 Auswertung der Ergebnismengen
Bei jeder SQL Anweisung gibt es Rückgabewerte. Es ist egal, ob man einen Befehl
wie UPDATE oder INSERT, die nur Daten in der Datenbank verändern und nicht abrufen
oder
ein SELECT Statement ausführt, welches eine ganze Reihe von Rückgabewerten
beinhalten kann.
In den nächsten Unterkapiteln werden die verschiedenen Arten von Rückgabewerten gesondert
behandelt.
25
Nachzulesen in http://java.sun.com/j2se/1.5.0/docs/api/java/sql/PreparedStatement.html
Im Gegensatz zum Prepared Statement wird die Stored Procedure in der DB gespeichert – nicht auf Ebene der
Programmiersprache.
26
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
197 / 210
23.03.2010
PROMOD-2a / SS10
15.12.1 Boolean und Integer als Rückgabewerte
Ein boolean oder ein Integer werden von Methoden zurückgegeben, wenn entweder die Methode
execute() oder executeUpdate() ausgeführt wurde. Typisch dafür sind die drei Anweisungen
UPDATE, INSERT oder DELETE.
Bei solchen Statements ist es nur wichtig zu wissen, ob der Befehl erfolgreich durchgeführt wurde
oder fehlgeschlagen ist. Ist er fehlgeschlagen, wird eine SQLException ausgelöst. Bei einem
erfolgreichen Durchführen wird je nach Methode ein bool`scher Wert oder ein Integer
zurückgegeben.
Bei jeder der drei Möglichkeiten Statement, PreparedStatement und CallableStatement gibt es
Methoden, die nur einen solchen Rückgabewert besitzen:
boolean execute();
int executeUpdate();
Dies sind zwei solche Methoden. Bei der oberen wird ein ‚true’ zurückgegeben, falls der SQL
Befehl ein RowSet Objekt zurückgibt. Bei einer Update Methode wird die Anzahl der Reihen als
Rückgabewert gesetzt. Löscht man mit einem DELETE Statement drei Reihen, so erhält man von
der Methode ebenfalls den Wert drei.
15.12.2 ResultSet
Ein Objekt vom Interface ResultSet wird üblicherweise bei SQL Anweisungen erstellt, die eine
Datenbank Abfrage sind. Der SELECT Befehl ist die wohl am häufigsten verwendete Anweisung.
Ein ResultSet kann auf verschiedene Weise konfiguriert werden. In der Standardeinstellung kann
das ResultSet reihenweise ausgelesen werden, beginnend mit der ersten Reihe.
Mit der Erstellung eines Statements, egal welcher Art, kann das ResultSet gestaltet werden. In der
folgenden Tabelle werden die einzelnen Einstellungsmöglichkeiten beschrieben. Als
Wiederholung wird eine Methode nun aufgezeigt, welche die verschiedenen Parameter für das
ResultSet beinhaltet:
Statement createStatement(int resultSetType,
int resultSetConcurrency,
int resultSetHoldability);
Diese drei Übergabewerte können mit verschiedenen Konstanten aus dem Interface ResultSet
besetzt werden:
Parametertyp
resultSetType
Konstante in ResultSet
ResultSet.TYPE_FORWARD_ONLY
ResultSet.TYPE_SCROLL_INSENSITIVE
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
Beschreibung
Das ResultSet darf nur vorwärts
gelesen werden.
Während ein ResultSet Objekt
geöffnet ist, reagiert es nicht auf
Veränderungen, die an diesem
vollzogen werden.
198 / 210
23.03.2010
PROMOD-2a / SS10
ResultSet.TYPE_SCROLL_SENSITIVE
resultSetConcurrency
resultSetHoldability
Während ein ResultSet Objekt
geöffnet ist, reagiert es auf
Veränderungen.
ResultSet.CONCUR_READ_ONLY
Das ResultSet darf nicht
verändert werden.
ResultSet.CONCUR_UPDATABLE
Das ResultSet darf verändert
werden.
ResultSet.HOLD_CURSORS_OVER_COMM Das ResultSet Objekt wird nicht
bei einem Commit-Befehl
IT
geschlossen, sondern ist für
weitere Anweisungen verfügbar.
ResultSet.CLOSE_CURSORS_AT_COMMIT Das ResultSet wird bei einem
Commit-Befehl geschlossen.
Tabelle 4: ResultSet Konstanten
Nun gibt es mehrere Möglichkeiten, sich die Ergebnisse mit einem ResultSet anzeigen zu lassen.
Will man den kompletten Rückgabewert auf einen Schlag, so kann man einfach das Objekt als
Ganzes ausgeben. Beispielsweise könnte dies so aussehen:
ResultSet rs = stmt.executeQuery(“SELECT Name,
Preis FROM Obst“);
System.out.println(rs);
Das Ergebnis zeigt alle Daten der Tabelle Obst an:
NAME
----------Apfel
Banane
Birne
PREIS
---------1.00
2.50
2.00
Es ist aber auch möglich, die Werte einzeln abzurufen. Statt der Ausgabe des gesamten
Objektes auf einmal, kann man jede Reihe einzelnen auswählen:
while (rs.next()) {
String name = rs.getString("Name");
float preis = rs.getFloat("Preis");
System.out.println("Ein(e) " + name + " kostet " + preis);
}
Die while-Schleife wird solange durchlaufen, wie es Reihen in dem ResultSet Objekt gibt. Das
Ergebnis sieht wiederum ähnlich zu dem oberen aus:
Ein(e) Apfel kostet 1.00
Ein(e) Banane kostet 2.50
Ein(e) Birne kostet 2.00
Ähnlich wie bei PreparedStatement gibt es eine Reihe von Datentypen, die aufgerufen werden
können. Eine aktuelle Liste dieser ‚getter’-Methoden gibt es in der Java JDK API27. In einem
ResultSet kann man auch zu einer bestimmten Reihe springen. Es gibt verschiedene Methoden
dafür, die in der nächsten Tabelle vorgestellt werden.
27
siehe http://java.sun.com/j2se/1.5.0/docs/api/java/sql/ResultSet.html
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
199 / 210
23.03.2010
PROMOD-2a / SS10
Methode
boolean absolute(int row);
Beschreibung
boolean first();
Springt zu der angegebenen Reihe. Ist diese
außerhalb des Objektes, wird ‚false’ zurückgegeben.
Damit kann man Reihen vor- und zurückgehen.
Auch hier wird ‚false’ zurückgegeben, falls die Reihe
nicht in dem Objekt liegt.
Springt eine Reihe zurück. Der Rückgabewert ist
‚true
, wenn die Reihe noch innerhalb des Objektes liegt.
Springt eine Reihe vor. Die Art des
Rückgabewertes ist dieselbe wie in der vorherigen
Methode.
Der Zeiger geht zurück zur ersten Reihe.
boolean last();
Das Objekt ist nun in der letzten Reihe.
void afterLast();
Mit dieser Methode springt man zu der Stelle nach
der letzten Reihe.
Mit dieser Methode springt man zu der Stelle vor der
esten Reihe.
boolean relative(int row);
boolean previous();
boolean next();
void beforeFirst();
Tabelle 5 Methoden, um eine Reihe in einem ResultSet auszuwählen
Ein ResultSet Objekt lässt sich auch bearbeiten. Dafür gibt es eine Reihe von ‚update’-Methoden.
(Siehe API Beschreibung). Natürlich wird dabei nur der Wert innerhalb des Objektes geändert,
nicht aber der zugrundeliegende Wert in der Datenbank. Will man beispielsweise den Preis des
Apfels verändern, so kann man dies folgendermaßen machen:
while (rs.next()) {
String name = rs.getString("Name");
float preis = rs.getFloat("Preis");
if(name.equals("Apfel") {
System.out.println("Ein " + name + " kostet " + preis);
Rs.updateFloat(1,1.75);
System.out.println("Ein " + name + " kostet nun" + preis);
}
}
Das Ergebnis sieht wie folgt aus:
Ein Apfel kostet 1.00
Ein Apfel kostet nun 1.75
Die Möglichkeit, den Datenbankeintrag doch mit diesem neuen Wert zu versehen, besteht mit
dieser Methode:
void updateRow();
Ein vorzeitiges Schließen des ResultSet Objektes, um keine weiteren Daten in das Objekt
einfließen zu lassen, ist mit einer simplen close()-Methode möglich.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
200 / 210
23.03.2010
PROMOD-2a / SS10
Siehe Literatur und zu folgenden Themen:
15.12.3 Konfigurieren der Ergebnismenge
15.12.4 Nachladen von Datensätzen konfigurieren
15.12.5 Navigieren in der Ergebnismenge
15.12.6 Werte der Ergebnismenge ermitteln
15.12.7 Update der Ergebnismenge
15.12.8 Nullwerte
15.13 Auslesen von Metadaten
Für Werkzeuge, die mit beliebigen Datenbankstrukturen arbeiten, sind Metadaten unerlässlich.
Metadaten, auch Metainformationen genannt, sind Daten, die Datenbankstrukturen und deren
Eigenschaften beschreiben. Eine Metainformation ist beispeilsweise die Auskunft, welche
Tabellen sich in einer Datenbank befinden oder welche Attribute in einem ResultSet enthalten
sind.
JDBC stellt zwei Klassen bereit, mit denen Metadaten abgefragt werden können:
ResultSetMetaData liefert Informationen über ein bestimmtes ResultSet.
DataBaseMetaData liefert Informationen zu Datenbanken.
15.13.1 Datenbankinformationen ermitteln
15.13.2 Informationen über eine Ergebnismenge ermittlen
15.14 Transaktionen
15.14.1 Manuelles und automatischs Commit
15.14.2 Isolationsebenen
15.14.3 Dirty Read
15.14.4 Repeatabel Read
15.14.5 Phantom Read
15.15 JDBC Option Package
15.15.1 DataSource
Ein DataSource-Objekt repräsentiert eine Datenbank oder eine beliebige andere Datenquelle und
beinhaltet die wichtigsten Informationen darüber. Verschiedene Datenquellen können so im
gleichen Programm abstrakt gehandabt werden. Für den Client sehen diese Datenquellen gleich
aus.
Ein DataSource-Objekt wird in der Regel von einem separaten Programm erzeugt und beim
Naming-Service registriert. Clients können dann im Netzwerk auf diese Datenquelle über ihren
logischen Namen zugreifen. Es ist nicht mehr notwendig die genau URL der Datenbank zu
kennen.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
201 / 210
23.03.2010
PROMOD-2a / SS10
15.15.2 Connection-Pooling
Der Aufbau von Datenbankverbindungen kostet Performance. Durch das Verwalten von
geöffneten Verbindungen in einem Pool können diese Kosten reduziert werden.
15.15.3 Verteilte Transaktionen
Das XA-Protokoll (ein X/Open Standard) wird verwendet, wenn mehrere Datenbanken oder
allgemeiner Datenquellen innerhalb einer Transaktion angesprochen werden sollen. Denkbar ist
beispielsweise die Anbindung eines Altsystems, das parallel weiter betrieben wird und damit auch
die Änderungen erhalten muss. Für die Implementierung verteilter Transaktionen ist die XASchnittstelle mit dem dem spezifizierten Zwei-Phasen-Commit notwendige Voraussetzung.
15.16 Datenbankzugriff über Applets
15.17 Debuggen von JDBC-Anwendungen
15.18 CaseStudy Aufgabe (Aufgabe 1 von 030_JDBC)
Wenn Sie mit Eclipse arbeiten und falls noch nicht geschehen, erstellen sie ein neues Eclipse
Projekt. Achten sie darauf, dass der JDBC Treiber für die MySQL Datenbank richtig in das Projekt
eingebunden ist. Erstellen sie eine neue Java Datei und folgen sie den Anweisungen:
1. Laden sie den Datenbanktreiber und erstellen sie eine Verbindung zur Datenbank
‚promod2’ her. Achten sie gegebenenfalls darauf, dass die Datenbank auf einem externen
Server liegen könnte und sich somit die URL ändern wird.
2. Sie sollen mithilfe eines Statement Objektes eine neue Tabelle ‚semester’ erstellen.
Folgende Attribute soll die Tabelle haben:
•
Die Matrikelnummer als INTEGER Typ
•
Vorname als VARCHAR Typ mit der Länge 32
•
Dasselbe für den Nachnamen
•
Die Semesterstufe als INTEGER Typ
•
Die Note als DOUBLE Typ
3. Überprüfen sie mit einer Ausgabe auf die Konsole, ob die Tabelle korrekt erstellt wurde.
Erklären sie, warum der Rückgabewert entweder false oder 0 sein muss.
4. Füllen sie die Tabelle mit Daten. Verwenden sie dazu einmal ein normales Statement und
für die restlichen PreparedStatements. Setzen sie dabei die Vorzüge eines
PreparedStatement ein. Diese Daten sollten in der Tabelle zu finden sein:
Matrikelnummer
111222
123456
987654
135797
Prof. Illik
Vorname
Max
Philipp
Maria
Klara
Nachname
Muster
Mueller
Durchschnitt
Stein
Semester
4
5
4
4
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
202 / 210
Note
1.3
2.0
3.0
4.7
23.03.2010
PROMOD-2a / SS10
5. Wenn sie die Methode executeUpdate() verwenden, zeigen sie den Unterschied zu Punkt 3
auf und erklären sie ihn.
6. Schließen sie die Verbindung korrekt.
15.19 CaseStudy Lösung
import
import
import
import
import
java.sql.Connection;
java.sql.DriverManager;
java.sql.PreparedStatement;
java.sql.SQLException;
java.sql.Statement;
public class Loesung_jdbc_1 {
public static void main(String[] args) {
try {
// Laden des Treibers
Class.forName("com.mysql.jdbc.Driver").newInstance();
} catch (Exception e) {
System.err.println("Treiber konnte nicht geladen werden: " + e);
}
try {
String url = "jdbc:mysql://127.0.0.1/promod2"; /////////////<<<<-------Connection con = DriverManager.getConnection(url, "benutzer","passwort");
Statement stmnt = con.createStatement();
String createTable =
+
+
+
"CREATE TABLE semester"
"(matrikelnummer INTEGER, vorname VARCHAR(32), "
"nachname VARCHAR(32), semester INTEGER, "
"note DOUBLE)";
if (stmnt.executeUpdate(createTable) == 0) {
System.out.println("Tabelle semester wurde erfolgreich erstellt."
+ " Der Rueckgabewert ist 0, weil ein CREATE "
+ "Statement nichts zurueckgibt.");
}
String insertData = "INSERT INTO semester(matrikelnummer, vorname, "
+ "nachname, semester, note) "
+ "VALUES(111222, 'Max', 'Muster', 4, 1.3)";
if(stmnt.executeUpdate(insertData) > 0 ){
System.out.println("Daten wurden korrekt in der DB gespeichert."
+ " Der Unterschied zum CREATE Statement liegt darin, "
+ "das 1 Zeile der Tabelle hinzugefuegt"
+ " und dieser Wert zurueckgegeben wurde.");
}
String preparedSt = "INSERT INTO semester(matrikelnummer, vorname, "
+ "nachname, semester, note) "
+ "VALUES(?, ?, ?, ?, ?)";
PreparedStatement p_stmnt = con.prepareStatement(preparedSt);
p_stmnt.setInt(1,123456);
p_stmnt.setString(2,"Philipp");
p_stmnt.setString(3,"Mueller");
p_stmnt.setInt(4,5);
p_stmnt.setDouble(5,2.0);
if(p_stmnt.executeUpdate()> 0){
System.out.println("Daten wurden korrekt in der DB gespeichert.");
}
p_stmnt.setInt(1,987654);
p_stmnt.setString(2,"Maria");
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
203 / 210
23.03.2010
PROMOD-2a / SS10
p_stmnt.setString(3,"Durchschnitt");
p_stmnt.setInt(4,4);
p_stmnt.setDouble(5,3.0);
if(p_stmnt.executeUpdate()> 0){
System.out.println("Daten wurden korrekt in der DB gespeichert.");
}
p_stmnt.setInt(1,135797);
p_stmnt.setString(2,"Klara");
p_stmnt.setString(3,"Stein");
p_stmnt.setInt(4,4);
p_stmnt.setDouble(5,4.7);
if(p_stmnt.executeUpdate()> 0){
System.out.println("Daten wurden korrekt in der DB gespeichert.");
}
}
} //end
con.close();
} catch (SQLException e) {
System.err.println("Ein Fehler ist aufgetreten: " + e);
}
15.20 Die Datenbank Derby
Seit Java 6 (Jahr 2005) ist die Datenbank „Derby“ Bestandteil von Java. Derby wurde
ursprünglich unter dem Namen Cloudscape von IBM entwickelt. Der Computerkonzern überließ
den Quellcode der Apache Foundation, die die Datenbank unter dem neuen Namen als OpenSource-Projekt weiterentwickelt.
WORKSHOP: Bearbeiten Sie die 2. Aufgabe der Case Studie 030_JDBC.
Nachdem die „Inbetriebnahme“ Ihrer JDBC-Lösung geklappt hat, entwickeln Sie
diese weiter.
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
204 / 210
23.03.2010
PROMOD-2a / SS10
16. Promod Tipps
16.1 Methodik: Scrum
“Programmieren und Modellieren (=PROMOD) ohne Tools und Methodik is nix.” Diese Erkenntnis
ist nicht gerade neu. Promod Tipp macht Tools und Methoden schmackhaft. Heute gehts um
Scrum.
Scrum gehört zu den agilen Methoden, die auf einem iterativen Ansatz basieren.
Die Wurzeln von Scrum reichen zurück auf Veröffentlichungen von Hirotaka Takeuchi und Ikujiro
Nonaka (es geht um eine Methode zur Beschleunigung von kommerziellen Produktentwicklungen)
aus dem Jahr 1986 bzw. in das Jahr 1991, in dem DeGrace und Stahl in ihrem Werk “Wiked
Problems, Righteour Solutions” den Ansatz von Takeuchi und Nonaka als als “Scrum Approach”
bezeichneten. Beiträge von Ken Schwaber und Jeff Sutherland runden die Methode ab.
Infortainment: Scrum http://www.illik-online.com/wordpress_blogpod/?p=115
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
205 / 210
23.03.2010
PROMOD-2a / SS10
*** Ende Skript ***
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
206 / 210
23.03.2010
PROMOD-2a / SS10
17. Literatur (nehmen Sie jeweils die jüngste Ausgabe)
Downloads (Vorlesung): jdk-6u10-docs; java_ee_javadocs; langspec-3.0 (Sie finden die Dokumente auf
der Sun Microsystem Site)
Pflichtlektüre (Vorlesung):
Goll/Weiß/Müller:
„Java als erste Programmiersprache – Vom Einsteiger zum Profi“,
Verlag Teubner, Stuttgart/Leipzig/Wiesbaden,
Empfohlene Lektüre (Vorlesung/Übungen):
Christian Ullenboom:
„Java ist auch eine Insel“,
, geb., mit CD, 49,90 Euro,
Galileo Computing, Bonn,
Cay S. Horstmann, Gary Cornell
„Core Java. Band 1 – Grundlagen“
Markt+Technik Verlag, München,
Die englische Version ist “revised and updated vor Java
SE 6”
Weitere empfohlene Literatur (Übungen/Projekte )
Verteilte Systeme:
J. Anton Illik
“Verteilte Systeme. Architekturen und Software-Technologien“
Expert Verlag, Renningen, 2007
ISBN 978-3-8169-2730-3
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
207 / 210
23.03.2010
PROMOD-2a / SS10
Johannes Nowak
„Fortgeschrittene Programmierung
mit Java 5“
dpunkt Verlag, Heidelberg,
Cay S. Horstmann, Gary Cornell
„Core Java. Band 2– Expertenwissen“
Markt+Technik Verlag, München,
Die englische Version ist “revised and updated vor Java
SE 6”
Mark Wutka
„J2EE Developer’s Guide“
Markt+Technik Verlag, München,
Michael Kroll, Stefan Haustein
„J2ME Developer’s Guide“
Markt+Technik Verlag, München,
Gottfried Wolmeringer
“Profikurs Eclipse 3“
Vieweg Verlag, Wiesbaden,
Volker Turau, Krister Saleck, Christopher Lenz
„Web-basierte Anwendungen entwickeln mit JSP2.
Einsatz von JSTL, Struts, JSF, JDBC, JDO, JCA”
d-punkt Verlag, Heidelberg
Weitere Projekt-Literatur zu den Tools:
Jakarta Struts:
James Turner, Kevin Bedall
“Struts”
Addison-Wesley;
James Goodwill
„Mastering Jakarta-Struts“
Wiley, Behandelt jedoch nur Version 1.0!
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
208 / 210
23.03.2010
PROMOD-2a / SS10
Ted Husted
“Struts in Action”
Manning;
James Holmes;
“Struts – The Complete Reference”
McGraw-Hill Osborne;
Tomcat:
“Professional Apache Tomcat 5”
Wrox-Press; I
CVS:
Karl Fogel, Moshe Bar;
“Open Source-Projekte mit CVS”
MITP
Offizielles Manual von Per Cederqvist
https://www.cvshome.org/docs/manual/
ANT:
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
209 / 210
23.03.2010
PROMOD-2a / SS10
Bernd Matzke;
„Ant – Das Java Build Tool in der Praxis“
Addison-Wesley;
-
siehe auch Empfehlung aus der Vorlesung –
Prof. Illik
PROMOD2_illik_02a_Java_SE_part02_v6_100201_students.doc
210 / 210
23.03.2010
Herunterladen