• Keine Ergebnisse gefunden

5. Objektorientiertes Programmieren

N/A
N/A
Protected

Academic year: 2022

Aktie "5. Objektorientiertes Programmieren"

Copied!
103
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

• Objekte, Klassen, Kapselung

• Subtypen und Vererbung

• Objektorientierte Bausteine und Bibliotheken

• Entwurf und Realisierung objektorientierter Programme

5. Objektorientiertes

Programmieren

(2)

5. Objektorientiertes Programmieren

5.1 Objektorientierte Modellierung von SW-Systemen 5.2 Objekte, Klassen, Kapselung

5.3 Subtypen und Vererbung

5.4 Objektorientierte Bausteine und Bibliotheken

Übersicht über Kapitel 5:

(3)

5.1 Objektorientierte Modellierung von Softwaresystemen

,,The basic philosophy underlying object-oriented programming is

to make the programs as far as possible reflect that part of the reality they are going to treat.

It is then often easier to understand and to get an overview of what is described in programs.

The reason is that human beings from the outset are used to and trained in the perception of what is going on in the real world.

The closer it is possible to use this way of thinking in programming, the easier it is to write and

understand programs.“

aus:

O. Lehrmann Madsen, B. Moller-Petersen, K. Nygaard:

Object-oriented Programming in the BETA

Programming Language, Addison-Wesley, 1993.

(4)

5.1.1 Objektorientiertes Paradigma:

Das Paradigma (vgl. 2.2.5) der Objektorientierung bezieht seine konzeptionellen Grundlagen aus der realen Welt:

Für den Menschen besteht

- die physische/materielle Umgebung und - die gedankliche/geistige Welt

aus logisch zusammengehörigen Objekten mit - eigenständiger Identität

- einem Zustand, der sich mit der Zeit ändern kann, - der Möglichkeit auf sie einwirken zu können bzw. der Fähigkeit zu kommunizieren und zu kooperieren.

(5)

Beispiele: (zum Objektbegriff)

Physische Objekte:

- Stühle, Tische, DVD-Spieler, Computer, - Autos, Staudämme,

- Häuser, Städte, Länder, Flüsse, Atmosphäre, - Personen, Tiere, Pflanzen, Augen, Organe, - Bücher, Bibliotheken, Läden

Gedankliche Objekte und Werte:

- Bücher, Vorlesungen, Prüfungen, - Reisen, Ruderregatten, Kriege, - Gesetze, Regeln, Pläne,

- Konten, Börsenkurse, Optionsscheine - Sprachen, Völker, Religionen (?)

- Prozedur, Variable, Programme,

- Web-Server, Telefonnetze, GUI-Fenster, - Funktionen, Mengen, Zahlen, Kreise.

Begriffe ohne Objektcharakter:

- Wachstum, Farbe, Größe,

- Klugheit, Liebe, Schönheit, Sein, - Demokratie, Humor,

- Einigkeit, Gerechtigkeit, Freiheit, - Freizeit, Arbeit,

- Effizienz, Information, Sichtbarkeit.

(6)

Zustand:

- Objekte haben Attribute (Alter, Größe,...).

- Die Werte der Attribute können sich verändern;

in der Programmierung heißt das, dass jedes Objekt für jedes Attribut eine Variable besitzt.

- Der Zustand eines Objekts zu einem Zeitpunkt ist charakterisiert durch die aktuellen Attributwerte.

Lebensdauer:

- Jedes Objekt hat eine Lebensdauer; das ist die

Zeit von seiner Entstehung bis zum Verschwinden bzw. von seiner Erzeugung bis zur Löschung.

- Die Lebensdauer kann in Zeit oder in Ablauf- schritten gemessen werden.

Aufenthaltsort:

- Objekte besitzen normalerweise einen Ort, der durch eine Adresse charakterisiert wird.

- Beispiele für Adressen: Hausadresse, Email- adresse, Web-Adresse, Telefonnummer, Geo- Koordinaten, Rechneradresse, Adresse im

Zum Objektbegriff:

(7)

Verhalten:

- Im Allg. können Objekte aktiv sein; sie können  ihren Zustand verändern,

 ihren Aufenthaltsort verändern,

 anderen Objekten eine Nachricht schicken,  Nachrichten von anderen Objekten empfangen  neue Objekte erzeugen.

- Zu jeder Nachricht gibt es eine sogenannte Methode, die beschreibt, wie eine Nachricht bearbeitet wird.

Identität / Gleichheit :

- Zu einem Objekt gibt es im Allg. gleichartige Objekte (z.B. zwei Bücher, zwei Häuser,...)

- Objekte kann man vergleichen.

- Objekte besitzen eine Identität, die vom Zustand unabhängig ist; d.h. sie können sich in allen

Eigenschaften gleichen, ohne identisch zu sein (z.B. zwei baugleiche Autos).

- Insbesondere kann man durch Klonen Objekte

erzeugen, die sich in allen Eigenschaften gleichen, aber nicht identisch sind.

(8)

Bemerkung:

• Die charakteristischen Eigenschaften Zustand, Lebensdauer, Ort, Verhalten und Identität

sind als Hilfestellung, nicht als scharfe Begriffsklärung zu verstehen.

• Anhand der charakteristischen Eigenschaften lässt sich untersuchen, wie ausgeprägt der Objektcharakter eines Gegenstands ist.

Beispiel:

• Ein Auto ist ein typisches Objekt:

- Attribute und ihr Zustand : Farbe : rot

Marke: Daimler Schiebedach: ja

Alter: 12 Jahre Tankfüllung: halbvoll Kilometerstand: 234568 Geschwindigkeit: 130 km/h - Lebensdauer: seit 1996 bis ??

- Ort: A1 zwischen Ennepetal und Volmarstein

(9)

- Verhalten:

Nachrichten, die verstanden werden:

 bremsen  Gas geben

 Scheibenwischer (an/aus)  ...

- Identität: gegeben.

• Ein GUI-Fenster ist ein typisches Objekt

Bemerkung:

Die üblichen mathematischen Objekte (Funktionen, Mengen, Zahlen) besitzen - keinen (veränderlichen) Zustand, - keine Lebensdauer,

- keinen Aufenthaltsort, - kein Verhalten,

- keine Identität jenseits der Gleichheit.

Deshalb unterscheiden wir sie von Objekten und nennen sie Werte (vgl. Folie 83f).

(Das schließt nicht aus, dass man Werte durch Objekte repräsentieren kann.)

(10)

Beziehungen zwischen Objekten:

Nachrichten und Methoden

Objekte können zueinander in unterschiedlichen Beziehungen stehen:

- Objekt X kann ein Teil von Objekt Y sein.

- Objekt X kann mit einem anderen assoziiert oder verknüpft sein.

Besteht keine Beziehung zwischen zwei Objekten, können sie auch nicht direkt mit einander

kommunizieren.

Objekte bieten Dienste an und nehmen Dienste anderer Objekte in Anspruch.

Um einen Dienst in Anspruch zu nehmen, schickt ein Objekt einem anderen eine Nachricht, die den Dienst bezeichnet und Parameter übergibt

(Auftragserteilung).

Erhält ein Objekt eine Nachricht, führt es eine Methode aus, die der Handlungsvorschrift zur Ausführung des Dienstes entspricht.

In der Objektorientierung werden also

(11)

Begriffsklärung: (objektbasiertes System)

