3 Objektorientierte Strukturen (1)
Inhalt
Objekte und Klassen ·Vererbung ·Klassenhierarchie ·Überschreiben
Literatur:
Die Lektion baut teilweise auf Kapitel 3 aus [Fla99b]
auf. Siehe auch Kapitel 4 und 8 aus [LL97]. Eine
schöne allgemeine Übersicht und Diskussion über objektorientierte
Sprachmerkmale bietet das Buch Object-Oriented Programming
[Bud97].
Beispiel 4 [Circle]
Der Zustand
eines Kreises als einer Instanz von Circle |
die Koordinaten seine Zentrums und sein Radius | wird in den
entsprechenden Feldern oder Instanzvariablen mit
passendem Typ gespeichert.
public class Circle1 {
// Felder
public double x, y; // Daten = Koordinaten
public double r; // in die Felder
// Methoden
public double circumference() {
return 2 * 3.1415926 * r;
}; //
}; // '{' und `}' zur Gruppierung
-
Objekt: Einheit von Daten (`` fields'') und
Methoden, die auf den Daten arbeiten.
- Instanz einer Klasse (Klasse als "Datentyp" des
Objektes).
- neue Objekte: Instantiierung mittels new11
- Methoden/Feld-Selektion (``Methodenaufruf): mittels
``.''.
Beispiel 5 [Circle]
Zugriff auf den Zustand eines Kreisobjektes
= Instanz von
Circle1 über seine Felder oder Methoden
Circle1 c = new Circle1(); // Instantiierung
c.x = 4.0; // Zugriff auf Felder
c.y = 2.3; // und Methoden mit
c.r = 1.5; // `.'
double a = c.circumference();
Objekte und Klassen: Konstruktoren
|
-
Konstruktor: ``Spezialmethode'' zur
Initialisierung eines Objektes mit dem selben Bezeichner wie
die Klasse
- new ÞAufruf der Konstruktor-Methode.
- Jede Klasse besitzt (mindestens einen) Konstruktor selben Namens,
falls keiner angegeben wird Þ Default-Konstruktor ohne
Parameter (siehe vorheriges Beispiel 5)
- mehrere Konstruktoren mit unterschiedliche Parameterlisten möglich:
Beispiel für `` method overloading''.13
- kein (expliziter) Rückgabetyp
Beispiel 6 [Circle]
Hier das gleiche Beispiel nochmal, diesmal mit (nicht-default)
Konstruktor. Instantiierung
nun mittels Circle2 c = new
Circle2(3.2, 4.5, 1.9).
public class Circle2 {
private double x, y; // private: kommt sp"ater
private double r; // genauer
Circle2 (double _x, double _y, double _r) {
x = _x;
y = _y;
r = _r;
}; // Konstruktor Circle2
//----------------------------
public double circumference() {
return 2 * Math.PI * r; // Konstante der Klasse Math
};
};
Instanz- und Klassenvariablen
|
Variablendeklarationen in einer Klasse (Felder) kann man in
Instanz- und Klassenvariablen unterteilen
-
bisher und für gewöhnlich: Instanzvariable: eine Kopie
pro Instanz
- Klassenvariable oder auch statische Variable:
-
eine Kopie der Variable pro Klasse
- Schlüsselwort static
- Zugriff: ebenfalls mittels `.', allerdings über den
Klassennamen: <classname>.<var_name>14
- sie entsprechen globale Variablen mit dem Klassennamen
als Diskriminator
(z.B. Math.PI)15
- lokale Variablen (innerhalb einer Methode) dürfen
nicht statisch sein
Beispiel 7 [Anzahl der Instanzen]
Sei ein Kurs durch die Menge seiner Teilnehmer modelliert. Die
Klassenvariable freiePlaetze drückt einen
globalen
``Zustand'' des Kurses aus.16
public class Teilnehmer {
public static int freiePlaetze = 10;
public String name;
public int matrikelnummer;
Teilnehmer(String n, int m) {
name = n;
matrikelnummer = m;
freiePlaetze--; // einer weniger frei
}; // Konstruktor
};
Klassenmethoden, statische Methoden
|
Analog der Situation bei Feldern unterscheidet man Instanz- und
Klassenmethoden.
-
bisher (fast) nur Instanzmethoden
- Klassenmethoden oder auch statische Methoden
-
Schlüsselwort static (``Modifier'')
- Zugriff: über den Klassenbezeichner (analog
Klassenvariablen)
- Wichtig: kein impliziter this-Parameter!
- Beispiel: die Klasse java.lang.Math enthält nur statische
Methoden17
Initialisierung von Klassenvariablen
|
Lebensende von Objekten: Müllabfuhr und Finalisierung
|
-
implizite, automatische Speicherverwaltung: Garbage
Collection (in Ruhephasen und bei Bedarf)
- ``Finalization'': spezielle Methode
(finalize()) von Objekten, aufgerufen bevor der Speicher für
das Objekt freigegeben wird.
- Verwendung: Freigabe von Systemresourcen, die nicht
automatisch | wie der Speicher | verwaltet werden, z. B.:
Schließen von Dateien, Abbau von Netzverbindung etc.
Unterklassen und Vererbung
|
Beispiel 8 [Konto]
public class Konto {
int kontostand;
String inhaber;
Konto (String name, int i) {
kontostand = i;
inhaber = name;
};
Konto (String name) { // konstruktor-overloading
kontostand = 0;
inhaber = name;
};
/* Konto (){
kontostand = 0;
inhaber = "";
};
*/
//-----------------------------------------
public void einzahlen (int betrag) {
kontostand += betrag;
};
public void auszahlen (int betrag) {
kontostand -= betrag;
}
public int wieviel () {
return kontostand;
};
};
Beispiel 9 [Sparkonto]
Ein Sparkonto
sei eine besondere Art von Konto, die auch noch
Verzinsung
bietet.
public class Sparkonto extends Konto { // Unterklasse
double z_faktor;
double ueberziehungsz_faktor;
Sparkonto (double z_satz, double z_satz2, String name, int i) {
super(name,i);
z_faktor = 1+ z_satz /100;
ueberziehungsz_faktor = 1+ z_satz2/100;
};
Sparkonto (double z_satz, String name) {
super(name); // this.inhaber = name;
// w"are falsch
z_faktor = 1 + z_satz/100;
};
/* Sparkonto(double rate) {... ginge nicht, denn
super hat kein Konto()-Konstruktor */
// ------------------------------------------------
public void verzinsen() {
kontostand = (int) (z_faktor * kontostand);
};
public void auszahlen (int betrag) { // "Uberschreiben
if (kontostand >= betrag)
kontostand -= betrag;
};
};
-
die Klassen von Java bzw. der Klassenbibliothek formen eine
Hierarchie
-
mit Wurzel java.lang.Object
- final-Klassen haben keine Unterklassen
- Kette der Konstruktoren (constructor chaining,
bei der Instanziierung; explizit oder implizit. Ausnahme: man umgeht es
mittels this.
- bis auf Object: jede Klasse hat eine Oberklasse
-
explizit mittels extends
- falls extends fehlt: Unterklasse von Object
- eine Klasse kann beliebig viele Interfaces
implementieren.19 Beachte: dies
ist keine Mehrfachvererbung
Überschreiben von Methoden
|
-
Überschreiben (overriding): Kerneigenschaft
oo-er Sprachen
- verschiedene Worte: dynamischer Methodenaufruf, dynamic
dispatch, späte20 Bindung, message passing,
meinen alle (so gut wie)21 das selbe:
Methodenaufruf läßt sich als Prozeduraufruf mit später
Bindung verstehen
- static-, private- und final-Methoden
können nicht überschrieben werden
- Referenzierung der überschriebenen Methode und der überschreibenden
Klasse mit super:
super.<methode>
- "`überschriebene"' Felder:
( shadowed), am besten vermeiden
Beispiel 10 [Überschreiben]
Hier ein Beispiel, welches Vererbung mit Überschreibung von Feldern und
Methoden auf etwas merkwürdige Weise kombiniert.
class A {
int i = 1;
int f() { return i;}
};
public class B extends A {
int i;
int f() {
System.out.println("i = " + i);
i = super.i + 1;
System.out.println("i = " + i);
System.out.println("super.f() = " + super.f());
return super.f() + i;
};
};
public class Main {
public static void main (String[] args) {
B b = new B();
System.out.println("i = " + b.f());
};
}
Die Ausgabe Beim Ausführen von main ist (bis auf die
``newlines''):i=5; i=2; super.f() = 1; i = 3.22 Das erste i ist klar, es ist einfach die in
B vorhanden Version von i die die aus A überschreibt (genauer
gesagt überschattet (shadowed)). Die Zuweisung
danach setzt i dann auf 2, wegen super, der Wert von i in
A ändert sich damit nicht. Das Ergebnis des Aufrufes von
super.f ist interessant, es greift auf die
Variable von A zu und liefert das, wie gesagt, unveränderte i =
1. Der Returnwert 3 am Ende sollte damit klar sein.
Wenn wir das Beispiel ändern indem wir die Zeile int
i = 5; aus B entfernen, erhalten wir folgendes Ergebnis:
i = 1; i = 2; super.f() = 2; i = 4. Das erste i ist klar: da
B das Feld i nicht einführt, erbt es das aus A mit dem Wert 1.
Auch die nächste Ausgabe ist klar: 1+1 = 2. Interessant die Ausgabe
von super.f, diesmal ergibt es den Wert 2, da
wir diesmal nur eine Kopie
des Feldes in A und B haben. Die
Rückgabe von 4 ist dann klar.
Beispiel 11
class A {
public int x = 3;
public void test () { System.out.println("A"); }
public void test2 () { System.out.println("x = " + x); }
};
class B extends A {
public int x = 4;
public void test () { System.out.println("B"); }
public void test2 () { System.out.println("x = " + x); }
};
public class Main2 {
public static void main (String[] args) {
A a = new A();
A b; b = new B(); // die wichtige Zeile!
b.test();
b.test2();
System.out.println("b.x = " + b.x);
}
}
Dieses Beispiel zeigt klar den Unterschied zwischen
statischer und dynamischer Bindung und auch den
Unterschied zwischen Deklaration einer Referenzvariablen und
dem Instanziierung den Objekts: Felder sind statisch gebunden
und Methoden dynamisch.23
Die Ausgabe des Programms lautet B; x = 4; b.x = 3. Der Aufruf
der Methode test an b liefert die Methode in B, das selbe
für den Aufruf von test2. Das interessante (oder krumme) ist
der Unterschied zu b.x der dadurch kommt, daß b als vom
Typ/Klasse A deklariert ist! Schriebe man
B b anstelle dessen, wäre die Ausgabe von
b.x ebenfalls 4!24
-
Wichtiges OO-Merkmal: Zugriff auf Daten (möglichst) nur über
Methoden
-
Datenabstraktion, Strukturierungs- und Scoping-Konzept ...
erlaubter Zugriff |
Sichtbarkeit |
|
public |
protected |
default |
private |
selbe Klasse |
Ja |
Ja |
Ja |
Ja |
selbes Paket |
Ja |
Ja |
Ja |
Nein |
Unterklassen (anderes Paket) |
Ja |
Ja |
Nein |
Nein |
beliebige Klasse |
Ja |
Nein |
Nein |
Nein |
Table 2: Sichtbarkeitsmodifikatoren
-
Merke: Geerbte Variablen und Methoden behalten ihre
Sichtbarkeitsstufe
- Konstruktoren sind public (und werden nicht
vererbt)
- protected: werden an Unterklassen vererbt
Ein paar Daumenregeln für die verschiedenen
Sichtbarkeitsstufen
-
public: für Methoden und Konstanten, die der Benutzer sehen
soll. Sehr sparsam bei Feldern25
- protected: Unwichtige für die Benutzung der Objekte,
aber wichtig für jemand, der ein Paket um Unterklassen außerhalb des
Paketes erweitert
- default (auch Paket-Sichtbarkeit): Methoden, die zur
Kooperation innerhalb eines Paketes notwendig sind
- private: lokale verborgene Definitionen innerhalb einer
Klasse
-
abstrakte Klasse: nicht instantiierbar
(abstract)
- abstrakte Methode: ohne Rumpf. nur Signatur
- Zusammenhang:
-
``konkrete'' Klasse mit abstrakten Methoden: verboten
- abstrakte Klasse mit ``konkreten'' Methoden: erlaubt
- konkrete Unterklasse einer abstrakten Klasse muß die
abstrakten Methoden (typkonform)
implementieren26
- Nutzen:
17. April 2000
July 4, 2000