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

9.4 Abstrakte Klassen und Polymorphismus



9.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, dass 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 Methodenrumpfs wird eine abstrakte Klasse konkret und kann aufgerufen werden.

Eine Klasse, die mindestens eine abstrakte Methode enthält, wird selbst als abstrakt angesehen und muss ebenfalls mit dem Schlüsselwort abstract versehen werden. Abstrakte Klassen können nicht instanziert werden, da sie Methoden enthalten, die nicht implementiert wurden. Stattdessen 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 

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

In der abgedruckten Form ist das Programm in Listing 9.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 

9.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:

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 /* Listing0913.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 Listing0913
039 {
040   public static void main(String[] args)
041   {
042     new ValuePair(10,20);
043   }
044 }
Listing0913.java
Listing 9.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 er 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, 7. Auflage, Addison Wesley, Version 7.0
 <<    <     >    >>   API  © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de