Java

Aus Debacher-Wiki
Wechseln zu: Navigation, Suche

1. Einführung

Java ist eine relativ junge Programmiersprache. Sie wurde 1991 von der Firma Sun im Rahmen eines Forschungsprojektes entwickelt. Ziel war es ursprünglich eine handliche und systemübergreifende Sprache für elektronische Haushaltsgeräte zu entwickeln. Die Sprache zeigt eine große Ähnlichkeit zu C und C++. Im Zusammenhang mit dem großen Erfolg des Web hat Sun die Sprache dann an diese Bedürfnisse angepasst.

Was von dem ursprünglichen Ansatz auf alle Fälle beibehalten wurde, ist die Plattformunabhängigkeit.


1.1. Applets und Anwendungen

Mit Java kann man zwei unterschiedliche Arten von Programmen entwickeln. Einerseits die Java-Anwendungen, das sind Programme, die allein lauffähig sind. Hierfür ist natürlich ein plattformabhängiger Compiler notwendig. Der von Sun mitgelieferte Browser HotJava ist solch eine Anwendung. Die Firma Corel hat von ihrem Office-Paket eine Java-Version erstellt.

Die vermutlich interessanteste Art von Java-Programmen sind die Applets. Hierbei handelt es sich um kleinere Anwendungen, die aus einem Browser heraus ausgeführt werden. Auch hierfür gibt es inzwischen kommerzielle Anwendungen. Die Internet-Banking Software manche Banken basiert z.B. auf solch einem Applet.


1.2. JDK

Sun hat nicht nur die Sprache entwickelt, sondern liefert auch gleich eine einfache Entwicklungsumgebung mit, das Java Development Kit (JDK). Von diesem gibt es mehrere Versionen, aktuell ist momentan die JDK-Version 1.6. Auf manchen Rechnern sind mehrere Versionen gleichzeitig vorhanden.

Für das Arbeiten mit Applets ist es wichtig zu wissen, welche Version des Java-Plugins im Browser installiert ist. Falls die älter ist, als die des Compilers kann es zu Problemen kommen. In den meisten Browsern kann man die Plugins abfragen, indem man in der Adresszeile „about:plugins“ eingibt.

2. Installation

Die Installation der ersten JDK-Versionen war recht mühsam. Inzwischen verfügt das JDK aber über eine der üblichen Installationsroutinen, die auch die notwendigen Änderungen an den Systemdateien vornimmt.

Dazu besorgt man sich, z.B. von http://java.sun.com/javase/downloads/ die benötigte Version des JDK (etwa 73 MB) und möglichst auch gleich die zur Version passende Dokumentation (56 MB). Beim JDK handelt es sich um ein selbstentpackendes Archiv, das sich in einen eigenen Ordner entpackt, bei der Dokumentation um ein ZIP-File, das möglichst im JDK-Ordner entpackt wird.


3. Ein erstes Beispiel

Verglichen mit der integrierten Entwicklungsumgebung von TurboPascal ist das JDK ein erheblicher Rückschritt. Es gibt aber auch eine Reihe von Entwicklungsumgebungen, von denen einige aber auch sehr umfangreich sind, z.B. Eclipse. Eine sehr an die schulischen Verhältnisse angepasste Entwicklungsumgebung für Windows findet sich unter http://lernen.bildung.hessen.de/informatik/javaeditor/index.htm.


3.1. Der Quelltext

Für das erste Beispiel starten wir unter Windows einen Editor wie z.B. WordPad und geben das folgende Listing ein:

import java.applet.*;
import java.awt.*;

public class HalloInternet extends Applet {

 public void paint (Graphics g) {
   g.drawString("Hallo Internet",50,50);
 }
}

Dieses Listing speichern wir dann unter dem Namen “HalloInternet.java” ab (natürlich auf Laufwerk u:), wobei man als “Nur Text” abspeichern muss. Bei WordPad sollte man den Datei-Namen auch wirklich in Anführungsstriche setzen, damit nicht noch .txt dahinter gesetzt wird.


3.2. Compilieren

Im nächsten Schritt muss nun dieses Programm compiliert werden. Dazu öffnen wir ein Dos-Fenster (Start -> Programme -> MS-DOS-Eingabeaufforderung), wechseln in den Ordner, in dem unser Quelltext liegt (also u:) und geben ein:

javac HalloInternet.java

Wenn der Compiler ohne Fehlermeldung durchläuft, er gibt dann überhaupt keine Meldung aus, müßte eine Datei HalloInternet.class vorliegen. Sollte eine Fehlermeldung wie “Befehl oder Dateiname nicht gefunden” auftauchen, so ist kein Pfad auf das bin-Verzeichnis gesetzt, in diesem Fall muss der Compiler mit vollständiger Pfadangabe aufgerufen werden. Fehlermeldungen des Compilers beruhen auf Tippfehlern im Quelltext, man muss auch auf die Groß-/Kleinschreibung achten!


3.3. In HTML-Seite einbinden

Um unser Applet, das nun in der Datei HalloInternet.class vorliegt, zu starten, benötigen wir eine HTML-Seite. Wir erstellen uns eine Seite HalloInternet.html mit folgendem Inhalt:

<html><head><title>Das erste Java-Programm</title></head>
<body>Nun kommt auch gleich das Applet:<br>
<applet code="HalloInternet.class" width=200 heigth=200></applet>
<br>
Nun ist es vorbei
</body></html>

Starten wir nun Firefox und laden die Seite HalloInternet.html, so ergibt sich das folgende Bild.

