1. Teil :
Scripte als Mitschrift des Vortrages


Copyright © 1999 by Rainer Tippmann
Fon/FAX: ( 0371) 24 25 26 , Am Harthwald 18, 09123 Chemnitz


Literatur:
[1] Das Buch zum Lernen:

"Java in 21 Tagen" von Lemay u.a.

Haar bei München: SAMS, 1996

[2] Die Java-Bibel:

"Java 1.1 mit Methode" von Glahn u.a.

C&L Verlag , Vaterstetten 1997

[3] Das Buch zum Nachschlagen:

"Java in a nutshell" von Flanagan

O´Reilly 1996

 zurückzurückzurück

1. Java - Eigenschaften 

 zurückzurückzurück

2. Programmerstellung

Editor zum Erstellen des Quelltextes.

Es eignet sich jeder ASCII-Editor wie z.B. das Notepad in Win95 oder der vi-Editor von Unix.

Datei name.java entsteht.

Compiler erzeugt den Java-Bytekode

Der Compiler ist Bestandteil einer Entwicklungsumgebung, wie sie von verschiedenen Anbietern (Sun, Microsoft, Symantec....) erhältlich ist. Das einfachste und preiswerteste Werkzeug ist das JDK von Sun.

Datei name.java entsteht

Interpreter oder Appletviewer führen das Programm aus.

Ein Javaprogramm existiert entweder als selbstständige, lauffähige Anwendung für den Interpreter oder als Applet, eingebettet in eine HTML-Datei, für den Appletviewer bzw. den HTML-Browser.

zurückzurückzurück

3. Anwendung oder Applet

Editor zum Erstellen des Quelltextes

Anwendung:

class MyClass{ public static void

main (String args[]){.....} }

Applet:

class MyClass extends Applet {....}

MyClass.java

Compiler erzeugt den Java-Bytekode

MyClass.class

Interpreter

führt das Programm MyClass.class aus.

Appletviewer oder Browser

lädt die HTML-Datei name.html und führt das Applet MyClass.class aus.

Ebenso wird ein Applet aus dem Internet geladen und im Browser ausgeführt.

Die HTML-Datei: name.html

<HTML>
<HEAD>
<TITLE>Ein Applet</TITLE>
</HEAD>
<BODY>

<APPLET CODE="MyClass.class" WIDTH=200 HEIGHT=300>
</APPLET>

</BODY></HTML>

zurückzurückzurück

4. Entwicklungsumgebungen

An dieser Stelle seien nur einige Entwicklungsumgebungen vorgestellt. Jedes Produkt sollte jeder selbst testen, um für sich persönlich die effektivste Arbeitsmethode auszuwählen.

Visual J++ von Microsoft

Läuft nur unter Windows 95 und NT. Visuall J++ beinhaltet einen Ressourceneditor mit Drag & Drop - Fähigkeit. Zur Programmausführung dient der MS-Internetexplorer und ein eigener Interpreter mit einer speziellen virtuellen Maschine für Windows.

Café von Symantec

Dies ist auch eine visuelle Entwicklungsumgebung mit Drag & Drop - Fähigkeiten. Ein sehr komfortabler Editor ermöglicht eine effektive Programmentwicklung. Als Interpreter sind die JDK-Produkte eingebunden.

Java Workshop von SUN

Basiert auf dem HotJava-Webbrowser von SUN, der in die Entwicklungsumgebung integriert ist. Das Paket ist voll in Java geschrieben und deshalb plattformunabhängig. Es setzt die Entwicklungsmaschine des JDK voraus.

 

zurückzurückzurück

5. Das JDK - Java Development Kit

Java - Compiler javac Klassenname.java

Der Compiler übersetzt den Quellkode "Klassenname.java" in den Java-Byte-Kode "Klassenname.class". Für jede Klasse wird eine separate Class-Datei erzeugt. Class-Dateien liegen standardmäßig im gleichen Verzeichnis wie die Quelldateien.
Wichtig: Die CLASSPATH-Umgebungsvariable muß gesetzt werden, damit Compiler und Interpreter die Dateien finden. Ein Eintrag in der Autoexec.bat könnte für mehrere Pfade etwa so aussehen:
SET CLASSPATH= .; D:\MODEM\CafeLite\JAVA\LIB\CLASSES.ZIP; D:\MODEM\CafeLite\JAVA\LIB; D:\Modem\TYJAVA\javafibel\classes

Java - Interpreter java Klassenname

Dieser Byte-Kode-Interpreter läßt die übersetzten Programme ablaufen. Die Datei "Klassenname.class" muß eine main()-Methode enthalten. Obwohl der Interpreter eine Datei "Klassenname" ruft, werden auch alle zusätzlich im Programm benötigten Klassen geladen.

Appletviewer appletviewer url

 

zurückzurückzurück

11. Anwenden von Methoden

 

Die Musterbeispiele 1 und 2 (MB1 und MB2) zeigen die Anwendung einer statischen Methode und die Verwendung einer Methode über ein Objekt. Diese zwei Beispiele sollten genau studiert werden, zeigen sie doch die grundsätzlichen Möglichkeiten, wie man Methoden in der Programmausführung nutzen kann.

 

MB1 - Statische Methode

Die Klasse Math1 stellt die Methode Kreisumfang() bereit. Bei gegebenem Durchmesser (Übergabeparameter) wird der Kreisumfang (Rückgabewert) berechnet.

Die Klasse kreis nutzt in ihrer main()-Methode die Methode Kreisumfang(). Der Aufruf erfolgt in der Art

Klassenname.Methodenname() ; Math1.Kreisumfang(x) ;

also ohne Nutzung eines Objektes.

Deshalb muß Kreisumfang() mit dem Schlüsselwort static definiert werden. Außerdem muß sie public definiert werden, damit sie auch in der Klasse kreis bekannt ist.

Und noch etwas Wichtiges lehrt dieses Musterbeispiel, die Definition einer Konstanten:

public final static datentyp konstantenname=konstantenwert;

public final static double PI=3.14159265;

Das Schlüsselwort final zeigt an, daß diese Deklaration unwiderruflich ist.

 

MB2 - Nutzen der Methode über ein Objekt

Die Klasse Math1 stellt wie in MB1 die Methode Kreisumfang() bereit, nur daß sie hier nicht static definiert ist. Deshalb muß in der Klasse kreis2 erst ein Objekt vom Typ Math1 erstellt werden, damit der Kreisumfang mit der Methode Kreisumfang() berechnet werden kann:

Math1 obj1=new Math1();

Mit diesem Objekt kann die Methode genutzt werden:

Objektname.Methodenname() ; obj1.Kreisumfang(x) ;

 

zurückzurückzurück

12. Java - Grundlagen

Allgemeine Regeln

In der Literatur wird empfohlen, die folgenden Regeln einzuhalten:

Datentypen

Typ Inhalt Standard Größe
boolean true oder false false 1 bit
char Zeichen (Unicode) \u0000 16 bit
byte Integer mit Vorzeichen 0 8 bit
short Integer mit Vorzeichen 0 16 bit
int Integer mit Vorzeichen 0 32 bit
long Integer mit Vorzeichen 0 64 bit
float Fließkomma 0.0 32 bit
double Fließkomma 0.0 64 bit

 

Arrays

Arrays sind Objekte, die immer erst vor ihrer Benutzung erzeugt werden müssen. Dafür gibt es zwei Möglichkeiten:

int puffer[] = new int[4] (Elemente von 0 bis 3)

Button buttons[]= new Button[10] (10 Button-Objekte)

int puffer[]={7,45,89,44} Es ist puffer[2]=89

Mit der Zuweisung int[] puffer wird ein Array der Variablen puffer zugewiesen. Das Array existiert aber damit noch nicht, sondern muß erst erzeugt werden.

Java überprüft immer die Länge eines Arrays. Eine ArrayIndexOutOfBoundException wird beim Überschreiten der vordefinierten Länge ausgelöst.

Für jedes Array existiert die Instanzvariable length, welche die Länge des Arrays beinhaltet. Mit laenge=puffer.length wird die Länge des Array "puffer" in die Variable "laenge" geschrieben.

Deklarationen und Anfangswerte

Deklaration eines Datenfeldes:

typ name;

int zahl1;

 

Deklaration von mehreren Datenfeldern desselben Typs:

typ name1, name2, name3;

int zahl1, zahl2, zahl3;

 

Zuweisung eines Wertes: name = wert;

zahl1=90

 

Zuweisung eines Wertes mit Typumwandlung (casting):

name = (typ) wert;

zahl1= (String) 90;

 

Deklaration mit Zuweisung eines Anfangswertes:

typ name = wert;

int zahl1=90

Achtung! Die Deklaration gilt immer, falls nicht mit besonderen Schlüsselwörtern anders vereinbart, nur für den Bereich (Klasse, Methode, Block innerhalb einer Methode), innerhalb dessen die Deklaration steht. Es könnte verwirrend sein, wenn man innerhalb eines Blockes denselben Namen, der bereits für eine globale Variable gewählt wurde, für eine gleichnamige lokale Variable verwendet. Zulässig und richtig ist jedoch solch eine Vereinbarung.

Deklaration einer Variablen, die eine Referenz auf Objekte einer bestimmten Klasse enthalten kann: ClassName name;

Button button1;

 

Mit Wertzuweisung: ClassName name = null;

Button button1= null;

Anlegen eines Objekts (einer Instanz) dieser Klasse und Zuweisung der Referenz auf dieses Objekt an die deklarierte Variable: name = new ClassName();

button1 = new Button();

 

Deklaration, Anlegen und Zuweisen in einem:

ClassName name = new ClassName();

Button button1 = new Button();

 

Deklaration und Anlegen von Strings:

String s;

String s = null;

s = "Rainer Tippmann";

String vollName = "Rainer Tippmann";

Die Länge eines String ermittelt die Methode length():

int laenge=vollName.length()

Operatoren

Operator Typ Operation Beispiel
++ , -- arithmetisch Inkrement , Dekrement ++z o. z--
+ , - arithmetisch Addition , Subtraktion a + b
* , / , % arithmetisch Multiplikation , Division , Rest a * b
== Vergleich Gleichheit (für alle Datentypen) a == b
!= Vergleich Ungleichheit a != b
<, >, =<; => Vergleich Nur für numerische Operatoren (x+y)=<z
instanceof Typenvergleich Gehört Instanz ax zur Klasse Math?  
&&, ||, ^ Verknüpfung Bedingungsoperatoren (nur bool. Operant.)  
& , |, ^ Verknüpfung bitweise oder log. UND bzw. ODER c & d

 

Typenvergleich: ax instanceof Math
Logische Operatoren: c & d = true, falls c und d gleich true c und d dürfen nur vom Typ boolean sein
Bedingungsoperatoren: c && d = wie log. Operatoren der linke Operand muß true sein, sonst wird Bedingung nicht abgearbeitet.
Bitweise Operatoren: c & d = Operanden müssen nicht vom Typ boolean sein. Ergebnis wird bitweise verknüpft.

Beispiel: c="A"=0x41=1000001 & d="B"=0x42=1000010

"@"=0x40=1000000

 

Modifikatoren

Modifikator für Klassen und Schnittstellen für Methoden und Variablen
public ist überall sichtbar überall in der Klasse sichtbar
private nicht erlaubt nur in der eigenen Klasse sichtbar
abstract kann nicht instanziiert werden Methoden ohne Rumpf
final keine Subklassen möglich kann nicht überschrieben werden
static nicht erlaubt Klassenmethode und Klassenvariable

 

 

if - Bedingung MB5 (unter case2)

Einfache Bedingung

if ( logischer Ausdruck ) {

Statements;}

Mit else-Zweig

if ( logischer Ausdruck ) {

Statements;}

else {

Statements;}

 

 

 

for-Schleife MB8

for (int name=wert; logischer Ausdruck; Wertänderung) {

Statements;}

 

 

 

while-Schleife MB6

while ( logischer Ausdruck ) {

Statements;}

do { Statements;}

while ( logischer Ausdruck );

 

 

 

switch-Anweisung MB5

switch ( ganzzahliger Ausdruck ) {

case wert1:

Statements;

break;

case wert2:

Statements;

break;

...

default:

Statements;

}

Mit continue; kann man den aktuellen Schleifendurchgang vorzeitig beenden und den nächsten Schleifendurchgang beginnen.

Mit break; kann man eine Schleife oder einen case-Block verlassen und zum Statement nach

Ende der Schleife bzw. des switch-Blockes springen.

Mit labelname: kann man einen Label vor ein for-, while- oder do-Statement setzen und dann mit continue labelname; bzw. break labelname; diese (äußere) Schleife vorzeitig beenden.

Es gibt in Java kein goto-Statement zum Sprung an beliebige Stellen.

Nach switch dürfen nur Fallausdrücke vom Typ int stehen, und case prüft nur int-Konstanten.

zurückzurückzurück

13. Das AWT

(Abstract Windowing Toolkit)

Das AWT stellt Hilfsmittel für die Benutzung von grafischen Oberflächen bereit.

Es ist ein Package, das Klassen für die Zusammenstellung und Verwendung von graphischen Benutzungsoberflächen (GUI - Graphical User Interface) enthält. GUIs bestehen aus Fenstern, Menüs, Eingabefeldern, Textfeldern, Buttons und ähnlichen Elementen. Die Steuerung durch den Benutzer erfolgt mit Tastatur (Keyboard) und Maus. Das sind Ereignisse (Events), die das Programm überwachen muß. Die linke Maustaste (als Beispiel) löst beim Drücken ein Ereignis aus. Dieses Ereignis wird vom Eventhandler verwaltet. Dadurch kann dann eine vorab definierte Aktion ausgelöst werden.

Programme werden von der Umgebung gesteuert. Man bezeichnet damit eine Oberfläche, von der aus zu steuern ist, wie ein Programm ablaufen soll. Es werden ein Einspringpunkt und und Verzweigungen zur Behandlung von Ereignissen definiert. Man legt z.B. fest, daß durch Betätigen des Button "ENDE" das aktuelle Fenster geschlossen wird. Durch Verknüpfen von Ereignissen mit einem dazu definierten Programmablauf wird die "Umgebung gesteuert".

Das Wort abstrakt (abstract) im Namen AWT deutet darauf hin, daß in diesen Klassen nur die plattformunabhängigen wesentlichen Eigenschaften der Komponenten definiert sind und für die konkrete Darstellung dann die am jeweiligen Rechner vorhandenen Systemkomponenten ("Peers") mit den auf diesem Rechner üblichen Layouts, Aussehen und Funktionalitäten verwendet werden. Ein im Java-Programm mit AWT definierter Button wird also auf einem PC wie normale Windows-Buttons, auf einem Macintosh wie normale Apple-Buttons und unter Unix wie normale Motif- Buttons (eine grafische Oberfläche unter UNIX) aussehen. Das grafische Layout ist also plattformspezifisch. Der Benutzer hat den Vorteil, daß er auf seinem Computersystem immer dieselben gewohnten GUI-Komponenten vorfindet, unabhängig von der Applikation. Einen Nachteil für Applets muß man aber anmerken: Die Anpassung der Größen, Farben, Schriftarten und Graphik-Elemente an die umgebende Web-Page ist nicht möglich.

