next up previous contents index
Nächste Seite: Interfaces Aufwärts: Programmieren in Java Vorherige Seite: Klassen und Objekte   Inhalt   Index

Unterabschnitte

Vererbung

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

\psfig {figure=bilder/cnstrind.ps}


public Object();




\psfig {figure=bilder/methind.ps}


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


Vererbung: Wann und wie?

Die neue erweiterte Klassen steht zur Originalklasse in einem

ist-ein / ist-eine-Art-von

Verhältnis (siehe Seite [*]).




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:




\psfig {figure=bilder/manager.ps}




Meist kann eine Person mehrere Rollen spielen. Die richtige Lösung ist daher:




\psfig {figure=bilder/rolle.ps}


Oberklassen-Konstruktion


   public point3D(float x, float y, float z)
   { super(x, y);
     this.z = z;
   }



Regeln:



Auf diese Art erhalten wir eine

Konstruktor-Kette

die bei jeder Objekt-Erzeugung bis zur Wurzelklasse Object hinaufreicht.




Im Gegensatz dazu erhalten wir nicht automatisch eine

Destruktor-Kette



Diese müssen wir bei Bedarf explizit aufbauen:


   finalize()
   { ...
     super.finalize();
   }

Dies ist durchaus ratsam (Mehr zu finalize auf Seite [*])

Was wird vererbt?

Eine Klasse umfaßt folgende Elemente



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




Verdecken von Attributen

Ein Attribut-Deklaration (statisch oder nicht-statisch) verdeckt eine Attribut-Deklaration einer Oberklasse oder eines Ober-Interfaces, die den gleichen Namen hat.



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:




Verdecken durch Klassenmethoden

Eine Klassenmethode (siehe Seite [*]) verdeckt alle Methoden gleicher Signatur in Oberklassen und Ober-Interfaces, die ansonsten zugreifbar wären (d.h. z.B. nicht die privaten Methoden).




Eine Klassenmethode darf allerdings keine Instanzmethode verdecken!




Wie bei den Attributen gibt es drei Möglichkeiten, aus der Unterklasse auf verdeckte Klassenmethoden zuzugreifen:




Überschreiben durch Instanzmethoden

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

super-Methodenaufruf



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.



Zusätzliche Regeln für Verdeckung und Überschreiben von Methoden




Dynamische Bindung

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();
   }



Der Unterschied zwischen Überschreiben / Verdecken

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



Überladen von Methoden

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



\psfig {figure=bilder/invok.ps}



Der Ablauf der statischen und dynamischen Methodenbindung im einzelnen




\psfig {figure=bilder/invok1.ps} Wie heißt die aufzurufende Methode und in welcher Klasse (oder welchem Interface) suchen wir die passende Methoden-Definition?





\psfig {figure=bilder/invok2.ps} 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

\psfig {figure=bilder/invok3.ps} Wir haben jetzt die Übersetzungszeit-Deklaration der aufzurufenden Methode. Die muß noch 3 Prüfungen bestehen:



\psfig {figure=bilder/invok4.ps} Welches Objekt soll die Methode ausführen?





\psfig {figure=bilder/invok5.ps} 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.




\psfig {figure=bilder/invok6.ps} 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.

\psfig {figure=bilder/invok7.ps} Welche Methode soll ausgeführt werden?




Die final-Kennzeichnung

Als final können in Java-Programmen markiert werden:

Bedeutung in allen Fällen: Dieses Element kann nicht verändert werden.


Finale Daten

Final kennzeichnet konstante Daten, also sowohl

Übersetzungszeitkonstanten

   final int MAX = 12;

also auch

Laufzeitkonstanten

   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

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:




Finale Klassen

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.


Abstrakte Methoden und abstrakte Klassen

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.




next up previous contents index
Nächste Seite: Interfaces Aufwärts: Programmieren in Java Vorherige Seite: Klassen und Objekte   Inhalt   Index
Peter Pfahler, 1997