Java Schulung Objektorientierte Programmierung in Java Teil III: Java Klassen und Objekte Prof. Dr. Nikolaus Wulff Referenzvariablen • • Referenzvariablen verweisen auf (unbenannte) Objekte im Speicher. Uninitialisierte Variablen enthalten eine Null-Referenz (null). a Point a,b; • Objekt a b null Objekte werden zur Laufzeit erzeugt. :Point new Point(0,0); 0 0 a = new Point(1,2); a • Objekte sind anonym. • Sie haben eine unveränderliche Identität. Prof. Dr. Nikolaus Wulff a:Point 1 2 2 Binden von Referenzvariablen • Beim Zuweisen eines Objektes auf eine Variable wird eine Referenz zugewiesen: a = new Point(0,0); b = new Point(3,4); • b Beim Zuweisen von Referenzvariablen aufeinander werden nur die Referenzen kopiert, nicht die Objekte: a = b; • a Vorsicht! Das Objekt kann jetzt über zwei Pfade geändert werden: a.set(5,6); b.set(20,30); Prof. Dr. Nikolaus Wulff a b 0 0 3 4 0 0 3 4 20 30 3 Vergleichen von Objekten • Beim Vergleichen zweier Referenzvariablen werden die Referenzen verglichen, nicht die referenzierten Objekte. Es wird also auf Objektidentität, nicht auf Inhaltsgleichheit geprüft. a b == (shallow equality) c d Prof. Dr. Nikolaus Wulff 3 4 3 4 a == b a != c d == null 4 Inhaltsvergleich • Der Inhalt von zwei Objekten wird durch die Methode equals verglichen. equals (deep equality) new Integer(1) != new Integer(1) new Integer(1).equals(new Integer(1)) • Bei selbstdefinierten Klassen vergleicht equals (geerbt von Object) per Default auch nur die Objektidentiät, d.h. verwendet shallow equality. • Die Methode equals muss selbst neu überladen werden, damit sie die gewünschte Funktionalität bietet. Prof. Dr. Nikolaus Wulff 5 Eine Klasse class Cell { private int value = 0; void setValue(int newValue){ this.value = newValue; } int getValue( ) { return this.value; } ... Attribut Methoden Klassendefinition } Prof. Dr. Nikolaus Wulff 6 Bestandteile einer Klasse • Einleitung einer Klasse durch Schlüsselwort class: class EineKlasse { /* Klassenrumpf */ } • Erzeugung eines Objektes mit new: EineKlasse a = new EineKlasse(); • Attribute: class Datengrab { int i; float f; boolean b; } Prof. Dr. Nikolaus Wulff Jede Instanz (jedes Objekt) der Klasse Datengrab bekommt eine eigene Kopie der Attribute. 7 Methoden • Methoden: Verhalten eines Objekts – Inspizieren und ändern den Zustand eines Objekts. – Berechnen von Rückgabewerten. – Anstoßen von weiteren Methoden über das Senden von Nachrichten an andere Objekte. – In anderen Sprachen: Funktionen oder Prozeduren. – Nur innerhalb von Klassen (keine Methode ohne Klasse!). • Aufruf einer Methode = Senden einer Nachricht: int x = a.f(); Prof. Dr. Nikolaus Wulff rufe Methode f von Objekt a bzw. sende Nachricht f an Objekt a 8 Organisatorisches • Hands on Tutorien jeweils Mittwochs 15 – 17 Uhr im Lab4Inf D117 mit Rene Brüggemann. • Praktikumverschiebung Informatiker Donnerstags von 13 – 17 auf 14 – 18 Uhr Pool 3. Prof. Dr. Nikolaus Wulff 9 Parameterliste • Die Parameterliste spezifiziert die Typen der Objekte, die als Argumente an die Methode übergeben werden können. • Jedes Objekt (Parameter) erhält einen lokalen Namen, über den innerhalb der Methode auf das Objekt zugegriffen werden kann. Typ Name void reset(Cell c) { c.setValue(0); } • Beachte: Es werden nicht die Objekte (Kopien), sondern Referenzen auf die Objekte übergeben! Cell aCel1 = new Cell(); aCel1.setValue(4); reset(aCel1); aCel1.getValue(); ==> 0 Prof. Dr. Nikolaus Wulff 10 Rückgabewerte • Methoden können Werte bzw. Objektreferenzen zurückliefern: Rückgabetyp Integer berechne(int a, int b) { int sum = a + b; return new Integer(sum); } Rückgabe • Mit return wird die Methode sofort verlassen - auch wenn noch Befehle dahinter folgen! • Methoden ohne Rückgabe haben den Rückgabetyp void: void print(int a, int b) { System.out.println("a + b = " + (a + b)); } Prof. Dr. Nikolaus Wulff 11 Methodenrumpf • Variablendeklarationen und -zuweisungen. void berechne(int a, int b) { int i; Deklaration i = a * b; Zuweisung } • Beachte: Lokale Variablen müssen initialisiert werden! void berechne() { int i; i++; Compile-Fehler! } Prof. Dr. Nikolaus Wulff 12 Statische Attribute und Methoden • Attribute und Methoden, die nicht für einzelne Objekte, sondern für die ganze Klasse definiert sind. • D.h., jedes Attribut kommt nur einmal pro Klasse vor! class Person { static int erzeugtePersonen = 0; static void printStatistik() { System.out.println("Erzeugt: " + erzeugtePersonen); } } • Statische Attribute und Methoden können genutzt werden, ohne Objekte zu erzeugen. Der Zugriff erfolgt über den Klassennamen: Person.printStatistik(); Person.erzeugtePersonen++; Prof. Dr. Nikolaus Wulff Vergleiche: • System.out.println(...); • main 13 Statische Attribute class Person { static int erzeugtePersonen = 0; String name; } Person erzeugtePersonen = 0 Klasse mit statischen Attribut das Objekt kennt seine Klasse peter mary class class name = „Peter“ name = „Mary“ Objekte mit Attributen Prof. Dr. Nikolaus Wulff 14 Konstanten • Konstanten = finale Werte von Basisdatentypen – Konstanten zur Übersetzungszeit werden vom Compiler ausgewertet: final int ANZAHL_MONATE = 12; – Konstanten zur Laufzeit sind nach der Initialisierung unveränderlich: final int ZUFALL = (int)(Math.random() * 20); • final static: nur ein Wert pro Klasse, der seinen Wert nach dem ersten Laden der Klasse nicht mehr ändert. • finale Referenzen: können nicht mehr an andere Objekte gebunden werden final Integer NULL = new Integer(0); Prof. Dr. Nikolaus Wulff 15 this und super • Auf das aktuelle Objekt, auf dem die Methode ausgeführt wird (an das die Nachricht gesendet wurde), kann über das Schlüsselwort this zugegriffen werden. • Auf das direkte Elternobjekt kann innerhalb einer Methode mit super zugegriffen werden. Prof. Dr. Nikolaus Wulff 16 Überladen von Methoden • Eine Nachricht mit mehreren Signaturen und Methodenimplementationen. class SuperWasher { void wash(Shirt s) { ... } void wash(Car c) { ... } void wash(Dog d) { ... } ... } SuperWasher sw = new SuperWasher(); Dog aDog = new Dog(); sw.wash(aDog); • Die Methode wird (statisch) beim Senden der Nachricht anhand der Parameter ausgewählt. Prof. Dr. Nikolaus Wulff 17 Kein Überladen bei Rückgabewerten • Folgende überladene Methoden unterscheiden sich nur im Rückgabewert: void washAll() {...} int washAll() {...} • Das geht nicht, denn der Compiler kann für diesen Aufruf nicht bestimmen, welche Methode zu verwenden ist: washAll(); Prof. Dr. Nikolaus Wulff 18 Default-Konstruktor • Konstruktoren dienen zur Erzeugung und Initialisierung von Objekten einer Klasse. • Wenn kein Konstruktor angegeben wird, ist implizit ein Default-Konstruktor (ohne Parameter) definiert. class Bird { String name; } class Bird { String name; Bird() { super(); Aufruf der Konstruktors der Superklasse } } Bird nc = new Bird(); Prof. Dr. Nikolaus Wulff 19 Konstruktordefinition • Konstruktoren können überladen werden und Parameter erhalten, z.B. zur Initialisierung von Attributen: class Worker extends Person { String name; Aufruf des Super-Konstruktors ist optional Worker() { (erfolgt sonst automatisch vom Compiler) super(); name = "Noname"; System.out.println("Erzeuge Noname"); }; Worker(String name) { super(); this.name = name; System.out.println("Erzeuge " + name); } Worker noname = new Worker(); } Worker peter = new Worker("Peter"); Prof. Dr. Nikolaus Wulff 20 Konstruktorverkettung • Konstruktoren können sich gegenseitig aufrufen: class Person { ... Person(String name, int alter) { this(name); Konstruktor dieser Klasse ... mit Parameter String aufrufen } } • Konstruktoren können Konstruktoren der Superklasse aufrufen: class Mitglied extends Person { ... Mitglied(String name, int tarifgruppe) { super(name); Konstruktor der Superklasse ... mit Parameter String aufrufen } } Prof. Dr. Nikolaus Wulff 21 Konstruktoraufrufe • Beim Erzeugen eines Objektes einer Subklasse wird automatisch der DefaultKonstruktor der Superklasse aufgerufen. class Person { Person() { System.out.println(" Person"); } } class Angestellter extends Person { Angestellter() { System.out.println(" Angestellter"); } } Angestellter peter = new Angestellter(); Ausgabe: Prof. Dr. Nikolaus Wulff Person Angestellter 22 Konstruktor mit Argumenten • Ein Konstruktor der Superklasse mit Argumenten muss – als erstes im Konstruktor – explizit (über super) aufgerufen werden. class Person { Person(String name) { System.out.println(" Person " + name); } } class Angestellter extends Person { Angestellter(String name) { super(name); System.out.println(" Angestellter " + name); } } Prof. Dr. Nikolaus Wulff 23 Initialisierungsreihenfolge class Test { Person p1 = new Person(); Person p2; // null static int zaehler; // 0 Test() { p2 = new Person(); } static { zaehler = 9; } 3: per Objekt: Attribute initialisieren 1: per Klasse: Attribute initialisieren 4: per Objekt: Konstruktor ausführen 2: per Klasse: Init-Code ausführen } Prof. Dr. Nikolaus Wulff 24 Arrays • Arrays sind Objekte. Bei der Deklaration wird keine Größe angegeben, da nur die Referenz abgelegt wird: int a[]; int[] a; • Einfache Arrays können direkt erzeugt und initialisiert werden: int [] a = { 1, 2, 3, 4, 5 }; • Beim Zugriff auf ein Array wird der Index auf Überschreitung der Array-Größe geprüft: int x = a[3]; int x = a[567]; ArrayIndexOutOfBounds • Die Länge des Arrays kann abgefragt werden: a.length Prof. Dr. Nikolaus Wulff 25 Arrays von Objekten • Arrays von Objekten werden so erzeugt und initialisiert: Person[] a = new Person[20]; for(int i = 0; i < a.length; i++) { a[i] = new Person("Person Nummer " + i); } • Wie bei einfachen Arrays kann direkt initialisiert werden: Integer[] a = { new Integer(1), new Integer(2), new Integer(3), }; Prof. Dr. Nikolaus Wulff 26 Mehrdimensionale Arrays • Mehrdimensionale Arrays werden so deklariert: int[] a[]; int[][] a; • Die Initialisierung kann auch hier direkt erfolgen: int[][] a = { { 1, 2, 3 }, { 5, 6} }; Prof. Dr. Nikolaus Wulff 27 Zeichenketten • • • • Zeichenketten sind unveränderliche Objekte der Klasse String. String-Literale werden durch “” eingefasst. String-Literale werden automatisch in String-Objekte umgewandelt. Zeichenketten können auch durch Aufruf eines Konstruktors der Klasse String erzeugt werden. String c = "abc"; • Außerdem erzeugen viele Methoden Zeichenketten: String c = new String("abc"); Integer i = new Integer(789); String c = i.toString(); Prof. Dr. Nikolaus Wulff 28 Verkettung von Zeichenketten • Zeichenketten können durch den Operator + verkettet werden: String c = "Hello " + "World"; • Die Verkettung erzeugt ein neues String-Objekt. • Werte von Basisdatentypen oder Objekte von Klassen können auch mit Strings verkettet werden. String n = "Die Antwort lautet " + 42; • Sie werden dann implizit in Strings umgewandelt, bei Objekten durch den Aufruf der Methode toString (definiert in der Klasse Object). Prof. Dr. Nikolaus Wulff 29 Sichtbarkeitsbereiche • Blockstrukturierte Sichtbarkeitsbereiche: Lokale Variablen sind nach dem Schließen des Blocks nicht mehr sichtbar. { // <-- Anfang Scope 1 int x; x = 12; // Nur x ist sichtbar, q gibt es noch nicht { // <-- Anfang Scope 2 int q = 96; x = q + x; // x kann nicht neu definiert werden! // x und q sind verfügbar. } // <-- Ende Scope 2 // Nur x ist noch sichtbar // Auf q kann nicht mehr zugegriffen werden. } // <-- Ende Scope 1 Prof. Dr. Nikolaus Wulff 30 Sichtbarkeitsbereiche • Gleichnahminge lokale Variablen und Member Variablen der Klasse müssen per this referenziert werden. class Foo { private int x = 2; // Scope 0 der Membervariablen public void bar() { // <-- Anfang Scope 1 int x; x = 12; // Nur x ist sichtbar, q gibt es noch nicht { // <-- Anfang Scope 2 int q = 96; x = q + x; // x kann nicht neu definiert werden! // x und q sind verfügbar. } // <-- Ende Scope 2 // Nur x ist noch sichtbar // Auf q kann nicht mehr zugegriffen werden. this.x = x; // jetzt wird Membervariable x gesetzt } // <-- Ende Scope 1 der Methode bar... Prof. Dr. Nikolaus Wulff 31 Bedingter Ausdruck • Verzweigung, die zu einem Wert evaluiert. int a, b; ... b = (a >= 0) ? a : -a; Bedingung then else • Wird häufig in Projekten verboten, da "schwer zu lesen". Aquivalent ist eine if - else Verzweigung: if (a >= 0) { b = a; } else { b = -a; } Prof. Dr. Nikolaus Wulff 32 Freispeicherverwaltung • Java-Objekte werden dynamisch mit new angelegt. • Sie werden nie explizit gelöscht oder freigegeben. • Die Java-Maschine schaut regelmäßig (oder nach Aufforderung) nach, welche Objekte nicht mehr erreichbar sind (garbage collection) und gibt diese frei. • Vor der Freigabe wird bei jedem Objekt die Methode finalize aufgerufen. Prof. Dr. Nikolaus Wulff 33 Freispeicherverwaltung (2) • Die virtuelle Maschine prüft, ob ein Objekt noch von Außen, d.h. ausgehend von der main Methode noch erreichbar ist. Ausgangspunkt main Speicher der Java Maschine Prof. Dr. Nikolaus Wulff X X X X Müll 34 Wiederverwendung • Vier Alternativen der Wiederverwendung: – Schreibe die Klasse komplett selber (keine echte Wiederverwendung). – Kaufe die fertige Klasse und nutze sie so (volle Wiederverwendung). – Benutze eine vorhandene Klasse und binde sie in einer Komposition in Deine Klasse ein. – Benutze eine vorhandene Klasse, indem Deine Klasse von ihr erbt und bestimmte Attribute/Methoden ergänzt oder überschreibt. • Dies gilt sowohl für einzelne Klassen als auch für Rahmenwerke (Frameworks) und Bibliotheken. Prof. Dr. Nikolaus Wulff 35 Komposition vs. Vererbung Auto Reifen[] Fenster[] Tuer[] Motor Shape draw() erase() Circle draw() erase() Square draw() erase() Line draw() erase() Komposition: Vererbung: • “hat ein” • “ist ein” • “besteht aus” • verschiedene Objekte/Klasse haben gemeinsame Eigenschaften Prof. Dr. Nikolaus Wulff 36 Komposition MeineKlasse Vorhandene Klasse Vorhandene Klasse Vorhandene Klasse Vorhandene Klasse ... Prof. Dr. Nikolaus Wulff class Auto { Motor motor = new Motor(100); Reifen[] reifen = new Reifen[4]; ... } • • • • Ein komponiertes (aggregiertes) Objekt hat Objekte vorhandenener Klassen als Attribute. “hat ein” oder “besteht aus” Flexibilität: Die Objekte können zur Laufzeit ausgewechselt werden. Die Komponenten werden meist im Konstruktor übergeben oder erzeugt. 37 Vererbung: Motivation class Person{ String name; String vorname; int alter; String getName(){...} String getId(){...} } class Angestellter { String name; String vorname; int alter; int uid; String getName(){...} String getId(){...} } Person name ... getName() getId() class Mitglied { String name; String vorname; int alter; int tarifgruppe; ... String getName(){...} String getId(){...} int getTarif(){...} Mitglied tarifgruppe Angestellter uid } getTarif() getId() Subklasse Prof. Dr. Nikolaus Wulff Superklasse 38 Vererbung: Semantik • “Ist ein” Die Attribute und Methoden aus der Superklasse sind automatisch auch in der Subklasse vorhanden. Person name vorname alter getName() getId() tarifgruppe Angestellter uid getTarif() getId() Mitglied Prof. Dr. Nikolaus Wulff Ererbte Methoden können in der Subklasse redefiniert werden, z.B. um eine andere Id zu implementieren (Uxxxxxx). 39 Vererbung: Syntax class Person { String name; String vorname; int alter; String getName(){...} String getId(){...} } class Mitglied extends Person { int tarifgruppe; neue Attribute hinzufügen String getTarif(){...} neue Methoden hinzufügen } class Angestellter extends Person { int uid; String getId(){...} redefiniert Person.getId() } Prof. Dr. Nikolaus Wulff derselbe Methodenname dieselbe Parameterliste derselbe Rückgabetyp 40 Komposition vs. Vererbung Ist ein Auto ein Motor? Motor Auto Auto Falsch! Reifen[] Fenster[] Tuer[] Richtig Ein Motor wird gestartet, ein Auto wird gestartet: Ist deshalb ein Auto so etwas wie ein Motor? Prof. Dr. Nikolaus Wulff Reifen[] Fenster[] Tuer[] Motor Generell: Komposition ist flexibler als Vererbung (Laufzeit vs. Übersetzungszeit). 41 Verwendung von final • Finale Methoden können in Subklassen nicht überschrieben (redefiniert) werden. final void print() { ... } • private Methoden sind implizit final. • finale Parameter: Dem Parameter darf innerhalb des Methodenrumpfes kein neuer Wert zugewiesen werden. void print(final int anzahl) { ... } • Von einer finalen Klassen kann keine Subklasse gebildet werden. final class Angestellter { ... } Prof. Dr. Nikolaus Wulff 42 Polymorphismus Substitutierbarkeit: Wann immer ein Objekt der Superklasse erwartet wird (als Parameter, Empfänger einer Nachricht, ...), kann ein Objekt einer Subklasse benutzt werden. Beispiel: Wenn eine Person angezeigt werden soll, kann auch ein Mitglied (Angestellter, ...) angezeigt werden. a Circle draw() : Shape a Square erase() a Line Prof. Dr. Nikolaus Wulff 43 Upcast void moveShape(Shape s) {...; s.draw();} Cast “nach oben“ in der Vererbungshierarchie Circle draw() erase() Shape draw() erase() Square draw() erase() Line draw() erase() Referenz auf das Circle-Objekt Shape s = (Shape)new Circle(); // explizit moveShape(new Circle()); // implizit Prof. Dr. Nikolaus Wulff 44 Statisches vs. Dynamisches Binden f() ... Funktion f f() ... f() shape.draw() Aufrufe statisch an die Funktion gebunden zur Übersetzungszeit Dynamischer BindeMechanismus ... shape.draw() ... shape.draw() Prof. Dr. Nikolaus Wulff zur Laufzeit Circle draw() Square draw() Line draw() 45 Abstrakte Klassen • Abstrakte Klassen können abstrakte Methoden enthalten, d.h. Methoden ohne Methodenrumpf. • Andere Methoden und Daten können definiert sein. abstract class ColouredShape { private Colour c; public abstract void draw(); public Colour getColour() { return c; } public abstract void erase(); } Abstrakte Methoden • Abstrakten Klassen können nicht instanziert werden. • Subklassen müssen die abstrakten Methoden implementieren - oder sie sind selber abstrakt. Prof. Dr. Nikolaus Wulff 46 Template Pattern • In einer abstrakten Klasse können die abstrakten Methoden von anderen Methoden benutzt werden. abstract class PrintTemplate { protected abstract void printHeader(); protected abstract void printFooter(); protected abstract void printContent(); public void print(){ printHeader(); printContent(); printFooter(); } } Prof. Dr. Nikolaus Wulff 47 Innere Klassen • Verschachtelte Klasse einer anderen Klasse. class Person { class Adresse { ... } ... } class Person { public String doIt(final int i) { class Adresse { ... i ... } } ... } • Der Klassenname ist versteckt. • Objekte der inneren Klasse können auf Attribute, Methoden der umgebenden Klasse zugreifen. • Wenn eine innere Klasse innerhalb einer Methode definiert wird, kann sie auf die Parameter und lokalen Variablen der Methode zugreifen, wenn diese final sind. Prof. Dr. Nikolaus Wulff 48 Anonyme innere Klassen • Innere Klassen könne auch anonym sein. • Dies wird häufig angewandt bei Schnittstellen. new WindowAdapter(){ public void windowClosing(WindowEvent e) {...} } class MyWindowListener extends WindowAdapter { public void windowClosing(WindowEvent e) {...} } new MyWindowListener(); Prof. Dr. Nikolaus Wulff 49 Schnittstellen • • Die gilt se Aus sag bi s J D sieh K 1. e eH 7 PK , ... Schnittstellen enthalten keine Implementationen, sondern nur Methodensignaturen und Konstanten (statische, finale Attribute). Methodensignatur : = Rückgabetyp + Methodenname + Parameterliste interface Shape { Point zero = new Point(0,0); void erase(); void draw(); } • automatisch static & final automatisch public Eine Schnittstelle kann von einer oder mehreren Schnittstellen erben. interface ColouredShape extends Shape, Colour { ... } Prof. Dr. Nikolaus Wulff 50 Schnittstellen und Implementierung • Eine Schnittstelle legt ein Protokoll (verstandene Nachrichten) fest. • Eine Klasse kann eine oder mehrere Schnittstellen implementieren. Schnittstellen class Circle implements Shape, Persistent { pivate int radius; public void erase() { ... } public String draw() { aus Shape ... } public int getRadius(){ ... } } Prof. Dr. Nikolaus Wulff 51 Übung • Erstellen und testen (wie?) Sie den Code für das folgende Klassenmodell: Painter painter + Painter + Paintable <<Interface>> Paintable (from painter) paintables : Paintable[] (from painter) 0..* drawAll() addPaintable() draw() Achtung: zwei verschieden Pakete für Painter und alle Shapes! Shape (from sha pes) type : String Shape(type : String) <<abstract>> draw() : void shapes + + + + Shape Circle Square Line Prof. Dr. Nikolaus Wulff Circle Square Line (from sha pes) (from shapes) (from shapes) 52 Tip • Schreiben Sie eine Main Klasse, die den Painter mit Shapes initialisiert und dann die drawAll Methode aufruft. • Die draw Methode der Shapes gibt einfach per System.out.println den Typ aus. • Die Zeichenobjekte werden in einem Array verwaltet, dieses muss dynamisch wachsen! – Verwenden Sie ein temporäres Hilfsarray. – (Später lernen Sie Collection Klassen kennen, die uns genau diese Arbeit abnehmen...) Prof. Dr. Nikolaus Wulff 53 Übung • Erstellen Sie eine Dokumentation der Klassen: – %SRC%>javadoc -d ../doc * • Navigieren Sie in der Klassendokumentation und passen Sie die Kommentare in Ihren Methoden und Klassen an. • Erkunden Sie die verschiedenen Möglichkeiten, die javadoc bietet (javadoc -? bietet Hilfe) • Javadoc läßt sich auch als Tool in Eclipse integrieren: – Run|External Tools|External Tools...|Programm|New – Location: %JAVA_HOME%\bin\javadoc.exe – Working Dir: ${project_loc}\src – Arguments: -d ${project_loc}\doc * Prof. Dr. Nikolaus Wulff 54 Package Übersicht Prof. Dr. Nikolaus Wulff 55