Vorlesung Informatik 1 Fachhochschule für Technik Esslingen Studiengang Wirtschaftsinformatik Teil 3: Objektorientierung Dr. rer. nat. Andreas Rau http://www.hs-esslingen.de/~rau [email protected] © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #1 Neues Kapitel, neue Arbeitstechnik Im Rahmen der Erarbeitung der Grundlagen haben wir zwar auch Java Klassen verwendet, jedoch nicht in "artgerechter" Haltung! Dies muss sich nun ändern. BISER bestand jedes Programm nur aus einer unabhängigen Datei. AB SOFORT werden Programme aus mehreren abhängigen Datei bestehen. Es macht Sinn, die Dateien eines Programms in einem Verzeichnis zu bündeln. Bisher gab es in jeder Datei eine main()-Methode. AB SOFORT wird es viele Dateien ohne main()-Methode geben. Eine main()-Methode ist nur in der Start-Klasse des Programms notwendig! Bisher haben wir keine Objekte erzeugt und alle Eigenschaften waren static. AB SOFORT wollen wir Objekte erzeugen und static Eigenschaften sind uncool*. Fehlermeldungen suggerieren etwas anderes, aber static ist nicht immer richtig! *die main()-Methode muss natürlich static bleiben © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #2 Grundlagen © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #3 Rückblende: Klassen und Objekte(1) In objektorientierten Sprachen werden Variablen mit Funktionen zum Zugriff und zur Manipulation kombiniert und in Objekten gekapselt. Dadurch wird verbunden, was zusammengehört und verborgen, was nicht für jeden relevant ist. Dies dient der Abstraktion sowie der Wartbarkeit und Wiederverwendbarkeit. Ein Klasse beschreibt die Struktur gleichartiger Objekte (Bauplan, Gußform) und existiert nur einmal. Es kann jedoch viele Objekte zu einer Klasse geben. Die Eigenschaften einer Klasse sind ihre Identität (z.B. Klassenname), ihr Zustand, ihr Verhalten und ggf. ihre Vaterklasse(n). Klassen sind statisch vordefiniert. Ein Objekt hat eine eindeutige Identität (z.B. Referenz bzw. Adresse im Hauptspeicher), einen Zustand (Werte der Variablen), ein Verhalten (Funktionsumfang) und gehört zu einer bestimmten Klasse. Objekte können zur Laufzeit (gemäß Bauplan) dynamisch erzeugt und gelöscht werden. In manchen Programmiersprachen sind Klassen auch (einzigartige) Objekte... © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #4 Wiederholung: Klassen und Objekte(2) © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #5 Eigenschaften von Objekten und Klassen Die Eigenschaften von Klassen und Objekten werden wie folgt bezeichnet Instanzvariablen – Zustand eines konkreten Objekts (für jedes Objekt); N-mal ● Instanzmethoden – Verhalten eines konkreten Objekts ● Klassenvariablen – Zustand der Klasse (gemeinsam für alle Objekte); 1-mal ● Klassenmethoden – Verhalten der Klasse (kein Bezug zu konkretem Objekt ● Die Methoden gehören zur jeweiligen Klasse bzw. zu einem bestimmten Objekt, sind aber anders als die Variablen im Speicher nur einmal vorhanden. Die individuelle Bindung an ein bestimmtes Objekt erfolgt während der Ausführung. Klassen und Objekte werden Ihrerseits zu Modulen (in Java: Pakete) zusammengefasst. Dadurch wird eine weitere Abstraktionsebene zur Beherrschung der Komplexität und zur Arbeitsteilung geschaffen. Zur Durchsetzung des Geheimnisprinzips ist es möglich, den Zugriff auf Klassen, Objekte und deren Bestandteile individuell als öffentlich (public) oder privat (private) zu kennzeichnen. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #6 Aufbau und Bestandteile einer Java Klasse public class BeispielKlasse { Legende static int klassenVariable; static void klassenMethode( int formalerParameter) { // ... } int instanzVariable; int instanzMethode( int formalerParameter) { // ... } public static void main( String[] args) { int lokaleVariable; klassenMethode( lokaleVariable); BeispielKlasse objektReferenz; objektReferenz = new BeispielKlasse(); lokaleVariable = objektReferenz.instanzMethode( lokaleVariable); } reservierte Wörter: Sind durch die jeweilige Programmiersprache vorgegeben (müssen genau so lauten) und dürfen nicht für eigene Bezeichner verwendet werden. eigene Bezeichner: Werden vom Programmierer vergeben (könnten auch anders lauten). } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #7 UML(1): Klassendiagramme Die Unified Modelling Language (UML) ist eine Notation zur grafischen Beschreibung von Software. Zu diesem Zweck kennt UML 13 verschiedene Arten von Diagrammen die man sogar mischen darf. Wir beschränken uns an dieser Stelle auf das Klassendiagramm mit dem die Eigenschaften und Zusammenhänge zwischen Klassen und Objekten darstellen kann. Objekt:Klasse - instanceVariable : int = defaultValue - classVariable : int = defaultValue + ~ # publicMethode(int p1, int p2) : int privateMethode(int p1, int p2) : int packageMethode(int p1, int p2) : int protectedMethode(int p1, int p2) : int Darstellung einer Klasse in UML (der JavaEditor erzeugt das auf Knopfdruck) © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #8 Rückblende: Beziehungen zwischen Objekten Objekte kommunizieren und kooperieren durch Austausch von Nachrichten. Praktisch bedeutet dies gegenseitigen Aufruf von Methoden. Voraussetzung dafür ist, dass sich die Objekte gegenseitig kennen, d.h. eine Beziehung zueinander haben (über Arten von Beziehungen später mehr). Praktisch werden Beziehungen durch Variablen mit Referenzen auf Objekte implementiert. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #9 Arten von Beziehungen zwischen Objekten und Klassen(1) Vererbung (is-a) Semantik: Jede Klasse in Java hat genau eine Superklasse. Implementierung: Deklaration bei der Klasse (extends + Superklasse) Beispiel: ein Auto ist-ein Fahrzeug Klassenzugehörigkeit (instance-of) Semantik: Jedes Objekt ist Instanz genau einer bestimmten Klasse. Implementierung: Festlegung gemäß Erzeugung (new Operator + Klasse) Beispiel: Klaus ist eine Exemplar der Klasse Person Allgemeine Beziehung (knows-of/uses) Semantik: Einfache und ggf. auch flüchtige Beziehung zwischen zwei Objekten. Implementierung: Instanzvariable (dauerhaft) oder Methodenparameter (flüchtig) Beispiel: Kunde kauft Artikel © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #10 Arten von Beziehungen zwischen Objekten und Klassen(2) Komposition (strong whole-part) Semantik: Die Komposition repräsentiert eine starke Teil-Ganzes Beziehung im Sinne von "besteht aus". Die Lebensdauer des Kompositums und seiner Teile sind gekoppelt. Ein Teil kann nur zu einem Kompositum gehören. Implementierung: Ganzes hat Instanzvariablen für Teile Beispiel: Ein Auto besteht aus Motor, Reifen, ... Auto kaputt => Motor kaputt Aggregation (weak whole-part) Semantik: Die Aggregation repräsentiert eine schwache Teil-Ganzes Beziehung im Sinne von "trägt bei zu". Die Lebensdauer des Aggregats und seiner Teile sind nicht gekoppelt. Ein Teil kann in mehr als einem Aggregat enthalten sein. Implementierung: Ganzes hat Instanzvariable für Teile Beispiel: Ein Team besteht aus Mitgliedern. Team wird aufgelöst, Mitglieder nicht © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #11 UML(2): Beziehungen Zur Darstellung von Beziehungen zwischen Klassen kennt UML verschiedene Symbole :Fahrzeug :Auto :FH :Student 0..* fährt mit 0..* :Auto :Reifen :Student :Bus Vererbung Komposition Aggregation Assoziation Bei mehreren Vererbungs-, Kompositions- oder Aggregationsbeziehungen kann man die Linienenden bei der übergeordneten Klasse zusammenfassen; das Netz wird zum Baum. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #12 Definition einer Klasse und Erzeugung von Objekten Eine Klasse kann im einfachsten Fall wie folgt definiert werden. public class Person { String firstName, lastName; } Dieser Bauplan schreibt vor, dass jedes Objekt einen eigenen Vornamen und Nachnamen hat. Die Erzeugung von Objekten erfolgt mit Hilfe des new-Operators. Anschließend kann auf die Eigenschaften des Objekts zugegriffen werden: Person p = new Person(); p.firstName = "Karl"; p.lastName = "Napf" System.out.println( p.firstName + " " + p.lastName); © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #13 Rückblende: Punktoperator(1) An dieser Stelle nochmal ein Blick zurück auf den Punktoperator. Anders als der Array-Zugriffsoperator, der nur mit speziellen Objekten (Arrays) funktioniert, kann der Punktoperator mit beliebigen Objekten oder einer Klasse arbeiten. Das sieht dann verallgemeinert so aus <Wo>.<Was> Das <Wo> gibt an, auf wen zugegriffen wird (bzw. wer die Eigenschaft hat). Dies muss ein Objekt(=Variable) oder eine Klasse(=Klassenname) sein. Mit elementaren Datentypen geht nichts! Auch bei verketteten Ausdrücken gilt, dass links vom Punkt stets ein Objekt oder eine Klasse stehen muss! (System.out).println( "Beispiel"); // System.out = ein Objekt Es gibt jedoch noch eine Sonderregel: Steht links vom Punkt nichts, ist dies gleichbedeutend mit "Ich Objekt" und wenn das nicht funktioniert "Meine Klasse". Das <Was> ist der Name einer Eigenschaft. Dies kann eine Variable oder eine Methode sein. Im letzteren Fall kommen noch der Funktionsaufruf-Operator sowie ggf. Parameter für die Methode ins Spiel. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #14 Rückblende: Punktoperator(2) public class Punktoperator { public static void main(String[] args) { // Hier geht's los setupDemo(); // Aufruf Klassenmethode über "Meine Klasse" } public static void setupDemo() { (new Punktoperator()).runDemo(); // Aufruf neues Objekt(!) } public void runDemo() { Integer i1 = new Integer(7); // neues Objekt int i2 = i1.intValue(); // Aufruf Instanzmethode String s = Integer.toString( i2); // Aufruf Klassenmethode print( s); // Aufruf Instanzmethode via "Ich Objekt" } public print( String text) { System.out.println( text); } } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #15 Rückblende: Punktoperator(3) Erkenntnisse aus dem Beispiel Die Welt der Klassen ist statisch (==static) ● Alle Klassen sind von Anfang an vorhanden und bleiben es auch ● Die Welt der Objekte ist dynamisch (!=static) ● Objekte werden dynamisch erzeugt und gelöscht ● Die Ausführung eines Programms beginnt immer in der Klassenwelt (sonst ist ja am Anfang nichts da). Dazu wurde vereinbart, dass die erste Methode main() heißt (das ist so ähnlich wie die Nelke im Knopfloch bei einem Blind-Date). Um in die Objektwelt zu wechseln, muss zunächst ein Objekt erzeugt werden. Interessanterweise "sieht man aus der Objektwelt die Klassenwelt" umgekehrt jedoch nicht. Das liegt daran, dass es zwar zu jedem Objekt eine eindeutige Klasse gibt, aber u.U. zu jeder Klasse mehrere Objekte. Blind zugreifen und richtig treffen kann man also nur in einer Richtung (von Objekt zu Klasse). © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #16 Kapselung © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #17 Zugriff auf Objekte und Klassen(1) Der Zugriff auf die Eigenschaften von Klassen und Objekten erfolgt mit Hilfe des Zugriffsoperators. Mit seiner Hilfe kann auf Zustandsvariablen zugegriffen werden. Auch Methoden werden auf diese Art und Weise aufgerufen. Dabei ist prinzipiell der Bezug zu einem Objekt bzw. einer Klasse nötig. Dieser wird entweder über eine Objektreferenz oder den Klassennamen hergestellt. Person p = new Person(); p.firstName = "Donald"; p.lastName = "Duck"; System.out.println( Person.RETIREMENT_AGE); Innerhalb einer Klasse, d.h. in deren Klassenmethoden und Instanzmethoden, kann auch ohne einen solchen Bezug auf Zustandsvariablen zugegriffen werden. Der Zugriff gilt dann immer für die aktuelle Klasse bzw. das aktuelle Objekt (wie das im Hintergrund genau funktioniert folgt in Kürze). public String getFullName() { return firstName + " " + lastName; } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #18 Zugriff auf Objekte und Klassen(2) Vor dem Zugriffsoperator muss also stets eine Referenz auf ein Objekt oder ein Klassenname stehen. Dies gilt auch für verkettete Zugriffe wie System.out.println( "Hallo"); Dieser Aufruf kann auch folgendermaßen geschrieben werden (System.out).println( "Hallo"); Das ist natürlich viel zu umständlich und aufgrund der links-Assoziativität des Zugriffsoperators auch nicht notwendig. Es wird jedoch deutlich, daß der Ausdruck aus zwei Zugriffen besteht. Damit der zweite Zugriff funktioniert, muss der erste Zugriff ein Objekt (in diesem Fall einen PrintStream, der in der Klassenvariable out der Klasse System gespeichert ist) zurückliefern. Dies wird bei folgender Schreibweise nochmals verdeutlicht. PrintStream ps = System.out; ps.println( "Hallo"); Auch bei anderen verketteten Ausdrücken müssen alle Zugriffe bis auf den letzten ein Objekt zurückliefern. Dabei kann ein einzelner Zugriff entweder ein Variablenzugriff oder ein Methodenaufruf sein. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #19 Modifizierer(1) Wir kennen bereits zwei Modifizierer für die Bedeutung von Klassenelementen: static Zur Kennzeichnung von Variablen und Methoden, die zur Klasse gehören. final Für konstante Variablen (später auch für "konstante" Klassen und Methoden) Für Klassen und Objekte kommen nun weitere Modifizierer hinzu © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #20 Modifizierer(2) Der Zugriff auf die Eigenschaften einer Klasse und ihrer Objekte kann über sog. Zugriffsmodifizierer reglementiert werden. Deren extremsten Ausprägungen sind public Kein Schutz bzw. Kapselung. Jeder darf auf Eigenschaften zugreifen. private Totaler Schutz bzw. Kapselung. Nur die eigene Klasse darf zugreifen. Später werden wir noch weitere Zugriffsmodifizierer kennenlernen, die eine feinere Einstellung der Zugriffsrechte, v.a. bei der Vererbung, erlauben. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #21 Zugriffsmethoden (Getter und Setter) Ein wichtiges Ziel der Objektorientierung ist, Daten und Funktionen nicht nur zusammenzuführen sondern die Daten in dem Zusammenhang auch zu schützen, d.h. den Zugriff nur noch über Methoden zu erlauben. Um dies zu erreichen kann man die Instanzvariablen als privat kennzeichnen und entsprechende Zugriffsmethoden definieren. Diese können von vielen Entwicklungsumgebungen auch automatisch generiert werden: public class Person { private String firstName, lastName; public String getFirstName() { return firstName; } public void setFirstName( String newFirstName) { firstName = newFirstName; } } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #22 Pakete © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #23 Pakete(1) Klassen fassen Daten und die Methoden zu ihrer Bearbeitung zusammen. Pakete fassen Klassen zusammen, die ähnliche oder gemeinsame Aufgaben haben und eng zusammenarbeiten. Dazu genießen Sie untereinander besondere Zugriffsrechte. Das Paket muss in der Klasse deklariert werden: package mypackage; public class MyClass { // ... } Klassen ohne explizite Paketdeklaration werden dem Default Paket zugeordnet. Dieses wird auch als main-package bezeichnet, hat aber nichts mit der gleichnamigen Methode zu tun sondern entspricht dem aktuellen Verzeichnis. Nachdem Klassen auf Dateien abgebildet werden liegt es nahe, die Paketstruktur auf Verzeichnisse abzubilden. Der Compiler prüft (1) ob der Klassenname zum Dateiname und (2) der Paketname zum Verzeichnis passt. Alle Klassen im selben Verzeichnis sind also im selben Paket. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #24 Pakete(2) Um die Inhalte eines Pakets in einer Klasse bequem nutzen zu können, wird die import-Anweisung verwendet. import java.io.*; // Importieren aller Klassen im Paket java.io import java.util.ArrayList; // Importieren einer einzelnen Klasse Zusätzlich ist es möglich (aber nicht empfehlenswert), direkt im Quellcode über einen qualifizierten Namen auf Paketinhalte zuzugreifen: // Objektdeklaration mit voll qualifiziertem Klassenname java.util.HashMap myMap; Dies ist quasi eine Erweiterung des Zugriffsoperators und immer dann notwendig, wenn zwei oder mehr Pakete identische Bezeichner enthalten (dann ist ein import nicht möglich). Wenn möglich empfielt sich jedoch die Verwendung der import-Anweisung, da auf diese Art und Weise (1) der Quellcode lesbar wird, und (2) sofort klar ist, welche anderen Klassen und Pakete verwendet werden. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #25 Pakete(3) Pakete können wie Verzeichnisse geschachtelt werden. Der vollständige Paketname entspricht dabei einem relativen Pfad ausgehend vom CLASSPATH. Damit kann die virtuelle Maschine Klassen anhand ihres Pakets auf der Festplatte lokalisieren. Da der CLASSPATH mehrere Wurzelverzeichnisse enthalten kann ist es außerdem möglich, den logischen Inhalt eines Pakets auf mehrere physikalische Verzeichnisse zu verteilen, z.B. um Produktklassen und Testklassen zu trennen. Beispiel <root>/ src/ people/ Person.java test/ people/ PersonTest.java © Andreas Rau, 24.03.11 CLASSPATH = "<root>\src;<root>\test" nicht "<root>\src\people; <root>\test\people" D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #26 Pakete(3a) Neben der Zusammenfassung von Klassen ist ein weiterer Zweck von Paketen sicherstellen, dass Klassennamen nicht kollidieren. Um im Gegenzug sicherzustellen, das Paketnamen ihrerseits nicht kollidieren, hat sich eine Konvention entwickelt, die auf "umgedrehten" Domainnamen basiert. Beispiel: package de.fhte.winf1.informatik; public class Person { } wird gespeichert in <Wurzelverzeichnis>\de\fhte\winf1\informatik\Person.java CLASSPATH = "<Wurzelverzeichnis>" © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #27 Ausführung eines Java Programms auf der virtuelle Maschine Aufruf mit "java [-cp <CLASSPATH>] <Startklasse>" Startklasse laden Wenn die Startklasse eine main-Methode hat { Aufruf der Main-Methode Solange Programmende nicht erreicht { Wenn Verwendung einer neuen Klasse { Wenn Verzeichnis gemäß Paketname im CLASSPATH gefunden { Wenn Datei gemäß Klassenname gefunden { Klasse laden } Sonst { Fehlermeldung "Class not found" } } Sonst { Fehlermeldung "Class not found" } } } } Sonst { Fehlermeldung "no such method: main()" } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #28 Pakete(4) Typische Fehler bzw. Fallstricke beim Umgang mit Paketen Der CLASSPATH bzw. die entsprechende Editoreinstellung sind falsch Der CLASSPATH zeigt auf das Verzeichnis, welches das Unterverzeichnis mit der Paketstruktur enthält, nicht auf die Verzeichnisse mit den Paketen selbst. Bei der Deklaration wird nur der eigentliche Paketname angegeben Es muss der Name des Pakets inkl. aller Vaterpakete angegeben werden Beim Import wird nicht der vollständig qualifizierte Paketname angegeben Es muss der Name des Pakets inkl. aller Vaterpakete angegeben werden Import Statements stehen vor der Paketdeklaration Um in ein Paket zu importieren müssen die Imports hinter der Deklaration stehen Beim Import mit * werden keine Unterpakete importiert Das ist richtig so, Unterpakete müssen explizit einzeln importiert werden. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #29 Wiederholung: Modifizierer Damit können wir nun die Liste der Zugriffsmodifizierer vervollständigen: private ● (default) ● protected ● public ● Zugriff nur innerhalb der eigenen Klasse Zugriff für Klassen im gleichen Paket Zugriff für Klassen im gleichen Paket und Subklassen Zugriff für Jedermann Wie man sieht, bauen diese Zugriffsarten aufeinander auf. Jede schließt die Zugriffsmöglichkeiten ihrer Vorgänger ein und erweitert sie. public protected (default) private © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #30 Wiederholung: Gültigkeitsbereiche Mit Paketen und Klassen kennen wir nun (fast) alle Gültigkeitsbereiche in Java: Paket Datei Klasse Objekt Instanzmethode Klassenmethode Block/Kontrollstruktur Block/Kontrollstruktur Der Zugriff auf andere Bereiche ist nur von Innen nach Außen möglich! © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #31 Wiederholung: Lebensdauer Programmende Objektzerstörung (GC) Blockvariablen Parameter, Lokale Variablen Objekte, Instanzvariablen Klassen, Klassenvariablen Rücksprung Blockaustritt Blockeintritt Methodenaufruf Objekterzeugung (new) Programmstart Die Lebensdauer von Variablen ist eng an die Gültigkeitsbereiche gekoppelt t © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #32 Lifecycle © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #33 Initialisierung und Konstruktoren Um neue Objekte nicht immer in mehreren Schritten initialisieren zu müssen, können bei der Erzeugung Parameter angegeben werden. Diese werden an eine spezielle Methode, den Konstruktor, übergeben. Person p = new Person( "Karl", "Napf"); Der Konstruktor ist eine Methode mit dem selben Namen wie die Klasse und hat keinen Rückgabewert. public Person Person( String firstName, String lastName); Wie jede andere Methode (z.B. println), kann auch der Konstruktor überladen werden, d.h. man kann mehrere Konstruktoren mit verschiedenen Parameterlisten schreiben. Wird kein eigener Konstruktor definiert (und nur dann), stellt der Compiler einen Default-Konstruktor ohne Parameter zur Verfügung. Man kann also durch die Erstellung eines eigenen Konstruktors die Initialisierung erzwingen. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #34 Garbage Collection In Java gibt es den Luxus einer automatischen Speicherverwaltung. Der Programmierer muss den Speicher für dynamische Objekte nicht selbst verwalten und diese explizit löschen. Stattdessen werden alle Objekte, die nicht mehr gebraucht werden (die nirgends mehr referenziert werden) automatisch aufgeräumt. Damit gehören Speicherlecks und ähnliche Probleme der Vergangenheit an. Um dennoch die Möglichkeit zu haben, eigene Aufräumarbeiten vorzunehmen (z.B. temporäre Dateien zu löschen oder Betriebsmittel außerhalb der virtuellen Maschine freizugeben) wird beim aufräumen von Objekten eine spezielle Methode aufgerufen. Diese kann selbst definiert werden und muss folgende Signatur besitzen. public void finalize() Dieser Aufruf erfolgt jedoch nur, wenn das betreffende Objekt während der Laufzeit des Programms aufgeräumt wird. Wird das Programm vorher beendet, werden der gesamte Speicher freigegeben ohne die Objekte einzeln aufzuräumen. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #35 Vererbung © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #36 Rückblende: Vererbung Eigenschaften lassen sich durch Vererbung abstrahieren und wiederverwenden: :Objekt :Fahrzeug :Landfahrzeug Ein Auto ist-ein spezielles Landfahrzeug :Wasserfahrzeug :Auto :Pkw :Boot :Lkw :Segelboot :Motorboot :Smart Statt Vererbung spricht man auch von Spezialisierung oder ist-ein Beziehung. Die Kunst besteht darin, die richtige Schrittweite zu finden – zum besseren Verständnis und für Erweiterungen, z.B. Fahrrad oder Surfbrett). © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #37 Vererbung(1) Bei der Vererbung werden alle Eigenschaften der Vaterklasse (engl. superclass) in die abgeleitete Klasse (engl. Subclass) übernommen. Die Objekte der abgeleiteten Klassen besitzen die selben Datenfelder und Methoden wie die Objekte der Vaterklasse und können diese durch neue Datenfelder und Methoden ergänzen. public class Subclass extends Superclass { // ... } geerbte Eigenschaften ergänzte Eigenschaften Wird keine Superklasse angegeben, wird implizit die Klasse Object verwendet. public class Person { // extends Object // ... } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #38 Vererbung(2) Aufgrund der ist-ein Semantik wird die Vererbungshierarchie auch Spezialisierung (von oben nach unten) bzw. Generalisierung (von unten nach oben) bezeichnet. Als Konsequenz dieser Semantik können Objekte von abgeleiteten Klassen an Stelle von Objekten der Vaterklasse verwendet werden. Es ist also möglich, ein Auto einfach als (spezielles) Fahrzeug zu betrachten. Beim Entwurf einer Vererbungshierarchie werden daher alle allgemeinen Eigenschaften möglichst weit oben implementiert, um so in möglichst vielen abgeleiteten Klassen wiederverwendet werden zu können und die Wartbarkeit zu verbessern. Diese Verallgemeinerung kann auch zu einem späteren Zeitpunkt geschehen. Dabei wird eine Eigenschaft, die zunächst in einer speziellen Klasse implementiert war, in eine darüberliegende Klasse verschoben und ggf. durch zusätzliche Parameter ergänzt oder in mehrere Teilschritte zerlegt wird. Grund hierfür kann die Entdeckung von Gemeinsamkeiten während der Programmierung ("sowas hab ich doch schonmal gemacht") oder das auftreten von neuen Klassen sein. Diesen Vorgang nennt man Refactoring. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #39 Überschreiben von Methoden Das Verhalten von Objekten einer abgeleiteten Klasse kann angepasst werden, indem Methoden der Vaterklasse durch neue Methoden ersetzt bzw. überschrieben (engl. overridden) werden. Dazu muss die neue Methode den selben Namen, die selben Parameter, den selben Rückgabewert und den selben Zugriffsschutz haben Für überschriebene Instanzmethoden wird erst zur Laufzeit entschieden, welche Variante aufgerufen wird. Der Compiler erzeugt also noch keinen konkreten Aufruf. Dies nennt man späte bzw. dynamische Bindung (engl. late binding / dynamic binding). Dies funktioniert für jeden Methodenaufruf – auch die in einer geerbten Methode. Die Vaterklasse kann also überschriebene Methoden in abgeleiteten Klassen aufrufen. Über solche Hook-Methoden, kann man sich in einer abgeleiteten Klasse in komplexere Abläufe "einklinken". Ausschlaggebend dabei ist nicht der statische Typ der in der entsprechenden Variablen gespeicherten Objektreferenz, sondern der dynamische Typ des referenzierten Objekts. Der Aufruf von Klassenmethoden wird dagegen über den Klassennamen fest an eine konkrete Klasse gebunden. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #40 Überladen vs Überschreiben Beim Überladen einer Methode soll diese unter dem selben Namen für verschiedene Datentypen bzw. Parameterlisten einsetzbar sein. Daher ist auch der Rückgabewert typischerweise identisch (dies ist jedoch nicht zwingend). Die Methoden sind in derselben Klasse realisiert und werden anhand ihrer Parameterliste unterschieden. Beim Überschreiben einer Methode soll deren Funktion in einer abgeleiteten Klasse angepasst werden. Damit die neue Methode die alte Methode ersetzen kann, muss Sie den selben Namen, dieselbe Parameterliste und den selben Rückgabewert haben. Methodenzweck Methodenname Parameterliste Rückgabewert Heimatklasse Zugriffsrechte © Andreas Rau, 24.03.11 überladen identisch identisch unterschiedlich typischerweise identisch identisch typischerweise identisch überschreiben identisch identisch identisch identisch unterschiedlich identisch D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #41 Ergänzung: Modifizierer Den Modifizierer final kennen wir bereits in seiner Funktion zur Kennzeichnung von Konstanten. Im Zusammenhang mit der Vererbung hat er noch zwei weitere Bedeutungen: finale Klassen Von einer finalen Klasse können keine weitere Klassen abgeleitet werden. finale Methoden Finale Methoden können in einer Subklasse nicht überschrieben werden. Da dies eine erhebliche Einschränkung der Vererbung und der damit verbundenen Vorteile darstellt, sollte der Einsatz von final zu diesem Zweck wohl durchdacht sein. Hinweis: Das engl. Adjektiv "final" bedeutet "endgültig" (z.B. bedeutet "all sales final" soviel wie "Umtausch ausgeschlossen"). In dieser Bedeutung macht das Schlüsselwort sowohl für Variablen als auch für Klassen und Methoden Sinn. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #42 Einschub: Subtypen Ein abstrakter Datentyp ist zwar eindeutig und gekennzeichnet durch seine Werte und die darauf möglichen Operationen. Bei der Betrachtung von Datentypen fallen jedoch gewisse Ähnlichkeiten auf, z.B. zwischen den numerischen Datentypen. Auf Basis dieser Ähnlichkeiten lassen sich Typfamilien definieren und hierarchisch darstellen. In diesem Zusammenhang spricht man auch von Subtypen. Ein Subtyp ist ein Typ, der überall anstelle des eigentlichen Typs verwendet werden kann (vgl. Liskov Substitution Principle). Formal gesprochen: (Quelle: www.foldoc.org) If S is a subtype of T then an expression of type S may be used anywhere that one of type T can and an implicit type conversion will be applied to convert it to type T Überall dort, wo ein Wert vom Typ T erwartet wird, darf also auch ein Wert vom Typ S stehen und mit den Werten vom Typ S muss man alles tun können, was man auch mit Werten vom Typ T machen könnte. Damit darf ein Subtyp weniger Werte und mehr Operationen enthalten als sein übergeordneter Typ. Mit anderen Worten: Subtypen sind eine Spezialisierung. Wir werden sehen, dass dies bei der Vererbung erfüllt ist: Subklassen sind also Subtypen. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #43 Liskov Substitution Principle & Programming by Contract Polymorphie ist nur möglich, weil jedes Objekt einer speziellen abgeleiteten Klasse die Eigenschaften aller allgemeineren Vaterklassen hat. In der Praxis funktioniert dies aber nur, wenn die Vererbung als „ist-ein“ Beziehung korrekt verwendet wurde. Barbara Liskov formulierte dies in der folgenden Forderung: „Methoden, die Referenzen auf Basisklassen benutzen, müssen in der Lage sein, Objekte von abgeleiteten Klassen zu benutzen, ohne es zu bemerken“ Ob diese Forderung erfüllt wird liegt aber nicht an den Methoden sondern an den Objekten der abgeleiteten Klassen. Zur Verdeutlichung kann man die Summe (der Schnittstellen) aller Methoden einer Klasse als einen Vertrag betrachten: Jede Methode stellt gewisse Erwartungen (Vorbedingungen) an ihre Parameter und garantiert dafür eine bestimmte Leistung (Nachbedingung). Dies nennt man auch „Programming by Contract“. Um den ursprünglichen Vertrag der Basisklasse nicht zu verletzen darf eine überschriebene Methode „weniger verlangen“ und „mehr liefern“ aber nicht umgekehrt! Technisch gesehen bedeutet dies: der Wertebereich für Parameter darf aufgeweitet und die Menge der Rückgabewerte eingeschränkt werden. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #44 Polymorphie Eine Referenzvariable kann auf Objekte ihres Typs sowie Objekte aller davon abgeleiteten Klassen zeigen ("ein Auto ist-ein spezielles Fahrzeug"). Das Objekt hinter dieser Variablen kann also in vielen (poly) Gestalten (morph) auftreten. Wird über diese Variable eine überschriebene Methode aufgerufen, wird stets die "richtige" Methode ausgeführt, also die in der tatsächlichen Klasse des Objekts definierte. Damit entscheidet das Objekt quasi selbst, wie es auf einen Methodenaufruf reagiert. Fallunterscheidungen nach Objekttyp und die damit verbundenen Probleme, z.B. bei der Einführung neuer Klassen, sind nicht mehr nötig. Der Code funktioniert mit allen derzeitigen und künftigen Subklassen! Beispiel: Fahrzeug f = new Auto(); /* nicht mehr nötig if (f instanceof Auto) { f.driveTo( "Hamburg"); } else (f instanceof Schiff) { f.swimTo( "Hamburg"); } */ f.moveTo( "Hamburg"); // ruft die Methode der Klasse Auto auf © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #45 Frühe Bindung / Späte Bindung In Java gibt es zwei verschiedene Arten, Methoden aufzurufen (ja, wirklich!) Frühe/Statische Bindung (Vorteil: schnell, effizient, sicher; Nachteil: unflexibel) Der Compiler entscheidet anhand des deklarierten Parametertyps, welche überladene Methode aufgerufen wird. Beispiel: Fahrzeug f = new Auto(); Presse.verschrotten( f); // ruft verschrotten(Fahrzeug) auf Späte/Dynamische Bindung (Vorteil: flexibel; Nachteil: fehleranfällig) Das Laufzeitsystem entscheidet anhand des tatsächlichen Objekttyps welche überschriebene Methode aufgerufen wird. Beispiel: Fahrzeug f = new Auto(); f.move(); // ruft Auto.move() auf Achtung: Aufrufe von Klassenmethoden werden immer früh gebunden! © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #46 Double-Dispatching Bei der Auswahl von überladenen Methoden findet die Bindung stets früh statt. Will man hier eine späte Bindung erreichen, muss man mit einer überschriebenen Methode "zurückrufen" (dies nennt man double-dispatching). Beispiel: public class Presse { public void verschrotten( Fahrzeug f) { f.verschrotten( this); } } public class Fahrzeug { public verschrotten( Presse p) { } } public class Auto extends Fahrzeug { public verschrotten( Presse p) { } } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #47 Delegation Eine Klasse zu beerben ist nicht die Einzige und auch nicht die Beste Möglichkeit, sich Ihre Dienste nutzbar zu machen. Mann kann auch einfach ein separates Objekt der Klasse mit der gewünschten Funktionalität erzeugen und für seine Zwecke einspannen. In diesem Fall spricht man von Delegation. Die Vererbung sollte nur eingesetzt werden, wenn dies der "ist-ein"-Beziehung gerecht wird. Dies ist regelmäßig dann der Fall, wenn die geerbte Funktionalität auch (mit derselben Schnittstelle) nach Außen angeboten werden soll. Wird sie hingegen nur intern verwendet, ist ein Hilfsobjekt meist die bessere Wahl. In der Umsetzung bedeutet die Delegation also, dass man nicht von der Hilfsklasse erbt sondern in seiner eigenen Klasse eine zusätzliche Instanzvariable einführt und in dieser ein Objekt der Hilfsklasse speichert, um es später aufrufen zu können. Hinweis: Ein besonderer Anwendungsfall für Delegation ist eine sog. Fassade © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #48 Analogien - Klassen und Objekte Wir stellen uns ein Industriegebiet mit vielen Maschinenfabriken vor... Jede Klasse ist eine Maschinenfabrik. Sie hat einen Bauplan für Maschinen. Die Produktion kann Anzeigen (Klassenvariablen) und Knöpfe (Klassenmethoden) haben. Allerdings können einige dieser Elemente verdeckt (private) sein. Ein Objekt ist eine Maschine aus einer bestimmten Fabrik und kann ebenfalls Anzeigen (Instanzvariablen) und Knöpfe (Instanzmethoden) haben. Auch hier ist nicht unbedingt alles öffentlich (public). Mit Hilfe der Vererbung kann man Maschinen weiterentwicklen. Dabei bleiben alle Elemente der usprünglichen Maschine erhalten und es können weitere Elemente hinzugefügt werden um eine speziellere Maschine zu konstruieren. Eine Maschine kann aber auch mit Hilfe der Aggregation aus mehreren kleineren Maschinen zusammengebaut werden. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #49 Pseudovariablen this und super © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #50 Die Magie im Hintergrund(1) Die Pseudovariable this Beim Zugriff auf Objekteigenschaften muss bekanntlich stets eine Objektreferenz angegeben werden. Damit ist es insbesondere Methoden möglich, mit verschiedenen Objekten zu arbeiten. Beispiel // kann als methodName(obj, p1, p2, p3) interpretiert werden obj.methodName(p1, p2, p3); Die Pseudovariable this ist also quasi ein unsichtbarer Parameter. Er ist nur in Objektmethoden verfügbar und zeigt stets auf das aktuelle Objekt. Ein nicht qualifizierter Zugriff auf (ohne obj.) bezieht sich immer auf das aktuelle Objekt. Beispiel myMethod(); // bedeutet implizit this.myMethod(); Zwingend notwendig ist der Einsatz von this also nur dann, wenn Klassenoder Instanzvariablen durch lokale Variablen verdeckt sind, z.B. im Konstruktor. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #51 Die Magie im Hintergrund(2) Die Pseudovariable super Gestattet beim Überschreiben einer Methode den Aufruf er bisherigen Methode in der direkten Vaterklasse. Dies ist sowohl in Instanzmethoden als auch in Klassenmethoden möglich. Damit kann die bisherige Implementierung entweder ersetzt oder am Anfang bzw. am Ende ergänzt werden. Beispiele public void someMethod() { // Variante 1: komplett ersetzen // my Code } public void someMethod() { // Variante 2: ergänzen am Anfang // my Code super.someMethod(); } public void someMethod() { // Variante 3: ergänzen am Ende super.someMethod(); // myCode } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #52 Die Magie im Hintergrund(3) Die Pseudovariable super (Fortsetzung) Um bei komplexen Methoden möglichst viel wiederverwenden zu können, bietet es sich an, diese in mehrere Schritte aufzuteilen und diese jeweils als eigene Methoden zu realisieren (auch eine Form von Refactoring). Beispiel für Refactoring public void someComplexMethod() { someComplexMethodStep1(); someComplexMethodStep2(); } Damit kann man entweder wie bisher den Gesamtablauf ersetzen oder ergänzen oder dasselbe für jeden der einzelnen Schritte tun. Beispiel public void someComplexMethod() { super.someComplexMethodStep1(); // my Code for step 2 } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #53 Die Magie im Hintergrund(4) Verwendung von super und this bei Konstruktoren Neben der Verwendung als Pseudovariable beim Zugriff auf Objekteigenschaften können super und this auch zum expliziten Aufruf von Konstruktoren verwendet werden. Dies ist nur in einem Konstruktor möglich und muß in der ersten Zeile erfolgen (da sonst eine Default-Initialisierung erfolgt). public class Person { public Person() { this( “N.“, “N.“); // ruft Person(String,String) auf } public Person( String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } public class Student extends Person { public Student( String firstName, String lastName) { super( firstName, lastName); // ruft Person(String,String) auf } } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #54 Grundlegende Objekteingenschaften © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #55 Die Klasse Object und allgemeine Objekteigenschaften Die Klasse Object ist "die Mutter aller Klassen": Jede andere Klasse erbt direkt oder indirekt von dieser Klasse. Daher stehen die Methoden dieser Klasse für alle Objekte in Java zur Verfügung. Die wichtigsten davon sind: public String toString() Liefert eine Beschreibung des Objekts (zur Anzeige in GUIs und bei Debugging). public boolean equals(Object obj) Vergleicht zwei Objekte auf Gleichheit bzw. Äquivalenz (nicht Identität). public int hashCode() Liefert eine "Kennziffer" des Objekts. Diese Kennziffer kann für verschiedene Objekte gleich sein und muss für gleiche Objekte gleich sein. public Class getClass() Liefert eine Objekt zurück, das die Klasse des Objekts repräsentiert. Weitere Methoden von Object werden später beschrieben. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #56 Vergleiche: Gleichheit und Identität(1) Gleichheit und Identität sind bei Objekten zwei verschiedene Dinge: ● ● Die Identität von Objekten wird mit dem Operator == überprüft. Dieser liefert eine Antwort auf die Frage "Ist dies dasselbe Objekt?". Die Gleichheit von Objekten wird mit der Methode equals() überprüft. Diese liefert eine Antwort auf die Frage "Sind diese beiden Objekten gleich(wertig)?". Der Operator == vergleicht also gewissermaßen den "Inhalt einer Schublade", die Methode equals() vergleicht Objekte. Bei primitiven Datentypen ist diese Unterscheidung nicht notwendig: Ihre Werte werden direkt mit == verglichen (Methoden stehen ohnehin nicht zur Verfügung). Damit die Objekte einer Klasse später auch in Collections (Datenstrukturen – wie Arrays, nur komfortabler) verwendet werden können, muss daneben für die Methoden equals() und hashCode() folgender Zusammenhang gelten(!): obj1.equals(obj2) ⇒ obj1.hashCode() == obj2.hashCode() Es muss also möglich sein, ein Objekt auch über seinen hashCode zu finden. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #57 Vergleiche: Gleichheit und Identität(2) Beispiel public class Person { public boolean equals( Object obj) { // Vergleich von instVars if ( this == obj) return true; if ( null != obj && obj instanceof Person) { Person other = (Person)obj; return (this.name.equals( other.name) && this.surname.equals( other.surname)); } else { return false; } } } public int hashCode() { // Verwendung von instVars (wie equals) return name.hashCode() + surname.hashCode(); } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #58 Vergleiche: Gleichheit und Identität(3a) Erweitertes Beispiel public class Person { public boolean equals( Object obj) { // Vergleich von instVars if ( this == obj) return true; return equalsImpl( obj); } Attributvergleich null !null null true (false) public boolean equalsImpl( Object obj) { !null equals() equals() if ( null != obj && obj instanceof Person) { Person other = (Person)obj; return (null==this.name ? null==other.name : this.name.equals( other.name)) && (null==this.surname ? null==other.surname : this.surname.equals( other.surname)); } } return false; public int hashCode() { // Verwendung von instVars (wie equals) return hashCodeImpl(); } public int hashCodeImpl() { } return (null!=name ? name.hashCode() : 7) + (null!=surname ? surname.hashCode() : 11); } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #59 Vergleiche: Gleichheit und Identität(3b) Erweitertes Beispiel (Fortsetzung) public class Student extends Person { public boolean equalsImpl( Object obj) { if ( super.equalsImpl( obj) { if ( null != obj && obj instanceof Student) { Student other = (Person)obj; return (null==this.matrikelnr ? null==other.matrikelnr : } } } return false; this.matrikelnr.equals( other.matrikelnr)); public int hashCodeImpl() { return super.hashCodeImpl() + null!=matrikelnr ? matrikelnr.hashCode() : 162; } } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #60 Die Klasse Class und Reflection(1) Zur Laufzeit des Programms können die Eigenschaften aller Klassen, z.B. der Klassenname, über ein entsprechendes Objekt der Klasse Class abgefragt werden. So kann man z.B. vorhandene Konstruktoren, Methoden und Felder sowie deren Eigenschaften abfragen und ggf. sogar darauf zugreifen. Die Konstruktoren, Methoden und Felder werden dabei ihrerseits durch entsprechende Klassen repräsentiert. Dies wird als Reflection bezeichnet und ist keineswegs selbstverständlich: nicht alle Programmiersprachen unterstützen dies (.net nennt dies Introspection). Die zugehörigen Klassen sind im Paket java.lang.reflect enthalten. Mit Reflection sind geprüfte dynamische Aufrufe zur Laufzeit möglich*. Dies ist Grundlage zahlreicher trickreicher Programmierkonstrukte. Damit können z.B. Klassen geschrieben werden, die mit beliebigen anderen Klassen umgehen und diese z.B. in eine Datenbank speichern können. *In interpretierten Skriptsprachen erfolgt dies mehr oder weniger ungeprüft mit einer eval-Funktion zur Ausführung beliebiger Zeichenketten © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #61 Die Klasse Class und Reflection(2) Der Zugriff auf das Class-Objekt kann dabei auf verschiedene Arten erfolgen: (1) Durch direktes Hinschreiben <Klassenname>.class (2) Durch Zugriff auf ein Objekt dieser Klasse obj.getClass() (3) Durch dynamisches Laden über dem Klassennamen Class.forName( "<Klassenname>") © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #62 Die Klasse Class und Reflection(3) Interessante Methoden in der Klasse Class sind u.a. String getName() liefert den voll qualifizierten Namen der Klasse (incl. Paketname) Package getPackage() liefert einen Repräsentanten des Pakets (oder null) Constructor[] getConstructors() liefert ein Array mit allen Konstruktoren Method[] getMethods() liefert ein Array mit allen Methoden Field[] getFields() liefert ein Array mit allen Datenfeldern © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #63 Exception Handling © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #64 Fehlerbehandlung(1) - Hintergrund Nicht immer läuft alles nach Plan: Entwickler machen Leichtsinns- bzw. Denkfehler und rufen z.B. Methoden mit ungültigen Parametern auf. Speichermedien und Netzwerke versagen. Benutzer geben Blödsinn ein oder drücken die falschen Knöpfe. Einige dieser Fehler können dauerhaft beseitigt werden! Mit dem Rest muss ein Programm klarkommen, um einsetzbar zu sein. Somit muss ein Programm neben der Implementierung des Normalfalls auch Code zum Umgang mit Sonderfällen und Fehlern (auch wenn man i.d.R. mit Ersterem anfängt) enthalten. Dieser kann oft einen erheblichen Umfang haben und kann die Lesbarkeit und Wartbarkeit des Programms erheblich erschweren. Das Ziel der Fehlersuche und Fehlerbehebung ist die Elimination von Fehlern innerhalb des Programms (z.B. Syntaxfehler, Denkfehler). Ziel der Fehlerbehandlung hingegen ist der Umgang mit Fehlern deren Ursache außerhalb des Programs liegt (z.B. Eingabefehler oder Fehler in der Peripherie). Fehlersuche und Fehlerbehebung während der Entwicklung einerseits und Fehlerbehandlung im Betrieb anderseits sind also zwei komplementäre Aktivitäten zur Implementierung von korrekten, zuverlässigen und robusten Programmen und damit ein wesentlicher Beitrag zur Qualität eines Programms. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #65 Fehlerbehandlung(2) - Grundprinzipien Die Behandlung von Fehlern setzt zunächst voraus, daß man(a) mit Ihnen rechnet und (b) ein Konzept zu ihrer Behandlung hat: Fehler mit denen keiner rechnet, werden meist auch nicht erkannt, und Fehler, die nicht unterschiedlich behandelt werden muss man wahrscheinlich auch nicht unterscheiden. Umgekehrt kann es Sinn machen, (a) "unerwartete Fehler" durch "überflüssige" else-Zweige u.ä. abzufangen und (b) Fehler detaillierter zu beschreiben als "Fehler"/"kein Fehler", um Sie leichter beseitigen oder behandeln zu können. Der grundsätzliche Ablauf bei der Behandlung von Fehlern besteht aus zwei Schritten. Diese finden i.d.R. an getrennten Stellen im Programm statt: Erkennung von Fehlern (z.B. in einer Bibliothek) ● Behandlung von Fehlern (z.B. durch Anzeige einer Fehlermeldung im GUI) ● Bei der Erkennung eines Fehlers ist also meist noch nicht klar, wie er behandelt werden soll. Die Information über aufgetretene Fehler muss daher möglichst elegant an die richtige Stelle weitertransportiert werden. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #66 Fehlerbehandlung(3) – Klassische Fehlerbehandlung Für die klassische Fehlerbehandlung werden Fehlerzustände in Variablen gespeichert und über besondere Rückgabewerte oder zusätzliche Parameter zurückgemeldet. Beispiel: public static double sqrt(double x) { if (x >= 0) { // Sicherheitsüberprüfung // Wurzel berechnen und zurückliefern } else { return -1; // Fehlerwert, Wurzel ist immer positiv } } public static void main(String[] args) { // Wert x eingeben double w = sqrt(x); if (w != -1) { // Weiter } else { // Fehler } } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #67 Fehlerbehandlung(4) – Klassische Fehlerbehandlung Die klassische Fehlerbehandlung hat einige gravierenden Nachteile Nicht immer gibt es "freie" Rückgabewerte für Fehler ● Zusätzliche Parameter sind aufwendig und verwässern die Schnittstelle ● Die Prüfung auf Fehler nach jedem Aufruf ist aufwendig. ● Das "saubere" Verlassen von Funktionen nach Fehlern ist aufwendig ● Hoher Aufwand für Rückmeldung von Fehlern über mehrere Aufrufebenen ● Keine klare Trennung zwischen produktivem Code und Fehlerbehandlung ● Keine/aufwendige Übergabe einer detaillierten Fehlerbeschreibung ● Identische Sprachmittel zur Funktionsrealisierung und Fehlerbehandlung ● Fehlerbehandlung kann vergessen werden ● Diese Nachteile lassen sich in Java durch Exception-Handling vermeiden. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #68 Fehlerbehandlung(5) – Exception Handling Exception Handling in Java und anderen Sprachen basiert auf folgenden Ideen: Verwendung von besonderen Objekten zur Repräsentation von Fehlern ● Übergabe einer detaillierten Fehlerbeschreibung im Objekt möglich ● Systematisierung von Fehlern mit Hilfe einer Klassenhierarchie ● Verwendung einer besonderen Schnittstelle zur Übergabe der Fehlerobjekte ● Kein verwässern von Schnittstellen ● Kein zusätzlicher Aufwand ● Geht immer ● Klare Trennung von produktivem Code, Fehlerbehandlung und "Aufräumcode" ● Direktes anspringen der Fehlerbehandlung durch besonderen Mechanismus ● Keine Überprüfung nach jedem Aufruf nötig ● Einfache Rückmeldung über mehrere Aufrufebenen ● Unterstützung/Erkennung besonderer Konstrukte durch den Compiler ● Pflicht zur Deklaration von Exceptions bei Methoden ● Pflicht zur Behandlung von Exceptions ● Damit sind die Nachteile der klassischen Fehlerbehandlung hinfällig. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #69 Fehlerbehandlung(6) – Exception Handling Die technische Umsetzung in Java basiert auf 6 Elementen ● Basisklassen für Exceptions ● Try-Block als "Sicherheitszone" für fehleranfälligen Code ● Throw-Anweisung zum "werfen" von Exceptions im Fehlerfall ● Catch-Blöcke zur Behandlung von Fehlern aus einem try-Block ● Finally-Block zum aufräumen nach dem Verlassen von Blöcken/Funktionen ● Angabe von Exceptions bei der Funktionsdeklaration ("Beipackzettel") Beispiel public static void unstable() throws Exception { throw new Exception(); } public static void main(String[] args) { try { unstable(); } catch (Exception ex) { // Fehler behandeln – wird nur im Fehlerfall ausgeführt } finally { // Aufräumen – wird immer ausgeführt } } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #70 Fehlerbehandlung(7) – Exception Handling Die catch-Blöcke haben eine gewisse Ähnlichkeit zu (a) Funktionen und (b) den cases einer switch-Anweisung. Tatsächlich können einem try-Block mehrere catch-Blöcke folgen. Der richtige catch-Block (case) wird dann anhand der Klasse der Exception (Funktionsparameter) ausgewählt. Der catch-Block kann also gewissermaßen überladen werden. Dabei ist es auch möglich, durch Verwendung einer abstrakten Exceptionklasse mehrere Arten von Exceptions gleich zu behandeln: Bei Verwendung der Klasse Exception, der "Mutter aller Exceptions", verhält sich der resultierende catch-Block wie default bei der switch-Anweisung. Dabei ist allerdings zu beachten, daß die Suche nach dem richtigen catch-Block von oben nach unten verläuft. Daher müssen die allgemeineren catch-Blöcke unten stehen. Der finally-Block wird immer ausgeführt ist aber nicht immer nötig. Manchmal gibt es auch keine catch-Blöcke sondern nur einen finally-Block. Dann wird die Exception nach dem Aufräumen weitergereicht. Ein try-Block muss jedoch immer mindestens einen catch-Block oder einen finally-Block haben (sonst nützt er ja auch nichts). © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #71 Fehlerbehandlung(7a) – Exception Handling Fehler selbst behandeln und hinterher aufräumen try { // Unsichere Operation ausführen } catch (Exception ex) { // Fehler behandeln – wird nur im Fehlerfall ausgeführt } finally { // Aufräumen – wird immer ausgeführt } Fehler selbst behandeln und nix zum aufräumen try { // Unsichere Operation ausführen } catch (Exception ex) { // Fehler behandeln – wird nur im Fehlerfall ausgeführt } Fehler nicht selbst behandeln aber vor der Weiterleitung aufräumen try { // Unsichere Operation ausführen } finally { // Aufräumen – wird immer ausgeführt } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #72 Fehlerbehandlung(8) – Exception Handling Unterschiedliche Fehler spezifisch behandeln public static void main(String[] args) { try { // throw ThisException oder ThatException } catch (ThisException ex) { // spezielle Exception 1 // ThisException behandeln } catch (ThatException ex) { // spezielle Exception 2 // ThatException behandeln } catch (Exception ex) { // allgemeiner Handler ("Besenwagen") am Ende // Alle anderen Exceptions behandeln } } Alle Fehler gleich behandeln public static void main(String[] args) { try { // throw ThisException oder ThatException } catch (Exception ex) { // allgemeiner Handler für alle Exceptions // Alle anderen Exceptions behandeln } } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #73 Fehlerbehandlung(9a) – Exception Handling Exception Handling funktioniert auch wunderbar über mehrere Aufrufe hinweg Beispiel public static void dritteFunktion() throws Exception { throw new Exception(); } public static void zweiteFunktion() throws Exception { dritteFunktion(); } public static void ersteFunktion() throws Exception { zweiteFunktion(); } public static void main(String[] args) { try { ersteFunktion(); } catch (Exception ex) { // Exception behandeln } } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #74 Fehlerbehandlung(9b) – Exception Handling main() ersteFunktion() zweiteFunktion() Normallfall: Schritt für Schritt zurück return dritteFunktion() return throw return Fehlerfall: direkt zum catch-Block() © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #75 Fehlerbehandlung(10) – Exception Handling Die Klasse Exception ist Teil einer vordefinierten Klassenhierarchie: Throwable Error "alles, was man werfen kann" interne Fehler Exception besondere Laufzeitfehler RuntimeException eigene Fehler allgemeine Exceptions eigene Exceptions Diese kann um eigene Exceptionklassen erweitert werden. In den Standardbibliotheken wird von dieser Möglichkeit ausgiebig Gebrauch gemacht. Eigene Exceptions werden i.d.R. von Exception abgeleitet. Für RuntimeExceptions erzwingt der Compiler die Deklaration und Behandlung nicht. Aber: Alle zur Laufzeit auftretenden aber nicht abgefangene Exceptions (Runtime oder nicht), beenden das Programm! © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #76 Fehlerbehandlung(11) - Anwendung In der Praxis begegnen uns Exceptions meist bei der Verwendung der Java Bibliothek. Die entsprechenden Exceptions sind in der API Dokumentation bei der jeweiligen Methode angegeben. Eigene Exception-Klassen zu implementieren ist für kleine Anwendungen i.d.R. nicht nötig. Beispiele für RuntimeExceptions: ● ArrayIndexOutOfBoundsException - beim Zugriff auf fehlende Array-Elemente ● NullPointerException - beim Zugriff auf Null-Referenzen ● ClassCastException - bei "falschen" Casts ● ArithmeticException - bei Berechnungen, z.B. Division durch Null ● NumberFormatException - bei Integer.parseInt( String); vgl. CommandLine Beispiele für Exceptions: ● InterruptedException - bei Thread.sleep( int); vgl. GridView ● IOException - beim Zugriff auf Dateien (später) © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #77 Fehlerbehandlung(12) - Anwendung In der Literatur bzw. im Internet finden sich 3 Regeln zum Exception-Handling: "Be specific" - Exceptions spezifisch behandeln ● "Throw early" – Exceptions werfen, sobald etwas schief geht ● "Catch late" – Exceptions nicht sofort wieder fangen sondern laufen lassen ● siehe auch http://today.java.net/pub/a/today/2003/12/04/exceptions.html © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #78 Interfaces © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #79 Interfaces(1a) - Hintergrund Methoden aus der fachlichen Modellierung werden mit Hilfe der Vererbung innerhalb einer (Teil)Hierarchie von Klassen verfügbar gemacht. Daneben gibt es jedoch auch Methoden, die unabhängig von der Vererbungshierarchie in verschiedenen Klassen benötigt werden (diese dienen typischerweise einem eher technischen Zweck). Dieser technische Blickwinkel ist von der fachlichen Hierarchie zunächst völlig unabhängig. Insbesondere können derartige Gruppen von Methoden an mehreren Stellen der Klassenhierarchie eingeführt werden (pflanzen sich aber dannach ganz normal über die Vererbung fort). Eine solche Gruppe von Methoden mit einem technischen Zweck fasst man in Java in einem sog. Interface zusammen. Das Konzept von Interfaces (Schnittstellen) in Java bezieht sich also nicht auf die Schnittstelle einer einzelnen Methode (diese wird als Signatur bezeichnet), sondern auf die Schnittstelle eines ganzen Objekts, d.h. die Signaturen aller sichtbaren Methoden (genauer gesagt: einer bestimmten Teilmenge davon). Was hat dies für einen Sinn? Unter der Vielzahl von Methoden eines Objekts lassen sich Gruppen von Methoden identifizieren, die einem gemeinsamem Zweck dienen. Sie stellen sozusagen einen (abstrakten) Dienst zur Verfügung. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #80 Interfaces(1b) - Hintergrund Interfaces werden ähnlich zu Klassen definiert, enthalten aber weniger Eigenschaften. Insbesondere bestehen ihre „Methoden“ nur aus einer Signatur und haben keinen Rumpf (es wird nur beschrieben, wie der Dienst nutzbar sein soll, nicht wie er erbracht wird). Das muss die implementierende Klasse durch „überscheiben“ tun. Für statische Methoden geht das nicht. Und da man Interfaces mangels Implementierung auch nicht direkt instanzieren kann machen Instanzvariablen natürlich auch keinen Sinn. Damit bleiben nur noch Klassenvariablen übrig – aber die müssen final sein! Interfaces lassen sich in Java Klassen zuordnen – auch mehrere! Damit lassen sich im Bedarfsfall unterschiedlichste Klassen abstrakt als Erbringer eines bestimmten Dienstes betrachten und nutzen. Manchmal ist die Funktion sogar bereits vorhanden und wird nur durch ein Interface "freigeschaltet" (MarkerInterface). Interfaces sind sehr ähnlich zu abstrakten Basisklassen, vermeiden aber die Nachteile der Mehrfachvererbung. Analogie: Eine geerbte Basisklasse ist ein Haus, auf das man aufbaut. Ein geerbtes Interface ist nur eine Fassade, die man nachbaut. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #81 Interfaces(2) - Beispiele Kopieren von Objekten (Cloneable) Bei einer Zuweisung zwischen Referenzvariablen werden nur die Referenzen kopiert, nicht die dahinterliegenden Objekte. Manchmal reicht das nicht. Vergleichen von Objekten (Comparable) Mit == können Objekte auf Identität verglichen werden (genauer: Gleichheit der Objektreferenzen), mit equals() kann man Objekte auf Gleichheit testen. Das Konzept der Gleichheit ist auf beliebige Klassen anwendbar. Für bestimmte Anwendungen, z.B. sortieren, braucht man aber mehr: eine Ordnungsrelation. Serialisierung von Objekten (Serializable) Will man Objekte speichern und wieder einladen oder über das Netz übertragen müssen diese in eine Bytefolge umgewandelt werden. Aber muss man dazu das Rad immer neu erfinden? © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #82 Interfaces(3) – Grundeigenschaften Interfaces definieren nur eine Schnittstelle, kein Verhalten und keinen Zustand. Daher werden in einem Interface nur die Signaturen der enthaltenen Methoden definiert, nicht deren Rümpfe. Die Implementierung eines Interface durch eine Klasse ist gleichbedeutend mit der Implementierung aller enthaltenen Methoden (diese kann jedoch auch in einer Vaterklasse erfolgen). Da Interfaces in verschiedensten Klassen verwendet werden, macht eine Weitergabe von Code wie bei der Vererbung auch keinen Sinn. Das Interface definiert also nur das "WAS" – das "WIE" ist Aufgabe der jeweiligen Klasse. Insofern ist ein Interface quasi eine Verkleidung ohne Inhalt bzw. einer Rolle. Aus diesem Grund sind auch keine Instanzvariablen erlaubt – es gibt schlicht keine Instanz. Es ist jedoch möglich, in einem Interface Konstanten zu definieren. Diese sind syntaktisch identisch zu konstanten Klassenvariablen. Die in einem Interface definierten Methoden ohne Rumpf werden auch als abstrakte Methoden bezeichnet. Solche Methoden können auch in einer Klasse definiert werden, um Signaturen für bestimmte Methoden vorzuschreiben. Damit wird die Klasse zu einer sogenannten abstrakten Klasse, von der keine Instanzen erzeugt werden können. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #83 Interfaces(4) – Beispiel Beispiel für ein Interface und dessen Implementierung public interface Cryptography public static final int RSA public static final int DSS public static final int DES } { = 1; = 2; = 3; public void encrypt( int algorithm, String passphrase); public void decrypt( int algorithm, String passphrase); public class Agent extends Person implements Cryptography { public void encrypt( int algorithm, String passphrase) { // Implementierung von encrypt } } public void decrypt( int algorithm, String passphrase) { // Implementierung von decrypt } © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #84 Interfaces(5) – Vergleich mit Klassen Ein Interface ist äquivalent zu einer abstrakten Vaterklasse. Während jedoch eine Klasse nur eine Vaterklasse haben kann, ist die Implementierung mehrerer Interfaces ohne weiteres möglich. Damit vermeiden Interfaces, die Notwendigkeit zur Mehrfachvererbung und die damit verbundenen Probleme. Ein Interface ist wie eine Klasse ein Datentyp, d.h. man kann Variablen mit einem Interface-Typ definieren und damit auf Objekte verweisen, die dieses Interface implementieren. Der Test ist wie bei Klassen mit instanceof möglich. Interfaces können voneinander erben. Dabei wird die Schnittstelle durch zusätzliche Signaturen und Konstanten erweitert. Auf diese Art und Weise können Hierarchien von Interfaces gebildet werden. Klassen können Klassenvariablen, Instanzvariablen, Klassenmethoden und Instanzmethoden definieren, in Interfaces sind nur konstante Klassenvariablen und Instanzmethoden möglich. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #85 Interfaces(5a) - Eselsbrücke Ein Interface kann man sich wie eine standardisierte Frontblende vorstellen, über die ein unbestimmter Anwenderkreis zu einem bestimmten Zweck auf eine unbekannte Maschine(=ein Objekt) zugreifen möchte. Die Maschine kann noch viele zusätzliche Knöpfe und Anzeigen(=Methoden) haben, aber diese interessieren den Anwenderkreis nicht, da sie für den vorgesehenen Zweck nicht notwendig sind. Die Nutzung der nötigen Funktionen über das vorhandene Bedienfeld der Maschine ist nicht möglich, wenn dieses von der standardisierten Frontblende abweicht, da die Anwender nicht jedesmal umlernen können/wollen. Die Angabe „implements <InterfaceName>„ bedeutet also das Versprechen, die notwendigen Funktionen über die vorgeschriebene Standardfrontblende bereitzustellen. Die Implementierung der geforderten Methoden enspricht der Integration der Standardfrontblende in das Bedienfeld der jeweiligen Maschine. Im Kern handelt es sich um eine Vereinbarung mit deren Hilfe man Teilfunktionen von Objekte benutzen kann ohne Ihren genauen Typ zu kennen. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #86 Interfaces(6) – Vergleichen von Objekten (Vertiefung) Das Interface Comparable definiert die Methode compareTo(). Mit Hilfe dieser Methode lassen sich beliebige* Objekte auf größer/gleich/kleiner Testen. Interface Comparable { public int compareTo(Object obj); } public class Person implements Comparable { String name; // ... } public int compareTo(Object obj) { return this.name.compareTo( ((Person)obj).name); } Hinweis: Vordefinierte Interfaces sind ebenfalls in der API-Dokumentation beschrieben und werden. Ihre Namen werden dort kursiv dargestellt. *nicht ganz: bei Unverträglichkeit wird eine ClassCastException geworfen. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #87 Interfaces (7) - Kopieren von Objekten(a) Bei der Zuweisung von Objektvariablen werden nur die Referenzen kopiert, nicht die Objekte. Um die Objekte selbst zu kopieren gibt es mehrere Möglichkeiten: Handarbeit ● Kopierkonstruktor ● Spezielle Methode ● Implementierung von Clonable ● Handarbeit Darunter versteht man das explizite Anlegen eines neuen Objekts mit den selben Attributwerten wie das Originalobjekt mit Hilfe des new-Operators. Das ist natürlich sehr mühsam und im Einzelfall gar nicht möglich, da nicht alle Attributwerte nach außen sichtbar sind. Beispiel Person p2 = new Person(); p2.setName( p1.getName()); //... weitere Attribute kopieren © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #88 Interfaces(8) - Kopieren von Objekten(d) Kopierkonstruktor Darunter versteht man den Aufruf eines Konstruktor, dem das Originalobjekt übergeben wird. Da der Konstruktor Teil der Klasse ist hat er Zugriff auf alle Attribute und kann diese kopieren. Dies ist bequemer als Handarbeit, läßt sich aber schlecht verallgemeinern, da die Klasse explizit genannt werden muss. Beispiel public class Person { //... public Person( Person template) { name = template.name; // ...weitere Attribute kopieren } } // im Hauptprogramm Person p2 = new Person( p1); Neben Kopierkonstruktoren kennt man in C++ auch Konvertierkonstruktoren. Bei diesen ist die Kopiervorlage ein Objekt einer anderen Klasse. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #89 Interfaces(9) - Kopieren von Objekten(b) Spezielle Methode Bei diesem Lösungsansatz wird der manuelle Kopiervorgang in eine spezielle Methode gepackt. Diese kann dann z.B. über ein Interface in verschiedene Klassen verfügbar gemacht werden. Damit ist eine Verallgemeinerung des Aufrufs möglich. Es bleibt jedoch das Problem, bei der Implementierung der Methode die Klasse explizit nennen zu müssen. Beispiel public class Person { //... public Person kopieren() { Person duplicate = new Person(); duplicate.name = this.name; // ...weitere Attribute kopieren } } // im Hauptprogramm Person p2 = p1.kopieren(); © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #90 Interfaces(10) - Kopieren von Objekten(c) Implementierung von Clonable Das Interface Clonable stellt mit clone() eine Kopiermethode wie beim vorhergehenden Ansatz zur Verfügung. Der Clou an dieser Methode ist, dass sie (1) bereits bei Objekt als protected Methode implementiert ist und (2) automatisch ein Objekt der richtigen Klasse erzeugt. Sie kann also in allen Klassen durch Angabe des Interfaces und eine triviale Definition "freigeschaltet" werden. Beispiel public class Person implements Cloneable { public Object clone() throws CloneNotSupportedException { return super.clone(); } } // im Hauptprogramm Person p2 = (Person)p1.clone(); Der Nachteil ist jedoch, daß der Rückgabewert immer vom Typ Object ist. Aber wenn man weiss, was man kopiert hat, ist der anschließende cast idiotensicher. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #91 Interfaces(11) - Kopieren von Objekten(d) flache Kopie und tiefe Kopie Die Attribute eines Objekts werden beim Kopieren durch einfache Zuweisung mitkopiert. Da sie jedoch ihrerseits (Instanz)variablen sind bedeutet dies, das in einem Objekt enthaltene Objekte nicht mitkopiert werden (Die Kopie eines Autos hätte denselben Motor wie das Original). Eine solche Kopie nennt man flache Kopie. Um eine tiefe Kopie zu erzeugen, d.h. (ausgewählte) enthaltene Objekte ebenfalls zu kopieren, muss die Methode clone() überschrieben und der Kopiervorgang nach der flachen Kopie des oberen Objekts mit den enthaltenen Objekten fortgesetzt werden. Beispiel: public class Auto implements Cloneable { public Object clone() throws CloneNotSupportedException { Auto obj = (Auto)super.clone(); // Originalobjekt kopieren obj.motor = (Motor)obj.motor.clone(); // Motor kopieren return obj; } } Es ist jedoch keinesfalls immer nötig, eine tiefe Kopie zu machen! © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #92 Interfaces(12) - Anwendungsbereiche Die Anwendungsbereiche für Interfaces sind überraschend vielfältig: ● Abstrakte Beschreibung von Funktionen für mehrere Klassen ohne Vererbung ➔ Querschnittsfunktionen (sog. cross-cutting concerns) wie Persistenz ➔ Funktionen mit gleichbleibendem WAS aber unterschiedlichem WIE ➔ Trennung von Konzept und Implementierung Beispiel: werden wir später z.B. bei den Collection Klassen finden ● Ergänzende Kennzeichnung von Klassen ➔ Verwendung von Marker-Interfaces und instanceof Beispiel: Freischaltung von Funktionen mit Cloneable und Serializable ● Verallgemeinerung von Algorithmen ➔ Beschreibung geforderter Eigenschaften über ein Interface, z.B. Comparable Beispiel: Sortieralgorithmen und das Interface Comparable © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #93 Patterns © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #94 Singleton-Pattern Manchmal möchte man nicht, dass von einer Klasse Instanzen angelegt werden. Beispiel: public class Singleton { private static Singleton instance; private Singleton() { } } public staic Singleton getInstance() { if ( null==instance) { instance = new Singleton(); } return instance; } Damit kann man auch Dienste, die nur einmal benötigt werden als Objekt implementieren. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #95 Visitor-Pattern Ein Ansatz um eine Objekthierarchie für "beliebige Zwecke" zu durchlaufen. Beispiel: public class Visitor { public void visit( Fahrzeug f) { } } public void visit( Auto f) { } public class Fahrzeug { public accept( Visitor v) { v.visit( this); } } Mit Hilfe dieses Ansatzes kann man quasi beliebige Operationen implementieren und auf einer Menge von Klassen durchführen. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #96 Abschluss Herzlichen Glückwunsch! Sie haben nun alle wesentlichen objektorientierten Konzepte und Sprachfeatures von Java kennengelernt(!) und verstanden(?). Im weiteren Verlauf der Vorlesung werden wir uns mit den Inhalten der JavaKlassenblibliothek beschäftigen und eine Reihe elementarer und mächtiger Klassen für die Ein-/Ausgabe den Umgang mit Datenstrukturen oder die Programmierung grafischer Oberflächen, den Zugriff auf Datenbanken sowie die Netzwerkprogrammierung kennenlernen. Dabei werden wir bei Bedarf spezielle Sprachfeatures wie innere Klassen kennenlernen. Mit anderen Worten: Die Zeit des übens mit den Grundwerkzeugen und der akademischen Beispiele geht langsam vorüber. Die reale Welt und Ihr erstes echtes Projekt (quasi das Gesellenstück) nahen. © Andreas Rau, 24.03.11 D:\home\ar\fhte\vorlesungen\informatik1\folien\informatik1-theorie-objektorientierung.odp #97