|
1. OBJEKTE
Objekte werden durch ZUSTAND und VERHALTEN beschrieben. Die
Zustandskomponenten heissen ATTRIBUTE, das Verhalten wird durch METHODEN (oder
synonym: OPERATIONEN) spezifiziert. Der Internzustand sowie bestimmte
Operationen, die nur innerhalb eines Objektes benötigt werden,
können nach dem GEHEIMNISPRINZIP (Information Hiding) vor externem
Zugriff geschützt werden. Die möglichen Verhaltensweisen eines
Objektes werden durch das Versenden von BOTSCHAFTEN an das Objekt
ausgelöst. Während bei der nicht objekt-orientierten imperativen
Programmierung Daten an eine Funktion zur Verarbeitung übergeben werden,
kapselt das Objekt Daten und auf ihnen anwendbare Funktionen; die Verarbeitung
der Daten wird durch Senden einer Botschaft an das Objekt ausgelöst. Die
Parameter der Botschaft dürfen wiederum Objekte sein, d.h. intuitiv
gesprochen, man kann mit den Eingangsdaten einer Botschaft auch gleich die
Methoden zur Verarbeitung dieser Eingangsdaten mitsenden.
[Literatur: Balzert, Abschnitt 2.5]
Methoden (Operationen) lassen sich in Prozeduren/Funktionen/Konstruktoren
gliedern. Funktionen sind Methoden, die einen Rückgabewert erzeugen,
Prozeduren sind Methoden ohne Rückgabewert
(void ). Konstruktoren sind spezielle Prozeduren, die automatisch
bei der Objekterzeugung aufgerufen werden. Beim Erzeugen eines neuen Objekts
durch den new -Operator bestimmt die Parameterliste, welcher
Konstruktor verwendet wird:
class C {
boolean b;
C () { b = true; } // Konstruktor 1
C (boolean b) { this.b = b; } // Konstruktor 2
public static void main (String[] args) {
C c1 = new C(); // -> verwendet Konstruktor 1
C c2 = new C(false); // -> verwendet Konstruktor 2
}
}
Java-Besonderheit:
- Wird ein Objekt durch den Garbage Collector deallokiert,
kann eine Finalization-Methode aufgerufen werden.
(VORSICHT: Zeitpunkt der Garbage Collection und des
Aufrufs der Finalisierungsmethode muss explizit
über Methoden der Laufzeitumgebung gesteuert werden.
Andernfalls hat mein keine Sicherheit, wann das
Objekt tatsächlich "finalisiert" und zerstört wird.)
Methoden haben eine SIGNATUR, diese ist definiert durch
- Methodenname
- Anzahl und Typ und Reihenfolge der Parameter
[Bemerkung: Der Rückgabewert einer Funktion ist NICHT Bestandteil
der Signatur].
Methoden können ÜBERLADEN (engl. OVERLOADING) werden: Hierunter
versteht man die Deklaration von Methoden mit gleichem Namen, aber
unterschiedlichen Signaturen.
[Literatur: Balzert, Abschnitt 2.11 und pp. 258-259]
In Java erfolgt die Zustandsspezifikation durch Feld-Variablen.
In Programmiersprachen wie z.B. C++ können Operatoren (z.B. +,-,==)
auf Klassen genau so definiert und überladen werden wie Methoden. Java
erlaubt das nicht.
2. KLASSEN
Klassen spezifizieren die Gemeinsamkeiten einer Menge von Objekten mit
denselben Eigenschaften (Attributen), demselben Verhalten (Methoden) und
denselben Beziehungen (d.h. Vererbungsstrukturen und Assoziationen, siehe
Vererbung weiter unten).
ABSTRAKTE KLASSEN definieren nicht alle erforderlichen Methoden für ihre
Objekte, sondern für manche Methoden nur jeweils die
Methodenschnittstelle.
Eine nicht-abstrakte Klasse dient als "Objektfabrik" OBJECT FACTORY. Durch die
Klasse erzeugte Objekte heissen INSTANZEN der Klasse.
In Java:
- Schlüsselworte
abstract , final
(final bedeutet, es dürfen keine Unterklassen erzeugt
werden).
- Java-Klassen sind Typen im Sinne der Objekt-Orientierung,
decken aber nicht das gesamte OO-Typkonzept ab, wie es
üblicherweise verwendet wird, siehe Punkt 3.
Die Attribute und Methoden einer Klasse werden gemeinsam als MITGLIEDER
(engl. members) der Klasse bezeichnet.
Eine Klasse (dann Unterklasse genannt) kann die Attribute und Methoden einer
Oberklasse übernehmen (ERBEN) und eigene Attribute und Methoden
hinzudefinieren. Ein Objekt O der Unterklasse K2
ist damit auch ein Mitglied einer jeden Oberklasse K1 : Bei
Methodenaufrufen, die Parameter vom Typ K1 verlangen, darf also
O als gültiger Wert verwendet werden. O besitzt
als Instanz von K2 spezielle Eigenschaften, die in
K1 nicht enthalten sind. Daher wird K2 als
SPEZIALISIERUNG von K1 bezeichnet; umgekehrt ist K1
eine GENERALISIERUNG von K2 .
Dabei dürfen vorhandene Methoden aus der Oberklasse von der Unterklasse
REDEFINIERT (engl. OVERRIDE - "sich über etwas hinwegsetzen")
werden (die Signatur bleibt gleich). [Bemerkung: In vielen deutschen
Büchern wird der Begriff "Überschreiben" verwendet, obwohl
Redefinieren gemeint ist, vgl. nächsten Absatz.]
Wird (im Gegensatz zum Redefinieren) derart Überladen
(s. Abschnitt 1), daß die unterschiedlichen
Methodendeklarationen auf Ober- und Unterklasse verteilt werden, spricht man
(nach Balzert) von ÜBERSCHREIBEN.
Wichtig ist einfach, die drei Möglichkeiten (1) Einführung einer
zusätzliche Methode gleichen Namens mit neuer Signatur in derselben
Klasse, (2) Einführung einer zusätzlichen Methode gleichen Namens
und gleicher Signatur in der Unterklasse, (3) Einführung einer
zusätzlichen Methode gleichen Namens und anderer Signatur in der
Unterklasse, zu verstehen und unterscheiden zu können.
Insbesondere können Objektinstanzen unterschiedlicher Klassen durch
Redefinition Methoden mit identischer Signatur besitzen, die unterschiedliches
Verhalten implementieren. Diese Eigenschaft objekt-orientierter
Programmiersprachen wird POLYMORPHISMUS genannt. Wird ein Objekt
O als Parameter einer Methode vom Typ K1 eingegeben,
ist damit nicht immer zur Übersetzungszeit bereits bekannt, ob zur
Laufzeit auch ein Objekt der Unterklasse K2 als Parameter
verwendet wird. Infolgedessen ist erst zur Laufzeit bekannt, ob beim Aufruf
O.m() die Methode m() der Klasse K1
oder die in K2 redefinierte Methode m() zur
Ausführung kommt. Dies muss von der Laufzeitumgebung ermittelt werden
(DYNAMSCHES BINDEN).
[Literatur: Balzert, Abschnitt 2.10, 2.14]
In Java:
- Syntax ist
class <Classname> extends <Classname> {
...
}
- Es darf nur von einer Klasse geerbt werden (EINFACHVERERBUNG)
- hierdurch ensteht eine Klassenhierarchie mit Baumstruktur.
Die Wurzelklasse dieser Baumstruktur ist
Object . (Im
Gegensatz zu Java lässt C++ beispielsweise MEHRFACHVERERBUNG zu
- das bedeutet, daß eine Unterklasse von mehr als einer
Oberklasse erben darf.)
- Eine Klasse darf beliebig viele Interfaces implementieren
(die
interface Deklaration wird weiter unten
erläutert).
- Die Konstruktoren der Oberklasse können explizit mit
super(<Parameterliste>)
referenziert werden. Das ist allerdings nur am Anfang eines
Konstruktors der Unterklasse erlaubt.
- Die Mitglieder (Attribute und Methoden) der
Oberklasse können durch Vorstellen von
super.
referenziert werden.
- Die Konstruktoren einer Klasse
können explizit mit
this(<Parameterliste>)
referenziert werden. Das ist allerdings nur am Anfang eines
Konstruktors in dieser Klasse zulässig.
- Die Mitglieder (Attribute und Methoden) einer Klasse
können explizit durch Vorstellen von
this.
referenziert werden. Insbesondere kann sich jede
Instanz der Unterklasse durch this selbst
referenzieren. Dies ist beispielsweise erforderlich, wenn eine
Methode eine Referenz auf ein Objekt des Klassentyps
zurückgibt und diese Referenz auf das eigene Objekt zeigen soll.
- Attribute werden bei der Vererbung nicht redefiniert,
sondern VERSTECKT (engl. HIDING): Definiert die Unterklasse ein
gleichnamiges Attribut wie die Oberklasse, so ist das Attribut der
Oberklasse weiterhin durch
super.<Attributname>
referenzierbar.
- Klassenattributewerden mit
<Name der
klasse>.<Attributname>
referenziert.
- Feld-Variablen können Klassen- oder
Objektattribute sein: Alle Instanzen einer Klasse "sehen"
dieselben Klassenattribute und besitzen eigene
lokale Objektattribute.
- Die Sichtbarkeit von Attibuten und Operationen wird durch folgende
Schlüsselworte festgelegt:
- Das Schlüsselwort
private schützt
Attribute und Operationen vor externem Zugriff (d.h. vor
Zugriff von außerhalb der umschließenden Klasse).
- Default-Sichtbarkeit (gilt, wenn keines der
Schlüsselworte
private , protected
oder public verwendet wird) erlaubt den Zugriff auf
Attribute und Operationen aus allen Klassen, die zu demselben
Paket gehören wie die umschließende Klasse.
- Das Schlüsselwort
protected erweitert die
Default-Sichtbarkeit, so daß Attribute und Operationen auch
für Unterklassen verfügbar sind, welche zu anderen
Paketen gehören.
- Das Schlüsselwort
public macht
Attribute und Operationen für den
Zugriff aus beliebigen Klassen verfügbar.
Die Sichtbarkeit von Mitgliedern einer Klasse wird durch
die Sichtbarkeit der Klasse selbst weiter eingeschränkt:
- Ist die gesamte Klasse default-sichtbar, dann gilt maximal
Default-Sichtbarkeit.
- Ist die gesamte Klasse
public , dann gibt es keine
zusätzlichen Einschränkungen.
- Klassen mit
private - oder
protected -Sichtbarkeit werden nur in speziellen
Kontexten verwendet (und hier vernachlässigt).
- Das Versenden von Botschaften an ein Objekt wird durch
Methodenaufrufe der syntaktischen Form
<objektname>.<Methodenname mit Aktualparametern>
vollzogen.
3. TYPEN
Während Klassen typischerweise Zustand und Verhalten zumindest teilweise
explizit definieren, benötigt man auch ein Konzept, mit dem man
Leistungen gewünschter (später explizit auszuprägender) Objekte
IMPLIZIT spezifizieren kann, d.h., ohne Zustandskomponenten und
Methodenrümpfe explizit zu benennen. Allgemein besteht eine implizite
Spezifikation aus
- einer Schnittstellenspezifikation bestehend aus
Signatur und ggf. Typ des Rückgabewertes
- optional: einem logischen Prädikat (häufig auch AXIOM
genannt), welches das Sollverhalten eines
der impliziten Spezifikation genügenden Objektes
beschreibt, ohne auf Internzustände und Algorithmen
Bezug zu nehmen, d.h.: das Axiom bezieht sich nur
auf Komponenten, die in der Schnittstellenspezifikation
sichtbar sind, beispielsweise
- Methodennamen,
- Parameternamen,
- Rückgabewerte
Formale Sprachen (z.B. VDM, Z, CSP und verschiedene algebraische
Spezifikationsformalismen) bieten in der Regel die Möglichkeit zur
impliziten Spezifikation mit Schnittstellenspezifikation und Axiomen. Dagegen
bestehen implizite Spezifikation bei Programmiersprachen in der Regel allein
aus der Schnittstellenspezifikation; die Definition logischer Prädikate
wird nicht als Sprachbestandteil angesehen. Beispiele für implizite
Spezifikation bei Programmiersprachen:
- C: Function prototypes in C-Header Files, wie z.B.
extern int f(int i,float ff,int j);
- C++: Klassendefinitionen in C++-Header Files,
deren Methoden sämtlich als
virtual
definiert sind und die keine Zustandsvariablen einführen.
In Java:
-
Schnittstellen werden ähnlich wie Klassen definiert:
interface MyInterface {
public void myMethod1 (int p1, float p2);
public float myMethod2 (Object o);
}
- Ein Interface kann nicht direkt instantiiert werden. Stattdessen
können Klassen Schnittstellen IMPLEMENTIEREN:
Dadurch sichert die Klasse zu, daß alle Methoden aus der
Schnittstelle in ihr definiert sind. Instanzen der Klasse sind dann
sowohl vom Typ der Klasse als auch vom Typ der Schnittstelle.
- Klassen dürfen mehrere Interfaces implementieren,
Deklarationssyntax ist:
class <Classname> implements <Interfacename1>,
<Interfacename2> {
//...
}
- Interfaces dürfen keine Zustandsvariablen definieren, wohl aber
Konstanten (z.B.
public static final int x = 5; ).
- Abstrakte Klassen können dasselbe wie Interfaces leisten, wenn
sie nur abstrakte Methoden und keine Zustandsvariablen definieren. Eine
Unterklasse darf dann aber wegen der Einfachvererbung in Java
nur diese eine abstrakte Klasse erweitern.
Vereinfacht ausgedrückt: In Java sind Interfaces reine
Entwurfsspezifikationen, während Klassen Mischungen aus
Entwurfsspezifikation und Implementierung sind.
[Literatur: Balzert, Abschnitt 2.16]
Manche Programmiersprachen lassen das "Programmieren mit Typen" zu, d.h. sie
bieten Methoden zur Abfrage von Eigenschaften eines Objekttyps an. In C-Code
kann man beispielsweise nicht abfragen, welchen Funktionsnamen die
aktuell ausgeführte C-Function hat. Es gibt auch keine C-Operationen, mit
denen man die Namen und Signaturen der in einer C-Bibliothek enthaltenen
Funktionen abfragen kann.
Dagegen in Java:
- Das Paket
java.lang enthält die Klasse
Class , deren Objekte "Auskunftsfunktionen" über andere
Objekte enthalten. Ein Objekt obj beliebiger Klasse
enthält die Methode getClass() , die ein solches
Auskunftsobjekt oInfo vom Typ
Class zurückgibt. Auf oInfo kann man
vordefinierte Abfragefunktionen anwenden, die Auskunft über den
Klassentyp von obj geben. Beispielsweise gibt die
Class -Methode getMethods() einen Array
von Objekten zurück, welche die zu obj
gehörigen Methoden (s.u.) repräsentieren.
- Im Java reflection package
java.lang.reflect werden zusätzliche Auskunftsklassen
definiert: Beispielsweise bietet die Klasse Method
Methoden an, mit denen man aus oInfo Signatur und
Rückgabewert der zu obj gehörigen Methoden
abfragen kann. Weiterhin lassen sich diese Methoden dann aufrufen
(mittels Method.invoke() ), ohne
den Methodennamen im Programmcode zu verwenden.
- Klassennamen existieren als vorübersetzte Literale mit Extension
.class , man kann damit auf Zugehörigkeit zu einem
bestimmten Klassentyp abfragen:
if ( obj.getClass() == String.class ) { ... }
4. GENERISCHE FUNKTIONEN UND KLASSEN
Die Eigenschaft GENERISCH bedeutet "Typunabhängigkeit". Mit diesem
Begriff ist die Möglichkeit gemeint, bei der Instantiierung eines
Objektes aus einer Klasse auch parametrisierbare Typen (in C++
Templates genannt) in den Methodenschnittstellen festzulegen und bei
Methodenaufrufen auch anzuwendende Verarbeitungsfunktionen als Parameter
mitzugeben.
In Java:
- Templates und Funktionsparameter sind nicht als explizite
Sprachkonstrukte vorhanden.
- Anstelle eines generischen Typparameters kann in einer
Methodendeklaration immer der Typ
Object verwendet
werden.
- Zur Laufzeit wird beim Methodenaufruf
als Parameter ein Objekt einer konkreteren
Klasse eingegeben; dies ist in jedem Fall typkorrekt, da die
konkretere Klasse immer eine Spezialisierung von
Object
ist.
- Bei den Klassenschablonen (Templates) von C++ kann die SORTENREINHEIT,
d.h. die ausschließliche Verwendbarkeit des Objekts mit den bei
der Instantiierung angegebenen Typen bereits zur
Übersetzungszeit erzwungen werden. In Java muss diese
Eigenschaft, falls gewünscht, durch explizite Typprüfungen zur
Laufzeit realisiert werden. Hierzu kann man den Objekttyp abfragen
und auch das Vorhandensein bestimmter für die Verarbeitung
benötigter Methoden explizit abfragen.
- Beispiel: Die Implementierung einer Behälterklasse wie
z.B.
List enthält eine Methode zum Auslesen eines
Objekts:
class List { Object getNext () { ... } ... }
- Benötigt man nun z.B. eine Liste von speziellen Objekten, so
muß man
- entweder Typumwandlungen vornehmen:
((Sound)list.getNext()).play();
Hier verliert man die Typensicherheit zur Übersetzungszeit
und bekommt ggf. Laufzeitfehler.
- oder eine spezialisierte Liste implementieren:
class SoundList { Sound getNext () { ... } ... }
Hier muß man neue Listen implementieren.
Mit Klassenschablonen (Templates) wie in C++ bräuchte man die
Listenklassen
nur entsprechend parametrisieren (geht in Java nicht):
List<Sound> = new List<Sound>();
- Anstelle von Funktionsparametern für die Verarbeitung der
konkreten Objekte werden die Verarbeitungsfunktionen immer als
Methoden der konkreten Klasse oder deren Unterklassen
definiert.
|
|