Info B VL 5: Klassenabhängigkeiten Objektorientiere Programmierung in Java 2003 Ute Schmid (Vorlesung) Elmar Ludwig (Übung) FB Mathematik/Informatik, Universität Osnabrück Info B VL 5: Klassenabhängigkeiten – p.113 Klassenhierarchie Jede Klasse, die definiert wird, hat eine Oberklasse. Wenn keine Oberklasse (spezifiziert nach extends) angegeben wird, dann wird als Oberklasse java.lang.Object vergeben. Als final deklarierte Klassen können nicht erweitert werden. z.B. java.lang.System Verhindern ungewünschter Erweiterungen (Compiler kann einige Optimierungen vornehmen) Info B VL 5: Klassenabhängigkeiten – p.114 Klasse ‘Object’ Wenn keine Oberklasse (spezifiziert nach extends) angegeben wird, dann wird als Oberklasse java.lang.Object vergeben. Object ist eine Klasse mit speziellem Status: einzige Klasse, die keine Superklasse hat; alle Java Klassen erben die Methoden von Object. Vorteile einer solchen “single rooted hierarchy”: Jedes Objekt ist garantiert vom Typ Object. Klassen/Methoden, die auf allen Objekten arbeiten Für alle Objekte ist via Object garantiert, dass sie sinnvoll mit dem Laufzeit-System interagieren (z.B. Garbage Collection) Info B VL 5: Klassenabhängigkeiten – p.115 Klassen-Baum In Java kann eine Klasse nur genau eine andere Klasse als Oberklasse haben. Im Gegensatz zu C++ gibt es keine Mehrfachvererbung Problem bei Mehrfachvererbung: Existiert dieselbe Komponente in mehr als einer Klasse, von der geerbt wird, muss geregelt werden, welche dieser Komponenten in die Unterklasse übernommen wird! Beispiel: java.lang.Object und einige Unterklassen Info B VL 5: Klassenabhängigkeiten – p.116 ‘java.lang’ java.lang Boolean 1995-7, Charles L. Perkins http://rendezvous.com/java Character FDBigInt local to package Class Cloneable ClassLoader Compiler Serializable Byte Double java.io-objects Math Float Number Integer Process Long Object Runtime Short FloatingDecimal SecurityManager local to package String NullSecurityManager StringBuffer local to package System Runnable Thread ThreadDeath ThreadGroup Error java.lang-errors Throwable Exception Void java.lang-exceptions Info B VL 5: Klassenabhängigkeiten – p.117 Vererbung und Konstruktoren (1) Erinnerung: PlaneCircle extends Circle // A new constructor method to initialize the new fields public PlaneCircle(double r, double x, double y) { super(r); // Invoke constructor of the superclass this.cx = x; // Initialize instance fields this.cy = y; } Info B VL 5: Klassenabhängigkeiten – p.118 Vererbung und Konstruktoren (2) super(): Aufruf eines Oberklassen-Konstruktors (analog zu this(): Aufruf eines Klassen-Konstruktors) super() kann nur innerhalb einer Konstruktor-Methode benutzt werden Aufruf des Oberklassen-Konstruktors muss an erster Stelle in einem Konstruktor stehen (sogar vor der Deklaration lokaler Variablen) Argumente, die an super() übergeben werden, müssen mit der Signatur eines Oberklassen-Konstruktors übereinstimmen. Der entsprechende Konstruktor wird dann ausgewählt. Info B VL 5: Klassenabhängigkeiten – p.119 Default-Konstruktoren (1) Java garantiert, dass immer, wenn eine Instanz einer Klasse erzeugt wird, eine Konstruktormethode der Klasse aufgerufen wird. Wenn die Klasse eine Unterklasse ist, ist ebenfalls garantiert, dass der Konstruktor der Oberklasse aufgerufen wird. Java muss sicherstellen, dass jede Konstruktormethode eine Konstruktormethode der Oberklasse aufruft. Wenn kein expliziert Aufruf angegeben ist, wird der Aufruf des Default Konstruktors super() eingefügt. Achtung: Wenn die Oberklasse keinen null-stelligen Konstruktor anbietet, erfolgt ein Compiler-Fehler! Info B VL 5: Klassenabhängigkeiten – p.120 Default-Konstruktoren (2) Wenn eine Klasse gar keinen Konstruktor definiert, wird ein null-stelliger Default-Konstruktor erzeugt: public Klassen erhalten public Konstruktoren; alle anderen Klassen erhalten den default Konstruktor ohne Sichtbarkeits-Modifikator. Um zu verhindern, dass ein public Konstruktor eingefügt wird, sollte mindestens ein nicht-public Konstruktor definiert werden. Für Klassen, die nicht instantiiert werden sollen, sollte ein private Konstruktor definiert werden (kann nicht von ausserhalb der Klasse aufgerufen werden, verhindert automatische Einführung eines default-Konstruktors.) (später: abstrakte Klassen) Info B VL 5: Klassenabhängigkeiten – p.121 Konstruktor-Verkettung (constructor chaining) Wenn eine neue Instanz eines Objekts zur Klasse erzeugt wird, wird der entsprechende Konstruktor aufgerufen. Dieser ruft explizit oder implizt den auf Konstruktor der unmittelbaren Oberklasse usw., solange bis der Konstruktor der Klasse Object aufgerufen wird. Die Ausführung ist in umgekehrter Reihenfolge zum Aufruf, also zuerst wird Object() ausgeführt. Wann immer der Körper eines Konstruktors ausgeführt wird, ist sichergestellt, dass alle Felder der Oberklasse bereits initialisiert sind! Anmerkung finalizer chaining: finalize() Methoden werden nicht automatisch verkettet. super.finalize() Info B VL 5: Klassenabhängigkeiten – p.122 Shadowing und Overriding Felder gleichen Namens und Methoden mit gleicher Signatur können in der Unterklasse “neu” definiert werden. Klassen- und Instanzfelder, sowie Klassenmethoden werden dabei überdeckt (shadowing) Instanzmethoden werden überschrieben (overriding) Info B VL 5: Klassenabhängigkeiten – p.123 Verdecken von Feldern Beispiel: Weiteres Instanz-Feld in Klasse PlaneCircle, das die Distanz zwischen dem Kreismittelpunkt und dem Ursprung (0, 0) angibt, das r genannt wird, also dieselbe Bezeichnung hat, wie das Feld r (für Radius) in der Oberklasse Circle. class PlaneCircle extends Circle { public double r; ... PlaneCircle(...) { // Pythagorean Theorem this.r = Math.sqrt(cx * cx + cy * cy); } } Info B VL 5: Klassenabhängigkeiten – p.124 Zugriff auf verdecktes Feld r this.r super.r // Feld der aktuellen Klasse // dito // Feld der Oberklasse Alternativ: Cast des Objekts zur entsprechenden Oberklasse. ((Circle) this).r Klammerung beachten: erst Cast, dann Zugriff auf Feld! Info B VL 5: Klassenabhängigkeiten – p.125 Zugriff via Casting in Klasse C: A +x B +x x this.x super.x ((B)this).x ((A)this).x super.super.x // // // // // // Field x dito Field x dito Field x Illegal in class C in class B in class A Syntax Bei Instanz von C: C +x C c = new C(); c.x // Field x of class C ((B)c).x // Field x of class B ((A)c).x // Field x of class A Info B VL 5: Klassenabhängigkeiten – p.126 Überdecken von Klassen-F. analog zu Instanzfeldern Beispiel: PI mit höherer Genauigkeit class PlaneCircle extends Circle { public static final double PI = 3.14159265358979323846; } Innerhalb von PlaneCircle: PI PlaneCircle.PI super.PI Circle.PI // // // // Field PI in class PlaneCircle dito Field PI in class Circle dito Ausserhalb: qualifizierter Name Info B VL 5: Klassenabhängigkeiten – p.127 Überdecken von K.-Methoden Klassen-Methoden werden nicht überschrieben sondern nur überdeckt. Klasse . methode (), OberKlasse . methode () haben in jedem Fall verschiedene Namen Info B VL 5: Klassenabhängigkeiten – p.128 Überschreiben von Methoden Wenn in einer Klasse eine Instanz-Methode definiert wird, die dieselbe Signatur (Name, Parameter) hat wie eine Methode der Oberklasse, wird die Methode der Oberklasse überschrieben: Wenn die Methode bei einem Objekt der Klasse aufgerufen wird, dann wird die neue Methodendefinition aktiviert. Überschreiben von Methoden ist eine wichtige Technik der objekt-orientierten Programmierung. Etwas konstruierte Erweiterung des Circle-Beispiels: Ellipse als Unterklasse von Circle mit neuer Definition von area() und circumference(). Es ist wichtig, dass für ein Objekt der Klasse Ellipse immer die neuen Methoden zur Berechnung verwendet werden! Info B VL 5: Klassenabhängigkeiten – p.129 Feldreferenz in vererbten Methoden Wird eine Methode vererbt und existieren die in ihr verwendeten Felder auch in der Unterklasse, so werden weiterhin die Felder in der Oberklasse referenziert! class A { int x = 1; int y = 2; int f() { return x+y; } } class B extends A { int x = 21; int y = 22; } public class OverShad { public static void main (String[] args) { B myB = new B(); A myA = new A(); System.out.println(myB.f()); System.out.println(myA.f()); } } Info B VL 5: Klassenabhängigkeiten – p.130 Dynamic Method Lookup Woher weiss der Compiler, ob er die Methode zur Oberklasse A oder zur Unterklasse B aufrufen soll, wenn beispielsweise ein Array von A Objekten definiert wurde, in dem manche Objekte zur Klasse B gehören können? Kann er nicht wissen: Code, der dynamisches Method Lookup zur Laufzeit benutzt: Interpreter prüft Typ eines Objektes und ruft die entsprechende Methode auf. auch als Virtual Method Invocation bezeichnet (in C++) Info B VL 5: Klassenabhängigkeiten – p.131 Statisches Method Lookup (1) Schneller, wenn kein dynamic method lookup zur Laufzeit benötigt wird. Wenn eine Methode mit dem final Modifikator deklariert ist, heißt das, dass die Methode nicht von einer Unterklassen-Methode überschrieben werden darf. Der Compiler weiss bereits, welche Version der Methode gemeint ist, und dynamisches Lookup ist damit unnötig. Info B VL 5: Klassenabhängigkeiten – p.132 Statisches Method Lookup (2) Für bestimmte Methoden kann das Java-Laufzeitsystem dynamic method lookup vermeiden: Alle Methoden einer final deklarierten Klasse sind final: also ist bekannt, für welche Klasse der Aufruf erfolgt. Alle private Methoden können generell nur in der Klasse selbst aufgerufen werden: damit ist ebenfalls bekannnt, für welche Klasse der Aufruf erfolgt. private Methoden sind implizit final und können nicht überschrieben werden. static (Klassen)-Methoden werden generell nicht überschrieben (sondern überdeckt). Info B VL 5: Klassenabhängigkeiten – p.133 Aufruf überschriebener Methoden (1) Aufruf überschriebener Methoden ist syntaktisch ähnlich zu Zugriff auf überdeckte Felder: super. methode () Aufruf einer überschriebenen Methode kann nicht mit Casting (((A)this).f()) realisiert werden! Modifizierte Form von dynamischem Method Lookup bei super: Gehe zur direkten Oberklasse derjenigen Klasse, innerhalb derer super aufgerufen wird. Wenn die Methode dort definiert ist, verwende sie, ansonsten gehe zur direkten Oberklasse dieser Klasse etc. super spricht die Methode an, die unmittelbar überschrieben wurde. Info B VL 5: Klassenabhängigkeiten – p.134 Aufruf überschriebener Methoden (2) super bezieht sich immer auf die unmittelbare Oberklasse der Klasse, in der der Aufruf steht. Beispiel: super.f() in OverrideTest bezieht sich auf die Klasse Object! Überdeckte Klassen-Methoden können ebenfalls durch super angesprochen werden. (Hier erfolgt generell kein dynamic lookup.) class A { // Define a class named A int i = 1; // An instance field int f() { return i; } // An instance method static char g() { return ’A’; } // A class method } class B extends A { // Define a subclass of A int i; // Shadows field i in class A int f() { // Overrides instance method f in A i = super.i + 1; // It can retrieve A.i like this return super.f() + i; // It can invoke A.f() like this }} Info B VL 5: Klassenabhängigkeiten – p.135 Overloading Nicht verwechseln mit overriding! Überladen (von Operatoren bzw. Methoden): verwenden des selben Namens mit verschiedenen Signaturen (Typen) auch “ad hoc Polymorphismus” Info B VL 5: Klassenabhängigkeiten – p.136 Operator-Overloading Beispiel: monadisches und diadisches -; + für Integers, Floats, Strings Java erlaubt dem Benutzer kein Überladen von primitiven Operatoren, aber Überladen von Methoden (und Konstruktoren). Vorteil von Overloading: Bedeutung eines Symbols im Kontext spart die Einführung zusätzlicher Symbole. Beispiel: Definition von für komplexe Zahlen. Vergleiche natürliche Sprache (das vermisste Buch finden, sein Glück finden). Nachteil von Overloading: Es ist nicht mehr ohneweiteres nachvollziehbar, was ein Operator wirklich tut (unklare Semantik), wenn primitive Operatoren vom Benutzer überladen werden dürfen (Kritik an C++). Info B VL 5: Klassenabhängigkeiten – p.137 Beispiel: a b Überladenes ‘+’ Intuitiv, wenn a und b vom gleichen primitiven numerischen Typ sind: int int, float float float. int Verschiedene Optionen für char char: liefert den String aus den beiden Zeichen, liefert die Summe der Ordnungszahl der Zeichen, ... Häufig sinnvoll: wenn verschiedene numerische Typen beteiligt sind, wird zunächst auf den “größeren” Typ float float. ge-castet: int Was tun bei char Bit)? short? (in Java haben beide 16 Eventuell Verlust der Kommutativität: Vorrang des short einen ersten Arguments, also könnte char char. anderen Ergebnistyp liefern als short Was ist die Intuition für Stack Info B VL 5: Klassenabhängigkeiten – p.138 Vector? Operator-Overloading in Java Arithmetische Operatoren und Vergleichsoperatoren sind für numerische Typen definiert (alle primitiven Typen ausser boolean) Es ist zulässig, dass ein Operator Argumente verschiedenen Typs miteinander verknüpft. Dabei erfolgt ein implizites Casting zu dem größeren Typ, mindestens zu int (widening, siehe Tabelle). Rückgabetyp bei arithmetischen Operatoren: double wenn mindestens ein Argument double, float wenn mindestens ein Argument float, long wenn mindestens ein Argument long, int sonst (auch, wenn beide Argumente byte, short oder char sind). Dabei werden zuerst die impliziten Casts auf den Operanden durchgeführt und dann der Operator angewendet. Info B VL 5: Klassenabhängigkeiten – p.139 ‘+’ für ‘String’ Der Operator + (und +=) ist zusätzlich für String-Objekte definiert. Wenn mindestens eines der Argumente String ist, wird das andere Argument zu String konvertiert. Klammern sind häufig notwendig: System.out.println("Total: " + 3 + 4); // Total: 34 nicht 7 (Klammern (3 + 4): erst Addition auf Zahlen) Für String Object wird Object in String umgewandelt, indem die toString()-Methode des Objekts angewendet wird. Achtung: Die für Object definierte toString() Methode liefert die Referenz-Adresse des Objekts als String. Falls andere Information gewünscht wird, muss diese Methode überschrieben werden. Info B VL 5: Klassenabhängigkeiten – p.140 Method-Overloading In Java musste Method-Overloading zugelassen werden, da es möglich sein sollte, mehr als einen Konstruktor für eine Klasse zu definieren. Überladene Methoden müssen sich eindeutig durch ihre Signatur unterscheiden: Anzahl, Reihenfolge und Typ der Argumente. Eine blosse Unterscheidung durch verschiedene Rückgabe-Typen ist unzulässig: void f() {}; int f() {}; Könnte nur klappen, wenn der Compiler eindeutig aus dem Kontext bestimmen kann, welche Methode gemeint ist, z.B. int x = f(); Auch bei Methoden mit unterschiedlichen Signaturen kann es Probleme geben, die “gemeinte” Methode zu identifizieren (Compiler-Fehler) Info B VL 5: Klassenabhängigkeiten – p.141 Beispiel public class Overloading { public static String f(String s, Object o) { return s + o; } public static Object f(Object o, String s) { return (Object) (o + s); } public static void main(String[] args) { System.out.println(f("Die Zahl ist ", (Object)"17")); System.out.println(f((Object)"17", " ist eine Zahl")); // System.out.println(f("Hello", "World")); // ambiguous!!! } } Info B VL 5: Klassenabhängigkeiten – p.142 Polymorphismus (1) In der objekt-orientierten Programmierung meint Polymorphismus, dass Variablen deklariert werden können, die zur Laufzeit auf Objekte verschiedener Klassen verweisen können. Vorteil: Eine Methode setColor() kann für beliebige Shape-Objekte definiert werden. Egal, ob die Methode zur Laufzeit zu einem Circle- oder einem Rectangle-Objekt gehört, sie kann auf jeden Fall angewendet werden. Dadurch wird Duplizierung von Code vermieden! Keine Kenntnis nötig, welche Unterklassen von Shape konkret existieren. Info B VL 5: Klassenabhängigkeiten – p.143 ‘Shapes’ Shape Shape s; s.setColor(); Upcasting + setColor Circle Rectangle Triangle + area + circumference + diameter + area + circumference + isSquare + area + circumference + ... Info B VL 5: Klassenabhängigkeiten – p.144 Polymorphismus (2) Wird eine Methode f() in der Oberklasse definiert, so kann sie in der Unterklasse überschrieben werden. Für ein Shape-Objekt, das ein Circle ist, wird dann seine spezifische Methode verwendet. Polymorphismus kann nur zusammen mit dynamic binding (auch late binding) realisiert werden. Info B VL 5: Klassenabhängigkeiten – p.145