[[Datei:Javaschule-001.png|400px]

Hier sieht man die Texte aus dem HTML-Text und die Fläche des Applets. Innerhalb der weißen Fläche findet sich dann der String “Hallo Internet”. Mehr sollte unser Applet bisher ja auch nicht machen. In der Fußzeile des Firefox-Fensters finden sich Meldungen des Java-Systems. Im vorliegenden Fall wird hier nur bestätigt, dass das Applet läuft.

3.4. Erläuterungen zum Quelltext

Im Prinzip ist das nicht allzu schwierig gewesen, trotzdem gibt es doch etwas Erläuterungsbedarf. Einige Dinge können aber erst viel später erklärt werden. In den ersten beiden Zeilen:

    import java.applet.*;
    import java.awt.*;

werden Standardbibliotheken eingebunden, damit wir die entsprechenden Dinge nutzen können. Da wir ein Applet schreiben, müssen wir auf alle Fälle java.applet.* einbinden. Für die graphische Ausgabe das Abstract Windows Toolkit (AWT). Jedes dieser Pakete besteht aus mehreren Unterklassen, die wir vorsichtshalber jeweils alle einbinden, deshalb der Stern am Ende der Zeilen. Wir hätten auch kürzer schreiben können:

    import java.*

doch dann hat der Compiler unnötig viel zu durchsuchen.

    public class HalloInternet extends Applet {...}

Wir schreiben ein Applet, das sich deshalb von der vordefinierten Klasse Applet ableitet. Jedes Applet verfügt automatisch über eine Methode (Prozedur) paint, die wir für unsere Zwecke neu definieren:

public void paint (Graphics g) {
   g.drawString("Hallo Internet",50,50);
}

Aufgerufen wird paint dann jeweils automatisch, wenn das Fenster neu gezeichnet werden muss. Der Name darf daher auch nicht verändert werden. Die Methode Paint bekommt immer ein Objekt vom Typ Graphics übergeben, auf den wir schreibend zugreifen können, hier mit g.drawString. Damit wird ab der Position (50,50) unser Text ausgegeben. Das zugehörige Koordinatensystem beginnt in der linken oberen Ecke mit dem Punkt (0,0).

Das Schlüsselwort void gibt an, dass Paint keinen Wert zurück gibt, also eine Prozedur und keine Funktion ist.

Das Schlüsselwort public gibt an, dass auch von außen auf diese Methode zugegriffen werden kann, was hier ja auch erforderlich ist.

4. Grafik mit dem AWT

Neben der Methode drawString verfügt das AWT über viele weitere Funktionen zum Zeichnen, hier eine Zusammenstellung.

4.1Zeichenfunktionen

drawLine(x1, y1, x2, y2)                      zeichnet eine Linie
drawRect(x, y, width, heigth)                 zeichnet ein (leeres) Rechteck
fillRect(x, y, width, height)                 zeichnet ein gefülltes Rechteck
clearRect(x, y, width, heigth)                löscht einen rechteckigen Bereich
drawRoundRect(x,y,width,height,arcw,arch)     zeichnet ein Rechteck mit abgerundeten Ecken
fillRoundRect(x,y,width,height,arcw,arch)     zeichnet ein gefülltes Rechteck mit abgerundeten Ecken
draw3DRect(x,y,width,heigth, boolean raised)  zeichnet ein Rechteck mit 3D Effekt
fill3DRect(x,y,width,heigth, boolean raised)  zeichnet ein gefülltes Rechteck mit 3D Effekt
drawOval(x, y, width, heigth)                 zeichnet eine Ellipse
fillOval(x, y, width, heigth)                 zeichnet eine gefüllte Ellipse
drawArc(x,y,width,height,startAngle,arcAngle) zeichnet einen Kreisbogen
fillArc(x,y,width,height,startAngle,arcAngle) zeichnet einen gefüllten Kreisbogen
drawPolygon(points)                           zeichnet ein Vieleck (Polygon)
fillPolygon(points)                           zeichnet ein gefülltes Vieleck (Polygon)
drawString(string s, x, y)                    gibt einen String aus
copyArea(xSrc,ySrc,width,height,xDest,yDest)  kopiert einen rechteckigen Bereich

Beispielprogramme für drawLine, drawRect und drawOval sollen hier nicht angegeben werden, sie lassen sich recht einfach aus dem einführenden Beispiel ableiten. Etwas schwieriger ist der Umgang mit Polygonen, Kreisbögen und Texten. Deshalb folgen hierzu jeweils Beispiele.


4.2.Linienzüge (Polygone)

Ein Linienzug wird beschrieben durch eine Anzahl von Punkten, die nacheinander verbunden werden. Bei einem gefüllten Linienzug wird anschließend der umschlossene Bereich mit der Füllfarbe gefüllt.

Ein normaler Linienzug wir nicht automatisch geschlossen, im Bedarfsfall muss man den ersten Punkt am Ende nochmals anfügen. Ein gefüllter Linienzug wird automatisch geschlossen.

Für die Angabe der Punkte gibt es mehrere Möglichkeiten. Entweder arbeitet man mit Koordinaten-Arrays oder mit Punktlisten. Da wir noch keine Arrays eingeführt haben, erst einmal mit Punktfeldern. Das folgende Listing zeigt, wie man ein geschlossenes Dreieck zeichnen kann.

Würden wir in diesem Beispiel fillPolygon anstelle von drawPolygon benutzen, so erhalten wir ein gefülltes Dreieck.

import java.applet.*;
import java.awt.*;

public class Polygone extends Applet {

 public void paint (Graphics g) {
   Polygon poly = new Polygon();
   poly.addPoint(10,10);
   poly.addPoint(100,10);
   poly.addPoint(50,110);
   poly.addPoint(10,10);
   g.drawPolygon(poly);
 }
}

Es wird eine Variable poly vom Typ Polygon erzeugt, in die die einzelnen Punkte mittel poly.addPoint hineingeschrieben werden. Anschließend wird diese Variable an drawPolygon übergeben.

Das gleiche Dreieck nun mit Arrays als Parametern. Die Arrays werden hier initialisiert, indem die Werte aufgezählt werden.

import java.applet.*;
import java.awt.*;

public class Polygone extends Applet {

 public void paint (Graphics g) {
  int xCoord[] = {10, 100, 50, 10};    //die x-Koordinaten
  int yCoord[] ={10, 10, 110, 10};    // die y-Koordinaten
  int anz = xCoord.length;
   g.drawPolygon(xCoord,yCoord,anz );
 }
}

Wie man an diesem Beispiel sieht, verfügt ein Array über eine eingebaute Methode length um die Anzahl der Elemente zu bestimmen.


4.3.Kreisbögen

Die folgende Zeile in einen entsprechenden Applet erzeugt einen einfachen Kreisbogen:

g.drawArc(100,100,70,30,0,270 );

Beispielprogramm Kreisbogen.html

Java01.png

Das Oval ist sowohl vom oberen, als auch vom unteren Rand jeweils 100 Punkte entfernt. Seine Breite beträgt 70 Punkte, seine Höhe 30 Punkte. Starten tut der Bogen im Osten (0°), sein Ende liegt im Süden (270° oder auch -90°). Gewöhnungsbedürftig ist sicherlich die Tatsache, dass die ersten beiden Zahlen nicht den Mittelpunkt des Kreises angeben, sondern seine linke obere Ecke.

4.4.Textausgaben

Mit dem AWT kann man nicht einfach nur Text ausgeben, sondern man hat auch Einfluß auf die Textgestaltung. Dies zeigt das folgende Beispiel.

import java.applet.*;
import java.awt.*;

public class Textausgaben extends Applet {

 public void paint (Graphics g) {
   g.drawLine(0,100,199,100 );
   g.drawLine(50,0,50,100);
   Font fnt = new Font("Courier", Font.BOLD+Font.ITALIC , 32);
   g.setFont(fnt);
   g.drawString("Ausgabe",50,100);
 }
}

Die Darstellung zeigt deutlich die Orientierung des Textes anhand der beiden Hilfslinien.

Beispielprogramm: Textausgaben.html

Java05.png

Es werden die Schriftarten

  • Serif
  • SansSerif
  • Monospaced

zur Verfügung gestellt.

Als Attribute stehen zur Verfügung:

  • BOLD
  • ITALIC
  • PLAIN

Die Attribute können einfach addiert werden, wie im obigen Beispiel. Die genaue Textdarstellung ist natürlich wieder Plattformabhängig. Es werden die konkreten Schriften und damit auch die Größen variieren. Will man Text sehr exakt platzieren können, so benötigt man Informationen über die Schriftzeichen. Dafür stellt das AWT die FontMetrics Objekte zur Verfügung, mit deren Hilfe alle Größen- und Höhenangaben ermittelt werden können. An dieser Stelle soll aber hierauf nicht eingegangen werden.


4.5.Farben

Bei den bisherigen Beispielen wurde immer mit Schwarz auf grauem Hintergrund gearbeitet. Das ist natürlich etwas langweilig. Java stellt eine Color-Klasse zur Verfügung, mit deren Hilfe sich die Bilder bunter gestalten lassen.

import java.applet.*;
import java.awt.*;

public class FarbPolygone extends Applet {

 public void paint (Graphics g) {
  int xCoord[] = {10, 100, 50, 10};    //die x-Koordinaten
  int yCoord[] ={10, 10, 100, 10};    // die y-Koordinaten
  int anz = xCoord.length;
  g.setColor(Color.red);
  g.fillPolygon(xCoord,yCoord,anz );
  g.setColor(Color.black);
  g.drawPolygon(xCoord,yCoord,anz );
 }
}


Dieses Programm erzeugt ein rotes Dreieck mit einem schwarzen Rand. Vordefiniert sind die folgenden Farben:

white, black, lightGray, gray, darkGray, red, green, blue, yellow, magenta, cyan, pink, orange

Will man eigene Farben definieren, so geht das folgendermaßen:

Color gelb = new Color(255,255,0);

FarbPolygone.html

Java06.png

Wir können die Farbe aber nicht nur zum Zeichnen verändern, sondern auch für den Hintergrund. Der Hintergrund ist bisher grau und soll im folgenden Beispiel grün werden.

Fügt man nun die folgende Zeile in das vorige Beispiel ein

    setBackground(Color.green);

so hat das nicht ganz den gewünschten Effekt. Das Dreieck liegt weiter vor dem grauen Hintergrund. Muss das Bild aber neu gezeichnet werden, weil z.B. die Größe des Browserfensters verändert wurde, so wird mit dem grünen Hintergrund gearbeitet. Das ist kein syntaktisches Problem, sondern ein logisches.

Der Hintergrund wurde schon gezeichnet, bevor die Methode paint aufgerufen wird. Daher kann die Änderung nur aktiv werden, wenn das gesamte Bild neu gezeichnet werden muss. Die Änderung muss also an anderer Stelle vorgenommen werden. Wie das geht zeigt das folgende Listing.

import java.applet.*;
import java.awt.*;

public class FarbPolygone extends Applet {

 public FarbPolygone() {
  setBackground(Color.green);
}

 public void paint (Graphics g) {
  int xCoord[] = {10, 100, 50, 10};    //die x-Koordinaten
  int yCoord[] ={10, 10, 100, 10};    // die y-Koordinaten
  int anz = xCoord.length;
  g.setColor(Color.red);
  g.fillPolygon(xCoord,yCoord,anz );
  g.setColor(Color.black);
  g.drawPolygon(xCoord,yCoord,anz );
 }
}

Hier wird ein Konstruktor benutzt. Das ist eine Methode, die beim Starten des Applets aufgerufen wird und definitionsgemäss genauso heißen muss, wie die Klasse. Das Schlüsselwort void muss hier entfallen, Konstruktoren dürfen nie einen Wert zurückgeben.

Mit dieser Veränderung funktioniert das Applet endlich so, wie es das sollte.


5. Grundlagen der objektorientierten Programmierung

In der Informatik wird man immer wieder mit der Mitteilung aufgeschreckt, dass es da schon wieder ein ganz neues Konzept gibt, nur um dann festzustellen, dass es sich um einen alten Bekannten handelt.

So auch im Bereich der objektorientierten Programmierung. Schon 1967 wurde mit Si­mula eine Sprache entwickelt, die Elemente dieses Konzeptes verwirklicht. Auch der Klassiker aller objektorientierten Programmiersprachen, Smalltalk, wurde schon in den siebziger Jahren entwickelt. Neu ist momentan die Verquickung prozeduraler Sprachen wie Java und Pascal mit Möglichkeiten der objektorientierten Programmierung. Gerade dadurch, dass ich nun die Mög­lichkeit habe, meine Probleme objektorientiert zu lösen, aber nicht dazu gezwungen bin, wird sich dieses Konzept weiter sehr stark verbreiten.

Der neue (oder alte) Ansatz bei der objektorientierten Programmierung besteht darin, Daten und Algorithmen (man spricht dann von Methoden) zu einer neuen Einheit zu­sammenzufas­sen. Das Konstrukt, das sich hierbei ergibt, wird als Objekt (oder Klasse) bezeichnet.

Insofern liegt hier eine konsequente Fortsetzung der Abstraktionsschritte vor, die wir schon hinter uns haben:

  • bei Maschinen- oder Assemblersprachen muss sich der Programmierer um jede ein­zelne Speicherstelle selbst kümmern.
  • bei höheren Programmiersprachen wie z.B. Fortran und Basic stehen schon Variable, Konstanten und Felder zur Verfügung. Über die einzelnen Speicher­stellen braucht man sich nicht mehr zu kümmern.
  • bei Programmiersprachen wie Pascal und C stehen auch benutzerdefinierte Da­tenty­pen, z.B. Records zu Verfügung, die logische Dateneinheiten zusam­menfassen.
  • bei objektorientierten Programmiersprachen entfällt auch die Trennung zwischen Daten und Methoden. Der Programmierer muss sich nicht mehr darum kümmern, die richtige Methode auf seine Daten loszulassen, dies übernimmt das System.

Bei OOP spielen einige neue (?) Begriffe eine Rolle:


5.1. Datenkapselung

Datenkapselung ist eine Möglichkeit zur Fehlervermeidung bei der Programmierung. Auf gekapselte Daten braucht (darf!) nicht direkt zugegriffen werden. Der Entwickler der Datenkapsel schafft Methoden, mit denen Zuweisungen und Abfragen vorgenom­men werden können. Hierdurch ist es möglich, die Struktur der Daten zu verändern, ohne dass ein Anwender der Datenkapsel etwas davon be­merkt. Das Programm, das diese Kapsel benutzt, braucht nicht verändert zu werden. Au­ßerdem wird so sichergestellt, das nicht wichtige Datenbestandteile aus Versehen beschädigt werden, da man ja nicht an die Daten herankommt.

Will ich zum Beispiel einen Stapelspeicher (Stack) realisieren, so kann ich das einer­seits mit einen Array oder andererseits mit einer verketteten Liste machen. Sofern ich aber nur die Methoden Push (schiebt ein Element auf den Stapel) und Pop (holt ein Element vom Stapel) zur Verfügung stelle, ist die Art der internen Realisierung voll­kommen gleichgül­tig und könnte sogar geändert werden.


5.2. Objekte

Ein einfaches Beispiel für ein Objekt ergibt sich aus dem folgenden Listing. In diesem Beispiel wird ein Objekt namens Rechteck definiert, das aus den Daten­feldern namens x, y, breite, hoehe, farbe und fuellfarbe und den Methoden verschiebe, versetze, paint und is_in besteht, sowie aus einem Konstruktor dem sechs Zahlen über­geben werden.

import java.awt.*;

public class Rechteck {
 private int x, y, breite , hoehe;
 private Color farbe, fuellfarbe;

 public Rechteck (int x_init, int y_init, 
int breite_init, int hoehe_init, 
Color farbe_init, Color fuellfarbe_init) {
  x = x_init;
  y=y_init;
  breite=breite_init;
  hoehe=hoehe_init;
  farbe=farbe_init;
  fuellfarbe=fuellfarbe_init;
 }

 public void verschiebe (int dx, int dy) {
   x=x+dx;
   y=y+dy;
 }

 public void versetze (int x_neu, int y_neu) {
   x=x_neu;
   y=y_neu;
 }

 public boolean is_in (int xpos, int ypos) {
   return ((xpos>=x - breite/2) && (xpos<=x +breite/2) && 
(ypos>=y -hoehe/2) && (ypos<=y +hoehe/2));
}

 public void paint(Graphics g) {
   g.setColor(fuellfarbe);
   g.fillRect(x-breite/2,y-hoehe/2,breite, hoehe);
   g.setColor(farbe);
   g.drawRect(x-breite/2,y-hoehe/2, breite,hoehe);
 }
}


Der Konstruktor Rechteck initialisiert das Objekt, dazu erwartet er die Angabe der sechs Zahlen als Parameter.

Die Metho­de veschiebe verschiebt die Rechteckposition um die angegebenen Werte. Die Methode versetze verschiebt das Rechteck auf die angegebenen Werte.

Mit der Methode is_in kann abgefragt werden, ob ein angegebener Punkt innerhalb des Rechteckes liegt oder nicht.

Ein di­rekter Zugriff auf die Varia­ble ist nicht notwen­dig und aufgrund der private Deklaration auch gar nicht möglich, insofern liegt hier eine Datenkapsel vor.


5.3. Instanzen eines Objektes

Schon anhand der Deklaration fällt dem erfahrenen Leser auf, das es sich bei Rechteck um eine Klasse handelt. Um konkret etwas mit dieser Klasse anfangen zu können, muss ich mir Variable vom Typ Rechteck, oder wie man hier sagt Instanzen des Objektes Rechteck erzeugen.

Eine Vervollständigung des ersten Listings zeigt, wie grundsätzlich mit solchen Instanzen gearbeitet wird. Dazu wird unsere gesamte Konstruktion in ein Applet eingebunden werden, so wie es das folgende Listing zeigt.

import java.awt.*;
import java.applet.*;

public class Rechtecke1 extends Applet  {
 Rechteck R1=new Rechteck(150,150,100,70,Color.black,Color.yellow);
 Rechteck R2=new Rechteck(50, 50, 70, 50,Color.black,Color.blue);

 public void paint(Graphics g) {
  R1.paint(g);
  R2.paint(g);
 }
}


In diesem Listing werden zwei Instanzen unseres Objektes erzeugt und über deren Methode paint auf dem Bildschirm dargestellt. Beide Instanzen sind voneinander vollkommen unabhängig.

6. Behandlung von Ereignissen (Eventhandling)

Bisher haben wir die Tatsache wenig benutzt, dass wir mit einer graphischen Oberfläche arbeiten, bei der z.B. Knöpfe und Mausaktionen möglich sind. Dazu müssen wir erst einmal klären, wie wir auf Tastatur- und Mausereignisse reagieren können.

Bei Java gibt es zwei Modelle des Event-Handling. In allen Versionen vorhanden, aber seit Version 1.1 als deprecated (engl. herabgesetzt, verachtet) gekennzeichnet ist das Modell, das auf Vererbung der Methoden action() und handleEvent() aus der Klasse Component beruht.

Seit JDK 1.1 aktuell ist das Delegation-Event-Modell, bei dem verschiedene Ereignistypen gekapselt in einer Klassenhierarchie basierend auf java.util.Event-Object vorliegen. Ereignisse werden von einem passenden Eventhandler registriert und ausgewertet.

Der Unterschied in der Ereignisbehandlung ist der Hauptgrund dafür, dass neuere Applets und Anwendungen nicht in älteren Laufzeitumgebungen funktionieren. Wer Applets auch für ältere Browser entwickeln möchte, der muss mit dem alten JDK 1.0.x arbeiten.


6.1.Das alte Event-Modell

Auch wenn das alte Modell inzwischen von Sun verachtet wird, so ist es bei Applets noch weit verbreitet, weil erst die neueren Browser-Versionen mit dem neuen Modell umgehen können. Daher hier noch als Einführung in die Ereignisbehandlung das folgende Beispiel:

import java.awt.*;
import java.applet.*;

public class Rechtecke2 extends Applet {
 Rechteck R1 = new Rechteck(150,150,100,70, Color.black, Color.yellow);

 public void paint(Graphics g) {
   R1.paint(g);
 }

 public boolean keyDown(Event ev, int key) {
   if (key==Event.LEFT) R1.verschiebe(-5,0);
   if (key==Event.RIGHT) R1.verschiebe(5,0);
   repaint();
   return true;
 }
}

Dieses Applet verfügt über die übliche paint-Methode. Es wird die Methode keyDown überschrieben. keyDown wird vom System immer dann aufgerufen, wenn der Anwender eine Taste drückt. Die Methode bekommt eine Variable vom Typ Event übergeben, die wir aber nicht weiter auswerten und eine Variable vom Typ int, die den Tastencode enthält.

Wichtig ist, dass nach dem Verschieben der Rechtecksposition repaint() aufgerufen wird, wodurch das Fenster neu dargestellt wird.

Der Rückgabewert true sagt dem System, dass wir mit dem Ereignis etwas anfangen konnten. Windows muss das Ereignis also nicht mehr an andere Objekte weiterreichen, es hat ein Objekt gefunden, welches das Ereignis ausgewertet hat.

Für folgende Events sind Ereignisroutinen vorhanden und können gegebenenfalls über­schrieben werden:

mouseUp()       Mousetaste wurde losgelassen
mouseDown()     Mousetaste wurde gedrückt
mouseDrag()     die Mouse wurde mit gedrückter Taste bewegt
mouseMove()     die Mouse wurde bewegt ohne gedrückte Taste
mouseEnter()    der Mousezeige wurde auf die Komponente bewegt
mouseExit()     der Mousezeiger wurde von der Komponente wegbewegt
gotFocus        das Fenster wurde aktiviert
lostFocus       das Fenster wurde deaktiviert
keyDown()       es wurde innerhalb des Applets eine Taste gedrückt
keyUp()         es wurde innerhalb des Applets eine Taste losgelassen

Bei Programmen mit vielen Objekten, die auf Ereignisse reagieren müssen, wird die Ereignisbehandlung durch die notwendigen Fallunterscheidungen schnell übersichtlich. Sinnvoller ist es dann, die Ereignisbehandlung an das jeweilige Objekt zu binden.


6.2.Das aktuelle Event-Modell

Zur Einführung gleich das letzte Beispiel angepasst auf das neue Eventhandling-Modell.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Rechtecke3 extends Applet 
implements KeyListener {
 Rechteck R1=new Rechteck(150,150,100,70,Color.black,Color.yellow);

 public void paint(Graphics g) {
  R1.paint(g);
 }

 public void keyPressed(KeyEvent ev) {
   if (ev.getKeyCode()==KeyEvent.VK_LEFT) R1.verschiebe(-5,0);
   if (ev.getKeyCode()==KeyEvent.VK_RIGHT) R1.verschiebe(5,0);
  repaint();
  }

 public void keyReleased(KeyEvent ev) {
 }

 public void keyTyped(KeyEvent ev) {
 }

 public void init() {
   this.addKeyListener(this);  
 }
}

Für die Ereignisbehandlung benutzt wird hier das Interface KeyListener. Interfaces sind in Java ein Ersatz für eine multiple (mehrfache) Vererbung. Eine Klasse darf immer nur von einer einzigen Klasse abstammen, nicht von mehreren gleichzeitig.

Da diese multiple Vererbung manchmal wünschenswert wäre, hat man die Interfaces eingeführt. Ein Interface darf aber nur Variable und Methodenköpfe besitzen. Die eigentliche Pro­grammierung bleibt der Klasse überlassen, die das Interface implementiert.

Das bedeutet aber auch, dass immer alle Methoden des Interfaces implementiert werden müssen, auch wenn sie im Programm nicht benötigt werden. Im vorliegenden Listing werden deshalb die Methoden keyReleased und keyTyped leer implementiert.

Jede Klasse, die wie Applet, von Component abstammt, verfügt über die Methode addKeyListener, über die der jeweilige Listener aktiviert wird. Der Bezeichner this, der in dieser Zeile zweifach auftaucht, bezeichnet das aktuelle Objekt selber, hier also das Applet. Es wird die Methode addKeyListener des Applets aufgerufen und ihr das Applet als Parameter übergeben, somit reagiert sie auf Tastaturereignisse des Applet-Objektes.

In einem späteren Beispiel werden wir auch Buttons einbinden, die bekommen dann ihren eigenen KeyListener zugeordnet.

Nachteilig ist die Tatsache, dass alle Methoden des Interface-Objektes implementiert werden müssen, was wieder etwas auf Kosten der Übersichtlichkeit geht. Daher bietet Java für jedes der Listener-Interfaces auch eine passende Adapterklasse an, die als interne Klasse in das Listing aufgenommen werden kann.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Rechtecke4 extends Applet  {
 Rechteck R1=new Rechteck(150,150,100,70,Color.black,Color.yellow);

 class myKeyAdapter extends KeyAdapter {

 public void keyPressed(KeyEvent ev) {
   if (ev.getKeyCode()==KeyEvent.VK_LEFT) R1.verschiebe(-5,0);
   if (ev.getKeyCode()==KeyEvent.VK_RIGHT) R1.verschiebe(5,0);
  repaint();
  }
 }            

 public void paint(Graphics g) {
  R1.paint(g);
 }

 public void init() {
   this.addKeyListener(new myKeyAdapter());  
 }
}

Die Veränderungen am Listing sind gering, vor allem die Methode keyPressed bleibt unverändert. Der Aufruf von addKeyListener verändert sich dahingehend, dass jetzt natürlich nicht mehr das Applet mit dem Interface übergeben wird, sondern eine Instanz der Klasse myKeyAdapter.

Den Ansatz kann man nun noch einmal knapper fassen, indem man auf die explizite Deklaration der Klasse myKeyAdapter verzichtet und die Deklaration implizit vornimmt. Die Klasse ist dann eine anonyme Klasse und kann an anderen Stellen im Programm nicht benutzt werden.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Rechtecke5 extends Applet  {
 Rechteck R1=new Rechteck(150,150,100,70,Color.black,Color.yellow);

 public void paint(Graphics g) {
  R1.paint(g);
 }

 public void init() {
   this.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent ev) {
       if (ev.getKeyCode()==KeyEvent.VK_LEFT) R1.verschiebe(-5,0);
       if (ev.getKeyCode()==KeyEvent.VK_RIGHT) R1.verschiebe(5,0);
       repaint();
      }
   }
   );  
 }
}