In der Objektorientierung werden Systeme als Menge kooperierender Objekte modelliert, - die untereinander in Beziehung stehen und - über Nachrichten miteinander kooperieren.

Derartige Systeme heißen objektbasiert.

Beispiel: (Dienste & Aufträge)

Modellierung eines Buchkaufs:

- Buchhändlerin B bietet den Dienst an, per Email Bücher zu bestellen.

- Herr P. (Senderobjekt) gibt Frau B. (Empfänger- objekt) den Auftrag, den „Mann ohne

Eigenschaften“ zu besorgen.

- Herr P. weiß nicht, wie Frau B. den Auftrag ausführt.

- Frau B. besitzt eine Methode, wie mit dem Auftrag zu verfahren ist. Nach Ausführung schickt sie Herrn P. das Buch zu.

(12)

Klassifikation und Vererbung:

Objekte lassen sich nach ihren Eigenschaften klassifizieren:

• Alle Objekte mit ähnlichen Eigenschaften werden zu einer Klasse zusammen gefasst.

• Die Klassen werden hierarchisch geordnet.

Einzelhandelsgeschäft

Lebensmittelladen Buchladen Möbelgeschäft

Kinderbuchladen Fachbuchhandlung

Beispiel: (Klassifikation)

Als Objekte betrachten wir Einzelhandelsgeschäfte.

Sie lassen sich nach ihren Produkten klassifizieren:

• Die übergeordneten Klassen besitzen Eigenschaften, die allen untergeordneten Klassen gemeinsam sind.

• Beachte die Unterscheidung: Objekt <--> Klasse

(13)

Beobachtung:

- Es gibt Klassen, zu denen nur die Objekte der Unterklassen gehören (z.B. gibt es kein

Geschäft, das nur ein Einzelhandelsgeschäft ist).

Diese Klassen nennt man abstrakt.

- Es gibt Klassen, zu denen eigene Objekte und die Objekte der untergeordneten Klassen gehören.

Vorteile der Klassifikation:

- Übergeordnete Klassen besitzen Eigenschaften,

die allen untergeordneten Klassen gemeinsam sind.

Auf Basis dieser Eigenschaften lassen sich Methoden formulieren, die für alle Objekte der untergeordneten Klassen funktionieren.

- Eigenschaften können von übergeordneten zu untergeordneten Klassen vererbt werden.

Begriffsklärung: (objektorientiertes System)

Objektbasierte Systeme, bei denen - die Objekte Klassen zugeordnet und - die Klassen gemäß einer Klassifikation

hierarchisch geordnet sind und Vererbung erlauben, heißen objektorientiert.

(14)

5.1.2 Zur objektorientierten Modellieren

(Nur Einführung; OO-Modellierung ist Gegenstand von SE 2; siehe auch Goos, Band 2, 10.2)

Die Analyse einer Aufgabenstellung führt zu einem Modell des Ausschnitts der Welt, der zur Lösung geeignet ist.

Das Modell beschreibt die wichtigen Eigenschaften des zu entwickelnden Systems. Es orientiert sich zunächst an der Anwendung und nicht der Realisierung.

Bei objektorientierter Modellierung ist zu klären:

1. Welche Objekte werden benötigt?

2. Welche Beziehungen gibt es zwischen den Objekten?

3. Welche Eigenschaften besitzen die Objekte?

4. Wie lassen sich die Objekte klassifizieren?

5. Wie werden die Objekte angewendet?

6. Was ist das Verhalten der Objekte?

Das Objektmodell liefert die Antworten zu 1-4.

Anwendungsfälle beantworten Frage 5.

(15)

Objektorientierte Analyse in Umfeld der objektorientierten Softwareentwicklung:

Analyse

Entwurf

Implementierung

Objekt- modell

Verhaltens- modell

Begriffsklärung: (Objekt-, Verhaltensmodell)

Das Objektmodell beschreibt:

- die relevanten Klassen von Objekten, - deren Attribute,

- welche Dienste/Nachrichten/Operationen bereit- gestellt werden,

- die Beziehungen zwischen den Objekten;

- ggf. mittels einzelner ausgezeichneter Objekte.

(16)

Das Verhaltensmodell beschreibt:

- die Wirkungsweise der Methoden,

- die möglichen Zustände von Objekten,

- das Ablaufverhalten und die Interaktion zwischen den Objekten.

Objektorientierte Analyse: Ein Beispiel Aufgabe:

Entwickle ein Planungs- und Informationssystem für die Veranstaltungen am Fachbereich Informatik.

Grober Leistungsumfang:

- Angebot der Veranstaltungen festlegen - Raum- und Zeitplanung

- Vorlesungseinschreibung - Prüfungsabwicklung

Beschreibungstechnik:

Die Analyseergebnisse beschreiben wir mit UML- Diagrammen. Hier erläutern wir deren Bedeutung informell. Eine präzise Einführung bietet SE 2.

(17)

1. Schritt: Auffinden der Schlüsselabstraktionen

Die Schlüsselabstraktionen sind die zentralen Begriffe des Aufgabenbereichs. Sie bilden den

Ausgangspunkt zur Entwicklung des Objektmodells.

Schlüsselabstraktionen zur Aufgabe:

- Student - Professor

- Veranstaltung (in einem Semester)

- Veranstaltungsverzeichnis für ein Semester - Vorlesung

- Seminar - Raum - Klausur - Zeitslot

Welche Abstraktionen haben den Charakter von Objekten, welche von Werten?

(18)

2. Schritt: Ermitteln der Beziehungen

Beziehungen zwischen den Objekten von jeweils zwei Klassen:

Professor Veranstaltung

1 bietet an *

Student * belegt * Veranstaltung

Beziehungen zwischen den Objekten mehrerer Klassen:

Veranstaltung Veranstaltungs-

verzeichnis

Einführung

1 *

(19)

Veranstaltung

Vorlesung

gegenstand

Seminar Raum

1 ort

1,.., *

Veranstaltungs- verzeichnis

Professor bietet an

Einführung

* 1

Student

* belegt * Zusammengesetztes Klassendiagramm:

0,1

gegenstand

0,1

(20)

3. Schritt: Ermitteln der Attribute

Beispiele:

Raum

nummer: String sitzplätze: int

Veranstaltung name: String termine: ??

Professor

name: String gruppe: String Student

name: String semester: int

Bemerkung:

Viele Beziehungen lassen sich als Attribute ausdrücken und umgekehrt.

Beispiel:

Termine kann man

- als Attribute mit Zeitslots als Typ oder

- als Beziehung zwischen Veranstaltungen und Zeitslots

(21)

Professor

gruppe: String Student

semester: int

Person

name: String

Unter Ausnutzung von Vererbung:

Professor

name: String gruppe: String Student

name: String semester: int

Person

4. Schritt: Ermitteln der Klassifikation

Beispiel:

(22)

Dies lässt sich im Klassendiagramm für Veranstaltung verwenden:

Seminar Vorlesung

Kurs

Kurs

Klassifikation kann zu Vereinfachungen führen:

Veranstaltung

gegenstand

Professor bietet an

Student

* belegt *

(23)

5. Schritt: Ermitteln der Invarianten

Invarianten beschreiben Eigenschaften der Objekte oder der Beziehungen zwischen den Objekten, die im Wesentlichen zu allen Ausführungszeitpunkten gelten.

Beispiele:

- Veranstaltungsraum fasst die Anzahl der Beleger.