Grob läßt sich das AWT in 4 Kategorien einteilen:

 

 

Komponenten

Komponenten werden in einem Container angeordnet, der wiederum Panels oder Windows enthalten kann:

Component

Canvas

Container

TextComponent ...............
 

Panel

Windows

   

Applet

Frame Dialog

Controls sind spezielle Komponenten zur optischen Gestaltung, mit denen der Benutzer meist auch interagieren kann. Man denke dabei an das Drücken eines Button. Das AWT stellt folgende Controls zur Verfügung:

Canvas Zeichenfläche
Choise Auswahlliste
Scrollbar Rollbalken
Checkbox Auswahlboxen und Radiobutton
Button Schalter
Label Statischer Text
TextComponent TextArea = mehrzeiliger Text
TextField = einzeiliger Text

Diese Controls werden in Container "gepackt". Dort können sie dann Ereignisse auslösen, die den Programmablauf steuern.

Darstellung der Komponenten

Die Komponenten werden in Fenstern dargestellt, in denen auch bestimmte Ereignisse auftreten können. Für Fenster und Ereignisse gibt es eine Hierarchie:

Die Klasse Container

Sie ist eine Erweiterung der Klasse Component, um das Zusammenfügen mehrer Komponenten in ein Layout zu ermöglichen. Deshalb gibt es Methoden, mit denen man Komponenten verwalten kann, und Methoden zum Festlegen eines Layouts. Jeder Container besitzt einen eigenen Layoutmanager. Dieser legt das grundsätzliche Layout fest und verwaltet es. Die einzelnen Komponenten werden dann einfach dem vorhandenen Layout hinzugefügt. Dazu benutzt man die Methode add(....).

Java bietet 5 Layout-Strategien:

Wie AWT-Komponenten am Bildschirm letztendlich aussehen, hängt hauptsächlich von zwei Aspekten ab: Zum Ersten ist wichtig, in welcher Reihenfolge sie in ein Panel eingefügt werden. Zum Zweiten bestimmt der Layoutmanager, wie Bildschirmbereiche aufgeteilt werden. Das BorderLayout teilt den Bildschirm in "North", "West", South", "East" und "Center" auf.

Um ein bestimmtes Layout herzustellen, geht man so vor:

setLayout(new FlowLayout());

class FlowDemo extends Frame {......}

FlowDemo(String Title){

super(Title);

setLayout(new FlowLayout());

resize(300,200);

.......

add(new Button());

.......

}

Ereignisse

Um mit Controls interaktiv zu arbeiten, müssen Ereignisse (=Mausklicks, Mausbewegungen und Tastaturaktionen), die diese Controls betreffen, erkannt und ausgewertet werden. Von einem auftretenden Ereignis muß zuerst dessen Herkunft ermittelt werden. Dann kann ihm eine bestimmte Aktion zugeordnet werden. Das AWT bietet hierfür die Klasse Event an. Diese Klasse enthält sehr viele Instanz-Variablen und Konstanten. Die wichtigsten Variablen sind:

public Object target; Herkunft des Ereignisses

public Object arg; Argument des Ereignisses

public long when; "wann" des Ereignisses

public int x; x-Koordinate des Ereignisses

public int y; y-Koordinate des Ereignisses

public int key; Tastenwert des Ereignisses

public int id; die Art des Ereignisses; z.B. steht KEY_PRESS für einen Tastendruck

Jede Komponente der Benutzeroberfläche erzeugt ihre spezielle Ereignisart, die man Aktion nennt:

Damit eine Komponente der Benutzeroberfläche aber überhaupt erst eine Aktion auslösen kann, muß in der Klasse eine action()-Methode definiert werden:

public boolean action(Event evt, Object arg) {....}

Das Event-Objekt enthält das eigentliche Ereignis und in arg ist das Argument des Ereignisses enthalten. Ganz konkret: Wird der Button mit der "Inschrift - OK" mit der Maus angeklickt, dann löst das AWT ein Ereignis aus. Es enthält als target die Instanz des gedrückten Button und als arg den Text des Labels.

Die in der Event-Klasse definierten Konstanten geben Auskunft darüber, welches Ereignis aufgetreten ist. Einige wichtige Konstanten:

public final static int ACTION_EVENT; beliebiger Mausklick

public final static int KEY_PRESS; beliebiger Tastendruck

public final static int MOUSE_DOWN; Maustaste gedrückt

public final static int MOUSE_UP; Maustaste losgelassen

public final static int MOUSE_MOVE; Maus bewegt

public final static int WINDOW_ICONIFY; Fenster wurde ikonifiziert

public final static int WINDOW_DESTROY; Fenster wurde zerstört

Während also diese Konstanten die Art des Ergnisses beschreiben, enthalten die Instanz-Variablen der Klasse Event eine nähere Beschreibung des Ereignisses.

Der AWT-Event-Handler

Tritt ein Ereignis ein, so wird der Container, der die Komponente enthält, informiert und die Methode handleEvent() wird aktiviert. In dieser Standardmethode werden die Ereignisse verarbeitet und entsprechende Methoden gerufen. In der Standardimplementierung dieser Methode werden viele Ereignisse selbsttätig behandelt (z.B. eine Mausbewegung). Um nun diese Standard-Ereignis-Behandlung zu ändern oder eigene Ereignisse zu definieren oder weiterzureichen, muß man handleEvent() einfach überschreiben:

 

public boolean handleEvent(Event evt) {..........}

 

Aber Vorsicht! Falls man diese Klasse überschreibt, werden danach keine der beschriebenen Standardmethoden mehr aufgerufen. So kann es z.B. passieren, daß ein Applet-Fenster nicht mehr geschlossen werden kann. Um solche Probleme zu vermeiden, testet man die Ereignisse, die interessieren, und ruft danach die Methode super.handleEvent(), so daß die Superklasse diese Aufgabe übernehmen kann. Das folgende Beispiel soll das Vorgehen verdeutlichen:

public boolean handleEvent(Event evt){

if (evt.id==Event.MOUSE_DOWN){/*Hier wird das Drücken der Maustaste verarbeitet*/

 

return true;}

else {return super.handleEvent(evt);}

}

Es gibt noch einen weiteren Grund, warum man sicherheitshalber noch die "Muttermethode" von handleEvent() rufen sollte. Der Rückgabewert von handleEvent() ist vom Typ boolean. Er zeigt an, ob die Behandlung des Ereignisses abgeschlossen ist, oder ob es noch weiterer Aktionen bedarf. Da man dies nie genau wissen kann, ist es sinnvoll, die Methode der Superklasse mit gleichen Parametern, die man selbst bekam, zu rufen und ihr die Entscheidung zu überlassen.

HandleEvent() ist der "Hauptverantwortliche" für die Ereignisbehandlung. Sie wird bei jedem Ereignis aufgerufen und diese Methode ruft dann bei Bedarf weitere Methoden auf:

HandleEvent()

action()

bei Mausklick

mouseDrag()

bei Mausbewegung

keyDown()

bei Tastendruck

Ereignisse der elementaren Controls

Jedes Control löst eine bestimmte Aktion aus. Die Zuordnung ist wie folgt (Auswahl):

Control id im Event Typ von arg
Button ACTION_EVENT String: Name des Button
Choise ACTION_EVENT String: Name der Auswahl
Checkbox ACTION_EVENT Boolean: Zustand der Box
Label ----- -----
Textarea KEY_PRESS String
TextField KEY_PRESS String
Scrollbar SCROLL_LINE_UP
SCROLL_LINE_DOWN
SCROLL_PAGE_UP
SCROLL_PAGE_DOWN
Integer: Position
Integer: Position
Integer: Position
Integer: Position

Die Klasse Component stellt Methoden zur Bearbeitung von diesen Ereignissen zur Verfügung. Ein JAVA-Programm kann solche Ereignisse durch Überschreiben einer der folgenden Methoden behandeln:

keyDown(Event evt, int key);

keyUp(Event evt, int key);

mouseDrag(Event evt, int x, int y);

mouseDown(Event evt, int x, int y);

mouseUp(Event evt, int x, int y);

action(Event evt, Object what);

Ereignisprüfung

Für die Behandlung von Ereignissen ist es wichtig zu wissen, welches Ereignis die Aktion auslöste. Man nutzt hierzu die action()-Methode:

 

public boolean action(Event evt, Object arg){

if (evt.target instanceof TextField)

{ ... ; return true}

return false

}

Kommt das Ereignis von einem Textfeld?

public boolean action(Event evt, Object arg){

if (evt.target == button1)

{ ... ; return true}

return false

}

Kommt das Ereignis von dem Button mit Namen "button1"?

public boolean action(Event evt, Object arg){

if (evt.id == KEY_PRESS)

{ ... ; return true}

return false

}

War das Ereignis ein Tastendruck?

 

zurückzurückzurück

14. Das AWT im Detail

 

Der folgende Abschnitt behandelt einzelne Komponenten des AWT in loser Reihenfolge. Eine sinnvolle Reihenfolge zu finden ist nicht ganz einfach, da alle Komponenten miteinander verzahnt und voneinander abhängig sind. Um aber überhaupt Komponenten darzustellen, braucht man einen Container. Von der Klasse Container ist die Windows-Klasse abgeleitet. Es liegt also nahe, mit dieser Klasse zu beginnen.

Fenster

Panel und Windows sind Subklassen von Container. Wie es der Name schon vermuten läßt, ist ein Window ein auf dem Bildschirm darstellbares Fenster. Es hat aber weder eine Titelleiste, noch Schaltflächen zum Schließen oder Bewegen des Fensters. Deshalb benutzt man diese Klasse auch nur zur Erstellung von Popup-Menüs. Windows besitzt aber noch die Subklassen Frame und Dialog. Während Dialog hauptsächlich für die Entgegennahme von Benutzereingaben verantwortlich zeichnet, ist Frame die eigentliche Fensterklasse. Dieses Fenster besitzt

Für die Erstellung eines Fensters bildet man einfach eine Subklasse von Frame. Durch Überschreiben der Methoden Component.action(), Component.mouseDown() und Component.keyDown() werden Ereignisse behandelt. Ebenso generiert die Frame-Klasse die folgenden Fensterereignisse:

Ein neu erstelltes Fenster ist zunächst unsichtbar. Erst die Methode show() zeigt es an. Mittels dispose() läßt es sich schließen.

Dem Frame ist der BorderLayout-Manager standardmäßig zugeordnet. Da dieses Layout nur 5 Komponenten (nämlich die 4 Seitenränder und die Mitte) enthalten kann, ist es besser, für umfangreiche Inhalte eines neues Layout zu definieren.

Bei einem einfachen Applet braucht man sich nicht um die Erstellung eines Fensters zu sorgen. Die Klasse Applet erledigt dies selbstständig. Erst wenn aus diesem "Urfenster" neue Fenster erzeugt werden sollen, nutzt man die Klasse Frame.

Das Musterbeispiel MB18 zeigt exemplarisch die Erstellung von zwei Fenstern.

class ButtonDemo extends Frame{

ButtonDemo(String Title){// Konstruktor für Fenster

super(Title);

setLayout(new FlowLayout());//Layoutmanager

resize(300,200);

for(int i=1; i<=5;i++)

add(new MyButton("Schalter"+i)); //Erzeugen von 5 Schaltern

show();// Anzeige der Schalter

}

Der Konstruktor für das Fenster wird gleich zur Erstellung eines neuen Layouts verwendet und erzeugt anschließend 5 Button. Damit nun auch tatsächlich Fenster auf dem Bildschirm zu sehen sind, müssen Objekte dieser Klasse erzeugt werden:

F1 = new ButtonDemo("Fenster 1");

F2 = new ButtonDemo("Fenster 2");

Ereignisse

Die "höchste" Ereignisbehandlung stammt aus der Klasse Component. Es ist die Methode handleEvent(). Sie wird vom System immer dann aufgerufen, wenn eine beliebige AWT-Komponente eine Aktion auslöste, also ein Ereignis auftrat. Die Aufgabe des Programmierers besteht nun darin, das aufgetretene Ereignis zu lokalisieren und entsprechende Reaktionen einzuleiten. Wird z.B. in einem Dialog der OK-Button gedrückt, so könnten alle mit diesem Dialog in Verbindung stehenden Fenster geschlossen werden.

Das Beispiel MB20 zeigt eine triviale Ereignisbehandlung. Drei Button in einem FlowLayout werden überwacht. Die tut die Methode handleEvent():

public boolean handleEvent(Event evt){

if (evt.id==Event.ACTION_EVENT && evt.target==S1)

{S2.disable();return true;}// Button deaktiv. (schwach zeigen)

if (evt.id==Event.ACTION_EVENT && evt.target==S3)

{S2.enable();return true;}// Button aktivieren (kräftig zeigen)

return false;// Rückgabewert, falls keine if-Bedingung erfüllt

}

}

Die Methode prüft, welches Ereignis (id) und vom wem (target) es ausgelöst wurde. Die entsprechenden Aktionen werden sogleich ausgeführt: Der Button S2 wird entweder aktiviert oder deaktiviert, abhängig davon, ob S3 oder S1 der Verursacher des Ereignisses war.

Layoutmanager

Der eigentliche Layoutmanager ist eine Schnittstelle, die von den Klassen FlowLayout, BorderLayout, CardLayout, Gridlayout und GridBagLayout implementiert wird. Mit ihm kommt man im Allgemeinen gar nicht in Berührung. Er ist eine Art transparente "Hilfsklasse", die beim Erstellen eines Layouts im Hintergrund mitarbeitet.

Beispiel MB16 zeigt die Erstellung eines FlowLayouts. Dieses Layout ordnet die Komponenten einfach in einer Reihe von links nach rechts an, und zwar standardmäßig zentriert. Die drei Konstanten FlowLayout.LEFT, FlowLayout.RIGTH und FlowLayout.CENTER sorgen explizit für die Ausrichtung. Das eigentliche Layout ist in der Klasse FlowDemo plaziert, die eine Subklasse von Frame ist:

setLayout(new FlowLayout());

Hier passieren gleich zwei Dinge: Der Konstruktor der FlowLayout-Klasse erzeugt ein neues Objekt dieser Klasse und die Methode setLayout() erzeugt bzw. plaziert das Layout im Frame. Nun, das Layout ist noch leer, Elemente müssen noch hinzugefügt werden:

add(new Button("Schalter"+i));

Die Methode add() fügt Komponenten hinzu. Welche Komponenten hinzugefügt werden, bekommt die Methode als Parameter mitgeteilt. In diesem speziellen Fall müssen die Button erst erstellt werden. Es sind nämlich noch keine Objekte vorhanden, die in das Layout eigefügt werden könnten. Also, mit "new Button()" (Konstruktor Button(String label) der Klasse Button) werden in der for-Schleife 5 Button erzeugt und danach zum vorher gesetzten Layout hinzugefügt.

