1. Klassen und Vererbung Klassen als strukturierter Datentyp 1. Klassen und Vererbung • selbst definierter Datentyp (Referenzdatentyp) • Modellierung neuer Strukturen • Sammlung von Variablen verschiedener Typen • Beispiel: Adresse • Eine Adresse soll als Ganzes repräsentiert werden können. • Teile des Ganzen haben unterschiedliche Datentypen. • Eine Klasse fasst die Komponentenvariablen zusammen. • Darstellung: UML-Klassendiagramm Adresse name: strasse: hausnummer: postleitzahl: wohnort: mail: kommentar: Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 String String int int String String String 11 1. Klassen und Vererbung Klassen als strukturierter Datentyp Erläuterungen und Terminologie: • Die Komponentenvariablen heißen hier Instanzvariablen. • Eine Klasse kann als Bauplan für Objekte verstanden werden. • Eine konkrete Realisierung einer Klasse K bezeichnen wir als Instanz der Klasse K bzw. als Objekt der Klasse K . • Jedes Objekt verfügt über eine separate Version der Instanzvariablen. • Komponentenvariablen, die in der Deklaration mit static versehen sind, sind dagegen Klassenvariablen • Klassenvariablen beschreiben üblicherweise eine Eigenschaft der Klasse, nicht eines einzelnen Objektes Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 12 1. Klassen und Vererbung Klassen als strukturierter Datentyp Deklaration von Klassen Syntax zur Deklaration von Klassen (vorläufig): public class Klassenname { Variablendeklaration ... Variablendeklaration } Beispiel: public class Adresse { String name; String strasse; int hausnummer; int postleitzahl; String wohnort; String mail; String kommentar; } 1. Wir schreiben die Deklaration der Klasse Adresse in die Datei Adresse.java. 2. Wir compilieren Adresse.java. Damit entsteht Adresse.class. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 13 1. Klassen und Vererbung Klassen als strukturierter Datentyp 3. Nun steht uns der neue Datentyp Adresse zur Verfügung. Eine Variablendeklaration der Form: Adresse a; ist nun möglich. ☞ Mit Klassen sind stets Referenzdatentypen verbunden. ☞ Also steht uns nach Adresse a; noch kein Objekt der Klasse Adresse zur Verfügung. a Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 14 1. Klassen und Vererbung Klassen als strukturierter Datentyp Instanziierung von Objekten Instanzen einer Klasse erzeugen wir mit dem new-Operator: Variablenname = new Klassenname(); Man bezeichnet dies als Instanziierung. Beispiel: Adresse a; a = new Adresse(); Natürlich ist es auch möglich, Variablendeklaration und Instanziierung in einer Anweisung zu formulieren: Adresse a = new Adresse(); Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 15 1. Klassen und Vererbung Klassen als strukturierter Datentyp Initialisierung von Instanzvariablen a name ☞ Instanzvariablen werden (automatisch) initialisiert wie Komponenten von Feldern. Hier: • postleitzahl, hausnummer: 0 • und alle anderen Komponeten: null, da Referenzdatentypen. strasse hausnummer 0 postleitzahl 0 wohnort mail kommentar Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 16 1. Klassen und Vererbung Klassen als strukturierter Datentyp Zugriff auf Instanzvariablen Zugriff auf Komponenten eines Objekts: Variablenname.Komponentenname Beispiele: Adresse a = new Adresse(); a.wohnort = "Musterhausen"; a.postleitzahl = 47111; System.out.println( "PLZ: " + a.postleitzahl ); System.out.println( "Ort: " + a.wohnort ); Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 17 1. Klassen und Vererbung Klassen als strukturierter Datentyp Objekte und Referenzen Adresse a; Adresse b; a = new Adresse(); b = a; a name b • Klassen definieren Referenzdatentypen. • Konsequenz: Eine Zuweisung sorgt nicht dafür, dass ein Objekt kopiert wird. • Analog zu Feldern: Nur die Referenz wird kopiert! Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 strasse hausnummer 0 postleitzahl 0 wohnort mail kommentar 18 1. Klassen und Vererbung Klassen als strukturierter Datentyp Felder von Objekten Natürlich können wir auch Felder deklarieren, die als Komponententyp eine Klasse haben. Adresse[] adressen = new Adresse[20]; adressen • Der obige Aufruf erzeugt nur das Feld für die Referenzen auf Instanzen von Adresse, nicht die Objekte selbst. • Die Feldkomponenten sind mit null initialisiert (siehe rechts). • Erzeugung der Objekte: for (int i=0 ; i<20 ; i++) adressen[i] = new Adresse(); Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 adressen[0] adressen[1] adressen[2] adressen[17] adressen[18] adressen[19] 19 1. Klassen und Vererbung Prinzipien der objektorientierten Programmierung Prinzipien der objektorientierten Programmierung • Objekte waren bisher nichts weiter als schlichte Datenspeicher, Klassen die Baupläne dieser Datenspeicher. • In der objektorientierten Programmierung wird diese Sicht erweitert. • Objekte sind hier mehr als reine Datenspeicher, sie haben insbesondere: – einen Zustand und – weisen ein bestimmtes Verhalten auf. • Eine Klasse beschreibt die möglichen Zustände und das Verhalten der zugehörigen Instanzen. Sie ist eine abstrakte Beschreibung der Objekte (Instanzen). Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 20 1. Klassen und Vererbung Prinzipien der objektorientierten Programmierung Klassen als Modelle • In der Softwareentwicklung transferieren wir die Problemwelt auf den Computer, indem wir ein Modell der Problemwelt erstellen. • Klassen sind wesentliche Bestandteile dieses Modells. Jede Klasse ist ein Modell für eine Gruppe gleichartiger Objekte der Problemwelt. • Durch die Instanziierung von Klassen erhalten wir Objekte. Diese Objekte stellen auf dem Rechner das Äquivalent zu Gegenständen, Eigenschaften oder Personen unserer Problemwelt dar. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 21 1. Klassen und Vererbung Prinzipien der objektorientierten Programmierung Vorteile des objektorientierten Ansatzes • Ein Entwurf wird in unabhängige Komponenten (Klassen/Objekte) unterteilt, die zusammen das Gesamtsystem bilden. • Wie die Einzelteile eines Modellbaukastens werden diese Objekte zu einer Gesamtheit zusammengefügt. Vorteile: 1. Durch die Aufteilung in (kleine) unabhängige Komponenten wird die Komplexität verringert. 2. Die einzelnen Komponenten können unabhängig entwickelt und getestet werden. 3. Der Anbau weiterer Komponenten ist ohne besondere Probleme möglich. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 22 1. Klassen und Vererbung Prinzipien der objektorientierten Programmierung Grundpfeiler der objektorientierten Programmierung • • • • Generalisierung Vererbung Kapselung Polymorphismus Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 23 1. Klassen und Vererbung Prinzipien der objektorientierten Programmierung Kapselung Grundidee der Kapselung: Vgl. Hinweis auf der Rückseite eines Fernsehgeräts: Gerät steht unter Spannung und darf nur vom Fachmann geöffnet werden! 1. Das Öffnen des Geräts ist für Unbefugte nicht ungefährlich. Wollen Sie sich von einem Fernsehmechaniker am Blinddarm operieren lassen? 2. Normale Benutzer sollten nicht wissen müssen, wie der Fernseher intern funktioniert. Hierzu dienen die Bedienelemente als Schnittstelle zur Außenwelt. In der objektorientierten Programmierung: 1. Der interne Aufbau einer Klasse bleibt verborgen (Data Hiding). Zur Benutzung der Klasse reichen Kenntnisse über deren Schnittstelle. 2. Der interne Aufbau einer Klasse kann geändert werden, ohne dass dies Auswirkungen auf andere Teile der Software hat. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 24 1. Klassen und Vererbung Zugriffsrechte Vom Referenzdatentyp zur Objektorientierung Wir beginnen mit einer einfachen Klasse: /** Diese Klasse repräsentiert Studierende */ public class Student { Student name: nummer: /** Der Name des/der Studierenden */ String name; String int /** Matrikelnummer */ int nummer; } Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 25 1. Klassen und Vererbung Zugriffsrechte Ist das Grundprinzip der Kapselung erfüllt? • Haben wir die interne Struktur der Klasse Student von der Schnittstelle nach außen getrennt? • Könnten wir die Instanzvariablen einfach verändern, ohne hiermit Probleme zu verursachen? Nein! Wie können wir dies ändern? • Anpassung der Zugriffsrechte für die Instanzvariablen. • Deklaration von Zugriffsmethoden. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 26 1. Klassen und Vererbung Zugriffsrechte Zugriffsrechte • Data Hiding: Wir wollen unsere Instanzvariablen vor der Außenwelt verstecken. Bisher kann auch von anderen Klassen aus auf die Instanzvariablen von Student zugegriffen werden. • Dies wollen wir verhindern! Hierzu schränken wir die Zugriffsrechte auf die Variablen ein. • Zugriffsrecht wird private, d.h. nur innerhalb von Student ist ein Zugriff auf die Instanzvariablen möglich. Student -name: -nummer: String int public class Student { private String name; private int nummer; } Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 27 1. Klassen und Vererbung Zugriffsrechte Die Klasse AndereKlasse enthalte folgende Anweisungen: Student studi = new Student(); studi.name = "Karla Karlsson"; studi.nummer = 4711; Wenn wir versuchen, AndereKlasse zu übersetzen, erhalten wir einen Fehler der Form: Variable name in class Student not accessible from class AndereKlasse. Das heißt, die Zugriffe werden verweigert. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 28 1. Klassen und Vererbung Instanzmethoden Instanzmethoden Wie können wir Daten aus Objekten auslesen oder zuweisen, wenn wir hierzu nicht berechtigt sind? ☞ Wir erweitern die Klasse um Instanzmethoden. • Instanzmethoden sind beim Aufruf stets an ein Objekt gebunden, d.h. wir rufen eine Instanzmethode auf einem Objekt auf. • Innerhalb der Instanzmethode haben wir Zugriff auf alle Instanzvariablen. • Methoden haben ihre eigenen Zugriffsrechte. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 29 1. Klassen und Vererbung Instanzmethoden Syntax der Deklaration von öffentlichen Instanzmethoden: public Rückgabetyp Methodenname ( Parameterliste ) { Anweisungen } Beispiele: public String getName() { return name; } public int getNummer() { return nummer; } public void setName( String name ) { this.name = name; } Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 30 1. Klassen und Vererbung Instanzmethoden public void setNummer( int n ) { nummer = n; } Darstellung als Klassendiagramm: Student -name: -nummer: +getName() +getNummer() +setName(String) +setNummer(int) Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 String int String int void void 31 1. Klassen und Vererbung Instanzmethoden Was ist this? • Das Schlüsselwort this liefert innerhalb einer Instanzmethode immer eine Referenz auf das Objekt selbst, d.h. auf das Objekt, auf dem die Methode aufgerufen wurde. • Das Schlüsselwort this wird insbesondere dann benötigt, wenn Instanzvariablen durch Namenskonflikte verdeckt werden. Beispiel: Die Methode setName. Der formale Parameter name verdeckt die Instanzvariable name. • Mittels this.name können wir die Instanzvariable ansprechen. • Der Compiler “denkt ein wenig mit”. Liegen keine Namenskonflikte vor, ist die Angabe von this nicht notwendig. "Karla Karlsson" name nummer 4711 this Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 32 1. Klassen und Vererbung Instanzmethoden Instanzmethoden als erweiterte Funktionalität Wir wollen die Menge der gültigen Matrikelnummern einschränken: Eine Matrikelnummer sei dann gültig, wenn sie fünf Stellen sowie keine führenden Nullen hat und ungerade ist. Hierfür definieren wir eine Methode validateNummer: private boolean validateNummer() { return (nummer >= 10000 && nummer <= 99999 && nummer % 2 != 0); } Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 33 1. Klassen und Vererbung Instanzmethoden Wir können nun unsere Methode setNummer so ändern, dass nur noch gültige Matrikelnummern akzeptiert werden. public void setNummer(int n) { int alteNummer = nummer; nummer = n; if (!validateNummer()) nummer = alteNummer; } Bemerkungen: • höhere Funktionalität • ohne Datenkapselung nicht möglich • keine Änderung an der Schnittstelle der Klasse Student Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 34 1. Klassen und Vererbung Instanzmethoden Beispiel: Ausgabe von Objekten auf der Konsole: public String toString() { return name + " (" + nummer + ")"; } Nutzung: Student studi = new Student(); studi.setName("Karla Karlsson"); studi.setNummer(12345); System.out.println(studi.toString()); Prinzipiell reicht sogar die Anweisung: System.out.println(studi); Grund: Jedes Objekt verfügt automatisch über eine Methode toString, die für die Ausgabe genutzt wird. Wir haben hier toString überschrieben. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 35 1. Klassen und Vererbung Statische Komponenten einer Klasse Klassenvariablen • Wir wollen die Zahl der instantiierten Studentenobjekte zählen. • Dies ist jedoch keine Eigenschaft eines einzelnen Objektes. Vielmehr gehört die Eigenschaft zu der Gesamtheit aller Studentenobjekte. • Eigenschaften einer Klasse als Ganzes werden durch Klassenvariablen repräsentiert. Syntax für eine private Klassenvariable: private static Datentyp Variablenname = Initialwert; Beispiel: private static int studZaehler = 0; • Das Schlüsselwort static “macht” eine Variable zur Klassenvariable. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 36 1. Klassen und Vererbung Statische Komponenten einer Klasse • Wenn die Zugriffsrechte nicht dagegen sprechen, erfolgt der Zugriff auf eine Klassenvariable durch: Klassenname.Variablenname • Beispiel: Wäre studZaehler öffentlich, könnte man den Zählerwert wie folgt ausgeben: System.out.println(Student.studZaehler); Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 37 1. Klassen und Vererbung Statische Komponenten einer Klasse Klassenmethoden Für das Auslesen des Studentenzählers definieren wir eine öffentliche Klassenmethode: public static int getZaehler() { return studZaehler; } Nutzung der Klassenmethode: System.out.println(Student.getZaehler()); • Eine Klassenmethode ist eine Methode, die der Klasse als Ganzes zugeordnet ist. • In Klassenmethoden ist kein Zugriff auf Instanzvariablen möglich. • Nutzung allgemein: Klassenname.Methodenname( aktuelle Parameter ) Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 38 1. Klassen und Vererbung Statische Komponenten einer Klasse Wie können wir dafür sorgen, dass bei der Erzeugung von Objekten der “Studentenzähler” korrekt erhöht wird? Eine (noch nicht optimale) Möglichkeit: Wir “verpacken” die Erzeugung in einer Klassenmethode: public static Student createStudent() { studZaehler = studzaehler + 1; return new Student(); } Nutzung: Student studi = Student.createStudent(); System.out.println(Student.getZaehler()); Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 39 1. Klassen und Vererbung • Warum nicht optimal? Bei älteren Programmen, die new verwenden, wird der Zähler nicht erhöht. • Wie können wir dies abstellen? Verwendung eines sogenannten Konstruktors (siehe folgendes Thema). • Klassenmethoden und Klassenvariablen werden in Klassendiagrammen durch Unterstreichung gekennzeichnet. Statische Komponenten einer Klasse Student -name: -nummer: -studZaehler: +getName() +getNummer() +setName(String) +setNummer(int) +validateNummer() +toString() +getZaehler() +createStudent() Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 String int int String int void void boolean String int Student 40 1. Klassen und Vererbung Statische Komponenten einer Klasse Vereinbarung von Konstanten • Die Vereinbarung von Konstanten erfolgt innerhalb einer Klassendefinition. • Konstanten sind Klassenvariablen. • Durch das Schlüsselwort final wird die nachträgliche Änderung des Variablenwerts verboten. Syntax für die Deklaration öffentlicher Konstanten: public static final Datentyp Variablenname = Initialwert; Beispiel: Definition von Konstanten für Studienfächer: public static final int MATHEMATIK STUDIUM = 1; public static final int INFORMATIK STUDIUM = 2; public static final int BIOLOGIE STUDIUM = 3; ... Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 41 1. Klassen und Vererbung Statische Komponenten einer Klasse • Der Zugriff erfolgt wie bei Klassenvariablen: Klassenname.Variablenname • Es hat sich die Schreibweise durchgesetzt, dass Konstantennamen ausschließlich aus Großbuchstaben und Unterstrichen bestehen. Teilwörter werden durch einen Unterstrich getrennt (siehe Java Code Conventions). • Die Zuweisung an eine als Konstante vereinbarte Variable führt zu einem Fehler bei der Übersetzung. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 42 1. Klassen und Vererbung Konstruktor Konstruktor • Mit Konstruktoren können wir Einfluss auf die Erzeugung eines Objektes nehmen. • Konstruktoren erlauben es, den Anfangszustand von Objekten zu parametrisieren. • Der Aufruf des/der Konstruktoren erfolgt implizit bei Anwendung von new. Syntax für einen öffentlichen Konstruktor: public Klassenname( Parameterliste ) { Anweisungen } • Ein Konstruktor heißt immer so wie die Klasse. • In der Parameterliste werden Argumente vereinbart. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 43 1. Klassen und Vererbung Konstruktor Der einfachste Konstruktor für Student lautet: public Student() { } Allgemein ist dies der Standard-Konstruktor. Er wird automatisch angelegt, wenn kein Konstruktor definiert wurde (aber nur genau für diesen Fall). Wir sollten im Konstruktor dafür sorgen, dass automatisch der Zähler für die Studentenobjekte erhöht wird. public Student() { studZaehler = studZaehler + 1; } Damit ist die Klassenmethode createStudent überflüssig. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 44 1. Klassen und Vererbung Konstruktor Überladen von Konstruktoren • Wie Methoden können auch Konstruktoren überladen werden. • D.h., es darf mehrere Konstruktoren zu einer Klasse geben. • Diese müssen sich aber in ihrer Signatur unterscheiden. Weiterer möglicher Konstruktor für Student: public Student(String name, int nummer) { studZaehler = studZaehler + 1; this.name = name; this.nummer = nummer; } Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 45 1. Klassen und Vererbung Konstruktor • Innerhalb eines Konstruktors darf als erste Anweisung ein anderer Konstruktor aufgerufen werden. • Dies erfolgt durch das Schlüsselwort this in Verbindung mit aktuellen Parametern. Beispiel: public Student(String name, int nummer) { this(); this.name = name; this.nummer = nummer; } Nutzung: Student student = new Student("Jo Mustermann", 90815 ); Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 46 1. Klassen und Vererbung Statische Komponenten einer Klasse Der statische Initialisierer • Ein Konstruktor ermöglicht uns eine objektbezogene Initialisierung. • Gibt es auch eine klassenbezogene Initialisierung? • Ja, mit Hilfe des statischen Initialisierers. Syntax: static { Anweisungen } Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 47 1. Klassen und Vererbung Statische Komponenten einer Klasse • Ein statischer Initialisierer einer Klasse wird ausgeführt, wenn die Klasse geladen wird. • Eine Klasse wird vom Class Loader der virtuellen Maschinene geladen, wenn sie das erste mal benötigt wird. • Eine Klasse kann mehrere statische Initialisierer haben. Für die Anweisungen gelten die Einschränkungen eines statischen Kontextes: • Nur Zugriff auf statische Komponenten einer Klasse. • Zugriff auf alle (auch private) Teile einer Klasse. • Weitere Einschränkung: Statische Initialisierer haben nur Zugriff auf statische Komponenten, die im Programmcode vor ihnen definiert wurden. ☞ Beispiel zum statischen Initialisierer. Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 48 1. Klassen und Vererbung Beispiele Nutzen der Kapselung Wenn wir Programmlogik in einem Objekt kapseln, dann können wir 1. diese Logik mehrmals zur gleichen Zeit einsetzen, Beispiel: gleichzeitig mehrere unabhängige Objekte 2. in unterschiedlichen Zusammenhängen nutzen und Beispiel: Programm zum Zählen von Zeilen, Wörtern und Zeichen 3. komplexere Objekte aus einfacheren Objekten zusammensetzen. Beispiel: Die Klassen TorZaehler und Spielstand ☞ Leichtere Wiederverwendbarkeit Peter Becker, Datenstrukturen und Algorithmen — Hochschule Bonn-Rhein-Sieg, SS 2010 49