- Zu jedem Zeitslot und Raum gibt es maximal eine Veranstaltung.

- Ein Professor bietet nur Veranstaltungen an, die an unterschiedlichen Zeitslots stattfinden.

- Veranstaltungen, die von Studenten mit

bestimmtem Status zu belegen sind, dürfen sich nicht überschneiden.

- Studierende dürfen nur Veranstaltungen belegen, für die sie die Voraussetzungen haben.

(24)

6. Schritt: Ermitteln des Verhaltens

Das Verhalten von Objekten wird durch das Versenden von Nachrichten und das Bearbeiten der Nachrichten durch Methoden modelliert.

Wir betrachten hier drei unterschiedliche Arten Verhaltensaspekte zu beschreiben:

- Skizzieren der wesentlichen Anwendungsfälle - Beschreiben des Nachrichtenaustauschs

zwischen einzelnen Objekten

- Beschreibung der Nachrichten, die die Objekte verstehen (Methodenschnittstelle)

Anwendungsfälle (Use cases)

Ausgangspunkt für die Verhaltensmodellierung sind die wesentlichen Anwendungsfälle des zu entwickelnden Systems.

Ein Anwendungsfall ist ein typischer Vorgang, der mit dem zu realisierenden System durchgeführt wird.

Anwendungsfälle verdeutlichen auch die

Abgrenzung des Systems von seiner Umgebung

(25)

Student

PI-System

Einschreibung

Vorlesungs- ankündigung

Prüfungsanmeldung

Professor

Beispiel:

Student und Professor sind sogenannte Akteure.

Akteure können auch agierende Softwareteile der Systemumgebung sein.

Interaktion zwischen Objekten

Um die Interaktion zwischen Objekten darzustellen, beschreibt man:

- die beteiligten Objekte

- die Namen der Nachrichten

- ggf. die Parameter der Nachrichten - den zeitlichen Ablauf der Nachrichten (UML-Diagramme dazu siehe SE 2)

(26)

5.2 Objekte, Klassen, Kapselung

Die objektorientierte Modellierung bildet die Grundlage für die objektorientierte Implementierung eines

Systems.

Dieser Abschnitt beschäftigt sich mit der Umsetzung der vorgestellten Modellierungskonzepte in objekt- orientierte Programmiersprachen, insbesondere Java.

Überblick:

• Beschreibung von Objekten und Klassen

• Anwendung von Objekten

• Spracherweiterungen: Ausnahmebehandlung und Initialisierung

• Anwenden und Entwerfen von Klassen

• Spracherweiterungen: Überladen, Klassenattribute und Klassenmethoden

• Zusammenwirken der Spracherweiterungen

• Rekursive Klassen

• Typsystem von Java und parametrische Typen

• Kapselung und Strukturieren von Klassen

(27)

Prototyp-Konzept:

Der Programmierer beschreibt direkt einzelne Objekte. Neue Objekte werden durch Klonen existierender Objekte und Verändern ihrer Eigenschaften zur Laufzeit erzeugt.

Klassenkonzept:

Der Programmierer deklariert Klassen als

Beschreibung der Eigenschaften, die Objekte dieser Klasse haben sollen. Die

Programmiersprache ermöglicht es, zur

Laufzeit Objekte der Klassen zu erzeugen, aber nicht, die Klassen zu verändern.

5.2.1 Beschreibung von

Objekten und Klassen

In der objektorientierten Programmierung

betrachtet man die Ausführung eines Programms als ein System kooperierender Objekte.

Wir betrachten hier nur das Klassenkonzept.

Grundsätzlich gibt es zwei Konzepte zur program- miersprachlichen Beschreibung von Objekten:

(28)

• welche Zustände ein Objekt annehmen kann,

• auf welche Nachrichten es reagieren kann und

• wie die Methoden aussehen, mit denen ein Objekt auf den Empfang von Nachrichten reagieren kann.

Die Eigenschaften und das Verhalten eines

(programmiersprachlichen) Objekts ergeben sich aus seinen möglichen Zuständen und daraus, wie es auf Nachrichten reagiert.

Eine Objektbeschreibung - insbesondere eine Klassendeklaration - muss daher festlegen:

Die Menge der möglichen Zustände eines

Objekts entspricht den Wertebereichen seiner Attribute.

Die Reaktionen eines Objekts auf eintreffende Nachrichten legen sein dynamisches Verhalten fest.

(29)

Eine einfache Klassendeklaration in Java hat folgende Bestandteile:

Ein Java-Objekt kann genau auf die Nachrichten reagieren, für die Methoden in seiner Klasse

deklariert sind oder für die es Methoden geerbt hat (vgl. Abschnitt 5.3).

class Person { String name;

Person(String n) { this.name = n;

}

String getName() { return this.name;

} }

Attribut

Methode Konstruktor Klassenname

(30)

Deklaration von Klassen

Klassenname

Direkt hinter dem Schlüsselwort class wird der Name der Klasse angegeben. Objekt einer

Klasse K nennt man auch Instanzen oder Ausprägungen von K.

Der Klassenname wird gleichzeitig als Typname für die Objekte dieser Klasse verwendet

(Klassentyp). Er kann im Programm dann wie elementare Typen (int, float, usw.) für die Deklaration von lokalen Variablen, Parametern und Rückgabewerten verwendet werden.

Beispiel:

Person eineMethode(Person p) { ...

Person vater;

...

}

Außer den Objekten einer Klasse K gehören auch alle Objekte von Unterklassen von K zum Typ K (siehe Abschnitt 5.3).

(31)

Attribute

Innerhalb einer Klasse K können beliebig viele Attribute deklariert werden.

Für jedes in K deklarierte Attribut vom Typ T besitzen die Objekte der Klasse K eine

objektlokale Variable vom Typ T. Diese

objektlokalen Variablen nennt man häufig auch Instanzvariablen.

Die Lebensdauer der Instanzvariablen ist gleich der Lebensdauer des Objekts.

Person

name : String

Klasse:

: Person

name:

Objekt:

getName(): String

(32)

Methoden

Innerhalb der Klassendeklaration können beliebig viele Methoden deklariert werden.

Methodendeklarationen bestehen aus einer

Signatur und einem Methodenrumpf. Syntaktisch sind sie wie Prozedurdeklarationen aufgebaut.

Außer den deklarierten Parametern besitzt jede Methode m eine weiteren, sogenannten impliziten Parameter vom Typ der Klasse, in dem m

deklariert wurde. Dieser Parameter wird im Methodenrumpf mit this bezeichnet.

String getName() { return this.name; }

String getName(Person this)

hätte in der prozeduralen Programmierung also die Signatur:

Die obige Methode in Klasse Person:

Beispiel:

(33)

Konstruktoren

Konstruktoren kann man als spezielle Methoden auffassen, die die Initialisierung von Objekten beschreiben.

Sie haben den gleichen Namen wie die Klasse, in der sie deklariert sind.

Beim Start der Ausführung eines Konstruktors ist das zugehörige Objekt bereits erzeugt, seine

Attribute jedoch nur mit Standardwerten initialisiert.

Konstruktoren liefern als Ergebnis das neu

erzeugte Objekt zurück, genauer eine Referenz auf dieses Objekt.

Neben den prozeduralen Anweisungen kann eine Methodenrumpf in Java:

- neue Objekte erzeugen, - auf Attribute zugreifen,

