Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 8 - OOP II: Vererbung, Polymorphismus und statische Elemente

## 8.4 Abstrakte Klassen und Polymorphismus

- 8.4 Abstrakte Klassen und Polymorphismus
  - 8.4.1 Abstrakte Klassen
  - 8.4.2 Ein Beispiel für Polymorphismus
  - 8.4.3 Polymorphe Methodenaufrufe in Konstruktoren

### 8.4.1 Abstrakte Klassen

In Java ist es möglich, abstrakte Methoden zu definieren. Im Gegensatz zu den konkreten Methoden enthalten sie nur die Deklaration der Methode, nicht aber ihre Implementierung. Syntaktisch unterscheiden sich abstrakte Methoden dadurch, daß anstelle der geschweiften Klammern mit den auszuführenden Anweisungen lediglich ein Semikolon steht. Zusätzlich wird die Methode mit dem Attribut `abstract` versehen. Abstrakte Methoden können nicht aufgerufen werden, sie definieren nur eine Schnittstelle. Erst durch Überlagerung in einer abgeleiteten Klasse und Implementierung des fehlenden Methodenrumpfes wird eine abstrakte Klasse konkret und kann aufgerufen werden.

|  | Eine Klasse, die mindestens eine abstrakte Methode enthält, wird selbst als abstrakt angesehen und muß ebenfalls mit dem Schlüsselwort `abstract` versehen werden. Abstrakte Klassen können nicht instanziert werden, da sie Methoden enthalten, die nicht implementiert wurden. Statt dessen werden abstrakte Klassen abgeleitet, und in der abgeleiteten Klasse werden eine oder mehrere der abstrakten Methoden implementiert. Eine abstrakte Klasse wird konkret, wenn alle ihre Methoden implementiert sind. Die Konkretisierung kann dabei auch schrittweise über mehrere Vererbungsstufen erfolgen. |  |
Hinweis

### 8.4.2 Ein Beispiel für Polymorphismus

Wir wollen uns ein Beispiel ansehen, um diese Ausführungen zu verdeutlichen. Zum Aufbau einer Mitarbeiterdatenbank soll zunächst eine Basisklasse definiert werden, die jene Eigenschaften implementiert, die auf alle Mitarbeiter zutreffen, wie beispielsweise persönliche Daten oder der Eintrittstermin in das Unternehmen. Gleichzeitig soll diese Klasse als Basis für spezialisierte Unterklassen verwendet werden, um die Besonderheiten spezieller Mitarbeitertypen, wie Arbeiter, Angestellte oder Manager, abzubilden. Da die Berechnung des monatlichen Gehalts zwar für jeden Mitarbeiter erforderlich, in ihrer konkreten Realisierung aber abhängig vom Typ des Mitarbeiters ist, soll eine abstrakte Methode `monatsBrutto` in der Basisklasse definiert und in den abgeleiteten Klassen konkretisiert werden.

Das folgende Listing zeigt die Implementierung der Klassen `Mitarbeiter`, `Arbeiter`, `Angestellter` und `Manager` zur Realisierung der verschiedenen Mitarbeitertypen. Zusätzlich wird die Klasse `Gehaltsberechnung` definiert, um das Hauptprogramm zur Verfügung zu stellen, in dem die Gehaltsberechnung durchgeführt wird. Dazu wird ein Array `ma` mit konkreten Untertypen der Klasse `Mitarbeiter` gefüllt (hier nur angedeutet; die Daten könnten zum Beispiel aus einer Datenbank gelesen werden) und dann für alle Elemente das Monatsgehalt durch Aufruf von `monatsBrutto` ermittelt.

|  | Das Listing ist ebenfalls ein Beispiel für Polymorphismus. Eine Variable vom Typ einer Basisklasse nimmt zur Laufzeit unterschiedliche Objekte aus abgeleiteten Klassen auf. Da bereits in der Basisklasse die Definition von `monatsBrutto` vorgenommen wurde, akzeptiert der Compiler den Aufruf dieser Methode bereits bei Objekten dieser vermeintlich abstrakten Klasse. Erst zur Laufzeit ist dann bekannt, welcher abgeleitete Typ hinter jedem einzelnen Array-Element steht, und das Laufzeitsystem ruft die darin implementierte Variante der Methode `monatsBrutto` auf. |  |
Hinweis

    001 /* Gehaltsberechnung.java */
    002
    003 import java.util.Date;
    004
    005 abstract class Mitarbeiter
    006 {
    007   int persnr;
    008   String name;
    009   Date eintritt;
    010
    011   public Mitarbeiter()
    012   {
    013   }
    014
    015   public abstract double monatsBrutto();
    016 }
    017
    018 class Arbeiter
    019 extends Mitarbeiter
    020 {
    021   double stundenlohn;
    022   double anzahlstunden;
    023   double ueberstundenzuschlag;
    024   double anzahlueberstunden;
    025   double schichtzulage;
    026
    027   public double monatsBrutto()
    028   {
    029     return stundenlohn*anzahlstunden+
    030            ueberstundenzuschlag*anzahlueberstunden+
    031            schichtzulage;
    032   }
    033 }
    034
    035 class Angestellter
    036 extends Mitarbeiter
    037 {
    038   double grundgehalt;
    039   double ortszuschlag;
    040   double zulage;
    041
    042   public double monatsBrutto()
    043   {
    044     return grundgehalt+
    045            ortszuschlag+
    046            zulage;
    047   }
    048 }
    049
    050 class Manager
    051 extends Mitarbeiter
    052 {
    053   double fixgehalt;
    054   double provision1;
    055   double provision2;
    056   double umsatz1;
    057   double umsatz2;
    058
    059   public double monatsBrutto()
    060   {
    061     return fixgehalt+
    062            umsatz1*provision1/100+
    063            umsatz2*provision2/100;
    064   }
    065 }
    066
    067 public class Gehaltsberechnung
    068 {
    069   private static final int ANZ_MA = 100;
    070   private static Mitarbeiter[] ma;
    071   private static double bruttosumme;
    072
    073   public static void main(String[] args)
    074   {
    075     ma = new Mitarbeiter[ANZ_MA];
    076
    077     //Mitarbeiter-Array füllen, z.B.
    078     //ma[0] = new Manager();
    079     //ma[1] = new Arbeiter();
    080     //ma[2] = new Angestellter();
    081     //...
    082
    083     //Bruttosumme berechnen
    084     bruttosumme = 0.0;
    085     for (int i=0; i<ma.length; ++i) {
    086       bruttosumme += ma[i].monatsBrutto();
    087     }
    088     System.out.println("Bruttosumme = "+bruttosumme);
    089   }
    090 }
