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