- Nachrichten an andere Objekte schicken (Methodenaufruf).

(34)

Im Allg. umfasst die Anwendung von Objekten vier Anweisungen:

- Objekterzeugung - Attributzugriff - Methodenaufruf

- Objektlöschung (in Java nicht direkt unterstützt)

5.2.2 Anwendung von Objekten

Objekte: Erzeugen und Referenzieren

Syntax in Java:

Objekte werden mit Ausdrücken folgender Form erzeugt (engl. object creation expression):

new <Konstruktorname> ( <Parameterliste> ) Semantik:

1. Erzeuge ein Objekt / eine Instanz der Klasse, der der Konstruktor gehört. Dabei werden

insbesondere die Instanzvariablen angelegt.

2. Werte die aktuellen Parameter aus.

3. Rufe den Konstruktor mit den Parametern auf.

Dieser sollte die Instanzvariablen initialisieren.

Ergebnis ist die Referenz des neu erzeugten

(35)

Begriffsklärung: (Referenz, Verweis, Zeiger)

Eine Objektreferenz (engl. object reference) ist

eine eindeutige abstrakte Adresse oder Bezeichnung für ein Objekt. Manchmal spricht man auch von

Verweis (engl. link) oder Zeiger (engl. pointer).

Variablen speichern nicht die Objekte als Ganzes, sondern Objektreferenzen.

Die Auswertung von Ausdrücken eines Klassentyps K liefert Referenzen auf Objekte des Typs K.

Person a,b;

a = new Person ("Klaus");

b = a; // a und b speichern dieselbe // Objektreferenz.

b.getName(); // liefert "Klaus"

Beispiel: (Objektreferenz)

Folgendes Programmfragment verdeutlicht die

Unterscheidung zwischen Objekt und Referenzen:

(36)

: Person

a:

b:

name: "Klaus"

Es gilt also: a.name == b.name

Sprechweisen & Bemerkungen:

• Häufig spricht man von Referenzen auf ein Objekt.

• Referenzen nennt man auch anonyme Namen oder Bezeichner. Eine Referenz ist ein Wert.

• Referenzen stellt man graphisch üblicherweise durch Pfeile dar. Zwei Referenzen sind gleich, wenn sie auf das gleiche Objekt zeigen.

• Die Unterscheidung Referenz / Objekt hat viele Analogien:

- Anschrift / Wohnung - Email-Adresse / Mailbox

- Telefonnummer / Telefonanschluss - Speicheradresse / Speicherzelle

(37)

class EinObjekt {

int meinAttribut;

EinObjekt( int n ) { meinAttribut = n;

} }

Beispiel: (Referenzsemantik)

Wir zeigen ein Programmbeispiel, das verdeutlicht, warum es wichtig ist, zwischen Objekten und

Referenzen zu unterscheiden:

EinObjekt a, b, c;

a = new EinObjekt(7);

b = a;

c = new EinObjekt(7);

if( a != c ) {

a.meinAttribut = 9;

}

System.out.println( b.meinAttribut );

System.out.println( c.meinAttribut );

Anwendung der Klasse EinObjekt:

(38)

Operationen auf Referenzen in Java:

• Referenzen lassen sich mit „==“ auf Gleichheit testen bzw. mit „!=“ auf Ungleichheit. Sie sind genau dann gleich, wenn sie dasselbe Objekt referenzieren.

Beispiel:

Im obigen Beispiel gilt nach der Zuweisung an c:

a == b und a != c und b != c

• Über Referenzen kann man Objekten Nachrichten schicke und auf sie zugreifen, d.h. auf ihre

Instanzvariablen.

Bemerkungen:

• Objekte können der gleichen Klasse angehören und den gleichen Zustand haben (gleich sein), aber trotzdem nicht die gleiche Identität haben und damit auch unterschiedliche Referenzen besitzen.

• Variablen von einem Klassentyp speichern

Referenzen. Wir sagen deshalb auch vereinfachend, dass eine Variable ein Objekt referenziert.

• Es ist wichtig zwischen einer Variablen und dem Objekt, dass sie referenziert zu unterscheiden!

(39)

Die null-Referenz:

Zur Initialisierung von referenzwertigen Variablen und als Dummy-Wert gibt es die Konstante null.

null gehört zu jedem Klassentyp und referenziert kein Objekt.

Der Versuch, eine Nachricht an null zu

schicken, oder auf eine Instanzvariable von null zuzugreifen, führt in Java zu einer NullPointer-

Ausnahme.

Person a,b;

a = new Person ("Klaus");

b = null; // zulässig