6.3.Ein kleines Malprogramm

Die im vorangegangenen Abschnitt gewonnenen Kenntnisse wollen wir nutzen, um ein kleines Malprogramm zu schreiben.

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class Malen extends Applet {
 private int lastX=0;
 private int lastY=0;

 public void init() {
   setBackground(Color.lightGray);
   this.addMouseListener(new MouseAdapter() {
     public void mousePressed(MouseEvent ev) {
       lastX=ev.getX();
       lastY=ev.getY();
     }
   }); // add

   this.addMouseMotionListener(new MouseMotionAdapter() {
     public void mouseDragged( MouseEvent ev) {
       Graphics g=getGraphics();
       g.drawLine(lastX, lastY, ev.getX(),ev.getY() );
       lastX=ev.getX();
       lastY=ev.getY();
     }
   }); // add

 } // init
} // Malen

Dieses Applet verfügt nicht über eine eigene paint-Methode. Hier werden die Methoden mousePressed und mouseDragged der jeweiligen Adapterklassen über­schrieben. MousePressed wird vom System immer dann aufgerufen, wenn der Anwender die Linke Maus­taste drückt, mouseDragged dann, wenn der Anwender die Maus mit gedrückter Maustaste bewegt. Die Methoden bekommen jeweils eine Variable vom Typ MousEvent über­geben.

