Folienset 5: Objektorientierte Programmierung in Java

Werbung
Objektorientierte Programmierung in Java
Algorithmen und Datenstrukturen II
1
Traditionelle Konzepte der Softwaretechnik
Folgende traditionelle Konzepte des Software-Engineering werden u.a. im
objektorientierten 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ändert
werden, sondern, dass dieser Zugriff ausschließlich über Zugriffsoperatoren erfolgt.
Es werden also die Implementierungen der Operationen und die Datenstrukturen
selbst versteckt.
Vorteil: Implementierungdetails können beliebig geändert werden, ohne
Auswirkung auf den Rest des Programmes zu haben.
abstrakte Datentypen (ADT)
Realisiert wird die Datenabstraktion duch den Einsatz abstrakter Datentypen, die
Liskov & Zilles (1974) folgendermaßen definierten:
Algorithmen und Datenstrukturen II
2
“An abstract data type defines a class of abstract objects which is
completely characterized by the operations available on those objects. This
means that an abstract data type can be defined by defining the
characterizing operations for that type.”
Algorithmen und Datenstrukturen II
3
Oder etwas prägnanter:
Datentyp = Menge(n) von Werten + Operationen darauf
abstrakter Datentyp = Operationen auf Werten, deren Repräsentation nicht
bekannt ist. Der Zugriff erfolgt ausschließlich über Operatoren.
Datenabstraktion fördert die Wiederverwendbarkeit von Programmteilen und die
Wartbarkeit großer Programme.
Algorithmen und Datenstrukturen II
4
Beispiel: Der ADT Stack
Stack: Eine Datenstruktur über einem Datentyp T bezeichnet man als Stacka , wenn
die Einträge der Datenstruktur als Folge organisiert sind und es die Operationen
push, pop und peek gibt:
push fügt 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 verändern.
Prinzip: last in first out (LIFO)
a bedeutet
soviel wie Keller oder Stapel
Algorithmen und Datenstrukturen II
5
Operatoren
Typen der Operationen:
T × Stack
−→ Stack
pop:
Stack
−→ Stack
peek:
Stack
−→ T
empty:
Stack
−→ boolean
push:
Algorithmen und Datenstrukturen II
−→ Stack
initStack:
6
Spezifikation
Spezifikation der Operationen durch Gleichungen. Sei x eine Variable vom Typ T,
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 es
keine Gleichungen für sie.
Algorithmen und Datenstrukturen II
7
Konzepte der objektorientierten Programmierung
Ziel jeglicher Programmierung ist:
• Modellierung von Ausschnitten der Realität
• sachgerechte Abstraktion
• realitätsnahes Verhalten
• Nachbildung von Ähnlichkeit im Verhalten
• Klassifikation von Problemen
Algorithmen und Datenstrukturen II
8
Phylogenetische Klassifizierung
Tiere
Insekten
HH
HH
H
HH
j
?
Säugetiere
Fische
@
Algorithmen und Datenstrukturen II
?
@
@
R
@
?
@
@
R
@
?
@
R
@
9
Ökonomische Klassifizierung
Tiere
Zuchttiere
H
HH
H
?
Wild
@
Algorithmen und Datenstrukturen II
?
HH
j
H
Störtiere
@
@
R
@
?
@
@
R
@
?
@
R
@
10
Drei Vorgehensweisen im Systementwurf
1. die funktionsorientierte
2. die datenorientierte
3. die objektorientierte
Algorithmen und Datenstrukturen II
11
Der Kerngedanke des objektorientierten Ansatzes besteht darin, Daten und
Funktionen zu verschmelzen. Im ersten Schritt werden die Daten abgeleitet, im
zweiten Schritt werden den Daten die Funktionen zugeordnet, die sie manipulieren.
Die entstehenden Einheiten aus Daten und Funktionen werden Objekte genannt. Wir
schränken den Begriff Objektorientierung gemäß folgender Gleichung von Coad &
Yourdon weiter ein:
Objektorientierung =
Klassen und Objekte
+ Kommunikation mit Nachrichten
+ Vererbung
Algorithmen und Datenstrukturen II
12
Einfachvererbung
Object
@
System
?
Math
@
R
@
Point
@
@
R
@
...
Algorithmen und Datenstrukturen II
13
Mehrfachvererbung
...
@
@
R
@
Pflanzen
Tiere
@
@
R
@
Fleischfresser
@
@
R
@
...
Algorithmen und Datenstrukturen II
14
Klasse Object
Object oref = new Point();
oref = "eine Zeichenkette";
Algorithmen und Datenstrukturen II
15
Konstruktoren und Initialisierungsblöcke
Algorithmen und Datenstrukturen II
16
public class Circle {
int x=0, y=0, r=1;
static int numCircles=0;
public Circle() {
numCircles++;
}
public double circumference() {
return 2*Math.PI*r;
}
public double area() {
return Math.PI*r*r;
}
public static void main(String[] args) {
Algorithmen und Datenstrukturen II
17
Circle c = new Circle();
System.out.println(c.r);
System.out.println(c.circumference());
System.out.println(c.area());
System.out.println(numCircles);
}
}
Algorithmen und Datenstrukturen II
18
Konstruktor mit Parametern
public Circle(int xCoord, int yCoord, int radius) {
numCircles++;
x = xCoord;
y = yCoord;
r = radius;
}
Algorithmen und Datenstrukturen II
19
mit this-Referenz
public Circle(int x, int y, int r) {
numCircles++;
this.x = x;
this.y = y;
this.r = r;
}
Algorithmen und Datenstrukturen II
20
Überladen von Konstruktoren
Algorithmen und Datenstrukturen II
21
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);
}
Algorithmen und Datenstrukturen II
22
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);
}
}
Algorithmen und Datenstrukturen II
23
Komplexe Initialisierung von Klassenvariablen
public class Circle {
public static double[] sines = new double[1000];
public static double[] cosines = new double[1000];
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);
}
}
}
Algorithmen und Datenstrukturen II
24
Java-Klassen als Realisierung und Implementierung
von abstrakten Datentypen
Durch den Modifizierer private können wir Implementierungsdetails verstecken, denn
als private deklarierte Attribute und Methoden sind nur in der Klasse selbst
zugreifbara . Folgende Klasse implementiert einen ADT Stack mittels eines Feldes:
a Synonyme
für Zugreifbarkeit sind: Gültigkeit bzw. Sichtbarkeit.
Algorithmen und Datenstrukturen II
25
Algorithmen und Datenstrukturen II
26
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;
}
Algorithmen und Datenstrukturen II
27
/** 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. */
Algorithmen und Datenstrukturen II
28
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. */
Algorithmen und Datenstrukturen II
29
public int size() {
return top+1;
}
}
Algorithmen und Datenstrukturen II
30
Der Dokumentationskommentar /** ... */ wird zur automatischen
Dokumentierung der Attribute und Methoden einer Klasse benutzt. Das Programm
javadoc generiert ein HTML-File, in dem alle sichtbaren Attribute und Methoden mit
deren Parameterlisten aufgezeigt und dokumentiert sind.
> javadoc Stack.java
Dieses HTML-File ist der Vertrag (die Schnittstelle) der Klasse und entspricht dem
ADT Stack, wobei die Operationen bzw. Methoden allerdings nur natürlichsprachlich
spezifiziert wurden. Die obige verbale Spezifikation entspricht weitgehend der der
vordefinierten Java-Klasse Stack (genauer java.util.Stack). Man beachte, dass
(aus diesem Grund) die obige Spezifikation von der Gleichungsspezifikation aus dem
Unterabschnitt ?? abweicht.
Algorithmen und Datenstrukturen II
31
Methoden in Java
1. Methoden und Signaturen
2. Überladen von Methoden
3. Wertübergabe (call by value)
Algorithmen und Datenstrukturen II
32
call by value
Die Parameterübergabe zu Methoden erfolgt in Java durch Wertübergabe (call by
value). D.h., dass Werte von Parametervariablen in einer Methode Kopien der vom
Aufrufer angegebenen Werte sind. Das nächste 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);
}
}
Algorithmen und Datenstrukturen II
33
> java CallByValue
9
3
Algorithmen und Datenstrukturen II
34
Allerdings ist zu beachten, dass nicht Objekte, sondern Objektreferenzen übergeben
werden. Wir betrachten unser Standardbeispiel Circle in folgender abgespeckter
Form (gemäß der Devise, Implementierungsdetails zu verbergen, werden die
Datenfelder als private deklariert).
Algorithmen und Datenstrukturen II
35
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;
}
Algorithmen und Datenstrukturen II
36
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);
}
}
Algorithmen und Datenstrukturen II
37
> java Circle
vorher : r = 1
nachher: r = 0
Dieses Verhalten entspricht jedoch nicht der Parameterübergabe call by reference,
denn bei der Wertübergabe wird eine Kopie der Referenz erzeugt und die
ursprüngliche Referenz bleibt erhalten. Bei call by reference würde die übergebene
Referenz eben nicht kopiert und daher in der Methode setToZero auf null gesetzt.
Algorithmen und Datenstrukturen II
38
Unterklassen und Vererbung in Java
Algorithmen und Datenstrukturen II
39
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;
}
Algorithmen und Datenstrukturen II
40
public void draw(Graphics g) {
g.setColor(outline);
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);
}
Algorithmen und Datenstrukturen II
41
}
Algorithmen und Datenstrukturen II
42
Color und Graphics
Color und Graphics sind vordefinierte Klassen, die durch import zugreifbar
gemacht werden (vgl. Abschnitt ??). Diese Klassen werden z.B. in [3] beschrieben.
Zum Verständnis reicht es hier zu wissen, dass der erste Konstruktor den Konstruktor
seiner Oberklasse aufruft (vgl. Abschnitt ??) und das Kreisinnere die Farbe hellgrau
erhält, sowie, dass die Methode draw einen farbigen Kreis zeichnet.
Algorithmen und Datenstrukturen II
43
Da GraphicCircle alle Methoden von Circle erbt, können wir z.B. den
Flächeninhalt eines Objektes gc vom Typ GraphicCircle berechen durch:
double area = gc.area();
Jedes Objekt gc vom Typ GraphicCircle ist ebenfalls ein Objekt vom Typ Circle
bzw. vom Typ Object. Deshalb sind folgende Zuweisungen korrekt.
Circle c = gc;
double area = c.area();
Man kann c durch casting a in ein Objekt vom Typ GraphicCircle zurückverwandeln.
GraphicCircle gc1 = (GraphicCircle)c;
Color color = gc1.fill;
Die oben gezeigte Typumwandlung funktioniert nur, weil c tatsächlich ein Objekt vom
Typ GraphicCircle ist.
a explizite
Typumwandlung
Algorithmen und Datenstrukturen II
44
Überschreiben 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);
}
}
Algorithmen und Datenstrukturen II
45
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);
}
}
Algorithmen und Datenstrukturen II
46
Verdecken von Datenfeldern
Jedes ExtendShow-Objekt hat zwei String-Variablen, die beide str heißen und von
denen eine ererbt wurde. Die neue Variable str verdeckt die ererbte; wir sagen auch
die ererbte ist verborgen. Sie existiert zwar, man kann aber nicht mehr durch Angabe
ihres Namens auf sie zugreifen.
Algorithmen und Datenstrukturen II
47
Überschreiben von Methoden
Wenn eine Methode von einem Objekt aufgerufen wird, dann bestimmt immer der
tatsächliche Typ des Objektes, welche Implementierung benutzt wird. Bei einem
Zugriff auf ein Datenfeld wird jedoch der deklarierte Typ der Referenz verwendet.
Daher erhalten wir folgende Ausgabe beim Aufruf der main-Methode:
> java ExtendShow
Extend.show: ExtendStr
Extend.show: ExtendStr
sup.str = SuperStr
ext.str = ExtendStr
Algorithmen und Datenstrukturen II
48
Die Objektreferenz super
Das Schlüsselwort super kann in allen objektbezogenen Methoden und Konstruktoren
verwendet werden. In Datenfeldzugriffen und Methodenaufrufen stellt es eine Referenz
zum aktuellen Objekt als eine Instanz seiner Oberklasse dar. Wenn super verwendet
wird, so bestimmt der Typ der Referenz über die Auswahl der zu verwendenden
Methodenimplementierung. Wir illustrieren dies wieder an einem Beispielprogramm.
Algorithmen und Datenstrukturen II
49
super Beispiel
public class T1 {
protected int x = 1;
protected String s() {
return "T1";
}
}
Algorithmen und Datenstrukturen II
50
Algorithmen und Datenstrukturen II
51
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);
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) {
Algorithmen und Datenstrukturen II
52
new T2().test();
}
}
Algorithmen und Datenstrukturen II
53
> java T2
x= 2
super.x= 1
((T1)this).x= 1
s(): T2
super.s(): T1
((T1)this).s(): T2
Algorithmen und Datenstrukturen II
54
Konstruktoren in Unterklassen
In Konstruktoren der Unterklasse kann direkt einer der Oberklassenkonstruktoren
mittels des super() Konstruktes aufgerufen werden.
Achtung: Der super-Aufruf muss die erste Anweisung des Konstruktors sein!
Algorithmen und Datenstrukturen II
55
Wird kein Oberklassenkonstruktor explizit aufgerufen, so wird der parameterlose
Konstruktor der Oberklasse automatisch aufgerufen, bevor die Anweisungen des neuen
Konstruktors ausgeführt werden. Verfügt die Oberklasse nicht über einen
parameterlosen Konstruktor, so muss ein Konstruktor der Oberklasse explizit mit
Parametern aufgerufen werden, da es sonst einen Fehler bei der Übersetzung gibt.
Ausnahme: Wird in der ersten Anweisung eines Konstruktors ein anderer
Konstruktor derselben Klasse mittels this aufgerufen, so wird nicht automatisch
der parameterlose Oberklassenkonstruktor aufgerufen.
Algorithmen und Datenstrukturen II
56
Java liefert einen voreingestellten parameterlosen Konstruktor für eine erweiternde
Klasse, die keinen Konstruktor enthält. Dieser ist äquivalent zu:
public class ExtendedClass extends SimpleClass {
public ExtendedClass () {
super();
}
}
Der voreingestellte Konstruktor hat dieselbe Sichtbarkeit wie seine Klasse.
Algorithmen und Datenstrukturen II
57
Ausnahme: Enthält die Oberklasse keinen parameterlosen Konstruktor, so muss die
Unterklasse mindestens einen Konstruktor bereitstellen.
Algorithmen und Datenstrukturen II
58
Reihenfolgeabhängigkeit von Konstruktoren
Wird ein Objekt erzeugt, so werden zuerst alle seine Datenfelder auf voreingestellte
Werte initialisiert. Jeder Konstruktor durchläuft dann drei Phasen:
• Aufruf des Konstruktors der Oberklasse.
• Initialisierung der Datenfelder mittels der Initialisierungsausdrücke.
• Ausführung des Rumpfes des Konstruktors.
Algorithmen und Datenstrukturen II
59
Algorithmen und Datenstrukturen II
60
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";
Algorithmen und Datenstrukturen II
61
public Y() {
suffix = suffix+extra;
alles = verbinde("Biele");
}
public static void main(String[] args) {
new Y();
}
}
Algorithmen und Datenstrukturen II
62
Schritt
Aktion
0
Datenfelder auf Voreinstellungen
1
Y-Konstruktor aufgerufen
2
X-Konstruktor aufgerufen
3
X-Datenfeld initialisiert
fel
4
X-Konstruktor ausgeführt
fel
5
Y-Datenfeld initialisiert
fel
6
Y-Konstruktor ausgeführt
fel
Algorithmen und Datenstrukturen II
infix
extra
suffix
alles
fel
Apfel
d
fel
Apfel
d
feld
Bielefeld
63
Abstrakte Klassen und Methoden
Es gilt:
• eine abstrakte Methode hat keinen Rumpf;
• jede Klasse, die eine abstrakte Methode enthält, ist selbst abstrakt und muss als
solche gekennzeichnet werden;
• jede abstrakte Klasse muss mindestens eine abstrakte Methode besitzen;
• man kann von einer abstrakten Klasse keine Objekte erzeugen;
• von einer Unterklasse einer abstrakten Klasse kann man Objekte erzeugen –
vorausgesetzt sie überschreibt alle abstrakten Methoden der Oberklasse und
implementiert diese;
• eine Unterklasse, die nicht alle abstrakten Methoden der Oberklasse
implementiert ist selbst wieder abstrakt.
Algorithmen und Datenstrukturen II
64
Beispiel Benchmark
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);
}
}
Algorithmen und Datenstrukturen II
65
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");
}
}
Algorithmen und Datenstrukturen II
66
Literatur
[1] K. Arnold, J. Gosling: JavaT M - Die Programmiersprache. Addison-Wesley, 1996.
[2] T.H. Cormen, C.E. Leierson, R.L. Rivest: Introduction to Algorithms. MIT Press, 1990.
[3] D. Flanagan: Java in a Nutshell. O’Reilly &
Associates Inc., 1996.
[4] F. Jobst: Programmieren in Java. Hanser Verlag, 1996.
[5] H. Klaeren: Vom Problem zum Programm.
2.Auflage, B.G. Teubner Verlag, 1991.
[6] K. Echtle, M. Goedicke: Lehrbuch der Programmierung mit Java. dpunkt-Verlag, 2000.
Herunterladen