Klassen sind Strukturen aus Daten und Methoden.
public class Kreis { public double x,y; // Koordinaten public double r; // Radius public double umfang() { return 2*3.14159*r; } public double fläche() { return 3.14159*r*r; } }
Klassen dienen (meist) dazu, den Inhalt und die Fähigkeiten von
Objekten zu beschreiben.
Objekte sind Instanzen von Klassen.
Sie leben z.B. in Variablen von geeignetem Typ
(siehe Seite ).
Kreis c;
und werden grundsätzlich dynamisch erzeugt:
c = new Kreis();
Zugriff auf Objekte-Daten durch Feld-Selektion:
Kreis c = new Kreis(); c.x = 2.0;
Aufruf von Instanzmethoden durch Methoden-Selektion:
Kreis c = new Kreis(); double f; c.r = 2.7; f = c.fläche();
Technische Realisierung: Übergabe der Selbstreferenz this
(siehe Seite )
an den Code, der fläche implementiert.
Kreis c = new Kreis();
Ablauf:
Konstruktoren initialisieren Objekte.
Wichtige Eigenschaften:
public Kreis (double x, double y, double r) { this.x = x; // 'this' löst this.y = y; // Namens- this.r = r; // konflikt } ... Kreis c = new Kreis(1.4,2.0.5.3);
Der parameterlose Konstruktor
Kreis();heißt Default-Konstruktor. Er wird vom Compiler zur Verfügung gestellt, falls es keinen Benutzer-definierten Konstruktor gibt.
Der Compiler-generierte Default-Konstruktor ruft lediglich den
Default-Konstruktor der Oberklasse auf.
Wir bekommen also Compiler-Fehlermeldungen,
this findet in Konstruktoren Anwendung, die andere Konstruktoren verwenden:
public Kreis (double x, double y, double r) { this.x = x; this.y = y; this.r = r; } public Kreis(double r) { this(0.0, 0.0, r); } public Kreis(Kreis c) { this(c.x, c.y, c.r); } public Kreis() { this(0.0, 0.0, 1.0); }
Andere typische Anwendungen von this siehe
Seite und Seite
.
Solche Aufrufe anderer Konstruktoren stehen vor anderen Anweisungen im Konstruktorrumpf.
Solch ein expliziter Konstruktor-Aufruf über this
All dieses führt zu Compiler-Fehlermeldungen.
public class Tisch { Bein[] beine; int anz_beine; public Tisch() { this(anz_beine-1); // FALSCH! } public Tisch(int n) { anz_beine = n; } }
Der Java-Compiler meldet:
Can't reference anz_beine before the superclass constructor has been called. { this(anz_beine-1); ^
Klassenvariablen beschreiben im Gegensatz
zu Instanzvariablen Eigenschaften der Klasse
(siehe auch Seite ).
Klassenvariablen werden mit dem Schlüsselwort static deklariert:
public class Kreis { static int anz_Kreise = 0; public double x,y; // Koordinaten public double r; // Radius public Kreis (double x, double y, double r) { this.x = x; this.y = y; this.r = r; anz_Kreise++; } }
Zugriff erfolgt außerhalb der Klassen qualifiziert mit dem Klassennamen:
if (Kreis.anz_kreise == 12) System.out.println(``Hello'');
static-Felder werden außerdem benutzt für
public class Kreis { public double x,y; // Koordinaten public double r; // Radius // Instanz-Methode: public Kreis bigger(Kreis k) { if (k.r > r) return k; else return this; } // Klassen-Methode: public static Kreis bigger(Kreis k1, Kreis k2) { if (k1.r > k2.r) return k1; else return k2; } }
Verwendung der beiden Methoden:
Kreis a = new Kreis(12.3); Kreis b = new Kreis(3.414); Kreis c = a.bigger(b); Kreis c = Kreis.bigger(a,b);
Besonderheiten:
// Klassen-Methode: public static Kreis bigger(Kreis k1, Kreis k2) { if (k1.r > k2.r && x > 0.0) // FALSCH! return k1; else return k2; }
Java-Compiler:
Can't make a static reference to nonstatic variable x in class Kreis. { if (k1.r > k2.r && x > 0.0) ^
als auch für
class Kreis { double r = 1.0; static int kreiszahl = 100; ... }
Initialisierung wird jedesmal bei Erzeugung eines Objektes ausgeführt.
Initialisierung wird nur einmal durchgeführt, wenn die Klasse geladen wird.
class Rechteck { static float max_sv = 2.0; static float max_h = 10.0 static float max_b = max_sv * max_h; ... }
Die Regeln:
Daher bekommen wir hiermit 2 Compiler-Fehlermeldungen:
class FalscheStaticInits { static float f = j; // VORWÄRTS! static int j = 1; public int max; static int k = max + 1; // INSTANZVAR! ... }
Für komplexere Initialisierungen, die nicht als einfacher Ausdruck geschrieben werden können, gibt es die statischen Initialisierungsblöcke:
public class Kreis { static private double pi = 3.14; static private double[] sins = new double[1000]; // Statischer Initialisierungsblock static { double x = 0.0; double delta_x = pi / 2 / (1000-1); int i; for (i=0; i < 1000; i++) { sins[i] = Math.sin(x); x += delta_x; } } }
Es darf mehrere solcher Blöcke geben. Sie werden behandelt wie ein
einziger, der durch Hintereinanderhängung entsteht.
public class Tisch { Bein[] beine; int anz_beine = 4; }
Hier gelten ähnliche Regeln wie bei den Klassenvariablen
(Seite ):
Daher bekommen wir hiermit 2 Compiler-Fehlermeldungen:
class FalscheInstanzInits { float f = j; // VORWÄRTS! int j = 1; int k = k + 1; // REKURSIV! ... }
Für komplexerer Initialisierung, die nicht als einfacher Ausdruck geschrieben werden können, gibt es die Instanz-Initialisierungsblöcke (ab Java1.1):
public class Tisch { String platte = "Holz"; String[] beine; int anz_beine = 4; { int i; for (i = 0; i < anz_beine; i++) beine[i] = "Bein " + i; } public Tisch(String p_material) { platte = p_material; } }
Es darf mehrere solcher Blöcke geben. Sie werden behandelt wie ein
einziger, der durch Hintereinanderhängung entsteht.
Nun kennen wir alle Akteure, die bei der Erzeugung neuer Objekte eine Rolle spielen:
Wann werden neue Objekte erzeugt?
new Tisch(); new Kreis(3.2);
Für
existiert zur Laufzeit eines Java-Programms ein Objekt der Klasse Class
Diese Objekte können nur von der virtuellen Maschine
erzeugt werden, nicht vom Benutzer-Programm.
Zu jedem Objekt findet man das Klassenobjekt durch getClass()
aus der Klasse Object:
Class c = new Diesel().getClass();
Man kann nun z.B. den Klassennamen ausgeben:
Class c = new Diesel().getClass(); System.out.println(c.getName());
Nachzulesen in der API-Dokumentation zu java.lang.Class.
[Ende des Einschubs.]
Der Ablauf der Objekterzeugung:
Reserviere einen Speicherbereich, groß genug für
Gelingt dies nicht, gibt es einen OutOfMemoryError-Fehler.
Gelingt es, werden anschließend alle Instanzvariablen (auch die
der Oberklassen) des neuen Objektes mit ihren Default-Werten
initialisiert.
Typ | Inhalt | Größe | Default |
boolean | true oder false | 1 bit | false |
char | Unicode Character | 16 bits | \u0000 |
byte | signed integer | 8 bits | 0 |
short | signed integer | 16 bits | 0 |
int | signed integer | 32 bits | 0 |
long | signed integer | 64 bits | 0 |
float | IEEE 754 floating point | 32 bits | 0.0 |
double | IEEE 754 floating point | 64 bits | 0.0 |
Klasse, Interface, Array | Referenz | / | null |
null ist ein Ausdruck von einem speziellen Nulltyp, der keinen Namen hat.
null kann an jede Array-, Objekt- oder Interface-Variable zugewiesen werden.
Die Größe von Referenzen wird von der virtuellen Maschine als ein
``Wort'' definiert (i.d.R. 32 oder 64 bits).
Ablauf:
Nicht unter der Kontrolle des Benutzerprogramms sondern durch einen automatischen Garbage Collector.
Der weiß zur Laufzeit, welche Objekte existieren und prüft, auf welche Objekte nicht mehr verwiesen wird.
Technisch: Low-priority-thread, der im Hintergrund mitläuft.
Nutzt z.B. die Zeiten des Wartens auf Benutzereingabe.
Hohe Priorität bekommt der Garbage Collector nur, wenn der Interpreter
keinen Speicher mehr hat.
Für die Entsorgung von Objekten muß also nichts getan werden.
Ein Beispielfall, wo wir dem Garbage Collector helfen können:
public static void meth () { int big_array[] = new int[100000]; // rechne auf big_array int result = compute(big_array); // brauchen big_array nicht mehr // nach verlassen von meth kann es // entsorgt werden. // falls meth aber NICHT verlassen // wird: big_array = null; for (;;) // für immer handle_user_input(); }
Hier ist die finalizer-Methode aus
java.io.FileOutputStream:
public void finalize() throws IOException; // overrides Object { // close the stream if (fd != null) close(); }
Regeln für Finalizer:
Grund: finalize-Methoden können Objekte ``wiederauferstehen'' lassen (z.B. durch Speichern des this-Zeigers).2
GC muß also erneut prüfen.
In Java 1.0:
Ziel: Definition von Hilfsklassen möglichst nahe an der Stelle, wo sie gebraucht werden.
Geschachtelte und innere Klassen sind wesentlich motiviert durch das
neue Ereignismodell im AWT von Java1.1.
Klassen oder Interfaces, die static deklariert sind und in andere ``top-level''-Klassen oder Interfaces eingeschachtelt sind.
Interfaces sind immer static, d.h. für Interfaces ist nur die ``top-level''-Variante möglich.
Geschachtelte ``top-level'' Klassen und Interfaces verhalten sich wie
andere Paket-Elemente auf der äußersten Ebene, außer daß ihrem Namen
der Name der umgebenden Klasse vorangestellt wird.
public class LinkedList { // interface verkettungselement public static interface Linkable { public Linkable getNext(); public void setNext(Linkable n); } // kopf der liste: Linkable head; // einfügen: public void insert(Linkable n) { n.setNext(head); head = n; } // kopfzugriff: public Linkable getHead() { return head; } }
Benutzung:
a) Definition der Verkettungselemente:
class LinkableInteger implements LinkedList.Linkable { int i; LinkedList.Linkable Next; // konstruktor: LinkableInteger(int i) { this.i = i; } // versprochene Methoden: public LinkedList.Linkable getNext() { return Next; } public void setNext(LinkedList.Linkable n) { Next = n; } // für die ausgabe: String-Umwandlung // überschreibe toString aus Object public String toString() { return i + ""; } }
b) Testlauf:
public class LiMain { public static void main (String[] argv) { LinkedList li = new LinkedList(); li.insert(new LinkableInteger(16)); li.insert(new LinkableInteger(6)); li.insert(new LinkableInteger(-4)); LinkedList.Linkable l; for (l = li.getHead(); l != null; l = l.getNext()) System.out.println(l); } }
c) Ergebnis:
-4 6 16
Im Gegensatz zu den static-deklarierten geschachtelten ``top-level''-Klassen, die mehr oder weniger nur zur Strukturierung dienen, sind Elementklassen echte innere Klassen.
Damit kann das Objekt der Elementklasse implizit auf die
Instanzvariablen der umgebenden Klasse zugreifen (auch auf
private).
Die umgebende Klasse hat Zugriffrechte auf alle Felder
aller ihrer Elementklassen.
Zwei Elementklassen der selben umgebenden Klasse haben Zugriffrechte
auf die Elemente des jeweilig anderen.
Elementklassen dürfen keine statischen Elemente (Attribute, Methoden,
Klassen, Interfaces) besitzen. Gilt auch für lokale Klassen
(Seite )
und anonyme Klassen (Seite
).
Beispielprojekt: Einbau eines Iterators in eine Buchstabenmenge
Beschreibung der Buchstabenmenge:
Name | Charset |
Attribut | chars vom Typ BitSet |
Konstruktor | Charset(String) |
Methode | toString() |
schreibt die Elemente in einen String. |
Ziel: ein Iterator für Charset.
Typisches Verwendungsmuster für einen solchen Iterator:
Charset cs = new Charset(somestring); Enumeration e = cs.characters(); while (e.hasMoreElements()) machwasmit(e.nextElement());
(Anderes Iteratorbeispiel auf Seite ).
a) Originalversion der Buchstabenmenge:
import java.util.BitSet; class Charset { // Buchstabenmenge: private BitSet chars = new BitSet(); // Konstruktor: public Charset(String str) { for (int i = 0; i < str.length(); i++) chars.set(str.charAt(i)); } // Umwandlung in String: public String toString() { String result="["; int size = chars.size(); for (int i = 0; i < size; i++) if (chars.get(i)) result += (char)i; return result +"]"; } }
b) Testen der Originalversion der Buchstabenmenge:
public class CharsetMain { // hauptprogramm public static void main(String argv[]) { // erstes kommandozeilenargument Charset cs = new Charset(argv[0]); System.out.println(cs + ""); } }
c) Testlauf:
Eingabe:
java CharsetMain JavaProgrammierung
Ausgabe:
[JPaegimnoruv]
c) Einbau einer inneren Iteratorklasse
import java.util.* // für BitSet, Enumeration // und NoSuchElementException class Charset { // Buchstabenmenge: private BitSet chars = new BitSet(); // Konstruktor: public Charset(String str) { for (int i = 0; i < str.length(); i++) chars.set(str.charAt(i)); } // innere Elementklasse: private class Cs_iterator implements Enumeration { private int pos; private int setsize = chars.size(); // versprochenes "hasMoreElements": public boolean hasMoreElements() { while (pos < setsize && !chars.get(pos)) pos++; return pos < setsize; }
// versprochenes "nextElement": public Object nextElement() throws NoSuchElementException { if (hasMoreElements()) return new Character((char) pos++); else throw new NoSuchElementException(); } } // liefert einen Iterator: public Enumeration characters() { return new cs_iterator(); } }
d) Testen des neuen Charset mit Iterator:
import java.util.*; public class It_CharsetMain { public static void main(String argv[]) { // erstes kommandozeilenargument Charset cs = new Charset(argv[0]); Enumeration e = cs.characters(); while (e.hasMoreElements()) System.out.println(e.nextElement()); } }e) Testlauf:
Eingabe:
java It_CharsetMain Java
Ausgabe:
J a v
Syntax-Erweiterungen und neue Regeln für Elementklassen
a)
Wie sprechen ``innere Objekte'' das ihnen zugeordnete Objekt der
umgebenden Klasse an?
Im Charset-Beispiel:
pos und setsize sind Attribute der Selbstreferenz
this .
Ihr expliziter Name wäre also this.pos, bzw. this.setsize.
Um chars explizit anzusprechen, schreiben wir:
Charset.this.chars
b) Tiefere Schachtelung von Elementklassen?
Beliebig möglich. Allerdings darf keine
der eingeschachtelten Klassen so heißen
wie eine der sie umgebenden!
Das bedeutet:
Obige neue Syntax:
classname.this
public class A { public string name = "a"; public class B { public string name = "b"; public class C { public string name = "c"; public void print() { System.out.println(name); System.out.println(this.name); System.out.println(C.this.name); System.out.println(B.this.name); System.out.println(A.this.name); } } } }
c) Welches ist das umgebende Objekt?
In unserem Beispiel haben wir den Default-Fall benutzt:
public Enumeration characters() { return new cs_iterator(); }
Hier: Keine Angabe des umgebenden Objektes für die neue Instanz der inneren Klasse. Daher wird die Selbstreferenz this als umgebendes Objekt angenommen:
Äquivalent zu obigem ist die neue explizite Schreibweise:
public Enumeration characters() { return this.new cs_iterator(); }
Für andere Fälle brauchen wir die neue allgemeine Syntax:
containing_instance.new inner_class(...)
wobei containing_instance eine Instanz der umfassenden Klasse
von inner_class ist.
d) Innere Objekte außerhalb der umgebenden Klasse erzeugen?
Geht, wenn die innere Klasse nicht wie in unserem
Charset-Beispiel privat ist.
Also ändern:
// innere Elementklasse: public class Cs_iterator implements Enumeration { private int pos; .... }
Dann funktioniert auch folgendes Hauptprogramm:
public static void main(String argv[]) { // erstes kommandozeilenargument Charset cs = new Charset(argv[0]); Enumeration e = cs.new cs_iterator(); while (e.hasMoreElements()) System.out.println(e.nextElement()); }
Noch ein Beispiel für die externe Erzeugung innerer Objekte.
Klassenhierarchie von Seite :
A a = new A; // A Instanz A.B b = a.new B(); // B Instanz in a A.B.C c = b.new C(); // C Instanz in b c.print(); // Methode aus C
e) Kann man von inneren Klassen erben?
Ja ( ``strange as it may seem'').
Damit gibt es aber auch eine zweite Art, wie Objekte innerer Klassen erzeugt werden können: mit dem Oberklassen-Konstruktoraufruf super.
Zusätzlich zur Syntax
containing_instance.new inner_class(...)
containing_instance.super(...)
f) Ändern sich durch innere Klassen die Regeln für die Namensbindung?
Ja. Wir haben jetzt drei Hierarchien in Java-Programmen:
Aufgabe der Namensanalyse: Finde zu jedem Auftreten eines Bezeichners die laut Sprachdefinition dazugehörige Definition.
Beispiel:
Eine Methode der Klasse K verwendet in einer Anweisung den Bezeichner x.
Ist x
Vererbungshierarchie und Klassenschachtelung im Bild:
Die beiden Hierarchien sind völlig unabhängig voneinander.
Es ist wichtig, sie nicht zu verwechseln!
Konfliktfall zwischen umfassender Klasse und Oberklasse
In Java 1.1. gelöst zugunsten der Oberklasse, d.h.
der Bezeichner x der Oberklasse verdeckt den der umfassenden Klasse.
Zusätzlich zu dieser Regel erfordert Java im Konfliktfall die
explizite Benennung des Bezeichners, d.h.
Sehr ähnlich zu Elementklassen (siehe Seite )
mit folgenden wichtigen Unterschieden:
Anonyme Klassen haben keinen Namen. Sie entstehen immer zusammen mit einem Objekt.
import java.io.*; // gibt die Namen aller .java-Dateien im // angegebenen Verzeichnis aus public class Dirlist {public static void main(String argv[]) { File f = new File(argv[0]); String[] list = f.list (new FilenameFilter() { public boolean accept(File f, String s) { return s.endsWith(".java"); } } ); // Formatierung schwierig! for (int i = 0; i < list.length; i++) System.out.println(list[i]); } }
Die Syntax für anonyme Klassen ist
new-expression class-body
Für die anonyme Klasse kann man keine extends- oder implements-Klauseln angeben.
Daher:
In unserem Beispiel
implementiert die anonyme Klasse ein Interface:
f.list(new FilenameFilter() { ... } ):
Da anonyme Klassen keinen Namen haben, können sie auch keine
Konstruktoren
haben. Deshalb hat Java 1.1
die
Instanzinitialisierungsblöcke eingeführt
(Seite ).
Die Sichtbarkeitsspezifikationen
geben an, wo Attribute und Methoden einer Klasse benutzt werden können.
Benutzbar in | Sichtbarkeit | |||
![]() |
![]() |
![]() |
![]() |
|
Selber Klasse | ja | ja | ja | ja |
Klasse, selbes Paket | ja | ja | ja | nein |
Unterklasse, anderes Paket | ja | ja | nein | nein |
Klasse, anderes Paket | ja | nein | nein | nein |
Besonderheit im Zugriff auf protected-Elemente aus Unterklassen:
Erlaubt ist der Zugriff auf protected-Elemente des eigenen
Oberklassenanteils.
Nicht Erlaubt ist der Zugriff auf protected-Elemente in anderen Objekten vom Oberklassentyp.
Beispiel:
Klasse A im Paket pack:
package pack; public class A { protected int x; }
Klasse B in einem anderen Paket:
class B extends pack.A { void doit() { x = 18; pack.A va = new pack.A(); va.x = 19; // NICHT ZULÄSSIG !! } }