Kurs Objektorientierte Programmierung

Werbung
Kurs
OOP
Objektorientierte
Programmierung
© 2009
Prof. Dr. Hans-Peter Hutter
Zürcher Hochschule für Angewandte Wissenschaften
Mitglied der Fachhochschule Zürich
Version 2.7
Inhaltsverzeichnis
1 Objekte und Klassen ........................................................................................................... 1
1.1 Ziele .......................................................................................................................... 1
1.2 Objekte ...................................................................................................................... 1
1.3 Kapselung und Geheimnisprinzip (encapsulation, information hiding) ................... 2
1.4 Klassen ...................................................................................................................... 3
1.5 Deklaration einer Klasse ........................................................................................... 4
1.6 Klassen und Dateien ................................................................................................. 8
1.7 Erzeugung eines Objektes (Instanzierung) ............................................................... 8
1.8 Interaktion mit Objekten........................................................................................... 9
1.9 Umgang mit Objekten ............................................................................................ 13
1.10 Löschen von Objekten ............................................................................................ 14
1.11 Objekte einfacher Datentypen ................................................................................ 15
1.12 Überladen von Methoden ....................................................................................... 15
1.13 Klassenvariablen und Klassenmethoden ................................................................ 16
2 Aufzählungstypen ............................................................................................................. 19
2.1 Ziele ........................................................................................................................ 19
2.2 Einführung .............................................................................................................. 19
2.3 Enumerations .......................................................................................................... 19
3 Die Benutzerschnittstelle (User Interface) .................................................................. 22
3.1 Ziele ........................................................................................................................ 22
3.2 Einführung .............................................................................................................. 22
3.3 Modell, View und Controller .................................................................................. 24
3.4 Der Controller ......................................................................................................... 27
3.5 Das Modell ............................................................................................................. 28
3.6 Die View (Ansicht) ................................................................................................. 28
3.7 Repetition Sichtbarkeitsbereiche ............................................................................ 30
4 Vererbung ......................................................................................................................... 31
4.1 Ziele ........................................................................................................................ 31
4.2 Einführung .............................................................................................................. 31
4.3 Beispiel Ballon ....................................................................................................... 31
4.4 Vererbung in Java ................................................................................................... 34
4.5 Sichtbarkeitsbereiche .............................................................................................. 35
4.6 Klassendiagramm (class diagram) .......................................................................... 36
4.7 Überschriebene Methoden ...................................................................................... 39
4.8 Überschriebene Attribute ........................................................................................ 40
4.9 Konstruktoren ......................................................................................................... 40
4.10 Schlüsselwort final ........................................................................................... 41
4.11 Erlaubte Operationen auf Objekten ........................................................................ 42
4.12 Erweitern von Bibliotheksklassen .......................................................................... 42
4.13 Polymorphismus ..................................................................................................... 42
4.14 Wiederverwendung von Software .......................................................................... 45
5 Erzeugung Grafischer Benützeroberflächen (GUI) .......................................................... 46
5.1 Ziele ........................................................................................................................ 46
5.2 Einführung .............................................................................................................. 46
5.3 AWT und Swing ..................................................................................................... 46
5.4 Klasse Component ............................................................................................... 48
5.5 Checkbox (Schalter) ............................................................................................... 48
5.6 Checkbox-Group (Schaltergruppe) ......................................................................... 49
5.7 Choice-Box (Drop-Down-Auswahlliste) ................................................................ 49
5.8 Liste ........................................................................................................................ 50
5.9 Klasse TextComponent ..................................................................................... 51
5.10 Textfeld (TextArea) ................................................................................................ 52
5.11 Canvas (Leinwand) .............................................................................................. 52
ii
Inhaltsverzeichnis
6
7
8
9
10
5.12 Klasse Container ............................................................................................... 53
5.13 Panel (Schalttafel) ................................................................................................... 53
5.14 Layout-Managers .................................................................................................... 54
5.15 Design von Benützer-Interfaces ............................................................................. 55
Exceptionhandling (Behandlung von Ausnahmefällen) ................................................... 56
6.1 Ziele ........................................................................................................................ 56
6.2 Einführung .............................................................................................................. 56
6.3 Ausnahmebehandlung in Java ................................................................................ 57
6.4 Exceptions .............................................................................................................. 60
6.5 Beispiele für Ausnahmebehandlungen ................................................................... 67
Eigenständige Programme ................................................................................................ 68
7.1 Ziele ........................................................................................................................ 68
7.2 Einführung .............................................................................................................. 68
7.3 Applikationen in Java ............................................................................................. 69
7.4 Applikationen mit eigener graphischer Benützeroberfläche (GUI) ....................... 71
7.5 Kochbuchrezept für die Umwandlung Applets zu Applikationen .......................... 75
7.6 Menus ..................................................................................................................... 75
Arbeiten mit Dateien ......................................................................................................... 80
8.1 Ziele ........................................................................................................................ 80
8.2 Einführung .............................................................................................................. 80
8.3 Zugriff auf Dateien ................................................................................................. 80
8.4 Sequentieller Filezugriff: Streams .......................................................................... 81
8.4.1 Direkter (byte-weiser) sequentieller Zugriff auf eine Datei ............................. 81
8.4.2 Gepufferter (blockweiser) Zugriff auf Datei .................................................... 82
8.5 Java IO-Klassen ...................................................................................................... 83
8.5.1 Erklärungen zu den einzelnen Klassen für das Lesen / Schreiben von Daten: 83
8.5.2 Lesen und Schreiben von Textdateien ............................................................. 84
8.6 Ausgabe auf ein File ............................................................................................... 86
8.7 Einlesen von einem File ......................................................................................... 88
8.8 File durchsuchen ..................................................................................................... 91
8.9 Filenamen aussuchen .............................................................................................. 94
8.10 Umgang mit Dateien ............................................................................................... 96
8.11 Konsolen-IO ........................................................................................................... 97
8.12 System-Klasse ...................................................................................................... 97
8.13 Konsolenbasierte Anwendungen ............................................................................ 98
8.14 Lesen von anderen Sites ....................................................................................... 101
8.15 Persistente Objekten ............................................................................................. 103
Programmierstil ............................................................................................................... 107
9.1 Ziele ...................................................................................................................... 107
9.2 Einführung ............................................................................................................ 107
9.3 Programmierstil .................................................................................................... 107
9.4 Programmierrichtlinien ......................................................................................... 107
9.4.1 Allgemeine Programmierrichtlinien ............................................................... 107
9.4.2 Namensgebung ............................................................................................... 108
9.4.3 Konstanten ...................................................................................................... 109
9.4.4 Kommentare ................................................................................................... 109
9.5 Problemfälle .......................................................................................................... 110
9.6 Dokumentation ..................................................................................................... 111
9.7 Mein eigener Programmierstil .............................................................................. 123
Programmpakete ............................................................................................................. 125
10.1 Ziele ...................................................................................................................... 125
10.2 Packages ............................................................................................................... 125
iii
Inhaltsverzeichnis
11
12
13
14
10.3 Importieren von Klassen und Packages ................................................................ 126
10.4 Sichtbarkeitsregeln für Packages .......................................................................... 126
Fehlersuche (Debugging) und Test ................................................................................. 129
11.1 Ziele ...................................................................................................................... 129
11.2 Debugging ............................................................................................................ 129
11.2.1 Einführung ...................................................................................................... 129
11.2.2 Fehlerarten ...................................................................................................... 129
11.2.3 Vorgehen beim Debugging ............................................................................ 131
11.2.4 Verwendung eines Debuggers ........................................................................ 133
11.3 Testen .................................................................................................................... 134
11.3.1 Ziel ................................................................................................................. 134
11.3.2 Programmspezifikation .................................................................................. 134
11.3.3 Techniken ....................................................................................................... 135
11.3.4 Black-Box-Test (Funktionaler Test) .............................................................. 135
11.3.5 White-Box-Test (Strukturtest) ........................................................................ 139
11.3.6 Inspektion und Walkthrough .......................................................................... 141
11.3.7 Formale Verifikation ...................................................................................... 141
11.3.8 Testen von Einheiten und Systemintegration ................................................. 142
11.3.9 Inkrementelle Entwicklung ............................................................................ 143
11.3.10 Schlussbemerkung .......................................................................................... 143
Abstrakte Klassen und Interfaces .................................................................................... 144
12.1 Ziele ...................................................................................................................... 144
12.2 Abstrakte Klassen ................................................................................................. 144
12.3 Interface (Schnittstelle) ......................................................................................... 147
12.4 Mehrfachvererbung und Interfaces ....................................................................... 149
12.5 Interfaces vs. abstrakte Klassen ............................................................................ 150
12.6 Adapterklassen ...................................................................................................... 150
Threads ............................................................................................................................ 151
13.1 Ziele ...................................................................................................................... 151
13.2 Einführung ............................................................................................................ 151
13.3 Begriffe ................................................................................................................. 151
13.4 Beispiel: Springender Ball .................................................................................... 153
13.4.1 Version 1: ....................................................................................................... 153
13.4.2 Version 2: Bouncer2 156
13.5 Nützliche Methoden für Threads .......................................................................... 160
13.6 Zustand eines Threads .......................................................................................... 161
13.7 Scheduling ............................................................................................................ 161
13.8 Interagierende Threads ......................................................................................... 161
13.9 Gegenseitiger Ausschluss (mutual exclusion) ...................................................... 165
13.10 Das Produzent-Konsument-Problem .................................................................... 166
13.10.1 Fall 1 : Produzent-Konsument-Problem ohne Warteschlange ..................... 166
13.10.2 Fall 2: Produzent-Konsument-Problem mit Warteschlange.......................... 175
13.11 Unterbrüche .......................................................................................................... 184
13.12 Deadlock ............................................................................................................... 185
Java-Übersicht ................................................................................................................. 187
14.1 Einfache Datentypen ............................................................................................. 187
14.2 Operatoren ............................................................................................................ 188
14.3 Variablen .............................................................................................................. 189
14.4 Kommentare ......................................................................................................... 189
14.5 Methoden .............................................................................................................. 190
14.6 Klassen und Objekte ............................................................................................. 190
14.7 Sichtbarkeitsbereiche ............................................................................................ 191
14.8 Weitere Modifikatoren ......................................................................................... 192
iv
Inhaltsverzeichnis
14.9 Reservierte Wörter ................................................................................................ 192
14.9.1 Reservierte Methodennamen .......................................................................... 192
14.10 Anweisungen ........................................................................................................ 193
14.11 Auswahlanweisungen ........................................................................................... 193
14.12 Schleifen ............................................................................................................... 193
14.13 Arrays ................................................................................................................... 194
14.14 Strings ................................................................................................................... 194
14.15 Exception .............................................................................................................. 196
14.16 Eigenständige Anwendungen ............................................................................... 196
14.17 Vererbung ............................................................................................................. 196
14.18 Threads ................................................................................................................. 197
14.19 Interface ................................................................................................................ 197
14.20 Abstrakte Klassen ................................................................................................. 198
14.21 Packages ............................................................................................................... 198
14.22 Enumeration (seit Java 5) ..................................................................................... 198
14.23 Java-Packages ....................................................................................................... 199
v
1
Objekte und Klassen
1.1
Ziele
•
•
•
•
•
•
•
Sie können eine bestehende Klasse richtig interpretieren und anwenden.
Sie können eine eigene Klasse deklarieren, die eine gegebene Spezifikation erfüllt.
Sie setzen Konstruktoren zur Initialisierung von Objekten richtig ein.
Sie können das Prinzip der Kapselung an Beispielen erklären.
Sie können mit Objekten richtig umgehen.
Sie können die Unterschiede zwischen Instanz- und Klassenvariablen erklären.
Sie können das Überladen von Methoden anhand von Beispielen erklären.
1.2
Objekte
• Rekapitulation
• OO-Programme bestehen aus einer Menge von Objekten, die miteinander kommunizieren.
• Objekte = Daten und die dazugehörigen Methoden.
• Daten (Attribute):
• Sie bestimmen die Eigenschaften und den Zustand des Objekts.
• Methoden (Operationen):
• Sie bestimmen das Verhalten des Objekts (was kann man damit machen)
• Sie erlauben auch den Zugriff auf die internen Daten des Objekts.
• Beispiel: Ballon-Objekt
Figur 1: Beispiel eines Ballon-Objektes
• Daten: x-, y-Koordinate, Durchmesser des Ballons
• Methoden:
• Für die Änderung des Durchmessers
• Für die Darstellung des Ballons: Gemäss OOP-Richtlinien ist jedes Objekt selbst dafür
verantwortlich, sich darzustellen.
1.3
Kapselung und Geheimnisprinzip (encapsulation, information hiding)
• Mit Objekten bzw. Klassen können zwei wichtige Konzepte bei der Programmierung realisiert
werden:
• Kapselung:
• Das Prinzip der Kapselung besagt, dass Daten und die dazugehörigen Methoden in einer
Einheit zusammengefasst werden sollen.
• Das Prinzip der Kapselung wird durch die Objekte in objektorientierten Programmen
umgesetzt, da diese Daten und die zugehörigen Methoden zusammenfassen.
• Geheimnisprinzip:
• Das Geheimnisprinzip besagt, dass diese Einheit eine klare Schnittstelle nach aussen
definieren soll.
• Sodann soll möglichst alle Information, die nicht für die Verwendung dieser Einheit nötig ist,
für den Benützer (andere Programmierer) versteckt werden.
• Schnittstelle bei Objekten:
• public-Methoden
• Die public-Methoden bilden die Schnittstelle der Objekte.
• Sie ermöglichen den Zugang zu den internen Daten des Objekts.
• public-Variablen
• sollten möglichst vermieden werden!
• Die Schnittstelle von Objekten sollte möglichst nicht mehr geändert werden, wenn sie einmal
festgelegt und publiziert ist. Jede Änderung an der Schnittstelle erfordert normalerweise
Änderungen in den Programmen, die diese Objekte benützen.
• Verstecken:
• Die Daten des Objekts sollten versteckt werden. Der Zugriff auf sie soll nur über die
entsprechenden Methoden erlaubt sein. So hat das Objekt jederzeit die Kontrolle über seine
Daten. als private deklarieren!
• Objektinterne Methoden, die für die Benützung des Objektes nicht nötig sind, sollten auch vor
dem Benützer versteckt werden. als private deklarieren!
• Die Implementierung der Methoden sind ebenfalls für den Benützer der Objekte normalerweise
unwichtig und sind deshalb ebenfalls zu verstecken.
• Vorteile:
• Durch die Kapselung stehen bei der Wiederverwendung neben den Daten gerade auch die
dazugehörigen Methoden zur Verfügung.
• Durch den kontrollierten Zugriff auf die Daten via die Methoden bleiben die Daten der Objekte
konsistent.
• Die Implementation einer Methode eines Objektes kann jederzeit geändert werden, ohne dass der
Benützer informiert werden muss.
• Dadurch wird die Abhängigkeit der Benützung eines Objektes von seiner Implementation
(seinem Innenleben) möglichst gering gehalten.
OOP_V2.7.fm
2
• Beispiel: Objekt meinBallon
Innenansicht:
private int diameter;
private int xCoord, yCoord;
public tuDasNicht;
public void changeSize(int change){
int x, y;
Anweisungen;
}
public void display(Graphics g){
...
}
private void flaeche(float d){
...
}
Sicht von aussen (für den Benützer des Objektes):
public tuDasNicht;
public void changeSize(int);
public void display(Graphics);
Figur 2: Innen- und Aussenansicht eines Objektes
1.4
Klassen
• Rekapitulation
• Wir können Objekte nicht direkt programmieren, sondern nur die Klassen, aus denen zur
Laufzeit dann Objekte erzeugt werden.
• Die Klasse ist der Bauplan für die Objekte, die daraus erzeugt werden. Sie wird deshalb auch als
Objektfabrik bezeichnet.
• Mit ihr können beliebig viele (im Verhalten) identische Objekte erzeugt werden.
• Ein Objekt wird auch als Instanz oder Exemplar der Klasse bezeichnet.
• Jedes objektorientierte Programm besteht aus mindestens einer Klasse. Bei grösseren Programmen
können es hunderte oder gar tausende von Klassen sein.
• Die folgenden Abschnitte befassen sich eingehend mit der Entwicklung eigener Klassen.
OOP_V2.7.fm
3
1.5
Deklaration einer Klasse
• Beispiel: Es soll eine Klasse für Ballonobjekte entwickelt werden:
• Bauplan:
(xCoord, yCoord)
diameter
• Klassendeklaration:
class Balloon {
private int diameter;
private int xCoord, yCoord;
public void changeSize(int change){
diameter = diameter + change;
}
public void display(Graphics g){
g.drawOval(xCoord, yCoord, diameter, diameter);
}
public Balloon(int initialDiameter, int initialX,
int initialY){
diameter = initialDiameter;
xCoord = initialX;
yCoord = initialY;
}
}
Programm 1 : Klasse Balloon
• Klassenname: Balloon
• Attribute der Ballon-Objekte
• Die Attribute eines Ballon-Objektes sind der Durchmesser und die Position des Ballons. Die
Klasse enthält dazu die Instanzvariablen int diameter, xCoord, yCoord.
• Die Attribute müssen als Instanzvariablen, d.h. ausserhalb der Methoden deklariert werden.
OOP_V2.7.fm
4
Damit sind sie in jeder Methode der Klasse sichtbar und behalten ihren Wert, solange das
Objekt existiert.
• Die Attribute müssen private deklariert werden:
– Sie sollen ausserhalb der Klasse (des Objekts) nicht sichtbar sein.
– Der Zugriff auf die Attribute soll nur via die Methoden erlaubt werden.
• Verhalten der Ballon-Objekte (was kann man damit machen):
• In unserem Beispiel soll die Grösse der Ballons geändert werden können. Zudem soll der
Ballon sich selber darstellen.
• Dazu werden in der Klasse Balloon die public -Methoden changeSize und display
deklariert.
• Frage: Welchen Zustand hat das Objekt nach der Erzeugung?
• Initialisierung:
• Merken Sie sich deshalb: Alle Variablen explizit initialisieren!
• Für Instanzvariablen gibt es dafür zwei Möglichkeiten
– Direkt bei Deklaration: Das Problem dabei ist, dass alle Objekte mit denselben
Attributwerten initialisiert werden.
– In einer speziellen Methode für die Initialisierung von Objekten: Konstruktor
• Konstruktor
– Ist eine spezielle Methode, die automatisch aufgerufen wird, wenn ein Objekt erzeugt
wird.
– Sie hat den gleichen Namen wie die Klasse.
– Mit einem Konstruktor können die Initialwerte der Instanzvariablen für jedes erzeugte
Objekt verschieden gewählt werden.
• Allgemeiner Aufbau einer Klasse:
class Klassenname {
Instanzvariablendeklaration
Konstruktorendeklaration
Methodendeklaration
}
• Klassenname:
• Klassennamen gross beginnen
• Klassennamen sollten immer Substantive im Singular sein, eventuell durch ein Adjektiv
ergänzt.
• Eine Klasse stellt einen neuen benützerdefinierten Datentyp dar. Im Gegensatz zu den
einfachen Datentypen int, float, etc. ist eine Klasse ein Referenzdatentyp
• Instanzvariablen
• Die Instanzvariablen stellen die Attribute der Objekte dar.
OOP_V2.7.fm
5
• Sie bestimmen den Zustand und die Eigenschaften des jeweiligen Objektes.
• Sie sind in allen Methoden des Objekts sichtbar.
• Um den Zugriff von ausserhalb des Objekts zu verhindern, muss der Zugriffsmodifikator
private sein.
• Achtung: Instanzvariablen nicht als public deklarieren!!
– gefährlich, da die Instanzvariablen dann von überall her veränderbar sind.
– sehr schlechter Programmierstil.
• Methoden:
• public
– Die public-Methoden bestimmen das Verhalten des Objektes.
– Sie sind die Schnittstelle des Objektes nach aussen.
• private
– private-Methoden sind nur für den internen Gebrauch gedacht.
– Sie sollen deshalb nur innerhalb der Klasse sichtbar sein.
• Konstruktor (Constructor)
• Ein Konstruktor ist eine spezielle Methode einer Klasse und hat folgenden allgemeinen
Aufbau:
Zugriffsmodifikator Klassenname (Parameterliste){
Konstruktorkörper
}
Programm 2 : Deklaration eines Konstruktors
•
•
•
•
•
Der Konstruktorkörper entspricht dem Methodenkörper bei Methoden.
Der Konstruktor wird automatisch aufgerufen, wenn ein Objekt erzeugt wird.
Der Konstruktor muss den gleichen Namen haben wie die Klasse.
Er hat keinen Resultattyp.
Er hat dementsprechend auch keine return-Anweisung. Es wird aber trotzdem etwas
zurückgegeben, nämlich ein Objekt der Klasse Klassenname.
• Ein Konstruktor hat eine Parameterliste wie normale Methoden.
• Der Zugriffsmodifikator ist normalerweise public.
• Falls der Programmierer selber keinen Konstruktor deklariert, fügt Java automatisch einen
Default-Konstruktor ohne Parameter in die Klasse ein. Dieser Konstruktor ist im Quellcode
zwar nicht sichtbar, aber trotzdem vorhanden. Das genaue Aussehen dieses Konstruktors wird
in Abschnitt 4.9 behandelt.
OOP_V2.7.fm
6
• Lernaufgabe:
Schreiben Sie eine Klasse Kachel, wobei eine Kachel durch ein Rechteck gegeben ist. Ihre Lösung
sollte Folgendes enthalten:
• Attribute der Kachel: Grösse, Position und Farbe.
• Für jede Eigenschaft eine Methode, um diese zu verändern.
• Einen Konstruktor, um die Farbe der Kachel wunschgemäss zu initialisieren.
OOP_V2.7.fm
7
1.6
Klassen und Dateien
• Normalerweise wird jede Klasse in ein separates File mit dem gleichen Namen wie die Klasse
abgelegt. Dabei ist zu beachten:
• Jede Klasse muss public sein, da sie sonst nicht verwendet werden kann.
• Vor jeder Klasse müssen die nötigen import-Statements stehen.
• Werden mehrere Klassen in das gleiche File gespeichert, muss Folgendes beachtet werden:
• Genau eine (normalerweise die erste) Klasse muss public sein.
• Alle übrigen Klassen dürfen nicht public sein.
• Die import-Statements für alle Klassen müssen am Anfang des Files stehen.
• In jedem Fall erzeugt der Compiler ein .class-File pro Klasse, das den erzeugten Bytecode der
Klasse enthält.
1.7
Erzeugung eines Objektes (Instanzierung)
• Beispiel Ballon-Objekt
Anhand einer Instanz der Klasse Balloon soll rekapituliert werden, was der Reihe nach bei der
Erzeugung eines Objektes abläuft:
• Variable deklarieren:
Balloon myBalloon = null;
•
•
•
•
Balloon ist eine Klasse. Diese definiert einen Referenz-Datentyp.
Es wird nur eine Variable vom Typ Balloon deklariert.
Das Objekt selbst besteht noch nicht.
Pro memoria: Objektnamen klein beginnen
• Objekt erzeugen und initialisieren:
myBalloon = new Balloon(20,50,50);
• Erst mit dieser Anweisung wird ein Objekt anhand der Klasse Balloon erzeugt.
• D.h., es wird Speicherplatz für die Instanzvariablen (dynamisch) alloziert und die Methoden
des Objektes werden bereitgestellt.
• Es können beliebig viele Objekte aus derselben Klasse erzeugt werden:
Balloon secondBalloon = new Balloon(40,60,80);
• Dieses Objekt hat die gleichen Methoden und Instanzvariablen wie meinBallon, aber
verschiedene Werte der Instanzvariablen.
OOP_V2.7.fm
8
1.8
Interaktion mit Objekten
Beispiel: Programm PlayBalloon
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class PlayBalloon extends Applet implements ActionListener {
private Button grow, shrink;
private Balloon myBalloon;
public void init() {
grow = new Button ("Grow");
add (grow);
grow.addActionListener(this);
shrink = new Button ("Shrink");
add (shrink);
shrink.addActionListener(this);
myBalloon = new Balloon (20, 50, 50);
}
public void actionPerformed(ActionEvent event) {
if (event.getSource() == grow){
myBalloon.changeSize(10);
}
if (event.getSource() == shrink){
myBalloon.changeSize(-10);
}
repaint();
}
public void paint (Graphics g) {
myBalloon.display(g);
}
Programm 3 : Klasse PlayBalloon
OOP_V2.7.fm
9
class Balloon {
private int diameter;
private int xCoord, yCoord;
public void changeSize(int change){
diameter = diameter + change;
}
public void display(Graphics g){
g.drawOval(xCoord, yCoord, diameter, diameter);
}
public Balloon(int initialDiameter, int initialX, int initialY){
diameter = initialDiameter;
xCoord = initialX;
yCoord = initialY;
}
}
Programm 4 : Klasse Balloon
OOP_V2.7.fm
10
• Übersicht:
• Das Programm PlayBalloon besteht aus den zwei Klassen PlayBalloon (Seite 9)
und Balloon (Seite 10).
• Aus diesen Klassen werden zur Laufzeit zwei Objekte erzeugt:
Figur 3: Objekte im Programm PlayBalloon
OOP_V2.7.fm
11
• Ablauf des Programms PlayBalloon:
• Der Browser erzeugt ein Objekt der Klasse PlayBalloon, indem er den Default-Konstuktor
aufruft, z.B.:
PlayBalloon appObj = new PlayBalloon();
• Der Browser ruft die Methode init() des Applet-Objekts appObj auf:
• Dort werden die Buttons grow und shrink erzeugt.
• Dann wird das Balloon-Objekt myBalloon erzeugt.
• actionPerformed(ActionEvent e) wird jedesmal aufgerufen, wenn einer der zwei
Buttons gedrückt wird:
• Dort werden die Buttonereignisse behandelt.
• Die Methode event.getSource() gibt das Objekt zurück, das das ActionEvent
erzeugt hat.
• paint() wird jedesmal aufgerufen, wenn das Applet neu gezeichnet wird:
• Sie ruft die display-Methode vom myBalloon-Objekt auf
• Lernaufgabe:
Schreiben Sie ein Applet, das folgende zwei Kacheln zeichnet:
• Position (50, 100), Breite 30, Höhe 50, Farbe rot.
• Position (70, 40), Breite 40, Höhe 20, Farbe blau.
OOP_V2.7.fm
12
1.9
Umgang mit Objekten
• Da Objekte Referenzdatentypen sind, ergeben sich beim Umgang mit Objekten einige wichtige
Unterschiede im Vergleich zu normalen Variablen, die hier nochmals wiederholt werden sollen:
1. Zuweisung
Balloon yourBalloon = null;
yourBalloon = myBalloon;
Es wird nur die Referenz kopiert, nicht das Objekt selbst.
myBalloon und yourBalloon zeigen deshalb auf das gleiche Objekt!
Für das Kopieren von Objekten muss man spezielle Methoden aufrufen.
2. Methodenaufruf/Returnwert
yourBalloon = methodeA(myBalloon);
private Balloon methodeA(Balloon b){
b.changeSize(-10);
//
verändert Ballon myBalloon!
return b;
}
Bei Objekten wird nur die Referenz übergeben. (Zwar wird auch hier eine
Kopie des Arguments an die Methode übergeben, aber das ist hier eben nur eine
Referenz auf ein Objekt und nicht das Objekt selbst)
OOP_V2.7.fm
13
• Beispiel: Es soll eine Methode geschrieben werden, die Buttons aufsetzt.
private Button installButton(String label, ActionListener actL){
Button but = new Button(label);
add(but);
but.addActionListener(actL);
return but;
}
Aufruf:
Button up;
up = installButton("Up", this);
3. Mögliche Operationen mit Objekten (was kann man damit machen)
• Objekte kann man erzeugen, zuweisen und als Argument übergeben.
• Die normalen Operatoren (*, /, + , –, >, <, &&, e,tc.) funktionieren nicht mit
Objekten!
• Die einzigen Operatoren, die auch für Objekte gültig sind, sind == und !=. Diese haben
aber nicht die gleiche Bedeutung wie bei einfachen Datentypen:
if ( myBalloon == yourBalloon){...}
– vergleicht die Referenzen (Adressen der Objekte) der Objekte nicht die Objekte selbst.
Dementsprechend ist die obige Bedingung nur wahr, falls myBalloon und
yourBalloon auf das gleiche Objekt zeigen.
– Um die Daten zweier Objekte zu vergleichen, müssen diese Objekte eine entsprechende
Methode zur Verfügung stellen, z.B. equals() bei String-Objekten.
• Operator instanceof:
Für Objekte ist der zusätzliche Operator instanceof definiert. Er gibt an, ob ein Objekt zu
einer bestimmten Klasse gehört oder nicht, z.B:
if ( event.getSource() instanceof TextField ) {...}
• Methoden:
Neben diesen grundlegenden Operationen, die für alle Objekte definiert sind, bestimmen die
Methoden der einzelnen Objekte, was man sonst noch alles mit ihnen machen kann. Sie
bestimmen das eigentliche Verhalten des Objektes.
1.10
Löschen von Objekten
• Im Gegensatz zu einfachen Datentypen, die nur wenige Bytes im Arbeitsspeicher belegen, können
Objekte unter Umständen sehr viel Speicherplatz belegen.
• Wenn der Programmierer alle Referenzen auf ein Objekt, das er erzeugt hat, löscht, dann kann er
nicht mehr auf das Objekt zugreifen. Es belegt aber immer noch den Arbeitsspeicher, bis das
Programm beendet ist.
OOP_V2.7.fm
14
• Falls Objekte nicht mehr gebraucht werden, sollte der belegte Speicherplatz möglichst rasch wieder
freigegeben werden.
• Eine Möglichkeit besteht darin, dass der Programmierer im Programm selbst den belegten Speicher
wieder zurückgibt. Diese Methode ist aber sehr mühsam und fehleranfällig, muss doch der
Programmierer genau Buch führen, welche Objekte er erzeugt hat.
• Garbage Collection
• Im Gegensatz zu vielen anderen Sprachen, muss sich der Java-Programmierer nicht um die
Rückgabe des Speicherplatzes von Objekten kümmern.
• Die sogenannte Gargabe-Collection, ein Prozess, der im Hintergrund läuft, entfernt automatisch
Objekte aus dem Speicher, sobald keine Referenzen mehr auf sie zeigen.
1.11
Objekte einfacher Datentypen
• Einfache Datentypen
• Variablen einfacher Datentypen sind keine Objekte.
• Variablen einfacher Datentypen (boolean, int, float, double, etc.) sind nach der
Deklaration sofort verwendbar, sie müssen nicht erzeugt werden.
• Zudem sind verschiedenste Operationen auf diesen Variablen vordefiniert (+, –, *, / etc.).
• Wrapper-Klassen:
• Manchmal möchte man diese Variablen wie Objekte behandeln: Man möchte z.B. int-Zahlen
sortieren, hat aber nur eine Sortiermethode für Objekte zur Verfügung.
• Deshalb wurden sogenannte Wrapper-Klassen für die einfachen Datentypen definiert:
Boolean, Integer, Float, Double
• Variablen einfacher Datentypen können in Objekte der entsprechenden Wrapper-Klassen
umgewandelt werden und umgekehrt.
• Diese Klassen definieren zudem verschiedene nützliche Methoden und Konstanten (Konversion,
Parsing, Min-, Maxwerte)
• Autoboxing (ab Java 5)
Ab Java 5 werden einfache Datentypen (int, float, ...) automatisch in die entsprechenden
Wrapper-Objekte umgewandelt, wenn nötig. Umgekehrt werden Wrapper-Objekte automatisch
in die entsprechenden einfachen Datentypen umgewandelt, wenn das nötig ist.
1.12
Überladen von Methoden
• Eine Klasse kann mehrere Methoden mit demselben Namen haben.
• Man nennt solche Methoden überladene Methoden.
• Voraussetzung dafür ist, dass die Methoden eine unterschiedliche Parameterliste, damit sie der
Compiler trotzdem unterscheiden kann.
• Beispiel einer überladenen Methode:
public void moveRight(int distance){
xCoord = xCoord + distance;
}
public void moveRight(){
xCoord = xCoord + 20;
}
OOP_V2.7.fm
15
moveRight(10);
//
Methode 1
moveRight();
//
Methode 2
• Häufig wird das Überladen von Methoden bei Konstruktoren gebraucht, da dort der
Konstruktorname vom Klassennamen vorgegeben ist.
Beispiel:
• public Balloon(int initDiam, int initX, int initY){...}
• public Balloon(int initX, int initY){...}
1.13
Klassenvariablen und Klassenmethoden
• Neben den normalen Instanzvariablen und Methoden, die einem einzelnen Objekt einer Klasse
gehören, gibt es die Möglichkeit, Variablen und Methoden zu deklarieren, die zur Klasse selbst
gehören.
• Dadurch ergeben sich verschiedene Unterschiede im Verhalten, wie Figur 4 zeigt.
Objekte der Klasse Balloon
Klasse Balloon
ball1
class Balloon{
private int diameter;
private int xCoord, yCoord;
private static float gravity = 9.8f;
private int diameter;
private int xCoord, yCoord;
public void changeSize(int change){
diameter = diameter + change;
}
public void changeSize(int change){
diameter = diameter + change;
}
public static void setGravity(float g){
gravity = g;
}
public Balloon(){
...
}
ball2
private int diameter;
private int xCoord, yCoord;
public void changeSize(int change){
diameter = diameter + change;
}
Aufruf:
Aufruf:
Figur 4: Klassenvariablen und -methoden
• Instanzvariablen/Instanzmethoden
• gehören zum jeweiligen Objekt
• Der Aufruf einer Instanzmethode oder der Zugriff auf eine Instanzvariable erfolgt via den
OOP_V2.7.fm
16
Objektnamen:
Bsp.: ball1.changeSize(7);
• Wenn der Wert einer Instanzvariablen in einem Objekt geändert wird, so ändert der Wert
derselben Instanzvariablen in einem zweiten Objekt nicht.
• Klassenvariablen
• Sind durch das Schlüsselwort static gekennzeichnet.
• Sie werden auch statische Variablen genannt.
• Sie gehören zur Klasse, nicht zu den Objekten.
• Es gibt nur eine Kopie pro Klassenvariable, egal wie viele Objekte aus der Klasse erzeugt
werden.
• Alle Objekte verwenden dieselbe Klassenvariable:
Die Änderung dieser Variablen wirkt sich in allen Objekten aus.
• Klassenvariablen entsprechen globalen Variablen in anderen Sprachen.
• Der Zugriff auf Klassenvariablen geschieht über den Klassennamen:
Klassenname.Variablenname
Beispiel: Balloon.gravity
(geht hier nur innerhalb der Klasse, weil gravity als private deklariert ist)
• Es braucht kein Objekt, um auf Klassenvariablen zuzugreifen.
• Klassenvariablen werden z.B. verwendet für:
• Konstanten: Math.PI, Scrollbar.HORIZONTAL,
• Zählen der Anzahl erzeugter Objekte
• Klassenmethoden
• sind ebenfalls durch das Schlüsselwort static gekennzeichnet.
• Sie werden auch statische Methoden genannt.
• Sie gehören ebenfalls zur Klasse, nicht zu den Objekten.
• Der Aufruf einer Klassenmethode geschieht über den Klassennamen:
Klassenname.Methodenname(...);
Beispiele:
Balloon.setGravity(9.81f);
Math.sin(3.14);
• Es braucht kein Objekt, um diese Methoden aufzurufen.
• Diese Methoden können ihrerseits keine Instanzmethoden aufrufen oder Instanzvariablen
benützen.
• this ist in diesen Methoden nicht definiert.
• Beispiele für Klassenmethoden:
Alle mathematischen Methoden (Math.sin, Math.cos, etc.)
OOP_V2.7.fm
17
• Aufgabe:
Ändern Sie die Klasse Kachel wie folgt:
• Alle Kachelobjekte sollen neu dieselbe Farbe haben.
• Schreiben Sie einen zusätzlichen Konstruktor, der eine quadratische Kachel einer bestimmten
Grösse an einer bestimmten Position erzeugt.
OOP_V2.7.fm
18
2
Aufzählungstypen
2.1
Ziele
• Sie können eigene Aufzählungstypen deklarieren.
• Sie verwenden eigene Aufzählungstypen situationsgerecht.
2.2
Einführung
• Oft hat man es in Programmen mit einer vordefinierte Menge von Dingen zu tun, die eindeutig
definiert sind:
• Beispiele
• Wochentage: Montag, Dienstag, Mittwoch, Donnerstag, Freitag
• Geschlecht: männlich, weiblich
• Kleidergrösse: S, M, L, XL
• Probleme:
• Wenn man diese Werte einfach als Strings implementiert, muss man jedesmal überprüfen, ob ein
String-Wert z.B. ein gültiger Wochentag ist. Zudem muss man Gross- und Kleinschreibung
berücksichtigen.
• Implementiert man jeden Aufzählungswert durch eine int-Konstante, so muss man ebenfalls
überprüfen, ob ein gegebener Wert überhaupt gültig ist. Zudem muss man die Zuordnung der
Zahlen z.B. zu den einzelnen Wochentagen kennen.
2.3
Enumerations
• Enumerations sind vom Programmierer definierte Datentypen für Aufzählungen, sogenannte
Aufzählungstypen
• Damit kann man einen eigenen Datentyp z.B. für die Wochentage definieren
• Vorteil:
• Es können nur gültige Wochentage im Programm verwendet werden (Compiler überprüft dies)
• Die Wochentage tauchen wirklich als solche im Programm auf, was die Lesbarkeit der
Programme stark erhöht.
• Deklaration von Aufzählungstypen
enum Enumtyp { el_1, el_2, ..., el_N }
• Beispiele
• enum Tag { MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG,
SAMSTAG, SONNTAG }
• enum Wochentag { MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG,
FREITAG }
• enum Geschlecht { MAENNLICH, WEIBLICH }
• enum Kleidergroesse { S, M, L, XL }
• Wo werden Aufzählungstypen deklariert?
• emum-Deklarationen werden normalerweise wie eine eigene Klasse deklariert
• enum-Deklarationen können aber auch innerhalb einer anderen Klasse deklariert werden
• Sie müssen aber immer ausserhalb der Methoden deklariert sein, wie InstanzvariablenDeklarationen
OOP_V2.7.fm
19
• Deklaration einer Variablen eines Aufzählungstyps
• Gleich wie eine Instanzvariablendeklaration einer normalen Klasse
• Beispiele:
• Wochentag tag;
• Geschlecht g = Geschlecht.MAENNLICH;
• Kleidergroesse size = Kleidergroesse.S;
• Verwendung von Aufzählungstypen
• Variablen von Aufzählungstypen können wie normale Variablen verwendet werden
• Es können ihnen allerdings nur Werte zugewiesen werden, die im Aufzählungstyp definiert sind
• Die Werte eines Aufzählungstyps werden wie statische Klassenvariablen angesprochen
• Beispiele:
tag
tag
tag
tag
=
=
=
=
Wochentag.DIENSTAG;
// ok
DIENSTAG
// falsch,
¨DIENSTAG¨;
// falsch,
Wochentag.dienstag; // falsch,
// geschrieben
Typ-Name fehlt!
String statt Wochentag
Dienstag nicht gleich
wie in der Enum-Deklaration
• Aufzählungswerte können nur innerhalb desselben Aufzählungstyps zugewiesen und verglichen
werden
tag = Wochentag.MONTAG;
tag = Tag.MONTAG;
// ok, derselbe Aufzählungstyp
// falsch, nicht derselbe Aufzählungstyp
• Wie werden Aufzählungstypen in Java implementiert:
• Im Hintergrund erzeugt Java für jeden Aufzählungstyp eine entsprechende Klasse mit den
nötigen Konstanten-Definitionen
• Beispiel:
• enum Kleidergrösse {S, M, L, XL }
• erzeugt eine Klasse Kleidergroesse mit den entsprechenden Konstanten. Dazu werden
Objekte der Enumeration-Klasse als Konstanten (siehe Abschnitt 4.10) deklariert:
class Kleidergroesse{
public final static
public final static
public final static
public final static
}
Kleidergroesse
Kleidergroesse
Kleidergroesse
Kleidergroesse
S = new
M = new
L = new
XL = new
Kleidergroesse();
Kleidergroesse();
Kleidergroesse();
Kleidergroesse();
• Eine generierte Aufzählungs-Klasse Enum enthält verschiedene vordefinierte Methoden:
• static Enum valueOf(String s)
gibt den Aufzählungswert zum entsprechenden Namen (String) zurück
• static Enum[] values()
liefert einen Array mit allen Aufzählungswerten zurück
• int ordinal()
Die Aufzählungswerte weisen ein bestimmte Reihenfolge auf, wobei das erste
Aufzählungselement den Index 0 hat. ordinal() gibt den Index eines Aufzählungselementes
zurück
OOP_V2.7.fm
20
• int compareTo(Enum e)
vergleicht zwei Aufzählungswerte anhand ihrer Indices
• boolean equals(Enum e)
vergleicht zwei Enum-Werte. Enum-Werte können auch mit == verglichen werden.
• Neben den generierte Methoden können auch eigene Methoden zu Aufzählungsklassen
hinzugefügt werden
• Beispiel Tag:
enum Tag {
MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG, SAMSTAG,
SONNTAG;
boolean istWochenende(){
return this == SAMSTAG || this == SONNTAG;
}
}
• Aufruf der Methoden der Aufzählungsklasse
public class Enumeration extends Applet {
public void paint(Graphics g){
Tag t = Tag.SONNTAG;
g.drawString("Heute ist " + t.toString(), 50, 50);
if (t.istWochenende()){
g.drawString("Es ist Wochenende", 50, 70);
}
}
}
• Das obige Applet Enumeration erzeugt folgenden Output:
Figur 5: Applet Enumeration
• Aufgabe: Schreiben sie die Methode boolean istArbeitstag() für die obige enum Tag.
OOP_V2.7.fm
21
3
Die Benutzerschnittstelle (User Interface)
3.1
Ziele
• Sie können die Idee der Trennung von Benutzerschnittstelle und Modell erläutern.
• Sie können drei Vorteile einer solchen Trennung nennen.
• Sie setzen die Trennung von Benutzerschnittstelle und Modell in Ihren Programmen praktisch um.
3.2
Einführung
• Generell wird der Herstellung von technischen Produkten folgendes Prinzip verfolgt:
• Entkopplung von Benützerschnittstelle und technischen Innereien
• Bsp. Auto
• Benützerschnittstelle:
Gaspedal, Bremspedal, Kupplungspedal, Ganghebel, Steuerrad, Tachometer, Tourenzähler,
Benzinanzeige, Lichtschalter etc.
• Innereien:
Motor, Kupplung, Bremsanlage, Lenkung, Lichtanlage
• Vorteile:
• Einfache und sichere Bedienung und Überwachung durch Benützer
• Verstecken der Komplexität vor dem Benützer
• Getrennte und damit einfachere Entwicklung und Wartung der Teilsysteme
OOP_V2.7.fm
22
• Dieses Prinzip wird auch in der Softwareentwicklung häufig angewandt. Es findet seinen Ausdruck
in der sogenannten Model-View-Controller-Architektur.
• Model-View-Controller-Architektur
• Jedes objektorientierte Programm besteht aus mindestens zwei Teilen:
• Das Modell:
– modelliert die eigentliche Anwendung
– implementiert die Funktionalität des Programms
– ist normalerweise nicht sichtbar für den Benützer
– sollte möglichst unabhängig von der Benützerschnittstelle sein
• Die Benützerschnittstelle:
Ist für die Kommunikation zwischen Benützer und Modell verantwortlich. Die
Benützerschnittstelle kann nochmals in zwei Komponenten aufgeteilt werden:
– View (Ansicht):
– umfasst alle Anzeigeelemente: Grafiken, Diagramme, Text
– Die View muss das Modell kennen, um dessen Daten darstellen zu können
– Controller (Steuerung):
– umfasst alle Steuerelemente: Buttons, Scrollbars, etc
– muss das Modell ebenfalls kennen, um es steuern zu können
– ist normalerweise stark gekoppelt mit der View.
• Designziel:
Ein wichtiges Ziel beim Design ist die möglichst starke Entkopplung von Modell und
Benützerschnittstelle
• Vorteile:
• Unabhängige Entwicklung/Test von Modell und Benützerschnittstelle möglich
• Es können gleichzeitig mehrere Views und Controllers für dasselbe Modell aktiv sein.
• Änderungen an Benutzerschnittstelle oder Modell leichter möglich.
• Fernsteuerung des Modells besser möglich.
OOP_V2.7.fm
23
3.3
Modell, View und Controller
• Beispielprogramm PlayBallon2:
public class PlayBalloon2 extends Applet implements ActionListener{
private Button grow, shrink, left, right;
private Balloon myBalloon;
public void init() {
grow = new Button ("Grow");
add (grow);
grow.addActionListener(this);
shrink = new Button ("Shrink");
add (shrink);
shrink.addActionListener(this);
left = new Button ("Left");
add (left);
left.addActionListener(this);
right = new Button ("Right");
add (right);
right.addActionListener(this);
myBalloon = new Balloon ();
}
public void actionPerformed(ActionEvent event) {
if (event.getSource() == grow){
myBalloon.grow();
}
if (event.getSource() == shrink){
myBalloon.shrink();
}
if (event.getSource() == left){
myBalloon.left();
}
if (event.getSource() == right){
myBalloon.right();
}
OOP_V2.7.fm
24
repaint();
}
public void paint (Graphics g) {
myBalloon.display(g);
}
}
class Balloon{
private int diameter = 10;
private int xCoord = 20, yCoord = 50;
public void display (Graphics g) {
g.drawOval (xCoord, yCoord, diameter, diameter);
}
public void left() {
xCoord = xCoord - 10;
}
public void right() {
xCoord = xCoord + 10;
}
public void grow() {
diameter = diameter + 5;
}
public void shrink() {
diameter = diameter - 5;
}
}
Programm 5 : PlayBallon2 mit Klasse Balloon
OOP_V2.7.fm
25
• Das Programm besteht aus 2 Klassen: PlayBalloon2, Balloon.
• Lernaufgabe:
Zeichnen Sie alle erzeugten Objekte, die nach der Initialisierung des Applets vorhanden sind, mit
ihren Daten und Methoden in Figur 6 ein:
Figur 6: Objekte des Programms PlayBalloon2
OOP_V2.7.fm
26
• Welche Objekte gehören zum Modell, View, Controller?
• Modell:
Balloon-Objekt myBalloon
– Attribute (Zustand): Grösse, Position unsichtbar
• Benützerschnittstelle (UI):
PlayBalloon2-Objekt
– Controller: Buttons grow, shrink, left, right
– View: Darstellung des Ballons (Methode paint) und der Buttons
3.4
Der Controller
• Die Aufgabe des Controller ist es, die Benützereingaben entgegen zu nehmen und weiter zu leiten.
• Programmablauf, wenn der Benützer einen Button betätigt:
• Der Benützer betätigt die Maus, während der Cursor über einem Button ist.
• Der Browser bemerkt dieses Ereignis.
• Der Browser sendet eine Nachricht an den entsprechenden Button.
• Dieser ruft den entsprechenden Eventhandler bei allen eingetragenen Listener-Objekten auf:
Methode actionPerformed(...) im PlayBalloon2-Objekt wird aufgerufen.
• Die Methode actionPerformed ruft die erforderliche Methode des Modells (Objekt
myBalloon)auf.
• Die aufgerufene Methode ändert die Daten des Modells (d.h. den Objektzustand).
• Methode actionPerformed(...) ruft zudem repaint() auf, um das
Benutzerschnittstelle neu zu zeichnen zu lassen.
Objekt myBalloon
PlayBalloon2-Objekt
grow()
Browser
Button
shrink()
actionPerformed(...)
left()
repaint()
right()
Figur 7: Programmablauf beim Betätigen eines Buttons
OOP_V2.7.fm
27
3.5
Das Modell
• Das Modell besteht hier nur aus dem Balloon-Objekt myBalloon. Häufig besteht ein Modell
aus mehreren Objekten.
• Das Modell muss Methoden zur Verfügung stellen, um
• das Modell steuern zu können
• sich selbst darstellen zu können
• Diese Verantwortlichkeiten werden im Balloon-Objekt von den Methoden left(),
right(), grow(), shrink() und display(Graphics g) übernommen.
3.6
Die View (Ansicht)
• Die View stellt sicher, dass das aktuelle Bild des Modells jeweils dargestellt wird.
• Der Browser weiss, wann die View neu gezeichnet werden muss (z.B. bei der Verkleinerung des
Fensters):
• Er ruft Methode paint(Graphics g) im PlayBalloon2-Objekt auf. Der paintMethode wird beim Aufruf ein Graphics-Objekt übergeben. Dieses hat den Namen g in der
Methode paint.
• Die paint-Methode ruft die Methode display(Graphics g) im Balloon-Objekt
myBalloon auf und übergibt ihr dabei das Graphics-Objekt g.
• Die Methode display schliesslich ruft die Methode drawOval(...) im GraphicsObjekt, das sie von der paint-Methode erhalten hat.
• Der Programmablauf sieht also folgendermassen aus:
Browser
PlayBalloon2-Objekt
Objekt myBalloon
Graphics-Objekt
paint(...)
display(...)
drawOval(...)
Figur 8: Programmablauf beim Neuzeichnen des Applets
• Falls sich am Modell etwas ändert, muss die View ebenfalls neu gezeichnet werden:
• Dass sich im Modell etwas geändert hat, kann nur einer der EventHandler wissen, z.B. die
actionPerformed-Methode.
• Diese ruft die Methode repaint() auf:
Dieser Aufruf fordert den Browser auf, das Applet (= View) neu zu zeichnen.
• Der Browser ruft sodann die Methode paint(...) des Applets, die vom Programmierer
geschrieben wurde, auf.
• paint ruft seinerseits die Methode display(...) im Objekt myBalloon auf.
• Die display-Methode des Balloon-Objekts zeichnet schliesslich den Ballon, indem
sie die drawOval-Methode des übergebenen Graphics-Objekts g aufruft.
OOP_V2.7.fm
28
Aufgaben zu PlayBalloon2
1. Erweitern Sie das Programm PlayBalloon2 auf Seite 25 so, dass Sie mit 2 Buttons den Ballon
aufwärts und abwärts bewegen können.
2. Zusätzlich soll nun der Ballon die Koordinaten seines Mittelpunktes (x,y) auf dem Bildschirm ausgeben.
OOP_V2.7.fm
29
Schreiben Sie die zusätzlich nötigen Anweisungen hier auf und geben Sie an, wo genau die Anweisungen im Programm eingefügt werden müssen.
3.7
Repetition Sichtbarkeitsbereiche
Aufgabe:
• Tragen Sie für alle Variablen in PlayBalloon2 ein, wo sie sichtbar sind (eine Farbe pro Sorte)
OOP_V2.7.fm
30
4
Vererbung
4.1
Ziele
•
•
•
•
•
•
Sie können das Prinzip der Vererbung in eigenen Worten erklären.
Sie können ein bestehendes Klassendiagramm interpretieren.
Sie können selbständig ein einfaches Klassendiagramm aufstellen.
Sie können die Sichtbarkeitsregeln anhand eines solchen Diagramms erklären.
Sie können bestehende Klassen durch Vererbung erweitern.
Sie können die Grundidee des Polymorphismus anhand eines Beispiels erklären.
4.2
Einführung
• Beispiel Auto:
• Eine Firma stellt einen PW her, 4-türig, mit Stufenheck
• Ein Kunde möchte einen solchen PW, aber als Caravan
• Was macht Entwicklungs-Ing.? Er hat folgende Alternativen:
• Er entwickelt ein neues Auto von Grund auf
Unsinn!!
• Er übernimmt möglichst viel vom bestehendem PW.
• Noch besser ist er dran, wenn er bereits das erste Auto so modular entwickelt hat, dass die
Umwandlung in einen Caravan einfach möglich ist.
• Dasselbe gilt für Softwareentwicklung:
• Nicht bei jedem neuen Programm soll das Rad wieder neu erfunden werden.
• Ein neues Programm soll möglichst aus bestehenden Softwarebausteinen aufgebaut werden:
• Die Bausteine bestehen bereits, sind getestet, haben sich bewährt.
• Alles andere ist häufig zu aufwändig, dauert zu lange und ist zu teuer!
• Ziele bei der Softwareentwicklung:
• Software möglichst aus bestehenden Komponenten aufbauen. Die wiederverwendeten Bausteine
müssen dabei meistens an die neuen Anforderungen angepasst werden.
• Möglichst wiederverwendbare Softwarebausteine entwickeln.
• Objektorientierte Programmierung
• Die wiederverwendbaren Softwarebausteine sind die Klassen
• Ein Mechanismus für die Wiederverwendung ist die sogenannte Vererbung.
4.3
Beispiel Ballon
• Es seien zwei Klassen gegeben (siehe Figur 9 Seite 32):
• Klasse Kugel
• Klasse Ballon, die von der Klasse Kugel abgeleitet ist.
• Die Klasse Ballon heisst Subklasse (Unterklasse) von der Klasse Kugel.
• Die Klasse Kugel heisst entsprechend Superklasse (Oberklasse) von der Klasse Ballon.
• Vererbungsmechanismus:
• Klasse Ballon erbt alle Attribute (Instanzvariablen) und Operationen (Methoden) von Klasse
Kugel.
• Attribute und Operationen werden nicht einfach in die Subklasse kopiert, sondern alle Attribute
und Operationen der Klasse Kugel sind auch in der Klasse Ballon verfügbar.
OOP_V2.7.fm
31
Kugel
Ballon
Figur 9: Graphische Darstellung der Klassenbeziehung zwischen der Klasse Kugel
und der Klasse Ballon in UML (Unified Modeling Language)
• Die Klasse Ballon erweitert die Klasse Kugel. Sie deklariert dazu
• zusätzliche Attribute:
Color farbe
• zusätzliche Operationen: up(), down(), setFarbe(Color col)
OOP_V2.7.fm
32
• Die Klasse Ballon kann auch Operationen (und Attribute) der Klasse Kugel sogenannt
überschreiben (override). Dazu wird in der Subklasse einfach nochmals eine Methode mit dem
gleichen Methodenkopf wie die geerbte Methode geschrieben.
Beispiel:
Die Methode display(Graphics g), die die Klasse Ballon von der Klasse Kugel erbt
wird in der Klasse Ballon überschrieben.
• Achtung:
Verwechseln Sie das Überschreiben einer Methode nicht mit dem Überladen (overloading) der
Methode!
• Beachten Sie: Die Klasse Kugel wird durch die Vererbung nicht verändert!
• Objekte der Klasse Ballon haben somit folgende Attribute und Methoden:
Figur 10: Objekt der Klasse Ballon
• Die Objekte besitzen alle Attribute und Operationen der Klasse Ballon und zudem die der
Klasse Kugel.
• Zwischen der Klasse Ballon und der Klasse Kugel besteht eine sogenannte „ist-ein“Beziehung: Jedes Ballon-Objekt ist gleichzeitig auch eine Kugel-Objekt, denn es besitzt alle
Attribute und Methoden eines Kugel-Objekts.
• Objekte der Klasse Ballon können überall dort eingesetzt werden, wo ein Objekt der Klasse
Kugel(Superklasse) erwartet wird, da sie dieselbe Schnittstelle (public-Methoden und
Variablen) nach aussen besitzt.
OOP_V2.7.fm
33
4.4
Vererbung in Java
• Das Erweitern einer Klasse ist in Java denkbar einfach:
• Bei der Deklaration der Subklasse muss nach dem Schlüsselwort extends die Superklasse
angegeben werden, die erweitert werden soll.
• Die Subklasse erbt alle Instanzvariablen und Methoden der Superklasse. Allerdings kann die
Subklasse nur auf Methoden und Attribute direkt zugreifen, die nicht private deklariert sind.
• Die Subklasse erhält keine Kopien der Variablen und Methoden, sondern den Zugriff auf die
Instanzvariablen und Methoden der Superklasse.
• Die Subklasse kann sodann beliebig zusätzliche Methoden und Instanzvariablen deklarieren (wie
bei einer normalen Klasse)
• Methoden überschreiben (overriding):
• Falls eine Methode in der Subklasse genau den gleichen Methodenkopf (Signatur) aufweist,
wie eine Methode in der Superklasse, so wird die Methode der Superklasse überschrieben.
• Der Methodenkopf in der Subklasse muss genau mit demjenigen in der Superklasse
übereinstimmen (gleicher Methodenname, gleiche Anzahl und gleicher Typ der Parameter,
gleicher Rückgabetyp, gleiche Sichtbarkeit, static, falls überschriebene Methode auch
static).
• Falls die Deklaration nicht genau mit der in der Superklasse übereinstimmt, wird die Methode
überladen statt überschrieben! D.h. beide Methoden würden dann in der Subklasse
nebeneinander existieren, die geerbte und die neu geschriebene.
• Einfachvererbung (single inheritence):
Eine Subklasse kann in Java nur von einer direkten Superklasse erben, nicht von mehreren wie z.B.
in C++. In Java gibt es dafür das Konzept der Interfaces.
• Allgemeine Deklaration einer Subklasse:
class Subklassenname extends Superklassenname {
Instanzvariablendeklarationen
Methodendeklarationen
Konstruktorendeklarationen
}
• Beispiel:
Die Klasse Ballon soll gemäss Figur 9 von der Klasse Kugel abgeleitet werden:
class Kugel {
protected int x = 100, y = 100, radius = 40;
public void setPos(int newX, int newY){
x = newX;
y = newY;
}
public void display(Graphics g){
g.drawOval(x,y,radius,radius);
}
}
OOP_V2.7.fm
34
class Ballon extends Kugel {
protected Color farbe = Color.green;
public void setFarbe(Color col){
farbe = col;
}
public void up(){
y = y - 10;
}
public void down(){
y = y + 10;
}
public void display(Graphics b){
Color fg = null;
fg = b.getColor();
b.setColor(farbe);
super.display(b);
b.setColor(fg);
}
}
Programm 6 : Klasse Kugel und Ballon
4.5
Sichtbarkeitsbereiche
• Im Zusammenhang mit Subklassen wird ein zusätzlicher Zugriffsmodifikator protected
definiert. Ein Überblick über die bisher behandelten Zugriffsmodifikatoren zusammen mit den
entsprechenden Sichtbarkeitsbereichen sind in Tabelle 1 zusammengefasst:
Sichtbarkeit
Zugriffsmodifikator
gleiche Klasse
Subklasse
andere Klasse
private
ja
nein
nein
protected
ja
ja
nein
public
ja
ja
ja
Tabelle 1: Sichtbarkeitsbereiche der Zugriffsmodifikatoren
• Der Sichtbarkeitsbereich gilt jeweils für die Klasse selbst und alle Objekte der Klasse. D.h.eine
private-Instanzvariable ist nur innerhalb desselben Objekts sichtbar, eine private static
Variable ist innerhalb der gleichen Klasse und allen Objekten davon sichtbar.
• public:
• public-Methoden sind überall sichtbar, d.h. können von überall aus aufgerufen werden.
• public-Instanzvariablen sind überall sichtbar, d.h. sie können von überall her gelesen und
verändert werden.
OOP_V2.7.fm
35
• private:
• private-Methoden/Instanzvariablen sind nur innerhalb derselben Klasse sichtbar.
• abgeleitete und andere Klassen können auf diese Variablen nur indirekt via Methoden zugreifen.
• protected:
• protected-Methoden/Instanzvariablen sind sichtbar in allen Subklassen der Klasse, in der
die Variable/Methode deklariert ist.
• Zur Erinnerung: lokale Variablen sind nur innerhalb derselben Methode sichtbar.
• Regeln für die Verwendung der Zugriffmodifikatoren:
• Nur Methoden, die einen Service für Benützer der Klasse bieten, als public deklarieren.
• Alle Instanzvariablen als private deklarieren. Zugriff nur via Methoden ermöglichen.
• Ausnahme:
Private Instanzvariablen/Methoden, die aber auch in abgeleiteten Klassen gebraucht werden,
müssen protected anstatt private deklariert werden.
4.6
Klassendiagramm (class diagram)
• Ganz allgemein zeigt ein Klassendiagramm die Beziehungen von Klassen untereinander. Sie stellt
somit eine statische Sicht eines Systems dar.
• Vererbungs-Hierarchie
• Eine Klasse ohne explizite Superklasse ist in Java Subklasse von der Klasse Object.
• Jede Subklasse kann seinerseits von weiteren Subklassen erweitert werden.
• Dadurch entseht eine baumartige Hierarchie von Subklassen.
• Diese Vererbungs-Hierarchie von Klassen wird typischerweise in einem Klassendiagramm
dargestellt.
• Beispiel:
• Figur 11 zeigt die Vererbungshierarchie der Klassen Kugel, Ball, Ballon, Billardkugel
und Fussball als Klassendiagramm.
• Die Subklassen erben alle Attribute (Instanzvariablen) und Operationen (Methoden) aller ihrer
Superklassen.
• So erbt beispielsweise die Klasse Fussball die Methoden kick, updatePos und
setRadius von der Klasse Ball, die Methoden setPos und display von Kugel und die
Methode equals von der Klasse Object.
• Die Klasse Fussball überschreibt die Methode display der Klasse Kugel und die
Methode updatePos der Klasse Ball. Zudem wir die geerbte Methode kick überladen.
• Die Klasse Fussball erbt zudem die Attribute x, y und radius von der Klasse Kugel und
vX und vY von der Klasse Ball. Die Klasse Fussball deklariert zusätzlich die Attribute z
und vZ.
• Ablauf der Suche einer Methode (oder Variablen) beim Aufruf:
Falls eine Operation (oder Attribut) eines bestimmten Objekts aufgerufen wird, so wird diese in
folgender Reihenfolge in den verschiedenen Klassen gesucht:
• zuerst in der Klasse des aktuellen Objekts. Falls sie nicht gefunden wird,
• in der direkten Superklasse
• in der nächst höheren Superklasse
• usw.
• schliesslich in der Klasse Object.
OOP_V2.7.fm
36
Object
equals()
Kugel
int x, y, radius
setPos(int x, int y)
display(Graphics g)
Ball
Ballon
int vX, vY
Color farbe
kick(int vX, int vY)
updatePos(int dt)
setRadius(int r)
Billardkugel
Color farbe
int zahl
display(Graphics k)
setFarbe(Color col)
up()
down()
display(Graphics b)
Fussball
int z, vZ
kick(int vX, int vY, int vZ)
updatePos(int dt)
display(Graphics g)
Figur 11: Klassendiagramm (Vererbungshierarchie) der Klasse Kugel
Aufgaben
• Lösen Sie anhand des Klassendiagramms in Figur 11 folgende Aufgaben:
• Welche Attribute und Operationen weist ein Objekt der Klasse Billardkugel auf?
• Schreiben Sie die Klasse Billardkugel. Sie soll einen Konstruktor enthalten, mit dem eine
Billardkugel mit einer bestimmten Zahl an der gewünschten Stelle und in der gewünschten Farbe
erzeugt werden kann.
Lösung:
OOP_V2.7.fm
37
OOP_V2.7.fm
38
4.7
Überschriebene Methoden
• Eine Methode einer Superklasse ist überschrieben, wenn in einer ihrer Subklassen eine Methode mit
demselben Methodenkopf (Signatur) steht.
• Beim Überschreiben einer Methode ist die ursprüngliche Methode in der Superklasse von der
Subklasse aus nicht mehr direkt aufrufbar.
• Falls die ursprüngliche Methode in der Superklasse von der Subklasse aus aufgerufen werden soll,
so muss der Objektname super verwendet werden:
super.Methodenname(...);
super bezeichnet das aktuelle Objekt, das aber als Objekt der Superklasse interpretiert wird
Dementsprechend wird die Methode der Superklasse aufgerufen.
• Beispiel:
Methode display in Klasse Ballon
public void display(Graphics b){
Color fg;
fg = b.getColor();
b.setColor(farbe);
super.display(b); // ruft display in der Klasse Kugel auf
b.setColor(fg);
}
• super.super.Methodenname ist nicht erlaubt.
Aufgabe 3:
Stellen Sie anhand des Klassendiagramms von Figur 11 fest, aus welcher Klasse die aufgerufene Methode stammt, falls Sie:
• die Methode up() eines Ballon-Objekts aufrufen
• die Methode display(Graphics g) eines Ball-Objekts aufrufen.
• die Methode display(Graphics g) eines Fussball-Objekts aufrufen.
• die Methode kick(int vX, int vY) eines Fussball-Objekts aufrufen.
• die Methode super.display(Graphics g) eines Fussball-Objekts aufrufen.
Lösung
• up()
// Ballon-Obj.
:
• display(Graphics g) // Ball-Obj.
:
• display(Graphics g)// Fussball-Obj.
:
• kick(int xV, int vY) // Fussball-Obj.
:
• super.display(Graphics g)// Fussball-Obj.
:
OOP_V2.7.fm
39
4.8
Überschriebene Attribute
• Falls in der Subklasse eine Instanzvariable mit gleichem Namen wie in der Superklasse deklariert
wird, ist die Instanzvariable der Superklasse in der Subklasse nicht mehr sichtbar.
• Das Überschreiben von Attributen sollte man möglichst vermeiden.
4.9
Konstruktoren
• Beim Erzeugen eines Objekts wird bekanntlich der entsprechende Kontstruktor ausgeführt. Dort
werden normalerweise die Instanzvariablen des erzeugten Objekts initialisiert.
• Beim Erzeugen eines Objekts einer Subklasse, wird ebenfalls der entsprechende Kontstruktor
ausgeführt, um dessen Instanzvariablen zu initialisieren. Damit nun aber auch die Instanzvariablen
aller Superklassen der Klasse richtig initialisiert werden, läuft folgender Vorgang ab:
• Vor dem Ausführen der ersten Anweisung im Konstruktor der Subklasse wird der StandardKonstruktor der Superklasse, Superklasse(), ausgeführt. Konkret wird von Java automatisch die
Anweisung super(); vor die erste Anweisung im Konstruktor der Subklasse eingefügt, was
den Aufruf des Superkonstruktors bewirkt.
• Falls ein anderer als der Standard-Konstruktor in der Superklasse ausgeführt werden soll, so
muss der Programmierer selbst diesen Superkonstruktor aufrufen, und zwar als erste Anweisung
im Konstruktor der Subklasse. Diese Anweisung muss sogar vor allfälligen Variablendeklarationen stehen. Dies macht der Programmierer, indem er als erste Anweisung den Befehl
super(Parameterliste);
im Konstruktor der Subklasse schreibt. Falls Java keine solche erste Anweisung findet, ruft es
automatisch den Standard-Konstruktor der Superklasse auf, d.h. super(). Falls die Superklasse keinen solchen Konstruktor besitzt, tritt ein Compilierungsfehler auf.
• Falls eine Klasse überhaupt keinen Konstruktor aufweist, wird von Java automatisch ein
Standard-Konstruktor eingefügt. Dieser macht nichts anderes, als den Superkonstruktor
aufzurufen und sieht dementprechend folgendermassen aus:
Klassenname(){
super();
}
• Mit diesen Mechanismen garantiert Java, dass beim Erzeugen eines Objekts einer Klasse die
Konstuktoren aller Superklassen der Klasse ausgeführt werden, bevor der Konstruktor der Klasse
selbst ausgeführt wird. Der erste Konstruktor, der ausgeführt wird ist also der Konstruktor der
Basisklasse Object, die Superklasse aller Klassen. Danach werden die Konstruktoren der
direkten Subklassen der Reihe nach ausgeführt bis zur aktuellen Klasse.
• Beispiel:
Wird im Klassendiagramm von Figur 11 ein Objekt der Klasse Fussball erzeugt, so werden
der Reihe nach folgende Konstruktoren ausgeführt:
1.Object();
2.Kugel();
3.Ball();
4.FussBall();
OOP_V2.7.fm
40
• Aufgabe:
Zeichnen Sie den Programmablauf beim Erzeugen eines Objekts der Klasse Billardkugel auf,
falls die Klassen folgende Konstruktoren definieren:
public class Object {
Object(){
...
}
}
public class Kugel {
Kugel(){
...
}
Kugel(int radius){
...
}
}
class Billardkugel extends Kugel {
Billardkugel(int iniX, int iniY, Color col){
super(20);
farbe = col;
x = iniX;
y = iniY;
}
}
4.10
Schlüsselwort final
• Manchmal möchte man vermeiden, dass Instanzvariablen oder Methoden einer Klasse überschrieben werden.
• Dies kann man tun, indem man die Variable oder Methode als final deklariert.
• Folge: Eine final-Methode oder -Variable kann nicht mehr überschrieben werden.
• Es können auch ganze Klassen als final deklariert werden:
• Folge: Die Klasse kann nicht mehr erweitert (subclassed) werden.
• Alle Methoden der Klasse sind dann automatisch auch final.
• Beispiel: Die Klasse System ist als final deklariert.
• static final –Variablen:
• Dies sind Klassenvariablen, die nicht überschrieben werden können.
• Diese Variablen entsprechen den Konstanten in anderen Sprachen.
• Java-Konvention: Namen von Konstanten in Grossbuchstaben schreiben.
• Beispiel:
Die Konstante Pi = 3.1415 ist in der Klasse Math definiert und kann von überall her mit dem
Namen Math.PI verwendet werden.
OOP_V2.7.fm
41
4.11
Erlaubte Operationen auf Objekten
• Bisher haben wir folgende Operationen auf Objekten kennen gelernt:
• Erzeugen eines Objekts aus seiner Klasse.
• Zuweisung zu einer Variablen derselben Klasse.
• Verwendung als Parameter und Rückgabewert bei einer Methode.
• Aufruf vn Methoden der Klasse des Objekts.
• Mit der Vererbung kommen neu dazu:
• Methodenaufruf aller public- und protected-Methoden in allen Superklassen des Objekts.
• Zuweisung eines Objekts einer Subklasse zu einer Variablen (d.h. einem Objekt) einer der
Superklassen dieser Klasse.
4.12
Erweitern von Bibliotheksklassen
• Klassen in Bibliotheken können wie die selbst programmierte Klassen erweitert werden.
• Beispiel: Klasse Applet
• Um ein Applet zu schreiben, haben wir jeweils eine Subklasse der Klasse Applet geschrieben:
public MyProgram extends Applet {...}
• MyProgram erbt alle Methoden von der Klasse Applet und deren Superklassen.
• Die Methoden add() und repaint() z.B. werden von den Superklassen von Applet zur
Verfügung gestellt.
• Die Methoden init() und paint(Graphics g) der Klasse Applet müssen in
MyProgram überschrieben werden.
• Wie findet man die nötigen Informationen über Bibliotheksklassen?
• In der Online-Dokumentation nachschauen (normalerweise direkt von der IDE aus möglich)
• In Java-Büchern (z.B. „Java in a Nutshell“)
4.13
Polymorphismus
• Bei der Erzeugung eines Objekts wird Folgendes ein für allemal festgelegt:
• Die Identität des Objekts (eindeutige Kennzeichnung). Innerhalb des Programms ist dies die
Referenz auf das Objekt.
• Die Klassenzugehörigkeit Sie bleibt erhalten, solange das Objekt existiert.
• Beachten Sie:
Wird das Objekt einer Variablen einer Superklasse zugeordnet, so ändert nur die Interpretation des
Objekts, nicht dessen Klassenzugehörigkeit!
• Wir wissen:
• Methoden einer Klasse können überschrieben oder überladen werden: Im Beispiel der Klasse
Tier in Figur 12 auf Seite 43 wird die Methode display(Graphics g) überschrieben in
den Subklassen Schwein und Kuh.
• Objekte einer Subklasse können Objekten der Superklasse zugewiesen werden: Wir können
deshalb ein Objekt eineKuh der Klasse Kuh einer Variablen vom Typ Tier, z.B. der Variablen
Tier meinTier, zuweisen:
Kuh eineKuh = new Kuh();
Tier meinTier = eineKuh;
• Frage:
Was passiert nun, wenn die Methode display(Graphics g) des Objekts meinTier aufgerufen
wird?
OOP_V2.7.fm
42
Tier
display(Graphics g)
Schwein
Kuh
int beine = 4;
int beine = 4;
display(Graphics g)
display(Graphics g)
Schlange
boolean giftig;
Figur 12: Klassendiagramm der Klasse Tier und seiner Subklassen
• Antwort:
• Die richtige Methode wird erst beim Aufruf der Methode anhand der Klassenidentität des
aktuellen Objekts gesucht (dynamisches Binden).
• Ablauf der Suche:
• Zuerst wird in der Klasse (die bei der Erzeugung festgelegt wurde) des Objekts, in vorherigen
Beispiel in der Klasse Kuh, nach einer passenden Methode gesucht (passender Name und
Parameterliste).
• Falls nichts Passendes gefunden wurde, wird in der entsprechenden Superklasse gesucht.
• Falls nichts Passendes gefunden wurde, wird in der darüberliegenden Superklasse gesucht
• usw.
• Folge:
Dieses dynamische Binden bewirkt, dass der gleiche Methodenaufruf (gleicher Objektname und
gleicher Methodenname) je nach Klassenzugehörigkeit des Objekts zur Ausführung verschiedener
Methoden führt!
Dieses Verhalten ist eine mögliche Form von Polymorphismus (Vielgestaltigkeit).
• Beispielprogramm Tiere
• Die Auswirkungen der Polymorphismus sollen am Beispielprogramm 7 Seite 45
veranschaulicht werden.
• Das Beispielprogramm Tiere basiert auf dem Klassendiagramm von Figur 12.
• Wird das Programm ausgeführt, so entsteht folgende Bildschirmausgabe:
Figur 13: Bildschirmausgabe des Programms Tiere
OOP_V2.7.fm
43
• Erklärungen zum Programmablauf:
• Die Methode display(...) der Klasse Tier wird von den Subklassen Kuh und
Schwein überschrieben, nicht jedoch von Schlange.
• Ein Objekt der Klasse Kuh kann nun dem Objekt meinTier der Klasse Tier zugewiesen
werden, da Tier Superklasse der Klasse Kuh ist.
– Wird nach dieser Zuweisung die Methode meinTier.display (...) aufgrufen, so
bestimmt Java die richtige display-Methode dynamisch anhand der Klasse des
aktuellen Objekts.
– Da das Objekt meinTier nach der obigen Zuweisung eigentlich eine Instanz der Klasse
Kuh ist, wird in diesem Fall Kuh.display(...) ausgeführt.
• Das Gleiche spielt sich ab, wenn ein Objekt der Klasse Schwein dem Objekt meinTier
zugewiesen wird.
• Wird ein Objekt der Klasse Schlange dem Objekt meinTier zugewiesen, so wird die
display-Methode der Klasse Tier ausgeführt, da die Klasse Schlange diese
Methode nicht überschrieben hat.
• Der gleiche Methodenaufruf meinTier.display(...)führt also je nach der Klassenzugehörigkeit des Objekts eine Methode einer anderen Klasse aus.
• Polymorphismus funktioniert nur für Methoden (polymorphe Operationen), nicht aber für
Instanzvariablen. Man spricht deshalb bei Variablen häufig von Überdecken (Shadowing) anstatt
Überschreiben.
• Das Progamm Tiere:
import java.awt.*;
import java.applet.Applet;
public class Tiere extends Applet {
Tier meinTier, tier1;
Kuh berta;
Schwein franz;
Schlange rosa;
public void init(){
tier1 = new Tier();
berta = new Kuh();
franz = new Schwein();
rosa = new Schlange();
}
public void paint(Graphics
meinTier = tier1;
meinTier.display(g); //
meinTier = berta;
meinTier.display(g); //
meinTier = franz;
meinTier.display(g); //
meinTier = rosa;
meinTier.display(g); //
}
g) {
ruft Tier.display() auf
ruft Kuh.display() auf
ruft Schwein.display() auf
ruft Tier.display() auf
}
OOP_V2.7.fm
44
class Tier {
protected static int y = 20;
public void display(Graphics g){
g.drawString("Ich bin ein Tier",20,y);
y = y + 20;
}
}
class Kuh extends Tier {
private int beine = 4;
public void display(Graphics g){
g.drawString("Ich bin eine Kuh",20,y);
y = y + 20;
}
}
class Schwein extends Tier {
private int beine = 4;
public void display(Graphics g){
g.drawString("Ich bin ein Schwein",20,y);
y = y + 20;
}
}
class Schlange extends Tier {
private boolean giftig;
}
Programm 7 : Tiere
4.14
Wiederverwendung von Software
• Wiederverwendung von Software spart Zeit (z.B. die Klassen von AWT und Klasse Math).
• Meist existiert schon Software, die 90% der Funktionalität aufweist.
• Ändern einer bestehenden Software ist aufwändig:
• Neue Fehler, Inkompatibilitäten mit anderen Komponenten können entstehen
• Umfangreiche Tests auch der bestehenden Teile werden daher nötig.
• Lösungsansatz des OOP:
• Die verwendeten Teile der Software werden belassen wie sie sind.
• Nötige Änderungen und Ergänzungen erreicht man durch Vererbung und Erweiterung.
• Empohlenes Vorgehen bei der Softwareentwicklung:
• Anforderungen an das Programm studieren.
• Klassen in der Bibliothek suchen, die die Funkltionalität am ehesten erfüllen.
• Klasse erweitern, um die nötigen Erweiterungen zu programmieren.
• Prüfen, ob Subklasse in einer „ist-ein“-Beziehung zur Superklasse steht.
OOP_V2.7.fm
45
5
Erzeugung Grafischer Benützeroberflächen (GUI)
5.1
Ziele
• Sie können in Ihren eigenen Worten erklären, was die 6 GUI-Elemente Checkbox, Choicebox,
Liste, Textfeld, Panel und Canvas sind und wozu man sie brauchen kann.
• Sie setzen die 6 GUI-Elemente in Ihren eigenen Programmen situationsgerecht ein.
• Sie wissen, welche GUI-Elemente ItemEvents erzeugen können, und wie Sie diese behandeln
können.
• Sie kennen die Klassenhierarchie der GUI-Komponenten im AWT.
• Sie können erklären, welchen Zweck die Klassen Component, TextComponent und
Container haben.
• Sie können mit den LayoutManagern FlowLayout und BorderLayout umgehen.
5.2
Einführung
• Sie haben bereits folgende GUI-Komponenten kennen gelernt:
• Schieberegler (Scrollbar),
• Labels (Label),
• Textzeilen (TextField),
• Schaltflächen (Button)
• In diesem Kapitel werden wir nun folgende weitere Kompenenten des AWT kennen lernen:
• Schalter (Checkbox )
• Schaltergruppen, Radioknöpfe (CheckboxGroup )
• Auswahllisten (Choice)
• Listen (List)
• Textfelder (TextArea)
• Leinwand (Canvas)
• Schalttafel (Panel)
• Layout-Manager (FlowLayout, BorderLayout)
5.3
AWT und Swing
• Das AWT (Abstract Window Toolkit) stellt eine ganze Reihe von Komponenten für graphische
Benützeroberflächen (GUI: Graphical User Interface) zur Verfügung.
• Diese Komponenten sind einfach zu handhaben und erlauben bereits, anspruchsvolle GUIs zu
entwickeln.
• Für die Entwicklung professioneller GUIs stellt Java seit der Version 1.2 zusätzlich die sogenannten
Swing-Klassen zur Verfügung:
• Diese stellen dieselben Komponenten wie in AWT zur Verfügung. Die entsprechenden SwingKomponenten beginnen jeweils mit einem "J", z.B. JButton , JScrollbar , etc.
• Daneben weist Swing noch etliche zusätzliche Komponenten für der professionellen Einsatz auf
(z.B. Tabellen, Baumstrukturen, uam.)
• Im Rahmen dieser Vorlesung werden wir uns auf die AWT-Komponenten beschränken.
OOP_V2.7.fm
46
• Die Klassenhierarchie der GUI-Komponenten des AWT sieht folgendermassen aus:
Object
Component
Button
Canvas
Checkbox
Choice
Container
Panel
Scrollpane
Window
Dialog
JComponent
Label
FileDialog
JDialog
Frame
JFrame
List
JWindow
Scrollbar
TextComponent
TextArea
TextField
Figur 14: Klassenhierarchie der AWT-Komponenten
OOP_V2.7.fm
47
5.4
Klasse Component
• Component ist die Superklasse der meisten GUI-Komponenten des AWT und von Swing.
• Sie ist eine abstrakte Klasse, d.h. sie kann nicht direkt instanziert werden (oder anders gesagt: es
können keine Objekte aus dieser Klasse erzeugt werden).
• Component enthält viele Methoden, die von den GUI-Komponenten geerbt bzw. überschrieben
werden.
• Die wichtigsten Methoden der Klasse Component sind:
setBackground(Color farbe)
setzt Hintergrundfarbe einer Komponente auf farbe
setForeground(Color farbe)
setzt Vordergrundfarbe einer Komponente auf farbe
setFont(Font font)
setzt den Font einer Komponente auf font
Color getBackground()
gibt die entsprechenden Werte der Komponente aus
Color getForeground()
Font getFont()
paint(Graphics g)
zeichnet die Komponente
repaint()
veranlasst AWT, die Methode update() der
Komponente (sobald als möglich) aufzurufen
update()
löscht den Bildschirmausschnitt, der der Komponente
entspricht, und ruft dann paint() der Komponente
auf
paint(Graphics g)
zeichnet die Komponente
setVisible(boolean b)
macht entsprechende Komponente unsichtbar oder
sichtbar je nach Wert von b.
5.5
Checkbox (Schalter)
• Bildschirmdarstellung:
• ermöglicht Auswahl aus einer (Check-)Liste
• Eine Checkbox hat einen Zustand kann ausgewählt sein oder nicht
• Erzeugung:
• Instanzierung:
• Checkbox cola = new Checkbox("Cola");
erzeugt eine Checkbox mit Label "Cola"
• Checkbox cola = new Checkbox("Cola",null,true);
erzeugt gleiche Checkbox wie oben, die aber bereits ausgewählt ist
(null ist die zugehörige Checkbox-Gruppe (siehe unten))
• Hinzufügen zu einem Container (z.B. Applet): add(cola);
• Aktuelles Objekt als Listener bei Checkbox cola registrieren:
cola.addItemListener(this);
• Events:
• Eine Checkbox erzeugt ein ItemEvent, jedesmal wenn sie selektiert oder deselektiert wird.
Um diese Events zu bearbeiten, muss das Applet einen ItemListener implementieren.
OOP_V2.7.fm
48
• Dazu muss das Applet die Methode
public void itemStateChanged(ItemEvent e)
zur Verfügung stellen.
• Die Methode e.getSource()gibt sodann, das Checkbox-Objekt zurück, das das Ereignis
erzeugt hat.
• Nützliche Methoden von Checkbox-Objekten:
String getLabel()
gibt Label der Checkbox zurück
boolean getState()
Zustand der Checkbox (selektiert oder nicht)
setLabel(String str)
Setzt Label einer Checkbox auf str
setState(boolean sel)
Selektriert oder deselektiert eine Checkbox gemäss sel
5.6
Checkbox-Group (Schaltergruppe)
• Eine Checkbox-Group ist eine Gruppe von Schaltern
• Von den Checkboxes einer Checkbox-Group kann kann maximal eine eingeschaltet sein.
• Beispiel: Checkbox-Gruppe für eine Farbauswahl:
• Instanzierung:
CheckboxGroup c = new CheckboxGroup();
red = new Checkbox("Rot",c,false);
• Erzeugt eine Schaltergruppe c und einen Schalter red mit dem Label "Rot", der ausgeschaltet
ist.
• Schaltergruppenname c wird nur bei der Erzeugung einer Checkbox gebraucht.
• Event-Handling und Methoden der Checkbox sind genau gleich wie bei einer unabhängigen
Checkbox.
5.7
Choice-Box (Drop-Down-Auswahlliste)
• Bildschirmdarstellung:
• Choice-Box ist eine aufklappbare Liste von Auswahlmöglichkeiten, von denen man eine per
Mausklick auswählen kann.
OOP_V2.7.fm
49
• Deklaration und Erzeugung einer Choice-Box, z.B. für die Auswahl von Farben:
Choice colourChoice = new Choice();
• Der Choice-Box können Auswahl-Items hinzugefügt werden mit der Methode add:
colourChoice.add("Red");
colourChoice.add("Blue");
• Die Choice-Box selbst wird normal wie andere GUI-Komponenten zum Applet hinzugefügt:
add(colourChoice);
• Behandlung der Events:
• Eine Choice-Box erzeugt ein ItemEvent, wenn etwas ausgewählt wird. Das Applet muss deshalb
einen ItemListener implementieren.
• Das Ereignis-Objekt eines ItemEvents stellt folgende Methode zur Verfügung:
Object getSource()
gibt Source-Objekt (z.B. Check-Box) zurück, das
das Ereignis erzeugte
Object getItem()
gibt das Item des Source-Objektes zurück (als
Objekt), das das Ereignis erzeugte
• Beispiel:
if (e.getSource() == colorChoice) {
String aChoice = e.getItem().toString();
}
aChoice wird der String des angewählten Items (z.B. "Red") der
Choicebox colorChoice zugewiesen.
• weitere nützliche Methoden von Choice-Objekten:
int getSelectedIndex()
Index des selektierten Items
String getSelectedItem()
Label des selektieren Items
String getItem(int index)
Label des Items an der Selle index
(1. Item hat index 0)
insert(String str,int index)
Fügt Label str an der Stelle index ein.
remove(int index)
entfernt entsprechendes Item aus der Liste
remove(String str)
removeAll()
entfernt alle Items aus der Liste
select(int index)
selektiert entsprechendes Item der Liste
select(String str)
5.8
Liste
• Liste von Strings zur Auswahl:
• Deklaration und Erzeugung:
• List liste = new List();
Erzeugt eine Liste ohne Mehrfachauswahl. Am Anfang ist kein Item sichtbar.
OOP_V2.7.fm
50
• List liste = new List(5,true)
Erzeugt eine Liste mit 5 sichtbaren Items und Mehrfachauswahl.
• Hinzufügen von Auswahl-Items zur Liste:
liste.add("Rot"); Fügt Menupunkt "Rot" an das Ende der Liste an
• Hinzufügen der Liste, z.B. zum Applet
add(liste);
• Behandlung der Events einer Liste:
• Da eine Liste sowohl Action- als auch Item-Events erzeugt, muss das Applet sowohl
ActionListener als auch ItemListener sein.
• ActionEvent :
wird erzeugt, wenn ein Item doppelgeklickt wird (die anderen Items
bleiben selektiert)
• ItemEvent
:
wird erzeugt, wenn ein Item selektiert oder deselektiert wird
• Bei einer Liste mit Mehrfachauswahl:
• Es braucht einen zusätzlichen Button um anzugeben, wann die Auswahl fertig ist.
• Mit dem Methodenaufruf String[] selection = liste.getSelectedItems()
erhält man sodann einen String-Array mit allen ausgewählten Items zurück.
• Weitere nützliche Methoden eines List-Objektes:
remove(int index)
entfernt Item an der Stelle index
remove(String str)
entfernt Item str aus der Liste
removeAll()
entfernt alle Items aus der Liste
replaceItem(String str,int idx)ersetzt Item idx durch String str
int getSelectedIndex()
gibt Index des ausgewählten Items zurück
int[] getSelectedIndexes() gibt Indices aller ausgewählten Items zurück
String getSelectedItem()
gibt das ausgewählte Item zurück
String[] getSelectedItems()gibt alle ausgewählten Items zurück
select(int index)
selektiert Item index
deselect(int index)
deselektiert Item index
makevisible(index)
schiebt Item index der Liste ins Sichtfenster der
Liste
5.9
Klasse TextComponent
• Die Klasse TextComponent ist die Superklasse der Klassen TextField und TextArea.
• TextComponent ist eine abstrakte Klasse, d.h. es können keine Objekte aus dieser Klasse
erzeugt werden.
• TextComponent definiert aber verschiedene Methoden, die von TextField und TextArea
geerbt werden:
setEditable(boolean b)
macht die Textkomponente editierbar oder nicht, je
nach Wert von b
boolean isEditable()
gibt zurück, ob Textkomponente editierbar ist
String getText()
liest den ganzen Text aus dem Textfeld aus
Zeilenende ist jeweils mit "\n" markiert
setText(String Text)
setzt Text in das Textfeld. Zeilenumbrüche mit
"\n" markieren.
int getSelectionStart() gibt Startindex des selektierten Textes zurück
int getSelectionEnd()
gibt EndIndex des selektierten Textes zurück
String getSelectedText() gibt den selektierten Text selbst zurück
OOP_V2.7.fm
51
select(int startidx ,int endidx) selektiert Text zwischen startidx und endidx
selectAll()
selektiert gesamten Text im Textfeld
addTextListener(Object lis)
registriert ein Objekt als TextListener bei der Textkomponente. Das Listenerobjekt wird informiert, sobald sich der Text in der Textkomponente geändert hat. Der TextListener muss dazu die
Methode public void textValueChanged(TextEvent e) deklarieren.
5.10
Textfeld (TextArea)
• Im Gegensatz zur Textzeile (TextField) ist beim Textfeld (TextArea) die Eingabe mehrerer
Zeilen möglich.
• Ein Textfeld hat 2 Scrollbars.
• Die Klassen TextField und TextArea sind Subklassen der Klasse TextComponent.
• Für die Erzeugung eines Textfeldes gibt es 3 Möglichkeiten:
TextArea t = new TextArea();
TextArea t = new TextArea("1. Zeile\n2.Zeile");
• erzeugt ein Textfeld mit 2 Zeilen. Das Zeilenende wird mit "\n" markiert.
TextArea t = new TextArea(anzahlReihen, anzahlKolonnen);
• Der Text im Textfeld ist mit der Maus editierbar (löschen, einfügen, kopieren).
• Nützliche Methoden (zusätzlich zu denen, die in Klasse TextComponent definiert sind):
insert(String text, int idx) fügt text an der Stelle idx ins Textfeld ein
replaceRange(String text, int startindex, int endindex)
ersetzt Text zwischen startindex und endindex des
Textfeldes durch text
append(String text)
hängt text am Ende des Textfeldes an
5.11
Canvas (Leinwand)
• Bisher zeichneten wir immer direkt ins Applet-Fenster.
• Problem dabei: Die Zeichnung kann durch andere GUI-Komponenten verdeckt werden.
• Eine Canvas ist eine separate Zeichenfläche, die wie andere Komponenten vom Layout-Manager
platziert wird.
• Beispiel: CanvasDemo:
import java.awt.*;
import java.awt.applet.*;
public class CanvasDemo extends Applet{
private MyCanvas canvas = new MyCanvas();
public void init(){
canvas.setBackground(Color.gray);
canvas.setForeground(Color.white);
canvas.setSize(200,100);
add(canvas);
}
}
OOP_V2.7.fm
52
class MyCanvas extends Canvas{
public void paint(Graphics g){
g.drawString("Ich bin in einer Canvas");
}
}
• MyCanvas wird als Subklasse von Canvas deklariert.
• Um etwas zu zeichnen, muss MyCanvas die Methode paint(Graphics g) von Canvas
überschreiben.
• Weitere nützliche Methoden (von Klasse Component geerbt):
paint(Graphics g)
überschreiben, um etwas ins Canvas zu zeichnen
setBackground(Color farbe)
setzt Hintergrund auf Farbe farbe
setSize(int breite, int hoehe)
setzt Canvas-Grösse ( Default-Grösse ist (0,0)! )
repaint()
veranlasst AWT, Canvas neu zu zeichnen
5.12
Klasse Container
• Die Klasse Container ist die Superklasse aller Komponenten, die andere Komponenten
enthalten können (Bsp. Panel). Sie ist zudem die Superklasse aller Swing-Komponenten.
• Sie selbst ist Subklasse der Klasse Component.
• Sie enthält einige nützliche Methoden, die von den Subklassen geerbt werden.
• Die wichtigsten Methoden sind:
setLayout(LayoutManager layout) setzt den Layout-Manager der Komponente
auf layout
add(Component komponente)
fügt die Komponente komponente zur
aktuellen Komponente hinzu
remove(Component komponente)
entfernt Komponente komponente von der
aktuellen Komponente
validate()
Führt einen neuen Layout aus unter
Verwendung des aktuellen Layout-Managers
(nötig, nachdem einzelne Komponenten
unsichtbar/sichtbar gemacht wurden)
5.13
Panel (Schalttafel)
•
•
•
•
Ein Panel ist eine unsichtbare Komponente, die andere Komponenten enthalten kann.
Sie dient normalerweise zur Gruppierung von Komponenten.
Die Grenzen des Panels sind unsichtbar, sie besitzt jedoch eine Hintergrundfarbe.
Die Komponenten innerhalb eines Panels werden gemäss dem entsprechenden Layout-Manager
platziert.
• Die Klasse Panel ist eine Subklasse der Klasse Container.
• Um Komponenten mit einem Panel zu gruppieren, geht man folgendermassen vor:
• Panel erzeugen
• Komponenten zu Panel hinzufügen
• Panel zu Applet oder oderen Panels hinzufügen
OOP_V2.7.fm
53
• Panel erzeugen:
Panel p1 = new Panel()
erzeugt ein Panel mit FlowLayout-Manager
Panel p2 = new Panel(LayoutManager)erzeugt Panel mit LayoutManager
• Komponenten zu den Panels p1 und p2 hinzufügen:
button b1 = new Button("button 1");
button b2 = new Button("button 2");
p1.add(b1);
p1.add(b2);
button b3 = new Button("button 3");
p2.add(b3);
• Panels zu aktuellem Container (z.B. zu Applet) hinzufügen:
add(p1);
add(p2);
5.14
Layout-Managers
• Die Komponenten eines GUI werden normalerweise nicht von Hand ausgelegt, da dies aufwendig
und wenig flexibel wäre.
• Das AWT definiert stattdessen verschiedene Layout-Manager
diese bestimmen, nach welchen Kriterien die Komponenten ausgelegt werden.
• Die zwei wichtigsten Layout-Manager sind Flow-Layout und Border-Layout.
• Flow-Layout:
• Dies ist der Standardlayout für Applets und Panels.
• Die Komponenten werden dabei in einer Reihe von links nach rechts, zentriert zum Fenster,
aufgereiht.
• Die Reihenfolge der Komponenten wird durch die Reihenfolge der add()-Befehle festgelegt.
• Erzeugung:
• FlowLayout layout = new FlowLayout(); FlowLayout, Reihen zentriert
• FlowLayout layout = new FlowLayout(FlowLayout.LEFT);
FlowLayout, Reihen linksbündig
• FlowLayout layout = new FlowLayout(FlowLayout.RIGHT,10,20);
FlowLayout, Reihen rechtsbündig, horizontaler Abstand der Komponenten 10 Pixel,
vertikalerAbstand 20 Pixel
• Layout setzen: setLayout(layout);
• Layout inaktivieren: setLayout(null);
• Border-Layout:
• Dies ist der Standardlayout bei eigenständigen Anwendungen.
• Die Komponenten werden dabei entlang der Fensterseiten angebracht.
• Der Border-Layout teilt das Fenster in 5 Felder ein mit den Namen North, South, East,
West, Center.
• Pro Feld kann nur eine Komponente eingefügt werden. Bei mehreren Komponenten, müssen diese
zuerst in ein Panel gepackt werden.
OOP_V2.7.fm
54
• Die Randfelder des Border-Layout werden zuerst angelegt. Das Feld Center belegt sodann den
verbleibenden Platz in der Mitte. Werden Scrollbars in die Randfelder eingefügt, so füllen sie immer
ganze Seitelänge aus.
• Erzeugung des Border-Layouts:
• BorderLayout layout = new BorderLayout();
Borderlayout ohne Zwischenräume zwischen den Komponenten
• BorderLayout layout = new BorderLayout(10,20);
Borderlayout mit 10 Pixel horizontalem und 20 Pixel vertikalem Zwischenraum zwischen den
Komponenten.
• Layout setzen:
• setLayout(layout); oder direkt
• setLayout(new BorderLayout());
• Komponenten in die Felder einfügen:
• 1. Variante:
Button norden = new Button("button 1");
add("North",norden); // Button in Feld am oberen Rand einfügen
• Alternative (besser):
Button norden = new Button("button 1");
add(norden, BorderLayout.NORTH); // Effekt derselbe wie oben
• Falls Komponenten in den Felder nicht auf die ganze Fläche des Feldes aufgebläht werden sollen
Komponente zuerst in Panel einpacken und dann Panel in Feld einfügen.
• Vorgehen:
– Panel erzeugen
– Komponente (z.B. Button) zu Panel hinzufügen
– Panel zu Applet (oder zu einer anderen Container-Komponente) hinzufügen
5.15
Design von Benützer-Interfaces
• Das Design von ergonomischen Benützer-Interfaces ist eine Wissenschaft für sich.
• Professionelle integrierte Entwicklungsumgebungen (IDE) enthalten graphischen GUI-Builder
• Komponenten können per Maus selektiert und arrangiert werden
• Code dazu wird automatisch erzeugt
• Es gibt zahllose Variationen beim GUI-Designs.
• Empfehlenswert: Einen Prototyp des GUIs erstellen und mit Benützer testen.
OOP_V2.7.fm
55
6
Exceptionhandling (Behandlung von Ausnahmefällen)
6.1
Ziele
•
•
•
•
Sie können das Exceptionhandling in Java in eigenen Worten erklären.
Sie können den Programmablauf bei einer Exception an einem Beispiel erläutern.
Sie können mit Exceptions richtig umgehen.
Sie fangen Exceptions in Ihren Programmen sinnvoll ab.
6.2
Einführung
• Beim Laufenlassen von Progammen entstehen immer wieder Ausnahmesituationen (Exceptions).
• Tragen Sie in Tabelle 2 in der linken Spalte einige Beispiele von Ausnahmesituationen ein:
Ausnahmesituation
Massnahme
Tabelle 2: Mögliche Ausnahmesituationen und geeignete Massnahmen
• Ein Programm kann auf verschiedene Arten auf eine Ausnahmesituation reagieren:
• Nichts tun, einfach weitermachen
• Fehlermeldung an Benützer ausgeben
• Programmabbruch
• Welches ist die vernünftigste Reaktion auf eine Ausnahmesituation?
• Das kann man nicht allgemein sagen. Es kommt auf die Ausnahmesituation an.
• Tragen Sie in Tabelle 2 für jede Ausnahmesituation die Ihrer Meinung nach vernünftigste
Reaktion des Programms ein.
• Ziel der Ausnahmebehandlung
• Behebung des Problems, soweit als möglich
• Programm fortfahren mit möglichst kleinen Verlusten/Einschränkungen.
• Die Ausnahmebehandlung eines Programmes ist meist komplexer als die eigentliche Funktionalität
des Programms!
• Ob ein Fehler lokal innerhalb der Methode behandelt werden kann, ob er weitergegeben werden
muss oder das Programm überhaupt abbrechen muss, das hängt von der Art des Fehlers ab.
OOP_V2.7.fm
56
• Es gibt verschiedene Konzepte für die Behandlung von Ausnahmesituationen:
• Den Fehler lokal behandeln mit Hilfe von if-else-Konstrukten:
doA();
if (doA schiefgegangen){
Problem doA behandeln;
}
else {
doB();
if (doB schiefgegangen){
Problem doB behandeln;
}
else { ... }
• Fehlercodes
• Jede Methode gibt einen Code zurück, der aussagt, ob die Methode erfolgreich war oder nicht
• Probleme bei diesem Ansatz:
– Die Programme werden unübersichtlich, da sich die Fehlerbehandlung mit dem
eigentlichen Programmcode stark vermischt.
– Der Ansatz ist unflexibel, da die Fehlercodes im Vorneherein festgelegt werden müssen.
Die Codes selber sagen zudem meist wenig über den Fehler aus.
– Methoden können nur einen Wert zurückgeben. Dies müsste dann immer der Fehlercode
sein.
• Spezielle Kontrukte für die Fehlerbehandlung
• In Java:
Die Ausnahmebehandlung ist in Java mit speziellen sogenannten Exception-Klassen realisiert.
6.3
Ausnahmebehandlung in Java
Die Ausnahmebehandlung in Java soll anhand des Programmausschnitts auf Seite 58 veranschaulicht
werden:
• Der Programmausschnitt besteht aus den drei Methoden methode1, methode2 und methode3.
Der Programmierer hat keine eigene Behandlung von Ausnahmesituation vorgesehen.
• Falls nun in diesem Programm z.B. in methode3 eine Ausnahmesituation (Exception) auftritt,
läuft Folgendes ab:
• methode3 bricht ab und das Programm kehrt zu methode2 zurück.
• methode2 bricht ebenfalls ab und das Programm kehrt zu methode1 zurück.
• methode1 schliesslich bricht ebenfalls ab und übergibt die Kontrolle dem Java-RuntimeSystem.
• Das Java-Runtime-System gibt schliesslich eine Meldung an den Benützer aus.
OOP_V2.7.fm
57
public void methode1(){
methode2();
}
public void methode2(){
methode3();
}
public void methode3(){
...
}
Programm 8 : Programmablauf bei Exceptions
• Für das Behandeln von Ausnahmesituationen stellt Java das folgende Konzept zur Verfügung:
• Beim Eintreten einer Ausnahmesituation wird eine bestimmte Exception erzeugt.
(thrown = geworfen)
• Eine Exception ist ein Objekt, das Information über den aufgetretenen Fehler enthält.
• Eine Methode kann eine Exception abfangen und behandeln (siehe Figur 16 auf Seite 62).
• Exception sollten grundsätzlich möglichst lokal behandelt werden.
• Falls eine Methode eine Exception selbst nicht behandelt:
• Wirft sie die Exception nach "oben", d.h. sie gibt sie an die aufrufende Methode weiter.
• Die Methode muss dies im ihrem Methodenkopf deklarieren, z.B.:
public void methode3(...) throws XException {
hierdrin kann eine XException auftreten, die von der
Methode selbst nicht behandelt wird.
}
• Abfangen von Exceptions
Für das Abfangen und Behandeln einer Exception ist das try-catch-Konstrukt vorgesehen:
try{
Code, der eine XException erzeugen kann
}
catch (XException e){
Code, der XException behandelt
}
OOP_V2.7.fm
58
• Ablauf des Programms bei einem try-catch-Block:
• Das Programm führt zuerst den try-Block aus.
• Falls keine XException auftritt, fährt das Programm normal nach dem catch-Block
weiter.
• Tritt im try-Block aber eine XException auf, passiert Folgendes:
– Das Programm unterbricht den try-Block sofort.
– Das Programm führt sodann den entsprechenden catch-Block aus.
– Das Programm fährt nachher nach dem catch-Block weiter.
• Beispiel: ExceptionDemo1
Figur 15: Programm ExceptionDemo1
• Bei diesem Programm kann via ein Textfeld eine ganze Zahl eingegeben werden. Das Programm
gibt sodann das Doppelte der eingegebenen Zahl in einem zweiten Textfeld aus.
• Die Methode actionPerformed des Programms sieht folgendermassen aus:
public void actionPerformed(ActionEvent event) {
if (event.getSource() == stringField) {
try{
int number;
number = Integer.parseInt( stringField.getText());
resultField.setText("Doubled value is " + (2*number));
}
catch (NumberFormatException e) {
resultField.setText("Error in number: retype ");
}
catch (ArithmeticException e) {
resultField.setText("Number overflow ");
}
}
}
// if (event.getSource..)
// method actionPerformed
Programm 9 : Auszug aus Programm ExceptionDemo1
OOP_V2.7.fm
59
• Bemerkungen
• Die Methode parseInt kann eine NumberFormatException werfen.
• Tritt kein Fehler im try-Block auf, so fährt das Programm nach der schliessenden
geschweiften Klammer, "}", des letzten catch-Blocks weiter. In diesem Fall ist es das Ende
der actionPerformed-Methode.
• Bei einer Exception, wird derjenige catch-Block ausgeführt, der den entsprechenden
Fehler behandelt.
• Ist kein passender catch-Block vorhanden, wird in der aufrufenden Methoden nach einem
passenden catch-Block gesucht.
• Nach der Behandlung der Exception fährt das Programm nach den catch-Blocks weiter.
Häufig wird die Methode dann beendet, da sie nach einer Exception nicht mehr sinnvoll
weitergeführt werden kann.
• Frage:
Wieso wird im Programm ExceptionDemo1 auf Seite 59 kein "Number overflow"
ausgegeben, wenn die resultierende Integerzahl zu gross wird, obwohl
ArithmeticExceptions abgefangen werden?
• Wann sollen Exceptions verwendet werden:
• Für vorhersehbare Ausnahmesituationen wie z.B. falsche Benützereingaben oder behebbare
Ressourcenprobleme.
• Exceptions sollten nicht für gewollte Ausnahmen eingesetzt werden, z.B., um das Erreichen des
Endes einer Eingabefolge oder einer Datei zu behandeln.
6.4
Exceptions
• Werfen von Exceptions:
Exceptions können mit einer der beiden folgenden Anweisungen erzeugt und auch gerade geworfen
werden:
• throw new ExceptionName();
Wirft eine Exception ExceptionName mit einer Standard-Fehlermeldung. Der ExceptionName ist
der Name einer existierenden Exceptionklasse (siehe nächste Seite).
• throw new ExceptionName("Meldung");
Wirft ebenfalls eine Exception ExceptionName, aber mit einer eigenen Fehlermeldung
"Meldung“. Diese wird im Exceptionobjekt gespeichert. Ein catch-Block erhält das
Exceptionobjekt als Argument und kann die Fehlermeldung mit der Methode getMessage()
abfragen.
OOP_V2.7.fm
60
• Beispiel:
Die folgende Methode verdoppeln(int i)verdoppelt eine ihr übergebene Integer-Zahl i.
Falls das Resultat ausserhalb des Integer-Wertebereiches liegt, wird eine ArithmenticException
geworfen.
private int verdoppeln(int i) throws ArithmeticException{
if (i > Integer.MAX_VALUE/2 || i < Integer.MIN_VALUE/2){
throw new ArithmeticException("Integer-Overflow");
}
return 2*i;
}
• In der actionPerformed-Methode vom vorherigen Beispiel würde die Methode
verdoppeln beispielsweise so aufgerufen:
public void actionPerformed(ActionEvent event) {
if (event.getSource() == stringField) {
try{
int number = Integer.parseInt(stringField.getText());
resultField.setText("Doubled value is " +
verdoppeln(number));
}
catch (NumberFormatException e) {
resultField.setText("Error in number: retype ");
}
catch (ArithmeticException e) {
resultField.setText(e.getMessage());
}
}
}
• Kann eine Methode Exceptions erzeugen, muss sie dies im Methodenkopf deklarieren.
• In der Dokumetation zu den Bibliotheksroutinen stehen neben dem Zweck und den Parametern
der Methode auch die möglichen Exceptions, die von der Methode geworfen werden können.
• Deklaration von Exceptions im Methodenkopf:
Methodenname(Prameterliste) throws Exceptionklassenname {,Exceptionklassenname}
{... }
• Exceptionklassen (siehe Figur • Seite 63)
• In Java gibt es ca. 30 vordefinierte Exception-Klassen, die hierarchisch geordnet sind.
• Möchte ein Programmierer eine Exception werfen, so findet er meistens eine passende
Exceptionklasse unter den bereits vorhandenen.
• Falls er keine findet, muss er eine eigene definieren. Dazu muss er eine bereits bestehende
Exceptionklasse erweitern.
OOP_V2.7.fm
61
• Die Klassenhierarchie der Exceptionklassen ist folgendermassen aufgebaut:
• Die Superklasse aller Exceptionklassen heisst: Throwable
Throwable
Error
(unchecked)
Exception
(checked)
RuntimeException (unchecked)
LinkageError
ArithmeticException
ThreadDeath
VirtualMachineError
IndexOutofBoundsException
ArrayIndexOutofBoundsException
OutOfMemoryError
StringIndexOutofBoundsException
IllegalArgumentException
NumberFormatException
NullPointerException
IOException
FileNotFoundException
MalformedURLException
InterruptedException
weitere Subklassen
Figur 16: Exceptionklassen in Java (Auswahl)
• Sodann gibt es 2 Hauptkategorien:
– Error:
Schwerwiegende Systemfehler, die der Programmierer normalerweise nicht
abfängt.
– Exception:
– Die meisten Subklassen von Exception sind checked, d.h., sie müssen behandelt
werden.
– Einzige Ausnahme bilden die RuntimeExceptions
– Sie können praktisch überall auftreten.
– Sie müssen daher nicht extra deklariert werden.
– Sie müssen nicht in jedem Fall behandelt werden.
– ArithmeticException
wird bei Berechnungsproblemen geworfen (z.B. Division durch 0)
OOP_V2.7.fm
62
– IllegalArgumentException
wird bei falschen oder ungeeigneten Argumentwerten geworfen
– NumberFormatException
wird bei der Umwandlung von einem String in eine Zahl geworfen, falls der String
nicht das geforderte Datenformat aufweist.
• Checked Exceptions
– Das sind Exceptions, die behandelt werden müssen.
– Der Compiler überprüft dies.
– Behandlung dieser Exceptions:
normal mit einem try-catch-Konstrukt.
• Abfangen mehrerer Exceptions:
Auch das Abfangen mehrerer verschiedener Exceptions ist möglich, indem
• mehrere catch-Blöcke hintereinander geschrieben werden, oder
• ein catch-Block für die entsprechende Superklasse der gewünschten Exceptionklassen
angegeben wird.
• Beispiel:
try {...}
catch (IOException e){...} // behandelt alle IOExceptions
catch (Exception e) {...}
// behandelt alle anderen Exceptions
• Beachten Sie:
Die generellere Klasse (Exception) muss hinter den spezielleren (IOException)
stehen.
• Beim Auftreten einer Exception wird der entsprechende catch-Block ausgeführt. Danach fährt
das Programm nach dem letzten der catch-Blöcke weiter. D.h. die nachfolgenden catchBlocks werden übersprungen.
• Aufruf einer Methode, die eine Exception werfen kann:
es gibt 2 Fälle:
1. Methode wirft eine Klasse von Exceptions, die behandelt werden müssen.
Die Exception muss in der aufrufenden Methode behandelt werden, oder es in dessen
Methodenkopfdeklariert sein, dass die Exception weiter nach oben propagiert wird.
2. Methode wirft eine Klasse von Exceptions, die nicht behandelt werden müssen.
(Exception ist zum Teil auch nicht deklariert wie z.B. ArithmeticException)
Die Exception muss weder behandelt noch im Methodenkopf deklariert werden.
Wird die Exception nicht behandelt, wird sie einfach nach oben propagiert.
• Sichbarkeitsbereiche von Variablen:
• Variablen die im try-Block definiert werden, sind im catch-Block nicht sichtbar.
• Abhilfe: Variablen, die in beiden Blöcken gebraucht werden, sind deshalb ausserhalb des tryBlocks deklarieren.
• Suche nach einem passenden catch-Block
• Falls in methode3 im Programm ExceptionDemo1 auf Seite 58 eine Exception auftritt,
sucht das Progamm nach einem passenden catch-Block in der Reihenfolge methode3,
methode2, methode1.
OOP_V2.7.fm
63
• Falls nirgends ein passender catch-Block gefunden wird:
• Das Java-System gibt eine Fehlermeldung aus. Diese macht Angaben über:
– Die Art der Exception.
– Die Hierarchie der aufgerufenen Methoden zum Zeitpunkt des Auftretens der Exception.
– Die Zeilennummer, wo die Exception aufgetreten ist, für jede der beteiligten Methoden
(falls ensprechende Information verfügbar ist).
• Das Applet läuft danach weiter, aber die Resultate sind nicht mehr verlässlich.
• finally-Block:
Das try-catch-Konstrukt kann optional um einen sogenannten finally-Block ergänzt
werden.
• Das Kontrukt sieht dann so aus:
try {...}
catch (Exceptionklassenname variablenname){...}
catch (Exceptionklassenname variablenname){...}
...
finally {
finally-Block: wird in jedem Fall ausgeführt.
}
• Der Code des finally-Blocks wird in jedem Fall ausgeführt, d.h. sowohl bei normalem
Ablauf als auch bei einer Exception. Dieser Block wird auch ausgeführt, falls im try-Block die
Methode mit einer return-Anweisung verlassen wird.
• Der finally-Block wird vor allem verwendet für:
• Schliessen von Files
• Freigeben von Ressourcen
• Anderer Code, der in jedem Fall ausgeführt werden muss.
• Aufgaben:
• Aufgabe 1:
Schreiben Sie eine Methode liesZiffer(String str), die prüft, ob der String str eine
Ziffer zwischen 0..9 darstellt. Falls ja, soll die Methode die Ziffer als Integer zurückgeben. Falls
nicht, soll die Methode eine IllegalArgumentException mit einer entsprechenden
Meldung erzeugen.
• Aufgabe 2 (für Fortgeschrittene):
Schreiben Sie eine actionPerformed-Methode, die ein Textfeld einliest, den String an die
Methode liesZiffer übergibt und das Resultat mit showStatus anzeigt. Die Methode soll
eventuelle Exceptions abfangen und eine entsprechende Meldung ausgeben.
OOP_V2.7.fm
64
• Lösungen:
OOP_V2.7.fm
65
OOP_V2.7.fm
66
6.5
Beispiele für Ausnahmebehandlungen
Im Folgenden wird an ein paar Fällen gezeigt, wie Ausnahmebedingungen sinnvoll abgefangen werden
können.
• Umwandlung eines Strings in eine Integer-Zahl
• Die dafür meistens verwendete Methode parseInt kann eine NumberFormatException
erzeugen.
• Diese Exception sollte möglichst lokal behandelt werden.
• Nach oben propagieren bringt hier nicht viel, da die unterste Methode am besten weiss, was zu
tun ist.
• Index eines Arrays liegt ausserhalb der erlaubten Grenzen
• Falls bei einem Array ein Index ausserhalb der erlaubten Arraygrenzen liegt, so wird eine
ArrayIndexOutOfBoundsException geworfen.
• Diese Exception sollte im Normalfall nicht abgefangen werden, damit der Ort im Programm, wo
der Fehler aufgetreten ist, anhand der vom System erzeugten Fehlermeldung schnell lokalisiert
werden kann. Würde die Exception abgefangen, so wird keine Fehlermeldung mehr erzeugt.
• Öffnen einer Datei
• Wie wir in einem späteren Kapitel sehen werden, müss Dateien geöffnet werden, bevor man
daraus etwas lesen kann
• Falls die Datei, die man öffnen möchte, nicht existiert, so wird eine sogenannte
FileNotFoundException geworfen.
• Es gibt 2 Varianten für die Behandlung dieser Ausnahmebedingung:
• Exception wird abgefangen
try{
\\ Versuche Datei zu oeffnen
inFile = new BufferedReader(new FileReader("meinFile.txt"));
}
catch (FileNotFoundException e){ \\ Falls Datei nicht gefunden
errorField.setText("Datei nicht gefunden")
}
• Exception wird nach oben propagiert
private void openFile() throws IOException{
inFile = new BufferedReader(new FileReader("meinFile.txt"));
}
OOP_V2.7.fm
67
7
Eigenständige Programme
7.1
Ziele
•
•
•
•
Sie können Applikationen in Java schreiben.
Sie können die wichtigsten Unterschiede zwischen einem Applet und einer Applikation erklären.
Sie können ein gegebenes Applet in eine eigenständige Applikation umwandeln und umgekehrt.
Sie setzen Menus in Ihren Applikationen richtig und situationsgerecht ein.
7.2
Einführung
• Bisher haben wir ausschliesslich Applets programmiert.
• Applets
• müssen in einer HTML-Seite eingebettet sein und benötigen einer Browser, um lauffähig zu sein:
public class MeinProgramm extends
Applet{
Eingabe/Ausgabe
JVM
public void init(){...}
Browser
public void paint(Graphics g)
{...}
Betriebssystem
Browser
HTML-Seite
.... ... ... .. . .. . ... .. .
... .. .. .
Applet
ok
public void actionPerformed
(ActionEvent e){...}
}
Figur 17: Ablauf eines Applets
• Der Browser ruft nach dem Aufstarten des Applets dessen init()-Methode auf.
• Bei Bedarf ruft der Browser sodann die Methoden paint(Graphics g) und allfällige
Listenermethoden (z.B. actionPerformed(...) ) auf.
• Applikationen
• sind eigenständige Programme. Sie brauchen keinen Browser.
• Sie laufen vollständig unabhängig von anderen Programmen.
• Applikationen werden gestartet durch Anklicken oder einen entsprechenden Befehl auf der
Kommandozeile.
• Applikationen können normalerweise nicht über das Internet gestartet werden.
• Sie können ebenfalls die AWT-Bibliothek benützen (ausser Applet-Klassen).
• Applikationen haben mehr Rechte bezüglich Zugriff auf lokale Ressourcen (Dateien).
OOP_V2.7.fm
68
7.3
Applikationen in Java
• Applikation mit Ein-/Ausgabe über die Kommandozeile:
Eingabe/Ausgabe
JVM
public class Gruss{
public static void main(String[] args)
{...}
Betriebssystem
}
Kommandozeile
> java Gruss Peter
Hallo Peter
>
Figur 18: Ablauf einer Kommandozeilen-Applikation
• Einfaches Beispiel:
public class Gruss {
public static void main(String[] args){
System.out.println("Hallo " + args[0]);
}
}
Programm 10 : Kommandozeilen-Applikation Gruss
• Jede Applikation muss eine Klassenmethode mit der Signatur:
public static void main(String[] args) { ... }
enthalten. Diese Methode ist der Startpunkt jeder Applikation.
• String[] args ist ein String-Array, der allfällige Argumente von der Kommandozeile
enthält. Die Arraygrösse entspricht genau der Anzahl übergebener Argumente.
• Das Programm Gruss kann nun von der Kommandozeile aus ausgeführt werden mit dem
Befehl:
java Gruss
• java ruft den Java-Interpreter auf und übergibt ihm als Argument den Namen des
auszuführenden Programms. Der Java-Interpreter interpretiert sodann den Byte-Code des
Programms Gruss.
• Die Ausführung des Programms Gruss beginnt mit der main-Methode der Klasse Gruss.
• Allgemein wird eine Kommandozeilen-Applikation in Java so aufgerufen:
java Klassenname { Argument }
OOP_V2.7.fm
69
– Klassenname
– { Argument }
Name des Programms (= Name der public-Klasse)
Argumente. Diese werden im String-Array args der Methode
main übergeben.
– Die Anzahl der übergebenen Argumente steht in der Variablen args.length
• Aufgabe:
Schreiben Sie ein Echo-Programm, das die Eingaben auf der Kommandozeile auf den
Bildschirm schreibt. Der folgende Aufruf des Programms Echo:
java Echo Das ist ein Test
sollte als Resultat folgenden Output liefern:
Das
ist
ein
Test
• Lösung:
• Die main-Methode
• Sie entspricht dem Hauptprogramm in anderen Programmiersprachen.
• Der Programmablauf beginnt immer mit der main-Methode.
• Die main-Methode kann weitere static-Methoden direkt aufrufen.
• Sie kann zudem beliebige Objekte erzeugen.
• Als erstes erzeugt die main-Methode normalerweise ein Objekt der ganzen Applikation, d.h.
ein Objekt derjenigen public-Klasse, in der die main-Methode selbst enthalten ist.
• Dann ruft die main-Methode eine Methode des erzeugten Applikations-Objektes auf.
• Bei Applikationen, die keine eigene Benützeroberfläche erzeugen, übernimmt die mainMethode die Steuerung des gesamten Programmablaufs und wird erst verlassen, wenn alle
Arbeiten im Programm beendet sind.
• Bei Applikationen mit eigener grafischer Benützeroberfläche spielt die main-Methode nur
eine temporäre Rolle. Nachdem sie die nötigen Objekte der Applikation erzeugt und die
notwendigen Methoden aufgerufen hat, wird sie beendet. Der Programmablauf wird sodann
von den erzeugten Objekten gesteuert.
OOP_V2.7.fm
70
• Ein Typischer Aufbau einer Applikation sieht somit folgendermassen aus:
public class Applikation {
public static void main(String[] args) {
Applikation a = new Applikation();
a.tuEtwas();
}
private void tuEtwas() {
...
}
}
• Ablauf des Programms:
• Java-Interpreter ruft die main-Methode der Klasse Applikation auf.
• main erzeugt eine Instanz der Klasse Applikation
• main ruft sodann eine Methode im erzeugten Objekt auf.
• Zur Erinnerung:
Bei einem Applet wird das erste Objekt (das Applet-Objekt) direkt vom Browser erzeugt.
7.4
Applikationen mit eigener graphischer Benützeroberfläche (GUI)
• Java-Applikationen können ebenfalls das Abstract Window Toolkit (AWT) verwenden.
• Dazu muss die Applikation zuerst ein eigenes Fenster erzeugen. Klasse Frame.
JVM
public class MeinProgramm extends Frame
implements ActionListener{
public static void main(String[] args){
MeinProgramm p = new MeinProgramm();
}
Betriebssystem
Eingabe/Ausgabe
MeinProgramm
ok
public void paint(Graphics g){...}
public void actionPerformed(ActionEvent e){
...
}
}
• Klasse Frame
• Verwaltet ein Fenster mit Scrollbars und Menubalken.
• Klassenhierarchie:
Aus der Klassendiagramm der GUI-Komponenten auf Seite 47 sieht man, dass die Klasse
Frame von den Klassen Component-Container-Window abgeleitet ist.
• Ein Frame kann sogenannte WindowEvents erzeugen.
• Um diese zu behandeln, muss eine Applikation einen WindowListener implementieren.
OOP_V2.7.fm
71
Dazu muss sie die folgenden 7 Methoden deklarieren:
• public void windowOpened(WindowEvent event)
wird aufgerufen, nachdem das Fenster das erste Mal sichtbar geworden ist.
• public void windowClosing(WindowEvent event)
wird aufgerufen, wenn der Benützer das Fenster schliessen möchte.
• public void windowClosed(WindowEvent event)
wird aufgerufen, sobald Fenster effektiv geschlossen ist.
• public void windowIconified(WindowEvent event)
wird aufgerufen, nachdem das Fenster zu einem Icon verkleinert worden ist.
• public void windowDeiconified(WindowEvent event)
wird aufgerufen, sobald der Benützer das Icon wieder geöffnet hat.
• public void windowActivated(WindowEvent event)
wird aufgerufen, sobald das Fenster aktiv ist, d.h. den Fokus erhält.
• public void windowDeactivated(WindowEvent event)
wird aufgerufen, sobald Fenster inaktiv geworden ist, d.h. den Fokus abgegeben hat.
• Konstruktoren:
Frame()
Frame(String Fenstertitel)
erzeugt ein Fenster ohne Titel
erzeugt ein Fenster mit Titel
• Wichtige Methoden (zum Teil von Klasse Window geerbt)
setSize(int breite, int hoehe)
setzt Fenstergrösse
show()
macht Fenster sichtbar
setVisible(boolean b)
macht Fenster un-/sichtbar
toBack()
schiebt Fenster nach hinten
toFront()
schiebt Fenster nach vorne
pack()
setzt Fenster auf bevorzugte Grösse
setTitle(String titel)
setzt Fenstertitel
setMenuBar(MenuBar menuleiste)
setzt neue Menuleiste des Fensters
• Beispiel:
Das Programm PlayBalloon soll als eigenständige Applikation programmiert werden (siehe
Programm 11 Seite 75):
• Wir gehen aus vom PlayBalloon-Applet auf Seite 10.
• Will man PlayBalloon als eigenständige Applikation programmieren, muss Folgendes am
Applet-Code geändert werden:
• PlayBalloon kann nicht mehr von Applet abgeleitet werden
Das hat zur Folge, dass das Applet-Fenster nicht mehr zur Verfügung steht.
• Die Applikation muss deshalb ein eigenes Fenster erzeugen. Dies erreicht man wie folgt:
– Die Applikation wird von der Klasse Frame anstatt Applet abgeleitet.
– Die Applikation muss zusätzlich einen WindowListener implementieren.
– Die Applikation braucht zusätzlich die Methode public static main(String[]
args), die Folgendes macht:
– Sie erzeugt zuerst ein PlayBalloon-Objekt , und damit ein neues Frame.
– Sie setzt die Grösse des Frames.
– Sie macht das Frame sichtbar.
• Die init()-Methode wird durch den Konstruktor PlayBalloon ersetzt. Dieser wird
beim Erzeugen eines PlayBalloon-Objekts aufgerufen.
OOP_V2.7.fm
72
• Im neuen Konstruktor wird zusätzlich zur früheren init()-Methode:
– der Frame-Titel gesetzt mit setTitle(titel)
– der Layout-Manager bestimmt mit setLayout(layout)
– das MyBalloon-Objekt als WindowListener bei sich selbst registriert
• Als WindowListener muss die Applikation 7 WindowListener-Methoden deklarieren.
• Als einzige der WindowListener-Methoden wird windowClosing implementiert:
In ihr wird das Programm beendet mit dem Befehl: System.exit(0);
• Die restlichen WindowListener-Methode bleiben leer.
• Beachten Sie:
Klasse Balloon bleibt bei der Umstellung vom Applet zur Applikation unberührt!
• Das PlayBalloon-Programm als eigenständige Applikation:
import java.awt.*;
import java.awt.event.*;
public class PlayBalloon extends Frame implements ActionListener,
WindowListener {
private Button grow, shrink;
private Balloon myBalloon;
public static void main(String[] args) {
PlayBalloon f = new PlayBalloon();
f.setSize(300,300);
f.setVisible(true);
}
public
PlayBalloon() {
setTitle("Balloon");
setLayout(new FlowLayout());
grow = new Button("Grow");
add(grow);
grow.addActionListener(this);
shrink = new Button("Shrink");
add(shrink);
shrink.addActionListener(this);
myBalloon = new Balloon(20, 50, 50);
this.addWindowListener(this);
}
OOP_V2.7.fm
73
public void actionPerformed (ActionEvent event) {
if (event.getSource() == grow){
myBalloon.changeSize(10);
}
if (event.getSource() == shrink){
myBalloon.changeSize(-10);
}
repaint();
}
public void windowClosing(WindowEvent event) {
System.exit(0);
}
public void windowIconified(WindowEvent event) { }
public void windowOpened(WindowEvent event) { }
public void windowClosed(WindowEvent event) { }
public void windowActivated(WindowEvent event) { }
public void windowDeiconified(WindowEvent event) { }
public void windowDeactivated(WindowEvent event) { }
public void paint (Graphics g) {
myBalloon.display(g);
}
}
class Balloon {
private int diameter, xCoord, yCoord;
public Balloon (int initialDiameter, int initialX, int initialY){
diameter = initialDiameter;
xCoord = initialX;
yCoord = initialY;
}
OOP_V2.7.fm
74
public void changeSize (int change) {
diameter = diameter+change;
}
public void display (Graphics g) {
g.drawOval (xCoord, yCoord, diameter, diameter);
}
}
Programm 11 : PlayBalloon als Applikation
7.5
Kochbuchrezept für die Umwandlung Applets zu Applikationen
• Umwandlung Java-Applet zu einer Java-Applikation:
• Methode init() in einen Konstruktor (d.h. gleicher Name wie Klasse) ohne Parameter
umwandeln. Nicht vergessen: Returntyp bei Konstruktor entfernen.
• extends Applet durch extends Frame ersetzen
• Main-Methode schreiben: public static void main(String[] args)
• muss static sein
• muss ein Objekt der eigenen Klasse (d.h. des Programms) erzeugen
damit wird das entsprechende Frame wird erzeugt
• muss Grösse der Frames setzen mit setSize(breite, hoehe);
• muss Frame sichtbar machen mit setVisible(true);
• Import der Klasse Applet löschen
• WindowListener implementieren und Applikation bei sich selbst als WindowListener
registieren mit this.addWindowListener(this);
• Layout-Manager auf FlowLayout ändern, falls gewünscht (Default-Layout für Frames ist
BorderLayout) mit setLayout(new FlowLayout());
• Kontrollieren, ob keine Methoden der Applet-Klasse mehr verwendet werden.
• Umwandlung einer Java-Applikation in ein Java-Applet:
• HTML-Seite mit applet-Tag erzeugen.
• main-Methode löschen
• extends Frame durch extends Applet ersetzen
• Klasse Applet importieren
• Konstruktor umbenennen in public void init()
• Layout-Manager auf Border-Layout setzen, falls gewünscht (Default-Layout für Applets ist
FlowLayout)
7.6
Menus
• Applikationen können im Gegensatz zu Applets neben den anderen GUI-Komponenten auch Menus
enthalten.
• Dazu braucht es eine sogenannte Menuleiste (Menubar). Das ist eine Leiste, an der die Menus
aufgehängt sind.
• Menu-Item: So heissen die einzelnen Einträge eines Menus (können selbst wieder Untermenus
sein)
OOP_V2.7.fm
75
• Klassenhierarchie
MenuComponent
MenuItem
Menu
MenuBar
Figur 19: Menu-Klassenhierarchie
• Klasse MenuComponent
• Sie ist die Superklasse aller Menu-Klassen, analog wie die Klasse Component Superklasse zu
allen Klassen der GUI-Komponenten ist.
• Klasse MenuItem
• erzeugt ein ActionEvent, falls das betroffene MenuItem ausgewählt wird und aktiv ist.
• Ein MenuItem-Listener muss nicht bei jedem MenuItem als Listener registriert sein.
• Er kann auch nur als Listener beim Menu selbst oder einem Submenu registriert sein. Die
ActionEvents eines Items werden nämlich zum darüber liegenden Menu propagiert.
• Konstruktoren
MenuItem(String label)
erzeugt eine Menu-Item label
MenuItem(String label, MenuShortCut shortcut)
erzeugt ein Menu-Item label mit der zugeordneten Tastenkombination shortcut.
• Nützliche Methoden
boolean isEnabled()
gibt zurück, ob Menu-Item aktiv ist
setEnabled(boolean b)
Aktiviert oder deaktiviert ein Menu-Item
String getLabel()
gibt Label des Menu-Items zurück
setLabel(String name)
setzt Label des Menu-Items auf name
addActionListener(ActionListener o)registriert Object o als Listener
setActionCommand(String command) setzt Befehlsname des Menu-Items auf
command (dieser kann aus dem ActionEventObjekt mit getActionCommand() ausge
lesen werden)
MenuShortcut getShortcut()
gibt Shortcut des Menu-Items zurück
setShortcut(MenuShortcut c)
setzt Shortcut des Menu-Items auf c
• Klasse Menu
• ist eine Subklasse von MenuItem
• Konstruktor:
Menu(String label)
erzeugt Menu mit Titel label
• Nützliche Methoden
add(String label)
erzeugt MenuItem label und fügt es ansEnde
des Menus an
MenuItem add(MenuItem item)
fügt Menu Item item zum Menu
(item wird zudem zurückgegeben)
OOP_V2.7.fm
76
addSeparator()
insert(MenuItem item, int pos)
insertSeparator(int pos)
fügt einen Separator ans Menu an
fügt Item item an der Stelle pos ins Menu ein
fügt Separator an der Stelle pos ein
• Beispiel: Programm MenuBalloon
Figur 20: Menu-Leiste des Programms MenuBalloon
Der Konstruktor im Programm 12, MenuBalloon, erzeugt für die PlayBalloon-Applikation eine
Menuleiste mit den Menus Size und Move wie in Figur 20 abgebildet. Das Menu Size hat die Einträge Grow und Shrink, Move die Einträge Left und Right. (Es wird angenommen, dass die
Klasse Balloon zusätzlich die beiden Methoden moveLeft() und moveRight() implementiert):
public class MenuBallon extends Frame implements ActionListener,
WindowListener {
private Balloon myBalloon;
private
MenuItem growItem, shrinkItem;
private
MenuItem leftItem, rightItem;
public static void main(String[] args) {
MenuBalloon f = new MenuBalloon();
f.setSize(300,300);
f.setVisible(true);
}
public
MenuBalloon() {
setTitle("Balloon");
setLayout(new FlowLayout());
MenuBar menuBar = new MenuBar();
Menu sizeMenu = new Menu("Size");
growItem = new MenuItem("Grow");
sizeMenu.add(growItem);
growItem.addActionListener(this);
shrinkItem = new MenuItem("Shrink");
OOP_V2.7.fm
77
sizeMenu.add(shrinkItem);
shrinkItem.addActionListener(this);
menuBar.add(sizeMenu);
Menu moveMenu = new Menu("Move");
leftItem = new MenuItem("Left");
moveMenu.add(leftItem);
leftItem.addActionListener(this);
moveMenu.addSeparator();
rightItem = new MenuItem("Right");
moveMenu.add(rightItem);
rightItem.addActionListener(this);
menuBar.add(moveMenu);
setMenuBar(menuBar);
myBalloon = new Balloon(20, 50, 50);
this.addWindowListener(this);
}
public void actionPerformed (ActionEvent event) {
if (event.getSource() == growItem){
myBalloon.changeSize(10);
}
if (event.getSource() == shrinkItem){
myBalloon.changeSize(-10);
}
if (event.getSource() == leftItem){
myBalloon.moveLeft();
}
if (event.getSource() == rightItem){
myBalloon.moveRight();
}
repaint();
}
Programm 12 : Applikation MenuBalloon
OOP_V2.7.fm
78
5 Schritte, um eine Menuleiste zu erzeugen:
1. Neue Menuleiste erzeugen: MenuBar menuBarName = new MenuBar();
2. Neues Menu erzeugen:
Menu menuName = new Menu(menuTitel);
3. Neuen Menueintrag erzeugen und ins Menu einfügen:
• MenuItem einItem = new MenuItem(itemName)
• menuName.add(einItem);
• Objekt als Listener des entsprechenden Menu-Item-Events anmelden:
einItem.addActionListener(this);
Schritt 3 für alle MenuItems wiederholen
4. Menu zu Menuleiste hinzufügen: menuBarName.add(menuName);
Schritte 2–4 für alle Menus wiederholen
5. MenuLeiste in Frame einfügen: setMenuBar(menuBarName);
OOP_V2.7.fm
79
8
Arbeiten mit Dateien
8.1
Ziele
•
•
•
•
•
Sie können einem Laien erklären, was eine Datei ist.
Sie unterscheiden zwei Zugriffsarten auf eine Datei.
Sie können das prinzipielle Vorgehen bei einem File-Zugriff in Java erläutern.
Sie kennen mindestens eine Möglichkeit, Textdateien zu lesen und zu schreiben.
Sie können mit Files und Directories in Ihren Programmen umgehen.
8.2
Einführung
• Daten im Arbeitsspeicher (RAM) gehen beim Ausschalten des Computers verloren.
• Es braucht deshalb eine Möglichkeit, um diese Daten permanent zu speichern.
Zusammengehörige Daten werden zu Dateien (Files) zusammengefasst und als eine Einheit
behandelt.
• Datei (File)
• Eine Datei ist nichts anderes als eine Folge von Bytes.
• Zusammengehörige Daten werden zu Dateien zusammengefasst, um sie effizient auf permanente
Speichermedien ablegen und wieder lesen zu können. ( Magnetspeicher)
• Es können beliebige Arten von Daten gespeichert werden:
• Text, Zahlen
• Bilder, Video, Sound
• Programme, Objekte
• Die Interpretation der Byte-Folge in einer Datei ist Sache des entsprechenden Programms.
8.3
Zugriff auf Dateien
• Es bestehen grundsätzlich 2 Möglichkeiten, um auf Dateien zuzugreifen:
• sequentieller Zugriff:
• Die Daten werden wie bei einem Tonband von vorne beginnend nacheinander eingelesen.
• Dieser Zugriff wird auch als Stream-Zugriff bezeichnet.
• Dies ist die häufigste Art, auf Dateien zuzugreifen.
• Zugriff in beliebiger Reihenfolge, wie z.B. bei Arbeitsspeicher oder einer CD:
• Dieser Zugriff wird auch als Random-Zugriff bezeichnet.
• Random-Zugriff auf Dateien wird nur in speziellen Fällen benötigt, wie z.B. bei
Datenbanken.
• Zugriffsrechte:
• Normalerweise hat der Benützer uneingeschränkte Zugriffsrechte auf seine Dateien (Lesen,
Schreiben, Umbenennen, Löschen, Ausführen).
• Die Zugriffsrechte können aber vom Besitzer der Datei eingeschränkt werden.
• Applets:
• Applets erhalten normalerweise vom Browser nur sehr beschränkte Zugriffsrechte auf Files
des Computers, auf dem das Applet läuft (aus Sicherheitsgründen).
• Signierte Applets oder mit einer entsprechend eingestellten Security-Policy können Applets
jedoch auch auf Files zugreifen.
• Zugriffe auf Files des Servers, von dem das Applet stammt, sind jedoch erlaubt.
OOP_V2.7.fm
80
• Dateien in Java
• Es existieren über 50 verschiedene Klassen für die verschiedenen Arten von Filezugriffen.
• Die Hierarchie dieser Klassen ist jedoch gut strukturiert und regulär aufgebaut.
8.4
Sequentieller Filezugriff: Streams
• Sequentieller Zugriff auf Dateien
• Beim sequentiellen Zugriff wird ein Byte nach dem anderen von vorne beginnend gelesen bzw.
geschrieben.
• Dadurch entsteht beim Lesen und Schreiben ein Datenstrom Stream
• Einen solchen Stream erhält man auch, wenn Daten statt von einem Speichermedium über eine
Kommunikationsleitung übertragen werden.
• Da das Lesen einzelner Bytes von einem Speichermedium sehr langsam im Vergleich zum
Zugriff auf den Arbeitsspeicher ist, werden normalerweise nur ganze Blöcke von Daten vom
Speichermedium gelesen bzw. darauf geschrieben. Die Daten werden dazu im Arbeitsspeicher
zwischengelagert (gepuffert).
• Ablauf beim Lesen eines Files:
• File öffnen
• Daten nacheinander einlesen und intern abspeichern
• File schliessen
• Ablauf beim Schreiben eines Files:
• File öffnen
• Daten nacheinander auf das File schreiben
• File schliessen
8.4.1
Direkter (byte-weiser) sequentieller Zugriff auf eine Datei
Direkter Lesezugriff
Datei
Direkter Schreibzugriff
Datei
Arbeitsspeicher
Arbeitsspeicher
Figur 21: Direkter sequentieller Zugriff auf eine Datei
OOP_V2.7.fm
81
• Vorteil des direkten byte-weisen Zugriffs ist die maximale Kontrolle, die der Programmierer über
den Dateizugriff hat, da die Daten ohne Verzögerung auf die Datei geschrieben bzw. von der Datei
gelesen werden.
• Die Nachteile sind zum einen, dass dieser Zugriff langsam und umständlich ist. Zudem wird
während des Dateizugriffs die Applikation jedesmal blockiert, bis das Byte gelesen ist.
8.4.2
Gepufferter (blockweiser) Zugriff auf Datei
Gepufferter Lesezugriff
Datei
Gepufferter Schreibzugriff
Datei
Block
Block
Arbeitsspeicher
Arbeitsspeicher
• Ablauf beim Lesen:
• Die Applikation wartet, bis der ganze Puffer gefüllt ist mit Daten von der Datei. Das Füllen des
Puffers ist langsam, da es von der Lesegeschwindigkeit des Speichermediums abhängt. Die
Applikation kann in der Zwischenzeit aber etwas anderes machen.
• Die Applikation liest den Puffer (Block) als Ganzes ein. Dieser Vorgang ist sehr schnell, da es
sich im Wesentlichen um das Umkopieren von Daten im Arbeitsspeicher handelt.
• Ablauf beim Schreiben:
• Applikation schreibt einen Block Daten in den Puffer.
• Von dort werden die Daten byteweise auf die Datei geschrieben.
• Während dieser Zeit kann die Applikation wiederum weiterarbeiten.
• Vorteile:
• Die Applikation ist weniger blockiert durch den Dateizugriff.
• Das blockweise Lesen und Schreiben ermöglicht komfortables Lesen und Schreiben grösserer
Einheiten (ganze Zahl, ganze Zeile, etc.)
• Nachteile:
• Die Applikation erhält die Daten normalerweise erst, wenn der Puffer gefüllt ist bzw. die Daten
werden normalerweise erst auf die Datei geschrieben, wenn der Puffer voll ist.
• Auch wenn die Applikation nur ein Byte braucht, wird ein ganzer Block von der Datei gelesen.
OOP_V2.7.fm
82
8.5
Java IO-Klassen
• Die folgende Liste zeigt die wichtigsten Klassen für das Lesen und Schreiben von Daten auf
Dateien und andere Medien (Bildschirm, Tastatur, Übertragungskanäle) in der Klassenhierarchie:
InputStream
Lesen von beliebigen Daten (Byte-Streams)
- FileIntputStream
Lesen beliebiger Daten von Files
OutputStream
- FileOutputStream
Schreiben beliebiger Daten (Byte-Stream)
Schreiben beliebiger Daten auf Files
Reader
Lesen von Character-Streams (Zeichenströme)
(besitzt analoge Subklassen wie InputStream)
gepuffertes Lesen, ermöglicht auch zeilenweises Lesen
liest Bytes und wandelt sie in Characters um
liest Character-Streams von einem File
- BufferedReader
- InputStreamReader
- FileReader
Writer
- PrintWriter
- OutputStreamWriter
- FileWriter
8.5.1
Schreiben von Character-Streams
(analoge Subklassen zu OutputStream)
schreibt beliebige Java-Objekte als Strings einen OutputStream
wandelt Characters in Bytes und gibt sie aus
schreibt Character-Streams auf ein File.
Erklärungen zu den einzelnen Klassen für das Lesen / Schreiben von Daten:
• FileInputStream, FileOutputStream
Datei
Datei
FileInputStream
Bytes
FileOutputStream
Bytes
read()
write()
Mit diesen Klassen werden einzelne Bytes von einer Datei gelesen (mit der Methode read())
oder geschrieben (mit write()).
OOP_V2.7.fm
83
• InputStreamReader, OutputStreamWriter
• Mit diesen Klassen können Byte-Streams in Character-Streams umgewandelt werden.
• Die bei der Umwandlung verwendete Codierung kann gewählt werden.
• Um Character-Streams von einer Datei zu lesen, müssen diese zuerst mit einem
FileInputStream gelesen werden:
Datei
Datei
FileInputStream
Bytes
FileOutputStream
Bytes
InputStreamReader
Chars
OutputStreamWriter
Chars
read()
write()
Figur 22: Byteweises Lesen und Schreiben einer Datei mit Umwandlung
des Byte-Streams in einen Character-Stream und umgekehrt.
• Diese Klassen werden auch verwendet, um auf andere Ein-/Ausgabe-Medien zuzugreifen, z.B.
Tastatur, Bildschirm oder Internet (lesen und schreiben von Web-Seiten).
8.5.2
Lesen und Schreiben von Textdateien
• Die häufigste Art von Dateien, die gelesen und geschrieben werden, sind Textdateien.
• Diese werden meistens zeilenweise gelesen/geschrieben.
• Aus diesem Grund gibt es spezielle Klassen, um einfach auf solche Textdateien zuzugreifen
:
• Klasse FileReader
liest einzelne Characters von einer Datei. Dabei wird die Standard-Codierung für die
Umwandlung von Bytes zu Characters verwendet.
• Klasse BufferedReader
Liest Characters aus einer Textdatei einzeln oder zeilenweise:
– read()
liest nächsten Character aus der Datei.
– readLine()
liest nächste Zeile aus Datei.
• Klasse FileWriter
schreibt einzelne Characters in eine Datei. Dabei wird die Standard-Codierung für die
Umwandlung von Characters zu Bytes verwendet.
OOP_V2.7.fm
84
• Klasse PrintWriter
Schreibt Characters in eine Textdatei, einzeln oder zeilenweise:
– print(String str)
hängt String str ans Ende der Datei
– println(String str)
hängt String str ans Ende der Datei, und fügt eine
Zeilenendemarke ('\n') an.
– analoge print-Methoden existieren für int-, float- , ..., double-Zahlen und
ganze Objekte.
• Direktes Lesen und Schreiben von Textdateien
• Textdateien können direkt zeichenweise gelesen und beschrieben werden mit den Klassen
FileReader und FileWriter:
FileReader
Datei
FileWriter
Datei
Bytes
Bytes
FileReader
Chars
FileWriter
Chars
read()
write()
Figur 23: Zeichenweises Lesen und Schreiben einer Datei
• Gepuffertes Lesen und Schreiben von Textdateien ermöglichen die Klassen BufferedReader
und PrintWriter:
BufferedReader
PrintWriter
Datei
Datei
Bytes
Bytes
FileReader
Chars
FileWriter
Chars
BufferedReader
PrintWriter
Chars
Chars
read()
readln()
print("Hello")
print(5)
print(true)
Figur 24: Gepuffertes Lesen und Schreiben von Textdateien
OOP_V2.7.fm
85
• Beachten Sie:
Viele Methoden, die Streams bearbeiten, können IOExceptions werfen.
Diese sind checked, d.h, sie müssen abgefangen werden.
8.6
Ausgabe auf ein File
• Beispiel: FileDemo1
Dieses Beispielprogramm demonstriert, wie ein Text in einer TextArea auf eine Datei geschrieben
werden kann mittels einem PrintWriter-Objekt.
import java.io.*;
import java.awt.*;
import java.awt.event.*;
public class FileDemo1 extends Frame implements WindowListener,
ActionListener {
private TextArea inputTextArea;
private Button saveButton;
private PrintWriter outFile;
public static void main (String []
args) {
FileDemo1 demo = new FileDemo1();
demo.setSize(300,400);
demo.setVisible(true);
}
public FileDemo1() {
saveButton = new Button("save");
add (saveButton, BorderLayout.NORTH);
saveButton.addActionListener(this);
inputTextArea = new TextArea(10,50);
add (inputTextArea, BorderLayout.CENTER);
addWindowListener(this);
//for windowClosing
}
OOP_V2.7.fm
86
public void actionPerformed(ActionEvent evt) {
if (evt.getSource() == saveButton )
{
try{
outFile = new PrintWriter(new FileWriter("testout.txt"),
true);
outFile.print( inputTextArea.getText() );
outFile.close();
}
catch (IOException e)
{
System.err.println("File Error: " + e.toString() );
System.exit(1);
}
}
}
public void windowClosing(WindowEvent e) {
System.exit(0);
}
//empty WindowListener Methods
public void windowIconified(WindowEvent e) { }
public void windowOpened(WindowEvent e) { }
public void windowClosed(WindowEvent e) { }
public void windowDeiconified(WindowEvent e) { }
public void windowActivated(WindowEvent e) { }
public void windowDeactivated(WindowEvent e) { }
}
Programm 13 : Schreiben einer Textdatei
• Erklärungen zum Programm
• Als erstes muss ein PrintWriter-Objekt deklariert werden:
private PrintWriter outFile;
• Beim Erzeugen des PrintWriter-Objektes wird sodann das entsprechende File neu erzeugt:
outFile = new PrintWriter( new FileWriter("filename.ext"), true);
•
•
•
•
Diese Anweisung öffnet das File mit Namen "filename.ext".
Falls es noch nicht existiert, wird es erzeugt.
Falls es existiert, wird es überschrieben.
Der verwendete Konstruktor des PrintWriters verlangt 2 Argumente:
– Ein Writer-Objekt. In der obigen Anweisung wird direkt ein FileWriter-Objekt
OOP_V2.7.fm
87
erzeugt mit new und dieses dem PrintWriter-Konstruktor übergeben. Der
Konstruktor von FileWriter verlangt seinerseits den gewünschten Namen des zu
erzeugenden Files.
– Das 2. Argument besagt, dass der Puffer (Zwischenspeicher) am Ende jeder Zeile auf das
File geschrieben werden soll (autoflush = true).
• Die erzeugte PrintWriter-Instanz wird schliesslich der Variablen outfile zugewiesen.
• Ab Java 5 kann das PrintWriter-Objekt auch direkt mit dem Filenamen erzeugt werden:
outfile = new PrintWriter("filename.ext");
• Beachten Sie:
Der Name des Files "filename.ext" ist der Name, wie er im Directory erscheint.
Dieser ist unabhängig vom Variablennamen outfile des PrintWriter-Objekts.
• PrintWriter ist ein Spezialfall:
• Diese Klasse wirft keine Exceptions.
• Um herauszufinden, ob beim Schreiben der Datei ein Fehler aufgetreten ist, muss der
Programmierer die Methode outfile.checkError() aufrufen. (Wird in diesem
Beispiel nicht gemacht)
• Nach dem Erzeugen und Öffnen der Datei, werden die Daten hineingeschrieben mit:
outFile.print(String str);
• Diese Anweisung schreibt den String str auf die Datei outFile.
• Nachdem das Schreiben der Datei fertig ist, muss das File wieder geschlossen werden mit:
outFile.close();
• Diese Anweisung schliesst das File outfile.
8.7
Einlesen von einem File
• Beispiel: FileDemo2
Das Beispielprogramm FileDemo2 zeigt, wie aus einem File gelesen und in eine TextArea
geschrieben werden kann.
import java.io.*;
import java.awt.*;
import java.awt.event.*;
public class FileDemo2 extends Frame implements WindowListener,
ActionListener {
private TextArea inputTextArea;
private Button loadButton;
private BufferedReader inFile;
private TextField nameField;
OOP_V2.7.fm
88
public static void main (String [] args) {
FileDemo2 demo = new FileDemo2();
demo.setSize(300,400);
demo.setVisible(true);
}
public FileDemo2() {
Panel top = new Panel();
loadButton = new Button("load");
top.add(loadButton);
loadButton.addActionListener(this);
nameField = new TextField(20);
top.add(nameField);
nameField.addActionListener(this);
add ("North", top);
inputTextArea = new TextArea("",10,50);
add ("Center", inputTextArea);
addWindowListener(this);
}
public void actionPerformed(ActionEvent evt) {
String fileName;
String line;
if (evt.getSource() == loadButton) {
fileName = nameField.getText();
try {
inFile = new BufferedReader(new FileReader(fileName));
inputTextArea.setText("");
// clear the input area
while( ( line = inFile.readLine() ) != null) {
inputTextArea.append(line+"\n");
}
inFile.close();
}
catch (IOException e) {
OOP_V2.7.fm
89
System.err.println("Error in file " + fileName +
": " + e.toString() );
System.exit(1);
}
}
}
public void windowClosing(WindowEvent e) {
System.exit(0);
}
//empty WindowListener Methods
. . .
}
Programm 14 : Lesen einer TextDatei
• Erklärungen zum Programm
• Um die Textdatei zu Lesen wird zuerst ein BufferedReader-Objekt deklariert:
private BufferedReader inFile;
• Das BufferedReader-Objekt wird mit folgender Anweisung geöffnet:
inFile = new BufferedReader(new FileReader(filename));
• Konstruktor von BufferedReader verlangt ein Reader-Objekt.
• Dieses wird mit new FileReader(fileName) erzeugt und gerade übergeben. Dabei
wird die Datei fileName geöffnet.
• Wird die Grösse des Lesepuffers nicht angegeben, so ist sie standardmässig 8K Zeichen.
• Die Daten werden nach dem Öffnen der Datei zeilenweise eingelesen mit folgender Schleife:
while ((line = inFile.readLine()) != null){
inputTextArea.append(line+"\n");
}
• readLine gibt die gelesene Zeile (ohne \n) zurück oder null, falls das Ende des Files
(EOF) erreicht worden ist.
• Die gelesene Zeile wird der Variable line zugewiesen.
• Danach wird line im while-Schleifenkopf auf null getestet.
• Klammern sind notwendig, damit die Zuweisung zu line vor dem Vergleich erfolgt.
• Die Schleife könnte ausführlich auch so geschrieben werden:
line = inFile.readLine();
while (line != null){
inputTextArea.append(line+"\n");
line = inFile.readLine();
}
• Das File muss nach dem Lesen geschlossen werden mit der Anweisung: inFile.close();
OOP_V2.7.fm
90
8.8
File durchsuchen
• Beispiel: FileSearch
Gegeben sei eine Datei "Punktzahlen.dat", die mit einem gewöhnlichen Editor erstellt worden ist.
Der Inhalt der Datei sieht folgendermassen aus
Muster, 10, 13
Meier, 40, 36
Müller, 34, 19
• Aufgabe:
Analysieren Sie das Programm FileSearch auf Seite 94. Beschreiben Sie in Stichworten, wie
das Programm genau abläuft. Die Bildschirmdarstellung des Programms ist in unten stehender Figur
25 dargestellt.
Figur 25: Bildschirmdarstellung des Programms FileSearch
• Ihre Erklärungen:
OOP_V2.7.fm
91
import java.io.*;
import java.awt.*;
import java.util.*;
// StringTokenizer
import java.awt.event.*;
public class FileSearch extends Frame implements ActionListener,
WindowListener {
private BufferedReader inFile;
private Button searchButton;
private TextField result1Field, result2Field, personField;
private TextField fileNameField, errorField;
private String fileName;
public static void main (String [ ]
FileSearch
args) {
search = new FileSearch();
search.setSize(400,400);
search.setVisible(true);
}
public FileSearch() {
setLayout(new FlowLayout() );
errorField= new TextField("Type the File name:");
errorField.setEditable(false);
add(errorField);
fileNameField = new TextField(20);
fileNameField.setText("");
add(fileNameField);
searchButton = new Button("Search");
add(searchButton);
searchButton.addActionListener(this);
add(new Label("Type Name:"));
personField = new TextField(20);
personField.setText("");
add(personField);
add( new Label("Result1:"));
result1Field = new TextField(5);
result1Field.setEditable(false);
OOP_V2.7.fm
92
add(result1Field);
add (new Label("Result2:"));
result2Field= new TextField(5);
result2Field.setEditable(false);
add(result2Field);
this.addWindowListener(this);
}
public void actionPerformed(ActionEvent evt) {
if (evt.getSource() == searchButton) {
fileName = fileNameField.getText();
try {
inFile = new BufferedReader(new FileReader(fileName));
}
catch (IOException e) {
errorField.setText("Can't find file ");
return;
}
errorField.setText("Type the file name:");
// now read the file
try {
String line;
boolean found = false;
while (( ( line = inFile.readLine() ) != null)
&& (! found)) {
// tokens split on commas, spaces
StringTokenizer tokens = new StringTokenizer(line," ,");
String nameInFile = tokens.nextToken();
if (personField.getText().equals(nameInFile)) {
found = true;
result1Field.setText(tokens.nextToken() );
result2Field.setText(tokens.nextToken() );
}
}
inFile.close();
}
OOP_V2.7.fm
93
catch (IOException e) {
System.err.println("Error reading file " +
fileName+": " + e.toString() );
System.exit(1);
}
}
}
// WindowListener methods - all needed!
public void windowClosing(WindowEvent e) {
System.exit(0);
}
//empty WindowListener Methods ...
}
Programm 15 : Suche eines Namens in einer Datei
8.9
Filenamen aussuchen
• Heutzutage ist es üblich, dass der Benützer eines Programms beim Öffnen oder Abspeichern einer
Datei ein Auswahlwahlfenster, wie unten abgebildet, mit den vorhandenen Dateien bekommt.
Dieses Dialogfenster wird in Java durch die Klasse FileDialog zur Verfügung gestellt.
• Beispiel: FileDialogDemo
In diesem Beispielprogramm wird gezeigt, wie ein FileDialog-Fenster erzeugt und wie die Auswahl
des Benützers vom Programm aus abgefragt werden kann.
OOP_V2.7.fm
94
import java.io.*;
import java.awt.*;
import java.awt.event.*;
public class FileDialogDemo extends Frame implements
ActionListener, WindowListener {
private Button loadButton;
private FileDialog getNameBox;
private TextField nameField;
public static void main (String []
args) {
FileDialogDemo demo = new FileDialogDemo();
demo.setSize(500,400);
demo.setVisible(true);
}
public FileDialogDemo() {
setLayout( new FlowLayout() );
loadButton = new Button("load");
add(loadButton);
loadButton.addActionListener(this);
nameField = new TextField(30);
add(nameField);
addWindowListener(this);
// for windowClosing
}
public void actionPerformed(ActionEvent evt) {
String fileName, dirName;
if (evt.getSource() == loadButton) {
getNameBox = new FileDialog(this, "get Name",
FileDialog.LOAD);
getNameBox.setVisible(true);
// display the name
fileName = getNameBox.getFile();
dirName = getNameBox.getDirectory();
nameField.setText(dirName + fileName);
}
}
OOP_V2.7.fm
95
public void windowClosing(WindowEvent e) {
System.exit(0);
}
//empty WindowListener Methods
...
}
Programm 16 : Präsentation eines Auswahldialoges für Dateien
• Erklärungen zum Programm
• Deklaration einer File-Dialogbox:
private FileDialog getNameBox;
• Erzeugen der Dialogbox:
getNameBox = new FileDialog(this,"get a name",FileDialog.LOAD);
getNameBox.setVisible(true);
• Parameter des Konstruktors:
– Frame, in dem die Dialog-Box erzeugt wird
– Titel der Dialog-Box
– Dialog-Art
– FileDialog.LOAD
um eine Datei zu öffnen
– FileDialog.SAVE
um eine Datei zu speichern
• es wird ein sogenannter modaler Dialog erzeugt, d.h.:
sobald setVisilble(ture) aufgerufen wird, blockiert das Programm, bis eine Datei
ausgewählt wurde.
• Den Namen des ausgewählten Files oder das aktuelle Verzeichnis erhält man mit den
Anweisungen:
String fileName, dirName;
fileName = getNameBox.getFile();
dirName = getNameBox.getDirectory();
8.10
Umgang mit Dateien
• Um Dateien als Ganzes zu bearbeiten, stellt Java die Klasse File zur Verfügung.
• Da Directories ebenfalls (spezielle) Files sind, können auch Directories mit dieser Klasse bearbeitet
werden.
• Instanzierung eines File-Objektes:
File myFile = new File(String absFilename);
• erzeugt ein File-Objekt, nicht das File selbst.
• absFilename: Filename samt absolutem Pfad (z.B. c:\java\demo.java)
• Um einen einzelnen Backslash ("\") in einem String einzugeben, schreibt man zwei Backslash
nacheinander "\\"
• Methoden der Klasse File:
String getPath()
gibt relativen Pfad des Files zurück
String getAbsolutePath()
gibt absoluten Pfad des Files zurück
OOP_V2.7.fm
96
boolean exists()
boolean isDirectory()
boolean isFile()
boolean canRead()
boolean canWrite()
boolean delete()
long length()
String[] list()
long lastModified()
boolean mkdir()
boolean renameTo(File name)
8.11
Testet, ob File existiert oder nicht
Testet, ob File ein Directory ist
Testet, ob es ein File (kein Directory) ist
gibt false zurück, falls File nicht existiert
ist Lesen des Files erlaubt
ist Schreiben erlaubt
Löscht das File. Gibt true zurück, falls File
erfolgreich gelöscht wurde
gibt die File-Grösse in Bytes zurück
gibt Liste von Filenamen des Directory zurück
(null falls File kein Directory ist)
Zeitpunkt der letzten Modifikation
(Zeit in ms seit Zeitursprung, nur für Vergleiche
geeignet)
erzeugt ein Verzeichnis anhand des Pfades des
Files
benennt File um zu name. Gibt true zurück, falls
erfolgreich
Konsolen-IO
• Historische Entwicklung von User-Interfaces:
• Am Anfang gab es nur Terminals/Konsolen
• Die Ein-/Ausgabe war dementsprechend nur textbasiert möglich:
– Eingabe per Keyboard
– Ausgabe auf Bildschirm
• Später kamen einfache Menus dazu:
• Diese boten eine Auswahlmöglichkeit aus einer Befehls-Liste via Cursor-Tasten.
• Schliesslich kamen die graphischen Benützerschnittstellen (GUI) auf:
• Diese bieten neue zusätzliche Eingabenmöglichkeiten per Maus.
• Software mit Kommandozeilen-Interface sind aber auch heute noch verbreitet
• Sie ermöglichen bespielsweise, dass der Output von einem Programm in einem anderen als Input
verwenden werden kann (sogenanntes Piping).
• Es erlaubt auch das Ausführen von Skripts, und damit das Automatisieren von sich
wiederholenden Tasks (z.B. Backup).
• Dieses Interface wird häufig auch von Systemadministration gebraucht.
• In Java sind Kommandozeilen-Interfaces einfach realisierbar mit speziellen Streams, die in der
System-Klasse bereits vordefiniert sind.
8.12
System-Klasse
• Die System-Klasse stellt eine plattformunabhängige Schnittstelle zu den Betriebssystemfunktionen zur Verfügung.
• Sie enthält nur Klassenvariablen und Klassenmethoden.
• Sie kann nicht instanziert werden.
• Sie stellt unter anderem 3 Streams zur Verfügung:
• System.in
• System.out
• System.err
OOP_V2.7.fm
97
• System.in
• Dies ist ein InputStream-Objekt. Es muss nicht erzeugt werden, sondern existiert schon.
• Es kann für das direkte Einlesen von Tastatureingaben verwendet werden, aber auch für Inputs
von Pipes.
• Mit dem Objekt System.in kann der Programmierer nur byteweise Daten einlesen.
• Das Einlesen von der Kommandozeile geht viel komfortabler mit einem BufferedReader:
private BufferedReader keyboard;
keyboard = new BufferedReader( new InputStreamReader(System.in),1);
• Die Puffergrösse des BuffereReader wird normalerweise auf 1 gesetzt
So entsteht keine
Verzögerung nach dem Drücken der Return-Taste. BufferedReader wartet nämlich bei
einem readLine() bis der Puffer voll ist, bevor er den ganzen Puffer weitergibt.
• Nachdem der BufferedReader aufgesetzt ist, kann man zeilenweise von der Tastatur lesen mit:
String line = keyboard.readLine();
• System.out
• ist ein PrintStream-Objekt (Diese Klasse wurde ab Java 1.1 abgelöst durch die Klasse
PrintWriter).
• Das System.out-Objekt muss ebenfalls nicht erzeugt werden, weil es bereits existiert.
• Methoden
• print(String str), println(String str)
str kann auch mehrere Zeilen enthalten
• Analoge print-Methoden existieren für int, float, ... und für Objekte
• System.out.flush() gibt Puffer nach einem print-Befehl auf den Bildschirm aus,
auch wenn Puffer noch nicht voll ist.
Dies braucht man, wenn das Programm einen Prompt an den Benützer ausgeben will und der
Benützer die Eingabe gerade nach dem Prompt auf der gleichen Zeile machen soll.
• Dieser Stream kann ebenfalls umgeleitet werden (Piping).
• System.err: gleich wie System.out, aber dieser Stream kann nicht umgeleitet werden.
• System.exit(int errorCode):
• beendet Programm augenblicklich und gibt errorCode an Betriebssystem zurück.
• Konvention für errorCode:
• 0 Normale Beendigung
• <>0 Abbruch nach Fehler
8.13
Konsolenbasierte Anwendungen
• Beispielprogramm: Finder
Das Programm Finder sucht einen String in einer Datei. Es gibt alle Zeilen aus, wo der gesuchte
String vorkommt zusammen mit der vorherigen und der nachfolgenden Zeile.
• Der Filename wird als Command-Line-Argument übergeben
• Für das Einlesen des gesuchten Strings erzeugt das Programm in der Methode prompt(...)
einen Prompt mit einer entsprechenden Meldung. Der Benützer kann dann, den gesuchten String
nach dem Prompt eingeben.
• In der Figur 26 Seite 99 ist der Dialog mit dem Programm dargestellt, um den String „if“ in der
OOP_V2.7.fm
98
Datei „Finder.java“ zu suchen. Es wurden zwei Zeilen mit „if“ gefunden.
Figur 26: Suche der Strings „if“ in der Datei „Finder.java“
import java.io.*;
public class Finder
{
private String line1, line2, line3;
private BufferedReader keyboard, inStream;
public static void main (String []
args) {
Finder aFind = new Finder();
aFind.doSearch(args[0]);
}
private void doSearch(String fileName) {
keyboard = new BufferedReader(new
InputStreamReader(System.in),1);
String
wanted = prompt("Type string to find:");
line1 = "";
line2 = "";
try {
inStream = new BufferedReader(new FileReader(fileName));
while ((line3 = inStream.readLine()) != null) {
if ( line2.indexOf(wanted) >= 0 ){ displayLine(); }
// advance to the next group of 3
line1 = line2;
line2 = line3;
// and get new line3 from file...
}
OOP_V2.7.fm
99
//check the last line
line3 = "";
//replace null value with ""
if (line2.indexOf(wanted) >= 0){
displayLine();
}
inStream.close();
}
catch (IOException e) {
System.err.println("Error in Finder: " + e.toString());
System.exit(1);
}
}
private void displayLine() {
System.out.println("<<------------
context:");
System.out.println(line1);
System.out.println(line2);
System.out.println(line3);
System.out.println("
------------->>");
System.out.println("");
}
private String prompt(String message) {
String reply = "";
try {
System.out.print(message);
System.out.flush();
reply = keyboard.readLine();
}
catch (IOException e) {
System.out.println("Keyboard input " +
e.toString() );
System.exit(2);
}
return reply;
}
}
OOP_V2.7.fm
100
8.14
Lesen von anderen Sites
• Lesen von anderen Sites geht so leicht wie das Lesen von lokalen Files.
• Was es dazu braucht, ist die Angabe des URL (Uniform Resource Locator) des gewünschten Files.
• Beispiel: TinyBrowser
Dieses Programm fragt vom Benützer eine URL ab. Es nimmt dann Verbindung auf zu dieser URL
und gibt den Text, der von dort geschickt wird (z.B. eine HTML-Seite), auf die Konsole aus.
Figur 27: Programm TinyBrowser stellt eine HTML-Seite als Text dar.
import java.io.*;
import java.net.*;
public class TinyBrowser
{
private BufferedReader inStream, keyboard;
public static void main (String []
args) {
TinyBrowser aBrowser = new TinyBrowser();
aBrowser.fetch();
}
private void fetch() {
String urlString = "";
String line;
keyboard = new BufferedReader(new
InputStreamReader(System.in),1);
try {
urlString = prompt (" Type a URL address (e.g
http://java.sun.com/) :");
// create a link to
a URL
URL urlAddress = new URL(urlString);
URLConnection link = urlAddress.openConnection();
inStream = new BufferedReader(new
InputStreamReader(link.getInputStream()));
OOP_V2.7.fm
101
while ((line = inStream.readLine()) != null) {
System.out.print(line);
}
} // try
catch (MalformedURLException e) {
System.err.println(urlString + e.toString());
System.exit(2);
}
catch (IOException e) {
System.err.println("Error in accessing URL: "+
e.toString());
System.exit(1);
}
} // fetch
private String prompt(String message) {
String reply = "";
try {
System.out.print(message);
System.out.flush();
reply = keyboard.readLine();
}
catch (IOException e) {
System.out.println(" Keyboard input " + e.toString() );
System.exit(2);
}
return reply;
}
}
• Methoden im Zusammenhang mit URLs (dazu muss java.net.* importiert werden):
• URL urlAddress = new URL(urlstring);
• gibt den gewünschten URL zurück.
• urlstring hat die Form Protokollname:Ressourcenname
• In Java 2 werden die Protokolle http und ftp unterstützt.
• URLConnection link = urlAddress.openConnection();
• öffnet die Verbindung zur gewünschten Ressource.
• link.getInputStream()
• gibt einen InputStream zurück, der von der geöffneten Verbindung Daten einliest.
• Dieser Stream wird mit dem InputStreamReader verarbeitet.
OOP_V2.7.fm
102
8.15
Persistente Objekte
• Bei fast jeder realen Applikation besteht das Bedürfnis, den aktuellen Zustand der Applikation
abspeichern zu können.
• Da der Zustand eines objektorientierten Programms in Objekten dezentral gespeichert ist, läuft
obige Forderung darauf hinaus, Objekte persistent abspeichern und wieder laden zu können.
• Zu diesem Zweck bietet Java die Möglichkeit, Objekte in einen Byte-Stream zu verwandeln, der
dann zum Beispiel auf eine Datei gespeichert werden kann. Dieser Vorgang wird Serialisierung
(serialization) eines Objekts genannt.
• Der abgespeicherte Byte-Stream kann später wieder eingelesen und daraus wieder ein identisches
Objekt erzeugt werden Dieser Vorgang wird Deserialisierung (dezerialization) genannt.
• Bei der Serialisierung eines Objekts werden alle notwendigen Attribute in einen Byte-Stream
verpackt.
• Falls ein Attribut eine Referenz auf eine zweites Objekt darstellt, wird auch dieses Objekt serialisiert
und in den Byte-Stream gepackt. Dies führt dazu, dass mit der Serialisierung eines Objekts ganze
Objekt-Graphen serialisiert werden.
• Die Serialisierung von Objekten dient nicht nur zum Speichern von Objekten. Damit können
Objekte auch über Kommunikationsleitungen (z.B. Internet) übertgragen werden.
• Für die Serialisierung stellt Java den ObjectOutputStream zur Verfügung. Dieser verfügt über
die Methode writeObject(Object obj), mit der Objekte direkt serialisiert werden können.
Für das Einlesen eines serialisierten Objekts stellt die Klasse ObjectInputStream die Methode
Object readObject() zur Verfügung.
• Damit ein Objekt serialisierbar ist, muss es das Interface Serializable implementieren. Dieses
Interface verlangt keine Methoden, die implementiert werden müssen. Es zeigt lediglich an, dass das
entsprechende Objekt für die Serialisierung geeignet ist.
• Beispiel:
• Das Programm 17 zeigt einen Auszug aus der Klasse BankApplikation. Dieser enthält zwei
Methoden um jedes Konto im Konto-Array konto einzeln abzuspeichern und wieder zu lesen.
• Um die Konto-Objekte abzuspeichern, wird zuerst das FileOutputStream-Objekt
ostream erzeugt. Dadurch wird eine neue Datei filename im Verzeichnis dirname angelegt.
• Mit Hilfe des FileOutputStream-Objekts ostream wird sodann ein
ObjectOutputStream-Objekt erzeugt.
• In einer Schleife wird schliesslich ein Konto-Objekt nach den anderen in den Object-Stream und
damit auf die Datei geschrieben mit Hilfe der Methode writeObject(Object obj) des
ObjectStream-Objekts. Da der Parameter dieser Methode vom Typ Object ist, können der
Methode Objekte beliebiger Klassen übergeben werden.
• Beim Laden der Objekte wird ein FileInputStream-Objekt erzeugt, dem ein
ObjectInputStream-Objekt im Konstruktor übergeben wird.
• Danach kann in einer Schleife wieder ein Objekt nach dem anderen mit Hilfe der vom
ObjectInputStream-Objekt zur Verfügung gestellten Methode Object readObject()
eingelesen werden.
• readObject() gibt als Resultat ein Objekt vom Datentyp Object zurück. Dieses muss nun
zuerst wieder in ein Objekt der gewünschten Klasse (Konto) zurückgewandelt werden.
• Damit ist die Wiederherstellung des Konto-Objekts abgeschlossen.
OOP_V2.7.fm
103
public class BankApplikation extends Frame implements
ActionListener, WindowListener {
private Konto[] konto = new Konto[20];
...
// speichert alle Kontos des Arrays konto
public void kontosSpeichern(String dirname, String filename){
try{
FileOutputStream ostream = new FileOutputStream(dirname +
filename);
ObjectOutputStream objstream =
new ObjectOutputStream(ostream);
for (int i= 0; i < konto.length; i++){
objstream.writeObject(konto[i]);
}
objstream.flush();
ostream.close();
}
catch (IOException e){
System.out.println("Error while saving accounts");
e.printStackTrace();
}
}
// lädt alle Kontos des Arrays konto
public void kontosLaden(String filename, String dirname){
try{
FileInputStream instream = new FileInputStream(dirname +
filename);
ObjectInputStream objstream = new ObjectInputStream(instream);
for (int i=0; i<konto.length;i++){
konto[i] = (Konto)objstream.readObject();
}
instream.close();
}
OOP_V2.7.fm
104
catch (IOException e){
System.out.println("Error while loading accounts");
e.printStackTrace();
}
catch (ClassNotFoundException e){
System.out.println("Error while loading accounts");
e.printStackTrace();
}
}
Programm 17 : Speichern von einzelnen Konto-Objekten
• Da beim Serialisieren eines Objekts auch alle damit verbundenen Objekte ebenfalls serialisiert
werden, können die Kontos im Progamm 17 auch einfach abgespeichert werden, indem der
Konto-Array konto als Ganzes serialisiert wird. Damit werden automatisch auch alle KontoObjekte mit serialisiert. Die entsprechenden Methoden sind im Programm 18 aufgelistet.
public class BankApplikation extends Frame implements
ActionListener, WindowListener {
private Konto[] konto = new Konto[20];
...
// speichert Konto-Array konto
public void kontoArraySpeichern(String dirname, String filename){
try{
FileOutputStream ostream = new FileOutputStream(dirname +
filename);
ObjectOutputStream objstream =
newObjectOutputStream(ostream);
objstream.writeObject(konto);
objstream.flush();
ostream.close();
}
catch (IOException e){
System.out.println("Error while saving accounts");
e.printStackTrace();
}
}
OOP_V2.7.fm
105
// lädt Konto-Array konto
public void kontoArrayLaden(){
try{
FileInputStream instream = new FileInputStream(dirname +
filename);
ObjectInputStream objstream = new
ObjectInputStream(instream);
konto = (Konto[])objstream.readObject();
instream.close();
}
catch (IOException e){
System.out.println("Error while loading accounts");
e.printStackTrace();
}
catch (ClassNotFoundException e){
System.out.println("Error while loading accounts");
e.printStackTrace();
}
}
Programm 18 : Speichern des Konto-Arrays als Ganzem
OOP_V2.7.fm
106
9
Programmierstil
9.1
Ziele
•
•
•
•
Sie halten sich an die allgemeinen und die Java-Programmierrichtlinien.
Sie halten sich an die in Java üblichen Namenskonventionen.
Sie schreiben leicht verständliche Programme.
Sie wenden Ihren gewählten Programmierstil konsequent und von Beginn weg an.
9.2
Einführung
• Heutige Compiler erlauben dem Programmierer, sein Progamm praktisch beliebig zu formatieren
(Leerschläge, Leerzeilen, Tabulatoren etc.).
• Ein sauberer Programmierstil ist aber trotzdem aus verschiedenen Gründen sehr wichtig:
• Er hilft dem Programmierer selbst beim Entwickeln und Testen seines Programms (aber nur,
wenn er seinen Programmierstil von Anfang anwendet und nicht erst im Nachhinein das
Progamm ins richtige Format bringt).
• Programme werden normalerweise von mehreren Programmierern entwickelt und meistens von
mehreren Leuten gelesen (Tests, Updates). Sie müssen deshalb leicht lesbar und nachvollziehbar
sein.
• Ihr Programmcode ist unter anderem Ihre Visitenkarte als Progammierer.
9.3
Programmierstil
• Ein guter Programmierstil zeichnet sich aus durch
• eine klare Programmstruktur, die die Programmlogik hervorhebt,
• das Befolgen allgemein anerkannten Richtlinien,
• die Konsistenz des Programmierstils,
• präzise Namensgebung und
• klare Kommentare.
9.4
Programmierrichtlinien
• Es gibt ein paar allgemeingültige Programmierrichtlinien, die von allen professionellen
Programmierern eingehalten werden.
• Daneben gibt es in jeder Programmiersprache Richtlinien, an die sich die meisten
Softwareentwickler halten.
• Schliesslich haben die meisten Softwareentwicklungsfirmen und -abteilungen zusätzliche interne
Programmierrichtlinien, an die sich die Entwickler halten müssen.
9.4.1
Allgemeine Programmierrichtlinien
• Programm
• sollte sinnvoll in verschiedene Klassen aufgeteilt sein.
• Normalerweise ist jede Klasse in einer eigenen Datei gespeichert.
• Bei mehreren Klassen in derselben Datei müssen diese klar voneinander getrennt werden.
Am besten jede Klasse auf einer neuen Seite beginnen.
OOP_V2.7.fm
107
• Klassen
• sollten nicht länger sein als etwa 2 Seiten (nur Code).
• Ein klares Layout hilft beim Entwickeln, Debuggen, Warten.
• Das Layout der Klassen und Methoden sollte die Programmlogik hervorheben. Deshalb sind
folgende Textblöcke einzurücken (2–4 Leerschläge):
• Methodenkörper
• Deklarationen von Instanzvariablen
• Anweisungsblöcke in if-Konstrukten und Schleifenkonstrukten
• Kommentare
Dadurch erhält man schneller den Überblick über die Programmstruktur
• Die öffnende geschweifte Klammer wird normalerweise am Schluss der Kopfzeile einer Klasse,
Methode, Bedingung, etc. gesetzt.
• Die schliessende Klammer steht auf einer eigenen Zeile, gleich eingerückt wie die entsprechende
Kopfzeile. Falls sie weit vom Kopf entfernt ist, kann sie mit einem Kommentar versehen werden.
• Reihenfolge der Deklarationen ist normalerweise:
• Instanzvariablen
– public
– private
• Methoden
– public
– private
• Deklarationsblock ist vom Ausführungsblock durch eine Leerzeile optisch zu trennen.
• Deklarationen von Variablen nicht mit Programmcode mischen, sondern am Anfang der
Methode, Klasse.
• Methoden
• sollten wenn möglich nicht länger sein als eine Seite
• Längere Methoden aber nicht um jeden Preis aufspalten
• kein Seitenumbruch innerhalb Methode
• durch Leerzeilen (3 und mehr) visuell voneinander trennen
9.4.2
Namensgebung
Namen so bedeutungsvoll und präzise wie möglich!!!
• gilt für alle Namen, vor allem aber bei nach aussen sichtbaren Namen (Klassennamen,
Methodennamen, Parameternamen von Methoden, etc.)
• Länge der Namen spielen keine Rolle: Lange Namen brauchen im compilierten Code genau
gleich viel Platz wie kurze.
• Namen wenn möglich so wählen, dass eine Anweisung wie ein Satz gelesen werden kann:
• Beispiel:
mittelwert = summe/anzahlWerte;
ist viel besser als
m = sum/n;
• Der Name sollte vor allem die Bedeutung der Variablen hervorheben:
• Beispiel: anzahlElemente ist besser als einInteger
OOP_V2.7.fm
108
• Klassennamen
• Für Klassennamen wählt man normalerweise Substantive in Einzahl.
Beispiele: FahrzeugNummer, SortierteListe, FileDialog
• Methodennamen
• Für Methodennamen werden normalerweise aktive Verben verwendet:
oeffneRechnung(), loescheKonto()
• wenn eine Methode aber einen boolean-Wert zurückgibt, so verwendet man für den
Methodennamen häufig Adjektive oder Partizipien:
fertig(), istLeer(), hatGedruckt(), hasLoaded()
• Java-Konventionen
• Klassennamen mit Grossbuchstaben beginnen
• Variablen- und Methodennamen mit Kleinbuchstaben beginnen
• Bei zusammengesetzten Wörtern: Jedes neue Wort im Namen mit Grossbuchstaben beginnen
• Methodennamen
• Methoden, die ein Attribut setzen, mit set beginnen, gefolgt vom Attributnamen:
setFarbe(Color fb) // setzt das Attribut farbe;
setHeight(int h)
// setzt das Attribut height;
• Methoden, die ein Attribut auslesen, mit get beginnen, gefolgt vom Attributnamen:
Color getFarbe()
// gibt Wert des Attributs fabre zurück
float getSaldo()
// gibt Wert des Attributs saldo zurück
• Packages beginnen normalerweise mit Kleinbuchstaben: awt, applet
• Kein '$' verwenden für eigene Namen, da dieses Zeichen bei der automatischen
Namensgenerierung von Java verwendet wird.
• Nur ASCII-Zeichen verwenden (keine anderen Unicode-Zeichen), ausser für lokale Variablen.
9.4.3
Konstanten
• Konstanten sind Variablendeklaration mit dem Modifikator final und einem Initialwert
• Java-Konvention: Konstanten mit lauter Grossbuchstaben schreiben.
Beispiele:
public static final int AKTIV = 1;
public static final float MAX_WERT = 10.0f;
• Konstanten sind nur als Klassen- oder Instanzvariablen möglich, nicht als lokale Variablen in
Methoden
• Vorteile bei der Verwendung von Konstanten
• Compiler merkt, wenn Konstante irrtümlich überschrieben wird
• Konstante wird an einem zentralen Ort verwaltet
spätere Änderung leicht möglich. Bsp. Änderung der Maximalwertes.
• Konstantenname sagt mehr aus als nackte Zahl
9.4.4
Kommentare
• Kennzeichnen von Kommentaren:
• //
Kommentar bis Ende der Zeile
• /* ... */
Kommentare über mehrere Zeilen (können nicht verschachtelt werden!)
• /** ... */
Kommentare, die vom Java-Documentationä-Generator (javadoc) für den
OOP_V2.7.fm
109
Report verwendet werden (zusammen mit Vererbungshierarchie, publicMethoden und Variablen)
• Programm javadoc
• erzeugt aus den speziell markierten Kommentaren eine komplette HTML-Dokumentation
• Aufruf:
javadoc filename.java
• Javadoc-Kommentare müssen vor der dokumentierten Methode, Klasse stehen.
• In diesen Kommentaren können spezielle Tags zur Kennzeichnung spezieller Informationsteile
verwendet werden. Diese Tags werden dann von javadoc speziell formatiert.
Beispiele:
@param p description Parameter
@return description Rückgabewerte
@version text
Version
@author autor
Autor
• Wo soll man Kommentare setzen:
• Am Anfang jeder Klasse und Methode:
Verantwortungsbereich der Klasse, Zweck der Methode und eventuell Verwendungshinweise
• Innerhalb der Klassen und Methoden:
Alles, was für einen normalen Java-Programmierer nicht eindeutig aus dem Code hervorgeht
(Bsp. Ausnützung von Seiteneffekten)
• Beispiel:
Auf der Seite 117ff sehen Sie den Quellcode der Klasse TextBuffer und die Dokumentation, die
daraus mit Javadoc erstellt wurde.
9.5
Problemfälle
• Verschachtelte if-Konstrukte und Schleifen
• Konsequent einrücken
• Verschachtelung manchmal vermeidbar durch zusätzliche Methode
• vermindert Effizienz, da zusätzlicher Methodenaufruf nötig
• Verschachtelung nicht um jeden Preis vermeiden
• Komplexe Bedingungen
• können Lesbarkeit erschweren
• Abhilfe: boolean-Variablen einführen
• Beispiel:
• schwierig zu interpretieren:
if ((a*a + b*b == c*c) && (a == b || a == c || b == c)){...}
• besser:
boolean rechtwinklig, gleichschenklig;
rechtwinklig = a*a + b*b == c*c;
gleichschenklig = a == b || a == c || b == c;
if (rechtwinklig && gleichschenklig){...}
OOP_V2.7.fm
110
9.6
Dokumentation
• Eine vollständige Dokumentation ist enorm wichtig für den Programmunterhalt
• Nur eine aktuelle Dokumentation ist eine gute Dokumentation
• Dokumentation ist kein Testament!
• nicht erst am Schluss schreiben sondern fortlaufend aufdatieren
•
hilft dem Programmierer auch schon bei der Entwicklung
• Zur Dokumentation gehören typischerweise:
• Spezifikation
• Source-Code-Listing
• Klassdiagramme, andere Design-Informationen
• Testplan
• Testresultate
• Modifikations-Geschichte
• Versionen des Programms und ihre Unterschiede
• Handbuch (falls nötig)
OOP_V2.7.fm
111
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.awt.datatransfer.*;
/**
Verwaltet einen Text. Der Text kann von einer
*
Datei geladen, bearbeitet und wieder auf eine
*
Datei gespeichert werden.
*
@version 1.0
*
@author H.-P. Hutter / ZHW
*/
public class TextBuffer extends TextArea implements TextListener {
private
Frame edit = null;
// Frame, das den TextBuffer enthält
private
boolean edited = false; // wurde Buffer editiert?
private
String filename = null;
private
String dirname = "";
private
StringSelection transferData = null;
private
Clipboard clipB = null;
/** Erzeugt einen TextBuffer
*
@param ed Frame, worin der TextBuffer dargstellt wird.
*/
public TextBuffer(Frame ed){
super(60,80);
// 60 Reihen, 80 Kolonnen
edit = ed;
addTextListener(this);
edited = false;
}
OOP_V2.7.fm
112
/** Lädt den TextBuffer von einer Datei.
*/
public void laden() throws IOException{
String line = null;
BufferedReader infile = null;
FileDialog openDialog = new FileDialog(edit,"Datei öffnen",
FileDialog.LOAD);
openDialog.setDirectory(dirname);
openDialog.show();
filename = openDialog.getFile();
dirname = openDialog.getDirectory();
if (filename == null){
return;
}
edit.setTitle("Mein-Simple-Editor
" + dirname + filename);
infile = new BufferedReader(new FileReader(dirname+filename));
setText("");
while ((line = infile.readLine()) != null){
append(line+"\n");
}
infile.close();
edited = false;
}
// end laden
/**
*
Gibt aktuellen Filenamen zurück.
@return aktuellen Filenamen
*/
public String getFilename(){
return filename;
}
OOP_V2.7.fm
113
/**
Gibt aktuelles Verzeichnis zurück.
*
@return aktuelles Verzeichnis
*/
public String getDirname(){
return dirname;
}
/**
Speichert TextBuffer in der Datei 'fname' im Verzeichnis
*
'dname'.
*
Falls fname = null, wird mit einem Dialogfenster der Filename
*
und das Verzeichnis abgefragt.
*
@param dname
Name des Verzeichnisses
*
@param fname
Name des Files
*/
public void speichern(String dname, String fname)
throws IOException {
PrintWriter outfile = null;
String fullname = null;
if (fname == null){
//
if (filename == null){
falls kein Filename mitgegeben
// falls kein Defaultname gespeichert,
// FileDialog starten
FileDialog closeDialog = new FileDialog(edit,
"Datei speichern", FileDialog.SAVE);
closeDialog.setDirectory(dirname);
closeDialog.show();
filename = closeDialog.getFile();
dirname = closeDialog.getDirectory();
fullname = dirname + filename;
}
else{
//
sonst Default-Filenamen verwenden
fullname = dirname + filename;
}
}
OOP_V2.7.fm
114
else{
// fname nicht null
fullname = dname + fname;
dirname = dname;
// aktuellen Filenamen aufdatieren
filename = fname;
// aktuelles Verzeichnis aufdatieren
}
if (filename == null){
return;
// falls keinen Namen ausgewählt, abbrechen
}
// TextBuffer abspeichern
outfile = new PrintWriter(new FileWriter(fullname), true);
outfile.print(getText());
outfile.close();
if (outfile.checkError()){
throw new IOException();
}
edited = false;
}
// end speichern
/** Speichert TextBuffer in eine Datei. Dateiname wird
*
abgefragt.
*/
public void speichernAsk() throws IOException {
filename = null;
speichern(null, null);
}
/**
Kopiert selektierten Text ins System-Clipboard.
*/
public void copy(){
// System-Clipboard holen
clipB = this.getToolkit().getSystemClipboard();
// Text in Clipboard legen
transferData = new StringSelection(this.getSelectedText());
clipB.setContents(transferData,transferData);
edited = true;
}
OOP_V2.7.fm
115
/**
*
Schneidet selektierten Text aus und legt ihn in
das System-Clipboard.
*/
public void cut(){
copy();
// cut ist wie copy, nur wird Text nachher gelöscht
// Text löschen
replaceRange("",getSelectionStart(),getSelectionEnd());
}
/** Fügt Text im Clipboard in den TextBuffer an der Stelle des
*
Cursors ein. Falls ein Text selektiert ist, wird dieser durch
*
den Clipboard-Inhalt ersetzt.
*/
public void paste(){
String einfuegeStr = "";
// Transferable-Objekt erzeugen
Transferable transf = clipB.getContents(transferData);
// versuche, Clipboard-Inhalt zu laden
try{
einfuegeStr = (String) transf.getTransferData(
DataFlavor.stringFlavor);
}
catch (UnsupportedFlavorException e){
// falls Format nicht String ist
this.getToolkit().beep();
}
catch (IOException e){
// falls sonst was schief gegangen ist
this.getToolkit().beep();
}
replaceRange(einfuegeStr, getSelectionStart(),
getSelectionEnd());
edited = true;
}
OOP_V2.7.fm
116
/**
*
TextListener-Methode
@param te TextEvent
*/
public void textValueChanged(TextEvent te){
if (te.getSource() == this){
edited = true;
}
}
/**
Gibt zurück, ob aktueller TextBuffer seit der letzten
*
Speicherung verändert worden ist.
*
@return true, falls Buffer seit der letzten Speicherung
*
editiert wurde.
*/
public boolean wurdeEditiert(){
return edited;
}
/**
*
Setzt das Editier-Flag des TextBuffers.
@param wasEdited
Editier-Flag
*/
public void setWurdeEditiert(boolean wasEdited){
edited = wasEdited;
}
} // end TextBuffer
Programm 19 : Klasse TextBuffer mit Javadoc-Kommentaren
• Auf den folgenden Seiten ist die HTML-Datei wiedergegeben, die Javadoc aus dem Quellcode der
Klasse TextBuffer erzeugt hat.
OOP_V2.7.fm
117
Auf den folgenden leeren Seiten ist die von Javadoc erzeugte HTML-Dokumentation zur Klasse
TextBuffer einzufügen.
OOP_V2.7.fm
118
OOP_V2.7.fm
119
OOP_V2.7.fm
120
OOP_V2.7.fm
121
OOP_V2.7.fm
122
9.7
Mein eigener Programmierstil
• Lernaufgabe:
Programmstudium (Zeit: 10 Minuten)
Tauschen Sie Ihr mitgebrachtes Musterprogramm mit Ihrem Tischnachbarn aus. Studieren Sie
sodann kritisch das Programm Ihres Kollegen anhand der Richtlinien für das Programmieren.
Massstab
• Finden Sie mindestens drei Punkte, die Sie gut finden am Programm Ihres Kollegen, und drei
Punkte, die noch verbessert werden könnten.
• Überlegen Sie sich, was genau Ihnen Mühe macht beim Verständnis seines Programms.
• Besprechung (Zeit: 10 Minuten)
• Diskutieren Sie mit Ihrem Kollegen Ihre Beurteilung seines Programmes.
• Überdenken Sie Ihren Programmierstil und halten Sie ihn dann hier als Erinnerung fest:
– Ich halte mich an die folgende allgemeinen Programmrichtlinien:
– Eine Klasse pro Datei
.........
– Jede neue Klasse beginnt auf einer neuen Seite
.........
– Öffnende geschweifte Klammer setze ich jeweils
– Schliessende geschweifte Klammer setze ich jeweils
– Reihenfolge bei den Deklarationen:
OOP_V2.7.fm
123
– Abstände
– Zwischen den Methoden
....... Zeilen
– Zwischen Deklarationsblock und 1. Anweisung
....... Zeilen
– Ich rücke jeweils um ...... Leerschläge ein, um Programmlogik hervorzuheben.
– Namensgebung
– Ich halte mich an die Java-Konventionen bezüglich
– Klassennamen
.......
– Methodennamen
.......
– Konstanten
.......
– Packages
.......
– Zusätzliche eigene Konventionen:
OOP_V2.7.fm
124
10
Programmpakete
10.1
Ziele
• Sie können Klassen aus gegebenen Packages importieren.
• Sie können eigene Packages deklarieren.
10.2
Packages
• Normalerweise befindet sich jede Java-Klasse in einem eigenen File mit dem gleichem Filenamen
wie die Klasse.
• Falls zwei Klassen denselben Namen haben, so sind sie nicht mehr eindeutig identifizierbar.
• Abhilfe:
Der gesamte Namensraum wird durch sogenannte Packages aufgeteilt.
• Packages
• Fasst eine Gruppe von Klassen (und eventuell Subpackages) zusammen unter einem (möglichst
eindeutigen) Namen
• Beispiele:
– java.applet
:
Alle Klassen im Zusammenhang mit Applets
– java.awt
:
Die Klassen des Abstract-Windowing-Toolkit
• Vorteile:
• Klassen werden eindeutig spezifizierbar durch Angabe des Packagenamens.
• Die Daten innerhalb eines Packages werden zusätzlich gekapselt.
• Es können zusätzliche Zugriffsberechtigung für Klassen innerhalb desselben Packages
definiert werden.
• Klassen desselben Package
• werden im gleichen Directory gespeichert.
• Der Name des Directorys muss gleich sein wie der des Package.
• Deklaration:
• Package-Deklaration muss als erstes Statement in jeder Klasse stehen:
package
mypackage;
import java.applet.*;
• Package-Namen
• Beginnen normalerweise mit Kleinbuchstaben (häufig ganzer Packagename kleingeschrieben)
• Sonst gelten die gleiche Regeln wie bei Klassennamen.
• Packagenamen sollten internetweit eindeutig sein.
• Vorschlag für internetweit eindeutige Package- Namen: URL verwenden. Damit würde eine
Methode einer bestimmten Klasse folgendermassen eindeutig spezifiziert:
firmenDomänenName.verzeichnis1.verzeichnis2.....packageName.KlassenName.methoden
Name
• Beispiel:
– com.sybase.jdbc.SybDriver
– ch.zhaw.hut.meinPackage.GenialeKlasse
• Klassen ohne Package-Angabe
• gehören zu einem anonymen Package.
OOP_V2.7.fm
125
• Zu ihm gehören alle "anonymen" Klassen im gleichen File und im gleichen Directory.
• Welche Klassen soll man zu Packages zusammenfassen?
• Es gibt keine Regeln über die Anzahl Klassen pro Package
• Klassen die einander häufig brauchen, sollten im gleichem Package ablegt sein
• Klassen, die häufig gebraucht werden, werden häufig in separate Packages abgelegt
(Klassenbibliothek)
10.3
Importieren von Klassen und Packages
• Generell muss jede Klasse, die verwendet wird, eindeutig mit dem vollen Package-Namen
spezifiziert werden.
• Der volle Package-Name kann nur weggelassen werden, wenn die entsprechende Klasse mit einem
import-Statement am Anfang der Datei importiert wird:
• Beispiel:
Wir möchten die Klassenmethode statMethode() der Klasse mypackage.MyClass
aufrufen.
• ohne import:
mypackage.MyClass.statMethode();
• mit import:
import mypackage.MyClass;
...
MyClass.statMethode();
• Falls alle Klassen eines Packages importiert werden sollen:
import mypackage.*;
• Zur eindeutigen Spezifikation eines Packages muss der ganze Directory-Pfad des Package
angegeben werden:
Beispiel: import
projects.myproject.mypackage;
projects muss dabei ein Subdirectory eines Verzeichnisses im Classpath von Java sein.
Die Punkte werden je nach Betriebssystem in „/“ oder „\“ übersetzt, um entsprechendes Directory zu
finden.
• Die Packages im JDK beginnen mit
• java
(Standardklassen)
• javax
(Standard-Erweiterungsklassen des JDK)
• org.omg
(CORBA)
• Basis-Package java.lang
• wird automatisch importiert
• enthält die grundlegenden Klassen von Java.
10.4
Sichtbarkeitsregeln für Packages
• Java definiert für Packages einen zusätzlichen Sichtbarkeitsbereich: package. Dies ist die
Standardsichtbarkeit in Java.
• Klassen, Methoden, Variablen mit package-Sichtbarkeit sind in jeder Klasse innerhalb des gleichen
Packages sichtbar.
• Deklaration der package-Sichtbarkeit:
int x; // ohne Sichtbarkeitsmodifikator
package-Sichtbarkeit
OOP_V2.7.fm
126
• Zusammenfassung der Zugriffsmodifikatoren
Sichtbarkeit
gleiche Klasse
gleiches
Package
Subklasse
in anderem
Package
andere Klasse
in anderem
Package
private
ja
nein
nein
nein
keiner (d.h. package)
ja
ja
nein
nein
protected
ja
ja
ja
nein
public
ja
ja
ja
ja
Zugriffsmodifikator
• Übung
Studieren Sie das folgende Programm und markieren Sie alle Zeilen, die einen Compiler-Fehler
ergeben (weil eine Variable, Methode oder Klasse) nicht sichtbar ist.
package sicht1;
import sicht2.*;
public class Sichtbarkeit {
public static void main(String[] args){
SamePackage same = new SamePackage();
PublicKlasse publicObj = new PublicKlasse();
PrivatKlasse privatObj = new PrivatKlasse();
SubOtherKlasse subOtherObj = new SubOtherKlasse();
System.out.println("a = "+ same.a);
System.out.println("b = "+ same.b);
System.out.println("c = "+ same.c);
System.out.println("d = "+ same.d);
System.out.println("Package sicht2");
System.out.println("a = "+ publicObj.a);
System.out.println("b = "+ publicObj.b);
System.out.println("c = "+ publicObj.c);
System.out.println("d = "+ publicObj.d);
System.out.println("SubOtherKlasse");
System.out.println("a = "+ subOtherObj.a);
System.out.println("b = "+ subOtherObj.b);
System.out.println("c = "+ subOtherObj.c);
System.out.println("d = "+ subOtherObj.d);
}
}
OOP_V2.7.fm
127
------------------------------------------------------------------------------------------- neue Datei
package sicht1;
class SamePackage{
private int a = 1;
public int b = 2;
int c = 3;
protected int d = 4;
}
------------------------------------------------------------------------------------------- neue Datei
package sicht1;
class SubOtherKlasse extends PublicKlasse{
SubOtherKlasse(){
System.out.println("a = "+ a);
System.out.println("b = "+ b);
System.out.println("c = "+ c);
System.out.println("d = "+ d);
}
}
------------------------------------------------------------------------------------------- neue Datei
package sicht2;
public class PublicKlasse{
private int a = 10;
public int b = 11;
int c = 12;
protected int d = 13;
}
------------------------------------------------------------------------------------------- neue Datei
package sicht2;
class PrivatKlasse{
private int a = 20;
public int b = 21;
int c = 22;
protected int d = 23;
}
Programm 20 : Programm Sichtbarkeit
OOP_V2.7.fm
128
11
Fehlersuche (Debugging) und Test
11.1
Ziele
•
•
•
•
Sie können den Unterschied zwischen Debugging und Testen erklären.
Sie unterscheiden mindestens 3 Arten von Fehlern.
Sie gehen systematisch vor beim Debuggen Ihrer Programme.
Sie können drei verschiedene Techniken für das Testen selber anwenden.
11.2
Debugging
11.2.1 Einführung
• Das Unangenehmste und meistens Zeitraubenste am Programmieren ist die Fehlersuche, d.h. die
Suche nach der Ursache eines Fehlverhaltens des Programmes.
• Die Fehlersuche in der Softwareentwicklung nennt man Debugging.
• Der Name stammt vom englischen bug (= Insekt).
• Der Name geht auf die Zeit der Röhren-Computer zurück, wo einmal ein Insekt das
Fehlverhalten eines Computers verursacht haben soll.
• Debugging ist Dedektivarbeit:
• Man muss sich ausgehend vom Symptom (Fehlverhalten des Programms) bis zur Ursache
(Fehler im Programm) hinabarbeiten.
• Debugging fordert und fördert analytisches Denken und systematisches Vorgehen
• Es gibt Hilfsmittel, die das Debugging erleichtern, sogenannt Debugger.
• Nachdem das Programm das erste Mal fehlerfrei läuft, muss es getestet werden.
• Testen heisst hier: Versuchen nachzuweisen, dass das Programm
• in jedem Fall richtig läuft
• die Anforderungen erfüllt
• Testen und Debugging beanspruchen typischerweise die Hälfte des gesamten
Entwicklungsaufwandes!
• Es werden verschiedenste Techniken eingesetzt, um Programme zu testen.
• Wichtig beim Testen:
• Nicht erst am Schluss, sondern laufend während der Programmentwicklung
• Je später ein Fehler gefunden wird, desto teurer wird dessen Behebung (Zeit, Kosten)
11.2.2 Fehlerarten
Listen Sie in der linken Spalte der Tabelle 3 typische Programmfehler auf, die Ihnen bisher beim Programmieren unterlaufen sind:
Tabelle 3: Fehlerarten
OOP_V2.7.fm
129
• Es gibt verschiedene Arten von Fehlern: Syntaxfehler, Laufzeitfehler, logische Fehler
• Syntaxfehler
• Syntaxfehler sind grammatikalische Fehler im Programm.
• Häufigste Ursache von Syntaxfehlern sind eigentliche Schreibfehler.
• Die meisten Syntaxfehler werden von einem guten Compiler entdeckt, wie beispielsweise:
• Falsche oder fehlende Variablendeklarationen
• Typenfehler
• Nicht initialisierte Variablen
• Fehlende Listener-Methoden
• Fehlermeldung: Der Compiler gibt den ungefähren Ort und den Grund des Fehlers an.
• Je nach Fehler gibt die Fehlermeldung nicht die wahre Ursache der Fehlers an und/oder der Ort
des Fehlers kann nicht richtig lokalisiert werden.
• Hitliste der Syntaxfehler in Java:
• Tippfehler
• Falsch platzierte oder fehlende Klammern
• Fehlende ( ) bei einem Methodenaufruf ohne Argumente
• Fehlende Strichpunkte
• Irrtümlicher Strichpunkt ; nach einer if-Bedingung oder for-Bedingung
• Fehlender oder falscher import
• extends Klassenname fehlt
• Listener-Methoden nicht oder falsch geschrieben oder mit falschen Parametern
• Typenfehler:
• Widerspruch zwischen Deklaration einer Variablen/Methode und deren Verwendung
• Typenfehler werden auch zu den Syntaxfehlern gezählt.
• Beispiele:
– Variable/Methode ist nciht deklariert/sichtbar.
– Einer Variablen wird ein Wert zugewiesen, der nicht ihrem Typ entspricht.
– Methode eines Objekts wird aufgerufen, die es dort nicht gibt.
– Argumentliste beim Aufruf einer Methode entspricht nicht der deklarierten Parameterliste.
– Returnwert einer Methode entpricht nicht dem Aufruf.
• Laufzeitfehler (runtime error)
• Laufzeitfehler sind Fehler, die erst zur Laufzeit des Progamms auftreten.
• Beispiele:
• Zugriff auf ungültigen Array-Index:
int table[] = new int[10];
for (int i = 0; i <= 10; i++){
table[i] = 0;
}
• Arithmetische Fehler: z.B. Division durch 0
int a,b,c,d;
a = b/(c-d) // Division durch 0 bei c == d!
• Verwendung von Objekten, bevor sie erzeugt wurden (mit new):
Button but;
add(but);
• Laufzeitfehler führen häufig zu Exceptions:
NullpointerExceptions, ArrayOutOfBoundsException, etc.
OOP_V2.7.fm
130
• Logische Fehler
• Das Programm läuft, aber es verhält sich nicht so, wie es sollte. Z.B.:
• Es liefert falsche oder zu ungenaue Resultate
• Unerwarteter Ablauf oder Reaktion des Programms
• Logische Fehler sind häufig schwierig zu finden (Compiler findet sie oft nicht)
• Beispiele:
• Ungewollte Endloschleife:
int a = 0;
while (a == 0);{
a++;
}
•
•
•
•
•
•
Mehrzeilige if- oder Schleifen-Konstrukte ohne geschweifte Klammern
Variablen nicht richtig initialisiert (v.a. in Arrays)
Nichtbehandeln von Events
Irrtümliches Verdecken von Variablen und Überschreiben von Methoden
repaint() vergessen, nach einer Änderung im GUI
Stringvergleiche mit s1 == s2 anstatt mit s1.equals(s2)
(Erster Vergleich testet, ob s1 und s2 das gleiche Objekt referenziert!)
• Logische Fehler können ebenfalls Exceptions verursachen.
• Aufgabe:
Ordnen Sie die Fehler, die Sie in Tabelle 3 eingetragen haben, den eben behandelten Fehlerarten zu.
Tragen Sie dazu die Fehlerarten in die rechte Spalte ein.
11.2.3 Vorgehen beim Debugging
Beim Debugging geht man typischerweise nach folgenden Schritten vor:
1. Fehleranalyse
Jedes Debugging beginnt mit der Analyse des Fehlverhaltens (Fehlersymptom) des Programms:
• Syntaxfehler:
• Fehlermeldung des Compilers genau lesen!!!
• Angabe des Fehlerortes (Zeilennummer) beachten
• Laufzeitfehler
• erzeugen häufig eine Exception mit der entsprechenden Fehlermeldung.
• Fehlermeldung des Programms genau lesen.
• Angabe des Fehlerortes genau studieren (Klasse, ev. Zeilennummern)
• Logische Fehler
• Zuerst Fehlvehalten genau beschreiben :
– was beobachten Sie als Benützer
(nicht beschreibuen, was Sie vermuten, wo das Problem liegt!)
– Beispiel:
"Ich sehe das Quadrat, das gezeichnet werden soll, nicht im Appletfenster"
anstatt
"Das Progamm zeichnet das Quadrat nicht."
• Dann Fehlverhalten zurückverfolgen:
2. Ursachen für das Fehlverhalten suchen und überprüfen
• Für die Lokalisierung von logischen Fehlern hilft es, nach möglichen Ursachen für das
OOP_V2.7.fm
131
Fehlverhalten zu fragen und diese zu überprüfen. Dabei sollten Sie die offensichtlichsten
Ursachen nicht vergessen.
Beispiel Quadrat:
Überlegen Sie ich einige Ursachen dafür, dass das Quadrat nicht auf dem Bildschirm erscheint.
(Als guter Analytiker sollten Sie mindestens drei mögliche Ursachen finden)
3. Fehler lokalisieren
• Nach der Analyse des Fehlverhaltens geht es darum, den eigentlichen Programmfehler zu
lokalisieren, der das Fehlverhalten verursacht.
• Dies ist dann einfach, wenn die Zeilennummer bei Exceptions oder Syntaxfehlern angegeben
wird.
• Bei logischen Fehlern hingegen ist die Fehlerlokalisierung meist schwierig.
• In diesem Fall suchen Sie die fehlerhafte Methode von hinten her:
• Sie überlegen sich, welche Methode direkt verantwortlich ist für das Verhalten, das Sie
erwarten und das nicht eingetroffen ist.
Beispiel Quadrat: "Das Quadrat sollte in der paint()-Methode gezeichnet werden "
paint()-Methode überprüfen
• Dann überprüfen Sie, ob die aktuelle Methode richtig aufgerufen wird.
Beispiel Quadrat: „Damit paint()-Methode aufgerufen wird, muss in
actionPerformed(...) die Methode repaint() aufgerufen werden“.
• Falls der Fehler so nicht lokalisierbar ist, bleibt meist nur die sogenannte binäre Suche:
1. Das Programmstück, das den Fehler enthält, wird in zwei Hälften geteilt.
2. Dann wird getestet, ob der Fehler in der 1. oder 2. Hälfte auftritt
3. Mit dem fehlerhaften Programmstück beginnt man wieder mit Schritt 1.
• Dies macht man solange, bis der Fehler auf einige Anweisungen genau lokalisiert ist.
4. Zusätzliche Informationen gewinnen
Um einen Fehler zu lokalisieren oder dessen Ursache herausfinden, hilft es häufig, wenn Sie sich
zusätzliche Informationen beschaffen:
• Haben die beteiligten Variablen die Werte, die Sie erwarten?
• Macht eine bestimmte Methode, was Sie von ihr erwarten?
• Für die Ausgabe von Zwischenresultaten eignen sich die Befehle:
System.out.println(); oder System.err.println();
• Die so erzeugten Meldungen erscheinen
– auf der Kommandozeile
– in der Java-Konsole (Netscape)
OOP_V2.7.fm
132
• System.out.println(this);
– Diese Anweisung gibt Informationen über das aktuelle Objekt aus.
– Sie ruft dazu toString() des aktuellen Objektes auf.
– Jede Klasse sollte die Methode toString()implementieren, um die wichtigsten
Informationen zur Klasse auszugeben.
– Beispiel:
Die Klasse Ballon könnte mit folgender toString-Methode seine aktuelle Position
und Grösse ausgeben:
public String toString(){
return "x = " + xCoord + " y = " + yCoord +
" diameter = " + diameter;
}
• Der richtige Ort des println-Aufrufs ist wichtig, z.B.:
– vor Aufruf einer Methode
– nach Aufruf einer Methode
5. Fehler zurückverfolgen
• möglicherweise ist der gefundene Fehler nur die Folge eines anderen Fehlers
zurück zu Schritt 1, um diesen Fehler zu finden.
• Beispiel Quadrat: neu gefundenes Symptom : "paint()-Methode wird nicht aufgerufen"
Ursache dafür suchen wieder bei Schritt 1 beginnen
11.2.4 Verwendung eines Debuggers
• Debugger:
Ein Debugger ist ein Programm, das einem erlaubt, das Programm irgendwo anzuhalten und dort
verschiedene Informationen über den Zustand des Programms (v.a. Variablenwerte) anzuschauen.
• jdb:
• Im JDK-Paket ist ein Debugger für Java, jdb, enthalten.
• Er ist kommandozeilen-orientiert.
• jdb wurde in Java 1.3 stark überarbeitet
• Die meisten integrierten Entwicklungsumgebungen enthalten einen Debugger.
• Ein guter Debugger sollte:
• Eine graphische Benützeroberfläche besitzen mit je einem Fenster mit Sourcecode, ProgrammOutput, selektierten Variablen mit ihren aktuellen Werten.
• Möglichkeiten bieten, sogenannte Breakpoints zu setzen
• Ein Breakpoint ist eine vom Benützer definierte Stelle (Programmzeile) im Programm, an
dem das Programm angehalten wird.
• Anstatt eines fixen Breakpoints kann das Programm auch angehalten werden, sobald ein
Fehler auftritt.
• Der Benützer kann nun an dieser Stelle:
– Variablenwerte abfragen und eventuell neu setzen
– Methoden aufrufen
– Programm weiter laufen lassen , eventuell schrittweise
• Schrittweises Durchlaufen des Programms (stepping) ermöglichen
• Das Programm hält nach jeder ausgeführten Programmzeile wieder an.
• Dabei kann gewählt werden, ob
– aufgerufene Methoden ohne Unterbruch ausgeführt werden (step over) oder
OOP_V2.7.fm
133
– in die aufgerufene Methode hineingesprungen werden soll (step in)
• Tracing ermöglichen:
An einer bestimmten Stelle im Programm wird jeweils eine gewünschte Information ausgegeben,
ohne das Programm anzuhalten.
• die Möglichkeit bieten, den Wert einer Variablen während des Programmablauf dauernd zu
überwachen (sogenannter watch)
• Das Vorgehen bei der Fehlersuche mit einem Debugger ist prinzipiell gleich wie ohne Debugger.
Der Debugger vereinfacht dabei vor allem das Zusammentragen von Informationen:
• Breakpoints an den gewünschten Stellen setzen
• vor Aufruf einer Methode oder
• am Anfang und Ende einer Methode
• Programm laufen lassen und Variablenwerte an den Breakpoints überprüfen.
• Stimmt etwas in einer Methode nicht, Breakpoint an Anfang der Methode setzen und dann
schrittweise die Methode durchgehen
11.3
Testen
11.3.1 Ziel
• Das Testen eines Programms (oder einer Komponente davon) hat zum Ziel zu verifizieren, ob
• das Programm (möglichst in allen Fällen) fehlerfrei läuft.
• das Programm die Spezifikation erfüllt
11.3.2 Programmspezifikation
• Startpunkt jedes Tests ist die Programmspezifikation.
• Zuerst muss die Spezifikation genau studiert und bei allfälligen Unklarheiten zurückgefragt werden.
• Beispiel: Spezifikation des Programms Summe
Schreiben Sie ein Programm, bei dem man eine Reihe von Zahlen über ein Textfeld eingeben kann.
Die Eingabe wird mit einer negativen Zahl abgeschlossen. Das Programm berechnet sodann die
Summe der Zahlen und zeigt sie an.
• Aufgabe 1:
Überlegen Sie sich, wo diese Spezifikation unklar ist. Formulieren Sie mindestens 3 Fragen, die Sie
dem Verfasser der Spezifikation zur Klärung der Spezifikation stellen würden.
OOP_V2.7.fm
134
11.3.3 Techniken
• Es gibt zahlreiche Techniken, um Programme zu testen. Diese lassen sich einteilen in
• Dynamische Testverfahren:
Das Programm wird dabei mit Testdaten ausgeführt.
Beispiele:
• Blackbox-Test (Funktionaler Test)
• Whitebox-Test (Struktureller Test)
• Statische Testverfahren:
Das Programm wird nicht ausgeführt, sondern nur der Quellcode analysiert.
Beispiele:
• Walkthrough
• Review (Inspektion)
• Formale Methoden (Verifikation)
Es wird versucht analytisch zu beweisen, dass ein Programm für alle in Frage kommenden
Eingabedaten korrekt abläuft und die Spezifikationen erfüllt.
11.3.4 Black-Box-Test (Funktionaler Test)
• Es geht letztlich darum zu prüfen, ob das Programm die Spezifikation erfüllt.
• Der funktionale Test verwendet deshalb nur Wissen über die gewünschte Funktion des Programms
(Spezifikation).
• Es wird bewusst kein Wissen über die Implementierung des Programms verwendet.
• Das Programm wird als Black Box betrachtet.
• Das Programm wird mit ausgewählten repräsentativen Eingabedaten laufen gelassen und dabei
werden die Ausgabedaten mit den erwarteten Resultaten verglichen.
• Problem: Vollständiger Test
• Ein vollständiger Test mit allen möglichen Kombinationen von Eingabedaten ist normalerweise
nicht möglich ausser für sehr kleine Programme!!
• Beispiel zur Illustration:
Bei 3 Eingangsparameter mit je 10000 möglichen Werten 100003 mögliche Kombinationen.
• Idee: Äquivalenzklassen bilden
• Alle möglichen Eingabewerte werden in Äquivalenzklassen eingeteilt:
Die Äquivalenzklassen werden so ausgewählt, dass alle Zahlen in derselben Äquivalenzklasse
vom Programm gleich verarbeitet werden (sollten).
• Sodann wird eine repräsentative Zahlenfolge pro Äquivalenzklasse ausgewählt.
• Grenzwertanalyse
• Die Erfahrung zeigt, dass Eingabewerte an den Grenzen von Äquivalenzklassen häufig zu
Fehlverhalten von Programmen führen.
• Deshalb werden nicht irgendwelche Daten einer Äquivalenzklasse ausgewählt, sondern gerade
die Werte an den Grenzen der Äquivalenzklassen.
• Beispiel: Summe von Zahlen
Es sei folgende Spezifikation für ein Programm zur Berechnung einer Reihensumme gegeben:
„Schreiben Sie ein Programm, bei dem eine Reihe von ganzen Zahlen z über ein Textfeld eingeben
werden kann. Die Zahlen sind im Bereich 0 <= z <= 10000. Es kann jeweils nur eine Zahl aufs
Mal eingegeben werden. Die Eingabe ist beendet, wenn der Button 'Summe' gedrückt wird. Das
Programm berechnet sodann die Summe der eingegebenen Zahlen und zeigt sie an. Danach können
OOP_V2.7.fm
135
wieder neue Zahlen eingegeben werden. Bei Eingabe einer ungültigen Zahl wird eine Warnung.
angezeigt und die Eingabe ignoriert.“
• Äquivalenzklassen
• gültige Äquivalenzklassen:
1. Zahlenfolge mit mindestens einer Zahl und lauter gültigen Werten z (0 <= z <= 10000)
2. leere Zahlenfolge
• ungültige Aquivalenzklassen:
3. Zahlenfolgen mit mindestens einer Zahl oberhalb des gültigen Wertebereichs
4. Zahlenfolgen mit mindestens einer Zahl unterhalb des gültigen Werterbereichs
5. Zahlenfolgen mit mindestens einer float-Zahl
6. Zahlenfolgen mit mindestens einem unerlaubten Zeichen
• Auswahl der Testfälle:
• Die Testfälle werden so ausgewählt, dass möglichst viele gültige Äquivalenzklassen
abgedeckt werden.
• Bei Testfällen für ungültige Äquivalenzklassen wird jeweils nur ein Testwert einer ungültigen
Äquivalenzklasse ausgewählt, die restlichen stammen aus gültigen Äquivalenzklassen. (Bei
mehreren ungültigen Werten pro Testfall wäre nicht klar, welcher die erwartete
Fehlerbehandlung ausgelöst hat).
• Liste der Testfälle
Die Tabelle 4 zeigt die Liste der vorgesehenen Testfälle für das Programm für die Berechnung
der Reihensumme.
• Jeder Testfall hat eine eindeutige Nummer.
• Bei jedem Testfall werden die Testwerte, die dadurch abgedeckten Äquivalenzklassen sowie
das erwartete Resultat beschrieben.
Erwartetes
Resultat
Testwerte
Abgedeckte Äquivalenzklassen und
Grenzwerte
(O=Obergenzen, U=Untergrenze)
1
0, 1, 10000 "Summe"
1O, 1U
10001
2
"Summe"
2
0
3
8, 10001, "Summe"
3U
8
4
94, -1 "Summe"
4O
94
5
5.76 "Summe"
5
0
6
5000, b "Summe"
6
5000
Testnummer
Tabelle 4: Testfälle für das Beispiel Summe
• Aufgabe 2:
• Stellen Sie einen Blackbox-Test für ein Programm auf, das die folgende Spezifikation erfüllt:
Das Programm bestimmt den grössten Wert einer Reihe von ganzen Zahlen. Die Zahlen werden
nacheinander mit Hilfe eines Textfeldes eingegeben. Die Zahlenreihe ist fertig, sobald der Button
"Ende" gedrückt wird. Sodann soll das Programm das Resultat berechnen und anzeigen. Bei der
Eingabe von ungültigen Werten soll das Programm eine Warnung ausgeben und die Eingabe
ignorieren.
OOP_V2.7.fm
136
• Massstab:
Sie sollten mindestens 6 der 9 Äquivalenzklassen mit den dazugehörigen Testfällen finden.
• Lösung:
• Äquivalenzklassen
• gültige Äquivalenzklassen:
• ungültige Aquivalenzklassen:
OOP_V2.7.fm
137
• Testfälle:
Testnummer
abgedeckte Äquivalenzklassen
Testwerte
erwartetes
Resultat
• Regeln zum Bilden von Äquivalenzklassen
• Bilden die gültigen Eingabewerte einen zusammenhängenden Wertebereich, so sind eine gültige
und zwei ungültige Äquivalenzklassen zu bilden.
• Ist anzunehmen, dass die Elemente einer Äquivalenzklasse verschieden behandelt werden, ist die
Äquivalenzklasse entsprechend aufzutrennen.
• Müssen die Eingabewerte eine Bedingung zwingend erfüllen (z.B. erstes Zeichen ist ein
Buchstabe), so ist eine gültige und eine ungültige Äquivalenzklasse zu bilden.
• Testdatensätze für ungültige Äquvalenzklassen sollten nur einen ungültigen Wert aufweisen.
OOP_V2.7.fm
138
11.3.5 White-Box-Test (Strukturtest)
• Im Unterschied zum Black-Box-Test verwendet man beim White-Box-Test das Programm-Listing.
• Wir behandeln hier nur die einfachste Art von Strukturtest, den sogenannten
Anweisungsüberdeckungstest.
• Anweisungsüberdeckungstest:
• Ziel: Jede Anweisung soll mindestens einmal ausgeführt werden
• Beispiel: Das unten stehende Programm soll die grösste von drei Zahlen a, b, c suchen.
int a, b, c ;
int largest;
if (a > b){
if (a > c){
largest = a;
{
else{
largest = c;
}
}
else{
if (b > c){
largest = b;
{
else{
largest = c;
}
}
• Testfälle:
• Es genügen die folgenden 4 Datensätze, um alle Anweisungen einmal auszuführen
Testnummer
Testdaten
a, b, c
Resultat
1
3, 2, 1
3
2
3, 2, 4
4
3
4, 5, 3
5
4
1, 2, 3
3
Tabelle 5: Testfälle für White-Box-Test
OOP_V2.7.fm
139
• Aufgabe 3:
• Erstellen Sie einen Whitebox-Test für die folgende Klasse, die anhand der Spezifikation aus
Aufgabe 2 erstellt wurde.
• Wie beurteilen Sie die Effektivität dieses Tests. Zeigen Sie mindestens 2 Mängel auf.
class Biggest{
private int largest = 0;
private boolean firstNum = true;
public void nextNumber(int n){
if (firstNum){
largest = n;
firstNum = false;
}
else {
if (n > largest){
largest = n;
}
}
}
public int getLargest(){
return largest;
}
public void reset(){
firstNum = true;
}
}
•
Lösung:
OOP_V2.7.fm
140
• Aufgrund dieser Mängel wurden verschiedene verbesserte Strukturtests entwickelt, z.B.
• Zweigüberdeckungstest
• Bedingungsüberdeckungstest
11.3.6 Inspektion und Walkthrough
• Inspektion (Review) und Walkthrough sind manuelle Prüfmethoden, wobei der Walkthrough
informeller ist als die Inspektion.
• Es braucht dazu nur die Spezifikation und das Programmlisting.
• Jemand (nicht der Programmierer selbst) studiert das Programmlisting. Dabei wird eine Methode
nach der anderen geprüft (nur eine Methode aus Mal):
• Sind alle Variablen initialisiert?
• Werden alle Schleifen in jedem Fall korrekt beendet?
• Haben die Methodenaufrufe die richtigen Argumente?
• Sind Auswahl- oder Schleifen-Bedingungen korrekt formuliert?
• Kontrolle der Logik des Programms
• Computer spielen
• Aufrufe von anderen Methoden nicht weiterverfolgen -> deshalb Walkthrough genannt
• ev. Überprüfung des Programmierstils
• Vegleiche der Inspektion mit experimentellen Tests:
Inspektion ist mindestens so gut wie experimentelles Testen
• Verwendung von Debuggern:
Damit wird die Kontrolle der Variablewerte bei der Inspektion erleichtert.
11.3.7 Formale Verifikation
• Für eine formale Verifikation muss die Spezifikation in einer formalen Sprache (Z, VDM) in Form
von Vorbedingungen und Nachbedingungen des Programms beschrieben sein.
• Zudem muss die Semantik (Bedeutung) jedes Programmkonstrukts der verwendeten
Programmiersprache formal beschrieben sein.
• Verifikationsregeln geben sodann an, wie eine Vorbedingung durch ein Programmkonstrukt in eine
Nachbedingung gewandelt werden kann.
• Es gibt 2 Möglichkeiten, formale Verifikation einzusetzen:
• Entweder zur Verifizierung eines bestehenden Programms (aufwändig) oder
• Programm wird durch eine Folge von Transformationen aus den Spezifikationen erzeugt
(bevorzugtes Vorgehen)
OOP_V2.7.fm
141
• Vorteil der formalen Verifikation: Es ist ein vollständiger Korrektheitsbeweis möglich.
• Nachteile:
• Für grosse Programme ist dieser aber sehr aufwändig bis unmöglich
• Programmiersprache muss eine formale Semantik besitzen
• Spezielle Spezifikationstechnik erforderlich.
• Die formale Verifikation wird heute hauptsächlich für Hochsicherheitsanwendungen verwendet.
11.3.8 Testen von Einheiten und Systemintegration
• Die kleinste sinnvolle Testeinheit bei objektorientierten Programmen ist die Klasse.
• Möglichkeit für den Test einzelner Klassen:
• main-Methode verwenden, um Klasse zu testen. Dies ist möglich, da die main-Methode nicht
ausgeführt wird, wenn ein Objekt der Klasse erzeugt wird.
• Beispiel:
Die obige Klasse Biggest könnte durch folgende main-Methode ergänzt werden:
public static void main(String[] args){
Biggest big = new Biggest();
big.nextNumber(-1);
big.nextNumber(2);
big.nextNumber(4);
System.out.println("Numbers : -1, 2, 4 ; Biggest: " +
big.getLargest());
big.reset();
big.nextNumber(9);
big.nextNumber(2);
System.out.println("Numbers : 9, 2 ; Biggest: " +
big.getLargest());
}
• Test der Klasse durch Aufruf von: java Biggest
• Typischer Aufbau des Testcodes innerhalb einer Klasse
• Objekt erzeugen
• Methoden aufrufen
– zuerst diejenigen, die den Zustand des Objektes nicht ändern (get-Methoden), dann die,
die den Objektzustand ändern.
– Äquivalenzklassenbildung und Grenzwertanalyse anhand der Methodenspezifikationen.
– Voneinander abhängige Methode in allen Ablaufkombinationen testen.
• Resultate anzeigen
• Als Testverfahren kann man eine oder mehrere der oben beschriebenen Test-Methoden
verwenden.
• Als Test-Methode für den Systemintegrationstest verwendet man meistens einen Blackbox-Test.
OOP_V2.7.fm
142
11.3.9 Inkrementelle Entwicklung
• Man sollte nicht alle Klassen auf einmal schreiben, dann alle zusammenfügen und dann testen.
• Vielmehr sollte das Programm inkrementell entwickelt werden:
• Ein Programmteil nach dem anderen entwickeln und testen.
• Dann Programmteil zum System hinzufügen und System als Ganzes testen.
• Dann den nächsten Teil entwickeln und hinzufügen.
• Reihenfolge beim Entwickeln und Testen: Am häufigsten Buttom-up
• Zuerst Klassen, die unabhängig von anderen Klassen sind entwickeln und testen.
• Dann, nach und nach die darauf aufbauenden Klassen
11.3.10 Schlussbemerkung
• Es existiert keine absolut sichere Testmethode. Jede hat ihre Stärken und Schwächen.
• Am besten verwendet man eine Kombination von Tests.
• Systematisches Vorgehen ist auch beim Testen sehr wichtig Inkrementelles Testen
OOP_V2.7.fm
143
12
Abstrakte Klassen und Interfaces
12.1
Ziele
• Sie können die Begriffe abstrakte Klasse, Methode und Interface erklären.
• Sie können mit abstrakten Klassen und Interfaces umgehen.
• Sie können an Beispielen erklären, wie und wozu abstrakte Klassen und Interfaces verwendet
werden.
• Sie können eigene abstrakte Klassen und Interfaces schreiben.
12.2
Abstrakte Klassen
• Problemstellung
• Beispiel:
• Wir möchten ein Package schreiben, das Klassen für verschiedene Figuren enthält (Rechteck,
Ellipse, Dreieck).
• Die Figuren sollen alle die gleiche Funktionalität haben
• Mögliche Klassenhierarchie des Package:
Figur
int xPos, yPos
int breite, hoehe
setSize(int b, int h)
setPosition(int x, int y)
anzeigen(Graphics g)
float getFlaeche()
Rechteck
Kreis
int radius
anzeigen(Graphics g)
float getFlaeche()
anzeigen(Graphics g)
float getFlaeche()
Figur 28: Mögliche Klassenhierarchie eines Package für Figuren
• Methoden, die alle Subklassen haben müssen, werden in der Superklasse deklariert. (Dank
Polymorphismus wird jeweils die richtige Methode ausgewählt)
• Einige Methode können auch in der Superklasse implementiert werden.
Beispiel: setSize(int b, int h), setPosition(int x, int y)
• Andere Methoden können hingegen nicht sinnvoll implementiert werden
Beispiel: anzeigen(Graphics g), float getFlaeche()
• Problem:
Wie stellt man sicher, dass auch zukünftige Subklassen von Figur, die gemeinsamen
OOP_V2.7.fm
144
Methoden implementieren?
• Lösung:
abstrakte Methoden und Klassen:
Die Methoden anzeigen(Graphics g) und float getFlaeche() werden als
abstract deklariert:
public abstract void anzeigen(Graphics g);
public abstract float getFlaeche();
• Abstrakte Methoden
• haben nur einen Methodenkopf (Signatur), keinen Methodenkörper
• haben einen Strichpunkt (;) nach der Signatur
• haben den Modifikator abstract im Methodenkopf
• Folge für die Klasse Figur:
Sobald eine Methode der Klasse Figur abstract ist, muss auch die Klasse selbst als
abstract deklariert sein:
public abstract class Figur{
public abstract void anzeigen(Grapics g);
public abstract float getFlaeche();
}
• Abstrakte Klasse
• Ein abstrakte Klasse hat mindestens eine abstrakte Methode und/oder ist als abstract
deklariert.
• Sie kann nicht instanziert werden.
• Subklassen von abstrakten Klassen müssen die abstrakten Methoden implementieren.
• Falls sie das nicht tun, müssen sie ebenfalls als abstract deklariert sein.
• Beispiel von Figur 28 auf Seite 144:
Die Klasse Rechteck wird von Klasse Figur abgeleitet und implementiert die abstrakten
Methoden:
class Rechteck extends Figur{
public void anzeigen(Graphics g){
g.drawRect(xPos, yPos, breite, hoehe);
}
public
float getFlaeche(){
return (float) breite*hoehe;
}
}
• Aufgabe:
OOP_V2.7.fm
145
Leiten Sie die Klasse Kreis von der abstrakten Klasse Figur ab.
• Lösung:
• Vorteile durch die Verwendung von abstrakten Klassen:
• Ein bestimmtes Klassendesign kann erzwungen werden.
• Sie erlaubt die Klassenbeschreibung in höheren Abstaktionsebenen:
Von der Klasse Figur von oben können keine Objekte erzeugt werden. Sie fasst aber alle
Objekte mit bestimmten Eigenschaften unter einem neuen Namen zusammen.
• Analogie in der Biologie:
Ein Säugetier existiert selbst nicht, sondern ist ein Sammelbegriff (Abstraktion) von real
existierenden Tieren.
• Alles in allem:
Abstrakte Klassen erleichtern die Planung und den Aufbau von Klassenhierarchien
OOP_V2.7.fm
146
12.3
Interface (Schnittstelle)
• Ein Interface definiert eine Schnittstelle zwischen Klassen
Klasse 1:
Klasse A:
verwendet
Interface P
implementiert
Interface P
Interface P
+
_
Klasse 2:
implementiert
Interface P
Klasse B:
.
.
.
verwendet
Interface P
Klasse 4:
implementiert
Interface P
Figur 29: Interface
• Ein Interface ist eine Klasse, die nur aus Methodenköpfen besteht.
Beispiel:
public interface Ballon {
public void changeSize(int newDiameter);
public void move(int newX, int newY);
public void display(Graphics g);
}
• Merkmale eines Interfaces
• Interfaces definieren wie Klassen einen neuen Datentyp.
• Es können somit Variablen dieses Datentyps deklariert werden, z.B.: Ballon ball;
• Interfaces können wie Klassen an Sub-Interfaces weitervererbt werden.
• Ein Interface ist immer abstract (abstract kann auch weggelassen werden).
• Es können deshalb keine Objekte aus einem Interface erzeugt werden.
• Ein Interface beschreibt nur die Schnittstelle (Klasse, Methoden, Parameter) einer Klassen,
nicht deren Implementierung.
• Alle Methoden eines Interfaces sind public und abstract, auch wenn sie nicht explizit so
deklariert sind.
• Methoden dürfen nicht static sein.
• Hingegen sind alle Variablen eines Interfaces static final (d.h. Konstanten) sein, auch
wenn sie nicht explizit so deklariert sind.
• Interfaces können compiliert, aber nicht laufen gelassen werden.
Das Compilieren erlaubt Typenprüfung bei der Verwendung eines Interfaces.
• Das compiliertes Interface wird ebenfalls als Class-Datei, InterfaceName.class,
gespeichert.
OOP_V2.7.fm
147
• Implementieren von Interfaces:
• Ein Interface wird durch eine Klasse implementiert, die alle Methoden des Interfaces
implementiert.
• Deklaration einer Klasse, die ein Interface implementiert:
class MeineKlasse implements Interfacename {...}
• Die Klasse muss alle Methoden des Interfaces Interfacename implementieren. Dies wird vom
Compiler überprüft.
• Interfaces können wie Klassen andere Interfaces durch extends erweitern.
• Klassen, die ein Sub-Interface implementieren, müssen auch alle Methoden der Super-Interfaces
implementieren.
• Eine Klasse kann gleichzeitig mehrere Interfaces implementieren:
class MeinKlasse implements Interface1, Interface2,... {...}
• Interfaces dienen zur Beschreibung von:
• Klassen, die bestimmte Methoden zur Verfügung stellen.
• ganzen Vererbungsstrukturen
• Interfaces können nicht verwendet werden für
• Beschreibung der Implementation der einzelnen Methoden
• Verwendung von anderen Klassen (hat-ein-Relation. Grund: Nur Konstanten in Interfaces
erlaubt)
• Beispiel für den Einsatz eines Interface: Methode zeichneLinksbuendig
• Wir möchten eine Methode schreiben, die eine Anzahl beliebiger graphischer Objekte
linksbündig darstellen kann.
• Die Objekte müssen die zwei Methoden setPosition und anzeigen zur Verfügung stellen.
• Dazu definieren wir folgendes Interface:
import java.awt.*;
public interface Darstellbar{
public void setPosition(int x, int y);
public void anzeigen(Graphics g);
}
• Die Methode zeichneLinksbuendig sieht dann wie folgt aus:
public void zeichneLinksbuendig(Graphics g,
Darstellbar[] figuren, int xPos, int abstand){
for (int i=0; i<figuren.length; i++){
figuren[i].setPosition(xPos,i*abstand);
figuren[i].anzeigen(g);
}
}
OOP_V2.7.fm
148
• Beachten Sie:
• zeichneLinksbuendig besitzt einen Array-Parameter vom Typ Darstellbar.
• Die Methode kann beliebige Objekte, die Darstellbar implementieren, darstellen.
• Aufgabe:
• Schreiben Sie eine Klasse Rechteck, die das Interface Darstellbar implementiert.
• Wie könnte man erreichen, dass alle Subklassen der Klasse Figur das Darstellbar-Interface
implementieren?
• Lösung:
12.4
Mehrfachvererbung und Interfaces
• Java erlaubt nur einfache Vererbung
• Mehrfachvererbung ist aber nachbildbar mit Interfaces, da eine Klasse mehrere Interfaces
implementieren kann.
• Beispiel:
• Das Applet MeinSpiel ist abgeleitet von der Klasse Applet (Vererbung)
• Zusätzlich soll es die Interfaces ActionListener und MouseListener implementieren:
public class MeinSpiel extends Applet
implements ActionListener, MouseListener{
...
}
OOP_V2.7.fm
149
• Darstellung in UML
Objekt
Applet
MouseListener
MeinSpiel
ActionListener
Figur 30: Darstellung von Interfaces in UML
12.5
Interfaces vs. abstrakte Klassen
• Unterschied zwischen abstakten Klassen und Interfaces:
• Abstrakte Klassen können Methoden-Implementationen enthalten, Interfaces nicht.
• Eine Klasse kann mehrere Interfaces implementieren, aber nur eine Klasse erweitern.
• Interfaces werden nur beim Compilieren gebraucht.
• Methoden abstrakter Klassen werden dagegen zur Laufzeit zum Programm gelinkt.
12.6
Adapterklassen
• In Java sind sogenannte Adapterklassen für jedes EventListener-Interface vordefiniert:
• Adapterklassen sind abstrakte Klassen, die das entsprechende Interface implementieren.
• Vorteil:
• Falls man nur eine Methode braucht, erweitert man einfacher die Adapterklasse, anstatt das
ganze Interface zu implementieren.
• Beispiel: MouseListener
• Interface schreibt schreibt vor, dass die 5 Methoden mouseClicked, mouseEntered,
mouseExited, mousePressed, mouseReleased implementiert werden müssen.
• Falls man z.B. nur mouseClicked braucht, müssen die anderen trotzdem geschrieben
werden.
• Alternative:
– Verwendung (d.h. Erweiterung) der Klasse MouseAdapter:
– Vorteil: Es muss nur die Methode mouseClicked überschrieben werden:
public class MeineKlasse extends MouseAdapter{
public void mousClicked(MouseEvent e){...}
}
• Zu jedem Listener mit dem Namen XXXListener gibt es eine entsprechende Adapterklasse
mit dem Namen XXXAdapter.
OOP_V2.7.fm
150
13
Threads
13.1
Ziele
•
•
•
•
•
•
•
•
Sie können in eigenen Worten erklären, was Threads sind und wozu sie verwendet werden.
Sie unterscheiden vier Zustände eines Threads.
Sie kennen mindestens eine Art, um einen Thread zu erzeugen.
Sie setzen Threads in Ihren eigenen Programmen situationsgerecht ein.
Sie können das Hauptproblem bei interagierenden Threads erklären.
Sie können eine Lösung mittels gegenseitigem Ausschluss für dieses Problem skizzieren.
Sie können das Produzent-Konsument-Problem erklären.
Sie können eine Lösung mittels Wecksignalen für dieses Problem in Umgangssprache (Pseudocode)
beschreiben.
• Sie können den Begriff "Deadlock" an einem Beispiel erklären.
13.2
Einführung
• Die meisten Computer haben nur einen Prozessor
• Dieser kann nur einen Befehl nach dem anderen ausführen
• Der Computer kann aber scheinbar trotzdem mehrere Aufgaben gleichzeitig bearbeiten
• Mehrere Benützer können gleichzeitig denselben Computer benützen
• Mehrere Programme können gleichzeitig laufen (Word, Netscape, Photoshop)
• Ein Programm kann gleichzeitig mehrere Aufgaben erledigen (Bsp. Browser):
– HTML-Seite editieren
– Datei herunterladen
– Mail anzeigen
• Wie ist das möglich?
Der Prozessor arbeitet einige Befehle des eines Programms ab und schaltet dann blitzschnell
zum nächsten Prozess (= laufendes Programm) um.
• Computer mit mehreren Prozessoren
• Jeder Prozessor führt ein oder mehrere Progamme gleichzeitig aus.
• Mehrere Prozessoren können auch am gleichen Programm beteiligt sein, um dessen
Ausführungsgeschwindigkeit zu erhöhen.
13.3
Begriffe
• Parallele Prozesse (Multitasking)
• sind verschiedene Programme, die gleichzeitig auf einem oder mehreren Prozessoren laufen
• falls nur ein Prozessor vorhanden ist, spricht man auch von nebenläufigen Prozessen
• Die Prozesse laufen jeder in seinem separaten Arbeitsspeicherbereich.
• Die Kommunikation zwischen der Prozessen finden etweder über einen gemeinsam benutzten
Speicherbereich oder über sogenannte Pipes statt.
• Die Umschaltung zwischen den Prozessen ist relativ aufwendig, da der gesamte Zustand eines
Prozesses vor dem Umschalten abgespeichert und bei der Rückkehr wieder geladen werden
muss.
• Nebenläufige Prozesse (Multithreading)
• sind Teile desselben Programms, die gleichzeitig ablaufen
OOP_V2.7.fm
151
• Thread ist ein Programmteil, der parallel zu den anderen Programmteilen abläuft.
• Threads desselben Programms laufen im gleichen Arbeitsspeicherbereich.
• Threads können
• entweder unabhängig voneinander ablaufen
• oder aber voneinander abhängig sein, z.B. wenn
– sie dieselben Ressourcen brauchen
– Bildschirm, Datei
– Variablen, Objekte
– ein Thread etwas produziert, das der andere braucht
• Da Threads im gleichen Speicherbereich laufen, ist das Umschalten des Prozessors schneller als
bei parallelen Prozessen.
• Den Unterschied im Ablauf eines Programms mit nur einem Thread (single-threaded) und mit
mehreren Threads (multi-threaded) kann man sich folgendermassen vorstellen:
Programmablauf mit einem Thread
Programmablauf mit meheren Threads
Figur 31: Programmablauf bei einem oder mehreren Threads
OOP_V2.7.fm
152
13.4
Beispiel: Springender Ball
• Der Unterschied zwischen einem Programm mit einem einzigen Thread und einem mit mehreren
Threads soll anhand des Programms Bouncer demonstriert werden.
• Das Programm Bouncer stellt einen Ball dar, der sich in einem Rechteck bewegt und jeweils
an den Wänden abprallt. Der Ball soll mit den zwei Buttons Start und Stop gestartet bzw.
gestoppt werden können, wie in Figur 32 dargestellt.
Figur 32: Programm Bouncer
13.4.1 Version 1:
• Die erste Version des Progamms besteht aus den zwei Klassen Bouncer1 und Ball.
• Das Programm besteht nur aus einem einzelnen Thread
import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;
public class Bouncer1 extends Applet implements ActionListener
private Button start, stop;
private Ball ball;
public void init() {
start = new Button("Start");
add(start);
start.addActionListener(this);
stop = new Button("Stop");
add(stop);
stop.addActionListener(this);
}
OOP_V2.7.fm
153
{
public void actionPerformed(ActionEvent event) {
if (event.getSource() == start) {
Graphics g = getGraphics();
ball = new Ball(g, getBackground());
ball.display();
}
if (event.getSource() == stop) {
ball.anhalten();
}
}
}
class Ball {
private Graphics g;
private Color bgColour;
private int xChange = 14;
private int yChange = 4;
private int diameter= 20;
private boolean weiterSo = true;
private int rectLeftX = 50, rectRightX = 350;
private int rectTopY = 50, rectBottomY = 350;
private int x = rectLeftX + xChange;
private int y = rectTopY + yChange;
public Ball(Graphics graphics, Color bg) {
g = graphics;
bgColour = bg;
}
OOP_V2.7.fm
154
public void display() {
g.drawRect(rectLeftX, rectTopY, rectRightX rectLeftX, rectBottomY-rectTopY);
for (int n = 1; (n< 200 && weiterSo); n++) {
g.setColor(bgColour);
g.fillOval (x, y, diameter, diameter);
if (x + xChange <= rectLeftX){
xChange = -xChange;
}
if (x + diameter + xChange >= rectRightX){
xChange = -xChange;
}
if (y + yChange <= rectTopY){
yChange = -yChange;
}
if (y + diameter + yChange >= rectBottomY){
yChange = -yChange;
}
x = x + xChange;
y = y + yChange;
g.setColor(Color.red);
g.fillOval (x, y, diameter, diameter);
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
g.drawString("sleep exception", 20, 20);
}
}
}
// end for
// end display
public void anhalten(){
weiterSo = false;
}
}
// end Ball
Programm 21 : Version 1 des Programms Bouncer mit nur einem Thread
OOP_V2.7.fm
155
• Problem bei dieser Version
• Computer ist völlig absorbiert, solange der Ball sich bewegt.
• Folge: Stop-Button funktioniert nicht, solange Ball sich bewegt.
• Das Programmablauf ist aus folgender Abbildung ersichtlich:
Ball
verschieben
50 ms schlafen
Bouncer1-Objekt
Ball-Objekt
Zeit
Figur 33: Ablauf des Programms Bouncer mit einem einzigen Thread
• Lösung:
Das Ballobjekt muss in einem eigenen Thread ablaufen.
13.4.2 Version 2: Bouncer2
• In der zweiten Version soll das Programm aus 2 Threads bestehen:
1. Thread (Hauptthread) :
Bouncer1-Objekt (User-Interface)
2. Thread :
Ball-Objekt
• Wie erzeugt man den 2. Thread?
• Man muss einfach die Klasse Ball von der Klasse Thread ableiten:
class Ball extends Thread {...}
• Um aber auch als eigener Thread laufen zu können, muss die Klasse Ball die Methode
run()bereitstellen.
• Sobald nun der 1. Thread ein Ball-Objekt erzeugt, wird ein neuer Thread erzeugt:
Ball ball = new Ball(g); // erzeugt aus dem Objekt ball
// einen neuen Thread
ball.start();
// Thread ball starten
• Beim Aufruf der Methode ball.start() wird automatisch ein neuer Thread. Dieser führt
die Methode run()des Objekts ball aus.
• Der neue Thread läuft ab jetzt (scheinbar) parallel zu Haupt-Thread.
OOP_V2.7.fm
156
• Programmablauf von Version 2:
Warten auf
Benützereingaben
Ball
verschieben
50 ms schlafen
Bouncer2-Objekt
Ball-Objekt
Zeit
Figur 34: Ablauf des Programms Bouncer mit zwei parallel laufenden Threads.
• Programmcode der Version 2 der Bouncer-Programms:
import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;
public class Bouncer2 extends Applet implements ActionListener
private Button start, stop;
private Ball ball = null;
public void init() {
start = new Button("Start");
add(start);
start.addActionListener(this);
stop = new Button("Stop");
OOP_V2.7.fm
157
{
add(stop);
stop.addActionListener(this);
}
public void actionPerformed(ActionEvent event) {
if (event.getSource() == start) {
Graphics g = getGraphics();
ball = new Ball(g, getBackground());
ball.start();
}
if (event.getSource() == stop) {
ball.anhalten();
}
}
}
class Ball extends Thread {
private Graphics g;
private Color bgColour;
private int xChange = 14;
private int yChange = 4;
private int diameter= 20;
private boolean weiterSo = true;
private int rectLeftX = 50, rectRightX = 350;
private int rectTopY = 50, rectBottomY = 350;
private int x = rectLeftX + xChange;
private int y = rectTopY + yChange;
public Ball(Graphics graphics, Color bg) {
g = graphics;
bgColour = bg;
}
OOP_V2.7.fm
158
public void run() {
g.drawRect(rectLeftX, rectTopY, rectRightXrectLeftX, rectBottomY-rectTopY);
while (weiterSo) {
g.setColor(bgColour);
g.fillOval (x, y, diameter, diameter);
if (x + xChange <= rectLeftX){
xChange = -xChange;
}
if (x + diameter + xChange >= rectRightX){
xChange = -xChange;
}
if (y + yChange <= rectTopY){
yChange = -yChange;
}
if (y + diameter + yChange >= rectBottomY){
yChange = -yChange;
}
x = x + xChange;
y = y + yChange;
g.setColor(Color.red);
g.fillOval (x, y, diameter, diameter);
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
g.drawString("sleep exception", 20, 20);
}
}
// end while
} // end run
public void anhalten(){
weiterSo = false;
}
} // end Ball
Programm 22 : Version 2 des Programms Bouncer
OOP_V2.7.fm
159
• Man kann mit diesem Programm auch mehrere Bälle erzeugen, die dann alle als eigenständige
Threads ablaufen.
• Da aber immer dieselbe Variable für die Ball-Objekte verwendet wird, kann man jeweils nur noch
den zuletzt erzeugten Thread wieder stoppen. Die Referenz auf die früher erzeugten Threads wurde
überschrieben.
• Lebensdauer eines Threads
• Ein Thread läuft so lange, bis seine run()-Methode fertig ist.
• Es gibt zwar noch eine Methode stop()in der Klasse Thread, mit der ein Thread gestoppt
werden kann. Diese Methode sollte jedoch nicht verwendet werden, da sie unsicher ist und nicht
mehr unterstützt wird.
• Es gibt in Java neben der bereits erwähnten Möglichkeit noch eine zweite, um aus einem Objekt
einen Thread zu machen:
• 1. Möglichkeit
• Eigene Klasse von Thread ableiten
class MeineKlasse extends Thread { ... }
• Ein Objekt der eigenen Klasse erzeugen. Dies erzeugt automatisch einen neuen Thread.
• Die Klasse MeineKlasse muss die run()-Methode überschreiben.
• 2. Möglichkeit
• Eigene Klasse implementiert Runnable
class MeineKlasse implements Runnable { ... }
– zu diesem Zweck muss die Klasse eine run()-Methode zur Verfügung stellen.
• Objekt aus der eigenen Klasse erzeugen und daraus einen Thread erzeugen:
MeineKlasse meinObj = new MeineKlasse();
Thread meinThread = new Thread(meinObj);
13.5
Nützliche Methoden für Threads
• start()
• stop()
• suspend()
• resume()
• sleep(int ms)
• yield()
•
•
•
•
join()
setName(String str)
String getName()
setPriority(int pri)
• int getPriority()
• boolean isAlive()
OOP_V2.7.fm
startet Thread
hält Thread an (mit einer Exception),nicht mehr unterstützt,
stattdessen wie in Bouncer2 implementieren
unterbricht Ausführung des Threads
(nicht mehr unterstützt, da unsicher)
lässt Thread weiterfahren (nicht mehr unterstützt, da
unsicher)
gerade ausgeführter Thread schläft für ms Millisekunden.
Thread gibt Kontrolle an anderen lauffähigen Thread ab.
gerade ausgeführter Thread übergibt Kontrolle einem
anderen Threads
wartet bis dieser Thread beendet ist
Name des Threads auf str setzen
Name des Threads auslesen
Priorität des Threads auf pri setzen
( vordefinierte Werte: MAX_PRIORITY,
MIN_PRIORITY, NORM_PRIORITY)
Priorität des Threads auslesen
gibt an, ob Thread noch läuft
160
13.6
Zustand eines Threads
• Mögliche Zustände eines Threads (vgl. Figur 35):
• New:
Thread erzeugt (mit new), aber noch nicht gestartet (mit start())
• Running:
Thread läuft
• Runnable:
Thread bereit zum laufen, aber anderer Thread läuft zur Zeit
• Blocked:
Thread kann zur Zeit nicht weiterfahren, z.B. wegen sleep
• Dead:
Thread ist fertig abgelaufen.
• Der Zustand eines Thread kann mit der Methode isAlive() abgefragt werden:
true
Thread runnable oder blocked
false
Thread new oder dead
Dead
stop()
run() fertig
st
)
p(
op
()
o
st
new()
New
sta
rt(
)
)
e(
m () ut
u
s y o
re otif e
n tim
ep
sle
Running/
Runnable
Blocked
)
d(
en ()
p
s it
su wa p()
e
sl e
Figur 35: Mögliche Zustände eines Threads
13.7
Scheduling
• Scheduler
• teilt die Prozessorzeit auf die verschiedenen laufbereiten Threads auf
• schaltet den Prozessor zwischen den Threads hin und her
• Normalfall:
• Threads haben alle dieselbe Priorität (5)
• Alle erhalten gleichviel Prozessorzeit
• Die Priorität eines Threads kann man setzen mit der Methode setPriority()
• Methode yield()
• Bei Aufruf von yield() gibt Thread die Kontrolle ab an andere Threads mit gleicher Priorität,
falls welche laufen.
• Falls keine anderen Threads vorhanden, läuft Thread weiter.
13.8
Interagierende Threads
• Interagierende Threads sind solche, die nicht unabhängig voneinander laufen. Dabei können sie sich
konkurrenzieren ober aber zusammenarbeiten.
• Sie brauchen zum Beispiel das gleiche Objekt, die gleiche Variable wie ein anderer Thread
• Bei interagierenden Threads können Probleme auftauchen, wenn sie gleichzeitig auf die
OOP_V2.7.fm
161
gemeinsame Ressource zugreifen wollen.
• Beispiel: Das Programm Squares zeigt zwei sich konkurrenzierende Threads
import java.applet.Applet;
import java.awt.*;
public class Squares extends Applet {
private Counter1 counter1;
private Counter2 counter2;
public void init(){
Graphics g = getGraphics();
// gemeinsames Objekt count
SharedNumber count = new SharedNumber(g);
// 2 Threads erzeugen und starten
counter1 = new Counter1(count);
counter2 = new Counter2(count);
counter1.start();
counter2.start();
}
public void paint(Graphics g){
g.setColor(Color.red);
g.fillRect(200,10,20,20);
g.drawString("Counter1", 240,25);
g.setColor(Color.blue);
g.fillRect(200,40,20,20);
g.drawString("Counter2", 240,55);
}
}
OOP_V2.7.fm
162
// Thread Counter1 zeichnet rote Quadrate
class Counter1 extends Thread {
private SharedNumber count;
public Counter1(SharedNumber count){
this.count = count;
}
public void run() {
for (int i = 1; i <= 50; i++){
try{
Thread.sleep(10);
}
catch (InterruptedException e){;}
count.increment(Color.red);
}
}
}
// Thread Counter2 zeichnet blaue Quadrate
class Counter2 extends Thread {
private SharedNumber count;
public Counter2(SharedNumber count){
this.count = count;
}
public void run() {
for (int i = 1; i <= 50; i++){
try{
Thread.sleep(10);
}
catch (InterruptedException e){;}
count.increment(Color.blue);
}
}
}
OOP_V2.7.fm
163
// Die gemeinsam verwendete Ressource:
// Zählervariable n
class SharedNumber {
private int n = 0;
// Zählervariable
private Graphics g;
public SharedNumber (Graphics g) {
this.g = g;
}
// erhöht Zähler n und zeichnet Quadrat
// an der Stelle (n,n)
public
void increment(Color col) {
n = n + 10;
try{
// Pause, damit Thread sicher
// hier unterbrochen wird
// und das Problem auftritt
Thread.sleep(1);
}
catch (InterruptedException e){;}
g.setColor(col);
g.fillRect(n, n, 10, 10);
}
}
Programm 23 : Programm Squares mit zwei konkurrenzierende Threads, die eine gemeinsame
Ressource benützen.
• Das Programm Squares besteht aus 3 Threads counter1, counter2 und das SquaresApplet
• Der Thread counter1 zeichnet rote Quadrate, Thread counter2 blaue
• Beide Threads benützen dasselbe Objekt count
• Objekt count
• verwaltet eine Zählervariable n
• beim Aufruf der Methode increment(Color col) wird
– der Zähler n um 10 erhöht und nach einer kurzen Pause
– ein farbiges Quadrat (blau oder rot je nach Thread) an der Stelle (n,n) gezeichnet
OOP_V2.7.fm
164
• Das Programm läuft fast immer richtig :
Es wird abwechslungsweise ein rotes (counter1) und ein blaues Quadrat (counter2)
entlang der Diagonalen gezeichnet
• Es kann nun aber folgender Fall eintreten:
• Der Thread counter1 hat gerade den Zähler n um 10 erhöht.
• Bevor counter1 sein Quadrat zeichnen kann, wird er unterbrochen (suspendiert).
• Thread counter2 bekommt nun Prozessorzeit:
– Er erhöht nun den (gleichen) Zähler n ebenfalls um 10.
– Dann wird counter2 darauf ebenfalls supendiert
• Nun erhält counter1 wieder Prozessorzeit und fährt weiter:
– Er zeichnet sein rotes Quadrat aber nicht an der vorgesehenen Stelle (n+10,n+10),
sondern an der Stelle (n+20,n+20)!!
• counter1 wird wieder suspendiert und counter2 fährt weiter:
– Er zeichnet nun sein blaues Quadrat ebenfalls an der Stelle (n+20,n+20).
– Folge:
Das rote Quadrat von counter1 wird jedesmal mit einem blauen überzeichnet!
• Resultat:
es werden nur noch blaue Quadrate gezeichnet!
• Dieser Fall tritt normalerweise sehr selten auf.
• Durch die künstlich eingefügten Pausen im Programm Squares tritt er hier aber praktisch
immer auf.
• Weitere unerwünschte Effekte führen dazu, dass das Programm jedesmal anders abläuft.
• Das eben beschriebene Problem im Programm Squares tritt zwar selten auf. Es muss aber
trotzdem verhindert werden!
• Ein solches Programm ist nicht thread-safe.
• Multithreaded Programme:
• Programme, in denen mehrere Threads gleichzeitig ablaufen können.
• Solche Programme müssen thread-safe sein!
• Programme mit GUIs sind meistens multithreaded Programme!
13.9
Gegenseitiger Ausschluss (mutual exclusion)
• Die Lösung dieses Problems erreicht man durch den sogenannten Gegenseitigen Ausschluss
(mutual exclusion):
Falls eine Methode von mehreren Threads durchlaufen wird und auf eine gemeinsame Ressource
(z.B. Variable) zugreift, so darf jeweils nur ein einziger Thread aufs mal in dieser Methoden sein.
• Lösung in Java:
Die betroffene Methode wird als synchronized deklariert. Im Beispiel der Programms
Squares muss also die Methode increment(Color col) in der Klasse SharedNumber
folgendermasser deklariert werden:
public synchronized void increment(Color col) {...}
• Falls mehrere Methoden in einer Klasse auf eine gemeinsame Ressource zugreifen, dann müssen
alle betroffen Methoden als synchronized deklariert werden.
• Dadurch wird für das betroffene Objekt ein sogenannter Monitor erzeugt.
• Dieser Monitor verwaltet einen sogenannten Lock für das Objekt.
OOP_V2.7.fm
165
• Einen Lock kann man sich wie einen Schlüssel für eine Türe eines Zimmers vorstellen.
• In diesem Zimmer darf nur eine Person aufs Mal sich aufhalten.
• Der Schlüssel wird nun vor die Türe gelegt. Wenn eine Person kommt, nimmt sie den Schlüssel,
geht ins Zimmer und schliesst die Türe von innen.
• Wenn nun eine zweite Person ins Zimmer will, muss sie vor der Türe warten, da sie keinen
Schlüssel zur Türe hat.
• Wenn die erste Person das Zimmer verlässt, legt sie den Schlüssel wieder vor die Türe.
• Der Monitor garantiert mit diesem Prinzip für alle synchronized-Methoden, die im selben
Objekt stehen:
• In jedem Zeitpunkt kann nur ein Thread eine dieser synchronized-Methoden desselben
Objektes abarbeiten.
• Dazu holt sich der Thread zuerst den Lock auf das entsprechende Objekt.
• Versucht ein anderer Thread zur selben Zeit eine der synchronized-Methoden desselben
Objektes aufzurufen, so wird er blockiert, bis der 1. Thread seine synchronized-Methode
verlassen hat. Erst dann bekommt der zweite Thread den Lock auf das Objekt.
• Resultat beim Beispielprogramm Squares:
Durch die Deklaration der Methode increment(Color col) in der Klasse SharedNumber
als synchronized werden abwechslungsweise die blauen und roten Quadrate am richtigen Ort
gezeichnet.
• Allgemeiner Aufbau eines Threads mit gegenseitigem Ausschluss:
while (true) {
tuEtwas1();
greifeAufGemeinsameRessourceZu();
tuEtwas2();
}
• Kritischer Abschnitt greifeAufGemeinsameRessourceZu()
• Dieser Methodenaufruf stellt den kritischer Abschnitt (critical section) des Programmes dar.
• Der kritische Abschnitt bezeichnet den Programmteil, wo die Threads auf eine gemeinsame
Ressource (Variable, File, Bildschirm, Drucker,...) zugreifen.
• Dieser Abschnitt muss mit gegeseitigem Ausschluss geschützt werden.
• Der kritische Abschnitt in einem Programm sollte möglichst klein sein, da sich die Threads in
diesem Abschnitt gegenseitig blockieren.
13.10 Das Produzent-Konsument-Problem
In diesem Abschnitt soll das Problem untersucht werden, wie zwei Threads zusammenarbeiten können.
13.10.1 Fall 1 : Produzent-Konsument-Problem ohne Warteschlange
• Der eine Thread, der sogenannte Produzent, erzeugt Daten für einen anderen Thread.
Beispiele: Einen Wert, ein Objekt, eine Datei
• Der andere Thread, der sogenannte Konsument, holt die Daten ab, sobald sie vorhanden sind.
• Der Konsument muss jeweils warten, bis die Daten vorhanden sind.
• Nachdem der Produzent die Daten bereitgestellt hat, muss er ebenfalls warten, bis die Daten vom
OOP_V2.7.fm
166
Konsument abgeholt wurden. Erst dann darf er wieder neue Daten bereitstellen. Ist dies nicht der
Fall, können Daten verloren gehen, wenn der Konsument langsamer ist als der Produzent.
• Beispiel: Uhr
Wir möchten eine Echtzeituhr entwickeln gemäss folgendem Konzept:
Die Uhr besteht aus zwei Threads, einer für die Sekunden, der andere für die Minuten. Der
Sekunden-Thread zählt jeweils 60 Sekunden und schickt dann dem Minuten-Thread ein TickSignal. Beim Erhalt dieses Signals zählt der Minuten-Thread eine Minute weiter. Die beiden
Threads kommunizieren via ein gemeinsames Objekt, das das Tick-Signal verwaltet.
• Version 1:
synchronized tick(){
Sekunden++
Minuten++
}
synchronized waitForTick(){
Minuten-Tick
erzeugen
Auf MinutenTick warten
}
Tick-Tock-Obj
Figur 36: 1. Version des Clock-Programms mit 2 interagierenden Threads
• Die erste Version des Programms besteht aus folgenden 4 Objekten:
• Sekunde-Objekt:
– zählt bis 60 Sekunden.
– Gibt dann ein Signal (tickHappens = true) an den Minute-Thread
• Minute-Objekt:
– wartet auf Signal (tickHappens == true) vom Sekunde-Thread
• TickTock-Objekt:
– Verwaltet das Tick-Signal (Boolean-Variable tickHappens) und regelt den Zugriff
darauf.
– Garantiert den gegenseitigen Ausschluss der beiden Threads beim Zugriff auf die
gemeinsame Variable.
– Stellt die 2 synchronized-Methoden tick() und waitForTick() zur Verfügung,
um tickHappens zu setzen bzw. abzufragen.
• ScreenMonitor-Objekt
– Verwaltet einen Monitor für das Graphics-Objekt und macht es damit thread-safe.
– Garantiert, dass nur ein Thread aufs Mal auf das Graphics-Objekt zugreift
OOP_V2.7.fm
167
• Programmcode:
import java.awt.*;
import java.applet.Applet;
public class Clock extends Applet {
public void init() {
Graphics g = getGraphics();
ScreenMonitor screen = new ScreenMonitor(g);
Font f = new Font("SansSerif",Font.BOLD,18);
g.setFont(f);
TickTock minuteTick = new TickTock1();
Minute minute = new Minute(screen,minuteTick);
minute.start();
Second second = new Second(screen,minuteTick);
second.start();
}
}
-------------------------------------------------------------------import java.awt.*;
// Verwaltet die Minuten
public class Minute extends Thread {
private int minutes = 0;
private ScreenMonitor screen;
private TickTock minuteTick;
public Minute(ScreenMonitor screen,TickTock minuteTick) {
this.screen = screen;
this.minuteTick = minuteTick;
}
OOP_V2.7.fm
168
public void run() {
while (true) {
minuteTick.waitForTick();
// Auf Tick warten
if (minutes == 59) {
minutes = 0;
// Nach 1h Minuten zuruecksetzen
}
else {minutes++;}
screen.display(minutes + " Minuten",10,60);
}
}
}
-------------------------------------------------------------------import java.awt.*;
// Verwaltet die Sekunden
public class Second extends Thread {
private int seconds = 0;
private ScreenMonitor screen;
private TickTock minuteTick;
public Second(ScreenMonitor g, TickTock minuteTick) {
this.screen = g;
this.minuteTick = minuteTick;
}
public void run() {
while (true) {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
OOP_V2.7.fm
169
System.err.println("Exception");
}
if (seconds == 59) {
// nach 60 Sek
minuteTick.tick();
// Tick-Signal senden
seconds = 0;
// Sekunden zuruecksetzen
}
else {seconds++;}
screen.display(seconds + " Sekunden",10,20);
}
}
}
--------------------------------------------------------------------
// Verwaltet das Tick-Signal
// Version 1
public class TickTock1 implements TickTock {
private boolean tickHappens = false;
public synchronized void waitForTick() {
while (!tickHappens){;}
// wartet bis tickHappens = true
tickHappens = false;
}
public synchronized void tick() {
tickHappens = true;
}
}
Figur 37: 1. Version des Clock-Programms
• Ablauf des Programms:
• Das Clock-Objekt startet den Minute- und den Second-Thread
• Minute-Thread absolviert folgende Endlosschleife:
1. wartet, bis eine Minute vorbei ist:
– dies macht er, indem er die Methode waitForTick() in TickTock aufruft
– In der Methode waitForTick() wartet der Thread, bis tickHappens = true.
2. Nachdem tickHappens true geworden ist, wird sie von waitForTick() sofort
OOP_V2.7.fm
170
wieder auf false gesetzt.
3. Danach wird der Minutenzähler aufdatiert und die Minuten angezeigt.
• Second-Thread führt folgende Endlosschleife aus:
1. Wartet eine Sekunde, erhöht dann den Sekundenzähler und zeigt die Anzahl Sekunden an.
2. Falls 60 Sekunden erreicht sind:
– Aufruf der Methode tick()
– In dieser Methode wird tickHappens auf true gesetzt.
• Probleme bei dieser Version:
• Programm hängt sich auf nach 59 Sekunden!
• Busy-Wait: while(!tickHappens){;}
Wartender Thread braucht die ganze CPU-Zeit für sich!!
muss vermieden werden.
• Aufgabe:
Überlegen Sie sich, wieso sich dieses Programm nach 59 Sekunden aufhängt. Beachten Sie
dabei, dass die Methoden im TickTock-Objekt synchronized sind.
• Lösung:
• Lösung des obigen Blockierungsproblems:
Um das obige Blockierungsproblem zu lösen, werden spezielle Wecksignale verwendet. Diese
Wecksignale beziehen sich in Java immer auf das Objekt, worin sie erzeugt werden.
• Klasse Object stellt 2 Methoden zur Verfügung für den Umgang mit Wecksignalen:
• wait()
• notify()
• wait()
• Dies ist die Methode, die auf ein bestimmtes Wecksignal wartet. Das Wecksignal ist bestimmt
durch das Objekt, von dem die wait()-Methode aufgerufen wird.
• Der Thread, der die Methode wait() aufruft, legt sich schlafen (suspended) bezüglich des
Objektes, von dem aus er wait()aufruft.
• Der Thread wartet sodann auf ein Wecksignal bezüglich dieses Objektes.
• Der schlafende Thread verbraucht keine CPU-Zeit mehr.
• Wichtig:
Der wartende Thread gibt den Lock auf das Objekt, in dem er sich befindet und das er damit
blockiert hat, frei, sobald er sich schlafen legt. Nun können andere Threads ebenfalls die
synchronized-Methoden desselben Objektes aufrufen.
• Sobald der schlafende Thread ein Wecksignal bezüglich dieses Objektes erhält, läuft er weiter.
• Nach dem Aufwecken muss der Thread zuerst wieder einen Lock auf das TickTock-Objekt
ergattern, bevor er weiterlaufen darf. Es darf ja nur ein Thread aufs Mal in einer
synchronized-Methode desselben Objektes laufen.
OOP_V2.7.fm
171
• Beachten Sie:
Wecksignale, die in anderen Objekten erzeugt werden, haben keinen Einfluss auf die schlafenden
Threads in diesem Objekt.
• notify()/notifyAll()
• Dies ist die Methode, die ein bestimmtes Wecksignal erzeugt. Das Wecksignal ist bestimmt durch
das Objekt, von dem die notify()-Methode aufgerufen wird.
• Der Thread, der notify() aufruft, erzeugt ein Wecksignal bezüglich des Objektes, von dem er
die notify()-Methode aufruft.
• Schläft kein anderer Thread bezüglich dieses Objektes, so passiert nichts. Das Wecksignal geht
ungenützt verloren.
• Schläft aber ein Thread bezüglich dieses Objektes, so wird dieser aufgeweckt. Das Wecksignal
wird damit von diesem Thread konsumiert.
• Schlafen mehrere Threads bezüglich dieses Objektes, dann wird irgend einer der schlafenden
Threads geweckt. Dieser konsumiert damit das Wecksignal. Die anderen Threads schlafen
weiter, bis ein weiteres Wecksignal bezüglich dieses Objektes erzeugt wird.
• notifyAll()
• weckt alle bezüglich diesem Objekt schlafenden Threads.
• nur der kann weiter laufen, der danach auch den Lock auf das Objekt erhält und dessen
Bedingung zum weiterlaufen erfüllt ist.
• Weil bei einem einfachen notify() nur einer der schlafenden Threads geweckt wird, kann es
passieren, dass der falsche Thread geweckt wird, was zu Deadlocks führen kann.
• Es ist deshalb zu empfehlen, immer notifyAll() statt bloss notify() zu verwenden.
• Das Wecksignal hat keinen Einfluss auf Threads, die bezüglich eines anderen Objektes warten,
d.h. auf Threads, die die wait()-Methode eines anderen Objektes aufgerufen haben.
• Version 2:
Die Version 2 des Clock-Programms besitzt eine TickTock-Klasse, die Wecksignale verwendet:
synchronized tick(){
Sekunden++
Minuten++
}
synchronized waitForTick(){
Minuten-Tick
erzeugen
Auf MinutenTick warten
}
Tick-Tock-Obj
Figur 38: Version 2 der Clock-Programms mit Wecksignalen.
• Version 2 der Klasse TickTock ist folgendermassen aufgebaut (vgl. Programm 24):
• Methode waitForTick()
– ruft wait() auf
Minute-Thread legt sich schlafen
wartet auf ein Wecksignal bezüglich TickTock
Objekt minuteTick wird freigegeben
OOP_V2.7.fm
172
• Methode tick()
• ruft notifyAll()auf
setzt tickHappens auf true
damit wird ein Wecksignal bezüglich TickTock erzeugt
• Neuer Programmablauf mit der zweiten Version von TickTock:
• Ablauf des Minute-Thread
1. ruft waitForTick() auf
ruft die Methode wait()bezüglich des TickTock-Objekts auf.
legt sich schlafen bezüglich des TickTock-Objekts
2. wird vom Second-Thread aufgeweckt mit notifyAll()
– muss dann nochmals prüfen, ob tickHappens == true ist. Es könnte sein, dass
ein anderer Thread kurz vorher aufgewacht ist und tickHappens wieder auf false
gesetzt hat.
– falls tickHappens == true ist, setzt der Thread tickHappens gleich wieder
auf false und kehrt aus der waitForTick-Methode zurück
– datiert sodann die Minuten auf und zeigt sie an
3. beginnt dann mit der nächsten Iteration
• Ablauf des Second-Thread:
1. zählt Sekunden bis es 60 sind.
2. ruft tick() auf
– setzt tickHappens auf true
– erzeugt Wecksignal im TickTock-Objekt
3. verlässt tick()-Methode wieder
4. datiert die Sekunden auf und zeigt sie an
5. beginnt dann mit nächster Iteration
• Programmcode der Version 2 von TickTock:
public class TickTock {
private boolean tickHappens = false;
public synchronized void waitForTick() {
while (!tickHappens){
try {
wait();
}
catch (InterruptedException e) {
System.err.println("Exception");
}
}
tickHappens = false;
}
OOP_V2.7.fm
173
public synchronized void tick() {
tickHappens = true;
notifyAll();
}
}
Programm 24 : Version 2 der Klasse TickTock mit Wecksignalen
• Beachten Sie:
• Bei der 2. Programmversion von TickTock wartet der Second-Thread nicht, bis der
Minute-Thread das Tick-Signal abgeholt hat.
• Dies spielt in diesem Fall keine Rolle. Im allgemeinen sollte der Produzent jedoch warten, bis der
Konsument die Daten abgeholt hat.
• Dritte Version von TickTock:
In der dritten Version soll nun der Second-Thread warten, bis Minute-Thread Wecksignal
abgeholt hat.
• Programmcode der Version 3 der Klasse TickTock:
// Vollständige Lösung des Produzent-Konsument-Problems
// Konsument wartet, bis Produzent das Produkt bereitstellt.
// Produzent wartet bis, bis Konsument das Produkt
// abgeholt hat, bevor er das neue bereitstellt.
public class TickTock3 implements TickTock {
private boolean tickHappens = false;
public synchronized void waitForTick() {
while (!tickHappens){
try {
wait();
// warte auf tickHappens
}
catch (InterruptedException e) {
System.err.println("Exception");
}
}
tickHappens = false;
notifyAll();
}
OOP_V2.7.fm
174
public synchronized void tick() {
while(tickHappens){
try {
wait();
// warte bis Konsument tickHappens abgeholt hat
}
catch (InterruptedException e) {
System.err.println("Exception");
}
}
tickHappens = true;
notifyAll();
}
}
Figur 39: Version 3 des Clock-Programms mit vollständiger Lösung des ProduzentKonsumentproblems
• Allgemeine Lösung des Produzent-Kosument-Problems (ohne Warteschlange):
Thread 1: Produzent
Thread 2: Konsument
while(weiterso){
Item produzieren;
Warten, bis letztes
Item abgeholt;
Gemeinsames Objekt
blockieren;
Item bereitstellen;
Gemeinsames Objekt
freigeben;
Konsument wecken;
}
while(weiterso){
Warten, bis nächstes Item
produziert ist;
Gemeinsames Objekt blockieren;
Item abholen;
Gemeinsames Objekt freigeben;
Produzent wecken;
}
Tabelle 6: Allgemeine Lösung des Produzent-Konsument-Problems
13.10.2 Fall 2: Produzent-Konsument-Problem mit Warteschlange
• Das Produzent-Konsument-Problem mit Warteschlange ist durch folgenden Ablauf gekennzeichnet:
• Produzent-Thread und Konsument-Thread haben eine gemeinsame Warteschlange
• Der Produzent füllt Daten in die Warteschlange
• Der Konsument holt Daten aus der Warteschlange
• Der Konsument muss beim Holen der Daten nur warten, wenn die Warteschlange leer ist.
• Produzent muss bei Einfüllen der Daten nur warten, wenn die Warteschlange voll ist.
• Beispiel: SingleCafe
Ein Koch ist alleine in seinem Café. Die Gäste schreiben ihre Bestellungen auf einen Zettel und
OOP_V2.7.fm
175
hängen diesen an ein Anschlagbrett mit einer vorgegebenen Anzahl von Plätzen für die
Bestellungen. Der Koch nimmt einen Zettel nach dem anderen und bearbeitet die Bestellung, die
darauf steht.
• Simulation des Cafés mit 3 Threads
• Guest-Thread:
– gibt die Bestellungen auf,
– er legt sie auf Warteschlange (Queue)
• Cook-Thread:
– arbeitet die Bestellungen ab
– er holt sie von Warteschlange
• GUI-Thread:
– erlaubt es, die Arbeitsgeschwindigkeit des Kochs zu beeinflussen
• Guest- und Cook-Thread senden jeweils ein Wecksignal (notifyAll()), nachdem sie
auf Warteschlange zugegriffen haben.
• Falls der andere Thread nicht gerade auf ein Wecksignal wartet, geht das Signal verloren. Es
passiert nichts weiter.
• Wartet der andere Thread hingegen auf ein Wecksignal, so wird er nun aufgeweckt.
• ScreenMonitor verwaltet den Zugriff auf das Graphics-Objekt und macht in thread-safe.
• Programmcode:
import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;
public class SingleCafe extends Applet implements ActionListener {
private Button langsamer, schneller;
private SimpleQueue queue;
// Warteschlange
private Cook koch;
private Guest gast;
private String meal;
private ScreenMonitor screen;
public void init() {
Graphics g = getGraphics();
screen = new ScreenMonitor(g);
Font f = new Font("SansSerif",Font.BOLD,18);
g.setFont(f);
queue = new SimpleQueue(screen);
OOP_V2.7.fm
176
// Koch-Thread
koch = new Cook(screen,queue);
// Gast-Thread
gast = new Guest(queue);
Label lab = new Label("Arbeitsgeschwindigkeit des Kochs ");
lab.setFont(f);
add(lab);
langsamer = new Button("langsamer");
langsamer.setFont(f);
add(langsamer);
langsamer.addActionListener(this);
schneller = new Button("schneller");
schneller.setFont(f);
add(schneller);
schneller.addActionListener(this);
gast.start();
koch.start();
}
public void actionPerformed(ActionEvent event) {
if (event.getSource() == schneller){
koch.schneller();
}
if (event.getSource() == langsamer){
koch.langsamer();
}
}
}
OOP_V2.7.fm
177
----------------------------------------------------------------------------------------- neue Datei
import java.awt.*;
// Verwaltet eine Warteschlange mit Strings
public class SimpleQueue {
private ScreenMonitor screen;
private String[] queue = new String[5];
private int count = 0;
public SimpleQueue(ScreenMonitor screen) {
this.screen = screen;
}
// fuegt ein Element in Queue ein
public synchronized void enter(String item) {
while (count >= queue.length){
try{
wait();
}
catch (InterruptedException e){
System.err.println("Exception");
}
}
queue[count] = item;
count++;
display();
notifyAll();
}
OOP_V2.7.fm
178
// Entnimmt ein Element aus der Queue
public synchronized String remove() {
while (count == 0){
try {
wait();
}
catch (InterruptedException e) {
System.err.println("Exception");
}
System.out.println(count);
}
String item = queue[0];
count--;
// Zaehler um 1 reduzieren
// Eintraege nach vorne schieben
for (int c=0; c < count; c++){
queue[c] = queue[c+1];
}
display();
notifyAll();
return item;
}
//
Queue darstellen
private void display() {
screen.display("QUEUE:",120,120);
screen.clearRect(120,120,150,120);
for (int c=0; c<count; c++){
screen.display(queue[c],120,140 + c*20);
}
}
}
----------------------------------------------------------------------------------------- neue Datei
import java.awt.*;
import java.awt.event.*;
OOP_V2.7.fm
179
// simuliert einen Koch
public class Cook extends Thread {
private ScreenMonitor screen;
private SimpleQueue queue;
private int sleepTime = 1000;
public Cook(ScreenMonitor screen, SimpleQueue q){
this.screen = screen;
queue = q;
}
public void run(){
while (true){
// bearbeitet regelmaessig Auftraege
try{
Thread.sleep(sleepTime);
}
catch (InterruptedException e){;}
String meal = queue.remove();
screen.clearRect(20,260,200,80);
screen.display(meal + " ist bereit!",50,280);
}
}
// schneller arbeiten
public void schneller(){
sleepTime = sleepTime/2;
}
// langsamer arbeiten
public void langsamer(){
sleepTime = sleepTime*2;
}
}
OOP_V2.7.fm
180
----------------------------------------------------------------------------------------- neue Datei
import java.awt.*;
import java.awt.event.*;
// Simuliert einen Gast, der regelmaessig
// Bestellungen aufgibt
public class Guest extends Thread {
private ScreenMonitor screen;
private SimpleQueue queue;
private int mealNum = 0;
private int sleepTime = 1000;
public Guest(ScreenMonitor screen, SimpleQueue q){
this.screen = screen;
queue = q;
}
public void run(){
while (true){
// regelmaessig Aufraege
// in Queue einfuellen
try{
Thread.sleep(sleepTime);
}
catch (InterruptedException e){;}
queue.enter("Auftrag"+mealNum);
screen.clearRect(50,40,280,40);
screen.display("Guest: Auftrag" + mealNum,50,60);
mealNum++;
}
}
}
Programm 25 : Programm Cafe mit 3 Threads
OOP_V2.7.fm
181
• Fall 1 – Normalfall: Die Warteschlange ist weder leer noch voll.
Gast-Thread
SimpleQueue
enter(String item){
wait();
Koch-Thread
remove(){
wait();
Auftrag
einfügen
Auftrag abholen
notifyAll();
notifyAll();
}
}
warten
tk
warten tg
schneller
langsamer
GUI-Thread
Figur 40: Normalfall im Programm Cafe - Warteschlange weder voll noch leer
• Sowohl der Gast-Thread als auch der Koch-Thread können auf die Warteschlange zugreifen,
ohne warten zu müssen (ausser das SimpleQueue-Objekt ist gerade vom anderen Thread
besetzt).
• Am Ende der enter- bzw. remove-Methoden werden jeweils Wecksignale an den
anderen Thread geschickt. Diese bleiben aber wirkungslos und gehen verloren.
OOP_V2.7.fm
182
• Fall 2: Warteschlange ist leer, Koch möchte eine Bestellung von der Warteschlange nehmen:
Gast-Thread
SimpleQueue
enter(String item){
wait();
Koch-Thread
remove(){
wait();
Auftrag
einfügen
Auftrag abholen
notifyAll();
notifyAll();
}
}
warten
tk
warten tg
schneller
langsamer
GUI-Thread
Figur 41: Fall, wo die Warteschlange leer ist, wenn der Koch-Thread darauf zugreift.
• Koch-Thread ruft Methode String remove() von SimpleQueue auf.
• Da die Warteschlange leer ist:
– ruft Koch-Thread Methode wait() auf
– legt der Koch-Thread sich schlafen und wartet auf ein Wecksignal
– gibt das SimpleQueue-Objekt frei.
• Wenn Gast-Thread das nächste Mal einen Auftrag in die Warteschlange legt, ruft er die
Methode enter(String item) auf.
• Nachdem der Gast-Thread eine Bestellung in die Warteschlange gelegt hat, ruft er die
Methode notifyAll().
• Dadurch schickt der Gast-Thread dem Koch-Thread ein Wecksignal und weckt ihn damit auf.
• Dieser kann aber erst weiterlaufen, nachdem der Gast-Thread die enter-Methode verlassen
hat und den Lock auf das SimpleQueue-Objekt freigibt.
OOP_V2.7.fm
183
• Fall 3: Die Warteschlange ist voll und der Gast-Thread möchte eine Bestellung aufgeben:
Gast-Thread
SimpleQueue
enter(String item){
wait();
Koch-Thread
remove(){
wait();
Auftrag
einfügen
Auftrag abholen
notifyAll();
notifyAll();
}
}
warten
tk
warten tg
schneller
langsamer
GUI-Thread
Figur 42: Fall, wo Warteschlange voll ist, wenn Gast-Thread darauf zugreifen will.
• Gast-Thread ruft die Methode enter(String item) im SimpleQueue-Objekt auf.
• Da die Warteschlange voll ist
– ruft der Gast-Thread wait() auf
– legt der Gast-Thread sich schlafen und wartet auf ein Wecksignal
– gibt er das SimpleQueue-Objekt frei
• Wenn Koch-Thread das nächste Mal einen Auftrag von der Warteschlange nimmt, ruft er die
Methode String remove() auf.
• Nachdem der Koch-Thread eine Bestellung von der Warteschlange genommen hat, ruft er die
Methode notifyAll()auf.
• Dadurch schickt der Koch-Thread dem Gast-Thread ein Wecksignal und weckt ihn damit auf.
• Dieser kann aber erst weiterlaufen, nachdem der Koch-Thread die remove-Methode verlassen
hat und den Lock auf das SimpleQueue-Objekt freigibt.
• Aufgabe:
Überlegen Sie sich, was passiert, wenn
– Der Koch- und Gast-Thread gleich schnell ablaufen.
– Der Koch-Thread schneller ist als der Gast-Thread
– Der Koch-Thread langsamer ist als Gast -Thread
OOP_V2.7.fm
184
• Lösung
• Allgemeine Lösung der Produzent-Konsument-Problems mit Warteschlange
Thread 1: Produzent
Thread 2: Konsument
while(weiterso){
Item produzieren;
Warten, falls
Queue voll ist;
Gemeinsames Objekt
blockieren;
Item in Queue
ablegen;
Gemeinsames Objekt
freigeben;
Konsument wecken;
}
while(weiterso){
Warten, falls Queue
leer ist;
Gemeinsames Objekt blockieren;
Ein Item aus Queue abholen;
Gemeinsames Objekt freigeben;
Produzent wecken;
}
Tabelle 7: Allgemeine Lösung des Produzent-Konsument-Problems mit Warteschlange
13.11 Unterbrüche
• Da die Methoden stop() und suspend() nicht mehr unterstützt werden ab Java 1.2, sollten
die Threads selber regelmässig testen, ob sie ein anderer Thread zum Aufhören auffordert. Dies
geschieht üblicherweise durch das Testen einer boolean-Variable in einer while-Schleife
innerhalb der run()-Methode.
• Falls nun aber der Thread gerade schläft oder auf ein Wecksignal wartet, kann er diese Variable nicht
testen.
• Aus diesem Grund stellt jeder Thread die Methode interrupt() zur Verfügung:
• Beim Aufruf dieser Methode wird das Interrupted-Flag des Threads gesetzt
• Falls der Thread gerade schläft, wird er mit einer InterruptedException aufgeweckt
• Verwendet ein Thread die Methoden sleep(...) oder wait(), so muss er deshalb
diese in einem try-Block aufrufen und die InterruptedException abfangen.
• Die Interrupted-Exception tritt nur auf, wenn die interrupt()-Methode des
Threads aufgerufen wird.
• Falls der Thread nicht schläft, wird nur das Interrupted-Flag gesetzt. Der Thread kann dieses testen
mit der Methode boolean interrupted().
• Der Thread kann selbst entscheiden, was er nach einer interrupt()-Aufforderung tun will:
Entweder den Thread beenden oder weiter machen oder sonst etwas.
OOP_V2.7.fm
185
13.12 Deadlock
• Da Threads Ressourcen blockieren können, kann es Situationen geben, wo das Programm stecken
bleibt, weil sich die Threads gegenseitig blockieren. Diese Situation bezeichnet man als Deadlock.
Dieses Problem haben wir bereits in der ersten Version des Programms TickTock auf Seite 170
angetroffen.
• Anschauliches Beispiel: Dining Philosophers
Figur 43: Veranschaulichung eines Deadlocks
•
•
•
•
5 Philosophen sitzen an einem runden Tisch.
Sie essen oder denken abwechslungsweise.
Zum Essen braucht jeder Philosoph zwei Gabeln. Es hat aber insgesamt nur 5 Gabeln.
Wenn ein Philosoph essen will, wartet er jeweils, bis die beiden Gabeln links und rechts von ihm
frei sind.
• Das Deadlock-Problem
• Es kann folgender Fall eintreten
– Alle Philosophen nehmen gleichzeitig die linke Gabel auf.
– Dann warten sie, bis die rechte Gabel frei ist. (und wenn sie nicht gestorben sind...)
Deadlock
• Deadlocks können auch bei Threads auftreten, z.B. wenn zwei Threads auf etwas warten, was der
andere jeweils gerade besetzt hat:
Thread 1
Thread 2
Ressource A
besetzen
Ressource B
besetzen
Ressource B
besetzen
Ressource A
besetzen
Deadlock, da Thread 1 auf Ressource B und Thread 2 auf Ressource A wartet, aber beide vom
jeweils anderen Thread besetzt sind.
OOP_V2.7.fm
186
• Eine mögliche Lösung, um Deadlocks zu vermeiden:
•
Alle Threads benützen die gemeinsamen Ressourcen immer in der gleichen Reihenfolge
• Am Beispiel von vorher:
Thread 1
Thread 2
Ressource A
besetzen
Ressource A
besetzen
Ressource B
besetzen
Ressource B
besetzen
Es kann kein Deadlock mehr auftreten.
OOP_V2.7.fm
187
14
Java-Übersicht
14.1
Einfache Datentypen
• ganze Zahlen
Typ
Grösse [Bits]
negativer Max.wert
positiver Max.wert
byte
8
-128
127
short
16
-32768
32767
int
32
-2147483648
2147483647
long
64
-9223372036854775808
9223372036854775807
• reelle Zahlen
Typ
Grösse [Bits]
Max.wert
Min.wert
Präzision
float
32
± 3.4e+38
±1.4e-45
6-7 Stellen
double
64
± 1.79e+308
±4.9e-324
14-15 St.
• andere:
Typ
Grösse [Bits]
Wertebereich
Art
boolean
1
true, false
logischer Wert
char
16
\u0000 ... \uFFFF
Unicode-Zeichen
• Spezielle Zeichen:
• '\n'
• '\t'
• '\r'
• '\f'
• '\"'
• '\''
• '\\'
• '\uxxxx'
Neue Zeile
Tabulator
Carriage return (Wagenrücklauf)
Form Feed (neue Seite)
Anführungszeichen
Apostroph
Backslash '\'
Unicode-Zeichen mit Hexcode xxxx
• Typenumwandlung
• Die Typenhierarchie:
• double höchster Typ
• float
• long
• int
• short
• byte
niedrigster Typ
OOP_V2.7.fm
188
• Regel:
Variable mit dem niedrigeren Datentypen wird automatisch in höheren Datentypen
umgewandelt.
• Sonst Umwandlung explizit verlangen mit Typ-Cast: ( Datentyp ) Variablenname
14.2
Operatoren
Zeichen
OperandTyp
Name/Bemerkung
Assoziation
Präze
denz
++
arithmetisch
Pre- (++i) oder Postinkrement (i++)
R
1
--
arithmetisch
Pre- (--i) oder Postdekrement (i--)
R
1
+ , -
arithmetisch
Vorzeichen
R
1
!
boolean
Not / Logisches Komplement
R
1
( typ )
beliebig
Type-Cast / Konvertierung in Datentyp typ
R
1
*
arithmetisch
Multiplikation
L
2
/
arithmetisch
Division
L
2
%
arithmetisch
Rest (Modulo)
L
2
+
arithmetisch
Addition
L
3
+
String
Konkatenation / Zusammenfügen von Strings
L
3
-
arithmetisch
Subtraktion
L
3
<, <=
arithmetisch
kleiner als, kleiner oder gleich
L
5
>, >=
arithmetisch
grösser als, grösser oder gleich
L
5
instanceof
Objekt,
Datentyp
Datentypvergleich / Ist ein Objekt eine Instanz
einer bestimmten Klasse (oder Interface)
L
5
==
einfach
ist gleich / haben den gleichen Wert
L
6
!=
einfach
ist nicht gleich / haben verschiedene Werte
L
6
==
Objekt
ist gleich / referenzieren gleiches Objekt
L
6
!=
Objekt
ist nicht gleich / referenzieren versch. Objekte
L
6
&&
boolean
logisches und
L
10
OOP_V2.7.fm
189
||
boolean
logisches oder
L
11
=
Variable,
beliebig
Zuweisung
R
12
+=, -=, /
=, *=
Variable,
beliebig
Zuweisung mit Operation
(x += a
x = x + a)
R
13
14.3
Variablen
• Lokale Variablen
• Innerhalb einer Methode (allg. eines Codeblocks) deklariert
• Deklaration:
Typ Variablenname [ = Initialwert ] { , Variablenname [ = Initialwert ] };.
• Modifikatoren:
keine (seit Java 1.1: final)
• Sichtbarkeit:
nur innerhalb Methode (Codeblock)
• Instanzvariablen
• = Attribute des Objektes
• Ausserhalb einer Methode deklariert (aber natürlich innerhalb einer Klasse)
• Deklaration :
[ Zugriffsmodifikator ] Typ Variablenname [ = Initialwert ] { , Variablenname [ = Initialwert ] };
• Zugriffsmodifikatoren: public, protected, private
• Sichtbarkeit:
gemäss Zugriffmodifikatoren
• Zugriff auf Instanzvariablen: [ this | objektName ].Variablenname
• Klassenvariablen
• gehören zur Klasse, nicht zum Objekt
• Deklaration:
static Typ Variablenname;
• Zugriff:
Klassenname.Variablenname
• Konstanten
• Deklaration:
[static] final Typ Variablenname [ = Initialwert ];
14.4
Kommentare
• //
• /* ... */
• /** ... */
OOP_V2.7.fm
Kommentar bis Ende der Zeile
Kommentare über mehrere Zeilen (können nicht verschachtelt werden!)
Kommentare, die Java Documentation Generator (javadoc) für Report
verwendet. Müssen vor der Klasse, Methode stehen.
190
14.5
Methoden
• Bestimmen das Verhalten des Objektes
• Deklaration:
[Zugriffsmodifikator] Resultattyp [abstract | final] [synchronized] Methodenname
( [ Parameterliste ] ) [ throws Exception { , Exception} ] Methodenkörper .
• Zugriffsmodifikatoren: public, protected, private
• Resultattyp:
irgend ein Datentyp (inkl. Klassen), void
• Methodenkörper: { ... }
• Parameterliste:
Typ Parametername { , Typ Parametername }
• Parametername: Variablenname
• Parameter:
lokale Variable bezüglich der Methode, wo er deklariert ist.
• Aufruf einer Methode:
• Methode mit Rückgabewert: Rückgabewert sollte irgendwie konsumiert werden, z.B. mit
Variablenname = Objektname.Methodenname( Argumentliste );
• Variablenname: Variable, in der Rückgabewert gespeichert wird
• Mehode ohne Rückgabewert ( void )
Methode kann direkt aufgerufen werden:
Objektname.Methodenname( [ Argumentliste ] );
• Argumentliste: Variablenwert { , Variablenwert }.
• Variablenwert:
– Ausdruck, der einen Wert eines bestimmten Datentyps ergibt (Zahl, Berechnung).
– Datentyp des Variablenwertes muss mit dem des entsprechenden Parameters
übereinstimmen
– Zuordnung der Argumente zu den Parametern: ausschliesslich auf Grund der
Reihenfolge
• Klassenmethoden
• Gehören zur Klasse, nicht zum Objekt
• Deklaration:
[ Zugriffsmodifikator ] [ static ] Resultattyp [ abstract | final ] [ synchronized ]
Methodenname ( Parameterliste ) [ throws Exception { , Exception } ] Methodenkörper .
• Aufruf: [ Variablenname = ] Klassenname.Methodenname( [ Argumentliste ] );
14.6
Klassen und Objekte
• Klasse
• = Beschreibung (Bauplan) von Objekten
• Deklaration
[ public ] [ abstract | final ] class Klassenname [ extends Klasse ]
[ implements Interface { , Interface } ] {
Instanzvariablendeklaration
Konstruktorendeklaration
Methodendeklaration
}.
• Kontruktorendeklaration:
[ public ] Klassenname ( [ Parameterliste ] ){
Konstruktorkörper
}
OOP_V2.7.fm
191
• Objekt
• Besteht aus Attributen (Instanzvariablen) und dazugehörige Methoden
• Deklaration: Klassenname objektname;
• Erzeugung: objektname = new Klassenname( [ Argumentliste ] );
• aktuelles Objekt: this
14.7
Sichtbarkeitsbereiche
• Sichtbarkeitsbereiche von Methoden und Instanzvariablen:
Sichtbarkeit
gleiche Klasse
gleiches
Package
Subklasse
in anderem
Package
andere Klasse
in anderem
Package
private
ja
nein
nein
nein
keiner (package)
ja
ja
nein
nein
protected
ja
ja
ja
nein
public
ja
ja
ja
ja
Zugriffsmodifikator
• Sichtbarkeit von lokalen Variablen
• nur in dem Code-Block, in dem sie deklariert sind (d.h. innerhalb der geschweiften Klammern)
• Beispiele:
Deklaration innerhalb
Sichtbar innerhalb
Methode (Parameter gehören
auch hierzu)
Methode
Schleife (im Kopf oder Körper)
Schleife
Anderer Codeblock (if, try,
etc.)
innerhalb der geschweiften Klammern des
Blocks
• Sichtbarkeit von Klassen
• public
von überall her sichtbar (vorausgesetzt, Zugriff auf das entsprechende Directory ist erlaubt)
• nichts (package) :
nur innerhalb Package sichtbar
• private, protected: nicht erlaubt für Klassen
OOP_V2.7.fm
192
14.8
Weitere Modifikatoren
Modifikator
angewandt auf
Bedeutung
abstract
Klasse
Klasse enthält nicht implementierte Methoden,
kann nicht instanziert werden
Methode
Methodenkörper fehlt, muss von
Subklassen geliefert werden
Klasse
Klasse kann nicht erweitert
werden.
Methode
Methode kann nicht überschrieben
werden
(Intanz-, lokale) Variable
ist eine Konstante
Instanzvariable, Methode
ist eine Klassenvariable (es gibt
nur eine Kopie). Zugriff via
Klassenname.
Methode
ist eine Klassenmethode. Aufruf
via Klassenname
Methode
Objekt wird blockiert, bevor
Methode ausgeführt wird.
(Bei Klassenmethoden wird ganze
Klasse blockiert)
final
static
synchronized
14.9
Reservierte Wörter
abstract
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
extends
final
finally
float
for
goto
if
implements
import
instanceof
int
interface
long
native
new
package
private
protected
public
return
short
static
super
switch
synchronized
this
throw
throws
transient
try
void
volatile
while
assert (ab Java 1.4)
enum (ab Java 5)
14.9.1 Reservierte Methodennamen
clone(), equals(), finalize(), getClass(), hashCode(), notify(),
notifyAll(), toString(), wait().
OOP_V2.7.fm
193
14.10 Anweisungen
• Anweisung ::= Block | Ausdrucksberechnung ; | Auswahlanweisung | Schleifenanweisung |
Leeranweisung.
• Block ::= { { Lokalvariablendeklaration ; | Anweisung } }
• Ausdrucksberechnung ::= Zuweisung | Methodenaufruf | Objekterzeugung.
• Zuweisung ::= Variable = Ausdruck.
• Methodenaufruf ::= Objektname.Methodenname( [ Argumentliste] ).
• Objekterzeugung ::= [ Variable = ] new Klassenname( [ Argumentliste ] ).
14.11 Auswahlanweisungen
• Einfache Auswahl:
if ( Bedingung )
Anweisung
else
Anweisung
• Bei mehr als einer Anweisung, Klammern nicht vergessen!
• Mehrfachauswahl:
switch ( Zahlausdruck ) {
case Konstante1 : { Anweisung } [ break; ]
case Konstante2 : { Anweisung } [ break; ]
...
[ default : { Anweisung } ]
}
• Zahlausdruck:
• Konstante1,2... :
int-, char-Variable
ganze Zahl oder Character (z.B. 3, -1,'a')
14.12 Schleifen
• while ( [ Bedingung ] )
Anweisung
• Bedeutung: Anweisung wird ausgeführt, solange Bedingung erfüllt ist.
• Bei mehr als einer Anweisung, Klammern nicht vergessen!
• do
Anweisung
while ( [ Bedingung ] );
• Bedeutung:
• Anweisung wird 1x ausgeführt
• Anweisung wird solange wiederholt, wie Bedingung erfüllt ist.
• Bei mehr als einer Anweisung, Klammern nicht vergessen!
OOP_V2.7.fm
194
• for ( [ Initialisierung ] ; [ Bedingung ] ; [ Aufdatierung ]
Anweisung
)
• Initialisierung:
• wird ein Mal vor dem Start der Schleife ausgeführt
•
Laufvariable (Zähler) initialisieren
• Bedingung:
• wird vor jedem Schleifendurchlauf getestet
• falls wahr, Schleife wiederholen, sonst Schleife verlassen
– weiter bei 1. Anweisung nach for-Schleife
•
Laufvariable testen
• Aufdatierung:
• wird am Ende jedes Schleifendurchlaufs ausgeführt
•
Laufvariable aufdatieren (z.B. um 1 erhöhen)
• Bedeutung:
Anweisung eine fixe Anzahl mal wiederholt
• Bei mehr als einer Anweisung, Klammern nicht vergessen!
14.13 Arrays
• Arrays sind in Java Objekte.
• Deklaration: Datentyp[] { [] } Arrayname;
• Datentyp: Datentyp der einzelnen Elemente
• Array existiert nocht nicht
• Grösse des Arrays ebenfalls noch nicht bestimmt
• Erzeugung: Arrayname = new Datentyp[ Arraygrösse1 ] { [ ArraygrösseX ] } ;
• 1. Element hat jeweils Index 0
• Anzahl Elemente:
• 1. Dimension: Arrayname.length
• 2. Dimension: Arrayname[0].length ( allg. Arrayname[ reiheNr ].length )
14.14 Strings
• Strings sind in Java Objekte
• Enthalten eine Folge von Zeichen
• Länge: beliebig viele Zeichen
• Jedes Zeichen hat einen Index
• 1. Zeichen hat Index 0
• 2 Klassen: String, StringBuffer
• Strings der Klasse String
• Deklaration und Erzeugung: String stringName [ = " Zeichenfolge " ];
• Zusammenhängen von Strings: str = str1 + str2;
• Strings sind immutable, d.h. jede Änderung an einem String erzeugt ein neues String-Objekt
• Nützliche Methoden:
• Vergleich
– boolean equals(String str)
– boolean equalsIngoreCase(String str)
– int compareTo(String str)
OOP_V2.7.fm
195
• Manipulation
– String replace(char ch1, char ch2)
– String toLowerCase()
– String toUpperCase()
– String trim()
• Untersuchung
– int length()
– String substring(int start, int afterEnd)
– char charAt(int index)
– int indexOf(String str, int stIdx)
– int lastIndexOf(String str, int stIdx)
– boolean endsWith(String str)
– boolean startsWith(String str)
• Reguläre Ausdrücke
Metasymbol
Beispiel
Bedeutung
Menge der gültigen Literale
*
ax*b
0 oder mehrere x
ab, axb, axxb, axxxb,...
+
ax+b
1 oder mehrere x
axb, axxb, axxxb, ...
?
ax?b
x optional
ab, axb
|
a|b
a oder b
a, b
()
x(a|b)x
Gruppierung
xax, xbx
.
a.b
Ein beliebiges Zeichen
aab, acb, aZb, a[b, ...
[ ]
[abc]x
1 Zeichen aus einer Klasse
ax, bx, cx
[-]
[a-h]
Zeichenbereich
a, b, c, ..., h
• Die wichtigsten Methoden der Klasse String, die reguläre Ausdrücke verwenden:
• boolean matches(String regexp)
• gibt an, ob der String dem in regexp angegebenen regulären Ausdruck entspricht.
• Beispiel:
String text = "Hallo Welt", neu;
boolean passt;
passt
passt
passt
passt
=
=
=
=
text.matches("H.*W.*");
//
text.matches("H..o Wel?t");
//
text.matches("H[alo]* W[elt]+");//
text.matches("Hal+o Welt.+");
//
gibt
gibt
gibt
gibt
true zurück
false zurück
true zurück
false zurück
• String replaceAll(String regexp, String replaceStr)
• ersetzt alle Substrings des gegebenen Strings, die dem regulären Ausdruck regexp
entsprechen, durch replaceStr.
• Beispiel:
neu = text.replaceAll(„l+“, „LL“); // neu = „HaLLo WeLLt“
OOP_V2.7.fm
196
• String replaceFirst(String regexp, String replaceStr)
• wie replaceAll, ersetzt aber nur das erste Vorkommen von regexp im gegebenen String.
• Beispiel:
neu = text.replaceFirst(„l+“, „LL“); // neu = „HaLLo Welt“
• String[] split(String regexp)
• teilt den gegebenen String in Substrings auf, wobei Zeichenfolgen, die dem regulären
Ausdruck regexp entsprechen, als Grenzmarken interpretiert werden.
Beispiele:
String[] teile = text.split("[ l]");
// teile[0] = "Ha", teile[1] ="", teile[2] = "o",
// teile[3] = "We", teile[4] = "t"
14.15 Exception
• Werfen einer Exception: throw new Exceptionname(" Meldung ");
• Deklaration einer Methode, die eine Exception wirft:
Methodenname throws Exceptionname { , Exceptionname } {...}
• Abfangen von Exceptions:
try{
Code, der eine XException erzeugen kann
}
catch (XException e){
Code, der XException behandelt
}
{ catch ( OtherException e){ Code, der OtherException behandelt } }
[ finally { Code, der in jedem Fall ausgeführt wird.} ]
14.16 Eigenständige Anwendungen
• Müssen eine Main-Klassenmethode mit folgender Signatur enthalten:
public static void main(String[] args){...}
• String-Array args ist genau so gross, wie die Anzahl der übergebenen Argumente
14.17 Vererbung
• Deklaration:
class Subklassenname extends Superklassenname {
Instanzvariablendeklarationen
Methodendeklarationen
Konstruktorendeklarationen
}
• Subklasse
• Erbt alle Instanzvariablen und Methoden der Superklasse.
• Erhält keine Kopien, sondern Zugriff auf die Instanzvariablen und Methoden der Superklasse.
• (ausser Methode / Variable ist als private deklariert)
• Kann zusätzliche Methoden und Instanzvariablen deklarieren
OOP_V2.7.fm
197
• Kann Methoden überschreiben (exakt identischer Methodenkopf)
• Zugriff auf überschriebene Methoden/Variablen mit super
• Kann Methoden überladen (gleicher Name, aber unterschiedliche Parameterliste, Rückgabetyp)
• Eine Subklasse kann nur von einer Superklasse erben (Einfachvererbung)
14.18 Threads
•
•
•
•
Programmteil, der eigenständig abläuft (ev. parallel zu anderen)
Threads laufen scheinbar parallel
Scheduler teilt Prozessorzeit auf die laufbereiten Threads auf
Erzeugung
• 1. Möglichkeit
• Eigene Klasse von Thread ableiten
– muss run-Methode überschreiben.
• Objekt der eigenen Klasse erzeugen
• 2. Möglichkeit
• Eigene Klasse implementiert Runnable
– class MeineKlasse implements Runnable
– muss Methode run() zur Verfügung stellen
• Objekt aus der eigenen Klasse erzeugen
– MeineKlasse meinObj = new MeineKlasse();
• Thread-Objekt erzeugen:
– Thread meinThread = new Thread(meinObj);
• Thread-Methoden:
• start()
startet Thread (ruft die Methode run() des Threads auf)
• stop()
hält Thread an (mit einer Exception) // nicht mehr verwenden
• suspend()
unterbricht Ausführung des Threads // nicht mehr verwenden
• resume()
lässt Thread weiterfahren
// nicht mehr verwenden
• sleep(int ms)
gerade ausgeführter Thread schläft für ms Millisekunden
• yield()
gerade ausgeführter Thread übergibt Kontrolle anderen Threads
• join()
wartet bis dieser Thread beendet ist
• setName(String str)
Name des Threads auf str setzen
• String getName()
Name des Threads auslesen
• setPriority(int pri)
Priorität des Threads auf pri setzen
(vordefinierte Werte: MAX_PRIORITY, MIN_PRIORITY,
NORM_PRIORITY)
• int getPriority()
Priorität des Threads auslesen
• boolean isAlive()
gibt ab, ob Thread noch läuft
• true
Thread runnable oder blocked
• false
Thread new oder dead
• wait()
legt Thread bezüglich Objekt schlafen. Wartet auf Wecksignal
• notify()
schickt Wecksignal an einen der Threads, die im selben Objekt
schlafen
• notifyAll()
schickt Wecksignale an alle Threads, die im selben Objekt
schlafen
OOP_V2.7.fm
198
14.19 Interface
• Deklaration:
[ public] [abstract] interface Interfacename [ extends Interface
{, Interface } ] { Interfacekörper }
• Interfacekörper:
Konstantendeklarationen
abstrakte Methodendeklarationen
• Methoden sind alle public und abstract auch wenn sie nicht so deklariert sind.
• Eine Klasse kann mehrere Interfaces implementieren
• Ein Interface kann andere Interfaces erweitern
• Interface kann nicht instanziert werden
• Interface definiert einen neuen Datentyp
14.20 Abstrakte Klassen
• Schlüsselwort: abstract
• Eine Klasse ist abstrakt, wenn sie mindestens eine abstrakte Methode enthält und/oder abstract
deklariert ist.
• Kann nicht instanziert werden
• Subklassen müssen alle abstrakten Methoden implementieren, sonst sind sie ebenfalls abstrakt.
14.21 Packages
• Deklaration: package Packagename;
• muss 1. Statement in Datei sein
• Alle Klassen desselben Packages müssen im gleichen Directory sein
(Direktory-Name = Packagename)
• Klassen importieren aus Packages:
• import Packagename.Klassenname; // importiert eine Klasse
• import Packagename.*;
// importiert alle Klassen des
Package
14.22 Enumeration (seit Java 5)
• Deklaration von Aufzählungstypen
enum Enumtyp { el_1, el_2, ..., el_N }
• Normalerweise wie eine eigene Klasse deklariert, d.h. ausserhalb anderer Klassen
• Aufzählungstypen können nicht innerhalb von Methoden deklariert werden
• Beispieldeklaration:
• enum Tag { MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG,
SAMSTAG, SONNTAG }
• Deklaration einer Variablen eines Aufzählungstyps
• Wie eine Variable einer Klasse
Tag einTag = Tag.MONTAG;
OOP_V2.7.fm
199
• Umgang mit Aufzählungstypen
• wie mit normalen Referenzdatentypen
• Zugriff auf die Elemente des Aufzählungstyp wie auf eine Klassenkonstante:
einTag = Tag.DIENSTAG;
• Vergleiche/Zuweisungen sind nur mit Aufzählungswerten desselben Aufzählungstyps erlaubt
• Aufzählungswerte können auch in switch-Anweisungen verwendet werden
• Bei der Deklaration eines Aufzählungstyps Enum wird eine entsprechende Aufzählungs-Klasse
Enum generiert, die verschiedene vordefinierte Methoden enthält:
• static Enum valueOf(String s)
gibt den Aufzählungswert zum entsprechenden Namen (String) zurück
• static Enum[] values()
liefert einen Array mit allen Aufzählungswerten zurück
• int ordinal()
Die Aufzählungswerte weisen ein bestimmte Reihenfolge auf, wobei das erste
Aufzählungselement den Index 0 hat. ordinal() gibt den Index eines Aufzählungselementes
zurück
• int compareTo(Enum e)
vergleicht zwei Aufzählungswerte anhand ihrer Indices
• boolean equals(Enum e)
vergleicht zwei Enum-Werte. Enum-Werte können auch mit == verglichen werden.
• Neben den generierte Methoden können auch eigene Methoden zu Aufzählungsklassen hinzugefügt
werden
Beispiel Tag:
enum Tag {
MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG, SAMSTAG,
SONNTAG;
boolean istWochenende(){
return this == SAMSTAG || this == SONNTAG;
}
}
• Aufruf von Methoden eines Aufzählungstyps, wie bei einem Methodenaufruf eines Objekts:
Tag eintag = Tag.FREITAG;
if (eintag.istWochende()){...}
14.23 Java-Packages
Neuste Informationen über das Java SDK und seine Packages finden Sie
unter:
java.sun.com
OOP_V2.7.fm
200
Herunterladen