Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das...

24
Kapitel 5 Objektorientierte Programmierung in Java Grundz¨ uge der objektorientierten Programmierung haben wir bereits in Kapitel 2 kennengelernt, auch Teile der entsprechenden Java-Syntax. Dieses Kapitel soll nun etwas systematischer und ausf¨ uhrlicher noch einmal die entsprechenden Java-Konstrukte zur Umsetzung objektori- entierter Programmierung sowie Ausnahmen und Spezialf¨ alle behan- deln. Beginnen wollen wir jedoch mit einem Blick in die Historie, denn die Objektorientierung ist nur der (derzeitige) Schlusspunkt ei- ner l¨ angeren Entwicklung. 5.1 Traditionelle Konzepte der Softwaretechnik Folgende traditionelle Konzepte des Software-Engineering werden u.a. im objek- torientierten Ansatz verwendet: Datenabstraktion (bzw. Datenkapselung) und Information Hiding Die zentrale Idee der Datenkapselung ist, dass auf eine Datenstruktur nicht direkt zugegriffen wird, indem etwa einzelne Komponenten gelesen oder ge¨ andert werden, sondern, dass dieser Zugriff ausschließlich ¨ uber Zugriffs- operatoren erfolgt. Es werden also die Implementierungen der Operationen und die Datenstrukturen selbst versteckt. Vorteil: Implementierungdetails k¨ onnen beliebig ge¨ andert werden, ohne Auswirkung auf den Rest des Programmes zu haben. abstrakte Datentypen (ADT) Realisiert wird die Datenabstraktion duch den Einsatz abstrakter Datenty- pen, die Liskov & Zilles (1974) folgendermaßen definierten: 61

Transcript of Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das...

Page 1: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

Kapitel 5

ObjektorientierteProgrammierung in Java

Grundzuge der objektorientierten Programmierung haben wir bereitsin Kapitel 2 kennengelernt, auch Teile der entsprechenden Java-Syntax.Dieses Kapitel soll nun etwas systematischer und ausfuhrlicher nocheinmal die entsprechenden Java-Konstrukte zur Umsetzung objektori-entierter Programmierung sowie Ausnahmen und Spezialfalle behan-deln. Beginnen wollen wir jedoch mit einem Blick in die Historie,denn die Objektorientierung ist nur der (derzeitige) Schlusspunkt ei-ner langeren Entwicklung.

5.1 Traditionelle Konzepte der Softwaretechnik

Folgende traditionelle Konzepte des Software-Engineering werden u.a. im objek-torientierten Ansatz verwendet:

Datenabstraktion (bzw. Datenkapselung) und Information HidingDie zentrale Idee der Datenkapselung ist, dass auf eine Datenstruktur nichtdirekt zugegriffen wird, indem etwa einzelne Komponenten gelesen odergeandert werden, sondern, dass dieser Zugriff ausschließlich uber Zugriffs-operatoren erfolgt. Es werden also die Implementierungen der Operationenund die Datenstrukturen selbst versteckt.

Vorteil: Implementierungdetails konnen beliebig geandert werden, ohneAuswirkung auf den Rest des Programmes zu haben.

abstrakte Datentypen (ADT)Realisiert wird die Datenabstraktion duch den Einsatz abstrakter Datenty-pen, die Liskov & Zilles (1974) folgendermaßen definierten:

61

Page 2: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

“An abstract data type defines a class of abstract objects whichis completely characterized by the operations available on thoseobjects. This means that an abstract data type can be defined bydefining the characterizing operations for that type.”

Oder etwas pragnanter:

Datentyp = Menge(n) von Werten + Operationen darauf

abstrakter Datentyp = Operationen auf Werten, deren Reprasentation nichtbekannt ist. Der Zugriff erfolgt ausschließlich uber Operatoren.

Datenabstraktion fordert die Wiederverwendbarkeit von Programmteilen und dieWartbarkeit großer Programme.

5.1.1 Beispiel: Der ADT Stack

Stack: Eine Datenstruktur uber einem Datentyp T bezeichnet man als Stack1,wenn die Eintrage der Datenstruktur als Folge organisiert sind und es dieOperationen push, pop und peek gibt:

push fugt ein Element von T stets an das Ende der Folge.pop entfernt stets das letzte Element der Folge.peek liefert das letzte Element der Folge, ohne sie zu verandern.

Prinzip: last in first out (LIFO)

Typen der Operationen: initStack: −→ Stack

push: T × Stack −→ Stack

pop: Stack −→ Stack

peek: Stack −→ T

empty: Stack −→ boolean

Spezifikation der Operationen durch Gleichungen. Sei x eine Variable vom TypT, stack eine Variable vom Typ Stack:

empty (initStack) = true

empty (push (x, stack)) = false

peek (push (x, stack)) = x

pop (push (x, stack)) = stack

initStack und push sind Konstruktoren (sie konstruieren Terme), daher gibt eskeine Gleichungen fur sie.

1bedeutet soviel wie Keller oder Stapel

62

Page 3: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

5.2 Konzepte der objektorientierten Program-

mierung

Ziel jeglicher Programmierung ist:

• Modellierung von Ausschnitten der Realitat

• sachgerechte Abstraktion

• realitatsnahes Verhalten

• Nachbildung von Ahnlichkeit im Verhalten

• Klassifikation von Problemen