| Gehaltsberechnung.java

Listing 8.11: Abstrakte Klassen und Methoden

Unabhängig davon, ob in einem Array-Element ein Arbeiter, Angestellter oder Manager gespeichert wird, führt der Aufruf von `monatsBrutto` dank der dynamischen Methodensuche die zum Typ des konkreten Objekts passende Berechnung aus. Auch weitere Verfeinerungen der Klassenhierarchie durch Ableiten neuer Klassen erfordern keine Veränderung in der Routine zur Berechnung der monatlichen Bruttosumme.

So könnte beispielsweise eine neue Klasse `GFManager` (ein Manager, der Mitglied der Geschäftsführung ist) aus Manager abgeleitet und problemlos in die Gehaltsberechnung integriert werden:

    001 public class GFManager
    002 extends Manager
    003 {
    004   double gfzulage;
    005
    006   public double monatsBrutto()
    007   {
    008     return super.monatsBrutto()+gfzulage;
    009   }
    010 }

Listing 8.12: Einfügen einer neuen Mitarbeiterklasse in die Gehaltsberechnung

|  | In der abgedruckten Form ist das Programm in Listing 8.11 natürlich nicht lauffähig, denn das Array `ma` wird nicht vollständig initialisiert. Ansonsten ist es aber korrekt und illustriert beispielhaft die Anwendung abstrakter Klassen und Methoden. |  |
Hinweis

### 8.4.3 Polymorphe Methodenaufrufe in Konstruktoren

Eine besondere Gefahrenquelle liegt darin, polymorphe Methoden im Konstruktor einer Klasse aufzurufen. Der Grund liegt in der Initialisierungsreihenfolge von Membervariablen während der Konstruktion eines Objekts:

- Zuerst werden die Konstruktoren der Vaterklassen aufgerufen und so deren Membervariablen initialisiert.
- Dann werden die Initialisierer und initialisierenden Zuweisungen der eigenen Klasse aufgerufen.
- Schließlich wird der Rumpf des eigenen Konstruktors ausgeführt.

Wird nun im eigenen Konstruktor eine Methode aufgerufen, die in einer abgeleiteten Klasse überlagert wurde, sind die Membervariablen der abgeleiteten Klasse noch nicht initialisiert. Ihr Konstruktor wird ja erst später aufgerufen. Das folgende Beispiel illustriert dieses Problem:

    001 /* Listing0813.java */
    002
    003 class SingleValue
    004 {
    005   protected int value1;
    006
    007   public SingleValue(int value1)
    008   {
    009     this.value1 = value1;
    010     print();
    011   }
    012
    013   public void print()
    014   {
    015     System.out.println("value = " + value1);
    016   }
    017 }
    018
    019 class ValuePair
    020 extends SingleValue
    021 {
    022   protected int value2;
    023
    024   public ValuePair(int value1, int value2)
    025   {
    026     super(value1);
    027     this.value2 = value2;
    028   }
    029
    030   public void print()
    031   {
    032     System.out.println(
    033       "value = (" + value1 + "," + value2 + ")"
    034     );
    035   }
    036 }
    037
    038 public class Listing0813
    039 {
    040   public static void main(String[] args)
    041   {
    042     new ValuePair(10,20);
    043   }
    044 }
| Listing0813.java

Listing 8.13: Polymorphe Methodenaufrufe im Konstruktor

Die Ausgabe des Programms ist nicht "value = 10", sondern "value = (10,0)". Bei der Konstruktion des `ValuePair`-Objekts wird zunächst der Konstruktor der Basisklasse `SingleValue` aufgerufen und initialisiert seine Membervariable `value1`. Der anschließende Aufruf von `print` wird polymorph ausgeführt, denn das zu instanzierende Objekt ist vom Typ `ValuePair`. Da zu diesem Zeitpunkt der Member `value2` aber noch nicht initialisiert ist (das würde erst passieren, wenn der Konstruktor von `SingleValue` komplett abgearbeitet wäre), wird an seiner Stelle 0 ausgegeben (beim Anlegen eines neuen Objekts wird der belegte Speicher mit Nullen gefüllt). Das kann zu schwer auffindbaren Fehlern führen. Aufrufe von Methoden, die möglicherweise überlagert werden, sollten daher im Konstruktor vermieden werden.

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de