[=#point1] = Java Grundlagen = [[PageOutline(1-2)]] '''ACHTUNG! ''' Diese Seite ist obsolet und wird demnächst entfernt. == Klassen == Definition:: Eine Klasse ist eine Schablone für das Erzeugen einer beliebigen Anzahl gleichartiger Objekte und umfasst die gemeinsamen Attribute und Methoden der Objekte. * selbst-definierte Datentypen. * können verschiedene bestehende Datentypen zu einem neuen, übergeordnetem Datentyp zusammenfassen. * können nicht nur Daten, sondern insbesondere Daten und die darauf arbeitenden Operationen zusammenfassen. === Syntax der Klassendefinition === Allgemeine Form:: ''sichtbarkeitsmodifikator class Klassenname {...}'' {{{ #!java public class Bruch { int n; // Nenner int z; // Zaehler void addieren(Bruch b) { n = n * b.d + b.n * d; d = d * b.d; } void teilen(Bruch b) { int n0 = n * b.d; d = d * b.n; n = n0; } } }}} [[BR]] == Objekte == Definition:: Ein Objekt ist eine Instanz (ein Exemplar) einer Klasse. Es wird bei Bedarf aus der Klasse abgeleitet. Ein Objekt besteht aus **Attributen** und **Methoden**. === Attribut === Attribute sind "Behälter" für Werte, die den internen Objektzustand mitbestimmen. === Methoden === Eine [#point2 Methode] ist eine Funktion eines Objektes, die nicht unbedingt von außen sichtbar sein muss. Die Menge aller Methoden bildet die Schnittstelle eines Objektes. Beispiel:: Bücher in einer Bibliothek können sehr verschieden sein.\\ Dennoch haben alle Bücher gleiche Eigenschaften (''Attribute'') und ''Methoden''. * ''Attribute'': Autoren, Titel, Verlag, Erscheinungsjahr, Seitenanzahl, ... * ''Methoden'': ausleihen, zurückgeben, vormerken, verlängern, ... === Erzeugen und Verwenden von Objekten === * Erzeugung von Objekten (Instanziierung) mit Schlüsselwort `new` {{{ #!java Buch javaLehrbuch = new Buch("Müller, Peter", "Java Grundlagen", 1999); Konto konto1 = new Konto("12345", "Meier, Fred"); }}} * Zugriff auf Objektmethoden und öffentliche Attribute mit Punktnotation {{{ #!java javaLehrbuch.setJahr(2000); konto1.setGuthaben(42000); kontonummer = konto1.getKontonr(); }}} Ein explizites Zerstören eines Objektes nach der Verwendung ist nicht notwendig. === Getter-/Setter-Methoden === Der direkte Zugriff auf Variablen eines Objektes ist nicht so elegant. Es ist besser, über Methoden auf Variablen zuzugreifen. Diese Methoden müssen die Objekt-Eigenschaften formulieren und die Werte der Variablen zurückliefern. Allgemeine Form:: {{{ #!java rueckgabetyp getAttributname( return this.attributname; } datentyp setAttributname(datentyp wert) { this.attributname = wert; } }}} Beispiel:: {{{ #!java class Rechteck { private double laenge, breite; void setLaenge(double l) { this.laenge = l; } double getLaenge() { return this.laenge; } void setBreite(double b) { this.breite = b; } double getBreite() { return this.breite; } }}} [#point1 Zurück zum Inhaltsverzeichnis] [[BR]] == Referenzvariable == In [#point3 primitiven Datentypen] werden in den Variablen unmittelbar die Werte (Zahlen, Zeichen,...) gespeichert. Strings, Arrays und Objekte sind hingegen **Referenztypen**. Sie werden nicht als Ganzen in einer Variablen gespeichert. In der Variablen steht vielmehr die Speicheradresse, unter der die Objektdaten zugreifbar sind. Daher spricht man auf von **Referenzvariablen**. * In Java ist die Speicheradresse nicht auslesbar. * Der Default-Wert von Referenzvariablen ist `null`. === Objektzugriffe === Allgemeine Form:: ''Typ referenzvariable;''\\''referenzvariable = new Typ();'' Beispiele:: {{{ #!java Rechteck r1; // Referenzvariable r1 ist vom Typ Rechteck r1 = new Rechteck(); // r1 verweist auf ein Objekt vom Typ Rechteck r1.laenge = 5; r1.breite = 2; r1 = null; // Objekt nicht mehr benutzbar }}} {{{ #!java // Direkte Instanziierung Kreis k1 = new Kreis(); k1.radius = 1,42; }}} {{{ #!java Dreieck d1 = new Dreieck(); Dreieck d2 = new Dreieck(); Dreieck d3 = new Dreieck(); d1.a = 5,1; d1.b = 12,5; d1.c = 2; d2 = d1; d3.b = d1.a; d2 = null; }}} [#point1 Zurück zum Inhaltsverzeichnis] [[BR]] == Methoden und Parameter == Um Daten in eine Methode zu übergeben, werden diese in die Parameterliste des Methodenaufrufs geschrieben. Inhalt der Parameterliste: * Typen der Parameter, die der Methode übergeben werden müssen. * Namen, unter denen sie in der Methode angesprochen werden. Ablauf eines Methodenaufrufs:: 1. Parameter auswerten \\ Paramterübergabe: übergebenen Werte werden den Methodenparametern automatisch zugewiesen. 2. Rumpf ausführen \\ Der Rumpf der aufgerufenen Methode wird ausgeführt. 3. Ergebnis zurückgeben \\ Die Ausführung des Methodenrumpfs liefert einen Ergebniswert zurück, der dem Rückgabedatentyp entspricht. Eine `return`-Anweisung beendet Methodenausführungen und gibt den Wert zurück. Es können auch mehrere `return`-Anweisungen verwendet werden. Beispiel:: Parameterübergabe per Wert || ||= aufrufender Teil =||= `methode(int innen)` =||= aussen =||= innen =|| || 1. || `int aussen = 42;` || || 42 || || || 2. || `methode(aussen);` || || ''42'' || 42 || || 3. || || `innen = 84;` || ''42'' || 84 || || 4. || || || 42 || || * beim Aufrufen einer Methode werden die Parameterwerte vom aufrufenden Programmteil in die Methode kopiert. * ''Wertänderungen der Parameter in der Methode wirken sich nicht auf Werte außerhalb der Methode aus! '' Parameterübergabe per Referenz * beim Aufrufen einer Methode werden die Verweise auf die Objekte vom aufrufenden Teil in die Methode kopiert, nicht die Objekte selbst. * ''Veränderungen der Objekte in der Methode wirken sich außerhalb der Methode aus! '' === Überladen von Methoden === [=#point5] Die Implementierung von Methoden mit gleichem Rückgabetyp und Methodenname, aber unterschiedlicher Parameterliste, nennt man ''Überladen''. Dadurch ergibt sich die Möglichkeit, unterschiedliche Methodenverhalten in Abhängigkeit von den gegebenen Parametern zu definieren. Beispiel:: {{{ #!java public int verlaengern(int tage) { ... leihFrist = leihFrist + tage; ... } public int verlaengern() { ... leihFrist = leihFrist + Standartwert; ... } }}} [#point1 Zurück zum Inhaltsverzeichnis] [[BR]] == Konstruktoren == Zur Objekterzeugung gibt es besondere Methoden, die sogenannten Konstruktoren. Dabei wird zwischen dem Default-Konstruktor und parametrisierbaren Konstruktoren unterschieden. \\ Es müssen jedoch keine Objekte explizit gelöscht werden. Das Java-System erkennt automatisch, dass ein Objekt nicht mehr von einer Variable direkt oder indirekt erreichbar ist und der Garbage-Collector sammelt diese auf. Mit dem Schlüsselwort `new` und dem Aufruf eines Konstruktors werden Objekte erzeugt. Allgemeine Form:: ''Klassenname variablenamen = new Klassenname();'' \\ ''Klassenname variablenamen = new Klassenname(datentyp name, ... );'' \\ ''Klassenname variablenamen = new Klassenname( Klassenname name );'' === Default-Konstruktor === Jede Klasse hat immer einen Konstruktor, auch wenn er nicht explizit implementiert wurde. (Default-Konstruktur) Allgemeine Form:: {{{ #!java public class Klassenname { datentyp attribut; public Klassenname() { } } }}} === Parametrisierte Konstruktoren === Parametrisierte Konstruktoren werden häufig für die Initialisierung von Attributen beim Erzeugen eines Objektes genutzt und können, wie Methoden, überladen werden. \\ ''Wenn ein parametrisierter Konstruktor definiert wurde, muss der Konstruktor ohne Parameter explizit definiert werden! Allgemeine Form:: {{{ #!java public class Klassenname { datentyp attribut; public Klassenname() { } // Parametrisierte Konstruktor public Klassenname(datentyp d) { this.attribut = d; } } }}} === Copy-Konstruktor === Der Copy-Konstruktor erlaubt es, typgleiche Objekte als Parameter zu übergeben. So lässt sich ein Objekt mit den Werten des Übergebenen instanziieren. Allgemeine Form:: {{{ #!java public class Klassenname { datentyp attribut; public Klassenname() { } // Copy-Konstruktor public Klassenname(Klassenname k) { this.attribut = k.attribut; } } }}} [#point1 Zurück zum Inhaltsverzeichnis] [[BR]] == Gültigkeitsbereich von Bezeichnern == Es besteht bei großen Programmen ein Problem, da verschiedene Ebenen erkennbar sind und überall Bezeichner benötigt werden. Die Ebenen eines Java-Programms sind hierarchisch angeordnet: 1. Dateien xxx.java 2. Klassendeklarationen ''(Klassennamen, Konstruktoren)'' 3. Methodendeklarationen ''(Namen von Methoden, formalen Parametern, lokalen Variablen)'' 4. Schleifen ''(Variablen zur Steuerung von Schleifen, Namen von Anweisungen)'' Die Gültigkeitsbereiche orientieren sich an syntaktischen !Kontext/Struktur: * Alle Bezeichner müssen vereinbart werden. * Jede Vereinbarung wird dem Block zugeordnet, in der sie geschrieben ist. * Bei mehreren verschachtelten Blöcken, gehört die Vereinbarung zum innersten Block. * Vereinbarung vor Benutzung. * Ein Bezeichner kann erst nach seiner Vereinbarung verwendet werden. * Ausnahmen: Klassen und Methoden innerhalb des gleichen Blocks. * Gültigkeit bis an das Ende des Blocks, welcher die Vereinbarung enthält. * Ein Bezeichner gilt nur bis zu der schließenden geschweiften Klammer des betreffenden Blocks. * Namensgleiche Bezeichner in einem inneren Block überdecken die aus einem äußeren Block. * In einem Block müssen alle Bezeichner verschieden sein. * Bezeichner im selben Block müssen verschiedene Namen aufweisen, damit man sie unterscheiden kann. * Ausnahmen: [#point5 Überladen von Methoden] und verschiedenartige Programmelemente ''(Klassen, Methoden,Variablen)'' [#point1 Zurück zum Inhaltsverzeichnis] [[BR]] == Erweiterung einer Klasse == Die ursprüngliche Idee für die Klassenbildung war, eine Einheit aus Daten und Methoden zu bilden und dies in einer Informationsstruktur mit einem engen Bezug zur Problemstellung darzustellen. \\ In Java lassen sich allgemeine und spezielle Eigenschaften durch Ober- und Unterklassen darstellen, wobei gemeinsame/allgemeine Eigenschaften (!Variablen/Methoden) in Oberklassen definiert werden. Begriffe:: ''Unterklasse'': Eine Klasse, die erweitert. \\ ''Oberklasse'': Eine Klasse, die erweitert wird. \\ ''Extension einer Klasse'': Die Menge aller Objekte einer Klasse und all ihrer Unterklassen. \\ ''Vererbung'': Das Konzept der Erweiterung von Klassen. Beispiel:: ''Eine Reise, Bahnreise, Flugreise'' {{{ #!java public class Reise { DatumsTyp datum; Ort start, ziel; } }}} {{{ #!java public class Bahnreise extends Reise { ZugTyp zug; } }}} {{{ #!java public class Flugreise extends Reise { FlugzeugTyp flugzeug; } }}} * Die Klasse Reise ist die Oberklasse von Flugreise und Bahnreise. * Die Klassen Flugreise und Bahnreise heißen Erweiterung der Klasse Reise. === Zuweisungskompatibilität === Der Zugriff auf Objekte erfolgt über Verweis-Variablen. * Objekte der Unterklasse sind auch Objekte der Oberklasse: \\ `Bahnreise extends Reise` * Variable haben aber einen deklarierten Typ und verweisen auf Objekte ihres Typs: \\ `Bahnreise b; Reise r;`\\ `Bahnreise b = new Bahnreise();` * Es ist jedoch auch erlaubt, dass Variablen auf Objekte ihrer jeweiligen Unterklassen verweisen: \\ `r = b;` Welche Attribute stehen dann in dieser Situation zur Verfügung? ==== Widening ==== Ein Objekt von einer Oberklasse her zu betrachten, wird Widening genannt. \\ Dadruch sind die speziellen Eigenschaften der Unterklassen nicht mehr sichtbar (implizite Typ-Anpassung): {{{ #!java Bahnreise b = new Bahnreise(); Flugreise f = new Flugreise(); Reise r = f; ... r.flugzeugtyp ... // unzulässig. }}} ==== Narrowing ==== Ein Objekt von einer Unterklasse her zu betrachten, wird Narrowing genannt. \\ Dabei muss die Typanpassung explizit angegeben werden: {{{ #!java Bahnreise b; b = (Bahnreise) r; }}} Der Effekt ist eine Typüberprüfung zur Laufzeit: \\ Bevor die Zuweisung vollendet wird, geschieht die Überprüfung, ob das aktuelle Objekt r vom Typ Bahnreise oder eine Unterklasse davon ist. Mit `instanceof` kann ein expliziter Test auf Klassenzugehörigkeit formuliert werden: {{{ #!java if (r instanceof Flugreise) { f = (Flugreise) r; ... } else if (r instanceof Bahnreise) { b = (Bahnreise) r; ... } else { System.out.println("r verweist auf ein Objekt" + " unbekannten Typs."); } }}} \\ Beispiele:: * `Bahnreise bahn1 = new Bahnreise();` \\ ''legal'': Variable vom Typ Bahnreise erhält Referenz auf Bahnreise Instanz * `Bahnreise bahn2 = new Flugreise();` \\ ''illegal'': Bahnreise hat keine Vererbungsbeziehung zu Flugreise. * `Reise reise = new Bahnreise();` \\ '' legal'': reise hat aber keinen Zugriff auf Bahnreise-spezifische Elemente. (Widening) * `Bahnreise bahn3 = new Reise();` \\ ''illegal'': In Reise sind nicht alle Elemente deklariert, die für Bahnreise benötigt werden. * `Bahnreise bahn4 = reise;` \\ ''illegal'': zur Compile-Zeit nicht erkennbar, ob es zur Laufzeit noch passt. * `Bahnreise bahn5 = (Bahnreise) reise;` \\ ''legal'': Narrowing. [#point1 Zurück zum Inhaltsverzeichnis] [[BR]] == Erzeugung von Objekten == Welche Rolle spielen Ober- und Unterklassen bei der Erzeugung von Objekten? \\ `new` liefert ein neues Objekt der jeweiligen Klasse, dabei werden die Konstruktoren aller Oberklassen aufgerufen. Konstruktor-Aufrufreihenfolge:: 1. Konstruktor der Oberklasse - rekursive Wiederholung bis zum leeren Konstruktor von ''Object'', der zuerst ausgeführt wird. 2. Konstruktoren der Attribute in der Reihenfolge ihrer Deklaration. 3. Rumpf des Konstruktors. ''Begründung'': Bei Instanziierung eines Objektes muss es möglich sein, auf Attribute der Oberklasse und auf die eigenen Attribute zuzugreifen. === this(...) & super(...) === Mit `this(...)` kann ein Konstruktor innerhalb der Klasse benutzt werden. \\ Mit `super(...)` können Konstruktoren der Oberklassen benutzt werden. Bedingungen: * `this(...)` oder `super(...)` müssen als erste ausführbare Anweisung in einem Konstruktor erscheinen. * `this(...)` und `super(...)` schließen sich gegenseitig aus. Beispiel:: {{{ #!java class Punkt2D { int x, y; Punkt2D() { // this(0,0) ruft den zweiten Konstruktor mit den Werten 0 und 0 auf this(0, 0); } Punkt2D(int x, int y) { this.x = x; this.y = y; } } }}} {{{ #!java class Punkt3D extends Punkt2D { int z; Punkt3D() { // this(0,0,0) ruft den zweiten Konstruktor mit den Werten 0 und 0 auf this(0, 0, 0); } Punkt3D(int x, int y, int z) { // super(x,y) ruft den zweiten Konstruktor aus der Klasse Punkt2D auf super(x, y); this.z = z; } } }}} [#point1 Zurück zum Inhaltsverzeichnis] [[BR]] == Verdecken und Überschreiben == Die Verwendung von identischen Namen in einer Erweiterung muss geregelt sein. Deshalb gilt: * Bei Variablen: \\ Deklaration in Unterklasse ''verdeckt'' die Deklaration in der Oberklasse. \\[[BR]] Beim Verdecken von Variablen ist (zunächst) nur die aktuelle Unterklassen-Variable sichtbar. {{{ #!java class Ober { // int abc ist durch boolean abc verdeckt. int abc; } }}} {{{ #!java class Unter extends Ober { boolean abc; } }}} Die verdeckten Variablen einer Oberklasse können mittels einer Variable des Typs der Oberklasse sichtbar gemacht werden. {{{ #!java Unter U = new Unter(); Ober O = U; O.abc = 2; U.abc = true; }}} [[BR]] * Bei Methoden: \\ Deklaration in Unterklasse ''überschreibt'' die Deklaration in der Oberklasse. \\[[BR]] Methoden sind identisch, wenn der Methodenkopf identisch ist. Es wird immer die speziellste Version einer Methode aufgerufen. Dieses Prinzip nennt man ''Späte Bindung.'' {{{ #!java class Ober { int b(boolean x) { if (x) { return 0; } return 42; } } }}} {{{ #!java class Ober { int b(boolean y) { if (y) { return 22; } return 1; } } }}} Die überschriebene Methode lässt sich durch den Selbstverweis `super.` aus Sicht der Oberklasse aufrufen. {{{ #!java class Ober { int b(boolean y) { return super.b(y); } } }}} [#point1 Zurück zum Inhaltsverzeichnis] [[BR]] == Verwendung einer Erweiterung == Die Erweiterungskonzepte für Methoden in Java: ''statisch'' und ''dynamisch'' * Statische Erweiterung von Methoden (Überschreiben): \\ -Methodenkopf identisch in Ober- und Unterklasse. \\ -Späte Bindung. * Dynamische Erweiterung:\\ -Es wird einer Methode der Oberklasse aus dem Rumpf einer Methode der Unterklasse aufgerufen.\\ -Benutzung des Selbstverweises `super.xx(yy)`. === Anonyme Erweiterung === Wenn von einer Unterklasse nur ein Objekt benötigt wird, kann man einer sogenannte einmalige (anonyme) Erweiterung benutzen. Beispiel:: {{{ #!java class Ober { int tuWas(int a) { return a * a; } } }}} {{{ #!java ... Ober o = new Ober() { int tuwas(int a) { // Spezielle, einmalige Erweiterung: return a + 42; } }; ... }}} [#point1 Zurück zum Inhaltsverzeichnis] [[BR]] == Beziehungen zwischen Klassen == Es gibt verschiedene Formen der Beziehung zwischen Klassen. 1. Statische Erweiterung (benannt oder anonym)\\ Methode der Oberklasse wird überschrieben. 2. Dynamische Erweiterung \\ Rückgriff auf Methode der Oberklasse mit `super.`. 3. Benutzt-Beziehung\\ Ein Objekt vom Typ X benutzt ein Objekt vom Typ Y: {{{ #!java public class X { // Referenzvariable ermöglicht Benutzt-Beziehung Y y; public void def() { // Benutzt-Beziehung zu einem neuen Objekt Y y = new Y(); y.abc(); y.test(); } } }}} 4. Erweiterung durch Delegation \\ Die Eigenschaften einer Klasse werden von einer anderen Klasse importiert und dann an spezifischen Stellen speziell erweitert: {{{ #!java class Speziell { Allgemein temp; public Speziell() { temp = new Allgemein(); } void abc() { ... // Delegation an Allgemein. temp.abc(); ... } } }}} Vorteil: Eigenschaften können währen der Laufzeit geändert und allgemeine Strukturen können spezialisiert werden. [#point1 Zurück zum Inhaltsverzeichnis] [[BR]]