Interessant ist noch die Zeile g=this.getGraphics. Die Methode soll zeichnen, hat aber kein Graphicsobjekt übergeben bekommen. Mit dieser Zeile erfragt sie ihr eigenes GraphicsObjekt (bzw. das des Applets) um dann damit arbeiten zu können.

Für folgende Events sind Ereignisroutinen vorhanden und können gegebenenfalls implementiert werden:


ActionListener
actionPerformed(ActionEvent)


FocusListener oder FocusAdapter
focusGained(FocusEvent) das Objekt wurde aktiviert
focusLost(FocusEvent) das Objekt wurde deaktiviert


KeyListener oder KeyAdapter
keyPressed(KeyEvent) es wurde eine Taste gedrückt
keyReleased(KeyEvent) es wurde eine Taste losgelassen
keyTyped(KeyEvent) ein gesamter Tastendruck


MouseListener oder MouseAdapter
mouseClicked(MouseEvent) Mausklick
mousePressed(MouseEvent) Maustaste gedrückt
mouseReleased(MouseEvent) Maustaste losgelassen
mouseEntered(MouseEvent) der Mauszeige wurde zur Komponente bewegt
mouseExited(MouseEvent) der Mauszeiger wurde von der Komponente weg­bewegt


MouseMotionListener oder MouseMotionAdapter
mouseDragged() die Maus wurde mit gedrückter Taste bewegt
mouseMoved() die Maus wurde bewegt ohne gedrückte Taste


ItemListener
itemStateChanged(ItemEvent) Zustand des Eintrages verändert


TextListener
textValueChanged(TextEvent) Wert des Textes verändert


WindowListener oder WindowAdapter
windowClosing(WindowEvent) Fenster schließen
windowActivated(WindowEvent) Focus auf das Fenster
windowDeactivated(WindowEvent) Focusverlust für das Fenster
windowIconified(WindowEvent) Fenster verkleinern
windowDeiconified(WindowEvent) Fenster wieder vergrößern


Wie einfach die Ereignisbehandlung durch die Listener-Objekte bzw. Adapterklassen wird, soll das folgende Beispiel zeigen, bei dem das Programm um einen Knopf zum Löschen erweitert wird.

Die neuen Zeilen sind im Listing kursiv hervorgehoben.

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class Malen extends Applet {
 private int lastX=0;
 private int lastY=0;
 private Button ClearButton;

 public void init() {
  setBackground(Color.lightGray);

  ClearButton = new Button("Löschen");
  this.add(ClearButton);

  ClearButton.addMouseListener(new MouseAdapter() {
    public void mouseReleased(MouseEvent ev) {
      Graphics g=getGraphics();
      Rectangle r=getBounds();
      g.setColor(getBackground());
      g.fillRect(r.x, r.y, r.width,r.height);
    }

  }
  );

  this.addMouseListener(new MouseAdapter() {
    public void mousePressed(MouseEvent ev) {
      lastX=ev.getX();
      lastY=ev.getY();
    }
  }
  );

 this.addMouseMotionListener(new MouseMotionAdapter() {
   public void mouseDragged( MouseEvent ev) {
     Graphics g=getGraphics();
     g.drawLine(lastX, lastY, ev.getX(),ev.getY() );
     lastX=ev.getX();
     lastY=ev.getY();
   }
 }
 );

}
}

Da der Knopf zum Applet hinzugefügt werden muss, initialisieren wir ihn in der init-Methode des Applets und fügen ihm gleich ein Listener-Objekt hinzu.

Die Auswertung des Knopfdruckes wird in der Methode mouseReleased des Mouse­Adapters vorgenommen.

Was bei diesem Beispiel bisher sehr nachteilig ist, ist die Tatsache, dass das Zeichnen nicht in der paint-Methode erfolgt. Dadurch wird die Abbildung auch bei jedem Fensterwechsel gelöscht. Das soll uns aber momentan nicht stören.

Beispielprogramm: Malen2.html

Java07.png

Die nebenstehende Abbildung zeigt schon gleich den nächsten Schritt, ein Auswahlfeld zur Farbwahl.

Die zu ändernden Programmteile sind hier noch einmal aufgeführt.

...
public class Malen extends Applet {
 private int lastX=0;
 private int lastY=0;
 private Button ClearButton;
 private Color Zeichenfarbe=Color.black;
 private Choice Farbenwahl;

