1 Einführung in die objektorientierte Programmierung

Werbung
1
Einführung in die objektorientierte Programmierung (OOP)
1
Einführung in die objektorientierte Programmierung (OOP)
1.1
Java
Was ist eine Klasse, was ist ein Objekt?
Wenn ein Kind sagt, dass der vor ihm stehende Dackel „Fifi“ ein Hund ist, dass aber auch der Schäferhund „Fakir“ ein Hund ist, dann hat das Kind im Prinzip gelernt, zwischen Klassen und Objekten im
Sinne von OOP zu unterscheiden. Den „Hund“ gibt es tatsächlich nicht, er ist lediglich eine Abstraktion, eine Idee. Wirklich existieren nur „Fifi“ und „Fakir“. In OOP sagen wir auch „Fifi“ und „Fakir“ sind
Instanzen oder Objekte der Klasse „Hund“.
Etwas allgemeiner können wir sagen, Klassen werden dazu
benutzt, Dinge der realen und der gedachten Welt zu modellieren. Solche „Dinge“ können Mitarbeiter in einer Firma sein,
oder eine komplizierte Gleichung in der Mathematik. Eine
Klasse können wir auch mit einem Stempel vergleichen. Jedes Mal, wenn wir den Stempel auf ein Papier drücken, erzeugen wir ein Objekt, für das der Stempel den Bauplan liefert. In die Felder ‚Name’ und ‚Gehalt’ (vgl. dazu die Abbildung) lassen sich dann konkrete Werte eintragen. Natürlich
sind Objekte, die nicht auf dem Papier, sondern im Speicher
eines Rechner existieren, wesentlich flexibler, so kann man
mit Methoden den konkreten Namens- bzw. Gehaltseintrag
wieder verändern, wenn dies z.B. durch Heirat bzw. Gehaltserhöhung angezeigt ist. In unserem Kurs wollen wir uns
mit Klassen wie Quadrat, Vieleck und Kreis beschäftigen,
wobei wir die wesentlichen Elemente der OOP wie Klassen,
abstrakte Klassen, Vererbung und Polymorphie kennen lernen werden. Wenn wir jetzt Klassen wie Quadrat, Vieleck und Kreis modellieren und schließlich implementieren, so sei betont, dass wir lediglich die sog. Fachklassen programmieren. Eine grafische
Oberfläche (GUI = Graphical User Interface) werden wir erst später kennen lernen. Wir werden dabei
von Anfang an mit der Forderung ernst machen: „Trenne Fachklasse von den GUI-Klassen“. Die
Codes gewinnen dadurch an Sicherheit und ihre Wiederverwertbarkeit wird deutlich erhöht.
1.2
Das Miniprojekt „Figuren“
Ziel des Miniprojekts ist es, ein Programm zu schreiben, das es erlaubt, Quadrate, Kreise und Vielecke unterschiedlicher Größe und Farbe zu zeichnen. Vier Screenshots mögen das Projekt veranschaulichen.
copyleft:munz
1
Fachschulen Lörrach
1
1.3
Einführung in die objektorientierte Programmierung (OOP)
Java
Trenne Fachklassen von den GUI-Klassen!
In den Fachklassen werden lediglich die funktionalen Eigenschaften modelliert. Eingabefelder, Buttons, Auswahlbuttons, allgemein das ganze Layout eines Programms gehören eindeutig zur GUI
(= Graphical User Interface).
Aufgabe 1:
1.4
Über welche Attribute und welche Methoden sollten die Fachklassen Quadrat, Kreis
und VielEck verfügen?
Die Fachklassen Quadrat und Kreis
Um ein Quadrat zu modellieren, fragen wir zunächst nach den wesentlichen Eigenschaften, die ein
Quadrat haben muss, um es z.B. zeichnen zu können.
copyleft:munz
2
Fachschulen Lörrach
1
Einführung in die objektorientierte Programmierung (OOP)
Java
So gibt man z.B. seinen Mittelpunkt (= Schnittpunkt der Diagonalen) durch seine x- und y-Koordinaten
an. Weiter soll das Quadrat eine Seitenlänge haben. Da wir das Quadrat mit einer Farbe gefüllt zeichnen wollen, besitzt es schließlich noch eine Farbe. Wir nennen diese Eigenschaften Attribute einer
Klasse. Genauer wäre der Begriff Objekt-Attribute,
denn jede Instanz eines Quadrats hat, unabhängig von
der Existenz weiterer Instanzen, ihren eigenen Mittelpunkt, ihre eigene Seitenlänge, ihre eigene Farbe. Jedes Objekt belegt somit die Attribute mit eigenen Werten.1 Von den Attributen verlangt man häufig, dass sie
gekapselt sind, d.h., anderen (d.h. anderen Klassen)
wird ein direkter Zugriff auf die Werte verboten. Ob und
wie man auf die Werte der Attribute eines Objektes
zugreifen kann, entscheidet das Objekt über seine
Methoden. Um Klassen unabhängig von der implementierten Sprache darstellen zu können, wurde die
UML (Unified Modelling Language) entwickelt, die sich
mittlerweile als Standard durchgesetzt hat. In der UML-Notation hat unsere Klasse Quadrat die links
oben stehende Gestalt: Die Attribute sind alle privat - in Java private -, erkennbar an dem „–“ vor dem
Bezeichner. Niemand kann „von außen“ schreibend oder lesend auf ihre Werte zugreifen. Die
Methoden dagegen sind öffentlich - in Java public -, erkennbar an den „+“-Zeichen vor den Methodennamen. An der Signatur des Konstruktors lässt sich folgendes ablesen: Erzeugt man eine Instanz
der Klasse Quadrat, so verfügt das erzeugte Objekt über einen wohldefinierten Mittelpunkt, eine bestimmte Seite und eine konkrete Farbe. Ansonsten besitzt ein Quadrat Methoden um den Mittelpunkt,
die Farbe und die Seitenlänge zu setzen (setMittelpunkt(…), setFarbe(…), setSeite(…))
und Methoden um Farbe und Seitenlänge abzufragen (getFarbe(), getSeite()). Schließlich hat
die Klasse eine Methode, die es erlaubt, das Quadrat
zeichnen zu lassen.
Die UML-Notation für den Kreis zeigt dessen gesamte
Funktionalität. Attribute und Methoden sind analog zu
der Klasse Quadrat konstruiert, so dass sie im
Einzelnen nicht mehr aufgezählt und dokumentiert
werden müssen. Auffällig ist, dass die Klassen Kreis
und Quadrat viele Attribute und Methoden gemeinsam haben. Das legt nahe, eine Klasse zu konstruieren, die alle gemeinsamen Attribute und Methoden von
Quadrat und Kreis aufnimmt. Diese neue Kasse
wollen wir Figur nennen.
1.5
Die abstrakte Klasse Figur
Welche Attribute und Methoden die Klasse Figur aus den Klassen Quadrat und Kreis übernimmt,
lässt sich leicht der Klassendarstellung unten entnehmen. Der Name der Klasse Figur ist in der
UML-Notation kursiv geschrieben, da Figur eine abstrakte Klasse ist - dazu später mehr. Die Pfeile
von Quadrat und Kreis zu Figur zeigen dem Leser, dass beide von Figur erben, d.h. alle Attribute und Methoden, die in Figur implementiert sind, stehen auch den Quadrat- und Kreisobjekten
zur Verfügung, so als wären sie dort implementiert. Die Attribute in Figur, aber auch die in den Klassen Kreis und Quadrat haben jetzt das „#“-Zeichen – in Java protected – vorangestellt. Das hat
zur Folge, dass auf die Attribute nicht nur von der Klasse selbst, sondern auch von ihren Unterklassen
direkt zugegriffen werden kann. Für alle anderen Klassen reagieren sie, als wären sie privat. Um
flexibel zu bleiben, haben wir die Attribute in den Klassen Quadrat und Kreis ebenfalls protected
gesetzt, damit Klassen, die von Quadrat bzw. Kreis abgeleitet sind, ebenfalls auf diese Attribute
zugreifen können.
Die Methoden zeichne(…) taucht mit gleicher Signatur in Figur und „ihren Erben“ auf. Einerseits
wird die von Figur geerbte Methode zeichne(…) in den Klassen Quadrat und Kreis überschrieben um ihr die klassenspezifischen Funktionalität zu geben. Andererseits sollten Dinge wie das Set1
Der geschilderte Fall kommt am häufigsten vor. Es macht aber manchmal auch Sinn, dem Objekt zu
verbieten, eine eigene Kopie eines Attributs anzulegen. Wir sprechen dann von Klassen-Attributen.
copyleft:munz
3
Fachschulen Lörrach
1
Einführung in die objektorientierte Programmierung (OOP)
Java
zen der Farben immer noch in Figur passieren, denn das ist etwas, was nicht(!) spezifisch Quadrat
oder Kreis ist. Folglich schafft man sich eine neue Methode, sie heißt zeigeDich(…), die das
Farbe setzten übernimmt. Dann muss aber zeigeDich(…) die Methode zeichne(…)aufrufen. Dazu
muss zeichne(…) in Figur angelegt sein, obwohl ihre Funktionalität erst in Quadrat bzw. Kreis
implementiert wird. Um sicher zu stellen, dass alle von Figur abgeleitete Klassen die Methode
zeichne(…) auch wirklich implementieren, notieren wir von zeichne(…) in Figur nur den Methodenkopf und setzten davor das Schlüsselwort abstract. Zwangsläufig wird dadurch die ganze
Klasse Figur abstrakt. Eine Klasse, die von Figur erbt, muss die geerbte abstrakte Methode überschreiben2, genauer, implementieren, andernfalls würde der Compiler eine Fehlermeldung erzeugen.
In der UML-Notation erkennt man eine abstrakte Klasse an der kursiven Schreibweise ihres Namens.
Um eine konkrete Figur, also eine Instanz von Quadrat bzw. Kreis zeichnen zu lassen, brauchen
wir nur für diese Objekte die Methode zeigeDich(…) aufzurufen, zeichne(…) braucht also nicht
mehr öffentlich zu sein, wir schützen sie, in dem wir sie protected setzen.
Aufgabe 2:
1.6
Formulieren Sie die Klasse VielEck in UML-Notation.
Pakete
Alle Fachklassen zu diesem Projekt gehören logisch zusammen. In der objektorientierten Programmierung gibt es die Möglichkeit, diese Zusammengehörigkeit mit Hilfe von Paketen (packages)
auszudrücken. In Java erzeugt man hierzu einen Ordner, wo alle Klassen, die zu dem Paket gehören,
gesammelt werden. Ordnernamen und Paket-Bezeichnung sind also identisch.
Man kann auch Unterpakete erzeugen. Dazu erzeugt man entsprechend Unterordner und in der Paketbezeichnung
trennt man diese anstatt durch einen Backslash durch einen Punkt. In Eclipse einfacher!!
Im Ordner figur (Pakete werden immer mit Kleinbuchstaben geschrieben) befinden sich die Klassen
Figur, Kreis, Quadrat und VielEck. In jeder dieser Klassen steht als erste Zeile
package figur;
Andere Klassen, die nicht zum Paket figur gehören, müssen mit import figur.*; die Klassen
aus dem Paket figur importieren.
2
Auf die durch Überschreibung zugedeckte Methode kann man mit super immer noch zugreifen, sie
ist also in den Unterklassen durch Überschreiben nicht verloren gegangen.
copyleft:munz
4
Fachschulen Lörrach
1
Einführung in die objektorientierte Programmierung (OOP)
copyleft:munz
5
Java
Fachschulen Lörrach
1
1.7
Einführung in die objektorientierte Programmierung (OOP)
Java
Die Quelltexte
Die abtrakte Klasse Figur:
package figur;
import java.awt.*;
public abstract class Figur {
protected int xMitte, yMitte;
protected Color farbe;
}
public Figur(int xMitte, int yMitte, Color farbe) {
this.xMitte = xMitte;
this.yMitte = yMitte;
this.farbe = farbe;
}
public void setMittelpunkt(int xMitte, int yMitte) {
this.xMitte = xMitte;
this.yMitte = yMitte;
}
public void setFarbe(Color farbe) {
this.farbe = farbe;
}
public Color getFarbe() {
return farbe;
}
public void zeigeDich(Graphics g) {
if (g != null) {
g.setColor(farbe);
zeichne(g);
}
}
protected abstract void zeichne(Graphics g);
Die Klasse Quadrat:
package figur;
import java.awt.*;
public class Quadrat extends Figur {
protected int seite;
public Quadrat(int xMitte, int yMitte, int seite, Color farbe) {
super(xMitte, yMitte, farbe);
setSeite(seite);
}
public void setSeite(int seite) {
if (seite >= 0) // Zusicherung
this.seite = seite;
}
public int getSeite() {
return seite;
}
protected void zeichne(Graphics g) {
g.fillRect(xMitte - seite/2, yMitte - seite/2,
2*(seite/2), 2*(seite/2)
);
}
}
copyleft:munz
6
Fachschulen Lörrach
1
Einführung in die objektorientierte Programmierung (OOP)
Java
Die Klasse Kreis:
package figur;
import java.awt.*;
public class Kreis extends Figur {
protected int radius = 0;
public Kreis(int xMitte, int yMitte, int radius, Color farbe) {
super(xMitte, yMitte, farbe);
setRadius(radius);
}
public void setRadius(int radius) {
if (radius >= 0) // Zusicherung
this.radius = radius;
}
public int getRadius() {
return radius;
}
protected void zeichne(Graphics g) {
g.fillOval(xMitte-radius, yMitte-radius, 2*radius, 2*radius);
}
}
Erläuterungen zu den Quelltexten:
Die Attribute xMitte, yMitte und farbe sind in der Klasse Figur außerhalb der Methoden deklariert. Man nennt sie deshalb Objektvariable, da sich ihre Gültigkeit auf das ganze von der Klasse erzeugte Objekt erstreckt. Nach der Deklaration der Attribute folgt die Deklaration der Konstruktoren und
der restlichen Methoden.
Konstruktoren erkennt man daran, dass sie als Bezeichner den Namen der Klasse tragen. Sie haben
keinen Rückgabewert. In einem Konstruktor wird all das untergebracht, was nötig ist, um ein Objekt
dieser Klasse beim Erzeugen von Anfang an in einen bestimmten Zustand zu versetzen. Dieser Zustand ist dadurch definiert, dass die Attribute ganz bestimmte Werte haben. Da Figur eine abstrakte
Klasse ist, lassen sich von ihr keine Objekte erzeugen.
Die Programmzeile
Figur eineFigur = new Figur(100,100, Color.BLUE);
ist nicht zulässig, sie würde zu einem Compiler-Fehler führen.
Anders mit den Klassen Quadrat und Kreis. Instanziiert man etwa ein Quadrat-Objekt, wir nennen
es z.B. einQuadrat, durch
Quadrat einQuadrat = new Quadrat(50, 100, 200, Color.BLUE);
so hat das erzeugte Quadrat einen Mittelpunkt (50, 100) eine Seitenlänge von 200 Pixel und die Füllfarbe blau. Wir erwähnen, dass jede Klasse einen Konstruktor besitzt. Wird er nicht explizit implementiert, so erzeugt nämlich der Compiler einen Standardkonstruktor, der im Fall der Klasse Quadrat die
Gestalt Quadrat() hätte. Diesen Standardkonstruktor gibt es nicht mehr, sobald man einen eigenen
erstellt hat. Will man auf ihn trotzdem nicht verzichten, muss man ihn explizit programmieren. Wir sehen also, dass eine Klasse mehr als einen Konstruktor haben kann.
In der folgenden Programmsequenz, wird eineFigur zunächst als vom Typ Figur deklariert (nicht
instanziiert).
Figur eineFigur;
if (…) eineFigur = new Quadrat(…);
else eineFigur = new Kreis(…);
eineFigur.zeigeDich();
Abhängig von der Erfüllung einer Bedingung (if…) wird eineFigur als ein Quadrat- bzw. als ein
Kreis-Objekt instanziiert. Für dieses Objekt wird zeigeDich(…), das sie von Figur geerbt haben,
aufgerufen, diese ruft ihrerseits die Methode zeichne(…) auf, die in ihrer Funktionalität der Klasse
Quadrat bzw. Kreis angepasst ist. Je nach Erfüllen der Bedingung wird also ein Quadrat oder ein
Kreis gezeichnet. Wir werden später ein Programm vorstellen, das dieses sog. dynamische Binden
ausnützt.
copyleft:munz
7
Fachschulen Lörrach
1
Einführung in die objektorientierte Programmierung (OOP)
Aufgabe 3:
Java
Implementieren Sie die Klasse VielEck, die wie Quadrat und Kreis von Figur erbt.
Ist die Eckenzahl 6 und die Farbe rot, so soll das Sechseck in der Form dargestellt werden, wie es die Abbildung zeigt.
Das regelmäßige Sechseck kann man sich einem unsichtbaren Kreis einbeschrieben
vorstellen. Dieser Kreis ist durch den Mittelpunkt des Vielecks und einen Radius festgelegt.
Zur Implementierung dient die ebenfalls unten stehende UML-Darstellung der Klasse
als Vorlage. Der Konstruktor wird analog zu den Konstruktoren von Quadrat und
Kreis implementiert, ebenso die restlichen Methoden. Für das Zeichnen des Vielecks
benutze man die Methode.
fillPolygon(…) aus der Klasse Graphics.
copyleft:munz
8
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
2
Grafik-Programmierung (GUI)
Bei der Entwicklung von Java wurde von Anfang an Wert auf die Gestaltung graphischer Oberflächen
(GUI = Graphical User Interface) gelegt, d.h. es werden folgende Eigenschaften unterstützt:
Einfache Grafik-Methoden zum Zeichen von Linien oder Figuren, zur Text-Darstellung und zum
Füllen von Flächen.
Erweiterte Grafik-Methoden zum Manipulieren von Bitmaps und zur Sound-Ausgabe.
Dialog-Elemente und Dialog-Fenster zur Kommunikation mit dem Anwender.
Methoden zum Reagieren auf Maus-, Tastatur- und Fenster-Ereignisse.
2.1
Das Abstract Windowing Toolkit (AWT)
Das Abstract Windowing Toolkit (AWT) ist eine Sammlung von Klassen, mit denen plattformunabhängig Benutzerschnittstellen wie Grafik-Fenster oder Sound programmiert werden können. Als Programmierer muss man sich somit nicht um die plattformspezifischen Fenster-Elemente kümmern, ein
entsprechendes Java-Programm läuft ohne neu kompiliert werden zu müssen auf jedem Computer,
auf dem die Java-Virtual-Machine (VM) installiert ist.
Bei den AWT-Klassen spricht man von „schwergewichtigen“ Komponenten, da sie auf Komponenten
zurückgreifen, die vom Betriebssystem zur Verfügung gestellt werden.
In der Praxis hat sich jedoch gezeigt, dass die Gemeinsamkeiten der von den verschiedenen Betriebsystemen bereitgestellten Grafikfunktionen nicht sehr groß sind. Das AWT muss deshalb mit einem
recht kleinen Funktionsumfang auskommen. Außerdem ist das Aussehen der Grafik-Fenster ebenfalls
plattformabhängig. Durch die Betriebssystemabhängigkeit ist es sehr aufwendig, fensterspezifische
Eigenschaften auf andere Plattformen zu portieren.
2.2
Swing
Um diese Nachteile auszugleichen hat SUN ab dem JDK 1.2 die Java Foundation Classes (JFC) geschaffen, deren wichtigster Bestandteil die Swing-Komponenten sind. Swing, das im Dezember 1998
offiziell frei gegeben wurde, ist eine Sammlung „leichtgewichtiger“ Grafik-Elemente. Als „leichtgewichtig“ bezeichnet man die Eigenschaft von Swing, vollständig die Kontrolle über Aussehen und
Wirkungsweise der betreffenden Elemente zu übernehmen und dies nicht dem jeweiligen Betriebssystem zu überlassen. Aussehen und Wirkung (pluggable look and feel) lassen sich beliebig ändern.
Von SUN und anderen Anbietern werden PL&F bereitgestellt. Wenn man möchte, kann man sich ein
PL&F auch selbst erstellen.
Hier drei Beispiele für verschiedene PL&F wie sie mit dem JDK mitgeliefert werden:
Java look and feel
copyleft:munz
Windows look and feel
9
CDE/Motif look and feel
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Die Vorteile von Swing:
MVC (model-view-controller) Architektur, d.h. Trennung von Daten, GUI-Anwendungen und
Benutzer-Eingaben
Plattformunabhängig, verschiedene pluggable look and feels.
Sehr umfangreiche Komponenten-Bibliothek
Unterstützung der Java-Beans-Convention, dadurch einheitliche Methoden-Bezeichner
Unterstützung von „drag & drop“
Bereitstellung aufwendiger Grafik-Routinen
Die Nachteile von Swing:
Da alle Komponenten selbst gezeichnet werden müssen, benötigt Swing eine hohe CPU-Leistung und recht viel Hauptspeicher.
Swing wird nicht von den gängigen Web-Browsern unterstützt, da diese bisher nur das JDK 1.0
und gelegentlich das JDK 1.1 implementieren.
Damit die Browser auch Applets mit Swing-Komponenten darstellen können, muss man ein besonderes Plug-In installieren, das mit dem JDK oder JRE mitgeliefert wird. Der HTML-Code für
Swing-Applets enthält deshalb auch nicht die üblichen Applet-Tags, sondern Code um Plug-Ins
einzubetten. Leider verfahren die verschiedenen Browserhersteller, insbesondere Microsoft und
Netscape, unterschiedlich beim Einbetten von Plug-Ins. Der HTML-Code wird dadurch unübersichtlich.
Zum Testen von Swing-Applets bietet sich deshalb der AppletViewer von SUN an, der auch die
gewöhnlichen Applet-Tags versteht.
Weiterhin liefert SUN mit dem JDK den HTMLConverter aus, ein komfortables Werkzeug, um
aus HTML-Code mit Applet-Tags entsprechenden HTML-Code für das Java-Plug-In zu machen.
Obwohl alle Swing-Komponenten vom AWT abgeleitet sind, wird dringend empfohlen, niemals
gleichzeitig AWT- und Swing-Komponenten nebeneinander zu verwenden. Es könnten sonst
unerwünschte Nebeneffekte auftreten.
2.3
Andere Java Foundation Classes (JFC)
Andere Firmen bieten konkurrierende Schnittstellen-Klassen an, so genannte Java Foundation
Classes (JFC):
Netscape bietet die Internet Foundation Classes (IFC) an, die mit den Swing-Klassen eine
gemeinsame Basis haben.
Microsoft geht mit den Application Foundation Classes (AFC) völlig eigene Wege, wie sie bei
Microsoft typisch sind.
copyleft:munz
10
Fachschulen Lörrach
2
2.4
Grafik-Programmierung (GUI)
Java
Grafik-Programmierung mit Swing
Das Erstellen von Grafiken mit Swing funktioniert im Prinzip genauso wie mit AWT. Dennoch gibt es
einige Unterschiede und einige Regeln, die zu beachten sind. Grafiken, die mit Swing erstellt werden,
sollten grundsätzlich in leichtgewichtigen Komponenten erstellt werden. Das sind Komponenten der
Klasse JComponent und deren Unterklassen, insbesondere JPanel, JButton oder JLabel. Diese
müssen dann letztendlich in einen so genannten Top-Level-Container wie JFrame, JDialog oder
JApplet eingebettet werden. Das Standardverfahren, um eine Zeichenfläche zu erzeugen, ist eine
Unterklasse von JPanel zu bilden (in AWT entspricht das der Klasse Canvas). Das ist besser, als
Unterklassen direkt von JComponent zu bilden, da JPanel als Unterklasse von JComponent einige
zusätzliche Eigenschaften hat. Instanzen von JPanel sind in der Regel opak, d.h. sie haben einen
nicht transparenten Hintergrund, und es ist das so genannte Double-Buffering eingeschaltet, das ein
Flackern beim Bildaufbau verhindert. Dazu wird die Grafik zunächst unsichtbar in einem imaginären
Bildbereich vollständig erstellt. Anschließend wird dieser Bildbereich mit dem sichtbaren Bereich ausgetauscht.
Das folgende Programm kann als Grundgerüst zum Erzeugen von Grafiken dienen. Zunächst wird
eine Klasse JGrafik als Unterklasse von JFrame erstellt. Diese enthält eine Instanz
zeichenFlaeche der Klasse GrafikPanel als Unterklasse von JPanel.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JGrafik extends JFrame {
private GrafikPanel zeichenFlaeche;
public JGrafik(String title) {
super (title);
addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent evt) {
System.exit(0);}}
);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
zeichenFlaeche = new GrafikPanel(300, 200);
cp.add(zeichenFlaeche);
pack();
setVisible(true);
}
}
copyleft:munz
public static void main (String[] args) {
new JGrafik("Grafik-Beispiel");
}
11
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Die eigentliche Zeichenfläche ist eine Unterklasse von JPanel:
import java.awt.*;
import javax.swing.*;
public class GrafikPanel extends JPanel {
private int breite, hoehe;
public GrafikPanel(int breite, int hoehe) {
super();
this.breite = breite; this.hoehe = hoehe;
}
public Dimension getPreferredSize() {
return new Dimension(breite, hoehe);
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g); // sollte immer aufgerufen werden!
// Hier kommen die eigentlichen Grafikanweisungen hin.
}
Zum Erstellen von Grafiken überschreibt man die Methode paintComponent aus
javax.swing.JComponent. Die Syntax von paintComponent lautet:
public void paintComponent(Graphics g). Dabei ist g der Grafik-Kontext der entsprechenden Klasse und eine Instanz der Klasse Graphics. Obwohl es in JComponent auch die Methode
paint gibt, wie die gleichnamige Methode in java.awt.Component, wird empfohlen
paintComponent anstatt paint zu verwenden. Das ist ökonomischer, da paint zusätzlich weitere
Seiteneffekte auslöst. So muss paint beispielsweise sehr aufwendig die Reihenfolge der zu zeichnenden Objekte ermitteln, wenn sich mehrere Objekte gegenseitig überdecken. Da paint letztlich
ohnehin aufgerufen wird, wäre das doppelter Aufwand. Damit Swing die selbst erstellte Komponente
richtig in die Überdeckungshierachie einordnen kann, sollte man zunächst durch
super.paintComponent die entsprechende Methode der Oberklasse aufrufen.
Der Aufruf von paintComponent selbst erfolgt dann automatisch durch Swing und sollte niemals aus
dem Programm heraus erfolgen.
Das Grundgerüst soll jetzt so erweitert werden, dass in der Mitte des Grafikfensters ein weißes Rechteck mit schwarzem Rand gezeichnet wird. Dazu muss paintComponent ein wenig erweitert werden.
public void paintComponent(Graphics g) {
super.paintComponent(g);
int dx = breite*2/3; int dy = hoehe*2/3;
int x = (breite - dx)/2;
int y = (hoehe - dy)/2;
}
copyleft:munz
g.setColor(Color.WHITE);
g.fillRect(x, y, dx, dy);
g.setColor(Color.BLACK);
g.drawRect(x, y, dx, dy);
12
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Das Ergebnis:
Eine weitere wichtige Veränderung von Swing gegenüber AWT ist das automatische Erkennen, ob
eine Komponente ganz oder in Teilen neu gezeichnet werden muss oder nicht. Das ist immer dann
der Fall, wenn sie z.B. durch andere Windows-Fenster teilweise oder ganz abgedeckt gewesen war.
Manchmal ist es jedoch erforderlich, ein Neuzeichnen aus dem Programm heraus zu veranlassen.
Das ist immer dann der Fall, wenn die Grafik durch ein Ereignis verändert werden soll. Im obigen Beispiel könnte man eine Schaltfläche einfügen, so dass bei jedem Klick die Farbe des Rechtecks verändert wird. In diesem Fall erzwingt man ein Neuzeichnen durch Aufruf der Methode repaint. Dabei
ruft Swing den RepaintManager auf, der ein Neuzeichnen der Komponenten für den betreffenden
Fensterbereich und in der richtigen Reihenfolge veranlasst.
Aufgabe 4:
2.5
Zeichnen Sie ein Rechteck statt in ein JPanel in eine Schaltfläche (JButton) oder in
ein Beschriftungsobjekt (JLabel).
Die wichtigsten Methoden zum Zeichnen grafischer Objekte
Die Methoden zum Zeichnen von grafischen Objekten findet man in der Klasse
java.awt.Graphics. Es ist eine abstrakte Klasse, somit kann man selbst keine Instanz dieser
Klasse erzeugen. Das ist aber auch nicht nötig, da alle Klassen, die ein Zeichnen ermöglichen, die
abstrakten Methoden in Graphics entsprechend implementieren. Beim Aufruf von
paintComponent(Graphics g) wird der Grafikkontext g erst zur Laufzeit ermittelt. Ein Versuch
diesen Grafikkontext z.B. mit getGraphics oder mit createGraphics schon vor dem eigentlichen
Zeichnen ermitteln zu wollen, wird scheitern, da createGraphics bzw. getGraphics dann null
zurückliefern.
Falls ein vorzeitiges Erstellen von Grafiken nötig sein sollte, erzeugt man eine Instanz image von
java.awt.BufferedImage und kann dann mit image.createGraphics() ihren Grafikkontext
vom Typ Graphics2D erzeugen oder mit image.getGraphics() den Grafikkontext ermitteln, der
in diesem Fall aus Kompatibilitätsgründen vom Typ Graphics ist. Zum eigentlichen Zeichnen ruft
man dann innerhalb von paintComponent die Methode drawImage(image) auf.
copyleft:munz
13
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Hier sind die wichtigsten Methoden aus Graphics aufgelistet. Manche Methoden haben verschiedene Signaturen. Eine genaue Auflistung findet man in der Dokumentation zum Java-API.
public void drawImage(
Image img, int x, int y, ImageObserver observer
);
Zeichnet am Punkt (x, y) soviel vom Image, wie gerade verfügbar ist.
public void drawLine(int x1, int y1, int x2, int y2);
zieht eine Linie vom Punkt (x1, y1) zum Punkt (x2, y2).
public void drawRect(int x, int y, int width, int height);
zeichnet ein Rechteck, dessen linke obere Ecke bei (x, y) liegt und die Breite width und die
Höhe height hat.
public void drawRoundRect(
int x, int y, int width, int height, int arcWidth, int arcHeight
);
zeichnet ein abgerundetes Rechteck, dessen linke obere Ecke bei (x, y) liegt, die Breite
width und die Höhe height hat und arcWidth und arcHeight die Ellipsenabschnitte der
abgerundeten Ecken bedeuten.
public void fillRect(int x, int y, int width, int height);
zeichnet ein ausgefülltes Rechteck.
public void fillRoundRect(
int x, int y, int width, int height, int arcWidth, int arcHeight
);
zeichnet ein ausgefülltes und abgerundetes Rechteck.
public void drawPolygon(int x_arr[], int y_arr[], int count);
zeichnet ein geschlossenes Polygon mit count Ecken. x_arr und y_arr sind jeweils ein Feld
von x– bzw. y–Koordinaten.
public void drawPolyline(int x_arr[], int y_arr[], int count);
zeichnet ein nicht geschlossenes Polygon.
public void fillPolygon(int x_arr[], int y_arr[], int count);
zeichnet ein ausgefülltes Polygon.
public void drawOval(int x, int y, int width, int height);
zeichnet eine Ellipse, die in ein Rechteck eingeschlossen ist, dessen linke obere Ecke bei
(x, y) liegt und die Breite width und die Höhe height hat.
Für Kreise müssen width und height denselben Wert haben.
public void fillOval(int x, int y, int width, int height);
zeichnet eine ausgefüllte Ellipse bzw. einen ausgefüllten Kreis.
public void drawArc(
int x, int y, int width, int height, int startAngle, int arcAngle
);
zeichnet einen Ellipsenbogen. Die Parameter haben die gleiche Bedeutung wie bei drawOval.
startAngle gibt den Winkel ab der 0°–Position an, die bei 3 Uh r liegt, und arcAngle ist der
Winkel im Gegenuhrzeigersinn ab startAngle. Winkel werden dabei im Gradmaß gemessen.
public void fillArc(
int x, int y, int width, int height, int startAngle, int arcAngle
);
zeichnet einen ausgefüllten Ellipsenbogen. Die Endpunkte werden durch eine Strecke verbunden.
public void clearRect(int x, int y, int width, int height);
löscht einen Rechteckbereich (d.h. füllt ihn mit der Hintergrundfarbe) mit der Breite width und
der Höhe height. Die linke obere Ecke ist bei (x, y).
copyleft:munz
14
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
public void copyArea(
int x, int y, int width, int height, int dx, int dy
);
kopiert das Rechteck bei (x, y) mit der Breite width und der Höhe height nach
(x+dx, y+dy).
public void drawString(String str, int x, int y);
schreibt den Text str beginnend bei (x, y) in ein Fenster.
Dabei wird der Text in einem systemabhängigen Standard–Font ausgegeben.
Zeichnen Sie anstatt eines Rechtecks eine Ellipse, ein Quadrat, ein Kreis, ein Dreieck,
ein Vieleck, …
Aufgabe 5:
2.6
Grafiken mit der Klasse BufferedImage
Das folgende Beispiel erzeugt eine Grafik mit einem zufälligen Strichmuster:
public void paintComponent(Graphics g) {
super.paintComponent(g);
int x0 = 80, y0 = 20, len = 100, maxX = breite - x0;
}
for (int x = x0; x < maxX; x++) {
if (Math.random() < 0.5)
g.drawLine(x, y0, x, y0+len);
}
Leider hat das Programm einen Haken. Da ein Grafik-Fenster bei jeder Veränderung (Größenänderung, Überdeckung durch andere Fenster der Desktops usw.) neu gezeichnet werden muss, wird bei
jedem Neuzeichnen paintComponent aufgerufen und dabei jedes Mal ein neues Zufallsmuster erzeugt. Besonders augenfällig wird das, wenn das Grafik-Fenster nur teilweise verdeckt wurde wie man
auf dem zweiten Bild sehen kann.
Damit das Grafikmuster bei einer Wiederherstellung des Fensters erhalten bleibt, zeichnet man es in
ein BufferedImage. Dann wird nur in das Image neu gezeichnet, dessen Grafik-Kontext man mit
getGraphics erhält. Die Methode drawImage, die sich auf den Grafik-Kontext der Klasse
GrafikPanel bezieht, stellt das Image schließlich dar. Wenn man jetzt zum Hauptprogramm eine
Schaltfläche hinzufügt, kann man das Muster gewollt auf Knopfdruck verändern.
copyleft:munz
15
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Für die Klasse GrafikPanel ist dann allerdings etwas mehr Aufwand nötig:
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
public class GrafikPanel extends JPanel {
private int breite, hoehe;
private BufferedImage image;
}
2.7
public GrafikPanel(int breite, int hoehe) {
this.breite = breite;
this.hoehe = hoehe;
setPreferredSize(new Dimension(breite, hoehe));
erstelleGrafik();
}
public void erstelleGrafik() {
int b = breite - 80, h = hoehe - 80;
image = new BufferedImage(b, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2D = image.createGraphics();
g2D.setColor(getBackground());
g2D.fillRect(0, 0, b, h);
}
public void zeichneGrafik() {
int b = image.getWidth(), h = image.getHeight();
g2D.setColor(getBackground());
g2D.fillRect(0, 0, b, h);
g2D.setColor(Color.BLACK);
for (int x = 0; x < b; x++) {
if (Math.random() < 0.5)
g2D.drawLine(x, 0, x, h);
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int x = (breite - image.getWidth())/2;
int y = (hoehe - image.getHeight())/2;
g.drawImage(image, x, y, this);
}
Java 2D Grafik API
Graphics2D ist ein 2D Graphics Application Programming Interface (API), das eine Vielzahl von
Grafikerweiterungen anbietet. Die wichtigsten sind:
Koordinatensystem mit Koordinaten des Datentyps float oder double
Zeichnen von Linien beliebiger Stärke und beliebigem Muster
Gestaltung von Linien-Enden und Linien-Verbindungen
Füllen von Flächen mit Gradienten und Mustern
Verschieben, Rotieren Skalieren und Beschneiden von Text und Grafik
Überlagerung von Text and Grafik
Kantenglättung und verschiedene Verfahren beim Rendern von Grafik-Darstellungen
Die folgenden Beispiele zeigen, wie man die Klasse Graphics2D einsetzt. Durch Type-Casting wandelt man den Grafikkontext vom Typ Graphics in einen Grafikkontext vom Typ Graphics2D um. Mit
setStroke kann man dann die verschiednen Linienarten zuweisen, die man zuvor mit einem der
Konstruktoren aus der Klasse BasicStroke erzeugt hat.
copyleft:munz
16
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Damit die Grafiken nicht so „pixelig“ ausschauen, bietet die Klasse Graphics2D die Möglichkeit, Grafiken mit Kantenglättung („Antialiasing“) darzustellen. Dazu erzeugt man eine Instanz von
RenderingHints mit einem geeigneten Schlüssel/Wert Paar und passt den speziellen Schlüssel an
den speziellen Wert dieses RenderingHints-Objekts mit Hilfe der Methode put aus
RenderingHints an. Dem Grafikkontext übergibt man zum Schluss dieses RenderingHints-Objekt mit der Methode add aus Graphics2D.
Das erste Beispiel erzeugt einen Ellipsenbogen mit runden Linien-Enden:
import java.awt.*;
import javax.swing.*;
public class GrafikPanel extends JPanel {
private int breite, hoehe;
private RenderingHints renderHints;
}
public GrafikPanel(int breite, int hoehe) {
this.breite = breite;
this.hoehe = hoehe;
setPreferredSize(new Dimension(breite, hoehe));
// Zum Glätten der Kanten Antialiasing einschalten
renderHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
renderHints.put(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY
);
}
public void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
g2D.setRenderingHints(renderHints);
BasicStroke stroke = new BasicStroke(
20.0f,
// Linien-Breite
BasicStroke.CAP_ROUND,
// Linien-Ende
BasicStroke.JOIN_ROUND
// Linien-Verbindung
);
g2D.setStroke(stroke);
g2D.drawArc(20, 20, breite - 40, hoehe - 40, 0, 180);
}
Das zweite Beispiel erzeugt einen Ellipsenbogen mit geraden Linien-Enden und einem benutzerdefinierten Linien-Muster:
public void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
g2D.setRenderingHints(renderHints);
BasicStroke stroke = new BasicStroke(
20.0f,
// Linien-Breite
BasicStroke.CAP_BUTT,
// Linien-Ende
BasicStroke.JOIN_ROUND,
// Linien verbinden
0.0f,
// Verbindungsgrenze
new float[] {10.0f, 10.0f, 2.0f, 10.0f}, // Dash-Pattern
0.0f);
// Dash-Pattern Offset
g2D.setStroke(stroke);
g2D.drawArc(20, 20, breite - 40, hoehe - 40, 0, 180);
}
copyleft:munz
17
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Zwei Bögen mit verschiedenen Linienmustern:
2.8
Font–Objekte
Um benutzerdefinierte Fonts zu erzeugen, verwendet man den Konstruktor
public Font(String name, int style, int size);
Der Parameter name gibt den Namen des gewünschten Fonts an, z.B. „Arial“ oder „Times New
Roman“. Diese Namen entsprechen den Namen real existierender True-Type-Fonts.
Man kann statt realer Font-Namen auch logische Font-Namen angeben, die man in der Regel immer
verwenden sollte:
SansSerif (wird in Windows im Allgemeinen durch „Arial“ ersetzt)
Serif (Windows: „Times New Roman“)
Monospaced (Windows: „Courier New“)
Dialog (Windows: „Arial“)
Für style setzt man eine der Konstanten:
Font.PLAIN (standard)
Font.BOLD (fett),
Font.ITALIC (kursiv)
Font.BOLD | Font.ITALIC (bitweise „oder“, ergibt fett und kursiv)
size gibt die Schriftgröße in point an (dabei gilt: 1 pica = 12 pt = 12/72 inch = 4,22 mm).
Mit public void setFont(Font font); wird das Font–Objekt in den Grafikkontext eingetragen,
mit public Font getFont(); kann der aktuelle Font abgefragt werden.
Beispiel:
font = new Font("Serif", Font.PLAIN, 18);
g.setFont(font);
g.drawString("Beispiel-Text", 10, 20);
2.8.1
Font–Informationen
Mit den Methoden
public String getFamily();
public int getStyle();
public int getSize();
können Informationen über den aktuellen Font abgefragt werden.
2.8.2
Font–Metriken
Mit
public FontMetrics getFontMetrics(Font font);
public FontMetrics getFontMetrics();
ermittelt man die Größeneigenschaften von Zeichen des Fonts font, bzw. des aktuellen Fonts (parameterlose Variante).
copyleft:munz
18
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Das Bild zeigt am Beispiel der Schriftart SansSerif (Times New Roman) mit der Schriftgöße 12 pt,
welche Größen eines Zeichens gemessen werden. Es stehen folgende Methoden zur Verfügung:
public int charWidth(char ch);
Breite des Zeichens ch
public int stringWidth(String s);
Breite des Strings s
public int getAscent();
Oberlänge
public int getDescent();
Unterlänge
public int getHeight();
Höhe
public int getLeading();
Zeilenabstand
Beispiel:
font = new Font("Times New Roman", Font.PLAIN, 36);
g.setFont(font);
g.drawString("mgdAW", 10, 20);
FontMetrics fm = getFontMetrics(font);
System.out.println("Breite:
" + fm.stringWidth("mgdAW"));
System.out.println("Oberlänge:
" + fm.getAscent());
System.out.println("Unterlänge:
" + fm.getDescent());
System.out.println("Höhe:
" + fm.getHeight());
System.out.println("Zeilenabstand: " + fm.getLeading());
copyleft:munz
19
Fachschulen Lörrach
2
2.9
Grafik-Programmierung (GUI)
Java
Aufgaben
Zeichnen Sie die nachfolgenden Figuren:
Aufgabe 6:
Zeichnen Sie ein Gitter mit breiten Aufgabe 7:
Strichen in unterschiedlicher Farbe.
Achten Sie genau auf die Farbe der
Kreuzungspunkte.
Ändern Sie das Programm so ab,
dass die Kreuzungspunkte genau
die andere Farbe erhalten.
Aufgabe 8:
Ändern Sie das Programm nochmals ab. Die Farben der Kreuzungspunkte sollen jetzt abwechselnd wie bei einem Schottenmuster sein.
Erzeugen Sie einen Stapel
„gepackter“ Kreise.
Aufgabe 10: Erzeugen Sie eine Figur mit gestrichelten Linien.
copyleft:munz
Aufgabe 9:
Aufgabe 11: Stellen Sie Text in unterschiedlichen Schriftarten dar.
20
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
2.10 Die Swing-Komponenten
2.10.1 Die Unterklassen von JComponent
Schaltflächen, Textfelder usw. sind von javax.swing.JComponent abgeleitet. Diese Klasse ist eine
direkte Unterklasse von java.awt.Container, welche wiederum direkt von
java.awt.Component abgeleitet ist.
Sämtliche Swing-Elemente aufzuzählen und ihre Funktionsweise zu beschreiben würde den Rahmen
dieser Handreichungen sprengen. Da sei auf die dicken Handbücher verwiesen oder besser auf die
Online-Hilfe und Online-Dokumentation.
Das folgende Schema zeigt ein (unvollständiges) Teilklassen-Diagramm der Swing-Komponenten, die
von javax.swing.JComponent abgeleitet sind (die rechts stehenden Klassen sind Unter-Klassen
der weiter links stehenden Klassen). Sie ersetzen die entsprechenden AWT-Komponenten.
JComponent
AbstractButton
JButton
JMenuItem
JToggleButton
BasicInternalFrameTitlePane
Box
Box.Filler
JColorChooser
JComboBox
JFileChooser
JInternalFrame
JInternalFrame.JDesktopIcon
JLabel
JLayeredPane
JList
JMenuBar
JOptionPane
JPanel
JPopupMenu
JProgressBar
JRootPane
JScrollBar
JScrollPane
JSeparator
JSlider
JSpinner
JSplitPane
JTabbedPane
JTable
JTableHeader
JTextComponent
JCheckBoxMenuItem
JMenu
JRadioButtonMenuItem
JCheckBox
JRadioButton
JDesktopPane
JEditorPane
JTextArea
JTextField
JTextPane
JPasswordField
JToolBar
JToolTip
JTree
JViewport
copyleft:munz
21
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
2.10.2 Die Fenster in Swing
Eine weitere wichtige Gruppe von Swing-Elementen sind die verschiedenen Fenster in Swing. Besonders häufig wird die Klasse javax.swing.JFrame verwendet.
Als weitere Fensterklasse gibt es die Dialog-Fenster, wie sie häufig bei PopUp-Meldungen eingesetzt
werden.
Hier eine Übersicht über die Vererbungshierachie bei den Fenster-Klassen.
Dabei ist java.awt.Window eine direkte Unterklasse von java.awt.Container:
java.awt.Window
jawa.awt.Dialog
java.awt.Frame
javax.swing.JWindow
javax.swing.JDialog
javax.swing.JFrame
2.10.3 JFrame
Ein JFrame stellt ein typisches Windows-Fenster dar, das Titel-Leiste, und ggf. Menü- und Symbolleisten besitzt. Außerdem hat ein JFrame Knöpfe zum Vergrößern, Verkleinern und Verstecken des
Fensters. Es hat einen Rahmen sowie Bereiche von wo aus man das Fenster vergrößern und verkleinern kann. Die eigentliche Fensterfläche enthält dann die oben aufgeführten Elemente.
Die Abbildung zeigt den Aufbau eines JFrames.
Ein JFrame enthält ein JRootPane, welches wiederum ein JLayeredPane enthält. Diese Schichten
wird man im Allgemeinen nicht weiter bearbeiten. Die wichtigste Schicht ist das ContentPane, welches von der Klasse Container abgeleitet ist. Mit getContentPane erhält man auf diese Schicht
Zugriff, mit setContentPane kann man das ContentPane ersetzten (z.B. durch ein JPanel). Im
ContentPane befinden sich auch die Menüleiste.
Das GlassPane ist eine „durchsichtige“ Schicht in vorderster Ebene. Bringt man hier Elemente unter,
überdecken sie die Elemente des ContentPanes.
2.11 Die LayoutManager
Wenn man einer Swing-Komponente wie z.B. JFrame mehrere Schaltflächen hinzufügt, packt Java
sie einfach übereinander. Zum richtigen Anordnen der Komponenten helfen die LayoutManager. Da
Java-Programme auf verschiedenen Rechnern mit unterschiedlichen Betriebsystemen laufen sollen,
kann man das Aussehen eines Grafikfensters als Programmierer nicht vorhersehen. Die einzelnen
Grafik-Komponenten ordnen sich nach gewissen Regeln selbst an, ähnlich wie bei einer HTML-Seite.
Die wichtigsten LayoutManager sind:
FlowLayout
BorderLayout
GridLayout
GridBagLayout
BoxLayout
copyleft:munz
22
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
2.11.1 Beispiele:
Wenn man auf den Einsatz eines LayoutManagers verzichtet, muss man Größe und Position der Grafik-Komponenten mit der Methode setBounds selbst festlegen. Damit kann man das Aussehen eines
Fensters pixelgenau festlegen, ähnlich den Fenstern in Windows. Möglicherweise ist dann das so erstellte Fenster für eine andere Bildschirmauflösung ungeeignet.
Container cp = getContentPane();
cp.setBackground(Color.WHITE);
cp.setLayout(null);
JButton jbTest1 = new JButton("B 1");
jbTest1.setBounds(10, 10, 100, 200);
cp.add(jbTest1);
JButton jbTest2 = new JButton("B 2");
jbTest2.setBounds(120, 140, 220, 60);
cp.add(jbTest2);
JButton jbTest3 = new JButton("B 3");
jbTest3.setBounds(300, 10, 80, 80);
cp.add(jbTest3);
Das FlowLayout reiht die Komponenten von links nach rechts und von oben nach unten an, so wie
Platz ist:
Container cp = getContentPane();
cp.setBackground(Color.WHITE);
cp.setLayout(new FlowLayout());
for (int i = 0; i < anzButton; i++) {
jbTest[i] = new JButton(Integer.toString(i));
cp.add(jbTest[i]);
}
copyleft:munz
23
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Das GridLayout ordnet die Komponenten in einem Gitter an und passt ihre Größe der Größe der
Gitterzellen an. Alle Komponenten erhalten dieselbe Größe.
Container cp = getContentPane();
cp.setBackground(Color.WHITE);
cp.setLayout(new GridLayout(3, 2));
for (int i = 0; i < anzButton; i++) {
jbTest[i] = new JButton(Integer.toString(i));
cp.add(jbTest[i]);
}
Das GridBagLayout ordnet ähnlich wie das GridLayout die Elemente in einem Gitter an. Die Elemente können zusätzlich Zeilen oder Spalten übergreifend angeordnet werden. Außerdem sind Abstandsdefinitionen zu den Zellenrändern möglich. Das GridBagLayout ist sehr umständlich zu
handhaben, weshalb hier auf weitere Erläuterungen verzichtet wird.
Das BorderLayout hat fünf Bereiche, einer im Zentrum und jeweils einen am Rand:
Container cp = getContentPane();
cp.setBackground(Color.WHITE);
cp.setLayout(new BorderLayout());
cp.add(new
cp.add(new
cp.add(new
cp.add(new
cp.add(new
JButton("North"), BorderLayout.NORTH);
JButton("South"), BorderLayout.SOUTH);
JButton("East"), BorderLayout.EAST);
JButton("West"), BorderLayout.WEST);
JButton("Center"), BorderLayout.CENTER);
Das BoxLayout ordnet die Elemente in einer Reihe entweder horizontal (BoxLayout.X_AXIS) oder
vertikal (BoxLayout.Y_AXIS) an, wobei jede Komponente, wenn es möglich ist, ihre individuelle
Größe behält.
copyleft:munz
24
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Container cp = getContentPane();
cp.setBackground(Color.WHITE);
cp.setLayout(new BoxLayout(cp, BoxLayout.Y_AXIS));
JButton jbTest1 =
cp.add(jbTest1);
JButton jbTest2 =
cp.add(jbTest2);
JButton jbTest3 =
cp.add(jbTest3);
JButton jbTest4 =
cp.add(jbTest4);
new JButton("B Nr. 1");
new JButton("Button 2");
new JButton("Schaltfläche 3");
new JButton("B 4");
In den meisten Fällen hat man mehr Komponenten als bei diesen sehr einfachen Beispielen. Komplexere Layouts erzielt man, wenn man verschiedene Instanzen von JPanel erzeugt, jedem JPanel ein
bestimmtes Layout zuweist und diese JPanel geeignet schachtelt.
Für das BoxLayout gibt es noch eine andere Alternative.
Anstatt ein JPanel mit BoxLayout zu erzeugen, gibt es die einfacher zu handhabende Klasse
javax.swing.Box:
Die Programm-Zeile
JPanel panel = new JPanel(new BoxLayout(panel, BoxLayout.X_AXIS));
wird ersetzt durch
Box box = new Box(BoxLayout.X_AXIS);
Die Klasse Box enthält weitere einfache Möglichkeiten, die Anordnung von Elementen zu beeinflussen. Mit den statischen Methoden Box.createGlue(), Box.createHorizontalGlue() und
Box.CreateVerticalGlue() lassen sich unsichtbare Elemente variabler Größe erzeugen. Mit
Box.createHorizontalStrut(int width) und Box.createVerticalStrut(int width)
entsprechend Elemente fester Breite bzw. Höhe.
Außerdem kennt jedes Element, das von javax.swing.JComponent abgeleitet ist, die Methoden
setAlignmentX(float alignmentX) und setAlignmentY(float alignmentY). Mit diesen
Methoden kann man die Lage der Ausrichtungsmarken von Komponenten verändern. Das Argument
0.0f steht für links bzw. oben, 0.5f für mittig und 1.0f für rechts bzw. unten.
Hier ein komplexeres Beispiel:
copyleft:munz
25
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Container cp = getContentPane();
cp.setBackground(Color.WHITE);
cp.setLayout(new BorderLayout());
Box box1 = new Box(BoxLayout.Y_AXIS);
box1.setAlignmentX(0.0f);
box1.setBorder(BorderFactory.createTitledBorder("Box 1"));
JButton jbB1_1 = new JButton("B 1");
jbB1_1.setAlignmentX(0.5f);
JButton jbB1_2 = new JButton("Button 2");
jbB1_2.setAlignmentX(0.5f);
JButton jbB1_3 = new JButton("Schaltfläche 3");
jbB1_3.setAlignmentX(0.5f);
JButton jbB1_4 = new JButton("B. Nr. 4");
box1.add(jbB1_1); box1.add(jbB1_2);
box1.add(jbB1_3); box1.add(jbB1_4);
Box box2 = new Box(BoxLayout.X_AXIS);
box2.setAlignmentX(0.0f);
box2.setBorder(BorderFactory.createTitledBorder("Box 2"));
JLabel jlB2 = new JLabel(" Ein Label ");
jlB2.setOpaque(true);
jlB2.setFont(new Font("SansSerif", Font.BOLD, 36));
JButton jbB2_1 = new JButton("Klick mich!");
jbB2_1.setAlignmentY(0.0f);
JButton jbB2_2 = new JButton("hier drücken");
jbB2_2.setAlignmentY(1.0f);
box2.add(jlB2); box2.add(jbB2_1); box2.add(jbB2_2);
Box box3 = new Box(BoxLayout.Y_AXIS);
box3.setBorder(BorderFactory.createTitledBorder("Box 3"));
JButton jbB3 = new JButton("beenden");
box3.add(box1); box3.add(box2);
box3.add(Box.createVerticalStrut(10));
box3.add(jbB3);
box3.add(Box.createVerticalGlue());
cp.add(box3);
2.12 Rahmen
Jeder von JComponent abgeleiteten Komponente lässt sich mit der Methode
setBorder(Border border) ein Rahmen hinzufügen. Das geht sehr einfach mit Hilfe der Klasse
javax.swing.BorderFactory, wo die wichtigsten Rahmen vordefiniert sind. Um einen Rahmen zu
erzeugen verwendet man eine der statischen Methoden BorderFactory.createXXXBorder, wobei createXXXBorder für eine der folgenden Methoden steht. Für die jeweiligen Argumente sei auf
die Dokumentation zum Java-API verwiesen.
createBevelBorder
Erzeugt einen Rahmen mit 3D-Effekt, je nach dem erhaben oder versenkt.
createLoweredBevelBorder
Erzeugt einen vertieften Rahmen mit 3D-Effekt.
createRaisedBevelBorder
Erzeugt einen erhabenen Rahmen mit 3D-Effekt.
createCompoundBorder
Erzeugt einen Rahmen, der aus zwei weiteren Rahmen zusammengesetzt ist.
createEmptyBorder
Erzeugt einen leeren Rahmen, besonders wichtig um Leerraum um Komponenten zu schaffen.
copyleft:munz
26
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
createEtchedBorder
Erzeugt einen Rahmen mit Relief-Effekt.
createLineBorder
Erzeugt eine farbige Linie um die Komponente
createMatteBorder
Erzeugt einen Rahmen, der sich nach gewissen Regeln beliebig gestalten lässt.
createTitledBorder
Erzeugt einen Rahmen mit Titel.
2.13 Ereignisse
Wenn man auf eine Schaltfläche klickt, Text in ein Textfeld eingibt, ein Fenster schließt oder an einem
Schieberegler zieht, löst man Ereignisse aus, auf die das Programm entsprechend reagieren soll.
In Java werden Ereignisse seit der Version 1.1 mit Hilfe eines Verfahrens verwaltet, das sich Event
Delegation Model nennt.
Das Betriebssystem informiert die GUI-Anwendung über Ereignisse wie z.B. Mausklicks, Tastatureingaben oder Veränderungen von Fenstern durch Versenden von Botschaften (Event). Diese Botschaften werden von Ereignisquellen, z.B. einer Schaltfläche ausgelöst. Empfänger dieser Botschaften sind
Ereignisabhorcher (EventListener), die auf die empfangenen Botschaften entsprechend reagieren
können. Damit die Nachrichtenquelle den zugehörigen EventListener kennt, muss sich der
EventListener bei der Nachrichtenquelle registrieren.
Der Vorteil dieses Verfahrens besteht zum einen darin, dass nur solche Nachrichten transportiert werden, zu denen es auch Empfänger gibt. Das erhöht die Leistungsfähigkeit des Programms. Weiterhin
dient es der klaren Trennung zwischen Programmcode zur Oberflächengestaltung (GUI-Klassen) und
Programm-Code zur eigentlichen Funktionsweise (Fachklassen). Der dadurch etwas größere
Programmieraufwand macht sich bezahlt durch einen weniger fehleranfälligen Code, der auch bei
sehr großen Programmen übersichtlich bleibt.
Je nach Größe des Programms und Anzahl der Ereignisse bieten sich verschiedene Musterlösungen
an.
2.13.1 Auf Button-Klicks reagieren
Zunächst soll an einem einfachen Beispiel die Behandlung von Button-Klicks erläutert werden.
In einem Fenster befinden sich drei Schaltflächen, mit denen die Hintergrundfarbe geändert werden
kann. Eine vierte Schaltfläche beendet beim Klicken das Programm.
Um ein GUI-Fenster zu erhalten, muss die zu erstellende Klasse von JFrame abgeleitet werden. Da
Elemente dieser Klasse auf Ereignisse reagieren können sollen, müsste man diese Klasse zusätzlich
von einer Klasse ableiten, die die Ereignissteuerung bereitstellt. Da in Java Mehrfachvererbung nicht
zulässig ist, klappt dieses Verfahren nicht.
Java stellt für diesen Fall Interfaces zur Verfügung. Ein Interface stellt dabei nur die MethodenKöpfe zur Verfügung. Die Methoden-Rümpfe müssen dann jeweils vom Programmierer an die Bedürfnisse des Programms angepasst werden.
Hier der Code des Beispielprogramms:
Zunächst die üblichen Pakete einbinden …
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
Die Klasse JRotGelbGruen von JFrame ableiten und da die Schaltflächen auf Ereignisse reagieren
sollen, müssen Aktionsabhorcher (ActionListener) hinzugefügt werden. Das Interface
ActionListener stellt dazu eine abstrakte Methode actionPerformed zur Verfügung, die je nach
Bedarf implementiert werden muss.
public class JRotGelbGruen extends JFrame
implements ActionListener {
copyleft:munz
27
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Die notwendigen Objekte deklarieren. Hier werden die Schaltflächen deklariert; die Variable bgColor
speichert die ursprüngliche Hintergrundfarbe.
private Color bgColor;
private JButton jbRot, jbGelb, jbGruen, jbNeutral, jbStop;
private JPanel jpButtons;
Hier beginnt der Konstruktor:
public JRotGelbGruen(String title) {
Den Konstruktor der Ober-Klasse aufrufen:
super(title);
Den ContentPane des JFrame JRotGelbGruen in der Instanz cp speichern:
Container cp = getContentPane();
Eine Instanz jpButtons von JPanel erzeugen, das die Schaltflächen enthält. Die Anordnung der
Schaltflächen wird durch ein FlowLayout geregelt.
Dann die Hintergrundfarbe sichern.
jpButtons = new JPanel();
jpButtons.setLayout(new FlowLayout());
bgColor = jpButtons.getBackground();
Jetzt müssen die Schaltflächen instanziiert und dem JPanel jpButtons hinzugefügt werden. Außerdem muss jeder Schaltfläche beim ActionListener angemeldet werden. Das geschieht mit der
Methode addActionListener. Als Eingabe-Wert erwartet addActionListener einen
ActionListener. In diesem Fall ist das die Klasse JRotGelbGruen selbst, was durch das Schlüsselwort this ausgedrückt wird, da JRotGelbGruen ja das Interface ActionListener implementiert.
jbRot = new JButton("rot");
jbRot.addActionListener(this);
jpButtons.add(jbRot);
jbGelb = new JButton("gelb");
jbGelb.addActionListener(this);
jpButtons.add(jbGelb);
jbGruen = new JButton("grün");
jbGruen.addActionListener(this);
jpButtons.add(jbGruen);
jbNeutral = new JButton("neutral");
jbNeutral.addActionListener(this);
jpButtons.add(jbNeutral);
jbStop = new JButton("Ende");
jbStop.addActionListener(this);
jpButtons.add(jbStop);
Das JPanel jpButtons zum ContentPane des JFrame hinzufügen:
cp.add(jpButtons);
Die übliche Routine zum ordentlichen Beenden des Programms:
addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent event) {
System.exit(0);
}
}
);
Größe festlegen und sicherstellen, dass das Fenster auch angezeigt wird:
setSize(400, 200);
setVisible(true);
}
copyleft:munz
28
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Hier beginnt die eigentliche Arbeit. Jeder Schaltfläche muss eine entsprechende Aktion zugeordnet
werden. Das geschieht folgendermaßen. Mit getSource wird die Ereignisquelle ermittelt. Dann erfolgt je nach Ereignisquelle eine bestimmte Reaktion wie z.B. das Ändern der Hintergundfarbe.
public void actionPerformed(ActionEvent e) {
Object obj = e.getSource();
}
if
if
if
if
if
(obj
(obj
(obj
(obj
(obj
==
==
==
==
==
jbRot) jpButtons.setBackground(Color.red);
jbGelb) jpButtons.setBackground(Color.yellow);
jbGruen) jpButtons.setBackground(Color.green);
jbNeutral) jpButtons.setBackground(bgColor);
jbStop) System.exit(0);
Die Methode main hat wie üblich nicht viel zu tun …
public static void main(String[] args) {
new JRotGelbGruen("Rot - Gelb - Grün");
}
}
Das Fenster nach Anklicken der Schaltfläche „gelb“:
2.13.2 Auf Texteingaben reagieren
Es soll ein Programm entwickelt werden, das zwei Textfelder enthält, eines für Euro-Werte und eines
für Dollar-Werte. Bei Änderung des Dollar-Wertes soll der Euro-Wert entsprechend geändert werden
und umgekehrt. Die Eingabe wird dabei mit Drücken der Eingabetaste abgeschlossen.
Zunächst der Programm-Code mit Erläuterungen:
Die üblichen Pakete importieren:
import java.text.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
Neu ist das Paket java.text. Es enthält Werkzeuge zur formatierten Textausgabe. Die Zahlenwerte
sollen auf zwei Dezimalen gerundet ausgegeben werden. Das Dezimaltrennzeichen ist das Komma,
das Tausender-Trennzeichen der Punkt.
Zunächst die Klasse JEuroDollarRechner von JFrame ableiten und das Interface
ActionListener hinzufügen:
public class JEuroDollarRechner extends JFrame
implements ActionListener {
Die Label und Textfelder deklarieren:
private JLabel jlblEuro, jlblDollar, jlblKurs, jlblTitel;
private JPanel jpNorth, jpCenter, jpSouth;
private JTextField jtfEuro, jtfDollar;
Einige notwendige Konstanten und Variablen deklarieren und initialisieren:
private final static double KURS = 1.06886; // Kurs am 6.5.03
private double wertEuro = 1.0, wertDollar = KURS;
copyleft:munz
29
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Eine Instanz von DecimalFormat zur formatierten Ausgabe deklarieren:;
private DecimalFormat decF;
Hier beginnt der Konstruktor:
public JEuroDollarRechner(String title) {
super(title);
Das Format zur Dezimalausgabe festlegen:
decF = new DecimalFormat("#,##0.00");
Das ContentPane von JEuroDollarRechner ermitteln und ein BorderLayout festlegen:
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
Der Titel:
jpNorth = new JPanel();
jlblTitel = new JLabel("Euro-Dollar-Rechner");
jpNorth.add(jlblTitel);
cp.add(jpNorth, BorderLayout.NORTH);
Das jpCenter-Panel enthält die Textfelder mit den zugehörigen Labels. Zum Anordnen eignet sich
eine 2×2-Matrix, realisiert durch ein GridLayout(2, 2):
jpCenter = new JPanel();
jpCenter.setLayout(new GridLayout(2, 2));
jlblEuro = new JLabel("Euro: ");
jpCenter.add(jlblEuro);
Die Textfelder mit Startwerten füllen:
jtfEuro = new JTextField(decF.format(wertEuro), 12);
jtfEuro.setHorizontalAlignment(JTextField.RIGHT);
jpCenter.add(jtfEuro);
Damit das Textfeld auf die Eingabe reagieren kann, muss ihm ein ActionListener hinzugefügt
werden:
jtfEuro.addActionListener(this);
jlblDollar = new JLabel("Dollar: ");
jpCenter.add(jlblDollar);
jtfDollar = new JTextField(decF.format(wertDollar), 12);
jtfDollar.setHorizontalAlignment(JTextField.RIGHT);
jpCenter.add(jtfDollar);
Ebenfalls einen ActionListener hinzufügen:
jtfDollar.addActionListener(this);
cp.add(jpCenter, BorderLayout.CENTER);
jpSouth = new JPanel();
jlblKurs = new JLabel("Kurs = " + Double.toString(KURS));
jpSouth.add(jlblKurs);
cp.add(jpSouth, BorderLayout.SOUTH);
Wie üblich zum Beenden:
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit( 0 );
}
});
Packen und anzeigen:
pack();
setVisible(true);
}
copyleft:munz
30
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Hier kommt die Hauptarbeit:
public void actionPerformed(ActionEvent actionEvent) {
Object obj = actionEvent.getSource();
if (obj == jtfEuro) {
Die Eingabe könnte fehlerhaft sein. Deshalb muss sie ggf. abgefangen werden:
try {
Das Textfeld auslesen, in ein Dezimal-Format umwandeln und als double-Wert speichern:
wertEuro =
decF.parse(jtfEuro.getText()).doubleValue();
jtfEuro.setText(decF.format(wertEuro));
wertDollar = wertEuro*KURS;
jtfDollar.setText(decF.format(wertDollar));
}
Falls ein Eingabefehler aufgetreten ist, das Textfeld wieder mit dem ursprünglichen Wert füllen:
catch (ParseException e) {
jtfEuro.setText(decF.format(wertEuro));
}
}
Genauso mit den Dollar-Feld:
if (obj == jtfDollar) {
try {
wertDollar =
decF.parse(jtfDollar.getText()).doubleValue();
jtfDollar.setText(decF.format(wertDollar));
wertEuro = wertDollar/KURS;
jtfEuro.setText(decF.format(wertEuro));
}
catch (ParseException e) {
jtfDollar.setText(decF.format(wertDollar));
}
}
}
Die Methode main erstellt die Instanz von JEuroDollarRechner …
public static void main(String[] args) {
new JEuroDollarRechner("Euro-Dollar-Rechner");
}
}
2.13.3 Die gleichzeitige Verwendung mehrerer EventListeners
Der Euro-Dollar-Rechner soll so erweitert werden, dass bei Aktivierung eines Textfeldes automatisch
der gesamte Text markiert wird.
Im Folgenden sind nur die Änderungen aufgeführt.
Zum ActionListener kommt nun ein FocusListener hinzu:
public class JEuroDollarRechner extends JFrame
implements ActionListener,
FocusListener {
copyleft:munz
31
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Da die Textfelder auf Eingabe und auf Veränderung reagieren sollen, müssen sie sich sowohl beim
ActionListener als auch beim FocusListener anmelden:
jtfEuro.addActionListener(this);
jtfEuro.addFocusListener(this);
…
jtfDollar.addActionListener(this);
jtfDollar.addFocusListener(this);
Für den FocusListener müssen zwei Methoden implementiert werden:
public void focusGained(FocusEvent focusEvent) {
JTextField jtf = (JTextField) focusEvent.getSource();
jtf.selectAll();
}
und
public void focusLost(FocusEvent focusEvent) {
JTextField jtf = (JTextField) focusEvent.getSource();
jtf.setText(decF.format(
(jtf == jtfEuro ? wertEuro : wertDollar)));
jtf.setCaretPosition(0);
}
2.13.4 Ereignisse bei sehr vielen GUI-Elementen.
Wenn eine Anwendung sehr viele Elemente besitzt, die Ereignisse auslösen können, sind die oben
vorgestellten Verfahren sehr aufwendig zu implementieren. Hier verfährt man besser wie folgt:
Man fasst GUI-Objekte mit ähnlichen Verhaltensweisen zusammen, indem man für diese eine eigene
Klasse als Unterklasse von z.B. JButton erstellt und für diese Klasse einen benötigten
EventListener implementiert. Diese Klasse kann als anonyme Klasse, als innere Klasse oder als
externe Klasse realisiert sein.
Das Beispielprogramm hierzu erstellt ein Feld von n×n Schaltflächen. Wenn auf eine der Schaltflächen
gedrückt wird, soll in einem Label die Zeilen- und Spaltennummer der betreffenden Schaltfläche erscheinen.
Die üblichen Pakete importieren:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
Die Container-Klasse erstellen:
public class ButtonFeld extends JFrame {
private static final int ANZ = 5;
Ein n×n-Array für die Schaltflächen deklarieren, die Instanzen der Klasse ActionButton sind:
private ActionButton[][] buttons;
Weitere GUI-Objekte:
private JPanel buttonPanel;
private JLabel label;
Der Konstruktor:
public ButtonFeld (String title) {
super(title);
addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent evt) {
System.exit(0);}}
);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
copyleft:munz
32
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Das Label für die Anzeige initialisieren:
label = new JLabel(
"kein Button gedrückt", SwingConstants.CENTER
);
cp.add(label, BorderLayout.NORTH);
buttonPanel = new JPanel();
cp.add(buttonPanel, BorderLayout.CENTER);
buttonPanel.setLayout(new GridLayout(ANZ, ANZ));
Das n×n-Feld initialisieren:
buttons = new ActionButton[ANZ][ANZ];
for (int i = 0; i < ANZ; i++)
for (int j = 0; j < ANZ; j++) {
buttons[i][j] = new ActionButton(i+1, j+1);
buttonPanel.add(buttons[i][j]);
}
Für die Anzeige:
}
pack();
setVisible(true);
Die Methode main:
public static void main (String[] args) {
new ButtonFeld("ButtonFeld");
}
Für die Schaltflächen eine innere Klasse erstellen. Diese ist von JButton abgeleitet und implementiert einen ActionListener:
private class ActionButton extends JButton
implements ActionListener {
private int zeile, spalte;
Der Konstruktor der inneren Klasse:
ActionButton(int i, int j) {
zeile = i; spalte = j;
setText("[" + i + ", " + j + "]");
addActionListener(this);
}
Die Methode actionPerformed muss implementiert werden:
public void actionPerformed(ActionEvent e) {
label.setText(
"Button in " + zeile + ". Zeile, " + spalte +
". Spalte gedrückt"
);
} // Ende von 'actionPerformed'
} // Ende der inneren Klasse 'ActionButton'
} // Ende der Hauptklasse 'ButtonFeld'
Bei diesem Beispielprogramm hat jede Schaltfläche ihren eigenen ActionListener. Das hat den
Vorteil, dass jede Schaltfläche selbst „weiß“ ob sie gedrückt wurde und wie sie dann zu reagieren hat.
copyleft:munz
33
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
2.13.5 Benutzen von Adapter-Klassen
In den obigen Programmen wurde die Ereignissteuerung durch Implementieren spezieller Interfaces
realisiert. Dabei müssen alle Methoden des Interfaces implementiert werden, selbst wenn eine Methode überhaupt nicht benötigt wird. Der Programmcode enthält dann viele Methodendeklarationen
mit leeren Methodenrümpfen. Das lässt sich durch die Verwendung von Adapter-Klassen vermeiden.
Java stellt für jedes Interface, dass mehr als eine Methodendeklaration enthält, eine entsprechende
Adapter-Klasse zur Verfügung. Eine Adapter-Klasse ist somit eine Implementierung eines Interfaces,
wobei zunächst jede Methode in der Adapter-Klasse mit einem leeren Methodenrumpf implementiert
wird. Bei der Programmerstellung leitet man die Ereignissteuerung von der benötigten Adapter-Klasse
ab und braucht nur die benötigten Methoden zu überschreiben. Wenn der Code in der benötigten
Methode nur aus wenigen Zeilen besteht, kann man seine eigene Klasse sogar als anonyme Klasse
schreiben. Anonym bedeutet, dass weder für die abgeleitete Klasse noch deren Instanzen Bezeichner
vergeben werden. Beim Compilieren wird dann eine Datei erzeugt, die den gleichen Dateinamen wie
die Hauptklasse erhält, gefolgt von einem $-Zeichen mir anschließender fortlaufender Nummer, wie
z.B. JGrafik$1.class.
Diese Verfahren wurde in allen bisherigen Beispielprogrammen zum Schließen und Beenden des
Programms angewandt. Dem Programmfenster, meist von JFrame abeleitet, wird mit der Methode
addWindowListener ein WindowListener hinzugefügt. Dieser wird als anonyme Klasse von
WindowAdapter abgeleitet und gleichzeitig mit der Deklaration der Methode als Parameter übergeben. Von den vielen Methoden in WindowListener wird hier nur windowClosing benötigt. Da hier
eine Adapterklasse verwendet wird, muss nur eine Methode überschrieben werden.
addWindowListener( //Methodenaufruf
new WindowAdapter() { //Instanz der anonymen Klasse erzeugen
public void windowClosing (WindowEvent evt) { //überschreiben
System.exit(0); //nur eine Anweisung
}
}
);
Im folgenden Beispielprogramm soll in einem Fenster mit der Maus gezeichnet werden können. Dazu
muss auf Ereignisse reagiert werden können, die durch Klicken oder Ziehen mit der Maus ausgelöst
werden. Maus-Klicks werden durch das Interface MouseListener behandelt, Ereignisse, die durch
das Ziehen mit der Maus verursacht werden, durch das Interface MouseMotionListener.
Das Interface MouseListener enthält die Methoden:
mouseClicked(MouseEvent e)
Wird aufgerufen, wenn auf ein Element mit der Maus geklickt (gedrückt und losgelassen) wurde.
mouseEntered(MouseEvent e)
Wird aufgerufen, wenn die Maus auf ein Element geführt wird.
mouseExited(MouseEvent e)
Wird aufgerufen, wenn die Maus aus einem Element heraus geführt wird.
mousePressed(MouseEvent e)
Wird aufgerufen, wenn man die Maustaste auf ein Element drückt.
mouseReleased(MouseEvent e)
Wird aufgerufen, wenn man die Maustaste auf einem Element loslässt.
Das Interface MouseMotionListener enthält die Methoden:
mouseDragged(MouseEvent e)
Wird aufgerufen, wenn eine Maustaste gedrückt ist und die Maus dabei bewegt wird.
mouseMoved(MouseEvent e)
Wird aufgerufen, wenn der Maus-Zeiger bewegt wird und keine Maustaste gedrückt ist.
copyleft:munz
34
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Für dieses Beispielprogramm werden die Methoden mousePressed und mouseDragged benötigt.
Deshalb wird die Ereignissteuerung mit Hilfe der zugehörigen Adapter-Klassen MouseAdapter und
MouseMotionAdapter realisiert.
Das Programm JGrafik erhält eine zusätzliche Schaltfläche um die Zeichnung zu löschen. Als Zeichenfläche wird eine erweiterte Version von GrafikPanel verwendet, dessen Quelltext erläutert
werden soll:
Zunächst die benötigten Pakete importieren:
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
Die Klasse deklarieren:
public class GrafikPanel extends JPanel {
private int breite, hoehe;
private int x, y;
private BufferedImage image;
private Graphics imgG;
Der Konstruktor:
public GrafikPanel(int breite, int hoehe) {
this.breite = breite;
this.hoehe = hoehe;
setPreferredSize(new Dimension(breite, hoehe));
setBackground(Color.WHITE);
erstelleGrafik();
MouseListener und MousMotionListener hinzufügen:
addMouseListener(new MyMouseAdapter());
addMouseMotionListener(new MyMouseMotionAdapter());
}
Ein Image und dessen Grafikkontext bereitstellen:
public void erstelleGrafik() {
image = new BufferedImage(
breite, hoehe, BufferedImage.TYPE_INT_RGB
);
imgG = image.createGraphics();
loescheGrafik();
}
copyleft:munz
35
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Eine Methode zum Löschen der Grafik bereitstellen. Diese Methode wird vom ActionListener der
Schaltfläche zum Löschen der Zeichnung aufgerufen:
public void loescheGrafik() {
imgG.setColor(getBackground());
imgG.fillRect(0, 0, breite, hoehe);
imgG.setColor(Color.BLACK);
repaint();
}
Die Methode paintComponent stellt das Image auf der Zeichenfläche dar:
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
Eine innere Klasse bereitstellen, die von MouseAdapter abgeleitet ist:
private class MyMouseAdapter extends MouseAdapter {
public void mousePressed(MouseEvent e) {
Nur reagieren, wenn die linke Maustaste gedrückt wurde:
if (SwingUtilities.isLeftMouseButton(e)) {
Mausposition beim Drücken der linken Maustaste merken:
x = e.getX(); y = e.getY();
}
} // Ende von 'mousePressed'
} // Ende von 'MyMousAdapter'
Eine innere Klasse bereitstellen, die von MouseMotionAdapter abgeleitet ist:
private class MyMouseMotionAdapter extends MouseMotionAdapter {
public void mouseDragged(MouseEvent e) {
Ebenfalls nur reagieren, wenn die linke Maustaste gedrückt wurde:
if (SwingUtilities.isLeftMouseButton(e)) {
int x_neu = e.getX();
int y_neu = e.getY();
Line vom gespeicherten Punkt zum aktuellen Punkt zeichnen:
imgG.drawLine(x, y, x_neu, y_neu);
x = x_neu; y = y_neu;
Zeichnung wurde verändert, Grafik neu zeichnen:
repaint();
}
} // Ende von 'mouseDragged'
} // Ende von 'MyMouseMotionAdapter'
} // Ende von 'GrafikPanel'
2.13.6 Getrennte GUI-Klassen und Klassen zur Ereignisbehandlung
Für große Programme wird empfohlen, aus Gründen der Übersichtlichkeit und der Programmsicherheit GUI-Klassen und Klassen zur Ereignisbehandlung zu trennen. Wenn die Ereignissteuerung Elemente aus der GUI-Klasse ansprechen soll, muss man sicherstellen, dass diese Elemente von der Ereignissteuerung auch angesprochen werden können. Das erreicht man z.B. dadurch, dass man die
aktuelle Instanz der GUI-Klasse dem Konstruktor der Klasse zur Ereignissteuerung übergibt. Eine
ausführliche Diskussion dieses Verfahrens würde den Rahmen dieser Handreichung sprengen. Es sei
dafür auf die umfangreicheren Beispielprogramme auf der beiliegenden CD-ROM verwiesen.
copyleft:munz
36
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
2.14 Die GUI-Klassen zum Miniprojekt „Figuren“
Bisher wurden zum Projekt „Figuren“ nur die zugehörigen Fachklassen vorgestellt. Die Figuren sollen
aber auch konkret auf dem Bildschirm dargestellt werden. Hierzu benötigen wir weitere Klassen,
GrafikPanel und FigurFrame im Paket figur_gui.
JDemoFigur als Unterklasse von FigurFrame erzeugt dann in seiner Methode main eine Instanz
von sich, bzw. von FigurFrame. Dabei wird das Programmfenster auf dem Bildschirm dargestellt.
Genauso könnte man FigurFrame in einem JApplet darstellen. Hier muss man nur aufpassen,
dass der WindowListener, der für das ordentliche Schließen und Beenden des Programms sorgt,
entfernt wird, da Applets keine Programme beenden dürfen.
2.14.1 Die Quelltexte
Das Zeichenfenster:
package figur_gui;
import figur.*;
import java.awt.*;
import javax.swing.*;
public class GrafikPanel extends JPanel{
private int width, height;
private Figur dieFigur;
Der Konstruktor legt Breite und Höhe fest und erzeugt einen Rahmen.
public GrafikPanel(int width, int height){
super();
this.width = width;
this.height = height;
setPreferredSize(new Dimension (width, height));
setBorder(BorderFactory.createLineBorder(Color.darkGray, 2));
setBackground(Color.WHITE);
}
Falls die Figur geändert wurde, muss sie der Zeichenfläche neu übergeben werden.
public void setFigur(Figur dieFigur) {
this.dieFigur = dieFigur;
}
Zum Darstellen von Zeichnungen muss paintComponent aus JComponent überschrieben werden.
In dieser Methode wird zeigeDich von Figur aufgerufen bzw. der Hintergrund gelöscht.
public void paintComponent(Graphics g){
super.paintComponent(g);
// Nur wenn ein Test instanziiert ist, soll gezeichnet werden.
if (dieFigur != null)
dieFigur.zeigeDich(g);
else {
g.setColor(getBackground());
g.drawRect(0, 0, getSize().width, getSize().height);
}
}
}
copyleft:munz
37
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Der zugehörige JFrame ist das eigentliche GUI-Fenster. Zunächst werden alle benötigten Pakete importiert, insbesondere figur. Dann werden Zeichenfläche, Schaltflächen, Textfelder und Auswahlfelder erzeugt.
package figur_gui;
import figur.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
Da das Programm auf Benutzeraktionen reagieren soll, muss das Interface ActionListener
implementiert werden.
public class FigurFrame extends JFrame
implements ActionListener {
Lokale Konstanten und Attribute festlegen:
// Konstanten
private static final int BREITE = 200, HOEHE = 200;
private static int xMitte, yMitte, b = 0;
//Attribute
private Box boxFarben, boxFiguren,
boxBreite, boxEckenZahl, boxWest, boxEast;
private JButton bnZeichne;
private JLabel lblTitel, lblCopyRight, lblBreite, lblEckenZahl;
private JTextField tfBreite, tfEckenZahl;
private ButtonGroup bgFarben, bgFiguren;
private JRadioButton rbRot, rbGruen, rbBlau, rbSchwarz,
rbKreis, rbQuadrat, rbVielEck;
private GrafikPanel zeichenFlaeche;
private Figur figur;
private Color farbe = Color.BLACK;
private int breite = 100, eckenZahl = 6;
Der Konstruktor:
public FigurFrame(String titel) {
super(titel);
Zum ordentlichen Beenden des Programms einen WindowListener hinzufügen:
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});
Anfangswerte festlegen:
xMitte = BREITE/2; yMitte = HOEHE/2;
b = Math.min(BREITE, HOEHE)*9/10;
Für den JFrame ein BorderLayout festlegen und ein JLabel als Titel erstellen:
JPanel cp = new JPanel(new BorderLayout(15, 5));
setContentPane(cp);
lblTitel = new JLabel("Figuren", SwingConstants.CENTER);
lblTitel.setFont(new Font("SansSerif", Font.BOLD, 30));
cp.add(lblTitel, BorderLayout.NORTH);
In den West-Bereich des BorderLayouts wird eine Box mit horizontaler Ausrichtung eingefügt.
Diese Box besteht wieder aus zwei weiteren Boxen mit vertikaler Ausrichtung, die jeweils die
JRadioButtons für Farbauswahl und Figurenwahl enthalten.
boxWest = new Box(BoxLayout.X_AXIS);
cp.add(boxWest, BorderLayout.WEST);
copyleft:munz
38
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Die Box für die Farbenauswahl:
boxFarben = new Box(BoxLayout.Y_AXIS);
bgFarben = new ButtonGroup();
rbSchwarz = new JRadioButton("schwarz", true);
rbGruen = new JRadioButton("grün", false);
rbRot = new JRadioButton("rot", false);
rbBlau = new JRadioButton("blau", false);
bgFarben.add(rbSchwarz);
bgFarben.add(rbGruen);
bgFarben.add(rbRot);
bgFarben.add(rbBlau);
boxFarben.add(rbSchwarz);
boxFarben.add(rbGruen);
boxFarben.add(rbRot);
boxFarben.add(rbBlau);
boxFarben.add(Box.createVerticalGlue());
boxWest.add(boxFarben);
Die Box für die Figurenauswahl:
boxFiguren = new Box(BoxLayout.Y_AXIS);
bgFiguren = new ButtonGroup();
rbKreis = new JRadioButton("Kreis", true);
rbKreis.addActionListener(this);
rbQuadrat = new JRadioButton("Quadrat", false);
rbQuadrat.addActionListener(this);
rbVielEck = new JRadioButton("Vieleck", false);
rbVielEck.addActionListener(this);
bgFiguren.add(rbKreis);
bgFiguren.add(rbQuadrat);
bgFiguren.add(rbVielEck);
boxFiguren.add(rbKreis);
boxFiguren.add(rbQuadrat);
boxFiguren.add(rbVielEck);
boxFiguren.add(Box.createVerticalGlue());
boxWest.add(boxFiguren);
Eine Box mit vertikaler Ausrichtung für den Ost-Bereich des BorderLayouts festlegen. Sie enthält
das Textfeld zur Größeneingabe, eine weitere Box zur Eingabe der Eckenzahl und die Schaltfläche
„Zeichne Figur!“.
boxEast = new Box(BoxLayout.Y_AXIS);
cp.add(boxEast, BorderLayout.EAST);
Dimension groesseLb = new Dimension(100, 24);
Dimension groesseTf = new Dimension(40, 24);
boxBreite = new Box(BoxLayout.X_AXIS);
lblBreite = new JLabel("Durchmesser: ", JLabel.RIGHT);
groesseAnpassen(lblBreite, groesseLb);
boxBreite.add(lblBreite);
tfBreite = new JTextField(Integer.toString(breite));
groesseAnpassen(tfBreite, groesseTf);
tfBreite.setHorizontalAlignment(JTextField.RIGHT);
boxBreite.add(tfBreite);
boxEast.add(boxBreite);
copyleft:munz
39
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Box mit horizontaler Ausrichtung für das Textfeld zur Eingabe der Eckenzahl, dem ein Label mit zugehörigem Text zugeordnet ist.
boxEckenZahl = new Box(BoxLayout.X_AXIS);
lblEckenZahl = new JLabel("Anzahl Ecken: ", JLabel.RIGHT);
groesseAnpassen(lblEckenZahl, groesseLb);
boxEckenZahl.add(lblEckenZahl);
tfEckenZahl = new JTextField(Integer.toString(eckenZahl));
groesseAnpassen(tfEckenZahl, groesseTf);
tfEckenZahl.setHorizontalAlignment(JTextField.RIGHT);
tfEckenZahl.setEnabled(false);
boxEckenZahl.add(tfEckenZahl);
boxEast.add(boxEckenZahl);
bnZeichne = new JButton("Zeichne Figur!");
bnZeichne.setAlignmentX(0.5f);
bnZeichne.addActionListener(this);
boxEast.add(bnZeichne);
Im Zentrum des BorderLayouts befindet sich die Zeichenfläche zum Darstellen der Figuren.
zeichenFlaeche = new GrafikPanel(BREITE, HOEHE);
zeichenFlaeche.setFigur(figur);
cp.add(zeichenFlaeche, BorderLayout.CENTER);
Ein kleines Label im Süd-Bereich der BorderLayouts.
lblCopyRight = new JLabel(
"Java II - Pohlig/Taulien, \u00a9 2002-2003",
SwingConstants.RIGHT
);
lblCopyRight.setFont(new Font("SansSerif", Font.PLAIN, 11));
cp.add(lblCopyRight, BorderLayout.SOUTH);
Das Erscheinungsbild des Programm-Fensters festlegen:
pack();
setResizable(false);
// Auf dem Bildschirm zentrieren und sichtbar machen
Dimension screenSize =
Toolkit.getDefaultToolkit().getScreenSize();
setLocation((screenSize.width - this.getSize().width)/2,
(screenSize.height - this.getSize().height)/2
);
setVisible(true);
}
Immer wiederkehrende Arbeiten lagert man am besten in private Methoden aus:
private void groesseAnpassen(JComponent comp, Dimension groesse) {
comp.setPreferredSize(groesse);
comp.setMinimumSize(groesse);
comp.setMaximumSize(groesse);
}
Hier beginnt die Ereignissteuerung. Ereignisse können die JRadioButtons oder die Schaltfläche
„Zeichne Figur!“ auslösen.
public void actionPerformed(ActionEvent e) {
// Welches Objekt hat das Ereignis ausgelöst?
Object obj = e.getSource();
copyleft:munz
40
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Fall das Ereignis von einem JRadioButton zur Figurenwahl ausgelöst wurde, wird das Textfeld zur
Eingabe der Eckenzahl aktiviert oder deaktiviert, jenachdem ob ein Vieleck gezeichnet warden soll
oder nicht.
if (obj == rbKreis || obj == rbQuadrat){
tfEckenZahl.setEnabled(false);
}
if (obj == rbVielEck){
tfEckenZahl.setEnabled(true);
}
Die Schaltfläche “Zeichne Figur!” wurde gedrückt. Dann wird zuerst die Figurenbreite aus dem zugehörigen Textfeld ausgelesen, ggf. wird für ein Vieleck die Eckenzahl ermittelt. Dann wird die Figurenfarbe bestimmt. Anschließend wird eine neue Figur (Kreis, Quadrat oder VielEck) erzeugt und der
Klassenvariablen figur zugewiesen.
if (obj == bnZeichne) {
Figurenbreite auslesen, ggf. Fehler abfangen:
try {
breite = Integer.parseInt(tfBreite.getText());
}
catch (NumberFormatException except) {
tfBreite.setText(Integer.toString(breite));
}
Eckenzahl auslesen, ggf. Fehler abfangen:
try {
eckenZahl = Integer.parseInt(tfEckenZahl.getText());
}
catch (NumberFormatException except) {
tfEckenZahl.setText(Integer.toString(eckenZahl));
}
Figurenfarbe bestimmen.
if
if
if
if
(rbSchwarz.isSelected()) farbe = Color.BLACK;
(rbRot.isSelected()) farbe = Color.red;
(rbGruen.isSelected()) farbe = Color.green;
(rbBlau.isSelected()) farbe = Color.blue;
Kreis war ausgewählt:
if (rbKreis.isSelected()) {
figur = new Kreis(xMitte, yMitte, breite/2, farbe);
tfBreite.setText(
Integer.toString(((Kreis)figur).getRadius()*2)
);
}
Quadrat war ausgewählt:
if (rbQuadrat.isSelected()) {
figur = new Quadrat(xMitte, yMitte, breite, farbe);
tfBreite.setText(
Integer.toString(((Quadrat)figur).getSeite())
);
}
copyleft:munz
41
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Vieleck war ausgewählt:
if (rbVielEck.isSelected()) {
figur = new VielEck(
xMitte, yMitte, breite/2, eckenZahl, farbe
);
tfBreite.setText(
Integer.toString(((VielEck)figur).getRadius()*2)
);
tfEckenZahl.setText(
Integer.toString(((VielEck)figur).getEckenZahl())
);
}
Der Zeichenfläche die neue Figur übergeben und alles neu zeichnen:
zeichenFlaeche.setFigur(figur);
}
repaint();
}
}
Das „Hauptprogramm“:
import figur_gui.*;
public class JDemoFigur extends FigurFrame {
public JDemoFigur(String titel) {
super(titel);
}
main erzeugt eine Instanz von JDemoFigur und damit von FigurFrame. Die zugehörigen Bilder
sind im Kapitel zu OOP zu finden.
public static void main(String args[]){
new JDemoFigur("Figuren");
}
}
Auch ein Applet ist möglich, in Swing ist es ein JApplet:
import figur_gui.*;
import javax.swing.*;
import java.awt.event.*;
public class JDemoFigurApplet extends JApplet {
In einem Applet gibt es kein main, die Instanz von FigurFrame erzeugt man direkt in init(). Da
ein Applet keine Rechte hat, ein Programm zu beenden muss man den zugehörigen
WindowListener entfernen. Mit javax.swing.JComponent.getWindowListeners() kann
man sich alle WindowListener besorgen, und diese dann der Reihe nach entfernen (hier gibt es
wohl nur einen).
public void init(){
FigurFrame frame = new FigurFrame("Figuren");
WindowListener[] winListener = frame.getWindowListeners();
for (int i = 0; i < winListener.length; i++)
frame.removeWindowListener(winListener[i]);
}
}
copyleft:munz
42
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
2.15 Einige GUI-Beispielprogramme
2.15.1 Fonts darstellen
Das Programm stellt eine Zeichenkette mit allen ihren Metriken in einem Grafikfenster dar. Die Zeichenkette kann in einem Textfeld (JTextField) eingegeben werden. Schriftart und Schriftstil wählt
man jeweils mit Hilfe eines DropDownListenfelds (JComboBox). Die Schriftgröße kann entweder mit
Hilfe eines Drehfelds(JSpinner) oder eines Schiebereglers (JSlider) eingestellt werden. Eine
Schaltfläche (JButton) aktualisiert ggf. die Ausgabe.
Die Abbildung zeigt das gewählte Layout:
Der folgende Quelltext des Programms zeigt nur diejenigen Ausschnitte, die gegenüber den vorigen
Programmbeispielen Neuerungen aufweisen.
Zunächst die Grafikpakete importieren. Neu ist das Paket swing.event. Hier sind Methoden enthalten, mit denen man auf Ereignisse von JSpinner und JSlider reagiert.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
JFonts erbt von JFrame und bindet die Schnittstellen ActionListener und ChangeListener
ein.
public class JFont extends JFrame
implements ActionListener, ChangeListener {
Schriftname und Schriftstil werden in Feldern gespeichert:
private String fontFamilyNames[];
private String fontStyleNames[] =
{ "normal", "fett", "kursiv", "fett-kursiv" };
private int fontStyles[] =
{Font.PLAIN, Font.BOLD, Font.ITALIC, Font.BOLD | Font.ITALIC};
Einige Konstanten festlegen, die bei Programmstart benötigt werden:
private static final int MIN_FONT_SIZE = 25, MAX_FONT_SIZE = 200;
private String text = "Font-Beispiel";
private int displayFontSize = 72;
// logischer Font für den Anfang
// Auswahl aus: SansSerif, Serif, Monospaced, Dialog, InputDialog
private String displayFontName = "Serif";
private int displayFontStyle = fontStyles[0];
private String displayFontStyleName = fontStyleNames[0];
copyleft:munz
43
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Der darzustellende Font:
private Font font;
Ein JTextField für die Text-Eingabe:
private JTextField jtfFontText;
Ein JSpinner und ein ein JSlider für die Schriftgröße bereitstellen. Dabei ist jspModel ein
SpinnerNumberModel, das das Verhalten von JSpinner festlegt. Hier wollen wir, dass die Schriftgröße in ganzahligen Schritten verändert wird.
private JSpinner jspFontSize;
private SpinnerNumberModel jspModel;
private JSlider jslFontSize;
Je eine JComboBox für Schriftname und Schriftstil anlegen:
private JComboBox jcbFontName, jcbFontStyle;
Eine Schaltfläche um die Anzeige ggf. zu aktualisieren:
private JButton jbOK;
JFonts erhält ein BorderLayout. Für den Nord- und Ost-Bereich richten wir je eine Box, für den
Süd-Bereich ein JPanel ein. Der Zentral-Bereich ist das eigentliche Ausgabefenster:
private Box boxNorth, boxEast;
private JPanel jpSouth;
private JFontPanel jfPanel;
Hier beginnt der Konstruktor:
public JFont(String title) {
super(title);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
Die installierten Schriftarten laden und den Anfangs-Font festlegen:
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
fontFamilyNames = ge.getAvailableFontFamilyNames();
font = new Font(
displayFontName, displayFontStyle, displayFontSize
);
Die Aktionselemente erzeugen. Sie kommen in den Nord-Bereich des Grafikfensters. Dabei wird der
Beispiel-Text, wegen des Titel-Rahmens in eine separate Box gepackt:
boxNorth = new Box(BoxLayout.X_AXIS);
Box boxFontText = new Box(BoxLayout.X_AXIS);
boxFontText.setBorder(
BorderFactory.createTitledBorder("Beispiel-Text")
);
Ein Textfeld für den Eingabetext:
jtfFontText = new JTextField(text);
jtfFontText.addActionListener(this);
boxFontText.add(jtfFontText);
boxFontText.add(Box.createHorizontalGlue());
boxNorth.add(boxFontText);
Eine JComboBox mit Titel-Rahmen zur Font-Name-Auswahl:
jcbFontName = new JComboBox(fontFamilyNames);
jcbFontName.setBorder(
BorderFactory.createTitledBorder("Name")
);
jcbFontName.setSelectedItem(font.getName());
jcbFontName.setEditable(false);
jcbFontName.addActionListener(this);
boxNorth.add(jcbFontName);
copyleft:munz
44
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Eine JComboBox mit Titel-Rahmen zur Font-Stil-Auswahl:
jcbFontStyle = new JComboBox(fontStyleNames);
jcbFontStyle.setBorder(
BorderFactory.createTitledBorder("Stil"));
jcbFontStyle.setSelectedIndex(0);
jcbFontStyle.setEditable(false);
jcbFontStyle.addActionListener(this);
boxNorth.add(jcbFontStyle);
Ein JSpinner mit Titel-Rahmen für die Font-Größe:
jspModel = new SpinnerNumberModel(
displayFontSize, MIN_FONT_SIZE, MAX_FONT_SIZE, 1
);
jspFontSize = new JSpinner(jspModel);
jspFontSize.setBorder(
BorderFactory.createTitledBorder("Größe")
);
jspFontSize.addChangeListener(this);
boxNorth.add(jspFontSize);
Alles in die Nord-Box tun:
cp.add(boxNorth, BorderLayout.NORTH);
Die Ost-Box für den JSlider erzeugen:
boxEast = new Box(BoxLayout.Y_AXIS);
boxEast.setBorder(BorderFactory.createTitledBorder("Größe"));
Ein JSlider-Element mit Titel-Rahmen für die Schriftgröße:
jslFontSize = new JSlider(
JSlider.VERTICAL, MIN_FONT_SIZE,
MAX_FONT_SIZE, displayFontSize
);
jslFontSize.setBorder(
BorderFactory.createEmptyBorder(10, 10, 0, 0)
);
Aussehen des JSliders festlegen:
jslFontSize.setMajorTickSpacing(25);
jslFontSize.setMinorTickSpacing(5);
jslFontSize.setPaintTicks(true);
jslFontSize.setPaintLabels(true);
jslFontSize.setPaintTrack(true);
jslFontSize.setValue(displayFontSize);
jslFontSize.addChangeListener(this);
boxEast.add(jslFontSize);
boxEast.add(Box.createVerticalGlue());
cp.add(boxEast, BorderLayout.EAST);
Das Ausgabe-Fenster erhält zusätzliche Scroll-Balken:
jfPanel = new JFontPanel(font, text);
JScrollPane jfScrollPane = new JScrollPane(jfPanel);
cp.add(jfScrollPane, BorderLayout.CENTER);
Die Aktualisierungs-Schaltfläche:
jpSouth = new JPanel();
jbOK = new JButton("OK");
jbOK.addActionListener(this);
jpSouth.add(jbOK);
cp.add(jpSouth, BorderLayout.SOUTH);
copyleft:munz
45
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Standard-Methode um das Fenster „Windows-mäßig“ zu schließen:
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit( 0 );
}
});
… und alles anzeigen:
pack();
Dimension screenSize =
Toolkit.getDefaultToolkit().getScreenSize();
setLocation((screenSize.width - getSize().width)/2,
(screenSize.height - getSize().height)/2);
setVisible(true);
}
Die Methoden des ChangeListeners:
public void stateChanged(ChangeEvent event) {
Object obj = event.getSource();
Eine andere Font-Größe wurde mit dem JSlider eingestellt:
if (obj == jslFontSize) {
displayFontSize = jslFontSize.getValue();
jspFontSize.setValue(new Integer(displayFontSize));
}
Eine andere Font-Größe wurde mit dem JSpinner eingestellt:
if (obj == jspFontSize) {
displayFontSize = jspModel.getNumber().intValue();
jslFontSize.setValue(displayFontSize);
}
Einen neuen Font erstellen:
font = new Font(
displayFontName, displayFontStyle, displayFontSize
);
jfPanel.setFont(font);
jfPanel.updateFontMetrics();
Falls der darzustellende Text über die Frame-Grenzen hinaus ragt, müssen Scroll-Leisten gezeichnet
werden, bzw. entfernt werden, wenn der Text in das gesamte Fenster passt:
jfPanel.revalidate();
Ggf. alles neu zeichnen:
repaint();
} // Ende von stateChanged
Die Methoden des ActionListeners:
public void actionPerformed(ActionEvent event) {
Object obj = event.getSource();
OK wurde gedrückt:
if (obj == jbOK) {
text = jtfFontText.getText();
displayFontName = (String) jcbFontName.getSelectedItem();
int index = jcbFontStyle.getSelectedIndex();
displayFontStyle = fontStyles[index];
displayFontStyleName = fontStyleNames[index];
}
Der Text wurde verändert, die Änderung mit der Return-Taste bestätigt:
if (obj == jtfFontText) {
text = jtfFontText.getText();
}
copyleft:munz
46
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Es wurde ein anderer Font-Name ausgewählt:
if (obj == jcbFontName) {
displayFontName = (String) jcbFontName.getSelectedItem();
}
Es wurde ein anderer Font-Stil ausgewählt:
if (obj == jcbFontStyle) {
int index = jcbFontStyle.getSelectedIndex();
displayFontStyle = fontStyles[index];
displayFontStyleName = fontStyleNames[index];
}
Nach allen Aktionen einen neuen Font erzeugen:
font = new Font(
displayFontName, displayFontStyle, displayFontSize
);
jfPanel.setText(text);
jfPanel.setFont(font);
jfPanel.updateFontMetrics();
Falls der darzustellende Text über die Frame-Grenzen hinaus ragt, müssen Scroll-Leisten gezeichnet
werden, bzw. entfernt werden, wenn der Text in das gesamte Fenster passt:
jfPanel.revalidate();
Ggf. alles neu zeichnen:
repaint();
} // Ende von actionPerformed
main tut nicht viel:
public static void main(String[] args)
new JFont("Font-Beispiel");
}
}
copyleft:munz
47
{
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
2.15.2 JDraw, ein kleines Grafik-Programm
Es soll ein Grafik-Programm entwickelt werden, das verschiedene Elemente wie Linien, Rechtecke,
Rechtecke mit abgerundeten Ecken oder Ellipsen gefüllt oder ungefüllt darstellen kann. Beim Festhalten der Umschalttaste sollen die Linien nur im 0°-, 45°- und 90°-Winkel gezeichnet werden. Rechtecke und Ellipsen werden dann zu Quadraten bzw. Kreisen.
Dieses Programm könnte man beliebig erweitern; z.B. könnte man die erzeugten Grafiken in einer Datei abspeichern oder aus einer Datei laden. Es sollte möglich sein, einzelne Figuren nachträglich zu
markieren, dann zu verändern, zu verschieben oder zu löschen. Weitere Anregungen zur Erweiterung
liefern die bekannten Grafikprogramme.
Zur Darstellung der Figuren wurden die Methoden der Klasse Java2D verwendet, dabei wurden eine
hohe Auflösung und Antialiasing verwendet. Die Farbauswahl geschieht mit Hilfe der Swing-Klasse
ColorChooser.
Dieses Programmbeispiel zeigt deutlich die Vorteile einer konsequent umgesetzten objektorientierten
Programmierung.
Auch ohne diese möglichen Erweiterungen erscheint die gestellte Aufgabe hinreichend komplex.
Dennoch benötigt die Realisierung in Java nur wenige Seiten Quellcode.
Die Schaltflächen werden samt zugehörigen Aktionen in einer Klasse DrawMenuBar erzeugt, die von
JMenuBar abgeleitet ist. Eine JMenuBar ist Bestandteil eines JFrames und ist deshalb sehr leicht
hinzuzufügen. Um das Aussehen der Schaltflächen zu ändern, mussten für jeden möglichen Zustand
einer Schaltfläche (d.h. ausgewählt, nicht ausgewählt, Maus über der ausgewählten bzw. nicht ausgewählten Schaltfläche) jeweils mehrere Grafiken pro Schaltfläche im gif-Format erstellt werden.
Die Klasse JMenuBar die den JColorChooser zur Farbauswahl enthält, eine Klasse zur Auswahl
der Linienbreite (ComboLineWidth abgeleitet von JComboBox) und eine Klasse zum Beenden des
Programms (JDrawWindowCommand abgeleitet von WindowAdapter) wurden in das Paket drawgui
gepackt.
Um die verschiedenen Figuren darstellen zu können, wurde eine Klasse Figur erzeugt. Eine Figur
kennt im wesentlichen ihren Ort, das sind die Koordinaten der linken oberen Ecke, ihre Breite und ihre
Höhe. Weitere Eigenschaften sind Randfarbe und Füllfarbe. Zudem weiß jede Figur selbst, wie sie
gezeichnet wird. Da Figur aber noch nichts von Rechteck, Ellipse oder Linie weiß, können die Methoden drawBackground und drawForeground nur abstrakt ausgeführt werden. Sie bekommen
also den Modifizierer abstract vor den Methodenkopf vorangestellt, ohne den Methodenrumpf weiter auszuführen. Dies bleibt den von Figur abgeleiteten Klassen vorbehalten.
Alle Figuren-Klassen wurden im Paket figuren zusammengefasst.
copyleft:munz
48
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Die Klasse Figur:
package figuren;
import java.awt.*;
import java.awt.geom.*;
abstract public class Figur {
protected
protected
protected
protected
protected
protected
protected
Point p1, p2;
String name, fillName;
Shape shape = null;
Color lineColor = Color.BLACK;
Color fillColor = Color.WHITE;
float lineWidth = 1.0f;
boolean filled = false;
public Figur(Point p1, Point p2) {
this.p1 = p1;
setEndPunkt(p2);
}
abstract public void setEndPunkt(Point p2);
public void setLineColor(Color lineColor) {
this.lineColor = lineColor;
}
public void setFillColor(Color fillColor) {
this.fillColor = fillColor;
}
public void setLineWidth(float lineWidth) {
this.lineWidth = lineWidth;
}
public void setFilled(boolean filled) {
this.filled = filled;
}
public void drawFigure(Graphics2D g2D) {
if (p1 != null && p2 != null) {
if (filled) {
g2D.setColor(fillColor);
g2D.fill(shape);
}
g2D.setColor(lineColor);
g2D.setStroke(new BasicStroke(lineWidth));
g2D.draw(shape);
}
}
public String toString() {
return (filled ? fillName : name) + ": [" +
(p1 == null ? " " : p1.x + ", " + p1.y) + "], [" +
(p2 == null ? " " : p2.x + ", " + p2.y) + "]";
}
}
copyleft:munz
49
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Die Klasse Rechteck leitet sich von Figur ab:
package figuren;
import java.awt.*;
import java.awt.geom.*;
public class Rechteck extends Figur {
public Rechteck(Point p1, Point p2) {
super(p1, p2);
name = "Rechteck";
fillName = "Gefülltes Rechteck";
}
}
public void setEndPunkt(Point p2) {
if (p2 != null) {
this.p2 = p2;
shape = new Rectangle2D.Double(
(p2.x > p1.x ? p1.x : p2.x),
(p2.y > p1.y ? p1.y : p2.y),
Math.abs(p2.x - p1.x),
Math.abs(p2.y - p1.y));
}
}
Die Klasse Quadrat ist dann eine Unter-Klasse von Rechteck:
package figuren;
import java.awt.*;
public class Quadrat extends Rechteck {
public Quadrat(Point p1, Point p2) {
super(p1, p2);
name = "Quadrat";
fillName = "Gefülltes Quadrat";
}
}
public void setEndPunkt(Point p2) {
if (p2 != null) {
int d = Math.max(Math.abs(p2.x - p1.x),
Math.abs(p2.y - p1.y)
);
super.setEndPunkt(
new Point(p1.x + (p2.x > p1.x ? d : -d),
p1.y + (p2.y > p1.y ? d : -d)
)
);
}
}
Die weiteren Klassen RundesRechteck, RundesQuadrat, Ellipse, Kreis, Linie und
SonderLinie werden analog gebildet. Die in der Klasse Graphics enthaltenen Methoden
drawRect, drawEllipse usw. gehen immer davon aus, dass der Startpunkt links oben liegt. Nun
kann man eine Figur auch von rechts unten nach links oben aufziehen, das muss beim Zeichnen dann
berücksichtigt werden. Im übrigen werden Ort und Größe auch von Ellipsen, Kreisen und Linien durch
Ort und Größe eines gedachten umbeschriebenen Rechtecks bestimmt.
Die Hauptklasse JDraw, sie enthält die Klasse main, wird von JFrame abgeleitet und bekommt ein
BorderLayout. Der Nord-Bereich enthält die Schaltflächen zur Auswahl der Figuren, zum Füllen und
copyleft:munz
50
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
zum Neuzeichnen und zur Farbauswahl. Der eigentliche Zeichenbereich ist im Zentrum, der Süd-Bereich enthält lediglich eine Statuszeile.
PaintPanel muss auf Maus- und Tastatur-Ereignisse reagieren können. Das Interface
MouseListener enthält mehrere abstrakte Methoden, die alle überschrieben werden müssen, wenn
man dieses Interface implementieren will, selbst wenn der Methodenrumpf leer bleiben sollte. In diesem Fall erstellt man besser eine neue Klasse DrawPanelMouseListener, die von der AdapterKlasse MouseAdapter abgeleitet ist.
Für das Bewegen der Maus gibt es die Klasse DrawPanelMouseMotionListener ,von
MouseMotionAdapter abgeleitet, und für die Tastatursteuerung gibt es die Klasse
DrawPanelKeyListener, abgeleitet von KeyAdapter.
DrawPanelKeyListener, DrawPanelMouseListener und DrawPanelMouseMotionListener
sind als innere Klassen von DrawPanel realisiert. Das hat den Vorteil, dass ihre Methoden direkt auf
die Klassenvariablen von DrawPanel zugreifen können.
Die Ereignissteuerung in JDraw ist etwas komplexer. Zum einen lösen die Schaltflächen in JDraw Ereignisse aus, zum andern hängen diese von der Belegung der Klassenvariablen in DrawPanel ab.
Somit wurde die Ereignissteuerung von JDraw in eine externe Klasse JDrawActionCommand gepackt. Auch das Schließen des JDraw-Fensters ist aufwendiger als bisher und in der externen Klasse
JDrawWindowCommand realisiert.
Es folgen Auszüge aus den Programmcodes der vorgestellten Klassen.
Die Ereignissteuerung ist vollständig in DrawMenuBar untergebracht:
package drawgui;
import figuren.*;
Alle benötigten AWT- und Swing-Pakete importieren, java.util wird für die Container-Klasse
Vector benötigt:
import java.util.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.colorchooser.*;
Zunächst einige Konstanten als Arrays festlegen:
public class DrawMenuBar extends JMenuBar {
// Dateinamen der Button-Icons:
private final static String[] btnImgName = {
"new", "fill", "linecolor", "fillcolor"
};
// Dateinamen der Figur-Icons:
private final static String[] figImgName = {
"line", "rect", "roundrect", "oval"
};
// Dateinamen der Linienbreite-Icons:
private final static String[] lineImgName = {
"width1", "width2", "width3", "width4", "width5"
};
Texte für die kleinen Sprechblasen festlegen:
private final static String[] btnFillToolTipText = {
"Füllen ein", "Füllen aus"
};
private final static String[] btnToolTipText = {
"neu", "Linienfarbe wählen", "Füllfarbe wählen"
};
copyleft:munz
51
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Texte für die Statuszeile festlegen:
private final static String[][] statusText = {
{"Linie", "Linie"},
{"Rechteck", "Gefülltes Rechteck"},
{"Abgerundetes Rechteck", "Gefülltes, abgerundetes Rechteck"},
{"Ellipse" "Gefüllte Ellipse"},
};
private final static int btnFigCount = figImgName.length;
Alle Schaltflächen können als AbstractButton können mit einer Klasse erzeugt werden:
private AbstractButton btnNew, btnLineColor, btnFillColor;
private AbstractButton[] btnImage =
new AbstractButton[btnFigCount];
private AbstractButton btnFilled;
Die Buttons gruppieren; nur jeweils einer kann gedrückt sein:
private ButtonGroup btnGroup;
private PaintPanel paintPanel;
private JLabel jlbStatus;
Auswahl-Feld für die Linien-Breite:
private ComboLineWidth comboLineWidth;
public DrawMenuBar(PaintPanel paintPanel, JLabel jlbStatus) {
super();
this.paintPanel = paintPanel;
this.jlbStatus = jlbStatus;
jlbStatus.setText(statusText[0][0]);
Button „neu“:
btnNew = createButton(
0, btnImgName[0], btnToolTipText[0], new NewAction()
);
add(btnNew);
Die Figuren-Buttons:
btnGroup = new ButtonGroup();
for (int i = 0; i < btnFigCount; i++) {
btnImage[i] = createButton(
1, figImgName[i], statusText[i][0],
new ImageSelectAction(i)
);
btnImage[i].setSelected(i == 0);
btnGroup.add(btnImage[i]);
add(btnImage[i]);
}
Der Button „Füllen ein/aus“:
btnFilled = createButton(
2, btnImgName[1], btnFillToolTipText[0],
new FilledAction()
);
add(btnFilled);
Die Buttons „Farbe wählen':
btnLineColor =
new PaintButton(0, btnToolTipText[1], new ColorAction());
add(btnLineColor);
btnFillColor =
new PaintButton(1, btnToolTipText[2], new ColorAction());
add(btnFillColor);
copyleft:munz
52
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Für die Wahl der Linien-Breite:
Vector list = new Vector();
for (int i = 0; i < lineImgName.length; i++) {
ImageIcon iconLineWidth =
createIcon("images/" + lineImgName[i] + ".gif");
if (iconLineWidth != null) {
iconLineWidth.setDescription("" + (i+1) + " pt");
list.add(iconLineWidth);
}
}
comboLineWidth = new ComboLineWidth(list, paintPanel);
add(comboLineWidth);
add(Box.createHorizontalGlue()); // rechts noch etwas Platz
}
Um Dateien einbinden zu können, macht Java keinen Unterschied, ob eine Datei von der Festplatte
oder aus dem Internet geladen wird. Einen allgemeingültigen Verweis auf eine Datei nennt man URL
(Unified Ressource Locator ). Es ergibt Sinn, einen Dateinamen in eine URL umzuwandeln, dann
kann man diese Dateien sogar in eine jar-Datei einbinden:
private ImageIcon createIcon(String path) {
java.net.URL url =
ClassLoader.getSystemClassLoader().getResource(path);
if (url != null)
return new ImageIcon(url);
else
return null;
}
Universalklasse für alle Buttons als innere Klasse:
private AbstractButton createButton(
int type, String imgName, String toolTipText, Action action) {
AbstractButton btn;
switch(type) {
case 0 : btn = new JButton(action); break;
case 1 : btn = new JRadioButton(action); break;
case 2 : btn = new JToggleButton(action); break;
default : btn = null; return btn;
}
Die verschiedenen Grafiken für die Buttons laden:
ImageIcon icon =
createIcon("images/" + imgName + ".gif");
ImageIcon iconPressed =
createIcon("images/" + imgName + "_pressed.gif");
ImageIcon iconRollover =
createIcon("images/" + imgName + "_rollover.gif");
ImageIcon iconRolloverSelected =
createIcon("images/" + imgName +
"_rollover_selected.gif");
if (iconPressed != null) {
btn.setPressedIcon(iconPressed);
btn.setSelectedIcon(iconPressed);
}
if (iconRollover != null)
btn.setRolloverIcon(iconRollover);
if (iconRolloverSelected != null)
btn.setRolloverSelectedIcon(iconRolloverSelected);
copyleft:munz
53
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
if(icon != null) {
btn.setIcon(icon);
btn.setPreferredSize(
new Dimension(
icon.getIconWidth()+2, icon.getIconHeight()+2
)
);
}
btn.setBorder(null);
btn.setFocusPainted(false);
btn.setToolTipText(toolTipText);
return btn;
}
Eine universelle innere Klasse für die Buttons:
private class PaintButton extends JButton {
private int type;
PaintButton(int type, String toolTipText, Action action) {
setAction(action);
this.type = type;
setBorder(null);
Dimension dim = new Dimension(26, 26);
setMinimumSize(dim);
setMaximumSize(dim);
setPreferredSize(dim);
setToolTipText(toolTipText);
}
Um Grafiken darstellen zu können muss die Methode paintComponent überschrieben werden. Hier
werden farbige Rechtecke auf Buttons zur Farbenwahl gezeichnet, damit man die aktuell ausgewählte
Farbe erkennen kann:
public void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
Dimension dim = getSize();
g2D.clearRect(0, 0, dim.width - 1, dim.height - 1);
Shape rect = new Rectangle2D.Float(
dim.width/4.f+1, dim.height/3.f+1.f,
dim.width/2.f, dim.height/3.f
);
g2D.setStroke(new BasicStroke(2.0f));
switch (type) {
case 0 :
g2D.setColor(paintPanel.getLineColor());
g2D.draw(rect);
break;
case 1 :
g2D.setColor(paintPanel.getFillColor());
g2D.fill(rect);
g2D.setColor(Color.BLACK);
g2D.draw(rect);
break;
}
}
}
copyleft:munz
54
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Hier werden für die unterschiedlichen Buttons die zugehörigen Aktionen bereitgestellt. Aktionen werden von der Klasse AbstractAction abgeleitet und dem Konstruktor des betreffenden Objekts als
Parameter übergeben.
Aktion für Button „neu“:
private class NewAction extends AbstractAction {
public void actionPerformed(ActionEvent event) {
if (JOptionPane.showConfirmDialog(null,
"Wollen Sie wirklich alles löschen?",
"Zeichnung löschen",
JOptionPane.YES_NO_OPTION) == 0) {
paintPanel.clear();
}
}
}
Aktion für die Buttons zur Figuren-Wahl:
private class ImageSelectAction extends AbstractAction {
private int btnNr;
}
ImageSelectAction(int btnNr) {
this.btnNr = btnNr;
}
public void actionPerformed(ActionEvent event) {
Object obj = event.getSource();
if (obj instanceof JRadioButton) {
JRadioButton btn = (JRadioButton) obj;
paintPanel.setFigureType(btnNr);
btn.setToolTipText(
paintPanel.isFilled() ?
statusText[btnNr][1] : statusText[btnNr][0]
);
jlbStatus.setText(
paintPanel.isFilled() ?
statusText[btnNr][1] : statusText[btnNr][0]
);
}
}
Aktion für Button „Füllen ein/aus“:
private class FilledAction extends AbstractAction {
public void actionPerformed(ActionEvent event) {
Object obj = event.getSource();
if (obj instanceof JToggleButton) {
JToggleButton btn = (JToggleButton) obj;
paintPanel.setFilled(btn.isSelected());
btn.setToolTipText(
paintPanel.isFilled() ?
btnFillToolTipText[1] : btnFillToolTipText[0]
);
jlbStatus.setText(
paintPanel.isFilled() ?
statusText[paintPanel.getFigureType()][1] :
statusText[paintPanel.getFigureType()][0]
);
}
}
}
copyleft:munz
55
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Aktion für die Buttons „Farbe wählen“:
private class ColorAction extends AbstractAction {
public void actionPerformed(ActionEvent event) {
Object obj = event.getSource();
if (obj == btnLineColor) {
Color newColor = JColorChooser.showDialog(
null, btnToolTipText[1], paintPanel.getLineColor()
);
if (newColor != null) {
paintPanel.setLineColor(newColor);
btnLineColor.repaint();
}
}
if (obj == btnFillColor) {
Color newColor = JColorChooser.showDialog(
null, btnToolTipText[2], paintPanel.getFillColor()
);
if (newColor != null) {
paintPanel.setFillColor(newColor);
btnFillColor.repaint();
}
}
}
}
}
In der Klasse PaintPanel wird die eigentliche Arbeit getan. Hier werden Mausbewegungen und
Tastaturereignisse abgefragt.
package drawgui;
import figuren.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public class PaintPanel extends JPanel {
// Variablen, auf die mit set... und get... zugegriffen wird.
private int figType;
private Color lineColor = Color.BLACK, fillColor = Color.WHITE;
private JLabel jlbStatus;
private float lineWidth = 1.0f;
private boolean filled = false;
// Lokale Variablen
private Dimension dim;
private RenderingHints renderHints;
private Figur actFig;
private Point anfPunkt, endPunkt;
private Vector paintList;
// Diverse Schalter
private boolean isShiftKeyPressed = false,
isMouseButtonPressed = false;
copyleft:munz
56
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Der Konstruktor:
public PaintPanel(Dimension dim, JLabel jlbStatus) {
super(true); // mit Double-Buffering
this.dim = dim;
this.jlbStatus = jlbStatus;
setOpaque(true);
setBackground(Color.WHITE);
// Zum Glätten der Kanten Antialiasing einschalten
renderHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
renderHints.put(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY
);
Die verschiedenen Grafikobjekte werden in einer Liste gespeichert:
paintList = new Vector();
addKeyListener(new DrawPanelKeyListener());
addMouseListener(new DrawPanelMouseListener());
addMouseMotionListener(new DrawPanelMouseMotionListener());
}
Diverse Methoden, um auf die verschiedenen Objektvariablen zugreifen zu können:
public int getFigureType() {
return figType;
}
public void setFigureType(int figType) {
this.figType = figType;
}
public Color getLineColor() {
return lineColor;
}
public void setLineColor(Color lineColor) {
this.lineColor = lineColor;
}
public Color getFillColor() {
return fillColor;
}
public void setFillColor(Color fillColor) {
this.fillColor = fillColor;
}
public float getLineWidth() {
return lineWidth;
}
public void setLineWidth(float lineWidth) {
this.lineWidth = lineWidth;
}
public boolean isFilled() {
return filled;
}
public void setFilled(boolean filled) {
this.filled = filled;
}
public Dimension getPreferredSize() {
return dim;
}
copyleft:munz
57
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Um die Grafiken darzustellen, muss paintComponent überschrieben werden. Beim Zeichnen wird
die Liste der Grafik-Objekte abgearbeitet:
public void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
g2D.setRenderingHints(renderHints);
g2D.setBackground(Color.WHITE);
g2D.clearRect(0, 0, getSize().width-1, getSize().height-1);
for (
Enumeration e = paintList.elements(); e.hasMoreElements();
) {
Figur fig = (Figur) e.nextElement();
fig.drawFigure(g2D);
}
}
if (actFig != null) {
actFig.drawFigure(g2D);
}
Alles löschen:
public void clear() {
paintList.clear();
anfPunkt = null;
endPunkt = null;
repaint();
}
Die innere Klasse DrawPanelKeyListener überprüft, ob eine Taste gedrückt wurde:
class DrawPanelKeyListener extends KeyAdapter {
public void keyPressed(KeyEvent event) {
// Shift-Taste wurde gedrueckt
if (!isShiftKeyPressed &&
(event.getKeyCode() == KeyEvent.VK_SHIFT)) {
isShiftKeyPressed = true;
// Die Figur soll quadratisch werden
if (actFig != null && isMouseButtonPressed) {
switch(figType) {
case 0: actFig = new SpezialLinie(
anfPunkt, endPunkt
); break;
case 1: actFig = new Quadrat(
anfPunkt, endPunkt
); break;
case 2: actFig = new RundesQuadrat(
anfPunkt, endPunkt
); break;
case 3: actFig = new Kreis(
anfPunkt, endPunkt
); break;
}
actFig.setLineColor(lineColor);
actFig.setFillColor(fillColor);
actFig.setFilled(filled);
actFig.setLineWidth(lineWidth);
jlbStatus.setText(actFig.toString());
repaint();
}
}
copyleft:munz
}
58
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
// Prueft, ob eine Taste losgelassen wurde
public void keyReleased(KeyEvent event) {
// Shift-Taste wurde losgelassen
if (isShiftKeyPressed &&
(event.getKeyCode() == KeyEvent.VK_SHIFT)) {
isShiftKeyPressed = false;
// Die Figur soll nicht mehr quadratisch sein
if (actFig != null && isMouseButtonPressed) {
switch (figType) {
case 0: actFig = new Linie(
anfPunkt, endPunkt
); break;
case 1: actFig = new Rechteck(
anfPunkt, endPunkt
); break;
case 2: actFig = new RundesRechteck(
anfPunkt, endPunkt
); break;
case 3: actFig = new Ellipse(
anfPunkt, endPunkt
); break;
}
actFig.setLineColor(lineColor);
actFig.setFillColor(fillColor);
actFig.setFilled(filled);
actFig.setLineWidth(lineWidth);
jlbStatus.setText(actFig.toString());
repaint();
}
}
}
}
Die innere Klasse DrawPanelMouseListener reagiert auf Mausklicks:
class DrawPanelMouseListener extends MouseAdapter {
mousePressed überschreiben:
public void mousePressed(MouseEvent event) {
KeyBoard-Events werden nur ausgelöst, wenn DrawPanel den Fokus hat. Bei gedrückter Umschalttaste sollen die Sonderfiguren gezeichnet werden:
requestFocus();
if (!isMouseButtonPressed &&
SwingUtilities.isLeftMouseButton(event)) {
isMouseButtonPressed = true;
// ganz neue Figur erstellen
anfPunkt = new Point(event.getX(), event.getY());
endPunkt = (Point) anfPunkt.clone();
Je nach ausgewähltem Figur-Typ reagieren:
switch (figType) {
case 0:
actFig = (isShiftKeyPressed ?
new SpezialLinie(anfPunkt, endPunkt) :
new Linie(anfPunkt, endPunkt));
break;
copyleft:munz
59
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
case 1:
actFig = (isShiftKeyPressed ?
new Quadrat(anfPunkt, endPunkt) :
new Rechteck(anfPunkt, endPunkt));
break;
case 2:
actFig = (isShiftKeyPressed ?
new RundesQuadrat(anfPunkt, endPunkt) :
new RundesRechteck(anfPunkt, endPunkt));
break;
case 3:
actFig = (isShiftKeyPressed ?
new Kreis(anfPunkt, endPunkt) :
new Ellipse(anfPunkt, endPunkt));
break;
}
actFig.setLineColor(lineColor);
actFig.setFillColor(fillColor);
actFig.setFilled(filled);
actFig.setLineWidth(lineWidth);
}
}
mouseReleased überschreiben:
public void mouseReleased(MouseEvent event) {
if (isMouseButtonPressed &&
SwingUtilities.isLeftMouseButton(event)) {
isMouseButtonPressed = false;
if (actFig != null) {
paintList.addElement(actFig);
actFig = null;
}
repaint();
}
}
}
Die innere Klasse DrawPanelMouseMotionListener reagiert auf Mausbewegungen:
class DrawPanelMouseMotionListener extends MouseMotionAdapter {
mouseDragged überschreiben:
public void mouseDragged(MouseEvent event) {
if (actFig != null) {
endPunkt = new Point(event.getX(), event.getY());
actFig.setEndPunkt(endPunkt);
jlbStatus.setText(actFig.toString());
repaint();
}
}
}
}
copyleft:munz
60
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Die Klasse ComboLineWidth stellt Methoden bereit, um die unterschiedliche Linienbreite einstellen
zu können. Sie ist von JComboBox abgeleitet, die eine Kombination aus Schaltflächen und einer
Drop-Down-Liste darstellt:
package drawgui;
import
import
import
import
java.util.*;
java.awt.*;
java.awt.event.*;
javax.swing.*;
public class ComboLineWidth extends JComboBox {
private PaintPanel paintPanel;
private ComboBoxRenderer renderer;
Als Parameter hat der Konstruktor eine Liste von Icons und einen Verweis auf PaintPanel, damit
auf Variablen aus PaintPanel zugegriffen werden kann:
public ComboLineWidth(Vector list, PaintPanel paintPanel) {
super(list);
this.paintPanel = paintPanel;
addActionListener(this);
setEditable(false);
setToolTipText("Linienbreite");
renderer = new ComboBoxRenderer();
setRenderer(renderer);
}
public void actionPerformed(ActionEvent event) {
JComboBox cb = (JComboBox) event.getSource();
paintPanel.setLineWidth(cb.getSelectedIndex() + 1);
}
Die innere Klasse ComboBoxRenderer ist von ListCellRenderer abgeleitet, was so eine Art
„Stempel“ ist, um die Elemente der Liste einheitlich darzustellen. Die Elemente der Liste sind Icons mit
den verschieden dick gezeichneten Linien, sie enthalten aber gleichzeitig auch den Wert der Linienstärke, den man ja zum Weiterverarbeiten benötigt:
class ComboBoxRenderer extends JLabel
implements ListCellRenderer {
public ComboBoxRenderer() {
setOpaque(true);
setHorizontalAlignment(LEFT);
setVerticalAlignment(CENTER);
}
copyleft:munz
61
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Die Methode getListCellRendererComponent aus ListCellRenderer muss überschrieben
werden:
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
ImageIcon icon = (ImageIcon)value;
setText(icon.getDescription());
setIcon(icon);
return this;
}
}
}
Die Klasse JDrawWindowCommand wird von WindowAdapter abgeleitet. Sie ermöglicht das Beenden des Programms mit einem modalen Dialogfenster:
package drawgui;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JDrawWindowCommand extends WindowAdapter {
// Das Fenster wird mit dem Windows-Befehl geschlossen
public void windowClosing(WindowEvent event) {
JFrame source = (JFrame) event.getSource();
if (JOptionPane.showConfirmDialog(source,
"Wollen Sie das Programm wirklich beenden?",
"Programm beenden",
JOptionPane.YES_NO_OPTION) == 0) {
System.exit(0);
}
}
}
Die „Haupt“-Klasse JDraw:
import drawgui.*;
import java.awt.*;
import javax.swing.*;
public class JDraw extends JFrame {
private PaintPanel zeichenFlaeche;
private JLabel jlbStatus;
public JDraw(String title) {
super(title);
Zum ordentlichen Schliessen des Fensters eine eigene Klasse verwenden:
JDrawWindowCommand windowCommand = new JDrawWindowCommand();
copyleft:munz
62
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Die Standardeinstellungen zum Schließen überschreiben:
this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
this.addWindowListener(windowCommand);
Das Layout festlegen:
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
Dimension dim = new Dimension(400, 400);
Die benötigten Elemente einbinden:
jlbStatus = new JLabel();
zeichenFlaeche = new PaintPanel(dim, jlbStatus);
setJMenuBar(new DrawMenuBar(zeichenFlaeche, jlbStatus));
cp.add(zeichenFlaeche);
cp.add(jlbStatus, BorderLayout.SOUTH);
Alles kompakt darstellen, das Fenster auf dem Bildschirm zentrieren:
pack();
Dimension screenSize =
Toolkit.getDefaultToolkit().getScreenSize();
setLocation((screenSize.width - getSize().width)/2,
(screenSize.height - getSize().height)/2);
setVisible(true);
}
main tut nicht viel:
public static void main(String[] args) {
new JDraw("Grafik");
}
}
copyleft:munz
63
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
2.16 Aufgaben
Aufgabe 12: Lösen einer quadratischen Gleichung.
Die quadratische Gleichung a ⋅ x 2 + b ⋅ x + c = 0 soll nach Eingabe der Koeffizienten a,
b und c gelöst werden. Dabei sollen alle möglichen Sonderfälle berücksichtigt werden.
Es ist guter Programmierstil, Fachklassen und GUI-Klassen zu trennen.
Erstellen Sie deshalb eine Fachklasse QuadratGleichung, die in Abhängigkeit der
Koeffizienten a, b und c die Lösung der Gleichung berechnet. Ein erläuternder Ausgabetext und die Lösungsmenge sollen von QuadratGleichung mit den öffentlichen
Methoden getLoesungText und getLoesungMenge zur Verfügung gestellt werden.
In der GUI-Klasse QuadratGleichungGUI können die Koeffizienten a, b und c mit
Hilfe von Textfeldern eingegeben werden.
Das Grafikfenster von QuadratGleichungGUI könnte folgendermaßen aussehen:
Aufgabe 13: Erweiterter Währungs-Rechner.
Erstellen Sie einen erweiterten Währungs-Rechner, der automatisch Währungen ausgewählter Länder konvertiert. Er könnte z.B. so aussehen:
copyleft:munz
64
Fachschulen Lörrach
2
Grafik-Programmierung (GUI)
Java
Aufgabe 14: Das Programm „Figuren“ aus dem Kapitel OOP soll noch weitere Figuren, z.B. einen
Stern, darstellen können.
Hinweis: Ein Stern ist ein besonderes Vieleck.
Außerdem wird noch eine weitere Farbe benötigt. Möglicherweise erhalten die Figuren
auch einen breiten Rand.
Kantenglättung verschönert die Darstellung.
copyleft:munz
65
Fachschulen Lörrach
3
Threads
3
Threads
Java
Programme sind häufig sequenziell angelegt, d.h. Programmschritt für Programmschritt wird nacheinander abgearbeitet. Man kann sie sich also linear angeordnet vorstellen, so dass man alle Programmschritte durchzählen kann. Will man, dass zwei oder mehr Aufgaben gleichzeitig, oder wie wir auch
gerne sagen, nebenläufig, abgearbeitet werden, benutzt man Prozesse oder Threads. Während zwei
Prozesse vollkommen getrennt voneinander in verschieden Speicherbereichen (Adressraum) ablaufen
– ein Beispiel dafür wäre etwa das Drucken aus einer Textverarbeitung heraus und das gleichzeitige
Surfen im Internet mittels eines Browsers – laufen Threads im gleichen Adressraum, benutzen aber
verschiedene Stapel (Stacks) um ihre Aufgaben zu erledigen. So können Threads ihre ihnen
zugewiesenen Methoden aufrufen und ihre eigenen Variablen verwalten.
Ohne dass wir es explizit programmieren müssen, besitzt jede Java-Anwendung mindestens einen
Thread, der im Falle einer Anwendung die main-Methode und im Falle eines Applets die Methoden
init(), start(), stop, und destroy() ausführt. Die Thraeds laufen dabei in der auf dem Rechner installierten VM bzw. der VM, die im Browser integriert ist.
3.1
Programme sind Threads
Einen laufenden Thread kann man mit der Thread-Klassenmethode currentThread() abfragen.
Genauer, currentThread() liefert das Thread-Objekt, das die Methode ausführt, aus der heraus
die Methode currentThread() aufgerufen wurde. Wir wollen dies an einem einfachen Programm
testen.
public class MainThread{
public static void main(String[] args){
Thread laufenderThread = Thread.currentThread();
System.out.println("Name des Threads: " +
laufenderThread.getName());
}
}
Ausgabe:
Wir erkennen, dass der Thread, der die main()-Methode ausführt, selbst main heisst.
Auch die Ausführung der paint()- oder paintComponent()-Methode in einem Frame- bzw.
JFrame-Objekt wird von einem Thread kontrolliert, ohne dass wir es programmieren müssen. Dies gilt
auch dann, wenn die paintComponent()-Methode, wie in der Klasse GrafikPanel geschehen,
überschrieben ist. Um dies zu zeigen, fügen wir in der Methode paintComponent(Graphics g)
von GrafikPanel die folgende Zeile ein:
System.out.println(Thread.currentThread().getName());
Sie bewirkt, dass jedes Mal, wenn ein Auswahlschalter oder das Zeichne-Button gedrückt wird, also
die Methode paintComponent() aufgerufen wird, im Anzeigefenster des JavaEditors der Name des
Threads ausgegeben wird, der die Methode paintComponent() ausführt.
copyleft:munz
66
Fachschulen Lörrach
3
Threads
Java
Wie die nachstehende Ausgabe belegt, ist dies das Thread-Objekt AWT-EventQueue-0.
3.2
Eine eigene Thread-Klasse
Um Ausführungen eines Programms nebenläufig, also parallel zueinander ausführen zu können,
bringt man sie am einfachsten in Klassen unter, die selbst einen Thread darstellen. In Java ist dies
leicht zu realisieren. Man lässt dazu die Klasse von der fertigen Klasse Thread erben und überschreibt die von Thread geerbte Methode run(). In dieser Methode bringt man die Anweisungen
unter, die nebenläufig abgearbeitet werden sollen.
Wir betrachten dies am Beispiel der Klasse EigenerThread:
public class EigenerThread extends Thread{
public void run(){
for (int i = 1; i<=10; i++){
try{
sleep((int)(Math.random()*10000));
System.out.println(getName()+": "+i);
}
catch(InterruptedException exp){
return;
}
}
}
}
copyleft:munz
67
Fachschulen Lörrach
3
Threads
Java
In der run()-Methode implementieren wir eine Schleife, in der bei jedem Durchgang die Klassenmethode sleep(int millis)3 aufgerufen wird. Diese Methode hat EigenerThread von Thread geerbt. Wird sie ausgeführt, so wird der Thread, der die run()-Methode kontrolliert, für eine gewisse
Zeit angehalten. Die „Schlafdauer“ wird der Methode in Millisekunden übergeben. Beim Aufruf in unserer Schleife übergeben wir der Methode sleep(…) eine Zufallszahl. Beim Ausführen der Methode
kann es zu einer Ausnahme kommen, weshalb eine try-catch Konstruktion implementiert werden
muss. Im try-Block implementieren wir die eigentliche Funktionalität von run(). Im Block von
catch(…) bringen wir all das unter, was getan werden soll, falls sleep(…) eine Ausnahme auslöst.
Eine solche Ausnahme liefert ein Objekt des Typs InterruptedException, weshalb catch ein
Objekt vom Typ InterruptedException übergeben werden muss.
Da es uns nur um die Demonstration geht, soll im Ausnahmefall nichts passieren, return liefert also
nichts.
Ebenfalls von Thread geerbt ist die Objekt-Methode getName(). Sie liefert den Namen des Threads,
der beim Erzeugen standardmäßig den Namen Thread-<Nr.> erhält.
Um die eigene Thread-Klasse zu testen, benutzen wir ein Demoprogramm. Zur Demonstration der
Nebenläufigkeit, erzeugen wir von EigenerThread zwei Instanzen und starten sie. Zum Starten benutzen wir die Methode start(), die EigenerThread von Thread erbt. Diese Thread-Objekt-Methode start() organisiert einiges; für uns wichtig ist, dass sie die run()-Methode des Threads
startet.
public class EigenerThreadDemo{
public static void main(String[] args){
EigenerThread s = new EigenerThread();
EigenerThread t = new EigenerThread();
s.start();
t.start();
}
}
Eine mögliche Ausgabe ist:
Thread-2: 1
Thread-2: 2
Thread-2: 3
Thread-1: 1
Thread-2: 4
Thread-1: 2
Thread-2: 5
Thread-1: 3
Thread-2: 6
Thread-2: 7
Thread-1: 4
Thread-2: 8
Thread-1: 5
Thread-1: 6
Thread-2: 9
Thread-2: 10
Thread-1: 7
Thread-1: 8
Thread-1: 9
Thread-1: 10
Sehr schön kann man die Nebenläufigkeit der beiden Algorithmen erkennen, da man nicht vorhersehen kann, ob zuerst Thread-1 oder Thread-2 eine Ausgabe erzeugen.
3
sleep() ist eine Klassenmethode, müsste also üblicherweise durch Thread.sleep(…)
aufgerufen werden. Da unsere Klasse von Thread erbt, ist sleep(…) auch eine Klassenmethode von
EigenerThread und der Aufruf kann mit this.sleep(…) oder kurz mit sleep(…) erfolgen.
copyleft:munz
68
Fachschulen Lörrach
3
Threads
Java
Aufgabe 15: Die Klasse Thread besitzt neben dem Standardkonstruktor Thread() noch sieben
weitere Konstruktoren (vgl. dazu die java-Dokumentation von SUN). Einen weiteren
wollen wir kennenlernen: Thread(String name). Mit Hilfe dieses Konstruktors kann
man einem Thread-Objekt statt des Standardnamens Thread-<Nr> einen eigenen
Namen geben. In der Klasse EigenerThread kann man mit Hilfe von
super(<Parameterliste des gewünschten Konstruktors>) auf alle Konstruktoren der Oberklasse Thread zugreifen und somit auch den Konstruktor von
EigenerThread gestalten.
Modifizieren Sie den Konstruktor von EigenerThread und passen Sie seinen Aufruf in
EigenerThreadDemo so an, dass der Thread einen von Ihnen gewählten Namen
trägt.
3.3
Threads – Runnable
Wir haben gelernt, dass wir eine Klasse X, in der wir einen nebenläufigen ‚Prozess’ laufen lassen
wollen, am besten von Thread erben lassen. Ein Problem haben wir, wenn unsere Klasse X bereits
von einer Klasse, nennen wir sie A, erbt. Wir hätten einen Fall von Mehrfachvererbung, was in Java
nicht zulässig ist.
Die Abbildung zeigt die Problematik. Eine Lösung bahnt sich an, wenn wir wissen, dass die Klasse
Thread das Interface4 Runnable implementiert (siehe Bild unten).
4
Ein Interface ist eine abstrakte Klasse, die keine Attribute und nur abstrakte Methoden enthält.Wenn
eine Klasse von einem Interface erben soll, verwenden wir statt des reservierten Wortes extends das
reservierte Wort implements. Dieses Wort ‚implements’ deutet an, was eine Klasse leisten muss,
will sie von einem Interface erben: sie muss(!) alle ererbten Methoden implementieren. Mittels
Interfaces kann man in Java eine Pseudo-Mehrfach-Vererbung realisieren, denn es lassen sich neben
einer regulären Vererbung beliebig viele Interfaces implementieren.
copyleft:munz
69
Fachschulen Lörrach
3
Threads
Java
Das Interface Runnable ist sehr einfach gebaut. Seine Aufgabe besteht allein darin, zu garantieren,
dass in den Klassen wie X die Methode run() tatsächlich implementiert wird. Aus diesem Grund
implementiert auch die Klasse Thread selbst das Interface Runnable.
Es hat die Gestalt:
public interface Runnable{
public abstract void run();
}
Der Ausweg besteht also darin, dass unsere Klasse X das Interface Runnable und damit die Methode run() implementiert.
public class X implements Runnable{
public void run(){
for (int i = 1; i<=10; i++){
try{
Thread.sleep((int)(Math.random()*10000));
System.out.println(Thread.currentThread().getName()+": "+i);
}
catch(InterruptedException exp){
return;
}
}
}
}
Da nun X nicht mehr von Thread erbt, müssen wir die Klassen-Methode sleep() jetzt mit
Thread.sleep() aufrufen. Wenn wir den Namen des Threads ausgeben wollen, der die Instanz
kontrolliert, müssen wir mit der Klassen-Methode currentThread(), diesen erst ermitteln und dann
seinen Namen mit getName() erfragen. Das allerdings ist noch nicht alles. Denn Runnable selbst ist
kein Thread, so dass die Klasse X die Rolle nicht ganz übernehmen kann, die EigenerThread
spielte. Wir zeigen zunächst Demo-Programm XDemo, das die Rolle von EigenerThreadDemo übernimmt und erläutern dann im Einzelnen, was passiert.
public class XDemo{
public static void main(String[] args){
Runnable rs = new X();
Runnable rt = new X();
Thread s = new Thread(rs,"Thread: s ");
Thread t = new Thread(rt,"Thread: t ");
s.start();
t.start();
}
}
copyleft:munz
70
Fachschulen Lörrach
3
Threads
Java
In der main(…)-Methode werden zunächst zwei Instanzen der Klasse X erzeugt. Statt
X rs = new X();
schreiben wir
Runnable rs = new X();
das geht, weil X eine Unterklasse von Runnable ist. Anschließend werden zwei Threads-Objekte erzeugt, wobei wir den Konstruktor von der Klasse Thread (siehe Java-Dokumentation) benutzen, der
es erlaubt, dem erzeugten Thread-Objekt ein Runnable-Objekt zu übergeben. Wir übergeben die
Objekte rs und rt. Damit ist sichergestellt, dass die Objekte übergeben sind, die die run()-Methode
implementiert haben. Als zweiten Parameter übergeben wir dem Konstruktor den Namen, den unser
erzeugter Thread tragen soll.
3.4
Runnable als anonyme Klasse
Im Beispiel XDemo2 zeigen wir, wie man auf die Klasse X verzichten kann und ihre Funktionalität als
anonyme Klasse in XDemo2 integriert. Das Vorgehen ist immer dann interessant, wenn der Aufwand
in der run()-Methode relativ klein ist und die Übersichtlichkeit nicht darunter leidet.
public class XDemo2{
public static void main(String[] args){
Runnable rs = new Runnable(){
public void run(){
schlafen(500);
}
};
Runnable rt = new Runnable(){
public void run(){
schlafen(1000);
}
};
Thread s = new Thread(rs,"Thread: s ");
Thread t = new Thread(rt,"Thread: t ");
s.start();
t.start();
}
private static void schlafen(int millis){
for (int i = 1; i<=10; i++){
try{
Thread.sleep((int)(Math.random()*millis));
System.out.println(Thread.currentThread().getName()
+": "+i);
}
catch(InterruptedException exp){
return;
}
}
}
}
Von Interesse sind die grau unterlegten Teile des Quelltextes:
Da Runnable ein Interface und damit eine abstrakte Klasse ist, lässt sich von ihr keine Instanz direkt
erzeugen. Eine Zeile wie
Runnable rs = new Runnable();
copyleft:munz
71
Fachschulen Lörrach
3
Threads
Java
wird vom Compiler nicht übersetzt. Er meldet einen Fehler5. Der grau unterlegte Text zeigt, wie man
sich helfen kann: Man erzeugt zwar ein Runnable-Objekt implementiert aber gleichzeitig die run()Methode
Runnable rs = new Runnable() {"hier run() implementieren"};
Da dies nun zweimal passieren müsste, haben wir die Funktionalität der run()-Methode in einer eigene schlafen()-Methode6 implementiert, die von run() aufgerufen wurde. Sie enthält den gleichen Quelltext, wie die run()-Methode aus dem letzten Kapitel.
Aufgabe 16: Erstelle eine Applikation mit einer animierten Grafik, z.B. könnte eine laufende Billardkugel an den Fensterbegrenzungen reflektiert werden.
5
Würde man Instanzen von Runnable zulassen, so könnte man nicht zusichern, dass die run()Methode implementiert ist.
6
Da schlafen() in der Klasse implementiert ist, in der auch die main()-Methode implementiert ist,
muss schlafen() als static deklariert werden.
copyleft:munz
72
Fachschulen Lörrach
4
Rekursionen
4
Rekursionen
4.1
Java
Was sind Rekursionen?
Häufig wird n! als ein Produkt erklärt, das 1 als ersten, 2 als zweiten und n als n.ten Faktor hat. Dazwischen setzt man die Reihe fort und schreibt kurz: n! = 1 2 3 … n und nimmt an, dass jeder weiß,
was man zwischen den Faktoren 3 und n zu tun hat. Will man n! etwas seriöser erklären schreibt man:
n! = n. (n-1)! mit 1! = 0 (und 0! =1)
Eine solche Definition nennt man rekursiv und auf den ersten Blick meint man, so könne man nicht
definieren, denn man erklärt etwas dadurch, dass man es selbst verwendet. Dies erscheint aber nur
auf den ersten Blick. Denn n! benutzt wieder eine Fakultät aber, und das ist wesentlich, mit einem
vereinfachten Parameter. Und schließlich findet man den Ausgang, und gewinnt dadurch den Boden
unter den Füßen mit 1!, dessen Wert man als 1 gesetzt hat. Wichtig bei einem rekursiven Abstieg sind
also die Vereinfachung (im Parameter) und die Abbruchbedingung.
A
B
C
D
E
F
6!
6!=6.5!
5!=5.4!
.
4!=4 3!
3!=3.2!
2!=2.1!
=
=
=
=
=
=
1!=1
.
2!=2.1
3!=3.2
4!=4 6
5!=5.24
720
6!=6.120
Person A bekommt die Aufgabe gestellt 6! zu berechnen. A kann aber die Aufgabe lösen, wenn sie
weiß was 5! ist. Also stellt A der Person B die Aufgabe 5! zu berechnen und wartet mit der Beantwortung der Frage, bis B ihm die gewünschte Zahl liefert. Nun ist B fast in der gleichen misslichen Lage
wie A. Im Prinzip hat B das gleiche Problem zu lösen, allerdings mit einer etwas einfacheren Zahl. Das
Spiel geht so weiter, die Rekursionstiefe nimmt zu, bis schließlich F die Frage gestellt bekommt, was
1! sei. Das weiß aber F, der Abstieg ist zu Ende und der Wiederaufstieg kann beginnen. E kann jetzt
2! berechnen und gibt sein Ergebnis an D weiter, bis schließlich A sein Frage, was denn 5! ist, beantwortet bekommt und seinem Auftraggeber sein Ergebnis 6! = 720 zurückliefern kann. In einer Programmiersprache implementiert, entsprechen den Personen A bis F Aufrufe einer Funktion.
4.2
Rekursive Methoden in Java
Das folgende Demoprogramm demonstriert den rekursiven Ab- und Aufstieg:
public class RekursionDemo{
private static void rekursion(int a){
a--;
System.out.println(a);
if (a>0)rekursion(a);
System.out.println(a);
}
public static void main(String[] args){
rekursion(5);
}
}
Um in unserem Bild von oben zu bleiben, scheint die Methode rekursion(…) der Person A zu entsprechen. Dann bleibt die Frage, wo sind die Personen B, C etc.? Tatsächlich entspricht nicht die Methode einer Person, sondern der Aufruf der Methode. Im Speicher des Rechners laufen tatsächlich 5
Methoden. Die erste, sie wird mit dem Parameter 5 aufgerufen, lebt am längsten. Dann folgen
nacheinander die Aufrufe von rekursion(…) mit den Parametern 4, 3, 2, und 1. Beim Ab- und beim
Aufsteigen wird jeweils eine Ausgabe des um 1 verminderten, aktuellen Parameters gemacht. Also
müssten nacheinander 4, 3, 2, 1, 0 beim Abstieg und danach 0, 1, 2, 3, 4 beim Aufstieg ausgegeben
werden, was auch die Ausgabe des Programms bestätigt.
copyleft:munz
73
Fachschulen Lörrach
4
Rekursionen
Java
Setzt man in seinem Java-Editor Breakpoints z.B. in den Zeilen 5 und 6 und arbeitet man mit dem Debugger, so wird die Rekursion noch deutlicher.
Der Debugger hält 5-mal in der Zeile 5 und erst danach 5-mal in der Zeile 7.
Aufgabe 17: Programmieren Sie eine rekursive, statische Methode fakultaet(…) in
FakultaetDemo.java. Der Bediener des Programms kann bestimmen, von welcher
Zahl die Fakultät bestimmt werden soll. Die Lösung könnte folgendes Aussehen haben.
Aufgabe 18: Es gilt fib(n) = fib(n-1)+fib(n-2) und fib(2) = fib(1) = 1. Nehmen Sie
die notwendigen Namensänderungen in FakultaetDemo.java vor und ersetzen Sie
die rekursive Methode fakultaet(…) durch die rekursive Methode fib(…).
f(2)
1
f(3)
copyleft:munz
Die obige Berechnung braucht selbst bei einem Pentium 4 (1,8 GHz) Prozessor nahezu
30 Sekunden. Das liegt daran, dass viele Berechnungen vielfach vorgenommen werden, wie der Berechnungsbaum für fib(6) zeigt:
Fügen Sie in der nachfolgenden Tabelle „Aufrufpfeile“ ein
f(6)
f(5)
f(4)
f(4)
f(3)
f(3)
f(2)
f(2)
f(2)
f(1)
f(2)
f(1)
1
f(1) 1
1
1
1
1
1
74
Fachschulen Lörrach
4
Rekursionen
Java
Fibonacci-Zahlen lassen sich wesentlich schneller auch nicht rekursiv berechnen. Es gilt nämlich:
Fib( n ) =
1
5
1+ 5
2
n
1− 5
−
2
n
.
Dass es sich hierbei tatsächlich um Fibonacci-Zahlen handelt ist schon schwerer zu erkennen. Die rekursive Formulierung ist da deutlicher.
Aufgabe 19: Der ggT zweier ganzer Zahlen lässt sich ebenfalls rekursiv berechnen. Dieses Verfahren ist leicht zu programmieren und effizient. Der Algorithmus heißt:
ggT(a,a) = ggT(a,0) = a und ggT(a,b) = ggT(b,a mod b)
Bemerkung: ggT(a,0) kann beim rekursiven Aufruf entstehen. Z.B. ggT(12,4), danach wird nämlich ggT(4,12 mod 4) = ggT(4,0) aufgerufen.
copyleft:munz
75
Fachschulen Lörrach
4
Rekursionen
4.3
Java
Rekursive Grafiken
Grafiken lassen sich leicht mittels einer Turtle erstellen. Wir unterbrechen also unseren Kurs für einen
kurzen Moment, um eine Turtle vorzustellen.
4.3.1
Eine einfache Turtle
Für den hier beschriebenen Kurs werden nur Turtle.class, TurtleGUI.class und das Interface
TGrafik.class benutzt. Die Quelltexte zu den drei Programmen finden sich im Anhang.
Die Klasse TurtleGUI implementiert das Interface TGrafik, das lediglich eine Methode
zeichne(Turtle t) anlegt. Da TurtleGUI diese Methode erbt, sie aber nicht implementiert, ist
die Klasse TurtleGUI abstrakt und eine Klasse wie z.B. Sierpinski muss die Methode
zeichne(Turtle t) implementieren. Auf diese Weise wird garantiert, dass eine Klasse, die das
Zeichnen organisiert, eine syntaktisch korrekte Methode zeichne(Turtle t) implementiert hat.
Zusätzlich sorgt das Verfahren, dass die „zeichnende Klasse“ von unnötigem Ballast befreit ist. Die
Übersichtlichkeit wird schließlich weiter erhöht, in dem man die Klassen TurtleGUI, TGrafik und
die Turtle selbst zu einem Paket turtle schnürt, das unser Programm, z.B. Sierpinski nur noch
zu importieren braucht.
Eine Klasse TurtleGrafikTest zeigt den prinzipiellen Aufbau eines Programms, das die Turtle
zum Zeichnen einsetzt. Im Konstruktor wird die Turtle positioniert, dass dies auch in der Methode
zeichne(Turtle t) auch geschehen kann, ja auch manchmal Sinn macht, werden an anderen
Beispielen noch zeigen. In der main–Methode wird beim Aufruf des Konstruktors diesem die Größe
des Zeichenfeldes übergeben. Dort kann man auch Veränderungen vornehmen, die die Größe des
Zeichenfeldes beeinflussen.
copyleft:munz
76
Fachschulen Lörrach
4
Rekursionen
Java
import turtle.*;
public class TurtleGrafikTest extends TurtleGUI {
public TurtleGrafikTest(String titel, int b, int h) {
super(titel, b, h);
t.setTurtle(100,200);
}
public void zeichne(Turtle t) {
t.vor(100);
t.rechts(100);
}
}
public static void main(String[] args){
new TurtleGrafikTest("Turtle-Test", 400, 300);
}
Um einem Benutzer des Paketes turtle sein volle Funktionalität mitzuteilen, benutzt man am Besten
eine Java-Dokumentation des Paktes7. Die unten stehende Abbildung zeigt einen Ausschnitt aus der
Dokumentation, nämlich die Auflistung der Methoden der Turtle.
7
Wer den Quelltext des Paktes besitzt kann mit Hilfe des Programms javadoc.exe, das Bestandteil
des Java-Pakets von Sun ist und in den JavaEditor integriert werden kann, diese Dokumentation
selbst erstellen.
copyleft:munz
77
Fachschulen Lörrach
4
Rekursionen
4.3.2
Java
Übung mit der Turtle
Die oben im Quelltext dargestellte Klasse TurtleGrafikTest erzeugt nach wiederholtem Anklicken
des Buttons „Zeichne!“ die nachstehende Grafik.
Aufgabe 20: a) Die Turtle soll nach viermaligen Klicken ein Quadrat gezeichnet haben.
b) Die Turtle soll das gleiche Quadrat nach einmal Klicken gezeichnet haben.
c) Die Turtle zeichnet ein Quadrat, das auf einer Ecke „steht“ Wenn dieses Quadrat
auf einen Mausklick gezeichnet wird, was passiert dann, wenn man das Button
„Zeichne!“ mehrmals anklickt?
Aufgabe 21: Die Turtle beginnt mit einer Schrittlänge 100, nach jedem Drehen um 900 nach rechts
wird die Schrittlänge um 10% verkürzt, bevor die Turtle weiter vorwärts schreitet.
4.4
Binärbaum
Um die Grundstruktur des Binärbaumes, ein auf dem Kopf stehendes 'T', zeichnen zu lassen, verändern wir lediglich die Methode zeichne(Trutle t)neu:
import turtle.*;
public class BinaerBaumGS extends TurtleGUI {
public BinaerBaumGS(String titel, int b, int h) {
super(titel, b, h);
t.setTurtle(200,100);
}
public void zeichne(Turtle t){
t.vor(-100);
t.rechts(90);
t.vor(100);
t.vor(-200);
}
public static void main(String[] args){
new BinaerBaumGS("Binär-Baum-GrundStruktur", 400,200);
}
}
copyleft:munz
78
Fachschulen Lörrach
4
Rekursionen
Java
Wir erhalten die folgende Darstellung:
Wir wollen an den linken und rechten Enden des wie auf dem Kopf stehend aussehende „T“ die gleiche Figur, allerdings verkleinert anfügen. Wir hätten dann vier solche Enden. Wollen wir so fortfahren,
beschreiben wir diesen Vorgang am besten rekursiv.
Die Turtle zeigt zu Beginn in Richtung Norden. Wir lassen sie sich bis zum rechten Ende ziehen. Bevor wir jetzt den ganzen Vorgang rekursiv aufrufen, muss die Turtle erst wieder nach Norden zeigen.
Nach dem rekursiven Aufruf wandert die Turtle zum linken Ende, wendet sich nach Norden und startet
den Vorgang erneut rekursiv. Danach muss sie an den Ausgangspunkt zurückkehren und zum
Schluss wieder nach Norden zeigen. Da der rekursive Aufruf mit einer halbierten Schrittlänge geschieht, und zeichne(Turtle t) lediglich die Übergabe der Turtle und nicht einer verkleinerten
Länge erlaubt, muss die rekursive Methode außerhalb von zeichen(Turtle t) z.B. in
zeichneBinaerBaum(Turtle t, int l) untergebracht werden. In unserem Beispiel findet der
rekursive Abstieg sein Ende, wenn l kleiner 1 wird. Stellen im Quelltext, die sich vom Quelltext von
BinaerBaumGS.java unterscheiden sind grau unterlegt.
Quelltext:
import turtle.*;
public class BinaerBaum extends TurtleGUI {
int l;
}
public BinaerBaum(String titel, int b, int h) {
super(titel, b, h);
l = 100;
}
public void zeichne(Turtle t){
t.setTurtle(200, 100);
zeichneBinaerBaum(t,l);
}
public void zeichneBinaerBaum(Turtle t, int l){
if (l>0){
t.vor(-l);
t.rechts(90); t.vor(l); t.rechts(-90);
zeichneBinaerBaum(t,l/2);
t.rechts(90); t.vor(-2*l); t.rechts(-90);
zeichneBinaerBaum(t,l/2);
t.rechts(90); t.vor(l); t.rechts(-90);
t.vor(l);
}
}
public static void main(String[] args){
new BinaerBaum("Binär-Baum", 400,400);
}
copyleft:munz
79
Fachschulen Lörrach
4
4.5
Rekursionen
Java
Kochkurve
Wir dritteln eine Strecke und setzen ein Dreieck nach der folgenden Vorschrift darauf: Gehe um die
neue (gedrittelte) Strecke nach vorne (F = forward), drehe um α = 60° gegen den Urzeigersinn (+),
gehe wieder um die neue Strecke vorwärts, drehe zweimal um α mit dem Uhrzeigersinn (– –), gehe
vorwärts, drehe um α gegen den Uhrzeigersinn und gehe ein letztes Mal vorwärts. Wir können mit der
„F+/–“-Notation also kurz schreiben:
L := F + F - - F + F
Das L-System in Java:
t.vor(l);
//F
t.rechts(-60);
//+
t.vor(l);
//F
t.rechts(120);
//-t.vor(l);
//F
t.rechts(-60);
//+
t.vor(l);
//F
Wir erhalten dann die nachstehende Figur:
Auf jeder Strecke machen wir nach einer weiteren Drittelung der Strecke das gleiche wieder. Das
heißt jedes F ersetzen wir durch L mit einer Drittelung der Strecke:
L + L -- L + L mit L = F + F - - F + F
wir erhalten dann:
Setzen wir die Rekursion fort, so hat die Kochkurve die folgende Gestalt:
Der Quelltext:
import turtle.*;
public class Kochkurve2 extends TurtleGUI {
int l;
public Kochkurve2(String titel, int b, int h){
super(titel, b, h);
l = 729;
}
copyleft:munz
80
Fachschulen Lörrach
4
Rekursionen
}
Java
public void zeichne(Turtle t){
t.setTurtle(10,300);
t.setOrientierungsWinkel(0);
t.rechts(90);
zeichneKoch(t,l);
}
public void zeichneKoch(Turtle t, int l){
if (l<9){
t.vor(l);
}
else{
zeichneKoch(t,l/3);
//L
t.rechts(-60);
//+
zeichneKoch(t,l/3);
//L
t.rechts(120);
//-zeichneKoch(t,l/3);
//L
t.rechts(-60);
//+
zeichneKoch(t,l/3);
//L
}
}
public static void main(String[] args){
new Kochkurve2("Koch-Kurve-2",700,400);
}
Das Setzten der Turtle geschieht jetzt in der Methode zeichne(Turtle t) und nicht mehr im Konstruktor. Der Grund dafür ist: Klickt man erneut auf das 'Zeichne!'-Button, soll die gleiche Kochkurve
gezeichnet werden. Zu diesem Zeitpunkt kann man aber nicht mehr zusichern, dass die Turtle richtig
positioniert ist. Auch die Orientierung kann sich geändert haben. Es reicht also nicht nur, die Turtle auf
ihren Ausgangspunkt zu positionieren wir müssen mit dem Aufruf t.setOrientierungsWinkel(0)
auch die Ausrichtung auf ihren Standardwert zurücksetzen.
copyleft:munz
81
Fachschulen Lörrach
4
Rekursionen
Java
Aufgabe 22: Programmieren Sie das L-System: L := F + F - F - F + F, mit α = 90° und der
Bedingung, dass beim rekursiven Aufruf die Streckenlänge wieder gedrittelt wird. Es
sollte sich die nachstehende Figur ergeben.
4.6
Sierpinski-Dreieck
Ausgangsgrafik für das Sierpinski-Dreieck ist folgende Figur:
Zum Sierpinski-Dreieck selbst kommen wir, wenn wir an die Enden des „Dreisterns“ rekursiv fortgesetzt wieder „Dreisterne“ mit jeweils halbierter Streckenlänge ansetzen.
Wir erhalten dann die unten stehende Figur:
copyleft:munz
82
Fachschulen Lörrach
4
Rekursionen
Java
Quelltext:
public class Sierpinski extends TurtleGUI {
int l;
public Sierpinski(String titel, int b, int h){
super(titel, b, h);
l = 128;
}
public void zeichne(Turtle t){
t.setTurtle(250,350);
int xa = t.getTurtleX();
int ya = t.getTurtleY();
zeichneSierpinski(t,l,xa,ya);
}
public void zeichneSierpinski(Turtle t, int l, int xa, int ya){
if (l>1){
t.setTurtle(xa,ya) ;
t.rechts(120); t.vor(l); t.setOrientierungsWinkel(0);
zeichneSierpinski(t,l/2,t.getTurtleX(),t.getTurtleY())
t.setTurtle(xa,ya);
t.vor(l);
zeichneSierpinski(t,l/2,t.getTurtleX(),t.getTurtleY());
t.setTurtle(xa,ya);
t.rechts(-120); t.vor(l); t.setOrientierungsWinkel(0);
zeichneSierpinski(t,l/2,t.getTurtleX(),t.getTurtleY());
}
}
public static void main(String[] args){
new Sierpinski("Sierpinski-Dreieck",500,500);
}
}
Wegen der Rundungsfehler bei der Berechnung der Positionen der Turtle kommen wir jetzt nicht mehr
alleine mit den Methoden vor(…) und rechts(…) aus. Wir benutzen deshalb noch getTurtleX(),
getTurtleY(), setTurtle(…) und setOrientierungsWinkel(…).
4.7
Pythagoras-Baum
In der ersten Stufe hat der Pythagoras-Baum die folgende Gestalt:
Man gibt ein Quadrat mit einer bestimmten Seitenlänge vor. Über der einen Seite zeichnet man ein
0
0
rechtwinkliges Dreieck (in unserem Beispiel hat dieses die Winkel 30 und 60 ). Über die noch freien
Katheten zeichnet man die Kathetenquadrate. Es entsteht die bekannte Darstellung zum Pythagoräischen Lehrsatz.
copyleft:munz
83
Fachschulen Lörrach
4
Rekursionen
Java
Den Pythagoras-Baum erhält man nun, wenn man die Kathetenquadrate selbst wieder als Hypothenusenquadrate für die nächste Generation auffasst. Wir erhalten dann das nächste Bild.
Erhöht man die Rekursionstiefe stark genug, bekommt man eine Darstellung des Pythagoras-Baumes.
Quelltext:
import turtle.*;
public class PythagorasBaum extends TurtleGUI {
int l;
public PythagorasBaum(String titel, int b, int h){
super(titel, b, h);
l = 80;
}
public void zeichne(Turtle t){
t.setTurtle(400,400);
zeichnePB(t,l);
}
public void zeichneQuadrat(Turtle t,int l){
for (int i = 0; i<4; i++){
t.vor(l); t.rechts(90);
}
}
copyleft:munz
84
Fachschulen Lörrach
4
Rekursionen
}
Java
public void zeichnePB(Turtle t, int l){
int xa = t.getTurtleX();
int ya = t.getTurtleY();
zeichneQuadrat(t,l);
if (l>0){
t.vor(l);
t.rechts(-30);
zeichnePB(t,(int)(0.5*l*Math.sqrt(3)));
t.rechts(30);
t.setTurtle(xa,ya);
t.vor(l);
t.rechts(60);
t.vor( (int)(0.5*l*Math.sqrt(3)) );
zeichnePB(t,(int)(0.5*l));
t.rechts(-60);
t.setTurtle(xa,ya);
}
}
public static void main(String[] args){
new PythagorasBaum("Pythagoras-Baum",700,500);
}
Zur Erläuterung des Algorithmus:
Die Turtle ‚sitzt’ im Ausgangspunkt. Zuerst wird ein Quadrat gezeichnet. Danach bewegt sich die
Turtle auf den Punkt P_li, dreht ihre Blickrichtung um 30° nach links (der dicke Pfeil gibt die Blickrichtung der Turtle an). Dann erfolgt der rekursive Aufruf der Methode zeichnePB(). Die Turtle bewegt
sich mit neuer Blickrichtung in den Punkt P_re. Wieder der rekursive Aufruf der Methode
zeichnePB(). Schließlich wird die Turtle wieder in den Ausgangspunkt gesetzt.
copyleft:munz
85
Fachschulen Lörrach
4
Rekursionen
Java
Eine wichtige Anmerkung:
Das Bewegen zu den Punkten P_li und P_re geht immer vom Ausgangspunkt aus, wohin die Turtle
zunächst gesetzt werden muss. Der Grund dafür ist wieder die mangelnde Genauigkeit beim Zeichnen, wenn man die Bewegungen allein mit den Turtle-Methoden vor(..) und rechts(..) bewerkstelligt. Die Ungenauigkeit schaukelt sich mit zunehmender Rekursionstiefe immer höher, so dass die
rechtwinkligen Dreiecke nicht mehr geschlossen erscheinen, die ganze Figur auseinander läuft, so
dass sie als Pythagoraus-Baum kaum mehr erkennbar ist, obwohl der Algorithmus stimmt.
Aufgabe 23: Programmieren Sie einen symmetrischen Pythagoras-Baum
copyleft:munz
86
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
5.1
Java
Nebenläufige Prozesse (Threads)
Ein Betriebssystem wie Windows verwaltet scheinbar parallel und unabhängig verlaufende Prozesse
(Multitasking) Solche eigenständigen Prozesse eignen sich besonders, um im Hintergrund laufende
Vorgänge zu übernehmen. Musterbeispiele sind die zahlreichen animierten Applets, die man überall
im Internet finden kann. Java gestattet den Einbau solcher scheinbar für sich eigenständig ablaufender Handlungs-Fäden („Threads“) in eigene Programme, ohne dass man ins Betriebssytem eingreifen
müsste.
Erstes Beispiel:
import java.awt.*;
import java.applet.*;
public class Thread_1 extends Applet {
//Globale Variablendeklarationen:
EinfachThread et1,et2;
//Diese Hilfsklasse muss im gleichen Verzeichnis stehen!
TextArea ausgabeTA;
public void init() {
ausgabeTA= new TextArea("",16,50,TextArea.SCROLLBARS_BOTH);
add(ausgabeTA);
ausgabeTA.setBackground(Color.white);
ausgabeTA.setEditable(false);
et1 = new EinfachThread("JavaThread1", ausgabeTA);
et2 = new EinfachThread("JavaThread2", ausgabeTA);
et1.start(); et2.start();
}//Ende von init-Methode
}//Ende des ganzen Applets
Hier die Hilfsklasse EinfachThread:
import java.awt.*;
public class EinfachThread extends Thread {
//Globale Variablendeklarationen:
String str;
TextArea tA;
public EinfachThread(String name,TextArea t) {
str=name; tA=t;
} //Ende Konstruktor
public void run() {
for (int i=1; i<7; i++) {
tA.append(i+": "+str+"\n");
try {
sleep((int)(Math.random()*1000));
}
catch (InterruptedException e){}
}//Ende for
tA.append("Ende von : "+str+"\n");
}//Ende von run
}//Ende der Klasse EinfachThread
copyleft:munz
87
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Ein mögliches Rennen der beiden Threads zeigt obige Abbildung. Beim nächsten Start kann es wieder anders aussehen!
Erläuterungen zu obigem Applet:
Thread ist eine eigene Java-Klasse, die man auf zwei Arten nutzen kann.
Man schafft wie in obigem Beispiel durch Vererbung (extends) von Thread abgeleitete Klassen wie
z.B. EinfachThread. Diese Klassen können dann in Applikationen oder Applets als Hilfsklassen
benutzt werden (da Java keine Mehrfachvererbung zulässt, kann man nicht EinfachThread sowohl
von Applet wie von Thread abstammen lassen.)
Will man aber keine Hilfsklassen benutzen, muss das Applet die Schnittstelle (Interface)
Runnable implementieren. Dies wird in späteren Beispielen gezeigt werden.
Im Applet Thread_1 werden lediglich in init die zwei Threads mit ihren Namen geschaffen, das
Ausgabetextfeld wird als Parameter übergeben. Dann werden beide Threads mit der Methode
start() der Klasse Thread gestartet.
Jeder so geschaffene EinfachThread wirft seine run()-Methode an, schreibt seinen Namen ins
Textfeld und legt sich dann über seine sleep()-Methode eine zufällige Zeit schlafen. Nach 6 Schleifendurchläufen endet er jeweils.
copyleft:munz
88
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Aufgabe 24: Erweitern Sie obige Klassen an geeigneter Stelle so, dass die jeweilige Verzögerungszeit im TextFeld hinter dem Thread-Namen ausgedruckt wird.
Aufgabe 25: Statten Sie das obige Applet mit einem Button „Los!“ aus, sodass Sie durch Knopfdruck
das Thread-Rennen erneut starten können.
Im nächsten Beispiel werden keine Hilfsklassen benutzt, es wird Gebrauch von der InterfaceTechnik gemacht.
import java.awt.*;
import java.applet.*;
public class Thread2Intflott extends Applet implements Runnable {
//Globale Variablendeklarationen:
Thread th1, th2; //Jetzt sind die beiden Instanzen von Thread!
TextArea ausgabeTA;
int verzoegerung;
public void init() {
ausgabeTA = new TextArea("",20,50,TextArea.SCROLLBARS_BOTH);
add(ausgabeTA);
ausgabeTA.setBackground(Color.white);
ausgabeTA.setEditable(false);
th1 = new Thread(this,"th1");
th2 = new Thread(this,"th2");
th1.start(); th2.start();
} //Ende von init-Methode
public void run() {
for (int i=1; i<7; i++) {
try {
verzoegerung = (int)(Math.random()*1000);
Thread.currentThread().sleep(verzoegerung);
ausgabeTA.append(
i +": " + Thread.currentThread().getName()+"
Verzoegerung=" + verzoegerung+"\n"
);
} //Ende try
catch (InterruptedException e){}
} //Ende for
ausgabeTA.append(
"Ende von : "+Thread.currentThread().getName()+"\n"
);
}//Ende von run
}//Ende des ganzen Applets
copyleft:munz
89
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Es entstehen ähnlich unvorhersagbare Rennergebnisse wie im Fall des ersten Applets mit den Hilfsklassen, z.B.:
Will man erreichen, dass ein Thread eine Methode erst „abarbeiten“ kann bevor ein anderer sie
„betritt“, muss man die Methode mit einem sog. „Monitor“ schützen. Das Java-Schlüsselwort dazu ist
synchronized (diese Methode wird bei „Multitasking“ oft angewandt, um so genannte „Dead-Locks“
zu vermeiden, d.h., dass ein Prozess, der im Besitz eines Betriebsmittels ist, auf einen zweiten Prozess warten muss, dieser aber genau das von Prozess 1 besetzte Betriebsmittel braucht und auf dessen Freigabe wartet). Hier dazu die Klasse:
import java.awt.*;
import java.applet.*;
public class Thread2IntflottSync extends Applet implements Runnable {
//Globale Variablendeklarationen:
//Diese Hilfsklasse muss im gleichen Verzeichnis stehen!
Thread th1,th2;
TextArea ausgabeTA;
int verzoegerung;
public void init() {
ausgabeTA= new TextArea("", 20, 50, TextArea.SCROLLBARS_BOTH);
add(ausgabeTA);
ausgabeTA.setBackground(Color.white);
ausgabeTA.setEditable(false);
th1=new Thread(this,"th1");
th2=new Thread(this,"th2");
th1.start(); th2.start();
}//Ende von init-Methode
copyleft:munz
90
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public synchronized void run() {
for (int i=1; i<7; i++) {
try {
verzoegerung=(int)(Math.random()*1000);
Thread.currentThread().sleep(verzoegerung);
ausgabeTA.append(
i+": "+Thread.currentThread().getName()+"
Verzoegerung="+verzoegerung+"\n"
}//Ende try
catch (InterruptedException e){}
}//Ende for
ausgabeTA.append(
"Ende von : "+Thread.currentThread().getName()+"\n"
);
}//Ende von run
}//Ende des ganzen Applets
Da th1 zuerst gestartet wird, ergibt sich:
Im letzten Beispiel dieses Abschnittes wird „ping-pong-artig“ von zwei Threads in das Ausgabetextfeld
geschrieben, synchronized schützt die zwei Methoden ping und pong, wait() zwingt den
Thread, der den Monitor hat, zur Abgabe desselben und zum Warten, bis ein anderer Thread über
notifyAll() alle wartenden Threads benachrichtigt, dass sie nun weiterlaufen dürfen.
import java.awt.*;
import java.applet.*;
public class Thread2Intsync2 extends Applet implements Runnable {
//Globale Variablendeklarationen:
Thread th1, th2;
boolean flag = true;
TextArea ausgabeTA;
int verzoegerung;
public void run() {
los();
}
copyleft:munz
91
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public void init() {
ausgabeTA= new TextArea("", 20, 50, TextArea.SCROLLBARS_BOTH);
add(ausgabeTA);
ausgabeTA.setBackground(Color.white);
ausgabeTA.setEditable(false);
th1=new Thread(this, "th1");
th2=new Thread(this, "th2");
th1.start();
th2.start();
}//Ende von init-Methode
public void los() {
for (int i = 0; i < 7; i++) {
if (flag) ping(i);
else pong(i);
}//Ende for
ausgabeTA.append(
"Ende von : " + Thread.currentThread().getName() + "\n"
);
}//Ende von los
public synchronized void ping(int i) {
try {
flag = !flag;
verzoegerung=(int)(Math.random()*1000);
Thread.sleep(verzoegerung);
ausgabeTA.append(
"ping: " + i + ": " +
Thread.currentThread().getName() +
" Verzoegerung=" + verzoegerung + "\n"
);
wait();
}
catch (InterruptedException e){}
}
public synchronized void pong(int i) {
try {
flag =!flag;
verzoegerung=(int)(Math.random()*1000);
Thread.sleep(verzoegerung);
ausgabeTA.append(
"pong: " + i + ": " +
Thread.currentThread().getName() +
" Verzoegerung=" + verzoegerung + "\n"
);
notifyAll();
}
catch (InterruptedException e){}
}
}//Ende des ganzen Applets
copyleft:munz
92
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Ein Ergebnis:
copyleft:munz
93
Fachschulen Lörrach
5
5.2
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Graphisch animierte Applets mit Threads
Wir entscheiden uns für die Interface-Methode, da in allen Beispielen ein Thread genügt, um das
Wesentliche zu demonstrieren. Generell lassen wir die run-Methode des Threads am Ende
repaint() aufrufen, also das Applet neu zeichnen (repaint ruft nun die Applet-Methode update
auf.) Um störendes Flackern bei schneller Animation zu vermeiden, muss stets diese update-Methode des Applets überschrieben werden, da die sonst geerbte update-Methode den Bildschirm erst
löscht und dann paint aufruft. Des Weiteren bedienen wir uns des sog. Double-Buffering d.h. wir
stellen die Zeichnung komplett im Speicher fertig und geben sie erst dann mit drawImage auf einen
Schlag auf die Appletfläche. Dieser unsichtbare Pufferbereich im Speicher heißt im folgenden Applet
offImage, offGraphics der zugehörige Grafikkontext.
Hinweis: Bei allen AWT-Anwendungen ist die Behandlung des Double-Bufferings vom Programmierer
selbst zu leisten. Klassen, die von Swing abgeleitet sind, unterstützen Double-Buffering automatisch.
import java.awt.*;
import java.applet.*;
public class Animacon extends Applet implements Runnable {
Thread mythread=null;
int dx, dy;
int verzoegerung;
int rot, gruen, blau;
int i=0;
Graphics offGraphics;
Image offImage;
public void init() {
dx=size().width;
dy=size().height; //Appletgröße holen!
verzoegerung=10;
//Speicherbereich für double-buffering:
offImage = createImage(dx, dy);
offGraphics = offImage.getGraphics();
//dazu gehöriges Graphics-Objekt
offGraphics.setColor(Color.black);
offGraphics.fillRect(0, 0, dx, dy);
}
public void start() {
// die start-Methode des Applets wird
// zum Start des Threads benutzt!
if(mythread == null) {
mythread = new Thread(this);
mythread.start();
}
}
public void stop() {
//die stop-Methode des Applets wird
// zum Beenden des Threads benutzt!
mythread = null;
offImage = null;
offGraphics = null;
}
public void run() {
while (mythread != null) {
try {
Thread.sleep(verzoegerung);
}
catch (InterruptedException e) {}
repaint(); //Neu zeichnen!
}
}
copyleft:munz
94
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
}
Java
public void paint(Graphics g) {
update(g);
}
public void update (Graphics g) {
//dieses update überschreibt das vorhandene update,
//das den Bildschirm löschen würde und Flackern erzeugte!
rot = (int)(Math.random()*256);
gruen = (int)(Math.random()*256);
blau = (int)(Math.random()*256);
//Würfeln der neuen Farbbytes im RGB-Modell
offGraphics.setColor(new Color(rot,gruen,blau));
//Daraus die neue Farbe für offGraphics bilden
//4 Linien zeichnen
//Danach i erhöhen, um die Punkte zu verändern!
offGraphics.drawLine(
i, 0, dx-i, dy);
offGraphics.drawLine(dx-i, 0,
i, dy);
offGraphics.drawLine(0,
i, dx, dy-i);
offGraphics.drawLine(0, dy-i, dx,
i);
i += 4; if(i > dx) i = 0; if(i > dy) i = 0;
if(i == 0){
offGraphics.setColor(Color.black);
offGraphics.fillRect(0, 0, dx, dy);
}
//Bild immer wieder von den vielen Linien befreien,
// wenn i von vorne beginnt!
//i zurücksetzen, falls über Rand hinaus!
g.drawImage(offImage, 0, 0, this);
//Die fertige Zeichnung auf einen Schlag
// im Appletfenster sichtbar machen!
}
Das etwas aufwendigere Animacon2-Applet gestattet,die Verzögerung während des Thread-Laufes
vom Benutzer ändern zu lassen:
import java.awt.*;
import java.applet.*;
public class Animacon2 extends Applet implements Runnable {
Thread mythread=null;
int dx, dy;
int verzoegerung;
double verzdouble;
int rot, gruen, blau;
int i=0;
Graphics offGraphics;
Image offImage;
String verzkette = "10";
TextField tf=new TextField(verzkette);
Label lb;
copyleft:munz
95
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public void init() {
dx=size().width;
dy=size().height; //Appletgröße holen!
verzoegerung=10;
setLayout(new FlowLayout());
lb = new Label("Verzögerung:");
add(lb);
add(tf);
offImage = createImage(dx, dy);
//Speicherbereich für double-buffering
offGraphics = offImage.getGraphics();
//dazu gehöriges Graphics-Objekt
offGraphics.setColor(Color.black);
offGraphics.fillRect(0, 0, dx, dy);
}
public void start() {
// die start-Methode des Applets wird
// zum Start des Threads benutzt!
if(mythread == null) {
mythread = new Thread(this);
mythread.start();
}
}
public void stop() {
// die stop-Methode des Applets wird
// zum Beenden des Threads benutzt!
mythread = null;
offImage = null;
offGraphics = null;
}
public void run() {
while (mythread != null) {
verzkette = tf.getText();
try {
verzdouble=Double.valueOf(verzkette).doubleValue();
verzdouble = Math.abs(verzdouble);
//Abfangen negativer Eingaben
verzoegerung=(int)verzdouble;
}
catch(NumberFormatException nfe) {
verzoegerung=10;
tf.setText("10");
} //Abfangen unsinniger Eingabeformate!
try {
Thread.sleep(verzoegerung);
}
catch (InterruptedException e) {}
repaint();//Neu zeichnen!
}
}
public void paint(Graphics g) {
update(g);
}
copyleft:munz
96
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
}
Java
public void update (Graphics g) {
// dieses update überschreibt das vorhandene update,
// das den Bildschirm löschen würde und Flackern erzeugte!
rot = (int)(Math.random()*256);
gruen = (int)(Math.random()*256);
blau = (int)(Math.random()*256);
// Würfeln der neuen Farbbytes im RGB-Modell
offGraphics.setColor(new Color(rot,gruen,blau));
// Daraus die neue Farbe für offGraphics bilden
// 4 Linien zeichnen
// Danach i erhöhen, um die Punkte zu verändern!
offGraphics.drawLine(
i, 0, dx-i, dy);
offGraphics.drawLine(dx-i, 0,
i, dy);
offGraphics.drawLine(0,
i, dx, dy-i);
offGraphics.drawLine(0, dy-i, dx,
i);
i += 4; if(i > dx) i = 0; if(i > dy) i = 0;
if(i == 0) {
offGraphics.setColor(Color.black);
offGraphics.fillRect(0, 0, dx, dy);
}
// Bild immer wieder von den vielen Linien befreien,
// wenn i von vorne beginnt!
// i zurücksetzen, falls über Rand hinaus!
g.drawImage(offImage, 0, 0, this);
// Die fertige Zeichnung auf einen Schlag
// im Appletfenster sichtbar machen!
}
Aufgabe 26: Erweitern Sie obiges Applet so, dass das i-Inkrement vom Benutzer während des Laufes änderbar ist. (hier ist es fest auf 4 gesetzt wg. i += 4;)
copyleft:munz
97
Fachschulen Lörrach
5
5.3
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Graphisch animierte Applets mit Threads und Turtlegraphik
Die seit LOGO-Zeiten beliebte Turtlegraphik wird im folgenden Abschnitt und im Kapitel über Rekursion eingesetzt, um mit einfachen Algorithmen höchst komplizierte und faszinierende Graphiken interaktiv und animiert entstehen zu lassen. Die Hilfsklasse Turtle ist für Pufferbereiche also DoubleBuffering konzipiert. Durch eine kleinere Änderung im Konstruktor lässt sich ihr aber ebensogut gleich
das Graphics-Objekt als Parameter übergeben, damit wäre sie dann auch ohne Benutzung eines
Pufferbereiches brauchbar.
Hier der Text von Turtle.java:
import java.awt.*;
class Turtle {
protected double posX, posY;
protected double winkel;
protected Color farbe = Color.black;
protected Image offImage;
protected double urX, urY;
protected boolean stiftUnten;
protected Graphics g;
public Turtle(
Image offImage, double x, double y, double richtung) {
posX=x; posY=y; winkel=richtung;
urX=x; urY=y;
g=offImage.getGraphics();
stiftUnten = true;
}
public void zumAnfang() {
posX=urX; posY=urY;
}
public void vor(double l)
g.setColor(farbe);
double neuX = posX + Math.cos(bogen(winkel))*l;
double neuY = posY - Math.sin(bogen(winkel))*l;
//- wg. y-Achse falsch rum!!
if (stiftUnten)
g.drawLine((int) posX, (int) posY,
(int) neuX, (int) neuY);
posX = neuX; posY = neuY;
}
public void drehe(double grad) {
winkel += grad;
}
public void fuelleKreis(double radius) {
int r = (int) (radius+0.5);
g.setColor(farbe);
g.fillOval((int) (posX -radius), (int) (posY-radius),
2*r, 2*r
);
}
public void inRichtung(double grad) {
winkel = grad;
}
public void geheZu(double neuX,double neuY) {
g.setColor(farbe);
if (stiftUnten)
g.drawLine((int) posX, (int) posY,
(int) neuX, (int) neuY
);
posX = neuX; posY = neuY;
}
copyleft:munz
98
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public void stiftfarbe(Color c) {
farbe = c;
}
public boolean stiftHoch() {
stiftUnten=false;
return false;
}
public boolean stiftAb() {
stiftUnten=true;
return true;
}
public double liesRichtung () {
return winkel;
}
public double liesX() {
return posX;
}
public double liesY() {
return posY;
}
public Color liesFarbe() {
return (Color)farbe;
}
private double bogen(double winkel) {
return winkel*Math.PI/180;
}
}//Ende von Turtle
Diese Hilfsklasse wird vom folgenden Applet eingesetzt, um mit einem Objekt t vom Typ Turtle ein
gleichseitiges Dreieck zu zeichnen, dessen Seitenlänge sich während der Animation ändert. Klickt
man mit der Maus irgendwo auf die Appletfläche, hält die Animation an.
import java.awt.*;
import java.applet.*;
public class MaleTurtle1 extends Applet implements Runnable {
Thread thread=null;
int dx, dy;
int verzoegerung;
int j;
int rot, gruen, blau;
boolean idle=false;
double delay;
String kette="Dreieck mit Turtle!";
String delkette="150";
TextField tf=new TextField(kette);
TextField tff=new TextField(delkette);
Label lb,lbb;
Graphics offGraphics;
Image offImage;
Turtle t;//Turtleklasse
copyleft:munz
99
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public void init() {
dx=size().width;
dy=size().height;
verzoegerung = 150;
setLayout(new FlowLayout());
lb=new Label("Ihr Text:");
add(lb);
add(tf);
lbb=new Label("Verzögerung:");
add(lbb);
add(tff);
j=0;
setBackground(Color.blue);
idle=false;
offImage=createImage(dx, dy);
offGraphics=offImage.getGraphics();
offGraphics.setColor(Color.green);
offGraphics.fillRect(0, 0, dx+1, dy+1);
t=new Turtle(offImage,100,300,0);
}
public void start() {
if(thread == null) {
thread = new Thread(this);
thread.start();
}
}
public void stop() {
thread = null;
offImage = null;
offGraphics = null;
}
public void run() {
while (thread != null) {
kette = tf.getText();
delkette = tff.getText();
try {
delay = Double.valueOf(delkette).doubleValue();
delay = Math.abs(delay); //Abfangen negativer Eingaben
verzoegerung=(int)delay;
}
catch(NumberFormatException nfe) {
verzoegerung=100;tff.setText("100");
}
try {
Thread.sleep(verzoegerung);
}
catch (InterruptedException e) {}
offGraphics.setColor(Color.black);
offGraphics.fillRect(0, 0, dx+1, dy+1);
repaint();
}
}
public void paint(Graphics g) {
update(g);
}
public boolean mouseDown(Event s, int x, int y) {
if(idle) thread.resume();
else thread.suspend();
idle = !idle;
return true;
} //Veraltet, geht aber ohne MouseListener-Interface!
copyleft:munz
100
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
}
Java
public void update (Graphics g) {
rot = (int)(Math.random()*256);
gruen = (int)(Math.random()*256);
blau = (int)(Math.random()*256);
offGraphics.setColor(new Color(rot, gruen, blau));
offGraphics.drawString(kette, dx/2, dy/2);
rot = (int)(Math.random()*256);
gruen = (int)(Math.random()*256);
blau = (int)(Math.random()*256);
offGraphics.setColor(new Color(rot, gruen, blau));
j++; if(j >= 8) j = 0;
offGraphics.drawString(
"TurtleMalen Längenwert : "+
String.valueOf(j), dx/10, dy/8
);
t.stiftfarbe(offGraphics.getColor());
for(int i=0; i<3; i++) {
t.vor(50+j*12); t.drehe(120);
}
t.stiftHoch(); t.zumAnfang(); t.stiftAb();
rot = (int)(Math.random()*256);
gruen = (int)(Math.random()*256);
blau = (int)(Math.random()*256);
offGraphics.setColor(new Color(rot, gruen, blau));
offGraphics.drawString(
"Klicken hält an! Nochmal klicken startet wieder!",
dx/20, 9*dy/10
);
g.drawImage(offImage, 0, 0, this);
}
mouseDown ist zwar etwas veraltet (deprecated) verlangt aber nicht noch einen
MouseMotionListener o.ä. zu implementieren.
Im folgenden Applet ist eine Schaltfläche zum Anhalten des Threads eingebaut worden, die ist zwar
nicht deprecated, verlangt aber die Einbindung von ActionListener als zusätzlichem Interface:
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class MaleTurtle2 extends Applet
implements Runnable, ActionListener {
Button b1 = new Button("Anhalten!");
Thread meinthread=null;
int dx, dy;
int verzoegerung;
int j;
boolean gestoppt=false;
double delay;
String kette="Dreieck mit Turtle!";
String delkette="150";
TextField tf=new TextField(kette);
TextField tff=new TextField(delkette);
Label lb,lbb;
Graphics offGraphics;
Image offImage;
Turtle t;//Turtleklasse
copyleft:munz
101
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public void init() {
dx=size().width;
dy=size().height;
verzoegerung = 150;
setLayout(new FlowLayout());
lb=new Label("Ihr Text:");
add(lb);
add(tf);
lbb=new Label("Verzögerung:");
add(lbb);
add(tff);
add(b1);b1.addActionListener(this);
j=0;
offImage=createImage(dx, dy);
offGraphics=offImage.getGraphics();
offGraphics.setColor(Color.green);
offGraphics.fillRect(0, 0, dx+1, dy+1);
t = new Turtle(offImage,100,300,0);
}
public void start() {
if(meinthread == null) {
meinthread = new Thread(this);
meinthread.start();
}
}
public void stop() {
meinthread = null;
offImage = null;
offGraphics = null;
}
public void run() {
while (meinthread != null) {
kette = tf.getText();
delkette = tff.getText();
try {
delay=Double.valueOf(delkette).doubleValue();
delay = Math.abs(delay);
//Abfangen negativer Eingaben
verzoegerung=(int)delay;
}
catch(NumberFormatException nfe) {
verzoegerung=100;tff.setText("100");
}
try {
meinthread.sleep(verzoegerung);
}
catch (InterruptedException e) {}
offGraphics.setColor(Color.black);
offGraphics.fillRect(0, 0, dx+1, dy+1);
repaint();
}
}
public void paint(Graphics g) {
update(g);
}
copyleft:munz
102
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
}
copyleft:munz
Java
public void actionPerformed(ActionEvent e) {
if(gestoppt) {
meinthread.resume();
b1.setLabel("Anhalten!");
}
else {
meinthread.suspend();
b1.setLabel("Los!");
}
gestoppt =! gestoppt;
}
public void neueFarbe(Graphics gg) {
int rot, gruen, blau;
rot = (int)(Math.random()*256);
gruen = (int)(Math.random()*256);
blau = (int)(Math.random()*256);
gg.setColor(new Color(rot,gruen,blau));
}
public void update (Graphics g) {
neueFarbe(offGraphics);
offGraphics.drawString(kette,dx/2,dy/2);
neueFarbe(offGraphics);
j++; if(j >= 8) j = 0;
offGraphics.drawString(
"TurtleMalen Längenwert : "+ String.valueOf(j),
dx/10, dy/8
);
t.stiftfarbe(offGraphics.getColor());
for(int i = 0; i < 3; i++) {
t.vor(50+j*12); t.drehe(120);
}
t.stiftHoch();t.zumAnfang();t.stiftAb();
neueFarbe(offGraphics);
g.drawImage(offImage, 0, 0, this);
}
103
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Aufgabe 27: Schreiben Sie eine eigene Methode für das Dreieck z.B. als:
public void malegleichseitigesDreieck(double seitenlaenge),
sie zeichnet das gleichseitige Dreieck mit seitenlaenge.
Rufen Sie die Methode danach in update an geeigneter Stelle auf:
malegleichseitigesDreieck(i+10); o.ä.
Aufgabe 28: Schreiben Sie eine Methode
public void maleregelmaessiges6Eck(double seitenlaenge),
sie zeichnet das regelmäßige 6-Eck mit seitenlaenge.
Bauen Sie sie geeignet in update ein.
Aufgabe 29: Schreiben Sie eine Methode
public void maleregelmaessigesNEck(int n, double seitenlaenge),
sie zeichnet das regelmäßige n-Eck mit seitenlaenge.
Bauen Sie sie geeignet in update ein.
Aufgabe 30: Eine Methode zum Zeichnen einer Quadratrosette ist zu schreiben und in update
einzubauen:
public void maleQuadratRosette(
double seitenlaenge, double winkel),
sie zeichnet ein Quadrat mit seitenlaenge, dann dreht sich die Turtle um winkel
zeichnet wieder ein Quadrat mit seitenlaenge, usw. bis der Gesamtdrehwinkel ein
Vielfaches von 360 Grad ist (in Java: gesamtdrehwinkel % 360 ==0 abfragen!).
Aufgabe 31: Eine Methode zum Zeichnen eines Polygons ist zu schreiben und in update einzubauen:
public void malePolygon(double seitenlaenge, double winkel),
sie zeichnet eine Strecke mit seitenlaenge, dann dreht sich die Turtle um winkel,
zeichnet wieder eine Strecke mit seitenlaenge, usw. bis der Gesamtdrehwinkel ein
Vielfaches von 360 Grad ist in (Java: gesamtdrehwinkel % 360 == 0 abfragen!).
Aufgabe 32: Eine Polygonspirale entsteht, wenn man in obiger Aufgabe in jedem Schritt die Seitenlänge um einen festen Wert erhöht (ausprobieren!).
Aufgabe 33: Spirolatertalkurven aus einem Bundeswettbewerb Informatik lassen sich ebenfalls noch
gut nicht-rekursiv (also nur mit Schleifen) programmieren. Man kann sie aus mehreren
Einzelhaken zusammensetzen oder sie auch in einer einzigen Methode ausprogrammieren.
Man gibt ein: int anzahl, double winkel, double startLaenge.
Dann geschieht folgendes:
Die Turtle zeichnet eine Strecke der Länge startLaenge, dreht sich um winkel,
zeichnet eine Strecke der Länge 2*startLaenge, dreht sich um winkel,
zeichnet eine Strecke der Länge 3*startLaenge, dreht sich um winkel, usw.
zeichnet eine Strecke der Länge anzahl*startLaenge, dreht sich um winkel.
Jetzt ist der erste „Haken“ fertig!
Jetzt wird der Gesamtdrehwinkel der Turtle gegenüber dem Zustand vor dem ersten
Aufruf berechnet! Ergebnis zu (int) casten!
Solange dieser Gesamtdrehwinkel kein Vielfaches von 360 Grad ist, wird wieder ein
Haken wie oben gezeichnet und dann der Gesamtdrehwinkel der Turtle gegenüber
dem Zustand vor dem ersten Aufruf erneut berechnet (immer zu (int) casten)!
(Ende der „Solange-Anweisungen“!)
copyleft:munz
104
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Ein Bild für winkel=120, anzahl=5:
Aufgabe 34: Erweitern Sie Ihre bisherigen Applets um diese Spirolateralkurven.
copyleft:munz
105
Fachschulen Lörrach
5
5.4
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Rekursion
Obwohl Rekursion oft als schwer verständliche Lösungstechnik gilt ist sie dann einfach zu verstehen
und in Hierarchien fast selbstverständliche Alltagstechnik, wenn man sich nicht bemüht zu verstehen,
wie sie rechnerintern verwaltet wird. Hart an der Grenze der Paradoxie gelegen, eine Sache durch
sich selbst zu definieren, vermeidet sie diese haarscharf dadurch, dass sie eine Sache nicht durch
sich selbst sondern durch eine einfachere Version ihrer selbst und den allereinfachsten Fall, den „Basisfall“ erklärt (vollständige Induktion!).
Will man also ein Problem P(n), n natürliche Zahl, rekursiv lösen, muss man zwei Dinge angeben:
den Basisfall P(0) oder P(1) den „Induktionsanfang“
die Zurückführung von P(n) auf P(n-1), den „Induktionsschritt“.
Das nächste Beispiel zeigt, wie die Verantwortung nach unten durchgereicht wird und anschließend
die Erfolgsmeldungen von unten wieder hochgereicht werden:
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class Arbeiten extends Applet implements ActionListener {
//Globale Variablendeklarationen:
Button b1=new Button("Problem lösen!");
TextField eingabefeld;
TextArea ausgabeTA; //TextFläche für Ausgabestrings!
Label lb;
String s1; //Globaler String
double azahl;
int anzahl,klickzahl;
public void init() {
eingabefeld=new TextField(3);
lb= new Label("Dienstgrad:");
add(lb);
add(eingabefeld);
add(b1); b1.addActionListener(this);
//Button auf Appletfläche und dann Lauscher ansetzen!
klickzahl=0;
s1="3";
eingabefeld.setText(s1);
ausgabeTA= new TextArea("",10,50,TextArea.SCROLLBARS_BOTH);
add(ausgabeTA);
ausgabeTA.setBackground(Color.white);
ausgabeTA.setEditable(false);
} //Ende von init-Methode
public void erledigeArbeit (int dienstgrad){
//Weitergabe der Drecksarbeit rekursiv!
if(dienstgrad == 0) { //Basisfall! Der Gemeine machts!
ausgabeTA.append(
"Hurra! Basisfall endlich von mir erledigt!"+"\n"
);
} //Ende Basisfall
else { //Rekursionsfall!Weitergabe der Drecksarbeit!
ausgabeTA.append(
"Von: "+dienstgrad+" an: " +(dienstgrad-1)+
" Arbeit erledigen!"+"\n"
);
erledigeArbeit(dienstgrad-1); //Rekursiver Aufruf!!!
ausgabeTA.append(
"Von: "+(dienstgrad-1)+" an: " +dienstgrad+
"Es ist jetzt vollbracht!"+"\n"
);
} //Ende else
} //Ende erledigeArbeit
copyleft:munz
106
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public void actionPerformed(ActionEvent e) {
klickzahl++;
s1 = eingabefeld.getText();
azahl = Double.valueOf(s1).doubleValue();
anzahl = (int)azahl;
repaint();
} //Am Ende wird neu gezeichnet!!
public void paint (Graphics g) {
if(klickzahl > 0)
erledigeArbeit(anzahl);
} //Ende von paint
} //Ende des ganzen Applets
Natürlich kann man auch wieder n! rekursiv berechnen usw., Rekursion zusammen mit Graphik
scheint uns aber interessanter.
Ein Musterbeispiel für graphische Rekursion ist die Kochkurve:
Alles beginnt mit einer Linie der Länge L („Initiator“,Rekursionsstufe 0),
der „Generator“ (Rekursionsstufe 1) besteht aus 4 Linien der Länge L/3 die mit der Turtle so zu zeichnen sind:
Initiator mit L/3 zeichnen, Linksdrehung 60 Grad, Initiator mit L/3 zeichnen, Rechtsdrehung 120 Grad,
Initiator mit L/3 zeichnen, Linksdrehung 60 Grad, Initiator mit L/3 zeichnen.
Der Generator ist unten abgebildet.
Damit ist die Rekursionsregel klar:
koch(n-1,L/3); links(60); koch(n-1,L/3); rechts(120);
koch(n-1,L/3); links(60); koch(n-1,L/3);
Der Basisfall ist der Generator: vorwärts(L);
copyleft:munz
107
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Im folgend abgedruckten Beispiel wird die Kochkurve mittels Threads und mit Hilfe unserer Hilfsklasse
Turtle aus dem Thread-Kapitel animiert und farbig angezeigt.
import
import
import
public
java.awt.*;
java.awt.event.*;
java.applet.*;
class KochKurve extends Applet
implements Runnable, ActionListener {
Button b1=new Button("Anhalten!");
Thread meinthread=null;
int dx, dy;
int verzoegerung;
int j,grad;
boolean gestoppt=false;
double delay,startlaenge;
String kette="Kochkurven mit Turtle!";
String delkette="150",startlaengekette="243";
TextField tf=new TextField(kette);
TextField tff=new TextField(delkette);
TextField tf5=new TextField(startlaengekette);
Label lb,lbb,lb5;
Graphics offGraphics;
Image offImage;
Turtle t; //Turtleklasse
copyleft:munz
108
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public void init() {
dx=size().width;
dy=size().height;
verzoegerung = 150;
setLayout(new FlowLayout());
lb=new Label("Ihr Text:");
add(lb);
add(tf);
lbb=new Label("Verzögerung:");
add(lbb);
add(tff);
add(b1);b1.addActionListener(this);
lb5=new Label("Startlänge:");
add(lb5);
add(tf5);
j=0;grad=0;
offImage=createImage(dx, dy);
offGraphics=offImage.getGraphics();
offGraphics.setColor(Color.green);
offGraphics.fillRect(0, 0, dx+1, dy+1);
t = new Turtle(offImage,100,200,0);
}
public void start() {
if(meinthread == null) {
meinthread = new Thread(this);
meinthread.start();
}
}
public void stop() {
meinthread = null;
offImage = null;
offGraphics = null;
}
copyleft:munz
109
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public void run() {
while (meinthread != null) {
kette = tf.getText();
delkette = tff.getText();
try {
delay = Double.valueOf(delkette).doubleValue();
delay = Math.abs(delay);
//Abfangen negativer Eingaben
verzoegerung=(int)delay;
}
catch(NumberFormatException nfe) {
verzoegerung=100;tff.setText("100");
}
startlaengekette = tf5.getText();
try {
startlaenge =
Double.valueOf(startlaengekette).doubleValue();
}
catch(NumberFormatException nfe) {
startlaenge=25;tf5.setText("25");
}
try {
meinthread.sleep(verzoegerung);
}
catch (InterruptedException e) {}
offGraphics.setColor(Color.black);
offGraphics.fillRect(0, 0, dx+1, dy+1);
repaint();
} //Ende while
} //Ende run
public void paint(Graphics g) {
update(g);
}
public void actionPerformed(ActionEvent e) {
if(gestoppt) {
meinthread.resume();
b1.setLabel("Anhalten!");
}
else {
meinthread.suspend();
b1.setLabel("Los!");
}
gestoppt=!gestoppt;
}
public void neueFarbe(Graphics gg) {
int rot, gruen, blau;
rot = (int)(Math.random()*256);
gruen = (int)(Math.random()*256);
blau = (int)(Math.random()*256);
gg.setColor(new Color(rot,gruen,blau));
}
copyleft:munz
110
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
public void maleKoch(int stufe,double laenge) {
if (stufe == 0){ //Basisfall!
t.vor(laenge);
}
else {
//Rekursionsfall,von n auf n-1
//den schwarzen Peter weitergeben!
maleKoch(stufe-1,laenge/3);
t.drehe(60);
maleKoch(stufe-1,laenge/3);
t.drehe(-120);
maleKoch(stufe-1,laenge/3);
t.drehe(60);
maleKoch(stufe-1,laenge/3);
} //Ende else
} // Ende maleKoch
public void update (Graphics g) {
neueFarbe(offGraphics);
offGraphics.drawString(kette,dx/2,dy/2);
neueFarbe(offGraphics);
j++; if(j >= 8) j = 0;
offGraphics.drawString(
"KochKurve malen Längenwert : "+ String.valueOf(j),
dx/10, dy/7
);
t.stiftfarbe(offGraphics.getColor());
maleKoch(grad,startlaenge+j*2);
grad++; if(grad > 5) grad = 0;
t.stiftHoch(); t.zumAnfang(); t.stiftAb();
neueFarbe(offGraphics);
g.drawImage(offImage, 0, 0, this);
} //Ende update
} //Ende Applet
copyleft:munz
111
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Aufgabe 35: Ändern Sie das Applet so ab, dass die Kreuzstichkurve gezeichnet wird. Diese hat den
gleichen Initiator wie die Kochkurve, den Generator entnehmen Sie der Abbildung:
copyleft:munz
112
Fachschulen Lörrach
5
Threads und Rekursionen – ein alternativer Zugang mit Applets
Java
Aufgabe 36: Bilden Sie aus der Kochkurve die „Schneeflockenkurve“, die aus drei Kochkurven besteht, die zu einem „gleichseitigen Dreieck“ hintereinander gelegt werden.
Die folgende Abb. zeigt eine höhere Rekursionsstufe.
Es gibt nicht nur rekursiv definierte Methoden sondern auch ebenso definierte Klassen. Dies benötigt
man für die Implementierung von Listen, Stacks, Bäumen u.ä. (in Sprachen wie Pascal hieß dies „abstrakte Datentypen“ oder „dynamische Datentypen“, in Java gibt es keine Datentypen mehr nur noch
Klassen). Diese Fragestellungen werden in einem eigenen Kapitel eingehender untersucht werden.
copyleft:munz
113
Fachschulen Lörrach
6
Abstrakte Datentypen
6
Abstrakte Datentypen
Java
Jedem der sich mit Informatik beschäftigt, werden irgendwann Begriffe wie Liste, Stack oder Baum begegnen. Bei diesen Begriffen handelt es sich um sog. abstrakte Datentypen. Der Stack, oder auch
Keller genannt, ist so wichtig, dass er in Java unter dem Namen Stack als Klasse standardmäßig
implementiert ist. Wir wollen uns den Datentyp Liste genauer anschauen und auch implementieren
und dabei Listentypen wie LIFO und FIFO kennen lernen.
6.1
Was ist eine Liste?
Eine erste Vorstellung von Listen kann man sich machen, wenn man sie von den Feldern abgrenzt.
int n = (int)(100*Math.random());
Object[] feld = new Object[n];
Die beiden Zeilen zeigen, wie vielfältig Felder in Java verwendet werden können. Sie können alle
möglichen Daten aufnehmen und ihre Größe ist insofern flexibel, dass erst zur Laufzeit bestimmt wird,
wie viele Eintragungen in das Feld vorgenommen werden können. Ist die Länge aber mal gesetzt,
wird das „Gebilde“ eher starr. Man kann das Feld nicht weiter wachsen lassen, wenn man erkennt,
dass noch einige Plätze nötig sind und man kann es nicht schrumpfen lassen, wenn man zu großzügig angelegt hat. Von Listen als abstrakte Datentypen verlangt man aber, dass sie mit dem Bedarf
8
wachsen und schrumpfen . Ein weiterer Unterschied zu den Feldern, bei denen man auf die einzelnen
Plätze mittels Platznummern zugreifen kann, besteht darin, dass Elemente einer Liste anders angeordnet sind. Es sind Gebilde, die zum einen einen Wert tragen und zum anderen auf das nächste Listenelement verweisen. Ein wahlfreier Zugriff auf einzelne Elemente ist nicht mehr möglich. Das unten
stehende Bild vermittelt einen Eindruck, wie man sich eine Liste aufgebaut vorstellen kann.
Wir modellieren zunächst ein Listenelement in Java; seinen Bauplan legen wir konsequenterweise in
der Klasse Element an.
An der Modellierung erkennt man, dass die Verkettung
eines Elementes in der Liste mit seinem Nachfolger dadurch geschieht, dass es ein Attribut hat, dessen Typ
selbst wieder ein Element ist und sein Wert nichts anderes ist, als eine Referenz auf ein neues ElementObjekt. Erinnern wir uns: Die Werte von Java-Variablen
sind, die primitiven Datentypen ausgenommen, immer
Referenzen. Ein Verzeigern, wie wir es von anderen
Sprachen gewohnt sind, entfällt, ja ist nicht einmal
möglich; aus Sicherheitsgründen haben die Entwickler
von Java ein explizites Referenzieren von Beginn an
ausgeschlossen.
8
Der in Java implementierte Datentyp Vector bietet hier größere Flexibilität
copyleft:munz
114
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
Das Implementieren der Klasse Element in Java ist nicht sehr schwierig, die Bezeichner von Attributen
und Methoden sind sprechend und sagen das Wesentliche aus. Die Klasse fügen wir dem Paket
abstraktDT hinzu.
package abstraktDT;
public class Element {
protected Object wert;
protected Element naechstes;
public Element() {
wert = null;
naechstes = null;
}
public Element(Object wert) {
setWert(wert);
naechstes = null;
}
public Object getWert() {
return wert;
}
public void setWert(Object wert) {
this.wert = wert;
}
public Element getNaechstes() {
return naechstes;
}
public void setNaechstes(Element naechstes) {
this.naechstes = naechstes;
}
}
Die Klasse hat zwei Konstruktoren. Mit Hilfe des ersten Konstruktors lassen sich Objekte der Klasse
Element erzeugen, die weder Werte noch Referenzen auf Nachfolger haben. Der zweite Konstruktor
dagegen erlaubt es, Element-Objekte zu erzeugen, die Werte haben. Referenzen auf Nachfolger gibt
es auch hier nicht. In beiden Fällen sind die Attributs-Werte von naechstes die Referenz null, „zeigen“ demnach auf kein konkretes Objekt9.
6.2
Die abstrakte Liste
Über eine Liste hat man solange Kontrolle, solange man den Kopf, sprich das erste Element kennt.
Ein Element, über das man einen direkten Zugriff auf die Liste hat,
könnte man Anker nennen. Anker werden auch benötigt, um die
zwei wesentlichen Operationen auf Listen auszuführen, nämlich das
Anhängen und das Ausgeben der Listenelemente. Wir modellieren
eine neue Klasse Liste, in der wir allerdings noch offen lassen wollen, wie wir anhängen. Das Anhängen kann nämlich auf zwei Arten
geschehen. Wie wir noch sehen werden, erhalten wir nämlich, je nachdem wie wir das Anhängen realisieren, eine LIFO – Liste oder eine FIFO – Liste. Deshalb legen wir das Anhängen in einer abstrakten
Methode an und machen damit Liste zu einer abstrakten Klasse. Auch sie kommt in das Paket
abstraktDT.
9
Da die Werte von noch nicht referenziierten Objekten standardmäßig null sind, hätte man auf die
Zeile naechstes = null; verzichten können; lediglich didaktische Gründe veranlassen uns, sie
stehen zu lassen.
copyleft:munz
115
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
Der Quelltext:
package abstraktDT;
public abstract class Liste {
protected Element kopf = null;
public void gibAus() {
Element lauf = kopf;
while (lauf != null) {
System.out.print(lauf.getWert()+ ", ");
lauf = lauf.getNaechstes();
}
System.out.println();
}
public abstract void haengeAn(Object wert);
}
Kommentare zum Quelltext:
Die Klasse Liste enthält, da kein anderer Konstruktor implementiert ist, den Standardkonstruktor
Liste(). In der Methode gibAus() wird lauf vom Typ Element deklariert und mit kopf initialisiert. Das Element-Objekt kopf spielt also die Rolle des Ankers. In der while-Schleife werden die
Werte der Elemente in der Liste solange ausgegeben, bis das letzte Listenelement erreicht ist, d.h. bis
naechstes auf null zeigt. Das ‚Durchhangeln’ durch die Liste geschieht in der Zeile:
lauf = lauf.getNaechstes();
Die Situation vor dem Abarbeiten der Zeile:
Die Situation nach dem Abarbeiten der Zeile:
copyleft:munz
116
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
Die Methode haengeAn() ist, wie schon angekündigt, abstrakt und muss von einer Klasse, die von
Liste erbt, implementiert werden.
6.3
ListeLIFO
LIFO steht für Last In First Out. Bei einer LIFO-Liste wird bei der Ausgabe immer das zuletzt eingegebene Element als erstes wieder ausgegeben. Dabei kann man Ausgaben sich ruhig verallgemeinert
als irgendwelche Jobs vorstellen. Eine LIFO-Liste lässt sich dann mit einem Stapel auf einem
Schreibtisch vergleichen. Neue Jobs werden oben aufgelegt und von oben wird auch abgearbeitet.
Der letzte Auftrag ist also der, der als erster erledigt wird. Wollte man diese Liste mit einem Motto beschreiben, so könnte dieses „Die Ersten werden die Letzten sein“ heißen.
Quelltext:
package abstraktDT;
public class ListeLIFO
extends Liste {
public void haengeAn(Object wert) {
Element neu = new Element(wert);
if (kopf != null)
neu.setNaechstes(kopf);
kopf = neu;
}
}
Das beim Aufruf der Methode haengeAn(..) übergebene Objekt wird im erzeugten Element-Objekt
neu abgelegt. Ist die Bedingung der if-Anweisung kopf !=null falsch, zeigt also der von Liste geerbte kopf auf null, und somit die Liste leer, so wird kopf auf neu gesetzt (kopf = neu;), die
Liste erhält ihren ersten Eintrag. Anders die Situation, wenn die Liste nicht mehr leer ist, dann werden
folgende Anweisungen durchgeführt:
neu.setNaechstes(kopf);
kopf = neu;
copyleft:munz
117
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
Schauen wir uns den Vorgang Schritt für Schritt an. Nach dem Erzeugen des Element-Objektes neu
haben wir die folgende Situation.
neu.naechstes zeigt noch auf null. Diese Referenz soll nun auf das Element kopf zeigen. Dies
geschieht mit neu.setNaechstes(kopf);
Schließlich soll kopf wieder das erste Element sein. Also benötigen wir auch hier die Programmzeile
kopf = neu;
copyleft:munz
118
Fachschulen Lörrach
6
6.4
Abstrakte Datentypen
Java
ListeFIFO
FIFO steht für First In First Out. Bei einer FIFO-Liste geschieht die Ausgabe in der gleichen Reihenfolge, wie die Eingabe. Ihr Motto könnte heißen: „Wer zuerst kommt, mahlt zuerst“. Eine FIFO-Liste
verwaltet man am besten mit zwei Ankern. Der erste ist wie gehabt der kopf, wir benötigen ihn zur
Ausgabe und mit ihm halten wir die Liste im „Griff“ und der zweite ist fuss, er ist das letzte Element,
sein Nachfolger ist immer null. Er markiert die Stelle, wo neue Elemente eingefügt werden sollen.
Wieder betrachten wir zuerst den Quelltext:
Package abstraktDT;
public class ListeFIFO extends Liste {
private Element fuss = null;
public void haengeAn(Object wert) {
Element neu = new Element(wert);
if (kopf == null) kopf = neu;
else fuss.setNaechstes(neu);
fuss = neu;
}
}
Wie schon bei ListeLIFO wird der übergebene Wert in ein neu erzeugtes Element-Objekt mit dem
Namen neu eingetragen. War die Liste zu diesem Moment noch leer, so bekommen kopf und fuss
die gleiche Referenz wie neu. Ist die Liste nicht leer, so haben wir vor dem Anhängen die folgende
Situation.
copyleft:munz
119
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
mit
fuss.setNaechstes(neu);
wird neu zum letzten Glied in der Liste:
Jetzt muss nur noch fuss zum letzten Eintrag werden.
fuss = neu;
copyleft:munz
120
Fachschulen Lörrach
6
6.5
Abstrakte Datentypen
Java
Listen-Demo
Mit einem einfachen Programm wollen wir die beiden Listen testen:
import abstraktDT.*;
public class ListenDemo {
public ListenDemo() {
ListeLIFO listeLIFO = new ListeLIFO();
ListeFIFO listeFIFO = new ListeFIFO();
for (int k = 0; k < 50; k++) {
Integer i = new Integer(k);
listeLIFO.haengeAn(i);
listeFIFO.haengeAn(i);
}
System.out.println("Ausgabe LIFO:");
listeLIFO.gibAus();
System.out.println();
System.out.println("Ausgabe FIFO:");
listeFIFO.gibAus();
}
public static void main (String args[]) {
new ListenDemo();
}
}
Bemerkungen:
Die ganzen Zahlen, die in die Listen eingetragen werden sollen, müssen als Integer-Objekte angelegt
werden, denn die Klasse Integer erbt von Object und ihre Instanzen können somit in die Listen
eingetragen werden. Werte vom Typ int können nicht in die Listen aufgenommen werden, da int
keine Klasse ist und damit nicht von Object erben kann.
Aufgabe 37: Implementieren Sie eine Klasse Person, mit den Attributen Name und Vorname, deren
Werte beim Aufruf des Konstruktors gesetzt werden. Weiter werde in dieser Klasse die
Methode toString() so überschrieben, dass
System.out.println(einePerson) Vor- und Zuname des Objektes einePerson
liefert. Passen Sie ListenDemo.java so an, dass Personen in die FIFO- und in die
LIFO-Listen eingetragen und wieder ausgegeben werden.
copyleft:munz
121
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
Das Bild zeigt einen Keller in UML-Notation:
Der Konstruktor erzeugt einen leeren Keller (vergleichbar einer Liste). Die
Methode
isEmpty() sagt, ob der Keller leer ist.
top() gibt das oberste Element aus (vgl. dem kopf in ListeLIFO).
push() legt das übergebene Objekt auf den Keller.
pop() nimmt das letzte Element vom Keller.
Implementieren Sie die Klasse Keller und fügen sie die Klasse in das
Paket abstraktDT ein. Zum Testen dieser Fachklasse verwenden Sie das einfache Testprogramm
KellerDemo.java.
import info1.*;
import abstraktDT.*;
public class KellerDemo{
static Keller k = new Keller();
public KellerDemo(){
boolean fertig = false;
int wahl;
Integer i;
while (!fertig){
System.out.println("isEmpty : 1");
System.out.println("top
: 2");
System.out.println("push
: 3");
System.out.println("pop
: 4");
System.out.println("fertig
: 5");
System.out.println();
wahl = Console.in.readInt();
switch(wahl){
case 1 : System.out.println(k.isEmpty());
break;
case 2 : System.out.println(
k.isEmpty()?"Keller leer":k.top());
break;
case 3 : System.out.print("Zahl: ");
i = new Integer(Console.in.readInt());
k.push(i);
break;
case 4 : if (!k.isEmpty()){
k.pop();
}
break;
case 5 : fertig = true;
break;
default: System.out.println("Falsche Eingabe");
}
}
}
public static void main(String[] args){
new KellerDemo();
}
}
Aufgabe 38: Schreiben Sie eine GUI-Klasse (mit Swing) für den Keller.
copyleft:munz
122
Fachschulen Lörrach
6
Abstrakte Datentypen
6.6
Java
Der abstrakte Datentyp Baum
In vielen Algorithmen, speziell Such- und Sortieralgorithmen, spielen solche Datentypen eine große
Rolle, die wir Bäume nennen. Bei unseren Betrachtungen beschränken wir uns auf binäre Bäume,
spezieller auf binäre Suchbäume. Wir werden sehen, dass diese geeignet sind, die in einem binären
Suchbaum untergebrachten Daten so auszugeben, dass die Daten anschließend sortiert sind.
6.6.1
Was ist ein Baum?
Ein binärer Baum ist entweder leer, oder hat folgende Gestalt:
Wurzel
B1
B2
Dabei sind B1 und B2 wieder binäre Bäume. Wir erkennen die Rekursion in der Definition.
Ein vollständiger Baum hat Knoten, in denen Werte stehen, und Kanten, die zwei Knoten miteinander
verbinden. Bei den Knoten unterscheiden wir 3 Arten: Die Wurzel, sie ist quasi der Start eines Baumes (rot markiert), die inneren Knoten (blau markiert) – von ihnen zeigt jeweils eine Kante nach
„oben“ und mindestens eine Kante nach „unten“ – und schließlich Blätter, das sind Knoten, von denen
nach unten keine Kanten zeigen. Zwei weitere Namen stehen für die Beziehung der Knoten untereinander. So nennen wir einen Knoten, von dem eine Kante nach unten zu einem weiteren Knoten zeigt,
Vater des nachrangigen Knotens, der seinerseits Sohn genant wird.
Wir geben uns eine 12-elementige Liste vor: [7, 12, 0, 5, 9, 2, 8, 1, 13, 10, 15, 3] und erzeugen einen
binären Baum mit 12 Knoten (die Knoten werden „in der Breite“, d. h. „zeilenweise von links nach
rechts“ aufgefüllt. Der Baum hat dann die folgende Gestalt:
7
1
4
12
0
2
3
5
5
6
9
1
13
10
15
3
8
9
10
11
12
2
7
8
Die Nummern neben bzw. unter den Knoten kennzeichnen die Reihenfolge der Erzeugung der Knoten
und ihre Belegung mit Werten.
copyleft:munz
123
Fachschulen Lörrach
6
Abstrakte Datentypen
6.6.2
Java
Binärer Suchbaum
Wir können die Zahlen aus der Liste [7, 12, 0, 5, 9, 2, 8, 1, 13, 10, 15, 3] auch nach folgender Regel in
einen Baum eintragen: Trage zunächst das erste Element in die Wurzel ein. Das nächste Element
kommt, wenn sein Wert kleiner ist als der Wert der Wurzel, in den Knoten, den man erreicht, wenn
man die linke Kante weiter geht, andernfalls kommt es in den rechten Knoten. So werden nacheinander alle Elemente der Liste abgearbeitet. Ist ein Knoten belegt, geht man eine Stufe tiefer, also in die
Knoten der „nächsten Generation“. Vor jedem Abstieg wird der Wert des einzutragenden Elements mit
dem Wert des Knotens auf die gleiche Weise verglichen und somit der Abstieg über die rechte bzw.
linke Kante gewählt. Das Bild unten zeigt den fertigen Baum.
7
1
0
12
3
2
4
6
5
5
2
1
3
8
12
9 13
9
8
10
15
7
10
11
Einen Baum, den wir auf die beschriebene Weise erstellt haben, nennen wir einen binären Suchbaum.
Die Definition eines binären Suchbaums lehnt sich sehr eng an die Definition eines binären Baums an:
• B ist leer oder
• Wenn B nicht leer ist gilt
o der rechte und der linke Unterbaum von B sind ebenfalls Binäre-Such-Bäume.
o Ist w der Wert in der Wurzel, so sind alle Werte in den Knoten des linken Unterbaums
kleiner als w und die Werte aller Knoten des rechten Unterbaums sind größer als w.
(Insbesondere kommt also ein Wert nur einmal in einem Binären-Such-Baum vor, es
sei denn man ersetzt größer durch größer gleich.
Der Grund, warum wir einen solchen Baum Suchbaum nennen, zeigt sich darin, dass das Auffinden
eines Wertes in einem Suchbaum sehr effizient ist. Um einen Wert in unserem Baum aufzufinden, benötigen wir maximal 4 Vergleiche (= Höhe des Baumes = Anzahl der Kanten im längsten Pfad). In der
Ausgangsliste waren es maximal 12. Noch effizienter gestaltet sich das Suchen, wenn der Baum ausgeglichen ist. Er ist ausgeglichen (AVL-Baum; Adel’son-Vel’skii and Landis [1962]) wenn für jeden
Knoten gilt, dass sich der linke und der rechte Teilbaum in ihren Höhen um maximal eins unterscheiden. Wäre der Baum in unserem Beispiel ausgeglichen, so hätte er eine Tiefe von drei. Das Suchen in
diesem Baum wäre somit auf drei Vergleiche beschränkt.
Ob ein Baum, nachdem er aufgebaut wurde, ausgeglichen ist oder nicht, hängt davon ab, in welcher
Reihenfolge die Werte in den Baum eingefügt werden. Dies soll an der Eingabe der drei Zahlen 1, 2
und 3 zeigen. Man kann sie auf mehrere Arten eingeben.
Eingabe: 1-2-3
Eingabe: 2-1-3
Eingabe: 3-1-2
2
1
1
2
3
3
1
2
3
Wie das erste Beispiel zeigt, erzeugt das Eingeben einer sortierten Liste einen extrem unausgeglichenen Baum, er hat dann nur linke oder nur rechte Kanten, der Baum wird im Extremfall zu einer Liste.
copyleft:munz
124
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
Es gibt nun Algorithmen, auf die wir nicht näher eingehen wollen, die es erlauben, aus einem nicht
ausgeglichenen Baum einen ausgeglichenen zu machen.
Der Baum aus unserem Beispiel könnte nach einem solchen Verfahren z. B. so aussehen:
7
2
1
0
12
3
9
8
5
13
10
15
Selbstverständlich lassen sich viele Arten von Daten in einen binären Suchbaum speichern, Voraussetzung allerdings ist, dass es in der Menge der einzutragenden Elemente eine Ordnungsrelation gibt,
denn ohne Vergleich lässt sich nicht entscheiden, ob man beim Eintrag eines Wertes in einem linken
oder einem rechten Teilbaum absteigen muss.
Bei der Ausgabe der Werte, die in einem Baum gespeichert sind, unterscheidet man drei Arten.
Pre-Order, In-Order und Post-Order.
6.6.3
Pre-Order-Strategie bei der Ausgabe eines binären Suchbaumes
Die Ausgabe Pre-Order lässt sich, wie auch die anderen beiden am besten rekursiv formulieren. Bei
der Strategie der Pre-Order-Ausgabe wird zunächst die Wurzel besucht und ihr Wert ausgegeben.
Dann wird der linke Teilbaum mit der Strategie Pre-Order ausgegeben, danach der rechte Teilbaum,
ebenfalls mit der Strategie Pre-Order.
Pre-Order =
Ausgabe Knoten
Pre-Order (linker Teilbaum)
Pre-Order (rechter Teilbaum)
Die Pre-Order-Strategie wird z.B. bei Termauswertung benutzt.
Aufgabe 39: Wie heißt die Liste der Zahlen, wenn der binäre Suchbaum nach Pre-Order ausgegeben wird?
6.6.4
In-Order-Strategie bei der Ausgabe eines binären Suchbaumes
In diesem Fall wird die Wurzel erst ausgegeben, wenn der linke Teilbaum mit der Strategie In-Order
ausgeben ist. Nach der Ausgabe des Wertes der Wurzel, erfolgt die Ausgabe des rechten Teilbaums
mit der Strategie In-Order.
In-Order =
In-Order (linker Teilbaum)
Ausgabe Knoten
In-Order (rechter Teilbaum)
Die In-Order-Strategie wird zum effizienten Sortieren benutzt.
Aufgabe 40: Wie heißt die Liste der Zahlen, wenn der binäre Suchbaum nach der In-Order-Strategie
ausgegeben wird?
Es sollte sich zeigen, dass die Werte sortiert ausgegeben werden, übrigens unabhängig
davon, wie der Baum aufgebaut wurde.
6.6.5
Post-Order-Strategie bei der Ausgabe eines binären Suchbaumes
Bei diesem Verfahren wird zunächst der linke Teilbaum mit der Strategie Post-Order ausgegeben, und
daran anschließend der rechte Teilbaum mit der gleichen Strategie. Danach erfolgt erst die Ausgabe
des Wertes der Wurzel.
copyleft:munz
125
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
Post-Order =
Post-Order (linker Teilbaum)
Post-Order (rechter Teilbaum)
Ausgabe Knoten
Das Post-Order-Verfahren wird z.B. benutzt bei der Eingabe von Termen bei einigen Taschenrechnern (Post-Fix-Notation).
6.6.6
Ein binärer Suchbaum wird in Java implementiert
Die Implementierung geht analog zur Implementierung der Liste LIFO bzw. Liste FIFO.
Was in einer Liste ein Element war, ist in einem Baum ein Knoten, wir implementieren also zuerst eine
Klasse Knoten.
Quelltext von Knoten:
package abstraktDT.baum;
public class Knoten {
protected Object wert;
private Knoten links, rechts;
}
copyleft:munz
public Knoten() {
Object wert;
links = rechts = null;
}
public Knoten(Object wert) {
setWert(wert);
links = rechts = null;
}
public void setWert(Object wert) {
this.wert = wert;
}
public Object getWert() {
return wert;
}
public void setLinks(Knoten links) {
this.links = links;
}
public Knoten getLinks() {
return links;
}
public void setRechts(Knoten rechts) {
this.rechts = rechts;
}
public Knoten getRechts() {
return rechts;
}
126
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
Die Klasse die man aus diesem Quelltext erzeugt kommt, wie die erste Zeile des Textes zeigt in ein
Paket
.
Syntax und Funktionalität der implementierten Konstruktoren und Methoden kann man der Dokumentation entnehmen (Anmerkung: die Java-Dokumentationskommentare sind, um die Übersichtlichkeit
zu gewährleisten aus dem obigen Quelltext entfernt worden.
Der Quelltext der abstrakten Klasse BinBaum:
package abstraktDT.baum;
public abstract class BinBaum {
public Knoten wurzel;
public void inOrder() {
inOrder(wurzel);
}
private void inOrder(Knoten knoten){
if (knoten!= null){
inOrder(knoten.getLinks());
System.out.print(knoten.getWert()+“,
inOrder(knoten.getRechts());
}
}
public void preOrder() {
preOrder(wurzel);
}
private void preOrder(Knoten knoten){
if (knoten!= null){
System.out.print(knoten.getWert()+“,
preOrder(knoten.getLinks());
preOrder(knoten.getRechts());
}
}
public void postOrder() {
postOrder(wurzel);
}
copyleft:munz
127
„);
„);
Fachschulen Lörrach
6
Abstrakte Datentypen
}
Java
private void postOrder(Knoten knoten){
if (knoten!= null){
postOrder(knoten.getLinks());
postOrder(knoten.getRechts());
System.out.print(knoten.getWert()+“, „);
}
}
public abstract void fuegeInBaum(Object wert);
Die abstrakte Klasse BinBaum ist der abstrakten Klasse Liste nachempfunden. Sie implementiert die
drei besprochenen rekursiven Ausgabemöglichkeiten des Baumes, während das Eintragen eines
Wertes in den Baum abstrakt und damit einem „Erben“ vorbehalten bleibt.
Der Quelltext von BinSuchBaum:
package abstraktDT.baum;
public class BinSuchBaum extends BinBaum {
public void fuegeInBaum(Object wert){
wurzel = fuegeInBaum(wurzel, wert);
}
private Knoten fuegeInBaum(Knoten aktuell, Object wert){
if(aktuell == null) aktuell = new Knoten(wert);
else{
if (wert.toString().compareTo
(aktuell.getWert().toString())<0){
aktuell.setLinks(fuegeInBaum(aktuell.getLinks(),wert));
}
else aktuell.setRechts(fuegeInBaum(
aktuell.getRechts(),wert));
}
return aktuell;
}
}
copyleft:munz
128
Fachschulen Lörrach
6
Abstrakte Datentypen
Java
Aufgabe 41: Erstellen Sie eine Liste von zehn Namen, fügen Sie die Werte in einem „Schreibtischtest“ in einen binären Suchbaum ein. Notieren Sie die Ausgaben wenn sie die Strategien Pre-Order, In-Order und Post-Order verwenden.
Aufgabe 42: Implementieren Sie ein Testprogramm, das die Ausgaben der vorangehenden Aufgabe
überprüft.
copyleft:munz
129
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
7
Java-Server im Intranet und Internet
7.1
Java
Direkte Kommunikation zweier Rechner über Sockets (TCP/IP)
Damit die Verständigung zwischen verschiedenen Rechnern klappt, müssen bestimmte Regeln eingehalten werden. Für die Verständigung im Internet sind solche Regeln in den Protokollen TCP und IP
festgehalten. Auch Rechner in lokalen Netzen können damit kommunizieren. Dazu muss man die IPAdresse der beiden Rechner kennen und einen Port, der für diese Art der Kommunikation benutzt
wird. Einige Portnummern sind für bestimmte Anwendungen reserviert, wie zum Beispiel die Nummer
21 für FTP, 23 für Telnet, 80 für http, 110 für POP. Wir wählen für unsere Zwecke vorsichtshalber den
Port mit einer Nummer über 1024. Mit Rechneradresse und Rechnerport haben wir gewissermaßen
eine „Steckdose“ für unsere Netzwerkverbindung, den Socket.
Zunächst stellen wir die Netzadresse unseres Rechners und die des Nachbarrechners fest. Dafür gibt
es das Programm ipconfig das wir auf der Betriebssystemebene DOS aufrufen können. Unter
Windows gibt es die Möglichkeit, mit Start - Ausführen – WINIPCFG anzuklicken, oder Start Einstellungen - Systemsteuerung - Netzwerk - Identifikation - Computername.
Man kann aber auch auf einem Rechner mit sich selbst kommunizieren. Man muss die Anwendung
oder die Entwicklungsumgebung mit der Anwendung dann einfach zweimal starten. Als Adresse verwendet man dann: „127.0.0.1“ oder „localhost“.
Der folgende Java- Kommunikationsserver besteht aus den Klassen KServer1, Empfaenger und
Sender. Im Konstruktor von Kserver1 wird die Benutzeroberfläche gestaltet. Sie hat einen Texteinund einen Textausgabebereich, sowie ein Textfeld für die Eingabe einer Rechneradresse. Bei Anklicken des Absendebuttons wird der in das Textausgabefeld geschriebene Text von einem Objekt der
Klasse Sender an den Rechner mit der angegebenen Adresse gesandt.
Ein Objekt der Klasse Empfaenger lauscht in einer endlosen While-Schleife an Port 6000 und zeigt
den Absender und die ankommende Textzeile im Textfenster an. Zwei Rechner im lokalen Netzwerk
können so kommunizieren, wenn die richtige Rechneradresse jeweils eingetragen wird.
import java.awt.*; import java.awt.event.*;
import java.net.*; import java.io.*;
public class Kserver1 extends Frame implements ActionListener {
TextArea textEin = new TextArea();
TextArea textAus = new TextArea();
TextField rechnerAdresse= new TextField("127.0.0.1");
Empfaenger meinEmpfaenger=new Empfaenger();
Sender meinSender=new Sender();
public Kserver1() {
super("Kommunikation über Sockets");
setLayout(new FlowLayout());
setSize(500,550);
copyleft:munz
130
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
Label e = new Label("Eingang:"); add(e);
add(textEin); textEin.setEditable(false);
Label a = new Label("Ausgang:"); add(a);
add(textAus);
add(rechnerAdresse);
Button b = new Button("Absenden"); add(b);
b.addActionListener(this);
setVisible(true);
addWindowListener(new FensterSchliesser());
meinEmpfaenger.lausche();
}
public void actionPerformed(ActionEvent e) {
meinSender.sende();
}
public static void main (String[] args) {
new Kserver1();
}
class Empfaenger {
public void lausche() {
String absender, sEin;
try {
ServerSocket verbindung=new ServerSocket(6000);
Socket lauschen;
while (true) {
lauschen=verbindung.accept();
absender=lauschen.getInetAddress().getHostAddress();
InputStreamReader portLeser;
portLeser =
new InputStreamReader(lauschen.getInputStream());
BufferedReader eingabe=new BufferedReader(portLeser);
sEin=eingabe.readLine();
textEin.append(absender + ": " + sEin + "\n");
}
}
catch (Exception e) {}
}
}
class Sender {
public void sende() {
try {
Socket zumNachbar =
new Socket(rechnerAdresse.getText(),6000);
PrintWriter ausg =
new PrintWriter(zumNachbar.getOutputStream(),true);
ausg.println(textAus.getText());
ausg.close();
textAus.setText("");
}
catch (Exception e) {}
}
}
class FensterSchliesser extends WindowAdapter {
public void windowClosing(WindowEvent e) { System.exit(0); }}
}
Will man sich mit einem Partner länger unterhalten, oder viele Daten übertragen, so hat obiges Programm den Nachteil, dass die Verbindung über den Port längere Zeit belegt ist. Um mehrere Partner
nahezu gleichzeitig bedienen zu können, kann man jede neu eingehende Verbindung in einen eigenständigen, nebenläufigen Prozess (Thread) auslagern.
copyleft:munz
131
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
Das nächste Programm lauscht also wieder in einer Endlosschleife an Port 6000. Wenn eine
Verbindung erlauscht wurde, wird das Socket-Objekt, das die Daten der Verbindung (Rechneradresse
und Port) enthält, sofort an einen unabhängigen Thread weitergegeben. Es können dann neue
Anfragen akzeptiert werden. In obigem Programm wird vom Konstruktor anstatt
meinEmpfaenger.lausche die Methode verbinde aufgerufen:
public void verbinde() {
ServerSocket verbindung;
try {
verbindung=new ServerSocket(6000);
while (true) {
Socket lauschen=verbindung.accept();
Empfaenger meinEmpfaenger=new Empfaenger(lauschen);
meinEmpfaenger.start();
}
}
catch (Exception e) { };
}
Der Empfänger kann dann so aussehen:
class Empfaenger extends Thread{
private Socket erlauscht;
public Empfaenger(Socket lauschen){
erlauscht=lauschen;
}
public void run() {
String sEin="";
try {
String absender=erlauscht.getInetAddress().getHostAddress();
InputStreamReader portLeser;
portLeser=new InputStreamReader(erlauscht.getInputStream());
BufferedReader eingabe=new BufferedReader(portLeser);
sEin=eingabe.readLine();
while (sEin!=null) {
textEin.append(absender+": "+sEin+"\n");
sEin=eingabe.readLine();
}
eingabe.close();
}
catch (Exception e) {}
}
}
Das vollständige Programm Kserver2 befindet sich im Anhang und auf der CD.
copyleft:munz
132
Fachschulen Lörrach
7
7.2
Java-Server im Intranet und Internet
Java
Client-Server-Kommunikation über das Common Gateway Interface (CGI)
Das Common Gateway Interface (CGI) regelt die Kommunikation zwischen einem Web-Browser und
einem Server-Programm. Zunächst eine Webseite, die außer einem Absende-Button noch keine
weiteren Eigabemöglichkeiten enthält:
<HTML> <HEAD> <TITLE> WebClient.html </TITLE> </HEAD>
<BODY>
Hallo Server! <BR>
<FORM METHOD=GET ACTION="http://localhost:5000">
<INPUT TYPE="SUBMIT">
</FORM>
</BODY> </HTML>
Der Java-Server auf dem selben Rechner lauscht an Port 5000 und schickt eine Antwort zurück.
Dabei muss das Protokoll HTTP und der Inhaltstyp HTML angegeben werden, gefolgt von einer
Leerzeile ( \n bedeutet new line ):
import java.net.*; import java.io.*;
public class CGIServer0{
public CGIServer0() {
try {
ServerSocket anschluss = new ServerSocket(5000);
while (true) {
Socket lausch=anschluss.accept();
PrintWriter aus=newPrintWriter(lausch.getOutputStream(),true);
aus.println("HTTP/1.0 200 OK\nContent-type: text/html\n\n");
aus.println("<HTML><BODY><FONT SIZE=8>Hallo Client!<BR>");
aus.println("<FONT COLOR=RED>Hier CGIServer0.</HTML>");
System.out.println("OK!");
aus.close();
}
}
catch (Exception e) { e.printStackTrace(); }
}
public static void main (String[] args) {
new CGIServer0();
}
}
Man kann auf einer HTML-Seite auch Daten eingeben und an den Server schicken. Hier eine
Webseite mit Eingabeformular für eine E-Mail-Adressliste:
<HTML>
<HEAD><TITLE> WebClient1.html </TITLE></HEAD>
<BODY>
<H3> Adressliste</H3>
Bitte beantworten Sie folgende Fragen zu Ihrer Person: <BR>
<FORM METHOD=GET ACTION="http://localhost:6000">
Vorname: <INPUT TYPE="TEXT" NAME="vorname"> <BR>
Nachname: <INPUT TYPE="TEXT" NAME="nachname"> <BR>
E- Mail- Adresse: <INPUT TYPE=text NAME=eMail SIZE=60>
<P>
<INPUT TYPE=reset>
<INPUT TYPE=submit>
</FORM>
</BODY>
</HTML>
copyleft:munz
133
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
Wir nehmen unser Server-Programm Kserver2 vom letzten Kapitel, um zu untersuchen, in welcher
Form die Daten beim Server ankommen:
Das Java-CGI-Programm empfängt die Daten vom Client. Sie sind, wie man sieht, in einer Textzeile
enthalten und bestehen jeweils aus dem Paar: Name und Inhalt der Variablen vorname, nachname
und eMail.
Wir ordnen sie einem Objekt der Klasse Kunde zu:
Kunde meinKunde = new Kunde();
Unsere Klasse Kunde enthält eine Methode entziffern zum Entnehmen der Daten aus dem eingehenden String sEin, eine Methode benachrichtigen zum Benachrichtigen des Kunden, dass
seine Daten angekommen sind und später eine Methode speichern zum Speichern der Daten in einer verfügbaren Datenbank. Diese Methoden werden in der Run-Methode des Empfängers aufgerufen:
meinKunde.entziffern();
meinKunde.benachrichtigen();
copyleft:munz
134
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
class Kunde{
String vorname, nachname, eMail;
void entziffern() {
vorname=""; nachname=""; eMail="";
int i=0;
char ch=sEin.charAt(i);
while (ch != '=') {
i=i+1;
ch=sEin.charAt(i);
}
while (ch != '&') {
vorname=vorname+ch;
i=i+1;
ch=sEin.charAt(i);
}
while (ch != '=') {
i=i+1;
ch=sEin.charAt(i);
}
while (ch != '&') {
nachname=nachname+ch;
i=i+1;
ch=sEin.charAt(i);
}
while (ch != '=') {
i=i+1;
ch=sEin.charAt(i);
}
while (ch != 'H') {
eMail=eMail+ch;
i=i+1;
ch=sEin.charAt(i);
}
}
void benachrichtigen() {
meinSender.sende(
"HTTP/1.0 200 OK\nContent-type: text/html\n\n"
);
meinSender.sende("Vorname" + vorname + "<BR>");
meinSender.sende("Nachname" + nachname + "<BR>");
meinSender.sende("Ihre E-Mail-Adresse" + eMail+ "<BR>\n");
}
Die Methode entziffern ist hier ganz auf die obige HTML-Seite des Client abgestimmt. In dem Programm CGIServer ist die Methode entziffern allgemeiner verwendbar. Sie liest die Namen der ankommenden Variablen und ihre Werte in ein Array. Das Programm CGIServer befindet sich im
Anhang.
Aufgabe 43: Ergänzen Sie die HTML-Seite (Client) durch Radio-Buttons und Auswahlmenüs und
untersuchen Sie, in welcher Form die Daten dann an den Server übermittelt werden.
copyleft:munz
135
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
7.3
Java
Datenbanken
Wir wollen nun die ankommenden Daten in einer Datenbank-Tabelle speichern. Wir müssen also zuerst eine (leere) Datenbank erstellen:
Eine Datenbank besteht, vereinfacht ausgedrückt, aus Tabellen, die zueinander in Beziehung stehen
können. Auf eine Datenbank wollen in der Regel mehrere Nutzer zugreifen. Das geschieht im lokalen
Netzwerk oder im Internet.
Wir beschreiben hier den Zugriff eines Java-Programms auf eine lokale Microsoft-Access-Datenbank
im Netz oder auf dem eigenen Rechner, sowie den Zugriff auf eine MySQL-Datenbank im Internet.
Auf Internet-Servern hat das Datenbank-Management-System MySQL weite Verbreitung gefunden.
Ein Hauptgrund ist seine Schnelligkeit bei gleichzeitigem Zugriff vieler Nutzer. MySQL ist kostenlos
erhältlich, es wird bei der Installation von Linux gleich mit installiert. Anleitungen und Tools sind im Internet verfügbar. Es gibt inzwischen MySQL auch kostenlos für Windows, so dass man damit auch auf
dem eigenen Rechner oder im Schulnetz arbeiten kann. Zum Erstellen einer neuen leeren MySQLDatenbank kann man ein Tool (z. B. MySQLWinAdmn.exe) benutzen, oder im Fenster der MSDOSEingabeaufforderung mysql aufrufen und direkt Kommandozeilen ergeben.
7.3.1
Erstellen einer lokalen Access-Datenbank
Nach dem Starten von Access geben wir der Datenbank zunächst einen Namen, die Endung *.mdb
wird automatisch hinzugefügt. Das erscheinende Datenbankfenster zeigt die Datenbank-Objekte wie
zum Beispiel die Tabellen. Hier eine Tabelle in der Datenblattansicht:
copyleft:munz
136
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
Zum Erstellen einer neuen Tabelle können wir die Entwurfsansicht wählen:
Mit Hilfe des Tabellenassistenten geben wir die Überschriften der einzelnen Tabellenspalten (Feldnamen) und ihren Typ (Felddatentyp) ein. Für den Felddatentyp wird eine Auswahl vorgeschlagen. Wir
wählen den Typ Text und geben die maximale Länge an.
Man kann Tabellen auch aus dem Tabellenkalkulationsprogramm Excel importieren:
Einfügen
Tabelle
Tabelle importieren.
copyleft:munz
137
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
7.3.2
Java
Zugriff auf eine Access-Datenbank ermöglichen
Wir wollen nun den Zugriff von außen auf eine existierende Microsoft-Access-Datenbank (als Datenquelle) ermöglichen. Den Zugang zu verschiedenen Datenquellen regelt unter Windows die Open
Database Connectivity (ODBC). Wir benutzen den Datenquellenadministrator, um diesen Zugang einzurichten. Er findet sich in der Windows-Systemsteuerung. Mit ihm fügen wir die Datenquelle mit
System-Datenquellen-Namen (DSN) und den passenden Access-Treiber hinzu:
Start - Einstellungen - Systemsteuerung (Verwaltung)
ODBC-Datenquelle 32 Bit (Datenquellenadministrator)
System-DNS - Hinzufügen
Microsoft Access-Treiber - Fertig stellen
Datenquellenname eingeben
Datenbank - Auswählen
copyleft:munz
138
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
7.3.3
Java
Erstellen einer MySQL- Datenbank
MySQL kopiert man am Besten in ein Verzeichnis auf der Festplatte. Im MySQL-Unterverzeichnis
Docs finden sich Anleitungen. Unter Windows NT odere Windows 2000 lässt sich der MySQL-Dämon
beispielsweise so starten: Man öffnet ein MS-DOS-Fenster, wechselt in das Unterverzeichnis bin und
gibt ein:
mysqld-nt --standalone
Der MySQL-Server läuft nun im Hintergrund. In einem weiteren DOS-Fenster ruft man mysql auf.
Eine neue Datenbank test2 erzeugt man mit
create database test2;
Die bestehenden Datenbanken werden angezeigt mit:
show databases;
Die Datenbank test ist schon vorgegeben. Im folgenden Bild wird gezeigt, wie man eine Tabelle in der
Datenbank test erzeugt:
Es gibt auch Tools zum Administrieren von MySQL-Datenbanken unter Windows, wie z. B.
MySQLWinAdmn.exe.
(Unter Linux wird häufig das Programm phpMyAdmin benutzt, für das zuvor PHP installiert sein muss.)
Das folgende Bild zeigt die Verwaltung der Tabelle kursliste in der Datenbank test mit Hilfe von
MySQLWinAdmn:
copyleft:munz
139
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
7.3.4
Java
Datenbank-Abfragesprache SQL
SQL (Structured Query Language) ist die standardisierte Abfragesprache, die man benutzt, um
Informationen aus einer Datenbank abzufragen. Sie wurde vereinbart, um unabhängig vom Hersteller
der Datenbank zu sein.
Wir verwenden als Beispiele nur wenige Vokabeln von SQL, wie insert und select und verweisen
auf die Literatur, bzw. das Internet. select bewirkt eine Selektion (Auswahl) von Zeilen
(Datensätzen) einer Tabelle:
SELECT * FROM weine;
Hier wird alles aus der Tabelle der Weine ausgewählt. Das Sternchen * ist ein Joker für alles.
SELECT * FROM weine WHERE nummer = 8;
Hier wird die Zeile ausgewählt, deren Nummer gleich 8 ist. Mit insert fügen wir Datensätze in die
Tabelle ein:
INSERT INTO kursliste (vorname, nachname, email)
VALUES ("Reinhold", "Ley", "[email protected]");
Unsere Tabelle heißt hier Klassenliste und hat Spalten mit den Namen Vorname, Nachname und
Email.
copyleft:munz
140
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
7.3.5
Java
Zugriff auf eine Datenbank mit Java - Die Java Database Conectivity (JDBC)
JDBC ist die Schnittstelle zur Anbindung von Datenbanken an Java-Programme. Wir beschreiben den
Zugriff in fünf Schritten:
Als Erstes ist ein Treiber für die Datenbank anzugeben:
1
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Hier ist ein Treiber für Access-Datenbanken angegeben. Die Open Database Connectivity (ODBC)
regelt die Kommunikation von MSWindows mit verschiedenen Datenbanksystemen. Eine
JDBC:ODBC-Brücke ermöglicht die Verbindung der Java- mit der MSWindows- Welt.
Im zweiten Schritt rufen wir die Methode getConnection des DriverManagers auf und geben die
Brücke, die URL zur Datenbank und den Datenbanknamen an. Hierbei ist auch eventuell ein LoginName und ein Passwort erforderlich. Im folgenden Beispiel befindet sich eine Access-Datenbank mit
dem Namen Test1 auf dem lokalen Rechner, es ist kein Login-Name und kein Passwort erforderlich:
String urlDB="jdbc:odbc:Test1";
2 Connection con=java.sql.DriverManager.getConnection(urlDB,"","");
Diese Zeilen sind zu ändern, wenn auf eine andere Datenbank zugegriffen werden soll. Ein Treiber für
MySQL-Datenbanken wäre: org.gjt.mm.mysql.Driver
Der Zugriff auf eine MySQL-Datenbank geht dann so:
String treiber="org.gjt.mm.mysql.Driver";
String urlDB="jdbc:mysql://localhost:3306/test";
String user="nobody"; String psst=null;
Connection con=DriverManager.getConnection(urlDB,user,psst);
Im dritten Schritt wird die Methode createStatement unserer Verbindung aufgerufen.
3
Statement s = con.createStatement();
Das Statement-Objekt erhält als Eingabe den Abfragetext. Der Abfragetext entstammt dem Vokabular
der Abfragesprache SQL:
INSERT INTO kursliste (vorname) VALUES ("Claus");
Mit der Methode execute wird im vierten Schritt dieser SQL-Befehl des Statements ausgeführt:
s.execute(eintrag);
4
Bei einer Abfrage befindet sich das Ergebnis dann im ResultSet-Objekt, es ist in der Regel eine Tabelle.
5
ResultSet resultat=s.getResultSet();
Das ResultSet-Objekt verfügt über die Methode next mit der wir immer zur nächsten Tabellenzeile
gelangen. Mit getString, bzw. getDouble erhalten wir die einzelnen Zellen, die durch den
Spaltennamen bestimmt sind:
resultat.getString("vorname")
copyleft:munz
141
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
7.3.6
Java
Datensatz in eine Datenbanktabelle einfügen
Server-
Daten-
Programm
bank
Eine erste Version unseres Java-Programms zum Datenbank-Eintrag schreibt einen explizit
angegebenen Namen in die Tabelle Klassenliste der Access-Datenbank Adressen:
import java.sql.*;
public class Eintrag1{
String treiber="sun.jdbc.odbc.JdbcOdbcDriver";
String urlDB="jdbc:odbc:Adressen";
String user="";
String psst="";
public Eintrag1() {
try {
Class.forName(treiber);
Connection con=java.sql.DriverManager.getConnection(urlDB,"","");
Statement s=con.createStatement();
String eintrag="INSERT INTO Klassenliste (Vorname, Nachname)
VALUES ('Alexander','Popp');";
s.execute(eintrag);
con.close();
}
catch (Exception e) { e.printStackTrace(); }
}
public static void main (String[] args) {
new Eintrag1();
}
}
Man beachte, dass im String eintrag noch Strings enthalten sind.
Mit unseren gewonnenen Kenntnissen können wir nun das Programm CGIServer vom vorherigen
Kapitel ergänzen und die im HTML-Formular eingegebenen Daten der Kunden in der AccessDatenbank Adressen speichern lassen.
Client:
Server-
Daten-
HTML-Seite
Programm
bank
Dazu fügen wir der Klasse Kunde die folgende Methode speichern hinzu.
copyleft:munz
142
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
void speichern() {
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String urlDB="jdbc:odbc:Adressen";
Connection con =
java.sql.DriverManager.getConnection(urlDB,"","");
Statement s = con.createStatement();
String eintrag;
eintrag="insert into Klassenliste("+varName[0];
for (int i=1; i<n; i=i+1) eintrag=eintrag+","+varName[i];
eintrag=eintrag+")values('"+varWert[0]+"'";
for (int i=1; i<n; i=i+1)
eintrag=eintrag+",'"+varWert[i]+"'";
eintrag=eintrag+")";
s.execute(eintrag);
con.close();
meinSender.sende("alles klar!\n");
}
catch (Exception e) {
System.out.println(""+e.getMessage());
}
}
Das vollständige Programm mit dem Namen AccessServer befindet sich im Anhang.
7.3.7
Der umgekehrte Weg: Holen von Daten aus einer Datenbanktabelle
Das folgende Java-Programm nimmt Verbindung zu einer lokalen Datenbank mit dem Namen „db1“
auf. In der Tabelle mit dem Namen „rotweine“ sind Informationen über gelagerte Rotweine gespeichert. Eine Tabellenspalte enthält die Namen und eine Tabellenspalte den Preis des Weins. Es werden die Weine ausgewählt, deren Preis unter 20 liegt. Die Ausgabe erfolgt hier über die
Standardausgabe:
import java.sql.*;
public class Anfrage {
public static void main(String[] args) {
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); 1
String urlDB="jdbc:odbc:db1";
2
Connection con =
java.sql.DriverManager.getConnection(urlDB,"","");
3
Statement s = con.createStatement();
String frage = "SELECT * FROM rotweine WHERE Preis<20";
s.execute(frage);
4
ResultSet resultat=s.getResultSet();
5
while (resultat.next()) {
System.out.print(resultat.getString("Name") + "\t");
System.out.println(resultat.getDouble("Preis"));
}
con.close();
}
catch (Exception e) {
System.out.println(""+e.getMessage());
}
}
}
copyleft:munz
143
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
7.3.8
Java
Ausgabe der Daten auf einer grafischen Oberfläche
import java.awt.*; import java.awt.event.*; import java.sql.*;
public class Anfrage3 extends Frame implements ActionListener{
Label l = new Label("Suchen in db1");
TextArea textAus = new TextArea("",10,60);
Button b = new Button("Los!");
public Anfrage3() {
setLayout(new FlowLayout());
add(l);
add(b); b.addActionListener(this);
add(textAus);
setSize(500,300); setVisible(true);
addWindowListener(new FensterLauscher());
}
public void actionPerformed(ActionEvent e) {
Connection con = null;
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String urlDB="jdbc:odbc:db1";
con = java.sql.DriverManager.getConnection(urlDB,"","");
Statement s=con.createStatement();
String frage = "SELECT * FROM rotweine WHERE Preis<20";
s.execute(frage);
ResultSet resultat=s.getResultSet();
String n; double p;
while (resultat.next()) {
n=resultat.getString("Name"); textAus.append(n+"\t");
p=resultat.getDouble("Preis");textAus.append(""+p+"\n");
}
con.close();
}
catch (Exception ex) { textAus.append(""+ex.getMessage());}
}
class FensterLauscher extends WindowAdapter {
public void windowClosing(WindowEvent e) {System.exit(0);}}
public static void main(String[] args) {
new Anfrage3();
}
}}
7.3.9
Ausgabe im Browser des Client, der Java-Server als Middleware
Auf eine Datenbank greifen unterschiedliche Nutzer zu. Sie haben auch unterschiedliche Zugriffsrechte. Der Administrator hat Zugriffsrechte, die ihm erlauben, eine Datenbank neu zu schaffen, oder
zu erweitern. Der Besitzer der Daten fügt neue Daten hinzu, z. B. über ein Warenlager. Der Kunde will
sich über die Schätze in der Datenbank informieren und darin nach bestimmten Kriterien suchen. Die
Datenbankverbindung ist eventuell lange geöffnet und störungsanfällig. Man schaltet deshalb noch
eine Ebene auf dem Server dazwischen. Zwischen Datenbank und Client befindet sich die sogenannte Middleware. Die Middleware reicht die Kundenanfrage mit Hilfe eines geeigneten Treiber-Zwischenstücks an die Datenbank weiter und bereitet die Antwort von dort in eine kundengefälligere
Form auf.
Client:
Server-
Daten-
HTML-Seite
Programm
bank
copyleft:munz
144
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
Unser HTML-Client sieht in einer einfachen Form so aus:
<HTML> <HEAD> <TITLE> WebClient2.html </TITLE> </HEAD>
<BODY>
Sie erhalten die Liste der Weine aus unserem Lagers, die mehr als 20
Euro kosten: <BR>
<FORM METHOD=GET ACTION="http://localhost:5000">
<INPUT TYPE="SUBMIT">
</FORM>
</BODY> </HTML>
Das folgende Java-Programm sendet die Liste der in der Datenbank gefundenen Weine an den Browser des Client. Dabei muss zur Verständigung das HTTP-Protokoll beachtet werden. Das heißt, es
wird eine Zeile vorangestellt, in der die Version des Protokolls und der Inhaltstyp angegeben werden,
gefolgt von einer Leerzeile. Erst danach kommen die Daten:
import java.net.*; import java.io.*; import java.sql.*;
public class AccessWeinServer{
String treiber="sun.jdbc.odbc.JdbcOdbcDriver";
String urlDB="jdbc:odbc:WeinDB";
String user="";
String psst="";
public AccessWeinServer () {
try {
ServerSocket anschluss = new ServerSocket(5000);
Socket lausch=anschluss.accept();
PrintWriter ausgabe =
new PrintWriter(lausch.getOutputStream(),true);
ausgabe.println(
"HTTP/1.0 200 OK\nContent-type:text/html\n\n");
Class.forName(treiber);
Connection con;
con=java.sql.DriverManager.getConnection(urlDB,user,psst);
Statement s= con.createStatement();
String frage =
"SELECT Name,Preis FROM rotweine WHERE Preis>20";
s.execute(frage);
ResultSet resultat=s.getResultSet();
while (resultat.next()) {
String name=resultat.getString("Name");
String preis=resultat.getString("Preis");
ausgabe.println(name + " " + preis + "<br>");
}
ausgabe.close();
}
catch (Exception e) { e.printStackTrace(); }
}
public static void main (String[] args) {
new AccessWeinServer();
}
}
Aufgabe 44: Erstellen Sie im Schulnetz eine Datenbank mit einer Tabelle der Adressen und
Telefonnummern der Schülerinnen und Schüler deiner Klasse. Gestalten Sie eine grafische Oberfläche für die Eingabe der Daten. Schreiben Sie ein Java-Programm für den
Eintrag in die Datenbank.
Aufgabe 45: Gestalten Sie eine Oberfläche für die Abfrage der Adressdatenbank, so dass unter
unterschiedlichen Gesichtspunkten gesucht werden kann. Schreiben Sie ein Java-Programm für die Suche in der Datenbank. Das Java-Programm soll die Ergebnisse in einer übersichtlichen Form wieder im Browser präsentieren.
copyleft:munz
145
Fachschulen Lörrach
7
7.4
Java-Server im Intranet und Internet
Java
Servlets
Servlet-Umgebung Tomcat einrichten:
Ein Java-Servlet entspricht etwa einem Java-Applet, aber es befindet sich auf dem Server und hat
keine eigene grafische Benutzerschnittstelle. Es kommuniziert mit einem Internet-Browser, dem Client.
Im Arbeitsspeicher des Webservers können viele Servletinstanzen nebeneinander leben. Servlets sind
also ideal, wenn gleichzeitig mehrere Anfragen aus dem WWW eintreffen.
Man kann Webspace mit der Servlet- Engine Tomcat auch mieten. Zum Entwickeln und Testen von
Servlets auf dem PC eignet sich die gleiche Servlet-Umgebung, die auch auf Webservern benutzt
wird: Tomcat. Tomcat gibt es auch für Windows. Nach dem Download entsteht beim Auspacken folgende Verzeichnisstruktur:
Im abgebildete Beispiel wurde das Installationsverzeichnis Tomcat genannt. Zu den Unterverzeichnissen:
bin\ Hier befinden sich die Programme zum Starten und beenden von Tomcat: startup.bat und
shutdown.bat. In
doc\ Hier stehen Anleitungen bereit. In
lib\ sind Klassen untergebracht, die benötigt werden.
webapps\examples\servlets\ Hier sind HTML-Seiten, mit denen man Beispiel- Servlets aufrufen
kann.
WEB-INF\classes\ Hier wurden Servlets untergebracht, und zwar fertig kompilierte Servlets, also die
ausführbaren class- Dateien.
Um den Java- Quellcode von selbstgeschriebenen Servlets zu kompilieren benötigt man das Java
Developers Kit, das früher schon installiert wurde und Servlet- Klassen des Java- Archivs
servlet.jar,
das sich im Unterverzeichnis lib\common befindet. Bei der Installation des JDK wurde eventuell früher
ein Pfad auf das Unterverzeichnis bin und ein Klassenpfad auf das Unterverzeichnis lib gesetzt, beispielsweise durch einen Eintrag in der Datei Autoexec.bat:
SET PATH=C:\jdk\bin;
SET CLASSPATH=C:\jdk\lib;
Nun ist dies zu ergänzen durch
SET CLASSPATH=C:\Tomcat\lib\common;
Man kann stattdessen auch die Umgebungsvariablen JAVA_HOME und TOMCAT_HOME in der
Windows- Systemsteuerung eintragen. Das geht mit
copyleft:munz
146
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
Start - Einstellungen - Systemsteuerung - System - Erweitert - Umgebungsvariablen - Systemvariable
bearbeiten - CLASSPATH
Auch bei Benutzung einer Java- Entwicklungsumgebung trägt man unter Optionen den Klassenpfad
ein. Im Java-Editor ist dies der Menupunkt Fenster - Konfiguration:
copyleft:munz
147
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
Bevor die Servlets getestet werden können, muss zuerst Tomcat mit startup.bat gestartet werden.
Man prüft, ob die Installation korrekt war, indem man die mitgelieferten Beispiel- Servlets laufen lässt.
Dazu kann man im Browser direkt eingeben:
http://localhost:8080/
Es erscheint die Startseite von Tomcat mit einer Auswahl von Servlets, die man anklicken kann. Einzelne Servlets ruft man von einer HTML- Seite aus auf, oder direkt im Browser, indem man z. B. eingibt:
http://localhost:8080/examples/servlet/HelloWorldExample
Hier ist also der Rechnername anzugeben, der Port, der Aliasname servlet und der Servletname.
Fehlerfreie Servlets überträgt man dann mit einem FTP- Programm wie z. B. WS_FTP auf einen Internet-Host. Auch dort sind sie im Verzeichnis WEB-INF/classes unterzubringen.
Ein Java- Treiber für MySQL heißt
mm.mysql-2.0.2-bin.jar
und wird in der Klassen-Library untergebracht.
HTML-Client:
Der Aufruf eines Servlets aus einer HTML-Seite sieht etwa so aus:
<html> <body>
<form method=GET action=http://localhost:8080/servlet/ServletEins>
<input type=submit>
</form>
</body> </html>
Obiges HTML-Formular enthält noch keine Eingaben.
Servlet Eins:
Das Servlet ServletEins antwortet mit Text. Der Inhaltstyp der Antwort ist text/html, deshalb gibt unser
PrintWriter Hypertext mit Tags zur Formatierung aus:
import java.io.*; import javax.servlet.*; import javax.servlet.http.*;
public class ServletEins extends HttpServlet {
public void doGet(
HttpServletRequest anfrage, HttpServletResponse antwort)
throws ServletException, IOException {
antwort.setContentType("text/html");
PrintWriter ausgabe = antwort.getWriter();
ausgabe.println("<BODY> <FONT SIZE=7>");
ausgabe.println("Hallo Client, hier spricht ServletEins!");
ausgabe.println("</FONT> </BODY>");
ausgabe.close();
}
}
Bei der Methode GET werden die Daten als Parameter an die übermittelte URL mit Fragezeichen
angehängt und jeder kann sie lesen. Auch ist die Anzahl der Zeichen beschränkt auf 255. Bei geheimen Daten oder wenn die Datenmenge größer ist, sollte man die Methode POST verwenden.
7.4.1
HTML-Client mit Eingabe
<HTML> <BODY>
<FORM METHOD=POST ACTION="http://localhost:8080/servlet/ServletEins">
Name:<br> <input type="Text" name="Name" size="30"><br>
Vorname:<br> <input type="Text" name="Vorname" size="30"><br>
EMail:<br> <input type="Text" name="EMail" size="50"><br>
<INPUT TYPE = SUBMIT VALUE = "Absenden">
</FORM> </BODY> </HTML>
Wenn der Submit- Button gedrückt wird, werden die Namen und Inhalte der Variablen Vorname und
Email an ServletZwei übermittelt.
copyleft:munz
148
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
7.4.2
Java
Request und Response
Das vom HTML- Formular angesprochene Servlet erhält die Namen und die Werte der eingegebenen
Daten (Parameter) mit Hilfe eines HttpServletRequest-Objekts, das wir anfrage nennen:
anfrage.getParameterNames();
Auf den Wert der Daten mit dem Namen "Vorname" greifen wir zu mit:
String Wert = anfrage.getParameterValues("Vorname");
Wir speichern alle Daten als Aufzählung (Enumeration). Enumeration aus dem Paket java.util besitzt
Methoden, die uns hier nützlich sind (siehe auch Dokumentation):
hasMoreElements()
liefert true, wenn die Aufzählung noch weitere Elemente hat.
nextElement()
liefert das nächste Objekt in der Aufzählung. Wir wandeln es in einen String um. Solange es noch ein
nächstes Element gibt, werden im folgenden Servlet die Werte der Daten eingelesen mit
getParameterValues(Name);
antwort ist ein HttpServletResponse-Objekt. Es soll auf den Eingang der Daten antworten.
Servlet Zwei:
import java.io.*; import java.util.*;
import javax.servlet.*; import javax.servlet.http.*;
public class ServletZwei extends HttpServlet {
public void doPost(
HttpServletRequest anfrage,HttpServletResponse antwort)
throws ServletException, IOException {
antwort.setContentType("text/html");
PrintWriter ausgabe = antwort.getWriter();
ausgabe.println("<HTML><BODY>");
ausgabe.println("<FONT SIZE = 6> Hallo Client.");
Enumeration Namen=anfrage.getParameterNames();
while (Namen.hasMoreElements()) {
String Name=(String)Namen.nextElement();
String Wert=anfrage.getParameterValues(Name)[0];
ausgabe.println(Name+" "+Wert);
}
ausgabe.println("</FONT></BODY></HTML>");
ausgabe.close();
}
}
Nach dem Test mit Tomcat übertragen wir die Servlets jeweils auf einen „Servlet-fähigen“ InternetHost. Im Verzeichnisbaum des Servers ist dafür eine spezielle Servlet-Zone vorgesehen.
7.4.3
Servlet mit MySQL-Datenbankanbindung
Es müssen 3 Dinge geprüft werden:
• Angabe des Datenbanktreiber: "org.gjt.mm.mysql.Driver"
• URL zu Datenbank, gegebenenfalls einschließlich Pfad und Portnummer, z. B.:
"jdbc:mysql://localhost:3306/test"
• Username und Passwort
Außerdem muss auf der HTML-Seite der Servlet-Aufruf angepasst werden. Lokal lautet er z. B.:
"http://localhost:8080/examples/servlet/ServletDreiA"
oder auf einem Internet-Host möglicherweise
"http://softley.de/servlet/ServletDreiA"
Servlet Drei:
ServletDreiA empfängt die eingegebenen Daten vom Browser des Client:
Enumeration Namen=request.getParameterNames();
und speichert sie mit
String Eintrag="insert into kursliste ...
s.execute(Eintrag);
in die Tabelle kursliste der Datenbank test:
copyleft:munz
149
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
import java.io.*;
import java.sql.*; import javax.servlet.*; import javax.servlet.http.*;
public class ServletDreiA extends HttpServlet
{
String treiber = "org.gjt.mm.mysql.Driver";
String url = "jdbc:mysql://localhost:3306/test";
String user = "nobody"; String psst = null;
public void doPost(HttpServletRequest anfrage,
HttpServletResponse antwort) throws ServletException, IOException{
Connection con = null; Statement s;
String vorname=""; String nachname=""; String email="";
String ergebnis="OK";
vorname = anfrage.getParameter("vorname");
nachname = anfrage.getParameter("nachname");
email = anfrage.getParameter("email");
antwort.setContentType("text/html");
PrintWriter ausgabe = antwort.getWriter();
ausgabe.println("<BODY> <H1> E-Mail-Liste speichern:</H1>");
ausgabe.println(vorname+"<BR>"+nachname+"<BR>"+email);
String eintrag="insert into kursliste (vorname, nachname, email)
values('"+vorname+"','"+nachname+"','"+email+"')";
try {
Class.forName("org.gjt.mm.mysql.Driver");
con=DriverManager.getConnection(url,user,psst);
s=con.createStatement();
s.execute(eintrag);
}
catch (Exception e) {ergebnis = e.getMessage();}
ausgabe.println("<BR>"+ergebnis+"</BODY> </HTML>");
ausgabe.close();
}
}
ServletDreiB holt alle Datensätze der Tabelle kursliste aus der Datenbank test:
import java.io.*;
import javax.servlet.*; import javax.servlet.http.*; import java.sql.*;
public class ServletDreiB extends HttpServlet {
String url = "jdbc:mysql://localhost:3306/test";
String treiber = "org.gjt.mm.mysql.Driver";
String user = "nobody"; String psst = null;
public void doGet (HttpServletRequest anfrage,
HttpServletResponse antwort)
throws ServletException, IOException {
antwort.setContentType( "text/html" );
PrintWriter ausgabe = antwort.getWriter( ); // Ausgabe an Browser
String titel = "kursliste";
ausgabe.println("<html><head><title>"+titel+"</title></head>" );
ausgabe.println("<body><H1>" + titel + "</H1>" );
ausgabe.println("<table border>");
String frageDB = "SELECT * FROM kursliste";
// SQL
try {
Class.forName(treiber);
Connection con = DriverManager.getConnection(url, user, psst);
Statement s = con.createStatement();
ResultSet resultat = s.executeQuery(frageDB);
ResultSetMetaData resMeta = resultat.getMetaData();
int spalten = resMeta.getColumnCount();
ausgabe.println("<tr>");
for (int i=1; i<=spalten; i=i+1) {
String spaltenname = resMeta.getColumnLabel(i);
ausgabe.println("<th> " + spaltenname + " </th>");
}
copyleft:munz
150
Fachschulen Lörrach
7
Java-Server im Intranet und Internet
Java
ausgabe.println("</tr>");
while (resultat.next()) {
ausgabe.println("<tr>");
for (int i=1; i<=spalten; i=i+1) {
String inhalt = resultat.getString(i);
ausgabe.println("<td> " + inhalt + " </td>");
}
ausgabe.println("</tr>");
}
con.close();
}
catch (Exception e) {ausgabe.println (e.getMessage()); }
ausgabe.println("</table>");
ausgabe.println("</body></html>" );
ausgabe.close();
}
}
Aufgabe 46: Schreiben Sie ein Abfrage-Servlet, das bei Eingabe eines Namens die Adresse,
Telefonnummer und E-Mail-Adresse deiner Mitschüler ausgibt.
7.4.4
Anfänge eines Online-Shops
Java-Servlets sind für E-Commerce-Software-Lösungen ideal. Für Servlets muss nicht jedes mal bei
einer Anfrage ein neues Programm gestartet werden. Wenn mehrere Kunden aus dem Internet
gleichzeitig das Servlet aufrufen, werden im Hauptspeicher mehrere Objekte nebeneinander und unabhängig voneinander ins Leben gerufen. Ihr Platz-und Zeitbedarf ist der jeweiligen transportierten
Datenmenge angepasst.
Aufgabe 47: Programmieren Sie für einen einfachen Laden
- ein Kunden-Servlet,
- ein Waren-Servlet,
- ein Such-Servlet,
- ein Korb-Servlet.
Das Kunden-Servlet: Ein Kunde besitzt Daten und diese sollten in der Datenbank gespeichert werden.
Der Höflichkeit halber wird er davon auch benachrichtigt. Damit ist es naheliegend, eine Klasse Kunde
zu schaffen. Das Speichern der Daten in der Datenbank geschieht genau wie früher beschrieben.
Natürlich werden noch mehr Daten als Vorname und Nachname aufgenommen. Es ist praktisch, diese
Daten mit den gleichen Variablennamen zu versehen, wie die Spalten in der entsprechenden Tabelle
der Datenbank.
Das Waren-Servlet: Beim Anlegen der Tabelle für die Waren sollte man sich auch überlegen, nach
welchen Kriterien ein Kunde im Warenbestand suchen möchte.
Das Suchergebnis trägt zur Kaufentscheidung bei. Es sollte deshalb übersichtlich in einer Tabelle
dargestellt werden.
Das Korb-Servlet: Das Korb-Servlet soll an einen Warenkorb erinnern. Man kann etwas hinzutun, man
kann den Preis addieren, die Bestellung wird aufgenommen und gespeichert.
copyleft:munz
151
Fachschulen Lörrach
8
Anhang
8
Anhang
8.1
Java
Quelltexte zur den einfachen Grafik-Programmen
Grundgerüst:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JGrafik extends JFrame {
private GrafikPanel zeichenFlaeche;
public JGrafik(String title) {
super (title);
addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent evt) {
System.exit(0);}}
);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
zeichenFlaeche = new GrafikPanel(300, 200);
cp.add(zeichenFlaeche);
pack();
setVisible(true);
}
public static void main (String[] args) {
new JGrafik("Grafik-Beispiel");
}
}
Die Zeichenfläche wird von JPanel abgeleitet. Hier wird ein Rechteck gezeichnet:
import java.awt.*;
import javax.swing.*;
public class GrafikPanel extends JPanel {
private int breite, hoehe;
public GrafikPanel(int breite, int hoehe) {
this.breite = breite;
this.hoehe = hoehe;
setPreferredSize(new Dimension(breite, hoehe));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int dx = breite*2/3; int dy = hoehe*2/3;
int x = (breite - dx)/2;
int y = (hoehe - dy)/2;
g.setColor(Color.WHITE);
g.fillRect(x, y, dx, dy);
g.setColor(Color.BLACK);
g.drawRect(x, y, dx, dy);
}
copyleft:munz
}
152
Fachschulen Lörrach
8
Anhang
Java
Die Zeichnung wird auf Knopfdruck verändert:
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JGrafik extends JFrame
implements ActionListener {
private GrafikPanel zeichenFlaeche;
private JButton jbNeu;
private Random rand;
public JGrafik(String title) {
// Frame-Initialisierung
super (title);
addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent evt) {
System.exit(0);}}
);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
rand = new Random();
jbNeu = new JButton("neu zeichnen");
jbNeu.addActionListener(this);
cp.add(jbNeu, BorderLayout.NORTH);
zeichenFlaeche = new GrafikPanel(300, 200);
cp.add(zeichenFlaeche);
pack();
setVisible(true);
}
}
public void actionPerformed(ActionEvent e) {
if (jbNeu == e.getSource()) {
zeichenFlaeche.setFarbe(
new Color(rand.nextInt(255),
rand.nextInt(255),
rand.nextInt(255))
);
zeichenFlaeche.zeichneGrafik();
}
}
public static void main (String[] args) {
new JGrafik("Grafik Beispiel");
}
Die Grafik wird in einer Image-Komponente zwischengespeichert:
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
public class GrafikPanel extends JPanel {
private int breite, hoehe;
private int x, y;
private Color farbe;
Die Image-Komponente:
private BufferedImage image;
copyleft:munz
153
Fachschulen Lörrach
8
Anhang
Java
Der Grafik-Kontext der Image-Komponente:
private Graphics2D g2D;
Der Konstruktor:
public GrafikPanel(int breite, int hoehe) {
this.breite = breite;
this.hoehe = hoehe;
setPreferredSize(new Dimension(breite, hoehe));
farbe = Color.WHITE;
erstelleGrafik();
zeichneGrafik();
}
Das Image erstellen, diese Methode wird nur lokal benötigt:
private void erstelleGrafik() {
image = new BufferedImage(
breite*8/10, hoehe*8/10, BufferedImage.TYPE_INT_RGB
);
g2D = image.createGraphics();
}
Die Methode zeichneGrafik wird von außen aufgerufen, sie ersetzt das alte Rechteck:
public void zeichneGrafik() {
g2D.setColor(farbe);
g2D.fillRect(0, 0, image.getWidth(), image.getHeight());
g2D G.setColor(Color.BLACK);
g2D drawRect(0, 0, image.getWidth(), image.getHeight());
repaint();
}
public void setFarbe(Color farbe) {
this.farbe = farbe;
}
paintComponent stellt das Image beim Programmstart dar und erneuert die Darstellung bei Bedarf
ohne es zu verändern.
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image,
(breite - image.getWidth())/2,
(hoehe - image.getHeight())/2,
null);
}
}
copyleft:munz
154
Fachschulen Lörrach
8
8.2
Anhang
Java
Quelltexte zum Grafikprogramm mit BufferedImage
Das Grundgerüst:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JGrafik extends JFrame implements ActionListener {
private GrafikPanel zeichenFlaeche;
private JButton bnNeu;
public JGrafik(String title) {
super (title);
addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent evt) {
System.exit(0);}}
);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
bnNeu = new JButton("Neue Zeichnung");
bnNeu.addActionListener(this);
cp.add(bnNeu, BorderLayout.NORTH);
zeichenFlaeche = new GrafikPanel(300, 200);
cp.add(zeichenFlaeche);
pack();
setVisible(true);
}
}
public void actionPerformed(ActionEvent event) {
zeichenFlaeche.zeichneMuster();
repaint();
}
public static void main (String[] args) {
new JGrafik("Grafik-Beispiel");
}
Die Zeichenfläche mit einer Image-Komponente:
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
public class GrafikPanel extends JPanel {
private int breite, hoehe;
private BufferedImage image;
private Graphics2D g2D;
public GrafikPanel(int breite, int hoehe) {
this.breite = breite;
this.hoehe = hoehe;
setPreferredSize(new Dimension(breite, hoehe));
erstelleGrafik();
}
private void erstelleGrafik() {
int b = breite - 80, h = hoehe - 80;
image = new BufferedImage(b, h, BufferedImage.TYPE_INT_RGB);
g2D = image.createGraphics();
g2D.setColor(getBackground());
g2D.fillRect(0, 0, b, h);
}
copyleft:munz
155
Fachschulen Lörrach
8
Anhang
}
copyleft:munz
Java
public void zeichneGrafik() {
int b = image.getWidth(), h = image.getHeight();
g2D.setColor(getBackground());
g2D.fillRect(0, 0, b, h);
g2D.setColor(Color.BLACK);
for (int x = 0; x < b; x++) {
if (Math.random() < 0.5)
g2D.drawLine(x, 0, x, h);
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int x = (breite - image.getWidth())/2;
int y = (hoehe - image.getHeight())/2;
g.drawImage(image, x, y, this);
}
156
Fachschulen Lörrach
8
8.3
Anhang
Java
Quelltexte zum Pakel turtle
Die Klasse turtle.java
package turtle;
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
public class Turtle extends JPanel {
private int b, h, xa = 0, ya = 0;
private int orientierungsWinkel = 0;
private double bogen=0;
private BufferedImage image;
private Graphics g;
public Turtle(){
this(200, 200);
}
public Turtle(int b, int h){
this.b = b;
this.h = h;
setPreferredSize(new Dimension(b, h));
image = new BufferedImage(b,h, BufferedImage.TYPE_INT_RGB);
g = image.getGraphics();
g.setColor(Color.white);
g.fillRect(0,0,b,h);
g.setColor(Color.BLACK);
}
public int getTurtleX(){
return xa;
}
public int getTurtleY(){
return ya;
}
public void setTurtle(int xa, int ya){
this.xa = xa;
this.ya = ya;
}
public void vor(int schritt){
int dx = (int) (schritt* Math.sin(bogen));
int dy = (int) (schritt* Math.cos(bogen));
int xe = xa + dx;
int ye = ya - dy;
g.drawLine(xa,ya,xe,ye);
xa=xe; ya=ye;
}
public void rechts(int drehWinkel){
setOrientierungsWinkel(getOrientierungsWinkel()+drehWinkel);
bogen = winkelInBogen(getOrientierungsWinkel());
}
public int getOrientierungsWinkel(){
return orientierungsWinkel;
}
public void setOrientierungsWinkel(int orientierungsWinkel){
this.orientierungsWinkel = orientierungsWinkel;
bogen = winkelInBogen(getOrientierungsWinkel());
}
copyleft:munz
157
Fachschulen Lörrach
8
Anhang
}
Java
private double winkelInBogen(int winkel){
return 2 * Math.PI*winkel/360;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image,0,0,this);
}
Das Interface TGrafik.java:
package turtle;
public interface TGrafik{
public abstract void zeichne(Turtle t);
}
Quelltext TurtleGUI:
package turtle;
import
import
import
public
java.awt.*;
javax.swing.*;
java.awt.event.*;
abstract class TurtleGUI extends JFrame
implements TGrafik, ActionListener {
protected Turtle t;
private JButton zeichneButton;
public TurtleGUI(String titel) {
super(titel);
t = new Turtle();
init();
}
public TurtleGUI(String titel, int b, int h) {
super(titel);
t = new Turtle(b, h);
init();
}
private void init() {
addWindowListener(new WindowAdapter() {
public void windowClosing (WindowEvent evt) {
System.exit(0);}}
);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
cp.add(t, BorderLayout.CENTER);
zeichneButton = new JButton("Zeichne!");
zeichneButton.addActionListener(this);
cp.add(zeichneButton, BorderLayout.SOUTH);
setLocation(10, 10);
pack();
setVisible(true);
setResizable(false);
}
public void actionPerformed(ActionEvent e){
zeichne(t);
repaint();
}
}
copyleft:munz
158
Fachschulen Lörrach
8
8.4
Anhang
Java
Quelltexte zum Kommunikationsserver
import java.awt.*; import java.awt.event.*;
import java.net.*; import java.io.*;
public class Kserver2 extends Frame implements ActionListener {
TextArea textEin = new TextArea();
TextArea textAus = new TextArea();
TextField rechnerAdresse= new TextField("127.0.0.1");
Sender meinSender=new Sender();
public Kserver2() {
super("Gib mir die Dose");
setLayout(new FlowLayout());
setSize(500,550);
Label e = new Label("Eingang:"); add(e);
add(textEin); textEin.setEditable(false);
Label a = new Label("Ausgang:"); add(a);
add(textAus);
add(rechnerAdresse);
Button b = new Button("Absenden"); add(b);
b.addActionListener(this);
setVisible(true);
addWindowListener(new FensterLauscher());
verbinde();
}
public void verbinde() {
ServerSocket verbindung;
try {
verbindung=new ServerSocket(6000);
while (true) {
Socket lauschen=verbindung.accept();
Empfaenger meinEmpfaenger=new Empfaenger(lauschen);
meinEmpfaenger.start();
}
}
catch (Exception e) { };
}
public void actionPerformed(ActionEvent e) {
meinSender.sende();
}
public static void main (String[] args) {
new Kserver2();
}
copyleft:munz
159
Fachschulen Lörrach
8
Anhang
Java
class Empfaenger extends Thread {
Socket erlauscht;
public Empfaenger(Socket lauschen) {
erlauscht=lauschen;
}
public void run() {
String sEin="";
try {
String absender=erlauscht.getInetAddress().getHostAddress();
InputStreamReader portLeser;
portLeser=new InputStreamReader(erlauscht.getInputStream());
BufferedReader eingabe=new BufferedReader(portLeser);
sEin=eingabe.readLine();
while (sEin!=null) {
textEin.append(absender+": "+sEin+"\n");
sEin=eingabe.readLine();
}
eingabe.close();
}
catch (Exception e) {}
}
}
class Sender{
public void sende() {
try {
Socket zumNachbar =
new Socket(rechnerAdresse.getText(),6000);
PrintWriter ausgabe;
ausgabe=new PrintWriter(zumNachbar.getOutputStream(),true);
ausgabe.println(textAus.getText());
ausgabe.close();
textAus.setText("");
}
catch (Exception e) {}
}
}
class FensterLauscher extends WindowAdapter {
public void windowClosing(WindowEvent e) { System.exit(0); }}
}
copyleft:munz
160
Fachschulen Lörrach
8
8.5
Anhang
Java
Quelltexte zum CGI-Server
import java.awt.*; import java.awt.event.*;
import java.net.*; import java.io.*;
public class CGIServer extends Frame implements ActionListener {
TextArea textEin = new TextArea();
TextArea textAus = new TextArea();
TextField rechnerAdresse= new TextField("127.0.0.1");
Sender meinSender=new Sender();
String sEin="";
Kunde meinKunde = new Kunde();
public CGIServer() {
super("Gib mir die Dose");
setLayout(new FlowLayout());
setSize(500,550);
Label e = new Label("Eingang:"); add(e);
add(textEin); textEin.setEditable(false);
Label a = new Label("Ausgang:"); add(a);
add(textAus);
add(rechnerAdresse);
Button b = new Button("Absenden"); add(b);
b.addActionListener(this);
setVisible(true);
addWindowListener(new FensterLauscher());
verbinde();
}
public void verbinde() {
ServerSocket verbindung;
try {
verbindung=new ServerSocket(6000);
while (true) {
Socket lauschen=verbindung.accept();
Empfaenger meinEmpfaenger=new Empfaenger(lauschen);
meinEmpfaenger.start();
}
}
catch (Exception e) { };
}
public void actionPerformed(ActionEvent e) {
meinSender.sende(textAus.getText());
textAus.setText("");
}
public static void main (String[] args) {
new CGIServer();
}
copyleft:munz
161
Fachschulen Lörrach
8
Anhang
Java
class Empfaenger extends Thread {
Socket erlauscht;
public Empfaenger(Socket lauschen){
erlauscht=lauschen;
}
public void run() {
try {
String absender=erlauscht.getInetAddress().getHostAddress();
InputStreamReader portLeser;
portLeser=new InputStreamReader(erlauscht.getInputStream());
BufferedReader eingabe=new BufferedReader(portLeser);
sEin=eingabe.readLine();
textEin.append(absender+": "+sEin+"\n");
eingabe.close();
meinKunde.entziffern();
meinKunde.benachrichtigen();
}
catch (Exception e) {}
}
}
class Sender{
public void sende(String s) {
try {
Socket zumNachbar = new Socket("localhost",6000);
PrintWriter ausgabe;
ausgabe=new PrintWriter(zumNachbar.getOutputStream(),true);
ausgabe.println(s);
}
catch (Exception e) {}
}
}
copyleft:munz
162
Fachschulen Lörrach
8
Anhang
Java
class Kunde{
String[] varName = new String[10];
String[] varWert = new String[10];
void entziffern() {
int i=0; int n=0;
char ch=sEin.charAt(i);
while (ch != '?') {
i=i+1;
ch=sEin.charAt(i);
}
while (ch != ' ') {
varName[n]=""; varWert[n]="";
i=i+1;
ch=sEin.charAt(i);
while (ch != '=') {
varName[n]=varName[n]+ch;
i=i+1;
ch=sEin.charAt(i);
}
i=i+1;
ch=sEin.charAt(i);
while ((ch != '&') && (ch != ' ')) {
varWert[n]=varWert[n]+ch;
i=i+1;
ch=sEin.charAt(i);
}
n=n+1;
}
}
void benachrichtigen() {
meinSender.sende("HTTP/1.0 200 OK\n“ +
Content-type: text/html\n\n");
for (int n=0; n<3; n=n+1)
meinSender.sende(varName[n]+" : "+ varWert[n]+ "<BR>\n");
}
}
class FensterLauscher extends WindowAdapter {
public void windowClosing(WindowEvent e) { System.exit(0); }
}
}
copyleft:munz
163
Fachschulen Lörrach
8
8.6
Anhang
Java
Quelltexte zum Server mit Datenbankzugang
import java.awt.*; import java.awt.event.*;
import java.net.*; import java.io.*; import java.sql.*;
public class AccessServer3 extends Frame implements ActionListener {
TextArea textEin = new TextArea();
TextArea textAus = new TextArea();
TextField rechnerAdresse= new TextField("127.0.0.1");
Sender meinSender=new Sender();
String sEin="";
Kunde meinKunde = new Kunde();
public AccessServer3() {
super("Gib mir die Dose");
setLayout(new FlowLayout());
setSize(500,550);
Label e = new Label("Eingang:"); add(e);
add(textEin); textEin.setEditable(false);
Label a = new Label("Ausgang:"); add(a);
add(textAus);
add(rechnerAdresse);
Button b = new Button("Absenden"); add(b);
b.addActionListener(this);
setVisible(true);
addWindowListener(new FensterLauscher());
verbinde();
}
public void verbinde() {
ServerSocket verbindung;
try {
verbindung=new ServerSocket(6000);
while (true) {
Socket lauschen=verbindung.accept();
Empfaenger meinEmpfaenger=new Empfaenger(lauschen);
meinEmpfaenger.start();
}
}
catch (Exception e) { };
}
public void actionPerformed(ActionEvent e) {
meinSender.sende(textAus.getText());
textAus.setText("");
}
public static void main (String[] args) {
new AccessServer3();
}
copyleft:munz
164
Fachschulen Lörrach
8
Anhang
Java
class Empfaenger extends Thread{
Socket erlauscht;
public Empfaenger(Socket lauschen){
erlauscht=lauschen;
}
public void run() {
try {
String absender=erlauscht.getInetAddress().getHostAddress();
InputStreamReader portLeser;
portLeser=new InputStreamReader(erlauscht.getInputStream());
BufferedReader eingabe=new BufferedReader(portLeser);
sEin=eingabe.readLine();
textEin.append(absender+": "+sEin+"\n");
eingabe.close();
meinKunde.entziffern();
meinKunde.speichern();
meinKunde.benachrichtigen();
}
catch (Exception e) {}
}
}
class Sender{
public void sende(String s) {
try {
Socket zumNachbar = new Socket("localhost",6000);
PrintWriter ausgabe;
ausgabe=new PrintWriter(zumNachbar.getOutputStream(),true);
ausgabe.println(s);
}
catch (Exception e) {}
}
}
copyleft:munz
165
Fachschulen Lörrach
8
Anhang
Java
class Kunde{
String[] varName = new String[10];
String[] varWert = new String[10];
void entziffern() {
int i=0; int n=0;
char ch=sEin.charAt(i);
while (ch != '?') {
i=i+1;
ch=sEin.charAt(i);
}
while (ch != ' ') {
varName[n]=""; varWert[n]="";
i=i+1;
ch=sEin.charAt(i);
while (ch != '=') {
varName[n]=varName[n]+ch;
i=i+1;
ch=sEin.charAt(i);
}
i=i+1;
ch=sEin.charAt(i);
while ((ch != '&') && (ch != ' ')) {
varWert[n]=varWert[n]+ch;
i=i+1;
ch=sEin.charAt(i);
}
n=n+1;
}
}
void benachrichtigen() {
meinSender.sende(
"HTTP/1.0 200 OK\nContent-type: text/html\n\n"
);
for (int n=0; n<3; n=n+1)
meinSender.sende(varName[n]+" : "+ varWert[n]+ "<BR>\n");
}
void speichern() {
try {
String treiber="sun.jdbc.odbc.JdbcOdbcDriver";
Class.forName(treiber);
String db="jdbc:odbc:Adressen";
Connection con =
java.sql.DriverManager.getConnection(db,"","");
Statement s = con.createStatement();
String eintrag;
eintrag="insert into Klassenliste("+varName[0];
for (int i=1; i<n; i=i+1) eintrag=eintrag+","+varName[i];
eintrag=eintrag+")values('"+varWert[0]+"'";
for (int i=1; i<n; i=i+1)
eintrag = eintrag+",'"+varWert[i]+"'";
eintrag=eintrag+")";
s.execute(eintrag);
con.close();
meinSender.sende("alles klar!\n");
}
catch (Exception e) { System.out.println(""+e.getMessage());}
}
}
class FensterLauscher extends WindowAdapter {
public void windowClosing(WindowEvent e) { System.exit(0); }
}
}
copyleft:munz
166
Fachschulen Lörrach
9
Java im Internet
9
Java im Internet
Java
JavaEditor:
http://www.bildung.hessen.de/abreich/inform/skii/material/java/editor.htm
JCreator:
http://www.jcreator.com/
Sun: Java™ 2 Platform, Standard Edition (J2SE™)
http://java.sun.com/j2sdk/
Sun: Java™ 2 Platform, Enterprise Edition (J2EE™)
http://java.sun.com/j2see/
Sun: The Java-Tutorial
http://java.sun.com/docs/books/tutorial/
→ Informationen zu Servlets:
http://java.sun.com/docs/books/tutorial/servlets/TOC.html
Sun: Tutorials & Short Courses
http://developer.java.sun.com/developer/onlineTraining/
Sun: Java Foundation Classes (JFC), Cross Platform GUIs & Graphics
http://java.sun.com/products/jfc/
→ The Swing Connection
http://java.sun.com/products/jfc/tsc/
→ Swing Connection, Acticle Index
http://java.sun.com/products/jfc/tsc/articles
→ Getting Started with Swing
http://java.sun.com/productsjfc/tsc/articles/getting_started
→ A Swing Architecture Overview
http://java.sun.com/productsjfc/tsc/articles/architecture
→ Painting in AWT and Swing
http://java.sun.com/productsjfc/tsc/articles/painting/
Das Java-Buch von Guido Krüger:
http://www.javabuch.de/
Tomcat (Servlet Container):
http://jakarta.apache.org/
MySQL-Treiber für Java:
http://www.worldserver.com/mm.mysql/
Datenbank-Management-System MySQL:
http://www.mysql.com/downloads/index.html
Landesinstitut für Erziehung und Unterricht Stuttgart, Musterlösungen für Novell, Windows, Linux:
http://www.lehrerfortbildung-bw.de/netz/muster/
Java-Seiten der Autoren:
http://www.lehrer.uni-karlsruhe.de/~za220/htm/kurse/informat/inform.html
http://www.pohlig.de
http://ww2.lehrer.uni-karlsruhe.de/~taulien/info/java/java.html
copyleft:munz
167
(Reinhold Ley)
(Michael Pohlig)
(Matthias Taulien)
Fachschulen Lörrach
10
Index
Java
10 Index
abstract .........................................................7
Abstract Windowing Toolkit ..........................12
AbstractAction .............................................58
AbstractButton .............................................55
abstrakte Klasse ............................................4
Access-Treiber .......................................... 140
ActionListener.................................. 30, 34, 41
actionPerformed ..........................................30
Adapter-Klasse ............................................37
addActionListener ........................................31
addWindowListener .....................................37
AFC.............................................................13
anonyme Klasse .............................. 35, 37, 74
Antialiasing ............................................ 20, 51
API ..............................................................19
Application Foundation Classes ...................13
Attribut...........................................................6
AVL-Baum ................................................. 127
AWT ............................................................12
BasicStroke ........................................... 19, 52
Baum......................................... 116, 117, 126
binärer Baum ............................................. 126
binärer Suchbaum ............................. 126, 127
Blätter........................................................ 126
BOLD ..........................................................21
BorderFactory..............................................29
BorderLayout ................................... 25, 27, 41
Box........................................................ 28, 41
BoxLayout ............................................. 25, 27
BufferedImage ....................................... 16, 18
Canvas ........................................................14
charWidth ....................................................22
clearRect .....................................................17
ColorChooser ..............................................51
Component............................................ 15, 24
Container......................................... 14, 24, 25
ContentPane................................................25
copyArea .....................................................18
createBevelBorder .......................................29
createCompoundBorder...............................29
createEmptyBorder......................................29
createEtchedBorder.....................................30
createGlue...................................................28
createGraphics ............................................16
createHorizontalGlue ...................................28
createHorizontalStrut ...................................28
createLineBorder .........................................30
createLoweredBevelBorder..........................29
createMatteBorder .......................................30
createRaisedBevelBorder ............................29
createTitledBorder .......................................30
CreateVerticalGlue ......................................28
createVerticalStrut .......................................28
Dialog.................................................... 21, 25
DNS .......................................................... 140
Double-Buffering............................ 14, 97, 101
drawArc .......................................................17
drawImage............................................. 17, 18
drawLine......................................................17
drawOval .....................................................17
copyleft:munz
drawPolygon................................................17
drawPolyline ................................................17
drawRect .....................................................17
drawRoundRect ...........................................17
drawString ...................................................18
E-Commerce ............................................. 151
Event...........................................................30
Event Delegation Model ...............................30
EventListener......................................... 30, 35
Fachklasse ....................................................4
Fachklassen ................................................30
FIFO.................................................. 117, 122
fillArc ...........................................................17
fillOval .........................................................17
fillPolygon ....................................................17
fillRect .........................................................17
fillRoundRect ...............................................17
First In First Out......................................... 122
FlowLayout ............................................ 25, 26
FocusListener ..............................................34
Font.............................................................21
Frame..........................................................25
FTP ........................................................... 133
getAscent ....................................................22
getContentPane...........................................25
getDescent ..................................................22
getFamily.....................................................21
getFont ........................................................21
getFontMetrics.............................................21
getGraphics ........................................... 16, 18
getHeight .....................................................22
getKeyCode.................................................61
getLeading...................................................22
getSize ........................................................21
getSource ....................................................32
getStyle .......................................................21
getWindowListeners() ..................................45
GlassPane...................................................25
Graphical User Interface .......................... 5, 12
Graphics................................................ 16, 19
Graphics2D .................................................19
GridBagLayout....................................... 25, 27
GridLayout............................................. 25, 27
GUI.......................................................... 5, 12
GUI-Klasse ....................................................4
GUI-Klassen ................................................30
Hostadresse .............................................. 133
HTML-Client .............................................. 149
HTMLConverter ...........................................13
http ............................................................ 133
IFC ..............................................................13
init() .............................................................45
innere Klasse...............................................35
In-Order..................................................... 128
Interface .......................................... 30, 37, 72
Internet Foundation Classes ........................13
IP-Adresse................................................. 133
ipconfig...................................................... 133
isLeftMouseButton ................................. 62, 63
ITALIC.........................................................21
168
Fachschulen Lörrach
10
Index
Java
JApplet .................................................. 40, 45
jar-Datei.......................................................56
Java Foundation Classes....................... 12, 13
java.util ........................................................54
Java2D ........................................................51
Java-Plug-In ................................................13
Java-Virtual-Machine ...................................12
JComboBox........................................... 51, 64
JComponent .......................................... 15, 24
JDialog ........................................................25
JFC ....................................................... 12, 13
JFrame ........................................................25
JLayeredPane .............................................25
JMenuBar ....................................................51
JPanel .........................................................25
JRadioButton...............................................41
JRootPane...................................................25
JWindow......................................................25
Kante......................................................... 126
Keller......................................................... 117
KeyAdapter..................................................61
KeyEvent.VK_SHIFT ...................................61
keyPressed..................................................61
Klasse ...........................................................4
Knoten....................................................... 126
Last In First Out ......................................... 120
LayoutManager............................................25
leichtgewichtig .............................................12
LIFO .................................................. 117, 120
Liste .................................................. 116, 117
localhost .................................................... 133
logischer......................................................21
Mehrfachvererbung......................................72
MenüLeiste..................................................25
Methode ........................................................6
Middleware ................................................ 144
model-view-controller...................................13
Monospaced ................................................21
MouseAdapter ....................................... 38, 62
mouseClicked ..............................................37
mouseDragged ...................................... 37, 63
mouseEntered .............................................37
mouseExited................................................37
MouseListener .............................................37
MouseMotionAdapter............................. 38, 63
MouseMotionListener...................................37
mouseMoved...............................................37
mousePressed.............................................37
mouseReleased..................................... 37, 63
MVC ............................................................13
Netzadresse .............................................. 133
notifyAll() .....................................................94
Objekt............................................................4
Objektvariable..............................................10
ODBC........................................................ 140
OOP ..............................................................4
package.........................................................7
paint ............................................................15
paintComponent .............................. 15, 57, 61
PL&F ...........................................................12
copyleft:munz
PLAIN..........................................................21
pluggable look and feel .......................... 12, 13
point ............................................................21
Polymorphie ..................................................4
POP .......................................................... 133
Port ........................................................... 133
Post-Fix-Notation ....................................... 129
Post-Order................................................. 128
Pre-Order .................................................. 128
privat .............................................................6
protected .......................................................6
Rekursion .......................................... 109, 126
RenderingHints...................................... 20, 59
repaint .........................................................16
RepaintManager ..........................................16
Request ..................................................... 149
requestFocus...............................................62
Response .................................................. 149
Runnable .....................................................72
SansSerif.....................................................21
schwergewichtig ..........................................12
Serif.............................................................21
Server................................................ 133, 146
Servlet ....................................................... 146
setBorder.....................................................29
setBounds ...................................................26
setContentPane ...........................................25
setFont ........................................................21
setStroke .....................................................19
Shape..........................................................52
Socket ....................................................... 133
SQL........................................................... 141
Stack ........................................... 69, 116, 117
stringWidth ..................................................22
Swing ..........................................................12
SwingUtilities ......................................... 62, 63
synchronized ......................................... 93, 94
System-DNS.............................................. 140
TCP/IP....................................................... 133
Telnet ........................................................ 133
Thread.........................................................69
Turtle...........................................................79
UML ..............................................................6
UML-Notation ................................................6
Unified Modelling Language...........................6
Unified Ressource Locator...........................56
URL............................................. 56, 148, 149
Vector.................................................... 54, 59
Vererbung......................................................4
VM......................................................... 12, 69
wait() ...........................................................94
Warenkorb................................................. 151
Window .......................................................25
WindowAdapter ..................................... 37, 51
windowClosing.............................................37
WindowListener ............................... 37, 40, 45
WINIPCFG ................................................ 133
Wurzel ....................................................... 126
X_AXIS........................................................27
Y_AXIS........................................................27
169
Fachschulen Lörrach
11
Inhaltsverzeichnis
Java
11 Inhaltsverzeichnis
1
2
3
4
5
6
7
8
Einführung in die objektorientierte Programmierung (OOP) .......................................................... 4
1.1
Was ist eine Klasse, was ist ein Objekt?................................................................................ 4
1.2
Das Miniprojekt „Figuren“...................................................................................................... 4
1.3
Trenne Fachklassen von den GUI-Klassen! .......................................................................... 5
1.4
Die Fachklassen Quadrat und Kreis ...................................................................................... 5
1.5
Die abstrakte Klasse Figur .................................................................................................... 6
1.6
Pakete .................................................................................................................................. 7
1.7
Die Quelltexte ....................................................................................................................... 9
Grafik-Programmierung (GUI) .................................................................................................... 12
2.1
Das Abstract Windowing Toolkit (AWT)............................................................................... 12
2.2
Swing ................................................................................................................................. 12
2.3
Andere Java Foundation Classes (JFC) .............................................................................. 13
2.4
Grafik-Programmierung mit Swing....................................................................................... 14
2.5
Die wichtigsten Methoden zum Zeichnen grafischer Objekte ............................................... 16
2.6
Grafiken mit der Klasse BufferedImage........................................................................... 18
2.7
Java 2D Grafik API ............................................................................................................. 19
2.8
Font–Objekte ...................................................................................................................... 21
2.9
Aufgaben ............................................................................................................................ 23
2.10 Die Swing-Komponenten.................................................................................................... 24
2.11 Die LayoutManager............................................................................................................ 25
2.12 Rahmen............................................................................................................................. 29
2.13 Ereignisse.......................................................................................................................... 30
2.14 Die GUI-Klassen zum Miniprojekt „Figuren“........................................................................ 40
2.15 Einige GUI-Beispielprogramme .......................................................................................... 46
2.16 Aufgaben ........................................................................................................................... 67
Threads ..................................................................................................................................... 69
3.1
Programme sind Threads.................................................................................................... 69
3.2
Eine eigene Thread-Klasse ................................................................................................. 70
3.3
Threads – Runnable ........................................................................................................... 72
3.4
Runnable als anonyme Klasse ............................................................................................ 74
Rekursionen .............................................................................................................................. 76
4.1
Was sind Rekursionen? ...................................................................................................... 76
4.2
Rekursive Methoden in Java ............................................................................................... 76
4.3
Rekursive Grafiken ............................................................................................................. 79
4.4
Binärbaum .......................................................................................................................... 81
4.5
Kochkurve .......................................................................................................................... 83
4.6
Sierpinski-Dreieck............................................................................................................... 85
4.7
Pythagoras-Baum ............................................................................................................... 86
Threads und Rekursionen – ein alternativer Zugang mit Applets ................................................ 90
5.1
Nebenläufige Prozesse (Threads) ....................................................................................... 90
5.2
Graphisch animierte Applets mit Threads ............................................................................ 97
5.3
Graphisch animierte Applets mit Threads und Turtlegraphik .............................................. 101
5.4
Rekursion ......................................................................................................................... 109
Abstrakte Datentypen .............................................................................................................. 117
6.1
Was ist eine Liste?............................................................................................................ 117
6.2
Die abstrakte Liste ............................................................................................................ 118
6.3
ListeLIFO.......................................................................................................................... 120
6.4
ListeFIFO.......................................................................................................................... 122
6.5
Listen-Demo ..................................................................................................................... 124
6.6
Der abstrakte Datentyp Baum ........................................................................................... 126
Java-Server im Intranet und Internet ........................................................................................ 133
7.1
Direkte Kommunikation zweier Rechner über Sockets (TCP/IP) ........................................ 133
7.2
Client-Server-Kommunikation über das Common Gateway Interface (CGI) ....................... 136
7.3
Datenbanken .................................................................................................................... 139
7.4
Servlets ............................................................................................................................ 149
Anhang .................................................................................................................................... 155
copyleft:munz
170
Fachschulen Lörrach
11
Inhaltsverzeichnis
Java
8.1
Quelltexte zur den einfachen Grafik-Programmen ............................................................. 155
8.2
Quelltexte zum Grafikprogramm mit BufferedImage ...................................................... 158
8.3
Quelltexte zum Pakel turtle ............................................................................................... 160
8.4
Quelltexte zum Kommunikationsserver ............................................................................. 162
8.5
Quelltexte zum CGI-Server ............................................................................................... 164
8.6
Quelltexte zum Server mit Datenbankzugang.................................................................... 167
9 Java im Internet ....................................................................................................................... 170
10 Index....................................................................................................................................... 171
11 Inhaltsverzeichnis.................................................................................................................... 173
copyleft:munz
171
Fachschulen Lörrach
Herunterladen