DIE WICHTIGSTEN STICHWORTE UND FAKTEN ZUM THEMA
"OBJEKT-ORIENTIERTE PROGRAMMIERSPRACHEN"
UND OO IN JAVA
1. OBJEKTE
1.1 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
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 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]
In Java:
- Zustandsspezifikation durch Feld-Variablen
- Feld-Variablen können Klassen- oder
Objektattribute sein
- Schlüsselwort 'public' macht Attribute und Operationen
für alle verfügbar
- Schlüsselwort 'private' schützt Attribute und Operationen
vor externem Zugriff
- Schlüsselwort 'protected' macht Attribute und Operationen
für Unterklassen verfügbar, welche die Methoden und
Attribute der Oberklasse erben.
- Das Versenden von Botschaften an ein Objekt wird durch
Methodenaufrufe der syntaktischen Form
<objektname>.<Methodenname mit Aktualparametern>
vollzogen.
[Literatur: Balzert, Abschnitt 2.10]
1.2 Methoden (Operationen) lassen sich in Prozedur/Funktion/Konstruktor
gliedern. Prozeduren sind void-Methoden, Konstruktoren
sind spezielle Prozeduren, die nur bei der Objekterzeugung
aufgerufen werden dürfen. Funktionen sind Methoden, die
einen Rückgabewert erzeugen. Die Objektinitialisierung geschieht durch
KONSTRUKTOREN (Operationen zur Erzeugung/Initialisierung
von Objekten)
In Java:
- 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 tatsaechlich "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 mehrfache Deklaration der Methode mit unterschiedlichen Signaturen.
[Literatur: Balzert, Abschnitt 2.11 und pp. 258-259]
In Java:
- Es dürfen nur Methoden, aber keine Operatoren (z.B. +,-,==)
überladen werden. Das Überladen von Operatoren ist
beispielsweise in C++ erlaubt.
2. KLASSEN
Klassen spezifizieren die Gemeinsamkeiten einer Menge von
Objekten mit denselben Eigenschaften (Attributen) und
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 nur Methodenschnittstellen.
Sind Klassen nicht abstrakt, bieten sie insbesondere
Methoden zur Objekterzeugung an (Konstruktoren). Die Klasse
dient dann als "Objektfabrik" OBJECT FACTORY. Durch die Klasse
erzeugte Objekte heissen INSTANZEN der Klasse.
In Java:
- Schlüsselworte 'abstract', 'public', '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 gmeinsam als
MITGLIEDER (engl. members) der Klasse bezeichnet.
Klassen können VERERBT werden: Eine Klasse (dann Unterklasse genannt)
kann die Attribute und Methoden einer oder mehrerer Oberklassen
übernehmen und dann 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 in Oberklassen vorhandene Methoden von der Unterklasse
- REDEFINIERT (engl. REDEFINE) werden (die Signatur bleibt
gleich) oder
- ÜBERSCHRIEBEN (engl. OVERRIDE) werden
(der Methodenname bleibt gleich, die Signatur ändert
sich). [VORSICHT: Diese Begriffsdefinitionen sind nach
Balzert eingeführt. Gosling benutzt 'Overriding' für
Redefinitionen und 'Overloading' für das Überschreiben,
unterscheidet als nicht zwischen Überladen und
Ü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 vestehen und unterscheiden
zu können.]
Damit können Objektinstanzen unterschiedlicher Klassen Methoden mit
identischer Signatur oder gleichen Namens 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 bereis bekannt, ob zur Laufzeit
auch ein Objekt der Unterklasse K2 als Parameter verwendet
wird. Infolgedessen ist erste 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.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)
- Eine Klasse darf beliebig viele Interfaces implementieren.
- Die Konstruktoren der Oberklasse koennen explizit mit
super()
bzw. super(<Parameterliste>)
referenziert
werden. Die Mitglieder (Attribute und Methoden) der Oberklasse
können durch Vorstellen von <Name der
Oberklasse>.
oder einfach von super.
referenziert werden.
- Die Konstruktoren der Unterklasse koennen explizit mit
this(<Parameterliste>)
referenziert
werden.Die Mitglieder (Attribute und Methoden) der Unterklasse
können explizit durch Vorstellen von <Name der
Unterklasse>.
oder einfach von this.
referenziert werden. Insbesondre 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 können bei der Vererbung nicht überschrieben,
sondern nur VERSTECKT (eng. HIDING)
werden: Definiert die Unterklasse ein
gleichnamiges Attribut der Oberklasse, so ist das Attribut der
Oberklasse weiterhin durch
<Name der Oberklasse>.<Attributname>
oder durch super.<Attributname>
referenzierbar.
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:
- Der Typ 'interface' dient zur impliziten Spezifikation.
- Ein Interface kann (offensichtlich) nicht direkt
instantiiert werden. Stattdessen wird ein Klassentyp
durch Hinzunahme eines Interfaces und seiner Implementierung
erweitert.
- Klassen dürfen mehrere Interfaces implementieren, Deklarationssyntax
class <Classname> implements <Interfacename> {
...
}
- 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 abtrakte Methoden und keine Zustandsvariablen definieren. Eine
Unterklasse darf dann aber nur diese eine abstrakte Klasse erweitern,
da Mehrfachvererbung nicht möglich ist.
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 mit
internen Identifikationscodes der zu obj
gehörigen
Methoden zurück.
- Im Java reflection package
java.lang.reflect
werden zusätzliche Auskunftsklassen
definiert: Beispielsweise bietet 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 (zu Method
gehörige
Methode 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, aber auch nicht erforderlich, da die
baumartige Vererbungshierarchie immer Object als
allgemeinsten Verarbeitungstyp kennt.
- Anstelle eines generischen Typparameters kann in einer
Methodendeklaration immer der Typ 'Object' verwendet
werden. Müssen die auf Object definierten Methoden
(z.B.
equals()
) um weitere dort nicht bereits
eingeführte erweitert werden, lässt sich dies mit Hilfe von
abstrakten Klassen und Interfaces realisieren.
- 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.
- Anstelle von Funktionsparametern für die Verarbeitung der
konkreten Objekte werden die Verarbeitungsfunktionen immer als
Methoden der konkreten Klasse definiert, ggf. durch Überschreiben
der bereits auf Object definierten, in der speziellen Anwendung
aber zu konkretisierenden Methoden.
- Bei den Template-Klassen 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.
Jan Peleska
- TZI - Bremen Institute of Safe Systems BISS /
<
jp@informatik.uni-bremen.de>
/ 29 MAY 2001