 public void init() {
  setBackground(Color.lightGray);

  Farbenwahl = new Choice();
  Farbenwahl.addItem("Schwarz");
  Farbenwahl.addItem("Blau");
  Farbenwahl.addItem("Rot");
  this.add(Farbenwahl);

  class FarbenListener implements ItemListener {
     public void itemStateChanged(ItemEvent ev) {
        String selected=  Farbenwahl.getSelectedItem();
      if (selected.equals("Schwarz")) Zeichenfarbe=Color.black;
      else if (selected.equals("Blau")) Zeichenfarbe=Color.blue;
      else if (selected.equals("Rot")) Zeichenfarbe=Color.red;
     }
  }

  FarbenListener myFarbenListener = new FarbenListener();
  Farbenwahl.addItemListener(myFarbenListener);
...

Was fehlt ist noch eine Zeile in der Methode mou­seDragged, die die Farbe dann mittels

     g.setColor(Zeichenfarbe);

vor dem Zeichnen auf den jeweils aktuellen Wert setzt.

7. Layout

Eine hardwareunabhängige Software hat es naturgemäß etwas schwieriger mit dem Anordnen von Objekten, da Größenangaben nicht allgemein verfügbar sind.

Will man in Java Objekte anordnen, so muss man auf die Layoutmanager eingehen, die zur Verfügung gestellt werden. Diese Layoutmanager dienen zum Anordnen von Elementen wir Buttons und Feldern.

Es gibt die folgenden Layoutmanager.


7.1. FlowLayout

Dies ist der Standard Layout-Manager für Panels und Applets. Er ordnet alle Elemente nebeneinander in gleicher Größe an. Passen nicht alle Elemente in eine Zeile, so wird eine zweite Zeile begonnen.


7.2. GridLayout

Hiermit wird eine Matrix beschrieben. Beim Initialisieren wird die Zahl der Spalten und Zeilen angegeben. Die Matrix wird dann zeilenweise von Links nach Rechts gefüllt.

7.3. BorderLayout

Beim BorderLayout gibt es die fünf vordefinierten Plätze „North”, „East”, „South”, „West” und „Center”.


7.4. CardLayout

Bei diesem Layout-Manager liegen die Elemente auf virtuellen Karteikarten übereinander, es ist immer nur die oberste Karte zu sehen.


7.5. GridBagLayout

Der aufwendigste aber flexibelste Layoutmanager. Es werden zusätzlich Zwänge (Contraints) angegeben, die den Platzbedarf der einzelnen Objekte beschreiben.


7.6. Ein kleiner Taschenrechner

Das folgende Listing implementiert die Oberfläche für einen kleinen Taschenrechner, der aber noch keine Berechnungen durchführen kann.

import java.applet.*;
import java.awt.*;

public class Taschenrechner extends Applet {
 Button gleichBut;
 Button clrBut;
 Button plusBut;
 TextField Eingabe1;
 TextField Eingabe2;
 TextField Ergebnis;

 Panel Buttons;
 Panel Eingaben;
 Panel Ausgabe;
 Panel Zusammen;

public void init() {
  gleichBut= new Button(" = ");
  clrBut= new Button("Clear");
  plusBut = new Button(" + ");
  Eingabe1= new TextField();
  Eingabe2= new TextField();
  Ergebnis = new TextField();
  Ergebnis.setEditable( false);

  Buttons = new Panel();
  Buttons.add(plusBut);
  Buttons.add(gleichBut);
  Buttons.add(clrBut);

  Eingaben= new Panel();
  Eingaben.setLayout( new GridLayout(2,2));
  Eingaben.add( new Label("Zahl 1:"));
  Eingaben.add(Eingabe1);
  Eingaben.add( new Label("Zahl 2:"));
  Eingaben.add(Eingabe2);

  Ausgabe= new Panel();
  Ausgabe.setLayout(new GridLayout(1,2));
  Ausgabe.add( new Label("Ergebnis:"));
  Ausgabe.add(Ergebnis);

  Zusammen= new Panel();
  Zusammen.setLayout( new BorderLayout());
  Zusammen.add("North", Buttons);
  Zusammen.add("South", Ausgabe);

  setLayout(new BorderLayout());
  add("North", Eingaben);
  add("South", Zusammen);

}
}

Zuerst wird eine Zahl von Elementen definiert, die dann in Panels mit passendem Layout zusammengefasst werden. Im Beispiel werden auch Panels wieder zu Panels zusammengefasst, wie im Panel Zusammen. Auch für das Applet insgesamt gibt es ein Layout, im vorliegenden Fall das BorderLayout.

Beispielprogramm: Taschenrechner.html

Java08.png


7.7. Taschenrechner ohne Layoutmanager

Man kann auch ohne Layoutmanager arbeiten, dann muss man aber explizit den Layoutmanager des Applets auf null setzen:

public class ucalc extends Applet {
   TextField txtDisplay;
   Button taste7;
   Button plusBut;

   public void init() {
    super.init();
    setLayout(null);

    txtDisplay=new TextField("Guten Morgen", 18);
    txtDisplay.setFont(new Font("Courier",Font.BOLD,12));
    txtDisplay.disable();
    add(txtDisplay);
    txtDisplay.setBounds(getInsets().left + 25, getInsets().top + 15,189,30);
    taste7=new Button("7");
    taste7.setFont(new Font("Dialog",Font.BOLD,12));
    add(taste7);
    taste7.setBounds(getInsets().left + 28,getInsets().top + 83,35,30);

    plusBut=new Button("+");
    plusBut.setFont(new Font("Courier",Font.BOLD,16));
    add(plusBut);
    plusBut.setBounds(getInsets().left + 182,getInsets().top + 128,35,45);
   }

Bei diesem Verfahren werden die einzelnen Knöpfe ganz normal erzeugt und in die Applet-Oberfläche eingefügt. Anschließend werden die Knöpfe mit der Methode resize in ihrer Größe und Position verändert. Im vorliegenden Listing wird dabei auf die Position des Applets Bezug genommen. Dazu dient getInsets, das über die vier Werte top, left, bottom und right verfügt. Damit wird das Rechteck, in dem sich das Applet befindet, angegeben.

Resize erwartet die Parameter x, y, width und height.

Dieses Verfahren ist sehr flexibel, aber auch nicht problemlos. Da wir selber die Positionierung vornehmen, kann es auf den verschiedenen Plattformen zu Darstellungsfehlern kommen. Eventuell sind die Größenangaben, die wir mittels resize vorgenommen haben, für die Darstellung des Textes nicht ausreichend. Probleme können auch auftauchen, wenn wir ein Element außerhalb des verfügbaren Platzes positionieren.

8. Exceptions

Bei der Programmierung, speziell beim Rechnen, kann es immer wieder zu Laufzeitfehlern kommen.

Diese Fehler fängt Java ab, man sieht dann einen entsprechenden Hinweistext in der Fußzeile des Browsers. Manchmal möchte man aber selber auf diese Fehler reagieren können, ohne dass der Programmaufruf gestört wird. Das geschieht, indem man die kritischen Programmzeilen in einen try {...} Block einbindet und direkt dahinter durch catch (..) {...} auf die unterschiedlichen Fehler reagiert.

Bei einem Taschenrechner könnte das folgendermaßen aussehen:

try {
        Operand1= Operand1 + d1.doubleValue();
}
catch (ArithmeticException e) {
  txtDisplay.setText("Error:" + e );
}
catch (Throwable e) {
  txtDisplay.setText("Fehler:" + e );   
}

9. Variablen, Konstanten, Typen und Werte

Bei Java muss man zwei grundlegende Arten von Typen unterscheiden, elementare Typen und Referenztypen.


9.1. Elementare Typen

Variablen eines elementaren Typs enthalten immer einen Wert aus dem jeweiligen Wertebereich. Bei der Initialisierung erhalten sie normalerweise den Wert 0 (boolean: false).


Typ Größe in Byte Wertebereich
boolean 1 false, true
byte 1 -128 bis 127
short 2 -32.768 bis 32767
int 4 -2.147.483.648 bis 2.147.483.647
long 8 -9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807
char 2 0 bis 65.535 (‘\u0000' bis ’\uffff’)
float 4 ±3.402823487E+38 (1.40239846E-45)
double 8 ±1.79769313486231570E+308 (4.94065645841246544E-324)

Bei Zuweisungen an Wertetypen werden wirklich die Werte verändert. Bei dem folgenden Beispiel:

int i = 5;
int j = i;
j++;

Hat die Variable i hinterher immer noch den Wert 5, die Variable j hat den Wert 6.


9.2.Referenztypen

Referenztypen sind Klassen, Interfaces und Felder. Ein Java-Objekt ist Instanz einer Klasse oder ein Feld. Alle Objekte sind implizit Instanzen der Klasse Object und verfügen damit z.B. über die Methoden equals() und toString().

Der Standardwert bei einer Instanz eines Referenztypes ist null.

Normale Zuweisungen beeinflussen nicht den Wert der Variablen, sondern die Referenz. Das folgende Beispiel arbeitet nicht mit den Typen Double oder Integer aus dem Math-Paket, sondern geht von zur Vereinfachung von einem eigenen Typ Zaehler aus:

Zaehler i = new Zaehler();
i.wert(5);
Zaehler j = i;
j.inkrementiere();

Danach ergeben i.wert() und j.wert() beide die Ausgabe 6.


10. Dokumentation mit Javadoc

In dem hier abgedruckten vollständigen Listing für einen Taschenrechner finden sich Kommentare, die von Javadoc ausgewertet werden:

/**
*Ein kleines Taschenrechner-Applet als Demonstrationsobjekt für den
Leistungskurs-Informatik. *Der Taschnrechner beachtet die Vorrangregel "Punkt-
vor Strichrechnung" nicht. *@version 1.2 vom 5.12.2000
*@author Uwe Debacher
*/
import java.awt.*;
import java.applet.*;
import java.awt.event.*;

public class ucalc extends Applet {
boolean bEqual = true;
double Operand1 = 0;
String Operator1="";
Label label;
TextField txtDisplay;
Button taste0;
Button taste1;
Button taste2;
Button taste3;
Button taste4;
Button taste5;
Button taste6;
Button taste7;
Button taste8;
Button taste9;
Button kommaBut;
Button minusBut;
Button plusBut;
Button gleichBut;
Button clrBut;
Button durchBut;
Button malBut;

/**
*Hier wird die Oberfläche initialisert.
*/
public void init() {
 super.init();
 setLayout(null);
 txtDisplay=new TextField("Guten Morgen", 18);
 txtDisplay.setFont(new Font("Courier",Font.BOLD,12));
 txtDisplay.setEnabled(false);
 add(txtDisplay);
 txtDisplay.setBounds(getInsets().left + 25,getInsets().top + 15,189,30);

 label=new Label("Uwes Taschenrechner v0.2");
 label.setFont(new Font("Dialog",Font.BOLD,12));
 add(label);
 label.setBounds(getInsets().left + 30,getInsets().top + 263,187,20);

 taste7=new Button("7");
 taste7.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste7);
 taste7.setBounds(getInsets().left + 28,getInsets().top + 83,35,30);
 taste7.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("7");     
   }
   }
 );
 taste8=new Button("8");
 taste8.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste8);
 taste8.setBounds(getInsets().left + 75,getInsets().top + 83,35,30);
 taste8.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("8");     
   }
   }
 );
 taste9=new Button("9");
 taste9.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste9);
 taste9.setBounds(getInsets().left + 126,getInsets().top + 83,35,30);
 taste9.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("9");     
   }
   }
 );
 taste5=new Button("5");
 taste5.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste5);
 taste5.setBounds(getInsets().left + 77,getInsets().top + 128,35,30);
 taste5.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("5");     
   }
   }
 );
 taste4=new Button("4");
 taste4.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste4);
 taste4.setBounds(getInsets().left + 28,getInsets().top + 128,35,30);
 taste4.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("4");     
   }
   }
 );
 taste6=new Button("6");
 taste6.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste6);
 taste6.setBounds(getInsets().left + 126,getInsets().top + 128,35,30);
 taste6.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("6");     
   }
   }
 );
 taste1=new Button("1");
 taste1.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste1);
 taste1.setBounds(getInsets().left + 28,getInsets().top + 173,35,30);
 taste1.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("1");     
   }
   }
 );
 taste2=new Button("2");
 taste2.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste2);
 taste2.setBounds(getInsets().left + 77,getInsets().top + 173,35,30);
 taste2.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("2");     
   }
   }
 );
 taste3=new Button("3");
 taste3.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste3);
 taste3.setBounds(getInsets().left + 126,getInsets().top + 173,35,30);
 taste3.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("3");     
   }
   }
 );
 taste0=new Button("0");
 taste0.setFont(new Font("Dialog",Font.BOLD,12));
 add(taste0);
 taste0.setBounds(getInsets().left + 28,getInsets().top + 218,84,30);
 taste0.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     ziffer("0");     
   }
   }
 );

 kommaBut=new Button(".");
 kommaBut.setFont(new Font("Dialog",Font.BOLD,18));
 add(kommaBut);
 kommaBut.setBounds(getInsets().left + 126,getInsets().top + 218,35,30);
 kommaBut.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
   String sValue = new String();
   int h;
    if (bEqual) {
      txtDisplay.setText("0.");
      bEqual = false;
    }
   else {
     sValue = new String(txtDisplay.getText());
     h = sValue.indexOf(".");
     if (h < 0) {
       txtDisplay.setText(txtDisplay.getText() + "."); }
     }
   } 
   }
 );
 minusBut=new Button("-");
 minusBut.setFont(new Font("Courier",Font.BOLD,16));
 add(minusBut);
 minusBut.setBounds(getInsets().left + 182,getInsets().top + 83,35,30);
 minusBut.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     verkn("-");     
   }
   }
 );
 plusBut=new Button("+");
 plusBut.setFont(new Font("Courier",Font.BOLD,16));
 add(plusBut);
 plusBut.setBounds(getInsets().left + 182,getInsets().top + 128,35,45);
 plusBut.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     verkn("+");     
   }
   }
 );
 durchBut=new Button("/");
 durchBut.setFont(new Font("Dialog",Font.BOLD,12));
 add(durchBut);
 durchBut.setBounds(getInsets().left + 128,getInsets().top + 53,35,20);
 durchBut.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     verkn("/");     
   }
   }
 );

 malBut=new Button("*");
 malBut.setFont(new Font("Dialog",Font.BOLD,14));
 add(malBut);
 malBut.setBounds(getInsets().left + 75,getInsets().top + 53,35,20); 
 malBut.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     verkn("*");     
   }
   }
 );
 gleichBut=new Button("=");
 gleichBut.setFont(new Font("Courier",Font.BOLD,16));
 add(gleichBut);
 gleichBut.setBounds(getInsets().left + 182,getInsets().top + 180,35,68);
 gleichBut.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
     verkn("");     
   }
   }
 );
 clrBut=new Button("C");
 clrBut.setFont(new Font("Dialog",Font.BOLD,12));
 add(clrBut);
 clrBut.setBounds(getInsets().left + 182,getInsets().top + 53,35,22);
 clrBut.addMouseListener(new MouseAdapter() {
   public void mouseReleased(MouseEvent ev) {
    txtDisplay.setText("Leer");
   bEqual = true;
    Operand1=0;
    Operator1="";   
   }
   }
 );

} // Ende von init()

