Programmiertechnik Vererbung & Polymorphismus für Fortgeschrittene Prof. Dr. Oliver Haase Oliver Haase Hochschule Konstanz 1 Motivation Aufgabe: Finanzbuchhaltungssystem für internationale Hotelkette Problem: Hotels in verschiedenen Ländern rechnen mit verschiedenen Währungen, die ineinander umgerechnet werden müssen. Annahme: Abrechnungen erfolgen in US-Dollar. Problem: Umtauschkurs ändert sich jeden Tag! Beispiel: Hotel in Tokio hat eine Safe-Einlage im Wert von $25000. Über Nacht steigt der Kurs des Yen um 10% neuer Wert der Safe-Einlage: $27500. Der Wert der Safe-Einlage darf nicht in US-$ gespeichert werden, sondern muss dynamisch berechnet werden. Oliver Haase Hochschule Konstanz 2 Abstrakte Klassen Idee: Generalisiere von den konkreten Währungen, und führe eine abstrakte Klasse Waehrung ein: public abstract class Waehrung { // gibt den Wert in US-$ aus public abstract double dollarBetrag(); } Eine abstrakte Klasse kann abstrakte Methoden enthalten. Eine abstrakte Methode ist eine Methodendeklaration ohne Implementierung. Jede Subklasse muss ihre eigene Implementierung bereitstellen. Eine abstrakte Klasse kann nicht instantiiert werden! Oliver Haase Hochschule Konstanz 3 Abstrakte Klassen in UML Waehrung dollarBetrag: double Merke: Die Namen abstrakter Klassen und Methoden werden kursiv geschrieben. Oliver Haase Hochschule Konstanz 4 Abstrakte Klassen – Zweck Wozu sind abstrakte Klassen gut, wenn sie nicht instantiiert werden können? Vererbung! Subklassen erben alle Eigenschaften der abstrakten Superklasse im Beispiel: alle Subklassen von Waehrung müssen die abstrakte Methode dollarBetrag implementieren. D.h. die abstrakte Klasse Waehrung garantiert, dass alle Subklassen die Methode dollarBetrag besitzen (Schnittstellenvererbung) Oliver Haase Hochschule Konstanz 5 Klasse USDollar einfachste Währung ist US-Dollar, da keine Umrechung nötig ist: public class USDollar extends Waehrung { // Instanzvariable: Wert in US-$ private double wert; public USDollar(double wert) { this.wert = wert; } public double dollarBetrag() { return wert; } } Oliver Haase Hochschule Konstanz 6 Klasse Yen public class Yen extends Waehrung { private static double kurs; Klassenvariable, da Kurs für alle Yen-Objekte gilt und nur einmal gespeichert werden muss // Instanzvariable: Wert in Yen private double wert; public Yen(double wert) { this.wert = wert; } public double dollarBetrag() { return wert * kurs; } } Wert wird in Yen gespeichert Wert in US-$ wird zurückgegeben. public static void setKurs(double kurs) { Yen.kurs = kurs; } Oliver Haase Kurs kann klassenweit aktualisiert werden Hochschule Konstanz 7 Verwendung der Klasse Yen Tokioter Hotel hat eine Safe-Einlage von ¥ 2 000 000: Yen safeInhalt = new Yen(2000000); Tauschkurs ändert sich auf 130 ¥ pro US-$: Yen.setKurs(1.0 / 130); Aktuellen Wert des Safe-Inhalts in US-$ ausgeben: System.out.println("Wert der Einlage in US-$: " + safeInhalt.dollarBetrag()); Konsole Wert der Einlage in US-$: 15384.62 Oliver Haase Hochschule Konstanz 8 Klasse Waehrung – Erweiterung Aufgabe: Berechnung der Steuer von 8% (veranlagt in den USA) für das gesamte Barvermögen der Hotelkette. Idee: Implementiere diese Funktionalität als Klassenmethode der abstrakten Klasse Waehrung: public static double berechneSteuer(Waehrung[] geld) { double summe = 0.0; for ( int i = 0; i < geld.length; i++ ) { summe += geld[i].dollarBetrag(); } return summe * 0.08; } Wie kann es ein Feld von Komponenten vom Typ Waehrung geben, wenn Waehrung nicht instanziiert werden kann? Oliver Haase Hochschule Konstanz 9 Waehrung – Polymorphismus Antwort: Jede Subklasse von Waehrung, d.h. USDollar und Yen, kann an die Stelle der Superklasse treten Polymorphismus! Erklärung: Objekte vom Typ USDollar und Yen besitzen mindestens alle Eigenschaften der Klasse Waehrung. Allgemein: Ein speziellerer Typ kann all das, was ein allgemeinerer Typ kann. Speziellerer Typ wird implizit aufwärtskonvertiert in allgemeineren Typ. Oliver Haase Hochschule Konstanz 10 Waehrung – Polymorphismus Beispiel: Waehrung[] geld = new Waehrung[3]; geld[0] = new USDollar(2500); geld[1] = new Yen(2000000); geld[2] = new USDollar(20); double steuer = berechneSteuer(geld); Beachte: Ein Objekt vom Typ USDollar kann an eine Variable vom Typ Waehrung zugewiesen werden US-$ ist eine (is-a) Währung. Oliver Haase Hochschule Konstanz 11 Objekttyp-Konversion Allgemeiner: Sei AllgemeineKlasse eine Superklasse von SpezielleKlasse. Dann sind die folgenden Codezeilen zulässig. AllgemeineKlasse a; SpezielleKlasse s; a = s; und auch: AllgemeineKlasse a = new SpezielleKlasse(); Beachte: Java führt eine implizite Aufwärtskonversion für Objekttypen durch, ähnlich der für skalare Typen. Diese kann auch explizit angegeben werden: AllgemeineKlasse a = (AllgemeineKlasse) new SpezielleKlasse(); Oliver Haase Hochschule Konstanz 12 Objekttyp-Konversion Auch eine explizite Abwärtstypwandlung ist möglich Vorsicht: kann zu Laufzeitfehler führen! AllgemeineKlasse a = new SpezielleKlasse(); SpezielleKlasse s = (SpezielleKlasse) a; OK AllgemeineKlasse a = new AllgemeineKlasse(); SpezielleKlasse s = (SpezielleKlasse) a; falsch! Oliver Haase Hochschule Konstanz 13 Objekttyp-Konversion Der Typ eines Objektes kann zur Laufzeit mit dem instanceofOperator geprüft werden: Syntaxregel <objectName> instanceof <KlassenName>; Die Operation liefert true, wenn das Objekt eine Instanz der angegeben Klasse oder einer (direkten oder indirekten) Subklasse ist, und false anderenfalls. Beispiel (Sichern einer Abwärtstypwandlung): AllgemeineKlasse a = new SpezielleKlasse(); if ( a instanceof SpezielleKlasse) SpezielleKlasse s = (SpezielleKlasse) a; else System.out.println( "something went terribly wrong!"); Oliver Haase Hochschule Konstanz 14 Waehrung – Polymorphismus Frage: Was passiert beim Aufruf von dollarBetrag() in der Methode berechneSteuer()? public static double berechneSteuer( Waehrung[] geld) { double summe = 0.0; for ( int i = 0; i < geld.length; i++ ) { summe += geld[i].dollarBetrag(); } return summe * 0.08; } Antwort: Für Objekte vom Typ USDollar wird die Methode verwendet, die in der Klasse USDollar definiert wurde, und für Objekte vom Typ Yen die Methode, die in der Klasse Yen definiert wurde späte Bindung (late binding). Oliver Haase Hochschule Konstanz 15 super Analog zu this gibt es auch ein Schlüsselwort super. Zur Erinnerung: this bezeichnet die aktuelle Instanz, für die gerade eine Instanzmethode ausgeführt wird. Mit this.<KomponentenName> kann auf eine Instanzvariable oder eine andere Instanzmethode zugegriffen werden. Als Methode – this(<Parameterliste>) – bezeichnet this einen Konstruktor. Damit kann ein (spezieller) Konstruktor auf einen (allgemeinen) Konstruktor zurückgeführt werden. Sehr ähnliches gilt für die super-Referenz und den superKonstruktor! Oliver Haase Hochschule Konstanz 16 super-Konstruktor Ein Konstruktor kann – und zwar nur in der 1. Zeile – einen Aufruf des Konstruktors der Superklasse enthalten. Wenn das nicht der Fall ist, fügt Java automatisch den parameterlosen Konstruktor der Superklasse ein. public class NonsensDollar extends USDollar { public NonsensDollar(double wert) { super(wert); } public double dollarBetrag() { überschreibt gleichreturn Math.random(); namige Methode der } Klasse USDollar. } Oliver Haase Hochschule Konstanz 17 K‘tor - Ablaufdiagramm Konstruktor bearbeiten Beginnt K'tor mit this? ja nein Konstruktor der Superentsprechenden klasse bearbeiten Konstruktor bearbeiten Initialisierer anwenden Rumpf ausführen Oliver Haase Hochschule Konstanz 18 super-Referenz Die super-Referenz bezeichnet das aktuelle Objekt, jedoch als Instanz der Superklasse betrachtet! Damit ermöglicht super Zugriff auf Komponenten (Variablen und Methoden), die anderenfalls überdeckt und damit nicht sichtbar wären: public class NonsensDollar extends USDollar { … public double jetztMalImErnst() { return super.dollarBetrag(); } } Oliver Haase Hochschule Konstanz 19 Die Klasse Object Jede Klasse, die keine explizite Superklasse hat, ist automatisch Subklasse von java.lang.Object. Damit ist letztendlich jede Klasse direkt oder indirekt Subklasse von Object, und hat damit auch alle Eigenschaften der Klasse Object. Object definiert eine Reihe von Methoden. Die für uns interessanteste ist public String toString(). Außerdem wird die gemeinsame Superklasse Object als Elementtyp von Kollektionen verwendet. Oliver Haase Hochschule Konstanz 20 Die Methode toString() Die Methode toString() wird verwendet, um Objekte in eine menschlich lesbare, textuelle Repräsentation zu bringen. Die Methode System.out.println() wendet auf alle Objektparameter (außer Strings) vor dem Ausdrucken die Methode toString() an. Der Code Yen betrag = new Yen(200000); System.out.println("Betrag: " + betrag); ist gleichwertig zu: Yen betrag = new Yen(200000); System.out.println( "Betrag: " + betrag.toString()); Oliver Haase Hochschule Konstanz 21 Die Methode toString() Die Standardimplementierung der Klasse Object führt für zu einem Ausdruck der Form: Konsole Betrag: Yen@18d107f Deshalb macht es Sinn, toString() geeignet zu überschreiben: public class Yen extends Waehrung { … private double wert; … public String toString() { return wert + " Yen"; } } Konsole Betrag: 200000.0 Yen Oliver Haase Hochschule Konstanz 22 Object für Kollektionen Die Superklasse Object kann verwendet werden, um Objekte beliebigen Typs in Kollektionen (Mengen, Multimengen, Listen) zu verwalten: public class CircularArray { private final static int SIZE = 100; private int index; private Object[] elements; public CircularArray() { elements = new Object[SIZE]; index = 0; } Oliver Haase Hochschule Konstanz 23 Object für Kollektionen } public void add(Object element) { elements[index] = element; index = (index + 1) % SIZE ; } public Object pop() { index = ( index > 0 ) ? index-1 : SIZE -1; return elements[index]; } Oliver Haase Hochschule Konstanz 24 Object für Kollektionen Verwendung für Student –Objekte: CircularArray array = new CircularArray(); Student studi1 = new Student(); studi1.setName("Karla Karlson"); studi1.setNummer(12345); Student is-a Object array.add(studi1); Student studi2 = (Student) array.pop(); Typwandlung nötig, da popMethode Ergebnis vom Typ Object liefert! Oliver Haase Hochschule Konstanz 25 Schnittstellen Aufgabe: Hotelkette besitzt neben Barvermögen (Objekte vom Typ Waehrung) auch andere Arten von Vermögen: Grundstücke Aktien Firmenbeteiligungen … Wir wollen sicher stellen, dass jeder Wertgegenstand seinen monetären Gegenwert nennen kann. Lösungsmöglichkeit: Definition einer abstrakten Superklasse mit einer abstrakten Methode, die alle Subklassen implementieren müssen. Oliver Haase Hochschule Konstanz 26 Schnittstellen alternative Lösung: Java-Schnittstelle (Interface): public interface Wertgegenstand { public Waehrung wert(); } Schnittstellen sind Sammlungen von Instanzmethoden. Wie eine äußere Klasse wird auch eine Schnittstelle in einer gleichnamigen .java-Datei gespeichert Wertgegenstand.java. Zu Schnittstellen existieren keine Kindklassen, die die Schnittstellen beerben (es gibt nichts zu erben!). Stattdessen kann eine Klasse eine Schnittstellen implementieren. Dazu muss sie alle in der Schnittstelle deklarierten Methoden definieren. Oliver Haase Hochschule Konstanz 27 Schnittstellen Beispiel: public class Goldbarren implements Wertgegenstand { public static double grammPreisInDollar = 60; private double gewicht; public Goldbarren(double gewicht) { this.gewicht = gewicht; } public Waehrung wert() { return new USDollar(gewicht * grammPreisInDollar); } } Beachte: Goldbarren muss die Methode wert() definieren! Oliver Haase Hochschule Konstanz 28 Schnittstellen in UML interface Wertgegenstand wert: Waehrung Goldbarren Oliver Haase Hochschule Konstanz 29 Schnittstellen Anwendung: public static Waehrung gesamtwert( Wertgegenstand[] objekte) { double summe = 0.0; for ( int i = 0; i < objekte.length; i++ ) { summe += objekte[i].wert().dollarBetrag(); } return new USDollar(summe); } Schnittstelle Wertgegenstand wird verwendet wie eine Klasse, siehe "Wertgegenstand[]". Das Feld objekte enthält Objekte, die die Schnittstelle Wertgegenstand implementieren. Damit ist sichergestellt, dass diese alle die Methode wert() definieren. Oliver Haase Hochschule Konstanz 30 Schnittstellen vs. Klassen Eine Klasse kann nur eine Superklasse haben Einfachvererbung, keine Mehrfachvererbung. Eine Klasse kann beliebig viele Schnittstellen implementieren. Eine Schnittstelle kann keine Methodendefinitionen enthalten. Eine abstrakte Klasse kann abstrakte sowie konkrete Methoden enthalten. Eine Schnittstelle kann außer Konstanten keine Variablen enthalten. Eine Schnittstelle kann keine Klassenmethoden deklarieren. Oliver Haase Hochschule Konstanz 31 Time for a hot cup of java! Oliver Haase Hochschule Konstanz 32