Die zwei grundlegenden Dinge für die Layoutdefinition, Layouterstellung und das Hinzufügen von Komponenten, sollte man sich gut merken. Auf die Layouterstellung kann verzichtet werden, falls man das für die jeweilige Klasse vordefinierte Standardlayout verwendet. Für Frame wäre dies Borderlayout. Da im MB16 jedoch FlowLayout zur Anwendung kommen sollte, mußte es auch neu gesetzt werden. Komponenten müssen immer zum Layout hinzugefügt werden. Ein Button etwa, oder ein Label, sind standardmäßig ohne add() nie vorhanden. Auch müssen diese Komponenten immer mittels show() zur Anzeige gebracht werden.

Das Musterbeispiel MB15 erzeugt ein BorderLayout in einem Fenster. Im Frame ist das BorderLayout Standard. Man sucht deshalb in diesem Beispiel die setLayout()-Methode vergeblich. Es müssen nur die Button erstellt und dem Layout hinzugefügt werden. Dies alles geschieht im Konstruktor der BorderDemo-Klasse. Da diese Klasse wiederum eine Subklasse von Frame ist, wird mit

new BorderDemo("Bsp. BorderLayout");

das Fenster erzeugt, in dem das Layout bereits mit allen Komponenten plaziert ist.

Button, Choise, Checkbox

Der Button stellt einen einzeiligen Text in einen tastenartigen Rahmen, eine Auswahlliste (Choise) listet Stringelemente auf, von denen aber immer nur ein Element in einem Textfeld sichtbar ist, und die Checkbox stellt ebenfalls Stringelemente dar, die man mittels Häckchen auswählen kann. Diese Elemente nehmen also keine Benutzereingaben (z.B. Texteingabe) entgegen, sondern wählen nur vordefinierte Elemente aus und lösen danach eine Aktion aus, um das Ereignis zu behandeln.

Eine zentrale Stellung nimmt der Button ein. Er stellt eine Fläche mit der Funktionalität eines Schalters dar. Ein Text in dieser Fläche bezeichnet den Schalter. Wird dieser Schalter mit der Maus angeklickt, so sendet das AWT einen Event "ACTION_EVENT". Als target enthält das Ereignis eine Instanz von Button und als Argument arg den Namen (Text) des Schalters. Diese Zusammenhänge sollte man sich immer vor Augen führen, wenn Schalter zu konstruieren und deren Aktionen auszuwerten sind

Das MB21 zeigt die Erstellung von Button:

public class Knopf6 extends Applet{

Button S1=new Button("Knopf1"); Button S2=new Button("Knopf2");

Button S3=new Button("Knopf3"); Button S4=new Button(" ");

Button S5=new Button("Knopf5"); Button S6=new Button("Knopf6");

public void init(){

setLayout(new FlowLayout()); resize(800,400);

add(S1);add(S2);add(S3);add(S4);add(S5);add(S6);

}

Zur Anwendung kommt der Konstruktor Button(String label) der Klasse Button. Die jeweilige Buttonbeschriftung wird dem Konstruktor als Parameter übergeben. Mit add(Sn) fügt sich der Button in das Layout.

Interessant wäre noch die Ereignisbehandlung:

if (evt.id==Event.ACTION_EVENT && evt.target==S1)

{S3.hide();S4.hide();S5.hide();S6.hide();S1.show();S2.show();return true;}

Wird z.B. der Button S1 betätigt, so treten die Methoden hide() (verstecken, bzw. Button inaktivieren) und show() (zeigen, bzw. Button aktivieren) in Aktion. Die beiden nebeneinander liegenden Button werden aktiviert und die restlichen deaktiviert. Die Methode handleEvent() besitzt einen Rückgabewert vom Typ boolean. Deshalb wird im Erfolgsfall "true" von dieser Methode zurückgegeben (das Ereignis konnte behandelt werden), ansonsten "false", was heißt, handleEvent() wurde zwar gerufen, aber die Aktion stammte nicht von den getesteten Quellen (evt.target==Sn) oder sie war kein Mausklick (evt.id==Event.ACTION_EVENT).

Das Erstellen einer Auswahlliste (Choise) zeigt MB21:

ChoiceDemo(String Title){

super(Title);

resize(300,200);

MyChoice waehle=new MyChoice();

for(int i=1; i<=5; i++)

waehle.addItem("Auswahl"+i);

waehle.select(3);

add("Center", waehle);

show();

}

new MyChoise(); Erstellt Listenelement

addItem fügt Einträge hinzu

Select() wählt ein Element aus

add() fügt gesamte Liste in das Layout

show() zeigt Liste im Layout an; hide() würde sie wieder verstecken.

Die Methode ChoiseDemo(String Title) ist der Konstruktor für diese im Musterbeispiel erstellte Klasse. Ihre Superklasse ist Frame, das erstellte Objekt ist also ein Fenster. In die Variable "waehle" wird ein Objekt vom Typ MyChoise geschrieben. Die Superklasse von MyChoise ist Choise. Das erstellte Objekt ist also eine Auswahlliste. Die Klasse Choise stellt die Methoden addItem() (ein Element der Liste hinzufügen) und select() (ein Element der Liste aktivieren) zur Verfügung. Die for-Schleife erstellt also 5 Listenelemente und danach wird das Element Nummer 3 ausgewählt. Nicht vergessen werden darf die Methode add(). BorderLayout ist Standard bei Frame (!!!), und "waehle" wird jetzt im "Center" (nicht in Nord oder Süd, sondern im Zentrum) dem Layout hinzugefügt. Mit show() erfolgt die Anzeige des Layout.

Die Klasse Choise beinhaltet auch die Methode getSelectedItem(). Sie liefert als String die Nummer des ausgewählten Listenelementes zurück. Aufgerufen wird diese Methode in action(), also immer dann, wenn Choise ausgewählt wurde.

Das Erstellen einer Checkbox zeigt MB25:

Checkbox cb1, cb2, cb3, cb4, cb5 ,cb6;

CheckboxGroup cbg;

Panel p1, p2;

cb1=new Checkbox();

cb1.setLabel("Checkbox1");

cb2=new Checkbox("Checkbox2");

cb3=new Checkbox("Checkbox3");

cb3.setState(true);

Die Objekte cb1....cb6 gehören der Klasse Checkbox des AWT an. Davon werden später cb4....cb6 dem Radiobutton cbg, ein Objekt der Klasse CheckboxGroup, zugeordnet. Anschaulich ist hier demonstriert, wie verschiedene Konstruktoren der Klasse Checkbox Verwendung finden. Das Objekt cb1 wird mit dem Standardkonstruktor erstellt. Mittels der Methode setLabel() bekommt das Element einen Namen. Durch die Methode setState(true) bekommt die Box cb3 ein Häckchen. Außerdem wurden zwei Panels p1 und p2 erstellt, in welche die Checkbox und der Radiobutton eingefügt werden.

 

cbg=new CheckboxGroup();

cb4=new Checkbox("Radiobutton1",cbg,false);

cb5=new Checkbox("Radiobutton2",cbg,false);

cb6=new Checkbox("Radiobutton3",cbg,false);

p2=new Panel();

p2.add(cb4);p2.add(cb5);p2.add(cb6);

setLayout(new GridLayout(1,2));

add(p1);

add(p2);

 

Die Elemente des Radiobutton cbg gehören der Klasse Checkbox an. Dies muß man wissen. Die Klasse CheckboxGroup realisiert nur die Eigenschaft "Radiobutton", nämlich daß von n-Elementen nur eines oder keines ausgewählt sein kann. Die eigentlichen Elemente sind Objekte von CheckBox. In diesem Beispiel ist noch kein Element des Radiobutton ausgewählt (false im Konstruktor). Dies tut dann erst der Nutzer des Applet.

Die Radiobutton cb4, cb5 und cb6 sind dem Panel p2 zugeordnet. Als Layout wird GridLayout mit einer Zeile und zwei Spalten gesetzt. Dieses Beispiel zeigt nochmals sehr anschaulich die Layouterstellung. Man erinnere sich: Beim Applet ist FlowLayout standardmäßig implementiert. Deshalb muß das GridLayout explizit gesetzt werden (setLayout()).

Label, Textarea, Textfeld

Label ist ein einzeiliges, nicht editierbares Textfeld. Eine TextArea ist eine mehrzeilige Textkomponente mit Rollbalken. Die Klasse TextField dagegen ist nur einzeilig. Beide sind editierbar und Subklassen von TextComponent. Von dort erben sie auch die Methoden, um den Text zu setzen und zu lesen. TextComponent ist Basisklasse für alle Controls, die der Editierung von Text dienen.

Das MB25 erstellt solche Textelemente:

class Fenster extends Frame{

TextField S1;

Button S2;

Label S3;

Fenster(String str){

super (str);

resize(400,200);

S1=new TextField(20);

S2=new Button("Zeige den Text an");

S3= new Label("Fein");

Im Konstruktor der Klasse Fenster werden die Textelemente S1 (einTextfeld von 20 Zeichen sichtbarer Länge) und S3 (Ein Label mit der Inschrift "Fein") erstellt. In S3 wird nach Drücken des Button S2 der Text, den der Benutzer in S1 eingegeben, geschrieben. TextField nimmt natürlich mehr als 20 Zeichen von der Eingabe entgegen. Aber es sind nur die letzten 20 eingegebenen Zeichen sichtbar. Die Konstruktoren TextField(int cols) und Label(String) entstammen den Klassen TextField bzw. Label. Die Klassen stellen meist Konstruktoren mit den verschiedensten Parametern zur Verfügung. Dies muß man wissen und gegebenfalls in der Klassendokumentation nachschauen.

Ein Lehrbeispiel ist noch die Behandlung der Ereignisse:

public boolean handleEvent(Event evt){

if(evt.id==Event.WINDOW_DESTROY){

System.exit(0);

}

return super.handleEvent(evt);

}

public boolean action(Event evt, Object arg){

if(evt.target==S2){

neu_objekt();return true;

}

return false;

}

void neu_objekt(){

String text=S1.getText();// Textfeld auslesen

System.out.println(text);

S3.setText(text);//Text in das Label schreiben

S1.setText("");//Textfeld löschen

}

Die Methode handleEvent() fängt nur das Schließen des Fensters ab. Damit wird das Programm beendet (System.exit(0)). Andere Ereignisse werden der Superklasse zur Weiterbehandlung übergeben (return super.handleEvent(evt)). Auf ein Ereignis von S2 wartet die action()-Methode. In ihr realisiert die Methode neu_objekt(), daß bei Mausklick auf den Button S2 das Textfeld S1 ausgelesen und in die Variable "text" geschrieben wird, diese Variable auf die Standardausgabe (Bildschirm) und in S3 (Label im Applet) geschrieben und das Textfeld S1 gelöscht wird.

Rollbalken, Menüs

Die Scrollbar ist eine Schiebeleiste, die hauptsächlich für zwei Anwendungen gebraucht wird:

Der Stand des Schiebers entspricht einem Integer. Mit Hilfe der Maus kann der Wert verändert werden. Die Methoden getValue() und setValue() lesen oder setzen diesen Wert. Die Ausrichtung des Rollbalkens bestimmen die Konstanten HORIZONTAL und VERTIKAL.

Das MB30 erstellt einen vertikalen und einen horizontalen Rollbalken:

class Rollbalken extends Frame{

Scrollbar h, v;

Rollbalken(String str){

super(str);

resize(300,300);

setLayout(null);

h=new Scrollbar(Scrollbar.HORIZONTAL,50,30,0,100);

v=new Scrollbar(Scrollbar.VERTICAL,50,20,0,100);

h.reshape(50,50,150,30); v.reshape(50,100,30,150);

add(h); add(v);

show();

}

 

Der Konstruktor Scrollbar(int orientation, int value, int visible, int minimum, int maximum) bedarf einer näheren Erläuterung. Orientation bestimmt die Ausrichtung, minumum und maximum die Extremwerte der Bar. Value meint den im Vorab eingestellten Wert der Bar, den der Anwender dann ändern kann. Visible sagt etwas über den sichtbaren Bereich aus. Für die horizontale Leiste ist visible=30 eigestellt. Bewegt man nun den Balken ganz nach rechts, also zum Maximum des "Endausschlages", so zeigt die Bar einen Wert von 70 an. Wollte man also einen Endausschlag von 100 erreichen, dann müßte visible=0 sein. Entsprechend stände bei visible=100 der Balken immer ganz links, am minimalen "Endausschlag", und ließe sich nicht nach rechts bewegen.

Reshape(int x, int x, int width, int height) legt die Größe und Ausdehnung fest. Die Methode stammt allerdings nicht aus der Klasse Scrollbar, sondern wird von der Superklasse (Component) geerbt.

Bei Benutzung der Scrollbar treten die u. a. Ereignisse SCROLL_LINE_UP oder SCROLL_LINE_DOWN auf. Aber keines der Ereignisse wird in handleEvent() überwacht:

public boolean handleEvent(Event evt){

if (evt.id==Event.WINDOW_DESTROY){

System.exit(0);

}

int text1=h.getValue();

int text2=v.getValue();

System.out.println("Horizontal="+text1+" /Vertikal="+text2);

return super.handleEvent(evt);

}

Vielmehr wird bei jeder Mausbewgung die Methode gerufen, und falls das Fenster nicht geschlossen wird, der aktuelle Wert der beiden Rollbalken in die Standardausgabe kopiert. Man schaue sich diese Beispiel gut an. Es zeigt sehr deutlich, wie das Programm auf jede Mausaktion reagiert. Eine gute Übung wäre es, das Beispiel so abzuwandeln, daß die Ausgabe der aktuellen Barwerte nur bei einem Mausklick und nicht bei jedem beliebigen Mausereignis erfolgt.

Das Erstellen von Menüs und die Auswertung der Menüereignisse gehört schon zur hohen Kunst der Programmierung. Zum Einfügen einer Menüzeile in ein Fenster ist indirekt die Klasse MenuComponent verantwortlich. Sie ist abstract und enthält nur die grundlegenden Gemeinsamkeiten aller von ihr abgeleiteten Menüklassen:

MenuComponent

MenuBar

MenuItem

Menu CheckboxMenuItem

Folgende Struktur muß man sich einprägen:

Frame     MenuBar     Menu      MenuItem oder CheckboxMenuItem

Man beachte, daß Menu eine Subklasse von MenuItem ist, und nicht umgekehrt! Der Frame nimmt einen MenuBar auf, dieser kann mehrere Menu aufnehmen. Die Menus schließlich integrieren je nach Bedarf MenuItem und CheckboxMenuItem. Das Letzte kann explizit mittels eines Häckchens für eine Zeitdauer aktiviert und danach wieder deaktiviert werden. Die Analogie zur Checkbox ist naheliegend.

Menüs werden ähnlich wie Layouts erstellt. Da Menüs mit Frames verbunden sind, gibt es in Frame eine Methode setMenuBar(MenuBar m), mit der eine Menübar erstellt wird. Und genau wie beim Layout, wird mit add(Menu m) aus der Klasse MenuBar ein (oder mehrere) Menü hinzugefügt, und diesem Menü wiederum mittels add(MenuItem mi) aus der Klasse Menu ein oder mehrere MenuItems hinzugefügt.

Man schaue sich MB33 bezüglich der Erstellung von Menüs sehr gut an:

MenuItem m_Sichern, m_Oeffnen, m_ROT,

m_HELLBLAU, m_MITTELBLAU,

m_DUNKELBLAU, m_PINK;

Sämtliche Menüeinträge sind Objekte von MenuItem. Es sind die eigentlichen "Menüoptionen", die zur Auswahl bereit stehen, also etwa "Sichern" (m_Sichern) und "Rot" (m_Rot). Komponenten, die MenuItems aufnehmen, sind Menüs (Klasse Menu). Und in dieser Reihenfolge wird die gesamte MenuBar erstellt:

public void initialisiereMenu(){

MenuBar menuLeiste=new MenuBar();

setMenuBar(menuLeiste);

Menu meinMenu;

menuLeiste.add(meinMenu=new Menu("Datei"));

meinMenu.add(m_Sichern=new MenuItem("Sichern"));

meinMenu.add(m_Oeffnen=new MenuItem("Oeffnen"));

menuLeiste.add(meinMenu=new Menu("Optionen"));

meinMenu.add(m_ROT=new MenuItem("ROT"));

meinMenu.add(m_PINK=new CheckboxMenuItem("PINK"));

Menu msubmenu=new Menu("BLAUTÖNE");

meinMenu.add(msubmenu);

msubmenu.add(m_HELLBLAU=new MenuItem("Hellblau"));

msubmenu.add(m_MITTELBLAU=new MenuItem("Mittelblau"));

msubmenu.add(m_DUNKELBLAU=new MenuItem("Dunkelblau"));

}

Das Submenü "Blautöne" ist ein Objekt der Klasse Menu. Es erscheint deshalb als Untermenü von "Optionen", weil es nicht zur menuLeiste, sondern zu meinMenu addiert wird. Auch die Reihenfolge der Erstellung ist wichtig. "Blautöne" erscheint nach "Pink", in der Reihenfolge der Erstellung.

Für die optische Darstellung der Menüs brauchen keine Ereignisse behandelt werden. Die Menüs öffnen sich nach Mausklick selbständig. Sogar das Häckchen des Menü "Pink" wird automatisch gesetzt und entfernt. Als "einzige" Aufgabe für den Programmierer verbleibt die Vergabe von Aktionen. Wird z.B. das Menü "Sichern" ausgewählt, dann muß der Dialog zum Sichern einer Datei eingeleitet werden. Und dies kann wieder schwierig werden.....

Im Beispiel wird das Ereignis "Maus im Menü Sichern" abgefangen. Als Reaktion erfolgt ein Ausdruck auf die Standardausgabe.

Zeichenfläche

Die Klasse Canvas bietet dem Nutzer eine nackte Zeichenleinwand. Sie soll speziell für Zeichnungen und Grafiken verwendet werden. Zwar kann man auch auf Panels und Windows zeichnen, aber einen grundlegenden Vorteil bietet Canvas doch: Da Canvas eine eigene Komponente ist, kann sie im Layout verschoben werden. Muß man diesen Vorteil nicht nutzen, so kann man ebensogut auf Canvas verzichten.

Canvas erzeugt Maus- und Tastaturereignisse und überschreibt die Methode paint() der Klasse Component.

Das Beispiel MB31 zeigt das Erstellen einer Leinwand:

class MyCanvas extends Canvas{

public void paint(Graphics g){

Rectangle r=bounds();

int b=r.width-10;

int h=r.height-10;

g.drawRect(0,0,b,h);

g.drawLine(0,0,b,h);

g.drawLine(b,0,0,h);

}

}

class Zeichenflaeche extends Frame{

Zeichenflaeche(String str){

super(str);

resize(300,200);

add("Center", new MyCanvas());

show();

}

MyCanvas erstellt mittels paint() ein Rechteck und zwei Linien. Für ein Rechteck existiert eine eigene Klasse, nämlich Rectangle. Das Rechteck wird durch seine Ursprungskoordinaten x und y, und seine Ausdehnung, height und width, vollständig beschrieben. Bounds() ermittelt indirekt diese Koordinaten. Die Methode bounds() entstammt der Klasse Component. Ihr Rückgabeparameter ist ein Objekt vom Typ Rectangle. In diesem Beispiel wird mittels bound() die Größe des Fensters ermittelt (das "oberste" Rechteck) und daraus die Koordinaten für das zu zeichnende Rechteck berechnet.

In der Klasse MyCanvas wird die Zeichenfläche also schon "bemalt". So muß später in der Klasse Zeichenfläche nur noch ein Objekt von MyCanvas erstellt werden. Nicht vergessen: Dieses Objekt muß dem Layout hinzugefügt und mit show() sichtbar gemacht werden!

Formulare

Formulare sind eigentlich keine Besonderheit oder bilden gar eine eigene Klasse. Vielmehr kann an Formularen demonstriert werden, wie die einzelnen Komponenten des AWT zusammenwirken. Im Web findet man häufig Applets mit Formularen. Sie nehmen Benutzereingaben (Name, Anschrift, Bestellauftrag, Nutzerprofil ...) entgegen und senden diese an den Server. Der Server generiert eine Antwort und gibt diese dem Client zurück. Die einfachste Art der interaktiven Kommunikation ist damit realisiert. Formulare sind einfache und effektive Methoden, um mit der Umwelt zu kommunizieren. Natürlich sind Formulare auch mit HTML oder Javascript zu realisieren. Ein Applet macht es ebensogut und besser.

Im Beispiel MB22 kann man sich exemplarisch die Erstellung eines einfachen Formulares verdeutlichen. Das Beispiel ist bewußt einfach gehalten, um die einzelnen Abläufe

klar herauszuzeichnen. Eigentlich gibt es zu diesem Beispiel nicht mehr viel zu sagen. Es beinhaltet alles bisher Gelernte. Man schaue es sich in Ruhe an. Wer dieses Beispiel ohne Probleme versteht und auch erklären kann warum der Ablauf so oder in ähnlicher Art erfolgen sollte, hat einen guten Teil von Java verstanden und kann sich getrost an die nächsten Kapitel wagen.

 

Bewegungen - Zeichnen und Neuzeichnen

 

Zum Simulieren von Bewegungen oder um veränderte Zeichnungen darzustellen, benutzt man die Methode repaint(). Diese erzwingt den Ruf der Methode update(), welche die Zeichenfläche löscht und danach wieder paint() auffordert, neu zu zeichnen. Dieser Ablauf ist immer dann notwendig, wenn sich Teile einer Grafik ändern und das Bild am Monitor aktualisiert werden muß:

repaint() ---- update() ---- paint()

Mit paint(Graphics g) zeichnet man das Objekt. Diese Methode stammt aus Component, wird aber von vielen Klassen überschrieben. Sie benötigt ein Objekt der Klasse Graphics, eine Subklasse von Object, die nicht so leicht zu durchschauen ist. Sie stellt einen Grafikkontext für Javaausgabegeräte wie Fenster und Images bereit. Man soll sie als plattformunabhängige Schnittstelle für Grafikroutinen begreifen. Sie enthält Methoden um Linien zu zeichnen, geometrische Figuren zu füllen, Flächen zu kopieren und Farben und Schriftfonts bereitzustellen.

Ein Objekt der Klasse Graphics kann nicht einfach mit einem Konstruktor erstellt werden. Wie sollte dies auch geschehen? Man muß sich bei dieser Klasse immer vor Augen halten, daß nur der Grafikkontext vermittelt wird. Deshalb stellt Component die Methode getGraphics() bereit, die ein solches Objekt liefert. Ein Duplikat einer vorhandenen Instanz erzeugt die Methode create() aus der Klasse Graphics.

MB32 nutzt die Methode repaint() zum erneuten Zeichnen einer Linie:

public class bewegeLinie extends Applet{

int x1=100; int y1=100; int x2=40; int y2=40;

public void paint(Graphics g){

g.drawLine(x1,y1,x2,y2);

}

 

public boolean mouseDown(Event e, int x, int y){

x2=x; y2=y;

repaint();

return true;

}

}

 

 

Die erste, ursprüngliche Linie zeichnet paint() mit den voreingestellten Koordinaten, die den Variablen am Programmanfang zugewiesen werden. Die mouesDown()-Methode ermittelt die aktuellen Mauskoordinaten und überschreibt damit die Endkoordinaten der Linie. Danach wird repaint() gerufen und die Zeichnung aktualisiert.

 

zurückzurückzurück

15. Applets

 

Applets können nur in einem Browser ausgeführt werden. Sie beinhalten gegenüber den eigenständigen Applikationen keine main()-Methode, laufen nach einem festen Schema ab und sie unterliegen einem bestimmten Lebenszyklus. Der Browser erzeugt ein Applet-Objekt und startet danach das Applet:

  1. public class AppletName extends Applet { ... }

Hier werden alle Initialisierungen vorgenommen. Das kann das Anlegen von Variablen oder das Laden von Ressourcen sein. Für eigene Initialisierungen überschreibt man init().

Diese Methode wird sofort nach den Initialisieren oder nach einen stop(), zum erneuten Starten des Applets ausgeführt. Das Applet wird z.B. gestoppt, wenn die Seite im Browser, in der das Applet sichtbar ist, verlassen wird, oder das Applet aus dem sichtbaren Bereich gescrollt wird. Kehrt man zur Applet-Seite zurück, dann startet mittels start() das Applet wieder. Dies alles erledigt standardmäßig der Browser. Für andere Reaktionen überschreibe man die start()-Methode.

Normalerweise braucht diese Methode nicht überschrieben zu werden, da der sogenannte Gabage-Collector für ein geordnetes Aufräumen sorgt.

init() start() Jetzt lebt das Applet!

stop()

destroy()

Geburt

Tod

Aus Sicherheitsgründen sind dem Applet einige Beschränkungen auferlegt:

MB14 zeigt ein einfaches und MB46 ein kompliziertes Applet.

zurückzurückzurück

16. Threads

 

Bisher liefen die betrachteten Musterbeispiele in einer rein sequentiellen Programmfolge ab. Ein Programm wurde gestartet, der Kode abgearbeitet, und danach kam es zum Ende. Man könnte sich aber auch vorstellen, daß ein Programm aus mehreren solchen Einzelprogrammen besteht. Diese Einzelprogramme bezeichnet man als Threads. Immer dann, wenn ein Programmabschnitt eine gewisse Zeit für seine Ausführung benötigt (man denke z.B. an das vorausschauende Herunterladen einer Web-Seite, die später im Programm benötigt wird) und das Hauptprogramm nicht gerade sinnerfüllt wartet, also auch etwas besseres tun könnte, immer dann sollte man diesen Programmteil in eine Thread stellen. So ist es möglich, daß ein Programm zur gleichen Zeit mehrere Aufgaben verrichten kann. Besitzt die Computerhardware mehrere Prozessoren, dann wird in Echtzeit parallel gearbeitet. Ist dagegen nur ein Prozessor vorhanden, dann muß das Betriebssystem multithreading unterstützen. In dieser Situation bekommt jeder Thread eine Zeitscheibe, in der er aktiv sein kann, vom Betriebssystem zugeteilt. UNIX, Windows 95 und NT sind multithreadfähige Betriebssyteme. Windows 3.x dagegen nicht. Dies ist auch der Grund, warum es für Windows 3.x noch keine Java-Unterstützung gibt (vielleicht ist es aber inzwischen schon realisiert).

Ein Thread ist eigentlich ein eigenständiges Programm, obwohl er nur im Rahmen des Hauptprogrammes ablaufen kann. Er benötigt deshalb seinen eigenen Programmzähler und seinen eigenen Stack. Deshalb nennt man einen Thread auch "lightweight prozess" oder "execution context".

Der Thread befindet sich stets in einem von 4 Zuständen:

run()

yield() , sleep() >
< run()

stop()

Geburt

Tod

Für die Threaderzeugung bietet Java zwei Möglichkeiten:

Die Schnittstelle wendet man immer dann an, wenn der Thread in Verbindung mit einem bereits bestehenden Objekt steht. Ein solches Objekt kann z.B. ein Fenster, ein Dialog oder auch ein Applet sein. Bei Verwendung der Klasse Thread erzeugt man ein Objekt dieser Klasse, das den Thread als solchen darstellt. Dazu gleich ein Beispiel MB36 :

class EinThread extends Thread{

EinThread(String str){

super(str);

}

public void run(){

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

System.out.println(i+" "+getName());

try{

sleep(Math.round(10000.0*Math.random()));//schläft 1 ... 10s

}

catch(InterruptedException e){}

}System.out.println("Naechster Urlaub in "+getName());

System.exit(0);//Programmende

}// Ende run()

}

class ZweiThread{

public static void main(String args[]){

new EinThread("Jamaika").start();// start() ruft run()!!!

new EinThread("Mallorca").start();

}

}

Dieses Programm lost aus, wo der nächste Urlaub auf uns wartet. Bei einer nicht so prall gefüllten Urlaubskasse, sollte man statt Mallorca und Jamaika besser Röhrsdorf und Marienberg vormerken. Das Urlaubsziel ist der Name eines Threads, ein Objekt der Klasse EinThread, eine Subklasse von Thread. Diese Klasse definiert einen Konstruktor, um Objekte der Klasse zu erstellen. Dieser Konstruktor ruft einfach seinen großen Bruder der Superklasse und übergibt diesen als String seinen Namen. Die Klasse ZweiThread wird danach nur noch benötigt, um zwei Threadobjekte zu inkarnieren und sofort zu starten (new EinThread ("Röhrsdorf). start();). Dem Thread wird hier keine Variable zugewiesen. Später könnte man also nicht über einen Variablennamen auf den speziellen Thread zugreifen. Im Beispiel ist dies auch nicht notwendig.

Die Klasse EinThread definiert eine run()-Methode für beide Threads. Sofort nach dem start() ruft jeder Thread seine eigene run() auf. Hier geschieht nun folgendes:

Beide Threads nutzen also den gleichen Programmkode für ihre run()-Methode. Nur, da jeder Thread seine eigenen Schlafenszeiten nach dem Zufallsprinzip ermittelt, muß natürlich einer von beiden Threads zuerst seine 10 Schleifendurchläufe absolviert haben. Dieser Thread sagt uns dann im letzten Ausdruck wohin der Urlaub geht und danach beendet er das gesamte Programm. Im nebenstehenden Ausdruck kommt der Thread "Mallorca" zuerst ins Ziel. Bei jedem Programmstart ergibt sich natürlich immer ein neuer zufälliger Ablauf. Aber einer von beiden Threads wird sich immer zuerst beenden.

Zur Übung kommentiere man die Programmzeile "System.exit(0);" einmal aus. Nun kommt natürlich auch der zweite Thread zu seinem Ende und bringt den Ausdruck bezüglich des Urlaubszieles auf den Monitor. Beide run()-Methoden kommen zu ihrem Ende, die Threads sterben, und das Java-Programm wartet bis in alle Ewigkeit auf sein eigenes Ende. Da keine Ereignisbehandlung erfolgt, kann das Programm auch nicht beendet werden. So besteht nur die Mögkichkeit, den Java-Interpreter zu killen. Und genau das erledigt "System.exit(0);" für uns automatisch.

Die Threadsteuerung im Applet zeigt MB37:

public class Blinker extends Applet implements Runnable{

Thread t1;

Durch Implementieren der Schnittstelle Runnable werden zwei Dinge realisiert. Man erhält Zugriff auf die Methoden start(), stop() und run() zur Threadsteuerung, und man kann ein Thread-Objekt erstellen, ohne (wie im vorherigen Beispiel) eine Subklasse von Thread zu deklarieren. Eine Schnittstelle realisiert also auch indirekt das Prinzip der Mehrfachvererbung. Die Klasse Blinker kann somit ein solches Objekt t1 deklarieren, obwohl sie keine Subklasse von Thread ist.

public void start(){//für Applett

if (t1==null){

t1=new Thread(this, "Blinker-Thread");

t1.start();}//Thread erstellt und gestartet

}

public void run(){

while(t1!=null){

repaint();

try{t1.sleep(1000);}

catch(InterruptedException e){}

}

}

Die Methode start() gehört nicht dem Thread, sondern dem Applett. Hier wird zuerst geprüft, ob ein Thread-Objekt t1 schon vorhanden ist. Falls nicht (t1==null heißt, die Variable ist leer, das Objekt somit nicht vorhanden), muß es erstellt werden. Die Erstellung des Objekts geschieht mit dem Konstruktor Thread(Runnable target, String name) aus der Klasse Thread. Statt "Runnable target" steht im Beispiel "this". Mit this ist das Objekt der aktuellen Klasse als Ziel gemeint. Mit anderen Worten: Dem Thread wird das Applet als Zielobjekt übergeben. Nun kann mit t1.start() der Thread gestartet werden.

Die dem Start folgende run()-Methode ist eine Methode des Threads. Der Thread ruft nach seinem t1.start() selbständig seine run() auf und wird in ihr nur durch t1.sleep(1000) jede Sekunde im Lauf gestört. Die run()-Methode läuft nämlich in einer while-Schleife, die sleep alle Sekunde unterbricht und dadurch mittels repaint() eine Ellipse neu gezeichnet wird.

public void paint(Graphics g){

blink=!blink;

if (blink)

g.setColor(Color.red);

else g.setColor(Color.cyan);

g.fillArc(1,1,size().width-1, size().height,0,360);

}

Die Methode repaint() ruft paint(), und dort wird zuerst der Wert der booleanschen Variablen blink invertiert. So ist sichergestellt, daß die Ellipse bei jedem Neuzeichnen eine wechselnde Farbe erhält. Die Ellipse blinkt mit einer Frequenz von 1 Hz. Gezeichnet wird eine Ellipse die das gesamt Fenster ausfüllt. Ist das Fenster also zufällig quadratisch, so formt sich die Ellipse zum Kreis.

public void stop(){if (t1!=null)//Applet stopt

t1=null;}

public boolean mouseDown(Event e, int x, int y){

if (t1==null) {System.out.println("Arbeit");start();}

else {t1.stop();t1=null;System.out.println("Pause");}

return true;}}

Die stop()-Methode des Applet prüft, ob der Thread noch lebt. Wenn "ja", dann wird er gelöscht. Das Starten und Stoppen des Threads erledigt die nachfolgende mouseDown()-Methode. Nach jedem Drücken der Maustaste wird geprüft, ob der Thread noch lebt. Wenn also die Variable t1 ein Thread-Objekt enthält, somit nicht "null" ist, wird der else-Zweig der if-Anweisung während der Ereignisbehandlung gerufen und der Thread mit t1.stop() angehalten, danach gelöscht und schließlich erfolgt die Ausschrift "Pause" auf die Standardausgabe (nicht im Applet!!). Im anderen Falle, wenn beim Drücken der Maustaste der Thread gerade nicht existiert, wird die start()-Methode des Applets gerufen, die den Thread neu erstellt und das Applet in der schon beschriebenen Weise startet.

Das letzte Beispiel war recht kompliziert. Nach einer Pause sollte es nochmals in Ruhe und ohne Eile durchgearbeitet werden.

Eine gute Übung ist auch das Musterbeispiel MB38. Hier dürfte es eigentlich keine Verständnisschwierigkeiten mehr geben, falls das vorherige Beispiel alle Unklarheiten beseitigte. Aber das ist vielleicht nur ein frommer Wunsch des Autors ..... Trotzdem: Nicht verzagen, weitermachen!

zurückzurückzurück

17. I/O-Ströme

 

In Java erfolgt die gesamte Ein/Ausgabe (Input/Output) über Streams. Diese Vorgehensweise ist ein Erbe von UNIX. Dort erkannte man recht bald, daß die vielen Kommunikationsarten des Betriebssystems vereinheitlicht werden mußten. Streams sind Datenkanäle, die aus einer Quelle lesen und in eine Senke schreiben. Dabei muß ein Stream seine Quellen nicht im Detail kennen. Die Vorgänge sind vollkommen transparent. Dadurch können unterschiedliche I/O-Geräte und Prozesse Daten austauschen, sprich: kommunizieren.

Java stellt für I/O-Operationen das Package java.io bereit. Die beiden Basisklassen sind InputStream und OutputStream, Subklassen von Object.

Object

Inputstream   Outputstream
ByteArrayInputStream Bytefelder ByteArrayOutputStream
FileInputStream Datei FileOutputStream
FilterInputStream Bearbeiten FilterOutputStream
PipedInputStream Umlenken PipedOutputStream
SequenceInputStream Aneinander-hängen  
StringbufferInputStream Stringpuffer  
Lesen   Schreiben

Diese Basisklassen bilden wieder Subklassen zur unterschiedlichsten Aufbereitung von Strömen. So sind die Klassen FileInputStream und FileOutputStream für das Lesen aus und das Schreiben in Dateien zuständig.

Einige Standard-Streams stellt die Laufzeitumgebung ohne Zutun des Nutzers immer zur Verfügung. Es sind dies die Ströme

Diese Streams werden durch die Variablen in, out und err der Klasse System repräsentiert. Es sind Klassenvariablen, denen der jeweilige Stream zugewiesen ist. Das bedeutet natürlich auch, daß die zugewiesenen Ströme umgeleitet werden können. So wäre es z.B. sinnvoll, die Fehlerausgabe in eine Datei zu lenken, damit der wirkliche Ausgabestrom nicht von Fehlermeldungen unterbrochen wird.

 

Mit den bisherigen Erkenntnissen ist die geheimnisvolle Druckausgabe über

System.out.println("Hallo Welt!")

leicht zu erklären. Im java.io-Paket gibt es von FileOutputStream eine Subklasse namens PrintStream. Diese Klasse enthält die Methode println(). Und nun aufgepaßt: Die Variable out der Klasse System ist vom Typ PrintStream. Nach der Art "Objetname.Methodenname()" wird nun gedruckt. Wird die Klasse java.lang.System geladen, dann stehen die Klassenvariablen in, out und err zur Verfügung. Da nun eine Instanz der Klasse PrintStream existiert, kann auch auf die Methode println() aus der Klasse PrintStream zugegriffen werden, und der Druck auf die Standard-Ausgabe ist möglich.

Eine der häufigsten Anwendungen für Streams besteht im Lesen und Schreiben von Dateien. Es ist auch die historisch älteste Anwendung. Ehe Prozesse oder gar Computer und ganze Netzwerke miteinander kommunizierten, stand einsam die Datei. Darüber wurden alle Daten ausgetauscht. Auch heute ist das Verfahren noch wichtig. Wer einige der vielen UNIX-Konfigurationsdateien kennt, weiß wovon hier die Rede ist.

Das Beispiel MB40 zeigt, wie in Java Dateien behandelt werden. Es wird die einfache Aufgabe realisiert, die Datei Text1.txt in die Datei Text2.txt zu kopieren:

File inputFile=new File("Text1.txt");

File outFile=new File("Text2.txt");

Für solche Aufgaben stellt die Klasse java.io.File plattformunabhängige Definitionen einer Datei und auch von Verzeichnisnamen zur Verfügung. Der Konstruktor File(String path) erstellt ein Objekt dieser Klasse. Damit kann aber noch nicht eine Datei gelesen oder beschieben werden. Hierzu bedarf es noch Objekte der Klassen FileInputStream und FileOutputStream:

FileInputStream fis=new FileInputStream(inputFile);

// File wird in das Objekt eingelesen

FileOutputStream fos=new FileOutputStream(outFile);

// File Text2.txt wird in das Objekt geschrieben

In die Variable fis wird ein Strom aus dem File "Text1.txt" geschrieben. Nur, gelesen und geschrieben wird durch diese Definitionen noch nicht. Es stehen eigentlich nur die Objekte zur Bearbeitung bereit. Die Dateien sind geöffnet und warten darauf, daß etwas mit ihnen geschieht. Die eigentliche Arbeit beginnt also erst:

int c;

while ((c=fis.read()) != -1){//Lesen bis Fileende EOF=-1

fos.write(c);// File schreiben in Text2.txt

System.out.println("Schreibe in File Byte-Wert: "+c);

}

fis.close(); fos.close();//beide Files Schließen

Die Klasse FileInputStream stellt die Methode read() bereit, die byteweise aus einer Datei liest. Entsprechend umgekehrt funktioniert das in der Klasse FileOutputStream. Dort schreibt write() byteweise in eine Datei. Die Variable c nimmt das aktuelle Byte auf. Das alles geschieht in einer while-Schleife, die solange durchlaufen wird, bis das Dateiende erreicht ist. Die Variable fis enthält als Objekt die Datei Text1.txt und fos die Datei Text2.txt.

Abschließend müssen die beiden Streams geschlossen werden. In beiden Klassen gibt es dafür die Methode close(). Das ist wichtig! Zum file handle gehört immer das Öffnen und Schließen der Dateien. Vergißt man ein close(), kann es fatale Folgen haben. Die Datei könnte z.B. nicht mehr lesbar sein. In diesem Beispiel war ein open() nicht notwendig. Das erledigte Java mit dem Erstellen der Stream-Objekte selbstständig. Eine Datei ist geöffnet, nachdem der Stream einmal durch Angabe des Dateinamens im Konstruktor instanziiert wurde. Und genau das wurde mit Erstellen der Objekte fis und fos getan. Dieser letzte Satz ist nicht ganz korrekt, da fis und fos nur Variablen sind, die Objekte refenzieren. Es ist ein kleiner, feiner Unterschied, aber wichtig ist, das eigentliche Problem zu erkennen.

zurückzurückzurück

18. Fehlerbehandlung

 

Die Fehlerbehandlung ist ein Thema, das gern übersehen oder einfach "vergessen" wird. Was soll passieren, wenn das Programm am korrekten Weiterlaufen gehindert wird, wenn z.B. eine Datei, die geöffnet werden muß, nicht vorhanden ist? Solche und ähnliche Fehler gilt es zu erkennen und, was ebenso wichtig ist, zu behandeln. Die Fehlerbehandlung umfaßt also "irgendwelche" Methoden die zum Einsatz kommen, wenn ein unerwartetes Ereignis im Programm auftritt. Das unerwartete Ereignis muß vorher erkannt und entsprechend signalisiert werden. Fehler sind Ausnahmen, die entweder erwartet (caught) oder erzeugt (thrown) werden.

Ein Grundsatz von Java ist es, sichere Programme zu schaffen. Deshalb wurde in die Sprache ein Mechanismus integriert, der den Programmierer zwingt, jeden Fehler zu registrieren und zu behandeln, oder zumindest abzufangen und weiterzuleiten. Dies erreicht man mit einer Klasse, deren Objekte Fehler repräsentieren. Diese Klasse namens Throwable ist in java.lang als Basisklassen aller Fehler definiert. Von ihr werden die Klassen Error und Exception abgeleitet, die wiederum die Basisklassen für Fehlerobjekte aus verschiedenen Paketen sind. Die Klasse Error erzeugt schwere Fehler, die im Allgemeinen nicht behandelt werden sollten und somit zum Programmabbruch führen. Dagegen stellen Objekt der Klasse Exception Fehler dar, die durch das Programm behandelt werden sollten. Kann z.B. eine Datei nicht geöffnet werden weil sie im aktuellen Verzeichnis nicht vorhanden ist, so könnte interaktiv eingegriffen und ein Filedialog eröffnet werden, der das Suchen der fraglichen Datei gestattet. Die Sache hört sich als formale Beschreibung relativ einfach an, ist aber in der Praxis recht schwierig ausführbar. In Java wird das Problem in etwa so gelöst: Nachdem eine Methode im Programm eine Ausnahme auslöste, sucht das Laufzeitsystem jemanden, der die Ausnahme behandeln kann, den sogenannten Exception Handler. Das ist eine Methode, die die Ausnahme behandelt. Eine solche Methode fängt die Ausnahme dann zumindest ab. Im Idealfall wird sie danach behandelt und das Programm kann fortgesetzt werden. Findet das Laufzeitsystem keine passende Methode, dann terminiert es und das Programm ist damit (ev. nach Ausgabe einer Fehlermeldung) beendet.

Eine Fehlerbehandlung besteht (innerhalb einer Methode) aus drei Blöcken:

  • try {// Hier steht der Programmkode, in dem Exception auftreten können.}
  • catch {// Ein oder mehrere catch-Blöcke behandeln den jeweiligen Fehler}

catch (IOException e) {....}

catch (Mein Fehler e) {.....}

  • finally {// Dieser Block ist optional und wird immer ausgeführt. }

Jede Methode, die eine Exception liefern kann, muß von einem try {} - Block umschlossen werden. Geschieht dies nicht, so wird die Exception von der aufrufenden Methode weitergeleitet. Diese Weiterleitung erfolgt solange, bis eine Methode mit try {} - Block gefunden wurde, die solche Ausnahme behandeln kann. Eine Weiterleitung kann gegebenfalls bis zur main() - Methode erfolgen. Falls dort nichts geschieht, wird der Interpreter das Programm wohl beenden müssen.

Währen der try {} - Block Fehlermeldungen abfängt und festlegt, auf welche Fehler die nachfolgenden catch {} - Blöcke reagieren sollen, wird in den catch {} - Blöcken der eigentliche spezifische Fehler behandelt. Jeder Fehler braucht seinen catch {} - Block. Hierzu wird die Klasse des Fehlerobjektes festgelegt, das als Argument übergeben wird, und die Variable, welche eine Referenz auf dieses Objekt darstellt. So ist es möglich, verschiedene Ausnahmen gleichzeitig zu behandeln. Es ist in etwa die gleiche Verfahrensweise wie der Polymorphismus bei Methoden.

Der finally {} - Block wird immer ausgeführt, falls er definiert wurde. Er wird auch unabhängig davon, ob der try {} - Block von einen auftretenden Fehler abgebrochen wurde oder nicht, ausgeführt. Damit ist er hervorragend zur Ausführung aller Art von Aufräumarbeiten oder zur Freigabe von Ressourcen geeignet. Es könnte hier z.B. auch das Schließen von geöffneten Dateien erfolgen. Selbst im Fehlerfalle wird diese wichtige Arbeit noch ausgeführt.

Die folgenden Ausführungen sollen als eine Gedankenstütze gesehen werden. In einem Programm soll mittels einer Methode eine Datei gelesen werden. Schematisch könnte der Ablauf so sein:

liesDatei{

try {

"Versuche folgende Aktionen:"

Öffne Datei

Ermittle Dateigröße

Lese Dateien in eine Variable

........

........

}

catch (OpenFailedException e) {

"Tue etwas, wenn sich Datei nicht öffnen läßt!"}

catch (SizeDeterminationFailed e) {

"Tue etwas, falls sich die Dateigröße nicht ermitteln läßt!"}

catch (memoryAllocationFailed e) {

"Tue ewas, falls Speicheranforderung nicht gelingt!"}

catch (Ausnahme e) { .... "Tue ......." .......}

finally {

"Führe Aufräumarbeiten durch!"

"Schließe Datei."

}

}

Das Beispiel MB40 zeigt nun eine praktische Realisierung des eben gezeigten Vorganges:

import java.io.*;

/* Text1.txt ---> FileInputStream ---> FileOutputStream ---> Text2.txt*/

class FileStreamTest{

public static void main(String args[]){

try{ File inputFile=new File("Text1.txt");

File outFile=new File("Text2.txt");

FileInputStream fis=new FileInputStream(inputFile);

FileOutputStream fos=new FileOutputStream(outFile);

int c;

while ((c=fis.read()) != -1){//Lesen bis Fileende EOF=-1

fos.write(c);// File schreiben in Text2.txt

System.out.println("Schreibe in File Byte-Wert: "+c);

}

fis.close(); fos.close();//beide Files Schließen

} // Ende try - Block

//Ausnahmebehandlung

catch(FileNotFoundException e){System.out.println("Datei nicht gefunden");}

catch(IOException e){System.out.println("I/O-Fehler");}

} // Ende main()

} // Ende der Klasse

In der main() - Funktion liegt der try {} - Block. Innerhalb dieses Blockes wird alles erledigt, was zum Lesen und Schreiben der beiden Dateien notwendig ist. Am Ende dieses Blockes werden die beiden Dateien geschlossen. Das könnte man auch in einem finally {} - Block erledigen. Aber, wie dieses Beispiel zeigt, solch ein Block muß nicht vorhanden sein. Um aber eine sinnvolle Fehlerbehandlung auszuführen, muß mindestens ein catch {} - Block vorhanden sein. In diesem Beispiel sind es zwei Blöcke. Der erste Block überwacht das Vorhandensein der Input-Datei, und der zweite catch {} - Block behandelt einen ""allgemeinen" I/O- Fehler. Es könnte z.B. sein, daß "Text2.txt" nicht geschlossen oder die Datei nicht auf den Datenträger zurückgeschrieben werden werden kann, weil dieser keinen freien Platz mehr bietet. Solche Fehler führen zu "IOExceptions".

In beiden catch {} - Blöcken wird der Fehler eigentlich nicht behoben. Es erfolgt nur die Ausgabe einer Fehlermeldung auf die Standardausgabe. Mehr wird zur Fehlerbehebung nicht getan. Um den Fehler eventuell auch noch zu beseitigen, müßte im catch {} - Block eine Methode gerufen werden, die genau dies tut.

Die Funktion dieses Programmes kann man einfach probieren, in dem man die Datei "Text1.txt" nicht zur Verfügung stellt. Ebenso erhält man eine "FileNotFoundException", wenn versucht wird, die Datei "Text2.txt" auf ein nicht vorhandenes Laufwerk oder ein Diskettenlaufwerk ohne eingeschobene Diskette zu schreiben. Dazu muß dann das Fileobjekt in etwa so aussehen:

File outFile=new File("A:Text2.txt");

Befindet sich keine Diskette im Laufwerk A:, erfolgt jetzt die Fehlerausschrift "Datei nicht gefunden". Falls sich jedoch eine Diskette im Laufwerk befindet, diese aber keinen freien Speicherplatz mehr bietet, wird der andere catch {} - Block abgearbeitet, und folgerichtig erfolgt der Ausdruck "I/O-Fehler".

 

zurückzurückzurück

19. Netzzugriffe, URL´s

 

Da Java haupsächlich für das Internet entwickelt wurde, enthält es natürlich auch Klassen, die plattformübergreifende Netzoperationen ermöglichen. Das Thema ist viel zu umfassend, um auch nur einen Überblick an dieser Stelle zu geben. Eine auferlegte Beschränkung ist deshalb sinnvoll. Das Lesen aus, und das Schreiben in eine URL, soll deshalb das Thema sein. Für diese Zwecke stellt das Paket java.net u.a. drei Klassen bereit:

  • URL repräsentiert ein URL-Objekt
  • URLConnectiondefiniert die Netzverbindung
  • URLEncoderkonvertiert in die URL-codierte Form

 

Klasse URL

 

Eine vollständige URL (Uniform Resource Locator) besteht aus

Protokoll Trenner Hostadresse :Portnummer Pfad Dateiname #Anker
http :// java.sun.com :80 /Tutorial/ intro.htm #DOWNLOAD

Mit einer gültigen WWW - Adresse kann ein URL - Objekt erzeugt und dann der Inhalt der URL heruntergeladen werden. Das funktioniert mit im Hintergrund arbeitenden residenten "Content Handler". Es sind Funktionen, die ankommende Daten in eine Form umwandeln, welche ein Javaprogramm verarbeiten kann. Die Methode getContent() realisiert dies. Ebenfalls geben die Methoden getProtocol(), getHost(), getPort(), getFile() und getRef() die einzelnen Teile einer URL zurück.

Das Laden einer URL gelingt mit drei Methoden:

  • mit openConnection() wird ein URLConnection-Objekt erzeugt,
  • mit openStream() wird ein InputStream-Objekt der Klasse java.io.InputStream erzeugt,
  • mit getContent() wird ein Objekt der Klasse Object erzeugt, das den Inhalt (den HTML-Kode) der URL direkt enthält.

Klasse URLConnection

Sie definiert eine Netzverbindung zu einem URL-Objekt. Mit connect() wird diese Verbindung ausgeführt. Der Konstruktor URLConnection(URL url) gibt eine URLConnection-Instanz zurück. In der Klasse URL gibt die Methode openConnection() ebenfalls eine URLconnection -Instanz zurück. Solch ein Objekt braucht man immer, um mehr Kontrolle über das Herunterladen zu bekommen. Die einfacheren Methoden in URL gestatten dies nicht. So kann man mit getContentLength(), getContentType() oder getLastModified Informationen über das URL-Objekt anfordern.

Klasse URLEncoder

Diese Klasse definiert nur die Methode encode(String s). Sie wird benutzt, um einen String in seine URL-codierte Form zu bringen. Das braucht das HTTP-Protokoll, wenn vom Server z.B. eine Web-Seite angefordert wird, deren URL Leerzeichen im Dateinamen enthält. Dafür gibt es folgende Regeln:

  • Leerzeichen sind in "+" zu wandeln.
  • Nicht alphanumerische Zeichen außer dem Unterstrich "_" werden in ihrem Hexadezimalwert, gefolgt vom Prozentzeichen "%", gewandelt. Für ein Minuszeichen "-" steht also "2D%".

Damit wird ein Standard geschaffen, der alle Computer weltweit mit einem minimalen Auszug aus dem ASCII-Standard-Zeichensatz versorgt.

Das Beispiel MB42 liest die Startseite eines Web-Servers (Inernetverbindung vorausgesetzt!) und gibt diesen HTML-Kode auf die Standard-Ausgabe aus:

URL yahoo=new URL("http://www.yahoo.com/");

DataInputStream dis=new DataInputStream(yahoo.openStream());

String inputLine;

while((inputLine=dis.readLine())!=null){

System.out.println(inputLine);

}

dis.close();

 

Der Konstruktor DataInputStream(InputStream in) erzeugt ein Objekt seiner Klasse. Diese Klasse benutzt man um Daten einzulesen. Das dafür benötigte InputStream-Objekt erzeugt uns die Methode openStream() aus der Klasse URL. Das eigentliche URL-Objekt liefert der Konstruktor URL(String spec). Als Parameter wird ihm die URL der Suchmaschine Yahoo übergeben. Durch yahoo.openStream() wird die Startseite der Suchmaschine gelesen, und zwar in ein DataInputStream-Objekt namens dis. Dort wartet das aus dem Internet heruntergeladene Objekt auf eine weitere Verarbeitung. Im Beispiel soll die Ausgabe auf die Standardausgabe erfolgen. Dafür stellt die Klasse DataInputStream die Methode readLine() bereit. Sie liest solange Zeichen aus einem Stream, bis ein Newline-Zeichen oder Carriage Return oder beides (bei DOS) erscheint. Dieses Auslesen erfolgt in die Variable inputLine, die natürlich vom Typ String sein muß. Die ganze Prozedur erfolgt in einer while-Schleife. Abschließend darf nicht vergessen werden, mit dis.close() den Datenstrom zu schließen.

 

Das Beispiel MB43 schreibt in eine Url. Das heißt nichts anderes, als daß einem Server eine Nachricht zugestellt wird. Das funktioniert natürlich nur, wenn der Server entsprechend vorbereitet ist. Zu Übungszwecken installierte die Firma SUN auf ihrem Server verschiedene CGI-Scripte. Das Script "backwards" im Verzeichnis "cgi-bin" des Servers nimmt eine Zeichenkette einer beliebigen Client -Verbindung entgegen, kehrt sie um und sendet diese dem Client zurück. Sendet man z.B. "konsum" zum Script, so lautet die Antwort "musnok". Die Realisierung dieses Beispiels setzt natürlich eine intakte Internetverbindung voraus.

Die umzukehrende Zeichenkette bekommt das Programm beim Aufruf als Parameter mit:

C:> java zeichenkettenumkehr konsum

Dieser String muß standardgemäß mittels der Methode encode() in die URL-codierte Form gebracht werden:

String zkzurUmkehr=URLEncoder.encode(args[0]);

Die Variable zkzurUmkehr bekommt später der Server zugestellt.

Der weitere Ablauf läßt sich übersichtlich in 7 Schritten realisieren:

  • 1. Erzeugen des URL-Objektes

URL url=new URL("http://java.sun.com/cgi-bin/backwards");

 

  • 2. Öffnen der Verbindung zur URL

URLConnection connection=url.openConnection();

Gegenüber dem vorherigen Musterbeispiel wird mit der Methode openConnection() aus der Klasse URL eine Verbindung zum Server geöffnet. Jetzt ist zwar die Verbindung hergestellt, aber es gibt noch kein Objekt, in welches der Datenstrom fließen könnte.

 

  • 3. Erhalten eines Ausgabestromes vom Server. Dieser Strom ist mit dem Standard-Eingabestrom des CGI-Scriptes auf dem Server verbunden.

PrintStream outStream=new PrintStream(connection.getOutputStream());

Das Objekt outStream, welches den Datenstrom aufnehmen kann, wird jetzt erstellt. Hinter connection verbirgt sich die Variable für die geöffnete Verbindung. Die Methode getOutputStream() stammt aus URLConnection und gibt ein Objekt der Klasse OutputStream zurück. Der Konstruktor PrintStream(OutputStream out) setzt ein solches Objekt voraus. Ein Objekt vom Typ PrintStream wird zur Übergabe der Variablen für die Zeichenumkehr benötigt.

 

  • 4. In den Ausgabestrom schreiben

outStream.println("string= "+zkzurUmkehr);

Dabei ist "string" der Name einer Variablen des CGI-Scriptes. Dies ist mit dem Server vereinbart und man muß es wissen. Solche Zusammenhänge werden in der Beschreibung des Scriptes dargelegt. Die Methode println(String s) entstammt der Klasse PrintStream, die viele Methoden implementiert, um Texte in einfacher Art und Weise in Java auszugeben. Im Beispiel wird also "string= konsum" in den Ausgabestrom geschrieben, den der Server entgegennimmt.

 

  • 5. Ausgabestrom schließen

outStream.close();

Das Schreiben in die URL ist nun abgeschlossen. Der Ausgabestrom kann geschlossen werden. Jetzt gilt es, eine Antwort vom Server zu empfangen.

 

  • 6. Öffne einen Eingabestrom vom Server und schreibe diesen in die Variable inputLine.

DataInputStream inStream=new DataInputStream( connection.getInputStream());

String inputLine;

while ((inputLine=inStream.readLine()) != null) {System.out.println(inputLine);}

In der gleichen Art wie unter 3., wo ein Ausgabestrom zum Server installiert wurde, muß jetzt der Server mit einem Eingabestrom verknüpft werden. Wie schon im Beispiel MB42 nutzt man die Klasse DataInputStream. Die Methode getInputStream der Klasse URLConnection gibt ein Objekt vom Typ InputStream zurück, welches der Konstruktor DataInputStream(InputStream in) benötigt. Die Methode readLine() liest den Stream zeichenweise aus und übergibt ihn der Variablen inputLine. Das Ergebnis wird auf dem Bildschirm ausgegeben.

 

  • 7. Eingabestrom schließen

inStream.close();

Nach Schließen des Eingabestromes ist die Aufgabe erledigt.

In groben Zügen hier nochmals die Zusammenhänge:

Verbindung zum Server Daten zum Server Daten vom Server
symbolisiert die Variable connection = url.openConnection() Über die Variable outStream bekommt der Server die Zeichenkette übermittelt. Über die Variable inStream bekommt der Client die Zeichenkettenumkehr übermittelt.

zurückzurückzurück

20. Bilder und Sound

 

Für Bilder gibt es in Java die Klasse Image, welche die Grafikinformationen für die Bilder enthält. Diese Grafikinformationen können entweder aus einer Grafikdatei oder aus einem Bildschirmbereich stammen. Die eigentliche Darstellung des Bildes realisiert jedoch die Klasse Image nicht, sondern man muß die Klasse Graphics bemühen.

Laden von Bildern

 

Hier muß man zwei Fälle unterscheiden: Applet oder Anwendung. Beim Applet geschieht der Ladevorgang über eine URL. Eine Anwendung greift auf die Klasse Toolkit zurück.

Die Klasse Applet spezifiziert die Methoden getDocumentBase(), sie gibt die URL der HTML-Seite des Applets, und getCodeBase(), sie gibt die URL des Applets selbst zurück. Der eigentliche Ladevorgang geschieht mit getImage(URL url, String name). In "url" steht die Basisadresse, "name" meint dann den Namen der Grafikdatei inklusive eines relativen Pfades.

Ähnliches geschieht in der Klasse Toolkit. Der Methode getImage(String name) übergibt man den Namen mit einem vorhanden relativen Pfad einer Grafikdatei und man erhält eine Instanz der Klasse Image zurück. Ob Applet oder Anwendung, mit dem Laden von Bildern ist noch keine Anzeige verbunden. Hier wird nur erreicht, daß ein Bild für das Programm zur weiteren Verwendung bereit steht. Man kann es sich vorstellen wie das Herzuholen von Hammer und Nagel, um ein Bild an die Wand zu hängen.

 

Zeichnen von Bildern

 

Das Einschlagen des Nagels geschieht mit der Methode drawImage(Image ima, int x, int y, ImageObserver observer) der Klasse Graphics. Das im vorherigen Abschnitt erstellte Image-Objekt muß dieser Methode übergeben werden. Sinnvoll ist es, drawImage() in paint() aufzurufen.

Beim Laden eines Bildes müssen die Pixeldaten erst entsprechend aufbereitet sein, bevor das Bild auf dem Monitor gezeichnet werden kann. Nun kann es passieren, daß dieser Vorgang noch nicht abgeschlossen ist, wenn paint() das Bild darstellen soll. Aus diesem Grund enthalten alle drawImage()-Methoden einen Imageobserver, der benachrichtigt wird, wenn die Datei vollständig vorliegt. Es ist ein Interface, das Informationen über den Status beim Laden von Bildern liefert. Da es ein asynchroner Vorgang ist, können allerlei Probleme auftreten. Die Arbeit mit dem Imageobserver soll aber nicht Gegenstand dieses Kapitels sein, da die Zusammenhänge doch recht kompliziert sind. Man muß ihn nicht unbedingt programmtechnisch einbinden. Nur, falls ein zu ladendes Bild nicht vorhanden ist, wird es auch nicht geladen und man kann keine Gegenmaßnahmen einleiten.

Das Musterbeispiel zu diesem Thema ist in MB46 enthalten, was später erläutert wird.

Sound

Mit Audiodateien verhält es sich ähnlich wie mit Bilddateien. Um ein Image-Objekt zu erstellen, nimmt man die Methode getImage() aus der Klasse Applet (java.applet.Applet). Für eine Audiodatei gibt es dort die Methode getAudioClip(URL url) und getAudioClip(URL url, String name). Nun folgt das "Zeichnen", was beim Audioclip natürlich das Abspielen des Clips bedeutet. Dafür gibt es in Java das Interface "java.applet.AudioClip" im Paket java.applet. Dieses Interface beinhaltet nur die drei Methoden play(), stop() und loop() zum Starten, Stoppen und Endlosablaufen einer Audiodatei. Diese Methoden könnte man als Pendant zu paint(), repaint() bzw. update() zum Zeichnen der Bilder auffassen. Sie starten und stoppen den Sound. Zum Abspielen stellt die Klasse Applet noch komfortablere Methoden bereit:

play(URL url); play(URL url, String name);

Die Ähnlichkeit zu den Methode getImage()-Methoden mit gleichen Parametern ist auffallend. Dort werden Bilder und hier Sounddateien von einer URL geladen. Die Sounddateien werden auch sofort abgespielt, während das Zeichnen der Bilder noch ein repaint() erfordert.

Das Musterbeispiel MB48 zeigt die Einbindung von Audioclips in ein Javaprogramm. Es muß die Klasse "java.applet.AudioClip" importiert werden, damit entsprechende Objekte erstellt werden können:

import java.applet.AudioClip; //Audioclips werden eingebunden

AudioClip piep, gong, musik; // 3 Audioobjekte

Nach der Objektdeklarierung können die Clips in eine Variable geladen werden:

piep=getAudioClip(getCodeBase(), "audio/beep.au");

gong=getAudioClip(getCodeBase(), "audio/gong.au");

musik=getAudioClip(getCodeBase(), "audio/loop.au");

Jetzt ist eigentlich alles für das Abspielen der Sounddateien vorbereitet. Zum gegebenen Moment, d.h. an der richtigen Stelle im Javaprogramm, kann mittels play() oder loop() das Abspielen gestartet und mit stop(), entsprechend gestoppt werden. Im Beispiel wird mit musik.loop() der Soundclip mit einer kurzen Musik in eine Endlosschleife gestellt. Nach musik.stop() tritt wieder Stille ein.

Falls eine Sounddatei nicht vorhanden ist, so wird sie einfach nicht geladen und der Sound bleibt aus. Dagegen verschwindet eine einmal geladene und mit loop() aktivierte Sounddatei , die z.B. als Hintergrundmusik läuft, nicht automatisch wieder, falls das Applet gestoppt wird (z.B. weil im Browser eine neue Seite geladen wird). Den Sound muß man immer expliziet mit der Methode stop() anhalten.

zurückzurückzurück

21. Parameterübergabe

 

Bei der Parameterübergabe bekommt die Anwendung oder das Applet von "außen" Daten übermittelt, die es während der Laufzeit verarbeitet. Wie dies bei einer selbständigen Anwendung funktioniert, zeigt MB4:

class zeichenanzahlabfrage{

public static void main(String args[]){

String p1,p2;

p1=args[0]; // 1. Übergebener Parameter

p2=args[1]; // 2. Übergebener Parameter

if (args.length !=2)

System.out.println("Falsche Parameteranzahl! "+p1);

else

System.out.println("Eingabe richtig! "+p1+" "+p2);

}

}

Von außen ist eine Datenübergabe nur für Daten vom Typ String möglich. Sollen z.B. Integerwerte empfangen werden, so müssen diese im Programm konvertiert werden. Das Array args[] in der main()-Funktion realisiert die Übergabe. Auf die einzelnen übergebenen Parameter greift man über den Index der Array-Komponenten zu. Da für jedes Array die Instanzvariable length angelegt wird, kann man mit args.length die Anzahl der übergebenen Parameter erfragen. Im Beispiel wird geprüft, ob genau 2 Parameter übergeben wurden. Ruft man das Programm wie folgt auf,

java zeichenanzahlabfrage ix otto

bekommt man den Ausdruck "Eingabe richtig! ix otto", weil genau 2 Parameter der Anwendung übergeben wurden.

Bei der Parameterübergabe ist zu beachten, daß Strings, die Leerzeichen enthalten, in Anführungszeichen zu setzen sind. Also übergibt

"Max Mueller" nur einen Parameter, aber

Max Mueller dagegen 2 Parameter.

 

Bei einem Applet erfogt die Parameterübergabe durch den Browser. Es gibt das HTML-Tag

<PARAM NAME=Parametername VALUE=Parameterwert>

Jeder Parameter besitzt sein eigenes Tag:

<PARAM NAME=text1 VALUE="Ich komme vom Applet!">

<PARAM NAME=text2 VALUE="Ich auch!">

Die Klasse java.applet.Applet stellt die Methode getParameter(String name) bereit. Ihr übergibt man den Namen des Parametertags, also z.B. getParameter("text1"), und die Methode liefert als String den Wert des Parameters, hier somit "Ich komme vom Applet!", zurück.

Das Beispiel MB44 zeigt das in der Praxis:

public class Textparameter extends Applet{

String s;

public void init(){

s=getParameter("text");

}

public void paint(Graphics g){

g.drawString(s,10,10);

}

}

<html><head>

<title>Textparameter</title>

</head>

<body>

<applet code=Textparameter width=200 height=200>

<PARAM NAME=text VALUE="Ich komme vom Applet!">

</applet>

<hr>

</body></html>

In die Variable s schreibt die Methode getParameter() den Inhalt von "text", so wie er in der HTML-Seite definiert wurde. Da getParameter() einen String zurückgibt, müssen andere Datentypen im Programm entsprechend aufbereitetet werden. Bei einer Anwendung, wo Parameter über ein Array übergeben werden, ist das ebenso.

zurückzurückzurück

22. Pakete

Verwandte Klassen werden in Java in Paketen zusammengefaßt. Solche Klassenbibliotheken bildet man, um bei der Vielzahl der Klassen den Überblick zu wahren. Da die Klassen größtenteils voneinander abgeleitetet sind, ist es auch sinnvoll, diese nach einem Schema zusammenzufassen. Man spricht in Java von Packages. Möchte man Klassen eines bestimmten Paketes in seinem Programm verwenden, so muß man diese mit der import - Anweisung in das Programm integrieren. Eine Ausnahme macht java.lang. Dieses Paket wird immer ohne Anweisung importiert.

Für die Namensbildung eines Paketes gilt folgendes:

de.ronnet. java.uebung. mb. MyClass

de.ronnet. java.uebung mb. MyClass
Hier steht der umgekehrte Domainename. Im Beispiel: "de.Firmenname". Diese wahlweise Unterteilung könnte die Firma vorgeben. Dieses Feld dürfte z.B. der Autor vergeben: mb steht für Musterbeispiele Hier steht der Klassenname

Die oberste Hierarchie im Paketname sollte der Firmenname sein. Das Standardpaket java macht eine Ausnahme. Man geht davon aus, daß jeder dies Paket in seine Anwendung integriert, ist doch die Klasse java.lang unabdinglich in jedem Programm notwendig.

Der Name für ein Paket ist natürlich beliebig. Es wird jedoch in der Literatur vorgeschlagen, den Paketnamen mit einem Kleinbuchstaben einzuleiten, im Unterschied zum Klassennamen, der ja mit einem Großbuchstaben beginnen sollte.

Die Paketstruktur ist ähnlich einer Verzeichnisstruktur angelegt. Befindet sich z.B. die Javastandard-Bibliothek im Verzeichnis "Lib", so ist die Klassendatei "Applet.class" zu finden unter:

../Lib/java/applet/Applet.class

Das obige Beispielpaket würde sich demgemäß wie folgt in diese Struktur einordnen:

../Lib/de/ronnet/java/uebung/mb/MyClass.class

Eigene Pakete definiert man so:

package paketname;

Diese Anweisung muß als erstes Kommando in der Datei stehen.

Importiert wird solch ein Paket in der gewohnten Art:

import paketname.KlassenName; //oder

import paketname.*; //das gesamte Paket

Das Beispiel MB44 definiert ein eigenes Paket:

package meinpaket;

public class Pers{

String Name,Vorname;

int Personalnummer;

public Pers(String Name, String Vorname, int Personalnummer){

this.Name=Name;this.Vorname=Vorname;this.Personalnummer=Personalnummer;

}

public void drucke(){

System.out.println("Name:"+Name);

System.out.println("Vorname:"+Vorname);

System.out.println("Personalnummer:"+Personalnummer);

}

}

Die Klasse namens Pers stellt nur einen Konstruktor und eine Methode zum Ausdruck der Objekte zur Verfügung. Nach dem Compilieren entsteht die Datei Pers.class. Damit das Paket nun auch von anderen Programmen genutzt werden kann, sind einige Vorbereitungen notwendig. In dem Verzeichnis, in dem sich die Javastandard-Bibliothek befindet, muß ein neues Unterverzeichnis mit dem Namen des Paketes angelegt werden. In dieses Verzeichnis wird Pers.class kopiert. Befindet sich z.B. die Klassenbiblothek in ../Lib/, dann muß das Unterverzeichnis "meinpaket" angelegt werden. Die Verzeichnisstruktur sieht dann wie folgt aus:

../Lib/meinpaket/Pers.class

Das Paket ist so in die Verzeichnishierarchie eingeordnet.

Möchte man die Methode drucke() dieses Paketes in einem Programm verwenden, so muß daß Paket "meinpaket" importiert werden:

 

import meinpaket.Pers;

public class Person{

public static void main(String args[]){

Pers P1=new Pers("Meier","Hans",10);

Pers P2=new Pers("Mueller","Peter",20);

Pers P3=new Pers("Otto","Dieter",30);

P1.drucke();P2.drucke();P3.drucke();

}

}

Durch den Import der Klasse "meinpaket.Pers" können Objekte der Klasse Pers gebildet und ebenfalls die Methode drucke() dieser Klasse genutzt werden.

zurückzurückzurück

23. Ein umfassendes Beispiel MB42

Das folgende Beispiel MB42 zeigt eine kleine Animation, die mittels 9 Bildern Schritt für Schritt erstellt wird. Zum besseren Verständnis schaue man sich das lauffähige Applet mehrmals an, um ein Gefühl für den Ablauf und die Reihenfolge der Einblendung von Bildern zu erhalten. Immer dann, wenn sich im Applet etwas bewegt, muß ein neues Bild in die Zeichenfläche eingeblendet werden. Das Applet läuft wie das beliebte Daumenkino aus der Kindheit ab. Den Daumen benötigen wir nicht mehr, dies erledigt Java für uns.

Das Kätzchen Neko hat im Applet einiges zu tun. Zuerst rennt es von links nach rechts in die Bildschirmmitte:

right1.gif und das Bild right2.gif sorgen für Bewegung im Applet. Für den zeitlichen Ablauf, die beiden Laufbilder müssen ja wechselseitig gegeneinander ausgetauscht werden, sorgt die Methode nekorun(). Danach bleibt Neko in Bildschirmmitte sitzen. stop.gif sorgt für diese Platzierung. Da Neko ruhig sitzt, müssen keine Bilder gewechselt werden. Java muß aber wissen, wie lange Neko sitzen soll. Das alles erledigt die Methode pause(). Als Parameter bekommt sie die Pausenzeit übergeben. Die Methode pause() soll universell einsetzbar sein. Sie wird in diesem "Theater" nach jedem "Akt", den Neko im Applet aufführt, benötigt. Gleiches gilt für die Methode nekorun() im einleitenden Teil von Nekos Theater. Im Schlußakt wird sie nochmals benötigt. Nämlich dann, wenn Neko aus der Bildschirmmitte nach rechts laufend die Bühne verläßt. Aber das folgt später. Jetzt jedoch, nach einer Sitzpause, möchte Neko sich kratzen. Das tun die Mitarbeiter der Firma Ronnet öfters auch, wenn einer der weltweiten Server zum Stillstand kommt. Neko benötigt zum Kratzen scratch1.gif und scratch2.gif. Da wieder zwischen zwei Bildern in einem definierten zeitlichen Ablauf gewechselt werden muß, wird die Methode nekokratz() benötigt. Ganz im Gegensatz zu den Ronnet-Mitarbeitern, legt sich Neko nach dem Kratzen erst einmal schlafen. Dafür haben wir ihm die Methode nekosleep() erstellt, in der die Bilder sleep1.gif und slepp2.gif als Schlafkulissen verwendet werden. Man muß die letzten beiden Bilder schon recht genau betrachten, um den Unterschied der einzelnen Schlafbewegungen zu erkennen. Die Laufbilder unterscheiden sich deutlicher. - Nach dem Schlafen erwacht Neko im Bild awake.gif, ist sofort hellwach und strahlt über das ganze Gesicht. So strahlt auch der Mitarbeiter von Ronnet, der inzwischen den ausgefallenen Server, von dem wir vielleicht gerade unser fertiges Neko-Applet laden möchten, zu neuen Leben erweckte. Er hat sich jetzt ein kurzes Schläfchen verdient, während Neko nun wieder laufen muß. Aber, das Laufen lernte er inzwischen. Ein erneuter Ruf der Methode nekorun() veranlaßt Neko, den Bildschirm zu verlassen. Das Neko-Spiel ist nun aus. Der Betrachter unseres Applets säße nun vor einem leeren Bildschirm. Das wollen wir unseren Kunden nicht zumuten, und plazieren unserer Kätzchen in seiner strahlenden Pose in die Bildschirmmitte. Das Bild awake.gif muß also nochmals geladen werden. Damit wäre die Animation beendet.

Mit diesen Schilderungen über die Aktivitäten von Neko wäre gleichzeitigt auch der Programmablauf für das Applet festgelegt. Sinnvoll ist an dieser Stelle eine kleine Skizze, die den Ablauf der Animation grob beschreibt, anzufertigen. Sie sollte die einzelnen Schritte symbolisch auflisten und den Ablauf in der gewünschten Reihenfolge darstellen.

Applet erstellen

Thread erstellen

nekorun() nekokratz() nekosleep() pause() paint()

Methoden erstellen

In der run() - Methode des Threads den Ablauf der Animation organisieren:

rennen nekorun()

pause()

sitzen

pause()

kratzen nekokratz()

pause()

schlafen nekosleep()

pause()

rennen nekorun()

Abschlußbild

Mit dieser Skizze ist eigentlich die meiste Arbeit schon getan. Jetzt kommt es darauf an, den Ablauf in Java umzusetzen. Je nachdem wie gut und sicher man die Sprache berherrscht, wird es schnell gehen, oder es braucht einige Tage bis der compilierte Kode fehlerfrei im Browser läuft.

Zuerst wird die Klasse für Neko erstellt. Wie gewohnt, muß diese eine Subklasse der Klasse Applet sein und, da ein Thread laufen soll, die Schnittstelle Runnable implementieren.

public class Neko extends java.applet.Applet

implements Runnable {}

Nun müssen die Variablen definiert werden:

Image nekobild[] = new Image[9]; // Array mit 9 Plätzen

Image currentbild; // aktuelles Bild

Thread runner;

int xpos; // Anfangspos. x -->

int ypos = 50; // Anfangspos. y |

Die einzelnen Bilder lassen sich am besten in einem Array handhaben. Über den Index des Arrays werden die einzelnen Bilder selektiert. Für solche Zwecke gibt es im AWT die Klasse Image. Ein Objekt dieser Klasse repräsentiert ein anzeigbares Bild in einer plattformunabhängigen Form. Ein Image-Objekt kann man nicht einfach direkt, wie bei anderen Objekten gewohnt, mit dem Konstruktor erstellen und sofort zur Anzeige bringen. Das liegt in der Tatsache begründet, das ein Bild ja irgendwo als Datei auf einem Datenträger liegt und von dort in das Programm geladen werden muß. Dies erledigt die Methode getImage(URL url, String name) aus der Klasse Applet. Diese Methode liefert ein Image-Objekt zurück, das auf der url "liegt", und dort mit "name" benannt ist. Das Array besteht nun aus solchen Image-Objekten, die während der Initialisierung des Applets geladen werden müssen.

Ein Bild, nämlich das "currentbild", ist währen der Programmausführung immer das aktuelle Bild, das gerade zur Zeit auf dem Bildschirm sichtbar ist. Um es zur gegebenen Zeit anzusprechen, muß es in einer Variablen stehen. Im Beispiel nutzen wir einfach ein Image-Objekt, das zum aktuellen Zeitpunkt mit dem richtigen Bild bestückt wird.

Abschließend müssen noch der Thread und die Koordinaten für die Anfangspositinen der Bilder im Applet benannt werden.

Jetzt kann das Applet initialisiert werden. In einer for - Schleife sollen die einzelnen Bilder geladen werden:

for (int i=0 ; i < nekobild.length ; i++ ){
// Arry initial.

nekobild[i] = getImage(getCodeBase(),
"images/"+bildname[i]);}

In jeden der 9 Plätze des Arrays wird ein Bild geladen. Die Methode getCodeBase() stammt aus der Klasse Applet. Sie gibt die URL-Adresse des Applet zurück, im Gegensatz zu getDocumentBase(), welches die Adresse der HTML-Seite liefert. Im gleichen Verzeichnis muß sich hier im Beispiel das Unterverzeichnis "images" befinden, denn von dort werden die Bilder mit den Namen bildname[i] geladen. Da die Methode getImage(URL url, String name) für den Bildnamen einen String benötigt, wurde ein Array mit Namen "bildname" mit 9 Plätzen vom Typ String angelegt:

String bildname[] = { "right1.gif", "right2.gif",

"stop.gif", "yawn.gif",

"scratch1.gif", "scratch2.gif",

"sleep1.gif", "sleep2.gif",

"awake.gif" };}

Jetzt sind die Informationen zum Laden der Bilder vollständig. Damit dies nun alles wunschgemäß funktioniert, müssen sich die HTML-Datei, die das Applet startet, und die Class-Datei Neko.class im gleichen Verzeichnis befinden. Dort muß auch ein Unterverzeichnis mit Namen "images" existieren, daß die einzelnen Gif-Dateien für das Kätzchen Neko enthält. Wenn dem so ist, kann das Applet gestartet werden:

public void start(){ // Beginn start()vom Applet

if (runner == null){

runner = new Thread(this);

runner.start();//Hier startet Thread

}

} // Ende start()

Jetzt wird das Thread-Objekt namens runner instanziiert und sofort gestartet. Das heißt, die run() - Methode vom Thread wird abgearbeitet. Die start() - Methode ist so aufgebaut, daß immer ein Thread neu instanziiert wird, falls das Objekt noch nicht lebt. Dies tut die if -Anweisung in der start() - Methode. Ganz ähnlich ist die stop() - Methode für das Applet aufgebaut, nur daß hier das Thread-Objekt gelöscht wird, falls es noch lebt.

Die run()-Methode des Applet führt die Animation aus. Die anfangs erstellte Programmskizze (oder auch der Programmablaufplan PAP genannt) legte schon alle Details fest. Es gilt nur noch die einzelnen Methoden aufzurufen, entsprechende Parameter festzulegen und die Pausenzeiten zwischen den Methoden eventuell durch Probieren zu ermitteln:

public void run(){ // Beginn run(): Thread im Applet arbeitet

setBackground(Color.white); // Hintergrund weiß

nekorun( 0 , this.size().width / 2, 0, 1);// Neko rennt herein

pause(200);

currentbild = nekobild[2]; // Neko bleibt stehen

repaint();

pause(1000);

nekokratz(10); // Neko kratzt sich

pause(200);

nekosleep(6); // Neko schläft

currentbild = nekobild[8]; // Neko erwacht

repaint();

pause(1000);

nekorun(xpos, size().width+10, 0, 1);// Neko rennt heraus

xpos = this.size().width/2; // Neko bleibt sitzen

currentbild = nekobild[8];

repaint();

} // Ende Run()

Mit setBackground(Color c) wird ein weißer Hintergrund gesetzt. Da die einzelnen Bilder von Neko ebenfalls auf einem weißen Hintergrund liegen, würde ein grauer Applethintergrund, so wie er oft standardmäßig im Browser eingestellt wurde, störend wirken. Die Klasse Component des AWT stellt die Methode bereit. Das Color-Objekt Color.withe ist in der Klasse Color als Konstante definiert.

In der run()-Methode wäre noch ein Punkt zu kommentieren: Nach dem Laden eines einzelnen Bildes (Beispiel: currentbild=nekobild[2]) muß man immer repaint() rufen. Im Programm sind es nämlich zwei verschiedene Operationen. Zum einen wird das aktuelle Bild in das zuständige Objekt geladen, und zum anderen muß diese Bild gezeichnet werden. Die Methoden nekorun(), nekokratz() und nekosleep() rufen repaint() selbständig. Deshalb muß in run() explizit kein Aufruf erfolgen.

Schauen wir uns nun die einzelnen "Arbeitsmethoden" an. Vier Integerwerte bekommt nekorun() übergeben: Start- und Stop-Position für Neko, und die Bildnummern der zwei benötigten Bilder. Man lädt das aktuelle Bild durch direkten Zugriff auf das Array:

currentbild=nekobild[n]

Die beiden Werte für n sind somit die übergebenen Parameter "bild0" und "bild1".

Der Bildwechsel für den rennenden Neko erfolgt in einer for-Schleife. Man prüft welches Bild geladen ist, und tauscht es danach gegen das zweite, zur Zeit ungeladene, aus:

if (currentbild == nekobild[bild0])

currentbild = nekobild[bild1];

else if (currentbild == nekobild[bild1])

currentbild = nekobild[bild0];

else currentbild = nekobild[bild0];

Nun ist augenfällig, daß die letzte else-Anweisung entfallen könnte. Nekobild[0] muß im vorherigen Konstrukt sicher geladen werden. Richtig!, falls eines von beiden Bildern schon einmal geladen wurde. Aber, wie auch sonst im Leben, beim ersten Mal ist alles anders. Hier ist das currentbild leer und das Programm gelangt bis zur letzten else-Anweisung.

Bei jedem Schleifendurchlauf wird ein neuer Wert für die Position des einzufügenden Bildes ermittelt. Diese Schrittweite ist intuitiv festzulegen. Dagegen ist der Beginn und das Ende des Neko-Laufes genau definiert. Läuft Neko in das Bild, bekommt er als Start "0" (linker Bildschirmrand), läuft er heraus, dann beginnt der Lauf an der gerade aktuellen Position xpos. Das Ende ist im ersten Fall in der Bildmitte (this.size().width/2) und im zweiten Fall 10 Pixel außerhalb des Appletts (this.size().width+10). Size() ist eine Methode aus Component. Sie liefert ein Objekt der Klasse Dimension zurück. Diese Klasse definiert zwei Instanzvariablen, height und width, die die Höhe und Breite des Objektes zurückliefern. Damit ist das Rätsel gelöst, warum Neko mindestens bis zur Bildmitte laufen muß. Da die Schrittweite seines Laufes nicht immer ganzzahlig durch die halbierte Bildweite teilbar sein muß, kann er maximal 7 Pixel übers Ziel schießen, bis die for-Schleife Neko zum Stehen bringt. - Und nicht vergessen: Nach jedem Schleifendurchlauf braucht Neko ein repaint() zum Neuzeichnen und das Auge des Betrachters eine pause(), um das Bild zu registrieren. 150 ms Pausenzeit entsprechen etwa 7 Bilder pro Sekunde, fürs Kino zu wenig, für eine Web-Animation wohl ausreichend.

Die Methoden zum Kratzen und Schlafen sind einfacher zu erstellen, weil hier die Bildposition nicht variabel ist. Neko sitzt beim Schlafen und Kratzen. Beide Methoden laden abwechselnd zwei Bilder mit unterschiedlichen Pausenzeiten zwischen den Bildern.

void nekokratz(int xmal){

for (int i = xmal ; i > 0 ; i--){

currentbild = nekobild[4];

repaint();

pause(100);

currentbild = nekobild[5];

repaint();

pause(250)

}}

void nekosleep(int xmal){

for (int i = xmal ; i > 0 ; i--){

currentbild = nekobild[6];

repaint();

pause(250);

currentbild = nekobild[7];

repaint();

pause(650);

}}

Wie man sieht ist es gar kein Problem, diese beiden Methoden zu vereinigen:

nekoschlaf_kratz(int xmal, int bild0, int bild1, int pause0, int pause1){}

Dies wäre eine gute Übungsaufgabe. In diesem Beispiel sollen aber zwei getrennte Methoden für Neko bereitstehen. Und diese Methoden sind so einfach, daß es eigentlich keiner weiteren Erläuterung bedarf. Mit den Pausenzeiten muß man eventuell experimentieren. Und: repaint() nicht vergessen!!!

Die Pausenmethode ist ebenfalls recht einfach. Genutzt wird die Methode sleep(long millisec) der Klasse Thread. Die Schlafzeitübergabe erfolgt in Millisekunden als long -Zahl. Die Methode erzeugt die Ausnahme "InterruptedException". Deshalb ist ein "try" - und "catch" - Block integriert.

Abschließend muß noch die Methode paint() überschrieben werden.

public void paint(Graphics g){ // aktuelles Bild zeichnen

g.drawString("Bildbreite="+this.size().width+" Höhe="

+this.size().height+

" Grafikkontext="+toString(),0,10);

g.drawImage(currentbild,xpos,ypos,this); // aktuelles Bild

}

Mit drawString(String str, int x, int y) aus der Klasse Graphics werden einige Informationen zur Grafik ausgegeben. Das Zeichnen des aktuellen Bildes für die Animation erledigt
drawImage(Image im, int x, int y, ImageObserver observer), ebenfalls aus der Klasse Image. In paint() wird mittels "this" dem ImageObserver ein Objekt übergeben. Der Observer ist eine Schnittstelle, die den Ladeverlauf des Bildes verfolgt und bei Bedarf darüber Auskunft gibt. Im Beispiel wird die Schnittstelle nicht genutzt. DrawImage() lädt immer das Bild, das sich aktuell in "currentbild" befindet, an die aktuelle Position xpos und ypos. Ist ein Bild nicht vorhanden, wird es in der Animation nicht geladen. Da der Ladevorgang durch keinen Observer überwacht wird, erfolgt auch keine Fehlermeldung oder eine andere Reaktion zur Abhilfe des Problems.

Für ein Bild im Array bildname[], nämlich yawn.gif, fand sich bisher noch keine Verwendung. Mit ihm ließe sich Neko zum Gähnen nach dem Erwachen anregen. Dies als Übungaufgabe zu realisieren, sollte jetzt kein Problem mehr sein. Im Beispiel MB47 ist es realisiert. Dort erledigt Neko das Kratzen, Schlafen und Gähnen mit einer universellen Methode. Je nach nach gewünschter Tätigkeit werden dieser die entsprechenden Parameter übergeben.

zurückzurückzurück


Copyright © 1999 by Rainer Tippmann
Fon/FAX: ( 0371) 24 25 26 , Am Harthwald 18, 09123 Chemnitz