Info B VL 4: Konstruktoren und Vererbung

Werbung
Info B VL 4: Konstruktoren und
Vererbung
Objektorientiere Programmierung in Java
2003
Ute Schmid (Vorlesung)
Elmar Ludwig (Übung)
FB Mathematik/Informatik, Universität Osnabrück
Info B VL 4: Konstruktoren und Vererbung – p.83
Konstruktoren
Spezielle Funktion zur Erzeugung von Objekten
Ursprünglich: in funktionaler Programmierung und
Typtheorie als Symbol zur Erzeugung eines
algebraischen Datentyps
Beispiel in ML:
datatype ’a list = nil | :: of ’a * ’a list
Ausdrücke, die nur Konstruktoren enthalten sind in
Normalform (werden nicht weiter
ausgewertet/reduziert)
Funktionen über algebraischen DTs können pattern
matching verwenden:
fun f(nil) = nil
| f(x::l) = if even(x) then f(l) else x::f(l);
Info B VL 4: Konstruktoren und Vererbung – p.84
Konstruktoren in OO-Sprachen
mit C++ eingeführt
Konstruktor wird von einer Klasse zur Verfügung
gestellt und dient der Initialisierung eines Objekts
da neue Objekte meist über Konstruktoraufruf
erzeugt werden kann Initialisierung nicht vergessen
werden
In C++ und Java: Konstruktornamen gleich
Klassennamen
In C++: Destrukturen als Gegenstück (Freigabe von
Ressourcen)
In Java kaum notwendig wegen Garbage Collector
Info B VL 4: Konstruktoren und Vererbung – p.85
Konstruktor-Definition
Konstruktoren haben keinen Rückgabetyp (auch nicht
void)
Wird für eine Klasse kein Konstruktor definiert, so wird
ein Default Konstruktor (auch “no-arg constructor”)
angelegt
Der Default Konstruktor hat keine Argumente
Alle Konstruktoren haben implizit eine Referenz zum
neu erzeugten Objekt (this) als Argument
Im Konstruktor-Körper werden die Initialisierungen des
this-Objekts vorgenommen.
Beispiel: Initialisierung eines Circle-Objekts
public Circle(double r) { this.r = r; }
...
Circle c = new Circle(2.0);
Info B VL 4: Konstruktoren und Vererbung – p.86
Definition mehrerer Konstruktoren
Möglichkeit, Objekt auf verschiedene Art zu
initialisieren
public Circle() { r = 1.0; }
public Circle(double r) { this.r = r; }
Wie bei Methoden gilt overloading: gleicher Name aber
unterschiedliche Signaturen (Anzahl und Typ der
Argumente)
Ein Konstruktor kann andere Konstruktoren aufrufen:
this() als Konstruktoraufruf; welcher Konstruktor
aktiviert wird, hängt wieder von Anzahl und Typ der
Argumente ab.
Verwendung von this() ist gute Strategie, wenn die
Konstruktoren Teile der Initialisierung gemeinsam
haben
this() darf nur als erste Anweisung in einem
Info B VL 4: Konstruktoren und Vererbung – p.87
Konstruktor vorkommen
Beispiel ‘Circle’
// This is the basic constructor:
// initialize the radius
public Circle(double r) { this.r = r; }
// This constructor uses this() to invoke
// the constructor above
public Circle() { this(1.0); }
Info B VL 4: Konstruktoren und Vererbung – p.88
Default Werte
Lokale Variablen (innerhalb von Methoden definiert)
haben keine Default-Werte
Werden lokale Variablen nicht vor ihrer Verwendung
initialisiert, liefert der Java-Compiler eine
Fehlermeldlung!
(Klassen- und Instanz-) Felder sind automatisch mit
Default-Werten initialisiert
Übliche Deklaration mit Zuweisung eines initialen
Wertes ist ebenfalls möglich
public static final double PI = 3.14159;
public double r = 1.0;
Info B VL 4: Konstruktoren und Vererbung – p.89
Default Werte für Felder
Typ
boolean
char
byte, short, int, long
float, double
reference
Default
false
‘ u0000’
0
0.0
null
Info B VL 4: Konstruktoren und Vererbung – p.90
Initialisierung von Instanz-Feldern
Konstruktoren
Java Compiler erzeugt Initialisierungscode für
Instanz-Felder und fügt sie in Konstruktor(en) ein
Reihenfolge: die im Programm angegebene (Nutzung
von bereits initialisierten Feldern bei der Initialisierung
weiterer Felder möglich)
Wenn ein Konstruktor mit this() Anweisung beginnt,
dann wird in diesen Konstruktor die Initialisierung nicht
eingefügt, sondern in denjenigen Konstruktor, der
durch this() aktiviert wird
ab Java 1.1: Initialisierungsblöcke für Instanz-Felder:
... , die an beliebiger Stelle (an der
Komponenten stehen können) in die Klasse eingefügt
werden können; üblich: direkt nach Feld; benutzt vor
allem für anonyme innere Klassen
Info B VL 4: Konstruktoren und Vererbung – p.91
Beispiel
public class TestClass {
public int len = 10;
public int[] table = new int[len];
public TestClass(){
for (int i = 0; i < len; i++) table[i] = i;
} }
Ist äquivalent zu
public class TestClass {
public int len;
public int[] table;
public TestClass() {
len = 10;
table = new int[len];
for (int i = 0; i < len; i++) table[i] = i;
} }
Info B VL 4: Konstruktoren und Vererbung – p.92
Initialisierung von Klassen-Feldern (1)
Statische Initialisierungs-Blöcke
Klassen-Felder existieren auch wenn kein Objekt
erzeugt wird
Initialisierung vor Konstruktoraufruf notwendig
Java Compiler erzeugt
Klassen-Initialisierungs-Methode (interne, versteckte
Methode clinit ), in der alle Klassen-Felder
initialisiert werden.
Diese Methode wird genau einmal ausgewertet,
nämlich wenn die Klasse das erstemal benutzt
(geladen) wird.
Initialisierung wieder in der im Programm
angegebenen Reihenfolge
Info B VL 4: Konstruktoren und Vererbung – p.93
Init. von Klassen-Feldern (2)
Explizite Initialisierung von Klassen-Feldern mit static
... , der an jeder
initializer Block: static
Stelle der Klasse stehen kann, wo Komponenten
stehen können
Es kann mehrere solche Initialisierungs-Blöcke geben
Initialisierungs-Blöcke werden vom Compiler in die
Klassen-Initialisierungs-Methode integriert
Statische Initialisierung ist wie eine Klassen-Methode,
also keine Verwendung von this möglich, keine
Nutzung von Instanz-Komponenten möglich
Info B VL 4: Konstruktoren und Vererbung – p.94
Beispiel
// We can draw the outline of a circle using trigonometric functions
// Trigonometry is slow, though, so we precompute a bunch of values
public class TrigCircle {
// Here are our static lookup tables and their own simple initializers
private static final int NUMPTS = 500;
private static double sines[] = new double[NUMPTS];
private static double cosines[] = new double[NUMPTS];
// Here’s a static initializer that fills in the arrays
static {
double x = 0.0;
double delta_x = (Circle.PI/2)/(NUMPTS-1);
for (int i = 0; i < NUMPTS; i++, x += delta_x) {
sines[i] = Math.sin(x);
cosines[i] = Math.cos(x);
} } }
Info B VL 4: Konstruktoren und Vererbung – p.95
Garbage Collection (1)
Mit new werden neue Objekte erzeugt (heap)
Wenn ein Objekt nicht länger benutzt wird, wird der
Speicherplatz automatisch freigegeben (garbage
collection)
Der Java Interpreter weiss, welche Objekte und Arrays
er angelegt (allocated) hat, und kann ermitteln, welche
Objekte und lokale Variablen auf andere Objekte
verweisen.
Wenn kein Verweis auf ein Objekt existiert, kann es
zerstört werden; dito für nicht mehr referenzierte
Verweis-Zyklen
Info B VL 4: Konstruktoren und Vererbung – p.96
Garbage Collection (2)
Garbage Collector läuft immer im Hintergrund als low
priority thread; wird im Normalfall immer aktiv, wenn
nichts Wichtiges passiert (z.B. beim Warten auf Input),
ausser: wenn kaum noch freier Speicher vorhanden ist
Garbage Collection kann nie so effizient sein wie gute
selbstgeschriebene Speicherverwaltung (free(),
delete); aber es verhindert Fehler (z.B. memory
leaks) und erlaubt schnellere Entwicklung von Code.
Info B VL 4: Konstruktoren und Vererbung – p.97
Finalization
Freigabe von bestimmten Resourcen, die ein Objekt
benutzt, wird nicht vom Garbage Collector erledigt
(z.B. temporäre Dateien löschen)
Finalizer ist Instanz-Methode, Gegenstück zu
Konstruktor (“Destruktor”); wird vom Garbage Collector
aufgerufen; keine Argumente, kein Rückgabewert
Es darf nur einen Finalizer pro Klasse geben.
protected void finalize()
Selbstgeschriebene Klassen benötigen selten explizite
Finalizer (Ausnahme native finalize für
Schnittstellen zu Code, der nicht unter Kontrolle des
Garbage Collectors ist)
Info B VL 4: Konstruktoren und Vererbung – p.98
Exkurs: Semantische Netze
Animal
has skin
can move around
eats
breathes
has wings
can fly
has feathers
Bird
can sing
Canary
Ostrich
is yellow
has thin long legs
is tall
can’t fly
Fish
has fins
can swim
has gills
can bite
Shark
is pink
Salmon
is edible
is dangerous
swims upriver
to lay eggs
Info B VL 4: Konstruktoren und Vererbung – p.99
Kognitive Ökonomie
Objekte sind hierarchisch organisiert
Eigenschaften werden nur einmal gespeichert und
vererbt
Psychologische Experimente: Antwortzeiten (Collins &
Quillian)
Prolog: flache, logische Repräsentation, Vererbung via
expliziert Transitivitätsregel
“natürlicher”: OO-Sprache, Ober-/Unterklassen werden
nur je einmal angegeben
Info B VL 4: Konstruktoren und Vererbung – p.100
Prolog
/* Fakten */
isa(canary, bird).
isa(ostrich, bird).
isa(bird, animal).
isa(shark, fish).
isa(salmon, fish).
isa(fish, animal).
has(skin, animal).
does(eat, animal).
...
/* Inferenzregeln */
is_a(A,B) :- isa(A,B).
/* direkter Fall */
is_a(A,C) :- isa(A,B), is_a(B,C). /* Transitivitaet */
/* analog fuer has, does, ... */
Info B VL 4: Konstruktoren und Vererbung – p.101
Java
class Animal {
boolean hasSkin = true;
boolean canEat = true;
}
class Bird extends Animal {
boolean hasWings = true;
boolean canFly = true; // default,
// gilt nicht fuer alle Voegel
}
class Ostrich extends Bird {
Ostrich() { canFly = false; }
}
Info B VL 4: Konstruktoren und Vererbung – p.102
Erweiterung von ‘Circle’
Beispielcode: PlaneCircle.java
Circle
PI
r
+ radiansToDegrees
+ area
+ circumference
PlaneCircle
cx
cy
+ isInside
Info B VL 4: Konstruktoren und Vererbung – p.103
Erweiterung einer Klasse
Name
extends
SName
class
Funktionale Erweiterung von Klassen durch
Unterklassenbildung ist zentral für objekt-orientierte
Programmierung
{ ... }
Felder und Methoden der Oberklasse werden
automatisch vererbt, Konstruktoren nicht!
Unterklassen-Konstruktor kann Konstruktor der
Oberklasse durch super() aufrufen (analog zu
this())
Weitere Felder und Methoden können für die
Unterklasse definiert werden.
Info B VL 4: Konstruktoren und Vererbung – p.104
Typkonversion
Typkonversion zwischen Unter- und Oberklassen:
von Unterklasse zu Oberklasse (upcasting), Objekt
wird allgemeiner (verliert Zugriff auf spezielle Felder
und Methoden) ohne Casting;
von Oberklasse zu Unterklasse (downcasting): Casting
notwendig (und Prüfung zur Laufzeit durch die VM)
(vergleiche widening und narrowing bei primitiven
Datentypen)
PlaneCircle pc = new PlaneCircle(2.0, 5.0, 5.0);
double ratio = pc.circumference() / pc.area();
Circle c = pc; // no access to positioning
PlaneCircle pc2 = (PlaneCircle) c;
boolean oins = ((PlaneCircle) c).isInside(0.0, 0.0);
Info B VL 4: Konstruktoren und Vererbung – p.105
Kapslung
Wichtige objekt-orientierte Technik: Information-Hiding,
Encapsulation:
Daten nur über Methoden zugänglich machen
Daten und interne (private) Methoden sind sicher in
der Klasse eingeschlossen und können nur von
vertrauenswürdigen Nutzern (also über ordentlich
definierte öffentliche Methoden der Klasse) benutzt
werden.
Schutz der Klasse gegen absichtliche oder
unabsichtliche Eingriffe
Verstecken interner Implementations-Details. Ändern
der Implementation, ohne dass genutzter Code dieser
Klasse betroffen ist.
Übersichtlichkeit durch kleine Menge öffentlicher
Information
Info B VL 4: Konstruktoren und Vererbung – p.106
Zugriffskontrolle
Paket-Zugriff: Nicht Teil von Java selbst (Lesbarkeit
von Dateien, Verzeichnissen)
Klassen-Zugriff: Default ist, dass top-level Klassen
paket-weit zugreifbar sind. public deklarierte Klassen
sind überall (wo das Paket zugreifbar ist) zugreifbar.
Zugriff auf Komponenten einer Klasse: Komponenten
sind in jedem Fall in der Klasse selbst zugreifbar;
Default: paket-weiter Zugriff;
Alle Klassen, die zum selben Paket gehören, dürfen
zugreifen. Bei unbenanntem Paket typischerweise alle
Klassen im selben Verzeichnis
(implementationsabhängig).
Zugriffsmodifikatoren: public, protected, private
für Felder und Methoden.
Info B VL 4: Konstruktoren und Vererbung – p.107
Zugriffsmodifikatoren
Für Klassen-Komponenten:
public: von überall (wo Paket zugreifbar ist)
zugreifbar
protected: paket-weit und aus allen Unterklassen
(egal, in welchem Paket sie definiert sind) zugreifbar
default: paket-weit zugreifbar (wenn kein Modifikator
angegeben)
private: nur in der Klasse selbst zugreifbar
Accessible to
public
protected
‘package’
private
Defining class
yes
yes
yes
yes
Class in same package
yes
yes
yes
no
Subclass in different package
yes
yes
no
no
Non-subclass in different package
yes
no
no
no
Info B VL 4: Konstruktoren und Vererbung – p.108
Zugriff und Vererbung
Unterklasse erbt alle Instanz-Felder und -Methoden
der Oberklasse. Manche Komponenten sind aufgrund
der Einschränkung der Sichtbarkeit nicht zugreifbar.
Wenn Ober- und Unterklasse im selben Paket: Zugriff
auf alle nicht-privaten Felder und Methoden.
Wenn Ober- und Unterklasse in verschiedenen
Paketen: Zugriff auf alle public und protected
Felder und Methoden.
private Komponenten und Konstruktoren können nie
ausserhalb der Klasse zugegriffen werden.
Subtile Probleme: wie verhält sich protected, wenn
Klasse A und Unterklasse B in verschiedenen Paketen
stehen und Klasse B in Klasse A genutzt wird? Wie
spielen Casting und Zugriffsmodifikatoren zusammen?
Info B VL 4: Konstruktoren und Vererbung – p.109
Beispiel
Circle mit protected r.
PlaneCircle (ist Unterklasse) in anderem Paket.
Methode in PlaneCircle:
public boolean isBigger (Circle c) {
return (this.r > c.r);
}
Compiler-Fehler: Zugriff auf this.r ist erlaubt, da das
Feld r von der Unterklasse PlaneCircle geerbt wird.
Aber: Zugriff auf c.r ist in PlaneCircle nicht erlaubt!
Wäre erlaubt, wenn PlaneCircle c anstelle von
Circle c, oder wenn Circle und PlaneCircle im
selben Paket definiert wären
Info B VL 4: Konstruktoren und Vererbung – p.110
Daumenregeln
Benutze public nur für Methoden und Konstanten,
die den öffentlichen Teil des API der Klasse darstellen
sollen.
Kapsle Felder: als privat deklarieren und Zugriff über
public Methoden
Benutze protected für Komponenten, die bei der
Erzeugung von Unterklassen notwendig sein könnten.
Achtung: Änderung von protected Komponenten
kann im Code der Klasse Inkonsistenzen erzeugen.
Benutze default Sichtbarkeit für Komponenten, die
interne Implementation realisieren und von Klassen im
selben Paket genutzt werden sollen.
Nutzung der package Anweisung, um miteinander
kooperierende Klassen in ein Paket zu bündeln.
Sonst benutze private.
Info B VL 4: Konstruktoren und Vererbung – p.111
Zugrifffsmethoden
Beispielprogramm: myshapes.Circle
Klasse ist einem Paket zugeordnet (Verzeichnisname
gleich dem Paketnamen).
Methoden sind um Fehlerprüfung erweitert (explizite
checkXX() Methoden).
Zugriff auf Werte von Instanz-Feldern erfolgt über
Methoden (setXX(), getXX()).
Felder, auf die mit set- und get-Methoden zugegriffen
wird, werden auch Properties genannt. Man spricht
von “Getter”- und “Setter”-Methoden.
Info B VL 4: Konstruktoren und Vererbung – p.112
Herunterladen