/**
*Wird aufgerufen, wenn die Ereignisbehandlung eine Zifferntaste erkannt hat.
*@param s Ziffer
*/
public void ziffer(String s) {
 if (bEqual) {
  txtDisplay.setText(s);
  bEqual = false;
 }
 else {
  txtDisplay.setText(txtDisplay.getText() + s);
 }
} // Ende von ziffer()
/**
*Wird aufgerufen, wenn die Ereignisbehandlung eine Verknüpfungstaste erkannt hat.
*@param s Verknüpfungszeichen
*/
public void verkn(String s) {
 Double d1;
 bEqual = true;
 try {
  if (Operator1=="") {
   d1=new Double(txtDisplay.getText());
   Operand1= d1.doubleValue();
   Operator1=s;
  }
  else {
   d1=new Double(txtDisplay.getText());
   if (Operator1=="+") { Operand1= Operand1 + d1.doubleValue(); }
   else if (Operator1=="-") { Operand1= Operand1 - d1.doubleValue(); }
   else if (Operator1=="*") { Operand1= Operand1 * d1.doubleValue(); }
   else if (Operator1=="/") { Operand1= Operand1 / d1.doubleValue(); }
   Operator1=s;
   txtDisplay.setText(s.valueOf(Operand1));
  }
}
catch (ArithmeticException e) {
  txtDisplay.setText("Error:" + e );
}
catch (Throwable e) {
  txtDisplay.setText("Fehler:" + e );
}
} // Ende von verkn()
}


Beispielprogramm: ucalc.html

Java09.png

11. Java-Applikationen

Eine Java-Applikation unterscheidet sich vom Applet dadurch, dass sie nicht unbedingt von der Klasse Applet abgeleitet wird. Eine reine Textanwendung benötigt nicht einmal eine andere Klasse, von der sie abgeleitet wird.

Wichtig ist aber auf alle Fälle, dass die Anwendung ein Hauptprogramm namens main zur Verfügung stellt, welches als static erklärt sein muss und keinen Wert zurückgeben darf. Als Parameter muss dieses Programm ein Stringfeld erwarten.


import java.io.*;
 
public class HalloWelt   {         

public static void main( String args[] ) {           
   System.out.println("Hallo Welt!");
}

}

Die Methode main muss statisch sein, damit sie zur Klasse gehört und nicht zu einer Instanz dieser Klasse, die es ja zu diesem Zeitpunkt noch gar nicht gibt. Konkrete Instanzen der Klasse könnten ja erst im Hauptprogramm erzeugt werden.

Eine Windows-Anwendung kann z.B. von der Klasse Frame abgeleitet werden, wenn man das AWT benutzt, bzw. von JFame, wenn man Swing nutzen will. Generell muss man sich bei der Programmierung graphischer Oberflächen zwischen Swing und AWT entscheiden.


11.1. Hallo Welt mit AWT

Das folgende Listing zeigt ein minimales Programm mit graphischer AWT Oberfläche.

import java.awt.*;
import java.awt.event.*;

public class HalloWeltAWT extends Frame {

  class Mein_WindowListener extends WindowAdapter {

    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }
  }

  public HalloWeltAWT() {
    super("Testanwendung");
    addWindowListener(new Mein_WindowListener());
    setLayout(null);
    setSize(420, 400);
  }

  public void paint(Graphics g) {
    g.drawString("Hallo Welt", 60, 100);
  }

  public static void main(String args[]) {
    HalloWeltAWT dieseAnw = new HalloWeltAWT();
    dieseAnw.setVisible(true);
  }
}


Hinweis: Ohne WindowListener ließe sich dieses Programm nicht beenden!

11.2 Hallo Welt mit Swing

Da das AWT vielen Anwendern nicht ausreicht, gibt es seit der Java-Version 1.2 mit den Swing-Bibliotheken, bzw. der Java Foundation Class eine umfangreiche Alternative.

import javax.swing.*;
import java.awt.*;

public class HalloWeltSwing extends JFrame {

    public HalloWeltSwing() {
    super("Testanwendung");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLayout(null);
    setSize(420, 400);
  }

  public void paint(Graphics g) {
    g.drawString("Hallo Welt", 60, 100);
  }

  public static void main(String args[]) {
    HalloWeltSwing dieseAnw = new HalloWeltSwing();
    dieseAnw.setVisible(true);
  }
}


Was man an diesem Beispiel gut erkennen kann ist, dass JFrame schon Methoden zum Schließen des Fensters mitbringt. Die letzte Zeile, die die Anwendung erst sichtbar macht, hätte natürlich entsprechend auch im Konstruktor stehen können.


12. Dateien in Java

In Java, speziell auch schon im AWT, sind viele Elemente vorhanden, mit denen man auch Dateien lesen und schreiben kann.


12.1. File Dialog

Von Java aus kann man die Betriebssystem-Dialoge zu Laden und Speichern von Dateien ansteuern. Ergebnis dieser Dialoge ist jeweils ein Dateiname.


FileDialog d = new FileDialog(this,"Text laden...",FileDialog.LOAD);
d.setVisible(true);
String Dateiname = d.getFile();
g.drawString(Dateiname, 60, 100);

Mittels getDirectory() kann man zusätzlich das Verzeichnis abfragen, aus dem die Datei entnommen werden bzw. in welches sie gespeichert werden soll. Gibt man in der ersten Zeile statt LOAD das Wort SAVE an, so bekommt man den Dialog zum Speichern. Die Unterschiede betreffen aber nur das Layout des Dialogs.

JFileChooser d = new JFileChooser();
d.setVisible(true);
d.showOpenDialog(null);
File Datei = d.getSelectedFile();
g.drawString(Datei.getName(), 60, 100);

Die Swing-Dialoge sehen deutlich angenehmer aus, als die AWT-Dialoge.


12.2. Einfache Datei Ein- und Ausgabe

Für die Ein- bzw. Ausgabe in Dateien sind (u.a.) die Klassen FileReader bzw. FileWriter zuständig. Beide Klassen benötigen als Parameter einen gültigen Dateinamen.

Das folgende Beispiel implementiert ein einfaches Kopierprogramm. Die Dateinamen sind fest eingestellt, man könnte ja aber auch Kommandozeilenparameter auswerten.

import java.io.*;