Je nach Problem konnen verschiedene Klassifikationen sachgerecht sein, dies istanhand eines Beispiels aus der Biologie in der Abbildung 5.1 dargestellt.

Tiere�������� ?

HHHHHHHjInsekten Fische Saugetiere

��� ?

@@@R

��� ?

@@@R

��� ?

@@@R

Tiere�������� ?

HHHHHHHjZuchttiere Wild Stortiere

��� ?

@@@R

��� ?

@@@R

��� ?

@@@R

Abbildung 5.1: Phylogenetische (oben) und okonomische Klassifizierung (un-ten).

Es werden immer bestimmte Funktionen auf bestimmte Daten angewendet. Sollnun die Architektur eines Systems (Modells) auf den Daten oder auf den Funk-tionen aufbauen?Grundsatzlich gibt es drei Vorgehensweisen:

63

Page 4: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

1. die funktionsorientierte

2. die datenorientierte

3. die objektorientierte

Der Kerngedanke des objektorientierten Ansatzes besteht darin, Daten und Funk-tionen zu verschmelzen. Im ersten Schritt werden die Daten abgeleitet, im zweitenSchritt werden den Daten die Funktionen zugeordnet, die sie manipulieren. Dieentstehenden Einheiten aus Daten und Funktionen werden Objekte genannt. Wirschranken den Begriff Objektorientierung gemaß folgender Gleichung von Coad& Yourdon weiter ein:

Objektorientierung = Klassen und Objekte+ Kommunikation mit Nachrichten+ Vererbung

Im folgenden erlautern wir diese Konzepte kurz.

5.3 Klassen und Objekte

Eine Klasse besteht konzeptionell aus einer Schnittstelle und einem Rumpf. In derSchnittstelle sind die nach außen zur Verfugung gestellten Methoden (und manch-mal auch offentlich zugangliche Daten), sowie deren Semantik aufgelistet. DieseAuflistung wird oft als Vertrag oder Nutzungsvorschrift zwischen dem Entwerferder Klasse und dem sie verwendenen Programmierer gedeutet. Der Klassenrumpfenthalt alle von außen unsichtbaren Implementierungdetails.Historisch gesehen ist der Klassenbegriff alter als der Begriff des abstrakten Da-tentypen (ADT). In der Programmiersprache Simula 67 gab es bereits Klassenals Mechanismus zur Datenkapselung (Abstakte Datentypen wurden erstmals1974 von Liskov & Zilles definiert). Der Kerngedanke der Objektorientierung,Daten und Funktionen konsequent als Objekte zusammenzufassen, wird jedochauf die Programmiersprache Smalltalk zuruckgefuhrt (entwickelt seit Beginn der70er Jahre).

5.4 Kommunikation mit Nachrichten

Objekte besitzen die Moglichkeit, mit Hilfe ihrer Methoden Aktionen auszufuh-ren. Das Senden einer Nachricht stoßt die Ausfuhrung einer Methode an. EineNachricht besteht aus einem Empfanger (das Objekt, das die Aktionen ausfuhrensoll), einem Selektor (die Methode, deren Aktionen auszufuhren sind) und gege-benenfalls aus Argumenten (Werte, auf die wahrend der Ausfuhrung der Aktionzugegriffen wird).

64

Page 5: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

5.5 Vererbung

Gleichartige Objekte werden zu Klassen zusammengefasst. Haufig besitzen Ob-jekte zwar bestimmte Gemeinsamkeiten, sind aber nicht vollig gleichartig. Umsolche Ahnlichkeiten auszudrucken, ist es moglich, zwischen Klassen Vererbungs-beziehungen festzulegen. Dazu wird das Verhalten einer existierenden Klasse er-weitert. Die Erweiterung erzeugt eine von ihr alle Attribute und Methoden er-bende neue Klasse, die um weitere Attribute und Methoden erganzt wird. Dieneue Klasse wird Unterklasse, die ursprungliche Klasse Oberklasse genannt.

Gemeinsamkeiten: in der OberklasseUnterschiede: in der Unterklasse

Eine Unterklasse kann auch von der Oberklasse ererbte Methoden redefinieren(uberschreiben). Wir sprechen von Einfachvererbung, wenn jede neue Klasse ge-nau eine Oberklasse erweitert (Abbildung 5.2).

Object

��� ?

@@@R

System Math Point

@@@R...

Abbildung 5.2: Einfachvererbung (Java)

. . .

���

@@@R

Tiere Pflanzen

@@@R

���

Fleischfresser

@@@R

. . .

Abbildung 5.3: Mehrfachvererbung

65

Page 6: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

Wenn eine Klasse mehrere Oberklassen besitzen kann, sprechen wir von Mehr-fachvererbung (Abbildung 5.3). In Java gibt es nur Einfachvererbung (aus gutemGrund). Die einzige Klasse, die keine Oberklasse erweitert, ist die vordefinierteKlasse Object. Klassen, die nicht explizit andere Klassen erweitern, erweiternimplizit die Klasse Object. Alle Objektreferenzen sind in polymorpher Weise vonder Klasse Object, so dass Object die generische Klasse fur Referenzen ist, diesich auf Objekte jeder beliebigen Klasse beziehen konnen. Das nachste Beispielverdeutlicht dies.

Object oref = new Point();

oref = "eine Zeichenkette";

5.6 Konstruktoren und Initialisierungsblocke

