4
von
Laura Lemay
Wir beginnen die heutige Lektion mit einer klaren Aussage: Da Java eine objektorientierte Sprache ist, haben Sie eine Menge mit Objekten zu tun. Wir erstellen, ändern und verschieben in dieser Lektion Objekte. Ferner ändern wir Variablen von Objekten, rufen ihre Methoden auf, kombinieren Sie mit anderen Objekten und entwickeln selbstverständlich Klassen. Außerdem verwenden wir die von Ihnen erstellten Objekte.
Heute lernen Sie alles über Java-Objekte in ihrem natürlichen Umfeld. Zu den heutigen Themen zählen:
Wenn Sie ein Java-Programm schreiben, definieren Sie verschiedene Klassen. Wie Sie am 2. Tag gelernt haben, dienen Klassen als Vorlagen für Objekte. Zum großen Teil benutzen Sie die Klassen lediglich, um Instanzen zu erstellen. Dann arbeiten Sie mit diesen Instanzen. In dieser Lektion lernen Sie, wie man ein neues Objekt aus einer Klasse erstellt.
Sie erinnern sich an die Zeichenketten aus der gestrigen Lektion? Sie haben gelernt, daß ein String-Literal eine Reihe von Zeichen zwischen doppelten Anführungszeichen eine neue Instanz der Klasse String mit dem Wert der jeweiligen Zeichenkette erzeugt.
Die String-Klasse ist in dieser Hinsicht ungewöhnlich. Es ist zwar eine Klasse, dennoch gibt es eine einfache Möglichkeit, anhand eines Literals Instanzen von dieser Klasse anzulegen. Für die anderen Klassen gibt es diesen Abkürzung nicht. Um Instanzen dieser Klassen zu erstellen, müssen sie explizit mit dem Operator new angelegt werden.
Um ein neues Objekt zu erstellen, benutzen Sie new mit dem Namen der Klasse, von der Sie eine Instanz anlegen wollen, und Klammern:
String str = new String();
Random r = new Random();
Motorcycle m2 = new Motorcycle();
Die Klammern sind wichtig. Sie dürfen auf keinen Fall weggelassen werden. Die Klammern können leer bleiben. In diesem Fall wird ein ganz einfaches Objekt erstellt. Die Klammern können aber auch Argumente enthalten, die die Anfangswerte von Instanzvariablen oder andere Anfangsqualitäten des Objektes bestimmen:
Date dt = new Date(90, 4, 1, 4, 30);
Point pt = new Point(0, 0);
Zahl und Typ von Argumenten, die Sie mit new verwenden können, werden von der Klasse anhand einer speziellen Methode namens Konstruktor vorgegeben. Sie lernen später in dieser Woche noch alles über Konstruktor-Methoden. Wenn Sie versuchen eine Instanz einer Klasse über new mit der falschen Anzahl an Parametern (oder Sie geben keine Parameter an, obwohl welche erwartet werden) zu erzeugen, tritt ein Fehler auf, sobald Sie versuchen das Java-Programm zu kompilieren.
Von einigen Klassen können keine Instanzen ohne Argumente erstellt werden. Um sicherzugehen, überprüfen Sie die Klasse.
Betrachten wir als Beispiel die Date-Klasse, die Datumsobjekte erstellt. Listing 4.1 ist ein Java-Programm, das die drei verschiedenen Arten des Erstellens eines Date-Objekts mit new aufzeigt:
Listing 4.1: Lauras Date-Programm
1: import java.util.Date;
2:
3: class CreateDates {
4:
5: public static void main (String args[]) {
6: Date d1, d2, d3;
7:
8: d1 = new Date();
9: System.out.println("Date 1: " + d1);
10:
11: d2 = new Date(71, 7, 1, 7, 30);
12: System.out.println("Date 2: " + d2);
13:
14: d3 = new Date("April 3 1993 3:24 PM");
15: System.out.println("Date 3: " + d3);
16: }
17: }
Date 1: Sun Nov 26 19:10:56 PST 1995
Date 2: Sun Aug 01 07:30:00 PDT 1971
Date 3: Sat Apr 03 15:24:00 PST 1993
In diesem Beispiel werden anhand unterschiedlicher Argumente für new drei verschiedene Datumsangaben erstellt. Die erste Instanz (Zeile 8) benutzt new ohne Argumente, was ein Date-Objekt für das heutige Datum erstellt (siehe erste Zeile der Ausgabe).
Das zweite Date-Objekt dieses Beispiels hat fünf Ganzzahlargumente. Die Argumente stellen ein Datum dar: Jahr, Monat, Tag, Stunden und Sekunden. Wie die Ausgabe zeigt, wird dadurch ein Date-Objekt für einen bestimmten Tag erstellt: Sonntag, erster August 1971, 7.30 Uhr.
Die dritte Version von Date hat ein Argument eine Zeichenkette, die das Datum als Textkette darstellt. Beim Erstellen des Date-Objekts wird die Zeichenkette automatisch analysiert und ein Date-Objekt mit dem betreffenden Datum und Uhrzeit wird ausgegeben (siehe dritte Zeile der Ausgabe). Die Date-Zeichenkette kann viele verschiedene Formate haben. Informationen hierüber finden Sie in der API-Dokumentation über die Date-Klasse (Teil des Pakets java.util).
Bei Verwendung des new-Operators passieren mehrere Dinge: Erstens wird die neue Instanz der jeweiligen Klasse angelegt und Speicher dafür zugewiesen. Zweitens wird eine bestimmte Methode, die in der jeweiligen Klasse definiert ist, aufgerufen. Diese spezielle Methode nennt man Konstruktor.
Sie können in einer Klasse mehrere Konstruktor-Definitionen verwenden. Diese können sich jeweiils in der Zahl und dem Typ der Argumente unterscheiden. Beim Aufruf eines Konstruktors bei der Verwendung von new, wird dann anhand der übergebenen Argumente verschiedene der richtige Konstruktor für jene Argumente verwendet. Auf diese Weise können die verschiedenen Versionen von new, die oben aufgeführt wurden, unterschiedliche Aufgaben erfüllen.
Wenn Sie eigene Klassen anlegen, können Sie beliebig viele Konstruktoren definieren, um das Verhalten einer Klasse zu bestimmen. Am 7. Tag lernen Sie, wie Konstruktoren erstellt werden.
Speichermanagement ist in Java dynamisch und automatisch. Wenn Sie in Java ein neues Objekt erstellen, wird ihm automatisch die richtige Speichermenge zugeteilt. Sie brauchen für Objekte nicht explizit Speicherplatz zuteilen. Das übernimmt Java für Sie.
Wenn Sie ein Objekt nicht mehr benötigen, wird der diesem Objekt zugeteilte Speicher ebenfalls automatisch wieder freigegeben. Ein Objekt, das nicht mehr gebraucht wird, hat keine lebenden Referenzen mehr (es wird keinen Variablen mehr zugewiesen, die Sie noch verwenden oder die in Arrays gespeichert sind). Java hat einen Papierkorb, der nach unbenutzten Objekten sucht und den diesen Objekten zugeteilten Speicherplatz zurückfordert. Sie brauchen also Speicherplatz nicht explizit freistellen. Sie müssen nur sicherstellen, daß keine Objekte, die Sie loswerden wollen, irgendwo verwendet werden. Am 21. Tag lernen Sie alles über den Java-Papierkorb.
Nun haben Sie ein eigenes Objekt mit definierten Klassen- oder Instanzvariablen. Wie funktionieren diese Variablen? Ganz einfach! Klassen- und Instanzvariablen verhalten sich genauso wie die lokale Variablen, die Sie gestern gelernt haben. Lediglich die Bezugnahme auf sie unterscheidet sich geringfügig von den üblichen Variablen im Code.
Um den Wert einer Instanzvariablen auszulesen, verwenden Sie die Punkt-Notation.
Bei der Punkt-Notation hat die Referenz auf eine Instanz- oder Klassenvariable zwei Teile: Das Objekt auf der linken Seite des Punkts und die Variable rechts davon.
Ist beispielsweise ein Objekt der Variablen myObject zugewiesen und hat dieses Objekt eine Variable namens var, nehmen Sie auf den Wert dieser Variablen wie folgt Bezug:
myObject.var;
Diese Art des Zugreifens auf Variablen ist ein Ausdruck (der einen Wert ausgibt), und was auf beiden Seiten des Punkts steht, ist ebenfalls ein Ausdruck. Das bedeutet, daß Sie den Zugriff auf Instanzvariablen verschachteln können. Beinhaltet die var-Instanzvariable selbst ein Objekt und dieses Objekt eine eigene Instanzvariable namens state, können Sie wie folgt darauf Bezug nehmen:
myObject.var.state;
Punktausdrücke werden von links nach rechts ausgewertet, deshalb beginnen Sie mit der Variablen var von myObject, das auf ein anderes Objekt verweist, das die Variable state enthält. Letzt endlich erhalten Sie den Wert der state-Variablen.
Die Zuweisung eines Wertes zu dieser Variablen ist ebenso einfach. Sie setzen einfach einen Zuweisungsoperator rechts neben den Ausdruck:
myObject.var.state = true;
Listing 4.2 ist ein Beispiel eines Programms, das die Instanzvariablen in einem Point-Objekt testet und ändert. Point ist Teil des Pakets java.awt und bezieht sich auf einen Koordinatenpunkt mit einem x- und einem y-Wert.
Listing 4.2: Die TestPoint-Klasse
1: import java.awt.Point;
2:
3: class TestPoint {
4:
5: public static void main (String args[]) {
6: Point thePoint = new Point(10,10);
7:
8: System.out.println("X is " + thePoint.x);
9: System.out.println("Y is " + thePoint.y);
10:
11: System.out.println("Setting X to 5.");
12: thePoint.x = 5;
13: System.out.println("Setting y to 15.");
14: thePoint.y = 15;
15:
16: System.out.println("X is " + thePoint.x);
17: System.out.println("Y is " + thePoint.y);
18:
19: }
20: }
X is 10
Y is 10
Setting X to 5.
Setting y to 15.
X is 5
Y is 15
In diesem Beispiel erstellen Sie zuerst eine Instanz von Point, wobei X und Y 10 sind (Zeile 6). Die Zeilen 8 und 9 geben diese Einzelwerte aus, und Sie können die Punkt-Notation hier in Funktion sehen. Die Zeilen 11 bis 14 ändern die Werte dieser Variablen auf 5 bzw. 15. Die Zeilen 16 und 17 geben die Werte von X und Y in der geänderten Form wieder aus.
Wie Sie bereits gelernt haben, werden Klassenvariablen in der Klasse selbst definiert und gespeichert. Deshalb wirken sich ihre Werte auf die Klasse und alle ihre Instanzen aus.
Bei Instanzvariablen erhält jede neue Instanz der Klasse eine neue Kopie der Instanzvariablen, die diese Klasse definiert. Jede Instanz kann dann die Werte dieser Instanzvariablen ändern, ohne daß sich das auf andere Instanzen auswirkt. Bei Klassenvariablen gibt es nur eine Kopie der Variablen. Jede Instanz der Klasse hat Zugang zu der Variablen, jedoch gibt es nur einen Wert. Durch Änderung des Wertes dieser Variablen ändern sich die Werte aller Instanzen der betreffenden Klasse.
Sie deklarieren Klassenvariablen, indem Sie das Schlüsselwort static vor die Variable setzen. Sie lernen hierüber am 6. Tag mehr. Betrachten wir als Beispiel folgende teilweise Klassendefinition:
class FamilyMember {
static String surname = "Johnson";
String name;
int age;
...
}
Instanzen der Klasse FamilyMember haben je einen eigenen Wert für Name (name) und Alter (age). Die Klassenvariable Nachname (surname) hat aber nur einen Wert für alle Familienmitglieder. Ändern Sie surname, wirkt sich das auf alle Instanzen von FamilyMember aus.
Um auf Klassenvariablen zuzugreifen, benutzen Sie die gleiche Punkt-Notation wie bei Instanzvariablen. Um den Wert der Klassenvariablen auszulesen oder zu ändern, können Sie entweder die Instanz oder den Namen der Klasse links neben dem Punkt eingeben. Beide Ausgabezeilen in diesem Beispiel geben den gleichen Wert aus:
FamilyMember dad = new FamilyMember();
System.out.println("Family's surname is: " + dad.surname);
System.out.println("Family's surname is: " + FamilyMember.surname);
Da Sie eine Instanz benutzen können, um den Wert einer Klassenvariablen zu ändern, entsteht leicht Verwirrung über Klassenvariablen und darüber, wo der Wert herkommt (Sie erinnern sich, daß sich der Wert einer Klassenvariablen auf alle Instanzen auswirkt). Aus diesem Grund empfiehlt es sich, den Namen der Klasse zu verwenden, wenn auf eine Klassenvariable verwiesen wird. Dadurch wird der Code besser lesbar und Fehler lassen sich schneller finden.
Das Aufrufen von Methoden in Objekten läuft ähnlich ab wie die Bezugnahme auf seine Instanzvariablen: Auch in Methodenaufrufen wird die Punkt-Notation benutzt. Das Objekt, dessen Methode Sie aufrufen, steht links neben dem Punkt. Der Name der Methode und ihre Argumente stehen rechts neben dem Punkt:
myObject.methodOne(arg1, arg2, arg3);
Beachten Sie, daß nach jeder Methode Klammern folgen müssen, auch wenn die Methode keine Argumente hat:
myObject.methodNoArgs();
Führt die aufgerufene Methode zu einem Objekt, das selbst Methoden hat, können Sie Methoden wie Variablen verschachteln:
myObject.getClass().getName();
Sie können auch verschachtelte Methodenaufrufe und Referenzen auf Instanzvariablen kombinieren:
myObject.var.methodTwo(arg1, arg2);
System.out.println(), die Methode, die Sie bisher in diesem Buch benutzt haben, um Text auszugeben, ist ein gutes Beispiel für die Verschachtelung von Variablen und Methoden. Die System-Klasse (Teil des Pakets java.lang) beschreibt systemspezifisches Verhalten. System.out ist eine Klassenvariable, die eine Instanz der Klasse PrintStream enthält. Diese Instanz zeigt auf die Standardausgabe des Systems. PrintStream Instanzen enthalten die Methode println(), die eine Zeichenkette an diesen Ausgabestream schickt.
Listing 4.3 zeigt ein Beispiel des Aufrufens einiger Methoden, die in der String-Klasse definiert sind. Zeichenketten beinhalten Methoden zum Testen und Ändern von Zeichenketten auf ähnliche Weise, wie man von einer Zeichenkettenbibliothek in anderen Sprachen erwarten würde.
Listing 4.3: Mehrere Verwendungen von String-Methoden
1: class TestString {
2:
3: public static void main (String args[]) {
4: String str = "Now is the winter of our discontent";
5:
6: System.out.println("The string is: " + str);
7: System.out.println("Length of this string: "
8: + str.length());
9: System.out.println("The character at position 5: "
10: + str.charAt(5));
11: System.out.println("The substring from 11 to 18: "
12: + str.substring(11, 18));
13: System.out.println("The index of the character d: "
14: + str.indexOf('d'));
15: System.out.print("The index of the beginning of the ");
16: System.out.println("substring \"winter\":"
17: + str.indexOf("winter"));
18: System.out.println("The string in upper case: "
19: + str.toUpperCase());
20: }
21: }
The string is: Now is the winter of our discontent
Length of this string: 35
The character at position 5: s
The substring from positions 11 to 18: winter
The index of the character d: 25
The index of the beginning of the substring "winter": 11
The string in upper case: NOW IS THE WINTER OF OUR DISCONTENT
In Zeile 4 erstellen Sie eine neue Instanz von String durch Verwendung eines Zeichenkettenliterals (das ist einfacher, als wenn man new verwendet und die Zeichen dann einzeln eingibt). Der Rest des Programms ruft verschiedene String-Methoden auf, um verschiedene Operationen auf diese Zeichenkette auszuführen:
Klassenmethoden wirken sich wie Klassenvariablen auf die ganze Klasse, nicht auf ihre einzelnen Instanzen aus. Klassenmethoden werden üblicherweise für allgemeine Utility-Methoden benutzt, die nicht direkt auf eine Instanz der Klasse ausgeführt werden sollen, sondern lediglich vom Konzept her in diese Klasse passen. Die String-Klasse enthält beispielsweise die Klassenmethode valueOf(), die einen von vielen verschiedenen Argumenttypen (Ganzzahlen, boolesche Operatoren, andere Objekte usw.) verarbeiten kann. Die Methode valueOf() gibt dann eine neue Instanz von String aus, die den Zeichenkettenwert des Arguments enthält. Diese Methode wird nicht direkt auf eine vorhandene Instanz von String ausgeführt. Das Konvertieren eines Objekts oder Datentyps in einen String ist definitiv eine Operation, die in die String-Klasse paßt. Deshalb ist es sinnvoll, sie gleich in der String-Klasse zu definieren.
Klassenmethoden sind auch nützlich, um allgemeine Methoden an einer Stelle (der Klasse) zusammenzufassen. Die Math-Klasse, die im Paket java.lang enthalten ist, umfaßt beispielsweise zahlreiche mathematische Operationen als Klassenmethoden. Es gibt keine Instanzen der Klasse Math. Trotzdem können Sie ihre Methoden mit numerischen oder booleschen Argumenten verwenden.
Um eine Klassenmethode aufzurufen, benutzen Sie die Punkt-Notation wie bei Instanzmethoden. Ebenso wie bei Klassenvariablen können Sie entweder eine Instanz der Klasse oder die Klasse selbst links neben den Punkt setzen. Allerdings ist die Verwendung des Namens der Klasse für Klassenvariablen aus den gleichen Gründen, die im Zusammenhang mit Klassenvariablen erwähnt wurden, empfehlenswert, da der Code dadurch übersichtlicher wird. Die letzten zwei Zeilen dieses Beispiels produzieren das gleiche Ergebnis:
String s, s2;
s = "foo";
s2 = s.valueOf(5);
s2 = String.valueOf(5);
Wie bei der Arbeit mit Objekten ist die Verwendung von Referenzen, die auf diese Objekte zeigen, ein wichtiger Aspekt. Wenn Sie Variablen für Objekte zuweisen oder Objekte als Argumente an Methoden weiterreichen, legen Sie Referenzen auf diese Objekte fest. Die Objekte selbst oder Kopien davon werden dabei nicht weitergereicht.
Ein Beispiel soll dies verdeutlichen. Sehen Sie sich den Code in Listing 4.4 an.
Listing 4.4: Ein Beispiel für Referenzen
import java.awt.Point;
class ReferencesTest {
public static void main (String args[]) {
Point pt1, pt2;
pt1 = new Point(100, 100);
pt2 = pt1;
pt1.x = 200;
pt1.y = 200;
System.out.println("Point1: " + pt1.x + ", " + pt1.y);
System.out.println("Point2: " + pt2.x + ", " + pt2.y);
}
}
In diesem Programm werden zwei Variablen vom Typ Point deklariert, und ein neues Point-Objekt wird pt1 zugewiesen. Danach wird der Wert von pt1 pt2 zugewiesen.
Hier ist die Herausforderung: Wie sieht pt2 aus, nachdem die Instanzvariablen x und y von pt1 geändert wurden?
Nachfolgend die Ausgabe dieses Programms:
Point1: 200, 200
Point2: 200, 200
Wie Sie sehen, hat sich auch pt2 geändert. Beim Zuweisen des pt1-Wertes auf pt2 wurde eine Referenz von pt2 auf das gleiche Objekt, auf das sich pt1 bezieht, erstellt. Durch Änderung des Objekts, auf das sich pt2 bezieht, ändert sich auch das Objekt, auf das pt1 zeigt, weil beide Referenzen auf das gleiche Objekt zeigen.
Abbildung 4.1:
|
Die Tatsache, daß Java Referenzen benutzt, gewinnt besondere Bedeutung, wenn Sie Argumente an Methoden weiterreichen. Sie lernen hierüber noch in dieser Lektion mehr.
Eventuell haben Sie in einem Ihrer Java-Programme irgendwo einen Wert mit dem falschen Typ gespeichert. Vielleicht befindet sich eine Instanz in der falschen Klasse oder ein float soll eigentlich int sein oder umgekehrt. Um den Wert von einem Typ in einen anderen zu konvertieren, wenden Sie in Java einen Mechanismus namens Casting an.
Obwohl das Casting-Konzept an sich einfach ist, werden die Regeln, die bestimmen, welche Typen in Java in andere konvertiert werden können, durch die Tatsache komplizierter, daß Java sowohl primitive Typen (int, float, boolean) als auch Objekttypen (String, Point, Window usw.) hat. Aufgrund dieser drei Typen gibt es drei Formen von Casting und Umwandlungen, über die wir in dieser Lektion sprechen:
Durch Konvertieren zwischen primitiven Typen können Sie den Wert eines Typs in einen anderen primitiven Typ umwandeln, z.B. um eine Zahl eines Typs einer Variablen zuzuweisen, die auf einem anderen Typ basiert. Primitive Typen werden am häufigsten in numerische Typen konvertiert. Boolesche Werte können nicht in einen anderen Primitivtyp konvertiert werden. Sie können aber 1 oder 0 in boolesche Werte konvertieren.
Ist der angestrebte Typ »größer« als der zu konvertierende Typ, müssen Sie eventuell kein explizites Casting anwenden. Meist kann ein Byte oder ein Zeichen automatisch z.B. als int, oder ein int als long, ein int als float oder etwas anderes als double behandelt werden. In diesem Fall gehen beim Konvertieren des Wertes keine Informationen verloren, weil der größere Typ mehr Genauigkeit bietet als der kleinere.
Um einen großen Wert auf einen kleineren Typ zu konvertieren, müssen Sie Casting explizit anwenden, weil bei dieser Umsetzung der Wert an Genauigkeit einbüßen kann. Explizites Casting sieht so aus:
(Typname) Wert
In dieser Form ist Typname der Name des Typs, auf den Sie konvertieren (z.B. short, int, float, boolean), und Wert ist ein Ausdruck, der den zu konvertierenden Wert ergibt. Dieser Ausdruck teilt den Wert von x durch den Wert von y und wandelt das Ergebnis in int um:
(int) (x / y);
Da Casting eine höhere Präzedenz hat als Arithmetik, müssen Sie Klammern eingeben, damit das Ergebnis der Division an das konvertierte int übergeben wird.
Mit einer Einschränkung können auch Klasseninstanzen in Instanzen anderer Klassen konvertiert werden: Beide betroffenen Klassen müssen durch Vererbung miteinander verbunden sein. Das bedeutet, daß Sie ein Objekt nur in eine Instanz der Sub- oder Superklassen seiner Klasse konvertieren können, nicht aber in eine beliebige Klasse.
Wie beim Konvertieren eines primitiven Wertes in einen größeren Typ müssen bestimmte Objekte nicht unbedingt explizit konvertiert zu werden. Insbesondere, weil die Subklassen von Instanzen normalerweise alle Informationen ihrer Superklassen enthalten, können Sie eine Instanz einer Subklasse irgendwo dort verwenden, wo eine Superklasse erwartet wird. Nehmen wir an, Sie haben eine Methode mit zwei Argumenten: eines vom Typ Object und eines vom Typ Number. Sie müssen nun nicht Instanzen dieser beiden Klassen an die Methode übergeben. Für das Object-Argument können Sie jede beliebige Subklasse von Object (anders ausgedrückt, jedes Objekt) und für das Number-Argument jede Instanz einer beliebigen Subklasse von Number (int, boolean, float usw.) weitergeben.
Durch Konvertieren eines Objekts in eine Instanz einer Superklasse dieses Objekts gehen die Informationen, die die ursprüngliche Subklasse bereitgestellt hat, verloren. Außerdem ist spezifisches Casting erforderlich. Um ein Objekt in eine andere Klasse zu konvertieren, wenden Sie die gleiche Casting-Operation wie bei den Grundtypen an:
(Klassenname) Objekt
In diesem Fall ist Klassenname der Name der Klasse, in die Sie das Objekt konvertieren wollen, und Objekt ist eine Referenz auf das konvertierte Objekt. Casting erstellt eine neue Instanz der neuen Klasse mit allen Informationen, die das alte Objekt enthielt. Das alte Objekt besteht unverändert fort.
Nachfolgend ein fiktives Beispiel, in dem eine Instanz der Klasse GreenApple in eine Instanz der Klasse Apple konvertiert wird (wobei GreenApple theoretisch eine Subklasse von Apple ist):
GreenApple a;
Apple a2;
a = new GreenApple();
a2 = (Apple) a;
Abgesehen vom Konvertieren von Objekten in Klassen können Sie auch Objekte in Schnittstellen konvertieren, jedoch nur, wenn die Klasse oder eine Superklasse des Objekts die Schnittstelle implementiert. Durch Casting eines Objekts in eine Schnittstelle können Sie dann eine der Methoden dieser Schnittstelle aufrufen, auch wenn die Klasse des Objekts diese Schnittstelle nicht direkt implementiert. In Woche 3 lernen Sie mehr über Schnittstellen.
Sie wissen jetzt, wie man einen primitiven Typ in einen anderen primitiven Typ und Objekte zwischen Klassen konvertiert. Wie kann man nun Primitivtypen in Objekte konvertieren?
Überhaupt nicht! Primitive Typen und Objekte sind in Java zwei völlig verschiedene Dinge, die nicht untereinander konvertiert und auf die kein automatisches Casting anwendbar ist. Allerdings enthält das Paket java.lang mehrere Sonderklassen, die je einem primitiven Datentyp entsprechen: Integer, Float, Boolean usw. Mit den in diesen Klassen definierten Klassenmethoden können Sie anhand von new für alle Primitivtypen ein Gegenstück zu einem Objekt erstellen. Die folgenden Codezeilen erstellen eine Instanz der Klasse Integer mit dem Wert 35:
Integer intObject = new Integer(35);
Da Sie Objekte verfügbar haben, können Sie diese Werte als Objekte behandeln. Möchten Sie die primitiven Werte zurückkonvertieren, gibt es auch dafür Methoden, z.B. intValue(). Diese Methode extrahiert einen primitiven int-Wert aus einem Integer-Objekt:
int theInt = intObject.intValue(); // gibt 35 aus
Schlagen Sie in der Java-API-Dokumentation über diese speziellen Klassen nach. Sie finden dort Erklärungen der Methoden zum Konvertieren von Primitivtypen in Objekte und umgekehrt.
In diesem Abschnitt werden verschiedene andere Informationen über das Arbeiten mit Objekten aufgeführt, insbesondere:
Gestern haben Sie Operatoren zum Vergleichen von Werten kennengelernt: gleich, ungleich, kleiner als usw. Die meisten dieser Operatoren funktionieren nur mit Primitivtypen, nicht mit Objekten. Falls Sie versuchen, andere Werte als Operanden zu verwenden, gibt der Java-Compiler Fehler aus.
Die Ausnahme zu dieser Regel bilden Operatoren für Gleichheit: == (gleich) und != (ungleich). Diese Operatoren können für Objekte benutzt werden, um zu prüfen, ob sich zwei Operanden auf genau das gleiche Objekt beziehen.
Was müssen Sie tun, um Instanzen Ihrer Klasse zu vergleichen und aussagefähige Ergebnisse zu erzielen? Sie müssen spezielle Methoden in Ihre Klasse einbinden und diese Methoden anhand der Methodennamen aufrufen.
Ein gutes Beispiel dafür ist die String-Klasse. Hier sind zwei Zeichenketten, d.h. zwei unabhängige Objekte im Speicher mit den gleichen Werten möglich, also mit den gleichen Zeichen in der gleichen Reihenfolge. Nach dem Operator == sind diese zwei String-Objekte aber nicht gleich, weil sie zwar den gleichen Inhalt haben, aber nicht die gleichen Objekte sind.
Die String-Klasse definiert die Methode equals(), die jedes Zeichen in der Zeichenkette prüft und true ausgibt, wenn die zwei Zeichenketten die gleichen Werte haben. Dies wird in Listing 4.4 verdeutlicht.
Listing 4.5: Zeichenketten testen
1: class EqualsTest {
2:
3: public static void main (String args[]) {
4: String str1, str2;
5: str1 = "She sells sea shells by the sea shore.";
6: str2 = str1;
7:
8: System.out.println("String1: " + str1);
9: System.out.println("String2: " + str2);
10: System.out.println("Same object? " + (str1 == str2));
11:
12: str2 = new String(str1);
13:
14: System.out.println("String1: " + str1);
15: System.out.println("String2: " + str2);
16: System.out.println("Same object? " + (str1 == str2));
17: System.out.println("Same value? " + str1.equals(str2));
18: }
19: }
Ausgabe:
String1: she sells sea shells by the sea shore.
String2: she sells sea shells by the sea shore.
Same object? true
String1: she sells sea shells by the sea shore.
String2: she sells sea shells by the sea shore.
Same object? false
Same value? true
Der erste Teil dieses Programms (Zeilen 4 bis 6) deklariert die zwei Variablen str1 und str2, weist das Literal She sells sea shells by the sea shore. str1 und dann diesen Wert str2 zu. Wie Sie von Objektreferenzen her wissen, zeigen str1 und str2 jetzt auf das gleiche Objekt. Das beweist der Test in Zeile 10.
Im zweiten Teil wird ein neues String-Objekt mit dem Wert von str1 erstellt. Jetzt bestehen zwei verschiedene String-Objekte mit dem gleichen Wert. Sie werden mit dem Operator == (in Zeile 16) geprüft, um zu ermitteln, ob sie das gleiche Objekt sind. Die erwartete Antwort wird ausgegeben. Schließlich erfolgt das Prüfen mit der equals-Methode (in Zeile 17), um ihre Werte zu vergleichen.
Möchten Sie die Klasse eines Objekts ermitteln? Hier ist eine Möglichkeit, dies bei einem Objekt zu erreichen, das der Variablen obj zugewiesen ist:
String name = obj.getClass().getName();
Was geschieht hier? Die Methode getClass() ist in der Klasse Object definiert und als solche für alle Objekte verfügbar. Das Ergebnis dieser Methode ist ein Class-Objekt (wobei Class selbst eine Klasse ist), die die Methode getName() hat. getName() gibt den Namen der Klasse als Zeichenkette aus.
Einen anderen nützlichen Test bietet der Operator instanceof. instanceof hat zwei Operanden: Ein Objekt links und den Namen einer Klasse rechts. Der Ausdruck gibt true oder false aus, je nach dem, ob das Objekt eine Instanz der benannten Klasse oder eines der Superklassen dieser Klasse ist:
"foo" instanceof String // true
Point pt = new Point(10,10);
pt instanceof String // false
Der Operator instanceof kann auch für Schnittstellen benutzt werden. Falls ein Objekt eine Schnittstelle implementiert, gibt der instanceof-Operator mit einem Schnittstellennamen auf der rechten Seite true aus. In Woche 3 lernen Sie alles über Schnittstellen.
Eine der Verbesserungen in Java Release 1.1 ist die Einführung von Reflektion, auch als Introspection bezeichnet. Reflektion bzw. Introspection aktiviert eine Java-Klasse beispielsweise ein von Ihnen geschriebenes Programm und lernt dann eine beliebige andre Klasse kennen.
Mit Reflektion kann ein Java-Programm eine Klasse laden, von der es nichts weiß, die Variablen, Methoden und Konstruktoren dieser Klasse ermitteln und damit arbeiten.
Das macht wahrscheinlich mehr Sinn, wenn Sie das am Beispiel sehen . Listing 4.6 ist eine kleine Java-Applikation namens SeeMethods.
Listing 4.6: Der komplette Text von SeeMethods.java.
1: import java.lang.reflect.*;
2: import java.util.Random;
3:
4: class SeeMethods {
5: public static void main(String[] argumentss) {
6: Random rd = new Random();
7: Class className = rd.getClass();
8: Method[] methods = className.getMethods();
9: for (int i = 0; i < methods.length; i++) {
10: System.out.println("Method: " + methods[i]);
11: }
12: }
13: }
In diesem Programm wird die java.lang.reflect.*-Klassengruppe genutzt, die Informationen über die Attribute, Methoden und Konstruktor-Methoden beliebiger Klassen liefert.
Die Applikation SeeMethods erzeugt in Zeile 6 ein Random-Objekt und nutzt dann Reflektion zur Anzeige aller öffentlichen Methoden, die ein Teil dieser Klasse sind. Listing 4.7 zeigt die Ausgabe der Applikation.
Listing 4.7: Die Ausgabe der Applikation SeeMethods.
1: Method: public final native java.lang.Class java.lang.Object.getClass()
2: Method: public native int java.lang.Object.hashCode()
3: Method: public boolean java.lang.Object.equals(java.lang.Object)
4: Method: public java.lang.String java.lang.Object.toString()
5: Method: public final native void java.lang.Object.notify()
6: Method: public final native void java.lang.Object.notifyAll()
7: Method: public final native void java.lang.Object.wait(long)
8: Method: public final void java.lang.Object.wait(long,int)
9: Method: public final void java.lang.Object.wait()
10: Method: public synchronized void java.util.Random.setSeed(long)
11: Method: public void java.util.Random.nextBytes(byte[])
12: Method: public int java.util.Random.nextInt()
13: Method: public long java.util.Random.nextLong()
14: Method: public float java.util.Random.nextFloat()
15: Method: public double java.util.Random.nextDouble()
16: Method: public synchronized double java.util.Random.nextGaussian()
Durch Reflektion kann die Applikation SeeMethods die einzelnen Methoden der Random-Klasse und alle Methoden, die sie von Random übergeordneten Klassen geerbt hat, kennenlernen. Jede Zeile im Listing zeigt folgende Informationen über eine Methode:
Die Applikation SeeMethods kann mit jeder Objektklasse ausgeführt werden ändern Sie die Zeile 6 in SeeMethods.java, um ein anderes Objekt zu erzeugen und einen Blick in sein Inneres zu werfen.
Am häufigsten wird Reflektion von Tools wie beispielsweise Klassen-Browser und -Debugger eingesetzt, um mehr über die Klassen der durchsuchten oder getesteten Objektklasse zu erfahren. Außerdem wird Reflektion im Zusammenhang mit Java Beans eingesetzt. Hier ist es bei der Erstellung größerer Anwendungen hilfreich, daß ein Objekt ein anderes Objekt abfragen kann, was es machen kann (und es dann auffordern kann, etwas zu tun). Über Java Beans erfahren Sie noch mehr während Tag 14, »Vernetzung, Fenster und verschiedene Leckerbissen».
Das java.lang.reflect-Paket umfaßt folgende Klassen:
Darüber hinaus sind eine Reihe neuer Methoden in einer Objektkasse namens Class verfügbar, die helfen, die verschiedenen Reflektion-Klassen zusammenzuhalten.
Reflektion ist eine fortgeschrittenes Feature, das Sie nicht einfach so in Ihren Programmen einsetzen. Es wird dann besonders nützlich, wenn Sie mit Objektfolgen, Java Beans und anderen ausgeklügelten Java-Programmiertechniken arbeiten.
Wir beenden die heutige Lektion mit einem Blick auf die Java-Klassenbibliothek. Sie haben inzwischen einige Klassen davon kennengelernt, deshalb sind sie Ihnen nicht mehr ganz fremd.
Die Java-Klassenbibliothek enthält Klassen, die garantiert in jeder kommerziellen Java-Umgebung (z.B. HotJava oder Netscape 2.0) verfügbar sind. Diese Klassen befinden sich im Java-Paket und enthalten alle bisher in diesem Buch behandelten Klassen sowie viele weitere, über die Sie in späteren Lektionen mehr erfahren (das Buch deckt aber nicht alle Klassen ab).
Das Java Developer's Kit wird mit kompletter Dokumentation über die Java-Klassenbibliothek ausgeliefert. Unter anderem werden dort Instanzvariablen, Methoden, Konstruktoren, Schnittstellen usw. aller Klassen beschrieben. Ferner befindet sich in Anhang B eine Übersicht über das Java-API. Die Erforschung der Java-Klassenbibliothek ist eine der besten Methoden, um herauszufinden, was Java alles kann, wo die Grenzen der Sprache liegen, und bildet einen Ausgangspunkt für eigene Entwicklungen.
Die Java-Klassenbibliothek enthält folgende Klassenpakete:
Zusätzlich zu den Java-Klassen kann Ihre Entwicklungsumgebung auch weitere Klassen enthalten, die andere Utilities oder Funktionen bieten. Diese Klassen können zwar nützlich sein, stehen aber anderen, die Ihr Java-Programm ausführen wollen, nicht zur Verfügung, weil sie nicht Teil der Java-Standardbibliothek sind. Das ist besonders bei Applets wichtig, weil Applets naturgemäß auf jeder Plattform mit jedem beliebigen javafähigen Browser laufen sollen. Nur Klassen aus dem Java-Paket stehen garantiert allen Browsern und Java-Umgebungen zur Verfügung.
Objekte, Objekte und nochmals Objekte. Heute haben sie alles über Objekte gelernt: Wie sie erstellt, wie die Werte ihrer Variablen ermittelt und geändert und wie ihre Methoden aufgerufen werden. Außerdem haben sie gelernt, wie man Objekte kopiert und vergleicht, und wie sie in andere Objekte konvertiert werden können. Schließlich haben Sie etwas über die Java-Klassenbibliothek erfahren, die Ihnen unzählige Möglichkeiten zum Entwickeln eigener Programme liefert.
Sie haben inzwischen die Grundlagen erworben, um mit einfachen Dingen in der Java-Sprache umzugehen. Was noch übrig bleibt, sind Arrays, Bedingungen und Schleifen. Das lernen Sie morgen. Am 6. Tag lernen Sie, wie man Klassen in Java-Anwendungen definiert und verwendet. Nächste Woche stürzen Sie sich direkt auf Applets. Jedenfalls haben Sie bei fast allem, was Sie in Ihren Java-Programmen durchführen, immer wieder mit Objekten zu tun.
F Mir ist der Unterschied zwischen Objekten und den primitiven Datentypen, z.B. int und boolean, noch nicht ganz klar.
A Die primitiven Typen (byte, short, int, long, float, double und char) sind die kleinsten Elemente der Sprache Java. Es sind keine Objekte, obwohl sie auf vielerlei Art wie Objekte gehandhabt werden. Sie können Variablen zugewiesen und zwischen Methoden weitergereicht werden. Die meisten Operationen werden aber auf Objekte ausgeführt.
Objekte stellen normalerweise Klasseninstanzen dar und sind daher viel komplexere Datentypen als einfache Zahlen und Zeichen. Sie enthalten aber meist Zahlen und Zeichen als Instanz- oder Klassenvariablen.
F Mit den Beispielen zum Aufrufen einer Methode mit je einer anderen Zahl von Argumenten in dem Abschnitt über das Aufrufen von Methoden habe ich jedesmal ein anderes Ergebnis erhalten. Wie ist das möglich?
A Das nennt man Methoden-Overloading. Durch Overloading kann der gleiche Funktionsname aufgrund des Arguments, mit dem er aufgerufen wird, ein anderes Verhalten haben, und sowohl Anzahl als auch Typ von Argumenten können variieren. In Ihren eigenen Klassen definieren Sie separate Methodensignaturen mit unterschiedlichen Argumenten und Definitionen. Wird diese Methode aufgerufen, ermittelt Java anhand der Zahl von Argumenten und deren Typ, mit denen Sie sie aufgerufen haben, welche Definition auszuführen ist.
Sie lernen alles darüber am 6. Tag.
F Warum können Operatoren in Java nicht überladen werden? Da Java angeblich auf C++ basiert, ist das seltsam.
A Java basiert tatsächlich auf C++, wurde aber mit dem Ziel entwickelt einfach zu sein, deshalb wurden viele C++-Merkmale außer Acht gelassen. Das Operator-Overloading wurde weggelassen, weil der Operator mit jeder beliebigen Bedeutung definiert werden kann. Dadurch wird es schwierig, zu ermitteln, was ein bestimmter Operator zu einem bestimmten Zeitpunkt macht. Das kann im Extremfall zu einem völlig unlesbaren Code führen. Angesichts des potentiellen Mißbrauchs waren sich die Java-Entwickler einig darüber, gerade dieses Merkmal von C++ wegzulassen.
(c) 1997 SAMS