HOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 400 – 00 – TH – 03 ----------------------------------------------------------------------------------- Programmieren in Java Kapitel 4 4. Nähere Betrachtung von Klassen und Interfaces 4.1. Definition von Klassen 4.2. Datenkomponenten 4.3. Memberfunktionen 4.4. Objekterzeugung 4.5. Konstruktoren und Initialisierungsblöcke 4.6. Vererbung 4.7. Interfaces 4.8. Eingebettete Klassen und Interfaces 4.9. Generische Klassen, Interfaces und Funktionen FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 411 – 00 – TH – 03 ----------------------------------------------------------------------------------- Definition von Klassen in Java (1) • Vorbemerkungen ◇ Klassen sind die grundlegenden Programmiereinheiten in Java. Jedes Java-Programm besteht aus einer oder mehreren Klassen. Es existiert kein Code ausserhalb einer Klasse. ◇ I.a. ist jede Klasse in einer eigenen Quelldatei definiert (Ausnahme : eingebettete Klassen). Diese Klassen-Quelldateien bilden die Übersetzungseinheiten (Übersetzungs-Module). Pro Klasse (auch für jede eingebettete Klasse) wird vom Compiler eine eigene Byte-Code-Datei erzeugt. ◇ Klassen definieren den Aufbau und die Funktionalität (das Verhalten) von Objekten. Objekte sind Instanzen von Klassen. Sie bestehen aus Datenkomponenten (Felder, fields), die ihren Zustand beschreiben und besitzen Funktionen (Memberfunktionen, Methoden, methods), die mit diesen Datenkomponenten arbeiten und ihr Verhalten festlegen. Die Datenkomponenten und die (Member-)Funktionen werden in einer Klassendefinition festgelegt und als Klassenkomponenten bezeichnet. ◇ Neben den instanzspezifischen Datenkomponenten (Instanz-Variable) und Memberfunktionen (Instanz-Methoden) kann eine Klasse auch klassenspezifische Datenkomponenten (Klassen-Variable) und Memberfunktionen (KlassenMethoden) besitzen. Derartige statische Klassenkomponenten beschreiben den Zustand und das Verhalten der Klasse. ◇ Instanz-Methoden wird als impliziter Parameter die this-Referenz übergeben. Diese referiert das aktuelle Objekt, für das die Methode aufgerufen wird. Klassen-Methoden besitzen diesen impliziten Parameter nicht. • Klassenkomponenten ◇ Klassenkomponenten (class members) können sein : ▻ Datenkomponenten (Membervariable, Felder, fields) ▻ Funktionskomponenten (Memberfunktionen, Methoden, methods) ▻ Eingebettete Klassen und Interfaces (nested classes and interfaces) ◇ Zusätzlich kann eine Klassendefinition enthalten : ▻ Konstruktoren (Sie werden in Java nicht zu den Memberfunktionen gerechnet) ▻ Initialisierungsblöcke (Code zur Initialisierung von Datenkomponenten) • Syntax der Klassendefinition : class Klassen-Name KlassenModifizierer Typ-ParamDeklaration { KlassenAbleitung Komponentendefinition Konstruktordefinition Initialisierungsblock ◇ Einfachste Klassendefinition : class Simple { ; } ; InterfaceImplementierung } FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 412 – 00 – TH – 04 ------------------------------------------------------------------------------------ Definition von Klassen in Java (2) • Anmerkung zur Typ-Param-Deklaration Deklaration von formalen Typ-Parametern (Typ-Variable). Falls vorhanden, wird dadurch eine generische Klasse definiert. • Klassen-Modifizierer ◇ Sie legen bestimmte Eigenschaften der Klasse fest ◇ Folgende Modifizierer sind möglich : ▻ public Die Klasse ist öffentlich zugänglich – sie kann überall verwendet werden. Ohne diesen Modifizierer ist sie nur innerhalb des Packages, in dem sie enthalten ist, verwendbar. ▻ abstract Die Klasse ist unvollständig definiert oder sie wird als unvollständig definiert betrachtet (abstrakte Klasse). Damit ist sie nicht instanzierbar. I.a. besitzt sie eine oder mehrere abstrakte Methoden. Eine abstrakte Methode besitzt keine vollständige Definition. Häufig ist sie lediglich deklariert. Ihre – vollständige – Definition bleibt einer abgeleiteten Klasse überlassen. ▻ final Von der Klasse können keine weiteren Klassen abgeleitet werden. Damit können ihre Methoden niemals überschrieben werden. ▻ strictfp Alle innerhalb der Klasse (und allen eingebetteten Klassen) auftretenden float- oder double-Ausdrücke sind FP-strict. D.h. sie werden so ausgewertet, dass auch alle Zwischenergebnisse mit der in IEEE 754 festgelegten einfachen (float) bzw doppelten (double) Genauigkeit für Gleitpunktwerte ermittelt werden. Dadurch wird sichergestellt, dass ein arithmetischer Ausdruck unabhängig von der jeweiligen JVM immer den exakt gleichen Wert ergibt. Ohne diesen Modifizierer (der auch auf der Methoden-Ebene eingesetzt werden kann), darf die JVM Zwischenergebnisse mit erweiterter Genauigkeit (größerer Exponentenbereich) bilden. Dies kann dazu führen, dass die Auswertung desselben arithmetischen Ausdrucks auf unterschiedlichen JVMs leicht unterschiedliche Ergebnisse liefert. ◇ Eine Klassendefinition kann mehrere Klassen-Modifizierer besitzen. Allerdings ist die gleichzeitige Verwendung von abstract und final nicht möglich. ◇ Anmerkung : Der Modifizierer abstract sollte nur für Klassen eingesetzt werden, von denen auch tatsächlich Klassen zur Vervollständigung der Implementierung abgeleitet werden sollen. Er ist nicht dafür vorgesehen, die Instanzierung von Klassen aus einem anderen Grund als der unvollständigen Definition zu verhindern. Ein solcher anderer Grund kann beispielsweise sein, dass die Klasse nur statische Klassenkomponenten enthält und damit ihre Instanzierung nicht sehr sinnvoll ist. In einem derartigen Fall besteht die saubere Lösung zur Verhinderung der Instanziierung in der Definition eines privaten Default-Konstruktors und keiner weiteren Konstruktoren. Durch Verwendung des Modifizierers final kann darüber hinaus verhindert werden, dass von der Klasse andere Klassen abgeleitete werden. Beispiel : Bibliotheks-Klasse Math Diese ist eine reine Utility-Klasse, die nur statische Datenkomponenten und statische Methoden zur Verfügung stellt. public final class Math { private Math() { } // privater Default-Konstruktor // statische Klassenkomponenten } • Kontrakt einer Klasse (contract of the class) Die Gesamtheit der von außen zugänglichen Methoden und Datenkomponenten einer Klasse, zusammen mit der Beschreibung ihres Verhaltens, wird häufig als Kontrakt der Klasse bezeichnet. FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 413 – 00 – TH – 02 ----------------------------------------------------------------------------------- Definition von Klassen in Java (3) • Unterschiede zur Klassendefinition in C++ ◇ Eine Klassendefinition in Java kann Klassen-Modifizierer enthalten. ◇ Eine Klassendefinition in Java wird nicht mit einem Semicolon (';') abgeschlossen. ◇ Andere Syntax zur Kennzeichnung einer Vererbung (s. später). ◇ Memberfunktionen müssen in Java innerhalb der Klassendefinition definiert werden. Sie werden dadurch nicht zu inline-Funktionen (inline-Funktionen gibt es nicht in Java !) ◇ Für Datenkomponenten können in Java Initialisierungswerte festgelegt werden. Zusätzlich sind Initialisierungsblöcke möglich (s. später). ◇ In Java muß ein eventueller Zugriffs-Spezifizierer für jede Klassenkomponente gesondert angegeben werden. (Ausnahme : Zusammenfassung mehrerer Datenkomponenten des gleichen Typs in einer Vereinbarung) • Vergleich einer Klassendefinition in C++ und Java // C++-Header-Datei Example.h // Definition der Klasse Example class Example { public : Example(int wx); ~Example(); void setName(char* wname); void setX(int wx); const char* getName(); int getX(); private : char* name; int x; }; // Example.java public class Example { private String name; private int x; public Example(int wx) { x = wx; name = null; } public void setName(String wname) { name = wname; } // C++-Quell-Datei Example.cpp // Implementierung der Klasse Example #include "Example.h" #include <cstdlib> public void setX (int wx) { x = wx; } // fuer NULL Example::Example(int wx) { x = wx; name = NULL; } public String getName() { return name; } Example::~Example() { /* ... */ } public int getX() { return x; } void Example::setName(char* wname) { /* ... */ } void Example::setX(int wx) { x = wx; } const char* Example::getName() { return name; } int Example::getX() { return x; } } FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 421 – 00 – TH – 04 ------------------------------------------------------------------------------------ Datenkomponenten in Java-Klassen (1) • Definition von Datenkomponenten ◇ Datenkomponenten von Klassen (Membervariable) werden in Java auch als Felder (fields) bezeichnet. ◇ Die Definition von Datenkomponenten des gleichen Typs kann in einer einzigen Vereinbarung zusammengefasst werden. ◇ Für die – gegebenenfalls in einer Vereinbarung zusammengefassten – Datenkomponenten, die nicht die Zugriffsberechtigung "package" besitzen sollen, ist gesondert eine andere Zugriffsberechtigung anzugeben. Wie in C++ stehen hierfür die Zugriffs-Spezifizierer (in Java auch als Zugriffs-Modifizierer bezeichnet) public, protected und private zur Verfügung. ◇ Zusätzlich können in jeder Datenkomponenten-Vereinbarung weitere Feld-Modifizierer (field modifier) angegeben werden. ◇ Syntax : Typangabe Feld-Modifizierer Komponentenname = Initialisierer ; , • Initialisierung von Datenkomponenten ◇ Zu jeder Datenkomponente kann bei ihrer Definition ein Initialisierungswert festgelegt werden. ◇ Ohne eine derartige Festlegung wird eine Datenkomponente mit einem vorgegebenen Default-Wert initialisiert (s. "Datentypen") ◇ Der einen Initialisierungswert festlegende Initialisierer (variable initializer) darf sein ▻ ein beliebiger Ausdruck, der einen Wert liefert, der zuweisungskompatibel zum Typ der Datenkomponente ist und keine geprüften Exceptions erzeugen kann. Weiterhin darf ein Ausdruck zur Initialisierung einer Klassen-Variablen keine Instanz-Variable enthalten ▻ ein Array-Initialisierer (array initializer), wenn die Datenkomponente ein Array ist (s. "Arrays"). ◇ Beispiele für gültige Initialisierer : public class InitDemo { double zero = 0.0; // Konstante double sum = 4.5 + 3.7; // konstanter Ausdruck double zeroCpy = zero; // andere Datenkomponente double wurz2 = Math.sqrt(2); // Methodenaufruf double some = sum + 2*Math.sqrt(wurz2); // gemischter Ausdruck int[] koeff = { 2, 4, 1, 3, 6, 7 }; // Array-Initialisierer double[] tfeld = { Math.sin(1), Math.cos(1), Math.tan(1) }; // Array-Init. } ◇ Zur Realisierung komplexerer Initialisierungsaufgaben können Initialisierungsblöcke definiert werden (s. später). ◇ Ein Initialisierer für eine Instanz-Variable wird bei jeder Instanzierung (Objekterzeugung) der Klasse ausgewertet. Ein Initialisierer für eine Klassen-Variable wird nur einmal beim Laden der Klasse in die JVM ausgewertet ◇ Eine Initialisierung von Instanz-Variablen kann auch in einem Konstruktor erfolgen. Gegebenenfalls werden "vorherige" Initialisierungen dadurch überschrieben. FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 422 – 00 – TH – 05 ------------------------------------------------------------------------------------ Datenkomponenten in Java-Klassen (2) • Feld-Modifizierer ◇ Sie legen bestimmte Eigenschaften einer Datenkomponente fest. ◇ Folgende Modifizierer sind möglich : ▻ die Zugriffs-Spezifizierer (Zugriffs-Modifizierer) public, protected und private. ▻ static Die Datenkomponente ist eine Klassen-Variable (statische Datenkomponente). Klassen-Variable müssen von ausserhalb (vorausgesetzt es besteht eine entsprechende Zugriffsberechtigung) mit ihrem vollqualifizierten Namen (Ergänzung um Klassenname und . –Operator) angesprochen werden. Sie können allerdings auch als Komponente eines Objekts ihrer Klasse angesprochen werden. Dies sollte jedoch aus semantischen Gründen zur Vermeidung von Missverständnissen unterbleiben. ▻ final Die Datenkomponente kann nach ihrer Initialisierung nicht mehr verändert werden. Sowohl Instanz- als auch Klassenvariable können diese Eigenschaft besitzen. Typischerweise wird eine als final vereinbarte Datenkomponente einen Initialisierer besitzen. Fehlt ein Initialisierer (blank final), so muß eine final-Klassen-Variable durch einen Initialisierungsblock, eine final-Instanz-Variable durch einen Initialisierungsblock oder durch einen Konstruktor initialisiert werden. Das Fehlen einer Initialisierung sowie der Versuch einer späteren Wertzuweisung führt zu einem Compiler-Fehler ▻ transient Die Datenkomponente gehört nicht zum persistenten Zustand eines Objekts. Beim persistenten Abspeichern des Objekts wird eine transient-Datenkomponente nicht mit abgespeichert. ▻ volatile Zu der Datenkomponente kann durch mehrere unsynchronisierte Threads zugegriffen werden. Der Compiler muß durch Vermeidung von Zugriffs-Optimierungen sicherstellen, dass die Datenkomponente sich immer in einem konsistenten Zustand befindet. Z.B. muß ein Thread jeden Zugriff zu einer von ihm gehaltenen lokalen Kopie einer derartigen Variablen immer mit der Original-Variablen abgleichen. ◇ Mehrere Feld-Modifizierer dürfen – soweit sinnvoll – miteinander kombiniert werden. Allerdings ist nur jeweils ein Zugriffs-Spezifizierer zulässig. Ausserdem kann eine Datenkomponente nicht gleichzeitig volatile und final sein. • Konstante in Java ◇ Konstante werden in Java i.a. als final-Klassen-Variable (Datenkomponenten mit den Modifizierern static und final) definiert. Es ist üblich – aber nicht verpflichtend – , Konstanten-Namen mit Großbuchstaben zu bilden. ◇ Beispiele aus der Java-Standard-Bibliothek public final class Integer { public static final int MAX_VALUE; public static final int MIN_VALUE; ..public static final Class<Integer> TYPE; // ... } public final class System { public static final InputStream in; ..// ... } ◇ Es lassen sich mit dem Schlüsselwort final auch lokale Konstante in Funktionen definieren : final int ANZ = 10; Sofern diese nicht bei ihrer Definition initialisiert werden, kann ihnen später einmal ein Wert zugewiesen werden. Jeder Versuch einer weiteren Wertzuweisung führt zu einem Compilerfehler FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 431 – 00 – TH – 04 ------------------------------------------------------------------------------------ Memberfunktionen in Java (1) • Definition von Memberfunktionen ◇ Jede Memberfunktion (Methode) muß – sofern es sich nicht um eine abstrakte Methode handelt – innerhalb der Klassendefinition vollständig definiert (implementiert) werden. ◇ Für jede Memberfunktion, die nicht die Zugriffsberechtigung "package" besitzen soll, ist gesondert eine andere Zugriffsberechtigung anzugeben. Wie in C++ stehen hierfür die Zugriffs-Spezifizierer (in Java auch als Zugriffs-Modifizierer bezeichnet) public, protected und private zur Verfügung. ◇ Können innerhalb einer Methode geprüfte Exceptions – direkt oder indirekt – geworfen werden und werden diese nicht gefangen, so müssen sie im Funktionskopf in einer throws-Klausel deklariert werden. ◇ Syntax : Typangabe MethodenModifizierer Typ-ParamDeklaration Methoden-Name ( Methoden-Kopf { Formal-Parameterliste ) throws-Klausel Anweisung } MethodenRumpf • Anmerkung zur Typ-Param-Deklaration Deklaration von formalen Typ-Parametern (Typ-Variable). Falls vorhanden, wird eine generische Methode definiert. • Anmerkungen zur Formal-Parameterliste : ◇ Auflistung der formalen Methoden-Parameter. Syntax weitgehend analog zu C/C++ : Angabe von Typ und Name für jeden Parameter, Trennung der Parameter durch Kommata ◇ Wenn eine Methode keine Parameter besitzt, ist die Formal-Parameterliste leer (keine Angabe von void) ◇ Default-Parameterwerte gibt es in Java nicht. ◇ Parameter eines einfachen Datentyps sind Wertparameter, Parameter eines Klassen-Typs sind Referenzparameter. ◇ Parameter können als final deklariert werden sie dürfen dann innerhalb der Funktion nicht verändert werden • Variable Parameterliste (Varargs) ◇ Seit dem JDK 5.0 ist es möglich, Methoden mit einer variablen Parameterliste (Varargs)zu definieren. ▻ In der Formal-Parameterliste muss hierfür die Typangabe des letzten Parameters von drei Punkten gefolgt werden. ▻ Eine derartige Methode kann mit einer beliebigen Anzahl aktueller Parameter aufgerufen werden. Die aktuellen Parameter müssen zum Typ des letzten formalen Parameters kompatibel sein. ▻ Wenn in einem derartigen Fall aktuelle Parameter beliebigen Typs übergebbar sein sollen, muss als Typangabe Object verwendet werden : Object... ▻ Beispiel : Methode printf() der Klasse PrintStream (bzw PrintWriter) public PrintStream printf(String format, Object... args) ▻ Statt mit einer variablen Anzahl von Parametern lässt sich eine derartige Methode auch mit einem Array, in dem die Parameter zusammengefasst sind, aufrufen ◇ Innerhalb der Methode werden die variablen aktuellen Parameter in einem Array des deklarierten Parametertyps zur Verfügung gestellt. Beispielsweise kann die Anzahl der aktuellen Parameter mittels der öffentlichen Array-Datenkomponente length ermittelt werden. FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 432 – 00 – TH – 05 ------------------------------------------------------------------------------------ Memberfunktionen in Java (2) • Demoprogramm zur variablen Parameterliste // VarargsDemo.java // Demoprogramm zur variablen Parameter-Liste import java.util.*; public class VarargsDemo { int varargsfunc(Object... pars) { System.out.printf("Aufruf mit %d Parametern\n", pars.length); return pars.length; } public static void main(String[] args) { VarargsDemo vad = new VarargsDemo(); vad.varargsfunc(3, 4, 6.5, "Hallo"); vad.varargsfunc(); vad.varargsfunc("Datum : ", new Date()); Number[] na = new Number[] { 3, 5.6, 7}; vad.varargsfunc(na); } } Aufruf Aufruf Aufruf Aufruf mit mit mit mit 4 0 2 3 Parametern Parametern Parametern Parametern • Signatur einer Memberfunktion ◇ Der Methoden-Name und die Formal-Parameterliste (Anzahl und Typ der Parameter) bilden die Signatur einer Memberfunktion. ◇ In einer Klasse dürfen nicht zwei Methoden mit der gleichen Signatur definiert werden, auch wenn der Rückgabetyp und / oder die throws-Klausel unterschiedlich ist ( Compiler-Fehler). ◇ Zulässig sind dagegen überladene Methoden : Zwei oder mehr Methoden gleichen Namens, aber unterschiedlicher Parameterliste ( unterschiedliche Signatur). Beispiel : public class Point { private double x = 0.0; private double y = 0.0; public Point move(double dx, double dy) { x += dx; y += dy; return this; } /* public void move(double px, double py) { x = px; y = py;} // unzulaessig */ public void move(Point p) { x = p.x; y = p.y;} // ... } // zulaessig : überlädt Point move(double, double) FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 433 – 00 – TH – 03 ----------------------------------------------------------------------------------- Memberfunktionen in Java (3) • Methoden-Modifizierer ◇ Sie legen bestimmte Eigenschaften einer Memberfunktion fest. ◇ Folgende Modifizierer sind möglich : ▻ die Zugriffs-Spezifizierer (Zugriffs-Modifizierer) public, protected und private. ▻ abstract Es handelt sich um eine abstrakte Methode. Für eine derartige Methode wird nur die Signatur (Name und Parameter), der Rückgabetyp und gegebenenfalls die throws-Klausel nicht jedoch die Implementierung festgelegt. (Methode wird nur deklariert) Der Methoden-Rumpf wird durch ein ; ersetzt. Abstrakte Methoden sind nur in abstrakten Klassen zulässig. Jede von der abstrakten Klasse abgeleitete Klasse, die selbst nicht wieder abstrakt ist, muß eine Definition der Methode bereitstellen. ▻ static Die Methode ist eine Klassen-Methode (statische Memberfunktion). Da eine derartige Methode nicht für ein konkretes Objekt aufgerufen werden kann und ihr keine implizite thisReferenz übergeben wird, kann sie direkt nur zu anderen statischen Klassenkomponenten zugreifen. Ein Zugriff zu nicht-statischen Klassenkomponenten der eigenen Klasse ist nur über ein konkretes Objekt möglich, das entweder als Parameter übergeben oder innerhalb der Methode erzeugt werden muß. ▻ final Die Methode kann in einer abgeleiteten Klasse weder überschrieben noch überdeckt werden. Allerdings ist es zulässig, die Methode in einer abgeleiteten Klasse zu überladen, d.h. eine Methode gleichen Namens aber mit anderer Parameterliste zu definieren. Eine private-Methode sowie alle Methoden einer final deklarierten Klasse sind implizit final. Eine explizite Angabe des Modifizierers final für derartige Methoden ist nicht notwendig, aber zulässig. Eine final-Methode kann vom Maschinencode-Generator der JVM als Makro ("inline") expandiert werden. ▻ synchronized Die Methode implementiert einen Synchronisations-Mechanismus für den Aufruf durch konkurierende Threads. ▻ native Dieser Modifizierer erlaubt das Einbinden von Methoden, die in nativen Machinencode vorliegen. Üblicherweise werden derartige Methoden in einer anderen Programmiersprache (z.B. C, C++, FORTRAN, Assembler) formuliert und dann in Machinencode übersetzt. Eine native-Methode wird in der Klassendefinition nur deklariert, d.h. der Methoden-Rumpf wird durch ein ; ersetzt. Die den Methoden-Code enthaltene Datei muss zur Laufzeit (üblicherweise bei der Klassen-Initialisierung) in die JVM geladen werden. Hierfür stehen in den Klassen Runtime und System die statischen Methoden load(...) und loadLibrary(...) zur Verfügung. Das Java-SDK definiert ein Interface zum Formulieren und Einbinden von native-Methoden ( JNI, Java Native Interface). ▻ strictfp Eine derartige Methode arbeitet FP-strict. D.h. alle innerhalb der Methode auftretenden float- oder doubleAusdrücke werden so ausgewertet, dass auch alle Zwischenergebnisse mit der in IEEE 754 festgelegten einfachen (float) bzw doppelten (double) Genauigkeit für Gleitpunktwerte ermittelt werden. (s. Klassen-Modifizierer !) Alle innerhalb einer strictfp deklarierten Klasse vereinbarten Methoden sind implizit strictfp. ◇ Mehrere Methoden-Modifizierer dürfen – soweit sinnvoll – miteinander kombiniert werden. Allerdings ist nur jeweils ein Zugriffs-Spezifizierer zulässig. Ausserdem darf eine abstract vereinbarte Methode nicht gleichzeitig private, static, final oder native sein. FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 441 – 00 – TH – 03 ------------------------------------------------------------------------------------ Objekterzeugung in Java (1) • Grundsätzliches ◇ Die Definition einer Objekt-Variablen erzeugt noch kein Objekt. Sie belegt lediglich Speicherplatz zur Aufnahme einer Objekt-Referenz. ◇ Objekte können in Java nur dynamisch auf dem Heap angelegt werden. ◇ Üblicherweise werden Objekte mittels eines new-Ausdrucks erzeugt. Ein new-Ausdruck wird aus dem new-Operator, dem Namen der zu instanzierenden Klasse und einer in runde Klammern eingeschlossenen Liste von Initialiserungswerten ( Parameter für den Konstruktor) gebildet. Die Liste der Initialisierungswerte kann auch leer sein, die runden Klammern müssen aber in jedem Fall angegeben werden. ◇ Ein new-Ausdruck ▻ bewirkt die Allokation von Speicher durch das Laufzeitsystem (JVM) für die Datenkomponenten des Objekts, ▻ initialisiert die Datenkomponenten ▻ und liefert nach Abschluß der Initialisierung eine Referenz auf das neu erzeugte Objekt zurück ◇ Die zurückgelieferte Referenz auf das erzeugte Objekt kann dann einer Objekt-Variablen passenden Typs zugewiesen werden. Sie kann gegebenenfalls auch als Referenz auf ein anonymes Objekt weiterverwendet werden, z.B. zum Aufruf von Memberfunktionen. ◇ Beispiel : Point pkt1; pkt1 = new Point(); pkt1.move(3.5, 4.7); Point pkt2 = (new Point()).move(-2.3, -1.7); ◇ Falls das Laufzeitsystem keinen ausreichenden Speicher allozieren kann, startet es den Garbage Collector, der versucht Speicher freizumachen. Falls auch danach nicht genügend freier Speicher für eine Allokation zur Verfügung steht, wird die Exception OutOfMemoryError geworfen. ◇ In Java sind noch weitere Mechanismen zur Objekterzeugung implementiert, u.a. ▻ Spezielle Generiermethoden (factory methods) im Rahmen des Reflection-APIs . Sie ermöglichen die Instanzierung einer Klasse unter dynamischer Bereitstellung des Klassennamens als String. Dies erlaubt z.B. die Instanzierung einer Klasse, deren Name durch ein Programm erst eingelesen wird. ▻ Die clone()-Methode ▻ Implizite Objekterzeugung aus String-Konkatenationsausdrücken und Array-Initialisierern • Anmerkung zur Freigabe von Objekten ◇ Eine explizite Freigabe von Objekten ist nicht möglich. Die Freigabe nicht mehr referierter Objekte erfolgt vielmehr durch einen Garbage Collector, der als niederpriorer Hintergrund-Thread in der JVM läuft. ◇ Damit der Garbage Collector sinnvoll arbeiten kann, sollte die Referenz zu nicht mehr benötigten Objekten gegebenenfalls explizit aufgehoben werden, z.B. durch Zuweisung von null an die referierende Objekt-Variable. • Initialisierung von Objekten ◇ Die Datenkomponenten eines neu erzeugten Objekts werden durch das Laufzeitsystem (die JVM) initialisiert. ◇ Hierfür setzt die JVM die folgenden Mechanismen – falls vorhanden – in der angegebenen Reihenfolge ein : ▻ Initialisierung mit dem ihrem jeweiligen Typ entsprechenden Default-Wert. ▻ Initialisierung mittels den bei der Datenkomponenten-Definition angegebenen Initialisierern und durch Initialisierungsblöcke. Die Auswertung der Initialisierer und die Abarbeitung der Initialisierungsblöcke erfolgt in der Reihenfolge ihres Auftritts in der Klassendefinition. ▻ Initialisierung durch einen Konstruktor FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 442 – 00 – TH – 03 ----------------------------------------------------------------------------------- Objekterzeugung in Java (2) • Dynamische Bereitstellung des Namens der zu instanziierenden Klasse ◇ Die im Rahmen des Reflection-API bereitgestellten Bibliotheksklassen ermöglichen die Instanziierung von Klassen, deren Name erst dynamisch zu Laufzeit festgelegt wird. ◇ Die einfachste Möglichkeit hierfür erlaubt die Instanziierung von Klassen, die über einen Konstruktor ohne Parameter (no-arg-Konstruktor) verfügen. Sie wird mittels Memberfunktionen der Klasse Class<T> (Package java.lang) realisiert. ◇ Die Klasse Constructor<T> (Package java.lang.reflect) ermöglicht darüberhinaus die Instanziierung von Klassen unter Verwendung parameterbehafteter Konstruktoren. Hierauf wird an dieser Stelle nicht eingegangen. ◇ Die Instanziierung einer Klasse, deren Name lediglich als String vorliegt, erfolgt in zwei Schritten : ▻ Ermittlung des Objekts der Klasse Class<T>, das die Klasse mit dem angegebenen Namen repräsentiert Der Typ-Parameter T steht für die repräsentierte Klasse (allgemein : den repräsentierten Typ) ▻ Erzeugung einer Instanz der durch das Class-Objekt repräsentierten Klasse. ◇ Ermittlung des eine Klasse repräsentierenden Class-Objekts : Mittels der statischen Memberfunktion der Klasse Class<T> : public static Class<?> forName(String name) throws ClassNotFoundException Diese Methode gibt eine Referenz auf das Objekt der Klasse Class<T> zurück, das die durch den vollqualifizierten Namen name bezeichnete Klasse repräsentiert (Objekt des Typs Class<name>). Wenn die zugehörige Klasse nicht gefunden wird, wird eine ClassNotFoundException geworfen. ◇ Instanziierung einer Klasse unter Verwendung ihres repräsentierenden Class-Objekts. Hierfür dient die nicht-statische Memberfunktion der Klasse Class<T> : public T newInstance() throws InstantationException, IllegalAccessException Diese Methode erzeugt ein neues Objekt der repräsentierten Klasse T, für das der no-arg-Konstruktor aufgerufen wird und gibt eine Referenz auf dieses Objekt zurück. Wenn die Klasse nicht instanziiert werden kann (z.B. abstrakte Klasse, Interface, Array, einfacher Datentyp, kein no-arg-Konstruktor vorhanden), wird eine InstantationException geworfen. Wenn die Klasse oder ihr no-arg-Konstruktor nicht zugreifbar ist, wird eine IllegalAccessException geworfen. ◇ Damit ein mittels newInstance() erzeugtes Objekt sinnvoll verwendet werden kann, muß es i.a. in seinen eigenen bzw in einen zuweisungskompatiblen Typ gecastet werden. ◇ Einfaches Demonstrationsbeispiel : public class ObjErzeuger { public static void main(String[] args) { try { if (args.length==0) throw new RuntimeException("Programmparameter (Klassenname) fehlt"); Class<?> cl = Class.forName(args[0]); Object obj = cl.newInstance(); System.out.println("Erzeugtes Objekt Typ : " + obj.getClass().getName()); } catch (Exception e) { System.out.println(e); } } } FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 451 – 00 – TH – 03 ----------------------------------------------------------------------------------- Konstruktoren in Java (1) • Allgemeines zu Konstruktoren ◇ Konstruktoren sind im Prinzip spezielle Memberfunktioen. Allerdings besitzen sie keinen Rückgabetyp und haben einen festgelegten Namen. In Java werden sie aber nicht zu den Memberfunktionen gerechnet. ◇ Konstruktoren sind nicht vererbbar und können damit weder überschrieben noch überdeckt werden. ◇ Konstruktoren werden implizit bei der Erzeugung eines Objekts zur Initialisierung seiner Datenkomponenten ausgeführt. Bei jeder Objekterzeugung wird genau ein Konstruktor durch das Laufzeitsystem aufgerufen. Sein Aufruf erfolgt nach einer Initialisierung der Datenkomponenten mit Defaultwerten und den Werten eventueller Initialisierer sowie nach der Abarbeitung eventueller Initialisierungsblöcke. Er kann gegebenenfalls weitere Konstruktoren explizit aufrufen. ◇ Konstruktoren können Parameter zur Festlegung der Initialisierungswerte für die Datenkomponenten besitzen. Eine Klasse kann mehrere Konstruktoren mit jeweils unterschiedlicher Parameterliste besitzen Konstruktoren können überladen werden. ◇ Ein Konstruktor ohne Parameter wird in Java als no-arg constructor bezeichnet. • Definition von Konstruktoren ◇ Die Definition von Konstruktoren entspricht im wesentlichen der Definition von Memberfunktionen. Der Name eines Konstruktors muß gleich dem Klassennamen sein. ◇ Syntax : Klassen-Name KonstruktorModifizierer ( Typ-ParamDeklaration Formal-Parameterliste ) throws-Klausel { Anweisung } KonstruktorKopf KonstruktorRumpf ◇ Als Konstruktor-Modifizierer sind nur die Zugriffs-Spezifizierer public, protected und private zulässig. ◇ Konstruktoren können auch Exceptions werfen. Geprüfte Exceptions sind in einer throws-Klausel zu deklarieren. Wirft ein Konstruktor eine Exception, wird der den Konstruktoraufruf veranlassende new-Ausdruck durch Werfen der Exception beendet. Eine Referenz auf das neu erzeugte Objekt wird nicht zurückgegeben. ◇ Ist in einer Klassendefinition kein Konstruktor explizit festgelegt, wird vom Compiler implizit ein Konstruktor ohne Parameter (no-arg constructor) erzeugt Default-Konstruktor. Die einzige Funktionalität dieses Konstruktors besteht im Aufruf des no-arg-Konstruktors der Basisklasse. Er besitzt die gleiche Zugriffsberechtigung , die für die Klasse festgelegt ist. ◇ In Java kann ein Konstruktor andere Konstruktoren der gleichen Klasse (andere Parameterliste) aufrufen Verkettung von Konstruktoren. Zum Aufruf eines anderen Konstruktors dient die this-Referenz, der in runden Klammern die aktuelle Parameterliste nachzustellen ist. Ein derartiger expliziter Konstruktoraufruf muß die erste Anweisung im aufrufenden Konstruktor sein. ◇ Copy-Konstruktoren sind auch möglich. Allerdings sind sie in Java weniger gebräuchlich. Zur Erzeugung der Kopie eines Objekts wird i.a. die – in der Klasse Object definierte und in anderen Klassen geeignet zu überladene – clone()-Methode bevorzugt. ◇ Ein Konstruktor kann auch eine Typ-Param-Deklaration besitzen, d.h. mit formalen Typ-Parametern definiert werden (unabhängig davon, ob die Klasse generisch ist) generischer Konstruktor FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 452 – 00 – TH – 02 ----------------------------------------------------------------------------------- Konstruktoren in Java (2) • Beispiel : // CelBody.java public class CelBody { private long idNum; private String name = "<unbekannt>"; private CelBody orbits; private static long nextID = 1; public CelBody() { System.out.println(toString()); idNum = nextID++; } public CelBody(String bName, CelBody orbAround) { this(); name = bName; orbits = orbAround; System.out.println(toString()); } public CelBody(String bName) { this(bName, null); } public String toString() { StringBuffer sb = new StringBuffer("Name : "); sb.append(name).append(" ID : ").append(idNum); sb.append(" orbits around : ").append((orbits==null) ? "null" : orbits.name); sb.append(" next ID : ").append(nextID); return new String(sb); } public CelBody getOrbits() { return orbits; } // ... public static void main(String[] args) { CelBody sonne = new CelBody("Sun"); CelBody erde = new CelBody("Earth", sonne); CelBody mond = new CelBody("Moon", erde); // ... } } Name Name Name Name Name Name : : : : : : <unbekannt> ID : 0 orbits around : null next ID : 1 Sun ID : 1 orbits around : null next ID : 2 <unbekannt> ID : 0 orbits around : null next ID : 2 Earth ID : 2 orbits around : Sun next ID : 3 <unbekannt> ID : 0 orbits around : null next ID : 3 Moon ID : 3 orbits around : Earth next ID : 4 FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 453 – 00 – TH – 04 ------------------------------------------------------------------------------------ Initialisierungsblöcke in Java (1) • Allgemeines ◇ Innerhalb Klassendefinitionen können auch sogenannte Initialisierungsblöcke definiert werden. ◇ Initialisierungsblöcke sind in das Klammerpaar { und } eingeschlossene Anweisungsfolgen, die ausserhalb jeder Memberfunktion stehen. ◇ Sie stellen eine Erweiterung der (Feld-)Initialisierer dar und ermöglichen eine komplexe nicht-triviale Initialisierung der Datenkomponenten einer Klasse. ◇ Sie werden konkurierend zu der Auswertung der (Feld-)Initialisierer ausgeführt, wobei sich die Reihenfolge nach der Auftritts-Reihenfolge in der Klassendefinition richtet. ◇ Einem Initialisierungsblock können keine Parameter übergeben werden. ◇ Ein Initialisierungsblock darf keine return-Anweisung enthalten ( Compiler-Fehler). ◇ Obwohl für die Initialisierung von Datenkomponenten vorgesehen, kann ein Initialisierunsgblock prinzipiell beliebigen Code ausführen. ◇ Es gibt ▻ Objekt-Initialisierungsblöcke (instance initializer) und ▻ Klassen-Initialisierungsblöcke (statische Initialisierungsblöcke, static initializer) • Objekt-Initialisierungsblöcke (instance initializer) ◇ Objekt-Initialisierungsblöcke werden bei jeder Instanzierung der Klasse ausgeführt. Sie sollen zur Initialisierung der objektspezifischen Datenkomponenten (Instanz-Variablen) dienen. ◇ Mehrere Objekt-Initialisierungsblöcke werden zusammen mit eventuellen Initialisierern für die Instanz-Variablen in der Reihenfolge ihres Auftritts in der Klassendefinition zu einer einzigen Initialisierungsroutine zusammengefasst. Diese wirkt so, als ob sie zu Beginn jedes Konstruktors der Klasse stehen würden. ◇ Objekt-Initialisierungsblöcke können das aktuelle Objekt mittels der this-Referenz referieren. ◇ Ein Objekt-Initialisierungsblock darf nur dann eine geprüfte Exception werfen, wenn alle Konstruktoren diese Exception (oder eine ihrer Basisklassen) als werfbar deklariert haben. ◇ Objekt-Initialisierungsblöcke lassen sich sinnvoll als Ersatz von no-arg-Konstruktoren einsetzen, wenn deren Funktionalität in jedem anderen Konstruktor verwendet werden soll (expliziter Aufruf des no-arg-Konstruktors), der no-argKonstruktor für die Objekt-Erzeugung aber direkt nicht benötigt wird. ◇ Beispiel : public class CelBody2 { private long idNum; private String name ; private CelBody2 orbits; private static long nextID = 1; { // Objekt-Initialisierungsblock name = "<unbekannt>"; System.out.println(toString()); idNum = nextID++; } public CelBody2(String bName, CelBody2 orbAround) { name = bName; orbits = orbAround; System.out.println(toString()); } // ... } FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 454 – 00 – TH – 01 ----------------------------------------------------------------------------------- Initialisierungsblöcke in Java (2) • Klassen-Initialisierungsblöcke (static initializer) ◇ Klassen-Initialisierungsblöcke werden nur einmal beim Laden einer Klasse in die JVM ausgeführt. Sie sollen zur Initialisierung der klassenspezifischen (statischen) Datenkomponenten (Klassen-Variablen) dienen. ◇ Syntax : static { Anweisung } ◇ Mehrere Klassen-Initialisierungsblöcke werden zusammen mit eventuellen Initialisierern für die Klassen-Variablen in der Reihenfolge ihres Auftritts in der Klassendefinition zu einer einzigen Initialisierungsroutine zusammengefasst. Diese kann im Prinzip als eine Art "Klassen-Konstruktor" aufgefasst werden. ◇ Ein Klassen-Initialisierungsblock kann nur statische Komponenten seiner Klasse referieren. ◇ Ein Klassen-Initialisierungsblock darf keine geprüften Exceptions werfen. ◇ Beispiele für Anwendungen : ▻ Initialisierung von statischen Datenkomponenten, deren Wertermittlung komplexerer Natur ist. Häufig handelt es sich hierbei um den Aufbau von Tabellen (z.B. Codetabellen, Tabellen mathematischer Funktionswerte usw). ▻ Einlesen von in Maschinencode vorliegenden Bibliotheks-Routinen, die native-Methoden implementieren (durch Aufruf von System.load(...) bzw System.loadLibrary()). ◇ Beispiel : public class Circle { // Lookup-Tabellen fuer sin- und cosin-Werte static private final int NUM_VALS = 1000; static private double sines[] = new double[NUM_VALS]; static private double cosines[] = new double[NUM_VALS]; // Klassen-Initialisierungsblock zum Fuellen der Lookup-Tabellen static { double x, deltaX; int i; deltaX = (Math.PI/2)/(NUM_VALS-1); for (i=0, x = 0.0; i < NUM_VALS; i++, x += deltaX) { sines[i] = Math.sin(x); cosines[i] = Math.cos(x); } System.out.println("Lookup-Tabellen initialisiert !"); } // ... public static void main(String[] args) { System.out.println("Klasse Circle wird verwendet !"); } } Lookup-Tabellen initialisiert ! Klasse Circle wird verwendet ! FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 461 – 00 – TH – 03 ----------------------------------------------------------------------------------- Vererbung in Java (1) • Prinzip und Eigenschaften ◇ Eine Klasse kann durch Vererbung erweitert (extended, subclassed) werden Definition einer abgeleiteten Klasse. Die abgeleitete Klasse (subclass, extended class) erbt alle Datenkomponenten und Memberfunktionen ihrer Basisklasse (superclass) Sie kann neue Datenkomponenten und Methoden hinzufügen. Ergänzung / Änderung des Verhaltens der Klasse ◇ Die in der abgeleiteten Klasse neu definierten Komponenten können den gleichen Namen wie Komponenten der Basisklasse tragen. In einem derartigen Fall werden die gleichnamigen Komponenten der Basisklasse entweder überdeckt oder überschrieben oder überladen. ◇ Von einer als final vereinbarten Klasse können keine weiteren Klassen abgeleitet werden. ◇ In Java hat jede Klasse, die nicht explizit von einer anderen Klasse abgeleitet ist, die Klasse Object als direkte Basisklasse. ◇ Da in Java nur einfache Vererbung möglich ist, besitzt jede Klasse – außer der Klasse Object – genau eine Basisklasse. ◇ Ein Objekt einer abgeleiteten Klasse kann überall verwendet werden, wo ein Objekt der Basisklasse benötigt wird. Referenzvariable einer Basisklasse können auch auf Objekte abgeleiteter Klassen zeigen. Polymorphie. Abgeleitete Klassen sind zuweisungs-kompatibel zu ihren Basisklassen. ◇ In jedem Objekt einer abgeleiteten Klasse steht mit dem Schlüsselwort super eine Referenz auf das in ihm enthaltene Basisklassen-Teilobjekt zur Verfügung. Mittels der super-Referenz können nur Komponenten der Basisklasse angesprochen sowie Basisklassen-Konstruktoren aufgerufen werden. Sie kann nicht – wie this – allein benutzt werden, um das Basisklasen-Teilobjekt insgesamt anzusprechen. • Syntax zur Angabe der Basisklasse in der Klassendefinition (extends-Deklaration) Klassen-Ableitung : extends Basisklassenname • Konstruktoren von abgeleiteten Klassen ◇ Ein Konstruktor kann nur die Datenkomponenten der eigenen Klasse initialisieren. Die Initialisierung der geerbten Datenkomponenten der Basisklasse muß durch einen Konstruktor der Basisklasse erfolgen. ◇ Jeder Aufruf eines Konstruktors muß daher als erstes den Aufruf eines Konstruktors der Basisklasse bewirken. Dies kann erfolgen durch : ▻ den expliziten Aufruf eines Basisklassen-Konstruktors mittels super(...). Dieser Aufruf muß die erste Anweisung im Konstruktor der abgeleiteten Klasse sein ▻ den indirekten Aufruf eines Basisklassen-Konstruktors über den Aufruf eines anderen Konstruktors der abgeleiteten (also der eigenen) Klasse mittels this(...). Der Aufruf this(...) muß die erste Anweisung im Konstruktor der abgeleiteten Klasse sein. ▻ den impliziten Aufruf des no-arg-Konstruktors der Basisklasse, wenn die erste Anweisung im Konstruktor der abgeleiteten Klasse weder super(...) noch this(...) ist. ◇ Nach der Abarbeitung des Basisklassen-Konstruktors werden zunächst die Initialisierer der Instanz-Variablen und die Objekt-Initialisierungsblöcke der eigenen (abgeleiteten) Klasse ausgewertet. Erst danach werden die – übrigen – Anweisungen im Rumpf des Konstruktors ausgeführt. ◇ Anmerkung : In den Parameter-Ausdrücken eines expliziten Konstruktorsaufrufs (mittels super(...) oder this(...)) darf keine Komponente des aktuellen Objekts referiert werden. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 462 – 00 – TH – 02 ----------------------------------------------------------------------------------- Vererbung in Java (2) • Überschreiben von Methoden (Overriding) ◇ Eine in der abgeleiteten Klasse definierte Methode mit gleichem Namen und gleicher Parameterliste ( == gleicher Signatur) sowie gleichem Rückgabetyp wie in der Basisklasse, überschreibt die Methode der Basisklasse. Hierdurch wird die Basisklassen-Implementierung der Methode für die abgeleitete Klasse durch eine neue Implementierung ersetzt. ◇ Handelt es sich bei der überschriebenen Funktion um eine abstrakte Methode (Deklaration als abstract), so wird sie durch die überschreibende Funktion erst implementiert. ◇ Ausnahme von der Gleichheit der Parameterlisten : Parameter dürfen sich in der final-Eigenschaft unterscheiden. ◇ Die Definition einer Methode in der abgeleiteten Klasse, die sich nur im Rückgabetyp von einer Methode gleichen Namens in der Basisklasse unterscheidet, ist ein Fehler. ◇ Es können nur Methoden überschrieben werden, die weder als static noch als private noch als final vereinbart sind. In Java ist jede nichtstatische Methode der Basisklasse, die nicht private oder final ist, grundsätzlich virtuell. ◇ Beim Aufruf einer derartigen Methode über eine Basisklassen-Referenz wird die in der tatsächlichen Klasse des aktuell referierten Objekts definierte Methode ausgeführt ( late binding, Polymorphie !) ◇ Eine in der abgeleiteten Klasse definierte überschreibende Funktion darf die in der Basisklasse festgelegte Zugriffsberechtigung nicht einschränken. Eine Erweiterung der Zugriffsberechtigung ist zulässig. Beispiel : Wenn in der Basisklasse für eine Methode protected festgelegt ist, darf eine überschreibende Methode in der abgeleiteten Klasse protected oder public sein, jedoch nicht private oder package. ◇ Die übrigen Methoden-Modifizierer dürfen wie folgt verändert werden : ▻ synchronized, native und strictfp können beliebig verändert (entfernt oder hinzugefügt) werden ▻ abstract kann entfernt oder hinzugefügt werden. ▻ eine überschreibende Methode kann final gemacht werden. ▻ eine überschreibende Methode kann nicht static sein. ◇ Bezüglich der Exception-Deklaration (throws-Klausel) gelten folgende Regeln : ▻ Die Exception-Deklarationen von überschreibender und überschriebener Methode können unterschiedlich sein. ▻ Eine überschreibende Methode darf in ihrer Exception-Deklaration nur Exception-Typen enthalten, die zu einem in der Exception-Deklaration der überschriebenen Methode festgelegten Typen polymorph kompatibel (d.h. von der gleichen Klasse oder einer von ihr abgeleiteten Klasse) sind. ▻ Eine überschreibende Methode muss nicht alle von der überschriebenen Methode deklarierten Exception-Typen ebenfalls deklarieren. Auch wenn die überschriebene Methode eine Exception-Deklaration hat, darf diese bei der überschreibenden Methode fehlen. ▻ Eine überschreibende Methode darf nichtgeprüfte Exceptions werfen, die in der überschriebenen Methode nicht auftreten können. Diese dürfen aber nicht in die Exception-Deklaration aufgenommen werden. ◇ Zu der überschriebenen Methode kann – innerhalb der überschreibenden Methode oder in einer anderen InstanzMethode der gleichen Klasse – über die Basisklassen-Teilobjekt-Referenz super zugegriffen werden. super.method(...) ruft immer die Implementierung der Methode method(...) auf, die in der direkten Basisklasse verwendet wird. Es kann sich dabei durchaus um eine Methode handeln, die in der Ableitungshierarchie "weiter oben" definiert und in der direkten Basisklasse selbst nicht neu definiert worden ist. ◇ Ein Zugriff zur überschriebenen Methode über ihren vollqualifizierten Namen ist nicht möglich ( Compiler-Fehler). ◇ Ein Cast einer Referenz auf ein Objekt der abgeleiteten Klasse in die Basisklassen-Referenz ändert nicht den tatsächlichen Typ des referierten Objekts. Über einen derartigen Cast kann daher nicht zur überschriebenen sondern nur zur überschreibenden Methode zugegriffen werden. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 463 – 00 – TH – 02 ----------------------------------------------------------------------------------- Vererbung in Java (3) • Überladen von Methoden (Overloading) ◇ Eine in der abgeleiteten Klasse definierte Methode mit gleichem Namen aber anderer Parameterliste, überlädt die Methode der Basisklasse. Der Rückgabetyp bleibt unberücksichtigt, er kann gleich oder unterschiedlich sein. Das gleiche gilt für die Exception-Deklaration (throws-Klausel). ◇ In der abgeleiteten Klasse kann sowohl zu der geerbten Methode der Basisklasse als auch zu der neu definierten Methode allein über ihren Namen zugegriffen werden. in Java ist – anders als in C++ – ein Überladen von Methoden auch über Klassengrenzen hinweg möglich. ◇ Zusätzlich können Methoden natürlich auch innerhalb einer Klasse überladen werden. ◇ Wenn in der Basisklasse zwei Methoden überladen sind, ist es möglich, dass nur eine der Methoden in einer abgeleiteten Klasse überschrieben wird. Die andere steht dann auch in der abgeleiteten Klasse als überladene Funktion zur Verfügung. Das Überschreiben von Methoden erfolgt auf der Basis der Signaturen. C++ verhält sich in einem derartigen Fall anders : Durch das Überschreiben einer Methode werden sämtliche weiteren überladenen Methoden der Basisklasse überdeckt. ◇ Auch statische Methoden können im obigen Sinn überladen werden. ◇ Ferner können statische Methoden nicht-statische Methoden und umgekehrt überladen. • Überdecken von Klassenkomponenten (Hiding) ◇ Statische Methoden können überdeckt statt überschrieben werden. Eine in der abgeleiteten Klasse definierte statische Methode mit gleicher Signatur sowie gleichen Rückgabetyps wie eine statische Methode der Basisklasse, überdeckt die Methode der Basisklasse. ◇ Eine statische Methode darf keine Instanz-Methode überdecken ( Compiler-Fehler) ◇ Überdecken von Datenkomponenten ist zulässig ▻ zwischen Instanz-Variablen (nicht-statischen Datenkomponenten) ▻ zwischen Klassen-Variablen (statischen Datenkomponenten) ▻ gemischt zwischen Instanz- und Klassen-Variablen. ◇ Das Überdecken zwischen Datenkomponenten unterschiedlichen Typs ist zulässig. ◇ Überdeckt werden genaugenommen Namen. Überdecken eines Namens bedeutet, dass in der abgeleiteten Klasse der Name allein nur in der in dieser Klasse definierten Bedeutung verwendet werden kann. Um den Namen in der Bedeutung, die in der Basisklasse definiert ist, zu verwenden, muss der entsprechende vollqualifizierte Name oder der Name zusammen mit einer anderen Basisklassen-Referenz (z.B. super.kompo, innerhalb einer Instanz-Methode) verwendet werden. Die Verwendung des vollqualifizierten Namens ist nur für statische Komponenten zulässig. ◇ Beim Zugriff zu überdeckten Komponenten wird die tatsächlich ausgewählte Komponente durch die deklarierte Klasse der verwendeten Referenz (und nicht durch die Klasse des referierten Objekts) festgelegt. (Festlegung zur Compile-Zeit, early binding) ◇ Zwischen einer Variablen und einer Funktion gleichen Namens findet kein Überdecken statt. Beide können gleichzeitig allein mit ihrem Namen verwendet werden. Das gilt auch, wenn beide in derselben Klasse definiert sind. ◇ Eine als private deklarierte Komponente kann weder überschrieben noch überdeckt werden (Sie ist ja in der abgeleiteten Klasse überhaupt nicht zugänglich). Ihr Name kann deshalb in der abgeleiteten Klasse für beliebige Definitionen verwendet werden. Beispielsweise ist es bei einer Memberfunktion zulässig, in der abgeleiteten Klasse eine Methode mit gleicher Signatur aber unterschiedlichem Rückgabetyp zudefinieren. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 464 – 01 – TH – 01 ----------------------------------------------------------------------------------- Vererbung in Java (4-1) • Demonstrationsbeispiel zum Überschreiben und Überladen von Methoden ◇ Definition einer Basisklasse Punkt public class Punkt { private double x = 0.0; private double y = 0.0; public Punkt() { } public Punkt(double wx, double wy) { x = wx; y = wy; } public Punkt move(double dx, double dy) { x += dx; y += dy; return this; } public Punkt change(Punkt p) { x = p.x; y = p.y; return this; } public String toString() { StringBuffer hb = new StringBuffer(); hb.append('(').append(x).append(" , ").append(y).append(')'); return new String(hb); } } ◇ Definition einer abgeleiteten Klasse Farbpunkt public class Farbpunkt extends Punkt { private String col; public Farbpunkt() { this("black"); } public Farbpunkt(String wcol) { super(); col = wcol; } public Farbpunkt(double wx, double wy) { this(wx, wy, "black"); } public Farbpunkt(double wx, double wy, String wcol) { super(wx, wy); col = wcol; } public Farbpunkt change(String ncol) { col = ncol; return this; } // ueberlaedt Basisklassenmethode public Farbpunkt change(Farbpunkt fp) { change((Punkt)fp); change(fp.col); return this; } // ueberlaedt Basisklassenmethode public String toString() // ueberschreibt Basisklassenmethode { StringBuffer hb = new StringBuffer(super.toString()); hb.append(" & ").append(col); return new String(hb); } FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 464 – 02 – TH – 01 ----------------------------------------------------------------------------------- Vererbung in Java (4-2) • Demonstrationsbeispiel zum Überschreiben und Überladen von Methoden, Forts ◇ Definition einer verwendenden Test-Klasse PunktTest // PunktTest.java public class PunktTest { public static void main(String[] args) { Punkt p1 = new Punkt(4.2, 6.3); Punkt p2 = new Farbpunkt(); Farbpunkt p3 = new Farbpunkt(1.8, 7.9); Punkt p4 = new Farbpunkt("magenta"); Farbpunkt p5 = new Farbpunkt(2.5, 3.7, "blue"); System.out.println("p1 : " + p1); System.out.println("p2 : " + p2); System.out.println("p3 : " + p3); System.out.println("p4 : " + p4); System.out.println("p5 : " + p5); System.out.println("nach einigen Aenderungen : "); p3.move(1.0, 1.0); System.out.println("p3 : " + p3); p3.change("yellow"); System.out.println("p3 : " + p3); p5.change(p1); System.out.println("p5 : " + p5); p3.change(p5); System.out.println("p3 : " + p3); } } ◇ Ausgabe des Demonstrations-Programms p1 : p2 : p3 : p4 : p5 : nach p3 : p3 : p5 : p3 : (4.2 , 6.3) (0.0 , 0.0) & black (1.8 , 7.9) & black (0.0 , 0.0) & magenta (2.5 , 3.7) & blue einigen Aenderungen : (2.8 , 8.9) & black (2.8 , 8.9) & yellow (4.2 , 6.3) & blue (4.2 , 6.3) & blue FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 465 – 00 – TH – 01 ----------------------------------------------------------------------------------- Vererbung in Java (5) • Demonstrationsbeispiel zum Überdecken und Überladen von statischen Methoden ◇ Definition einer Basisklasse Ober2 class Ober2 { public static String gruss() { return "sagt Hallo !"; } public String name() { return "Ober2 "; } } ◇ Definition einer abgeleiteten Klasse Unter2 class Unter2 extends Ober2 { public static String gruss() { return "sagt Guten-Morgen !"; } // ueberdeckt Basisklassen-Methode public static String gruss(String gr) { return "sagt " + gr; } // ueberlaedt Basisklassen-Methode public String name() { return "Unter2 "; } // ueberschreibt Basisklassen-Methode public static void main(String[] args) { Ober2 s = new Ober2(); System.out.println(s.name() + s.gruss()); s = new Unter2(); System.out.println(s.name() + s.gruss()); Unter2 u = new Unter2(); System.out.println(u.name() + u.gruss()); System.out.println(u.name() + u.gruss("Servus !")); System.out.println(u.name() + gruss()); System.out.println(u.name() + Ober2.gruss()); } } ◇ Ausgabe des Demonstrations-Programms Ober2 Unter2 Unter2 Unter2 Unter2 Unter2 sagt sagt sagt sagt sagt sagt Hallo ! Hallo ! Guten-Morgen ! Servus ! Guten-Morgen ! Hallo ! FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 466 – 00 – TH – 01 ----------------------------------------------------------------------------------- Vererbung in Java (6) • Demonstrationsbeispiel zum Überdecken von Datenkomponenten ◇ Definition einer Basisklasse Ober3 class Ober3 { protected int meinW = 125; protected static float deinW = 6.35f; protected int val = 23; } ◇ Definition einer abgeleiteten Klasse Unter3 class Unter3 extends Ober3 { private static double meinW = 3.5; private float deinW = 5.2f; public int val() { return val; } public void nutzeWerte() { System.out.println("in Unter3 System.out.print( "in Ober3 System.out.println(" deinW " System.out.print( "in Ober3 System.out.println(" deinW " } : : + : + meinW " + meinW + " deinW " + deinW); meinW " + super.meinW); super.deinW); meinW " + super.meinW); Ober3.deinW); public static void main(String[] args) { Unter3 u = new Unter3(); u.nutzeWerte(); System.out.println(); System.out.println("val direkt : " + u.val); System.out.println("val ueber Fkt : " + u.val()); } } ◇ Ausgabe des Demonstrations-Programms in Unter3 : meinW 3.5 in Ober3 : meinW 125 in Ober3 : meinW 125 val direkt : 23 val ueber Fkt : 23 deinW 5.2 deinW 6.35 deinW 6.35 FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 467 – 00 – TH – 02 ----------------------------------------------------------------------------------- Vererbung in Java (7) • Typkompatibilität und -umwandlung ◇ Eine abgeleitete Klasse ist zuweisungs-kompatibel zu ihrer Basisklasse. Einer Basisklassen-Variablen kann jederzeit eine Referenz auf ein Objekt einer von der Basisklasse abgeleiteten Klasse zugewiesen werden. ◇ Das durch eine Basisklassen-Variable referierte Objekt einer abgeleiteten Klasse wird dadurch als Objekt der Basisklasse behandelbar. Implizite Typumwandlung. Da von einem spezialisierteren Typ in einen allgemeineren Typ umgewandelt wird, spricht man auch von einer erweiternden Typumwandlung (widening conversion). Weil diese Umwandlung in der Klassen-Hierarchie aufwärts erfolgt nennt man sie auch Aufwärts-Cast (upcast). ◇ Die Zuweisung einer Basisklassen-Referenz, die aber tatsächlich auf ein Objekt einer abgeleiteten Klasse zeigt, an eine Variable des tatsächlich referierten Typs ist nicht implizit möglich. Sie erfordert eine explizite Typumwandlung mittels des Cast-Operators. Hierbei findet eine Umwandlung von einem allgemeineren Typ in einen spezialisierteren Typ statt. Man nennt dies eine einengende Typumwandlung (narrowing conversion) oder einen Abwärts-Cast (down cast). ◇ Ein Abwärts-Cast ist prinzipiell unsicher. Er darf nur dann durchgeführt werden, wenn durch die Basisklassen-Referenz tatsächlich ein Objekt des angegebenen Zieltyps referiert wird. Der Versuch einer Typumwandlung, bei der dies nicht erfüllt ist, führt zum Werfen einer Exception vom Typ ClassCastException. • Der Operator instanceof ◇ Dieser binäre Operator ermöglicht die Überprüfung, ob ein Ausdruck zuweisungskompatibel zu einem bestimmten Typ ist. ◇ Syntax : Ausdruck instanceof Typangabe ◇ Wert eines instanceof-Ausdrucks : ▻ true, wenn der Ausdruck der linken Seite zuweisungs-kompatibel zum Typ der rechten Seite ist ▻ false, wenn der Ausdruck der linken Seite nicht zuweisungs-kompatibel zum Typ der rechten Seite ist ◇ Der instanceof-Operator kann eingesetzt werden, um einen sicheren Abwärts-Cast zu ermöglichen. ◇ Beispiel : class That { // ... } class More extends That { // ... } // ... That sref = new More(); More mref; // ... if (sref instanceof More) mref=(More)sref; // ... FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 471 – 00 – TH – 04 ----------------------------------------------------------------------------------- Interfaces in Java (1) • Prinzip ◇ Interfaces definieren – ähnlich wie Klassen – Typen. Allerdings erfolgt die Definition in einer abstrakten Form, die keinerlei Implementierung bereitstellt. Damit lassen sich Interfaces nicht instanziieren. Sie legen – wie der Name bereits ausdrückt – lediglich eine Schnittstelle (Kontrakt) fest, die dann von Klassen implementiert werden kann. ◇ Ein Interface besteht – ähnlich wie eine Klasse – aus Komponenten. Diese Komponenten können aber lediglich sein : ▻ abstrakte Methoden ▻ Konstante ▻ eingebettete Klassen und Interfaces ◇ Im Prinzip ähnelt ein Interface einer abstrakten Klasse. Im Unterschied zu einem Interface kann eine abstrakte Klasse aber eine (Teil-) Implementierung enthalten. (Instanz- und Klassen-Variable, Klassen-Methoden, definierte Instanz-Methoden). Ein Interface kann als abstrakte Klasse, die nur Konstante und abstrakte Methoden (und gegebenenfalls eingebettete Typen) vereinbart, aufgefasst werden ("rein abstrakte" Klasse). ◇ Interfaces müssen wie Klassen in jeweils einer eigenen Quelldatei (Ausnahme eingebettete Interfaces) definiert werden, deren Hauptname gleich dem Interface-Namen sein muß. Interfaces werden vom Compiler auch getrennt übersetzt. • Interface-Definition ◇ Syntax : interface Interface-Name InterfaceModifizierer Typ-ParamDeklaration { Konstantendefinition Abstrakte-Methoden-Deklaration Klassen-/Interface-Definition ; ◇ Beispiel : public interface Fahrbar { int MOTOR_UND_REIFEN = 3; int MOTOR_UND_SCHRAUBE = 5; int MUSKEL_UND_REIFEN = 2; int MUSKEL_UND_SCHRAUBE = 4; int LEHRLAUF = 0; void setAntrieb(int antr); void fahreVor(double dist); void fahreRueck(double dist); int getAntrieb(); double getFahrZaehler(); } InterfaceAbleitung } FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 472 – 00 – TH – 04 ----------------------------------------------------------------------------------- Interfaces in Java (2) • Interface-Modifizierer ◇ Zulässig sind : ▻ public Ohne diesen Zugriffs-Spezifizierer besitzt jedes Interface die Zugrifssberechtigung package. ▻ abstract Jedes Interface ist implizit abstract. Eine explizite Angabe dieses Modifizierers ist daher überflüssig. In der Sprachspezifikation ist sie als "überholt" (obsolete) bezeichnet und sollte daher nicht verwendet werden. ▻ strictfp Alle Floating-Point-Ausdrücke innerhalb der Interface-Definition (z.B. zur Konstanten-Initialisierung bzw innerhalb eingebetteter Typen) werden FP-strict ausgewertet ◇ Für Interfaces, die innerhalb von Klassen oder anderen Interfaces definiert sind (eingebettete Interfaces, member interfaces) sind darüber hinaus auch zulässig : ▻ protected ▻ private ▻ static • Definition von Memberkonstanten (Interface-Konstante) ◇ Ein Interface darf als Datenkomponenten nur Konstante enthalten. ◇ Jede Datenkomponente ist daher implizit static und final. Zusätzlich ist sie implizit public. Eine explizite Angabe dieser Feld-Modifizierer ist zwar zulässig, aber überflüssig. ◇ Jede Datenkomponente muß durch einen Initialisierer initialisiert werden. blank finals sind nicht zulässig. Der Initialisierer muß ein konstanter Ausdruck sein. Dieser Ausdruck darf weder den Namen der zu initialisierenden Komponente noch einer Komponente, die erst später definiert wird, enthalten Compiler-Fehler. ◇ Beispiel : interface { float f int j int k } Test = j; = 1; = k + 1; // Compiler-Fehler ! // Compiler-Fehler ! • Deklaration abstrakter Memberfunktionen (Interface-Methoden) ◇ Jede in einer Interface-Definition enthaltene Memberfunktion ist implizit abstract. Ihre Vereinbarung muß an Stelle eines Funktionsrumpfes ein ; enthalten. ◇ Jede in einer Interface-Definition enthaltene Memberfunktion ist implizit public. Nichtöffentliche Memberfunktionen machen keinen Sinn. ◇ Eine explizite Verwendung der Modifizierer abstract und public ist zwar zulässig, aber überflüssig und sollte vermieden werden. ◇ Andere Modifizierer sind unzulässig. Allerdings dürfen die in implementierenden Klassen enthaltenen Definitionen der Interface-Methoden mit den Modifiern strictfp, native, synchronized und final versehen werden. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 473 – 00 – TH – 04 ----------------------------------------------------------------------------------- Interfaces in Java (3) • Vererbung von Interfaces ◇ Interfaces können auch voneinander abgeleitet werden ◇ Es gibt aber kein allgemeines Wurzel-Basis-Interface (etwa wie die Wurzel-Basisklasse Object) ◇ Im Unterschied zu Klassen ist bei Interfaces Mehrfachvererbung zulässig. Ein Interface kann von mehreren anderen Interfaces abgeleitet sein. ◇ Syntax : Interface-Ableitung : extends Basis-Interface-Name , ◇ Beispiel : Bibliotheks-Interfaces Serializable (Package java.io) Runnable (Package java.lang) public Interface SerializeAndRunnable extends java.io.Serializable, Runnable { // ... } Das Interface SerializeAndRunnable enthält – durch Vererbung – alle Komponenten der Interfaces Serializable und Runnable. Zusätzlich kann es weitere Komponenten definieren. ◇ Bei abgeleiteten Interfaces kann ebenfalls Überschreiben, Überladen und Überdecken von Komponenten auftreten. Prinzipiell gelten hierfür die gleichen Regeln und Einschränkungen wie bei abgeleiteten Klassen : ▻ Überschreiben von Memberfunktionen : Die Deklaration einer Memberfunktion in einem abgeleiteten Interface überschreibt alle geerbten Memberfunktionen mit gleicher Signatur (gleicher Name und gleiche Parameterliste) und gleichem Rückgabetyp. Eine tatsächliche Auswirkung hat ein derartiges Überschreiben nur, wenn dadurch im abgeleiteten Interface eine Einschränkung in der Exception-Deklaration (throws-Klausel) der Methode vorgenommen wird (s. Vererbung bei Klassen). ▻ Die Deklaration einer Memberfunktion in einem abgeleiteten Interface mit gleicher Signatur aber unterschiedlichem Rückgabetyp wie in einem Basis-Interface ist unzulässig und führt zu einem Compilerfehler. Gleiches gilt, wenn zwei Methoden mit gleichem Namen und gleicher Signatur aber unterschiedlichen Rückgabetypen – von unterschiedlichen Basis-Interfaces – geerbt werden. ▻ Überladen von Memberfunktionen : Die Deklaration einer Memberfunktion in einem abgeleiteten Interface überlädt alle geerbten Memberfunktionen mit gleichem Namen aber unterschiedlicher Parameterliste. Der Rückgabetyp und die Exception-Deklaration bleiben unberücksichtigt. Überladen liegt auch vor, wenn innerhalb desselben Interfaces Methoden mit gleichem Namen aber unterschiedlicher Parameterliste deklariert werden oder wenn alle derartigen Methoden von Basis-Interfaces geerbt werden. ▻ Überdecken von Datenkomponenten (Konstanten) Die Definition einer Konstanten in einem abgeleiteten Interface überdeckt jede geerbte Konstante gleichen Namens, unabhängig vom jeweiligen Typ. Der Zugriff zur geerbten Konstanten ist nur über deren vollqualifizierten Namen möglich. ◇ Erbt ein Interface von zwei verschiedenen Basis-Interfaces zwei Methoden gleicher Signatur und gleichem Rückgabetyp, so "verschmelzen" diese im abgeleiteten Interface zu einer einzigen Methode. ◇ Erbt ein Interface von zwei verschiedenen Basis-Interfaces zwei Konstante gleichen Namens, so kann zu Ihnen – unabhängig von ihrem jeweiligen Typ – nur über ihren vollqualifizierten Namen zugegriffen werden. ◇ Es ist auch zulässig, dass ein Interface dieselbe Methode bzw Konstante auf verschiedenen Ableitungswegen erbt. Die Methode bzw die Konstante ist in dem abgeleiteten Interface nur einmal vorhanden. Ein Zugriff ist eindeutig. FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 474 – 00 – TH – 06 ------------------------------------------------------------------------------------ Interfaces in Java (4) • Implementierung von Interfaces ◇ Der Sinn von Interfaces liegt in der Definition von Schnittstellen, die zur Realisierung einer Funktionalität durch Klassen implementiert werden müssen. ◇ Eine Klasse, die ein Interface implementiert, muß sämtliche Methoden des Interfaces (einschliesslich der von BasisInterfaces geerbten) definieren (d.h. für sie eine Implementierung bereitstellen) oder selbst als abstract deklariert sein. Im letzteren Fall können – müssen aber nicht – die nicht implementierten Methoden des Interfaces als abstrakt deklariert werden Prinzipiell entspricht die Implementierung eines Interfaces damit der Ableitung von einer (rein) abstrakten Klasse. ◇ Eine Klasse kann – gegebenenfalls zusätzlich zur Ableitung von einer Klasse – mehrere Interfaces implementieren. Realisierung einer Art eingeschränkter Mehrfachableitung in Java. ◇ Syntax : Interface-Implementierung : implements Interface-Name , Die implements-Klausel muss nach einer – gegebenenfalls vorhandenen – extends-Klausel aufgeführt werden. ◇ Beispiel : public class CompCelBody extends CelBody implements Comparable<CompCelBody> { private int orbDist; public CompCelBody(String bName, CelBody orbAround, int dist) { super(bName, orbAround); orbDist = dist; } public int compareTo(CompCelBody o) { if (getOrbits() == o.getOrbits()) return orbDist-o.orbDist; else throw new IllegalArgumentException("unterschiedlicher Orbit"); } // ... } ◇ Die Implementierung eines Interfaces wird auch an abgeleitete Klassen vererbt. In einem derartigen Fall ist die Angabe einer implements-Klausel bei der abgeleiteten Klasse nicht explizit notwendig, aber zulässig. Dem Kopf einer Klassendefinition können nicht in jedem Fall die implementierten Interfaces entnommen werden. Beispiel : public interface GraphObj { /* ... */ } public class Punkt implements GraphObj { /* ... */ } public class Quadrat extends Punkt // implements GraphObj { /* ... */ } Die Klasse Quadrat implementiert auch das Interface GraphObj. ◇ Ob die Klasse eines Objekts ein bestimmtes Interface implementiert, kann mit dem instanceof-Operator überprüft werden. Beispiel : Quadrat qd = new Quadrat(); boolean isgo = qd instanceof GraphObj; // true FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 475 – 00 – TH – 06 ------------------------------------------------------------------------------------ Interfaces in Java (5) • Interfaces als Objekt-Referenzen ◇ Objekte einer Klasse, die ein Interface implementiert, lassen sich auch als Instanzen des implementierten Interfaces behandeln. Explizite oder implizite Typkonvertierung (cast) eines Objekts in den Typ eines implementierten Interfaces. ◇ Man kann Variable eines Interface-Typs definieren und ihnen Referenzen auf Objekte einer implementierenden Klasse zuweisen. Natürlich lassen sich über eine Interface-Variable auch nur die Komponenten eines Objekts ansprechen, die in dem implementierten Interface definiert sind. ◇ Beispiel : public interface GraphObj { void show(); void hide(); // ... } public class FPunkt implements GraphObj { // ... public FPunkt(int x, int y, Color f) { public void show() { public void hide() { // ... public void drawPoint() { /* ... */ } /* ... */ } /* ... */ } /* ... */ } // ... } // ... GraphObj go = new FPunkt(1,1, Color.GREEN); go.show(); go.drawPoint(); // unzulässig ! // ... ◇ Jede Objekt-Referenz eines Interface-Typs lässt sich allerdings als Referenz auf die Klasse Object verwenden. Damit lassen sich über eine Interface-Variable auch die in der Klasse Object definierten Methoden aufrufen. Dies ist deswegen möglich, da ja das referierte Objekt von irgendeiner Klasse sein muß und jede Klasse von Object abgeleitet ist. Beispiel : // ... GraphObj go = new FPunkt(5,5, Color.BLACK); String str = go.toString(); // keine Methode von GraphObj, aber von Object // ... • Marker Interfaces ◇ Es kann auch sinnvoll sein, ein Interface leer, d.h. ohne jegliche Komponenten, zu definieren. ◇ Ein derartige Interface dient dazu, anzuzeigen, dass eine implementierende Klasse über eine bestimmte Eigenschaft verfügt. Es markiert die Klasse hinsichtlich des Besitzes dieser Eigenschaft Marker Interface ◇ In der Java-Standardbibliothek sind mehrere derartige Marker Interfaces enthalten. Beispiele : ▻ Cloneable (Package java.lang) Es legt fest, dass Objekte einer implementierenden Klasse mit der clone()-Methode geclont werden können. Die clone()-Methode ist selbst nicht Komponente des Interfaces, sondern der Klasse Object. ▻ Serializable (Package java.io) Es kennzeichnet, dass Objekte einer implementierenden Klasse serialisiert (in einen Byte-Stream umgewandelt) und deserialisiert (aus einem Byte-Stream wiedergewonnen) werden können. Klassen, die zur Realisierung des Serialisierungs-/Deserialisierungs-Mechanismus einer besonderen Behandlung bedürfen, müssen die Methoden writeObject() und readObject() implementieren. Diese Methoden sind aber keine Komponenten des Interfaces Serializable. FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 481 – 00 – TH – 03 ----------------------------------------------------------------------------------- Eingebettete Klassen und Interfaces in Java (1) • Überblick ◇ In Java können (ab JDK 1.1) Klassen und Interfaces auch innerhalb anderer Klassen und Interfaces definiert werden. eingebettete Typen (nested types). ◇ Die Definition eingebetteter Typen ist möglich als ▻ Komponente der umgebenden Klasse bzw des umgebenden Interfaces ▻ lokale Klasse innerhalb eines in der umgebenden Klasse enthaltenen Codeblocks (z.B. in einer Memberfunktion, Konstruktor oder Initialisierungsblock) ◇ Die Möglichkeit, eingebettete Typen zu definieren, ist im wesentlichen in zweierlei Hinsicht zweckmässig : ▻ Strukturierung von Klassen und Interfaces in Gruppen entsprechend ihres logischen Zusammenhangs ▻ Definition von mit spezieller Funktionalität ausgestatteten "Hilfs"- oder "Adaptor"-Klassen an genau der Stelle im Programm, an der sie gebraucht werden. Dadurch lassen sich logisch zusammenhängende Objekte in einfacher und effizienter Weise miteinander verbinden. Hiervon wird insbesondere im Event-Model der GUI-Klassen der JFC (Java Foundation Classes) und in der JavaBeans-Komponenten-Architektur Gebrauch gemacht. ◇ Ein eingebetteter Typ kann als Bestandteil des umschliessenden Typs aufgefaßt werden. Das bedeutet, dass die beiden Typen prinzipiell gegenseitigen Zugriff auch zu ihren jeweiligen private- und protected- Komponenten besitzen (Unterschied zu C++ !). ◇ Ein eingebetteter Typ kann i.a. wieder eingebettete Typen enthalten Grundsätzlich ist eine Einbettung in beliebiger Tiefe möglich. Allerdings vermindert jede weitere Einbettung die Übersichtlichkeit und Klarheit einer Klassendefinition. Eine Einbettung über mehr als zwei Ebenen sollte daher vermieden werden. In den meisten Fällen ist eine Einbettungsebene ausreichend. ◇ Der Compiler erzeugt für jeden eingebetteten Typ eine eigene class-Datei. Deren Haupt-Name (=Typ-Name) wird aus den Typnamen des umschliessenden und des eingebetteten Typs zusammengesetzt (Trennung durch $) • Arten eingebetteter Typen ◇ Es existieren vier verschieden Arten eingebetteter Typen. Sie unterscheiden sich hinsichtlich des Orts und der Art ihrer Definition (Komponente / lokale Klasse, statisch/nichtstatisch, Typname / namenlos) ◇ Eingebettete Top-Level-Klassen und –Interfaces (nested top level classes and interfaces) Sie werden als static-Komponenten einer Klasse oder eines Interfaces definiert. Eingebettete Interfaces sind immer von dieser Art (sie sind implizit static). Auch alle innerhalb eines Interfaces definierte Typen (Klassen und Interfaces) sind immer implizit static (und public) und damit ebenfalls immer von dieser Art. ◇ Innere Klassen (inner classes, member classes) Sie werden als Komponenten einer Klasse, die nicht static sind, definiert. Ihre Definition als Interface-Komponente ist nicht möglich. Auch gibt es keine inneren Interfaces. Ein Objekt einer inneren Klasse ist immer mit einem Objekt der umfassenden Klasse assoziiert. ◇ Lokale Klassen (local classes, local inner classes) Sie werden innerhalb eines Code-Blocks definiert und sind nur innerhalb dieses Blockes verwendbar. Lokale Interfaces sind nicht möglich. ◇ Anonyme Klassen (anonymous classes) Hierbei handelt es sich ebenfalls um Klassen, die innerhalb eines Code-Blocks definiert werden (es sind also auch lokale Klassen). Im Unterschied zu "normalen" lokalen Klassen werden sie aber ohne Namen innerhalb eines newAusdrucks definiert. Anonyme Interfaces sind ebenfalls nicht möglich. FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 482 – 00 – TH – 04 ------------------------------------------------------------------------------------ Eingebettete Klassen und Interfaces in Java (2) • Eingebettete Top-Level-Klassen und –Interfaces ◇ Normalerweise werden Klassen und Interfaces auf der globalen Ebene innerhalb einer jeweils eigenen Quelldatei – gegebenenfalls als Mitglied eines Packages – definiert. Derartige Klassen / Interfaces werden als Top-Level-Klassen / -Interfaces bezeichnet. ◇ Eingebettete Top-Level-Klassen / -Interfaces sind Typen, die innerhalb einer umschliessenden Klasse / Interface als static-Komponente definiert sind. Dabei sind alle innerhalb eines Interfaces definierte Klassen oder Interfaces implizit static (und public). Ebenfalls sind alle innerhalb einer Klasse definierten Interfaces implizit static. Die explizite Angabe des Modifiers static ist in diesen Fällen also überflüssig, aber zulässig. ◇ Diese Typen verhalten sich wie "normale" – nicht eingebettete – Top-Level-Typen, mit dem Unterschied, dass ihr Name und ihre Zugreifbarkeit durch den umschliessenden Typ festgelegt wird. Sie lassen sich ausserhalb ihrer umschliessenden Klasse / Interface prinzipiell wie jeder andere Top-Level-Typ ver wenden, mit den Einschränkungen, dass ▻ ihr Name mit dem Namen des umschliessenden Typs qualifiziert werden muß ( vollqualifizierter Name) ▻ und zu ihnen nur zugegriffen werden kann, wenn auch der umschliessende Typ zugreifbar ist. Unter Berücksichtigung dieser Einschränkungen und der jeweiligen Zugriffsberechtigung kann ein eingebetteter TopLevel-Typ von einer anderen Klasse / Interface abgeleitet sein bzw selbst Basisklasse / Basisinterface für weitere Ableitungen sein. Eine eingebettete Top-Level-Klasse kann beliebige Interfaces implementieren und kann instanziiert werden. Sie kann final oder abstract deklariert werden. ◇ Bezüglich der Zugreifbarkeit eingebetteter Typen gelten die gleichen Regeln wie für die Daten- bzw Funktionskomponenten einer Klasse /eines Interfaces. Das bedeutet, dass innerhalb einer Klasse definierte eingebettete Typen die Zugriffsberechtigungen private, package, protected oder public besitzen können. Für innerhalb eines Interfaces definierte Typen ist dagegen die Zugriffsberechtigung public implizit festgelegt. ◇ Da eingebettete Top-Level-Typen Komponenten ihrer jeweiligen umschliessenden Klasse sind, besteht zwischen ihnen und dieser Klasse ein besonders privilegiertes Verhältnis bezüglich der gegenseitigen Zugriffsberechtigung : Die eingebetteten Typen dürfen zu allen anderen Komponenten ihrer umschliessenden Klasse – insbesondere auch zu den private deklarierten – zugreifen. Umgekehrt gilt, dass auch die umschliessende Klasse die Zugriffsberechtigung zu allen Komponenten der eingebetteten Klasse besitzt. Zu den nicht-statischen Komponenten kann natürlich nur über ein Objekt der jeweiligen Klasse zugegriffen werden. Das privilegierte Zugriffsberechtigungsverhältnis wird nicht an abgeleitete Klassen vererbt. ◇ Eingebettete Top-Level-Klassen / -Interfaces dienen zur Strukturierung logisch zusammenhängender Typen. ◇ Beispiel : public class MyLinkedList { public static interface Linkable { public Linkable getNext(); public void setNext(Linkable neu); } // eingebettetes Interface private Linkable head; public void insert(Linkable neu) { /* ... */ } public void remove(Linkable elem) { /* ... */ } } public class LinkableInteger implements MyLinkedList.Linkable { private int val; private MyLinkedList.Linkable next; public LinkableInteger(int i) { val = i; next = null; } public MyLinkedList.Linkable getNext() { return next; } public void setNext(MyLinkedList.Linkable neu) { next = neu; } } FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 483 – 01 – TH – 05 ------------------------------------------------------------------------------------ Eingebettete Klassen und Interfaces in Java (3 - 1) • Innere Klassen ◇ Dies sind Klassen, die innerhalb einer anderen Klasse als nicht-statische Komponenten definiert werden. ◇ Eine innere Klasse kann prinzipiell analog zu einer Top-Level-Klasse definiert werden. Allerdings darf sie keine static-Komponenten besitzen Ausnahme : sie darf Konstante (static final, initialisiert mit einem konstanten Ausdruck) enthalten. ◇ Eine innere Klasse kann von einer beliebigen anderen Klasse abgeleitet sein und beliebige Interfaces implementieren. Sie kann Basisklasse für beliebige andere Klassen sein, sofern sie für diese zugreifbar ist. Sie kann final oder abstract deklariert werden. ◇ Ein Objekt einer inneren Klasse muß immer mit genau einem Objekt der umschliessenden Klasse assoziiert sein (Die Umkehrung gilt nicht). Der Compiler ergänzt die Parameterliste jedes Konstruktors einer inneren Klasse um ein verborgenes Argument, dem implizit eine Referenz auf das assoziierte Objekt der umschliessenden Klasse übergeben wird. Diese Referenz wird in einer zusätzlichen – ebenfalls vom Compiler automatisch eingefügten – final-Datenkomponente abgelegt. Über diese Datenkomponente kann das Objekt der inneren Klasse implizit zu allen – auch den private-Komponenten des assoziierten Objekts der umschliessenden Klasse zugreifen. ◇ Beispiel : Klasse BankKonto mit innerer Klasse Buchung (s. nächste Seite) ◇ Die implizit abgelegte Referenz auf das Objekt der umschliessenden Klasse kann auch explizit angegeben werden. Syntax : Name_der_umschliessenden_Klasse . this Das Objekt der inneren Klasse kann damit auch über diese explizite Referenz zu den Komponenten des Objekts der umschliessenden Klasse zugreifen. Beispiel : Zugriff zur Datenkomponente nummer des assoziierten Objekts der umschliessenden Klasse BankKonto aus einem Objekt der inneren Klasse Buchung : BankKonto.this.nummer (s. Beispiel nächste Seite) Eine explizite Referenz ist insbesondere sinnvoll (und dann auch notwendig), wenn ▻ Komponenten der umschliessenden Klasse durch gleichnamige Komponenten der inneren Klasse überdeckt werden. (Eine Methode einer inneren Klasse überdeckt alle überladenen Formen einer gleichnamigen Methode der umschliessenden Klasse) ▻ zu Komponenten einer umschliessenden Klasse höherer Ebene zugegriffen werden soll. ◇ Aus der umschliessenden Klasse kann ebenfalls zu den privaten Komponenten der inneren Klasse zugegriffen werden – allerdings nur über eine explizite Referenz auf ein Objekt der inneren Klasse. • Instanziierung innerer Klassen ◇ Normalerweise werden innere Klassen in Instanz-Memberfunktionen der umschliessenden Klasse instanziiert. In einem derartigen Fall ist die Referenz auf das Objekt der umschliessenden Klasse durch this gegeben. ◇ Soll ein neu zu erzeugendes Objekt der inneren Klasse dagegen mit einem anderen Objekt der umschliessenden Klasse assoziiert werden, so muß dieses bei der Objekterzeugung explizit angegeben werden Hierfür existiert eine Erweiterung der Syntax für den new-Ausdruck : qualified class instance creation expression Syntax : Referenz_auf_umschliessendes_Objekt . new Konstruktoraufruf Beispiel : Klasse Buchung sei innere Klasse von BankKonto Eine Methode von BankKonto (z.B. main()) könnte folgenden Code enthalten : BankKonto deinKto = new BankKonto(2481); Buchung buchg = deinKto.new Buchung(null, 0.0f); FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 483 – 02 – TH – 01 ----------------------------------------------------------------------------------- Eingebettete Klassen und Interfaces in Java (3 - 2) • Demonstrationsbeispiel zu inneren Klassen // BankKonto.java import java.io.*; public class BankKonto { private long nummer; private float ktoStand; private Buchung letzteBuch; // ---------------------------- innere Klasse ------------------------------public class Buchung { private String art; private float betrag; Buchung(String act, float betr) { art = act; betrag = betr; } public String toString() { return nummer + " : " + art + ' ' + betrag; } // BankKonto.this.nummer } // -------------------------------------------------------------------------public BankKonto(long num) { nummer = num; ktoStand = 0.0f; letzteBuch = null; } public void einzahlen(float betr) { ktoStand += betr; letzteBuch = new Buchung("Einzahlung", betr); } public boolean abbuchen(float betr) { boolean bRet = true; if (ktoStand >= betr) { ktoStand -= betr; letzteBuch = new Buchung("Abbuchung", betr); } else { letzteBuch = new Buchung("Versuch der Kontoueberziehung", betr); bRet = false; } return bRet; } public void ueberweisen(BankKonto empf, float betr) { if (abbuchen(betr)) { empf.einzahlen(betr); letzteBuch = new Buchung("Ueberweisung ab ", betr); empf.letzteBuch = empf.new Buchung("Ueberweisung auf ",betr); } } // ... } FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 483 – 03 – TH – 04 ------------------------------------------------------------------------------------ Eingebettete Klassen und Interfaces in Java (3 - 3) • Ableitung innerer Klassen ◇ Analog zu Top-Level-Klassen können auch innere Klassen abgeleitet werden. Die abgeleitete Klasse kann sowohl eine Top-Level-Klasse als auch eine innere Klasse sein. ◇ Die einzige besondere Erfordernis ist, dass auch ein Objekt der abgeleiteten Klasse mit einem Objekt der ursprünglichen umfassenden Klasse (d.h. der umfassenden Klasse seiner Basisklasse) oder einer davon abgeleiteten Klasse assoziiert sein muß (Basisklassen-Teilobjekt enthält verborgene Komponente auf assoziiertes Objekt !). I.a. ist das kein Problem, da die abgeleitete Klasse meist als innere Klasse einer Klasse, die von der ursprünglichen umschliessenden Klasse abgeleitet ist, definiert wird. ◇ Beispiel : class Outer { class Inner { // ... } // ... } class ExtOuter extends Outer { class ExtInner extends Inner { // ... } // ... Inner ref = new ExtInner(); // ... } Da hier sowohl Inner als auch die hiervon abgeleitete Klasse ExtInner innere Klassen sind, wird bei der Erzeugung eines Objekts der Klasse ExtInner (z.B. zur Initialisierung von ref) dieses zweimal an ein Objekt der Klasse ExtOuter gebunden : - Das ExtOuter-Objekt wird dem Konstruktor von ExtInner implizit übergeben. Mit ihm wird die für das Objekt der Klasse ExtInner angelegte verborgene Komponente initialisiert - Zum anderen wird dieses Objekt auch dem – hier implizit aufgerufenen – Konstruktor der Basisklasse Inner (super()) implizit übergeben Dieser setzt die für das Teilobjekt der Klasse Inner angelegte und vom ExtInner-Objekt geerbte verborgene Komponente damit auf dasselbe ExtOuter-Objekt. ◇ Wenn die von der inneren Klasse abgeleitete Klasse selbst keine innere Klasse ist, dann muß dem Konstruktor ihrer Basisklasse eine explizite Referenz auf ein assoziierendes Objekt deren umschliessender Klasse übergeben werden. Hierfür existiert wiederum eine spezielle Syntax : Syntax : Referenz_auf_umschliessendes_Objekt . super Beispiel : class ExtNotInner extends Outer.Inner { ExtNotInner(Outer ref) { ref.super(); } // ... } ( Parameterliste ) FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 484 – 01 – TH – 03 ------------------------------------------------------------------------------------ Eingebettete Klassen und Interfaces in Java (4- 1) • Lokale Klassen ◇ Dies sind Klassen, die innerhalb eines Codeblocks (Methode, Konstruktor oder Initialisierungsblock) definiert sind. ◇ Sie können von beliebigen anderen Klassen abgeleitet sein und beliebige Interfaces implementieren. ◇ Es handelt sich bei Ihnen nicht um Klassenkomponenten. ◇ Sie sind lokal zu dem Block, in dem sie definiert sind, analog zu lokalen Variablen. Ausserhalb dieses Blocks ist ihre jeweilige Definition nicht zugänglich und damit auch nicht verwendbar. ◇ Instanzen von Ihnen können aber prinzipiell wie Objekte anderer Klassen verwendet werden. Sie existieren nach ihrer Erzeugung, bis sie nicht mehr referiert werden. Referenzen auf sie können als Funktionswerte zurückgegeben und als Parameter an Funktionen übergeben werden. Da ihr Klassenname ausserhalb des definierenden Codeblocks nicht zugänglich ist, werden derartige Objekte i.a. dann als Instanzen eines von ihnen implementierten Interfaces oder einer Basisklasse referiert ◇ Wie innere Klassen dürfen sie keine static-Komponenten besitzen, ausgenommen Daten-Komponenten, die als static und final deklariert sind (Konstante) ◇ Lokale Klassen können nicht mit einem Zugriffs-Spezifizierer (public, protected, private) deklariert werden • Zugriffsmöglichkeiten von lokalen Klassen ◇ Lokale Klassen können zu allen lokalen Variablen und Funktionsparametern, die in ihrem Sichtbarkeitsbereich (scope) liegen und die als final vereinbart sind, zugreifen. Derartige Variablen / Funktionsparameter werden den Konstruktoren einer lokalen Klasse implizit über vom Compiler hinzugefügte verborgene Parameter übergeben. Sie dienen zur Initialisierung von entsprechenden – ebenfalls vom Compiler hinzugefügten – privaten Datenkomponenten der lokalen Klasse. Genaugenommen arbeiten die Objekte einer lokalen Klasse also nicht mit den originalen lokalen Variablen und Funktionsparametern sondern mit Kopien derselben. Allerdings werden in den folgenden Fällen keine privaten Datenkomponenten für lokale Variable / Funktionsparameter angelegt : ▻ lokale Variable / Funktionsparameter, die in der lokalen Klasse nicht verwendet werden ▻ lokale final-Variable eines einfachen Datentyps, die gleich bei ihrer Definition initialisiert werden (= lokale Konstante). Ihr jeweiliger Wert wird direkt als Konstante eingesetzt ◇ Objekte einer lokalen Klasse, die innerhalb einer nicht-statischen Memberfunktion oder in einem Objekt-Initialisierungsblock definiert ist, müssen – wie Objekte innerer Klassen – immer an ein Objekt der umschliessenden Klasse gebunden sein. Damit haben sie direkten Zugriff zu allen Komponenten dieses Objekts. Die Implementierung dieser Bindung ist analog zu inneren Klassen realisiert (Übergabe einer Referenz auf das assoziierte Objekt der umschliessenden Klasse über einen verborgenen Konstruktorparameter und Speicherung derselben in einer hinzugefügten privaten Datenkomponente). Auch die explizite Referierung des assoziierten Objekts ist – mit derselben Syntax – wie bei inneren Klassen möglich (Erweiterte Form der this-Referenz). ◇ Objekte einer lokale Klasse, die in einer statischen Memberfunktion oder in einem Klassen-Initialisierungsblock definiert ist, besitzen kein assoziiertes Objekt der umschliessenden Klasse. Sie können direkt nur zu statischen Komponenten der umschliessenden Klasse zugreifen. Zum Zugriff zu den nicht-statischen Klassenkomponenten benötigten sie eine explizite Objekt-Referenz. • Typische Anwendung von lokalen Klassen ◇ Event-Listener-Klassen für GUI-Komponenten der JFC werden häufig als lokale Klassen definiert (Adapter-Klassen). Allerdings ist für diese auch die Definition als innere Klassen oder als anonyme Klassen üblich. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 484 – 02 – TH – 02 ----------------------------------------------------------------------------------- Eingebettete Klassen und Interfaces in Java (4- 2) • Demonstrationsprogramm zu lokalen Klassen // ArrayWalker.java // Implementierung einer Klasse, die eine – hier statische – Memberfunktion // makeEnum() zur Erzeugung eines Enumeration-Objekts für Arrays bereitstellt. // Die Klasse ArrayEnum des erzeugten Enumeration-Objekts ist als lokale Klasse // der Erzeugungs-Funktion implementiert. // Sie implementiert das Bibliotheks-Interface Enumeration import java.util.Enumeration; public class ArrayWalker { public static Enumeration makeEnum(final Object[] objs) { final int startpos; startpos = 0; // nur zu Demo-Zwecken so umstaendlich // ------------------------ lokale Klasse ------------------------------class ArrayEnum implements Enumeration { private int pos = startpos; // nur zu Demo-Zwecken so umstaendlich public boolean hasMoreElements() { return pos < objs.length; } public Object nextElement() { return objs[pos++]; } } // ---------------------------------------------------------------------return new ArrayEnum(); } // Demonstration der Anwendung public static void main(String[] args) { final int anz = 10; Integer[] ia = new Integer[anz]; for (int i=0; i<anz; i++) ia[i]=new Integer(i); for (Enumeration e = makeEnum(ia); e.hasMoreElements(); ) System.out.print(e.nextElement() + " "); System.out.println(); } } Programmausgabe : 0 1 2 3 4 5 6 7 8 9 FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 485 – 01 – TH – 02 ----------------------------------------------------------------------------------- Eingebettete Klassen und Interfaces in Java (5- 1) • Anonyme Klassen ◇ Anonyme Klassen sind im wesentlichen lokale Klassen ohne Namen. Sie verfügen über alle Eigenschaften von lokalen Klassen, die nicht vom Besitz eines Klassennamens abhängen. ◇ Sie müssen immer von einer anderen Klasse abgeleitet sein oder genau ein Interface implementieren. ◇ Sie werden gleichzeitig mit ihrer Instanziierung in einem new-Ausdruck definiert. Von ihnen lässt sich also immer nur ein Objekt erzeugen. ◇ Zur Realisierung anonymer Klassen existiert eine entsprechende Erweiterung der Syntax für den new-Ausdruck : new Klassen-/Interface-Name ( Parameterliste ) { Klassen-Rumpf } Der new-Ausdruck wird nicht – wie bei Klassen mit Namen – mit einem Semikolon abgeschlossen, sondern durch einen Klassen-Rumpf ergänzt. Der Klassen-Rumpf bildet die Definition der anonymen Klasse. Er enthält die Definition ihrer Komponenten und Initialisierungsblöcke. Der im new-Ausdruck anzugebende Klassen-/bzw Interface-Name ▻ ist der Name der Klasse, von dem die anonyme Klasse abgeleitet ist ▻ bzw der Name des Interfaces, das sie implementiert. Eine nicht-leere Parameterliste kann nur im Fall der Ableitung von einer anderen Klasse angegeben werden. Die Parameter werden dem implizit aufgerufenen Konstruktor der Basisklasse übergeben. Im Fall der Implementierung eines Interfaces muss die Parameterliste leer sein. Die explizite Angabe einer extends- oder implements-Klausel ist nicht möglich. ◇ Beispiel : // DirLister.java import java.io.*; public class DirLister { public static void main(String[] args) { File dir = new File(args[0]); final String endung = args[1]; // Achtung : hier keine Überprüfung // auf richtiges Aufrufformat String[] flist = dir.list(new FilenameFilter() { public boolean accept(File d, String s) { return s.endsWith(endung); } }); for (int i=0; i<flist.length; i++) System.out.println(flist[i]); } } ◇ Für anonyme Klassen kann kein expliziter Konstruktor definiert werden (kein Klassenname !). Stattdessen erzeugt der Compiler implizit einen anonymen Konstruktor, der den Basisklassen-Konstruktor aufruft. Falls Komponenten eines Objekts einer anonymen Klasse bei der Erzeugung explizit initialisiert werden sollen, muß dies durch Initialisierungsausdrücke bzw einen Intialisierungsblock erfolgen. ◇ Da anonyme Klassen keinen Namen besitzen, können sie nicht als Basisklasse verwendet werden anonyme Klassen sind implizit final. FACHHOCHSCHULE MUENCHEN FACHBEREICH ELEKTROTECHNIK UND INFORMATIONSTECHNIK BEREICH DATENTECHNIK V – JV – 485 – 02 – TH – 01 ----------------------------------------------------------------------------------- Eingebettete Klassen und Interfaces in Java (5- 2) • Verwendung anonymer Klassen ◇ Anonyme Klassen werden häufig konkurierend zu – benannten – lokalen Klassen eingesetzt. Sie dienen im wesentlichen auch zur Definition von Adapter-Klassen, wie z.B. Event-Listener-Klassen für GUI-Komponenten. ◇ Die Verwendung einer anonymen Klasse statt einer lokalen Klasse kann insbesondere sinnvoll sein, wenn ▻ die Definition der Klasse sehr kurz ist (nur wenige Quellcode-Zeilen umfasst), ▻ nur ein Objekt der Klasse benötigt wird ▻ die Klasse direkt dort, wo sie definiert wird, auch verwendet werden soll, ▻ ein Klassenname nicht explizit benötigt wird und die Vergabe eines Klassennamens den Code nicht verständlicher macht. • class-Dateiname einer anonymen Klasse ◇ Der Haupt-Name der vom Compiler erzeugten class-Datei für eine anonyme Klasse besteht aus dem Namen der umschliessenden Klasse gefolgt vom Zeichen $ und einer laufenden Nummer. • Demonstrationsprogramm zu anonymen Klassen Realisierung der Klasse ArrayEnum (s. Demonstrationsprogramm zu lokalen Klassen) als anonyme Klasse // ArrayWalker2.java import java.util.Enumeration; public class ArrayWalker2 { public static Enumeration makeEnum(final Object[] objs) { final int startpos; startpos=0; return new Enumeration() { private int pos = startpos; // Definition der anonymen Klasse public boolean hasMoreElements() { return pos < objs.length; } public Object nextElement() { return objs[pos++]; } }; } public static void main(String[] args) { final int anz = 10; Integer[] ia = new Integer[anz]; for (int i=0; i<anz; i++) ia[i]=new Integer(i); for (Enumeration e = makeEnum(ia); e.hasMoreElements(); ) System.out.print(e.nextElement() + " "); System.out.println(); } } FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 491 – 00 – TH – 03 ------------------------------------------------------------------------------------ Generische Klassen, Interfaces und Funktionen (Generics) in Java (1) • Allgemeines ◇ Mit dem JDK 5.0 wurde in Java die Möglichkeit der generischen Programmierung mittels Typ-Parameterieisrung eingeführt (Konzept der Generics) ◇ Typ-Parameterisierung lässt sich anwenden bei : ▻ Klassen generische Klassen ▻ Interfaces generische Interfaces ▻ Memberfunktionen generische Methoden ▻ Konstruktoren generische Konstruktoren ◇ Typ-Parameterisierung bedeutet, dass bei der Definition einer Klasse (bzw Interface, bzw Methode bzw Konstruktor) durch eine Typ-Param-Deklaration formale Typ-Parameter (Typ-Variablen) definiert werden. Dabei kann eine Typ-Variable durch eine Typ-Begrenzung (Type Bound) ergänzt werden Zur Anwendung der Klasse (bzw Interface, Methode, Konstruktor) müssen dieTyp-Variablen durch konkrete aktuelle Typ-Parameter (Typ-Argumente) ersetzt werden. Dadurch lässt sich der gleiche – nur einmal vorhandene – Code für unterschiedliche Typen verwenden. • Typ-Param-Deklaration ◇ Sie ist anzugeben bei ▻ der Klassen- und Interface-Definition : unmittelbar nach dem Klassen- bzw Interface-Namen ▻ der Methoden-Definition : unmittelbar vor dem Rückgabetyp ▻ der Konstruktor-Definition : unmittelbar vor dem Konstruktor(==Klassen)-Namen ◇ Syntax der Typ-Param-Deklaration : < Typ-Parameter > , Typ-Parameter : formaler Typ-Name Typ-Begrenzung Typ-Begrenzung : (Type Bound) Klassen-Typ extends & Interface-Typ Interface-Typ ◇ Durch Angabe einer Typ-Begrenzung werden die Typen der möglichen aktuellen Typ-Parameter (Typ-Argumente) eingeschränkt. Ein Typ-Argument muss zu der jeweiligen Typ-Begrenzung kompatibel sein, d.h. gleich dem angegebenen KlassenTyp oder von ihm abgeleitet sein und/oder die angegebenen Interface-Typen implementieren. Das Schlüsselwort extends steht hier sowohl für die Ableitung als auch für die Implementierung. ◇ Beispiele : public interface Map<K, V> { ... } public class EnumMap<K extends Enum<K>, V> extends ... implements ... { ... } public static <T extends Number & Serializable & Comparable<T>> T getMaxElement(Collection<T> coll) { ... } FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 492 – 00 – TH – 03 ------------------------------------------------------------------------------------ Generische Klassen, Interfaces und Funktionen (Generics) in Java (2) • Anmerkungen zu generischen Klassen und Interfaces ◇ Die bei der Definition einer generischen Klasse bzw Interfaces deklarierten formalen Typ-Parameter (Typ-Variable) können in der gesamten Klassendefinition verwendet werden, einschliesslich der Typ-Param-Deklaration selbst. (z.B. in der eigenen Typ-Begrenzung oder den Typ-Begrenzugen anderer Typ-Variabler) Innerhalb der Klassendefinition können sie als Typ-Angabe für Datenkomponenten, Parameter- und Rückgabetyp von Memberfunktionen sowie in eingebetteten Typen auftreten. Ausnahmen : ▻ Typ-Parameter von generischen Klassen dürfen nicht verwendet werden : - für statische Datenkomponenten (einschliesslich statischer Datenkomponenten eingebetteter Typen) - in statischen Funktionskomponenten (einschliesslich statischer Funktionskomponenten eingebetteter Typen) - in statischen Initialisierungsblöcken (einschliesslich statischer Initialisierungsblöcke eingebetteter Typen) ▻ Typ-Parameter von generischen Interfaces dürfen nicht verwendet werden : - für Datenkomponenten (sind immer implizit static und final) - in eingebetteten Typen ◇ Durch Anwendung aktueller Typ-Parameter (Typ-Argumente) wird aus einer generischen Klasse oder Interface ein konkreter Typ erzeugt parameterisierter Typ ◇ Alle aus einer generischen Klasse erzeugbaren parameterisierten Typen teilen sich dieselbe Klasse der folgende Ausdruck liefert true : new Vector<Integer>().getClass() == new Vector<String>().getClass(); ◇ Eine generische Klasse sowie ein aus einer generischen Klasse erzeugter parameterisierter Typ kann Basisklasse für andere generische und nicht-generische Klassen sein. Folgende Klassendefinitionen sind somit prinzipiell zulässig : public class GenVector<T> extends Vector<T> { ... } public class Menge<T> extends Vector<String> { ... } public class SpecVector extends Vector<T> { ... } public class MyIntList extends LinkedList<Integer> { ... } Analog können generische Interfaces und aus diesen gewonnene parameterisierten Typen von generischen und nicht-generischen Klassen implementiert werden ◇ Eine generische Klasse darf weder direkt noch indirekt von Throwable abgeleitet sein. Exception-Klassen können nicht generisch sein. • Raw Types ◇ Generische Klassen und Interfaces können auch ganz ohne Typ-Argumente verwendet werden. Der dadurch erhaltene Typ wird als Raw Type bezeichnet. ◇ Innerhalb eines Raw Types sind alle Auftritte einer Typ-Variablen (formaler Typ-Parameter) durch den Typ Object bzw – falls vorhanden – den ersten Typ einer Typ-Begrenzung ersetzt. Alle eventuell enthaltenen Vorkommen von generischen Klassen/Interfaces sowie parameterisierten Typen werden durch ihre zugehörigen Raw Types ersetzt. Der zu dieser Ersetzung führende Mechanismus wird Type Erasure genannt. public interface Iterator<E> { // ... E next(); } Beispiel : mittels Type Erasure wird hieraus : public interface Iterator { // ... Object next(); } ◇ Raw Types ermöglichen die Kompatibilität zu nicht-generischem Java-Code Mit ihrer Anwendung geht allerdings auch die durch die Typ-Parameterisierung erreichte Typ-Sicherheit (Überprüfung durch den Compiler !) verloren. Der Programmierer ist wieder selbst für richtige Type Casts verantwortlich Raw Types sollten daher möglichst nur dort statt parameterisierter Typen verwendet werden, wo es aus Gründen der Kompatibilität zu altem (nicht-generischem ) Code notwendig ist. FACHHOCHSCHULE MUENCHEN FAKULTÄT ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 493 – 00 – TH – 02 ----------------------------------------------------------------------------------- Generische Klassen, Interfaces und Funktionen (Generics) in Java (3) • Anmerkungen zu generischen Methoden und Konstruktoren ◇ Die Definition/Deklaration generischer Methoden und Konstruktoren ist unabhängig von einer generischen Eigenschaft ihrer jeweiligen Klasse/Interface. Generische Methoden/Konstruktoren lassen sich sowohl für generische als auch normale nicht-generische Klassen/Interfaces vereinbaren. ◇ Auch statische Methoden können generisch sein. ◇ Die bei der Definition einer generischen Methode bzw Konstruktors deklarierten Typ-Variablen (formale TypParameter) können in der gesamten Funktionsdefinition verwendet werden, einschliesslich der Typ-ParamDeklaration selbst (z.B. in der eigenen Typ-Begrenzung oder den Typ-Begrenzugen anderer Typ-Variabler), Innerhalb der Funktionsdefinition können sie als Typ-Angabe für lokale Variable, sowie Parameter- und Rückgabetyp der Funktion auftreten. ◇ Beispiele : public class GenericsDemo { public static <T extends Number & Serializable & Comparable<T>> T getMaxElement(Collection<T> coll) { T mv = Collections.max(coll); return mv; } public <T> void fromArrayToCollection(T[] ar, Collection<T> coll) { for (T elem : ar) coll.add(elem); } // ... } ◇ Aufruf generischer Methoden : ▻ Im Normalfall kann eine generische Funktion ohne explizite Angabe von aktuellen Typ-Parametern (Typ-Argumente) aufgerufen werden. Fast immer kann der Compiler die aktuellen Typ-Parameter mittels der sogenannten Type Inference implizit aus dem Funktionsaufruf und seinem Kontext ermitteln. ▻ Falls die aktuellen Typ-Parameter explizit angegeben werden, was natürlich prinzipiell immer möglich ist, müssen sie in spitzen Klammern eingeschlossen unmittelbar vor dem Methoden-/Konstruktor-Namen angegeben werden. Zusätzlich muss das Zielobjekt des Aufrufs (gegebenenfalls für das aktuelle Objekt this) bzw die Klasse (bei statischen Methoden) angegeben werden. ▻ Beispiele : public class GenericsDemo { // ... void main(String[] args) { ArrayList<Integer> ali = new ArrayList<Integer>(); // ... System.out.println(getMaxElement(ali)); System.out.println(GenericsDemo.<Integer>getMaxElement(ali)); Double[] da = {2.5, 3.5, 4.5, 0.5, 1.5}; Vector<Double> dv = new Vector<Double>(); GenericsDemo gendemo = new GenericsDemo(); gendemo./*<Double>*/fromArrayToCollection(da, dv); } } FACHHOCHSCHULE MUENCHEN FAKULTÄT FÜR ELEKTROTECHNIK UND INFORMATIONSTECHNIK FG TECHNISCHE INFORMATIK V – JV – 494 – 00 – TH – 02 ------------------------------------------------------------------------------------ Generische Klassen, Interfaces und Funktionen (Generics) in Java (4) • Implementierung von Generics ◇ Generics werden in Java allein vom Compiler bearbeitet. Im erzeugten Bytecode (.class-Datei) ist jeglicher Auftritt von Generics entfernt : Er besteht nur aus nicht-generischem Code. Zur Laufzeit steht keinerlei Information über gegebenenfalls im Quellcode enthaltene generische Eigenschaften (den Typ-Parametern) von Klassen/Interfaces/Methoden/Konstruktoren zur Verfügung. Typ-Variable existieren nicht zur Laufzeit. Die JVM kennt keine Generics. ◇ Bei der Übersetzung von generischem Quellcode entfernt der Compiler die Generics-Informationen mittels Type Erasure. Er erzeugt also den Byte-Code für Raw Types und "Raw Functions". Allerdings legt er die Generics-Informationen (Informationen über Typ-Parameter !) in sogenannten "Signaturattributen" ab und bettet diese in dem erzeugten Byte-Code ein. Signaturattribute sind aber kein Bestandteil des ByteCodes und werden von der JVM (sowie älteren Compilern) ignoriert. Java-Compiler ab dem JDK 5.0 werten sie jedoch bei der Verwendung der Klassen/Interfaces/Methoden/Konstruktoren durch anderen Code aus. Dadurch sind sie in der Lage, auf die Verwendung richtiger Typen zu prüfen und die gegebenenfalls benötigten Type Casts zu erzeugen. ◇ Die Tatsache, dass der Compiler für generische Klassen/Interfaces deren jeweiligen Raw Type erzeugt, erklärt auch, warum für alle erzeugbaren parameterisierten Typen immer nur eine Klasse in der JVM existiert. Analoges gilt für generische Methoden/Konstruktoren. Unabhängig von den bei ihrem Aufruf verwendeten aktuellen Typ-Parametern ist jeweils nur eine einzige Version des Methoden-/Konstruktor-Codes in der JVM enthalten. • Grenzen in der Anwendung von Generics ◇ Die Art der Implementierung von Generics (Reduzierung auf Raw Types und "Raw Functions" durch Type Erasure) bedingt, dass mit Typ-Variablen nicht in jeder Hinsicht genauso wie mit konkreten Datentypen umgegangen werden kann. ◇ Typ-Argumente müssen immer Referenztypen sein. Statt einfacher Datentypen müssen daher ihre Wrapperklassen als Typ-Argumente eingesetzt werden. Im übrigen ermöglicht Autoboxing, dass einfache Datentypen wie Referenztypen (Wrapper-Klassen) verwendet werden können. ◇ Typ-Variable dürfen nicht für/in statische Klassen- bzw Interface-Komponenten verwendet werden (s. oben) ◇ Der instanceof-Operator darf nicht verwendet werden für - Typ-Variable - Parameterisierte Typen Beispiele : Integer i = new Integer(25); if (i instanceof T) // unzulässig (T sei Typ-Variable einer generischen Klasse) // ... ArrayList<Punkt> ali = new ArrayList<Punkt>(); if (ali instanceof ArrayList<Punkt) // unzulässig // ... ◇ Es dürfen keine Konstruktoren von Typ-Variablen aufgerufen werden. Beispiel : class Nogood<T> { private T val; public Nogood() { val = new T(); } } // unzulässig ◇ Es dürfen keine Arrays von Typ-Variablen erzeugt werden Beispiel : class Behaelter<T> { private T[] arr = new T[512]; // ... } // unzulässig