Info B VL 5: Klassenabhängigkeiten

Werbung
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
Herunterladen