if( a != b ) { // Vergleich ok String s;

s = b.name; // NullPointerException b.getName(); // NullPointerException }

Beispiel: (Objektreferenz)

Folgendes Programmfragment verdeutlicht die

Unterscheidung zwischen Objekt und Referenzen:

(40)

Begriffsklärung: (Objektgeflecht)

Eine Menge von Objekten, die sich gegenseitig referenzieren, nennen wir ein Objektgeflecht (vgl. Folie 4.71).

Bemerkung:

• Objektgeflechte werden zur Laufzeit aufgebaut und verändert, sind also dynamische Entitäten.

• Klassendiagramme kann man als vereinfachte statische Approximationen von Objektgeflechten verstehen.

Lebensdauer von Objekten und Instanzvariablen:

In Java lassen sich Objekte nicht löschen.

Aus Sicht des Programmierers leben Objekte und deren Instanzvariablen von der Objekterzeugung bis zum Ende der Ausführung des Programms.

Der Speicher nicht erreichbarer Objekte wird ggf.

vor Ablauf der Lebensdauer von der automatischen

(41)

Attributzugriff

Syntax in Java:

Auf Instanzvariablen von Objekten kann mit

Ausdrücken folgender Form zugegriffen werden:

<referenzwertiger Ausdruck>.<Attributbezeichner>

Semantik:

Werte den referenzwertigen Ausdruck aus. Liefert dieser null, löse eine NullPointerException aus.

Andernfalls liefert er die Referenz auf ein Objekt X;

in dem Fall liefert der gesamte Ausdruck die

Instanzvariable von X zum angegenen Attribut (L- Wert) oder deren Wert (R-Wert).

Abkürzende Notation:

Der implizite Methodenparameter this kann beim Zugriff auf eine Attribut a weggelassen werden, d.h.

a

ist gleichbedeutend mit this.a

innerhalb von Klassen, in denen a deklariert ist.

(42)

Beispiel: (Attributzugriffe)

class DeinObjekt { MeinObjekt du;

String deinName;

}

class MeinObjekt {

boolean binEingetragen;

String meinName;

void dichInit (DeinObjekt d) {

System.out.println( d.deinName );

d.deinName = this.meinName;

this.binEingetragen = true;

meinName = d.du.meinName;

} }

(43)

Methodenaufruf: (engl. method invocation)

Syntax:

Ein Methodenaufruf ist ein Ausdruck ähnlich einem Prozeduraufruf, allerdings mit einem zusätzlichen Parameter:

<refwertigerAusdruck>.<Methodenbezeichner> ( <AktuelleParameterliste> ) Semantik:

Werte den referenzwertigen Ausdruck aus. Liefert dieser null, löse eine NullPointerException aus.

Andernfalls liefert er die Referenz auf ein Objekt X.

Werte die aktuellen Parameter p1, ..., pn aus.

Führe den Rumpf der angegebenen Methode mit - X als implizitem Parameter und

- p1, ... , pn als expliziten Parametern aus.

Das Ergebnis des Aufrufs ist der Rückgabewert der Ausführung des entsprechenden Methodenrumpfes.

Bemerkung:

Eine verfeinerte Semantik wird in 5.3 behandelt.

Dabei wird auch der Zusammenhang zum Senden von Nachrichten angesprochen.

(44)

Beispiel: (Methodenaufrufe)

class Mensch {

Mensch vater, mutter;

String name;

Mensch getOpa (boolean mutterseits) { if ( mutterseits ) {

return mutter.vater;

} else {

return vater.vater;

} }

void eineMethode (Mensch m) { Mensch opaV;

String opaMName;

opaV = m.getOpa(false);

opaMName = m.getOpa(true). name;

} ...

Abkürzende Notation:

Wie beim Attributzugriff kann auch beim Methoden- aufruf der implizite Methodenparameter this weg- gelassen werden, also m(...) statt this.m(...) .

(45)

Objektorientierte Programme

Ein objektorientiertes Java-Programm Π besteht aus einer Menge von Klassen. Mindestens eine der

Klassen muss eine Methode mit Namen main und folgender Signatur besitzen:

public static void main ( String[] args ) {

...

}

Beim Start von Π wird die Klasse angegeben, deren main-Methode ausgeführt werden soll:

java <Klassenname> <arg1> <arg2> ...

Die Argumente arg1,... werden dabei im Parameter args als ein Feld von Strings übergeben.

Bei der Ausführung werden die benötigten Objekte erzeugt. Diese Bearbeiten ihre Aufträge durch

Ausführung von Methoden.

(46)

Initialisierung

Attribute und lokale Variablen können direkt an ihrer Deklarationsstelle initialisiert werden.

Somit sind folgende Programmstücke gleichbedeutend.

float pi;

pi = 3.141;

float pi = 3.141;

In Java können Attribute und Variablen durch das Schlüsselwort final als unveränderlich

deklariert werden.

In diesem Fall muss die Initialisierung an der Deklarationsstelle erfolgen.

class Mathe { ...

final float pi = 3.141; // Konstante ...

}

Die Initialisierung von Attributen erfolgt vor dem

5.2.3 Spracherweiterungen: Initialisierung

und Ausnahmebehandlung

(47)

Beispiel: (Initialisieren von Attributen)

Folgendes Programm mit Initialisierung

class C {

int ax = 7, ay = 9;

C(){

m();

}

void m(){

int v1 = 4848, v2 = -3; ...

} }

ist äquivalent zu folgendem Programm

class C {

int ax, ay;

C(){

ax = 7;

ay = 9;

m();

}

void m(){

int v1, v2;

v1 = 4848; v2 = -3; ...

} }

(48)

Ausnahmebehandlung

Wie in 3.4.1., Folie 3.169, bereits angesprochen, kann die Auswertung eines Ausdrucks bzw.

die Ausführung einer Anweisung:

- normal terminieren

- in eine Ausnahmesituation kommen und abrupt terminieren

- nicht terminieren

Es gibt drei Arten von Ausnahmesituationen:

1. Vom Programmierer schwer zu kontrollierende

und zu beseitigende Situtationen (Speichermangel) 2. Programmierfehler (Nulldereferenzierung, Verlet- zung von Indexgrenzen

3. Zeitweise nicht verfügbare Ressourcen, anwen- dungsspezifische Ausnahmen, die behebbar sind.

Ausnahmesituationen werden in Programmier- sprachen unterschiedlich behandelt:

- Programmabbruch (engl. abortion) - Ausnahmebehandlung

(49)

Auslösen von Ausnahmen:

Das Auslösen einer Ausnahme kann

- sprachdefiniert (z.B. NullPointer, IndexOutOfBounds) - oder durch eine Anweisung spezifiziert sein.

In Java gibt es zum Auslösen von Ausnahmen die throw-Anweisung.

Syntax:

Die throw-Anweisung hat die Form:

throw <Ausdruck>;

wobei der Ausdruck ein Ausnahmeobjekt als Ergebnis liefern muss. (Die throw-Anweisung entspricht dem raise-Ausdruck in ML.)

Java bietet Sprachmittel für die Ausnahme- behandlung (engl. exception handling).

Dabei spielen drei Fragen eine Rolle:

1. Wann/wie werden Ausnahmen ausgelöst?

2. Wie kann man sie abfangen?

3. Wie kann man neue Ausnahmetypen deklarieren?

(50)

void myMethod (String[] sfeld) {

try {

myPrint( sfeld[0] );

myPrint( sfeld[1] );

} catch (NullPointerException e) { myPrintln("sfeld is null");

} catch (IndexOutOfBoundsException e){

myPrintln("sfeld too small");

}

Abfangen von Ausnahmen:

Die try-catch-Anweisung dient dem Abfangen und Behandeln von Ausnahmen (entspricht dem handle-Ausdruck in ML):

Semantik:

Werte den Ausdruck aus.

Löst die Auswertung eine Ausnahme aus, ist dies die Ausnahme, die von der Anweisung ausgelöst wird.

Andernfalls löse die Ausnahme aus, die das Ergebnis des Ausdrucks ist.

(51)

Bemerkung:

Java verlangt die Deklaration derjenigen Ausnahme- typen in der Signatur einer Methoden m, die nicht von m abgefangen werden (Genaueres in 5.3).

Beispiel:

int m( int i ) throws SomeException { if( i<0 ){

throw new SomeException();

} ...

}

Die Deklaration von Exception-Klassen behandeln wir in Abschnitt 5.3.

Benutzerdefinierte Ausnahmetypen:

Tritt eine Ausnahme vom Typ A im try-Block auf, wird ein A-Objekt X erzeugt.

Ist der Typ A in der Liste der catch-Klauseln aufgeführt, - wird die Ausnahme gefangen,

- X an den Bezeichner der entsprechenden catch- Klausel gebunden und

- diese catch-Klausel ausgeführt (Verfeinerung in 5.3).

(52)

Beispiel: (Ausnahmebehandlung)

class Try {

public static void main( String[] argf ){

long maxint = 2147483647L;

try{

int m, n, ergebnis = 0 ;

m = Integer.parseInt( argf[0] );

n = Integer.parseInt( argf[1] );

long aux = (long)m + (long)n;

if( aux > maxint ) throw new Ueberlauf();

ergebnis = (int)aux ;

} catch ( IndexOutOfBoundsException e ) { System.out.println("Falsche Argumente");

} catch ( NumberFormatException e ) { System.out.println(

"Element in argf keine int-Konstante");

} catch ( Ueberlauf e ) {

System.out.println("Ueberlauf");

} }

(53)

Klassen bilden das zentrale Sprachkonstrukt von Java.

Dementsprechend stehen beim Programmentwurf zwei Fragen im Mittelpunkt:

• Welche existierenden Klassen können für den Programmentwurf herangezogen werden?

• Welche Klassen müssen neu entworfen werden?

Zur Diskussion dieser Aspekte betrachten wir ein kleines Beispiel.

Aufgabenstellung:

Ein rudimentäres Browser-Programm soll realisiert werden, mit dem einfache W3Seiten bei einem

Server geholt und in einem Fenster angezeigt werden können.

Wir gehen davon aus, dass die folgenden Klassen existieren:

- W3Seite: Implementiert W3Seiten.

- W3Server: Implementiert W3Server bzw. ihre Schnittstelle.

- Textfenster: Kann W3-Seiten anzeigen.

5.2.4 Anwenden und Entwerfen von Klassen

(54)

W3Server Browser

Textfenster

1..

1

Klassendiagramm zur Lösung der Aufgabenstellung:

*

ablegen(String,W3Seite) W3Seite holen(String)

anzeigen(...)

* *

W3Seite

String getTitel() String getInhalt()

*

1

Schnittstellen der gegebenen Klassen:

class W3Server {

W3Server() { ... }

void ablegenSeite( String adr, W3Seite s ){

...

}

W3Seite holenSeite( String adr ) { ... } }

class TextFenster ... { ...

TextFenster() { ... }

void anzeigen(String tzeile,String text){

...

laden(...)

aktSeite: W3Seite

(55)

/**

* Objekte repräsentieren triviale

* Web-Seiten mit Titelzeile und Inhalt */

class W3Seite {

String titel;

String inhalt;

W3Seite ( String t, String i ) { titel = t;

this.inhalt = i;

}

String getTitel() { return this.titel;

}

String getInhalt() { return inhalt;

} }

Die vollständige Klasse W3Seiten:

(56)

class Browser {

W3Server meinServer;

TextFenster oberfl;

W3Seite aktSeite; // aktuelle Seite Browser( W3Server server ){

meinServer = server;

oberfl = new TextFenster();

laden( new W3Seite("Startseite",

"NetzSurfer: Keiner ist kleiner") );

interaktiveSteuerung();

}

void laden( W3Seite s ){

aktSeite = s;

oberfl.anzeigen( aktSeite.getTitel(), aktSeite.getInhalt());

}

void interaktiveSteuerung() { ... } }

Wichtige Implementierungsteile einer rudimentären Browser-Klasse:

(57)

In Java ist es erlaubt, innerhalb einer Klasse mehrere Methoden mit dem gleichen Namen zu deklarieren, d.h. es gibt zwei Bindungen mit gleichem Namen.

Eine derartige Mehrfachverwendung nennt man Überladen eines Namens. Methoden mit gleichen

Namen müssen sich in der Anzahl oder in den Typen der Parameter unterscheiden.

Durch die unterschiedliche Signatur kann der

Übersetzer die Überladung auflösen, d.h. für jede Aufrufstelle ermitteln, welche von den Methoden gleichen Namens an der Aufrufstelle gemeint ist.

Entsprechend dem Überladen von Methodennamen erlaubt Java auch das Überladen bei Konstruktoren.

5.2.5 Spracherweiterungen: Überladen, Klassenvariablen und -methoden Überladen

Beispiel

:

(Überladen)

Die Java-Bibliothek bietet viele Beispiele für Überladung. Wir betrachten die Klasse String (hier nur unvollständig wiedergegeben):

(58)

class String {

/** The value is used for character storage */

char[] value;

/** The offset is the first index of the used storage*/

int offset;

/** The count is the number of characters in the ... */

int count;

String() { value = new char[0]; } String( String value ) { ... } String( char[] value ) {

this.count = value.length;

this.value = new char[count];

System.arraycopy(value,0,this.value,0,count);

} ...

int indexOf(int ch) { return indexOf(ch, 0);}

int indexOf(int ch, int fromIndex) { ... } int indexOf(String str) { ...}

int indexOf(String str, int fromIndex) {...}

int length() { return count; } char charAt(int index) {

if ((index < 0) || (index >= count)) { throw

new StringIndexOutOfBoundsException(index);

}

return value[index + offset];

}

(59)

Klassenattribute und Klassenmethoden

Die Deklaration eines Klassenattributs liefert eine klassenlokale Variable. Syntax:

static <Typausdruck> <Attributname> ; Klassenattribute/-variablen werden häufig auch als statische Attribute/Variablen bezeichnet.

Die Variable kann innerhalb der Klasse mit dem Attributnamen, außerhalb mittels

<Klassenname> . <Attributname>

angesprochen werden. Die Lebensdauer der Variablen entspricht der Lebensdauer der Klasse.

Bemerkung:

Klassenvariablen verhalten sich ähnlich wie globale Variablen in der prozeduralen Programmierung.

(60)

Beispiel: (Klassenattribut)

class InstanceCount {

static int instCount = 0;

InstanceCount(){

instCount++;

...

} ...

}

Die Deklaration einer Klassenmethode entspricht der Deklaration einer Prozedur. Klassenmethoden besitzen keinen impliziten Parameter. Sie können nur auf Klassenattribute, Parameter und lokale Variable zugreifen. Syntax:

static <Methodendeklaration>

Klassenmethoden werden häufig auch als statische Methoden bezeichnet.

Klassenmethoden werden mit folgender Syntax aufgerufen:

<Klassenname> . <Methodenname> ( ... )

Innerhalb der Klasse kann der Klassenname entfallen.

(61)

Beispiel: (Klassen-, statische Methoden)

Deklaration:

class String { ...

static String valueOf( long l ) { ... } static String valueOf( float f ) { ... } ...

}

Anwendung/Aufruf:

String.valueOf( (float)(7./9.) )

liefert die Zeichenreihe: "0.7777778"

Bemerkung:

In Kapitel 4 wurden Klassenmethoden zur

prozeduralen Programmierung in Java genutzt.

(62)

class System {

final static InputStream in = ...;

final static PrintStream out = ...;

static void exit(int status) { ... } static native void arraycopy(

Object src,int src_position,

Object dst,int dst_position, int length);

}

Die Klasse PrintStream besitzt Methoden print und println:

System.out.print("Das klaert die Syntax");

System.out.println(" von Printaufrufen");

Beispiele: (Klassenattribute u. -methoden)

1. Charakteristische Beispiele für Klassenattribute und -methoden liefert die Klasse System, die eine Schnitt- stelle von Programmen zur Umgebung bereitstellt:

(63)

2. Unsere Klasse InputOutput liefert auch schöne Beispiele für statische Methoden und Überladung:

public class InputOutput {

public static int readInt(){...}

public static String readString(){...}

public static char readChar(){...}

public static void print(int i){

System.out.print(i);

}

public static void println(int i){

System.out.println(i);

}

public static void print(char c){

System.out.print(c);

}

public static void println(char c){

System.out.println(c);

}

public static void print(String s){

System.out.print(s);

}

public static void println(String s){

System.out.println(s);

} }

(64)

5.2.6 Zusammenwirken der Spracherweiterungen

Das Zusammenwirken der eingeführten Sprach-

elemente erlaubt bereits, recht komplexe Programme zu schreiben.

Folgendes Programmbeispiel mischt prozedurale und objektorientierte Sprachelemente. Es dient zum Studium des Zusammenwirkens der

Spracherweiterungen.

Beispiel: (Zusammenwirken von Sprachel.)

Wir erweitern das Browserbeispiel von 5.2.4:

- Unterstützung mehrerer Browserfenster - Interaktive Steuerung über die Konsole

class Konsole {

static String readString() { ... }

static void writeString( String s ) {...}

}

(65)

Entwurf der Implementierung:

- Die gemeinsamen Teile aller Browserfenster werden durch Klassenattribute und –methoden realisiert.

- Es gibt zwei Konstruktoren: Einer startet das erste Browserobjekt; der andere weitere

Browserobjekte.

- Die gemeinsamen Teile der Konstruktoren werden von der Methode initialisieren erledigt.

- Die interaktive Steuerung von der Konsole

wird durch eine statische Methode implementiert.

- Zur einfacheren Handhabung steht eine

Klassenmethode start zur Verfügung, die den W3Server als Argument bekommt:

...

Browser.start( testServer );

...

- Die Browserfenster werden in einem Feld auf Klassenebene verwaltet.

(66)

class Browser {

TextFenster oberfl;

W3Seite aktSeite;

static W3Server meinServer;

static final int MAX_ANZAHL = 4;

static Browser[] gestarteteBrowser =

new Browser[MAX_ANZAHL];

static int naechsterFreierIndex = 0;

static int aktBrowserIndex;

static W3Seite startseite = new W3Seite("Startseite",

"NetzSurfer: Keiner ist kleiner");

// Konstruktor für ersten Browsers Browser( W3Server server ) {

if( naechsterFreierIndex != 0 ) {

System.out.println("Browser gestartet");

} else {

meinServer = server;

initialisieren();

} }

// Konstruktor für weiterere Browserfenster Browser() {

if( naechsterFreierIndex == MAX_ANZAHL ) { System.out.print("Maximale Anzahl ");

System.out.println(" Browser erreicht");

} else {

initialisieren();

}

(67)

static void start( W3Server server ) { new Browser(server);

Browser.interaktiveSteuerung();

}

void initialisieren() {

oberfl = new TextFenster();

gestarteteBrowser[ naechsterFreierIndex ] = this;

aktBrowserIndex = naechsterFreierIndex;

naechsterFreierIndex++ ; laden( startseite );

}

void laden( W3Seite s ){

aktSeite = s;

oberfl.anzeigen(aktSeite.getTitel(), aktSeite.getInhalt());

}

(68)

static void interaktiveSteuerung() { char steuerzeichen = '0';

do {

Konsole.writeString("Steuerzeichen [lnwe]: ");

try {

String eingabe = Konsole.readString();

if( eingabe.equals("") ) steuerzeichen = '0';

else

steuerzeichen = eingabe.charAt(0);

} catch( Exception e ) { System.exit( 0 );

}

switch( steuerzeichen ){

case 'l':

String seitenadr;

Konsole.writeString("Seitenadresse: ");

seitenadr = Konsole.readString();

gestarteteBrowser[aktBrowserIndex] .

laden( meinServer.holenSeite( seitenadr ) );

break;

case 'n': new Browser(); break;

case 'w':

aktBrowserIndex =

(aktBrowserIndex+1) % naechsterFreierIndex;

break;

case 'e':

System.exit( 0 );

default:

Konsole.writeString("falsche Eingabe\n");

}

} while( true );

}

(69)

5.2.7 Rekursive Klassen

Definition: (rekursive Klassendeklarationen)

Eine Klassendeklaration K heißt direkt rekursiv, wenn Attribute von K den Typ K haben.

Eine Menge von Klassendeklarationen heißt verschränkt rekursiv oder indirekt rekursiv

(engl. mutually recursive), wenn die Deklarationen gegenseitig voneinander abhängen.

Eine Klassendeklaration heißt rekursiv, wenn sie direkt rekursiv ist oder Element einer Menge verschränkt rekursiver Klassendeklarationen ist.

Bemerkung:

• Wir identifizieren Klassen mit ihren Deklarationen.

• Wichtige Anwendung rekursiver Klassen ist die Implementierung von Listen-, Baum- und Graph- strukturen.

Im Folgenden betrachten wir rekursive Klassen

für Listen. Dabei variieren wir die Programmierstile und die bereitgestellten Schnittstellen.

Implementierung von Listen

(70)

Beispiel: (Einfachverkettete Listen)

class ProcList { int head;

ProcList tail;

}

Bei einfachverketteten Listen gibt es für jedes

Listenelement ein Objekt mit zwei Instanzvariablen:

- zum Speichern des Elements

- zum Speichern der Referenz auf den Rest der Liste.

: ProcList head: 6 tail:

: ProcList : ProcList head: -3

tail:

head: 84 tail:

Prozedurale Datenstrukturen:

In der prozeduralen Programmierung sind

Datentypen und Prozeduren zunächst getrennt (Zusammenfassung erst auf Modulebene).

(71)

class ProcListMain {

static boolean sortiert(ProcList l){

if( l == null || l.tail == null ) { return true;

} else if( l.head <= l.tail.head ){

return sortiert( l.tail );

} else {

return false;

} }

public static void main( String[] argf ){

ProcList l1 = new ProcList();

ProcList l2 = new ProcList();

ProcList l3 = new ProcList();

l1.head = 1;

l2.head = 2;

l3.head = 3;

l1.tail = l2;

l2.tail = l3;

l3.tail = null;

System.out.println( sortiert(l1) );

l3.head = 0;

System.out.println( sortiert(l1) );

} }

(72)

Diskussion:

Die prozedurale Fassung erlaubt es jedem, der eine Referenz auf ein Listenknoten hat, das Objektgeflecht unkontrolliert zu verändern.

Zum Beispiel könnte man Listen in ein zyklisches Geflecht verändern und damit Invarianten

verletzen.

Funktionale Datenstrukturen:

Unterbindet man den beliebigen Zugriff auf die Attribute und bietet nur Methoden an, um Listen auf- und abzubauen, kann man ein

Verhalten wie in der funktionalen Programmierung erreichen.

Das Mehr an Garantien wird durch weniger

Flexibilität bezahlt. Insbesondere ist das direkte Einfügen und Modifizieren in der „Mitte“ einer Datenstruktur nicht mehr möglich.

(73)

Beispiel: (Zugriff nur über Methoden)

class FunctionalList { private int head;

private FunctionalList tail;

static FunctionalList empty() { return new FunctionalList();

}

boolean isempty(){

return tail == null;

}

int head(){

if( isempty() ){

throw new NoSuchElementException();

}

return head;

}

FunctionalList tail(){

if( isempty() ){

throw new NoSuchElementException();

}

return tail;

}

FunctionalList cons( int i ) {

FunctionalList aux = new FunctionalList();

aux.head = i;

aux.tail = this;

return aux;

} }

(74)

class FunctionalListMain {

static boolean sortiert( FunctionalList l ){

if( l.isempty() || l.tail().isempty() ) { return true;

} else if( l.head() <= l.tail().head() ){

return sortiert( l.tail() );

} else {

return false;

} }

public static void main( String[] argf ){

FunctionalList le, l1, l2, l3;

le = FunctionalList.empty();

l3 = le.cons(3);

l2 = l3.cons(2);

l1 = l2.cons(1);

System.out.println( sortiert(l1) );

l1 = l3.cons(4);

System.out.println( sortiert(l1) );

} }

Objektorientierte Listen:

Aus objektorientierter Sicht ist eine Liste ein Behälter, in den man etwas hineintun und aus dem man etwas herausnehmen kann:

(75)

Beispiel: (Liste als Behälter)

class SLinkedList {

// Liefert das erste Element der Liste, // ohne diese Liste zu veraendern

int getFirst(){ ... }

// Fügt vorne ein neues Element an diese // Liste an

void addFirst( int n ) { ... }

// Löscht das erste Element dieser Liste // und liefert es als Ergebnis

int removeFirst() { ... }

// Liefert die Elementanzahl dieser Liste int size() { ... }

}

class SLinkedListMain {

public static void main( String[] argf ){

SLinkedList l = new SLinkedList();

l.addFirst(3);

l.addFirst(2);

l.addFirst(1);

System.out.println( l.removeFirst() );

System.out.println( l.size() );

System.out.println( l.removeFirst() );

}

Zunächst die Schnittstelle und Anwendung der Klasse:

(76)

Und nun Teile der Implementierung von SLinkedList:

import java.util.NoSuchElementException;

class SEntry { int head;

SEntry tail;

}

class SLinkedList {

private SEntry entries = null;

private int size = 0;

int getFirst(){

if( size == 0 ){

throw new NoSuchElementException();

}

return entries.head;

}

void addFirst( int n ) { size++;

SEntry auxe = new SEntry();

auxe.head = n;

auxe.tail = entries;

entries = auxe;

} ...

}

(77)

: Entry element:

next:

:LinkedList header:

size: 3

previous:

: Entry element:

next:

previous:

: Entry element:

next:

previous:

: Entry element:

next:

previous:

Problem bei der erläuterten Implementierung:

Rekursives oder iteratives Durchlaufen durch die Liste ist nicht möglich (Abhilfe: s.u.).

Andere Formen von Listenimplementierungen

speichern die Elemente in Feldern oder nutzen eine doppelte Verkettung der Eintragsknoten:

... ... ...

(78)

Implementierung von Bäumen

Binäre Bäume sind wichtige Datenstrukturen, z.B.

zur Darstellung von Mengen.

class BinTree {

private int elem;

private BinTree left, right;

BinTree( int e ) { elem = e;

}

void sorted_insert( int e ) { if( e < elem ) {

if( left == null ) { left = new BinTree(e);

} else {

left.sorted_insert(e);

}

} else if( elem < e ) { if( right == null ) {

right = new BinTree(e);

} else {

right.sorted_insert(e);

} } }

Beispiel: (Bäume)

(79)

boolean contains( int e ) {

if( e < elem && left != null ) { return left.contains(e);

} else if( elem > e && right != null ) { return right.contains(e);

} else {

return e==elem;

} }

void printTree() {

if( left != null ) left.printTree();

System.out.println(elem);

if( right != null ) right.printTree();

} }

class BinTreeMain {

public static void main( String[] argf ){

BinTree bt = new BinTree(12);

bt.sorted_insert(3);

bt.sorted_insert(12);

bt.sorted_insert(11);

bt.sorted_insert(12343);

bt.sorted_insert(-2343);

bt.sorted_insert(233);

bt.printTree();

} }

(80)

Iteratoren

Iteratoren erlauben es, schrittweise über Behälter- Datenstrukturen zu laufen, so dass alle Elemente der Reihe nach besucht werden. Im Zusammenhang mit Kapselung (s.u.) sind sie unverzichtbar.

Beispiel: (Iteratoren)

Wir reichern die Klasse SLinkedList mit Iteratoren an und zeigen deren Anwendung.

class Iterator {

private SEntry current;

Iterator( SEntry se ) { current = se;

}

boolean hasNext() {

return current!=null;

}

int next() {

if( current==null ){

throw new NoSuchElementException();

}

int res = current.head;

current = current.tail;

return res;

(81)

class SLinkedList {

private SEntry entries = null;

...

Iterator iterator() {

return new Iterator( entries );

} }

class SLinkedListMain {

public static void main( String[] argf ){

SLinkedList l = new SLinkedList();

l.addFirst(3);

l.addFirst(2);

l.addFirst(4);

Iterator iter = l.iterator();

while( iter.hasNext() ) {

System.out.println( iter.next() );

} } }

Bemerkung:

Der Iterator muss Zugriff auf die interne Repräsentation der Datenstruktur haben, über die er iteriert.

(82)

5.2.8 Typsystem von Java und parametrische Typen

Werte in Java sind

- die Elemente der elementaren Datentypen, - Referenzen auf Objekte,

- der Wert null.

Jeder Wert in Java gehört zu mindestens einem Typ.

Vordefinierte und benutzerdeklarierte Typen sind - die vordefinierten elementaren Typen,

- die durch Klassen deklarierten Typen,

- die durch Schnittstellen deklarierten Typen (s.u.).

Implizit deklariert sind die Feldtypen zu den Klassen- und Schnittstellentypen (Typkonstruktor „[]“ ).

Feld-, Klassen- und Schnittstellentypen fasst man unter dem Namen Referenztypen zusammen.

(null gehört zu allen Referenztypen.)

Klassen- und Schnittstellentypen beschreiben, welche Nachrichten ihre Objekte verstehen bzw. welche

Typsystem von Java

(83)

Bemerkung:

In typisierten objektorientierten Sprachen können Werte zu mehreren Typen gehören (Subtypen).

Parametrische Typen

Auch objektorientierte Sprachen unterstützen

parametrische Typsysteme wie in ML. Für Java ist eine derartige Unterstützung ab Version 1.5

verfügbar.

Wir betrachten eine parametrische Fassung der Klasse SLinkedList:

Beispiel: (Parametrische Typen)

class SLinkedList<A> { A getFirst(){ ... }

void addFirst( A n ) { ... } A removeFirst() { ... }

int size() { ... } }

(84)

class Test {

public static void main( String[] argf ){

SLinkedList<String> l1

= new SLinkedList<String>();

l1.addFirst("Die Ersten werden");

l1.addFirst("die Letzten sein");

int i = l1.getFirst().indexOf("sein");

// liefert 13 SLinkedList<W3Seite> l2

= new SLinkedList<W3Seite>();

l2.addFirst(new W3Seite("Titel","Inhalt"));

l2.addFirst(new W3Seite("Title","Content"));

int i = l2.getFirst().indexOf("sein");

// liefert Übersetzungsfehler }

}

In Java 5 ist die Instanzierung der Typ-

Parameter nur durch Referenztypen gestattet:

Referenzen

ÄHNLICHE DOKUMENTE

(z.B. Quadrat) Lage, Position, Größe, Strichstärke, Strichfarbe, Füllfarbe, Füllmuster,

Damit der Node * next Pointer nicht unkontrolliert geändert werden kann sollte er jedoch privat sein.. • Ändern sie die Implementierung entsprechend ab, so dass Node * next

Schreiben Sie eine freie Funktion, die für ein gegebenes Testproblem und eine gegebene Qua- draturregel die Konvergenzordnung bestimmt. In einer verbesserten Variante kann diese

−3 2t 2 + 5 mit 10002 Stützstellen benötigte Zeit für alle vier Versionen einmal ohne Optimierung (Compiler-Option “-O0”) und einmal mit Optimie- rung (Compiler-Option

Knoten auf diesem Teil des Gebietsrandes (Dirichlet-Knoten) sind keine echten Freiheitsgrade und müssen beim Aufstellen der Matrix gesondert behandelt werden4. Die Anzahl dieser

Welche Teile sollte man als eigene Komponente programmieren, um sie in unterschiedlichen Berechnungen einsetzen zu können.. Programmieren Sie die Komponenten und daraus

Ein neugeborenes Paar wird nach einem Monat fruchtbar und bekommt somit nach zwei Monaten seine ersten Nachkommen.. Es soll ange- nommen werden, daß die Hasen

Um die ganze Konstruktion mit StackInterface und StackImpl vom Nutzer zu verstecken, erhält zu guter Letzt der Stack noch einen templatisierten Konstruktor, dieser bekommt einen