Java - Anwendungen mit dem JBuilder erstellen

Werbung
PROGRAMMIEREN MIT
JAVA
Unterrichtsbegleitendes Skript
„Habe Mut, den
eigenen Verstand
zu gebrauchen.“
Kant
C. Endreß
08/2006
1. Grundlagen der Programmierung
1. Grundlagen der Programmierung
Lernziele
☺ Die Begriffe Algorithmus, Programm, Programmiersprache, Compiler und Interpreter kennen.
☺ Wissen, welche grundlegenden Schritte bei der Software-Entwicklung durchlaufen werden und
den Begriff Software-Lifecycle kennen.
☺ Die prinzipiellen Verarbeitungsmodelle kompilierter und interpretierter Programmiersprachen
kennen.
☺ Algorithmen mit Struktogrammen und Programmablaufplänen beschreiben.
Computer und ihre Anwendungen sind aus unserem täglichen Leben sowohl innerhalb der Arbeitswelt als
auch im privaten Bereich nicht mehr wegzudenken. Fast überall werden heutzutage Daten verarbeitet und
Geräte gesteuert. Ziel dieses Kapitels ist es, Sie mit den grundlegenden Begriffen der Informatik vertraut zu
machen.
1.1 Grundbegriffe
Computer
Als Computer bezeichnet man ein technisches Gerät, das schnell und meist zuverlässig
rechnen sowie Daten und Informationen automatisch verarbeiten und speichern kann.
Im Unterschied zu einem normalen Automaten, wie z.B. einem Geld- oder Getränkeautomaten, der nur festgelegte Aktionen ausführt, können wir einem Computer die Vorschriften, nach denen er arbeiten soll, stets neu vorgeben.
Algorithmus
Arbeitsvorschriften oder Handlungsanweisungen wie beispielsweise die Regeln für
Kreditberechnungen einer Bank oder die Anleitung zur Steuerung von Signalanlagen
einer Modelleisenbahn bezeichnet man in der Informatik mit dem Begriff Algorithmus.
Ein Algorithmus ist eine Verfahrensvorschrift zur Lösung eines Problems.
Die Bezeichnung Algorithmus wurde abgeleitet vom Namen des arabischen Mathematikers Muhammad Ibn
Musa Al Chwarismi aus dem 9. Jahrhundert, der als einer der ersten systematische Lösungsvorschriften für
die Lösung von quadratischen Gleichungen in einem Buch zusammenfasste. Die sehr ausgereifte Theorie der
Algorithmen gehört zu den wichtigsten Grundlagen der heutigen Informatik.
Ein Algorithmus muss …
ausführbar sein.
Folgende Vorschrift ist z.B. nicht ausführbar: „Wenn man in 14 Tagen
Zahnschmerzen bekommt, dann ist bereits heute mit dem Zahnarzt ein Termin auszumachen, um die Wartezeit zu verkürzen“
C. Endreß
1 / 17
11/2006
1. Grundlagen der Programmierung
eindeutig beschrieben sein.
Die Abfolge der einzelnen Verarbeitungsschritte muss eindeutig festgelegt sein. Programmiersprachen wurden dazu entwickelt, eine eindeutige Beschreibung sicherzustellen. Ein Algorithmus kann
aber auch in jedem beliebigen anderen Formalismus dargestellt werden, der die eindeutige Interpretation der Verarbeitungsschritte sicherstellt.
endlich sein.
D.h. er muss nach endlich vielen Abarbeitungsschritten ein Ergebnis liefern und anhalten (terminieren).
allgemein sein.
Ein Algorithmus löst ein allgemeines Problem oder eine allgemeine Problemklasse und nicht nur ein
spezielles Problem. Die Wahl eines einzelnen, aktuell zu lösenden Problems aus dieser Klasse erfolgt
durch Parameter. Unter dem Gesichtspunkt der „Wiederverwendbarkeit“ von Programmen ist es besonders wichtig, mit Algorithmen möglichst allgemeine Problemklassen zu lösen.
20 cm
c
10 cm
b
30 cm
a
Spezielles Verfahren:
Die Oberfläche des Quaders mit den
Kantenlängen 30 cm, 10 cm und 20 cm
beträgt:
Allgemeines Verfahren:
Die Oberfläche des Quaders mit
den Kantenlängen a, b und c berechnet sich nach der Formel:
O = 2ab + 2bc + 2ac
2 * 30 cm * 10 cm +
2 * 10 cm * 20 cm +
2 * 30 cm * 20 cm = 2200 cm²
Programme
Um dem Computer einen Algorithmus in einer präzisen Form mitzuteilen, muss man den Algorithmus als
Programm formulieren. Programme bestehen aus speziellen Handlungsanweisungen, die ein Computer
„verstehen“ kann. Programme sind daher konkreter und eingeschränkter als Algorithmen. Sie werden durch
eine Programmiersprache beschrieben und enthalten für die Daten eine bestimmte Darstellung.
Computersystem
Der Computer mit seinen Programmen wird häufig auch als Computersystem bezeichnet.
Generell gesehen setzt sich ein Computersystem zusammen aus den materiellen Teilen, der sogenannten Hardware, und den immateriellen Teilen,
der sogenannten Software. Unter dem Begriff Software versteht man neben den Programmen auch zugehörige Daten und Dokumentationen.
Man unterscheidet Systemsoftware und Anwendungssoftware. Zur erst genannten Gruppe zählt
man überlicherweise das Betriebssystem, Compiler, Datenbanken, Kommunikationsprogramme und spezielle Dienstprogramme. Anwendungssoftware wie beispielsweise Textverarbeitungs- oder Zeichenprogramme dient dem Anwender zum Lösen von Aufgaben.
C. Endreß
2 / 17
11/2006
1. Grundlagen der Programmierung
Informatik
Im Laufe der Entwicklung der Computer- und Softwaretechnik von den Kindertagen der ersten Rechner mit
Transistortechnik und integrierten Schaltkreisen in Großrechnern über die ersten Mikroprozessoren bis zu
den heutigen leistungsfähigen PCs hat sich mit der Informatik eine eigenständige Wissenschaftsdisziplin
entwickelt, die sich mit der theoretischen Analyse und Konzeption sowie der konkreten Realisierung von
Computersystemen befasst.
Programmiersprache
Ein Programm ist ein Algorithmus, der in einer
Programmiersprache formuliert ist. Diese Sprache erlaubt
es den Anwendern bzw. Programmieren, mit dem Computer
zu „sprechen“ und diesem Anweisungen zu geben. Dieses
„Sprechen“ kann auf unterschiedliche Weise erfolgen.
Sprich mit mir!
Programmiersprachen dienen der Kommunikation
Mensch – Maschine und zeichnen sich durch einen exakten Formalismus aus, der gekennzeichnet ist durch
Syntax (Rechtschreibung und Grammatik) und
Semantik (Bedeutung)
Es gibt zum Beispiel Sprachen, die der Prozessor des Computers direkt „versteht“. Man nennt diese
Sprachen Maschinensprachen oder Assemblersprachen. Programme in solchen Sprachen kann der
Prozessor direkt ausführen. Allerdings ist man mit solchen maschinennahen Sprachen stark abhängig
vom Prozessortyp und muss den Algorithmus dem jeweiligen Prozessor anpassen.
Alternativ gibt es sogenannte benutzernahe bzw. problemnahe Programmiersprachen, die man als höhere Programmiersprachen oder auch problemorientierte Programmiersprachen bezeichnet
(z.B. Pascal, Delphi, C/C++, Java). Diese Sprachen ermöglichen problemnahe, prozessorunabhängige
Programmierung. Man benötigt allerdings einen Übersetzer in Form einer speziellen Software, der die
Anweisungen der höheren Programmiersprache in Maschinensprache überführt.
Maschinensprache
01110110
11110110 Assembler
01101101
LD RG1 23
11011100
Frühe problemorientierte
MOV RG7 RG2
00101100
Programmiersprache
ADD RG2 RG1
01001011
ADD
RG2
RG7
10 PRINT “HALLO“
10011010
LD RG5
Java
2035
SET A = 7
DIV RG2
RG4
30 GOSUB F1
public class HelloWorld {
MOV RG5
40 RG7
PRINT “WELT“
public static void main(String[] args) {
50 GOTO 130 System.out.println(“Hallo Welt!”);
}
}
C. Endreß
3 / 17
11/2006
1. Grundlagen der Programmierung
Programmieren
Unter Programmieren versteht man eine Tätigkeit, bei der unter Verwendung einer gegebenen Programmiersprache ein gestelltes Problem gelöst wird.
Programmieren heißt also nicht einfach nur, ein Programm einzutippen. Normalerweise ist eine ganze
Reihe von Arbeitsschritten nötig, bis ein gestelltes Problem zufrieden stellend mit dem Computer gelöst
ist. Die Bezeichnung Programmieren umfasst daher meist eine ganze Kette von Arbeitsvorgängen, beginnend bei der Analyse des Problems und endend bei der Kontrolle oder Interpretation der Ergebnisse.
Oft stellt dabei die Analyse den aufwändigsten Teil dar.
Bei der Problemanalyse oder auch Modellierung müssen wir meistens ein umgangssprachlich formuliertes Problem analysieren und so aufbereiten (modellieren), dass sich die einzelnen Teilprobleme leicht
und übersichtlich programmieren lassen.
Dazu erstellen wir eine algorithmische Beschreibung für die Lösung des Problems bzw. seiner Teilprobleme. Man fasst diese Beschreibungen auch unter dem Begriff Entwurf zusammen.
Anschließend wird der Entwurf auf einem Computersystem implementiert, d.h. realisiert und umgesetzt. Dabei nennt man das Übertragen der algorithmischen Beschreibung in eine Programmiersprache
und Eingeben des Programms in den Computer auch Kodierung.
Programme, die zum ersten Mal kodiert werden, sind fast niemals fehlerfrei. Man unterscheidet sogenannte Syntaxfehler, die durch einfache Schreibfehler oder falschen Einsatz von Sprachelementen entstehen, und sogenannte Semantikfehler, die bei falschem logischem Aufbau des Programms auftreten.
Während Syntaxfehler bereits bei der Kodierung vom Compiler erkannt werden, zeigen sich Semantikfehler erst beim Ablauf des Programms. Auch insgesamt gesehen kann ein Programm eine falsche Struktur haben, was meistens auf eine fehlerhafte Problemanalyse zurückzuführen ist. Selbst nach umfangreichen Tests müssen „richtig“ erscheinende Programme nicht zwangsweise logisch korrekt sein. Erfahrungsgemäß enthält ein hoher Prozentsatz aller technisch-wissenschaftlichen Programme auch nach umfassenden Tests noch Fehler, die nur durch hohen Aufwand oder durch Zufall entdeckt werden.
Bedenken Sie beim Programmieren, dass Programme nicht nur vom Autor, sondern besonders bei industriellen Software-Entwicklungen von vielen Personen gelesen werden. Deshalb gilt:
Eine Dokumentation der Programme ist unerlässlich.
Das Testen, Warten und Pflegen von Software wird durch gute Dokumentationen erheblich erleichtert.
Das reduziert Kosten!
C. Endreß
4 / 17
11/2006
1. Grundlagen der Programmierung
Der Software-Lifecycle
Analyse
Ziel: exakte, eindeutige und vollständige Beschreibung des zu lösenden Problems
Ausdrucksmittel: Umgangssprache, Prozessdiagramme, UML
Unabhängig von technischen Systemen
Was soll gelöst werden?
Wartung
Sichert reibungslosen
Betrieb eines Programms
im Dauereinsatz
Entwurf
Software lebt länger als
Hardware
Wie soll es gelöst werden?
Ziel: Bauplan des Programms,
Algorithmische Beschreibung
Testen
Ausdrucksmittel: Struktogramme, Programmablaufpläne, UML, Pseudocode, Umgangssprache
Ziel: korrektes, robustes
Programm
Implementierung sichert
nur Grundfunktion
Systematisches Testen ist
schwierig
Implementierung
Ziel: Funktionsfähiges Programm
Ausdrucksmittel: Programmiersprache
C. Endreß
5 / 17
11/2006
1. Grundlagen der Programmierung
Verarbeitungsmodelle
Grundsätzlich gibt es für die Überführung von Programmen, die in problemorientierten Programmiersprachen
formuliert wurden, zwei verschiedene Verarbeitungsmodelle.
Compiler
Ein Übersetzer, der problemorientierte Programme in maschinennahe Programme übersetzt, heißt Compiler. Der Compiler übersetzt das gesamte Quell-Programm (geschrieben in der Programmiersprache) in ein Ziel-Programm bzw. eine ausführbare Datei (executable). Während der Übersetzung prüft
der Compiler, ob die Syntax (Rechtschreibung) des Programms fehlerfrei ist.
=> Hohe Effizienz
Das Zielprogramm kann beliebig oft ausgeführt werden ohne erneutes Kompilieren.
Jede Plattform erfordert einen eigenen Compiler, mit dem das Zielprogramm zu übersetzen ist.
Programmiersprachen sind oft nicht plattformunabhängig definiert.
Traditionelle Compilersprachen: Pascal, C, C++
Beispiel:
Programmausführung eines C++-Programms
Quell-Programm in C++
Compiler für
C++ und Windows
Compiler für
C++ und Linux
Compiler für
C++ und MacOS
Ziel-Programm
für Windows
Ziel-Programm
für Linux
Ziel-Programm
für MacOS
Legende:
Teilprodukt
Tätigkeit
C. Endreß
Windows
Linux
6 / 17
MacOS
11/2006
1. Grundlagen der Programmierung
Interpreter
Der Interpreter analysiert das Quell-Programm Anweisung für Anweisung und führt jede Anweisung
sofort aus, bevor er die folgende Anweisung analysiert.
Ein Interpreter ermöglicht die Plattformunabhängigkeit von Programmen.
Interpreter haben eine schlechtere Performance als Compiler.
Populäre interpretierte Sprache: Java
Beispiel:
Programmausführung eines Java-Programms
Quell-Programm in Java
Compiler für Java
Plattformunabhängiger
Byte-Code
Virtuelle Maschine
für Windows
Virtuelle Maschine
für Linux
Virtuelle Maschine
für MacOS
Windows
Linux
MacOS
Legende:
Teilprodukt
Tätigkeit
Die Programmiersprache Java wurde mit dem Ziel entwickelt, plattformunabhängig zu sein. Ein Compiler
übersetzt das Quell-Programm in sogenannten Java-Bytecode, der zwar plattformunabhängig ist, jedoch
nicht unmittelbar ausgeführt werden kann. Erst der Java-Interpreter analysiert den erzeugten Byte-Code
und führt ihn schrittweise aus. Der Interpreter selbst wird vom Prozessor ausgeführt und verdeckt die Eigenschaften des jeweiligen Prozessortyps, wodurch sich eine höhere „Abstraktionsschicht“ bietet. Da man sich
diese Abstraktionsschicht als einen gedachten Prozessor vorstellen kann, spricht man von einer virtuellen
Maschine (virtual machine), abgekürzt VM.
C. Endreß
7 / 17
11/2006
1. Grundlagen der Programmierung
1.2 Kontrollstrukturen
Kontrollstrukturen steuern den Ablauf eines Algorithmus.
Kontrollstrukturen sollen
es ermöglichen, Problemlösungen in natürlicher, problemangepasster Form zu beschreiben,
so beschaffen sein, dass sich die Problemstruktur im Algorithmus widerspiegelt,
leicht lesbar und verständlich sein,
eine leichte Zuordnung zwischen statischem Algorithmustext und dynamischem Algorithmuszustand
erlauben,
mit minimalen Konzepten ein breites Anwendungsspektrum abdecken,
Korrektheitsbeweise von Algorithmen erleichtern.
In den 70er Jahren hat sich herausgestellt, dass man mit den folgenden vier Strukturen auskommt, um den
Ablauf eines Algorithmus bzw. Programms zu steuern:
Sequenz
„Mach was!“
Auswahl
„Mach was, falls …“
Wiederholung
„Mach was wieder und wieder“
Aufruf anderer Algorithmen
„Mach jenes …“
Notationsformen
Die Darstellung dieser vier Kontrollstrukturen kann in verschiedenen Notationen angegeben werden:
Die Struktogramm-Notation
beruht auf einem Vorschlag von Nassi und Shneiderman, daher auch Nassi-Shneiderman-Diagramm genannt, und ermöglicht eine grafische Darstellung von Kontrollstrukturen. Die Notation ist in DIN 66261
genormt.
Die Pseudo-Code-Notation
ist eine textuelle, semiformale Darstellungsform in Anlehnung an problemorientierte Programmiersprachen. Während für die Kontrollstrukturen die Syntax und die Wortsymbole von Programmiersprachen
verwendet werden (z.B. if, switch, while), werden für die Anweisungen entweder verbale Formulierungen oder mehr oder weniger programmiersprachliche Notationen benutzt. Der im Folgenden
benutzte Pseudo-Code orientiert sich an der Programmiersprache Java.
Die Programmablaufplan-Notation (PAP)
benutzt grafische Symbole, die durch Linien miteinander verbunden sind, um Kontrollstrukturen zu beschreiben. PAPs bzw. Flussdiagramme sind seit 1969 in Gebrauch und genormt in DIN 66001.
C. Endreß
8 / 17
11/2006
1. Grundlagen der Programmierung
1.2.1
Sequenz
Erfordert eine Problemlösung, dass mehrere Anweisungen hintereinander auszuführen sind, formuliert man
eine Sequenz bzw. eine Aneinanderreihung. Bei der Sequenz erfolgt die Abarbeitung von oben nach unten.
Sequenz
allgemein
Erläuterung
Ein beliebig groß gewähltes Viereck
wird nach jeder Anweisung mit
einer horizontalen Linie abgeschlossen
Anweisung 1
Struktogramm
Pseudo-Code
Anweisung 2
Die einzelnen Anweisungen werden
durch Semikolon voneinander getrennt.
Anweisung 1;
Anweisung 2;
Anweisung 1
PAP
Einfache Anweisungen werden
durch Rechtecke dargestellt, die
wiederum durch Ablauflinien verbunden werden.
Anweisung 2
Beispiel:
Sequenz zur Berechnung des Mehrwertsteuerbetrags
sequentielle Abfolge der
Verarbeitungsschritte
Eingabe Nettopreis
Multipliziere Nettopreis mit 16 %
Gib Ergebnis aus
1.2.2
Auswahl
Sollen Anweisungen in Abhängigkeit von bestimmten Bedingungen ausgeführt werden, dann verwendet man
das Konzept der Auswahl bzw. Verzweigung.
Es gibt drei verschiedene Auswahl-Konzepte, die jeweils für bestimmte Problemlösungen geeignet sind:
einseitige Auswahl
zweiseitige Auswahl
Mehrfachauswahl
C. Endreß
9 / 17
11/2006
1. Grundlagen der Programmierung
Auswahl
(ein- und zweiseitig)
allgemein
Erläuterung
Ausdruck
ja
Struktogramm
nein
Ja-Anweisung(en)
Pseudo-Code
Nein-Anweisung(en)
Ist der Ausdruck wahr, werden
die Ja-Anweisungen ausgeführt,
sonst werden die NeinAnweisungen ausgeführt.
if ( Ausdruck )
Ja-Anweisung;
else
Nein-Anweisung;
Das Ergebnis des Ausdrucks
muss von Typ boolean sein.
Ausdruck
ja
Ja-Anweisung
PAP
Bei der einseitigen Auswahl fehlt
der Alternativzweig.
nein
Nein-Anweisung
Beispiel:
Ein EDV-Großhändler gewährt auf optische PC-Mäuse Mengenrabatt von 5 % ab einer Bestellmenge von 100
Stück. Diese Rabattregelung können wir mit einem Struktogramm oder einem PAP folgendermaßen darstellen:
Bestellmenge < 100
ja
Rabatt = 0
Bestellmenge < 100
nein
nein
ja
Rabatt = 5 %
Berechne Rabattbetrag
Rabatt = 0
Rabatt = 5 %
Berechne Rabattbetrag
C. Endreß
10 / 17
11/2006
1. Grundlagen der Programmierung
Muss zwischen mehr als zwei Möglichkeiten gewählt werden, wird die Mehrfachauswahl verwendet.
Mehrfachauswahl
allgemein
Erläuterung
Ausdruck
Fall 1
Fall 2
Fall 3
default
Struktogramm
Anw. 1
Pseudo-Code
Anw. 2
AusnahmeAnweisungen
Anw. 3
switch ( Ausdruck )
{
case konstanterAusdruck1;
Anweisung(en); break;
case konstanterAusdruck2;
Anweisung(en); break;
...
default: Anweisung;
}
Anweisung(en);
Der Ausdruck dient als Selektor
zum Auswählen der einzelnen
Fälle. Ist ein entsprechender Fall
nicht aufgeführt, wird die Anweisung hinter default ausgeführt.
Ausdruck
PAP
Fall 1
Fall 2
Fall 3
Fall 4
Anw. 1
Anw. 2
Anw. 3
Anw. 4
Beispiel:
Ein Hardwarehändler gewährt seinen Kunden Treue-Rabatt. Dazu teilt er die Kunden in Kategorien ein, die
unterschiedliche Rabatte erhalten.
Kategorie
C. Endreß
default
1
2
3
Rabatt = 5 %
Rabatt = 10 %
Rabatt = 15 %
11 / 17
Rabatt = 0
11/2006
1. Grundlagen der Programmierung
1.2.3
Wiederholung
Um eine oder mehrere Anweisungen in Abhängigkeit von einer Bedingung zu wiederholen oder um eine
gegebene Anzahl von Wiederholungen zu durchlaufen, verwendet man das Konzept der Wiederholung bzw.
Schleife.
Drei Wiederholungskonstrukte werden unterschieden:
Kopfgesteuerte Schleife: (Wiederholung mit Abfrage vor jedem Wiederholungsdurchlauf)
Ist der Ausdruck im Schleifenkopf wahr, wird der Schleifenkörper durchlaufen. Andernfalls wird mit der
ersten Anweisung hinter dem Wiederholungsblock fortgefahren. Vor jeder weiteren Wiederholung wird
der Ausdruck erneut ausgewertet.
Fußgesteuerte Schleife: (Wiederholung mit Abfrage nach jedem Wiederholungsdurchlauf)
Der Wiederholungsblock wird auf jeden Fall einmal durchlaufen. Weitere Wiederholungen erfolgen nur,
wenn der Ausdruck im Schleifenfuß wahr ist. Andernfalls wird mit der ersten Anweisung hinter dem
Schleifenfuß fortgefahren.
Zählschleife: (Wiederholung mit fester Wiederholungsanzahl)
Liegt bei einem Problem die Anzahl der Wiederholungen von Anfang an fest, dann wird die so genannte
Zählschleife bzw. Laufanweisung verwendet. Die Anzahl der Wiederholungen wird durch eine Zählvariable mitgezählt und die Bedingung so gewählt, dass nach der geforderten Wiederholungsanzahl der
Abbruch erfolgt.
Wiederholung
allgemein
Solange Ausdruck
Wiederholungsanweisung(en)
Wiederholungsanweisung(en)
Struktogramm
Solange Ausdruck
for ( Startausdruck, Endausdruck, Schrittweite )
Wiederholungsanweisung(en)
do
{
Fußgesteuerte Schleife:
Wiederholung mit Abfrage
nach jedem Wiederholungsdurchlauf
Zählschleife:
Wiederholung mit fester
Wiederholungszahl
Fußgesteuerte Schleife
Wiederholungsanweisungen;
}
while ( Ausdruck );
for (Startaudruck, Endeausdruck, Schrittweite)
{
Wiederholungsanweisungen;
}
C. Endreß
Kopfgesteuerte Schleife:
Wiederholung mit Abfrage
vor jedem Wiederholungsdurchlauf
Kopfgesteuerte Schleife
while ( Ausdruck )
{
Wiederholungsanweisungen;
}
Pseudo-Code
Erläuterung
12 / 17
Zählschleife
11/2006
1. Grundlagen der Programmierung
Die PAP-Notation besitzt keine eigenen Darstellungsformen für Schleifenkonstrukte. Wiederholungsstrukturen müssen mit Auswahlsymbolen formuliert werden, wie es das folgende Beispiel zeigt.
Beispiel:
Es sollen alle geraden Zahlen zwischen 1 und 20 ausgegeben werden.
Setze Zahl auf 0
Setze Zahl auf 0
Solange Zahl < 20
Erhöhe Zahl um 2
Zahl < 20
Gib Zahl aus
nein
ja
Erhöhe Zahl um 2
Gib Zahl aus
1.2.4
Aufruf anderer Algorithmen
Soll in einem Algorithmus ein anderer Algorithmus angewendet werden, dann geschieht dies durch einen
Aufruf.
Ein Aufruf erfolgt durch Angabe des Algorithmusnamens, gefolgt von der Liste der aktuellen Parameter.
Nach Ausführung des aufgerufenen Algorithmus wird der rufende Algorithmus hinter der Aufrufstelle
fortgesetzt. Ein Algorithmus kann sich auch selbst aufrufen (rekursiver Aufruf).
Aufruf
allgemein
Erläuterung
Anweisung 1
Struktogramm
Operationsname
(aktuelle Parameter)
Nach Ausführung der aufgerufenen
Operation wird der Ablauf hinter
der Aufrufstelle fortgesetzt
Anweisung 2
Pseudo-Code
Anweisung 1;
Operationsname ( aktuelle Parameter );
Anweisung 2;
Ein Aufruf erfolgt durch Angabe
des Operationsnamens gefolgt von
der Liste der aktuellen Parameter.
Anweisung 1
PAP
Operationsname
(aktuelle Parameter)
Anweisung 2
C. Endreß
13 / 17
11/2006
1. Grundlagen der Programmierung
Beispiel:
Der Bruttopreis einer Ware ergibt sich aus dem Nettopreis zuzüglich der Mehrwertsteuer.
Eingabe Nettopreis
BerechneMehrwertSteuer(Nettopreis)
Bruttopreis = Nettopreis + Mehrwertsteuer
Ausgabe Bruttopreis
Schachtelung von Kontrollstrukturen
Komplexe Abläufe können durch Schachtelung von Kontrollstrukturen beschrieben werden.
Innerhalb von Wiederholungsanweisungen können weitere Wiederholungsanweisungen oder/und Auswahlanweisungen stehen. Im Prinzip kann man die Kontrollstrukturen in beliebiger Kombination beliebig
tief ineinander schachteln.
1.2.5
Strukturierte Programmierung
Man hat nachgewiesen, dass alle Kontrollflüsse durch eine Auswahlkonstruktion und eine Wiederholungskonstruktion beschrieben werden können.
Die vorgestellten Kontrollstrukturen besitzen jeweils genau einen Eingang und einen Ausgang. Zwischen dem Eingang und dem Ausgang gilt das Lokalitätsprinzip, d.h. der Kontrollfluss verlässt den
durch Eingang und Ausgang definierten Kontrollflussbereich nicht.
Struktogramme
Vorteile:
Optimale grafische Darstellung von linearen Kontrollstrukturen, da es nicht möglich ist, Sprünge darzustellen.
Auswahl wird in ablaufadäquater Form dargestellt, d.h., die Alternativen werden vertikal angeordnet,
während in textuellen Darstellungsformen eine horizontale Anordnung erfolgt.
Kann leicht zerlegt und zusammengesetzt werden (Modularisierung)
Der verfügbare Platz ermöglicht die Wahl aussagekräftiger Namen. Der Vereinbarungsteil kann am
Anfang in einem eigenen Kasten beschrieben werden.
Programmablaufplan
Vorteile:
Viele Freiheiten bzgl. Kontrollfluss
Nachteile:
Für grundlegende Kontrollstrukturen wie Mehrfachauswahl und Wiederholungen gibt es keine eigenen Symbole.
Schachtelungsstrukturen sind kaum erkennbar.
Schlechte Darstellung von linearen Kontrollstrukturen aufgrund zu großer Freiheiten.
C. Endreß
14 / 17
11/2006
1. Grundlagen der Programmierung
1.2.6
Erstes Beispiel
Problemstellung:
Schlage einen Nagel in die Wand und hänge ein Bild daran auf!
Verbale Formulierung des Algorithmus:
Schritt
Anweisung
1
Hammer in die Hand nehmen.
2
Nagel mit der anderen Hand auf dem Zielpunkt festhalten.
3
Wiederhole die folgenden Anweisungen solange, bis der Nagel eingeschlagen ist
oder Du genug hast.
{
4
Mit dem Hammer auf den Nagel schlagen.
5
Falls Nagel nicht getroffen
{
6
Einige laute Worte sprechen.
7
Hammer weg werfen.
8
Finger desinfizieren und verbinden.
}
}
9
Falls der Nagel eingeschlagen ist, Bild aufhängen.
10
Sonst jemanden rufen, der sich mit Bilder Aufhängen auskennt.
An dieser Stelle ist es Zeit, dass Sie mal wieder aktiv
werden. Nehmen Sie Papier und Bleistift zur Hand,
und erstellen Sie für den formulierten Algorithmus
einen Programmablaufplan und ein Struktogramm!
C. Endreß
15 / 17
11/2006
1. Grundlagen der Programmierung
1.2.7
Zweites Beispiel
Problemstellung:
Es ist ein Algorithmus zur Berechnung der Summe der natürlichen Zahlen von 1 bis n zu formulieren:
1+2+3+...+n=?
Die Zahl n wird fest vorgegeben.
Seit Carl Friedrich Gauß in seiner Grundschulzeit einmal die natürlichen Zahlen von 1 bis 100 addieren musste, kennt die Mathematik eine geschlossene
Formel für diese Aufgabenstellung:
Summe = n * (n + 1) / 2
Carl Friedrich Gauß
Lösungsansatz
Zuerst n festlegen.
Summe mit 0 festlegen.
Summe durch fortlaufendes Aufaddieren erhöhen.
Mit einem Zähler, der von 1 bis n mitläuft, die Additionen kontrollieren
Summe ausgeben
Verbale Formulierung (für n = 5)
Schritt
Anweisung
1
Setze n auf den Wert 5.
2
Setze Summe auf den Wert 0.
3
Setze Zähler auf den Wert 1.
4
Wiederhole, solange der Zähler kleiner oder gleich n ist
{
5
Addiere den Wert des Zählers zur Summe.
6
Erhöhe den Zähler um 1.
}
7
C. Endreß
Gib den Wert der Summe aus.
16 / 17
11/2006
1. Grundlagen der Programmierung
Schreibtischtest
Schritt
n
Summe
Zähler
Zähler <= n
Schritt
n
Summe
Zähler
Zähler <= n
1
5
?
?
?
5
5
6
3
wahr
2
5
0
?
?
6
5
6
4
wahr
3
5
0
1
wahr
4
5
6
4
wahr
4
5
0
1
wahr
5
5
10
4
wahr
5
5
1
1
wahr
6
5
10
5
wahr
6
5
1
2
wahr
4
5
10
5
wahr
4
5
1
2
wahr
5
5
15
5
wahr
5
5
3
2
wahr
6
5
15
6
falsch
6
5
3
3
wahr
4
5
15
6
falsch
4
5
3
3
wahr
7
5
15
6
falsch
C. Endreß
17 / 17
11/2006
2. Was ist Java?
2. Was ist Java?
Lernziele
☺ Das Verarbeitungsmodell von Java kennen.
☺ Die Entstehungsgeschichte von Java kennen lernen.
☺ Wissen, welche besonderen Merkmale die Sprache auszeichnen.
Quizfrage
Was ist Java?
A: Tropische Südseeinsel
B: Amerikanische Umgangssprache für „Kaffee“
C: Objektorientierte Programmiersprache
D: Die Frau von Bill Gates
Lösung: A, B, C
2.1 Wie Java funktioniert
Nehmen wir an, Sie möchten eine Anwendung (z.B. eine interaktive Partyeinladung) schreiben, die auf den
beliebigen Geräten, die Ihre Freunde besitzen, funktionieren soll.
ss Party {
public cla
ic void
public stat args) {
g[]
main(Strin
.println(”
System.out ”);
Tim
Party bei
Quellcode für
interaktive
Partyeinladung
l
cia
_0 pe
()
ad kes od ect
alo o th bj
0 inv Me g.O
1 <
1 lan
# ava.
J
Party
b ei
Tim
Quelle
Compiler
Code
Erstellen Sie ein
Quelldokument.
Verwenden Sie
als
Programmiersprache Java.
Lassen Sie Ihren
Quellcode durch einen
Quellcode-Compiler
laufen. Der Compiler
prüft, ob Fehler
vorhanden sind und
kompiliert erst, wenn er
sicher ist, dass alles
richtig läuft.
Der Compiler erstellt ein
neues Dokument, das in
Java-Bytecode kodiert ist.
Jedes Gerät, das Java
ausführen kann, kann diese
Datei interpretieren bzw. in
etwas übersetzen, das es
ausführen kann. Der
kompilierte Bytecode ist
plattformunabhängig.
C. Endreß
Party
bei Tim
Method Party()
0 aload_0
1 invokespecial
# 1 <Method
java.lang.Object()>
4 return
Method void …
1/6
Party
bei Tim
Virtuelle Maschinen
Ihre Freunde haben keine
physische Java-Maschine,
sondern alle haben eine
virtuelle (in Software
implementierte) JavaMaschine, die innerhalb Ihrer
elektronischen Geräte läuft. Die
virtuelle Maschine liest den
Bytecode und führt ihn aus.
07/2006
2. Was ist Java?
2.2 Was Sie in Java machen
Sie geben eine Quellcode-Datei ein, kompilieren diese mit dem javac-Compiler und führen den kompilierten
Bytecode anschließend auf einer virtuellen Java-Maschine aus.
import java.awt.*
import java.awt.event;
class Party{
public void erstelleEinladung(){
Frame f = new Frame();
Label l = new Label(“Party bei Tim”);
Button a = new Button(“Sicher doch”);
Button b = new Button(“Ohne mich”);
Panel p = new Panel();
p.add(l);
p.add(a);
p.add(b);
}
// mehr Code
}
Quelle
Geben Sie Ihren Quellcode ein. Dazu reicht
ein einfacher Texteditor.
Speichern Sie den Quellcode unter
Party.java.
Compiler
Kompilieren Sie die Datei Party.java, indem
Sie auf der Kommandokonsole mit dem
Befehl javac die Compiler-Anwendung
aufrufen. Wenn der Compiler keinen Fehler
im Quellcode findet, generiert er die
Bytecode-Datei Party.class.
Method Party()
0 aload_0
1 invokespecial #1 <Method
java.lang.Object()>
4 return
Method void erstelleEinladung()
0 new #2 <Class.java.awt.Frame>
3 dup
4 invokespecial #3 <Method
java.awt.Frame>
Code
Kompilierter Code Party.class.
Virtuelle Maschinen
Führen Sie das Programm aus, indem Sie die
Java Virtual Maschine (JVM) mit der Datei
Party.class starten. Die JVM übersetzt die
Datei in etwas, das die zugrunde liegende
Plattform versteht, und führt das Programm
aus.
Hinweis: Diese Darstellung soll keine Anleitung für Ihr erstes Programm sein, sondern Ihnen nur ein
Gefühl für die Verarbeitung von Java-Programmen vermitteln.
C. Endreß
2/6
07/2006
2. Was ist Java?
Klassen in der Java-Standardbibliothek
2.3 Historisches zu Java
3500
3000
„Duke“ das
Java-Maskottchen
2500
2000
1500
1000
500
0
Java 1.02
1991
C. Endreß
Java 1.1
250 Klassen
500 Klassen
Java 2
(Version 1.2 – 1.4)
Java 5.0
(Version 1.5 und
höher)
Langsam
Etwas schneller
2300 Klassen
Hübscher Name,
hübsches Logo. Spaß
beim Arbeiten, viele
Fehler. Applets sind
die große Sache.
Mehr Fähigkeiten,
freundlicher.
Beginnt, sehr beliebt
zu werden. Besserer
GUI-Code
Viel schneller
3500 Klassen
Kann (manchmal) mit
nativer
Geschwindigkeit
laufen. Ernsthaft,
mächtig. Kommt in
drei Varianten: Micro
Edition (J2ME),
Standard Edition
(J2SE) und Enterprise
Edition (J2EE). Wird
zur Sprache der
Wahl für neue
EnterpriseAnwendungen
(insbesondere
webbasierte) und
mobile Anwendungen.
Mehr Power.
Erleichtert die
Entwicklung
Neben dem
Hinzufügen von mehr
als 1000 Klassen
werden in Java 5.0
größere Änderungen
an der Sprache selbst
vorgenommen, um
das Programmieren
zu vereinfachen.
Außerdem werden
neue Features
hinzugefügt, die in
anderen Sprachen
beliebt sind.
Alles begann mit einer E-Mail, in der der 25-jährige SUN-Programmierer Patrick Naughton
die Firmen- und Produktpolitik seines Arbeitgebers anprangerte. Die Produkte seien zu
akademisch, die Benutzung zu kompliziert. Die Klagen wurden von den SUN-Bossen Scott
McNealy und Bill Joy erhört. Naughton begann, mit James Gosling und Mike Sheridan in
einem neuen Projekt einen Prototyp zur Steuerung und Integration von elektronischen
Konsumgeräten (Toaster, Videorekorder, Fernseher etc.) zu entwickeln.
3/6
07/2006
2. Was ist Java?
1992
Naughton und sein Team bauten ein Gerät, das zur Fernbedienung unterschiedlicher
elektronischer Konsumgeräte eingesetzt werden konnte.
Dieser Prototyp („*7“ StarSeven) glich heutigen Palm-Computern. Er verfügte über
ein Betriebssystem (Green-OS)
einen Portabler Interpreter (Oak)
ein graphisches Subsystem, das eine tastaturlose graphische Oberfläche realisierte
eine Drahtlose Datenübertragung
1994
*7 wurde zwar zur Serienreife entwickelt, die Vermarktung scheiterte jedoch. Zu dieser Zeit
erkannte Bill Joy das Potential des rasant wachsenden World-Wide-Webs und die Bedeutung
einer plattformunabhängigen Programmiersprache, mit der neben aktuellen Inhalten auch
Programme transportiert und ohne Installations- und Portierungsaufwand auf einem
beliebigen Zielrechner ausgeführt werden können.
Daraufhin entwickelten Naughton und Gosling mit der Programmiersprache Oak im Herbst
1994 die erste Version des Browsers WebRunner, der neben der Darstellung von HTMLSeiten auch kleine Java-Programme (Applets) aus dem WWW laden und innerhalb des
Browserfensters ausführen konnte.
1995
Oak wurde unter dem Namen HotJava stabilisiert und weiterentwickelt. Der
Durchbruch für HotJava kam jedoch erst, als die Firma Netscape, die mit dem
Navigator den führenden Browser besaß, sich entschied, die Java-Technologie
von SUN zu lizenzieren. Diese Ankündigung von Netscape am 23. Mai 1995
wird von SUN als offizielle Geburtsstunde von Java gesehen.
1996
SUN gab JDK 1.0 (Java Development Kit) frei.
1999
JDK 1.2 wurde auf den Markt gebracht und in Java 2 Platform umbenannt.
Wichtigste Neuerungen: Swing Toolset, Java 2D API, Drag-and-Drop API
2002
JDK 1.4 bringt Performance-Verbesserungen, neue Bibliotheken, XML-Unterstützung
2004
JDK 1.5 bringt weitere Verbesserungen und Ergänzungen wie u.a. generische Klassen und
Enumerations. Sun ändert die Bezeichnung von JDK 1.5 in J2SE 5.0.
2.4 Java-Programme
Applications
Applications sind Anwendungen, die selbständig auf einem Computer laufen.
Voraussetzung: Eine Java-Laufzeit-Umgebung (JRE = Java-Runtime-Environment) ist auf dem System
installiert.
Applets
Applets sind Programme, die über das Internet von einem Web-Server geladen und in einem WebBrowser ausgeführt werden. Das Sicherheitskonzept der Java-Applets verhindert, dass Applets auf lokale
Daten des Systems, auf das sie geladen werden, zugreifen.
Voraussetzung: JRE im Browser oder externes JRE durch Java-PlugIn vorhanden.
C. Endreß
4/6
07/2006
2. Was ist Java?
2.5 Eigenschaften von Java
Einfach
Objektorientiert
Plattformunabhängig
Robust
Interpretiert
Nebenläufig (unterstützt multi-threading)
Verteilt
Sicher
Dynamisch
2.5.1
Java ist einfach
Zu großen Teilen wurde Java von C / C++ abgeleitet. Viele potentielle Fehlerquellen von C & C++
wurden jedoch entfernt. So gibt es in Java z.B. keine Zeiger.
Java kennt nur 8 elementare Datentypen.
Es gibt nur 50 reservierte Wörter.
Java verfügt über ein Memory Management und eine Garbage Collection:
Nicht mehr referenzierte Objekt werden automatisch durch die Garbage Collection aus dem Speicher
entfernt. Das vermeidet memory leaks.
Man bekommt insgesamt eine bessere Performance durch effizientes Memory Management.
Java verfügt über eine integrierte Synchronisation von Threads.
2.5.2
Java ist objektorientiert
Java Programme bestehen aus Klassen. Jedes Programm besitzt mindestens eine Klasse.
Java verfügt über den Mechanismus der Datenkapselung (durch Klassen und Zugriffsbeschränkungen
private, public, protected ).
Polymorphismus (Abstrakte Klassen und Methoden) ist möglich.
Vererbung ist möglich.
Dynamic binding ist möglich.
2.5.3
Java ist plattformunabhängig
Java ist plattformunabhängig.
Der Java Compiler erzeugt Byte Code und nicht Maschinencode.
Java Programme können auf jeder Plattform ausgeführt werden, die eine Virtuelle Maschine besitzt.
Java-Programme sind portierbar auf jede Plattform, für die eine JVM existiert.
Datentypen sind unabhängig von der Implementierung der JVM.
C. Endreß
5/6
07/2006
2. Was ist Java?
2.5.4
Java ist robust
Folgende Gründe machen Java robust:
Strikte Typüberprüfung zur Übersetzungs- und Laufzeit
Automatisches Memory Management
Automatische Prüfung von Zugriffen auf Arrays und Strings
2.5.5
Java ist sicher
Speicheranforderungen und Referenzen werden von der JVM verwaltet.
Entwickler haben keinen direkten Zugriff auf den Speicher (keine Pointer).
Es wird eine Überprüfung durchgeführt, ob der Byte-Code
Zeiger verändert
Zugriffsbeschränkungen verletzt
Der Byte-Code-Verifier stellt sicher, dass kein Stack-Überlauf oder -Unterlauf stattfindet und die Typen
sämtlicher Parameter korrekt sind.
2.5.6
Java ist nebenläufig
Mit Nebenläufigkeit bezeichnet man die Fähigkeit eines Systems, zwei oder mehr Vorgänge gleichzeitig oder
quasi-gleichzeitig ausführen zu können. Ein Thread ist ein eigenständiges Programmfragment, das parallel zu
anderen Threads laufen kann. Ein Thread ähnelt damit einem Prozess, arbeitet aber auf einer feineren
Ebene. Während ein Prozess das Instrument zur Ausführung eines kompletten Programms ist, können
innerhalb dieses Prozesses mehrere Threads parallel laufen. Der Laufzeit-Overhead zur Erzeugung und
Verwaltung eines Threads ist relativ gering und kann in den meisten Programmen vernachlässigt werden.
Ein wichtiger Unterschied zwischen Threads und Prozessen ist, dass sich alle Threads eines Programms
einen gemeinsamen Adressraum teilen, also auf dieselben Variablen zugreifen, während die Adressräume
unterschiedlicher Prozesse streng voneinander getrennt sind.
Java hat Threads direkt in der Sprache integriert. Die Klasse Thread enthält Methoden zum Starten,
Stoppen und Verwalten von Threads.
Die Synchronisation mehrerer Threads ist möglich.
Java Laufzeitbibliotheken sind threadsicher.
2.5.7
Java ist dynamisch
Klassen können zur Laufzeit geladen werden, weil die Programme interpretiert werden.
Anwendungen können auf Änderungen ihrer Laufzeitumgebung reagieren, ohne dass der Code
verändert und neu übersetzt werden muss.
C. Endreß
6/6
07/2006
3. Aufbau von Anwendungen
3. Aufbau von Anwendungen
Lernziele
☺ Wissen, aus welchen Bausteinen ein Java-Programm aufgebaut wird.
☺ Wissen, welche Namenskonventionen in Java bestehen.
☺ Wissen, was Packages sind und wie sie organisiert werden.
☺ Wissen, wie Java-Programme kompiliert und ausgeführt werden.
3.1 Aller Anfang ist schwer
Diese sprichwörtliche Erkenntnis gilt naturgemäß auch für das Erlernen einer
Programmiersprache.
Die
Ursache
bestimmter
Anlaufschwierigkeiten
liegt
möglicherweise darin begründet, dass selbst eine noch so einfach gehaltene Einführung
in das Programmieren nicht ohne ein gewisses Mindestmaß an Formalismus auskommt,
um bestimmte Sachverhalte korrekt wiederzugeben. Einsteiger werden dadurch leicht
abgeschreckt.
Das Erlernen einer Programmiersprache unterscheidet sich im Grunde nicht vom Erlernen einer
Fremdsprache wie Englisch oder Französisch. Es gibt eine gewisse Grammatik, die festgelegt ist durch
den Wortschatz, die Syntax und die Semantik. Nach den Regeln der Grammatik werden Sätze gebildet.
Für diese Sätze werden Vokabeln benötigt. Mit Hilfe der erlernten Worte und der Grammatik der Sprache
formen wir Sätze und Texte.
In einer Programmiersprache funktioniert das Ganze auf die gleiche Art und Weise. Wir werden lernen,
nach gewissen Regeln mit dem Computer zu „sprechen“, d.h. dem Computer verständlich zu machen,
was er für uns tun soll. Dazu müssen wir uns zuerst mit gewissen Grundelementen der
Programmiersprache vertraut machen. Es ist daher nicht zu vermeiden, sich mit bestimmten formalen
Aspekten der Sprache zu beschäftigen.
3.2 Struktur eines Java-Programms
Packen Sie eine Klasse in eine Quelldatei.
Klassen sind die grundlegenden Ausführungseinheiten in Java. Sie
enthalten neben den Methoden auch die Daten, auf denen die
Methoden operieren. Grundsätzlich bilden Klassen die Basis der
objektorientierten Programmierung.
Klasse
Methode 1
Anweisung
Packen Sie Methoden in eine Klasse
Methoden bilden funktionale Einheiten. Sie sind in Klassen
definiert.
Packen Sie Anweisungen in eine Methode.
Anweisungen bilden den Kern eines Java-Programms.
C. Endreß
Quelldatei
1/9
Methode 2
Anweisung1
Anweisung2
10/2008
3. Aufbau von Anwendungen
Was kommt in eine Quelldatei?
Quelldateien besitzen die Endung .java. Eine Quelldatei enthält eine
Klassendefinition. Obwohl es möglich ist, mehrere Klassen in eine
Quelldatei zu schreiben, sollten Sie dieses vermeiden.
public class Hund {
Die Klassendefinition muss von einem Paar geschweifter Klammern
eingefasst werden. Der Name der Quelldatei muss mit dem Namen der
Klasse identisch sein.
Java ist „case-sensitive“, d.h. Sie müssen Groß- und Kleinschreibung
beachten.
Klasse
}
public class Hund {
Was kommt in eine Klasse?
void bellen(){
Eine Klasse hat eine oder mehrere Methoden, die innerhalb der Klasse
deklariert werden müssen. Die Methodendeklaration wird ebenfalls in
geschweifte Klammern gefasst. Die Methode bellen() sollte
Anweisungen dazu enthalten, wie ein Hund bellen soll.
}
}
Was kommt in eine Methode?
Methode
public class Hund {
In die geschweiften Klammern der Methode schreiben Sie die
Anweisungen, die in der Methode ausgeführt werden sollen.
void bellen(){
Anweisung1;
Anweisung2;
}
}
Anweisungen
Die Anatomie einer Klasse
Wenn die JVM gestartet wird, sucht sie nach der Klasse, die Sie beim Aufruf der JVM angegeben haben.
Anschließend sucht die JVM in dieser Klasse nach einer Methode mit der Bezeichnung main(), die
genau so aussehen muss:
public static void main(String[] args) {
// hier kommt Ihr Quellcode hinein
}
Mit dieser Methode beginnt die Programmausführung, und die virtuelle Maschine arbeitet alle
Anweisungen zwischen den geschweiften Klammern der Methode main() ab.
Jede Java-Anwendung muss mindestens eine Klasse besitzen. Eine Klasse der Anwendung muss eine
Methode main() in der oben dargestellten Weise enthalten.
C. Endreß
2/9
10/2008
3. Aufbau von Anwendungen
Das ist eine
Klasse
public, damit jeder
darauf zugreifen kann
Die öffnende
geschweifte Klammer
der Klasse
Der Name der Klasse
public class MeineErsteAnwendung {
(Das kommt
später)
Der Rückgabetyp void bedeutet, dass es
keinen Rückgabewert gibt
Dieser Methode muss ein
Array von Strings
übergeben werden, das
„args“ genannt wird.
(Spielt noch keine Rolle)
public static void main (String[] args) {
Der Name der
Methode
System.out.println (“Java macht Spaß”) ;
Sagt, dass etwas auf der Standardausgabe
ausgegeben werden soll
Die öffnende
geschweifte Klammer
der Methode
Jede Anweisung
MUSS mit einem
Semikolon enden!
Die Zeichenkette, die Sie
ausgeben möchten
}
Die schließende
geschweifte Klammer
der Methode
}
Die schließende
geschweifte Klammer
der Klasse
3.3 Das erste Java-Programm
Die Problemstellung
Wir schreiben eine einfache Konsolenanwendung zur Mehrwertsteuerberechnung. Das
Programm soll zu einem Nettobetrag für den festen Mehrwertsteuersatz von 19 % die
Mehrwertsteuer sowie den zugehörigen Bruttobetrag errechnen und ausgeben.
Der Lösungsansatz
Sie erinnern sich noch an den Software-Lifecycle? Nach der Analyse unseres
zugegebenermaßen kleinen Problems wollen wir einen Algorithmus zur Lösung entwerfen.
Als Ausdrucksmittel zur Formulierung von Algorithmen verwendet man in der Informatik
i.d.R. Diagramme wie Programmablaufpläne (PAP) oder Struktogramme. Mit Diagrammen
lassen sich Abläufe besser verdeutlichen als durch umgangssprachliche Beschreibungen.
In der folgenden Abbildung wird der Lösungsansatz mit beiden Diagrammtypen dargestellt.
C. Endreß
3/9
10/2008
3. Aufbau von Anwendungen
Programmablaufplan (PAP)
Struktogramm
Eingabe: Nettobetrag
Eingabe: Nettobetrag
Berechne Mehrwertsteuer
Berechne Mehrwertsteuer
Berechne Bruttobetrag
Ausgabe: MwSt und Bruttobetrag
Berechne Bruttobetrag
Ausgabe: MwSt und Bruttobetrag
Der Quelltext
Wir erstellen ein Verzeichnis mit der Bezeichnung mehrwertsteuer und speichern die Quelltextdatei
MehrwertSteuerBerechnung.java in diesem Verzeichnis.
1
package mehrwertsteuer;
2
import support.Console;
3
4
public class MehrwertSteuerBerechnung {
public static void main(String[] args) throws Exception {
// Variablendeklaration
double mwst, netto, brutto;
5
6
// Konstante
final int MWSTSATZ = 19;
7
8
// Eingabe
Console.print("Nettobetrag: ");
netto = Console.readDouble();
9
// Verarbeitung
mwst
= netto * MWSTSATZ / 100;
brutto = netto + mwst;
10
// Ausgabe
Console.println("Mehrwertsteuer: " + mwst);
Console.println("Bruttobetrag : " + brutto);
}
}
Erläuterungen
1
Die Anweisung package mehrwertsteuer; gibt an, zu welchem Paket die Klasse gehört. Die
package-Anweisung muss stets die erste Anweisung des Quellcodes sein. Üblicherweise fasst man in
Java die Klassen eines Programms zur besseren Strukturierung in Pakten zusammen. In diesem Beispiel
gehört die Klasse MehrwertSteuerBerechnung zu dem Paket mehrwertsteuer. Das bedeutet, die
Quelldatei muss in einem Verzeichnis namens mehrwertsteuer gespeichert werden.
C. Endreß
4/9
10/2008
3. Aufbau von Anwendungen
2
Eine Klasse muss im Java-Quellcode über den vollqualifizierenden Klassennamen angesprochen werden.
Dieser setzt sich zusammen aus dem Namen des Packages, in dem sich die Klasse befindet, und dem
Namen der Klasse selbst. Allerdings führt diese vollqualifizierende Bezeichnung sehr schnell zu sehr
langen Ausdrücken. Diesen Aufwand kann man reduzieren, indem man einzelne Klassen oder ganze
Packages mit der Anweisung import einbindet. import-Anweisungen müssen nach der packageAnweisung am Anfang der Datei aufgelistet werden. Dadurch ist die Angabe des Package-Namens bei
einem Klassenaufruf nicht mehr nötig.
In diesem Beispielprogramm wird die Klasse Console aus dem Package support eingebunden, so
dass wir im Quellcode auf den vollqualifizierenden Klassennamen support.Console verzichten und
uns auf den einfachen Klassennamen beschränken können. Die Klasse Console stellt Methoden zur
einfachen Ein- und Ausgabe in Konsolenanwendungen zur Verfügung (siehe 7 bis 9). Sie ist nicht
Bestandteil des JDK von Sun, sondern ist ein externes Paket, das uns die Handhabung von Ein- und
Ausgaben im Vergleich zu den Bordmitteln von Java erheblich erleichtert.
3
Das Schlüsselwort class gefolgt vom Namen der Klasse leitet die Definition einer Klasse ein. Die Klasse,
die die Methode main() enthält, muss öffentlich sichtbar sein, was durch den Sichtbarkeitsmodifzierer
public festgelegt wird.
4
Der Interpreter der virtuellen Maschine beginnt die Ausführung eines Java-Programms immer mit der
Methode main(), die folgendermaßen deklariert sein muss:
public static void main(String[] args)
Fehlt diese Methode in Ihrem Programm, so kann die virtuelle Maschine das Programm nicht ausführen.
Die Ergänzung throws Exception ist in unserem Beispiel erforderlich, weil die Methode
readDouble() aus der Klasse Console auf fehlerhafte Eingaben mit einer sogenannten Exception
(Ausnahme) reagiert. Java besteht darauf, dass jemand die Verantwortung für die Behandlung der
Exception übernimmt. Durch den Befehl throws Exception teilen wir dem Programm mit, dass wir
uns nicht um die Ausnahme kümmern werden, sondern dass wir die Verantwortung für die
Ausnahmebehandlung auf eine übergeordnete Ebene „werfen“. In diesem Fall ist das die virtuelle
Maschine. Auf Exception-Handling werden wir zu einem späteren Zeitpunkt eingehen.
5
Aus der Mathematik oder Physik wissen wir, dass Berechnungen mit Formeln ausgeführt werden, die
Formelgrößen oder Variablen enthalten. Auch in Programmen arbeiten wir mit Variablen. Bevor wir
jedoch eine Variable in einem Programm verwenden können, müssen wir die Variable deklarieren, d.h.
dem Programm den Namen der Variablen und die Art der zu speichernden Daten mitteilen. In dieser
Zeile deklarieren wir die Variablen mwst, netto und brutto. Die Variablen sollen mit Dezimalzahlen
arbeiten. Der entsprechende Datentyp in Java heißt double. Da die drei Variablen von demselben
Datentyp sind, können wir sie in einer Anweisung durch Komma getrennt deklarieren. Unsere drei
Variablen sind lokale Variablen der main()-Methode, weil wir sie in der Methode main() deklarieren.
6
Den Mehrwertsteuersatz wollen wir in unserem Programm einmal festgelegen und nicht mehr verändern.
Wir deklarieren ihn deshalb als Konstante. Einer Konstanten müssen wir bei der Deklaration ein Wert
zuweisen, der im Gegensatz zum Wert einer Variablen während des Programmablaufs nicht mehr
veränderbar ist. Das Schlüsselwort final, das vor dem Datentyp angegeben wird, deklariert
MWSTSATZ als Konstante. Als Datentyp für MWSTSATZ verwenden wir den Ganzzahltyp int.
7
Vor der ersten Benutzereingabe sollten wir dem Benutzer durch eine kurze Anweisung mitteilen, welche
Eingabe das Programm erwartet. Die Methode print() der Klasse Console gibt einen Text oder eine
Zeichenkette im Konsolenfenster aus. Die auszugebende Zeichenkette muss als Parameter in die
Klammern hinter dem Methodennamen eingetragen werden. Man sagt auch, der Parameter wird an die
Methode übergeben. Zeichenketten werden in doppelte Hochkommata gesetzt (“Nettobetrag: “).
8
Die Methode readDouble() der Klasse Console liest eine Fließkommazahl des Typs double von der
Konsole ein. Der eingelesene Wert wird der Variablen netto zugewiesen.
C. Endreß
5/9
10/2008
3. Aufbau von Anwendungen
9
Diese Anweisung ist eine Zuweisung und keine Gleichung. Das Gleichheitszeichen symbolisiert den
Zuweisungsoperator, der in der Programmierung nicht dieselbe Bedeutung besitzt wie ein
mathematisches Gleichheitszeichen. Arithmetische Ausdrücke dürfen nur auf der rechten Seite des
Zuweisungsoperators stehen. Es wird zuerst der arithmetische Ausdruck rechts vom Zuweisungsoperator
berechnet (hier: netto * MWSTSATZ / 100). Anschließend wird das Ergebnis der Variablen links
vom Zuweisungsoperator (hier: mwst) zugewiesen.
10 Nach der Berechnung der Variablenwerte für mwst und brutto wollen wir unsere Ergebnisse dem
Benutzer mitteilen. Die Methode println() der Klasse Console gibt eine Zeichenkette aus, die mit
Hilfe des Verkettungsoperators (+) aus der Zeichenkette “Mehrwertsteuer: “ und dem Wert der
Variablen mwst gebildet wird. Am Ende der Zeile erfolgt ein Zeilenvorschub, so dass die nächste
Ausgabeanweisung den Text in eine neue Zeile schreibt.
3.4 Bezeichner
Ein Bezeichner ist eine Folge von Zeichen, die Variablen, Methoden, Konstanten, Klassen und Packages
benennt.
Bezeichner …
können in Java beliebig lang sein.
müssen mit einem Buchstaben (‚a’ bis ‚z’ oder ‚A’ bis ‚Z’) beginnen und können dann beliebig
fortgesetzt werden (ohne Leerzeichen). Java unterscheidet Groß- und Kleinschreibung!
Aus historischen Gründen sind zwar auch noch der Unterstrich (‚_’) oder das Dollarzeichen (‚$’)
als Anfangszeichen erlaubt, jedoch entspricht dieses nicht mehr der Konvention.
dürfen kein Java-Schlüsselwort sein.
müssen in ihrem Gültigkeitsbereich eindeutig sein.
3.5 Namenskonventionen
In Java gibt es für die Vergabe von Namen bestimmte Konventionen, die das Verständnis der Quelltexte
erleichtern und daher eingehalten werden sollten.
Als Bezeichner sollten grundsätzlich aussagekräftige Namen gewählt werden, die die Aufgabe oder Funktion
des bezeichneten Elements beschreiben.
Namen für
Konvention
Beispiele
Variablen
beginnen mit Kleinbuchstaben. Bei zusammengesetzten
Wörtern wird der erste Buchstabe jedes folgenden
Wortes groß geschrieben.
meinGehalt, kundenNummer,
mehrWertSteuer
Methoden
wie Variablennamen, oft ist die erste Silbe ein Verb.
print, readDouble,
berechneZinsen
Klassen
beginnen mit einem Großbuchstaben. Bei
zusammengesetzten Wörtern wird der erste Buchstabe
jedes folgenden Wortes auch groß geschrieben.
String, HelloWorld,
MehrWertSteuerBerechnung
Packages
bestehen ausschließlich aus Kleinbuchstaben. Mehrteilige
Paketnamen werden durch Punkte separiert.
mehrwertsteuer,
java.lang,
com.borland.jbuilder.ide
Konstanten bestehen ausschließlich aus Großbuchstaben. Mehrteilige
Konstantennamen können durch Unterstriche separiert
werden.
C. Endreß
6/9
MWSTSATZ, MAX_ANZAHL
10/2008
3. Aufbau von Anwendungen
3.6 Packages
Java organisiert Quelltextdateien (.java) und übersetzte Klassen (.class) in sogenannten Packages.
Jede Klasse gehört zu genau einem Package.
Der Zugriff auf eine Klasse erfolgt immer über den Package-Namen.
Package-Namen bestehen in Java aus einem oder mehreren Wörtern, die durch einen Punkt getrennt
sind. Jedes Wort entspricht dabei einem Unterverzeichnis im Verzeichnispfad der gewünschten
Klassendatei.
Eine Klasse aus dem Paket java.awt.image befindet sich also im Verzeichnis java\awt\image.
Die Klassenbibliothek des JDK 1.4 enthält in über 130 Packages mehrere tausend Klassen. Der Umfang
des aktuellen JDK 1.5 ist dem gegenüber noch gewachsen.
Wichtige Standard-Pakete
java.lang
Standard-Funktionalitäten (fundamentales Paket, das immer automatisch
eingebunden wird)
java.math
Klassen mit mathematische Methoden
java.util
Datenstrukturen, Tools und Hilfsklassen
javax.swing
Swing-Klassen für GUI
Wichtige externe Klassen, die das Programmieren auf der Konsolenebene erleichtern.
Externe Pakete
support.Console
Ein- und Ausgabefunktionen für Konsolenanwendungen
support.NumberFormat Formatierungs-Methoden für die numerischen Datentypen double und
int zur Verfügung.
Archive
Grundsätzlich müssen sich in Java alle Klassen in Packages, also in einer Verzeichnisstruktur, befinden. Die
Verwaltung vieler kleiner Klassen und Pakete innerhalb einer Verzeichnisstruktur hat mehrere Nachteile.
Deshalb bietet Java die Möglichkeit, Klassen und Pakete in Archivdateien zusammenzufassen. Ein JavaArchiv ist eine Datei, die mehrere Dateien umfasst und die Endung .jar hat. Vorteile: platzsparend,
übersichtlich, schneller Zugriff
3.7 Java-Basics: Punkt für Punkt
Eine Java-Datei ist die kleinste Einheit Programm-Code, die der Java-Compiler kompilieren kann.
Eine Java-Datei besteht aus:
1. einer optionalen package-Anweisung
2. null oder mehr import-Anweisungen
3. einer (oder mehr) Klassendefinition(en)
Anweisungen enden mit einem Semikolon;
Codeblöcke werden durch ein Paar geschweifte Klammern definiert {}
C. Endreß
7/9
10/2008
3. Aufbau von Anwendungen
Das Gleichheitszeichen (=) ist der Zuweisungsoperator und beschreibt in Java keine mathematische
Gleichung.
Eine Java-Datei darf maximal eine public deklarierte Klasse enthalten. Der Name der Datei muss mit
dem Namen der Klasse identisch sein und die Namenserweiterung .java haben.
Es ist gute Programmierpraxis, nur eine Klasse pro Java-Datei zu definieren.
Begründung: Der Compiler javac erzeugt für jede Klasse, die in einer Quelltextdatei definiert ist, eine
eigene Bytecode-Datei (.class) mit dem Namen der Klasse.
Eine Bytecode-Datei (.class) hat denselben Namen wie die Klasse, die sie definiert.
Eine Java-Anwendung benötigt eine Klasse, mit einer main()-Methode:
public static void main(String[] args)
Diese main()-Methode ist für den Java-Interpreter der Einstiegspunkt zur Programmausführung.
3.8 Anwendungen kompilieren und ausführen
3.8.1
Der Java-Compiler javac
Mit dem Java-Compiler javac werden Java-Quelltextdateien in Byte-Code übersetzt.
Der Compiler wird von der Kommandozeile mit folgendem Befehl aufgerufen:
javac [Optionen] [Dateinamen] [@Dateiliste]
Parameter
Bedeutung
Optionen
-classpath : Klassenpfad zum Suchen benutzerdefinierter Klassen; mehrere Pfade sind
bei Windows durch Semikolon zu trennen
Dateinamen
Namen der Quelltextdateien, die zu übersetzen sind (durch Leerzeichen getrennt)
Alle Dateien des Verzeichnisse übersetzen: *.java
@Dateiliste Textdatei, die eine Liste der zu übersetzenden Namen enthält
Der Compiler erzeugt für jede definierte Klasse des Quelltextes eine Klassendatei (.class).
Gibt der Compiler keine Meldung aus, wurde das Programm erfolgreich übersetzt.
3.8.2
Der Java-Interpreter java
Der Java-Interpreter kann über zwei Kommandos aufgerufen werden:
java [Optionen] Klassendatei [Argumente]
führt Anwendungen aus, öffnet bei graphischen
Anwendungen zusätzlich ein Konsolenfenster
javaw [Optionen] Klassendatei [Argumente]
führt unter Windows graphische Anwendungen
aus, ohne ein Konsolenfenster zu öffnen
C. Endreß
8/9
10/2008
3. Aufbau von Anwendungen
Der Name der Klassendatei muss ohne die Endung .class angegeben werden.
Argumente werden direkt an die Anwendung übergeben und von der main()-Methode im String-Array
args entgegengenommen.
Optionen:
-classpath (Klassenpfad der benutzerdefinierten Klassen, die das Programm benötigt)
-jar
C. Endreß
(Startet eine Anwendung, die sich in einem Archiv befindet.)
9/9
10/2008
4. Variablen und Ausdrücke
4. Variablen und Ausdrücke
Lernziele
☺ Wissen, was Variablen und Konstanten sind, und wie man sie verwendet.
☺ Wissen, welche elementaren Datentypen Java besitzt.
☺ Wissen, welche Operatoren es in Java gibt.
☺ Wissen, wie Ausdrücke zusammengesetzt und ausgewertet werden.
Wie bekomme ich Daten in meine Programme?
Computerprogramme werden zur elektronischen Verarbeitung von Daten eingesetzt. Im ersten
Programmbeispiel des vorangegangenen Kapitels haben wir ein kleines Programm zur Berechnung der
Mehrwertsteuer geschrieben. Die Daten, mit denen unser Programm gearbeitet hat, waren der Nettopreis,
die Mehrwertsteuer usw. Diese Werte wurden in Variablen gespeichert. Nähere Erklärungen zum Umgang
mit Variablen in Java und zu deren Verarbeitung liefert dieses Kapitel.
4.1
Variablen
Variablen dienen dazu, Daten im Hauptspeicher eines Programms
abzulegen,
zu lesen,
zu verändern.
Schreibender Zugriff auf eine Variable:
Es wird eine Information bzw. ein Wert in der Variablen abgelegt. Vorher vorhandene Werte werden
überschrieben, d.h. gelöscht.
Lesender Zugriff auf eine Variable:
Es wird der Wert der Variablen gelesen. Der gespeicherte Wert bleibt dabei unverändert. Beim Lesen
wird nur eine Kopie des gespeicherten Wertes erzeugt, die dann weiter verarbeitet wird.
Eine Variable ist ein Datenelement eines bestimmten Datentyps, das durch einen Bezeichner (Namen)
identifiziert wird. Der Wert einer Variablen kann während des Programmablaufs verändert werden
(variabel = veränderbar).
In Java gilt:
Variablen müssen einen Typ haben.
Variablen müssen einen Namen haben.
Außerdem hat jede Variable einen Wert und ist in
einem Speicherbereich abgelegt.
C. Endreß
1 / 13
double nettoBetrag;
Typ
Name
10/2008
4. Variablen und Ausdrücke
Java kümmert sich um Typen. Die Typüberprüfungen werden zur Compile-Zeit vorgenommen. => Java
ist typsicher.
Variablen müssen vor ihrer ersten Verwendung deklariert werden, d.h. dem Compiler bekannt gemacht
werden.
Vor der ersten lesenden Verwendung müssen Variablen initialisiert werden, d.h. sie müssen einen
definierten Wert erhalten.
Wenn möglich sollte eine Initialisierung bereits bei der Deklaration erfolgen.
“Einen doppelten Espresso
bitte!”
Wenn Sie über Variablen nachdenken, denken Sie an Becher - bei Java denken Sie natürlich an KaffeeBecher.
Eine Variable ist einfach ein Becher. Ein Behälter. Die Variable enthält etwas.
Sie hat eine Größe und einen Typ. In diesem Kapitel interessieren uns zunächst nur die Behälter für die
elementaren Typen. Später kommen wir dann noch zu den Bechern, die Referenzen und Objekte
enthalten. Auch wenn diese Becher-Analogie zunächst simpel scheinen mag, bietet Sie uns ein vertrautes
Mittel, die Dinge in einer einfachen Weise zu betrachten, auch wenn die Zusammenhänge später komplexer
werden. Die Becher für elementare Datentypen gleichen den Bechern, die Sie in einem Café bekommen. Es
gibt z.B. bei Starbucks Bechergrößen wie „Short“, „Tall“ oder „Grande“
Auch elementare Typen in Java kommen in unterschiedlichen Größen vor, die jeweils
unterschiedliche Namen haben. Für Ihre Variablen benötigen Sie also lediglich
die passende Bechergröße. Java besitzt vier Behälter für ganzzahlige
Datentypen. Statt einer Bestellung wie „Ich nehme einen Tall
Hot Chocolate“ sagen Sie dem Compiler einfach
„Ich nehme eine int-Variable mit dem Wert 90“.
Allerdings müssen Sie in Java Ihrem Becher noch
einen Namen geben. Sie sagen also „Ich nehme
eine int-Variable mit den Wert 90. Gib der
int
short
byte
Variablen den Namen gewicht.“
long
Im Quellcode würden Sie diese „Bestellung“ so formulieren:
int gewicht = 90;
Jede Variable eines elementaren Datentyps belegt eine festgelegte Anzahl von Bits (die Bechergröße) im
Arbeitsspeicher des Rechners. Die Größen (in Bit) der 8 elementaren Datentypen von Java sehen Sie hier:
byte
8
short
16
int
32
Ganze Zahlen
C. Endreß
long
64
float
32
double
64
boolean
8
char
16
Fließkommazahlen
2 / 13
10/2008
4. Variablen und Ausdrücke
Der Datentyp einer Variablen legt fest, welche Werte die Variable annehmen kann und welche Operationen
auf diesen Werten ausgeführt werden können.
Elementare Datentypen
Anwendung
Typname
Länge
Wertebereich
Logik
boolean
1 Byte
true, false
Zeichen
char
2 Byte
Alle Unicode-Zeichen
Ganze Zahlen
byte
1 Byte
-27 . . . 27 – 1
short
2 Byte
-215 . . . 215 – 1
int
4 Byte
-231 . . . 231 – 1
long
8 Byte
-263 . . . 263 – 1
float
4 Byte
+/- 3.4 * 1038
double
8 Byte
+/- 1.8 * 10308
Fließkommazahlen
Logischer Typ
Mit boolean besitzt Java einen eigenen logischen Datentyp und beseitigt damit eine Schwäche von
C/C++. Dort konnten Integer-Typen auch für logische Ausdrücke „missbraucht“ werden. In Java muss
der Typ boolean dort verwendet werden, wo ein logischer Operand erforderlich ist.
Zeichentyp
char-Literale werden grundsätzlich in einfache Hochkommata gesetzt: ’a’
String-Literale stehen in doppelten Hochkommata: “Hallo“
Java stellt eine Reihe von Standard-Escape-Sequenzen zur Verfügung, die zur Darstellung von
Sonderzeichen verwendet werden können:
Zeichen
Bedeutung
\b
Rückschritt (Backspace)
\t
Horizontaler Tabulator
\n
Zeilenvorschub (Newline)
\f
Seitenumbruch (Formfeed)
\r
Wagenrücklauf (Carriage return)
\”
Doppeltes Anführungszeichen
\\
Backslash
Ganze Zahlen
Alle ganzzahligen Typen sind in Java vorzeichenbehaftet. Ihre Länge ist auf allen Plattformen gleich.
C. Endreß
3 / 13
10/2008
4. Variablen und Ausdrücke
Fließkommazahlen
Fließkommazahlen werden in Java mit einem Dezimalpunkt geschrieben: 4.678
Die Fließkommatypen in Java setzen sich nach IEEE-754 folgendermaßen zusammen:
Vorzeichen-Bit (1 Bit)
Mantisse (23 Bit bei float, 52 Bit bei double)
Exponent (8 Bit bei float, 15 Bit bei double)
Neben den numerischen Literalen gibt es noch einige symbolische Literale in den Klassen Float und
Double des Pakets java.lang:
Name
Bedeutung
MAX_VALUE
Größter darstellbarer positiver Wert
MIN_VALUE
Kleinster darstellbarer positiver Wert
NaN
Not-A-Number
NEGATIVE_INFINITY
Negativ unendlich
POSITIV_INFINITY
Positiv unendlich
Zeichenketten
Zeichenketten-Literale stehen in doppelten Hochkommata: “Hallo“
Zeichenketten werden in Java durch Objekte die Klasse String dargestellt. Die Klasse String ist
die wichtigste Datenstruktur für alle Aufgaben, die etwas mit Ein- und Ausgabe oder der
Verarbeitung von Zeichen zu tun haben. Sie bietet viele wichtige Methoden zum Verarbeiten von
Zeichenketten (z.B. + Operator zur Verkettung).
4.1.1
Variablennamen
Variablennamen müssen mit einem Buchstaben beginnen (‘A‘ bis ‘Z‘, ‘a‘ bis ‘z‘) und dürfen
dann weitere Buchstaben oder Ziffern enthalten.
Variablennamen beginnen nach der Java-Sprachkonvention mit einem Kleinbuchstaben: meinGehalt.
Java unterscheidet Groß- und Kleinschreibung. D.h. die Namen zahl, Zahl und zAHL bezeichnen
drei unterschiedliche Variablen.
Variablennamen dürfen kein von Java reserviertes Schlüsselwort sein.
4.1.2
Deklaration und Initialisierung
Bei der Deklaration müssen Datentyp und Name der Variable angegeben werden. Die Deklaration wird
wie alle Anweisungen in Java durch ein Semikolon abgeschlossen. Mehrere Variablen gleichen Typs
können in einer Liste durch Kommata getrennt in einer Anweisung deklariert werden. Variablendeklarationen dürfen an einer beliebigen Stelle im Programm-Code erscheinen.
Mit Hilfe des Zuweisungsoperators (=) können Variablen bei der Deklaration mit einem Wert initialisiert
werden.
C. Endreß
4 / 13
10/2008
4. Variablen und Ausdrücke
Deklaration:
Datentyp VariablenName;
Initialisierung:
Datentyp VariablenName = InitialWert;
int anzahl;
anzahl = 24;
boolean machtSpass;
machtSpass = true;
double radius = 5.517;
double laenge, breite, hoehe;
char zeichen = ‘A’;
boolean lichtAn = true;
lichtAn = machtSpass;
float pi = 3.141f;
Dezimalzahlen werden in Java mit
einem Dezimalpunkt geschrieben!
Achten Sie auf das >>f<<. Bei einem
float-Wert brauchen Sie das, weil
Java sonst denkt, dass alles mit
Dezimalpunkt ein double ist.
Sie wollen doch sicher nichts verschütten …
Achten sie darauf, dass der Wert in die Variable
passt.
Einen großen Wert können Sie nicht in einen kleinen
Becher stecken – jedenfalls nicht, ohne etwas zu
verschütten.
Der Compiler versucht, das zu verhindern. Wenn er aus
Ihrem Code erkennen kann, dass ein Wert nicht in den
Behälter
(Becher/Variable)
passt,
gibt
er
eine
Fehlermeldung aus und erzeugt keinen Bytecode zu Ihrem
fehlerhaften Quellcode.
Sie können beispielsweise nicht den Wert aus einer intVariablen in einen Behälter mit byte-Größe stopfen.
int x = 24;
byte b = x;
Das funktioniert nicht!
Sie fragen jetzt vielleicht, warum das nicht funktioniert.
Schließlich ist der Wert von x nur 24, und 24 ist definitiv klein genug, um in ein byte gestopft zu werden.
Sie wissen das! Der Compiler kümmert sich aber nur darum, dass Sie versuchen etwas Großes in einen zu
kleinen Behälter zu stopfen und dabei eventuell etwas verschütten. Sie dürfen nicht erwarten, dass der
Compiler weiß, welchen Wert x hat, selbst wenn Sie den Wert in Ihrem Code ausgeschrieben haben.
Einer Variablen können Sie auf mehreren Wegen einen Wert zuweisen.
C. Endreß
Einen literalen Wert mit dem Zuweisungsoperator (=) zuweisen:
x = 12;
istGut = true;
Einer Variablen den Wert einer anderen Variablen zuordnen:
x = y;
In einem Ausdruck Variablen verknüpfen:
x = y + 47;
5 / 13
10/2008
4. Variablen und Ausdrücke
4.1.3
Hintergründiges zu Variablen
Nachdem wir weiter oben eine Variablen-Becher-Analogie zur leichteren Anschauung im Umgang mit
Variablen eingeführt haben, folgt nun ein Einblick in die Hintergründe der Variablenverarbeitung eines JavaProgramms. Sie wissen natürlich, dass Java keine Becher mit Namen versieht und Werte hineinpakt. Der
sogenannte Memorymanager von Java legt die Variablen in den Speicherzellen des Arbeitsspeichers ab. Die
jeweils ein Byte breiten Speicherzellen sind mit physikalischen Adressen versehen.
Ein Java-Programm reserviert Speicherzellen, die den Namen der Variablen als symbolische Adresse
erhalten. Über diese symbolische Adresse wird dann schreibend oder lesend auf den Inhalt der Speicherzelle
zugegriffen. Wie wir wissen, ist Java eine streng typisierte Sprache. Daher muss prinzipiell der Datentyp des
Initialwertes mit dem Datentyp der Variablen übereinstimmen.
int anzahl = 24;
double radius = 5.517;
char letter = ’A’;
1004
5.517
radius
letter
double
letter
char
Wir haben gelernt, dass der Compiler nichts Großes in kleine Becher
füllt, weil er nichts verschütten will. Umgekehrt ist es jedoch möglich,
den Inhalt kleiner Becher in große Becher umzufüllen, wie das
folgende Beispiel zeigt:
’A’
hoch
Typkonvertierungen
1012
int
4.1.4
24
anzahl
’A’
int
radius
Physikalische
Adresse
1000
5.517
24
anzahl
Symbolische
Adresse
int hoch = 2347;
long sehrHoch;
sehrHoch = hoch;
sehr
Hoch
In bestimmten Fällen nimmt der Java-Compiler erweiternde
Typkonvertierungen vor (implizites Type-Casting). Dabei wird der
Grundsatz verfolgt, dass ein einfacher Datentyp in einen
umfassenderen Datentyp konvertiert wird. Auf diese Weise werden keine Informationen verloren.
byte
short
int
long
float
long
double
char
C. Endreß
6 / 13
10/2008
4. Variablen und Ausdrücke
Die Umwandlung vom Typ int oder long nach float oder vom Typ long nach double kann jedoch zu
einem Genauigkeitsverlust führen.
Der Compiler konvertiert automatisch …
bei einer Zuweisung, wenn der Typ der Variablen mit dem Typ des zugewiesenen Ausdrucks nicht
identisch ist.
bei der Auswertung eines arithmetischen Ausdrucks, wenn Operanden unterschiedlich typisiert sind.
beim Aufruf einer Methode, falls die Typen der aktuellen Parameter nicht mit den formalen
Parametern der Methode übereinstimmen.
4.2
Konstanten
Eine Konstante ist ein Datenelement, dem nur einmal im Programmverlauf ein Wert zugewiesen wird. Die
Initialisierung muss bei der Deklaration erfolgen. Man kann nach ihrer Initialisierung nur noch lesend auf
eine Konstante zugreifen.
Zur Deklaration einer Konstanten wird das Schlüsselwort final verwendet.
Syntax:
final Datentyp KonstantenName = Wert;
final double PI = 3.141;
final char DOLLAR = ‘$’;
final int SPIELER = 11;
Der Wert einer Konstanten ist nicht veränderbar (konstant).
In Java ist es Konvention, die Namen von Konstanten ausschließlich in Großbuchstaben zu schreiben
(z.B. MWST, PI, MAX_WERT, ANZAHL_TAGE)
4.3
Ausdrücke
Ein Ausdruck ist eine Verarbeitungsvorschrift zur Ermittlung eines Wertes. Ein Ausdruck besteht aus
Operanden und Operatoren. Jeder Operand kann selbst wieder ein Ausdruck sein.
Operator: Gibt an, was gemacht wird.
Operand: Gibt an, womit etwas gemacht wird.
Das Ergebnis eines Ausdrucks nennt man Rückgabewert.
Operatoren
Rückgabewert
4 + 5 * 3 => 19
Operanden
C. Endreß
7 / 13
10/2008
4. Variablen und Ausdrücke
Der Typ des Rückgabewertes hängt vom Typ der Operanden ab.
Ausdrücke sind die kleinsten ausführbaren Einheiten eines Java-Programms.
Sie dienen dazu
Variablen einen Wert zuzuweisen,
numerische Berechnungen anzustellen oder
logische Bedingungen zu formulieren.
In Java gibt es folgende Arten von Operatoren:
Arithmetische Operatoren
(Berechnungen)
Relationale Operatoren
(Vergleiche)
Logische Operatoren
(Verknüpfung boolescher Werte)
Zuweisungsoperatoren
4.3.1
Arithmetische Operatoren
Arithmetische Operatoren erwarten einen numerischen Operanden und liefern einen numerischen
Rückgabewert. Werden Operanden unterschiedlichen Typs in einem arithmetischen Ausdruck verwendet,
konvertiert der Compiler die Operanden automatisch in den umfassenderen Datentyp.
12 + 5.3
12.0 + 5.3 = 18.3
Ergebnis von Typ
double
Typ-Konvertierung von
int nach double
Operator
Beispiel
Bedeutung
+
a+b
addiert a und b
-
a–b
subtrahiert b von a
*
a*b
multipliziert a mit b
/
a/b
dividiert a durch b
-
-a
%
a%b
++
a++
anwendbar auf
Zahlen, char
negatives Vorzeichen
a modulo b, d.h. es ergibt den Rest der Ganz- und
Division a / b
Fließkommazahlen
Inkrement: erhöht a um 1
++a
--
a--
Dekrement: verringert a um 1
Variablen
--a
Vorsicht bei der Verwendung von Inkrement- und Dekrement-Operatoren in Zuweisungen:
Präfix:
a = ++b
Postfix: a = b++
C. Endreß
erst wird b um 1 erhöht, dann wird a das Ergebnis zugewiesen.
erst wird a der Wert von b zugewiesen, dann wird b um 1 erhöht.
8 / 13
10/2008
4. Variablen und Ausdrücke
4.3.2
Relationale Operatoren
Relationale Operatoren vergleichen Ausdrücke miteinander und erzeugen einen boolschen Rückgabewert
(true oder false)
Operator
==
Beispiel
Bedeutung
a == b
a gleich b: Ergibt true, wenn a gleich b ist.
Bei Referenztypen ergibt sich true, wenn beide Werte auf dasselbe
Objekt zeigen.
a ungleich b: Ergibt true, wenn a nicht gleich b ist.
!=
a != b
<
a<b
<=
a <= b
>
a>b
>=
a >= b
4.3.3
anwendbar auf
Zahlen, boolean und
Referenztypen
Bei Referenztypen ergibt sich true, wenn beide Werte nicht auf
dasselbe Objekt zeigen.
a kleiner b: Ergibt true, wenn a kleiner b ist.
a kleiner oder gleich b: Ergibt true, wenn a kleiner
oder gleich b ist.
a größer b: Ergibt true, wenn a größer b ist.
Zahlen
a größer oder gleich b: Ergibt true, wenn a größer
oder gleich b ist.
Logische Operatoren
Logische Operatoren dienen dazu, Werte vom Typ boolean miteinander zu vergleichen. Der Rückgabewert
ist ebenfalls vom Typ boolean.
Operator
Beispiel
Bedeutung
anwendbar auf
logisches NICHT: macht aus true false
und umgekehrt
!
!a
&&
a && b
logisches UND:
ergibt nur true, wenn sowohl Operand a
als auch b true sind, sonst false
||
a || b
logisches ODER:
ergibt true, wenn mindestens einer der
beiden Operanden a oder b true ist
^
a^b
logisches Exklusiv-ODER:
ergibt true, wenn beide Operanden einen
unterschiedlichen Wahrheitswert haben
boolean
Die Operatoren && und || führen zu einer sogenannten Short-Circuit-Evaluation des logischen
Ausdrucks, d.h. ein weiter rechts stehender Teilausdruck wird nur noch dann ausgewertet, wenn er für
das Ergebnis des Gesamtausdrucks noch von Bedeutung ist.
Die logischen Operatoren & (UND) und | (ODER) führen die Auswertung ohne Short-Circuit-Evaluation
durch.
C. Endreß
9 / 13
10/2008
4. Variablen und Ausdrücke
4.3.4
Zuweisungsoperatoren
Zuweisungsoperatoren sind anwendbar auf alle Datentypen.
Die Datentypen der Operanden müssen kompatibel sein, d.h.
die Typen sind gleich oder
ineinander konvertierbar.
Operator
4.3.5
Beispiel
Bedeutung
Einfache Zuweisung:
weist a den Wert von b zu und liefert
b als Rückgabewert.
=
a=b
+=
a += b
a=a+b
-=
a -= b
a=a-b
*=
a *= b
a=a*b
/=
a /= b
a=a/b
%=
a %= b
a=a%b
Sonstige Operatoren
String-Verkettung
Der Operator „+“ arbeitet bei Zeichenketten als Verkettungsoperator.
Wenn wenigstens einer der beiden Operatoren a + b ein String ist, wird der gesamte Ausdruck als
String-Verkettung ausgeführt. Hierbei wird gegebenenfalls der Nicht-String-Operand in einen String
konvertiert und anschließend mit dem anderen Operand verkettet.
Verkettungsoperator
Das folgende Beispiel
double liter = 9.5;
System.out.println(″Ihr Benzinverbrauch beträgt ″ + liter + ″ Liter
pro 100 km.″);
erzeugt die Bildschirmausgabe: Ihr Benzinverbrauch beträgt 9.5 Liter pro 100 km.
Der Wert von
liter wird implizit
in einen String
umgewandelt
Type-Cast-Operator
Mit Hilfe des Type-Cast-Operators können Typumwandlungen explizit vorgenommen werden. Der Compiler
akzeptiert es, wenn Sie ihm durch einen Cast-Operator mitteilen, dass Sie den Inhalt eines größeren Bechers
in einen kleineren umfüllen möchten. Dass Sie dabei Inhalte verschütten, liegt dann in Ihrer Verantwortung.
int i = 13;
byte b = (byte) i;
i = (int) 13.678;
C. Endreß
Erzwingt die Konvertierung des
int-Wertes in byte
Erzwingt die Konvertierung des
double-Literals in den int-Wert 13
10 / 13
10/2008
4. Variablen und Ausdrücke
4.3.6
Prioritäten
Bei Ausdrücken, die mehrere Operatoren beinhalten, legen die Vorrangregeln der Operatoren die
Reihenfolge der Auswertung fest. Diese Vorrangregeln bezeichnet man als Prioritäten.
In Java gelten folgende Vorrangregeln:
Gruppe Operator Bezeichnung
anwendbar auf
1
()
Klammern
2
++
Inkrement
--
Dekrement
-
negatives Vorzeichen
!
Logisches NICHT
L
S = Strings
Type-Cast
A
V = Variablen
3
(type)
4
*
Multiplikation
/
Division
%
Modulo
+
Additon
-
Subtraktion
+
String-Verkettung
<
Kleiner
5
6
<=
>
5
7
8
A
Abkürzungen
E = elementare Typen
N
==
Gleich
!=
Ungleich
&&
Logisches UND
||
Logisches ODER
=
Zuweisung
N = numerische Typen
N, N
S, A
N, N
Größer
Größer gleich
L = logische Typen
N, N
Kleiner gleich
>=
A = alle
E, E
L, L
V, A
+=
Additionszuweisung
-=
Subtraktionszuweisung
*=
Multiplikationszuweisung
/=
Divisionszuweisung
%=
Modulozuweisung
V, N
Operatoren gleicher Priorität werden von links nach rechts ausgewertet. Die Reihenfolge der Auswertung
kann durch das Setzen von Klammern () verändert werden. Ausdrücke in Klammern werden zuerst
ausgewertet.
C. Endreß
11 / 13
10/2008
4. Variablen und Ausdrücke
Bei arithmetischen Ausdrücken gilt wie in der Mathematik: Punkt-vor-Strich-Rechnung.
Beispiel: Aus der Mathematik ist das Polynom zweiten Grades bekannt: y = a x2 + b x + c.
Die Berechnung von y erfolgt in Java über eine Zuweisung.
y = a * x * x + b * x + c;
Reihenfolge der Operatoren-Auswertung
Sind die Variablen mit den Werten a = 2, b = 3, c = 7, x = 5 initialisiert, ergibt sich die Auswertung
y = 2 * 5 * 5 + 3 * 5 + 7;
y =
10
* 5 + 3 * 5 + 7;
y =
50
+ 3 * 5 + 7;
y =
50
+
y =
65
y =
4.4
15
+ 7;
+ 7;
72;
Anweisungen
Eine Anweisung ist eine Ausführungseinheit, d.h ein vollständiger Befehl an den Computer. Anweisungen
verändern den Zustand von Programmen - z.B. durch Berechnungen von Variablen.
Ein Programm besteht aus einer Folge von Anweisungen, die in einer bestimmten Reihenfolge
ausgeführt werden.
Ausdrücke, die Berechnungen anstellen, oder Befehle, die bestimmte Abläufe bewirken, werden in
Anweisungen formuliert.
Anweisungen werden in Java mit Semikolon (;) abgeschlossen.
y = x + 7 / a;
System.out.println(“Java bereitet mir Kopfschmerzen!“);
Mehrere Anweisungen werden in Code-Blöcken zusammengefasst. Die Blöcke werden durch ein
Klammerpaar { } eingeschlossen.
Anfang
des Code-Blocks
{
Anweisung1;
Anweisung2;
...
}
Ende des Code-Blocks
C. Endreß
12 / 13
10/2008
4. Variablen und Ausdrücke
4.5
Kommentare
Kommentare dienen dazu, Notizen, Bemerkungen oder Erläuterungen direkt in den Quelltext
aufzunehmen.
Einzeiliger Kommentar
//
// Kommentar bis zum Zeilenende
Mehrzeiliger Kommentar
/*
*/
/* ... Kommentar über mehrere Zeilen */
Dokumentationskommentar
/**
*/
/** Dokumentation über mehrere Zeilen, kann mit
javadoc aus der Quelle extrahiert und in ein HTMLDokument geschrieben werden */
Die Laufzeit oder Ausführung des Programms wird durch Kommentare nicht beeinflusst, weil der
Compiler Kommentare nicht übersetzt und in das Programm einbindet.
Spitzen Sie Ihren Bleistift!
Nachdem Sie nun einiges über Variablen, Konstanten und Ausdrücke erfahren haben,
versuchen Sie herauszufinden, welche der folgenden Anweisungen zulässig sind und
welche nicht!
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
C. Endreß
int x = 34.5;
boolean boo = x;
int g = 17;
int y = g;
y = y + 10.5;
short s;
s = y;
byte b;
byte v = b;
short n = 12;
v = n;
byte k = 128;
char a = ’Hallo’;
String z = ″Java″;
z = z + 5.0;
final int K = 10;
g++;
K++;
z = g + K + 100;
13 / 13
10/2008
5. Kontrollstrukturen
5. Kontrollstrukturen
Lernziele
☺ Wissen, was Kontrollstrukturen sind und wie sie verwendet werden.
☺ Die Struktogramm-/PAP-Notation von Kontrollstrukturen kennen und anwenden.
☺ Die Java-Syntax der Kontrollstrukturen Sequenz, Auswahl und Wiederholung kennen.
☺ Kontrollstrukturen aus Struktogramm-/PAP-Notation und Java-Syntax ineinander überführen
können.
☺ Kriterien zur Auswahl geeigneter Kontrollstrukturen kennen.
In der Programmiertechnik unterscheidet man
einfache Anweisungen und
Steueranweisungen oder Kontrollstrukturen.
Kontrollstrukturen steuern den Ablauf eines Algorithmus.
Eine problemadäquate Umsetzung von Problemlösungen in Kontrollstrukturen wird durch folgende vier
semantisch unterschiedliche Kontrollstrukturen ermöglicht:
Sequenz,
Auswahl,
Wiederholung,
Aufruf anderer Algorithmen.
5.1 Sequenz
Man formuliert eine Sequenz bzw. eine Aneinanderreihung, wenn mehrere Anweisungen hintereinander
auszuführen sind. Bei der Sequenz erfolgt die Abarbeitung von oben nach unten.
Der Anweisungsblock
Anweisungen, die nacheinander ausgeführt werden, fasst man in einem Block zusammen.
Ein Block gilt als eine einzelne Anweisung und kann überall dort verwendet werden, wo syntaktisch eine
einzelne elementare Anweisung erlaubt wäre.
Ein Block darf auch Anweisungen zur Deklaration lokaler Variablen enthalten. Diese sind nur innerhalb
des Blocks gültig und sichtbar.
Syntax
{
Anweisung1;
Anweisung2;
...
}
C. Endreß
1 / 10
09/2006
5. Kontrollstrukturen
Lokale Variablen dürfen sich nicht gegenseitig verdecken. Es ist also nicht erlaubt, eine bereits
deklarierte Variable x in einem tiefer geschachtelten Block erneut zu deklarieren. Das Verdecken von
Klassen- oder Instanzvariablen dagegen ist zulässig (this-Operator schafft dabei Klarheit).
5.2 Auswahlstrukturen
Auswahlstrukturen dienen dazu, bestimmte Programmteile nur beim Eintreten vorgegebener Bedingungen
auszuführen.
5.2.1
Ein-/zweiseitige Auswahl
if-Anweisung
Syntax
if( Ausdruck )
Anweisung1;
else
Anweisung2;
Ist der Ausdruck wahr, so wird Anweisung1 ausgeführt. Sonst wird Anweisung2 ausgeführt.
Die else-Alternative ist optional. Bei der einseitigen Auswahl fehlt sie. Ist das Ergebnis von
Ausdruck falsch, wird in diesem Fall mit der ersten Anweisung nach Anweisung1 fortgefahren.
Anstelle einer einzelnen Anweisung kann auch eine Folge von Anweisungen in einem Code-Block
angegeben werden, der von geschweiften Klammern { } eingefasst wird.
Struktogramm
ja
Programmablaufplan
Ausdruck
Anweisung1
nein
ja
Anweisung2
Anweisung1
Ausdruck
nein
Anweisung2
Beispiel: Einseitige Auswahl
Ein Hardware-Händler liefert frei Haus, wenn die Bestellmenge bei
mindestens 10 PC-Mäusen liegt. Bei einer geringeren Bestellmenge
berechnet er eine Transportpauschale von 10,00 €.
C. Endreß
2 / 10
09/2006
5. Kontrollstrukturen
Java
Struktogramm
Transportpauschale = 0
transportPauschale = 0;
Anzahl < 10
ja
if( anzahl < 10 )
transportPauschale = 10.0;
nein
Transportpauschale = 10
PAP
Transportpauschale = 0
nein
Anzahl < 10
ja
Transportpauschale = 10
Beispiel: Zweiseitige Auswahl
Wegen eines Jubiläums bietet der Hardware-Großhändler Sonderkonditionen beim Kauf von PC-Mäusen: Bei
Abnahme von weniger als 12 Mäusen gewährt er einen Rabatt von 3 %, bei größeren Abnahmemengen
beträgt der Rabatt 5 %.
Java
Struktogramm
Anzahl < 12
if( anzahl < 12 )
rabatt = 0.03;
else
rabatt = 0.05;
nein
ja
Rabatt = 3 %
Rabatt = 5 %
PAP
ja
Rabatt = 3 %
C. Endreß
3 / 10
Anzahl < 12
nein
Rabatt = 5 %
09/2006
5. Kontrollstrukturen
5.2.2
Mehrseitige Auswahl
Bei der mehrseitigen Auswahl werden mehrere Auswahlstrukturen ineinander geschachtelt. Für die
einzelnen Auswahlstrukturen gelten die Erläuterungen zur ein- und zweiseitigen Auswahl.
Bei der Schachtelung von Strukturen ist die Verwendung von geschweiften Klammern und das Einrücken
der einzelnen Kontrollblöcke besonders wichtig, um den Überblick über die Struktur zu behalten.
Beispiel: Mehrseitige Auswahl
Ein
Krawattenversand-Händler
führt
während
der
heißen
Sommermonate neue Rabattkonditionen für sein Krawattensortiment
ein, um seinen Umsatz zu steigern. Er wendet folgende Rabattstaffel an:
Bei einem Bestellwert von weniger als 100 € gewährt er 10 % Rabatt,
bei einem Bestellwert von 100 € bis 500 € beträgt der Rabatt 15 %, in
allen anderen Fällen liegt der Rabatt bei 20 %.
Java
Struktogramm
if( warenWert < 100 )
rabattSatz = 10;
else
{
if( warenWert <= 500 )
rabattSatz = 15;
else
rabattSatz = 20;
}
rabatt = warenWert * rabattSatz / 100;
Warenwert < 100
ja
nein
Rabattsatz = 10
Warenwert <= 500
ja
Rabattsatz = 15
nein
Rabattsatz = 20
Rabatt = Warenwert * Rabattsatz / 100
Wichtig:
Bei einer Schachtelung von if-else-Konstrukten bezieht sich else immer auf das vorangegangene if.
Fehlerhafte Einrückung sorgt hier für Missverständnisse:
if( ausdruck1 )
if( ausdruck2 )
anweisung1;
else
anweisung2;
if( ausdruck1 )
if( ausdruck2 )
anweisung1;
else
anweisung2;
C. Endreß
Die korrekte Einrückung sieht so aus:
4 / 10
09/2006
5. Kontrollstrukturen
5.2.3
Mehrfachauswahl
Bei der Mehrfachauswahl oder Fallunterscheidung ist, abhängig vom Ergebnis eines zu prüfenden
Ausdrucks, genau ein Zweig mit Anweisungen auszuführen. Die Mehrfachauswahl erhöht vor allem die
Übersichtlichkeit, wenn viele Alternativen zur Auswahl stehen.
switch-Anweisung
Syntax
switch( Ausdruck )
{
case Kontante1: Anweisung(en); break;
case Kontante2: Anweisung(en); break;
case Kontante3: Anweisung(en); break;
...
default: Anweisung;
}
Der Ausdruck dient als Selektor zum Auswählen der einzelnen Fälle.
Der Selektor muss eine Variable oder ein Ausdruck eines ganzzahligen Typs (byte, short, char
oder int) sein.
Es wird die Sprungmarke (case) angesprungen, deren Konstante mit dem Wert des Selektors
übereinstimmt.
Die optionale default-Marke wird angesprungen, wenn keine passende Sprungmarke gefunden wird.
Die optionale break-Anweisung beendet die Ausführung der switch-Struktur und verhindert so, dass
die Anweisungen der folgenden case-Marken ebenfalls ausgeführt werden.
Struktogramm
PAP
Ausdruck
Konstante1 Konstante2 Konstante3
Anweisung
C. Endreß
Anweisung
Anweisung
Ausdruck
default
Anweisung
5 / 10
Fall 1
Fall 2
Fall 3
Fall 4
Anw. 1
Anw. 2
Anw. 3
Anw. 4
09/2006
5. Kontrollstrukturen
Beispiel: Mehrfachauswahl
Der Krawattenversender aus dem vorangegangenen Beispiel erweitert sein Rabattmodell. Er gewährt seinen
Kunden einen Treuerabatt. Hierfür hat er alle Kunden in Kategorien eingeteilt und gewährt folgende
Rabattsätze:
Kategorie
Rabattsatz
1
10 %
2
12 %
3
15 %
4
20 %
5
30 %
andere
0%
Das Problem ist auch hier mit der Schachtelung mehrerer Auswahlstrukturen lösbar. Bei sehr vielen
Alternativen wird die mehrseitige Auswahl jedoch schnell unübersichtlich. Deshalb wird man in solchen Fällen
als Kontrollstruktur die Mehrfachauswahl verwenden.
Java
switch( kategorie )
{
case 1: rabattSatz = 10;
case 2: rabattSatz = 12;
case 3: rabattSatz = 15;
case 4: rabattSatz = 20;
case 5: rabattSatz = 30;
default: rabattSatz = 0;
}
C. Endreß
Struktogramm
Kategorie
=1
break;
break;
break;
break;
break;
=2
=3
=4
=5
sonst
Rabatt- Rabatt- Rabatt- Rabatt- Rabatt- Rabattsatz
satz
satz
satz
satz
satz
= 10% = 12% = 15% = 20% = 30% = 0%
6 / 10
09/2006
5. Kontrollstrukturen
5.3 Wiederholungsstrukturen
Wenn es nötig ist, bestimmte Programmteile mehrfach auszuführen, verwendet man
Wiederholungsstrukturen - sogenannte Schleifen oder Iterationen.
Eine Schleife besteht aus einer Anweisungsfolge, die mehrfach durchlaufen werden kann, und einer
Schleifenbedingung.
Lineare Struktur
Kopfgesteuerte Schleife
Fußgesteuerte Schleife
Anweisung1
Schleifenbedingung
Anweisung1
Anweisung2
Anweisung1
Anweisung2
Anweisung1
Anweisung2
Schleifenbedingung
...
Die Schleifenbedingung enthält die Kontrollinformationen für die Anzahl der Schleifendurchläufe.
Bei sogenannten geschlossenen Schleifen steht die Anzahl der Schleifendurchläufe bereits vor das
Schleifenausführung fest. Man nennt diese Schleifen daher auch Zählschleifen.
Bei sogenannten offene Schleifen steht die Anzahl der Schleifendurchläufe beim Eintritt in die Schleife
noch nicht fest.
Eine fußgesteuerte Schleife wird mindestens einmal durchlaufen. Weitere Wiederholungen
werden durch die Schleifenbedingung am Ende des Schleifenkörpers gesteuert.
Die kopfgesteuerte Schleife muss nicht zwingend durchlaufen werden. Die Schleifenbedingung
im Schleifenkopf steuert die Zahl der Wiederholungen.
C. Endreß
7 / 10
09/2006
5. Kontrollstrukturen
5.3.1
Die Zählschleife
for-Schleife
Syntax
for(Initialisierung; Schleifenbedingung; Wiederholungsausdruck){
Anweisung(en);
}
Der Ausdruck Initialisierung wird nur einmal beim Start der Schleife aufgerufen. Üblicherweise wird
hier die Zählvariable der Schleife initialisiert und auch deklariert. Fehlt der Initialisierungsteil, wird keine
Initialisierung im Kopf der Schleife durchgeführt.
Die Schleifenbedingung wird vor jedem Schleifendurchlauf ausgewertet. Ist das Ergebnis true, wird
der Anweisungsblock der Schleife ausgeführt. Fehlt die Schleifenbedingung, so setzt der Compiler an
dieser Stelle die Konstante true ein.
Im Wiederholungsausdruck können Sie ein oder mehrere Dinge angeben, die bei jedem
Schleifendurchlauf ausgeführt werden sollen (z.B. Zählvariable inkrementieren). Der
Wiederholungsausdruck wird am Ende jedes Schleifendurchlaufs ausgeführt. Anschließend wird
erneut die Schleifenbedingung geprüft.
Struktogramm
for ( Initialisierung; Schleifenbedingung; Wiederholungsausdruck )
Anweisung(en)
100 Mal wiederholen
for(int i = 0; i < 100; i++){ }
In normalem Deutsch bedeutet das: „100 Mal wiederholen.“
Wie der Compiler es sieht:
Erstelle eine int-Variable i und setze sie auf 0.
Wiederhole, solange i < 100 ist.
Addiere am Ende jedes Schleifendurchlaufs 1 zu i hinzu.
C. Endreß
8 / 10
09/2006
5. Kontrollstrukturen
Beispiel: Zählschleife
Eine Bank bietet ihren Kunden „Wachstumssparen“ zu folgende Konditionen an: Das Anlagekapital wird mit
einem Zinssatz von 4 % pro Jahr verzinst. Die jährlich anfallenden Zinsen werden dem Kapital zugeschlagen
und im Folgejahr mit verzinst (Zinseszinseffekt). Die Laufzeit der Anlage beträgt 5 Jahre.
Java
Struktogramm
for ( i = 1; i <= 5; i++ )
for( int i = 1; i <= 5; i++ )
{
zinsen = kapital * zinsSatz / 100;
kapital = kapital + zinsen;
}
5.3.2
zinsen = kapital * zinsSatz / 100
kapital = kapital + zinsen
Die kopfgesteuerte Schleife
while-Schleife
Syntax
Struktogramm
Solange Ausdruck wahr
while( Ausdruck ){
Anweisung(en);
}
Anweisung(en)
Wenn der Ausdruck, der vom Typ boolean sein muss, wahr ist, wird die Anweisung(en) im
Schleifenrumpf ausgeführt.
Am Ende des Schleifenkörpers wird Ausdruck erneut geprüft. Ist das Ergebnis false, wird der
Schleifenkörper nicht mehr wiederholt, und die Programmausführung wird hinter dem Schleifenkörper
fortgesetzt.
Beispiel: Kopfgesteuerte Schleife
Ein Anlagekapital von 10000 € wird jährlich mit 5 % Zinsen verzinst. Nach wie viel
Jahren hat sich das Kapital verdoppelt, wenn die jährlichen Zinsen dem Kapital
zugeschlagen werden?
C. Endreß
9 / 10
09/2006
5. Kontrollstrukturen
Java
Struktogramm
endkapital = kapital * 2
endkapital = 2 * kapital;
laufzeit = 0;
while( kapital < endkapital )
{
zinsen = kapital * zinsSatz / 100;
kapital = kapital + zinsen;
laufzeit = laufzeit + 1;
}
5.3.3
laufzeit = 0
Solange ( kapital < endkapital )
zinsen = kapital * zinsSatz / 100
kapital = kapital + zinsen
laufzeit = laufzeit + 1
Die Fußgesteuerte Schleife
do-while-Schleife
Syntax
Struktogramm
Anweisung(en)
do {
Anweisung(en);
} while( Ausdruck );
Solange Ausdruck wahr
Die do-while-Schleife wird mindestens einmal durchlaufen.
Wenn die Auswertung des Ausdrucks in der while-Anweisung das Ergebnis true liefert, wird der
Schleifenkörper erneut durchlaufen. Ist das Ergebnis false, wird die Programmausführung hinter der
while-Anweisung fortgesetzt.
Der Codeblock im Schleifenrumpf kann eine oder mehrere Anweisungen enthalten.
Beispiel: Fußgesteuerte Schleife
Die Problemstellung aus dem Beispiel der kopfgesteuerten Schleife kann auch mit einer fußgesteuerten
Schleife gelöst werden.
Java
Struktogramm
endkapital = kapital * 2
endkapital = 2 * kapital;
laufzeit = 0;
do
{
zinsen = kapital * zinsSatz / 100;
kapital = kapital + zinsen;
laufzeit = laufzeit + 1;
} while( kapital < endkapital );
C. Endreß
laufzeit = 0
zinsen = kapital * zinsSatz / 100
kapital = kapital + zinsen
laufzeit = laufzeit + 1
Solange ( kapital < endkapital )
10 / 10
09/2006
6. Felder, Enumerationen und Zeichenketten
6. Felder, Enumerationen und Zeichenketten
Lernziele
☺ Die Unterschiede zwischen den Java-Konzepten für einfache Datentypen und
Referenzdatentypen erklären können.
☺ Das Java-Konzept für Felder als Beispiel für Referenzdatentypen erklären, darstellen und
anwenden können.
☺ Das Java-Konzept für Aufzählungstypen (Enumerationen) erklären und anwenden können.
☺ Das Java-Konzept für Zeichenketten (Klasse String) als weiteres Beispiel für Referenzdatentypen
erklären, darstellen und anwenden können.
6.1 Einfache Datentypen und Referenzen
Bei einfachen Datentypen wird über den Namen der Variable direkt der Bereich des Speichers
angesprochen, in dem der Datenwert der Variable abgelegt ist. Der Name der Variablen stellt damit die
symbolische Adresse einer Speicherzelle dar.
Variable
primZahl
preis
131
295.90
6.2 Referenzdatentypen
Mit Hilfe von Referenzdatentypen können wir aus einfachen Datentypen „eigene“ Datentypen
erzeugen. Solche selbst definierten Typen sind erforderlich, wenn kompliziertere Anwendungen effizient
programmiert werden sollen.
In Java gibt es prinzipiell zwei Arten von Referenzdatentypen: Felder und Klassen.
Im Gegensatz zu einfachen Datentypen kann man mit Referenzdatentypen die eigentlichen Werte von
Variablen nicht direkt, sondern nur indirekt über eine Referenz bearbeiten.
Der Arbeitsspeicher eines Rechners ist in Speicherzellen eingeteilt, wobei jede Speicherzelle eine
numerische Adresse besitzt. Das folgende Bild soll den prinzipiellen Speicheraufbau verdeutlichen. In
der Speicherzelle mit der Adresse 1100 ist der ganzzahlige Wert 131 abgelegt. Dieser Variablen ist die
Bezeichnung primZahl als symbolische Adresse zugeordnet, über die direkt auf den Inhalt der
Speicherzelle mit der Adresse 1100 zugegriffen wird.
C. Endreß
1 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Adresse
im Speicher
Typ des
Inhalts
...
Symbolische
Adresse
primZahl
Inhalt der
Speicherzelle
1100
131 ganzzahliger Wert
...
nachName
1200
1600 Referenz
...
1600
Meier Zeichenkette
...
Die Speicherzelle mit der Adresse 1200, der die symbolische Adresse nachName zu geordnet ist, enthält
den Wert 1600. Im Unterschied zu einem einfachen Datentyp wie der Variablen primZahl wird der
Wert 1600 nicht als numerischer Wert interpretiert, sondern als Adresse. D.h. der Inhalt der Variablen
nachName ist ein Verweis - eine Referenz - auf eine andere Speicherzelle. Das eigentliche „Objekt“
befindet sich erst in der Speicherzelle mit der referenzierten Adresse. In diesem Beispiel enthält die
Speicherzelle mit der Adresse 1600 die Zeichenkette Meier. Die Speicherzelle mit der Adresse 1600
besitzt keine symbolische Adresse. Auf ihren Inhalt kann nur indirekt über Referenzen zugegriffen
werden.
Variablen von Referenzdatentypen enthalten Referenzen, d.h. Verweise auf Speicheradressen, an denen
die eigentlichen Daten-Objekte abgelegt sind.
Objekt
r1
Referenzen
r2
Daten
variable
Objekt
Daten
Referenz
Es können auch mehrere Referenzen auf dasselbe Objekt verweisen.
C. Endreß
2 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Referenzdatentypen auf Gleichheit prüfen: Der Gleichheitsoperator (==) prüft nur die
Gleichheit der Speicheradressen, auf die die
Referenzen verweisen. Die inhaltliche Gleichheit
der referenzierten Objekte muss mit einer Methode
(z.B. equals()) geprüft werden.
variable1
Referenzen
==
Objekte
equals()
variable2
6.3 Felder
6.3.1
Einführendes Beispiel: Terminkalender
Wir wollen einen kleinen Terminkalender programmieren. Unser Programm soll für jede Stunde des Tages
einen Texteintrag ermöglichen.
Montag
00:00
12:00
01:00
13:00
Gleich ist die Schule aus
02:00
14:00
Mittagessen bei McD.
03:00
15:00
Siesta
04:00
16:00
05:00
Weckerklingeln
17:00
Hausaufgaben für Programmierung
06:00
Heftiges Weckerklingeln
18:00
Biergarten
07:00
Aufstehen
19:00
08:00
Doppelstunde Programmierung
20:00
09:00
21:00
10:00
22:00
11:00
23:00
Schlafen
Der Einfachheit halber realisieren wir das Programm zunächst nur für einen Wochentag, z.B. Montag. Das
Programm soll Einträge über ein Menü aufnehmen und eine komplette Tagesseite auf dem Bildschirm
darstellen können. Eine Realisierung mit den Mitteln, die uns bisher zur Verfügung stehen, erstreckt sich
über mehrere Seiten und könnte folgendermaßen aussehen:
C. Endreß
3 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
/* Programm: UmstaendlicherKalender
Umstaendlicher Terminkalender fuer 24 Stundeneintraege eines
Kalendertags
*/
import java.io.IOException;
import support.Console;
public class UmstaendlicherKalender {
1
2
3
public static void main(String[] args) throws IOException {
// Variabledeklaration
// Fuer jede Stunde eine Variable
String termin_00 = "";
String termin_01 = "";
String termin_02 = "";
String termin_03 = "";
String termin_04 = "";
String termin_05 = "";
String termin_06 = "";
String termin_07 = "";
String termin_08 = "";
String termin_09 = "";
String termin_10 = "";
String termin_11 = "";
String termin_12 = "";
String termin_13 = "";
String termin_14 = "";
String termin_15 = "";
String termin_16 = "";
String termin_17 = "";
String termin_18 = "";
String termin_19 = "";
String termin_20 = "";
String termin_21 = "";
String termin_22 = "";
String termin_23 = "";
// Das Hauptprogramm in einer Schleife
boolean fertig = false;
while (!fertig) {
// Zuerst ein Bildschirmmenue
Console.println("\n1 = Neuer Eintrag");
Console.println("2 = Termine ausgeben");
Console.println("3 = Programm beenden");
Console.print("Ihre Wahl ->");
int auswahl = Console.readInt();
// Fallunterscheidung der Auswahl
switch (auswahl) {
case 1: // Termine eingeben
Console.print("Welche Uhrzeit? ->");
int uhrzeit = Console.readInt();
if (uhrzeit < 0 || uhrzeit > 23) {
Console.println("Eingabefehler!");
break;
}
4
Console.print("Termin ->");
String eintrag = Console.readln();
// Termin einordnen
switch (uhrzeit) {
C. Endreß
4 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
case 0:
termin_00
break;
case 1:
termin_01
break;
case 2:
termin_02
break;
case 3:
termin_03
break;
case 4:
termin_04
break;
case 5:
termin_05
break;
case 6:
termin_06
break;
case 7:
termin_07
break;
case 8:
termin_08
break;
case 9:
termin_09
break;
case 10:
termin_10
break;
case 11:
termin_11
break;
case 12:
termin_12
break;
case 13:
termin_13
break;
case 14:
termin_14
break;
case 15:
termin_15
break;
case 16:
termin_16
break;
case 17:
termin_17
break;
case 18:
termin_18
break;
case 19:
termin_19
break;
case 20:
termin_20
break;
C. Endreß
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
= eintrag;
5 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
case 21:
termin_21 = eintrag;
break;
case 22:
termin_22 = eintrag;
break;
case 23:
termin_23 = eintrag;
break;
}
break;
case 2: // Termine ausgeben
Console.println(" 0 Uhr: " + termin_00);
Console.println(" 1 Uhr: " + termin_01);
Console.println(" 2 Uhr: " + termin_02);
Console.println(" 3 Uhr: " + termin_03);
Console.println(" 4 Uhr: " + termin_04);
Console.println(" 5 Uhr: " + termin_05);
Console.println(" 6 Uhr: " + termin_06);
Console.println(" 7 Uhr: " + termin_07);
Console.println(" 8 Uhr: " + termin_08);
Console.println(" 9 Uhr: " + termin_09);
Console.println("10 Uhr: " + termin_10);
Console.println("11 Uhr: " + termin_11);
Console.println("12 Uhr: " + termin_12);
Console.println("13 Uhr: " + termin_13);
Console.println("14 Uhr: " + termin_14);
Console.println("15 Uhr: " + termin_15);
Console.println("16 Uhr: " + termin_16);
Console.println("17 Uhr: " + termin_17);
Console.println("18 Uhr: " + termin_18);
Console.println("19 Uhr: " + termin_19);
Console.println("20 Uhr: " + termin_20);
Console.println("21 Uhr: " + termin_21);
Console.println("22 Uhr: " + termin_22);
Console.println("23 Uhr: " + termin_23);
break;
case 3: // Programm beenden
fertig = true;
break;
default: // Falsche Zahl eingegeben
Console.println("Eingabefehler!");
}
5
}
}
}
Erläuterungen
1
Für jede Stunde des Tages wird eine Variable vom Typ String deklariert, die eine Zeichenkette –
in diesem Fall den Termineintrag - aufnehmen kann. Alleine hierfür sind 24 Deklarationen
erforderlich.
2
In einem kleinen Konsolenmenü wird dem Benutzer die Auswahl zwischen einer
Termineintragung, der Kalenderausgabe und dem Programmende angeboten. Die Auswahl
erfolgt numerisch und wird in der ganzzahligen Variablen auswahl gespeichert.
3
Zum Auswerten der Menüauswahl wird der Wert von auswahl einer Fallunterscheidung
unterzogen.
4
Zum Eintragen oder Verändern eines Termins müssen Uhrzeit (uhrzeit) und Text (eintrag)
eingegeben werden. Um herauszufinden, in welcher Terminvariablen der Eintrag zu speichern
C. Endreß
6 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
ist, muss die Variable uhrzeit einer aufwendigen Fallunterscheidung unterzogen werden. Es
sind 24 Fälle zu unterscheiden.
Bei einer Kalenderausgabe sind ebenfalls wieder 24 Fälle zu unterscheiden, da die
Terminvariablen einzeln angesprochen werden müssen.
5
Wir haben also dreimal 24 Zeilen, d.h. 72 Zeilen allein darauf verwendet Texteinträge für einen
Kalendertag zu verwalten. Wenn wir nun von 365 Tagen pro Jahr ausgehen und unseren Kalender 5
Jahre lang verwenden wollen, erhalten nach der gegebenen Lösungsvariante eine Programmlänge von
weit über 130.000 Programmzeilen! Viel Spaß beim Programmieren!
Wenn wir das Problem näher analysieren, stellen wir fest, dass unsere Terminvariablen termin_00 bis
termin_23 alle vom Typ String sind und ähnliche Inhalte – nämlich Termine - repräsentieren. Auch
die Bezeichner der Variablen unterscheiden sich nur durch eine nachstehende Ziffer, d.h. durch einen
Index. Wenn es also eine Möglichkeit gäbe, verschiedene Variablen nur durch ihren Index anzusprechen,
könnten wir uns damit die aufwendigen switch-Konstrukte zur Fallunterscheidung ersparen.
Diese Möglichkeit eröffnen uns die sogenannten Felder oder Arrays. Mit ihrer Hilfe können wir Werte
wie in einer Tabelle anlegen und über einen Index Zugriff auf die Werte erhalten.
6.3.2
Ein bisschen Theorie zu Feldern (Arrays)
In der Praxis tritt oft das Problem auf, dass viele Variablen vom gleichen Typ deklariert werden müssen,
die auch inhaltlich eine identische Bedeutung haben wie z.B. double messWert1, double
messWert2, ...
Aus diesem Grund enthalten fast alle Programmiersprachen einen Konstruktionsmechanismus, der es
erlaubt, typenidentische Variablen zu einer Einheit zusammenzufassen. Eine solche Typenkonstruktion
bezeichnet man als Feld oder Array.
Ohne Feld:
messWert1
messWert2
messWert3
Elemente (alle vom gleichen Typ)
Mit Feld:
messReihe
Index
0
1
2
Ein Feld/Array besteht aus Elementen bzw. Komponenten desselben Typs. Auf die einzelnen Elemente des
Felds wird über Indizes zugegriffen.
Im Gegensatz zu anderen Programmiersprachen sind Felder in Java Objekte, d.h.
Feld-Variablen sind Referenzen,
Felder besitzen Methoden und Instanzvariablen,
Felder werden zur Laufzeit erzeugt.
C. Endreß
7 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Die Feld-Variable ist eine Referenz auf ein Feld-Objekt.
Referenzen sind Zeiger auf Objekte. Sie enthalten keine Daten, sondern nur die Speicheradresse des
Objekts, auf das sie deuten. Es können mehrere Referenzen auf ein Objekt deuten.
Referenz
Feld-Variable
Objekt
Feld-Objekt
Element[0]
Element[1]
Element[n-1]
Das Feld-Objekt ist eine Einheit, welche die Feld-Elemente enthält.
Das Feld-Objekt wird einer Feld-Variablen zugeordnet.
Felder in Java sind semidynamisch. Ihre Größe kann zur Laufzeit festgelegt, danach aber nicht mehr
verändert werden.
6.3.3
Felder erzeugen
Um ein Feld in Java zu erzeugen, sind zwei Schritte nötig:
Deklaration einer Feld-Variablen:
int[] feld;
feld
null
boolean[] c;
double[] messReihe;
Wahlweise können die eckigen Klammern auch hinter den Variablennamen geschrieben werden, aber das ist ein
Tribut an die Kompatibilität zu C/C++ und sollte in neuen Java-Programmen vermieden werden.
Zum Zeitpunkt der Deklaration wird noch nicht festgelegt, wie viele Elemente das Feld besitzen soll.
Diese Festlegung erfolgt erst bei seiner Initialisierung mit Hilfe des new-Operators.
Festlegen der Feldgröße:
feld = new int[4];
feld
0
messReihe = new double[10];
0
Alle Elemente des Feldes werden bei der Erzeugung mit new
automatisch initialisiert. Die Initialwerte hängen vom Typ des Feldes ab:
C. Endreß
0
c = new boolean[7];
Datentyp
Default-Wert
boolean
false
ganzzahlige Typen
0
Fließkommatypen
0.0
char
’\u0’
Objekte
null
8 / 23
0
10/2008
6. Felder, Enumerationen und Zeichenketten
Mit dem new-Operator erzeugte Arrays werden häufig in Zählschleifen initialisiert.
for(int i = 0; i < 4; i++){
feld[ i ] = i + 1;
}
feld
1
feld[0]
2
feld[1]
3
feld[2]
4
feld[3]
Alternativ zur Verwendung des new -Operators kann ein Feld auch literal initialisiert werden. Dazu
werden die Feld-Elemente mit einer Initialisierungsliste erzeugt.
int [] feld2 = { 1, 12, 4, 108};
boolean[] feld3 = { true, true, false };
String[] feld4 = {“Hallo,”, “ich bin”, “ein Array”};
1
feld2
true
feld3
Hallo,
feld4
12
true
ich bin
4
false
ein Array
108
Die Größe des Feldes ergibt sich hierbei aus der Anzahl der zugewiesenen Elemente. Anders als bei der
expliziten Initialisierung mit new, muss in diesem Fall die Initialisierung direkt bei der Deklaration
erfolgen.
Deklaration und Erzeugung eines Feldes können auch in einer Anweisung vollzogen werden:
int[] feld = new int[4];
boolean[] c = new boolean[7];
double[] messReihe = new double[10];
6.3.4
Felder verwenden
Bei der Erzeugung eines Feldes von n Elementen werden die einzelnen Elemente von 0 aufsteigend bis
n – 1 durchnummeriert.
Der Zugriff auf ein Element erfolgt über den Feldnamen und den numerischen Index des Elements, der
nach dem Feldnamen in eckige Klammern geschrieben wird. Der Zugriff kann sowohl schreibend als
auch lesend erfolgen.
x[0] = 17;
// weist dem ersten Element des Feldes x den Wert 17
// zu.
x[3] = 25;
// weist dem vierten Element des Feldes x den Wert 25
// zu.
x[4] = x[0] + x[3];
// weist dem fünften Element des Feldes x
// Summe der Elemente x[0] und x[3] zu.
die
Der Feld-Index muss vom Typ int sein. Aufgrund der automatischen Typkonvertierungen, die der
Compiler vornimmt, sind auch short, byte und char-Werte erlaubt. Fließkomma- oder booleanWerte sind als Indexwert nicht zulässig.
C. Endreß
9 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Obwohl long auch ein Ganzzahltyp ist, ist dieser Typ ebenfalls nicht als Index zulässig. Ein Feld mit
263 – 1 Elementen sprengt jeden Arbeitsspeicher – auch wenn die kleinsten Datentypen wie boolean
oder byte verwendet werden, die jeweils nur 1 Byte groß sind.
Jedes Feld hat eine Instanzvariable length, welche die Anzahl seiner Elemente angibt.
int[] temperaturen = {18, 23, 27, 32, 25};
int anzahlElemente = temperaturen.length;
// ergibt 5
18
temperaturen
23
27
length = 5
32
25
Häufig verwendet man for-Schleifen, um über die Elemente eines Feldes zu iterieren.
for(int i = 0; i < temperaturen.length; i++){
summe = summe + temperaturen[i];
}
durchSchnitt = durchSchnitt / temperaturen.length;
Indexwerte werden vom Laufzeitsystem auf Einhaltung der Feld-Grenzen geprüft. Die Indexwerte
müssen größer oder gleich 0 und kleiner als length sein. Andernfalls löst der Interpreter eine
ArrayIndexOutOfBoundsException aus.
6.3.5
Erweiterte for-Schleife
Mit Java 5 wurde speziell für Felder die sogenannte erweiterte for-Schleife eingeführt.
for(int temperatur : temperaturen){
summe = summe + temperatur;
}
durchSchnitt = durchSchnitt / temperaturen.length;
Der Iterator wird beim Start der Schleife mit dem ersten Element des Feldes initialisiert. Mit Beginn jedes
weiteren Durchlaufs wird der Iterator auf das jeweils folgende Element gesetzt. Auf diese Weise läuft die
Schleife selbständig über alle Elemente des Feldes. Explizite Zählvariablen oder Schleifenbedingungen
gibt es bei dieser vereinfachten Schleife nicht.
C. Endreß
10 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Beispiel: Umsatzbilanz einer Filiale
Die Filiale Nord der Firma Cash & Co hat im vergangenen Jahr folgende Umsätze erzielt:
1. Quartal
2. Quartal
3. Quartal
4. Quartal
65000 €
56000 €
54000 €
52000 €
Problemstellung:
Es sollen der Gesamtumsatz des Jahres und der durchschnittliche Quartalsumsatz
berechnet werden.
Lösungsansatz:
Die einzelnen Umsatzzahlen werden in einem Array gespeichert. Anschließend
erfolgen die geforderten Berechnungen.
Java-Quellcode
/* Programm: Umsatzbilanz
Demonstriert die Handhabung eines eindimensionalen Arrays
*/
import java.io.IOException;
import support.Console;
public class UmsatzBilanz {
public static void main(String[] args) throws IOException {
// Deklaration der Variablen
double[] umsaetze ;
umsaetze = new double[4];
double gesamtUmsatz = 0;
double quartalsDurchschnitt = 0;
1
2
3
4
// Einlesen der Quartalsumsätze
for(int i = 0; i < umsaetze.length; i++){
Console.print((i + 1) + ". Quartal -> ");
umsaetze[i] = Console.readDouble();
}
5
6
// Summe und Durchschnitt berechnen
for(double quartalsUmsatz : umsaetze){
gesamtUmsatz = gesamtUmsatz + quartalsUmsaetz;
}
7
quartalsDurchschnitt = gesamtUmsatz / umsaetze.length;
// Ausgabe der Ergebnisse
Console.println("Gesamtumsatz: " + gesamtUmsatz);
Console.println("Quartalsdurchschnitt: " + quartalsDurchschnitt);
}
}
Konsolen-Ausgabe
1. Quartal -> 65000
2. Quartal -> 56000
3. Quartal -> 54000
4. Quartal -> 52000
Gesamtumsatz: 227000.0
Quartalsdurchschnitt: 56750.0
C. Endreß
11 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Erläuterungen
1
Es wird eine Feld-Variable mit der Bezeichnung umsaetze deklariert für ein Feld mit Elementen des
Typs double. Ein Feld wird jedoch noch nicht angelegt. Die Variable umsaetze referenziert daher
noch keinen Speicherbereich, sondern wird zunächst mit dem Wert null initialisiert.
umsaetze
null
Feld-Variable
2
Der Operator new legt ein Feld mit 4 Elementen des Typs double im Arbeitsspeicher an. Java
initialisiert die Elemente des Feldes mit sogenannten default-Werten, die abhängig sind vom
Datentyp der Elemente. Der default-Wert für Variablen des Typs double ist 0.0. Die Zuweisung
initialisiert die Variable umsaetze mit einer Referenz auf das erzeugte Array.
umsaetze
null
Feld-Variable
0.0
umsaetze[0]
0.0
umsaetze[1]
0.0
umsaetze[2]
0.0
umsaetze[3]
Feld-Objekt
Die Deklaration (Zeile 1) und die Initialisierung (Zeile 2) des Feldes können auch in einer Anweisung
erfolgen:
double[] umsaetze = new double[4];
3
Wir benötigen eine Variable, in der wir den Gesamtumsatz speichern können. Daher deklarieren wir
eine Variable gesamtUmsatz vom Typ double und weisen ihr explizit den Wert 0.0 zu. Dieses
müssen wir tun, da die Variable gesamtUmsatz eine lokale Variable ist. Lokale Variablen werden in
Java nicht automatisch mit default-Werten initialisiert.
4
Auch für die Berechnung des Quartalsdurchschnitts benötigen wir eine double-Variable. Deklaration
und Initialisierung erfolgen analog zu Zeile 3.
5
Die Initialisierung des Feldes erfolgt in einer Zählschleife elementweise. Die Zählvariable i wird als
Index benutzt, um die einzelnen Array-Elemente anzusprechen. Beim Durchlaufen des Feldes muss
sichergestellt werden, dass der Feldindex nicht über die Feldgrenzen hinausläuft, denn ein
Feldelement umsaetze[4] gibt es nicht. Beim der Verwendung eines Positionsindex, der außerhalb
der Feldgrenzen liegt, kommt es zu einer ArrayIndexOutOfBoundsException. Deshalb
durchlaufen wir die for-Schleife nur solange die Bedingung i < umsaetze.length erfüllt ist,
d.h. solange i < 4 ist.
C. Endreß
12 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
6
Dem Feld werden die Umsatzwerte elementweise durch eine Konsoleneingabe des Benutzers
zugewiesen.
umsaetze
Feld-Variable
65000
umsaetze[0]
56000
umsaetze[1]
54000
umsaetze[2]
52000
umsaetze[3]
Feld-Objekt
7
6.3.6
Zur Berechung des Gesamtumsatzes müssen die Werte der einzelnen Feldelemente addiert werden.
Dazu wird mit einer vereinfachten for-Schleife über alle Feldelemente iteriert. Der Iterator
quartalsUmsatz wird beim Start der Schleife mit dem ersten Element des Feldes umsaetze
initialisiert. Mit Beginn jedes weiteren Durchlaufs wird der Iterator auf das jeweils folgende Element
gesetzt. Auf diese Weise läuft die Schleife selbständig über alle Elemente des Feldes umsaetze,
deren jeweiliger Wert zum gesamtUmsatz addiert wird.
Felder kopieren
Felder sind Objekte und können deshalb nicht mit dem Zuweisungsoperator (=) kopiert werden, wie es
bei Variablen einfacher Datentypen möglich ist.
int[] a = {10, 20, 30};
int[] b;
b = a;
a
Vorsicht!
Zwei Referenzen auf
dasselbe Array-Objekt
a[0] = 111;
20
b
30
a
111
20
b[2] = 321;
summe = a[0] + a[2];
10
// ergibt 432
b
321
a
10
Felder können kopiert werden durch
elementweises Kopieren (Schleifen erforderlich) oder
die Methode clone()
20
30
int[] a = {10, 20, 30};
int[] b;
b
b = (int[]) a.clone();
10
20
30
C. Endreß
13 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
6.4 Mehrdimensionale Felder
Alle Felder sind in Java grundsätzlich eindimensional.
Die Elemente eines Feldes können ihrerseits Felder sein, was das Schachteln von Feldern ermöglicht.
Mehrdimensionale Felder lassen sich durch geschachtelte Arrays darstellen.
Geschachtelte Felder werden durch mehrere Klammerpaare deklariert.
Beispiel: Umsatzbilanz einer Firma
Wir erweitern das vorangegangene Beispiel. Die Firma Cash & Co besitzt drei Filialen, die im vergangenen
Jahr folgende Umsätze erzielt haben:
Filiale
1. Quartal
2. Quartal
3. Quartal
4. Quartal
Nord
65000 €
56000 €
54000 €
52000 €
Mitte
70000 €
65000 €
62000 €
65000 €
Süd
75000 €
76000 €
84000 €
82000 €
Problemstellung: Unser Ziel ist es, die Umsatztabelle ist in strukturierter Form, d.h. in einem
zweidimensionalen Feld im Programmspeicher abzubilden, um verschiedene
rechnerische Auswertungen durchzuführen. So sollen beispielsweise die Jahresumsätze
für jede Filiale sowie die Firmenumsätze pro Quartal berechnet werden.
Lösungsansatz:
Die Umsatztabelle weist eine zweidimensionale Struktur auf. Die Umsätze einer Filiale
sind in einer Zeile abgelegt. Jede Spaltenposition in der Zeile entspricht einem Quartal.
Für eine einzige Filiale können die Umsätze in einem eindimensionalen Array
gespeichert werden, so wie wir es bereits im vorangegangenen Beispiel ausgeführt
haben. Für die drei angegebenen Filialen ergibt dieses Vorgehen die folgenden drei
eindimensionalen Felder:
double[] filialeNord = { 65000, 56000, 54000, 52000 };
double[] filialeMitte = { 70000, 65000, 62000, 60000 };
double[] filialeSued = { 80000, 75000, 67000, 70000 };
filialeNord
65000
56000
54000
52000
filialeMitte
70000
65000
62000
60000
filialeSued
80000
75000
67000
70000
Die drei Filial-Arrays werden nun zu einer Tabelle in Form eines zweidimensionalen
Feldes zusammengefasst. Dabei machen wir uns den Grundsatz zunutze, dass Elemente
eines eindimensionalen Feldes wiederum Felder sein können.
double[][] umsaetze = { filialeNord, filialeMitte, filialeSued };
umsaetze
filialeNord
filialeMitte
filialeSued
C. Endreß
14 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Das Feld umsaetze ist somit ein Feld, das aus Feldern besteht. Die Vektoren
filialeNord, filialeMitte und filialeSued der Filialen stellen die Zeilen des
neuen zweidimensionalen Feldes dar, das eine Dimension von 3 x 4 hat, d.h. 3 Zeilen
und 4 Spalten. Man bezeichnet diese Struktur auch als 3 x 4 Matrix.
Die Zeilen (horizontale Dimension) enthalten die Umsätze einer Filiale. Die Spalten
(vertikale Dimension) enthalten die Umsätze eines Quartals.
Die Position eines einzelnen Feldelementes wird durch den Zeilen- und den
Spaltenindex festgelegt, die in eckigen Klammern hinter dem Namen des Arrays
angegeben werden. So bezeichnet z.B. umsaetze[1][3] das Element in der zweiten
Zeile und der vierten Spalte mit dem Wert 60000. Die Zählung der Positionsnummern
beginnt mit Null.
Die Deklaration des Feldes umsaetze kann in Java auch mit einer Initialisierungsliste
erfolgen.
umsaetze
umsaetze[0][0]
umsaetze[0][1]
umsaetze[0][2]
umsaetze[0][3]
65000
56000
54000
52000
umsaetze[1][0]
umsaetze[1][1]
umsaetze[1][2]
umsaetze[1][3]
70000
65000
62000
60000
umsaetze[2][0]
umsaetze[2][1]
umsaetze[2][2]
umsaetze[2][3]
80000
75000
67000
70000
umsaetze[0]
umsaetze[1]
umsaetze[2]
umsaetze[i].length = 4
umsaetze.length = 3
Zeilenindex i = 0, 1, 2
double[][] umsaetze = { { 65000, 56000, 54000, 52000 },
{ 70000, 65000, 62000, 60000 },
{ 80000, 75000, 67000, 70000 } };
Java-Quellcode
/* Programm: UmsatzTabelle
Demonstriert die Handhabung eines zweidimensionalen Arrays
*/
import support.Console;
1
public class UmsatzTabelle {
public static void main(String[] args) {
// Deklaration des Feldes
double[][] umsaetze = { { 65000, 56000, 54000, 52000 },
{ 70000, 65000, 62000, 60000 },
{ 80000, 75000, 67000, 70000 } };
// Ausgabe der Tabelle
C. Endreß
15 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
2
3
4
for (int zeile = 0; zeile < umsaetze.length; zeile++) {
for (int spalte = 0; spalte < umsaetze[zeile].length; spalte++) {
Console.print(umsaetze[zeile][spalte] + "\t\t");
}
Console.println();
}
5
// Ausgabe mit erweiterter for-Schleife
for (double[] filialUmsatz : umsaetze) {
for (double quartalsUmsatz : filialUmsatz) {
Console.print(quartalsUmsatz + "\t\t");
}
Console.println();
}
6
7
}
}
Konsolen-Ausgabe
65000.0
70000.0
80000.0
56000.0
65000.0
75000.0
54000.0
62000.0
67000.0
52000.0
60000.0
70000.0
Erläuterungen
1
Deklaration eines zweidimensionalen Feldes mittels Initialisierungsliste: Die 3 Unterlisten mit jeweils
4 Elementen bilden die Zeilen des Arrays umsaetze.
2
Um ein zweidimensionales Feld in tabellarischer Form auf der Konsole auszugeben, sind zwei
geschachtelte Schleifen erforderlich. Die Zählvariable zeile der äußeren Schleife gibt den
Zeilenindex der auszugebenden Zeile vor. Der Zeilenindex beginnt bei 0 (erste Zeile). Die Schleife
wird durchlaufen, solange der Wert der Zählvariablen zeile kleiner ist als die Anzahl der Zeilen des
Arrays. Diese Anzahl ist in der Eigenschaft length des Arrays umsaetze abgelegt.
Daher lautet die Schleifenbedingung: zeile < umsaetze.length
3
Die innere Zählschleife hat eine Zählvariable spalte, den Spaltenindex symbolisiert und über alle
Spaltenpositionen der ausgewählten Zeile läuft. Der Spaltenindex beginnt bei 0 (erste Spalte). Die
Länge der auszugebenden Zeile wird über die Eigenschaft length des gewählten Zeilenvektors
umsaetze[zeile] erfragt.
Daher lautet die Schleifenbedingung: spalte < umsaetze[zeile].length
4
Auf die Elemente des Feldes wird über die Angabe des Feldnamens und der Positionsnummern der
Zeile und Spalte zugegriffen: umsaetze[zeile][spalte]
Die zur besseren Formatierung werden in der Ausgabeanweisung nach dem Umsatzwert jeweils zwei
Tabulator-Sequenzen ("\t\t") eingefügt.
5
Nach der Ausgabe einer Zeilen wird ein Zeilenvorschub ausgeführt.
6
Das zweidimensionale Feld umsaetze besteht aus zeilenweise angeordneten eindimensionalen
Feldern. Der Iterator, mit dem die einzelnen Zeilen angesprochen werden können, muss daher eine
Feldvariable sein (double[] filialUmsatz). Mit diesem Iterator durchläuft die äußere Schleife
die Zeilen der Matrix.
7
Die innere Schleife iteriert mit der double-Variablen quartalsUmsatz über alle Elemente des
Zeilenfeldes filialUmsatz.
C. Endreß
16 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Um ein mehrdimensionales Feld zu erstellen, muss wie bereits vom Eindimensionalen bekannt eine FeldVariable deklariert werden. Bei der Deklaration der Feld-Variablen ist für jede Dimension des Feldes ein
eckiges Klammerpaar hinter dem Typ der Elemente anzugeben.
Beispiel: Erzeugung eines Arrays mit 3 x 2 Elementen vom Typ int
int[][] matrix;
// Deklaration eines 2-dimensionalen Feldes
matrix = new int[3][5];
// Initialisierung des Array-Objekts mit 3 Zeilen
// und 5 Spalten
Deklaration und Initialisierung können bei mehrdimensionalen Feldern auch in einer Anweisung erfolgen.
int[][] matrix = new int[3][5];
Geschachtelte Felder können auch über geschachtelte Initialisierungslisten erzeugt werden.
int[][] matrix = { {1, -1, 2}, {2, 0, 1}, {1, 0, 0} };
Feld
1
-1
2
2
0
1
1
0
0
Unterfelder
Die Unterfelder müssen nicht gleich groß sein:
int[][] pascal = { {1}, {1, 1}, {1, 2, 1} };
Feld
1
1
1
1
2
Unterfelder
1
Länge des ersten Unterfeldes: pascal[0].length = 1
Länge des zweiten Unterfeldes: pascal[1].length = 2
Länge des dritten Unterfeldes: pascal[2].length = 3
Bei geschachtelten Feldern muss mindestens die Elementzahl der obersten Schachtelungsebene
angegeben werden. Bei tieferen Ebenen ist die Angabe optional. Wird eine Längenangabe in einer
tieferen Ebene gemacht, dann müssen alle darüber liegenden Ebenen ebenfalls festgelegt werden.
Lücken dürfen bei der Festlegung nicht entstehen. Außerdem müssen bei der Anwendung des newOperators so viele Ebenen wie in der Deklaration angegeben werden.
double[][][] dreiDimensionen1, dreiDimensionen2, dreiDimensionen3;
C. Endreß
dreiDimensionen1 = new double[2][3][];
// erlaubt
dreiDimensionen2 = new double[2][][4];
// Lücke nicht erlaubt
dreiDimensionen3 = new double[5][];
// eine Ebene fehlt,
// nicht erlaubt
17 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Der Arrayindex wird in Java grundsätzlich von 0 an gezählt. Bei geschachtelten Feldern muss der Index
für jede Dimension in einem eigenen Klammernpaar stehen.
int[][] matrix = new int [5][5];
int x = matrix[3][4];
6.5 Enumerationen
Eigene Konstanten können Sie erstellen, indem Sie Variablen als static final deklarieren. Wenn Sie
jedoch einen ganzen Satz von konstanten Werten erzeugen wollen, die die einzigen gültigen Werte für
eine Variable repräsentieren, benötigen Sie einen sogenannten Aufzählungstyp. Aufzählungstypen
bezeichnet man auch als Enumerationen.
In Java werden Enumeratioen mit dem Schlüsselwort enum definiert.
Java-Quellcode
/* Programm: DemoEnum
Demonstriert die Handhabung einer einfachen Aufzählung*/
public class DemoEnum {
enum Ampel {ROT, GELB, GRUEN};
public static void main(String[] args) {
Ampel signal = Ampel.ROT;
for(Ampel eineLampe : Ampel.values()){
System.out.println("Lampe: " + eineLampe);
}
if(signal == Ampel.ROT){
System.out.println("STOP! Rote Ampel!");
}
}
}
Konsolen-Ausgabe
Lampe: ROT
Lampe: GELB
Lampe: GRUEN
STOP! Rote Ampel!
Sie können Enumerationen in bedingten Anweisungen wie if- und switch-Anweisungen verwenden.
Enum-Instanzen können mit == oder der Methode equals() verglichen werden.
Mit einer erweiterten for-Schleife kann man eine Aufzählung durchlaufen. Die Methode values()
liefert ein Feld mit allen Werten der Aufzählung. Die Reihenfolge der Feldelemente entspricht der
Reihenfolge der Initialisierungsliste.
C. Endreß
18 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
Wenn Sie eine Enumeration erstellen, erzeugen Sie eine neue Klasse und erweitern implizit die Klasse
java.lang.Enum. Sie können eine Enumeration als eigenständige Klasse in einer eigenen Quelldatei
deklarieren oder als Member einer anderen Klasse wie im Beispiel die enum Ampel.
Sie können Enumerationen Konstruktoren, Methoden, Variablen und einen sogenannten
konstantenspezifischen Klassenbody zuweisen.
/* Programm: DemoEnumComplex
Das Programm demonstriert die Deklaration einer Enumeration in einer
eigenständigen Klasse. */
public enum Namen {
JERRY("Lead-Gitarre") {
public String singt() {
return "heiser";
}
},
BOBBY("Schlagzeug") {
public String singt() {
return "gar nicht";
}
},
PHIL("Bass");
private String instrument;
Namen(String instrument) {
this.instrument = instrument;
}
public String getInstrument() {
return instrument;
}
public String singt() {
return "gelegentlich";
}
}
public class DemoEnumComplex {
public static void main(String[] args) {
for (Namen name : Namen.values()) {
System.out.print(name + " spielt " + name.getInstrument());
System.out.println(" und singt " + name.singt());
}
}
}
Konsolen-Ausgabe
JERRY spielt Lead-Gitarre und singt heiser
BOBBY spielt Schlagzeug und singt gar nicht
PHIL spielt Bass und singt gelegentlich
C. Endreß
19 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
6.6 Zeichenketten: Die Klasse String
Zeichenketten werden in Java nicht mittels eines einfachen Datentypen, sondern in Form eines
Referenzdatentypen durch die Klasse String dargestellt. (Package: java.lang.String)
Die Klasse String bietet Methoden
zum Erzeugen von Zeichenketten,
zur Extraktion von Teilstrings,
zum Vergleich mit anderen Strings,
und zur Erzeugung von Strings aus primitiven Typen
Strings sind Objekte.
String-Variablen sind (wie Array-Variablen auch) Referenzenvariablen.
Ein Vergleich der Referenzvariablen mit dem Gleichheitsoperator (==) vergleicht lediglich die
Referenzen nicht aber den Inhalt der String-Objekt.
Zum physischen Kopieren von Zeichenketten benötigt man geeignete Methoden. Es reicht nicht aus,
die Referenzvariable eine Strings einer anderen String-Variablen zuzuweisen, da hierbei nur die
Referenzen kopiert werden.
Zeichenketten sind keine Zeichen-Felder.
6.6.1
Strings erzeugen
Zeichenketten-Objekte können auf zwei Arten erzeugt werden:
Vereinfachte Syntax (Literale Initialisierung)
String text = “Java“;
Konstruktoren
text
String text = new String( “Java“ );
Erzeugt einen neuen String aus einem String-Literal.
J
String-Variable
(Referenz)
a
v
String-Objekt
String neuerText = new String( alterText );
Erzeugt einen neuen String durch Duplizierung eines vorhandenen Strings.
char[] zeichenfeld = {’J’‚ ’a’, ’v’, ’a’};
String text = new String( zeichenfeld );
Erzeugt einen String aus einem vorhandenen Zeichen-Array.
C. Endreß
20 / 23
10/2008
a
6. Felder, Enumerationen und Zeichenketten
6.6.2
Strings verwenden
Zeichenketten verbinden: Konkatenation
Zeichenketten können mit dem + Operator aneinandergehängt werden.
String text = “Java“;
String neuerText = text + “ ist eine Insel.”;
Länge einer Zeichenkette
public int length()
Liefert die Länge einer Zeichenkette. Wenn das String-Objekt leer ist, wird der Wert 0 zurückgegeben.
String text = “Java“;
int laenge = text.length();
// laenge = 4
Vergleichen von Zeichenketten
public boolean equals(Object einObjekt)
Die Methode equals() liefert true, wenn das aktuelle String-Objekt und einObjekt gleich sind.
Diese Vergleichsmethode steht übrigens für alle Objekttypen, die von der Klasse Object abgeleitet
wurden zur Verfügung, muss allerdings für selbst definierte Klassen auch selbst implementiert werden,
da die Basisklasse Object nicht wissen kann, wie Objekte selbst definierter Klassen auf Gelichheit zu
prüfen sind.
String meinName = “Karl“;
String deinName = “Otto“;
if(meinName.equals(deinName))
Console.println(“Die Namen sind gleich!“);
else
Console.println(“Die Namen sind nicht gleich!“);
Ein Vergleich von String-Variablen mit dem Gleichheitsoperator == vergleicht nicht die Inhalte der
Zeichenketten, sondern lediglich die Referenzen, also die Adressen, auf die sich die String-Variablen
beziehen.
if(meinName == deinName)
Console.println(“Die Referenzen zeigen auf dasselbe String-Objekt“);
meinName
String-Variablen
(Referenzen)
deinName
C. Endreß
K
a
r
l
String-Objekte
==
O
21 / 23
t
t
equals()
o
10/2008
6. Felder, Enumerationen und Zeichenketten
Wichtige Methoden der Klasse String:
Methode
Beschreibung
charAt()
holt ein Zeichen aus dem String heraus
compareTo()
lexikalischer Vergleich zweier Strings
equals()
equalsIgnoreCase()
prüfen auf Gleichheit
startsWith()
endsWith()
vergleichen den Anfang bzw. das Ende eines Strings mit einem
angegebenen Wert
indexOf()
lastIndexOf()
suchen in einem String vorwärts bzw. rückwärts nach einem angegebenen
Zeichen oder Teilstring
substring()
gibt einen Teilstring aus einem String zurück
replace()
erzeugt eine neue Kopie aus dem String, in der ein Zeichen durch ein
anderes ersetzt wird
toUpperCase()
toLowerCase()
konvertieren die Groß-/Kleinschreibung eines Strings
trim()
entfernt Leerraum am Anfang und Ende
length()
gibt die Länge des Strings zurück
valueOf()
Statische Methode, die diverse primitive Datentypen in Strings konvertiert
String-Objekte sind unveränderlich! Es gibt keine Methode, um den Inhalt zu verändern. Die obigen
Methoden, die einen String zurückgeben, modifizieren nicht den übergebenen String, sondern erzeugen
stattdessen eine modifizierte Kopie. Die Klasse StringBuffer stellt Zeichenketten zur Verfügung, die
veränderbar sind.
Umwandlung einer Zahl in einen String
int zahl = 12;
String zahlAlsText;
zahlAlsText = String.valueOf(zahl);
Umwandlung eines Strings in eine Zahl
int zahl;
String text = "123";
zahl = Integer.parseInt(text);
In Java gibt es Hüllklassen (wrapper classes) für einfache Datentypen. Diese Klassen heißen gleich
oder ähnlich wie die zugehörigen Typen. Sie beginnen aber wie bei Klassennamen üblich mit
Großbuchstaben: z.B. die Klasse Integer für den Typ int.
Die Hüllklassen stellen Operationen und Attribute bereit, die dem elementaren Typ zugeordnet werden,
wie z.B. Zeichenkettenumwandlungen.
Seit der J2SE 5.0 gibt es einen Mechanismus, der das automatische Ein- und Auspacken von primitiven
Typen in und aus Wrapper-Klassen unterstützt. Dieses als Autoboxing bzw. Autounboxing
("automatisches Ein- und Auspacken") bezeichnete Verfahren sorgt dafür, dass an vielen Stellen
automatisch zwischen primitiven Typen und Wrapperobjekten konvertiert wird. Erwartet eine Methode
C. Endreß
22 / 23
10/2008
6. Felder, Enumerationen und Zeichenketten
beispielsweise einen Integer-Wert als Argument, kann außer einem Integer auch direkt ein int
übergeben werden. Dieser wird dann automatisch in einen gleichwertigen Integer konvertiert. Auch in
umgekehrter Richtung funktioniert das, etwa wenn in einem arithmetischen Ausdruck ein double
erwartet, aber ein Double übergeben wird.
Damit kann ab J2SE 5 die Umwandlung eines Strings in eine Zahl auch folgendermaßen ablaufen:
int zahl;
String text = "123";
zahl = Integer.valueOf(text);
C. Endreß
23 / 23
10/2008
7. Funktionen
7. Funktionen
Lernziele
☺ Wissen, wie Programme durch das Verwenden von Funktionen strukturiert werden und welche
Vorteile dieses Strukturierungsprinzip besitzt.
☺ Die Parameterübergabemechanismen erläutern können.
☺ Den Gültigkeitsbereich von Parametern und Variablen kennen.
☺ Methoden schreiben und den geeigneten Parameterübergabemechanismus anwenden können.
☺ Überladene Methoden bei geeigneten Problemlösungen verwenden können.
☺ Einfache rekursive Methoden schreiben können.
7.1 Divide et impera!
Eine Funktion ist ein abgeschlossener Programmteil, der aus einer oder mehreren Anweisungen besteht,
und einen eigenen Namen besitzt. Unter diesem Namen kann die Funktion von einem Programmteil
aufgerufen werden. Eine Funktion erledigt eine abgeschlossene Teilaufgabe.
Vorteile des Funktionskonzepts:
Zerlegung komplexer Problemstellungen in einfachere Teilprobleme
Übersichtlichkeit des Quellcodes durch Strukturierung
Mehrfachverwendung und Wiederverwendbarkeit von Softwarekomponenten
Einfachere Programmpflege, bessere Wartbarkeit
Funktionen stellen Dienstleistungen zur Verfügung. Zum Anwenden einer Funktion muss bekannt sein,
„was“ die Funktion macht. „Wie“ die Funktion ihre Aufgabe ausführt (Algorithmen und Variablen), ist
für den Anwender uninteressant. (Funktionale Abstraktion)
Beim Aufruf einer Funktion wird ein Speicherbereich reserviert, in dem die lokalen Variablen der Funktion
abgelegt werden. Nach Beendigung der Funktion kehrt das Programm wieder an die Aufrufstelle zurück
und setzt die Programmausführung fort.
Funktionen können durch Parameter Werte übergeben werden.
In Funktionen können lokale Variablen deklariert werden, die unabhängig von anderen Funktionen sind.
Allgemein bezeichnet man Funktionen, die Bestandteile von Klassen sind, als Methoden. Da JavaProgramme immer aus Klassen bestehen, spricht man in Java nur von Methoden.
C. Endreß
1/9
05/2007
7. Funktionen
7.2 Methoden
Eine Methode muss deklariert werden.
In Java muss die Deklaration immer in einer Klasse erfolgen.
Methoden
Syntax
Modifier Typ MethodenName([Parameterliste])
{
Anweisung(en);
[ return rueckgabeWert; ]
}
Der Modifier gibt die Sichtbarkeit der Methode an.
private
: Methode ist nur innerhalb der eigenen Klasse aufrufbar
public
: Methode kann auch von anderen Klassen aufgerufen werden
protected : gilt im Zusammenhang mit Vererbung, dazu später mehr
Typ gibt den Datentyp des Rückgabewertes an.
Mit return gibt die Methode einen Wert an den Aufrufer zurück.
Wenn die Methode kein Ergebnis zurückgibt, ist der Typ void zu verwenden.
Die Parameterliste ist optional. Mehrere Parameter werden durch Komma getrennt.
Für jeden Parameter sind der Typ und der Name des Parameters anzugeben.
Die Parameter in der Deklaration bezeichnet man als formale Parameter.
Die Werte, die beim Methodenaufruf an die Methode übergeben werden, nennt man aktuelle
Parameter.
Beispiel
Sichtbarkeit
Rückgabetyp
Methodenname
formaler Parameter
public double berechneQuadrat( double x )
{
return x * x;
}
C. Endreß
2/9
05/2007
7. Funktionen
7.2.1
Aufruf von Methoden
Eine Methode wird über ihren Namen aufgerufen. Auf den Namen folgt in Klammern die Liste der
aktuellen Parameter.
Beispiel:
import support.Console;
public class MethodenAufruf {
public static double quadrat( double x ) {
return x * x;
}
1
2
public static void main(String[] args) {
double y;
y = quadrat( 2.5 );
Console.print(“2.5 hoch 2 ist “ + y);
}
3
}
1
Deklaration der Methode quadrat. Das Schlüsselwort static deklariert die Methode als
sogenannte statische Methode, damit wir sie in der Methode main einfach mit dem Methodennamen
aufrufen können. Nähere Erläuterungen zu statischen Methoden erfolgen weiter unten.
2
Die Methode gibt ein Ergebnis mit der return-Anweisung an den Aufrufer zurück.
3
Aufruf der Methode: Der aktuelle Parameter hat den Wert 2.5. Dieser wird an die Methode
übergeben. Der Rückgabewert der Methode quadrat wird der Variable y zugewiesen.
7.2.2
Datenaustausch zwischen Methoden
Der Aufrufer einer Methode tauscht Daten mit der Methode über eine „Schnittstelle“ aus, die im
Methodenkopf durch die Festlegung der Parameterliste und des Rückgabetyps definiert wird.
Zinsertrag = berechneZins(Kapital, Zinssatz, Anlagedauer);
Schnittstelle
Methodenaufruf
Zinsertrag
Methode
Kapital
double
k
Zinsatz
double
p
Anlagedauer
int
T
int
int
berechneZins
zinsen
Rückgabewert
C. Endreß
3/9
05/2007
7. Funktionen
Als Seiteneffekt ist bezeichnet man einen Datenaustausch, der an der Schnittstelle vorbeiläuft. Das kann
z.B. bei der Verwendung von globalen Variablen passieren. Seiteneffekte sind unbedingt zu vermeiden,
da sie das Prinzip der funktionalen Abstraktion („was“ vom „wie“ trennen) untergraben und die
Modularisierung von Softwarekomponenten verhindern.
7.2.3
Gültigkeitsbereich von Variablen
Alle Variablen, die in einer Methode deklariert werden, sind lokale Variablen und existieren nur innerhalb
der Methode.
Der Speicherplatz für lokale Variablen wird erst angelegt, wenn die Methode ausgeführt wird. Wenn die
Methode beendet ist, wird der Speicherplatz wieder freigegeben und kann für andere Zwecke verwendet
werden. D.h. die Speicherinhalte sind nach dem Ende der Methode nicht mehr verfügbar.
Auch die formalen Parameter des Methodenkopfs sind lokale Variablen der Methode.
import support.Console;
public class MehrWertSteuer {
public static double mwst( double netto, double mwstSatz){
double steuer;
steuer = netto * mwstSatz / 100;
return steuer;
}
1
2
3
4
public static void main(String[] args) {
double netto = 200.0;
double brutto = 0.0;
brutto = netto + mwst( netto, 16.0 );
Console.println("Netto : " + netto);
Console.println("Brutto: " + brutto);
}
5
6
7
8
9
}
Ein Ablaufprotokoll verdeutlich den Programmablauf:
Zeile
C. Endreß
main()
netto
lokale Variable in mwst(),
verdeckt gleichnamige
Variable aus main()
brutto
5
200.0
6
200.0
0.0
7
200.0
0.0
netto
mwstSatz
1
---
---
200.0
16.0
2
---
---
200.0
16.0
3
---
---
200.0
16.0
32.0
4
---
---
200.0
16.0
32.0
8
200.0
232.0
mwst()
4/9
steuer
05/2007
7. Funktionen
7.2.4
Parameterübergabe
Welcher aktuelle Parameter welchem formalen Parameter zugeordnet wird, wird in Java durch die
Positionszuordnung festgelegt. Beim Methodenaufruf werden die akutellen Parameter in der Reihenfolge
angegeben, die durch die Liste der formalen Parameter bei der Methodendeklaration vorgegeben ist. Der
erste aktuelle Parameter wird dem ersten formalen Parameter zugeordnet, der zweite aktuelle Parameter
dem zweitem formalen Parameter usw.
Die Anzahl der aktuellen und formalen Parameter müssen immer übereinstimmen.
public static void main(String[] args) {
...
netto = 200.0;
brutto = netto + mwst( netto, 16.0 );
...
}
aktuelle Parameter
200.0
32.0
16.0
public static double mwst(double betrag, double mwstSatz){
double steuer;
steuer = betrag * mwstSatz / 100;
formale Parameter
return steuer;
}
Grundsätzlich unterscheidet man zwei Arten der Parameterübergabe:
die Übergabe per Wert (call by value )
die Übergabe per Referenz (call by reference)
call by value
In Java werden einfache Datentypen per Wert übergeben, d.h. der formale
Parameter wird mit einer Kopie des aktuellen Parameters initialisiert.
Veränderungen am Wert des formalen Parameters bleiben lokal beschränkt
und wirken nicht auf den Eingabeparameter des aufrufenden Programmteils
zurück.
netto
200.0
Kopie
betrag
200.0
In der Liste der aktuellen Parameter kann auch ein beliebiger Ausdruck stehen,
der dem Datentyp des formalen Parameters entspricht (z.B. ein arithmetischer
Ausdruck wie 7.5 + 14.8). Dieser wird zunächst ausgewertet und das Ergebnis
dann an den formalen Parameter der Methode übergeben.
C. Endreß
5/9
05/2007
7. Funktionen
call by reference
Objekte werden in Java per Referenz übergeben. Da Objekte sehr groß sein können (z.B. Arrays), ist
es nicht sinnvoll, ihre Werte in eine Methode zu kopieren. Es wird lediglich eine Kopie der ObjektVariable, die in Java immer eine Referenz ist, an den formalen Parameter übergeben.
Alle Änderungen, die in einer Methode vorgenommen werden, werden direkt am Originalobjekt
vorgenommen und nicht an einer Kopie.
Das Kopieren von Referenzen spart Speicherplatz und Kopierzeit.
public class CallByReference {
1
public static double summe( double[] a ) {
double sum = 0.0;
for( int i = 0; i < a.length; i++)
sum = sum + a[i];
return sum;
}
public static void main(String[] args) {
double[] umsatz;
...
gesamtUmsatz = summe( umsatz );
...
}
2
3
}
1
Der formale Parameter der Methode summe ist die Array-Variable a. Eine Array-Variable ist immer
eine Referenz auf ein Array. Außerdem ist die Variable a eine lokale Variable der Methode summe().
2
Als Array-Variable ist umsatz ebenfalls eine Referenz auf ein Feld.
3
Der aktuelle Parameter enthält eine Referenz (also eine Speicheradresse) auf das Array umsatz.
Dieser Referenzwert wird an den formalen Parameter der Methode summe als übergeben. Der
formale Parameter ist die Variable a, die durch die Parameterübergabe zu einer zweiten Referenz
auf das bestehende Array wird. Allerdings existiert a nur als lokale Variable in der Methode summe()
und wird nach Ausführung der Methode wieder aus dem Arbeitsspeicher gelöscht.
Array-Objekt
umsatz
Kopie der
Referenz
a
C. Endreß
6/9
05/2007
7. Funktionen
7.2.5
Überladen von Methoden
Die Signatur einer Methode besteht aus dem Methodennamen sowie der Anzahl und der Reihenfolge der
Typen der formalen Parameter. Der Ergebnistyp gehört nicht zur Signatur.
public static double berechneZins(double k, double p, int T)
Signatur
Bei einem Methodenaufruf wird zunächst die Signatur der Methode bestimmt und anschließend die
passende Methode gefunden. Falls in einer Klasse zwei oder mehr Methoden dieselbe Signatur besitzen,
gibt der Compiler Fehlermeldungen aus, weil die Methoden nicht mehr unterscheidbar sind.
Besitzen zwei oder mehr Funktionen derselben Klasse denselben Namen, aber sonst unterschiedliche
Signaturen, so bezeichnet man den Funktion als überladen.
Vorteil des Überladens: Man muss keine unterschiedlichen Methodennamen wählen, wenn Methoden
sich lediglich in ihren Parametern unterscheiden aber sonst die gleiche Aufgabe erledigen.
1
public class MethodenUeberladen {
public static double summe( double[] a ) {
double sum = 0.0;
for( int i = 0; i < a.length; i++)
sum = sum + a[i];
return sum;
}
public static int summe( int[] a ) {
int sum = 0.0;
for( int i = 0; i < a.length; i++)
sum = sum + a[i];
return sum;
}
2
public static void main(String[] args) {
double[] umsatz;
int[] arbeitsTage;
...
jahresArbeitsZeit = summe( arbeitsTage );
jahresUmsatz = summe( umsatz );
...
}
3
4
}
1
Deklaration der ersten Methode summe mit einen Parameter vom Typ double[].
2
Deklaration der ersten Methode summe mit einen Parameter vom Typ int[].
3
Passend zur Signatur des Aufrufs wird die Methode in Zeile 2 aufgerufen.
4
Passend zur Signatur des Aufrufs wird die Methode in Zeile 1 aufgerufen.
C. Endreß
7/9
05/2007
7. Funktionen
7.2.6
Statische Methoden
Methoden mit dem Attribut static (statisch) sind nicht an die Existenz eines konkreten Objekts
gebunden, sondern existieren vom Laden der Klasse bis zum Beenden des Programms.
Statische Methoden können aufgerufen werden, ohne ein Objekt der Klasse zu verwenden, in der die
Methode deklariert wurde.
Methoden, die nicht statisch sind, können nur über zugehörige Objekte aufgerufen werden.
einige Beispiele:
statische Methoden
nicht statische Methoden
public static void main(String[] args)
String text = “Hallo”;
text = text.toUpperCase();
int zahl = Console.readInt();
char zeichen = text.charAt(4);
double x = Math.sqrt(6.25);
Console.print(“Ich bin static”);
7.2.7
Rekursion
Wenn eine Anweisung innerhalb einer Methode die Methode selbst wieder aufruft, bezeichnet man die
Methode als rekursiv.
Mit rekursiven Algorithmen können Problem auf sehr elegante Weise gelöst werden. Da allerdings bei
jedem erneuten Aufruf der Funktion ein eigener Namensraum für die Funktion und ihre lokalen Daten
angelegt wird, ist die Performance von rekursiven Algorithmen schlechter als bei iterativen Lösungen.
Beispiel: Fakultät
Die Fakultät einer natürlichen Zahl ist folgendermaßen definiert:
5 ! = 5 ⋅ 4 ⋅ 3 ⋅ 2 ⋅ 1 = 120
n ! = n ⋅ (n − 1) ⋅ (n − 2) ⋅ ... ⋅ 2 ⋅ 1
n != n ⋅
(n − 1)!
Rekursive Formulierung:
1
, falls
⎧
n! = ⎨
⎩ n ⋅ (n − 1)! , falls
n=1
n>1
Rekursive Funktionen bestehen aus
einem Teil, der das Problem verkleinert und einen rekursiven Aufruf ausführt
sowie einem Teil, der ein Ergebnis zurückliefert (Basisfall, Abbruchbedingung).
C. Endreß
8/9
05/2007
7. Funktionen
Der Quellcode einer rekursiven Fakultätsfunktion könnte so aussehen:
public static int fakultaet( int n )
{
if( n > 1 )
return n * fakultaet( n – 1 );
else
return 1;
}
Zur Berechnung von 5! ergibt sich der folgende Rekursionsablauf:
rekursiver Abstieg
rekursiver Aufstieg
5!
5!
5! = 5 * 24 = 120
5 * 4!
5 * 4!
4! = 4 * 6 = 24
4 * 3!
4 * 3!
3! = 3 * 2 = 6
3 * 2!
3 * 2!
2! = 2 * 1 = 2
2 * 1!
2 * 1!
1! = 1
1
Basisfall
1
Solange das Argument n > 1 ist, wird die Funktion fakultaet mit einem verkleinerten Argument (n-1)
erneut aufgerufen. Erst wenn der Basisfall n = 1 erreicht wird, wird ein Wert (hier 1) zurückgegeben.
C. Endreß
9/9
05/2007
8. Objekte und Klassen – Teil 1
8. Objekte und Klassen – Teil 1
Lernziele
☺ Die Begriffe Klasse, Objekt, Attribut, Operation und Botschaft anhand von Beispielen erklären
können.
☺ UML-Notation zur Darstellung von Klassen anwenden.
8.1 Einführung
Objektorientierte Programmierung erlaubt eine natürliche Modellierung vieler Problemstellungen.
Ein Objekt ist ein tatsächlich existierendes „Ding“ aus der Anwendungswelt des Programms.
Beispiel: Girokonto
einzahlen
4711
auszahlen
Kontostand
Objekte haben
bestimmte Eigenschaften,
ein bestimmtes Verhalten,
eine Identität.
Denke in Begriffen des Problems!
Man muss unterscheiden zwischen der Beschreibung von Objekten und den Objekten selbst:
Die Beschreibung eines Objekts entspricht einem Bauplan (Klasse).
Mit einem Bauplan können beliebig viele Objekte erstellt werden (Exemplare, Instanzen).
Vorteile der objektorientierten Programmierung (OOP):
Objektorientierung wird nicht nur in der Programmierung, sondern auch als Prinzip in der Analyse
und dem Entwurf von Software eingesetzt. Daraus ergibt sich eine Software-Entwicklung „aus einem
Guss“.
Modularisierung des Software-Projekts
Wiederverwendbarkeit von Software-Komponenten
C. Endreß
1 / 12
08/2006
8. Objekte und Klassen – Teil 1
8.2 Klassen
Beispiel
Für eine Bank-Software soll eine Kontoverwaltung entwickelt werden. Zunächst sollen nur Girokonten
verwaltet werden. In einem ersten Schritt werden die Anforderungen an die Software in einem
Pflichtenheft zusammengefasst:
/1/
Zu einem Girokonto werden eine Kontonummer und der aktuelle Kontostand gespeichert.
/2/
Wird ein Girokonto angelegt, so wird diesem eine Kontonummer zugewiesen.
/3/
Es werden Ein- und Auszahlungen auf dem Girokonto vorgenommen.
/4/
Alle Daten des Kontos können einzeln gelesen werden.
Modellierung des Girokontos
Ein Girokonto hat einen Zustand, der durch seine Attribute (z.B. seinen Kontostand und seine
Kontonummer) beschrieben wird.
Ein Girokonto hat ein Verhalten, das durch seine Operationen (z.B. einzahlen und auszahlen)
beschrieben wird.
Um eine Operation aufzurufen, wird eine Botschaft an das betreffende Objekt gesendet. Diese Botschaft
aktiviert eine Operation gleichen Namens. Die gewünschten Ausgabedaten werden an den Sender der
Botschaft zurückgegeben.
Empfang einer
Botschaft
einzahlen
auszahlen
kontoNr
kontoStand
getKontoNr
Empfang einer
Botschaft
C. Endreß
getKonto
Stand
Antwort auf eine
Botschaft
2 / 12
08/2006
8. Objekte und Klassen – Teil 1
Definitionen
Attribute sind Eigenschaften eines Objekts. Sie beschreiben die Daten, die von Objekten einer Klasse
angenommen werden können.
Attribute sind also ...
Datenelemente,
die durch einen Namen bezeichnet werden,
einen bestimmten Typ haben und
verschiedene Werte speichern können.
Alle Objekte einer Klasse besitzen dieselben Attribute, die sich im allgemeinen jedoch in ihren Werten
unterscheiden.
Operationen oder Methoden beschreiben das Verhalten eines Objekts. Sie sind Funktionen, die in der
Klasse definiert sind. Jede Operation kann auf alle Attribute eines Objekts dieser Klasse zugreifen.
Operationen kommunizieren mit der Umwelt über Ein- und Ausgabeparameter.
Das Senden einer Botschaft oder Nachricht an ein Objekt bedeutet den Aufruf der gleichnamigen
Operation.
Mit einem Objekt können nur die Aktionen ausgeführt werden, die es nach außen zur Verfügung stellt
(Schnittstelle).
Ein Objekt reagiert nur auf Botschaften, die es „versteht“.
Eine Botschaft besteht aus 3 Teilen:
Empfänger-Objekt
Name der Methode
Parameter der Methoden
Ein Objekt kann mit einem Rückgabewert auf eine Botschaft antworten. Methoden mit Rückgabewerten
werden üblicherweise zum Abfragen der aktuellen Attributwerte verwendet (z.B. getKontoStand())
Eine Klasse ist ein Bauplan für Objekte. Sie definiert die Attribute und Methoden, die alle Objekte besitzen,
die von dieser Klasse erzeugt werden.
Jede Klasse besitzt einen Namen (z.B. „GiroKonto“).
Jede Klasse besitzt einen Mechanismus, um Objekte zu erzeugen. Dieser Mechanismus wird durch eine
oder mehrere (überladene) Methoden realisiert, deren Name gleich dem Klassennamen ist. Man nennt
diese Methoden Konstruktoren.
Die Attribute sind in der Regel private deklariert. Damit sind sie außerhalb der Klasse nicht sichtbar
und können nur über Zugriffsmethoden angesprochen werden, welche die Klasse selbst zur Verfügung
stellt. Dieses Prinzip der Kapselung ist eine wichtige Eigenschaft objektorientierter
Programmiersprachen.
In der objektorientierten Programmierung verwendet man zur Modellierung eine graphische
Notationsform namens UML (Unified Modelling Language).
C. Endreß
3 / 12
08/2006
8. Objekte und Klassen – Teil 1
UML-Klassen-Diagramm
GiroKonto
- kontoStand: double
Klassenname
Attribute
- kontoNr: int
+ GiroKonto()
Java-Klassendeklaration
public class GiroKonto {
//-------------- Attribute -------------private double kontoStand;
private int kontoNr;
Methoden
//-------------- Methoden --------------// Konstruktor
public GiroKonto(int kontoNr) {
this.kontoNr = kontoNr;
kontoStand = 0.0;
}
+ einzahlen()
+ auszahlen()
+ getKontoNr()
+ getKontoStand()
public void einzahlen(double betrag) {
kontoStand = kontoStand + betrag;
}
public double auszahlen(double betrag) {
kontoStand = kontoStand - betrag;
return betrag;
}
public int getKontoNr() {
return kontoNr;
}
public double getKontoStand() {
return kontoStand;
}
}
8.3 Objekte
Objekte der OOP sollen Objekte der realen Welt abbilden.
Reale Objekte können konkret (z.B. Fahrrad, Girokonto) oder abstrakt (z.B. Menge der ganzen Zahlen)
sein.
Wie reale Objekte haben Software-Objekte
eine Identität
-> jedes Objekt kann eindeutig identifiziert werden
einen Zustand
-> jedes Objekt hat charakteristische Merkmale (Attribute)
ein Verhalten
-> jedes Objekt kann in bestimmter Weise handeln (Methoden)
Objekte sind Instanzen von Klassen.
Objekte verhalten sich, wie die Klasse es vorgibt.
C. Endreß
4 / 12
08/2006
8. Objekte und Klassen – Teil 1
Objekte interagieren durch Nachrichtenaustausch.
Objekt A schickt eine Botschaft an Objekt B.
Objekt B reagiert darauf in geeigneter Weise.
Objekte sind die Bausteine für Programme.
Ein Objekt ist eine Software-Einheit, die aus Daten (Attribute) und Funktionalität (Methoden) besteht.
Die Klassendeklaration legt die Attribute und Methoden der zugehörigen Objekte fest. Sie legt aber nicht
die Attributwerte fest.
Um ein Objekt von einer Klasse anzulegen, muss eine Objektvariable vom Typ der Klasse deklariert
werden. Mit Hilfe des new-Operators wird dieser Variable ein neu erzeugtes Objekt zugewiesen.
Ein Objekt wird nie direkt angesprochen. Objektvariablen sind Referenzen auf Objekte.
Ein Objekt kann viele Referenzen besitzen.
Beispiel:
/* Programm: Kontoverwaltung
Demonstriert die Handhabung der Klasse GiroKonto
*/
import support.Console;
public class KontoVerwaltung {
public static void main(String[] args) {
GiroKonto erstesKonto;
GiroKonto zweitesKonto;
1
2
erstesKonto = new GiroKonto(4711);
zweitesKonto = new GiroKonto(4712);
3
erstesKonto.einzahlen(1000);
zweitesKonto.einzahlen(2000);
4
5
double betrag = erstesKonto.auszahlen(500);
zweitesKonto.einzahlen(betrag);
6
Console.println("Konto " + erstesKonto.getKontoNr() +
": Kontostand = " + erstesKonto.getKontoStand());
Console.println("Konto " + zweitesKonto.getKontoNr() +
": Kontostand = " + zweitesKonto.getKontoStand());
}
}
Konsolen-Ausgabe
Konto 4711: Kontostand = 500.0
Konto 4712: Kontostand = 2500.0
Erläuterungen
1
C. Endreß
Deklaration von zwei Objekt-Variablen (Referenzen) der Klasse GiroKonto.
5 / 12
08/2006
8. Objekte und Klassen – Teil 1
2
Erzeugung zweier Objekte durch den Aufruf des Konstruktors der Klasse GiroKonto in
Verbindung mit dem new-Operator. Der Konstruktor erhält als Parameter die Kontonummer des
zu erzeugenden Kontos. Die Variablen erstesKonto und zweitesKonto sind Referenzen auf
die erzeugten GiroKonto-Objekte.
Referenz
Objekt
erstesKonto
kontoNr
4711
kontoStand 0.0
kontoNr
zweitesKonto
4712
kontoStand 0.0
3
Aufruf der Methode einzahlen() für beide GiroKonto-Objekte. Methoden werden in Java
über den Namen der Objekt-Variable gefolgt vom Punkt-Operator und dem Methodennamen
aufgerufen.
4
Aufruf der Methode auszahlen() für das Objekt erstesKonto. Die Methode liefert den
Auszahlungsbetrag als Rückgabewert, der der Variable betrag zugewiesen wird.
5
Der Betrag wird mit der Methode einzahlen() auf das Objekt zweitesKonto eingezahlt.
6
Die beiden Ausgabeanweisungen geben mit Hilfe der jeweiligen get-Methoden die Attributwerte
der beiden GiroKonto-Objekte aus.
Objekt-Diagramm
Die UML stellt mit Objekt-Diagrammen eine graphische Möglichkeit zur Darstellung von Objekten zur
Verfügung.
Ein Objekt-Diagramm beschreibt Objekte, Attributwerte und Verbindungen zwischen Objekten zu einem
bestimmten Zeitpunkt des Programms.
UML-Objekt-Diagramm
Objektname
Attribute
einObjekt:Klasse
attribut1 = Wert1
attribut2 = Wert2
:Klasse
einObjekt
C. Endreß
Klassenname
Vollständige Darstellung
Im unteren Feld werden die im Kontext relevanten
Attribute angegeben. Die Attributtypen sind bereits bei
der Klasse deklariert. Sie müssen deshalb hier nicht mehr
angegeben werden.
Verkürzte Darstellungen
Wenn die Klasse aus dem Kontext ersichtlich ist, genügt
auch der unterstrichene Objektname.
6 / 12
08/2006
8. Objekte und Klassen – Teil 1
Beispiel: Objekt-Diagramm
Im vorangegangenen Programm KontoVerwaltung.java wurden zwei Objekte erzeugt, deren Zustand
am Programmende mit folgendem Objekt-Diagramm beschrieben werden kann.
erstesKonto:GiroKonto
zweitesKonto:GiroKonto
kontoNr = 4711
kontoStand = 500.0
kontoNr = 4712
kontoStand = 2500.0
Verbindungen zwischen Objekten werden mit einfachen Verbindungslinien zwischen den ObjektSymbolen dargestellt.
Konstruktoren
Ein Konstruktor ist eine spezielle Methode, die automatisch bei der Erzeugung eines Objekts aufgerufen
wird.
Konstruktoren erhalten den Namen der Klasse, zu der sie gehören.
Konstruktoren haben keinen Rückgabewert – auch nicht void.
Konstruktoren sind üblicherweise nach außen sichtbar: public
Ein Konstruktor kann nicht wie eine Funktion aufgerufen werden.
Konstruktoren dürfen eine beliebige Anzahl von Parametern haben und können überladen werden. Die
Parameter dienen zum Initialisieren der Attribute. Wenn ein Konstruktor keine Parameter besitzt, werden
die Attribute des erzeugten Objekts mit Standardwerten vorbelegt.
Ein Konstruktor ohne Parameter heißt default-Konstruktor. Der default-Konstruktor wird implizit vom
Compiler generiert, falls in einer Klasse kein Konstruktor definiert wurde. Wird jedoch ein beliebiger
Konstruktor definiert, erzeugt der Compiler keinen default-Konstruktor.
Beim Ausführen der new-Anweisung wählt der Compiler anhand der aktuellen Parameterliste den
passenden Konstruktor aus und ruft ihn mit den angegebnen Argumenten auf.
Beispiel:
public class GiroKonto {
. . .
// parameterloser Konstruktor
public GiroKonto(){
kontoStand = 0.0;
}
public GiroKonto(int kontoNr) {
this.kontoNr = kontoNr;
kontoStand = 0.0;
}
}
C. Endreß
7 / 12
08/2006
8. Objekte und Klassen – Teil 1
Löschen von Objekten
In Java muss sich der Programmierer nicht um das Löschen von Objekten kümmern. In unregelmäßigen
Abständen findet automatisch eine Speicherbereinigung (garbage collection) statt.
Es werden alle Objekte automatisch gelöscht, auf die keine Referenzen mehr zeigen.
Beispiel:
public class KontoVerwaltung {
public static void main(String[] args) {
GiroKonto erstesKonto = new GiroKonto(4711);
. . .
erstesKonto = null; // explizit zum Löschen freigegeben
}
Zuweisungsoperator
In Java ist jede Variable, die ein Exemplar einer Klasse bezeichnet, eine Referenz auf ein Objekt der Klasse.
Eine Zuweisung kopiert keine Objekte! Sie kopiert nur die Referenz darauf.
Um eine Kopie eines Objekts zu erhalten, muss man eigene Konstruktoren einführen oder mit
sogenannten Clones arbeiten.
Objekte als Parameter
Bei der Übergabe von Objekten als Parameter von Methoden werden Referenzen weitergegeben.
Diesen Parameterübergabemechanismus nennt man call by reference.
Im Gegensatz zu call by value werden nun alle Änderungen am Originalobjekt und nicht an einer Kopie
des Objekts vorgenommen.
Beispiel:
import support.Console;
public class KontoVerwaltung {
1
static void print(GiroKonto einKonto){
Console.println("Konto " + einKonto.getKontoNr() +
": Kontostand = " + einKonto.getKontoStand());
}
2
public static void main(String[] args) {
GiroKonto erstesKonto;
GiroKonto zweitesKonto;
3
4
erstesKonto = new GiroKonto(4711);
zweitesKonto = erstesKonto;
5
erstesKonto.einzahlen(1200);
zweitesKonto.einzahlen(500);
6
print(erstesKonto);
print(zweitesKonto);
C. Endreß
8 / 12
08/2006
8. Objekte und Klassen – Teil 1
}
}
Konsolen-Ausgabe
Konto 4711: Kontostand = 1700.0
Konto 4711: Kontostand = 1700.0
Erläuterungen
1
Deklaration der Methode print() mit dem Referenz-Parameter einKonto. Die Methode gibt
die Kontonummer und den Kontostand eines GiroKonto-Objekts auf der Konsole aus.
2
Deklarieren von zwei Objekt-Variablen der Klasse GiroKonto.
3
Erzeugung eines Girokonto-Objekts durch Konstruktoraufruf.
4
Der Referenz-Variablen zweitesKonto wird eine Referenz auf das Girokonto-Objekt
erstesKonto zugewiesen. Beide Referenz-Variablen verweisen damit auf dasselbe Objekt.
GiroKontoerstesKonto
Objekt
Kopie der
Referenz
zweitesKonto
5
Aufruf der Methode einzahlen() für die Objekte erstesKonto und zweitesKonto. Beide
Methodenaufrufe wirken auf dasselbe Objekt, wie die Ausgabe der Attribute in 6 zeigt.
6
Ausgabe der aktuellen Attributwerte des GiroKonto-Objekts.
8.4 Felder von Objekten
Frage: Wie können wir unser Beispiel zur Kontoverwaltung so modifizieren, dass wir eine größere Anzahl
von Konto-Objekten verwalten können?
Antwort: Wir speichern die Konto-Objekte in einem Array.
Felder können in Java nicht nur für einfache Datentypen, sondern auch für Referenzdatentypen
aufgebaut werden. Als Datentyp der Feldelemente wird die Klasse der zu speichernden Objekte
angegeben:
Klasse[] arrayName = new Klasse[anzahl];
Beispiel: Kontoverwaltung für 20 Girokonten
import support.Console;
public class KontoVerwaltung {
1
C. Endreß
public static void main(String[] args) {
GiroKonto[] konten = new GiroKonto[20];
9 / 12
08/2006
8. Objekte und Klassen – Teil 1
2
3
for(int i = 0; i < konten.length; i++){
konten[i] = new GiroKonto(4000 + i);
}
4
konten[0].einzahlen(1200);
konten[1].einzahlen(500);
konten[19].einzahlen(7500);
5
printKonto(konten[0]);
Console.println("\nKONTOLISTE:");
printAlleKonten(konten);
6
}
7
public static void printKonto(GiroKonto einKonto){
Console.println("Konto " + einKonto.getKontoNr() +
": Kontostand = " + einKonto.getKontoStand());
}
8
public static void printAlleKonten(GiroKonto[] alleKonten){
for(int i = 0; i < alleKonten.length; i++){
printKonto(alleKonten[i]);
}
}
}
Konsolen-Ausgabe
Konto 4000: Kontostand = 1200.0
KONTOLISTE:
Konto 4000:
Konto 4001:
Konto 4002:
.
.
.
Konto 4019:
Kontostand = 1200.0
Kontostand = 500.0
Kontostand = 0.0
Kontostand = 7500.0
Erläuterungen
1
Deklaration einer Feldvariablen mit der Bezeichnung konten, die auf die Feldelemente
konten[0] bis konten[19] verweist. Die Feldkomponenten sind Referenzen auf Objekte der
Klasse GiroKonto. Diese Referenzen zeigen anfangs „nirgendwohin“, d.h. sie referenzieren
kein spezifisches Objekt, sondern die sogenannte Null-Referenz, die in Java mit null bezeichnet
wird. Um in unserem Programm tatsächlich mit 20 GiroKonto-Objekten arbeiten zu können,
müssen wir diese Objekte erst erzeugen.
konten
2
C. Endreß
null
konten[0]
null
konten[1]
null
konten[19]
Zur Erzeugung der 20 GiroKonto-Objekte verwenden wir eine Zählschleife, die über alle
Feldelemente iteriert. Die Zählvariable i beginnt mit 0, dem Index des ersten Feldelements. Die
Schleife wird durchlaufen, solange die Zählvariable kleiner als die Anzahl der Feldelemente ist.
10 / 12
08/2006
8. Objekte und Klassen – Teil 1
3
Innerhalb der Schleife initialisieren wir jede Feldkomponente mit einem eigenen GiroKontoObjekt. Der Konstruktor erhält die Kontonummer (hier: 4000 + i) als Parameter und
initialisiert mit diesem Wert das Attribut kontoNr. Das Attribut kontoStand wird auf den Wert
0.0 gesetzt.
GiroKonto-Objekte
konten
4000
kontoNr
0.0
kontoStand
konten[0]
konten[1]
4001
kontoNr
0.0
kontoStand
konten[19]
4019
kontoNr
0.0
kontoStand
4
Die folgenden Anweisungen führen Einzahlungen auf den Objekten konten[0], konten[1]
und konten[19] aus.
5
Die Methode printKonto() wird aufgerufen, um die Attribute eines GiroKonto-Objekts
auszugeben. In diesem Fall werden die Attributwerte des ersten Array-Elements ausgegeben.
6
Wenn die Daten aller Objekte des GiroKonto-Feldes ausgegeben werden sollen, ist es sinnvoll
hierfür eine eigene Methode zu schreiben.
8.5 Klassenvariablen, Klassenmethoden
Die Deklaration von Klassenvariablen und Klassenmethoden erfolgt mit dem Schlüsselwort static.
Deshalb bezeichnet man Klassenvariablen auch als statische Variablen.
// Klassenvariable
public static double wechselKurs;
// Klassenmethode
public static void setWechselKurs(double tagesKurs){...}
Eine statische Variable oder statische Methode gehört zur Klasse selbst und nicht zu den Objekten der
Klasse. Es müssen keine Objekte für den Zugriff erzeugt werden (z.B. Math.sqrt() oder
Console.println()).
Innerhalb einer static-Methode darf nur auf Elemente zugegriffen werden, die ebenfalls static sind,
da beim Aufruf einer Klassenmethode nicht davon ausgegangen werden kann, dass von dieser Klasse
bereits Objekte erzeugt worden sind.
Klassenmethoden gelten implizit als final und können nicht
überschrieben werden.
Klassenvariablen existieren nur einmal im Speicher. Sie haben für alle
Instanzen der Klasse denselben Wert.
C. Endreß
11 / 12
USDollar
wechselKurs
...
setWechselKurs()
...
08/2006
8. Objekte und Klassen – Teil 1
Instanzvariablen existieren für jedes Objekt. Sie können für jede Instanz individuelle Werte annehmen.
Klassenvariablen und Klassenmethoden werden im UML-Klassendiagramm unterstrichen dargestellt.
8.6 Zusammenfassung
Die objektorientierte Programmierung basiert auf einer Reihe von Konzepten, von denen folgende behandelt
wurden.
Ein Objekt (auch Exemplar oder Instanz genannt) ist ein individuelles Exemplar von Dingen, Personen
oder Begriffen. Es besitzt eine Objekt-Identität. Objekte werden durch Konstruktoren erzeugt und im
Arbeitsspeicher auf dem sogenannten heap gespeichert. In Java sind Objektvariablen Referenzen auf
Objekte. Die Sprache verfügt über eine Speicherbereinigung (garbage collection), die Objekte
automatisch löscht, wenn keine Referenz mehr auf sie vorhanden ist.
Attribute beschreiben die Eigenschaften eines Objekts. Attributwerte sind die aktuellen Werte, die
die Attribute besitzen.
Operationen (auch Methoden genannt) beschreiben das Verhalten eines Objekts, d.h. die
Dienstleistungen die es seiner Umwelt oder sich selbst zur Verfügung stellt. Operationen kommunizieren
mit der Umwelt über Ein-/Ausgabeparameter.
Klassen sind der Bauplan für Objekte. Sie fassen Objekte mit gleichen Attributen und Operationen zu
einer Einheit zusammen. Das Geheimnisprinzip fordert, dass auf Attributwerte nur über Operationen
der Klasse zugegriffen werden kann.
Durch Botschaften kommunizieren Objekte und Klassen untereinander.
Die UML (Unified Modelling Language) ist eine graphische Notationsform zur Darstellung von Klassen
und Objekten.
In einem Pflichtenheft werden die Anforderungen an eine Software festgelegt und folgendermaßen
durchnummeriert:
/1/ Anforderung 1
/2/ Anforderung 2
…
/n/ Anforderung n
Anhand der Anforderungen ist ein Klassendiagramm in UML-Notation zu erstellen, wobei zunächst nur
sogenannte Fachkonzept-Klassen modelliert werden, die den fachlichen Teil der Anforderungen
beschreiben.
C. Endreß
12 / 12
08/2006
9. Objekte und Klassen – Teil 2
9. Objekte und Klassen – Teil 2
Lernziele
☺ Den Begriff Kapselung erklären können.
☺ Die Begriffe Oberklassen, Unterklasse, Vererbung und Polymorphie anhand von Beispielen
erklären können.
☺ Die Konzepte Vererbung, Überschreiben und Polymorphie in Java-Programmen einsetzen
können.
☺ UML-Notation zur Darstellung von Klassendiagrammen anwenden.
9.1 Grundprinzipien der OOP
Kapselung
Vererbung
Polymorphie
9.1.1
Kapselung
Der Zustand eines Objekts wird durch seine Attributwerte beschrieben. Es ist ein Prinzip der OOP, die
Details der Implementierung vor der Außenwelt zu verbergen.
Nach außen ist nur die „Schnittstelle“ des Objekts sichtbar. Sie wird gebildet durch Operationen, die für
andere Objekte nutzbar sind.
Die Komplexität der Benutzung eines Objekts wird durch die Schnittstelle vereinfacht. Um die
Operationen zu verwenden, muss man nicht deren interne Implementierung kennen. (Um ein Auto
fahren zu können, muss man nicht über die technischen Details des Motors Bescheid wissen.)
Kapselung bedeutet, dass Attribute und Implementierung eines Objekts nicht nach außen sichtbar sind.
Der Zugriff auf Eigenschaften (Attribute) und Funktionalität (Methoden) von Objekten erfolgt nur über die
Schnittstelle des Objekts.
Ziele der Kapselung
Verbergen komplizierter Einzelheiten
Klare Definition der Schnittstelle
Vermeiden von Seiteneffekten
Unterstützung von Aufgabenteilung und Modularisierung
C. Endreß
1 / 12
09/2005
9. Objekte und Klassen – Teil 2
Verwendung eines Objekts wird unabhängig von seiner Implementierung.
=> Implementierung austauschbar
Jede Klasse legt die Zugriffsrechte auf ihre Methoden und Attribute selbst fest. Dabei gilt:
Methoden, die nur Hilfsfunktionen für andere Methoden erfüllen, sollten private oder protected
sein.
Attribute sollten nicht public sein, um unkontrolliertes Ändern zu vermeiden und Datenkonsistenz
zu gewährleisten.
Für Zugriffe auf Attribute sollten set- und get-Methoden zur Verfügung gestellt werden.
Beispiel:
UML
GiroKonto
private
- kontoStand: double
- kontoNr: int
…
+ getKontoStand()
public
+ setKontoStand()
…
9.1.2
Java
public class GiroKonto {
// Attribute
private int kontoNr;
private double kontoStand;
. . .
// Zugriffsmethoden
public double getKontoStand() {
return kontoStand;
}
public void setKontoStand(double kontoStand){
this.kontoStand = kontoStand;
}
. . .
}
Vererbung
Beispiel:
Die Kontoverwaltung für Girokonten soll nun auf die Verwaltung von Sparkonten und Festgeldkonten
ausgeweitet werden. Das Pflichtenheft wird zu diesem Zweck um folgende Anforderungen erweitert:
/1/
Zu einem Girokonto sollen neben der Kontonummer und dem aktuellen Kontostand auch ein
Dispositionslimit, ein Sollzins und ein Habenzins gespeichert werden.
/2/
Sparkonten werden mit einem Habenzins verzinst. Sie müssen stets eine Mindesteinlage aufweisen.
/3/
Festgeldkonten haben eine bestimmte Laufzeit und einen Zinssatz.
/4/
Spar- und Festgeldkonten haben eine Kündigungsfrist.
/5/
Wird ein Konto angelegt, wird ihm eine Kontonummer zugeordnet.
/6/
Es müssen Ein- und Auszahlungen auf den Konten vorgenommen werden können.
/7/
Von einem Girokonto sollen Überweisungen an andere Konten durchgeführt werden können.
/8/
Alle Daten müssen einzeln gelesen werden können.
C. Endreß
2 / 12
09/2005
9. Objekte und Klassen – Teil 2
Modellierung der Konten
Die drei Kontoarten zeigen ähnliche Eigenschaften und ähnliches Verhalten.
GiroKonto
kontoNr
kontoStand
habenZins
sollZins
dispo
GiroKonto()
einzahlen()
auszahlen()
ueberweisen()
getKontoNr()
getKontoStand()
getHabenZins()
getSollZins()
getDispo()
SparKonto
FestGeldKonto
kontoNr
kontoStand
habenZins
mindestEinlage
kontoNr
kontoStand
habenZins
laufZeit
SparKonto()
einzahlen()
auszahlen()
kuendigen()
getKontoNr()
getKontoStand()
getHabenZins()
getMindestEinlage()
FestGeldKonto()
einzahlen()
auszahlen()
kuendigen()
getKontoNr()
getKontoStand()
getHabenZins()
getLaufzeit()
Gemeinsame Eigenschaften und Methoden werden in eine Oberklasse Konto ausgelagert. Es ergibt sich ein
erweitertes Klassenmodell:
Generalisierung
Konto
Oberklasse
Spezialisierung
kontoNr
kontoStand
habenZins
Konto()
einzahlen()
auszahlen()
getKontoNr()
getKontoStand()
getHabenZins()
Vererbung
GiroKonto
SparKonto
FestGeldKonto
sollZins
dispo
mindestEinlage
laufZeit
GiroKonto()
auszahlen()
ueberweisen()
getSollZins()
SparKonto()
auszahlen()
kuendigen()
getMindestEinlage()
FestGeldKonto()
kuendigen()
getLaufzeit()
Unterklasse
Vererbung bezeichnet die Weitergabe von Eigenschaften (Attributen) und Operationen einer Klasse an eine
andere Klasse.
C. Endreß
3 / 12
09/2005
9. Objekte und Klassen – Teil 2
Vererbung ist ein wichtiger Mechanismus der objektorientierten Programmierung. Sie ermöglicht das
Ableiten neuer Klassen aus bestehenden Klassen, indem Attribute und Methoden an neue Klassen
„vererbt“ werden.
Die abgeleitete Klasse (Unterklasse, Subklasse) erbt alle Attribute und Methoden der zugeordneten
Oberklasse (Basisklasse, Superklasse).
Die Klasse Konto ist also die Oberklasse der Klassen GiroKonto, SparKonto und FestGeldKonto.
Diese Klassen sind Unterklassen der Klasse Konto.
Erhält eine Unterklasse zusätzliche Attribute und Methoden, stellt sie eine Spezialisierung der
Oberklasse dar.
Ein Objekt einer Unterklasse kann überall dort verwendet werden, wo ein Objekt der Oberklasse erlaubt
ist.
Sinnvoll ist das Konzept der Vererbung immer dann, wenn die Oberklasse eine Generalisierung der
Unterklasse darstellt bzw. die Unterklasse eine Spezialisierung der Oberklasse darstellt.
Vorteile der Vererbung
Aufbauend auf bestehenden Klassen können mit wenig Aufwand neue Klassen erstellt werden.
Die Änderbarkeit wird unterstützt. So wirkt sich beispielsweise die Änderung eines Attributs oder einer
Methode automatisch auf alle Unterklassen der Klassenhierarchie aus.
Das Konzept der Vererbung widerspricht allerdings dem Geheimnisprinzip (Kapselung), das besagt, dass
keine Klasse die Attribute einer anderen Klasse sieht.
Vererbung in Java
Konstuktoren werden nicht vererbt. Konstruktoren der abgeleiteten Klassen müssen neu definiert
werden!
Konstruktoren der Unterklassen rufen implizit den default-Konstruktor der Oberklasse auf. Wurde kein
default-Konstruktor in der Oberklasse definiert, gibt es einen Compiler-Fehler. Das ist der Fall, wenn in
der Oberklasse lediglich parametrisierte Konstruktoren angegeben wurden und daher ein parameterloser
default-Konstruktor nicht automatisch erzeugt wurde.
Ein parametrisierter Konstruktor der Oberklasse wird mit dem Schlüsselwort super aus einem
Unterklassen-Konstruktor heraus aufgerufen. super muss die erste Anweisung im
Unterklassenkonstruktor sein und wird verwendet wie eine normale Methode.
Es ist auch erlaubt, mit Hilfe der this-Methode einen anderen Konstruktor der eigenen Klasse
aufzurufen.
Modifizierer für den Zugriff auf Attribute und Methoden
public
Zugriff für alle Klassen, keine Einschränkungen
protected
Zugriff nur für die Klasse selbst und für Unterklassen
private
Zugriff nur für die Klasse, in der die Methode oder das Attribut deklariert ist
-
Zugriff für die Klasse selbst und alle Klassen des Packages
In Java kann durch das Schlüsselwort final verhindert werden, dass von einer Klasse Unterklassen
abgeleitet werden. Dieses ist sinnvoll, wenn verhindert werden soll, dass vererbte Operationen
C. Endreß
4 / 12
09/2005
9. Objekte und Klassen – Teil 2
redefiniert werden.
Beispielsweise erlauben die Klassen Math und String keine Unterklassen.
Überschreiben von Methoden
Methoden der Unterklasse überschreiben bzw. redefinieren gleichnamige, geerbte Methoden der
Oberklasse.
Im Beispiel Kontoverwaltung wird die Methode auszahlen() der Basisklasse Konto in den Unterklassen
überschrieben. In der Klasse SparKonto muss nach der Auszahlung noch eine mindestEinlage auf dem
Konto verbleiben. Ein GiroKonto kann dagegen bis zu einem Limit (dispo) überzogen werden.
Beispiel:
Das Klassenmodell der Kontoverwaltung kann in Java folgendermaßen implementiert werden:
/* Programm: Kontoverwaltung
Fachkonzept-Klasse Konto,
Oberklasse der Fachkonzept-Klassen GiroKonto
SparKonto
FestGeldKonto
*/
1
public class Konto {
//---------- Attribute ------------protected int kontoNr;
protected double kontoStand;
protected double habenZins = 1.5; // gilt für alle
Konten
//---------- Methoden -------------// Konstruktoren
public Konto(int kontoNr) {
this.kontoNr = kontoNr;
kontoStand = 0;
}
public Konto(int kontoNr, double kontoStand) {
this.kontoNr = kontoNr;
this.kontoStand = kontoStand;
}
public void einzahlen(double betrag) {
if(betrag > 0.0){
kontoStand = kontoStand + betrag;
}
}
public boolean auszahlen(double betrag) {
if(kontoStand >= betrag){
kontoStand = kontoStand - betrag;
return true;
}
else
return false;
}
public int getKontoNr() {
return kontoNr;
}
public double getKontoStand() {
return kontoStand;
}
public double getHabenZins() {
return habenZins;
}
2
3
4
5
}
C. Endreß
5 / 12
09/2005
9. Objekte und Klassen – Teil 2
Erläuterungen
1
Attribute werden als protected deklariert, damit sie auch von den Unterklassen aus sichtbar
sind.
2
Deklaration von zwei überladenen Konstruktoren (unterscheiden sich in ihrer Signatur)
3
Die Methode einzahlen() wird vererbt und ist gültig für alle Konten.
4
Die Methode auszahlen() ist hier als Standardlösung implementiert. Sie wird in den
Unterklassen gemäß den jeweiligen Anforderungen überschrieben.
5
Die Zugriffsmethoden sind gültig für alle Konten.
/* Programm: Kontoverwaltung
Fachkonzept-Klasse GiroKonto, abgeleitet von der Klasse Konto
*/
public class GiroKonto extends Konto {
//------------- Attribute ----------------private double dispo;
private double sollZins = 13.0; //gilt für alle Girokonten
1
2
//------------- Methoden -----------------// Konstruktor
public GiroKonto(int kontoNr, double dispo) {
super(kontoNr);
this.dispo = dispo;
}
3
4
public boolean auszahlen(double betrag) {
if (kontoStand + dispo >= betrag) {
kontoStand = kontoStand - betrag;
return true;
}
else
return false;
}
5
public boolean ueberweisen(double betrag, Konto empfaenger) {
if (auszahlen(betrag) == true) {
empfaenger.einzahlen(betrag);
return true;
}
else
return false;
}
6
public double getSollZins() {
return sollZins;
}
public double getDispo() {
return dispo;
}
}
Erläuterungen
1
Die Klasse GiroKonto ist eine Unterklasse der Klasse Konto. Diese Vererbungsbeziehung wird
durch das Schlüsselwort extends festgelegt.
2
Deklaration der Attribute, die für die Klasse GiroKonto neu hinzukommen (Spezialisierung). Das
Attribut sollZins wird mit 13.0 initialisiert.
C. Endreß
6 / 12
09/2005
9. Objekte und Klassen – Teil 2
3
Deklaration des Konstruktors der Klasse GiroKonto. In der Anweisung super(kontoNr) wird der
Konstruktor der Oberklasse Konto aufgerufen und der Parameter kontoNr übergeben. Die superAnweisung muss die erste Anweisung eines Konstruktors sein.
4
Die Methode auszahlen() in GiroKonto überschreibt die gleichnamige Methode der Oberklasse
Konto. Die Implementierung ist den Erfordernissen eines Girokontos angepasst.
5
Die Methode ueberweisen() ermöglicht die Überweisung eines Geldbetrags (betrag) auf ein
Konto (empfaenger). Hier wird von der Möglichkeit Gebrauch gemacht, Objekte als Parameter zu
übergeben. Das Konto-Objekt empfaenger kann dabei sowohl ein Objekt der Basisklasse Konto
als auch ein Objekt der drei Unterklassen GiroKonto, SparKonto oder FestGeldKonto sein.
6
Zugriffsmethoden für die Attribute, die neu zur Klasse GiroKonto hinzugekommen sind.
/* Programm : Kontoverwaltung
Testrahmen für die Klasse GiroKonto
*/
import support.Console;
public class KontoVerwaltung {
public static void main(String[] args) {
// Deklaration der Objekt-Variablen
GiroKonto erstesKonto;
GiroKonto zweitesKonto;
1
2
// zwei Girokonten anlegen
erstesKonto = new GiroKonto(4711, 500);
zweitesKonto = new GiroKonto(4712, 500);
3
// Einzahlung und Überweisung
erstesKonto.einzahlen(1200);
4
if( erstesKonto.ueberweisen(500, zweitesKonto) )
Console.println("Überweisung ausgeführt");
else
Console.println("Überweisung nicht ausgeführt");
// Ausgabe der aktuellen Kontostände
Console.println("Konto " + erstesKonto.getKontoNr() +
": Kontostand = " + erstesKonto.getKontoStand());
Console.println("Konto " + zweitesKonto.getKontoNr() +
": Kontostand = " + zweitesKonto.getKontoStand());
}
}
Konsolenausgabe
Überweisung ausgeführt
Konto 4711: Kontostand = 700.0
Konto 4712: Kontostand = 500.0
Erläuterungen
1
Deklaration von zwei Referenz-Variablen der Klasse GiroKonto.
2
Erzeugung von zwei Objekten der Klasse GiroKonto durch Aufruf der entsprechenden
Konstruktoren.
C. Endreß
7 / 12
09/2005
9. Objekte und Klassen – Teil 2
3
Aufruf der Methode einzahlen() für das Objekt erstesKonto.
4
Aufruf der Methode ueberweisen() für das Objekt erstesKonto. Die Methode erhält als
Parameter den Überweisungsbetrag und das GiroKonto-Objekt zweitesKonto, auf das der
Betrag überwiesen werden soll. Der boolsche Rückgabewert der Methode wird in einer ifAnweisung ausgewertet.
9.1.3
Polymorphie
Der Begriff Polymorphie kommt aus dem Griechischen und bedeutet Vielgestaltigkeit. Die gleiche
Botschaft kann an Objekte unterschiedlicher Klassen gesendet werden. Die Empfängerobjekte reagieren
darauf in ihrer eigenen, klassenspezifischen Weise. So löst z.B. die Botschaft auszahlen() bei Objekten
der Klassen GiroKonto oder SparKonto einen Auszahlvorgang aus, der jedoch in den beiden Klassen
unterschiedlich gestaltet ist.
Polymorphie ist die Verfügbarkeit mehrerer Definitionen einer Methode, aus denen erst zum Zeitpunkt der
Ausführung eine geeignete Definition ausgewählt wird.
/* Programm: Kontoverwaltung
Testrahmen für polymorphe Methode auszahlen()
*/
import support.Console;
public class KontoVerwaltungPolymorph {
static void print(Konto einKonto){
Console.println("Konto " + einKonto.getKontoNr() +
": Kontostand = " + einKonto.getKontoStand());
}
public static void main(String[] args) {
// Deklaration der Objekt-Variablen
Konto erstesKonto;
Konto zweitesKonto;
1
// Konten anlegen
erstesKonto = new GiroKonto(4711, 500);
zweitesKonto = new SparKonto(1234);
2
3
// Einzahlung
erstesKonto.einzahlen(1200);
zweitesKonto.einzahlen(2000);
4
// Aufruf einer polymorphen Methode
erstesKonto.auszahlen(1500);
zweitesKonto.auszahlen(2000);
5
6
// Ausgabe der aktuellen Kontostände
print(erstesKonto);
print(zweitesKonto);
}
}
Konsolenausgabe
Konto 4711: Kontostand = -300.0
Konto 1234: Kontostand = 2000.0
C. Endreß
8 / 12
09/2005
9. Objekte und Klassen – Teil 2
Erläuterungen
1
Deklaration von Objekt-Variablen der Klasse Konto.
2
Erzeugung eines Objekts der Klasse GiroKonto durch Aufruf des entsprechenden Konstruktors.
Eine Objekt-Variable der Klasse Konto kann auch auf ein Objekt einer Unterklasse von Konto
zeigen.
3
Erzeugung eines Objekts der Klasse SparKonto durch Aufruf des entsprechenden Konstruktors.
Eine Objekt-Variable der Klasse Konto kann auch auf ein Objekt einer Unterklasse von Konto
zeigen.
4
Aufruf der von der Oberklasse Konto geerbten Methode einzahlen().
5
Die Methode auszahlen() ist polymorph. Sie ist in der Oberklasse Konto in einer Variante
implementiert, die durch spezialisierte Definitionen in den Unterklassen GiroKonto und
SparKonto redefiniert wird. Die Auszahlungsanweisung des Girokontos kann aufgrund des
vorhandenen Dispolimits ausgeführt werden. Der Auszahlvorgang beim Sparkonto fordert eine
verbleibende Mindesteinlage, die jedoch in Zeile 6 nicht zurückbleibt, so dass die Auszahlung
nicht erfolgt.
Oberklasse Konto
public boolean auszahlen(double betrag) {
if(kontoStand >= betrag){
kontoStand = kontoStand - betrag;
return true;
}
else
return false;
}
Unterklasse GiroKonto
public boolean auszahlen(double betrag) {
if (kontoStand + dispo >= betrag) {
kontoStand = kontoStand - betrag;
return true;
}
else
return false;
}
Unterklasse SparKonto
public boolean auszahlen(double betrag) {
if(kontoStand - mindestEinlage >= betrag){
kontoStand = kontoStand - betrag;
return true;
}
else
return false;
}
Eine Referenz-Variable kann zur Laufzeit eines Programms auf verschiedene Objekte verweisen. Somit
kann auch erst zur Laufzeit des Programms die Operation festgelegt werden, die zum jeweiligen Objekt
passt.
Wird bereits beim Compilieren festgelegt welche Methode aufgerufen wird, spricht man von einer
statischen Bindung (early binding). Wird dagegen erst während der Laufzeit die aufzurufende
Methode festgelegt, nennt man dieses dynamische Bindung (late binding).
C. Endreß
9 / 12
09/2005
9. Objekte und Klassen – Teil 2
9.1.4
Abstrakte Klassen und Methoden
Häufig ergibt sich bei der Modellierung von Klassen folgende Situation:
Alle Unterklassen einer Basisklasse haben eine gleichnamige Methode mit unterschiedlicher
Implementierung.
Beispiel: auszahlen(double betrag)
GiroKonto:
beliebige Auszahlung bis Limit
SparKonto:
Mindesteinlage nötig
FestGeldKonto:
Auszahlung erst nach Ende der Laufzeit
Lösung: abstrakte Methode in der Oberklasse. Eine abstrakte Methode ist eine Methode, die nicht
realisiert ist.
Die abstrakte Methode der Oberklasse gibt nur die Signatur der Methode an, nicht aber ihre
Realisierung. Die Methodendeklaration verwendet das Schlüsselwort abstract.
Abstrakte Methoden müssen in den Unterklassen redefiniert werden.
Sobald eine Klasse eine abstrakte Methode enthält, ist die ganze Klasse abstrakt.
Von einer abstrakten Klasse können keine Objekte erzeugt werden.
Es können nur Objekte zu nicht abstrakten Unterklassen erzeugt werden.
Abstrakte Methoden müssen in den Unterklassen implementiert werden, es sei denn, die Unterklassen
sind ihrerseits wieder abstrakte Klassen.
Eine abstrakte Klasse ist eine Klasse, von der keine Exemplare erzeugt werden können. Meist enthalten
abstrakte Klassen abstrakte Methoden, die auf dem Abstraktionsniveau der Klassen nicht formuliert
werden können und nur eine gemeinsame Schnittstelle für alle Unterklassen definieren.
Abstrakte Klassen, Redefinition und Polymorphie erlauben die Konstruktion flexibler Strukturen von
Klassen, in denen (abstrakte) Oberklassen Protokolle festlegen, die von allen Unterklassen eingehalten
werden müssen.
Dabei ist jedes Objekt Experte für sein eigenes Handeln, da es seine Methoden aus der Klasse ableitet,
von der es erzeugt wurde.
Beispiel:
Der Oberklasse Konto werden Informationen (Attribute und Methoden) hinzugefügt. So wird die
Beschreibung verfeinert (GiroKonto bzw. SparKonto)
Die Klasse Konto sollte als abstrakte Klasse modelliert werden, da es in der
Realität keine „allgemeinen“ Konten gibt, sondern nur Spezialisierungen wie
Giro- und Sparkonten.
GiroKonto und SparKonto sind konkrete Klassen, da es Objekte ihrer Art
gibt.
In der UML werden abstrakte Klassen und Methoden durch kursive Schrift
gekennzeichnet. Alternativ können sie im Namensfeld der Klasse mit
{abstract} gekennzeichnet sein.
C. Endreß
10 / 12
Konto
kontoNr
kontoStand
habenZins
Konto()
einzahlen()
auszahlen()
getKontoNr()
getKontoStand()
getHabenZins()
09/2005
9. Objekte und Klassen – Teil 2
Beispiel:
Die abstrakte Klasse Konto wird in Java folgendermaßen implementiert:
/* Programm: Kontoverwaltung
Fachkonzept-Klasse Konto,
abstrakte Oberklasse der Klassen GiroKonto
SparKonto
FestGeldKonto
*/
public abstract class Konto {
1
//------------ Attribute ----------------protected int kontoNr;
protected double kontoStand;
protected double habenZins = 1.5;
//------------ Methoden -----------------// Konstruktor
public Konto(int kontoNr) {
this.kontoNr = kontoNr;
kontoStand = 0;
}
public void einzahlen(double betrag) {
if(betrag > 0.0)
kontoStand = kontoStand + betrag;
}
2
public abstract boolean auszahlen(double betrag);
public int getKontoNr() {
return kontoNr;
}
public double getKontoStand() {
return kontoStand;
}
public double getHabenZins() {
return habenZins;
}
}
Erläuterungen
1
Durch das Schlüsselwort abstract wird die Klasse Konto als abstrakte Klasse deklariert. Damit
ist diese Klasse nicht mehr instanzierbar.
2
Die Methode auszahlen() wird abstract deklariert. Ein Anweisungsblock ist damit nicht
mehr möglich. Die Methode muss in den Unterklassen redefiniert werden.
C. Endreß
11 / 12
09/2005
9. Objekte und Klassen – Teil 2
9.2 Zusammenfassung
Klassen fassen Objekte mit gleichen Attributen und Methoden zu einer Einheit zusammen. Nach dem
Geheimnisprinzip kapselt die Klasse Attribute und eventuell Methoden, die nicht nach außen sichtbar
sein sollen. Auf diese als private bzw. protected deklarierten Attribute kann nur innerhalb ihrer
Klasse zugegriffen werden. Für den Zugriff von außen sind public deklarierte set- und get-Methoden
erforderlich.
Durch die Vererbung werden Attribute und Operationen an alle Unterklassen einer Oberklasse
weitergegeben. Es entsteht eine Klassenhierarchie, die je nach Blickrichtung eine Generalisierungsbzw. Spezialisierungshierarchie darstellt. Geerbte Attribute werden durch gleichnamige Attribute der
Unterklasse verborgen, geerbte Methoden werden durch gleichnamige Methoden überschrieben bzw.
redefiniert.
In Java kann durch das Schlüsselwort super auf verborgene Attribute bzw. überschriebene Methoden
zugegriffen werden.
Von abstrakten Klassen können keine Objekte erzeugt werden. Abstrakte Methoden müssen in den
Unterklassen definiert werden.
Polymorphie erlaubt es, gleiche Botschaften an Objekte unterschiedlicher Klassen zu senden. Schreibt
man eine Methode, die einen Parameter vom Typ der Oberklasse enthält, kann während der Laufzeit
dort auch ein Objekt einer Unterklasse eingesetzt werden. Erst beim Aufruf der Methode steht fest, auf
welches Objekt welcher Klasse die Methode angewendet werden soll.
C. Endreß
12 / 12
09/2005
10. Datenstrukturen
10. Datenstrukturen
Lernziele
☺ Die wichtigsten Datenstrukturen der Collections-API kennen.
☺ Die Unterschiede zwischen den Strukturen Feldern, Listen, Maps und Sets kennen und geeignete
Strukturen anwenden können.
☺ Sortier- und Suchfunktionen der Collections-API kennen und anwenden.
10.1 Wenn Felder nicht genug sind
In den vergangenen Kapiteln haben wir gleichartige Daten in Feldern, die wir mit Hilfe des new-Operators in
einer bestimmten Größe erzeugt haben, gepackt und im Arbeitsspeicher abgelegt. Dieses Verfahren ist gut
geeignet, wenn man die Anzahl der zu speichernden Elemente beim Anlegen des Feldes kennt. Ist diese
Anzahl jedoch nicht bekannt oder muss die Kapazität des Feldes im Laufe der Zeit vergrößert oder
verkleinert werden, erweisen sich Felder als unflexibel und problematisch.
Problem:
Ein Benutzer gibt eine Liste von Namen über die Tastatur ein. Anfangs weiß
er noch nicht, um wie viele Namen es sich handelt. Wie groß soll das Feld für
die Namensliste werden?
Lösung:
Anstelle eines klassischen Feldes, das nach dem Initialisieren in seiner Größe
festgelegt ist, benötigen wir eine flexible Datenstruktur, die trotzdem die
Funktionalität von Feldern bietet. Die Firma Sun hat zu diesem Zweck eine
Vielzahl von sogenannten Collection-Klassen vordefiniert. Diese
Collections sind eine Sammlung von Klassen, die eine beliebige Anzahl von
Objekten speichern können. Jede dieser Klassen besitzt eine Methode
toArray(), mit der man die gesammelten Objekte jederzeit in ein Feld
umwandeln kann.
Für die Namensliste verwenden wir die Klasse ArrayList. Diese Klasse besitzt
eine Methode add, mit sich Objekte an Ende der Liste anhängen lassen,
eine Methode get, mit der sich beliebige Objekte der Liste auslesen,
eine Methode size, die die Anzahl der Objekte in der Liste angibt.
ArrayList<String> namensListe = new ArrayList<String>();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String name = br.readLine();
namensListe.add(name);
for(int i = 0; i < namensListe.size(); i++){
System.out.println(namensListe.get(i));
}
C. Endreß
1 / 11
10/2008
10. Datenstrukturen
10.2 Collections
Collections haben gegenüber einfachen Feldern den Vorteil, dass die Anzahl der Elemente, die man in
ihnen speichern möchte, nicht von vornherein feststehen muss. Sie sind in ihrer Größe flexibel.
Collections verfügen über zusätzliche Methoden, die den Umgang mit ihnen sehr komfortabel gestalten.
Spezielle Collections garantieren, dass ein bestimmtes Element nicht zweimal in der Menge
aufgenommen wird.
Es gibt sogenannte Iteratoren, mit denen man sich auf sehr bequeme Weise durch die Werte in einer
Collections arbeiten kann.
10.2.1
Drei wichtige Strukturen: List, Set, Map
In der Collections-API finden wir die drei grundlegenden Interfaces List, Set und Map. Eine ArrayList ist
z.B. eine List.
LIST
(wenn es auf die Reihenfolge ankommt)
Collections, die die Indexposition kennen.
Lists wissen, wo etwas in der
Liste steht. Dasselbe Objekt
kann durch mehrere Elemente
referenziert werden.
0
1
2
3
List
SET
(wenn es auf die Einzigartigkeit ankommt)
Collections, die nichts mehrfach enthalten dürfen.
Sets wissen, ob etwas schon in der Sammlung
vorkommt. Es kann nie mehr als ein Element
dasselbe Objekt referenzieren, und es können
auch nicht zwei Objekte referenziert werden, die
als gleich gelten.
Set
C. Endreß
2 / 11
10/2008
10. Datenstrukturen
MAP
(wenn etwas anhand eines Schlüssels gefunden werden soll)
Collections, die Schlüssel/Wert-Paare verwenden.
Maps wissen, welcher Wert mit
einem bestimmten Schlüssel
verknüpft ist. Es können zwei
verschiedene Schlüssel
vorkommen, die denselben Wert
referenzieren. Die Schlüssel
selbst können nicht mehrmals
vorkommen. Normalerweise
werden als Schlüssel StringNamen benutzt, so dass Sie
beispielsweise Name/WertEigenschaftslisten erstellen
können. Ein Schlüssel darf
jedoch jedes beliebige Objekt
sein.
10.2.2
“Ball1“
“Ball2“
“Fisch“
“Auto“
Map
Auszug aus der Collcetions-API
«interface»
Collection
«interface»
Set
«interface»
List
«interface»
SortedSet
TreeSet
LinkedHashSet
HashSet
ArrayList
LinkedList
Vector
«interface»
Map
«interface»
SortedMap
TreeMap
C. Endreß
HashMap
LinkedHashMap
3 / 11
Hashtable
10/2008
10. Datenstrukturen
10.3 ArrayList
Eine ArrayList ist eine Liste von Elementen, die man im Prinzip wie ein Feld handhaben kann. Nur die Syntax
sieht etwas anders aus.
Einige wichtige Methoden der Klasse ArrayList:
ArrayList
add(Object elem)
Fügt der Liste das angegebene Objekt elem hinzu.
get(int index)
Liefert das Objekt zurück, das sich aktuell an der Position index befindet.
size()
Liefert die Anzahl der Elemente zurück, die sich aktuell in der Liste befinden.
remove(int index)
Entfernt das Objekt an der Position index aus der Liste.
remove(Object elem)
Entfernt das Objekt elem, wenn es sich in der Liste befindet.
contains(Object elem)
Liefert true zurück, wenn das Objekt in der Liste vorhanden ist, sonst false.
isEmpty()
Liefert true zurück, wenn die Liste keine Elemente enthält, sonst false.
indexOf(Object elem)
Liefert entweder den Index für das übergebene Objekt oder -1 zurück.
Bei einer ArrayList arbeiten Sie mit einem Objekt der Klasse ArrayList. Sie rufen die Methoden des
ArrayList-Objekts wie bei anderen Objekten auch mit dem Punktoperator auf.
ArrayLists sind genau wie Felder nullbasiert, d.h. das erste Element befindet sich an der Position 0
und das letzte Element an der Position size() – 1.
C. Endreß
4 / 11
10/2008
10. Datenstrukturen
Ein paar Dinge, die Sie mit einer ArrayList machen können:
Eine erstellen
ArrayList<Ball> meineListe = new ArrayList<Ball>();
Etwas reinstecken
Ball b = new Ball();
meineListe.add(b);
b
Noch etwas reinstecken
Ball x = new Ball();
meineListe.add(x);
b
x
Ermitteln, wie viele Dinge in der Liste drin sind
int anzahlDerBälle = meineListe.size();
Herausfinden, ob sie etwas enthält
boolean istDrin = meineListe.contains(x);
Herausfinden, wo sich etwas befindet
int index = meineListe.indexOf(x);
Ermitteln, ob sie leer ist
boolean istLeer = meineListe.isEmpty();
Etwas daraus entfernen
meineListe.remove(b);
C. Endreß
x
5 / 11
10/2008
10. Datenstrukturen
Ein Vergleich zwischen ArrayList und einem gewöhnlichen Feld (Array)
1
Ein einfaches Feld muss seine Größe bereits kennen, wenn es erzeugt wird.
Bei einer ArrayList erzeugen Sie einfach ein Objekt der Klasse ArrayList. Die ArrayList muss nie
wissen, wie groß sie sein soll, weil sie automatisch wächst und schrumpft, wenn hinzugefügt bzw.
entfernt werden.
String[] einFeld = new String[5];
ArrayList<String> eineListe = new ArrayList<String>();
2
Wenn Sie ein Objekt in ein gewöhnliches Feld stecken möchten, müssen Sie es einem
bestimmten Ort zuweisen.
Der Positionsindex muss zwischen 0 und der Feldlänge minus 1 liegen.
einFeld[4] = ″Hallo″;
Wenn der Index außerhalb der Arraygrenzen liegt, gibt es zur Laufzeit eine Exception der Art
ArrayIndexOutOfBoundsException.
Bei einer ArrayList brauchen Sie sich um Feldgrenzen keine Gedanken zu machen. Verwenden Sie
einfach die add()-Methode, um ein neues Element der Liste hinzuzufügen.
eineListe.add(″Hallo″);
3
Felder verwenden in Java die Array-Syntax, die in Java an keiner anderen Stelle
verwendet wird.
ArrayLists sind ganz gewöhnliche Java-Objekte. Es gibt also keine besondere Syntax.
einFeld[2]
4
ArrayLists sind seit Java 5.0 parametrisiert.
Vor Java 5.0 gab es keine Möglichkeit, den Typ der Dinge zu deklarieren, die in einer ArrayList
gespeichert werden. ArrayLists waren als heterogene Sammlungen von Objekten. Aber seit Java 5.0
können wir den Typ der Objekte, die in der ArrayList abgelegt werden, festlegen.
ArrayList<String>
C. Endreß
6 / 11
10/2008
10. Datenstrukturen
10.4 Höhere Typsicherheit durch Generics
Bevor es Generics gab, d.h. vor Java 5.0, scherte sich der Compiler nicht darum, was in eine Collection
gesteckt wurde. Alle Collection-Implementierungen waren so deklariert, dass sie den Typ Object enthielten.
Man konnte einfach alles in einer ArrayList ablegen. Der Compiler hinderte Sie nicht daran, einen Hund in
eine Liste von Enten zu stecken.
Ohne Generics
Objekte werden als Fußball-,
Hund-, Fisch- oder AutoReferenzen hineingesteckt …
Bevor es Generics gab, hatte man
keine Möglichkeit, den Typ einer
ArrayList zu deklarieren, daher
wurden ihrer Methode add()
Argumente vom Typ Object
übergeben.
ArrayList
Object
Object
Object
Object
… und kommen als Referenz vom Typ Object wieder heraus.
Mit Generics
Objekte werden ausschließlich
als Referenzen auf FischObjekte hineingesteckt …
Mit Generics können Sie nur Fisch-Objekte
in die ArrayList<Fisch> stecken. Deshalb
kommen die Objekte auch als FischReferenzen wieder heraus. Sie brauchen
sich keine Sorgen zu machen, dass
irgendjemand einen Volkswagen in die
ArrayList hineinsteckt oder dass das, was
Sie herausholen, vielleicht nicht auf eine
Fisch-Referenz gecastet werden kann.
ArrayList <Fisch>
… und kommen als Referenz vom Typ Fisch wieder heraus.
C. Endreß
7 / 11
10/2008
10. Datenstrukturen
Mit Generics können Sie typsichere Collections erzeugen, wodurch
einige Probleme schon zur Kompilierzeit abgefangen werden und nicht
erst zur Laufzeit auftreten.
Ohne Generics durften Sie mit Genehmigung des Compilers
einen Kürbis in eine ArrayList stecken, die eigentlich nur
Objekte vom Typ Katze enthalten sollte.
10.4.1
Verwendung von generischen Klassen
Da ArrayList unsere meistgenutzte generische
Klasse ist, werfen wir einen Blick in die ArrayListDokumentation der Java API. Für andere
Collection-Klassen gilt dasselbe. Die beiden
wichtigen Stellen, die man sich bei einer
generischen Klasse ansehen muss, sind:
1. die Deklaration der Klasse und
2. die Deklaration der Methoden, mit denen
Sie Elemente hinzufügen können.
Die ArrayList-Dokumentation der Java-API verstehen
(Oder: Was sich wirklich hinter <E> verbirgt)
public class ArrayList<E> extends AbstractList<E> implements
List<E> ...{
public boolean add(E e)
// mehr Code
}
C. Endreß
8 / 11
10/2008
10. Datenstrukturen
Verwendung von Typparametern bei ArrayList
Dieser Code
ArrayList<Auto> meinFuhrpark = new ArrayList<Auto>()
bedeutet, dass ArrayList
public class ArrayList<E> extends AbstractList<E> ... {
public boolean add(E e)
// mehr Code
}
vom Compiler behandelt wird als
public class ArrayList<Auto> extends AbstractList<Auto> ... {
public boolean add(Auto e)
// mehr Code
}
Mit anderen Worten, das „E“ wird durch den tatsächlichen Typ (der auch als Typparameter bezeichnet
wird) ersetzt, den Sie bei der Erzeugung der ArrayList benutzen. Deshalb lässt Sie die ArrayList-Methode
add() nichts anderes hinzufügen als Objekte eines Referenztyps, der mit dem Typ von „E“ kompatibel
ist. Wenn Sie also eine ArrayList<Auto> erzeugen, wird aus der Methode add() plötzlich eine
Methode add(Auto e).
C. Endreß
9 / 11
10/2008
10. Datenstrukturen
10.4.2
Verwendung von generischen Methoden
Bei einer generischen Klasse enthält die Klassendeklaration einen Typparameter. Bei einer generischen
Methode wird bei der Deklaration ein Typparameter in der Signatur angegeben.
1
Verwendung eines in der Klassendeklaration definierten Typparameters
public class ArrayList<E> extends AbstractList<E> ... {
}
public boolean add(E e)
...
Wenn Sie einen Typparameter für die Klasse
deklarieren, können Sie diesen Typ einfach überall
dort einsetzen, wo Sie als Typ eine wirkliche Klasse
oder ein wirkliches Interface benutzt hätten. Der im
Methodenargument deklarierte Typ wird dann
letztendlich durch den Typ ersetzt, den Sie beim
Instanzieren der Klasse verwenden.
2
Verwendung eines NICHT in der Klassendeklaration definierten Typparameters
public <T extends Haustier> void etwasMachen(ArrayList<T> liste)
Wenn die Klasse selbst keiner Typparameter
verwendet, können Sie trotzdem noch einen für eine
Methode angeben, indem Sie ihn an einer sehr
vor dem
ungewöhnlichen
Stelle
deklarieren:
Rückgabetyp. In dieser Methode kann „T“
beispielsweise „jeder Typ von Haustier“ sein.
Wenn Sie eine Liste von HaustierObjekten etwas machen soll, warum sagen
Sie das nicht einfach?
Warum schreiben Sie nicht einfach
etwasMachen(ArrayList<Haustier> liste)
Die Antwort ist einfach . . .
C. Endreß
10 / 11
10/2008
10. Datenstrukturen
Das hier
public <T extends Haustier> void etwasMachen(ArrayList<T> liste)
ist nicht dasselbe wie
public void etwasMachen(ArrayList<Haustier> liste)
Beide Formulierungen sind zulässig, aber sie haben unterschiedliche Bedeutungen.
Die erste Variante mit <T extends Haustier> bedeutet, dass jede ArrayList zulässig ist, die mit der
Klasse Haustier oder einer Unterklasse von Haustier wie z.B. Hund oder Katze deklariert wurde. Deshalb
können Sie die obere Methode mit ArrayList<Hund> oder ArrayList<Katze> oder
ArrayList<Haustier> aufrufen.
Die zweite Variante mit ArrayList<Haustier> bedeutet, dass ausschließlich eine
ArrayList<Haustier> als Parameter zulässig ist. Eine ArrayList<Hund> oder eine
ArrayList<Katze> dürfen nicht an die Methode übergeben werden, auch wenn die Klassen Hund und
Katze Unterklassen von Haustier sind. Die ArrayList<Haustier> darf allerdings Objekte der
Haustier-Unterklassen wie z.B. Hund und Katze enthalten. Jedes Element der ArrayList<Haustier>
wird aber als Haustier betrachtet, d.h. es können für diese Elemente nur Methoden der Basisklasse
Haustier aufgerufen werden. Um spezielle Hund- oder Katze-Methoden zu verwenden, müssen Sie die
Elemente in die jeweiligen Unterklassen casten.
10.5 Weitere Collections im Überblick
TreeSet
Bewahrt die Elemente in sortierter Reihenfolge auf und verhindert ein doppeltes Vorkommen.
HashMap
Ermöglicht die Speicherung und den Zugriff auf Elemente in Form von Name/Wert-Paaren.
LinkedList
Für eine schnellere Verarbeitung beim Einfügen oder Löschen von Elementen in der Mitte einer
Collection. (In der Praxis werden Sie aber auch hierfür in der Regel eine ArrayList einsetzen.)
HashSet
Verhindert doppelte Vorkommen von Elementen in der Collection und kann ein gegebenes Element in
der Collection schnell finden.
LinkedHashMap
Wie eine normale HashMap, kann sich jedoch die Reihenfolge merken, in der Elemente (Name/WertPaar) eingefügt wurden, oder kann so konfiguriert werden, dass sie sich die Reihenfolge merkt, in der
zuletzt auf die Elemente zugegriffen wurde.
C. Endreß
11 / 11
10/2008
11. Objekte und Klassen – Assoziationen
11. Objekte und Klassen – Assoziationen
Lernziele
☺ Erklären können, was eine Assoziation ist.
☺ Erklären können, was Aggregation und Komposition sind.
☺ UML-Notation für Assoziationen anwenden können.
☺ Assoziationen in Java realisieren können.
11.1 Zuerst die Theorie: Assoziationen
Eine Assoziation modelliert Verbindungen zwischen Objekten einer oder mehrerer Klassen.
Sie ist auch zwischen Objekten derselben Klasse zulässig. Eine Assoziation beschreibt stets Beziehungen
zwischen Objekten, nicht zwischen Klassen. Es ist jedoch üblich, von einer Assoziation zwischen Klassen zu
sprechen, obwohl streng genommen die Objekte dieser Klassen gemeint sind. Eine reflexive Assoziation
besteht zwischen Objekten derselben Klasse.
Beispiel: Kontoverwaltung
Wir betrachten die Kontoverwaltung einer Bank. Al Capone eröffnet am 04.04.2004 ein Geschäftskonto mit
der Kontonummer 4711. Er wird dadurch zum Kunden der Bank. Einen Monat später eröffnet er bei der
gleichen Bank ein privates Konto, das die Kontonummer 9876 erhält. Es besteht eine Verbindung zwischen
dem Kontobesitzer Al Capone und den Konten mit den Nummer 4711 und 9876.
Assoziationen zwischen Kunde und Konto:
Objektdiagramm
:Kunde
:Konto
Name = Al Capone
Kontonr = 4711
Art = Geschäft
Eröffnung = 04.04.04
:Konto
Kontonr = 9876
Art = Privat
Eröffnung = 10.05.04
Klassendiagramm
Kunde
Name
C. Endreß
1
*
besitzt 1 / 16
Konto
Kontonr
Art
Eröffnung
10/2008
11. Objekte und Klassen – Assoziationen
Für die Klassen Kunde und Konto gilt im betrachteten Modell:
Jeder Kunde kann mehrere Konten besitzen.
Jedes Konto gehört zu genau einem Kunden.
Die Menge aller Verbindungen bezeichnet man als Assoziationen zwischen den Objekten der Klassen Kunde
und Konto.
Assoziationen sind in der Systemanalyse bidirektional. (D.h. der Kunde kennt seine Konten und jedes
Konto kennt seinen Kunden.)
Assoziationen werden durch eine Linie zwischen Klassen beschrieben.
An jedem Ende der Linie muss die Wertigkeit bzw. Multiplizität angegeben werden. Die Multiplizität
spezifiziert wie viele Objekte ein bestimmtes Objekt kennen kann.
Man unterscheidet Kann- und Muss-Assoziationen. Eine Kann-Beziehung hat als Untergrenze die
Kardinalität 0. Eine Muss-Beziehung hat als Untergrenze die Kardinalität 1 oder größer.
Darstellung von Assoziationen und Multiplizitäten in UML-Notation:
Klasse A
Assoziationsname
Rolle A
Anzahl der Assoziationen:
Klasse A
Klasse A
Klasse A
Klasse A
Klasse A
Klasse A
Rolle B
Assoziationsname
Klasse B
Zwischen einem beliebigen Objekt und
einem Objekt der Klasse A gibt es:
1
genau eine Beziehung
(Muss-Beziehung)
*
viele Beziehungen (Null, eine oder mehrere)
(Kann-Beziehung)
0..1
Null oder eine Beziehung
(Kann-Beziehung)
1..*
eine oder mehrere Beziehungen
(Muss-Beziehung)
1, 3, 5
eine, drei oder fünf Beziehungen
(Muss-Beziehung)
0..5
null bis fünf Beziehungen
(Kann-Beziehung)
Bezogen auf das obige Beispiel ergibt sich folgende Interpretation: Die Kann-Beziehung von Kunde zu Konto
(*) bedeutet, dass es Bankkunden geben kann, die kein Konto besitzen. Die Muss-Beziehung von Konto zu
Kunde (1) bedeutet, dass ein Konto nicht auf mehrere Namen laufen kann. Ein neues Konto darf nur für
einen existierenden Kunden eingerichtet werden.
muss
Kunde
kann
1
*
besitzt Jedes Konto muss
zu genau einem
Kunden gehören
C. Endreß
Konto
Jeder Kunde kann
Null, ein oder
mehrere Konten
besitzen
2 / 16
10/2008
11. Objekte und Klassen – Assoziationen
Wird dagegen auch die Assoziation von Kunde zu Konto als Muss-Assoziation (1..*) modelliert, so darf es
keine Kunden geben, die kein Konto besitzen. Wird das letzte Konto eines Kunden gelöscht, muss auch der
Kunde gelöscht werden. Wird ein Kunde im System gelöscht, werden auch alle seine Konten gelöscht.
muss
muss
1
Kunde
1..*
besitzt Konto
Assoziationen können benannt werden. Der Name beschreibt i.a. nur eine Richtung.
Der Rollenname beschreibt die Bedeutung der Klasse in der Assoziation. Er wird jeweils an ein Ende der
Assoziation geschrieben. Die geschickte Wahl der Rollennamen kann zur Verständlichkeit des Modells
mehr beitragen als der Name der Assoziation.
Beispiel:
Der Bankkunde kann zum einen als Kontoinhaber auftreten (Muss-Beziehung von Konto zu Kunde). Er kann
aber auch als Kontoberechtigter auftreten (Kann-Beziehung von Konto zu Kunde).
Kunde
1
Name
Konto
1..*
Kontonr
Art
1..* Eröffnung
Kontoinhaber
*
Kontoberechtigter
Abgeleitete Assoziation
Eine Assoziation heißt abgeleitet (derived association), wenn die gleichen Abhängigkeiten bereits durch
andere Assoziationen beschrieben werden. Sie fügt keine neuen Informationen zum Modell hinzu und ist
daher redundant. Eine abgeleitete Assoziation wird durch das Präfix „/“ vor dem Assoziationsnamen oder
Rollennamen gekennzeichnet.
Wie das folgende Klassendiagramm zeigt, gibt es einen „direkten Weg“ von Professor zu Studenten und
einen „Umweg“ über die Vorlesung.
Professor
liest
1
*
Vorlesung
*
hört
*
*
/ ist Hörer von
C. Endreß
3 / 16
*
Student
10/2008
11. Objekte und Klassen – Assoziationen
11.2 ... dann die Praxis: Assoziationen in Java
Die Programmierung einer Assoziation hängt davon ab, ob
die Obergrenze der Multiplizitäten maximal 1 oder größer 1 ist,
die Assoziation bidirektional oder unidirektional ist.
Unidirektionale Verbindungen bestehen nur in einer Richtung. Beispielsweise kennt ein Objekt der
Klasse Kunde seine Konto-Objekte. Ein Konto-Objekt kennt aber nicht das Kunden-Objekt, mit dem es
verbunden ist. Unidirektionale Assoziationen werden in der UML durch eine Pfeilspitze gekennzeichnet.
Bei einer bidirektionalen Assoziation kennen sich alle verbundenen Objekte gegenseitig. Dies ist bei
Fachkonzept-Klassen (auf Analyseebene) in der Regel der Fall.
Bei einer unidirektionalen Assoziation muss nur eine Verbindungsrichtung verwaltet werden, während bei
der bidirektionalen Assoziation beide Verbindungsrichtungen verwaltet werden müssen.
Übliche Operationen zum Verwalten von
Verbindungen zu Objekten einer Klasse XY:
11.2.1
setXY
Anlegen einer Verbindung
getXY
Aufrufen einer Verbindung
removeXY
Löschen einer Verbindung
Unidirektionale Assoziationen
Beispiel: Kontoverwaltung
Ein Bankkunde besitzt ein Konto. Das Klassendiagramm zeigt eine unidirektionale 1:1-Assoziation. D.h. der
Kunde kennt in seiner Rolle als Kontobesitzer sein Konto, das Konto kennt seinen Besitzer jedoch nicht.
Kunde
- name : String
- vorName : String
Konto
1
besitzt
1
Kontobesitzer
+ Kunde( name : String, vorName : String )
- kontoNr : int
- kontoStand : double = 0
+ Konto( kontoNr : int )
+ einzahlen( betrag : double ) : void
+ auszahlen( betrag : double) : boolean
Es ergibt sich die folgende Realisierung in Java-Quellcode:
/* Beispiel: Kontoverwaltung
Klasse: Kunde
Demonstriert die Handhabung einer unidirektionalen 1:1-Assoziation
zwischen der Klasse Kunde und der Klasse Konto */
public class Kunde {
// Attribute
private String name;
private String vorName;
1
// Verbindungsvariable zu einen Objekt der Klasse Konto
private Konto meinKonto;
// Konstruktor
public Kunde(String name, String vorName) {
C. Endreß
4 / 16
10/2008
11. Objekte und Klassen – Assoziationen
this.name = name;
this.vorName = vorName;
this.meinKonto = null;
2
}
// Verwaltungsmethoden für die Assoziation
// Verbindung vom Kunden zum Konto-Objekt aufbauen
public void setKonto(Konto einKonto) {
this.meinKonto = einKonto;
}
3
// Aufrufen der Verbindung für den Zugriff auf das Konto
public Konto getKonto() {
return meinKonto;
}
4
// Löschen der Verbindung zum Konto
public void removeKonto(){
meinKonto = null;
}
5
// Get-Methoden für die Attribute
public String getName() {
return name;
}
public String getVorName() {
return vorName;
}
}
Erläuterungen
1
Das Attribut meinKonto ist eine Referenzvariable der Klasse Konto. Mit dieser Referenzvariable
wird die Verbindung zwischen einem Kunden-Objekt und einem Konto-Objekt hergestellt. Java
weist Attributen, die nicht explizit im Quellcode mit einem Initialwert belegt werden, defaultWerte zu. Für Referenzvariablen ist der default-Wert null.
:Kunde
name
vorName
meinKonto =
null
Verbindungsvariable
2
Im Konstruktor des Kunden wird die Verbindungsvariable meinKonto zunächst noch einmal
bewusst mit null initialisiert. Es besteht noch keine Verbindung des Kunden zu einem Konto.
Das Anlegen der Verbindung zu einem Konto-Objekt wird mit der Operation setKonto()
hergestellt.
3
Die Methode setKonto() stellt die Assoziation zwischen dem Objekt der Klasse Kunde und
einem Konto-Objekt her. Dazu wird der Methode setKonto() das Konto-Objekt übergeben,
zu dem eine Verbindung aufgebaut werden soll. Das Attribut meinKonto des Kunden-Objekts
wird in setKonto() mit der Referenz auf den übergebenen Parameter einKonto initialisiert.
Damit wird die unidirektionale Verbindung zwischen einem Kunden und dem zugehörigen Konto
erstellt.
4
Die Methode getKonto() ruft die Verbindung zum assoziierten Konto-Objekt auf, indem die
Verbindungsvariable meinKonto zurückgegeben wird.
C. Endreß
5 / 16
10/2008
11. Objekte und Klassen – Assoziationen
Die Methode removeKonto() löscht die Verbindung zum Konto, indem das Attribut
meinKonto, das auf das Konto-Objekt zeigt, auf null gesetzt wird. Das dereferenzierte
Konto-Objekt wird dann vom Garbage Collector aus dem Programmspeicher gelöscht.
5
Die Assoziation zwischen Kunde und Konto ist unidirektional. D.h. der Kunde kennt in seiner Rolle als
Kontobesitzer sein Konto, das Konto kennt seinen Besitzer jedoch nicht. Die Klasse Konto enthält kein
Referenz-Attribut auf ein Kunden-Objekt.
/* Beispiel: Kontoverwaltung
Klasse: Konto
Demonstriert die Handhabung einer unidirektionalen 1:1-Assoziation
zwischen der Klasse Kunde und der Klasse Konto */
public class Konto {
// Attribute
private int kontoNr;
private double kontoStand;
// Konstruktor
public Konto(int kontoNr) {
this.kontoNr = kontoNr;
kontoStand = 0;
}
// Methoden
public void einzahlen(double betrag) {
if (betrag > 0) {
kontoStand += betrag;
}
}
public boolean auszahlen(double betrag) {
if (kontoStand - betrag >= 0) {
kontoStand -= betrag;
return true;
}
else
return false;
}
public int getKontoNr(){
return kontoNr;
}
public double getKontoStand(){
return kontoStand;
}
}
In der folgenden Anwendung wird ein Kunde angelegt, für den anschließend ein Konto eröffnet wird.
/* Beispiel: Kontoverwaltung
Klasse: Kontoverwaltung
Demonstriert die Handhabung einer unidirektionalen 1:1-Assoziation
zwischen der Klasse Kunde und der Klasse Konto */
import support.Console;
public class Kontoverwaltung {
public static void main(String[] args) {
1
C. Endreß
Kunde einKunde = new Kunde("Capone", "Al");
6 / 16
10/2008
11. Objekte und Klassen – Assoziationen
2
// Konto erzeugen und Assoziation zum Kontobesitzer herstellen
einKunde.setKonto(new Konto(4711));
3
// Zugriff auf das Konto-Objekt über die Assoziation
einKunde.getKonto().einzahlen(2000);
4
Console.println("Kontobesitzer: "
+ einKunde.getName() + ", "
+ einKunde.getVorName());
Console.println("Kontonummer: "
+ einKunde.getKonto().getKontoNr());
Console.println("Kontostand : "
+ einKunde.getKonto().getKontoStand() + " €");
// Konto löschen
einKunde.removeKonto();
5
}
}
Konsolenausgabe
Kontobesitzer: Capone, Al
Kontonummer: 4711
Kontostand : 2000.0 €
Erläuterungen
1
Anlegen des Kunden-Objekts einKunde.
2
Mit dem new-Operator wird ein anonymes Konto-Objekt erzeugt, das mit der Kontonummer
4711 initialisiert und als Parameter an die Instanz-Methode setKonto() des Objekts
einKunde übergeben wird. Wie bereits oben erläutert, stellt die Methode setKonto() die
Verbindung zwischen dem Objekt des Kontobesitzers und dem Konto-Objekt her, indem die
Verbindungsvariable meinKonto mit dem entsprechenden Konto-Objekt initialisiert wird.
einKunde
:Konto
name = Capone
vorName = Al
kontoNr = 4711
kontoStand = 0
meinKonto =
Verbindungsvariable
3
Das Kunden-Objekt einKunde ruft über die Methode getKonto() die Verbindung zum
assoziierten Konto-Objekt auf und kann dadurch auf die Instanz-Methoden des Kontos
zugreifen. Mit der Methode einzahlen() wird ein Betrag von 2000 € auf das Konto eingezahlt.
4
Ausgeben der Attribute des Objekts einKunde und im Folgenden der Attribute des assoziierten
Konto-Objekts.
5
Die Methode einKunde.removeKonto() löscht die Assoziation zwischen dem Objekt
einKunde und seinem Konto-Objekt, indem die Verbindungsvariable meinKonto wieder auf
null gesetzt wird. Damit ist das Konto-Objekt nicht mehr referenziert und wird vom Garbage
Collector der virtuellen Maschine aus dem Programmspeicher gelöscht.
Bei unidirektionalen Assoziationen wie im gezeigten Fall enthält nur eine Klasse Referenz-Attribute und
Verwaltungsoperationen für die Verbindung.
C. Endreß
7 / 16
10/2008
11. Objekte und Klassen – Assoziationen
11.2.2
Bidirektionale Assoziationen
Bei bidirektionalen Assoziationen sind in beiden beteiligten Klassen Referenz-Attribute und
Verwaltungsoperationen für die Verbindungen vorzusehen.
Beispiel: Kontoverwaltung
a) 1:1-Assoziation
Wir erweitern das vorangegangene Beispiel zu einer bidirektionalen Verbindung zwischen Kunde und Konto.
Der Kontobesitzer kennt sein Konto. Außerdem kennt nun auch das Konto seinen Kontobesitzer. Das
Klassendiagramm zeigt jetzt eine bidirektionale 1:1-Assoziation.
Kunde
- name : String
- vorName : String
Konto
1
besitzt
Kontobesitzer
+ Kunde( name : String, vorName : String )
1
- kontoNr : int
- kontoStand : double = 0
+ Konto( kontoNr : int )
+ einzahlen( betrag : double ) : void
+ auszahlen( betrag : double) : boolean
Die Klasse Kunde kann aus dem letzten Beispiel übernommen werden, da sich für den Kunden nichts ändert.
Die Klasse Konto muss die Verbindung zum Kunden erweitert werden:
/* Beispiel: Kontoverwaltung
Klasse: Konto
Demonstriert die Handhabung einer bidirektionalen 1:1-Assoziation
zwischen der Klasse Kunde und der Klasse Konto */
public class Konto {
// Attribute
private int kontoNr;
private double kontoStand;
1
2
3
4
5
C. Endreß
// Verbindungsvariable für die Assoziation zu einem Kunden
private Kunde kontoBesitzer;
// Konstruktor
public Konto(int kontoNr, Kunde kontoBesitzer) {
this.kontoNr = kontoNr;
this.kontoStand = 0;
this.setKontoBesitzer(kontoBesitzer);
}
// Verwaltungsmethoden für die Assoziation
// Verbindungen vom Konto zum Kontobesitzer aufbauen
public void setKontoBesitzer(Kunde kontoBesitzer){
this.kontoBesitzer = kontoBesitzer;
this.kontoBesitzer.setKonto(this);
}
// Verbindung zum Kontobesitzer aufrufen
public Kunde getKontoBesitzer(){
return kontoBesitzer;
}
// Verbindungen zwischen Konto und Kontobesitzer löschen
public void removeKontoBesitzer(){
8 / 16
10/2008
11. Objekte und Klassen – Assoziationen
this.kontoBesitzer.removeKonto();
this.kontoBesitzer = null;
}
// Get-Methoden
public int getKontoNr(){
return kontoNr;
}
public double getKontoStand(){
return kontoStand;
}
// Instanzmethoden
public void einzahlen(double betrag) {
if (betrag > 0) {
kontoStand += betrag;
}
}
public boolean auszahlen(double betrag) {
if (kontoStand - betrag >= 0) {
kontoStand -= betrag;
return true;
}
else
return false;
}
}
Erläuterungen
1
Deklaration
der
Verbindungsvariable
kontoBesitzer:
Die
Verbindungsvariable
kontoBesitzer soll eine Assoziation zum Kontobesitzer, einem Objekt der Klasse Kunde,
realisieren. Die Variable muss deshalb von der Klasse Kunde sein.
2
Dem Konstruktor des Konto-Objekts wird der Kontobesitzer als Parameter übergeben. Damit
kann sofort bei der Erzeugung des Konto-Objekts die Verbindung zum Kontobesitzer hergestellt
werden,
indem
die
Verbindungsvariable
kontoBesitzer
an
die
Methode
setKontoBesitzer() übergebenen wird.
3
Die Methode setKontoBesitzer() baut die bidirektionale Verbindung zwischen dem KundenObjekt und dem Konto-Objekt auf. Zuerst wird die Referenzvariable this.kontoBesitzer mit
dem übergebenen Kunden-Objekt initialisiert, so dass das Konto nun über einen Link zum
Kontobesitzer verfügt. Anschließend wird mit diesem Link mit der Botschaft setKonto() an den
Kontobesitzer die Verbindung vom Kunden zum Konto zu initialisiert. Als Parameter übergibt sich
das Konto selbst: setKonto(this).
2. Rückverbindung vom Kunden zum Konto setzen:
this.kontoBesitzer.setKonto(this)
Kunde
Konto
name
vorName
kontoNr
kontoStand
meinKonto
kontoBesitzer
1. Verbindung vom Konto zum Kunden setzen:
this.kontoBesitzer = kontoBesitzer
C. Endreß
9 / 16
10/2008
11. Objekte und Klassen – Assoziationen
4
getKontoBesitzer() ruft die Verbindung zum Kontobesitzer auf, so dass das Konto auf
seinen Besitzer zugreifen kann.
5
Die beidseitige Verbindung zwischen dem Konto und dem Kontobesitzer wird in der Methode
removeKontoBesitzer() gelöscht, indem zuerst die Verbindung vom Kontobesitzer zum
und
dann
die
Konto
ausgelöst
wird
(this.kontoBesitzer.removeKonto())
Verbindungsvariable this.kontoBesitzer wieder auf null gesetzt wird.
/* Beispiel: Kontoverwaltung
Klasse: Kontoverwaltung
Demonstriert die Handhabung einer bidirektionalen 1:1-Assoziation
zwischen der Klasse Kunde und der Klasse Konto */
import support.Console;
public class Kontoverwaltung {
public static void main(String[] args) {
1
// Ein Kunden-Objekt anlegen
Kunde einKunde = new Kunde("Capone", "Al");
2
// Konto-Objekt erzeugen und den Kontobesitzer übergeben
Konto einKonto = new Konto(4711, einKunde);
3
// Zugriff auf das Konto-Objekt über den Kontobesitzer
einKunde.getKonto().einzahlen(2000);
Console.println("Assoziation Kunde -> Konto: ");
Console.println("Kontobesitzer: "
+ einKunde.getName() + ", "
+ einKunde.getVorName());
Console.println("Kontonummer: "
+ einKunde.getKonto().getKontoNr());
Console.println("Kontostand : "
+ einKunde.getKonto().getKontoStand() + " €");
// Zugriff auf den Kontobesitzer über das Konto-Objekt
Console.println("\nAssoziation Konto -> Kunde: ");
Console.println("Konto: "
+ einKonto.getKontoNr());
Console.println("Kontobesitzer: "
+ einKonto.getKontoBesitzer().getName()+ ", "
+ einKonto.getKontoBesitzer().getVorName());
4
}
}
Konsolenausgabe
Assoziation Kunde -> Konto:
Kontobesitzer: Capone, Al
Kontonummer: 4711
Kontostand : 2000.0 €
Assoziation Konto -> Kunde:
Konto: 4711
Kontobesitzer: Capone, Al
C. Endreß
10 / 16
10/2008
11. Objekte und Klassen – Assoziationen
Erläuterungen
1
Anlegen des Kunden-Objekts einKunde.
2
Anlegen des Objekts einKonto: Dem Konstruktor der Klasse Konto werden die Kontonummer
(4711) und ein Objekt der Klasse Kunde (einKunde) übergeben. Im Konstruktor von Konto
wird dann das Attribut kontoNr mit 4711 initialisiert. Außerdem wird im Konto-Konstruktor
die Verbindung des neuen Konto-Objekts einKonto zum Kunden-Objekt einKunde erzeugt
und anschließend sofort die Rückverbindung vom Kunden-Objekt einKunde zum Konto-Objekt
einKonto hergestellt (siehe oben Erläuterungen zur Klasse Konto).
2. Rückverbindung vom Kunden zum Konto:
this.kontoBesitzer.setKonto(this)
einKunde:Kunde
einKonto:Konto
name = Capone
vorName = Al
kontoNr = 4711
kontoStand = 0
meinKonto = einKonto
kontoBesitzer = einKunde
1. Verbindung von Konto zum Kunden:
this.kontoBesitzer = kontoBesitzer
3
Die Klasse Kunde stellt die Methode getKonto() bereit, um die Verbindung zum assoziierten
Konto-Objekt aufzurufen. Mit Hilfe von einKunde.getKonto() kann dann auf die
verschiedenen Instanz-Methoden des verbundenen Kontos zugegriffen werden.
4
In der umgekehrten Richtung ruft einKonto.getKunde() die Verbindung vom Objekt
einKonto zum assoziierten Kunden-Objekt auf.
b) 1:*-Assoziation
Wir gehen noch einen Schritt weiter und erweitern das vorangegangene Beispiel zu einer bidirektionalen 1:*Assoziation zwischen Kunde und Konto. Ein Kontobesitzer kann beliebig viele Konten besitzen. Jedes Konto
hat nur einen Besitzer und kennt diesen.
Klassendiagramm
Kunde
- name : String
- vorName : String
Konto
1
besitzt
Kontobesitzer
+ Kunde( name : String, vorName : String )
0..*
- kontoNr : int
- kontoStand : double = 0
+ Konto( kontoNr : int )
+ einzahlen( betrag : double ) : void
+ auszahlen( betrag : double) : boolean
Die folgenden Auszüge aus dem Java-Quellcode enthalten den Programmcode, der für die 1:*-Assoziation
relevant ist. Anstelle einer einzelnen Referenzvariable vom Typ Konto benötigt der Kunde nun eine
Möglichkeit, beliebig viele Referenzvariablen auf Konto-Objekte zu verwalten. Dazu kann man auf die
Collections von Java zurückgreifen und z.B. ein Objekt der Klasse ArrayList verwenden.
C. Endreß
11 / 16
10/2008
11. Objekte und Klassen – Assoziationen
/* Programm: Kontoverwaltung
Klasse: KontoVerwaltung.java
Demonstriert die Handhabung einer bidirektionalen 1:*-Assoziation
zwischen der Klasse Kunde und der Klasse Konto */
*/
package kontoverwaltung;
import support.*;
public class KontoVerwaltung {
public static void main(String[] args) {
Kunde ersterKunde = new Kunde("Capone", "Al");
ersterKunde.addKonto(new Konto(2020));
ersterKunde.addKonto(new Konto(5050));
// Einzahlung
ersterKunde.getKonto(2020).einzahlen(1000);
// Auszahlung
ersterKunde.getKonto(2020).auszahlen(300);
// weiterer Code
}
}
/* Programm: Kontoverwaltung
Klasse: Kunde.java
*/
package kontoverwaltung;
public class Kunde {
1
private ArrayList<Konto> kundenKonten = new ArrayList<Konto>();
// weiterer Code
2
public void addKonto(Konto neuesKonto){
neuesKonto.setKontoBesitzer(this);
this.kundenKonten.add(neuesKonto);
}
3
public void removeKonto(Konto wegMitDemKonto){
if(this.kundenKonten.contains(wegMitDemKonto)){
wegMitDemKonto.setKontoBesitzer(null);
this.kundenKonten.remove(wegMitDemKonto);
}
}
4
public Konto getKonto(int kontoNr) {
for (Konto konto : kundenKonten) {
if (konto.kontoNr == kontoNr) {
return konto;
}
}
return null;
}
}
C. Endreß
12 / 16
10/2008
11. Objekte und Klassen – Assoziationen
Erläuterungen
1
Die 0..*-Assoziation zwischen Kunde und Konten wird mit einer ArrayList realisiert, da diese eine
flexible Kapazität besitzt.
2
Die Methode addKonto() baut die beidseitige Beziehung auf. Zuerst wird beim neuen Konto
die Referenzvariable für den Kontobesitzer initialisiert. Dann wird das neue Konto in die ArrayList
der Konten aufgenommen.
3
Die Methode removeKonto() löscht die bidirektionale Verbindung zwischen dem Kunden- und
dem Konto-Objekt wegMitDemKonto. Wenn wegMitDemKonto ein Element der Kontoliste ist,
wird mit setKontoBesitzer(null) die Verbindung vom Konto zum Kunden gelöscht.
Anschließend wird das Konto-Objekt aus der Liste entfernt.
4
Mit einer erweiterten for-Schleife wird über die Konten der ArrayList kundenKonten iteriert.
Das Objekt konto ist der Iterator mit dem die ArrayList durchlaufen wird.
/* Programm: Kontoverwaltung
Klasse: Konto.java
*/
package kontoverwaltung;
public class Konto {
...
// Konstruktor
public Konto(int kontoNr) {
this.kontoNr = kontoNr;
this.kontoStand = 0;
this.kontoBesitzer = null;
}
// Verwaltungsmethoden für die Assoziation
// Verbindungen vom Konto zum Kontobesitzer aufbauen
public void setKontoBesitzer(Kunde kontoBesitzer){
this.kontoBesitzer = kontoBesitzer;
}
1
// Verbindungen zwischen Konto und Kontobesitzer löschen
public void removeKontoBesitzer(){
this.kontoBesitzer.removeKonto(this);
this.kontoBesitzer = null;
}
2
}
Erläuterungen
1
Die Methode setKontoBesitzer() wird vom Kundenobjekt in dessen Instanz-Methode
addKonto() aufgerufen.
2
Die Methode removeKontoBesitzer() löscht beide Referenzen: Zuerst die Referenz vom
Kontobesitzer zu dessen Konto (this.kontoBesitzer.removeKonto(this)) und dann die
Referenz des Kontos auf seinen Kontobesitzer (this.kontoBesitzer = null).
C. Endreß
13 / 16
10/2008
11. Objekte und Klassen – Assoziationen
11.2.3
Spezielle Assoziationen
Die UML kennt außer der einfachen Assoziation noch zwei weitere, speziellere Arten:
Aggregation und
Komposition
11.2.4
Aggregation
Eine Aggregation liegt vor, wenn zwischen den Objekten der beteiligten Klassen (kurz: den beteiligten
Klassen) eine Rangordnung gilt, die sich durch „ist Teil von“ bzw. „besteht aus“ beschreiben lässt.
In einer Aggregationsbeziehung zwischen zwei Klassen muss genau ein Ende der Beziehung die
Aggregatklasse (das Ganze) sein und das andere Ende für die Einzelteile stehen. Das bedeutet: Wenn
B Teil von A ist, dann darf A nicht Teil von B sein.
Aggregationen sind gewöhnlich 1-zu-viele-Beziehungen.
Es besteht keine existentielle Abhängigkeit zwischen der Aggregatklasse und der Teilklasse. Das Ganze
kann gelöscht werden und seine Einzelteil bleiben erhalten.
Bei einer Aggregation kennzeichnet eine weiße bzw. transparente Raute die Aggregatklasse, also das
Ganze. Alle anderen Angaben (Kardinalitäten, Namen, Rollen, etc.) werden analog zur Assoziation
angegeben.
Beispiele: Aggregation
Ein Pkw hat üblicherweise vier Räder und eventuell eine Ersatzrad. Jedes Rad gehört genau zu einem Pkw.
1
Pkw
4..5
Aggregatklasse
Rad
Teilklasse
Einem Web-Auftritt können mehrere Web-Seiten zugeordnet sein. Jede Web-Seite kann in mehreren WebAuftritten referenziert werden. Der Web-Auftritt kann gelöscht werden, ohne dass die referenzierten WebSites gelöscht werden müssen.
*
Web-Auftritt
1..*
Aggregatklasse
Web-Site
Teilklasse
Eine Abteilung umfasst mehrere Mitarbeiter. Wird die Abteilung aufgelöst, so werden die Mitarbeiter jedoch
nicht zwingend entlassen.
1
Abteilung
Aggregatklasse
1..*
Mitarbeiter
Teilklasse
Achtung Placebo! Assoziation und Aggregation sind semantisch gleichwertig. Sie sind im resultierenden
Programmcode nicht unbedingt zu unterscheiden. Falls Sie unsicher sind, welche Variante die richtige ist,
machen Sie sich klar, dass es streng genommen sowieso keinen Unterschied gibt. Die Aggregation gibt
einen wichtigen Hinweis auf die höhere Bindung zwischen den an der Aggregationsbeziehung beteiligten
Klassen, was Klassenmodelle verständlicher macht. Im Zweifelsfall nehmen Sie die einfachere Variante,
also die Assoziation.
C. Endreß
14 / 16
10/2008
11. Objekte und Klassen – Assoziationen
11.2.5
Komposition
Eine Komposition ist eine strenge Form der Aggregation. Auch hier muss eine „ist Teil von“-Beziehung
vorliegen. Die Teile sind vom Ganzen existenzabhängig.
Jedes Objekt der Teilklasse kann – zu einem Zeitpunkt – nur Komponente eines einzigen Objekts der
Aggregatklasse sein, d.h. die bei der Aggregatklasse angetragene Multiplizität darf nicht größer als eins
sein. Ein Teil darf jedoch – zu einem anderen Zeitpunkt – auch einem anderen Ganzen zugeordnet
werden.
Es besteht eine existentielle Abhängigkeit zwischen der Aggregatklasse und der Teilklasse. Wird das
Ganze gelöscht, dann werden automatisch seine Teile gelöscht. Ein Teil darf jedoch zuvor explizit
entfernt werden.
Falls eine variable Kardinalität bei den Teilen angegeben ist (z.B.: 1..*), heißt dies, sie müssen nicht
gemeinsam mit dem Aggregat erzeugt werden, sondern können auch später entstehen.
Die dynamische Semantik des Ganzen gilt auch für seine Teile. Wird beispielsweise das Ganze kopiert, so
werden auch seine Teile kopiert.
Bei einer Komposition kennzeichnet eine schwarze bzw. gefüllte Raute das Ganze. Alle anderen Angaben
(Kardinalitäten, Namen, Rollen, Restriktionen, etc.) werden analog zur Assoziation angegeben.
Beispiele: Komposition
Eine Rechnung enthält mindestens eine Rechnungsposition. Eine Rechnungsposition gehört zu genau einer
Rechnung. Die Rechnungspositionen sind existenzabhängig von der Rechnung. Sobald die Rechnung
gelöscht wird, werden auch alle Rechnungspositionen in ihr ebenfalls gelöscht. Die Klasse Rechnung
übernimmt bestimmte Aufgaben für die Gesamtheit. So wird sie beispielsweise Operationen wie
anzahlPositionen() oder summeBilden() enthalten.
1
Rechnung
1..*
Aggregatklasse
Rechnungsposition
Teilklasse
Einem Sparkonto können jeweils mehrere Kontobewegungen zugeordnet sein. Es kann allerdings jede
Kontobewegung nur einem Sparkonto zugeordnet sein. Wird das Sparkonto-Objekt kopiert, dann werden
auch alle ihm zugeordneten Kontobewegungen kopiert. Wird das Konto gelöscht, werden auch die
zugeordneten Kontobewegungen gelöscht.
1
Sparkonto
*
Aggregatklasse
Kontobewegung
Teilklasse
Ein Verzeichnis kann mehrere Dateien enthalten, wobei jede Datei nur im einem Verzeichnis enthalten sein
kann. Wird das Verzeichnis kopiert, werden auch alle darin enthaltenen Dateien kopiert. Wird das
Verzeichnis gelöscht, werden auch alle darin enthaltenen Dateien gelöscht.
1
Verzeichnis
Aggregatklasse
C. Endreß
15 / 16
*
Datei
Teilklasse
10/2008
11. Objekte und Klassen – Assoziationen
11.3 Zusammenfassung
Eine Assoziation modelliert Verbindungen zwischen Objekten einer oder mehrerer Klassen. Diese
Verbindungen können nur in einer Richtung (unidirektional) oder in wechselseitiger Richtung
(bidirektional)bestehen.
Assoziationen zwischen Objekten werden in Java mit Referenz-Variablen realisiert.
Sonderfälle der Assoziation sind die Aggregation und die Komposition.
Eine Aggregationen modelliert eine Ist-Teil-von-Beziehung.
Eine Komposition ist eine strenge Form der Aggregation. Es besteht eine existentielle Abhängigkeit
zwischen dem Ganzen (Aggregatklasse) und seinen Teilen. Jedes Teil kann zu einem Zeitpunkt nur zu
einem Ganzen gehören.
Durch Kardinalitäten wird die Wertigkeit von Assoziationen, Aggregationen und Kompositionen
spezifiziert. Zusätzlich kann durch eine Rolle die Funktion des Objekts in einer Assoziation, Aggregation
oder Komposition festgelegt werden.
C. Endreß
16 / 16
10/2008
12. Objekte und Klassen – Dynamische Abläufe
12. Objekte und Klassen – Dynamische Abläufe
Lernziele
☺ Dynamische Abläufe mit den UML-Notationen Sequenzdiagramm und Kollaborationsdiagramm
beschreiben können.
12.1 Dynamische Abläufe
Objektdiagramme, die wir bisher kennen gelernt haben, zeigen nur eine Momentaufnahme des Systems zu
einem bestimmten Zeitpunkt. Sie eignen sich deshalb nur bedingt zur graphischen Darstellung dynamischer
Abläufe. Die UML verfügt zu diesem Zweck über geeignetere Notationen:
Kollaborationsdiagramme und
Sequenzdiagramme
12.1.1
Kollaborationsdiagramm
Ein Kollaborationsdiagramm stellt den Botschaftenfluss zwischen Objekten dar.
Es sieht dem Objektdiagramm, in dem Objekte und ihre Verbindungen beschrieben werden, relativ ähnlich.
Ein Kollaborationsdiagramm erweitert das Objektdiagramm um Botschaften.
Es zeigt diejenigen Objekte, die für die Ausführung einer bestimmten Operation relevant sind.
Objekte, die während der Ausführung neu erzeugt werden, sind mit {new}, Objekte, die während der
Ausführung gelöscht werden, mit {destroyed} gekennzeichnet. Objekte, die während der Ausführung
sowohl erzeugt als auch wieder gelöscht werden, sind {transient}.
Als Auslöser einer Operation kann ein Akteur – in der Regel der Benutzer – eingetragen werden,
dargestellt als „Strichmännchen“.
An jede Verbindung (link) kann eine Botschaft in Form eines Pfeils, einer laufenden Nummer und dem
Operationsnamen angetragen werden. Die Reihenfolge und Verschachtelung der Operationen wird durch
eine hierarchische Nummerierung angegeben.
1:nachricht1()
:Klasse1
{transient}
2:nachricht2()
:Klasse2
{new}
3:nachricht4()
© C. Endreß
:Klasse4
{new}
1/7
2.1:nachricht7()
:Klasse3
{new}
wird während der Ausführung neu erzeugt
{{transient} wird während der Ausführung erzeugt und
wieder gelöscht
{destroyed} wird während der Ausführung gelöscht
10/2008
12. Objekte und Klassen – Dynamische Abläufe
12.1.2
Sequenzdiagramme
Ein Sequenzdiagramm dient zur schematischen Veranschaulichung von zeitbasierten Vorgängen.
Sequenzdiagramme erlauben eine genaue zeitliche Darstellung von Abläufen – allerdings unter Verzicht auf
Attributangaben und Verbindungen zwischen Objekten. In der Systemanalyse werden Sequenzdiagramme
verwendet, um Abläufe so präzise zu beschreiben, dass deren fachliche Korrektheit diskutiert werden kann.
Im Systementwurf werden Sequenzdiagramme für eine detaillierte Spezifikation der Operationsabläufe
verwendet und enthalten dann alle beteiligten Operationen.
Kennzeichnend für diese Darstellungsform des Sequenzdiagramms ist eine Zeitachse, die vertikal von
oben nach unten führt.
Objekte, die Botschaften austauschen, werden durch gestrichelte vertikale Geraden dargestellt (sog.
Objektlinien oder Lebenslinien). Jede Linie repräsentiert die Existenz eines Objekts während einer
bestimmten Zeit. Eine Objektlinie beginnt nach dem Erzeugen des Objekts und endet mit dem Löschen
des Objekts. Existiert ein Objekt während der gesamten Ausführungszeit, dann ist die Linie von oben
nach unten durchgezogen. Am oberen Ende der Linie wird ein Objektsymbol gezeichnet.
Wird ein Objekt erst im Laufe der Ausführung erzeugt, dann zeigt eine Botschaft auf dieses
Objektsymbol.
Das Löschen des Objekts wird durch ein großes „X“ markiert.
Die Reihenfolge der Objekte ist beliebig. Sie soll so gewählt werden, dass ein möglichst übersichtliches
Diagramm entsteht.
Die erste vertikale Linie bildet in vielen Sequenzdiagrammen einen Akteur – in der Regel der Benutzer –
dargestellt als »Strichmännchen«.
In das Sequenzdiagramm werden die Botschaften eingetragen, die zum Aktivieren der Operationen
dienen. Jede Botschaft wird als gerichtete Kante (mit gefüllter Pfeilspitze) vom Sender zum Empfänger
gezeichnet. Der Pfeil wird mit dem Namen der aktivierten Operation beschriftet. Eine aktive Operation
wird durch ein schmales Rechteck (Steuerungsfokus) auf der Objektlinie angezeigt. Nach dem
Beenden der Operation zeigt eine gestrichelte Linie mit offener Pfeilspitze, dass der Kontrollfluss zur
aufrufenden Operation zurückgeht. Auf diese gestrichelte Linie kann verzichtet werden.
Die UML erlaubt die Angabe von Bedingungen und Wiederholungen im Sequenzdiagramm.
Die Bedingung (condition) wird in eckigen Klammern angegeben:
[Bedingung] Operation()
Die aufgeführte Operation wird dann aufgerufen, wenn die Bedingung erfüllt ist.
Wiederholungen (iterations) können spezifiziert werden durch:
Operation()
oder
* [Bedingung] Operation()
Wenn keine Wiederholungen in ein Diagramm eingetragen werden, so bedeutet dies in der UML,
dass die Anzahl der Wiederholungen unspezifiziert ist. Die Bedingung wird in der Systemanalyse in
der Regel umgangssprachlich formuliert.
© C. Endreß
2/7
10/2008
12. Objekte und Klassen – Dynamische Abläufe
bereits existierende Objekte
Akteur
einObjekt
:Klasse1
Bedingung:
[bedingung] operation()
:Klasse2
Wiederholung:
* operation()
* [bedingung] operation()
Objekt wird
erzeugt
nachricht1()
Klasse3()
neuesObjekt:
Klasse3
nachricht5()
Auf Antwortpfeile
kann auch
verzichtet werden.
nachricht3()
Objekt schickt
Botschaften an
sich selbst
nachricht2()
Steuerungsfokus
Objekt wird
gelöscht
Lebenslinie
Beispiel: Kontoverwaltung
Ein Bankkunde kann beliebig viele Sparkonten besitzen. Für jedes Konto wird ein individueller Habenzins
festgelegt. Außerdem besitzt jedes Konto eine eindeutige Kontonummer. Ein Kunde kann Beträge einzahlen
und abheben. Desweiteren werden Zinsen gutgeschrieben. Um die Zinsen zu berechnen, muss für jede
Kontobewegung das Datum und der Betrag notiert werden. Die Gutschrift der Zinsen erfolgt bei den
Sparkonten jährlich. Ein Kunde kann jedes seiner Konten wieder auflösen.
Klassendiagramm
Das statische Konzept der Problemlösung wird durch folgendes UML-Klassendiagramm dargestellt.
Kunde
Name
Vorname
SparKonto
1
0..* Kontonummer
KontoBewegung
1
Habenzins
/ Kontostand
1..*
Betrag
Datum
einzahlen()
auszahlen()
gutschreibenZinsen()
© C. Endreß
3/7
10/2008
12. Objekte und Klassen – Dynamische Abläufe
Objektdiagramm
Das Objektdiagramm liefert eine Momentaufnahme des Systems, da es den Zustand nur zu einem
bestimmten Zeitpunkt beschreibt.
Der Kunde Al Capone hat ein Sparkonto mit der Kontonummer 1111 eröffnet und am 04.04.2004 eine erste
und bisher einzige Einzahlung über 1000 € vorgenommen. Am 10.04.2004 folgte eine Auszahlung über 300
€. Weitere Kontobewegungen gab bis dahin nicht.
ersterKunde:Kunde
Name = ”Capone“
Vorname = “Al“
:SparKonto
:KontoBewegung
Kontonummer = 1111
Habenzins = 1.5
/ Kontostand = 700 €
Betrag = 1000 €
Datum = 04.04.2004
:KontoBewegung
Betrag = -300 €
Datum = 10.04.2004
Kollaborationsdiagramm
Der Ablauf einer Auszahlung für ein Sparkonto des Kunden-Objekts ersterKunde wird mit folgendem
Objektdiagramm dargestellt:
1 : getLinkKonto()
Kontoverwaltung
ersterKunde : Kunde
2 : auszahlen(betrag)
2.1.1 : KontoBewegung()
: SparKonto
: KontoBewegung
{new}
2.1: [kontoStand > betrag] addKontoBewegung(betrag)
Die Reihenfolge der Operationen wird durch die Nummerierung festgelegt. Die Methode
setLinkKontoBewegung wird im Rahmen der Methode auszahlen aufgerufen – erkennbar an der
Nummer 2.1. Außerdem ist die Ausführung der Methode setLinkKontoBewegung von der Bedingung
kontoStand > betrag abhängig, was durch die eckigen Klammern gekennzeichnet ist. Der
Konstruktoraufruf KontoBewegung() ist wiederum Bestandteil der Methode setLinkKontoBewegung.
© C. Endreß
4/7
10/2008
12. Objekte und Klassen – Dynamische Abläufe
Sequenzdiagramm
Das folgende Sequenzdiagramm modelliert den Vorgang einer Auszahlung für das Kunden-Objekt
ersterKunde.
Kontoverwaltung
ersterKunde : Kunde
getLinkKonto()
: SparKonto
auszahlen(betrag)
[kontoStand > betrag]
addKontoBewegung()
KontoBewegung()
: KontoBewegung
Als Akteur tritt in diesem Fall die Klasse Kontoverwaltung auf, die an das Objekt ersterKunde die
Botschaft getLinkKonto sendet. Das Objekt ersterKunde baut eine Verbindung zu einem Sparkonto auf
und sendet die Botschaft auszahlen an das SparKonto-Objekt. Wenn der aktuelle Kontostand größer als
der Auszahlungsbetrag ist, wird die Auszahlung ausgeführt, indem mit der Methode
setLinkKontoBewegung ein neues Objekt der Klasse KontoBewegung mit den Daten der Transaktion
generiert wird. Die Verbindung zwischen dem SparKonto und dem neuen KontoBewegung-Objekt wird
mit der Methode setLinkKontoBewegung erzeugt.
Implementierung in Java
Die folgenden Auszüge aus dem Java-Quellcode enthalten den Programmcode, der für die modellierte
Auszahlung relevant ist.
/* Programm: Kontoverwaltung
Klasse: KontoVerwaltung.java
*/
package kontoverwaltung;
import support.*;
import java.util.GregorianCalendar;
public class KontoVerwaltung {
public static void main(String[] args) {
Kunde ersterKunde = new Kunde("Capone", "Al");
ersterKunde.addSparKonto(new SparKonto(1111));
// Einzahlung
ersterKunde.getLinkKonto(1111).einzahlen(1000);
// Auszahlung
© C. Endreß
5/7
10/2008
12. Objekte und Klassen – Dynamische Abläufe
ersterKunde.getLinkKonto(1111).auszahlen(300);
druckeKontoAuszug(ersterKunde.getLinkKonto(1111));
}
public static void druckeKontoAuszug(Konto konto) {
...
}
}
/* Programm: Kontoverwaltung
Klasse: Kunde.java
*/
package kontoverwaltung;
public class Kunde {
...
1
private ArrayList<SparKonto> kundenKonten = new ArrayList<SparKonto>();
...
public void addSparKonto(SparKonto neuesKonto){
this.kundenKonten.add(neuesKonto);
}
public SparKonto getLinkKonto(int kontoNr) {
for (SparKonto konto : kundenKonten) {
if (konto.kontoNr == kontoNr) {
return konto;
}
}
return null;
}
2
}
Erläuterungen
1
Die 0..*-Assoziation zwischen Kunde und Sparkonten wird mit einer ArrayList realisiert, die diese
eine flexible Kapazität besitzt.
2
Mit einer erweiterten for-Schleife wird über die Sparkonten der ArrayList kundenKonten
iteriert. Das Objekt konto ist der Iterator mit dem die ArrayList durchlaufen wird.
/* Programm: Kontoverwaltung
Klasse: SparKonto.java
*/
package kontoverwaltung;
public class SparKonto {
...
1
private ArrayList<KontoBewegung> transAktionen =
new ArrayList<KontoBewegung>();
...
© C. Endreß
6/7
10/2008
12. Objekte und Klassen – Dynamische Abläufe
public boolean auszahlen(double betrag) {
if (this.getKontoStand() – betrag >= 0) {
this.addKontoBewegung(-betrag));
return true;
}
else
return false;
}
2
3
private void addKontoBewegung(double betrag) {
this.transaktionen.add(new KontoBewegung(betrag));
}
}
Erläuterungen
1
Deklaration einer ArrayList für Objekte der Klasse Kontobewegung.
2
Eine Auszahlung erfolgt nur, wenn der Kontostand größer als der Auszahlungsbetrag ist. Es wird
dann ein mit der Methode addKontoBewegung() ein neues KontoBewegung-Objekt der
ArrayList transAktionen hinzugefügt. Um die Kontobewegung als Auszahlung zu
kennzeichnen, erhält der Betrag ein negatives Vorzeichen, indem die Variable betrag negiert
wird. Der Kontostand ist nach der vorangegangenen Modellierung (siehe Klassendiagramm) ein
abgeleitetes Attribut. Er wird stets aktuell in der Methode getKontoStand() aus den
Kontobewegungen errechnet.
3
Die Methode addKontoBewegung() ist mit der Sichtbarkeit private versehen. Dadurch wird
sichergestellt, dass eine Kontobewegung nur innerhalb der der Klasse SparKonto im Rahmen
der Methoden einzahlen() und auszahlen() erzeugt werden kann.
/* Programm: Kontoverwaltung
Klasse: KontoBewegung.java
*/
package kontoverwaltung;
import java.util.GregorianCalendar;
public class KontoBewegung {
private double betrag;
private GregorianCalendar datum;
public KontoBewegung(double betrag) {
this.betrag = betrag;
// speichert das aktuelle Systemdatum
datum = new GregorianCalendar();
}
...
}
© C. Endreß
7/7
10/2008
13. Objekte und Klassen – Interfaces
13. Objekte und Klassen – Interfaces
Lernziele
☺ Syntax und Semantik von Java-Interfaces anhand von Beispielen erklären können.
13.1 Interfaces
In der objektorientierten Software-Entwicklung gibt es neben Klassen noch Schnittstellen. Der Begriff
wird nicht einheitlich verwendet. In der Regel definieren Schnittstellen Dienstleistungen für Anwender,
d.h. für aufrufende Klassen, ohne etwas über die Implementierung der Dienstleistung festzulegen. Es
werden Operationssignaturen bereitgestellt, die das „Was“ aber nicht das „Wie“ festlegen. Eine
Schnittstelle besteht also im Allgemeinen nur aus Operationssignaturen, d.h. sie besitzt keine
Operationsrümpfe und keine Attribute.
Schnittstellen werden in Java Interfaces genannt.
Ein Interface beschreibt Funktionalitäten aber stellt keinerlei Implementierung der Funktionalität zur
Verfügung (funktionale Abstraktion).
Ein Interface ist ähnlich wie eine Klasse aufgebaut, enthält jedoch weder Attribute noch
Methodenimplementierungen. Es werden nur die Signaturen (Methodenkopf mit Parameterliste) der
Methoden angegeben, und es dürfen Konstanten deklariert werden.
Sollen die Funktionalitäten, die ein Interface beschreibt, von einer Klasse zur Verfügung gestellt werden,
muss diese das Interface implementieren, d.h. die Funktionen „ausformulieren“.
13.1.1
Interfaces definieren
Regeln für die Definition eines Interface:
In Java wird ein Interface mit dem Schlüsselwort interface deklariert.
Alle Methoden müssen mit dem Modifikator public deklariert werden.
Die Methoden dürfen keinen Anweisungsblock (Rumpf) enthalten.
Es dürfen nur unveränderbare Klassenattribute (Konstanten) deklariert werden, die unmittelbar mit
einem Wert zu initialisieren sind. Diese Klassenattribute sind implizit public, final und static.
Ein Interface hat keine Konstruktoren, weil es nicht instanziiert werden kann.
13.1.2
Interfaces verwenden
Eine Klasse kann beliebig viele Interfaces implementieren. Die Implementierung erfolgt mit dem
Schlüsselwort implements. Auf diese Weise kann in Java eine Form von Mehrfachvererbung
realisiert werden.
Jede Klasse, die ein Interface implementiert, muss alle Methoden des Interface implementieren. Diese
Methoden müssen in der Klasse als public deklariert werden. Außerdem muss die Signatur der
C. Endreß
1/8
02/2006
13. Objekte und Klassen – Interfaces
implementierenden Methode exakt mit der Signatur übereinstimmen, die in der Interface-Definition
spezifiziert ist.
Implementiert eine Klasse die Methoden eines Interface nicht vollständig, so muss diese Klasse als
abstrakte Klasse deklariert werden.
Es ist erlaubt und üblich, dass Klassen, die Interfaces implementieren, eigene zusätzliche Operationen
definieren.
Analog zu Referenz-Variablen auf Objekte können auch Referenz-Variablen von Interface-Typen
deklariert werden.
Eine Interface-Referenz kennt nur die Operationen, die im Interface deklariert sind.
Wenn eine Klasse ein Interface implementiert, können Objekte dieser Klasse an Variablen des InterfaceTyps zugewiesen werden.
In einem Interface können Konstanten definiert werden, die implizit sowohl static als auch final
deklariert sind. Jede Klasse, die das Interface implementiert, erbt diese Konstanten und kann sie
verwenden, als wären sie direkt in der Klasse definiert. Es ist nicht notwendig, den Namen des Interface
voranzustellen oder irgendeine Art von Implementation der Konstanten zu schreiben.
Nützlich ist ein Interface immer dann, wenn Eigenschaften einer Klasse beschrieben werden sollen, die
nicht direkt in seiner normalen Vererbungshierarchie abgebildet werden können.
Beispiel
Die bereits bekannte Kontoverwaltung soll zu einer Verwaltung für Kapitalanlagen erweitert werden. Zur
Vereinfachung betrachten wir in diesem Beispiel nur Sparkonten und Sparbriefe. Da ein Sparbrief kein Konto
im üblichen Sinn ist, wird die Klasse SparBrief nicht von Konto abgeleitet. Die neuen Anforderungen
werden in einem Pflichtenheft formuliert:
/1/
Neben Sparkonten sollen als neue Anlageart auch Sparbriefe verwaltet werden.
/2/
Sparbriefe haben einen Zinssatz, einen Nennwert und eine Laufzeit. Sie besitzen außerdem eine
Vertragsnummer. Alle Attribute werden bei Vertragsabschluss festgelegt und sind nicht änderbar.
/3/
Ein- und Auszahlungen sind bei Sparbriefen nicht möglich.
/4/
Die Zinsen für Sparbriefe und Sparkonten werden jährlich berechnet.
/5/
Kapitaleinkünfte sind steuerpflichtig. Auf Zinserträge von Sparbriefen und Sparkonten wird eine 30prozentige Kapitalertragssteuer erhoben. Es sind Sparerfreibeträge zu berücksichtigen.
/6/
Alle Daten müssen einzeln gelesen werden können.
C. Endreß
2/8
02/2006
13. Objekte und Klassen – Interfaces
Das Pflichtenheft fordert für Objekte beider Klassen die Berechnung von Zinsen und Steuern. Da die beiden
Klassen nicht in einer Vererbungshierarchie stehen, definieren wir ein Interface KapitalAnlage, das die
gemeinsamen Funktionalitäten berechneZinsen und berechneSteuern beschreibt und die
Kapitalertragssteuer als Konstante deklariert. Die Klassen SparKonto und SparBrief implementieren das
Interface. Das folgende UML-Diagramm zeigt die Klassenhierarchie.
«interface»
KapitalAnlage
Konto
# kontoNr: int
+ KAPITALERTRAGSSTEUER: double
# kontoStand: double
# habenZins: double
+ berechneZinsen()
+ berechneSteuern()
+ Konto()
+ einzahlen()
+ auszahlen()
+ getKontoNr()
+ getKontoStand()
SparBrief
+ getHabenZins()
- zinsSatz: double
- nennWert: double
- laufzeit: int
- vertragsNr: int
SparKonto
- freiStellung: double
+ SparBrief()
+ berechneZinsen()
- mindestEinlage: double
- freiStellung: double
+ berechneSteuern()
+ SparKonto()
+ getLaufzeit()
+ auszahlen()
+ getNennWert()
+ berechneZinsen()
+ getVertragsNr()
+ berechneSteuern()
+ getZinsSatz()
+ getMindestEinlage()
+ getFreiStellung()
+ getFreiStellung()
+ setFreiStellung()
+ setFreiStellung()
Java:
/* Interface KapitalAnlage:
Demonstriert die Definition eines Java-Interfaces mit einem
Klassenattribut und zwei Methoden-Signaturen
*/
public interface KapitalAnlage {
// Klassenattribut
double KAPITALERTRAGSSTEUER = 30.0;
1
2
// Deklaration der Methoden
public double berechneZinsen(int tage);
public double berechneSteuern(int tage);
3
}
Erläuterungen
1
Durch das Schlüsselwort interface wird KapitalAnlage als Java-Interface deklariert.
2
Deklaration eines Klassenattributs. Alle Klassen, die das Interface KapitalAnlage implementieren,
erben das Attribut. Das Attribut wird implizit zu einer Konstanten, auch wenn das Schlüsselwort
final, mit dem Konstanten üblicherweise deklariert werden, nicht explizit verwendet wird. Der
C. Endreß
3/8
02/2006
13. Objekte und Klassen – Interfaces
Wert von KAPITALERTRAGSSTEUER kann daher von den implementierenden Klassen nicht mehr
verändert werden.
3
Bei der Methodendeklaration wird kein Methodenrumpf (auch nicht { }) angegeben, sondern nur ein
abschließendes Semikolon.
/* Klasse SparKonto:
SparKonto ist eine Unterklasse von Konto
und implementiert das Interface KapitalAnlage.
Das Listing zeigt nur die Ergänzungen des Quellcodes gegenüber
dem bekannten Quellcode von SparKonto.
*/
public class SparKonto extends Konto implements KapitalAnlage {
1
2
private double freiStellung;
. . .
3
public double berechneZinsen(int tage) {
double zinsen = 0.0;
double zinsSatz = getHabenZins();
double kapital = getKontoStand();
if (tage > 0)
zinsen = kapital * (zinsSatz / 100) * tage / 360;
return zinsen;
}
4
public double berechneSteuern(int tage) {
double steuer = 0.0;
double freiBetrag = getFreiStellung();
double zinsen = berechneZinsen(tage);
if (zinsen > freiBetrag)
steuer = (zinsen - freiBetrag) * KAPITALERTRAGSSTEUER / 100;
5
return steuer;
}
6
public double getFreiStellung() {
return freiStellung;
}
7
public void setFreiStellung(double betrag) {
freiStellung = betrag;
}
. . .
}
Erläuterungen
1
Die Klasse SparKonto wird als Unterklasse von Konto deklariert (extends Konto) und
implementiert das Interface KapitalAnlage (implements KapitalAnlage). Die Klasse
SparKonto erbt somit die Attribute und Methoden der Oberklasse Konto sowie die Konstanten und
Methoden des Interface KapitalAnlage. Die Methoden des Interface sind abstrakt und müssen
von der Unterklasse SparKonto ebenso implementiert werden wie die abstrakten Methoden der
Oberklasse Konto.
2
Deklaration des Attributs für den Sparerfreibetrag.
C. Endreß
4/8
02/2006
13. Objekte und Klassen – Interfaces
3
Die Methode berechneZinsen() wurde im Interface KapitalAnlage deklariert und wird an
dieser Stelle von der Klasse SparKonto implementiert. Der Modifizierer muss public sein. Die
Methoden-Signatur der implementierenden Methode muss mit der Methoden-Signatur des Interfaces
übereinstimmen.
4
Die Methode berechneSteuern() wurde im Interface KapitalAnlage deklariert und wird an
dieser Stelle von der Klasse SparKonto implementiert. Es gelten die bekannten Regeln.
5
Die Methode berechneSteuern() kann direkt auf die Konstante KAPITALERTRAGSSTEUER
zugreifen, weil SparKonto diese Konstante vom Interface KapitalAnlage geerbt hat.
6
Methode zum Lesen des Attributwerts von freiStellung.
7
Methode zum Schreiben des Attributwerts von freiStellung.
1
2
3
4
/* Klasse SparBrief:
SparBrief implementiert das Interface KapitalAnlage.
*/
public class SparBrief implements KapitalAnlage {
// Attribute
private double zinsSatz;
private double nennWert;
private int laufzeit;
private int vertragsNr;
private double freiStellung;
// Konstruktor
public SparBrief(int vertragsNr, double zinsSatz, double betrag, int
jahre) {
this.vertragsNr = vertragsNr;
this.zinsSatz = zinsSatz;
nennWert = betrag;
laufzeit = jahre * 360;
freiStellung = 0.0;
}
public double berechneZinsen(int tage) {
double zinsen = 0.0;
double zinsSatz = getZinsSatz();
double kapital = getNennWert();
if (tage > 0 && tage <= laufzeit)
zinsen = kapital * (zinsSatz / 100) * tage / 360;
return zinsen;
}
5
public double berechneSteuern(int tage) {
double steuer = 0.0;
double freiBetrag = getFreiStellung();
double zinsen = berechneZinsen(tage);
if (zinsen > freiBetrag)
steuer = (zinsen - freiBetrag) * KAPITALERTRAGSSTEUER / 100;
6
return steuer;
}
7
C. Endreß
public int getLaufzeit() {
return laufzeit;
}
5/8
02/2006
13. Objekte und Klassen – Interfaces
public double getNennWert() {
return nennWert;
}
public int getVertragsNr() {
return vertragsNr;
}
public double getZinsSatz() {
return zinsSatz;
}
public double getFreiStellung() {
return freiStellung;
}
public void setFreiStellung(double betrag) {
freiStellung = betrag;
}
}
Erläuterungen
1
Die Klasse SparBrief implementiert das Interface KapitalAnlage (implements
KapitalAnlage). Sie erbt damit alle Konstanten und Methoden des Interface. Da die Methoden
des Interface abstrakt sind, muss die Klasse SparBrief diese implementieren.
2
Deklaration der Attribute
3
Gemäß Anforderung des Pflichtenhefts (/2/) werden vom Konstruktor die Attribute initialisiert. setMethoden sind in der Klasse nicht vorgesehen, da die Attributwerte während der Laufzeit des
Sparbriefs laut Pflichtenheft (/2/) nicht änderbar sein sollen.
4
Die Methode berechneZinsen() wurde im Interface KapitalAnlage deklariert und wird an
dieser Stelle von der Klasse SparBrief implementiert. Der Modifizierer muss public sein. Die
Methoden-Signatur der implementierenden Methode muss mit der Methoden-Signatur des Interfaces
übereinstimmen.
5
Die Methode berechneSteuern() wurde im Interface KapitalAnlage deklariert und wird an
dieser Stelle von der Klasse SparBrief implementiert. Es gelten die bekannten Regeln.
6
Die Methode berechneSteuern() kann direkt auf die Konstante KAPITALERTRAGSSTEUER
zugreifen, weil SparBrief diese Konstante vom Interface KapitalAnlage geerbt hat.
7
Deklarationen der get-Methoden zum Lesen der Attributwerte.
/* Programm:
Testrahmen für die Klassen SparKonto und SparBrief
*/
public class Anwendung {
1
2
3
4
C. Endreß
static void druckeZinsen(KapitalAnlage anlage, int tage){
Console.println("Zeitraum: " + tage + " Tage");
Console.println("Zinsen : " + anlage.berechneZinsen(tage));
}
public static void main(String[] args) {
// Deklaration der Objekt-Variablen
SparKonto einSparKonto = new SparKonto(4711);
einSparKonto.einzahlen(5000);
6/8
02/2006
13. Objekte und Klassen – Interfaces
5
6
SparBrief einSparBrief = new SparBrief(1234, 4.0, 5000, 4);
einSparBrief.setFreiStellung(1000);
Console.println("Sparkonto #" + einSparKonto.getKontoNr());
druckeZinsen(einSparKonto, 360);
Console.println("Kapitalertragssteuer: "
+ einSparKonto.berechneSteuern(360));
Console.println("\nSparbrief #" + einSparBrief.getVertragsNr());
druckeZinsen(einSparBrief, 360);
Console.println("Kapitalertragssteuer: "
+ einSparBrief.berechneSteuern(360));
7
8
9
10
}
}
Konsolenausgabe
Sparkonto #4711
Zeitraum: 360 Tage
Zinsen : 75.0
Kapitalertragssteuer: 22.5
Sparbrief #1234
Zeitraum: 360 Tage
Zinsen : 200.0
Kapitalertragssteuer: 0.0
Erläuterungen
1
Deklaration Methode druckeZinsen() zur Ausgabe des Zinsertrags. Der Parameter anlage der
Methode ist eine Variable vom Typ des Interface KapitalAnlage. Diesem Parameter dürfen
Objekte aller Klasse übergeben werden, die das Interface KapitalAnlage implementierten – in
diesem Fall die Klassen SparBrief und SparKonto. Von welcher Klasse das übergebene Objekt
ist, entscheidet sich erst zur Laufzeit (dynamisches Binden).
2
Aufruf der polymorphen Methode berechneZinsen(). Alle Klassen, die KapitalAnlage
implementieren, müssen die Methoden berechneZinsen() implementieren. Die Art der
Implementierung hängt von der Klasse (Polymorphie).
3
Deklaration der Referenz-Variable einSparKonto und Erzeugung eines Objekts der Klasse
SparKonto.
4
Aufruf der Instanz-Methode einzahlen().
5
Deklaration der Referenz-Variable einSparBrief und Erzeugung eines Objekts der Klasse
SparBrief.
6
Aufruf der Instanz-Methode setFreiStellung().
7
In der Methode druckeZinsen() wird dem Parameter anlage vom Interface-Typ
KapitalAnlage das Objekt einSparKonto der Klasse SparKonto übergeben. Dies ist nur
möglich, weil die Klasse SparKonto das Interface KapitalAnlage implementiert und damit alle
ihre Objekte kompatibel zu Interface-Variablen von KapitalAnlage sind.
8
Aufruf der polymorphen Methode berechneSteuern(), die vom Interface KapitalAnlage
deklariert und in der Klasse SparKonto implementiert wird.
9
In der Methode druckeZinsen() wird dem Parameter anlage vom Interface-Typ
KapitalAnlage das Objekt einSparBrief der Klasse SparBrief übergeben. Dies ist nur
möglich, weil die Klasse SparBrief das Interface KapitalAnlage implementiert und damit alle
ihre Objekte kompatibel zu Interface-Variablen von KapitalAnlage sind.
10 Aufruf der polymorphen Methode berechneSteuern(), die vom Interface KapitalAnlage
deklariert und in der Klasse SparBrief implementiert wird.
C. Endreß
7/8
02/2006
13. Objekte und Klassen – Interfaces
13.1.3
Interfaces und Abstrakte Klassen
Ein Interface enthält ausschließlich abstrakte Methoden und stellt damit eine Sonderform einer
abstrakten Klasse dar.
Eine abstrakte Klasse muss hingegen nicht vollständig abstrakt sein. Sie kann Teile einer
Implementierung enthalten, von der erbende Klassen Gebrauch machen können.
In Java kann eine Unterklasse nur von einer Oberklasse erben (Einfachvererbung). Allerdings kann eine
Klasse beliebig viele Interfaces implementieren, wodurch eine Art Mehrfachvererbung realisiert werden
kann.
Ein wichtiger Unterschied zwischen Interfaces und abstrakten Klassen hängt mit der Kompatibilität
zusammen. Wird ein Interface als Bestandteil einer öffentlichen Schnittstelle definiert und später eine
neue Methode zu diesem Interface hinzugefügt, so werden alle Klassen funktionsunfähig, die frühere
Versionen dieses Interface implementiert haben, es sei denn, Sie ergänzen in allen implementierenden
Klassen die neue Methodenimplementierung. Bei einer abstrakten Klasse ist es dagegen möglich nichtabstrakte Methoden hinzuzufügen, ohne existierende abgeleitete Klassen modifizieren zu müssen.
13.2 Zusammenfassung
Das Schnittstellen-Konzept von Java erlaubt es, Konstanten und abstrakte Methoden zu einem
Interface (Schnittstelle) zu bündeln.
Eine Klasse kann mit dem Schlüsselwort implements beliebig viele Schnittstellen implementieren. Die
Klasse erbt dabei alle Konstanten und abstrakten Operationen des Interface.
Variablen können vom Typ eines Interface sein. In diesem Fall können sie auf Objekte verweisen, die
dieses Interface oder ein daraus abgeleitetes Interface implementieren.
Interface-Variablen können ihrerseits Variablen zugewiesen werden, die zu Klassen gehören, die das
Interface implementieren.
C. Endreß
8/8
02/2006
14. Graphische Oberflächen
14. Graphische Oberflächen
Lernziele
☺ Die Konzepte graphischer Oberflächen in Java verstehen .
☺ Das Konzept der Ereignisbehandlung verstehen und anwenden können.
☺ Grundelemente graphischer Oberflächen kennen.
☺ Graphische Benutzungsoberflächen mit Swing erstellen können.
14.1 Einführung
Die graphischen Benutzungsoberflächen, über die ein Benutzer mit einer Anwendungssoftware interagiert
und kommuniziert, bezeichnet man als Graphical User Interface, abgekürzt GUI.
Java stellt für die GUI-Programmierung entsprechende Klassen zur Verfügung, die seit dem JDK 1.2 unter
dem Oberbegriff Java Foundation Classes (JFC) zusammen gefasst werden und in mehrere Pakete aufgeteilt
sind:
AWT (Abstract Windowing Toolkit):
Diese Klassenbibliothek heißt „abstrakt”, weil sich der Programmierer keine Gedanken darüber machen
muss, wie die Elemente der Oberfläche, z.B. Eingabefelder und Druckknöpfe auf einem konkreten
System umgesetzt werden. Alle Interaktionselemente werden vom darunter liegenden Betriebssystem
zur Verfügung gestellt. Man nennt diese Vorgehensweise Peer-Ansatz, weil die AWT-Komponenten alle
auszuführenden Aktionen an plattformspezifische GUI-Objekte, Peers genannt, weiterreichen.
Komponenten, die solche Peer-Objekte benötigen, werden als heavyweight (schwergewichtig)
bezeichnet. Sie sehen auf unterschiedlichen Betriebssystemen wie z.B. Windows oder Linux auch
unterschiedlich aus.
Infolge dieses Prinzips kann das AWT nur diejenigen GUI-Funktionalitäten bereitstellen, die auf allen
unterstützen Plattformen verfügbar sind.
Swing
Die Nachteile der AWT-Klassen haben dafür gesorgt, dass mit der Entwicklung der Swing-Klassen ein
anderer Weg eingeschlagen wurde. Fast alle Swing-Komponenten sind vollständig in Java geschrieben
und werden deshalb als lightweight (leichtgewichtig) bezeichnet. Nur wenige Komponenten benutzen
noch in kleinem Ausmaß plattformspezifische GUI-Objekte. Form und Funktion der Komponenten sind
somit nicht mehr an das Betriebssystem gebunden, auf dem das Programm ausgeführt wird. Die
Oberfläche kann plattformunabhängig vollständig selbst gestaltet und auch noch zur Laufzeit des
Programms im look and feel verändert werden.
Swing bietet wesentlich mehr Möglichkeiten zur Oberflächengestaltung und ist damit flexibler und
effizienter als AWT.
© C. Endreß
1 / 33
02/2006
14. Graphische Oberflächen
14.2 Ein erstes Beispiel
In einem ersten Beispiel soll mit Swing zunächst ein leeres
Fenster mit der Titelzeile „Mein erstes Swing-Fenster“ erzeugt
werden.
1
/* Programm: FensterOhneInhalt.java
* Erzeugt ein einfaches Swing-Fenster auf dem Bildschirm
*/
import javax.swing.*;
public class FensterOhneInhalt {
public static void main(String[] args) {
// Fenster-Objekt erzeugen
JFrame fenster = new JFrame();
2
// Attribute des Fensters setzen
fenster.setTitle("Mein erstes Swing-Fenster");
fenster.setSize(300, 150);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
3
4
5
6
}
}
Erläuterungen
1
Importieren des swing-Pakets, das die swing-Klassen für die graphischen Oberflächen enthält.
2
Die Klasse JFrame ist die wichtigste Hauptfensterklasse in Swing. Mit Hilfe des Konstruktors der
Klasse JFrame wird ein leeres Anwendungsfenster namens fenster generiert.
3
Das JFrame-Objekt fenster besitzt verschiedene Attribute, die mit seinen set-Methoden
angepasst werden. Die Methode setTitle legt den Text fest, der in der Rahmenleiste des
Fensters als Fenster-Titel angezeigt wird.
4
Die Methode setSize bestimmt die Breite (300 Pixel) und die Höhe (150 Pixel) des Fensters.
5
Der Status des Fenster wird mit der Methode setVisible auf „sichtbar“ gesetzt, um das
Fenster auf dem Bildschirm erscheinen zu lassen. Standardmäßig sind neu erzeugte Fenster der
Klasse JFrame nicht sichtbar.
6
Die Instanzmethode setDefaultCloseOperation des JFrame-Objekts legt fest, wie das
Fenster auf Betätigen des Schließen-Symbols
bzw. der Tastenkombination Alt-F4 reagieren
soll. Durch Angabe der Konstanten EXIT_ON_CLOSE wird mit dem Schließen des Fenster auch
das Programm beendet.
In älteren Programmen kann anstelle der Methode setDefaultCloseOperation ein
sogenannter WindowsListener als Routine zur Ereignisbehandlung verwendet werden, der alle
Aktionen ausführt, die mit dem Schließen des Fensters verbunden sind. Allerdings wird der
Quellcode durch den WindowsListener nur unnötig aufgebläht, wenn mit dem Schließen des
Fensters lediglich das Programm beendet wird. Falls jedoch mit dem Beenden des Programms
weitere Aktionen wie beispielsweise das Speichern von Daten verbunden sind, wird eine
Ereignisbehandlung unumgänglich. (Dazu später mehr)
© C. Endreß
2 / 33
02/2006
14. Graphische Oberflächen
fenster.addWindowListener( new WindowAdapter() {
public void windowClosing( WindowEvent e ) {
System.exit(0);
}
});
Wenn wir das Programm ausführen, öffnet sich das Windows-Fenster, das sich in der gewohnten Weise mit
der Maus bewegen, vergrößern und verkleinern lässt. Beendet wird das Programm über die bekannten
oder Tastenkombination Alt-F4 ).
Mechanismen des Betriebssystems (Schließen-Symbol
Warum wird das Programm nicht, so wie wir es von Konsolenprogrammen kennen, unmittelbar nach
Ausführung der letzten Anweisung der main-Methode beendet?
Durch das Erzeugen des JFrame-Objekts wird ein zusätzlicher Programmfluss für das Fenster gestartet,
der parallel zum Programmfluss der main-Methode abgearbeitet wird. Einen solchen parallelen
Programmfluss bezeichnet man als Thread (deutsch: Faden). Ein Programm kann aus vielen Threads
bestehen und ist erst dann beendet, wenn alle Threads beendet sind. Das Programm
FensterOhneInhalt terminiert also erst, wenn der Thread, der für die Fensterdarstellung zuständig
ist, beendet ist.
Wir wollen nun noch eine kleine Veränderung an der Klasse FensterOhneInhalt vornehmen, so dass
diese für zukünftige Erweiterungen geeignet ist. Die neue Variante sieht dann folgendermaßen aus:
/* Programm: FensterOhneInhalt.java
* Erzeugt ein einfaches Swing-Fenster auf dem Bildschirm
*/
import javax.swing.*;
1
2
public class FensterOhneInhalt extends JFrame {
// Konstruktor
public FensterOhneInhalt(){
// Hier werden später Komponenten hinzugefügt
}
public static void main(String[] args) {
// Fenster-Objekt erzeugen
FensterOhneInhalt fenster = new FensterOhneInhalt();
3
// Attribute des Fensters setzen
fenster.setTitle("Mein erstes Swing-Fenster");
fenster.setSize(300, 150);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Die Klasse FensterOhneInhalt erbt jetzt von JFrame. Wir definieren auf diese Weise unsere
eigene Fenster-Klasse.
2
Die Klasse ist mit einem Konstruktor ausgestattet, der zunächst noch einen leeren Rumpf
aufweist. Dort werden später die Komponenten des Fensters eingefügt.
3
In der main-Methode arbeiten wir nicht mehr mit einem JFrame-Objekt, sondern mit einem
Objekt der selbstdefinierten Klasse FensterOhneInhalt. Grundsätzlich könnte die mainMethode auch in einer anderen Klasse definiert sein. Der Einfachheit halber ist sie noch in die
Klasse FensterOhneInhalt gepackt.
© C. Endreß
3 / 33
02/2006
14. Graphische Oberflächen
14.3 Grundsätzliches zum Aufbau graphischer Oberflächen
Der Aufbau einer graphischen Benutzungsoberfläche erfolgt nach einem hierarchischen Baukastenprinzip.
Aus einer vorgegebenen Menge sogenannter Komponenten wählt man Bausteine für die Oberfläche aus.
Diese Bausteine werden in Container-Komponenten angeordnet, mit denen ein Basis-Container wie z.B. ein
Fenster bestückt wird.
In der Java-Klassenbibliothek finden sich alle benötigten Klassen zum Aufbau einer graphischen Oberfläche:
Grundkomponenten: einfache Oberflächenelemente wie z.B. Beschriftungen (Labels), Knöpfe
(Buttons), Auswahlfelder oder Klapptafeln.
Container: Komponenten, die selbst wieder Komponenten enthalten können.
Aufbau von Swing-Fenstern
Ein bedeutender Unterschied zwischen AWT- und Swing-Fenstern besteht in ihrer Komponentenstruktur.
Während die Komponenten eines AWT-Fensters direkt auf dem Fenster platziert werden, besitzt ein
Swing-Hauptfenster eine einzige Hauptkomponente, die alle anderen Komponenten aufnimmt.
Die Hauptkomponente eines Swing-Fensters wird als RootPane (Wurzelfeld, Basisfeld) bezeichnet und ist
vom Typ JRootPane.
Eine RootPane enthält folgende Komponenten:
eine normalerweise unsichtbare GlassPane,
eine sichtbare LayeredPane mit
einem optionalen Menübalken (MenuBar) und
einem Inhaltsfeld (ContentPane)
MenuBar
ContentPane
GlassPane
LayeredPane
Beim Anlegen eines Fensters wird die RootPane mit den darin enthaltenen Schichten GlassPane,
LayeredPane und ContentPane automatisch erzeugt. Die Menüleiste bleibt standardmäßig leer.
Alle Komponenten des Anwendungsfensters werden zur ContentPane, dem Inhaltsfeld, hinzugefügt.
Auf die Bestandteile eines Swing-Fensters kann mit den folgenden Operationen zugegriffen werden:
public JLayeredPane getLayeredPane()
liefert die LayeredPane, die von der RootPane
benutzt wird
public JMenuBar getJMenuBar()
liefert den Menübalken der LayeredPane
public Container getContentPane()
liefert die das Inhaltsfeld
public Component getGlassPane()
liefert die aktuelle GlassPane
© C. Endreß
4 / 33
02/2006
14. Graphische Oberflächen
14.4 Ein Fenster mit Text
In folgenden Beispiel wird ein leeres Fenster mit einem kleinen
Text gefüllt. Dazu verwenden wird die Swing-Komponente
JLabel, die einen Schriftzug oder ein Icon enthalten kann.
1
2
3
4
5
6
7
/* Programm: FensterMitText.java
* Erzeugt ein einfaches Swing-Fenster mit Text-Label
*/
import java.awt.*;
import javax.swing.*;
public class FensterMitText extends JFrame {
Container c;
JLabel beschriftung;
// Konstruktor
public FensterMitText(){
c = getContentPane();
c.setLayout(new FlowLayout());
beschriftung = new JLabel("Label-Text im Fenster");
c.add(beschriftung);
}
8
public static void main(String[] args) {
// Fenster-Objekt erzeugen
FensterMitText fenster = new FensterMitText();
// Attribute des Fensters setzen
fenster.setTitle("Fenster mit Text-Label");
fenster.setSize(300, 150);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Die Klasse Container, die für die ContentPane des Fensters benötigt wird, ist eine Klasse aus
dem Package java.awt, das deshalb importiert wird.
2
Zunächst vereinbaren wir in der Klasse FensterMitText zwei Instanzvariablen, die im
Konstruktor initialisiert werden. Die Variable c vom Typ Container benötigen wir, um eine
Referenz auf den Container (in diesem Fall die ContentPane) unseres Fenster-Objekts zu
speichern. Denn wir dürfen, wie oben bereits erwähnt, keine Komponenten direkt einem
JFrame-Objekt hinzufügen, sondern müssen diese in das Inhaltsfeld des Fensters einfügen.
3
Die Variable beschriftung ist eine Referenz auf ein Objekt von Typ JLabel. Wir benötigen
sie, um den Text darzustellen.
4
Die Referenz auf den Container des Fensters wird mit der Methode getContentPane
festgelegt. Das Fenster erbt diese Methode von der Klasse JFrame.
5
Mit der Methode setLayout des Container-Objekts legen wir das Layout des Containers fest.
Das Layout FlowLayout legt ein „fließendes“ Layout fest, das es dem Inhaltsfeld erlaubt, die
Komponenten abhängig von der aktuellen Größe des Fensters fließend anzuordnen.
© C. Endreß
5 / 33
02/2006
14. Graphische Oberflächen
6
Erzeugen des JLabel-Objekts beschriftung mit dem Textinhalt, der dem Konstruktor
übergeben wird.
7
Die Komponente beschriftung muss der ContentPane des Fensters hinzugefügt werden.
Dieses erfolgt, indem wir der Instanzmethode add des Containers c die Objekt-Variable
beschriftung als Parameter übergeben.
8
Die main-Methode ist gegenüber dem Beispiel FensterOhneInhalt nahezu unverändert –
abgesehen von der Erzeugung des Fensterobjekts, das jetzt die Klasse FensterMitText
verwendet.
14.5 AWT- und Swing-Klassenbibliothek im Kurzüberblick
Die folgende Abbildung stellt auszugsweise die Hierarchie der wichtigsten AWT- und Swing-Klassen dar. Die
Namen aller Swing-Klassen beginnen mit einem J.
Component
Container
Panel
Windows
Applet
Dialog
Frame
JDialog
JFrame
JApplet
JWindows
Top-Level-Container
JComponent
JLabel
JList
JComboBox
JPanel
weitere SwingKomponenten
An oberster Stelle steht die abstrakte Klasse Component, die als Oberklasse aller AWT- und SwingKlassen Basismethoden zur Verfügung stellt, die allen graphischen Komponenten gemeinsam sind. Von
Component abgeleitet ist die Klasse Container, die Basisklasse für alle Container-Klassen ist.
© C. Endreß
6 / 33
02/2006
14. Graphische Oberflächen
Als Basis einer graphischen Benutzungsoberfläche mit Swing verwendet man einen sogenannten TopLevel-Container, der alle weiteren Komponenten und Untercontainer der Oberfläche aufnimmt.
Top-Level-Container der Swing-Klassen
JFrame
Standardfenster mit Rahmen, Titelleiste und optionalem Menübalken
JWindow
Fenster ohne Rahmen, Titelleiste und Menübalken
JDialog
Modale und nichtmodale Dialoge, die auch als Unterfenster verwendet werden
können
JApplet
Container für Swing-Element in einem Applet
Diese 4 Top-Level-Container sind im Gegensatz zu allen anderen Swing-Klassen nicht leichtgewichtig,
sondern müssen durch das jeweilige Betriebssystem dargestellt werden.
Alle leichtgewichtigen Swing-Komponenten (JLabel, JButton usw.) sind Unterklassen der abstrakten
Klasse JComponent.
Warnung!
Niemals AWT- und Swing-Klassen in einem Fenster mischen, da dies zu unvorhersehbaren Effekten
führen kann!
14.6 Layout-Manager
Layout-Manager legen die Anordnung der verschiedenen Komponenten in einem Container fest.
Dabei verteilen Layout-Manager den Gesamtplatz der Container-Fläche abhängig von den eingepflegten
Komponenten, wobei je nach Layout teilweise Zwischenraum eingefügt wird oder Komponenten in ihrer
Größe angepasst bzw. gar nicht dargestellt werden.
Jeder AWT- und Swing-Container besitzt einen voreingestellten Layout-Manager.
Die Verwendung eines Layout-Managers ist nicht zwingend erforderlich. Wenn Sie dem Layout-Manager
den Wert null zuweisen, können Komponenten durch die Angabe exakter Größen und Positionen in
Containern angeordnet werden. Dazu können Methoden wie setSize und setLocation verwendet
werden. Allerdings lassen sich solche Oberflächen schlechter portieren und anpassen.
Die am häufigsten verwendeten Layout-Manager stellt die folgende Tabelle vor:
Layout-Manager
BorderLayout
© C. Endreß
Beschreibung
Die Containerfläche wird in die fünf
Gebiete „North“, „South“, „East“, „West“
und „Center“ eingeteilt. In jedes dieser
Gebiete kann eine Komponente eingefügt
werden. Die Größe der Komponenten im
Norden und Süden wird durch ihre Höhe,
die der Komponenten im Westen und
Osten durch ihre Breite bestimmt. Die
Größe des zentralen Gebiets kann je nach
Größe des Containers variieren. (DefaultLayout der Klasse JFrame)
7 / 33
North
West
Center
East
South
02/2006
14. Graphische Oberflächen
FlowLayout
Die Komponenten werden „fließend“ von
links nach rechts in Zeilen angeordnet.
Die Zeilen werden von oben nach unten
gefüllt und können linksbündig, zentriert
(standardmäßig)
und
rechtsbündig
angeordnet werden.
Nummer 1 Nummer 2
Nummer 3
Nummer 4
Nummer 5
Nummer 6
GridLayout
null
Erzeugt alle Komponeten in gleicher
Größe und ordnet sie in einem Gitter mit
angegebenen Ausmaßen an. Die Anzahl
der Zeilen und Spalten werden beim
Konstruktoraufruf des Layout-Manager
festgelegt.
Nr.1
Nr. 2
Nr. 3
Nr. 4
Nr. 5
Nr. 6
Nr. 7
Nr. 8
Nr. 9 Nr. 10
Die Komponenten werden nicht vom
Layout-Manager verwaltet. Position
und Größe muss für jede Komponente
explizit mit geeigneten Methoden wie
setLocation,
setSize
oder
setBounds gesetzt werden.
14.7 Einige Grundkomponenten
Alle Grundkomponenten sind Unterklassen der abstrakten Klasse JComponent, die am Anfang der SwingKomponenten-Hierarchie steht. Aufgrund der Mächtigkeit der Swing-Bibliothek können an dieser Stelle nur
einige ausgewählte Komponenten besprochen werden. Die folgende Tabelle liefert einen kurzen Überblick
über wichtige Grundkomponenten.
Schaltflächen und Optionsschalter
JButton
Eine Schaltfläche bzw. ein Taster auf dem Text, ein Bild oder beides angezeigt wird.
JToggleButton
Ein Schalter auf dem Text, ein Bild oder beides angezeigt wird. Der Schalter kann an- oder
ausgeschaltet bzw. selektiert oder nicht selektiert sein. Unterklassen sind JCheckBox und
JRadioButton.
JCheckBox
Ein Schalter für die Auswahl/Anzeige von Optionen, der sich nicht gegenseitig ausschließen
(Mehrfachoptionen).
JRadioButton
Ein Schalter für die Auswahl/Anzeige von Optionen, die sich gegenseitig ausschließen
(Einfachoptionen).
Textanzeige und Textbearbeitung
JLabel
Eine einfache Komponente, um Text, ein Bild oder beides anzuzeigen.
JTextField
Eine Komponente zur Anzeige, Eingabe und Bearbeitung einer einzelnen Textzeile.
JTextArea
Eine Komponente zur Anzeige, Eingabe und Bearbeitung von mehrzeiligem Text.
Einfache Dialoge
JOptionPane
© C. Endreß
Eine komplexe Komponente, mit der einfache und häufig benötigte Dialoge angezeigt
werden können.
8 / 33
02/2006
14. Graphische Oberflächen
Auswahllisten
JComboBox
Eine Kombination aus einem Texteingabefeld und einer Auswahlliste. Der Benutzer kann
einen Wert eingeben oder aus einer Liste auswählen.
JList
Zeigt eine Auswahlliste an. Die Elemente sind üblicherweise Zeichenketten oder Bilder.
Mehrfachauswahl von Elementen ist möglich.
Einfache Container
Ein Container, in den andere Komponenten platziert werden können. Wird meistens mit
einem geeigneten Layout-Manager verwendet.
JPanel
14.7.1
Die Klasse JLabel
Ein Label kann zur Darstellung von Text und Bildern verwendet werden, wobei auch beides kombiniert
werden kann. Für Text und Bild kann die horizontale und vertikale Ausrichtung innerhalb des Labels
festgelegt werden. Für Text ist die standardmäßige horizontale Ausrichtung „linksbündig“, für Bilder ist
sie „zentriert“. Vertikal wird standardmäßig jeweils „zentriert“ ausgerichtet. Es stehen verschiedene
Konstruktoren zur Verfügung, denen sowohl Text als auch Bilder oder ein Bild und Text übergeben
werden können.
Bild hinzufügen:
Einem Label kann man ein Bildobjekt hinzufügen, das das Interface Icon implementiert. In der Regel
geschieht das durch ein Objekt der Klasse ImageIcon. Der Konstruktor
public ImageIcon (String fileName)
erzeugt ein ImageIcon-Objekt aus dem Bild der Datei fileName.
Beispiel:
Das Fenster vom Typ JFrame erhält ein Label mit Bild und
zugehörigem Text.
/* Programm: FensterMitLabel.java
* Erzeugt ein einfaches Swing-Fenster mit einem
* Label, das Text und ein Icon enthält
*/
import java.awt.*;
import javax.swing.*;
1
2
public class FensterMitLabel extends JFrame {
private Container c;
private JLabel einLabel;
3
// Konstruktor
public FensterMitLabel(){
c = getContentPane();
c.setLayout(new BorderLayout());
// Bildobjekt erzeugen
© C. Endreß
9 / 33
02/2006
14. Graphische Oberflächen
4
Icon bild = new ImageIcon("duke_flip.gif");
// Label mit Text und Bild beschriften
einLabel = new JLabel("Let's swing!",bild, JLabel.CENTER);
einLabel.setHorizontalTextPosition(JLabel.CENTER);
einLabel.setVerticalTextPosition(JLabel.BOTTOM);
c.add(einLabel);
5
6
7
}
public static void main(String[] args) {
// Erzeuge ein Fenster-Objekt
FensterMitLabel fenster = new FensterMitLabel();
// Attribute des Fensters setzen
fenster.setTitle("Label mit Bild und Text");
fenster.setSize(300, 150);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Deklaration des Containers für die ContentPane des Fensters.
2
Deklaration des Label-Objekts einLabel vom Typ JLabel.
3
Der Layout-Manager für den Container wird auf BorderLayout gesetzt.
4
Mit dem Konstruktoraufruf der Klasse ImageIcon wird ein Bildobjekt vom Typ Icon erzeugt.
Der Dateiname der Bilddatei wird als String angegeben. Dabei ist zu beachten:
"duke_flip.gif"
Die Bilddatei liegt in demselben Verzeichnis wie das Programm. Funktioniert nicht aus JBuilder
und Eclipse heraus, sondern nur über das Konsolenkommando java
"oberflaechen/duke_flip.gif"
Programmdatei und Bilddatei liegen im Package oberflaechen. Funktioniert nicht aus bei
JBuilder und Eclipse heraus, sondern nur über das Konsolenkommando java
"D:/DukeIcons/duke_flip.gif"
absolute Pfadangabe der Bilddatei, Slashes (/) verwenden
Icon bild = new
ImageIcon(getClass().getResource("/oberflaechen/duke_flip.gif"));
Programmdatei und Bilddatei liegen im Package oberflaechen. Funktioniert auch aus
JBuilder und Eclipse heraus.
5
Das Label wird mit Text und Icon initialisiert, wobei die horizontale Ausrichtung mit der
Konstanten JLabel.CENTER auf zentriert gesetzt wird.
6
Die beiden folgenden Anweisungen positionieren den Text zentriert unter dem Bild.
7
Das Label-Objekt wird der ContentPane des Fensters hinzugefügt.
© C. Endreß
10 / 33
02/2006
14. Graphische Oberflächen
14.7.2
Schaltflächen mit JButton und JToggleButton
Beispiel
Taster 1 ist ein Objekt der Klasse JButton. Schalter 1 und
Schalter 2
sind Objekte der Klasse JToggleButton.
Schalter 2 ist bereits zum Programmstart selektiert, steht also
auf „an“. Der kleine bläuliche Rahmen innerhalb von Taster 1
zeigt an, dass dieser Taster gerade den Fokus besitzt. Er
kann daher auch ohne Maus durch Drücken der Leertaste
betätigt werden. Mit der Tabulator-Taste kann der Fokus an
den nächsten Schalter weitergegeben werden.
Beim Betätigen von Taster 1 verändert sich dessen
Darstellung. Der Hintergrund wird dunkelgrau eingefärbt.
Nach dem Loslassen ist Taster 1 wieder deaktiviert.
Schalter 1 verändert seine Darstellung, sobald er gedrückt
bzw. selektiert wird. Dieser Zustand beleibt erhalten, wenn
der Schalter losgelassen wird. Erneutes Betätigen von
Schalter 1 stellt den ursprünglichen Zustand wieder her.
/* Programm: FensterMitButtons.java
* Erzeugt ein einfaches Swing-Fenster mit
* einem Taster und zwei Schaltern
*/
import java.awt.*;
import javax.swing.*;
1
public class FensterMitButtons extends JFrame {
private Container c;
private JButton button1;
private JToggleButton toBtn1, toBtn2;
// Konstruktor
public FensterMitButtons(){
c = getContentPane();
c.setLayout(new FlowLayout());
2
button1 = new JButton("Taster 1");
toBtn1 = new JToggleButton("Schalter 1");
toBtn2 = new JToggleButton("Schalter 2");
3
button1.setFont(new Font("SansSerif", Font.ITALIC, 24));
toBtn1.setFont(new Font("SansSerif", Font.ITALIC, 24));
toBtn2.setFont(new Font("SansSerif", Font.ITALIC, 24));
© C. Endreß
11 / 33
02/2006
14. Graphische Oberflächen
4
toBtn2.setSelected(true);
5
c.add(button1);
c.add(toBtn1);
c.add(toBtn2);
}
public static void main(String[] args) {
FensterMitButtons fenster = new FensterMitButtons();
fenster.setTitle("Taster und Schalter");
fenster.setSize(300, 150);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Deklaration der Objektvariablen der Schaltflächen (ein Button, zwei Toggle-Buttons)
2
Initialisieren der Schaltflächen: Die Beschriftungen der Schaltflächen wird dem Konstruktor als
Parameter übergeben, kann aber auch nachträglich mit der Instanz-Methode setText zugefügt
werden.
3
Die Beschriftung der Schaltflächen-Objekte wird hier in ihren Eigenschaften verändert.
4
Der Toggle-Button toBtn2 wird vorselektiert. Dadurch ist toBtn2 bereits beim Programmstart
„angeschaltet“.
5
Die Komponenten werden der ContentPane hinzugefügt.
14.7.3
Optionsschalter
JCheckBox
Mit Checkboxes kann eine Auswahl für Mehrfachoptionen
realisiert werden. D.h. es können mehrere Optionen
gleichzeitig selektiert werden. Die Selektion einer
Checkbox wird durch ein Häkchen angezeigt.
/* Programm: CheckBoxes.java
* Erzeugt ein einfaches Swing-Fenster mit
* drei Checkboxes
*/
import java.awt.*;
import javax.swing.*;
1
public class CheckBoxes extends JFrame {
private Container c;
private JCheckBox cbKursiv, cbFett, cbUnterstrichen;
// Konstruktor
public CheckBoxes() {
c = getContentPane();
c.setLayout(new FlowLayout());
2
© C. Endreß
cbKursiv = new JCheckBox("Kursiv");
cbFett = new JCheckBox("Fett");
cbUnterstrichen = new JCheckBox("Unterstrichen");
12 / 33
02/2006
14. Graphische Oberflächen
3
cbFett.setSelected(true);
cbUnterstrichen.setSelected(true);
4
c.add(cbFett);
c.add(cbKursiv);
c.add(cbUnterstrichen);
}
public static void main(String[] args) {
CheckBoxes fenster = new CheckBoxes();
fenster.setTitle("CheckBoxes");
fenster.setSize(300, 100);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Deklaration von drei JCheckBox-Objekten
2
Initialisierung der drei JCheckBox-Objekte: Die Beschriftung der Checkbox wird dem
Konstruktor als Parameter übergeben.
3
Die Checkboxes cbFett und cbUnterstrichen werden mit der Methode setSelected
bereits zum Programmstart selektiert.
Mit der Methode isSelected kann abgefragt werden, ob ein JCheckBox-Objekt selektiert ist.
(Rückgabewert: true = selektiert, false = nicht selektiert).
4
Die JCheckBox-Objekte werden der ContentPane hinzugefügt.
JRadioButton
Radiobuttons können ähnlich wie Checkboxes die Zustände „selektiert“ und „nicht selektiert“ annehmen.
Der „selektiert“-Zustand wird durch einen Punkt gekennzeichnet.
Normalerweise werden JRadiobutton-Objekte mit
einem ButtonGroup-Objekt zu einer Gruppe
zusammengefasst, in der immer höchstens ein
Radiobutton aktiviert sein kann (Einfachoption).
ButtonGroup-Objekte können auch zur Gruppierung
von JToggleButton-Objekten eingesetzt werden.
/* Programm: RadioButtons.java
* Erzeugt ein einfaches Swing-Fenster mit
* drei RadioButtons, die in einer ButtonGroup gruppiert werden.
*/
import java.awt.*;
import javax.swing.*;
1
2
public class RadioButtons extends JFrame {
private Container c;
private JRadioButton rbFestGeld, rbSpar, rbGiro;
private ButtonGroup bgKontoAuswahl;
// Konstruktor
public RadioButtons() {
c = getContentPane();
c.setLayout(new FlowLayout());
© C. Endreß
13 / 33
02/2006
14. Graphische Oberflächen
3
rbGiro = new JRadioButton("Girokonto");
rbSpar = new JRadioButton("Sparkonto");
rbFestGeld = new JRadioButton("Festgeldkonto");
4
rbGiro.setSelected(true);
5
6
bgKontoAuswahl = new ButtonGroup();
bgKontoAuswahl.add(rbGiro);
bgKontoAuswahl.add(rbSpar);
bgKontoAuswahl.add(rbFestGeld);
7
c.add(rbGiro);
c.add(rbSpar);
c.add(rbFestGeld);
}
public static void main(String[] args) {
RadioButtons fenster = new RadioButtons();
fenster.setTitle("RadioButtons");
fenster.setSize(300, 100);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Deklaration der JRadioButton-Objekte
2
Deklaration des ButtonGroup-Objekts bgKontoAuswahl, in dem die drei JRadioButtonObjekte später zusammengefasst werden.
3
Initialisierung der drei JRadioButton-Objekte: Dem JRadioButton-Konstruktor wird die
Beschriftung des jeweiligen JRadioButton-Objekts als Parameter übergeben. Die Beschriftung
kann auch nachträglich mit der Methode setText eingerichtet werden.
4
Das
JRadioButton-Objekt rbGiro wird selektiert. Der Selektionszustand eines
JRadioButton-Objekts kann wie bei JCheckBox-Objekten mit der Methode isSelected
abgefragt werden.
5
Das ButtonGroup-Objekt bgKontoAuswahl wird initialisiert.
6
Die JRadioButton-Objekte werden der ButtonGroup hinzugefügt. Die Zusammenfassung zu
einer ButtonGroup hat nur logische Bedeutung und wirkt sich nicht auf die graphische
Darstellung und Anordnung der Radiobuttons auf der Oberfläche aus.
7
Die JRadioButton-Objekte müssen einzeln dem Oberflächen-Container hinzugefügt werden.
14.7.4
Klassen für die Ein-/Ausgabe von Texten
Java stellt verschiedene Klassen zur Eingabe und Ausgabe von Texten bereit. In der abstrakten Klasse
JTextComponent, von der alle Textkomponenten erben, werden die Basismethoden definiert.
getText()
liefert den kompletten Text der Komponente
getSelectedText()
liefert den gerade markierten Text der Komponente
setText(String s)
setzt den Text der Komponente auf den Inhalt s
setEditable(boolean b)
b = true => Modus „editierbar”
b = false => Modus „nicht editierbar“
© C. Endreß
14 / 33
02/2006
14. Graphische Oberflächen
Die Methode getText() liefert den Text der Komponente als String zurück. Zum Verarbeiten von
Zahlenwerten ist es erforderlich den String mittels sog. Wrapper-Klassen (Integer, Double usw.) in
den gewünschten numerischen Datentyp (int, double usw.) umzuwandeln.
Umwandlung einer Zeichenkette aus einem JTextField-Objekt in eine ganze Zahl:
int ganzeZahl = (Integer.valueOf(eingabe.getText())).intValue();
1
2
3
3
:Integer
2
:String
1
eingabe:JTextField
12
wert = 12
“12“
text = “12“
analog erfolgt die Umwandlung einer Zeichenkette in eine Fließkommazahl:
double dezimalZahl = (Double.valueOf(eingabe.getText())).doubleValue();
Umwandlung einer Zahl in eine Zeichenkette:
String intZahlAlsText = String.valueOf(ganzeZahl);
String doubleZahlAlsText = String.valueOf(doubleZahl);
Mit Objekten der Klassen JTextField und JPasswordField können einzeilige Texte verarbeitet
werden. Während ein JTextField-Objekt die Textzeile lesbar darstellt, wird diese in einem
JPasswordField-Objekt unlesbar mittels einer entsprechenden Anzahl von Ersatz-Zeichen,
sogenannten „Echo-Zeichen“, dargestellt.
setHorizantolAlignment(int alignment)
setzt die horizontale Ausrichtung
Objekte der Klasse JTextArea dienen zur Verarbeitung mehrzeiliger Texte.
getLineCount()
liefert die Anzahl der Zeilen
setLineWrap(boolean wrap)
wrap = true => aktiviert Zeilenumbruch
wrap = false => deaktiviert Zeilenumbruch
setWrapStyleWord(boolean b)
b = true => aktiviert wortweisen Zeilenumbruch
b = false => deaktiviert wortweisen Zeilenumbruch
Falls in einem JTextArea-Objekt so viel Text eingegeben wird, dass die vorgegebene Größe des
Objekts zur Darstellung des Texts nicht ausreicht, wird der überschüssige Text nicht mehr
angezeigt. Um den angezeigten Ausschnitt zu verschieben, bettet man die JTextAreaKomponente in eine JScrollPane-Komponente ein, die einen Schieberegler bereitstellt.
© C. Endreß
15 / 33
02/2006
14. Graphische Oberflächen
Daneben gibt es noch die Klassen JEditorPane und JTextPane, die auch formatierte Texte wie
z.B. HTML-Dokumente verarbeiten können.
14.7.5
Oberflächen strukturieren
Die Klasse JPanel gehört zur Gruppe der Container. Sie kann selbst Komponenten enthalten und dient
hauptsächlich der Strukturierung von Oberflächen.
Als Layout-Manager ist FlowLayout voreingestellt.
Beispiel
Das Beispiel zeigt ein Fenster, dessen Oberfläche mit drei Panel strukturiert wurde, die im Norden, Süden
und Zentrum des JFrames platziert wurden. Die Panels werden randlos dargestellt und sind daher nicht
erkennbar. Die blauen Rahmen sind nachträglich eingefügt und deuten nur die Positionen der Panels an. Mit
der Methode setBorder kann für Panels genauso wie für andere Komponenten auch ein Rand eingestellt
werden.
Panel 1
(FlowLayout)
Panel 2
(FlowLayout)
Panel 3
(GridLayout)
/* Programm: FensterMitPanles.java
* Erzeugt ein Swing-Fenster, das mit
* drei Panels strukturiert wird.
*/
import java.awt.*;
import javax.swing.*;
1
public class FensterMitPanels extends JFrame {
private Container c;
private JPanel jp1, jp2, jp3;
// Konstruktor
public FensterMitPanels() {
c = getContentPane();
2
jp1 = new JPanel();
jp2 = new JPanel();
jp3 = new JPanel(new GridLayout(2, 3));
3
// Vier Tasten in Panel 1 einfuegen
for (int i = 1; i <= 4; i++)
jp1.add(new JButton("Taste " + i));
4
5
// Bildobjekt erzeugen
Icon bild = new
ImageIcon(getClass().getResource("/oberflaechen/duke_flip.gif"));
// Bild dreimal in Panel 2 einfuegen
© C. Endreß
16 / 33
02/2006
14. Graphische Oberflächen
6
for (int i = 0; i < 3; i++)
jp2.add(new JLabel(bild));
// 6 Checkboxes in Panel 3 setzen
for (int i = 1; i <= 6; i++)
jp3.add(new JCheckBox("Auswahl " + i));
7
8
c.add(jp1, BorderLayout.NORTH);
c.add(jp2, BorderLayout.CENTER);
c.add(jp3, BorderLayout.SOUTH);
}
public static void main(String[] args) {
FensterMitPanels fenster = new FensterMitPanels();
fenster.setTitle("Fenster mit Panels");
fenster.setSize(350, 200);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Drei JPanel-Objekte deklarieren.
2
Initialisieren der JPanel-Objekte. Das Standardlayout ist FlowLayout.
3
Das JPanel-Objekt jp3 erhält ein GridLayout mit 2 Zeilen und 3 Spalten. In diesem Panel
werden später 6 Checkboxen untergebracht.
4
4 Schaltflächen von Typ JButton werden dem Panel jp1 hinzugefügt. Wegen des
FlowLayout der JPanel-Komponente erscheinen die Buttons in einer Reihe.
5
Erzeugen eines Icon-Objekts für die Labels des zweiten Panels.
6
Drei JLabel-Objekte werden jeweils mit dem Icon-Objekt bild erzeugt und dem Panel jp2
zugefügt.
7
Für Panel jp3 haben wir ein GridLayout mit 2 Zeilen und 3 Spalten eingestellt. Die 6
JCheckBox-Objekte werden zeilenweise in das Gitter dieses Panels gesetzt.
8
Nachdem die visuellen Komponenten den drei JPanel-Objekten zugefügt wurden, müssen die
Panels noch auf der ContentPane des Fensters angeordnet werden. Die jeweiligen Positionen
werden der add-Methode als Parameter (z.B. BorderLayout.NORTH) übergeben.
14.8 Ereignisverarbeitung
14.8.1
Einführung
Programme mit graphischer Benutzungsoberfläche bedient ein Benutzer durch Tastatureingaben und
Mausklicks. Diese Benutzeraktivitäten lösen Ereignisse aus, die vom jeweiligen Betriebssystem bzw. GUISystem an das Programm weitergegeben werden. Das Programm wird dabei über alle Arten von Ereignissen
und Zustandsänderungen informiert und reagiert in seinem Ablauf darauf.
Beispiele für mögliche Ereignisse bzw. Zustandsänderungen:
Mausklicks, Mausbewegungen
Tastatureingaben
Veränderungen an Größe oder Lage bzw. Schließen von Fenstern
© C. Endreß
17 / 33
02/2006
14. Graphische Oberflächen
Timerintervalle
Ankunft von Datenpaketen in einem Netzwerk
usw.
Programme mit einer graphischen Benutzungsoberfläche sind ereignisgesteuert. Der Programmablauf
wird von Ereignissen und Zustandsänderungen bestimmt, auf die das Programm reagiert.
14.8.2
Ereignisverarbeitung in Java
Zuerst die Theorie ...
Ereignisübermittlung in der „realen Welt“:
Ereignis
Empfänger
Quelle
In Java:
Ereignisquelle (Event-Source): ein Objekt, das ein Ereignis auslöst (z.B. ein Button)
Ereignis (Event): ein Objekt einer Ereignis-Klasse (z.B. ein Mausklick)
Ereignisempfänger/-abhörer (Event-Listener): ein Objekt, das auf Ereignisse reagiert (z.B. eine
bestimmte Aktion startet)
Delegation Event Model:
Ereignisquellen lösen Ereignisse aus. Die Ereignisabhörer werden von der Ereignisquelle über
eingetretene Ereignisse informiert. Damit ein Empfänger tatsächlich Ereignisse von einer Ereignisquelle
empfangen kann, muss er zuvor bei dieser registriert werden.
Eine Ereignisquelle kann i.d.R. an eine beliebige Anzahl von Ereignisabhörern Ereignisse senden.
Ein Ereignisabhörer kann bei beliebig vielen Ereignisquellen angemeldet sein.
Ereignisquelle 1
Ereignisquelle 2
Ereignis
Ereignis
Empfänger 1
Empfänger 2
Ereignis
Ereignisquelle 3
© C. Endreß
Ereignis
18 / 33
Empfänger 3
02/2006
14. Graphische Oberflächen
Beispiel: Button und zugehöriger Ereignisabhörer
ActionEvent
Button
actionPerformed
Ereignisquelle
Ereignis
(Nachricht)
ActionListener
Ereignisabhörer
... dann die Praxis
Beispiel: Farbwechsel
In einer minimalistischen graphischen Oberfläche, die nur einen Knopf enthält, soll sich durch Drücken des
Knopfs die Hintergrundfarbe des Fensters zufällig verändern.
Lösungsschritte:
Gestalten der Oberfläche:
Ein Fenster, das von der Klasse JFrame abgeleitet wird und ein JButton-Objekt enthält.
Ereignisverarbeitung:
Die Ereignisquelle ist ein Button (JButton-Objekt). Durch Drücken des Buttons wird ein Ereignis
ausgelöst. Um auf dieses Ereignis reagieren zu können, müssen wir
einen Ereignisempfänger erzeugen und
diesen bei der Ereignis-Quelle (hier der Button) anmelden.
Für die Erzeugung eines Ereignisempfängers benötigen wir eine Klasse, die über alle erforderlichen
Eigenschaften verfügt. Dazu müssen wir wissen, auf welche Art von Ereignis der Empfänger ansprechen
soll. Im Fall eines gedrückten Buttons handelt es sich um ein Action-Ereignis, d.h. um ein Objekt der
Klasse ActionEvent. Wir müssen also eine Empfängerklasse schreiben, deren Objekte wissen, was
beim Empfang eines ActionEvent-Ereignisses zu tun ist. Um dies zu gewährleisten, müssen wir uns an
gewisse „Regeln“ halten, die in einem Interface festgelegt sind. Für ActionEvent-Ereignisse ist das
Interface ActionListener zuständig, das deshalb in unserer Empfänger-Klasse implementiert werden
muss.
© C. Endreß
19 / 33
02/2006
14. Graphische Oberflächen
Das ActionListener-Interface „weiß“, dass für die Bearbeitung eines ActionEvent-Objekts
automatisch die Methode actionPerformed aufgerufen wird. Diese Methode müssen wir in unserer
Empfänger-Klasse in der von uns gewünschten Weise implementieren. D.h. wir werden im MethodenRumpf ausformulieren, dass bei einem Druck auf den Button die Hintergrundfarbe des Containers
zufällig verändert wird. Das ActionListener-Interface enthält im übrigen keine weiteren Methoden.
1
/* Programm: Farbwechsel.java
* Erzeugt ein Swing-Fenster mit einem Button. Die
* Hintergrundfarbe des Fensters wechselt bei jedem
* Betätigen des Buttons auf eine zufällige Farbe.
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Farbwechsel extends JFrame{
Container c;
JButton button;
// Konstruktor
public Farbwechsel(){
c = getContentPane();
2
3
// Button erzeugen und dem Container hinzufügen
button = new JButton("Hintergrundfarbe wechseln");
c.add(button, BorderLayout.NORTH);
4
5
// Listener-Objekt erzeugen und beim Button anmelden
ButtonListener bListener = new ButtonListener();
button.addActionListener(bListener);
}
// Innere Klasse ButtonListener
class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e){
// RGB-Farbe des Containerhintergrunds zufaellig aendern
float zufallR = (float) Math.random();
float zufallG = (float) Math.random();
float zufallB = (float) Math.random();
Color farbe = new Color(zufallR, zufallG, zufallB);
c.setBackground(farbe); // Zugriff auf c moeglich, da
// ButtonListener innere Klasse
}
}
6
7
8
9
public static void main(String[] args) {
Farbwechsel fenster = new Farbwechsel();
fenster.setTitle("Farbwechsel");
fenster.setSize(300, 150);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Im Paket java.awt.event befinden sich alle Ereignis-Klassen.
2
Erzeugen eines JButton-Objekts mit der Beschriftung „Hintergrundfarbe wechseln“.
3
Das JButton-Objekt button wird dem Inhaltsfeld des Fensters zugefügt und im oberen
Fensterteil positioniert.
© C. Endreß
20 / 33
02/2006
14. Graphische Oberflächen
4
Erzeugen eines Ereignisempfänger-Objekts mit Namen bListener, das auf Button-Ereignisse
reagiert. Die Ereignisempfänger-Klasse ButtonListener wird weiter unten deklariert.
5
Das Empfänger-Objekt bListener wird mit der Methode addActionListener bei der
Ereignisquelle button registriert. Die Methode addActionListener wird von der Klasse
JButton bereit gestellt und erwartet einen Parameter vom Typ ActionListener, d.h. ein
Objekt einer Klasse, die das Interface ActionListener implementiert.
6
Deklarieren der Ereignisempfänger-Klasse ButtonListener als innere Klasse der Klasse
Farbwechsel. Damit Objekte der Klasse ButtonListener auf ActionEvents, die ein
JButton-Objekt (in diesem Fall das Objekt button) auslöst, reagieren können, muss die
Klasse das Interface ActionListener implementieren.
Beim Drücken von button wird die Botschaft actionPerformed an das Objekt bListener
gesendet.
Der Compiler erzeugt nicht nur für die Klasse Farbwechsel, sondern auch für deren innere
Klasse
ButtonListener
eine
Bytecode-Datei,
die
er
mit
dem
Namen
Farbwechsel$ButtonListener.class versieht.
7
Es werden drei Zufallszahlen mit float-Werten zwischen 0 und 1 erzeugt. Die Methode
Math.random() liefert double-Werte, so dass die Ergebnisse nach float gecastet werden
müssen.
8
Objekte der Klasse Color legen ihre Farbe durch Anteile an Rot, Grün und Blau fest. Man
spricht daher auch vom RGB-Farbmodell. Die Rot-, Grün- und Blau-Anteile werden jeweils als
int-Werte im Bereich 0 bis 255 oder alternativ als float-Werte im Bereich 0.0 bis 1.0
angegeben. Das Objekt farbe wird mit Zufallswerten initialisiert.
9
Die Hintergrundfarbe des Container c wird auf farbe gesetzt. In der Klasse ButtonListener
kann auf c zugegriffen werden, weil ButtonListener eine innere Klasse der Klasse
Farbwechsel ist und damit Zugriff auf die Objekte der Klasse Farbwechsel hat.
14.8.3
Programmiervarianten für die Ereignisverarbeitung
Es gibt verschiedene Möglichkeiten, das in Java verwendete Modell der Ereignisverarbeitung
programmiertechnisch umzusetzen. Man kann dabei prinzipiell 4 Varianten unterscheiden:
Die Listener-Klasse wird als innere Klasse realisiert.
Die Listener-Klasse wird als anonyme Klasse realisiert.
Die Container-Klasse wird selbst zur Listener-Klasse.
Die Listener-Klasse wird als separate Klasse realisiert.
Diese Varianten werden im folgenden am Beispiel Farbwechsel erläutert.
Variante 1: Listener-Klasse als innere Klasse
Innere Klassen werden in die Klasse eingeschachtelt, die die Ereignisquelle enthält, und haben Zugriff auf
alle Attribute und Operationen ihrer umgebenden Klasse.
© C. Endreß
21 / 33
02/2006
14. Graphische Oberflächen
In dem vorangegangenen Beispiel sind wir nach diesem Verfahren vorgegangen. Die Klasse Farbwechsel
enthält eine innere Klasse ButtonListener, die durch die Implementierung der Schnittstelle
ActionListener zur Listener-Klasse wird.
Variante 2: Listener-Klasse als anonyme Klasse
Anonyme Klassen sind eine Spezialform von inneren Klassen. Sie können quasi an jeder Stelle definiert
werden – sogar innerhalb von Methodenaufrufen oder in Wertzuweisungen.
Anonyme Klassen zeichnen sich dadurch aus, dass sie keinen eigenen Klassennamen besitzen. Man definiert
sie nach folgendem Schema:
new <NameDerSuperklasseOderDesInterfaces>() {
// Anweisungsblock
}
Die Definition steht in direktem Zusammenhang mit dem Konstruktoraufruf und definiert eine Klasse, die ein
Interface implementiert oder eine Klasse erweitert. Anschließend wird die Definition wieder vergessen.
Anonyme Klassen werden hauptsächlich für die Definition von „Wegwerfklassen“ verwendet, also von
Klassen, die nur ein einziges Mal im gesamten Programm verwendet werden.
Wenn sich die Ereignisverarbeitung so einfach gestaltet wie in unserem Farbwechsel-Programm, in dem
lediglich ein einziges Listener-Objekt benötigt wird, kann man darauf verzichten, die Listener-Klasse explizit
mit einem Namen zu versehen.
Stattdessen erzeugt man das Listener-Objekt mit einer anonymen Klasse. Dabei wird erst unmittelbar beim
Erzeugen des Objekts die Struktur der anonymen Klasse festgelegt. Man gibt hinter dem new-Operator den
Namen der Superklasse, von der die anonyme Klasse erben soll, oder den Namen des Interfaces, das die
anonyme Klasse implementieren soll, an.
Mit dieser Technik verkürzt sich das Beispielprogramm:
/* Programm: Farbwechsel.java
* Listener-Klasse als anonyme Klasse
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Farbwechsel extends JFrame{
Container c;
JButton button;
// Konstruktor
public Farbwechsel(){
c = getContentPane();
// Button erzeugen und dem Container hinzufügen
button = new JButton("Hintergrundfarbe wechseln");
c.add(button, BorderLayout.NORTH);
1
© C. Endreß
// Listener-Objekt erzeugen und beim Button anmelden
ActionListener btnListener = new ActionListener() {
public void actionPerformed(ActionEvent e){
// Hintergrundfarbe der Containers zufaellig aendern
float zufallR = (float) Math.random();
float zufallG = (float) Math.random();
float zufallB = (float) Math.random();
22 / 33
02/2006
14. Graphische Oberflächen
Color farbe = new Color(zufallR, zufallG, zufallB);
c.setBackground(farbe);
}
}; // Ende der anonymen Klassendefinition
2
button.addActionListener(btnListener);
}
public static void main(String[] args) {
Farbwechsel fenster = new Farbwechsel();
fenster.setTitle("Farbwechsel");
fenster.setSize(300, 150);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Definieren einer anonymen Klasse, die das Interface ActionListener implementiert, bei
gleichzeitigem Erzeugen des Listener-Objekt bListener.
2
Listener-Objekt bListener bei der Ereignisquelle button registrieren.
Für die anonyme Klasse erzeugt der Compiler eine Bytecode-Datei mit dem Dateinamen
Farbwechsel$1.class. Anonyme Klassen werden lediglich nummeriert. Die Zugehörigkeit zur Klasse
Farbwechsel wird durch den ersten Teil des Namens deutlich.
Variante 3: Container-Klasse als Listener-Klasse
Die zuletzt beschriebene Variante lässt sich weiter verkürzen, indem man sogar darauf verzichtet, mittels
einer inneren oder anonymen Klasse ein eigenes Listener-Objekt zu erzeugen. Stattdessen verwendet man
das Objekt, in dem man sich zu Laufzeit des Programms befindet (also das Objekt der Klasse JFrame), als
Listener-Objekt. Dafür muss die Fenster-Klasse selbst das entsprechende Listener-Interface implementieren.
/* Programm: Farbwechsel_V3.java
* Container-Klasse als Listener-Klasse
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
1
public class Farbwechsel_V3 extends JFrame implements ActionListener{
Container c;
JButton button;
// Konstruktor
public Farbwechsel_V3(){
c = getContentPane();
// Button erzeugen und dem Container hinzufügen
button = new JButton("Hintergrundfarbe wechseln");
c.add(button, BorderLayout.NORTH);
// eigenes Objekt beim Button als Listener anmelden
button.addActionListener(this);
2
}
3
© C. Endreß
public void actionPerformed(ActionEvent e){
// Hintergrundfarbe der Containers zufaellig aendern
float zufallR = (float) Math.random();
23 / 33
02/2006
14. Graphische Oberflächen
float zufallG = (float) Math.random();
float zufallB = (float) Math.random();
Color farbe = new Color(zufallR, zufallG, zufallB);
c.setBackground(farbe);
}
public static void main(String[] args) {
Farbwechsel fenster = new Farbwechsel();
fenster.setTitle("Farbwechsel");
fenster.setSize(300, 150);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Die Klasse Farbwechsel erbt wie gehabt von JFrame und implementier das Interface
ActionListener. Dadurch müssen wir in die Klasse Farbwechsel die Instanzmethode
actionPerformed aufnehmen.
2
Das Fenster-Objekt der Klasse Farbwechsel kann nun zur Laufzeit selbst die Rolle des
Listeners übernehmen. Daher genügt es, mit der Methode addActionListener einfach die
this-Referenz (die Referenz auf das eigene Objekt) bei dem Button-Objekt registrieren zu
lassen.
3
Die Methode actionPerformed muss als Instanz-Methode in der Klasse Farbwechsel
definiert werden, da Farbwechsel das entsprechende Listener-Interface implementiert. Der
Methodenrumpf von actionPerformed unterscheidet sich nicht von den vorhergehenden
Beispielen.
Variante 4: Listener-Klasse als separate Klasse
Diese vierte Variante ermöglicht eine strikte Trennung zwischen der graphischen Oberfläche und der
Ereignisverarbeitung. Man lagert die Listener-Klasse vollständig in eine eigenständige Klasse aus. Dabei ist
jedoch zu beachten, dass die Listener-Klasse je nach Aufgabenstellung einen Zugriff auf die Ereignis-Quelle,
ihren Container oder andere Objekte benötigt. Dieses kann realisiert werden, indem man dem ListenerObjekt die entsprechenden Informationen bzw. Referenzen bereits bei seiner Erzeugung übergibt. Für diese
Aufgabe muss natürlich ein spezieller, parametrisierter Konstruktor programmiert werden.
In unserem Farbwechsel-Beispiel ergäbe sich für die Listener-Klasse ButtonListener.java folgende
Implementierung:
/* ButtonListener.java
* Eigenstaendige Listener-Klasse
*/
import java.awt.*;
import java.awt.event.*;
1
public class ButtonListener implements ActionListener {
Container c; // Referenz auf den zu beeinflussenden Container
2
// Konstruktor
public ButtonListener(Container c){
this.c = c;
}
public void actionPerformed(ActionEvent e){
// Hintergrundfarbe der Containers zufaellig aendern
© C. Endreß
24 / 33
02/2006
14. Graphische Oberflächen
float zufallR = (float) Math.random();
float zufallG = (float) Math.random();
float zufallB = (float) Math.random();
Color farbe = new Color(zufallR, zufallG, zufallB);
c.setBackground(farbe);
}
}
Erläuterungen
1
Wir benötigen einen Zugriff auf den Container, der den auslösenden Button enthält. Dazu wird
die Referenz-Variable c vom Typ Container deklariert.
2
Die Referenz-Variable c des Listeners wird mit dem zu beeinflussenden Container initialisiert.
/* Programm: Farbwechsel_V4.java
* Listener-Klasse als separate Klasse ausführen
*/
import java.awt.*;
import javax.swing.*;
public class Farbwechsel_V4 extends JFrame{
Container c;
JButton button;
// Konstruktor
public Farbwechsel_V4(){
c = getContentPane();
// Button erzeugen und dem Container hinzufügen
button = new JButton("Hintergrundfarbe wechseln");
c.add(button, BorderLayout.NORTH);
// Listener-Objekt erzeugen und beim Button anmelden
ButtonListener btnListener = new ButtonListener(c);
button.addActionListener(btnListener);
1
2
}
public static void main(String[] args) {
Farbwechsel_V4 fenster = new Farbwechsel_V4();
fenster.setTitle("Farbwechsel");
fenster.setSize(300, 150);
fenster.setVisible(true);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Erläuterungen
1
Beim
Erzeugen
des
Listener-Objekts
btnListener
muss
dem
Konstruktor
ButtonListener() eine Referenz auf den Container c des Fensters übergeben werden.
2
Das Listener-Objekt btnListener wird bei der Ereignisquelle button mit der Methode
addActionListener angemeldet.
© C. Endreß
25 / 33
02/2006
14. Graphische Oberflächen
Tabelle: Varianten der Ereignisverarbeitung
Listener-Klasse als ...
Bewertung
innere Klasse
+ Geeignet für kleine Programme
+ Die Listener-Klasse kann von Adapter-Klassen abgeleitet werden.
anonyme Klasse
+ Geeignet für kleine Programme
– Nur ein einziges Listener-Objekt der Listener-Klasse möglich.
Container-Klasse
+ Einfach zu implementieren, da keine weiteren Klassen erforderlich sind.
– Keine Trennung zwischen Oberfläche und Ereignisverarbeitung.
– Für jeden Ereignistyp ist ein passendes Listener-Interface zu
implementieren. Das führt schnell zu vielen leeren Methoden Rümpfen und
unübersichtlichen Programmen. Adapter-Klassen können nicht verwendet
werden, weil Mehrfachvererbung in Java nicht möglich ist.
separate Klasse
+ Aufgrund der Trennung zwischen Oberflächengestaltung und
Ereignisverarbeitung gut geeignet für komplexe Programme.
14.8.4
Listener-Interfaces und Adapter-Klassen
Elementare Ereignisse (Low-Level-Ereignisse) sind Ereignisse die z.B. durch Maus oder Taststur
ausgelöst werden. Sie werden in der Klasse ComponentEvent und deren Unterklassen behandelt.
Semantische Ereignisse sind nicht an ein bestimmtes Interaktionselement gebunden. Das Ereignis
ActionEvent kann z.B. von vielen Komponenten ausgelöst werden.
Alle Interfaces, die zur Implementierung von Ereignisempfänger-Klassen genutzt werden, sind
Subinterfaces von EventListener.
Grundsätzlich gibt es zu jeder Ereignis-Klasse
ein Interface
XxxEvent
XxxListener
Listener-Interfaces für semantische Ereignisse enthalten lediglich eine einzige zu implementierende
Methode. Z.B. besitzt das Interface ActionListener nur die Methode actionPerformed.
Listener-Interfaces zu Low-Level-Ereignissen umfassen mehrere Methoden, wie z.B. die Interfaces
MouseListener oder WindowListener.
Bei der Implementierung von Interfaces müssen grundsätzlich alle Methoden des Interfaces
implementiert werden. Das kann lästig werden, wenn das Interface mehrere Methoden besitzt und man
für eine graphische Oberfläche lediglich eine der Methoden benötigt. Zur Vereinfachung dieses Problems
gibt es sogenannte Adapter-Klassen.
Eine Adapter-Klasse ist eine abstrakte Klasse, die das entsprechende Interface implementiert und alle
Methoden mit leeren Rümpfen versieht.
Eine Adapter-Klasse kann verwendet werden, wenn aus einem Interface nur ein Teil der Methoden
benötigt wird.
Eine selbstgeschriebene Empfänger-Klasse kann von der Adapter-Klasse erben und redefiniert nur die
benötigten Methoden. Man muss dann nicht mehr alle Methoden des Interfaces implementieren.
© C. Endreß
26 / 33
02/2006
14. Graphische Oberflächen
Anstelle von
class EigenerListener implements XxxListener { . . . }
schreibt man
class EigenerListener extends XxxAdapter { . . . }
Interface XxxListener
=>
zugehörige Adapter-Klasse XxxAdapter
Zu jedem Low-Level-Ereignis stellt das Paket java.awt.event eine passende Adapter-Klasse zur
Verfügung.
Beispiel: CloseToggleButtons.java
Die Oberfläche des Beispielprogramms enthält zwei Schalter in Form von JToggleButton-Objekten sowie
ein Label, das den Anwender darüber informiert, dass das Fenster nur geschlossen werden kann, wenn
beide Schalter aktiviert sind. Sind nicht beide Schalter aktiviert und der Anwender versucht, das Fenster zu
schließen, erhält er in einem modalen Dialogfenster eine Mitteilung. Dieses Dialogfenster muss erst mit dem
OK-Button geschlossen werden, bevor der Benutzer im Anwendungsfenster fortfahren kann.
/* Programm: CloseToggleButtons.java
* Erzeugt ein Swing-Fenster mit zwei Toggle-Buttons,
* die beide zum Schliessen des Fensters aktiviert sein muessen
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CloseToggleButtons extends JFrame {
Container c;
// Container dieses Frames
JLabel label;
JToggleButton tBtn1, tBtn2;
// Konstruktor
public CloseToggleButtons() {
c = getContentPane();
c.setLayout(new FlowLayout());
// Erzeuge die Label- und Button-Objekte
label =
new JLabel("Zum Schliessen des Fensters beide Schalter
aktivieren!");
tBtn1 = new JToggleButton("Schalter 1");
tBtn2 = new JToggleButton("Schalter 2");
© C. Endreß
27 / 33
02/2006
14. Graphische Oberflächen
// Fuege die Komponenten dem Frame hinzu
c.add(label);
c.add(tBtn1);
c.add(tBtn2);
// Registriere WindowListener beim Fenster
addWindowListener(new ClosingListener());
1
}
// Innere Listener-Klasse
public class ClosingListener extends WindowAdapter {
public void windowClosing(WindowEvent event) {
if (tBtn1.isSelected() && tBtn2.isSelected()) {
event.getWindow().dispose();
System.exit(0);
}
else
JOptionPane.showMessageDialog(
c,
"Vor dem Schliessen erst beide Schalter aktivieren!");
}
}
2
3
4
5
6
7
public static void main(String[] args) {
CloseToggleButtons fenster = new CloseToggleButtons();
fenster.setTitle("CloseToggleButtons");
fenster.setSize(400, 100);
fenster.setVisible(true);
// Setze das Verhalten des Frames beim Schliessen auf "Nichtstun"
fenster.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
8
}
}
Erläuterungen
1
Der WindowListener wird beim Fenster angemeldet.
2
Deklarieren der inneren Klasse ClosingListener: Die Klasse ClosingListener muss eine
Empfänger-Klasse für WindowEvents sein. Bisher haben wir in unseren Empfänger-Klassen
stets das entsprechende Listener-Interface (hier: WindowListener) implementiert. Von den 7
Methoden des Interfaces WindowListener benötigen wir in diesem Programm nur die
Methode windowClosing. Um nicht auch noch die übrigen 6 Methoden definieren zu müssen,
implementieren wir nicht das Listener-Interface WindowListener, sondern leiten unsere
Empfänger-Klasse ClosingListener von der zugehörigen Adapter-Klasse WindowAdapter
ab.
3
Die Methode windowClosing wird aufgerufen, wenn das Fenster geschlossen werden soll. Der
Anwender löst dieses Ereignis mit einem Klick auf den Schließen-Knopf des Fensters oder die
Tastenkombination Alt-F4 aus. Das Fenster soll jedoch erst geschlossen werden können, wenn
beide Schalter aktiviert sind. Diese Voraussetzung ist in der Methode windowClosing zu
prüfen.
4
Die Auswahlzustände der beiden JToggleButton-Objekte werden mit der Methode
isSelected abgefragt. Die Methode liefert das Ergebnis true, wenn ein JToggleButtonObjekt selektiert ist.
5
Über das Ereignis-Objekt event wird mit der Methode getWindow zunächst die Referenz auf
das Fenster ermittelt, das das Ereignis ausgelöst hat. Danach wird das Fenster durch seine
Instanzmethode dispose zerstört.
6
Das Programm wird mit der Methode System.exit beendet.
© C. Endreß
28 / 33
02/2006
14. Graphische Oberflächen
7
Sind nicht beide Schalter aktiviert, erzeugen wir mit der Klasse JOptionPane ein modales
Dialogfenster, das nur eine Mitteilung mit einen OK-Button präsentiert.
8
Damit das Schließen des Fensters auch in der oben gewünschten Weise abläuft setzen wir die
Standardeinstellung beim Schließen des Fensters nicht mehr auf „Exit“ sondern auf „Nichtstuns“.
Die eingestellte Standardtätigkeit des Fenster wird immer nach dem Ausführen des
WindowListeners durchgeführt. Da bereits der Listener das Fenster schließt, muss dieses hier
nicht mehr erfolgen.
14.8.5
Empfänger-Registrierung bei Ereignis-Quellen
Grundsätzlich muss jedes Empfänger-Objekt bei der Ereignis-Quelle registriert werden, von der es
Ereignisse empfangen soll.
Implementiertes Listener-Interfaces :
XxxListener
Empfänger-Objekt anmelden
addXxxListener(Empfänger-Objekt)
Empfänger-Objekt abmelden
removeXxxListener(Empfänger-Objekt)
Was passiert bei der Registrierung?
Jede Ereignis-Quelle, die von der Basisklasse der Swing-Komponenten JComponent abgeleitet wird,
enthält eine Instanzvariable listenerList vom Typ EventListenerList. Durch den Aufruf einer
entsprechenden Registrierungsanweisung (addXxxListener) wird eine Referenz auf das übergebene
Empfänger-Objekt in die listenerList eingetragen. Die Referenz ist vom Typ des Empfänger-Objekts
(z.B. ActionListener, MouseListener etc.).
Wenn ein Ereignis auftritt, wird dieses von der Ereignis-Quelle nur an Listener-Objekte weitergeleitet, die
in der Listenerliste mit dem passenden Referenztyp eingetragen sind. So werden ActionEvents nur an
ActionListener weitergeleitet, MouseEvents nur an MouseListener usw.
button1
button1Listener
JButton-Objekt, enthält
eine Instanzvariable
listenerList vom Typ
EventListenerList.
Ereignis-Abhörer für button1, der das
Interface ActionListener
implementiert und die Methode
actionPerformed definiert.
listenerList
public void
actionPerformed(ActionEvent e) {
// Ereignisbehandlung
…
}
...
Diese Referenz wird erzeugt durch die Anweisung
button1.addActionListener( button1Listener );
© C. Endreß
29 / 33
02/2006
14. Graphische Oberflächen
14.8.6
Vorgehensweise
Um eine problemgerechte Ereignisverarbeitung in Java zu programmieren, bietet sich folgende
Vorgehensweise an:
Ereignisverarbeitung in Java programmieren
1. Überlegen, von welcher Ereignisquelle Ereignisse abgehört werden sollen.
2. Feststellen, welche Ereignisklassen diese Ereignisquelle erzeugt. (s.a. Tabellen zu elementaren und
semantischen Ereignissen)
3. Pro Ereignisklasse eine Empfänger-Klasse für den Ereignistyp schreiben
z.B. class AktionsAbhoerer
a)
Wenn semantisches Ereignis, dann entsprechendes Interface implementieren
z.B. class AktionsAbhoerer implements ActionListener
und alle Interface-Methoden definieren
z.B. public void actionPerformed(ActionEvent event){…}
b)
Wenn elementares Ereignis, dann eventuell entsprechende Adapter-Klasse erweitern
z.B. class MausAbhoerer extends MouseAdapter
und nur die benötigten Methoden definieren.
4. Pro Empfänger-Klasse ein Empfänger-Objekt erzeugen
z.B. AktionsAbhoerer einAbhoerer = new ActionsAbhoerer()
5. Pro Ereignisquelle entsprechendes Empfänger-Objekt registrieren
z.B. einButton.addActionListener(einAbhoerer)
© C. Endreß
30 / 33
02/2006
14. Graphische Oberflächen
Tabelle: Elementare Ereignisse (Low-Level-Ereignisse) und ihre Verarbeitung
Ereignisquellen
Ereignisklasse
Component
ComponentEvent
Schnittstelle und ihre Methoden
Adapterklasse
Registrierung
ComponentListener
ComponentAdapter
addComponentListener
componentHidden
componentMoved
componentResized
componentShown
FocusEvent
FocusListener
focusGained
focusLost
KeyEvent
KeyListener
keyPressed
keyReleased
keyTyped
MouseEvent
MouseListener
mouseClicked
mouseEntered
mouseExited
mousePressed
mouseReleased
MouseMotionEvent
MouseMotionListener
mouseDragged
mouseMoved
Container
ContainerEvent
ContainerListener
componentAdded
componentRemoved
© C. Endreß
31 / 33
Eine Komponente wurde unsichtbar geschaltet.
Die Position einer Komponente wurde verändert.
Die Größe einer Komponente wurde verändert.
Eine Komponente wurde sichtbar geschaltet.
FocusAdapter
addFocusListener
Eine Komponente hat den Tastatur-Fokus erhalten.
Eine Komponente hat der Tastatur-Fokus verloren.
KeyAdapter
addKeyListener
Eine Taste wurde gedrückt.
Eine Taste wurde losgelassen.
Eine Taste wurde gedrückt und wieder losgelassen.
MouseAdapter
addMouseListener
Die Maustaste wurde geklickt (gedrückt und wieder losgelassen).
Die Maus wurde in eine Komponente bewegt.
Die Maus aus einer Komponente heraus bewegt wurde.
Die Maustaste wurde gedrückt.
Die Maustaste wurde losgelassen.
MouseMotionAdapter
addMouseMotionListener
Die Maus wurde mit gedrückter Maustaste bewegt.
Die Maus wurde ohne gedrückte Maustaste bewegt.
ContainerAdapter
Eine Komponente wurde einem Container hinzugefügt.
Eine Komponente wurde aus einem Container entfernt.
02/2006
addContainerListener
14. Graphische Oberflächen
JDialog, JFrame
WindowEvent
WindowListener
windowActivated
windowClosed
windowClosing
windowDeactivated
windowDeiconfied
windowIconfied
windowOpened
WindowFocusListener
windowGaindedFocus
windowLostFocus
WindowStateListener
windowStateChanged
© C. Endreß
32 / 33
WindowAdapter
addWindowListener
Das Fenster wurde aktiviert.
Das Fenster wurde geschlossen.
Das Fenster soll geschlossen werden.
Das Fenster wurde deaktiviert.
Das Fenster wurde wieder hergestellt.
Das Fenster wurde minimiert.
Das Fenster wurde das erste Mal sichtbar.
WindowAdapter
addWindowFocusListener
Das Fenster hat den Focus erhalten.
Das Fenster hat den Focus verloren.
WindowAdapter
Der Zustand des Fensters hat sich geändert.
02/2006
addWindowStateListener
14. Graphische Oberflächen
Tabelle: Semantische Ereignisse und ihre Verarbeitung
Ereignisquellen
Ereignisklasse
Schnittstelle und ihre Methoden
Registrierung
JButton, JComboBox, JMenuItem,
JTextField, JToggleButton, JList,
JCheckBox, JRadioButton
ActionEvent
ActionListener
actionPerformed
addActionListener
JScrollBar
AdjustmentEvent
JButton, JList, JMenuItem,
JTextField, JToggleButton,
JCheckBox, JRadioButton, JSlider
ChangeEvent
JCheckBox, JRadioButton,
JMenuItem. JComboBox,
JToggleButton
ItemEvent
JTextField, JTextArea
TextEvent
JTextField, JTextArea
JList, JTable
JMenu
© C. Endreß
CaretEvent
ListSelectionEvent
MenuEvent
Eine Aktion wurde ausgeführt.
AdjustmentListener
adjustmentValueChanged
addAdjustmentListener
Der Wert wurde geändert.
ChangeListener
stateChanged
Der Zustand der Ereignis-Quelle wurde verändert.
ItemListener
itemStateChanged
Der Zustand der Ereignis-Quelle wurde verändert.
TextListener
textValueChanged
Der Text wurde verändert
addChangeListener
addItemListener
addTextListener
CaretListener
caretUpdate
Die Cursor-Position wurde aktualisiert.
ListSelectionListener
valueChanged
Die Auswahl der Listeneinträge wurde verändert.
MenuListener
menuCanceld
menuDeselected
menuSelected
33 / 33
addCaretListener
addListSelectionListener
addMenuListener
Ein Menüpunkt wurde verworfen.
Ein Menüpunkt wurde deselektiert.
Ein Menüpunkt wurde selektiert.
02/2006
15. Serialisierung
15. Serialisierung
Lernziele
☺ Die Begriffe Serialisierung und Deserialsierung erklären können.
☺ Serialisierung und Deserialisierung einfacher Objektstrukturen in Java realisieren können.
15.1 Begriffsbestimmung
Objekte, die in einem Programm erzeugt werden, sind nur in dessen Speicherbereich verfügbar, solange das
Programm läuft. Sollen diese Objekte in einer Datei gespeichert oder über eine Netzwerkverbindung
transportiert werden, so müssen die Objekte in ein geeignetes Format konvertiert werden. Dazu wandelt
man den Zustand eines Objekts, der durch die Werte seiner Instanzvariablen festgelegt ist, in eine
systemunabhängige Binärdarstellung um, die über einen Datenstrom transportiert werden kann. Diesen
Vorgang nennt man Serialisierung.
Definition
Serialisierung ist die Umwandlung eines Objekts in eine systemunabhängige Binärdarstellung zum
Transportieren des Objekts über einen Datenstrom. Das Rekonstruieren von Objekten aus serialisierten
Daten bezeichnet man als Deserialisierung.
15.2 Serialisierung
Mit der Klasse ObjectOutputStream können Datenströme zur Serialisierung von Objekten erzeugt
werden.
public ObjectOutputStream(OutputStream out)
An den Konstruktor wird ein OutputStream-Objekt (Byte-Strom) übergeben, das als Ziel der Ausgabe
dient. Zum Schreiben der serialisierten Daten in eine Datei verwendet man ein FileOutputStreamObjekt. Die Klasse FileOutputStream ist eine Unterklasse von OutputStream.
Die Klasse ObjectOutputStream besitzt Methoden, um elementare Datentypen zu serialisieren.
Zum Serialisieren von Objekten verfügt die Klasse ObjectOutputStream über die Methode
public final void writeObject(Object obj)
Objekte können nur serialisiert werden, wenn sie das Interface Serializable implementieren. Dabei
handelt es sich um ein Interface, das weder Methoden nach Variablen enthält. Es reicht daher aus, eine
Klasse lediglich mit implements Serializable zu erweitern. Methoden sind nicht zu
implementieren.
Beim Aufruf der Methode writeObject werden nur Instanzvariablen des übergebenen Objekts
serialisiert. Klassenvariablen werden von der Serialisierung ausgenommen, da sie nicht zum Objekt,
sondern zur Klasse gehören. Will man auch bestimmte Instanzvariablen von der Serialisierung
ausnehmen, müssen diese in der Klassendefinition durch das Schlüsselwort transient markiert
werden.
C. Endreß
1/4
02/2006
15. Serialisierung
Beispiel:
import java.io.*;
1
2
public class Datensatz implements Serializable {
public int
nr;
// Nummer des Datensatzes
public double wert; // Wert des Datensatzes
public String kom; // Kommentar
3
public Datensatz (int nr, double wert, String kom) { // Konstruktor
this.nr = nr;
this.wert = wert;
this.kom = kom;
}
4
public String toString() {
// Erzeugung einer String-Darstellung
return "Nr. " + nr + ": " + wert + " (" + kom + ")";
}
}
Erläuterungen
1
Um Objekte der Klasse Datensatz serialisieren zu können, muss die Klasse das Interface
Serializable implementieren. Serializable enthält weder Methoden noch Variablen, die in
der implementierenden Klasse definiert werden müssten.
2
Mit der Sichtbarkeit public verstoßen die Deklarationen der Attribute gegen das Prinzip der
Datenkapselung der objekt-orientierten Programmierung. Die Veröffentlichung der Attribute dient
hier lediglich der Verkürzung des Quellcodes, da auf get- und set-Methoden verzichtet werden
kann. Zur Nachahmung sei sie jedoch nicht empfohlen.
3
Dem parametrisierten Konstruktor werden die Attributwerte der Instanzvariablen übergeben.
4
Die Methode toString liefert die textuelle Repräsentation der Datensatz-Objekte.
Das folgende Listing demonstriert das Serialisieren von Objekten der Klasse Datensatz in der Datei
MeineDaten.dat:
import java.io.*;
1
2
3
4
public class ObjectWrite {
public static void main(String[] summand) {
try {
String dateiname = "MeineDaten.dat";
FileOutputStream datAus = new FileOutputStream(dateiname);
ObjectOutputStream oAus = new ObjectOutputStream(datAus);
5
int anzahl = 2;
Datensatz a = new Datensatz(99, 56, "Coca Cola");
Datensatz b = new Datensatz(111, 1234.79, "Fahrrad");
6
7
8
oAus.writeInt(anzahl); // Anzahl der Datensätze
oAus.writeObject(a);
// Datensatz 1
oAus.writeObject(b);
// Datensatz 2
9
oAus.close();
System.out.println(anzahl + " Datensaetze in die Datei " + dateiname
+ " geschrieben");
System.out.println(a);
System.out.println(b);
}
C. Endreß
2/4
02/2006
15. Serialisierung
catch (Exception e) {
System.out.println("Fehler beim Schreiben: " + e);
}
}
}
Erläuterungen
1
Die Konstruktoren der OutputStreams sowie die Methode writeObject werfen IOExceptions
aus. Deshalb werden die Anweisungen in einen try-Block gesetzt. Die folgende catch-Anweisung
fängt geworfene Exceptions auf und zeigt deren Fehlermeldungen an.
2
Die Stringvariable enthält den Dateinamen.
3
Das FileOutputStream-Objekt datAus wird mit der Datei MeineDaten.dat initialisiert. Der
Stream stellt eine Verbindung zur Zieldatei her, über die Daten in die Datei geschrieben werden
können.
4
Das ObjectOutputStream-Objekt oAus wird mit dem FileOutputStream-Objekt datAus
initialisiert. Dadurch entsteht eine Verbindung von oAus über datAus in die Datei. Der
ObjectOutputStream serialisiert die Objekte, der FileOutputStream überträgt die
serialisierten Binärdaten in die Datei.
5
Es werden zwei Datensatz-Objekte a und b erzeugt.
6
Die Anzahl der zu serialisierenden Datensätze wird in die Datei geschrieben.
7
Die Methode writeObject serialisiert das Datensatz-Objekt a über den ObjectOutputStream
und schreibt es in die Datei.
8
Die Methode writeObject serialisiert das Datensatz-Objekt b über den ObjectOutputStream
und schreibt es in die Datei.
9
Die Methode close schließt den OutputStream und beendet die Verbindung zur Datei.
15.3 Deserialisierung
Um Datenströme zu deserialisieren, verwendet man die Klasse ObjectInputStream:
public ObjectInputStream(InputStream in)
An den Konstruktor wird ein InputStream-Objekt (Byte-Strom) übergeben, über das Daten von einer
Quelle eingelesen werden. Zum Lesen von Daten aus einer Datei verwendet man ein
FileInputStream-Objekt. Die Klasse FileInputStream ist eine Unterklasse von InputStream.
Die Methode
public final Object readObject()
liest ein Objekt aus dem Datenstrom und liefert es als Ergebnis zurück.
Beispiel:
Mit dem folgenden Programm werden Objekte, die im vorangegangenen Beispiel serialisiert wurden, aus
einer Datei ausgelesen und rekonstruiert.
Import java.io.*;
1
2
3
4
C. Endreß
public class ObjectRead {
public static void main (String[] summand) {
try {
String dateiname = "MeineDaten.dat";
FileInputStream datEin = new FileInputStream(dateiname);
ObjectInputStream oEin = new ObjectInputStream(datEin);
3/4
02/2006
15. Serialisierung
5
int anzahl = oEin.readInt();
System.out.println("Die Datei " + dateiname + " enthaelt " +
anzahl + " Datensaetze");
for (int i=1; i<=anzahl; i++) {
Datensatz gelesen = (Datensatz) oEin.readObject();
System.out.println(gelesen);
}
oEin.close();
6
7
}
catch (Exception e) {
System.out.println("Fehler beim Lesen: " + e);
}
}
}
Erläuterungen
1
Die Konstruktoren der InputStreams sowie die Methode readObject werfen IOExceptions
aus. Deshalb werden die Anweisungen in einen try-Block gesetzt. Die folgende catch-Anweisung
fängt geworfene Exceptions auf und zeigt deren Fehlermeldungen an.
2
Die Stringvariable enthält den Dateinamen.
3
Das FileInputStream-Objekt datEin wird mit der Datei MeineDaten.dat initialisiert. Der
InputStream stellt eine Verbindung zur Quelldatei her, von der die Daten gelesen werden sollen.
4
Das ObjectInputStream-Objekt oEin wird mit dem FileInputStream-Objekt datEin
initialisiert. Dadurch entsteht eine Verbindung von der Quelldatei über datEin zu oEin. Der
ObjectInputStream deserialisiert die binären Daten, die der FileInputStream aus der
Quelldatei liest, und rekonstruiert Objekte.
5
Als erste Information wird die Anzahl der serialisierten Objekte ausgelesen.
6
Es wird ein Objekt der Klasse Datensatz erzeugt. Die Methode readObject liest die serialisierten
Binärdaten über den ObjectInputStream oEin aus der Datei und rekonstruiert ein Objekt.
Dieses deserialisierte Objekt ist von der Basisklasse Object. Für eine Zuweisung an das
Datensatz-Objekt gelesen muss ein Typ-Cast nach Datensatz durchgeführt werden.
7
Nachdem alle Objekte eingelesen sind, wird der Eingabestream geschlossen.
C. Endreß
4/4
02/2006
16. Suchen und Sortieren
16. Suchen und Sortieren
Lernziele
☺ Algorithmen für lineare und binäre Suche erklären und anwenden können.
☺ Die Begriffe Komplexität und Ordnung eines Algorithmus kennen.
☺ Algorithmen für die Sortierverfahren Bubble-Sort, Insertion-Sort und Quick-Sort erklären können.
16.1 Suchverfahren
Das Suchen von Informationen in Informationsbeständen gehört zu den häufigsten Aufgaben, die mit
Computersystemen ausgeführt werden. Oft ist die gesuchte Information eindeutig durch einen Schlüssel
identifizierbar. Schlüssel sind in der Regel positive ganze Zahlen (z.B. Artikelnummer, Kontonummer etc.)
oder alphabetische Schlüssel (z.B. Nachname, Firmenname usw.). Von dem Schlüssel gibt es meist eine
Referenz auf die eigentlichen Informationen.
Die Suchverfahren können Kategorien zugeordnet werden:
Elementare Suchverfahren: Es werden nur Vergleichsoperationen zwischen den Schlüsseln
ausgeführt.
Schlüssel-Transformationen (Hash-Verfahren): Aus dem Suchschlüssel wird mit arithmetischen
Operationen direkt die Adresse von Datensätzen
berechnet.
Suchen in Texten: Suchen eines Musters in einer Zeichenkette.
16.1.1
Lineare Suche
Die lineare bzw. sequentielle Suche wird immer dann angewendet, wenn die Schlüssel in ungeordneter
Folge in einer Liste abgelegt sind.
Lineare Suche: Die Elemente der Liste werden der Reihe nach mit dem Suchschlüssel verglichen, bis das
passende Element gefunden wird oder das Ende der Liste erreicht ist.
Beispiel:
Feld: a[ ]
Feldlänge: n =14
Suchschlüssel: k = 57
Index:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
27
2
13
6
8
16
5
59
47
33
57
19
29
4
lineare Suche
a[ i ]
Suchschlüssel gefunden an Position i = 10
C. Endreß
1 / 14
10/2008
16. Suchen und Sortieren
Als Ergebnis der Suche kann entweder die Position des gesuchten Elements zurückgegeben werden oder
auch nur die Information ob das gesuchte Element in der Liste enthalten ist.
Das Struktogramm zeigt eine Funktion, die bei erfolgreicher Suche die Schlüsselposition zurückgibt, deren
Wert zwischen 0 und (n – 1) liegt. Ist das gesuchte Element nicht im Feld enthalten, liefert die Funktion den
Wert n zurück.
lineareSuche( int a[ ], int n, int k )
int i = 0
while( i < n && k != a[ i ] )
i=i+1
return i
Aufwandsbetrachtung
Unter der Komplexität eines Algorithmus versteht man den zu seiner Durchführung erforderlichen Aufwand
an Betriebsmitteln wie Rechenzeit und Speicherplatz.
Die asymptotische Zeitkomplexität wird als Ordnung bezeichnet und durch ein großes O ausgedrückt:
O(n). Wenn ein Algorithmus Eingabedaten des Umfangs n verarbeitet, und der Zeitaufwand linear ist,
besitzt der Algorithmus die Ordnung n. Seine Zeitkomplexität ist dann proportional n.
Für die lineare Suche in einer Liste gilt: Enthält die Liste n Elemente, dann werden im schlechtesten Fall n
Schlüsselvergleiche für eine erfolgreiche Suche benötigt. Nimmt man an, dass die Verteilung der Schlüssel
gleichwahrscheinlich ist, dann ist zu erwarten, dass für eine erfolgreiche Suche im Mittel
1
⋅
n
n
∑i =
i=1
n +1
2
Schlüsselvergleiche durchzuführen sind.
Lineare Suche:
16.1.2
O(n) = n
Binäre Suche
Liegen die Schlüssel geordnet in einer linearen Liste vor, kann eine binäre Suche durchgeführt werden.
In einem sortierten Feld kann man deutlich schneller suchen.
Binäre Suche: Man vergleicht zunächst den Suchschlüssel mit dem Element in der Mitte des Feldes. Ist der
Suchschlüssel kleiner als das mittlere Element, setzt man die Suche im unteren Teilfeld fort. Ist der
Suchschlüssel größer als das mittlere Element, wird die Suche im oberen Teilfeld fortgeführt. Im
ausgewählten Teilfeld vergleicht man nun den Suchschlüssel wieder mit dem mittleren Element des
Teilfeldes. Gegebenenfalls muss das Teilfeld erneut halbiert und die Suche nach dem beschrieben Verfahren
fortgesetzt werden, bis das gesuchte Element gefunden ist oder nur noch ein Element übrig bleibt.
C. Endreß
2 / 14
10/2008
16. Suchen und Sortieren
Beispiel 1: Suchschlüssel im Feld vorhanden
Feld: a[ ]
Feldlänge: n =14
Suchschlüssel: k = 8
Teilfeldgrenzen: low, high
Teilfeldmitte: m
Index:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
2
5
6
8
13
16
19
27
29
33
47
52
57
59
2
5
6
8
13
16
low = 0, high = 5
m=2
8
13
16
low = 3, high = 5
m=4
low = 0, high = 13
m=6
low = 3, high = 3
m=3
8
Suchschlüssel gefunden an Position i = 3
Beispiel 2: Suchschlüssel ist nicht im Feld vorhanden
Feld: a[ ]
Feldlänge: n =14
Suchschlüssel: k = 54
Teilfeldgrenzen: low, high
Teilfeldmitte: m
Index:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
2
5
6
8
13
16
19
27
29
33
47
52
57
59
low = 0, high = 13
m=6
27
29
33
47
52
57
59
low = 7, high = 13
m = 10
52
57
59
low = 11, high = 13
m = 12
52
low = 11, high = 11
m = 11
Suchschlüssel nicht gefunden
C. Endreß
3 / 14
10/2008
16. Suchen und Sortieren
Der Suchalgorithmus kann sowohl rekursiv als auch iterativ formuliert werden. Die folgenden Struktogramme
zeigen mögliche Formulierungen einer rekursiven sowie einer iterativen Funktion binSuche.
int binSuche( int a[ ], int k, int low, int high )
int m = (low + high) / 2
low <= high
ja
ja
k = a[ m ]
return m
nein
nein
k < a[ m ]
ja
return binSuche(a, k, low, m - 1)
return -1
nein
return binSuche(a, k, m + 1, high)
Rekursive Formulierung der binären Suche
int binSuche( int a[ ], int n, int k )
int m
int low = 0
int high = n - 1
while( low <= high)
m = ( low + high ) / 2
k = a[ m ]
ja
return m
nein
k > a[ m ]
ja
low = m + 1
nein
high = m - 1
return -1
Iterative Formulierung der binären Suche
Aufwandsbetrachtung
Entscheidend für den Aufwand ist die Anzahl der notwendigen Halbierungen, die natürlich von der Anzahl
der Feldelemente abhängt. Im schlechtesten Fall sind so viele Halbierungen vorzunehmen, bis nur noch ein
Element zu betrachten ist. Der Aufwand für eine binäre Suche ist durch die maximale Anzahl der
Halbierungen des Feldes begrenzt.
C. Endreß
Anzahl der Halbierungen
0
1
2
3
Länge des zu
betrachtenden Teilfeldes
n
n/2
n/4
n/8
4 / 14
4
5
6
7
k
n/16 n/32 n/64 n/128 n/2k
10/2008
16. Suchen und Sortieren
Da die Mindestgröße des zu betrachtenden Teilfeldes 1 sein muss, ergibt sich die folgende Ungleichung:
n
2k
≥ 1.
Löst man diese Ungleichung durch Logarithmieren nach k auf, ergibt sich für die Anzahl der Halbierungen:
k ≤ log2 (n)
Das gesuchte Element ist also nach maximal log2(n) Halbierungsschritten gefunden.
Binäre Suche:
O(n) = log2 (n)
16.2 Sortieren
In vielen Anwendungen muss eine Folge von Objekten in eine bestimmte Reihefolge gebracht werden. Als
Beispiel sei hier auf ein Telefonverzeichnis hingewiesen, in dem die Einträge alphabetisch nach Namen zu
sortieren sind. Bei kaufmännisch-administrativen Anwendungen werden beispielsweise 25 % der
Computerzeit für Sortiervorgänge benötigt. Daher wurde intensiv nach effizienten Sortieralgorithmen
gesucht, die sich nach folgenden Kriterien klassifizieren lassen:
Zeitverhalten
Interne vs. externe Sortierung, d.h. passen die zu sortierenden Schlüssel alle in den Arbeitsspeicher oder
nicht.
Arbeitsspeicherverbrauch, d.h. wird zusätzlicher Speicherplatz außer dem Platz für die Schlüssel
benötigt.
Stabiles vs. instabiles Verhalten, d.h. die Reihenfolge von Elementen mit gleichem Sortierschlüssel wird
während des Sortierens nicht vertauscht. Stabilität ist oft erwünscht, wenn die Elemente bereits nach
einem zweitrangigen Schlüssel geordnet sind (z.B. Name, Vorname)
Sensibilität bezogen auf die Eingabeverteilung, d.h. verändert sich das Zeitverhalten, wenn die
Eingabefolge bereits sortiert oder vollständig unsortiert ist.
Allgemeines vs. spezielles Verhalten, d.h. wird nur eine lineare Ordnung auf der Menge der Schlüssel
vorausgesetzt oder müssen die Schlüssel von spezieller Gestalt sein.
Anhand dieser Kriterien ist für ein gegebenes Sortierproblem eine geeignete Auswahl zu treffen. Das
Hauptkriterium ist natürlich das Zeitverhalten. Dazu gilt folgender Satz:
Kein Sortierverfahren kommt mit weniger als n log2( n ) Vergleichen zwischen Schlüsseln aus.
C. Endreß
5 / 14
10/2008
16. Suchen und Sortieren
Die folgende Abbildung gibt einen Überblick über bekannte Sortierverfahren, die nach ihrem Zeitverhalten
klassifiziert sind.
Sortierverfahren
Für kleine n (n < 500)
Für große n (n >= 500)
Elementare Verfahren
Schnelle Verfahren
2
Schlüsselvergleiche: O(n) = n
(für zufällig sortierte Folgen)
Schlüsselvergleiche: O(n) = n log2(n)
Sortieren durch Auswahl
Mischsortieren
Sortieren durch Austauschen
Quicksort
Sortieren durch Einfügen
Sortieren mit einer Halde
(selection sort)
(mergesort)
(bubblesort, exchange sort)
(insertion sort)
16.2.1
(Heapsort)
Bubble-Sort
Bubble-Sort: Durchlaufe das Feld in aufsteigender Richtung. Betrachte dabei immer zwei benachbarte
Elemente. Wenn die zwei benachbarten Elemente nicht in aufsteigender Ordnung sind, vertausche sie. Nach
dem ersten Durchlauf ist auf jeden Fall das größte Element am Ende des Feldes.
Wiederhole den obigen Verfahrensschritt so lange, bis das Feld vollständig sortiert ist. Dabei muss jeweils
das letzte Element des vorherigen Durchlaufs nicht mehr betrachtet werden, da es schon seine endgültige
Position gefunden hat.
Beispiel:
C. Endreß
0
1
2
3
4
320
178
86
207
59
178
320
86
207
59
178
86
320
207
59
178
86
207
320
59
178
86
207
59
320
6 / 14
1. Durchgang
10/2008
16. Suchen und Sortieren
178
86
207
59
320
86
178
207
59
320
2. Durchgang
86
178
207
59
320
86
178
59
207
320
86
178
59
207
320
86
178
59
207
320
86
59
178
207
320
86
59
178
207
320
3. Durchgang
4. Durchgang
59
86
178
207
320
Der Name Bubble-Sort rührt daher, dass man das Wandern des größten Elements ganz nach rechts mit dem
Aufsteigen von Luftblasen vergleichen kann.
Der Algorithmus kann folgendermaßen mit einem Struktogramm formuliert werden:
void bubbleSort( int a[ ], int n )
int temp
for( int i = n - 1; i > 0; i-- )
for( int k = 0; k < i; k++ )
a[ k ] > a[ k + 1 ]
nein
ja
temp = a[ k ]
a[ k ] = a[ k + 1 ]
a[ k + 1 ] = temp
Bubble-Sort
C. Endreß
7 / 14
10/2008
16. Suchen und Sortieren
Aufwandsbetrachtung
Strukturell besteht der Bubble-Sort-Algorithmus aus einem Kern, der in zwei Schleifen eingebettet ist. Die
Laufzeit des Kerns hängt natürlich davon ab, ob eine Vertauschung durchzuführen ist oder nicht. Im
günstigsten Fall (best case) ist das Feld bereits sortiert und es findet keine Vertauschung von Elementen
statt. Im ungünstigsten Fall (worst case) erfolgt jedoch bei jedem Kerndurchlauf eine Vertauschung.
Wenn wir eine zufällige Verteilung der Daten annehmen, können wir davon ausgehen, dass in etwa der
Hälfte aller Kerndurchläufe eine Vertauschung durchzuführen ist. Der Kern wird also bei zufälliger Verteilung
eine mittlere Laufzeit haben, die etwa die Hälfte der zur Vertauschung von zwei Elementen benötigten
Rechenzeit ausmacht. Der Wert dieser Rechenzeit ist natürlich abhängig von der Plattform, auf der der
Algorithmus abläuft, sowie von dem Datentyp der Elemente. Strings oder Objekte benötigen eine wesentlich
längere Rechenzeit zur Vertauschung als elementare Datentypen.
Unter der oben gemachten Annahme einer mittleren Rechenzeit für den Algorithmuskern lässt sich die
Komplexität des Algorithmus nach folgender Überlegung berechnen:
Beim ersten Durchlauf durch das Feld läuft die Laufvariable k der inneren Schleife von 0 bis n-2. Die innere
Schleife wird also n-1 mal durchlaufen. Beim zweiten Durchlauf wird die innere Schleife nur noch n-2 mal
durchlaufen. Beim dritten Durchlauf sind es nur noch n-3 Läufe durch die innere Schleife. Beim letzten
Durchlauf wird die innere Schleife schließlich nur noch einmal durchlaufen. Daraus ergibt sich für die Summe
S der Läufe durch den Kern:
n −1
S = (n − 1) + (n − 2) + (n − 3) + . . . + 3 + 2 + 1 =
∑i
i =1
Zur Berechnung der Summe verdoppeln wir beide Seiten der Gleichung:
2 ⋅ S = (n − 1) + (n − 2) + (n − 3) + K +
1
+
2
+
3
+
2
+
+
1
3
+ K + (n − 3) + (n − 2) + (n − 1)
dass
der
2 ⋅ S = (n − 1) ⋅ n
S=
(n − 1) ⋅ n
2
Aus der oben hergeleiteten
Laufzeitverhalten aufweist.
Bubble Sort:
16.2.2
Formel
folgt,
Bubble-Sort-Algorithmus
ein
quadratisches
O(n) = n2
Insertion-Sort
Insertion-Sort ist ein Sortierverfahren, das so arbeitet wie wir Spielkarten auf der Hand sortieren.
Insertion-Sort: Die erste Karte ganz links ist sortiert. Wir nehmen die zweite Karte und stecken sie, je
nach Größe, vor oder hinter die erste Karte. Damit sind die beiden ersten Karten relativ zueinander sortiert.
Wir nehmen die dritte Karte und schieben sie solange nach links, bis wir an die Stelle kommen, an der sie
passt. Dort stecken wir sie hinein. Für alle weiteren Karten verfahren wir der Reihe nach ebenso.
C. Endreß
8 / 14
10/2008
16. Suchen und Sortieren
In einem Array funktioniert das natürlich nicht ganz so leicht wie in einem Kartenspiel, da wir nicht einfach
ein von rechts kommendes Element dazwischen schieben können. Dazu müssen zunächst alle
übersprungenen Elemente nach rechts aufrücken, um für das einzusetzende Element Platz zu machen.
0
1
2
3
4
320
178
86
207
101
unsortiertes Feld
86
207
101
1. Durchlauf
207
101
2. Durchlauf
101
3. Durchlauf
320
178
178
320
86
86
178
320
207
86
178
207
4. Durchlauf
320
101
86
101
178
207
320
sortiertes Feld
void insertionSort( int a [ ], int n )
int element, i, j
for( i = 1; i < n; i++ )
element = a[ i ]
j=i
while( j > 0 && element < a[ j - 1 ] )
a[ j ] = a[ j - 1 ]
j=j-1
a[ j ] = element
Insertion-Sort
C. Endreß
9 / 14
10/2008
16. Suchen und Sortieren
Aufwandsbetrachtung
Beim Insertion-Sort-Verfahren haben wir zwei ineinander verschachtelte Schleifen zu analysieren. Hierbei
wird jedoch die innere Schleife über eine zusätzliche Bedingung (element < a[ j – 1 ]) gesteuert. Bei zufällig
verteilten Daten können wir davon ausgehen, dass diese Bedingung im Mittel bei der Hälfte des zu
durchlaufenden Indexbereichs erfüllt ist, die Schleife also im Durchschnitt auf halber Strecke abgebrochen
werden kann. Bezeichnen wir die Laufzeit der inneren Schleife mit cins2 und die Laufzeit der äußeren Schleife
mit cins1, so ergibt sich die folgende Formel für das Laufzeitverhalten von Insertion-Sort:
t(n) =
t(n) =
n −1 ⎛
∑
i/2
⎞
⎟
⎜c
+
⎜ ins1
i=1 ⎝
∑c
n −1
i
⎞
⋅ c ins2 ⎟
2
⎠
⎛
∑ ⎜⎝ c
i =1
ins1
+
j =1
t(n) = c ins1(n − 1) +
ins 2 ⎟
c ins 2
⋅
2
t(n) = c ins1(n − 1) + c ins2 ⋅
⎠
n −1
∑i
i=1
n ⋅ (n − 1)
4
Auch hier haben wir wieder asymptotisch quadratisches Verhalten.
Insertion-Sort:
16.2.3
O(n) = n2
Quicksort
Das Quicksort-Verfahren wurde 1960 von C.A.R. Hoare erfunden ist eines der schnellsten
Sortierverfahren für Felder, die weitgehend unsortiert sind.
Der Grundgedanke von Quicksort verfolgt die Divide-and-Conquer-Strategie: eine Folge wird durch
Zerlegen in kleinere Teilfolgen sortiert.
Quicksort: Zu Beginn wird in dem zu sortierenden Feld ein als Pivotelement (Angelpunkt) bezeichnetes
Element beliebig gewählt. In einem Durchlauf werden dann die Feldelemente so miteinander vertauscht,
dass am Ende des Durchlaufs das Pivotelement das Feld in ein linkes Teilfeld und ein rechtes Teilfeld zerlegt.
Alle Elemente des linken Teilfeldes sind kleiner oder gleich dem Pivotelement, die Elemente des rechten
Teilfeldes sind größer oder gleich dem Pivotelement. Damit hat das Pivotelement seine endgültige Position in
dem Feld eingenommen. Mit den beiden Teilfeldern wird nun in gleicher Weise verfahren, bis ein Teilfeld nur
noch ein oder gar kein Element mehr enthält und somit sortiert ist.
C. Endreß
10 / 14
10/2008
16. Suchen und Sortieren
Beispiel:
Das Feld a[ ] mit 10 Elementen ist zu sortieren. In der Folge wird das Element in der
Feldmitte (a[m] = 60) als Pivotelement für eine Aufteilung der Folge in zwei Teilfolgen
ausgewählt.
0
1
2
3
4
5
6
7
8
9
76
7
58
88
60
41
82
77
49
86
Als nächstes wird die Folge von links durchsucht, bis ein Element gefunden wird, das größer
als das Pivotelement oder das Pivotelement selbst ist. Das ist bereits bei a[0] = 76 der Fall.
Von rechts wird die Folge durchsucht, bis ein Element gefunden wird, das kleiner als das
Pivotelement oder das Pivotelement selbst ist. Die Suche hält bei a[8] = 49 an. Die Elemente
a[0] und a[8] werden anschließend vertauscht.
0
1
2
3
4
5
6
7
8
9
76
7
58
88
60
41
82
77
49
86
Suche
von rechts
Suche
von links
Elemente vertauschen
Die Suche wird von links mit dem Index 1 und von rechts mit dem Index 7 fortgesetzt. Die
nächsten Elemente, die vertauscht werden sind im linken Teilfeld a[3] = 88 und im rechten
Teilfeld a[5] = 41.
0
1
2
3
4
5
6
7
8
9
49
7
58
88
60
41
82
77
76
86
Der linke Suchindex wird auf 4 gesetzt. Der rechte Index erhält ebenfalls den Wert 4. Die
Suchbereiche überschneiden sich damit und der Durchlauf wird beendet. Das Pivotelement hat
seinen endgültigen Platz erreicht. Im linken Teilfeld befinden sich nur noch Werte, die kleiner
als der Pivot a[4] sind. Die Werte im rechten Teilfeld sind größer als der Pivot.
0
1
2
3
4
5
6
7
8
9
49
7
58
41
60
88
82
77
76
86
Überschneidung beim Durchsuchen
Die beiden Teilfelder werden nun rekursiv nach demselben Verfahren sortiert.
Sortierung des linken Teilfelds a[0] . . . a[3]:
Rekursionsebene 1
C. Endreß
0
1
2
3
49
7
58
41
0
1
2
3
7
49
58
41
Als Pivot wird a[1] festgelegt. Die Suche von links
ergibt, dass a[0] größer als das Pivotelement ist.
Der rechte Index läuft bis zum Pivotelement, da
kein Element im rechten Feld kleiner als der Pivot
ist. a[0] und der Pivot a[1] werden getauscht.
Die Suchbereiche überschneiden sich, das
Pivotelement hat seine endgültige Position im
Feld bei a[0] erreicht. Das linke Teilfeld ist leer.
Das rechte Teilfeld a[1]...a[3] wird in der
nächsten Rekursionsebene bearbeitet
11 / 14
10/2008
16. Suchen und Sortieren
Rekursionsebene 2
Rekursionsebene 3
1
2
3
49
58
41
1
2
3
49
41
58
1
2
49
41
1
2
41
49
Das Pivotelement wird aus der Feldmitte (a[2])
gewählt. Das Element a[3] des rechten Feldes
wird mit dem Pivot vertauscht, da es kleiner ist.
Es kommt wieder zu einer Überschneidung der
Suchbereiche von links und rechts. Der Durchlauf
wird abgebrochen. Das linke Teilfeld a[1]...a[2]
wird in der nächsten Rekursionsebene sortiert.
Das Element a[1] wird als Pivot gewählt. Die
Elemente werden vertauscht.
Da das linke Teilfeld nur ein Element enthält, ist
es sortiert. Das rechte Teilfeld ist leer. Damit ist
die gesamte Teilfolge a[0]...a[3] links des ersten
Pivotelements a[4] sortiert.
Die Sortierung des rechten Teilfeldes a[5] . . . a[9] erfolgt analog:
Rekursionsebene 1
Rekursionsebene 2
C. Endreß
5
6
7
8
9
88
82
77
76
86
5
6
7
8
9
76
82
77
88
86
5
6
7
8
9
76
77
82
88
86
7
8
9
82
88
86
7
8
9
82
86
88
Die Suchbereichüberschneidung beendet den
Durchlauf. Das linke Teilfeld besteht nur noch aus
einem Element (a[5]) und ist deshalb sortiert. Das
Element a[6] befindet sich ebenfalls an seiner
endgültigen Position. Das rechte Teilfeld
a[7]...a[9] wird in der nächsten Rekursionsebene
sortiert.
Als Pivot wird wieder die Feldmitte gewählt (a[8])
Das Pivotelement hat seinen Platz erreicht. Die
linke Teilfolge wird wieder rekursiv sortiert.
12 / 14
10/2008
16. Suchen und Sortieren
Rekursionsebene 3
7
8
82
86
a[7] wird als Pivot gewählt. Die Suchbereiche
überschneiden sich. Die Elemente sind sortiert
Fertig sortierte Folge:
0
1
2
3
4
5
6
7
8
9
7
41
49
58
60
76
77
82
86
88
Der Quicksort-Algorithmus arbeitet rekursiv:
void quickSort( int UnterGrenze, int OberGrenze, int a[ ] )
int links = UnterGrenze
int rechts = OberGrenze
int pivot = a[ (links + rechts) / 2 ]
while ( a[ links ] < pivot )
links = links + 1
while ( pivot < a[ rechts ] )
rechts = rechts - 1
links <= rechts
ja
nein
vertausche( a[ links ], a[ rechts ] )
links = links + 1
rechts = rechts - 1
while ( links <= rechts )
UnterGrenze < rechts
ja
nein
quickSort ( UnterGrenze, rechts, a )
links < OberGrenze
ja
nein
quickSort( links, OberGrenze, a )
Quicksort
Aufwandsbetrachtung
Wenn wir eine zentrierte Lage des Pivots unterstellen, erfolgt eine fortlaufende Halbierung der Teilfelder, so
dass das Feld mit log2 n Halbierungen sortiert ist. Um jedes Element mit dem Pivot zu vergleichen, sind n–1
Vergleiche und im Mittel n/2 Vertauschungen notwendig. Daraus ergibt sich im Mittel ein Aufwand von
Quicksort:
C. Endreß
O(n) = n * log2 n
13 / 14
10/2008
16. Suchen und Sortieren
16.3 Vergleichen von Zeichenketten
Such- und Sortierverfahren werden häufig auf textuelle Daten angewendet wie z.B. Namen oder
Buchtitel. Es ist dann erforderlich, solche Zeichenketten mit einander zu vergleichen.
Zeichenketten sind in Java Objekte der Klasse String. Zum Vergleich von String-Objekten können die
logischen Operatoren <, >, ==, != nicht verwendet werden. Stattdessen stellt die Klasse String
Instanzmethoden zur Verfügung, mit welchen Zeichenketten verglichen werden können.
Methode
compareTo(String s)
compareToIgnoreCase(String s)
Beschreibung
Lexikalischer Vergleich zweier Strings:
Bei einem lexikalischen Vergleich werden die Zeichen
paarweise von links nach rechts verglichen. Tritt ein
Unterschied auf oder ist einer der Strings beendet, wird das
Ergebnis ermittelt.
Ist das aktuelle String-Objekt kleiner als s, wird ein negativer
Wert zurückgegeben. Ist es größer, wird ein positiver Wert
zurückgegeben. Bei Gleichheit liefert die Methode den
Rückgabewert 0.
equals(String s)
equalsIgnoreCase(String s)
Prüfen auf Gleichheit:
Die Methoden liefern true, wenn das aktuelle String-Objekt
und der Parameter s inhaltlich gleich sind.
Beispiel:
String name1 = “Adam“;
String name2 = “Eva“;
name1.compareTo(name2) liefert -1, weil “Adam“ im Lexikon vor “Eva“ steht.
C. Endreß
14 / 14
10/2008
Herunterladen