Universität Bremen  
  Universität Bremen FB3 TZI BISS  
  AG BS > Lehre > SoSe 2003 > Praktische Informatik 2, SoSe 2003 > Hintergrundinformationen > Deutsch
English
 

Die wichtigsten Stichworte und Fakten zum Thema "Objekt-Orientierte Programmiersprachen"

 

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.
 
   
Autor: jp
 
  AG Betriebssysteme, Verteilte Systeme 
Zuletzt geändert am: 2. November 2022   Impressum