ein Bezeichner mit mehreren Bedeutungen
poly-morph =
einfaches Überladen von Bezeichnern
Typparameter für generische Klassen und Methoden
Auswahl der Methoden-Implementierung
durch Laufzzeittyp des Objektes
Motivation: Objekt =
Einfachste Implementierung:
Anwendung: Datei-Objekte in UNIX (seit 1970)
(Merksatz 1: all the world is a file) (Merksatz 2:
those who do not know UNIX are doomed
to re-invent it, poorly)
(d. h. objektorientiert, aber ohne Klassen)
Objekte, Attribute, Methoden:
Übung: Überschreiben
gemeinsame Datenform und Verhalten von Objekten
allgemein: Klasse:
Motivation: Methode soll wissen,
für welches Argument sie gerufen wurde
(in Java, C# geschieht das implizit)
Def: Klasse D
Anwendung: dynamische Polymorphie
⇒
http://existentialtype.wordpress.com/2011/03/15/teaching-fp-to-freshmen/
zwei Klassen, je eine Methode mit gleichem Typ
eine Klasse, mehrere Methoden mit versch. Typen
Quelle von Programmierfehlern
...sondern so:
Sie muß deswegen überschrieben werden.
Das
für diese findet kein dynmischer Dispatch statt.
(Beispiele--Puzzle 48, 54)
Damit das klar ist, wird dieser Schreibstil empfohlen:
Durch
Bsp.
Relation ≤2
(t1, t2)≤2(t1', t2') : t1≤t1'∧t2≤t2'
es gilt
(D, D)≤2(C, C);(D, D)≤2(C, D);(C, D)≤2(C, C);(E, C)≤2(C, C)
Auflösung von
(für diese gilt: statischer Typ der Argumente ≤n
(Def: m
(z. B.
(
Warum geht das nicht:
Antwort: wenn das erlaubt wäre, dann:
C#:
als Argument ist jeder Typ T
Als Argument ist jeder Typ S
Kovarianz (
Unterscheidung: Schranken/Varianz:
bei Schranken geht es um die Instantiierung (Wahl der Typargument)
bei Varianz um den erzeugten Typ (seine Zuweisungskompatibilität)
das gibt keinen Typfehler:
warum ist die Typprüfung für Arrays schwächer als
für Collections?
Historische Gründe. Das sollte gehen:
Das sieht aber mit Generics besser so aus: ...
typedef struct {
int x; int y; // Daten
void (*print) (FILE *fp); // Verhalten
} point;
point *p; ... ; (*(p->print))(stdout);
var o = { a : 3,
m : function (x) { return x + this.a; } };
Vererbung zwischen Objekten:
var p = { __proto__ : o };
Attribut (/Methode) im Objekt nicht gefunden
⇒
p.m = function (x) { return x + 2*this.a }
var q = { __proto__ : p }
q.a = 4
alert (q.m(5))
typedef struct { int (*method[5])(); } cls;
typedef struct {
cls * c;
} obj;
obj *o; ... (*(o->c->method[3]))();
Objekt:
typedef struct { int (*method[5])(obj *o);
} cls;
typedef struct {
int data [3]; // Daten des Objekts
cls *c; // Zeiger auf Klasse
} obj;
obj *o; ... (*(o->c->method[3]))(o);
int sum (obj *this) {
return this->data[0] + this->data[1]; }
jede Methode bekommt this als erstes Argument
class C {
int x = 2; int p () { return this.x + 3; }
}
C x = new C() ; int y = x.p ();
Überschreiben:
class E extends C {
int p () { return this.x + 4; }
}
C x = // statischer Typ: C
new E() ; // dynamischer Typ: E
int y = x.p ();
class C {
void p () { ... q(); ... };
void q () { .. };
}
Jetzt wird q
überschrieben
(evtl. auch unabsichtlich--in Java),
dadurch ändert sich das Verhalten von p
.
class D extends C {
void q () { ... }
}
Korrektheit von D
abhängig von Implementierung von
C
virtual
deklarieren
override
angezeigen,
@overrides
class C {
final int x; final int y;
C (int x, int y) { this.x = x; this.y = y; }
int hashCode () { return this.x + 31 * this.y; }
}
nicht so:
public boolean equals (C that) {
return this.x == that.x && this.y == that.y;
}
public boolean equals (Object o) {
if (! (o instanceof C)) return false;
C that = (C) o;
return this.x == that.x && this.y == that.y;
}
Die Methode boolean equals(Object o)
wird aus HashSet aufgerufen.
boolean equals (C that)
hat
den Methodenamen nur überladen.
this
lautet,
extends/implements
entsteht eine Halbordnung auf Typen
class C; class D extends C; class E extends C
definiert Relation
(≤) = {(C, C),(D, C),(D, D),(E, C),(E, E)}
p (new D(), new D())
bzgl.
static void p (C x, D y);
static void p (C x, C y);
static void p (E x, C y);
interface I { void P (); }
static void Q (IList<I> xs)
{ foreach (I x in xs) { x.P(); } }
static void R<C> (Action<C> S, IList<C> xs)
{ foreach (C x in xs) { S(x); } }
für gleichzeitige Behandlung mehrerer Objekte
ist Vererbungspolymorphie meist ungeeignet
Object.equals(Object o)
falsch,
Comparable<T>.compareTo(T o)
richtig)
E
≤
C
,
dann auch List<E>
≤
List<C>
List<E>
≤
List<in C>
,
List<C>
≤
List<out C>
)
class C { }
class E extends C { void m () { } }
List<E> x = new LinkedList<E>();
List<C> y = x; // Typfehler
class<T extends S> { ... }
,
class <T> where T : S { ... }
interface Comparable<T>
{ int compareTo(T x); }
static <T extends Comparable<T>>
T max (Collection<T> c) { .. }
<S super T>
static <T> int binarySearch
(List<? extends T> list, T key,
Comparator<? super T> c)
List<? extends Number> z =
Arrays.asList(new Double[]{1.0, 2.0});
z.add(new Double(3.0));
in P
), Kontravarianz (out P
)
class C {} class E : C {}
interface I<in P> { }
class K<P> : I<P> { }
I<C> x = new K<C>();
I<E> y = x;
class C { }
class E extends C { void m () { } }
E [] x = { new E (), new E () };
C [] y = x;
y [0] = new C ();
x [0].m();
aber ...(Übung)
void fill (Object[] a, Object x) { .. }
String [] a = new String [3];
fill (a, "foo");