Einem neu erzeugten Objekt wird ein Anfangszustand zugewiesen. Datenfelderkonnen bei ihrer Deklaration mit einem Wert initialisiert werden, was manchmalausreicht, um einen sinnvollen Anfangszustand sicherzustellen. Oft ist aber mehrals nur einfache Dateninitialisierung zur Erzeugung eines Anfangszustands no-tig; der erzeugende Code muss vielleicht Anfangsdaten liefern oder Operationenausfuhren, die nicht als einfache Zuweisungen ausgedruckt werden konnen. Ummehr als einfache Initialisierungen bewerkstelligen zu konnen, konnen KlassenKonstruktoren enthalten. Konstruktoren sind keine Methoden, aber methoden-ahnlich: Sie haben denselben Namen wie die von ihnen initialisierte Klasse, ha-ben keine oder mehrere Parameter und keinen Ruckgabetyp. Bei der Erzeugungeines Objekts mit new werden eventuelle Parameterwerte nach dem Klassenna-men in einem Klammernpaar angegeben. Bei der Objekterzeugung werden zuerstden Instanzvariablen ihre voreingestellten Anfangswerte zugewiesen, dann ihreInitialisierungsausdrucke berechnet und zugewiesen und dann der Konstruktoraufgerufen.

Im folgenden benutzen wir die Klasse Circle als Standardbeispiel. Ein Kreisbesteht aus einer x-Koordinate, einer y-Koordinate sowie dem Radius r. Des-weiteren wird die Anzahl der erzeugten Kreise gezahlt durch die AnweisungnumCircles++;, die bei jedem Aufruf des parameterlosen Konstruktors ausge-fuhrt wird.

public class Circle {

int x=0, y=0, r=1;

static int numCircles=0;

public Circle() {

numCircles++;

}

66

Page 7: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

public double circumference() {

return 2*Math.PI*r;

}

public double area() {

return Math.PI*r*r;

}

public static void main(String[] args) {

Circle c = new Circle();

System.out.println(c.r);

System.out.println(c.circumference());

System.out.println(c.area());

System.out.println(numCircles);

}

}

Statt des parameterlosen Konstruktors hatten wir in der Klasse auch einen Kon-struktor mit drei Parametern definieren konnen, der nicht nur Einheitskreise er-zeugen kann:

public Circle(int xCoord, int yCoord, int radius) {

numCircles++;

x = xCoord;

y = yCoord;

r = radius;

}

Standardmaßig benennt man die Parametervariablen im Konstruktor genauso wiedie Variablen in der Klasse. Da aber hierbei Namenskonflikte entstehen, muss mandie Variable des Objektes mit this.Variable referenzieren.

public Circle(int x, int y, int r) {

numCircles++;

this.x = x;

this.y = y;

this.r = r;

}

Fur eine Klasse kann es in Java auch mehrere Konstruktoren geben. Diese mus-sen sich allerdings in der Anzahl der Attribute bzw. deren Typen unterscheiden.Dies nennt man Uberladen von Konstruktoren. In der folgenden Klasse gibt esdrei Konstruktoren namens Circle. Die Konstruktoren mit Parametern rufen den

67

Page 8: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

parameterlosen Konstruktor mittels this() auf. Dies hat den Vorteil, dass Ande-rungen an den Konstruktoren nicht an drei Stellen gemacht werden mussen (wasfehleranfallig ist), sondern nur im parameterlosen Konstruktor. Um die Anzahlder erzeugten Kreise zu zahlen, muss man die Programmzeile numCircles++; nurdem parameterlosen Konstruktor hinzufugen.

public class Circle {

int x = 0, y = 0, r = 1;

static int numCircles;

public Circle() {

numCircles++;

}

public Circle(int x, int y, int r) {

this();

this.x = x;

this.y = y;

this.r = r;

}

public Circle(int r) {

this(0,0,r);

}

public static void main(String[] args) {

Circle c1 = new Circle();

Circle c2 = new Circle(1,1,2);

Circle c3 = new Circle(3);

System.out.println(numCircles);

}

}

Klassenvariablen werden initialisiert, wenn die Klasse das erste Mal geladen wird.Das Analogon zu Konstruktoren, um komplexe Initialisierungen von Klassenva-riablen durchzufuhren, sind die sogenannten Initialisierungsblocke. Diese Blockewerden durch static {. . . } umschlossen, wie folgendes Beispiel demonstriert.

Beispiel 5.6.1 (Flanagan [3], S. 59)

public class Circle {

public static double[] sines = new double[1000];

public static double[] cosines = new double[1000];

68

Page 9: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

static {

double x, delta_x;

int i;

delta_x = (Math.PI/2)/(1000-1);

for(i=0,x=0; i<1000; i++,x+=delta_x) {

sines[i] = Math.sin(x);

cosines[i] = Math.cos(x);

}

}

}

Es konnen mehrere klassenbezogene Initialisierungsblocke in einer Klasse enthal-ten sein. Die Klasseninitialisierung erfolgt von links nach rechts und von obennach unten.

5.7 Java-Klassen als Realisierung und Implemen-

tierung von abstrakten Datentypen

Durch den Modifizierer private konnen wir Implementierungsdetails verstecken,denn als private deklarierte Attribute und Methoden sind nur in der Klasseselbst zugreifbar2. Folgende Klasse implementiert einen ADT Stack mittels einesFeldes:

