Neben der Komposition (Seite ) die wichtigste Form
der Wiederverwendung auf der Basis von Klassen.
Eine Oberklasse
class Point { private float x,y; static int pcount = 0; public Point(float x,float y) { this.x = x; this.y = y; pcount++; } public Point() { this(0.0, 0.0); } public void move(float dx, float dy) { x += dx; y += dy; } }
wird zu Implementierung einer neuen Klasse wiederverwendet:
class Point3D extends Point { private float z; public Point3D(float x, float y, float z) { super(x, y); this.z = z; } public Point3D() { this(0.0, 0.0, 0.0); } public void move(float dx, float dy, float dz) { move(dx, dy); z += dz; } }
Jede Java-Klasse benutzt Vererbung:
Durch direkte oder indirekte Vererbung ist Object die
Oberklasse aller Java-Klassen.
Wie sieht Object aus?
Information aus der Dokumentation der JDK APIs.
Oder direkt von
java.sun.com
public Object();
public final native Class getClass() public native int hashCode() public boolean equals(Object obj) public native Object clone() public String toString() protected void finalize()
außerdem 5 Methoden zur thread-Behandlung:
notify, notifyAll, wait
Die neue erweiterte Klassen steht zur Originalklasse in einem
Beispiel:
Ein Punkt im 3D-Raum ist-ein Punkt.
Ein 3D-Punkt hat alle Eigenschaften eines Punktes.
Ein Kreis ist-kein Punkt.
Ein Kreis kann aber durch einen Mittelpunkt und einen Radius beschrieben werden.
Also: Ein Kreis hat-ein Punkt (Mittelpunkt) und einen Radius
( Wiederverwendung durch Komposition, Seite ).
So leicht ist es in der Praxis nicht immer!
Beliebtes Beispiel, das in der Praxis scheitert:
Meist kann eine Person mehrere Rollen spielen. Die richtige Lösung ist
daher:
public point3D(float x, float y, float z) { super(x, y); this.z = z; }
Regeln:
Auf diese Art erhalten wir eine
die bei jeder Objekt-Erzeugung bis zur Wurzelklasse Object hinaufreicht.
Im Gegensatz dazu erhalten wir nicht automatisch eine
Diese müssen wir bei Bedarf explizit aufbauen:
finalize() { ... super.finalize(); }
Dies ist durchaus ratsam (Mehr zu
finalize auf Seite )
Hierbei gelten als Elemente
Keine Elemente im Sinne dieser Definition sind also
Geerbt werden alle Elemente der direkten Oberklassen und Ober-Interfaces, die in einer Klasse
Wegen der ``Zugreifbarkeits-Klausel'' gilt insbesondere
Der Typ von verdeckendem und verdeckten Attribut muß dabei
nicht der gleiche sein.
class Point { public float x,y; static int pcount = 0; } class Test extends Point { static int x; boolean pcount; }
Es gibt drei Möglichkeiten, aus der Unterklasse auf verdeckte Namen zuzugreifen:
Point.pcount
super.x und super.pcount
((Point)this).x und ((Point)this).pcount
Eine Klassenmethode darf allerdings keine Instanzmethode verdecken!
Wie bei den Attributen gibt es drei Möglichkeiten, aus der Unterklasse
auf verdeckte Klassenmethoden zuzugreifen:
Eine Instanzmethode überschreibt alle Methoden gleicher Signatur in Oberklassen und Ober-Interfaces, die ansonsten zugreifbar wären (d.h. z.B. nicht die privaten Methoden).
Eine Instanzmethode darf allerdings keine Klassenmethode überschreiben!
Die einzige Möglichkeit, aus der Unterklasse
auf überschriebene Instanzmethoden zuzugreifen ist der
class Ober { String WhoAreYou() { return "Ober"; } } class Unter extends Ober { String WhoAreYou() { return "Unter"; } void identify() { System.out.println( "I'm " + WhoAreYou() + ". My superclass is " + super.WhoAreYou() + "."); Ober o = (Ober)this; System.out.println( "My superclass is not " + o.WhoAreYou() + "."); } }
Die Anweisung new Unter().identify(); schreibt:
I'm Unter. My superclass is Ober. My superclass is not Unter.
In polymorphen Sprachen kann bei überschriebenen Methoden erst zur Laufzeit festgestellt werden, welche Methode aufgerufen wird ( dynamic method lookup):
class UnterUndOber { Ober a[] = new Ober[100]; for (int i = 0; i < a.length; i++) a[i] = prim(i) ? new Ober() : new Unter(); for (int i = 0; i < a.length; i++) a[i].WhoAreYou(); }
Obwohl in mancher Hinsicht ähnlich, gibt es zwischen überschriebenen Methoden und verdeckten Attributen oder Klassenmethoden wichtige Unterschiede:
class Ober { String id = "ooo"; String WhoAreYou() { return "Ober"; } } class Unter extends Ober { String id = "uuu"; String WhoAreYou() { return "Unter"; } void identify() { Unter u = new Unter(); Ober o = u; System.out.println(o.id); System.out.println(u.id); System.out.println(o.WhoAreYou()); System.out.println(u.WhoAreYou()); }
Ausgabe von new Unter().identify():
ooo uuu Unter Unter
Zwei Methoden einer Klassen heißen überladen, wenn sie den gleichen Namen aber unterschiedliche Signatur haben.
Es spielt keine Rolle,
Erbt man überladene Methoden, kann man einzelne davon überschreiben, ohne die
nicht überschriebenen zu verlieren (Unterschied zu C++).
Für einen Methodenaufruf wird die Signatur der aufzurufenden Methode zur Übersetzungszeit festgelegt; die aufzurufende Methode zur Laufzeit.
Grober Ablauf der statischen und dynamischen Methodenbindung
Der Ablauf der statischen und dynamischen Methodenbindung im einzelnen
Wie heißt die aufzurufende Methode und in welcher Klasse (oder
welchem Interface) suchen wir die passende Methoden-Definition?
Welche Methodendeklarationen der Suchklasse sind anwendbar und
zugreifbar? Welche von denen ist die speziellste?
Eine Methodendeklaration ist anwendbar auf einen Methodenaufruf, wenn
Eine Methodendeklaration ist zugreifbar für einen
Methodenaufruf, wenn die Zugriffsrechte (Seite )
es erlauben.
Gibt es mehrere anwend- und zugreifbare Methodendeklarationen,
nimm die speziellste (die mit den ``spezialisiertesten'' Parametertypen):
void test (Ober o1, Ober o2) { ... } void test (Unter u1, Ober o1) { ... } void test (Ober o1, Unter u1) { ... } void test (Unter u1, Unter u2) { ... }
Compiler-Fehlermeldungen gibt es, wenn
Wir haben jetzt die Übersetzungszeit-Deklaration der
aufzurufenden Methode. Die muß noch 3 Prüfungen bestehen:
Welches Objekt soll die Methode ausführen?
Die Argumentausdrücke werden nacheinander von links nach rechts
ausgewertet.
Wenn eine dieser Auswertungen abbricht, wird keines der Argumente
rechts davon ausgewertet (bzw. man merkt nichts davon)
und der gesamte Methodenaufruf wird abgebrochen.
Gibt es es die aufzurufende Methode aus der
Übersetzungszeitdeklaration
überhaupt
noch und darf man auf sie zugreifen?
Der Interpretierer merkt sich das Ergebnis der Prüfung solange die
betreffende Klasse geladen bleibt.
Welche Methode soll ausgeführt werden?
Als final können in Java-Programmen markiert werden:
Bedeutung in allen Fällen: Dieses Element kann nicht verändert werden.
Final kennzeichnet konstante Daten, also sowohl
final int MAX = 12;
also auch
final int RAND = (int)random*12;
final-Werte müssen bei ihrer Deklaration initialisiert werden:
// konstanter float-Wert: static final float PI = 3.14; // konstante Referenz: final Uhr u = new Uhr(14,50);
Ausnahme: Die in Java1.1 neuen Blanko-Konstanten ( blank finals):
Konstanten, die für jede Instanz einer Klasse einen anderen Wert haben können. Jeder Konstruktor muß diesen Wert zuweisen:
class Blankfinaltest { final int C; int val; Blankfinaltest() { C = 7; } Blankfinaltest(int v) // FALSCH { val = v; } }
Der Compiler:
Blank final variable 'C' may not have been initialized. It must be assigned a value in an initializer, or in every constructor.
Finale Methoden können von abgeleiteten Klassen nicht überschrieben werden:
class Finalm { final void m() { System.out.println("fini"); } } class Finalsub extends Finalm { void m() // FALSCH { System.out.println("over"); } }
Der Compiler:
Final methods can't be overriden. Method void m() is final in class Finalm.
Gründe für finale Methoden:
Insgesamt kann der Java-Compiler in folgenden Fällen dynamische Methodenbindung durch einen schnellen Direktaufruf ersetzen:
Darüberhinaus kann in diesen Fällen Inlining der Methodenrümpfe in Betracht gezogen werden.
Durch final-Kennzeichnung einer gesamten Klasse verbietet man die Erweiterung dieser Klasse:
final class Finalc { void m() { System.out.println("fini"); } } class Finalsub extends Finalc // FALSCH { void m() { System.out.println("over"); } }
Der Compiler:
Can't subclass final classes: class Finalc
Die Gründe für finale Klassen sind ebenfalls Sicherheit und/oder Effizienz.
Eine Methode, von der nur die Signatur und nicht der Rumpf definiert ist3. Erbende Klassen können die Implementierung ``nachliefern''.
abstract class Bench { abstract void benchmark(); public long repeat(int count) { long st = System.currentTimeMillis(); for (int i = 0; i < count; i++) benchmark(); return System.currentTimeMillis() - st; } } class Benchmark extends Bench { void benchmark() { // leere Methode } public static void main (String[] args) { int count = Integer.parseInt(args[0]); long time = new Benchmark().repeat(count); System.out.println(count + " methods in " + time + " milliseconds"); } }
Regeln für abstrakte Methoden und Klassen:
Typisches Anwendungsmuster:
Eine Klasse bezieht ``Expertenwissen'' oder spezielles Verhalten von
ihren Unterklassen.
Das Design der Klasse wird angegeben, nicht aber die (vollständige) Implementierung.