Einfuhrung in die Informatik: Der...

1
Einf ¨ uhrung in die Informatik: Der Java-Teil

Transcript of Einfuhrung in die Informatik: Der...

Page 1: Einfuhrung in die Informatik: Der Java-Teil¨ais.informatik.uni-freiburg.de/teaching/ws01/info1/material/onepage.pdf · Einfuhrung in die Informatik: Der Java-Teil¨ Einf¨uhrung

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: [email protected]: 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: [email protected]: 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="[email protected]">

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