public class Stack {

private Object[] stack;

private int top = -1;

private static final int CAPACITY = 10000;

/** liefert einen leeren Keller. */

public Stack() {

stack = new Object[CAPACITY];

}

/** legt ein Objekt im Keller ab und liefert dieses Objekt

zusaetzlich zurueck. */

public Object push(Object item) {

stack[++top] = item;

return item;

2Synonyme fur Zugreifbarkeit sind: Gultigkeit bzw. Sichtbarkeit.

69

Page 10: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

}

/** entfernt das oberste Objekt vom Keller und liefert es zurueck.

Bei leerem Keller wird eine Fehlermeldung ausgegeben und

null zurueckgeliefert. */

public Object pop() {

if (empty()) {

System.out.println("Method pop: empty stack");

return null;

}

else

return stack[top--];

}

/** liefert das oberste Objekt des Kellers, ohne ihn zu veraendern.

Bei leerem Keller wird eine Fehlermeldung ausgegeben und

null zurueckgeliefert. */

public Object peek() {

if (empty()) {

System.out.println("Method peek: empty stack");

return null;

}

else

return stack[top];

}

/** liefert true genau dann, wenn der Keller leer ist. */

public boolean empty() {

return (top == -1);

}

/** liefert die Anzahl der Elemente des Kellers. */

public int size() {

return top+1;

}

}

Der Dokumentationskommentar /** ... */ wird zur automatischen Dokumen-tierung der Attribute und Methoden einer Klasse benutzt. Das Programm javadoc

70

Page 11: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

generiert ein HTML-File, in dem alle sichtbaren Attribute und Methoden mit de-ren Parameterlisten aufgezeigt und dokumentiert sind.

> javadoc Stack.java

Dieses HTML-File ist der Vertrag (die Schnittstelle) der Klasse und entsprichtdem ADT Stack, wobei die Operationen bzw. Methoden allerdings nur naturlich-sprachlich spezifiziert wurden. Die obige verbale Spezifikation entspricht weitge-hend der der vordefinierten Java-Klasse Stack (genauer java.util.Stack). Manbeachte, dass (aus diesem Grund) die obige Spezifikation von der Gleichungsspe-zifikation aus dem Unterabschnitt 5.1.1 abweicht.

5.8 Methoden in Java

Methoden konnen wie Konstruktoren uberladen werden. In Java besitzt jede Me-thode eine Signatur, die ihren Namen sowie die Anzahl und Typen der Parameterdefiniert. Zwei Methoden konnen denselben Namen haben, wenn ihre Signaturenunterschiedliche Anzahlen oder Typen von Parametern aufweisen; dies wird alsUberladen von Methoden bezeichnet. Wird eine Methode aufgerufen, vergleichtder Ubersetzer die Anzahl und die Typen der Parameter mit den verfugbarenSignaturen, um die passende Methode zu finden.

Die Parameterubergabe zu Methoden erfolgt in Java durch Wertubergabe (callby value). D.h., dass Werte von Parametervariablen in einer Methode Kopien dervom Aufrufer angegebenen Werte sind. Das nachste Beispiel verdeutlicht dies.

public class CallByValue {

public static int sqr(int i) {

i = i*i;

return(i);

}

public static void main(String[] args) {

int i = 3;

System.out.println(sqr(i));

System.out.println(i);

}

}

> java CallByValue

9

3

71

Page 12: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

Allerdings ist zu beachten, dass nicht Objekte, sondern Objektreferenzen uber-geben werden. Wir betrachten unser Standardbeispiel Circle in folgender abge-speckter Form (gemaß der Devise, Implementierungsdetails zu verbergen, werdendie Datenfelder als private deklariert).

public class Circle {

private int x,y,r;

public Circle(int x, int y, int r) {

this.x = x;

this.y = y;

this.r = r;

}

public double circumference() {

return 2 * Math.PI * r;

}

public double area() {

return Math.PI * r * r;

}

public static void setToZero (Circle arg) {

arg.r = 0;

arg = null;

}

public static void main(String[] args) {

Circle kreis = new Circle(10,10,1);

System.out.println("vorher : r = "+kreis.r);

setToZero(kreis);

System.out.println("nachher: r = "+kreis.r);

}

}

> java Circle

vorher : r = 1

nachher: r = 0

Dieses Verhalten entspricht jedoch nicht der Parameterubergabe call by reference,denn bei der Wertubergabe wird eine Kopie der Referenz erzeugt und die ur-sprungliche Referenz bleibt erhalten. Bei call by reference wurde die ubergebene

72

Page 13: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

Referenz eben nicht kopiert und daher in der Methode setToZero auf null ge-setzt.

5.9 Unterklassen und Vererbung in Java

Wir wollen die Klasse Circle so erweitern, dass wir deren Instanzen auch gra-phisch darstellen konnen. Da ein solcher “graphischer Kreis” ein Kreis ist (esherrscht eine “ist-ein” Beziehung), erweitern wir die Klasse Circle zu der neuenKlasse GraphicCircle3. Durch das Schlusselwort extends wird GraphicCircle

eine Unterklasse von Circle. Wir sagen auch GraphicCircle erweitert die (Ober-)Klasse Circle. Damit erbt die Klasse GraphicCircle alle Attribute und Metho-den von Circle, nur die als private deklarierten sind nicht uber ihren Namenzugreifbar. Damit ist unsere Entscheidung, die Attribute x, y und r privat zuhalten, nicht mehr sinnvoll. Um diese Attribute dennoch vor unerwunschten Zu-griffen zu schutzen, werden sie als protected deklariert. Damit sind sie zugreifbarfur Unterklassen und werden an diese vererbt, in anderen Klassen sind sie nichtzugreifbar4.