public class Kopierer   {         

public static void main( String args[] )  throws IOException  {           
 int zeichen;
 boolean ende = false;
 FileReader ein = new FileReader("Kopierer.java");
 FileWriter aus = new FileWriter("Kopierer.kop");
 while(!ende) {
   zeichen = ein.read();
   System.out.print( (char) zeichen);
   if ( zeichen  == -1)
     ende =true;
  else
    aus.write(zeichen);
 }
 aus.close();
 ein.close(); 
}
}

Windows ist anscheinend sehr pingelig, was das Schließen der Dateien angeht. Schließt man die Ausgabedatei nicht ordnungsgemäß, so wird sie zwar erzeugt, ist aber leer. Gelesen und geschrieben werden Variable vom Typ int, die dem Unicode des jeweiligen Zeichens entsprechen. Daher kann im Beispiel das Zeichen mittels Typecast gewonnen werden.


12.3. Drucken in Java

In den meisten Programmiersprachen ist die Druckausgabe nur ein Spezialfall der Dateiausgabe. Kopiert man z.B. unter WinDOS eine beliebige Datei auf lpt1, so ist das eine Druck­ausgabe. In Java ist das etwas anders und die Druckausgabe überhaupt erst mit dem JDK 1.1 möglich geworden.

PrintJob  pjob = getToolkit().getPrintJob(this, "Java kann drucken", null);
 if (pjob != null) {
   Graphics pg = pjob.getGraphics();
  if (pg != null) {
    txtA.printAll(pg);
    pg.dispose();
  }
  pjob.end();
 }


Man verschafft sich zuerst einen PrintJob von Windows. Dazu wird über das Toolkit dieses systemspezifische Objekt angefordert. Falls die Anforderung geklappt hat, kann man sich über den Printjob ein Graphic-Objekt anfordern. Über dieses Objekt erfolgt dann die eigentliche Ausgabe mit der Methode printAll. Über diese Methode verfügen z.B. die Objekte TextArea, TextField und Canvas.

12.4. Menüs in Java

Bisher haben wir meistens Knöpfe zur Bedienung unserer Anwendungen be­nutzt, aber auch Menüs lassen sich in Java relativ einfach integrieren. Auch hier gibt es natürlich wieder die zwei Wege über das AWT und über Swing.

Hauptobjekt ist ein Objekt vom Typ MenuBar, das mit der Methode setMenuBar des Frameobjektes als Menü eingetragen wird. In die Menüleiste eingetragen werden die beiden Menüs menu1 und menu2 jeweils mittels add. In diese Menüs eingetragen werden dann die Menüpunkte vom Typ menuItem wiederum mittels add. Zusätzlich zu den menuItems kann man auch Trennlinien einfügen, mittels addSeparator(). Das folgende Listing zeigt einen Ausschnitt aus dem Konstruktor eines Programmes:

  MenuBar menüleiste = new MenuBar();
  setMenuBar(menüleiste);

  Menu menu1 = new Menu("Datei");
  MenuItem item1_1  = new MenuItem("Datei kodieren");
  MenuItem item1_2 = new MenuItem("Programm beenden");
  menu1.add(item1_1);
  menu1.addSeparator();
  menu1.add(item1_2);
  menüleiste.add(menu1);

  Menu menu2 = new Menu("Spezial");
  MenuItem item2_1 = new MenuItem("Drucken");
  MenuItem item2_2 = new MenuItem("String suchen");
  menu2.add(item2_1);
  menu2.add(item2_2);
  menüleiste.add(menu2);

  menu1.addActionListener(this);
  menu2.addActionListener(this);

Hervorzuheben ist, dass hier auch mit dem ActionListener gearbeitet wird, um auf die Ereignisse reagieren zu können. Im folgenden Listing wurde, anders als im Abschnitt 15, der Actionlistener aber nicht als interne Klasse definiert, sondern implementiert, was eigentlich kürzer und einfacher zu formulieren ist:

public class Caesar extends Frame implements ActionListener {         

