ACHTUNG! Diese Seite befindet sich aktuell in der Überarbeitung.
Objektorientierte Programmierung
In der objektorientierten Programmierung werden - abweichend vom imperativen Ansatz - Daten und die zugehörigen Funktionen in Objekten aneinander gebunden. Dies entspricht unserer Wahrnehmung der realen Welt und erlaubt es, Softwaresysteme auf der Basis der von ihnen zu verarbeitenden fachlichen Objekte der realen Welt zu entwerfen.
Beim imperativen Ansatz wird eine Funktionalität - beispielsweise in Form einer Methode - implementiert und die Daten werden der Methode als Parameter übergeben:
// Aufruf einer Methode, die das Maximum von a zurückliefert. // Der konkrete Typ von a ist in diesem Beispiel unerheblich. max(a);
Beim objektorientierten Ansatz werden demgegenüber die Daten eines Objektes sowie die zugehörigen Funktionalitäten definiert und eine bestimmte Funktionalität wird auf diesem Objekt ausgeführt:
// Anweisung an das Objekt a, seine Methode max auszuführen a.max();
Der Methodenaufruf liefert entweder ein Ergebnis zurück oder ändert den Status des Objekts.
Status eines Objekts
Unter dem Status (oder auch Zustand) eines Objekts wird die Gesamtheit aller aktuellen Werte seiner Daten verstanden. Falls ein Objekt zwei Ganzzahlen und eine Zeichenkette als Attribute (so nennt man die Daten eines Objekts) enthält, könnte sein aktueller Zustand beispielsweise (17, 42, "Java") oder (-5, 466, "EGK77-32/17") sein, sofern den drei Attributen die entsprechenden Werte zugewiesen wurden.
Java Insel
Themen
- Klasse
- Referenzvariable
- Abstraktion
- Kapselung
- Vererbung
- Polymorphie
- this / super
- null Referenz
- Copy Konstruktor
- clone()
- Serialisierung (JavaWorld)
- Klassenvariable
- Statische Methode
- ToDo
- 3 Prinzipien der OOP
- Zugriff auf Objekte: Variable, Arrayelement, Methodenrückgabe, ...
- Vorlesungsbeispiele einbauen
- Interfaces
- Abstrakte Klassen
Klasse
Klassen sind Schablonen für Objekte. In der Klasse wird definiert, aus welchen Komponenten (Attribute, Methoden, Konstruktoren) die Objekte dieser Klasse bestehen. Mit einer Klasse wird also ein Datentyp definiert. Von diesem Typ lassen sich wiederum Instanzen bilden, die Objekte. Jedes erzeugte Objekt verfügt über alle Attribute, die in der zugehörigen Klasse deklariert sind und kann alle Methoden aufrufen, die in dieser Klasse definiert wurden.
- Allgemeine Form
- Sichtbarkeitsmodifikator class Klassenname {...}
- Beispiel
Die Klasse Datum definiert ein Datum bestehend aus Tag, Monat und Jahr als Ganzzahlen sowie einer Methode toString, welche das Datum als Zeichenkette mit Punkten (.) getrennt zurückliefert:
public class Datum { int tag; int monat; int jahr; public String toString() { return tag + "." + monat + "." + jahr; } }
Objekte
Objekte sind Instanzen von Klassen, also die im Programm tatsächlich verwendeten, Daten enthaltenden Exemplare.
Attribute
Methoden
Konstruktoren
Klassenvariable
- Definition
- Eine Klasse ist eine Schablone für das Erzeugen einer beliebigen Anzahl gleichartiger Objekte und umfasst die gemeinsamen Attribute und Methoden der Objekte.
- selbst-definierte Datentypen.
- können verschiedene bestehende Datentypen zu einem neuen, übergeordnetem Datentyp zusammenfassen.
- können nicht nur Daten, sondern insbesondere Daten und die darauf arbeitenden Operationen zusammenfassen.
Objekte
- Definition
- Ein Objekt ist eine Instanz (ein Exemplar) einer Klasse. Es wird bei Bedarf aus der Klasse abgeleitet.
Ein Objekt besteht aus Attributen und Methoden.
Attribut
Attribute sind "Behälter" für Werte, die den internen Objektzustand mitbestimmen.
Methoden
Eine Methode ist eine Funktion eines Objektes, die nicht unbedingt von außen sichtbar sein muss. Die Menge aller Methoden bildet die Schnittstelle eines Objektes.
- Beispiel
-
Bücher in einer Bibliothek können sehr verschieden sein.
Dennoch haben alle Bücher gleiche Eigenschaften (Attribute) und Methoden.- Attribute: Autoren, Titel, Verlag, Erscheinungsjahr, Seitenanzahl, ...
- Methoden: ausleihen, zurückgeben, vormerken, verlängern, ...
Erzeugen und Verwenden von Objekten
- Erzeugung von Objekten (Instanziierung) mit Schlüsselwort new
Buch javaLehrbuch = new Buch("Müller, Peter", "Java Grundlagen", 1999); Konto konto1 = new Konto("12345", "Meier, Fred");
- Zugriff auf Objektmethoden und öffentliche Attribute mit Punktnotation
javaLehrbuch.setJahr(2000); konto1.setGuthaben(42000); kontonummer = konto1.getKontonr();
Ein explizites Zerstören eines Objektes nach der Verwendung ist nicht notwendig.
Getter-/Setter-Methoden
Der direkte Zugriff auf Variablen eines Objektes ist nicht so elegant. Es ist besser, über Methoden auf Variablen zuzugreifen. Diese Methoden müssen die Objekt-Eigenschaften formulieren und die Werte der Variablen zurückliefern.
- Allgemeine Form
-
rueckgabetyp getAttributname( return this.attributname; } datentyp setAttributname(datentyp wert) { this.attributname = wert; }
- Beispiel
-
class Rechteck { private double laenge, breite; void setLaenge(double l) { this.laenge = l; } double getLaenge() { return this.laenge; } void setBreite(double b) { this.breite = b; } double getBreite() { return this.breite; }
Referenzvariable
In primitiven Datentypen werden in den Variablen unmittelbar die Werte (Zahlen, Zeichen,...) gespeichert.
Strings, Arrays und Objekte sind hingegen Referenztypen. Sie werden nicht als Ganzen in einer Variablen gespeichert. In der Variablen steht vielmehr die Speicheradresse, unter der die Objektdaten zugreifbar sind. Daher spricht man auf von Referenzvariablen.
- In Java ist die Speicheradresse nicht auslesbar.
- Der Default-Wert von Referenzvariablen ist null.
Objektzugriffe
- Allgemeine Form
- Typ referenzvariable;
referenzvariable = new Typ();
- Beispiele
Rechteck r1; // Referenzvariable r1 ist vom Typ Rechteck r1 = new Rechteck(); // r1 verweist auf ein Objekt vom Typ Rechteck r1.laenge = 5; r1.breite = 2; r1 = null; // Objekt nicht mehr benutzbar
// Direkte Instanziierung Kreis k1 = new Kreis(); k1.radius = 1.42;
Dreieck d1 = new Dreieck(); Dreieck d2 = new Dreieck(); Dreieck d3 = new Dreieck(); d1.a = 5.1; d1.b = 6.8; d1.c = 2; d2 = d1; d3.b = d1.a; d2 = null;
Methoden und Parameter
Um Daten in eine Methode zu übergeben, werden diese in die Parameterliste des Methodenaufrufs geschrieben.
Inhalt der Parameterliste:
- Typen der Parameter, die der Methode übergeben werden müssen.
- Namen, unter denen sie in der Methode angesprochen werden.
- Ablauf eines Methodenaufrufs
-
- Parameter auswerten
Paramterübergabe: übergebenen Werte werden den Methodenparametern automatisch zugewiesen. - Rumpf ausführen
Der Rumpf der aufgerufenen Methode wird ausgeführt. - Ergebnis zurückgeben
Die Ausführung des Methodenrumpfs liefert einen Ergebniswert zurück, der dem Rückgabedatentyp entspricht. Eine return-Anweisung beendet Methodenausführungen und gibt den Wert zurück. Es können auch mehrere return-Anweisungen verwendet werden.
- Parameter auswerten
- Beispiel
Parameterübergabe per Wert
aufrufender Teil | methode(int innen) | aussen | innen | |
---|---|---|---|---|
1. | int aussen = 42; | 42 | ||
2. | methode(aussen); | 42 | 42 | |
3. | innen = 84; | 42 | 84 | |
4. | 42 |
- beim Aufrufen einer Methode werden die Parameterwerte vom aufrufenden Programmteil in die Methode kopiert.
- Wertänderungen der Parameter in der Methode wirken sich nicht auf Werte außerhalb der Methode aus!
Parameterübergabe per Referenz
- beim Aufrufen einer Methode werden die Verweise auf die Objekte vom aufrufenden Teil in die Methode kopiert, nicht die Objekte selbst.
- Veränderungen der Objekte in der Methode wirken sich außerhalb der Methode aus!
Überladen von Methoden
Die Implementierung von Methoden mit gleichem Rückgabetyp und Methodenname, aber unterschiedlicher Parameterliste, nennt man Überladen. Dadurch ergibt sich die Möglichkeit, unterschiedliche Methodenverhalten in Abhängigkeit von den gegebenen Parametern zu definieren.
- Beispiel
public int verlaengern(int tage) { ... leihFrist = leihFrist + tage; ... } public int verlaengern() { ... leihFrist = leihFrist + Standartwert; ... }
Konstruktoren
Zur Objekterzeugung gibt es besondere Methoden, die sogenannten Konstruktoren. Dabei wird zwischen dem Default-Konstruktor und parametrisierbaren Konstruktoren unterschieden.
Es müssen jedoch keine Objekte explizit gelöscht werden. Das Java-System erkennt automatisch, dass ein Objekt nicht mehr von einer Variable direkt oder indirekt erreichbar ist und der Garbage-Collector sammelt diese auf.
Mit dem Schlüsselwort new und dem Aufruf eines Konstruktors werden Objekte erzeugt.
- Allgemeine Form
- Klassenname variablenamen = new Klassenname();
Klassenname variablenamen = new Klassenname( wert1, wert2, ... );
Klassenname variablenamen = new Klassenname( wert );
Default-Konstruktor
Jede Klasse hat immer einen Konstruktor, auch wenn er nicht explizit implementiert wurde. (Default-Konstruktur)
- Allgemeine Form
-
public class Klassenname { datentyp attribut; public Klassenname() { } }
Parametrisierte Konstruktoren
Parametrisierte Konstruktoren werden häufig für die Initialisierung von Attributen beim Erzeugen eines Objektes genutzt und können, wie Methoden, überladen werden.
Wenn ein parametrisierter Konstruktor definiert wurde, muss der Konstruktor ohne Parameter explizit definiert werden!
public class Klassenname { datentyp attribut; public Klassenname() { } // Parametrisierte Konstruktor public Klassenname(datentyp d) { this.attribut = d; } }
Copy-Konstruktor
Der Copy-Konstruktor erlaubt es, typgleiche Objekte als Parameter zu übergeben. So lässt sich ein Objekt mit den Werten des Übergebenen instanziieren.
- Allgemeine Form
-
public class Klassenname { datentyp attribut; public Klassenname() { } // Copy-Konstruktor public Klassenname(Klassenname k) { this.attribut = k.attribut; } }
Gültigkeitsbereich von Bezeichnern
Es besteht bei großen Programmen ein Problem, da verschiedene Ebenen erkennbar sind und überall Bezeichner benötigt werden.
Die Ebenen eines Java-Programms sind hierarchisch angeordnet:
- Dateien xxx.java
- Klassendeklarationen (Klassennamen, Konstruktoren)
- Methodendeklarationen (Namen von Methoden, formalen Parametern, lokalen Variablen)
- Schleifen (Variablen zur Steuerung von Schleifen, Namen von Anweisungen)
Die Gültigkeitsbereiche orientieren sich an syntaktischen Kontext/Struktur:
- Alle Bezeichner müssen vereinbart werden.
- Jede Vereinbarung wird dem Block zugeordnet, in der sie geschrieben ist.
- Bei mehreren verschachtelten Blöcken, gehört die Vereinbarung zum innersten Block.
- Vereinbarung vor Benutzung.
- Ein Bezeichner kann erst nach seiner Vereinbarung verwendet werden.
- Ausnahmen: Klassen und Methoden innerhalb des gleichen Blocks.
- Gültigkeit bis an das Ende des Blocks, welcher die Vereinbarung enthält.
- Ein Bezeichner gilt nur bis zu der schließenden geschweiften Klammer des betreffenden Blocks.
- Namensgleiche Bezeichner in einem inneren Block überdecken die aus einem äußeren Block.
- In einem Block müssen alle Bezeichner verschieden sein.
- Bezeichner im selben Block müssen verschiedene Namen aufweisen, damit man sie unterscheiden kann.
- Ausnahmen: Überladen von Methoden und verschiedenartige Programmelemente (Klassen, Methoden,Variablen)
Erweiterung einer Klasse
Die ursprüngliche Idee für die Klassenbildung war, eine Einheit aus Daten und Methoden zu bilden und dies in einer Informationsstruktur mit einem engen Bezug zur Problemstellung darzustellen.
In Java lassen sich allgemeine und spezielle Eigenschaften durch Ober- und Unterklassen darstellen, wobei gemeinsame/allgemeine Eigenschaften (Variablen/Methoden) in Oberklassen definiert werden.
- Begriffe
-
Unterklasse: Eine Klasse, die erweitert.
Oberklasse: Eine Klasse, die erweitert wird.
Extension einer Klasse: Die Menge aller Objekte einer Klasse und all ihrer Unterklassen.
Vererbung: Das Konzept der Erweiterung von Klassen.
- Beispiel
- Eine Reise, Bahnreise, Flugreise
public class Reise { DatumsTyp datum; Ort start, ziel; }
public class Bahnreise extends Reise { ZugTyp zug; }
public class Flugreise extends Reise { FlugzeugTyp flugzeug; }
- Die Klasse Reise ist die Oberklasse von Flugreise und Bahnreise.
- Die Klassen Flugreise und Bahnreise heißen Erweiterung der Klasse Reise.
Zuweisungskompatibilität
Der Zugriff auf Objekte erfolgt über Verweis-Variablen.
- Objekte der Unterklasse sind auch Objekte der Oberklasse:
Bahnreise extends Reise - Variable haben aber einen deklarierten Typ und verweisen auf Objekte ihres Typs:
Bahnreise b; Reise r;
Bahnreise b = new Bahnreise(); - Es ist jedoch auch erlaubt, dass Variablen auf Objekte ihrer jeweiligen Unterklassen verweisen:
r = b;
Welche Attribute stehen dann in dieser Situation zur Verfügung?
Widening
Ein Objekt von einer Oberklasse her zu betrachten, wird Widening genannt.
Dadruch sind die speziellen Eigenschaften der Unterklassen nicht mehr sichtbar (implizite Typ-Anpassung):
Bahnreise b = new Bahnreise(); Flugreise f = new Flugreise(); Reise r = f; ... r.flugzeugtyp ... // unzulässig.
Narrowing
Ein Objekt von einer Unterklasse her zu betrachten, wird Narrowing genannt.
Dabei muss die Typanpassung explizit angegeben werden:
Bahnreise b; b = (Bahnreise) r;
Der Effekt ist eine Typüberprüfung zur Laufzeit:
Bevor die Zuweisung vollendet wird, geschieht die Überprüfung, ob das aktuelle Objekt r vom Typ Bahnreise oder eine Unterklasse davon ist.
Mit instanceof kann ein expliziter Test auf Klassenzugehörigkeit formuliert werden:
if (r instanceof Flugreise) { f = (Flugreise) r; ... } else if (r instanceof Bahnreise) { b = (Bahnreise) r; ... } else { System.out.println("r verweist auf ein Objekt" + " unbekannten Typs."); }
- Beispiele
- Bahnreise bahn1 = new Bahnreise();
legal: Variable vom Typ Bahnreise erhält Referenz auf Bahnreise Instanz - Bahnreise bahn2 = new Flugreise();
illegal: Bahnreise hat keine Vererbungsbeziehung zu Flugreise. - Reise reise = new Bahnreise();
legal: reise hat aber keinen Zugriff auf Bahnreise-spezifische Elemente. (Widening) - Bahnreise bahn3 = new Reise();
illegal: In Reise sind nicht alle Elemente deklariert, die für Bahnreise benötigt werden. - Bahnreise bahn4 = reise;
illegal: zur Compile-Zeit nicht erkennbar, ob es zur Laufzeit noch passt. - Bahnreise bahn5 = (Bahnreise) reise;
legal: Narrowing.
Erzeugung von Objekten
Welche Rolle spielen Ober- und Unterklassen bei der Erzeugung von Objekten?
new liefert ein neues Objekt der jeweiligen Klasse, dabei werden die Konstruktoren aller Oberklassen aufgerufen.
- Konstruktor-Aufrufreihenfolge
- Konstruktor der Oberklasse
- rekursive Wiederholung bis zum leeren Konstruktor von Object, der zuerst ausgeführt wird.
- Konstruktoren der Attribute in der Reihenfolge ihrer Deklaration.
- Rumpf des Konstruktors.
Begründung: Bei Instanziierung eines Objektes muss es möglich sein, auf Attribute der Oberklasse und auf die eigenen Attribute zuzugreifen.
this(...) & super(...)
Mit this(...) kann ein Konstruktor innerhalb der Klasse benutzt werden.
Mit super(...) können Konstruktoren der Oberklassen benutzt werden.
Bedingungen:
- this(...) oder super(...) müssen als erste ausführbare Anweisung in einem Konstruktor erscheinen.
- this(...) und super(...) schließen sich gegenseitig aus.
- Beispiel
class Punkt2D { int x, y; Punkt2D() { // this(0,0) ruft den zweiten Konstruktor mit den Werten 0 und 0 auf this(0, 0); } Punkt2D(int x, int y) { this.x = x; this.y = y; } }
class Punkt3D extends Punkt2D { int z; Punkt3D() { // this(0,0,0) ruft den zweiten Konstruktor mit den Werten 0 und 0 auf this(0, 0, 0); } Punkt3D(int x, int y, int z) { // super(x,y) ruft den zweiten Konstruktor aus der Klasse Punkt2D auf super(x, y); this.z = z; } }
Verdecken und Überschreiben
Die Verwendung von identischen Namen in einer Erweiterung muss geregelt sein. Deshalb gilt:
- Bei Variablen:
Deklaration in Unterklasse verdeckt die Deklaration in der Oberklasse.
Beim Verdecken von Variablen ist (zunächst) nur die aktuelle Unterklassen-Variable sichtbar.class Ober { // int abc ist durch boolean abc verdeckt. int abc; }
Die verdeckten Variablen einer Oberklasse können mittels einer Variable des Typs der Oberklasse sichtbar gemacht werden.class Unter extends Ober { boolean abc; }
Unter U = new Unter(); Ober O = U; O.abc = 2; U.abc = true;
- Bei Methoden:
Deklaration in Unterklasse überschreibt die Deklaration in der Oberklasse.
Methoden sind identisch, wenn der Methodenkopf identisch ist. Es wird immer die speziellste Version einer Methode aufgerufen. Dieses Prinzip nennt man Späte Bindung.class Ober { int b(boolean x) { if (x) { return 0; } return 42; } }
Die überschriebene Methode lässt sich durch den Selbstverweis super. aus Sicht der Oberklasse aufrufen.class Ober { int b(boolean y) { if (y) { return 22; } return 1; } }
class Ober { int b(boolean y) { return super.b(y); } }
Verwendung einer Erweiterung
Die Erweiterungskonzepte für Methoden in Java: statisch und dynamisch
- Statische Erweiterung von Methoden (Überschreiben):
-Methodenkopf identisch in Ober- und Unterklasse.
-Späte Bindung. - Dynamische Erweiterung:
-Es wird einer Methode der Oberklasse aus dem Rumpf einer Methode der Unterklasse aufgerufen.
-Benutzung des Selbstverweises super.xx(yy).
Anonyme Erweiterung
Wenn von einer Unterklasse nur ein Objekt benötigt wird, kann man einer sogenannte einmalige (anonyme) Erweiterung benutzen.
- Beispiel
-
class Ober { int tuWas(int a) { return a * a; } }
... Ober o = new Ober() { int tuwas(int a) { // Spezielle, einmalige Erweiterung: return a + 42; } }; ...
Beziehungen zwischen Klassen
Es gibt verschiedene Formen der Beziehung zwischen Klassen.
- Statische Erweiterung (benannt oder anonym)
Methode der Oberklasse wird überschrieben.
- Dynamische Erweiterung
Rückgriff auf Methode der Oberklasse mit super..
- Benutzt-Beziehung
Ein Objekt vom Typ X benutzt ein Objekt vom Typ Y:public class X { // Referenzvariable ermöglicht Benutzt-Beziehung Y y; public void def() { // Benutzt-Beziehung zu einem neuen Objekt Y y = new Y(); y.abc(); y.test(); } }
- Erweiterung durch Delegation
Die Eigenschaften einer Klasse werden von einer anderen Klasse importiert und dann an spezifischen Stellen speziell erweitert:Vorteil: Eigenschaften können währen der Laufzeit geändert und allgemeine Strukturen können spezialisiert werden.class Speziell { Allgemein temp; public Speziell() { temp = new Allgemein(); } void abc() { ... // Delegation an Allgemein. temp.abc(); ... } }