import java.awt.Color;

import java.awt.Graphics;

public class GraphicCircle extends Circle {

protected Color outline; // Farbe der Umrandung

protected Color fill; // Farbe des Inneren

public GraphicCircle(int x,int y,int r,Color outline) {

super(x,y,r);

this.outline = outline;

this.fill = Color.lightGray;

}

public GraphicCircle(int x,int y,int r,Color outline,Color fill) {

this(x,y,r,outline);

this.fill = fill;

}

public void draw(Graphics g) {

g.setColor(outline);

3Nur wenn eine solche “ist-ein” Beziehung herrscht, ist eine Erweiterung sinnvoll. Beispiels-weise ware eine Erweiterung der Klasse Circle zu einer Klasse Ellipse ein Design-Fehler, daeine Ellipse kein Kreis ist. Umgekehrt ware dieses sinniger, da ein Kreis eine Ellipse ist.

4Es sei denn, die Klasse befindet sich im selben Paket (siehe Abschnitt 2.4.2)!

73

Page 14: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

g.drawOval(x-r, y-r, 2*r, 2*r);

g.setColor(fill);

g.fillOval(x-r, y-r, 2*r, 2*r);

}

public static void main(String[] args) {

GraphicCircle gc = new GraphicCircle(0,0,100,Color.red,Color.blue);

double area = gc.area();

System.out.println(area);

Circle c = gc;

double circumference = c.circumference();

System.out.println(circumference);

GraphicCircle gc1 = (GraphicCircle) c;

Color color = gc1.fill;

System.out.println(color);

}

}

Color und Graphics sind vordefinierte Klassen, die durch import zugreifbar ge-macht werden (vgl. Abschnitt 2.4.2). Diese Klassen werden z.B. in [3] beschrieben.Zum Verstandnis reicht es hier zu wissen, dass der erste Konstruktor den Kon-struktor seiner Oberklasse aufruft (vgl. Abschnitt 5.11) und das Kreisinnere dieFarbe hellgrau erhalt, sowie, dass die Methode draw einen farbigen Kreis zeichnet.

Da GraphicCircle alle Methoden von Circle erbt, konnen wir z.B. den Fla-cheninhalt eines Objektes gc vom Typ GraphicCircle berechen durch:

double area = gc.area();

Jedes Objekt gc vom Typ GraphicCircle ist ebenfalls ein Objekt vom TypCircle bzw. vom Typ Object. Deshalb sind folgende Zuweisungen korrekt.

Circle c = gc;

double area = c.area();

Man kann c durch casting5 in ein Objekt vom Typ GraphicCircle zuruckver-wandeln.

GraphicCircle gc1 = (GraphicCircle)c;

Color color = gc1.fill;

Die oben gezeigte Typumwandlung funktioniert nur, weil c tatsachlich ein Objektvom Typ GraphicCircle ist.

5explizite Typumwandlung

74

Page 15: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

5.10 Uberschreiben von Methoden und Verdecken

von Datenfeldern

Wir betrachten folgendes Java-Programm (Arnold & Gosling [1], S. 66):

public class SuperShow {

public String str = "SuperStr";

public void show() {

System.out.println("Super.show: "+str);

}

}

public class ExtendShow extends SuperShow {

public String str = "ExtendStr";

public void show() {

System.out.println("Extend.show: "+str);

}

public static void main(String[] args) {

ExtendShow ext = new ExtendShow();

SuperShow sup = ext;

sup.show();

ext.show();

System.out.println("sup.str = "+sup.str);

System.out.println("ext.str = "+ext.str);

}

}

Verdecken von Datenfeldern

Jedes ExtendShow-Objekt hat zwei String-Variablen, die beide str heißen undvon denen eine ererbt wurde. Die neue Variable str verdeckt die ererbte; wirsagen auch die ererbte ist verborgen. Sie existiert zwar, man kann aber nichtmehr durch Angabe ihres Namens auf sie zugreifen.

Uberschreiben von Methoden

Die Methode show() der Klasse ExtendShow uberschreibt die gleichnamige Me-thode der Oberklasse. Dies bedeutet, dass die Implementierung der Methode derOberklasse durch eine neue Implementierung der Unterklasse ersetzt wird. Dabeimussen Signatur und Ruckgabetyp dieselben sein. Uberschreibende Methoden

75

Page 16: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

besitzen ihre eigenen Zugriffsangaben. Eine in der Oberklasse als protected de-klarierte Methode kann wieder als protected redeklariert werden6, oder sie wirdmit dem Modifizierer public erweitert. Der Gultigkeitsbereich kann aber nichtz.B durch private eingeschrankt werden. (Eine Begrundung dafur findet man inArnold & Gosling [1], S. 66.)

Wenn eine Methode von einem Objekt aufgerufen wird, dann bestimmt immerder tatsachliche Typ des Objektes, welche Implementierung benutzt wird. Beieinem Zugriff auf ein Datenfeld wird jedoch der deklarierte Typ der Referenzverwendet. Daher erhalten wir folgende Ausgabe beim Aufruf der main-Methode:

> java ExtendShow

Extend.show: ExtendStr

Extend.show: ExtendStr

sup.str = SuperStr

ext.str = ExtendStr

Die Objektreferenz super

