Post on 09-Sep-2019
Einf uhrung in die Informatik: Der Java-TeilEinfuhrung in die Informatik
Objektorientierte Programmierung mit
Geschichte,Organisatorisches, Literatur
Wolfram Burgard
1
Who am I?
Dozent:
Prof. Dr. Wolfram BurgardGebaude 079, Raum 1014Sprechstunden: n.V.Email: burgard@informatik.uni-freiburg.deTel: 0761 203-8006/8026http://www.informatik.uni-freiburg.de/˜burgard/
Informationen zur Vorlesung:http://www.informatik.uni-freiburg.de/˜ais/
2
Ubungen
Hauptverantwortliche Betreuerin:
Dipl.-Inf. Maren BennewitzGebaude 079, Raum 1020Email: maren@informatik.uni-freiburg.deTel: 0761 203-8025http://www.informatik.uni-freiburg.de/˜maren/
3
Literatur zum Java-Teil
Introduction to Programming Using Java: An Object-OrientedApproachDavid Arnow, Gerald Weiss, ISBN 0-201-61272-0
�Im Prinzip konnen auch andere Bucher verwendet werden, z.B. das Buch
”Einfuhrung in die Informatik – Objektorientiert mit Java“ von Kuchlin und
Weber erschienen im Springer-Verlag (ca. 30 Euro).� Eine Vielzahl weiterer Literatur zu Java (auch kostenlose, on-lineverfugbare Bucher) findet sich unterhttp://www.informatik.uni-freiburg.de/Java/
4
Klausur
� Termin: 25. Februar 2002, 14:00 bis 16:00 Uhr� Hilfsmittel: keine� Wer an dieser Abschlussklausur teilnehmen will, muss sich imPrufungsamt zu der Prufung in Informatik I anmelden.� Anmeldeschluss: Dienstag, 11. Dezember 2001
5
Erinnerung: Arten von Programmiersprachen
Maschinensprachen: Prozessorabhangiges Bitmuster, das die Ausfuhrungder Befehle steuert.Beispiel: FF54F330
Assemblersprachen: Verwendung von symbolischen Namen undmnemonischen Abkurzungen fur Bitmuster. Dadurch nur noch abhangigvon der Prozessorfamilie.Beispiel: call 48(%ebx,%esi,8)
Virtuelle Maschine:� imperative Programmiersprachen, z.B. FORTRAN, Pascal, C, oderAda�objektorientierte Programmiersprachen, z.B. Smalltalk, Eiffel, Cecil,Self, C++ oder Java
Abstraktes Modell: funktionale und logische Programmiersprachen,z.B. Scheme, Haskell, ML oder Prolog
6
Fakten zu Java
� Entwicklung geht auf den Anfang der 90er Jahre zuruck.�Java sollte eine objektorientierte, sichere und leicht zu erlernendeSprache sein.� Ursprungliches Ziel: Programmiersprache fur Benutzerinterfaces� Spater: Einsatz als plattformunabhangige Grundlage von Anwendungenfur das Internet.� 1997: JDK 1.1 (Java Development Kit)� Seit 1999 ist Java als OpenSource-Lizenz verfugbar.
7
Merkmale von Java
� einfach�objektorientiert� verteilt� portabel� dynamisch
8
Einfuhrung in die Informatik
Jumping into Java
Programme, Modelle, Objekte, Klassen, Methoden
Wolfram Burgard
9
Java, eine moderne, objektorientierte Sprache
Beispielprogramm:
import java.io.*;
class Program1 {
public static void main(String arg[]) {
System.out.println("This is my first Java program");
System.out.println("but it won’t be my last.");
}
}
Ausgabe dieses Programms:
This is my first Java program
but it won’t be my last.
10
Modelle
Modelle sind (vereinfachte) Darstellungen. Sie enthalten die relevantenEigenschaften eines modellierten Objektes.
Beispiel Kundendienst:� Stadtplan.� Nadeln markieren die Standorte der Kundendienstmitarbeiter.� Fahnchen markieren die Stellen, an denen Kunden warten.
Abstraktes, unvollstandiges Modell, welches aber fur die Zuordnung vonMitarbeitern zu Kunden gut geeignet sein kann.
11
Eigenschaften von Modellen
� Elemente eines Modells reprasentieren andere, komplexe Dinge.Nadeln reprasentieren die Kundendienstmitarbeiter.� Die Elemente eines Modells haben ein bestimmtes, konsistentesVerhalten.Nadeln reprasentieren Positionen und konnen an andere Stellen bewegtwerden.� Die Elemente eines Modells konnen entsprechend ihrem Verhaltenzu verschiedenen Gruppen zusammengefasst werden.Mitarbeiter fahren von Kunde zu Kunde, Kunden kommen hinzu undverschwinden wieder.� Aktionen von außen stoßen das Verhalten eines Modellelements an.Nadeln werden in der Zentrale per Hand bewegt.
12
Objekte
In Java heißen die Elemente eines Modells Objekte.
Kundendienstmodell Java-Modell
Die 43 Kundendienstmitarbeiterwerden durch 43 Nadeln
reprasentiert.
In Java wurden die 43Kundendienstmitarbeiter durch 43
Objekte modelliert.
Kunden werden durch Fahnchenmarkiert.
In Java wurden spezielleKunden-Objekte verwendet.
Wenn ein Kunde anruft, wird in
der Zentrale ein Fahnchen in dieKarte gesteckt.
In Java wurde ein Kunden-Objekt
erzeugt.
13
Verhalten
Alle Kundendienstmitarbeiter verhalten sich ahnlich: Sie fahren vonKunde zu Kunde und fuhren dort jeweils ihre Auftrage aus.
In Java haben alle Objekte fur Kundendienstmitarbeiter das gleicheVerhalten.
Auch Kundenobjekte haben ein gleiches Verhalten: Nach Auftragseingangwerden sie erzeugt. Sie werden geloscht, sobald der Auftrag ausgefuhrt ist.
In Java wird das Verhalten eines Objektes durch ein Programmstuckdefiniert, welches als Klasse bezeichnet wird.
14
Klassen und Instanzen
� Kategorien von Elementen eines Modells heißen in Java Klassen.� Die Definition einer Klasse ist ein Programmstuck (Code), welchesspezifiziert, wie sich die Objekte dieser Klasse verhalten.� Nachdem eine Klasse definiert wurde, konnen Objekte dieser Klasseerzeugt werden.� Jedes Objekt gehort zu genau einer Klasse und wird Instanz dieserKlasse genannt.
15
Vordefinierte Klassen
� Glucklicherweise mussen Programmierer das Rad nicht standig neuerfinden.� Java beinhaltet ein große Anzahl vordefinierter Klassen und Objekte.� Diese Klassen konnen verwendet werden, um auf einfache Weisekomplizierte Anwendungen und z.B. graphischeBenutzeroberflachen zu realisieren.
16
Ein einfaches Beispiel: Der Monitor
Programm Monitor Benutzer
Erika und Erol sitzen vor zwei unterschiedlich großen Bildschirmen. Erika:
”Erol, wurdest Du bitte die Helligkeit des großen erhohen?“
Informationen in dieser Frage: des großen die Helligkeit soll verstellt werdennach oben
17
Java-Terminologie:
Monitor
Benutzer
ändere Helligkeit:nach oben
Senderan den großen
Empfänger”Des großen“ ist eine Referenz , an die eine Nachricht (Message)
geschickt wird. Die Nachricht spezifiziert ein Verhalten (Helligkeit verstellen) und enthalt weitere Details (nach oben).
18
Reprasentation in Java
� In Java wird der Bildschirm durch ein vordefiniertes Objektreprasentiert.� Dieses Objekt ist Instanz der Klasse PrintStream.� Objekte der Klasse PrintStream erlauben das Ausgeben vonZeichenketten.
Monitorvordefinierte Instanz der KlasseSystem.out
referenziert
PrintStream
modelliert
Objekt� Eine Referenz in Java ist eine Phrase, die einen Bezug zu einemObjekt herstellt. Wir sagen, dass sie das Objekt referenziert.� System.out referenziert ein Objekt der Klasse PrintStream, welchesden Monitor modelliert. System.out ist eine Referenz auf das Objekt.
19
Nachrichten/Messages in Java
� Um einen Text auf dem Bildschirm auszugeben, schickt man in Java eineNachricht an das durch Objekt System.out referenzierte Objekt.� Dieses Message spezifiziert ein Verhalten, welches die KlassePrintStream zur Verfugung stellt.� Hier ist diese Nachricht das Ausgeben einer Zeile, welche println
genannt wird.�Die weiteren Details sind dabei die Zeichen, die ausgegeben werdensollen, z.B.:
”Welcome to Java!“.
Zusammenfassend:� Referenz auf den Monitor: System.out� Nachricht: println� Notwendige Details: ("Welcome to Java!")20
Verschicken einer Nachricht an das System.out-Objekt
In Java bestehen Nachrichten aus folgende Komponenten: Der Name des Verhaltens (z.B. println) Die weiteren Informationen (z.B. "Welcome to Java!"), die in Klammerneingeschlossen wird.
In Java verwenden wir Phrasen, die aus folgenden Komponenten bestehen, um eineNachricht an den entsprechenden Empfanger zu senden: einer Referenz auf das Empfangerobjekt (z.B. System.out) gefolgt von einem
Punkt und der Nachricht, die wir verschicken wollen.
Unser Beispiel: System.out.println("Welcome to Java!")
Referenz auf das Objekt
DetailsVerhalten
Nachricht21
Java Statements
� Das Versenden einer Nachricht an ein Objekt ist eine vom Programmiererspezifizierte Aktion.� Diese Aktion wird vom Computer ausgefuhrt, wenn das Programm lauft.�In Java werden alle Aktionen durch Statements spezifiziert.
Um ein Statement zu erzeugen, welches unsere Ausgabe-Aktion durchfuhrt,schreiben wir die entsprechende Nachricht hin und fugen ein Semikolonhinzu:
System.out.println("Welcome to Java!");
22
Ein Java-Programm
Ziel: Ausgabe der beiden Zeilen
This is my first Java program
but it won’t be my last.
Wir wissen:� Es gibt ein vordefiniertes Objekt (referenziert durch System.out), dessenVerhalten auch das Ausgeben von Zeichen auf dem Bildschirm einschließt.� Dieses Verhalten wird aktiviert, indem wir diesem Objekt eine Nachricht mit demNamen println schicken.� Die weiteren Details sind die Argumente, die das Objekt benotigt, um dasVerhalten auszufuhren (hier die Zeile, die ausgegeben werden soll).
Um zwei Zeilen auszugeben, schicken wir einfach zwei Nachrichten:
System.out.println("This is my first Java program");
System.out.println("but it won’t be my last.");23
Das komplette Programm
Um ein gultiges Programm zu erhalten, mussen wir seinen Namen festlegen und
zusatzlichen Text hinzufugen. Wir nennen Namen Identifier oder Bezeichner.
Ein Identifier ist eine Folge von Buchstaben, Ziffern und Unterstrichen. Das ersteZeichen muss ein Buchstabe sein.
Beispiele: Program1, class, x7, xyp xrz
Dies ergibt das bereits bekannte Programm:
import java.io.*;
class Program1 {
public static void main(String arg[]) {
System.out.println("This is my first Java program");
System.out.println("but it won’t be my last.");
}
}
24
Einige Regeln (1)
Identifier: Klassen und Nachrichten mussen einen Bezeichner haben. Dabeiwird zwischen Groß- und Kleinschreibung unterschieden.
Keywords: Keywords oder Schlusselworte sind spezielle Worte mit einervordefinierten Bezeichnung, wie z.B. System, println, static, class. . .
Reihenfolge von Statements: Statements werden in der Reihenfolgeausgefuhrt, in der sie im Programm stehen.
System.out.println("This is my first Java program");
System.out.println("but it won’t be my last.");
erzeugt eine andere Ausgabe als
System.out.println("but it won’t be my last.");
System.out.println("This is my first Java program");
25
Einige Regeln (2)
Formatierung: Halten Sie sich bei der Erstellung von Java-Programmen anfolgende Regeln:
1. Jede Zeile enthalt hochstens ein Statement.
2. Verwenden Sie die Tabulator-Taste, um Statements einzurucken.
3. Halten Sie sich an den Stil dieser Vorlesung.
Vorteile:
1. Programme sind leichter lesbar,
2. leichter zu verstehen und damit auch
3. leichter zu warten.
26
Einige Regeln (3)
Prinzipiell ist Java sehr flexibel.
Eine der Hauptregeln: Bezeichner mussen durch ein Leerzeichengetrennt sein.
classProgram1 entspricht nicht class Program1.
Zulassig ware aber z.B.:
import java.io.*; class Program1 { public static void
main(String arg[]) { System.out.println("This is my first
Java program"); System.out.println("but it won’t be my
last."); } }
27
Einige Regeln (4)
Kommentare: Java erlaubt dem Programmierer, Kommentare in den Codeeinzufugen. Es gibt zwei Arten:
1. Eingeschlossene Kommentare, d.h. Text, der zwischen /* und */
eingeschlossen ist:
/*
* This program prints out several greetings
*/
2. Zeilenkommentare: Alle Zeichen hinter einem // werden alsKommentare angesehen:
// This program prints out several greetings
28
Hinweise zur Verwendung von Kommentaren
1. Vor jeder Zeile, die mit dem Wort class beginnt, sollte ein Kommentarstehen, der die Klasse erklart.
2. Kommentare sollten dazu dienen, Dinge zu erklaren, die nicht unmittelbaraus dem Code abgelesen werden konnen.
3. Kommentare sollten nicht in der Mitte von einem Statement auftauchen.
29
Ein Programm mit Kommentaren
import java.io.*;
/*
* This program prints my first Java experience and my
* intent to continue.
*/
class Program1 {
public static void main(String arg[]) {
System.out.println("This is my first Java program");
System.out.println("but it won’t be my last.");
}
}
30
Vom Programm zur Ausfuhrung
Um ein Programm auf einem Rechner ausfuhren zu konnen, mussen wir
1. das Programm fur den Computer zuganglich machen,
2. das Programm in eine Form ubersetzen, die der Rechner versteht, und
3. den Computer anweisen, das Programm auszufuhren.
31
Schritt 1: Eingeben des Programms
Um ein Programm fur den Rechner zuganglich zu machen, mussen wir eineProgrammdatei erstellen, d.h. eine Textdatei, die den Programmtextenthalt.
In Java muss der Name der Datei die Form X.java haben, wobei X derProgrammname ist.
32
Schritt 2: Ubersetzen des Programms
� Computer konnen Java-Programme nicht direkt ausfuhren.� Computer sind nur in der Lage, Anweisungen einer primitiven und sogenannten Maschinensprache auszufuhren.� Da wir eine abstrakte Hochsprache verwenden, mussen wir eineMoglichkeit finden, unsere Programme in Maschinen-Code zuuberfuhren.� Dafur gibt es verschiedene Modelle: Am weitesten verbreitet sindCompiler und Interpreter.
33
Das Compiler-Modell
Das Programm wird direkt in Maschinencode ubersetzt und kann vomRechner unmittelbar ausgefuhrt werden.
{ int x = 17+4;
}
pushl %ebppushl %ebxpushl %esipushl %edimovl %esp,%ebpsubl $4,%espmovl 21,-4(%ebp)
addl $4,%espmovl %ebp,%esp
popl %esi
pushl -4(%ebp)call print
popl %edi
main(...)
print(...,x);Assembler code
C code
C Compiler
Pentium Prozessor
34
Das Interpreter-Modell
Prinzip:�Das Programm wird von dem Interpreter direkt ausgefuhrt.� Der Interpreter verwendet fur jedes Statement bei der Ausfuhrung dieentsprechende Sequenz von Maschinenbefehlen.
Vergleich zum Compiler:
1. Das Programm muss nicht erst ubersetzt werden.
2. Die Interpretation ist langsamer als die Ausfuhrung kompilierterProgramme.
35
Das Java-Modell
Java verwendet einen Interpreter und einen Compiler:� Java ubersetzt zunachst in eine Byte Code genannte Zwischensprache.� Der Byte Code wird dann von der Java Virtual Machine interpretiert.
main(...){ int x = 17+4; print(x);}
Java
allocate xload 17load 4addstore xfetch xcall print
Java Compiler
Byte code
Byte code Interpreter
36
Erzeugung eins Ausfuhrbaren Java-Programms
Java byte code
Editor
Java Program
Java compiler
37
Programmierfehler
Wir unterscheiden grob 2 Arten von Fehlern:
1. Compile-Zeit-Fehler (Compile-Time Errors) werden bei der Ubersetzungentdeckt und fuhren zum Abbruch des Ubersetzungsprozesses.
2. Laufzeitfehler (Run-Time Errors) treten erst bei der Ausfuhrung auf undliefern falsche Ergebnisse oder fuhren zum Absturz des Programms.
Laufzeitfehler sind haufig schwerer zu entdecken!
38
Beispiel fur einen Compile-Time Error
import java.io.*;
class Program1 {
public static void main(String arg[]) {
Sistem.out.println("This is my first Java program");
Sistem.out.println("but it won’t be my last.");
}
}
39
Ausgabe des Compilers
javac Program1sistem.java
Program1sistem.java:4: cannot resolve symbol
symbol : class out
location: package Sistem
Sistem.out.println("This is my first Java program");
ˆ
Program1sistem.java:5: cannot resolve symbol
symbol : class out
location: package Sistem
Sistem.out.println("but it won’t be my last.");
ˆ
2 errors
Compilation exited abnormally with code 1 at Mon Dec 10 20:30:09
40
Beispiel fur einen Run-Time Error
import java.io.*;
class Program1last {
public static void main(String arg[]) {
System.out.println("This is my first Java program");
System.out.println("but it will be my last.");
}
}
Das Programm kann compiliert und ausgefuhrt werden und liefert dieAusgabe:
This is my first Java program
but it will be my last.
41
Zusammenfassung (1)
� Programme sind Texte, die ein Computer ausfuhrt, um eine bestimmteAufgabe durchzufuhren.� Java-Programme konnen aufgefasst werden als Modelle, die Objekteund ihr Verhalten beschreiben.� Objekte sind in Java die grundlegenden Modellierungskomponenten.� Objekte mit gleichem Verhalten werden in Kategorien, bzw. Klassenzusammengefasst.� Das Verhalten eines Objektes wird durch das Senden einer Nachrichtan dieses Objekt angestoßen.
42
Zusammenfassung (2)
� Die Verhalten der Objekte werden durch Programmstucke beschrieben,die aus einzelnen Statements bestehen.� Eine typische Form eines Statements wiederum ist das Versendeneiner Nachricht an ein Objekt.� Um einem Objekt eine Nachricht zu senden, verwendet man eineReferenz auf dieses Objekt und den entsprechenden Methodennamen.� Java-Programme werden in Java Byte Code ubersetzt.� Dieser Byte Code wird dann von dem Java-Interpreter auf dem Rechnerausgefuhrt.
43
Einfuhrung in die Informatik
Objekte
Referenzen, Methoden, Klassen, Variablen
Wolfram Burgard
44
Verwendung von PrintStream-Objekten
Wenn wir die Nachricht
println("something to display")
an das durch System.out referenzierte PrintStream-Objekt senden, fuhrtdas zur Ausgabe des entsprechenden Textes auf dem Bildschirm.
Jedes weitere Zeichen danach wird am Beginn der nachsten Zeileausgegeben.
45
Die Nachrichten print und println()
Alternative:
print("something to display")
Bei Verwendung von print erscheint das nachste gedruckte Zeichen in dergleichen Zeile:
System.out.print("JA");
System.out.print("VA");
ergibt die AusgabeJAVA.
Eine Variante der println-Nachricht ist println(). Die Nachricht
System.out.println();
bewirkt, dass nachfolgende Ausgaben in der nachsten Zeile beginnen.
46
Referenzen
� Eine Referenz in Java ist jede Phrase, die sich auf ein Objekt bezieht.� Referenzen werden verwendet, um dem entsprechenden Objekt eineNachricht zu schicken.� Streng genommen ist System.out kein Objekt sondern nur eineReferenz.
Bezeichnung: Das System.out-Objekt
47
Ausfuhren von Nachrichten
� Java-Statements werden in der Reihenfolge ausgefuhrt, in der sie imProgramm stehen.� Wenn eine Message an ein Objekt (den Empfanger) geschickt wird, wirdder Code des Senders unterbrochen, bis der Empfanger die Nachrichterhalten hat.� Der Empfanger fuhrt die durch die Nachricht spezifizierte Methode aus.Dies nennen wir
”Aufrufen einer Methode“.� Wenn der Empfanger die Ausfuhrung seines Codes beendet hat, kehrt
die Ausfuhrung zum Code des Senders zuruck.
48
Die String-Klasse
� String ist eine vordefinierte Klasse.� Sie modelliert Folgen von Zeichen (Characters).� Zu den zulassigen Zeichen gehoren Buchstaben, Ziffern,Interpunktionssymbole, Leerzeichen und andere, spezielle Symbole.� In Java werden alle Folgen von Zeichen, die in Hochkommataeingeschlossen sind, als Referenzen auf Zeichenketten interpretiert.
referenziert"Hello" Eine Instanz vonString
modelliertHello
Referenz Objekt durch das Objektmodellierte Zeichenkette
49
Die String-Methode toUpperCase
Eine Methode, die von der String-Klasse zur Verfugung gestellt wird, isttoUpperCase, welche keine Argumente hat.
Um eine Nachricht an die String-Klasse zu senden, verwenden wir dieubliche Notation:
Referenz . Methodenname ( Argumente )
Anwendungsbeispiel: "ibm".toUpperCase()
Der Empfanger der toUpperCase-Nachricht ist das String-Objekt,welches durch "ibm" referenziert wird.
eine Instanz von
modelliert
String,Referenz "ibm"
"ibm"
referenziert
toUpperCase()Nachricht
die die Zeichenkette
Empfänger−Objekt
50
Effekte von String-Methoden
� Eine Methode der Klasse String andert nie den Wert desEmpfangers.� Stattdessen liefert sie als Ergebnis eine Referenz auf ein neuesObjekt, an welchem die entsprechenden Anderungen vorgenommenwurden.
Beispiel:� "ibm".toUpperCase() sendet nicht nur die toUpperCase-Nachrichtan das "ibm"-Objekt.� Der Ausdruck liefert auch eine Referenz auf ein neues "IBM"-Objekt.� Wir sagen:
”Der Return-Wert von "ibm".toUpperCase() ist eine
Referenz.“
51
Beispiel
Da die println-Methode der Klasse PrintStream eine Referenz auf einString-Objekt verlangt, konnen wir schreiben:
System.out.println("ibm".toUpperCase());
"ibm".toUpperCase()
Referenz
Referenz
neu erzeugtes Objekt
String Objekt, das
Objekt, dasString
modelliertibm
modelliertIBM
52
Methoden, Argumente und Return-Werte
Eigenschaften der bisher betrachteten Methoden:
Klasse Methode Return-Wert Argumente
PrintStream println kein kein
PrintStream println kein Referenz auf einString-Objekt
PrintStream print kein Referenz auf ein
String-Objekt
String toUpperCase Referenz auf ein
String-Objekt
kein
Signatur einer Methode: Bezeichnung der Methode plus Beschreibungseiner Argumente
Prototyp einer Methode: Signatur + Beschreibung des Return-Wertes
53
Methoden ohne Return-Wert
� Viele Methoden liefern eine Referenz auf ein Objekt zuruck.� Das gilt insbesondere fur Methoden, die ein Ergebnis liefern, wie z.B. dieString-Methode toUpperCase(), die eine Referenz auf einString-Objekt liefert.� Aber welchen Typ sollen Methoden wie println() haben, die keinErgebnis liefern?� Wir sagen:
”Methoden ohne Ergebnis haben den Typ void.“
54
Referenz-Variablen
� Eine Variable ist ein Bezeichner, dem ein Wert zugewiesen werden kann,wie z.B. sei � � � .� Sie wird Variable genannt, weil sie zu verschiedenen Zeitpunktenverschiedene Werte annehmen kann.� Eine Referenz-Variable ist eine Variable, deren Wert eine Referenz ist.
Angenommen line ist eine String-Referenz-Variable mit folgenderReferenz als Wert: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Dann konnen wir mit folgenden Anweisungen drei Zeilen von x-en ausgeben:
System.out.println(line);
System.out.println(line);
System.out.println(line);
Moglich ist auch: System.out.println(line.toUpperCase());55
Deklaration von Referenz-Variablen und Wertzuweisungen
� Um in Java eine Referenz-Variable zu deklarieren, geben wir die Klasseund den Bezeichner an und schließen mit einem Semikolon ab:
String greeting; // Referenz auf einen Begruessungs-String
PrintStream output; // Referenz auf dasselbe PrintStream-Objekt
// wie System.out
Um einer Variable einen Wert zu geben, verwenden wir eine so genannteWertzuweisung:
Variable = Wert;
Beispiele:
output = System.out;
greeting = "Hello";
Wir konnen jetzt schreiben:output.println(greeting.toUpperCase());
56
Effekt einer Wertzuweisung an eine Referenz-Variable
Nach
greeting = "Hello";
referenzieren "Hello" und greeting dasselbe Objekt.
eine Instanz von StringObjekt durch das Objekt
modellierte Zeichenkette
Hello
greeting
Referenzvariable
"Hello"Referenz referenziert
57
Wertzuweisung versus Gleichheit
Betrachte
t = "Springtime";
t = "Wintertime";� Eine Wertzuweisung ordnet einer Variablen den Wert auf der rechtenSeite des Statements zu.� Der bisherige Wert der Variablen geht verloren.� Nach der ersten Zuweisung ist der Wert von t die Referenz auf das durch"Springtime" referenzierte String-Objekt.� Nach der zweiten Zuweisung ist der Wert von t die Referenz auf das"Wintertime"-Objekt.� Wir sagen:
”Eine Wertzuweisung ist imperativ .“� Variablen enthalten immer nur den letzten, ihnen zugewiesenen Wert.
58
Rollen von Variablen
Je nachdem, wo Variablen auftreten, konnen sie
1. Informationen speichern oder
2. den in ihnen gespeicherten Wert reprasentieren.
Beispiele:
String s, t;
s = "Springtime";
t = s; Die Erste Zuweisung speichert in s die Referenz auf das"Springtime"-Objekt (Fall 1). Die zweite Zuweisung bewirkt, das t und s dasselbe Objekt referenzieren(Fall 1 fur t und Fall 2 fur s).
59
Mehrere Referenzen auf dasselbe Objekt
String s, t;
s = "ACM";
t = s;
String −Objekt Referenzvariable
t
Referenzvariable
s
String−Konstante"ACM"
ACM
referenziert referenziertreferenziert
60
Unabhangigkeit von Variablen
String s;
String t;
s = "Inventory";
t = s;! Nach der Anweisung t = s referenziert t dasselbe Objekt wie s.! Wenn wir anschließend s einen neuen Wert zuweisen, z.B. mits = "payroll", andert das den Wert fur t nicht.! Durch t = s wird lediglich der Wert von s in t kopiert.! Eine Wertzuweisung realisiert keine permanente Gleichheit.! Variablen sind unabhangig voneinander, d.h. ihre Werte konnenunabhangig voneinander geandert werden.
61
Reihenfolge von Statements (erneut) (1)
String greeting;
String bigGreeting;
greeting = "Yo, World";
bigGreeting = greeting.toUpperCase();
System.out.println(greeting);
System.out.println(bigGreeting);
Ausgabe:
Yo, World
YO, WORLD
62
Reihenfolge von Statements (erneut) (2)
Alternativ dazu hatten wir auch die folgende Reihenfolge verwenden konnen:
String greeting;
greeting = "Yo, World";
System.out.println(greeting);
String bigGreeting;
bigGreeting = greeting.toUpperCase();
System.out.println(bigGreeting);
Deklarationen konnen an jeder Stelle auftauchen, vorausgesetzt sie gehenjedem anderen Vorkommen der deklarierten Variablen voraus.
Deklarationen konnen auch so genannte Initialisierungen enthalten:
String greeting = "Yo, World";
63
Weitere String-Methoden
Einige Prototypen von Methoden der String-Klasse:
Methode Return-Wert Argumente
toUpperCase String-Objekt-Referenz keine
toLowerCase String-Objekt-Referenz keine
length Eine Zahl keine
trim String-Objekt-Referenz keine
concat String-Objekt-Referenz String-Objekt-Referenz
substring String-Objekt-Referenz eine Zahl
substring String-Objekt-Referenz zwei Zahlen
64
Eigenschaften dieser Methoden
" length gibt die Anzahl der Zeichen in dem Empfangerobjekt zuruck.
"Hello".length() ist der Wert # ." trim liefert eine Referenz auf ein String-Objekt, welches sichdurch das Argument dadurch unterscheidet, dass fuhrende odernachfolgende Leer- oder Tabulatorzeichen fehlen.
" Hello ".trim() ist eine Referenz auf "Hello"."concat liefert eine Referenz auf ein String-Objekt, welches sichdurch das Anhangen des Argumentes an das Empfangerobjektergibt.
"ham".concat("burger") ist eine Referenz auf "hamburger".
65
Wirkung der Methode concat
String s, t, u;
s = "ham";
t = "burger";
u = s.concat(t);
Objektstringein Argument
s
Referenz ham
neu erzeugtes Objekthamburger
concat (t)
burger
referenziertNachricht
Empfängerobjekt
erreicht
66
Die Varianten der Methode substring
$substring erzeugt eine Referenz auf eine Teilsequenz derZeichenkette des Empfangerobjekts.
In Java startet die Nummerierung bei % .$ Die Variante mit einem Argument gibt eine Referenz auf den Teilstringzuruck, der an der durch den Wert des Argumentes spezifiziertenPosition beginnt.$ Die Version mit zwei Argument gibt eine Referenz Teilstring, der ab derdurch den Wert des ersten Argumentes gegebenen Position beginnt undunmittelbar vor dem durch das zweite Argument gegebenen Zeichenendet.
67
Funktion der Methode substring mit einem Argument
String s, t;
s = "hamburger";
t = s.substring(3);
h a m b u r g e r0 1 2 3 4 5 6 7 8
h a m b u r g e r0 1 2 3 4 5 6 7 8
h a m b u r g e r0 1 2 3 4 5 6 7 8
an Position 3substring beginnend
vierter character
erster character
68
Funktion der Methode substring mit einem Argument
String s, t;
s = "hamburger";
t = s.substring(3);
hamburger
Empfängerobjekt
s
Referenz
burger
neu erzeugtes Objekt
substring(3)
Nachricht
erreichtreferenziert
erzeugt
69
Funktion der Methode substring mit zwei Argumenten
String s, t;
s = "hamburger";
t = s.substring(3,7);
h a m b u r g e r0 1 2 3 4 5 6 7 8
substring endet hiersubstring beginnt hier
hamburger.substring(3,7)hamburger, from 3 to 7
70
Beispiel: Finden des mittleren Zeichens einer Zeichenkette
String word = "antidisestablishmentarianism";
Zur Berechnung der mittleren Position des Wortes verwenden wir
word.length() / 2
Das mittlere Zeichen berechnen wir dann mit:
word.substring(word.length()/2, word.length()/2 + 1);
71
Das komplette Programm
import java.io.*;
class Program2 {
public static void main(String[] arg) {
String word = "antidisestablishmentarianism";
String middle;
middle = word.substring(word.length()/2,
word.length()/2 + 1);
System.out.println(middle);
}
}
Ausgabe des Programms:
s
72
Uberladung/Overloading
&Die String-Klasse hat zwei Methoden mit demselben Namensubstring.&Beide Methoden haben verschiedene Signaturen, denn sie unterscheidensich in den Argumenten, die sie benotigen.&Und sie haben unterschiedliche Funktionalitat.& Methoden mit gleichem Namen und unterschiedlichen Signaturen heißenuberladen bzw. overloaded.& Der Technik, Klassen mit mehreren Methoden gleichen Namens zuentwerfen, heißt Uberladung bzw. Overloading.
73
Kaskadieren von Methodenaufrufen
String s1 = "ham", s2 = "bur", s3 = "ger", s4;
Um eine Referenz auf die Verkettung der drei durch s1, s2, und s3
referenzierten String-Objekte zu erzeugen, mussten wir schreiben:
s4 = s1.concat(s2);
s4 = s4.concat(s3);
Dies geht jedoch einfacher mit s4 = s1.concat(s2).concat(s3);
Funktionsweise:
1. Die Message concat(s2) wird an s1 gesendet.
2. Die Message concat(s3) wird an das Ergebnisobjekt gesendet.
3. Die Referenz auf das dadurch erzeugte String-Objekt wird s4
zugewiesen.
74
Funktion kaskadierter Nachrichten
String firstInit = "J", middleInit = "F", lastInit = "K";
System.out.println(firstInit.concat(middleInit).concat(lastInit));
J
JF
JFK
concat(middleInit)
F
K
Nachricht referenziertconcat(lastInit)
Eine concatNachricht wird an das"J" Objekt gesendet
Das "J" Objekt empfängt eineconcat Nachricht, erzeugt das"JF" Objekt und liefert eineReferenz darauf zurück
firstInit
referenziert Nachricht referenziert
Eine concatNachricht wird an das"JF" Objekt gesendetDas
concat
Referenz darauf zurück
"JF"Nachricht, erzeugt dasObjekt empfängt eine
"JFK" Objekt und liefert eine
75
Schachteln von Methodenaufrufen / Komposition
Alternativ zu Kaskaden wie in
s4 = s1.concat(s2).concat(s3);
konnen wir Methodenaufrufe auch schachteln, was einer Kompositionentspricht:
s4 = s1.concat(s2.concat(s3));
Auswertung des Ausdrucks auf der rechten Seite:
1. Die Message concat(s3) wird an s2 gesendet.
2. An das s1-Objekt wird eine concat-Nachricht geschickt, die alsArgument eine Referenz auf das in Schritt 1 erzeugte Ergebnisobjekt hat.
3. Die Referenz auf das dadurch erzeugte String-Objekt wird schließlichs4 zugewiesen.
76
Wirkung der Komposition
String firstInit = "J", middleInit = "F", lastInit = "k";
System.out.println(firstInit.concat(middleInit.concat(lastInit)));
F
middleInit.concat(lastInit)
K
J
JFK
FK
firstInit.concat( )
referenziert referenziertreferenziert
referenziert
referenziert
77
Weitere Eigenschaften von String-Objekten
'String-Objekte konnen nicht verandert werden, da alle Funktionen alsReturn-Wert neue Objekte liefern.' Der leere String "" hat die Lange 0, d.h. "".length() liefert 0.
78
Zusammenfassung (1)
( Das Verhalten von Objekten wird durch Methoden spezifiziert.( Die Signatur einer Methode besteht aus dem Namen der Methode sowieder Anzahl und den Typen der Argumente.( Der Prototyp einer Methode ist die Signatur zusammen mit demReturn-Wert.( Wird eine Nachricht an ein Objekt gesendet, wird der Code desAufrufers unterbrochen bis die Methode ausgefuhrt worden ist.( Einige Methode haben Return-Werte. Wenn eine Methode keinenReturn-Wert hat, ist der Return-Typ void.( Variablen konnen Werte zugeordnet werden.
79
Zusammenfassung (2)
) Verschiedene Variablen sind unabhangig voneinander.) Jede Variable hat zu einem Zeitpunkt nur einen Wert.) Wertzuweisungen sind destruktiv, d.h. sie loschen denvorhergehenden Wert der Variablen.) Referenzwerte, die von Methoden zuruckgegeben werden, konnenVariablen zugewiesen werden.) Return-Werte konnen aber auch Empfanger neuer Nachrichten sein.Dies heißt Kaskadierung.) Return-Werte konnen auch als Argumente verwendet werden. DieserProzess heißt Komposition.
80
Einfuhrung in die Informatik
Klassen
Konstruktoren, Methoden, Implementierung, Dateien
Wolfram Burgard
81
Erzeugen von Objekten
* Jede Klasse hat wenigstens eine Methode zum Erzeugen von Objekten.* Solche Methoden heißen Konstruktoren.* Der Name eines Konstruktors stimmt stets mit dem der Klasse uberein.* Wie andere Methoden auch, konnen Konstruktoren Argumente haben.* Da wir mit Konstruktoren neue Objekte erzeugen wollen, konnen wir siean kein Objekt senden.* In Java verwenden wir das Schlusselwort new, um einen Konstruktoraufzurufen:
new String("hello world")* Dies erzeugt ein String-Objekt und sendet ihm die NachrichtString("hello world").
82
Die Operation new
+ Das Schlusselwort new bezeichnet eine Operation, die einen Wertzuruckgibt.+ Der Return-Wert einer new-Operation ist die Referenz auf das neuerzeugte Objekt.+ Wir nennen new einen Operator.
83
Sichern neu erzeugter Objekte
, Der new-Operator liefert uns eine Referenz auf ein neues Objekt., Um dieses Objekt im Programm verwenden zu konnen, mussen wir dieReferenz in einer Referenzvariablen sichern.
Beispiel:
String s, t, upper, lower;
s = new String("Hello");
t = new String(); // identisch mit ""
upper = s.toUpperCase();
lower = s.toLowerCase();
System.out.println(s);
84
Dateien
- Bisher gingen alle Ausgaben nach Standard output, d.h. auf den Monitor.-Der Vorteil von Dateien ist die Persistenz, d.h. die Information bleibtdauerhaft erhalten.
Grundlegende Eigenschaften von Dateien
Dateiname: Ublicherweise setzen sich Dateinamen aus Zeichenkettenzusammen.
Inhalt (Daten) : Die Daten konnen beliebige Informationen sein: Brief,Einkaufsliste, Adressen, . . .
85
Grundlegende Datei-Operationen
. Erzeugen einer Datei. In eine Datei schreiben.. Aus einer Datei lesen.. Eine Datei loschen.. Den Dateinamen andern.. Die Datei uberschreiben, d.h. nur den Inhalt verandern.
86
Die File-Klasse
/ Java stellt eine vordefinierte Klasse File zur Verfugung/ Der Konstruktor fur File nimmt als Argument den Dateinamen.
Beispiel:
File f1, f2;
f1 = new File("letterToJoanna");
f2 = new File("letterToMatthew");
Hinweis:Wenn ein File-Objekt erzeugt wird, bedeutet das nicht, dass gleichzeitig auchdie Datei erzeugt wird.
87
Dateien Umbenennen und Loschen
0 Existierende Dateien konnen in Java mit rename umbenannt werden.0 Mit der Methode delete konnen vorhandene Dateien geloscht werden.
Prototypen:
Methode Return-Wert Argumente Aktion
delete void keine loscht die Datei
rename void File-Objekt-Referenz nennt die Datei um
88
Ausgabe in Dateien
1 In Java verwenden wir so genannte (Ausgabe-) Strome bzw. (Output-)Streams, um Dateien mit Inhalt zu fullen.1 Die Klasse FileOutputStream stellt einen solchen Strom zurVerfugung.1 Der Konstruktor von FileOutputStream akzeptiert als Argument eineReferenz auf ein File-Objekt.1 Die Datei mit dem durch das Argument gegebenen Namen wird geoffnet.1 Ist die Datei nicht vorhanden, so wird sie erzeugt.1 Ist die Datei vorhanden, wird ihr Inhalt geloscht.
Beispiel:
File f = new File("Americas.Most.Wanted");
FileOutputStream fs = new FileOutputStream(f);89
Wirkung von FileOutputStream
2 FileOutputStream modelliert die Ausgabe als eine Sequenz vonkleinen, uninterpretierten Einheiten bzw. Bytes.2 Sie stellt keine Moglichkeit zur Verfugung, die Daten zu gruppieren.2 Methoden wie println zum Ausgeben von Zeilen werden nicht zurVerfugung gestellt.
Java Program Disc
File onFileOutputStream
Data
90
PrintStream-Objekte
3 Um Ausgaben auf dem Monitor zu erzeugen, haben wir bisher dieNachrichten println oder print an das System.out-Objektgeschickt.3 Dabei ist System.out eine Referenz auf eine Instanz der KlassePrintStream.3 Um in eine Datei zu schreiben, erzeugen wir ein PrintStream-Objekt,welches die Datei reprasentiert.3 Darauf wenden wir dann die Methoden println oder print an.
91
Erzeugen von PrintStream-Objekten
Der Konstruktor von PrintStream akzeptiert eine Referenz auf einenFileOutputStream
File diskFile = new File("data.out");
FileOutputStream diskFileStream = new FileOutputStream(diskFile);
PrintStream target = new PrintStream(diskFileStream);
target.println("Hello Disk File");
Dieser Code erzeugt eine Datei data.out mit dem Inhalt
Hello Disk File.
Eine evtl. existierende Datei mit gleichem Namen wird geloscht und ihr Inhaltwird uberschrieben.
92
Notwendige Schritte, um in eine Datei zu schreiben
1. Erzeugen eines File-Objektes
2. Erzeugen eines FileOutputStream-Objektes unter Verwendung dessoeben erzeugten File-Objektes.
3. Erzeugen eines PrintStream-Objektes mithilfe der Referenz auf dasFileOutputStream-Objekt.
4. Verwenden von print oder println, um Texte in die Datei auszugeben.
93
Kompakte Erzeugung von PrintStream-Objektenfur Dateien
Die Konstruktion der PrintStream-Objekte kann auch ohne diediskFileStream-Variable durch Schachteln von Aufrufen erreicht werden:
import java.io.*;
class ProgramFileCompact {
public static void main(String[] arg) throws IOException {
String fileName = new String("data1.out");
PrintStream target = new PrintStream(new
FileOutputStream(new File(fileName)));
target.print("Hello disk file ");
target.println(fileName);
}
}
94
Beispiel: Backup der Ausgabe in einer Datei
import java.io.*;
class Program1Backup {
public static void main(String arg[]) throws IOException {
PrintStream backup;
FileOutputStream backupFileStream;
File backupFile;
backupFile = new File("backup");
backupFileStream = new FileOutputStream(backupFile);
backup = new PrintStream(backupFileStream);
System.out.println("This is my first Java program");
backup.println("This is my first Java program");
System.out.println("... but it won’t be my last.");
backup.println("... but it won’t be my last.");
}
}
95
Mogliche Fehler
4 Das Erzeugen einer Datei stellt eine Interaktion mit externenKomponenten dar (z.B. Betriebssystem, Hardware etc.)4 Dabei konnen Fehler auftreten, die nicht durch das Programm selbstverschuldet sind.4 Beispielsweise kann die Festplatte voll sein oder sie kann einenSchreibfehler haben. Weiter kann das Verzeichnis, in dem das Programmausgefuhrt wird, schreibgeschutzt sein.4 In solchen Fallen wird das einen Fehler produzieren.4 Java erwartet, dass der Programmierer mogliche Fehler explizit erwahnt.4 Dazu wird die Phrase throws Exception verwendet.
96
Mogliche Ein- und Ausgabequellen in Java
Dateisystem
Internet Eingabemedien
Monitor
Java
??
?
System.outFileOutputStream
97
Eingabe: Ein typisches Verfahren
Um Eingaben von einem Eingabestrom verarbeiten zu konnen, mussenfolgende Schritte durchgefuhrt werden.
1. Erzeugen Sie ein InputStream-Objekt, ein FileInputStream-Objektoder verwenden Sie das System.in-Objekt.
2. Verwenden Sie dieses Eingabestrom-Objekt, um einenInputStreamReader-Objekt zu erzeugen.
3. Erzeugen Sie ein BufferedReader-Objekt mithilfe desInputStreamReader-Objektes.
Dabei wird FileInputStream fur das Einlesen aus Dateien, InputStreamfur das Einlesen aus dem Internet und das System.in-Objekt fur dieEingabe von der Tastatur und verwendet.
98
Wirkung eines InputStream-Objektes
InputStream-Objekte, FileInputStream-Objekte oder dasSystem.in-Objekt modellieren die Eingabe als eine kontinuierliche,zusammenhangende Sequenz kleiner Einheiten, d.h. als eine Folge vonBytes:
InputStream
Eingabe-quelleProgramm
Java-
Daten
99
Wirkung eines InputStreamReader-Objektes
InputStreamReader-Objekte hingegen modellieren die Eingabe als eineFolge von Zeichen, sodass daraus Zeichenketten zusammengesetzt werdenkonnen:
Java InputStreamReader
n o w i s t h e t i m e f o rProgrammEingabe-quelle
100
BufferedReader
BufferedReader-Objekte schließlich modellieren die Eingabe als eineFolge von Zeilen, die einzeln durch String-Objekte reprasentiert werdenkonnen:
Java BufferedReader
Forefathers brought
Four score and sevenyears ago, our
ProgrammEingabe-quelle
101
Eingabe vom Keyboard
5 Java stellt ein vordefiniertes InputStream-Objekt zur Verfugung, dasdie Eingabe von der Tastatur reprasentiert. System.in ist eine Referenzauf dieses Objekt.5 Allerdings kann man von System.in nicht direkt lesen.
Notwendiges Vorgehen:
InputStreamReader isr;
BufferedReader keyb;
isr = new InputStreamReader(System.in)
keyb = new BufferedReader(isr);
Das Einlesen geschieht dann mit:
keyb.readLine()
102
Schema fur die Eingabe von der Tastatur mit Buffer
keyb
isr
System.in
Neu erschaffener
Tastatur
BufferedReader
Neu erschaffenerInputStreamReader
InputStream-Objekt
103
Beispiel: Einlesen einer Zeile von der Tastatur
Naives Verfahren zur Ausgabe des Plurals eines Wortes:
import java.io.*;
class Program4 {
public static void main(String arg[]) throws IOException {
InputStreamReader isr;
BufferedReader keyboard;
String inputLine;
isr = new InputStreamReader(System.in);
keyboard = new BufferedReader(isr);
inputLine = keyboard.readLine();
System.out.print(inputLine);
System.out.println("s");
}
}
104
Interaktive Programme
Um den Benutzer auf eine notwendige Eingabe hinzuweisen, konnen wireinen so genannten Prompt ausgeben.
PrintStream verwendet einen Buffer, um Ausgabeauftrage zu sammeln.Die Ausgabe erfolgt erst, wenn der Buffer voll oder das Programm beendet ist.
Da dies eventuell erst nach der Eingabe sein kann, stellt diePrintStream-Klasse eine Methode flush zur Verfugung. Diese erzwingtdie Ausgabe des Buffers.
Vorgehen daher:
System.out.println(
"Type in a word to be pluralized, please ");
System.out.flush();
inputLine = keyboard.readline();
105
Input aus Dateien
Das Lesen aus einer Datei unterscheidet sich vom Lesen von der Tastatur nurdadurch, dass wir ein FileInputStream-Objekt und nicht dasSystem.in-Objekt verwenden:
// Vom Dateinamen zum FileInputStream
File f = new File("Americas.Most.Wanted");
FileInputStream fs = new FileInputStream(f);
// Vom FileInputStream zum BufferedReader
InputStreamReader isr;
BufferedReader fileInput;
isr = new InputStreamReader(fs);
fileInput = new BufferedReader(isr);
106
Einlesen aus Dateien mit Buffer
isr
Neu erschaffenerBufferedReader
Neu erschaffenerInputStreamReader
Neu erschaffenerFileInputStream
Datei
fs
fileInput
107
Einlesen einer Zeile aus einer Datei
import java.io.*;
class Program5 {
public static void main(String arg[]) throws IOException {
String inputLine;
// Vom Dateinamen zum FileInputStream
File f = new File("Americas.Most.Wanted");
FileInputStream fs = new FileInputStream(f);
// Vom FileInputStream zum BufferedReader
InputStreamReader isr;
BufferedReader fileInput;
isr = new InputStreamReader(fs);
fileInput = new BufferedReader(isr);
inputLine = fileInput.readLine();
System.out.println(inputLine);
}
}
108
Gleichzeitige Verwendung mehrerer Streams:Kopieren einer Datei
1. Frage nach Quelldatei (und Zieldatei).
2. Lies Quelldatei.
3. Schreibe Zieldatei.
109
Schematische Darstellung
Tastatur
FileInputStream
InputStreamReader
BufferedReader
CopyFile Programm
PrintStream
FileOutpuStream
InputStreamReader
BufferedReader
Datei
InputStreamIrgendein
Datei
110
Daten aus dem Internet einlesen
Computer-Netzwerk: Gruppe von Computern, die untereinander direktInformationen austauschen konnen (z.B. durch eine geeigneteVerkabelung).
Internet: Gruppe von Computer-Netzwerken, die es Rechnern aus einemNetz erlaubt, mit Computern aus dem anderen Netz zu kommunizieren.
Internet-Adresse: Eindeutige Adresse, mit deren Hilfe jeder Rechner imNetz eindeutig identifiziert werden kann. Beispiele:6
www.informatik.uni-freiburg.de6 www.uni-freiburg.de6www.whitehouse.gov
Netzwerk-Ressource: Einheit von Informationen wie z.B. Text, Bilder,Sounds etc.
URL: (Abk. fur Universal Ressource Locator) Eindeutige Adresse vonNetzwerk-Ressourcen.
111
Komponenten einer URL
Bestandteil Beispiel Zweck
Protokoll http Legt die Software fest, die fur den Zu-griff auf die Daten benotigt wird
Internet-Adresse www.yahoo.com Identifiziert den Computer, auf demdie Ressource liegt
Dateiname index.html Definiert die Datei mit der Ressource
Zusammengesetzt werden diese Komponenten wie folgt:
protocol://internet address/file name
Beispiel:
http://www.yahoo.com/index.html
112
Netzwerk-Input
7 Um Daten aus dem Netzwerk einzulesen, verwenden wir einInputStream-Objekt.7 Die Java-Klassenbibliothek stellt eine Klasse URL zur Verfugung, um URL’s zumodellieren.7 Die URL-Klasse stellt einen Konstruktor mit einem String-Argument zurVerfugung:
URL u = new URL("http://www.yahoo.com/");7 Weiterhin stellt sie eine Methode openStream bereit, die keine Argumente hatund ein InputStream-Objekt zuruckgibt:
InputStream ins = u.openStream();7 Sobald wir einen InputStream haben, konnen wir wie ublich fortfahren:
InputStreamReader isr = new InputStreamReader(ins);
BufferedReader remote = newBufferedReader(isr);
... remote.readLine() ...113
Einlesen aus dem Internet mit Buffer
URL
ins
bsr
isr
liefert zurück
InputStreamNeu erschaffener
InputStreamReaderNeu erschaffener
BufferedReaderNeu erschaffener
Computer
Internet
openStream()
Nachricht
114
Beispiel: Einlesen der ersten funf Zeilen vonwww.informatik.uni-freiburg.de
import java.net.*;
import java.io.*;
class WebPageRead {
public static void main(String[] arg) throws Exception {
URL u = new URL("http://www.informatik.uni-freiburg.de/");
InputStream ins = u.openStream();
InputStreamReader isr = new InputStreamReader(ins);
BufferedReader webPage = new BufferedReader(isr);
System.out.println(webPage.readLine());
System.out.println(webPage.readLine());
System.out.println(webPage.readLine());
System.out.println(webPage.readLine());
System.out.println(webPage.readLine());
}
}
115
Ergebnis der Ausfuhrung
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta name="author" content="www@informatik.uni-freiburg.de">
116
Die Titelseite der Informatik in Freiburg
117
Der Quellcode der Titelseite
118
Zusammenfassung
8 Neue Objekte einer Klasse konnen mit dem new-Operator erzeugtwerden.8 Zusammen mit dem new-Operator verwenden wir den Konstruktor, derden gleichen Bezeichner hat wie die Klasse selbst.8 Haufig mussen mehrere Objekte erzeugt werden, um ein bestimmtesVerhalten zu erreichen.8 Um beispielsweise Zeilen aus dem Internet einzulesen, benotigen wir einBufferedReader-Objekt.8 Dies erfordert das Erzeugen eines InputStreamReader-Objektes8 Das InputStreamReader-Objekt hingegen benotigt einentsprechendes InputStream-Objekt.
119
Add-on: Programme als Applets im Web-Browser
9 Ein Applet ist ein Programm, welches in eine Web-Page integriert ist.9 Java stellt eine Abstract-Window-Toolkit-Klasse (AWT) zur Verfugung, umgrafische Benutzeroberflachen zu programmieren.
import java.awt.*;
import java.applet.*;
public class FirstApplet extends Applet {
public void paint(Graphics g) {
Color c = new Color(20,120,160);
g.setColor(c);
g.fillOval(20,20,60,30);
}
}
120
Eine einfache Web-Page mit Applet
<HTML>
<HEAD>
<TITLE>Hw1</TITLE>
</HEAD>
<BODY>
<HR>
<APPLET CODE="FirstApplet.class" WIDTH=300 HEIGHT=60></APPLET>
<HR>
<A HREF="FirstApplet.java">The source.</A>
</BODY>
</HTML>
121
Das Ergebnis
122
Einfuhrung in die Informatik
Definition von Klassen
Wolfram Burgard
123
Motivation
: Auch wenn Java ein große Zahl von vordefinierten Klassen undMethoden zur Verfugung stellt, sind dies nur Grundfunktionen fur eineModellierung vollstandiger Anwendungen.: Um Anwendungen zu realisieren, kann man diese vordefinierten Klassennutzen.: Allerdings erfordern manche Anwendungen Objekte und Methoden, furdie es keine vordefinierten Klassen gibt.: Java erlaubt daher dem Programmierer, eigene Klassen zu definieren.
124
Klassendefinitionen: Konstruktoren und Methoden
class Laugher1 {
public Laugher1(){
}
public void laugh() {
System.out.println("haha");
}
} ; Durch diesen Code wird eine Klasse Laugher1 definiert.; Diese Klasse stellt eine einzige Methode laugh zur Verfugung
125
Anwendung der Klasse Laugher1
Auf der Basis dieser Definition konnen wir ein Laugher1-Objekt deklarieren:
Laugher1 x;
x = new Laugher1();
Dem durch x referenzierten Objekt konnen wir anschließend die Messagelaugh schicken:
x.laugh();
126
Aufbau einer Klassendefinition
Das Textstuck
class Laugher1 {
lautet in unserem Beispiel die Definition der Klasse Laugher1 ein.
Die Klammern < und = werden Begrenzer oder Delimiter genannt, weil sieden Anfang und das Ende der Klassendefinition markieren.
Zwischen diesen Delimitern befindet sich die Definition des Konstruktors
public Laugher1(){
}
und einer Methode.
public void laugh() {
System.out.println("haha");
}127
Aufbau der Methodendefinition laugh
Die Definition der Methode besteht aus einem Prototyp
public void laugh()
und dem Methodenrumpf
{
System.out.println("haha");
}
1. Der Prototyp der Methode beginnt mit dem Schlusselwort public.
2. Danach folgt der Typ des Return-Wertes.
3. Dann wird der Methodenname angegeben.
4. Schließlich folgen zwei Klammern (), zwischen denen die Argumenteaufgelistet werden.
128
Der Rumpf der Methode laugh
Der Methodenrumpf enthalt die Statements, die ausgefuhrt werden, wenn dieMethode aufgerufen wird.
Die Methode laugh druckt den Text "haha" auf den Monitor.
Zusammenfassend ergibt sich:
public void laugh()
System.out.println("haha");
}
{Prototyp
MethodennameMethodenrumpf
Rückgabetyp
129
Der Konstruktor der Klasse Laugher1
> Die Form eines Konstruktors ist identisch zu einerMethodendefinition.> Lediglich der Return-Typ wird ausgelassen.> Konstruktoren werden immer mit dem Schlusselwort new aufgerufen.> Dieser Aufruf gibt eine Referenz auf ein neu erzeugtes Objekt zuruck.> Der Konstruktor Laugher1 tut nichts.
130
Struktur der Klassendefinition Laugher1
public void Laugh() {
System.out.println("haha");
}
}
public Laugher1() {
}
class Laugher1(){
Begrenzer
Klassenname
Konstruktor
Methode
131
Parameter
In der Methode laugh wird der auszugebende Text vorgegeben.
Wenn wir dem Sender einer Nachricht erlauben wollen, die Lachsilbefestzulegen, (z.B. ha oder he), mussen wir eine Methode mit Argumentverwenden:
x.laugh("ha");
oder
x.laugh("yuk");
Parameter sind Variablen, die im Prototyp einer Methode spezifiziert werden.
132
Definition einer Methode mit Argument
Da unsere neue Version von laugh ein String-Argument hat, mussen wirden Prototyp wie folgt andern:
public void laugh(String syllable)
Der Rumpf kann dann z.B. sein:
{
String laughSound;
laughSound = syllable.concat(syllable);
System.out.println(laughSound);
}
Wird diese Methode mit dem Argument "ho" aufgerufen, so gibt sie den Texthoho auf dem Bildschirm aus.
133
Struktur einer Methode mit Parametern
public void laugh( String syllable ) {
String laughSound;
laughSound = syllable.concat(syllable);
System.out.println(laughSound);
}
Prototyp
MethodennameRückgabewert
Methodenrumpf
Parameterdeklaration
Parametergebrauch
134
Eine erweiterte Laugher2-Klasse
class Laugher2 {
public Laugher2() {
}
public void laugh() {
System.out.println("haha");
}
public void laugh(String syllable) {
String laughSound;
laughSound = syllable.concat(syllable);
System.out.println(laughSound);
}
}
135
Overloading
Diese Definition von Laugher2 stellt zwei Methoden mit dem gleichenNamen aber unterschiedlichen Signaturen zur Verfugung:
laugh()
laugh(String syllable)
In diesem Fall ist die Methode laugh uberladen bzw. overloaded.
Wenn wir haha ausgeben wollen, genugt der Aufruf
x.laugh();
Um einen anderes Lachen (z.B. yukyuk) zu erzeugen, verwenden wir diezweite Methode:
x.laugh("yuk");
Die Methode ohne Parameter reprasentiert das Standardverhalten undheißt daher Default.
136
Variante 3: Veranderbare Standardsilbe
Am Ende wollen wir auch die Moglichkeit haben, die Standardlachsilbe desLaugher-Objektes im Konstruktor anzugeben.
Die gewunschte Anwendung ist:
Laugher3 x;
x = new Laugher3("ho");
x.laugh("heee");
x.laugh();
Um dies zu erreichen, erhalt der Konstruktor jetzt ein String-Argument, sodass er folgende Signatur hat:
Laugher3(String s)
137
Instanzvariablen
Wie konnen wir jetzt in der Methode laugh() auf dieses String-Objektzugreifen?
Die Losung besteht darin, in der Klasse eine String-Variable zu definieren,die außerhalb der Methoden der Klasse steht.
Eine solche Variable heißt Instanzvariable.
Sie gehort zu dem gesamten Objekt und nicht zu einer einzelnen Methode.
Auf Instanzvariablen kann von jeder Methode aus zugegriffen werden.
Instanzvariablen werden genauso deklariert wie andere Variablen. In derRegel geht der Deklaration jedoch das Schlusselwort private voraus.
138
Deklaration von und Zugriff auf Instanzvariablen
}
public void Laugh () {
defaultSyllableZu beachten:
kann in jedem
benutzt werdenRumpf einer Methode
class Laugher3{
public Laugher3 ( String s) {
...
}
private String defaultSyllable;
...
}
...
Instanzvariablen-Deklaration
139
Verwendung der Instanzvariable
In unserem Beispiel ist die Aufgabe des Konstruktors, die mit dem Argumenterhaltene Information in der Instanzvariablen defaultSyllable zuspeichern:
public Laugher3(String s) {
defaultSyllable = s;
}
Anschließend kann die laugh()-Methode auf defaultSyllable zugreifen:
public void laugh() {
String laughSound;
laughSound = defaultSyllable.concat(defaultSyllable);
System.out.println(laughSound);
}
140
Die Komplette Laugher3-Klasse
class Laugher3 {
public Laugher3(String s) {
defaultSyllable = s;
}
public void laugh() {
String laughSound;
laughSound = defaultSyllable.concat(defaultSyllable);
System.out.println(laughSound);
}
public void laugh(String syllable) {
String laughSound;
laughSound = syllable.concat(syllable);
System.out.println(laughSound);
}
private String defaultSyllable;
}
141
Verwendung einer Klassendefinition
1. Wir kompilieren Laugher3.java.
2. Wir schreiben ein Programm, das die Laugher3-Klasse benutzt:
import java.io.*;
class LaughALittle {
public static void main(String[] a) {
System.out.println("Live and laugh!!!");
Laugher3 x,y;
x = new Laugher3("yuk");
y = new Laugher3("harr");
x.laugh();
x.laugh("hee");
y.laugh();
}
}
142
Der Klassenentwurfsprozess
Im vorangegangenen Beispiel haben wir mit einer einfachen Klassebegonnen und diese schrittweise verfeinert.
Fur große Programmsysteme ist ein solcher Ansatz nicht praktikabel.
Stattdessen benotigt man ein systematischeres Vorgehen:
1. Festlegen des Verhaltens der Klasse.
2. Definition des Interfaces bzw. der Schnittstellen der Klasse, d.h. der Artder Verwendung. Dabei werden die Prototypen der Methoden festgelegt.
3. Entwicklung eines kleinen Beispielprogramms, das die Verwendung derKlasse demonstriert und gleichzeitig zum Testen verwendet werden kann.
4. Formulierung des Skelettes der Klasse, d.h. dieStandard-Klassendefinition zusammen mit den Prototypen.
143
Festlegen des Verhaltens einer Klasseam Beispiel InteractiveIO
Fur eine Klasse InteractiveIO wunschen wir das folgende Verhalten:? Ausgeben einer Meldung auf dem Monitor (mit der Zusicherung, dass sieunmittelbar angezeigt wird).? Von dem Benutzer einen String vom Keyboard einlesen.
Außerdem sollte der Programmierer die Moglichkeit haben,InteractiveIO-Objekte zu erzeugen, ohne System.in oder System.outverwenden zu mussen.
144
Interfaces und Prototypen (1)
Die Schnittstelle einer Klasse beschreibt die Art, in der die Objekte dieserKlasse verwendet werden konnen.
Fur unsere InteractiveIO-Klasse waren dies:@ Deklaration einer InteractiveIO-Referenzvariablen:
InteractiveIO interIO;@ Erzeugen eines InteractiveIO-Objektes:
interIO = new InteractiveIO();
In diesem Beispiel benotigt der Konstruktor kein Argument.
145
Interfaces und Prototypen (2)
A Senden einer Nachricht zur Ausgabe eines String-Objektes an einInteractiveIO-Objekt.
interIO.write("Please answer each question");
Resultierender Prototyp:
public void write(String s)A Ausgeben eines Prompts auf dem Monitor und Einlesen einesString-Objektes von der Tastatur. Dabei soll eine Referenz auf denString zuruckgegeben werden:
String s;
s = interIO.promptAndRead("What is your first name? ");
Resultierender Prototyp:
public String promptAndRead(String s)
146
Ein Beispielprogramm, das InteractiveIO verwendet
Aufgaben des Beispielprogramms:
1. Demonstrieren, wie die neue Klasse verwendet wird.
2. Prufen, ob die Prototypen so sinnvoll sind.
import java.io.*;
class TryInteractiveIO {
public static void main(String[] arg) throws Exception {
InteractiveIO interIO;
String line;
interIO = new InteractiveIO();
line = interIO.promptAndRead("Please type in a word: ");
interIO.write(line);
}
}
147
Das Skelett von InteractiveIO
Gegenuber der kompletten Klassendefinition fehlt dem Skelett der Code, derdie Methoden realisiert:
class InteractiveIO {
public InteractiveIO() {
}
// Write s to the monitor
public void write(String s) {
}
// Write s to the monitor, read a string from the keyboard,
// and return a reference to it.
public String promptAndRead(String s) throws Exception {
}
}
148
Implementierung von InteractiveIO (1)
Die Implementierung einer Klasse besteht aus dem Rumpf der Methodensowie den Instanzvariablen.
Dabei spielt die Reihenfolge, in der Methoden (weiter-) entwickelt werden,keine Rolle.
Der Konstruktor tut nichts:
public InteractiveIO() {
}
Als nachstes definieren wir die Methode write:
public void write(String s) {
System.out.println(s);
System.out.flush();
}
149
Implementierung von InteractiveIO (2)
Schließlich implementieren wir promptAndRead.
Um in einer Methode den Return-Wert zuruckzugeben, verwenden wir dasReturn-Statement:
return Wert ;
Dies ergibt:
public String promptAndRead(String s) throws Exception {
System.out.println(s);
System.out.flush();
BufferedReader br;
br = new BufferedReader(new InputStreamReader(System.in));
String line;
line = br.readLine();
return line;
}150
Die komplette Klasse InteractiveIO
import java.io.*;
class InteractiveIO {
public InteractiveIO() {
}
public void write(String s) {
System.out.println(s);
System.out.flush();
}
public String promptAndRead(String s) throws Exception {
System.out.println(s);
System.out.flush();
BufferedReader br;
br = new BufferedReader(new InputStreamReader(System.in));
String line;
line = br.readLine();
return line;
}
}
151
Verbessern der Implementierung von InteractiveIO
B Haufig ist eine erste Implementierung einer Klasse noch nicht optimal.B Nachteilhaft an unserer Implementierung ist, dass bei jedem Einleseneiner Zeile ein BufferedReader und ein InputStreamReader
erzeugt wird.B Es ware viel gunstiger, diese Objekte einmal zu erzeugen undanschließend wiederzuverwenden.B Die entsprechenden Variablen konnen wir als Instanzvariablendeklarieren und die Erzeugung der Objekte konnen wir in den Konstruktorverschieben.
152
Prinzip der Verbesserung von InteractiveIO
Der Konstructor
class InteractiveIO{
public InteractiveIO() throws Exception {
br = new BufferedReader(
new InputStreamReader(System.in));
}
System.out.println(s);
System.out.flush();
String line;
...
}
public String promptAndRead(String s) throws Exception {
...
}
private BufferedReader br; Jetzt Instanzvariable
153
Weitere Vereinfachungen
1. Wir konnen die von readLine erzeugte Referenz auch direktzuruckgeben:
return br.readline();
2. Beide Methoden write und promtAndRead geben etwas auf demMonitor aus und verwenden println und flush. Dies kann in einerMethode zusammengefasst werden:
private void writeAndFlush(String s){
System.out.println(s);
System.out.flush();
}
154
Das Schlusselwort this
Mit der Methode writeAndFlush konnen wir sowohl in write als auch inpromtAndRead die entsprechende Code-Fragmente ersetzen.
Problem: Methoden werden aufgerufen, indem Nachrichten an Objektegesendet werden. Aber an welches Objekt konnen wir aus der Methodewrite eine Nachricht writeAndFlush senden?
Antwort: Es ist dasselbe Objekt.
Java stellt das Schlusselwort this zur Verfugung, damit eine Methode dasObjekt, zu dem sie gehort, referenzieren kann:
this.writeAndFlush(s);
155
Die komplette, verbesserte Klasse InteractiveIO
import java.io.*;
class InteractiveIO {
public InteractiveIO() {
br = new BufferedReader(new InputStreamReader(System.in));
}
public void write(String s) {
this.writeAndFlush(s);
}
public String promptAndRead(String s) throws Exception {
this.writeAndFlush(s);
return br.readLine();
}
private void writeAndFlush(String s) {
System.out.println(s);
System.out.flush();
}
private BufferedReader br;
}
156
Deklarationen und das return-Statement
Reihenfolge der Vereinbarungen: Die Reihenfolge von Variablen- undMethodendeklarationen in einer Klasse ist beliebig. Es ist jedoch einegangige Konvention, erst die Methoden zu deklarieren und dann dieVariablen.
Das return-Statement: Der Sender einer Nachricht kann nicht fortfahren,bis die entsprechende Methode beendet ist.
In Java geschieht die Ruckkehr zum Sender durch ein return-Statementoder, sofern die Methode den Typ void hat, am Ende der Methode.
Allerdings konnen auch void-Methoden mit return beendet werden:
private void writeAndFlush(String s){
System.out.println(s);
System.out.flush();
return;
}157
Zugriffskontrolle
Eine Klassendefinition besteht aus Methoden und Instanzvariablen.
Der Programmierer kann einen unterschiedlichen Zugriff auf Methoden oderVariablen gestatten, indem er die Schlusselworter public oder privateverwendet.
Als public deklarierte Methoden konnen von außen aufgerufen werden.Als private vereinbarte Methoden sind jedoch nur innerhalb der Klassebekannt.
Gleiches gilt fur Instanzvariablen.
158
Variablen und ihre Lebensdauer
Wir haben drei verschiedene Arten von Variablen kennengelernt:
1. als Parameter im Kopf der Definition einer Methode,
2. als lokale Variable definiert innerhalb des Rumpfes einer Methode und
3. als Instanzvariablen in der Klassendefinition.
159
Variablen als Parameter
Variablen, die Parameter einer Methode sind, werden beim Aufruf derMethode automatisch erzeugt und sind innerhalb der Methode bekannt. Istdie Methode beendet, kann auf sie nicht mehr zugegriffen werden: IhreLebenszeit ist dieselbe wie die der Methode.
Parameter erhalten ihren initialen Wert vom Sender der Nachricht und dieArgumente des Aufrufs mussen exakt mit den Argumenten der Methodeubereinstimmen.
Fur void f(String s1, PrintStream p) ist der Aufruf
f("hello", System.out)
zulassig. Die folgenden Aufrufe hingegen sind alle unzulassig:
f("hello")
f("hello", "goodbye")
f("hello", System.out, "bye")
160
Lokale Variablen
C Lokale Variablen sind Variablen, die in Methoden definiert werden.C Sie haben die gleiche Lebenszeit wie Parameter.C Sie werden beim Aufruf einer Methode erzeugt und beim Verlassen derMethode geloscht.C Lokale Variablen mussen innerhalb der Methode initialisiert werden.C Parameter und Variablen sind außerhalb der Methode, in der sie definiertwerden, nicht sichtbar, d.h. auf sie kann von anderen Methoden nichtzugegriffen werden.C Wird in verschiedenen Methoden derselbe Bezeichner fur lokaleVariablen verwendet, so handelt es sich um jeweils verschiedene lokaleVariablen.
161
Beispiel
String getGenre() {
String s = "classic rock/".concat(getFormat());
return s;
}
String getFormat() {
String s = "no commercials";
return s;
}
Die Werzuweisung an s in getFormat hat keinerlei Effekt auf die lokaleReferenzvariable s in getGenre.
Ruckgabewert von getGenre ist somit eine Referenz auf
"classic rock/no commercials"
162
Instanzvariablen
D Instanzvariablen haben dieselbe Gultigkeitsdauer wie das Objekt, zudem sie gehoren.D Auf Instanzvariablen kann von jeder Methode eines Objektes auszugegriffen werden.D Der Zugriff von außen wird, ebenso wie bei den Methoden, durch dieSchlusselworte public und private geregelt.
163
Lebensdauer von Objekten
E Objekte konnen in den Methoden einer Klasse durch Verwendung vonnew oder durch den Aufruf anderer Methoden neu erzeugt werden.E Java loscht nicht referenzierte Objekte automatisch.
public void m2() {
string s;
s = new String("Hello world!");
s = new String("Welcome to Java!");
...
}
Nach der zweiten Wertzuweisung gibt es keine Referenz auf"Hello world!" mehr.
Konsequenz: Objekte bleiben so lange erhalten, wie es eine Referenz aufsie gibt.
164
Das Schlusselwort this
Mit dem Schlusselwort this kann man innerhalb von Methoden einer Klassedas Objekt selbst referenzieren.Damit kann man
1. dem Objekt selbst eine Nachricht schicken oder
2. bei Mehrdeutigkeiten auf das Objekt selbst referenzieren.
class ... {
...
public void m1(){
String s;
...
}
...
private String s;
}
Innerhalb der Methode m1 ist s eine lokale Variable. Hingegen ist this.s dieInstanzvariable.
165
Der Konstruktor
F Der Konstruktor ist immer die erste Methode, die aufgerufen wird.FDie Aufgabe eines Konstruktors ist daher, dass das entsprechendeObjekt
”sein Leben“ mit den richtigen Werten beginnt.F Insbesondere soll der Konstruktor die notwendigen Initialisierungen
der Instanzvariablen vornehmen.
166
Zusammenfassung
G Eine Klassendefinition setzt sich zusammen aus der Formulierung derMethoden und der Deklaration der Instanzvariablen.G Methoden und Instanzvariablen konnen als public oder privatedeklariert werden, um den Zugriff von außen festzulegen.G Es gibt drei Arten von Variablen: Instanzvariablen, lokale Variablenund Parameter.G Lokale Variablen sind Variablen, die in Methoden deklariert werden.G Parameter werden im Prototyp einer Methode definiert und beimAufruf durch die Wertubergabe initialisiert.G Instanzvariablen werden außerhalb der Methoden aber innerhalb derKlasse definiert.G Instanzvariablen speichern Informationen, die uber verschiedeneMethodenaufrufe hinweg benotigt werden.
167
Einfuhrung in die Informatik
Processing Numbers
Wolfram Burgard
168
Motivation
H Computer bzw. Rechenmaschinen wurden ursprunglich gebaut, umschnell und zuverlassig mit Zahlen zu rechnen.H Erste Anwendungen von Rechenmaschinen und Computern waren dieBerechnung von Zahlentabellen, Codes, Buchhaltungen, . . .H Auch heute spielen numerische Berechnungen immer noch einebedeutende Rolle.H Nicht nur in Buchhaltungen, graphischen Oberflachen (Kreise, Ellipsen,. . . ) sondern auch bei der Interpretation von Daten (z.B. Bildverarbeitung,Robotik, . . . ) wird ublicherweise mit Zahlen gerechnet.H Auch Java bietet Moglichkeiten, Zahlen zu reprasentieren undarithmetische Berechnungen durchzufuhren.
169
Primitive Datentypen
I Einer der grundlegende Datentypen von Computern sind Zahlen.I Anstatt Klassen fur die Manipulation von Zahlen zur Verfugung zu stellen,bietet Java einen direkten Zugriff auf Zahlen.I Verschiedene Typen von Zahlen (ganze Zahlen, . . . ) werden in Java auchdirekt, d.h. unter direkter Verwendung der zugrundeliegenden Hardwarerealisiert ohne den Umweg uber Klassendefinitionen zu gehen.I Dies hat den Vorteil, dass numerische Berechnungen besonders effizientausgefuhrt werden konnen.
170
Operatoren versus Methoden
J Allerdings fuhrt der Verzicht auf Klassen fur Zahlen dazu, dassBerechnungen nicht mithilfe von Nachrichten ausgefuhrt werden, diean Objekte gesendet werden, sondern mithilfe so genannter Operatoren.J Daruber hinaus ist
x / (y + 1)
ist leichter lesbar als
x.divide(y.add(1))
171
Variablen versus Referenzvariablen
K Referenzvariablen haben als Wert Referenzen bzw. Bezuge aufObjekte.K Variablen hingegen enthalten Werte einfacher Datentypen undwerden nicht mit Objekten
”assoziiert“.K einer dieser Datentypen ist int, der ganze Zahlen reprasentiert.
Hello
Objekt, das "Hello" repräsentiert
String s = new String("Hello"); int i = 3;
3
is
172
Unterschiede zwischen Variablen und Referenzvariablen
Referenzvariable Einfache Variable
Definiert durch Klassendefinition Sprache
Wert erzeugt durch new System
Wert initialisiert durch Konstruktor System
Variable initialisiert durch Zuweisung einer Referenz Zuweisung eines Wertes
Variable enthalt Referenz auf Objekt primitiven Wert
Verwendet zusammen mit Methoden Operatoren
Nachrichtenempfanger Ja Nein
173
Grundlegende Arithmetische Operatoren
Einige der Operatoren, die in Java im Zusammenhang mit ganzen Zahlen(int) benutzt werden konnen, sind.
+ Addition
Ergebnis ist die Summe der beiden Operanden: L M N O- Subtraktion
Ergebnis ist die Differenz der beiden Operanden L M N P* Multiplikation
Ergebnis ist das Produkt der beiden Operanden L Q M N R L/ Division
Ganzzahlige Division ohne Rest: L S M N R% Rest
Ergebnis ist der Rest bei ganzzahliger Division: L T M N P174
Operatoren, Operanden und Ausdrucke
U Operatoren korrespondieren zu Aktionen, die Werte berechnen.U Die Objekte, auf die Operatoren angewandt werden, heißen Operanden.UOperatoren zusammen mit ihren Operanden werden Ausdrucke genannt.U In dem Ausdruck x / y sind x und y die Operanden und / der Operator.U Da Ausdrucke wiederum Werte reprasentieren, konnen Ausdrucke auchals Operanden verwendet werden.
Fur die Integer-Variablen x, y und z lassen sich folgende Ausdrucke bilden:
x + y
z / x
(x - y)*(x + y)
175
Literale
V Innerhalb von Ausdrucken durfen auch konkrete (Zahlen)-Werteverwendet werden.V Zahlenwerte, die von der Programmiersprache vorgegeben werden, wiez.B. -1 oder 2, heißen Literale.
Damit sind auch folgende Ausdrucke zulassig:
2 * x + y
75 / x
33 / 5 + y
176
Prazedenzregeln
W Sofern in einem Ausdruck mehr als ein Operator vorkommt, gibt esMehrdeutigkeiten.W Je nachdem, wie der Ausdruck ausgewertet wird, erhalt manunterschiedliche Ergebnisse.W Beispielsweise kann 4*3+2 als 14 interpretiert werden, wenn manzunachst 4 mit 3 multipliziert und anschließend 2 addiert, oder als 20,wenn man zuerst 3 und 2 addiert und das Ergebnis mit 4 multipliziert.W Java verwendet so genannte
”Prazedenzregeln“, um solche
Mehrdeutigkeiten aufzulosen.W Dabei haben *,/ und % eine hohere Prazedenz als die zweistelligenOperatoren + und -.W Die einstelligen Operatoren + und - (Vorzeichen) wiederum habenhohere Prazedenz als *,/ und %.
177
Prazedenzregeln und Klammern
Der Ausdruck
4 * 3 * -2 + 2 * -4
ist somit aquivalent zu
((4 * 3) * (-2)) + (2 * (-4))
Ebenso wie in der Mathematik kann man runde Klammern verwenden, umPrazedenzregeln zu uberschreiben:
(4 * 3) + 2
4 * (3 + 2)
178
Wertzuweisungenund zusammengesetzte Wertzuweisungen
X Ausdrucke (wie die oben verwendeten) konnen auf der rechten Seite vonWertzuweisungen verwendet werden:
x = y + 4; y = 2 * x + 5;X Verschiedene Wertzuweisungen tauchen jedoch sehr haufig auf, wie z.B.
x = x + y; y = 2 * y;
Fur diese Form der Wertzuweisungen stellt Java zusammengesetzteWertzuweisungen zur Verfugung:
x += y; ---> x = x + y;
y *= 2; ---> y = y * 2;
179
Inkrement und Dekrement
Y Die haufigsten arithmetischen Operationen sind das Addieren und dasSubtrahieren von 1.Y Auch hierfur stellt Java spezielle Operatoren zur Verfugung:
x++; ++x;
y--; --y;Y Die oberen zwei Operatoren heißt Inkrement-Operatoren. Die unterenzwei werden Dekrement-Operatoren genannt.Y Diese Statements stehen fur eine Wertzuweisung, durch welche der Wertder entsprechenden Variable um eins erhoht bzw. erniedrigt wird.Y Dementsprechend durfen die Argumente dieser Operatoren wederLiterale noch zusammengesetzte Ausdrucke sein.
180
Methoden fur Integers
Z Die Menge der Operatoren ist auf die Grundrechenarten eingeschrankt.Z Haufig benotigt man jedoch weitere Funktionen.Z Fur Integer-Objekte werden einige Methoden in den vordefinierten KlasseInteger und Math zur Verfugung gestellt.Z Eine dieser Methoden ist z.B. Math.abs:
int i = -2;
int j = Math.abs(i);
Nach der zweiten Zuweisung hat j den Wert 2.
181
Auswertung von Ausdrucken
1. Ausdrucke werden von links nach rechts unter Berucksichtigungder Prazedenzregeln und der Klammerung ausgewertet.
2. Bei Operatoren mit gleicher Prazedenz wird von links nach rechtsvorgegangen.
3. Dabei werden die Variablen und Methodenaufrufe, sobald sie an dieReihe kommen, durch ihre jeweils aktuellen Werte ersetzt.
182
Beispiele fur die Ausdrucksauswertung
Gegeben:
int p = 2, q = 4, r = 4, w = 6, x = 2, y = 1;
Dies ergibt:
p * r % q + w / x - y
2 * 4 % 4 + 6 / 2 - 1 ---> 2
p * x * x + w * x + -q
2 * 2 * 2 + 6 * 2 + -4 ---> 16
(p + q * 2) + ((p - 2) * r - w)
(2 + 4 * 2) + ((2 - 2) * 4 - 6) ---> 4
183
Zuweisungen und Inkrementoperatoren in Ausdrucken
[ Sowohl die Wertzuweisung = als auch die Inkrementoperatoren ++ und-- stellen Operatoren dar.[ Sie durfen daher auch in Wertzuweisungen vorkommen.[ Der Ausdruck x = y hat als Wert den Wert von y.[ Hat x den Wert 3, liefert ++x als Ergebnis den Wert 4. Dabei wird x von 3auf 4 erhoht.[ x++ liefert in derselben Situation den Wert 3. Danach wird x um 1 erhoht.[ Zulassig sind daher
x = y = z = 0; x = y = z++; x = z++ + --z;
Empfehlung: Keine Zuweisungen und Operatoren mit Seiteneffekten inAusdrucken verwenden!
184
Auswertung der Beispiele
x = y = z = 0;
x = y = z++;
x = z++ + --z;
185
Ursprung der Integer-Methoden
Problem: Um die Methode abs aufzurufen, mussten wir strenggenommeneine Nachricht an ein Objekt senden. Eine Integer-Variable ist aber keinObjekt.
Fur den Fall, dass der Empfanger einer Nachricht fehlt, bietet Java dieMoglichkeit, Nachrichten direkt an eine Klasse zu senden (anstatt an dasentsprechende Objekt).
Um solche Methoden bei der Deklaration zu kennzeichnen verwendet man inJava das Schlusselwort static.
class Math {
...
static int abs(int a) { ... }
...
}
Bei als static deklarierten Methoden ist stets die Klasse derEmpfanger.
186
Einlesen von Zahlen von der Tastatur
\ Um Zahlen von der Tastatur einzulesen, benotigen wir entsprechendeMethoden.\ In Java wird das durch die Komposition von zwei Methoden erreicht.\ Die erste liest ein String-Objekt aus dem Eingabestrom.\Die zweite Methode wandelt die Zeichen dieses String-Objektes ineine Zahl um:
String s = br.readLine();
int i = Integer.parseInt(s);
Kompakter geht es mit:
int i = Integer.parseInt(br.readLine());
187
Mogliche Fehler
Damit das Einlesen einer Zahl erfolgreich ist, muss sich die eingelesene Zeiletatsachlich auch in eine Zahl umwandeln lassen.
Eingaben wie
2
75
-1
sind zulassig. Bei folgenden Eingaben hingegen wird ein Fehler auftreten:
Hello
75 40
12o
188
Der Datentyp long fur große ganze Zahlen
] Der Typ int modelliert ganze Zahlen in dem Bereich^ _ ` a b a c d e a c f _ ` a b a c d e a b g] Leider reicht dieser Wertebereich fur viele Anwendungen nicht aus:
Erdbevolkerung, Staatsschulden, Entfernungen im Weltall etc.] Java stellt daher auch den Typ long mit dem Wertebereich^ h _ _ d d b _ i d e c j a b b j c i c f h _ _ d d b _ i d e c j a b b j c i b gzur Verfugung, der fur
fast alle Anwendungen im Bereich Administration und Handel ausreicht.] Fur den Datentyp long gelten die gleichen Operatoren wie fur int.]Long-Literale werden durch ein abschließendes L gekennzeichnet.
long x = 2000L, y = 1000L;
y *= x;
x += 1500L;
189
Warum int und long und nicht nur long?
Hierfur gibt es zwei Grunde:
1. Variablen vom Typ int benotigen nur vier Byte=32 Bit, wahrend solchevom Typ long acht Byte = 64 Bit benotigen.
Wenn also ein Programm sehr viele ganze Zahlen verwendet, verbrauchtman bei Verwendung von ints nur die Halfte an Speicherplatz.
2. Die Hardware heutiger Computer ist haufig auf 32-Bit-Werte ausgelegt.Daher gehen Berechnungen mit ints ublicherweise schneller alsdieselben mit longs.
In der Praxis muss man die Anforderungen an die Genauigkeit sehr genauuntersuchen und kann ggf. auf die schnelleren ints zuruckgreifen.
190
Gleichzeitige Verwendung mehrerer Typen: Casting
k Java erlaubt die Zuweisung eines Wertes vom Typ int an eine Variablevom Typ long.kDabei geht offensichtlich keine Information verloren.k Umgekehrt ist das jedoch nicht der Fall, weil der Wert außerhalb desBereichs von int liegen kann.k Wenn man einer Variable vom Typ int einen Ausdruck vom Typ long
zuordnen will und man sicher ist, dass keine Bereichsuberschreitungstattfinden kann, muss man eine spezielle Notation verwenden, dieCasting genannt wird.k Dabei stellt man dem Ausdruck den Typ, in den sein Wert konvertiertwerden soll, in Klammern voraus.
Die folgenden Wertzuweisungen sind daher zulassig:
long x = 98;
int i = (int) x; // casting191
Modellieren von Messdaten
l Integer sind Zahlen, die ublicherweise zum Zahlen verwendet werden.l Integer sollten daher immer dann verwendet werden, wenn derWertebereich einer Variablen in den ganzen Zahlen liegt (AnzahlStudenten, die immatrikuliert sind, Anzahl der Kinder, . . . ).l Insbesondere bei der Verarbeitung von Messdaten erhalt man jedoch oftWerte, die keine ganzen Zahlen sind (540.3 Meter, 10.97 Sekunden,. . . ).l Deshalb benotigt man in einer Programmiersprache auch Werte mitNachkommastellen:
12.34
3.1415926
1.414
192
Fließkommazahlen
m In der Welt der Computer werden Messwerte ublicherweise durchFließkommazahlen reprasentiert.m Hierbei handelt es sich um Zahlen der Formn o p q r s t p u v w
m Diese Zahl wurde in Java reprasentiert durch
3.1479E15fm Dabei sind sowohl der Vorkommaanteil, der Nachkommaanteil und derExponent in der Anzahl der Stellen begrenzt.m Fließkommazahlen reprasentieren eine endliche Teilmenge derrationalen Zahlen.
193
Die Datentypen float und double
x Java stellt mit float und double zwei elementare Datentypen mitunterschiedlicher Genauigkeit fur die Reprasentation vonFließkommazahlen zur Verfugung.x Der Typ float modelliert Fließkommazahlen mit ungefahr siebenstelligerGenauigkeit. Der Absolutbetrag der Werte kann entweder 0 sein oder imBereich [1.4E-45f, 3.4028235E38f] liegen.x Demgegenuber hat der Typ double eine ungefahr funfzehnstelligeGenauigkeit. Der Absolutbetrag der Werte kann entweder 0 sein oder imBereich [4.9E-324, 1.7976931348623157E308] liegen.
194
Vergleich der Typen float und int
y Variablen vom Typ float benotigen ebenso wie Variablen vom Typ int
lediglich 4 Byte = 32 Bit.y Variablen vom Typ float konnen großere Werte reprasentieren alsVariablen vom Typ int.y Allerdings haben floats nur eine beschrankte Genauigkeit.
Beispiel:
float f1 = 1234567089f;
System.out.println(f1);
liefert als Ausgabe 1.23456704E9.
195
Fließkommazahlen und Rundungsfehler
z Fließkommazahlen stellen nur eine begrenzte Genauigkeit zurVerfugung.z Ein typisches Beispiel fur mogliche Rechenfehler ist:
float x = 0.0644456f;
float y = 0.032754f;
float z = x * y;
System.out.println(z);
Ausgabe dieses Programmstucks ist 0.0021108512.z Korrekt ware 0.0021108511824.
196
Verwendung von float oder double
{ Variablen vom Typ float und double werden ahnlich verwendet wieVariablen vom Typ int.{ Mit folgendem Programmstuck lasst sich die Flache eines Kreisesberechnen:
double area, radius;
radius = 12.0;
area = 3.14159*radius*radius;
197
Einlesen von Werten vom Typ float und double
| Leider ist das Einlesen von Werten fur float und double nicht soeinfach wie fur int.| Java stellt ein Klasse Double zur Verfugung, die es erlaubt, einDouble-Objekt aus einem String-Objekt zu berechnen.| Schließlich benotigt man noch eine Methode, um den Wert einesDouble-Objektes zu erhalten.
Double d = Double.valueOf(br.readLine());
double x = d.doubleValue();
Dies geht auch kompakt mit
double x = Double.valueOf(br.readLine()).doubleValue();
198
Wann soll man float oder double verwenden?
} Fließkommazahlen werden in der Regel verwendet, wenn man Zahlenmit Nachkommaanteil benotigt.} Die Genauigkeit von double ist fur viele Anwendungen hinreichend.} Allerdings gibt es Anwendungen, fur welche die Genauigkeit vondouble nicht ausreicht.} Ein typisches Beispiel ist das Losen großer Gleichungssysteme.Probleme tauchen aber auch bei Berechnungen im Finanzbereich auf,bei denen Rundungsfehler bis zur zweiten Nachkommastelleausgeschlossen werden mussen.
199
Gemischte Arithmetik
~ Dieselben Gesetze, die fur die Konvertierung zwischen int und long
gelten, finden auch fur float und double Anwendung.~ Allerdings kann man auch Integer-Variablen die Werte vonFließkommazahlen zuordnen und umgekehrt.~ Nur wenn es mit keinem Informationsverlust verbunden ist, kann eineZuweisung direkt erfolgen.~ Andernfalls muss man das Casting verwenden.
double x = 4.5;
int i = (int) x;
x = i;~ Dabei wird bei der Konvertierung von Fließkommazahlen nachInteger-Zahlen stets der Nachkommaanteil abgeschnitten
200
Zusammenfassung
� Java stellt verschiedene elementare Datentypen fur das”Verarbeiten
von Zahlen“ bereit.� Die Integer-Datentypen reprasentieren ganze Zahlen.� Die Datentypen float und double reprasentieren Fließkommazahlen.� Fließkommazahlen sind eine Teilmenge der rationalen und reellenZahlen.� Die Werte dieser Datentypen werden durch Literale beschrieben.� Fur die Konvertierung zwischen Datentypen verwendet man das Casting.� Berechnungen mit Daten vom Typ double und float konnenRundungsfehler produzieren.� Dadurch entstehen haufig falsche Ausgaben und Ergebnisse.� You have been warned!
201
Einfuhrung in die Informatik
Controlling Behavior
Das if-Statement
Wolfram Burgard
202
Motivation
� Bisher bestanden die Rumpfe unserer Methoden aus einzelnenStatements, z.B. Wertzuweisungen oder Methodenaufrufen.� Es gibt bisher keine Moglichkeit, Statements nur in Abhangigkeitbestimmter Umstande auszufuhren.� In diesem Kapitel behandeln wir bedingte Anweisungen, die eserlauben, Statements in Abhangigkeit davon auszufuhren, dass einebestimmte Bedingung erfullt ist.� Dadurch konnen wir flexiblere Methoden schreiben und deutlichmachtigere Modelle entwickeln.
203
Das if-Statement
� Java stellt mit dem if-Statement eine Form der bedingten Anweisungzur Verfugung.�Mit Hilfe des if-Statements konnen wir eine Bedingung testen und, jenach Ausgang des Tests, eine von zwei Anweisungen durchfuhren.
if (axles == 2)
tollDue = 4;
else
tollDue = 5 * axles;� Zeile 1 enthalt den Test, den wir ausfuhren.� Zeile 2 enthalt das Statement, das bei erfolgreichem Test ausgefuhrt wird.� Zeile 3 enthalt das Schlusselwort else und lautet den Teil ein, derausgefuhrt wird, wenn der Test fehlschlagt.� Zeile 4 enthalt das Statement, welches bei negativem Ausgang des Testsausgefuhrt wird. 204
Anwendungsbeispiel: Lohn und Gehalt
Aufgabe:� Modellieren Sie ein Lohnbuchhaltungssystem fur Mitarbeiter, die auf einerStundenbasis bezahlt werden.� Das System sollte in der Lage sein, den Lohn eines Mitarbeiters fur eineWoche aus seinem Grundlohn und den gearbeiteten Stunden zubestimmen.� Mitarbeiter, die mehr als 40 Stunden pro Woche gearbeitet haben,bekommen die Uberstunden mit 150% bezahlt.� Falls ein Mitarbeiter in zwei aufeinanderfolgenden Wochen mehr als 30Uberstunden absolviert hat, soll eine Warnung ausgegeben werden.
Ziel:� Modellierung dieser Anwendung mit gleichzeitiger Demonstration derVerwendung des if-Statements.
205
Beispielsitzung
� Zunachst wird der Name des Mitarbeiters und sein Gehalt ausgegeben.� Dann werden die Berechnungen fur die einzelnen, aufeinanderfolgendenWochen durchgefuhrt und die Ergebnisse ausgegeben.
Employee name: Gerald Weiss
Employee rate/hour: 20
Gerald Weiss earned 600 Dollar for 30 hours
Gerald Weiss earned 1100 Dollar for 50 hours
Gerald Weiss has worked 30 hours of overtime
Gerald Weiss earned 1400 Dollar for 60 hours
206
Benotigte Objekte
� In unserer Anwendung tauchen die Begriffe Mitarbeiter, Stunden, Lohnund Gehalt auf.� Davon ist Mitarbeiter der wichtigste.� Um Mitarbeiter zu modellieren, fuhren wir eine Klasse Employee ein:
class Employee {
// benotigte Methoden und Instanzvariablen
...
}
207
Erforderliches Verhalten
� Wir mussen das Gehalt berechnen, welches an den Mitarbeiterausgezahlt werden soll und verwenden dafur eine Methode calcPay.� Wir benotigen den Namen des Mitarbeiters und definieren dafur eineMethode getName.� Offensichtlich brauchen wir auch einen Konstruktor Employee, umEmployee-Objekte zu erzeugen.
208
Das Interface des Konstruktors Employee
� Wenn wir ein Objekt der Klasse Employee erzeugen wollen, mussen wirdie Daten wissen, die zur Gehaltsberechnung notwendig sind.� In unserer Anwendung sind das der Name und das Gehalt pro Stunde.� Um ein Employee-Objekt zu erzeugen, mussen wir also folgenden Codehinschreiben:
Employee e;
e = new Employee("Rudy Crew", 10);
209
Die Interfaces der Methoden calcPay und getName
� Um das Gehalt fur eine Woche zu berechnen, benotigen wir die Anzahlder gearbeiteten Stunden.� Das Ergebnis der Lohnberechnung ist der auszuzahlende Betrag.� Unser System modelliert Betrage durch ganze Zahlen vom Typ int.� Die Methode calcPay wird daher folgendermaßen verwendet:
int pay;
pay = e.calcPay(30);� Die Verwendung der getName-Methode wiederum ist einfach:
System.out.print(e.getName());
210
Das komplette Interface
Insgesamt ergibt sich folgende Beispielanwendung:
class Payroll {
public static void main(String a[]) {
Employee e;
e = new Employee("Rudy Crew", 10);
int pay;
int hours = 30;
pay = e.calcPay(hours);
System.out.print(e.getName());
System.out.print(" earned ");
System.out.print(pay);
System.out.print(" Dollars for ");
System.out.print(hours);
System.out.println(" hours");
}
}
211
Die Prototypen unserer Methoden
Aus dem Interface erhalten wir unmittelbar die Prototypen:
class Employee {
public Employee(String name, int rate) {...}
public int calcPay(int hours) {...}
public String getName() {...}
// Instance variable to be supplied
}
212
Erforderliche Instanzvariablen
� Ein Employee-Objekt wird erzeugt mit dem Namen des Mitarbeiters undseinem Gehalt.� Da beide Werte von Methoden benotigt werden, mussen wir sie inInstanzvariablen ablegen.� Hat der Mitarbeiter zu viele Uberstunden in den letzten beiden Wochendurchgefuhrt, so soll eine Warnung ausgegeben werden. Da die Anzahlder Uberstunden in der Vorwoche nicht von calcPay ubergeben wird,mussen wir auch diesen Wert in einer Instanzvariable ablegen.class Employee {
// Methods
...
// Instance variables
private String name;
private int rate;
private int lastWeeksOvertime;
}213
Implementierung: Der Employee-Konstruktor
Die Aufgabe des Konstruktors ist die Initialisierung der Instanzvariableneines Employee-Objektes:
public Employee(String name, int rate) {
this.name = name;
this.rate = rate;
this.lastWeeksOvertime = 0;
}
214
Implementierung: Die getName-Methode
Die Methode getName gibt einfach den Wert der Instanzvariable name
dieses Objektes zuruck.
public String getName() {
return this.name;
}
215
Implementierung: Die Methode calcPay (1)
� Die Methode calcPay erfullt zwei Aufgaben gleichzeitig:
1. Sie berechnet den Wochenlohn.
2. Sie gibt eine Warnung aus bei zu vielen Uberstunden.� Der Wochenlohn lasst sich einfach berechnen, wenn keine Uberstundenvorliegen:
pay = hours * this.rate; // No overtime
currentOvertime = 0;� Wenn mehr als 40 Stunden gearbeitet wurden, dann werden die ersten40 Stunden normal und alle daruber hinausgehenden Stunden mit dem1.5-fachen Stundenlohn vergutet:
pay = 40 * this.rate + (hours - 40)*(this.rate + this.rate/2);
currentOvertime = hours - 40;
216
Implementierung mit einer if-Anweisung
public int calcPay(int hours){
int pay, currentOvertime;
if (hours <= 40) {
pay = hours * rate;
currentOvertime = 0;
}
else {
pay = 40*rate+(hours-40)*(rate+rate/2);
currentOvertime = hours - 40;
}
...
}
217
Implementierung: Die Methode calcPay (2)
�Daruber hinaus muss die Methode calcPay auch noch berechnen, obeine Warnung wegen zu vielen Uberstunden ausgegeben werden soll.� Wir verwenden wiederum ein if-Statement, um zu bestimmen, ob eineWarnung generiert wird:
if (currentOvertime + this.lastWeeksOvertime >= 30) {
System.out.print(this.name);
System.out.println(" has worked 30 or more hours of overtime");
}
218
Implementierung: Die Methode calcPay (3)
� Dannn muss noch der Ubertrag der Uberstunden berechnet werden.� Schließlich muss der auszuzahlende Betrag als Ergebniswertzuruckgegeben werden.
this.lastWeeksOvertime = currentOvertime;
return pay;
219
Die komplette Implementierung (1)
class Employee {
public Employee(String name, int rate) {
this.name = name;
this.rate = rate;
this.lastWeeksOvertime = 0;
}
public int calcPay(int hours) {
int pay;
int currentOvertime;
if (hours <= 40) {
pay = hours * rate;
currentOvertime = 0;
}
else {
pay = 40*rate+(hours-40)*(rate+rate/2);
currentOvertime = hours - 40;
}
220
Die komplette Implementierung (2)
if (currentOvertime + this.lastWeeksOvertime >= 30) {
System.out.print(this.name);
System.out.println(
" has worked 30 or more hours of overtime");
}
this.lastWeeksOvertime = currentOvertime;
return pay;
}
public String getName() {
return this.name;
}
private String name;
private int rate;
private int lastWeeksOvertime;
}
221
Verwendung der Klasse Employee
class Payroll {
public static void main(String a[]) {
Employee e;
e = new Employee("Rudy Crew", 10);
int pay;
int hours = 30;
pay = e.calcPay(hours);
System.out.print(e.getName());
System.out.print(" earned ");
System.out.print(pay);
System.out.print(" Dollars for ");
System.out.print(hours);
System.out.println(" hours");
}
}
Ausgabe: Rudy Crew earned 300 Dollars for 30 hours222
Eine langere Beispielanwendung
e = new Employee("Rudy Crew", 10);
int pay;
int hours = 30;
pay = e.calcPay(hours);
System.out.print(e.getName());
...
hours = 50;
pay = e.calcPay(hours);
System.out.print(e.getName());
...
hours = 60;
pay = e.calcPay(hours);
System.out.print(e.getName());
...
Ausgabe:
Rudy Crew earned 300 Dollars for 30 hours
Rudy Crew earned 550 Dollars for 50 hours
Rudy Crew has worked 30 or more hours of overtime
Rudy Crew earned 700 Dollars for 60 hours223
Diskussion der Modellierung
� Durch die Festlegung von Employee als das wichtigste Objekt konntenalle notwendigen Methoden und Eigenschaften sinnvoll in einer Klassedefiniert werden.� Die anderen in diesem Modell betrachteten Einheiten, wie z.B. name undrate haben eine spezielle Beziehung zu der Klasse Employee.� Wir bezeichnen diese Beziehung als eine has-a-Relation: Ein Mitarbeiterhat einen Namen etc.� Da die Wochenarbeitszeit nicht einen langerfristig gleichen Wert hat wiez.B. name und rate, wurden die Stunden, die sich permanent andern,nicht in einer Instanzvariablen abgelegt (obwohl das prinzipiell moglichware).
224
Die zwei Formen des if-Statements
� Die erste Variante des if-Statements verzweigt in zwei unterschiedlicheProgrammstucke, die in Abhangigkeit von dem Test ausgefuhrt werden:
if (condition)
statement1
else
statement2� Die zweite Version lasst den else-Teil aus und fuhrt das Statement nuraus, wenn der Test erfolgreich ist.
if (condition)
statement
In dieser Version wird das Statement nur ausgefuhrt, wenn dieBedingung wahr ist, sonst wird die Anweisung ausgelassen.
225
Mehrere Anweisungen in if-Statements
�In der Grundversion des if-Statements konnen nur einzelne Statementsim then-Teil und else-Teil verwendet werden.� Sollen mehrere Statements ausgefuhrt werden, muss man diese zueinem Block zusammenfassen, indem man sie in Klammern (� und � )einschließt.
System.out.print(x); System.out.print(" is greater than "); System.out.println(y);
else {
System.out.print(x);
System.out.println(y); System.out.print(" is not greater than ");
}
}
if (x > y) {
Statementszusammengesetzte
226
Bedingungen in if-Statements
� Die Bedingung eines if-Statements muss ein Ausdruck sein, derentweder wahr oder falsch ist.� Im Moment schranken wir uns auf Vergleiche zwischen Zahlwerten ein.� Java stellt folgende Operatoren fur den Vergleich von Zahlen zurVerfugung:
Operator Bedeutung
< kleiner
> großer
== gleich
<= kleiner gleich
>= großer gleich
!= ungleich
227
Kaskadierte if-Statements
� In der Praxis tauchen haufig Situationen auf, in denen es mehrereAlternativen gibt und in denen das Programm in mehrere Teileverzweigen muss.�Eine einfache Variante sind so genannte Multiway-Tests, bei denen einAusdruck mehr als zwei Werte haben kann.�Ein typisches Beispiel hierfur ist
if (hours <= 40)
System.out.println("No overtime");
else if (hours <= 60)
System.out.println("Overtime");
else // Hours > 60
System.out.println("Double-overtime");
228
Geschachtelte if-Statements
� if-Statements lassen sich nicht nur kaskadieren sondern auchschachteln.� Dadurch wird in manchen Fallen kompakterer Code erreicht.� Nehmen wir an, wir wollten nur etwas ausgeben, wenn Uberstundengemacht wurden:
if (hours > 40)
if (hours <= 60)
System.out.println("Overtime");
else
System.out.println("Double-overtime");
229
Zu welchem if gehort ein else?
� In unserem Beispiel konnte das else entweder zu dem Testhours > 40 oder zu dem Test hours <= 60 gehoren.� Jede dieser Varianten hat ein eigenes Verhalten des Programms zurFolge.� Ein else gehort immer zu dem letzten if, fur das noch ein else
fehlt.� Unser Beispiel entspricht daher:
if (hours > 40) {
if (hours <= 60)
System.out.println("Overtime");
else
System.out.println("Double-overtime");
}230
Wirkung von Klammern bei Geschachtelten if-Statements
� In bestimmten Fallen mussen Klammern gesetzt werden, um daskorrekte Verhalten zu erreichen.� Bei der folgenden Variante wurden falsche Ausgaben erzeugt, wenn dieKlammern fehlten (Double-overtime bei hochstens 40 Stunden):
if (hours <= 60) {
if (hours > 40)
System.out.println("Overtime");
}
else
System.out.println("Double-overtime");� Beachten Sie die Einruckung der Statements. Diese sollte dieZuordnung der Statements widerspiegeln.
231
Das switch-Statement
� Fur den Fall, dass in einem kaskadierten if-Statement nur Tests aufGleichheit vorkommen, kann man mit dem switch-Statement eineeinfachere Konstruktion verwenden.�Die Struktur des switch-Statements ist
switch (x) {
case value1: statement1;
break;
case value2: statement2;
break;
...
case valuen: statementn;
break;
default: statement;
break;
}
232
Anwendung des switch-Statements
� Die einzelnen Werte mussen Konstanten oder Literale sein.� Im Gegensatz zum if-Statement werden keine Klammern benotigt, umverschiedene Statements innerhalb eines Falles zu verwenden.� Der default-Fall ist optional.� Jede Sequenz von Anweisungen fur einen Fall soll mit der Anweisungbreak abschließen.� Fehlt das break-Statement, wird mit den Anweisungen des nachstenFalles fortgefahren.
233
Beispielanwendung
switch (dayOfWeek) {
case 2:
case 4: System.out.println("Heute ist Informatik-Vorlesung!");
break;
case 1:
case 3:
case 5:
case 6:
case 7: System.out.println("Heute ist keine Informatik-Vorlesung!");
break;
default: System.out.print("Fehler: dayOfWeek hat unzulassigen Wert: ");
System.out.println(dayOfWeek);
break;
}
234
if-Statement versus switch-Statement
� Im Prinzip kann jedes switch-Statement durch kaskadierteif-Statements ersetzt werden.� switch-Statements sind jedoch einfacher zu lesen.� Daruber hinaus werden switch-Statements haufig schnellerausgewertet als aquivalente if-Statements.
235
Anwendung des if-Statements: Testen auf Ende des Inputs
� Viele Methoden haben Return-Werte.� Bei manchen von diesen Methoden kann jedoch nicht garantiert werden, dasstatsachlich ein Wert zuruckgegeben werden kann.� Ein Beispiel ist das Einlesen aus Dateien oder dem Internet: Wenn die Datei oderdie Ressource keine Zeile (mehr) enthalt, kann die Methode readLine keinString-Objekt zuruckliefern.� Fur solche Falle gibt es das Schlusselwort null.� Wahrend jeder Wert einer Referenzvariablen ein Objekt referenziert, steht derWert null fur den Fall, dass kein Objekt referenziert wird.
String s;
s = br.readline();
if (s == null)
... // nothing can be read
else
... // something could be read236
Der Typ boolean
� Fur logischen Werte wahr und falsch gibt es in Java einen primitivenDatentyp boolean� Die moglichen Werte von Variablen dieses Typs sind true und false.� Wie Integer-Variablen kann man auch Variablen vom Typ boolean
vereinbaren.� Diesen Variablen konnen Werte logischer Ausdrucke zugewiesenwerden.
237
Anwendung vom Typ boolean
Typische Situation:
boolean hasOvertime;
if (hours > 40)
hasOvertime = true;
else
hasOvertime = false;
...
if (hasOvertime) // same as: if (hasOvertime == true)
...
Alternative:
boolean hasOvertime;
hasOvertime = (hours > 40);
...
if (hasOvertime)
...238
Logische Ausdrucke
� Die Bedingung temp < 32 ist ein Ausdruck, der einenVergleichsoperator enthalt.� Da das Ergebnis eines solchen Vergleichs ein Boolescher Wert ist,bezeichnen wir Ausdrucke dieser Art als Boolesche oder logischeAusdrucke.� Im Bedingungsteil einer if-Anweisung konnen beliebige BoolescheAusdrucke stehen.
239
Logische Operatorenund zusammengesetzte logische Ausdrucke
�Haufig besteht eine Bedingung aus mehreren Teilbedingungen diegleichzeitig erfullt sein mussen.�Beispielsweise hat ein Mitarbeiter normale Uberstunden absolviert, wenner uber 40 und hochstens 60 Stunden pro Woche gearbeitet hat.�Java erlaubt es, mehrere Tests mit Hilfe logischer Operatoren zu einemTest zusammenzusetzten:
hours > 40 && hours <= 60� Der &&-Operator reprasentiert das logische Und.� Der ||-Operator realisiert das logische Oder.� Der !-Operator realisiert die Negation.
240
Prazedenzregeln fur logische Operatoren
� Der !-Operator hat die hochste Prazedenz von den logischenOperatoren. Zweithochste Prazedenz hat der &&-Operator. Schließlichfolgt der ||-Operator.� Der Ausdruck
if (this.hours < hours ||
this.hours == hours && this.minutes < minutes)
hat daher die gleiche Bedeutung wie
if (this.hours < hours ||
(this.hours == hours && this.minutes < minutes))� Durch Klammern werden Ausdrucke leichter lesbar!
241
Das if-Statement und das Logische Und
� Der &&-Operator fur das logische Und kann auch durch einegeschachtelte if-Anweisung realisiert werden.
if (condition1)
if (condition2)
statement
und
if (condition1 && condition2)
statement
haben die gleiche Wirkung.� In der Regel sind zusammengesetzte Ausdrucke jedoch einfacher zulesen als geschachtelte if-Anweisungen.
242
Das if-Statement und das Logische Oder
� Sind Statements in aufeinanderfolgenden Then-Teilen vonkaskadierten if-Anweisungen identisch, kann die if-Anweisung durchVerwendung des ||-Operators fur das logische Oder vereinfachtwerden.� Beispielsweise kann
if (condition1)
statement
else if (condition2)
statement
ersetzt werden durch
if (condition1 || condition2)
statement� Auch hier ist die zweite Variante vorzuziehen, um Wiederholungenderselben Anweisung(sfolge) zu vermeiden.
243
Zusammenfassung (1)
Bedingte Anweisungen erlauben es, in Abhangigkeit von derAuswertung einer Bedingung im Programm verschiedene Anweisungendurchzufuhren. Dadurch kann der Programmierer den Kontrollfluss steuern und inseinem Programm entsprechend verzweigen. Mit einem if-Statement kann man zwei Falle unterscheiden. Das switch-Statement erlaubt das Verzweigen in mehr als zweiAlternativen. Mit Hilfe der bedingten Anweisung kann man testen, ob das Ende einerDatei erreicht ist. In diesem Fall liefert die readLine-Methode den Wert null.
244
Zusammenfassung (2)
¡ Bedingungen sind Boolesche Ausdrucke, die zu true oder falseausgewertet werden..¡ In Java gibt es dafur den primitiven Datentyp boolean mit den beidenWerten true und false.¡ Einfache Boolesche Ausdrucke konnen mit den Vergleichsoperatoren<, >, <=, >=, ==, und !=, die auf Zahltypen operieren, definiert werden.¡ Komplexere Boolesche Ausdrucke werden mit den logischenOperatoren &&, || und ! zusammengesetzt.
245
Einfuhrung in die Informatik
Working with Multiple Objects and the while-Loop
Wolfram Burgard
246
Motivation
¢ In der Praxis ist es haufig erforderlich, ein und dieselbe Anweisung oderAnweisungsfolge auf vielen Objekten zu wiederholen.¢ Beispielsweise mochte man das Gehalt fur mehrere tausend Mitarbeiterberechnen.¢ In Java gibt es mit dem while-Statement eine weitere Moglichkeit dieProgrammausfuhrung zu beeinflussen.¢ Insbesondere lassen sich mit dem while-Statement Anweisungsfolgenbeliebig oft wiederholen.
247
Verarbeiten mehrerer Objekte
Ein einfaches Problem ist die Situation, in der£ alle Objekte unabhangig voneinander verarbeitet werden konnen und£ in der einmal verarbeitete Objekte nicht langer benotigt werden.
Beispiel fur eine solche Situation:
Employee e = Emloyee.readIn(br);
int hours = Integer.parseInt(br.readline());
System.out.print("Employee " + e.getName() +
" has earned " + e.calcPay(hours));
Diese Anweisungen sollen fur alle Mitarbeiter jeweils einmal ausgefuhrtwerden.
248
Das Konstrukt einer Schleife
Gesucht ist eine Anweisung, die es uns erlaubt, eine gegebene Sequenz vonStatements zu wiederholen. Eine entsprechende Konstruktion nennt maneine Schleife oder Loop.
Bestandteile einer Schleife:¤ Rumpf der Schleife. Dieser enthalt alle Anweisungen, die wiederholtausgefuhrt werden sollen.¤ Der Kopf der Schleife. Mit diesem wird festgelegt, wie haufig dieSchleife ausgefuhrt werden soll oder wann mit der Ausfuhrungabgebrochen werden soll, d.h. wann sie terminieren soll.
Employee e = Emloyee.readIn(br); int hours = Integer.parseInt(br.readline());System.out.print("Employee " + e.getName() + " has earned " + e.calcPay(hours));
249
Das while-Statement
¥ Das erste Wiederholungskonstrukt ist die while-Schleife.¥ Die allgemeine Form ist:
while(condition)
body¥ Dabei sind die Bedingung (condition) und der Rumpf (body) ebensowie bei der if-Anweisung aufgebaut.¥ Die Bedingung im Schleifenkopf ist eine logischer Ausdruck vom Typboolean.¥ Der Rumpf ist ein einfaches oder ein zusammengesetztes Statement.
250
Ausfuhrung der while-Anweisung
1. Zunachst wird die Bedingung uberpruft.
2. Ist der Wert des Ausdrucks false, wird die Schleife beendet. DieAusfuhrung wird dann mit der nachsten Anweisung fortgesetzt, dieunmittelbar auf den Rumpf folgt.
3. Wertet sich der Ausdruck hingegen zu true aus, so wird der Rumpf derSchleife ausgefuhrt.
4. Dieser Prozess wird solange wiederholt, bis in Schritt 2. der Fall eintritt,dass sich der Ausdruck zu false auswertet.
251
Typisches Schema fur die Verwendung einer while-Schleife
¦ Vor der Schleife findet die Initialisierung fur den ersten Durchlauf einerSchleife statt:
1. Bereitstellen der Objekte fur den ersten Durchlauf und
2. Variablen, die in der Abbruchbedingung vorkommen, inAbhangigkeit von dem vorangegangenen Schritt setzen.¦ In jedem Schleifendurchlauf dann:
1. Das aktuelle Objekt verarbeiten und
2. Variablen fur den nachsten Durchlauf setzen, d.h. zum nachstenObjekt ubergehen.
Haufig werden dabei die einzelnen Schritte vermischt, d.h. mit demBereitstellen der Daten fur den ersten Durchlauf wird auch gleich dieAbbruchbedingung entsprechend gesetzt.
252
Zuruck zum Buchhaltungsbeispiel
Employee e = Employee.readIn(br); //Read first object
while (e != null) { //Object read?
int hours = Integer.parseInt(br.readLine()); //Process Object ...
System.out.println("Employee " + e.getName() +
" has earned " + e.calcPay(hours));
e = Employee.readIn(br); //Read next object
}
Sofern das Einlesen des ersten Objektes gelingt, ist e ungleich null.
Dies ist eine typisches Beispiel fur die Verwendung einer while-Schleife
253
Beispiel: Einlesen aller Zeilen von www.whitehouse.gov
import java.net.*;
import java.io.*;
class WHWWWLong {
public static void main(String[] arg) throws Exception {
URL u = new URL("http://www.whitehouse.gov/");
BufferedReader whiteHouse = new BufferedReader(
new InputStreamReader(u.openStream()));
String line = whiteHouse.readLine(); // Read first object.
while (line != null){ // Something read?
System.out.println(line); // Process object.
line = whiteHouse.readLine(); // Get next object.
}
}
}
254
Eine kompaktere Version
Das Einlesen kann mit einer Anweisung durchgefuhrt werden, wenn eineWertzuweisung im Test verwendet wird:
import java.net.*;
import java.io.*;
class WHWWWAll {
public static void main(String[] arg) throws Exception {
URL u = new URL("http://www.whitehouse.gov/");
BufferedReader whiteHouse = new BufferedReader(
new InputStreamReader(u.openStream()));
String line;
while ((line = whiteHouse.readLine()) != null)
System.out.println(line);
}
}
255
Anwendung der while-Schleife zur Approximation
Viele Werte (Nullstellen, Extrema, ...) lassen sich in Java nicht durchgeschlossene Ausdrucke berechnen, sondern mussen durch geeigneteVerfahren approximiert werden.
Beispiel: Approximation von §¨ ©Ein beliebtes Verfahren ist die Folge
© ª « ¬ © ª © ®ª ©¯ ° © ±ª ²wobei
© ¬ ³ ´ein beliebiger Startwert ist.
Mit µ ¶ · konvergierta© ª
gegen §¨ ©, d.h.¸ ¹ ºª » ¼ © ª §¨ ©
aSofern kein ½ ¾ ¿ À256
Muster einer Realisierung
Á Zur naherungsweisen Berechnung verwenden wir eine while-Schleife.ÁDabei mussen wir zwei Abbruchkriterien berucksichtigen:
1. Das Ergebnis ist hinreichend genau, d.h. Â Ã Ä Å und  Ãunterscheiden sich nur geringfugig.
2. Um zu vermeiden, dass die Schleife nicht anhalt, weil die gewunschteGenauigkeit nicht erreicht werden kann, muss man die Anzahl vonSchleifendurchlaufen begrenzen.Á Wir mussen also solange weiterrechnen wie folgendes gilt:
Math.abs((xnPlus1 - xn)) >= maxError && n < maxIterations
257
Das Programm zur Berechnung der Dritten Wurzel
import java.io.*;
class ProgramRoot {
public static void main(String arg[]) throws Exception{
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
int n = 1, maxIterations = 1000;
double maxError = 1e-6, xnPlus1, xn = 1, x;
x = Double.valueOf(br.readLine()).doubleValue();
xnPlus1 = xn - ( xn * xn * xn - x) / (3 * xn * xn);
while (Math.abs((xnPlus1 - xn)) >= maxError && n < maxIterations){
xn = xnPlus1;
xnPlus1 = xn - ( xn * xn * xn - x) / (3 * xn * xn);
System.out.println("n = " + n + ": " + xnPlus1);
n = n+1;
}
}
}
258
Anwendung des Programms
Eingabe: Æ Ç È Eingabe: É Ê Ë Ìn = 1: -5.685155555555555
n = 2: -4.068560488977107
n = 3: -3.256075689936079
n = 4: -3.0196112473705674
n = 5: -3.0001270919925287
n = 6: -3.000000005383821
n = 7: -3.0
Process ProgramRoot finished
n = 1: 2.2222222222222218E89
n = 2: 1.481481481481481E89
n = 3: 9.876543209876541E88
n = 4: 6.584362139917694E88
...
n = 996: 9.999999999999999E29
n = 997: 1.0E30
n = 998: 9.999999999999999E29
n = 999: 1.0E30
Process ProgramRoot finished
259
Anwendung der while-Schleife: Berechnung des ggT
Í Das Verfahren zur Berechnung des großten gemeinsamen Teilers(ggT) von zwei Zahlen a und b geht zuruck auf Euklid (etwa 300 v. Chr.)Í Idee des Verfahrens:
Î Î Ï Ð Ñ Ò Ó Ô Õ Ö× Ø Ó falls Ñ Ù Ú Û Ó Õ ÜÎ Î Ï Ð Ó Ò Ñ Ù Ú Û Ó Ô sonst
Í Auswertungsbeispiele:Î Î Ï Ð Ý Þ Ò ß Ô Õ Î Î Ï Ð ß Ò à Ô Õ àÎ Î Ï Ð á â Ò Ý ß Ô Õ Î Î Ï Ð Ý ß Ò ã Ô Õ ã260
Anwendung des Verfahrens
ä ä å æ ç è é ê ë ìí î é falls ç ï ð ñ é ë òä ä å æ é è ç ï ð ñ é ê sonst
Beispiel: a = 27, b = 48
a b a%b
261
Das komplette Programm
import java.io.*;
class ProgramggT {
public static void main(String arg[]) throws java.io.IOException{
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
int a = Integer.parseInt(br.readLine());
int b = Integer.parseInt(br.readLine());
while (a % b != 0){ // Done?
int r = a % b; // Process data and prepare next round
a = b;
b = r;
}
System.out.println("Der ggT ist " + b);
}
}
262
Anwendungsbeispiel
Betrachten wir die Eingaben 81 fur a und 15 fur b.
Die folgende Tabelle enthalt die Werte der Ausdrucke bzw. Variablen zumZeitpunkt des i-ten Tests der Bedingung a % b == 0 der while-Schleife:
Test Nr. a b a % b
1 81 15 6
2 15 6 3
3 6 3 0
Ausgabe daher: 3
263
Geschachtelte while-Schleifen
ó Ebenso wie if-Anweisungen, konnen auch while-Schleifengeschachtelt werden.ó Eine typische Struktur ist:
while (condition1) {
...
while (condition2) {
...
}
...
}ó Dabei wird fur jeden Durchlauf der außeren Schleife (condition1) dieinnere Schleife (condition2) ausgefuhrt.
264
Beispiel: Berechnung von Primzahlen unter 10000
ô n ist durch i teilbar, wenn n % i == 0.ô Eine Zahl ist Primzahl, wenn sie durch keine andere Zahl teilbar ist.ô D.h. fur jede Zahl i, die kleiner als n ist, mussen wir prufen mussen, ob i
Teiler von n ist.ô Dies mussen wir fur jedes n < 10000 durchfuhren. Dabei genugt es, bei2 zu beginnen.
265
Das komplette Programm
class ProgramPrimes1 {
public static void main(String arg[]) {
int m = 10000;
int n = 2;
while (n < m){
int i = 2;
boolean prime = true; // Assume n is a prime
while (i < n && prime){
prime = ((n % i) != 0); // Is n dividable by i?
i++;
}
if (prime)
System.out.println("Prime number : " + n);
n++;
}
}
}
266
Entwicklung einer effizienteren Version
õ In dieser Version wird die”innere Schleife“ jeweils n-mal
”durchlaufen“.õ Tatsachlich genugt es jedoch nur die Zahlen ö ÷ ø ù zu betrachten:
1. Ist ö Teiler von ù , so existiert ein ú mit ö û ú ü ù2. Offensichtlich genugt es dann, alle ö mit ö ÷ ú zu betrachten.
3. Aus ö û ú ÷ ù und ö ÷ ú folgt aber, dass ö ÷ ø ù ist.
267
Eine effizientere Version
Es genugt, alle i mit i*i <= n zu betrachten!
class ProgramPrimes2 {
public static void main(String arg[]) {
int m = 10000;
int n = 2;
while (n < m){
int i = 2;
boolean prime = true; // Assume n is a prime
while (i*i <= n && prime){
prime = ((n % i) != 0); // Is n dividable by i?
i++;
}
if (prime)
System.out.println("Prime number : " + n);
n++;
}
}
}
268
Vergleich der Laufzeiten
0
5
10
15
20
25
0 5000 10000 15000 20000
runt
ime
[s]
m
ProgramPrimes1ProgramPrimes2
269
Kollektionen mehrere Objekte: Die Klasse Vector
ý Mit Vector stellt Java eine Klasse zur Verfugung, die eineZusammenfassung von unter Umstanden auch verschiedenenObjekten in einer Liste oder Reihe erlaubt.ý Die Klasse Vector ist nicht gedacht fur Vektoren im mathematischenSinn und deren Operationen.ý Grundoperationen fur Kollektionen von Objekten sind:
1. das Erzeugen einer Kollektion (mit dem Konstruktor),
2. das Hinzufugen von Objekten in die Kollektion,
3. das Loschen von Objekten aus der Kollektion, und
4. das Verarbeiten von Objekten in der Kollektion.
270
Kollektion eventuell unterschiedlicher Objektemit der Klasse Vector
v
object 1
object 2
object 3
...
object n
...
objectVector−
271
Erzeugen eines Vector-Objektes
þ Wie bei anderen Klassen auch werden Objekte der Klasse Vector mitdem Konstruktor erzeugt.þ Der Konstruktor von Vector hat keine Argumente:
Vector v = new Vector();þ Wirkung des Konstruktors:v
objectVector−
272
Hinzufugen von Objekten zu einem Vector-Objekt
ÿ Um Objekte zu einem Vector-Objekt hinzuzufugen, verwenden wir dieMethode addElement.ÿ Dieser Methoden geben wir als Argument das hinzuzufugende Objekt mit.ÿ Das folgende Programm liest eine Sequenz von String-Objekten einund fugt sie unserem Vector-Objekt hinzu:
String s = br.readLine() // Read first string
while (s != null){ // Something read?
v.addElement(s); // Processing adds s to v
s = br.readLine(); // Read next string
}
273
Anwendung dieses Programmstucks
1 Aufruf von addElement 4 Aufrufe von addElement
v
objectVector−
String−objectv
objectVector−
String−objects
Hier referenziert unser Vector-Objekt lediglich Objekte der Klasse String.
274
Durchlauf durch einen Vektor
� Der Prozess des Verarbeitens aller Objekte einer Kollektion wird auchDurchlauf genannt.� Ziel ist es, eine (von der Anwendung abhangige) Operation auf allenObjekten der Kollektion auszufuhren.� Dazu verwenden wir eine while-Schleife der Form:
while (es gibt noch Objekte, die zu besuchen sind)
besuche das nachste Objekt� Die zentralen Aufgaben, die wir dabei durchfuhren mussen, sind:
– auf die Objekte einer Kollektion zugreifen,
– zum nachsten Element einer Kollektion ubergehen und
– testen, ob es noch weitere Objekte gibt, die besucht werdenmussen.
275
Wie kann man Durchlaufe realisieren?
� Offensichtlich mussen diese Funktionen von jeder Kollektionsklasserealisiert werden.� Dabei sollten die entsprechenden Methoden moglichst so sein, dass sienicht von der verwendeten Kollektionsklasse abhangen.� Vielmehr ist es wunschenswert, dass jede Kollektionsklasse sich aneinen Standard bei diesen Methoden halt.� Auf diese Weise kann man sehr leicht zu anderen Kollektionsklassenubergehen, ohne dass man das Programm andern muss, welches dieKollektionsklasse verwendet.
276
Enumerations
� Java bietet eine abstrakte Klasse Enumeration zur Realisierung vonDurchlaufen durch Vector-Objekte und andere Kollektionsklassen.� Jede Kollektionsklasse stellt eine Methode zur Erzeugung einesEnumeration-Objektes zur Verfugung.� Die Klasse Vector enthalt eine Methode elements, die eine Referenzauf ein Enumeration-Objekt liefert. Ihr Prototyp ist:
Enumeration elements() // Liefert eine Enumeration fur einen Vector� Die Klasse Enumeration wiederum bietet die folgenden Methoden
boolean hasMoreElements() // True, falls es weitere Elemente gibt
Object nextElement() // Liefert das nachste Objekt
277
Der Return-Type von nextElement
� Im Prinzip muss die Methode nextElement Referenzen auf Objektebeliebiger Klassen liefern.� Um eine breite Anwendbarkeit realisieren zu konnen, mussen Klassenwie Vector oder Enumeration diese Flexibilitat haben.� Aus diesem Grund liefern solche Methoden eine Referenz auf einObject-Objekt.� Object ist eine Klasse und in Java ist jedes Objekt auch einObject-Objekt.� Somit kann also eine Methode wie nextElement mit ihremObject-Ruckgabewert beliebige Objekte zuruckgeben.� Allerdings muss man in Java mittels Casting stets mitteilen, was fur einObjekt durch einen Aufruf von nextElement zuruckgegeben wird.
278
Durchlauf durch ein Vector-Objekt
� Um einen Durchlauf durch unser Vector-Objekt v zu realisieren, gehenwir nun wie folgt vor:
while (es gibt weitere Elemente) {
x = hole das nachste Element
verarbeite x
}� Dies wird nun uberfuhrt zu
Enumeration enum = v.elements();
while (enum.hasMoreElements()) {
String s = (String) enum.getNextElement();
System.out.print(s);
}
279
Primitive Datentypen und Vector-Objekte
� Kollektionsklassen wie Vector konnen nur Objekte aufnehmen.� Primitive Datentypen wie int, float und boolean stellen keineObjects dar und konnen daher nicht als Parameter von addElement
verwendet werden.� Um dieses Problem zu losen bietet Java so genannte Wrapper-Klassenfur primitive Datentypen dar, z.B.:
Primitiver Typ Wrapper-Typ
int Integer
boolean Boolean
float Float� Daruber hinaus enthalten die Wrapper-Klassen auch static-Methodenfur den jeweiligen primitiven Datentyp.
280
Einfugen von ints in ein Vector-Objekt
Vector vi = new Vector();
int i;
i = 1;
vi.addElement(new Integer(i));
i = 2;
vi.addElement(new Integer(i));
i = 3;
vi.addElement(new Integer(i));
Enumeration e = vi.elements();
while (e.hasMoreElements()) {
Integer i1 = (Integer) e.nextElement();
System.out.println(i1.intValue());
}
Fur double, boolean, float etc. ist das Verfahren analog.281
Anwendung von Vector zur Modellierung von Mengen
� Auf der Basis solcher Kollektionsklassen wie Vector lassen sich nunandere Kollektionsklassen definieren.� Im folgenden modellieren wir Mengen mithilfe der Vector-Klasse.� Ziel ist die Implementierung einer eigenen Klasse Set einschließlichtypischer Mengen-Operationen.
282
Festlegen des Verhaltens der Set-Klasse
In unserem Beispiel wollen wir die folgenden Mengenoperationenbzw. Methoden zur Verfugung stellen:� Den Set-Konstruktor� contains (Elementtest)�
isEmpty (Test auf die leere Menge)� addElment (hinzufugen eines Elements)� copy (Kopie einer Menge erzeugen)�size (Anzahl der Elemente)� elements (Durchlauf durch eine Menge)�union (Vereinigung)�intersection (Durchschnitt)� Alle Elemente ausgeben
283
Notwendigkeit der copy-Operation
Der Effekt der Anweisung s2 = s1 = new Set() ist, dass es zweiReferenzen auf ein- und dasselbe Set-Objekt gibt:
Da Methoden wie addElement ein Set-Objekt verandern, benotigen wireine Kopier-Operation um eine Menge zu speichern
Nach der Anweisung s2 = s1.copy() gibt es zwei Referenzen auf zweiunterschiedliche Objekte mit gleichem Inhalt.
s1 s2
Set −Objekt
Set −Objekt
Set −Objekt
s1
s2
s2 = s1 = new Set(); s2 = s1.copy();
284
Festlegen der Schnittstellen
Prototypen der einzelnen Methoden:
public Set()
public boolean isEmpty()
public int size()
public boolean contains(Object o)
public void addElement(Object o)
public Set copy()
public Set union(Set s)
public Set intersection(Set s)
public Enumeration elements()
285
Ein typisches Beispielprogramm
class useSet {
public static void main(String [] args) {
Set s1 = new Set();
s1.addElement("A");
s1.addElement("B");
s1.addElement("C");
s1.addElement("A");
System.out.println(s1);
Set s2 = new Set();
s2.addElement("B");
s2.addElement("C");
s2.addElement("D");
s2.addElement("D");
System.out.println(s2);
System.out.println(s1.union(s2));
System.out.println(s1.intersection(s2));
}
}
286
Das Skelett der Set-Klasse
class Set {
public Set() {... };
public boolean isEmpty() {... };
public int size() {... };
public boolean contains(Object o) {... };
public void addElement(Object o) {... };
public Set copy() {... };
public Set union(Set s) {... };
public Set intersection(Set s) {... };
public Enumeration elements() {... };
...
private Vector theElements;
}
287
Implementierung der Methoden (1)
1. Der Konstruktor ruft lediglich die entsprechende Methode derVector-Klasse auf:
public Set() {
this.theElements = new Vector();
}
2. Die Methoden size und empty nutzen ebenfalls vordefinierte Methodender Klasse Vector:
public boolean isEmpty() {
return this.theElements.isEmpty();
}
public int size() {
return this.theElements.size();
}288
Implementierung der Methoden (2)
3. Um alle Elemente der Menge aufzuzahlen, mussen wir eine Methodeelements realisieren:
Enumeration elements() {
return this.theElements.elements();
}
4. Die copy-Methode muss alle Elemente des Vector-Objektesdurchlaufen und sie einem neuen Set-Objekt hinzufugen:
public Set copy() {
Set destSet = new Set();
Enumeration enum = this.elements();
while (enum.hasMoreElements())
destSet.addElement(enum.nextElement());
return destSet;
}289
Implementierung der Methoden (3)
5. Da Mengen jeden Wert hochstens einmal enthalten, mussen wir vor demEinfugen prufen, ob der entsprechende Wert bereits enthalten ist:
public void addElement(Object o) {
if (!this.contains(o))
this.theElements.addElement(o);
}
290
Implementierung der Methoden (4)
6. Um die Vereinigung von zwei Mengen zu berechnen, kopieren wir dieerste Menge und fugen der Kopie alle noch nicht enthaltenen Elementeaus der zweiten Menge hinzu.
public Set union(Set s) {
Set unionSet = s.copy();
Enumeration enum = this.elements();
while (enum.hasMoreElements())
unionSet.addElement(enum.nextElement());
return unionSet;
}
291
Implementierung der Methoden (5)
7. Um den Durchschnitt von zwei Mengen zu berechnen, starten wir mitder leeren Menge. Dann durchlaufen wir das Empfanger-Set und fugenalle Elemente zu der neuen Menge hinzu, sofern sie auch in dem zweitenSet-Objekt vorkommen.
public Set intersection(Set s) {
Set interSet = new Set();
Enumeration enum = this.elements();
while (enum.hasMoreElements()) {
Object elem = enum.nextElement();
if (s.contains(elem))
interSet.addElement(elem);
}
return interSet;
}
292
Implementierung der Methoden (6)
8. Um zu testen, ob ein Objekt in einer Menge enthalten ist, mussen wireinen Durchlauf realisieren. Dabei testen wir in jedem Schritt, ob dasgegebene Objekt mit dem aktuellen Objekt in der Menge ubereinstimmt:� Dies wirft das Problem auf, dass wir Objekte vergleichen mussen, ohne
dass wir wissen, zu welcher Klasse sie gehoren.� Hierbei ist zu beachten, dass der Gleichheitstest == lediglich testet, ob derWert von zwei Variablen gleich ist, d.h. bei Referenzvariablen, ob siedasselbe Objekt referenzieren (im Gegensatz zu
”das gleiche“).� Um beliebige Objekte einer Klasse miteinander vergleichen zu konnen,
stellt die Klasse Objekt eine Methode equals zur Verfugung.� Spezielle Klassen wie z.B. Integer oder String aber auchprogrammierte Klassen konnen ihre eigene equals-Methodebereitstellen.� Im folgenden gehen wir davon aus, dass eine solche Methode stetsexistiert.
293
Implementierung der Methoden (6)
9. Daraus resultiert die folgende Implementierung der Methode contains:
public boolean contains(Object o) {
Enumeration enum = this.elements();
while (enum.hasMoreElements()) {
Object elem = enum.nextElement();
if (elem.equals(o))
return true;
}
return false;
}
294
Implementierung der Methoden (7)
10. Um die Elemente auszugeben, verwenden wir ebenfalls wieder einenDurchlauf. Dabei gehen wir erneut davon aus, dass die Klasse desreferenzierten Objektes (wie die Object-Klasse) eine MethodetoString bereitstellt. Prinzipiell gibt es hierfur verschiedene Alternativen. Eine offensichtliche Moglichkeit besteht darin, eine Methode
print(PrintStream ps) zu implementieren. In Java gibt es aber eine elegantere Variante: Es genugt eine MethodetoString() zu realisieren. Diese wird immer dann aufgerufen, wenn ein Set-Objekt als Argumenteiner print-Methode verwendet wird.
295
Die Methode toString()
public String toString(){
String s = "[";
Enumeration enum = this.elements();
if (enum.hasMoreElements())
s += enum.nextElement().toString();
while (enum.hasMoreElements())
s += ", " + enum.nextElement().toString();
return s + "]";
}
296
Die komplette Klasse Set
import java.io.*;import java.util.*;
class Set {public Set() {
this.theElements = new Vector();}
public boolean isEmpty() {return this.theElements.isEmpty();
}
public int size() {return this.theElements.size();
}
Enumeration elements() {return this.theElements.elements();
}
public boolean contains(Object o) {Enumeration enum = this.elements();while (enum.hasMoreElements()) {
Object elem = enum.nextElement();if (elem.equals(o))
return true;}return false;
}
public void addElement(Object o) {if (!this.contains(o))
this.theElements.addElement(o);}
public Set copy() {Set destSet = new Set();Enumeration enum = this.elements();while (enum.hasMoreElements())
destSet.addElement(enum.nextElement());return destSet;
}
public Set union(Set s) {Set unionSet = s.copy();Enumeration enum = this.elements();while (enum.hasMoreElements())
unionSet.addElement(enum.nextElement());return unionSet;
}
public Set intersection(Set s) {Set interSet = new Set();Enumeration enum = this.elements();while (enum.hasMoreElements()) {
Object elem = enum.nextElement();if (s.contains(elem))
interSet.addElement(elem);}return interSet;
}
void removeAllElements() {this.theElements.removeAllElements();
}
public String toString(){String s = "[";Enumeration enum = this.elements();if (enum.hasMoreElements())
s += enum.nextElement().toString();while (enum.hasMoreElements())
s += ", " + enum.nextElement().toString();return s + "]";
}
private Vector theElements;}
297
Unser Beispielprogramm (erneut)
class useSet {
public static void main(String [] args) {
Set s1 = new Set();
s1.addElement("A");
s1.addElement("B");
s1.addElement("C");
s1.addElement("A");
System.out.println(s1);
Set s2 = new Set();
s2.addElement("B");
s2.addElement("C");
s2.addElement("D");
s2.addElement("D");
System.out.println(s2);
System.out.println(s1.union(s2));
System.out.println(s1.intersection(s2));
}
}
298
Ausgabe des Beispielprogramms
java useSet
[A, B, C]
[B, C, D]
[B, C, D, A]
[B, C]
Process useSet finished
299
Zusammenfassung (1)
Die Wiederholung von Anweisungssequenzen durch Schleifen oderLoops ist eines der machtigsten Programmierkonstrukte. Mit Hilfe von Schleifen wie der while-Schleife konnen Sequenzen vonAnweisungen beliebig haufig wiederholt werden. Kollektionen sind Objekte, die es erlauben, Objektezusammenzufassen.Vector ist eine solche Kollektionsklasse, mit der Objekte beliebigerKlassen zusammengefasst werden konnen.
300
Zusammenfassung (2)
� Die einzelnen Objekte eines Vector-Objektes konnen mit Durchlaufenunter Verwendung eines Objektes der Klasse Enumeration prozessiertwerden.� Mit Hilfe der Klasse Vector konnen wir dann andereKollektionsklassen definieren (wie z.B. eine Set-Klasse).� Fur primitive Datentypen verwenden wir Wrapper-Klassen, um sie inKollektionen einzufugen.
301
Einfuhrung in die Informatik
Iterationen
Konstruktion, Anwendungen, Varianten
Wolfram Burgard
302
Motivation
� Im vorangegangenen Kapitel haben wir mit der while-Schleife eineForm von Wiederholungsanweisungen fur Iterationen kennengelernt.� In diesem Kapitel werden wir etwas systematischer beschreiben, wie manwhile-Schleifen formuliert.� Daruber hinaus werden wir in diesem Kapitel weitere Anwendungen derwhile-Schleife kennenlernen.� Schließlich werden wir ein alternatives Schleifen-Konstrukt betrachten.
303
Formulieren von Schleifen
Gegeben sei eine Situation, in der wir in vielen Methoden einer Klasseeine Potenz fur ganze Zahlen mit nicht-negativen, ganzzahligenExponenten berechnen mussen: � � Der Prototyp einer solchen Methode ist offensichtlich
private int power(int x, int y) Da uns an Grundrechenarten die Multiplikation zur Verfugung steht,konnen wir die Potenz durch wiederholte Multiplikationen realisieren.
304
Informelle Beschreibung des Verfahrens
� Zur Formulierung des Verfahrens betrachten wir zunachst, wie wir dieBerechnung von � � per Hand durchfuhren wurden:
� � ����� ��� � falls � � �� � � � � � �� � � � mal
sonst�� � � � � � � � �� � � � mal
� Daraus ergibt sich ein informelles Verfahren:
1. starte mit 1
2. multipliziere sie mit x
3. multipliziere das Ergebnis mit x
4. fuhre Schritt 3) solange aus, bis y Multiplikationen durchgefuhrtwurden.
305
Wahl und Definition der Variablen
! Der nachste Schritt ist, dass wir die Informationen bestimmen, die wirwahrend unserer informellen Prozedur benotigt haben.! Im einzelnen sind dies zwei Werte:
1. Zunachst benotigen wir das Ergebnis der zuletzt durchgefuhrtenMultiplikation.
2. Daruber hinaus mussen wir mitzahlen, wie viele Multiplikationenwir bereits ausgefuhrt haben.! Wenn wir fur das Ergebnis die Variable result und zum Zahlen die
Variable count verwendet, erhalten wir folgende Deklarationen:
int count, // Anzahl durchgefuhrter Multiplikationen
result; // result == 1*x*...*x count mal! Bisher sind die Variablen allerdings noch nicht initialisiert.
306
Das Skelett des Codes
Im nachsten Schritt formulieren wir ein Skelett des Codes einschließlich
1. des Methodenkopfes (Prototyp),
2. der Deklarationen und
3. dem Skelett der while-Schleife
private int power(int x, int y) {
int count, // Anzahl durchgefuhrter Multiplikationen
result; // result == 1*x*...*x count mal
...
while (condition)
body
...
}
307
Die Bedingung der while-Schleife (1)
" Um die Bedingung der while-Schleife zu formulieren, mussen wirwissen, was nach Beendigung der while-Schleife gelten soll." Dafur mussen wir wiederum festlegen, welchen Status unsere Variablennach Beendigung der while-Schleife haben sollen und welcheOperationen das Programm ausgefuhrt haben soll."In dem Fall der Potenzierung sind wir fertig, wenn wir y Multiplikationendurchgefuhrt haben, d.h. wenn der Wert von count mit dem von y
ubereinstimmt." Entsprechend dem Kommentar in der Deklaration, hat die Variableresult stets den Wert # $ % & ' ( ." Demnach hat result den Wert # ) , wenn count nach Beendigung derSchleife den Wert y hat.
308
Die Bedingung der while-Schleife (2)
* Ziel ist nun, dass count nach Beendigung der while-Schleife dengleichen Wert hat wie y.* Da wir mit count die Anzahl der durchgefuhrten Multiplikationen zahlen,mussen wir die Schleife solange wiederholen, bis count den gleichenWert wie y hat.
private int power(int x, int y){
int count, // Anzahl durchgefuhrter Multiplikationen
result; // result == 1*x*...*x count mal
...
while (count != y)
body
// count == y, result == x**y
return result;
}
309
Initialisierung (1)
+ Wenn das while-Statement erreicht wird, mussen die Variableninitialisiert sein, da andernfalls z.B. die Bedingung des while-Statementsnicht sinnvoll ausgewertet werden kann.+ In unserem Beispiel mussen wir die Variable count entsprechendinitialisieren.+ In unserer while-Schleife zahlt count die Anzahl durchgefuhrterMultiplikationen.+ Deswegen sollte count vor Eintritt in die while-Schleife den Wert 0haben.
310
Initialisierung (2)
, Unser Code muss daher folgendermaßen aussehen:
private int power(int x, int y){
int count, // Anzahl durchgefuhrter Multiplikationen
result; // result == 1*x*...*x count mal
...
count = 0;
while (count != y)
body
// count == y, result == x**y
return result;
}
311
Der Rumpf der Schleife (1)
Bei der Formulierung des Rumpfes der Schleife mussen wir auf zwei Dingeachten:
1. Zunachst mussen wir garantieren, dass die Schleife terminiertbzw. stoppt. Der Rumpf einer Schleife wird nicht ausgefuhrt, wenn dieBedingung im Schleifenkopf zu false ausgewertet wird.
Der Schleifenrumpf muss daher Anweisungen enthalten, die einenFortschritt im Hinblick auf die Terminierung realisieren, d.h. Code, derdafur sorgt, dass die Schleifenbedingung irgendwann den Wert falsehat.
Unsere while-Schleife soll terminieren, sobald count == y.
312
Der Rumpf der Schleife (2)
Da wir in jedem Schleifendurchlauf eine Multiplikation ausfuhren undcount die Anzahl notwendiger Multiplikationen zahlt, mussen wir in jederRunde count um 1 erhohen.
private int power(int x, int y){
int count, // Anzahl durchgefuhrter Multiplikationen
result; // result == 1*x*...*x count mal
...
count = 0;
while (count != y){
rest of body
count++;
}
// count == y, result == x**y
return result;
}
313
Der Rumpf der Schleife (3)
2. Daruber hinaus mussen wir Statements in den Rumpf einfugen, welchedie erforderlichen Berechnungen durchfuhren.
In unserem Beispiel zahlt count die Anzahl durchgefuhrterMultiplikationen.
Dementsprechend mussen wir in jedem Schleifendurchlauf auch eineMultiplikation ausfuhren, sodass result stets den Wert - . / 0 1 2 hat.
In unserem Beispiel wird das realisiert durch:
private int power(int x, int y){
...
while (count != y){
result *= x;
count++;
}
...
}314
Akkumulatoren
3 Die Variable result enthalt in unserem Programm stets das aktuelleZwischenergebnis 4 5 6 7 8 9 .3 In result speichern wir das aktuelle Teilprodukt.3 Daher nennen wir result einen Akkumulator.3 Akkumulatoren tauchen immer auf, wenn z.B. Summen oder Produkteuber eine Sequenz von Objekten berechnet werden soll.3 Beispiele sind Summe der Einkommen uber alle Mitarbeiter, Summe derLange aller Zeilen in einem Text etc.
315
Initialisierung (erneut)
: Wie bereits erwahnt, mussen alle Variablen korrekt initialisiert sein, bevorwir in die Schleife eintreten.: Von den beiden im Rumpf der Schleife vorkommenden Variablen habenwir count bereits korrekt initialisiert.: In unserem Beispiel soll result stets den Wert ; < = > ? @ haben.: Da count mit 0 initialisiert wird, mussen wir result den Wert A B ; Cgeben.
private int power(int x, int y){
int count, // Anzahl durchgefuhrter Multiplikationen
result; // result == 1*x*...*x count mal
result = 1;
count = 0;
...
return result;
}316
Die komplette Prozedur
static int power(int x, int y){
int count, // Anzahl durchgefuhrter Multiplikationen
result; // result == 1*x*...*x count mal
result = 1;
count = 0;
while (count != y) {
result *= x;
count++;
}
// count == y, result == x**y
return result;
}
317
Die einzelnen Schritte zur Formulierung einer Schleife
1. Aufschreiben einer informellen Prozedur
2. Bestimmen der Variablen
3. Das Skelett der Schleife
4. Die Bedingung der while-Schleife
5. Initialisierung der Variablen in der Bedingung
6. Erreichen der Terminierung
7. Vervollstandigen des Rumpfes mit notwendigen Statements
8. Initialisierung der anderen im Rumpf benotigten Variablen
318
Muster einer Schleife: Zahlen
D In der Praxis ist es haufig erforderlich, Objekte zu zahlen.D Typische Beispiele sind die Anzahl der weiblichen Studierenden, dieAnzahl derer, die 50% der Ubungsaufgaben gelost haben etc.D Um zu zahlen, mussen wir entweder durch eine Kollektion laufen oderObjekte einlesen.D Fur das Zahlen verwenden wir ublicherweise einen Zahler, d.h. eineVariable vom Typ int, die wir hier count nennen:
int count = 0; //count == Anzahl der entsprechenden Falle
...
while (...) {
...
if (Objekt erfullt bestimmte Bedingung)
count++
...
}319
Zahlen der weiblichen Studierenden,welche mehr als 50 Punkte erreicht haben
1. Es gibt eine Klasse Student, mit zwei Methoden isFemale und getPoints.
2. Die Methode isFemale liefert genau dann true, wenn das entsprechendeObjekt eine Studentin reprasentiert.
3. Die Methode getPoints liefert die erreichte Punktzahl.
4. Alle Studierenden sind in einem Kollektionselement studentSetzusammengefasst.
Enumeration e = studentSet.elements();
Student stud;
int count = 0;
while (e.hasMoreElements()){
stud = (Student) e.nextElement();
if (stud.isFemale() && stud.getPoints() > 50)
count++;
}320
Muster einer Schleife: Finden des Maximums
E Das Finden des maximalen Elements ist ebenfalls ein typisches Problemin einer Vielzahl von Anwendungen.E Eine Beispielanwendung sind Wetterdaten: Welcher Tag war der heißesteim Jahrhundert?E Da man das Auffinden des Maximums ublicherweise auf eine große Zahlvon Objekten anwenden will, konnen wir davon ausgehen, dass dieObjekte entweder eingelesen werden oder in einer Kollektionsklassezusammengefasst sind.
321
Typisches Vorgehen zum Finden des Maximums
someType extreme; // extreme == Referenz auf ein Objekt,
// welches den maximalen Wert unter allen
// bisher betrachteten Elementen hat.
// extreme == null, wenn es kein Objekt
// gibt.
extreme = null;
while (...) {
...
if (extreme == null || aktuelles Objekt ist großer
als alle bisher betrachteten)
extreme = aktuelles Objekt;
}
322
Anwendungsbeispiel: Langste Zeile in einem Text
Enumeration e = v.elements();
String s;
String longest = null;
while (e.hasMoreElements()) {
s = (String) e.nextElement();
if (longest == null || s.length() > longest.length())
longest = s;
}
System.out.println("Longest String is " + longest);
323
Maximaler Wert fur primitive Datentypen
F Die Variante fur primitive Datentypen ist sehr ahnlich zu der fur Objekte.F Allerdings enthalten primitive Datentypen wie int oder float nicht denWert null.F Deswegen mussen wir eine Variable vom Typ boolean verwenden, umzu reprasentieren, dass noch kein Wert betrachtet wurde.
324
Muster einer entsprechenden Schleife
someType extreme; // extreme == Der bisher gefundene
// Extremwert unter allen
// bisher betrachteten Elementen.
// Ist foundExtreme == false, ist der Wert
// bedeutungslos.
boolean foundExtreme;
foundExtreme = false;
extreme = beliebiger Wert;
while (...) {
...
if (!foundExtreme || aktueller Wert ist großer
als alle bisher betrachteten) {
extreme = aktueller Wert;
foundExtreme = true;
}
}325
Beispiel: Kleinste, von der Tastatur eingelesene Zahl
import java.io.*;
class ProgramSmallest {public static void main(String arg[]) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
boolean foundExtreme = false;int smallest = 0;String line;
while ((line = br.readLine()) != null) {int x = Integer.parseInt(line);if (!foundExtreme || x < smallest){
smallest = x;foundExtreme = true;
}}if (foundExtreme)
System.out.println("Smallest number is " + smallest);}
}
326
Die for-Schleife
G Speziell fur Situationen, in denen die Anzahl der Durchlaufe von Beginnan feststeht, stellt Java mit der for-Schleife eine Alternative zurwhile-Schleife zur Verfugung.G Die allgemeine Form der for-Schleife ist:
for (Initialisierungsanweisung; Bedingung; Inkrementierung)
RumpfG Sie ist aquivalent zu
Initialisierungsanweisung
while (Bedingung) {
Rumpf
Inkrementierung
}
327
Potenzierung mit der for-Anweisung
H Bei der Potenzierung mussten wir genau y Multiplikationen durchfuhren.H Die Anzahl durchgefuhrter Multiplikationen wurde in der Variablen countgespeichert.
static int power(int x, int y){
int count, result = 1;
for (count = 0; count < y; count++)
result *= x;
return result;
}
328
Komplexere for-Anweisungen
I Die Initialisierungs- und die Inkrementierungsanweisung konnen ausmehreren, durch Kommata getrennten Anweisungen bestehen.I Betrachten wir die analoge while-Schleife, so werden dieInitialisierungsanweisungen vor dem ersten Schleifendurchlaufausgefuhrt.I Auf der anderen Seite werden die Inkrementierungsanweisungen amEnde jedes Durchlaufs ausgefuhrt.I Damit konnen wir auch folgende for-Anweisung zur Berechnung von J Kverwenden:
for (count = 0, result = 1; count < y; result*=x, count++);I Solche kompakten Formen der for-Anweisung sind ublicherweiseschwerer verstandlich und daher fur die Praxis nicht zu empfehlen.
329
Realisierung einer Endlosschleife mit der for-Anweisung
L Viele Systeme sind eigentlich dafur gedacht, permanent zu laufen.L Typische Beispiele sind Web-Server oder Betriebssysteme.L Da es fur solche Systeme eher die Ausnahme ist, dass sie terminieren(es soll z.B. nur dann geschehen, wenn der Rechner ausgeschaltetwerden soll), laufen sie ublicherweise in einer Endlosschleife.L Endlosschleifen implementiert man dadurch, dass man denInitialisierungs-, den Bedingungs- und den Inkrementierungsteil leer lasst,wobei das Anhalten dann beispielsweise durch ein return realisiertwird.
330
Typisches Muster einer for-Endlosschleife
for (;;) {
...
if (...) {
...
return;
}
...
}
331
Bedingte Auswertung logischer Ausdrucke
M Wie bereits erwahnt, wertet Java Ausdrucke von links nach rechts aus.M Im Fall Boolescher Ausdrucke wertet Java jedoch nicht immer alleTeilausdrucke aus.MBetrachten Sie folgenden Booleschen Ausdruck:
hours >= 40 && hours < 60M Hat hours den Wert 30, gibt es keinen Grund, den zweiten Vergleichhours < 60 noch auszuwerten, weil der Ausdruck in jedem Fall falsesein wird.M Java bricht daher die Auswertung ab, sofern das Ergebnis bereitsfeststeht.M Die bedingte Auswertung im Fall von || ist analog.
332
Ausnutzung der Bedingten Anweisung bei Tests
NDie Tatsache, dass Java logische Ausdrucke nur bedingt auswertet,haben wir bei folgendem Test ausgenutzt:
if (longest == null || s.length() > longest.length())N Falls auch im Fall longest == null beide Ausdrucke ausgewertetwurden, so wurde das zu einem Fehler fuhren, weil wir dann einernull-Referenz die Nachricht length schicken wurden.
333
Zusammenfassung
OUm eine while-Schleife zu formulieren, geht man in verschiedenenSchritten vor.O Fur prototypische Problemstellungen, gibt es Muster einer Realisierung.O Hierzu gehoren das Zahlen, die Akkumulation oder das Finden einesMaximums.O Die for-Anweisung stellt eine Alternative zur while-Schleife dar.O Logische Ausdrucke werden bedingt ausgewertet.
334
Terminologie
Akkumulator: Variable, die eine Teilsumme, ein Teilprodukt oder einTeilergebnis einer anderen Operation als + und * enthalt.
Zahler: Variable, die zum Zahlen verwendet wird.
Iteration: Wiederholung einer Folge von Statements bis eine bestimmteBedingung erfullt ist.
Bedingte Auswertung: Logische Ausdrucke werden nicht weiterausgewertet, sobald ihr endgultiger Wert bereits feststeht.
335
Einfuhrung in die Informatik
Organizing Objects
Indizierungen, Suchen, Aufwandsanalyse, Sortieren, Arrays
Wolfram Burgard
336
Motivation
PBisher haben wir Objekte in Kollektionen zusammengefasst undEnumerations verwendet, um Durchlaufe zu realisieren.P Enumerations lassen jedoch offen, wie die Objekte in der Kollektionangeordnet sind und in welcher Reihenfolge sie ausgegeben werden.P In diesem Kapitel werden wir Moglichkeiten kennenlernen, Ordnungenauf den Objekten auszunutzen.P Daruber werden wir Aufwandsanalysen kennenlernen, die es unsz.B. erlauben, die von Programmen benotigte Rechenzeit zucharakterisieren.PSchließlich werden wir auch diskutieren, wie man Objekte sortierenkann.
337
Indizierung
Q Ein Vector-Objekt stellt nicht nur eine Kollektion von Objekten dar.Q Die in einem Vector-Objekt abgelegten Objekte sind entsprechend ihrerEinfugung angeordnet: Das erste Objekt befindet sich an Position 0. Dieweiteren folgen an den Positionen 1, 2, . . .Q Die Nummerierung der Positionen von Objekten heißt Indizierung oderIndexing.Q Ein Index ist die Position eines Objektes in einer Kollektion.
position 0position 1position 2
Vector
Second element added
Third element added
First element addedrefers to
position 3
338
Die Methode elementAt fur den Zugriffauf ein Objekt an einer Position
R Um auf die an einer bestimmten Position gespeicherte Referenz einesVector-Objektes zu erhalten, verwenden wir die Methode elementAt:
Vector v = new Vector();
v.addElement("First line");
v.addElement("Second line");
v.addElement("Third line");
String s = (String) v.elementAt(1);
System.out.println(s);
Dieses Programm druckt den Text Second line.
339
Situation nach der vierten Anweisung
position 0position 1position 2
Vector
s
"First line"
"Second line"
"Third line"
refers
to
refers to
v
position 3
S Die Position des ersten Objektes ist 0.S Entsprechend ist die Position des letzten Objektes v.size()-1.
340
Grenzen von Enumerations
T Enumerations stellen eine komfortable Moglichkeit dar, Durchlaufe durchKollektionen zu realisieren.T Allerdings haben wir keinen Einfluss darauf, in welcher Reihenfolge dieKollektionen durchlaufen werden.T Ein typisches Beispiel ist das Invertieren eines Textes, bei dem dieeingelesenen Zeilen in umgekehrter Reihenfolge ausgegeben werdensollen.T In diesem Fall ist die Reihenfolge, in der die Objekte durchlaufen werdenrelevant.T Ein weiteres Beispiel ist das Sortieren von Objekten.
341
Anwendung: Eingelesene Zeilen in umgekehrterReihenfolge ausgeben
Prinzip:
1. Einlesen der Zeilen in ein Vector-Objekt.
2. Ausgeben der Zeilen”von hinten nach vorne“.
Einlesen der Zeilen in ein Vector-Objekt:
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
Vector v = new Vector();
String line;
line = br.readLine();
while (line != null) {
v.addElement(line);
line = br.readLine();
}342
Ausgeben in umgekehrter Reihenfolge
1. Wir starten mit dem letzten Objekt, welches sich an Position k ==
v.size()-1 befindet.
2. Wir geben das Objekt an Position k aus und gehen zu dem Objekt an derPosition davor (k = k-1).
3. Schritt 2) wiederholen wir solange, bis wir das erste Objekt ausgegebenhaben. In diesem Fall muss gelten: k == -1.
Dies ergibt folgenden Code:
int k = v.size()-1;
while (k != -1) {
System.out.println(v.elementAt(k));
--k;
}
343
Das komplette Programm
import java.io.*;import java.util.*;
class ReverseLines {public static void main(String[] a) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Vector v = new Vector();String line;line = br.readLine();while (line != null) {
v.addElement(line);line = br.readLine();
}int k = v.size()-1;while (k != -1) {
System.out.println(v.elementAt(k));--k;
}}
}
344
Enumerations versus Indizierung
U Ahnlich wie mit einem Enumeration-Objekt konnen wir auch mit derMethode elementAt eine Aufzahlung realisieren.U Dabei haben wir sogar noch die Flexibilitat, die Reihenfolge, in der dieElemente prozessiert werden, festzulegen.U Was sind die Vorteile einer Enumeration?
– In vielen Fallen ist die Reihenfolge, in der die Objekte prozessiertwerden, irrelevant.
– Die Verwendung einer Enumeration ist deutlich wenigerfehleranfallig als die Verwendung von elementAt.
– Ein typischer Fehler bei der Verwendung von Indizes ist der Zugriffauf nicht existierende Objekte. Weiter passiert es auch haufig, dassnicht auf alle Objekte zugegriffen wird. Ursache sind in der Regelfalsche Abbruchbedingungen.
345
Suche in Vector-Objekten
V Das Suchen in Kollektionen nach Objekten mit bestimmten Eigenschaftenist eine der typischen Aufgaben von Programmen.V In diesem Kapitel befassen wir uns mit der Suche von Objekten, die miteinem gegebenen Objekt ubereinstimmen.V Dabei soll die Suchprozedur den Index des gefundenen Objektes ineinem Vector-Objektes zuruckgeben, sofern es in dem Vector
enthalten ist und -1 sonst.V Wir unterscheiden zwei Falle:
1. Die Objekte sind im Vector-Objekt ungeordnet angeordnet.
2. Die Objekte sind entsprechend ihrer Ordnung angeordnet.
346
Suche in ungeordneten Vector-Objekten
W Wenn das Vector-Objekt ungeordnet ist, mussen wir (ebenso wie beider Methode contains der Klasse Set) den kompletten Vector
durchlaufen.WWir starten mit dem Objekt an Position 0 und brechen ab, sobald wir dasEnde des Vectors erreicht haben oder das Objekt mit dem gesuchtenubereinstimmt.WDie Bedingung der while-Schleife ist somit
while (!(k==v.size() || o.equals(v.elementAt(k))))W Hierbei nutzen wir die bedingte Auswertung logischer Ausdrucke aus.
347
Die resultierende while-Schleife
public int linearSearch(Vector v, Object o) {
int k = 0;
while (!(k==v.size() || o.equals(v.elementAt(k))))
k++;
// k==v.size || o.equals(v.elementAt(k))
if (k==v.size())
return -1;
else
return k;
}
348
Aufwandsanalysen
In der Informatik interessiert man sich nicht nur fur die Korrektheit, sondernauch um die Kosten von Verfahren.
Hierbei gibt es prinzipiell verschiedene Kriterien:
1. Wie hoch ist der Programmieraufwand?
2. Wie hoch ist der erforderliche Kenntnisstand eines Programmierers?
3. Wie lange rechnet das Programm?
4. Wieviel Speicherplatz benotigt das Verfahren?
Wir werden uns im folgenden auf die Rechenzeit und denSpeicherplatzbedarf beschranken.
349
Asymptotische Komplexitat
Wir beschranken uns auf die so genannte asymptotische Analyse desAufwands (oder der Komplexitat).
Wir bestimmen die Rechenzeit und den Platzbedarf als eine Funktion derGroße der Eingabe (Anzahl der eingelesenen/verarbeiteten Elemente).
Dabei werden wir untersuchen, ob die Rechenzeitkurve logarithmisch, linear,quadratisch etc. ist, und dabei konstante Faktoren außer Acht lassen.
Die konstanten Faktoren hangen in der Regel von dem gegebenen Rechnerund der verwendeten Programmiersprache ab, d.h. bei Verwendung eineranderen Sprache oder eines anderen Rechners bleibt die Kurvenformerhalten.
350
Zeitkomplexitat
Um die Zeitkomplexitat eines Verfahrens zu ermitteln, betrachten wir stets dieAnzahl der durchzufuhrenden OperationenX im schlechtesten Fall (Worst Case),X im besten Fall (Best Case) undX im Durchschnitt (Average Case).
351
Komplexitat der Suche in nicht geordnetenVector-Objekten (1)
Wir nehmen an, dass die einzelnen Operationen in unserer Suchprozedurjeweils konstante Zeit benotigen.
Dies schließt einY Z [ viele Operationen fur die Initialisierung und das Beenden der Prozedur,Y Z \ viele Operationen fur den Test der Bedingung undY Z ] viele Operationen in jedem Schleifendurchlauf.
352
Der Worst Case und der Best Case
Worst Case: Die Schleife wird hochstens n == v.size() mal durchlaufen.
Somit benotigt die Ausfuhrung von linearSearch hochstens^ _ ` a b ^ c ^ d eOperationen.
Best Case: Ist das erste Element bereits das gesuchte oder der Vektor leer,so benotigen wir^ _ ^ c
Operationen, d.h. die Ausfuhrungszeit istunabhangig von`.
353
Der Average Case
Nehmen wir an, dass nach jedem Objekt gleich haufig gesucht wird.
Bei f Aufrufen wird somit nach jedem der f Objekte im Vector-Objekt genaueinmal gesucht.
Dies ergibt die folgende, durchschnittliche Anzahl von Schleifendurchlaufen:g h i j k k k ffml n g o g p qr g h f l n f i qf l j l ng o g s q
r g h f ij l n g o g p qDa diese Funktion linear in f ist, sagen wir, dass die durchschnittliche,asymptotische Komplexitat von linearSearch linear in der Anzahl derElemente ist.
354
Die t -Notation fur asymptotische Komplexitat
Anstatt die Komplexitat eines Verfahrens exakt zu berechnen, wollen wir imFolgenden eher die Ordnung der Komplexitat bestimmen.
Im Prinzip bestimmen wir das Wachstum der Funktion, welches dieKomplexitat des Verfahrens beschreibt, und abstrahieren von moglichenKonstanten.
Definition: Sei u v w x y z{ . Die Ordnung von u ist die Menge| } u } ~ � � � � � v w x y z{ � � � y z � ~ { � w � ~ � ~ { v � } ~ � � � � u } ~ � �
Informell: Ist� � | } u � , so wachst
�(bis auf konstante Faktoren) hochstens
so wie u .355
Beispiele
1. � � � � � � � � � � ist in � � � � :� � � � � � � � � �� � � � � �� � � �
Wir wahlen � � � � und � � � � .2. � � � � � � � � � � � � ist in � � � � � , denn fur � � � � und � � � � gilt:
� � � � � � � � � � � � � � � � � � � � � � � �Somit wahlen wir � � � � ;
3. Entsprechend gilt, dass ein Polynom vom Grad � in � � � � � liegt.
356
Typische Wachstumskurven
Klasse Bezeichnung ¡ ¢ £konstant
(log ¤ ) logarithmisch ¡ ¤ £ linear ¡ ¤ log ¤ £ ¤ log ¤ ¡ ¤ ¥ £ quadratisch ¡ ¤ ¦ £ kubisch ¡ ¤ § £ polynomiell ¡ ¨ © £exponentiell
357
Verlaufe von Kurven (1)
-1
0
1
2
3
4
5
6
7
8
0 1 2
1x
x * log(x)x*x
x*x*x2**x
358
Verlaufe von Kurven (2)
-500
0
500
1000
1500
2000
2500
3000
3500
4000
4500
0 2 4 6 8 10 12
1x
x * log(x)x*x
x*x*x2**x
359
Typische Wachstumskurven (3)
-10
0
10
20
30
40
50
0 5 10 15 20 25 30 35 40 45 50
1x
log(x)sqrt(x)
-10
0
10
20
30
40
50
0 5 10 15 20 25 30 35 40 45 50
1x
log(x)sqrt(x)
360
Methoden der Klasse Vector und ihre Effizienz
Bisher haben wir verschiedene Methoden fur Vektoren kennengelernt:
size() Liefert Anzahl der Elemente in einemVector-Objekt.
elementAt(int n) Liefert eine Referenz auf das an Po-sition n gespeicherte Objekt.
insertElementAt(Object o, int n) Fugt die Referenz auf Objekt o an Po-sition n ein. Vorher wird die Positionder Objekte an Position ª « jeweilsum eins erhoht.
removeElementAt(int n) Loscht das Objekt an Position n.
361
Aufwand von size
¬ Die Methode size kann sehr einfach umgesetzt werden, wenn man dieAnzahl der Elemente in einem Vector in einer Instanzvariablen ablegt.¬ Dieser Zahler muss dann naturlich bei jeder Einfuge- oderLoschoperation inkrementiert oder dekrementiert werden.¬ Wenn man so vorgeht und die Anzahl der Elemente beispielsweise ineiner Variablen numberOfElements ablegt, so kann die Methode size
folgendermaßen realisiert werden:
public int size(){
return this.numberOfElements;
}¬ Da diese Funktion konstante Zeit benotigt, ist die Komplexitat von size
in ® ¯ ° .362
Komplexitat von elementAt
± Ublicherweise werden Vektoren durch so genannte Felder realisiert.± Ein Feld wiederum lasst sich vergleichen mit einer Tabelle oder einerReihe von durchnummerierten Platzen, in der die Objekte hintereinanderangeordnet sind.± Benotigt man nun fur eine eine Referenzvariable ² Bytes und kennt mandie Stelle ³ im Programmspeicher, an der sich das erste Objektes derTabelle befindet, so kann man die Position des ´ -ten Objektes mit derFormel ³ ² µ ´ in ¶ · ¸ ¹ (d.h. in konstanter Zeit) berechnen.
m
k k
m+2*km+k
k k
m+3*k m+i*k... ...
object 0 object 1 object 2 object i
363
Die Kosten fur das Einfugen
º Wenn wir ein Objekt an einer Position 0 einfugen wollen, dann mussenwir alle Objekte in dem Vector um jeweils eine Position nach hintenverschieben.º Die Einfugeoperation insertElementAt("Harpo",0) geht demnachfolgendermaßen vor:
0
1
2
3
4
Vorher
Groucho
Chico Groucho
ChicoZeppo
Verschieben
0
1
2
3
4
Zeppo
Harpo
Nachher
º Da im Durchschnitt großenordnungsmaßig » ¼ ½ Objekte verschobenwerden mussen, ist die durchschnittliche Laufzeit ¾ ¿ » À .
364
Das Entfernen eines Objektes
Á Beim Entfernen eines Objektes aus einem Vector-Objekt geht Javaumgekehrt vor.Á Wenn wir ein Objekt mit removeElementAt(i) an der Position i
loschen, rucken alle Objekte an den Positionen i+1 bis v.size()-1jeweils um einen Platz nach vorne.Á D.h., wir mussen v.size()-i-1 Verschiebungen durchfuhren.Á Ist n == v.size() so ist die durchschnittliche Laufzeit vonremoveElementAt ebenfalls in Â Ã Ä Å .
Folgerung: Werden in einem Vector-Objekt viele Vertauschungen vonObjekten durchgefuhrt, sollte man ggf. alternative Methoden mit geringeremAufwand verwenden.
365
Beispiel: Invertieren der Reihenfolge in einem Vektor
Æ Um die Reihenfolge aller Objekte in einem Vector-Objekt umzudrehen,mussen wir den Vector von 0 bis v.size()/2 durchlaufen und jeweilsdie Objekte an den Position i und v.size()-i-1 vertauschen.Æ Verwenden wir fur die Vertauschung die Methoden removeElementAtund insertElementAt, so benotigen wir folgenden Code:
Integer j1, j2;
j1 = (Integer) v.elementAt(i);
j2 = (Integer) v.elementAt(v.size()-i-1);
v.removeElementAt(i);
v.insertElementAt(j2, i);
v.insertElementAt(j1,v.size()-i-1);
v.removeElementAt(v.size()-i-1);
366
Invertieren mit removeElementAt und insertElementAt
import java.util.*;
class ReverseInsert {public static void main(String [] args) {
Vector v = new Vector();int i;for (i = 0; i < 40000; i++)
v.addElement(new Integer(i));
for (i = 0; i < v.size() / 2; i++){Integer j1, j2;j1 = (Integer) v.elementAt(i);j2 = (Integer) v.elementAt(v.size()-i-1);v.removeElementAt(i);v.insertElementAt(j2, i);v.insertElementAt(j1,v.size()-i-1);v.removeElementAt(v.size()-i-1);
}}
}
367
Aufwandsabschatzung fur ReverseInsert
Sei n == v.size(). Falls Ç È É und Ç gerade, fuhren wir fur Ê Ë É Ì Í Í Í Ç Î Ïjeweils vier Operationen aus. Dabei gilt:Ð removeElementAt(i) benotigt Ç Ê Ñ Verschiebungen (danach
enthalt v noch Ç Ñ Objekte).Ð insertElementAt(j2, i) erfordert Ç Ê Ñ Verschiebungen.Ð insertElementAt(j1, v.size()-i-1) erfordert Ê ÑVerschiebungen.ÐremoveElementAt(v.size()-i-1) benotigt Ê Verschiebungen.
Damit erhalten wir fur jedes Ê Ë É Ì Í Í Í Ì Ç Î ÏÒ Ç Ê Ñ Ó Ò Ç Ê Ñ Ó Ò Ê Ñ Ó Ê Ë Ï Ç Ñnotwendige Verschiebungen.
Bei Ç Î Ï Wiederholungen ergibt dasÒ Ç Ô Õ Ô Ó Ö × Ò Ç Ô Ó Verschiebungen.
368
Herleitung
removeElementAt(i): Ø Ù Ú Verschiebungen.
insertElementAt(j2, i): Ø Ù Ú Verschiebungen.
369
Eine effizientere Variante auf der Basis von setElementAt
Anstatt die Objekte aus dem Vektor zu entfernen und wieder neu einzufugen,konnen wir auch einfach deren Inhalte vertauschen.
Hierzu verwenden wir die folgende Methode swap, welche in einemVector-Objekt die Inhalte der Objekte an den Positionen i und j vertauscht.
static void swap(Vector v, int i, int j){
Object o = v.elementAt(i);
v.setElementAt(v.elementAt(j), i);
v.setElementAt(o, j);
}
370
Wirkung der Methode swap
i
j
Bill
Mary
o
o = v.elementAt(i)
i
j
Bill
Mary
o
v.setElementAt(v.elementAt(j), i)
i
j
Bill
Mary
o
v.setElementAt(o, j)
371
Das komplette Beispielprogramm
import java.util.*;
class ReverseSwap {static void swap(Vector v, int i, int j){
Object o = v.elementAt(i);v.setElementAt(v.elementAt(j), i);v.setElementAt(o, j);
}
public static void main(String [] args) {Vector v = new Vector();int i;for (i = 0; i < 40000; i++)
v.addElement(new Integer(i));
for (i = 0; i < v.size() / 2; i++)swap(v, i, v.size()-i-1);
// System.out.println(v.toString());}
}
372
Aufwandsabschatzung fur ReverseSwap
Nehmen wir erneut an, dass n==v.size() mit Û Ü Ý und Û gerade.
Pro Runde fuhren wir jetzt eine Vertauschung der Werte aus. Dies erfordertdrei Wertzuweisungen.
Damit erhalten wir einen Aufwand von Þß Û à á â Û ã .Schlussfolgerung: Die Variante auf der Basis von setElementAt istdeutlich effizienter als die Variante, die removeElementAt undinsertElementAt verwendet.
373
Rechenzeit in Abhangigkeit von der Anzahl der Elemente
0
5
10
15
20
0 10000 20000 30000
Rec
henz
eit [
s]
Anzahl der Elemente
ReverseInsertReverseSwap
374
Effizientere Suche fur sortierte Vector-Objekte
Wenn Vektoren sortiert sind, kann man schneller als mit linearSearchnach einem Objekt suchen.
Beispiel: Sie sollen eine ganze Zahl ä zwischen 0 und 999 erraten, die IhrGegenuber sich ausgedacht hat. Sie durfen Ihrem Gegenuber nur Fragenstellen, die er mit true oder false beantworten kann.
Eine schnelle Methode, die Zahl zu erraten, ist das wiederholte Halbieren desSuchintervalls.
375
Anwendungsbeispiel
Wir suchen eine Zahl in å æ ç è æ æ æ å . Der Gegenuber hat 533 gewahlt.é Ist ê ë ë ì í í ? Antwort: falseé Ist ê î ì í í ? Antwort: true ï Zahl ist in å ð æ æ ç è æ æ æ å .é Ist ê ë ñ ì í ? Antwort: falseé Ist ê î ñ ì í ? Antwort: false ï Zahl ist in å ð æ æ ç ñ ì í å .é . . .
376
Informelle Prozedur
Zur Suche eines String-Objekts s in sortierten Vector-Objekten gehen wiranalog vor.ò
Wir verwenden zwei Positionen left und right, die das Suchintervallcharakterisieren.òIn jedem Schritt betrachten wir das Element in der Mitte von left undright.òLiegt das Element in der Mitte alphabetisch vor s, wird die Mitte desIntervalls plus 1 die neue linke Grenze left. Ist es großer als s, wirdright auf die Mitte des Intervalls gesetzt.ò Ist das Element in der Mitte identisch mit s, ist die Mitte das Ergebnis.ò Ist irgendwann das Suchintervall leer, so ist s nicht in dem Vektorenthalten.
377
Prinzip der Binarsuche (1)
ó Angenommen unser Suchstring s ist "Wolfram".ó Weiter nehmen wir an, left und right hatten die folgenden Werte.
...
Van Dyke Wolsey
left
...
right
378
Prinzip der Binarsuche (2)
ô Wir vergleichen "Wolfram" mit dem Element in der Mitte zwischenleft und right.ô Dort finden wir das Element "Webster".
...
Van Dyke Wolsey
Webster
left
middle
...
right
379
Prinzip der Binarsuche (3)
õ Entsprechend der lexikographischen Ordnung ist "Webster" kleiner als"Wolfram".õ Daher wird die Position rechts von "Webster" zur neuen linken Grenzeleft.
... ...
WolseyWebster
left right
380
Prinzip der Binarsuche (4)
ö Angenommen unser Suchstring ware "Wallace".ö Da "Webster" großer als "Wallace" ist, erhalten wir folgendes, neuesSuchintervall.
...
Van Dyke
Webster
left right
...
381
Benotigte Variablen
÷ Um das aktuelle Suchintervall zu beschreiben, benotigen wir zweiInteger-Variablen:
int left, right;÷ Zusatzlich zu den Variablen selbst mussen wir die Bedeutung dieserVariablen genau festlegen.÷ Im Folgenden gehen wir davon aus, dass left die Position des erstenElementes im Suchintervall enthalt.÷ Im Gegensatz dazu ist right die Position des ersten Elementesrechts vom Suchintervall.
382
Das Suchintervall
øleft ist der Index des kleinsten Element im Suchintervall.øright ist der Index des ersten Elementes rechts vom Suchintervall.ø Die Indizes im Suchintervall gehen daher von left bis right-1.øWir mussen solange suchen, wie noch Elemente im Suchintervall sind,d.h. solange left < right.
...
Van Dyke Wolsey
left
...
right
383
Die Bedingung der while-Schleife
ù Wir suchen solange, wie das Intervall noch wenigstens ein Elemententhalt. Das Suchintervall ist leer, falls
left >= rightù Die Bedingung der while-Schleife hat somit die folgende Form:
while (left < right)ù Nach Beendigung der while-Schleife ist das Suchintervall leer:
left right
384
Initialisierung
ú Unsere Suchmethode soll ein gegebenes Objekt s in einemVector-Objekt finden.ú Demnach muss das Suchintervall anfangs den gesamten Vektorumfassen.ú Anfangs muss left daher den Wert 0 und right den Wert v.size()haben.ú Die Initialisierungsanweisung fur unsere Bedingung in derwhile-Schleife ist demnach:
left = 0;
right = v.size();
385
Der Rumpf der Schleife (1)
Im Rumpf der Schleife mussen wir drei Falle unterscheiden:
1. Das Objekt in der Mitte ist kleiner als das gesuchte Objekt,
2. das Objekt in der Mitte ist großer als das gesuchte Objekt oder
3. das Objekt in der Mitte stimmt mit dem gesuchten Objekt uberein.
386
Die String-Methode compareTo
û Um String-Objekte miteinander zu vergleichen stellt Java die MethodecompareTo zur Verfugung, die als Ergebnis einen Wert vom Typ int
liefert.û Dabei liefert s1.compareTo(s) den Wert 0, wenn s1 und s
ubereinstimmen, einen Wert kleiner als 0 wenn das Empfangerobjekt s1lexikographisch kleiner ist als das Argument s, und einen Wert großerals 0 sonst.û Die lexikographische Ordnung entspricht bei String-Objekten deralphabetischen Reihenfolge der Zeichenketten.
"hugo".compareTo("hugo") --> 0
"paul".compareTo("paula") --> <0
"paul".compareTo("hugo") --> >0
387
Der Rumpf der Schleife (2)
Sei s1 das String-Objekt in der Mitte mid des Suchintervalls. Entsprechendunserer Festlegung von left und right muss gelten:
1. Ist s1.compareTo(s) < 0, so ist left = mid+1.
2. Ist s1.compareTo(s) > 0, so ist right = mid.
3. Ist s1.compareTo(s) == 0, so ist mid die gesuchte Position.
Dies entspricht:
if (s1.compareTo(s) < 0)
left = mid+1;
else if (s1.compareTo(s) > 0)
right = mid;
else
return mid;
388
Der Rumpf der Schleife (3)
Es fehlen noch die Anweisungen zur Initialisierung der Variablen mid und s1
im Schleifenrumpf.
1. Die Variable mid muss auf die Mitte des Intervalls gesetzt werden.
2. Hierfur wahlen wir die Position (left+(right-1))/2.
3. Das Objekt in der Mitte erhalten wir dann mit der Methode elementAt.
int mid = (left + (right-1)) / 2;
String s1 = (String) v.elementAt(mid);
389
Wenn das gesuchte Objekt nicht gefunden wird . . .
ü Ist das gesuchte Objekt nicht in dem Vektor enthalten, so mussen wir -1als Return-Wert zuruckgeben.ü In unserem Programm ist das Element nicht enthalten, wenn die Schleifebeendet ist, d.h. wenn left >= right.ü Wir fuhren daher nach Beendigung der Schleife die Anweisung
return -1;
aus.
390
Das komplette Programm
import java.util.*;
class BinarySearch {static int binarySearch(Vector v, String s){
int left, right;
left = 0;right = v.size();
while (left < right){int mid = (left + (right-1)) / 2;String s1 = (String) v.elementAt(mid);if (s1.compareTo(s) < 0)
left = mid+1;else if (s1.compareTo(s) > 0)
right = mid;else
return mid;}return -1;
}}
391
Ein Anwendungsbeispiel
import java.io.*;import java.util.*;
class UseBinarySearch {public static void main(String [] args) {
Vector v = new Vector();
v.addElement("hugo");v.addElement("paula");v.addElement("peter");
for (int i = 0; i < v.size(); i++)System.out.println(v.elementAt(i) + " is at position "
+ BinarySearch.binarySearch(v, (String) v.elementAt(i)));System.out.println("wolfram is at position "
+ BinarySearch.binarySearch(v,"wolfram"));}
}
392
Ausgabe des Anwendungsprogramms
Die Ausgabe dieses Programms ist:
java UseBinarySearch
hugo is at position 0
paula is at position 1
peter is at position 2
wolfram is at position -1
Process UseBinarySearch finished
393
Aufwandsabschatzung fur BinarySearch
ý Im schlimmsten Fall bricht die Suche ab, nachdem das Suchintervall dieGroße 1 hat.ý In jeder Runde halbieren wir das Suchintervall.ý Starten wir mit 4000 Elementen, so ergeben sich ungefahr folgende 12Intervallgroßen:þ ÿ ÿ ÿ � � ÿ ÿ ÿ � � ÿ ÿ ÿ � � ÿ ÿ � � � ÿ � � � � � � � � � � � � � � � � � � �ý Da wir ein Intervall mit n Elementen großenordnungsmaßig � � � � mal
halbieren konnen, erhalten wir eine Komplexitat von � � � � � � .ý BinarySearch benotigt demnach im schlechtesten Fall nurlogarithmisch viele Schritte um das gesuchte Objekt zu finden.
394
Sortieren
� Unsere Suchprozedur BinarySearch fur die binare Suche erforderte,dass die Elemente im Vektor sortiert sind.� Im Folgenden wollen wir nun betrachten, wie man die Elemente einesVektors sortieren kann.� Hierbei bezeichnen wir einen Vektor als gemaß einer Ordnung � sortiert,falls er folgende Eigenschaft hat:
Fur je zwei Zahlen � und � mit � � � � � � � � � � � � � � gilt v.elementAt(i)ist unter � kleiner oder gleich v.elementAt(j).� Wir betrachten hier demnach aufsteigend sortierte Vektoren.� Fur das Sortieren gibt es zahlreiche Methoden. Wir werden hier das sehreinfache Sortieren durch Auswahlen betrachten.
395
Informelles Verfahren
Fur einen Vektor mit n==v.size() Elementen gehen wir folgendermassenvor.
1. Wir suchen das kleinste Element an den Positionen 0 bis n-1.
2. Wir vertauschen das kleinste Element mit dem Element an Position 0.Danach ist das kleinste Element offensichtlich bereits an der richtigenPosition.
3. Wir wiederholen die Schritte 1) und 2) fur die die Positionen� � � � ! ".
Dabei suchen wir stets das kleinste Element an den Positionen k bis n-1und vertauschen es mit dem Element an Position k.
396
Vorgehen dieser Prozedur (1)
Ausgangssituation:
Plunke
ttMac
Diarmad
a
Collins
Clarke
Hurley
Conoll
ySan
ds
Pearse
Ceann
t
k=0:
Plunke
ttMac
Diarmad
a
Collins
Clarke
Hurley
Conoll
ySan
ds
Ceann
t
Pearse
397
Vorgehen dieser Prozedur (2)
k=1:
MacDiar
mada
Collins
Hurley
Conoll
ySan
ds
Ceann
t
Pearse
Plunke
tt
Clarke
k=2:
Hurley
Conoll
ySan
ds
Ceann
t
Pearse
Plunke
tt
Clarke
MacDiar
mada
Collins
398
Die benotigten Variablen
# Wahrend der Sortierung wird der Vektor in zwei Teile aufgeteilt.# Wahrend die eine Halfte des Vektors ist bereits sortiert ist, enthalt diezweite Halfte alle noch nicht sortierten Elemente.# Um die Position zu markieren, ab der wir noch sortieren mussen,verwenden wir die Variable k. Diese wird anfangs mit 0 initialisiert.# Daruber hinaus verwenden wir die Variable n fur die Anzahl der Elementim Vektor.
int k = 0;
int n = v.size();
399
Skelett der Prozedur
static void sort(Vector v){
int k = 0;
int n = v.size();
while (condition)
body
...
}
400
Die Bedingung der while-Schleife und Terminierung
$ Offensichtlich sind wir fertig, wenn der zu sortierende Bereich desVector-Objekts nur noch ein Objekt enthalt.$ Vektoren der Lange 1 und 0 sind immer sortiert.$ Dementsprechend konnen wir abbrechen, sobald k >= n-1$ Die Bedingung der while-Schleife ist somit:
while (k < n-1)$ Um die Terminierung zu garantieren, mussen wir in jeder Runde k um 1
erhohen:
k++;
401
Suchen des kleinsten Elementes
% Um das kleinste Element im noch nicht sortierten Bereich zu suchen,mussen wir unsere Methode linearSearch modifizieren.% Anstatt die Suche immer bei 0 zu starten, erlauben wir jetzt einenbeliebigen Startpunkt, den wir als Argument ubergeben:
static int getSmallest(Vector v, int k)
402
Die modifizierte getSmallest-Methode
static int getSmallest(Vector v, int k) {
if (v==null || v.size()<=k)
return -1;
int i = k+1;
int small = k;
String smallest = (String) v.elementAt(small);
while (i != v.size()) {
String current = (String) v.elementAt(i);
if (current.compareTo(smallest)<0){
small = i;
smallest = (String) v.elementAt(i);
}
i++;
}
return small;
}403
Tauschen zweier Elemente
Das Vertauschen von zwei Elementen haben wir bereits bei der Invertierungder Reihenfolge im Vektor kennengelernt:
static void swap(Vector v, int i, int j){
Object o = v.elementAt(i);
v.setElementAt(v.elementAt(j), i);
v.setElementAt(o, j);
}
404
Die komplette Sortiermethode
& Um den Vektor v mit n==v.size() Elementen zu sortieren, gehen wirwie geplant vor.& Fur k = 0, ..., n-2 vertauschen wir das Element an Position k mitdem kleinsten Element im Bereich i = k, ..., n-1:
static void sort(Vector v) {
int k = 0;
int n = v.size();
while (k < n-1) {
swap(v, k, getSmallest(v, k));
k++;
}
}
405
Eine aquivalente Version auf der Basis der for-Anweisung
static void sort(Vector v) {
for (int k = 0; k < v.size()-1; k++)
swap(v, k, getSmallest(v, k));
}
406
Das Programm zum Sortieren eines Vektors
import java.io.*;import java.util.*;
class Sort {
static void swap(Vector v, int i, int j){Object o = v.elementAt(i);v.setElementAt(v.elementAt(j), i);v.setElementAt(o, j);
}
static int getSmallest(Vector v, int k) {if (v==null || v.size()<=k)
return -1;int i = k+1;int small = k;String smallest = (String) v.elementAt(small);while (i != v.size()) {
String current = (String) v.elementAt(i);if (current.compareTo(smallest)<0){
small = i;smallest = (String) v.elementAt(i);
}i++;
}return small;
}
static void sort(Vector v) {for (int k = 0; k < v.size()-1; k++)
swap(v, k, getSmallest(v, k));}
public static void main(String arg[]) {Vector v = new Vector();Vector v1 = new Vector();Vector v2 = new Vector();
v.addElement("Albert");v.addElement("Ludwig");v.addElement("Jaeger");v1.addElement("Albert");sort(v);sort(v1);sort(v2);System.out.println(v.toString());System.out.println(v1.toString());System.out.println(v2.toString());
}}
407
Eine Aufwandsanalyse fur Sortieren durch Auswahlen
' In der Methode sort wird der Rumpf der for-Schleife genau n =
v.size()-1 mal ausgefuhrt.' In jeder Runde werden die Methoden getSmallest und swap
ausgefuhrt.'Wahrend swap jeweils drei Anweisungen ausfuhrt, benotigtgetSmallest stets n-k-1 Schritte.' Insgesamt sind die Kosten daher( ) * + , - . ( ( ) * + ( ) / + , , , * + , - 0 1 ( ) * + , - . ( ) * + , )/ , - 0wobei
- .und
- 0die Kosten fur die einzelnen Operationen sind.' Der Aufwand fur Sortieren durch Auswahlen liegt daher in 2 ( ) 0 +
.' Dabei gibt es keinen Unterschied zwischen Best, Worst und AverageCase.
408
Alternative Sortiermethoden
3 Fur das Sortieren von Kollektionen gibt es eine Vielzahl von von (in derRegel komplizierteren) Alternativen.3 Die meisten dieser Verfahren sind zumindest in bestimmten Fallen (WorstCase, Best Case oder Average Case) besser als Sortieren durchAuswahlen.3 Die besten Methoden benotigen 4 5 6 7 8 9 6 : .3 Dies entspricht auch dem Aufwand, den man fur das Sortieren vonVektoren mindestens benotigt.
409
Arrays
; Der Array ist ebenso wie die primitiven Datentypen ein eingebauterDatentyp fur Kollektionen.; Arrays haben verschiedene Gemeinsamkeiten mit Vektoren:
– Er enthalt mehrere Elemente,
– auf jedes Element kann durch einen Index zugegriffen werden,
– die erste Position ist 0,
– Arrays werden durch die new-Operation erzeugt,
– ein Array ist ein Objekt und
– fur Arrays werden Referenzvariablen verwendet.
410
Unterschiede zwischen Arrays und Vektoren
< Fur Arrays gibt keine Klasse.< Arrays sind — ebenso wie primitive Datentypen — in die Spracheeingebaut.< Es gibt keine Methoden fur Arrays.< Jedem ein Array ist eine Variable length zugeordnet, welche als Wertdie Anzahl der Element des Arrays enthalt.< Arrays konnen — im Gegensatz zu Vektoren — primitive Datentypenwie z.B. int enthalten.< Arrays sind homogen, d.h. alle Element mussen denselben Typ haben.< Arrays haben eine feste Große. Ihre Lange wachst nicht automatischwie die von Vektoren.< Arrays sind, da sie von der Sprache direkt zur Verfugung gestelltwerden, effizienter als Vektoren.
411
Deklaration und Erzeugung von Arrays
= Bei der Deklaration einer Referenzvariable fur ein Array, geben wirebenfalls den Typ der Elemente an:
int[] lottoNumber;
String[] winner;
Employee[] emp;= Um ein Array zu erzeugen, verwenden wir ebenfalls den new-Operator.Dabei geben wir den Typ der Elemente und ihre Anzahl an:
lottoNumber = new int[6];
winner = new String[100];
emp = new Employee[1000];
lottoNumber0 1 2 3 4 5
412
Zugriff auf die Elemente eines Arrays
> Um das Element an Position k eines Arrays auf einen bestimmten Wertzu setzen, verwenden wir eine Wertzuweisung der Form:
lottoNumber[k] = value;> Um ein Array-Element innerhalb eines Vektors zu verwenden, schreibenwir:
n = lottoNumber[3];> Zugriffe auf die Elemente eines Arrays lassen sich naturlich auchschachteln:
s = winner[z[3]];
413
Mehrdimensionale Arrays
? Arrays konnen nicht nur eindimensional, sondern auchmehrdimensional sein.? Fur ein zweidimensionales Feld von double-Werten wird beispielsweisefolgende Deklaration verwendet.
public double[][] value;? Das zweidimensionale Feld wird dann mit dem Statement
value = new double[m][n];
erzeugt.? Der Zugriff auf die Elemente eines zweidimensionalen Arrays wirdfolgendermaßen durchgefuhrt:
value[i][j] = 3.0;
414
Matrizen: Anwendung zweidimensionaler Arrays
@ Eine Matrix ist die Anordnung von A B C Werten in einer Tabelle von AZeilen und C Spalten. Dabei heißt eine Matrix quadratisch, falls A D D C .@ Eine A E C Matrix hat die Form:FGGGH
I J K L J K L B B B M J N L J C O L...
. . ....M J A O L J N L B B B M J A O L J C O L
P QQQR@ Eine typische Matrizenoperation ist das Transponieren, d.h. das
Vertauschen der Zeilen und Spalten einer Matrix.@ Das Element an Position a[i][j] der Transponierten entspricht demElement a[j][i] der Originalmatrix.
415
Eine einfache Klasse fur Matrizen
class Matrix {public Matrix(int m, int n) {
this.value = new double[m][n];this.m = m;this.n = n;
}public Matrix transpose(){
Matrix mat = new Matrix(this.n, this.m);for (int i = 0; i < this.m; i++)
for (int j = 0; j < this.n; j++)mat.value[j][i] = this.value[i][j];
return mat;}public void print(){
for (int i = 0; i < this.m; i++){for (int j = 0; j < this.n; j++)
System.out.print(this.value[i][j] + " ");System.out.println();
}}public double[][] value;public int m;public int n;
}
416
Eine kleine Beispielanwendung
class UseMatrix {public static void main(String [] args) {
Matrix m = new Matrix(2,2);m.value[0][0] = m.value[1][0] = m.value[1][1] = 0.0;m.value[0][1] = 1.0;
m.print();System.out.println();m.transpose().print();
}}
Dieses Programm erzeugt die Ausgabe
0.0 1.0
0.0 0.0
0.0 0.0
1.0 0.0
417
Zusammenfassung
S Kollektionen wie Vector-Objekte oder Arrays enthalten die Objekte ineiner bestimmten Ordnung.S Der Index der Elemente in Vektoren und Arrays beginnt bei 0.S Aufwandsanalysen dienen dazu, den Zeit- und Platzbedarf vonProgrammen/Verfahren zu ermitteln.S Es gibt verschiedene Klassen fur die Komplexitat. Beispiele sind T U V W ,T U V X W oder T U Y Z [ V W .S Die Suche in unsortierten Vektoren gelingt in T U V W .S Sind die Elemente im Vektor sortiert, konnen wir mit Binarsuche inT U Y Z [ V W suchen.S Eine Methode fur das Sortieren ist Sortieren durch Auswahlen.S Dies benotigt T U V X W . Die effizientesten Verfahren konnen Vektorenjedoch in T U V Y Z [ V W sortieren.
418
Einfuhrung in die Informatik
Recursion
Wolfram Burgard
419
Einfuhrung (1)
\ Geschirr nach großer Party muss gespult werden.\ Sie laufen unvorsichtigerweise an der Kuche vorbei und jemand sagtIhnen: Erledigen Sie den Abwasch.\ Was tun Sie?\ Sie sind faul und deshalb
– spulen Sie ein einziges Teil und
– suchen dann die nachste Person, um ihr/ihm zu sagen, dass sie denAbwasch erledigen soll.\ Vielleicht ist die Person, der Sie die Aufgabe ubergeben haben genauso
faul und verhalt sich nach dem gleichen Muster wie Sie und ebenso allenachfolgenden.
420
Einfuhrung (2)
] Diese Methode ist sehr angenehm, denn so muss keiner mehr als ein Teilspulen.] Der Letzte muss gar nichts mehr machen, er sieht nur ein leeresSpulbecken.] Demnach konnen wir den faulen (lazy) Ansatz von Erledige denAbwasch folgendermaßen definieren:
– Wenn das Spulbecken leer ist, ist nichts zu tun.
– Wenn es nicht leer ist, dann^ spule ein Teil und^ finde die nachste Person und sage ihr/ihm:”Erledige den
Abwasch.“
421
Bemerkungen
_ Der faule Ansatz enthalt einen Aufruf zu sich selbst._ Um sicherzugehen, dass dieser Ansatz nicht zu einem endlosenWeiterleiten fuhrt, muss jede Person einen Teil der Arbeit tun._ Weil der Job somit fur die nachste Person ein wenig kleiner wird, ist erirgendwann ganz erledigt und die Kette, dem Nachsten zu sagen, denRest zu tun, kann gebrochen werden._ Eine Prozedur dieser Art, die einen Teil der Aufgabe selbst lost und dannden Rest erledigt, indem sie sich selbst aufruft, wird rekursive Prozedurgenannt._ Rekursion ist ein einfaches und machtiges Prinzip, mit dem vieleschwierige Probleme gehandhabt werden konnen.
422
Beispiel: Potenzierung mithilfe von Rekursion
` Um das Prinzip naher zu verstehen, beginnen wir mit der Potenzierung,ein einfaches Problem, welches wir bereits mit Iteration gelost haben undfur das Rekursion nicht zwingend notwendig ist.` Wir suchen eine Funktion, die zwei Integer-Parameter x und y ubergebenbekommt und einen Integer-Wert, namlich a b , zuruckgibt.` Der Prototyp einer solchen Methode ist also
private int power(int x, int y)` Die Definition der Potenzierung ista b c d e a e f f f e ag h i jb mal
423
Potenzierung mithilfe von Rekursion (1)
k Um dieses Problem mithilfe von Rekursion zu losen, stellen wir uns vor,wir mussten die Rechnung von Hand durchfuhren.k Wenn y ziemlich groß ist, erscheint die Berechnung von l m aufwendig zusein.k Also nehmen wir den faulen Ansatz und lassen jemand anderen einenTeil der Arbeit tun.k Wenn wir einen Assistenten hatten, der uns l m n o berechnet, mussten wirdas Ergebnis des Assistenten nur noch mit l multiplizieren.k Naturlich kann der Assistent ebenfalls einen Assistenten haben, der l m n pberechnet usw.
424
Potenzierung mithilfe von Rekursion (2)
q Wie lauft diese Prozedur also genau ab?q Wenn wir davon ausgehen, dass der Assistent pruft, ob er den einfachenFall von r s zu berechnen hat, in welchem Fall das Ergebnis 1 ist, kanndie Prozedur Berechne r hoch t folgendermaßen angegeben werden:
– Wenn t u v ist, sind keine Multiplikationen durchzufuhren und dasErgebnis ist 1.
– Wenn t w v ist, dannx Sage einem Assistenten:”Berechne r hoch t y .“x Das Ergebnis ist r -mal das Ergebnis des Assistenten.
425
Potenzierung mithilfe von Rekursion (3)
z Die Prozedur ist rekursiv, weil sie einen Aufruf zu sich selbst enthalt undsie ist gultig, weil sie
– einem Assistenten ein kleineres Problem zu losen gibt und weil
– sie einen Test zur Verfugung stellt, um zu prufen, ob es uberhauptnoch Arbeit zu erledigen gibt.
43 4 4 42 0
A B C(1) hires (2) hires (3) hires
(last) returns 64
(4) returns 1(6) returns 16 (5) returns 4
1
426
Potenzierung mithilfe von Rekursion: Java-Code
static int power(int x, int y) {int assistantResult;
if (y==0)
// nothing to do, result is 1
return 1;
else {// tell the assistant to compute x to the (y-1) power
assistantResult = power(x, y-1);
// the result is x times the assistant’s result
return x * assistantResult;||427
Worauf muss beim Definieren einer rekursiven Methodegeachtet werden?
} Der rekursive Aufruf: Die Argumente des rekursiven Aufrufs musseneine Aufgabe darstellen, die einfacher zu losen ist als die Aufgabe, diedem Aufrufer ubergeben wurde.} Terminierung: Bei jedem Aufruf einer rekursiven Methode muss gepruftwerden, ob Aufgabe ohne erneute Rekursion gelost werden kann.
– Der Terminierungs-Code muss vor rekursivem Aufruf stehen!
– Andernfalls wurde nie Terminierung erreicht und immer wieder derrekursive Aufruf gestartet werden.
428
Wie designed man eine rekursive Methode?
1. Wie kann das gegebene Problem verkleinert/vereinfacht werden?~ Dies erledigt der rekursive Aufruf.~Oft kann Faulheit hierbei eine Inspiration sein.
2. Wann ist das Problem klein/einfach genug, dass es direkt gelost werdenkann?~ Dies erledigt der Terminierungscode.~ Ebenfalls kann hierbei Faulheit eine Inspiration sein. Frage: Was ist
der einfachste Fall des zu losenden Problems?
429
Einlesen von Daten, um eine Sammlung vonEmployee-Objekten zu erzeugen
Prototyp der Methode:
private void getEmployees(BufferedReader br, Vector v)
1. Wir sind faul und lesen immer nur ein Objekt, das wir v hinzufugen:
Employee e = Employer.read(br);
v.addElement(e);
Dann uberlassen wir jemand anderem (dem rekursiven Aufruf) den Restdes Einlesens (das Problem ist kleiner geworden, da ein Objekt wenigerim BufferedReader ist).
getEmployees(br, v);
2. Der Test auf Terminierung ist einfach: Wir konnen aufhoren, wenn eskeinen weiteren Input gibt, also die Employee.read-Methode nullzuruckliefert.
if (e==null) return;
430
Rekursives Einlesen von Daten - Javacode
private void getEmployees (BufferedReader br, Vector v) �Employee e = Employer.read(br);
// termination code must come after the attempt to read
// but BEFORE we add to v or make the recursive call
if (e==null)
return;
v.addElement(e);
getEmployees(br, v);�
431
Es gibt zwei Rekursionsmuster� Wir losen ein Problem, indem wir einen kleinen Teil selbst erledigenund den rekursiven Aufruf den Rest machen lassen (Beispiele:Abwasch, Einlesen).method (problem) {
if (problem is very easy)
solve it and return;
solve part of the problem, leaving a smaller problem;
method (smaller problem);
}� Ein einfacheres Problem wird rekursivem Aufruf ubergeben anddessen Ergebnis wird benutzt, um das ursprungliche Problem zu losen(Beispiel: power).method (problem) {
if (problem is very easy)
return solution to easy problem;
solution to smaller problem = method(smaller problem);
solve problem, using solution to smaller problem;
return solution;
}432
Speicherverbrauch wahrend des Aufrufseiner rekursiven Methode
Um den Speicherverbrauch einer Rekursionsmethode zu verstehen, schauenwir uns einmal an, was genau passiert:
1. Aufrufer kommt zu der Stelle des rekursiven Aufrufes.
2. Aufrufer ubergibt Argumente an Aufgerufenen.
3. Aufgerufener reserviert Speicher fur Parameter und lokale Variablen.
4. Programmcode des Aufgerufenen wird ausgefuhrt.
5. Aufgerufener gibt sein Ergebnis an Aufrufer zuruck und gibt Speicher frei.
6. Aufrufer arbeitet seinen Programmcode weiter ab.
Innerhalb der Ausfuhrung des Aufgerufenen kann es naturlich zu weiterenrekursiven Aufrufen kommen.
433
Activation Records
Das Programm muss sich also fur jeden rekursiven Aufruf Informationenmerken, d.h. es wird Speicher belegt fur:� die Ubergabeparameter,� die lokalen Variablen und� die Stelle im Programm, bei der nach der Ausfuhrung des rekursiven
Aufrufes weitergemacht werden soll.
Ein solcher Block an Informationen heißt Activation Record.
Bei jedem neuen rekursiven Aufruf wird ein solcher Activation Record erstelltund beim Verlasen des rekursiven Aufrufs wieder geloscht, d.h. der Speicherwird freigegeben.
434
Beispiel Activation Records (1)
Wir befinden uns in einer Methode f und es kommt zum Aufruf der Funktionpower:
...
x = power(3,2); // Zeile z
...
� Durch den Aufruf power(3,2) wird ein Activation Record (x=3, y=2,assistantResult, Rucksprung in Zeile z) erstellt und im Speichergehalten, bis power(3,2) komplett abgearbeitet wurde.� Da innerhalb von power(3,2) auch power(3,1) und power(3,0) aufgerufenwerden, entstehen wahrend der Ausfuhrung auch noch dieentsprechenden anderen beiden Activation Records.
435
Beispiel Activation Records (2)
Nach Aufruf von power(3,2) entstehen folgende Activation Records:
Sender: whoever invoked f
x 3 y assistantResult1
x 3 y assistantResult0
Activation record for f(...)
Activation record for power(3,2)
Activation record for power(3,1)
Activation record for power(3,0)
Sender: method f, line N
x 3 y 2 assistantResult
Sender: method power, line 5
Sender: method power, line 5
(the current activation record)
436
Was bewirkt das return-Statement?
� Es wertet den Ruckgabewert aus.� Es zerstort das aktuelle Activation Record.� Es ersetzt den Aufruf der Methode durch den Ruckgabewert.�Es bewirkt, dass beim Sender mit der Ausfuhrung des Programmcodesfortgefahren wird.
437
Beispiel Activation Records (3)
Nachdem power(3,0) abgearbeitet wurde (Terminierung), gibt es folgendeActivation Records:
Sender: whoever invoked f
x 3 y assistantResult1
Activation record for f(...)
Activation record for power(3,2)
Activation record for power(3,1)
Sender: method f, line N
x 3 y 2 assistantResult
Sender: method power, line 5
1
438
Ausgabe der eingelesen Worter in umgekehrter Reihenfolge
import java.io.*;
class ReverseInputRecursive {static void reverse(BufferedReader br) throws Exception {
String s = br.readLine();if (s != null){
reverse(br);System.out.println(s);
}}
public static void main(String arg[]) throws Exception {BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
reverse(br);}
}
439
Speicherverbrauch - Zusammenfassung
� Bei Methoden mit vielen rekursiven Aufrufen ensteht eine sehr großeMenge an Activation Records, die im Speicher gehalten werdenmussen.� Unter Umstanden kommt es dadurch zu einem Uberlauf des Speichers,d.h. es ist nicht genugend Speicher zum Verwalten der ActivationRecords vorhanden.� Aus diesem Grund limitiert der zur Verfugung stehende Speicher dieTiefe einer Rekursion!
440
Rekursion und Iteration (1)
� Rekursion und Iteration basieren beide auf dem Verfahren derWiederholung bis hin zu einer Abbruchbedingung.� Eine Iteration, z. B. eine while-Schleife, kann auf einfache Weise in eineRekursion uberfuhrt werden.
Sei folgende allgemeine while-Schleife gegeben:
while(condition)
body;
wobei:�v1, ..., vN Variablen sind, die in condition und body auftreten undderen Werte nach Terminierung der Schleife noch benotigt werdenund�p1, ..., pN Variablen sind, die in condition und body auftreten undderen Werte nach Terminierung der Schleife nicht mehr benotigtwerden.
441
Uberfuhrung der Iteration in eine Rekursion
1. Deklaration von v1, ..., vN als Instanzvariablen.
2. Definition einer rekursiven Methode fur die while-Schleife:
private void recursiveWhile(p1, ..., pN) {
if (!condition)
return;
body;
recursiveWhile(p1, ..., pN);
}
3. Ersetzen der ursprunglichen while-Schleife durch folgenden Aufruf:
recursiveWhile(p1, ..., pN);
442
Berechnung der Summe der Elemente eines Arrays miteiner Iterative Version
class SomeClass{...public int getSum(int[] x) {
int n = x.length;int i = 0;int sum = 0;while(i!=n) {
sum += x[i];i++;
}// sum == x[0] + x[1] + ... + x[n-1]return sum;
}...
}
443
Berechnung der Summe der Elemente eines Arrays -Uberfuhrung in rekursive Version
� Die Variablen n und i werden in der Schleife benutzt, aber anschließendnicht mehr benotigt. Also werden sie zu Parametern der rekursivenMethode.� Die Variable sum wird nach Terminierung der while-Schleife gebraucht.Deshalb wird sie eine Instanzvariable.
444
Berechnung der Summe der Elemente eines Arrays -Eine rekursive Version
class SomeClass{...public int getSum(int[] x, int n) {
int n = x.length;int i = 0;this.sum = 0;recomputeSum(x,i,n); // recursiveWhile(x,p1,p2)
}
private void recomputeSum(int[] x, // recursiveWhile(x,p1,p2)int i, int n) {
if (!(i!=n)) // if (!condition)return; // return;
this.sum += x[i]; // bodyi++; // bodyrecomputeSum(x, i, n); // recursiveWhile(p1,p2)
}...private int sum; // instance variable
}
445
Wie sieht es mit der Umwandlungvon Rekursion in Iteration aus?
� In bestimmten Fallen ist der andere Transformationsweg, also eineRekursion in eine Iteration zu verwandeln, einfach.� Prinzipiell ist dies dagegen nicht so einfach oder nur mit großeremAufwand moglich.
446
Endrekursion
� In einer Schleife sind die Variablen des Schleifenkorpers nur ein einzigesMal vorhanden — bei jeder Zuweisung werden sie uberschrieben.� Dagegen werden in jedem rekursiven Aufruf mit dem Activation Recordneue Variablen erzeugt.� Ein Aufruf einer rekursiven Methode heißt endrekursiv, wenn er dieletzte Anweisung der rekursiven Methode ist.� Funktionen mit einem einzigen endrekursiven Aufruf lassen sich leichtin Schleifen umwandeln, weil man die lokalen Variablen einfachuberschreiben kann, da sie nach der Ruckkehr aus dem Methodenaufrufnicht mehr benotigt werden.
447
Berechnung der Summe der Elemente eines Arrays:Eine endrekursive Version ohne Instanzvariable
class SomeClass{...private int recomputeSum(int[] x, int i, int sum) {
if (i == x.length)return sum;
return recomputeSum(x, i+1, sum+x[i]);}
public int getSum(int[] x) {return recomputeSum(x, 0, 0);
}...
}� Anstelle der Instanzvariable verwendet man einen Parameter, in demman die Zwischensumme akkumuliert.� Ist die Rekursion beendet, gibt man den Wert des Akkumulators zuruck.
448
Wechselseitig rekursive Methoden
Zwei Methoden p und q heißen wechselseitig rekursiv, wenn p die Methodeq aufruft und q zu einem Aufruf von p fuhrt.
class evenOdd {
static boolean even (int i) {if (i == 0)
return true;else
return odd(i-1);}
static boolean odd (int i) {if (i == 0)
return false;else
return even(i-1);}
}
449
Geschwindigkeit von Rekursion und Iteration
� Bei Rekursion und Iteration (mittels while-Schleifen) handelt es sich umgleich machtige Verfahren. D.h. alle Probleme, die man mit einerRekursion losen kann, kann man auch mit einer (while-Schleife losenund umgekehrt.�In den meisten Fallen sind auch beide Verfahren gleich schnell.� Der Nachteil der Rekursion besteht darin, dass, sofern das System nichtentsprechende Optimierungen vornimmt, stets Activation Recordsangelegt werden.� Dieser zusatzliche Speicherplatz fallt bei vielen iterativen Methoden nichtan (siehe z.B. power).� Bei sehr tiefen Rekursionen kann es daher vorkommen, dass eineiterative Losung schneller ist, da die Verwaltung der Activation Recordsentfallt.
450
Weshalb benutzt man Rekursionen?
� Mit Hilfe der Rekursion lassen sich einige Problem sehr viel eleganterlosen als mit der Iteration.�Die Korrektheit eines rekursiven Programmes lasst sich haufigwesentlich einfacher zeigen als die der iterativen Variante.�Bei rekursiven Losungen muss man nicht immer alleZwischenergebnisse speichern, da diese automatisch in denActivation records abgelegt werden. Die Verwaltung der zuverarbeitenden Objekte entfallt. Beispielsweise benotigt die rekursiveVersion des Umdrehens der Reihenfolge der Zeilen im Gegensatz zuriterativen Variante mittels while-Schleife kein Vector-Objekt.
451
Eine bekannte rekursive Funktion: Die Ackermann-Funktion
� Die Ackermann-Funktion spielt eine wichtige Bedeutung in dertheoretischen Informatik, da sie außerordentlich schnell wachst.� Die mathematische Definition der Ackermann-Funktion ist:
� � � � � � � � � ���� ���� �
falls� � �� � � � � � � � � falls � � �� � � � � � � � � � � � � � � � � sonst
452
Implementierung der Ackermann-Funktion
class ProgramAck {static int ack(int x, int y){
if (x == 0)return y+1;
else if (y == 0)return ack(x-1, 1);
elsereturn ack(x-1, ack(x,y-1));
}
public static void main(String arg[]) {for (int x = 0; x < 5; x++)
for (int y = 0; y < 5; y++)System.out.println("Ack("+x+","+y+")="+ack(x,y));
}}
453
Die Werte der Ackermann-Funktion
0 1 2 3 4
0 1 2 3 4 5
1 2 3 4 5 6
2 3 5 7 9 11
3 5 13 29 61 125
4 13 65533 ? ? ?� Der Wert von � � ¡ ¢ £ ¤ ¥ kann mit 19729 Dezimalziffern beschriebenwerden.� � � ¡ ¢ £ ¢ ¥ ist großer ¦ § ¨ © ª « ª ¬ « « « . (Die Anzahl der Elementarteilchen imbekannten Universum liegt etwa bei ¦ § © .)
454
Berechnung aller Permutationen eines Vector-Objektes
Ein weiteres Problem, dass sich nur schwer mit Iteration realisieren lasst istdie Berechnung aller Permutationen:
static void printPermutation(int n, Vector v){
if (n >= v.size())
System.out.println(v);
else{
printPermutation(n+1, v);
for (int i = n+1; i < v.size(); i++){
swap(v, n, i);
printPermutation(n+1, v);
swap(v, n, i);
}
}
}
455
Zusammenfassung
® Rekursion ist ein machtiges Verfahren zur Definition von Methoden.® Grundidee der Rekursion ist die Reduktion eines gegebenen Problemsauf ein einfacheres Problem.® Bei der Rekursion werden Activation Records angelegt, um dienotwendigen Informationen (lokale Variablen etc.) zu speichern.® Dadurch werden rekursive Varianten manchmal kompakter alsiterative Methoden, allerdings kostet die Verwaltung der ActivationRecords Zeit.
456
Einfuhrung in die Informatik
Extending Class Behavior
Ableitung von Klassen, Vererbung, Polymorphie, Abstraktion
Wolfram Burgard
457
Einleitung
¯ Bisher haben wir Klassen immer vollstandig implementiert, d.h. wir habenalle Instanzvariablen und Methoden der Klassen selbst definiert.¯ Ziel dieses Kapitels ist die Vorstellung von Techniken, um neue Klassenausgehend von bereits bestehenden Klassen zu definieren.¯ Im Gegensatz zum bisherigen Vorgehen, bei dem wir Methoden andererKlassen verwendet haben, um bestehende Klassen zu realisieren,werden wir jetzt neue Klassen als Ableitung bestehender Klassendefinieren.
458
Beispiel: Ein besserer BufferedReader
° Die Klasse BufferedReader stellt Methoden fur das Einlesen vonString-Objekten zur Verfugung.° Wann immer wir Objekte von der Tastatur oder aus einer Datei lesenwollen, mussen wir ein BufferedReader-Objekt verwenden.° Um jedoch eine Zahl einzulesen, mussen wir immer zuerst einen Stringeinlesen und danach die Zahl aus diesem String mittelsInteger.parseInt herauslesen.° In Situationen, in denen haufig Zahlen eingelesen werden mussen, istman jedoch an BufferedReader-Objekten interessiert, die Zahlendirekt einlesen konnen.°Im Folgenden werden wir daher untersuchen, wie wir von der KlasseBufferedReader eine neue Klasse ableiten konnen, die direkt Zahleneinlesen kann.
459
Zwei Moglichkeiten zur Erweiterung von Klassen
Zur Erweiterung einer Klasse um weitere Funktionalitat bestehen im Prinzipzwei Moglichkeiten:
1. Wir andern die Definition der Originalklasse.
2. Wir definieren eine neue Klasse, die alle Methoden und Instanzvariablender Originalklasse ubernimmt, und fugen lediglich die fehlendenMethoden und Instanzvariablen hinzu. Diesen Prozess nennen wirAbleitung.
460
Vorteile der Ableitung von Klassen
Die Ableitung von Klassen mit Ubernahme der bestehenden Eigenschaftender Originalklasse hat gegenuber einer Erweiterung einer bestehendenKlasse verschiedene Vorteile:± Die Originalklasse ist ublicherweise gut getestet und es ist nicht klar,
welche Konsequenzen Anderungen der Originalklasse haben.± Andere Programme verwenden die Originalklasse bereits und benotigendie neuen Methoden nicht.± Haufig ist der Code einer Klasse nicht zuganglich (z.B. bei eingebautenKlassen). Eine Anderung ist dann nicht moglich.± In manchen Fallen ist eine Klasse aber auch sehr kompliziert und daherschwer zu verstehen. In diesem Fall ist es nicht empfehlenswert, diebestehende Klasse zu modifizieren.
461
Vererbung / Inheritance
² Objektorientierte Sprachen wie z.B. Java stellen mit der MoglichkeitKlassen abzuleiten ein sehr machtiges Konzept zur Verfugung umKlassen zu erweitern ohne den Code der Originalklasse zumodifizieren.² Diese Technik, die als Vererbung bezeichnet wird, ist eine dergrundlegenden Eigenschaften objektorientierterProgrammiersprachen.
462
Ableitung von Klassen durch Vererbung
³ Um in Java bestehende Klassen zu erweitern und ihre Verhalten zuerben, verwenden wir das Schlusselwort extends in derKlassendefinition:
class BetterBR extends BufferedReader ´...µ
³ Dadurch erbt die Klasse BetterBR, die einen erweitertenBufferedReader realisieren soll, alle Methoden und Instanzvariablender Klasse BufferedReader.³ Die Vererbung geschieht, ohne dass wir den Source-Code der KlasseBufferedReader kopieren mussen.³ In unserem Beispiel ist BufferedReader die Superklasse vonBetterBR.³ Umgekehrt ist BetterBR Subklasse von BufferedReader.
463
Realisierung eines erweiterten Buffered Readers BetterBR
Unser erweiterter Buffered Reader BetterBR soll in folgender Hinsicht eineErweiterung der BufferedReader-Klasse darstellen:
1. Wir mochten im Konstruktor einfach einen Dateinamen angeben konnen.
BetterBR(String fileName) {...}
2. Geben wir kein Argument an, soll System.in verwendet werden.
BetterBR() {...}
3. Die Klasse BetterBR soll eine Methode readInt zur Verfugung stellen,die einen int-Wert zuruckliefert:
int readInt() {...}
464
Implementierung der Methoden: Die Konstruktoren
¶ Da ein BetterBR-Objekt auch ein BufferedReader-Objekt beinhaltet,mussen wir dafur sorgen, dass das BufferedReader-Objekt korrektinitialisiert wird.¶ Normalerweise wird der Konstruktor einer Klasse automatischaufgerufen, wenn wir ein neues Objekt dieser Klasse mit demnew-Operator erzeugen.¶ In unserem Fall erzeugt der Benutzer unserer Klasse aber einBetterBR-Objekt und kein BufferedReader-Objekt:
BetterBR bbr = new BetterBR();¶ Daher muss der Konstruktor von BetterBR dafur sorgen, dass derKonstruktor der BufferedReader-Klasse aufgerufen wird:
public BetterBR()·super(new InputStreamReader(System.in));¸
465
Das Schlusselwort super
¹ Der Konstruktor von BetterBR muss offensichtlich den Konstruktor vonBufferedReader aufrufen.¹ Generell muss der Konstruktor einer abgeleiteten Klasse immer denKonstruktor seiner Super-Klasse mit allen erforderlichenArgumenten aufrufen.¹ Dies geschieht nicht auf die ubliche Weise, d.h. mit
BufferedReader(new InputStreamReader(System.in))
in unserem Beispiel, sondern unter Verwendung des Schlusselwortssuper:
super(new InputStreamReader(System.in))¹ Wenn der Konstruktor der Superklasse aus dem Konstruktor einerSubklasse heraus aufgerufen wird, wird das Schlusselwort superautomatisch durch den Namen der Superklasse ersetzt.
466
Die Konstruktoren von BetterBR (2)
ºNach dem Aufruf des Konstruktors der Superklasse, muss die Subklassedie Initialisierungen, die fur Objekte dieser Klasse erforderlich sind,vornehmen.º In unserem Beispiel, d.h. fur BetterBR sind jedoch keine weiterenInitialisierungen erforderlich. BetterBR hat keine Instanzvariablen.º Der zweite Konstruktor von BetterBR soll einen Dateinamenakzeptieren:
public BetterBR(String fileName) throws FileNotFoundException{
super(new InputStreamReader(
new FileInputStream(new File(fileName))));
}
467
Die Methode readInt der Klasse BetterBR
» Offensichtlich muss die Methode readInt die Methode readLine
aufrufen, um ein String-Objekt einzulesen.» Anschließend kann sie die Zahl aus dem String-Objekt mithilfe vonInteger.parseInt herauslesen.» Da readLine eine Nachricht ist, die in der Superklasse definiert ist,verwenden wir erneut das Schlusselwort super:
public int readInt() throws IOException ¼return Integer.parseInt(super.readLine());½
468
Die vollstandige Klasse BetterBR
import java.io.*;
class BetterBR extends BufferedReader {public BetterBR(){
super(new InputStreamReader(System.in));}
public BetterBR(String fileName) throws FileNotFoundException{super(new InputStreamReader(
new FileInputStream(new File(fileName))));
}
public int readInt() throws IOException{return Integer.parseInt(super.readLine());
}}
469
Subklassen mit Instanzvariablen
¾ Ublicherweise erfordern Subklassen aber auch eigene Instanzvariablen,um zusatzliche Informationen abzulegen.¾ Im Folgenden werden wir anhand einer Klasse Name betrachten, wiediese abgeleitet werden kann zu einer Klasse ExtendedName.¾Ein Problem stellen hierbei als private deklarierte Instanzvariablender Superklasse dar, da sie den Zugriff auf die Instanzvariablen in derSubklasse unterbinden.¾ Um innerhalb einer Subklasse auf die Instanzvariablen der Superklassezugreifen zu konnen und dennoch den Zugriff von außen zu unterbinden,verwendet man daher das Schlusselwort protected (anstelle vonprivate).
470
Die Ausgangsklasse Name
class Name {public Name(String first, String last) {
this.firstName = first;this.lastName = last;this.title = "";
}public String getInitials() {
return this.firstName.substring(0,1) + "."+ this.lastName.substring(0,1) + ".";
}public String getLastFirst() {
return this.lastName + ", " + this.firstName;}public String getFirstLast() {
return (this.title+" "+this.firstName+" "+ this.lastName).trim();}public void setTitle(String newTitle) {
this.title = newTitle;}protected String firstName;protected String lastName;protected String title;
}
471
Erweiterung der Klasse Name
¿Ziel ist die Entwicklung einer Klasse, die zusatzlich zur Klasse Name aucheinen zweiten Vornamen (middleName) zur Verfugung stellt.¿Der Name dieser Klasse soll ExtendedName sein.¿ Daruber hinaus soll die Klasse eine Methode formalName zurVerfugung stellen, die den kompletten Namen einschließlichmiddleName zuruckgibt.¿ Schließlich soll die Klasse ExtendedName alle Methoden der KlasseName beinhalten.
472
Das Skelett der Klasse ExtendedName
Da Vorname, Name und Titel in der Klasse Name gespeichert werden,mussen wir in ExtendedName lediglich den zweiten Vornamen speichern:
class ExtendedName extends Name{
public ExtendedName(String firstName, String lastName) {
...
}
public ExtendedName(String firstName,
String middleName, String lastName) {
...
}
public String getFormalName() {
...
}
protected String middleName;
}
473
Die Konstruktoren von ExtendedName
1. Der Konstruktor benotigt beide Vornamen und den Nachnamen. ImKonstruktor mussen wir den Konstruktor von Name aufrufen undanschließend Variable middleName setzen:
public ExtendedName(String firstName,
String middleName, String lastName) {
super(firstName, lastName);
this.middleName = middleName;
}
2. Der zweite Konstruktor akzeptiert lediglich den ersten Vornamen und denNachnamen. Der zweite Vorname ist dann leer.
public ExtendedName(String firstName, String lastName) {
super(firstName, lastName);
this.middleName = "";
}474
Die Methode getFormalName
À Die Methode getFormalName liefert den vollstandigen Nameneinschließlich des Titels und der Vornamen.À Hierbei berucksichtigen wir, dass der zweite Vorname und der Titelevtl. leer sein konnen.
public String getFormalName() {
if (this.middleName.equals(""))
return super.getFirstLast();
else
return (super.title + " " + super.firstName + " "
+ this.middleName + " "
+ super.lastName).trim();
}
475
Overriding: Uberschreiben von Methoden
Á Die Klasse ExtendedName ubernimmt alle Methoden der Klasse Name.Á Da die Name-Klasse nur den ersten Vornamen und den Nachnamenkennt, liefert die Methode getInitials der Klasse Name das falscheErgebnis.Á Wir mussen daher die Methode getInitials der Klasse Name in derKlasse ExtendedName durch eine korrekte Methode ersetzen.Á Diesen Prozess der Ersetzung bzw. Uberschreibung von Methodennennt man Overriding.
476
Uberschreibung der Methode getInitials
 Die Methode getInitials muss in der Klasse ExtendedName neudefiniert werden. Lediglich in dem Fall, dass middleName leer ist, konnen wir auf dieentsprechende Methode der Superklasse zuruckgreifen:
public String getInitials() {
if (this.middleName.equals(""))
return super.getInitials();
else
return super.firstName.substring(0,1) + "."
+ this.middleName.substring(0,1) + "."
+ super.lastName.substring(0,1) + ".";
}
477
Die resultierende Klasse ExtendedName
firstNamelastNametitle
middleName
Name-Object
ExtendedName-Object
getLastFirstgetFirstLastgetInitialssetTitle
getFormalNamegetInitials
478
Der Programmcode von ExtendedName
class ExtendedName extends Name{public ExtendedName(String firstName, String lastName) {
super(firstName, lastName);this.middleName = "";
}public ExtendedName(String firstName, String middleName, String lastName) {
super(firstName, lastName);this.middleName = middleName;
}public String getInitials() {
if (this.middleName.equals(""))return super.getInitials();
elsereturn super.firstName.substring(0,1) + "."
+ this.middleName.substring(0,1) + "."+ this.lastName.substring(0,1) + ".";
}public String getFormalName() {
if (this.middleName.equals(""))return super.getFirstLast();
elsereturn (super.title + " " + super.firstName + " "
+ this.middleName + " " + super.lastName).trim();}protected String middleName;
}
479
Anwendung der Klasse ExtendedName
class useExtendedName {public static void main(String [] args) {
ExtendedName name1 = new ExtendedName("Peter", "Mueller");ExtendedName name2 = new ExtendedName("Peter", "Paul", "Meyer");System.out.println(name1.getFormalName());System.out.println(name1.getInitials());System.out.println(name2.getFormalName());System.out.println(name2.getInitials());name2.setTitle("Dr.");System.out.println(name2.getFormalName());System.out.println(name2.getInitials());
}}
liefert:
Peter MuellerP.M.Peter Paul MeyerP.P.M.Dr. Peter Paul MeyerP.P.M.
480
Inheritance versus Komposition
à Bisher haben wir neue Klassen immer definiert, indem wir Objekteanderer Klassen verwendet haben.à Beispielsweise haben wir zur Realisierung der Klasse Name Objekte derKlasse String verwendet. Ahnliches gilt fur die Klasse Set, bei derenDefinition wir auf Methoden der Klasse Vector zuruckgegriffen haben.à Dieses Verfahren, bei dem man Referenzvariablen auf Objekte andererKlassen verwendet, bezeichnet man als Komposition.à Im Gegensatz dazu werden bei der Ableitung von KlassenInstanzvariablen und Methoden von der Superklasse geerbt.
481
Eine Erweiterung von Name mittels Komposition
class ExtendedNameComposition {
public ExtendedNameComposition(String firstName,
String middleName,
String lastName) {
this.name = new Name(firstName, lastName);
this.middleName = middleName;
}
// ...
private String middleName;
private Name name;
}
482
Nachteile dieses Vorgehens
1. Die Klasse ExtendedNameComposition erbt nicht die Methoden derKlasse Name.
2. Stattdessen muss der Programmierer fur alle Methoden derSuperklasse Name, die auch durch ExtendedNameComposition zurVerfugung gestellt werden sollen, entsprechenden Codeprogrammieren.
3. Innerhalb der Klasse ExtendedNameComposition kann man nicht aufdie in Name als protected definierten Variablen zugreifen.
4. Daher konnen die Methoden getFormalName nicht so, wie inExtendedName, programmiert werden, da nicht auf die Instanzvariablenvon Name zugegriffen werden kann.
5. Eine Losung besteht darin, die Instanzvariablen als public zudeklarieren, was allerdings ein Sicherheitsrisiko in sich birgt.
483
Klassenhierarchien
Ä In unseren Beispielen haben wir immer nur eine einfache Ableitungvorgenommen.Ä Prinzipiell gibt es jedoch keinen Grund dafur, dass neue Klassen nichtauch von Subklassen abgeleitet werden konnen.Ä Daruber hinaus ist es naturlich auch moglich, mehrere Klassen voneiner Klasse abzuleiten.Ä Dabei unterstutzt Java lediglich Single Inheritance (einfache Vererbung),d.h. jede Klasse kann lediglich eine Superklasse haben.Ä Dies fuhrt zu stammbaumahnlichen Strukturen, die man alsKlassenhierarchie bezeichnet.
484
Ein Ausschnitt aus der Klassenhierarchie von Java
485
Polymorphismus (1)
Das Prinzip des Overriding:Å Wir haben gesehen, dass eine abgeleitete Klasse eine Methode ihrerSuperklasse uberschreiben kann.Å Im Beispiel
Name n = new Name("Shlomo", "Weiss");
ExtendedName en = new ExtendedName("William", "Tecumseh",
"Sherman");
String initals = n.getInitials();
initials = en.getInitials();
wird beim ersten Aufruf von getInitials die Methode der Klasse Name
und beim zweiten Aufruf die Methode der Klasse ExtendedName
aufgerufen.
486
Polymorphismus (2)
Æ Ist hingegen die Methode in der abgeleiteten Klasse nicht definiert, wiez.B. bei der Nachricht
String s = en.getLastFirst();
dann sucht der Java-Interpreter zunachst nach der MethodegetLastFirst in der Klasse extendedName.Æ Findet er sie dort, wird sie ausgefuhrt.Æ Findet er sie nicht, wird zur Superklasse Name ubergegangen und dortnach der Methode gesucht.Æ Dieses Verfahren wird rekursiv fortgesetzt, bis eine Superklassegefunden wurde, in der die Methode enthalten ist.
487
Polymorphismus (3): Dynamisches Binden
Ç Betrachten wir die folgende Deklaration:
Name n = new ExtendedName("Thomas", "Alva", "Edison");Ç Dieses Deklaration ist zulassig, weil wir immer ein Objekt einerSubklasse anstelle eines Objektes der Klasse selbst verwendendurfena.Ç Wenn wir n jetzt die Nachricht
String s = n.getInitials();
senden, so verwendet Java die Methode der Klasse ExtendedName undnicht die der Klasse Name.Ç Allgemein gilt: Die Suche nach der Methode beginnt immer in derKlasse des Objektes und nicht in der Klasse der Referenzvariable.
aWir haben dies bereits bei der Definition der Klasse Set (Object) ausgenutzt
488
Dynamic Binding am Beispiel der Methode toString
È Ein typisches Beispiel fur Polymorphismus ist die Methode toString.È Diese Methode ist in der Klasse Object definiert und soll vonabgeleiteten Klassen uberschrieben werden.È Sinn der Methode toString ist die Erzeugung einerString-Reprasentation des Objektes:
Enumeration e = v.elements();
while (e.hasMoreElements())
System.out.println((e.nextElement()).toString());È Wahrend der Ausfuhrung sucht der Java-Interpreter dann mit demoben beschriebenen rekursiven Verfahren nach dertoString-Methode.
489
Zusammenfassung gemeinsamer Eigenschaftendurch Vererbung
É Betrachten Sie die Situation, dass Sie eine Inventarverwaltung fur einFotogeschaft realisieren sollen.É Durch dieses System sollen verschiedene Objekte wie Objektive (Lens),Filme (Film) und Kameras (Camera) etc. verwaltet werden.É Wenngleich alle Objekte verschiedene Eigenschaften haben, gibt es auchbestimmte Attribute, die bei allen Objekten abgelegt werden. Hierzusollen in unserem Beispiel gehoren:
– Bezeichnung (description),
– Identifikationsnummer (id),
– Anzahl (quantity) und
– Preis (price).
490
Das Skelett der Klasse Lens
class Lens {
public Lens(...) {...} // Konstructor
public String getDescription() {
return this.description;
}
public int getQuantity() {
return this.quantity;
}
... // weitere fuer Lens spezifische Methoden
private String description; // allgemeine Instanzvariablen
private int id;
private int quantity;
private int price;
private boolean isZoom; // Instanzvariablen der Klasse Lens
private double focalLength;
}
491
Das Skelett der Klasse Film
class Film {
public Film(...) {...} // Konstructor
public String getDescription() {
return this.description;
}
public int getQuantity() {
return this.quantity;
}
... // weitere fuer Film spezifische Methoden
private String description; // allgemeine Instanzvariablen
private int id;
private int quantity;
private int price;
private int speed; // Instanzvariablen der Klasse Film
private int numberOfExposures;
}
Analog fur Camera . . .
492
Zusammenfassung gemeinsamer Eigenschaften in einerSuperklasse
ÊOffensichtlich unterscheiden sich die Klassen Film, Lens undCamera in bestimmten Instanzvariablen und Methoden.Ê Allerdings haben auch alle drei Klassen einige Verhalten gemeinsam.Ê Hier konnen wir jetzt Vererbung verwenden, und die gemeinsamenEigenschaften in einer Superklasse InventoryItem
zusammenfassen.
493
Die Klasse InventoryItem
class InventoryItem {
public InventoryItem(...) {...} // Konstruktor
public String getDescription() {
return this.description;
}
public int getQuantity() {
return this.quantity;
}
... // weitere allgemeine Methoden von
// InventoryItem
protected String description; // Instanzvariablen
protected int id;
protected int quantity;
protected int price;
}
494
Ableitung der Klassen Lens und Film
Ë Jetzt, da wir alle gemeinsamen Eigenschaften in der KlasseInventoryItem zusammengefasst haben, konnen wir die eigentlichbenotigten Klassen daraus ableiten.Ë Dabei konnen wir uns in der Definition dann auf die spezifischenEigenschaften der Subklassen konzentrieren:
class Film extends InventoryItem {
public Film(...) {...} // Konstructor
... // Fuer Film spezifische Methoden
private int speed; // Instanzvariablen der Klasse Film
private int numberOfExposures;
}
495
Die abgeleitete Klasse Lens
class Lens extends InventoryItem {
public Lens(...) {...} // Konstructor
... // Fuer Lens spezifische Methoden
private boolean isZoom; // Instanzvariablen der Klasse Lens
private double focalLength;
}
496
Ausnutzung des Polymorphismus
Wenn wir jetzt Kollektionen von InventoryItem-Objekten verwalten,konnen wir den Polymorphismus ausnutzen:
InventoryItem[] inv = newInventoryItem[3];
inv[0] = new Lens(...);
inv[1] = new Film(...);
inv[2] = new Camera(...);
for (int i = 0; i < inv.length; i++)
System.out.println(inv[i].getDescription() + ": "
+ inv[i].getQuantity());
497
Abstrakte Methoden und Klassen (Motivation)
Ì Die Klasse InventoryItem haben wir lediglich dafur benutzt, umgemeinsame Eigenschaften ihrer Subklassen zusammenzufassen.Ì Daruber hinaus haben wir in der Schleife
for (int i = 0; i < inv.length; i++)
System.out.println(inv[i].getDescription() + ": "
+ inv[i].getQuantity());
nur auf die Methoden der Klasse InventoryItem zugegriffen.Ì Was aber konnen wir tun, wenn wir in der Schleife eine print-Methodeverwenden wollen, die detailliertere Ausgaben macht (und z.B. auch dieEmpfindlichkeit eines Films ausgibt)?Ì Hier taucht das Problem auf, dass die Nachrichten an dieser Stelle an dieInventoryItem-Objekte inv[i] geschickt werden, so dass dieseKlasse auch eine (eigentlich uberflussige) print-Methode enthaltenmuss (die immer von den Subklassen uberschrieben wird).
498
Das Schlusselwort abstract
Í Java bietet eine einfache Moglichkeit die Definition dieses Problem zuvermeiden.Í Durch das Voranstellen des Schlusselworts abstract an dieKlassendefinition und den Prototypen der Methode wie in
abstract class InventoryItem Î...
abstract void print();
...Ïspezifizieren wir, dass es sich bei der Methode print und damit auchbei der Klasse InventoryItem um eine abstrakte Methodebzw. Klasse handelt.Í Fur abstrakte Methoden mussen die Subklassen entsprechendenCode enthalten.
499
Verwendung abstrakter Klassen
Ð Es konnen keine Objekte erzeugt werden konnen, die Instanzenabstrakter Klassen sind. Folgendes Statement fuhrt daher zu einemFehler:
InventoryItem inv = new InventoryItem(...)Ð Allerdings konnen wir Referenzvariablen fur abstrakte Klassendeklarieren und diesen eine Referenz auf Objekte von Subklassenzuweisen:
InventoryItem inv = new Lens(...)Ð Daruber hinaus konnen wir Objekten von Subklassen abstrakterKlassen eine abstrakte Nachricht senden:
InventoryItem inv[];
...
for (i = 0; i < inv.length; i++)
inv[i].print();500
Interfaces: Gleiches Verhalten verschiedener Klassen
Ñ Die Vererbung stellt bereits ein machtiges Konzept fur die Beschreibunggleichen Verhaltens dar.ÑIn dem oben angegebenen Inventarisierungsbeispiel macht diegemeinsame Superklasse auch deshalb Sinn, weil zwischen demInventoryItem und seinen Subklassen eine Is-a-Beziehung besteht.Ñ Was aber konnen wir tun, wenn wir generische Methoden spezifizierenwollen, die wir auf beliebigen Objekten ausfuhren mochten.Ñ Ein Beispiel sind Enumerations, die jeweils das nachste Objekt(unabhangig von seiner Klasse) aus einer Kollektion liefern.Ñ Dieses Problem taucht auch beim Sortieren auf: Wie konnen wir Vektorenbeliebiger Objekte sortieren kann?Ñ Um zu vermeiden, dass nicht zusammenhangende Objekte zu diesemZweck unter einer (abstrakten) Superklasse zusammengefasst werdenmussen, stellt Java so genannte Interfaces zur Verfugung.
501
Interfaces und ihre Anwendung
Ò Interfaces dienen dazu, ein Verhalten zu spezifizieren, das von Klassenimplementiert werden soll:
interface Enumeration {
Object nextElement();
boolean hasMoreElements();
}Ò Durch diese Definition wird festgelegt, welche Methoden eine Klasse, dieein solches Interface besitzt, realisieren muss.Ò In der Klassendefinition kennzeichnet man die Interfaces mit demSchlusselwort implements:
class VectorEnumeration implements Enumeration {
...
Object nextElement() {...};
boolean hasMoreElements() {...};
} 502
Interfaces und die Klassenhierarchie
Ó Interfaces stellen keine Klassen dar.Ó Sie erben nichts und sie vererben nichts.Ó Klassen konnen Interfaces auch nicht erweitern.Ó Klassen konnen Interfaces lediglich implementieren.Ó Daher konnen Klassen gleichzeitig Interfaces realisieren und Subklassensein:
class ACSStudent extends Student implements Comparable {...}Ó Wenn eine Klasse ein Interface implementiert, kann sie behandeltwerden, als ware sie eine Subklasse des Interfaces.
503
Beispiel: Das Interface Comparable
”Vergleichbare“ Objekte sollen die Methoden lessThan und equal zur
Verfugung stellen:
interface Comparable{
boolean lessThan(Comparable comp);
boolean equal(Comparable comp);
}
504
Verwendung von Comparable: Die Klasse Student
class Student implements Comparable {public Student(String firstName, String name, int id) {
this.name = name;this.firstName = firstName;this.id = id;
}public boolean lessThan(Comparable comp){
Student s1 = (Student) comp;int val;if ((val = this.name.compareTo(s1.name)) == 0)
return this.firstName.compareTo(s1.firstName) < 0;else
return val < 0;}public boolean equal(Comparable comp){
return this.id == ((Student) comp).id;}public String toString(){
return firstName + " " + name;}
private String name;private String firstName;private int id;
}
505
Eine weitere Klasse, die Comparable implementiert
class SortableInteger implements Comparable{SortableInteger(int val){
this.val = val;}public int intValue(){
return this.val;}public boolean lessThan(Comparable comp){
return this.val < ((SortableInteger) comp).val;}public boolean equal(Comparable comp){
return ((SortableInteger) comp).val == this.val;}public String toString(){
return new String(""+this.val);}private int val;
}
506
Eine generische Methode getSmallest
static int getSmallest(Vector v, int k) {
if (v==null || v.size()<=k)
return -1;
int i = k+1;
int small = k;
Comparable smallest = (Comparable) v.elementAt(small);
while (i != v.size()) {
Comparable current = (Comparable) v.elementAt(i);
if (current.lessThan(smallest)){
small = i;
smallest = (Comparable) v.elementAt(i);
}
i++;
}
return small;
}
507
Anwendung der Generischen Sortiermethode
Wenn wir jetzt die getSmallest-Methode in unserer Sortiermethodeverwenden, konnen wir beliebige Objekte sortieren, sofern sie dasComparable-Interface implementieren.
Vector v1 = new Vector();
Vector v2 = new Vector();
Vector v3 = new Vector();
v1.addElement(new Student("Albert", "Ludwig", 0));
v1.addElement(new Student("Dirk", "Becker", 10101));
v1.addElement(new Student("Dieter", "Becker", 10102));
v2.addElement(new SortableInteger(1));
v2.addElement(new SortableInteger(3));
v2.addElement(new SortableInteger(2));
sort(v1);
sort(v2);
System.out.println(v1.toString());
System.out.println(v2.toString());
Liefert:
[Dieter Becker, Dirk Becker, Albert Ludwig]
[1, 2, 3] 508
Zusammenfassung (1)
Ô Vererbung ist ein Mechanismus um neue Klassen aus bestehendenKlassen abzuleiten.Ô Vererbung stellt eine Art is-a-Beziehung zwischen der Sub- und derSuperklasse her.Ô Dabei erben die neuen Subklassen alle Instanzvariablen undMethoden von der SuperklasseÔ Daruber hinaus konnen Methoden in einer Subklasse uberschriebenwerden (Overriding).Ô Vererbung kann aber auch dazu genutzt werden um gemeinsamesVerhalten von Klassen in einer gemeinsamen Superklasse zuimplementieren.
509
Zusammenfassung (2)
Õ Ist eine Referenzvariable fur ein Superklassenobjekt deklariert aber anein Subklassenobjekt gebunden und wird dieser Variablen ein Nachrichtgeschickt, so wird zunachst beidem Subklassenobjekt nach dieserMethode gesucht. Dies nennt man Polymorphismus.Õ Abstrakte Methoden und Klassen wiederum befreien denProgrammierer davon, Methoden in der Superklasse zu definieren, dienur in Subklassen benotigt werden.Õ Sind alle Methoden in einer Klasse abstrakt, verwendet manublicherweise Interfaces.Õ Wahrend in Java jede Klasse nur eine Superklasse haben kann (SingleInheritance), kann eine Klasse mehrere Interfaces implementieren.Õ Mithilfe von Interfaces konnen generische Methoden realisiert werden,die auf einer Vielzahl verschiedener Objekte operieren (Enumerations,Sortieren, . . . ).
510
Und es gibt auch ein paar Grenzen . . .
Ö Allerdings sind diese Mechanismen in Java beschrankt:
– Keine Mehrfachvererbung.
– Eingebaute wie Klassen wie Integer konnen nicht weiter abgeleitetwerden.
– Dadurch konnen mit generischen Methoden keine Integer-Objektesortiert werden.
– . . .
511
Das Problem der Mehrfachvererbung
1. Welche Methode wird geerbt, wenn beide Superklassen die gleicheMethode implementieren?
2. Welcher Wert einer Instanzvariable wird verwendet, wenn Variablen mitgleichem Namen in beiden Superklassen definiert sind?
}
int f(...){...}
int i;
class B extends A {
}
int f(...){...}
int i;
class C extends A {
class A {
}
int f(...){...}
int i;
class D extends B extends C {
}
512
Einfuhrung in die Informatik
Reference Variables
Einfach und doppelt verkettete Listen, Baume
Wolfram Burgard
513
Einleitung
× Variablen enthalten Referenzen auf Objekte.× Bei der Komposition von Objekten haben wir dies ausgenutzt und inInstanzvariablen Referenzen auf Objekte gespeichert.× Dabei waren die Instanzvariablen immer Referenzen auf Objekte andererKlassen.× In diesem Kapitel werden wir den speziellen Fall betrachten, dass eineInstanzvariable ein Objekt derselben Klasse referenziert.× Durch diesen Mechanismus lassen sich Kollektionen definieren, diedynamisch (d.h. zur Laufzeit) mit der Anzahl der zu reprasentierendenObjekte wachsen konnen.
514
Referenzen
Ø Eine Referenz ist ein Verweis auf den Ort, wo sich der Wert oder dasObjekt befindet.Ø Auf der Maschinenebene ist die Referenz eine Speicheradresse, ander der zugehorige Wert abgelegt ist.Ø Variablen, deren Wert eine Referenz auf ein Objekt ist, heißenReferenzvariablen.Ø Der spezielle Wert null symbolisiert dabei, dass die Variable auf keinegultige Speicheradresse verweist.Ø Referenzvariablen werden auch als Zeigervariablen, Zeiger oderPointer bezeichnet.
515
Komposition und Ketten von Referenzen
Ù Bei der Konstruktion von Klassen mittels Komposition haben wirInstanzvariablen verwendet, die Referenzvariablen waren.Ù Dadurch konnen wir im Prinzip ganze Ketten von Referenzen definieren.
myCar Car-Object
...
Engine-Object
Engine eng; Cylinder [] cyl;...
...... ...
516
Listen
Ú Dabei waren die referenzierten Objekte stets Instanzen anderer Klassen.Ú Listen lassen sich dadurch konstruieren, dass die Klasse eineInstanzvariable enthalt, die auf Objekte derselben Klasse referenziert.Ú Das Ende der Liste wird durch den Wert null markiert.
class Node {
...
private Node nextNode;
}
Node nextNode;...
...Node nextNode;...
...Node nextNode;...
...
Node-Object
...
Node-Object Node-Object
517
Inhalt von Listenelementen
Û Um mit Listen Kollektionen zu realisieren, muss man in jedem Knoteneinen Inhalt ablegen.Û Dies geschieht am allgemeinsten dadurch, dass man in jedem Knoteneine Referenz auf ein Object-Objekt in einer Instanzvariablen ablegt.
class Node {
...
private Object content;
private Node nextNode;
}
Node nextNode;
Object content;
Node nextNode;
Object content;
Node nextNode;
Object content;
Node−Object
...
Node−Object Node−Object
Object 1 Object 2 Object 3
518
Listen und Knoten
Ü Es ist oft zweckmaßig, fur Listen eine separate Klassen zu realisieren.Ü In unserem Fall fuhren wir daher zusatzlich die KlasseSingleLinkedList fur wie oben beschriebene, einfach verketteteListen ein.Ü Diese enthalten neben den ublichen Methoden fur eine Liste auch dieReferenz auf das erste Objekt, den so genannten Kopf der Liste.
public class SingleLinkedList {
...
private Node head;
}
519
Methoden fur Knoten
Knoten sollen die folgenden Methoden liefern:
1. Inhalt setzen,
2. Inhalt lesen,
3. den nachsten Knoten erhalten,
4. die Referenz auf den nachsten Knoten setzen sowie
5. die Methode toString.
520
Die Prototypen der Methoden fur die Klasse Node
public Node(Object o, Node n);
public void setContent(Object o);
public Object getContent();
public Node getNextNode();
public void setNextNode(Node n);
public String toString();
521
Implementierung der Methoden fur Node (1)
Ý Der Konstruktor erzeugt ein Node-Objekt und setzt eine Referenz auf einObject-Objekt.Ý Gleichzeitig wird die Referenz auf das Nachfolgeelement gesetzt.
public Node (Object o, Node n) {
this.content = o;
this.nextNode = n;
}
522
Implementierung der Methoden fur Node (2)
Þ Die Methode getContent liefert das im Knoten abgelegt Objekt.
public Object getContent() {
return this.content:
}Þ Mithilfe der Methode setContent kann der Inhalt eines Knotens gesetztwerden:
public void setContent(Object o) {
this.content = o;
}
523
Implementierung der Methoden fur Node (3)
ß Die Methoden getNextNode liefert als Ergebnis den Inhalt derInstanzvariablen nextNode, d.h. die im Knoten gespeicherte Referenzauf das Nachfolgeelement.
public Node getNextNode() {
return this.nextNode;
}ß Mit setNextNode kann diese Referenz gesetzt werden:
public void setNextNode(Node n) {
this.nextNode = n;
}ßDie Methode toString:
public String toString() {
return this.content.toString();
}
524
Die komplette Klasse Node
class Node {
public Node (Object o, Node n) {this.content = o;this.nextNode = n;
}
public Object getContent() {return this.content;
}
public void setContent(Object o) {this.content = o;
}
public Node getNextNode() {return this.nextNode;
}
public void setNextNode(Node n) {this.nextNode = n;
}
public String toString() {return this.content.toString();
}
Object content;Node nextNode;
}
525
Methoden fur Listen
Ahnlich wie fur andere Kollektionen auch sollen Listen die folgendenMethoden zur Verfugung stellen:
1. Test, ob die Liste leer ist,
2. Einfugen eines neuen Listenelementes am Anfang,
3. Einfugen eines neuen Listenelementes am Ende,
4. Einfugen eines neuen Listenelementes an einer beliebigen Stelle in derListe,
5. Loschen eines Listenelementes,
6. Suchen eines Knotens, der ein gegebenes Objekt enthalt,
7. Invertieren der Reihenfolge der Listenelemente und
8. Aufzahlen aller in einer Liste enthaltenen Objekte.526
Die Prototypen der Methoden der KlasseSingleLinkedList
public SingleLinkedList();
public boolean isEmpty();
public void insertHead(Object o);
public void insertTail(Object o);
public void insertAfterNode(Object o, Node node);
privat void removeNextNode(Node node);
public void removeFirstNode();
public Node searchNode(Object o)
public void reverseList();
public String toString();
527
Der Konstruktor und die Methode isEmpty
Der Konstruktor erzeugt eine leere Liste:
public SingleLinkedList() {
this.head = null;
}
Die Methode isEmpty liefert genau dann true, wenn der Kopf der Listeden Wert null hat:
public boolean isEmpty() {
return (this.head == null);
}
528
Einfugen eines neuen Elementes am Anfang der Liste
Hierbei mussen wir
1. einen neuen Knoten erzeugen,
2. die Referenzvariable fur den Listenkopf auf diesen Knoten setzen und
3. in diesem Knoten nextNode auf das zuvor erste Element setzen.
head
SigleLinkedList
...
node 1
new node
o
head
SigleLinkedList
...
node 1
public void insertFirst(Object o) {
this.head = new Node(o, this.head);
}529
Einfugen eines neuen Elementes am Ende der Liste
Um ein neues Element am Ende einzufugen, mussen wir
1. einen neuen Knoten erzeugen,
2. zum Ende der Liste laufen und das neue Element anfugen und
3. den Spezialfall beachten, dass die Liste leer sein konnte.
...
node n
...
node n
new node
o
530
Die Methode insertLast
public void insertLast(Object o) {
if (this.isEmpty()) { /* list is empty */
this.insertFirst(o);
}
else {
Node tmp = this.head;
while (tmp.getNextNode() != null)
tmp = tmp.getNextNode();
tmp.setNextNode(new Node(o, null));
}
}
531
Einfugen eines neuen Knotens nach einem Knoten
1. Nachfolgereferenz des neuen Knotens auf den Nachfolgeknoten desaktuellen Knotens setzen
2. Nachfolgereferenz des aktuellen Knotens auf den neuen Knoten setzen.
n
new Node(o, n.getNextNode())
......
n.getNextNode()
public void insertAfterNode(Object o, Node n) {
n.setNextNode(new Node(o, n.getNextNode()));
}532
Loschen eines Listenelementes
Hierbei unterscheiden wir zwei Falle:
1. Loschen des ersten Elementes.
2. Loschen des Nachfolgeelementes eines gegebenen Elementes.
Dabei mussen wir uns nicht um die nicht mehr referenzierten Node-Objektekummern. Die Freigabe des Speichers ubernimmt das Java-System mitseiner automatischen Garbage Collection.
533
Loschen des ersten Elementes einer Liste
à Wenn wir das erste Element einer Liste loschen, genugt es, den Wert derInstanzvariablen head auf das zweite Listenelement zu setzen.à Allerdings darf der Wert von head nicht null sein.
head
SigleLinkedList
...
head
SigleLinkedList
...
node 1
node 2node 2
node 1
void removeFirstNode() {
if (!this.isEmpty())
this.head = this.head.getNextNode();
}
534
Der andere Fall: removeNextNode
á Wir konnen ein Listenelement nur dann Loschen, wenn wir auch denVorganger kennen, da wir dort die Referenzvariable nextNode auf denNachfolger des Nachfolgeknotens setzen mussen:
node.setNextNode(node.getNextNode().getNextNode());á Allerdings geht dies nur, wenn das Listenelement, nach dem wir loschenwollen, nicht das letzte Element der Liste ist.
535
Die Methode removeNextNode
n
......
n
......
n.getNextNode()
n.getNextNode().getNextNode()
private void removeNextNode(Node n) {
if (n.getNextNode() != null)
n.setNextNode(n.getNextNode().getNextNode());
}
536
Suchen eines Listenelementes mit einem bestimmten Inhalt
â Durchlaufen der Liste.â Ruckgabe der Referenz auf den Knoten, der das gesuchte Objekt enthalt.
public Node searchNode(Object o) {
Node n = this.head;
while (n != null && !n.getContent().equals(o))
n = n.getNextNode();
return n;
}
537
Invertieren einer Listeã Da wir einfach verkettete Listen immer nur in einer Richtungdurchlaufen konnen, ware eine Invertierung durch Vertauschen derElemente (wie wir das bei Vektoren realisiert haben) zu aufwendig.ã Daher konnen wir Listen effizient nur dadurch invertieren, dass wir dieReferenzen in den Listenelementen geeignet umsetzen.ã Im Prinzip muss die Nachfolgereferenz in einem Knoten nur so gesetztwerden, dass sie das Vorgangerelement referenziert.ã Um dies zu realisieren benotigen wir daher zwei Referenzvariablen: Eine,die fur den Anfang des noch zu invertierenden Restes der Liste undeine fur das ehemalige Vorgangerelement, d.h. den Anfang des bereitsinvertierten Teils der Liste.ã Beim letzten Listenelement sind wir fertig.ã In diesem Fall muss die Referenzvariable head auf den zuletztbesuchten Knoten gesetzt werden.
538
Das Verfahren zum Invertieren einer Liste
node
node node.getNextNode()
prev
prev
539
Eine rekursive Implementierung
private void reverseRecursive(Node node, Node prev){
Node next = node.getNextNode();
if (next == null)
this.head = node;
else {
reverseRecursive(next, node);
}
node.setNextNode(prev);
}
public void reverseList() {
if (!this.isEmpty())
reverseRecursive(this.head, null);
}
540
Die Methode toString
ä Um die Methode toString zu realsieren, mussen wir einmal die Listedurchlaufen.äDabei mussen wir ein entsprechendes String-Objekt zusammensetzen.
public String toString() {
String str = "[";
Node tmp = this.head;
while (tmp != null) {
str += tmp.getContent().toString();
tmp = tmp.getNextNode();
if (tmp != null)
str += ", ";
}
return str+"]";
}
541
Ein Enumeration-Interface fur Listen
å Um Programmieren eine komfortable Moglichkeit zur Verfugung zustellen, Listendurchlaufe zu realisieren, stellen wir einEnumeration-Interface zur Verfugung.å Da wir ggf. mehrere Enumerations unabhangig voneinander nutzenwollen, realisieren wir eine Hilfsklasse
class SingleLinkedListEnumeration implements Enumeration {
...
}.
å Fur jede Enumeration verwenden wir ein eigenes Objekt dieserKlasse.å Darin speichern wir den Zustand des Enumeration-Objekteszwischen einzelnen getNextElement-Aufrufen.
542
Das Interface Enumeration
interface Enumeration {
Object nextElement();
boolean hasMoreElements();
}
543
Die Klasse SingleLinkedListEnumeration
æ Zur Speicherung des Zustands einer Enumeration fur Listen ist eineReferenz auf den nachsten zu besuchenden Knoten hinreichend.æ Sie erlaubt den Zugriff auf das Objekt im nachsten Element.æ Falls die Referenz auf das nachste Element den Wert null hat, gibt eskeine weiteren Elemente mehr, da wir am Ende der Liste angelangt sind.
544
Implementierung der KlasseSingleLinkedListEnumeration
class SingleLinkedListEnumeration implements Enumeration {
public SingleLinkedListEnumeration(Node node) {
this.node = node;
}
public boolean hasMoreElements() {
return (this.node != null);
}
public Object nextElement() {
Object o = node.getContent();
this.node = this.node.getNextNode();
return o;
}
private Node node;
}
545
Verwendung der Klasse SingleLinkedListEnumeration
ç Um auf die ubliche Weise ein Enumeration-Objekt zu erzeugen, mussunsere List-Klasse ein Methode elements bereitstellen.ç Eigentlich konnen wir nur ein SingleListEnumeration-Objektzuruckgeben.ç Der Programmierer kennt jedoch nur die Klasse Enumeration, d.h. erwird folgendes hinschreiben wollen:
Enumeration e = list.elements();
while (e.hasMoreElements()) {
Object o = (Object) e.nextElement();
...
}
546
Losung: Erweiterung Is-a-Beziehung auf Interfaces
è Implementiert eine Klasse ein Interface so besteht zwischen denObjekten und dem Interface eine Is-a-Beziehung, d.h. die Klasse wirdbehandelt wie eine Sub-Klasse.è Damit ist folgende Definition innerhalb der Klasse SingleLinkedListzulassig:
public Enumeration elements(){
return new SingleLinkedListEnumeration(this.head);
}
547
Anwendung: Suche eines Objektes in einer Liste
static boolean contains(Object o, SingleLinkedList l) {
Enumeration enum = l.elements();
while (enum.hasMoreElements()) {
Object content = enum.nextElement();
if (content.equals(o))
return true;
}
return false;
}
548
Auswirkung des dynamischen Bindens auf die Suche
é Innerhalb der Suchmethode verwenden wir die Methode equals.é Wegen des dynamische Bindens wird erst zur Laufzeit festgelegt,welche Methode tatsachlich ausgefuhrt wird.
Object i1 = new Interger(1), i2 = new Integer(1);
list.insertLast(i1);
Object o1 = new Object(), o2 = new Object();
list.insertLast(o1);
System.out.println(contains(i2, list);
System.out.println(contains(o2, list);é Wahrend die equals-Methode der Klasse Integer den Inhaltvergleicht, uberpruft Object.equals nur die Referenzen. Daher ist dieSuche im ersten Fall erfolgreich. Im zweiten Fall scheitert sie.
549
Anwendung der Klasse SingleLinkedList
import java.util.Enumeration;
public class SingleLinkedListTest {public static void main(String args[]) {
/* variables */SingleLinkedList list;
/* create list */list = new SingleLinkedList();
/* insert elements */list.insertFirst(new Integer(2));Node n = list.searchNode(new Integer(2));list.insertAfterNode(new Integer(1), n);list.insertLast(new Integer(3));list.insertFirst(new Integer(4));list.insertLast(new Integer(5));System.out.println(list);list.removeFirstNode();System.out.println(list);
/* search for elements */System.out.println(new Integer(1)
+ " in the list: "+ (list.searchNode(new Integer(1)) != null));
System.out.println(new Integer(3)+ " in the list: "+ (list.searchNode(new Integer(3)) != null));
/* remove elements */list.remove(new Integer(1));list.remove(new Integer(5));System.out.println(list);
/* searchNode for elements */System.out.println(new Integer(1)
+ " in the list: "+ (list.searchNode(new Integer(1)) != null));
System.out.println(new Integer(3)+ " in the list: "+ (list.searchNode(new Integer(3)) != null));
/* inserting elements */list.insertFirst(new Integer(2));System.out.println(list);
/* reverse list */list.reverseList();list.reverseList();list.reverseList();System.out.println(list);
/* test the enumaration */System.out.println("testing enumeration:");Enumeration e = list.elements();while (e.hasMoreElements()) {
Object o = (Object) e.nextElement();System.out.println(o.toString());
}
Object i1 = new Integer(1), i2 = new Integer(1);list.insertLast(i1);Object o1 = new Object(), o2 = new Object();list.insertLast(o1);System.out.println(list.searchNode(i2));System.out.println(list.searchNode(o2));System.out.println("DONE");
}}
550
Ausgabe des Programms
insertingFirst 2
insertingAfter 1
insertingLast 3
insertingFirst 4
insertingLast 5
[4, 2, 1, 3, 5]
removing first node
[2, 1, 3, 5]
1 in the list: true
3 in the list: true
removing 1
removing 5
[2, 3]
1 in the list: false
3 in the list: true
insertingFirst 2
[2, 2, 3]
reversing list
reversing list
reversing list
[3, 2, 2]
testing enumeration:
3
2
2
insertingLast 1
insertingLast java.lang.Object@80ab1d8
1
null
DONE
551
Aufwand einiger Listenoperationen
Operation SingleLinkedList Vector
Einfugen am Anfang ê ë ì í ê ë î íEinfugen am Ende ê ë î í ê ë ì í ï ð ñEinfugen an gegebener Stelle ê ë ì í ê ë î íSuchen ê ë î í ê ë î íSuchen in sortierter Kollektion ê ë î í ê ë ò ó ô î íInvertieren ê ë î í ê ë î í
Hinweis zu (a): Nur falls der Vektor noch Platz fur neue Elemente hat, sonstê ë î í .552
Doppelt verkettete Listen
õ Einfach verkettete Listen haben den Nachteil, dass man sie nur ineiner Richtung durchlaufen kann.õ Daruber hinaus kann man ein referenziertes Listenelement nichtunmittelbar loschen, da man von diesem Element keinen Zugriff aufdas Vorgangerelement hat.õ Doppelt verkettete Listen umgehen dieses Problem, indem sie in jedemKnoten zusatzlich noch eine Referenz auf den Vorgangerknotenspeichern.
553
Die Klasse Node fur doppelt verkettete Listen
ö Im Gegensatz zu einfach verketteten Listen haben doppelt verketteteListen in den Knoten eine zusatzliche Instanzvariable fur die Referenzauf den Vorgangerknoten
Node−Object Node−Object Node−Object
Node nextNode; Node nextNode; Node nextNode;Node prevNode; Node prevNode; Node prevNode;
Object content; Object content; Object content;
class Node {
...
private Object content;
private Node nextNode;
private Node prevNode;
}
554
Methoden fur die Klasse Node
÷ Die Methoden fur Knoten in doppelt verketteten Listen sind eine einfacheErweiterung der entsprechenden fur einfach verkettete.÷ Allerdings kommen noch einige Methoden fur das Vorgangerelementhinzu.÷ Der Konstruktor beispielsweise muss nun folgendermaßen realisiertwerden:
class Node {
public Node (Object o, Node prev, Node next) {
this.content = o;
this.nextNode = next;
this.prevNode = prev;
}
... // Rest analog
}
555
Die Klasse DoubleLinkedList
ø Die Klasse DoubleLinkedList hat im wesentlichen dieselbenMethoden wie die Klasse SingleLinkedList.ø Bei der Realisierung der Methoden muss allerdings darauf achten, dassstets auch die Referenz auf den Vorgangerknoten korrekt gesetzt wird.ø Außerdem wollen wir in der Klasse DoubleLinkedList auch eineInstanzvariable tail fur das letzte Listenelement ablegen.
public class DoubleLinkedList {
...
protected Node head;
protected Node tail;
}
556
Beispiel: Die Methode insertHead
SigleLinkedList
SigleLinkedList
... ...
node 1
node 1
head headtail tail onew node
public void insertHead(Object o) {
if (this.isEmpty())
this.head = this.tail = new Node(o, null, null);
else {
this.head.setPreviousNode(new Node(o, null, this.head));
this.head = this.head.getPreviousNode();
}
}
557
Die Methode insertTail
ù Dadurch, dass wir jetzt eine Referenz auf das letzte Element haben,konnen wir wesentlich effizienter am Ende einfugen:ù Diese Operation ist symmetrisch zum Einfugen am Anfang.
public void insertTail(Object o) {
if (this.isEmpty())
this.insertHead(o);
else {
this.tail.setNextNode(new Node(o, this.tail, null));
this.tail = this.tail.getNextNode();
}
}
558
Die Methode removeNode
ú Da wir bei doppelt verketteten Listen das Vorgangerelement und dasNachfolgerelement erreichen konnen, haben wir die Moglichkeit,referenzierte Listenelemente direkt zu loschen.ú Dabei mussen wir jedoch die Falle berucksichtigen, dass sich das zuloschende Element am Anfang oder am Ende der Liste befinden kann.
n
... ... ... ...
n
n.getNextNode()n.getPreviousNode()
559
Die Implementierung der Methode removeNode
public void removeNode(Node n){
Node nextNode = n.getNextNode();
Node prevNode = n.getPreviousNode();
if (this.head == n)
this.head = nextNode;
if (this.tail == n)
this.tail = prevNode;
if (prevNode != null)
prevNode.setNextNode(nextNode);
if (nextNode != null)
nextNode.setPreviousNode(prevNode);
}
560
Die Methoden removeHead und removeTail
û Diese Methoden lassen sich nun sehr leicht realisieren:
public void removeHead(){
this.removeNode(this.head);
}
public void removeTail(){
this.removeNode(this.tail);
}
561
Invertieren einer doppelt verketteten Liste
ü Das Invertieren einer doppelt verketteten Liste ist ebenfalls deutlicheinfacher als bei einer einfach verketteten Liste.ü Wegen der Symmetrie brauchen genugt es, in jedem Element dieVorganger- und Nachfolgerreferenzen zu vertauschen.ü Zusatzlich mussen die Werte von head und tail getauscht werden.
... ... ... ...
562
Implementierung von reverse
public void reverse(){
Node tmp = this.head;
while (tmp != null){
// swap prev and next
Node next = tmp.getNextNode();
tmp.setNextNode(tmp.getPreviousNode());
tmp.setPreviousNode(next);
tmp = next;
}
// swap head and tail
tmp = this.head;
this.head = this.tail;
this.tail = tmp;
}
563
Eine Verbesserte Klasse DoubleLinkedList
ý Im Prinzip sind die Doppelt verketteten Listen vollkommen symmetrisch.ý Wie gesehen mussen wir lediglich die prev und next-Zeiger sowiehead und tail vertauschen, um eine Liste zu invertieren.ý Wenn wir den Status einer Liste, d.h. ob sie invertiert ist oder nicht, in derListe selbst abspeichern und jeder Methode den Status mit ubergeben,konnen die Methoden die entsprechenden Aktionen durchfuhren (je nachStatus).ý Wir werden jetzt eine Variante der doppelt verketteten Liste betrachten,die man in Konstantzeit, d.h. þ ÿ � � invertieren kann.
564
Die Klasse Index
� Um den Status einer Liste zu reprasentieren, fuhren wir eine KlasseIndex ein.
� Wir verwenden dann fur jede Liste genau ein Index-Objekt, um zuspeichern, welche Referenz in einem Knoten das Nachfolger-bzw. Vorgangerelement referenziert.
� Da wir schnell zwischen den beiden Modi hin- und herschalten wollen,mussen wir dieses Index-Objekt ebenfalls in jedem Knotenreferenzieren.
� Gleichzeitig legen wir die beiden Referenzen in den Node-Objekten jetztin einem Array linkedNode der Lange zwei ab.
565
Die modifizierten Klassen Node und DoubleLinkedList
class Node {
...
private Object content;
private Node linkedNode[]; // replaces next and prev
private Index index;
}
public class DoubleLinkedList {
...
private Node head;
private Node tail;
private Index index;
}
566
Die Klasse Index
� Die Klasse Index speichert, welche Referenz in linkedNode denVorganger und welche den Nachfolger referenziert.
� In unserem Fall verwenden wir dafur zwei Instanzvariablen:
class Index {
...
private int predIndex;
private int nextIndex;
}
� Im Normalfall hat predIndex den Wert 0 und nextIndex den Wert 1.
� Ist die Liste invertiert, sind beide Werte vertauscht.
� Der Vorganger kann somit linkedNode[p] erreicht werden, wobei pder aktuelle Wert von index.predIndex ist.
567
Die resultierende Struktur der Listen
� Beim Erzeugen der Liste generieren wir ein Index-Objekt.
� Jeder Knoten referenziert das Index-Objekt der Liste.
Node head;Node tail;Index index;
Doubled-linked-list-object
Index index;Node linkedNode;Object content;
Node-object
Index index;Node linkedNode;Object content;
Node-object
Index-object
int nextIndex;int predIndex;
... ...
568
Implementierung der Klasse Index (1)
� Die wichtigsten Methoden sind der Konstruktor sowie die Methode zumInvertieren der Reihenfolgen.
� Da die Instanzvariablen private deklariert sind, mussen wir zusatzlichnoch Methoden definieren, welche die Werte dieser Variablenzuruckliefern.
569
Implementierung der Klasse Index (2)
class Index {
public Index() {
this.prevIndex = 0;
this.nextIndex = 1;
}
public int prev() {
return this.prevIndex;
}
public int next() {
return this.nextIndex;
}
public void toggle() { // swap indexing
this.prevIndex = this.nextIndex;
this.nextIndex = 1 - this.nextIndex;
}
private int prevIndex;
private int nextIndex;
}
570
Entsprechend modifizierte Methoden der Klasse Node
public Node(Object o, Node p, Node n, Index index) {
this.linkedNode = new Node[2];
this.setIndex(index);
this.setContent(o);
this.setNextNode(p);
this.setPreviousNode(n);
}
public void setNextNode(Node n) {
this.linkedNode[this.index.next()] = n;
}
public void setPreviousNode(Node p) {
this.linkedNode[this.index.prev()] = p;
}
private void setIndex(Index index){
this.index = index;
}
public Node getNextNode() {
return this.linkedNode[this.index.next()];
}
...571
Der Konstruktor der Klasse DoubleLinkedList
� Im Konstruktor mussen wir jetzt zusatzlich dafur sorgen, dass ein neuesIndex-Element angelegt wird.
public DoubleLinkedList() {
this.head = null;
this.tail = null;
this.index = new Index();
}
572
Die modifizierte Methode insertHead
Diese Methode unterscheidet sich von der Originalmethode lediglich darin,dass wir beim Konstruktor fur Node jetzt die Referenz auf dasIndex-Element mit ubergeben:
public void insertHead(Object o) {
if (this.isEmpty())
this.head = this.tail = new Node(o, null, null, this.index);
else {
this.head.setPreviousNode(
new Node(o, null, this.head, this.index));
this.head = this.head.getPreviousNode();
}
}
573
Schnelles Invertieren eines DoubleLinkedList-Objektes
� Dadurch, dass die Werte der Instanzvariablen des Index-Objektesangeben, welche Referenz auf den Nachfolger bzw. Vorganger zeigt, wirddas Invertieren einer Liste deutlich einfacher.
� Zusatzlich zum Aufruf der Methode toggle des Index-Objektesmussen wir allerdings noch die Werte von head und tail vertauschen.
� Da beide Operationen lediglich Konstantzeit benotigen, erfolgt dasInvertieren der Liste somit in � � .
public void reverse() {
Node tmp = this.head;
this.head = this.tail;
this.tail = tmp;
this.index.toggle();
}
574
Anwendung von reverse: Eine einfache Version voninsertTail
Da reverse unsere Listen in Konstantzeit invertiert, konnen wir die MethodeinsertTail vollstandig auf der Basis der Methode insertHead definieren:
public void insertTail(Object o) {
reverse();
insertHead(o);
reverse();
}
575
Aufwand einiger Listenoperationen im Vergleich
SingleLinkedList: SLL
DoubleLinkedList: DLL
verbesserte DoubleLinkedList: DLL�Vector: V
Operation SLL DLL DLL� V
Einfugen am Anfang � � � � � � � � � � � �Einfugen am Ende � � � � � � � � � � � �Einfugen an gegebener Stelle � � � � � � � � � � � �Suchen � � � � � � � � � � � �Suchen in sortierter Kollektion � � � � � � � � � � � � � � �Invertieren � � � � � � � � � � � �
576
Eine weitere Anwendung von Referenzvariablen:Binarbaume
� Die Knoten von Binarbaumen unterscheiden sich im Prinzip nicht von dervon doppelt verketteten Listen.
� Binarbaume haben im Gegensatz zu Listen jedoch eine Baumstruktur:
Node-Object
Object content;Node left;Node right;
Node-Object
Object content;Node left;Node right;
...
Object content;Node left;Node right;
Node-Object
...
577
Binarbaume
Ein Binarbaum ist entweder
� leer oder
� er besteht aus einem Knoten mit zwei disjunkten linken und rechtenTeilbaumen, die jeweils wieder Binarbaume sind.
578
Durchlauf durch einen Binarbaum
� Wegen der rekursiven Struktur von Binarbaumen eignen sichrekursive Methoden besonders gut, um Durchlaufe auf elegante Weisezu formulieren.
� Ein gangiges Verfahren ist, zunachst den Inhalt auszugeben und danndie Inhalte der linken und rechten Teilbaume zu drucken. DiesesVerfahren heißt Pre-order-Durchlauf durch einen Binarbaum.
class Node {
...
public void preorder(){
System.out.println(this.getContent());
if (this.getLeftNode() != null)
this.getLeftNode().preorder();
if (this.getRightNode() != null)
this.getRightNode().preorder();
}
}579
Zusammenfassung
� Mithilfe von Referenzvariablen lassen sich Kollektionen konstruieren,die sich dynamisch an die Anzahl der gespeicherten Elementeanpassen lassen.
� Wir haben mit einfach verketteten Listen, doppelt verketteten Listenund Binarbaumen drei typische Vertreter solcher Strukturenkennengelernt.
� Dabei haben wir ebenfalls festgestellt, dass sich bestimmte Operationendurch geschickte Modellierungen deutlich beschleunigen lassen.
� Beispielsweise gelingt das Invertieren einer doppelt verketteten Listebei geeigneter Darstellung in Konstantzeit, d.h. � � � � .
� Binarbaume sind rekursive definierte Strukturen.
� Fur Binarbaume lassen sich mit rekursiven Methoden sehr elegantDurchlaufe realisieren.
580
Einfuhrung in die Informatik
Turing Machines
Eine abstrakte Maschinezur Prazisierung des Algorithmenbegriffs
Wolfram Burgard
581
Motivation und Einleitung
� Bisher haben wir verschiedene Programmiersprachen zurFormulierung von Algorithmen kennengelernt.
� Dabei haben wir uns keine Gedanken gemacht uber die Frage, ob dieseSprachen eigentlich aquivalent sind oder ob eine Spracheggf. machtiger ist als eine andere.
� Einen entscheidenden Beitrag zur Beantwortung dieser Frage hat AlanTuring geliefert.
� Mit seiner Turing-Maschine hat er ein mathematisch einfaches Modellfur Algorithmen entwickelt.
� Tatsachlich hat sich gezeigt, dass alle gangigen Programmiersprachengenau das konnen, was eine Turing-Maschine kann, so dass dieMenge der durch Maschinen berechenbaren Funktionen genau mit dendurch Turing-Maschinen berechenbaren zusammenfallen.
582
Motivation der Turing-Maschine
� Bei der Definition der Turing-Maschine ging Turing davon aus, wieMenschen eine systematische Berechnung (etwa eine Addition)durchfuhren.
� Der Mensch verwendet dafur ein Rechenblatt, auf dem die Rechnungeinschließlich aller Rechenergebnisse notiert wird.
� Fur das Aufschreiben verwendet er Bleistift und ggf. Radiergummi, umZeichen zu notieren bzw. wieder zu loschen.
� Die jeweilige Aktion hangt nur von endlich vielen Zeichen ab, die sichauf dem Rechenblatt befinden.
� Die Berechnung selbst wird gesteuert durch eine endlicheBerechnungsvorschrift.
583
Bestandteile der Turing-Maschine
� Eine Turing-Maschine ist eine einfache Maschine.
� Sie besteht aus:
– einem potentiell unendlichen Band, welches in Felder eingeteilt istund pro Feld genau ein Zeichen aufnehmen kann,
– einem Schreib-Lesekopf sowie
– einem internen Zustand.
� Je nach Zustand und Inhalt des Bandes kann die Turing-Maschinefolgende Aktionen ausfuhren:
– ein neues Zeichen Schreiben,
– den Schreib-Lesekopf um eine Position nach rechts oder linksbewegen und
– den internen Zustand verandern.584
Aufbau der Turing-Maschine
Schreib−LesekopfBand
Kontrolleinheit
...... 1 1 1 111 0 0 0 0
ProgrammZustand
z
585
Definition der Turing-Maschine
Eine Turingmaschine ist gegeben durch
eine endliche Zustandsmenge ! ,
eine endliche Menge von Eingabezeichen " ,
eine Menge von zulassigen Bandzeichen # $ " ,
den Startzustand % & ' ! ,
das Leerzeichen ( ' # " ,
den Endzustand % ) ' ! sowie
die Transitionstabelle * + ! , # , # , - . / 0 / 1 2 , ! .
Insgesamt benotigen wir also ein 7-Tupel der Form 3 ! / " / # / % & / ( / % ) / * 4 .
586
Bedeutung der Transitionstabelle
Die Transitionstabelle einer Turingmaschine besteht aus 5-Tupeln der Form
5 6 7 8 9 : 8 9 ; 8 < 8 6 = > 8
wobei
? 6 7der aktuelle Zustand der Maschine,
? 9 :das Zeichen unter dem Schreib-Lesekopf
? <die auszufuhrende Aktion @ , A oder B (links, rechts, noop),
? 9 ;das vorher zu druckende Zeichen, und
? 6 =der Nachfolgezustand ist.
587
Ein Beispielprogramm
Gegeben sei folgende Maschine:
C D E F G H I G J K,
C L E F M I N K ,
C O E F M I N I P KC sowie die Transitionstabelle Q E
G H M N R G HG H N M R G HG H P P S G J
588
Wirkung dieses Programms
T Im Folgenden gehen wir immer davon aus, dass der Schreib-Lesekopfzu Beginn stets links auf dem ersten Zeichen der Eingabe steht undsich im Startzustand U V befindet.
T Wenn dieses Programm gestartet wird,wandert die Maschine nach rechtsuber die Eingabe und dreht alle Zeichen um.
T D.h. fur jedes W wird ein X und fur jedes X wird ein W gedruckt.
T Sobald die Maschine am Ende der Eingabe angekommen ist, stoppt sie.
589
Ein weiteres Beispiel: Inkrementieren einer Binarzahl
Um zu einer Binarzahl eins zu addieren, gehe folgendermaßen vor:
1. Gehe zur letzten Ziffer der Zahl und starte mit einem Ubertrag von 1.
2. Steht an der aktuellen Position eine 0 und ist der Ubertrag 1, drucke eine1, setzen den Ubertrag auf 0 und gehe nach links.
3. Steht an der aktuellen Position eine 1 und ist der Ubertrag 1, drucke eine0, setze den Ubertrag auf 1 und gehen nach links.
4. Steht an der aktuellen Position ein Leerzeichen und ist der Ubertrag 1,drucke eine 1 und halte an.
5. Ist der Ubertrag 0 und steht an der aktuellen Position eine 0 oder 1 sodrucke dasselbe Zeichen und gehe nach links.
6. Ist der Ubertrag 0 und steht an der aktuellen Position ein Leerzeichen, sogehe nach rechts.
Am Ende steht die Maschine dann auf dem ersten Zeichen des Ergebnisses.590
Die Transitionstabelle
Startzustand: Y , Endzustand: Z Z , Leerzeichen [\ ] ^ _ ^ ` a \ bY Y Y c Y gehe nach rechts
Y d d c YY [ [ e dd Y d e f Ubertrag berechnen
d d Y e dd [ d g Z Zf Y Y e f gehe nach links
f d d e ff [ [ c Z Z
591
Anwendungsbeispiel (1)
592
Anwendungsbeispiel (2)
593
Ein komplexeres Beispiel: Addition von Binarzahlen
# Addition of two binary numbers
#
# States:
# 0 Startstate
# 88 Error
# 99 Success
#
0 1 1 n 12
0 0 0 n 12
0 * * n 88
#
# decrementer
#
1 1 1 r 1
1 0 0 r 1
1 * * l 2
#
2 0 1 l 2
2 1 0 n 3
2 * * n 99
#
3 1 1 l 3
3 0 0 l 3
3 * * n 4
#
4 * * l 4
4 1 1 n 11
4 0 0 n 11
#
# incrementer
#
11 1 0 l 11
11 * 1 r 12
11 0 1 r 12
#
12 1 1 r 12
12 0 0 r 12
12 * * n 13
#
13 * * r 13
13 0 0 n 1
13 1 1 n 1
594
Anwendung des Addierers (Input)
595
Anwendungsbeispiel (Output)
596
Anmerkungen zum Java Applet
h Das Applet zum Simulieren von Turing-Maschinen wurde komplett in Javaimplementiert.
h Dabei wurden das Java2 Software-Development-Kit in der Version 1.3.1verwendet.
h Die Graphische Oberflache wurde mit Netbeans-IDE (Version 3.1)entwickelt.
h Nahere Infos zum Applet (Download, Installation etc.) sind unterhttp://ais.informatik.uni-freiburg.de/turing-applet/ zu finden.
597
Analogie zwischen manueller Berechnung und Berechnungdurch die Turing-Maschine
i Offensichtlich stellt die Turing-Maschine eine Vereinfachung dar.
i Das zweidimensionale Rechenblatt wird ersetzt durch eineindimensionales Band.
i Bleistift und Radiergummi werden durch den Schreib-Lesekopf ersetzt.
i Eine Berechnung wird nun dadurch ausgefuhrt, dass man den Inhalt desBandes als Eingabe auffasst. Die Ausgabe kann man dann von demBand ablesen, sobald die Maschine ihren Endzustand erreicht hat.
598
Turing-Berechenbarkeit
j Im Laufe dieser Vorlesung haben wir drei verschiedene Konzepte zurBeschreibung von Algorithmen kennengelernt.
j Dies waren Java-, Scheme- und Turing-Maschinen-Programme.
j Alle diese Programme berechneten (ebenso wie Algorithmen)Funktionen, d.h. ermitteln fur eine gegebene Eingabe in eineentsprechende Ausgabe.
j Es stellt sich nun die Frage nach der Machtigkeit dieserBeschreibungsmoglichkeiten. Oder anders ausgedruckt: Kann ich miteinem Verfahren mehr berechnen als mit einem anderen?
j Bis heute hat man noch kein Beispiel fur eine berechenbare Funktiongefunden, die nicht durch eine Turing-Maschine berechenbar ist.
j Daher ist man heute davon uberzeugt, dass eine Funktion, die nicht durcheine Turing-Maschine berechenbar ist, uberhaupt nicht berechenbar ist.
599
Die Churchsche These
Die Uberzeugung, dass alles was berechenbar ist, durch Turing-Maschinenbeschreibbar ist, fasst man unter dem Namen Churchsche Thesezusammen:
Jede im intuitiven Sinn berechenbare Funktion ist Turing-Maschinenberechenbar.
Damit gilt, dass alle berechenbaren Funktionen genau durch den BegriffAlgorithmus charakterisiert werden.
600
Auswirkung auf Java
k Oben haben wir gesehen, dass alles, was berechenbar ist, durchTuring-Maschinen berechnet werden kann.
k Andererseits haben wir aber ein Java-Programm gesehen, welchesbeliebige Turing-Maschinen-Programme ausfuhren kann.
k Dies war unser Applet, welches ein Turing-Maschinen-Programm einliestund auf eine beliebige Eingabe anwendet.
k Damit ist Java mindestens so machtig wie Turing-Maschinen.
k Da Turing-Maschinen aber bereits das, was berechenbar istcharakterisieren, ist Java berechnungs-universell, d.h. wir konnendamit genau die berechenbaren Funktionen programmieren.
k Dies gilt ubrigens auch fur andere Programmiersprachen, wiez.B. Pascal, Lisp, Scheme, Prolog, Simula, Fortran, . . .
601
Nicht-Determinismus und Turing-Maschinen
l An Turing-Maschinen lasst sich der Begriff des Nicht-Determinisumssehr gut deutlich machen.
l Wir haben namlich nicht festgelegt, dass es fur jede Kombination vonaktuellem Zustand und Bandsymbol nur genau einen Eintrag in derProgrammtabelle geben darf.
l Tatsachlich sind mehrere Aktionen fur einen Zustand und einBandsymbol moglich.
l In diesem Fall wird die Turingmaschine nicht-deterministisch, denn siekann einen beliebigen, passenden Eintrag aus der Tabelle verwenden.
l Eine nicht-deterministische Turing-Maschine kann somit eine Losungzufallig schnell ermitteln.
l Allerdings kann man mit nicht-deterministischen Turing-Maschinennicht mehr Probleme losen als mit deterministischen.
602
Zusammenfassung
m Turing-Maschinen stellen eine einfache, abstrakte Art von Maschinendar.
m Sie wurden entwickelt, um den Begriff des algorithmischBerechenbaren zu charakterisieren.
m Trotz ihrer einfachen Konstruktion sind Turing-Maschinenberechnungs-universell, d.h. sie konnen jede im intuitiven Sinnberechenbare Funktion berechnen.
m Dies wir durch die Churchsche These untermauert.
m Da man in Java beliebige Turing-Maschinen programmieren undsimulieren kann, ist auch Java berechnungsuniversell.
m Damit konnen wir prinzipiell jeden Algorithmus in Javaprogrammieren.
603