 public void actionPerformed(ActionEvent e)    {
    String beschr  = e.getActionCommand();
    if (beschr.equals("Programm beenden"))       System.exit(1);
    if (beschr.equals("Datei kodieren"))      kodieren();  
    if (beschr.equals("Drucken"))      drucken();  
    if (beschr.equals("String suchen"))      suchen();  
 }


Mit einer Zeile wie:

MenuItem item1_1  = new MenuItem("Datei kodieren", new MenuShortcut(100))

kann man die Tastenkombination Ctrl+D als Shortcut hinzufügen. Es muss dann aber auch eine Routine zur Ereignisbehandlung geben, die diesen Tastendruck auswertet.

Einzelne Menüpunkte kann man zu Laufzeit folgendermaßen aktivieren bzw. deaktivieren:

item1_2.setEnabled(false);

Mittels false wird deaktiviert, mit true aktiviert.

Menüs kann man in Java sehr dynamisch handhaben. Einerseits kann man natürlich zwischen verschiedenen Menüs wechseln, andererseits kann man zur Laufzeit Menüs und Menüpunkte hinzufügen bzw. entfernen. Auch die Beschriftung lässt sich zur Laufzeit verändern. Hierzu können die folgenden Methoden nützlich sein:

insert(MenuItem, int)  Fügt das Menüelement an der spezifizierten Position ein
insert(String, int)    Fügt ein Menüelement an der spezifizierten Position ein
remove(int n)          Entfernt das n-te Element aus dem Menü


12.5. Eine vollständige kleine Anwendung (AWT)

Die meisten der bisher angesprochenen Java-Komponenten finden sich in der folgenden Anwendung, die als Grundlage dienen kann für ein Programm zum Kodieren von Texten.


import java.awt.*;
import java.awt.event.*;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Textbetrachter extends Frame implements ActionListener {

    TextArea txtA;
    Label lblA;

    public void actionPerformed(ActionEvent e) {
        String beschr = e.getActionCommand();
        if (beschr.equals("Programm beenden"))
            System.exit(1);
        if (beschr.equals("Datei öffnen"))
            dateiÖffnen();
    }

    class Mein_WindowListener extends WindowAdapter {

        public void windowClosing(WindowEvent e) {
            System.exit(0);
        }
    }

    public Textbetrachter() {
        super("Textbetrachter");
        addWindowListener(new Mein_WindowListener());
        setLayout(null);
        setSize(600, 600);
        setVisible(true);
        MenuBar menüleiste = new MenuBar();
        setMenuBar(menüleiste);

        Menu menu1 = new Menu("Datei");
        MenuItem item1_1 = new MenuItem("Datei öffnen");
        MenuItem item1_2 = new MenuItem("Programm beenden");
        menu1.add(item1_1);
        menu1.addSeparator();
        menu1.add(item1_2);
        menüleiste.add(menu1);
        menu1.addActionListener(this);

        lblA = new Label();
        lblA.setText("Bitte zuerst eine Datei öffnen");
        lblA.setVisible(true);
        add(lblA);
        lblA.setBounds(getInsets().left + 2, getInsets().top + 30, 500, 20);

        txtA = new TextArea();
        add(txtA);
        txtA.setBounds(getInsets().left + 2, getInsets().top + 60, 500, 400);
        txtA.setText("Start-Text");
        txtA.setEditable(false);
    }

    public void dateiÖffnen() {
         FileDialog d = new FileDialog(this, "Text laden...", FileDialog.LOAD);
         d.setVisible(true);
         String Dateiname = d.getDirectory() + d.getFile();
         int zeichen = 0;
         boolean ende = false;
         FileReader ein = null;
         lblA.setText(Dateiname);
         txtA.setText("");
         try {
            ein = new FileReader(Dateiname);
         } catch (FileNotFoundException e) {
            System.out.println("Datei " + Dateiname + " nicht gefunden");
         }
         while (!ende) {
             try {
            zeichen = ein.read();
             } catch (IOException e) {
            System.out.println("Fehler beim Lesen aus Datei "+Dateiname);
             }
             if (zeichen == -1)
            ende = true;
             else
            txtA.append((char) zeichen + "");
         }

         try {
        ein.close();
         } catch (IOException e) {
        System.out.println("Fehler beim Schließen von Datei " + Dateiname);
         }
    }

    public static void main(String args[]) {
        Textbetrachter dieseAnw = new Textbetrachter();
    }
}

Java02.png

Die nebenstehende Abbildung zeigt das Erscheinungsbild des Programmes mit geöffneten Datei-Dialog unter Linux. Es wirkt recht hausbacken.


12.6. Eine vollständige kleine Anwendung (Swing)

import java.awt.event.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class TextbetrachterSwing extends JFrame implements ActionListener {

    JTextArea txtA;
    JLabel lblA;

    public void actionPerformed(ActionEvent e) {
        String beschr = e.getActionCommand();
        if (beschr.equals("Programm beenden"))
            System.exit(1);
        if (beschr.equals("Datei öffnen"))
            dateiÖffnen();
    }

    public TextbetrachterSwing() {
        super("Textbetrachter");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setLayout(null);
      setSize(600, 600);

      JMenuBar menüleiste = new JMenuBar();

      JMenu menu1 = new JMenu("Datei");
      JMenuItem item1_1 = new JMenuItem("Datei öffnen");
      item1_1.addActionListener(this);
      menu1.add(item1_1);
      menu1.add(item1_1);

      JMenuItem item1_2 = new JMenuItem("Programm beenden");
      item1_2.addActionListener(this);

      menu1.add(item1_2);
      menüleiste.add(menu1);

      setJMenuBar(menüleiste);

      lblA = new JLabel();
      lblA.setText("Bitte zuerst eine Datei öffnen");
      lblA.setVisible(true);
      add(lblA);
      lblA.setBounds(getInsets().left + 2, getInsets().top+30,500,20);

        txtA = new JTextArea();
      txtA.setText("Start-Text");
      txtA.setEditable(false);
      JScrollPane scrollPane = new JScrollPane(
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
      scrollPane.setViewportView(txtA);
      add(scrollPane);
      scrollPane.setBounds(getInsets().left+2,getInsets().top+60,500, 400);
      setVisible(true);
    }

    public void dateiÖffnen() {
      JFileChooser d = new JFileChooser();
      d.setVisible(true);
      d.showOpenDialog(null);
      File Datei = d.getSelectedFile();
      String Dateiname = Datei.getPath();
      int zeichen = 0;
      boolean ende = false;
      FileReader ein = null;
      lblA.setText(Dateiname);
      txtA.setText("");
      try {
        ein = new FileReader(Dateiname);
      } catch (FileNotFoundException e) {
        System.out.println("Datei " + Dateiname + " nicht gefunden");
      }
      while (!ende) {
        try {
            zeichen = ein.read();
        } catch (IOException e) {
           System.out.println("Fehler beim Lesen aus Datei "+ Dateiname);
        }
        if (zeichen == -1)
            ende = true;
        else
            txtA.append((char) zeichen + "");
      }

      try {
        ein.close();
      } catch (IOException e) {
        System.out.println("Fehler beim Schließen von Datei " + Dateiname);
      }
    }

    public static void main(String args[]) {
        TextbetrachterSwing dieseAnw = new TextbetrachterSwing();
    }
}

Java03.png

Die nebenstehende Abbildung zeigt das Erscheinungsbild des Programmes mit geöffnetem Datei-Dialog unter Linux. Es wirkt deutlich freundlicher, als das entsprechende AWT-Programm.

13. Vererbung II

Im folgenden Abschnitt wollen wir eine Klasse von graphischen Objekten definieren, die mittels Maus (eventuell auch Tastatur) über den Bildschirm bewegt werden können. Zu diesen Objekten sollen z.B. Kreise, Rechtecke und Dreiecke gehören. Sucht man nach dem kleinsten gemeinsamen Nenner, so führt man entweder eine abstrakte Klasse Figur ein, oder die einfache konkrete Klasse Punkt. Den zweiten Weg geht das folgende Beispiel:

import java.applet.*;
import java.awt.*;

class Punkt {
 protected int x_lo;
 protected int y_lo;
 protected Color farbe;

public Punkt(int x, int y, Color f) {
  x_lo=x;
  y_lo=y;
  farbe=f;
}

public void paint(Graphics g) {
  g.setColor(farbe);
  g.drawLine(x_lo-3, y_lo-3, x_lo+3 , y_lo+3);  
  g.drawLine(x_lo-3, y_lo+3, x_lo+3 , y_lo-3);  
}

public void moveto(int x, int y) {
  x_lo=x;
  y_lo=y;
}

} //Ende der Klassendefinition Punkt

public class Objekte extends Applet {
 public  Punkt p = new Punkt(10,10, Color.red);

public  void paint(Graphics g) {
  p.paint(g);
}

public boolean mouseMove(Event e, int x, int y)
{
  p.moveto(x,y);   
  repaint();
  return true;
}
} // Ender der Klassendefinition Objekte

Damit man den Punkt auf dem Bildschirm auch wahrnimmt, wird ein kleines Kreuz gezeichnet. Dieses Kreuz folgt der Mausbewegung Die Zeichnung bleibt auch bei Fensterwechsel erhalten, da hier in der paint-Methode gezeichnet wird.

Das Schlüsselwort protected bei der Definition der Variablen ist wichtig, da die Datenkapselung nicht ganz sauber eingehalten wird. Wir wollen in späteren abgeleiteten Klassen auch auf die Variablen direkt zugreifen können, daher dürfen diese nicht als private deklariert werden. Will man aber trotzdem private Deklarieren, dann muss man Methoden zum Setzen und Abfragen der Werte zur Verfügung stellen. Das würde aber die Übersichtlichkeit des Beispiellistings verringern.

Insgesamt gibt es folgende Sichtbarkeitsdefinitionen für Datenfelder:


Zugriff default public protected private protected private
Nicht-Subklassen desselben Paketes ja ja ja nein nein
Subklassen desselben Paketes ja ja ja nein nein
Nicht-Subklassen anderer Pakete nein ja nein nein nein
Subklassen anderer Pakete nein ja nein nein nein

Von dieser Klasse kann man nun beliebig Unterklassen ableiten, bei denen dann jeweils nur der Konstruktor und die paint-Methode verändert werden müssen. Im folgenden Listing wird eine Klasse Kreis abgeleitet.

class Kreis extends Punkt {
 protected int radius;

public Kreis(int x, int y,  int r, Color f) {
  super(x,y,f);
  radius=r;
}

public void paint(Graphics g) {
  g.setColor(farbe);
  g.drawOval(x_lo-radius/2, y_lo-radius/2, radius, radius);
}

} // Ende der Klassendefinition Kreis
.

Die Klasse Kreis erbt die Datenfelder und Methoden von der Klasse Punkt. Neu hinzu kommt ein Datenfeld für den Radius, daher muss auch der Konstruktor überschrieben werden. Die Methode Paint muss selbstverständlich überschreiben werden, hierin stecken ja die Besonderheiten der Objektklasse Kreis. Zum Ausprobieren muss nun noch ein Objekt dieser Klasse erzeugt werden, das dann in die paint- und in die mouseMove-Methode aufgenommen werden muss.

14. Listen von Objekten

Im nächsten Schritt wollen wir erreichen, dass die Objekte etwas einfacher zu verwalten sind. Dazu erstellen wir eine Liste der Objekte. Dieser Liste soll es egal sein, ob das konkrete Objekt ein Punkt ist, ein Kreis oder ein Rechteck, es werden immer die passsenden Methoden aufgerufen.

Eine ideale Klasse hierfür ist die Vector-Klasse, von der wir eine eigene Klasse ableiten, die dann zusätzlich über die Methoden unserer Objekte verfügt:

class meinVector extends Vector {

public void paint(Graphics g) {

Punkt p;

for (int i=0; i<size(); i++) {

p= (Punkt) elementAt(i);

p.paint(g);

}

}

public void moveto(int x, int y) {

for (int i=0; i<size(); i++) {

((Punkt) elementAt(i)).moveto(x,y);

}

}

} // Ende der Klassendefinition Vector


An diesem Listing sieht man sehr schön das Problem beim bisherigen Ansatz. Die Stamm­klasse aller Objektklassen ist Punkt, dieser Bezeichner irritiert hier aber als Platzhalter für alle Objekte. Besser wäre eine abstrakte Stammklasse wie z.B. Figur.

Im Prinzip könnten hier die beiden Methoden nahezu identisch formuliert werden. In der Methode paint wird noch ein umständlicher Weg gewählt, um an die einzelnen Listenelemente heranzukommen. Es wird eine lokale Variable p benutzt. In der Methode moveto wird hier etwas eleganter ein Type-Cast benutzt, eine Typumwandlung. Der Type-Cast ist notwendig, da die Listenelemente sonst nur den allgemeinen Typ Object besitzen, den Typ der Rückgabe von elementAt.

Die Applet-Klasse ist dann folgendermaßen implementiert:


public class Objekte extends Applet {
 public meinVector v=new meinVector();

public Objekte() {
 v.addElement(new Punkt(10,10,Color.red));
 v.addElement(new Kreis(20,20,10, Color.blue));
 v.addElement(new Kreis(20,20,40, Color.red));
}

public  void paint(Graphics g) {
  v.paint(g);
}

public boolean mouseMove(Event e, int x, int y)
{
  v.moveto(x,y);   
  repaint();
  return true;
}
} // Ender der Klassendefinition Objekte

15. Das bereinigte Beispiel

Zieht man die Konsequenzen und bereinigt die Probleme der vorangegangenen Versionen, so ergibt sich das folgende Listing:

import java.applet.*;
import java.awt.*;
import java.util.*;

abstract class Figur {
 protected int x_lo;
 protected int y_lo;
 protected Color farbe;

public Figur(int x, int y, Color f) {
  x_lo=x;
  y_lo=y;
  farbe=f;
}

public void move(int x, int y) {
  x_lo=x_lo+x;
  y_lo=y_lo+y;
}

public abstract void paint(Graphics g);
public abstract boolean isIn(int x, int y);

} //Ende der Klassendefinition Figur

class Kreis extends Figur {
 private int radius;
public Kreis(int x, int y,  int r, Color f) {
  super(x,y,f);
  radius=r;
}

public void paint(Graphics g) {
  g.setColor(farbe);
  g.drawOval(x_lo-radius/2, y_lo-radius/2, radius, radius);
}

public boolean isIn(int x, int y) {
 if  (((x-x_lo)*(x-x_lo)+(y-y_lo)*(y-y_lo)) < (radius*radius/4)) 
  return true;
 else
 return false;
}
} // Ende der Klassendefinition Kreis

class meinVector extends Vector {

public void paint(Graphics g) {
Figur p;
  for (int i=0; i<size(); i++) {
   p= (Figur) elementAt(i);
   p.paint(g);
 }
}

public void move(int x_von, int x_nach, int y_von, int y_nach) {
  for (int i=0; i<size(); i++) {
    if (((Figur) elementAt(i)).isIn(x_von,y_von))
   ((Figur) elementAt(i)).move(x_von-x_nach,y_von-y_nach);
  }
}
} // Ende der Klassendefinition Vector

public class Objekte extends Applet {
 public meinVector v=new meinVector();
 private int lastX=0;
 private int lastY=0;

public Objekte() {
 v.addElement(new Kreis(20,20,10, Color.blue));
 v.addElement(new Kreis(50,50, 100, Color.red));
}

public  void paint(Graphics g) {
  v.paint(g);
}

public boolean mouseDown (Event e, int x, int y) {
  lastX=x;
  lastY=y;
 return true;
}

public boolean mouseDrag(Event e, int x, int y)  {
  v.move(x,lastX, y,lastY);   
  lastX=x;
  lastY=y;  
  repaint();
  return true;
}
} // End der der Klassendefinition Objekte