Das Schlusselwort super kann in allen objektbezogenen Methoden und Konstruk-toren verwendet werden. In Datenfeldzugriffen und Methodenaufrufen stellt eseine Referenz zum aktuellen Objekt als eine Instanz seiner Oberklasse dar. Wennsuper verwendet wird, so bestimmt der Typ der Referenz uber die Auswahlder zu verwendenden Methodenimplementierung. Wir illustrieren dies wieder aneinem Beispielprogramm.

public class T1 {

protected int x = 1;

protected String s() {

return "T1";

}

}

public class T2 extends T1 {

protected int x = 2;

protected String s() {

return "T2";

}

protected void test() {

System.out.println("x= "+x);

System.out.println("super.x= "+super.x);

System.out.println("((T1)this).x= "+((T1)this).x);

6Dies ist die ubliche Vorgehensweise.

76

Page 17: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

System.out.println("s(): "+s());

System.out.println("super.s(): "+super.s());

System.out.println("((T1)this).s(): "+((T1)this).s());

}

public static void main(String[] args) {

new T2().test();

}

}

> java T2

x= 2

super.x= 1

((T1)this).x= 1

s(): T2

super.s(): T1

((T1)this).s(): T2

5.11 Konstruktoren in Unterklassen

In Konstruktoren der Unterklasse kann direkt einer der Oberklassenkonstruktorenmittels des super() Konstruktes aufgerufen werden.

Achtung: Der super-Aufruf muss die erste Anweisung des Konstruktors sein!

Wird kein Oberklassenkonstruktor explizit aufgerufen, so wird der parameterloseKonstruktor der Oberklasse automatisch aufgerufen, bevor die Anweisungen desneuen Konstruktors ausgefuhrt werden. Verfugt die Oberklasse nicht uber einenparameterlosen Konstruktor, so muss ein Konstruktor der Oberklasse explizitmit Parametern aufgerufen werden, da es sonst einen Fehler bei der Ubersetzunggibt.

Ausnahme: Wird in der ersten Anweisung eines Konstruktors ein anderer Kon-struktor derselben Klasse mittels this aufgerufen, so wird nicht automa-tisch der parameterlose Oberklassenkonstruktor aufgerufen.

Java liefert einen voreingestellten parameterlosen Konstruktor fur eine erweitern-de Klasse, die keinen Konstruktor enthalt. Dieser ist aquivalent zu:

public class ExtendedClass extends SimpleClass {

public ExtendedClass () {

super();

}

}

77

Page 18: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

Der voreingestellte Konstruktor hat dieselbe Sichtbarkeit wie seine Klasse.

Ausnahme: Enthalt die Oberklasse keinen parameterlosen Konstruktor, so mussdie Unterklasse mindestens einen Konstruktor bereitstellen.

5.12 Reihenfolgeabhangigkeit von Konstrukto-

ren

Wird ein Objekt erzeugt, so werden zuerst alle seine Datenfelder auf voreinge-stellte Werte initialisiert. Jeder Konstruktor durchlauft dann drei Phasen:

• Aufruf des Konstruktors der Oberklasse.

• Initialisierung der Datenfelder mittels der Initialisierungsausdrucke.

• Ausfuhrung des Rumpfes des Konstruktors.

Beispiel 5.12.1

public class X {

protected String infix = "fel";

protected String suffix;

protected String alles;

public X() {

suffix = infix;

alles = verbinde("Ap");

}

public String verbinde(String original) {

return (original+suffix);

}

}

public class Y extends X {

protected String extra = "d";

public Y() {

suffix = suffix+extra;

alles = verbinde("Biele");

}

public static void main(String[] args) {

78

Page 19: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

new Y();

}

}

Die Reihenfolge der Phasen ist ein wichtiger Punkt, wenn wahrend des AufbausMethoden aufgerufen werden (wie im obigen Beispiel). Wenn man eine Methodeaufruft, erhalt man immer die Implementierung dieser Methode fur den derzeiti-gen Objekttyp. Verwendet die Methode Datenfelder des derzeitigen Typs, dannsind diese vielleicht noch nicht initialisiert worden. Die folgende Tabelle zeigt dieInhalte der Datenfelder beim Aufruf der main-Methode (d.h. des Y-Konstruktors).

Schritt Aktion infix extra suffix alles

0 Datenfelder auf Voreinstellungen1 Y-Konstruktor aufgerufen2 X-Konstruktor aufgerufen3 X-Datenfeld initialisiert fel4 X-Konstruktor ausgefuhrt fel fel Apfel5 Y-Datenfeld initialisiert fel d fel Apfel6 Y-Konstruktor ausgefuhrt fel d feld Bielefeld

Die wahrend des Objektaufbaus aufgerufenen Methoden sollten unter Beachtungdieser Faktoren entworfen werden. Auch sollte man alle vom Konstruktor aufge-rufenen Methoden sorgfaltig dokumentieren, um diejenigen, die den Konstruktoruberschreiben mochten, von den potentiellen Einschrankungen in Kenntnis zusetzen.

5.13 Abstrakte Klassen und Methoden

Ein sehr nutzliches Merkmal der objektorientierten Programmierung ist das derabstrakten Klasse. Mittels abstrakter Klassen konnen Klassen deklariert werden,die nur einen Teil der Implementierung definieren und erweiternden Klassen diespezifische Implementierung einiger oder aller Methoden uberlassen. Abstraktionist hilfreich, wenn Teile des Verhaltens fur alle oder die meisten Objekte einesgegebenen Typs richtig sind, es aber auch Verhalten gibt, das nur fur bestimmteObjekte sinnvoll ist und nicht fur alle. Es gilt:

• eine abstrakte Methode hat keinen Rumpf;

• jede Klasse, die eine abstrakte Methode enthalt, ist selbst abstrakt undmuss als solche gekennzeichnet werden;

• jede abstrakte Klasse muss mindestens eine abstrakte Methode besitzen;

79

Page 20: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

• man kann von einer abstrakten Klasse keine Objekte erzeugen;

• von einer Unterklasse einer abstrakten Klasse kann man Objekte erzeugen– vorausgesetzt sie uberschreibt alle abstrakten Methoden der Oberklasseund implementiert diese;

• eine Unterklasse, die nicht alle abstrakten Methoden der Oberklasse imple-mentiert ist selbst wieder abstrakt.

Beispiel 5.13.1 (vgl. Arnold & Gosling [1], S. 72 ff.)Wir wollen ein Programm zur Bewertung von Programm(teilen) schreiben. UnsereImplementierung weiß, wie eine Bewertung gefahren und gemessen wird, aber siekann nicht im voraus wissen, welches andere Programm bewertet werden soll.Die meisten abstrakten Klassen entsprechen diesem Muster: eine Klasse ist zwarExperte in einem Bereich, doch ein fehlendes Stuck kommt aus einer anderenKlasse. In unserem Beispiel ist das fehlende Stuck ein Code, der bewertet werdenmuss. Eine solche Klasse konnte wie folgt aussehen:

public abstract class Benchmark {

public abstract void benchmark();

public long repeat(int count) {

long start = System.currentTimeMillis();

for(int i=0; i<count; i++)

benchmark();

return (System.currentTimeMillis()-start);

}

}

Die Klasse ist als abstract deklariert, weil eine Klasse mit abstrakten Methodenselbst als abstract deklariert werden muss. Diese Redundanz hilft dem Leser,schnell zu erfassen, dass die Klasse abstrakt ist, ohne alle Methoden der Klassedurchzusehen, ob zumindest eine von ihnen abstrakt ist.

Die Methode repeat stellt das Sachwissen zur Bewertung bereit. Sie weiß, wie derZeitbedarf fur die Ausfuhrung von count Aufrufen des zu bewertenden Codes zumessen ist. Wird die Messung komplizierter (vielleicht durch Messung der Zeitenjeder Ausfuhrung und Berechnung der Varianz als statistisches Maß daruber), sokann diese Methode verbessert werden, ohne die Implementierung des speziellenzu bewertenden Codes in einer erweiternden Klasse zu beeinflussen.

Die abstrakte Methode benchmark muss von jeder selbst nicht wieder abstraktenUnterklasse implementiert werden. Deshalb gibt es in dieser Klasse keine Imple-mentierung, sondern nur eine Deklaration. Hier nun ein Beispiel einer einfachenErweiterung von Benchmark:

80

Page 21: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

public class MethodBenchmark extends Benchmark {

public void benchmark() { }

public static void main(String[] args) {

int count = Integer.parseInt(args[0]);

long time = new MethodBenchmark().repeat(count);

System.out.println(count+" Methodenaufrufe in "+time+

" Millisekunden");

}

}

Die Implementierung von benchmark ist denkbar einfach: die Methode hat einenleeren Rumpf. Man kann daher den Zeitbedarf von n Methodenaufrufen feststel-len, indem man die main-Methode der Klasse MethodBenchmark mit der Angaben der gewunschten Testwiederholungen laufen lasst.

5.14 Aufgaben

Aufgabe 5.14.1 Eine Folge heißt Schlange (engl. queue), wenn Elemente einesgegebenen Datentyps T nur am Ende eingefugt und am Anfang entfernt werdendurfen (FIFO-Prinzip: first in first out).In Analogie zum abstrakten Datentypen Stack sollen Sie hier einen abstraktenDatentypen Queue spezifizieren, der folgende Operationen enthalt:

initQueue: Erzeugen einer leeren Schlange.enqueue: Einfugeoperation.dequeue: Entfernt das vorderste Element.peek: Liefert das vorderste Element der Schlange, ohne die Schlange zu ver-

andern.empty: Liefert true gdw. die Schlange leer ist.

Die Operationen sind durch Gleichungen zu spezifizieren.Hinweis: Fallunterscheidungen uber Schlangen mit nur einem Element und Schlan-gen mit mindestens zwei Elementen sind hilfreich.

Aufgabe 5.14.2 Implementieren Sie eine Klasse Rent in Java, die die KlasseStack benutzt. Nehmen Sie an, Herr Meier ist Besitzer eines Buches. Herr Meier,der Eigentumer, wird im ersten Eintrag des Stapels beschrieben. Leiht jemanddas Buch aus, vielleicht Herr Schmidt, wird dessen Name auf dem Stapel abge-legt. Verleiht Herr Schmidt es wiederum weiter, z.B. an Herrn Muller, erscheintdessen Name an der Spitze des Stapels, usw. Wird das Buch an seinen Vorgangerzuruckgegeben, wird der Name des Entleihers vom Stapel entfernt. Z.B. wird derName Muller vom Stapel entfernt, wenn er das Buch Herrn Schmidt zuruckgibt.Der letzte Name wird nie aus dem Stapel entfernt, denn sonst ginge die Informa-

81

Page 22: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

tion uber den Bucheigentumer verloren.Hinweis: Die Klassen Stack und Rent mussen sich im selben Verzeichnis befinden.

Aufgabe 5.14.3 (a) Harry Hacker hat wieder einmal programmiert, ohne genaunachzudenken. Er wollte mit der folgenden Methode einen Stack kopieren (d.h.einen neuen Stack mit den gleichen Werten kreieren):

public static Stack copy(Stack stack) {

Stack cpStack = stack;

return cpStack;

}

Was hat Harry nicht bedacht? Und wie kann man Harry helfen? Schreiben Sie inJava eine Methode betterCopy, die den ursprunglichen Gedanken von Harry er-fullt. Erganzen Sie ebenfalls eine main-Methode, in der die beiden copy-Methodenaufgerufen werden, so dass der Unterschied deutlich wird.(b) Implementieren Sie statt der klassenbezogenen Methode betterCopy eine ob-jektbezogene Methode gleichen Namens, die dasselbe leistet.

Aufgabe 5.14.4 Objektorientierte Programmierung ermoglicht eine relativ ein-fache Modellierung von Ausschnitten der realen Welt. In dieser Aufgabe sollen Sieeine Klasse Vehicle implementieren, die zwei Unterklassen enthalt: (i) motorge-triebene Fahrzeuge (Motorrad, Auto, Bus, LKW, ...) und (ii) personengetriebeneFahrzeuge (Fahrrad, Tretroller, Inliner, ...). Diese Klassen sollen wiederum Unter-klassen besitzen. Z.B. kann man die motorgetriebenen Fahrzeuge in zweiradrige,vierradrige und mehr-als-vierradrige Fahrzeuge unterteilen. Modellieren Sie Fahr-zeuge in sinnvoller Klassenhierarchie. Obligatorisch sind folgende Attribute undMethoden:(a) Die Klasse Vehicle sollte mindestens Datenfelder fur die aktuelle Geschwin-digkeit, die aktuelle Richtung in Grad, den Preis und den Besitzernamen enthal-ten.(b) Eine Klasse EnginePoweredVehicle soll mindestens Datenfelder uber die Lei-stung in kW, Front- oder Heckantrieb und Hochstgeschwindigkeit besitzen.(c) Die Klasse PersonPoweredVehicle soll mindestens ein Datenfeld besitzen,das Auskunft uber die Anzahl der Personen gibt, die das Fahrzeug antreiben.(d) Es sollen Klassen Car, Bus, Truck, Bike, Motorbike, Inliner und Scooter

geben. Alle besitzen ein Datenfeld fur eine eindeutige Identifikationsnummer.(e) Schreiben Sie Methoden, die die einzelnen Eigenschaften verandern konn-nen. Z.B. sollte die Klasse EnginePoweredVehicle eine Methode besitzen, diees ermoglicht, die Hochstgeschwindigkeit zu setzen (verandern). Jede Klasse sollmindestens zwei Methoden enthalten!(f) Schreiben Sie schließlich eine Klasse SomeVehicles mit einer main-Methode,die sechs Fahrzeuge konstruiert. Darauf sollen jeweils mindestens zwei Methodenangewendet werden.

82

Page 23: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

(g) Wenn diese Aufgabe Sie unterfordert, brechen Sie die Ubung ab. Hauptsache,Sie haben das Prinzip verstanden.

Aufgabe 5.14.5 Schreiben Sie eine Klasse LinkedList, die ein Datenfeld vomTyp Object und eine Referenz zum nachsten LinkedList-Element in der Listeenthalt. Schreiben Sie zusatzlich fur Ihre Klasse LinkedList eine main-Methode,die einige Objekte vom Typ Vehicle erzeugt und sie aufeinanderfolgend in dieListe einfugt. Konnen Sie mit Ihrer Implementierung eine leere Liste erzeugen?

Aufgabe 5.14.6 Sie haben schon die Klasse Circle kennengelernt, die drei Da-tenfelder besaß: die Koordinaten x und y, die den Mittelpunkt eines Kreisesangeben, und eine Variable r, die den Radius enthalt.(a) Schreiben Sie analog dazu ein Klasse Rectangle, die vier Datenfelder besitzt.Je zwei Koordinaten x1 und y1, sowie x2 und y2, beschreiben die Endpunkte derDiagonalen eines Rechtecks, das damit vollstandig beschrieben ist.(b) Schreiben Sie eine abstrakte Klasse Shape, die die beiden abstrakten Metho-den area (berechnet den Inhalt eines geometrischen Objekts) und circumference

(berechnet den Umfang eines geometrischen Objekts) beinhaltet.(c) Die Klassen Circle und Rectangle sollen als erweiternde Klassen von Shape

implementiert werden.(d) Schreiben Sie dann noch eine main Methode, in der ein Array von Shape-Objekten der Lange 5 konstruiert wird, das Circle- und/oder Rectangle-Objekteenthalten kann. Dann soll das Array mit 5 entsprechenden Objekten gefullt wer-den und der Gesamtumfang bzw. der Gesamtflacheninhalt aller Objekte ausge-geben werden.

83

Page 24: Kapitel 5 Objektorientierte Programmierung in Java · Nachricht besteht aus einem Empf¨anger (das Objekt, das die Aktionen ausf uhren¨ soll), einem Selektor (die Methode, deren

84